diff --git a/.claude/settings.json b/.claude/settings.json index f530da8..9b26631 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,7 +2,13 @@ "permissions": { "allow": [ "Bash(git add *)", - "Bash(python3 -c \"import ast; ast.parse\\(open\\('main.py'\\).read\\(\\)\\)\")" + "Bash(python3 -c \"import ast; ast.parse\\(open\\('main.py'\\).read\\(\\)\\)\")", + "WebFetch(domain:raw.githubusercontent.com)", + "WebSearch", + "Bash(find /private/tmp/claude-502/-Users-p4piwabl0-Desktop-projects-virtual-controller-clean/29ede664-3f90-4b9a-a462-57bbd9febfef/scratchpad/helgobox -maxdepth 1 -iname target -o -maxdepth 1 -iname *.gitmodules)", + "Bash(SRC=/private/tmp/claude-502/-Users-p4piwabl0-Desktop-projects-virtual-controller-clean/29ede664-3f90-4b9a-a462-57bbd9febfef/scratchpad/helgobox *)", + "Bash(find /Users/p4piwabl0/Desktop/projects/virtual-controller-clean/plugin-reaper-realearn -iname .env -not -iname *.example)", + "Bash(git commit -m ' *)" ] } } diff --git a/plugin-reaper-realearn/.cargo/config.toml b/plugin-reaper-realearn/.cargo/config.toml new file mode 100644 index 0000000..3b7e5fa --- /dev/null +++ b/plugin-reaper-realearn/.cargo/config.toml @@ -0,0 +1,23 @@ +# `+crt-static` enables static linking to C runtime. +# +# In MSVC this would be: "C/C++ => Code Generation => Multithreaded (/MT) instead of Multithreaded-DLL (/MD)"". +# That links the Visual C++ Redistributable stuff statically. So no missing "msvcr*.dll". + +[target.i686-pc-windows-msvc] +rustflags = ["-Ctarget-feature=+crt-static"] + +[target.x86_64-pc-windows-msvc] +rustflags = ["-Ctarget-feature=+crt-static"] + +# This was an attempt to be compatible with older Linux versions (older libc). +# Doesn't work for now because of: https://github.com/rust-lang/rust/issues/78210 +# Also see https://github.com/helgoboss/helgobox/issues/829 for latest ideas. +# TODO-medium Try again later. Not urgent. +#[target.aarch64-unknown-linux-gnu] +#rustflags = ["-Ctarget-feature=+crt-static"] +# +#[target.armv7-unknown-linux-gnueabihf] +#rustflags = ["-Ctarget-feature=+crt-static"] +# +#[target.x86_64-unknown-linux-gnu] +#rustflags = ["-Ctarget-feature=+crt-static"] \ No newline at end of file diff --git a/plugin-reaper-realearn/.env.example b/plugin-reaper-realearn/.env.example new file mode 100644 index 0000000..3304afc --- /dev/null +++ b/plugin-reaper-realearn/.env.example @@ -0,0 +1,6 @@ +# A build with features "playtime" and "licensing" enabled must have the following +# environment variables set. + +PLAYTIME_LICENSE_VERIFYING_KEY= +PLAYTIME_PRESET_VERIFYING_KEY= +PLAYTIME_AUTHENTICITY_SIGNING_KEY= \ No newline at end of file diff --git a/plugin-reaper-realearn/.gitattributes b/plugin-reaper-realearn/.gitattributes new file mode 100644 index 0000000..cc6d6b6 --- /dev/null +++ b/plugin-reaper-realearn/.gitattributes @@ -0,0 +1,4 @@ +* text=auto + +# REAPER projects always seem to use CRLF +*.RPP text eol=crlf \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/FUNDING.yml b/plugin-reaper-realearn/.github/FUNDING.yml new file mode 100644 index 0000000..a37cf58 --- /dev/null +++ b/plugin-reaper-realearn/.github/FUNDING.yml @@ -0,0 +1,3 @@ +liberapay: helgoboss +custom: + - "https://paypal.me/helgoboss" \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/ISSUE_TEMPLATE/bug_report.md b/plugin-reaper-realearn/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..8eeca78 --- /dev/null +++ b/plugin-reaper-realearn/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Use this if something doesn't work +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. macOS] + - Version [e.g. Ventura 13.6.7] + +**Additional context** +Add any other context about the problem here. diff --git a/plugin-reaper-realearn/.github/ISSUE_TEMPLATE/config.yml b/plugin-reaper-realearn/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..56abbd0 --- /dev/null +++ b/plugin-reaper-realearn/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Question + url: https://github.com/helgoboss/helgobox/discussions/categories/q-a + about: "GitHub Discussions: Ask or answer a question" + - name: Idea + url: https://github.com/helgoboss/helgobox/discussions/categories/ideas?discussions_q=category%3AIdeas+is%3Aopen+sort%3Adate_created+ + about: "GitHub Discussions: Request a new feature or suggest an improvement (please watch out for existing issues first and vote for them)" + - name: General feedback + url: https://github.com/helgoboss/helgobox/discussions/categories/general + about: "GitHub Discussions: Give general feedback" \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/actions/build/action.yml b/plugin-reaper-realearn/.github/actions/build/action.yml new file mode 100644 index 0000000..2f32f6f --- /dev/null +++ b/plugin-reaper-realearn/.github/actions/build/action.yml @@ -0,0 +1,50 @@ +name: Build +description: Build and optionally Rust code +inputs: + args: + description: 'Cargo arguments' + required: false + default: '' + targets: + description: 'Additional Rust targets' + required: false + default: '' + test: + description: 'Test?' + required: false + default: 'true' +runs: + using: "composite" + steps: + - name: Install Rust + uses: dtolnay/rust-toolchain@1.84.0 + with: + targets: ${{ inputs.targets }} + + - name: Set up dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install nasm php libudev-dev libxdo-dev libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev libspeechd-dev libgtk-3-dev + shell: sh + - name: Set up dependencies + run: brew install php + if: runner.os == 'macOS' + shell: sh + + - name: Rust cache + uses: swatinem/rust-cache@v2 + + - name: cargo build + env: + ARGS: ${{ inputs.args }} + run: cargo build $ARGS + shell: sh + + - name: cargo test + if: ${{ inputs.test == 'true' }} + env: + RUST_MIN_STACK: 5242880 + ARGS: ${{ inputs.args }} + run: cargo test $ARGS -- --nocapture + shell: sh \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/actions/check/action.yml b/plugin-reaper-realearn/.github/actions/check/action.yml new file mode 100644 index 0000000..2a57249 --- /dev/null +++ b/plugin-reaper-realearn/.github/actions/check/action.yml @@ -0,0 +1,30 @@ +name: Check +description: Check Rust code via Clippy +inputs: + args: + description: 'Cargo arguments' + required: false + default: '' +runs: + using: "composite" + steps: + - name: Install Rust + uses: dtolnay/rust-toolchain@1.84.0 + with: + components: clippy + + - name: Set up dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install nasm php libudev-dev libxdo-dev libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev libspeechd-dev libgtk-3-dev + shell: sh + + - name: Rust cache + uses: swatinem/rust-cache@v2 + + - name: cargo clippy + env: + ARGS: ${{ inputs.args }} + run: cargo clippy $ARGS -- -D warnings + shell: sh \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/actions/fmt/action.yml b/plugin-reaper-realearn/.github/actions/fmt/action.yml new file mode 100644 index 0000000..61c8957 --- /dev/null +++ b/plugin-reaper-realearn/.github/actions/fmt/action.yml @@ -0,0 +1,16 @@ +name: fmt +description: Format Rust code via rustfmt +runs: + using: "composite" + steps: + - name: Install Rust + uses: dtolnay/rust-toolchain@1.84.0 + with: + components: rustfmt + + - name: Rust cache + uses: swatinem/rust-cache@v2 + + - name: cargo fmt + run: cargo fmt --all -- --check + shell: sh \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/actions/post-checkout/action.yml b/plugin-reaper-realearn/.github/actions/post-checkout/action.yml new file mode 100644 index 0000000..810c134 --- /dev/null +++ b/plugin-reaper-realearn/.github/actions/post-checkout/action.yml @@ -0,0 +1,24 @@ +name: Post-checkout +description: Checkout with Playtime Engine +inputs: + playtime-engine-deploy-key: + description: 'Playtime Engine deploy key' + required: true + license-processor-deploy-key: + description: 'License Processor deploy key' + required: true +runs: + using: "composite" + steps: + # Checkout with Playtime Engine + - name: Setup ssh-agent + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: | + ${{ inputs.playtime-engine-deploy-key }} + ${{ inputs.license-processor-deploy-key }} + + - name: Update submodules + run: | + git submodule update --init + shell: sh diff --git a/plugin-reaper-realearn/.github/workflows/linux-aarch64.yml b/plugin-reaper-realearn/.github/workflows/linux-aarch64.yml new file mode 100644 index 0000000..4faa316 --- /dev/null +++ b/plugin-reaper-realearn/.github/workflows/linux-aarch64.yml @@ -0,0 +1,42 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + # For quickly detecting important differences in runner configurations + schedule: + - cron: '0 5 * * MON' + +name: Linux aarch64 + +jobs: + check: + name: Check + runs-on: ubuntu-22.04-arm + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/check + with: + args: '--features playtime' + + test: + name: Test + runs-on: ubuntu-22.04-arm + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/build + with: + args: '--features playtime' \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/workflows/linux-x86_64.yml b/plugin-reaper-realearn/.github/workflows/linux-x86_64.yml new file mode 100644 index 0000000..4b61157 --- /dev/null +++ b/plugin-reaper-realearn/.github/workflows/linux-x86_64.yml @@ -0,0 +1,54 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + # For quickly detecting important differences in runner configurations + schedule: + - cron: '0 5 * * MON' + +name: Linux x86_64 + +jobs: + check: + name: Check + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/check + with: + args: '--features playtime' + + test: + name: Test + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/build + with: + args: '--features playtime' + + fmt: + name: Rustfmt + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/fmt diff --git a/plugin-reaper-realearn/.github/workflows/macos-aarch64.yml b/plugin-reaper-realearn/.github/workflows/macos-aarch64.yml new file mode 100644 index 0000000..ef000e4 --- /dev/null +++ b/plugin-reaper-realearn/.github/workflows/macos-aarch64.yml @@ -0,0 +1,31 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + # For quickly detecting important differences in runner configurations + schedule: + - cron: '0 5 * * MON' + +name: macOS aarch64 + +jobs: + test: + name: Build + runs-on: macos-14 + env: + # The first aarch64 target + MACOSX_DEPLOYMENT_TARGET: 11.0 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/build + with: + args: '--features playtime,egui' \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/workflows/macos-x86_64.yml b/plugin-reaper-realearn/.github/workflows/macos-x86_64.yml new file mode 100644 index 0000000..9809176 --- /dev/null +++ b/plugin-reaper-realearn/.github/workflows/macos-x86_64.yml @@ -0,0 +1,30 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + # For quickly detecting important differences in runner configurations + schedule: + - cron: '0 5 * * MON' + +name: macOS x86_64 + +jobs: + check: + name: Check + runs-on: macos-14 + env: + MACOSX_DEPLOYMENT_TARGET: 10.9 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/build + with: + args: '--features playtime,egui' \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/workflows/release.yml b/plugin-reaper-realearn/.github/workflows/release.yml new file mode 100644 index 0000000..9dc9c68 --- /dev/null +++ b/plugin-reaper-realearn/.github/workflows/release.yml @@ -0,0 +1,193 @@ +on: + create: + ref_type: tag + ref: 'v*' + workflow_dispatch: + +name: Create release + +jobs: + + build-release-artifacts: + name: Build artifact + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + + - artifact: windows-x86_64 + os: windows-2022 + lib_file_name: helgobox.dll + extension_file_name: reaper_helgobox.dll + target: x86_64-pc-windows-msvc + profile: release + features: "playtime,egui,licensing" + macosx_deployment_target: "" + lib_file_name_dst: helgobox-windows-x86_64.dll + extension_file_name_dst: reaper_helgobox-windows-x86_64.dll + + - artifact: windows-i686 + os: windows-2022 + lib_file_name: helgobox.dll + extension_file_name: reaper_helgobox.dll + target: i686-pc-windows-msvc + profile: release-llvm-out-of-memory-fix + features: "egui" + lib_file_name_dst: helgobox-windows-i686.dll + extension_file_name_dst: reaper_helgobox-windows-i686.dll + + - artifact: macos-x86_64 + os: macos-14 + lib_file_name: libhelgobox.dylib + extension_file_name: libreaper_helgobox.dylib + target: x86_64-apple-darwin + profile: release-strip + features: "playtime,egui,licensing" + # The minimum version that I tested with is 10.13. + # Writing 10.7 builds, but it won't run, for example, on 10.14 (weird)! + # https://github.com/helgoboss/helgobox/issues/1384 + macosx_deployment_target: "10.9" + lib_file_name_dst: helgobox-macos-x86_64.vst.dylib + extension_file_name_dst: reaper_helgobox-macos-x86_64.dylib + + - artifact: macos-aarch64 + os: macos-14 + lib_file_name: libhelgobox.dylib + extension_file_name: libreaper_helgobox.dylib + target: aarch64-apple-darwin + profile: release-strip + features: "playtime,egui,licensing" + # First aarch64 macOS version + macosx_deployment_target: "11.0" + lib_file_name_dst: helgobox-macos-aarch64.vst.dylib + extension_file_name_dst: reaper_helgobox-macos-aarch64.dylib + + - artifact: linux-x86_64 + os: ubuntu-22.04 + lib_file_name: libhelgobox.so + extension_file_name: libreaper_helgobox.so + target: x86_64-unknown-linux-gnu + profile: release-strip + features: "playtime,licensing" + macosx_deployment_target: "" + lib_file_name_dst: helgobox-linux-x86_64.so + extension_file_name_dst: reaper_helgobox-linux-x86_64.so + + - artifact: linux-aarch64 + os: ubuntu-22.04-arm + lib_file_name: libhelgobox.so + extension_file_name: libreaper_helgobox.so + target: aarch64-unknown-linux-gnu + profile: release-strip + features: "playtime,licensing" + macosx_deployment_target: "" + lib_file_name_dst: helgobox-linux-aarch64.so + extension_file_name_dst: reaper_helgobox-linux-aarch64.so + + env: + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} + + steps: + # Prepare (all) + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + + - name: Install Rust + uses: dtolnay/rust-toolchain@1.84.0 + with: + target: ${{ matrix.target }} + + - name: Rust cache + uses: swatinem/rust-cache@v2 + + # Install OS dependencies (Linux/macOS only) + - name: Set up dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install nasm php libudev-dev libxdo-dev libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev libspeechd-dev libgtk-3-dev + shell: sh + - name: Set up dependencies + run: brew install php + if: runner.os == 'macOS' + shell: sh + + # Build (all) + - name: Build release + shell: sh + env: + PLAYTIME_AUTHENTICITY_SIGNING_KEY: "${{ secrets.PLAYTIME_AUTHENTICITY_SIGNING_KEY }}" + PLAYTIME_LICENSE_VERIFYING_KEY: "${{ secrets.PLAYTIME_LICENSE_VERIFYING_KEY }}" + PLAYTIME_PRESET_VERIFYING_KEY: "${{ secrets.PLAYTIME_PRESET_VERIFYING_KEY }}" + AWS_LC_SYS_CMAKE_BUILDER: "1" + run: cargo build --features "${{ matrix.features }}" --profile ${{ matrix.profile }} --target ${{ matrix.target }} + + # Upload to artifact + - name: Upload plug-in and extension to artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: | + target/${{ matrix.target }}/${{ matrix.profile }}/${{ matrix.lib_file_name }} + target/${{ matrix.target }}/${{ matrix.profile }}/${{ matrix.extension_file_name }} + target/${{ matrix.target }}/${{ matrix.profile }}/helgobox.pdb + target/${{ matrix.target }}/${{ matrix.profile }}/deps/libhelgobox.dylib.dSYM/ + target/${{ matrix.target }}/${{ matrix.profile }}/libhelgobox-debug.so + + # Windows x64: Upload PDB to Sentry + - name: Upload PDB + if: startsWith(matrix.artifact, 'windows-x86_64') + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + shell: powershell + run: | + # Install Sentry CLI + npm install -g @sentry/cli + + # Upload + sentry-cli debug-files upload --auth-token $env:SENTRY_AUTH_TOKEN -o $env:SENTRY_ORG -p $env:SENTRY_PROJECT "target/${{ matrix.target }}/${{ matrix.profile }}/helgobox.pdb" + + - name: Check if release exists + id: check_release + shell: bash + env: + GITHUB_TOKEN: ${{ github.TOKEN }} + run: | + if gh release view ${{ github.ref_name }} &> /dev/null; then + echo "RELEASE_EXISTS=true" >> "$GITHUB_OUTPUT" + else + echo "RELEASE_EXISTS=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create release + if: steps.check_release.outputs.RELEASE_EXISTS != 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ github.TOKEN }} + run: | + gh release create ${{ github.ref_name }} --draft --prerelease --verify-tag + + - name: Download artifacts from build job + uses: actions/download-artifact@v4 + + - name: Upload lib file + shell: bash + env: + GITHUB_TOKEN: ${{ github.TOKEN }} + run: | + mv "target/${{ matrix.target }}/${{ matrix.profile }}/${{ matrix.lib_file_name }}" "${{ matrix.lib_file_name_dst }}" + gh release upload ${{ github.ref_name }} --clobber "${{ matrix.lib_file_name_dst }}" + + - name: Upload extension file + shell: bash + env: + GITHUB_TOKEN: ${{ github.TOKEN }} + run: | + mv "target/${{ matrix.target }}/${{ matrix.profile }}/${{ matrix.extension_file_name }}" "${{ matrix.extension_file_name_dst }}" + gh release upload ${{ github.ref_name }} --clobber "${{ matrix.extension_file_name_dst }}" \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/workflows/windows-i686.yml b/plugin-reaper-realearn/.github/workflows/windows-i686.yml new file mode 100644 index 0000000..e613149 --- /dev/null +++ b/plugin-reaper-realearn/.github/workflows/windows-i686.yml @@ -0,0 +1,31 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + # For quickly detecting important differences in runner configurations + schedule: + - cron: '0 5 * * MON' + +name: Windows i686 + +jobs: + test: + name: Build + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/build + with: + # Prevent "LLVM ERROR: out of memory" + args: '--features egui --profile dev-llvm-out-of-memory-fix --target i686-pc-windows-msvc' + targets: 'i686-pc-windows-msvc' + test: 'false' \ No newline at end of file diff --git a/plugin-reaper-realearn/.github/workflows/windows-x86_64.yml b/plugin-reaper-realearn/.github/workflows/windows-x86_64.yml new file mode 100644 index 0000000..99d2047 --- /dev/null +++ b/plugin-reaper-realearn/.github/workflows/windows-x86_64.yml @@ -0,0 +1,28 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + # For quickly detecting important differences in runner configurations + schedule: + - cron: '0 5 * * MON' + +name: Windows x86_64 + +jobs: + test: + name: Test + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: ./.github/actions/post-checkout + with: + playtime-engine-deploy-key: ${{ secrets.PRIVATE_PLAYTIME_CLIP_ENGINE_DEPLOY_KEY }} + license-processor-deploy-key: ${{ secrets.PRIVATE_HELGOBOSS_LICENSE_PROCESSOR_DEPLOY_KEY }} + - uses: ./.github/actions/build + with: + args: '--features playtime,egui' \ No newline at end of file diff --git a/plugin-reaper-realearn/.gitignore b/plugin-reaper-realearn/.gitignore new file mode 100644 index 0000000..d49a28a --- /dev/null +++ b/plugin-reaper-realearn/.gitignore @@ -0,0 +1,132 @@ + +# Created by https://www.gitignore.io/api/rust,clion+all +# Edit at https://www.gitignore.io/?templates=rust,clion+all + +### CLion+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +/*/**/Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# End of https://www.gitignore.io/api/rust,clion+all + + +# Created by https://www.gitignore.io/api/visualstudiocode +# Edit at https://www.gitignore.io/?templates=visualstudiocode + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.env +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +# End of https://www.gitignore.io/api/visualstudiocode + +*.rpp-bak +*.rpp-PROX +*.reapeaks +.vs/ +*.aps +*.user +tree.txt +*.env + +.DS_Store \ No newline at end of file diff --git a/plugin-reaper-realearn/.tokeignore b/plugin-reaper-realearn/.tokeignore new file mode 100644 index 0000000..8f57b91 --- /dev/null +++ b/plugin-reaper-realearn/.tokeignore @@ -0,0 +1,4 @@ +target +main/lib/WDL +*.json +*.lua \ No newline at end of file diff --git a/plugin-reaper-realearn/.vscode/launch.json b/plugin-reaper-realearn/.vscode/launch.json new file mode 100644 index 0000000..ca1ca37 --- /dev/null +++ b/plugin-reaper-realearn/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Unix Portable", + "type": "lldb", + "request": "launch", + "program": "${userHome}/opt/REAPER/reaper", + "args": [], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.vscode/launch.env" + }, + { + "name": "Portable Windows 64-bit", + "type": "cppvsdbg", + "request": "launch", + "program": "C:\\REAPER\\reaper.exe", + "args": ["-newinst"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.vscode/launch.env" + }, + { + "name": "Portable Windows 32-bit", + "type": "cppvsdbg", + "request": "launch", + "program": "C:\\REAPER32\\reaper.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.vscode/launch.env" + }, + { + "name": "Global Windows 64-bit", + "type": "cppvsdbg", + "request": "launch", + "program": "C:\\Program Files\\REAPER (x64)\\reaper.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.vscode/launch.env" + }, + { + "name": "Windows attach", + "type": "cppvsdbg", + "request": "attach", + "processId": "${command:pickProcess}", + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/ARCHITECTURE.adoc b/plugin-reaper-realearn/ARCHITECTURE.adoc new file mode 100644 index 0000000..293f8e4 --- /dev/null +++ b/plugin-reaper-realearn/ARCHITECTURE.adoc @@ -0,0 +1,196 @@ += Architecture +:toc: +:sectnums: +:sectnumlevels: 2 + +This document describes the software architecture of ReaLearn. +It's a work in progress. + +== Basics + +Technically, ReaLearn is a *third-party native VST plug-in for REAPER which makes heavy use of the REAPER extension API*. + +… Woah! +That is a lot to take in! +Let’s see what this means in detail: + +* First and foremost, ReaLearn is a *plug-in for REAPER*. +That means ReaLearn is a software module which is loaded by REAPER dynamically (at runtime). +It comes in the form of a link:https://en.m.wikipedia.org/wiki/Dynamic-link_library[DLL] on Windows, a link:https://stackoverflow.com/questions/2339679/what-are-the-differences-between-so-and-dylib-on-macos[DYLIB] on macOS and a link:https://superuser.com/questions/71404/what-is-an-so-file[SO] on Linux. +Communication goes both ways: ReaLearn calls REAPER functions and REAPER calls ReaLearn functions ("callbacks"). +* It makes heavy use of the *REAPER extension API*. +That means ReaLearn uses functions and data structures of REAPER which are _specific to REAPER_, so they are _not_ part of a DAW-agnostic plug-in standard. +In theory, other DAWs could implement this API as well, but in practice only REAPER itself does it. +The reason is that this API is huge and that many parts of it only make sense for REAPER. +As a consequence, ReaLearn runs in REAPER only! +* At the same time, it is a *VST plug-in*. +That means … +** … it's not loaded immediately at REAPER startup time. +It gets loaded as soon as the user adds the first ReaLearn plug-in instance (in the form of a REAPER FX) or loads a project or FX chain containing a ReaLearn instance. +** … once loaded, there can be arbitrary many instances of ReaLearn (a big difference to REAPER extensions, which can "exist" only once). +Adding more instances is rather cheap because the dynamic library is already loaded at that time. +** … plug-in data is saved per instance (as a VST chunk). +As a consequence, plug-in data is typically saved along with a specific project (but it doesn't have to because you can put ReaLearn on the monitoring FX chain as well). +** … it can _receive_ MIDI and audio data from the FX input pins (using functions and data structures defined by the VST plug-in standard). +** … it can _send_ MIDI and audio data to the FX output pins (using functions and data structures defined by the VST plug-in standard). +* It's a *native plug-in*. +That means ReaLearn *is not a script*! +It's full-blown native machine code that operates on eye level with REAPER. +Once ReaLearn is loaded, it essentially becomes a part of REAPER itself. +It runs in the same process, can open its own threads and is not sandboxed or restrained by a virtual machine (unlike Lua/EEL-based ReaScript or JSFX). +That's great because it means ReaLearn is a "first-class citizen" and doesn't have any inherent disadvantage in terms of speed and possibilities. +Also, it's written in a language that can leverage all of the power and performance potential that comes with running on "bare metal": link:https://www.rust-lang.org[Rust]. +Rust is a non-garbage-collected system programming language, just like C or C++ but more modern. +* It's a *third-party product*. +That mean's it's not made by Cockos, the developer of REAPER. +It's made by link:https://www.helgoboss.org/projects/[Helgoboss] and must be installed separately. + +== Modules + +ReaLearn is built in a modular fashion. +The following diagram shows ReaLearn's most important modules (excluding 3rd-party modules): + +[.text-center] +image:doc/architecture/images/modules.svg[ReaLearn modules] + +* *main:* The main module of ReaLearn which contains most of its code. +We will learn more about it in the following sections. +* *api:* This contains the data structures for ReaLearn presets. +Its main use case is _ReaLearn Script_, a way to build mappings with the Lua scripting language. +* *swell-ui:* A tiny custom-written GUI framework based on the Win32 API (Windows) and Cockos SWELL (macOS, Linux) respectively. +SWELL makes it possible to write the GUI code only once, using a subset of the Windows-specific Win32 API, but making it work on macOS and Linux as well. +Basically by translating the Win32 API calls to OS-native GUI framework calls (Cocoa on macOS, GTK on Linux). +* *reaper-rs:* Rust bindings to the REAPER API (which itself is based on C and partially C++). +* *helgoboss-learn:* A library which contains reusable and DAW-agnostic code related to MIDI/OSC-learn functionality. +Some of ReaLearn's basic notions such as _Source_, _Glue_ (still called _Mode_ in most parts of the codebase) and _Target_ are defined in this DAW-neutral module. +* *helgoboss-midi:* A general-purpose and carefully designed library for dealing with MIDI messages according to the MIDI 1.0 specification. + +== Layers + +The _main_ module of ReaLearn is roughly built around an architectural pattern sometimes called link:https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html[Onion Architecture]. +This means it's divided into multiple "onion" layers: + +[.text-center] +image:doc/architecture/images/onion-layers.svg[ReaLearn onion layers] + +These layers follow a simple but strict rule: + +____ + +Outer layers use its own code and code of inner layers, *but inner layers are not allowed to use code of outer layers!* + +____ + +This rule prevents "Spaghetti code" between the different layers and makes sure there's a clean separation between different responsibilities. + +IMPORTANT: This rule doesn't restrict control and data flow! +Both control and data can still flow in both directions. +In practice they also do because e.g. data from the processing layer needs to be "sent up" to the user interface in order to be displayed! +The rule is concerned with the visibility of code symbols only. + +The contents and responsibilities of each layer are described in the following. + +=== Base layer + +* Contains very generic and reusable utility code that is not specific to ReaLearn and at the same time not substantial enough to put it into a dedicated library module. +* Also, this layer can be considered as the layer that contains the Rust standard library and other Rust crates that provide utility code (although those are obviously not part of ReaLearn's own codebase). +* In addition, it makes very much sense to think of the base layer as the layer that contains REAPER itself. +ReaLearn is built _around_ REAPER, it's not designed to be usable without it. +** As a direct consequence, all layers within ReaLearn are allowed to use the REAPER API! +** This doesn't mean that ReaLearn couldn't be ported to other DAWs. +It could, provided the other DAW is substantially extensible via native modules. +On ReaLearn's side, some effort in this direction has already been done: As mentioned before, the module _helgoboss-learn_ is designed to contain those parts of ReaLearn's logic that are DAW-agnostic. + +=== Processing layer + +* This layer contains the essence of ReaLearn: Its processing logic. +This includes the complete control and feedback logic. +* If you would take away ReaLearn's graphical user interface, its projection feature, its plug-in nature, its capability to memorize its settings (= persistence) … in short, all the stuff that is more "facade" than "central", then what's left is the processing layer. +The processing layer alone would still be capable of doing ReaLearn's main job: Routing incoming MIDI or OSC messages through the mapping list and controlling the targets accordingly as well as handling feedback. +* Because the processing layer is very independent and doesn't dictate things like user interface and persistence, It would be quite easy to factor it out into a separate module and use it in other ways, e.g. in order to build a totally different user interface on top of it! +* All the data structures in this layer are custom-tailored and optimized with one primary goal in mind: Performance. +ReaLearn should do its main job very fast and efficiently! + +=== Management layer + +* This layer contains everything related to _managing_ ReaLearn's objects: Mappings, groups, parameters and all that stuff. +* All the data structures in this layer (usually called _models_) are tailored to this purpose. +If you think that there's a lot of duplication between this layer and the processing layer, look twice. +Yes, the data structures look similar at times, but often they are completely different. +That's because they are designed for different purposes. +This strict separation of concerns ensures that no compromises need to be made between performance (processing layer) and managing/GUI (management/infrastructure layers). +* Even though this layer _still_ doesn't dictate a particular user interface, it is user-interface-aware and provides functions and data structures that are typically used by user interfaces. +It also allows user interfaces to register hooks in order to be notified whenever the state of ReaLearn's objects change. +The management layer is built with a _reactive_ GUI in mind which reflects all changes immediately. + +=== Infrastructure layer + +* This layer is basically responsible for connecting ReaLearn to the outside world: The user (*user interface*), the storage (*data*), the DAW (*plug-in*), the scripting feature (*api*) and the Projection server (*server*). + +==== Plug-in + +* Contains the VST plug-in implementation of ReaLearn. +* This is the main entry point, the place where ReaLearn's global initialization happens as well as the initialization per instance. + +==== User interface (UI) + +* Contains the implementation of ReaLearn's main graphical user interface. +* It's based on the _swell-ui_ module. +That means it uses platform-native user interface widgets - which gives ReaLearn the somewhat old-school but extremely professional look ;) + +==== Data + +* Contains data structures for the serialization/deserialization of all ReaLearn objects (mappings, groups, etc.). +* The data structures in this layer are similar to the corresponding data structures in the management layer but they serve a quite different purpose: Serialization and deserialization of ReaLearn's state. +This is necessary for persistence and features such as copy&paste. +* One could wonder about the code duplication here, but again: The data structures in this layer serve different purposes than the ones in the management layer. +Serialization/deserialization for persistence purposes absolutely needs to be concerned with backward compatibility, which makes these data structures very hard to change. +Keeping things separate ensures that the management data structures can develop freely, without being constrained by backward compatibility considerations. +Again: No compromises. + +==== API + +* Contains the data structures that make up ReaLearn Script. +* The focus of these data structures is to provide an expressive API with a wording that's straight to the point. +* These data structures are complete in that they can represent and express all valid ReaLearn object states, much like the structures in _data_. +* They were written much later (end of 2021) than the data structures in _data_ (2016) and therefore reflect ReaLearn's current wording and structure much better. +* In future, the API data structures might actually be used for persistence and copy&paste as well, eventually replacing _data_. + +==== Server + +* Contains HTTP/WebSocket server code for enabling ReaLearn's Projection feature. +* Will also soon contain gRPC server code for enabling full-blown apps built on top of ReaLearn, such as Playtime 2. + +== Components + +* ReaLearn is made up by a plethora of data structures that resemble components, which can be considered as ReaLearn's main pillars. +* Some of these components are part of each instance, others exist globally only once. + +=== Overview + +[.text-center] +image:doc/architecture/images/components.svg[ReaLearn components] + +=== Focus: Management communication + +[.text-center] +image:doc/architecture/images/components-management.svg[ReaLearn components] + +=== Focus: Real-time MIDI communication (from/to FX input/output) + +[.text-center] +image:doc/architecture/images/components-midi-fx.svg[ReaLearn components] + +=== Focus: Real-time MIDI communication (from/to hardware device) + +[.text-center] +image:doc/architecture/images/components-midi-device.svg[ReaLearn components] + +=== Focus: Real-time OSC communication + +[.text-center] +image:doc/architecture/images/components-osc.svg[ReaLearn components] + +== Design decisions + +See link:doc/architecture/design-decisions.adoc[Design decisions] \ No newline at end of file diff --git a/plugin-reaper-realearn/CONTRIBUTING.adoc b/plugin-reaper-realearn/CONTRIBUTING.adoc new file mode 100644 index 0000000..881f3bb --- /dev/null +++ b/plugin-reaper-realearn/CONTRIBUTING.adoc @@ -0,0 +1,611 @@ += Contributing +:toc: +:sectnums: +:sectnumlevels: 2 + +Contributions are welcome! *However, please open an issue first and discuss the details with me, preferably before you start working on a pull request!* And if possible, avoid very large PRs. +I usually can't find the time to process large PRs. + +== Basics + +ReaLearn is written in the programming language https://www.rust-lang.org/[Rust]. +It makes heavy use of +https://github.com/helgoboss/reaper-rs[reaper-rs], which provides Rust bindings for the +https://www.reaper.fm/sdk/plugin/plugin.php[REAPER C++ API]. _reaper-rs_ was developed together with ReaLearn but is designed as independent library that can be used for REAPER plug-ins of all sorts. + +Another noteworthy dependency and byproduct of ReaLearn is https://github.com/helgoboss/helgoboss-learn[helgoboss-learn], a crate which provides DAW-agnostic MIDI learn logic (basically the _source_ and _mode_ parts of ReaLearn). +Like _reaper-rs_, it's designed as independent library and could be used to provide similar MIDI-learn functionality in other DAWs. + +== Code statistics + +Use https://github.com/XAMPPRocky/tokei[Tokei] or https://github.com/o2sh/onefetch:[Onefetch] to display some interesting statistics about the code, e.g. the lines of code and used languages. +The file `.tokeignore` contains code to be ignored when counting the lines of code. + +== Architecture + +See link:ARCHITECTURE.adoc[here]. + +== Directory structure + +|=== +|Directory entry |Content + +|`/` |Workspace root +|`/allocator` |A custom global allocator for deferring deallocation in real-time threads +|`/api` |Data structures of ReaLearn's preset API (which is e.g. used in the Lua-based ReaLearn Script) +|`/base` |Very generic utility code used by many crates in the workspace +|`/csi` |Code for interfacing with the ControlSurfaceIntegrator (CSI) project +|`/dialogs` |The single source of truth for ReaLearn's GUI dialogs +|`/doc` |Documentation +|`/extension` |Helgobox REAPER extension (provides some additional convenience around the actual ReaLearn plug-in) +|`/helgoboss-license-processor` |Contains code for license processing (currently a private submodule) +|`/macros` |Various Rust macros for usage in this project only +|`/main` |Main crate: The actual ReaLearn instrument plug-in (`realearn`) +|`/playtime-api` |Playtime data structures for describing e.g. clip engine presets +|`/playtime-clip-engine` |Playtime Clip Engine for playing/recording clips (currently a private submodule). Is a workspace member because that makes it much simpler to use the same dependency versions everywhere. +|`/playtime-clip-engine-placeholder` |A placeholder crate for the Playtime Clip Engine. Users who don't have access to +the Playtime Clip Engine submodule, must rename this directory to `playtime-clip-engine` in order to be able to build +ReaLearn without feature `playtime`. +|`/pot` |Core logic behind Pot Browser, also powers the Pot targets +|`/pot-browser` |The actual Pot Browser user interface +|`/resources` |REAPER projects for manual testing, controller preset files, etc. +|`/rx-util` |Some reactive programming helpers +|`/swell-ui` |Minimalistic UI framework based on https://www.cockos.com/wdl/[SWELL] (`swell-ui`) +|=== + +== Build + +=== All operating systems + +==== Update Cockos WDL (optional) + +[source,shell] +---- +cd main/lib/WDL +git checkout main +git pull +# After updating Cockos WDL, regenerate Rust bindings (because we use WDL's EEL code, for example) +cargo build --features generate +cargo fmt +---- + +==== Regenerate Luau language bindings (optional) + +Luau language bindings should be regenerated from Rust after changing something in link:api[api] or link:playtime-api[playtime-api]. +This is done simply by executing all tests like this: + +[source,shell] +---- +RUST_MIN_STACK=5242880 cargo test --package helgobox-api --lib bindings::luau::export_luau +---- + +==== Regenerate artwork (optional) + +Artwork such as toolbar icons can be regenerated by running a crate: + +[source,shell] +---- +cargo run helgobox-artwork-processor +---- + +3 different approaches for generating code ... yes, maybe it's time to unify this ;) + +==== Regenerate diagrams in documentation + +This is about the diagrams in the Antora documentation, e.g. the glue signal flow. + +[source,shell] +---- +sh regenerate-doc-diagrams.sh +---- + +==== Building with Playtime (can only be done by Helgoboss) + +Add this to `$HOME/.cargo/config` (otherwise Cargo will have issues fetching the private submodules): + +[source,ini] +---- +[net] +git-fetch-with-cli = true +---- + +=== Windows + +In the following, you will find the complete instructions for Windows 10/11, including Rust setup. +Points where you have to consider the target architecture (for example, REAPER 32-bit vs. 64-bit) are marked with :star:. + +. Enable "Developer mode" in the Windows settings (this is needed because ReaLearn uses link:https://stackoverflow.com/a/59761201[Symlinks within its Git repository]) +. Setup "Visual Studio" (currently tested with version 2022) +* Rust uses native build toolchains. +On Windows, it's necessary to use the MSVC (Microsoft Visual Studio C++) toolchain because REAPER plug-ins only work with that. +* https://visualstudio.microsoft.com/downloads/[Visual Studio downloads] → All downloads → Tools for Visual Studio 2022 + → Build Tools for Visual Studio 2022 +* Start it and follow the installer instructions +* Required components +** Workloads tab +*** "C++ build tools" (large box on the left) +*** Make sure "Windows 10 SDK" is checked on the right side (usually it is) +*** If on Windows ARM64: Make sure "C++ Clang-Tools" is checked on the right side (normally not checked!, currently necessary for compilation of `ring` dependency) +** Language packs +*** English +. Setup Rust +* https://www.rust-lang.org/tools/install[Download] and execute `rustup-init.exe` +* Accept the defaults +* Set the correct toolchain default :star: ++ +[source,shell] +---- +rustup default 1.84.0-x86_64-pc-windows-msvc +---- +. If you want to regenerate bindings (usually not necessary): Download and install LLVM from https://github.com/llvm/llvm-project/releases (look for something like `LLVM-*-win64.exe`) +. Download and install https://git-scm.com/download/win[Git for Windows] +. Clone the ReaLearn Git repository ++ +[source,shell] +---- +git clone https://github.com/helgoboss/helgobox.git` +cd helgobox +git checkout v2.16.0 # or any other release tag + +# ONLY IF YOU ARE HELGOBOSS +git submodule update --init + +# OTHERWISE +git submodule update --init main/lib/WDL main/lib/helgoboss-learn +rmdir playtime-clip-engine +rename playtime-clip-engine-placeholder playtime-clip-engine +---- +. Build ReaLearn (after that you should have a `helgobox.dll` in `target\debug`) ++ +[source,shell] +---- +cargo build --features egui +---- + +==== Windows 7 support is gone and its future is uncertain + +Helgobox 2.16.15 brought back Windows 7 support, which has gone missing at some point before that. +This was primarily achieved by producing the Windows build using the last official Rust version that supported Windows 7, namely Rust 1.77.2. + +However, it turned out that it's very hard to stick with that version. +It means having to give up on new Rust features. +But most importantly, it means updating dependencies is not possible if they raise their MSRV (minimal supported Rust version). +A high price to pay for a rarely used OS. + +An alternative would be to use the tier 3 target `x86_64-win7-windows-msvc`. +Compilation works using the `RUSTC_BOOTSTRAP` hack: + +.PowerShell +[source] +---- +$env:RUSTC_BOOTSTRAP=1 +# For compiling indexmap +$env:CARGO_FEATURE_STD=1 +cargo build -Z build-std --release --target x86_64-win7-windows-msvc +---- + +However, linking fails because `windows.lib` can't be opened. +Probably related to https://www.reddit.com/r/rust/comments/1dikeq6/compiling_for_win7_missing_windows0485lib/[this] and https://github.com/rust-lang/rust/issues/128218#issuecomment-2251864932[this] issue. +If -- and only if -- users are genuinely interested in a working Windows 7 build of ReaLearn, I would look into it a bit further. + +=== Linux + +Complete instructions to build ReaLearn from a _fresh_ Ubuntu 18.04.3 LTS installation, including Rust setup: + +[source,shell] +---- +# Install native dependencies +sudo apt update +sudo apt install -y curl git build-essential pkg-config php nasm llvm-dev libclang-dev clang libudev-dev libxdo-dev libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev libspeechd-dev libgtk-3-dev + + +# Install Rust (copied from the official Linux installation instructions) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # choose 1 (default) +source $HOME/.cargo/env + +# Set the correct toolchain default +rustup default 1.84.0-x86_64-unknown-linux-gnu + +# Clone ReaLearn repository +git clone https://github.com/helgoboss/helgobox.git +cd helgobox +git checkout v2.16.0 # or any other release tag + +# ONLY IF YOU ARE HELGOBOSS +git submodule update --init + +# OTHERWISE +git submodule update --init main/lib/WDL main/lib/helgoboss-learn +rmdir playtime-clip-engine +mv playtime-clip-engine-placeholder playtime-clip-engine + +# Build (after that you should have a "libhelgobox.so" in "target/debug") +cargo build --features egui + +---- + +Some words about the native dependencies: + +* `curl git build-essential pkg-config` are bare essentials. +* `php` is needed to translate the ReaLearn dialog resource file to C++ so it can be processed by the SWELL + dialog generator. It's also necessary for generating the 64-bit EEL assembler code. All of this is the + typical WDL C++ way of doing things, no Rust specifics here. +* `nasm` is needed for assembling the 64-bit EEL assembler code to produce `asm-nseel-x64.o`, which is necessary to make the custom https://www.cockos.com/EEL2/[EEL] control and feedback transformations in ReaLearn's absolute mode work. +* `llvm-dev libclang-dev clang` are necessary for building with feature `generate` (to generate bindings to C). +* `libxdo-dev` is needed to control the mouse (see target "Global: Mouse") +* `libudev-dev` is needed for connecting to Stream Deck via HID API +* `libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev` are necessary for https://github.com/BillyDM/egui-baseview[egui-baseview] (https://github.com/emilk/egui[egui] is the GUI framework used for ReaLearn's control transformation editor) +* `libspeechd-dev` is necessary for the speech source +* `libgtk-3-dev` is necessary to obtain the X window and X display from a SWELL OS window, in order to fire up OpenGL/egui in it + +=== macOS + +The following instructions include Rust setup. +However, it's very well possible that some native toolchain setup instructions are missing, because I don't have a bare macOS installation at my disposal. +The Rust installation script should provide you with the necessary instructions if something is missing. + +[source,shell] +---- +# Install Rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # choose 1 (default) +source $HOME/.cargo/env +rustup default 1.84.0-x86_64-apple-darwin + +# Clone ReaLearn +cd Downloads +git clone https://github.com/helgoboss/helgobox.git +cd helgobox +git checkout v2.16.0 # or any other release tag + +# ONLY IF YOU ARE HELGOBOSS +git submodule update --init + +# OTHERWISE +git submodule update --init main/lib/WDL main/lib/helgoboss-learn +rmdir playtime-clip-engine +mv playtime-clip-engine-placeholder playtime-clip-engine + +# Install build dependencies +brew install php + +# Build ReaLearn +cargo build --features egui +---- + +== GUI + +The GUI dialogs are defined in the `dialogs` directory. +Whenever ReaLearn is built, the code there generates an old-school Windows dialog resource file (`target/generated/msvc.rc`) and a Rust file which contains all the resource ID constants (`main/src/infrastructure/ui/bindings.rs`). + +Previously I used the Visual Studio C++ 2019 resource editor to WYSIWYG-edit this file as part of the solution +link:main/src/infrastructure/ui/msvc/msvc.sln[msvc.sln], but this was too tedious. + +WARNING: You can still preview the generated file in Visual Studio but don't edit the RC file, the changes will be overwritten at build time! +Adjust the Rust code in the `dialogs` directory instead. + +On macOS and Linux, an extra step will happen at build time: It will try to use a PHP script (part of Cockos SWELL) to generate +`target/generated/msvc.rc_mac_dlg`, which is a translation of the RC file to C code using SWELL. +So make sure you have PHP installed on these platforms! + +== Test + +Yes, there are tests but there should be more. +While ReaLearn's basic building blocks +https://github.com/helgoboss/helgoboss-learn[helgoboss-learn] and https://github.com/helgoboss/reaper-rs[reaper-rs] +are tested pretty thoroughly, ReaLearn itself has room for improvement in that aspect. + +=== Unit tests + +Unit tests should be executed with a higher stack size because there's one unit test that generates and formats Lua code and this currently overflows the stack in debug builds. + +[source,shell] +---- +RUST_MIN_STACK=104857600 cargo test +---- + +=== Integration tests + +There's a growing built-in integration test, launchable via action `[developer] ReaLearn: Run integration test`. +In future, it would be nice to run this integration test during continuous integration, just like in _reaper-rs_. + +== Log + +It's possible to make ReaLearn output log messages to `stdout` by setting the `HELGOBOX_LOG` environment variable, e.g. to `debug,vst=info`. +It follows https://docs.rs/env_logger/0.8.2/env_logger/index.html[this] format. +Beware that e.g. on Windows, `stdout` is not shown, not even when executing REAPER from the command line. +One way to make it visible is to execute REAPER with a debugger. + +== Metrics + +It's possible to make ReaLearn expose execution metrics. + +=== Prometheus endpoint + +* If the projection server is running, metrics will then be exposed at `/realearn/metrics` in the popular +https://prometheus.io/[Prometheus] format. +That's great for visualization. +** Just add this to your `prometheus.yml` (you might need to adjust the port): +[source,yaml] +---- +scrape_configs: + - job_name: 'realearn' + metrics_path: '/realearn/metrics' + static_configs: + - targets: ['localhost:39080'] +---- + +** If you don't have any metrics enabled, this will show zeros only. + +Prometheus is usually available at http://localhost:9090/. + +=== ReaLearn metrics + +- You can turn on ReaLearn metrics by setting the environment variable `HELGOBOX_METRICS` (value doesn't matter). +- If this environment variable is set (value doesn't matter), ReaLearn will record some metrics and expose them on the Prometheus endpoint mentioned above. +- If ReaLearn is built with the Playtime Clip Engine, this flag will also enable Clip Engine metrics. +This can negatively effect clip playing performance because many clip engine metrics are captured in real-time threads. + +== Debug + +=== Debug REAPER scanning ReaLearn + +Set `vst_scan=1` in the `[reaper]` section of `reaper.ini`. +That makes the debugged REAPER process itself do the scanning. + +=== Obtain debug symbols + +Debug symbols are stripped from release builds but stored as build artifact of the GitHub Actions "Create release" workflow. +If you want the symbols for a specific build, proceed as follows: + +. Open the https://github.com/helgoboss/helgobox/actions?query=workflow%3A%22Create+release%22[list of ReaLearn "Create release" workflows]. +. Use the branch filter to show all releases builds made for a specific version, e.g. "v1.11.0". +. Click the desired workflow. +** GitHub seems to do a fuzzy search, so if there are pre-releases (e.g. "v1.11.0-pre2"), you will see them, too. +** In that case, just choose the latest one. +. You will see a list of artifacts, one for each OS-architecture combination. +. Download the one you need and unzip it. +** You will find both the library file and the symbol file (e.g. `realearn.pdb` for a Windows build). + +=== Turn on complete backtraces + +As soon as you have the debug symbols, you can make ReaLearn print full backtraces (including line number etc.) in the REAPER ReaScript console. +Here's how you do it. + +==== Windows + +. Set the environment variable `_NT_ALT_SYMBOL_PATH` to some directory of your choice. +. Copy the PDB file in there. +. Fire up REAPER with ReaLearn an make it panic. +You should see a complete backtrace now. + +=== Lookup symbols for symbol-less backtraces + +The problem with release builds is that they don't contain debug symbols and therefore backtraces usually contain not much more than memory addresses. +Especially backtraces generated by Windows release builds leave a lot to be desired. + +ReaLearn has a built-in REAPER action which attempts to look up symbol information for a given error report: +"ReaLearn: Resolve symbols from clipboard". +Works on Windows only. +To be used like this: + +. Make sure the PDB for the release build in question is on the search path (see section above). +. Fire up an ReaLearn using exactly that release build. +. Copy the error report to the clipboard. +. Execute the action. + +=== Differences between debug levels + +==== macOS + +Insights: + +* The size difference between `debug = 1` and `debug = 2` is almost nothing (both 58 MB), and there's nothing to gain from `debug = 2` in terms of stack traces. +* The size difference between `debug = 0` and `debug = 1` is around 5 MB (53 MB vs. 58 MB), and `debug = 1` only makes a difference if the source files exist (showing line numbers), and only for panics. +* Hard crash stack traces are completely independent of the `debug` value. +They are always helpful except when stripping the symbols. +* `strip = symbols` leads to the smallest binaries (38 MB) but also to completely useless stack traces, both in soft and hard crashes. +However, `split-debuginfo = "packed" seems to fix this at least for hard crashes, at a similar-sized binary (not for panics though) ... even if the DSYM directories are not on disk. What also fixes this for hard crashes is stripping via `strip -u -r`. +We shouldn't do that anymore! +* `strip = debuginfo` leads to an okay size reduction (53 MB) but removes line numbers even if source files exist. +However, `split-debuginfo = "packed"` solves this by creating dSYM directories, as long as they are there. + +Takeaway: + +* We should build with `debug = 2` +** While `debug = 1` is actually enough for most purposes, it can't hurt building with `debug = 2` since we strip debuginfo anyway. +So there's no size difference for the final binary. +That way we have more debuginfo on the server whenever we need it. +* `strip = debuginfo` is the max we can strip away if we want panics to contain something useful +* `strip = symbols` is only okay if we are fine with bogus stack traces in panics. +In that case, we must use `split-debuginfo = "packed"` to get at least detailed stack traces in case of hard crashes. +* We should use `split-debuginfo = "packed"` in all cases. +* Would be good to find a way to leverage symbols for panic stack traces, but I think there is none. + +===== "debug = 0" + +.Panic (sources don't matter) +---- +7: 0x120531450 - helgobox::infrastructure::plugin::sandbox::execute::h9608b1370cb08106 +---- + +.Hard crash (sources don't matter) +---- +4 helgobox-arm64.vst.dylib 0x120527df4 _$LT$helgobox..domain..targets..track_volume_target..TrackVolumeTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::hff7df2fe68cec5d5 + 20 +---- + +===== "debug = 1" + +.Panic if sources exist +---- +7: 0x13071ffdc - helgobox::infrastructure::plugin::sandbox::execute::hb564445c2d9211ee + at /Users/helgoboss/Documents/projects/dev/realearn/main/src/infrastructure/plugin/sandbox.rs:3:5 +---- + +.Panic if sources are gone +---- +7: 0x140f1ffdc - helgobox::infrastructure::plugin::sandbox::execute::hb564445c2d9211ee +---- + +.Hard crash (sources don't matter) +---- +6 helgobox-arm64.vst.dylib 0x1302d4a64 _$LT$helgobox..domain..mapping..CompoundMappingTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::hadfcb1be993900b3 + 20 (mapping.rs:2498) [inlined] +---- + +===== "debug = 2" + +.Panic if sources exist +---- +7: 0x12160dafc - helgobox::infrastructure::plugin::sandbox::execute::h18fb689d4112e2d3 + at /Users/helgoboss/Documents/projects/dev/realearn/main/src/infrastructure/plugin/sandbox.rs:3:5 +---- + +.Panic if sources are gone +---- +7: 0x153f81afc - helgobox::infrastructure::plugin::sandbox::execute::h18fb689d4112e2d3 +---- + +.Hard crash (sources don't matter) +---- +6 helgobox-arm64.vst.dylib 0x1212d8148 _$LT$helgobox..domain..mapping..CompoundMappingTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::h15041e3226fa455d + 20 (mapping.rs:2498) [inlined] +---- + +===== "debug = 2; strip = debuginfo" + +.Panic (sources don't matter) +---- +7: 0x138616570 - helgobox::infrastructure::plugin::sandbox::execute::hd0d406afe4d62df9 +---- + +.Hard crash (sources don't matter) +---- +4 helgobox-arm64.vst.dylib 0x13885aab4 _$LT$helgobox..domain..targets..track_volume_target..TrackVolumeTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::h35c07d80eb0a312b + 20 +---- + +===== "debug = 2; strip = symbols" + +.Soft crash (sources don't matter) +---- +0: 0x14bdef0ec - _NSEEL_HOSTSTUB_EnterMutex +1: 0x14bd56f08 - _NSEEL_HOSTSTUB_EnterMutex +2: 0x14bfbe4e4 - _cpp_to_rust_ProjectStateContext_SetTempFlag +3: 0x14bfbddcc - _cpp_to_rust_ProjectStateContext_SetTempFlag +4: 0x14bfbca08 - _cpp_to_rust_ProjectStateContext_SetTempFlag +5: 0x14bfbdabc - _cpp_to_rust_ProjectStateContext_SetTempFlag +6: 0x14c075710 - _cpp_to_rust_ProjectStateContext_SetTempFlag +7: 0x14af50aa0 - _ReaperPluginEntry +8: 0x14bd58fcc - _NSEEL_HOSTSTUB_EnterMutex +9: 0x14bd82844 - _NSEEL_HOSTSTUB_EnterMutex +---- + +.Hard crash (sources don't matter) +---- +7 helgobox-arm64.vst.dylib 0x14b34b5e0 0x14a65c000 + 13563360 +---- + +== Documentation + +All documentation is written in AsciiDoc. + +- link:doc/realearn/README.adoc[ReaLearn Reference] +- link:ARCHITECTURE.adoc[Software architecture documentation] + +Some SVGs embedded in the architecture documentation are generated via link:https://nodejs.org/[NodeJS] / link:https://svgjs.dev/[SVG.js] in link:doc/svg-gen/index.js[]. +After modifying this file, you need to execute the following command in the project root: + + node doc/svg-gen/index.js + +== License check + +It's important to make sure that the licenses of all dependencies are compatible with the final license. +We use link:https://github.com/EmbarkStudios/cargo-deny[cargo-deny] for this. + +Installation: + +[source] +---- +cargo install --locked cargo-deny +---- + +Check: + +[source] +---- +cargo deny check licenses +---- + +== License report + +It's important to make sure that the licenses of all dependencies are compatible with the final license. +We use link:https://github.com/EmbarkStudios/cargo-deny[cargo-deny] for this. + +Installation: + +[source] +---- +cargo install --locked cargo-about +---- + +Generate the report: + +[source] +---- +cargo about generate --fail --workspace --all-features --threshold 0.93 about.hbs > about.html +---- + +== Release + +This serves mainly as a checklist for Helgobox's author. + +. Check licenses via `cargo deny check licenses` and make sure the outcome is "licenses ok" +. Update license report (see above) +. Take care of app versioning +** Plug-in repository: Adjust `HOST_API_VERSION` and `MIN_APP_API_VERSION` +** App repository: Adjust `appApiVersion` (macOS, Swift), `APP_API_VERSION` (Windows, C++) and `_minHostApiVersionString` (Dart) +. Bump up the app version number in `pubspec.yaml`. +. Bump up the plug-in version number in link:main/Cargo.toml[main/Cargo.toml]. +** Either to a prerelease (e.g. `2.0.0-pre1`) or a final release (e.g. `2.0.0`). +** This is important for having the correct version number displayed in ReaLearn UI. +. Build at least once via `cargo build --features playtime,egui`. +** This updates `Cargo.lock` and is important for not having the `-dirty` display in ReaLearn UI. +. Update the user guide if not done already. +. Create a version tag via `git tag v2.0.0-pre1`. +. Push via `git push origin v2.0.0-pre.1`. +. While GitHub Actions executes the release job, take care of the following. +** Can only be done by @helgoboss because it needs access to the https://github.com/helgoboss/helgoboss-org[helgoboss.org website repository]. +** Add a changelog entry in https://github.com/helgoboss/helgoboss-org/blob/master/src/data/projects/realearn/data.yaml[data.yaml]. +** In `src/snippets/projects/realearn/repo`, enter `git checkout master` and `git pull` to pull the latest user guide changes. +** Push via `git push origin HEAD` and wait until Netlify deployed the page. +** All the following stuff needs to be done using Netlify's branch preview if it's a prerelease! +** Update https://github.com/helgoboss/reaper-packages/blob/master/index.xml[helgoboss ReaPack index]. +*** Generate ReaLearn-only ReaPack index by requesting https://www.helgoboss.org/projects/realearn/reapack.txt[/projects/realearn/reapack.txt]. +*** Integrate the generated index by copying everything from `<category name="Extensions">` and pasting it to the +https://github.com/helgoboss/reaper-packages/blob/master/index.xml[helgoboss ReaPack index] without overwriting the preset categories on the top of the file. +*** Don't push the index yet! +** Create a REAPER forum ReaLearn thread entry with help of https://www.helgoboss.org/projects/realearn/reaper-forum.txt[/projects/realearn/reaper-forum.txt] +but don't submit yet! +** Download the user guide by requesting https://www.helgoboss.org/projects/realearn/user-guide[/projects/realearn/user-guide]. +** Copy the corresponding changelog entry in markdown format by requesting https://www.helgoboss.org/projects/realearn/changelog.md[/projects/realearn/changelog.md]. +. Once the release job has finished successfully, edit the not-yet-published release that has been created. +** Paste the copied changelog entry to the release notes. +** Manually add the previously downloaded user guide as release artifact named `realearn-user-guide.pdf`. +. Publish the release. +. Push the https://github.com/helgoboss/reaper-packages/blob/master/index.xml[helgoboss ReaPack index]. +. Submit the REAPER forum ReaLearn thread entry. +. Check if synchronization of the ReaPack repository works. +. Update website's `config.yaml` latest versions (for update notifications) + +== Troubleshooting + +=== Windows: ReaLearn DLL doesn't unload + +In REAPER for Windows it's possible to enable complete unload of VST plug-ins (Options -> Settings/Preferences -> Plug-ins -> VST -> Allow complete unload of VST plug-ins). +This also affects ReaLearn. +Removing the last ReaLearn instance should work with and without this flag enabled, it's important to test this. + +I ran into a case in which Windows was *not* unloading ReaLearn even though that option was enabled. +The reason turned out to be a registry entry that Windows must have created automatically at some point: + +`HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers` -> `C:\REAPER\reaper.exe` with value `$ IgnoreFreeLibrary` + +Removing this entry made unloading work again. +What a nasty trap! \ No newline at end of file diff --git a/plugin-reaper-realearn/Cargo.lock b/plugin-reaper-realearn/Cargo.lock new file mode 100644 index 0000000..608ee1e --- /dev/null +++ b/plugin-reaper-realearn/Cargo.lock @@ -0,0 +1,8962 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "any_ascii" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70033777eb8b5124a81a1889416543dddef2de240019b674c81285a2635a7e1e" + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569" + +[[package]] +name = "arboard" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08" +dependencies = [ + "clipboard-win 4.5.0", + "core-graphics 0.22.2", + "image 0.24.8", + "log", + "objc", + "objc-foundation", + "objc_id", + "parking_lot", + "thiserror", + "winapi", + "x11rb", +] + +[[package]] +name = "arc-swap" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ab7d9e73059c86c36473f459b52adbd99c3554a4fec492caef460806006f00" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + +[[package]] +name = "ascii" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn 2.0.58", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue 1.2.2", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +dependencies = [ + "concurrent-queue 2.4.0", + "event-listener 4.0.3", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +dependencies = [ + "async-lock 3.3.0", + "async-task", + "concurrent-queue 2.4.0", + "fastrand 2.0.1", + "futures-lite 2.0.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +dependencies = [ + "async-channel 1.6.1", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite 1.12.0", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue 2.4.0", + "futures-lite 1.12.0", + "log", + "parking", + "polling", + "rustix 0.37.13", + "slab", + "socket2 0.4.7", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-process" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock 2.8.0", + "autocfg", + "blocking", + "cfg-if", + "event-listener 2.5.3", + "futures-lite 1.12.0", + "rustix 0.37.13", + "signal-hook", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-std" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +dependencies = [ + "async-channel 1.6.1", + "async-global-executor", + "async-io", + "async-lock 2.8.0", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.12.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "async-task" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "atk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "atomic_refcell" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "auto_impl" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "serde", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.22.1", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa 1.0.6", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-server" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56bac90848f6a9393ac03c63c640925c4b7c8ca21654de40d53f55964667c7d8" +dependencies = [ + "arc-swap", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower 0.4.13", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide 0.8.0", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base" +version = "0.1.0" +dependencies = [ + "anyhow", + "ascii", + "camino", + "crossbeam-channel", + "derive_more", + "device_query", + "either", + "enigo", + "fragile", + "futures", + "futures-timer", + "helgobox-api", + "indexmap 2.1.0", + "logos 0.13.0", + "macos-accessibility-client", + "metrics", + "once_cell", + "reaper-high", + "reaper-low", + "reaper-medium", + "reaper-rx", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "xxhash-rust", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "baseview" +version = "0.1.0" +source = "git+https://github.com/helgoboss/baseview.git?branch=realearn#4387a36125fcb10a6afd2369e706be79864b9a95" +dependencies = [ + "cocoa", + "core-foundation 0.9.1", + "keyboard-types", + "libloading 0.8.0", + "nix 0.22.3", + "objc", + "raw-window-handle 0.4.3", + "uuid 0.8.2", + "winapi", + "x11", + "xcb 0.9.0", + "xcb-util", +] + +[[package]] +name = "basic-toml" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" +dependencies = [ + "serde", +] + +[[package]] +name = "beef" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" + +[[package]] +name = "bindgen" +version = "0.69.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d" +dependencies = [ + "bitflags 2.4.1", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.58", + "which 4.4.2", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bitstream-io" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e445576659fd04a57b44cbd00aa37aaa815ebefa0aa3cb677a6b5e63d883074f" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys", + "objc2-encode", +] + +[[package]] +name = "blocking" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" +dependencies = [ + "async-channel 1.6.1", + "async-task", + "atomic-waker", + "fastrand 1.5.0", + "futures-lite 1.12.0", + "once_cell", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata 0.1.10", + "serde", +] + +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +dependencies = [ + "memchr", + "regex-automata 0.4.4", + "serde", +] + +[[package]] +name = "built" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9c056b9ed43aee5e064b683aa1ec783e19c6acec7559e3ae931b7490472fbe" +dependencies = [ + "cargo-lock", +] + +[[package]] +name = "built" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d17f4d6e4dc36d1a02fbedc2753a096848e7c1b0772f7654eab8e2c927dd53" +dependencies = [ + "chrono", + "git2", +] + +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + +[[package]] +name = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "bytesize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" + +[[package]] +name = "c_str_macro" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d44951c469019e225e7667d799052f67fb8ea358d086878f3582b39f0de5e5" + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + +[[package]] +name = "cached" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d73155ae6b28cf5de4cfc29aeb02b8a1c6dab883cb015d15cd514e42766846" +dependencies = [ + "ahash 0.8.7", + "cached_proc_macro", + "cached_proc_macro_types", + "hashbrown 0.14.3", + "once_cell", + "thiserror", + "web-time", +] + +[[package]] +name = "cached_proc_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-lock" +version = "8.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996" +dependencies = [ + "semver 1.0.17", + "serde", + "toml 0.5.8", + "url", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "git+https://github.com/petrochenkov/cc-rs.git?rev=4d52bd211aeb2b4ddccd1b9c0a0841e03aaaef7c#4d52bd211aeb2b4ddccd1b9c0a0841e03aaaef7c" + +[[package]] +name = "cc" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295b6eb918a60a25fec0b23a5e633e74fddbaf7bb04411e65a10c366aca4b5cd" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time 0.1.43", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +dependencies = [ + "glob", + "libc", + "libloading 0.7.3", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_derive 3.2.25", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "once_cell", + "strsim 0.10.0", + "termcolor", + "textwrap 0.16.0", +] + +[[package]] +name = "clap" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +dependencies = [ + "clap_builder", + "clap_derive 4.3.2", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +dependencies = [ + "anstyle", + "clap_lex 0.5.0", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.1", + "core-graphics 0.22.2", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.1", + "core-graphics-types 0.1.1", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "const-oid" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" + +[[package]] +name = "const-random" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "copypasta" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7216b5c1e9ad3867252505995b02d01c6fa7e6db0d8abd42634352ef377777e" +dependencies = [ + "clipboard-win 3.1.1", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +dependencies = [ + "core-foundation-sys 0.6.2", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f15b3cb55687886a6b66953123621e5a1529a91a01666d646fb64baa13f900f0" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.6.4", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.1", + "core-graphics-types 0.1.1", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.4.1", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.1", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.4.1", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core_maths" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +dependencies = [ + "libm", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr 0.2.17", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "curve25519-dalek" +version = "4.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version 0.4.0", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.58", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core 0.20.10", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "dasp_frame" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6" +dependencies = [ + "dasp_sample", +] + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid 1.7.0", +] + +[[package]] +name = "der" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version 0.3.3", + "syn 1.0.109", +] + +[[package]] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" + +[[package]] +name = "device_query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b54c3f0350a597abbed2c10fff7b233f92744c9422697d5fb58dc5397ab6fae" +dependencies = [ + "lazy_static", + "macos-accessibility-client", + "pkg-config", + "readkey", + "readmouse", + "winapi", + "x11", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading 0.7.3", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dns-lookup" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" +dependencies = [ + "cfg-if", + "libc", + "socket2 0.5.5", + "windows-sys 0.48.0", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + +[[package]] +name = "ebur128" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12aebdd6b6b47b5880c049efb0e77f8762178a0745ef778878908f5981c05f52" +dependencies = [ + "bitflags 1.3.2", + "dasp_frame", + "dasp_sample", + "smallvec", +] + +[[package]] +name = "ec4rs" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbe77819d458aa1ffc58cfdc1cc240f6356952908ea4ee1864da53b0c7e19437" + +[[package]] +name = "ecolor" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f99fe3cac305af9d6d92971af60d0f7ea4d783201ef1673571567b6699964d9" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ed25519" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "edit" +version = "0.1.3" +source = "git+https://github.com/helgoboss/edit?branch=realearn#6d94fbbd4dbf346cf29d337c37560f5fc5eb9bcc" +dependencies = [ + "tempfile", + "which 4.4.2", +] + +[[package]] +name = "egui" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6412a21e0bde7c0918f7fb44bbbb86b5e1f88e63c026a4e747cc7af02f76dfbe" +dependencies = [ + "ahash 0.8.7", + "epaint", + "nohash-hasher", +] + +[[package]] +name = "egui-baseview" +version = "0.1.0" +source = "git+https://github.com/helgoboss/egui-baseview.git?branch=realearn#278b7434577ecb0c0ef75fe7a50f55b1ba66869d" +dependencies = [ + "baseview", + "copypasta", + "egui", + "egui_glow", + "keyboard-types", + "raw-window-handle 0.4.3", + "webbrowser", +] + +[[package]] +name = "egui-toast" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0aebd18cd109a99d787771c7b236fc2727379150b63cfe39d88bbf313e08a17" +dependencies = [ + "egui", +] + +[[package]] +name = "egui_extras" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f051342e97dfa2445107cb7d2e720617f5c840199b5cb4fe0ffcf481fcf5cce" +dependencies = [ + "egui", + "serde", +] + +[[package]] +name = "egui_glow" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8257332fb168a965b3dca81d6a344e053153773c889cabdba0b3b76f1629ae81" +dependencies = [ + "bytemuck", + "egui", + "glow", + "memoffset 0.6.4", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "emath" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ecd80612937e0267909d5351770fe150004e24dab93954f69ca62eecd3f77e" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "embed-resource" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bde55e389bea6a966bd467ad1ad7da0ae14546a5bc794d16d1e55e7fca44881" +dependencies = [ + "cc 1.1.10", + "memchr", + "rustc_version 0.4.0", + "toml 0.8.8", + "vswhom", + "winreg", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "enigo" +version = "0.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce8d7672e87b3155fd5e8a9226276da5c833e15bc879c7b98a78f743b67814" +dependencies = [ + "core-graphics 0.18.0", + "libc", + "objc", + "pkg-config", + "unicode-segmentation", + "winapi", +] + +[[package]] +name = "enum-map" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a56d54c8dd9b3ad34752ed197a4eb2a6601bc010808eb097a04a58ae4c43e1" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9045e2676cd5af83c3b167d917b0a5c90a4d8e266e2683d6631b235c457fc27" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd53b3fde38a39a06b2e66dc282f3e86191e53bd04cc499929c15742beae3df8" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enumflags2" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "enumset" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" +dependencies = [ + "enumset_derive", + "serde", +] + +[[package]] +name = "enumset_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "envcrypt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e5cd13693829e7659d44b8ed8e00d57fe069139b7e403e0b221bd60004857e" +dependencies = [ + "chacha20poly1305", + "envcrypt-macro", +] + +[[package]] +name = "envcrypt-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4133614f4dd40b8d48777a810b199d545005df184204d6c9a574e5ccd3d442df" +dependencies = [ + "chacha20poly1305", + "proc-macro-error", + "proc-macro2", + "quote", +] + +[[package]] +name = "epaint" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e78b5c58a1f7f621f9d546add2adce20636422c9b251e29f749e8a2f713c95" +dependencies = [ + "ab_glyph", + "ahash 0.8.7", + "atomic_refcell", + "bytemuck", + "ecolor", + "emath", + "nohash-hasher", + "parking_lot", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d05712b2d8d88102bc9868020c9e5c7a1f5527c452b9b97450a1d006140ba7" +dependencies = [ + "serde", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc 1.1.10", + "libc", +] + +[[package]] +name = "error-code" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue 2.4.0", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide 0.7.1", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fasteval" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4cdac9e4065d7c48e30770f8665b8cef9a3a73a63a4056a33a5f395bc7cf75" + +[[package]] +name = "fastrand" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fdeflate" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", +] + +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc 1.1.10", + "lazy_static", + "libc", + "winapi", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide 0.7.1", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fontconfig-parser" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4" +dependencies = [ + "roxmltree 0.18.1", +] + +[[package]] +name = "fontdb" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a6f9af55fb97ad673fb7a69533eb2f967648a06fa21f8c9bb2cd6d33975716" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.9.4", + "slotmap", + "tinyvec", + "ttf-parser 0.24.1", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "full_moon" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ef4f8ad0689d3a86bb483650422d72e6f79a37fdc83ed5426cafe96b776ce1" +dependencies = [ + "bytecount", + "cfg-if", + "derive_more", + "full_moon_derive", + "logos 0.12.1", + "paste", + "serde", + "smol_str", +] + +[[package]] +name = "full_moon_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b4bd12ce56927d1dc5478d21528ea8c4b93ca85ff8f8043b6a5351a2a3c6f7" +dependencies = [ + "indexmap 1.9.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "function_name" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ab577a896d09940b5fe12ec5ae71f9d8211fff62c919c03a3750a9901e98a7" +dependencies = [ + "function_name-proc-macro", +] + +[[package]] +name = "function_name-proc-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand 1.5.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1155db57329dca6d018b61e76b1488ce9a2e5e44028cac420a5898f4fcef63" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "git2" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +dependencies = [ + "bitflags 2.4.1", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glidesort" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e102e6eb644d3e0b186fc161e4460417880a0a0b87d235f2e5b8fb30f2e9e0" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr 1.9.0", + "log", + "regex-automata 0.4.4", + "regex-syntax 0.8.2", +] + +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glow" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" +dependencies = [ + "ahash 0.8.7", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.7", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +dependencies = [ + "hashbrown 0.12.3", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "helgoboss-learn" +version = "0.1.0" +dependencies = [ + "approx", + "base", + "bytemuck", + "derivative", + "derive_more", + "helgoboss-midi", + "image 0.25.2", + "lazycell", + "logos 0.13.0", + "nom", + "num", + "num_enum 0.7.2", + "once_cell", + "partial-min-max", + "reaper-common-types", + "reaper-low", + "regex", + "rosc", + "serde", + "serde_json", + "serde_repr", + "serde_with", + "strum", + "thiserror", + "tracing", +] + +[[package]] +name = "helgoboss-license-api" +version = "0.1.0" +source = "git+https://github.com/helgoboss/helgoboss-license-api#ac05d8e49a10635099176ed0818a421449cea3f9" +dependencies = [ + "anyhow", + "base64 0.21.2", + "clap 4.3.11", + "rmp-serde", + "serde", + "validator", +] + +[[package]] +name = "helgoboss-license-processor" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.21.2", + "ed25519-dalek", + "helgoboss-license-api", + "rand", + "rmp-serde", +] + +[[package]] +name = "helgoboss-midi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc8b9091737ce4a801c6248f600f2f787c16722890daf82187381e65d23214c7" +dependencies = [ + "derive_more", + "doc-comment", + "num_enum 0.5.4", + "serde", + "serde_repr", +] + +[[package]] +name = "helgobox" +version = "2.18.3-pre.1" +dependencies = [ + "ab_glyph", + "anyhow", + "approx", + "arboard", + "ascii", + "askama", + "async-channel 2.1.1", + "atomic", + "auto_impl", + "axum", + "axum-server", + "base", + "baseview", + "bindgen", + "built 0.7.1", + "bytesize", + "cached", + "camino", + "cc 1.0.73", + "chrono", + "crossbeam-channel", + "derivative", + "derive_more", + "dirs", + "dns-lookup", + "edit", + "egui", + "egui-baseview", + "egui_extras", + "either", + "embed-resource", + "enum-map", + "enum_dispatch", + "enumflags2", + "env_logger", + "fasteval", + "fragile", + "futures", + "helgoboss-learn", + "helgoboss-license-api", + "helgoboss-midi", + "helgobox-allocator", + "helgobox-api", + "helgobox-dialogs", + "hex", + "hidapi", + "hostname 0.3.1", + "image 0.25.2", + "imageproc 0.25.0", + "include_dir", + "indexmap 2.1.0", + "itertools 0.12.0", + "libloading 0.8.0", + "maplit", + "metrics", + "metrics-exporter-prometheus", + "mlua", + "nanoid", + "nom", + "num_enum 0.7.2", + "once_cell", + "open", + "palette", + "pathdiff", + "playtime-api", + "playtime-clip-engine", + "pot", + "pot-browser", + "prost", + "qrcode", + "rcgen", + "reaper-common-types", + "reaper-high", + "reaper-low", + "reaper-macros", + "reaper-medium", + "reaper-rx", + "regex", + "reqwest", + "rosc", + "runas", + "rustls", + "rx-util", + "rxrust", + "scopeguard", + "semver 1.0.17", + "serde", + "serde_ini", + "serde_json", + "serde_plain", + "serde_repr", + "serde_with", + "serde_yaml", + "simple_moving_average", + "slug", + "smallvec", + "static_assertions", + "streamdeck", + "strum", + "swell-ui", + "sys-info", + "sysinfo 0.30.5", + "tar", + "tempfile", + "tinyvec", + "tokio", + "tokio-stream", + "tonic", + "tower 0.5.2", + "tower-http", + "tracing", + "tracing-core", + "tracing-subscriber", + "tts", + "url", + "uuid 1.7.0", + "vst", + "walkdir", + "webbrowser", + "whoami", + "wildmatch", + "winapi", + "xcap", + "zstd", +] + +[[package]] +name = "helgobox-allocator" +version = "0.1.0" + +[[package]] +name = "helgobox-api" +version = "0.1.0" +dependencies = [ + "anyhow", + "darling 0.20.10", + "derive_more", + "enum-map", + "enumset", + "heck 0.4.1", + "helgoboss-license-api", + "helgobox-macros", + "mlua", + "num_enum 0.7.2", + "playtime-api", + "reaper-low", + "semver 1.0.17", + "serde", + "serde_json", + "serde_with", + "strum", + "stylua", + "syn 2.0.58", +] + +[[package]] +name = "helgobox-artwork-processor" +version = "0.1.0" +dependencies = [ + "anyhow", + "resvg", +] + +[[package]] +name = "helgobox-dialogs" +version = "0.1.0" +dependencies = [ + "derive_more", + "indexmap 2.1.0", +] + +[[package]] +name = "helgobox-extension" +version = "0.1.0" +dependencies = [ + "anyhow", + "libloading 0.8.0", + "reaper-fluent", + "reaper-low", + "reaper-macros", + "reaper-medium", +] + +[[package]] +name = "helgobox-macros" +version = "0.1.0" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hidapi" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" +dependencies = [ + "cc 1.1.10", + "cfg-if", + "libc", + "pkg-config", + "windows-sys 0.48.0", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows 0.52.0", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.6", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.6", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6884a48c6826ec44f524c7456b163cebe9e55a18d7b5e307cb4f100371cc767" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +dependencies = [ + "android_system_properties", + "core-foundation-sys 0.8.7", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.4", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "image" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imageproc" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a0d7770f428b4615960cc8602775d1f04c75d41b0ccdef862e889ebaae9bbf" +dependencies = [ + "ab_glyph", + "approx", + "conv", + "getrandom", + "image 0.25.2", + "itertools 0.12.0", + "nalgebra", + "num", + "rand", + "rand_distr", + "rayon", +] + +[[package]] +name = "imageproc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d" +dependencies = [ + "ab_glyph", + "approx", + "getrandom", + "image 0.25.2", + "itertools 0.12.0", + "nalgebra", + "num", + "rand", + "rand_distr", + "rayon", +] + +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.2", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.2", + "io-lifetimes", + "rustix 0.37.13", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "kinded" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4bdbb2f423660b19f0e9f7115182214732d8dd5f840cd0a3aee3e22562f34c" +dependencies = [ + "kinded_macros", +] + +[[package]] +name = "kinded_macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13b4ddc5dcb32f45dac3d6f606da2a52fdb9964a18427e63cd5ef6c0d13288d" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "lexical-sort" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09e4591611e231daf4d4c685a66cb0410cc1e502027a20ae55f2bb9e997207a" +dependencies = [ + "any_ascii", +] + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "cc 1.1.10", + "pkg-config", +] + +[[package]] +name = "libfuzzer-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf184a4b6b274f82a5df6b357da6055d3e82272327bba281c28bbba6f1664ef" +dependencies = [ + "arbitrary", + "cc 1.1.10", +] + +[[package]] +name = "libgit2-sys" +version = "0.16.1+1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" +dependencies = [ + "cc 1.1.10", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc 1.1.10", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +dependencies = [ + "cc 1.1.10", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "value-bag", +] + +[[package]] +name = "logos" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" +dependencies = [ + "logos-derive 0.12.1", +] + +[[package]] +name = "logos" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" +dependencies = [ + "logos-derive 0.13.0", +] + +[[package]] +name = "logos-codegen" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.25", + "syn 2.0.58", +] + +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.25", + "syn 1.0.109", +] + +[[package]] +name = "logos-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2994eeba8ed550fd9b47a0b38f0242bc3344e496483c6180b69139cc2fa5d1d7" +dependencies = [ + "hashbrown 0.14.3", +] + +[[package]] +name = "lua-src" +version = "547.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42" +dependencies = [ + "cc 1.1.10", +] + +[[package]] +name = "luajit-src" +version = "210.5.5+f2336c4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bcba9790f4e3b1c1467d75cdd011a63bbe6bc75da95af5d2cb4e3631f939c4" +dependencies = [ + "cc 1.1.10", + "which 5.0.0", +] + +[[package]] +name = "luau0-src" +version = "0.11.2+luau653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02313a53daf1fae25e82f7e7ca56180b72d1f08c514426672877cd957298201c" +dependencies = [ + "cc 1.1.10", +] + +[[package]] +name = "macos-accessibility-client" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf7710fbff50c24124331760978fb9086d6de6288dcdb38b25a97f8b1bdebbb" +dependencies = [ + "core-foundation 0.9.1", + "core-foundation-sys 0.8.7", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "memmap2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metrics" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b9e10a211c839210fd7f99954bda26e5f8e26ec686ad68da6a32df7c80e782" +dependencies = [ + "ahash 0.8.7", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a4c4718a371ddfb7806378f23617876eea8b82e5ff1324516bcd283249d9ea" +dependencies = [ + "base64 0.21.2", + "indexmap 1.9.3", + "metrics", + "metrics-util", + "quanta", + "thiserror", +] + +[[package]] +name = "metrics-util" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2670b8badcc285d486261e2e9f1615b506baff91427b61bd336a472b65bbf5ed" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.13.1", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mlua" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea43c3ffac2d0798bd7128815212dd78c98316b299b7a902dabef13dc7b6b8d" +dependencies = [ + "anyhow", + "bstr 1.9.0", + "either", + "erased-serde", + "libloading 0.8.0", + "mlua-sys", + "num-traits", + "parking_lot", + "rustc-hash 2.1.0", + "serde", + "serde-value", +] + +[[package]] +name = "mlua-sys" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a11d485edf0f3f04a508615d36c7d50d299cf61a7ee6d3e2530651e0a31771" +dependencies = [ + "cc 1.1.10", + "cfg-if", + "lua-src", + "luajit-src", + "luau0-src", + "pkg-config", +] + +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum 0.5.4", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling 0.13.4", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags 1.3.2", + "cc 1.1.10", + "cfg-if", + "libc", + "memoffset 0.6.4", +] + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.4", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "normpath" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.2", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +dependencies = [ + "derivative", + "num_enum_derive 0.5.4", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive 0.7.2", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "nutype" +version = "0.4.1-beta.1" +source = "git+https://github.com/helgoboss/nutype.git?branch=feature%2Fnew_unchecked_const#e186e09268b5f411d502b0afaa6678532aabf970" +dependencies = [ + "nutype_macros", +] + +[[package]] +name = "nutype_macros" +version = "0.4.1-beta.1" +source = "git+https://github.com/helgoboss/nutype.git?branch=feature%2Fnew_unchecked_const#e186e09268b5f411d502b0afaa6678532aabf970" +dependencies = [ + "cfg-if", + "kinded", + "proc-macro2", + "quote", + "syn 2.0.58", + "urlencoding", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe31e5425d3d0b89a15982c024392815da40689aceb34bad364d58732bcfd649" +dependencies = [ + "block2", + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc 1.1.10", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "open" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "opener" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8df34be653210fbe9ffaff41d3b92721c56ce82dfee58ee684f9afb5e3a90c0" +dependencies = [ + "bstr 1.9.0", + "dbus", + "normpath", + "url", + "windows-sys 0.52.0", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-multimap" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d6a8c22fc714f0c2373e6091bf6f5e9b37b1bc0b1184874b7e0a4e303d318f" +dependencies = [ + "dlv-list", + "hashbrown 0.14.3", +] + +[[package]] +name = "os_info" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ca711d8b83edbb00b44d504503cd247c9c0bd8b0fa2694f2a1a3d8165379ce" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser 0.25.0", +] + +[[package]] +name = "palette" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d38e6e5ca1612e2081cc31188f08c3cba630ce4ba44709a153f1a0f38d678f2" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05d1c929301fee6830dafa764341118829b2535c216b0571e3821ecac5c885b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "partial-min-max" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6448add382c60bbbc64f9dab41309a12ec530c05191601042f911356ac09758c" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.2", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher 0.3.7", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + +[[package]] +name = "playtime-api" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.21.2", + "camino", + "chrono", + "derive_more", + "helgobox-macros", + "nanoid", + "num_enum 0.7.2", + "reaper-common-types", + "reaper-low", + "rmp-serde", + "serde", + "serde_json", + "strum", + "thiserror", +] + +[[package]] +name = "playtime-clip-engine" +version = "2.0.0" +dependencies = [ + "anyhow", + "atomic", + "base", + "bytemuck", + "camino", + "chrono", + "crossbeam-channel", + "derivative", + "derive_more", + "ebur128", + "either", + "enumflags2", + "envcrypt", + "erased-serde", + "fragile", + "function_name", + "futures", + "glidesort", + "helgoboss-learn", + "helgoboss-license-api", + "helgoboss-license-processor", + "helgoboss-midi", + "helgobox-allocator", + "itertools 0.12.0", + "libc", + "nanoid", + "num", + "once_cell", + "path-slash", + "pathdiff", + "playtime-api", + "reaper-high", + "reaper-low", + "reaper-medium", + "rstest", + "rtrb", + "semver 1.0.17", + "serde", + "slug", + "static_assertions", + "strum", + "swell-ui", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "tracing-test", + "winapi", +] + +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.1", +] + +[[package]] +name = "polling" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92341d779fa34ea8437ef4d82d440d5e1ce3f3ff7f824aa64424cd481f9a1f25" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "pot" +version = "0.1.0" +dependencies = [ + "anyhow", + "base", + "camino", + "chrono", + "derivative", + "derive_more", + "dirs", + "either", + "enum-map", + "enumset", + "futures", + "helgobox-api", + "itertools 0.12.0", + "lexical-sort", + "nanoid", + "once_cell", + "reaper-high", + "reaper-medium", + "regex", + "riff-io", + "rmp-serde", + "rppxml-parser", + "rusqlite", + "rust-ini", + "sanitize-filename", + "serde", + "serde_json", + "splitty", + "strum", + "tempfile", + "tokio", + "tracing", + "walkdir", + "wildmatch", +] + +[[package]] +name = "pot-browser" +version = "0.1.0" +dependencies = [ + "anyhow", + "base", + "bytesize", + "camino", + "chrono", + "crossbeam-channel", + "dirs", + "egui", + "egui-toast", + "egui_extras", + "helgobox-api", + "lru", + "opener", + "pot", + "pulldown-cmark", + "reaper-high", + "reaper-medium", + "rfd", + "strum", + "swell-ui", + "url", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "prettyplease" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" +dependencies = [ + "proc-macro2", + "syn 2.0.58", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" +dependencies = [ + "anyhow", + "itertools 0.12.0", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" +dependencies = [ + "bitflags 1.3.2", + "getopts", + "memchr", + "unicase", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" +dependencies = [ + "image 0.25.2", +] + +[[package]] +name = "quanta" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d2fb862b7ba45e615c1429def928f2e15f815bdf933b27a2d3824e224c1f46" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.0", + "rustls", + "socket2 0.5.5", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash 2.1.0", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.5", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rav1e" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c383692a5e7abd9f6d1eddb1a5e0269f859392387883361bb09e5555852ec1" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built 0.5.2", + "cfg-if", + "interpolate_name", + "itertools 0.10.5", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "rand", + "rand_chacha", + "rust_hawktracer", + "rustc_version 0.4.0", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "badc69028460108fa7e32d4aec2b0c980710d7a31a896864002c8c1fc61516ee" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +dependencies = [ + "bitflags 2.4.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d918c80c5a4c7560db726763020bd16db179e4d5b828078842274a443addb5d" +dependencies = [ + "pem", + "ring", + "time 0.3.14", + "yasna", +] + +[[package]] +name = "readkey" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d401b6d6a1725a59f1b4e813275d289dff3ad09c72b373a10a7a8217ba3146" + +[[package]] +name = "readmouse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be105c72a1e6a5a1198acee3d5b506a15676b74a02ecd78060042a447f408d94" + +[[package]] +name = "realearn-csi" +version = "0.1.0" +dependencies = [ + "base", + "derive_more", + "helgoboss-midi", + "helgobox-api", + "nom", +] + +[[package]] +name = "reaper-common-types" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "hex-literal", + "nutype", + "palette", + "serde", +] + +[[package]] +name = "reaper-fluent" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "fragile", + "reaper-low", + "reaper-medium", +] + +[[package]] +name = "reaper-high" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "backtrace", + "base64 0.13.0", + "c_str_macro", + "camino", + "crossbeam-channel", + "csv", + "derivative", + "either", + "enumflags2", + "fragile", + "futures", + "helgoboss-midi", + "once_cell", + "pathdiff", + "reaper-low", + "reaper-medium", + "ref-cast", + "regex", + "sentry", + "serde", + "tracing", + "winapi", +] + +[[package]] +name = "reaper-low" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "c_str_macro", + "cc 1.1.10", + "derive_more", + "downcast-rs", + "fragile", + "libc", + "tracing", + "vst", + "winapi", +] + +[[package]] +name = "reaper-macros" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "darling 0.10.2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "reaper-medium" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "camino", + "derive_more", + "enumflags2", + "helgoboss-midi", + "libc", + "reaper-common-types", + "reaper-low", + "ref-cast", + "serde", + "tracing", + "vst", + "winapi", +] + +[[package]] +name = "reaper-rx" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "helgoboss-midi", + "reaper-high", + "reaper-medium", + "rxrust", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall 0.2.10", +] + +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.4", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.25", +] + +[[package]] +name = "regex-automata" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "result" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" + +[[package]] +name = "resvg" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958" +dependencies = [ + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg", +] + +[[package]] +name = "rfd" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9e7b57df6e8472152674607f6cc68aa14a748a3157a857a94f516e11aeacc2" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle 0.5.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "riff-io" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc9f5158c1cd3a4b90eb096b5659b27a977f33d52aeb20220846a919de79860" +dependencies = [ + "memmap", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc 1.1.10", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rmp" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rosc" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e63d9e6b0d090be1485cf159b1e04c3973d2d3e1614963544ea2ff47a4a981" +dependencies = [ + "byteorder", + "nom", +] + +[[package]] +name = "roxmltree" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rppxml-parser" +version = "0.1.0" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#218ff0806fcdc90c73d10dc82f062cec04957e6c" +dependencies = [ + "splitty", +] + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.0", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.0", + "syn 2.0.58", + "unicode-ident", +] + +[[package]] +name = "rtrb" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636b228b5adb32add7f0e41e5084d38aa66fb0f942e8a91751c1e90023288fbe" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "runas" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49535b7c73aec5596ae2c44a6d8a7a8f8592e5744564c327fd4846750413d921" +dependencies = [ + "libc", + "security-framework-sys", + "which 4.4.2", + "windows-sys 0.48.0", +] + +[[package]] +name = "rusqlite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" +dependencies = [ + "bitflags 2.4.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rust-ini" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rust_hawktracer" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3480a29b927f66c6e06527be7f49ef4d291a01d694ec1fe85b0de71d6b02ac1" +dependencies = [ + "rust_hawktracer_normal_macro", + "rust_hawktracer_proc_macro", +] + +[[package]] +name = "rust_hawktracer_normal_macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a570059949e1dcdc6f35228fa389f54c2c84dfe0c94c05022baacd56eacd2e9" + +[[package]] +name = "rust_hawktracer_proc_macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb626abdbed5e93f031baae60d72032f56bc964e11ac2ff65f2ba3ed98d6d3e1" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.17", +] + +[[package]] +name = "rustix" +version = "0.37.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys 0.4.13", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustls" +version = "0.23.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "rustybuzz" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" +dependencies = [ + "bitflags 2.4.1", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser 0.24.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rx-util" +version = "0.1.0" +dependencies = [ + "rxrust", +] + +[[package]] +name = "rxrust" +version = "0.13.0" +source = "git+https://github.com/rxRust/rxRust?rev=349e50b3197e05926e2378ef8fc45cb67ad43b83#349e50b3197e05926e2378ef8fc45cb67ad43b83" +dependencies = [ + "async-std", + "futures", + "smallvec", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sanitize-filename" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "sentry" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016958f51b96861dead7c1e02290f138411d05e94fad175c8636a835dee6e51e" +dependencies = [ + "httpdate", + "reqwest", + "rustls", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", + "webpki-roots", +] + +[[package]] +name = "sentry-backtrace" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57712c24e99252ef175b4b06c485294f10ad6bc5b5e1567ff3803ee7a0b7d3f" +dependencies = [ + "backtrace", + "once_cell", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba8754ec3b9279e00aa6d64916f211d44202370a1699afde1db2c16cbada089" +dependencies = [ + "hostname 0.4.0", + "libc", + "os_info", + "rustc_version 0.4.0", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8b6dcd4fbae1e3e22b447f32670360b27e31b62ab040f7fb04e0f80c04d92" +dependencies = [ + "once_cell", + "rand", + "sentry-types", + "serde", + "serde_json", +] + +[[package]] +name = "sentry-debug-images" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8982a69133d3f5e4efdbfa0776937fca43c3a2e275a8fe184f50b1b0aa92e07c" +dependencies = [ + "findshlibs", + "once_cell", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de296dae6f01e931b65071ee5fe28d66a27909857f744018f107ed15fd1f6b25" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263f73c757ed7915d3e1e34625eae18cad498a95b4261603d4ce3f87b159a6f0" +dependencies = [ + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a71ed3a389948a6a6d92b98e997a2723ca22f09660c5a7b7388ecd509a70a527" +dependencies = [ + "debugid", + "hex", + "rand", + "serde", + "serde_json", + "thiserror", + "time 0.3.14", + "url", + "uuid 1.7.0", +] + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "serde_ini" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb236687e2bb073a7521c021949be944641e671b8505a94069ca37b656c81139" +dependencies = [ + "result", + "serde", + "void", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa 1.0.6", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa 1.0.6", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.6", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c9fdb6b00a489875b22efd4b78fe2b363b72265cc5f6eb2e2b9ee270e6140c" +dependencies = [ + "base64 0.21.2", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.14", +] + +[[package]] +name = "serde_with_macros" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff351eb4b33600a2e138dfa0b10b65a238ea8ff8fb2387c422c5022a3e8298" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "serde_yaml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad104641f3c958dab30eb3010e834c2622d1f3f4c530fef1dee20ad9485f3c09" +dependencies = [ + "dtoa", + "indexmap 1.9.3", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" + +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "similar" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +dependencies = [ + "serde", +] + +[[package]] +name = "simple_moving_average" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4b144ad185430cd033299e2c93e465d5a7e65fbb858593dc57181fa13cd310" +dependencies = [ + "num-traits", +] + +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "simplelog" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" +dependencies = [ + "log", + "termcolor", + "time 0.3.14", +] + +[[package]] +name = "siphasher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "sketches-ddsketch" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a406c1882ed7f29cd5e248c9848a80e7cb6ae0fea82346d2746f2f941c07e1" + +[[package]] +name = "slab" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags 1.3.2", + "dlib", + "lazy_static", + "log", + "memmap2 0.5.7", + "nix 0.24.2", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "smol_str" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "speech-dispatcher" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e891b686b175e04b1a81d56f869d64a2993fe2ab1f53ecc4d3249b2fb72c82b" +dependencies = [ + "lazy_static", + "speech-dispatcher-sys", +] + +[[package]] +name = "speech-dispatcher-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3e8acdf2b1f4bb13f1813b40b52f3edf4cc94d8a55fe713a584f672a10388d" +dependencies = [ + "bindgen", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "splitty" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dae68aa5bd5dc2d3a2137b0f6bcdd8255dce1983dc155fe0246572e179c9c3a" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str-buf" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" + +[[package]] +name = "streamdeck" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67325ca3ab85003cac6eb10395aa8c7506b4c273ff48513604e658b85f77ea72" +dependencies = [ + "ab_glyph", + "hidapi", + "humantime", + "image 0.25.2", + "imageproc 0.24.0", + "log", + "simplelog", + "structopt", + "thiserror", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap 2.34.0", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] + +[[package]] +name = "stylua" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db772a1b2de2ba4e1d9750c304dfdf0fc89bb8bfab841fd58f18707aefe62430" +dependencies = [ + "anyhow", + "cfg-if", + "clap 3.2.25", + "console", + "crossbeam-channel", + "ec4rs", + "env_logger", + "full_moon", + "globset", + "ignore", + "lazy_static", + "log", + "num_cpus", + "regex", + "serde", + "serde_json", + "similar", + "thiserror", + "threadpool", + "toml 0.8.8", + "wasm-bindgen", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "svgtypes" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" +dependencies = [ + "kurbo", + "siphasher 1.0.1", +] + +[[package]] +name = "swell-ui" +version = "0.1.0" +dependencies = [ + "base", + "fragile", + "libloading 0.8.0", + "objc2", + "palette", + "raw-window-handle 0.4.3", + "reaper-common-types", + "reaper-low", + "reaper-medium", + "rxrust", + "scopeguard", + "tracing", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "sys-info" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" +dependencies = [ + "cc 1.1.10", + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.30.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" +dependencies = [ + "cfg-if", + "core-foundation-sys 0.8.7", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows 0.52.0", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-deps" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" +dependencies = [ + "cfg-expr", + "heck 0.4.1", + "pkg-config", + "toml 0.5.8", + "version-compare", +] + +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall 0.2.10", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +dependencies = [ + "itoa 1.0.6", + "libc", + "num_threads", + "serde", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.21.0", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2 0.5.5", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "http", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +dependencies = [ + "ansi_term", + "lazy_static", + "matchers", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-test" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a2c0ff408fe918a94c428a3f2ad04e4afd5c95bbc08fcf868eff750c15728a4" +dependencies = [ + "lazy_static", + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bc1c4f8e2e73a977812ab339d503e6feeb92700f6d07a6de4d321522d5c08" +dependencies = [ + "lazy_static", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" +dependencies = [ + "core_maths", +] + +[[package]] +name = "ttf-parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" + +[[package]] +name = "tts" +version = "0.24.2" +source = "git+https://github.com/helgoboss/tts-rs?branch=helgoboss-fixes#0e6ffe3ed1854b742c093d162cb3031ba1f6ec5d" +dependencies = [ + "cocoa-foundation", + "core-foundation 0.9.1", + "dyn-clonable", + "jni 0.19.0", + "lazy_static", + "libc", + "log", + "ndk-glue", + "objc", + "speech-dispatcher", + "thiserror", + "unic-langid", + "wasm-bindgen", + "web-sys", + "windows 0.42.0", +] + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" + +[[package]] +name = "unicode-ccc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f91c8b21fbbaa18853c3d0801c78f4fc94cdb976699bb03e832e75f7fd22f0" + +[[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna 0.5.0", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "usvg" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher 1.0.1", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "validator" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd" +dependencies = [ + "idna 0.4.0", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "vst" +version = "0.4.0" +source = "git+https://github.com/helgoboss/vst-rs.git?branch=feature%2Fparam-props#6270170beec99e8185128046a0eb15485de99fbc" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libloading 0.7.3", + "log", + "num-traits", + "num_enum 0.5.4", +] + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f5402d3d0e79a069714f7b48e3ecc60be7775a2c049cb839457457a239532" +dependencies = [ + "cc 1.1.10", + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.58", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73157efb9af26fb564bb59a009afd1c7c334a44db171d280690d0c3faaec3468" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.2", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.2", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.2", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71" +dependencies = [ + "core-foundation 0.9.1", + "home", + "jni 0.21.1", + "log", + "ndk-context", + "objc", + "raw-window-handle 0.5.0", + "url", + "web-sys", +] + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc 1.1.10", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.13", +] + +[[package]] +name = "which" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.13", + "windows-sys 0.48.0", +] + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wide" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "wildmatch" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0286ba339aa753e70765d521bb0242cc48e1194562bfa2a2ad7ac8a6de28f5d5" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "x11" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ae97874a928d821b061fce3d1fc52f08071dd53c89a6102bc06efcac3b2908" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a7468a5768fea473e6c8c0d4b60d6d7001a64acceaac267207ca0281e1337e8" +dependencies = [ + "xcb 1.4.0", +] + +[[package]] +name = "x11rb" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" +dependencies = [ + "gethostname", + "nix 0.26.4", + "winapi", + "winapi-wsapoll", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" +dependencies = [ + "nix 0.26.4", +] + +[[package]] +name = "xattr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +dependencies = [ + "libc", +] + +[[package]] +name = "xcap" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a584e18d74df1db4bd35947d61e70c25c81a73db48add6eb4e00ad2227a51258" +dependencies = [ + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "dbus", + "image 0.25.2", + "log", + "percent-encoding", + "sysinfo 0.31.4", + "thiserror", + "windows 0.58.0", + "xcb 1.4.0", +] + +[[package]] +name = "xcb" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62056f63138b39116f82a540c983cc11f1c90cd70b3d492a70c25eaa50bd22a6" +dependencies = [ + "libc", + "log", + "x11", +] + +[[package]] +name = "xcb" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e75181b5a62b6eeaa72f303d3cef7dbb841e22885bf6d3e66fe23e88c55dc6" +dependencies = [ + "bitflags 1.3.2", + "libc", + "quick-xml", +] + +[[package]] +name = "xcb-util" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43893e47f27bf7d81d489feef3a0e34a457e90bc314b7e74ad9bb3980e4c1c48" +dependencies = [ + "libc", + "xcb 0.9.0", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time 0.3.14", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "zeroize" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc 1.1.10", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] diff --git a/plugin-reaper-realearn/Cargo.toml b/plugin-reaper-realearn/Cargo.toml new file mode 100644 index 0000000..9de69d9 --- /dev/null +++ b/plugin-reaper-realearn/Cargo.toml @@ -0,0 +1,272 @@ +[workspace] +resolver = "2" +members = [ + "allocator", + "api", + "dialogs", + "extension", + "macros", + "playtime-clip-engine", + "playtime-api", + "pot", + "pot-browser", + "csi", + "main", + "rx-util", + "swell-ui", + "base", + "artwork-processor", + # Will probably be excluded from the workspace in future + "main/lib/helgoboss-learn" +] + +[workspace.dependencies] +# Own +base = { path = "base" } +pot = { path = "pot" } +pot-browser = { path = "pot-browser" } +helgobox-dialogs = { path = "dialogs" } +reaper-common-types = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" } +reaper-rx = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" } +reaper-fluent = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" } +reaper-high = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master", features = ["serde"] } +reaper-medium = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master", features = ["serde"] } +reaper-low = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" } +reaper-macros = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" } +rppxml-parser = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" } +swell-ui = { path = "swell-ui" } +rx-util = { path = "rx-util" } +playtime-clip-engine = { path = "playtime-clip-engine" } +helgobox-api = { path = "api" } +helgobox-macros = { path = "macros" } +helgobox-allocator = { path = "allocator" } +playtime-api = { path = "playtime-api" } +realearn-csi = { path = "csi" } +helgoboss-learn = { path = "main/lib/helgoboss-learn", features = ["reaper-low"] } +helgoboss-midi = { version = "0.4", features = ["serde", "serde_repr"] } +helgoboss-license-api = { git = "https://github.com/helgoboss/helgoboss-license-api" } + +# 3rd-party +scopeguard = "1.1.0" +rxrust = { git = "https://github.com/rxRust/rxRust", rev = "349e50b3197e05926e2378ef8fc45cb67ad43b83" } +indexmap = "2.1.0" +itertools = "0.12.0" +wildmatch = "2.1.0" +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = "1.0" +serde_plain = "1.0.2" +derive_more = "0.99.16" +tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros", "time"] } +enum-map = { version = "2.4.1", features = ["serde"] } +once_cell = "1.4.0" +strum = { version = "0.25.0", features = ["derive"] } +regex = "1" +walkdir = "2" +either = "1.8.0" +tracing = "0.1.40" +tracing-core = "0.1.32" +tracing-subscriber = "0.3.7" +futures = { version = "0.3", default-features = false } +derivative = "2.2.0" +tempfile = "3.1.0" +xxhash-rust = { version = "0.8.15", features = ["xxh3"] } +crossbeam-channel = "0.5" +futures-timer = "3.0.2" +metrics = "0.22.0" +ascii = "1.0" +winapi = "0.3" +bindgen = "0.69.2" +enumflags2 = "0.7.4" +nanoid = "0.4.0" +slug = "0.1.4" +num_enum = "0.7.2" +nom = "7.0.0" +semver = { version = "1.0.17", features = ["serde"] } +enumset = "1.0.12" +mlua = { version = "0.10.2", features = ["vendored", "luau", "serialize", "anyhow"] } +chrono = "0.4.11" +dirs = "5.0.1" +libloading = "0.8" +tokio-stream = { version = "0.1.8", features = ["sync"] } +raw-window-handle = "0.4.2" +egui = "0.21.0" +egui_extras = "0.21.0" +egui-toast = "0.6.0" +bytesize = "1.0.1" +hex = "0.4.2" +rmp-serde = "1.1.1" +anyhow = "1.0.71" +thiserror = "1.0.45" +enum_dispatch = "0.3.6" +simple_moving_average = "1.0.2" +tinyvec = "1.6.0" +erased-serde = "0.4.2" +fragile = "2.0.0" +approx = "0.5.1" +serde_repr = "0.1.5" +serde_with = "3.4.0" +lazycell = "1.2" +rosc = "0.10.1" +rust-ini = "0.20.0" +function_name = "0.3.0" +num = "0.4.1" +logos = "0.13.0" +camino = "1.1.7" +auto_impl = "1.1.0" +bytemuck = "1.13.1" +palette = "0.7.4" +libc = "0.2.153" +path-slash = "0.2.1" +pathdiff = "0.2.1" +open = "5.0.1" +url = "2.5.2" +atomic = "0.6.0" +static_assertions = "1.1.0" +image = { version = "0.25.2", default-features = false } +cached = "0.53.1" +imageproc = "0.25.0" +tower = "0.5.2" +axum = "0.7.9" +axum-server = "0.7.1" +tower-http = "0.6.2" +tonic = "0.12.3" +prost = "0.13.4" +rcgen = "0.12.0" +dns-lookup = "2.0.4" +hostname = "^0.3" +askama = "0.12.1" +serde_ini = "0.2.0" +webbrowser = "0.8.12" +runas = "1.1.0" +qrcode = "0.14.1" +uuid = "1.6.1" +vst = "0.4.0" +c_str_macro = "1.0.2" +arboard = "3.3.0" +smallvec = "1.7.0" +backtrace = "0.3.74" +serde_yaml = "0.8.17" +fasteval = { version = "0.2.4", default-features = false } +maplit = "1.0.2" +metrics-exporter-prometheus = { version = "0.13.0", default-features = false } +embed-resource = "2.4.1" +sysinfo = "0.30.5" +sys-info = "0.9.1" +# We use a special version because of "link_lib_modifiers", which allows us to add "+whole-archive" +# in a convenient way. TODO-low-wait https://github.com/rust-lang/cc-rs/pull/671 +cc = { git = "https://github.com/petrochenkov/cc-rs.git", rev = "4d52bd211aeb2b4ddccd1b9c0a0841e03aaaef7c" } +built = "0.7.1" +tts = { git = "https://github.com/helgoboss/tts-rs", branch = "helgoboss-fixes" } +edit = { git = "https://github.com/helgoboss/edit", branch = "realearn" } +async-channel = "2.1.1" +env_logger = "0.10.1" +egui-baseview = { git = "https://github.com/helgoboss/egui-baseview.git", branch = "realearn" } +baseview = { git = "https://github.com/helgoboss/baseview.git", branch = "realearn" } +include_dir = "0.7.3" +whoami = "1.4.1" +streamdeck = "0.9.0" +ab_glyph = "0.2.29" +hidapi = "2.4" +xcap = "0.0.13" +syn = "2.0.48" +darling = "0.20.3" +heck = "0.4.0" +stylua = "0.19.1" +resvg = "0.44.0" +enigo = "0.0.14" +device_query = "1.1.1" +macos-accessibility-client = "0.0.1" +base64 = "0.21.2" +rtrb = "0.3.0" +envcrypt = "0.5.0" +glidesort = "0.1.2" +ebur128 = "0.1.8" +tracing-test = "0.2.4" +rstest = "0.18.2" +lexical-sort = "0.3.1" +splitty = "1.0.1" +riff-io = "0.1.2" +rusqlite = "0.30.0" +sanitize-filename = "0.5.0" +lru = "0.12.1" +pulldown-cmark = "0.9.2" +rfd = "0.12.1" +opener = "0.7.1" +objc2 = "0.3.0-beta.3" +reqwest = { version = "0.12.9", default-features = false } +rustls = { version = "0.23.20", default-features = false } + +[profile.release] +debug = 2 + +[profile.release-strip] +inherits = "release" +# We use this profile on Linux and macOS only. To support Windows 7, we should build with Rust +# version 1.77.2 on Windows. This old version strips away too much due to an implementation +# error in rustc: +# +# "Prior to 1.79, this unintentionally disabled the generation of *.pdb files on MSVC, resulting +# in the absence of symbols." +# (https://doc.rust-lang.org/rustc/codegen-options/index.html#strip). +# +# Not stripping on Windows is okay because the PDB is a separate file anyway. We do the following +# mainly for macOS. There's a long reasoning about the concrete values in CONTRIBUTING.adoc. +# BTW, switching to "symbols" would reduce size even more but leads to useless stack traces when +# panicking. +strip = "debuginfo" +split-debuginfo = "packed" + +[profile.dev-llvm-out-of-memory-fix] +inherits = "dev" +# Lately, i686 Windows builds often fail due to "LLVM ERROR: out of memory". +# Lowering the amount of debug information included in the binary is supposed to fix that. +debug = 1 + +[profile.release-llvm-out-of-memory-fix] +inherits = "release" +# Lately, i686 Windows builds often fail due to "LLVM ERROR: out of memory". +# Lowering the amount of debug information included in the binary is supposed to fix that. +debug = 1 + +[patch.crates-io] + +# TODO-low-wait +# I absolutely need Flutter to be able to connect with ReaLearn based on a LAN IP address. Without this hack, Flutter +# will fail to connect with a TLSV1_ALERT_DECODE_ERROR. As soon as https://github.com/briansmith/webpki/issues/54 is +# closed and rustls has updated the dependency, we should remove this! +#webpki = { git = "https://github.com/helgoboss/webpki.git", branch = "workaround/54-alert-decode-error-0.22.0" } + +# Yes! Thanks to the use of axum-server (instead of warp) we don't need this Hyper patch anymore! axum-server seems to +# use lower-level Hyper features and implements a proper graceful shutdown feature on it that allows one to specify a +# timeout. If not all connections are closed until that timeout, it just shuts down forcibly. That's important and +# exactly what we need. We need the server shutdown happen immediately before ReaLearn is unloaded without having to +# force the user to wait for open connections to finish. The following was a temporary hack to make this possible. +# See https://github.com/hyperium/hyper/issues/1885, https://github.com/hyperium/hyper/issues/2386. +# For a moment, I thought that I need to reintroduce this patch for gRPC, which is driven by tonic. Indeed, when using +# tonic's serve_with_shutdown() feature, REAPER doesn't quit until Playtime gRPC client has disconnected :/ But +# fortunately, using the tokio::select! workaround (mentioned in issue 2386) works and *also* closes the ports this +# time! Turns out the same is actually true for axum, so we use select! there as well. +#hyper = { git = "https://github.com/helgoboss/hyper.git", branch = "feature/realearn" } + +# We need to use our on "vst" crate that contains a bunch of improvements +vst = { git = "https://github.com/helgoboss/vst-rs.git", branch = "feature/param-props" } +#vst = { path = "../vst-rs" } + +# This is for temporary development with local reaper-rs. +#[patch.'https://github.com/helgoboss/reaper-rs.git'] +#reaper-common-types = { path = "../reaper-rs/main/common-types" } +#reaper-fluent = { path = "../reaper-rs/main/fluent" } +#reaper-high = { path = "../reaper-rs/main/high" } +#reaper-medium = { path = "../reaper-rs/main/medium" } +#reaper-macros = { path = "../reaper-rs/main/macros" } +#reaper-low = { path = "../reaper-rs/main/low" } +#reaper-rx = { path = "../reaper-rs/main/rx" } +#rppxml-parser = { path = "../reaper-rs/main/rppxml-parser" } + +## This is for temporary development with local egui-baseview. +#[patch.'https://github.com/helgoboss/egui-baseview.git'] +#egui-baseview = { path = "../egui-baseview" } +# +## This is for temporary development with local baseview. +#[patch.'https://github.com/helgoboss/baseview.git'] +#baseview = { path = "../baseview" } \ No newline at end of file diff --git a/plugin-reaper-realearn/Cross.toml b/plugin-reaper-realearn/Cross.toml new file mode 100644 index 0000000..28af009 --- /dev/null +++ b/plugin-reaper-realearn/Cross.toml @@ -0,0 +1,5 @@ +[target.aarch64-unknown-linux-gnu] +pre-build = ["dpkg --add-architecture arm64 && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y python3 php libxdo-dev:arm64 libudev-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxcb-dri2-0-dev:arm64 libxcb-icccm4-dev:arm64 libx11-xcb-dev:arm64 mesa-common-dev:arm64 libgl1-mesa-dev:arm64 libglu1-mesa-dev:arm64 libspeechd-dev:arm64 libgtk-3-dev:arm64"] + +[target.armv7-unknown-linux-gnueabihf] +pre-build = ["dpkg --add-architecture armhf && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y python3 php libxdo-dev:armhf libudev-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxcb-dri2-0-dev:armhf libxcb-icccm4-dev:armhf libx11-xcb-dev:armhf mesa-common-dev:armhf libgl1-mesa-dev:armhf libglu1-mesa-dev:armhf libspeechd-dev:armhf libgtk-3-dev:armhf"] \ No newline at end of file diff --git a/plugin-reaper-realearn/LICENSE b/plugin-reaper-realearn/LICENSE new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/plugin-reaper-realearn/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/plugin-reaper-realearn/README.adoc b/plugin-reaper-realearn/README.adoc new file mode 100644 index 0000000..c7269e7 --- /dev/null +++ b/plugin-reaper-realearn/README.adoc @@ -0,0 +1,40 @@ += Helgobox: ReaLearn & Playtime +:toc: preamble +:sectnumlevels: 2 + +image:https://github.com/helgoboss/helgobox/actions/workflows/windows-x86_64.yml/badge.svg[Windows x86_64,link=https://github.com/helgoboss/helgobox/actions/workflows/windows-x86_64.yml] +image:https://github.com/helgoboss/helgobox/actions/workflows/windows-i686.yml/badge.svg[Windows i686,link=https://github.com/helgoboss/helgobox/actions/workflows/windows-i686.yml] +image:https://github.com/helgoboss/helgobox/actions/workflows/macos-x86_64.yml/badge.svg[macOS x86_64,link=https://github.com/helgoboss/helgobox/actions/workflows/macos-x86_64.yml] +image:https://github.com/helgoboss/helgobox/actions/workflows/macos-aarch64.yml/badge.svg[macOS aarch64,link=https://github.com/helgoboss/helgobox/actions/workflows/macos-aarch64.yml] +image:https://github.com/helgoboss/helgobox/actions/workflows/linux-x86_64.yml/badge.svg[Linux x86_64,link=https://github.com/helgoboss/helgobox/actions/workflows/linux-x86_64.yml] +image:https://github.com/helgoboss/helgobox/actions/workflows/linux-aarch64.yml/badge.svg[Linux aarch64,link=https://github.com/helgoboss/helgobox/actions/workflows/linux-aarch64.yml] +image:https://github.com/helgoboss/helgobox/actions/workflows/linux-armv7.yml/badge.svg[Linux armv7,link=https://github.com/helgoboss/helgobox/actions/workflows/linux-armv7.yml] +image:https://img.shields.io/badge/license-GPL-blue.svg[GitHub license,link=https://raw.githubusercontent.com/helgoboss/realearn/master/LICENSE] +image:https://img.shields.io/badge/Donate-PayPal-orange.svg[Donate,link=https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9CTAK2KKA8Z2S&source=url] + +Helgobox is a link:https://www.reaper.fm[REAPER] plug-in that unites multiple creative products by link:https://www.helgoboss.org[Helgoboss]. +It currently contains link:https://www.helgoboss.org/projects/realearn[ReaLearn] (a versatile controller integration tool, free) and +link:https://www.helgoboss.org/projects/playtime[Playtime] (a modern session view, paid). + +== Installation + +See section link:https://docs.helgoboss.org/helgobox/installation.html[Installation] of the Helgobox Reference. + +== Usage + +See section link:https://docs.helgoboss.org/helgobox/usage.html[Usage] of the Helgobox Reference. + +== Documentation + +We have: + +- link:https://github.com/helgoboss/helgobox/wiki[Helgobox Wiki] (suitable for beginners, includes links to video tutorials) +- link:https://docs.helgoboss.org[Helgoboss Docs website] (extensive references, includes PDF downloads) + +== Architecture + +See link:ARCHITECTURE.adoc[architecture documentation]. + +== Contributing + +See link:CONTRIBUTING.adoc[contributing documentation]. \ No newline at end of file diff --git a/plugin-reaper-realearn/about.hbs b/plugin-reaper-realearn/about.hbs new file mode 100644 index 0000000..9f7b914 --- /dev/null +++ b/plugin-reaper-realearn/about.hbs @@ -0,0 +1,79 @@ + + + + + + + +
+
+

Third Party Licenses

+

This page lists the licenses of the projects used in the Helgobox Plug-In and + Extension.

+
+ +

Overview of licenses:

+
    + {{#each overview}} +
  • {{name}} ({{count}})
  • + {{/each}} +
+ +

All license text:

+ +
+ + + diff --git a/plugin-reaper-realearn/about.html b/plugin-reaper-realearn/about.html new file mode 100644 index 0000000..4dbfafb --- /dev/null +++ b/plugin-reaper-realearn/about.html @@ -0,0 +1,15279 @@ + + + + + + + +
+
+

Third Party Licenses

+

This page lists the licenses of the projects used in the Helgobox Plug-In and + Extension.

+
+ +

Overview of licenses:

+ + +

All license text:

+
    +
  • +

    Apache License 2.0

    +

    Used by:

    + +
    +                                 Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
    +                                 Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright 2021 Functional Software, Inc. dba Sentry (https://sentry.io)
    +   and individual contributors. All rights reserved.
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
    +                                 Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright [yyyy] [name of copyright owner]
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
    +                                 Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright [yyyy] [name of copyright owner]
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                     Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                     Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright 2020 Sébastien Crozet
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                     Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright [2019] [Mike Heffner]
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                     Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright [yyyy] [name of copyright owner]
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                     Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "{}"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright 2020 Alex Butler
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                     Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "{}"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright {yyyy} {name of copyright owner}
    +
    +   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.
    +
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                     Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +   APPENDIX: How to apply the Apache License to your work.
    +
    +      To apply the Apache License to your work, attach the following
    +      boilerplate notice, with the fields enclosed by brackets "[]"
    +      replaced with your own identifying information. (Don't include
    +      the brackets!)  The text should be enclosed in the appropriate
    +      comment syntax for the file format. We also recommend that a
    +      file or class name and description of purpose be included on the
    +      same "printed page" as the copyright notice for easier
    +      identification within third-party archives.
    +
    +   Copyright [yyyy] [name of copyright owner]
    +
    +   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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
                                  Apache License
    +                        Version 2.0, January 2004
    +                     http://www.apache.org/licenses/
    +
    +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +1. Definitions.
    +
    +   "License" shall mean the terms and conditions for use, reproduction,
    +   and distribution as defined by Sections 1 through 9 of this document.
    +
    +   "Licensor" shall mean the copyright owner or entity authorized by
    +   the copyright owner that is granting the License.
    +
    +   "Legal Entity" shall mean the union of the acting entity and all
    +   other entities that control, are controlled by, or are under common
    +   control with that entity. For the purposes of this definition,
    +   "control" means (i) the power, direct or indirect, to cause the
    +   direction or management of such entity, whether by contract or
    +   otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +   outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +   "You" (or "Your") shall mean an individual or Legal Entity
    +   exercising permissions granted by this License.
    +
    +   "Source" form shall mean the preferred form for making modifications,
    +   including but not limited to software source code, documentation
    +   source, and configuration files.
    +
    +   "Object" form shall mean any form resulting from mechanical
    +   transformation or translation of a Source form, including but
    +   not limited to compiled object code, generated documentation,
    +   and conversions to other media types.
    +
    +   "Work" shall mean the work of authorship, whether in Source or
    +   Object form, made available under the License, as indicated by a
    +   copyright notice that is included in or attached to the work
    +   (an example is provided in the Appendix below).
    +
    +   "Derivative Works" shall mean any work, whether in Source or Object
    +   form, that is based on (or derived from) the Work and for which the
    +   editorial revisions, annotations, elaborations, or other modifications
    +   represent, as a whole, an original work of authorship. For the purposes
    +   of this License, Derivative Works shall not include works that remain
    +   separable from, or merely link (or bind by name) to the interfaces of,
    +   the Work and Derivative Works thereof.
    +
    +   "Contribution" shall mean any work of authorship, including
    +   the original version of the Work and any modifications or additions
    +   to that Work or Derivative Works thereof, that is intentionally
    +   submitted to Licensor for inclusion in the Work by the copyright owner
    +   or by an individual or Legal Entity authorized to submit on behalf of
    +   the copyright owner. For the purposes of this definition, "submitted"
    +   means any form of electronic, verbal, or written communication sent
    +   to the Licensor or its representatives, including but not limited to
    +   communication on electronic mailing lists, source code control systems,
    +   and issue tracking systems that are managed by, or on behalf of, the
    +   Licensor for the purpose of discussing and improving the Work, but
    +   excluding communication that is conspicuously marked or otherwise
    +   designated in writing by the copyright owner as "Not a Contribution."
    +
    +   "Contributor" shall mean Licensor and any individual or Legal Entity
    +   on behalf of whom a Contribution has been received by Licensor and
    +   subsequently incorporated within the Work.
    +
    +2. Grant of Copyright License. Subject to the terms and conditions of
    +   this License, each Contributor hereby grants to You a perpetual,
    +   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +   copyright license to reproduce, prepare Derivative Works of,
    +   publicly display, publicly perform, sublicense, and distribute the
    +   Work and such Derivative Works in Source or Object form.
    +
    +3. Grant of Patent License. Subject to the terms and conditions of
    +   this License, each Contributor hereby grants to You a perpetual,
    +   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +   (except as stated in this section) patent license to make, have made,
    +   use, offer to sell, sell, import, and otherwise transfer the Work,
    +   where such license applies only to those patent claims licensable
    +   by such Contributor that are necessarily infringed by their
    +   Contribution(s) alone or by combination of their Contribution(s)
    +   with the Work to which such Contribution(s) was submitted. If You
    +   institute patent litigation against any entity (including a
    +   cross-claim or counterclaim in a lawsuit) alleging that the Work
    +   or a Contribution incorporated within the Work constitutes direct
    +   or contributory patent infringement, then any patent licenses
    +   granted to You under this License for that Work shall terminate
    +   as of the date such litigation is filed.
    +
    +4. Redistribution. You may reproduce and distribute copies of the
    +   Work or Derivative Works thereof in any medium, with or without
    +   modifications, and in Source or Object form, provided that You
    +   meet the following conditions:
    +
    +   (a) You must give any other recipients of the Work or
    +       Derivative Works a copy of this License; and
    +
    +   (b) You must cause any modified files to carry prominent notices
    +       stating that You changed the files; and
    +
    +   (c) You must retain, in the Source form of any Derivative Works
    +       that You distribute, all copyright, patent, trademark, and
    +       attribution notices from the Source form of the Work,
    +       excluding those notices that do not pertain to any part of
    +       the Derivative Works; and
    +
    +   (d) If the Work includes a "NOTICE" text file as part of its
    +       distribution, then any Derivative Works that You distribute must
    +       include a readable copy of the attribution notices contained
    +       within such NOTICE file, excluding those notices that do not
    +       pertain to any part of the Derivative Works, in at least one
    +       of the following places: within a NOTICE text file distributed
    +       as part of the Derivative Works; within the Source form or
    +       documentation, if provided along with the Derivative Works; or,
    +       within a display generated by the Derivative Works, if and
    +       wherever such third-party notices normally appear. The contents
    +       of the NOTICE file are for informational purposes only and
    +       do not modify the License. You may add Your own attribution
    +       notices within Derivative Works that You distribute, alongside
    +       or as an addendum to the NOTICE text from the Work, provided
    +       that such additional attribution notices cannot be construed
    +       as modifying the License.
    +
    +   You may add Your own copyright statement to Your modifications and
    +   may provide additional or different license terms and conditions
    +   for use, reproduction, or distribution of Your modifications, or
    +   for any such Derivative Works as a whole, provided Your use,
    +   reproduction, and distribution of the Work otherwise complies with
    +   the conditions stated in this License.
    +
    +5. Submission of Contributions. Unless You explicitly state otherwise,
    +   any Contribution intentionally submitted for inclusion in the Work
    +   by You to the Licensor shall be under the terms and conditions of
    +   this License, without any additional terms or conditions.
    +   Notwithstanding the above, nothing herein shall supersede or modify
    +   the terms of any separate license agreement you may have executed
    +   with Licensor regarding such Contributions.
    +
    +6. Trademarks. This License does not grant permission to use the trade
    +   names, trademarks, service marks, or product names of the Licensor,
    +   except as required for reasonable and customary use in describing the
    +   origin of the Work and reproducing the content of the NOTICE file.
    +
    +7. Disclaimer of Warranty. Unless required by applicable law or
    +   agreed to in writing, Licensor provides the Work (and each
    +   Contributor provides its Contributions) on an "AS IS" BASIS,
    +   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +   implied, including, without limitation, any warranties or conditions
    +   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +   PARTICULAR PURPOSE. You are solely responsible for determining the
    +   appropriateness of using or redistributing the Work and assume any
    +   risks associated with Your exercise of permissions under this License.
    +
    +8. Limitation of Liability. In no event and under no legal theory,
    +   whether in tort (including negligence), contract, or otherwise,
    +   unless required by applicable law (such as deliberate and grossly
    +   negligent acts) or agreed to in writing, shall any Contributor be
    +   liable to You for damages, including any direct, indirect, special,
    +   incidental, or consequential damages of any character arising as a
    +   result of this License or out of the use or inability to use the
    +   Work (including but not limited to damages for loss of goodwill,
    +   work stoppage, computer failure or malfunction, or any and all
    +   other commercial damages or losses), even if such Contributor
    +   has been advised of the possibility of such damages.
    +
    +9. Accepting Warranty or Additional Liability. While redistributing
    +   the Work or Derivative Works thereof, You may choose to offer,
    +   and charge a fee for, acceptance of support, warranty, indemnity,
    +   or other liability obligations and/or rights consistent with this
    +   License. However, in accepting such obligations, You may act only
    +   on Your own behalf and on Your sole responsibility, not on behalf
    +   of any other Contributor, and only if You agree to indemnify,
    +   defend, and hold each Contributor harmless for any liability
    +   incurred by, or claims asserted against, such Contributor by reason
    +   of your accepting any such warranty or additional liability.
    +
    +END OF TERMS AND CONDITIONS
    +
    +APPENDIX: How to apply the Apache License to your work.
    +
    +   To apply the Apache License to your work, attach the following
    +   boilerplate notice, with the fields enclosed by brackets "[]"
    +   replaced with your own identifying information. (Don't include
    +   the brackets!)  The text should be enclosed in the appropriate
    +   comment syntax for the file format. We also recommend that a
    +   file or class name and description of purpose be included on the
    +   same "printed page" as the copyright notice for easier
    +   identification within third-party archives.
    +
    +Copyright [yyyy] [name of copyright owner]
    +
    +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.
    +
    +
  • +
  • +

    Apache License 2.0

    +

    Used by:

    + +
    Apache License
    +Version 2.0, January 2004
    +http://www.apache.org/licenses/
    +
    +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +1. Definitions.
    +
    +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
    +
    +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
    +
    +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
    +
    +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
    +
    +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
    +
    +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
    +
    +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
    +
    +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
    +
    +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
    +
    +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
    +
    +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
    +
    +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
    +
    +     (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
    +
    +     (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
    +
    +     (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
    +
    +     (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
    +
    +     You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
    +
    +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
    +
    +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
    +
    +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
    +
    +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
    +
    +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
    +
    +END OF TERMS AND CONDITIONS
    +
    +APPENDIX: How to apply the Apache License to your work.
    +
    +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!)  The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
    +
    +Copyright [yyyy] [name of copyright owner]
    +
    +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.
    +
    +
  • +
  • +

    BSD 2-Clause "Simplified" License

    +

    Used by:

    + +
    BSD 2-Clause License
    +
    +Copyright (c) 2017-2021, the rav1e contributors
    +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.
    +
    +
  • +
  • +

    BSD 2-Clause "Simplified" License

    +

    Used by:

    + +
    BSD 2-Clause License
    +
    +Copyright (c) 2017-2022, the rav1e contributors
    +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.
    +
    +
  • +
  • +

    BSD 2-Clause "Simplified" License

    +

    Used by:

    + +
    BSD 2-Clause License
    +
    +Copyright (c) 2022-2022, the rav1e contributors
    +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.
    +
    +
  • +
  • +

    BSD 2-Clause "Simplified" License

    +

    Used by:

    + +
    Copyright (c) 2015 David Roundy <roundyd@physics.oregonstate.edu>
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are
    +met:
    +
    +1. Redistributions of source code must retain the above copyright
    +   notice, this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright
    +   notice, this list of conditions and the following disclaimer in the
    +   documentation and/or other materials provided with the
    +   distribution.
    +
    +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.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    # Licenses
    +
    +This Rust implementation of the OpenEXR image format
    +and the exrs crate are not affiliated with the 
    +[OpenEXR project](https://www.openexr.com/) or the 
    +[ACADEMY SOFTWARE FOUNDATION](https://www.aswf.io/).
    +
    +
    +## The OpenEXR Image Format: [BSD-3-Clause](https://github.com/AcademySoftwareFoundation/openexr/blob/master/LICENSE.md)
    +Copyright (c) Contributors to the OpenEXR Project. All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    +
    +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +## The `exrs` Project, and independent Rust implementation of the official OpenEXR Image Format: Also [BSD-3-Clause](https://github.com/AcademySoftwareFoundation/openexr/blob/master/LICENSE.md)
    +Copyright (c) Contributors to the exrs Project. All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    +
    +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    BSD 3-Clause License
    +
    +Copyright (c) 2020, Cloudflare, Inc.
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +1. Redistributions of source code must retain the above copyright notice, this
    +   list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright notice,
    +   this list of conditions and the following disclaimer in the documentation
    +   and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its
    +   contributors may be used to endorse or promote products derived from
    +   this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    BSD 3-Clause License
    +
    +Copyright (c) 2020, Kornel
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +1. Redistributions of source code must retain the above copyright notice, this
    +   list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright notice,
    +   this list of conditions and the following disclaimer in the documentation
    +   and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its
    +   contributors may be used to endorse or promote products derived from
    +   this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) 2011 Google Inc. All rights reserved.
    +Copyright (c) 2020 Yevhenii Reizner 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.
    +
    +  * Neither the name of the copyright holder nor the names of its
    +    contributors may be used to endorse or promote products derived
    +    from this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +OWNER 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.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are
    +met:
    +
    +1. Redistributions of source code must retain the above copyright
    +notice, this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright
    +notice, this list of conditions and the following disclaimer in the
    +documentation and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its
    +contributors may be used to endorse or promote products derived from
    +this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
    +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
    +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) 2016-2021 isis agora lovecruft. All rights reserved.
    +Copyright (c) 2016-2021 Henry de Valence. All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are
    +met:
    +
    +1. Redistributions of source code must retain the above copyright
    +notice, this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright
    +notice, this list of conditions and the following disclaimer in the
    +documentation and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its
    +contributors may be used to endorse or promote products derived from
    +this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
    +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
    +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
    +
    +========================================================================
    +
    +Portions of curve25519-dalek were originally derived from Adam Langley's
    +Go ed25519 implementation, found at <https://github.com/agl/ed25519/>,
    +under the following licence:
    +
    +========================================================================
    +
    +Copyright (c) 2012 The Go Authors. 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.
    +   * Neither the name of Google Inc. nor the names of its
    +contributors may be used to endorse or promote products derived from
    +this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
    +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
    +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.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) 2017-2019 isis agora lovecruft. All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are
    +met:
    +
    +1. Redistributions of source code must retain the above copyright
    +notice, this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright
    +notice, this list of conditions and the following disclaimer in the
    +documentation and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its
    +contributors may be used to endorse or promote products derived from
    +this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
    +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
    +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) 2018, Daniel Wagner-Hall
    +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.
    +
    +* Neither the name of num_enum nor the names of its
    +  contributors may be used to endorse or promote products derived from
    +  this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) 2019, Sébastien Crozet
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +1. Redistributions of source code must retain the above copyright notice, this
    +   list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright notice,
    +   this list of conditions and the following disclaimer in the documentation
    +   and/or other materials provided with the distribution.
    +
    +3. Neither the name of the author nor the names of its contributors may be used
    +   to endorse or promote products derived from this software without specific
    +   prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) 2022 Contributors to the lebe Project. All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without modification,
    +are permitted provided that the following conditions are met:
    +
    +1. Redistributions of source code must retain the above copyright notice,
    +this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright notice,
    +this list of conditions and the following disclaimer in the documentation
    +and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its contributors
    +may be used to endorse or promote products derived from this software without
    +specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
    +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +
  • +
  • +

    BSD 3-Clause "New" or "Revised" License

    +

    Used by:

    + +
    Copyright (c) <year> <owner>. 
    +
    +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    +
    +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    +
    +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    +
    +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +
  • +
  • +

    Boost Software License 1.0

    +

    Used by:

    + +
    Boost Software License - Version 1.0 - August 17th, 2003
    +
    +Permission is hereby granted, free of charge, to any person or organization
    +obtaining a copy of the software and accompanying documentation covered by
    +this license (the "Software") to use, reproduce, display, distribute,
    +execute, and transmit the Software, and to prepare derivative works of the
    +Software, and to permit third-parties to whom the Software is furnished to
    +do so, all subject to the following:
    +
    +The copyright notices in the Software and this entire statement, including
    +the above license grant, this restriction and the following disclaimer,
    +must be included in all copies of the Software, in whole or in part, and
    +all derivative works of the Software, unless such copies or derivative
    +works are solely in the form of machine-executable object code generated by
    +a source language processor.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
    +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
    +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
    +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    Boost Software License 1.0

    +

    Used by:

    + +
    Boost Software License - Version 1.0 - August 17th, 2003
    +
    +Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:
    +
    +The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    Creative Commons Zero v1.0 Universal

    +

    Used by:

    + +
    CC0 1.0 Universal
    +
    +Statement of Purpose
    +
    +The laws of most jurisdictions throughout the world automatically confer
    +exclusive Copyright and Related Rights (defined below) upon the creator and
    +subsequent owner(s) (each and all, an "owner") of an original work of
    +authorship and/or a database (each, a "Work").
    +
    +Certain owners wish to permanently relinquish those rights to a Work for the
    +purpose of contributing to a commons of creative, cultural and scientific
    +works ("Commons") that the public can reliably and without fear of later
    +claims of infringement build upon, modify, incorporate in other works, reuse
    +and redistribute as freely as possible in any form whatsoever and for any
    +purposes, including without limitation commercial purposes. These owners may
    +contribute to the Commons to promote the ideal of a free culture and the
    +further production of creative, cultural and scientific works, or to gain
    +reputation or greater distribution for their Work in part through the use and
    +efforts of others.
    +
    +For these and/or other purposes and motivations, and without any expectation
    +of additional consideration or compensation, the person associating CC0 with a
    +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
    +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
    +and publicly distribute the Work under its terms, with knowledge of his or her
    +Copyright and Related Rights in the Work and the meaning and intended legal
    +effect of CC0 on those rights.
    +
    +1. Copyright and Related Rights. A Work made available under CC0 may be
    +protected by copyright and related or neighboring rights ("Copyright and
    +Related Rights"). Copyright and Related Rights include, but are not limited
    +to, the following:
    +
    +  i. the right to reproduce, adapt, distribute, perform, display, communicate,
    +  and translate a Work;
    +
    +  ii. moral rights retained by the original author(s) and/or performer(s);
    +
    +  iii. publicity and privacy rights pertaining to a person's image or likeness
    +  depicted in a Work;
    +
    +  iv. rights protecting against unfair competition in regards to a Work,
    +  subject to the limitations in paragraph 4(a), below;
    +
    +  v. rights protecting the extraction, dissemination, use and reuse of data in
    +  a Work;
    +
    +  vi. database rights (such as those arising under Directive 96/9/EC of the
    +  European Parliament and of the Council of 11 March 1996 on the legal
    +  protection of databases, and under any national implementation thereof,
    +  including any amended or successor version of such directive); and
    +
    +  vii. other similar, equivalent or corresponding rights throughout the world
    +  based on applicable law or treaty, and any national implementations thereof.
    +
    +2. Waiver. To the greatest extent permitted by, but not in contravention of,
    +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
    +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
    +and Related Rights and associated claims and causes of action, whether now
    +known or unknown (including existing as well as future claims and causes of
    +action), in the Work (i) in all territories worldwide, (ii) for the maximum
    +duration provided by applicable law or treaty (including future time
    +extensions), (iii) in any current or future medium and for any number of
    +copies, and (iv) for any purpose whatsoever, including without limitation
    +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
    +the Waiver for the benefit of each member of the public at large and to the
    +detriment of Affirmer's heirs and successors, fully intending that such Waiver
    +shall not be subject to revocation, rescission, cancellation, termination, or
    +any other legal or equitable action to disrupt the quiet enjoyment of the Work
    +by the public as contemplated by Affirmer's express Statement of Purpose.
    +
    +3. Public License Fallback. Should any part of the Waiver for any reason be
    +judged legally invalid or ineffective under applicable law, then the Waiver
    +shall be preserved to the maximum extent permitted taking into account
    +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
    +is so judged Affirmer hereby grants to each affected person a royalty-free,
    +non transferable, non sublicensable, non exclusive, irrevocable and
    +unconditional license to exercise Affirmer's Copyright and Related Rights in
    +the Work (i) in all territories worldwide, (ii) for the maximum duration
    +provided by applicable law or treaty (including future time extensions), (iii)
    +in any current or future medium and for any number of copies, and (iv) for any
    +purpose whatsoever, including without limitation commercial, advertising or
    +promotional purposes (the "License"). The License shall be deemed effective as
    +of the date CC0 was applied by Affirmer to the Work. Should any part of the
    +License for any reason be judged legally invalid or ineffective under
    +applicable law, such partial invalidity or ineffectiveness shall not
    +invalidate the remainder of the License, and in such case Affirmer hereby
    +affirms that he or she will not (i) exercise any of his or her remaining
    +Copyright and Related Rights in the Work or (ii) assert any associated claims
    +and causes of action with respect to the Work, in either case contrary to
    +Affirmer's express Statement of Purpose.
    +
    +4. Limitations and Disclaimers.
    +
    +  a. No trademark or patent rights held by Affirmer are waived, abandoned,
    +  surrendered, licensed or otherwise affected by this document.
    +
    +  b. Affirmer offers the Work as-is and makes no representations or warranties
    +  of any kind concerning the Work, express, implied, statutory or otherwise,
    +  including without limitation warranties of title, merchantability, fitness
    +  for a particular purpose, non infringement, or the absence of latent or
    +  other defects, accuracy, or the present or absence of errors, whether or not
    +  discoverable, all to the greatest extent permissible under applicable law.
    +
    +  c. Affirmer disclaims responsibility for clearing rights of other persons
    +  that may apply to the Work or any use thereof, including without limitation
    +  any person's Copyright and Related Rights in the Work. Further, Affirmer
    +  disclaims responsibility for obtaining any necessary consents, permissions
    +  or other rights required for any use of the Work.
    +
    +  d. Affirmer understands and acknowledges that Creative Commons is not a
    +  party to this document and has no duty or obligation with respect to this
    +  CC0 or use of the Work.
    +
    +For more information, please see
    +<http://creativecommons.org/publicdomain/zero/1.0/>
    +
    +
  • +
  • +

    Creative Commons Zero v1.0 Universal

    +

    Used by:

    + +
    Creative Commons Legal Code
    +
    +CC0 1.0 Universal
    +
    +    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
    +    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
    +    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
    +    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
    +    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
    +    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
    +    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
    +    HEREUNDER.
    +
    +Statement of Purpose
    +
    +The laws of most jurisdictions throughout the world automatically confer
    +exclusive Copyright and Related Rights (defined below) upon the creator
    +and subsequent owner(s) (each and all, an "owner") of an original work of
    +authorship and/or a database (each, a "Work").
    +
    +Certain owners wish to permanently relinquish those rights to a Work for
    +the purpose of contributing to a commons of creative, cultural and
    +scientific works ("Commons") that the public can reliably and without fear
    +of later claims of infringement build upon, modify, incorporate in other
    +works, reuse and redistribute as freely as possible in any form whatsoever
    +and for any purposes, including without limitation commercial purposes.
    +These owners may contribute to the Commons to promote the ideal of a free
    +culture and the further production of creative, cultural and scientific
    +works, or to gain reputation or greater distribution for their Work in
    +part through the use and efforts of others.
    +
    +For these and/or other purposes and motivations, and without any
    +expectation of additional consideration or compensation, the person
    +associating CC0 with a Work (the "Affirmer"), to the extent that he or she
    +is an owner of Copyright and Related Rights in the Work, voluntarily
    +elects to apply CC0 to the Work and publicly distribute the Work under its
    +terms, with knowledge of his or her Copyright and Related Rights in the
    +Work and the meaning and intended legal effect of CC0 on those rights.
    +
    +1. Copyright and Related Rights. A Work made available under CC0 may be
    +protected by copyright and related or neighboring rights ("Copyright and
    +Related Rights"). Copyright and Related Rights include, but are not
    +limited to, the following:
    +
    +  i. the right to reproduce, adapt, distribute, perform, display,
    +     communicate, and translate a Work;
    + ii. moral rights retained by the original author(s) and/or performer(s);
    +iii. publicity and privacy rights pertaining to a person's image or
    +     likeness depicted in a Work;
    + iv. rights protecting against unfair competition in regards to a Work,
    +     subject to the limitations in paragraph 4(a), below;
    +  v. rights protecting the extraction, dissemination, use and reuse of data
    +     in a Work;
    + vi. database rights (such as those arising under Directive 96/9/EC of the
    +     European Parliament and of the Council of 11 March 1996 on the legal
    +     protection of databases, and under any national implementation
    +     thereof, including any amended or successor version of such
    +     directive); and
    +vii. other similar, equivalent or corresponding rights throughout the
    +     world based on applicable law or treaty, and any national
    +     implementations thereof.
    +
    +2. Waiver. To the greatest extent permitted by, but not in contravention
    +of, applicable law, Affirmer hereby overtly, fully, permanently,
    +irrevocably and unconditionally waives, abandons, and surrenders all of
    +Affirmer's Copyright and Related Rights and associated claims and causes
    +of action, whether now known or unknown (including existing as well as
    +future claims and causes of action), in the Work (i) in all territories
    +worldwide, (ii) for the maximum duration provided by applicable law or
    +treaty (including future time extensions), (iii) in any current or future
    +medium and for any number of copies, and (iv) for any purpose whatsoever,
    +including without limitation commercial, advertising or promotional
    +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
    +member of the public at large and to the detriment of Affirmer's heirs and
    +successors, fully intending that such Waiver shall not be subject to
    +revocation, rescission, cancellation, termination, or any other legal or
    +equitable action to disrupt the quiet enjoyment of the Work by the public
    +as contemplated by Affirmer's express Statement of Purpose.
    +
    +3. Public License Fallback. Should any part of the Waiver for any reason
    +be judged legally invalid or ineffective under applicable law, then the
    +Waiver shall be preserved to the maximum extent permitted taking into
    +account Affirmer's express Statement of Purpose. In addition, to the
    +extent the Waiver is so judged Affirmer hereby grants to each affected
    +person a royalty-free, non transferable, non sublicensable, non exclusive,
    +irrevocable and unconditional license to exercise Affirmer's Copyright and
    +Related Rights in the Work (i) in all territories worldwide, (ii) for the
    +maximum duration provided by applicable law or treaty (including future
    +time extensions), (iii) in any current or future medium and for any number
    +of copies, and (iv) for any purpose whatsoever, including without
    +limitation commercial, advertising or promotional purposes (the
    +"License"). The License shall be deemed effective as of the date CC0 was
    +applied by Affirmer to the Work. Should any part of the License for any
    +reason be judged legally invalid or ineffective under applicable law, such
    +partial invalidity or ineffectiveness shall not invalidate the remainder
    +of the License, and in such case Affirmer hereby affirms that he or she
    +will not (i) exercise any of his or her remaining Copyright and Related
    +Rights in the Work or (ii) assert any associated claims and causes of
    +action with respect to the Work, in either case contrary to Affirmer's
    +express Statement of Purpose.
    +
    +4. Limitations and Disclaimers.
    +
    + a. No trademark or patent rights held by Affirmer are waived, abandoned,
    +    surrendered, licensed or otherwise affected by this document.
    + b. Affirmer offers the Work as-is and makes no representations or
    +    warranties of any kind concerning the Work, express, implied,
    +    statutory or otherwise, including without limitation warranties of
    +    title, merchantability, fitness for a particular purpose, non
    +    infringement, or the absence of latent or other defects, accuracy, or
    +    the present or absence of errors, whether or not discoverable, all to
    +    the greatest extent permissible under applicable law.
    + c. Affirmer disclaims responsibility for clearing rights of other persons
    +    that may apply to the Work or any use thereof, including without
    +    limitation any person's Copyright and Related Rights in the Work.
    +    Further, Affirmer disclaims responsibility for obtaining any necessary
    +    consents, permissions or other rights required for any use of the
    +    Work.
    + d. Affirmer understands and acknowledges that Creative Commons is not a
    +    party to this document and has no duty or obligation with respect to
    +    this CC0 or use of the Work.
    +
    +
  • +
  • +

    ISC License

    +

    Used by:

    + +
       Copyright 2015-2016 Brian Smith.
    +
    +   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 AUTHORS DISCLAIM ALL WARRANTIES
    +   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    +   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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.
    +
  • +
  • +

    ISC License

    +

    Used by:

    + +
    /* Copyright (c) 2015, Google Inc.
    + *
    + * 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 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. */
    +
    +
  • +
  • +

    ISC License

    +

    Used by:

    + +
    // Copyright 2015-2016 Brian Smith.
    +//
    +// 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 AUTHORS DISCLAIM ALL WARRANTIES
    +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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.
    +
    +
  • +
  • +

    ISC License

    +

    Used by:

    + +
    Copyright © 2015, Simonas Kazlauskas
    +
    +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 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.
    +
    +
  • +
  • +

    ISC License

    +

    Used by:

    + +
    ISC License
    +
    +Copyright (c) 2020, Hunter WB <hunterwb.com>
    +
    +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 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.
    +
    +
  • +
  • +

    ISC License

    +

    Used by:

    + +
    ISC License:
    +
    +Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
    +Copyright (c) 1995-2003 by Internet Software Consortium
    +
    +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 ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
        MIT License
    +
    +    Copyright (c) Microsoft Corporation.
    +
    +    Permission is hereby granted, free of charge, to any person obtaining a copy
    +    of this software and associated documentation files (the "Software"), to deal
    +    in the Software without restriction, including without limitation the rights
    +    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +    copies of the Software, and to permit persons to whom the Software is
    +    furnished to do so, subject to the following conditions:
    +
    +    The above copyright notice and this permission notice shall be included in all
    +    copies or substantial portions of the Software.
    +
    +    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +    SOFTWARE
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    # Contributing
    +
    +## License
    +
    +Licensed under either of
    +
    + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
    + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
    +
    +at your option.
    +
    +### Contribution
    +
    +Unless you explicitly state otherwise, any contribution intentionally submitted
    +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
    +additional terms or conditions.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    - xdg-utils: https://cgit.freedesktop.org/xdg/xdg-utils/plain/LICENSE
    +
    +    Permission is hereby granted, free of charge, to any person obtaining a
    +    copy of this software and associated documentation files (the "Software"),
    +    to deal in the Software without restriction, including without limitation
    +    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    +    and/or sell copies of the Software, and to permit persons to whom the
    +    Software is furnished to do so, subject to the following conditions:
    +    #
    +    The above copyright notice and this permission notice shall be included
    +    in all copies or substantial portions of the Software.
    +    #
    +    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    +    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    +    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
    +    OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
    +    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    +    OTHER DEALINGS IN THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    // Copyright (c) 2019 Nuclear Furnace
    +//
    +// Permission is hereby granted, free of charge, to any person obtaining a copy
    +// of this software and associated documentation files (the "Software"), to deal
    +// in the Software without restriction, including without limitation the rights
    +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +// copies of the Software, and to permit persons to whom the Software is
    +// furnished to do so, subject to the following conditions:
    +//
    +// The above copyright notice and this permission notice shall be included in all
    +// copies or substantial portions of the Software.
    +//
    +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +// SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    // Licensed under the Apache License, Version 2.0
    +// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
    +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
    +// All files in the project carrying such notice may not be copied, modified, or distributed
    +// except according to those terms.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Both mlua and rlua are distributed under the MIT license, which is reproduced
    +below:
    +
    +MIT License
    +
    +Copyright (c) 2019-2021 A. Orlenko
    +Copyright (c) 2017 rlua
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2006-2009 Graydon Hoare
    +Copyright (c) 2009-2013 Mozilla Foundation
    +Copyright (c) 2016 Artyom Pavlov
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2010 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2011 Jan Kokemüller
    +Copyright (c) 2020 Sebastian Dröge <sebastian@centricular.com>
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2012-2013 Mozilla Foundation
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2013 James Miller <james@aatch.net>
    +Copyright (c) 2016
    +        Remi Thebault <remi.thebault@gmail.com>
    +        Thomas Bracht Laumann Jespersen <laumann.thomas@gmail.com>
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2013 James Miller <james@aatch.net>
    +Copyright (c) 2016-2023 Rémi Thebault <@rtbo> and many others
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2013-2014 The Rust Project Developers.
    +Copyright (c) 2015-2020 The rust-hex Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2013-2016 The rust-url developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2013-2022 The rust-url developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 Alex Crichton
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 Alex Crichton
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 Alex Crichton
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 Carl Lerche and other MIO contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 Chris Wong
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 Sean McArthur
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 The Rust Project Developers
    +Copyright (c) 2018 Ashley Mannix, Christopher Armstrong, Dylan DPC, Hunar Roop Kahlon
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014 The Rust Project Developers
    +Copyright (c) 2018 Ashley Mannix, Christopher Armstrong, Dylan DPC, Hunar Roop Kahlon
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014-2017 Sean McArthur
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014-2018 David Henningsson <diwic@ubuntu.com> and other contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014-2019 Geoffroy Couprie
    +
    +Permission is hereby granted, free of charge, to any person obtaining
    +a copy of this software and associated documentation files (the
    +"Software"), to deal in the Software without restriction, including
    +without limitation the rights to use, copy, modify, merge, publish,
    +distribute, sublicense, and/or sell copies of the Software, and to
    +permit persons to whom the Software is furnished to do so, subject to
    +the following conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014-2020 Optimal Computing (NZ) Ltd
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    +of the Software, and to permit persons to whom the Software is furnished to do
    +so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    +IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014-2020 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014-2021 Sean McArthur
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2014-2021 The rusqlite developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 Dan Burkert
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 Daniel Keep
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 Jonathan Reem
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 Steven Allen
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 The Gimli Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 The quick-error Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 The rust-jni-sys Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 The rust-openssl-verify Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 Victor Berger
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 fangyuanziti
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015 nwin
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015-2016 the fiat-crypto authors (see
    +https://github.com/mit-plv/fiat-crypto/blob/master/AUTHORS).
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015-2018 The winapi-rs Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015-2019 Doug Tangren
    +
    +Permission is hereby granted, free of charge, to any person obtaining
    +a copy of this software and associated documentation files (the
    +"Software"), to deal in the Software without restriction, including
    +without limitation the rights to use, copy, modify, merge, publish,
    +distribute, sublicense, and/or sell copies of the Software, and to
    +permit persons to whom the Software is furnished to do so, subject to
    +the following conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015-2019 RustCrypto Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2015-2021 Sean McArthur
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 - 2023 Ulrik Sverdrup "bluss"
    +Copyirhgt (c) 2018 R. Janis Goldschmidt
    +Copyright (c) 2021 DutchGhost [constparse.rs]
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 Alex Crichton
    +Copyright (c) 2017 The Tokio Authors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 Amanieu d'Antras
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 Anthony Ramine
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 Joseph Birr-Pixton <jpixton@gmail.com>
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 Martin Carton
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    +of the Software, and to permit persons to whom the Software is furnished to do
    +so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 Pyfisch
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 Sean McArthur
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 The fallible-streaming-iterator Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 The humantime Developers
    +
    +Includes parts of http date with the following copyright:
    +Copyright (c) 2016 Pyfisch
    +
    +Includes portions of musl libc with the following copyright:
    +Copyright © 2005-2013 Rich Felker
    +
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 arcnmx
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 kennytm
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software is furnished to do so,
    +subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 meh. <meh@schizofreni.co>
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    +of the Software, and to permit persons to whom the Software is furnished to do
    +so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016 rust-uname Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016--2017
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016--2023
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016-2018 The gimli Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016-2019 Ulrik Sverdrup "bluss" and scopeguard developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2016-2020 RustCrypto Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Aaron Power
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Alexey Galakhov
    +Copyright (c) 2016 Jason Housley
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Artyom Pavlov
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Avraham Weinstock
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Brian Langenberger
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Daniel Abramov
    +Copyright (c) 2017 Alexey Galakhov
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Gilad Naaman
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Jorge Aparicio
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Maik Klein
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Mikhail Zabaluev
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Pyfisch
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Redox OS Developers
    +
    +MIT License
    +
    +Permission is hereby granted, free of charge, to any person obtaining
    +a copy of this software and associated documentation files (the
    +"Software"), to deal in the Software without restriction, including
    +without limitation the rights to use, copy, modify, merge, publish,
    +distribute, sublicense, and/or sell copies of the Software, and to
    +permit persons to whom the Software is furnished to do so, subject to
    +the following conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 The foreign-types Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 Tim Visée
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 arc-swap developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 h2 authors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 http-rs authors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 quininer kel
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017 tokio-jsonrpc developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017-2020 Alissa Rao <lymiahugs@gmail.com>
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017-2020 Dirkjan Ochtman
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2017-2023 Maik Klein, Maja Kądziołka
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Artyom Pavlov
    +Copyright (c) 2018 The RustCrypto Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Carl Lerche
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Chris Wong
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 David Tolnay
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Jorge Aparicio
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Lucas Timmins & Victor Berger
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Raph Levien
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Sean McArthur
    +Copyright (c) 2016 Alex Crichton
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 The Servo Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 The pin-utils authors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Tom Kaitchuck
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Victor Berger
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 Yevhenii Reizner
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018 auto_impl Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018-2019 Sean McArthur
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018-2019 The RustCrypto Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018-2019 dirs-rs contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018-2023 RustCrypto Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2018-2024 The rust-random Project Developers
    +Copyright (c) 2014 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Axum Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Carl Lerche
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Carl Lerche
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +Copyright (c) 2018 David Tolnay
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Eliza Weisman
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Eliza Weisman
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Embark Studios
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Hyper Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Nick Fitzgerald
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 The CryptoCorrosion Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 The RustCrypto Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 The RustCrypto Project Developers
    +Copyright (c) 2019 MobileCoin, LLC
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Tokio Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Tower Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019 Yevhenii Reizner
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019-2020 RustCrypto Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019-2021 Tower Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019-2023 The RustCrypto Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2019-2024 Sean McArthur & Hyper Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2020 Andrew D. Straw
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2020 Ashish Myles and contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2020 Lucio Franco
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2020 Ludwig Stecher and contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2020 Maciej Hirsz <hello@maciej.codes>
    +
    +The MIT License (MIT)
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2020 The RustCrypto Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2020 Yevhenii Reizner
    +Copyright (c) 2015 Dan Burkert
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2021 Jacob Pratt
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2021 Metrics Contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2021 RustCrypto Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2021 Thom Chiovoloni
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2022 Ivan Smirnov
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2022 Jacob Pratt et al.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2022 The RustCrypto Project Developers
    +Copyright (c) 2022 Artyom Pavlov
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2022 The rust-cli Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2022 Tokio Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2022 Yevhenii Reizner
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2023 Dirkjan Ochtman <dirkjan@ochtman.nl>
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2023 Sean McArthur
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2023 The allocator-api2 project developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2023 Tokio Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) 2023 Tokio Contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +The MIT License (MIT)
    +
    +Copyright (c) 2019 Yoshua Wuyts
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) Individual contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright (c) Ulrik Sverdrup "bluss" 2015-2023
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2017 Juniper Networks, Inc.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2017 The hidapi-rs Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
    +associated documentation files (the "Software"), to deal in the Software without restriction,
    +including without limitation the rights to use, copy, modify, merge, publish, distribute,
    +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial
    +portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
    +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
    +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2018 Developers of the Rand project
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2018 Developers of the Rand project
    +Copyright (c) 2014 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2018 Parity Technologies (UK) Ltd.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software is furnished to do so,
    +subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
    +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2019 x11rb Contributers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2021 Axum Contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2021 Axum Server Contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2021 Oskar Gustafsson <oskargustafsson88@gmail.com>
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright 2023 The Fuchsia Authors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Copyright for portions of the project dns-lookup are held by The Rust Project Developers
    +as part of The Rust Project. All other copyright for project dns-lookup are held by keeperofdakeys.
    +
    +The MIT License (MIT)
    +
    +Copyright (c) 2016 keeperofdakeys
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Licensed under the Apache License, Version 2.0
    +<LICENSE-APACHE or
    +http://www.apache.org/licenses/LICENSE-2.0> or the MIT
    +license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
    +at your option. All files in the project carrying such
    +notice may not be copied, modified, or distributed except
    +according to those terms.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
    +http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
    +<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
    +option. All files in the project carrying such notice may not be
    +copied, modified, or distributed except according to those terms.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2014 John Slegers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2016 Andreas Linz
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2016 Jerome Froelich
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2016 Martin Geisler
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2016 The jpeg-decoder Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2016 fengcen
    +Copyright (c) 2019 svartalf
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Evgeny Safronov
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Frommi
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 James Kominick
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Michael Neumann
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Nikolai Vazquez
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Nikolay Govorov
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Ted Driggs
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Thomas Bahn and contributors
    +Copyright (c) 2014 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 pythoneer
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 Guillaume Gomez
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 Luca Barbato
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 PistonDevelopers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 Sam Rijs, Alex Crichton and contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 ostrosco
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 segeljakt
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 sgodwincs
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018-2021 The RustCrypto Project Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Adoo
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Anton Lazarev
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Christopher Sebastian
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Daniel Henry-Mantilla
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Kornel
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Luca Barbato
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Manish Goregaokar
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Osspial
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Peter Glotfelty
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 segeljakt
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019-2020 CreepySkeleton
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 Armin Becher
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 Benjamin Klum
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 Canop
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 David Purdum
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 Samuele Esposito
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 Soveu
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 dylni (https://github.com/dylni)
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2020 sval-rs
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2021 Andy Grove
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2021 Billy Messenger
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2021 Joshua Holmer
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2021 Riey
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2022 Bartłomiej Maryńczak
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2022 Ibraheem Ahmed
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2022 Scott Godwin
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2022 The Arboard contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2022 Urho Laukkarinen
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2022 sarah
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2023 Sean Larkin
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2023 dAxpeDDa
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) <year> <copyright holders>
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) Steven Sheldon
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) Tokio Contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) [2021] [Marvin Countryman]
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +Copyright (c) 2017 quininer@live.com
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2017 Maiddog
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2018 Alexandru Ene
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Alexandru Ene
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Daniel "Lokathor" Gee.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Manish Goregaokar
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019 Osspial
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2019-2020 Philip Degarmo and other wepoll-ffi contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT License
    +
    +Copyright (c) 2023 Daniel "Lokathor" Gee.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT OR Apache-2.0
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    MIT or Apache-2.0
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Original work Copyright (c) 2014 The Rust Project Developers
    +Modified work Copyright (c) 2016-2018 Nikita Pekin and lazycell contributors
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any person obtaining
    +a copy of this software and associated documentation files (the
    +"Software"), to deal in the Software without restriction, including
    +without limitation the rights to use, copy, modify, merge, publish,
    +distribute, sublicense, and/or sell copies of the Software, and to
    +permit persons to whom the Software is furnished to do so, subject to
    +the following conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"),to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The GIFLIB distribution is Copyright (c) 1997  Eric S. Raymond
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License
    +
    +Copyright 2015 Google Inc. All rights reserved.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2013 Nicolas Silva
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software is furnished to do so,
    +subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2014 Benjamin Sago
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2014 Mathijs van de Nes
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2014 Mathijs van de Nes
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2014 Paho Lurie-Gregg
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2014 PistonDevelopers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2014 Y. T. CHUNG
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2014-2022 Steven Fackler, Yuki Okushi
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software is furnished to do so,
    +subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Alice Maz
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Andrew Gallant
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Austin Bonander
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Carl Lerche + nix-rust Authors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Chen Yuheng
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Danny Guo
    +Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Danny Guo
    +Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>
    +Copyright (c) 2018 Akash Kurdekar
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Erik Hedvall
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Gerd Zellweger
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Guillaume Gomez
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Marko Mijalkovic
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Markus Westerlind
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 PistonDevelopers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Siyu Wang
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Steven Fackler
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software is furnished to do so,
    +subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Victor Brekenfeld
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 nwin
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015-2016 Kevin B. Knapp
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015-2020 Julien Cretin
    +Copyright (c) 2017-2020 Google Inc.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015-2020 the fiat-crypto authors (see the AUTHORS file).
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015-2022 Amod Malviya
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015-2022 Kevin B. Knapp and Clap Contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2016 Jelte Fennema
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2016 Jonathan Creekmore
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2016 Philipp Oppermann
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2016 PistonDevelopers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2016 Prevoty, Inc. and jni-rs contributors
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2016 The weldr Project Developers
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2017 Andrew Gallant
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2017 Jose Narvaez
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2017 Stanislav Tkach
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2018 Reizner Evgeniy
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2018 Yevhenii Reizner
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2018-2019 Andrew Gallant
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2019 Reizner Evgeniy
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2019 The Crossbeam Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2020 Yevhenii Reizner
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) HarfBuzz developers
    +Copyright (c) 2020 Yevhenii Reizner
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) HeroicKatora 2020
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +=====================
    +
    +Copyright © `2015` `Sebastian Thiel`
    +
    +Permission is hereby granted, free of charge, to any person
    +obtaining a copy of this software and associated documentation
    +files (the “Software”), to deal in the Software without
    +restriction, including without limitation the rights to use,
    +copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the
    +Software is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    +OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +Copyright (c) 2016 Alexandre Bury
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Bartłomiej Kamiński
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2019 Yoshua Wuyts
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    This project is dual-licensed under the Unlicense and MIT licenses.
    +
    +You may use this code under the terms of either license.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    This project is licensed under either of
    +
    + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
    +   http://www.apache.org/licenses/LICENSE-2.0)
    + * MIT license ([LICENSE-MIT](LICENSE-MIT) or
    +   http://opensource.org/licenses/MIT)
    +
    +at your option.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    This work is derived in part from the `linked-hash-map` crate, Copyright (c)
    +2015 The Rust Project Developers
    +
    +Permission is hereby granted, free of charge, to any
    +person obtaining a copy of this software and associated
    +documentation files (the "Software"), to deal in the
    +Software without restriction, including without
    +limitation the rights to use, copy, modify, merge,
    +publish, distribute, sublicense, and/or sell copies of
    +the Software, and to permit persons to whom the Software
    +is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice
    +shall be included in all copies or substantial portions
    +of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
    +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
    +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
    +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    +DEALINGS IN THE SOFTWARE.
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    the MIT License
    +
    +Copyright (c) 2018 rhysd
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    +of the Software, and to permit persons to whom the Software is furnished to do so,
    +subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
    +THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    © 2016 Bertram Truong
    +© 2021 Kornel Lesiński
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    +
  • +
  • +

    MIT License

    +

    Used by:

    + +
    © Kornel Lesiński
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    +
  • +
  • +

    Mozilla Public License 2.0

    +

    Used by:

    + +
    Mozilla Public License Version 2.0
    +==================================
    +
    +1. Definitions
    +--------------
    +
    +1.1. "Contributor"
    +    means each individual or legal entity that creates, contributes to
    +    the creation of, or owns Covered Software.
    +
    +1.2. "Contributor Version"
    +    means the combination of the Contributions of others (if any) used
    +    by a Contributor and that particular Contributor's Contribution.
    +
    +1.3. "Contribution"
    +    means Covered Software of a particular Contributor.
    +
    +1.4. "Covered Software"
    +    means Source Code Form to which the initial Contributor has attached
    +    the notice in Exhibit A, the Executable Form of such Source Code
    +    Form, and Modifications of such Source Code Form, in each case
    +    including portions thereof.
    +
    +1.5. "Incompatible With Secondary Licenses"
    +    means
    +
    +    (a) that the initial Contributor has attached the notice described
    +        in Exhibit B to the Covered Software; or
    +
    +    (b) that the Covered Software was made available under the terms of
    +        version 1.1 or earlier of the License, but not also under the
    +        terms of a Secondary License.
    +
    +1.6. "Executable Form"
    +    means any form of the work other than Source Code Form.
    +
    +1.7. "Larger Work"
    +    means a work that combines Covered Software with other material, in
    +    a separate file or files, that is not Covered Software.
    +
    +1.8. "License"
    +    means this document.
    +
    +1.9. "Licensable"
    +    means having the right to grant, to the maximum extent possible,
    +    whether at the time of the initial grant or subsequently, any and
    +    all of the rights conveyed by this License.
    +
    +1.10. "Modifications"
    +    means any of the following:
    +
    +    (a) any file in Source Code Form that results from an addition to,
    +        deletion from, or modification of the contents of Covered
    +        Software; or
    +
    +    (b) any new file in Source Code Form that contains any Covered
    +        Software.
    +
    +1.11. "Patent Claims" of a Contributor
    +    means any patent claim(s), including without limitation, method,
    +    process, and apparatus claims, in any patent Licensable by such
    +    Contributor that would be infringed, but for the grant of the
    +    License, by the making, using, selling, offering for sale, having
    +    made, import, or transfer of either its Contributions or its
    +    Contributor Version.
    +
    +1.12. "Secondary License"
    +    means either the GNU General Public License, Version 2.0, the GNU
    +    Lesser General Public License, Version 2.1, the GNU Affero General
    +    Public License, Version 3.0, or any later versions of those
    +    licenses.
    +
    +1.13. "Source Code Form"
    +    means the form of the work preferred for making modifications.
    +
    +1.14. "You" (or "Your")
    +    means an individual or a legal entity exercising rights under this
    +    License. For legal entities, "You" includes any entity that
    +    controls, is controlled by, or is under common control with You. For
    +    purposes of this definition, "control" means (a) the power, direct
    +    or indirect, to cause the direction or management of such entity,
    +    whether by contract or otherwise, or (b) ownership of more than
    +    fifty percent (50%) of the outstanding shares or beneficial
    +    ownership of such entity.
    +
    +2. License Grants and Conditions
    +--------------------------------
    +
    +2.1. Grants
    +
    +Each Contributor hereby grants You a world-wide, royalty-free,
    +non-exclusive license:
    +
    +(a) under intellectual property rights (other than patent or trademark)
    +    Licensable by such Contributor to use, reproduce, make available,
    +    modify, display, perform, distribute, and otherwise exploit its
    +    Contributions, either on an unmodified basis, with Modifications, or
    +    as part of a Larger Work; and
    +
    +(b) under Patent Claims of such Contributor to make, use, sell, offer
    +    for sale, have made, import, and otherwise transfer either its
    +    Contributions or its Contributor Version.
    +
    +2.2. Effective Date
    +
    +The licenses granted in Section 2.1 with respect to any Contribution
    +become effective for each Contribution on the date the Contributor first
    +distributes such Contribution.
    +
    +2.3. Limitations on Grant Scope
    +
    +The licenses granted in this Section 2 are the only rights granted under
    +this License. No additional rights or licenses will be implied from the
    +distribution or licensing of Covered Software under this License.
    +Notwithstanding Section 2.1(b) above, no patent license is granted by a
    +Contributor:
    +
    +(a) for any code that a Contributor has removed from Covered Software;
    +    or
    +
    +(b) for infringements caused by: (i) Your and any other third party's
    +    modifications of Covered Software, or (ii) the combination of its
    +    Contributions with other software (except as part of its Contributor
    +    Version); or
    +
    +(c) under Patent Claims infringed by Covered Software in the absence of
    +    its Contributions.
    +
    +This License does not grant any rights in the trademarks, service marks,
    +or logos of any Contributor (except as may be necessary to comply with
    +the notice requirements in Section 3.4).
    +
    +2.4. Subsequent Licenses
    +
    +No Contributor makes additional grants as a result of Your choice to
    +distribute the Covered Software under a subsequent version of this
    +License (see Section 10.2) or under the terms of a Secondary License (if
    +permitted under the terms of Section 3.3).
    +
    +2.5. Representation
    +
    +Each Contributor represents that the Contributor believes its
    +Contributions are its original creation(s) or it has sufficient rights
    +to grant the rights to its Contributions conveyed by this License.
    +
    +2.6. Fair Use
    +
    +This License is not intended to limit any rights You have under
    +applicable copyright doctrines of fair use, fair dealing, or other
    +equivalents.
    +
    +2.7. Conditions
    +
    +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
    +in Section 2.1.
    +
    +3. Responsibilities
    +-------------------
    +
    +3.1. Distribution of Source Form
    +
    +All distribution of Covered Software in Source Code Form, including any
    +Modifications that You create or to which You contribute, must be under
    +the terms of this License. You must inform recipients that the Source
    +Code Form of the Covered Software is governed by the terms of this
    +License, and how they can obtain a copy of this License. You may not
    +attempt to alter or restrict the recipients' rights in the Source Code
    +Form.
    +
    +3.2. Distribution of Executable Form
    +
    +If You distribute Covered Software in Executable Form then:
    +
    +(a) such Covered Software must also be made available in Source Code
    +    Form, as described in Section 3.1, and You must inform recipients of
    +    the Executable Form how they can obtain a copy of such Source Code
    +    Form by reasonable means in a timely manner, at a charge no more
    +    than the cost of distribution to the recipient; and
    +
    +(b) You may distribute such Executable Form under the terms of this
    +    License, or sublicense it under different terms, provided that the
    +    license for the Executable Form does not attempt to limit or alter
    +    the recipients' rights in the Source Code Form under this License.
    +
    +3.3. Distribution of a Larger Work
    +
    +You may create and distribute a Larger Work under terms of Your choice,
    +provided that You also comply with the requirements of this License for
    +the Covered Software. If the Larger Work is a combination of Covered
    +Software with a work governed by one or more Secondary Licenses, and the
    +Covered Software is not Incompatible With Secondary Licenses, this
    +License permits You to additionally distribute such Covered Software
    +under the terms of such Secondary License(s), so that the recipient of
    +the Larger Work may, at their option, further distribute the Covered
    +Software under the terms of either this License or such Secondary
    +License(s).
    +
    +3.4. Notices
    +
    +You may not remove or alter the substance of any license notices
    +(including copyright notices, patent notices, disclaimers of warranty,
    +or limitations of liability) contained within the Source Code Form of
    +the Covered Software, except that You may alter any license notices to
    +the extent required to remedy known factual inaccuracies.
    +
    +3.5. Application of Additional Terms
    +
    +You may choose to offer, and to charge a fee for, warranty, support,
    +indemnity or liability obligations to one or more recipients of Covered
    +Software. However, You may do so only on Your own behalf, and not on
    +behalf of any Contributor. You must make it absolutely clear that any
    +such warranty, support, indemnity, or liability obligation is offered by
    +You alone, and You hereby agree to indemnify every Contributor for any
    +liability incurred by such Contributor as a result of warranty, support,
    +indemnity or liability terms You offer. You may include additional
    +disclaimers of warranty and limitations of liability specific to any
    +jurisdiction.
    +
    +4. Inability to Comply Due to Statute or Regulation
    +---------------------------------------------------
    +
    +If it is impossible for You to comply with any of the terms of this
    +License with respect to some or all of the Covered Software due to
    +statute, judicial order, or regulation then You must: (a) comply with
    +the terms of this License to the maximum extent possible; and (b)
    +describe the limitations and the code they affect. Such description must
    +be placed in a text file included with all distributions of the Covered
    +Software under this License. Except to the extent prohibited by statute
    +or regulation, such description must be sufficiently detailed for a
    +recipient of ordinary skill to be able to understand it.
    +
    +5. Termination
    +--------------
    +
    +5.1. The rights granted under this License will terminate automatically
    +if You fail to comply with any of its terms. However, if You become
    +compliant, then the rights granted under this License from a particular
    +Contributor are reinstated (a) provisionally, unless and until such
    +Contributor explicitly and finally terminates Your grants, and (b) on an
    +ongoing basis, if such Contributor fails to notify You of the
    +non-compliance by some reasonable means prior to 60 days after You have
    +come back into compliance. Moreover, Your grants from a particular
    +Contributor are reinstated on an ongoing basis if such Contributor
    +notifies You of the non-compliance by some reasonable means, this is the
    +first time You have received notice of non-compliance with this License
    +from such Contributor, and You become compliant prior to 30 days after
    +Your receipt of the notice.
    +
    +5.2. If You initiate litigation against any entity by asserting a patent
    +infringement claim (excluding declaratory judgment actions,
    +counter-claims, and cross-claims) alleging that a Contributor Version
    +directly or indirectly infringes any patent, then the rights granted to
    +You by any and all Contributors for the Covered Software under Section
    +2.1 of this License shall terminate.
    +
    +5.3. In the event of termination under Sections 5.1 or 5.2 above, all
    +end user license agreements (excluding distributors and resellers) which
    +have been validly granted by You or Your distributors under this License
    +prior to termination shall survive termination.
    +
    +************************************************************************
    +*                                                                      *
    +*  6. Disclaimer of Warranty                                           *
    +*  -------------------------                                           *
    +*                                                                      *
    +*  Covered Software is provided under this License on an "as is"       *
    +*  basis, without warranty of any kind, either expressed, implied, or  *
    +*  statutory, including, without limitation, warranties that the       *
    +*  Covered Software is free of defects, merchantable, fit for a        *
    +*  particular purpose or non-infringing. The entire risk as to the     *
    +*  quality and performance of the Covered Software is with You.        *
    +*  Should any Covered Software prove defective in any respect, You     *
    +*  (not any Contributor) assume the cost of any necessary servicing,   *
    +*  repair, or correction. This disclaimer of warranty constitutes an   *
    +*  essential part of this License. No use of any Covered Software is   *
    +*  authorized under this License except under this disclaimer.         *
    +*                                                                      *
    +************************************************************************
    +
    +************************************************************************
    +*                                                                      *
    +*  7. Limitation of Liability                                          *
    +*  --------------------------                                          *
    +*                                                                      *
    +*  Under no circumstances and under no legal theory, whether tort      *
    +*  (including negligence), contract, or otherwise, shall any           *
    +*  Contributor, or anyone who distributes Covered Software as          *
    +*  permitted above, be liable to You for any direct, indirect,         *
    +*  special, incidental, or consequential damages of any character      *
    +*  including, without limitation, damages for lost profits, loss of    *
    +*  goodwill, work stoppage, computer failure or malfunction, or any    *
    +*  and all other commercial damages or losses, even if such party      *
    +*  shall have been informed of the possibility of such damages. This   *
    +*  limitation of liability shall not apply to liability for death or   *
    +*  personal injury resulting from such party's negligence to the       *
    +*  extent applicable law prohibits such limitation. Some               *
    +*  jurisdictions do not allow the exclusion or limitation of           *
    +*  incidental or consequential damages, so this exclusion and          *
    +*  limitation may not apply to You.                                    *
    +*                                                                      *
    +************************************************************************
    +
    +8. Litigation
    +-------------
    +
    +Any litigation relating to this License may be brought only in the
    +courts of a jurisdiction where the defendant maintains its principal
    +place of business and such litigation shall be governed by laws of that
    +jurisdiction, without reference to its conflict-of-law provisions.
    +Nothing in this Section shall prevent a party's ability to bring
    +cross-claims or counter-claims.
    +
    +9. Miscellaneous
    +----------------
    +
    +This License represents the complete agreement concerning the subject
    +matter hereof. If any provision of this License is held to be
    +unenforceable, such provision shall be reformed only to the extent
    +necessary to make it enforceable. Any law or regulation which provides
    +that the language of a contract shall be construed against the drafter
    +shall not be used to construe this License against a Contributor.
    +
    +10. Versions of the License
    +---------------------------
    +
    +10.1. New Versions
    +
    +Mozilla Foundation is the license steward. Except as provided in Section
    +10.3, no one other than the license steward has the right to modify or
    +publish new versions of this License. Each version will be given a
    +distinguishing version number.
    +
    +10.2. Effect of New Versions
    +
    +You may distribute the Covered Software under the terms of the version
    +of the License under which You originally received the Covered Software,
    +or under the terms of any subsequent version published by the license
    +steward.
    +
    +10.3. Modified Versions
    +
    +If you create software not governed by this License, and you want to
    +create a new license for such software, you may create and use a
    +modified version of this License if you rename the license and remove
    +any references to the name of the license steward (except to note that
    +such modified license differs from this License).
    +
    +10.4. Distributing Source Code Form that is Incompatible With Secondary
    +Licenses
    +
    +If You choose to distribute Source Code Form that is Incompatible With
    +Secondary Licenses under the terms of this version of the License, the
    +notice described in Exhibit B of this License must be attached.
    +
    +Exhibit A - Source Code Form License Notice
    +-------------------------------------------
    +
    +  This Source Code Form is subject to the terms of the Mozilla Public
    +  License, v. 2.0. If a copy of the MPL was not distributed with this
    +  file, You can obtain one at http://mozilla.org/MPL/2.0/.
    +
    +If it is not possible or desirable to put the notice in a particular
    +file, then You may include the notice in a location (such as a LICENSE
    +file in a relevant directory) where a recipient would be likely to look
    +for such a notice.
    +
    +You may add additional accurate notices of copyright ownership.
    +
    +Exhibit B - "Incompatible With Secondary Licenses" Notice
    +---------------------------------------------------------
    +
    +  This Source Code Form is "Incompatible With Secondary Licenses", as
    +  defined by the Mozilla Public License, v. 2.0.
    +
    +
  • +
  • +

    Mozilla Public License 2.0

    +

    Used by:

    + +
    Mozilla Public License Version 2.0
    +==================================
    +
    +1. Definitions
    +--------------
    +
    +1.1. "Contributor"
    +    means each individual or legal entity that creates, contributes to
    +    the creation of, or owns Covered Software.
    +
    +1.2. "Contributor Version"
    +    means the combination of the Contributions of others (if any) used
    +    by a Contributor and that particular Contributor's Contribution.
    +
    +1.3. "Contribution"
    +    means Covered Software of a particular Contributor.
    +
    +1.4. "Covered Software"
    +    means Source Code Form to which the initial Contributor has attached
    +    the notice in Exhibit A, the Executable Form of such Source Code
    +    Form, and Modifications of such Source Code Form, in each case
    +    including portions thereof.
    +
    +1.5. "Incompatible With Secondary Licenses"
    +    means
    +
    +    (a) that the initial Contributor has attached the notice described
    +        in Exhibit B to the Covered Software; or
    +
    +    (b) that the Covered Software was made available under the terms of
    +        version 1.1 or earlier of the License, but not also under the
    +        terms of a Secondary License.
    +
    +1.6. "Executable Form"
    +    means any form of the work other than Source Code Form.
    +
    +1.7. "Larger Work"
    +    means a work that combines Covered Software with other material, in 
    +    a separate file or files, that is not Covered Software.
    +
    +1.8. "License"
    +    means this document.
    +
    +1.9. "Licensable"
    +    means having the right to grant, to the maximum extent possible,
    +    whether at the time of the initial grant or subsequently, any and
    +    all of the rights conveyed by this License.
    +
    +1.10. "Modifications"
    +    means any of the following:
    +
    +    (a) any file in Source Code Form that results from an addition to,
    +        deletion from, or modification of the contents of Covered
    +        Software; or
    +
    +    (b) any new file in Source Code Form that contains any Covered
    +        Software.
    +
    +1.11. "Patent Claims" of a Contributor
    +    means any patent claim(s), including without limitation, method,
    +    process, and apparatus claims, in any patent Licensable by such
    +    Contributor that would be infringed, but for the grant of the
    +    License, by the making, using, selling, offering for sale, having
    +    made, import, or transfer of either its Contributions or its
    +    Contributor Version.
    +
    +1.12. "Secondary License"
    +    means either the GNU General Public License, Version 2.0, the GNU
    +    Lesser General Public License, Version 2.1, the GNU Affero General
    +    Public License, Version 3.0, or any later versions of those
    +    licenses.
    +
    +1.13. "Source Code Form"
    +    means the form of the work preferred for making modifications.
    +
    +1.14. "You" (or "Your")
    +    means an individual or a legal entity exercising rights under this
    +    License. For legal entities, "You" includes any entity that
    +    controls, is controlled by, or is under common control with You. For
    +    purposes of this definition, "control" means (a) the power, direct
    +    or indirect, to cause the direction or management of such entity,
    +    whether by contract or otherwise, or (b) ownership of more than
    +    fifty percent (50%) of the outstanding shares or beneficial
    +    ownership of such entity.
    +
    +2. License Grants and Conditions
    +--------------------------------
    +
    +2.1. Grants
    +
    +Each Contributor hereby grants You a world-wide, royalty-free,
    +non-exclusive license:
    +
    +(a) under intellectual property rights (other than patent or trademark)
    +    Licensable by such Contributor to use, reproduce, make available,
    +    modify, display, perform, distribute, and otherwise exploit its
    +    Contributions, either on an unmodified basis, with Modifications, or
    +    as part of a Larger Work; and
    +
    +(b) under Patent Claims of such Contributor to make, use, sell, offer
    +    for sale, have made, import, and otherwise transfer either its
    +    Contributions or its Contributor Version.
    +
    +2.2. Effective Date
    +
    +The licenses granted in Section 2.1 with respect to any Contribution
    +become effective for each Contribution on the date the Contributor first
    +distributes such Contribution.
    +
    +2.3. Limitations on Grant Scope
    +
    +The licenses granted in this Section 2 are the only rights granted under
    +this License. No additional rights or licenses will be implied from the
    +distribution or licensing of Covered Software under this License.
    +Notwithstanding Section 2.1(b) above, no patent license is granted by a
    +Contributor:
    +
    +(a) for any code that a Contributor has removed from Covered Software;
    +    or
    +
    +(b) for infringements caused by: (i) Your and any other third party's
    +    modifications of Covered Software, or (ii) the combination of its
    +    Contributions with other software (except as part of its Contributor
    +    Version); or
    +
    +(c) under Patent Claims infringed by Covered Software in the absence of
    +    its Contributions.
    +
    +This License does not grant any rights in the trademarks, service marks,
    +or logos of any Contributor (except as may be necessary to comply with
    +the notice requirements in Section 3.4).
    +
    +2.4. Subsequent Licenses
    +
    +No Contributor makes additional grants as a result of Your choice to
    +distribute the Covered Software under a subsequent version of this
    +License (see Section 10.2) or under the terms of a Secondary License (if
    +permitted under the terms of Section 3.3).
    +
    +2.5. Representation
    +
    +Each Contributor represents that the Contributor believes its
    +Contributions are its original creation(s) or it has sufficient rights
    +to grant the rights to its Contributions conveyed by this License.
    +
    +2.6. Fair Use
    +
    +This License is not intended to limit any rights You have under
    +applicable copyright doctrines of fair use, fair dealing, or other
    +equivalents.
    +
    +2.7. Conditions
    +
    +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
    +in Section 2.1.
    +
    +3. Responsibilities
    +-------------------
    +
    +3.1. Distribution of Source Form
    +
    +All distribution of Covered Software in Source Code Form, including any
    +Modifications that You create or to which You contribute, must be under
    +the terms of this License. You must inform recipients that the Source
    +Code Form of the Covered Software is governed by the terms of this
    +License, and how they can obtain a copy of this License. You may not
    +attempt to alter or restrict the recipients' rights in the Source Code
    +Form.
    +
    +3.2. Distribution of Executable Form
    +
    +If You distribute Covered Software in Executable Form then:
    +
    +(a) such Covered Software must also be made available in Source Code
    +    Form, as described in Section 3.1, and You must inform recipients of
    +    the Executable Form how they can obtain a copy of such Source Code
    +    Form by reasonable means in a timely manner, at a charge no more
    +    than the cost of distribution to the recipient; and
    +
    +(b) You may distribute such Executable Form under the terms of this
    +    License, or sublicense it under different terms, provided that the
    +    license for the Executable Form does not attempt to limit or alter
    +    the recipients' rights in the Source Code Form under this License.
    +
    +3.3. Distribution of a Larger Work
    +
    +You may create and distribute a Larger Work under terms of Your choice,
    +provided that You also comply with the requirements of this License for
    +the Covered Software. If the Larger Work is a combination of Covered
    +Software with a work governed by one or more Secondary Licenses, and the
    +Covered Software is not Incompatible With Secondary Licenses, this
    +License permits You to additionally distribute such Covered Software
    +under the terms of such Secondary License(s), so that the recipient of
    +the Larger Work may, at their option, further distribute the Covered
    +Software under the terms of either this License or such Secondary
    +License(s).
    +
    +3.4. Notices
    +
    +You may not remove or alter the substance of any license notices
    +(including copyright notices, patent notices, disclaimers of warranty,
    +or limitations of liability) contained within the Source Code Form of
    +the Covered Software, except that You may alter any license notices to
    +the extent required to remedy known factual inaccuracies.
    +
    +3.5. Application of Additional Terms
    +
    +You may choose to offer, and to charge a fee for, warranty, support,
    +indemnity or liability obligations to one or more recipients of Covered
    +Software. However, You may do so only on Your own behalf, and not on
    +behalf of any Contributor. You must make it absolutely clear that any
    +such warranty, support, indemnity, or liability obligation is offered by
    +You alone, and You hereby agree to indemnify every Contributor for any
    +liability incurred by such Contributor as a result of warranty, support,
    +indemnity or liability terms You offer. You may include additional
    +disclaimers of warranty and limitations of liability specific to any
    +jurisdiction.
    +
    +4. Inability to Comply Due to Statute or Regulation
    +---------------------------------------------------
    +
    +If it is impossible for You to comply with any of the terms of this
    +License with respect to some or all of the Covered Software due to
    +statute, judicial order, or regulation then You must: (a) comply with
    +the terms of this License to the maximum extent possible; and (b)
    +describe the limitations and the code they affect. Such description must
    +be placed in a text file included with all distributions of the Covered
    +Software under this License. Except to the extent prohibited by statute
    +or regulation, such description must be sufficiently detailed for a
    +recipient of ordinary skill to be able to understand it.
    +
    +5. Termination
    +--------------
    +
    +5.1. The rights granted under this License will terminate automatically
    +if You fail to comply with any of its terms. However, if You become
    +compliant, then the rights granted under this License from a particular
    +Contributor are reinstated (a) provisionally, unless and until such
    +Contributor explicitly and finally terminates Your grants, and (b) on an
    +ongoing basis, if such Contributor fails to notify You of the
    +non-compliance by some reasonable means prior to 60 days after You have
    +come back into compliance. Moreover, Your grants from a particular
    +Contributor are reinstated on an ongoing basis if such Contributor
    +notifies You of the non-compliance by some reasonable means, this is the
    +first time You have received notice of non-compliance with this License
    +from such Contributor, and You become compliant prior to 30 days after
    +Your receipt of the notice.
    +
    +5.2. If You initiate litigation against any entity by asserting a patent
    +infringement claim (excluding declaratory judgment actions,
    +counter-claims, and cross-claims) alleging that a Contributor Version
    +directly or indirectly infringes any patent, then the rights granted to
    +You by any and all Contributors for the Covered Software under Section
    +2.1 of this License shall terminate.
    +
    +5.3. In the event of termination under Sections 5.1 or 5.2 above, all
    +end user license agreements (excluding distributors and resellers) which
    +have been validly granted by You or Your distributors under this License
    +prior to termination shall survive termination.
    +
    +************************************************************************
    +*                                                                      *
    +*  6. Disclaimer of Warranty                                           *
    +*  -------------------------                                           *
    +*                                                                      *
    +*  Covered Software is provided under this License on an "as is"       *
    +*  basis, without warranty of any kind, either expressed, implied, or  *
    +*  statutory, including, without limitation, warranties that the       *
    +*  Covered Software is free of defects, merchantable, fit for a        *
    +*  particular purpose or non-infringing. The entire risk as to the     *
    +*  quality and performance of the Covered Software is with You.        *
    +*  Should any Covered Software prove defective in any respect, You     *
    +*  (not any Contributor) assume the cost of any necessary servicing,   *
    +*  repair, or correction. This disclaimer of warranty constitutes an   *
    +*  essential part of this License. No use of any Covered Software is   *
    +*  authorized under this License except under this disclaimer.         *
    +*                                                                      *
    +************************************************************************
    +
    +************************************************************************
    +*                                                                      *
    +*  7. Limitation of Liability                                          *
    +*  --------------------------                                          *
    +*                                                                      *
    +*  Under no circumstances and under no legal theory, whether tort      *
    +*  (including negligence), contract, or otherwise, shall any           *
    +*  Contributor, or anyone who distributes Covered Software as          *
    +*  permitted above, be liable to You for any direct, indirect,         *
    +*  special, incidental, or consequential damages of any character      *
    +*  including, without limitation, damages for lost profits, loss of    *
    +*  goodwill, work stoppage, computer failure or malfunction, or any    *
    +*  and all other commercial damages or losses, even if such party      *
    +*  shall have been informed of the possibility of such damages. This   *
    +*  limitation of liability shall not apply to liability for death or   *
    +*  personal injury resulting from such party's negligence to the       *
    +*  extent applicable law prohibits such limitation. Some               *
    +*  jurisdictions do not allow the exclusion or limitation of           *
    +*  incidental or consequential damages, so this exclusion and          *
    +*  limitation may not apply to You.                                    *
    +*                                                                      *
    +************************************************************************
    +
    +8. Litigation
    +-------------
    +
    +Any litigation relating to this License may be brought only in the
    +courts of a jurisdiction where the defendant maintains its principal
    +place of business and such litigation shall be governed by laws of that
    +jurisdiction, without reference to its conflict-of-law provisions.
    +Nothing in this Section shall prevent a party's ability to bring
    +cross-claims or counter-claims.
    +
    +9. Miscellaneous
    +----------------
    +
    +This License represents the complete agreement concerning the subject
    +matter hereof. If any provision of this License is held to be
    +unenforceable, such provision shall be reformed only to the extent
    +necessary to make it enforceable. Any law or regulation which provides
    +that the language of a contract shall be construed against the drafter
    +shall not be used to construe this License against a Contributor.
    +
    +10. Versions of the License
    +---------------------------
    +
    +10.1. New Versions
    +
    +Mozilla Foundation is the license steward. Except as provided in Section
    +10.3, no one other than the license steward has the right to modify or
    +publish new versions of this License. Each version will be given a
    +distinguishing version number.
    +
    +10.2. Effect of New Versions
    +
    +You may distribute the Covered Software under the terms of the version
    +of the License under which You originally received the Covered Software,
    +or under the terms of any subsequent version published by the license
    +steward.
    +
    +10.3. Modified Versions
    +
    +If you create software not governed by this License, and you want to
    +create a new license for such software, you may create and use a
    +modified version of this License if you rename the license and remove
    +any references to the name of the license steward (except to note that
    +such modified license differs from this License).
    +
    +10.4. Distributing Source Code Form that is Incompatible With Secondary
    +Licenses
    +
    +If You choose to distribute Source Code Form that is Incompatible With
    +Secondary Licenses under the terms of this version of the License, the
    +notice described in Exhibit B of this License must be attached.
    +
    +Exhibit A - Source Code Form License Notice
    +-------------------------------------------
    +
    +  This Source Code Form is subject to the terms of the Mozilla Public
    +  License, v. 2.0. If a copy of the MPL was not distributed with this
    +  file, You can obtain one at http://mozilla.org/MPL/2.0/.
    +
    +If it is not possible or desirable to put the notice in a particular
    +file, then You may include the notice in a location (such as a LICENSE
    +file in a relevant directory) where a recipient would be likely to look
    +for such a notice.
    +
    +You may add additional accurate notices of copyright ownership.
    +
    +Exhibit B - "Incompatible With Secondary Licenses" Notice
    +---------------------------------------------------------
    +
    +  This Source Code Form is "Incompatible With Secondary Licenses", as
    +  defined by the Mozilla Public License, v. 2.0.
    +
    +
  • +
  • +

    Mozilla Public License 2.0

    +

    Used by:

    + +
    Mozilla Public License Version 2.0
    +==================================
    +
    +1. Definitions
    +--------------
    +
    +1.1. "Contributor"
    +    means each individual or legal entity that creates, contributes to
    +    the creation of, or owns Covered Software.
    +
    +1.2. "Contributor Version"
    +    means the combination of the Contributions of others (if any) used
    +    by a Contributor and that particular Contributor's Contribution.
    +
    +1.3. "Contribution"
    +    means Covered Software of a particular Contributor.
    +
    +1.4. "Covered Software"
    +    means Source Code Form to which the initial Contributor has attached
    +    the notice in Exhibit A, the Executable Form of such Source Code
    +    Form, and Modifications of such Source Code Form, in each case
    +    including portions thereof.
    +
    +1.5. "Incompatible With Secondary Licenses"
    +    means
    +
    +    (a) that the initial Contributor has attached the notice described
    +        in Exhibit B to the Covered Software; or
    +
    +    (b) that the Covered Software was made available under the terms of
    +        version 1.1 or earlier of the License, but not also under the
    +        terms of a Secondary License.
    +
    +1.6. "Executable Form"
    +    means any form of the work other than Source Code Form.
    +
    +1.7. "Larger Work"
    +    means a work that combines Covered Software with other material, in 
    +    a separate file or files, that is not Covered Software.
    +
    +1.8. "License"
    +    means this document.
    +
    +1.9. "Licensable"
    +    means having the right to grant, to the maximum extent possible,
    +    whether at the time of the initial grant or subsequently, any and
    +    all of the rights conveyed by this License.
    +
    +1.10. "Modifications"
    +    means any of the following:
    +
    +    (a) any file in Source Code Form that results from an addition to,
    +        deletion from, or modification of the contents of Covered
    +        Software; or
    +
    +    (b) any new file in Source Code Form that contains any Covered
    +        Software.
    +
    +1.11. "Patent Claims" of a Contributor
    +    means any patent claim(s), including without limitation, method,
    +    process, and apparatus claims, in any patent Licensable by such
    +    Contributor that would be infringed, but for the grant of the
    +    License, by the making, using, selling, offering for sale, having
    +    made, import, or transfer of either its Contributions or its
    +    Contributor Version.
    +
    +1.12. "Secondary License"
    +    means either the GNU General Public License, Version 2.0, the GNU
    +    Lesser General Public License, Version 2.1, the GNU Affero General
    +    Public License, Version 3.0, or any later versions of those
    +    licenses.
    +
    +1.13. "Source Code Form"
    +    means the form of the work preferred for making modifications.
    +
    +1.14. "You" (or "Your")
    +    means an individual or a legal entity exercising rights under this
    +    License. For legal entities, "You" includes any entity that
    +    controls, is controlled by, or is under common control with You. For
    +    purposes of this definition, "control" means (a) the power, direct
    +    or indirect, to cause the direction or management of such entity,
    +    whether by contract or otherwise, or (b) ownership of more than
    +    fifty percent (50%) of the outstanding shares or beneficial
    +    ownership of such entity.
    +
    +2. License Grants and Conditions
    +--------------------------------
    +
    +2.1. Grants
    +
    +Each Contributor hereby grants You a world-wide, royalty-free,
    +non-exclusive license:
    +
    +(a) under intellectual property rights (other than patent or trademark)
    +    Licensable by such Contributor to use, reproduce, make available,
    +    modify, display, perform, distribute, and otherwise exploit its
    +    Contributions, either on an unmodified basis, with Modifications, or
    +    as part of a Larger Work; and
    +
    +(b) under Patent Claims of such Contributor to make, use, sell, offer
    +    for sale, have made, import, and otherwise transfer either its
    +    Contributions or its Contributor Version.
    +
    +2.2. Effective Date
    +
    +The licenses granted in Section 2.1 with respect to any Contribution
    +become effective for each Contribution on the date the Contributor first
    +distributes such Contribution.
    +
    +2.3. Limitations on Grant Scope
    +
    +The licenses granted in this Section 2 are the only rights granted under
    +this License. No additional rights or licenses will be implied from the
    +distribution or licensing of Covered Software under this License.
    +Notwithstanding Section 2.1(b) above, no patent license is granted by a
    +Contributor:
    +
    +(a) for any code that a Contributor has removed from Covered Software;
    +    or
    +
    +(b) for infringements caused by: (i) Your and any other third party's
    +    modifications of Covered Software, or (ii) the combination of its
    +    Contributions with other software (except as part of its Contributor
    +    Version); or
    +
    +(c) under Patent Claims infringed by Covered Software in the absence of
    +    its Contributions.
    +
    +This License does not grant any rights in the trademarks, service marks,
    +or logos of any Contributor (except as may be necessary to comply with
    +the notice requirements in Section 3.4).
    +
    +2.4. Subsequent Licenses
    +
    +No Contributor makes additional grants as a result of Your choice to
    +distribute the Covered Software under a subsequent version of this
    +License (see Section 10.2) or under the terms of a Secondary License (if
    +permitted under the terms of Section 3.3).
    +
    +2.5. Representation
    +
    +Each Contributor represents that the Contributor believes its
    +Contributions are its original creation(s) or it has sufficient rights
    +to grant the rights to its Contributions conveyed by this License.
    +
    +2.6. Fair Use
    +
    +This License is not intended to limit any rights You have under
    +applicable copyright doctrines of fair use, fair dealing, or other
    +equivalents.
    +
    +2.7. Conditions
    +
    +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
    +in Section 2.1.
    +
    +3. Responsibilities
    +-------------------
    +
    +3.1. Distribution of Source Form
    +
    +All distribution of Covered Software in Source Code Form, including any
    +Modifications that You create or to which You contribute, must be under
    +the terms of this License. You must inform recipients that the Source
    +Code Form of the Covered Software is governed by the terms of this
    +License, and how they can obtain a copy of this License. You may not
    +attempt to alter or restrict the recipients' rights in the Source Code
    +Form.
    +
    +3.2. Distribution of Executable Form
    +
    +If You distribute Covered Software in Executable Form then:
    +
    +(a) such Covered Software must also be made available in Source Code
    +    Form, as described in Section 3.1, and You must inform recipients of
    +    the Executable Form how they can obtain a copy of such Source Code
    +    Form by reasonable means in a timely manner, at a charge no more
    +    than the cost of distribution to the recipient; and
    +
    +(b) You may distribute such Executable Form under the terms of this
    +    License, or sublicense it under different terms, provided that the
    +    license for the Executable Form does not attempt to limit or alter
    +    the recipients' rights in the Source Code Form under this License.
    +
    +3.3. Distribution of a Larger Work
    +
    +You may create and distribute a Larger Work under terms of Your choice,
    +provided that You also comply with the requirements of this License for
    +the Covered Software. If the Larger Work is a combination of Covered
    +Software with a work governed by one or more Secondary Licenses, and the
    +Covered Software is not Incompatible With Secondary Licenses, this
    +License permits You to additionally distribute such Covered Software
    +under the terms of such Secondary License(s), so that the recipient of
    +the Larger Work may, at their option, further distribute the Covered
    +Software under the terms of either this License or such Secondary
    +License(s).
    +
    +3.4. Notices
    +
    +You may not remove or alter the substance of any license notices
    +(including copyright notices, patent notices, disclaimers of warranty,
    +or limitations of liability) contained within the Source Code Form of
    +the Covered Software, except that You may alter any license notices to
    +the extent required to remedy known factual inaccuracies.
    +
    +3.5. Application of Additional Terms
    +
    +You may choose to offer, and to charge a fee for, warranty, support,
    +indemnity or liability obligations to one or more recipients of Covered
    +Software. However, You may do so only on Your own behalf, and not on
    +behalf of any Contributor. You must make it absolutely clear that any
    +such warranty, support, indemnity, or liability obligation is offered by
    +You alone, and You hereby agree to indemnify every Contributor for any
    +liability incurred by such Contributor as a result of warranty, support,
    +indemnity or liability terms You offer. You may include additional
    +disclaimers of warranty and limitations of liability specific to any
    +jurisdiction.
    +
    +4. Inability to Comply Due to Statute or Regulation
    +---------------------------------------------------
    +
    +If it is impossible for You to comply with any of the terms of this
    +License with respect to some or all of the Covered Software due to
    +statute, judicial order, or regulation then You must: (a) comply with
    +the terms of this License to the maximum extent possible; and (b)
    +describe the limitations and the code they affect. Such description must
    +be placed in a text file included with all distributions of the Covered
    +Software under this License. Except to the extent prohibited by statute
    +or regulation, such description must be sufficiently detailed for a
    +recipient of ordinary skill to be able to understand it.
    +
    +5. Termination
    +--------------
    +
    +5.1. The rights granted under this License will terminate automatically
    +if You fail to comply with any of its terms. However, if You become
    +compliant, then the rights granted under this License from a particular
    +Contributor are reinstated (a) provisionally, unless and until such
    +Contributor explicitly and finally terminates Your grants, and (b) on an
    +ongoing basis, if such Contributor fails to notify You of the
    +non-compliance by some reasonable means prior to 60 days after You have
    +come back into compliance. Moreover, Your grants from a particular
    +Contributor are reinstated on an ongoing basis if such Contributor
    +notifies You of the non-compliance by some reasonable means, this is the
    +first time You have received notice of non-compliance with this License
    +from such Contributor, and You become compliant prior to 30 days after
    +Your receipt of the notice.
    +
    +5.2. If You initiate litigation against any entity by asserting a patent
    +infringement claim (excluding declaratory judgment actions,
    +counter-claims, and cross-claims) alleging that a Contributor Version
    +directly or indirectly infringes any patent, then the rights granted to
    +You by any and all Contributors for the Covered Software under Section
    +2.1 of this License shall terminate.
    +
    +5.3. In the event of termination under Sections 5.1 or 5.2 above, all
    +end user license agreements (excluding distributors and resellers) which
    +have been validly granted by You or Your distributors under this License
    +prior to termination shall survive termination.
    +
    +************************************************************************
    +*                                                                      *
    +*  6. Disclaimer of Warranty                                           *
    +*  -------------------------                                           *
    +*                                                                      *
    +*  Covered Software is provided under this License on an "as is"       *
    +*  basis, without warranty of any kind, either expressed, implied, or  *
    +*  statutory, including, without limitation, warranties that the       *
    +*  Covered Software is free of defects, merchantable, fit for a        *
    +*  particular purpose or non-infringing. The entire risk as to the     *
    +*  quality and performance of the Covered Software is with You.        *
    +*  Should any Covered Software prove defective in any respect, You     *
    +*  (not any Contributor) assume the cost of any necessary servicing,   *
    +*  repair, or correction. This disclaimer of warranty constitutes an   *
    +*  essential part of this License. No use of any Covered Software is   *
    +*  authorized under this License except under this disclaimer.         *
    +*                                                                      *
    +************************************************************************
    +
    +************************************************************************
    +*                                                                      *
    +*  7. Limitation of Liability                                          *
    +*  --------------------------                                          *
    +*                                                                      *
    +*  Under no circumstances and under no legal theory, whether tort      *
    +*  (including negligence), contract, or otherwise, shall any           *
    +*  Contributor, or anyone who distributes Covered Software as          *
    +*  permitted above, be liable to You for any direct, indirect,         *
    +*  special, incidental, or consequential damages of any character      *
    +*  including, without limitation, damages for lost profits, loss of    *
    +*  goodwill, work stoppage, computer failure or malfunction, or any    *
    +*  and all other commercial damages or losses, even if such party      *
    +*  shall have been informed of the possibility of such damages. This   *
    +*  limitation of liability shall not apply to liability for death or   *
    +*  personal injury resulting from such party's negligence to the       *
    +*  extent applicable law prohibits such limitation. Some               *
    +*  jurisdictions do not allow the exclusion or limitation of           *
    +*  incidental or consequential damages, so this exclusion and          *
    +*  limitation may not apply to You.                                    *
    +*                                                                      *
    +************************************************************************
    +
    +8. Litigation
    +-------------
    +
    +Any litigation relating to this License may be brought only in the
    +courts of a jurisdiction where the defendant maintains its principal
    +place of business and such litigation shall be governed by laws of that
    +jurisdiction, without reference to its conflict-of-law provisions.
    +Nothing in this Section shall prevent a party's ability to bring
    +cross-claims or counter-claims.
    +
    +9. Miscellaneous
    +----------------
    +
    +This License represents the complete agreement concerning the subject
    +matter hereof. If any provision of this License is held to be
    +unenforceable, such provision shall be reformed only to the extent
    +necessary to make it enforceable. Any law or regulation which provides
    +that the language of a contract shall be construed against the drafter
    +shall not be used to construe this License against a Contributor.
    +
    +10. Versions of the License
    +---------------------------
    +
    +10.1. New Versions
    +
    +Mozilla Foundation is the license steward. Except as provided in Section
    +10.3, no one other than the license steward has the right to modify or
    +publish new versions of this License. Each version will be given a
    +distinguishing version number.
    +
    +10.2. Effect of New Versions
    +
    +You may distribute the Covered Software under the terms of the version
    +of the License under which You originally received the Covered Software,
    +or under the terms of any subsequent version published by the license
    +steward.
    +
    +10.3. Modified Versions
    +
    +If you create software not governed by this License, and you want to
    +create a new license for such software, you may create and use a
    +modified version of this License if you rename the license and remove
    +any references to the name of the license steward (except to note that
    +such modified license differs from this License).
    +
    +10.4. Distributing Source Code Form that is Incompatible With Secondary
    +Licenses
    +
    +If You choose to distribute Source Code Form that is Incompatible With
    +Secondary Licenses under the terms of this version of the License, the
    +notice described in Exhibit B of this License must be attached.
    +
    +Exhibit A - Source Code Form License Notice
    +-------------------------------------------
    +
    +  This Source Code Form is subject to the terms of the Mozilla Public
    +  License, v. 2.0. If a copy of the MPL was not distributed with this
    +  file, You can obtain one at https://mozilla.org/MPL/2.0/.
    +
    +If it is not possible or desirable to put the notice in a particular
    +file, then You may include the notice in a location (such as a LICENSE
    +file in a relevant directory) where a recipient would be likely to look
    +for such a notice.
    +
    +You may add additional accurate notices of copyright ownership.
    +
    +Exhibit B - "Incompatible With Secondary Licenses" Notice
    +---------------------------------------------------------
    +
    +  This Source Code Form is "Incompatible With Secondary Licenses", as
    +  defined by the Mozilla Public License, v. 2.0.
    +
    +
  • +
  • +

    Mozilla Public License 2.0

    +

    Used by:

    + +
    This packge contains a modified version of ca-bundle.crt:	
    +
    +ca-bundle.crt -- Bundle of CA Root Certificates	
    +
    +Certificate data from Mozilla as of: Thu Nov  3 19:04:19 2011#	
    +This is a bundle of X.509 certificates of public Certificate Authorities	
    +(CA). These were automatically extracted from Mozilla's root certificates	
    +file (certdata.txt).  This file can be found in the mozilla source tree:	
    +http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1#	
    +It contains the certificates in PEM format and therefore	
    +can be directly used with curl / libcurl / php_curl, or with	
    +an Apache+mod_ssl webserver for SSL client authentication.	
    +Just configure this file as the SSLCACertificateFile.#	
    +
    +***** BEGIN LICENSE BLOCK *****	
    +This Source Code Form is subject to the terms of the Mozilla Public License,	
    +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain	
    +one at http://mozilla.org/MPL/2.0/.	
    +
    +***** END LICENSE BLOCK *****	
    +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $	
    +
    +
  • +
  • +

    SIL Open Font License 1.1

    +

    Used by:

    + +
    This Font Software is licensed under the SIL Open Font License,
    +Version 1.1.
    +
    +This license is copied below, and is also available with a FAQ at:
    +http://scripts.sil.org/OFL
    +
    +-----------------------------------------------------------
    +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
    +-----------------------------------------------------------
    +
    +PREAMBLE
    +The goals of the Open Font License (OFL) are to stimulate worldwide
    +development of collaborative font projects, to support the font
    +creation efforts of academic and linguistic communities, and to
    +provide a free and open framework in which fonts may be shared and
    +improved in partnership with others.
    +
    +The OFL allows the licensed fonts to be used, studied, modified and
    +redistributed freely as long as they are not sold by themselves. The
    +fonts, including any derivative works, can be bundled, embedded,
    +redistributed and/or sold with any software provided that any reserved
    +names are not used by derivative works. The fonts and derivatives,
    +however, cannot be released under any other type of license. The
    +requirement for fonts to remain under this license does not apply to
    +any document created using the fonts or their derivatives.
    +
    +DEFINITIONS
    +"Font Software" refers to the set of files released by the Copyright
    +Holder(s) under this license and clearly marked as such. This may
    +include source files, build scripts and documentation.
    +
    +"Reserved Font Name" refers to any names specified as such after the
    +copyright statement(s).
    +
    +"Original Version" refers to the collection of Font Software
    +components as distributed by the Copyright Holder(s).
    +
    +"Modified Version" refers to any derivative made by adding to,
    +deleting, or substituting -- in part or in whole -- any of the
    +components of the Original Version, by changing formats or by porting
    +the Font Software to a new environment.
    +
    +"Author" refers to any designer, engineer, programmer, technical
    +writer or other person who contributed to the Font Software.
    +
    +PERMISSION & CONDITIONS
    +Permission is hereby granted, free of charge, to any person obtaining
    +a copy of the Font Software, to use, study, copy, merge, embed,
    +modify, redistribute, and sell modified and unmodified copies of the
    +Font Software, subject to the following conditions:
    +
    +1) Neither the Font Software nor any of its individual components, in
    +Original or Modified Versions, may be sold by itself.
    +
    +2) Original or Modified Versions of the Font Software may be bundled,
    +redistributed and/or sold with any software, provided that each copy
    +contains the above copyright notice and this license. These can be
    +included either as stand-alone text files, human-readable headers or
    +in the appropriate machine-readable metadata fields within text or
    +binary files as long as those fields can be easily viewed by the user.
    +
    +3) No Modified Version of the Font Software may use the Reserved Font
    +Name(s) unless explicit written permission is granted by the
    +corresponding Copyright Holder. This restriction only applies to the
    +primary font name as presented to the users.
    +
    +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
    +Software shall not be used to promote, endorse or advertise any
    +Modified Version, except to acknowledge the contribution(s) of the
    +Copyright Holder(s) and the Author(s) or with their explicit written
    +permission.
    +
    +5) The Font Software, modified or unmodified, in part or in whole,
    +must be distributed entirely under this license, and must not be
    +distributed under any other license. The requirement for fonts to
    +remain under this license does not apply to any document created using
    +the Font Software.
    +
    +TERMINATION
    +This license becomes null and void if any of the above conditions are
    +not met.
    +
    +DISCLAIMER
    +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
    +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
    +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
    +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
    +OTHER DEALINGS IN THE FONT SOFTWARE.
    +
    +
  • +
  • +

    OpenSSL License

    +

    Used by:

    + +
    /* ====================================================================
    + * Copyright (c) 1998-2011 The OpenSSL Project.  All rights reserved.
    + *
    + * Redistribution and use in source and binary forms, with or without
    + * modification, are permitted provided that the following conditions
    + * are met:
    + *
    + * 1. Redistributions of source code must retain the above copyright
    + *    notice, this list of conditions and the following disclaimer. 
    + *
    + * 2. Redistributions in binary form must reproduce the above copyright
    + *    notice, this list of conditions and the following disclaimer in
    + *    the documentation and/or other materials provided with the
    + *    distribution.
    + *
    + * 3. All advertising materials mentioning features or use of this
    + *    software must display the following acknowledgment:
    + *    "This product includes software developed by the OpenSSL Project
    + *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
    + *
    + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
    + *    endorse or promote products derived from this software without
    + *    prior written permission. For written permission, please contact
    + *    openssl-core@openssl.org.
    + *
    + * 5. Products derived from this software may not be called "OpenSSL"
    + *    nor may "OpenSSL" appear in their names without prior written
    + *    permission of the OpenSSL Project.
    + *
    + * 6. Redistributions of any form whatsoever must retain the following
    + *    acknowledgment:
    + *    "This product includes software developed by the OpenSSL Project
    + *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
    + *
    + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
    + * EXPRESSED 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 OpenSSL PROJECT OR
    + * ITS 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.
    + * ====================================================================
    + *
    + * This product includes cryptographic software written by Eric Young
    + * (eay@cryptsoft.com).  This product includes software written by Tim
    + * Hudson (tjh@cryptsoft.com).
    + *
    + */
    +
  • +
  • +

    Unicode License Agreement - Data Files and Software (2016)

    +

    Used by:

    + +
    UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
    +
    +Unicode Data Files include all data files under the directories http://www.unicode.org/Public/, http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and http://www.unicode.org/utility/trac/browser/.
    +
    +Unicode Data Files do not include PDF online code charts under the directory http://www.unicode.org/Public/.
    +
    +Software includes any source code published in the Unicode Standard or under the directories http://www.unicode.org/Public/, http://www.unicode.org/reports/, http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and http://www.unicode.org/utility/trac/browser/.
    +
    +NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
    +
    +COPYRIGHT AND PERMISSION NOTICE
    +
    +Copyright © 1991-2016 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation (the "Data Files") or Unicode software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that either
    +
    +     (a) this copyright and permission notice appear with all copies of the Data Files or Software, or
    +     (b) this copyright and permission notice appear in associated Documentation.
    +
    +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL 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 THE DATA FILES OR SOFTWARE.
    +
    +Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder.
    +
    +
  • +
  • +

    zlib License

    +

    Used by:

    + +
    Copyright (c) 2021 Orson Peters <orsonpeters@gmail.com>
    +
    +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.
    +
    +
  • +
+
+ + + + diff --git a/plugin-reaper-realearn/about.toml b/plugin-reaper-realearn/about.toml new file mode 100644 index 0000000..12a28b0 --- /dev/null +++ b/plugin-reaper-realearn/about.toml @@ -0,0 +1,24 @@ +accepted = [ + "MIT", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "BSL-1.0", + "CC0-1.0", + "OFL-1.1", + "Zlib", + "MPL-2.0", + "Unicode-DFS-2016", + "LicenseRef-UFL-1.0", + "OpenSSL", +] + +workarounds = [ + "ring", +] + +private = { ignore = true } + +ignore-dev-dependencies = true +ignore-build-dependencies = true diff --git a/plugin-reaper-realearn/allocator/Cargo.toml b/plugin-reaper-realearn/allocator/Cargo.toml new file mode 100644 index 0000000..dda9f11 --- /dev/null +++ b/plugin-reaper-realearn/allocator/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "helgobox-allocator" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/allocator/src/lib.rs b/plugin-reaper-realearn/allocator/src/lib.rs new file mode 100644 index 0000000..031d877 --- /dev/null +++ b/plugin-reaper-realearn/allocator/src/lib.rs @@ -0,0 +1,312 @@ +//! A variation of https://github.com/Windfisch/rust-assert-no-alloc.git with the following changes: +//! +//! - Automatically offloads deallocation to other thread when in real-time thread, both in debug +//! and release builds +//! - Provides a pluggable deallocation function (for recording stats etc.) +//! - Assertions are active in debug builds only +//! - When assertion violated, it always panics instead of aborting or printing an error (mainly for +//! good testability but also nice otherwise as long as set_alloc_error_hook() is still unstable) +use std::alloc::{GlobalAlloc, Layout, System}; +use std::ffi::c_void; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::mpsc::{Receiver, SyncSender, TrySendError}; +use std::sync::{mpsc, OnceLock}; +use std::thread; +use std::thread::JoinHandle; + +#[cfg(debug_assertions)] +thread_local! { + static ALLOC_FORBID_COUNT: std::cell::Cell = const { std::cell::Cell::new(0) }; + static ALLOC_PERMIT_COUNT: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + +static UNDESIRED_ALLOCATION_COUNTER: AtomicU32 = AtomicU32::new(0); + +pub fn undesired_allocation_count() -> u32 { + UNDESIRED_ALLOCATION_COUNTER.load(Ordering::Relaxed) +} + +#[cfg(not(debug_assertions))] +pub fn assert_no_alloc T>(func: F) -> T { + // no-op + func() +} + +#[cfg(not(debug_assertions))] +pub fn permit_alloc T>(func: F) -> T { + // no-op + func() +} + +#[cfg(debug_assertions)] +/// Calls the `func` closure, but forbids any (de)allocations. +/// +/// If a call to the allocator is made, the program will abort with an error, +/// print a warning (depending on the `warn_debug` feature flag. Or ignore +/// the situation, when compiled in `--release` mode with the `disable_release` +///feature flag set (which is the default)). +pub fn assert_no_alloc T>(func: F) -> T { + // RAII guard for managing the forbid counter. This is to ensure correct behaviour + // when catch_unwind is used + struct Guard; + impl Guard { + fn new() -> Guard { + ALLOC_FORBID_COUNT.with(|c| c.set(c.get() + 1)); + Guard + } + } + impl Drop for Guard { + fn drop(&mut self) { + ALLOC_FORBID_COUNT.with(|c| c.set(c.get() - 1)); + } + } + + let guard = Guard::new(); // increment the forbid counter + let ret = func(); + std::mem::drop(guard); // decrement the forbid counter + ret +} + +#[cfg(debug_assertions)] +/// Calls the `func` closure. Allocations are temporarily allowed, even if this +/// code runs inside of assert_no_alloc. +pub fn permit_alloc T>(func: F) -> T { + // RAII guard for managing the permit counter + struct Guard; + impl Guard { + fn new() -> Guard { + ALLOC_PERMIT_COUNT.with(|c| c.set(c.get() + 1)); + Guard + } + } + impl Drop for Guard { + fn drop(&mut self) { + ALLOC_PERMIT_COUNT.with(|c| c.set(c.get() - 1)); + } + } + + let guard = Guard::new(); // increment the forbid counter + let ret = func(); + std::mem::drop(guard); // decrement the forbid counter + ret +} + +/// The custom allocator that handles the checking. +pub struct HelgobossAllocator { + sync_deallocator: D, + async_deallocation_machine: OnceLock>, +} + +struct AsyncDeallocationMachine { + sender: SyncSender, + integration: I, +} + +#[derive(Debug)] +pub struct AsyncDeallocatorCommandReceiver(Receiver); + +#[derive(Debug)] +enum AsyncDeallocatorCommand { + Stop, + Deallocate { + ptr: *mut u8, + layout: Layout, + }, + DeallocateForeign { + value: *mut c_void, + deallocate: unsafe extern "C" fn(value: *mut c_void), + }, +} + +unsafe impl Send for AsyncDeallocatorCommand {} + +pub trait AsyncDeallocationIntegration { + /// Should return `true` if deallocation should be offloaded to the dedicated deallocation + /// thread (e.g. when the current thread is a real-time thread). + fn offload_deallocation(&self) -> bool; +} + +pub trait Deallocate { + /// The actual code executed on deallocation. + fn deallocate(&self, ptr: *mut u8, layout: Layout); +} + +pub fn start_async_deallocation_thread( + deallocator: impl Deallocate + Send + 'static, + receiver: AsyncDeallocatorCommandReceiver, +) -> JoinHandle { + thread::Builder::new() + .name("Helgobox deallocator".to_string()) + .spawn(move || { + while let Ok(cmd) = receiver.0.recv() { + match cmd { + AsyncDeallocatorCommand::Stop => break, + AsyncDeallocatorCommand::Deallocate { ptr, layout } => { + deallocator.deallocate(ptr, layout); + } + AsyncDeallocatorCommand::DeallocateForeign { value, deallocate } => { + unsafe { deallocate(value) }; + } + } + } + receiver + }) + .unwrap() +} + +impl HelgobossAllocator +where + I: AsyncDeallocationIntegration, +{ + /// Initializes the allocator. + /// + /// The deallocator that you pass here will only be used for synchronous deallocation. + /// + /// This is just the first step! In order to offload deallocations, you need to call + /// [`Self::init`]! It needs to be 2 steps + pub const fn new(deallocator: D) -> Self { + Self { + sync_deallocator: deallocator, + async_deallocation_machine: OnceLock::new(), + } + } + + /// This call is necessary to initialize automatic deallocation offloading (asynchronous + /// deallocation). + /// + /// The deallocator that you pass here will only be used for asynchronous deallocation. + /// + /// As soon as the given capacity is reached, deallocation will be done synchronously until + /// the deallocation thread has capacity again. + pub fn init(&self, capacity: usize, integration: I) -> AsyncDeallocatorCommandReceiver { + let (sender, receiver) = mpsc::sync_channel::(capacity); + let machine = AsyncDeallocationMachine { + sender, + integration, + }; + if self.async_deallocation_machine.set(machine).is_err() { + panic!("attempted to initialize async deallocator more than once"); + } + AsyncDeallocatorCommandReceiver(receiver) + } + + /// Executes the given deallocation function on the given value, possibly asynchronously. + /// + /// This makes it possible to defer deallocation even with values that are not managed by Rust, + /// e.g. C values. + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn dealloc_foreign_value( + &self, + deallocate: unsafe extern "C" fn(ptr: *mut c_void), + value: *mut c_void, + ) { + self.dealloc_internal( + || self.check(None), + || unsafe { + deallocate(value); + }, + || AsyncDeallocatorCommand::DeallocateForeign { value, deallocate }, + ) + } + + /// Sends a stop signal to the receiver of asynchronous deallocation commands (usually a + /// dedicated thread). + pub fn stop_async_deallocation(&self) { + if let Some(machine) = self.async_deallocation_machine.get() { + let _ = machine.sender.try_send(AsyncDeallocatorCommand::Stop); + } + } + + fn dealloc_internal( + &self, + check: impl FnOnce(), + dealloc_sync: impl FnOnce(), + create_async_command: impl FnOnce() -> AsyncDeallocatorCommand, + ) { + #[cfg(not(debug_assertions))] + let _ = check; + let Some(deallocation_machine) = self.async_deallocation_machine.get() else { + // We are not initialized yet. Attempt normal synchronous deallocation. + #[cfg(debug_assertions)] + check(); + dealloc_sync(); + return; + }; + if deallocation_machine.integration.offload_deallocation() { + // Deallocation shall be offloaded and we are already initialized. + if let Err(e) = deallocation_machine.sender.try_send(create_async_command()) { + match e { + TrySendError::Full(_) => { + // This is possible if we have very many deallocations in a row or if for + // some reason the async deallocator thread is not running. Then do + // deallocation synchronously. + #[cfg(debug_assertions)] + check(); + dealloc_sync(); + } + TrySendError::Disconnected(_) => { + // Could happen on shutdown + dealloc_sync(); + } + } + } + } else { + // Synchronous deallocation is fine. It's still possible that we are in an + // assert_no_alloc block. In that case, we want to report the violation. + #[cfg(debug_assertions)] + check(); + dealloc_sync(); + } + } + + /// For deallocation of foreign structs (e.g. C structs), the layout is not given. + fn check(&self, layout: Option) { + #[cfg(debug_assertions)] + { + let forbid_count = ALLOC_FORBID_COUNT.with(|f| f.get()); + let permit_count = ALLOC_PERMIT_COUNT.with(|p| p.get()); + if forbid_count > 0 && permit_count == 0 { + // Increase our counter (will be displayed in ReaLearn status line) + UNDESIRED_ALLOCATION_COUNTER.fetch_add(1, Ordering::Relaxed); + // Comment out if you don't want to log the violation + // permit_alloc(|| { + // if let Some(layout) = layout { + // eprintln!( + // "Undesired memory (de)allocation of {} bytes from:\n{:?}", + // layout.size(), + // backtrace::Backtrace::new() + // ); + // } else { + // eprintln!( + // "Undesired memory (de)allocation of a foreign value from:\n{:?}", + // backtrace::Backtrace::new() + // ); + // } + // }); + // Comment out if you don't want to abort + // if let Some(layout) = layout { + // std::alloc::handle_alloc_error(layout); + // } + } + } + let _ = layout; + } +} + +unsafe impl GlobalAlloc + for HelgobossAllocator +{ + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + #[cfg(debug_assertions)] + self.check(Some(layout)); + System.alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.dealloc_internal( + || self.check(Some(layout)), + || self.sync_deallocator.deallocate(ptr, layout), + || AsyncDeallocatorCommand::Deallocate { ptr, layout }, + ); + } +} diff --git a/plugin-reaper-realearn/api/Cargo.toml b/plugin-reaper-realearn/api/Cargo.toml new file mode 100644 index 0000000..856dabf --- /dev/null +++ b/plugin-reaper-realearn/api/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "helgobox-api" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[features] +default = [] + +[dependencies] +# For being able to use the API macro +helgobox-macros.workspace = true +reaper-low.workspace = true +serde.workspace = true +semver.workspace = true +serde_json.workspace = true +playtime-api.workspace = true +derive_more.workspace = true +strum.workspace = true +num_enum.workspace = true +enum-map.workspace = true +enumset = { workspace = true, features = ["serde", "alloc"] } +helgoboss-license-api.workspace = true +serde_with.workspace = true + +[dev-dependencies] +# For testing Lua compatibility +mlua.workspace = true +# For generating Luau type definitions from our Rust APIs +syn = { workspace = true, features = ["full", "extra-traits"] } +# For generating Luau type definitions from our Rust APIs +darling.workspace = true +# For generating Luau type definitions from our Rust APIs +heck.workspace = true +# For formatting generated Luau Type definitions +stylua = { workspace = true, features = ["luau"] } +anyhow.workspace = true + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/api/src/bindings/luau/luau_converter.rs b/plugin-reaper-realearn/api/src/bindings/luau/luau_converter.rs new file mode 100644 index 0000000..03f20a0 --- /dev/null +++ b/plugin-reaper-realearn/api/src/bindings/luau/luau_converter.rs @@ -0,0 +1,1011 @@ +use anyhow::{bail, Context}; +use darling::FromMeta; +use std::fmt::{Display, Formatter, Write}; + +use std::{fmt, iter}; + +use syn::{ + Attribute, Expr, ExprLit, Field, Fields, FieldsNamed, File, GenericArgument, GenericParam, + Generics, Ident, Item, ItemEnum, ItemStruct, Lit, Meta, MetaNameValue, PathArguments, Type, + Variant, +}; + +pub trait Hook { + /// Return `true` if you want to include this type in the language binding. + /// + /// Called for each Rust type with its simple name (not including the path). + fn include_type(&self, simple_ident: &str) -> bool { + let _ = simple_ident; + true + } + + /// Return the Luau module name that corresponds to the given Rust crate name. Return `None` + /// if you want to use the crate's name as Luau module name. + /// + /// Called for each referenced Rust type that's addressed with an explicit module path. It will pass the first + /// segment of that past. Ideally, our logic would resolve the crate name by looking at the use statements, + /// but it's okay, we just need to be a bit disciplined when writing the Rust API code. + fn translate_crate_name(&self, rust_crate_ident: &str) -> Option<&'static str> { + let _ = rust_crate_ident; + None + } +} + +pub struct LuauFile<'a, H> { + context: ConvContext<'a, H>, +} + +struct ConvContext<'a, H> { + file: &'a File, + hook: &'a H, + foreign_files: &'a [File], +} + +impl<'a, H> ConvContext<'a, H> { + fn get_item_by_type(&self, ty: &Type) -> anyhow::Result<&Item> { + let referenced_type_ident = ty.get_ident()?; + self.find_item_by_ident(referenced_type_ident) + .with_context(|| { + let type_ident = LuauIdent(referenced_type_ident, Case::UpperCamelCase); + format!("Couldn't find item for type {type_ident} (or is not a struct or enum).") + }) + } + + fn find_item_by_ident(&self, needle: &Ident) -> Option<&Item> { + iter::once(self.file) + .chain(self.foreign_files.iter()) + .flat_map(|file| file.items.iter()) + .find(|it| match it { + Item::Struct(ItemStruct { ident, .. }) | Item::Enum(ItemEnum { ident, .. }) => { + ident == needle + } + _ => false, + }) + } +} + +impl<'a, H> Clone for ConvContext<'a, H> { + fn clone(&self) -> Self { + Self { + file: self.file, + hook: self.hook, + foreign_files: self.foreign_files, + } + } +} + +impl<'a, H> Copy for ConvContext<'a, H> {} + +struct LuauItem<'a, H> { + value: &'a Item, + context: ConvContext<'a, H>, +} + +impl<'a, H: Hook> LuauItem<'a, H> { + pub fn new(value: &'a Item, context: ConvContext<'a, H>) -> Self { + Self { value, context } + } +} + +struct LuauStruct<'a, H> { + value: &'a ItemStruct, + context: ConvContext<'a, H>, +} + +impl<'a, H> LuauStruct<'a, H> { + pub fn new(value: &'a ItemStruct, context: ConvContext<'a, H>) -> Self { + Self { value, context } + } +} + +struct LuauEnum<'a, H> { + value: &'a ItemEnum, + context: ConvContext<'a, H>, +} + +impl<'a, H> LuauEnum<'a, H> { + pub fn new(value: &'a ItemEnum, context: ConvContext<'a, H>) -> Self { + Self { value, context } + } +} + +struct RichLuauEnum<'a, H> { + item_enum: &'a ItemEnum, + /// `None` means untagged + tag: Option<&'a str>, + context: ConvContext<'a, H>, +} + +struct PrimitiveLuauEnum<'a> { + item_enum: &'a ItemEnum, +} + +impl<'a> PrimitiveLuauEnum<'a> { + pub fn new(item_enum: &'a ItemEnum) -> Self { + Self { item_enum } + } +} + +struct LuauGenerics<'a>(&'a Generics); +struct LuauGenericArguments<'a, H> { + value: &'a PathArguments, + context: ConvContext<'a, H>, +} + +impl<'a, H> LuauGenericArguments<'a, H> { + pub fn new(value: &'a PathArguments, context: ConvContext<'a, H>) -> Self { + Self { value, context } + } +} + +struct LuauStructField<'a, H> { + value: &'a Field, + context: ConvContext<'a, H>, +} + +impl<'a, H> LuauStructField<'a, H> { + pub fn new(value: &'a Field, context: ConvContext<'a, H>) -> Self { + Self { value, context } + } +} + +struct LuauVariant<'a, H> { + variant: &'a Variant, + enum_ident: LuauIdent<'a>, + /// `None` means untagged. + tag: Option<&'a str>, + context: ConvContext<'a, H>, +} + +struct LuauTaggedVariantBuilder<'a, H> { + variant: &'a Variant, + enum_ident: LuauIdent<'a>, + tag: &'a str, + context: ConvContext<'a, H>, +} + +struct LuauTaggedVariantIdent<'a> { + enum_ident: LuauIdent<'a>, + variant: &'a Variant, +} + +fn line_breaked(value: T) -> Separated { + Separated { + separator: "\n", + value, + } +} + +fn separated(separator: &'static str, value: T) -> Separated { + Separated { separator, value } +} + +struct Separated { + separator: &'static str, + value: T, +} + +fn delimited(delimiters: (&'static str, &'static str), value: T) -> Delimited { + Delimited { delimiters, value } +} + +struct Delimited { + delimiters: (&'static str, &'static str), + value: T, +} + +struct LuauType<'a, H> { + value: &'a Type, + context: ConvContext<'a, H>, + force_optional: bool, +} + +struct LuauDoc<'a> { + attributes: &'a [Attribute], +} + +impl<'a> LuauDoc<'a> { + pub fn new(attributes: &'a [Attribute]) -> Self { + Self { attributes } + } +} + +impl<'a> Display for LuauDoc<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for (_i, attr) in attributes_where_ident(self.attributes, "doc").enumerate() { + let doc_line = match &attr.meta { + Meta::NameValue(MetaNameValue { + value: + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }), + .. + }) => s.value(), + _ => panic!("unusual doc comment"), + }; + write!(f, "\n---{doc_line}")?; + } + Ok(()) + } +} + +impl<'a, H> LuauType<'a, H> { + fn new(value: &'a Type, context: ConvContext<'a, H>) -> Self { + Self { + value, + context, + force_optional: false, + } + } + + fn new_detailed(value: &'a Type, context: ConvContext<'a, H>, force_optional: bool) -> Self { + Self { + value, + context, + force_optional, + } + } +} + +#[derive(Copy, Clone)] +struct LuauIdent<'a>(&'a Ident, Case); + +#[derive(Copy, Clone)] +enum Case { + Original, + UpperCamelCase, + SnakeCase, +} + +impl<'a, H> LuauFile<'a, H> { + pub fn new(file: &'a File, hook: &'a H, foreign_files: &'a [File]) -> Self { + Self { + context: ConvContext { + file, + hook, + foreign_files, + }, + } + } +} + +impl<'a, H: Hook> Display for LuauFile<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("local module = {}\n\n")?; + line_breaked(|| { + self.context + .file + .items + .iter() + .filter(|item| { + item_is_included(item, |ident| self.context.hook.include_type(ident)) + }) + .map(|item| LuauItem::new(item, self.context)) + }) + .fmt(f)?; + f.write_str("return module\n")?; + Ok(()) + } +} + +fn item_is_included(item: &Item, include_ident: impl Fn(&str) -> bool) -> bool { + let ident = match item { + Item::Enum(e) => &e.ident, + Item::Struct(s) => &s.ident, + Item::Type(t) => &t.ident, + _ => return true, + }; + include_ident(&ident.to_string()) +} + +impl<'a> Display for LuauIdent<'a> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self.1 { + Case::Original => { + self.0.fmt(f)?; + } + Case::UpperCamelCase => { + let ident = heck::AsUpperCamelCase(self.0.to_string()); + ident.fmt(f)?; + } + Case::SnakeCase => { + let ident = heck::AsSnakeCase(self.0.to_string()); + ident.fmt(f)?; + } + } + Ok(()) + } +} + +impl<'a, H: Hook> Display for LuauItem<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match &self.value { + Item::Enum(e) => LuauEnum::new(e, self.context).fmt(f)?, + Item::Struct(s) => LuauStruct::new(s, self.context).fmt(f)?, + Item::Type(_) => {} + _ => {} + } + Ok(()) + } +} + +fn line_breaked_named_fields<'a>( + fields: &'a FieldsNamed, + context: ConvContext<'a, impl Hook>, +) -> impl Display + 'a { + line_breaked(move || { + fields + .named + .iter() + .map(move |f| LuauStructField::new(f, context)) + }) +} + +impl<'a, H: Hook> Display for LuauStruct<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let ident = LuauIdent(&self.value.ident, Case::Original); + let generics = LuauGenerics(&self.value.generics); + let doc = LuauDoc::new(&self.value.attrs); + match &self.value.fields { + Fields::Named(fields) => { + let fields = line_breaked_named_fields(fields, self.context); + writeln!( + f, + r#" + {doc} + export type {ident}{generics} = {{ + {fields} + }} + --- Creates a {ident} value.{doc} + function module.{ident}(value: {ident}): {ident} + return value + end + "# + )?; + } + Fields::Unnamed(fields) => { + let field = fields + .unnamed + .first() + .expect("empty tuple structs not supported"); + // Type alias + let ty = LuauType::new(&field.ty, self.context); + if fields.unnamed.len() == 1 { + writeln!(f, "export type {ident}{generics} = {ty};")?; + } else { + let fields_equal = fields.unnamed.iter().all(|f| f == field); + if !fields_equal { + panic!("tuple structs where fields have different types are not supported: {ident}"); + } + writeln!(f, "export type {ident}{generics} = {{ {ty} }};")?; + } + } + Fields::Unit => { + panic!("unit structs not supported: {}", ident); + } + } + Ok(()) + } +} + +#[derive(Debug, darling::FromMeta)] +pub struct SerdeArgs { + tag: Option, + #[darling(default)] + untagged: bool, + #[darling(default)] + default: Option, + #[darling(default)] + flatten: bool, +} + +#[derive(Debug)] +struct FlagOrStringAttribute; + +impl FromMeta for FlagOrStringAttribute { + fn from_meta(item: &Meta) -> Result { + match item { + Meta::Path(_) | Meta::NameValue(_) => Ok(Self), + _ => Err(darling::Error::unsupported_format("no default")), + } + } +} + +impl<'a, H: Hook> Display for LuauEnum<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let args = serde_args(&self.value.attrs).next(); + match args { + None => { + // Primitive enum + PrimitiveLuauEnum::new(self.value).fmt(f)?; + } + Some(args) => { + match (args.tag, args.untagged) { + (None, false) => { + panic!("serde attribute containing neither tag nor untagged") + } + (None, true) => { + // Untagged enum + let rich_enum = RichLuauEnum { + item_enum: self.value, + tag: None, + context: self.context, + }; + rich_enum.fmt(f)?; + } + (Some(tag), _) => { + // Tagged enum + let rich_enum = RichLuauEnum { + item_enum: self.value, + tag: Some(&tag), + context: self.context, + }; + rich_enum.fmt(f)?; + } + } + } + } + Ok(()) + } +} + +impl<'a, H: Hook> Display for RichLuauEnum<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let enum_ident = LuauIdent(&self.item_enum.ident, Case::Original); + let generics = LuauGenerics(&self.item_enum.generics); + let has_type_params = generics.has_type_params(); + if has_type_params { + panic!( + "generics not fully supported at the moment, but could be done I guess: {}", + enum_ident + ); + } + let variants = line_breaked(|| { + self.item_enum.variants.iter().map(|variant| LuauVariant { + variant, + enum_ident, + tag: self.tag, + context: self.context, + }) + }); + let variant_disjunction = separated("|", || { + self.item_enum + .variants + .iter() + .map(|variant| LuauTaggedVariantIdent { + enum_ident, + variant, + }) + }); + let doc = LuauDoc::new(&self.item_enum.attrs); + writeln!( + f, + r#" + {variants}{doc} + export type {enum_ident} = {variant_disjunction} + "# + )?; + // For tagged enums, we also generate a convenience type that contains all the tags + if let Some(tag) = self.tag { + let upper_tag = heck::AsUpperCamelCase(tag); + let variant_ident_disjunction = variant_ident_disjunction(self.item_enum); + writeln!( + f, + r#" + --- A type that represents all possible kinds of {enum_ident}. + export type {enum_ident}{upper_tag} = {variant_ident_disjunction} + "# + )?; + } + // Also generate variant builders + if let Some(tag) = self.tag { + let variant_builders = line_breaked(|| { + self.item_enum + .variants + .iter() + .map(|variant| LuauTaggedVariantBuilder { + variant, + enum_ident, + tag, + context: self.context, + }) + }); + writeln!( + f, + r#" + --- Helper table to create {enum_ident} values of different kinds.{doc} + module.{enum_ident} = {{}} + + {variant_builders} + "# + )?; + } else { + writeln!( + f, + r#" + --- Creates a {enum_ident} value. + function module.{enum_ident}(value: {enum_ident}): {enum_ident} + return value + end + "# + )?; + } + Ok(()) + } +} + +fn variant_ident_disjunction(item_enum: &ItemEnum) -> impl Display + '_ { + separated("|", || { + item_enum.variants.iter().map(|variant| { + delimited( + DOUBLE_QUOTES, + LuauIdent(&variant.ident, Case::UpperCamelCase), + ) + }) + }) +} + +impl<'a> Display for PrimitiveLuauEnum<'a> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let ident = LuauIdent(&self.item_enum.ident, Case::Original); + let variant_ident_disjunction = variant_ident_disjunction(self.item_enum); + let doc = LuauDoc::new(&self.item_enum.attrs); + writeln!( + f, + r#" + {doc} + export type {ident} = {variant_ident_disjunction} + "# + )?; + Ok(()) + } +} + +impl<'a> LuauGenerics<'a> { + pub fn has_type_params(&self) -> bool { + !self.0.params.is_empty() + } +} + +impl<'a> Display for LuauGenerics<'a> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if !self.has_type_params() { + return Ok(()); + } + f.write_char('<')?; + for (i, param) in self.0.params.iter().enumerate() { + if i > 0 { + f.write_str(" ,")?; + } + let GenericParam::Type(type_param) = param else { + panic!("only type params supported: {:?}", param); + }; + LuauIdent(&type_param.ident, Case::Original).fmt(f)?; + } + f.write_char('>')?; + Ok(()) + } +} + +impl<'a, H: Hook> Display for LuauGenericArguments<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let PathArguments::AngleBracketed(args) = self.value else { + return Ok(()); + }; + f.write_char('<')?; + for (i, arg) in args.args.iter().enumerate() { + if i > 0 { + f.write_str(" ,")?; + } + let GenericArgument::Type(ty) = arg else { + panic!("only type arguments supported: {:?}", arg); + }; + LuauType::new(ty, self.context).fmt(f)?; + } + f.write_char('>')?; + Ok(()) + } +} + +impl Display for Separated +where + F: Fn() -> I, + I: Iterator, + D: Display, +{ + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let sep = self.separator; + for (i, item) in (self.value)().enumerate() { + if i == 0 { + item.fmt(f)?; + } else { + write!(f, "{sep}{}", item)?; + } + } + Ok(()) + } +} + +impl Display for Delimited { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "{}{}{}", + self.delimiters.0, self.value, self.delimiters.1 + ) + } +} + +trait GetIdent { + fn find_ident(&self) -> Option<&Ident> { + self.get_ident().ok() + } + fn get_ident(&self) -> anyhow::Result<&Ident>; +} + +impl GetIdent for Type { + fn get_ident(&self) -> anyhow::Result<&Ident> { + match self { + Type::Path(p) => Ok(&p.path.segments.last().unwrap().ident), + _ => bail!("Type doesn't contain path-like ident"), + } + } +} + +impl GetIdent for Item { + fn get_ident(&self) -> anyhow::Result<&Ident> { + let ident = match self { + Item::Const(v) => &v.ident, + Item::Enum(v) => &v.ident, + Item::ExternCrate(v) => &v.ident, + Item::Mod(v) => &v.ident, + Item::Static(v) => &v.ident, + Item::Struct(v) => &v.ident, + Item::Trait(v) => &v.ident, + Item::TraitAlias(v) => &v.ident, + Item::Type(v) => &v.ident, + Item::Union(v) => &v.ident, + _ => bail!("item has no ident"), + }; + Ok(ident) + } +} + +impl<'a, H: Hook> Display for LuauStructField<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if has_serde_flatten_arg(&self.value.attrs) { + // #[serde(flatten)] + // We need to expand the type! + let referenced_item = self + .context + .get_item_by_type(&self.value.ty) + .unwrap_or_else(|e| panic!("flattened struct field type not found: {e:#}")); + match referenced_item { + Item::Struct(ItemStruct { + fields: Fields::Named(fields_named), + .. + }) => { + line_breaked_named_fields(fields_named, self.context).fmt(f)?; + } + _ => panic!("flattened struct field type is not a struct"), + } + } else { + // Normal, no flattening + let ty = LuauType::new_detailed( + &self.value.ty, + self.context, + has_serde_default_arg(&self.value.attrs), + ); + let ident = self + .value + .ident + .as_ref() + .expect("no tuple struct field expected"); + let json_ident = LuauIdent(ident, Case::SnakeCase); + write!(f, "{json_ident}: {ty},")?; + } + Ok(()) + } +} + +impl<'a> Display for LuauTaggedVariantIdent<'a> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let enum_ident = self.enum_ident; + let variant_ident = LuauIdent(&self.variant.ident, Case::UpperCamelCase); + write!(f, "{enum_ident}_{variant_ident}") + } +} + +impl<'a, H: Hook> Display for LuauVariant<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let enum_ident = self.enum_ident; + let variant_ident = LuauTaggedVariantIdent { + enum_ident, + variant: self.variant, + }; + let doc = LuauDoc::new(&self.variant.attrs); + write!( + f, + r#" + {doc} + export type {variant_ident} = + "# + )?; + let upper_ident = LuauIdent(&self.variant.ident, Case::UpperCamelCase); + match &self.variant.fields { + Fields::Named(fields) => { + // Struct-like enum variant + f.write_str("{ ")?; + if let Some(tag) = self.tag { + // Tagged enum + write!(f, r#"{tag}: "{upper_ident}", "#)?; + } + line_breaked_named_fields(fields, self.context).fmt(f)?; + f.write_str("}")?; + } + Fields::Unnamed(fields) => { + assert_eq!( + fields.unnamed.len(), + 1, + "enum tuple variants with more than one value not supported: {}", + self.enum_ident + ); + // Tuple enum variant with only one field. Very common. + let field = fields.unnamed.first().unwrap(); + if let Some(tag) = self.tag { + // Tagged + + // Ideally, we would just build a type intersection with the type of the single field. + // Example: ClipSlotDescriptor_ByIndex = {address: "ByIndex"} & playtime.SlotAddress + // write!( + // f, + // r#"{{ {tag}: "{upper_ident}" }} & {}"#, + // LuauType::new(&field.ty, self.context) + // )?; + // However, Luau somehow doesn't understand this, so we have to inline the referenced type. + // I reported this bug in https://github.com/luau-lang/luau/issues/961. + + // As an alternative, we resolve and inline the referenced types + let referenced_item = self.context.get_item_by_type(&field.ty).unwrap_or_else(|e| { + panic!("Couldn't find referred type of enum variant {enum_ident}::{upper_ident}: {e:#}") + }); + match referenced_item { + Item::Struct(ItemStruct { + fields: Fields::Named(fields_named), + .. + }) => { + // The referenced item is a struct. Inline all struct fields. + f.write_str("{ ")?; + write!(f, r#"{tag}: "{upper_ident}", "#)?; + line_breaked_named_fields(fields_named, self.context).fmt(f)?; + f.write_str("}")?; + } + Item::Enum(ItemEnum { variants, .. }) => { + // The referenced item is an enum. It must be an untagged one! + // We don't check this here. Example: BookmarkRef + // Create a tagged union of all variants. + for (i, variant) in variants.iter().enumerate() { + let Fields::Named(fields_named) = &variant.fields else { + let luau_ident = LuauIdent( + referenced_item.get_ident().unwrap(), + Case::UpperCamelCase, + ); + panic!("Enum {luau_ident} referenced by {enum_ident}::{upper_ident} doesn't contain named fields. This is not supported at the moment."); + }; + if i > 0 { + f.write_str(" | ")?; + } + f.write_str("{ ")?; + write!(f, r#"{tag}: "{upper_ident}", "#)?; + line_breaked_named_fields(fields_named, self.context).fmt(f)?; + f.write_str("}")?; + } + } + _ => {} + } + } else { + // Untagged + write!(f, "{}", LuauType::new(&field.ty, self.context))?; + } + } + Fields::Unit => { + // Primitive enum variant + if let Some(tag) = self.tag { + // Tagged enum + write!(f, r#"{{ {tag}: "{upper_ident}" }}"#)?; + } else { + panic!("untagged enums with primitive variants invalid"); + } + } + }; + Ok(()) + } +} + +impl<'a, H: Hook> Display for LuauTaggedVariantBuilder<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let enum_ident = self.enum_ident; + let simple_variant_ident = LuauIdent(&self.variant.ident, Case::Original); + let tagged_variant_ident = LuauTaggedVariantIdent { + enum_ident, + variant: self.variant, + }; + let tag = self.tag; + let doc = LuauDoc::new(&self.variant.attrs); + match &self.variant.fields { + Fields::Named(fields) => { + // Struct-like enum variant + let named_fields = line_breaked_named_fields(fields, self.context); + write!( + f, + r#" + --- Creates a {enum_ident} of kind {simple_variant_ident}.{doc} + function module.{enum_ident}.{simple_variant_ident}(value: {{{named_fields}}}): {tagged_variant_ident} + local t: any = table.clone(value) + t.{tag} = "{simple_variant_ident}" + return t + end + "# + )?; + } + Fields::Unnamed(fields) => { + assert_eq!( + fields.unnamed.len(), + 1, + "enum tuple variants with more than one value not supported: {}", + self.enum_ident + ); + // Tuple enum variant with only one field. Very common. + let field = fields.unnamed.first().unwrap(); + let ref_type = LuauType::new(&field.ty, self.context); + write!( + f, + r#" + --- Creates a {enum_ident} of kind {simple_variant_ident}.{doc} + function module.{enum_ident}.{simple_variant_ident}(value: {ref_type}): {tagged_variant_ident} + local t: any = table.clone(value) + t.{tag} = "{simple_variant_ident}" + return t + end + "# + )?; + } + Fields::Unit => { + // Primitive enum variant + write!( + f, + r#" + --- Creates a {enum_ident} of kind {simple_variant_ident}.{doc} + function module.{enum_ident}.{simple_variant_ident}(): {tagged_variant_ident} + return {{ + {tag} = "{simple_variant_ident}" + }} + end + "# + )?; + } + }; + Ok(()) + } +} + +impl<'a, H: Hook> Display for LuauType<'a, H> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self.value { + Type::Path(p) => { + let el = p.path.segments.last().unwrap(); + let ident = el.ident.to_string(); + match ident.as_str() { + "Option" => { + let ty = get_type_arg(&el.arguments, 0); + write!(f, "{}?", LuauType::new(ty, self.context))?; + } + "Vec" | "EnumSet" | "HashSet" => { + let ty = get_type_arg(&el.arguments, 0); + write!(f, "{{{}}}", LuauType::new(ty, self.context))?; + } + "HashMap" | "Map" => { + let ty1 = get_type_arg(&el.arguments, 0); + let ty2 = get_type_arg(&el.arguments, 1); + write!( + f, + "{{[{}]: {}}}", + LuauType::new(ty1, self.context), + LuauType::new(ty2, self.context) + )?; + } + _ => { + let final_type_name = translate_type_name(&ident).unwrap_or(&ident); + let generic_arguments = + LuauGenericArguments::new(&el.arguments, self.context); + if p.path.segments.len() > 1 { + if let Some(first_seg) = p.path.segments.first() { + if let Some(module) = self + .context + .hook + .translate_crate_name(&first_seg.ident.to_string()) + { + write!(f, "{}.", module)?; + } + } + } + write!(f, "{final_type_name}{generic_arguments}")?; + } + } + if self.force_optional && ident.as_str() != "Option" { + f.write_char('?')?; + } + } + _ => panic!("unsupported type {:?}", self.value), + } + Ok(()) + } +} + +fn translate_type_name(ident: &str) -> Option<&str> { + let translated = match ident { + "usize" | "u8" | "u16" | "u32" | "u64" | "isize" | "i8" | "i16" | "i32" | "i64" + | "NonZeroU32" => "number", + "f64" | "f32" | "Db" | "Bpm" | "DurationInSeconds" | "DurationInBeats" => "number", + "PathBuf" | "Utf8PathBuf" | "NaiveDateTime" | "Version" | "String" => "string", + "bool" => "boolean", + // serde_json::Value + "Value" => "any", + _ => return None, + }; + Some(translated) +} + +fn get_type_arg(args: &PathArguments, n: usize) -> &Type { + let PathArguments::AngleBracketed(args) = args else { + panic!("angle-bracketed type argument expected"); + }; + let arg = args + .args + .iter() + .skip(n) + .next() + .expect("at least one argument expected"); + let GenericArgument::Type(ty) = arg else { + panic!("type argument expected") + }; + ty +} + +const DOUBLE_QUOTES: (&str, &str) = (DOUBLE_QUOTE, DOUBLE_QUOTE); +const DOUBLE_QUOTE: &str = "\""; + +fn has_serde_flatten_arg(attributes: &[Attribute]) -> bool { + serde_args(attributes).any(|args| args.flatten) +} + +fn has_serde_default_arg(attributes: &[Attribute]) -> bool { + serde_args(attributes).any(|args| args.default.is_some()) +} + +fn serde_args(attributes: &[Attribute]) -> impl Iterator + '_ { + serde_attributes(attributes).filter_map(|a| SerdeArgs::from_meta(&a.meta).ok()) +} + +fn serde_attributes(attributes: &[Attribute]) -> impl Iterator { + attributes_where_ident(attributes, "serde") +} + +fn attributes_where_ident<'a>( + attributes: &'a [Attribute], + ident: &'a str, +) -> impl Iterator + 'a { + attributes + .iter() + .filter(move |a| a.find_ident().is_some_and(|id| id == ident)) +} + +impl GetIdent for Attribute { + fn get_ident(&self) -> anyhow::Result<&Ident> { + self.path() + .segments + .last() + .map(|seg| &seg.ident) + .context("attribute has no ident") + } +} diff --git a/plugin-reaper-realearn/api/src/bindings/luau/mod.rs b/plugin-reaper-realearn/api/src/bindings/luau/mod.rs new file mode 100644 index 0000000..729be9e --- /dev/null +++ b/plugin-reaper-realearn/api/src/bindings/luau/mod.rs @@ -0,0 +1,109 @@ +use crate::bindings::luau::luau_converter::Hook; +use std::fs; +use std::path::PathBuf; +use stylua_lib::OutputVerification; + +mod luau_converter; + +/// The final code formatting causes error `has overflowed its stack` by default. You need to set +/// `RUST_MIN_STACK` environment variable (e.g. `RUST_MIN_STACK=104857600`) or execute the test in +/// release mode for this to work. +#[test] +pub fn export_luau() { + struct RealearnApiExportHook; + impl Hook for RealearnApiExportHook { + fn translate_crate_name(&self, rust_crate_ident: &str) -> Option<&'static str> { + match rust_crate_ident { + "playtime_api" => Some("playtime"), + _ => None, + } + } + } + export_luau_internal( + "realearn", + "Contains types and helper functions for building ReaLearn presets", + [ + "src/persistence/compartment.rs", + "src/persistence/glue.rs", + "src/persistence/group.rs", + "src/persistence/mapping.rs", + "src/persistence/parameter.rs", + "src/persistence/source.rs", + "src/persistence/target.rs", + ], + &RealearnApiExportHook, + ["playtime"], + ["../playtime-api/src/persistence/mod.rs"], + ); + struct PlaytimeApiExportHook; + impl Hook for PlaytimeApiExportHook { + fn include_type(&self, simple_ident: &str) -> bool { + !matches!( + simple_ident, + "FlexibleMatrix" + | "PlaytimeApiError" + | "PlaytimePersistenceRoot" + | "RawEvenQuantization" + ) + } + } + export_luau_internal( + "playtime", + "Contains types and helper functions for building Playtime presets", + ["../playtime-api/src/persistence/mod.rs"], + &PlaytimeApiExportHook, + [], + [], + ); +} + +fn export_luau_internal<'a>( + name: &str, + description: &str, + src_files: impl IntoIterator, + hook: &impl Hook, + requires: impl AsRef<[&'a str]>, + foreign_files: impl IntoIterator, +) { + let rust_codes: Vec<_> = src_files + .into_iter() + .map(|src_file| { + let code = fs::read_to_string(src_file).unwrap(); + let filtered_code: Vec<_> = code + .lines() + .filter(|line| !line.starts_with("//!")) + .collect(); + filtered_code.join("\n") + }) + .collect(); + let merged_rust_code = rust_codes.join("\n\n"); + let rust_file = parse_rust_code(&merged_rust_code); + let foreign_rust_files: Vec<_> = foreign_files + .into_iter() + .map(|path| { + let code = fs::read_to_string(path).unwrap(); + parse_rust_code(&code) + }) + .collect(); + let luau_file = luau_converter::LuauFile::new(&rust_file, hook, &foreign_rust_files); + use std::fmt::Write; + let mut luau_code = "--!strict\n\n--- Attention: This file is generated from Rust code! Don't modify it directly!\n\n".to_string(); + for req in requires.as_ref() { + writeln!(&mut luau_code, "local {req} = require(\"{req}\")").unwrap(); + } + writeln!(&mut luau_code, "\n--- {description}").unwrap(); + write!(&mut luau_code, "{luau_file}").unwrap(); + let luau_code = stylua_lib::format_code( + &luau_code, + Default::default(), + None, + OutputVerification::Full, + ) + .unwrap(); + let dest_file = PathBuf::from(format!("../resources/api/luau/{name}.luau")); + fs::write(&dest_file, luau_code).unwrap(); +} + +fn parse_rust_code(code: &str) -> syn::File { + syn::parse_file(code).expect("unable to parse Rust file") +} diff --git a/plugin-reaper-realearn/api/src/bindings/mod.rs b/plugin-reaper-realearn/api/src/bindings/mod.rs new file mode 100644 index 0000000..7e1684f --- /dev/null +++ b/plugin-reaper-realearn/api/src/bindings/mod.rs @@ -0,0 +1 @@ +mod luau; diff --git a/plugin-reaper-realearn/api/src/lib.rs b/plugin-reaper-realearn/api/src/lib.rs new file mode 100644 index 0000000..61d2874 --- /dev/null +++ b/plugin-reaper-realearn/api/src/lib.rs @@ -0,0 +1,9 @@ +pub mod persistence; + +pub mod runtime; + +/// Bindings are generated as result of unit tests. +#[cfg(test)] +mod bindings; + +mod util; diff --git a/plugin-reaper-realearn/api/src/persistence/compartment.rs b/plugin-reaper-realearn/api/src/persistence/compartment.rs new file mode 100644 index 0000000..0c7bc30 --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/compartment.rs @@ -0,0 +1,41 @@ +use crate::persistence::*; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; + +/// Complete content of a ReaLearn compartment, including mappings, groups, parameters etc. +#[derive(Default, Serialize, Deserialize)] +pub struct Compartment { + /// Settings of the default group in this compartment. + /// + /// Group fields `id` and `name` will be ignored for the default group. + #[serde(skip_serializing_if = "Option::is_none")] + pub default_group: Option, + /// All parameters in this compartment + #[serde(skip_serializing_if = "Option::is_none")] + pub parameters: Option>, + /// All mapping groups in this compartment. + #[serde(skip_serializing_if = "Option::is_none")] + pub groups: Option>, + /// All mappings in this compartment. + #[serde(skip_serializing_if = "Option::is_none")] + pub mappings: Option>, + /// Lua code that will be compiled only once and can then be reused in various Lua scripts within mappings. + /// + /// This code should return a value. This value will then be made available to the scripts. How exactly, depends + /// on the particular kind of script. In most cases, you want to return a table that contains functions, variables + /// and other stuff that you want to make available in your scripts. + #[serde(skip_serializing_if = "Option::is_none")] + pub common_lua: Option, + /// Arbitrarily formed data in this compartment. + /// + /// The first level is a key-value map where a key represents a sort of namespace. E.g. data that's relevant + /// for the application ReaLearn Companion has key "companion" and data relevant for the application Playtime has + /// the key "playtime". Everything nested below is application-specific. + #[serde(skip_serializing_if = "Option::is_none")] + pub custom_data: Option>, + /// Can contain text notes, e.g. a helpful description of this compartment, instructions etc. + #[serde(skip_serializing_if = "Option::is_none")] + pub notes: Option, + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub unknown_props: Option>, +} diff --git a/plugin-reaper-realearn/api/src/persistence/controller.rs b/plugin-reaper-realearn/api/src/persistence/controller.rs new file mode 100644 index 0000000..250656e --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/controller.rs @@ -0,0 +1,154 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct ControllerConfig { + /// All configured controllers. + #[serde(default)] + pub controllers: Vec, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Controller { + /// ID of the controller. + /// + /// Should be unique on a particular machine and ideally globally unique (good for potential + /// merging scenarios). + pub id: String, + /// Descriptive name of the controller. + /// + /// If one uses multiple controllers of the same kind, this should make clear which + /// particular controller instance we are talking about. + pub name: String, + /// If not enabled, no auto units will be created for that controller. + #[serde(default)] + pub enabled: bool, + /// Controller color. + /// + /// Used e.g. for the control unit rectangle. + #[serde(skip_serializing_if = "Option::is_none")] + pub palette_color: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub connection: Option, + /// Default controller preset to load whenever an auto unit with this controller is created. + /// + /// ReaLearn has mechanisms to automatically identify and load a suitable controller preset + /// depending on which main preset is loaded. If it has to choose between multiple + /// candidates and no default controller preset is set, it will prefer a factory controller + /// preset. If a default controller preset is set and it satisfies the needs of the main preset, + /// it will use this one instead. It will also use the default controller preset if it can't + /// automatically identify the correct one. + #[serde(skip_serializing_if = "Option::is_none")] + pub default_controller_preset: Option, + /// Default main preset to load whenever an auto unit with this controller is created. + // TODO-high-playtime-after-release The plan is to introduce an advanced mode where you don't just set a main preset but can define + // a decision table per controller. It's a list of rules. A rule is made from conditions + // (fixed number, typed, every condition optional, AND) and effects (fixed number, typed, optional). + // The default_main_preset would act as fallback, as last line in the list of rules, which doesn't + // define any additional condition. + // Possible conditions: + // - Playtime matrix (if at least one instance is active that has a Playtime matrix) + // - Active pot unit? + // Possible effects: + // - Main preset (optional) + // - Use auto-load in unit + // "Use auto-load in unit" uses the already existing global FX-to-preset links to do auto-load within + // the unit. FX-to-preset links already is very similar to a decision table. It's going to be a + // second global decision table, but a subordinate one, which acts on a single unit (not by adding/removing + // units). The main preset defined in the controller rule should act as fallback if none of the + // FX-to-preset links was effective. This should be implemented by the existing auto-load. Maybe by + // memorizing the preset that was active when "Auto-load depending on instance FX" was active. + // FX-to-preset links are not 100% a decision table already. Because their order doesn't matter. + // But we can turn it into one by sorting the list according to our current automatic ranking + // (a migration step). Making it a decision table would have the nice effect that the user has + // much influence and it's immediately clear why something happens, no implicit ranking. The list + // of conditions can be easily extended. E.g. we could not just react on what unit FX is + // active but also unit track and so on. In any case, we should add a "Controller" condition, so + // that one e.g. can load a different main preset depending on which FX is focused AND which controller + // is connected. + #[serde(skip_serializing_if = "Option::is_none")] + pub default_main_preset: Option, +} + +/// The way a controller is connected to ReaLearn. +/// +/// Protocol-specific. +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ControllerConnection { + Midi(MidiControllerConnection), + Osc(OscControllerConnection), +} + +/// A connection via MIDI. +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MidiControllerConnection { + /// The expected response to a MIDI device inquiry. + /// + /// Example: "F0 7E 00 06 02 00 20 6B 02 00 04 02 0E 02 01 01 F7" + /// + /// Can be used by ReaLearn to verify whether the device connected to a port is the correct one. + #[serde(skip_serializing_if = "Option::is_none")] + pub identity_response: Option, + /// The MIDI input port to which this controller is usually connected on this machine. + #[serde(skip_serializing_if = "Option::is_none")] + pub input_port: Option, + /// The MIDI output port to which this controller is usually connected on this machine. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_port: Option, +} + +/// A connection via OSC. +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct OscControllerConnection { + #[serde(skip_serializing_if = "Option::is_none")] + pub osc_device_id: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MidiInputPort(u32); + +impl MidiInputPort { + pub fn new(raw: u32) -> Self { + Self(raw) + } + + pub fn get(&self) -> u32 { + self.0 + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MidiOutputPort(u32); + +impl MidiOutputPort { + pub fn new(raw: u32) -> Self { + Self(raw) + } + + pub fn get(&self) -> u32 { + self.0 + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct OscDeviceId(String); + +impl OscDeviceId { + pub fn get(&self) -> &str { + &self.0 + } +} + +/// ID of a controller or main preset (which one depends on the context). +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct CompartmentPresetId(String); + +impl CompartmentPresetId { + pub fn new(raw: String) -> Self { + Self(raw) + } + + pub fn get(&self) -> &str { + &self.0 + } +} diff --git a/plugin-reaper-realearn/api/src/persistence/glue.rs b/plugin-reaper-realearn/api/src/persistence/glue.rs new file mode 100644 index 0000000..d145b9e --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/glue.rs @@ -0,0 +1,252 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(PartialEq, Default, Serialize, Deserialize)] +pub struct Glue { + //region Relevant for control and feedback + #[serde(skip_serializing_if = "Option::is_none")] + pub absolute_mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub source_interval: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub target_interval: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub reverse: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub out_of_range_behavior: Option, + //endregion + + //region Relevant for control only (might change in future) + #[serde(skip_serializing_if = "Option::is_none")] + pub target_value_sequence: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub round_target_value: Option, + //endregion + + //region Relevant for control only (guaranteed) + #[serde(skip_serializing_if = "Option::is_none")] + pub wrap: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub jump_interval: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub takeover_mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub control_transformation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub step_size_interval: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub step_factor_interval: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub button_filter: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub encoder_filter: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub relative_mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub fire_mode: Option, + //endregion + + //region Relevant for feedback only (guaranteed) + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_value_table: Option, + //endregion +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum FeedbackValueTable { + FromTextToDiscrete(DiscreteFeedbackValueTableContent), + FromTextToContinuous(ContinuousFeedbackValueTableContent), +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct DiscreteFeedbackValueTableContent { + pub value: HashMap, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct ContinuousFeedbackValueTableContent { + pub value: HashMap, +} + +#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub enum AbsoluteMode { + #[default] + Normal, + IncrementalButton, + ToggleButton, + MakeRelative, + PerformanceControl, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum RelativeMode { + Normal, + MakeAbsolute, +} + +impl Default for RelativeMode { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum FireMode { + Normal(NormalFireMode), + AfterTimeout(AfterTimeoutFireMode), + AfterTimeoutKeepFiring(AfterTimeoutKeepFiringFireMode), + OnSinglePress(OnSinglePressFireMode), + OnDoublePress, +} + +impl Default for FireMode { + fn default() -> Self { + Self::Normal(Default::default()) + } +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct NormalFireMode { + #[serde(skip_serializing_if = "Option::is_none")] + pub press_duration_interval: Option>, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct AfterTimeoutFireMode { + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct AfterTimeoutKeepFiringFireMode { + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + pub rate: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct OnSinglePressFireMode { + #[serde(skip_serializing_if = "Option::is_none")] + pub max_duration: Option, +} + +#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum VirtualColor { + Rgb(RgbColor), + Prop(PropColor), +} + +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct RgbColor(pub u8, pub u8, pub u8); + +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct PropColor { + pub prop: String, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum OutOfRangeBehavior { + MinOrMax, + Min, + Ignore, +} + +impl Default for OutOfRangeBehavior { + fn default() -> Self { + Self::MinOrMax + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TakeoverMode { + Off, + PickUpTolerant, + PickUp, + LongTimeNoSee, + Parallel, + CatchUp, +} + +impl Default for TakeoverMode { + fn default() -> Self { + Self::Off + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum ButtonFilter { + PressOnly, + ReleaseOnly, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum EncoderFilter { + IncrementOnly, + DecrementOnly, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum Interaction { + SameControl, + SameTargetValue, + InverseControl, + InverseTargetValue, + InverseTargetValueOnOnly, + InverseTargetValueOffOnly, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct FeedbackCommons { + #[serde(skip_serializing_if = "Option::is_none")] + pub color: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub background_color: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum Feedback { + Numeric(NumericFeedback), + Text(TextFeedback), + Dynamic(DynamicFeedback), +} + +impl Default for Feedback { + fn default() -> Self { + Self::Numeric(NumericFeedback::default()) + } +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct NumericFeedback { + #[serde(flatten)] + pub commons: FeedbackCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub transformation: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TextFeedback { + #[serde(flatten)] + pub commons: FeedbackCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub text_expression: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct DynamicFeedback { + #[serde(flatten)] + pub commons: FeedbackCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub script: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct Interval(pub T, pub T); diff --git a/plugin-reaper-realearn/api/src/persistence/group.rs b/plugin-reaper-realearn/api/src/persistence/group.rs new file mode 100644 index 0000000..0bf46ab --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/group.rs @@ -0,0 +1,22 @@ +use crate::persistence::*; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct Group { + /// An optional ID that you can assign to this group in order to refer + /// to it from somewhere else. + /// + /// This ID should be unique within all groups in the same compartment. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub control_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub activation_condition: Option, +} diff --git a/plugin-reaper-realearn/api/src/persistence/instance.rs b/plugin-reaper-realearn/api/src/persistence/instance.rs new file mode 100644 index 0000000..6b64278 --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/instance.rs @@ -0,0 +1,51 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct InstanceSettings { + pub control: InstanceControlSettings, +} + +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct InstanceControlSettings { + /// Whether auto units will be created for all controllers that have a main preset set. + pub global_control_enabled: bool, + // Local overrides of controller settings. + // + // If global control is enabled, each override will alter the behavior of the corresponding + // controller. + // + // If global control is disabled, each override which has a main preset set will enable + // that specific controller. This way you can selectively enable controllers, either with + // the global default preset or with your own one. + // + // TODO-high-playtime-after-release Controller overrides are not yet implemented because about doubts. + // What if the user deletes the controller? Then all project/instance that have + // an override of that controller will reference a now gone controller. Consequently, the + // overrides will not work anymore. Ideas: + // 1. Memorize the original controller data as part of the override and update it whenever + // the global controller changes. Then we can use that data if the controller is gone. + // => the project will still work but it will be disconnected. + // 2. Don't actually use the global controller anymore once there's an override ... that's like + // disconnecting immediately. + // 3. GOOD SOLUTION FOR NOW Don't provide the possibility for overrides. Force user to create + // a ReaLearn setup that is completely self-containing (the other extreme instead of something + // in-between), including the preset content. + // 4. Is the controller role idea better after all? I don't think so. Yes, it allows a bit + // more instance-specific tuning of global control without depending on particular + // controllers. However, the kind of tuning that it allows is far from exhaustive. Also, + // it's opinionated (clip/daw roles) and has other issues (being harder to grasp and + // awkward when it comes to all-in-one controllers that do both clip/DAW control). + // 5. INTERESTING If all we need is the possibility to disable e.g. global DAW control for a + // specific instance, we could simply let the main preset declare which usage role + // it implements (e.g. the "DAW control" role) and allow the instance to switch on/off + // roles - which will cause the main preset to be loaded or not. + // pub controller_overrides: Vec, +} + +// #[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +// pub struct ControllerOverride { +// /// ID of the controller which should be overridden. +// pub controller_id: String, +// /// If this is `None`, the controller default main preset will be used. +// pub main_preset: Option, +// } diff --git a/plugin-reaper-realearn/api/src/persistence/mapping.rs b/plugin-reaper-realearn/api/src/persistence/mapping.rs new file mode 100644 index 0000000..4919689 --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/mapping.rs @@ -0,0 +1,190 @@ +use super::*; +use derive_more::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Default, Serialize, Deserialize)] +pub struct Mapping { + /// An optional ID that you can assign to this mapping in order to refer + /// to it from somewhere else. + /// + /// This ID should be unique within all mappings in the compartment. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub group: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub visible_in_projection: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub control_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub activation_condition: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub on_activate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub on_deactivate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub glue: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub success_audio_feedback: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unprocessed: Option>, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct LifecycleHook { + #[serde(skip_serializing_if = "Option::is_none")] + pub send_midi_feedback: Option>, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum SendMidiFeedbackAction { + Raw { message: RawMidiMessage }, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum RawMidiMessage { + HexString(String), + ByteArray(Vec), +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum SuccessAudioFeedback { + Simple, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ActivationCondition { + Modifier(ModifierActivationCondition), + Bank(BankActivationCondition), + Eel(EelActivationCondition), + Expression(ExpressionActivationCondition), + TargetValue(TargetValueActivationCondition), +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct ModifierActivationCondition { + pub modifiers: Option>, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct ModifierState { + pub parameter: ParamRef, + pub on: bool, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct BankActivationCondition { + pub parameter: ParamRef, + pub bank_index: u32, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct EelActivationCondition { + pub condition: String, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct ExpressionActivationCondition { + pub condition: String, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct TargetValueActivationCondition { + #[serde(skip_serializing_if = "Option::is_none")] + pub mapping: Option, + pub condition: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ParamRef { + Index(u32), + Key(String), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum VirtualControlElementId { + Indexed(u32), + Named(String), +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Debug, + Default, + Serialize, + Deserialize, + Display, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, +)] +#[repr(usize)] +pub enum VirtualControlElementCharacter { + /// A control element that can represent more than 2 states. + #[default] + #[serde(alias = "multi")] + Multi, + /// A control element that can represent at a maximum 2 states. + #[serde(alias = "button")] + Button, +} + +#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize)] +pub struct OscArgument { + #[serde(skip_serializing_if = "Option::is_none")] + pub index: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "kind")] + pub arg_kind: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub value_range: Option>, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum OscArgKind { + Float, + Double, + Bool, + Nil, + Inf, + Int, + String, + Blob, + Time, + Long, + Char, + Color, + Midi, + Array, +} + +impl Default for OscArgKind { + fn default() -> Self { + Self::Float + } +} diff --git a/plugin-reaper-realearn/api/src/persistence/mod.rs b/plugin-reaper-realearn/api/src/persistence/mod.rs new file mode 100644 index 0000000..986123f --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/mod.rs @@ -0,0 +1,118 @@ +mod compartment; +mod controller; +mod glue; +mod group; +mod instance; +mod mapping; +mod parameter; +mod preset; +mod root; +mod session; +mod source; +mod target; + +pub use compartment::*; +pub use controller::*; +pub use glue::*; +pub use group::*; +pub use instance::*; +pub use mapping::*; +pub use parameter::*; +pub use preset::*; +pub use root::*; +pub use session::*; +pub use source::*; +pub use target::*; + +use semver::Version; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Envelope { + #[serde(default)] + pub version: Option, + pub value: T, +} + +impl Envelope { + pub fn new(version: Option, value: T) -> Self { + Self { version, value } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ApiObject { + /// A Playtime matrix. + ClipMatrix(Envelope>>), + /// Main compartment. + MainCompartment(Envelope>), + /// Controller compartment. + ControllerCompartment(Envelope>), + /// A flat list of mappings. + Mappings(Envelope>), + /// A single mapping. + Mapping(Envelope>), +} + +impl ApiObject { + pub fn into_mappings(self) -> Option>> { + match self { + ApiObject::Mappings(Envelope { + value: mappings, + version, + }) => Some(Envelope::new(version, mappings)), + ApiObject::Mapping(Envelope { value: m, version }) => { + Some(Envelope::new(version, vec![*m])) + } + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn example_to_json() { + let mapping = Mapping { + id: Some("volume".to_string()), + name: Some("Volume".to_string()), + tags: Some(vec!["mix".to_string(), "master".to_string()]), + group: Some("faders".to_string()), + visible_in_projection: Some(true), + enabled: Some(true), + control_enabled: Some(true), + feedback_enabled: Some(true), + activation_condition: None, + source: Some(Source::MidiControlChangeValue( + MidiControlChangeValueSource { + feedback_behavior: Some(FeedbackBehavior::Normal), + channel: Some(0), + controller_number: Some(64), + character: Some(SourceCharacter::Button), + fourteen_bit: Some(false), + }, + )), + glue: Some(Glue { + source_interval: Some(Interval(0.3, 0.7)), + ..Default::default() + }), + target: None, + ..Default::default() + }; + serde_json::to_string_pretty(&mapping).unwrap(); + // std::fs::write("src/schema/test/example.json", json).unwrap(); + } + + #[test] + fn example_from_lua() { + use mlua::{Lua, LuaSerdeExt}; + let lua = Lua::new(); + let value = lua.load(include_str!("test/example.lua")).eval().unwrap(); + let mapping: Mapping = lua.from_value(value).unwrap(); + serde_json::to_string_pretty(&mapping).unwrap(); + // std::fs::write("src/schema/test/example_from_lua.json", json).unwrap(); + } +} diff --git a/plugin-reaper-realearn/api/src/persistence/parameter.rs b/plugin-reaper-realearn/api/src/persistence/parameter.rs new file mode 100644 index 0000000..878683d --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/parameter.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; +use std::num::NonZeroU32; + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct Parameter { + pub index: u32, + /// An optional ID that you can assign to this parameter in order to refer + /// to it from somewhere else. + /// + /// This ID should be unique within all parameters in the same compartment. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub value_count: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub value_labels: Option>, +} diff --git a/plugin-reaper-realearn/api/src/persistence/preset.rs b/plugin-reaper-realearn/api/src/persistence/preset.rs new file mode 100644 index 0000000..9a2bb2e --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/preset.rs @@ -0,0 +1,248 @@ +use crate::util::deserialize_null_default; +use semver::Version; +use serde::{Deserialize, Serialize}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::collections::HashSet; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; +use strum::{Display, EnumString}; + +/// Meta data that is common to both main and controller presets. +/// +/// Preset meta data is everything that is loaded right at startup in order to be able to +/// display a list of preset, do certain validations etc. It doesn't include the preset +/// content which is necessary to actually use the preset (e.g. it doesn't include the mappings). +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct CommonPresetMetaData { + /// Display name of the preset. + pub name: String, + /// The ReaLearn version for which this preset was built. + /// + /// This can effect the way the preset is loaded, e.g. it can lead to different interpretation + /// or migration of properties. So care should be taken to set this correctly! + /// + /// If `None`, it's assumed that it was built for a very old version (< 1.12.0-pre18) that + /// didn't have the versioning concept yet. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "Option::is_none" + )] + #[serde(alias = "version")] + pub realearn_version: Option, + /// Author of the preset. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "Option::is_none" + )] + pub author: Option, + /// Preset description (prose). + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "Option::is_none" + )] + pub description: Option, + /// Preset setup instructions (prose). + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "Option::is_none" + )] + pub setup_instructions: Option, + /// Device manufacturer. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "Option::is_none" + )] + pub device_manufacturer: Option, + /// Original name of the device. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "Option::is_none" + )] + pub device_name: Option, +} + +/// Metadata that is specific to controller presets. +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct ControllerPresetMetaData { + /// MIDI identity compatibility pattern. + /// + /// Will be used for auto-adding controllers and for finding the correct controller preset when calculating auto + /// units. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "Option::is_none" + )] + pub midi_identity_pattern: Option, + /// Possible MIDI identity compatibility patterns. + /// + /// Will be used for auto-adding controllers and for finding the correct controller preset when calculating auto + /// units. + /// + /// It should only be provided if the device in question doesn't reply to device queries or if it exposes + /// multiple ports which all respond with the same device identity and only one of the ports is the correct one. + /// Example: APC Key 25 mk2, which exposes a "Control" and a "Keys" port. + /// + /// It's a list because names often differ between operating systems. ReaLearn will match any in the list. + #[serde(default)] + pub midi_output_port_patterns: Vec, + /// Provided virtual control schemes. + /// + /// Will be used for finding the correct controller preset when calculating auto units. + /// + /// The order matters! It directly influences the choice of the best-suited main presets. In particular, + /// schemes that are more specific to this particular controller (e.g. "novation/launchpad-mk3") should come first. + /// Generic schemes (e.g. "grid") should come last. When auto-picking a main preset, matches of more specific + /// schemes will be favored over less specific ones. + #[serde(default)] + pub provided_schemes: Vec, +} + +#[derive(Clone, Eq, PartialEq, Debug, SerializeDisplay, DeserializeFromStr)] +pub struct MidiPortPattern { + pub scope: Option, + pub name_pattern: String, +} + +impl MidiPortPattern { + pub fn scope_matches(&self) -> bool { + let Some(scope) = self.scope else { + return true; + }; + match scope { + MidiPortPatternScope::Windows => cfg!(windows), + MidiPortPatternScope::MacOs => cfg!(target_os = "macos"), + MidiPortPatternScope::Linux => cfg!(target_os = "linux"), + } + } +} + +impl FromStr for MidiPortPattern { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + if let Some((scope_string, name_pattern)) = s.split_once(':') { + if let Ok(scope) = MidiPortPatternScope::from_str(scope_string) { + // MIDI port pattern with scope restriction + let pattern = Self { + scope: Some(scope), + name_pattern: name_pattern.to_string(), + }; + return Ok(pattern); + } + } + // MIDI port pattern without scope restriction + let pattern = Self { + scope: None, + name_pattern: s.to_string(), + }; + Ok(pattern) + } +} + +impl Display for MidiPortPattern { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let Some(s) = self.scope { + write!(f, "{s}:")?; + } + self.name_pattern.fmt(f)?; + Ok(()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, EnumString)] +#[strum(serialize_all = "lowercase")] +pub enum MidiPortPatternScope { + Windows, + MacOs, + Linux, +} + +/// Metadata that is specific to main presets. +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MainPresetMetaData { + /// Used virtual control schemes. + /// + /// Will be used for finding the correct controller preset when calculating auto units. + #[serde(default)] + pub used_schemes: HashSet, + /// A set of features that a Helgobox instance needs to provide for the preset to make sense. + /// + /// See [instance_features]. + /// + /// Will be used for determining whether an auto unit should be created for a specific instance + /// or not. Example: If the required feature is "playtime" and a controller is configured with + /// this main preset but the instance doesn't contain a Playtime matrix, this instance will + /// not load the main preset. + #[serde(default)] + pub required_features: HashSet, +} + +impl MainPresetMetaData { + pub fn requires_playtime(&self) -> bool { + self.required_features.contains(instance_features::PLAYTIME) + } + + /// Higher specificity means that the main preset uses a scheme provided by the controller that's more specific to + /// that particular controller (and therefore better suited). + /// + /// When picking the "best" main preset for a given controller preset, this is the first criteria taken into + /// account, if there are two competing main preset candidates. + /// + /// Given a controller preset that provides the schemes [bla, foo]. + /// If main preset A uses schemes [bla] and main preset B [foo], we want main preset A to win because + /// "bla" comes first in the controller preset's list of provided schemes, meaning that "bla" is the + /// more specific scheme. + /// + /// An example where this matters in practice: + /// - Controller preset "Launchpad Pro mk3 - Live mode" provides schemes [novation/launchpad-pro-mk3/live, grid] + /// - Main preset "Generic grid controller - Playtime" uses schemes [grid] + /// - Main preset "Launchpad Pro mk3 - Playtime" uses schemes [novation/launchpad-pro-mk3/live] + /// + /// Without that rule, it could easily happen that "Generic grid controller - Playtime" will be picked. Bad! + /// + /// Returns `None` if no scheme matches. + pub fn calc_scheme_specificity( + &self, + provided_schemes: &[VirtualControlSchemeId], + ) -> Option { + let lowest_matching_index = self + .used_schemes + .iter() + .filter_map(|used_scheme| provided_schemes.iter().position(|s| s == used_scheme)) + .min()?; + Some((provided_schemes.len() - lowest_matching_index) as u8) + } + + /// Higher coverage means that the main preset uses more schemes provided by the controller. + /// + /// When picking the "best" main preset for a given controller preset, this is the second criteria taken into + /// account, if the specificity of two main preset candidates is the same. + pub fn calc_scheme_coverage(&self, provided_schemes: &[VirtualControlSchemeId]) -> u8 { + self.used_schemes + .iter() + .filter(|used_scheme| provided_schemes.contains(used_scheme)) + .count() as u8 + } +} + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)] +pub struct VirtualControlSchemeId(String); + +impl VirtualControlSchemeId { + pub fn get(&self) -> &str { + &self.0 + } +} + +/// Known instance features. +pub mod instance_features { + /// Instance owns a Playtime matrix. + pub const PLAYTIME: &str = "playtime"; +} diff --git a/plugin-reaper-realearn/api/src/persistence/root.rs b/plugin-reaper-realearn/api/src/persistence/root.rs new file mode 100644 index 0000000..0e9911d --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/root.rs @@ -0,0 +1,6 @@ +use crate::persistence::session::Session; + +/// Only used for JSON schema generation. +pub struct RealearnPersistenceRoot { + _session: Session, +} diff --git a/plugin-reaper-realearn/api/src/persistence/session.rs b/plugin-reaper-realearn/api/src/persistence/session.rs new file mode 100644 index 0000000..539b792 --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/session.rs @@ -0,0 +1,20 @@ +use crate::persistence::*; + +/// Only used for JSON schema generation at the moment. +pub struct Session { + _main_compartment: Option, + _clip_matrix: Option, + _mapping_snapshots: Vec, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct MappingSnapshot { + pub id: String, + pub mappings: Vec, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct MappingInSnapshot { + pub id: String, + pub target_value: TargetValue, +} diff --git a/plugin-reaper-realearn/api/src/persistence/source.rs b/plugin-reaper-realearn/api/src/persistence/source.rs new file mode 100644 index 0000000..c0f0d22 --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/source.rs @@ -0,0 +1,394 @@ +use crate::persistence::{OscArgument, VirtualControlElementCharacter, VirtualControlElementId}; +use derive_more::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use serde::{Deserialize, Serialize}; +use strum::EnumIter; + +#[derive(PartialEq, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +#[allow(clippy::enum_variant_names)] +pub enum Source { + // None + #[default] + None, + // REAPER + MidiDeviceChanges, + RealearnInstanceStart, + RealearnCompartmentLoaded, + Timer(TimerSource), + RealearnParameter(RealearnParameterSource), + Speech, + // MIDI + MidiNoteVelocity(MidiNoteVelocitySource), + MidiNoteKeyNumber(MidiNoteKeyNumberSource), + MidiPolyphonicKeyPressureAmount(MidiPolyphonicKeyPressureAmountSource), + MidiControlChangeValue(MidiControlChangeValueSource), + MidiProgramChangeNumber(MidiProgramChangeNumberSource), + MidiSpecificProgramChange(MidiSpecificProgramChangeSource), + MidiChannelPressureAmount(MidiChannelPressureAmountSource), + MidiPitchBendChangeValue(MidiPitchBendChangeValueSource), + MidiParameterNumberValue(MidiParameterNumberValueSource), + MidiClockTempo, + MidiClockTransport(MidiClockTransportSource), + MidiRaw(MidiRawSource), + MidiScript(MidiScriptSource), + MackieLcd(MackieLcdSource), + XTouchMackieLcd(XTouchMackieLcdSource), + MackieSevenSegmentDisplay(MackieSevenSegmentDisplaySource), + SlKeyboardDisplay(SlKeyboardDisplaySource), + SiniConE24Display(SiniConE24DisplaySource), + LaunchpadProScrollingTextDisplay, + // OSC + Osc(OscSource), + // Keyboard + Key(KeySource), + // StreamDeck + StreamDeck(StreamDeckSource), + // Virtual + Virtual(VirtualSource), +} + +// Only makes sense for sources that support both control *and* feedback. +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum FeedbackBehavior { + Normal, + SendFeedbackAfterControl, + PreventEchoFeedback, +} + +impl Default for FeedbackBehavior { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiNoteVelocitySource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub key_number: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiNoteKeyNumberSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiPolyphonicKeyPressureAmountSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub key_number: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiControlChangeValueSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub controller_number: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub character: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub fourteen_bit: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiProgramChangeNumberSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiSpecificProgramChangeSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub program_number: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiChannelPressureAmountSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiPitchBendChangeValueSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiParameterNumberValueSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub number: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub fourteen_bit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub registered: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub character: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiClockTransportSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiRawSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pattern: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub character: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MidiScriptSource { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "kind")] + pub script_kind: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub script: Option, +} + +/// Kind of a MIDI script +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum MidiScriptKind { + #[default] + #[serde(alias = "eel")] + #[display(fmt = "EEL")] + Eel, + #[serde(alias = "lua")] + #[display(fmt = "Lua")] + Lua, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub enum SourceCharacter { + #[default] + Range, + Button, + // 127 = decrement; 0 = none; 1 = increment + Relative1, + // 63 = decrement; 64 = none; 65 = increment + Relative2, + // 65 = decrement; 0 = none; 1 = increment + Relative3, + StatefulButton, +} + +#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub enum MidiClockTransportMessage { + #[default] + Start, + Continue, + Stop, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct MackieLcdSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub extender_index: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct XTouchMackieLcdSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub extender_index: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct SlKeyboardDisplaySource { + #[serde(skip_serializing_if = "Option::is_none")] + pub section: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct MackieSevenSegmentDisplaySource { + #[serde(skip_serializing_if = "Option::is_none")] + pub scope: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub enum MackieSevenSegmentDisplayScope { + All, + #[default] + Assignment, + Tc, + TcHoursBars, + TcMinutesBeats, + TcSecondsSub, + TcFramesTicks, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct SiniConE24DisplaySource { + #[serde(skip_serializing_if = "Option::is_none")] + pub cell_index: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub item_index: Option, +} + +#[derive(Default, PartialEq, Serialize, Deserialize)] +pub struct OscSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub argument: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub relative: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_arguments: Option>, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct RealearnParameterSource { + pub parameter_index: u32, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct TimerSource { + pub duration: u64, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct KeySource { + #[serde(skip_serializing_if = "Option::is_none")] + pub keystroke: Option, +} + +#[derive(Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct StreamDeckSource { + pub button_index: u32, + #[serde(default)] + pub button_design: StreamDeckButtonDesign, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonDesign { + #[serde(default)] + pub background: StreamDeckButtonBackground, + #[serde(default)] + pub foreground: StreamDeckButtonForeground, + #[serde(default)] + pub static_text: String, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum StreamDeckButtonForeground { + #[default] + None, + FadingColor(StreamDeckButtonFadingColorForeground), + FadingImage(StreamDeckButtonFadingImageForeground), + SlidingImage(StreamDeckButtonSlidingImageForeground), + FullBar(StreamDeckButtonFullBarForeground), + Knob(StreamDeckButtonKnobForeground), +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum StreamDeckButtonBackground { + Color(StreamDeckButtonColorBackground), + Image(StreamDeckButtonImageBackground), +} + +impl Default for StreamDeckButtonBackground { + fn default() -> Self { + Self::Color(StreamDeckButtonColorBackground::default()) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonFadingImageForeground { + pub path: String, +} + +#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonSlidingImageForeground { + pub path: String, +} + +#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonFadingColorForeground {} + +#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonFullBarForeground {} + +#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonKnobForeground {} + +#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonImageBackground { + pub path: String, +} + +#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)] +pub struct StreamDeckButtonColorBackground {} + +#[derive(Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct Keystroke { + pub modifiers: u8, + pub key: u16, +} + +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct VirtualSource { + pub id: VirtualControlElementId, + #[serde(skip_serializing_if = "Option::is_none")] + pub character: Option, +} diff --git a/plugin-reaper-realearn/api/src/persistence/target.rs b/plugin-reaper-realearn/api/src/persistence/target.rs new file mode 100644 index 0000000..0e04e48 --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/target.rs @@ -0,0 +1,2453 @@ +use crate::persistence::{OscArgument, VirtualControlElementCharacter, VirtualControlElementId}; +use derive_more::Display; +use enumset::EnumSet; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::fmt::{Display, Formatter}; +use strum::{EnumIter, IntoEnumIterator}; + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Serialize, + Deserialize, + enum_map::Enum, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum LearnableTargetKind { + TrackVolume, + TrackPan, + RouteVolume, + RoutePan, + TrackArmState, + TrackMuteState, + TrackSoloState, + TrackSelectionState, + FxOnOffState, + FxParameterValue, + BrowseFxPresets, + PlayRate, + Tempo, + TrackAutomationMode, + TrackMonitoringMode, + AutomationModeOverride, + ReaperAction, + TransportAction, + // Could be nice to add to the list of learnable targets + // Seek, + // TrackParentSendState, + // AllTrackFxOnOffState, + // TrackPhase, + // TrackWidth, + // TrackVisibility, + // FxOnlineOfflineState, + // LoadFxSnapshot, + // FxVisibility, + // RouteAutomationMode, + // RouteMonoState, + // RouteMuteState, + // RoutePhase, +} + +/// Which target invocations to observe, based on causality. E.g. only those not triggered by +/// ReaLearn (would pick up invocations triggered by mouse interaction with REAPER but not by +/// ReaLearn mapping control). +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Default, + Serialize, + Deserialize, + enum_map::Enum, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + derive_more::Display, +)] +#[repr(usize)] +pub enum TargetTouchCause { + #[display(fmt = "Any")] + #[default] + Any, + #[display(fmt = "Caused by ReaLearn (via mapping)")] + Realearn, + #[display(fmt = "Not caused by ReaLearn (e.g. via mouse)")] + Reaper, +} + +#[derive(PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum Target { + Mouse(MouseTarget), + LastTouched(LastTouchedTarget), + AutomationModeOverride(AutomationModeOverrideTarget), + ReaperAction(ReaperActionTarget), + TransportAction(TransportActionTarget), + AnyOn(AnyOnTarget), + #[serde(alias = "CycleThroughTracks")] + BrowseTracks(BrowseTracksTarget), + Seek(SeekTarget), + PlayRate(PlayRateTarget), + Tempo(TempoTarget), + GoToBookmark(GoToBookmarkTarget), + TrackArmState(TrackArmStateTarget), + TrackParentSendState(TrackParentSendStateTarget), + AllTrackFxOnOffState(AllTrackFxOnOffStateTarget), + TrackMuteState(TrackMuteStateTarget), + TrackPeak(TrackPeakTarget), + TrackPhase(TrackPhaseTarget), + TrackSelectionState(TrackSelectionStateTarget), + TrackAutomationMode(TrackAutomationModeTarget), + TrackMonitoringMode(TrackMonitoringModeTarget), + TrackAutomationTouchState(TrackAutomationTouchStateTarget), + TrackPan(TrackPanTarget), + TrackWidth(TrackWidthTarget), + TrackVolume(TrackVolumeTarget), + #[serde(alias = "Track")] + TrackTool(TrackToolTarget), + TrackVisibility(TrackVisibilityTarget), + TrackSoloState(TrackSoloStateTarget), + #[serde(alias = "CycleThroughFx")] + BrowseFxChain(BrowseFxChainTarget), + FxOnOffState(FxOnOffStateTarget), + FxOnlineOfflineState(FxOnlineOfflineStateTarget), + LoadFxSnapshot(LoadFxSnapshotTarget), + #[serde(alias = "CycleThroughFxPresets")] + BrowseFxPresets(BrowseFxPresetsTarget), + #[serde(alias = "Fx")] + FxTool(FxToolTarget), + FxVisibility(FxVisibilityTarget), + FxParameterValue(FxParameterValueTarget), + FxParameterAutomationTouchState(FxParameterAutomationTouchStateTarget), + RouteAutomationMode(RouteAutomationModeTarget), + RouteMonoState(RouteMonoStateTarget), + RouteMuteState(RouteMuteStateTarget), + RoutePhase(RoutePhaseTarget), + RoutePan(RoutePanTarget), + RouteVolume(RouteVolumeTarget), + RouteTouchState(RouteTouchStateTarget), + #[serde(alias = "ClipTransportAction")] + PlaytimeSlotTransportAction(PlaytimeSlotTransportActionTarget), + #[serde(alias = "ClipColumnAction")] + PlaytimeColumnAction(PlaytimeColumnActionTarget), + #[serde(alias = "ClipRowAction")] + PlaytimeRowAction(PlaytimeRowActionTarget), + #[serde(alias = "ClipMatrixAction")] + PlaytimeMatrixAction(PlaytimeMatrixActionTarget), + PlaytimeControlUnitScroll(PlaytimeControlUnitScrollTarget), + PlaytimeBrowseCells(PlaytimeBrowseCellsTarget), + #[serde(alias = "ClipSeek")] + PlaytimeSlotSeek(PlaytimeSlotSeekTarget), + #[serde(alias = "ClipVolume")] + PlaytimeSlotVolume(PlaytimeSlotVolumeTarget), + #[serde(alias = "ClipManagement")] + PlaytimeSlotManagementAction(PlaytimeSlotManagementActionTarget), + SendMidi(SendMidiTarget), + SendOsc(SendOscTarget), + Dummy(DummyTarget), + EnableInstances(EnableInstancesTarget), + EnableUnits(EnableUnitsTarget), + EnableMappings(EnableMappingsTarget), + ModifyMapping(ModifyMappingTarget), + CompartmentParameterValue(CompartmentParameterValueTarget), + #[serde(alias = "LoadMappingSnapshots")] + LoadMappingSnapshot(LoadMappingSnapshotTarget), + TakeMappingSnapshot(TakeMappingSnapshotTarget), + #[serde(alias = "CycleThroughGroupMappings")] + BrowseGroupMappings(BrowseGroupMappingsTarget), + BrowsePotFilterItems(BrowsePotFilterItemsTarget), + #[serde(alias = "NavigateWithinPotPresets")] + BrowsePotPresets(BrowsePotPresetsTarget), + PreviewPotPreset(PreviewPotPresetTarget), + LoadPotPreset(LoadPotPresetTarget), + StreamDeckBrightness(StreamDeckBrightnessTarget), + Virtual(VirtualTarget), +} + +impl Default for Target { + fn default() -> Self { + Self::LastTouched(Default::default()) + } +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TargetCommons { + #[serde(skip_serializing_if = "Option::is_none")] + pub unit: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub enum TargetUnit { + Native, + Percent, +} + +impl Default for TargetUnit { + fn default() -> Self { + Self::Native + } +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct MouseTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub action: MouseAction, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct LastTouchedTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub included_targets: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub touch_cause: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct AutomationModeOverrideTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "override")] + pub override_value: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct ReaperActionTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub scope: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub command: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub invocation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum ActionScope { + #[display(fmt = "Main")] + #[default] + Main, + #[display(fmt = "Active MIDI editor")] + ActiveMidiEditor, + #[display(fmt = "Active MIDI event list editor")] + ActiveMidiEventListEditor, + #[display(fmt = "Media explorer")] + MediaExplorer, +} + +impl ActionScope { + pub fn guess_from_section_id(section_id: u32) -> Self { + match section_id { + 32060 => ActionScope::ActiveMidiEditor, + 32061 => ActionScope::ActiveMidiEventListEditor, + 32063 => ActionScope::MediaExplorer, + _ => ActionScope::Main, + } + } + + pub fn section_id(&self) -> u32 { + match self { + ActionScope::Main => 0, + ActionScope::ActiveMidiEditor => 32060, + ActionScope::ActiveMidiEventListEditor => 32061, + ActionScope::MediaExplorer => 32063, + } + } +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct TransportActionTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub action: TransportAction, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct AnyOnTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub parameter: AnyOnParameter, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct BrowseTracksTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub scroll_arrange_view: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub scroll_mixer: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct SeekTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_time_selection: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_loop_points: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_regions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_project: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub move_view: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub seek_play: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_resolution: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub behavior: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct PlayRateTarget { + #[serde(flatten)] + pub commons: TargetCommons, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TempoTarget { + #[serde(flatten)] + pub commons: TargetCommons, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct GoToBookmarkTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub bookmark: BookmarkDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + pub set_time_selection: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub set_loop_points: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub seek_behavior: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackArmStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackParentSendStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct AllTrackFxOnOffStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub poll_for_feedback: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackMuteStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackPeakTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackPhaseTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub poll_for_feedback: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackSelectionStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub scroll_arrange_view: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub scroll_mixer: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct TrackAutomationModeTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + pub mode: AutomationMode, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct TrackMonitoringModeTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + pub mode: MonitoringMode, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct TrackAutomationTouchStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, + pub touched_parameter: TouchedTrackParameter, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackPanTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackWidthTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackVolumeTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackToolTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub action: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub instance_tags: Option>, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum TrackToolAction { + #[display(fmt = "None (feedback only)")] + DoNothing, + #[display(fmt = "Set (as unit track)")] + #[serde(alias = "SetAsInstanceTrack")] + SetAsUnitTrack, + #[display(fmt = "Pin (as unit track)")] + #[serde(alias = "PinAsInstanceTrack")] + PinAsUnitTrack, +} + +impl Default for TrackToolAction { + fn default() -> Self { + Self::DoNothing + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum MouseAction { + /// Mouse position on the given axis. + /// + /// Control: + /// + /// - Move by (using relative control value) + /// - Move to (use absolute control value) + /// - Move to absolute coordinates before clicking (2 mappings for moving, 1 for clicking) + /// + /// Feedback: + /// + /// - Reflect position of the mouse cursor + /// + /// Future extension possibilities: + /// + /// - Canvas: Relative to all screens, current screen, REAPER window or focused window + /// - Pixel density: Take pixel density into account + MoveTo { + #[serde(skip_serializing_if = "Option::is_none")] + axis: Option, + }, + /// Like [`Self::Move`] but prefers relative control, so the glue section will feed it + /// with relative control values whenever possible. Could make a little difference. + MoveBy { + #[serde(skip_serializing_if = "Option::is_none")] + axis: Option, + }, + /// Button state. + /// + /// Control: + /// + /// - Press and release a mouse button + /// - Press a mouse button and keep it pressed (press-only filter) + /// - Just release a mouse button (release-only filter, e.g. for manual drag control) + /// + /// Feedback: + /// + /// - Whether the button is down or up + /// + /// Future extension possibilities: + /// + /// - Click or double-click a mouse button (press and immediate release, this could be a generic + /// "Glue" option because it could be useful for other on/off targets as well). + PressOrRelease { + #[serde(skip_serializing_if = "Option::is_none")] + button: Option, + }, + /// Scroll wheel. + /// + /// Control: + /// + /// - Invoke scroll wheel + /// + /// Feedback: None + Scroll { + #[serde(skip_serializing_if = "Option::is_none")] + axis: Option, + }, +} + +impl Default for MouseAction { + fn default() -> Self { + Self::MoveTo { + axis: Default::default(), + } + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum Axis { + #[default] + #[display(fmt = "X (horizontal)")] + X, + #[display(fmt = "Y (vertical)")] + Y, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum MouseButton { + Left, + Middle, + Right, +} + +impl Default for MouseButton { + fn default() -> Self { + Self::Left + } +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct TrackVisibilityTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + /// Made obsolete in 2.14.0-pre.8. + #[serde(skip_serializing)] + pub poll_for_feedback: Option, + pub area: TrackArea, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TrackSoloStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub behavior: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_track_grouping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_selection_ganging: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct BrowseFxChainTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub chain: FxChainDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + pub display_kind: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct FxOnOffStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct FxOnlineOfflineStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct LoadFxSnapshotTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct BrowseFxPresetsTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct FxToolTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub action: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub instance_tags: Option>, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum FxToolAction { + #[display(fmt = "None (feedback only)")] + DoNothing, + #[display(fmt = "Set (as unit FX)")] + #[serde(alias = "SetAsInstanceFx")] + SetAsUnitFx, + #[display(fmt = "Pin (as unit FX)")] + #[serde(alias = "PinAsInstanceFx")] + PinAsUnitFx, +} + +impl Default for FxToolAction { + fn default() -> Self { + Self::DoNothing + } +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct FxVisibilityTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub display_kind: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct FxParameterValueTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub parameter: FxParameterDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + pub poll_for_feedback: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub retrigger: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub real_time: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct CompartmentParameterValueTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub parameter: CompartmentParameterDescriptor, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct FxParameterAutomationTouchStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub parameter: FxParameterDescriptor, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct RouteAutomationModeTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub route: RouteDescriptor, + pub mode: AutomationMode, + #[serde(skip_serializing_if = "Option::is_none")] + pub poll_for_feedback: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct RouteMonoStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub route: RouteDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + pub poll_for_feedback: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct RouteMuteStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub route: RouteDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + pub poll_for_feedback: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct RoutePhaseTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub route: RouteDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + pub poll_for_feedback: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct RoutePanTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub route: RouteDescriptor, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct RouteVolumeTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub route: RouteDescriptor, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct RouteTouchStateTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub route: RouteDescriptor, + pub touched_parameter: TouchedRouteParameter, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeSlotTransportActionTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub slot: PlaytimeSlotDescriptor, + pub action: PlaytimeSlotTransportAction, + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_column_if_slot_empty: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeColumnActionTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub column: PlaytimeColumnDescriptor, + pub action: PlaytimeColumnAction, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeRowActionTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub row: PlaytimeRowDescriptor, + pub action: PlaytimeRowAction, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeMatrixActionTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub action: PlaytimeMatrixAction, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeControlUnitScrollTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub axis: Axis, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeBrowseCellsTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub axis: Axis, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeSlotSeekTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub slot: PlaytimeSlotDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback_resolution: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct PlaytimeSlotVolumeTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub slot: PlaytimeSlotDescriptor, +} + +#[derive(PartialEq, Serialize, Deserialize)] +pub struct PlaytimeSlotManagementActionTarget { + #[serde(flatten)] + pub commons: TargetCommons, + pub slot: PlaytimeSlotDescriptor, + pub action: PlaytimeSlotManagementAction, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeSlotManagementAction { + #[display(fmt = "Clear slot")] + ClearSlot, + #[display(fmt = "Fill slot with selected item")] + FillSlotWithSelectedItem, + #[display(fmt = "Edit first clip")] + EditClip, + #[display(fmt = "Copy or paste clip")] + CopyOrPasteClip, + #[display(fmt = "Double clip section length")] + DoubleClipSectionLength, + #[display(fmt = "Halve clip section length")] + HalveClipSectionLength, + #[display(fmt = "Quantization on/off state")] + QuantizationOnOffState, + #[display(fmt = "Duplicate")] + Duplicate, + #[display(fmt = "Activate")] + Activate, +} + +impl Default for PlaytimeSlotManagementAction { + fn default() -> Self { + Self::ClearSlot + } +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct AdjustClipSectionLengthAction { + pub factor: f64, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct SendMidiTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub destination: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct DummyTarget { + #[serde(flatten)] + pub commons: TargetCommons, +} + +#[derive(PartialEq, Default, Serialize, Deserialize)] +pub struct SendOscTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub argument: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub destination: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct EnableInstancesTarget { + #[serde(flatten)] + pub commons: TargetCommons, + /// For backwards compatibility, this defaults to [InstanceTagKind::UnitTags]. + #[serde(skip_serializing_if = "Option::is_none")] + pub tag_kind: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct EnableUnitsTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct EnableMappingsTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct ModifyMappingTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub session: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mapping: Option, + pub modification: MappingModification, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum MappingModification { + LearnTarget(LearnTargetMappingModification), + SetTargetToLastTouched(SetTargetToLastTouchedMappingModification), +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct LearnTargetMappingModification { + #[serde(skip_serializing_if = "Option::is_none")] + pub included_targets: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub touch_cause: Option, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct SetTargetToLastTouchedMappingModification { + #[serde(skip_serializing_if = "Option::is_none")] + pub included_targets: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub touch_cause: Option, +} + +#[derive(PartialEq, Default, Serialize, Deserialize)] +pub struct LoadMappingSnapshotTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub active_mappings_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_value: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct TakeMappingSnapshotTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub active_mappings_only: Option, + #[serde(alias = "snapshot_id")] + pub snapshot: BackwardCompatibleMappingSnapshotDescForTake, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BackwardCompatibleMappingSnapshotDescForTake { + Old(String), + New(MappingSnapshotDescForTake), +} + +impl Default for BackwardCompatibleMappingSnapshotDescForTake { + fn default() -> Self { + Self::New(Default::default()) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum MappingSnapshotDescForLoad { + Initial, + ById { id: String }, +} + +/// The kind of tag to use to address Helgobox instances. +/// +/// Of course, normally we should use instance tags. The only reason that we allow instances to be addressed by unit +/// tags is that there was no other possibility in the past, and we need to maintain backward compatibility. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum InstanceTagKind { + /// This is usually the correct way to address instances. + #[default] + #[display(fmt = "Instance tags")] + InstanceTags, + /// This is the legacy way to address instances. + #[display(fmt = "Unit tags")] + UnitTags, +} + +impl MappingSnapshotDescForLoad { + pub fn id(&self) -> Option<&str> { + match self { + MappingSnapshotDescForLoad::Initial => None, + MappingSnapshotDescForLoad::ById { id } => Some(id), + } + } +} + +impl Default for MappingSnapshotDescForLoad { + fn default() -> Self { + Self::Initial + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum MappingSnapshotDescForTake { + LastLoaded, + ById { id: String }, +} + +impl MappingSnapshotDescForTake { + pub fn id(&self) -> Option<&str> { + match self { + MappingSnapshotDescForTake::LastLoaded => None, + MappingSnapshotDescForTake::ById { id } => Some(id), + } + } +} + +impl Default for MappingSnapshotDescForTake { + fn default() -> Self { + Self::LastLoaded + } +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct BrowseGroupMappingsTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub exclusivity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub group: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct BrowsePotFilterItemsTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub item_kind: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct BrowsePotPresetsTarget { + #[serde(flatten)] + pub commons: TargetCommons, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct PreviewPotPresetTarget { + #[serde(flatten)] + pub commons: TargetCommons, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct LoadPotPresetTarget { + #[serde(flatten)] + pub commons: TargetCommons, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx: Option, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct StreamDeckBrightnessTarget { + #[serde(flatten)] + pub commons: TargetCommons, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + enum_map::Enum, + enumset::EnumSetType, +)] +#[enumset(no_super_impls)] +#[repr(usize)] +pub enum PotFilterKind { + #[display(fmt = "Database")] + Database, + /// Is available or not + #[display(fmt = "Availability")] + IsAvailable, + /// Is supported or not + #[display(fmt = "Support")] + IsSupported, + /// Is user preset or factory preset + #[display(fmt = "Content types")] + #[serde(alias = "NksContentType")] + IsUser, + /// Instrument, Effect, Loop, One Shot + #[display(fmt = "Product types")] + #[serde(alias = "NksProductType")] + ProductKind, + /// Is favorite or not + #[display(fmt = "Favorite")] + #[serde(alias = "NksFavorite")] + IsFavorite, + #[display(fmt = "Project")] + #[serde(alias = "Project")] + Project, + #[display(fmt = "Product")] + #[serde(alias = "NksBank")] + Bank, + #[display(fmt = "Bank")] + #[serde(alias = "NksSubBank")] + SubBank, + #[display(fmt = "Type")] + #[serde(alias = "NksCategory")] + Category, + #[display(fmt = "Sub type")] + #[serde(alias = "NksSubCategory")] + SubCategory, + #[display(fmt = "Character")] + #[serde(alias = "NksMode")] + Mode, + #[display(fmt = "Preview")] + HasPreview, +} + +impl PotFilterKind { + /// We could also use the generated `into_enum_iter()` everywhere but IDE completion + /// in IntelliJ Rust doesn't work for that at the time of this writing. + pub fn enum_iter() -> impl ExactSizeIterator { + Self::iter() + } + + pub fn allows_excludes(&self) -> bool { + use PotFilterKind as K; + matches!(self, K::Database | K::Bank | K::SubBank) + } + + pub fn wants_sorting(&self) -> bool { + use PotFilterKind as K; + matches!( + self, + K::Database + | K::Project + | K::Bank + | K::SubBank + | K::Category + | K::SubCategory + | K::Mode + ) + } + + pub fn parent(&self) -> Option { + match self { + PotFilterKind::SubBank => Some(PotFilterKind::Bank), + PotFilterKind::SubCategory => Some(PotFilterKind::Category), + _ => None, + } + } + + pub fn dependent_kinds(&self) -> impl Iterator { + let dep_pos = self.dependency_position(); + Self::iter().filter(move |k| k.dependency_position() > dep_pos) + } + + pub fn core_kinds() -> EnumSet { + Self::iter().filter(|k| k.is_core_kind()).collect() + } + + /// Those kinds are always supported, no matter the database. + /// + /// The other ones are called "advanced" kinds. + pub fn is_core_kind(&self) -> bool { + use PotFilterKind as K; + matches!( + self, + K::Database + | K::IsAvailable + | K::IsSupported + | K::IsUser + | K::ProductKind + | K::IsFavorite + | K::HasPreview + ) + } + + /// Filter kinds with lower dependency positions affect filter kinds with higher dependency + /// positions. + /// + /// This position is used by code in order to determine whether filter items need to be + /// recalculated. E.g. when changing the category, it means the set of possible sub categories + /// might be affected (higher position) but the set of possible banks not (lower position). + pub fn dependency_position(&self) -> u32 { + use PotFilterKind as K; + match self { + K::Database + | K::IsAvailable + | K::IsSupported + | K::IsUser + | K::ProductKind + | K::IsFavorite => 0, + K::Project => 1, + K::Bank => 2, + K::SubBank => 3, + K::Category => 4, + K::SubCategory => 5, + K::Mode => 6, + K::HasPreview => 7, + } + } +} + +impl Default for PotFilterKind { + fn default() -> Self { + Self::Database + } +} + +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct VirtualTarget { + pub id: VirtualControlElementId, + #[serde(skip_serializing_if = "Option::is_none")] + pub character: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub learnable: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum AutomationModeOverride { + Bypass, + Mode { mode: AutomationMode }, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum AutomationMode { + TrimRead, + Read, + Touch, + Write, + Latch, + LatchPreview, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum MonitoringMode { + #[display(fmt = "Off")] + Off, + #[display(fmt = "Normal")] + Normal, + #[display(fmt = "Tape style (off when playing)")] + TapeStyle, +} + +impl Default for MonitoringMode { + fn default() -> Self { + Self::Off + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TransportAction { + PlayStop, + PlayPause, + Stop, + Pause, + Record, + Repeat, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeSlotTransportAction { + /// Triggers the slot according to the matrix settings (toggle, momentary, retrigger). + #[display(fmt = "Trigger")] + Trigger, + /// Starts or stops playback. + /// + /// - If slot filled, starts or stops playback. + /// - If slot empty, has no effect. + /// - If slot recording, has no effect or stops recording. + #[display(fmt = "Play/stop")] + PlayStop, + /// Starts or pauses playback. + /// + /// - If slot filled, starts or pauses playback. + /// - If slot empty, has no effect. + /// - If slot recording, has no effect. + #[display(fmt = "Play/pause")] + PlayPause, + /// Stops playback or recording. + /// + /// - If slot filled, stops playback. + /// - If slot empty, has no effect. + /// - If slot recording, stops recording. + #[display(fmt = "Stop")] + Stop, + /// Pauses playback. + /// + /// - If slot filled, pauses playback. + /// - If slot empty, has no effect. + /// - If slot recording, has no effect. + #[display(fmt = "Pause")] + Pause, + /// Starts or stops recording. + /// + /// - If slot filled, starts recording or stops playback. + /// - If slot empty, starts recording or has no effect. + /// - If slot recording, has no effect or stops recording. + #[display(fmt = "Record/stop")] + RecordStop, + /// Starts or stops playback or recording. + /// + /// - If slot filled, starts or stops playback. + /// - If slot empty, starts recording or has no effect. + /// - If slot recording, has no effect or stops recording. + #[display(fmt = "Record/play/stop")] + RecordPlayStop, + /// Starts or stops overdubbing. + /// + /// - If slot is stopped, starts playing and overdubbing. + /// - If slot is playing, continues playing and starts overdubbing. + /// - If slot is overdubbing or recording, continues playing and stops overdubbing. + /// - If slot empty or not MIDI, does nothing. + #[display(fmt = "Overdub/play")] + OverdubPlay, + /// Changes the loop setting. + /// + /// - If slot is filled, sets looped on or off. + /// - If slot empty, has no effect. + /// - If slot recording, has no effect. + #[display(fmt = "Looped")] + Looped, +} + +impl Default for PlaytimeSlotTransportAction { + fn default() -> Self { + Self::PlayStop + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeColumnAction { + #[display(fmt = "Stop")] + Stop, + #[display(fmt = "Arm/Disarm")] + ArmState, + #[display(fmt = "Arm/Disarm exclusive")] + ArmStateExclusive, + #[display(fmt = "Activate")] + Activate, +} + +impl Default for PlaytimeColumnAction { + fn default() -> Self { + Self::Stop + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeRowAction { + #[display(fmt = "Play")] + PlayScene, + #[display(fmt = "Build scene")] + BuildScene, + #[display(fmt = "Clear")] + ClearScene, + #[display(fmt = "Copy or paste")] + CopyOrPasteScene, + #[display(fmt = "Activate")] + Activate, +} + +impl Default for PlaytimeRowAction { + fn default() -> Self { + Self::PlayScene + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeMatrixAction { + #[display(fmt = "Stop")] + Stop, + #[display(fmt = "Undo")] + Undo, + #[display(fmt = "Redo")] + Redo, + #[display(fmt = "Build scene")] + BuildScene, + #[display(fmt = "Set record length mode")] + SetRecordLengthMode, + #[display(fmt = "Set custom record length in bars")] + SetCustomRecordLengthInBars, + #[display(fmt = "Enable/disable click")] + ClickOnOffState, + #[display(fmt = "Enable/disable MIDI auto-quantize")] + MidiAutoQuantizationOnOffState, + #[display(fmt = "Smart record")] + SmartRecord, + #[display(fmt = "Start or stop playback")] + #[serde(alias = "EnterSilenceModeOrPlayIgnited")] + #[serde(alias = "PlayIgnitedOrEnterSilenceMode")] + StartOrStopPlayback, + #[display(fmt = "Enable/disable silence mode")] + SilenceModeOnOffState, + #[display(fmt = "Panic")] + Panic, + #[display(fmt = "Enable/disable sequencer recording")] + SequencerRecordOnOffState, + #[display(fmt = "Enable/disable sequencer playing")] + SequencerPlayOnOffState, + #[display(fmt = "Tap tempo")] + TapTempo, +} + +impl Default for PlaytimeMatrixAction { + fn default() -> Self { + Self::Stop + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum AnyOnParameter { + TrackSolo, + TrackMute, + TrackArm, + TrackSelection, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum ActionInvocationKind { + Trigger, + #[serde(alias = "Absolute")] + Absolute14Bit, + Absolute7Bit, + Relative, +} + +impl Default for ActionInvocationKind { + fn default() -> Self { + Self::Absolute14Bit + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ReaperCommand { + Id(u32), + Name(String), +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum TrackDescriptor { + /// Resolves to the track on which this ReaLearn instance is installed. + /// + /// Doesn't make sense if ReaLearn is installed on the monitoring FX chain. + This { + #[serde(flatten)] + commons: TrackDescriptorCommons, + }, + Master { + #[serde(flatten)] + commons: TrackDescriptorCommons, + }, + Instance { + #[serde(flatten)] + commons: TrackDescriptorCommons, + }, + Selected { + #[serde(skip_serializing_if = "Option::is_none")] + allow_multiple: Option, + }, + Dynamic { + #[serde(flatten)] + commons: TrackDescriptorCommons, + expression: String, + #[serde(skip_serializing_if = "Option::is_none")] + scope: Option, + }, + ById { + #[serde(flatten)] + commons: TrackDescriptorCommons, + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + }, + ByIndex { + #[serde(flatten)] + commons: TrackDescriptorCommons, + index: u32, + #[serde(skip_serializing_if = "Option::is_none")] + scope: Option, + }, + ByName { + #[serde(flatten)] + commons: TrackDescriptorCommons, + name: String, + #[serde(skip_serializing_if = "Option::is_none")] + allow_multiple: Option, + }, + FromClipColumn { + #[serde(flatten)] + commons: TrackDescriptorCommons, + column: PlaytimeColumnDescriptor, + context: ClipColumnTrackContext, + }, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub enum ClipColumnTrackContext { + Playback, + Recording, +} + +impl Default for ClipColumnTrackContext { + fn default() -> Self { + Self::Playback + } +} + +impl Default for TrackDescriptor { + fn default() -> Self { + Self::This { + commons: Default::default(), + } + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct TrackDescriptorCommons { + #[serde(skip_serializing_if = "Option::is_none")] + pub track_must_be_selected: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum FeedbackResolution { + Beat, + High, +} + +impl Default for FeedbackResolution { + fn default() -> Self { + Self::Beat + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[allow(clippy::enum_variant_names)] +pub enum TrackExclusivity { + WithinProject, + WithinFolder, + WithinProjectOnOnly, + WithinFolderOnOnly, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum InstanceExclusivity { + Exclusive, + ExclusiveOnOnly, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum UnitExclusivity { + Exclusive, + ExclusiveOnOnly, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum MappingExclusivity { + Exclusive, + ExclusiveOnOnly, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum GroupMappingExclusivity { + Exclusive, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TouchedTrackParameter { + Volume, + Pan, + Width, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TouchedRouteParameter { + Volume, + Pan, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TrackArea { + Tcp, + Mcp, +} + +#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] +pub enum SoloBehavior { + #[default] + InPlace, + IgnoreRouting, + ReaperPreference, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum SeekBehavior { + #[default] + #[display(fmt = "Immediate")] + Immediate, + #[display(fmt = "Smooth")] + Smooth, + #[display(fmt = "Use REAPER preference")] + ReaperPreference, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum BookmarkDescriptor { + Marker(BookmarkRef), + Region(BookmarkRef), +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BookmarkRef { + ById { id: u32 }, + ByIndex { index: u32 }, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct FxDescriptorCommons { + #[serde(skip_serializing_if = "Option::is_none")] + pub fx_must_have_focus: Option, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum FxDescriptor { + This { + #[serde(flatten)] + commons: FxDescriptorCommons, + }, + Focused, + Instance { + #[serde(flatten)] + commons: FxDescriptorCommons, + }, + Dynamic { + #[serde(flatten)] + commons: FxDescriptorCommons, + chain: FxChainDescriptor, + expression: String, + }, + ById { + #[serde(flatten)] + commons: FxDescriptorCommons, + chain: FxChainDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + }, + ByIndex { + #[serde(flatten)] + commons: FxDescriptorCommons, + chain: FxChainDescriptor, + index: u32, + }, + ByName { + #[serde(flatten)] + commons: FxDescriptorCommons, + chain: FxChainDescriptor, + name: String, + #[serde(skip_serializing_if = "Option::is_none")] + allow_multiple: Option, + }, +} + +impl Default for FxDescriptor { + fn default() -> Self { + Self::This { + commons: Default::default(), + } + } +} + +// The best default for this would be a FX chain but we don't have this yet! +// Therefore we don't implement Default at all for now. We can still do it later. +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum FxChainDescriptor { + Track { + #[serde(skip_serializing_if = "Option::is_none")] + track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + chain: Option, + }, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub enum TrackFxChain { + Normal, + Input, +} + +impl TrackFxChain { + pub fn is_input_fx(&self) -> bool { + matches!(self, Self::Input) + } +} + +impl Default for TrackFxChain { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum FxDisplayKind { + FloatingWindow, + Chain, +} + +impl Default for FxDisplayKind { + fn default() -> Self { + Self::FloatingWindow + } +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +pub struct FxSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub fx_kind: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub fx_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub preset_name: Option, + pub content: FxSnapshotContent, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum FxSnapshotContent { + Chunk { chunk: String }, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum FxParameterDescriptor { + Dynamic { + #[serde(skip_serializing_if = "Option::is_none")] + fx: Option, + expression: String, + }, + ById { + #[serde(skip_serializing_if = "Option::is_none")] + fx: Option, + index: u32, + }, + ByIndex { + #[serde(skip_serializing_if = "Option::is_none")] + fx: Option, + index: u32, + }, + ByName { + #[serde(skip_serializing_if = "Option::is_none")] + fx: Option, + name: String, + }, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum CompartmentParameterDescriptor { + ById { index: u32 }, +} + +#[derive(Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct RouteDescriptorCommons { + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "kind")] + pub route_kind: Option, +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum RouteDescriptor { + Dynamic { + #[serde(flatten)] + commons: RouteDescriptorCommons, + expression: String, + }, + ById { + #[serde(flatten)] + commons: RouteDescriptorCommons, + id: Option, + }, + ByIndex { + #[serde(flatten)] + commons: RouteDescriptorCommons, + index: u32, + }, + ByName { + #[serde(flatten)] + commons: RouteDescriptorCommons, + name: String, + }, +} + +#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TrackRouteKind { + Send, + Receive, + HardwareOutput, +} + +impl Default for TrackRouteKind { + fn default() -> Self { + Self::Send + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum PlaytimeSlotDescriptor { + Active, + ByIndex(playtime_api::persistence::SlotAddress), + Dynamic { + column_expression: String, + row_expression: String, + }, +} +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeSlotDescriptorKind { + #[default] + #[display(fmt = "Active")] + Active, + #[display(fmt = "At coordinates")] + ByIndex, + #[display(fmt = "Dynamic")] + Dynamic, +} + +impl Display for PlaytimeSlotDescriptor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PlaytimeSlotDescriptor::Active => f.write_str("Active slot"), + PlaytimeSlotDescriptor::ByIndex(address) => address.fmt(f), + PlaytimeSlotDescriptor::Dynamic { + column_expression, + row_expression, + } => { + f.write_str("Dynamic slot")?; + if f.alternate() { + write!(f, " (col = {column_expression}, row = {row_expression})")?; + } + Ok(()) + } + } + } +} + +impl Default for PlaytimeSlotDescriptor { + fn default() -> Self { + Self::Active + } +} + +impl PlaytimeSlotDescriptor { + pub fn from_kind(kind: PlaytimeSlotDescriptorKind) -> Self { + match kind { + PlaytimeSlotDescriptorKind::Active => Self::Active, + PlaytimeSlotDescriptorKind::ByIndex => Self::ByIndex(Default::default()), + PlaytimeSlotDescriptorKind::Dynamic => Self::Dynamic { + column_expression: "".to_string(), + row_expression: "".to_string(), + }, + } + } + + pub fn kind(&self) -> PlaytimeSlotDescriptorKind { + match self { + Self::Active => PlaytimeSlotDescriptorKind::Active, + Self::ByIndex(_) => PlaytimeSlotDescriptorKind::ByIndex, + Self::Dynamic { .. } => PlaytimeSlotDescriptorKind::Dynamic, + } + } + + pub fn fixed_address(&self) -> Option { + if let Self::ByIndex(address) = self { + Some(*address) + } else { + None + } + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum PlaytimeColumnDescriptor { + Active, + ByIndex(playtime_api::persistence::ColumnAddress), + Dynamic { expression: String }, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeColumnDescriptorKind { + #[default] + #[display(fmt = "Active")] + Active, + #[display(fmt = "At position")] + ByIndex, + #[display(fmt = "Dynamic")] + Dynamic, +} + +impl Default for PlaytimeColumnDescriptor { + fn default() -> Self { + Self::Active + } +} + +impl PlaytimeColumnDescriptor { + pub fn from_kind(kind: PlaytimeColumnDescriptorKind) -> Self { + match kind { + PlaytimeColumnDescriptorKind::Active => Self::Active, + PlaytimeColumnDescriptorKind::ByIndex => Self::ByIndex(Default::default()), + PlaytimeColumnDescriptorKind::Dynamic => Self::Dynamic { + expression: "".to_string(), + }, + } + } + + pub fn kind(&self) -> PlaytimeColumnDescriptorKind { + match self { + Self::Active => PlaytimeColumnDescriptorKind::Active, + Self::ByIndex(_) => PlaytimeColumnDescriptorKind::ByIndex, + Self::Dynamic { .. } => PlaytimeColumnDescriptorKind::Dynamic, + } + } + + pub fn fixed_address(&self) -> Option { + if let Self::ByIndex(address) = self { + Some(*address) + } else { + None + } + } +} + +impl Display for PlaytimeColumnDescriptor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PlaytimeColumnDescriptor::Active => f.write_str("Selected column"), + PlaytimeColumnDescriptor::ByIndex(address) => address.fmt(f), + PlaytimeColumnDescriptor::Dynamic { .. } => f.write_str("Dynamic column"), + } + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "address")] +pub enum PlaytimeRowDescriptor { + Active, + ByIndex(playtime_api::persistence::RowAddress), + Dynamic { expression: String }, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + strum::EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum PlaytimeRowDescriptorKind { + #[display(fmt = "Active")] + #[default] + Active, + #[display(fmt = "At position")] + ByIndex, + #[display(fmt = "Dynamic")] + Dynamic, +} + +impl Display for PlaytimeRowDescriptor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PlaytimeRowDescriptor::Active => f.write_str("Selected row"), + PlaytimeRowDescriptor::ByIndex(address) => address.fmt(f), + PlaytimeRowDescriptor::Dynamic { .. } => f.write_str("Dynamic row"), + } + } +} + +impl PlaytimeRowDescriptor { + pub fn from_kind(kind: PlaytimeRowDescriptorKind) -> Self { + match kind { + PlaytimeRowDescriptorKind::Active => Self::Active, + PlaytimeRowDescriptorKind::ByIndex => Self::ByIndex(Default::default()), + PlaytimeRowDescriptorKind::Dynamic => Self::Dynamic { + expression: "".to_string(), + }, + } + } + + pub fn kind(&self) -> PlaytimeRowDescriptorKind { + match self { + Self::Active => PlaytimeRowDescriptorKind::Active, + Self::ByIndex(_) => PlaytimeRowDescriptorKind::ByIndex, + Self::Dynamic { .. } => PlaytimeRowDescriptorKind::Dynamic, + } + } + + pub fn fixed_address(&self) -> Option { + if let Self::ByIndex(address) = self { + Some(*address) + } else { + None + } + } +} + +impl Default for PlaytimeRowDescriptor { + fn default() -> Self { + Self::Active + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum SendMidiDestination { + FxOutput, + FeedbackOutput, + InputDevice(InputDeviceMidiDestination), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct InputDeviceMidiDestination { + #[serde(skip_serializing_if = "Option::is_none")] + pub device_id: Option, +} + +impl Default for SendMidiDestination { + fn default() -> Self { + Self::FeedbackOutput + } +} + +#[derive(Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum OscDestination { + FeedbackOutput, + Device { id: String }, +} + +impl Default for OscDestination { + fn default() -> Self { + Self::FeedbackOutput + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[repr(usize)] +pub enum TrackScope { + AllTracks, + TracksVisibleInTcp, + TracksVisibleInMcp, +} + +impl Default for TrackScope { + fn default() -> Self { + Self::AllTracks + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + derive_more::Display, + strum::EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum BrowseTracksMode { + #[display(fmt = "All tracks")] + AllTracks, + #[display(fmt = "Only tracks visible in TCP")] + TracksVisibleInTcp, + #[display(fmt = "Only tracks visible in TCP (allow 2 selections)")] + TracksVisibleInTcpAllowTwoSelections, + #[display(fmt = "Only tracks visible in MCP")] + TracksVisibleInMcp, + #[display(fmt = "Only tracks visible in MCP (allow 2 selections)")] + TracksVisibleInMcpAllowTwoSelections, +} + +impl Default for BrowseTracksMode { + fn default() -> Self { + Self::AllTracks + } +} + +impl BrowseTracksMode { + pub fn scope(&self) -> TrackScope { + use BrowseTracksMode as M; + match self { + M::AllTracks => TrackScope::AllTracks, + M::TracksVisibleInTcp | M::TracksVisibleInTcpAllowTwoSelections => { + TrackScope::TracksVisibleInTcp + } + M::TracksVisibleInMcp | M::TracksVisibleInMcpAllowTwoSelections => { + TrackScope::TracksVisibleInMcp + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum TargetValue { + #[serde(alias = "Normalized")] + Unit { + value: f64, + }, + Discrete { + value: u32, + }, +} diff --git a/plugin-reaper-realearn/api/src/persistence/test/example.lua b/plugin-reaper-realearn/api/src/persistence/test/example.lua new file mode 100644 index 0000000..87f350f --- /dev/null +++ b/plugin-reaper-realearn/api/src/persistence/test/example.lua @@ -0,0 +1,30 @@ +return { + name = "Pedal to Delay", + source = { + kind = "MidiControlChangeValue", + channel = 0, + controller_number = 64, + character = "Button", + fourteen_bit = false, + }, + glue = { + target_interval = {0, 0.53}, + jump_interval = {0, 0.53}, + step_size_interval = {0.01, 0.01}, + step_factor_interval = {1, 1}, + }, + target = { + kind = "FxParameterValue", + parameter = { + address = "ById", + fx = { + address = "ById", + chain = { + address = "Track", + }, + id = "22FD4FC0-A4DD-4E6F-BCB3-38F242B557B2", + }, + index = 23, + }, + }, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/api/src/runtime/global_info_event.rs b/plugin-reaper-realearn/api/src/runtime/global_info_event.rs new file mode 100644 index 0000000..7c68127 --- /dev/null +++ b/plugin-reaper-realearn/api/src/runtime/global_info_event.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum GlobalInfoEvent { + Generic(GenericGlobalInfoEvent), + AutoAddedController(AutoAddedControllerEvent), + PlaytimeActivationSucceeded, + PlaytimeActivationFailed, +} + +impl GlobalInfoEvent { + pub fn generic(message: impl Into) -> Self { + Self::Generic(GenericGlobalInfoEvent { + message: message.into(), + }) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct GenericGlobalInfoEvent { + pub message: String, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct AutoAddedControllerEvent { + pub controller_id: String, +} diff --git a/plugin-reaper-realearn/api/src/runtime/instance_info_event.rs b/plugin-reaper-realearn/api/src/runtime/instance_info_event.rs new file mode 100644 index 0000000..a14c9ef --- /dev/null +++ b/plugin-reaper-realearn/api/src/runtime/instance_info_event.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum InstanceInfoEvent { + Generic(GenericInstanceInfoEvent), + /// If attempting to MIDI-learn but the track is either not armed or the input monitoring mode + /// is not suitable. + MidiLearnFromFxInputButTrackNotArmed, + MidiLearnFromFxInputButTrackHasAudioInput, +} + +impl InstanceInfoEvent { + pub fn generic(message: impl Into) -> Self { + Self::Generic(GenericInstanceInfoEvent { + message: message.into(), + }) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct GenericInstanceInfoEvent { + pub message: String, +} diff --git a/plugin-reaper-realearn/api/src/runtime/licensing.rs b/plugin-reaper-realearn/api/src/runtime/licensing.rs new file mode 100644 index 0000000..620c9b5 --- /dev/null +++ b/plugin-reaper-realearn/api/src/runtime/licensing.rs @@ -0,0 +1,13 @@ +use helgoboss_license_api::persistence::LicenseData; +use serde::Serialize; + +#[derive(Clone, Eq, PartialEq, Debug, Serialize)] +pub struct LicenseInfo { + pub licenses: Vec, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize)] +pub struct ValidatedLicense { + pub license: LicenseData, + pub valid: bool, +} diff --git a/plugin-reaper-realearn/api/src/runtime/mod.rs b/plugin-reaper-realearn/api/src/runtime/mod.rs new file mode 100644 index 0000000..ddf4288 --- /dev/null +++ b/plugin-reaper-realearn/api/src/runtime/mod.rs @@ -0,0 +1,14 @@ +mod preset; +pub use preset::*; + +mod global_info_event; +pub use global_info_event::*; + +mod instance_info_event; +pub use instance_info_event::*; + +mod reaper; +pub use reaper::*; + +mod licensing; +pub use licensing::*; diff --git a/plugin-reaper-realearn/api/src/runtime/preset.rs b/plugin-reaper-realearn/api/src/runtime/preset.rs new file mode 100644 index 0000000..9cdcdaa --- /dev/null +++ b/plugin-reaper-realearn/api/src/runtime/preset.rs @@ -0,0 +1,16 @@ +use crate::persistence::{CommonPresetMetaData, ControllerPresetMetaData, MainPresetMetaData}; +use serde::Serialize; + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize)] +pub struct MainPreset { + pub id: String, + pub common: CommonPresetMetaData, + pub specific: MainPresetMetaData, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize)] +pub struct ControllerPreset { + pub id: String, + pub common: CommonPresetMetaData, + pub specific: ControllerPresetMetaData, +} diff --git a/plugin-reaper-realearn/api/src/runtime/reaper.rs b/plugin-reaper-realearn/api/src/runtime/reaper.rs new file mode 100644 index 0000000..0ee025d --- /dev/null +++ b/plugin-reaper-realearn/api/src/runtime/reaper.rs @@ -0,0 +1,14 @@ +#![allow(non_snake_case)] +use helgobox_macros::reaper_api; + +reaper_api![ + HelgoboxApi, HelgoboxApiPointers, HelgoboxApiSession, register_helgobox_api + { + /// Finds the first Helgobox instance in the given project. + /// + /// If the given project is `null`, it will look in the current project. + /// + /// Returns the instance ID or -1 if none exists. + HB_FindFirstHelgoboxInstanceInProject(project: *mut reaper_low::raw::ReaProject) -> std::ffi::c_int; + } +]; diff --git a/plugin-reaper-realearn/api/src/util.rs b/plugin-reaper-realearn/api/src/util.rs new file mode 100644 index 0000000..65d6b69 --- /dev/null +++ b/plugin-reaper-realearn/api/src/util.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Deserializer}; + +/// Makes sure that JSON `null` is treated the same as omitting a property. +/// +/// Use as `#[serde(deserialize_with = "deserialize_null_default")]`. +/// +/// See https://github.com/serde-rs/serde/issues/1098#issuecomment-760711617. +pub fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result +where + T: Default + Deserialize<'de>, + D: Deserializer<'de>, +{ + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} diff --git a/plugin-reaper-realearn/artwork-processor/Cargo.toml b/plugin-reaper-realearn/artwork-processor/Cargo.toml new file mode 100644 index 0000000..7b20d01 --- /dev/null +++ b/plugin-reaper-realearn/artwork-processor/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "helgobox-artwork-processor" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +resvg = { workspace = true, features = ["system-fonts"] } +anyhow.workspace = true + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/artwork-processor/src/main.rs b/plugin-reaper-realearn/artwork-processor/src/main.rs new file mode 100644 index 0000000..aa736ae --- /dev/null +++ b/plugin-reaper-realearn/artwork-processor/src/main.rs @@ -0,0 +1,130 @@ +use anyhow::{Context, Result}; +use resvg::tiny_skia::{Pixmap, PremultipliedColorU8, Transform}; +use resvg::usvg; +use resvg::usvg::Options; +use std::fs; +use std::path::Path; +use std::sync::Arc; + +fn main() -> Result<()> { + render_artwork()?; + println!("Finished rendering artwork"); + Ok(()) +} + +fn render_artwork() -> Result<()> { + let logo_file = "resources/artwork/playtime-logo.svg"; + let logo_svg = fs::read_to_string(logo_file)?; + generate_icons("playtime", &logo_svg, "")?; + generate_icons("playtime-custom", &logo_svg, "with-settings-icon")?; + let logo_svg_with_text_2 = logo_svg.replace("TEXT_PLACEHOLDER", "2"); + generate_icons("playtime-2", &logo_svg_with_text_2, "with-text-badge")?; + Ok(()) +} + +fn generate_icons(name_with_dashes: &str, svg: &str, additional_root_classes: &str) -> Result<()> { + let name_with_underscores = name_with_dashes.replace('-', "_"); + // Toolbar icons + generate_toolbar_icons(&name_with_underscores, svg, additional_root_classes)?; + // Icons for docs + generate_icon( + svg, + format!("doc/playtime/modules/ROOT/images/screenshots/{name_with_dashes}-toolbar-icon.png"), + (120, 120), + additional_root_classes, + &[ToolbarIconStatus::Normal], + )?; + Ok(()) +} + +fn generate_toolbar_icons(name: &str, svg: &str, additional_root_classes: &str) -> Result<()> { + use ToolbarIconStatus as S; + let toolbar_statuses = [S::Normal, S::Hovered, S::Selected]; + generate_icon( + svg, + format!("resources/artwork/toolbar_icons/toolbar_{name}.png"), + (30, 30), + additional_root_classes, + &toolbar_statuses, + )?; + generate_icon( + svg, + format!("resources/artwork/toolbar_icons/150/toolbar_{name}.png"), + (45, 45), + additional_root_classes, + &toolbar_statuses, + )?; + generate_icon( + svg, + format!("resources/artwork/toolbar_icons/200/toolbar_{name}.png"), + (60, 60), + additional_root_classes, + &toolbar_statuses, + )?; + Ok(()) +} + +fn generate_icon( + svg: &str, + dst_file: impl AsRef, + (width, height): (u32, u32), + additional_root_classes: &str, + statuses: &[ToolbarIconStatus], +) -> Result<()> { + let dst_file = dst_file.as_ref(); + let pixmap = render_toolbar_icon(svg, (width, height), additional_root_classes, statuses)?; + fs::create_dir_all(dst_file.parent().context("no parent file")?)?; + pixmap.save_png(dst_file)?; + Ok(()) +} + +fn render_toolbar_icon( + svg: &str, + (width, height): (u32, u32), + additional_root_classes: &str, + statuses: &[ToolbarIconStatus], +) -> Result { + let sprite_count = statuses.len() as u32; + let mut pixmap = Pixmap::new(width * sprite_count, height).unwrap(); + use ToolbarIconStatus as S; + for (i, status) in statuses.iter().enumerate() { + let fg_color = match status { + S::Normal => "#818989", + S::Hovered => "#939a9a", + S::Selected => "#1abc98", + }; + let interpolated_svg = svg + .replace( + "ROOT_CLASSES_PLACEHOLDER", + &format!("toolbar-icon {additional_root_classes}"), + ) + .replace("var(--fg-color)", fg_color) + .replace("var(--bg-color", "#333333"); + let mut options = Options::default(); + let mut font_db = usvg::fontdb::Database::new(); + font_db.load_fonts_dir("resources/artwork/fonts"); + options.fontdb = Arc::new(font_db); + let tree = usvg::Tree::from_str(&interpolated_svg, &options)?; + // Render sprite + let transform = Transform::from_scale( + width as f32 / tree.size().width(), + height as f32 / tree.size().height(), + ) + .post_translate(i as f32 * width as f32, 0.0); + resvg::render(&tree, transform, &mut pixmap.as_mut()); + } + // Replace "shine-through" color with transparency + let shine_through_color = PremultipliedColorU8::from_rgba(255, 0, 255, 255).unwrap(); // Magenta + for pixel in pixmap.pixels_mut() { + if *pixel == shine_through_color { + *pixel = PremultipliedColorU8::from_rgba(0, 0, 0, 0).unwrap(); // Transparent + } + } + Ok(pixmap) +} + +enum ToolbarIconStatus { + Normal, + Hovered, + Selected, +} diff --git a/plugin-reaper-realearn/base/Cargo.toml b/plugin-reaper-realearn/base/Cargo.toml new file mode 100644 index 0000000..3066f88 --- /dev/null +++ b/plugin-reaper-realearn/base/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "base" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +# Own +reaper-high.workspace = true +reaper-medium.workspace = true +reaper-low.workspace = true +reaper-rx.workspace = true +helgobox-api.workspace = true + +# 3rd-party +serde.workspace = true +serde_json.workspace = true +xxhash-rust.workspace = true +crossbeam-channel.workspace = true +futures-timer.workspace = true +once_cell.workspace = true +tracing.workspace = true +metrics.workspace = true +ascii.workspace = true +enigo.workspace = true +# For getting current mouse state +device_query.workspace = true +derive_more.workspace = true +either.workspace = true +logos.workspace = true +anyhow.workspace = true +thiserror.workspace = true +camino.workspace = true +indexmap.workspace = true +futures.workspace = true +tokio.workspace = true +fragile.workspace = true + +[target.'cfg(target_os = "macos")'.dependencies] +# For not letting device_query panic when macOS accessibility permissions not granted +macos-accessibility-client.workspace = true + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/base/src/approx_f64.rs b/plugin-reaper-realearn/base/src/approx_f64.rs new file mode 100644 index 0000000..4a58a08 --- /dev/null +++ b/plugin-reaper-realearn/base/src/approx_f64.rs @@ -0,0 +1,59 @@ +use std::cmp::Ordering; +use std::fmt::{Display, Formatter}; + +/// An approximate floating-point type that uses the same epsilon for comparison as the == operator in +/// [EEL2](https://www.cockos.com/EEL2/): +/// Two values are considered equal if the difference is less than 0.00001 (1/100000), 0 if not. +pub type AudioF64 = ApproxF64<100000>; + +/// Simple newtype that allows for approximate comparison of 64-bit floating-point numbers. +/// +/// The const type parameter `E` ("epsilon") defines how tolerant floating-point comparison is. Two values are considered +/// equal if the difference is less than 1/E. +#[derive(Copy, Clone, Debug, Default)] +pub struct ApproxF64(pub f64); + +impl ApproxF64 { + const EPSILON: f64 = 1.0 / E as f64; + + pub fn new(raw: f64) -> Self { + Self(raw) + } + + fn difference_is_neglectable(&self, other: &Self) -> bool { + (self.0 - other.0).abs() < Self::EPSILON + } +} + +impl PartialOrd for ApproxF64 { + fn partial_cmp(&self, other: &Self) -> Option { + if self.difference_is_neglectable(other) { + return Some(Ordering::Equal); + } + self.0.partial_cmp(&other.0) + } +} + +impl PartialEq for ApproxF64 { + fn eq(&self, other: &Self) -> bool { + self.difference_is_neglectable(other) + } +} + +impl Display for ApproxF64 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basics() { + assert_eq!(AudioF64::new(0.75), AudioF64::new(0.75)); + assert_ne!(AudioF64::new(0.00001), AudioF64::new(0.00002)); + assert_eq!(AudioF64::new(0.000001), AudioF64::new(0.000002)); + } +} diff --git a/plugin-reaper-realearn/base/src/byte_pattern.rs b/plugin-reaper-realearn/base/src/byte_pattern.rs new file mode 100644 index 0000000..c478161 --- /dev/null +++ b/plugin-reaper-realearn/base/src/byte_pattern.rs @@ -0,0 +1,114 @@ +//! We have a raw MIDI pattern in helgoboss-learn already (raw MIDI source), however this is more +//! complicated than this one as it also allows single bits to be variable. + +use logos::{Lexer, Logos}; +use std::num::ParseIntError; +use std::str::FromStr; + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct BytePattern { + bytes: Vec, +} + +impl BytePattern { + pub const fn new(bytes: Vec) -> Self { + Self { bytes } + } + + pub fn matches(&self, bytes: &[u8]) -> bool { + use PatternByte as B; + let mut byte_iter = bytes.iter(); + let mut last_was_multi = false; + for pattern_byte in &self.bytes { + let matches = match pattern_byte { + B::Fixed(expected_byte) => { + if last_was_multi { + // Last pattern byte was multi + last_was_multi = false; + // Greedily consume any follow-up actual bytes until we meet the expected + // byte. If we don't meet it, no match! + byte_iter.any(|b| b == expected_byte) + } else { + // Last pattern byte was single or fixed + byte_iter.next().is_some_and(|b| b == expected_byte) + } + } + B::Single => { + last_was_multi = false; + // We need to have an actual byte but it doesn't matter which one! + byte_iter.next().is_some() + } + B::Multi => { + last_was_multi = true; + // Match even if no actual byte left! + true + } + }; + if !matches { + return false; + } + } + byte_iter.next().is_none() || last_was_multi + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Logos)] +#[logos(skip r"[ \t\n\f]+")] +#[logos(error = ParseBytePatternError)] +pub enum PatternByte { + #[regex(r"[0-9a-fA-F][0-9a-fA-F]?", parse_as_byte)] + Fixed(u8), + #[token("?")] + Single, + #[token("*")] + Multi, +} + +#[derive(Clone, PartialEq, Debug, Default, thiserror::Error)] +#[error("{msg}")] +pub struct ParseBytePatternError { + msg: &'static str, +} + +impl From for ParseBytePatternError { + fn from(_: ParseIntError) -> Self { + Self { + msg: "problem parsing fixed byte", + } + } +} + +fn parse_as_byte(lex: &mut Lexer) -> Result { + u8::from_str_radix(lex.slice(), 16) +} + +impl FromStr for BytePattern { + type Err = ParseBytePatternError; + + fn from_str(s: &str) -> Result { + let lex: Lexer = PatternByte::lexer(s); + let entries: Result, _> = lex.collect(); + Ok(BytePattern::new(entries?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basics() { + // Given + let pattern: BytePattern = "F0 7E ? 06 02 * F7".parse().unwrap(); + // When + assert!(!pattern.matches(&[])); + assert!(!pattern.matches(&[0xF0])); + assert!(!pattern.matches(&[0xF0, 0x7E])); + assert!(!pattern.matches(&[0xF0, 0x7E, 0x00])); + assert!(!pattern.matches(&[0xF0, 0x7E, 0x00, 0x06])); + assert!(pattern.matches(&[0xF0, 0x7E, 0x00, 0x06, 0x02, 0xF7])); + assert!(pattern.matches(&[0xF0, 0x7E, 0x01, 0x06, 0x02, 0xF7])); + assert!(pattern.matches(&[0xF0, 0x7E, 0xFF, 0x06, 0x02, 0xFF, 0x60, 0xF7])); + assert!(!pattern.matches(&[0xF0, 0x7E, 0xFF, 0x06, 0x02, 0xFF, 0x60, 0xF7, 0xF7])); + } +} diff --git a/plugin-reaper-realearn/base/src/channels.rs b/plugin-reaper-realearn/base/src/channels.rs new file mode 100644 index 0000000..07c8587 --- /dev/null +++ b/plugin-reaper-realearn/base/src/channels.rs @@ -0,0 +1,376 @@ +use crossbeam_channel::{Receiver, Sender, TryRecvError, TrySendError}; +use reaper_high::Reaper; +use std::error::Error; +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; +use std::sync::atomic::{AtomicBool, Ordering}; + +pub trait NamedChannelSender { + type Msg; + + /// Sends the given message if the channel still has space and the receiver is still + /// connected, otherwise does nothing. + fn send_if_space(&self, msg: Self::Msg); + + /// Sends the given message if the channel still has space, otherwise panics. + /// + /// If the receiver is disconnected, does nothing. + fn send_complaining(&self, msg: Self::Msg); +} + +/// A channel intended to send important messages from a real-time thread to a normal (non-real-time) thread. +/// +/// The way this currently works is that it uses 2 senders: One that has an initial capacity and is normally used. +/// And another one that is unbounded (and can therefore allocate) that is only used if the initial one is full. +/// +/// TODO-medium Find a channel library that allows pre-allocated unbounded channels (with a high initial capacity). +pub struct ImportantSenderFromRtToNormalThread { + channel_name: &'static str, + bounded_normal_sender: Sender, + unbounded_emergency_sender: Sender, +} + +impl ImportantSenderFromRtToNormalThread { + pub fn new( + channel_name: &'static str, + capacity: usize, + ) -> (Self, ImportantReceiverFromRtToNormalThread) { + // Main sender should belong to a bounded channel pre-allocated for normal usage. + // + // Emergency sender should belong to an unbounded channel and is only used if sending to the main channel would + // block because it's full. Better allocate than block or discard the event. + let (bounded_normal_sender, bounded_normal_receiver) = crossbeam_channel::bounded(capacity); + let (unbounded_emergency_sender, unbounded_emergency_receiver) = + crossbeam_channel::unbounded(); + ( + ImportantSenderFromRtToNormalThread { + channel_name, + bounded_normal_sender, + unbounded_emergency_sender, + }, + ImportantReceiverFromRtToNormalThread { + channel_name, + bounded_normal_receiver, + unbounded_emergency_receiver, + }, + ) + } +} + +impl ImportantSenderFromRtToNormalThread { + /// Returns `false` if receiver gone. + pub fn send(&self, msg: T) -> bool { + if let Err(e) = self.bounded_normal_sender.try_send(msg) { + match e { + TrySendError::Full(msg) => { + tracing::warn!( + msg = "Main sequence channel was full, using emergency channel (may allocate)!", + %self.channel_name, + ); + let _ = self.unbounded_emergency_sender.send(msg); + true + } + TrySendError::Disconnected(_) => false, + } + } else { + true + } + } +} + +impl Clone for ImportantSenderFromRtToNormalThread { + fn clone(&self) -> Self { + Self { + channel_name: self.channel_name, + bounded_normal_sender: self.bounded_normal_sender.clone(), + unbounded_emergency_sender: self.unbounded_emergency_sender.clone(), + } + } +} + +pub struct ImportantReceiverFromRtToNormalThread { + channel_name: &'static str, + bounded_normal_receiver: Receiver, + unbounded_emergency_receiver: Receiver, +} + +impl ImportantReceiverFromRtToNormalThread { + pub fn try_recv(&self) -> Result { + self.bounded_normal_receiver + .try_recv() + .or_else(|_| self.unbounded_emergency_receiver.try_recv()) + } +} + +impl Debug for ImportantReceiverFromRtToNormalThread { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("ImportantReceiverFromRtToNormalThread") + .field("channel_name", &self.channel_name) + .field("bounded_normal_receiver", &self.bounded_normal_receiver) + .field( + "unbounded_emergency_receiver", + &self.unbounded_emergency_receiver, + ) + .finish() + } +} + +impl Debug for ImportantSenderFromRtToNormalThread { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("ImportantSenderFromRtToNormalThread") + .field("channel_name", &self.channel_name) + .field("bounded_normal_sender", &self.bounded_normal_sender) + .field( + "unbounded_emergency_sender", + &self.unbounded_emergency_sender, + ) + .finish() + } +} + +/// A channel intended to send messages to a normal (non-real-time) thread. +/// +/// - Either unbounded (should only be used if the sender is also a normal thread). +/// - Or bounded (can also be used if the sender is a real-time thread). +/// +/// If you need an unbounded one that is okay to use from a real-time thread, look into +/// [`ImportantSenderFromRtToNormalThread`]. +pub struct SenderToNormalThread { + channel_name: &'static str, + sender: Sender, + complained_already: AtomicBool, +} + +impl Debug for SenderToNormalThread { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("SenderToNormalThread") + .field("channel_name", &self.channel_name) + .field("sender", &self.sender) + .finish() + } +} + +impl NamedChannelSender for SenderToNormalThread { + type Msg = T; + + fn send_if_space(&self, msg: T) { + let _ = self.send_internal(msg); + } + + fn send_complaining(&self, msg: T) { + let result = self.send_internal(msg); + if !receiver_is_disconnected(&result) + && !self.complained_already.swap(true, Ordering::Relaxed) + { + // Complain + result.unwrap(); + } + } +} + +fn receiver_is_disconnected(result: &Result<(), NamedChannelTrySendError>) -> bool { + if let Err(e) = &result { + matches!(e.try_send_error, TrySendError::Disconnected(_)) + } else { + false + } +} + +impl SenderToNormalThread { + /// Creates a bounded channel. + /// + /// - **Pro:** Never allocates when sending and is therefore safe to use from real-time threads. + /// - **Con:** We can get "channel full" errors on load spikes if the capacity is not high + /// enough. Choosing an extremely high capacity to avoid this is not a good idea either + /// because it consumes memory that's almost never going to be used. + pub fn new_bounded_channel(name: &'static str, capacity: usize) -> (Self, Receiver) { + let (sender, receiver) = crossbeam_channel::bounded(capacity); + ( + Self { + channel_name: name, + sender, + complained_already: AtomicBool::new(false), + }, + receiver, + ) + } + + /// Creates an unbounded channel. + /// + /// - **Pro:** We don't get "channel full" errors on load spikes. + /// - **Con:** This can allocate when sending, so don't use this if the sender is used in + /// real-time threads! If you still do so, it will complain in debug mode because we forbid + /// allocation in real-time threads. + /// + /// We set a (very high) upper limit even for unbounded channels just to avoid memory exhaustion + /// if the channel grows endlessly because of another error. This limit is not ensured by + /// pre-allocating the channel with a certain capacity but by checking the current number + /// of messages in the channel before sending. + pub fn new_unbounded_channel(name: &'static str) -> (Self, Receiver) { + let (sender, receiver) = crossbeam_channel::unbounded(); + ( + Self { + channel_name: name, + sender, + complained_already: AtomicBool::new(false), + }, + receiver, + ) + } + + pub fn try_to_send(&self, msg: T) -> bool { + self.sender.try_send(msg).is_ok() + } + + pub fn is_bounded(&self) -> bool { + self.sender.capacity().is_some() + } + + fn send_internal(&self, msg: T) -> Result<(), NamedChannelTrySendError> { + if !self.is_bounded() { + // The channel is not bounded but we still want to panic if the number of messages + // in the channel is extremely high, to prevent memory exhaustion. + let msg_count = self.sender.len(); + if msg_count > 1_000_000 { + panic!( + "Unbounded channel {} is extremely full ({} messages). \ + Not accepting new messages in order to prevent memory exhaustion.", + self.channel_name, msg_count + ); + } + } + try_send_on_named_channel(&self.sender, self.channel_name, msg) + } +} + +impl Clone for SenderToNormalThread { + fn clone(&self) -> Self { + Self { + channel_name: self.channel_name, + sender: self.sender.clone(), + complained_already: AtomicBool::new(false), + } + } +} + +/// A channel intended to send messages to real-time threads. +/// +/// It has special logic which makes sure the queue doesn't run full when audio is not running. +#[derive(Debug)] +pub struct SenderToRealTimeThread { + channel_name: &'static str, + sender: Sender, + complained_already: AtomicBool, +} + +impl Clone for SenderToRealTimeThread { + fn clone(&self) -> Self { + Self { + channel_name: self.channel_name, + sender: self.sender.clone(), + complained_already: AtomicBool::new(false), + } + } +} + +impl NamedChannelSender for SenderToRealTimeThread { + type Msg = T; + + fn send_if_space(&self, msg: T) { + let _ = self.send_internal(msg); + } + + fn send_complaining(&self, msg: T) { + let result = self.send_internal(msg); + if !receiver_is_disconnected(&result) + && !self.complained_already.swap(true, Ordering::Relaxed) + { + // Complain + result.unwrap(); + } + } +} + +impl SenderToRealTimeThread { + pub fn new_channel(name: &'static str, capacity: usize) -> (Self, Receiver) { + let (sender, receiver) = crossbeam_channel::bounded(capacity); + ( + Self { + channel_name: name, + sender, + complained_already: AtomicBool::new(false), + }, + receiver, + ) + } + + fn send_internal(&self, msg: T) -> Result<(), NamedChannelTrySendError> { + if Reaper::get().audio_is_running() { + // Audio is running so sending should always work. If not, it's an unexpected error and + // we must return it. + try_send_on_named_channel(&self.sender, self.channel_name, msg) + } else { + // Audio is not running. Maybe this is just a very temporary outage or a short initial + // non-running state. + if self.channel_still_has_some_headroom() { + // Channel still has some headroom, so we send the task in order to support a + // temporary outage. This should not fail unless another sender has exhausted the + // channel in the meanwhile. Even then, so what. See "else" branch. + let _ = self.sender.try_send(msg); + Ok(()) + } else { + // Channel has already accumulated lots of tasks. Don't send! + // It's not bad if we don't send this task because the real-time processor will + // not be able to process it anyway at the moment (it's not going to be called + // because the audio engine is stopped). Fear not, ReaLearn's audio hook has logic + // that detects a "rebirth" - the moment when the audio cycle starts again. In this + // case it will request a full resync of everything so nothing should get lost + // in theory. + Ok(()) + } + } + } + + fn channel_still_has_some_headroom(&self) -> bool { + self.sender.len() <= self.sender.capacity().unwrap() / 2 + } +} + +fn try_send_on_named_channel( + sender: &Sender, + channel_name: &'static str, + msg: T, +) -> Result<(), NamedChannelTrySendError> { + sender.try_send(msg).map_err(|e| NamedChannelTrySendError { + channel_name, + try_send_error: e, + }) +} + +#[derive(Copy, Clone, Eq, PartialEq)] +struct NamedChannelTrySendError { + channel_name: &'static str, + try_send_error: TrySendError, +} + +impl Debug for NamedChannelTrySendError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "Channel [{}]: {:?}", + self.channel_name, self.try_send_error + ) + } +} + +impl Display for NamedChannelTrySendError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "Channel [{}]: {}", + self.channel_name, self.try_send_error + ) + } +} + +impl Error for NamedChannelTrySendError {} diff --git a/plugin-reaper-realearn/base/src/default_util.rs b/plugin-reaper-realearn/base/src/default_util.rs new file mode 100644 index 0000000..728b754 --- /dev/null +++ b/plugin-reaper-realearn/base/src/default_util.rs @@ -0,0 +1,38 @@ +use serde::{Deserialize, Deserializer}; + +pub fn is_default(v: &T) -> bool { + v == &T::default() +} + +pub fn bool_true() -> bool { + true +} + +pub fn is_bool_true(v: &bool) -> bool { + *v +} + +/// Should only be used when the deserialization checks the data version number because only that +/// way it can check if `None` represents the old default or the new one! (That is, if there's +/// even a difference between `None` and `Some(default())`, otherwise it doesn't matter). +pub fn is_none_or_some_default(v: &Option) -> bool { + if let Some(i) = v { + i == &T::default() + } else { + true + } +} + +/// Makes sure that JSON `null` is treated the same as omitting a property. +/// +/// Use as `#[serde(deserialize_with = "deserialize_null_default")]`. +/// +/// See https://github.com/serde-rs/serde/issues/1098#issuecomment-760711617. +pub fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result +where + T: Default + Deserialize<'de>, + D: Deserializer<'de>, +{ + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} diff --git a/plugin-reaper-realearn/base/src/file_util.rs b/plugin-reaper-realearn/base/src/file_util.rs new file mode 100644 index 0000000..378a946 --- /dev/null +++ b/plugin-reaper-realearn/base/src/file_util.rs @@ -0,0 +1,36 @@ +use crate::hash_util::PersistentHash; +use std::ffi::OsStr; + +pub fn is_hidden(file_name: &OsStr) -> bool { + file_name + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) +} + +/// Converts a persistent hash number to something like +/// "a9/4a/8fe5ccb19ba61c4c0873d391e987.RfxChain" for the purpose to not get too many +/// files in one directory. +pub fn convert_hash_to_dir_structure(hash: PersistentHash, suffix: &str) -> String { + let hash = hash.get(); + let first_byte = hash.rotate_left(8) & 0xff; + let second_byte = hash.rotate_left(16) & 0xff; + // Remaining: 112 bits = 14 bytes = 28 hex chars + let remaining = hash & 0xffffffffffffffffffffffffffff; + format!("{first_byte:02x}/{second_byte:02x}/{remaining:028x}{suffix}") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash_util; + + #[test] + fn hash_to_dir_structure_simple() { + let hash = hash_util::calculate_persistent_non_crypto_hash_one_shot("test".as_bytes()); + assert_eq!( + convert_hash_to_dir_structure(hash, ".RfxChain"), + "6c/78/e0e3bd51d358d01e758642b85fb8.RfxChain".to_string() + ); + } +} diff --git a/plugin-reaper-realearn/base/src/future_util.rs b/plugin-reaper-realearn/base/src/future_util.rs new file mode 100644 index 0000000..fcc29ed --- /dev/null +++ b/plugin-reaper-realearn/base/src/future_util.rs @@ -0,0 +1,5 @@ +use std::time::Duration; + +pub async fn millis(amount: u64) { + futures_timer::Delay::new(Duration::from_millis(amount)).await; +} diff --git a/plugin-reaper-realearn/base/src/global.rs b/plugin-reaper-realearn/base/src/global.rs new file mode 100644 index 0000000..958f2c0 --- /dev/null +++ b/plugin-reaper-realearn/base/src/global.rs @@ -0,0 +1,123 @@ +use crossbeam_channel::{Receiver, Sender}; +use fragile::Fragile; +use reaper_high::{ + FutureMiddleware, FutureSupport, MainTaskMiddleware, MainThreadTask, TaskSupport, + DEFAULT_MAIN_THREAD_TASK_BULK_SIZE, +}; +use reaper_rx::{ActionRx, ActionRxProvider, ControlSurfaceRx, MainRx}; +use std::sync::LazyLock; + +static INSTANCE: LazyLock = LazyLock::new(Global::default); + +/// Spawns the given future in the main thread. +/// +/// This only works if the future support is already running (= if the backbone shell is already woken up). +pub fn spawn_in_main_thread( + future: impl std::future::Future>> + 'static, +) { + Global::future_support().spawn_in_main_thread_from_main_thread(future); +} + +pub struct Global { + main_rx: Fragile, + task_support: TaskSupport, + future_support: FutureSupport, + task_sender: Sender, + task_receiver: Receiver, + send_future_executor: reaper_high::run_loop_executor::RunLoopExecutor, + non_send_future_executor: reaper_high::local_run_loop_executor::RunLoopExecutor, +} + +impl Default for Global { + fn default() -> Self { + // It's important that all of the below channels are unbounded. It's not just that they + // can run full and then panic, it's worse. If sending and receiving happens on the same + // thread (which we use quite often in order to schedule/spawn something on the main + // thread) and the channel is full, we will get a deadlock! It's okay that they allocate + // on sending because `Global` can't be used from a real-time thread. + // See https://github.com/helgoboss/helgobox/issues/875. + let (task_sender, task_receiver) = crossbeam_channel::unbounded(); + let (send_future_spawner, send_future_executor) = + reaper_high::run_loop_executor::new_spawner_and_executor( + DEFAULT_MAIN_THREAD_TASK_BULK_SIZE, + ); + let (non_send_future_spawner, non_send_future_executor) = + reaper_high::local_run_loop_executor::new_spawner_and_executor( + DEFAULT_MAIN_THREAD_TASK_BULK_SIZE, + ); + Self { + main_rx: Default::default(), + task_support: TaskSupport::new(task_sender.clone()), + future_support: FutureSupport::new(send_future_spawner, non_send_future_spawner), + task_sender, + task_receiver, + send_future_executor, + non_send_future_executor, + } + } +} + +impl Global { + pub fn get() -> &'static Self { + assert!( + !reaper_high::Reaper::get() + .medium_reaper() + .is_in_real_time_audio(), + "this function must not be called in a real-time thread" + ); + &INSTANCE + } + + // This is kept static just for allowing easy observable subscription from everywhere. For + // pushing to the subjects, static access is not necessary. + + // Don't use from real-time thread! + pub fn control_surface_rx() -> &'static ControlSurfaceRx { + Global::get().main_rx.get().control_surface() + } + + // This really needs to be kept static for pushing to the subjects because hook commands can't + // take user data. + // + // Don't use from real-time thread! + pub fn action_rx() -> &'static ActionRx { + Global::get().main_rx.get().action() + } + + /// Allows you to schedule tasks for execution on the main thread from anywhere. + /// + /// Important: Don't use this to schedule tasks from a real-time thread! This is backed by an + /// unbounded channel now because of https://github.com/helgoboss/helgobox/issues/875, so + /// sending can allocate! + pub fn task_support() -> &'static TaskSupport { + &Global::get().task_support + } + + /// Allows you to spawn futures from anywhere. + /// + /// Important: Don't use this to spawn futures from a real-time thread! This is backed by an + /// unbounded channel now because of https://github.com/helgoboss/helgobox/issues/875, so + /// sending can allocate! + pub fn future_support() -> &'static FutureSupport { + &Global::get().future_support + } + + /// Creates the middleware that drives the task support. + pub fn create_task_support_middleware(&self) -> MainTaskMiddleware { + MainTaskMiddleware::new(self.task_sender.clone(), self.task_receiver.clone()) + } + + /// Creates the middleware that drives the future support. + pub fn create_future_support_middleware(&self) -> FutureMiddleware { + FutureMiddleware::new( + self.send_future_executor.clone(), + self.non_send_future_executor.clone(), + ) + } +} + +impl ActionRxProvider for Global { + fn action_rx() -> &'static ActionRx { + Global::action_rx() + } +} diff --git a/plugin-reaper-realearn/base/src/global_macros.rs b/plugin-reaper-realearn/base/src/global_macros.rs new file mode 100644 index 0000000..a5111a9 --- /dev/null +++ b/plugin-reaper-realearn/base/src/global_macros.rs @@ -0,0 +1,30 @@ +/// Use only where absolutely necessary because of static-only FFI stuff! +#[macro_export] +macro_rules! make_available_globally_in_main_thread_on_demand { + ($instance_struct:path) => { + static INSTANCE: std::sync::OnceLock> = + std::sync::OnceLock::new(); + + impl $instance_struct { + pub fn make_available_globally(create_instance: impl FnOnce() -> $instance_struct) { + if INSTANCE.get().is_some() { + return; + } + let _ = INSTANCE.set(fragile::Fragile::new(create_instance())); + } + + /// Whether this instance is (already/still) loaded. + pub fn is_loaded() -> bool { + INSTANCE.get().is_some() + } + + /// Panics if not in main thread. + pub fn get() -> &'static $instance_struct { + INSTANCE + .get() + .expect("call `make_available_globally()` before using `get()`") + .get() + } + } + }; +} diff --git a/plugin-reaper-realearn/base/src/hash_util.rs b/plugin-reaper-realearn/base/src/hash_util.rs new file mode 100644 index 0000000..d620719 --- /dev/null +++ b/plugin-reaper-realearn/base/src/hash_util.rs @@ -0,0 +1,117 @@ +use indexmap::{IndexMap, IndexSet}; +use std::collections::{HashMap, HashSet}; +use std::hash::{BuildHasher, Hash, Hasher}; +use xxhash_rust::xxh3::{Xxh3Default, Xxh3DefaultBuilder}; + +/// The default choice for hashing in Helgobox. +pub type NonCryptoHashBuilder = Xxh3DefaultBuilder; + +/// The default choice for hashing in Helgobox. +pub type NonCryptoHasher = Xxh3Default; + +/// The default choice for hash maps in Helgobox. +pub type NonCryptoHashMap = HashMap; + +/// The default choice for hash sets in Helgobox. +pub type NonCryptoHashSet = HashSet; + +/// The default choice for index maps in Helgobox. +pub type NonCryptoIndexMap = IndexMap; + +/// The default choice for index sets in Helgobox. +pub type NonCryptoIndexSet = IndexSet; + +pub fn clone_to_other_hash_map< + K: Eq + Hash + Clone, + V: Clone, + S1: BuildHasher, + S2: BuildHasher + Default, +>( + non_crypto: &HashMap, +) -> HashMap { + non_crypto + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect() +} + +pub fn convert_into_other_hash_map( + non_crypto: HashMap, +) -> HashMap { + non_crypto.into_iter().collect() +} + +pub fn convert_into_other_hash_set( + non_crypto: HashSet, +) -> HashSet { + non_crypto.into_iter().collect() +} + +/// Calculates a 64-bit non-crypto hash directly from the given bytes. +/// +/// A bit faster than the streaming version. +pub fn calculate_non_crypto_hash_one_shot(payload: &[u8]) -> u64 { + xxhash_rust::xxh3::xxh3_64(payload) +} + +/// Calculates a 128-bit non-crypto hash directly from the given bytes suitable for persistence. +/// +/// This implementation must not change! +pub fn calculate_persistent_non_crypto_hash_one_shot(payload: &[u8]) -> PersistentHash { + // Don't change the hash function! It's used e.g. for file names. + PersistentHash(xxhash_rust::xxh3::xxh3_128(payload)) +} + +/// Calculates a 64-bit non-crypto hash from the given hashable type. +/// +/// If you already have a slice of bytes, use the one-shot version instead. +pub fn calculate_non_crypto_hash(t: &T) -> u64 { + let mut hasher = create_non_crypto_hasher(); + t.hash(&mut hasher); + hasher.finish() +} + +/// Creates a hasher for calculating a 64-bit non-crypto hash. +pub fn create_non_crypto_hasher() -> impl Hasher { + NonCryptoHasher::new() +} + +/// Creates a builder for a hasher for calculating a 64-bit non-crypto hash. +pub fn create_non_crypto_hash_builder() -> NonCryptoHashBuilder { + NonCryptoHashBuilder::new() +} + +/// This newtype should be used whenever it matters to keep a stable hash function, for example +/// when the hashes are going to be persisted. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct PersistentHash(u128); + +impl PersistentHash { + pub fn get(&self) -> u128 { + self.0 + } +} + +#[derive(Default)] +pub struct PersistentHasher(Xxh3Default); + +impl PersistentHasher { + pub fn new() -> Self { + // Don't change the wrapped hasher! It's used e.g. for file names. + Self::default() + } + + pub fn digest_128(&self) -> PersistentHash { + PersistentHash(self.0.digest128()) + } +} + +impl Hasher for PersistentHasher { + fn finish(&self) -> u64 { + self.0.finish() + } + + fn write(&mut self, bytes: &[u8]) { + self.0.write(bytes) + } +} diff --git a/plugin-reaper-realearn/base/src/lib.rs b/plugin-reaper-realearn/base/src/lib.rs new file mode 100644 index 0000000..05660f8 --- /dev/null +++ b/plugin-reaper-realearn/base/src/lib.rs @@ -0,0 +1,49 @@ +#[macro_use] +mod regex_util; + +#[macro_use] +pub mod tracing_util; + +#[macro_use] +mod global_macros; + +mod mouse; +pub use mouse::*; + +mod global; +pub use global::*; + +pub mod default_util; + +pub mod hash_util; + +mod channels; +pub use channels::*; + +mod mutex_util; +pub use mutex_util::*; + +pub mod file_util; + +pub mod future_util; + +pub mod metrics_util; + +mod small_ascii_string; +pub use small_ascii_string::*; + +mod sound_player; +pub use sound_player::*; + +pub mod validation_util; + +pub mod peak_util; + +pub mod byte_pattern; + +pub mod serde_json_util; + +mod approx_f64; +pub use approx_f64::*; + +pub mod replenishment_channel; diff --git a/plugin-reaper-realearn/base/src/metrics_util.rs b/plugin-reaper-realearn/base/src/metrics_util.rs new file mode 100644 index 0000000..6d8066e --- /dev/null +++ b/plugin-reaper-realearn/base/src/metrics_util.rs @@ -0,0 +1,131 @@ +use std::sync::mpsc::{Receiver, SyncSender}; +use std::sync::OnceLock; +use std::thread; +use std::time::{Duration, Instant}; + +/// This will contain the metrics sender for async metrics recording if metrics are enabled. +static METRICS_SENDER: OnceLock> = OnceLock::new(); + +#[derive(Debug)] +pub struct MetricsHook { + sender: SyncSender, +} + +impl Drop for MetricsHook { + fn drop(&mut self) { + // This prevents the metrics recorder thread from lurking around after the library + // is unloaded (which is of importance if "Allow complete unload of VST plug-ins" + // is enabled in REAPER for Windows). Ideally, we would just destroy the sender to achieve + // the same effect. But the sender is in a static variable which already has been + // initialized once and therefore can't be set to `None`. Unloading the library will just + // free the memory without triggering the drop, so that wouldn't work either. + let _ = self.sender.try_send(MetricsRecorderCommand::Finish); + // Joining the thread here somehow leads to a deadlock. Not sure why. It doesn't + // seem to be necessary anyway. The thread will end no matter what. + } +} + +impl MetricsHook { + /// Initializes metrics recording if the env variable `HELGOBOX_METRICS` is set. + /// + /// This starts a dedicated metrics recording thread, which is responsible for actually + /// recording certain metrics (e.g. durations), which is especially important when measuring + /// stuff from real-time threads. It avoids allocation and doesn't slow down real-time + /// processing (with the exception of measuring the duration itself). + /// + /// This should be called only once within the lifetime of the loaded shared library! On Linux + /// and macOS, this means it must only be called once within the lifetime of REAPER because once + /// a shared library is loaded, it's not unloaded anymore. On Windows, it can be called again + /// after REAPER unloaded the library via `FreeLibrary` and reloaded it again. + /// + /// The returned metrics hook must be dropped before the library is unloaded, otherwise the + /// metrics thread sticks around and that can't be good. + pub fn init() -> Option { + std::env::var("HELGOBOX_METRICS").ok()?; + let (sender, receiver) = std::sync::mpsc::sync_channel(5000); + thread::Builder::new() + .name(String::from("Helgobox metrics")) + .spawn(move || { + keep_recording_metrics(receiver); + }) + .expect("Helgobox metrics thread couldn't be created"); + METRICS_SENDER + .set(sender.clone()) + .expect("attempting to initializing metrics hook more than once"); + let hook = Self { sender }; + Some(hook) + } +} + +/// A simple function that doesn't expose anything to the metrics endpoint but warns if a +/// threshold is exceeded. Doesn't do anything in release builds (except executing the function). +pub fn warn_if_takes_too_long(label: &'static str, max: Duration, f: impl FnOnce() -> R) -> R { + #[cfg(debug_assertions)] + { + let before = Instant::now(); + let r = f(); + let elapsed = before.elapsed(); + if elapsed > max { + tracing::warn!( + "Operation took too long: \"{label}\" ({})ms", + elapsed.as_millis() + ); + } + r + } + #[cfg(not(debug_assertions))] + { + let _ = (label, max); + f() + } +} + +/// Synchronously records the occurrence of the given event. +pub fn record_occurrence(id: &'static str) { + if !metrics_are_enabled() { + return; + } + metrics::counter!(id).increment(1); +} + +/// Asynchronously measures and records the time of the given operation and exposes it at the +/// metrics endpoint. +pub fn measure_time(id: &'static str, f: impl FnOnce() -> R) -> R { + if !metrics_are_enabled() { + return f(); + } + let start = Instant::now(); + let result = f(); + record_duration(id, start.elapsed()); + result +} + +/// Records the given duration into a histogram. +pub fn record_duration(id: &'static str, delta: Duration) { + if let Some(sender) = METRICS_SENDER.get() { + let task = MetricsRecorderCommand::Histogram { id, delta }; + if sender.try_send(task).is_err() { + tracing::debug!("Helgobox metrics channel is full"); + } + } +} + +pub fn metrics_are_enabled() -> bool { + METRICS_SENDER.get().is_some() +} + +enum MetricsRecorderCommand { + Finish, + Histogram { id: &'static str, delta: Duration }, +} + +fn keep_recording_metrics(receiver: Receiver) { + while let Ok(task) = receiver.recv() { + match task { + MetricsRecorderCommand::Finish => break, + MetricsRecorderCommand::Histogram { id, delta } => { + metrics::histogram!(id).record(delta); + } + } + } +} diff --git a/plugin-reaper-realearn/base/src/mouse/api.rs b/plugin-reaper-realearn/base/src/mouse/api.rs new file mode 100644 index 0000000..a57958f --- /dev/null +++ b/plugin-reaper-realearn/base/src/mouse/api.rs @@ -0,0 +1,41 @@ +use helgobox_api::persistence::{Axis, MouseButton}; + +pub trait Mouse { + fn axis_size(&self, axis: Axis) -> u32; + + fn cursor_position(&self) -> Result; + + fn set_cursor_position(&mut self, new_pos: MouseCursorPosition) -> Result<(), &'static str>; + + /// Moves the mouse cursor relatively to its current position. + /// + /// - On the x axis, positive delta scrolls right and negative left. + /// - On the y axis, positive delta scrolls down and negative up (because it's natural for + /// screens to consider the top-left as zero). + fn adjust_cursor_position(&mut self, x_delta: i32, y_delta: i32) -> Result<(), &'static str>; + + /// Invokes the scroll wheel. + /// + /// - On the x axis, positive delta scrolls right and negative left. + /// - On the y axis, positive delta scrolls up and negative down (because it's natural for + /// knobs and especially faders to increase when scrolling up). + fn scroll(&mut self, axis: Axis, delta: i32) -> Result<(), &'static str>; + + fn press(&mut self, button: MouseButton) -> Result<(), &'static str>; + + fn release(&mut self, button: MouseButton) -> Result<(), &'static str>; + + fn is_pressed(&self, button: MouseButton) -> Result; +} + +#[derive(Copy, Clone, Debug)] +pub struct MouseCursorPosition { + pub x: u32, + pub y: u32, +} + +impl MouseCursorPosition { + pub fn new(x: u32, y: u32) -> Self { + Self { x, y } + } +} diff --git a/plugin-reaper-realearn/base/src/mouse/enigo.rs b/plugin-reaper-realearn/base/src/mouse/enigo.rs new file mode 100644 index 0000000..41c699f --- /dev/null +++ b/plugin-reaper-realearn/base/src/mouse/enigo.rs @@ -0,0 +1,159 @@ +use crate::{Mouse, MouseCursorPosition}; +use device_query::DeviceState; +use enigo::{Enigo, MouseControllable}; +use helgobox_api::persistence::{Axis, MouseButton}; +use std::fmt::Debug; + +#[derive(Debug)] +pub struct EnigoMouse { + enigo: Enigo, + device_state: Option, +} + +impl Default for EnigoMouse { + fn default() -> Self { + Self::new() + } +} + +impl EnigoMouse { + pub fn new() -> Self { + Self { + enigo: Default::default(), + device_state: create_device_state(), + } + } +} + +fn create_device_state() -> Option { + #[cfg(target_os = "macos")] + { + let trusted = + macos_accessibility_client::accessibility::application_is_trusted_with_prompt(); + if trusted { + Some(DeviceState::new()) + } else { + reaper_high::Reaper::get().show_console_msg("This Helgobox feature only works if Helgobox can access the state of your mouse. For this, it needs macOS accessibility permissions. Please grant REAPER the accessibility permission in the macOS system settings and restart it!\n\n"); + None + } + } + #[cfg(not(target_os = "macos"))] + { + Some(DeviceState::new()) + } +} + +unsafe impl Send for EnigoMouse {} + +impl Clone for EnigoMouse { + fn clone(&self) -> Self { + Self::new() + } +} + +impl PartialEq for EnigoMouse { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for EnigoMouse {} + +impl Mouse for EnigoMouse { + fn axis_size(&self, axis: Axis) -> u32 { + #[cfg(any(target_os = "windows", target_os = "macos"))] + { + let (width, height) = Enigo::main_display_size(); + let axis_size = match axis { + Axis::X => width, + Axis::Y => height, + }; + axis_size as u32 + } + #[cfg(target_os = "linux")] + { + let index = match axis { + Axis::X => reaper_low::raw::SM_CXSCREEN, + Axis::Y => reaper_low::raw::SM_CYSCREEN, + }; + reaper_low::Swell::get().GetSystemMetrics(index) as _ + } + } + + fn cursor_position(&self) -> Result { + #[cfg(any(target_os = "windows", target_os = "macos"))] + let (x, y) = Enigo::mouse_location(); + #[cfg(target_os = "linux")] + let (x, y) = { + let device_state = self + .device_state + .as_ref() + .expect("DeviceState should always work on Linux") + .query_pointer(); + (device_state.coords.0, device_state.coords.1) + }; + Ok(MouseCursorPosition::new(x.max(0) as u32, y.max(0) as u32)) + } + + fn set_cursor_position(&mut self, new_pos: MouseCursorPosition) -> Result<(), &'static str> { + self.enigo.mouse_move_to(new_pos.x as _, new_pos.y as _); + Ok(()) + } + + fn adjust_cursor_position(&mut self, x_delta: i32, y_delta: i32) -> Result<(), &'static str> { + self.enigo.mouse_move_relative(x_delta, y_delta); + Ok(()) + } + + fn scroll(&mut self, axis: Axis, delta: i32) -> Result<(), &'static str> { + match axis { + Axis::X => self.enigo.mouse_scroll_x(delta), + Axis::Y => { + // Handle https://github.com/enigo-rs/enigo/issues/117 + let final_delta = if cfg!(windows) { delta } else { -delta }; + self.enigo.mouse_scroll_y(final_delta) + } + } + Ok(()) + } + + fn press(&mut self, button: MouseButton) -> Result<(), &'static str> { + self.enigo.mouse_down(convert_button_to_enigo(button)); + Ok(()) + } + + fn release(&mut self, button: MouseButton) -> Result<(), &'static str> { + self.enigo.mouse_up(convert_button_to_enigo(button)); + Ok(()) + } + + fn is_pressed(&self, button: MouseButton) -> Result { + let mouse_state = self + .device_state + .as_ref() + .ok_or("macOS accessibility permissions not granted")? + .query_pointer(); + let button_index = convert_button_to_device_query(button); + let pressed = mouse_state + .button_pressed + .get(button_index) + .ok_or("couldn't get button")?; + Ok(*pressed) + } +} + +fn convert_button_to_device_query(button: MouseButton) -> usize { + match button { + MouseButton::Left => 1, + MouseButton::Middle => 3, + MouseButton::Right => 2, + } +} + +fn convert_button_to_enigo(button: MouseButton) -> enigo::MouseButton { + match button { + MouseButton::Left => enigo::MouseButton::Left, + MouseButton::Middle => enigo::MouseButton::Middle, + MouseButton::Right => enigo::MouseButton::Right, + } +} diff --git a/plugin-reaper-realearn/base/src/mouse/mod.rs b/plugin-reaper-realearn/base/src/mouse/mod.rs new file mode 100644 index 0000000..0a34d46 --- /dev/null +++ b/plugin-reaper-realearn/base/src/mouse/mod.rs @@ -0,0 +1,4 @@ +mod api; +pub use api::*; + +pub mod enigo; diff --git a/plugin-reaper-realearn/base/src/mutex_util.rs b/plugin-reaper-realearn/base/src/mutex_util.rs new file mode 100644 index 0000000..bbbed36 --- /dev/null +++ b/plugin-reaper-realearn/base/src/mutex_util.rs @@ -0,0 +1,90 @@ +use crate::metrics_util::warn_if_takes_too_long; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::time::Duration; + +/// Attempts to lock the given mutex. +/// +/// Returns the guard even if mutex is poisoned. +/// +/// # Panics +/// +/// Panics in debug builds if already locked (blocks in release builds). +pub fn non_blocking_lock<'a, T>( + mutex: &'a Mutex, + description: &'static str, +) -> MutexGuard<'a, T> { + // TODO-high-performance This panics when pressing play. Check it out! + // #[cfg(debug_assertions)] + // match mutex.try_lock() { + // Ok(g) => g, + // Err(std::sync::TryLockError::Poisoned(e)) => e.into_inner(), + // Err(std::sync::TryLockError::WouldBlock) => { + // panic!("locking mutex would block: {}", description) + // } + // } + // #[cfg(not(debug_assertions))] + blocking_lock(mutex, description) +} + +/// Locks the given mutex. +/// +/// Returns the guard even if mutex is poisoned. +pub fn blocking_lock_arc<'a, T>( + mutex: &'a Arc>, + description: &'static str, +) -> MutexGuard<'a, T> { + blocking_lock(&**mutex, description) +} + +/// Locks the given mutex. +/// +/// Returns the guard even if mutex is poisoned. +pub fn blocking_lock<'a, T>(mutex: &'a Mutex, description: &'static str) -> MutexGuard<'a, T> { + warn_if_takes_too_long(description, MAX_MUTEX_LOCK_DURATION, || { + match mutex.lock() { + Ok(g) => g, + Err(e) => e.into_inner(), + } + }) +} + +/// Locks the given rw lock. +/// +/// Returns the guard even if mutex is poisoned. +pub fn blocking_read_lock<'a, T>( + rw_lock: &'a RwLock, + description: &'static str, +) -> RwLockReadGuard<'a, T> { + warn_if_takes_too_long(description, MAX_MUTEX_LOCK_DURATION, || { + match rw_lock.read() { + Ok(g) => g, + Err(e) => e.into_inner(), + } + }) +} + +/// Returns `None` if access would block. +pub fn non_blocking_try_read_lock(rw_lock: &RwLock) -> Option> { + match rw_lock.try_read() { + Ok(g) => Some(g), + Err(std::sync::TryLockError::Poisoned(e)) => Some(e.into_inner()), + Err(std::sync::TryLockError::WouldBlock) => None, + } +} + +/// Locks the given rw lock. +/// +/// Returns the guard even if mutex is poisoned. +pub fn blocking_write_lock<'a, T>( + rw_lock: &'a RwLock, + description: &'static str, +) -> RwLockWriteGuard<'a, T> { + warn_if_takes_too_long(description, MAX_MUTEX_LOCK_DURATION, || { + match rw_lock.write() { + Ok(g) => g, + Err(e) => e.into_inner(), + } + }) +} + +const MAX_MUTEX_LOCK_DURATION: Duration = Duration::from_millis(30); diff --git a/plugin-reaper-realearn/base/src/peak_util.rs b/plugin-reaper-realearn/base/src/peak_util.rs new file mode 100644 index 0000000..ed6da6a --- /dev/null +++ b/plugin-reaper-realearn/base/src/peak_util.rs @@ -0,0 +1,36 @@ +use either::Either; +use reaper_high::{Reaper, Track}; +use reaper_medium::{MediaTrack, ReaperVolumeValue, SoloMode, TrackAttributeKey}; +use std::iter; + +/// Returns whether the peaks should better be hidden even they are available. +/// +/// This is for the case if another track is soloed, reporting the peak would be misleading then. +pub fn peaks_should_be_hidden(track: &Track) -> bool { + let is_master = track.is_master_track(); + (is_master && track.is_muted()) + || (!is_master && track.project().any_solo() && track.solo_mode() == SoloMode::Off) +} + +/// Returns the track's peaks as iterator. +/// +/// This takes VU mode / channel count intricacies into account. It returns peaks even if another +/// track is soloed! See [`peaks_should_be_hidden`]. +pub fn get_track_peaks(track: MediaTrack) -> impl ExactSizeIterator { + let reaper = Reaper::get().medium_reaper(); + let vu_mode = + unsafe { reaper.get_media_track_info_value(track, TrackAttributeKey::VuMode) as i32 }; + let channel_count = if matches!(vu_mode, 2 | 8) { + // These VU modes have multi-channel support. + unsafe { reaper.get_media_track_info_value(track, TrackAttributeKey::Nchan) as i32 } + } else { + // Other VU modes always use stereo. + 2 + }; + if channel_count <= 0 { + return Either::Left(iter::empty()); + } + let iter = + (0..channel_count).map(move |ch| unsafe { reaper.track_get_peak_info(track, ch as u32) }); + Either::Right(iter) +} diff --git a/plugin-reaper-realearn/base/src/regex_util.rs b/plugin-reaper-realearn/base/src/regex_util.rs new file mode 100644 index 0000000..2f30638 --- /dev/null +++ b/plugin-reaper-realearn/base/src/regex_util.rs @@ -0,0 +1,7 @@ +#[macro_export] +macro_rules! regex { + ($re:literal $(,)?) => {{ + static RE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + RE.get_or_init(|| regex::Regex::new($re).unwrap()) + }}; +} diff --git a/plugin-reaper-realearn/base/src/replenishment_channel.rs b/plugin-reaper-realearn/base/src/replenishment_channel.rs new file mode 100644 index 0000000..cef3b8d --- /dev/null +++ b/plugin-reaper-realearn/base/src/replenishment_channel.rs @@ -0,0 +1,49 @@ +use futures::future::BoxFuture; +use tokio::sync::mpsc::Receiver; +use tracing::debug; + +/// An orchestration (task and receiver) to be used to supply the receiver with spare parts that might or might not be +/// necessary. +/// +/// This is usually used in scenarios where the consumer lives in a thread that is not allowed to allocate +/// (for example, real-time threads). +pub struct ReplenishmentOrchestration { + pub task: F, + pub receiver: ReplenishmentReceiver, +} + +/// Creates an orchestration. +/// +/// The capacity should be very low, depending on how many spare items you want to create. +pub fn orchestrate_replenishment( + capacity: usize, + mut create_next_item: impl FnMut() -> T + Send + 'static, +) -> ReplenishmentOrchestration> +where + T: Send + 'static, +{ + let (sender, receiver) = tokio::sync::mpsc::channel::(capacity); + let task = async move { + while let Ok(permit) = sender.reserve().await { + debug!("Replenishment channel has capacity. Create next item."); + let item = create_next_item(); + permit.send(item); + } + }; + ReplenishmentOrchestration { + receiver: ReplenishmentReceiver { receiver }, + task: Box::pin(task), + } +} + +#[derive(Debug)] +pub struct ReplenishmentReceiver { + receiver: Receiver, +} + +impl ReplenishmentReceiver { + /// Returns the next available item if one is available. + pub fn request_item(&mut self) -> Option { + self.receiver.try_recv().ok() + } +} diff --git a/plugin-reaper-realearn/base/src/serde_json_util.rs b/plugin-reaper-realearn/base/src/serde_json_util.rs new file mode 100644 index 0000000..9869afc --- /dev/null +++ b/plugin-reaper-realearn/base/src/serde_json_util.rs @@ -0,0 +1,18 @@ +use serde_json::Value; + +/// https://stackoverflow.com/a/54118457 +pub fn merge(a: &mut Value, b: Value) { + if let Value::Object(a) = a { + if let Value::Object(b) = b { + for (k, v) in b { + if v.is_null() { + a.remove(&k); + } else { + merge(a.entry(k).or_insert(Value::Null), v); + } + } + return; + } + } + *a = b; +} diff --git a/plugin-reaper-realearn/base/src/small_ascii_string.rs b/plugin-reaper-realearn/base/src/small_ascii_string.rs new file mode 100644 index 0000000..faaea6a --- /dev/null +++ b/plugin-reaper-realearn/base/src/small_ascii_string.rs @@ -0,0 +1,84 @@ +use ascii::{AsciiChar, AsciiStr, AsciiString, ToAsciiChar}; +use core::fmt; + +/// String with a maximum of 32 ASCII characters. +/// +/// It's useful in the audio thread because it can be cheaply copied and doesn't need allocation. +/// If you are okay with allocation and need cheap cloning, you could just as well use an +/// `Rc`. +pub type SmallAsciiString = LimitedAsciiString<32>; + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] +pub struct LimitedAsciiString { + length: u8, + content: [u8; N], +} + +impl LimitedAsciiString { + pub const MAX_LENGTH: usize = N; + + /// Crops the string if necessary. + pub fn from_ascii_str_cropping(ascii_str: &AsciiStr) -> Self { + let short = + AsciiString::from(&ascii_str.as_slice()[..Self::MAX_LENGTH.min(ascii_str.len())]); + Self::from_ascii_str(&short) + } + + /// Returns an error if the given string is not completely ASCII or is too long. + pub fn try_from_str(value: &str) -> Result { + let ascii_string: Result = + value.chars().map(|c| c.to_ascii_char()).collect(); + let ascii_string = ascii_string.map_err(|_| "value contains non-ASCII characters")?; + Self::try_from_ascii_str(&ascii_string) + } + + /// Returns an error if the given string is too long. + pub fn try_from_ascii_str(ascii_str: &AsciiStr) -> Result { + if ascii_str.len() > Self::MAX_LENGTH { + return Err("ASCII string too large"); + } + Ok(Self::from_ascii_str(ascii_str)) + } + + /// Panics if the given string is too long. + fn from_ascii_str(ascii_str: &AsciiStr) -> Self { + let mut content = [0u8; N]; + content[..ascii_str.len()].copy_from_slice(ascii_str.as_bytes()); + Self { + content, + length: ascii_str.len() as u8, + } + } + + pub fn as_ascii_str(&self) -> &AsciiStr { + AsciiStr::from_ascii(self.as_slice()).unwrap() + } + + pub fn as_slice(&self) -> &[u8] { + &self.content[..(self.length as usize)] + } +} + +pub fn convert_to_identifier(text: &str) -> Result { + let ascii_string: AsciiString = text + .chars() + // Remove all non-ASCII schars + .filter_map(|c| c.to_ascii_char().ok()) + // Allow only letters, digits and underscore + .filter(|ch| ch.is_ascii_alphanumeric() || *ch == AsciiChar::UnderScore) + // Skip leading digits + .skip_while(|ch| ch.is_ascii_digit()) + // No uppercase + .map(|ch| ch.to_ascii_lowercase()) + .collect(); + if ascii_string.is_empty() { + return Err("empty tag"); + } + Ok(SmallAsciiString::from_ascii_str_cropping(&ascii_string)) +} + +impl fmt::Display for LimitedAsciiString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_ascii_str().fmt(f) + } +} diff --git a/plugin-reaper-realearn/base/src/sound_player.rs b/plugin-reaper-realearn/base/src/sound_player.rs new file mode 100644 index 0000000..a658a5d --- /dev/null +++ b/plugin-reaper-realearn/base/src/sound_player.rs @@ -0,0 +1,92 @@ +use anyhow::{ensure, Context}; +use camino::Utf8Path; +use reaper_high::Reaper; +use reaper_low::raw; +use reaper_medium::{ + FlexibleOwnedPcmSource, Handle, MeasureAlignment, MidiImportBehavior, OwnedPreviewRegister, + PositionInSeconds, ReaperMutex, ReaperMutexGuard, ReaperVolumeValue, +}; +use std::cell::Cell; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub struct SoundPlayer { + preview_register: Arc>, + play_handle: Cell>>, +} + +unsafe impl Send for SoundPlayer {} + +impl Default for SoundPlayer { + fn default() -> Self { + Self::new() + } +} + +impl SoundPlayer { + pub fn new() -> Self { + let mut register = OwnedPreviewRegister::new(); + register.set_volume(ReaperVolumeValue::ZERO_DB); + let preview_register = Arc::new(ReaperMutex::new(register)); + Self { + preview_register, + play_handle: Cell::new(None), + } + } + + pub fn load_file(&mut self, path_to_file: &Utf8Path) -> anyhow::Result<()> { + ensure!(path_to_file.exists(), "sound file doesn't exist"); + let source = Reaper::get() + .medium_reaper() + .pcm_source_create_from_file_ex(path_to_file, MidiImportBehavior::UsePreference)?; + self.load_pcm_source(FlexibleOwnedPcmSource::Reaper(source)) + } + + pub fn load_pcm_source(&mut self, source: FlexibleOwnedPcmSource) -> anyhow::Result<()> { + let mut preview_register = self.lock_preview_register()?; + preview_register.set_src(Some(source)); + Ok(()) + } + + pub fn volume(&self) -> anyhow::Result { + let preview_register = self.lock_preview_register()?; + Ok(preview_register.volume()) + } + + pub fn set_volume(&self, volume: ReaperVolumeValue) -> anyhow::Result<()> { + let mut preview_register = self.lock_preview_register()?; + preview_register.set_volume(volume); + Ok(()) + } + + pub fn play(&self) -> anyhow::Result<()> { + if self.play_handle.get().is_some() { + // Is playing already. Simply rewind. + let mut preview_register = self.lock_preview_register()?; + preview_register.set_cur_pos(PositionInSeconds::ZERO); + } else { + // Is not yet playing. Start playing. + let handle = Reaper::get().medium_session().play_preview_ex( + self.preview_register.clone(), + Default::default(), + MeasureAlignment::PlayImmediately, + )?; + self.play_handle.set(Some(handle)); + } + Ok(()) + } + + pub fn stop(&self) -> anyhow::Result<()> { + let play_handle = self.play_handle.take().context("not playing")?; + Reaper::get().medium_session().stop_preview(play_handle)?; + self.lock_preview_register()? + .set_cur_pos(PositionInSeconds::ZERO); + Ok(()) + } + + fn lock_preview_register(&self) -> anyhow::Result> { + self.preview_register + .lock() + .context("couldn't acquire preview register lock in sound player") + } +} diff --git a/plugin-reaper-realearn/base/src/tracing_util.rs b/plugin-reaper-realearn/base/src/tracing_util.rs new file mode 100644 index 0000000..e8b1ac0 --- /dev/null +++ b/plugin-reaper-realearn/base/src/tracing_util.rs @@ -0,0 +1,19 @@ +use std::error::Error; +use std::fmt::Display; + +pub fn ok_or_log_as_warn(result: Result) -> Option { + match result { + Ok(v) => Some(v), + Err(e) => { + tracing::warn!("{e}"); + None + } + } +} + +pub fn log_if_error(result: Result>) { + if let Err(error) = result { + let error = error.as_ref(); + tracing::error!(msg = "Error", error); + } +} diff --git a/plugin-reaper-realearn/base/src/validation_util.rs b/plugin-reaper-realearn/base/src/validation_util.rs new file mode 100644 index 0000000..6d5d470 --- /dev/null +++ b/plugin-reaper-realearn/base/src/validation_util.rs @@ -0,0 +1,42 @@ +use crate::hash_util::NonCryptoHashSet; +use std::error::Error; +use std::fmt::Display; +use std::hash::Hash; + +#[derive(Debug, derive_more::Display)] +pub struct ValidationError(String); + +impl Error for ValidationError {} + +#[allow(clippy::unnecessary_filter_map)] +pub fn ensure_no_duplicate(list_label: &str, iter: T) -> Result<(), ValidationError> +where + T: IntoIterator, + T::Item: Eq + Hash + Display, +{ + use std::fmt::Write; + let mut uniq = NonCryptoHashSet::default(); + let duplicates: NonCryptoHashSet<_> = iter + .into_iter() + .filter_map(|d| { + if uniq.contains(&d) { + Some(d) + } else { + uniq.insert(d); + None + } + }) + .collect(); + if duplicates.is_empty() { + Ok(()) + } else { + let mut s = format!("Found the following duplicate {list_label}: "); + for (i, d) in duplicates.into_iter().enumerate() { + if i > 0 { + s.push_str(", "); + } + let _ = write!(&mut s, "{d}"); + } + Err(ValidationError(s)) + } +} diff --git a/plugin-reaper-realearn/build-linux-complete.sh b/plugin-reaper-realearn/build-linux-complete.sh new file mode 100755 index 0000000..95d28c8 --- /dev/null +++ b/plugin-reaper-realearn/build-linux-complete.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +PROFILE="$1" + +set -a +source ".$PROFILE.env" +set +a + +# Build commands +cargo build --features "playtime,licensing" \ No newline at end of file diff --git a/plugin-reaper-realearn/clippy.toml b/plugin-reaper-realearn/clippy.toml new file mode 100644 index 0000000..34c2b9f --- /dev/null +++ b/plugin-reaper-realearn/clippy.toml @@ -0,0 +1 @@ +large-error-threshold = 200 \ No newline at end of file diff --git a/plugin-reaper-realearn/csi/Cargo.toml b/plugin-reaper-realearn/csi/Cargo.toml new file mode 100644 index 0000000..5fc0aa6 --- /dev/null +++ b/plugin-reaper-realearn/csi/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "realearn-csi" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +nom.workspace = true +helgoboss-midi.workspace = true +base.workspace = true +helgobox-api.workspace = true +derive_more.workspace = true + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/csi/src/lib.rs b/plugin-reaper-realearn/csi/src/lib.rs new file mode 100644 index 0000000..380c64c --- /dev/null +++ b/plugin-reaper-realearn/csi/src/lib.rs @@ -0,0 +1,837 @@ +use base::hash_util::NonCryptoHashSet; +use derive_more::Display; +use helgoboss_midi::{RawShortMessage, ShortMessage, StructuredShortMessage, U14, U7}; +use helgobox_api::persistence::{ + ApiObject, ButtonFilter, Compartment, Envelope, Glue, Interval, MackieLcdSource, + MackieSevenSegmentDisplayScope, MackieSevenSegmentDisplaySource, Mapping, + MidiChannelPressureAmountSource, MidiControlChangeValueSource, MidiNoteVelocitySource, + MidiPitchBendChangeValueSource, MidiPolyphonicKeyPressureAmountSource, + MidiProgramChangeNumberSource, MidiRawSource, Source, SourceCharacter, Target, + VirtualControlElementCharacter, VirtualControlElementId, VirtualTarget, +}; +use std::error::Error; +use std::fmt::{Display, Formatter}; + +mod parser; +mod schema; + +pub use schema::*; + +pub enum CsiObject { + Widgets(Vec), +} + +type CsiResult = Result>; + +pub fn deserialize_csi_object_from_csi(text: &str) -> Result> { + let widgets = parser::mst_file_content(text)?; + Ok(CsiObject::Widgets(widgets)) +} + +#[derive(Default)] +pub struct Annotator { + context_stack: Vec, + annotations: Vec, +} + +impl Annotator { + pub fn new() -> Self { + Self::default() + } + + pub fn with_context(&mut self, context: String, f: impl FnOnce(&mut Annotator) -> R) -> R { + self.context_stack.push(context); + let result = f(self); + self.context_stack.pop(); + result + } + + pub fn info(&mut self, message: impl Into) { + self.annotate(message, AnnotationLevel::Info); + } + + pub fn warn(&mut self, message: impl Into) { + self.annotate(message, AnnotationLevel::Warn); + } + + fn annotate(&mut self, message: impl Into, level: AnnotationLevel) { + let annotation = Annotation { + context_stack: self.context_stack.clone(), + message: message.into(), + level, + }; + self.annotations.push(annotation); + } + + pub fn build_result(self, value: T) -> AnnotatedResult { + AnnotatedResult { + value, + annotations: self.annotations, + } + } +} + +#[derive(Display)] +enum AnnotationLevel { + #[display(fmt = "INFO")] + Info, + #[display(fmt = "WARN")] + Warn, +} + +pub struct Annotation { + context_stack: Vec, + level: AnnotationLevel, + message: String, +} + +impl Display for Annotation { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + let context_expression = self.context_stack.join(" => "); + write!(f, "{} {}: {}", self.level, context_expression, self.message) + } +} + +pub struct AnnotatedResult { + pub value: T, + pub annotations: Vec, +} + +impl AnnotatedResult { + pub fn without_annotations(value: T) -> Self { + Self { + value, + annotations: vec![], + } + } +} + +impl CsiObject { + pub fn try_into_api_object(self) -> Result, Box> { + let mut annotator = Annotator::new(); + use CsiObject as O; + let api_object = match self { + O::Widgets(widgets) => { + let results: Vec<_> = widgets + .into_iter() + .filter_map(|w| { + annotator.with_context(format!("Widget \"{}\"", w.name), |annotator| { + match convert_widget(w, annotator) { + Ok(res) => Some(res), + Err(e) => { + annotator.warn(e.to_string()); + None + } + } + }) + }) + .collect(); + let has_duplicate_widget_ids = { + let id_set: NonCryptoHashSet<_> = + results.iter().map(|r| r.widget_id.clone()).collect(); + results.len() != id_set.len() + }; + if has_duplicate_widget_ids { + annotator.warn("Duplicate widget IDs were produced because of truncation. This will most likely lead to problems! Please shorten the affected widget names.") + } + let mappings = results.into_iter().flat_map(|r| r.mappings).collect(); + let compartment = Compartment { + mappings: Some(mappings), + ..Default::default() + }; + ApiObject::ControllerCompartment(Envelope { + version: None, + value: Box::new(compartment), + }) + } + }; + Ok(annotator.build_result(api_object)) + } +} + +struct WidgetConvResult { + widget_id: String, + mappings: Vec, +} + +fn convert_widget(widget: Widget, annotator: &mut Annotator) -> CsiResult { + let widget_name = widget.name; + let widget_id = convert_widget_name_to_id(&widget_name, annotator)?; + let mappings = widget + .capabilities + .into_iter() + .flat_map(|c| { + annotator.with_context(format!("Capability \"{c}\""), |annotator| { + convert_capability_to_mappings(&widget_name, &widget_id, c, annotator) + .unwrap_or_else(|e| { + annotator.info(e.to_string()); + vec![] + }) + }) + }) + .collect(); + let res = WidgetConvResult { + widget_id, + mappings, + }; + Ok(res) +} + +fn convert_capability_to_mappings( + widget_name: &str, + widget_id: &str, + capability: Capability, + annotator: &mut Annotator, +) -> CsiResult> { + let base_mapping = Mapping { + id: Some(format!("{widget_id}-{capability}")), + name: Some(format!("{widget_name} - {capability}")), + ..Default::default() + }; + let target_character = if capability.is_virtual_button() { + VirtualControlElementCharacter::Button + } else { + VirtualControlElementCharacter::Multi + }; + let mappings = match capability { + Capability::Press { press, release } => { + let press_res = convert_max_short_msg_to_source(MsgConvInput { + msg: press, + character: SourceCharacter::Button, + press_only: release.is_none(), + fourteen_bit: false, + })?; + // If press-only and we have a value that's neither MAX or MIN, it means we want to a + // message with this particular value ONLY. In this case it's best to create a raw + // MIDI message source. + if let Some(release) = release { + let release_res = convert_max_short_msg_to_source(MsgConvInput { + msg: release, + character: SourceCharacter::Button, + press_only: false, + fourteen_bit: false, + })?; + if release_res.source != press_res.source { + annotator.warn("Press and release messages differ not just in value but also in type or channel. This is very uncommon and might be a mistake or shortcoming of the widget definition. In general, ReaLearn supports such exotic cases but the CSI-to-ReaLearn conversion not yet. If you really need it, open an issue at GitHub.") + } + } + let mapping = Mapping { + feedback_enabled: Some(false), + source: Some(press_res.source), + glue: { + let g = Glue { + button_filter: if release.is_some() { + None + } else { + Some(ButtonFilter::PressOnly) + }, + reverse: Some(press_res.reverse_if_button_like), + ..Default::default() + }; + Some(g) + }, + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::FbTwoState { on, off } => { + let on_res = convert_max_short_msg_to_source(MsgConvInput { + msg: on, + character: SourceCharacter::Button, + press_only: false, + fourteen_bit: false, + })?; + let off_res = convert_max_short_msg_to_source(MsgConvInput { + msg: off, + character: SourceCharacter::Button, + press_only: false, + fourteen_bit: false, + })?; + if off_res.source != on_res.source { + annotator.warn("On and off messages differ not just in value but also in type or channel. This is very uncommon and might be a mistake or shortcoming of the widget definition. In general, ReaLearn supports such exotic cases but the CSI-to-ReaLearn conversion for this case has not been implemented. If you really need it, open an issue at GitHub.") + } + let mapping = Mapping { + control_enabled: Some(false), + source: Some(on_res.source), + glue: { + let g = Glue { + reverse: Some(on_res.reverse_if_button_like), + ..Default::default() + }; + Some(g) + }, + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::Encoder { + main, + accelerations, + } => { + let acc_conv_res = convert_accelerations(accelerations, annotator)?; + let main_res = convert_max_short_msg_to_source(MsgConvInput { + msg: main, + character: acc_conv_res.character, + press_only: false, + fourteen_bit: false, + })?; + let mapping = Mapping { + feedback_enabled: Some(false), + source: Some(main_res.source), + glue: { + let g = Glue { + step_factor_interval: Some(acc_conv_res.step_factor_interval), + ..Default::default() + }; + Some(g) + }, + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::FbEncoder { max } => { + let max_res = convert_max_short_msg_to_source(MsgConvInput { + msg: max, + character: SourceCharacter::Relative1, + press_only: false, + fourteen_bit: false, + })?; + let mapping = Mapping { + control_enabled: Some(false), + source: Some(max_res.source), + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::Toggle { on } => { + let on_res = convert_max_short_msg_to_source(MsgConvInput { + msg: on, + character: SourceCharacter::Button, + press_only: false, + fourteen_bit: false, + })?; + let mapping = Mapping { + feedback_enabled: Some(false), + source: Some(on_res.source), + glue: { + let g = Glue { + reverse: Some(on_res.reverse_if_button_like), + ..Default::default() + }; + Some(g) + }, + // TODO-medium Mmh, there's also a separate mapping for that. What's the point of + // "Toggle" then? Maybe it's just a duplicate in the X-Touch mst file. Check! + target: virtual_target( + extended_control_element_id(widget_id, "push")?, + target_character, + ), + ..base_mapping + }; + vec![mapping] + } + Capability::Touch { touch, release } => { + let touch_res = convert_max_short_msg_to_source(MsgConvInput { + msg: touch, + character: SourceCharacter::Button, + press_only: false, + fourteen_bit: false, + })?; + + let release_res = convert_max_short_msg_to_source(MsgConvInput { + msg: release, + character: SourceCharacter::Button, + press_only: false, + fourteen_bit: false, + })?; + if release_res.source != touch_res.source { + annotator.warn("Touch and release messages differ not just in value but also in type or channel. This is very uncommon and might be a mistake or shortcoming of the widget definition. In general, ReaLearn supports such exotic cases but the CSI-to-ReaLearn conversion for this case has not been implemented. If you really need it, open an issue at GitHub.") + } + let mapping = Mapping { + feedback_enabled: Some(false), + source: Some(touch_res.source), + glue: { + let g = Glue { + reverse: Some(touch_res.reverse_if_button_like), + ..Default::default() + }; + Some(g) + }, + target: virtual_target( + extended_control_element_id(widget_id, "touch")?, + target_character, + ), + ..base_mapping + }; + vec![mapping] + } + Capability::Fader14Bit { max } => { + let max_res = convert_max_short_msg_to_source(MsgConvInput { + msg: max, + character: SourceCharacter::Range, + press_only: false, + fourteen_bit: true, + })?; + let mapping = Mapping { + feedback_enabled: Some(false), + source: Some(max_res.source), + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::FbFader14Bit { max } => { + let max_res = convert_max_short_msg_to_source(MsgConvInput { + msg: max, + character: SourceCharacter::Range, + press_only: false, + fourteen_bit: true, + })?; + let mapping = Mapping { + control_enabled: Some(false), + source: Some(max_res.source), + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::FbMcuVuMeter { index } => { + let source = Source::MidiRaw(MidiRawSource { + feedback_behavior: None, + pattern: Some(format!("D0 [{index:04b} dcba]")), + character: Some(SourceCharacter::Range), + }); + let mapping = Mapping { + control_enabled: Some(false), + source: Some(source), + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::FbMcuTimeDisplay => { + let source = Source::MackieSevenSegmentDisplay(MackieSevenSegmentDisplaySource { + scope: Some(MackieSevenSegmentDisplayScope::Tc), + }); + let mapping = Mapping { + control_enabled: Some(false), + source: Some(source), + target: virtual_target(widget_id.to_owned(), target_character), + ..base_mapping + }; + vec![mapping] + } + Capability::FbMcuDisplayLower { index } => { + let mapping = create_mackie_lcd_mapping(base_mapping, widget_id.to_owned(), index, 1); + vec![mapping] + } + Capability::FbMcuDisplayUpper { index } => { + let mapping = create_mackie_lcd_mapping(base_mapping, widget_id.to_owned(), index, 0); + vec![mapping] + } + Capability::Unknown(_) => { + annotator.warn("Unknown capability. If this is a valid CSI capability, please create a ReaLearn issue at GitHub."); + vec![] + } + }; + Ok(mappings) +} + +fn extended_control_element_id(base: &str, extension: &str) -> CsiResult { + let res = format!("{base}/{extension}"); + if res.len() > MAX_CONTROL_ELEMENT_ID_LENGTH { + return Err(format!("{res} is an invalid control element ID because it's too long. Please shorten the corresponding widget name.").into()); + } + Ok(res) +} + +fn virtual_target(id: String, character: VirtualControlElementCharacter) -> Option { + let t = VirtualTarget { + id: VirtualControlElementId::Named(id), + character: Some(character), + learnable: None, + }; + Some(Target::Virtual(t)) +} + +struct AccelerationConvResult { + character: SourceCharacter, + step_factor_interval: Interval, +} + +fn convert_accelerations( + accelerations: Option, + annotator: &mut Annotator, +) -> CsiResult { + let accelerations = if let Some(acc) = accelerations { + acc + } else { + let res = AccelerationConvResult { + character: SourceCharacter::Relative3, + step_factor_interval: Interval(1, 1), + }; + return Ok(res); + }; + let native_decrements = NativeAcceleration::from_acceleration(accelerations.decrements) + .map_err(|_| "No acceleration values provided for counter-clockwise encoder movement")?; + let native_increments = NativeAcceleration::from_acceleration(accelerations.increments) + .map_err(|_| "No acceleration values provided for clockwise encoder movement")?; + let neutral_accelerations = neutralize_accelerations(native_decrements, native_increments)?; + let res = AccelerationConvResult { + character: neutral_accelerations.character, + step_factor_interval: Interval(1, neutral_accelerations.max_acceleration()), + }; + let dec_diff = neutral_accelerations.decrements.diff(); + let inc_diff = neutral_accelerations.increments.diff(); + if dec_diff.is_non_continuous() || inc_diff.is_non_continuous() { + annotator.warn( + "Non-continuous acceleration profile detected. Encoder acceleration behavior might be slightly different in ReaLearn.", + ); + } + if dec_diff != inc_diff { + annotator.warn("Clockwise acceleration profile differs from counter-clockwise acceleration profile. In general supported by ReaLearn but not yet supported by the CSI-to-ReaLearn conversion. That means the acceleration behavior might be slightly different in ReaLearn."); + } + Ok(res) +} + +const MAX_CONTROL_ELEMENT_ID_LENGTH: usize = 16; + +fn convert_widget_name_to_id(name: &str, annotator: &mut Annotator) -> CsiResult { + let id = name + .chars() + .filter(|ch| ch.is_ascii_alphanumeric() || ch.is_ascii_punctuation()) + .take(MAX_CONTROL_ELEMENT_ID_LENGTH) + .collect(); + if name.chars().count() > MAX_CONTROL_ELEMENT_ID_LENGTH { + annotator.info(format!("ReaLearn doesn't allow for virtual control element IDs longer than 16 characters, therefore the widget name \"{name}\" was truncated to the ID \"{id}\".")); + } + Ok(id) +} + +struct MsgConvOutput { + source: Source, + reverse_if_button_like: bool, +} + +struct MsgConvInput { + msg: RawShortMessage, + character: SourceCharacter, + press_only: bool, + fourteen_bit: bool, +} + +impl MsgConvInput { + fn should_produce_raw_midi_source_7_bit(&self, value: U7) -> bool { + self.press_only && (1..U7::MAX.get()).contains(&value.get()) + } + + fn should_produce_raw_midi_source_14_bit(&self, value: U14) -> bool { + self.press_only && (1..U14::MAX.get()).contains(&value.get()) + } +} + +fn convert_max_short_msg_to_source(input: MsgConvInput) -> CsiResult { + use StructuredShortMessage as M; + let res = match input.msg.to_structured() { + M::NoteOn { + channel, + key_number, + velocity, + } => { + if input.should_produce_raw_midi_source_7_bit(velocity) { + convert_short_msg_to_raw_midi_source(input) + } else { + MsgConvOutput { + source: Source::MidiNoteVelocity(MidiNoteVelocitySource { + feedback_behavior: None, + channel: Some(channel.get()), + key_number: Some(key_number.get()), + }), + reverse_if_button_like: velocity == U7::MIN, + } + } + } + M::NoteOff { + channel, + key_number, + velocity, + } => { + if input.should_produce_raw_midi_source_7_bit(velocity) { + convert_short_msg_to_raw_midi_source(input) + } else { + MsgConvOutput { + source: Source::MidiNoteVelocity(MidiNoteVelocitySource { + feedback_behavior: None, + channel: Some(channel.get()), + key_number: Some(key_number.get()), + }), + reverse_if_button_like: true, + } + } + } + M::PolyphonicKeyPressure { + channel, + key_number, + pressure_amount, + } => { + if input.should_produce_raw_midi_source_7_bit(pressure_amount) { + convert_short_msg_to_raw_midi_source(input) + } else { + MsgConvOutput { + source: Source::MidiPolyphonicKeyPressureAmount( + MidiPolyphonicKeyPressureAmountSource { + feedback_behavior: None, + channel: Some(channel.get()), + key_number: Some(key_number.get()), + }, + ), + reverse_if_button_like: pressure_amount == U7::MIN, + } + } + } + M::ControlChange { + channel, + controller_number, + control_value, + } => { + if input.should_produce_raw_midi_source_7_bit(control_value) { + convert_short_msg_to_raw_midi_source(input) + } else { + MsgConvOutput { + source: Source::MidiControlChangeValue(MidiControlChangeValueSource { + feedback_behavior: None, + channel: Some(channel.get()), + controller_number: Some(controller_number.get()), + character: Some(input.character), + fourteen_bit: Some(input.fourteen_bit), + }), + reverse_if_button_like: control_value == U7::MIN, + } + } + } + M::ProgramChange { + channel, + program_number, + } => { + if input.should_produce_raw_midi_source_7_bit(program_number) { + convert_short_msg_to_raw_midi_source(input) + } else { + MsgConvOutput { + source: Source::MidiProgramChangeNumber(MidiProgramChangeNumberSource { + feedback_behavior: None, + channel: Some(channel.get()), + }), + reverse_if_button_like: program_number == U7::MIN, + } + } + } + M::ChannelPressure { + channel, + pressure_amount, + } => { + if input.should_produce_raw_midi_source_7_bit(pressure_amount) { + convert_short_msg_to_raw_midi_source(input) + } else { + MsgConvOutput { + source: Source::MidiChannelPressureAmount(MidiChannelPressureAmountSource { + feedback_behavior: None, + channel: Some(channel.get()), + }), + reverse_if_button_like: pressure_amount == U7::MIN, + } + } + } + M::PitchBendChange { + channel, + pitch_bend_value, + } => { + if input.should_produce_raw_midi_source_14_bit(pitch_bend_value) { + convert_short_msg_to_raw_midi_source(input) + } else { + MsgConvOutput { + source: Source::MidiPitchBendChangeValue(MidiPitchBendChangeValueSource { + feedback_behavior: None, + channel: Some(channel.get()), + }), + reverse_if_button_like: pitch_bend_value == U14::MIN, + } + } + } + _ => { + return Err(format!("Message {:?} not handled in source conversion", input.msg).into()) + } + }; + Ok(res) +} + +fn convert_short_msg_to_raw_midi_source(input: MsgConvInput) -> MsgConvOutput { + MsgConvOutput { + source: Source::MidiRaw(MidiRawSource { + feedback_behavior: None, + pattern: Some(convert_to_raw_midi_pattern(input.msg)), + character: Some(input.character), + }), + reverse_if_button_like: false, + } +} + +fn convert_to_raw_midi_pattern(msg: RawShortMessage) -> String { + let (status_byte, data_byte_1, data_byte_2) = msg.to_bytes(); + format!( + "{:02X} {:02X} {:02X}", + status_byte, + data_byte_1.get(), + data_byte_2.get() + ) +} + +fn create_mackie_lcd_mapping( + base_mapping: Mapping, + widget_id: String, + index: u8, + line: u8, +) -> Mapping { + let source = Source::MackieLcd(MackieLcdSource { + extender_index: None, + channel: Some(index), + line: Some(line), + }); + Mapping { + control_enabled: Some(false), + source: Some(source), + target: virtual_target(widget_id, VirtualControlElementCharacter::Multi), + ..base_mapping + } +} + +struct NeutralAccelerations { + character: SourceCharacter, + /// This should contain values > 1 where each value contains the decrement amount. + decrements: NeutralAcceleration, + /// This should contain values > 1 where each value contains the increment amount. + increments: NeutralAcceleration, +} + +impl NeutralAccelerations { + pub fn max_acceleration(&self) -> i32 { + std::cmp::max( + self.decrements.0.iter().max().copied().unwrap_or(0), + self.increments.0.iter().max().copied().unwrap_or(0), + ) + } +} + +struct NativeAcceleration(Vec); + +impl NativeAcceleration { + pub fn from_acceleration(acc: Acceleration) -> Result { + let vec = match acc { + Acceleration::Sequence(s) => s, + Acceleration::Range(r) => r.collect(), + }; + if vec.is_empty() { + return Err("no acceleration values provided"); + } + Ok(Self(vec)) + } + + pub fn first(&self) -> u8 { + *self.0.first().expect("impossible") + } + + pub fn neutralize(self, crementor: i32) -> NeutralAcceleration { + let vec = self + .0 + .into_iter() + .map(|b| (b as i32 + crementor).abs()) + .collect(); + NeutralAcceleration(vec) + } +} + +struct NeutralAcceleration(Vec); + +impl NeutralAcceleration { + pub fn diff(&self) -> AccelerationDiff { + let vec = self + .0 + .iter() + .copied() + .zip(self.0.iter().copied().skip(1)) + .map(|(prev, next)| next - prev) + .collect(); + AccelerationDiff(vec) + } +} + +#[derive(PartialEq)] +struct AccelerationDiff(Vec); + +impl AccelerationDiff { + pub fn is_non_continuous(&self) -> bool { + self.0.iter().any(|d| *d != 1) + } +} + +fn neutralize_accelerations( + decrements: NativeAcceleration, + increments: NativeAcceleration, +) -> CsiResult { + let (character, decrementor, incrementor) = match (decrements.first(), increments.first()) { + (121..=127, 1..=7) => (SourceCharacter::Relative1, -128, 0), + (57..=63, 65..=71) => (SourceCharacter::Relative2, -64, -64), + (65..=71, 1..=7) => (SourceCharacter::Relative3, -64, 0), + _ => return Err("Unsupported relative encoder type".into()), + }; + let neutralized_acc = NeutralAccelerations { + character, + decrements: decrements.neutralize(decrementor), + increments: increments.neutralize(incrementor), + }; + Ok(neutralized_acc) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn neutralize_accelerations_relative_3() { + // Given + let decrements = NativeAcceleration(vec![0x41, 0x42, 0x43]); + let increments = NativeAcceleration(vec![0x01, 0x02, 0x03]); + // When + let neutralized = neutralize_accelerations(decrements, increments).unwrap(); + // Then + assert_eq!(neutralized.character, SourceCharacter::Relative3); + assert_eq!(neutralized.decrements.0, vec![1, 2, 3]); + assert_eq!(neutralized.increments.0, vec![1, 2, 3]); + } + + #[test] + fn neutralize_accelerations_relative_1() { + // Given + let decrements = NativeAcceleration(vec![0x7f, 0x7e, 0x7c, 0x7a]); + let increments = NativeAcceleration(vec![0x01, 0x04, 0x07]); + // When + let neutralized = neutralize_accelerations(decrements, increments).unwrap(); + // Then + assert_eq!(neutralized.character, SourceCharacter::Relative1); + assert_eq!(neutralized.decrements.0, vec![1, 2, 4, 6]); + assert_eq!(neutralized.increments.0, vec![1, 4, 7]); + } + + #[test] + fn neutral_diff() { + // Given + let increments = NeutralAcceleration(vec![0x01, 0x04, 0x07]); + // When + let diff = increments.diff(); + // Then + assert_eq!(diff.0, vec![3, 3]); + } +} diff --git a/plugin-reaper-realearn/csi/src/parser.rs b/plugin-reaper-realearn/csi/src/parser.rs new file mode 100644 index 0000000..ccf090f --- /dev/null +++ b/plugin-reaper-realearn/csi/src/parser.rs @@ -0,0 +1,457 @@ +use crate::schema::{Acceleration, Accelerations, Capability, Widget}; +use helgoboss_midi::{RawShortMessage, ShortMessageFactory}; +use nom::branch::alt; +use nom::bytes::complete::{tag, take_while1, take_while_m_n}; +use nom::character::complete::{multispace0, not_line_ending, space0, space1}; +use nom::combinator::{all_consuming, map, map_res, opt, verify}; +use nom::error::ParseError; +use nom::multi::{separated_list0, separated_list1}; +use nom::sequence::{preceded, separated_pair}; +use nom::{character::complete::char, sequence::delimited, sequence::tuple, Err, IResult, Parser}; +use std::convert::TryInto; + +type Res<'a, T> = IResult<&'a str, T>; + +pub fn mst_file_content(input: &str) -> Result, String> { + let non_comment_lines: Vec<_> = input + .lines() + .filter(|l| !l.trim_start().starts_with('/')) + .collect(); + let input_without_comments = non_comment_lines.join("\n"); + let (_, widgets) = all_consuming(widgets)(&input_without_comments).map_err(|e| { + let short_err = match e { + Err::Error(e) => Err::Error(nom::error::Error::new(&e.input[0..30], e.code)), + e => e, + }; + short_err.to_string() + })?; + Ok(widgets) +} + +fn widgets(input: &str) -> Res> { + delimited( + multispace0, + separated_list0(space_with_at_least_one_line_ending, widget), + multispace0, + )(input) +} + +fn widget(input: &str) -> Res { + map( + tuple(( + widget_begin, + space_with_at_least_one_line_ending, + widget_capabilities, + space_with_at_least_one_line_ending, + tag("WidgetEnd"), + )), + |(name, _, capabilities, _, _)| Widget { + name: name.to_owned(), + capabilities, + }, + )(input) +} + +fn widget_begin(input: &str) -> Res<&str> { + preceded( + tuple((tag("Widget"), space1)), + take_while1(|ch: char| ch.is_alphanumeric() || matches!(ch, '-' | '_')), + )(input) +} + +fn widget_capabilities(input: &str) -> Res> { + separated_list0(space_with_at_least_one_line_ending, capability)(input) +} + +fn capability(input: &str) -> Res { + alt(( + capability_press, + capability_fb_two_state, + capability_encoder, + capability_fb_encoder, + capability_toggle, + capability_fader_14_bit, + capability_fb_fader_14_bit, + capability_touch, + capability_fb_mcu_display_upper, + capability_fb_mcu_display_lower, + capability_fb_mcu_vu_meter, + capability_fb_mcu_time_display, + capability_unknown, + ))(input) +} + +fn capability_press(input: &str) -> Res { + map(util::capability_msg_opt_msg("Press"), |(press, release)| { + Capability::Press { press, release } + })(input) +} + +fn capability_fb_two_state(input: &str) -> Res { + map(util::capability_msg_msg("FB_TwoState"), |(on, off)| { + Capability::FbTwoState { on, off } + })(input) +} + +fn capability_fb_encoder(input: &str) -> Res { + map(util::capability_msg("FB_Encoder"), |max| { + Capability::FbEncoder { max } + })(input) +} + +fn capability_toggle(input: &str) -> Res { + map(util::capability_msg("Toggle"), |on| Capability::Toggle { + on, + })(input) +} + +fn capability_fader_14_bit(input: &str) -> Res { + map(util::capability_msg("Fader14Bit"), |max| { + Capability::Fader14Bit { max } + })(input) +} + +fn capability_fb_fader_14_bit(input: &str) -> Res { + map(util::capability_msg("FB_Fader14Bit"), |max| { + Capability::FbFader14Bit { max } + })(input) +} + +fn capability_touch(input: &str) -> Res { + map(util::capability_msg_msg("Touch"), |(on, off)| { + Capability::Touch { + touch: on, + release: off, + } + })(input) +} + +fn capability_fb_mcu_display_upper(input: &str) -> Res { + map(util::capability_index("FB_MCUDisplayUpper"), |index| { + Capability::FbMcuDisplayUpper { index } + })(input) +} + +fn capability_fb_mcu_display_lower(input: &str) -> Res { + map(util::capability_index("FB_MCUDisplayLower"), |index| { + Capability::FbMcuDisplayLower { index } + })(input) +} + +fn capability_fb_mcu_vu_meter(input: &str) -> Res { + map(util::capability_index("FB_MCUVUMeter"), |index| { + Capability::FbMcuVuMeter { index } + })(input) +} + +fn capability_fb_mcu_time_display(input: &str) -> Res { + map(util::capability_empty("FB_MCUTimeDisplay"), |_| { + Capability::FbMcuTimeDisplay + })(input) +} + +fn capability_encoder(input: &str) -> Res { + map( + tuple(( + preceded(tuple((tag("Encoder"), space1)), short_midi_msg), + opt(preceded(space1, accelerations)), + )), + |(main, accelerations)| Capability::Encoder { + main, + accelerations, + }, + )(input) +} + +fn capability_unknown(input: &str) -> Res { + map( + verify(not_line_ending, |s: &str| s != "WidgetEnd"), + |line: &str| Capability::Unknown(line.to_owned()), + )(input) +} + +fn short_midi_msg(input: &str) -> Res { + map_res( + tuple((hex_byte, space1, hex_byte, space1, hex_byte)), + |(b1, _, b2, _, b3)| { + RawShortMessage::from_bytes(( + b1, + b2.try_into().map_err(|_| "data byte 1 too high")?, + b3.try_into().map_err(|_| "data byte 2 too high")?, + )) + .map_err(|_| "invalid short message") + }, + )(input) +} + +fn accelerations(input: &str) -> Res { + map( + delimited( + ws(char('[')), + tuple(( + parameterized_acceleration('<'), + parameterized_acceleration('>'), + )), + ws(char(']')), + ), + |(decrements, increments)| Accelerations { + increments, + decrements, + }, + )(input) +} + +fn parameterized_acceleration<'a>( + letter: char, +) -> impl FnMut(&'a str) -> IResult<&'a str, Acceleration> { + preceded(ws(char(letter)), acceleration) +} + +fn acceleration(input: &str) -> Res { + alt((acceleration_range, acceleration_sequence))(input) +} + +fn acceleration_sequence(input: &str) -> Res { + map(separated_list1(space1, hex_byte), |values| { + Acceleration::Sequence(values) + })(input) +} + +fn acceleration_range(input: &str) -> Res { + map( + separated_pair(hex_byte, char('-'), hex_byte), + |(min, max)| Acceleration::Range(min..=max), + )(input) +} + +fn hex_byte(input: &str) -> Res { + map_res(take_while_m_n(2, 2, util::is_hex_digit), util::from_hex)(input) +} + +fn space_with_at_least_one_line_ending(input: &str) -> Res<&str> { + verify(multispace0, |s: &str| s.contains(&['\r', '\n'][..]))(input) +} + +/// Surrounded by optional whitespace (no line endings). +fn ws<'a, O, E>(p: impl Parser<&'a str, O, E>) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> +where + E: ParseError<&'a str>, +{ + delimited(space0, p, space0) +} + +mod util { + use super::*; + use nom::character::complete::digit1; + use nom::combinator::value; + + pub fn is_hex_digit(c: char) -> bool { + c.is_ascii_hexdigit() + } + + pub fn from_hex(input: &str) -> Result { + u8::from_str_radix(input, 16) + } + + pub fn capability_msg_opt_msg<'a>( + name: &'static str, + ) -> impl FnMut(&'a str) -> Res<'a, (RawShortMessage, Option)> { + preceded( + tag(name), + tuple(( + preceded(space1, short_midi_msg), + opt(preceded(space1, short_midi_msg)), + )), + ) + } + + pub fn capability_msg_msg<'a>( + name: &'static str, + ) -> impl FnMut(&'a str) -> Res<'a, (RawShortMessage, RawShortMessage)> { + preceded( + tag(name), + tuple(( + preceded(space1, short_midi_msg), + preceded(space1, short_midi_msg), + )), + ) + } + + pub fn capability_index<'a>(name: &'static str) -> impl FnMut(&'a str) -> Res<'a, u8> { + map_res(preceded(tuple((tag(name), space1)), digit1), |s: &str| { + s.parse::() + }) + } + + pub fn capability_empty<'a>(name: &'static str) -> impl FnMut(&'a str) -> Res<'a, ()> { + value((), tag(name)) + } + + pub fn capability_msg<'a>( + name: &'static str, + ) -> impl FnMut(&'a str) -> Res<'a, RawShortMessage> { + preceded(tag(name), preceded(space1, short_midi_msg)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::schema::{Acceleration, Widget}; + use helgoboss_midi::test_util::u7; + use helgoboss_midi::ShortMessageFactory; + + #[test] + fn parse_widgets() { + let mst_content = include_str!("test_data/test.mst"); + let (_, widgets) = widgets(mst_content).unwrap(); + assert_eq!(widgets.len(), 146); + for w in widgets { + for c in w.capabilities { + assert!(!c.is_unknown()); + } + } + } + + #[test] + fn parse_widget() { + assert_eq!( + widget( + "\ +Widget RecordArm1 + Press 90 00 7f 90 00 00 + FB_TwoState 90 00 7f 90 00 00 + Weird eu 898 dqwun wd08 . --- +WidgetEnd" + ), + Ok(( + "", + Widget { + name: "RecordArm1".to_owned(), + capabilities: vec![ + Capability::Press { + press: short(0x90, 0x00, 0x7f), + release: Some(short(0x90, 0x00, 0x00)), + }, + Capability::FbTwoState { + on: short(0x90, 0x00, 0x7f), + off: short(0x90, 0x00, 0x00), + }, + Capability::Unknown("Weird eu 898 dqwun wd08 . ---".to_owned()) + ] + } + )) + ); + } + + #[test] + fn parse_widget_ugly_formatting() { + assert_eq!( + widget( + "\ +Widget RecordArm1 + + Press 90 00 7f 90 00 00 + + FB_TwoState 90 00 7f 90 00 00 +Weird eu 898 dqwun wd08 . --- + + +WidgetEnd" + ), + Ok(( + "", + Widget { + name: "RecordArm1".to_owned(), + capabilities: vec![ + Capability::Press { + press: short(0x90, 0x00, 0x7f), + release: Some(short(0x90, 0x00, 0x00)), + }, + Capability::FbTwoState { + on: short(0x90, 0x00, 0x7f), + off: short(0x90, 0x00, 0x00), + }, + Capability::Unknown("Weird eu 898 dqwun wd08 . ---".to_owned()) + ] + } + )) + ); + } + + #[test] + fn parse_press_capability_without_release() { + assert_eq!( + capability("Press 90 28 7f"), + Ok(( + "", + Capability::Press { + press: short(0x90, 0x28, 0x7f), + release: None, + } + )) + ); + } + + #[test] + fn parse_press_capability_with_release() { + assert_eq!( + capability("Press 90 28 7f 90 28 00"), + Ok(( + "", + Capability::Press { + press: short(0x90, 0x28, 0x7f), + release: Some(short(0x90, 0x28, 0x00)), + } + )) + ); + } + + #[test] + fn parse_fb_two_state_capability() { + assert_eq!( + capability("FB_TwoState 90 00 7f 90 00 00"), + Ok(( + "", + Capability::FbTwoState { + on: short(0x90, 0x00, 0x7f), + off: short(0x90, 0x00, 0x00), + } + )) + ); + } + + #[test] + fn parse_encoder_capability_with_range() { + assert_eq!( + capability("Encoder b0 10 7f [ < 41-48 > 01-08 ]"), + Ok(( + "", + Capability::Encoder { + main: short(0xb0, 0x10, 0x7f), + accelerations: Some(Accelerations { + decrements: Acceleration::Range(0x41..=0x48), + increments: Acceleration::Range(0x01..=0x08) + }) + } + )) + ); + } + + #[test] + fn parse_short_midi_msg() { + assert_eq!( + short_midi_msg("90 28 7f"), + Ok(("", short(0x90, 0x28, 0x7f))) + ); + } + + #[test] + fn single_hex_byte() { + assert_eq!(hex_byte("90"), Ok(("", 0x90))); + } + + fn short(status_byte: u8, data_byte_1: u8, data_byte_2: u8) -> RawShortMessage { + RawShortMessage::from_bytes((status_byte, u7(data_byte_1), u7(data_byte_2))).unwrap() + } +} diff --git a/plugin-reaper-realearn/csi/src/schema.rs b/plugin-reaper-realearn/csi/src/schema.rs new file mode 100644 index 0000000..2967bbe --- /dev/null +++ b/plugin-reaper-realearn/csi/src/schema.rs @@ -0,0 +1,74 @@ +use derive_more::Display; +use helgoboss_midi::RawShortMessage; +use std::ops::RangeInclusive; + +#[derive(Eq, PartialEq, Debug)] +pub struct Widget { + pub name: String, + pub capabilities: Vec, +} + +#[derive(Eq, PartialEq, Debug, Display)] +pub enum Capability { + #[display(fmt = "Press")] + Press { + press: RawShortMessage, + release: Option, + }, + #[display(fmt = "FB_TwoState")] + FbTwoState { + on: RawShortMessage, + off: RawShortMessage, + }, + #[display(fmt = "Encoder")] + Encoder { + main: RawShortMessage, + accelerations: Option, + }, + #[display(fmt = "FB_Encoder")] + FbEncoder { max: RawShortMessage }, + #[display(fmt = "Toggle")] + Toggle { on: RawShortMessage }, + #[display(fmt = "Fader14Bit")] + Fader14Bit { max: RawShortMessage }, + #[display(fmt = "FB_Fader14Bit")] + FbFader14Bit { max: RawShortMessage }, + #[display(fmt = "Touch")] + Touch { + touch: RawShortMessage, + release: RawShortMessage, + }, + #[display(fmt = "FB_MCUDisplayLower")] + FbMcuDisplayLower { index: u8 }, + #[display(fmt = "FB_MCUDisplayUpper")] + FbMcuDisplayUpper { index: u8 }, + #[display(fmt = "FB_MCUTimeDisplay")] + FbMcuTimeDisplay, + #[display(fmt = "FB_MCUVUMeter")] + FbMcuVuMeter { index: u8 }, + #[display(fmt = "{_0}")] + Unknown(String), +} + +impl Capability { + pub fn is_unknown(&self) -> bool { + matches!(self, Self::Unknown(_)) + } + + pub fn is_virtual_button(&self) -> bool { + use Capability as C; + matches!(self, C::Press { .. } | C::Toggle { .. } | C::Touch { .. }) + } +} + +#[derive(Eq, PartialEq, Debug)] +pub struct Accelerations { + pub decrements: Acceleration, + pub increments: Acceleration, +} + +#[derive(Eq, PartialEq, Debug)] +pub enum Acceleration { + Sequence(Vec), + Range(RangeInclusive), +} diff --git a/plugin-reaper-realearn/csi/src/test_data/test.mst b/plugin-reaper-realearn/csi/src/test_data/test.mst new file mode 100644 index 0000000..275a03c --- /dev/null +++ b/plugin-reaper-realearn/csi/src/test_data/test.mst @@ -0,0 +1,711 @@ +Widget RecordArm1 + Press 90 00 7f 90 00 00 + FB_TwoState 90 00 7f 90 00 00 +WidgetEnd + +Widget RecordArm2 + Press 90 01 7f 90 01 00 + FB_TwoState 90 01 7f 90 01 00 +WidgetEnd + +Widget RecordArm3 + Press 90 02 7f 90 02 00 + FB_TwoState 90 02 7f 90 02 00 +WidgetEnd + +Widget RecordArm4 + Press 90 03 7f 90 03 00 + FB_TwoState 90 03 7f 90 03 00 +WidgetEnd + +Widget RecordArm5 + Press 90 04 7f 90 04 00 + FB_TwoState 90 04 7f 90 04 00 +WidgetEnd + +Widget RecordArm6 + Press 90 05 7f 90 05 00 + FB_TwoState 90 05 7f 90 05 00 +WidgetEnd + +Widget RecordArm7 + Press 90 06 7f 90 06 00 + FB_TwoState 90 06 7f 90 06 00 +WidgetEnd + +Widget RecordArm8 + Press 90 07 7f 90 07 00 + FB_TwoState 90 07 7f 90 07 00 +WidgetEnd + +Widget Solo1 + Press 90 08 7f 90 08 00 + FB_TwoState 90 08 7f 90 08 00 +WidgetEnd + +Widget Solo2 + Press 90 09 7f 90 09 00 + FB_TwoState 90 09 7f 90 09 00 +WidgetEnd + +Widget Solo3 + Press 90 0a 7f 90 0a 00 + FB_TwoState 90 0a 7f 90 0a 00 +WidgetEnd + +Widget Solo4 + Press 90 0b 7f 90 0b 00 + FB_TwoState 90 0b 7f 90 0b 00 +WidgetEnd + +Widget Solo5 + Press 90 0c 7f 90 0c 00 + FB_TwoState 90 0c 7f 90 0c 00 +WidgetEnd + +Widget Solo6 + Press 90 0d 7f 90 0d 00 + FB_TwoState 90 0d 7f 90 0d 00 +WidgetEnd + +Widget Solo7 + Press 90 0e 7f 90 0e 00 + FB_TwoState 90 0e 7f 90 0e 00 +WidgetEnd + +Widget Solo8 + Press 90 0f 7f 90 0f 00 + FB_TwoState 90 0f 7f 90 0f 00 +WidgetEnd + +Widget Mute1 + Press 90 10 7f 90 10 00 + FB_TwoState 90 10 7f 90 10 00 +WidgetEnd + +Widget Mute2 + Press 90 11 7f 90 11 00 + FB_TwoState 90 11 7f 90 11 00 +WidgetEnd + +Widget Mute3 + Press 90 12 7f 90 12 00 + FB_TwoState 90 12 7f 90 12 00 +WidgetEnd + +Widget Mute4 + Press 90 13 7f 90 13 00 + FB_TwoState 90 13 7f 90 13 00 +WidgetEnd + +Widget Mute5 + Press 90 14 7f 90 14 00 + FB_TwoState 90 14 7f 90 14 00 +WidgetEnd + +Widget Mute6 + Press 90 15 7f 90 15 00 + FB_TwoState 90 15 7f 90 15 00 +WidgetEnd + +Widget Mute7 + Press 90 16 7f 90 16 00 + FB_TwoState 90 16 7f 90 16 00 +WidgetEnd + +Widget Mute8 + Press 90 17 7f 90 17 00 + FB_TwoState 90 17 7f 90 17 00 +WidgetEnd + +Widget Select1 + Press 90 18 7f 90 18 00 + FB_TwoState 90 18 7f 90 18 00 +WidgetEnd + +Widget Select2 + Press 90 19 7f 90 19 00 + FB_TwoState 90 19 7f 90 19 00 +WidgetEnd + +Widget Select3 + Press 90 1a 7f 90 1a 00 + FB_TwoState 90 1a 7f 90 1a 00 +WidgetEnd + +Widget Select4 + Press 90 1b 7f 90 1b 00 + FB_TwoState 90 1b 7f 90 1b 00 +WidgetEnd + +Widget Select5 + Press 90 1c 7f 90 1c 00 + FB_TwoState 90 1c 7f 90 1c 00 +WidgetEnd + +Widget Select6 + Press 90 1d 7f 90 1d 00 + FB_TwoState 90 1d 7f 90 1d 00 +WidgetEnd + +Widget Select7 + Press 90 1e 7f 90 1e 00 + FB_TwoState 90 1e 7f 90 1e 00 +WidgetEnd + +Widget Select8 + Press 90 1f 7f 90 1f 00 + FB_TwoState 90 1f 7f 90 1f 00 +WidgetEnd + +Widget RotaryPush1 + Press 90 20 7f 90 20 00 +WidgetEnd + +Widget RotaryPush2 + Press 90 21 7f 90 21 00 +WidgetEnd + +Widget RotaryPush3 + Press 90 22 7f 90 22 00 +WidgetEnd + +Widget RotaryPush4 + Press 90 23 7f 90 23 00 +WidgetEnd + +Widget RotaryPush5 + Press 90 24 7f 90 24 00 +WidgetEnd + +Widget RotaryPush6 + Press 90 25 7f 90 25 00 +WidgetEnd + +Widget RotaryPush7 + Press 90 26 7f 90 26 00 +WidgetEnd + +Widget RotaryPush8 + Press 90 27 7f 90 27 00 +WidgetEnd + +Widget Rotary1 + Encoder b0 10 7f [ < 41-48 > 01-08 ] + FB_Encoder b0 10 7f + Toggle 90 20 7f +WidgetEnd + +Widget Rotary2 + Encoder b0 11 7f [ < 41-4a > 01-09 ] + FB_Encoder b0 11 7f + Toggle 90 21 7f +WidgetEnd + +Widget Rotary3 + Encoder b0 12 7f [ < 41-4a > 01-09 ] + FB_Encoder b0 12 7f + Toggle 90 22 7f +WidgetEnd + +Widget Rotary4 + Encoder b0 13 7f [ < 41-4a > 01-09 ] + FB_Encoder b0 13 7f + Toggle 90 23 7f +WidgetEnd + +Widget Rotary5 + Encoder b0 14 7f [ < 41-4a > 01-09 ] + FB_Encoder b0 14 7f + Toggle 90 24 7f +WidgetEnd + +Widget Rotary6 + Encoder b0 15 7f [ < 41-4a > 01-09 ] + FB_Encoder b0 15 7f + Toggle 90 25 7f +WidgetEnd + +Widget Rotary7 + Encoder b0 16 7f [ < 41-4a > 01-09 ] + FB_Encoder b0 16 7f + Toggle 90 26 7f +WidgetEnd + +Widget Rotary8 + Encoder b0 17 7f [ < 41-4a > 01-09 ] + FB_Encoder b0 17 7f + Toggle 90 27 7f +WidgetEnd + + +Widget Track + Press 90 28 7f 90 28 00 + FB_TwoState 90 28 7f 90 28 00 +WidgetEnd + +Widget Send + Press 90 29 7f 90 29 00 + FB_TwoState 90 29 7f 90 29 00 +WidgetEnd + +Widget Pan + Press 90 2a 7f 90 2a 00 + FB_TwoState 90 2a 7f 90 2a 00 +WidgetEnd + +Widget Plugin + Press 90 2b 7f 90 2b 00 + FB_TwoState 90 2b 7f 90 2b 00 +WidgetEnd + +Widget EQ + Press 90 2c 7f 90 2c 00 + FB_TwoState 90 2c 7f 90 2c 00 +WidgetEnd + +Widget Instrument + Press 90 2d 7f 90 2d 00 + FB_TwoState 90 2d 7f 90 2d 00 +WidgetEnd + +Widget BankLeft + Press 90 2e 7f 90 2e 00 + FB_TwoState 90 2e 7f 90 2e 00 +WidgetEnd + +Widget BankRight + Press 90 2f 7f 90 2f 00 + FB_TwoState 90 2f 7f 90 2f 00 +WidgetEnd + +Widget ChannelLeft + Press 90 30 7f 90 30 00 + FB_TwoState 90 30 7f 90 30 00 +WidgetEnd + +Widget ChannelRight + Press 90 31 7f 90 31 00 + FB_TwoState 90 31 7f 90 31 00 +WidgetEnd + +Widget Flip + Press 90 32 7f 90 32 00 + FB_TwoState 90 32 7f 90 32 00 +WidgetEnd + +Widget GlobalView + Press 90 33 7f 90 33 00 + FB_TwoState 90 33 7f 90 33 00 +WidgetEnd + +Widget BPM-Time + Press 90 35 7f 90 35 00 + FB_TwoState 90 35 7f 90 35 00 +WidgetEnd + +Widget nameValue + Press 90 34 7f +WidgetEnd + +Widget F1 + Press 90 36 7f 90 36 00 + FB_TwoState 90 36 7f 90 36 00 +WidgetEnd + +Widget F2 + Press 90 37 7f 90 37 00 + FB_TwoState 90 37 7f 90 37 00 +WidgetEnd + +Widget F3 + Press 90 38 7f 90 38 00 + FB_TwoState 90 38 7f 90 38 00 +WidgetEnd + +Widget F4 + Press 90 39 7f 90 39 00 + FB_TwoState 90 39 7f 90 39 00 +WidgetEnd + +Widget F5 + Press 90 3a 7f 90 3a 00 + FB_TwoState 90 3a 7f 90 3a 00 +WidgetEnd + +Widget F6 + Press 90 3b 7f 90 3b 00 + FB_TwoState 90 3b 7f 90 3b 00 +WidgetEnd + +Widget F7 + Press 90 3c 7f 90 3c 00 + FB_TwoState 90 3c 7f 90 3c 00 +WidgetEnd + +Widget F8 + Press 90 3d 7f 90 3d 00 + FB_TwoState 90 3d 7f 90 3d 00 +WidgetEnd + +Widget MidiTracks + Press 90 3e 7f 90 3e 00 + FB_TwoState 90 3e 7f 90 3e 00 +WidgetEnd + +Widget Inputs + Press 90 3f 7f 90 3f 00 + FB_TwoState 90 3f 7f 90 3f 00 +WidgetEnd + +Widget AudioTracks + Press 90 40 7f 90 40 00 + FB_TwoState 90 40 7f 90 40 00 +WidgetEnd + +Widget AudioInstrument + Press 90 41 7f 90 41 00 + FB_TwoState 90 41 7f 90 41 00 +WidgetEnd + +Widget Aux + Press 90 42 7f 90 42 00 + FB_TwoState 90 42 7f 90 42 00 +WidgetEnd + +Widget Busses + Press 90 43 7f 90 43 00 + FB_TwoState 90 43 7f 90 43 00 +WidgetEnd + +Widget Outputs + Press 90 44 7f 90 44 00 + FB_TwoState 90 44 7f 90 44 00 +WidgetEnd + +Widget User + Press 90 45 7f 90 45 00 + FB_TwoState 90 45 7f 90 45 00 +WidgetEnd + +Widget Shift + Press 90 46 7f 90 46 00 + FB_TwoState 90 46 7f 90 46 00 +WidgetEnd + +Widget Option + Press 90 47 7f 90 47 00 + FB_TwoState 90 47 7f 90 47 00 +WidgetEnd + +Widget Control + Press 90 48 7f 90 48 00 + FB_TwoState 90 48 7f 90 48 00 +WidgetEnd + +Widget Alt + Press 90 49 7f 90 49 00 + FB_TwoState 90 49 7f 90 49 00 +WidgetEnd + +Widget Read + Press 90 4a 7f 90 4a 00 + FB_TwoState 90 4a 7f 90 4a 00 +WidgetEnd + +Widget Write + Press 90 4b 7f 90 4b 00 + FB_TwoState 90 4b 7f 90 4b 00 +WidgetEnd + +Widget Trim + Press 90 4c 7f 90 4c 00 + FB_TwoState 90 4c 7f 90 4c 00 +WidgetEnd + +Widget Touch + Press 90 4d 7f 90 4d 00 + FB_TwoState 90 4d 7f 90 4d 00 +WidgetEnd + +Widget Latch + Press 90 4e 7f 90 4e 00 + FB_TwoState 90 4e 7f 90 4e 00 +WidgetEnd + +Widget Group + Press 90 4f 7f 90 4f 00 + FB_TwoState 90 4f 7f 90 4f 00 +WidgetEnd + +Widget Save + Press 90 50 7f 90 50 00 + FB_TwoState 90 50 7f 90 50 00 +WidgetEnd + +Widget Undo + Press 90 51 7f 90 51 00 + FB_TwoState 90 51 7f 90 51 00 +WidgetEnd + +Widget Cancel + Press 90 52 7f 90 52 00 + FB_TwoState 90 52 7f 90 52 00 +WidgetEnd + +Widget Enter + Press 90 53 7f 90 53 00 + FB_TwoState 90 53 7f 90 53 00 +WidgetEnd + +Widget Marker + Press 90 54 7f 90 54 00 + FB_TwoState 90 54 7f 90 54 00 +WidgetEnd + +Widget Nudge + Press 90 55 7f 90 55 00 + FB_TwoState 90 55 7f 90 55 00 +WidgetEnd + +Widget Cycle + Press 90 56 7f 90 56 00 + FB_TwoState 90 56 7f 90 56 00 +WidgetEnd + +Widget Drop + Press 90 57 7f 90 57 00 + FB_TwoState 90 57 7f 90 57 00 +WidgetEnd + +Widget Replace + Press 90 58 7f 90 58 00 + FB_TwoState 90 58 7f 90 58 00 +WidgetEnd + +Widget Click + Press 90 59 7f 90 59 00 + FB_TwoState 90 59 7f 90 59 00 +WidgetEnd + +Widget Solo + Press 90 5a 7f 90 5a 00 + FB_TwoState 90 5a 7f 90 5a 00 +WidgetEnd + +Widget Rewind + Press 90 5b 7f 90 5b 00 + FB_TwoState 90 5b 7f 90 5b 00 +WidgetEnd + +Widget FastForward + Press 90 5c 7f 90 5c 00 + FB_TwoState 90 5c 7f 90 5c 00 +WidgetEnd + +Widget Stop + Press 90 5d 7f 90 5d 00 + FB_TwoState 90 5d 7f 90 5d 00 +WidgetEnd + +Widget Play + Press 90 5e 7f 90 5e 00 + FB_TwoState 90 5e 7f 90 5e 00 +WidgetEnd + +Widget Record + Press 90 5f 7f 90 5f 00 + FB_TwoState 90 5f 7f 90 5f 00 +WidgetEnd + +Widget Up + Press 90 60 7f 90 60 00 + FB_TwoState 90 60 7f 90 60 00 +WidgetEnd + +Widget Down + Press 90 61 7f 90 61 00 + FB_TwoState 90 61 7f 90 61 00 +WidgetEnd + +Widget Left + Press 90 62 7f 90 62 00 + FB_TwoState 90 62 7f 90 62 00 +WidgetEnd + +Widget Right + Press 90 63 7f 90 63 00 + FB_TwoState 90 63 7f 90 63 00 +WidgetEnd + +Widget Zoom + Press 90 64 7f 90 64 00 + FB_TwoState 90 64 7f 90 64 00 +WidgetEnd + +Widget Scrub + Press 90 65 7f 90 65 00 + FB_TwoState 90 65 7f 90 65 00 +WidgetEnd + +Widget Fader1 + Fader14Bit e0 7f 7f + FB_Fader14Bit e0 7f 7f + Touch 90 68 7f 90 68 00 +WidgetEnd + +Widget Fader2 + Fader14Bit e1 7f 7f + FB_Fader14Bit e1 7f 7f + Touch 90 69 7f 90 69 00 +WidgetEnd + +Widget Fader3 + Fader14Bit e2 7f 7f + FB_Fader14Bit e2 7f 7f + Touch 90 6a 7f 90 6a 00 +WidgetEnd + +Widget Fader4 + Fader14Bit e3 7f 7f + FB_Fader14Bit e3 7f 7f + Touch 90 6b 7f 90 6b 00 +WidgetEnd + +Widget Fader5 + Fader14Bit e4 7f 7f + FB_Fader14Bit e4 7f 7f + Touch 90 6c 7f 90 6c 00 +WidgetEnd + +Widget Fader6 + Fader14Bit e5 7f 7f + FB_Fader14Bit e5 7f 7f + Touch 90 6d 7f 90 6d 00 +WidgetEnd + +Widget Fader7 + Fader14Bit e6 7f 7f + FB_Fader14Bit e6 7f 7f + Touch 90 6e 7f 90 6e 00 +WidgetEnd + +Widget Fader8 + Fader14Bit e7 7f 7f + FB_Fader14Bit e7 7f 7f + Touch 90 6f 7f 90 6f 00 +WidgetEnd + +Widget MasterFader + Fader14Bit e8 7f 7f + FB_Fader14Bit e8 7f 7f + Touch e8 7f 7f e8 7f 00 +WidgetEnd + +Widget DisplayLower1 + FB_MCUDisplayLower 0 +WidgetEnd + +Widget DisplayLower2 + FB_MCUDisplayLower 1 +WidgetEnd + +Widget DisplayLower3 + FB_MCUDisplayLower 2 +WidgetEnd + +Widget DisplayLower4 + FB_MCUDisplayLower 3 +WidgetEnd + +Widget DisplayLower5 + FB_MCUDisplayLower 4 +WidgetEnd + +Widget DisplayLower6 + FB_MCUDisplayLower 5 +WidgetEnd + +Widget DisplayLower7 + FB_MCUDisplayLower 6 +WidgetEnd + +Widget DisplayLower8 + FB_MCUDisplayLower 7 +WidgetEnd + +Widget DisplayUpper1 + FB_MCUDisplayUpper 0 +WidgetEnd + +Widget DisplayUpper2 + FB_MCUDisplayUpper 1 +WidgetEnd + +Widget DisplayUpper3 + FB_MCUDisplayUpper 2 +WidgetEnd + +Widget DisplayUpper4 + FB_MCUDisplayUpper 3 +WidgetEnd + +Widget DisplayUpper5 + FB_MCUDisplayUpper 4 +WidgetEnd + +Widget DisplayUpper6 + FB_MCUDisplayUpper 5 +WidgetEnd + +Widget DisplayUpper7 + FB_MCUDisplayUpper 6 +WidgetEnd + +Widget DisplayUpper8 + FB_MCUDisplayUpper 7 +WidgetEnd + +Widget TimeDisplay + FB_MCUTimeDisplay +WidgetEnd + +Widget VUMeter1 + FB_MCUVUMeter 0 +WidgetEnd + +Widget VUMeter2 + FB_MCUVUMeter 1 +WidgetEnd + +Widget VUMeter3 + FB_MCUVUMeter 2 +WidgetEnd + +Widget VUMeter4 + FB_MCUVUMeter 3 +WidgetEnd + +Widget VUMeter5 + FB_MCUVUMeter 4 +WidgetEnd + +Widget VUMeter6 + FB_MCUVUMeter 5 +WidgetEnd + +Widget VUMeter7 + FB_MCUVUMeter 6 +WidgetEnd + +Widget VUMeter8 + FB_MCUVUMeter 7 +WidgetEnd + +Widget JogWheelRotaryCW1 + Press b0 3c 01 +WidgetEnd + +Widget JogWheelRotaryCCW1 + Press b0 3c 41 +WidgetEnd \ No newline at end of file diff --git a/plugin-reaper-realearn/deny.toml b/plugin-reaper-realearn/deny.toml new file mode 100644 index 0000000..7178585 --- /dev/null +++ b/plugin-reaper-realearn/deny.toml @@ -0,0 +1,246 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = true +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "BSL-1.0", + "CC0-1.0", + "OFL-1.1", + "Zlib", + "MPL-2.0", + "Unicode-DFS-2016", + "LicenseRef-UFL-1.0", + "OpenSSL", + #"Apache-2.0 WITH LLVM-exception", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.93 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +# The package spec the clarification applies to +crate = "ring" +# The SPDX expression for the license requirements of the crate +expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + { path = "LICENSE", hash = 0xbd0eed23 } +] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/plugin-reaper-realearn/dialogs/Cargo.toml b/plugin-reaper-realearn/dialogs/Cargo.toml new file mode 100644 index 0000000..9836e8e --- /dev/null +++ b/plugin-reaper-realearn/dialogs/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "helgobox-dialogs" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +derive_more.workspace = true +indexmap.workspace = true + +[lints.clippy] +# Enum glob use is not really dangerous in this module. Non-critical. +enum_glob_use = "allow" \ No newline at end of file diff --git a/plugin-reaper-realearn/dialogs/src/base.rs b/plugin-reaper-realearn/dialogs/src/base.rs new file mode 100644 index 0000000..f69d9b8 --- /dev/null +++ b/plugin-reaper-realearn/dialogs/src/base.rs @@ -0,0 +1,759 @@ +#![allow(non_camel_case_types, clippy::upper_case_acronyms)] + +use indexmap::IndexMap; +use std::collections::HashSet; +use std::fmt; +use std::fmt::Display; +use std::fmt::Formatter; +use std::ops::Add; + +pub type Caption = &'static str; + +pub struct ResourceInfo { + global_scope: Scope, + scopes: IndexMap, + optional_dialog_ids: HashSet, + conditional_control_ids: HashSet, + named_ids: Vec, +} + +/// Formats the info as C header file. +/// +/// Useful if you want to preview the dialogs in Visual Studio. +pub struct ResourceInfoAsCHeaderCode<'a>(pub &'a ResourceInfo); + +impl Display for ResourceInfoAsCHeaderCode<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for id in &self.0.named_ids { + writeln!(f, "#define {} {}", id.name, id.value)?; + } + Ok(()) + } +} + +/// Formats the header as Rust code. +/// +/// Uses a similar format like bindgen because previously, bindgen was used to translate +/// the C header file to Rust. +pub struct ResourceInfoAsRustCode<'a>(pub &'a ResourceInfo); + +impl Display for ResourceInfoAsRustCode<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Write module opener + f.write_str("pub mod root {\n")?; + // Write scaling information + ScopeAsRustCode::new("GLOBAL", &self.0.global_scope).fmt(f)?; + for (key, scope) in self.0.scopes.iter() { + ScopeAsRustCode::new(key, scope).fmt(f)?; + } + // Write resource IDs + for id in &self.0.named_ids { + if self.0.optional_dialog_ids.contains(id) + || self.0.conditional_control_ids.contains(id) + { + f.write_str(" #[allow(dead_code)]\n")?; + } + writeln!(f, " pub const {}: u32 = {};", id.name, id.value)?; + } + // Write module closer + f.write_str("}\n")?; + Ok(()) + } +} + +#[derive(Default)] +pub struct Resource { + pub dialogs: Vec, +} + +impl Resource { + pub fn generate_info(&self, context: &Context) -> ResourceInfo { + ResourceInfo { + global_scope: context.global_scope, + scopes: context.scopes.clone(), + optional_dialog_ids: self.optional_dialog_ids().collect(), + conditional_control_ids: self.conditional_control_ids().collect(), + named_ids: self.named_ids().collect(), + } + } + + fn named_ids(&self) -> impl Iterator + '_ { + self.dialogs.iter().flat_map(|dialog| { + fn get_if_named(id: Id) -> Option { + if id.is_named() { + Some(id) + } else { + None + } + } + let named_dialog_id = get_if_named(dialog.id); + let named_control_ids = dialog + .controls + .iter() + .flat_map(|control| get_if_named(control.id)); + named_dialog_id.into_iter().chain(named_control_ids) + }) + } + + fn optional_dialog_ids(&self) -> impl Iterator + '_ { + self.dialogs.iter().filter(|d| d.optional).map(|d| d.id) + } + + fn conditional_control_ids(&self) -> impl Iterator + '_ { + self.dialogs.iter().flat_map(|dialog| { + dialog + .controls + .iter() + .filter(|control| !control.conditions.is_empty()) + .map(|control| control.id) + }) + } +} + +impl Display for Resource { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for (i, dialog) in self.dialogs.iter().enumerate() { + dialog.fmt(f)?; + if i < self.dialogs.len() - 1 { + f.write_str("\n\n")?; + } + } + Ok(()) + } +} + +#[derive(Clone, Default)] +pub struct Dialog { + pub id: Id, + pub optional: bool, + pub rect: Rect, + pub kind: DialogKind, + pub styles: Styles, + pub ex_styles: Styles, + pub caption: Caption, + pub font: Option, + pub controls: Vec, +} + +#[derive(Clone, Default)] +pub struct Styles(pub Vec \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/architecture/images/components-midi-device.svg b/plugin-reaper-realearn/doc/architecture/images/components-midi-device.svg new file mode 100644 index 0000000..4adcc2b --- /dev/null +++ b/plugin-reaper-realearn/doc/architecture/images/components-midi-device.svg @@ -0,0 +1,695 @@ + + +components + + +cluster_realearn + +ReaLearn + + +cluster_instance + +Instance + + + +MidiInputDevice + +MidiInputDevice + + + +RealearnAudioHook + +RealearnAudioHook + + + +MidiInputDevice->RealearnAudioHook + + +MIDI control events (device input) + + + +MidiOutputDevice + +MidiOutputDevice + + + +FxChain + +FxChain + + + +RealearnPlugin + +RealearnPlugin + + + +FxChain->RealearnPlugin + + +MIDI control events (FX input) + + + +Reaper + +Reaper + + + +RealearnControlSurfaceMiddleware + +RealearnControlSurfaceMiddleware + + + +Reaper->RealearnControlSurfaceMiddleware + + +REAPER change events + + + +App + +App + + + +Server + +Server + + + +Backbone + +Backbone + + + +MainProcessor + +MainProcessor + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +REAPER and instance change events + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +OSC control events + + + +RealearnAudioHook->MidiOutputDevice + + +MIDI feedback events (device output) + + + +RealTimeProcessor + +RealTimeProcessor + + + +RealearnAudioHook->RealTimeProcessor + + +MIDI control events (device input) + + + +OscOutputDevice + +OscOutputDevice + + + + + + + + +RealearnPlugin->RealTimeProcessor + + +MIDI control events (FX input) + + + +RealearnUi + +RealearnUi + + + +Session + +Session + + + +Session->RealearnUi + + +Events + + + +Session->MainProcessor + + +Sync data + + + +InstanceState + +InstanceState + + + +InstanceState->RealearnControlSurfaceMiddleware + + +Instance change events + + + +RealTimeProcessor->FxChain + + +MIDI feedback events (FX output) + + + +RealTimeProcessor->MainProcessor + + +Control values (from MIDI events) + + + +MainProcessor->RealearnAudioHook + + +MIDI feedback events (device output) + + + +MainProcessor->OscOutputDevice + + +OSC feedback events + + + +MainProcessor->Session + + +Events + + + +MainProcessor->RealTimeProcessor + + +Sync data + + + +MainProcessor->RealTimeProcessor + + +MIDI feedback events (FX output) + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/architecture/images/components-midi-fx.svg b/plugin-reaper-realearn/doc/architecture/images/components-midi-fx.svg new file mode 100644 index 0000000..8834d15 --- /dev/null +++ b/plugin-reaper-realearn/doc/architecture/images/components-midi-fx.svg @@ -0,0 +1,695 @@ + + +components + + +cluster_realearn + +ReaLearn + + +cluster_instance + +Instance + + + +MidiInputDevice + +MidiInputDevice + + + +RealearnAudioHook + +RealearnAudioHook + + + +MidiInputDevice->RealearnAudioHook + + +MIDI control events (device input) + + + +MidiOutputDevice + +MidiOutputDevice + + + +FxChain + +FxChain + + + +RealearnPlugin + +RealearnPlugin + + + +FxChain->RealearnPlugin + + +MIDI control events (FX input) + + + +Reaper + +Reaper + + + +RealearnControlSurfaceMiddleware + +RealearnControlSurfaceMiddleware + + + +Reaper->RealearnControlSurfaceMiddleware + + +REAPER change events + + + +App + +App + + + +Server + +Server + + + +Backbone + +Backbone + + + +MainProcessor + +MainProcessor + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +REAPER and instance change events + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +OSC control events + + + +RealearnAudioHook->MidiOutputDevice + + +MIDI feedback events (device output) + + + +RealTimeProcessor + +RealTimeProcessor + + + +RealearnAudioHook->RealTimeProcessor + + +MIDI control events (device input) + + + +OscOutputDevice + +OscOutputDevice + + + + + + + + +RealearnPlugin->RealTimeProcessor + + +MIDI control events (FX input) + + + +RealearnUi + +RealearnUi + + + +Session + +Session + + + +Session->RealearnUi + + +Events + + + +Session->MainProcessor + + +Sync data + + + +InstanceState + +InstanceState + + + +InstanceState->RealearnControlSurfaceMiddleware + + +Instance change events + + + +RealTimeProcessor->FxChain + + +MIDI feedback events (FX output) + + + +RealTimeProcessor->MainProcessor + + +Control values (from MIDI events) + + + +MainProcessor->RealearnAudioHook + + +MIDI feedback events (device output) + + + +MainProcessor->OscOutputDevice + + +OSC feedback events + + + +MainProcessor->Session + + +Events + + + +MainProcessor->RealTimeProcessor + + +Sync data + + + +MainProcessor->RealTimeProcessor + + +MIDI feedback events (FX output) + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/architecture/images/components-osc.svg b/plugin-reaper-realearn/doc/architecture/images/components-osc.svg new file mode 100644 index 0000000..2b4dfe4 --- /dev/null +++ b/plugin-reaper-realearn/doc/architecture/images/components-osc.svg @@ -0,0 +1,695 @@ + + +components + + +cluster_realearn + +ReaLearn + + +cluster_instance + +Instance + + + +MidiInputDevice + +MidiInputDevice + + + +RealearnAudioHook + +RealearnAudioHook + + + +MidiInputDevice->RealearnAudioHook + + +MIDI control events (device input) + + + +MidiOutputDevice + +MidiOutputDevice + + + +FxChain + +FxChain + + + +RealearnPlugin + +RealearnPlugin + + + +FxChain->RealearnPlugin + + +MIDI control events (FX input) + + + +Reaper + +Reaper + + + +RealearnControlSurfaceMiddleware + +RealearnControlSurfaceMiddleware + + + +Reaper->RealearnControlSurfaceMiddleware + + +REAPER change events + + + +App + +App + + + +Server + +Server + + + +Backbone + +Backbone + + + +MainProcessor + +MainProcessor + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +REAPER and instance change events + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +OSC control events + + + +RealearnAudioHook->MidiOutputDevice + + +MIDI feedback events (device output) + + + +RealTimeProcessor + +RealTimeProcessor + + + +RealearnAudioHook->RealTimeProcessor + + +MIDI control events (device input) + + + +OscOutputDevice + +OscOutputDevice + + + + + + + + +RealearnPlugin->RealTimeProcessor + + +MIDI control events (FX input) + + + +RealearnUi + +RealearnUi + + + +Session + +Session + + + +Session->RealearnUi + + +Events + + + +Session->MainProcessor + + +Sync data + + + +InstanceState + +InstanceState + + + +InstanceState->RealearnControlSurfaceMiddleware + + +Instance change events + + + +RealTimeProcessor->FxChain + + +MIDI feedback events (FX output) + + + +RealTimeProcessor->MainProcessor + + +Control values (from MIDI events) + + + +MainProcessor->RealearnAudioHook + + +MIDI feedback events (device output) + + + +MainProcessor->OscOutputDevice + + +OSC feedback events + + + +MainProcessor->Session + + +Events + + + +MainProcessor->RealTimeProcessor + + +Sync data + + + +MainProcessor->RealTimeProcessor + + +MIDI feedback events (FX output) + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/architecture/images/components.svg b/plugin-reaper-realearn/doc/architecture/images/components.svg new file mode 100644 index 0000000..4fd4b2b --- /dev/null +++ b/plugin-reaper-realearn/doc/architecture/images/components.svg @@ -0,0 +1,695 @@ + + +components + + +cluster_realearn + +ReaLearn + + +cluster_instance + +Instance + + + +MidiInputDevice + +MidiInputDevice + + + +RealearnAudioHook + +RealearnAudioHook + + + +MidiInputDevice->RealearnAudioHook + + +MIDI control events (device input) + + + +MidiOutputDevice + +MidiOutputDevice + + + +FxChain + +FxChain + + + +RealearnPlugin + +RealearnPlugin + + + +FxChain->RealearnPlugin + + +MIDI control events (FX input) + + + +Reaper + +Reaper + + + +RealearnControlSurfaceMiddleware + +RealearnControlSurfaceMiddleware + + + +Reaper->RealearnControlSurfaceMiddleware + + +REAPER change events + + + +App + +App + + + +Server + +Server + + + +Backbone + +Backbone + + + +MainProcessor + +MainProcessor + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +REAPER and instance change events + + + +RealearnControlSurfaceMiddleware->MainProcessor + + +OSC control events + + + +RealearnAudioHook->MidiOutputDevice + + +MIDI feedback events (device output) + + + +RealTimeProcessor + +RealTimeProcessor + + + +RealearnAudioHook->RealTimeProcessor + + +MIDI control events (device input) + + + +OscOutputDevice + +OscOutputDevice + + + + + + + + +RealearnPlugin->RealTimeProcessor + + +MIDI control events (FX input) + + + +RealearnUi + +RealearnUi + + + +Session + +Session + + + +Session->RealearnUi + + +Events + + + +Session->MainProcessor + + +Sync data + + + +InstanceState + +InstanceState + + + +InstanceState->RealearnControlSurfaceMiddleware + + +Instance change events + + + +RealTimeProcessor->FxChain + + +MIDI feedback events (FX output) + + + +RealTimeProcessor->MainProcessor + + +Control values (from MIDI events) + + + +MainProcessor->RealearnAudioHook + + +MIDI feedback events (device output) + + + +MainProcessor->OscOutputDevice + + +OSC feedback events + + + +MainProcessor->Session + + +Events + + + +MainProcessor->RealTimeProcessor + + +Sync data + + + +MainProcessor->RealTimeProcessor + + +MIDI feedback events (FX output) + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/architecture/images/logo.svg b/plugin-reaper-realearn/doc/architecture/images/logo.svg new file mode 100644 index 0000000..6e2932f --- /dev/null +++ b/plugin-reaper-realearn/doc/architecture/images/logo.svg @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + .......... + + + + + + ReaLearn + 2 + + + diff --git a/plugin-reaper-realearn/doc/architecture/images/modules.svg b/plugin-reaper-realearn/doc/architecture/images/modules.svg new file mode 100644 index 0000000..6b40625 --- /dev/null +++ b/plugin-reaper-realearn/doc/architecture/images/modules.svg @@ -0,0 +1,541 @@ + + +modules + + +cluster_realearn + +ReaLearn + + + +reaper_rs + +reaper-rs + + + +helgoboss_midi + +helgoboss-midi + + + +reaper_rs->helgoboss_midi + + + + + +helgoboss_learn + +helgoboss-learn + + + +helgoboss_learn->helgoboss_midi + + + + + +main + +main + + + +main->reaper_rs + + + + + +main->helgoboss_learn + + + + + +main->helgoboss_midi + + + + + +swell_ui + +swell-ui + + + +main->swell_ui + + + + + +api + +api + + + +main->api + + + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/architecture/images/onion-layers.svg b/plugin-reaper-realearn/doc/architecture/images/onion-layers.svg new file mode 100644 index 0000000..7786a8a --- /dev/null +++ b/plugin-reaper-realearn/doc/architecture/images/onion-layers.svg @@ -0,0 +1,453 @@ +infrastructureGUIAPIPersistenceServermanagementprocessingbasemay use code in \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/controllers.adoc b/plugin-reaper-realearn/doc/controllers.adoc new file mode 100644 index 0000000..482e9ab --- /dev/null +++ b/plugin-reaper-realearn/doc/controllers.adoc @@ -0,0 +1,2 @@ +The list of tested controllers is now part of the Wiki. +You can find it link:https://github.com/helgoboss/helgobox/wiki/ReaLearn-Controllers[here]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/README.adoc b/plugin-reaper-realearn/doc/helgobox/README.adoc new file mode 100644 index 0000000..9ac861d --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/README.adoc @@ -0,0 +1,6 @@ += Helgobox Reference +:experimental: + +This reference is written in AsciiDoc and follows the directory conventions of the documentation site generator link:https://antora.org/[Antora]. + +A good place to start is link:modules/ROOT/pages/introduction.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/antora.yml b/plugin-reaper-realearn/doc/helgobox/antora.yml new file mode 100644 index 0000000..fb51378 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/antora.yml @@ -0,0 +1,10 @@ +name: helgobox +title: Helgobox Reference +version: ~ +nav: + - modules/ROOT/nav.adoc +start_page: introduction.adoc +asciidoc: + attributes: + # For making menu and button macros work + experimental: true \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/area/nav-bar-0.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/area/nav-bar-0.png new file mode 100644 index 0000000..ccdbba3 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/area/nav-bar-0.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/area/window-title-bar-0.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/area/window-title-bar-0.png new file mode 100644 index 0000000..be07c29 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/area/window-title-bar-0.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/enable-disable-global-control.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/enable-disable-global-control.png new file mode 100644 index 0000000..dc47a32 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/enable-disable-global-control.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/home.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/home.png new file mode 100644 index 0000000..3124db1 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/home.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/pick-instance.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/pick-instance.png new file mode 100644 index 0000000..c7a633b Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/pick-instance.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/playtime.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/playtime.png new file mode 100644 index 0000000..a6a147a Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/playtime.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/projection.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/projection.png new file mode 100644 index 0000000..896d69a Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/projection.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/show-helgobox-plugin.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/show-helgobox-plugin.png new file mode 100644 index 0000000..b2667bb Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/navbar/show-helgobox-plugin.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/color-scheme.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/color-scheme.png new file mode 100644 index 0000000..afe902d Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/color-scheme.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/contrast.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/contrast.png new file mode 100644 index 0000000..f2ec682 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/contrast.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/custom-color-rendering.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/custom-color-rendering.png new file mode 100644 index 0000000..5cf67ae Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/custom-color-rendering.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/dim-opacity.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/dim-opacity.png new file mode 100644 index 0000000..4ea60d8 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/dim-opacity.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-development-status-hints.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-development-status-hints.png new file mode 100644 index 0000000..d3e07be Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-development-status-hints.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-license-info.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-license-info.png new file mode 100644 index 0000000..ddd0399 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-license-info.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-tooltip-if-help-panel-hidden.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-tooltip-if-help-panel-hidden.png new file mode 100644 index 0000000..f7bb319 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/display-tooltip-if-help-panel-hidden.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/hide-window-when-escape-pressed.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/hide-window-when-escape-pressed.png new file mode 100644 index 0000000..6fc0bfb Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/hide-window-when-escape-pressed.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/overall-size.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/overall-size.png new file mode 100644 index 0000000..d60c68b Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/overall-size.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/prefer-native-popup-menus.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/prefer-native-popup-menus.png new file mode 100644 index 0000000..811de1e Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/prefer-native-popup-menus.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/prefer-tidy-appearance.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/prefer-tidy-appearance.png new file mode 100644 index 0000000..9a40e07 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/prefer-tidy-appearance.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/roundness.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/roundness.png new file mode 100644 index 0000000..bd5fb1a Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/roundness.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/theme-mode.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/theme-mode.png new file mode 100644 index 0000000..ffb10d9 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/theme-mode.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/use-context-coloring.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/use-context-coloring.png new file mode 100644 index 0000000..ccb6df7 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/use-context-coloring.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/use-host-colors.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/use-host-colors.png new file mode 100644 index 0000000..ec23842 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/settings/use-host-colors.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/close-window.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/close-window.png new file mode 100644 index 0000000..686d790 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/close-window.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/dim.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/dim.png new file mode 100644 index 0000000..a5ded8e Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/dim.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/full-screen.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/full-screen.png new file mode 100644 index 0000000..c5825f8 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/full-screen.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/helgobox-info.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/helgobox-info.png new file mode 100644 index 0000000..9cc7152 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/helgobox-info.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/help.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/help.png new file mode 100644 index 0000000..52d3a91 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/help.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/move-window.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/move-window.png new file mode 100644 index 0000000..9041eaf Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/move-window.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/panic-midi.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/panic-midi.png new file mode 100644 index 0000000..039ed75 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/panic-midi.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/pause-arrangement.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/pause-arrangement.png new file mode 100644 index 0000000..a127c2e Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/pause-arrangement.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/play-arrangement.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/play-arrangement.png new file mode 100644 index 0000000..4310343 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/play-arrangement.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/redo.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/redo.png new file mode 100644 index 0000000..ac49130 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/redo.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/refresh.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/refresh.png new file mode 100644 index 0000000..72ea4df Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/refresh.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/save-project.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/save-project.png new file mode 100644 index 0000000..602feff Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/save-project.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/settings.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/settings.png new file mode 100644 index 0000000..f47edfc Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/settings.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/stop-arrangement.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/stop-arrangement.png new file mode 100644 index 0000000..abdc9ff Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/stop-arrangement.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/theme-mode.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/theme-mode.png new file mode 100644 index 0000000..0bce473 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/theme-mode.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/theme-switcher.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/theme-switcher.png new file mode 100644 index 0000000..96058d3 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/theme-switcher.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/toggle-nav-bar.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/toggle-nav-bar.png new file mode 100644 index 0000000..1aa5324 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/toggle-nav-bar.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/undo.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/undo.png new file mode 100644 index 0000000..156faf6 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/elements/title-bar/undo.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/about.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/about.png new file mode 100644 index 0000000..97e598f Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/about.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/home-screen.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/home-screen.png new file mode 100644 index 0000000..c1f22f0 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/home-screen.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/settings.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/settings.png new file mode 100644 index 0000000..9258e82 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/generated/screenshots/main/settings.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/app-window.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/app-window.png new file mode 100644 index 0000000..ad78ee9 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/app-window.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/plugin-window.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/plugin-window.png new file mode 100644 index 0000000..66ee66e Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/plugin-window.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/welcome-dialog.png b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/welcome-dialog.png new file mode 100644 index 0000000..9da7e71 Binary files /dev/null and b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/images/screenshots/welcome-dialog.png differ diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/nav.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/nav.adoc new file mode 100644 index 0000000..ab10db1 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/nav.adoc @@ -0,0 +1,18 @@ +* xref:introduction.adoc[] +* xref:products.adoc[] +* xref:installation.adoc[] +* xref:key-concepts.adoc[] +* xref:plug-in.adoc[] +** xref:plug-in/user-interface.adoc[] +*** xref:plug-in/user-interface/welcome-dialog.adoc[] +*** xref:plug-in/user-interface/menu-bar.adoc[] +* xref:app.adoc[] +** xref:app/user-interface.adoc[] +*** xref:app/user-interface/general.adoc[] +*** xref:app/user-interface/title-bar.adoc[] +*** xref:app/user-interface/navigation-bar.adoc[] +*** xref:app/user-interface/settings-dialog.adoc[] +*** xref:app/user-interface/keyboard-shortcuts.adoc[] +*** xref:app/user-interface/cli.adoc[] +* xref:reaper-actions.adoc[] +* xref:configuration-files.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app.adoc new file mode 100644 index 0000000..8be5bb0 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app.adoc @@ -0,0 +1,88 @@ += Helgobox App + +.What is the Helgobox App? +____ +include::partial$app-desc.adoc[] +____ + +Have you created an xref:key-concepts.adoc#instance[] of the xref:plug-in.adoc[] already? Good. Then you can connect to it with the _Helgobox App_. + +You can run the app in two ways: *Embedded* or *Remote* + +[[app-embedded-mode]] +== Embedded mode + +Each Helgobox xref:key-concepts.adoc#instance[] can display its own xref:app.adoc[] window, directly embedded into REAPER: + +. Open the desired xref:plug-in.adoc[] instance +. Press menu:Menu[Show App] + +Embedded mode is the recommended way to run the app. It offers seamless integration with REAPER. + +[[app-remote-mode]] +== Remote mode + +There's an additional way to use the app: To run it as a separate program that connects with REAPER over a network connection. + +=== Use cases + +Running the app in remote mode opens up interesting possibilities. + +Crash protection:: +You can start the app on the same machine, but outside of REAPER. +If it crashes for some reason, only the app itself will crash. +REAPER and the xref:plug-in.adoc[] will continue running. It's similar to running plug-ins in REAPER in bridged mode. This could be interesting for live scenarios. + +Additional views:: +You can open additional app windows on the same machine and connect them to the same xref:key-concepts.adoc#instance[]. This gives you multiple views on the same instance. + +Performance optimization:: +You can run the app on an extra computer and connect it to REAPER on the main machine. This way, you can be sure that the app doesn't take any valuable processing power away from REAPER and the xref:plug-in.adoc[]. + +Touch screen operation:: +Maybe your main machine is a powerful Mac mini standing somewhere further away from you. You could take a Windows tablet (and in the future an iOS or Android tablet) and run the app there, connecting to your main machine. + +Running Playtime on Linux (temporarily):: +Playtime's Linux support is currently limited to remote mode. Embedded mode is still in the works. + +=== How to do it + +Here's what you need to do to run the app in remote mode. + +==== Starting the server + +To allow connections to Helgobox from computers in the same network, you need to start the xref:plug-in/user-interface/menu-bar.adoc#server[Helgobox Server]. + +. Open the xref:plug-in.adoc[] +. Press menu:Menu[Server > Enable and start!] + +You need to do this only once. Next time you start REAPER and Helgobox, the server will start automatically! + +==== Starting the app + +Just like the plug-in, the app is part of the Helgobox installation and is located in your REAPER resource directory: + +. Open an arbitrary instance of the xref:plug-in.adoc[] ++ +.Hello, Playtime for Linux testers! +TIP: If you are here because you want to try Playtime for Linux *Stage 1*, pressing the image:playtime::screenshots/playtime-toolbar-icon.png[Playtime,width=50, pdfwidth=8mm] button should already have created a "Playtime" track for you, along with a xref:plug-in.adoc[] instance. Simply open that existing one! +. Press menu:Menu[Server > Open app folder] +. Start the app executable: +[horizontal] +Windows:: `helgobox.exe` +macOS:: `Contents/MacOS/helgobox` +Linux:: `helgobox` + +By default, the app connects to the REAPER process running on the _same machine_ (to the xref:plug-in/user-interface/menu-bar.adoc#server[Helgobox Server] running in that process, to be accurate). You can change that by running the app with special xref:app/user-interface/cli.adoc[command line arguments]. + +Also by default, the app connects to the first xref:key-concepts.adoc#instance[] running in that REAPER process. You can switch to another instance (within the same process) by pressing the xref:app/user-interface/navigation-bar.adoc#navbar-pick-instance[] button or by running the app with special xref:app/user-interface/cli.adoc[command line arguments]. + +.Copying the app directory somewhere else +[CAUTION] +==== +You may copy the app directory to another location or even to another computer. However, you should renew your copy whenever you update Helgobox! An update often contains both a new version of the xref:plug-in.adoc[] **and** a new version of the xref:app.adoc[]. + +If you connect the app to a plug-in and the versions don't match, you will see the following error message: + +> Host API version doesn't match +==== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface.adoc new file mode 100644 index 0000000..95e52fa --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface.adoc @@ -0,0 +1,8 @@ += User interface + +Unlike the xref:plug-in.adoc[], the App offers a modern and fancy user interface. +At the moment, it primarily provides the user interface for xref:playtime::introduction.adoc[Playtime], but it will contain more stuff in the future. + +.App window +image::generated/screenshots/main/home-screen.png[] + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/about-dialog.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/about-dialog.adoc new file mode 100644 index 0000000..9ebf6c3 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/about-dialog.adoc @@ -0,0 +1,9 @@ += About dialog + +This dialog appears when you click the xref:app/user-interface/help-menu.adoc#title-bar-about[] button in the help menu. + +image::generated/screenshots/main/about.png[] + +The version number displayed here is the version of the xref:app.adoc[], not the one of the controlled xref:plug-in.adoc[]! _App_ and _plug-in_ are often updated together, but that's not always the case. They can evolve independently. + +The app version number is most relevant when running the app in xref:app.adoc#app-remote-mode[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/cli.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/cli.adoc new file mode 100644 index 0000000..cc43a81 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/cli.adoc @@ -0,0 +1,58 @@ += Command line interface + +When running in xref:app.adoc#app-remote-mode[], the app session can optionally be configured using command line arguments. + +== Synopsis + +*helgobox* [_OPTIONS_] + +== Options + +--connection=:: +URL that decides how the app connects to the xref:plug-in.adoc[]. ++ + +.Connection examples +[cols="1,2"] +|=== +| +`grpc:://localhost:39051` + +`grpc:://127.0.0.1:39051` +| +Connects to the plug-in running on the same machine, assuming a xref:configuration-files.adoc#realearn-ini[standard server configuration] (`server_grpc_port=39051`). + +This is the default if the connection option is omitted. + +| +`grpc:://winpc:39051` + +`grpc:://192.168.1.47:39051` +| +Connects to the plug-in running on the machine with the host name `winpc` or the IP address 192.168.1.47. +|=== + +--location:: +Path that decides which page is shown initially. ++ +This is not a file system path. It's comparable to the path of a URL that you would see in a web browser's address bar. ++ + +.Location examples +[cols="1,2"] +|=== +| +`/instance/apc/projection` +| +Opens the xref:realearn::user-interface/projection.adoc[projection page] for the xref:key-concepts.adoc#instance[] with xref:key-concepts.adoc#instance-key[] `apc`. + +| +`/instance/3/playtime` +| +Opens the xref:playtime::user-interface.adoc[Playtime] page for the xref:key-concepts.adoc#instance[] with xref:key-concepts.adoc#instance-id[] *3*. + +Numbers are always interpreted as instance IDs and strings as instance keys. Normally, you want to use an xref:key-concepts.adoc#instance-key[instance key] to refer to the desired instance, because instance IDs are likely to change after restarting REAPER. + + +|=== + +--help:: +Displays an overview of all supported command line options. diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/general.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/general.adoc new file mode 100644 index 0000000..39160f2 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/general.adoc @@ -0,0 +1,59 @@ += General usage + +This page describes the basic usage of common user interface elements in the Helgobox app. + +[[drag-field]] +== Drag field + +image::playtime::generated/screenshots/elements/toolbar/tempo.png[] + +A drag field allows you to adjust numeric values in various ways. + +include::partial$app/user-interface/interactions/mouse-dragging.adoc[] +include::partial$app/user-interface/interactions/mouse-wheel-scrolling.adoc[] +include::partial$app/user-interface/interactions/touchpad-panning.adoc[] +include::partial$app/user-interface/interactions/text-entry.adoc[] +include::partial$app/user-interface/interactions/reset-to-default.adoc[] +include::partial$app/user-interface/interactions/fine-adjustment.adoc[] + +== Knob + +image::playtime::generated/screenshots/elements/toolbar/play-rate.png[] + +A knob allows you to adjust numeric values in various ways. + +include::partial$app/user-interface/interactions/mouse-dragging.adoc[] +include::partial$app/user-interface/interactions/mouse-wheel-scrolling.adoc[] +include::partial$app/user-interface/interactions/touchpad-panning.adoc[] +include::partial$app/user-interface/interactions/text-entry.adoc[] +include::partial$app/user-interface/interactions/reset-to-default.adoc[] +include::partial$app/user-interface/interactions/fine-adjustment.adoc[] + +== Slider + +image::playtime::generated/screenshots/elements/track-panel/volume.png[] + +A slider allows you to adjust numeric values in various ways. + +include::partial$app/user-interface/interactions/mouse-dragging.adoc[] +include::partial$app/user-interface/interactions/mouse-wheel-scrolling.adoc[] +include::partial$app/user-interface/interactions/touchpad-panning.adoc[] +include::partial$app/user-interface/interactions/reset-to-default.adoc[] + +== Search list + +image::playtime::generated/screenshots/main/sequences.png[] + +Search lists allow you to pick an item from a potentially large list. + +icon:search[] Search:: +Simply enter text to drill the list down to items matching your search text. + +icon:keyboard-o[] Navigate:: +Press kbd:[↑] or kbd:[↓] to navigate within the list of items. + +↕ Scroll:: +Scroll within the list using the scrollbar on the right or using the mousewheel. + +icon:mouse-pointer[] Select:: +Click the item or press kbd:[Enter] to select it. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/help-menu.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/help-menu.adoc new file mode 100644 index 0000000..8a150c8 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/help-menu.adoc @@ -0,0 +1,13 @@ += Help menu + +This menu appears when clicking the xref:app/user-interface/title-bar.adoc#title-bar-help[] button in the window title bar. + +include::partial$generated/menus/help/open-helgobox-reference.adoc[] + +include::partial$generated/menus/help/open-helgobox-website.adoc[] + +include::partial$generated/menus/help/highlight-screen-areas.adoc[] + +include::partial$generated/menus/help/about.adoc[] + +See xref:app/user-interface/about-dialog.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/keyboard-shortcuts.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/keyboard-shortcuts.adoc new file mode 100644 index 0000000..cc5b2bf --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/keyboard-shortcuts.adoc @@ -0,0 +1,44 @@ += Keyboard shortcuts + +Within the app, you can use the following keyboard shortcuts. + +|=== +|Shortcut |Purpose + +|[[esc,Esc]] kbd:[Esc] +|Hides the app window. + +Hiding the window doesn't completely unload the app, it really just hides the window. + +|kbd:[F] +|Toggles full-screen mode. + +|kbd:[F1] +|Opens online help for the element that is currently under the mouse cursor. + +|kbd:[Shift+Cmd/Ctrl+H] +|Executes the REAPER action xref:helgobox::reaper-actions.adoc#toggle-app-focus[]. + +|=== + +Depending on the currently selected page, more keyboard shortcuts are available: + +- xref:playtime::user-interface/keyboard-shortcuts.adoc[Playtime shortcuts] + +[TIP] +==== +**Normal REAPER's keyboard shortcuts are generally disabled when the app is focused!** + +This prevents conflicts and ensures flexibility for adding more app shortcuts in the future. + +If you want to use a certain REAPER shortcut while the app is focused, you must make it global: + +. Open menu:Actions[Show action list...] +. Press btn:[Find shortcut...] +. Press the shortcut key ++ +The action list should jump to the action mapped to that key. +. Double-click the desired shortcut in the "Shortcuts for selected action" list +. Change the property "Scope" to **Global** +. Press btn:[OK] +==== diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/navigation-bar.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/navigation-bar.adoc new file mode 100644 index 0000000..c568be1 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/navigation-bar.adoc @@ -0,0 +1,22 @@ += Navigation bar + +The bar on the left is the _Navigation bar_. + +Its main purpose is to switch between different pages. +You can show or hide it using the xref:app/user-interface/title-bar.adoc#title-bar-toggle-nav-bar[] button in the xref:app/user-interface/title-bar.adoc[]. + +include::partial$generated/elements/navbar/home.adoc[] + +include::partial$generated/elements/navbar/projection.adoc[] + +include::partial$generated/elements/navbar/playtime.adoc[] + +include::partial$generated/elements/navbar/enable-disable-global-control.adoc[] + +See xref:realearn::further-concepts/instance.adoc#auto-units[]. + +include::partial$generated/elements/navbar/show-helgobox-plugin.adoc[] + +include::partial$generated/elements/navbar/pick-instance.adoc[] + +This is only displayed if you run the app in xref:app.adoc#app-remote-mode[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/settings-dialog.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/settings-dialog.adoc new file mode 100644 index 0000000..750f45e --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/settings-dialog.adoc @@ -0,0 +1,50 @@ += Settings dialog + +The dialog allows you to adjust the majority of global settings for Helgobox and its contained products. +You can open it by pressing the xref:app/user-interface/title-bar.adoc#title-bar-settings[] button in the xref:app/user-interface/title-bar.adoc[]. + +image::generated/screenshots/main/settings.png[] + +For a description of the product-specific settings, see: + +* xref:realearn::user-interface/settings-dialog.adoc[ReaLearn settings] +* xref:playtime:ROOT:user-interface/dialogs/settings-dialog.adoc[Playtime settings] + +== Appearance + +include::partial$generated/elements/settings/overall-size.adoc[] + +include::partial$generated/elements/settings/roundness.adoc[] + +.For Playtime users +TIP: If you want slot cells with rounded corners, play with the xref:playtime:ROOT:user-interface/dialogs/settings-dialog.adoc#settings-cell-color-position[] setting! + +include::partial$generated/elements/settings/prefer-tidy-appearance.adoc[] + +include::partial$generated/elements/settings/display-development-status-hints.adoc[] + +include::partial$generated/elements/settings/display-tooltip-if-help-panel-hidden.adoc[] + +include::partial$generated/elements/settings/display-license-info.adoc[] + +include::partial$generated/elements/settings/prefer-native-popup-menus.adoc[] + +include::partial$generated/elements/settings/theme-mode.adoc[] + +include::partial$generated/elements/settings/use-context-coloring.adoc[] + +include::partial$generated/elements/settings/use-host-colors.adoc[] + +include::partial$generated/elements/settings/contrast.adoc[] + +include::partial$generated/elements/settings/custom-color-rendering.adoc[] + +include::partial$generated/elements/settings/color-scheme.adoc[] + +== Title bar + +include::partial$generated/elements/settings/dim-opacity.adoc[] + +== Shortcuts + +include::partial$generated/elements/settings/hide-window-when-escape-pressed.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/title-bar.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/title-bar.adoc new file mode 100644 index 0000000..40ff02b --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/app/user-interface/title-bar.adoc @@ -0,0 +1,55 @@ += Title bar + +The bar on the top of the app window is the _Title bar_. +It primarily provides general functions, such as window controls, access to settings, and quick access to the REAPER transport. + +image::generated/screenshots/elements/area/window-title-bar-0.png[] + +include::partial$generated/elements/title-bar/close-window.adoc[] + +This has the same effect as pressing xref:helgobox::plug-in/user-interface/menu-bar.adoc#close-app[]. + +include::partial$generated/elements/title-bar/full-screen.adoc[] + +include::partial$generated/elements/title-bar/move-window.adoc[] + +include::partial$generated/elements/title-bar/toggle-nav-bar.adoc[] + +include::partial$generated/elements/title-bar/theme-mode.adoc[] + +include::partial$generated/elements/title-bar/dim.adoc[] + +The opacity of the opaque window can be adjusted via xref:app/user-interface/settings-dialog.adoc#settings-dim-opacity[]. + +include::partial$generated/elements/title-bar/settings.adoc[] + +See xref:app/user-interface/settings-dialog.adoc[]. + +include::partial$generated/elements/title-bar/refresh.adoc[] + +include::partial$generated/elements/title-bar/undo.adoc[] + +include::partial$generated/elements/title-bar/redo.adoc[] + +include::partial$generated/elements/title-bar/theme-switcher.adoc[] + +Themes are simply pre-selected combinations of various appearance settings. +Once you’ve selected a theme, you can further customize it by adjusting the settings. + +include::partial$generated/elements/title-bar/panic-midi.adoc[] + +include::partial$generated/elements/title-bar/save-project.adoc[] + +include::partial$generated/elements/title-bar/play-arrangement.adoc[] + +include::partial$generated/elements/title-bar/stop-arrangement.adoc[] + +include::partial$generated/elements/title-bar/pause-arrangement.adoc[] + +include::partial$generated/elements/title-bar/help.adoc[] + +See xref:app/user-interface/help-menu.adoc[]. + +include::partial$generated/elements/title-bar/helgobox-info.adoc[] + +See xref:key-concepts.adoc#instance-id[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/configuration-files.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/configuration-files.adoc new file mode 100644 index 0000000..73c571d --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/configuration-files.adoc @@ -0,0 +1,34 @@ += Configuration files + +Helgobox creates and/or reads a few files in REAPER's resource directory. + +For product-specific configuration files, please refer to: + +* xref:realearn::configuration-files.adoc[ReaLearn configuration files] +* xref:playtime::configuration-files.adoc[Playtime configuration files] + +Helgobox itself maintains the following files: + +`Data/helgoboss`:: Directory which contains data such as presets or resources that need to be distributed via ReaPack + +`Data/helgoboss/archives`:: Directory which contains archives e.g. the compressed app, distributed via ReaPack + +`Data/helgoboss/doc`:: Contains offline documentation as PDFs + +`Helgoboss`:: Directory which contains user-specific or device-specific data, not touched via ReaPack + +`licensing.json`:: Contains license keys + +`Helgoboss/App/bin`:: Contains the uncompressed xref:app.adoc[], if installed + +`Helgoboss/App/etc/settings.json`:: Contains the global settings for Helgobox and all of its contained products + +`Helgoboss/Pot/previews`:: Directory which contains previews recorded by xref:products.adoc#pot-browser[Pot Browser] + +`Helgoboss/Server/certificates`:: Contains a list of certificates and corresponding private keys in order to allow encrypted communication with ReaLearn Companion and App. + +[[realearn-ini]] `Helgoboss/ReaLearn/realearn.ini`:: Very basic global configuration. +The reference to ReaLearn is there just for historical reasons. +Most properties in here affect Helgobox as a whole. ++ +Currently supported properties: `server_enabled`, `server_http_port`, `server_https_port`, `server_grpc_port`, `companion_web_app_url`, `showed_welcome_screen`, `background_colors_enabled` \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/installation.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/installation.adoc new file mode 100644 index 0000000..7d69fc8 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/installation.adoc @@ -0,0 +1,186 @@ += Installation +:plugin-name: "VSTi: Helgobox - ReaLearn & Playtime (Helgoboss)" +:package-name: "Helgobox: ReaLearn & Playtime" + +Helgobox is provided as https://reapack.com/[ReaPack] package. +ReaPack is a popular 3rd-party package manager for REAPER that makes installing add-ons easy and keeps your installation up to date effortlessly. + +== System requirements + +Please check below system requirements before attempting to install Helgobox! + +=== REAPER + +* Playtime needs REAPER 7.23 or later +* ReaLearn works acceptably with older REAPER versions, but it works better with REAPER 7.23 or later + +[[windows]] +=== Windows + +* xref:plug-in.adoc[] needs Windows 7 or later +* xref:app.adoc[] needs Windows 8 or later + +[[macos]] +=== macOS + +* xref:plug-in.adoc[] needs macOS 10.12 or later +* xref:app.adoc[] needs macOS 10.15 or later +* For macOS 10.14 or later, all below-mentioned installation options should work +* **For macOS 10.13 or earlier, installation via installer is not supported. +You need to install via ReaPack!** + +[[linux]] +=== Linux + +* On x64 architecture: Needs at least glibc v2.31 (for example, Ubuntu 20+, Debian 11+) +* On arm64 architecture: Needs at least glibc v2.35 (for example, Ubuntu 22+, Debian 12+) +* **Installation via installer is not supported. +You need to install via ReaPack!** +* Needs libxdo. +You can install it like this: ++ +[source,shell] +.Debian, Ubuntu +---- +sudo apt-get install -y libxdo-dev +---- ++ +[source,shell] +.openSUSE +---- +sudo zypper install xdotool +---- +* Other dependencies that you might need to install manually, depending on your Linux distribution: `libx11`, `libstdc++`, `libudev` +* If {plugin-name} isn't available after installation, REAPER failed to scan it. And that is most likely due to a missing dependency. In that case, please link:https://github.com/helgoboss/helgobox/issues/503#issuecomment-1320964181[try this to analyze the issue] and send the result to info@helgoboss.org. + +IMPORTANT: If you want to use a _Stream Deck_ controller with ReaLearn on Linux, xref:realearn::sources/stream-deck.adoc#linux[more setup] is necessary. + +NOTE: A more detailed system compatibility table can be found link:https://www.helgoboss.org/projects/helgobox#requirements[here]. + +== Installing Helgobox + +Please choose: + +[loweralpha] +. <> +. <> +. <> +. <> + +[[installer]] +=== I want an installer 👍 + +For users who don't have ReaPack installed already, there's a **convenient installer** that automatically installs both ReaPack and Helgobox. +It gets you up and running in no time, supporting even portable REAPER installations! + +You can use it if you have Windows 7+ or macOS 10.14+. + +Simply press the following link and follow the instructions: link:https://reaboot.com/install/https%3A%2F%2Fraw.githubusercontent.com%2Fhelgoboss%2Fhelgobox%2Fmaster%2Freaboot.json[Install Helgobox] + +[[i-have-reapack]] +=== I have ReaPack and want to use it to install Helgobox + +Here's how you can install Helgobox via ReaPack: + +. In REAPER, select menu:Extensions[ReaPack > Import repositories…] +. Copy and paste the following repository URL into the text area: ++ + https://github.com/helgoboss/reaper-packages/raw/master/index.xml ++ +. Select menu:Extensions[ReaPack > Browse packages…] +. Search for `helgobox` +. Right mouse click on the package {package-name} and choose menu:Install…[] +. Press btn:[OK] or btn:[Apply] +. Restart REAPER + +[[i-want-to-install-reapack]] +=== I don't have ReaPack but want to use it to install Helgobox + +. Install ReaPack, following the link:https://reapack.com/user-guide[installation section of its user guide] +. Restart REAPER +. Follow the instructions in <> + +[[install-without-reapack]] +=== I want to install Helgobox manually, without ReaPack + +If you are more the download type of person, you can find the latest `dll`, `dylib` and `so` files here at GitHub on the https://github.com/helgoboss/helgobox/releases[releases page] for manual installation. + +You also must install Helgobox manually if you plan to use it in both REAPER for Windows 32-bit and REAPER for Windows 64-bit because then it's important to use two separate VST plug-in directories. + +== Validating the installation + +You know that Helgobox is installed successfully if you see the menu menu:Extensions[Helgobox]. + +In addition, you should see a plug-in entry {plugin-name} after right-clicking the empty area of REAPER's track panel and choosing menu:Insert virtual instrument on new track...[]. + +NOTE: On Helgobox for Linux, the extension menu entry doesn't exist yet. +But the plug-in should be there. + +== Troubleshooting + +=== Helgobox plug-in doesn't appear in the list of plug-ins + +If the plug-in {plugin-name} doesn't appear in the list of plug-ins, please proceed as follows: + +. If you are on Linux, make sure to follow the <>! + +. Make sure that Helgobox runs as native plug-in! +** By default, each plug-in runs as native plug-in. +If not, you probably changed the configuration at some point. +** You can fix that by right-clicking {plugin-name} in the plug-in list and select menu:Run as[Native only (prevent bridging)]. +** It's technically impossible to run Helgobox as a bridged plug-in! + +TIP: If none of these suggestions help, link:https://github.com/helgoboss/helgobox/issues/new/choose[please report a bug]! + +[[updating-helgobox]] +== Updating Helgobox + +Helgobox development moves fast! +To take advantage of the latest features, improvements and fixes, you should check the link:https://www.helgoboss.org/projects/helgobox[Helgobox homepage] for updates from time to time. + +Here's how you can install the latest stable Helgobox version: + +. Run menu:Extensions[ReaPack > Synchronize packages] +** ReaPack will show you a summary at the end, which tells you whether a new Helgobox version has been installed. +. Restart REAPER + +If <>, you can alternatively just run the link:https://www.helgoboss.org/projects/helgobox#download[Helgobox installer app] again. + +[[downgrading-helgobox]] +== Downgrading Helgobox + +Downgrading is easily possible via ReaPack as well. + +.Be careful when saving projects after updates or downgrades! +[CAUTION] +==== +It is not guaranteed that projects saved with newer versions can still be opened with older versions of Helgobox! Even if it works, saving a project with that older version might overwrite changes that you made in the newer version. That's why Helgobox will warn you in this case. + +If your project is crucial, it is recommended to make a backup before re-saving it with another version. +==== + +. menu:Extensions[ReaPack > Browse packages...] +. Right-click the package {package-name} and navigate to the submenu menu:Versions[] +. Select the desired old version +. Press btn:[OK] or btn:[Apply] +. Restart REAPER + + +[[pre-releases]] +== Testing new features and improvements + +If you want to get access to cutting-edge but untested versions of Helgobox, you can install Helgobox pre-releases. + +=== Install a specific pre-release of Helgobox + +. menu:Extensions[ReaPack > Browse packages...] +. Right-click the package {package-name} and navigate to the submenu menu:Versions[] +. Select the desired pre-release version (versions containing `-pre` or `-rc`) +. Press btn:[OK] or btn:[Apply] +. Restart REAPER + +=== Enable pre-releases globally + +. menu:Extensions[ReaPack > Manage repositories] +. menu:Options…[Enable pre-releases globally (bleeding edge)] +. After that, whenever you synchronize packages, you will get the latest stuff. diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/introduction.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/introduction.adoc new file mode 100644 index 0000000..5fc78b3 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/introduction.adoc @@ -0,0 +1,19 @@ += Introduction + +include::helgobox::partial$intro-header.adoc[] + +|=== +|Last update of text: |`2025-03-15 (v2.18.0)` +|Last update of screenshots: |`2025-03-15 (v2.18.0)` +|=== + +link:https://www.helgoboss.org/projects/helgobox[Helgobox] is an add-on for link:https://www.reaper.fm[REAPER] that unites xref:products.adoc[a few creative tools] under one roof, most importantly link:https://www.helgoboss.org/projects/realearn[ReaLearn] and link:https://www.helgoboss.org/projects/playtime[Playtime]. + +It consists of two technical components: + +xref:plug-in.adoc[]:: +include::partial$plug-in-desc.adoc[] +xref:app.adoc[]:: +include::partial$app-desc.adoc[] + +include::partial$product-specific-info.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/key-concepts.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/key-concepts.adoc new file mode 100644 index 0000000..2d939d5 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/key-concepts.adoc @@ -0,0 +1,27 @@ += Key concepts + +[[instance]] +== Instance + +Helgobox is an instrument plug-in. +That means you can add multiple instances of it, just as you would add multiple instances of a synth or effect. +For example, you could place one instance on the monitoring FX chain and two instances somewhere in your project. + +[[instance-id]] +== Instance ID + +A *randomly assigned*, *non-persistent* identifier that identifies a particular <> and is *guaranteed to be unique* within one REAPER session. + +CAUTION: This ID can change after a restart of REAPER! If you are looking for something that doesn't change, use the <>. + +[[instance-key]] +== Instance key + +A *user-defined*, *persistent* identifier that identifies a particular <>. +The instance key gets relevant if you want to connect to an instance from an xref:app.adoc[] running in xref:app.adoc#app-remote-mode[]. + +You can change the instance key of an instance by changing the xref:realearn::further-concepts/unit.adoc#unit-key[] of the instance's xref:realearn::key-concepts.adoc#unit[main unit] -- they are the same. + + +.Make your instance keys unique! +CAUTION: Because the instance key is user-defined, uniqueness can't be guaranteed. For example, if you just copy and paste a Helgobox FX instance, you end up with two instances that have the same key. You should avoid this. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in.adoc new file mode 100644 index 0000000..833d259 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in.adoc @@ -0,0 +1,27 @@ += Helgobox Plug-In + +.What is the Helgobox Plug-In? +____ +include::partial$plug-in-desc.adoc[] +It is complemented by the xref:app.adoc[]. +____ + +== Adding a new plug-in instance + +The plug-in is fired up just like any other VST instrument in REAPER: By adding an instance of it to an FX chain: + +. Right-click the empty space in REAPER's track control panel +. Select menu:Insert virtual instrument on new track...[] +. Double-click entry "VSTi: Helgobox - ReaLearn & Playtime (Helgoboss)" + +Initially, you see the xref:plug-in/user-interface.adoc[plug-in window]. + +== Opening an existing plug-in instance + +To open an existing plug-in instance: + +1. Scroll to the REAPER track which contains the instance. +2. Press the btn:[FX] button of that track. This opens the track's FX chain. +3. Click on the Helgobox FX instance. + +You will see the xref:plug-in/user-interface.adoc[plug-in window] again. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface.adoc new file mode 100644 index 0000000..e635830 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface.adoc @@ -0,0 +1,9 @@ += User interface + +The plug-in's native user interface features a plain and simple design. +It mainly hosts link:https://www.helgoboss.org/projects/realearn[ReaLearn], the controller integration tool of Helgobox. + +include::partial$realearn-specific-ui-elements.adoc[] + +.Plug-in window +image::screenshots/plugin-window.png[] diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface/menu-bar.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface/menu-bar.adoc new file mode 100644 index 0000000..bba3909 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface/menu-bar.adoc @@ -0,0 +1,58 @@ += Menu bar + +include::partial$realearn-specific-ui-elements.adoc[] + +== Menu button + +This opens the main menu of Helgobox/ReaLearn. +The same menu opens when you right-click an empty area. + +It provides the following general entries. + +[[open-pot-browser]] Open Pot Browser:: +This will open Pot Browser. +See xref:products.adoc#pot-browser[] for details. + +[[show-app]] Show App:: +Shows the xref:helgobox::app.adoc[] associated with this xref:helgobox::key-concepts.adoc#instance[]. + +[[close-app]] Close App:: +Closes the xref:helgobox::app.adoc[] associated with this xref:helgobox::key-concepts.adoc#instance[]. + +[[server]] Server:: +Helgobox features a built-in server which allows the xref:app.adoc[] (and the old ReaLearn Companion App) to connect to Helgobox. +The server runs globally, not per instance! + +[[enable-and-start-server]] Enable and start!::: This starts the server and makes sure it will automatically be started next time you use Helgobox. + +Disable and stop!::: This stops the server and makes sure it will not be started next time you use Helgobox. + +Add firewall rule::: Attempts to add a firewall rule for making the server accessible from other devices or displays instructions on how to do it. + +Open app folder::: Opens the folder which contains the xref:app.adoc[] (in case you want to run it in xref:app.adoc#app-remote-mode[]). + +[[export-to-clipboard]] +== Export to clipboard button + +Pressing the export button allows you to copy Helgobox settings to the clipboard so you can import them in another instance or edit them in a link:https://en.wikipedia.org/wiki/Text_editor[text editor]. + +-- +The following list only describes the general-purpose menu entries. For the rest, see: + +* xref:realearn::user-interface/main-panel/menu-bar.adoc#export-to-clipboard-button[ReaLearn entries] +* xref:playtime::user-interface/menus/export-to-clipboard-menu.adoc[Playtime entries] +-- + +Export instance as JSON:: +Copies a _complete_ dump of this xref:key-concepts.adoc#instance[] to the clipboard in JSON format. + +[[import-from-clipboard]] +== Import from clipboard button + +Pressing the import button does the opposite: It restores whatever Helgobox dump is currently in the clipboard. +It supports JSON or Luau. + +[[help]] +== Help button (?) + +Provides links to the reference and other documentation. diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface/welcome-dialog.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface/welcome-dialog.adoc new file mode 100644 index 0000000..34b262a --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/plug-in/user-interface/welcome-dialog.adoc @@ -0,0 +1,63 @@ += Welcome dialog + +This dialog appears once after installing Helgobox and whenever you start the action xref:reaper-actions.adoc#show-welcome-screen[]. + +image::helgobox::screenshots/welcome-dialog.png[width=500] + +== Checkbox "Add Playtime button to main toolbar" + +Lets you enable or disable a button in the main toolbar that starts xref:playtime::introduction.adoc[Playtime]. + +== Error checkboxes + +Like any other complex piece of software, Helgobox might have link:https://en.wikipedia.org/wiki/Software_bug[bugs]. There are roughly two types of bugs: _Subtle_ bugs and _detectable_ bugs. Subtle bugs are very hard to investigate. Detectable bugs, however, are "great" because they contain an error message and give me (the developer) a clue of what went wrong. This clue is crucial because I need it to fix the bug. + +The error checkboxes let you decide what Helgobox does if it detects a _detectable_ bug. Let's say the bug occurs. Here's what happens, depending on your configuration: + +[cols="1,1,4"] +|=== +|Send errors |Show errors | Effect + +|Off +|Off +| +A small btn:[ERR] notification will show up in the REAPER title bar (easy to miss!). When you click it, you will see only very basic information about the error. + +|Off +|On +| +A console window will appear with detailed information about the error and a plea to send this information to the developer manually. + +|On +|Off +| +If you are online, Helgobox will send detailed information about the error to the developer (using the third-party error tracking service https://sentry.io[Sentry], see our https://www.helgoboss.org/privacy-statement[privacy statement]). In addition, a small btn:[ERR] notification will show up in the REAPER title bar. Clicking it will reveal further instructions. + +If you are offline, a console window will appear with detailed information about the error and a plea to send this information to the developer manually. + +|On +|On +| +If you are online, Helgobox will send detailed information about the error to the developer and show a console window with further instructions. + +If you are offline, a console window will appear with detailed information about the error and a plea to send this information to the developer manually. +|=== + +.Recommendation +[TIP] +==== +I recommend enabling both checkboxes! + +Sending the error automatically makes sure that I learn about the error as soon as possible, even without your manual notification (I know that you are busy with other things than reporting errors icon:smile-o[]). It also means that the error message shown in the console gets much smaller, as Helgobox then only shows an error ID instead of the full info. + +Showing the error in the console in addition makes sure that you don't miss that something went wrong and offers you ways how to deal with the situation. In many cases, it helps if I get further information from you, such as instructions how to reproduce the error. +==== + + +IMPORTANT: Unless you have a live situation (in which case it might make sense to ignore errors), please tick at least one of the checkboxes. I can't fix bugs if I don't know about them! Thanks. + +== Checkbox "Notify about updates" + +By default, Helgobox checks on startup for available updates. If a more recent version is available, you will see a notification somewhere in the xref:helgobox::plug-in/user-interface.adoc[plug-in window] and the xref:app/user-interface.adoc[app window]. + +If you don't want ReaLearn to check for updates, you can disable it here. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/products.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/products.adoc new file mode 100644 index 0000000..e79b190 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/products.adoc @@ -0,0 +1,39 @@ += Contained products + +Helgobox provides the framework for the following products. + +[[realearn]] +== ReaLearn + +link:https://www.helgoboss.org/projects/realearn[ReaLearn] is a versatile controller integration tool for REAPER. +It lies at the very core of Helgobox, as controller integration is a key aspect of all the other products it contains. + +xref:realearn::introduction.adoc[→ Read more about ReaLearn] + +[[playtime]] +== Playtime + +link:https://www.helgoboss.org/projects/playtime[Playtime] is a modern session view / clip launcher for REAPER, built straight into Helgobox. +Each xref:key-concepts.adoc#instance[Helgobox Instance] may contain one xref:playtime::key-concepts.adoc#matrix[] (by default not loaded). + +xref:playtime::introduction.adoc[→ Read more about Playtime] + +[[pot-browser]] +== Pot Browser + +Pot Browser is an experimental modern preset browser built straight into Helgobox. +It's just a prototype so far. +It will probably look quite different in the future. +You can open it via menu action xref:plug-in/user-interface/menu-bar.adoc#open-pot-browser[]. + +It's recommended to use Pot Browser from a ReaLearn instance on the monitoring FX chain, that way you have the browser accessible from any project. + +TIP: Add a toolbar button which triggers the REAPER action "ReaLearn: Open first Pot Browser" to get quick and convenient access to the browser. + +Remarks: + +- Pot Browser is in an experimental stage, it doesn't save any of your settings! +- Each Helgobox instance can have one _Pot Unit_ (by default not loaded). +Each Pot Unit has its own filter and preset state. +When you open the Pot Browser from an instance, it connects to the Pot Unit of that instance. +- ReaLearn's "Pot" targets such as xref:realearn::targets/pot/browse-presets.adoc[] can be used to control the Pot Unit from any controller. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/reaper-actions.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/reaper-actions.adoc new file mode 100644 index 0000000..4cfead1 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/pages/reaper-actions.adoc @@ -0,0 +1,23 @@ += REAPER actions + +== General actions + +Helgobox provides a few global REAPER actions. + +You can find those actions directly in the menu menu:Extensions[Helgobox]. +Alternatively, search for "helgobox" in REAPER's action list (menu:Actions[Show action list...]). + +[[show-welcome-screen]] Helgobox/General: Show welcome screen:: +Shows the xref:plug-in/user-interface/welcome-dialog.adoc[] that was displayed after the first installation of Helgobox. + +[[toggle-app-focus]] Helgobox/General: Toggle app focus:: +Switches focus between REAPER and the xref:app.adoc[]. ++ +The default shortcut is kbd:[Shift+Cmd/Ctrl+H]. + +== Product-specific actions + +The products contained in Helgobox provide additional actions: + +- xref:realearn::reaper-actions.adoc[ReaLearn actions] +- xref:playtime::reaper-actions.adoc[Playtime actions] diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app-desc.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app-desc.adoc new file mode 100644 index 0000000..0bb5b7c --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app-desc.adoc @@ -0,0 +1,2 @@ +A complement to the xref:plug-in.adoc[] which provides a modern user interface. +It runs embedded in REAPER or as a separate program that connects to REAPER. Mobile versions for iOS and Android are planned. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/fine-adjustment.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/fine-adjustment.adoc new file mode 100644 index 0000000..9e8885a --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/fine-adjustment.adoc @@ -0,0 +1,2 @@ +Fine adjustments:: +Hold the kbd:[Cmd/Ctrl] key while dragging, scrolling or panning in order to do fine adjustments. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/mouse-dragging.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/mouse-dragging.adoc new file mode 100644 index 0000000..f937a00 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/mouse-dragging.adoc @@ -0,0 +1,4 @@ +icon:mouse-pointer[] Mouse dragging:: +Click and hold the left mouse button, then drag the mouse up or down to increase or decrease the value. +Release the button when finished. +Most elements update the value in real-time as you drag, while others apply the change only after you release the mouse button. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/mouse-wheel-scrolling.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/mouse-wheel-scrolling.adoc new file mode 100644 index 0000000..8068451 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/mouse-wheel-scrolling.adoc @@ -0,0 +1,3 @@ +↕ Mouse wheel scrolling:: +Move the mouse wheel up or down to increase or decrease the value. footnote:wheel-direction[Depending on your operating system settings, the mouse wheel directions might be reversed.] +The new value will be applied immediately. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/reset-to-default.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/reset-to-default.adoc new file mode 100644 index 0000000..fa5ca98 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/reset-to-default.adoc @@ -0,0 +1,2 @@ +Reset to default:: +Double-click the element to restore the default value. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/text-entry.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/text-entry.adoc new file mode 100644 index 0000000..63f9788 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/text-entry.adoc @@ -0,0 +1,3 @@ +icon:keyboard-o[] Enter text:: +Right-click the element and enter the value via keyboard. +Press kbd:[Enter] or click somewhere else to apply the value. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/touchpad-panning.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/touchpad-panning.adoc new file mode 100644 index 0000000..7006d3b --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/app/user-interface/interactions/touchpad-panning.adoc @@ -0,0 +1,2 @@ +icon:hand-peace-o[] Touchpad panning:: +If you have a touchpad, place the cursor over the element and slide up or down with two fingers to increase or decrease the value. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/area/nav-bar-0.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/area/nav-bar-0.adoc new file mode 100644 index 0000000..7c1c89f --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/area/nav-bar-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-nav-bar-0,Navigation bar]]] +ifndef::pdf-theme[[[area-nav-bar-0,Navigation bar image:helgobox::generated/screenshots/elements/area/nav-bar-0.png[width=50, pdfwidth=8mm]]]] +== Navigation bar + +image::helgobox::generated/screenshots/elements/area/nav-bar-0.png[Navigation bar, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/area/window-title-bar-0.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/area/window-title-bar-0.adoc new file mode 100644 index 0000000..5529657 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/area/window-title-bar-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-window-title-bar-0,Title bar]]] +ifndef::pdf-theme[[[area-window-title-bar-0,Title bar image:helgobox::generated/screenshots/elements/area/window-title-bar-0.png[width=50, pdfwidth=8mm]]]] +== Title bar + +image::helgobox::generated/screenshots/elements/area/window-title-bar-0.png[Title bar, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/enable-disable-global-control.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/enable-disable-global-control.adoc new file mode 100644 index 0000000..40790c3 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/enable-disable-global-control.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[navbar-enable-disable-global-control,Enable/disable global control]]] +ifndef::pdf-theme[[[navbar-enable-disable-global-control,Enable/disable global control image:helgobox::generated/screenshots/elements/navbar/enable-disable-global-control.png[width=50, pdfwidth=8mm]]]] +== Enable/disable global control + +image::helgobox::generated/screenshots/elements/navbar/enable-disable-global-control.png[Enable/disable global control, role="related thumb right", float=right] + +Enables or disables the usage of global controllers for this Helgobox instance. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/home.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/home.adoc new file mode 100644 index 0000000..57518b3 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/home.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[navbar-home,Home]]] +ifndef::pdf-theme[[[navbar-home,Home image:helgobox::generated/screenshots/elements/navbar/home.png[width=50, pdfwidth=8mm]]]] +== Home + +image::helgobox::generated/screenshots/elements/navbar/home.png[Home, role="related thumb right", float=right] + +Shows the Helgobox welcome page. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/pick-instance.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/pick-instance.adoc new file mode 100644 index 0000000..1f8c80a --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/pick-instance.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[navbar-pick-instance,Pick another instance]]] +ifndef::pdf-theme[[[navbar-pick-instance,Pick another instance image:helgobox::generated/screenshots/elements/navbar/pick-instance.png[width=50, pdfwidth=8mm]]]] +== Pick another instance + +image::helgobox::generated/screenshots/elements/navbar/pick-instance.png[Pick another instance, role="related thumb right", float=right] + +Lets you pick another Helgobox instance. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/playtime.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/playtime.adoc new file mode 100644 index 0000000..f0c6213 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/playtime.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[navbar-playtime,Playtime]]] +ifndef::pdf-theme[[[navbar-playtime,Playtime image:helgobox::generated/screenshots/elements/navbar/playtime.png[width=50, pdfwidth=8mm]]]] +== Playtime + +image::helgobox::generated/screenshots/elements/navbar/playtime.png[Playtime, role="related thumb right", float=right] + +Shows the Playtime Matrix associated with this Helgobox instance. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/projection.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/projection.adoc new file mode 100644 index 0000000..d607f5b --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/projection.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[navbar-projection,ReaLearn Projection]]] +ifndef::pdf-theme[[[navbar-projection,ReaLearn Projection image:helgobox::generated/screenshots/elements/navbar/projection.png[width=50, pdfwidth=8mm]]]] +== ReaLearn Projection + +image::helgobox::generated/screenshots/elements/navbar/projection.png[ReaLearn Projection, role="related thumb right", float=right] + +Shows the ReaLearn Projection page for this Helgobox instance. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/show-helgobox-plugin.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/show-helgobox-plugin.adoc new file mode 100644 index 0000000..57b9d12 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/navbar/show-helgobox-plugin.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[navbar-show-helgobox-plugin,Show Helgobox plug-in]]] +ifndef::pdf-theme[[[navbar-show-helgobox-plugin,Show Helgobox plug-in image:helgobox::generated/screenshots/elements/navbar/show-helgobox-plugin.png[width=50, pdfwidth=8mm]]]] +== Show Helgobox plug-in + +image::helgobox::generated/screenshots/elements/navbar/show-helgobox-plugin.png[Show Helgobox plug-in, role="related thumb right", float=right] + +Opens the Helgobox plug-in which is controlled by this app. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/color-scheme.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/color-scheme.adoc new file mode 100644 index 0000000..bba27e3 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/color-scheme.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-color-scheme,Color scheme]]] +ifndef::pdf-theme[[[settings-color-scheme,Color scheme]]] +=== Color scheme + +image::helgobox::generated/screenshots/elements/settings/color-scheme.png[Color scheme] + +Choose the prominent color for the user interface. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/contrast.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/contrast.adoc new file mode 100644 index 0000000..fd05c19 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/contrast.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-contrast,Contrast]]] +ifndef::pdf-theme[[[settings-contrast,Contrast]]] +=== Contrast + +image::helgobox::generated/screenshots/elements/settings/contrast.png[Contrast] + +Choose between multiple color variations that influence the perceived contrast of the app's appearance. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/custom-color-rendering.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/custom-color-rendering.adoc new file mode 100644 index 0000000..0ef7dd0 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/custom-color-rendering.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-custom-color-rendering,Custom color rendering]]] +ifndef::pdf-theme[[[settings-custom-color-rendering,Custom color rendering]]] +=== Custom color rendering + +image::helgobox::generated/screenshots/elements/settings/custom-color-rendering.png[Custom color rendering] + +Choose how to render user-defined colors, such as for tracks and clips. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/dim-opacity.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/dim-opacity.adoc new file mode 100644 index 0000000..3938c5e --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/dim-opacity.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-dim-opacity,Dim opacity]]] +ifndef::pdf-theme[[[settings-dim-opacity,Dim opacity image:helgobox::generated/screenshots/elements/settings/dim-opacity.png[width=50, pdfwidth=8mm]]]] +=== Dim opacity + +image::helgobox::generated/screenshots/elements/settings/dim-opacity.png[Dim opacity, role="related thumb right", float=right] + +Adjust how much to dim the Helgobox app window when pressing the dim button. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-development-status-hints.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-development-status-hints.adoc new file mode 100644 index 0000000..d49edf0 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-development-status-hints.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-display-development-status-hints,Display development status hints]]] +ifndef::pdf-theme[[[settings-display-development-status-hints,Display development status hints]]] +=== Display development status hints + +image::helgobox::generated/screenshots/elements/settings/display-development-status-hints.png[Display development status hints] + +If enabled, the inspector will display even settings that are not available yet and it will show small annotations about the development state of a feature. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-license-info.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-license-info.adoc new file mode 100644 index 0000000..eb01497 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-license-info.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-display-license-info,Display license info]]] +ifndef::pdf-theme[[[settings-display-license-info,Display license info]]] +=== Display license info + +image::helgobox::generated/screenshots/elements/settings/display-license-info.png[Display license info] + +If enabled, the licensee name will be displayed in the window title bar. This only affects commercial Helgobox products, such as Playtime. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-tooltip-if-help-panel-hidden.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-tooltip-if-help-panel-hidden.adoc new file mode 100644 index 0000000..b43526e --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/display-tooltip-if-help-panel-hidden.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-display-tooltip-if-help-panel-hidden,Display tooltip if help panel hidden]]] +ifndef::pdf-theme[[[settings-display-tooltip-if-help-panel-hidden,Display tooltip if help panel hidden]]] +=== Display tooltip if help panel hidden + +image::helgobox::generated/screenshots/elements/settings/display-tooltip-if-help-panel-hidden.png[Display tooltip if help panel hidden] + +If enabled and the help panel in the bottom right corner is not visible, a tooltip will be showed instead. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/hide-window-when-escape-pressed.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/hide-window-when-escape-pressed.adoc new file mode 100644 index 0000000..bf97388 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/hide-window-when-escape-pressed.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-hide-window-when-escape-pressed,Hide window when escape pressed]]] +ifndef::pdf-theme[[[settings-hide-window-when-escape-pressed,Hide window when escape pressed]]] +=== Hide window when escape pressed + +image::helgobox::generated/screenshots/elements/settings/hide-window-when-escape-pressed.png[Hide window when escape pressed] + +If enabled, pressing escape if no dialog is open will hide the window. + +This will only have an effect when running embedded within REAPER. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/overall-size.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/overall-size.adoc new file mode 100644 index 0000000..263fed0 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/overall-size.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-overall-size,Overall size]]] +ifndef::pdf-theme[[[settings-overall-size,Overall size]]] +=== Overall size + +image::helgobox::generated/screenshots/elements/settings/overall-size.png[Overall size] + +Adjust the overall size of the user interface, including the font size. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/prefer-native-popup-menus.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/prefer-native-popup-menus.adoc new file mode 100644 index 0000000..a3596d5 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/prefer-native-popup-menus.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[settings-prefer-native-popup-menus,Prefer native popup menus (experimental)]]] +ifndef::pdf-theme[[[settings-prefer-native-popup-menus,Prefer native popup menus (experimental)]]] +=== Prefer native popup menus (experimental) + +image::helgobox::generated/screenshots/elements/settings/prefer-native-popup-menus.png[Prefer native popup menus (experimental)] + +If enabled, dropdown and right-click menus will be rendered using your system's native user interface component. +[NOTE] +.Development status: Feedback wanted! +==== +This doesn't work particular well at the moment. On Windows, it has caused crashes in the past. +==== + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/prefer-tidy-appearance.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/prefer-tidy-appearance.adoc new file mode 100644 index 0000000..fd58d53 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/prefer-tidy-appearance.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-prefer-tidy-appearance,Prefer tidy appearance]]] +ifndef::pdf-theme[[[settings-prefer-tidy-appearance,Prefer tidy appearance]]] +=== Prefer tidy appearance + +image::helgobox::generated/screenshots/elements/settings/prefer-tidy-appearance.png[Prefer tidy appearance] + +If enabled, the inspector will show dropdown menus even if if they present just a handful of options. This looks more tidy but you will need two mouse clicks to choose an option. + +If disabled, it will show all options as buttons instead. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/roundness.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/roundness.adoc new file mode 100644 index 0000000..67d8ebc --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/roundness.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-roundness,Roundness]]] +ifndef::pdf-theme[[[settings-roundness,Roundness]]] +=== Roundness + +image::helgobox::generated/screenshots/elements/settings/roundness.png[Roundness] + +Choose whether to round off corners of various user interface elements and how much. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/theme-mode.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/theme-mode.adoc new file mode 100644 index 0000000..b3d4ad5 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/theme-mode.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-theme-mode,Theme mode]]] +ifndef::pdf-theme[[[settings-theme-mode,Theme mode]]] +=== Theme mode + +image::helgobox::generated/screenshots/elements/settings/theme-mode.png[Theme mode] + +Switches between dark, light, and automatic theme modes. In automatic mode, the app will follow your system's theme settings. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/use-context-coloring.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/use-context-coloring.adoc new file mode 100644 index 0000000..d128974 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/use-context-coloring.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-use-context-coloring,Use context coloring]]] +ifndef::pdf-theme[[[settings-use-context-coloring,Use context coloring]]] +=== Use context coloring + +image::helgobox::generated/screenshots/elements/settings/use-context-coloring.png[Use context coloring] + +If enabled, some inspector panels will get a special background color that reflects their purpose, such as red for recording and green for playback. This makes it easier to distinguish between the purpose of different settings but it may not go so well with the color design of your theme. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/use-host-colors.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/use-host-colors.adoc new file mode 100644 index 0000000..af08e88 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/settings/use-host-colors.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-use-host-colors,Use host colors (experimental)]]] +ifndef::pdf-theme[[[settings-use-host-colors,Use host colors (experimental)]]] +=== Use host colors (experimental) + +image::helgobox::generated/screenshots/elements/settings/use-host-colors.png[Use host colors (experimental)] + +Borrows some color from the REAPER theme. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/close-window.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/close-window.adoc new file mode 100644 index 0000000..8b1513b --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/close-window.adoc @@ -0,0 +1,9 @@ +ifdef::pdf-theme[[[title-bar-close-window,Close window]]] +ifndef::pdf-theme[[[title-bar-close-window,Close window image:helgobox::generated/screenshots/elements/title-bar/close-window.png[width=50, pdfwidth=8mm]]]] +== Close window + +image::helgobox::generated/screenshots/elements/title-bar/close-window.png[Close window, role="related thumb right", float=right] + +Closes the app window. + + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/dim.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/dim.adoc new file mode 100644 index 0000000..8b3e073 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/dim.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-dim,Dim]]] +ifndef::pdf-theme[[[title-bar-dim,Dim image:helgobox::generated/screenshots/elements/title-bar/dim.png[width=50, pdfwidth=8mm]]]] +== Dim + +image::helgobox::generated/screenshots/elements/title-bar/dim.png[Dim, role="related thumb right", float=right] + +Toggles between a transparent and opaque window. Useful for quickly having a look what REAPER is doing under the hood ;) + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/full-screen.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/full-screen.adoc new file mode 100644 index 0000000..54cc02e --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/full-screen.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-full-screen,Full screen]]] +ifndef::pdf-theme[[[title-bar-full-screen,Full screen image:helgobox::generated/screenshots/elements/title-bar/full-screen.png[width=50, pdfwidth=8mm]]]] +== Full screen + +image::helgobox::generated/screenshots/elements/title-bar/full-screen.png[Full screen, role="related thumb right", float=right] + +Toggles between full-screen and windowed mode. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/helgobox-info.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/helgobox-info.adoc new file mode 100644 index 0000000..43e97e2 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/helgobox-info.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-helgobox-info,Helgobox Info]]] +ifndef::pdf-theme[[[title-bar-helgobox-info,Helgobox Info image:helgobox::generated/screenshots/elements/title-bar/helgobox-info.png[width=50, pdfwidth=8mm]]]] +== Helgobox Info + +image::helgobox::generated/screenshots/elements/title-bar/helgobox-info.png[Helgobox Info, role="related thumb right", float=right] + +Shows the version number of the controlled Helgobox plug-in as well as the numeric ID of the Helgobox instance to which this App window is connected. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/help.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/help.adoc new file mode 100644 index 0000000..0a43e3d --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/help.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-help,Help]]] +ifndef::pdf-theme[[[title-bar-help,Help image:helgobox::generated/screenshots/elements/title-bar/help.png[width=50, pdfwidth=8mm]]]] +== Help + +image::helgobox::generated/screenshots/elements/title-bar/help.png[Help, role="related thumb right", float=right] + +Shows a help menu + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/move-window.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/move-window.adoc new file mode 100644 index 0000000..4db114c --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/move-window.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-move-window,Move window]]] +ifndef::pdf-theme[[[title-bar-move-window,Move window image:helgobox::generated/screenshots/elements/title-bar/move-window.png[width=50, pdfwidth=8mm]]]] +== Move window + +image::helgobox::generated/screenshots/elements/title-bar/move-window.png[Move window, role="related thumb right", float=right] + +Drag this button to move the window. Useful when the window is very narrow, in which case the main drag area in the center will disappear. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/panic-midi.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/panic-midi.adoc new file mode 100644 index 0000000..51e49d2 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/panic-midi.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-panic-midi,Panic]]] +ifndef::pdf-theme[[[title-bar-panic-midi,Panic image:helgobox::generated/screenshots/elements/title-bar/panic-midi.png[width=50, pdfwidth=8mm]]]] +== Panic + +image::helgobox::generated/screenshots/elements/title-bar/panic-midi.png[Panic, role="related thumb right", float=right] + +Attempts to stops all currently playing MIDI notes. Whenever you experience hanging notes, press this button. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/pause-arrangement.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/pause-arrangement.adoc new file mode 100644 index 0000000..872c7a9 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/pause-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-pause-arrangement,Pause arrangement]]] +ifndef::pdf-theme[[[title-bar-pause-arrangement,Pause arrangement image:helgobox::generated/screenshots/elements/title-bar/pause-arrangement.png[width=50, pdfwidth=8mm]]]] +== Pause arrangement + +image::helgobox::generated/screenshots/elements/title-bar/pause-arrangement.png[Pause arrangement, role="related thumb right", float=right] + +Pauses the REAPER arrangement + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/play-arrangement.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/play-arrangement.adoc new file mode 100644 index 0000000..c063a22 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/play-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-play-arrangement,Play arrangement]]] +ifndef::pdf-theme[[[title-bar-play-arrangement,Play arrangement image:helgobox::generated/screenshots/elements/title-bar/play-arrangement.png[width=50, pdfwidth=8mm]]]] +== Play arrangement + +image::helgobox::generated/screenshots/elements/title-bar/play-arrangement.png[Play arrangement, role="related thumb right", float=right] + +Starts playing the REAPER arrangement + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/redo.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/redo.adoc new file mode 100644 index 0000000..19ad4eb --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/redo.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-redo,Redo]]] +ifndef::pdf-theme[[[title-bar-redo,Redo image:helgobox::generated/screenshots/elements/title-bar/redo.png[width=50, pdfwidth=8mm]]]] +== Redo + +image::helgobox::generated/screenshots/elements/title-bar/redo.png[Redo, role="related thumb right", float=right] + +Redoes the most recent operation that was undone within this Playtime matrix. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/refresh.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/refresh.adoc new file mode 100644 index 0000000..808d7d1 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/refresh.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[title-bar-refresh,Refresh]]] +ifndef::pdf-theme[[[title-bar-refresh,Refresh image:helgobox::generated/screenshots/elements/title-bar/refresh.png[width=50, pdfwidth=8mm]]]] +== Refresh + +image::helgobox::generated/screenshots/elements/title-bar/refresh.png[Refresh, role="related thumb right", float=right] + +Refreshes the displayed information. + +Pressing this button is typically not required during normal operation. However, if there are any issues preventing Helgobox from displaying the latest information automatically, this button can help resolve the problem. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/save-project.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/save-project.adoc new file mode 100644 index 0000000..4fbdcb4 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/save-project.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-save-project,Save project]]] +ifndef::pdf-theme[[[title-bar-save-project,Save project image:helgobox::generated/screenshots/elements/title-bar/save-project.png[width=50, pdfwidth=8mm]]]] +== Save project + +image::helgobox::generated/screenshots/elements/title-bar/save-project.png[Save project, role="related thumb right", float=right] + +Saves the current REAPER project + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/settings.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/settings.adoc new file mode 100644 index 0000000..c1ec1ac --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/settings.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-settings,Settings]]] +ifndef::pdf-theme[[[title-bar-settings,Settings image:helgobox::generated/screenshots/elements/title-bar/settings.png[width=50, pdfwidth=8mm]]]] +== Settings + +image::helgobox::generated/screenshots/elements/title-bar/settings.png[Settings, role="related thumb right", float=right] + +Opens the central settings dialog. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/stop-arrangement.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/stop-arrangement.adoc new file mode 100644 index 0000000..8555895 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/stop-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-stop-arrangement,Stop arrangement]]] +ifndef::pdf-theme[[[title-bar-stop-arrangement,Stop arrangement image:helgobox::generated/screenshots/elements/title-bar/stop-arrangement.png[width=50, pdfwidth=8mm]]]] +== Stop arrangement + +image::helgobox::generated/screenshots/elements/title-bar/stop-arrangement.png[Stop arrangement, role="related thumb right", float=right] + +Stops the REAPER arrangement + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/theme-mode.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/theme-mode.adoc new file mode 100644 index 0000000..e550c2d --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/theme-mode.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-theme-mode,Theme mode]]] +ifndef::pdf-theme[[[title-bar-theme-mode,Theme mode image:helgobox::generated/screenshots/elements/title-bar/theme-mode.png[width=50, pdfwidth=8mm]]]] +== Theme mode + +image::helgobox::generated/screenshots/elements/title-bar/theme-mode.png[Theme mode, role="related thumb right", float=right] + +Switches between dark, light, and automatic theme modes. In automatic mode, the app will follow your system's theme settings. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/theme-switcher.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/theme-switcher.adoc new file mode 100644 index 0000000..16e9189 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/theme-switcher.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-theme-switcher,Quick theme switcher]]] +ifndef::pdf-theme[[[title-bar-theme-switcher,Quick theme switcher image:helgobox::generated/screenshots/elements/title-bar/theme-switcher.png[width=50, pdfwidth=8mm]]]] +== Quick theme switcher + +image::helgobox::generated/screenshots/elements/title-bar/theme-switcher.png[Quick theme switcher, role="related thumb right", float=right] + +Cycle through all available themes. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/toggle-nav-bar.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/toggle-nav-bar.adoc new file mode 100644 index 0000000..61fcf66 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/toggle-nav-bar.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-toggle-nav-bar,Toggle navigation bar]]] +ifndef::pdf-theme[[[title-bar-toggle-nav-bar,Toggle navigation bar image:helgobox::generated/screenshots/elements/title-bar/toggle-nav-bar.png[width=50, pdfwidth=8mm]]]] +== Toggle navigation bar + +image::helgobox::generated/screenshots/elements/title-bar/toggle-nav-bar.png[Toggle navigation bar, role="related thumb right", float=right] + +Shows or hides the navigation bar on the left. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/undo.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/undo.adoc new file mode 100644 index 0000000..8c803fc --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/elements/title-bar/undo.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-undo,Undo]]] +ifndef::pdf-theme[[[title-bar-undo,Undo image:helgobox::generated/screenshots/elements/title-bar/undo.png[width=50, pdfwidth=8mm]]]] +== Undo + +image::helgobox::generated/screenshots/elements/title-bar/undo.png[Undo, role="related thumb right", float=right] + +Undoes the most recent operation performed within this Playtime matrix. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/about.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/about.adoc new file mode 100644 index 0000000..a9a8ca5 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/about.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-about,About...]]] +ifndef::pdf-theme[[[title-bar-about,About...]]] +== About... + + + +Shows the about screen for the Helgobox App. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/highlight-screen-areas.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/highlight-screen-areas.adoc new file mode 100644 index 0000000..e3d7c84 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/highlight-screen-areas.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-highlight-screen-areas,Highlight screen areas]]] +ifndef::pdf-theme[[[title-bar-highlight-screen-areas,Highlight screen areas]]] +== Highlight screen areas + + + +Highlights the most important areas of the main screen. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/open-helgobox-reference.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/open-helgobox-reference.adoc new file mode 100644 index 0000000..888c929 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/open-helgobox-reference.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-open-helgobox-reference,Open Helgobox reference]]] +ifndef::pdf-theme[[[title-bar-open-helgobox-reference,Open Helgobox reference]]] +== Open Helgobox reference + + + +Opens the Helgobox Reference online. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/open-helgobox-website.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/open-helgobox-website.adoc new file mode 100644 index 0000000..20e6e0c --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/generated/menus/help/open-helgobox-website.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-open-helgobox-website,Open Helgobox website]]] +ifndef::pdf-theme[[[title-bar-open-helgobox-website,Open Helgobox website]]] +== Open Helgobox website + + + +Opens the Helgobox product page. + diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/intro-header.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/intro-header.adoc new file mode 100644 index 0000000..29f8e90 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/intro-header.adoc @@ -0,0 +1,6 @@ +ifdef::pdf-theme[] +[TIP] +==== +You are viewing the printable PDF version of the {page-component-title}. For a better and more interactive reading experience, visit the link:https://docs.helgoboss.org/{page-component-name}[online version]! +==== +endif::[] diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/plug-in-desc.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/plug-in-desc.adoc new file mode 100644 index 0000000..db1be1b --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/plug-in-desc.adoc @@ -0,0 +1 @@ +A VST instrument plug-in that deeply integrates with REAPER by using its link:https://www.reaper.fm/sdk/plugin/plugin.php[Plug-in Extensions] mechanism. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/product-specific-info.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/product-specific-info.adoc new file mode 100644 index 0000000..224fe60 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/product-specific-info.adoc @@ -0,0 +1 @@ +For product-specific information, please consult the link:https://docs.helgoboss.org/realearn[ReaLearn Reference] or link:https://docs.helgoboss.org/playtime[Playtime Reference]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/realearn-specific-ui-elements.adoc b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/realearn-specific-ui-elements.adoc new file mode 100644 index 0000000..7d06479 --- /dev/null +++ b/plugin-reaper-realearn/doc/helgobox/modules/ROOT/partials/realearn-specific-ui-elements.adoc @@ -0,0 +1,2 @@ +NOTE: This section only describes the few general user interface elements that are _not_ specific to ReaLearn. +Refer to the xref:realearn::user-interface.adoc[ReaLearn user interface documentation] to learn more about all the other elements! \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime-clip-engine.adoc b/plugin-reaper-realearn/doc/playtime-clip-engine.adoc new file mode 100644 index 0000000..fdbef64 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime-clip-engine.adoc @@ -0,0 +1,557 @@ += Playtime 2 - Clip Engine +:toc: +:toclevels: 2 + +== Usage + +[IMPORTANT] +==== +**This document is outdated!** The Clip Engine has finally (November 2024) culminated in the final product link:https://www.helgoboss.org/projects/playtime[Playtime 2], which contains both GUI and engine. + +The document is kept for historical purposes. Most of the mentioned engine features made it into the final product, but not all. There are also new ones. +==== + + +This section describes how to use the Clip Engine without the Playtime App. + +[NOTE] +==== +The Clip Engine is basically Playtime without a graphical user interface. +That means usage is not straightforward and needs to be explained. + +If you are not up for it, wait for the Playtime App. +For more info, watch the link:https://youtu.be/6iEMKUcwzMs[Clip Engine introduction video]. +==== + +[WARNING] +==== +At this point (May 2022), the Clip Engine is in early alpha stage. +I can't guarantee that projects or presets that you save with the current version can be loaded in future versions of the Clip Engine! + +The current version is intended for alpha testing and experimentation, *not* for live usage or building your dream setup. +==== + +Using the Clip Engine involves all the steps explained in the following sections. + +=== Step 1: Install the latest pre-release of ReaLearn + +The Clip Engine is built into the REAPER plug-in ReaLearn, so you first need to install it by following the instructions on the link:https://github.com/helgoboss/realearn#installation[ReaLearn project website]. + +IMPORTANT: Make absolutely sure to install the latest *pre-release* (>= v2.13.0-pre.4), otherwise it will not work! +Pre-releases can be installed by right-clicking the package in ReaPack. + +TIP: First things first: If you are new to ReaLearn, it's helpful to get a basic feel for it and make sure that your controller generally works, so I strongly suggest walking through the link:https://github.com/helgoboss/helgobox/blob/master/doc/realearn-user-guide.adoc#quick-start[Quick start] section of its user guide before diving into the template project! +It doesn't take long. + +=== Step 2: Optimize the REAPER preferences + +In order to get the best experience, adjust the REAPER preferences like this: + +|=== +|Category | Option | Value + +|Audio +|Close audio device when stopped and application is inactive +|Off + +|Audio → Playback +|Stop/Repeat playback at end of project +|Off +|=== + +=== Step 3: Open the Clip Engine template project in REAPER + +You need a template project as a starting point: + +. Download the basic Clip Engine project template (RPP file) from link:https://github.com/helgoboss/helgobox/tree/master/resources/template-projects/clip-engine-basic[here]. +. Open it in REAPER. + +The basic template project contains a preconfigured ReaLearn instance with the Clip Engine enabled, a folder track, 8 column tracks and a few demonstration MIDI clips in the first 4 columns and rows. +The folder track is optional, it just makes the project look a bit more tidy in case you add other non-column tracks later. + +=== Step 4: Set up your specific controller in ReaLearn + +==== Suitable controllers + +In order to exhaust the full potential of the Clip Engine, you need a grid controller. +One that provides visual feedback, otherwise you don't see what's going on (remember, we don't have the Playtime App yet, so you don't see anything on the screen). + +It's possible to use simple MIDI keyboards or computer keyboards (QWERTY), although they are not ideal for this purpose because they lack visual feedback. +DAW controllers with MCU support are possible as well but as their control elements are not aligned in columns and rows, they are only feasible for controlling one clip at a time (one-channel controllers) or one row (multi-channel controllers). + +==== Instructions + +Open the ReaLearn FX instance "Clip Engine" on the track with the same name. +Then follow the general and controller-specific instructions in the <> section. + +=== Step 5: Adjust the column tracks to your needs (optional) + +By default ... + +- ... column tracks 1 to 4 are configured to record MIDI from all MIDI inputs, have input monitoring enabled and a ReaSynth instance on the FX chain. +- ... column tracks 5 to 8 are configured to record audio from the first hardware input (mono) and have input monitoring disabled. + +You can adjust those tracks to your needs: + +* Change name +* Change input +* Enable/disable input monitoring +* Add/remove effects or virtual instruments if desired + +CAUTION: *Don't delete column tracks*, otherwise you will get error messages next time you load the project. +You can move them, rename them but not delete them. +Future versions of the Clip Engine will handle deleted tracks more gracefully. + +=== Step 6: Adjust the clip matrix settings to your needs (optional) + +As we don't have the Playtime App yet, you need to do this by adjusting Lua code. + +. Press _Export to clipboard_ and choose _Export clip matrix as Lua_. +. Paste the code into a link:https://en.wikipedia.org/wiki/Text_editor[text editor] of your choice. +. Adjust the code and copy it. +. Press _Import from clipboard_ and confirm. + +Sorry, there's no good documentation yet of the clip matrix data structure. +If you are not afraid of Rust, have a look link:https://github.com/helgoboss/helgobox/blob/master/playtime-api/src/lib.rs[here] to get an idea of what's possible. +Not all attributes are implemented yet. + +[CAUTION] +==== +The Lua export also contains the clips! +Take care not to accidentally overwrite already recorded clips. + +The best way to do that is by strictly using the export-modify-import process whenever you want to do a modification. + +Reminder: You should not become attached to content recorded with the Clip Engine anyway because we are in alpha stage. +==== + +[#supported-controllers] +== Supported controllers + +At the moment, only a few controllers are supported out-of-the-box. +Here you will find a list and setup instructions. + +Because the Clip Engine controller support is still in a state of flux, you won't find suitable ReaLearn presets in ReaPack yet. +Instead, you will need to copy Lua snippets from ReaLearn's project website on GitHub and import them into ReaLearn via _Import from Clipboard_. + +It's possible to integrate other controllers than the ones presented here, but you'll need to write Lua code to achieve that because clip-engine-related targets can't be configured via ReaLearn's GUI! +If you want to do this, have a look at the existing link:https://github.com/helgoboss/helgobox/tree/master/resources/main-presets[main presets] ending with `-to-clip-engine.lua`. +Start with link:https://github.com/helgoboss/helgobox/tree/master/resources/main-presets/notes-to-clip-engine.lua[notes-to-clip-engine.lua], it's the easiest one. + +[TIP] +==== +In the following instructions, you'll see the words _controller preset_ and _main preset_. +If you are unfamiliar with these, here's an explanation. + +A _controller preset_ takes care of giving the knobs/faders/buttons on a hardware controller descriptive names and sometimes even provides a visual layout for ReaLearn's link:https://www.youtube.com/watch?v=dUPyqYaIkYA&t=1603s[Projection feature]. +For CSI users: It's somewhat comparable to the widgets defined in MST files. + +A _main preset_ takes care of mapping knobs/faders/buttons to actions/parameters in REAPER, ReaLearn or the Clip Engine. +For CSI users: It's comparable to the zone files. +==== + +=== Akai APC Key 25 + +This controller has the best support so far because it's my main test hardware. +Except for the _Device_ button, it mainly works as demonstrated in this link:https://youtu.be/9yr7aeg354U[Ableton-Live-specific video]. +Plus, it adds some much-needed mappings for undo/redo, deleting clips, etc. + +. Select the device both as _Control input_ and _Feedback output_. +. Import the controller preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/controller-presets/apc-key-25.lua[apc-key-25.lua] and import it into ReaLearn. +. Import the main preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/main-presets/apc-key-25-to-clip-engine.lua[apc-key-25-to-clip-engine.lua] and import it into ReaLearn. + +For a basic test, check if the _PLAY/PAUSE_ button plays the project. + +In order to see which knob/button has which effect, you can use ReaLearn's _Projection_ button. +When seeing the projection, press and hold _Shift_ or _Sustain_ to see alternative functions. + +=== DJ TechTools Midi Fighter Twister + +This is not the typical grid controller, so my main preset is quite opinionated. +Nevertheless, due to its grid-like alignment of the push encoders and rich visual feedback options, it's quite suitable for recording and launching clips. +It can even show the current position within the clip. + +. Set up the Twister exactly as described in the link:https://github.com/helgoboss/helgobox/blob/master/doc/realearn-user-guide.adoc#dj-techtools-midi-fighter-twister[corresponding section] in ReaLearn's user guide, subsection "Preparation". ++ +IMPORTANT: Existing ReaLearn users, watch out. +I've added new instructions! ++ +. Select the device both as _Control input_ and _Feedback output_. +. Import the controller preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/controller-presets/midi-fighter-twister.lua[midi-fighter-twister.lua] and import it into ReaLearn. +. Import the main preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/main-presets/mft-to-clip-engine.lua[mft-to-clip-engine.lua] and import it into ReaLearn. + +For a basic test, press the upper-left push encoder. +It should play a test clip. + +In order to see which encoder/button has which effect, you can use ReaLearn's _Projection_ button. +When seeing the projection, press and hold the side buttons to see alternative functions. + +=== DAW controllers (MCU) + +Support for DAW controllers is very incomplete and experimental because they are not grid controllers and therefore not my first priority. +But the basics work and improving the presets is just a matter of adding additional mappings. +I tested this with a Behringer X-Touch One and an iCON Platform M+. + +. Make sure your controller is operating in Mackie mode (MCU). +. Select the device both as _Control input_ and _Feedback output_. +. Import the controller preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/controller-presets/mackie-control.lua[mackie-control.lua] and import it into ReaLearn. +. Import the main preset +* If you have a DAW controller with one channel only, copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/main-presets/one-channel-daw-control-to-clip-engine.lua[one-channel-daw-control-to-clip-engine.lua] and import it into ReaLearn. +* If you have a DAW controller with multiple channels, copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/main-presets/multi-channel-daw-control-to-clip-engine.lua[multi-channel-daw-control-to-clip-engine.lua] and import it into ReaLearn. + +For a basic test, press the _Play_ button (one-channel controller) or the first push encoder (multi-channel controller). +It should play a test clip. + +The Mackie Control controller preset doesn't define any Projection layout, so you can't see easily which control has which effect. +But you can use "Filter source" in the main compartment and touch some controls to see what action would be triggered when invoking it. +Or you have a look into the Lua file of the main preset (scroll down to see the actual mappings). + +=== OSC apps + +There are plenty of OSC apps out there and even more layouts. +Unless you are satisfied with my super simplistic TouchOSC Mk2 preset, you'll need to adjust the main preset Lua code. +Recording is not implemented at the moment because it would need some tweaking of the OSC layout to get proper visual feedback for it (red color). +Feel free to adjust it to your needs. + +. Set up your TouchOSC device globally in ReaLearn (not in REAPER!) by following link:https://github.com/helgoboss/helgobox/blob/master/doc/realearn-user-guide.adoc#hexler-touchosc-the-recent-version[these instructions]. +. Select the device as both _Control input_ and _Feedback output_. +. In TouchOSC: Choose the built-in "Simple Mk2" layout and open the tab "Matrix". +. Import the main preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/main-presets/osc-to-clip-engine.lua[osc-to-clip-engine.lua] and import it into ReaLearn. + +For a basic test, press the top-left grid button. +It should play a test clip. + +=== MIDI keyboards + +You can play clips with a simple MIDI keyboard. +Every octave covers one column. +Clips start immediately, not quantized (irrespective of the quantization settings of the clip matrix). +Clip recording is disabled because it would be a bit confusing without proper visual feedback. + +. Select the device as _Control input_. +. Import the controller preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/controller-presets/notes.lua[notes.lua] and import it into ReaLearn. +. Import the main preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/main-presets/notes-to-clip-engine.lua[notes-to-clip-engine.lua] and import it into ReaLearn. + +For a basic test, press and hold key "C0" (the deepest note, so you might need to press "Octave down" a few times). +It should play a test clip. + +=== Computer keyboard + +You can play clips with the computer keyboard. [1, Q, A, Z] to [8, I, K, comma] are mapped to clip launching. [9, O, L, period] are mapped to scene launching. +Clips start immediately, not quantized (irrespective of the quantization settings of the clip matrix). +Clip recording is disabled because it would be a bit confusing without proper visual feedback. + +. Make sure your keyboard layout is switched to English, QWERTY. +. Select "Computer keyboard" as _Control input_. +. Import the controller preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/controller-presets/qwerty.lua[qwerty.lua] and import it into ReaLearn. +. Import the main preset +* Copy the content of link:https://github.com/helgoboss/helgobox/blob/master/resources/main-presets/qwerty-to-clip-engine.lua[qwerty-to-clip-engine.lua] and import it into ReaLearn. + +For a basic test, press and hold key "1". +It should play a test clip. + +== Features + +NOTE: This is a work-in-progress feature list of the Clip Engine. +Not very polished yet. + +=== Clip playback and recording + +* Launch and record MIDI and audio clips + +=== Operates also when REAPER project not playing + +* The engine uses REAPER's preview register mechanism to inject MIDI or audio material directly into REAPER tracks. +* That means, it's not necessary anymore to have the REAPER transport running in order to play or record clips. + +=== Doesn't mess with timeline items + +* For playback and recording, the engine doesn't touch items on the project timeline. + +=== Record performance as multi-track audio + +* One can simply record a clip engine performance as audio items on column tracks by choosing to record "Track output" in REAPER's track settings and pressing the REAPER record button. + +=== Timeline sync + +* Playback of clips is synchronized with REAPER transport actions (play, pause, stop). +* Clips that have been playing when the REAPER's stop button was pressed are memorized and will start playing again when pressing the REAPER's play button. + +=== Automatic tempo adjustment + +* Tempo of beat-based clips is automatically adjusted to the current project tempo. + +=== Support for different time signatures + +* This hasn't been tested thoroughly yet but in theory, the engine should be fine with arbitrary time signatures (signature changes during the project might be problematic though). + +=== Combined audio/MIDI columns + +* One column can hold both audio and MIDI clips + +=== Volume adjustment per clip + +* It's possible to adjust the volume of a clip, not just the volume of column tracks. +* Adjusting the volume of MIDI clips modifies the velocity. + +=== Non-exclusive columns + +* If requested, a column can play multiple clips simultaneously. + +=== Columns that don't follow scenes + +* If requested, columns can be configured to ignore scenes. + +=== Customizable clip start/stop timing + +* Start/stop timing of playback and recording is completely customizable. +* One can use arbitrary even quantizations (such as 1 bar, 2 bars or 3/8th). +* Or choose to start/stop immediately. +* When stopping a clip, an additional option is available: Stop at end of clip + +=== Meta clips + +* Record and play MIDI clips that control the clip matrix itself (or other clip matrix instances). + +=== Record with predefined length + +* Clip recording can stop automatically after a predefined length (arbitrary even quantizations, e.g. 4 bars) + +=== Scenes + +* Launch complete rows of clips + +[#downbeat] +=== Adjustable downbeat position + +* It's possible to start clip playback in the count-in phase. +* This makes it possible to play pickup beats (also known as anacrusis). + +=== Support for project play rate changes + +* Playback doesn't just react to tempo changes but also to play rate changes. + +=== Non-destructive sections + +* Play just a portion of the audio/MIDI material + +=== Backpedaling + +* Backpedal from certain clip start/stop actions before they actually happen. +* That's useful e.g. when you decide you didn't want to stop a clip and let it keep playing, or when you are still in the count-in phase of a clip and decide to not let the clip play anymore. + +=== Recording input modes + +* Choose from where to record material. +* Audio can be recorded from: +** Track input +** Track output +*** With this, you can record virtual instruments as audio instead of MIDI. +** FX input of ReaLearn +*** Lets you record any audio that you send to the ReaLearn track. +* MIDI can be recorded from +** Track input +** FX input of ReaLearn + +=== Extensive controller integration + +* Thanks to the deep ReaLearn integration, you can add support for MIDI/OSC controllers yourself and customize existing presets to your needs. +* Not just for the control direction, but also for the feedback direction. +See what's actually happening by mapping clip properties to LEDs motor faders or displays. +* Display things like the clip play state, whether it's looped or not or real-time position within the clip. +- This also includes text feedback to hardware displays. + +=== [.line-through]#Rollback to previous clip# + +* When cancelling recording on an already filled clip slot, the previous clip is restored. + +=== Multiple matrices + +* It's possible to open multiple completely independent clip matrices within one project (by using multiple ReaLearn instances). + +=== Seek within clips + +* Seek within playing or paused clips (useful for long tempo-independent material). + +=== Pause clips + +* Pause playing clips (useful for long tempo-independent material) + +=== Support for different time bases + +* Switch between time base _Beat_ (tempo-dependent) and _Time_ (tempo-independent). + +=== Flexible inheritance structure + +* Column settings can override matrix settings +* Clip settings can override column settings + +=== Lua import/export + +* The complete matrix settings and contents can be exported/imported as Lua code. +* That makes it for example possible to modify a clip matrix in programmatic ways or to generate a clip matrix. + +=== Undo/redo history + +* Because REAPER's undo/redo sometimes is a bit too coarse- or fine-grained, the Clip Engine provides its own undo/redo history on instance basis. + +=== Features related to audio clips only + +==== Broad audio format support + +* The engine supports all audio formats for playback which are supported by REAPER itself because it uses REAPER's PCM source system under the hood. + +==== Auto-fades + +* The reason why you don't hear crazy clicking when recording audio loops is that the engine automatically introduces non-destructive fades for ensuring smooth and click-less audio playback. +* There are different kinds of fades: Source fades, section fades, fades for sudden start/stop ... it's all very customizable. + +==== Resampling algorithms + +* For resampling and VariSpeed, you can choose among all resampling engines available in REAPER. + +==== Time stretching algorithms + +* For time stretching, you can choose among all pitch shift engines available in REAPER. + +==== Multi-channel support + +* Basic support multi-channel audio clips. + +==== In-memory caching + +* One can load the source audio file completely into memory, which ensures that the clip engine doesn't run into problems where it cannot read from the disk quickly enough. + +=== Features related to MIDI clips only + +==== MIDI file support + +Supported MIDI data modes for playback: + +* External MIDI files (can be imported but will be converted to in-matrix clips, the reference will not be kept) +* In-matrix clips +** Means that MIDI data of the clip is embedded in the clip matrix instance itself. + +==== MIDI overdub + +* You can overdub more material onto an existing MIDI clip. + +==== Reset messages + +* Complete customization which MIDI reset messages will be sent: +** Playing-notes-off +** All-notes-off +** All-sounds-off +** Sustain off +** Reset all controllers +* Per event: +** Sudden start/stop +** Complete loop (not yet implemented) +** Section +** Source + +==== Downbeat / pickup beat detection + +* When recording MIDI, notes preceding the start of the bar can be recorded and change the downbeat position accordingly (see <> feature). + +== Internal features + +The following list of features is probably more interesting for developers than for users. + +=== Open source (GPL v3.0) + +* The engine is available as open-source, which is an important precondition to future proofness, given that it's not backed by a big company but created by a solo developer. + +=== Scalable + +* The biggest issue with Playtime 1 was that it was not scalable, neither in terms of further development (old frameworks, bad code base) nor in terms of possibilities (relying too much on REAPER's linear workflow, which comes with severe limitations). +* The Playtime 2 Clip Engine fixes that. +It's built with scalability in mind, uses a more modern programming language allowing for more productivity (Rust) and builds on lower-level primitives of the REAPER API. + +=== Sample-accurate continuity + +* The engines advances clips with the primary focus on continuity of playback. +* The big part of the engine operates in the real-time thread and can therefore operate on sample accuracy. + +=== Native + +* This is a clean native implementation of a clip engine, it's not a script! +Neither a ReaScript nor a JS FX. +It wouldn't be possible nor desirable to write a clip engine with this performance and broad feature set in these languages. +* The clip engine is written in Rust, a modern C++ alternative which is just as fast (no garbage collection) but more modern, clean and safe in many ways. + +=== Lock- and allocation-free design for real-time threads + +* In most places, the engine doesn't use lock mutexes in real-time threads. +** In the few places where locks are used, they are contention-free - rigorously asserted at debug time by panicking when attempting to lock a mutex that's already locked. +* Also, great care is taken to not allocate in real-time threads. +** This is rigorously asserted at debug time by aborting on allocation in real-time threads. +* In other words: The engine is conceived for live usage where it's important that most interactions don't cause crackling and dropouts ++ +[CAUTION] +==== +It's not there yet because we are in an early alpha stage! +==== + +=== Integer arithmetics + +* In order to avoid rounding issues, the engine uses integer arithmetics wherever feasible. +* For example, at runtime it addresses clip positions in frames instead of seconds. + +=== Clean separation of processing responsibilities + +* The clip processing code is cleanly separated according to its responsibility (_supplier chain_ concept). + +=== Clean state machine modeling of clip play/record logic + +* Instead of maintaining dozens of booleans and integers, the clip processing states are modeled cleanly as state machine, making use of Rusts's awesome "rich" enum data structures. + +=== Isolated module + +* Although the engine is shipped with ReaLearn, it doesn't depend on it. +* The engine code is located in a separate module. +ReaLearn depends on that module. + +=== Asynchronous real-time debug logging + +* In order to allow easier debugging of real-time processing issues, the engine has built-in asynchronous logging, which doesn't block the real-time thread. + +=== Asynchronous real-time metrics + +* The engine can easily expose metrics to Prometheus, even asynchronously, which is important to not distort the metrics in real-time code. + +=== GUI-agnostic + +* The engine is built in a way that allows different GUIs to be built on top of it. +* The first one will be Playtime App (the main GUI). + +== Roadmap + +Some things that I plan to add or improve: + +* Quantization of MIDI clips +* Auto quantization of MIDI clips during recording +* Tempo detection (let length of first recorded clip determine global tempo) +* Time stretching performance improvements +* Improve pre-buffering of audio material +* Runtime scriptability (e.g. for adding small in-REAPER GUIs) +* Follow actions +* Legato +* Repeat +* Scenes with associated tempo and time signature changes +* Use time stretching as an effect for time-based material (as effect) +* Reverse +* Pendulum looping +* Auto-detect input with global record button +* Wait for input (gives you more time to get ready for playing after having pressed the record button) +* Downbeat detection for audio recordings +* Velocity-sensitive clip launching diff --git a/plugin-reaper-realearn/doc/playtime/README.adoc b/plugin-reaper-realearn/doc/playtime/README.adoc new file mode 100644 index 0000000..43b57fd --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/README.adoc @@ -0,0 +1,6 @@ += Playtime Reference +:experimental: + +This reference is written in AsciiDoc and follows the directory conventions of the documentation site generator link:https://antora.org/[Antora]. + +A good place to start is link:modules/ROOT/pages/introduction.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/antora.yml b/plugin-reaper-realearn/doc/playtime/antora.yml new file mode 100644 index 0000000..8af0b63 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/antora.yml @@ -0,0 +1,10 @@ +name: playtime +title: Playtime Reference +version: ~ +nav: + - modules/ROOT/nav.adoc +start_page: introduction.adoc +asciidoc: + attributes: + # For making menu and button macros work + experimental: true \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/content-panel-0.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/content-panel-0.png new file mode 100644 index 0000000..63b22de Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/content-panel-0.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/info-panel-0.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/info-panel-0.png new file mode 100644 index 0000000..5582c95 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/info-panel-0.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/inspector-0.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/inspector-0.png new file mode 100644 index 0000000..09e6ca7 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/inspector-0.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-0.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-0.png new file mode 100644 index 0000000..e8342a2 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-0.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-1.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-1.png new file mode 100644 index 0000000..890db9f Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-1.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-2.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-2.png new file mode 100644 index 0000000..3a3a1b4 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-2.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-3.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-3.png new file mode 100644 index 0000000..f71d61d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/matrix-area-3.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/toolbar-0.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/toolbar-0.png new file mode 100644 index 0000000..348a111 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/toolbar-0.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/track-area-0.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/track-area-0.png new file mode 100644 index 0000000..3b6cbca Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/track-area-0.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/track-area-1.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/track-area-1.png new file mode 100644 index 0000000..2a854c0 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/area/track-area-1.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/append.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/append.png new file mode 100644 index 0000000..d9b1c19 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/append.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/merge-at-cursor.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/merge-at-cursor.png new file mode 100644 index 0000000..312a5a5 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/merge-at-cursor.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/replace.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/replace.png new file mode 100644 index 0000000..e71f364 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/arrangement-writing-mode/replace.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell.png new file mode 100644 index 0000000..97e66fc Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/exclusive-mode.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/exclusive-mode.png new file mode 100644 index 0000000..f339d9e Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/exclusive-mode.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/follow-scenes.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/follow-scenes.png new file mode 100644 index 0000000..0f21909 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/follow-scenes.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/has-overrides.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/has-overrides.png new file mode 100644 index 0000000..1a31b83 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/has-overrides.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/label.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/label.png new file mode 100644 index 0000000..3c631c5 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/label.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/stop.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/stop.png new file mode 100644 index 0000000..4553d26 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/column-cell/stop.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffer-fallback-count.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffer-fallback-count.png new file mode 100644 index 0000000..1e4532e Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffer-fallback-count.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffer-miss-count.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffer-miss-count.png new file mode 100644 index 0000000..30834a6 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffer-miss-count.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-avg.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-avg.png new file mode 100644 index 0000000..101b180 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-avg.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-max.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-max.png new file mode 100644 index 0000000..d9bf4d7 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-max.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-min.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-min.png new file mode 100644 index 0000000..7cb9a4a Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-min.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/tempo-buffer-size.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/tempo-buffer-size.png new file mode 100644 index 0000000..05f30f9 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/stats/tempo-buffer-size.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/warnings.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/warnings.png new file mode 100644 index 0000000..3635bf0 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/info-panel/warnings.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip.png new file mode 100644 index 0000000..84f7d3b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/audio.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/audio.png new file mode 100644 index 0000000..a6b6317 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/audio.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/basics.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/basics.png new file mode 100644 index 0000000..c9a348f Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/basics.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/color.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/color.png new file mode 100644 index 0000000..a1fda11 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/color.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/downbeat.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/downbeat.png new file mode 100644 index 0000000..fcb2439 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/downbeat.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/expert-settings.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/expert-settings.png new file mode 100644 index 0000000..d9e5284 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/expert-settings.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/length.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/length.png new file mode 100644 index 0000000..353abca Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/length.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/looped.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/looped.png new file mode 100644 index 0000000..78aef5c Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/looped.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-channel-original.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-channel-original.png new file mode 100644 index 0000000..767d1b5 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-channel-original.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-channel.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-channel.png new file mode 100644 index 0000000..a00baf2 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-channel.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-left.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-left.png new file mode 100644 index 0000000..7ca9b43 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-left.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-right.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-right.png new file mode 100644 index 0000000..a3dd3cf Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-right.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-left.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-left.png new file mode 100644 index 0000000..d18ef9f Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-left.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-right.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-right.png new file mode 100644 index 0000000..4574f5d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-right.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/none.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/none.png new file mode 100644 index 0000000..0a220b9 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi-reset-behavior/none.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi.png new file mode 100644 index 0000000..d2604d3 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/midi.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/name.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/name.png new file mode 100644 index 0000000..e2f9a2e Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/name.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/overdub.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/overdub.png new file mode 100644 index 0000000..526db43 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/overdub.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/pitch.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/pitch.png new file mode 100644 index 0000000..e58cb1b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/pitch.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/quantize.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/quantize.png new file mode 100644 index 0000000..9b4d518 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/quantize.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/section.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/section.png new file mode 100644 index 0000000..aec2840 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/section.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-cut-length.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-cut-length.png new file mode 100644 index 0000000..a982aef Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-cut-length.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-cut-start.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-cut-start.png new file mode 100644 index 0000000..723d85b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-cut-start.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-fades.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-fades.png new file mode 100644 index 0000000..9334563 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-fades.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-info.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-info.png new file mode 100644 index 0000000..afca5f8 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/source-info.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-position.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-position.png new file mode 100644 index 0000000..a1d2938 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-position.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-timing/custom.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-timing/custom.png new file mode 100644 index 0000000..1152eca Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-timing/custom.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-timing/immediately.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-timing/immediately.png new file mode 100644 index 0000000..75369ed Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/start-timing/immediately.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/custom.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/custom.png new file mode 100644 index 0000000..1152eca Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/custom.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/immediately.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/immediately.png new file mode 100644 index 0000000..7b1812d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/immediately.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/like-start-timing.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/like-start-timing.png new file mode 100644 index 0000000..13d47f1 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/like-start-timing.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/until-end-of-clip.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/until-end-of-clip.png new file mode 100644 index 0000000..5c7cb65 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/stop-timing/until-end-of-clip.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/sync-to-project-tempo.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/sync-to-project-tempo.png new file mode 100644 index 0000000..3c8faae Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/sync-to-project-tempo.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/synchronization.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/synchronization.png new file mode 100644 index 0000000..87c5a89 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/synchronization.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/tempo.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/tempo.png new file mode 100644 index 0000000..7f1ad7b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/tempo.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-signature.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-signature.png new file mode 100644 index 0000000..a63614b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-signature.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-stretch-mode/keeping-pitch.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-stretch-mode/keeping-pitch.png new file mode 100644 index 0000000..dbc51cc Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-stretch-mode/keeping-pitch.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-stretch-mode/vari-speed.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-stretch-mode/vari-speed.png new file mode 100644 index 0000000..a42fbca Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/time-stretch-mode/vari-speed.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/triggering.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/triggering.png new file mode 100644 index 0000000..569be5f Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/triggering.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/velocity-sensitivity.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/velocity-sensitivity.png new file mode 100644 index 0000000..84b3626 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/velocity-sensitivity.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/volume.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/volume.png new file mode 100644 index 0000000..830ec0c Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/clip/volume.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/custom-name.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/custom-name.png new file mode 100644 index 0000000..bd61a04 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/custom-name.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/exclusive-mode.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/exclusive-mode.png new file mode 100644 index 0000000..c76c84a Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/exclusive-mode.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/follow-scenes.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/follow-scenes.png new file mode 100644 index 0000000..b0c7887 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/follow-scenes.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/playback-track.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/playback-track.png new file mode 100644 index 0000000..adaa99a Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/playback-track.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/inherit.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/inherit.png new file mode 100644 index 0000000..2eb93cb Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/inherit.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/momentary.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/momentary.png new file mode 100644 index 0000000..08feb95 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/momentary.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/retrigger.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/retrigger.png new file mode 100644 index 0000000..3ef4e71 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/retrigger.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/toggle.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/toggle.png new file mode 100644 index 0000000..a2ad21d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/trigger-mode/toggle.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/velocity-sensitivity.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/velocity-sensitivity.png new file mode 100644 index 0000000..ca27dcb Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/column/velocity-sensitivity.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/activate-slot-on-trigger.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/activate-slot-on-trigger.png new file mode 100644 index 0000000..26af303 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/activate-slot-on-trigger.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/limit-recording-length.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/limit-recording-length.png new file mode 100644 index 0000000..56667b2 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/limit-recording-length.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/midi-quantization-grid.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/midi-quantization-grid.png new file mode 100644 index 0000000..c564954 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/midi-quantization-grid.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-allow-tempo-detection-recording.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-allow-tempo-detection-recording.png new file mode 100644 index 0000000..5488065 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-allow-tempo-detection-recording.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-auto-quantize.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-auto-quantize.png new file mode 100644 index 0000000..9ad519f Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-auto-quantize.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-detect-downbeat.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-detect-downbeat.png new file mode 100644 index 0000000..7b92e23 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-detect-downbeat.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-length.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-length.png new file mode 100644 index 0000000..1495a63 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-length.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-looped.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-looped.png new file mode 100644 index 0000000..303ac5e Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-looped.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo.png new file mode 100644 index 0000000..85af643 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/auto.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/auto.png new file mode 100644 index 0000000..cfbe4f4 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/auto.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/no.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/no.png new file mode 100644 index 0000000..62886d4 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/no.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/yes.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/yes.png new file mode 100644 index 0000000..a58da9d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/yes.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/resample-mode.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/resample-mode.png new file mode 100644 index 0000000..41bd50a Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/resample-mode.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/tempo-detection-range.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/tempo-detection-range.png new file mode 100644 index 0000000..d59971d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/tempo-detection-range.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/transport-sync.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/transport-sync.png new file mode 100644 index 0000000..3a3d6ab Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/transport-sync.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/velocity-sensitivity.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/velocity-sensitivity.png new file mode 100644 index 0000000..abe1607 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/matrix/velocity-sensitivity.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/row/name.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/row/name.png new file mode 100644 index 0000000..27b5bfe Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/row/name.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/row/tempo.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/row/tempo.png new file mode 100644 index 0000000..5d5e1ed Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/row/tempo.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/slot/clip-picker.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/slot/clip-picker.png new file mode 100644 index 0000000..56bfe8c Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/slot/clip-picker.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/slot/clip.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/slot/clip.png new file mode 100644 index 0000000..9cb2b9a Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/slot/clip.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/track/color.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/track/color.png new file mode 100644 index 0000000..c10e7e4 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/track/color.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/track/name.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/track/name.png new file mode 100644 index 0000000..ea019c9 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/inspector/track/name.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell.png new file mode 100644 index 0000000..e8342a2 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell/settings.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell/settings.png new file mode 100644 index 0000000..2e0c008 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell/settings.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell/stop.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell/stop.png new file mode 100644 index 0000000..024ec95 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix-cell/stop.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/add-column.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/add-column.png new file mode 100644 index 0000000..8c9cddf Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/add-column.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/add-row.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/add-row.png new file mode 100644 index 0000000..d288468 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/add-row.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/audio-template-button.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/audio-template-button.png new file mode 100644 index 0000000..bbbe10b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/audio-template-button.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/midi-template-button.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/midi-template-button.png new file mode 100644 index 0000000..c500784 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/midi-template-button.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/mixed-template-button.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/mixed-template-button.png new file mode 100644 index 0000000..4008b6e Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/mixed-template-button.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/scroll-indicator.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/scroll-indicator.png new file mode 100644 index 0000000..3071697 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/scroll-indicator.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/template-panel.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/template-panel.png new file mode 100644 index 0000000..7a27beb Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/matrix/template-panel.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/purchase/verify-license-key.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/purchase/verify-license-key.png new file mode 100644 index 0000000..eb1f0fb Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/purchase/verify-license-key.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell.png new file mode 100644 index 0000000..da46839 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell/label.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell/label.png new file mode 100644 index 0000000..18ebf62 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell/label.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell/play-scene.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell/play-scene.png new file mode 100644 index 0000000..304b2c8 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/row-cell/play-scene.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/sequences/delete.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/sequences/delete.png new file mode 100644 index 0000000..1bf4684 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/sequences/delete.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/sequences/edit.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/sequences/edit.png new file mode 100644 index 0000000..36ed658 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/sequences/edit.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-color-position.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-color-position.png new file mode 100644 index 0000000..d690bef Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-color-position.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-height.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-height.png new file mode 100644 index 0000000..d3d054f Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-height.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-progress-position.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-progress-position.png new file mode 100644 index 0000000..d455b06 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-progress-position.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-width.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-width.png new file mode 100644 index 0000000..4e2b7f4 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/cell-width.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-cell-selection-as-crosshair.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-cell-selection-as-crosshair.png new file mode 100644 index 0000000..33f3f38 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-cell-selection-as-crosshair.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-clip-activity.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-clip-activity.png new file mode 100644 index 0000000..abdc970 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-clip-activity.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-column-numbers.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-column-numbers.png new file mode 100644 index 0000000..b2647ff Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-column-numbers.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-control-unit-frames.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-control-unit-frames.png new file mode 100644 index 0000000..6cd8197 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-control-unit-frames.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-hypothetical-play-cursor.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-hypothetical-play-cursor.png new file mode 100644 index 0000000..cb334a1 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/show-hypothetical-play-cursor.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/tempo-latency.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/tempo-latency.png new file mode 100644 index 0000000..c7f8d45 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/settings/tempo-latency.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell.png new file mode 100644 index 0000000..76315c9 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/clip-count.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/clip-count.png new file mode 100644 index 0000000..97e4193 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/clip-count.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/content.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/content.png new file mode 100644 index 0000000..a74cbad Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/content.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/record.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/record.png new file mode 100644 index 0000000..4d09898 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/record.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/trigger.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/trigger.png new file mode 100644 index 0000000..304b2c8 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/slot-cell/trigger.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/click-volume.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/click-volume.png new file mode 100644 index 0000000..7efb08f Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/click-volume.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/count-in.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/count-in.png new file mode 100644 index 0000000..e1a8f86 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/count-in.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/tap-volume.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/tap-volume.png new file mode 100644 index 0000000..da0a1da Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/tempo-settings/tap-volume.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/title-bar/purchase-playtime.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/title-bar/purchase-playtime.png new file mode 100644 index 0000000..8376302 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/title-bar/purchase-playtime.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/title-bar/tempo-detection-recording-hint.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/title-bar/tempo-detection-recording-hint.png new file mode 100644 index 0000000..7dcbde8 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/title-bar/tempo-detection-recording-hint.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/todo/clip.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/todo/clip.png new file mode 100644 index 0000000..84f7d3b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/todo/clip.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-sequence-picker.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-sequence-picker.png new file mode 100644 index 0000000..14a343b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-sequence-picker.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-sequencer.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-sequencer.png new file mode 100644 index 0000000..1180d43 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-sequencer.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-start-timing.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-start-timing.png new file mode 100644 index 0000000..568fab9 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/matrix-start-timing.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/metronome.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/metronome.png new file mode 100644 index 0000000..abf4a67 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/metronome.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/open-ended-recording.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/open-ended-recording.png new file mode 100644 index 0000000..5462464 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/open-ended-recording.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/pane-toggler.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/pane-toggler.png new file mode 100644 index 0000000..2a7a5e6 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/pane-toggler.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/play-rate.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/play-rate.png new file mode 100644 index 0000000..0656d35 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/play-rate.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/play-stop-matrix-sequence.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/play-stop-matrix-sequence.png new file mode 100644 index 0000000..f8a8779 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/play-stop-matrix-sequence.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/record-matrix-sequence.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/record-matrix-sequence.png new file mode 100644 index 0000000..6f7fcfe Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/record-matrix-sequence.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/show-hide-midi-triggers.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/show-hide-midi-triggers.png new file mode 100644 index 0000000..e21405e Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/show-hide-midi-triggers.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/smart-record.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/smart-record.png new file mode 100644 index 0000000..0fda7b1 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/smart-record.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/start-stop-playback.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/start-stop-playback.png new file mode 100644 index 0000000..f8a8779 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/start-stop-playback.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tap-tempo.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tap-tempo.png new file mode 100644 index 0000000..e910bd1 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tap-tempo.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tempo-section.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tempo-section.png new file mode 100644 index 0000000..7e51ed9 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tempo-section.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tempo.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tempo.png new file mode 100644 index 0000000..f3cad3b Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/tempo.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/time-signature.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/time-signature.png new file mode 100644 index 0000000..a1f4c0d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/time-signature.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/transport-section.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/transport-section.png new file mode 100644 index 0000000..b942880 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/transport-section.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/visual-metronome.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/visual-metronome.png new file mode 100644 index 0000000..7a73bba Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/visual-metronome.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/write-to-arrangement.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/write-to-arrangement.png new file mode 100644 index 0000000..db717a5 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/toolbar/write-to-arrangement.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/arm.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/arm.png new file mode 100644 index 0000000..a418953 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/arm.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/fx-chain.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/fx-chain.png new file mode 100644 index 0000000..9a34832 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/fx-chain.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-auto.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-auto.png new file mode 100644 index 0000000..9fa1bcf Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-auto.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-off.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-off.png new file mode 100644 index 0000000..22b0551 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-off.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-on.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-on.png new file mode 100644 index 0000000..a2c8edb Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring-on.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring.png new file mode 100644 index 0000000..1fb2c23 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-monitoring.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-type-indicator.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-type-indicator.png new file mode 100644 index 0000000..591bc79 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input-type-indicator.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input.png new file mode 100644 index 0000000..901ad49 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/input.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/instrument-fx.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/instrument-fx.png new file mode 100644 index 0000000..e359c82 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/instrument-fx.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/label.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/label.png new file mode 100644 index 0000000..a58213e Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/label.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/map-to-midi-channel.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/map-to-midi-channel.png new file mode 100644 index 0000000..0f738a7 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/map-to-midi-channel.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/midi-channel-filter.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/midi-channel-filter.png new file mode 100644 index 0000000..8548aec Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/midi-channel-filter.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/mute.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/mute.png new file mode 100644 index 0000000..996b2d3 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/mute.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pan.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pan.png new file mode 100644 index 0000000..a7a9383 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pan.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-critical.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-critical.png new file mode 100644 index 0000000..7ae049d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-critical.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-significant.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-significant.png new file mode 100644 index 0000000..f4810a1 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-significant.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-tiny.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-tiny.png new file mode 100644 index 0000000..0b9ed02 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/pdc-tiny.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/routing.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/routing.png new file mode 100644 index 0000000..469dcd7 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/routing.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/solo.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/solo.png new file mode 100644 index 0000000..b7e4328 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/solo.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/volume.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/volume.png new file mode 100644 index 0000000..cdb8066 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/track-panel/volume.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/waveform-view.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/waveform-view.png new file mode 100644 index 0000000..63b22de Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/elements/waveform-view.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/arrangement-writing-mode.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/arrangement-writing-mode.png new file mode 100644 index 0000000..811bf05 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/arrangement-writing-mode.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen-areas.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen-areas.png new file mode 100644 index 0000000..c832b15 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen-areas.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen-empty.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen-empty.png new file mode 100644 index 0000000..d4575f4 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen-empty.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen.png new file mode 100644 index 0000000..606861a Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/main-screen.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/purchase.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/purchase.png new file mode 100644 index 0000000..b31c4f6 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/purchase.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/sequences.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/sequences.png new file mode 100644 index 0000000..672803d Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/sequences.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/settings.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/settings.png new file mode 100644 index 0000000..7e84a86 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/settings.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/tempo-settings.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/tempo-settings.png new file mode 100644 index 0000000..d6e68a3 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/generated/screenshots/main/tempo-settings.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-2-toolbar-icon.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-2-toolbar-icon.png new file mode 100644 index 0000000..988e5c0 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-2-toolbar-icon.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-custom-toolbar-icon.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-custom-toolbar-icon.png new file mode 100644 index 0000000..e9dd191 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-custom-toolbar-icon.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-toolbar-icon.png b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-toolbar-icon.png new file mode 100644 index 0000000..f49e5d2 Binary files /dev/null and b/plugin-reaper-realearn/doc/playtime/modules/ROOT/images/screenshots/playtime-toolbar-icon.png differ diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/nav.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/nav.adoc new file mode 100644 index 0000000..083000c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/nav.adoc @@ -0,0 +1,61 @@ +* xref:introduction.adoc[] +* xref:installation.adoc[] +* xref:usage.adoc[] +** xref:usage/prepare.adoc[] +** xref:usage/create.adoc[] +** xref:usage/play.adoc[] +** xref:usage/grid-controllers.adoc[] +** xref:usage/adjust.adoc[] +** xref:usage/structure.adoc[] +** xref:usage/arrangement.adoc[] +** xref:usage/render.adoc[] +* xref:key-concepts.adoc[] +* xref:user-interface.adoc[] +** xref:user-interface/general.adoc[] +** xref:user-interface/title-bar.adoc[] +** xref:user-interface/navigation-bar.adoc[] +** xref:user-interface/toolbar.adoc[] +** xref:user-interface/matrix-area.adoc[] +** xref:user-interface/track-area.adoc[] +** xref:user-interface/info-panel.adoc[] +** xref:user-interface/inspector.adoc[] +*** xref:user-interface/inspector/matrix.adoc[] +**** xref:user-interface/inspector/matrix/playback.adoc[] +**** xref:user-interface/inspector/matrix/recording.adoc[] +*** xref:user-interface/inspector/column.adoc[] +**** xref:user-interface/inspector/column/playback.adoc[] +**** xref:user-interface/inspector/column/recording.adoc[] +*** xref:user-interface/inspector/track.adoc[] +*** xref:user-interface/inspector/row.adoc[] +*** xref:user-interface/inspector/slot.adoc[] +*** xref:user-interface/inspector/clip.adoc[] +** xref:user-interface/dialogs.adoc[] +*** xref:user-interface/dialogs/purchase-and-activate-dialog.adoc[] +*** xref:user-interface/dialogs/settings-dialog.adoc[] +*** xref:user-interface/dialogs/sequences-dialog.adoc[] +*** xref:user-interface/dialogs/arrangement-writing-mode-dialog.adoc[] +*** xref:user-interface/dialogs/tempo-settings-dialog.adoc[] +** xref:user-interface/menus.adoc[] +*** xref:user-interface/smart-record-menu.adoc[] +*** xref:user-interface/menus/matrix-menu.adoc[] +*** xref:user-interface/menus/column-menu.adoc[] +*** xref:user-interface/menus/row-menu.adoc[] +*** xref:user-interface/menus/slot-menu.adoc[] +*** xref:user-interface/menus/clip-menu.adoc[] +*** xref:user-interface/menus/source-menu.adoc[] +** xref:user-interface/keyboard-shortcuts.adoc[] +* xref:further-concepts.adoc[] +** xref:further-concepts/general.adoc[] +** xref:further-concepts/matrix.adoc[] +** xref:further-concepts/slot.adoc[] +* xref:advanced-usage-scenarios.adoc[] +** xref:advanced-usage-scenarios/start-custom-playtime.adoc[] +** xref:advanced-usage-scenarios/record-performance-as-audio.adoc[] +** xref:advanced-usage-scenarios/control-with-items.adoc[] +** xref:advanced-usage-scenarios/meta-clips.adoc[] +** xref:advanced-usage-scenarios/multiple-instances.adoc[] +** xref:advanced-usage-scenarios/generate-matrix.adoc[] +* xref:reaper-actions.adoc[] +* xref:realearn-targets.adoc[] +* xref:configuration-files.adoc[] +* xref:supported-controllers.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios.adoc new file mode 100644 index 0000000..fb3a69b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios.adoc @@ -0,0 +1,5 @@ += Advanced usage scenarios + +In xref:usage.adoc[], we have learned how Playtime is typically used. But it doesn't end there! Playtime is designed in a quite open-ended way, which makes it versatile enough to use it in plenty of other scenarios. + +This section explores some of the more advanced usage scenarios. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/control-with-items.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/control-with-items.adoc new file mode 100644 index 0000000..214c08d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/control-with-items.adoc @@ -0,0 +1,118 @@ += Control Playtime with items + +Because Playtime is an instrument plug-in, it can be controlled via MIDI. Not just via external MIDI controllers, but also from within REAPER. You can just put MIDI items on the Playtime track and start REAPER playback, exactly as you would do it with a synthesizer. + +== 1. Set up matrix + +Click xref:user-interface/matrix-area.adoc#matrix-cell-settings[] to open the xref:user-interface/inspector/matrix.adoc[] and make the following settings: + +|=== +| Setting | Value | Reason + +| xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-start-timing[] +| xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing-immediately[] +.2+| Position and length of a note in the MIDI editor should correspond exactly to the position and length of clip playback. + +| xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-trigger-mode[] +| xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode-momentary[] + +| xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-activate-slot-on-trigger[] +| Off +| We don't want the Playtime selection to jump around like crazy. + +|=== + +Also ensure that you haven't overridden the first two properties on column or clip level! + +Additionally, you might want to switch off xref:user-interface/inspector/column/playback.adoc#inspector-column-exclusive-mode[] for columns, in order to get more freedom of playing. + +== 2. Create MIDI triggers + +Before you can control Playtime with MIDI notes, you need to set up MIDI triggers. + +=== Option A: Learn MIDI triggers manually + +One way to do this is to learn the necessary MIDI triggers manually by using the xref:user-interface/toolbar.adoc#toolbar-show-hide-midi-triggers[] feature. See xref:usage/play.adoc#feature-keys[]. + +[[assign-midi-triggers-automatically]] +=== Option B: Assign MIDI triggers automatically + +Another way to do this is to automatically assign MIDI notes to each slot cell. We can easily do that by importing a snippet of Lua code into ReaLearn. + +Below code snippet by default spreads MIDI notes row by row in groups of 8, starting at the note C3. You can change that by modifying the configuration section before importing it into ReaLearn. + +. Copy below code to the clipboard +. Press xref:helgobox::app/user-interface/navigation-bar.adoc#navbar-show-helgobox-plugin[] in the navigation bar on the left. This should open the xref:realearn::user-interface/main-panel.adoc[ReaLearn main panel]. +. Press the xref:realearn::user-interface/main-panel/menu-bar.adoc#import-from-clipboard[] ++ +CAUTION: This will overwrite existing MIDI mappings in the xref:realearn::key-concepts.adoc#unit[ReaLearn main unit]. +. After confirming the import, you should see a list of mappings in ReaLearn. But also in the xref:user-interface.adoc[Playtime user interface], when pressing xref:user-interface/toolbar.adoc#toolbar-show-hide-midi-triggers[showing the MIDI triggers]. + +[source,lua] +---- +-- Configuration + +-- The first mapped note (key 48 correponds to C3) +local first_key = 48 +-- The first mapped column +local first_column = 0 +-- The first mapped row +local first_row = 0 +-- If true, assignments will be spread row by row, otherwise column by column +local row_wise = true +-- Make that many assignments until jumping to the next column or row +local group_size = 8 + +-- Build mappings + +local mappings = {} +for k = first_key, 127 do + local i = k - first_key + local i1 = math.floor(i / group_size) + local i2 = i % group_size + local mapping = { + source = { + kind = "MidiNoteVelocity", + channel = 0, + key_number = k, + }, + target = { + kind = "PlaytimeSlotTransportAction", + slot = { + address = "ByIndex", + column_index = first_column + if row_wise then i2 else i1, + row_index = first_row + if row_wise then i1 else i2, + }, + action = "Trigger", + }, + } + table.insert(mappings, mapping) +end + +-- Return result + +return { + kind = "MainCompartment", + value = { + mappings = mappings, + }, +} +---- + +[[record-and-edit-midi-items]] +== 3. Record and edit MIDI items + +Now we are ready to record some MIDI items that control our clips. + +. Ensure that the Playtime track is armed in REAPER and that all other tracks are unarmed. You should be able to trigger Playtime clips with your MIDI keyboard! +. Start recording in REAPER +. Play stuff on your MIDI keyboard +. Stop recording in REAPER +. Play back the MIDI item which you just recorded on the Playtime track + +REAPER should repeat what you just played, cool! + +Now you can edit the MIDI item as desired, using REAPER's MIDI editor. For example, you could quantize all notes. + +.Rendering advice +IMPORTANT: Do you want to render your project? As Playtime will not participate in rendering, you first need to turn your performance into a regular REAPER arrangement. Please read xref:usage/arrangement.adoc[] for more information. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/generate-matrix.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/generate-matrix.adoc new file mode 100644 index 0000000..b48d91f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/generate-matrix.adoc @@ -0,0 +1,82 @@ += Generate a matrix using code + +[[feature-import-export-lua]] +Just as it is possible to xref:advanced-usage-scenarios/control-with-items.adoc#assign-midi-triggers-automatically[generate ReaLearn mappings via Luau], you can also generate a Playtime xref:key-concepts.adoc#matrix[] via Luau. + +.This guide is a work in progress +WARNING: The matrix generated by this example xref:key-concepts.adoc#matrix[] is viewable, but not yet playable! It will be improved in the future to be playable as well. + +Proceed like this: + +. Copy below Luau code to the clipboard +. Press the xref:helgobox::app/user-interface/navigation-bar.adoc#navbar-show-helgobox-plugin[] icon in the navigation bar on the left. +. Press xref:helgobox::plug-in/user-interface/menu-bar.adoc#import-from-clipboard[]. +. After confirming the import, you should see a large and wildly colorful matrix. + +.Colorful matrix +[source,lua] +---- +local column_count = 10 +local row_count = 200 + +-- Clip source + +-- Build columns +local columns = {} +local clip_index = 0 +for c = 0, column_count - 1 do + local slots = {} + for r = 0, row_count - 1 do + if (c % 2 == 0 and r % 2 == 0) or (c % 2 == 1 and r % 2 == 1) then + local slot = { + row = r, + clips = { + { + name = `Clip {c + 1}/{r + 1}`, + color = { + kind = "PaletteColor", + index = 5 + clip_index % 17, + }, + source = { + kind = "MidiChunk", + chunk = [[ +HASDATA 1 960 QN +E 3840 b0 7b 00 0 +IGNTEMPO 1 120 4 4 +]], +} + }, + }, + } + table.insert(slots, slot) + clip_index += 1 + end + end + local column = { + slots = slots or nil, + } + table.insert(columns, column) +end + +-- Build rows +local rows = {} +for r = 1, row_count do + local row = { + name = `Scene {r}`, + } + table.insert(rows, row) +end + +-- Build matrix +local matrix = { + columns = columns, + rows = rows, +} + +-- Return result +return { + kind = "ClipMatrix", + version = "2.16.15", + value = matrix, +} +---- \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/meta-clips.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/meta-clips.adoc new file mode 100644 index 0000000..1696776 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/meta-clips.adoc @@ -0,0 +1,49 @@ += Meta clips + +We can take the previous scenario xref:advanced-usage-scenarios/control-with-items.adoc[] a step further! + +[[feature-meta-clips]] +== The idea + +If we can control Playtime with MIDI items, shouldn't it be possible to control it with MIDI xref:key-concepts.adoc#clip[_clips_] as well? The answer is yes! I call such clips _meta clips_. *Playtime can basically play itself!* + +Meta clips open up a whole new world of possibilities. Here are just some of the things you can do with them: + +* Meta clips can be used as xref:key-concepts.adoc#scene[scenes] on steroids. You can trigger many other clips at once, no matter in which row they are. +* You can record sequences of clip invocations and play them back at the press of a button. + +== Basic setup + +. Prepare the matrix and set up MIDI triggers as explained in xref:advanced-usage-scenarios/control-with-items.adoc[]. This should make it possible to play clips using your MIDI keyboard. Make sure that part works. +. Select the track "Playtime" in your REAPER project (this is the track which contains the xref:helgobox::key-concepts.adoc#instance[Helgobox instance], by default the first track in the project) +. Right-click the xref:user-interface/matrix-area.adoc#column-cell-label[] of some column, for example the last column. Choose xref:user-interface/menus/matrix-menu.adoc#matrix-insert-column-for-each-selected-track[]. ++ +This should add a new column named "Playtime" that plays back its MIDI clips on the "Playtime" track, and can therefore be used to _play other clips_. +. +. Now simply record a clip into the new "Playtime" column, while playing clips in other columns using your MIDI keyboard. +. That's it! You recorded your first meta clip. When playing it back, it should trigger other clips in other columns. You can also edit that meta clip, e.g. quantize its notes. + +You may look at the "Playtime" column as the *leader* column (which you trigger) and the other columns as *follower* columns (which are triggered by the leader column). + +== Adjust the setup to your needs + +This was just an example setup. You can adjust it to your needs. Here are some ideas: + +* Introduce more leader columns +* Move the leader column to the very left to make it obvious that it is a very important column +* Set the xref:user-interface/inspector/column/playback.adoc#inspector-column-start-timing[] for the *Playtime* column to *1 bar* +* Set the xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode[] to xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode-toggle[] + + +IMPORTANT: Using MIDI clips to control Playtime is subject to the same considerations about _rendering_ as when using MIDI items. Read xref:usage/arrangement.adoc[] for more information. + +[TIP] +.Meta clips are just the beginning +==== +The simple fact that Playtime is a MIDI-controllable instrument opens up even more possibilities. + +Here are some ideas: + +* Use a MIDI arpeggiator to arpeggiate between different clips +* Write a MIDI link:https://www.reaper.fm/sdk/js/js.php[JSFX] effect that controls clips +==== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/multiple-instances.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/multiple-instances.adoc new file mode 100644 index 0000000..3eaef7a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/multiple-instances.adoc @@ -0,0 +1,24 @@ += Multiple instances + +[[feature-multiple-instances]] +Because Helgobox is implemented as an xref:helgobox::plug-in.adoc[instrument plug-in], you can add multiple xref:helgobox::key-concepts.adoc#instance[instances] of it. That also means you can have multiple xref:key-concepts.adoc#matrix[Playtime matrices] in the same project. + +== How to do it + +Assuming you have one Playtime instance already, here's how you add another one: + +. Right-click REAPER's track control panel and choose menu:Insert virtual instrument on new track...[] +. Double-click instrument plug-in "VSTi: Helgobox - ReaLearn & Playtime (Helgoboss)" +. In the xref:helgobox::plug-in/user-interface.adoc[Helgobox plug-in window], press menu:Menu[Show app] +. In the xref:helgobox::app/user-interface.adoc[Helgobox app window], press the image:screenshots/playtime-toolbar-icon.png[Playtime,width=30] button in the xref:helgobox::app/user-interface/navigation-bar.adoc[] on the left. +. Press the button btn:[Create a Playtime Matrix] + +That's it! You now have a second xref:key-concepts.adoc#matrix[] in the project, that runs independently of the first one. + +TIP: Currently, the only way to open that second matrix is to open the xref:helgobox::plug-in/user-interface.adoc[plug-in window] of that second xref:helgobox::key-concepts.adoc#instance[Helgobox instance] and press menu:Menu[Show app]. In the future, a separate REAPER action is planned, to make that step easier. + +== Why? + +Up to you! + +I'm keen to hear if and how you use this possibility. Let me know on my link:https://www.youtube.com/channel/UC5fE-sfXnXuAX3EC1VM85oA[YouTube channel], on link:https://github.com/helgoboss/helgobox/discussions[GitHub discussions], on link:https://forum.cockos.com/showthread.php?t=292969[the REAPER forum] or via mailto:info@helgoboss.org[] :) \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/record-performance-as-audio.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/record-performance-as-audio.adoc new file mode 100644 index 0000000..e5a6ef4 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/record-performance-as-audio.adoc @@ -0,0 +1,35 @@ += Record performance as multi-track audio + +Due to the way Playtime works, it is possible to record your Playtime performance directly to the xref:key-concepts.adoc#column-track[column tracks] as audio items. This is basically a sort of multi-track online rendering approach. It can be used as an audio-only post-FX alternative to recording xref:key-concepts.adoc#matrix-sequence[matrix sequences]. + +.Drawbacks +[CAUTION] +==== +Before you proceed, be aware that this technique has a few drawbacks: + +. Below-mentioned step 2 will change the xref:user-interface/track-area.adoc#track-panel-input-monitoring[] setting for all xref:key-concepts.adoc#column-track[column tracks], which might not be what you want! You will need to restore the old input monitoring settings afterward. +. Step 4 obviously changes the record-arm state within Playtime, which might also not be what you want! You will need to restore the old arm states afterward. +. Playing back the resulting multi-track audio will naturally flow through the track FX chain again. However, since the recording was done post-FX, the track FX is already part of the recorded audio. In order to avoid applying the FX twice, you would need to temporarily disable the FX of the affected xref:key-concepts.adoc#column-track[column tracks]. +==== + + +It goes like this: + +. Select all xref:key-concepts.adoc#column-track[column tracks] in the REAPER track control panel +. Right-click the record-arm button of one track in the selection and disable menu:Monitor input[]. This is a safety measure to prevent audio feedback. +. Right-click the record-arm button again and enable menu:Record: output[Record: output (stereo)] ++ +TIP: This step doesn't have any effect on recording within Playtime. Playtime will still record from the xref:user-interface/track-area.adoc#track-panel-input[], not from the track output. So this step shouldn't have any negative consequences. +. Click the record-arm button. This should record-arm all column tracks. +. Start recording in REAPER +. Now play and stop clips in the Playtime xref:key-concepts.adoc#matrix[] as desired +. Stop recording in REAPER when you are finished + +Each column track now contains a stereo recording of its audio output. + + +.Feedback wanted +[NOTE] +==== +Recording the performance as multi-track audio is a sort of "accidental feature" that is technically possible but not "officially" supported, because Playtime has not been optimized for it! It comes with the mentioned drawbacks, but it is there when you need it. If you like this feature and wish there would be a more streamlined version of it, post your idea at link:https://github.com/helgoboss/helgobox/discussions[GitHub discussions]. +==== diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/start-custom-playtime.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/start-custom-playtime.adoc new file mode 100644 index 0000000..9639724 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/advanced-usage-scenarios/start-custom-playtime.adoc @@ -0,0 +1,44 @@ += Start custom Playtime from template + +[[start-custom-playtime]] +Would you like to adjust the default settings of new xref:key-concepts.adoc#instance[Playtime instances]? Then you are at the right place. + +== Most settings in Playtime are instance-specific + +When designing software with the concept of xref:key-concepts.adoc#instance[instances] or projects, a common challenge is deciding whether a particula setting should be **global** (immediately affecting all instances), **global template-like** (affecting all new instances) or **instance-specific** (affect only the current instance). + +For Playtime, I chose to make most settings **instance-specific**. Only settings related to _appearance_ and _performance_ -- essentially everything in the xref:user-interface/dialogs/settings-dialog.adoc[] -- are global. The primary advantage of this approach is that it allows precise restoration of any instance, ensuring it behaves exactly as it was saved. + +However, this approach has a drawback: You need to adjust settings for every new instance manually, especially if the default settings don’t match your preferences. + +== Track templates to the rescue + +You can solve this issue by leveraging a feature that you probably already know: *REAPER track templates*! + +. Create a Playtime instance as usual as explained in xref:usage/prepare.adoc#fire-up-playtime[]. + +. Modify the instance-specific settings to suit your needs. For example: +* Change settings in the xref:user-interface/inspector/matrix.adoc[]. +* Change metronome settings in the xref:user-interface/dialogs/tempo-settings-dialog.adoc[]. +* Add frequently used columns with specific track inputs. + +. Right-click the "Playtime" track and all xref:key-concepts.adoc#column-track[column tracks] and choose menu:Save tracks as track template...[], saving the new track template as "Playtime.RTrackTemplate". +. Whenever you want to start with this custom Playtime instance in a new project, right-click the track control panel, choose +. In new projects, you can now right-click the track control panel and choose menu:Insert track from template[] to insert your custom "Playtime" track template. + +Done! You immediately get a fresh Playtime instance with your favorite settings, including all columns. + +== An action to make this approach more streamlined + +The downside of this approach is that you can't use the beloved image:screenshots/playtime-toolbar-icon.png[Playtime,width=50, pdfwidth=8mm] button to add Playtime, because this one still creates the Playtime default instance, not your custom one. + +This is where the action xref:reaper-actions.adoc#show-hide-custom-playtime[] comes into play. You can add a toolbar button for this action: + +. Right-click an empty area of the desired toolbar and choose menu:Customize toolbar...[] +. Press btn:[Add...] +. Search for action "Helgobox/Playtime: Show/hide custom Playtime" and double-click it +. Press btn:[OK] + +The default icon for this button is image:screenshots/playtime-custom-toolbar-icon.png[Playtime,width=50, pdfwidth=8mm]. When you press it, it looks for a track template called "Playtime" and loads it. + +No default shortcut is assigned to this action, but you can assign your own in REAPER menu menu:Actions[Show action list...]. For example, you could remove the kbd:[Shift+Cmd/Ctrl+P] shortcut from the xref:reaper-actions.adoc#show-hide-playtime[] action and instead assign it to this action. diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/configuration-files.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/configuration-files.adoc new file mode 100644 index 0000000..40f9f63 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/configuration-files.adoc @@ -0,0 +1,5 @@ += Configuration files + +In addition to the xref:helgobox::configuration-files.adoc[general configuration files], Playtime maintains the following ones: + +`Helgoboss/Playtime/settings.json`:: Contains global configuration that affects all Playtime instances. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts.adoc new file mode 100644 index 0000000..3a57602 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts.adoc @@ -0,0 +1 @@ += Further concepts \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/general.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/general.adoc new file mode 100644 index 0000000..34108fe --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/general.adoc @@ -0,0 +1,45 @@ += General + +[#pdc] +== Plugin delay compensation (PDC) + +FX (an effect or instrument) may add latency to a track due to internal processing. This can happen even with FX that's not on the track itself, but somewhere downstream in the signal flow, e.g. on the master track. + +REAPER and Playtime apply plug-in delay compensation (PDC) in order to fix the timing. That means they start processing content ahead of time in order to make up for the latency. PDC ensures that all tracks stay perfectly synchronized by delaying other tracks to match the plugin's latency. This prevents timing issues and maintains phase alignment in your project, ensuring accurate playback and mixing. + +[#latency-compensation] +== Latency compensation during recording + +When recording in DAWs, it is important to account for certain types of latencies. Playtime compensates or doesn't compensate latencies during recording, depending on a multitude of factors. Here's the overview: + +|=== +|Input monitoring|Start timing|Kind|Compensation|Comment + +|Off|Immediately|Any|Off| + +|Off|Quantized|Audio|Audio output latency + audio input latency| + +|Off|Quantized|MIDI (normal)|Audio output latency + block size (new)| + +|Off|Quantized|MIDI (overdub)|Audio output latency + block size + PDC read-ahead (new)|Results not too good when using high buffer sizes, but okay for now. + +|On|Any|Audio|Audio input latency| + +|On|Any|MIDI (any)|Off| +|=== + +=== When does this matter? + +In general, those things should only matter when using high buffer sizes, since only then the delays introduced by latency become musically sensible. + +=== Why compensate output latency only if input monitoring is off? + +When input monitoring is off:: +If you don't enable input monitoring in the DAW (or in Playtime, it's the same), Playtime assumes that you are using some kind of zero-latency monitoring. In case of audio, this might be direct monitoring via audio interface or just listening to the instrument/voice itself. In case of MIDI, this might be an external hardware synthesizer with Local Mode being On or no monitoring at all (just pressing the keys at the right moment). ++ +In that case, what counts in terms of timing is the actual key press (keyboard), strum (guitar), beat (drums), etc. This needs output latency compensation! + +When input monitoring is on:: +If you enable input monitoring in the DAW, Playtime assumes that the player adjusts his playing according to what he hears coming out from the DAW and expects that this is the timing that ends up in the recording. ++ +This means that output latency compensation is not necessary. Having input monitoring on in the DAW is usually only bearable when using low buffer sizes, otherwise it's super confusing. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/matrix.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/matrix.adoc new file mode 100644 index 0000000..998cac5 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/matrix.adoc @@ -0,0 +1,136 @@ += Matrix concepts + +[[cell]] +== Cell + +A _cell_ is a broad term that can refer to a xref:key-concepts.adoc#slot[], a xref:key-concepts.adoc#column[], a xref:key-concepts.adoc#row[], or the entire xref:key-concepts.adoc#matrix[]. + +While it may seem like a concept exclusive to Playtime's xref:user-interface.adoc[], it is not limited to that context. + +[[active-cell]] +== Active cell + +In a xref:key-concepts.adoc#matrix[], there's always exactly one <> that is considered the currently _active_ one. It is then possible to carry out certain actions on this cell, for example using the xref:realearn-targets.adoc[]. + +One way to _activate_ a cell is to select it in the xref:user-interface.adoc[]. Vice versa, the selection in the user interface usually follows the currently active cell. + +By default, a _slot cell_ is also _activated_ just by <> it. This can be changed by setting the matrix property xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-activate-slot-on-trigger[]. + +[[trigger-slot]] +== Trigger a slot + +_Triggering a slot_ is an action carried out on a xref:key-concepts.adoc#slot[] that can have one of the following results, depending on the current slot contents and play state: + +* Start slot playback +* Stop slot playback +* Start slot recording +* Stop slot recording + +A slot can be triggered either by pressing kbd:[Enter] in the xref:user-interface.adoc[] or from a controller (see ReaLearn xref:realearn::targets/playtime/slot-transport-action.adoc[]). + +Triggering also takes the xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode[] and xref:user-interface/inspector/clip.adoc#inspector-clip-velocity-sensitivity[] into account. + +[[control-unit]] +== Control unit + +A _control unit_ represents a xref:realearn::key-concepts.adoc#controller[] connected to a certain xref:key-concepts.adoc#matrix[Playtime matrix]. The concept is most important for xref:key-concepts.adoc#grid-controller[grid controllers] because they have special features within Playtime, most importantly <> through the matrix and displaying a corresponding <> in the xref:user-interface.adoc[]. + +One control unit always corresponds to a one xref:realearn::key-concepts.adoc#unit[ReaLearn unit] within the same xref:helgobox::key-concepts.adoc#instance[] as the xref:key-concepts.adoc#matrix[Playtime matrix]. + +A control unit has the following properties: + +Name:: +Usually corresponds to the name of the connected xref:realearn::key-concepts.adoc#managed-controller[], e.g. "Launchpad". The name is currently not visible in Playtime, but might be displayed as part of the <> in the future. + +Color:: +The color of the control unit is derived from the color of the xref:realearn::key-concepts.adoc#managed-controller[]. You can see the color "in action" by looking at the <>. + +Size:: +The number of columns and rows available on the controller. For example, in case of Launchpads, it's usually 8 columns and 8 rows. ++ +The size of the control unit is provided by xref:realearn::further-concepts/compartment.adoc#custom-data[] of the xref:realearn::key-concepts.adoc#main-compartment[] within the xref:realearn::key-concepts.adoc#unit[ReaLearn unit] representing this control unit: ++ +[cols="m,1,1"] +|=== +| Variable | Type | Description + +| playtime.control_unit.column_count +| Integer +| Number of available columns + +| playtime.control_unit.row_count +| Integer +| Number of available rows +|=== ++ +[source,lua] +.Example in Lua +---- +custom_data = { + playtime = { + control_unit = { + column_count = 8, + row_count = 8, + }, + }, +}, +---- + +[[control-unit-scroll-position]] Current scroll position:: +The address of the top-left visible slot. See <>. + +[[control-unit-scrolling]] +== Control unit scrolling + +When connecting a xref:key-concepts.adoc#grid-controller[] to Playtime, it reflects the contents of the xref:key-concepts.adoc#matrix[]. It often happens that the matrix is larger than the size of the controller grid. In that case, it's important to be able to scroll. + +[[control-unit-frame]] +== Control unit frame + +The _control unit frame_ is a rectangle in the xref:user-interface/matrix-area.adoc[] that shows which portion of the matrix is currently shown on a certain grid controller, according to the current <>. + +The display of control unit frames can be switched off using xref:user-interface/dialogs/settings-dialog.adoc#settings-show-control-unit-frames[]. + +[[count-in-recording]] +== Count-in recording + +A _count-in recording_ is a special recording method initiated while Playtime playback is stopped. The metronome starts clicking during the count-in phase, which gives you enough time to prepare for the recording. + +It goes like this: + +. Make sure that playback is stopped. If Playtime is playing, press the kbd:[Space] key. +. Make sure Playtime's metronome is turned *on*. If it's off, press the kbd:[M] key. +. Press the xref:user-interface/matrix-area.adoc#slot-cell-record[] button in an empty slot. That should start Playtime's playback, and you should hear and see a count-in of 2 bars, during which you can prepare. When the slot count-down reaches zero, Playtime records as usual. + +The count-in length can be adjusted in by right-clicking the xref:user-interface/toolbar.adoc#toolbar-metronome[] button and adjusting xref:user-interface/dialogs/tempo-settings-dialog.adoc#tempo-settings-count-in[]. + +Here's a video showing this way of recording: + +video::sMckj_gsqh0[youtube, list=PL0bFMT0iEtAgjbtAN-lp6d_-vLA_YUP8O] + +[[feature-tempo-detection]] +== Tempo detection recording + +A _tempo detection recording_ is a special recording method tailored to looper-style live improvisation without metronome. + +Let's say you want to use a foot switch to start recording an initial loop and press it again to set the tempo for all remaining loops. + +Here's how you do that: + +. Make sure that playback is stopped. If Playtime is playing, press the kbd:[Space] key. +. Make sure Playtime's metronome is turned *off*. If it's on, press the kbd:[M] key. should reveal a text in the title bar, saying: ++ +____ +If you record a clip now, its length will dictate the project tempo! +____ ++ +This tells us that _tempo detection_ will be used. +. Press the MIDI foot switch mapped to the xref:user-interface/toolbar.adoc#toolbar-smart-record[] button. That should start recording *immediately*, regardless of the currently set xref:key-concepts.adoc#clip-start-timing[]. +. Press the foot switch again. This should stop recording *immediately*. Playtime should set the project tempo based on the length of the recording and play back the recorded loop immediately. + + +Obviously, this needs a bit of practice to get right because you need to trigger start and end of the recording precisely. + +Here's a video showing this way of recording: + +video::kRvu6IG_0rk[youtube, list=PL0bFMT0iEtAgjbtAN-lp6d_-vLA_YUP8O] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/slot.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/slot.adoc new file mode 100644 index 0000000..99b16b6 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/further-concepts/slot.adoc @@ -0,0 +1,6 @@ += Slot concepts + +[[ignited-slot]] +== Ignited slot + +Stopping Playtime playback doesn't just stop all playing slots, it also memorizes them, so that next time you start playback, they will play again. We call those memorized slots _ignited_. Ignited clips have a circle around their xref:user-interface/matrix-area.adoc#slot-cell-trigger[play button]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/installation.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/installation.adoc new file mode 100644 index 0000000..d98a572 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/installation.adoc @@ -0,0 +1,37 @@ += Installation + +At first, please follow the xref:helgobox::installation.adoc[installation instructions for Helgobox], the add-on that contains Playtime. + +== Playtime main toolbar button + +After installing Helgobox, the easiest way to access Playtime is by using the convenient image:screenshots/playtime-toolbar-icon.png[Playtime,width=50, pdfwidth=8mm] button in REAPER's main toolbar. + +You can add this button very easily by doing this: + +. Select menu:Extensions[Helgobox > Show welcome screen] +. Check "Add Playtime button to toolbar" + +image::helgobox::screenshots/welcome-dialog.png[width=500] + +== Alternatives + +If you don't like that button, here are a few alternatives. + +TIP: Manually removing the image:screenshots/playtime-toolbar-icon.png[Playtime,width=50, pdfwidth=8mm] button from the main toolbar is not going to work: You will have it back the next time you start REAPER. +In order to remove the button permanently, open above-mentioned welcome screen and disable the checkbox! + +=== Start directly via action + +Playtime provides the action xref:reaper-actions.adoc#show-hide-playtime[], which you can invoke from the REAPER action list or via menu:Extensions[Helgobox > Playtime > Show/hide Playtime]. +In the action list, you can also assign your preferred keyboard shortcut. +The default is kbd:[Shift+Cmd/Ctrl+P]. + +=== Add the button to another toolbar + +You can add the image:screenshots/playtime-toolbar-icon.png[Playtime,width=50, pdfwidth=8mm] Playtime button to your own toolbar like this: + +. Right-click the toolbar and choose menu:Customize toolbar...[] +. Click btn:[Add...] (should open the REAPER action list) +. Search for the action xref:reaper-actions.adoc#show-hide-playtime[] and double-click it. + +That's it! you should now see the button in your custom toolbar. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/introduction.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/introduction.adoc new file mode 100644 index 0000000..9dcad27 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/introduction.adoc @@ -0,0 +1,29 @@ += Introduction + +include::helgobox::partial$intro-header.adoc[] + +|=== +|Last update of text: |`2025-03-15 (v2.18.0)` +|Last update of screenshots: |`2025-03-15 (v2.18.0)` +|=== + +== What is Playtime? + +link:https://www.helgoboss.org/projects/playtime[Playtime] is a powerful and versatile *session view* or *clip launcher* made specifically for the DAW link:https://www.reaper.fm[REAPER]. It is designed as a creative instrument for recording and triggering both audio and MIDI clips, making it ideal for *live performances*, *improvisation*, *jamming*, *looping* and *sketching arrangements*. + +As part of the link:https://www.helgoboss.org/projects/helgobox[Helgobox] plug-in, +Playtime integrates seamlessly with REAPER by leveraging its unique native-speed extension API. This deep integration provides a smoother and more efficient user experience compared to using REAPER alongside other DAWs. + +For a more detailed introduction to Playtime and a shiny presentation of its features, visit the link:https://www.helgoboss.org/projects/playtime[Playtime website]. + +== About this document + +This reference provides a detailed guide to Playtime's usage, concepts, and features. + +[TIP] +==== + +**You also might want to check out the link:https://github.com/helgoboss/helgobox/wiki/Playtime-Home[Playtime Wiki]!** + +It is the place for user-contributed content and contains links to other related content, such as tutorials and guides. +==== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/key-concepts.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/key-concepts.adoc new file mode 100644 index 0000000..071ae1d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/key-concepts.adoc @@ -0,0 +1,287 @@ += Key concepts + +This section offers brief descriptions of Playtime's key concepts. +An understanding of these concepts is beneficial for effectively using Playtime, regardless of which features you plan to utilize. + +== Linear vs. non-linear music production + +[[linear-production]] +=== Linear music production + +In _linear_ music production, tracks are arranged sequentially along a timeline, supporting structured composition and precise arrangement from start to finish. + +[[non-linear-production]] +=== Non-linear music production + +In _non-linear_ music production, such as in jamming and sketching, musical elements are arranged in flexible, often loop-based sections that can be triggered or layered in any order, allowing for spontaneous creativity and experimentation. + +Playtime's purpose is to add one popular flavor of _non-linear_ music production to REAPER: Music production via <>. + +[[session-view]] +== Session view / Clip launcher + +A _session view_ or _clip launcher_ is a user interface tailored to <> where musical elements are arranged in a grid of rows and columns. +Each cell typically holds a single musical element, and users can trigger, loop, and record these elements in real time, often for live performance or experimental composition. + +Playtime is a session view. + +[[matrix]] +== Matrix + +In Playtime, a _matrix_ is the top-level structure. +It is fundamentally a two-dimensional grid of <> that hold <>, arranged in <> and <>. +On a broader level, the matrix functions as a container, comparable to a project in REAPER, and includes various settings. + +Each <> can load a single Playtime matrix. +By default, it contains no matrix, so it must be created explicitly. +This approach ensures that Helgobox instances dedicated solely to xref:realearn::introduction.adoc[ReaLearn] don't consume any Playtime-related CPU or memory resources - you only "pay for what you use". + +[[instance]] +== Playtime Instance + +The terms _Playtime Instance_ and xref:helgobox:ROOT:key-concepts.adoc#instance[Helgobox instance] are interchangeable — they both refer to the same concept. + +[[column]] +== Column + +In Playtime, a _column_ refers to a group of <>, typically arranged vertically. + +A column is linked to a <>, which is used for playing back the <> it contains. + +TIP: There's one exception: If you load an external matrix preset into Helgobox and an associated column track doesn't exist in the REAPER project, the column will be track-less and unusable for playback. +In this case, you'll need to manually reassign a track in the xref:user-interface/inspector.adoc[]. + +By default, only one slot in a column plays at a time, but Playtime also allows for simultaneous playback of multiple slots in the same column. + +In practice, each column typically represents a single musical instrument or vocal track. + +[[column-track]] +== Column track + +A _column_ track is a normal REAPER track linked to a Playtime <>. + +[[row]] +== Row + +Like a <>, a _row_ refers to a group of <>, but arranged horizontally. + +A row is always associated with a <>, so the terms are often used interchangeably. + +In practice, each row often represents a part within a larger song structure. + +[[slot]] +== Slot + +In Playtime, a _slot_ is a fixed position in the matrix that can hold <> and can be played back or stopped. +Each slot functions as a mini audio player. + +Typically, a slot contains a single clip, but Playtime allows multiple clips in a slot to play back simultaneously. +In such cases, the <> of each individual clip is relevant. +The slot itself does not contain any settings. + +[[clip]] +== Clip + +In Playtime, a _clip_ is a container that holds a snippet of audio or MIDI, referred to as the <>, and can be configured in various ways. +It is somewhat comparable to an _item_ in REAPER. + +In practice, a clip is often a short, beat-aligned loop. + +[[source]] +== Source + +In Playtime, a _source_ represents the raw media data. + +Audio source:: +An _audio_ source contains link:https://en.wikipedia.org/wiki/Digital_audio[audio] data. +Technically, it is not more than a reference to an audio file, e.g. a WAV or MP3 file. +All audio formats supported by REAPER are also supported by Playtime. + +MIDI source:: +A _MIDI_ source contains link:https://en.wikipedia.org/wiki/MIDI[MIDI] data. +It is typically used to control instruments. +Technically, it is an in-memory representation of a sequence of MIDI events, similar to REAPER's _in-project MIDI sources_. + +[[scene]] +== Scene + +In Playtime, a _scene_ denotes a segment within a larger song structure. +It corresponds to a <>. + +Each scene features a play button that triggers playback for all <> in that row. + +By default, a scene encompasses all <> of the row. +However, Playtime allows a <> to opt out of scene playback if desired. + +[[matrix-sequence]] +== Matrix sequence + +A _matrix sequence_ is a recording of interactions with the <>. + +Playtime lets you record matrix interactions into a sequence. +Each sequence is saved as part of the matrix. +Later, you can play it back, directly in Playtime. + +CAUTION: Matrix sequence playback always uses the current matrix settings, slots and clips for playback. +Therefore,subsequent playbacks don't necessarily sound the same! + +Most importantly, a sequence can be "written down" to a REAPER <> at any time: All clips occurrences are placed on <> as REAPER items. +This is often done at the point in the song creation process, when you are satisfied with the sequence and want to transition to linear arranging on the <>. + +[[arrangement]] +== Arrangement + +In Playtime, an _arrangement_ typically refers to a set of REAPER items on the <> of the matrix, making it suitable for <>. + +An arrangement can be created ... + +[loweralpha] +. ... by writing a <> to the arrangement +. ... by exporting clips, scenes or the complete matrix to the arrangement +. ... by placing items there on your own + +Playtime is not necessary to play the arrangement back. +You can share a REAPER project containing an arrangement written via Playtime with someone who doesn't own Playtime, and they will still able to play it back! +In practice, however, it's recommended to keep Playtime as part of the project, to freely switch between <> and <>. + +[[clip-start-timing]] +== Clip start timing + +The clip _start timing_ is a configuration property for a <>. +Because of its importance, it's mentioned here already as a key concept. + +The _start timing_ decides when a clip actually starts to be played back, after you have triggered its playback. + +We distinguish between _quantized_ start timings and _immediate_ start timing. + +Quantized start timing:: +By default, the start timing is *1 bar*, which means that the clip will start playing at the beginning of the next bar. +This is what makes everything in-sync. You can change that to arbitrary <>, such as *2 quarter notes* or *3 bars*. + +Immediate start timing:: +Alternatively, you can use the start timing *Immediately*, which makes the clip start playing instantly when you trigger it. Of course, that means *you* are now in charge to get the timing right. + +You can set the start timing for xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-start-timing[the complete matrix] and override it on xref:user-interface/inspector/column/playback.adoc#inspector-column-start-timing[column] or xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing[clip] level. + +[[clip-stop-timing]] +== Clip stop timing + +The clip _stop timing_ is very similar to the <> but determines when the clip actually stops after you have triggered its stop. In most cases, it makes sense to leave it at its default settings xref:user-interface/inspector/clip.adoc#inspector-clip-stop-timing-like-start-timing[]. + +== Loop vs. one-shot + +[[loop]] +=== Loop + +In Playtime, a _loop_ is a <> that is _looped_, which simply means that it seamlessly repeats. + +[[one-shot]] +=== One-shot + +In Playtime a _one-shot_ is a <> that plays only once, that is, it's not looped. + +[[bar]] +== Bar + +A _bar_, also known as a _measure_, is a section of music that contains a specific number of <>, as defined by the <>. +It helps organize the music into manageable parts, making it easier for musicians to read and perform. +For example, in a 4/4 time signature, each bar has four beats. + +[[beat]] +== Beat + +A _beat_ is the basic unit of time in music, serving as the rhythmic foundation for a piece. +It’s what you tap your foot to when listening to a song. +Beats are grouped together in <> and can vary in length and speed, creating different feels and tempos in music. + +[[time-signature]] +== Time signature + +A _time signature_ in music is a way to show how <> are grouped in a piece. +It tells you how many <> are in each <> and which type of note gets one beat. +For example, in a 3/4 time signature, there are three beats per bar, and each beat is a quarter note. +It helps musicians understand the rhythm and feel of the music. + +[[time-signature-nominator]] +=== Nominator + +The top number of a time signature (the *3* in 3/4) is called _nominator_ and shows the number of beats per measure. + +[[time-signature-denominator]] +The bottom number (the *4* in 3/4) is called _denominator_ and shows the type of note that counts as one <>. + +[[in-sync]] +== In sync + +In the context of rhythmical music, _in sync_ means that everything is synchronized and the timing aligns correctly. When you play <> in Playtime, you typically want them to be _in sync_ with each other and with all other rhythmical material on the <>. + +To achieve this synchronization, the following conditions must be met: + +[[in-sync-correct-section]] Correct start and end position:: +The start and end positions of the <> must align *precisely* with a musical <> in the <>. + +[[in-sync-beat-aligned-playback]] Beat-aligned start of playback:: +<> playback must start exactly on a beat of the <>. + +[[in-sync-playback-with-same-tempo]] Playback with the same tempo:: +The <> must be played back in the same tempo (or a multiple of it). + +When using Playtime's default settings, all conditions are automatically met: + +* <> is met because by default, Playtime starts and stops a clip _recording_ quantized to a 1-bar grid (see xref:clip-start-timing[]). +* <> is met because by default, Playtime starts and stops clip _playback_ quantized to a 1-bar grid (see xref:clip-start-timing[]). +* <> is met because by default, Playtime adjusts the tempo of a clip to the current project tempo (see xref:user-interface/inspector/clip.adoc#inspector-clip-sync-to-project-tempo[]). + +[TIP] +==== +Concerning the first condition: + +* In most cases, you want the start point to align exactly with the downbeat, and the length to be precisely 1 bar (or a multiple of it). +* If the start point is located between beats or the length falls between bars, the loop will be _out of sync_. +* By selecting a <> start/stop timing, such as "1 bar", Playtime ensures that your loop's start point and length are perfectly beat-aligned with sample precision! +* If you choose immediate start/stop timing, it's your responsibility to ensure that the start point and length are musically appropriate. +Achieving perfect timing with a button or foot pedal press is close to impossible. +==== + +[[quantization]] +== Quantization + +A _quantization_ in Playtime defines when an event should occur, such as the start of clip playback or the stop of a recording. +It divides the <> into equal time intervals, ensuring that the event occurs only at the beginning of each interval. + +A quantization is typically expressed as a <>. + +.Quantization examples +==== + +3/4:: The length of each interval will be 3 quarter notes. +1/8:: The length of each interval will be 1 eight's note. +1/1:: The length of each interval will be 1 bar. +4/1:: The length of each interval will be 4 bars. +==== + +[[timeline]] +== Timeline + +In Playtime, a _timeline_ refers to the concept of time divided into bars and beats, featuring a continuously advancing playhead and a current tempo. + +One <> has exactly one _reference timeline_ at a given point in time. +<> events such as start of <> playback are aligned to this reference timeline. + +[[reaper-project-timeline]] +=== REAPER project timeline + +When the REAPER project is playing, Playtime uses the _REAPER project timeline_ as reference. +That is, it starts and stop playback according to the bar/beat division and tempo of the REAPER project. + +[[playtime-timeline]] +=== Playtime timeline + +When the REAPER project is stopped, Playtime uses its own timeline as reference, which however, shares a common tempo with the <>. + +[[grid-controller]] +== Grid controller + +A _grid controller_ is a unique class of hardware controller designed specifically for clip launching. A well-known example is the Novation Launchpad. A grid controller feature a grid of pads with LEDs arranged in rows and columns — exactly like Playtime's xref:key-concepts.adoc#matrix[]! + +Playtime includes out-of-the-box support for many popular grid controllers. xref:supported-controllers.adoc[Here] you can find the current list of supported devices. diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/realearn-targets.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/realearn-targets.adoc new file mode 100644 index 0000000..5915322 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/realearn-targets.adoc @@ -0,0 +1,10 @@ += ReaLearn targets + +[[feature-realearn]] +xref:realearn::introduction.adoc[ReaLearn] is the controller integration tool at the heart of Helgobox that enables Playtime's _haptic_ features. It drives the xref:user-interface/toolbar.adoc#toolbar-show-hide-midi-triggers[MIDI trigger] functionality, integrates the xref:key-concepts.adoc#grid-controller[grid controllers], and allows you to craft your own controller experiences. ReaLearn is the engine behind it all! + +In simple terms, a xref:realearn::key-concepts.adoc#target[ReaLearn target] defines what happens when you press a button on your controller. ReaLearn provides a wide variety of such xref:realearn::targets.adoc[targets], including a xref:realearn::targets/playtime.adoc[specialized set of targets] tailored for Playtime. Most of these targets even support xref:realearn::key-concepts.adoc#feedback[], enabling features like LED lighting or display updates. That gives you the flexibility to create highly customized controller integrations for Playtime! + +For a glimpse of what's possible, take a look at Playtime's xref:usage/grid-controllers.adoc[grid controller support]. This functionality isn't hard-coded into Playtime, it's entirely implemented through xref:realearn::further-concepts/compartment.adoc#compartment-preset[ReaLearn compartment presets]. + +All targets are described xref:realearn::targets/playtime.adoc[here]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/reaper-actions.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/reaper-actions.adoc new file mode 100644 index 0000000..05f3333 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/reaper-actions.adoc @@ -0,0 +1,12 @@ += REAPER actions + +Playtime provides the following REAPER actions in addition to the xref:helgobox::reaper-actions.adoc[general actions]. + +[[show-hide-playtime]] Helgobox/Playtime: Show/hide Playtime:: +If there's no Helgobox instance with a Playtime matrix in the current project, this action adds one and shows the matrix. If there is one already, it shows or hides the app window. This doesn't completely unload the app, it really just hides the window. + ++ +The default shortcut is kbd:[Shift+Cmd/Ctrl+P]. + +[[show-hide-custom-playtime]] Helgobox/Playtime: Show/hide custom Playtime:: +Works like <> with the following difference: If there's no Helgobox instance with a Playtime matrix in the current project, it looks for a top-level track template called "Playtime" and adds it to the project. See xref:advanced-usage-scenarios/start-custom-playtime.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/supported-controllers.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/supported-controllers.adoc new file mode 100644 index 0000000..667957b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/supported-controllers.adoc @@ -0,0 +1,72 @@ += Supported controllers + +:primary-functions-supported: Clip-related functions are supported. +:support-mostly-complete: Support is mostly complete. +:non-assigned-buttons: Notable functions which are currently not supported + +At the moment, Playtime has built-in support for the following controllers. More controllers will follow over time. If your controller is not supported yet, feel free to create a new discussion in link:https://github.com/helgoboss/helgobox/discussions/categories/ideas?discussions_q=category%3AIdeas+label%3Aplaytime+is%3Aopen+sort%3Adate_created[Playtime's idea tracker]. + +== Akai APC Key 25 + +{support-mostly-complete} + +.{non-assigned-buttons} +NOTE: Device + +== Akai APC Key 25 mk2 + +{support-mostly-complete} + +.{non-assigned-buttons} +NOTE: Device + +== Akai APC mini (experimental) + +{support-mostly-complete} + +.{non-assigned-buttons} +NOTE: Device + +== Akai APC mini mk2 + +{support-mostly-complete} + +.{non-assigned-buttons} +NOTE: Device + +== Novation Launchpad Mini mk3 + +{support-mostly-complete} + +.{non-assigned-buttons} +NOTE: Drums, Keys, User + +== Novation Launchpad Pro mk2 + +{primary-functions-supported} + +.{non-assigned-buttons} +NOTE: Note, Device, User, Volume, Pan, Sends + +== Novation Launchpad Pro mk3 + +{primary-functions-supported} + +.{non-assigned-buttons} +NOTE: Note, Chord, Custom, Sequencer, Projects, Volume, Pan, Sends, Device, +Swing, Tempo, Capture MIDI, Patterns, Steps, Pattern Settings, Velocity, Probability, +Mutation, Micro Step, Print to Clip + +== Novation Launchpad X + +{primary-functions-supported} + +.{non-assigned-buttons} +NOTE: Note, Custom, Volume, Pan, Send A, Send B, Capture MIDI + +== Novation Launchpad mk1 + +{primary-functions-supported} + +.{non-assigned-buttons} +NOTE: vol, pan, snd A, snd B \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage.adoc new file mode 100644 index 0000000..d4525b2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage.adoc @@ -0,0 +1,19 @@ += Basic usage + +Let's get familiar with the basics! + +A typical Playtime usage scenario, from beginning to end, looks like this: + +. xref:usage/prepare.adoc[] +. xref:usage/create.adoc[] +. xref:usage/play.adoc[] +. xref:usage/grid-controllers.adoc[] +. xref:usage/adjust.adoc[] +. xref:usage/structure.adoc[] +. xref:usage/arrangement.adoc[] +. xref:usage/render.adoc[] + +In the following sections, we will learn step by step how to do this. + +TIP: This guide assumes that you already installed Playtime. +If not, head to the xref:installation.adoc[] page! \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/adjust.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/adjust.adoc new file mode 100644 index 0000000..3fb4feb --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/adjust.adoc @@ -0,0 +1,33 @@ += Adjust clips + +Now that you know how to create and play clips, you might want change them a bit. + +== Editing the clip source + +If you are dealing with MIDI clips, you can easily edit the clip xref:key-concepts.adoc#source[]: + +. Double-click the xref:user-interface/matrix-area.adoc#slot-cell-content[] area of the desired slot. This should open the clip source in REAPER's MIDI editor. +. Make changes in the MIDI editor. Any change should be applied right away if the clip is currently playing. +. You can also use the playback controls of REAPER's MIDI editor or arrange view. As soon as you use them, the clip will stop playing in Playtime and you can play it within REAPER instead. As long as the MIDI editor is open, the clip is mirrored by a regular REAPER MIDI item positioned on the xref:key-concepts.adoc#column-track[]. +. Close the MIDI editor. + +Any change can be undone using Playtime's xref:helgobox::app/user-interface/title-bar.adoc#title-bar-undo[] function. + +[[feature-clip-settings]] +== Adjusting clip properties + +Each clip has certain properties in addition to its actual source, which you can adjust. + +=== 1. Select the slot + +Select the desired slot by clicking the xref:user-interface/matrix-area.adoc#slot-cell-content[] area. + +This will show the properties of the clip within that slot in the xref:user-interface/inspector/clip.adoc[] on the right side. + +=== 2. Adjust clip properties + +Now go ahead and check what properties xref:user-interface/inspector/clip.adoc[are available] in the inspector. + +We will just take the example of the *pitch* property. Find the xref:user-interface/inspector/clip.adoc#inspector-clip-pitch[] setting, move your mouse over the number *0.00* semitones and use your mouse wheel or any other xref:helgobox::app/user-interface/general.adoc#drag-field[] interaction to change the pitch. For example, to *12.00* semitones, which corresponds to one octave. + +The new pitch will be applied right away or according to the xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-start-timing[matrix start timing]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/arrangement.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/arrangement.adoc new file mode 100644 index 0000000..d2d8f82 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/arrangement.adoc @@ -0,0 +1,105 @@ += Create an arrangement + +Maybe you want to use Playtime for live improvisation only, in which case the basic usage guide would end here. But chances are, you want to go further than that and turn your Playtime matrix into a proper REAPER arrangement at some point. + +This is also important if you xref:advanced-usage-scenarios/control-with-items.adoc[control Playtime with items] or xref:advanced-usage-scenarios/meta-clips.adoc[use meta clips] and your goal is to include the output of Playtime in the final rendering. Unlike traditional instrument plug-ins, Playtime doesn't participate in rendering! footnote:direct-output[It's possible that this will be added in the future by introducing a new mode of operation.] Which is why you first need to turn the output into a conventional REAPER arrangement. + +There are two ways of building an arrangement. + +[[feature-export-to-arrangement]] +== Export matrix content + +You can export parts of the matrix to the arrangement directly: + +=== 1. Make a time selection (optional) + +You can influence the start and length of the export on the timeline by creating a time selection on the REAPER timeline. If you don't do that, the length will correspond to the longest exported clip. + +=== 2. Export the desired content + +* If you want to export all clips in the matrix, right-click the xref:user-interface/matrix-area.adoc#matrix-cell[] and choose xref:user-interface/menus/matrix-menu.adoc#matrix-export-to-arrangement[]. +* If you want to export all clips in a particular column, right-click the corresponding xref:user-interface/matrix-area.adoc#column-cell[] and choose xref:user-interface/menus/column-menu.adoc#column-export-to-arrangement[]. +* If you want to export all clips in a particular row, right-click the corresponding xref:user-interface/matrix-area.adoc#row-cell[] and choose xref:user-interface/menus/row-menu.adoc#row-export-to-arrangement[]. +* If you want to export the primary clip of a slot, right-click the corresponding xref:user-interface/matrix-area.adoc#slot-cell[] and choose xref:user-interface/menus/slot-menu.adoc#slot-export-primary-clip-to-arrangement[]. + +.Quickly export different parts of your song +[TIP] +==== +Maybe you have named the matrix rows so that they represent different parts of your song, such as _Verse_, _Chorus_, _Bridge_ and _Solo_. Now you want to export those parts like this: + +* 2x Verse +* 1x Chorus +* 1x Bridge +* 1x Verse +* 1x Solo +* 2x Chorus + +Making such an export can be done very quickly just by using keyboard keys: + +. Select the _Verse_ row cell +. Press key kbd:[A] 2 times (the shortcut for exporting the selected cell to the arrangement) +. Select the _Chorus_ row cell +. Press key kbd:[A] 1 time +. ... + +This works nicely because after exporting a row, Playtime automatically moves the play cursor or the complete time selection to the right. +==== + +[[feature-arrangement]] +== Record and write a sequence + +Another way to export an arrangement is to record a Playtime performance and write the result to the arrangement. This is done using Playtime's built-in xref:user-interface/toolbar.adoc#toolbar-matrix-sequencer[]: + +=== 1. Start recording a new matrix sequence + +Press the xref:user-interface/toolbar.adoc#toolbar-record-matrix-sequence[] button in the xref:user-interface/toolbar.adoc#toolbar-matrix-sequencer[] of the xref:user-interface/toolbar.adoc[]. + +This will always create a new sequence and never overwrite an existing one, so you can press this anytime. + +=== 2. Perform your song within Playtime + +Playtime will record all play and stop interactions with your matrix. + +=== 3. Stop recording the sequence + +Press the xref:user-interface/toolbar.adoc#toolbar-record-matrix-sequence[] button again to stop recording. + +=== 4. Play sequence (optional) + +If you want, you can play the sequence directly within Playtime by pressing the xref:user-interface/toolbar.adoc#toolbar-play-stop-matrix-sequence[]. + +=== 5. Write the sequence to the arrangement + +If you are satisfied, you can write the sequence to the REAPER tracks by pressing xref:user-interface/toolbar.adoc#toolbar-write-to-arrangement[]. + +== The translation from clips to items + +What happens in both cases is that Playtime translates its xref:key-concepts.adoc#clip[clips] into REAPER items. + +Ideally, the items - when played back by REAPER - should sound exactly as the clips in Playtime. If you detect deviations, please open a bug report in the link:https://github.com/helgoboss/helgobox/issues[Helgobox issue tracker]. + +[TIP] +==== +The resulting items are played back by REAPER alone. Those are conventional REAPER items, there is no connection to Playtime anymore! + +That also means that if you share the REAPER project file with other people, they won't need a Playtime installation to play back the arrangement. +==== + + +.Deep-dive: Translation from clips to items +**** +When translating clips to items, Playtime may set track, item or take properties. + +Here are some noteworthy translations: + +Free item positioning:: +Playtime enables free item positioning for a track as soon as it detects that some items written to the same track would overlap in time. This usually happens when two clips play at the same time on the same track. Not enabling it would lead to a hard-to-see overlap. + +Time base:: +If the clip is configured to xref:user-interface/inspector/clip.adoc#inspector-clip-sync-to-project-tempo[synchronize to the project tempo], it chooses item time base *Beats (auto-stretch at tempo changes)*, because this reflects how Playtime itself would behave when changing the tempo. With this mode, REAPER may automatically create stretch markers. ++ +If the clip is configured to *not* synchronize to the project tempo, it chooses item timebase *Time* (if start timing is xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing-immediately[]) or *Beats (position only)* (the the start timing is quantized), because those settings resemble Playtime's clip playing behavior best. + +Play rate:: +For audio clips, the play rate is always set to the ratio between the clip's xref:user-interface/inspector/clip.adoc#inspector-clip-tempo[] and the project tempo. +**** \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/create.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/create.adoc new file mode 100644 index 0000000..ebbdfc3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/create.adoc @@ -0,0 +1,163 @@ += Create clips + +Playtime is all about xref:key-concepts.adoc#clip[clips], small snippets of audio or MIDI that you can freely play together with other clips to compose a larger musical piece. + +Before we can play a clip, we need to create one. Let's do this! + +== 1. Create a column + +At first, we need to make some space for the clip within the xref:key-concepts.adoc#matrix[]. +Press the xref:user-interface/matrix-area.adoc#matrix-add-column[] button on the right of xref:user-interface/matrix-area.adoc#matrix-cell-settings[] to add a new xref:key-concepts.adoc#column[]. + +You should then see a new column named "Column 1" with empty slots that can take our new clip. + +[TIP] +.Column track +==== +This didn't just create a new column, it also created a new REAPER track with the same name, the so-called xref:key-concepts.adoc#column-track[]. Playtime will play clips within this column through that track. +==== + +IMPORTANT: Exactly as in REAPER, Playtime columns don't have a type. They can always play both, audio and MIDI clips. The xref:user-interface/track-area.adoc#track-panel-input-type-indicator[] looks a bit as if the column would have a special type, but it really just reflects the input currently selected for recording clips. + +.Alternatives +**** +Use matrix templates:: +Instead of creating the column manually, you could also have started with a template by pressing xref:user-interface/matrix-area.adoc#matrix-mixed-template-button[] in the xref:user-interface/matrix-area.adoc#matrix-template-panel[], for example. + +Import existing tracks:: +Maybe you have existing tracks already that you want to use as xref:key-concepts.adoc#column-track[column tracks]. In that case, select them, right-click xref:user-interface/matrix-area.adoc#matrix-cell-settings[] and choose xref:user-interface/menus/matrix-menu.adoc#matrix-insert-column-for-each-selected-track[]. +**** + +== 2. Create a clip + +There are many ways to create a new clip. Let's explore the most common ones. + +[[feature-import]] +=== Import an existing audio or MIDI file + +Maybe you have an existing audio or MIDI file somewhere on your computer. Simply drag it from your favorite file manager or from REAPER's media explorer onto one of the empty slots! See xref:user-interface/matrix-area.adoc#slot-drag-and-drop[] for details. + +.Alternatives +**** +Use file picker:: +You can pick the file using Playtime's own file picker by right-clicking an empty slot and pressing xref:user-interface/menus/slot-menu.adoc#slot-import-files[]. +**** + +[[import-item]] +=== Import an existing REAPER item + +Maybe you have an existing item on a REAPER track that you want to turn into a Playtime clip. + +. Select the item +. Right-click an empty slot and choose xref:user-interface/menus/slot-menu.adoc#slot-import-selected-item[] + +This does more than just importing the audio file or MIDI data from the item: It also translates many of the item's properties, such as the item cut and pitch, to Playtime clip properties. + + +[[create-empty-midi-clip]] +=== Create an empty MIDI clip + +If you want to start entering MIDI notes from scratch, simply double-click the slot. This will create an empty MIDI clip with the length of 1 bar. + +[[feature-record]] +=== Record a clip + +Playtime offers plenty of ways to record clips directly. The most common one is to record a xref:key-concepts.adoc#loop[] of rhythmic material while Playtime is playing. + +In this example, we are going to record an audio loop to a click: + +==== 1. Start playback + +Start playback in Playtime by pressing the xref:user-interface/toolbar.adoc#toolbar-start-stop-playback[] button in the transport section of the toolbar or simply by pressing the kbd:[Space] key. The xref:user-interface/toolbar.adoc#toolbar-visual-metronome[] in the middle of the Playtime text logo should start running. + +==== 2. Turn on metronome + +Turn on the metronome by pressing the xref:user-interface/toolbar.adoc#toolbar-metronome[] button in the tempo section of the toolbar. You should now hear the metronome clicking. + +[[choose-track-input]] +==== 3. Choose track input + +Press xref:user-interface/track-area.adoc#track-panel-input[] in the track panel below the column in order to choose the audio input channel(s) from which you want to record. + +TIP: If you don't see the input section, increase the height of the track panel a bit by grabbing the resize handle above the track panels and dragging it up. + +==== 4. Arm track + +Press the round xref:user-interface/track-area.adoc#track-panel-arm[] button. It should turn red and record buttons should appear in all empty slots of that column. + +CAUTION: If you record from a microphone and don't wear a headphone, you probably should turn off input monitoring by pressing xref:user-interface/track-area.adoc#track-panel-input-monitoring-off[]. Otherwise, you might hear loud feedback. + +==== 5. Get ready + +At this point, you can try making some noise. Do you see something in the xref:user-interface/track-area.adoc#track-panel-volume[meter]? Good! Now, get ready for recording! + +==== 6. Start recording + +Shortly after the start of a bar, press the xref:user-interface/matrix-area.adoc#slot-cell-record[] button in an empty slot. You should see a count-down which will reach zero exactly when the next bar starts. The record button will turn red and Playtime will record whatever you play. + +[[stop-recording]] +==== 7. Stop recording + +Press the red xref:user-interface/matrix-area.adoc#slot-cell-record[] button again to indicate that you want to stop recording. The button will start to blink and turn into a xref:user-interface/matrix-area.adoc#slot-cell-trigger[play button] as soon as the next bar starts. Playtime will play back your recording repeatedly. + +==== 8. Be happy + +Congratulations, you have recorded your first xref:key-concepts.adoc#loop[]. If you didn't get the timing right, don't worry. After some attempts, you will get the hang of it. Plus, Playtime offers quite a few ways to make recording clips less stressful. + +.Variations +**** +There are many variations of above recording procedure. Let's look at a few aspects that you can do differently: + +Record MIDI instead of audio:: +If you want to record MIDI, choose a MIDI device in step <>. You probably also want to put an instrument on the FX chain of the xref:key-concepts.adoc#column-track[] by pressing the xref:user-interface/track-area.adoc#track-panel-fx-chain[] button, or route the track to an external MIDI synthesizer by pressing the xref:user-interface/track-area.adoc#track-panel-routing[] button. + +Record a one-shot instead of a loop:: +If you want to record material that should play just once without being repeated, open the xref:user-interface/inspector/matrix/recording.adoc[] and uncheck the xref:user-interface/inspector/matrix/recording.adoc#inspector-matrix-recording-looped[] checkbox. If you do that, the recorded material will *not* immediately be played back after recording. + +Stop recording automatically:: +Find it too stressful having to press the stop button *while* you are playing? Often we know in advance how many beats or bars we want to record. You can tell Playtime by opening the xref:user-interface/inspector/matrix/recording.adoc[], enabling xref:user-interface/inspector/matrix/recording.adoc#inspector-matrix-limit-recording-length[] and choosing the desired xref:user-interface/inspector/matrix/recording.adoc#inspector-matrix-recording-length[]. After that, Playtime will stop clip recordings automatically. + +Wait longer until the recording starts:: +Find it too stressful having to grab your instrument within the short time period after pressing the record button? One way to get more time is to adjust the xref:key-concepts.adoc#clip-start-timing[]. For example, you could set xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-start-timing[] to *4 bars* instead of just *1 bar*. Or you do it just for one specific column. ++ +However, this approach has downsides. It will also have an effect on playing clips. Plus, you might have to wait quite long to get your turn. The next variation might suit you better. + +Record in stopped state with count-in:: +There's another way to give you more time to prepare. So far we have always recorded while Playtime was already playing, but we can also start recording while it's still stopped. That gives us the possibility of an arbitrarily long count-in phase. See xref:further-concepts/matrix.adoc#count-in-recording[]. + +[[record-via-foot-switch]] Record via foot switch:: +One more way to give you more time is to start recording with your foot instead of your hand. For this, Playtime offers the possibility to start recording clips via MIDI. In addition, it offers the convenient xref:user-interface/toolbar.adoc#toolbar-smart-record[] toolbar button, which makes it possible to use just one button - or foot switch - to record into the next non-occupied slot. ++ +. Press the xref:user-interface/toolbar.adoc#toolbar-show-hide-midi-triggers[] button in the toolbar. This should reveal a small MIDI trigger area on the right sight of the xref:user-interface/toolbar.adoc#toolbar-smart-record[] button. +. Press that MIDI trigger area. It should turn red. +. Press the MIDI foot switch connected to your MIDI keyboard. Playtime should pick it up. If not, see xref:user-interface/toolbar.adoc#midi-trigger-troubleshooting[]. +. Press the MIDI foot switch to start recording. +. Press it again to stop recording. ++ +TIP: You can hide the MIDI trigger areas at any time by pressing the xref:user-interface/toolbar.adoc#toolbar-show-hide-midi-triggers[] button again. They will remain active! + +Record with tempo detection and without click:: +All the approaches that we have talked about so far require a metronome or another clip to provide a rhythmical reference. However, imagine you want to do a looper-style live improvisation, without metronome. In that case, you can do a xref:further-concepts/matrix.adoc#feature-tempo-detection[]. + +Record non-rhythmic material:: +Playtime suites itself very much for rhythmic material. But what if you want to record something that's tempo-independent? In that case, you probably want two things: + +* Recording should start immediately, no need to wait until the next bar. +* Playtime shouldn't time-stretch your clip later on when changing the project playback tempo. + ++ +-- +You can simply achieve that by setting the xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-start-timing[] in the xref:user-interface/inspector/matrix/playback.adoc[] to xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing-immediately[]. If you want to use this behavior just for a specific column, you can xref:user-interface/inspector/column/playback.adoc#inspector-column-start-timing[override this setting on column level] instead! +-- + + +MIDI overdub:: +Maybe you already have a MIDI clip and want to slowly add some notes by playing them on your keyboard: ++ +. Select the desired (looped) MIDI clip by clicking the xref:user-interface/matrix-area.adoc#slot-cell-content[] area. +. In the xref:user-interface/inspector/clip.adoc[] on the right side, press the xref:user-interface/inspector/clip.adoc#inspector-clip-overdub[] button. The clip should start playing repeatedly. +. Play some MIDI notes on your music keyboard. They will be recorded into the playing clip. You will hear them on the next repetition. +. When you are done, press the same button again. + +TIP: Many of the above aspects can be combined, which gives you a maximum of flexibility. +**** \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/grid-controllers.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/grid-controllers.adoc new file mode 100644 index 0000000..6f7c5f3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/grid-controllers.adoc @@ -0,0 +1,28 @@ += Use grid controllers + +[[feature-grid]] +.Grid controllers are optional! +TIP: You don't need a xref:key-concepts.adoc#grid-controller[] to use Playtime! However, using one adds a tactile dimension to the clip-launching experience, making Playtime feel more like a distinct musical instrument. It’s a fun way to enhance interaction and creativity. + +If your xref:key-concepts.adoc#grid-controller[] is on the xref:supported-controllers.adoc[list of supported devices], here's how you set it up: + +. Ensure that Playtime is running and you see its xref:user-interface.adoc[]. +. If the device is already connected to the USB port, **disconnect it** and wait a few seconds! +. **Connect** the device to the USB port. +. After waiting for a few seconds, Playtime should show a green success notice at the bottom of the screen. + +After that, the device should visually reflect the contents of the Playtime xref:key-concepts.adoc#matrix[] and you should be able to control the matrix with it. + +In addition, the device should now appear in the xref:realearn:ROOT:user-interface/settings-dialog.adoc#controllers[ReaLearn controllers] section of the xref:helgobox::app/user-interface/settings-dialog.adoc[]. This is also the place where you can manually set up the device in case it was not auto-detected. + +== Troubleshooting + +If the device is not detected, please proceed as follows: + +* Execute REAPER action xref:realearn::reaper-actions.adoc#auto-detect-controllers-with-logging[] and copy the output to the clipboard. +* If the controller is not in the xref:supported-controllers.adoc[list of supported devices], create a https://github.com/helgoboss/helgobox/discussions/categories/ideas[feature request], naming your controller and including the copied info. +* If the controller is in the list, write an email to info@helgoboss.org, naming your controller and including the copied info. + +== Related videos + +video::tkyNCGOVumw[youtube, list=PL0bFMT0iEtAgjbtAN-lp6d_-vLA_YUP8O] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/play.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/play.adoc new file mode 100644 index 0000000..55994b9 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/play.adoc @@ -0,0 +1,136 @@ += Play clips + +As soon as you have some clips in the matrix, you can play them! + +Unlike items on the REAPER timeline, you can start and stop clips totally freely. There are no rules, which makes Playtime very suitable for improvisation. + +[[feature-play]] +== Playing clips using the app + +The obvious way to play a clip is to use your mouse: Press the xref:user-interface/matrix-area.adoc#slot-cell-trigger[] button of its containing xref:key-concepts.adoc#slot[]: + +* If Playtime's transport was stopped before you pressed the button (see xref:user-interface/toolbar.adoc#toolbar-start-stop-playback[] button in the toolbar), Playtime and your clip should start playing immediately. +* If Playtime's transport was playing already, the clip should start playing according to the configured xref:key-concepts.adoc#clip-start-timing[], by default at the next bar. + +You can stop the clip by pressing the xref:user-interface/matrix-area.adoc#slot-cell-trigger[same button] again, which now appears as a stop icon. The clip should stop playing according to the configured xref:key-concepts.adoc#clip-stop-timing[]. + +.Playtime pipes audio and MIDI directly to the column tracks! +NOTE: Playtime "magically" plays the clip's MIDI or audio content directly through the xref:key-concepts.adoc#column-track[column track] of the containing column. It doesn't use any sends for this! In fact, the xref:helgobox::plug-in.adoc[] itself doesn't produce any audio or MIDI data at all. footnote:direct-output[It's possible that this will be added in the future by introducing a new mode of operation.] + +TIP: You can xref:helgobox::app/user-interface/keyboard-shortcuts.adoc#esc[hide] or xref:helgobox::app/user-interface/title-bar.adoc#title-bar-close-window[close] the app window at any time. This will not affect playback or recording. Playtime also works without its user interface! + +[[feature-keys]] +== Playing clips using a MIDI keyboard + +Playtime allows you to play your clips by pressing keys on your MIDI keyboard: + +. Press the xref:user-interface/toolbar.adoc#toolbar-show-hide-midi-triggers[] button. This should reveal small MIDI trigger areas on the right side of each slot cell. +. Press the MIDI trigger area of the slot that you want to play. It should turn red. +. Press a key on your MIDI keyboard. Playtime should pick it up. If not, see xref:user-interface/toolbar.adoc#midi-trigger-troubleshooting[]. +. Press the key once. The clip should start playing. +. Press the key again. The clip should stop. + + +.Variations +**** +Play as long as you press the key:: +By default, pressing the key toggles between _playing_ and _stopped_. You can change that, for example on column level, by setting the column property xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode[] to xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode-momentary[] in the xref:user-interface/inspector/column.adoc[]. Then pressing the key down will start playback and releasing it will stop playback. This goes very well with xref:key-concepts.adoc#clip-start-timing[] xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing-immediately[]. + +Restart when pressing the key again:: +Another xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode[] is xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode-retrigger[]: Tapping the key once will start playback and tapping it again will trigger a restart. + +Play with different velocities:: +Maybe you want to play the clip with a lower volume when hitting the key softly and with a higher volume when hitting it hard. You can achieve this and fine-tune the effect by adjusting the matrix property xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-velocity-sensitivity[] in the xref:user-interface/inspector/matrix.adoc[]. It is of course also adjustable on xref:user-interface/inspector/column/playback.adoc#inspector-column-velocity-sensitivity[column] and xref:user-interface/inspector/clip.adoc#inspector-clip-velocity-sensitivity[clip] level. +**** + + +.Suitable for finger drumming! +[NOTE] +==== +Triggering clips via MIDI happens in real-time! As with any other instrument plug-in that you use in REAPER, the latency depends on the block size set in REAPER. + +So if you use xref:key-concepts.adoc#clip-start-timing[] xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing-immediately[] and set the block size in REAPER menu:Options[Preferences/Settings... > Device] low enough (e.g. 256 samples), you will not perceive any delay between pressing the key and hearing the sound. +==== + +.Is Playtime a sampler? +[TIP] +==== +When playing Playtime via MIDI, it works a bit like a sampler. But instead of playing single tones, it is optimized to trigger complete musical _phrases_. And not just audio phrases, also MIDI sequences. + +You are basically working on a higher level. Each of the triggered phrases in turn can trigger samplers or synthesizers ... or even xref:advanced-usage-scenarios/meta-clips.adoc[Playtime itself]. +==== + +== Playing multiple clips + +The fun starts when playing multiple clips simultaneously. + +=== Columns in exclusive mode + +By default, playback within one column is limited to just one slot. We call this xref:user-interface/inspector/column/playback.adoc#inspector-column-exclusive-mode[], because one slot has the exclusive right of playing. + +.Exclusive mode +==== +Let's say you have clip A in the first column cell and clip B in the second column cell. + +. Trigger clip A and wait until it started playing +. Now, trigger clip B + +You will see that clip A stops and clip B takes over. +==== + +*The consequence is:* If you want to play multiple slots simultaneously, you would need to create a new column. Musically, that makes a lot of sense. It is very common in clip launchers to have one column per instrument. And one instrument can normally only play one phrase at a time. Alternating between those phrases is often exactly what people want. + +[[feature-non-exclusive-columns]] +=== Columns in non-exclusive mode + +Playtime is a quite relaxed fellow and allows you to opt out of the exclusive mode. The easiest way to do this is to press the xref:user-interface/matrix-area.adoc#column-cell-exclusive-mode[] button in the xref:user-interface/matrix-area.adoc#column-cell[]. + +After disabling exclusive mode, you can play multiple clips within that column, as your heart desires. + +=== Playing in sync + +Usually when playing multiple clips within a clip launcher, one expects those clips to play perfectly xref:key-concepts.adoc#in-sync[in sync] with each other. This "perfectly in sync" is by default given in Playtime, but it all depends on how each of the clips is configured. + +If you have messed with xref:key-concepts.adoc#clip-start-timing[], xref:user-interface/inspector/clip.adoc#inspector-clip-start-position[], xref:user-interface/inspector/clip.adoc#inspector-clip-length[] or xref:user-interface/inspector/clip.adoc#inspector-clip-sync-to-project-tempo[], your clips might get out of sync. If this happens, and you don't know why, revisit xref:key-concepts.adoc#in-sync[] to deepen your understanding. + +=== Row and column transport buttons + +You can play all clips in a row at once by pressing the xref:user-interface/matrix-area.adoc#row-cell-play-scene[] button at the very left. + +You can permanently stop all clips in a column at once by pressing the xref:user-interface/matrix-area.adoc#column-cell-stop[] button in the xref:user-interface/matrix-area.adoc#column-cell[] at the top of the column. Double-clicking this button stops the clips abruptly. + +You can permanently stop all clips in the complete matrix at once by pressing the xref:user-interface/matrix-area.adoc#matrix-cell-stop[] button in the xref:user-interface/matrix-area.adoc#matrix-cell[] at the top-left of the matrix area. Double-clicking this button stops the clips abruptly. + +== Starting and stopping Playtime playback + +Playtime's playback can be started and stopped xref:helgobox::key-concepts.adoc#instance[instance-wide], using either the xref:user-interface/toolbar.adoc#toolbar-start-stop-playback[] button in the xref:user-interface/toolbar.adoc#toolbar-transport-section[] or the kbd:[Space] key. + +Stopping Playtime playback doesn't just stop all playing slots, it also memorizes which ones it stopped, so that next time you start playback, they will play again. We call those memorized slots xref:further-concepts/slot.adoc#ignited-slot[ignited slots]. Ignited slots have a circle around their xref:user-interface/matrix-area.adoc#slot-cell-trigger[play button]. + +Starting Playtime playback will automatically start all ignited slots. If you don't want that, press the xref:user-interface/matrix-area.adoc#matrix-cell-stop[] or xref:user-interface/matrix-area.adoc#column-cell-stop[] button to un-ignite the slots. + +.Alternatives +**** +Conducting a count-in by tapping tempo:: +A neat way to start playback with a new tempo is to stop playback and repeatedly press the xref:user-interface/toolbar.adoc#toolbar-tap-tempo[] button in a "1, 2, 3, 4"-style count-in. +**** + +== Playing together with the REAPER arrangement + +=== Starting playback within REAPER + +Maybe you already have some items on the REAPER timeline that you want to improvise over using Playtime. All you need to do for this is to start playback within REAPER: + +. Click somewhere into the REAPER main window in order to switch focus to REAPER. +. Press kbd:[Space] to start REAPER playback. + +Playtime will now play synchronized to the arrangement. Changing the REAPER play cursor position will automatically retrigger Playtime's clip so that they stay in sync. + +=== Full transport synchronization + +Maybe you would like to always play together with the REAPER arrangement, even if you start playback within Playtime: + +. Set xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-transport-sync[] in the xref:user-interface/inspector/matrix.adoc[] to *Full* +. Press kbd:[Space] within Playtime. + +REAPER should start playing as well. diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/prepare.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/prepare.adoc new file mode 100644 index 0000000..5916f30 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/prepare.adoc @@ -0,0 +1,60 @@ += Prepare + +At first, we need to do some preparations. + +== 1. Start REAPER + +Playtime runs within REAPER, so at first you need to start REAPER. + +== 2. Set up your audio and MIDI devices + +If you haven't done it already, you should let REAPER know which audio and MIDI devices you are planning to use with it, because Playtime will use them as well! + +. Open the REAPER preferences via menu:Options[Preferences/Settings...] +. In the section menu:Audio[Device], configure your audio input and output. +. In the section menu:Audio[MIDI Inputs], configure your MIDI input devices, such as music keyboards. +. In the section menu:Audio[MIDI Outputs], configure your MIDI output devices, such as hardware synthesizers. + +If you want to know more about this process, please consult the one and only link:https://www.reaper.fm/userguide.php[REAPER user guide]. + +[[fire-up-playtime]] +== 3. Fire up Playtime + +Press the image:screenshots/playtime-toolbar-icon.png[Playtime,width=50, pdfwidth=8mm] button in REAPER's main toolbar, or use any of the alternative ways to start Playtime (described in xref:installation.adoc[]). +After pressing the button, you should see the Playtime window with an empty xref:key-concepts.adoc#matrix[]: + +.Playtime window with empty matrix +image::generated/screenshots/main/main-screen-empty.png[] + +You can press kbd:[Shift+Cmd/Ctrl+P] to show/hide the Playtime window at any time. + +At this point, you could directly continue with xref:usage/create.adoc[], but I strongly recommend to read the following deep dive, as it will improve your understanding of how Playtime works! + +.Deep dive: Playtime button +**** +Pressing the button actually does multiple things in one go: + +. Create a new REAPER track named "Playtime" at the very top of the project. +. Arm the track and set its input to "MIDI: All: All channels". +. Add an xref:helgobox::key-concepts.adoc#instance[instance] of the Helgobox plug-in to the track's FX chain. +. Open the xref:helgobox::app.adoc[] associated with that new Helgobox instance. +. Navigate to the Playtime section of the App. +. Create a xref:key-concepts.adoc#matrix[Playtime Matrix] in this Helgobox instance. + + +If you wanted, you could do all of those steps manually instead: + +* Right-click REAPER's track control panel and choose menu:Insert virtual instrument on new track...[] +* Double-click instrument plug-in "VSTi: Helgobox - ReaLearn & Playtime (Helgoboss)" +* Optionally rename the track to "Playtime" +* In the xref:helgobox::plug-in/user-interface.adoc[Helgobox plug-in window], press menu:Menu[Show app]. +* In the xref:helgobox::app/user-interface.adoc[Helgobox app window], press the image:screenshots/playtime-toolbar-icon.png[Playtime,width=30] button in the xref:helgobox::app/user-interface/navigation-bar.adoc[] on the left. +* Press the button btn:[Create a Playtime Matrix] + +What we learn from that: + +* Playtime is built into Helgobox, which is an instrument plug-in, so there needs to be an instance of it somewhere in your project. *→ This is a good thing because it means that you can xref:usage/play.adoc[play] it like an instrument!* +* A new Helgobox instance doesn't have a xref:key-concepts.adoc#matrix[Playtime Matrix] by default. +You first need to create one. +*→ This means Playtime doesn't consume any CPU and memory if you don't use it!* +**** \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/render.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/render.adoc new file mode 100644 index 0000000..5261946 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/render.adoc @@ -0,0 +1,12 @@ += Render + +At the end, you probably want to render/mix-down/export the arrangement xref:usage/arrangement.adoc[created in the previous step] to an audio file. + + +All you need to do is to use REAPER's built-in render functionality as usual: + +. Open menu:File[Render...] +. Change options if desired +. Press the btn:[Render] button + +That's it! Playtime doesn't change anything about the rendering process. In fact, it's not involved anymore at this point. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/structure.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/structure.adoc new file mode 100644 index 0000000..08bbdf0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/usage/structure.adoc @@ -0,0 +1,42 @@ += Structure the song + +Playtime's xref:user-interface/matrix-area.adoc[] suits itself for organizing the clips into a musical structure. + +== Rename slots + +Renaming a slot is as easy as selecting the xref:user-interface/matrix-area.adoc#slot-cell[] and pressing kbd:[F2], or right-clicking the cell and selecting xref:user-interface/menus/slot-menu.adoc#slot-rename-primary-clip[]. + +== Add more columns + +You can add more columns, for example one column per instrument. + +You can rename a column or its corresponding xref:key-concepts.adoc#column-track[] by selecting the xref:user-interface/matrix-area.adoc#column-cell[] and pressing kbd:[F2], or right-clicking the cell and selecting xref:user-interface/menus/column-menu.adoc#column-rename-column-or-track[]. + +== Drag and drop things + +You can freely drag and drop slot contents or other things. Check out the following links to learn more about it: + +* xref:user-interface/matrix-area.adoc#column-drag-and-drop[] +* xref:user-interface/matrix-area.adoc#row-drag-and-drop[] +* xref:user-interface/matrix-area.adoc#slot-drag-and-drop[] + +== Add more rows + +You can add more rows if you are running out of vertical space for your clips. Just press the xref:user-interface/matrix-area.adoc#matrix-add-row[] button or right-click a xref:user-interface/matrix-area.adoc#row-cell[] and choose xref:user-interface/menus/row-menu.adoc#row-insert-row-below[]. + +Renaming works just as with columns. + +== Use colors to spice things up + +There are two things in a xref:key-concepts.adoc#matrix[] that can get individual colors: Tracks and clips. + +In order to change the color of a track, click the xref:user-interface/track-area.adoc#track-panel-label[] to select the track. Then change the color in the xref:user-interface/inspector/track.adoc[] on the right. This sets the color of the REAPER track. + +In order to change the color of an individual clip, select the xref:user-interface/matrix-area.adoc#slot-cell[] and change the color in the xref:user-interface/inspector/clip.adoc[] on the right. + +[[feature-scenes]] +== Build scenes + +One particular way to make use of rows of the matrix is to consider a row as a xref:key-concepts.adoc#scene[] - a part within a song. By organizing your clips into scenes, the scene column on the left of the matrix basically turns into a playlist for your song, which is very convenient if you want to xref:usage/arrangement.adoc[turn the song into an arrangement]. + +One function which greatly helps to build scenes (apart from drag-and-drop and copy-and-paste) is xref:user-interface/menus/row-menu.adoc#row-build-scene-from-currently-playing-clips[], which you can access by right-clicking the xref:user-interface/matrix-area.adoc#row-cell-label[] of an empty row. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface.adoc new file mode 100644 index 0000000..43ea0fb --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface.adoc @@ -0,0 +1,41 @@ += User interface + +This section provides a detailed overview of all elements in Playtime's user interface. + +== Main screen + +Playtime's design centers around a single, large _main screen_, offering streamlined access to its most essential features. + +.Main screen +image::generated/screenshots/main/main-screen.png[] + +== Main screen areas + +The main screen is divided into distinct areas, each serving a specific purpose. + +.Areas of the main screen +image::generated/screenshots/main/main-screen-areas.png[] + +xref:user-interface/title-bar.adoc[]:: +include::partial$user-interface/title-bar-desc.adoc[] + +xref:user-interface/navigation-bar.adoc[]:: +include::partial$user-interface/nav-bar-desc.adoc[] + +xref:user-interface/toolbar.adoc[]:: +include::partial$user-interface/toolbar-desc.adoc[] + +xref:user-interface/content-panel.adoc[]:: +include::partial$user-interface/content-panel-desc.adoc[] + +xref:user-interface/matrix-area.adoc[]:: +include::partial$user-interface/matrix-area-desc.adoc[] + +xref:user-interface/track-area.adoc[]:: +include::partial$user-interface/track-area-desc.adoc[] + +xref:user-interface/inspector.adoc[]:: +include::partial$user-interface/inspector-desc.adoc[] + +xref:user-interface/info-panel.adoc[]:: +include::partial$user-interface/info-panel-desc.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/content-panel.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/content-panel.adoc new file mode 100644 index 0000000..578118c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/content-panel.adoc @@ -0,0 +1,12 @@ += Content panel + +include::partial$user-interface/content-panel-desc.adoc[] + +You can close the content panel by pressing the icon:close[] button on the left side, which appears as soon as your mouse hovers over the area. + +include::partial$generated/elements/waveform-view.adoc[] + +The waveform view shows only the relevant section of the actual audio file. +Which part is relevant, is dictated by the clip properties xref:user-interface/inspector/clip.adoc#inspector-clip-source-cut-start[] and xref:user-interface/inspector/clip.adoc#inspector-clip-source-cut-length[]. + +If you set the clip properties xref:user-interface/inspector/clip.adoc#inspector-clip-start-position[] or xref:user-interface/inspector/clip.adoc#inspector-clip-length[], you will see start and end markers. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs.adoc new file mode 100644 index 0000000..b62734e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs.adoc @@ -0,0 +1,3 @@ += Dialogs + +Dialogs are small windows that are opened on demand. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/arrangement-writing-mode-dialog.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/arrangement-writing-mode-dialog.adoc new file mode 100644 index 0000000..2f3cd54 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/arrangement-writing-mode-dialog.adoc @@ -0,0 +1,9 @@ += Arrangement writing mode dialog + +This dialog opens when pressing the xref:user-interface/toolbar.adoc#toolbar-write-to-arrangement[] button in the title bar and the current arrangement is dirty. It gives you multiple options how to deal with the fact that there are already items in the arrangement. + +image::generated/screenshots/main/arrangement-writing-mode.png[] + +include::partial$generated/elements/arrangement-writing-mode/replace.adoc[] +include::partial$generated/elements/arrangement-writing-mode/merge-at-cursor.adoc[] +include::partial$generated/elements/arrangement-writing-mode/append.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/purchase-and-activate-dialog.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/purchase-and-activate-dialog.adoc new file mode 100644 index 0000000..623a9cd --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/purchase-and-activate-dialog.adoc @@ -0,0 +1,17 @@ += Purchase & Activate dialog + +This dialog opens when clicking the button xref:user-interface/title-bar.adoc#title-bar-purchase-playtime[]. + +image::generated/screenshots/main/purchase.png[] + +include::partial$generated/elements/purchase/verify-license-key.adoc[] + +If unlocking Playtime doesn't work, please write an email to info@helgoboss.org! + +[NOTE] +==== +The copy protection mechanism of Playtime is as unobtrusive as the one used in REAPER: No iLok, no online verification. +Just a regular license key that is checked offline. + +No need to worry that you run in demo mode when your gig comes up! +==== diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/sequences-dialog.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/sequences-dialog.adoc new file mode 100644 index 0000000..9f9526f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/sequences-dialog.adoc @@ -0,0 +1,15 @@ += Sequences dialog + +This dialog opens when pressing the xref:user-interface/toolbar.adoc#toolbar-matrix-sequence-picker[] button in the title bar. + +image::generated/screenshots/main/sequences.png[] + +Clicking on a sequence makes it the active sequence. + +include::partial$generated/elements/sequences/edit.adoc[] + +At the moment, all you can do here is to adjust its name. + +include::partial$generated/elements/sequences/delete.adoc[] + +You can undo this operation using Playtime's built-in undo function. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/settings-dialog.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/settings-dialog.adoc new file mode 100644 index 0000000..872090a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/settings-dialog.adoc @@ -0,0 +1,31 @@ += Settings dialog + +This dialog opens when pressing the xref:helgobox:ROOT:app/user-interface/title-bar.adoc#title-bar-settings[] button in the title bar and navigating to the Playtime section. + +image::generated/screenshots/main/settings.png[] + +== Appearance + +include::partial$generated/elements/settings/show-control-unit-frames.adoc[] + +See xref:further-concepts/matrix.adoc#control-unit-frame[]. + +include::partial$generated/elements/settings/show-column-numbers.adoc[] + +Disabling this will give you more vertical space, but also hides the shortcut buttons such as xref:user-interface/matrix-area.adoc#column-cell-exclusive-mode[]. In that case, you can still change those things in the xref:user-interface/inspector/column.adoc[]. + +include::partial$generated/elements/settings/show-clip-activity.adoc[] +include::partial$generated/elements/settings/show-cell-selection-as-crosshair.adoc[] +include::partial$generated/elements/settings/show-hypothetical-play-cursor.adoc[] +include::partial$generated/elements/settings/cell-color-position.adoc[] +include::partial$generated/elements/settings/cell-progress-position.adoc[] + +include::partial$generated/elements/settings/cell-width.adoc[] + +include::partial$generated/elements/settings/cell-height.adoc[] + +In addition, you might be interested in the xref:helgobox::app/user-interface/settings-dialog.adoc#settings-overall-size[] setting, located in the general appearance section. + +== Engine + +include::partial$generated/elements/settings/tempo-latency.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/tempo-settings-dialog.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/tempo-settings-dialog.adoc new file mode 100644 index 0000000..e9538cd --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/dialogs/tempo-settings-dialog.adoc @@ -0,0 +1,14 @@ += Tempo settings dialog + +This dialog opens when right-clicking the xref:user-interface/toolbar.adoc#toolbar-metronome[] button in the title bar. +It lets you adjust various tempo-related settings. + +NOTE: The settings in here are not global! +They are saved as part of the matrix and therefore together with the REAPER project. + +image::generated/screenshots/main/tempo-settings.png[] + +include::partial$generated/elements/tempo-settings/click-and-tap.adoc[] +include::partial$generated/elements/tempo-settings/click-volume.adoc[] +include::partial$generated/elements/tempo-settings/tap-volume.adoc[] +include::partial$generated/elements/tempo-settings/count-in.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/general.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/general.adoc new file mode 100644 index 0000000..19626a2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/general.adoc @@ -0,0 +1,3 @@ += General usage + +See xref:helgobox::app/user-interface/general.adoc[general usage] in the Helgobox Reference. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/info-panel.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/info-panel.adoc new file mode 100644 index 0000000..ce23aa1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/info-panel.adoc @@ -0,0 +1,27 @@ += Info panel + +include::partial$user-interface/info-panel-desc.adoc[] + +== Help + +This tab displays helpful information about the item under the mouse. + +== Stats + +This tab displays information about the current state of the Playtime engine. + +include::partial$generated/elements/info-panel/stats/tempo-buffer-size.adoc[] + +See xref:user-interface/dialogs/settings-dialog.adoc#settings-tempo-latency[]. + +include::partial$generated/elements/info-panel/stats/pre-buffered-blocks-playing-min.adoc[] + +include::partial$generated/elements/info-panel/stats/pre-buffered-blocks-playing-avg.adoc[] + +include::partial$generated/elements/info-panel/stats/pre-buffered-blocks-playing-max.adoc[] + +include::partial$generated/elements/info-panel/stats/pre-buffer-fallback-count.adoc[] + +include::partial$generated/elements/info-panel/stats/pre-buffer-miss-count.adoc[] + +include::partial$generated/elements/info-panel/warnings.adoc[] diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector.adoc new file mode 100644 index 0000000..99f2fa7 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector.adoc @@ -0,0 +1,11 @@ += Inspector + +include::partial$user-interface/inspector-desc.adoc[] + +What exactly the inspector shows, depends on the current selection: + +* xref:user-interface/inspector/matrix.adoc[] +* xref:user-interface/inspector/column.adoc[] +* xref:user-interface/inspector/row.adoc[] +* xref:user-interface/inspector/slot.adoc[] +* xref:user-interface/inspector/track.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/clip.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/clip.adoc new file mode 100644 index 0000000..8a8e79c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/clip.adoc @@ -0,0 +1,167 @@ += Clip inspector + +include::partial$generated/elements/inspector/clip/quick-actions.adoc[] + +include::partial$generated/elements/inspector/clip/overdub.adoc[] + +include::partial$generated/elements/inspector/clip/quantize.adoc[] + +See xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-midi-quantization-grid[]. + +[NOTE] +==== +Playtime uses its own quantization algorithm, but it remembers the original event positions in a way that is compatible with REAPER. + +If you open a MIDI clip quantized by Playtime in REAPER's MIDI editor, you will see that the MIDI events are quantized there as well, and you have the possibility to unquantize directly in the MIDI editor. + +Vice versa, Playtime detects correctly if a clip has been quantized in REAPER's MIDI editor. +That means you can use both quantization methods interchangeably! +==== + +include::partial$generated/elements/inspector/clip/basics.adoc[] + +include::partial$generated/elements/inspector/clip/name.adoc[] + +TIP: Another way to change the sanem is to press kbd:[F2] when the slot is selected. + +include::partial$generated/elements/inspector/clip/looped.adoc[] + +See xref:key-concepts.adoc#loop[] and xref:key-concepts.adoc#one-shot[]. + +include::partial$generated/elements/inspector/clip/volume.adoc[] + +include::partial$generated/elements/inspector/clip/pitch.adoc[] + +For audio, pitch shifting is done using REAPER's pitch-shifting/time-stretching engine with the clip's xref:user-interface/inspector/clip.adoc#inspector-clip-time-stretch-mode[]. + +include::partial$clip-prop-applied-quantized.adoc[] + +include::partial$generated/elements/inspector/clip/velocity-sensitivity.adoc[] + +include::partial$generated/elements/inspector/clip/source-info.adoc[] + +See xref:key-concepts.adoc#source[]. + +TIP: Right-clicking this area opens the xref:user-interface/menus/source-menu.adoc[]. + +include::partial$generated/elements/inspector/clip/color.adoc[] + +include::partial$generated/elements/inspector/clip/triggering.adoc[] + +include::partial$generated/elements/inspector/clip/start-timing.adoc[] + +See xref:key-concepts.adoc#clip-start-timing[]. + +include::partial$generated/elements/inspector/clip/start-timing/immediately.adoc[] + +include::partial$generated/elements/inspector/clip/start-timing/custom.adoc[] + +TIP: In most cases, picking one of the pre-defined quantizations (e.g. **1 bar**) should be sufficient. + +include::partial$generated/elements/inspector/clip/stop-timing.adoc[] +include::partial$generated/elements/inspector/clip/stop-timing/like-start-timing.adoc[] +include::partial$generated/elements/inspector/clip/stop-timing/until-end-of-clip.adoc[] + +CAUTION: This can easily lead to unintentional concurrent playback of clips when using xref:user-interface/inspector/column/playback.adoc#inspector-column-exclusive-mode[]. + +include::partial$generated/elements/inspector/clip/stop-timing/immediately.adoc[] +include::partial$generated/elements/inspector/clip/stop-timing/custom.adoc[] + + +include::partial$generated/elements/inspector/clip/synchronization.adoc[] + +include::partial$generated/elements/inspector/clip/sync-to-project-tempo.adoc[] + +TIP: This alone doesn't guarantee yet that your clip will be played in time. +It's just one of multiple ingredients for in-sync playback. +See xref:key-concepts.adoc#in-sync[]. + +NOTE: This is comparable to REAPER's item time base setting. +In fact, this will be translated to different item time base settings when xref:user-interface/toolbar.adoc#toolbar-write-to-arrangement[writing to the arrangement]. + +include::partial$generated/elements/inspector/clip/tempo.adoc[] + +TIP: This is available for audio clips only. +MIDI clips are by its very nature tempo-agnostic. + +include::partial$clip-prop-applied-quantized.adoc[] + +include::partial$generated/elements/inspector/clip/time-signature.adoc[] + +include::partial$generated/elements/inspector/clip/downbeat.adoc[] + +TIP: This can be used to realize an link:https://en.wikipedia.org/wiki/Anacrusis[Anacrusis] (or pick-up beat). +See xref:user-interface/inspector/matrix/recording.adoc#inspector-matrix-recording-detect-downbeat[]. + +include::partial$generated/elements/inspector/clip/section.adoc[] + +.Usage with MIDI clips +CAUTION: These properties work for both audio and MIDI clips. +When using it on MIDI clips, be aware that the REAPER MIDI editor doesn't indicate the start position or length in any way, which can be confusing. +If you want to change the start position and length permanently, it's usually better to adjust the underlying MIDI data. + +include::partial$generated/elements/inspector/clip/start-position.adoc[] + +include::partial$user-interface/clip-prop-adjust-in-beats.adoc[] + +include::partial$clip-prop-applied-quantized.adoc[] + +include::partial$generated/elements/inspector/clip/length.adoc[] + +The value **Original** means that the source will end at the original end of the source material. + +Use the ÷2 and ×2 buttons to halve or double the current length. + +include::partial$user-interface/clip-prop-adjust-in-beats.adoc[] + +include::partial$clip-prop-applied-quantized.adoc[] + +include::partial$generated/elements/inspector/clip/midi.adoc[] + +include::partial$generated/elements/inspector/clip/midi-reset-behavior.adoc[] + +include::partial$generated/elements/inspector/clip/midi-reset-behavior/none.adoc[] +include::partial$generated/elements/inspector/clip/midi-reset-behavior/light-left.adoc[] +include::partial$generated/elements/inspector/clip/midi-reset-behavior/extreme-left.adoc[] +include::partial$generated/elements/inspector/clip/midi-reset-behavior/light-right.adoc[] +include::partial$generated/elements/inspector/clip/midi-reset-behavior/extreme-right.adoc[] + +include::partial$generated/elements/inspector/clip/midi-channel.adoc[] + +include::partial$generated/elements/inspector/clip/audio.adoc[] + +include::partial$generated/elements/inspector/clip/source-fades.adoc[] + +include::partial$generated/elements/inspector/clip/time-stretch-mode.adoc[] + +Playtime uses this algorithm whenever it needs to adjust beat-based audio clips to the current tempo while keeping the original pitch, but also when xref:user-interface/inspector/clip.adoc#inspector-clip-pitch[adjusting the pitch] of audio clips. + +include::partial$clip-prop-applied-quantized.adoc[] + +include::partial$generated/elements/inspector/clip/time-stretch-mode/vari-speed.adoc[] +include::partial$generated/elements/inspector/clip/time-stretch-mode/keeping-pitch.adoc[] + +When selecting this option, you can choose between all pitch shifting modes and corresponding sub modes that are supported by REAPER. + +include::partial$generated/elements/inspector/clip/resample-mode.adoc[] + +Playtime uses resampling in the following cases: + +. Vari-speed time stretching +. Adjusting to play rate changes +. Adjusting clips that have a sample rate other than the project/hardware sample rate + +You can choose between all resample modes that are supported by REAPER. + +include::partial$clip-prop-applied-quantized.adoc[] + +include::partial$generated/elements/inspector/clip/expert-settings.adoc[] + +include::partial$generated/elements/inspector/clip/source-cut-start.adoc[] + +include::partial$clip-prop-applied-quantized.adoc[] + + +include::partial$generated/elements/inspector/clip/source-cut-length.adoc[] + +include::partial$clip-prop-applied-quantized.adoc[] diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column.adoc new file mode 100644 index 0000000..5b1ec8f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column.adoc @@ -0,0 +1,11 @@ += Column inspector + +You will see this when selecting a xref:user-interface/matrix-area.adoc#column-cell[]. + +This inspector is divided into two areas: + +xref:user-interface/inspector/column/playback.adoc[]:: +Settings that are mainly about playing clips + +xref:user-interface/inspector/column/recording.adoc[]:: +Settings that are mainly about recording clips \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column/playback.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column/playback.adoc new file mode 100644 index 0000000..18d1725 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column/playback.adoc @@ -0,0 +1,43 @@ += Column playback settings + +You will see this when selecting a xref:user-interface/matrix-area.adoc#column-cell[]. + +include::partial$generated/elements/inspector/column/basics.adoc[] + +include::partial$generated/elements/inspector/column/playback-track.adoc[] + +See xref:key-concepts.adoc#column-track[]. + +include::partial$generated/elements/inspector/column/custom-name.adoc[] + +include::partial$generated/elements/inspector/column/exclusive-mode.adoc[] + +include::partial$generated/elements/inspector/column/follow-scenes.adoc[] + +include::partial$generated/elements/inspector/column/more.adoc[] + +include::partial$generated/elements/inspector/column/start-timing.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing[]. + +include::partial$generated/elements/inspector/column/stop-timing.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-stop-timing[]. + +include::partial$generated/elements/inspector/column/trigger-mode.adoc[] +include::partial$generated/elements/inspector/column/trigger-mode/toggle.adoc[] +include::partial$generated/elements/inspector/column/trigger-mode/momentary.adoc[] +include::partial$generated/elements/inspector/column/trigger-mode/retrigger.adoc[] + + +include::partial$generated/elements/inspector/column/velocity-sensitivity.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-velocity-sensitivity[]. + +include::partial$generated/elements/inspector/column/time-stretch-mode.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-time-stretch-mode[]. + +include::partial$generated/elements/inspector/column/resample-mode.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-resample-mode[]. diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column/recording.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column/recording.adoc new file mode 100644 index 0000000..201e697 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/column/recording.adoc @@ -0,0 +1,5 @@ += Column recording settings + +You will see this when selecting a xref:user-interface/matrix-area.adoc#column-cell[]. + +NOTE: There are no column-specific recording settings yet. This section is reserved for the future, \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix.adoc new file mode 100644 index 0000000..a70115d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix.adoc @@ -0,0 +1,11 @@ += Matrix inspector + +You will see this when selecting the xref:user-interface/matrix-area.adoc#matrix-cell[]. + +This inspector is divided into two areas: + +xref:user-interface/inspector/matrix/playback.adoc[]:: +Settings that are mainly about playing clips + +xref:user-interface/inspector/matrix/recording.adoc[]:: +Settings that are mainly about recording clips \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix/playback.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix/playback.adoc new file mode 100644 index 0000000..d2277cf --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix/playback.adoc @@ -0,0 +1,41 @@ += Matrix playback settings + +You will see this when selecting the xref:user-interface/matrix-area.adoc#matrix-cell[]. + +include::partial$generated/elements/inspector/matrix/playback-basics.adoc[] + +include::partial$generated/elements/inspector/matrix/start-timing.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-start-timing[]. + +include::partial$generated/elements/inspector/matrix/stop-timing.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-stop-timing[]. + +include::partial$generated/elements/inspector/matrix/trigger-mode.adoc[] + +See xref:user-interface/inspector/column/playback.adoc#inspector-column-trigger-mode[]. + +include::partial$generated/elements/inspector/matrix/velocity-sensitivity.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-velocity-sensitivity[]. + +include::partial$generated/elements/inspector/matrix/activate-slot-on-trigger.adoc[] + +include::partial$generated/elements/inspector/matrix/transport-sync.adoc[] + +include::partial$generated/elements/inspector/matrix/midi-quantization-grid.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-quantize[]. + +TIP: If you want more advanced quantization options, do quantization in REAPER's MIDI editor instead. + +include::partial$generated/elements/inspector/matrix/playback-more.adoc[] + +include::partial$generated/elements/inspector/matrix/time-stretch-mode.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-time-stretch-mode[]. + +include::partial$generated/elements/inspector/matrix/resample-mode.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-resample-mode[]. diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix/recording.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix/recording.adoc new file mode 100644 index 0000000..204ba3f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/matrix/recording.adoc @@ -0,0 +1,45 @@ += Matrix recording settings + +You will see this when selecting the xref:user-interface/matrix-area.adoc#matrix-cell[]. + +include::partial$generated/elements/inspector/matrix/recording-basics.adoc[] + +include::partial$generated/elements/inspector/matrix/recording-looped.adoc[] + +include::partial$generated/elements/inspector/matrix/recording-allow-tempo-detection-recording.adoc[] + +include::partial$generated/elements/inspector/matrix/limit-recording-length.adoc[] + +include::partial$generated/elements/inspector/matrix/recording-length.adoc[] + +include::partial$generated/elements/inspector/matrix/recording-midi.adoc[] + +include::partial$generated/elements/inspector/matrix/recording-detect-downbeat.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-downbeat[]. + +include::partial$generated/elements/inspector/matrix/recording-auto-quantize.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-quantize[] and xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-midi-quantization-grid[]. + +include::partial$generated/elements/inspector/matrix/recording-settings-for-new-clips.adoc[] + +include::partial$generated/elements/inspector/matrix/recording-sync-to-project-tempo.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-sync-to-project-tempo[]. + +include::partial$generated/elements/inspector/matrix/recording-sync-to-project-tempo/auto.adoc[] +include::partial$generated/elements/inspector/matrix/recording-sync-to-project-tempo/no.adoc[] +include::partial$generated/elements/inspector/matrix/recording-sync-to-project-tempo/yes.adoc[] + +include::partial$generated/elements/inspector/matrix/recording-midi-reset-behavior.adoc[] + +See xref:user-interface/inspector/clip.adoc#inspector-clip-midi-reset-behavior[]. + +include::partial$generated/elements/inspector/matrix/tempo-detection-range.adoc[] + +.Simple example +==== +Playtime might detect that imported audio material has the following valid tempos: 30bpm, 60bpm, 120bpm, 240 bpm. +By setting the tempo detection range to 80bpm - 180bpm, Playtime will for sure pick 120bpm. +==== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/row.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/row.adoc new file mode 100644 index 0000000..67cb000 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/row.adoc @@ -0,0 +1,11 @@ += Row inspector + +You will see this when selecting a xref:user-interface/matrix-area.adoc#row-cell[]. + +include::partial$generated/elements/inspector/row/basics.adoc[] + +include::partial$generated/elements/inspector/row/name.adoc[] + +include::partial$generated/elements/inspector/row/tempo.adoc[] + +WARNING: This is an experimental feature. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/slot.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/slot.adoc new file mode 100644 index 0000000..a8e69e8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/slot.adoc @@ -0,0 +1,18 @@ += Slot inspector + +You will see this when selecting a xref:user-interface/matrix-area.adoc#slot-cell[]. + +include::partial$generated/elements/inspector/slot/clip-picker.adoc[] + +include::partial$generated/elements/inspector/slot/clip.adoc[] + +For details about the actual clip, see xref:user-interface/inspector/clip.adoc[]. + +TIP: Right-clicking one of the clips opens the xref:user-interface/menus/clip-menu.adoc[]. + +[[clip-drag-and-drop]] +.Drag-and-drop support +**** +Move a clip to another slot:: +Drag one clip onto another slot in order to move it there. +**** \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/track.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/track.adoc new file mode 100644 index 0000000..ed4863b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/inspector/track.adoc @@ -0,0 +1,9 @@ += Track inspector + +You will see this when selecting a xref:user-interface/track-area.adoc[track panel]. + +include::partial$generated/elements/inspector/track/basics.adoc[] + +include::partial$generated/elements/inspector/track/name.adoc[] + +include::partial$generated/elements/inspector/track/color.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/keyboard-shortcuts.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/keyboard-shortcuts.adoc new file mode 100644 index 0000000..ba9a946 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/keyboard-shortcuts.adoc @@ -0,0 +1,64 @@ += Keyboard shortcuts + +Playtime provides keyboard shortcuts in addition to the xref:helgobox::app/user-interface/keyboard-shortcuts.adoc[general ones]. + +[cols = "1,1a"] +|=== +|Shortcut |Purpose + +|kbd:[Cmd+Z] +|Invokes Playtime's undo function. + +|kbd:[Cmd+R] +|Invokes Playtime's redo function. + +|kbd:[Backspace/Delete] +|Deletes the currently selected column or row, or clears the selected slot's contents. + +|kbd:[Cmd/Ctrl+C] +|Copies the currently selected row or slot contents to the clipboard. + +|kbd:[Cmd/Ctrl+X] +|Cuts the currently selected row or slot contents to the clipboard. + +|kbd:[Cmd/Ctrl+V] +|Pastes row or slot contents from the clipboard to the currently selected row or slot. + +|kbd:[Space] +|Starts or stops playback. + +|[[enter,Enter]] kbd:[Enter] +| +Carries out an action depending on the current selection: + +* When the xref:user-interface/matrix-area.adoc#matrix-cell[] is selected, stops all clips in the xref:key-concepts.adoc#matrix[]. +* When a xref:user-interface/matrix-area.adoc#column-cell[] is selected, stops all clips in the xref:key-concepts.adoc#column[]. +* When a xref:user-interface/matrix-area.adoc#row-cell[] is selected, plays all clips in the xref:key-concepts.adoc#row[] whose column follows scenes. +* When a xref:user-interface/matrix-area.adoc#slot-cell[] is selected, xref:further-concepts/matrix.adoc#trigger-slot[triggers] the xref:key-concepts.adoc#slot[]. + +|kbd:[F2] +|Renames the currently selected column, row, clip or track. + +|kbd:[T] +|Taps the tempo. + +|kbd:[M] +|Toggles the metronome. + +|kbd:[E] +|Edits the first clip in the currently selected slot. + +|kbd:[Q] +|Quantizes the first clip in the currently selected slot. + +|kbd:[O] +|Overdubs the first clip in the currently selected slot, if it's a MIDI clip. + +|kbd:[A] +|Exports the complete matrix or the currently selected column, row or clip to the arrangement. + +|kbd:[Shift+Cmd/Ctrl+P] +|Executes the REAPER action xref:reaper-actions.adoc#show-hide-playtime[]. + +|=== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/matrix-area.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/matrix-area.adoc new file mode 100644 index 0000000..dc3fd20 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/matrix-area.adoc @@ -0,0 +1,131 @@ += Matrix area + +include::partial$user-interface/matrix-area-desc.adoc[] + +.Column cells of the matrix area +image::generated/screenshots/elements/area/matrix-area-1.png[] + +.Row cells of the matrix area +image::generated/screenshots/elements/area/matrix-area-2.png[width=100] + +.Slot cells of the matrix area +image::generated/screenshots/elements/area/matrix-area-3.png[] + +include::partial$generated/elements/matrix/template-panel.adoc[] + +This is only shown if the matrix doesn't have any columns yet. + +include::partial$generated/elements/matrix/audio-template-button.adoc[] +include::partial$generated/elements/matrix/midi-template-button.adoc[] +include::partial$generated/elements/matrix/mixed-template-button.adoc[] + +== General + +include::partial$generated/elements/matrix/add-column.adoc[] + +Adding a new column automatically creates a corresponding REAPER track, on which clips in this column will be played back. +See xref:key-concepts.adoc#column-track[]. + +include::partial$generated/elements/matrix/add-row.adoc[] + +include::partial$generated/elements/matrix/scroll-indicator.adoc[] + +// Matrix cell + +include::partial$generated/elements/matrix-cell.adoc[] + +include::partial$generated/elements/matrix-cell/stop.adoc[] + +include::partial$generated/elements/matrix-cell/settings.adoc[] + +See xref:user-interface/inspector/matrix.adoc[]. + +TIP: Right-clicking this area opens the xref:user-interface/menus/matrix-menu.adoc[]. + +// Column cell + +include::partial$generated/elements/column-cell.adoc[] + +If you don't see the upper part, check xref:user-interface/dialogs/settings-dialog.adoc#settings-show-column-numbers[]. + +[[column-drag-and-drop]] +.Column drag-and-drop support +**** +Reorder column:: +Drag the cell left or right to change the position of the column. +**** + +include::partial$generated/elements/column-cell/exclusive-mode.adoc[] + +This is a shortcut for xref:user-interface/inspector/column/playback.adoc#inspector-column-exclusive-mode[]. + +include::partial$generated/elements/column-cell/follow-scenes.adoc[] + +This is a shortcut for xref:user-interface/inspector/column/playback.adoc#inspector-column-follow-scenes[]. + +include::partial$generated/elements/column-cell/has-overrides.adoc[] + +include::partial$generated/elements/column-cell/stop.adoc[] + +include::partial$generated/elements/column-cell/label.adoc[] + +See xref:user-interface/inspector/column.adoc[]. + +TIP: Right-clicking this area opens the xref:user-interface/menus/column-menu.adoc[]. + +// Row cell + +include::partial$generated/elements/row-cell.adoc[] + +[[row-drag-and-drop]] +.Row drag-and-drop support +**** +Reorder row:: +Drag the cell up or down to change the position of the row. +**** + +include::partial$generated/elements/row-cell/play-scene.adoc[] + +See xref:key-concepts.adoc#scene[]. + +include::partial$generated/elements/row-cell/label.adoc[] + +See xref:user-interface/inspector/row.adoc[]. + +TIP: Right-clicking this area opens the xref:user-interface/menus/row-menu.adoc[]. + +// Slot cell + +include::partial$generated/elements/slot-cell.adoc[] + +include::partial$generated/elements/slot-cell/trigger.adoc[] + +include::partial$generated/elements/slot-cell/record.adoc[] + +This is only available if the corresponding xref:key-concepts.adoc#column-track[] is armed. + +include::partial$generated/elements/slot-cell/content.adoc[] + +See xref:user-interface/inspector/slot.adoc[]. + +TIP: Right-clicking this area opens the xref:user-interface/menus/slot-menu.adoc[]. + +[[slot-drag-and-drop]] +.Slot drag-and-drop support +**** +Move slot contents:: +Drag this area onto another slot in order to move all clips within this slot to the other slot. + +Copy slot contents:: +Drag this area onto another slot while holding kbd:[Cmd/Ctrl] in order to copy all clips within this slot to the other slot. + +Import content:: +Drag a MIDI or audio file from outside onto this area in order to import it as a clip. This should work from the REAPER media explorer, from other plug-ins that offer dragging out content as files and from most file managers. ++ +NOTE: Dragging REAPER items onto the slot won't work, because REAPER items are not real files. Use xref:user-interface/menus/slot-menu.adoc#slot-import-selected-item[] instead! +**** + +include::partial$generated/elements/slot-cell/clip-count.adoc[] + +If there's more than one clip in the slot, all will be triggered at the same time. +However, each one will start according to its individual start timing. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus.adoc new file mode 100644 index 0000000..88e2362 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus.adoc @@ -0,0 +1,3 @@ += Menus + +Most of the menus in Playtime are context menus that you can open by right-clicking certain areas. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/clip-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/clip-menu.adoc new file mode 100644 index 0000000..e18446e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/clip-menu.adoc @@ -0,0 +1,7 @@ += Clip context menu + +Right-clicking one of the clips in the xref:user-interface/inspector/slot.adoc#inspector-slot-clip-picker[] opens this context menu. + +include::partial$generated/menus/clip/remove-from-slot.adoc[] +include::partial$generated/menus/clip/promote-to-top.adoc[] + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/column-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/column-menu.adoc new file mode 100644 index 0000000..092f8df --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/column-menu.adoc @@ -0,0 +1,13 @@ += Column context menu + +Right-clicking xref:user-interface/matrix-area.adoc#column-cell-label[] opens this context menu. + +include::partial$generated/menus/column/rename-column-or-track.adoc[] +include::partial$generated/menus/column/duplicate.adoc[] +include::partial$generated/menus/column/insert-column-left.adoc[] +include::partial$generated/menus/column/insert-column-right.adoc[] +include::partial$generated/menus/column/insert-column-for-each-selected-track.adoc[] +include::partial$generated/menus/column/remove.adoc[] +include::partial$generated/menus/column/remove-keep-track.adoc[] +include::partial$generated/menus/column/export-to-arrangement.adoc[] +include::partial$generated/menus/column/export-to-clipboard-as-lua.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/export-to-clipboard-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/export-to-clipboard-menu.adoc new file mode 100644 index 0000000..4ab4875 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/export-to-clipboard-menu.adoc @@ -0,0 +1,11 @@ += Export to clipboard menu + +This menu can be found in the xref:helgobox::plug-in/user-interface.adoc[plug-in window]. + +The following list only describes the Playtime-related menu entries. For the rest, see xref:helgobox::plug-in/user-interface/menu-bar.adoc#export-to-clipboard[] in the Helgobox Reference. + +[[export-playtime-matrix-as-json]] Export Playtime matrix as JSON:: +Copies a dump of the xref:key-concepts.adoc#matrix[] (including its settings and contents) to the clipboard in JSON format. + +Export Playtime matrix as Lua:: +Like <> but generates Lua/Luau code instead. This can be helpful if you want to xref:advanced-usage-scenarios/generate-matrix.adoc[generate a matrix using code]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/matrix-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/matrix-menu.adoc new file mode 100644 index 0000000..33632cc --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/matrix-menu.adoc @@ -0,0 +1,9 @@ += Matrix context menu + +Right-clicking xref:user-interface/matrix-area.adoc#matrix-cell-settings[] opens this context menu. + +include::partial$generated/menus/matrix/insert-column-for-each-selected-track.adoc[] +include::partial$generated/menus/matrix/insert-column-right.adoc[] +include::partial$generated/menus/matrix/insert-row-below.adoc[] +include::partial$generated/menus/matrix/export-to-arrangement.adoc[] +include::partial$generated/menus/matrix/export-to-clipboard-as-lua.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/row-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/row-menu.adoc new file mode 100644 index 0000000..cdb2bb3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/row-menu.adoc @@ -0,0 +1,15 @@ += Row context menu + +Right-clicking xref:user-interface/matrix-area.adoc#row-cell-label[] opens this context menu. + +include::partial$generated/menus/row/rename.adoc[] +include::partial$generated/menus/row/build-scene-from-currently-playing-clips.adoc[] +include::partial$generated/menus/row/copy.adoc[] +include::partial$generated/menus/row/cut.adoc[] +include::partial$generated/menus/row/paste.adoc[] +include::partial$generated/menus/row/clear.adoc[] +include::partial$generated/menus/row/duplicate.adoc[] +include::partial$generated/menus/row/insert-row-above.adoc[] +include::partial$generated/menus/row/insert-row-below.adoc[] +include::partial$generated/menus/row/remove.adoc[] +include::partial$generated/menus/row/export-to-arrangement.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/slot-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/slot-menu.adoc new file mode 100644 index 0000000..89c1635 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/slot-menu.adoc @@ -0,0 +1,15 @@ += Slot context menu + +Right-clicking xref:user-interface/matrix-area.adoc#slot-cell-content[] opens this context menu. + +include::partial$generated/menus/slot/rename-primary-clip.adoc[] +include::partial$generated/menus/slot/copy.adoc[] +include::partial$generated/menus/slot/cut.adoc[] +include::partial$generated/menus/slot/paste.adoc[] +include::partial$generated/menus/slot/clear.adoc[] +include::partial$generated/menus/slot/import-selected-item.adoc[] +include::partial$generated/menus/slot/import-files.adoc[] +include::partial$generated/menus/slot/export-primary-clip-to-arrangement.adoc[] +include::partial$generated/menus/slot/export-primary-clip-to-clipboard-as-lua.adoc[] + +This can be helpful if you want to xref:advanced-usage-scenarios/generate-matrix.adoc[generate a matrix using code]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/source-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/source-menu.adoc new file mode 100644 index 0000000..0023168 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/menus/source-menu.adoc @@ -0,0 +1,5 @@ += Source context menu + +Right-clicking xref:user-interface/inspector/clip.adoc#inspector-clip-source-info[] opens this context menu. + +include::partial$generated/menus/source-info/open-in-media-explorer.adoc[] diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/navigation-bar.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/navigation-bar.adoc new file mode 100644 index 0000000..7c30965 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/navigation-bar.adoc @@ -0,0 +1,5 @@ += Navigation bar + +include::partial$user-interface/nav-bar-desc.adoc[] + +See xref:helgobox::app/user-interface/navigation-bar.adoc[] in the Helgobox documentation. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/smart-record-menu.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/smart-record-menu.adoc new file mode 100644 index 0000000..8e3b2d4 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/smart-record-menu.adoc @@ -0,0 +1,7 @@ += Smart-record menu + +Right-clicking xref:user-interface/toolbar.adoc#toolbar-smart-record[] opens this menu. + +include::partial$generated/menus/smart-record/limit-recording-length.adoc[] + +This is a shortcut for xref:user-interface/inspector/matrix/recording.adoc#inspector-matrix-limit-recording-length[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/title-bar.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/title-bar.adoc new file mode 100644 index 0000000..8d9927b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/title-bar.adoc @@ -0,0 +1,14 @@ += Title bar + +include::partial$user-interface/title-bar-desc.adoc[] + +This section focuses solely on the Playtime-specific functions. +For more information on the general functions, refer to the xref:helgobox::app/user-interface/title-bar.adoc[] section in the Helgobox documentation. + +include::partial$generated/elements/title-bar/tempo-detection-recording-hint.adoc[] + +include::partial$generated/elements/title-bar/purchase-playtime.adoc[] + +This button is displayed only if your copy of Playtime has not been activated yet. + +See xref:user-interface/dialogs/purchase-and-activate-dialog.adoc[]. diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/toolbar.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/toolbar.adoc new file mode 100644 index 0000000..13fdc4d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/toolbar.adoc @@ -0,0 +1,123 @@ += Toolbar + +include::partial$user-interface/toolbar-desc.adoc[] + +.Toolbar +image::generated/screenshots/elements/area/toolbar-0.png[] + +// Tempo section + +include::partial$generated/elements/toolbar/tempo-section.adoc[] + +include::partial$generated/elements/toolbar/metronome.adoc[] + +See xref:user-interface/dialogs/tempo-settings-dialog.adoc[]. + +NOTE: The Playtime metronome is only audible when REAPER is not playing. When REAPER is playing, REAPER's normal metronome takes over. This is also the reason why Playtime automatically syncs Playtime's metronome on/off status to REAPER's metronome on/off status. + +include::partial$generated/elements/toolbar/tempo.adoc[] + +include::partial$generated/elements/toolbar/tap-tempo.adoc[] + +include::partial$generated/elements/toolbar/time-signature.adoc[] + +include::partial$generated/elements/toolbar/play-rate.adoc[] + +// Transport section + +include::partial$generated/elements/toolbar/transport-section.adoc[] + +Those are different from the REAPER transport controls, as Playtime is capable of playing and recording while REAPER is stopped. + +include::partial$generated/elements/toolbar/smart-record.adoc[] + +TIP: Right-clicking the smart-record button opens the xref:user-interface/smart-record-menu.adoc[]. + +include::partial$generated/elements/toolbar/start-stop-playback.adoc[] + +If you want to start REAPER playback together with Playtime, you have two options: + +[loweralpha] +. Start playback within REAPER itself (which automatically starts Playtime playback) +. Switch xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-transport-sync[] to **Full** + +// Visual metronome + +include::partial$generated/elements/toolbar/visual-metronome.adoc[] + +// Start timing + +include::partial$generated/elements/toolbar/matrix-start-timing.adoc[] + +See xref:key-concepts.adoc#clip-start-timing[]. + +// Matrix sequencer + +include::partial$generated/elements/toolbar/matrix-sequencer.adoc[] + +See xref:key-concepts.adoc#matrix-sequence[]. + +include::partial$generated/elements/toolbar/record-matrix-sequence.adoc[] + +This will always create a new sequence. +Old sequences are retained, so you can press this anytime without worrying to overwrite old sequences. + +Even though the button turns red right after clicking it, you don't need to rush with your performance. +After recording, Playtime will automatically remove silence at the beginning of the sequence. + +include::partial$generated/elements/toolbar/matrix-sequence-picker.adoc[] + +See xref:user-interface/dialogs/sequences-dialog.adoc[]. + +include::partial$generated/elements/toolbar/play-stop-matrix-sequence.adoc[] + +include::partial$generated/elements/toolbar/write-to-arrangement.adoc[] + +If the Playtime column tracks already contain items, pressing this button will show the xref:user-interface/dialogs/arrangement-writing-mode-dialog.adoc[]. + +See xref:usage/arrangement.adoc#feature-arrangement[] for details. + +include::partial$generated/elements/toolbar/show-hide-midi-triggers.adoc[] + +[IMPORTANT] +==== +The purpose of this feature is to quickly create *simple* and *xref:helgobox:ROOT:key-concepts.adoc#instance[instance-specific]* mappings of MIDI keys/pads/buttons to cells. + +* If you are looking for out-of-the-box support for full-featured grid controllers, such as the Launchpad, see xref:usage/grid-controllers.adoc[]. +* If you are looking for advanced, highly-customizable control scenarios, you should use xref:realearn::introduction.adoc[ReaLearn] with its dedicated xref:realearn::targets/playtime.adoc[]. +==== + +Here's how you assign a new MIDI trigger to a cell or toolbar button, after showing the MIDI triggers: + +. Click the MIDI trigger area in the cell or next to the toolbar button. It should turn red, which indicates that Playtime waits for you to press something. +. Press a key or foot switch on your music keyboard. The area should now turn back to its original color and the corresponding note name or MIDI CC number should appear. +. You can now use the note or foot switch to trigger the cell or invoke the toolbar function. + +.Powered by ReaLearn +[TIP] +==== +Playtime's MIDI triggers are powered by xref:realearn::introduction.adoc[ReaLearn], Helgobox's controller integration solution. In fact, each Playtime MIDI trigger corresponds to one xref:realearn::key-concepts.adoc#mapping[] in ReaLearn's xref:realearn::key-concepts.adoc#unit[Main unit]. + +When you learn a MIDI trigger, you are actually learning the source of a ReaLearn mapping that Playtime automatically creates for you. The target is pre-configured by Playtime and is nothing more than one of the xref:realearn::targets/playtime.adoc[]. The beauty of this is that under the hood, you have the complete power of ReaLearn at your disposal, including its xref:realearn::key-concepts.adoc#feedback[] capabilities! +==== + + +[[midi-trigger-troubleshooting]] +.Troubleshooting +**** +If nothing happens when you press the note or foot switch, we need to check the MIDI signal flow. + +. Press the xref:helgobox::app/user-interface/navigation-bar.adoc#navbar-show-helgobox-plugin[] button in the navigation bar on the left. This should open the xref:helgobox::plug-in/user-interface.adoc[plug-in window] starring ReaLearn's xref:realearn::user-interface/main-panel.adoc[]. +. Check ReaLearn's xref:realearn::user-interface/main-panel/input-output-section.adoc[input]! + +By default, the input should be set to xref:realearn::user-interface/main-panel/input-output-section.adoc#fx-input[]. This means that ReaLearn captures MIDI messages from the surrounding REAPER track (the one containing this xref:helgobox::key-concepts.adoc#instance[Helgobox instance]). *In this case, make sure that the track is armed and input monitoring is switched on!* + +If you want, you can also set the input to a specific device instead! In that case, the Helgobox track doesn't need to be armed. + +After that, try again learning the MIDI trigger within Playtime! + +.Leveraging button LEDs using ReaLearn's feedback capabilities +TIP: If the buttons on your device have LEDs, you might want to set ReaLearn's output to the same device. With a bit of luck, that makes the LEDs reflect the current clip playback or function state. However, depending on the specific controller, that part might need a bit more work. +**** + +include::partial$generated/elements/toolbar/pane-toggler.adoc[] diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/track-area.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/track-area.adoc new file mode 100644 index 0000000..597202d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/pages/user-interface/track-area.adoc @@ -0,0 +1,150 @@ += Track area + +include::partial$user-interface/track-area-desc.adoc[] + +NOTE: Since multiple columns can have the same playback track, it's possible that you see one track panel multiple times! + +.Master track panel +image::generated/screenshots/elements/area/track-area-0.png[width=80] + +.Column track panels +image::generated/screenshots/elements/area/track-area-1.png[] + +== Track panel header + +The top of the track panel is the _track panel header_. + +.Drag-and-drop support +**** +Reorder column:: +Drag the header left or right to change the position of the corresponding column. +**** + +include::partial$generated/elements/track-panel/input-type-indicator.adoc[] + +include::partial$generated/elements/track-panel/label.adoc[] + +== Track input section + +include::partial$generated/elements/track-panel/input.adoc[] + +WARNING: Playtime captures audio or MIDI input more or less directly from the hardware. +That also means that the track's _input_ FX chain will not have any effect on the recording! +You can work around this limitation by using *Loopback* channels instead of input FX. + +TIP: Selecting *Loopback* channels as audio input doesn't automatically enable them! +Make sure that the REAPER setting *Virtual loopback audio hardware channels* in menu:Options[Preferences/Settings... > Audio] is high enough! + +NOTE: This directly sets the input of the corresponding REAPER track. +Although technically not necessary for recording because Playtime doesn't use REAPER's track recording facility, it is important for input monitoring. + +include::partial$generated/elements/track-panel/midi-channel-filter.adoc[] + +NOTE: This directly sets the input MIDI channel of the corresponding REAPER track. + +include::partial$generated/elements/track-panel/map-to-midi-channel.adoc[] + +NOTE: This directly sets the "Map input to channel" value of the corresponding REAPER track. + +include::partial$generated/elements/track-panel/input-monitoring.adoc[] + +include::partial$generated/elements/track-panel/input-monitoring-off.adoc[] + +include::partial$generated/elements/track-panel/input-monitoring-auto.adoc[] + +include::partial$generated/elements/track-panel/input-monitoring-on.adoc[] + +NOTE: Which input monitoring mode you choose also has an effect on how Playtime does latency compensation during recording. Read more about that in xref:further-concepts/general.adoc#latency-compensation[]. + +[[input-monitoring-explained, Playtime input monitoring explained]] +[NOTE] +.Playtime input monitoring explained +==== +Playtime input monitoring options are slightly different from REAPER's input monitoring options. +Each input monitoring option mode translates to a certain combination of REAPER track properties: + +[cols="s,s,1,1,1"] +|=== +|Track input monitoring in Playtime | Track armed in Playtime | Track monitoring in REAPER | Track record input in REAPER | Track armed in REAPER + +|Off +|No +|Off +|Record: input (audio or MIDI) +|No + +|Off +|Yes +|Off +|Record: input (audio or MIDI) +|Yes + +|Auto +|No +|On (Monitor Input) +|Record: input (audio or MIDI) +|No + +|Auto +|Yes +|On (Monitor Input) +|Record: input (audio or MIDI) +|Yes + +|On +|No +|On (Monitor Input) +|Record: disable (input monitoring only) +|Yes + +|On +|Yes +|On (Monitor Input) +|Record: input (audio or MIDI) +|Yes +|=== +==== + +== Track output section + +=== PDC info + +If the track is affected by xref:further-concepts/general.adoc#pdc[], a small icon will light up. + +include::partial$generated/elements/track-panel/pdc-tiny.adoc[] +include::partial$generated/elements/track-panel/pdc-significant.adoc[] +include::partial$generated/elements/track-panel/pdc-critical.adoc[] + +include::partial$generated/elements/track-panel/pan.adoc[] + +NOTE: This directly sets the pan value of the corresponding REAPER track. + +include::partial$generated/elements/track-panel/volume.adoc[] + +When not enough vertical space is available, this turns into a drag field, visualizing the peaks as color changes. + +NOTE: This directly sets the volume of the corresponding REAPER track. + +include::partial$generated/elements/track-panel/instrument-fx.adoc[] + +This icon is only visible if there's at least one instrument in the FX chain. + +include::partial$generated/elements/track-panel/fx-chain.adoc[] + +The button is displayed in bold font if there's at least one FX in the chain. + +include::partial$generated/elements/track-panel/routing.adoc[] + +include::partial$generated/elements/track-panel/arm.adoc[] + +NOTE: See <> to learn how this translates to REAPER track properties. + + +include::partial$generated/elements/track-panel/mute.adoc[] + +NOTE: This directly sets the mute state of the corresponding REAPER track. + +include::partial$generated/elements/track-panel/solo.adoc[] + +NOTE: This directly sets the solo state of the corresponding REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/clip-prop-applied-quantized.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/clip-prop-applied-quantized.adoc new file mode 100644 index 0000000..4533200 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/clip-prop-applied-quantized.adoc @@ -0,0 +1,2 @@ +.Quantized application +NOTE: If the clip is currently playing and configured to xref:user-interface/inspector/clip.adoc#inspector-clip-sync-to-project-tempo[synchronize to the project tempo], changes of this clip property will not be applied immediately. Instead, Playtime applies them at the next quantization interval, according to the xref:user-interface/inspector/matrix/playback.adoc#inspector-matrix-start-timing[matrix start timing]. This ensures that the clip will keep playing in time. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/content-panel-0.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/content-panel-0.adoc new file mode 100644 index 0000000..2d40c51 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/content-panel-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-content-panel-0,Content panel]]] +ifndef::pdf-theme[[[area-content-panel-0,Content panel image:playtime::generated/screenshots/elements/area/content-panel-0.png[width=50, pdfwidth=8mm]]]] +== Content panel + +image::playtime::generated/screenshots/elements/area/content-panel-0.png[Content panel, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/info-panel-0.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/info-panel-0.adoc new file mode 100644 index 0000000..e8ee650 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/info-panel-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-info-panel-0,Info panel]]] +ifndef::pdf-theme[[[area-info-panel-0,Info panel image:playtime::generated/screenshots/elements/area/info-panel-0.png[width=50, pdfwidth=8mm]]]] +== Info panel + +image::playtime::generated/screenshots/elements/area/info-panel-0.png[Info panel, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/inspector-0.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/inspector-0.adoc new file mode 100644 index 0000000..0680f3d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/inspector-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-inspector-0,Inspector]]] +ifndef::pdf-theme[[[area-inspector-0,Inspector image:playtime::generated/screenshots/elements/area/inspector-0.png[width=50, pdfwidth=8mm]]]] +== Inspector + +image::playtime::generated/screenshots/elements/area/inspector-0.png[Inspector, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-0.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-0.adoc new file mode 100644 index 0000000..971288c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-matrix-area-0,Matrix area]]] +ifndef::pdf-theme[[[area-matrix-area-0,Matrix area image:playtime::generated/screenshots/elements/area/matrix-area-0.png[width=50, pdfwidth=8mm]]]] +== Matrix area + +image::playtime::generated/screenshots/elements/area/matrix-area-0.png[Matrix area, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-1.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-1.adoc new file mode 100644 index 0000000..23386a2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-1.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-matrix-area-1,Matrix area]]] +ifndef::pdf-theme[[[area-matrix-area-1,Matrix area image:playtime::generated/screenshots/elements/area/matrix-area-1.png[width=50, pdfwidth=8mm]]]] +== Matrix area + +image::playtime::generated/screenshots/elements/area/matrix-area-1.png[Matrix area, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-2.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-2.adoc new file mode 100644 index 0000000..41d09fa --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-2.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-matrix-area-2,Matrix area]]] +ifndef::pdf-theme[[[area-matrix-area-2,Matrix area image:playtime::generated/screenshots/elements/area/matrix-area-2.png[width=50, pdfwidth=8mm]]]] +== Matrix area + +image::playtime::generated/screenshots/elements/area/matrix-area-2.png[Matrix area, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-3.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-3.adoc new file mode 100644 index 0000000..8e97ed7 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/matrix-area-3.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-matrix-area-3,Matrix area]]] +ifndef::pdf-theme[[[area-matrix-area-3,Matrix area image:playtime::generated/screenshots/elements/area/matrix-area-3.png[width=50, pdfwidth=8mm]]]] +== Matrix area + +image::playtime::generated/screenshots/elements/area/matrix-area-3.png[Matrix area, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/toolbar-0.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/toolbar-0.adoc new file mode 100644 index 0000000..2bdddb8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/toolbar-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-toolbar-0,Toolbar]]] +ifndef::pdf-theme[[[area-toolbar-0,Toolbar image:playtime::generated/screenshots/elements/area/toolbar-0.png[width=50, pdfwidth=8mm]]]] +== Toolbar + +image::playtime::generated/screenshots/elements/area/toolbar-0.png[Toolbar, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/track-area-0.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/track-area-0.adoc new file mode 100644 index 0000000..a4996c8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/track-area-0.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-track-area-0,Track area]]] +ifndef::pdf-theme[[[area-track-area-0,Track area image:playtime::generated/screenshots/elements/area/track-area-0.png[width=50, pdfwidth=8mm]]]] +== Track area + +image::playtime::generated/screenshots/elements/area/track-area-0.png[Track area, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/track-area-1.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/track-area-1.adoc new file mode 100644 index 0000000..a9808f3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/area/track-area-1.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[area-track-area-1,Track area]]] +ifndef::pdf-theme[[[area-track-area-1,Track area image:playtime::generated/screenshots/elements/area/track-area-1.png[width=50, pdfwidth=8mm]]]] +== Track area + +image::playtime::generated/screenshots/elements/area/track-area-1.png[Track area, role="related thumb right", float=right] + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/append.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/append.adoc new file mode 100644 index 0000000..842c754 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/append.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[arrangement-writing-mode-append,Append]]] +ifndef::pdf-theme[[[arrangement-writing-mode-append,Append image:playtime::generated/screenshots/elements/arrangement-writing-mode/append.png[width=50, pdfwidth=8mm]]]] +== Append + +image::playtime::generated/screenshots/elements/arrangement-writing-mode/append.png[Append, role="related thumb right", float=right] + +Appends the new arrangement to the existing one. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/merge-at-cursor.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/merge-at-cursor.adoc new file mode 100644 index 0000000..f836a23 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/merge-at-cursor.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[arrangement-writing-mode-merge-at-cursor,Merge at cursor]]] +ifndef::pdf-theme[[[arrangement-writing-mode-merge-at-cursor,Merge at cursor image:playtime::generated/screenshots/elements/arrangement-writing-mode/merge-at-cursor.png[width=50, pdfwidth=8mm]]]] +== Merge at cursor + +image::playtime::generated/screenshots/elements/arrangement-writing-mode/merge-at-cursor.png[Merge at cursor, role="related thumb right", float=right] + +Merges the new arrangement into the existing one, starting at the current cursor position. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/replace.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/replace.adoc new file mode 100644 index 0000000..88617c2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/arrangement-writing-mode/replace.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[arrangement-writing-mode-replace,Replace]]] +ifndef::pdf-theme[[[arrangement-writing-mode-replace,Replace image:playtime::generated/screenshots/elements/arrangement-writing-mode/replace.png[width=50, pdfwidth=8mm]]]] +== Replace + +image::playtime::generated/screenshots/elements/arrangement-writing-mode/replace.png[Replace, role="related thumb right", float=right] + +Replaces the existing arrangement and places the new one at the start of the project. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell.adoc new file mode 100644 index 0000000..9bdf100 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-cell,Column cell]]] +ifndef::pdf-theme[[[column-cell,Column cell image:playtime::generated/screenshots/elements/column-cell.png[width=50, pdfwidth=8mm]]]] +== Column cell + +image::playtime::generated/screenshots/elements/column-cell.png[Column cell, role="related thumb right", float=right] + +This cell represents the complete column, including all of its slots. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/exclusive-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/exclusive-mode.adoc new file mode 100644 index 0000000..76d43d1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/exclusive-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[column-cell-exclusive-mode,Exclusive mode]]] +ifndef::pdf-theme[[[column-cell-exclusive-mode,Exclusive mode image:playtime::generated/screenshots/elements/column-cell/exclusive-mode.png[width=50, pdfwidth=8mm]]]] +=== Exclusive mode + +image::playtime::generated/screenshots/elements/column-cell/exclusive-mode.png[Exclusive mode, role="related thumb right", float=right] + +This lights up if the column is in exclusive mode, which means that only one slot at a time will usually be playing. + +You can also click this button to toggle exclusive mode on/off. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/follow-scenes.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/follow-scenes.adoc new file mode 100644 index 0000000..70556f2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/follow-scenes.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-cell-follow-scenes,Follow scenes]]] +ifndef::pdf-theme[[[column-cell-follow-scenes,Follow scenes image:playtime::generated/screenshots/elements/column-cell/follow-scenes.png[width=50, pdfwidth=8mm]]]] +=== Follow scenes + +image::playtime::generated/screenshots/elements/column-cell/follow-scenes.png[Follow scenes, role="related thumb right", float=right] + +This lights up if the column follows scenes. You can also click this button to follow/unfollow. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/has-overrides.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/has-overrides.adoc new file mode 100644 index 0000000..5d6d098 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/has-overrides.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-cell-has-overrides,Has overrides]]] +ifndef::pdf-theme[[[column-cell-has-overrides,Has overrides image:playtime::generated/screenshots/elements/column-cell/has-overrides.png[width=50, pdfwidth=8mm]]]] +=== Has overrides + +image::playtime::generated/screenshots/elements/column-cell/has-overrides.png[Has overrides, role="related thumb right", float=right] + +This lights up if the column overrides at least one matrix setting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/label.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/label.adoc new file mode 100644 index 0000000..541f3ce --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/label.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-cell-label,Column label]]] +ifndef::pdf-theme[[[column-cell-label,Column label image:playtime::generated/screenshots/elements/column-cell/label.png[width=50, pdfwidth=8mm]]]] +=== Column label + +image::playtime::generated/screenshots/elements/column-cell/label.png[Column label, role="related thumb right", float=right] + +Selects this column cell. This makes the inspector show settings related to this column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/stop.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/stop.adoc new file mode 100644 index 0000000..b7d7280 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/column-cell/stop.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[column-cell-stop,Stop column]]] +ifndef::pdf-theme[[[column-cell-stop,Stop column image:playtime::generated/screenshots/elements/column-cell/stop.png[width=50, pdfwidth=8mm]]]] +=== Stop column + +image::playtime::generated/screenshots/elements/column-cell/stop.png[Stop column, role="related thumb right", float=right] + +A single click stops all playing clips in this column according to their stop timing. + +A double click stops all playing clips in this column instantly. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffer-fallback-count.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffer-fallback-count.adoc new file mode 100644 index 0000000..725ccb5 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffer-fallback-count.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[info-panel-stats-pre-buffer-fallback-count,Pre-buffer fallbacks so far]]] +ifndef::pdf-theme[[[info-panel-stats-pre-buffer-fallback-count,Pre-buffer fallbacks so far]]] +=== Pre-buffer fallbacks so far + +image::playtime::generated/screenshots/elements/info-panel/stats/pre-buffer-fallback-count.png[Pre-buffer fallbacks so far] + +The number of times in which any audio clip's dynamic pre-buffer couldn't provide the desired pre-rendered audio content, but the static pre-buffer was able to provide it instead. + +It's normal that this rises when playing audio clips, especially when using "Immediate" start timing. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffer-miss-count.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffer-miss-count.adoc new file mode 100644 index 0000000..bfb65b3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffer-miss-count.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[info-panel-stats-pre-buffer-miss-count,Pre-buffer misses so far]]] +ifndef::pdf-theme[[[info-panel-stats-pre-buffer-miss-count,Pre-buffer misses so far]]] +=== Pre-buffer misses so far + +image::playtime::generated/screenshots/elements/info-panel/stats/pre-buffer-miss-count.png[Pre-buffer misses so far] + +The number of times in which any audio clip's dynamic pre-buffer couldn't provide the desired pre-rendered audio content and even the static pre-buffer failed to provide it. + +If this rises from time to time, it's usually no reason to worry. Often the non-provided audio content lies within the count-in section of a clip, which is most likely silence anyway. However, if this continuously rises, it's not good, and you might experience audio dropouts or even completely silent clips. In this case, increasing REAPER's audio device buffer size and/or decreasing the sample rate might help. You can also try to increase Playtime's tempo latency. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-avg.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-avg.adoc new file mode 100644 index 0000000..123a4b9 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-avg.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[info-panel-stats-pre-buffered-blocks-playing-avg,Pre-buffered blocks playing (avg)]]] +ifndef::pdf-theme[[[info-panel-stats-pre-buffered-blocks-playing-avg,Pre-buffered blocks playing (avg)]]] +=== Pre-buffered blocks playing (avg) + +image::playtime::generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-avg.png[Pre-buffered blocks playing (avg)] + +The average number of pre-buffered blocks among currently playing audio clips. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-max.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-max.adoc new file mode 100644 index 0000000..a5e2550 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-max.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[info-panel-stats-pre-buffered-blocks-playing-max,Pre-buffered blocks playing (max)]]] +ifndef::pdf-theme[[[info-panel-stats-pre-buffered-blocks-playing-max,Pre-buffered blocks playing (max)]]] +=== Pre-buffered blocks playing (max) + +image::playtime::generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-max.png[Pre-buffered blocks playing (max)] + +The largest encountered number of pre-buffered blocks among currently playing audio clips. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-min.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-min.adoc new file mode 100644 index 0000000..6c19dfd --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/pre-buffered-blocks-playing-min.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[info-panel-stats-pre-buffered-blocks-playing-min,Pre-buffered blocks playing (min)]]] +ifndef::pdf-theme[[[info-panel-stats-pre-buffered-blocks-playing-min,Pre-buffered blocks playing (min)]]] +=== Pre-buffered blocks playing (min) + +image::playtime::generated/screenshots/elements/info-panel/stats/pre-buffered-blocks-playing-min.png[Pre-buffered blocks playing (min)] + +The smallest encountered number of pre-buffered blocks among currently playing audio clips. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/tempo-buffer-size.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/tempo-buffer-size.adoc new file mode 100644 index 0000000..8249206 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/stats/tempo-buffer-size.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[info-panel-stats-tempo-buffer-size,Tempo buffer size (in blocks)]]] +ifndef::pdf-theme[[[info-panel-stats-tempo-buffer-size,Tempo buffer size (in blocks)]]] +=== Tempo buffer size (in blocks) + +image::playtime::generated/screenshots/elements/info-panel/stats/tempo-buffer-size.png[Tempo buffer size (in blocks)] + +The current size of the tempo buffer in blocks. + +The tempo buffer size determines how many audio blocks Playtime may look ahead in order to pre-render audio clips with the correct tempo and sample rate. It is influenced by the "Tempo latency" setting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/warnings.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/warnings.adoc new file mode 100644 index 0000000..9845cd0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/info-panel/warnings.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[info-panel-warnings,Warnings]]] +ifndef::pdf-theme[[[info-panel-warnings,Warnings image:playtime::generated/screenshots/elements/info-panel/warnings.png[width=50, pdfwidth=8mm]]]] +== Warnings + +image::playtime::generated/screenshots/elements/info-panel/warnings.png[Warnings, role="related thumb right", float=right] + +The presence of this button indicates potential issues that might or might not affect you. Click the button to learn more. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip.adoc new file mode 100644 index 0000000..41ee4b0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip,Clip]]] +ifndef::pdf-theme[[[inspector-clip,Clip image:playtime::generated/screenshots/elements/inspector/clip.png[width=50, pdfwidth=8mm]]]] +== Clip + +image::playtime::generated/screenshots/elements/inspector/clip.png[Clip, role="related thumb right", float=right] + +This is one of the clips in this slot. Clicking on it will reveal its settings in the area below. Double-clicking on it opens the clip in the waveform view or MIDI editor. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/audio.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/audio.adoc new file mode 100644 index 0000000..f30d41b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/audio.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-audio,Audio ⊖]]] +ifndef::pdf-theme[[[inspector-clip-audio,Audio ⊖]]] +== Audio ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/basics.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/basics.adoc new file mode 100644 index 0000000..e00ed3e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/basics.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-basics,Basics ⊖]]] +ifndef::pdf-theme[[[inspector-clip-basics,Basics ⊖]]] +== Basics ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/color.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/color.adoc new file mode 100644 index 0000000..75fa0a3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/color.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-color,Color]]] +ifndef::pdf-theme[[[inspector-clip-color,Color]]] +=== Color + +image::playtime::generated/screenshots/elements/inspector/clip/color.png[Color] + +Pick a color for this clip from the matrix color palette. + +If you choose the "Auto" color, the clip will be displayed using the same color as the containing track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/downbeat.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/downbeat.adoc new file mode 100644 index 0000000..d790008 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/downbeat.adoc @@ -0,0 +1,12 @@ +ifdef::pdf-theme[[[inspector-clip-downbeat,Downbeat]]] +ifndef::pdf-theme[[[inspector-clip-downbeat,Downbeat]]] +=== Downbeat + +image::playtime::generated/screenshots/elements/inspector/clip/downbeat.png[Downbeat] + +Defines which beat in the material is considered the first downbeat. + +If you set a beat greater than 0, Playtime will shift the clip to the left accordingly so that the downbeat ends up exactly on the chosen play start timing quantization. + +This setting takes effect the next time you trigger this clip with a quantized start timing. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/expert-settings.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/expert-settings.adoc new file mode 100644 index 0000000..bca3b4a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/expert-settings.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-expert-settings,Expert settings ⊖]]] +ifndef::pdf-theme[[[inspector-clip-expert-settings,Expert settings ⊖]]] +== Expert settings ⊖ + + + +It's usually not necessary to touch the settings in this section. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/length.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/length.adoc new file mode 100644 index 0000000..318dad7 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/length.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-length,Length]]] +ifndef::pdf-theme[[[inspector-clip-length,Length]]] +=== Length + +image::playtime::generated/screenshots/elements/inspector/clip/length.png[Length] + +Crops the clip to a specific length, starting from the start position. If the length exceeds the end of the source, Playtime will insert silence. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/looped.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/looped.adoc new file mode 100644 index 0000000..79bb70b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/looped.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-looped,Looped]]] +ifndef::pdf-theme[[[inspector-clip-looped,Looped]]] +=== Looped + +image::playtime::generated/screenshots/elements/inspector/clip/looped.png[Looped] + +If enabled, the clip will loop until you stop it. If disabled, it will become a **One shot**. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-channel-original.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-channel-original.adoc new file mode 100644 index 0000000..34f4169 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-channel-original.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-midi-channel-original,Original]]] +ifndef::pdf-theme[[[inspector-clip-midi-channel-original,Original image:playtime::generated/screenshots/elements/inspector/clip/midi-channel-original.png[width=50, pdfwidth=8mm]]]] +== Original + +image::playtime::generated/screenshots/elements/inspector/clip/midi-channel-original.png[Original, role="related thumb right", float=right] + +Simply keeps the original channel, doesn't apply any channel transformation. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-channel.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-channel.adoc new file mode 100644 index 0000000..d2c4975 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-channel.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-midi-channel,MIDI channel]]] +ifndef::pdf-theme[[[inspector-clip-midi-channel,MIDI channel]]] +=== MIDI channel + +image::playtime::generated/screenshots/elements/inspector/clip/midi-channel.png[MIDI channel] + +Re-route the MIDI events in this clip to a MIDI channel of your choice. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior.adoc new file mode 100644 index 0000000..5cbd70d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-midi-reset-behavior,Reset behavior]]] +ifndef::pdf-theme[[[inspector-clip-midi-reset-behavior,Reset behavior]]] +=== Reset behavior + + + +Choose which MIDI reset messages Playtime should send before and/or after playing the clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/extreme-left.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/extreme-left.adoc new file mode 100644 index 0000000..472ad61 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/extreme-left.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-midi-reset-behavior-extreme-left,Extreme left]]] +ifndef::pdf-theme[[[inspector-clip-midi-reset-behavior-extreme-left,Extreme left image:playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-left.png[width=50, pdfwidth=8mm]]]] +=== Extreme left + +image::playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-left.png[Extreme left, role="related thumb right", float=right] + +Resets whatever it can when the clip starts, often leading to a hard mute of the complete instrument. + +This setting is very likely to interfere with other clips playing the instrument at the same time. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/extreme-right.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/extreme-right.adoc new file mode 100644 index 0000000..5899af9 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/extreme-right.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-midi-reset-behavior-extreme-right,Extreme right]]] +ifndef::pdf-theme[[[inspector-clip-midi-reset-behavior-extreme-right,Extreme right image:playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-right.png[width=50, pdfwidth=8mm]]]] +=== Extreme right + +image::playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/extreme-right.png[Extreme right, role="related thumb right", float=right] + +Resets whatever it can when the clip stops, often leading to a hard mute of the complete instrument. + +This setting is very likely to interfere with other clips playing the instrument at the same time. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/light-left.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/light-left.adoc new file mode 100644 index 0000000..4a7d280 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/light-left.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-midi-reset-behavior-light-left,Light left]]] +ifndef::pdf-theme[[[inspector-clip-midi-reset-behavior-light-left,Light left image:playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-left.png[width=50, pdfwidth=8mm]]]] +=== Light left + +image::playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-left.png[Light left, role="related thumb right", float=right] + +Ends all playing notes and lifts the sustain pedal when the clip starts. Maybe useful for ambient music. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/light-right.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/light-right.adoc new file mode 100644 index 0000000..566675d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/light-right.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-midi-reset-behavior-light-right,Light right]]] +ifndef::pdf-theme[[[inspector-clip-midi-reset-behavior-light-right,Light right image:playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-right.png[width=50, pdfwidth=8mm]]]] +=== Light right + +image::playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/light-right.png[Light right, role="related thumb right", float=right] + +Ends all playing notes and lifts the sustain pedal when the clip stops. A reasonable default. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/none.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/none.adoc new file mode 100644 index 0000000..58e067a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi-reset-behavior/none.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-midi-reset-behavior-none,None]]] +ifndef::pdf-theme[[[inspector-clip-midi-reset-behavior-none,None image:playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/none.png[width=50, pdfwidth=8mm]]]] +=== None + +image::playtime::generated/screenshots/elements/inspector/clip/midi-reset-behavior/none.png[None, role="related thumb right", float=right] + +No reset messages at all. Say hello to hanging notes :D + +Usually not recommended. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi.adoc new file mode 100644 index 0000000..4964937 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/midi.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-midi,MIDI ⊖]]] +ifndef::pdf-theme[[[inspector-clip-midi,MIDI ⊖]]] +== MIDI ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/name.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/name.adoc new file mode 100644 index 0000000..e62fbca --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/name.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-name,Name]]] +ifndef::pdf-theme[[[inspector-clip-name,Name]]] +=== Name + +image::playtime::generated/screenshots/elements/inspector/clip/name.png[Name] + +Give your clip a descriptive name! + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/overdub.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/overdub.adoc new file mode 100644 index 0000000..46789d1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/overdub.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-overdub,Overdub]]] +ifndef::pdf-theme[[[inspector-clip-overdub,Overdub image:playtime::generated/screenshots/elements/inspector/clip/overdub.png[width=50, pdfwidth=8mm]]]] +=== Overdub + +image::playtime::generated/screenshots/elements/inspector/clip/overdub.png[Overdub, role="related thumb right", float=right] + +Starts/stops a MIDI overdub session on this clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/pitch.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/pitch.adoc new file mode 100644 index 0000000..a09f6dc --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/pitch.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-pitch,Pitch]]] +ifndef::pdf-theme[[[inspector-clip-pitch,Pitch]]] +=== Pitch + +image::playtime::generated/screenshots/elements/inspector/clip/pitch.png[Pitch] + +Adjust the pitch of this clip. In case of MIDI, only integers are supported. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/quantize.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/quantize.adoc new file mode 100644 index 0000000..24361f0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/quantize.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-quantize,Quantize]]] +ifndef::pdf-theme[[[inspector-clip-quantize,Quantize image:playtime::generated/screenshots/elements/inspector/clip/quantize.png[width=50, pdfwidth=8mm]]]] +=== Quantize + +image::playtime::generated/screenshots/elements/inspector/clip/quantize.png[Quantize, role="related thumb right", float=right] + +Pressing this will quantize or unquantize the MIDI notes of this clip according to the matrix-wide MIDI quantization grid setting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/quick-actions.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/quick-actions.adoc new file mode 100644 index 0000000..2dbd4b4 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/quick-actions.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-quick-actions,Quick actions]]] +ifndef::pdf-theme[[[inspector-clip-quick-actions,Quick actions]]] +== Quick actions + + + +Provides quick access to popular clip actions. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/resample-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/resample-mode.adoc new file mode 100644 index 0000000..6d79d5e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/resample-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-resample-mode,Resample mode]]] +ifndef::pdf-theme[[[inspector-clip-resample-mode,Resample mode]]] +=== Resample mode + + + +Defines which kind of resampling algorithm to use on audio clips. + +This overrides the resample mode defined in the column or matrix settings. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/section.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/section.adoc new file mode 100644 index 0000000..2f56bda --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/section.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-section,Section ⊖]]] +ifndef::pdf-theme[[[inspector-clip-section,Section ⊖]]] +== Section ⊖ + + + +Here you can define which part of the audio or MIDI clip you want to play. + +You can play with the section start position and length as part of a performance! This distinguishes it from the more permanent "Source cut" settings in the expert section. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-cut-length.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-cut-length.adoc new file mode 100644 index 0000000..4ee01fe --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-cut-length.adoc @@ -0,0 +1,12 @@ +ifdef::pdf-theme[[[inspector-clip-source-cut-length,Source cut: Length]]] +ifndef::pdf-theme[[[inspector-clip-source-cut-length,Source cut: Length]]] +=== Source cut: Length + +image::playtime::generated/screenshots/elements/inspector/clip/source-cut-length.png[Source cut: Length] + +Crops the source material to a specific length, starting from the start position. If the length exceeds the end of the source, Playtime will insert silence. + +This is usually set by Playtime itself only, right after recording an audio clip, in order to obtain a perfectly cut loop that is independent of the actual length of the audio file. + +You may set this yourself, but you should be careful with it as it can easily destroy timing. Using this with MIDI clips is not recommended. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-cut-start.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-cut-start.adoc new file mode 100644 index 0000000..eeda004 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-cut-start.adoc @@ -0,0 +1,12 @@ +ifdef::pdf-theme[[[inspector-clip-source-cut-start,Source cut: Start]]] +ifndef::pdf-theme[[[inspector-clip-source-cut-start,Source cut: Start]]] +=== Source cut: Start + +image::playtime::generated/screenshots/elements/inspector/clip/source-cut-start.png[Source cut: Start] + +Sets the start position within the source material. + +This is usually set by Playtime itself only, right after recording an audio clip, in order to crop away the count-in phase of the recording. + +You may set this yourself, but you should be careful with it as it can easily destroy timing. Using this with MIDI clips is not recommended. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-fades.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-fades.adoc new file mode 100644 index 0000000..7383fe4 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-fades.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-source-fades,Source fades]]] +ifndef::pdf-theme[[[inspector-clip-source-fades,Source fades]]] +=== Source fades + +image::playtime::generated/screenshots/elements/inspector/clip/source-fades.png[Source fades] + +By default, Playtime adds a fade-in/fade-out of just a few milliseconds at the start and the end of an audio clip. It does this in order to make loops sound smooth (= no clicks at the loop bounds). However, sometimes you deal with pre-produced samples that are *already* cut into perfect loops. In that case, you can disable this option to preserve the transients of the original audio material. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-info.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-info.adoc new file mode 100644 index 0000000..f317b8d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/source-info.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-source-info,Source info]]] +ifndef::pdf-theme[[[inspector-clip-source-info,Source info]]] +=== Source info + +image::playtime::generated/screenshots/elements/inspector/clip/source-info.png[Source info] + +Displays information about the source of this clip, e.g. the audio file. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-position.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-position.adoc new file mode 100644 index 0000000..3ef8e68 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-position.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-start-position,Start position]]] +ifndef::pdf-theme[[[inspector-clip-start-position,Start position]]] +=== Start position + +image::playtime::generated/screenshots/elements/inspector/clip/start-position.png[Start position] + +Sets the start position within the clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing.adoc new file mode 100644 index 0000000..c449a01 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-start-timing,Start timing]]] +ifndef::pdf-theme[[[inspector-clip-start-timing,Start timing]]] +=== Start timing + + + +Decides when a clip actually starts playing after you have triggered its playback. + +This overrides the start timing defined in the column or matrix settings. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing/custom.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing/custom.adoc new file mode 100644 index 0000000..21b1b95 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing/custom.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-start-timing-custom,Custom]]] +ifndef::pdf-theme[[[inspector-clip-start-timing-custom,Custom image:playtime::generated/screenshots/elements/inspector/clip/start-timing/custom.png[width=50, pdfwidth=8mm]]]] +=== Custom + +image::playtime::generated/screenshots/elements/inspector/clip/start-timing/custom.png[Custom, role="related thumb right", float=right] + +Use a custom quantization for starting the clip. + +This can only be set in the matrix settings inspector. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing/immediately.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing/immediately.adoc new file mode 100644 index 0000000..6482eb1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/start-timing/immediately.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-start-timing-immediately,Immediately]]] +ifndef::pdf-theme[[[inspector-clip-start-timing-immediately,Immediately image:playtime::generated/screenshots/elements/inspector/clip/start-timing/immediately.png[width=50, pdfwidth=8mm]]]] +=== Immediately + +image::playtime::generated/screenshots/elements/inspector/clip/start-timing/immediately.png[Immediately, role="related thumb right", float=right] + +Plays the clip instantly. Good for finger drumming. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing.adoc new file mode 100644 index 0000000..3326470 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-stop-timing,Stop timing]]] +ifndef::pdf-theme[[[inspector-clip-stop-timing,Stop timing]]] +=== Stop timing + + + +Decides when a clip actually stops playing after you have triggered its stop. + +This overrides the stop timing defined in the column or matrix settings. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/custom.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/custom.adoc new file mode 100644 index 0000000..0deeca2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/custom.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-stop-timing-custom,Custom]]] +ifndef::pdf-theme[[[inspector-clip-stop-timing-custom,Custom image:playtime::generated/screenshots/elements/inspector/clip/stop-timing/custom.png[width=50, pdfwidth=8mm]]]] +=== Custom + +image::playtime::generated/screenshots/elements/inspector/clip/stop-timing/custom.png[Custom, role="related thumb right", float=right] + +Use a custom quantization for stopping the clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/immediately.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/immediately.adoc new file mode 100644 index 0000000..2639076 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/immediately.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-stop-timing-immediately,Immediately]]] +ifndef::pdf-theme[[[inspector-clip-stop-timing-immediately,Immediately image:playtime::generated/screenshots/elements/inspector/clip/stop-timing/immediately.png[width=50, pdfwidth=8mm]]]] +=== Immediately + +image::playtime::generated/screenshots/elements/inspector/clip/stop-timing/immediately.png[Immediately, role="related thumb right", float=right] + +Stops the clip instantly. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/like-start-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/like-start-timing.adoc new file mode 100644 index 0000000..56274bb --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/like-start-timing.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-stop-timing-like-start-timing,Like start timing]]] +ifndef::pdf-theme[[[inspector-clip-stop-timing-like-start-timing,Like start timing image:playtime::generated/screenshots/elements/inspector/clip/stop-timing/like-start-timing.png[width=50, pdfwidth=8mm]]]] +=== Like start timing + +image::playtime::generated/screenshots/elements/inspector/clip/stop-timing/like-start-timing.png[Like start timing, role="related thumb right", float=right] + +Derives the stop timing from the start timing. E.g. if the start timing is "1 bar", the stop timing will also be "1 bar". + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/until-end-of-clip.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/until-end-of-clip.adoc new file mode 100644 index 0000000..7970c7c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/stop-timing/until-end-of-clip.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-stop-timing-until-end-of-clip,Until end of clip]]] +ifndef::pdf-theme[[[inspector-clip-stop-timing-until-end-of-clip,Until end of clip image:playtime::generated/screenshots/elements/inspector/clip/stop-timing/until-end-of-clip.png[width=50, pdfwidth=8mm]]]] +=== Until end of clip + +image::playtime::generated/screenshots/elements/inspector/clip/stop-timing/until-end-of-clip.png[Until end of clip, role="related thumb right", float=right] + +Stops after playing until the end of this clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/sync-to-project-tempo.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/sync-to-project-tempo.adoc new file mode 100644 index 0000000..7ccdc60 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/sync-to-project-tempo.adoc @@ -0,0 +1,9 @@ +ifdef::pdf-theme[[[inspector-clip-sync-to-project-tempo,Sync to project tempo]]] +ifndef::pdf-theme[[[inspector-clip-sync-to-project-tempo,Sync to project tempo]]] +=== Sync to project tempo + +image::playtime::generated/screenshots/elements/inspector/clip/sync-to-project-tempo.png[Sync to project tempo] + +Whether to adjust the playback tempo of the clip to the project tempo. You usually want to have this enabled for rhythmic material. + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/synchronization.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/synchronization.adoc new file mode 100644 index 0000000..b0285ca --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/synchronization.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-synchronization,Synchronization ⊖]]] +ifndef::pdf-theme[[[inspector-clip-synchronization,Synchronization ⊖]]] +== Synchronization ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/tempo.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/tempo.adoc new file mode 100644 index 0000000..da1e46d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/tempo.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-tempo,Tempo]]] +ifndef::pdf-theme[[[inspector-clip-tempo,Tempo]]] +=== Tempo + +image::playtime::generated/screenshots/elements/inspector/clip/tempo.png[Tempo] + +The original tempo in which the audio material has been recorded. + +This setting influences the time stretching factor which Playtime applies to the clip in order to sync it to the project tempo. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-signature.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-signature.adoc new file mode 100644 index 0000000..7d88235 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-signature.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-time-signature,Time signature]]] +ifndef::pdf-theme[[[inspector-clip-time-signature,Time signature]]] +=== Time signature + +image::playtime::generated/screenshots/elements/inspector/clip/time-signature.png[Time signature] + +The original time signature in which the material has been recorded. + +This will be used for display purposes. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode.adoc new file mode 100644 index 0000000..79904d9 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-time-stretch-mode,Time stretch mode]]] +ifndef::pdf-theme[[[inspector-clip-time-stretch-mode,Time stretch mode]]] +=== Time stretch mode + + + +Defines which kind of time stretching / pitch shifting algorithm to use on audio clips. + +This overrides the time stretch mode defined in the column or matrix settings. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode/keeping-pitch.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode/keeping-pitch.adoc new file mode 100644 index 0000000..eb3250a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode/keeping-pitch.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-time-stretch-mode-keeping-pitch,Keeping pitch]]] +ifndef::pdf-theme[[[inspector-clip-time-stretch-mode-keeping-pitch,Keeping pitch image:playtime::generated/screenshots/elements/inspector/clip/time-stretch-mode/keeping-pitch.png[width=50, pdfwidth=8mm]]]] +=== Keeping pitch + +image::playtime::generated/screenshots/elements/inspector/clip/time-stretch-mode/keeping-pitch.png[Keeping pitch, role="related thumb right", float=right] + +Changes tempo while keeping the pitch. This can be quite CPU-intensive. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode/vari-speed.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode/vari-speed.adoc new file mode 100644 index 0000000..01de4bb --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/time-stretch-mode/vari-speed.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-time-stretch-mode-vari-speed,Vari-speed]]] +ifndef::pdf-theme[[[inspector-clip-time-stretch-mode-vari-speed,Vari-speed image:playtime::generated/screenshots/elements/inspector/clip/time-stretch-mode/vari-speed.png[width=50, pdfwidth=8mm]]]] +=== Vari-speed + +image::playtime::generated/screenshots/elements/inspector/clip/time-stretch-mode/vari-speed.png[Vari-speed, role="related thumb right", float=right] + +Changes tempo and pitch together. This uses resampling under the hood (uses much less CPU than real time stretching). + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/triggering.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/triggering.adoc new file mode 100644 index 0000000..d0c2d02 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/triggering.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-triggering,Triggering ⊖]]] +ifndef::pdf-theme[[[inspector-clip-triggering,Triggering ⊖]]] +== Triggering ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/velocity-sensitivity.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/velocity-sensitivity.adoc new file mode 100644 index 0000000..8492adb --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/velocity-sensitivity.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-clip-velocity-sensitivity,Velocity sensitivity]]] +ifndef::pdf-theme[[[inspector-clip-velocity-sensitivity,Velocity sensitivity]]] +=== Velocity sensitivity + +image::playtime::generated/screenshots/elements/inspector/clip/velocity-sensitivity.png[Velocity sensitivity] + +Adjusts how much the velocity of an incoming control message (e.g. MIDI note) affects the clip volume when triggering a slot. + +This overrides the velocity sensitivity defined in the column or matrix settings. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/volume.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/volume.adoc new file mode 100644 index 0000000..b9520e1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/clip/volume.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-volume,Volume]]] +ifndef::pdf-theme[[[inspector-clip-volume,Volume]]] +=== Volume + +image::playtime::generated/screenshots/elements/inspector/clip/volume.png[Volume] + +Set the volume of this clip. In case of MIDI, it will adjust the velocity of played MIDI notes. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/basics.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/basics.adoc new file mode 100644 index 0000000..fcd77ef --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/basics.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-column-basics,Basics ⊖]]] +ifndef::pdf-theme[[[inspector-column-basics,Basics ⊖]]] +== Basics ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/custom-name.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/custom-name.adoc new file mode 100644 index 0000000..9182781 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/custom-name.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-custom-name,Custom name]]] +ifndef::pdf-theme[[[inspector-column-custom-name,Custom name]]] +=== Custom name + +image::playtime::generated/screenshots/elements/inspector/column/custom-name.png[Custom name] + +Makes it possible to give the column a custom name. + +By default, a column inherits the name of its playback track. But if you have multiple columns associated with the same track, assigning custom names can help distinguishing them from each other. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/exclusive-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/exclusive-mode.adoc new file mode 100644 index 0000000..3f66db0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/exclusive-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-exclusive-mode,Exclusive mode]]] +ifndef::pdf-theme[[[inspector-column-exclusive-mode,Exclusive mode]]] +=== Exclusive mode + +image::playtime::generated/screenshots/elements/inspector/column/exclusive-mode.png[Exclusive mode] + +If exclusive mode is enabled, in this column only one slot at a time will usually be playing. That's good for switching between different variations. + +If it's disabled, multiple slots can be playing at the same time. That's great for ad-hoc layering of multiple clips. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/follow-scenes.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/follow-scenes.adoc new file mode 100644 index 0000000..c611c7e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/follow-scenes.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-follow-scenes,Follow scenes]]] +ifndef::pdf-theme[[[inspector-column-follow-scenes,Follow scenes]]] +=== Follow scenes + +image::playtime::generated/screenshots/elements/inspector/column/follow-scenes.png[Follow scenes] + +If the column follows scenes, playing a scene (= pressing the play button of the row) will trigger playback of the corresponding slot in this column. This is great for sketching out parts of a song. + +If the column doesn't follow scenes, it will ignore scene playback. Good for letting some clips play independently of the song structure. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/more.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/more.adoc new file mode 100644 index 0000000..5744802 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/more.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-column-more,More ⊖]]] +ifndef::pdf-theme[[[inspector-column-more,More ⊖]]] +== More ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/origin.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/origin.adoc new file mode 100644 index 0000000..e9169cc --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/origin.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-column-origin,Origin]]] +ifndef::pdf-theme[[[inspector-column-origin,Origin]]] +=== Origin + + + +This decides from where to capture audio input during recording. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/playback-track.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/playback-track.adoc new file mode 100644 index 0000000..12d312e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/playback-track.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-playback-track,Playback track]]] +ifndef::pdf-theme[[[inspector-column-playback-track,Playback track]]] +=== Playback track + +image::playtime::generated/screenshots/elements/inspector/column/playback-track.png[Playback track] + +Choose the REAPER track on which this column plays its clips. + +It's even possible to associate multiple columns with the same playback track! + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/resample-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/resample-mode.adoc new file mode 100644 index 0000000..9eff155 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/resample-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-resample-mode,Resample mode]]] +ifndef::pdf-theme[[[inspector-column-resample-mode,Resample mode]]] +=== Resample mode + + + +Defines which kind of resampling algorithm to use on audio clips. + +This overrides the resample mode (defined in the matrix settings) for all clips in this column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/start-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/start-timing.adoc new file mode 100644 index 0000000..63cbd53 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/start-timing.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-start-timing,Start timing]]] +ifndef::pdf-theme[[[inspector-column-start-timing,Start timing]]] +=== Start timing + + + +Decides when a clip actually starts playing after you have triggered its playback. + +This overrides the start timing (defined in the matrix settings) for all clips in this column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/stop-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/stop-timing.adoc new file mode 100644 index 0000000..30e74be --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/stop-timing.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-stop-timing,Stop timing]]] +ifndef::pdf-theme[[[inspector-column-stop-timing,Stop timing]]] +=== Stop timing + + + +Decides when a clip actually stops playing after you have triggered its stop. + +This overrides the stop timing (defined in the matrix settings) for all clips in this column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/time-stretch-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/time-stretch-mode.adoc new file mode 100644 index 0000000..45bdc67 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/time-stretch-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-time-stretch-mode,Time stretch mode]]] +ifndef::pdf-theme[[[inspector-column-time-stretch-mode,Time stretch mode]]] +=== Time stretch mode + + + +Defines which kind of time stretching / pitch shifting algorithm to use on audio clips. + +This overrides the time stretch mode (defined in the matrix settings) for all clips in this column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode.adoc new file mode 100644 index 0000000..e88c70e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-trigger-mode,Trigger mode]]] +ifndef::pdf-theme[[[inspector-column-trigger-mode,Trigger mode]]] +=== Trigger mode + + + +Choose between different modes of triggering slots. + +This overrides the trigger mode defined in the matrix settings for all clips in this column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/inherit.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/inherit.adoc new file mode 100644 index 0000000..c588b28 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/inherit.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-column-trigger-mode-inherit,Inherit]]] +ifndef::pdf-theme[[[inspector-column-trigger-mode-inherit,Inherit image:playtime::generated/screenshots/elements/inspector/column/trigger-mode/inherit.png[width=50, pdfwidth=8mm]]]] +=== Inherit + +image::playtime::generated/screenshots/elements/inspector/column/trigger-mode/inherit.png[Inherit, role="related thumb right", float=right] + +Inherits the parent setting. No override. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/momentary.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/momentary.adoc new file mode 100644 index 0000000..be3c54e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/momentary.adoc @@ -0,0 +1,9 @@ +ifdef::pdf-theme[[[inspector-column-trigger-mode-momentary,Momentary]]] +ifndef::pdf-theme[[[inspector-column-trigger-mode-momentary,Momentary image:playtime::generated/screenshots/elements/inspector/column/trigger-mode/momentary.png[width=50, pdfwidth=8mm]]]] +=== Momentary + +image::playtime::generated/screenshots/elements/inspector/column/trigger-mode/momentary.png[Momentary, role="related thumb right", float=right] + +Press to play, release to stop. Goes well with immediate start/stop timing, feels weird otherwise. + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/retrigger.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/retrigger.adoc new file mode 100644 index 0000000..8cdce77 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/retrigger.adoc @@ -0,0 +1,9 @@ +ifdef::pdf-theme[[[inspector-column-trigger-mode-retrigger,Retrigger]]] +ifndef::pdf-theme[[[inspector-column-trigger-mode-retrigger,Retrigger image:playtime::generated/screenshots/elements/inspector/column/trigger-mode/retrigger.png[width=50, pdfwidth=8mm]]]] +=== Retrigger + +image::playtime::generated/screenshots/elements/inspector/column/trigger-mode/retrigger.png[Retrigger, role="related thumb right", float=right] + +Press once to play, press again to play from start. + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/toggle.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/toggle.adoc new file mode 100644 index 0000000..f79a85b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/trigger-mode/toggle.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-column-trigger-mode-toggle,Toggle]]] +ifndef::pdf-theme[[[inspector-column-trigger-mode-toggle,Toggle image:playtime::generated/screenshots/elements/inspector/column/trigger-mode/toggle.png[width=50, pdfwidth=8mm]]]] +=== Toggle + +image::playtime::generated/screenshots/elements/inspector/column/trigger-mode/toggle.png[Toggle, role="related thumb right", float=right] + +Press once to play, press again to stop. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/velocity-sensitivity.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/velocity-sensitivity.adoc new file mode 100644 index 0000000..8fbc22b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/column/velocity-sensitivity.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-column-velocity-sensitivity,Velocity sensitivity]]] +ifndef::pdf-theme[[[inspector-column-velocity-sensitivity,Velocity sensitivity]]] +=== Velocity sensitivity + +image::playtime::generated/screenshots/elements/inspector/column/velocity-sensitivity.png[Velocity sensitivity] + +Adjusts how much the velocity of an incoming control message (e.g. MIDI note) affects the clip volume when triggering a slot. + +This overrides the velocity sensitivity (defined in the matrix settings) for all clips in this column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/activate-slot-on-trigger.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/activate-slot-on-trigger.adoc new file mode 100644 index 0000000..953248a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/activate-slot-on-trigger.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-activate-slot-on-trigger,Activate slot on trigger]]] +ifndef::pdf-theme[[[inspector-matrix-activate-slot-on-trigger,Activate slot on trigger]]] +=== Activate slot on trigger + +image::playtime::generated/screenshots/elements/inspector/matrix/activate-slot-on-trigger.png[Activate slot on trigger] + +When slot triggered via controller, also make it the active one. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/limit-recording-length.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/limit-recording-length.adoc new file mode 100644 index 0000000..6665eb2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/limit-recording-length.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-limit-recording-length,Limit recording length]]] +ifndef::pdf-theme[[[inspector-matrix-limit-recording-length,Limit recording length]]] +=== Limit recording length + +image::playtime::generated/screenshots/elements/inspector/matrix/limit-recording-length.png[Limit recording length] + +If enabled, recording a clip will automatically stop after a certain amount of time. + +This is useful if you know in advance that you want to record, let's say, 2 bars only. Then you don't have to reach for the stop button manually. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/midi-quantization-grid.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/midi-quantization-grid.adoc new file mode 100644 index 0000000..8f87b52 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/midi-quantization-grid.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-midi-quantization-grid,MIDI quantization grid]]] +ifndef::pdf-theme[[[inspector-matrix-midi-quantization-grid,MIDI quantization grid]]] +=== MIDI quantization grid + +image::playtime::generated/screenshots/elements/inspector/matrix/midi-quantization-grid.png[MIDI quantization grid] + +Which grid to use when quantizing MIDI clips within Playtime. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/playback-basics.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/playback-basics.adoc new file mode 100644 index 0000000..8f11bc9 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/playback-basics.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-playback-basics,Basics ⊖]]] +ifndef::pdf-theme[[[inspector-matrix-playback-basics,Basics ⊖]]] +== Basics ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/playback-more.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/playback-more.adoc new file mode 100644 index 0000000..5e9e815 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/playback-more.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-playback-more,More ⊖]]] +ifndef::pdf-theme[[[inspector-matrix-playback-more,More ⊖]]] +== More ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-allow-tempo-detection-recording.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-allow-tempo-detection-recording.adoc new file mode 100644 index 0000000..60ab3f2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-allow-tempo-detection-recording.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-allow-tempo-detection-recording,Allow tempo detection recording]]] +ifndef::pdf-theme[[[inspector-matrix-recording-allow-tempo-detection-recording,Allow tempo detection recording]]] +=== Allow tempo detection recording + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-allow-tempo-detection-recording.png[Allow tempo detection recording] + +If this is enabled, recording a clip will initiate a link:https://docs.helgoboss.org/playtime/goto#feature-tempo-detection[tempo detection recording] when playback is stopped and the metronome is off. + +If disabled, it will initiate a count-in recording instead. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-auto-quantize.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-auto-quantize.adoc new file mode 100644 index 0000000..8ffafe4 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-auto-quantize.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-auto-quantize,Auto-quantize]]] +ifndef::pdf-theme[[[inspector-matrix-recording-auto-quantize,Auto-quantize]]] +=== Auto-quantize + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-auto-quantize.png[Auto-quantize] + +If you enable this, Playtime will automatically quantize a newly recorded MIDI clip after it has been recorded. It will do so using the global matrix quantization grid setting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-basics.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-basics.adoc new file mode 100644 index 0000000..aff9491 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-basics.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-basics,Basics ⊖]]] +ifndef::pdf-theme[[[inspector-matrix-recording-basics,Basics ⊖]]] +== Basics ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-detect-downbeat.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-detect-downbeat.adoc new file mode 100644 index 0000000..948c8bb --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-detect-downbeat.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-detect-downbeat,Detect downbeat]]] +ifndef::pdf-theme[[[inspector-matrix-recording-detect-downbeat,Detect downbeat]]] +=== Detect downbeat + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-detect-downbeat.png[Detect downbeat] + +If you enable this, Playtime will record even MIDI notes that you play *before* the scheduled recording start (e.g. before the next bar). It will interpret those notes as pickup beat and adjust the downbeat setting accordingly. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-length.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-length.adoc new file mode 100644 index 0000000..86fdb63 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-length.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-length,Length]]] +ifndef::pdf-theme[[[inspector-matrix-recording-length,Length]]] +=== Length + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-length.png[Length] + +Defines the length to be used when limiting the clip recording length. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-looped.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-looped.adoc new file mode 100644 index 0000000..78e9a71 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-looped.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-looped,Looped]]] +ifndef::pdf-theme[[[inspector-matrix-recording-looped,Looped]]] +=== Looped + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-looped.png[Looped] + +If this is enabled, a clip will immediately start playing after it has been recorded, and it will be looped. + +If disabled, the clip will be stopped after its recording, and it will be a **One shot**. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-midi-reset-behavior.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-midi-reset-behavior.adoc new file mode 100644 index 0000000..c06b722 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-midi-reset-behavior.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-midi-reset-behavior,MIDI reset behavior]]] +ifndef::pdf-theme[[[inspector-matrix-recording-midi-reset-behavior,MIDI reset behavior]]] +=== MIDI reset behavior + + + +MIDI reset behavior that will be assigned to new clips: Recorded clips, imported clips and empty clips created by double-clicking a slot. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-midi.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-midi.adoc new file mode 100644 index 0000000..ec53579 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-midi.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-midi,MIDI ⊖]]] +ifndef::pdf-theme[[[inspector-matrix-recording-midi,MIDI ⊖]]] +== MIDI ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-play-start-timing-override.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-play-start-timing-override.adoc new file mode 100644 index 0000000..87404cf --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-play-start-timing-override.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-play-start-timing-override,Play start timing override]]] +ifndef::pdf-theme[[[inspector-matrix-recording-play-start-timing-override,Play start timing override]]] +=== Play start timing override + + + + +[NOTE] +.Development status: Interested? +==== +With this feature, one could explicitly set the start timing of the resulting clip instead of deriving it from the record start timing. I can't think of any situation where this is practical. Can you? +==== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-play-stop-timing-override.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-play-stop-timing-override.adoc new file mode 100644 index 0000000..b12eb9e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-play-stop-timing-override.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-play-stop-timing-override,Play stop timing override]]] +ifndef::pdf-theme[[[inspector-matrix-recording-play-stop-timing-override,Play stop timing override]]] +=== Play stop timing override + + + + +[NOTE] +.Development status: Interested? +==== +With this feature, one could explicitly set the stop timing of the resulting clip instead of deriving it from the record stop timing. I can't think of any situation where this is practical. Can you? +==== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-settings-for-new-clips.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-settings-for-new-clips.adoc new file mode 100644 index 0000000..1e436da --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-settings-for-new-clips.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-settings-for-new-clips,Settings for new clips ⊖]]] +ifndef::pdf-theme[[[inspector-matrix-recording-settings-for-new-clips,Settings for new clips ⊖]]] +== Settings for new clips ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-start-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-start-timing.adoc new file mode 100644 index 0000000..90af979 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-start-timing.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-start-timing,Start timing]]] +ifndef::pdf-theme[[[inspector-matrix-recording-start-timing,Start timing]]] +=== Start timing + + + + +[NOTE] +.Development status: Planned! +==== +The record start timing determines when to start recording after a recording start has been triggered. +==== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-stop-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-stop-timing.adoc new file mode 100644 index 0000000..1e05c42 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-stop-timing.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-stop-timing,Stop timing]]] +ifndef::pdf-theme[[[inspector-matrix-recording-stop-timing,Stop timing]]] +=== Stop timing + + + + +[NOTE] +.Development status: Interested? +==== +The record stop timing determines when to stop recording after a recording stop has been triggered. In my opinion, this doesn't justify a separate setting, and it should just use the global stop timing from the playback settings. +==== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo.adoc new file mode 100644 index 0000000..5b7e27d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo,Sync to project tempo]]] +ifndef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo,Sync to project tempo]]] +=== Sync to project tempo + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo.png[Sync to project tempo] + +Set whether playback of the recorded clip (after its recording) should synchronize to the project tempo. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/auto.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/auto.adoc new file mode 100644 index 0000000..8758814 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/auto.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo-auto,Auto]]] +ifndef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo-auto,Auto image:playtime::generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/auto.png[width=50, pdfwidth=8mm]]]] +=== Auto + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/auto.png[Auto, role="related thumb right", float=right] + +Derives this option automatically from the start timing. + +Example: If the recording start timing was quantized, it will choose to synchronize to the project tempo. If it was "Immediately", it will not. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/no.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/no.adoc new file mode 100644 index 0000000..fb5ef20 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/no.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo-no,No]]] +ifndef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo-no,No image:playtime::generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/no.png[width=50, pdfwidth=8mm]]]] +=== No + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/no.png[No, role="related thumb right", float=right] + +Doesn't sync to project tempo. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/yes.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/yes.adoc new file mode 100644 index 0000000..e50817f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/recording-sync-to-project-tempo/yes.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo-yes,Yes]]] +ifndef::pdf-theme[[[inspector-matrix-recording-sync-to-project-tempo-yes,Yes image:playtime::generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/yes.png[width=50, pdfwidth=8mm]]]] +=== Yes + +image::playtime::generated/screenshots/elements/inspector/matrix/recording-sync-to-project-tempo/yes.png[Yes, role="related thumb right", float=right] + +Syncs to project tempo. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/resample-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/resample-mode.adoc new file mode 100644 index 0000000..5b870f7 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/resample-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-resample-mode,Resample mode]]] +ifndef::pdf-theme[[[inspector-matrix-resample-mode,Resample mode]]] +=== Resample mode + +image::playtime::generated/screenshots/elements/inspector/matrix/resample-mode.png[Resample mode] + +Defines which kind of resampling algorithm to use on audio clips. + +This setting applies to all clips in the matrix, but it can be overridden at column and clip level. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/start-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/start-timing.adoc new file mode 100644 index 0000000..32cea9a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/start-timing.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-start-timing,Start timing]]] +ifndef::pdf-theme[[[inspector-matrix-start-timing,Start timing]]] +=== Start timing + + + +Decides when a clip actually starts playing after you have triggered its playback. + +This setting applies to all clips in the matrix, but it can be overridden at column and clip level. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/stop-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/stop-timing.adoc new file mode 100644 index 0000000..c859335 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/stop-timing.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-stop-timing,Stop timing]]] +ifndef::pdf-theme[[[inspector-matrix-stop-timing,Stop timing]]] +=== Stop timing + + + +Decides when a clip actually stops playing after you have triggered its stop. + +This setting applies to all clips in the matrix, but it can be overridden at column and clip level. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/tempo-detection-range.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/tempo-detection-range.adoc new file mode 100644 index 0000000..c8344bf --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/tempo-detection-range.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-tempo-detection-range,Tempo detection range]]] +ifndef::pdf-theme[[[inspector-matrix-tempo-detection-range,Tempo detection range]]] +=== Tempo detection range + +image::playtime::generated/screenshots/elements/inspector/matrix/tempo-detection-range.png[Tempo detection range] + +This influences how Playtime detects the tempo of imported or recorded audio material. + +Playtime uses tempo detection if you import an audio clip or if you do tempo detection recordings. It derives the tempo from the length of the material, which should always give an accurate result if the material is already cut. However, it can easily happen that Playtime detects half-time or double-time tempos. This might result in clip playback being half or twice as fast as you want. By restricting the tempo detection range, you can improve the results. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/time-stretch-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/time-stretch-mode.adoc new file mode 100644 index 0000000..3a897b5 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/time-stretch-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-time-stretch-mode,Time stretch mode]]] +ifndef::pdf-theme[[[inspector-matrix-time-stretch-mode,Time stretch mode]]] +=== Time stretch mode + + + +Defines which kind of time stretching / pitch shifting algorithm to use on audio clips. + +This setting applies to all clips in the matrix, but it can be overridden at column and clip level. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/transport-sync.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/transport-sync.adoc new file mode 100644 index 0000000..ffb7475 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/transport-sync.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-matrix-transport-sync,Transport synchronization]]] +ifndef::pdf-theme[[[inspector-matrix-transport-sync,Transport synchronization]]] +=== Transport synchronization + +image::playtime::generated/screenshots/elements/inspector/matrix/transport-sync.png[Transport synchronization] + +**Partial transport synchronization** means that starting playback in Playtime doesn't start playback of REAPER's arrangement. You can click this button to switch to **Full transport synchronization**, then starting playback in Playtime will start REAPER playback as well. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/trigger-mode.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/trigger-mode.adoc new file mode 100644 index 0000000..bae9b08 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/trigger-mode.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-trigger-mode,Trigger mode]]] +ifndef::pdf-theme[[[inspector-matrix-trigger-mode,Trigger mode]]] +=== Trigger mode + + + +Choose between different modes of triggering slots. + +This setting applies to all clips in the matrix, but it can be overridden at column level. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/velocity-sensitivity.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/velocity-sensitivity.adoc new file mode 100644 index 0000000..1bdb63b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/matrix/velocity-sensitivity.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-matrix-velocity-sensitivity,Velocity sensitivity]]] +ifndef::pdf-theme[[[inspector-matrix-velocity-sensitivity,Velocity sensitivity]]] +=== Velocity sensitivity + +image::playtime::generated/screenshots/elements/inspector/matrix/velocity-sensitivity.png[Velocity sensitivity] + +Adjusts how much the velocity of an incoming control message (e.g. MIDI note) affects the clip volume when triggering a slot. + +This setting applies to all clips in the matrix, but it can be overridden at column and clip level. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/basics.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/basics.adoc new file mode 100644 index 0000000..89d529d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/basics.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-row-basics,Basics]]] +ifndef::pdf-theme[[[inspector-row-basics,Basics]]] +== Basics + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/name.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/name.adoc new file mode 100644 index 0000000..41974d9 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/name.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-row-name,Name]]] +ifndef::pdf-theme[[[inspector-row-name,Name]]] +=== Name + +image::playtime::generated/screenshots/elements/inspector/row/name.png[Name] + +Name of the row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/tempo.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/tempo.adoc new file mode 100644 index 0000000..d25196d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/row/tempo.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-row-tempo,Change tempo]]] +ifndef::pdf-theme[[[inspector-row-tempo,Change tempo]]] +=== Change tempo + +image::playtime::generated/screenshots/elements/inspector/row/tempo.png[Change tempo] + +Enable this if you want to change the project tempo when triggering this row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/slot/clip-picker.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/slot/clip-picker.adoc new file mode 100644 index 0000000..3d675a5 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/slot/clip-picker.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[inspector-slot-clip-picker,Clip picker]]] +ifndef::pdf-theme[[[inspector-slot-clip-picker,Clip picker image:playtime::generated/screenshots/elements/inspector/slot/clip-picker.png[width=50, pdfwidth=8mm]]]] +== Clip picker + +image::playtime::generated/screenshots/elements/inspector/slot/clip-picker.png[Clip picker, role="related thumb right", float=right] + +Playtime lets you have *multiple* clips within one slot, which is great for layering clips on top of each other. + +Click on one of the clips to reveal its settings! + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/slot/clip.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/slot/clip.adoc new file mode 100644 index 0000000..6843306 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/slot/clip.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-slot-clip,Clip]]] +ifndef::pdf-theme[[[inspector-slot-clip,Clip image:playtime::generated/screenshots/elements/inspector/slot/clip.png[width=50, pdfwidth=8mm]]]] +=== Clip + +image::playtime::generated/screenshots/elements/inspector/slot/clip.png[Clip, role="related thumb right", float=right] + +This is one of the clips in this slot. Clicking on it will reveal its settings in the area below. Double-clicking on it opens the clip in the waveform view or MIDI editor. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/basics.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/basics.adoc new file mode 100644 index 0000000..8f5a165 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/basics.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-track-basics,Basics]]] +ifndef::pdf-theme[[[inspector-track-basics,Basics]]] +== Basics + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/color.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/color.adoc new file mode 100644 index 0000000..a676259 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/color.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-track-color,Color]]] +ifndef::pdf-theme[[[inspector-track-color,Color]]] +=== Color + +image::playtime::generated/screenshots/elements/inspector/track/color.png[Color] + +Change the color of the REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/name.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/name.adoc new file mode 100644 index 0000000..6783409 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/inspector/track/name.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-track-name,Name]]] +ifndef::pdf-theme[[[inspector-track-name,Name]]] +=== Name + +image::playtime::generated/screenshots/elements/inspector/track/name.png[Name] + +Lets you change the name of the REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell.adoc new file mode 100644 index 0000000..8cb814e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-cell,Matrix cell]]] +ifndef::pdf-theme[[[matrix-cell,Matrix cell image:playtime::generated/screenshots/elements/matrix-cell.png[width=50, pdfwidth=8mm]]]] +== Matrix cell + +image::playtime::generated/screenshots/elements/matrix-cell.png[Matrix cell, role="related thumb right", float=right] + +This cell represents the complete matrix including all of its columns, rows and slots. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell/settings.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell/settings.adoc new file mode 100644 index 0000000..4cf9b64 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell/settings.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-cell-settings,Matrix settings]]] +ifndef::pdf-theme[[[matrix-cell-settings,Matrix settings image:playtime::generated/screenshots/elements/matrix-cell/settings.png[width=50, pdfwidth=8mm]]]] +=== Matrix settings + +image::playtime::generated/screenshots/elements/matrix-cell/settings.png[Matrix settings, role="related thumb right", float=right] + +Selects the matrix settings cell. This makes the inspector show settings related to the complete matrix. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell/stop.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell/stop.adoc new file mode 100644 index 0000000..e737c4f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix-cell/stop.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[matrix-cell-stop,Stop matrix]]] +ifndef::pdf-theme[[[matrix-cell-stop,Stop matrix image:playtime::generated/screenshots/elements/matrix-cell/stop.png[width=50, pdfwidth=8mm]]]] +=== Stop matrix + +image::playtime::generated/screenshots/elements/matrix-cell/stop.png[Stop matrix, role="related thumb right", float=right] + +A single click stops all playing clips according to their stop timing. + +A double click stops all playing clips instantly. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/add-column.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/add-column.adoc new file mode 100644 index 0000000..2ce8c33 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/add-column.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-add-column,Add column]]] +ifndef::pdf-theme[[[matrix-add-column,Add column image:playtime::generated/screenshots/elements/matrix/add-column.png[width=50, pdfwidth=8mm]]]] +=== Add column + +image::playtime::generated/screenshots/elements/matrix/add-column.png[Add column, role="related thumb right", float=right] + +Adds a new column to the matrix and associates it with a newly created REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/add-row.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/add-row.adoc new file mode 100644 index 0000000..77c1d55 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/add-row.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-add-row,Add row]]] +ifndef::pdf-theme[[[matrix-add-row,Add row image:playtime::generated/screenshots/elements/matrix/add-row.png[width=50, pdfwidth=8mm]]]] +=== Add row + +image::playtime::generated/screenshots/elements/matrix/add-row.png[Add row, role="related thumb right", float=right] + +Adds a new row to the matrix + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/audio-template-button.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/audio-template-button.adoc new file mode 100644 index 0000000..d017968 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/audio-template-button.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-audio-template-button,Audio]]] +ifndef::pdf-theme[[[matrix-audio-template-button,Audio image:playtime::generated/screenshots/elements/matrix/audio-template-button.png[width=50, pdfwidth=8mm]]]] +=== Audio + +image::playtime::generated/screenshots/elements/matrix/audio-template-button.png[Audio, role="related thumb right", float=right] + +Creates a simple matrix with all track inputs set to audio channels. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/midi-template-button.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/midi-template-button.adoc new file mode 100644 index 0000000..54c7e46 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/midi-template-button.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-midi-template-button,MIDI]]] +ifndef::pdf-theme[[[matrix-midi-template-button,MIDI image:playtime::generated/screenshots/elements/matrix/midi-template-button.png[width=50, pdfwidth=8mm]]]] +=== MIDI + +image::playtime::generated/screenshots/elements/matrix/midi-template-button.png[MIDI, role="related thumb right", float=right] + +Creates a simple matrix with all track inputs set to MIDI devices. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/mixed-template-button.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/mixed-template-button.adoc new file mode 100644 index 0000000..51ff81b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/mixed-template-button.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-mixed-template-button,Mixed]]] +ifndef::pdf-theme[[[matrix-mixed-template-button,Mixed image:playtime::generated/screenshots/elements/matrix/mixed-template-button.png[width=50, pdfwidth=8mm]]]] +=== Mixed + +image::playtime::generated/screenshots/elements/matrix/mixed-template-button.png[Mixed, role="related thumb right", float=right] + +Creates a simple matrix with half of the track inputs set to audio channels and the other half to MIDI devices. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/scroll-indicator.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/scroll-indicator.adoc new file mode 100644 index 0000000..db73757 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/scroll-indicator.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-scroll-indicator,Scroll indicator]]] +ifndef::pdf-theme[[[matrix-scroll-indicator,Scroll indicator image:playtime::generated/screenshots/elements/matrix/scroll-indicator.png[width=50, pdfwidth=8mm]]]] +=== Scroll indicator + +image::playtime::generated/screenshots/elements/matrix/scroll-indicator.png[Scroll indicator, role="related thumb right", float=right] + +This arrow tells that you are seeing only a part of the matrix and that you can scroll in the indicated direction to see more of it! + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/template-panel.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/template-panel.adoc new file mode 100644 index 0000000..5dbde62 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/matrix/template-panel.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-template-panel,Template panel]]] +ifndef::pdf-theme[[[matrix-template-panel,Template panel]]] +== Template panel + +image::playtime::generated/screenshots/elements/matrix/template-panel.png[Template panel] + +Lets you pick among a bunch of simple matrix templates. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/purchase/verify-license-key.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/purchase/verify-license-key.adoc new file mode 100644 index 0000000..f630999 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/purchase/verify-license-key.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[purchase-verify-license-key,Verify license key!]]] +ifndef::pdf-theme[[[purchase-verify-license-key,Verify license key! image:playtime::generated/screenshots/elements/purchase/verify-license-key.png[width=50, pdfwidth=8mm]]]] +== Verify license key! + +image::playtime::generated/screenshots/elements/purchase/verify-license-key.png[Verify license key!, role="related thumb right", float=right] + +Reads the license key that you have copied to the clipboard and verifies it, unlocking Playtime's full version on success. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell.adoc new file mode 100644 index 0000000..1d89876 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-cell,Row cell]]] +ifndef::pdf-theme[[[row-cell,Row cell image:playtime::generated/screenshots/elements/row-cell.png[width=50, pdfwidth=8mm]]]] +== Row cell + +image::playtime::generated/screenshots/elements/row-cell.png[Row cell, role="related thumb right", float=right] + +This cell represents the complete row, including all of its slots. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell/label.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell/label.adoc new file mode 100644 index 0000000..bb0aa3d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell/label.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-cell-label,Row label]]] +ifndef::pdf-theme[[[row-cell-label,Row label image:playtime::generated/screenshots/elements/row-cell/label.png[width=50, pdfwidth=8mm]]]] +=== Row label + +image::playtime::generated/screenshots/elements/row-cell/label.png[Row label, role="related thumb right", float=right] + +Selects this row cell. This makes the inspector show settings related to this row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell/play-scene.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell/play-scene.adoc new file mode 100644 index 0000000..a90e4fa --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/row-cell/play-scene.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-cell-play-scene,Play scene]]] +ifndef::pdf-theme[[[row-cell-play-scene,Play scene image:playtime::generated/screenshots/elements/row-cell/play-scene.png[width=50, pdfwidth=8mm]]]] +=== Play scene + +image::playtime::generated/screenshots/elements/row-cell/play-scene.png[Play scene, role="related thumb right", float=right] + +Plays all clips in this row whose column follows scenes. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/sequences/delete.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/sequences/delete.adoc new file mode 100644 index 0000000..64495d6 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/sequences/delete.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[sequences-delete,Delete sequence]]] +ifndef::pdf-theme[[[sequences-delete,Delete sequence image:playtime::generated/screenshots/elements/sequences/delete.png[width=50, pdfwidth=8mm]]]] +== Delete sequence + +image::playtime::generated/screenshots/elements/sequences/delete.png[Delete sequence, role="related thumb right", float=right] + +Deletes the sequence. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/sequences/edit.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/sequences/edit.adoc new file mode 100644 index 0000000..24fa859 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/sequences/edit.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[sequences-edit,Edit sequence]]] +ifndef::pdf-theme[[[sequences-edit,Edit sequence image:playtime::generated/screenshots/elements/sequences/edit.png[width=50, pdfwidth=8mm]]]] +== Edit sequence + +image::playtime::generated/screenshots/elements/sequences/edit.png[Edit sequence, role="related thumb right", float=right] + +Edits the sequence. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-color-position.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-color-position.adoc new file mode 100644 index 0000000..24e4bc2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-color-position.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-cell-color-position,Cell color position]]] +ifndef::pdf-theme[[[settings-cell-color-position,Cell color position]]] +=== Cell color position + +image::playtime::generated/screenshots/elements/settings/cell-color-position.png[Cell color position] + +Choose how a slot cell shows the color of its primary clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-height.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-height.adoc new file mode 100644 index 0000000..fe2d40b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-height.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-cell-height,Cell height]]] +ifndef::pdf-theme[[[settings-cell-height,Cell height]]] +=== Cell height + +image::playtime::generated/screenshots/elements/settings/cell-height.png[Cell height] + +Sets the height of all cells. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-progress-position.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-progress-position.adoc new file mode 100644 index 0000000..a92f9ba --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-progress-position.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-cell-progress-position,Cell progress position]]] +ifndef::pdf-theme[[[settings-cell-progress-position,Cell progress position]]] +=== Cell progress position + +image::playtime::generated/screenshots/elements/settings/cell-progress-position.png[Cell progress position] + +Choose how a slot cell shows the playback progress of a clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-width.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-width.adoc new file mode 100644 index 0000000..f375464 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/cell-width.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-cell-width,Cell width]]] +ifndef::pdf-theme[[[settings-cell-width,Cell width]]] +=== Cell width + +image::playtime::generated/screenshots/elements/settings/cell-width.png[Cell width] + +Sets the width of all cells relative to their height. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-cell-selection-as-crosshair.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-cell-selection-as-crosshair.adoc new file mode 100644 index 0000000..6810adc --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-cell-selection-as-crosshair.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-show-cell-selection-as-crosshair,Show cell selection as crosshair]]] +ifndef::pdf-theme[[[settings-show-cell-selection-as-crosshair,Show cell selection as crosshair]]] +=== Show cell selection as crosshair + +image::playtime::generated/screenshots/elements/settings/show-cell-selection-as-crosshair.png[Show cell selection as crosshair] + +If enabled, Playtime will not just highlight the currently selected cells but also all other cells which are in the same row and column. Makes it easier to spot the selected cell. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-clip-activity.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-clip-activity.adoc new file mode 100644 index 0000000..8eda727 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-clip-activity.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-show-clip-activity,Show clip activity]]] +ifndef::pdf-theme[[[settings-show-clip-activity,Show clip activity]]] +=== Show clip activity + +image::playtime::generated/screenshots/elements/settings/show-clip-activity.png[Show clip activity] + +Makes a slot cell light up according to the activity of its clips (e.g. when the clip plays a note). + +Depending on color settings, this will be more or less visible. If the cell background is used for displaying the clip progress, it's not going to make a difference at all. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-column-numbers.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-column-numbers.adoc new file mode 100644 index 0000000..050817f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-column-numbers.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-show-column-numbers,Show column numbers]]] +ifndef::pdf-theme[[[settings-show-column-numbers,Show column numbers]]] +=== Show column numbers + +image::playtime::generated/screenshots/elements/settings/show-column-numbers.png[Show column numbers] + +If enabled, a column number will appear on top of each column. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-control-unit-frames.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-control-unit-frames.adoc new file mode 100644 index 0000000..79585c3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-control-unit-frames.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-show-control-unit-frames,Show control unit frames]]] +ifndef::pdf-theme[[[settings-show-control-unit-frames,Show control unit frames]]] +=== Show control unit frames + +image::playtime::generated/screenshots/elements/settings/show-control-unit-frames.png[Show control unit frames] + +If supported grid controllers are connected, Playtime usually shows which part of the matrix they are currently controlling by showing a colored frame. You can disable that. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-hypothetical-play-cursor.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-hypothetical-play-cursor.adoc new file mode 100644 index 0000000..1012277 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/show-hypothetical-play-cursor.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-show-hypothetical-play-cursor,Show hypothetical play cursor]]] +ifndef::pdf-theme[[[settings-show-hypothetical-play-cursor,Show hypothetical play cursor]]] +=== Show hypothetical play cursor + +image::playtime::generated/screenshots/elements/settings/show-hypothetical-play-cursor.png[Show hypothetical play cursor] + +If enabled, Playtime will also show an additional red cursor in the waveform view that represents the hypothetical playback position. + +This is an expert feature usable for timing and bug analysis and should normally be turned off. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/tempo-latency.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/tempo-latency.adoc new file mode 100644 index 0000000..6620a5c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/settings/tempo-latency.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-tempo-latency,Tempo latency]]] +ifndef::pdf-theme[[[settings-tempo-latency,Tempo latency image:playtime::generated/screenshots/elements/settings/tempo-latency.png[width=50, pdfwidth=8mm]]]] +=== Tempo latency + +image::playtime::generated/screenshots/elements/settings/tempo-latency.png[Tempo latency, role="related thumb right", float=right] + +Adjust how many milliseconds of audio will be pre-buffered ahead of time. + +Higher latencies improve resilience against sudden CPU spikes when playing back audio clips. But they will also increase the time that Playtime needs to respond to ad-hoc tempo changes. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell.adoc new file mode 100644 index 0000000..7646560 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-cell,Slot cell]]] +ifndef::pdf-theme[[[slot-cell,Slot cell image:playtime::generated/screenshots/elements/slot-cell.png[width=50, pdfwidth=8mm]]]] +== Slot cell + +image::playtime::generated/screenshots/elements/slot-cell.png[Slot cell, role="related thumb right", float=right] + +This cell represents a slot in the matrix. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/clip-count.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/clip-count.adoc new file mode 100644 index 0000000..67fac4d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/clip-count.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-cell-clip-count,Number of clips]]] +ifndef::pdf-theme[[[slot-cell-clip-count,Number of clips image:playtime::generated/screenshots/elements/slot-cell/clip-count.png[width=50, pdfwidth=8mm]]]] +=== Number of clips + +image::playtime::generated/screenshots/elements/slot-cell/clip-count.png[Number of clips, role="related thumb right", float=right] + +Shows the number of clips contained in this slot if there's more than one. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/content.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/content.adoc new file mode 100644 index 0000000..c33e633 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/content.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[slot-cell-content,Slot content]]] +ifndef::pdf-theme[[[slot-cell-content,Slot content image:playtime::generated/screenshots/elements/slot-cell/content.png[width=50, pdfwidth=8mm]]]] +=== Slot content + +image::playtime::generated/screenshots/elements/slot-cell/content.png[Slot content, role="related thumb right", float=right] + +Selects this slot cell. This makes the inspector show settings for each clip in this slot. + +A double click on a filled slot cell opens the waveform view or the MIDI editor of the primary clip in this slot. A double click on an empty slot cell creates an empty MIDI clip. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/record.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/record.adoc new file mode 100644 index 0000000..05a12b8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/record.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-cell-record,Record clip]]] +ifndef::pdf-theme[[[slot-cell-record,Record clip image:playtime::generated/screenshots/elements/slot-cell/record.png[width=50, pdfwidth=8mm]]]] +=== Record clip + +image::playtime::generated/screenshots/elements/slot-cell/record.png[Record clip, role="related thumb right", float=right] + +Starts recording a new clip into this slot. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/trigger.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/trigger.adoc new file mode 100644 index 0000000..ce8e7c3 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/slot-cell/trigger.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[slot-cell-trigger,Trigger slot]]] +ifndef::pdf-theme[[[slot-cell-trigger,Trigger slot image:playtime::generated/screenshots/elements/slot-cell/trigger.png[width=50, pdfwidth=8mm]]]] +=== Trigger slot + +image::playtime::generated/screenshots/elements/slot-cell/trigger.png[Trigger slot, role="related thumb right", float=right] + +Plays, retriggers or stops all clips in this slot depending on the trigger behavior configured for the matrix. + +**Stop:** A single click stops all contained clips according to their stop timing. A double click stops all contained clips instantly. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/click-and-tap.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/click-and-tap.adoc new file mode 100644 index 0000000..b2a70aa --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/click-and-tap.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[tempo-settings-click-and-tap,Click and tap]]] +ifndef::pdf-theme[[[tempo-settings-click-and-tap,Click and tap]]] +== Click and tap + + + + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/click-volume.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/click-volume.adoc new file mode 100644 index 0000000..f75a29e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/click-volume.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[tempo-settings-click-volume,Click volume]]] +ifndef::pdf-theme[[[tempo-settings-click-volume,Click volume]]] +=== Click volume + +image::playtime::generated/screenshots/elements/tempo-settings/click-volume.png[Click volume] + +Set the volume of the metronome click. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/count-in.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/count-in.adoc new file mode 100644 index 0000000..55bb0dc --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/count-in.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[tempo-settings-count-in,Count-in]]] +ifndef::pdf-theme[[[tempo-settings-count-in,Count-in]]] +=== Count-in + +image::playtime::generated/screenshots/elements/tempo-settings/count-in.png[Count-in] + +Set the number of count-in bars used when doing count-in recording from stopped state. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/tap-volume.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/tap-volume.adoc new file mode 100644 index 0000000..a5a39b8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/tempo-settings/tap-volume.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[tempo-settings-tap-volume,Tap volume]]] +ifndef::pdf-theme[[[tempo-settings-tap-volume,Tap volume]]] +=== Tap volume + +image::playtime::generated/screenshots/elements/tempo-settings/tap-volume.png[Tap volume] + +Set the volume of the tempo tap sound. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/title-bar/purchase-playtime.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/title-bar/purchase-playtime.adoc new file mode 100644 index 0000000..af8cebd --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/title-bar/purchase-playtime.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[title-bar-purchase-playtime,Purchase & Activate]]] +ifndef::pdf-theme[[[title-bar-purchase-playtime,Purchase & Activate image:playtime::generated/screenshots/elements/title-bar/purchase-playtime.png[width=50, pdfwidth=8mm]]]] +== Purchase & Activate + +image::playtime::generated/screenshots/elements/title-bar/purchase-playtime.png[Purchase & Activate, role="related thumb right", float=right] + +This shows further information on how you can purchase Playtime and lets you enter the license key. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/title-bar/tempo-detection-recording-hint.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/title-bar/tempo-detection-recording-hint.adoc new file mode 100644 index 0000000..1f8ad1a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/title-bar/tempo-detection-recording-hint.adoc @@ -0,0 +1,12 @@ +ifdef::pdf-theme[[[title-bar-tempo-detection-recording-hint,Tempo detection recording hint]]] +ifndef::pdf-theme[[[title-bar-tempo-detection-recording-hint,Tempo detection recording hint image:playtime::generated/screenshots/elements/title-bar/tempo-detection-recording-hint.png[width=50, pdfwidth=8mm]]]] +== Tempo detection recording hint + +image::playtime::generated/screenshots/elements/title-bar/tempo-detection-recording-hint.png[Tempo detection recording hint, role="related thumb right", float=right] + +Playtime is stopped and the metronome is disabled. When you record a clip now, Playtime will initiate a link:https://docs.helgoboss.org/playtime/goto#feature-tempo-detection[tempo detection recording]. + +If you would like to do a count-in recording instead, enable the metronome. + +You can link:https://docs.helgoboss.org/playtime/goto#inspector-matrix-recording-allow-tempo-detection-recording[disable this special behavior] in the matrix recording settings. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/todo/clip.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/todo/clip.adoc new file mode 100644 index 0000000..668bfd5 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/todo/clip.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[todo-clip,Clip]]] +ifndef::pdf-theme[[[todo-clip,Clip image:playtime::generated/screenshots/elements/todo/clip.png[width=50, pdfwidth=8mm]]]] +== Clip + +image::playtime::generated/screenshots/elements/todo/clip.png[Clip, role="related thumb right", float=right] + +This is one of the clips in this slot. Clicking on it will reveal its settings in the area below. Double-clicking on it opens the clip in the waveform view or MIDI editor. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-sequence-picker.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-sequence-picker.adoc new file mode 100644 index 0000000..776dcae --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-sequence-picker.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-matrix-sequence-picker,Matrix sequence picker]]] +ifndef::pdf-theme[[[toolbar-matrix-sequence-picker,Matrix sequence picker image:playtime::generated/screenshots/elements/toolbar/matrix-sequence-picker.png[width=50, pdfwidth=8mm]]]] +=== Matrix sequence picker + +image::playtime::generated/screenshots/elements/toolbar/matrix-sequence-picker.png[Matrix sequence picker, role="related thumb right", float=right] + +Pick the active matrix sequence + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-sequencer.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-sequencer.adoc new file mode 100644 index 0000000..e033df1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-sequencer.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-matrix-sequencer,Matrix sequencer]]] +ifndef::pdf-theme[[[toolbar-matrix-sequencer,Matrix sequencer image:playtime::generated/screenshots/elements/toolbar/matrix-sequencer.png[width=50, pdfwidth=8mm]]]] +== Matrix sequencer + +image::playtime::generated/screenshots/elements/toolbar/matrix-sequencer.png[Matrix sequencer, role="related thumb right", float=right] + +The matrix sequencer lets you record play and stop interactions with the matrix and write them to the arrangement when satisfied. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-start-timing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-start-timing.adoc new file mode 100644 index 0000000..d173153 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/matrix-start-timing.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[toolbar-matrix-start-timing,Matrix start timing]]] +ifndef::pdf-theme[[[toolbar-matrix-start-timing,Matrix start timing image:playtime::generated/screenshots/elements/toolbar/matrix-start-timing.png[width=50, pdfwidth=8mm]]]] +== Matrix start timing + +image::playtime::generated/screenshots/elements/toolbar/matrix-start-timing.png[Matrix start timing, role="related thumb right", float=right] + +Decides when a clip actually starts playing after you have triggered its playback. + +This setting applies to all clips in the matrix, but it can be overridden at column and clip level. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/metronome.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/metronome.adoc new file mode 100644 index 0000000..9255b2f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/metronome.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[toolbar-metronome,Metronome]]] +ifndef::pdf-theme[[[toolbar-metronome,Metronome image:playtime::generated/screenshots/elements/toolbar/metronome.png[width=50, pdfwidth=8mm]]]] +=== Metronome + +image::playtime::generated/screenshots/elements/toolbar/metronome.png[Metronome, role="related thumb right", float=right] + +Toggles the metronome. + +Right-click to open the tempo settings dialog. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/open-ended-recording.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/open-ended-recording.adoc new file mode 100644 index 0000000..a4809eb --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/open-ended-recording.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-open-ended-recording,Open-ended recording]]] +ifndef::pdf-theme[[[toolbar-open-ended-recording,Open-ended recording image:playtime::generated/screenshots/elements/toolbar/open-ended-recording.png[width=50, pdfwidth=8mm]]]] +== Open-ended recording + +image::playtime::generated/screenshots/elements/toolbar/open-ended-recording.png[Open-ended recording, role="related thumb right", float=right] + +Clips recordings are not limited in length. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/pane-toggler.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/pane-toggler.adoc new file mode 100644 index 0000000..48534b7 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/pane-toggler.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-pane-toggler,Pane toggler]]] +ifndef::pdf-theme[[[toolbar-pane-toggler,Pane toggler image:playtime::generated/screenshots/elements/toolbar/pane-toggler.png[width=50, pdfwidth=8mm]]]] +== Pane toggler + +image::playtime::generated/screenshots/elements/toolbar/pane-toggler.png[Pane toggler, role="related thumb right", float=right] + +Shows or hides different areas of Playtime's user interface. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/play-rate.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/play-rate.adoc new file mode 100644 index 0000000..8d0de8f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/play-rate.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-play-rate,Play rate]]] +ifndef::pdf-theme[[[toolbar-play-rate,Play rate image:playtime::generated/screenshots/elements/toolbar/play-rate.png[width=50, pdfwidth=8mm]]]] +=== Play rate + +image::playtime::generated/screenshots/elements/toolbar/play-rate.png[Play rate, role="related thumb right", float=right] + +Controls the project play rate. This is only possible as long as REAPER playback is stopped and no tempo marker exists in the project. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/play-stop-matrix-sequence.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/play-stop-matrix-sequence.adoc new file mode 100644 index 0000000..23d79ca --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/play-stop-matrix-sequence.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-play-stop-matrix-sequence,Play/stop matrix sequence]]] +ifndef::pdf-theme[[[toolbar-play-stop-matrix-sequence,Play/stop matrix sequence image:playtime::generated/screenshots/elements/toolbar/play-stop-matrix-sequence.png[width=50, pdfwidth=8mm]]]] +=== Play/stop matrix sequence + +image::playtime::generated/screenshots/elements/toolbar/play-stop-matrix-sequence.png[Play/stop matrix sequence, role="related thumb right", float=right] + +Plays or stops the currently active matrix sequence + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/record-matrix-sequence.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/record-matrix-sequence.adoc new file mode 100644 index 0000000..0b97d9a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/record-matrix-sequence.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-record-matrix-sequence,Record matrix sequence]]] +ifndef::pdf-theme[[[toolbar-record-matrix-sequence,Record matrix sequence image:playtime::generated/screenshots/elements/toolbar/record-matrix-sequence.png[width=50, pdfwidth=8mm]]]] +=== Record matrix sequence + +image::playtime::generated/screenshots/elements/toolbar/record-matrix-sequence.png[Record matrix sequence, role="related thumb right", float=right] + +Starts recording a new matrix performance + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/show-hide-midi-triggers.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/show-hide-midi-triggers.adoc new file mode 100644 index 0000000..f909a03 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/show-hide-midi-triggers.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-show-hide-midi-triggers,Show/hide MIDI triggers]]] +ifndef::pdf-theme[[[toolbar-show-hide-midi-triggers,Show/hide MIDI triggers image:playtime::generated/screenshots/elements/toolbar/show-hide-midi-triggers.png[width=50, pdfwidth=8mm]]]] +== Show/hide MIDI triggers + +image::playtime::generated/screenshots/elements/toolbar/show-hide-midi-triggers.png[Show/hide MIDI triggers, role="related thumb right", float=right] + +Shows or hides MIDI triggers assigned to cells. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/smart-record.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/smart-record.adoc new file mode 100644 index 0000000..69bfb7c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/smart-record.adoc @@ -0,0 +1,16 @@ +ifdef::pdf-theme[[[toolbar-smart-record,Smart record]]] +ifndef::pdf-theme[[[toolbar-smart-record,Smart record image:playtime::generated/screenshots/elements/toolbar/smart-record.png[width=50, pdfwidth=8mm]]]] +=== Smart record + +image::playtime::generated/screenshots/elements/toolbar/smart-record.png[Smart record, role="related thumb right", float=right] + +This is a convenience button for recording clips without first having to choose a particular matrix slot. + +If nothing is recording yet, this will start recording into the next free slot of the first column whose track is armed. + +If there is an ongoing recording, it will be stopped. + +If playback is stopped, this button is displayed with a pulse animation. This indicates that pressing the button will trigger a count-in recording (if metronome enabled) or tempo detection recording (if metronome disabled). + +Right-clicking the button lets you change various settings related to recording clips. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/start-stop-playback.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/start-stop-playback.adoc new file mode 100644 index 0000000..4c075e8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/start-stop-playback.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[toolbar-start-stop-playback,Start/stop playback]]] +ifndef::pdf-theme[[[toolbar-start-stop-playback,Start/stop playback image:playtime::generated/screenshots/elements/toolbar/start-stop-playback.png[width=50, pdfwidth=8mm]]]] +=== Start/stop playback + +image::playtime::generated/screenshots/elements/toolbar/start-stop-playback.png[Start/stop playback, role="related thumb right", float=right] + +Pressing this button start or stops Playtime playback. + +Stopping playback automatically *ignites* all clips that have been playing. After stopping, you can use the same button to start playback again or use the tap button to count in with a completely new tempo. + +Starting playback also triggers playback of all previously *ignited* clips in the matrix. If you want to permanently stop all ignited clips before playback, simply click the matrix stop button. + + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tap-tempo.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tap-tempo.adoc new file mode 100644 index 0000000..2c83408 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tap-tempo.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-tap-tempo,Tap tempo]]] +ifndef::pdf-theme[[[toolbar-tap-tempo,Tap tempo image:playtime::generated/screenshots/elements/toolbar/tap-tempo.png[width=50, pdfwidth=8mm]]]] +=== Tap tempo + +image::playtime::generated/screenshots/elements/toolbar/tap-tempo.png[Tap tempo, role="related thumb right", float=right] + +Click here repeatedly in order to tap the tempo. This is especially useful when playback is stopped, in which case it lets you conduct a count-in. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tempo-section.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tempo-section.adoc new file mode 100644 index 0000000..84141fd --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tempo-section.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-tempo-section,Tempo section]]] +ifndef::pdf-theme[[[toolbar-tempo-section,Tempo section image:playtime::generated/screenshots/elements/toolbar/tempo-section.png[width=50, pdfwidth=8mm]]]] +== Tempo section + +image::playtime::generated/screenshots/elements/toolbar/tempo-section.png[Tempo section, role="related thumb right", float=right] + +Provides tempo-related controls. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tempo.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tempo.adoc new file mode 100644 index 0000000..3a1b70a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/tempo.adoc @@ -0,0 +1,15 @@ +ifdef::pdf-theme[[[toolbar-tempo,Tempo]]] +ifndef::pdf-theme[[[toolbar-tempo,Tempo image:playtime::generated/screenshots/elements/toolbar/tempo.png[width=50, pdfwidth=8mm]]]] +=== Tempo + +image::playtime::generated/screenshots/elements/toolbar/tempo.png[Tempo, role="related thumb right", float=right] + +Controls the project tempo. This is only possible as long as REAPER playback is stopped and no tempo marker exists in the project. +[NOTE] +.Development status: To be improved +==== +The ability to follow the REAPER tempo envelope might come in a future update. + +Tempo adjustment during REAPER playback is disabled because of potential timing issues. It might be possible to fix this, but it needs a new feature on REAPER side. **Please note**: Using REAPER's own tempo control during project playback is *not* a workaround! It will potentially cause the mentioned timing issues. +==== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/time-signature.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/time-signature.adoc new file mode 100644 index 0000000..8a2617a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/time-signature.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[toolbar-time-signature,Time signature]]] +ifndef::pdf-theme[[[toolbar-time-signature,Time signature image:playtime::generated/screenshots/elements/toolbar/time-signature.png[width=50, pdfwidth=8mm]]]] +=== Time signature + +image::playtime::generated/screenshots/elements/toolbar/time-signature.png[Time signature, role="related thumb right", float=right] + +Displays the project's time signature. +[NOTE] +.Development status: To be improved +==== +Changing the time signature from here is planned. For the time being, you can do it within REAPER itself. +==== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/transport-section.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/transport-section.adoc new file mode 100644 index 0000000..7d7fccf --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/transport-section.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-transport-section,Transport section]]] +ifndef::pdf-theme[[[toolbar-transport-section,Transport section image:playtime::generated/screenshots/elements/toolbar/transport-section.png[width=50, pdfwidth=8mm]]]] +== Transport section + +image::playtime::generated/screenshots/elements/toolbar/transport-section.png[Transport section, role="related thumb right", float=right] + +Provides the Playtime transport controls. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/visual-metronome.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/visual-metronome.adoc new file mode 100644 index 0000000..eb22aab --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/visual-metronome.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-visual-metronome,Visual metronome]]] +ifndef::pdf-theme[[[toolbar-visual-metronome,Visual metronome image:playtime::generated/screenshots/elements/toolbar/visual-metronome.png[width=50, pdfwidth=8mm]]]] +== Visual metronome + +image::playtime::generated/screenshots/elements/toolbar/visual-metronome.png[Visual metronome, role="related thumb right", float=right] + +Shows the current position within the bar in beats. Can also be used for tempo tapping. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/write-to-arrangement.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/write-to-arrangement.adoc new file mode 100644 index 0000000..67dba23 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/toolbar/write-to-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-write-to-arrangement,Write to arrangement]]] +ifndef::pdf-theme[[[toolbar-write-to-arrangement,Write to arrangement image:playtime::generated/screenshots/elements/toolbar/write-to-arrangement.png[width=50, pdfwidth=8mm]]]] +=== Write to arrangement + +image::playtime::generated/screenshots/elements/toolbar/write-to-arrangement.png[Write to arrangement, role="related thumb right", float=right] + +Writes the currently active matrix sequence to REAPER's tracks + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/arm.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/arm.adoc new file mode 100644 index 0000000..cfcc92e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/arm.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-arm,Arm track]]] +ifndef::pdf-theme[[[track-panel-arm,Arm track image:playtime::generated/screenshots/elements/track-panel/arm.png[width=50, pdfwidth=8mm]]]] +=== Arm track + +image::playtime::generated/screenshots/elements/track-panel/arm.png[Arm track, role="related thumb right", float=right] + +Arms this track for recording + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/fx-chain.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/fx-chain.adoc new file mode 100644 index 0000000..79af59f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/fx-chain.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-fx-chain,FX chain]]] +ifndef::pdf-theme[[[track-panel-fx-chain,FX chain image:playtime::generated/screenshots/elements/track-panel/fx-chain.png[width=50, pdfwidth=8mm]]]] +=== FX chain + +image::playtime::generated/screenshots/elements/track-panel/fx-chain.png[FX chain, role="related thumb right", float=right] + +Shows the REAPER FX chain of this track + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-auto.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-auto.adoc new file mode 100644 index 0000000..2a463ea --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-auto.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-input-monitoring-auto,Input monitoring auto]]] +ifndef::pdf-theme[[[track-panel-input-monitoring-auto,Input monitoring auto image:playtime::generated/screenshots/elements/track-panel/input-monitoring-auto.png[width=50, pdfwidth=8mm]]]] +==== Input monitoring auto + +image::playtime::generated/screenshots/elements/track-panel/input-monitoring-auto.png[Input monitoring auto, role="related thumb right", float=right] + +Switches input monitoring to automatic mode. That means it will be on if the track is armed for recording and off if not. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-off.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-off.adoc new file mode 100644 index 0000000..b2bd1a6 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-off.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-input-monitoring-off,Input monitoring off]]] +ifndef::pdf-theme[[[track-panel-input-monitoring-off,Input monitoring off image:playtime::generated/screenshots/elements/track-panel/input-monitoring-off.png[width=50, pdfwidth=8mm]]]] +==== Input monitoring off + +image::playtime::generated/screenshots/elements/track-panel/input-monitoring-off.png[Input monitoring off, role="related thumb right", float=right] + +Switches input monitoring for this track off. That means you will not hear what your play. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-on.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-on.adoc new file mode 100644 index 0000000..169d20b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring-on.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-input-monitoring-on,Input monitoring on]]] +ifndef::pdf-theme[[[track-panel-input-monitoring-on,Input monitoring on image:playtime::generated/screenshots/elements/track-panel/input-monitoring-on.png[width=50, pdfwidth=8mm]]]] +==== Input monitoring on + +image::playtime::generated/screenshots/elements/track-panel/input-monitoring-on.png[Input monitoring on, role="related thumb right", float=right] + +Switches input monitoring for this track on. That means you will hear what you play. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring.adoc new file mode 100644 index 0000000..7b60de8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-monitoring.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-input-monitoring,Input monitoring]]] +ifndef::pdf-theme[[[track-panel-input-monitoring,Input monitoring image:playtime::generated/screenshots/elements/track-panel/input-monitoring.png[width=50, pdfwidth=8mm]]]] +=== Input monitoring + +image::playtime::generated/screenshots/elements/track-panel/input-monitoring.png[Input monitoring, role="related thumb right", float=right] + +Controls if and when the track input signal will be audible. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-type-indicator.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-type-indicator.adoc new file mode 100644 index 0000000..e57c085 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input-type-indicator.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[track-panel-input-type-indicator,Track input type indicator]]] +ifndef::pdf-theme[[[track-panel-input-type-indicator,Track input type indicator image:playtime::generated/screenshots/elements/track-panel/input-type-indicator.png[width=50, pdfwidth=8mm]]]] +=== Track input type indicator + +image::playtime::generated/screenshots/elements/track-panel/input-type-indicator.png[Track input type indicator, role="related thumb right", float=right] + +This icon indicates whether the track reacts to MIDI or audio input. + +This should **not** be confused with a track type! Tracks in REAPER/Playtime don't have any type. A track can always **play** both audio and MIDI clips, no matter what's displayed here. The icon only refers to the current recording input! + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input.adoc new file mode 100644 index 0000000..01e4f1f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/input.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-input,Track input]]] +ifndef::pdf-theme[[[track-panel-input,Track input image:playtime::generated/screenshots/elements/track-panel/input.png[width=50, pdfwidth=8mm]]]] +=== Track input + +image::playtime::generated/screenshots/elements/track-panel/input.png[Track input, role="related thumb right", float=right] + +Pick the hardware input of this track used for recording and input monitoring. This can be a MIDI or audio input. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/instrument-fx.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/instrument-fx.adoc new file mode 100644 index 0000000..cc3cad0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/instrument-fx.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-instrument-fx,Instrument FX]]] +ifndef::pdf-theme[[[track-panel-instrument-fx,Instrument FX image:playtime::generated/screenshots/elements/track-panel/instrument-fx.png[width=50, pdfwidth=8mm]]]] +=== Instrument FX + +image::playtime::generated/screenshots/elements/track-panel/instrument-fx.png[Instrument FX, role="related thumb right", float=right] + +Shows the first instrument FX on this track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/label.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/label.adoc new file mode 100644 index 0000000..270439a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/label.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-label,Track label]]] +ifndef::pdf-theme[[[track-panel-label,Track label image:playtime::generated/screenshots/elements/track-panel/label.png[width=50, pdfwidth=8mm]]]] +=== Track label + +image::playtime::generated/screenshots/elements/track-panel/label.png[Track label, role="related thumb right", float=right] + +Displays the name of the column playback track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/map-to-midi-channel.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/map-to-midi-channel.adoc new file mode 100644 index 0000000..ca8c56b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/map-to-midi-channel.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-map-to-midi-channel,Map to MIDI channel]]] +ifndef::pdf-theme[[[track-panel-map-to-midi-channel,Map to MIDI channel image:playtime::generated/screenshots/elements/track-panel/map-to-midi-channel.png[width=50, pdfwidth=8mm]]]] +=== Map to MIDI channel + +image::playtime::generated/screenshots/elements/track-panel/map-to-midi-channel.png[Map to MIDI channel, role="related thumb right", float=right] + +Route all incoming MIDI events to a specific MIDI channel at record time. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/midi-channel-filter.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/midi-channel-filter.adoc new file mode 100644 index 0000000..2b417ec --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/midi-channel-filter.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-midi-channel-filter,MIDI channel filter]]] +ifndef::pdf-theme[[[track-panel-midi-channel-filter,MIDI channel filter image:playtime::generated/screenshots/elements/track-panel/midi-channel-filter.png[width=50, pdfwidth=8mm]]]] +=== MIDI channel filter + +image::playtime::generated/screenshots/elements/track-panel/midi-channel-filter.png[MIDI channel filter, role="related thumb right", float=right] + +Allows to listen to a specific MIDI channel only. Messages from other MIDI channels will be ignored. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/mute.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/mute.adoc new file mode 100644 index 0000000..6995cd4 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/mute.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-mute,Mute track]]] +ifndef::pdf-theme[[[track-panel-mute,Mute track image:playtime::generated/screenshots/elements/track-panel/mute.png[width=50, pdfwidth=8mm]]]] +=== Mute track + +image::playtime::generated/screenshots/elements/track-panel/mute.png[Mute track, role="related thumb right", float=right] + +Mutes this track + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pan.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pan.adoc new file mode 100644 index 0000000..372a1be --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pan.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-pan,Pan]]] +ifndef::pdf-theme[[[track-panel-pan,Pan image:playtime::generated/screenshots/elements/track-panel/pan.png[width=50, pdfwidth=8mm]]]] +=== Pan + +image::playtime::generated/screenshots/elements/track-panel/pan.png[Pan, role="related thumb right", float=right] + +Changes the track panning. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-critical.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-critical.adoc new file mode 100644 index 0000000..2713e83 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-critical.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[track-panel-pdc-critical,Critical latency: 200ms]]] +ifndef::pdf-theme[[[track-panel-pdc-critical,Critical latency: 200ms image:playtime::generated/screenshots/elements/track-panel/pdc-critical.png[width=50, pdfwidth=8mm]]]] +==== Critical latency: 200ms + +image::playtime::generated/screenshots/elements/track-panel/pdc-critical.png[Critical latency: 200ms, role="related thumb right", float=right] + +This track is affected by a very high latency, which Playtime compensates during playback. Input monitoring will be absolutely terrible because of high delays. Playback will not be optimal. + +Please note that Playtime is a live tool and therefore optimized for low latencies. It will do its best to deal with high latencies, but the experience might be less smooth. Consider turning off the offending FX if you want to use Playtime for jamming! + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-significant.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-significant.adoc new file mode 100644 index 0000000..5128f80 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-significant.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-pdc-significant,Significant latency: 20ms]]] +ifndef::pdf-theme[[[track-panel-pdc-significant,Significant latency: 20ms image:playtime::generated/screenshots/elements/track-panel/pdc-significant.png[width=50, pdfwidth=8mm]]]] +==== Significant latency: 20ms + +image::playtime::generated/screenshots/elements/track-panel/pdc-significant.png[Significant latency: 20ms, role="related thumb right", float=right] + +This track is affected by a significant latency, which Playtime compensates during playback. Input monitoring will probably not be too much fun because of the introduced delay. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-tiny.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-tiny.adoc new file mode 100644 index 0000000..2a6fe14 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/pdc-tiny.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-pdc-tiny,Tiny latency: 5ms]]] +ifndef::pdf-theme[[[track-panel-pdc-tiny,Tiny latency: 5ms image:playtime::generated/screenshots/elements/track-panel/pdc-tiny.png[width=50, pdfwidth=8mm]]]] +==== Tiny latency: 5ms + +image::playtime::generated/screenshots/elements/track-panel/pdc-tiny.png[Tiny latency: 5ms, role="related thumb right", float=right] + +This track is affected by a tiny latency, which Playtime compensates during playback. Input monitoring should be fine. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/routing.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/routing.adoc new file mode 100644 index 0000000..ad0a346 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/routing.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-routing,Track routing]]] +ifndef::pdf-theme[[[track-panel-routing,Track routing image:playtime::generated/screenshots/elements/track-panel/routing.png[width=50, pdfwidth=8mm]]]] +=== Track routing + +image::playtime::generated/screenshots/elements/track-panel/routing.png[Track routing, role="related thumb right", float=right] + +Shows the REAPER routing window of this track + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/solo.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/solo.adoc new file mode 100644 index 0000000..1f9c0f8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/solo.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-solo,Solo track]]] +ifndef::pdf-theme[[[track-panel-solo,Solo track image:playtime::generated/screenshots/elements/track-panel/solo.png[width=50, pdfwidth=8mm]]]] +=== Solo track + +image::playtime::generated/screenshots/elements/track-panel/solo.png[Solo track, role="related thumb right", float=right] + +Soloes this track + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/volume.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/volume.adoc new file mode 100644 index 0000000..f6b78f0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/track-panel/volume.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[track-panel-volume,Track volume]]] +ifndef::pdf-theme[[[track-panel-volume,Track volume]]] +=== Track volume + +image::playtime::generated/screenshots/elements/track-panel/volume.png[Track volume] + +Shows the track peaks and lets you control the track volume. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/waveform-view.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/waveform-view.adoc new file mode 100644 index 0000000..d65a4b5 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/elements/waveform-view.adoc @@ -0,0 +1,13 @@ +ifdef::pdf-theme[[[waveform-view,Waveform view]]] +ifndef::pdf-theme[[[waveform-view,Waveform view image:playtime::generated/screenshots/elements/waveform-view.png[width=50, pdfwidth=8mm]]]] +== Waveform view + +image::playtime::generated/screenshots/elements/waveform-view.png[Waveform view, role="related thumb right", float=right] + +Shows the waveform and the section bounds of a clip. +[NOTE] +.Development status: To be improved +==== +In the future, this will probably be enhanced with features such as zooming and editing section bounds. +==== + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/clip/promote-to-top.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/clip/promote-to-top.adoc new file mode 100644 index 0000000..dc8eb13 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/clip/promote-to-top.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-promote-to-top,Promote clip to top]]] +ifndef::pdf-theme[[[inspector-clip-promote-to-top,Promote clip to top]]] +== Promote clip to top + + + +Makes this clip the primary clip of the slot. That means you will see this one in the matrix. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/clip/remove-from-slot.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/clip/remove-from-slot.adoc new file mode 100644 index 0000000..a7e9d37 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/clip/remove-from-slot.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-remove-from-slot,Remove clip from slot]]] +ifndef::pdf-theme[[[inspector-clip-remove-from-slot,Remove clip from slot]]] +== Remove clip from slot + + + +Removes the clip from this slot. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/duplicate.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/duplicate.adoc new file mode 100644 index 0000000..2a2136b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/duplicate.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-duplicate,Duplicate column]]] +ifndef::pdf-theme[[[column-duplicate,Duplicate column]]] +== Duplicate column + + + +Creates a copy of this column and its associated track and inserts it to the right. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/export-to-arrangement.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/export-to-arrangement.adoc new file mode 100644 index 0000000..1364c6c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/export-to-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-export-to-arrangement,Export column to arrangement]]] +ifndef::pdf-theme[[[column-export-to-arrangement,Export column to arrangement]]] +== Export column to arrangement + + + +Exports all clips in this column to the REAPER arrangement. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/export-to-clipboard-as-lua.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/export-to-clipboard-as-lua.adoc new file mode 100644 index 0000000..b429673 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/export-to-clipboard-as-lua.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-export-to-clipboard-as-lua,Export column to clipboard as Lua]]] +ifndef::pdf-theme[[[column-export-to-clipboard-as-lua,Export column to clipboard as Lua]]] +== Export column to clipboard as Lua + + + +Exports the column to the clipboard as Lua code. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-for-each-selected-track.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-for-each-selected-track.adoc new file mode 100644 index 0000000..b79b2ff --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-for-each-selected-track.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-insert-column-for-each-selected-track,Insert column for each selected track]]] +ifndef::pdf-theme[[[column-insert-column-for-each-selected-track,Insert column for each selected track]]] +== Insert column for each selected track + + + +Inserts new empty columns to the right, one for each selected REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-left.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-left.adoc new file mode 100644 index 0000000..7c4a561 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-left.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-insert-column-left,Insert column left]]] +ifndef::pdf-theme[[[column-insert-column-left,Insert column left]]] +== Insert column left + + + +Inserts a new empty column to the left and associates it with a newly created REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-right.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-right.adoc new file mode 100644 index 0000000..4a296d0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/insert-column-right.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-insert-column-right,Insert column right]]] +ifndef::pdf-theme[[[column-insert-column-right,Insert column right]]] +== Insert column right + + + +Inserts a new empty column to the right and associates it with a newly created REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/remove-keep-track.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/remove-keep-track.adoc new file mode 100644 index 0000000..fea7313 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/remove-keep-track.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-remove-keep-track,Remove column (keep track)]]] +ifndef::pdf-theme[[[column-remove-keep-track,Remove column (keep track)]]] +== Remove column (keep track) + + + +Removes this column but keeps the associated REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/remove.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/remove.adoc new file mode 100644 index 0000000..20ac87d --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/remove.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[column-remove,Remove column]]] +ifndef::pdf-theme[[[column-remove,Remove column]]] +== Remove column + + + +Removes this column, including the associated REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/rename-column-or-track.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/rename-column-or-track.adoc new file mode 100644 index 0000000..c0f6ca1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/column/rename-column-or-track.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[column-rename-column-or-track,Rename column or track...]]] +ifndef::pdf-theme[[[column-rename-column-or-track,Rename column or track...]]] +== Rename column or track... + + + +Renames this column of the associated track. + +If the track associated with this column is not associated with any other column, the track will be renamed. In all other cases, the column itself. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/export-to-arrangement.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/export-to-arrangement.adoc new file mode 100644 index 0000000..3a3d53e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/export-to-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-export-to-arrangement,Export matrix to arrangement]]] +ifndef::pdf-theme[[[matrix-export-to-arrangement,Export matrix to arrangement]]] +== Export matrix to arrangement + + + +Exports all clips in this matrix to the REAPER arrangement. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/export-to-clipboard-as-lua.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/export-to-clipboard-as-lua.adoc new file mode 100644 index 0000000..0db89da --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/export-to-clipboard-as-lua.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-export-to-clipboard-as-lua,Export matrix to clipboard as Lua]]] +ifndef::pdf-theme[[[matrix-export-to-clipboard-as-lua,Export matrix to clipboard as Lua]]] +== Export matrix to clipboard as Lua + + + +Exports the complete matrix to the clipboard as Lua code. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-column-for-each-selected-track.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-column-for-each-selected-track.adoc new file mode 100644 index 0000000..cb6feb2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-column-for-each-selected-track.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-insert-column-for-each-selected-track,Insert column for each selected track]]] +ifndef::pdf-theme[[[matrix-insert-column-for-each-selected-track,Insert column for each selected track]]] +== Insert column for each selected track + + + +Inserts new columns to the right, one for each selected REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-column-right.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-column-right.adoc new file mode 100644 index 0000000..0a600b8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-column-right.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-insert-column-right,Insert column right]]] +ifndef::pdf-theme[[[matrix-insert-column-right,Insert column right]]] +== Insert column right + + + +Inserts a new column to the right and associates it with a newly created REAPER track. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-row-below.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-row-below.adoc new file mode 100644 index 0000000..119866c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/matrix/insert-row-below.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[matrix-insert-row-below,Insert row below]]] +ifndef::pdf-theme[[[matrix-insert-row-below,Insert row below]]] +== Insert row below + + + +Inserts a new row below. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/build-scene-from-currently-playing-clips.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/build-scene-from-currently-playing-clips.adoc new file mode 100644 index 0000000..40bbb20 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/build-scene-from-currently-playing-clips.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-build-scene-from-currently-playing-clips,Build scene from currently playing clips]]] +ifndef::pdf-theme[[[row-build-scene-from-currently-playing-clips,Build scene from currently playing clips]]] +== Build scene from currently playing clips + + + +Takes all clips that are currently playing in scene-following columns and copies them to this row. In other words, it takes a snapshot of what is currently playing and pastes it into this row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/clear.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/clear.adoc new file mode 100644 index 0000000..8721a13 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/clear.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-clear,Clear row]]] +ifndef::pdf-theme[[[row-clear,Clear row]]] +== Clear row + + + +Clears all slots in this row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/copy.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/copy.adoc new file mode 100644 index 0000000..dce0a16 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/copy.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-copy,Copy row]]] +ifndef::pdf-theme[[[row-copy,Copy row]]] +== Copy row + + + +Copies the contents of this row for later pasting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/cut.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/cut.adoc new file mode 100644 index 0000000..24a187b --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/cut.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-cut,Cut row]]] +ifndef::pdf-theme[[[row-cut,Cut row]]] +== Cut row + + + +Cuts the contents of this row for later pasting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/duplicate.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/duplicate.adoc new file mode 100644 index 0000000..ccb50e8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/duplicate.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-duplicate,Duplicate row]]] +ifndef::pdf-theme[[[row-duplicate,Duplicate row]]] +== Duplicate row + + + +Creates a copy of this row and inserts it below. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/export-to-arrangement.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/export-to-arrangement.adoc new file mode 100644 index 0000000..1c9fb09 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/export-to-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-export-to-arrangement,Export row to arrangement]]] +ifndef::pdf-theme[[[row-export-to-arrangement,Export row to arrangement]]] +== Export row to arrangement + + + +Exports all clips in this row to the REAPER arrangement. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/insert-row-above.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/insert-row-above.adoc new file mode 100644 index 0000000..5012862 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/insert-row-above.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-insert-row-above,Insert row above]]] +ifndef::pdf-theme[[[row-insert-row-above,Insert row above]]] +== Insert row above + + + +Inserts a new empty row above. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/insert-row-below.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/insert-row-below.adoc new file mode 100644 index 0000000..d71fd91 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/insert-row-below.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-insert-row-below,Insert row below]]] +ifndef::pdf-theme[[[row-insert-row-below,Insert row below]]] +== Insert row below + + + +Inserts a new empty row below. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/paste.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/paste.adoc new file mode 100644 index 0000000..afbfcb1 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/paste.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-paste,Paste row]]] +ifndef::pdf-theme[[[row-paste,Paste row]]] +== Paste row + + + +Pastes previously copied or cut row contents into this row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/remove.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/remove.adoc new file mode 100644 index 0000000..a3f763c --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/remove.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-remove,Remove row]]] +ifndef::pdf-theme[[[row-remove,Remove row]]] +== Remove row + + + +Removes this row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/rename.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/rename.adoc new file mode 100644 index 0000000..429e215 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/row/rename.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[row-rename,Rename row...]]] +ifndef::pdf-theme[[[row-rename,Rename row...]]] +== Rename row... + + + +Renames this row. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/clear.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/clear.adoc new file mode 100644 index 0000000..fef9f1f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/clear.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-clear,Clear slot]]] +ifndef::pdf-theme[[[slot-clear,Clear slot]]] +== Clear slot + + + +Removes all clips from this slot. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/copy.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/copy.adoc new file mode 100644 index 0000000..1ab3ab4 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/copy.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-copy,Copy slot]]] +ifndef::pdf-theme[[[slot-copy,Copy slot]]] +== Copy slot + + + +Copies the contents of this slot for later pasting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/cut.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/cut.adoc new file mode 100644 index 0000000..2dfd450 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/cut.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-cut,Cut slot]]] +ifndef::pdf-theme[[[slot-cut,Cut slot]]] +== Cut slot + + + +Cuts the contents of this slot for later pasting. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/export-primary-clip-to-arrangement.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/export-primary-clip-to-arrangement.adoc new file mode 100644 index 0000000..1768721 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/export-primary-clip-to-arrangement.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-export-primary-clip-to-arrangement,Export primary clip to arrangement]]] +ifndef::pdf-theme[[[slot-export-primary-clip-to-arrangement,Export primary clip to arrangement]]] +== Export primary clip to arrangement + + + +Exports the primary clip of this slot to the REAPER arrangement. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/export-primary-clip-to-clipboard-as-lua.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/export-primary-clip-to-clipboard-as-lua.adoc new file mode 100644 index 0000000..5fa24e8 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/export-primary-clip-to-clipboard-as-lua.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-export-primary-clip-to-clipboard-as-lua,Export primary clip to clipboard as Lua]]] +ifndef::pdf-theme[[[slot-export-primary-clip-to-clipboard-as-lua,Export primary clip to clipboard as Lua]]] +== Export primary clip to clipboard as Lua + + + +Exports the primary clip of this slot to the clipboard as Lua code. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/import-files.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/import-files.adoc new file mode 100644 index 0000000..b066101 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/import-files.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-import-files,Import file(s)...]]] +ifndef::pdf-theme[[[slot-import-files,Import file(s)...]]] +== Import file(s)... + + + +Lets you pick a file and adds it to this slot as a clip. If you pick more than one file, they will end up in the slots below. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/import-selected-item.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/import-selected-item.adoc new file mode 100644 index 0000000..ab2eac0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/import-selected-item.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-import-selected-item,Import selected item(s) into slot]]] +ifndef::pdf-theme[[[slot-import-selected-item,Import selected item(s) into slot]]] +== Import selected item(s) into slot + + + +Creates a clip that resembles the currently selected REAPER item and adds it to this slot. If you selected more than one item, the corresponding clips will end up in the slots below. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/paste.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/paste.adoc new file mode 100644 index 0000000..97cd90a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/paste.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-paste,Paste slot]]] +ifndef::pdf-theme[[[slot-paste,Paste slot]]] +== Paste slot + + + +Pastes previously copied or cut slot contents into this slot, replacing what is already there. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/rename-primary-clip.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/rename-primary-clip.adoc new file mode 100644 index 0000000..403f4a0 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/slot/rename-primary-clip.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[slot-rename-primary-clip,Rename primary clip...]]] +ifndef::pdf-theme[[[slot-rename-primary-clip,Rename primary clip...]]] +== Rename primary clip... + + + +Renames the primary clip in this slot. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/smart-record/limit-recording-length.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/smart-record/limit-recording-length.adoc new file mode 100644 index 0000000..502234e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/smart-record/limit-recording-length.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[toolbar-smart-record-limit-recording-length,Limit recording length]]] +ifndef::pdf-theme[[[toolbar-smart-record-limit-recording-length,Limit recording length]]] +== Limit recording length + + + +If enabled, recording a clip will automatically stop after a certain amount of time. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/source-info/open-in-media-explorer.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/source-info/open-in-media-explorer.adoc new file mode 100644 index 0000000..465d78e --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/generated/menus/source-info/open-in-media-explorer.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[inspector-clip-source-open-in-media-explorer,Open in media explorer]]] +ifndef::pdf-theme[[[inspector-clip-source-open-in-media-explorer,Open in media explorer]]] +== Open in media explorer + + + +Opens the source file of this clip in the REAPER media explorer. + diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/clip-prop-adjust-in-beats.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/clip-prop-adjust-in-beats.adoc new file mode 100644 index 0000000..6ee4662 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/clip-prop-adjust-in-beats.adoc @@ -0,0 +1 @@ +If the clip is configured to xref:user-interface/inspector/clip.adoc#inspector-clip-sync-to-project-tempo[sync to the project tempo], this value is adjusted in beats, otherwise in seconds. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/content-panel-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/content-panel-desc.adoc new file mode 100644 index 0000000..4b142fe --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/content-panel-desc.adoc @@ -0,0 +1 @@ +This area appears when you double-click an audio clip. At the moment, it only contains a waveform view. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/info-panel-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/info-panel-desc.adoc new file mode 100644 index 0000000..d5b88ce --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/info-panel-desc.adoc @@ -0,0 +1,2 @@ +The area at the bottom right is the _info panel_. +It offers context-sensitive help and displays engine statistics. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/inspector-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/inspector-desc.adoc new file mode 100644 index 0000000..bfc788f --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/inspector-desc.adoc @@ -0,0 +1,3 @@ +The vertical panel on the right side is the _inspector_. +It displays detail settings for the currently selected element in the xref:user-interface/matrix-area.adoc[] or +xref:user-interface/track-area.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/matrix-area-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/matrix-area-desc.adoc new file mode 100644 index 0000000..5014e60 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/matrix-area-desc.adoc @@ -0,0 +1 @@ +The large central area is the _matrix area_. It displays the xref:key-concepts.adoc#matrix[], which is the very core of Playtime. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/nav-bar-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/nav-bar-desc.adoc new file mode 100644 index 0000000..f8994a6 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/nav-bar-desc.adoc @@ -0,0 +1,2 @@ +The vertical bar on the left is the _Navigation bar_. +Its primary function is to navigate between different Helgobox user interface pages. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/title-bar-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/title-bar-desc.adoc new file mode 100644 index 0000000..a40c45a --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/title-bar-desc.adoc @@ -0,0 +1,2 @@ +The bar at the top of the app window is the _Title bar_. +It primarily provides general Helgobox functions that are not specific to Playtime, such as window controls, access to settings, and quick access to the REAPER transport. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/toolbar-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/toolbar-desc.adoc new file mode 100644 index 0000000..cc11cf2 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/toolbar-desc.adoc @@ -0,0 +1 @@ +The bar directly below the window title bar is the Playtime _toolbar_. It provides quick access to essential performance controls, including play/stop, tempo adjustments, and more. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/track-area-desc.adoc b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/track-area-desc.adoc new file mode 100644 index 0000000..73dac12 --- /dev/null +++ b/plugin-reaper-realearn/doc/playtime/modules/ROOT/partials/user-interface/track-area-desc.adoc @@ -0,0 +1,3 @@ +The area at the bottom is the _track area_. +It displays the master output track at the very left, followed by the xref:key-concepts.adoc#column-track[column tracks]. +The tracks are arranged horizontally, mirroring the layout of REAPER's mixer control panel. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn-user-guide.adoc b/plugin-reaper-realearn/doc/realearn-user-guide.adoc new file mode 100644 index 0000000..d52397f --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn-user-guide.adoc @@ -0,0 +1 @@ +The ReaLearn Reference is now available at https://docs.helgoboss.org/realearn. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/README.adoc b/plugin-reaper-realearn/doc/realearn/README.adoc new file mode 100644 index 0000000..3f33efb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/README.adoc @@ -0,0 +1,9 @@ += ReaLearn Reference +:experimental: + +This reference is written in AsciiDoc and follows the directory conventions of the documentation site generator link:https://antora.org/[Antora]. + +A good place to start is link:modules/ROOT/pages/introduction.adoc[]. + +Screenshots are generated on macOS as part of the Helgobox integration test. +After executing the tests via menu:Extensions[Helgobox > Run integration test], they can be found at `DOCUMENTS_DIR/realearn-screenshots` and should then be copied to link:modules/ROOT/images/realearn/screenshots[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/antora.yml b/plugin-reaper-realearn/doc/realearn/antora.yml new file mode 100644 index 0000000..b96f56f --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/antora.yml @@ -0,0 +1,10 @@ +name: realearn +title: ReaLearn Reference +version: ~ +nav: + - modules/ROOT/nav.adoc +start_page: introduction.adoc +asciidoc: + attributes: + # For making menu and button macros work + experimental: true \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/color.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/color.png new file mode 100644 index 0000000..bfd89cd Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/color.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/default-controller-preset.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/default-controller-preset.png new file mode 100644 index 0000000..85f5e23 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/default-controller-preset.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/enabled.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/enabled.png new file mode 100644 index 0000000..342575d Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/enabled.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/main-preset.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/main-preset.png new file mode 100644 index 0000000..385b449 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/main-preset.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/name.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/name.png new file mode 100644 index 0000000..5a5df95 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controller/name.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controllers/add.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controllers/add.png new file mode 100644 index 0000000..4ad3ea9 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controllers/add.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controllers/delete.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controllers/delete.png new file mode 100644 index 0000000..1bf4684 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/elements/settings/controllers/delete.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/compartment-preset-picker.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/compartment-preset-picker.png new file mode 100644 index 0000000..894141e Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/compartment-preset-picker.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/controller.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/controller.png new file mode 100644 index 0000000..5904cd7 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/controller.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/settings.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/settings.png new file mode 100644 index 0000000..1e622a1 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/generated/screenshots/main/settings.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-incremental.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-incremental.d2 new file mode 100644 index 0000000..c9ef31e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-incremental.d2 @@ -0,0 +1,9 @@ +...@includes/declarations.d2 + +direction: down + +...@includes/absolute-control-start.d2 + +fire_mode_result -> control_absolute_incremental_buttons.is_zero_or_out_of_range: pass + +control_absolute_incremental_buttons: @includes/control-absolute-incremental-buttons.d2 diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-make-relative.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-make-relative.d2 new file mode 100644 index 0000000..6fb4660 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-make-relative.d2 @@ -0,0 +1,7 @@ +...@includes/declarations.d2 + +direction: down + +...@includes/absolute-control-start.d2 + +fire_mode_result -> todo: pass \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-normal.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-normal.d2 new file mode 100644 index 0000000..1de6365 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-normal.d2 @@ -0,0 +1,24 @@ +...@includes/declarations.d2 + +direction: down + +...@includes/absolute-control-start.d2 + +fire_mode_result -> pre_process_absolute_value.is_in_source_interval: pass + + +pre_process_absolute_value: @includes/pre-process-absolute-value.d2 +pre_process_absolute_value.source_min_max -> prepare_absolute_value_no_performance_mode.control_transformation + +prepare_absolute_value_no_performance_mode: @includes/prepare-absolute-value-no-performance-mode.d2 +prepare_absolute_value_no_performance_mode.round_target_value-> hit_target_considering_max_jump.does_target_provide_value +prepare_absolute_value_no_performance_mode.value_sequence -> hit_target_considering_max_jump.does_target_provide_value + +hit_target_considering_max_jump: @includes/hit-target-considering-max-jump.d2 +hit_target_considering_max_jump.does_target_provide_value -> target: No (e.g. virtual target) +hit_target_considering_max_jump.which_takeover_mode -> hit_if_changed.is_target_retriggerable: Normal +hit_target_considering_max_jump.takover_mode_result -> hit_if_changed.is_target_retriggerable: pass + + +# Common end +...@includes/hit-if-changed-end.d2 \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-performance.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-performance.d2 new file mode 100644 index 0000000..6fb4660 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-performance.d2 @@ -0,0 +1,7 @@ +...@includes/declarations.d2 + +direction: down + +...@includes/absolute-control-start.d2 + +fire_mode_result -> todo: pass \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-toggle.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-toggle.d2 new file mode 100644 index 0000000..d2f22e6 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-absolute-toggle.d2 @@ -0,0 +1,11 @@ +...@includes/declarations.d2 + +direction: down + +...@includes/absolute-control-start.d2 + +fire_mode_result -> control_absolute_toggle_buttons.is_control_value_zero: pass + +control_absolute_toggle_buttons: @includes/control-absolute-toggle-buttons.d2 +control_absolute_toggle_buttons.choose_zero_or_target_max -> target +control_absolute_toggle_buttons.choose_target_min_or_max -> target \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-relative-make-absolute.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-relative-make-absolute.d2 new file mode 100644 index 0000000..32829ce --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-relative-make-absolute.d2 @@ -0,0 +1,9 @@ +...@includes/declarations.d2 + +direction: down + +direction: down + +...@includes/relative-control-start.d2 + +encoder_filter_result -> todo: pass diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-relative-normal.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-relative-normal.d2 new file mode 100644 index 0000000..adaff32 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/control-flow-relative-normal.d2 @@ -0,0 +1,11 @@ +...@includes/declarations.d2 + +direction: down + +...@includes/relative-control-start.d2 + +encoder_filter_result -> control_relative_normal.is_value_seq_defined: pass + +...@includes/relative-control-end.d2 + +...@includes/hit-if-changed-end.d2 \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/absolute-control-start.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/absolute-control-start.d2 new file mode 100644 index 0000000..6813696 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/absolute-control-start.d2 @@ -0,0 +1,15 @@ +discard1.class: discard + +apply_button_filter_and_fire_mode: { + label: Apply button filter and fire mode +} + +source -> apply_button_filter_and_fire_mode: Absolute control value + +apply_button_filter_and_fire_mode -> fire_mode_result + +fire_mode_result: { + label: Result? + class: decision +} +fire_mode_result -> discard1: ignore {left} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/choose-value-sequence-item-relative.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/choose-value-sequence-item-relative.d2 new file mode 100644 index 0000000..ca5de34 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/choose-value-sequence-item-relative.d2 @@ -0,0 +1,16 @@ +class: container + +discard.class: discard + +choose_value_sequence_item_relative { + label: Choose value in sequence relatively, considering wrap +} + +choose_value_sequence_item_relative -> equals_target_value + +equals_target_value { + label: Same as current target value? + class: decision +} + +equals_target_value -> discard: yes \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-absolute-incremental-buttons.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-absolute-incremental-buttons.d2 new file mode 100644 index 0000000..c9d9c23 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-absolute-incremental-buttons.d2 @@ -0,0 +1,22 @@ +discard1.class: discard + +class: container + +is_zero_or_out_of_range: { + label: Is value zero or out of source min/max range? + class: decision +} +is_zero_or_out_of_range -> discard1: yes +is_zero_or_out_of_range -> is_make_absolute: no +is_make_absolute: { + label: Is make-absolute enabled? + class: decision +} +is_make_absolute -> todo: yes +is_make_absolute -> is_value_seq_defined: no +is_value_seq_defined: { + label: Do we have a value sequence? + class: decision +} +is_value_seq_defined -> todo2: yes +is_value_seq_defined -> todo3: no diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-absolute-toggle-buttons.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-absolute-toggle-buttons.d2 new file mode 100644 index 0000000..10bef0a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-absolute-toggle-buttons.d2 @@ -0,0 +1,19 @@ +class: container + +is_control_value_zero: { + label: "Is control value zero?" + class: decision +} +is_control_value_zero -> discard: yes +is_control_value_zero -> is_target_min_max_equal: no +is_target_min_max_equal: { + label: "Is target min/max equal?" +} +is_target_min_max_equal -> choose_zero_or_target_max: yes +is_target_min_max_equal -> choose_target_min_or_max: no +choose_zero_or_target_max: { + label: "Choose zero or target max" +} +choose_target_min_or_max: { + label: "Choose target min or max" +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-relative-normal.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-relative-normal.d2 new file mode 100644 index 0000000..cd4ea75 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/control-relative-normal.d2 @@ -0,0 +1,60 @@ +class: container + +discard1.class: discard +discard2.class: discard + +is_value_seq_defined: { + label: Do we have a value sequence? + class: decision +} +is_value_seq_defined -> prepare_increment3.apply_speed: yes + +prepare_increment3: @prepare-increment.d2 +prepare_increment3.apply_reverse -> choose_value_sequence_item_relative.choose_value_sequence_item_relative +choose_value_sequence_item_relative: @choose-value-sequence-item-relative.d2 + + +is_value_seq_defined -> which_target_control_type: no +which_target_control_type: { + label: What control type does the target have? + class: decision +} +which_target_control_type -> does_target_provide_value: Absolute continuous +which_target_control_type -> prepare_increment.apply_speed: Absolute discrete +which_target_control_type -> prepare_increment2: Relative or virtual + +prepare_increment2: @prepare-increment.d2 + +prepare_increment: @prepare-increment.d2 + +does_target_provide_value: { + label: Does target provide current value? + class: decision +} +does_target_provide_value -> is_target_retriggerable: no +is_target_retriggerable: { + label: Is target retriggerable? + class: decision +} +is_target_retriggerable -> discard1: no +is_target_retriggerable -> process_negative_speed: yes +does_target_provide_value -> apply_reverse: yes + +apply_reverse: { + label: Reverse if desired +} + +apply_reverse -> apply_step_size_min_max +apply_step_size_min_max: { + label: Apply step size min/max +} + +process_negative_speed: { + label: Process negative speed +} +process_negative_speed -> process_negative_speed_result +process_negative_speed_result: { + label: Result? + class: decision +} +process_negative_speed_result -> discard2: ignore diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/declarations.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/declarations.d2 new file mode 100644 index 0000000..8968410 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/declarations.d2 @@ -0,0 +1,39 @@ +classes: { + start: { + style.fill: "#BDD0D8" + } + end: { + style.fill: "#EEE0C9" + } + decision: { + shape: diamond + width: 100 + } + discard: { + shape: image + label: "" + icon: https://icons.terrastruct.com/essentials%2F037-stop.svg + # icon: ./discard.svg + width: 50 + height: 50 + } + container: { + label: "" + style.fill: "#F1F0E8" + } + portal: { + icon: https://icons.terrastruct.com/essentials%2F036-play%20button.svg + width: 50 + height: 50 + } +} + +source: { + label: Source + class: start +} + +target: { + label: Target + class: end +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/discard.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/discard.svg new file mode 100644 index 0000000..9cb543d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/discard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-if-changed-end.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-if-changed-end.d2 new file mode 100644 index 0000000..c68ffa7 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-if-changed-end.d2 @@ -0,0 +1,3 @@ +hit_if_changed: @hit-if-changed.d2 +hit_if_changed.is_target_retriggerable -> target: yes +hit_if_changed.does_target_have_desired_value -> target: no \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-if-changed.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-if-changed.d2 new file mode 100644 index 0000000..20b99b9 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-if-changed.d2 @@ -0,0 +1,14 @@ +class: container + +discard.class: discard + +is_target_retriggerable -> does_target_have_desired_value: no +does_target_have_desired_value -> discard: yes +is_target_retriggerable: { + label: Is target retriggerable? + class: decision +} +does_target_have_desired_value: { + label: Does target already have the desired value? + class: decision +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-target-considering-max-jump.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-target-considering-max-jump.d2 new file mode 100644 index 0000000..89b74c3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/hit-target-considering-max-jump.d2 @@ -0,0 +1,23 @@ +class: container + +discard1.class: discard + +does_target_provide_value -> which_takeover_mode: Yes (real target) +which_takeover_mode -> takeover_mode: else +takeover_mode -> takover_mode_result +takover_mode_result -> discard1: ignore +takeover_mode: { + label: Apply special takeover mode +} +does_target_provide_value: { + label: Does the target provide a current value? + class: decision +} +which_takeover_mode: { + label: What's the takeover mode? + class: decision +} +takover_mode_result: { + label: Result? + class: decision +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/portal-normal-relative-control.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/portal-normal-relative-control.d2 new file mode 100644 index 0000000..ff34423 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/portal-normal-relative-control.d2 @@ -0,0 +1,4 @@ +portal_normal_relative_control: { + label: Continue with relative control + class: portal +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/pre-process-absolute-value.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/pre-process-absolute-value.d2 new file mode 100644 index 0000000..7ccc1cb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/pre-process-absolute-value.d2 @@ -0,0 +1,23 @@ +class: container + +discard1.class: discard + +is_in_source_interval: { + label: Is source value within Source Min/Max range? + class: decision +} +out_of_range: { + label: Apply out-of-range behavior +} +is_in_source_interval -> out_of_range: Out of range +is_in_source_interval -> source_min_max: Within range +out_of_range -> out_of_range_result +out_of_range_result -> discard1: ignore +out_of_range_result -> source_min_max: pass +source_min_max: { + label: Normalize value to source min/max +} +out_of_range_result: { + label: Result? + class: decision +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/prepare-absolute-value-no-performance-mode.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/prepare-absolute-value-no-performance-mode.d2 new file mode 100644 index 0000000..1f12b37 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/prepare-absolute-value-no-performance-mode.d2 @@ -0,0 +1,36 @@ +class: container + +control_transformation: { + label: Apply EEL control transformation +} +reverse: { + label: Reverse value if desired +} +target_min_max: { + label: Denormalize value to target min/max +} +round_target_value: { + label: Round value if desired +} +value_sequence: { + label: Pick value from sequence +} +control_transformation -> control_transformation_result +control_transformation_result -> reverse: absolute +control_transformation_result -> portal_normal_relative_control: relative +...@portal-normal-relative-control.d2 + +reverse -> is_value_seq_defined +is_value_seq_defined -> value_sequence: yes +is_value_seq_defined -> target_min_max: no +target_min_max -> round_target_value + +control_transformation_result: { + label: Result? + class: decision +} + +is_value_seq_defined: { + label: Do we have a value sequence? + class: decision +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/prepare-increment.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/prepare-increment.d2 new file mode 100644 index 0000000..8a31bb4 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/prepare-increment.d2 @@ -0,0 +1,9 @@ +class: container + +apply_speed: { + label: Apply speed +} +apply_speed -> apply_reverse +apply_reverse: { + label: Reverse if desired +} diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/relative-control-end.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/relative-control-end.d2 new file mode 100644 index 0000000..b687403 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/relative-control-end.d2 @@ -0,0 +1,14 @@ +...@portal-normal-relative-control.d2 +portal_normal_relative_control -> control_relative_normal + +control_relative_normal: @control-relative-normal.d2 +control_relative_normal.prepare_increment.apply_reverse -> apply_increment +control_relative_normal.prepare_increment2.apply_reverse -> target +control_relative_normal.apply_step_size_min_max -> apply_increment +control_relative_normal.process_negative_speed_result -> target: Hit with 100% +control_relative_normal.choose_value_sequence_item_relative.equals_target_value -> target: no + +apply_increment { + label: Apply increment respecting wrap and target min/max +} +apply_increment -> hit_if_changed.is_target_retriggerable \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/relative-control-start.d2 b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/relative-control-start.d2 new file mode 100644 index 0000000..32ea325 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/includes/relative-control-start.d2 @@ -0,0 +1,16 @@ +discard.class: discard + +source -> apply_encoder_filter: Relative control Value + +apply_encoder_filter { + label: Apply encoder filter +} + +apply_encoder_filter -> encoder_filter_result + +encoder_filter_result { + label: Result? + class: decision +} + +encoder_filter_result -> discard: ignore \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-incremental.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-incremental.svg new file mode 100644 index 0000000..298e8e5 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-incremental.svg @@ -0,0 +1,136 @@ + + + + + + + + +Apply button filter and fire modeResult?SourceTargetIs value zero or out of source min/max range?Is make-absolute enabled?todoDo we have a value sequence?todo2todo3 pass Absolute control value yes no ignore yes no yes no + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-make-relative.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-make-relative.svg new file mode 100644 index 0000000..80f16b7 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-make-relative.svg @@ -0,0 +1,125 @@ + + + + + + + + +Apply button filter and fire modetodoResult?SourceTarget pass Absolute control value ignore + + + + + + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-normal.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-normal.svg new file mode 100644 index 0000000..02dafde --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-normal.svg @@ -0,0 +1,160 @@ + + + + + + + + +Apply button filter and fire modeResult?SourceTargetApply EEL control transformationIs source value within Source Min/Max range?Does the target provide a current value?Does target already have the desired value?What's the takeover mode?Reverse value if desiredApply special takeover modeDenormalize value to target min/maxApply out-of-range behaviorResult?Round value if desiredPick value from sequenceNormalize value to source min/maxResult?Result?Continue with relative controlDo we have a value sequence?Is target retriggerable? yes no Yes (real target) pass no yes Absolute control value else ignore Out of range Within range ignore ignore absolute pass relative yes no No (e.g. virtual target) Normal pass + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-performance.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-performance.svg new file mode 100644 index 0000000..80f16b7 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-performance.svg @@ -0,0 +1,125 @@ + + + + + + + + +Apply button filter and fire modetodoResult?SourceTarget pass Absolute control value ignore + + + + + + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-toggle.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-toggle.svg new file mode 100644 index 0000000..f00e609 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-absolute-toggle.svg @@ -0,0 +1,133 @@ + + + + + + + + +Apply button filter and fire modeResult?SourceTargetIs control value zero?discardIs target min/max equal?Choose zero or target maxChoose target min or max pass yes Absolute control value no yes ignore no + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-relative-make-absolute.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-relative-make-absolute.svg new file mode 100644 index 0000000..a57fab4 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-relative-make-absolute.svg @@ -0,0 +1,125 @@ + + + + + + + + +Apply encoder filtertodoResult?SourceTarget Relative control Value pass ignore + + + + + + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-relative-normal.svg b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-relative-normal.svg new file mode 100644 index 0000000..a8db3a1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow/target/control-flow-relative-normal.svg @@ -0,0 +1,161 @@ + + + + + + + + +Continue with relative controlApply encoder filterResult?Apply increment respecting wrap and target min/maxSourceTargetDoes target already have the desired value?Do we have a value sequence?What control type does the target have?Does target provide current value?Is target retriggerable?Is target retriggerable?Process negative speedReverse if desiredApply step size min/maxResult?Apply speedApply speedApply speedChoose value in sequence relatively, considering wrapReverse if desiredReverse if desiredReverse if desiredSame as current target value? Relative control Value yes no pass no yes yes ignore yes Hit with 100% no no Absolute continuous Absolute discrete Relative or virtual no no yes yes ignore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/group-panel.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/group-panel.png new file mode 100644 index 0000000..5ecb74b Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/group-panel.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-bottom.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-bottom.png new file mode 100644 index 0000000..7be4fc2 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-bottom.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-group.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-group.png new file mode 100644 index 0000000..043b212 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-group.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-input-output.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-input-output.png new file mode 100644 index 0000000..8b2a5e0 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-input-output.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-let-through-checkboxes.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-let-through-checkboxes.png new file mode 100644 index 0000000..7c2a5cd Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-let-through-checkboxes.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-mapping-row.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-mapping-row.png new file mode 100644 index 0000000..dfe2713 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-mapping-row.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-mapping-toolbar.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-mapping-toolbar.png new file mode 100644 index 0000000..9d6a323 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-mapping-toolbar.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-menu-bar.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-menu-bar.png new file mode 100644 index 0000000..831e1ee Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-menu-bar.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-notes-button.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-notes-button.png new file mode 100644 index 0000000..9a52123 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-notes-button.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-preset.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-preset.png new file mode 100644 index 0000000..fa81e3b Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-preset.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-show-buttons.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-show-buttons.png new file mode 100644 index 0000000..4954dbf Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel-show-buttons.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel.png new file mode 100644 index 0000000..66ee66e Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/main-panel.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-bottom.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-bottom.png new file mode 100644 index 0000000..45146c1 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-bottom.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-general.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-general.png new file mode 100644 index 0000000..c197860 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-general.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-glue.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-glue.png new file mode 100644 index 0000000..349ba67 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-glue.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-source.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-source.png new file mode 100644 index 0000000..64db08c Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-source.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-target.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-target.png new file mode 100644 index 0000000..61ef877 Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel-target.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel.png b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel.png new file mode 100644 index 0000000..7806a3e Binary files /dev/null and b/plugin-reaper-realearn/doc/realearn/modules/ROOT/images/realearn/screenshots/mapping-panel.png differ diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/nav.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/nav.adoc new file mode 100644 index 0000000..c27375e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/nav.adoc @@ -0,0 +1,148 @@ +* xref:introduction.adoc[] +* xref:installation.adoc[] +* xref:key-concepts.adoc[] +* xref:user-interface.adoc[] +** xref:user-interface/main-panel.adoc[] +*** xref:user-interface/main-panel/input-output-section.adoc[] +*** xref:user-interface/main-panel/menu-bar.adoc[] +*** xref:user-interface/main-panel/let-through-section.adoc[] +*** xref:user-interface/main-panel/show-section.adoc[] +*** xref:user-interface/main-panel/preset-section.adoc[] +*** xref:user-interface/main-panel/mapping-group-section.adoc[] +*** xref:user-interface/main-panel/notes-button.adoc[] +*** xref:user-interface/main-panel/mappings-toolbar.adoc[] +*** xref:user-interface/main-panel/mapping-row.adoc[] +*** xref:user-interface/main-panel/bottom-section.adoc[] +** xref:user-interface/mapping-panel.adoc[] +*** xref:user-interface/mapping-panel/top-section.adoc[] +*** xref:user-interface/mapping-panel/source-section.adoc[] +*** xref:user-interface/mapping-panel/target-section.adoc[] +*** xref:user-interface/mapping-panel/glue-section.adoc[] +*** xref:user-interface/mapping-panel/bottom-section.adoc[] +*** xref:user-interface/mapping-panel/advanced-settings-dialog.adoc[] +** xref:user-interface/settings-dialog.adoc[] +** xref:user-interface/controller-dialog.adoc[] +** xref:user-interface/projection.adoc[] +* xref:sources.adoc[] +** xref:sources/none.adoc[] +** xref:sources/midi.adoc[] +*** xref:sources/midi/cc-value.adoc[] +*** xref:sources/midi/note-velocity.adoc[] +*** xref:sources/midi/note-number.adoc[] +*** xref:sources/midi/pitch-wheel.adoc[] +*** xref:sources/midi/channel-after-touch.adoc[] +*** xref:sources/midi/program-change.adoc[] +*** xref:sources/midi/nrpn-value.adoc[] +*** xref:sources/midi/polyphonic-after-touch.adoc[] +*** xref:sources/midi/midi-clock-tempo.adoc[] +*** xref:sources/midi/midi-clock-transport.adoc[] +*** xref:sources/midi/raw-midi-sysex.adoc[] +*** xref:sources/midi/midi-script.adoc[] +*** xref:sources/midi/display.adoc[] +*** xref:sources/midi/specific-program-change.adoc[] +** xref:sources/osc.adoc[] +** xref:sources/stream-deck.adoc[] +** xref:sources/keyboard.adoc[] +** xref:sources/reaper.adoc[] +*** xref:sources/reaper/midi-device-changes.adoc[] +*** xref:sources/reaper/realearn-unit-start.adoc[] +*** xref:sources/reaper/timer.adoc[] +*** xref:sources/reaper/realearn-parameter.adoc[] +*** xref:sources/reaper/speech.adoc[] +** xref:sources/virtual.adoc[] +* xref:targets.adoc[] +** xref:targets/global.adoc[] +*** xref:targets/global/last-touched.adoc[] +*** xref:targets/global/mouse.adoc[] +*** xref:targets/global/set-automation-mode-override.adoc[] +** xref:targets/project.adoc[] +*** xref:targets/project/any-on.adoc[] +*** xref:targets/project/invoke-reaper-action.adoc[] +*** xref:targets/project/invoke-transport-action.adoc[] +*** xref:targets/project/browse-tracks.adoc[] +*** xref:targets/project/seek.adoc[] +*** xref:targets/project/set-playrate.adoc[] +*** xref:targets/project/set-tempo.adoc[] +** xref:targets/marker-region.adoc[] +*** xref:targets/marker-region/go-to.adoc[] +** xref:targets/track.adoc[] +*** xref:targets/track/track.adoc[] +*** xref:targets/track/arm-disarm.adoc[] +*** xref:targets/track/enable-disable-all-fx.adoc[] +*** xref:targets/track/enable-disable-parent-send.adoc[] +*** xref:targets/track/mute-unmute.adoc[] +*** xref:targets/track/peak.adoc[] +*** xref:targets/track/phase-invert-normal.adoc[] +*** xref:targets/track/select-unselect.adoc[] +*** xref:targets/track/set-automation-mode.adoc[] +*** xref:targets/track/set-monitoring-mode.adoc[] +*** xref:targets/track/set-automation-touch-state.adoc[] +*** xref:targets/track/set-pan.adoc[] +*** xref:targets/track/set-stereo-pan-width.adoc[] +*** xref:targets/track/set-volume.adoc[] +*** xref:targets/track/show-hide.adoc[] +*** xref:targets/track/solo-unsolo.adoc[] +** xref:targets/fx-chain.adoc[] +*** xref:targets/fx-chain/browse-fxs.adoc[] +** xref:targets/fx.adoc[] +*** xref:targets/fx/fx.adoc[] +*** xref:targets/fx/enable-disable.adoc[] +*** xref:targets/fx/set-online-offline.adoc[] +*** xref:targets/fx/load-snapshot.adoc[] +*** xref:targets/fx/browse-presets.adoc[] +*** xref:targets/fx/open-close.adoc[] +** xref:targets/fx-parameter.adoc[] +*** xref:targets/fx-parameter/set-automation-touch-state.adoc[] +*** xref:targets/fx-parameter/set-value.adoc[] +** xref:targets/pot.adoc[] +*** xref:targets/pot/browse-filter-items.adoc[] +*** xref:targets/pot/browse-presets.adoc[] +*** xref:targets/pot/preview-preset.adoc[] +*** xref:targets/pot/load-preset.adoc[] +** xref:targets/send-receive.adoc[] +*** xref:targets/send-receive/automation-mode.adoc[] +*** xref:targets/send-receive/mono-stereo.adoc[] +*** xref:targets/send-receive/mute-unmute.adoc[] +*** xref:targets/send-receive/phase-invert-normal.adoc[] +*** xref:targets/send-receive/set-automation-touch-state.adoc[] +*** xref:targets/send-receive/set-pan.adoc[] +*** xref:targets/send-receive/set-volume.adoc[] +** xref:targets/playtime.adoc[] +*** xref:targets/playtime/slot-management-action.adoc[] +*** xref:targets/playtime/slot-transport-action.adoc[] +*** xref:targets/playtime/slot-seek.adoc[] +*** xref:targets/playtime/slot-volume.adoc[] +*** xref:targets/playtime/column-action.adoc[] +*** xref:targets/playtime/row-action.adoc[] +*** xref:targets/playtime/matrix-action.adoc[] +*** xref:targets/playtime/control-unit-scroll.adoc[] +*** xref:targets/playtime/browse-cells.adoc[] +** xref:targets/midi.adoc[] +*** xref:targets/midi/send-message.adoc[] +** xref:targets/osc.adoc[] +*** xref:targets/osc/send-message.adoc[] +** xref:targets/realearn.adoc[] +*** xref:targets/realearn/dummy.adoc[] +*** xref:targets/realearn/enable-disable-instances.adoc[] +*** xref:targets/realearn/enable-disable-mappings.adoc[] +*** xref:targets/realearn/enable-disable-units.adoc[] +*** xref:targets/realearn/load-mapping-snapshot.adoc[] +*** xref:targets/realearn/modify-mapping.adoc[] +*** xref:targets/realearn/take-mapping-snapshot.adoc[] +*** xref:targets/realearn/browse-group-mappings.adoc[] +** xref:targets/virtual.adoc[] +* xref:further-concepts.adoc[] +** xref:further-concepts/general.adoc[] +** xref:further-concepts/instance.adoc[] +** xref:further-concepts/unit.adoc[] +** xref:further-concepts/compartment.adoc[] +** xref:further-concepts/mapping.adoc[] +** xref:further-concepts/source.adoc[] +** xref:further-concepts/glue.adoc[] +** xref:further-concepts/target.adoc[] +* xref:glue-signal-flow.adoc[] +** xref:glue-signal-flow/control.adoc[] +** xref:glue-signal-flow/feedback.adoc[] +* xref:best-practices.adoc[] +* xref:reaper-actions.adoc[] +* xref:configuration-files.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/best-practices.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/best-practices.adoc new file mode 100644 index 0000000..72df765 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/best-practices.adoc @@ -0,0 +1,106 @@ += Best practices + +[[best-practices-input-output,Best practices for setting input and output]] +== Setting input and output + +Prefer output to a specific device over xref:user-interface/main-panel/input-output-section.adoc#fx-output[]!:: +It's usually better to select a specific output device because sending MIDI feedback to the FX output has drawbacks. ++ +First, it doesn't participate in ReaLearn's multi-unit feedback orchestration. +That means you might experience misbehaving LEDs/faders/displays when using multiple units. ++ +Second, it doesn't work if ReaLearn FX is suspended, e.g. in the following cases: + +* ReaLearn FX is disabled. +* Project is paused and ReaLearn track is not armed. +* ReaLearn FX is on input FX chain and track is not armed. + +[[using-the-controller-compartment]] +== Using the controller compartment + +The xref:key-concepts.adoc#controller-compartment[] lets you describe a xref:key-concepts.adoc#controller[] simply by adding xref:key-concepts.adoc#mapping[mappings]. +When you do that, each xref:key-concepts.adoc#controller-mapping[] represents a xref:key-concepts.adoc#control-element[] on your xref:key-concepts.adoc#controller[], e.g. a xref:further-concepts/mapping.adoc#momentary-button[] or xref:further-concepts/mapping.adoc#fader[]. + +Describing your controller is optional but it brings benefits: + +* You can use the xref:further-concepts/unit.adoc#projection[] feature to project your controller mapping to a smartphone or tablet (link:https://www.youtube.com/watch?v=omuYBznEShk&feature=youtu.be[watch video]). +* You can use xref:further-concepts/compartment.adoc#controller-preset[controller presets] either built-in ones or those made by other users ... and thereby save precious setup time. +Or you can contribute them yourself! +* You can make your xref:key-concepts.adoc#main-mapping[main mappings] independent of the actually used xref:key-concepts.adoc#controller[]. +This is done using xref:further-concepts/source.adoc#virtual-source[virtual sources] and xref:further-concepts/target.adoc#virtual-target[virtual targets]. +* It allows you to give your knobs, buttons etc. descriptive and friendly names instead of just e.g. "CC 15". +* You don't need to learn your xref:key-concepts.adoc#control-element[control elements] again and again. +Although the process of learning an element is easy in ReaLearn, it can take some time in case the xref:further-concepts/source.adoc#midi-source-character[] is not guessed correctly. +Just do it once and be done with it! + +If you want to make ReaLearn "learn" about your nice controller device, all you need to do is to create a suitable controller mapping for each of its control elements. + +Let's first look at the "slow" way to do this - adding and editing each controller mapping one by one: + +. Press the xref:user-interface/main-panel/mappings-toolbar.adoc#add-one[]. +. Learn the xref:key-concepts.adoc#source[] by pressing the xref:user-interface/main-panel/mapping-row.adoc#learn-source[] and touching the control element. +. Press the xref:user-interface/main-panel/mapping-row.adoc#row-edit[]. +. Enter a descriptive name for the xref:key-concepts.adoc#control-element[]. ++ +TIP: This name will appear in many places so you want it to be short, clear and unique! +. Assign a unique xref:further-concepts/target.adoc#virtual-target[]. +** At this point we don't want to assign a xref:further-concepts/target.adoc#real-target[] yet. +The point of xref:further-concepts/compartment.adoc#controller-preset[controller presets] is to make them as reusable as possible, that's why we choose a xref:further-concepts/target.adoc#virtual-target[]. +** In the _Category_ dropdown, choose _Virtual_. +** As _Type_, choose xref:further-concepts/compartment.adoc#virtual-control-element-type[] if your control element is a sort of button (something which you can press) or xref:further-concepts/compartment.adoc#virtual-control-element-type-multi[] in all other cases. +** Use for each xref:key-concepts.adoc#control-element[] a unique combination of xref:further-concepts/compartment.adoc#virtual-control-element-type[] and xref:further-concepts/compartment.adoc#virtual-control-element-id[], starting with number *1* and counting. ++ +TIP: It's okay and desired to have one control element mapped to "Multi 1" and one to "Button 1". +** Just imagine the "8 generic knobs + 8 generic buttons" layout which is typical for lots of popular controllers. +You can easily model that by assigning 8 multis and 8 buttons. +** Maybe you have realized that the xref:user-interface/mapping-panel/glue-section.adoc[] is available for controller mappings as well! +That opens up all kinds of possibilities. +You could for example restrict the target range for a certain control element. +Or make an encoder generally slower or faster. +Or you could simulate a rotary encoder by making two buttons on your controller act as -/+ buttons emitting relative values. +This is possible by mapping them to the same xref:further-concepts/compartment.adoc#virtual-control-element[] in xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[]. + +Before you go ahead and do that for each control element, you might want to check out what this is good for: Navigate back to the xref:key-concepts.adoc#main-compartment[], learn the xref:key-concepts.adoc#source[] of some xref:key-concepts.adoc#main-mapping[] and touch the xref:key-concepts.adoc#control-element[] that you have just mapped: Take note how ReaLearn will assign a xref:further-concepts/source.adoc#virtual-source[] this time, not a xref:sources/midi.adoc[MIDI source]! +It will also display the name of the xref:further-concepts/compartment.adoc#virtual-control-element[] as source label. + +Now, let's say at some point you swap your xref:key-concepts.adoc#controller[] with another one that has a similar layout, all you need to do is to switch the xref:further-concepts/compartment.adoc#controller-preset[] and you are golden! +You have decoupled your xref:key-concepts.adoc#main-mapping[] from the actual xref:key-concepts.adoc#controller[]. +Plus, you can now take full advantage of the xref:further-concepts/unit.adoc#projection[] feature. + +All of this might be a bit of an effort, but it's well worth it! +Plus, there's a way to do this _a lot_ faster by using _batch learning_: + +. Press the xref:user-interface/main-panel/mappings-toolbar.adoc#learn-many[]. +. Choose whether you want to learn all the xref:further-concepts/compartment.adoc#virtual-control-element-type-multi[] elements on your xref:key-concepts.adoc#controller[] or all the xref:further-concepts/compartment.adoc#virtual-control-element-type[] elements. +. Simply touch all relevant xref:key-concepts.adoc#control-element[control elements] in the desired order. +ReaLearn will take care of automatically incrementing the xref:further-concepts/compartment.adoc#virtual-control-element-id[]. +. Press btn:[Stop]. +. Done! +** At this point it's recommended to recheck the learned mappings. +** ReaLearn's xref:further-concepts/source.adoc#midi-source-character[] detection for MIDI CCs is naturally just a guess, so it can be wrong. +If so, just adjust the character in the corresponding xref:user-interface/mapping-panel.adoc[]. + +You can share your preset with other users by sending them to link:mailto:info@helgoboss.org[info@helgoboss.org]. +I will add it to https://github.com/helgoboss/helgobox/tree/master/resources/controller-presets[this +list]. + +== Naming compartment parameters + +Because ReaLearn's xref:further-concepts/compartment.adoc#compartment-parameter[compartment parameters] are freely assignable, they have very generic names by default. +However, as soon as you give them meaning by using them in a specific way, it can be very helpful to give them a name by using the xref:user-interface/main-panel/menu-bar.adoc#compartment-parameters[]. + +[[troubleshooting-luau-import]] +== Troubleshooting Luau import + +The way Luau import works in ReaLearn is: + +. ReaLearn attempts to execute the Luau script in the clipboard. +. ReaLearn attempts to interpret the returned value as ReaLearn API object. +. ReaLearn loads the API object + +If step 1 fails, ReaLearn displays an error messages that hopefully contains a line number. +If step 2 fails, ReaLearn shows a validation error message. + +If importing Luau code fails and the displayed error message is not helpful, you can try xref:user-interface/main-panel/menu-bar.adoc#dry-run-lua-script[]. +This action enables you to just execute step 1 and see the "expanded" result. +This can help to make sense of a possible validation error message in step 2. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/configuration-files.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/configuration-files.adoc new file mode 100644 index 0000000..7246e28 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/configuration-files.adoc @@ -0,0 +1,11 @@ += Configuration files + +In addition to the xref:helgobox::configuration-files.adoc[general configuration files], ReaLearn maintains the following ones: + +`Data/helgoboss/auto-load-configs/fx.json`:: Contains global FX-to-preset links, see xref:further-concepts/unit.adoc#auto-load[] + +`Data/helgoboss/presets/controller`:: Contains preset for the controller compartment + +`Data/helgoboss/presets/main`:: Contains preset for the main compartment + +`Helgoboss/ReaLearn/osc.json`:: Global OSC device configurations, see xref:user-interface/main-panel/input-output-section.adoc#manage-osc-devices[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts.adoc new file mode 100644 index 0000000..2aa1916 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts.adoc @@ -0,0 +1,4 @@ += Further concepts + +This section describes further concepts. +You may or may not need to understand them, it depends on which ReaLearn features you are going to use and how complex your control scenarios are. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/compartment.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/compartment.adoc new file mode 100644 index 0000000..0bfaabc --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/compartment.adoc @@ -0,0 +1,484 @@ += Compartment concepts + +[[compartment-preset]] +== Compartment preset + + +=== Main presets vs. controller presets + +[[main-preset]] +==== Main preset + +The term _main preset_ is just a shortcut for saying "compartment preset in the main compartment". + +The number of factory _main_ presets is relatively small because ReaLearn is all about enabling you to quickly come up with your own customized main mappings. However, a few interesting main presets are available link:https://github.com/helgoboss/helgobox/wiki/ReaLearn-Interesting-Presets[in the Wiki]. + +[[controller-preset]] +==== Controller preset + +The term _controller preset_ is just a shortcut for saying "compartment preset in the controller compartment". + +.Mix and match! +TIP: The big advantage of separating controller presets from main presets is that you can freely combine them on a mix-and-match basis. +This enables you to get out-of-the-box access to all kinds of interesting control scenarios with potentially very different controllers. + +=== Factory preset vs. User preset + +[[factory-compartment-preset]] +==== Factory preset + +Factory presets are built-in compartment presets. + +Characteristics: + +* You can't change them. +* If you want to change a factory preset, you can make a copy of it, either by pressing btn:[Save as...] or by <>. +* It's always possible that factory presets are changed in future ReaLearn versions. ++ +.No worries +NOTE: This is not a problem for existing compartments. A changed preset will *only* be applied to the compartment if you select the preset again! ++ +.Preset stability +TIP: Whenever you like a certain factory preset and want it to remain the same, even if you select it again, it's best if you make your own copy of it. The simplest way to do this is to press btn:[Save as...]. + + +[[user-compartment-preset]] +==== User preset + +User presets are made by users, for example by you. + +* Saving your mappings as a preset is optional. +All controller mappings are saved together with your current ReaLearn unit anyway, no worries. +But as soon as you want to reuse these mappings in other ReaLearn unit or for xref:further-concepts/unit.adoc#auto-load[], it makes of course sense to save them as a preset! +* All of your presets end up in the REAPER resource directory (REAPER → Options → Show REAPER resource path in explorer/finder) at +`Data/helgoboss/realearn/presets` followed by `main` (for main compartment presets) or `controller` (for controller compartment presets). +They are JSON files and very similar to what you get when you press +_Export to clipboard_. +* They can even be in a subdirectory. +Please note that the subdirectory name becomes a part of the preset ID, so better don't move existing presets around if you want preset references of existing ReaLearn units to stay intact. +* JSON files can also contain <>. + +[#writing-presets-with-luau] +=== Writing presets with Luau + +It is possible to write compartment presets with the link:https://luau.org/[Luau language] instead of building them via the user interface. +Many of the more complex ReaLearn factory presets are written in Lua, e.g. the "DAW control" preset. + +A good way to get started writing Luau presets is to create your personal compartment preset user workspace. + +A preset workspace is a subdirectory within the compartment preset parent directory that may contain a bunch of presets and other files. + +Important facts about preset workspaces/namespaces: + +* It may contain both Luau presets (`.preset.luau`) and conventional JSON presets (`.json`)! +* The name of the workspace (subdirectory) is at the same time the first part of the preset ID. +For example, if the subdirectory name is `helgoboss` and it contains a preset file `my-preset.json`, the final ID of that preset will be `helgoboss/my-preset`. +* That also means that presets from different workspaces never conflict with each other. +* Therefore, a preset "workspace" is at the same time a preset "namespace". +Those terms are sometimes used interchangeably. +* It's important that the ID of a preset doesn't change, especially if you want to use that preset with xref:further-concepts/unit.adoc#auto-load[]. +If you change the ID, it's another preset from ReaLearn's perspective! +* Conversely, the name of the workspace directory and the name/path of the preset file within the workspace directory should not change! +* The only thing that is allowed to change is the file extension. +This makes it possible to convert a preset from JSON to Luau and vice versa. +* Preset workspaces are self-contained. +What does that mean? +Luau presets can use the `require` statement to share common Luau code. +However, this is only possible within one preset workspace. +* As a result, it is safe to have multiple completely different preset workspace, and it's guaranteed that they don't conflict with each other. +This makes preset sharing easy (it's just a matter of copying the preset workspace directory). +* There's one special preset workspace: The _user workspace_. +It's the workspace whose directory has the same name as your macOS/Windows/Linux user. +Special features: +** The user workspace is where ReaLearn puts your presets when you save them via the user interface (as `.json` files). +** All `require` statements in Luau code imported via *Import from clipboard* are resolved against this user workspace. + +You can create a preset workspace by pressing *Menu → Compartment presets → Create compartment preset workspace (including factory presets)* (done for each compartment type separately). +This will create a randomly-named preset workspace directory within the compartment preset parent directory. +If this is your first preset workspace, it is best practice to turn it into your personal _user workspace_ by renaming the generated directory to your macOS/Windows/Linux username (name must match exactly!). + +Maybe the user workspace directory exists already. +Most likely because you have saved presets from the user interface, in which case it should contain only JSON files. +In that case you can safely move all files and directories from the generated preset workspace directory into that existing directory. + +The generated workspace contains: + +* A README file with some general information and tips. +* A copy of all ReaLearn factory presets for that compartment. +** Mainly Luau presets (ending with `.preset.luau`). +** You can use them as inspiration for your own ones. +** Most of the factory presets in the main compartment are quite advanced. +One of the easier ones is +`generic/numbered/fx-paraameters.preset.luau`. +* A bunch of Luau SDK files in the first directory level of the workspace. +** They contain Luau types and utility functions. +** You can require them within your own Luau files in that workspace and use them to build presets. +** However, the usage of the SDK files is completely optional! +The only important thing about building ReaLearn presets is that the returned table conforms to the ReaLearn compartment API (= has the structure that you get when you do *Export from clipboard → Export ... compartment as Lua*). +It doesn't matter if you use Luau's type system to build that table or the provided utility functions or your own or none. +** The SDK files can change in incompatible ways in newer ReaLearn versions. +Only ReaLearn's built-in compartment API is guaranteed to stay backward-compatible! + +Luau presets have a YAML frontmatter comment section right at the top of the file that contain meta information about the preset. +The following properties are possible: + +[cols="m,1,3"] +|=== + +| +name +| +required +| +Preset display name + +| +realearn_version +| +required +| +The ReaLearn version for which this preset was built. + +This can effect the way the preset is loaded, e.g. it can lead to different interpretation +or migration of properties. So care should be taken to set this correctly! + + +| +author +| +| +Preset author + + +| +description +| +| +Preset description. + +Preferably in link:https://en.wikipedia.org/wiki/Markdown[Markdown] format, but can also be plain text. + +| +setup_instructions +| +| +Setup instructions. + +Preferably in link:https://en.wikipedia.org/wiki/Markdown[Markdown] format, but can also be plain text. + +| +device_manufacturer +| +controller compartment only +| +Manufacturer of the device represented by the controller preset. + +| +device_name +| +controller compartment only +| +Name of the device represented by the controller preset. + +| +midi_identity_pattern +| +controller compartment only +| +MIDI identity compatibility pattern. + +Will be used for auto-adding controllers and for finding the correct controller preset when calculating auto-units. + +| +midi_output_port_patterns +| +controller compartment only +| +Possible MIDI identity compatibility patterns. + +Will be used for auto-adding controllers and for finding the correct controller preset when calculating auto-units. + +It should only be provided if the device in question doesn't reply to device queries or if it exposes +multiple ports which all respond with the same device identity and only one of the ports is the correct one. +Example: APC Key 25 mk2, which exposes a "Control" and a "Keys" port. + +ReaLearn will match any in the list. OS-prefixes are alowed, e.g. `macos:` will only match on macOS. + +| +provided_schemes +| +controller compartment only +| +Provided virtual control schemes. + +Will be used for finding the correct controller preset when calculating auto units. + +The order matters! It directly influences the choice of the best-suited main presets. In particular, schemes that are more specific to this particular controller (e.g. "novation/launchpad-mk3") should come first. Generic schemes (e.g. "grid") should come last. When auto-picking a main preset, matches of more specific schemes will be favored over less specific ones. + +| +used_schemes +| +main compartment only +| +Used virtual control schemes. + +Will be used for finding the correct controller preset when calculating auto units. + +| +required_features +| +main compartment only +| +A set of features that a Helgobox instance needs to provide for the preset to make sense. + +Will be used for determining whether an auto unit should be created for a specific instance or not. Example: If the required feature is "playtime" and a controller is configured with +this main preset but the instance doesn't contain a Playtime Matrix, this instance will not load the main preset. + +Currently, only feature `playtime` is supported, which matches if the Helgobox instance contains a Playtime Matrix. + +|=== + +[#compartment-parameter] +== Compartment parameter + +Each ReaLearn compartment contains 100 freely assignable parameters. +Compartment parameters can be used in the following ways: + +* For xref:further-concepts/mapping.adoc#conditional-activation[] (lets the parameter value influence which mappings are active) +* For xref:further-concepts/target.adoc#dynamic-selector[] (lets the parameter value influence which object, such as track or FX, is targeted for a specific mapping) +* As xref:sources/reaper/realearn-parameter.adoc[] (lets the parameter value control any ReaLearn target) + +They can be customized as described in xref:user-interface/main-panel/menu-bar.adoc#compartment-parameters[]. +Parameter customizations are saved together with the compartment preset. +Parameter values will be reset whenever you load a preset (just the ones in that compartment). + +[[continuous-vs-discrete-compartment-parameters]] +=== Continuous vs. discrete compartment parameters + +By default, <> have a xref:further-concepts/target.adoc#continuous-value-range[]. +Although that makes them very versatile, it's often easier to work with a xref:further-concepts/target.adoc#discrete-value-range[]. + +Entering a value count (see xref:user-interface/main-panel/menu-bar.adoc#set-compartment-parameter-value-count[]) turns this into a parameter with a xref:further-concepts/target.adoc#discrete-value-range[], with the given number of integer values. +For example, a value count of 10 means that the parameter can represent exactly 10 values (0 to 9). + +[CAUTION] +==== +*Choose the value count wisely and think twice before changing it to a different value at a later point in time!* + +You probably want to refer to values of this parameter in certain parts of ReaLearn, e.g. in xref:user-interface/mapping-panel/glue-section.adoc#target-min-max[]. +If you do that and later change the value count, these value references will not be valid anymore. +They will point to other integers than you intended to. +So if you are not sure, better pick a large value count and stick to it! +==== + +[[compartment-wide-lua-code]] +== Compartment-wide Lua code + +Each compartment may contain arbitrary Luau code to be reused by multiple mapping MIDI source and feedback scripts. +This avoids code duplication and decreases memory usage. +It even allows the usage of shared state. + +You can set the compartment-wide Lua code using xref:user-interface/main-panel/menu-bar.adoc#edit-compartment-wide-lua-code[]. + +The code that you provide here is treated as a module that xref:further-concepts/source.adoc#midi-source-script[MIDI source scripts] and xref:further-concepts/glue.adoc#dynamic-feedback[Feedback scripts] can import using `require("compartment")`. +That means you need to export everything that you want the MIDI source and feedback scripts to see, simply by returning it. + +.Compartment-wide Lua code +==== +The following compartment-wide Lua code exports 2 functions named `get_text` and `get_number`: + +[source,lua] +---- +local module = {} + +local function private_function() + return "i'm private" +end + +function module.get_text() + return "hello world" +end + +function module.get_number() + return 5 +end + +return module +---- + +These functions can then be reused in MIDI source and feedback scripts: + +[source,lua] +---- +local compartment = require("compartment") +local text = compartment.get_text() +local number = compartment.get_number() +---- +==== + +Compartment-wide Lua code is part of the compartment, that means it's also saved as part of a compartment preset! + +[[virtual-control]] +== Virtual control + +_Virtual control_ makes it possible to create <> +that can be reused with many different xref:key-concepts.adoc#controller[controllers]. + +The idea is simple: + +1. You define a <> for a DAW controller, mapping each <> (e.g. its first fader, which emits MIDI CC7 messages) to a corresponding <> (e.g. named `ch1/fader`) by using a xref:further-concepts/target.adoc#virtual-target[]. +2. You define a <>, mapping each <> to some xref:further-concepts/target.adoc#real-target[] by using a xref:further-concepts/source.adoc#virtual-source[]. +For example, you map `ch1/fader` to the xref:targets/track/set-volume.adoc[]. +3. ReaLearn creates a sort of wire between the xref:key-concepts.adoc#controller-compartment[] and the xref:key-concepts.adoc#main-compartment[]. +So you can now control the track volume by moving the first fader. +4. Most importantly, the main preset is now generic because it's not built for a specific controller anymore! + +See xref:best-practices.adoc#using-the-controller-compartment[] for more information how to do this in detail! + +[[virtual-feedback]] +== Virtual feedback + +_Virtual feedback_ is just like <>, but in the opposite direction (from REAPER to your xref:key-concepts.adoc#controller[]). + +== Real vs. virtual control elements + +Wait ... control elements that are not real!? +Yes! +In ReaLearn, they exist. +We can distinguish between <> and <>. + +[[real-control-element]] +=== Real control element + +A _real_ control element is an element that really exists on a xref:key-concepts.adoc#controller[], e.g. a fader that you can touch. + +[[virtual-control-element]] +=== Virtual control element + +A _virtual_ control element is an abstraction of a <>. +It enables <> and <>. + +Each virtual control element has a _type_ and an _ID_. + +[[virtual-control-element-id]] +==== Virtual control element ID + +A number or name that uniquely identifies the control element on the device. + +Numbers are especially suited for the 8-knobs/8-buttons layouts. +In a row of 8 knobs one would typically assign number 1 to the leftmost and number 8 to the rightmost one. +It's your choice. + +For more advanced virtual control scenarios it can be useful to think in names instead of numbers. +You can use up to 32 alphanumeric and punctuation characters (no exotic characters, e.g. no umlauts). + +[[virtual-control-element-type]] +==== Virtual control element type + +If you want to define a virtual control element, you should first decide which type is should have: _Multi_ or _Button_. +This distinction is used by ReaLearn to optimize its user interface. + +IMPORTANT: For numbered control elements, the type is even part of the <>. +For example, "Multi 1" is considered a different virtual control element than "Button 1". +For named control elements, this is not the case. `col1/row1/pad` defined as Multi is considered the same as `col1/row1/pad` defined as Button. + +[[virtual-control-element-type-multi]] Multi:: +Represents a control element that you can "move", that is, something that allows you to choose between more than 2 values. +Usually everything which is _not_ a simple on/off button :) Here's a list of typical _multis_: +* Fader +* Knob +* Pitch wheel +* Mod wheel +* Endless encoder +* XY pad (1 axis) +* Touch strip +* Rotary (endless) encoder +* Velocity-sensitive pads or keys + +[[virtual-control-element-type-button]] Button:: +Represents a control element that distinguishes between two possible states only (e.g. on/off), or even just one ("trigger"). +Usually it has the form factor of a button that you can "press". +Here's a list of typical _buttons_: + +* Play button +* Switch +* Sustain pedal (a simple on/off one, not a half-pedaling one!) + +Please note that velocity-sensitive keys should be exposed as <> - unless you know for sure that you are not interested in the velocity sensitivity. + +[[virtual-control-scheme]] +==== Virtual control scheme + +===== Introduction + +Mixing and matching controller and main presets only works as long as controller and main presets share the same philosophy and virtual control element naming/numbering. + +IMPORTANT: A main preset is not automagically compatible with any controller preset! + +As you can imagine, there are far too many types of controllers and control scenarios out there to make this work in all cases. +However, ReaLearn provides many means to get close to this ideal. + +To make it easier for users to know which main presets are compatible with which controller presets, ReaLearn has the concept of _virtual control schemes_. At the moment, ReaLearn uses the following schemes: + +DAW control (`daw`):: +Control elements that are typically used for controlling a DAW. +The control elements names available in this scheme are heavily inspired by the wording used on Mackie Control devices, e.g. `ch1/fader`. + +Grid (`grid`):: +Control elements that are laid out in columns and rows, as for example found on the Novation Launchpad. Typical control element names are `col1/row2/pad`. + +Numbered (`numbered`):: +Control elements that are laid out as a simple row or column. The names available in this scheme are simply numbers, e.g. from 1 to 8. + +The general idea is: + +* A **controller preset** _provides_ an arbitrary number of schemes. +** E.g. the Akai APC Key 25 provides control elements of all the mentioned schemes. +** A controller should not double-expose one single control element under different virtual control element names. +* A **main preset** _uses_ an arbitrary number of schemes. +** E.g. they could assign a track volume change to both the numbered multi control element 5 (_Numbered_ scheme) and to the named multi control element `ch5/fader` (_DAW_ scheme) by using 2 mappings with the same target. +Then we say this preset uses both schemes. + +===== How main presets can leverage virtual control schemes + +There are roughly 3 kinds of main presets: + +. **Controller-specific main presets without virtual sources:** They are built for one specific controller and use MIDI/OSC sources directly. +. **Controller-specific main presets with virtual sources:** They are built for one specific controller but primarily use virtual sources (e.g. in order to take advantage of ReaLearn's projection feature). +. **Reusable main presets:** Thy are built for all controllers that provide a specific control scheme. + +===== Usage + +If you want your main preset to be compatible with as many controller presets as possible, try to use predefined control element names instead of coming up with your own way of naming things! + +When you define a virtual source or virtual target, there's a convenient picker that provides names for the following standardized virtual control schemes. + +[[custom-data]] +== Custom compartment data + +Each xref:key-concepts.adoc#compartment[] in ReaLearn can carry _custom data_. That is arbitrary structured (JSON-like) data which can be used in certain ways. + +I know that's a quite generic description. It's best to give some examples: + +* ReaLearn's xref:further-concepts/unit.adoc#projection[projection feature] uses custom compartment data within the xref:key-concepts.adoc#controller-compartment[] in order to memorize the positions and shapes of all control elements. +* Playtime's xref:playtime::further-concepts/matrix.adoc#control-unit[] feature reads custom compartment data within the xref:key-concepts.adoc#main-compartment[] in order to know the size of the connected xref:playtime::key-concepts.adoc#grid-controller[]. + +Such custom data can't directly be edited in ReaLearn's xref:user-interface.adoc[]. But you can read it when dealing with JSON or Lua chunks, e.g. when <>. + +== Order in which mappings are processed + +Since ReaLearn 2.10.0, mappings are processed from top to button, exactly in the order in which they are defined within the corresponding compartment. +This matters if you want to map multiple targets to one button and the order of execution matters. + +*Important:* There's an exception. +ReaLearn's processing of its own VST parameters is always deferred. + +- That means changing a ReaLearn parameter in one mapping and relying on it in the next one (in terms of conditional activation or in a `` expression), will not work! +- You can work around that by delaying execution of the next mapping via xref:user-interface/mapping-panel/glue-section.adoc#fire-mode[fire mode] but that's a dirty hack. +ReaLearn's parameters are not supposed to be used that way! +- Imagine a railway: ReaLearn's targets can be considered as trains. +Triggering a target means moving the train forward. +ReaLearn's parameters can be considered as railway switches. +Changing a parameter means setting a course. +The course needs to be set in advance, at least one step before! +Not at the same time as moving the train over the switch. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/general.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/general.adoc new file mode 100644 index 0000000..3e6cb80 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/general.adoc @@ -0,0 +1,55 @@ + += General concepts + +[[import-export]] +== Import/export + +ReaLearn can import and export data in 2 formats: + +link:https://www.json.org/json-en.html[JSON]:: +A wide-spread data exchange format. +It's a text format, so if you are familiar with the search & replace feature of your favorite link:https://en.wikipedia.org/wiki/Text_editor[text editor], this is one way to do batch editing. + +link:https://luau.org/[Luau]:: +A full-blown programming language derived from the famous Lua language that is also used in REAPER itself. + +[TIP] +==== + +For the programmers and script junkies out there: It's perfectly possible to program ReaLearn from outside by passing it a snippet of JSON via https://www.reaper.fm/sdk/reascript/reascripthelp.html#TrackFX_SetNamedConfigParm[`TrackFX_SetNamedConfigParm()`]. +Parameter name is `set-state`. +This mechanism is implemented on ReaLearn side using https://www.reaper.fm/sdk/vst/vst_ext.php[REAPER's named parameter mechanism] (search for `named_parameter_name`). + +Example that assumes that the first FX of the first track is a ReaLearn instance: + +[source,lua] +---- +local track = reaper.GetTrack(0, 0) +local state = [[ +{ + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2 + } + } + ] +} +]] +reaper.TrackFX_SetNamedConfigParm(track, 0, "set-state", state) +---- +==== + +[#feedback-relay] +== Feedback relay + +Feedback _relay_ happens when a feedback-enabled mapping becomes inactive and another feedback-enabled mapping with the same xref:key-concepts.adoc#source[] becomes active. In that case, ReaLearn has to swap the displayed value of the previous mapping target with the displayed value of the new mapping target. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/glue.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/glue.adoc new file mode 100644 index 0000000..ba8c426 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/glue.adoc @@ -0,0 +1,257 @@ += Glue concepts + +[[target-value-sequence]] +== Target value sequence + +A target value sequence represents a list of possible target values. +It can be entered using into the xref:user-interface/mapping-panel/glue-section.adoc#value-sequence[]. + +The mapping will set the target only to values contained in that sequence. +Such a sequence doesn't just support single values but also ranges with customizable step sizes. +All values are entered comma-separated using the target unit specified with the xref:user-interface/mapping-panel/target-section.adoc#display-unit[]. + +.Single values +==== +Enter this sequence for a volume target with target unit switched to *dB*: + +`-20, -14, -12, -6, -3.5, 0` + +When you move your knob or rotary encoder or press a button using xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[], ReaLearn will step through the entered dB values for you. +==== + +.Value ranges +==== + +Enter this sequence for a target with a continuous value range and target unit switched to *%*: + +`10 - 30, 50 - 70 (5), 80 - 90 (2)` + +It will first step in 1% steps from 10% to 30%, then in 2% steps from 50% to 70% and finally from 80% to 90% in 2% steps. +It's important that the numbers and the range dash are separated by spaces! +==== + +.Non-monotonic or non-strict-monotonic sequences +==== +Let's look at this sequence: + +`20, 10, 10, -5, 8` + +It's non-monotonic: It decreases, and then increases again. +Even if it would just decrease, it would be non-strict monotonic because it contains duplicates (value 10). + +When using xref:further-concepts/mapping.adoc#absolute-control[], it's no problem stepping through such sequences. + +However, xref:further-concepts/mapping.adoc#relative-control[] only supports strictly increasing or strictly decreasing sequences. +So if you control this sequence e.g. via xref:further-concepts/mapping.adoc#rotary-endless-encoder[] or via xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[], the sequence will be stepped through like this: -5, 8, 10, 20. + +Alternative: Use xref:user-interface/mapping-panel/glue-section.adoc#make-absolute[]! +==== + +[#feedback-type] +== Feedback type + +The _feedback type_ determines whether to send numeric, text or dynamic feedback to the source. +It can be set using the xref:user-interface/mapping-panel/glue-section.adoc#feedback-type-controls[]. + +[[numeric-feedback-type]] +=== Numeric feedback: EEL transformation + +Sends numeric feedback to the source. +This is the default. + +Numeric feedback can be combined with an EEL feedback transformation formula. +This is similar to xref:user-interface/mapping-panel/glue-section.adoc#control-transformation[] but used for translating a target value back to a source value for feedback purposes. + +Be aware: Here `x` is the desired source value (= output value) and `y` is the current target value (= input value), so you must assign the desired source value to `x`. + +.Simple feedback transformation formula +==== +`x = y * 2` +==== + +ReaLearn's feedback processing order is: + +. Apply target interval. +. Apply reverse. +. Apply transformation. +. Apply source interval. + +[[text-feedback]] +=== Text feedback: Text expression + +With this option, ReaLearn will send text feedback values to the source. +This only works with sources that are capable of displaying text: That is any xref:sources/osc.adoc[] with argument type _String_, xref:sources/midi/display.adoc[] and xref:sources/midi/midi-script.adoc[]. + +Text feedback can be combined with a _text expression_, which lets you define which text is going to be sent to the source _whenever the target value changes_ and immediately when entering the text. +Whatever text you enter here, will be sent verbatim to the source. + +Of course, entering a fixed text here is not very exciting. +Most likely you want to display dynamic text such as the name of the currently selected track or the current target value, nicely formatted! +You can do that by using placeholders, delimited by double braces. + +.Simple text expression +==== +`{{target.text_value}}` +==== + +See xref:further-concepts/target.adoc#target-property[] for a list of properties that you can use in placeholders. + +[#dynamic-feedback] +=== Dynamic feedback: Lua script + +This feedback type puts you fully in charge about which feedback to send to the source. +It does so by letting you define a Luau script that builds numeric, textual or even arbitrarily structured feedback. + +==== General mechanics + +ReaLearn executes your script whenever one of the ReaLearn-provided properties used in your script might have changed its value. + +The script receives an input and must produce an output. + +Script input:: +* The input is a function `context.prop` which you can use to query arbitrary properties, e.g. target or mapping properties. +Those properties are the very same properties that you can use in <>. ++ +.How to use `context.prop()` +==== +[source,lua] +---- +local preset_name = context.prop("target.preset.name") +local param_name = context.prop("target.fx_parameter.name") +---- +==== ++ +* Values returned by this function can be `nil`! +E.g. target-related properties return a `nil` value whenever the mapping or target turns inactive, which is a very common situation. +So it's important to prepare your Luau code for that, otherwise script execution fails and no feedback will be sent. +One way to deal with a `nil` value returned by `context.prop` is to also return `nil` as `value` (see below). + +Script output:: +* The output that the script is supposed to return is a table which looks as in the following example. ++ +.Result table structure +==== +[source,lua] +---- +return { + feedback_event = { + -- The feedback value <1> + value = "Arbitrary text", + -- An optional color <2> + color = { r = 0, g = 255, b = 0 }, + -- An optional background color <3> + background_color = nil, + } +} +---- + +<1> In this example it's a text value, but it can be anything! +<2> Has the same effect as color in xref:user-interface/mapping-panel/glue-section.adoc#feedback-style[] +<3> Has the same effect as background color in xref:user-interface/mapping-panel/glue-section.adoc#feedback-style[] +==== ++ +* The most important thing here is `value`. +It can either be ... +** ... a string (ideal for display sources) +** ... a number (ideal for LEDs and motor faders) +** ... `nil` (which means "turn the source off", e.g. turn off the LED, turn down the motorfader, clear the display text) +** ... or anything else (`true`, `false` or an arbitrary table ... at the moment, this is only useful for the xref:sources/midi/midi-script.adoc[] because other sources don't know how to deal with it) + + +.`global.realearn.time` +==== +Displays the number of milliseconds passed since ReaLearn was loaded: + +[source,lua] +---- +local millis = context.prop("global.realearn.time") or 0 +return { + feedback_event = { + value = "" .. millis .. "ms" + }, +} +---- +==== + +.Animation +==== +Creates an animation to make a long FX name visible on a tiny screen: + +[source,lua] +---- +function create_left_right_animation(global_millis, max_char_count, frame_length, text) + if text == nil then + return nil + end + if #text > max_char_count then + local frame_count = #text - max_char_count + local frame_index = math.floor(global_millis / frame_length) % (frame_count * 2) + local text_offset + if frame_index < frame_count then + text_offset = frame_index + else + local distance = frame_index - frame_count + text_offset = frame_count - distance + end + return text:sub(text_offset + 1, text_offset + max_char_count) + else + return text + end +end + +-- The maximum number of characters we want to display at once +local max_char_count = 10 +-- How many milliseconds to remain in one position +local frame_length = 150 +local millis = context.prop("global.realearn.time") +local fx_name = context.prop("target.fx.name") +local animation = create_left_right_animation(millis, 10, frame_length, fx_name) +return { + feedback_event = { + value = animation + }, +} +---- +==== + +.Structured feedback values +==== +Returns a structured feedback value ... + +[source,lua] +---- +return { + feedback_event = { + value = { + available = context.prop("target.available"), + more_info = { + index = context.prop("target.discrete_value"), + count = context.prop("target.discrete_value_count"), + }, + } + }, +} +---- + ++...+ which can then be processed by a xref:sources/midi/midi-script.adoc[]: + +[source,lua] +---- +return { + address = 0x4bb0, + messages = { + { 0xb0, 0x4b, y.more_info.index, y.more_info.count } + } +} +---- + +This example is not realistic, it just shows how you can access the value table returned by the glue section feedback script. +==== + +You can share code between multiple feedback scripts by using xref:further-concepts/compartment.adoc#compartment-wide-lua-code[], with the following limitations (which hopefully will be lifted over time): + +* The shared code is not yet available to the Lua code editor window. +That means writing `require("compartment")` will evaluate to `nil` in the editor. +You might see a corresponding error message when the editor tries to compile your code. +* When ReaLearn queries the script in advance to know which target properties it needs, the shared code is also not available yet. +Currently, you need to make sure that the target properties are queried even if `require("compartment")` evaluates to `nil`. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/instance.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/instance.adoc new file mode 100644 index 0000000..a4cad4a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/instance.adoc @@ -0,0 +1,38 @@ += Instance concepts + +[[instance-tag]] +== Instance tag + +Each instance can have arbitrarily many tags. + +Tags are important if you want to dynamically enable or disable instances using the xref:targets/realearn/enable-disable-instances.adoc[]. + +[[auto-units]] +== Auto units + +Each xref:key-concepts.adoc#instance[] optionally supports the automatic addition and configuration of a xref:key-concepts.adoc#unit[] with a user-defined xref:further-concepts/compartment.adoc#main-preset[] if a certain type of controller is connected, and automatic removal if it is disconnected. + +The general procedure is: + +. Globally define once what controllers you have at your disposal and choose which main preset you want to use for which controller (in the xref:user-interface/settings-dialog.adoc[] of the xref:helgobox::app.adoc[]) +. Enable global control for one xref:key-concepts.adoc#instance[] using xref:user-interface/main-panel/menu-bar.adoc#enable-global-control[]. + +=== Global control + +If global control is enabled for an instance, all connected controllers that are configured in the xref:realearn::user-interface/settings-dialog.adoc[ReaLearn settings dialog] will load their corresponding main preset. +You can configure controllers using the xref:helgobox::app/user-interface/title-bar.adoc#title-bar-settings[] button in the window title bar of the xref:helgobox::app.adoc[]. + +If global control is disabled for an instance, global controllers will be ignored in this instance. +This gives you the freedom to create a custom control experience using ReaLearn. + + +TIP: It's a good idea to enable global control for a Helgobox instance on the monitoring FX chain. +Such an instance will be around permanently as long as REAPER is running, even if you open and close different projects. +Perfect for project-spanning control scenarios! + +=== Overrides + +If you decide to use a specific device for something else in a certain project, all you need to do is to set use the device as input and/or output in a project-specific ReaLearn unit! +If you do that, the project-specific instance "wins" over the monitoring FX instance. +You got a project-specific override. +If you close the project, the monitoring FX instance takes over again. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/mapping.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/mapping.adoc new file mode 100644 index 0000000..16fcbab --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/mapping.adoc @@ -0,0 +1,523 @@ += Mapping concepts + +== Absolute vs. relative control + +We can distinguish between two fundamentally different ways of xref:key-concepts.adoc#control[]: _Absolute_ and _relative_ control. + +[[absolute-control]] +=== Absolute control + +Imagine someone tells you: "Change the volume to -6 dB!" You go ahead and move the fader to -6 dB. + +That's absolute control in a nutshell! +Absolute control uses <>. + +[TIP] +==== +Often, absolute control involves an <>, but it doesn't have to! + +ReaLearn can simulate absolute control even with a <> (see xref:user-interface/mapping-panel/glue-section.adoc#make-absolute[]). +==== + +[[relative-control]] +=== Relative control + +Imagine someone tells you: "Raise the volume by 2 dB!" You go ahead and move the fader up by 2 dB. +Before it was -6 dB, that means it's -4 dB now. + +That's relative control! +Relative control uses <>. + +[TIP] +==== +Often, relative control involves a <>, but it doesn't have to! +ReaLearn masters multiple ways of turning absolute control into relative one: + +* Buttons or keys naturally emit absolute control values. +But using xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[], they can be used for relative control (previous/next style buttons). +* Knobs naturally emit absolute control values. +But using xref:user-interface/mapping-panel/glue-section.adoc#make-relative[] or xref:user-interface/mapping-panel/glue-section.adoc#performance-control[], they can be used for relative control. +==== + +== Absolute vs. relative control elements + +We can distinguish between control elements with regard to what sort of <> they emit: In this dimension, we have <> and <>. + +[[absolute-control-element]] +=== Absolute control element + +A xref:key-concepts.adoc#control-element[] is _absolute_ if it emits absolute values. +You can think of an absolute value as a percentage: The value is something between 0% and 100%, where 0% represents the minimum possible value and 100% the maximum. + +.Typical absolute control elements +[[fader]] Fader:: +A fader is a vertical or horizontal element with a thumb that you can move. +When moving a fader up from bottom to top, it will continuously emit values from 0% to 100%. + +[[knob]] Knob:: +A knob is a circular element with boundaries on the left and right. +When moving a knob clockwise, it will continuously emit values from 0% (at the left boundary) to 100% (at the right boundary). + +[[momentary-button]] Momentary button:: +A momentary button is a button that emits 100% when pressed and 0% when released. +ReaLearn can easily use momentary buttons to toggle a target. +See xref:user-interface/mapping-panel/glue-section.adoc#toggle-button-mode[]. + +[[toggle-button]] Toggle button:: +A toggle button is a button that emits 100% when pressed and 0% when pressed again. ++ +IMPORTANT: You don't want toggle buttons on the hardware side! +They are much less flexible than momentary buttons! +Momentary buttons can be turned into toggle buttons by ReaLearn, but not vice versa! +If you have to put up with a hardware toggle button, use xref:further-concepts/source.adoc#midi-source-character[] xref:further-concepts/source.adoc#toggle-only-button[]. + +[[velocity-sensitive-key]] Velocity-sensitive key:: A key emits a value between 0% and 100% when pressed, depending on the velocity with which the key was hit. +When released, it will emit 0%. + +[[aftertouch]] Aftertouch:: When pressing the key a bit more into the keybed after it has already been pressed, it will continuously emit increasing values starting at 0%. +When releasing pressure, it will continuously emit decreasing values until reaching 0% again. + +[[pitch-wheel]] Pitch wheel:: When moving a pitch wheel up, it will continuously emit increasing values starting at 50%. +When letting it snap back, it will continuously emit decreasing value until reaching 50% again. +When moving it down, it will continuously emit decreasing values, and so on. + +[[touch-strip]] Touch strips:: When touching the strip somewhere in the middle, it will emit a value around 50%. +When dragging upward, it will continuously emit increasing values starting from where you touched it first. + +[[relative-control-element]] +=== Relative control element + +A xref:key-concepts.adoc#control-element[] is _relative_ if it emits relative values. +You can think of a relative value as an _instruction_. +It can be one of the following two instructions: + +Please decrease!:: We call such a value a *decrement*. +Please increase!:: We call such a value an *increment*. + +.Typical relative control elements +[[rotary-endless-encoder]] Rotary endless encoder:: +A rotary endless encoder (or just _encoder_) is a circular, like a knob. +But unlike a knob, it doesn't have boundaries. +When moving a rotary endless encoder clockwise, it will continuously emit increments. +When moving it counter-clockwise, it will continuously emit decrements. ++ +IMPORTANT: It happens very often that controllers have rotary endless encoders, but they will act like knobs by default, sending absolute messages. +That is a great waste, and you should change that setting as soon as possible on the hardware side. + +[[control-value]] +== Control value + +A _control value_ is the signal that travels through a ReaLearn xref:key-concepts.adoc#mapping[] from xref:key-concepts.adoc#source[] to xref:key-concepts.adoc#target[] when it receives an event from an xref:key-concepts.adoc#input-port[]. +A control value can be absolute or relative. + +[[feedback-value]] +== Feedback value + +A _feedback value_ is the signal that travels through a ReaLearn xref:key-concepts.adoc#mapping[] back from xref:key-concepts.adoc#target[] to xref:key-concepts.adoc#source[] when the target changes its value. +A feedback value is always absolute. + +== Absolute vs. relative control values + +[[absolute-control-value]] +=== Absolute control value + +An _absolute_ control value is conceptually a percentage between 0.0% and 100.0%. + +Internally, it is represented by a high-precision floating point number between 0.0 and 1.0. E.g. 0.25 is 25%. + +[[relative-control-value]] +=== Relative control value + +A _relative_ control value is a number of increments or decrements. + +Internally, it is represented as a positive or negative integer. +E.g. control value -2 means a decrement of 2. + +[[mapping-tag]] +== Mapping tag + +Each mapping can have arbitrarily many tags. +Such tags can be used to organize mappings in a way that is much more flexible than groups. + +Tags are not just something for people that love to keep things tidy! +They also get meaning in combination with certain ReaLearn targets such as xref:targets/realearn/enable-disable-mappings.adoc[]. + +[[group]] +== Mapping group + +Mapping groups are part of the currently shown compartment and enable you to divide the list of mappings into multiple groups. + +Groups can be useful … + +* To apply an activation condition to multiple mappings at once. +* To enable/disable control/feedback for multiple mappings at once. +* To keep track of mappings if there are many of them. + +You can decide which group is displays using xref:user-interface/main-panel/mapping-group-section.adoc#mapping-group[]. + +You can move existing mappings between groups by opening the context menu (accessible via right-click on Windows and Linux, control-click on macOS) of the corresponding mapping row and choosing "Move to group". + +Groups are saved as part of the project, VST plug-in preset and compartment preset. + +[[mapping-activation-state]] +== Mapping activation state + +A mapping is considered as *on* or *active* (terms are used interchangeably) only if all following criteria are fulfilled: + +. The mapping is complete, that is, both source and target are completely specified +. The mapping is enabled as a whole +. The mapping has control and/or feedback enabled +. The <> is fulfilled +. The xref:further-concepts/target.adoc#target-activation-condition[] is fulfilled +. The target is valid ++ +==== +Example: A track target can be invalid when it's using xref:further-concepts/target.adoc#selected-selector[] but no track is currently selected). +==== + +In all other cases, mapping is *off* or *inactive*. +In that case, it doesn't have any effect! + +(Controller) mappings with xref:further-concepts/target.adoc#virtual-target[] are always considered active as long as the feedback checkbox is ticked. + +[[mapping-signal-flow]] +== Mapping signal flow + +Here's how ReaLearn processes an incoming control event that matches a mapping source. + +1. ReaLearn converts the event coming from the xref:key-concepts.adoc#input-port[] to a <>. +2. ReaLearn feeds the <> to the mapping's xref:key-concepts.adoc#glue[]. +The glue section is responsible for transforming control values before they reach the xref:key-concepts.adoc#target[]. +This transformation can change the type of the control value, e.g. from relative to absolute - it depends on the settings in the glue section and the mapping's target. +The glue section can even "eat" control values so that they don't arrive at the target at all. +3. Finally, ReaLearn converts the transformed <> into some target instruction (e.g. "set volume to -6.0 dB") and executes it. + +Feedback (from target to source) works in a similar fashion but is restricted to absolute control values. +Even if the source is relative (e.g. an encoder), ReaLearn will always emit absolute feedback, because relative feedback doesn't make sense. + +[[conditional-activation]] +== Conditional activation + +Conditional activation is a powerful feature that allows you to dynamically activate or deactivate a mapping depending on its <>. + +.Control A when a button is not pressed, control B when it is +==== +Here's how you would implement a typical use case. +You want your rotary encoder to control target A when the button is not pressed and control target B when it's pressed. + +. Create a mapping for the button +** As "Target", you need to choose ReaLearn itself (Type: xref:targets/fx-parameter/set-value.adoc[], Track: ``, FX: "… VSTi: ReaLearn (Helgoboss)"). +As "Parameter", choose an arbitrary ReaLearn parameter, e.g. "Parameter 1". +** As "Mode", choose either "Absolute" (if you want to switch the encoder function just momentarily) or "Toggle" (if you want the button to toggle between the two encoder functions). +. Create a mapping with target A +** Set "Active" to "When modifiers on/off", "Modifier A" to "Parameter 1" and disable the checkbox beside it. +Set "Modifier B" to ``. +** This basically means "Hey, ReaLearn! +Please activate this mapping only if ReaLearn Parameter 1 is *off*!" (remember, we control ReaLearn Parameter 1 using the button). +** At this point, turning your encoder should control target A, but only if you don't press the button! +. Create a mapping with target B +** Just as in step 2, set "Active" to "When modifiers on/off" and "Modifier A" to "Parameter 1". *But*: Now *enable* the checkbox beside it. +Set "Modifier B" to ``. +** This basically means "Hey, ReaLearn! +Please activate this mapping only if ReaLearn Parameter 1 is *on*!" +** At this point, turning your encoder should control target A if you don't press the button and control target B if you press the button. +==== + +[[mapping-activation-condition]] +== Mapping activation condition + +The activation condition of a mapping determines under which circumstances a mapping is active or inactive, based on the value of a xref:further-concepts/compartment.adoc#compartment-parameter[] or based on the state of arbitrary xref:key-concepts.adoc#target[targets]. +It is especially practical if your controller has a limited amount of control elements and you want to give control elements several responsibilities. +It lets you easily implement use cases such as: + +* "This knob should control the track pan, but only when my sustain pedal is pressed, otherwise it should control track volume!" (modifier use cases) +* "I want to have two buttons for switching between different banks where each bank represents a group of mappings." (bank use cases) +* "I want to control the volume of this track only if it's not muted." (target-state based use cases) + +TIP: Since ReaLearn 2.11.0, xref:targets/realearn/enable-disable-mappings.adoc[] provides a slightly less powerful but more straightforward way to implement use cases that were before only achievable with parameter-based conditional activation. + +There are 6 different activation modes: + +* *Always:* Mapping is always active (the default) +* *When modifiers on/off:* Mapping becomes active only if something is pressed / not pressed +* *When bank selected:* Allows you to step through different groups of mappings (sometimes also called "pages") +* *When EEL met* Let an EEL formula decide (total freedom) +* *When expression met:* Let an expression decide (total freedom) +* *When target value met:* Let the current value of the target of another mapping decide + +[NOTE] +==== +At this occasion, some words about ReaLearn's own freely assignable FX parameters. + +ReaLearn itself isn't just able to control parameters of other FX, it also offers FX parameters itself. +At the moment it offers 200 FX parameters, 100 for the main compartment and 100 for the controller compartment. +You can control them just like parameters in other FX: + +- Via automation envelopes, +- via track controls, +- via REAPER's own MIDI/OSC learn +- … and of course via ReaLearn itself. + +Initially, they don't do anything at all. +First, you need to give meaning to them by referring to them in activation conditions or `` selector expressions. +==== + +=== When modifiers on/off + +This mode is comparable to modifier keys on a computer keyboard. +For example, when you press `Ctrl+V` +for pasting text, `Ctrl` is a modifier because it modifies the meaning of the `V` key. +When this modifier is "on" (= pressed), it activates the "paste text" and deactivates the "write the letter V" functionality of the `V` key. + +In ReaLearn, the modifier is one of the FX parameters. +It's considered to be "on" if the parameter has a value greater than 0 and "off" if the value is 0. + +You can choose up to 2 modifier parameters, "Modifier A" and "Modifier B". +If you select "", the modifier gets disabled (it won't have any effect on activation). +The checkbox to the right of the dropdown lets you decide if the modifier must be "on" for the mapping to become active or "off". + +Example: The following setting means that this mapping becomes active _only_ if both "Parameter 1" and "Parameter 2" are "on". + +* *Modifier A:* "Parameter 1" +* *Checkbox A:* Checked +* *Modifier B:* "Parameter 2" +* *Checkbox B:* Checked + +Now you just have to map 2 controller buttons to "Parameter 1" and "Parameter 2" via ReaLearn (by creating 2 additional mappings - in the same ReaLearn instance or another one, up to you) et voilà, it works. +The beauty of this solution lies in how you can compose different ReaLearn features to obtain exactly the result you want. +For example, the _absolute mode_ of the mapping that controls the modifier parameter decides if the modifier button is momentary (has to be pressed all the time) or toggled (switches between on and off everytime you press it). +You can also be more adventurous and let the modifier on/off state change over time, using REAPER's automation envelopes. + +=== When bank selected + +This is the correct activation mode if you want control surface "bank-style" mapping. + +TIP: For this kind of use cases you should consider the new xref:targets/realearn/enable-disable-mappings.adoc[], which is available since ReaLearn 2.11.0 as an alternative. +It's slightly less powerful than conditional activation but probably easier to use, partly because you can dictate which mappings should be active "from outside", not from the perspective of the mapping itself. + +You can tell ReaLearn to only activate your mapping if a certain parameter has a particular value. +The particular value is called "Bank". +Why? +Let's assume you mapped 2 buttons "Previous" and "Next" to increase/decrease the value of the parameter (by using "Incremental button" mode, you will learn how to do that further below). +And you have multiple mappings where each one uses "When bank selected" with the same parameter but a different "Bank". +Then the result is that you can press "Previous" and "Next" and it will switch between different mappings within that parameter. +If you assign the same "Bank" to multiple mappings, it's like putting those mappings into one group which can be activated/deactivated as a whole. + +Switching between different programs via "Previous" and "Next" buttons is just one possibility. +Here are some other ones: + +* *Browse banks using a rotary encoder:* Just map the rotary encoder to the "Bank" parameter and restrict the target range as desired. +* *Activate each bank with a separate button:* Map each button to the "Bank" parameter (with absolute mode "Normal") and set "Target Min/Max" to a distinct value. +E.g. set button 1 min/max both to 0% and button 2 min/max both to 1%. +Then pressing button 1 will activate bank 0 and pressing button 2 will activate bank 1. + +In previous versions of ReaLearn you could use other methods to achieve a similar behavior, but it always involved using multiple ReaLearn instances: + +* *By enabling/disabling other ReaLearn instances:* You can use one main ReaLearn instance containing a bunch of mappings with xref:targets/fx/enable-disable.adoc[] in order to enable/disable other ReaLearn FX instances. +Then each of the other ReaLearn instances acts as one mapping bank/group. +* *By switching between presets of another ReaLearn instance:* You can use one main ReaLearn instance containing a mapping with xref:targets/fx/browse-presets.adoc[] in order to browse presets of another ReaLearn FX instance. +Then each preset in the other ReaLearn instance acts as one mapping bank/group. +However, that method is pretty limited and hard to maintain because presets are something global (not saved together with your REAPER project). + +With _Conditional activation_ you can do the same (and more) within just one ReaLearn unit. + +TIP: If you want to adjust the number of banks and improve bank handling in general, set a discrete value count for the corresponding bank parameter (see xref:further-concepts/compartment.adoc#compartment-parameter[]). + +=== When EEL met + +This is for experts. +It allows you to write a formula in https://www.cockos.com/EEL2/[EEL2] language that determines if the mapping becomes active or not, based on potentially all parameter values. +This is the most flexible of all parameter-based activation modes. +The other modes can be easily simulated. +The example modifier condition scenario mentioned above written as formula would be: + +---- +y = p1 > 0 && p2 > 0 +---- + +`y` represents the result. +If `y` is greater than zero, the mapping will become active, otherwise it will become inactive. `p1` to `p100` contain the current parameter values. +Each of them has a value between 0.0 (= 0%) and 1.0 (= 100%). + +This activation mode accounts for ReaLearn's philosophy to allow for great flexibility instead of just implementing one particular use case. +If you feel limited by the other activation modes, just use EEL. + +TIP: For most activation conditions which need this amount of freedom, the newer activation mode <> is a slightly better choice because it's easier to use and generally performs a bit better. + +[#expression-based-activation-condition] +=== When expression met + +This is very similar to the previous EEL activation mode. +But instead of EEL, it lets you use the same expression language as used in xref:further-concepts/target.adoc#dynamic-selector[dynamic selectors] to express the activation condition. + +The equivalent expression to above EEL example is: + +`p[0] > 0 && p[1] > 0` + +[#target-based-activation-condition] +=== When target value met + +This is different from all the other activation condition types in that it doesn't look at ReaLearn's internal parameter values. +Instead, it looks at the target of another mapping (the so-called "lead mapping") and switches our mapping (the so-called "follow mapping") on or off depending on the target value of the lead mapping. + +It works like this: + +. Create the lead mapping and give it a target, e.g. xref:targets/track/select-unselect.adoc[]. +* This lead mapping doesn't need to have a source. +It can even be completely disabled! +. In the **Mapping** dropdown, pick this newly created mapping. +. In the **Expression** text field to the right, enter `y > 0`. +* This means you want the follow mapping to be active whenever the target value of the lead mapping is greater than 0.0. Or in other words, when it's "switched on". + +You can detect an inactive target by using `y == none`. + +[[raw-midi-pattern]] +== Raw MIDI pattern + +xref:sources/midi/raw-midi-sysex.adoc[] and xref:targets/midi/send-message.adoc[] allow to enter so-called MIDI patterns, an advanced ReaLearn concept. + +=== Pattern basics + +In its most basic form, the pattern is a sequence of bytes notated as hexadecimal numbers. +This is typical notation, especially for system-exclusive MIDI messages. + +.A SysEx pattern +==== +---- +F0 00 20 6B 7F 42 02 00 10 77 00 F7 +---- + +If you enter this and ReaLearn receives this system-exclusive message from the input, it will fire a 100% value. +If feedback is set up correctly, this message will be sent to the device whenever the target value changes. +==== + +Remarks: + +* You can check if the correct feedback messages are sent to the device by enabling xref:further-concepts/unit.adoc#logging-of-real-feedback-messages[]. +* Each byte is written using 2 hexadecimal digits. +* Spaces between the bytes can be omitted. +* You can express all types of MIDI messages using this raw notation (e.g. pitch wheel), not just system-exclusive ones. +If you do this, it will work as expected for the _feedback_ direction. +Please note that it will not work for the _control_ direction at the moment (I don't think this is needed). +* If you want a system-exclusive MIDI message, you _must_ include its start (`F0`) and end status byte (`F7`)! + +=== Binary notation + +ReaLearn also supports binary notation of a byte. +You need to enclose the binary digits of one byte in brackets. + +.Binary notation +==== +---- +F0 00 20 [0110 1011] 7F 42 02 00 10 77 00 F7 +---- + +This is equivalent to the previous example (`6B` in hexadecimal notation is the same as `0110 1011` in binary notation). +==== + +Remarks: + +* Between the brackets, each digit represents one bit. +The left bit is the most significant one. +* Spaces between the two nibbles (4 bits) can be omitted. + +=== Variable patterns (extracting and encoding a value) + +For the xref:key-concepts.adoc#feedback[] direction, the examples I've shown you so far aren't real-world examples, because there's no point in sending the same MIDI message to the device over and over again! +If you really would want to send a constant MIDI message to the device, you would be much better off using a xref:user-interface/mapping-panel/advanced-settings-dialog.adoc#mapping-lifecycle-actions[Mapping lifecycle action], which allow you to send raw MIDI messages once when a mapping is initialized, not on every target value change. + +But even for the xref:key-concepts.adoc#control[] direction, you might want to react to a whole _range_ of system-exclusive messages, not just a fixed one. +One part of your message might represent a variable value. +You might want to extract it and control the target with it. + +Fortunately, ReaLearn offers a uniform way to extract a variable value from the raw MIDI message (control) or encode the current target value into the raw MIDI message (feedback). +Bytes which contain a variable value (or a part of it) _must_ be expressed using binary notation. + +.Variable pattern +==== +---- +F0 00 20 6B 7F 42 02 00 10 77 [0000 dcba] F7 +---- + +The second nibble of the second last byte contains the lowercase letters `dcba`. +This is the portion of the byte that denotes the variable value. +==== + +Each letter represents one bit of the variable value: + +[horizontal] +`a`:: Bit 1 (least significant bit of the variable value) +`b`:: Bit 2 +`c`:: Bit 3 +`d`:: Bit 4 +… +`m`:: Bit 13 +`n`:: Bit 14 +`o`:: Bit 15 +`p`:: Bit 16 (most significant bit of the variable value) + +=== Resolution of variable patterns + +The resolution of the variable value always corresponds to the letter in the whole pattern which represents the highest bit number. +In the example above, the resolution is 4 bit because there's no letter greater than `d` in the pattern. + +.Another variable pattern +==== +In this example, the resolution is 7 bit because `n` is the greatest letter in the whole pattern. + +---- +F0 00 20 6B 7F 42 02 00 10 [00nm lkji] [hgfe dcba] F7 +---- +==== + +Remarks: + +* The highest resolution currently supported is 16 bit (= 65536 different values). +* You can put these letter bits anywhere in the pattern (but only within bytes that use binary notation). + +=== Byte order + +This form of notation is slightly unconventional, but I think it's very flexible because it gives you much control over the resulting MIDI message. +This amount of control seems appropriate considering the many different ways hardware manufacturers used and still use to encode their MIDI data. + +When a number is expressed within more than one byte, manufacturers sometimes put the most significant byte first and sometimes the least significant one, there's no rule. +This notation supports both because you decide where the bits end up: + +.Most significant byte first +==== +---- +F0 00 20 6B 7F 42 02 00 10 [ponm lkji] [hgfe dcba] F7 +---- +==== + +.Least significant byte first +==== +---- +F0 00 20 6B 7F 42 02 00 10 [hgfe dcba] [ponm lkji] F7 +---- +==== + +=== More examples + +."Romeo and Juliet" bits (separated by 2 bytes) +==== +---- +F0 [1111 000b] [a101 0100] F7 +---- +==== + +.Simple on/off value (1 bit only) +==== +---- +F0 A0 [1111 010a] F7 +---- +==== + +.Pitch wheel simulation +==== +This behaves like pitch wheel because the pattern describes exactly the way how pitch wheel messages are encoded. + +---- +E0 [0gfe dcba] [0nml kjih] +---- +==== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/source.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/source.adoc new file mode 100644 index 0000000..ef67df3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/source.adoc @@ -0,0 +1,318 @@ += Source concepts + +== Real vs. virtual sources + +We distinguish between _virtual_ and _real_ sources. + +[[virtual-source]] +=== Virtual source + +A _virtual_ source refers to a xref:further-concepts/compartment.adoc#virtual-control-element[] and can only be used in the xref:key-concepts.adoc#main-compartment[]. + +Examples: `ch1/fader` + +[[real-source]] +=== Real source + +A _real_ source refers to a xref:further-concepts/compartment.adoc#real-control-element[]. + +[[midi-source-character]] +== MIDI source character + +MIDI control-change messages serve a very wide spectrum of MIDI control use cases. +Even though some control-change controller numbers have a special purpose according to the MIDI specification (e.g. CC 7 = channel volume), nothing prevents one from using them for totally different purposes. +In practice that happens quite often, especially when using general-purpose controllers. +Also, there's no strict standard whatsoever that specifies how relative values (increments/decrements) shall be emitted and which controller numbers emit them. + +Therefore, you explicitly need to tell ReaLearn about it by setting the _source character_. + +The good news is: If you use "Learn source", ReaLearn will try to guess the source character for you by looking at the emitted values. +Naturally, the result is not always correct. +The best guessing result can be achieved by turning the knob or encoder quickly and "passionately" into clockwise direction. +Please note that guessing doesn't support encoder type 3. + +The possible source characters are: + +Range element (knob, fader, etc.):: +A control element that emits continuous absolute values. +Examples: Fader, knob, modulation wheel, pitch bend, ribbon controller. +Would also include a endless rotary encoder which is (maybe unknowingly) configured to transmit absolute values. + +Button (momentary):: +A control element that can be pressed and emits absolute values. +It emits a > 0% value when pressing it and optionally a 0% value when releasing it. +Examples: Damper pedal. + +Encoder (relative type _x_):: +A control element that emits relative values, usually an endless rotary encoder. +The _x_ specifies _how_ the relative values are sent. +This 1:1 corresponds to the relative modes in REAPER's built-in MIDI learn: + +Type 1::: +* 127 = decrement; 0 = none; 1 = increment +* 127 > value > 63 results in higher decrements (64 possible decrement amounts) +* 1 < value +<=+ 63 results in higher increments (63 possible increment amounts) + +Type 2::: +* 63 = decrement; 64 = none; 65 = increment +* 63 > value >= 0 results in higher decrements (64 possible decrement amounts) +* 65 < value +<=+ 127 results in higher increments (63 possible increment amounts) + +Type 3::: +* 65 = decrement; 0 = none; 1 = increment +* 65 < value +<=+ 127 results in higher decrements (63 possible decrement amounts) +* 1 < value +<=+ 64 results in higher increments (64 possible increment amounts) + +[[toggle-only-button,Toggle-only button]] Toggle-only button (avoid!):: A control element that can be pressed and emits absolute values. +ReaLearn will simply emit 100%, no matter what the hardware sends. ++ +This is a workaround for controllers that don't have momentary buttons! +You should only use this character if there's absolutely no way to configure this control element as a momentary button. ++ +[NOTE] +.Background +==== +ReaLearn can make a momentary hardware button work like a full-blown toggle button. +Its toggle mode is inherently more powerful than your controller's built-in toggle mode!). + +However, the opposite is not true. +It can't make a toggle hardware button act like a momentary button. +==== ++ +[CAUTION] +.Combination with xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[] +==== +If you use the toggle-only source character in combination with mode xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[], you must leave source max at the (default) theoretical maximum value for that source (e.g. 127 for MIDI CC). +Even if your controller device only sends 0 and 1 and in all other mappings you would enter the controller's concrete (instead of theoretically possible) maximum value. +Otherwise, for this special case, a fixed out-of-range-behavior will set in that will just ignore all button presses. +==== + +[[midi-source-script]] +== MIDI source script + +MIDI source scripts are EEL or Luau scripts to configure the xref:sources/midi/midi-script.adoc[]. + +=== General mechanics + +Each script receives an input and must produce an output. + +Script input:: +* The main input is the current feedback value, which the script can access as a variable. + +Script output:: +* The main output that the script is supposed to return is the MIDI message to be sent to the MIDI device. +* Additionally, the script can provide a so-called _feedback address_, which is supposed to uniquely identify the LED, motor fader or display. ++ +It's important to provide an address if you want ReaLearn to handle feedback relay correctly, e.g. that it switches off the LED when not in use anymore and doesn't switch it off if another mapping "takes over" the same LED. +By convention, the constant (non-variable) bytes of the MIDI message should be used as address. +The examples below might help to understand. + +=== EEL script specifics + +Scripts written in EEL work as follows. + +Script input:: +* EEL scripts can access numeric feedback values only. +The current numeric feedback value is available as variable `y`, a floating point number between 0.0 and 1.0. This is essentially the current normalized target value after being processed by the "Glue" section of the mapping. + +Script output:: +* In order to provide the output MIDI message, you must assign the raw bytes of that message to subsequent slots of the EEL script's virtual local address space (by indexing via brackets) *and* +set the variable `msg_size` to the number of bytes to be sent. +If you forget the latter step, nothing will be sent because that variable defaults to zero! +* In order to provide the address, simply assign an appropriate number to the `address` variable. + +.Creating a 3-byte MIDI message +==== +[source,eel] +---- +address = 0x4bb0; +msg_size = 3; +0[] = 0xb0; +1[] = 0x4b; +2[] = y * 64; +---- +==== + +=== Luau script specifics + +Scripts written in Luau work as follows. + +Script input:: +* Luau scripts can access numeric, text and dynamic feedback values. +* Here's the list of input variables: +`y`::: The feedback value, either numeric (`type(y) == "number"`) or text (`type(y) == "string")`. +`context.feedback_event.color`::: The color as set in the xref:user-interface/mapping-panel/glue-section.adoc[] section. +Either the default color (`== nil`) or an RGB color (table with properties `r`, `g` and `b`). +`context.feedback_event.background_color`::: The background color. + +Script output:: +* A Luau script can even generate multiple output messages. +* You need to return a table which contains the following keys: +`address`::: The feedback address. +`messages`::: An array containing all the messages, where each message itself is an array contaning the message bytes. + +.Creating a 3-byte MIDI message, assuming that `y` is a numeric normalized value. +==== +[source,lua] +---- +return { + address = 0x4bb0, + messages = { + { 0xb0, 0x4b, math.floor(y * 10) } + } +} +---- +==== + +.Creating a MIDI sys-ex message that changes the color of some fictional device LED/display. +==== +[source,lua] +---- +local color = context.feedback_event.color +if color == nil then + -- This means no specific color is set. Choose whatever you need. + color = { r = 0, g = 0, b = 0 } +end +return { + address = 0x4b, + -- Whatever messages your device needs to set that color. + messages = { + { 0xf0, 0x02, 0x4b, color.r, color.g, color.b, 0xf7 } + } +} +---- +==== + +.Creating a 3-byte MIDI message, assuming that `y` is a text value. +==== +[source,lua] +---- +local lookup_table = { + playing = 5, + stopped = 6, + paused = 7, +} +return { + messages = { + { 0xb0, 0x4b, lookup_table[y] or 0 } + } +} +---- +==== + +[TIP] +==== + +Please note that this kind of simple mapping from text values to integer numbers doesn't need a script. +You can use the `feedback_value_table` xref:user-interface/mapping-panel/glue-section.adoc[] property instead, which can only be set via API though. +Do a full-text search for `feedback_value_table` in directory `resources/controller-presets` of the link:https://github.com/helgoboss/realearn[ReaLearn source code] to find usage examples. +==== + +You can share code between multiple MIDI scripts by using xref:further-concepts/compartment.adoc#compartment-wide-lua-code[], with the following limitations (which hopefully will be lifted over time): + +* The shared code is not yet available to the Lua code editor window. +That means writing `require("compartment")` will evaluate to `nil` in the editor. +You might see a corresponding error message when the editor tries to compile your code. + +[[osc-feedback-arguments-expression]] +== OSC feedback arguments expression + +This expression is used to enable for more flexible feedback for the xref:sources/osc.adoc[]. + +It allows you to define exactly which feedback value is sent at which argument position. +If this field is non-empty, the argument type will be ignored for the xref:key-concepts.adoc#feedback[] direction. + +The format of this field is very simple: You enter feedback value property keys separated by spaces. +Each entered property key corresponds to one argument position. + +.Custom feedback with 2 arguments +==== +If you want ReaLearn to send the current feedback value in text form at argument 1 and the color (see xref:user-interface/mapping-panel/glue-section.adoc#feedback-style[]) as RRGGBB string at argument 2, you would enter: + +---- +value.string style.color.rrggbb +---- +==== + +The following properties are available: + +[cols="m,m,1"] +|=== +| Property | Type | Description + +| +value.float +| +Float +| +Numeric feedback value interpreted as float + +| +value.double +| +Double +| +Numeric feedback value interpreted as double + +| +value.bool +| +Bool +| +Numeric feedback interpreted as bool (on/off only) + +| +value.string +| +String +| +Numeric or textual feedback value formatted as string + + +| +style.color.rrggbb +| +String +| +Feedback value color formatted as RRGGBB string + + +| +style.background_color.rrggbb +| +String +| +Feedback value background color formatted as RRGGBB string + + + +| +style.color +| +Color +| +Feedback value color as native OSC color + + +| +style.background_color +| +Color +| +Feedback value background color as native OSC color + + +| +nil +| +Nil +| +Nil value + +| +inf +| +Infinity +| +Infinity value +|=== diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/target.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/target.adoc new file mode 100644 index 0000000..2ac24b4 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/target.adoc @@ -0,0 +1,539 @@ += Target concepts + +== Real vs. virtual targets + +We distinguish between _virtual_ and _real_ targets. + +[[virtual-target]] +=== Virtual target + +A _virtual_ target controls a xref:further-concepts/compartment.adoc#virtual-control-element[] and can only be used in the xref:key-concepts.adoc#controller-compartment[]. + +Example: `ch1/fader` + +It's then picked up by a xref:further-concepts/source.adoc#virtual-source[]] in the xref:key-concepts.adoc#main-compartment[]. + +[[real-target]] +=== Real target + +All others targets are real. + +Examples: xref:targets/track/set-volume.adoc[] + +[[target-object-selectors]] +== Target object selectors + +Many ReaLearn xref:key-concepts.adoc#target[targets] work on some kind of _object_: + +* A track +* An FX +* An FX parameter +* A send or receive + +All of those objects need to be _addressed_ somehow. +For this purpose, ReaLearn uses so-called _object selectors_. + +=== Common object selectors + +This section describes commonly available object selectors. + +NOTE: The descriptions below are somewhat tailored to track objects. +However, the same concepts can easily be applied to other objects that support these selectors. + +[#unit-selector] +==== Unit selector + +This selector makes the target work on the current xref:further-concepts/unit.adoc#unit-track[] or current xref:further-concepts/unit.adoc#unit-fx[] of this particular ReaLearn xref:key-concepts.adoc#unit[]. + +[#particular-selector] +==== Particular selector + +Lets you pick a specific object (e.g. track) and refer to it by its unique ID. +This is the default. +Choose this if you want ReaLearn to always control that very particular track even in case you move it somewhere else or rename it. + +Please note that this is an extremely <> selector. +It's +_not possible_ with this setting to create a ReaLearn preset that is reusable among different projects. +Because a track ID is globally unique, even across projects. +That also means it doesn't make sense to use this setting in a ReaLearn xref:key-concepts.adoc#unit[] on the monitoring FX chain. + +[#at-position-selector] +==== At position selector + +This is the most straightforward selector. +It lets you refer to a track by its position in the track list. +This is great if you want to build a preset that you are going to reuse among multiple projects. + +However, this selector has the disadvantage that things fall apart if you reorder, insert or delete tracks. +This is why it's not the default. + +Next to the dropdown you will find a text field. +Here you should enter the position as number, starting with number `1`. + +[#named-selector] +==== Named selector + +Allows you to choose a track depending on its name. +In case there are multiple tracks with the same name, it will always prefer the first one. +This will allow you to use one ReaLearn preset across multiple projects that have similar naming schemes, e.g. as monitoring FX. + +In the name field next to the dropdown, you can enter a name. + +If you don't want exact matching, you can use wildcards: + +[horizontal] +`*`:: for matching zero or arbitrary many characters +`?`:: for matching exactly one arbitrary character. + +.Wildcards in named selectors +==== +`Violin *` would match `Violin 1` or `Violin 12` but not `12th Violin`. +==== + +[#dynamic-selector] +==== Dynamic selector + +This selector allows you to _calculate_ which object (e.g. track) you want to use. + +When you choose this option, a text field will appear next to it. +This lets you enter a mathematical expression whose result should be the object's _index_. +E.g. the first track in the project has index 0, the master track -1. For your convenience, you will find a small text label next to the expression text field that always shows the current result of your formula (clamped to the target value range). + +NOTE: Please note that the expression language is _not_ EEL - this is a notable difference to ReaLearn's control/feedback transformation text fields! +The expression language used here just provides very basic mathematical operations like addition (`+/-`), multiplication (`*`) etc. and it also doesn't allow or need any assignment to an output variable. + +The dynamic selector is a very powerful tool because you can use some special variables: + +[cols="m,1,1,3"] +|=== +| Variable | Type | Applicable to | Description + +| none | - | All selectors | +Special value that represents a "not set" value. See below for examples. + +| p | Array of floating-point numbers | All selectors | +Allows you to access the values of ReaLearn's internal parameters. E.g. you can get the number of the first parameter by writing `p[0]`. + +By default, parameter values are normalized floating point values, that means they are decimal numbers between 0.0 and 1.0. This can be changed by customizing the parameter with a specific integer value count (see xref:further-concepts/compartment.adoc#compartment-parameter[]). + +| p1 ... p100 | Floating-point numbers | All selectors | +Values of ReaLearn's internal parameters as single variables. + +_Deprecated_: Recent ReaLearn versions offer the `p` array instead. Better use that one. + + +| selected_track_index | Integer >= -1 | Track selectors | +Resolves to the zero-based index of the first currently selected track within the containing project. +If no track is selected, this resolves to `none`. If the master track is selected, `-1`. + +| selected_track_tcp_index | Integer >= -1 | Track selectors | +Like `selected_track_index` but counts only tracks that are visible in the track control panel. + +| selected_track_mcp_index | Integer >= -1 | Track selectors | +Like `selected_track_index` but counts only tracks that are visible in the mixer control panel. + +| selected_track_indexes | Array of integers >= -1 | Track selectors | +Lets you access the indexes of multiple selected tracks. + +E.g. if 2 tracks are selected, `selected_track_indexes[0]` gives you the index of the first selected track whereas `selected_track_indexes[1]` gives you the index of the second selected track. `selected_track_indexes[2]` would resolve to `none`. + +| this_track_index | Integer >= -1 | Track selectors | + +Resolves to the zero-based index of the track on which this ReaLearn instance is located. + +| instance_track_index | Integer >= -1 | Track selectors | + +Resolves to the zero-based index of the instance track of this ReaLearn instance. + +| instance_track_tcp_index | Integer >= -1 | Track selectors | + +Like `instance_track_index` but counts only tracks that are visible in the track control panel. + +| instance_track_mcp_index | Integer >= -1 | Track selectors | + +Like `instance_track_index` but counts only tracks that are visible in the mixer control panel. + +| this_fx_index | Integer >= 0 | FX selectors | + +Resolves to the zero-based index of this ReaLearn FX instance. + +| instance_fx_index | Integer >= 0 | FX selectors | + +Resolves to the zero-based index of the instance FX of this ReaLearn instance. + +| tcp_fx_indexes | Array of integers >= 0 | FX selectors | + +Lets you access the indexes of FXs in a track control panel. + +E.g. `tcp_fx_indexes[2]` will resolve to the index of the third FX displayed in the track control panel. + +| tcp_fx_parameter_indexes | Array of integers >= 0 | FX parameter selectors | + +Lets you access the indexes of FX parameters in a track control panel. + +E.g. `selected_fx_parameter_indexes[2]` will resolve to the index of the third FX parameter displayed in the track control panel. + +This only makes sense if used in conjunction with `tcp_fx_indexes`. + +|=== + +.Simple example +==== +`p1 * 99` + +* Will point to track with index 0 (first track) if xref:further-concepts/compartment.adoc#compartment-parameter[] 1 is set to the minimum and to track with index 99 (= track number 100) if it's set to the maximum. +* If you use a formula like that, you should make sure that xref:further-concepts/compartment.adoc#compartment-parameter[] 1 is controlled with a step size that allows for exactly 100 different values. +This conforms to ReaLearn's default step size 0.01 = 1%. +* Since ReaLearn 2.13, this is easier because it adds support for integer parameters: +** Set the xref:user-interface/main-panel/menu-bar.adoc#set-compartment-parameter-value-count[] of the parameter to 100 +** You can now treat the parameter in the formula as an integer (just `p1` instead of `p1 * 99`). +** Most importantly, ReaLearn will take care of using the correct step size automatically when setting up a mapping for controlling that parameter. +==== + +.More complex example +==== +`p1 * 3 * 100 + p2 * 99` + +This will treat xref:further-concepts/compartment.adoc#compartment-parameter[] 1 as a kind of bank selector that allows you to choose between exactly 4 banks (0, 1, 2, 3) of 100 tracks each. +xref:further-concepts/compartment.adoc#compartment-parameter[] 2 will select the track number within the bank. +You see, this is very flexible. +==== + +=== Additional object selectors for tracks + +==== selector + +Track which hosts this ReaLearn instance. +If ReaLearn is on the monitoring FX chain, this resolves to the master track of the current project. + +[#selected-selector] +==== selector + +Currently selected track. +If multiple tracks are selected, refers only to the first one. + +==== selector + +All currently selected tracks. +This makes track targets (not FX target and not send targets) do their job on _all_ selected tracks. +The feedback value always corresponds to the highest value among all selected tracks. + +CAUTION: If you select many tracks, things can become quite slow! + +==== selector + +Master track of the project which hosts this ReaLearn instance. + +* If ReaLearn is on the monitoring FX chain, this resolves to the master track of the current project. +* If you don't have ReaLearn on the monitoring FX chain, but you want to control an FX on the monitoring FX chain, this option is the right choice as well. +Make sure to enable the "Monitoring FX" checkbox. + +==== All named selector + +Allows you to use wildcards (see <>) to make track targets do their thing on all matching tracks instead of only the first one. + +==== At TCP position selector + +Like <> but just considers tracks that are visible in the track control panel. + +==== At MCP position selector + +Like <> but just considers tracks that are visible in the mixer control panel. + +==== Dynamic (TCP) selector + +Like <> but the result should be an index counting only tracks visible in the track control panel. + +==== Dynamic (MCP) selector + +Like <> but the result should be an index counting only tracks visible in the mixer control panel. + +==== By ID or name (legacy) selector + +This lets you refer to a track by its unique ID and name as fallback. +This was the default behavior for ReaLearn versions up to 1.11.0 and is just kept for compatibility reasons. + +IMPORTANT: **This selector is deprecated!** You shouldn't use it anymore. + +=== Additional target selectors for FXs + +==== selector + +Always points to the own ReaLearn FX xref:helgobox:ROOT:key-concepts.adoc#instance[]. +Perfect for changing own parameters, e.g. for usage of the conditional activation or <> features (especially important if you want to create reusable presets that make use of these features). + +[[fx-focused-selector]] +==== Focused selector + +Currently or last focused FX. _Track_ and _Input FX_ settings are ignored. + +[#particular-fx-selector] +==== Particular selector + +Lets you pick a specific FX in the FX chain. +Renaming the FX or moving it within the FX chain is fine - ReaLearn will still keep controlling exactly this FX. +Please note that this only makes sense if you address the containing track using <> as well. + +[#named-fx-selector] +==== Named selector + +Lets you address the FX by its name in the FX chain. +Just as with tracks, you can use wildcards to have a blurry search. + +[#all-named-fx-selector] +==== All named selector + +Allows you to use wildcards (see <>) to make FX targets do their thing on all matching FX instances instead of only the first one. + +==== By ID or position (legacy) selector + +This refers to the FX by its unique ID with its position as fallback. +This was the default behavior for ReaLearn versions up to 1.11.0 and is just kept for compatibility reasons. + +IMPORTANT: **This selector is deprecated!** You shouldn't use it anymore. + +=== Sticky vs. non-sticky selectors + +We call object selectors _sticky_ if they refer to a particular object (e.g. a track). + +[[sticky-selectors]] Sticky selectors:: ``, ``, `Particular` + +[[non-sticky-selectors]] Non-sticky selectors:: ``, ``, ``, ``, ``, `Named`, `All named`, `At position`, `From Playtime column` + +[[target-property]] +== Target property + +Targets can expose properties, which you can use for xref:further-concepts/glue.adoc#text-feedback[] or xref:further-concepts/glue.adoc#dynamic-feedback[]. + +Which properties are available, depends very much on the type of the target type. + +There are some properties which are available for (almost) any target (for very target-specific properties, please look up the corresponding target in xref:targets.adoc[]): + +.Common target properties +[cols="m,1,3"] +|=== +|Name | Type | Description + +| +global.realearn.time +| +Decimal +| +Time in milliseconds since ReaLearn has been loaded (the first instance). + +| +mapping.name +| +String +| +Name of the mapping. Contains the explicitly assigned mapping name, never an automatically generated one. + +| +target.text_value +| +String +| +Short text representing the current target value, including a possible unit. + +If the target value can be represented using some kind of name, this name is preferred over a possibly alternative numeric representation. Example: Let's assume the 4th track in our project is called "Guitar" and the mapping target is xref:targets/project/browse-tracks.adoc[]. Then `target.text_value` contains the text _Guitar_, not the text _4_. + +This is the default value shown if textual feedback is enabled and the textual feedback +expression is empty. + +| +target.available +| +Boolean +| +A boolean value indicating whether the target is currently available or not. + +Most targets that are _active_ are also _available_. But some targets can be _active_ and _unavailable_. Example: xref:targets/pot/preview-preset.adoc[] is not _available_ if no preview is available for the preset currently selected in Pot browser. But the target is still considered _active_ in this case! + +Usually used together with xref:further-concepts/glue.adoc#dynamic-feedback[], for example in order to display different things on displays depending on the target's availability. + +| +target.discrete_value +| +Integer +| +The current target value as zero-based integer. This only works for discrete targets. + +| +target.discrete_value_count +| +Integer +| +The number of possible values in the current target. This only works for discrete targets. + +| +target.numeric_value +| +Decimal +| +The current target value as a "human-friendly" number without its unit. + +The purpose of this placeholder is to allow for more freedom in formatting numerical target values than +when using `target.text_value`. This can be done using xref:further-concepts/glue.adoc#dynamic-feedback[]. + +| +target.numeric_value.unit +| +String +| +Contains the unit of `target.numeric_value` (e.g. _dB_). + +| +target.normalized_value +| +Decimal +| +The current target value as normalized number, that is, a value between 0.0 and 1.0 (the so-called unit interval). You can think of this number as a percentage, and indeed, it's currently always formatted as percentage. + +This value is available for most targets and good if you need a totally uniform and predictable representation of the target value that doesn't differ between target types. + +By default, this number is formatted as percentage _without_ the percent sign. Future versions of ReaLearn might offer user-defined +formatting. This will also be the preferred form to format on/off states in a +custom way (where 0% represents _off_). + +| +target.type.name +| +String +| +Short name representing the type of the mapping target. + +| +target.type.long_name +| +String +| +Long name representing the type of the mapping target. + +| +target.track.index +| +Integer +| +Zero-based index of the first resolved target track (if supported). + +| +target.track.name +| +String +| +Name of the first resolved target track (if supported). + +| +target.track.color +| +Color +| +Custom color of the first resolved target track (if supported). + +| +target.fx.index +| +Integer +| +Zero-based index of the first resolved target FX (if supported). + +| +target.fx.name +| +String +| +Name of the first resolved target FX (if supported). + +| +target.route.index +| +Integer +| +Zero-based index of the first resolved target send/receive/output (if supported). + +| +target.route.name +| +String +| +Name of the first resolved target send/receive/output (if supported). +|=== + +[[target-value-polling]] +== Target value polling + +Target value polling makes ReaLearn query the current value of a target every few milliseconds as part of the main application loop in order to send up-to-date feedback to your controller at all times. + +IMPORTANT: Target value polling is not necessary for most targets because usually ReaLearn takes advantage of REAPER's internal notification system to get notified about target value changes (which is good for performance). +For the few targets for which it is, this option is enabled by default in order to give you the best feedback experience out-of-the-box. + +Remarks: + +* For most targets that support polling, if you disable polling, automatic feedback for that target will simply stop working. +This means you will not receive up-to-date feedback anymore whenever you change the target value within REAPER itself (not using ReaLearn). +* The xref:targets/fx-parameter/set-value.adoc[] is an exception. +Automatic feedback will still work, even without _Poll for feedback_ enabled. +But in the following corner cases it might not: +** If the FX is on the monitoring FX chain. +** If you change a preset from within the FX GUI. + +[[target-activation-condition]] +== Target activation condition + +xref:key-concepts.adoc#target[Targets] can have activation conditions as well. +They are very specific to the type of the target. + +.Typical target conditions +==== +* xref:user-interface/mapping-panel/target-section.adoc#track-must-be-selected[] +* xref:user-interface/mapping-panel/target-section.adoc#fx-must-have-focus[] +==== + +[[continuous-vs-discrete-value-range]] +== Continuous vs. discrete value range + +ReaLearn xref:key-concepts.adoc#target[targets] can have a _continuous_ or _discrete_ value range. + +[[continuous-value-range]] +=== Continuous value range + +A _continuous_ value range is a range of arbitrary floating point numbers between 0.0 and 1.0. You can also think of them as _percentages_ between 0.0% and 100.0%. +Continuous value ranges don't have steps. + +.Some targets with a continuous value range. +==== +* xref:targets/track/set-volume.adoc[] +* xref:targets/project/set-tempo.adoc[] +==== + +[[discrete-value-range]] +=== Discrete value range + +A _discrete_ value range is a range of integers, e.g. 0 to 9. That would be 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. That's it! +No other values are possible. +Discrete value ranges have steps. + +.Some targets with a discrete value range. +==== +* xref:targets/fx/browse-presets.adoc[] +* xref:targets/project/browse-tracks.adoc[] +==== + +[[group-properties]] +== Group properties + +A group doesn't just have a name. +It has a few properties that are also available for mappings and thus affect all mappings in that group: + +Tags:: +Tags defined in the group will be merged with the tags in each mapping. + +Control/feedback-enabled:: +Lets you switch off control and/or feedback off for all mappings in that group. + +Activation condition:: +The activation condition that you provide here is combined with the one that you provide in the mapping. +Only if both, the group activation conditions and the mapping activation condition are satisfied, the corresponding mapping will be active. +Read more about +xref:further-concepts/mapping.adoc#conditional-activation[conditional activation] in the section about the xref:user-interface/mapping-panel.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/unit.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/unit.adoc new file mode 100644 index 0000000..5e981fe --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/further-concepts/unit.adoc @@ -0,0 +1,187 @@ += Unit concepts + +[[letting-through-events]] +== Letting through MIDI events + +ReaLearn by default "eats" incoming MIDI events for which there's at least one active mapping with that source. +In other words, it doesn't forward MIDI events which are used to control a target parameter. +However, unmatched MIDI events are forwarded! +You can change this using xref:user-interface/main-panel/let-through-section.adoc[]. + +The exact behavior differs depending on what you choose as xref:user-interface/main-panel/input-output-section.adoc#input[]: + +* If input is set to xref:user-interface/main-panel/input-output-section.adoc#fx-input[] +** MIDI events arrive from ReaLearn's FX input. +If they get forwarded, they get forwarded to the FX output, usually to the plug-in which is located right below ReaLearn FX. +The default setting often makes much sense here, especially if you put ReaLearn right above another instrument plug-in. +* If input is set to a MIDI hardware device +** MIDI events arrive directly from the MIDI hardware device. +If they get forwarded, they get forwarded to REAPER's tracks as they would usually do without ReaLearn. +If they don't get forwarded, it means they get filtered and will never make it to the tracks. +ReaLearn completely eats them, globally! +That means, ReaLearn can act as global MIDI filter. +** Please note, with input set to a real MIDI device, MIDI events coming from _FX input_ are _always_ forwarded to the FX output. +** Also, MIDI events captured from a real MIDI device input are *never* forwarded to ReaLearn's FX output. ++ +TIP: This global MIDI filter feature is only available in REAPER v6.36+. +* The checkboxes don't have any effect on computer keyboard input or OSC. +Keys are always passed through when doing text entry and never passed through if a mapping matches. + +[[auto-load]] +== Auto-load + +If you activate xref:user-interface/main-panel/preset-section.adoc#auto-load-based-on-unit-fx[], ReaLearn will start to observe the <> of this ReaLearn unit and keep loading xref:further-concepts/compartment.adoc#main-preset[main presets] according to which <> you have defined. +By default, the unit FX is set to ``, which means, it will reflect whatever FX is currently focused. +Whenever the unit FX changes, it will check if you have linked a compartment preset to it and will automatically load it. +Whenever the unit FX switches to an unlinked FX or the FX loses focus, ReaLearn falls back to the mapping list or preset that was active before activating auto-load. + +Of course this makes sense only if you actually have linked some presets. +Section <> describes how to do that. + +[[fx-to-preset-link]] +== FX-to-preset link + +A link between a FX and a xref:further-concepts/compartment.adoc#main-preset[]. +Used in <>. + +[[unit-fx-to-preset-link]] +=== Unit FX-to-preset link + +A link saved as part of a xref:key-concepts.adoc#unit[]. + +[[global-fx-to-preset-link]] +=== Global FX-to-preset link + +This is like a <> but the link is saved globally. +This is useful if you have only one controller or if you have x controllers (= and therefore x ReaLearn units) and want both of them to always auto-load the same preset if the unit FX points to the same plug-in. + +* All links will be saved _globally_, not just within this project! +* Location: REAPER resource directory (menu:Options[Show REAPER resource path in explorer/finder]) at `Data/helgoboss/realearn/auto-load-configs/fx.json`. + +[[unit-key]] +== Unit key + +Each ReaLearn unit has a key that's used to address this particular ReaLearn unit when using the <> feature. +By default, the unit key is a random cryptic string which ensures that every unit is uniquely addressable. +The result is that scanning the QR code of this ReaLearn unit will let your mobile device connect for sure with this unique unit, not with another one - remember, you can use many units of ReaLearn in parallel. +This is usually what you want. + +But a side effect is that with every new ReaLearn unit that you create, you first have to point your mobile device to it in order to see its +<> (by scanning the QR code). +Let's assume you have in many of your projects exactly one ReaLearn unit that lets your favorite MIDI controller control track volumes. +By customizing the unit key, you can tell your mobile device that it should always show the <> of this very ReaLearn unit - no matter in which REAPER project you are and even if they control the volumes of totally different tracks. + +You can achieve this by setting the unit key of each volume-controlling ReaLearn unit to exactly the same value, in each project, using xref:user-interface/main-panel/bottom-section.adoc#set-unit-data[]. +Ideally it's a descriptive name without spaces, such as "track-volumes". +You have to do the pairing only once et voilà, you have a dedicated device for monitoring your volume control ReaLearn units in each project. + +[CAUTION] +==== + +Make sure to not have more than one ReaLearn unit with the same unit key active at the same time because then it's not clear to which your mobile device will connect! +==== + +*At the moment, the unit key is part of the ReaLearn preset!* That means, opening a preset, copying/cutting a ReaLearn FX, importing from clipboard - all of that will overwrite the unit key. +This might change in future in favor of a more nuanced approach! + +[#unit-track] +== Unit track + +The second line of the bottom panel shows the current track chosen as **Unit track** for this unit of ReaLearn. +This can be something like "Track 3" or "The currently selected track". +Mappings in this ReaLearn unit can refer to this track by choosing the track selector xref:further-concepts/target.adoc#unit-selector[]. + +The unit track can be changed via xref:targets/track/track.adoc[]. + +[#unit-fx] +== Unit FX + +The second line of the bottom panel also shows the current FX chosen as **Unit FX** for this unit of ReaLearn. +This can be something like "FX 5 on track 3" or "The currently focused track". +Mappings in this ReaLearn unit can refer to this FX by choosing the FX selector xref:further-concepts/target.adoc#unit-selector[]. + +The unit FX can be changed via xref:targets/fx/fx.adoc[]. + +[[unit-tag]] +== Unit tag + +Each unit can have arbitrarily many tags. + +Tags are important if you want to dynamically enable or disable units using the xref:targets/realearn/enable-disable-units.adoc[]. + +[[projection]] +== Projection + +Projection is a quite unique feature that allows you to project a schematic representation of your currently active controller to a mobile device (e.g. a tablet computer). +You can put this device close to your controller in order to see immediately which control element is mapped to which parameter. +This is an attempt to solve an inherent problem with generic controllers: That it's easy to forget which control element is mapped to which target parameter. + +See xref:user-interface/projection.adoc[]. + +== Logging + +Logging can be enabled or disabled via xref:user-interface/main-panel/menu-bar.adoc#logging[]. + +[[logging-of-real-control-messages]] +=== Logging of real control messages + +Each log entry contains the following information: + +* Timestamp in seconds +* Helgobox xref:helgobox:ROOT:key-concepts.adoc#instance-id[] +* Message purpose +** *Real control:* A message used for controlling targets. +** *Real learn:* A message used for learning a source. +* Actual message (MIDI messages will be shown as hexadecimal byte sequence, short MIDI messages also as decimal byte sequence and decoded) +* Match result +** *unmatched:* The message didn't match any mappings. +** *matched:* The message matched at least one of the mappings. +** *consumed:* Only for short MIDI messages. +This short message is part of a (N)RPN or 14-bit CC message and there's at least one active mapping that has a (N)RPN or 14-bit CC source. +That means it will not be processed. +The complete (N)RPN or 14-bit CC message will be. + +[[logging-of-real-feedback-messages]] +=== Logging of real feedback messages + +The log entries look similar to the ones described above, with the following notable differences. + +* Message purpose +** *Feedback output:* A message sent to your controller as response to target value changes. +** *Lifecycle output:* A message sent to your controller as response to mapping activation/deactivation (see xref:user-interface/mapping-panel/advanced-settings-dialog.adoc#mapping-lifecycle-actions[Mapping lifecycle actions]). +** *Target output:* A message sent because of either the xref:targets/midi/send-message.adoc[] or xref:targets/osc/send-message.adoc[]. + +[[superior-units]] +== Superior units + +When a unit is made superior via menu entry xref:user-interface/main-panel/menu-bar.adoc#make-unit-superior[], this unit is allowed to suspend other units which share the same input and/or output device (hardware devices only, not FX input or output!). + +[TIP] +==== +Making units superior is **rarely needed**! + +This option was initially introduced in order to add more flexibility to the <> feature. +The idea was to let a controller _fall back_ to some default behavior if the currently focused FX is closed. +Multiple instances were necessary to make this work with one of them (the auto-load instance) being superior. + +However, since ReaLearn 2.14.0, falling back to initial mappings when the FX loses focus in auto-load mode became much easier and doesn't require multiple units anymore! +Your initial mappings or initial preset will be memorized and reloaded once the FX loses focus. +See <> for more information. +==== + +Behavior: + +* By default, ReaLearn units are not superior, just normal. +This is most of the time okay, even if you have multiple units that share the same input and output ... as long as you don't have any conflicting mappings active at the same time. +* For example, if 2 units use the same input or output device, and they use different control elements, they can peacefully coexist. +And even if they share a control element for the _control direction_, they are still fine with it. +The same control element will control 2 mappings, why not! +* Things start to get hairy as soon as 2 units want to send _feedback_ to the same control elements at the same time. +You should avoid this. +You should not even do this within one ReaLearn unit. +This can't work. +* Sometimes you want one unit to suspend/cover/cancel/mute another one! +You can do this by making this unit _superior_. +Then, whenever this unit has at least one active mapping, all non-superior units with the same control and/or feedback device will be disabled for control and/or feedback. +* You can have multiple superior units. +Make sure they get along with each other :) \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow.adoc new file mode 100644 index 0000000..b9024af --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow.adoc @@ -0,0 +1,3 @@ += Glue signal flow + +The diagrams in this section illustrate how signals travel through the xref:key-concepts.adoc#glue[] section of a mapping. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow/control.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow/control.adoc new file mode 100644 index 0000000..e8ba720 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow/control.adoc @@ -0,0 +1,59 @@ += Control direction + +The diagrams in this section illustrate how control values travel through the xref:key-concepts.adoc#glue[] section of a mapping, from source to target. + +== Absolute control values + +The following diagrams apply whenever the source emits xref:further-concepts/mapping.adoc#absolute-control-value[absolute] control values. + +=== Absolute mode "Normal" + +See xref:user-interface/mapping-panel/glue-section.adoc#absolute-mode-normal[]. + +image::realearn/diagrams/control-flow/target/control-flow-absolute-normal.svg[] + +=== Absolute mode "Toggle button" + +See xref:user-interface/mapping-panel/glue-section.adoc#toggle-button-mode[]. + +image::realearn/diagrams/control-flow/target/control-flow-absolute-toggle.svg[] + +=== Absolute mode "Incremental button" + +See xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[]. + +include::partial$under-construction.adoc[] + +image::realearn/diagrams/control-flow/target/control-flow-absolute-incremental.svg[] + +=== Absolute mode "Performance control" + +See xref:user-interface/mapping-panel/glue-section.adoc#performance-control[]. + +include::partial$under-construction.adoc[] + +image::realearn/diagrams/control-flow/target/control-flow-absolute-performance.svg[] + +=== Absolute mode "Make relative" + +See xref:user-interface/mapping-panel/glue-section.adoc#make-relative[]. + +include::partial$under-construction.adoc[] + +image::realearn/diagrams/control-flow/target/control-flow-absolute-make-relative.svg[] + +== Relative control values + +The following diagrams apply whenever the source emits xref:further-concepts/mapping.adoc#relative-control-value[relative] control values. + +=== "Make absolute" disabled + +image::realearn/diagrams/control-flow/target/control-flow-relative-normal.svg[] + +=== "Make absolute" enabled + +See xref:user-interface/mapping-panel/glue-section.adoc#make-absolute[]. + +include::partial$under-construction.adoc[] + +image::realearn/diagrams/control-flow/target/control-flow-relative-make-absolute.svg[] diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow/feedback.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow/feedback.adoc new file mode 100644 index 0000000..9f6c711 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/glue-signal-flow/feedback.adoc @@ -0,0 +1,5 @@ += Feedback direction + +The diagrams in this section illustrate how feedback values travel through the xref:key-concepts.adoc#glue[] section of a mapping, from target to source. + +include::partial$under-construction.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/installation.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/installation.adoc new file mode 100644 index 0000000..60e7dc1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/installation.adoc @@ -0,0 +1,3 @@ += Installation + +See xref:helgobox::installation.adoc[installation instructions for Helgobox], the plug-in that contains ReaLearn. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/introduction.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/introduction.adoc new file mode 100644 index 0000000..1dc72cd --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/introduction.adoc @@ -0,0 +1,24 @@ += Introduction + +include::helgobox::partial$intro-header.adoc[] + +|=== +|Last update of text: |`2026-01-28 (v2.18.3-pre.1)` +|Last update of screenshots: |`2025-03-15 (v2.18.0)` +|=== + +link:https://www.helgoboss.org/projects/realearn[ReaLearn] is a versatile controller integration tool for link:https://www.reaper.fm[REAPER]. +It is part of the link:https://www.helgoboss.org/projects/helgobox[Helgobox] plug-in. + +This reference provides a detailed description of ReaLearn's user interface, concepts and functionalities. +Use it whenever you need an in-depth exploration of a specific feature or functionality in ReaLearn. + +[IMPORTANT] +==== + +**If you are a beginner, please use the link:https://github.com/helgoboss/helgobox/wiki/ReaLearn-Home[ReaLearn Wiki] instead!** + +This reference is targeted at users who are already familiar with the ReaLearn basics and want to look up specific information. +The Wiki is perfect if you are just starting off. +It guides you through the very basics and contains links to easily digestible video tutorials. +==== diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/key-concepts.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/key-concepts.adoc new file mode 100644 index 0000000..b37df45 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/key-concepts.adoc @@ -0,0 +1,169 @@ += Key concepts + +This section offers brief descriptions of ReaLearn's key concepts. +A solid understanding of these concepts is essential for effectively using ReaLearn, regardless of which features you plan to utilize. + +[[control]] +== Control + +In ReaLearn, the term _control_ typically refers to the process of triggering or adjusting something in REAPER, such as executing an action or modifying an FX parameter. + +[[feedback]] +== Feedback + +In ReaLearn, the term _feedback_ refers to controlling LEDs, motorized faders, or displays on your device in response to events in REAPER, such as a track volume change. + +== Controller + +Most of the time, when talking about _controllers_ in the documentation, we are talking about controllers in the _general_ sense. + +[[controller,Controller]] +=== Controller in the general sense + +A _controller_ in the general sense is the device you use to control REAPER. +It is usually a hardware device, such as a MIDI keyboard or control surface, but it can also be software, like an OSC app. + +Controllers are normally not specifically managed within ReaLearn, because you just need to set an <> and and <>, and that's all ReaLearn needs to know. An exception are <>. + +[[managed-controller]] +=== Managed controller + +Only recently, ReaLearn introduced the concept of xref:further-concepts/instance.adoc#auto-units[], in which controllers can actually be managed in a xref:user-interface/settings-dialog.adoc#controllers[list]. ReaLearn then automatically creates <> depending on which of these controllers are connected. Such controllers are called _managed controllers_. + +[[control-element]] +== Control element + +A control element is any component you can use to control something. +In most cases, it's a physical part of your hardware <>. + +Examples include knobs, encoders, faders, buttons, keys, pads, pitch wheels and acceleration sensors. + +[[control-element-interaction]] +== Control element interaction + +A control element _interaction_ is the act of using a <>. + +Typically, each control element has one primary interaction type: + +* _Turning_ a knob +* _Pressing/releasing_ a button +* _Moving_ a fader + +However, some control elements allow multiple interactions: + +* _Moving_ a touch-sensitive fader +* _Touching/releasing_ a touch-sensitive fader + +In this reference, <> often implies <>, as they are usually synonymous. + +[[feedback-element]] +== Feedback element + +A _feedback element_ is any part of your <> that can indicate or display information. + +Examples includes LEDs, motor faders and displays. + +Very frequently, control elements and feedback elements are combined: + +- Button with an integrated LED +- Encoder with an LED ring +- Motorized fader + +For this reason, this reference sometimes uses <> to refer to both the <> and the corresponding <>. + +[[input-port]] +== Input port + +To enable control, ReaLearn needs to respond to events from your <>. +It achieves this by listening to events from an _input port_, which can be a MIDI device port, an OSC port or your computer keyboard. + +You can change the input port using the xref:user-interface/main-panel/input-output-section.adoc#input[]. + +[[output-port]] +== Output port + +To send <> back to your <>, ReaLearn transmits instructions through an _output port_, which can be a MIDI device port or an OSC port. + +You can change the output port using the xref:user-interface/main-panel/input-output-section.adoc#output[]. + +[[instance]] +== ReaLearn Instance + +The terms _ReaLearn Instance_ and xref:helgobox:ROOT:key-concepts.adoc#instance[Helgobox instance] are interchangeable — they both refer to the same concept. + +[[unit]] +== Unit + +Each ReaLearn <> contains at least one _unit_, known as the _main unit_, but it can also contain an arbitrary number of additional units. + +Units function like "mini instances" within a single ReaLearn <>, allowing that instance to manage multiple controllers simultaneously. +Each unit has its own <>, <>, <>, <>, xref:further-concepts/compartment.adoc#controller-preset[], xref:further-concepts/compartment.adoc#main-preset[], and more. + +[[compartment]] +== Compartment + +Each unit consists of two compartments. +A compartment is a self-contained list of mappings that can be saved as an independent preset. +The two compartments in each unit are: + +[[main-compartment]] Main compartment:: +This is the primary compartment. +Its purpose is to define what the controller device should do, e.g., allowing a fader to control track volume or displaying the name of an FX parameter on a hardware display. ++ +We refer to the mappings in this compartment as [[main-mapping,Main mapping]] _main mappings_ and to the presets as _main presets_. + +[[controller-compartment]] Controller compartment:: +The controller compartment is optional and serves two main purposes: Describing all control elements of the controller, assigning them descriptive names and enabling xref:further-concepts/compartment.adoc#virtual-control[]. ++ +We refer to the mappings in this compartment as [[controller-mapping,Controller mapping]] _controller mappings_ and to the presets as _controller presets_. + +[#mapping] +== Mapping + +Each compartment contains a list of mappings. + +A _mapping_ connects a <> and/or <> on your <> with an action or parameter in REAPER. + +Each mapping consists of <>, <> and <>. + +[[source]] +== Source + +A _source_ is the part of a <> that typically describes a <> and/or <> on the <>. +More generally, it can be anything that emits xref:further-concepts/mapping.adoc#control-value[control values]. + +Examples: MIDI source, OSC source + +[[glue]] +== Glue + +A _glue_ is the part of a <> that sits between <> and <>, filtering and transforming <> and <> streams. + +This is a very powerful feature of ReaLearn, as can be seen by looking at the xref:glue-signal-flow.adoc[]. + +[[target]] +== Target + +A _target_ is the part of the <> that describes the thing which should be controlled and/or provides feedback data. + +Examples: Track volume, cursor position, REAPER action + +[[learning]] +== Learning + +This section wouldn't be complete without mentioning the concept that inspired ReaLearn's name: _Learning_. +Learning simply means that you press a btn:[Learn] button instead of performing manual setup, saving you valuable time! + +In ReaLearn, you can learn <> and <>. + +[[learn-source]] +=== Learn source + +Sources can be learned by pressing the xref:user-interface/main-panel/mapping-row.adoc#learn-source[] and then touching a <> on your controller. +This saves you from the tedious job of setting up MIDI or OSC sources manually. + +[[learn-target]] +=== Learn target + +Targets can be learned by pressing the xref:user-interface/main-panel/mapping-row.adoc#learn-target[] and then invoking a <> within REAPER. +This saves you from choosing xref:further-concepts/target.adoc#target-object-selectors[] and other stuff manually. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/reaper-actions.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/reaper-actions.adoc new file mode 100644 index 0000000..ab3a18c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/reaper-actions.adoc @@ -0,0 +1,26 @@ += REAPER actions + +ReaLearn provides the following REAPER actions in addition to the xref:helgobox::reaper-actions.adoc[general actions]. + +Helgobox/ReaLearn: Find first mapping by source:: This action will ask you to touch some control element. +As soon as you touch a control element which is mapped, it will open the mapping panel for the corresponding mapping. +It will search within all ReaLearn instances/units loaded in your current project as well as the ones on the monitoring FX chain. + +Helgobox/ReaLearn: Find first mapping by target:: This action is similar to _Find first mapping by source_. +It asks you to touch some (learnable) REAPER parameter. +As soon as you touch one that is mapped, it will open its mapping panel. + +Helgobox/ReaLearn: Learn single mapping (reassigning source):: Asks you to touch a control element and target and adds a new mapping in the first ReaLearn unit that it encounters. +It prefers units in the current project over monitoring FX. +It automatically chooses the unit with the correct MIDI/OSC input. +If there's a unit which already has that source assigned, it will be reassigned to the new target that you touched. + +Helgobox/ReaLearn: Learn single mapping (reassigning source) and open it:: Like _Learn single mapping_ but additionally opens the mapping panel after having learned the mapping. +This is great for subsequent fine-tuning. + +Helgobox/ReaLearn: Learn source for last touched target (reassigning target):: This behaves similar to REAPER's built-in MIDI learn in that it always relates to the target that has been touched last. + +[[send-feedback-for-all-instances]] Helgobox/ReaLearn: Send feedback for all instances:: Makes each ReaLearn instance/unit in all project tabs send feedback for all mappings. +That shouldn't be necessary most of the time because ReaLearn usually sends feedback automatically, but there are situations when it might come in handy. + +[[auto-detect-controllers-with-logging]] Helgobox/ReaLearn: Auto-detect controllers (with logging):: Looks at each currently connected MIDI device and automatically creates a xref:key-concepts.adoc#managed-controller[] if the device is known and a suitable preset exists. Also logs information that helps troubleshoot controller detection issues. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources.adoc new file mode 100644 index 0000000..aaee6c1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources.adoc @@ -0,0 +1,3 @@ += Sources + +ReaLearn supports the following xref:key-concepts.adoc#source[] types. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/keyboard.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/keyboard.adoc new file mode 100644 index 0000000..1f4ca36 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/keyboard.adoc @@ -0,0 +1,22 @@ += Source "Keyboard" + +This source reacts to pressing or releasing a key on your computer keyboard. +It emits a value of 100% when the key is pressed and 0% when released. + +In order to set the key, simply click the btn:[Learn] button and press the key of your choice. + +In addition to the key label, ReaLearn might show some warnings regarding the portability of your keystroke. +This helps you to avoid keyboard shortcuts that don't reliably work cross-platform (in other operating systems) or on other keyboard layouts. +You can ignore portability warnings if you use just this operating system and don't plan to share your keyboard presets with other users. + +[TIP] +==== +* This only works if xref:user-interface/main-panel/input-output-section.adoc#computer-keyboard[] is enabled in the xref:user-interface/main-panel/input-output-section.adoc#input[]. +* If you hold a key, it will not keep firing. +This is by design! +Use xref:user-interface/mapping-panel/glue-section.adoc#fire-after-timeout-keep-firing[] instead. +* Key combinations are not supported. +This is by design! +Use xref:further-concepts/mapping.adoc#conditional-activation[] instead. +* Matching keys are automatically consumed by ReaLearn, so that they will not reach the rest of REAPER (unless a text field is focused). Check out xref:user-interface/main-panel/menu-bar.adoc#match-even-inactive-mappings[] to fine-tune matching behavior. +==== diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi.adoc new file mode 100644 index 0000000..f289aaf --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi.adoc @@ -0,0 +1,9 @@ += MIDI sources + +Most types in the MIDI category have the following UI elements in common. + +Channel menu:: +Optionally restricts this source to messages from a certain MIDI channel. +Only available for sources that emit MIDI channel messages. + +The remaining UI elements in this section depend on the chosen source type. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/cc-value.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/cc-value.adoc new file mode 100644 index 0000000..bbf71b3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/cc-value.adoc @@ -0,0 +1,17 @@ += Source "CC value" + +This source reacts to incoming MIDI control-change messages. + +== CC menu + +Optionally restricts this source to messages with a certain MIDI control-change controller number. + +== Character menu + +See xref:further-concepts/source.adoc#midi-source-character[]. + +== 14-bit values checkbox + +If unchecked, this source reacts to MIDI control-change messages with 7-bit resolution (usually the case). +If checked, it reacts to MIDI control-change messages with 14-bit resolution. +This is not so common but sometimes used by controllers with high-precision faders. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/channel-after-touch.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/channel-after-touch.adoc new file mode 100644 index 0000000..8bb4407 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/channel-after-touch.adoc @@ -0,0 +1,4 @@ += Source "Channel after touch" + +This source reacts to incoming MIDI channel-pressure messages. +The higher the pressure, the higher the absolute control value. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/display.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/display.adoc new file mode 100644 index 0000000..cbc8b0f --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/display.adoc @@ -0,0 +1,33 @@ += Source "Display" + +This is a feedback-only source used to display text on MIDI-controllable hardware displays (LCDs, OLED displays, 7-segment displays, ...). + +== Protocol menu + +Lets you choose the display protocol, which tells ReaLearn how it should communicate with the hardware display and which options it supports. + +Mackie LCD:: Use this for MCU-compatible LCDs. +Depending on your particular control surface, there can be up to 8 LCDs, each of which has up to 2 lines. +Mackie XT LCD:: Use this to control the displays of MCU XT devices (= control surface extenders, which provide additional faders and displays). +X-Touch Mackie LCD:: Like _Mackie LCD_ but also supports colors on certain X-Touch devices. +X-Touch Mackie XT LCD:: Like _Mackie LCD XT_ but also supports colors on certain X-Touch devices. +Mackie 7-segment display:: Use this for MCU-compatible 7-segment displays (you know, the ones which only show digits). +There's usually one small assignment display and a larger one for showing the time code. +SiniCon E24:: Use this with the https://www.sinicon.io/[SiniCon E24 controller]. +Launchpad Pro - Scrolling text:: Displays looped scrolling text on a Novation Launchpad Pro. +Only seems to work if you set _Output_ to `MIDIOUT2 (Launchpad Pro)`. +Studiologic SL Keyboard display:: Displays text on the display of Studiologic SL keyboards (tested with SL88). + +== Display menu + +Choose the particular display or display portion to which you want to send text. + +== Line menu + +Choose the line number. + +CAUTION: For controllers with multiple displays and lines, ReaLearn allows you to spread your text over all available displays and lines. +This is great if you need to display a lot of text but one display doesn't provide enough space. +But be aware: xref:further-concepts/general.adoc#feedback-relay[] doesn't work nicely anymore if you make use of this feature. + +If you want to know how to define which text shall be sent to the displays, please see xref:further-concepts/glue.adoc#text-feedback[textual feedback] in the xref:user-interface/mapping-panel/glue-section.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-clock-tempo.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-clock-tempo.adoc new file mode 100644 index 0000000..fef0a4a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-clock-tempo.adoc @@ -0,0 +1,14 @@ += Source "MIDI clock tempo" + +This source reacts to incoming MIDI clock (MTC) tempo messages. +These are metronome-beat-like messages which can be regularly transmitted by some DAWs and MIDI devices. +The frequency with which this message is sent dictates the tempo. + +The higher the calculated tempo, the higher the absolute control value. +A tempo of 1 bpm will be translated to a control value of 0%, a tempo of 960 bpm to 100% (this corresponds to REAPER's supported tempo range). + +This source can be used in combination with the xref:targets/project/set-tempo.adoc[] to obtain a "poor man's" tempo synchronization. + +Be aware: MIDI clock naturally suffers from certain inaccuracies and latencies - that's an issue inherent to the nature of the MIDI clock protocol itself. +E.g. it's not really suitable if you need super accurate and instant tempo synchronization. +Additionally, ReaLearn's algorithm for calculating the tempo could probably be improved (that's why this source is marked as experimental). \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-clock-transport.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-clock-transport.adoc new file mode 100644 index 0000000..0b5cbdf --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-clock-transport.adoc @@ -0,0 +1,8 @@ += Source "MIDI clock transport" + +This source reacts to incoming MIDI clock (MTC) transport messages. +These are simple start, continue and stop messages which can be sent by some DAWs and MIDI devices. + +== Message menu + +The specific transport message to which this source should react. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-script.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-script.adoc new file mode 100644 index 0000000..a54a09c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/midi-script.adoc @@ -0,0 +1,23 @@ += Source "MIDI Script" + +[[source-midi-script]] +This source is feedback-only and exists for enabling more complex feedback use cases such as controlling LCDs that are not yet supported by the xref:sources/midi/display.adoc[]. +It lets you write an EEL or Luau script that will be executed whenever ReaLearn "feels" like it needs to send some feedback to the MIDI device. + +== Kind menu + +Whether to use the EEL or Luau language. + +== Script field + +The script. +Is disabled if the script contains more than one line. + +See xref:further-concepts/source.adoc#midi-source-script[] for details. + +== More button (…) + +Opens the script in a separate window (for multi-line scripts). + +TIP: Prefer the xref:sources/midi/display.adoc[] over this one whenever possible. +It's easier to use. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/note-number.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/note-number.adoc new file mode 100644 index 0000000..42296e6 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/note-number.adoc @@ -0,0 +1,6 @@ += Source "Note number" + +This source reacts to incoming MIDI note-on messages. +The higher the note number (= key on a MIDI keyboard), the higher the absolute control value. + +This essentially turns your MIDI keyboard into a "huge fader" with the advantage that you can jump to any value at any time. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/note-velocity.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/note-velocity.adoc new file mode 100644 index 0000000..b4a5e95 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/note-velocity.adoc @@ -0,0 +1,9 @@ += Source "Note velocity" + +This source reacts to incoming MIDI note-on and note-off messages. +The higher the velocity of the incoming note-on message, the higher the absolute control value. +Note-off messages are always translated to 0%, even if there's a note-off velocity. + +== Note menu + +Optionally restricts this source to messages with a certain note number (note numbers represent keys on the MIDI keyboard, e.g. 60 corresponds to C4). \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/nrpn-value.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/nrpn-value.adoc new file mode 100644 index 0000000..026138a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/nrpn-value.adoc @@ -0,0 +1,28 @@ += Source "(N)RPN value" + +This source reacts to incoming non-registered (NRPN) or registered (RPN) MIDI parameter-number messages. +The higher the emitted value, the higher the absolute control value. + +(N)RPN messages are not widely used. +If they are, then mostly to take advantage of their ability to transmit 14-bit values (up to 16384 different values instead of only 128), resulting in a higher resolution. + +== RPN checkbox + +If unchecked, this source reacts to unregistered parameter-number messages (NRPN). +If checked, it reacts to registered ones (RPN). + +== Number field + +The number of the registered or unregistered parameter-number message. +This is a value between 0 and 16383. + +== 14-bit values checkbox + +If unchecked, this source reacts to (N)RPN messages with 7-bit resolution, including increment/decrement messages. + +If checked, it reacts to those with 14-bit resolution. +In practice, this if often checked. + +== Character menu + +See xref:further-concepts/source.adoc#midi-source-character[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/pitch-wheel.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/pitch-wheel.adoc new file mode 100644 index 0000000..4393b5c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/pitch-wheel.adoc @@ -0,0 +1,5 @@ += Source "Pitch wheel" + +This source reacts to incoming MIDI pitch-bend change messages. +The higher the pitch-wheel position, the higher the absolute control value. +The center position corresponds to an absolute control value of 50%. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/polyphonic-after-touch.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/polyphonic-after-touch.adoc new file mode 100644 index 0000000..30c0dc4 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/polyphonic-after-touch.adoc @@ -0,0 +1,8 @@ += Source "Polyphonic after touch" + +This source reacts to incoming MIDI polyphonic-key-pressure messages. +The higher the pressure, the higher the absolute control value. + +== Note menu + +Optionally restricts this source to messages with a certain note number. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/program-change.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/program-change.adoc new file mode 100644 index 0000000..28fb007 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/program-change.adoc @@ -0,0 +1,4 @@ += Source "Program change" + +This source reacts to a range of incoming MIDI program-change messages. +The higher the program number, the higher the absolute control value. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/raw-midi-sysex.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/raw-midi-sysex.adoc new file mode 100644 index 0000000..47b8f0d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/raw-midi-sysex.adoc @@ -0,0 +1,10 @@ += Source "Raw MIDI / SysEx" + +[[source-midi-raw]] +This source primarily deals with system-exclusive MIDI messages. +It supports both control and feedback direction! + +== Pattern field + +Pattern describing the raw MIDI message. +See xref:further-concepts/mapping.adoc#raw-midi-pattern[]. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/specific-program-change.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/specific-program-change.adoc new file mode 100644 index 0000000..b5e0739 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/midi/specific-program-change.adoc @@ -0,0 +1,5 @@ + += Source "Specific program change" + +This source reacts to MIDI program-change messages with a specific program. +This is a trigger-only source, that means it always fires 100% (whenever the program number corresponds to the configured one). diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/none.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/none.adoc new file mode 100644 index 0000000..25a923a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/none.adoc @@ -0,0 +1,6 @@ += Source "None" + +A special kind of source that will never emit any events. +It's intended to be used on mappings which are not supposed to be controlled directly but only via xref:user-interface/mapping-panel/glue-section.adoc#group-interaction[]. + +[split=1] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/osc.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/osc.adoc new file mode 100644 index 0000000..757c45d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/osc.adoc @@ -0,0 +1,66 @@ += Source "OSC" + +OSC sources allow configuration of the following aspects: + +== Address field + +This needs to correspond exactly to the address of the corresponding control element on your OSC device. + +Example: `/1/fader1` + +You don't need to figure that out yourself, just use the btn:[Learn] button. + +== Argument section + +Each OSC message consists of an arbitrary number of arguments. +In most cases, e.g. with faders, knobs or buttons, it's just one argument. +X/Y controls often send 2 arguments, one for each axis. +There are rare cases in which messages have even more arguments. + +=== Argument number menu + +The first dropdown menu allows you to choose the number of the argument that ReaLearn should look at and process. `1` denotes the first argument, `2` the second one, and so on. + +=== Argument type menu + +The second dropdown menu lets you choose the argument type which ReaLearn should use to construct a proper feedback message. + +This is usually the same type as the one used for control direction. +For control direction, choosing an explicit type is irrelevant because ReaLearn handles whatever type arrives automatically in the best possible way. + +If you use _Learn_, the type is filled automatically. +* The value to be sent will be derived from the type (see <>): + +[cols="m,m"] +|=== +| Type | Property + +| Float | value.float +| Double | value.double +| Int | value.int +| Long | value.long +| Bool | value.bool +| Nil | nil +| Inf | inf +| String | value.string +| Color | style.color +|=== + +If you want more control over what feedback values are sent, use the <> field. + +== Range field + +Values of argument types _Float_ and _Double_ are by default interpreted as decimal values between 0.0 and 1.0. You can change that by entering a different value range here. +Even negative numbers are allowed. + +Customizing the value range is especially important for argument types _Int_ and _Long_ because they don't have a standard value range. + +== Is relative checkbox + +Some messages transmitted by OSC devices are meant to be interpreted as relative increments/decrements instead of absolute values, e.g. jog wheels. +When you enable this checkbox, ReaLearn will treat each received _1_ value as an increment and _0_ value a decrement. + +[#feedback-arguments] +== Feedback arguments field + +Allows you to modify the xref:further-concepts/source.adoc#osc-feedback-arguments-expression[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper.adoc new file mode 100644 index 0000000..ac0f15c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper.adoc @@ -0,0 +1 @@ += REAPER sources \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/midi-device-changes.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/midi-device-changes.adoc new file mode 100644 index 0000000..d66c835 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/midi-device-changes.adoc @@ -0,0 +1,4 @@ += Source "MIDI device changes" + +This source emits a value of 100% whenever any MIDI device is connected and 0% whenever any MIDI device is disconnected. +You can map this to the REAPER action "Reset all MIDI devices" to achieve true plug and play of MIDI devices (provided the corresponding device has been enabled at least once in REAPER's MIDI device preferences). \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/realearn-parameter.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/realearn-parameter.adoc new file mode 100644 index 0000000..2b3b0fb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/realearn-parameter.adoc @@ -0,0 +1,8 @@ += Source "ReaLearn parameter" + +This source fires whenever a selected xref:further-concepts/compartment.adoc#compartment-parameter[] changes its value. + +One of many ways to use this is to create macro parameters which control multiple parameters of multiple other plug-ins. + +CAUTION: This is one of the sources that can't participate in rendering. +So it's important to write down automation *before* rendering. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/realearn-unit-start.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/realearn-unit-start.adoc new file mode 100644 index 0000000..6c65a4d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/realearn-unit-start.adoc @@ -0,0 +1,4 @@ += Source "ReaLearn unit start" + +This source fires (emits a value of 100%) when ReaLearn starts. +It can be used to execute an actions or restore certain states on REAPER startup or project load. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/speech.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/speech.adoc new file mode 100644 index 0000000..c5da2cc --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/speech.adoc @@ -0,0 +1,11 @@ += Source "Speech" + +This source works for xref:key-concepts.adoc#feedback[] only. +It uses the native Windows or macOS text-to-speech engine to speak out any feedback value. + +== Troubleshooting + +If you don't hear anything, check the xref:user-interface/main-panel/bottom-section.adoc[] if unit-wide feedback is currently off. +If yes, maybe xref:user-interface/main-panel/menu-bar.adoc#send-feedback-only-if-track-armed[] is enabled but your track is not armed. + +Also keep in mind that ReaLearn only resends feedback when it changed. Therefore, the speech source will only say something when the target value actually changes. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/timer.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/timer.adoc new file mode 100644 index 0000000..aef6377 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/reaper/timer.adoc @@ -0,0 +1,3 @@ += Source "Timer" + +This source fires (emits a value of 100%) repeatedly every _n_ milliseconds. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/stream-deck.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/stream-deck.adoc new file mode 100644 index 0000000..fbc5cb4 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/stream-deck.adoc @@ -0,0 +1,156 @@ += Source "Stream Deck" + +With this source, you can leverage your Stream Deck with ReaLearn, for example the link:https://www.elgato.com/en/en/p/stream-deck-mk2-black[Elgato Stream Deck MK.2]. +This includes reacting to buttons and sending xref:key-concepts.adoc#feedback[] to the button displays. + +CAUTION: This source is experimental. Behavioral changes are still possible! + +== Supported devices + +The following StreamDeck devices are currently supported: + +* Original +* Original v2 +* Mini +* Revised Mini +* XL +* MK.2 + +== Setup + +=== General + +**Make sure that the official Stream Deck software is not running!** + +ReaLearn connects to your Stream Deck directly via USB. +Only one software can connect to the device, so it's _either_ ReaLearn _or_ the Stream Deck software. + +[[linux]] +=== Linux + +If you use Stream Deck on Linux, you also need to explicitly allow normal users access to the device, otherwise ReaLearn won't be able to connect to it. + +==== 1. Find the Vendor and Product ID + +Plug in the Stream Deck and identify its vendor and product IDs: + +[source,bash] +---- +lsusb +---- + +Look for a line similar to: + +`Bus 001 Device 002: ID 0fd9:0080 Elgato Systems GmbH` + +In this example, `0fd9` is the vendor ID, and `0080` is the product ID (these may vary). + +==== 2. Create a udev Rule + +Create a new udev rule file in `/etc/udev/rules.d/`: + +[source,bash] +---- +sudo nano /etc/udev/rules.d/99-streamdeck.rules +---- + +Add the following rule, replacing `0fd9` and `0080` with the correct vendor and product IDs if necessary: + +[source] +---- +SUBSYSTEM=="usb", ATTR{idVendor}=="0fd9", ATTR{idProduct}=="0080", MODE="0660", GROUP="plugdev" +KERNEL=="hidraw*", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="0080", MODE="0660", GROUP="plugdev" +---- + +This rule sets permissions to `0660`, allowing the device owner and users in the `plugdev` group access to the device with the specified vendor and product IDs. + +==== 3. Reload udev Rules + +Reload the udev rules to apply changes: + +[source,bash] +---- +sudo udevadm control --reload-rules +---- + +==== 4. Unplug and Replug the Device + +Unplug the Stream Deck and plug it back in to apply the new rule. +Alternatively, you can execute `sudo udevadm trigger`. + +== User interface + +=== Button menu + +Select the button that you want to use to control or display something. + +=== Back menu + +Allows you to choose the desired button backgrounds. +Unlike the button foreground, the button background is always static. +It doesn't react to feedback value changes. + +Color:: +Uses a solid color as button background, by default black. +You can change the color via xref:user-interface/mapping-panel/glue-section.adoc#feedback-style[], that means the background color can even be dynamic, e.g. reflect the currently selected track's color. + +Image:: +Uses a PNG image as button background. ++ +The image should ideally be a square. +If not, some parts of the image will not be visible. +The ideal dimensions of that square depends on your specific device. +On most Stream Decks, it's 72x72 pixels. +But ReaLearn will automatically scale the image. +If in doubt, prefer _larger_ image sizes. +Scaling down gives you a better quality than scaling up. ++ +If the image has transparent parts, the color defined in xref:user-interface/mapping-panel/glue-section.adoc#feedback-style[] will shine through. + +TIP: When picking images to be displayed on your Stream Deck, prefer images that are somewhere within your REAPER resource path (REAPER → Options → Show REAPER resource path in explorer/finder), e.g. toolbar icons. +That makes ReaLearn save it as relative path, so your mapping becomes more portable (which is important if you want to share presets). + +=== Front menu + +Allows you to choose the desired button foreground, if desired. +A foreground is usually dynamic, that means it can change depending on the target value. + +None:: +No foreground. +Choose this if all you want to display is a static background. + +Color fade:: +Paints a color over the background with an opacity that depends on the target value. +For example, if the target value is at 10%, the color will be almost transparent. +It will fade depending The color is defined in xref:user-interface/mapping-panel/glue-section.adoc#feedback-style[]. + +Image fade:: +Paints an image over the background with an opacity that depends on the target value. +In terms of sizing, it behaves exactly like a background image. + +Image slide:: +Paints the portion of an image over the background. +Which portion, depends on the target value. +Landscape images slide horizontally, portrait images slide vertically. ++ +This type of foreground is perfect for making use of REAPER's toolbar images. +They are actually sprites, that means they contain multipe images in one file, at different horizontal positions. + +Full bar:: +Paints a full-sized bottom-up half-transparent bar over the background, indicating the current target value. ++ +Good in combination with targets that have a continuous range, such as xref:targets/track/set-volume.adoc[] or xref:targets/track/peak.adoc[]. + +Knob:: +Paints a knob over the background, indicating the current target value. + +=== Default text field + +Here you can enter static text that should be displayed over the background and foreground. +ReaLearn will automatically choose a contrast color or dim the background so that the text always stays readable. + +You can press btn:[...] to enter multi-line text. +ReaLearn supports up to 4 lines of text. + +You can also display dynamic text from the target! +See xref:user-interface/mapping-panel/glue-section.adoc#feedback-style[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/virtual.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/virtual.adoc new file mode 100644 index 0000000..aa08a20 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/sources/virtual.adoc @@ -0,0 +1,20 @@ += Source "Virtual" + +See xref:further-concepts/source.adoc#virtual-source[]. + +== Type menu + +See xref:further-concepts/compartment.adoc#virtual-control-element-type[]. + +== ID section + +See xref:further-concepts/compartment.adoc#virtual-control-element-id[]. + +=== Pick menu + +The convenient picker provides IDs from standardized xref:further-concepts/compartment.adoc#virtual-control-scheme[virtual control schemes]. +It also contains a category for accessing the virtual control elements that are currently exposed by the xref:key-concepts.adoc#controller-compartment[]. + +=== ID field + +Lets you enter the ID manually. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets.adoc new file mode 100644 index 0000000..ed27bcb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets.adoc @@ -0,0 +1 @@ += Targets \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-chain.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-chain.adoc new file mode 100644 index 0000000..0b609eb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-chain.adoc @@ -0,0 +1 @@ += FX chain targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-chain/browse-fxs.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-chain/browse-fxs.adoc new file mode 100644 index 0000000..fa1aafb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-chain/browse-fxs.adoc @@ -0,0 +1,8 @@ += Target "FX chain: Browse FXs" + +Steps through the FX instances in the FX chain by always having exactly one FX instance visible. +To be used with endless rotary encoders or previous/next-style "Incremental buttons". + +== Display menu + +Here you can decide if you want to display the FX as part of the FX chain or in a dedicated floating window. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter.adoc new file mode 100644 index 0000000..fd62b8e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter.adoc @@ -0,0 +1,2 @@ + += FX parameter targets \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter/set-automation-touch-state.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter/set-automation-touch-state.adoc new file mode 100644 index 0000000..f02a596 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter/set-automation-touch-state.adoc @@ -0,0 +1,9 @@ += Target "FX parameter: Set automation touch state" + +This is similar to xref:targets/track/set-automation-touch-state.adoc[] but for FX parameter value changes. + +IMPORTANT: There's one difference. +This target currently only reacts to _release_ events, not _press_ events. +So you can't start writing automation by touching the fader. +You can only end writing automation by releasing it. +The reason is that REAPER doesn't provide a function `TrackFX_BeginParamEdit`, it only provides link:https://www.reaper.fm/sdk/reascript/reascripthelp.html#TrackFX_EndParamEdit[TrackFX_EndParamEdit]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter/set-value.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter/set-value.adoc new file mode 100644 index 0000000..7094ab3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx-parameter/set-value.adoc @@ -0,0 +1,97 @@ += Target "FX parameter: Set value" + +Sets the value of a particular track FX parameter. + +== Parameter controls + +Use them to set the parameter to be controlled. + +Please note that both xref:further-concepts/target.adoc#particular-fx-selector[] and xref:further-concepts/target.adoc#at-position-selector[] address the FX by its position in the FX chain. +The difference between the two is that xref:further-concepts/target.adoc#particular-selector[] shows a dropdown containing the available parameters and xref:further-concepts/target.adoc#at-position-selector[] lets you enter the position as a number in a text field. +The latter is useful if at the time of choosing the position, the FX is not available. + +== Retrigger checkbox + +By default, ReaLearn doesn't set the parameter if it already has the desired value. That prevents unnecessary invocations. + +However, some FX parameters are more like triggers. They don't actually have a value and are just used to trigger some action within that FX. In this case, it's important to enable _Retrigger_, which sets the parameter no matter what. + +== Real-time checkbox + +=== Main thread vs. real-time thread + +By default, ReaLearn does FX parameter value adjustments from the so-called _main_ thread instead of the _real-time_ thread. That means, in the worst case, we get latency as long as one main loop cycle. One main loop cycle is usually around 30 ms. + +In many control scenarios, this is completely acceptable. Basically, all control surface solutions including REAPER's built-in control surfaces, CSI and DrivenByMoss adjust FX parameters in the _main_ thread. Imagine you adjust a volume with a knob for mixing purposes. Such adjustments are usually rather slow and gradual, so it won't matter if the effect comes in 5 ms or 30 ms later. + +However, ReaLearn is a tool not just for mixing, also for performing. And in performing, there's sometimes demand for low latencies and fast responses, even when controlling FX parameters. + +=== Enabling real-time + +If you enable this checkbox, ReaLearn will **under certain conditions** control the FX parameter from a _real-time_ thread, enabling much lower latencies. In particular, the latency will correspond to the configured audio device block size -- the same thing that influences how fast virtual instruments respond when you press a note. + +The conditions are as follows: + +Condition 1: Same track:: The controlled FX must be on the **same track** as the ReaLearn instance. + +Condition 2: FX input:: The xref:key-concepts.adoc#input-port[] must be set to xref:user-interface/main-panel/input-output-section.adoc#fx-input[], **not** to a particular device. + +In all other circumstances, ReaLearn will fall back to adjusting the FX parameter from the _main_ thread. + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +| +target.fx_parameter.index +| +Integer +| +Zero-based index of the resolved FX parameter. + +| +target.fx_parameter.name +| +String +| +Name of the resolved FX parameter. + +| +target.fx_parameter.macro.name +| +String +| +Name of the corresponding Pot macro parameter. Only works if this parameter is part of a preset loaded via Pot. + +| +target.fx_parameter.macro.section.name +| +String +| +Name of the corresponding Pot macro parameter section. Only works if this parameter is part of a preset loaded via Pot. + +| +target.fx_parameter.macro.section.index +| +Integer +| +Zero-based index of the corresponding Pot macro parameter section (within the current bank). Only works if this parameter is part of a preset loaded via Pot. + +| +target.fx_parameter.macro.new_section.name +| +String +| +Name of the corresponding Pot macro parameter section, but only if this parameter marks the start of a new section. Only works if this parameter is part of a preset loaded via Pot. + +| +target.fx_parameter.macro.bank.name +| +String +| +Name of the corresponding Pot macro parameter bank. Only works if this parameter is part of a preset loaded via Pot. +|=== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx.adoc new file mode 100644 index 0000000..891fd48 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx.adoc @@ -0,0 +1,2 @@ + += FX targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/browse-presets.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/browse-presets.adoc new file mode 100644 index 0000000..3eaa324 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/browse-presets.adoc @@ -0,0 +1,12 @@ += Target "FX: Browse presets" + +Steps through FX presets. + +This target is suited for use with xref:further-concepts/mapping.adoc#knob[knobs], xref:further-concepts/mapping.adoc#rotary-endless-encoder[encoders] and xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[] because it allows you to step through the complete preset list. +The minimum value always represents _No preset_ whereas the maximum value always represents the last available preset. + +It's _not_ suited for activating a particular preset (e.g. by setting xref:user-interface/mapping-panel/glue-section.adoc#target-min-max[] to the same value), because the preset list of an FX is usually not constant. +As soon as you modify the preset list, this value will might suddenly point to a completely different preset. +Even worse, the actual preset might have been deleted. + +If you want to activate a particular preset, please use the xref:targets/fx/load-snapshot.adoc[] instead. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/enable-disable.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/enable-disable.adoc new file mode 100644 index 0000000..49ff252 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/enable-disable.adoc @@ -0,0 +1,3 @@ += Target "FX: Enable/disable" + +Enables the FX instance if the incoming absolute control value is greater than 0%, otherwise disables it. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/fx.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/fx.adoc new file mode 100644 index 0000000..1499688 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/fx.adoc @@ -0,0 +1,8 @@ += Target "FX" + +A target that allows you to define an FX, in its basic variant perfect for acquiring feedback for a specific FX. + +== Act/Tags controls + +The setting **Act/Tags** allows you to optionally set/pin the declared FX as xref:further-concepts/unit.adoc#unit-fx[]. +This works pretty much the same as described in xref:targets/track/track.adoc[]. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/load-snapshot.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/load-snapshot.adoc new file mode 100644 index 0000000..2af6ed6 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/load-snapshot.adoc @@ -0,0 +1,13 @@ += Target "FX: Load snapshot" + +Restores a certain state of a particular FX. +Before using this target, you need to take a snapshot of the desired FX state using the btn:[Take!] button. +This snapshot will be saved as part of ReaLearn's state itself and as a direct consequence as a part of your project. +This makes your project nicely self-contained. +It's perfect for activating particular FX presets because it will always restore the desired state, even if the preset list has changed. + +This target supports feedback, but only if the snapshot is loaded via ReaLearn itself. + +Please note that some plug-ins have _very large_ states. +Therefore, you should keep an eye on the snapshot size, which will be displayed once you take the snapshot. +ReaLearn's own state will grow with every new snapshot mapping, so this can quickly add up and make REAPER/ReaLearn slow! diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/open-close.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/open-close.adoc new file mode 100644 index 0000000..99bad94 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/open-close.adoc @@ -0,0 +1,7 @@ += Target "FX: Open/close" + +Makes the FX instance visible if the incoming control value is greater than 0%, otherwise hides it. + +== Display menu + +Here you can decide if you want to display the FX as part of the FX chain or in a dedicated floating window. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/set-online-offline.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/set-online-offline.adoc new file mode 100644 index 0000000..341ed7f --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/fx/set-online-offline.adoc @@ -0,0 +1,3 @@ += Target "FX: Set online/offline" + +Sets the FX instance online if the incoming absolute control value is greater than 0%, otherwise sets it offline. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global.adoc new file mode 100644 index 0000000..2773141 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global.adoc @@ -0,0 +1,2 @@ + += Global targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/last-touched.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/last-touched.adoc new file mode 100644 index 0000000..47461af --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/last-touched.adoc @@ -0,0 +1,13 @@ += Target "Global: Last touched" + +This will control whatever target has been last touched in REAPER. +It's similar to the built-in REAPER action "Adjust last touched FX parameter" but provides the following benefits: + +. It's applicable to all ReaLearn targets that are learnable, not just FX parameters. +. It offers feedback. +. It can distinguish between parameter modifications caused by ReaLearn (i.e. hardware control) and those caused in other ways (e.g. via mouse). + +== Pick button + +This opens a window that lets you pick all considered target types and types of invocations (only macOS and Windows so far). +Last-touched targets not checked in this window will be ignored. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/mouse.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/mouse.adoc new file mode 100644 index 0000000..44c550b --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/mouse.adoc @@ -0,0 +1,36 @@ += Target "Global: Mouse" + +This will control the mouse. + +== Action menu + +Move cursor to:: Moves the mouse cursor on the given axis in an absolute manner. +This is a good choice for absolute mouse movement, that is, if you want to position the mouse cursor to a specific screen position. +Although it's also possible to move the mouse cursor relatively with this action by controlling the target with relative messages, it's usually better to use _Move cursor by_ instead. + +Move cursor by:: Moves the mouse cursor on the given axis in a relative manner. +This is a good choice if you want to move the cursor e.g. up a bit, starting from its current position. +This only works with relative control elements such as encoders or features such as xref:user-interface/mapping-panel/glue-section.adoc#make-relative[]. + +Press or release button:: Presses or releases a certain mouse button, depending on the incoming control value (0% = release, anything else = press). + +[[turn-scroll-wheel]] Turn scroll wheel:: Simulates the scroll wheel. + +== Axis menu + +Determines the direction of movement or scrolling. + +X (horizontal):: Horizontal movement or scrolling +[[mouse-axis-y]] Y (vertical):: Vertical movement or scrolling + +== Button menu + +Determines which mouse button to use. + +TIP: One popular use of this target is to adjust the FX parameter under the mouse cursor. +For this, it's usually best to use action <> and <>. + +TIP: You can unfold the magic of this target by combining multiple mappings. +E.g. one can simulate mouse dragging by using one mapping to press/release the left button and another mapping to move the cursor. link:https://raw.githubusercontent.com/helgoboss/realearn/master/resources/test-projects/issue-686-mouse-target.RPP[This example project] contains multiple examples (one per group). + +WARNING: Feedback for this target is not fully implemented. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/set-automation-mode-override.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/set-automation-mode-override.adoc new file mode 100644 index 0000000..86f0a39 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/global/set-automation-mode-override.adoc @@ -0,0 +1,11 @@ += Target "Global: Set automation mode override" + +Sets the global automation mode override to the desired value if the incoming control value is greater than 0%, otherwise removes the override. + +== Behavior menu + +Lets you decide between not overriding anything, bypassing all envelopes or overriding with a specific automation mode. + +== Mode menu + +Here you can pick the desired automation mode if _Behavior_ is _Override_. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/marker-region.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/marker-region.adoc new file mode 100644 index 0000000..fe498de --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/marker-region.adoc @@ -0,0 +1,2 @@ + += Marker/region targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/marker-region/go-to.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/marker-region/go-to.adoc new file mode 100644 index 0000000..5a9ba41 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/marker-region/go-to.adoc @@ -0,0 +1,64 @@ += Target "Marker/region: Go to" + +Navigates to a specific marker or region. +Here's the behavior in detail: + +Regions:: +* If the project is stopped, the editor cursor immediately jumps to the start position of the given region. +* If the project is playing, playback will continue with the given region as soon as the currently playing region (or measure if not within a region) has finished playing. +This is called "smooth seek". +* *Attention:* This currently doesn't work if the project containing ReaLearn is not the active project tab. + +Markers:: +* If the project is stopped, the editor cursor immediately jumps to the given marker. +* If the project is playing, playback will immediately be continued at the given marker. + +The advantage over REAPER's built-in actions is that this target allows to target arbitrarily many markers/regions (either by position or by ID) … and that it supports visual feedback! +If you assign this target to a button which has an LED, you will see which marker/region is currently playing just by looking at your controller. + +Please note that this doesn't work when recording! + +== Marker/region selector menu + +This dropdown lets you choose if you want to refer to a marker/region by its user-assigned ID or by its position on the timeline. + +== Marker/region menu + +This dropdown displays the markers or regions (depending on the _Regions_ checkbox state). + +== Now! button + +This sets the target to the currently playing (or currently focused, if stopped) marker/region. + +== Behavior menu + +Determines whether to use immediate or smooth seeking. + +== Regions checkbox + +Switches between markers and regions. + +== Set loop points checkbox + +For regions, this will additionally set the loop points to the region start and end position. + +== Set time selection checkbox + +For regions, this will additionally set the time selection to the region start and end position. + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +|target.bookmark.id | Intger | (Numeric) ID of the bookmark +|target.bookmark.index | Integer | Index of the bookmark (counting both markers and regions) +|target.bookmark.index_within_type | Integer | Index of the bookmark (counting only markers or regions, respectively) +|target.bookmark.name | String | Name of the bookmark +| target.bookmark.color | Color | Custom color of the resolved marker or region. + +|=== + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/midi.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/midi.adoc new file mode 100644 index 0000000..d08b6b7 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/midi.adoc @@ -0,0 +1,2 @@ + += MIDI targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/midi/send-message.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/midi/send-message.adoc new file mode 100644 index 0000000..98f6d6f --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/midi/send-message.adoc @@ -0,0 +1,50 @@ += Target "MIDI: Send message" + +[[target-midi-send-message]] +Sends arbitrary MIDI messages (also sys-ex!) in response to incoming messages. +This target turns ReaLearn into a capable and convenient MIDI/OSC/Keyboard-to-MIDI converter. + +== Output menu + +Where to send the MIDI message. + +FX output:: Sends the MIDI message to the output of this ReaLearn instance - which usually means it flows into the FX below ReaLearn, e.g. a VST instrument. + +Feedback output:: Sends the MIDI message to the device which is set as _output_. + +[[midi-send-output-input-device]] Input device:: Injects the MIDI message into the current MIDI input device buffer. +Enables a unique feature called "Global MIDI transformation", as shown in link:https://www.youtube.com/watch?v=WJiwmlJSsi8&list=PL0bFMT0iEtAgKY2BUSyjEO1I4s20lZa5G&index=11[tutorial video 11]. + +== Device menu + +When choosing output <>, you can choose into which MIDI input device buffer the message will be injected. + +:: Injects the message into the same buffer of the MIDI input device chosen as xref:user-interface/main-panel/input-output-section.adoc#input[]. + +_Specific input device_:: Injects the message into another specific MIDI input device. +This can be useful for doing global MIDI transformation with controllers that expose multiple MIDI input ports. +A practical example is shown in link:https://www.youtube.com/watch?v=WJiwmlJSsi8&list=PL0bFMT0iEtAgKY2BUSyjEO1I4s20lZa5G&index=11[tutorial video 11]. + +== Pattern field + +Defines the MIDI message to be sent as xref:further-concepts/mapping.adoc#raw-midi-pattern[]. +It allows you to encode the incoming _absolute_ control value as part of the message (after it has been processed by xref:key-concepts.adoc#glue[]). + +== Pre-defined patterns menu (...) + +Provides predefined patterns. + +[NOTE] +.This is a target capable of real-time control! +==== +This target is a bit special in that it carries out its processing logic exclusively in the audio thread if it's controlled by a MIDI source. +This has the big advantage that receiving and producing MIDI messages happens in one go (without inter-thread-communication latency), which is often important when using MIDI message conversion. + +However, this also means that the following things won't work when controlling this target using MIDI: + +* It can't take the lead in xref:user-interface/mapping-panel/glue-section.adoc#group-interaction[]. +* It won't work with timed xref:user-interface/mapping-panel/glue-section.adoc#fire-mode[fire modes]. +* If _output_ is set to xref:user-interface/main-panel/input-output-section.adoc#fx-output[], additional limitations apply: +** It can't act as a follower in xref:user-interface/mapping-panel/glue-section.adoc#group-interaction[], either. +** It can't participate in xref:targets/realearn/load-mapping-snapshot.adoc[]. +==== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/osc.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/osc.adoc new file mode 100644 index 0000000..23c896c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/osc.adoc @@ -0,0 +1,2 @@ + += OSC targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/osc/send-message.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/osc/send-message.adoc new file mode 100644 index 0000000..3c66be8 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/osc/send-message.adoc @@ -0,0 +1,15 @@ += Target "OSC: Send message" + +Sends OSC messages with up to one argument in response to incoming messages. +This target turns ReaLearn into a capable and convenient MIDI → OSC and OSC → OSC converter. +If an argument number is entered (e.g. `1`), it will encode the incoming absolute control value as that argument (after it has been processed by the glue section). + +== Output menu + +Where to send the OSC message. + +:: Sends the OSC message to the device which is set as _Output_. +Of course this only works if it's an OSC device. +_Specific device:_:: Sends the OSC message to a specific device. +Address, Argument and Range:: These correspond to the identically named settings of xref:sources/osc.adoc[]. +Check that section for details. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime.adoc new file mode 100644 index 0000000..a45b7a9 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime.adoc @@ -0,0 +1,69 @@ += Playtime targets + +The targets in this section are made for controlling xref:playtime::introduction.adoc[Playtime], the clip launcher contained in Helgobox. + +[[column-selector]] +== Column selector + +Column-related targets use a _column selector_ to define what xref:playtime::key-concepts.adoc#column[] the target should be applied to. + +The following kinds of column selectors are available: + +Active:: +The xref:playtime::further-concepts/matrix.adoc#active-cell[currently active] column. + +At position:: +Here you can refer to a fixed column by entering the column number, e.g. 2. + +Dynamic:: +Allows you to dynamically refer to a certain column. See <>. + +[[row-selector]] +== Row selector + +Row-related targets use a _row selector_ to define what xref:playtime::key-concepts.adoc#row[] the target should be applied to. + +The following kinds of row selectors are available: + +Active:: +The xref:playtime::further-concepts/matrix.adoc#active-cell[currently active] row. + +At position:: +Here you can refer to a fixed column by entering the row number, e.g. 2. + +Dynamic:: +Allows you to dynamically refer to a certain row. See <>. + +[[slot-selector]] +== Slot selector + +Slot-related targets use a _slot selector_ to define what xref:playtime::key-concepts.adoc#slot[] the target should be applied to. + +The following kinds of slot selectors are available: + +Active:: +The xref:playtime::further-concepts/matrix.adoc#active-cell[currently active] slot. + +At coordinates:: +Here you can refer to a fixed slot by entering the slot address, e.g. 2 and 4. The first number is the number of the xref:playtime::key-concepts.adoc#column[], the second one the number of the xref:playtime::key-concepts.adoc#row[]. + +Dynamic:: +Allows you to dynamically refer to a certain slot. There are two expressions, the first one should evaluate to the column index and the second one to the row index. As always, expressions must evaluate to zero-based indexes! See <>. + +[[dynamic-selector-variables]] +== Dynamic selector variables + +In addition to the general variables described in xref:further-concepts/target.adoc#dynamic-selector[], the dynamic selectors mentioned above grants access to the following variables: + +[cols="m,1,3"] +|=== +| Variable | Type | Description + +| control_unit_column_index +| Integer >= 0 +| Column index of the current top-left scroll position within the xref:playtime::further-concepts/matrix.adoc#control-unit[Playtime control unit] represented by this xref:key-concepts.adoc#unit[ReaLearn unit]. + +| control_unit_row_index +| Integer >= 0 +| Row index of the current top-left scroll position within the xref:playtime::further-concepts/matrix.adoc#control-unit[Playtime control unit] represented by this xref:key-concepts.adoc#unit[ReaLearn unit]. +|=== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/browse-cells.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/browse-cells.adoc new file mode 100644 index 0000000..df86a07 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/browse-cells.adoc @@ -0,0 +1,9 @@ += Target "Playtime: Browse cells" + +Changes the currently xref:playtime::further-concepts/matrix.adoc#active-cell[]. + +Best uses with xref:further-concepts/mapping.adoc#rotary-endless-encoder[encoders] or previous/next-style buttons. + +== Axis + +Sets the direction into which to browse. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/column-action.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/column-action.adoc new file mode 100644 index 0000000..2b34a32 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/column-action.adoc @@ -0,0 +1,23 @@ += Target "Playtime: Column action" + +Invokes a xref:playtime::key-concepts.adoc#column[]-related action. + +== Action menu + +The kind of action to be executed. + +Stop:: +Stops all slots in this column. + +Arm/disarm:: +When hit with an _on_ value, arms the associated xref:playtime::key-concepts.adoc#column-track[], otherwise disarms it. + +Arm/disarm () exclusive:: +When hit with an _on_ value, arms the associated xref:playtime::key-concepts.adoc#column-track[] and disarms all other column tracks, otherwise disarms it. + +Activate:: +Makes this column the xref:playtime::further-concepts/matrix.adoc#active-cell[] . + +== Column menu + +The xref:playtime::key-concepts.adoc#column[] on which to carry out this action. See xref:targets/playtime.adoc#column-selector[]. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/control-unit-scroll.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/control-unit-scroll.adoc new file mode 100644 index 0000000..4d8f2cb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/control-unit-scroll.adoc @@ -0,0 +1,7 @@ += Target "Playtime: Control unit scroll" + +Controls the current xref:playtime::further-concepts/matrix.adoc#control-unit-scroll-position[] of the xref:playtime::further-concepts/matrix.adoc#control-unit[] represented by this xref:key-concepts.adoc#unit[ReaLearn unit]. + +== Axis + +Sets the direction into which to scroll. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/matrix-action.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/matrix-action.adoc new file mode 100644 index 0000000..0e432ae --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/matrix-action.adoc @@ -0,0 +1,52 @@ += Target "Playtime: Matrix action" + +Invokes a xref:playtime::key-concepts.adoc#matrix[]-related action. + +== Action menu + +The kind of action to be executed. + +Stop:: +Stops all clips in the matrix. See xref:playtime::user-interface/matrix-area.adoc#column-cell-stop[]. Supports real-time operation. + +Undo:: +When hit with an _on_ value, undoes the last matrix operation. See xref:helgobox::app/user-interface/title-bar.adoc#title-bar-undo[]. + +Redo:: +When hit with an _on_ value, redoes the last matrix operation. See xref:helgobox::app/user-interface/title-bar.adoc#title-bar-redo[]. + +Build scene:: +When hit with an _on_ value, builds a xref:playtime::key-concepts.adoc#scene[] of all currently playing xref:playtime::key-concepts.adoc#clip[clips] in the first empty xref:playtime::key-concepts.adoc#row[]. + +Set record length mode:: +Switches to a specific record-length mode. At the moment, only two possible modes exist: *Open end* and *Custom length* are supported. They correspond to the off and on state of xref:playtime::user-interface/inspector/matrix/recording.adoc#inspector-matrix-recording-length[]. + +Set custom record length in bars:: +Sets the xref:playtime::user-interface/inspector/matrix/recording.adoc#inspector-matrix-recording-length[] to the desired number of bars. + +Enable/disable click:: +When hit with an _on_ value, enables the xref:playtime::user-interface/toolbar.adoc#toolbar-metronome[], otherwise disables it. + +Enable/disable MIDI auto-quantize:: +When hit with an _on_ value, enables xref:playtime::user-interface/inspector/matrix/recording.adoc#inspector-matrix-recording-auto-quantize[], otherwise disables it. + +Smart record:: +When hit with an _on_ value, triggers the xref:playtime::user-interface/toolbar.adoc#toolbar-smart-record[] function. If this leads to the stop of a xref:playtime::further-concepts/matrix.adoc#feature-tempo-detection[], the operation can be carried out in real-time. + +Start or stop playback:: +When hit with an _on_ value, this starts Playtime playback and plays all xref:playtime::further-concepts/slot.adoc#ignited-slot[ignited] slots. Otherwise, this stops Playtime playback. This basically resembles the xref:playtime::user-interface/toolbar.adoc#toolbar-start-stop-playback[] button. This action supports real-time invocation. + +Enable/disable silence mode:: +When hit with an _on_ value, this stops Playtime playback. Otherwise, it starts playback _without_ playing xref:playtime::further-concepts/slot.adoc#ignited-slot[ignited] slots. This action supports real-time invocation. + +Panic:: +This abruptly stops all clips in the matrix. This resembles a double click on the xref:playtime::user-interface/matrix-area.adoc#matrix-cell-stop[] button. This action supports real-time invocation. + +Enable/disable sequencer recording:: +When hit with an _on_ value, this starts recording a new xref:playtime::key-concepts.adoc#matrix-sequence[], otherwise it stops recording. + +Enable/disable sequencer playing:: +When hit with an _on_ value, this starts playting the currently active xref:playtime::key-concepts.adoc#matrix-sequence[], otherwise it stops playing. + +Tap tempo:: +Invokes the tap-tempo function. This action supports real-time invocation. See xref:playtime::user-interface/toolbar.adoc#toolbar-tap-tempo[]. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/row-action.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/row-action.adoc new file mode 100644 index 0000000..f1aed36 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/row-action.adoc @@ -0,0 +1,26 @@ += Target "Playtime: Row action" + +== Action menu + +The kind of action to be executed. + +Most of the actions only execute when the target is hit with an _on_ value. In all other cases, it's specifically mentioned. + +Play:: +Acts like the xref:playtime::user-interface/matrix-area.adoc#row-cell-play-scene[] button. This action supports real-time invocation. + +Build scene:: +Acts like the xref:playtime:ROOT:user-interface/menus/row-menu.adoc#row-build-scene-from-currently-playing-clips[] function. + +Clear:: +Acts like the xref:playtime:ROOT:user-interface/menus/row-menu.adoc#row-clear[] function. + +Copy or paste:: +If the row contains clips, copies them to a matrix-internal clipboard. If the row is empty, attempts to paste clips from the matrix-internal clipboard into that row, replacing existing ones. + +Activate:: +Makes this row the xref:playtime::further-concepts/matrix.adoc#active-cell[]. + +== Row menu + +The xref:playtime::key-concepts.adoc#row[] on which to carry out this action. See xref:targets/playtime.adoc#row-selector[]. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-management-action.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-management-action.adoc new file mode 100644 index 0000000..61e3c15 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-management-action.adoc @@ -0,0 +1,56 @@ += Target "Playtime: Slot management action" + +Invokes a xref:playtime::key-concepts.adoc#slot[]-related action that has something to do with modifying slots. + +== Action menu + +The kind of action to be executed. + +Most of the actions only execute when the target is hit with an _on_ value. In all other cases, it's specifically mentioned. + +Clear slot:: +See xref:playtime:ROOT:user-interface/menus/slot-menu.adoc#slot-clear[]. + +Fill slot with selected item:: +See xref:playtime:ROOT:user-interface/menus/slot-menu.adoc#slot-import-selected-item[]. + +Edit first clip:: +When hit with an _on_ value, opens the MIDI editor for the primary clip in the slot, otherwise close its. Works for MIDI clips only. + +Copy or paste clip:: +If the slot contains clips, copies them to a matrix-internal clipboard. If the slot is empty, attempts to paste clips from the matrix-internal clipboard into that slot, replacing existing ones. + +Double clip section length:: +Doubles the xref:playtime::user-interface/inspector/clip.adoc#inspector-clip-length[] of each clip contained in the slot. + +Halve clip section length:: +Halves the xref:playtime::user-interface/inspector/clip.adoc#inspector-clip-length[] of each clip contained in the slot. + +Quantization on/off state:: +When hit with an _on_ value, quantizes the first MIDI clip in this slot, otherwise un-quantizes it. + +Duplicate:: +Duplicates the clips of this slot to the slot below. If the slot below already contains clips, they will get replaced. + +Activate:: +Makes this slot the xref:playtime::further-concepts/matrix.adoc#active-cell[]. + +include::partial$targets/playtime/slot-menu.adoc[] + + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +| +target.clip.name +| +String +| +Name of the first clip in the slot. + +|=== diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-seek.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-seek.adoc new file mode 100644 index 0000000..bcaddca --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-seek.adoc @@ -0,0 +1,14 @@ += Target "Playtime: Slot seek" + +This target exists mainly to provide xref:key-concepts.adoc#feedback[] about the current playback position of the xref:playtime::key-concepts.adoc#slot[]. + +For example, you can use it to make an LED ring, motor fader or text display indicate the current playback position. + +[CAUTION] +==== +This also works in xref:key-concepts.adoc#control[control direction], changing the current play position of the clips in that slot. But this is **experimental**. +==== + +include::partial$targets/feedback-frequency-menu.adoc[] + +include::partial$targets/playtime/slot-menu.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-transport-action.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-transport-action.adoc new file mode 100644 index 0000000..9ba8db7 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-transport-action.adoc @@ -0,0 +1,52 @@ += Target "Playtime: Slot transport action" + +Invokes a xref:playtime::key-concepts.adoc#slot[]-related action that has something to do with transport. + +All actions that are about starting and stopping slots are real-time capable, which means they can be executed in real-time when controlled via xref:sources/midi.adoc[]. + +== Action menu + +The kind of action to be executed. + +[[trigger]] Trigger:: +xref:playtime::further-concepts/matrix.adoc#trigger-slot[Triggers] the slot. + +Play/stop:: +When hit with an _on_ value, starts slot playback, otherwise stops it. + +Play/pause:: +When hit with an _on_ value, starts slot playback, otherwise pauses it. ++ +CAUTION: Pausing slots is experimental. + +Stop:: +When hit with an _on_ value, stops the slot. + +Pause:: +When hit with an _on_ value, pauses the slot. ++ +CAUTION: Pausing slots is experimental. + +Record/stop:: +When hit with an _on_ value, starts slot recording, otherwise stops it. + +Record/play/stop:: +When hit with an _on_ value and the slot is empty, starts slot recording. ++ +When hit with an _on_ value and the slot contains clips, starts playback. ++ +When hit with an _off_ value, stops the slot. + +Overdub/play:: +When hit with an _on_ value, starts MIDI-overdubbing the slot, otherwise stops it. + +Looped:: +Controls the xref:playtime::user-interface/inspector/clip.adoc#inspector-clip-looped[] setting of the clip. + +include::partial$targets/playtime/slot-menu.adoc[] + +== Stop if empty + +If this is checked, and you play or trigger empty slots, all slots in that column will be stopped. + +In other words, this makes empty slots act as stop button. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-volume.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-volume.adoc new file mode 100644 index 0000000..21928c8 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/playtime/slot-volume.adoc @@ -0,0 +1,5 @@ += Target "Playtime: Slot volume" + +Sets the xref:playtime::user-interface/inspector/clip.adoc#inspector-clip-volume[volume] property of all clips in a xref:playtime::key-concepts.adoc#slot[]. + +include::partial$targets/playtime/slot-menu.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot.adoc new file mode 100644 index 0000000..24f95ed --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot.adoc @@ -0,0 +1,2 @@ + += Pot targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/browse-filter-items.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/browse-filter-items.adoc new file mode 100644 index 0000000..18df7b5 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/browse-filter-items.adoc @@ -0,0 +1,32 @@ += Target "Pot: Browse filter items" + +This target can be used to filter the potentially very large collection of presets in xref:targets/pot/browse-presets.adoc[]. +The idea is to map this target to an endless rotary encoder or previous/next buttons (using xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[] mode) and then navigate within the available filter items, e.g. instruments or banks. + +== Kind menu + +Choose the kind of filter items that you want to browse. +They correspond to the filters available in xref:helgobox::products.adoc#pot-browser[]. + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +| +target.item.name +| +String +| +Name of the filter item. + +| +target.item.parent.name +| +String +| +Name of the parent filter item if there's any. E.g. the instrument to which a bank belongs or the type to which a subtype belongs. +|=== diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/browse-presets.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/browse-presets.adoc new file mode 100644 index 0000000..251a06d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/browse-presets.adoc @@ -0,0 +1,60 @@ += Target "Pot: Browse presets" + +Use this target to browse a collection of presets. +By default, this is the complete collection of presets available in all supported databases, so potentially thousands of presets. +If you want to browse just a subset, see xref:targets/pot/browse-filter-items.adoc[]. + +The idea is to map this target to an endless rotary encoder or previous/next buttons (using xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[] mode) and then navigate within the available presets. +Once you have selected a preset, you can audition it via xref:targets/pot/preview-preset.adoc[] (if it's a sound preset) and load it via xref:targets/pot/load-preset.adoc[]. + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +| +target.preset.name +| +String +| +Name of the preset. + +| +target.preset.product.name +| +String +| +Name of the product to which this preset belongs, if available. + +| +target.preset.file_ext +| +String +| +File extension of the preset, in case it's a file-based preset. + +| +target.preset.author +| +String +| +Name of the preset author, if available. + +| +target.preset.vendor +| +String +| +Name of the preset vendor, if available. + +| +target.preset.comment +| +String +| +Preset comment, if available. + +|=== diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/load-preset.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/load-preset.adoc new file mode 100644 index 0000000..c52b447 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/load-preset.adoc @@ -0,0 +1,19 @@ += Target "Pot: Load preset" + +Loads a preset selected via xref:targets/pot/browse-presets.adoc[]. + +NOTE: This needs at least REAPER version 6.69+dev1030! Also, it only works if you have the VST2/VST2i version of the corresponding plug-in installed. + +NKS audio file presets will be loaded into ReaSamplOmatic5000. + +== Track/FX controls + +You must tell the target at which FX slot to load the corresponding plug-in. +The best idea is to use FX selector xref:further-concepts/target.adoc#at-position-selector[]. +Selectors such as xref:further-concepts/target.adoc#particular-fx-selector[] or xref:further-concepts/target.adoc#named-fx-selector[] are not suited because the target might replace the plug-in with another one, in which the unique FX ID and the FX name can change. +Then the target would turn inactive and stop working. + +== Target-specific properties + +This target supports the same additional xref:further-concepts/target.adoc#target-property[target properties] as xref:targets/pot/browse-presets.adoc[]. +The only difference is that the ones in this one relate to the currently loaded preset, not the one that's selected in the preset browser. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/preview-preset.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/preview-preset.adoc new file mode 100644 index 0000000..bab2c38 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/pot/preview-preset.adoc @@ -0,0 +1,4 @@ += Target "Pot: Preview preset" + +Auditions a preset selected via xref:targets/pot/browse-presets.adoc[]. +Only works if it's a sound preset and a sound preview file is available. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project.adoc new file mode 100644 index 0000000..ad6498e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project.adoc @@ -0,0 +1,2 @@ + += Project targets \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/any-on.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/any-on.adoc new file mode 100644 index 0000000..1afa846 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/any-on.adoc @@ -0,0 +1,10 @@ += Target "Project: Any on (solo/mute/...)" + +This target is most useful in feedback direction. +Map it to some LED on your controller and the LED will light up if at least one of the tracks in your project is e.g. mute (depending on the track parameter in question). + +If the control element is also a button, pressing the button will e.g. unmute all tracks in your project. + +== Parameter menu + +The track parameter in question. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/browse-tracks.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/browse-tracks.adoc new file mode 100644 index 0000000..94be456 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/browse-tracks.adoc @@ -0,0 +1,29 @@ += Target "Project: Browse tracks" + +Steps through tracks. +To be used with endless rotary encoders or xref:user-interface/mapping-panel/glue-section.adoc#incremental-button[]. + +== Scroll TCP checkbox + +See xref:targets/track/select-unselect.adoc[]. + +== Scroll mixer checkbox + +See xref:targets/track/select-unselect.adoc[]. + +== Scope menu + +Decides which tracks are considered and how. + +All tracks:: Considers all tracks even those which are hidden. + +Only tracks visible in TCP:: Considers only those tracks which are visible in the track control panel. + +Only tracks visible in TCP (allow 2 selections):: Like "Only tracks visible in TCP" but makes it possible to have 2 selections. +One for the MCP and one for the TCP. +These selections can be moved independently. +This can make sense if you have a bunch of tracks that you only show in the TCP and another separate bunch of tracks that you only show in the MCP. + +Only tracks visible in MCP:: Considers only those tracks which are visible in the mixer control panel. + +Only tracks visible in MCP (allow 2 selections):: See above. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/invoke-reaper-action.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/invoke-reaper-action.adoc new file mode 100644 index 0000000..989bf4c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/invoke-reaper-action.adoc @@ -0,0 +1,80 @@ += Target "Project: Invoke REAPER action" + +Triggers or sets the value of a particular REAPER action in the main section. + +== Section menu + +Specifies in which context the action is going to be invoked. + +Main:: Invokes a main action. +Active MIDI editor:: Invokes a MIDI editor action, applied to the currently active MIDI editor. +Active MIDI event list editor:: Invokes a MIDI event list action, applied to the currently active MIDI editor. +Media explorer:: Invokes a media explorer action. + +[[invocation-type]] +== Invocation type + +Specifies _how_ the picked action is going to be controlled. + +Trigger:: Invokes the action with the incoming absolute control value, but only if it's greater than 0%. +Most suitable for simple trigger-like actions that neither have an on/off state nor are annotated with "(MIDI CC/OSC only)" or similar. + +Absolute 14-bit:: Invokes the action with the incoming absolute control value, even if it's 0%. +Most suitable for actions which either have an on/off state or are annotated with "(MIDI CC/OSC only)" or similar. +The resolution of the invocation is 14-bit, no matter what's the resolution of your control element). + +Absolute 7-bit:: Just like the previous invocation mode but uses 7-bit resolution. +Might be necessary for actions provided by 3rd-party extensions which don't interpret 14-bit control values correctly. +In all other circumstances, 14-bit is probably the better default choice. + +Relative:: Invokes the action with the incoming relative control value (absolute ones are ignored). +Only works for actions that are annotated with ("MIDI CC relative only") or similar. + +== Pick! button + +Opens REAPER's action dialog so you can select the desired action. + +== With track checkbox + +Allows you to choose a track which ReaLearn will select before executing the action. +This makes it possible to combine ReaLearn's flexible track selection capabilities with the plethora of REAPER actions that work on the currently selected track. + +== Limitations + +The particular action decides if toggling/feedback works completely, has limitations or is not possible at all. +There are multiple types of actions so it's not possible to settle with one invocation type and be done with it. +The types of actions can roughly be divided into: + +Actions that take care of toggling themselves _and_ report on/off state:: +* Example: "25. Track: Toggle record arm for track 01" +* If you want toggle behavior, you have 2 options: +** a) Set Invoke to "Absolute" and Mode to "Toggle button" (preferred). +** b) Set Invoke to "Trigger" and Mode to "Normal". +* Feedback is completely supported. + +Actions that take care of toggling themselves but _don't_ report on/off state:: +* Example: "40175. Item properties: Toggle mute" +* Toggle behavior is achieved as described in (1) but support for toggling and feedback has limitations (explained in (4)). + +Actions that don't take care of toggling themselves ("trigger only"):: +* Example: "1007. Transport: Play" +* There's no way to make such an action toggle because the action is not designed to do so. +* If the action reports an on/off state, feedback is completely supported though, otherwise not at all! + +Actions that have a complete range of values as state:: +* Example: "994. View: Adjust vertical zoom (MIDI CC/OSC only)" +* Since ReaLearn 2 and REAPER 6.20, there's special support for this type of actions. +Starting from the first time this action is triggered, ReaLearn will track its current value. +* That's why toggling is supported. +Because ReaLearn itself takes care of toggling, you need to set _Invoke_ to "Absolute" and Mode to "Toggle button". +* Feedback is also supported. +* Toggling/feedback for this type of actions comes with some inherent limitations that are related to the fact that a) REAPER itself doesn't necessarily use actions to invoke its own functions and b) MIDI CC/OSC actions don't have the concept of a "current value" (unlike e.g. toggle actions or FX parameters). +* The bottom line of these limitations is that toggling/feedback will only work if the action itself is used to trigger the change and if the action is an absolute action (not relative). +* Limitations in detail: ++ +. In most cases, feedback will not work when changing the value in REAPER directly (e.g. when adjusting vertical zoom directly via the REAPER user interface). +. It will only work for actions that support some kind of absolute value range (usually the case for all non-relative MIDI CC/OSC actions). +. When the action is invoked via ReaLearn, the feedback will only work if "Invoke" is "Trigger" or "Absolute". +It won't work with "Relative". +. When the action is invoked from ReaScript or other extensions, it will only work if the invocation was done via `KBD_OnMainActionEx()` and an absolute value change. +. When the action is invoked via a native REAPER action mapping, it will only work if the invocation is done using absolute MIDI CC/OSC (not relative). \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/invoke-transport-action.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/invoke-transport-action.adoc new file mode 100644 index 0000000..656ab76 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/invoke-transport-action.adoc @@ -0,0 +1,16 @@ += Target "Project: Invoke transport action" + +Invokes a transport-related action. + +== Action menu + +Specifies which transport action should be invoked. + +Play/stop:: Starts playing the containing project if the incoming absolute control value is greater than 0%, otherwise invokes stop. +Play/pause:: Starts playing the containing project if the incoming absolute control value is greater than 0%, otherwise invokes pause. +Stop:: Stops the containing project if the incoming absolute control value is greater than 0%. +Useful for distinguishing feedback between _paused_ and _stopped_ state. +Pause:: Pauses the containing project if the incoming absolute control value is greater than 0%. +Useful for distinguishing feedback between _paused_ and _stopped_ state. +Record:: Starts/enables recording for the current project if the incoming absolute control value is greater than 0%, otherwise disables recording. +Repeat:: Enables repeat for the containing project if the incoming absolute control value is greater than 0%, otherwise disables it. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/seek.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/seek.adoc new file mode 100644 index 0000000..2be0b64 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/seek.adoc @@ -0,0 +1,72 @@ += Target "Project: Seek" + +Allows you to use faders, knobs, encoders or incremental buttons to seek within portions of your project … with feedback that indicates the current position! + +include::partial$targets/feedback-frequency-menu.adoc[] + +== Behavior menu + +Determines whether to use immediate or smooth seeking. + +== Seek play checkbox + +Doesn't just change the edit cursor but also changes the play position when the project is currently being played. + +== Move view checkbox + +Allow to scroll / change viewport when seeking. + +== "Use" checkboxes + +The following checkboxes determine which time ranges will be taken into consideration as reference for seeking (control) and feedback. + +If you don't tick any "Use" checkbox, ReaLearn will seek within the currently visible viewport. + +If you tick multiple options, this is the order of fallbacks: + +* If there's no time selection, the loop points will be used. +* If there are no loop points, the current region is used. +* If there's no current region, the project will be used. +* If the project is empty, the viewport will be used. + +=== Use time selection checkbox + +Can use the currently set time selection as reference. + +=== Use loop points checkbox + +Can use the currently set loop points as reference. + +=== Use regions checkbox + +Can use the current region as reference. + +=== Use project checkbox + +Can use the complete project as reference, from start to end. + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +|target.position.project_default | String | Position in the current transport time unit +|target.position.time | String | _minute:second.milli_ +|target.position.measures_beats_time | String | _measure.beat.milli_ +|target.position.measures_beats | String | _measure.beat.milli_ +|target.position.seconds | String | _second.milli_ +|target.position.samples | String | _sample_ +|target.position.hmsf | String | _hour:minute:second:milli_ +|target.position.absolute_frames | String | _frames_ +|target.position.project_default.mcu | String | Like `target.position.project_default` but tailored to Mackie Control timecode displays +|target.position.time.mcu | String | Like `target.position.time` but tailored to Mackie Control timecode displays +|target.position.measures_beats_time.mcu | String | Like `target.position.measures_beats_time` but tailored to Mackie Control timecode displays +|target.position.measures_beats.mcu | String | Like `target.position.measures_beats` but tailored to Mackie Control timecode displays +|target.position.seconds.mcu | String | Like `target.position.seconds` but tailored to Mackie Control timecode displays +|target.position.samples.mcu | String | Like `target.position.samples` but tailored to Mackie Control timecode displays +|target.position.hmsf.mcu | String | Like `target.position.hmsf` but tailored to Mackie Control timecode displays +|target.position.absolute_frames.mcu | String | Like `target.position.absolute_frames` but tailored to Mackie Control timecode displays +|=== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/set-playrate.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/set-playrate.adoc new file mode 100644 index 0000000..2b8c5f3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/set-playrate.adoc @@ -0,0 +1,5 @@ += Target "Project: Set playrate" + +Sets REAPER's master playrate. + +CAUTION: This target doesn't currently work if the project containing ReaLearn is not the active project tab. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/set-tempo.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/set-tempo.adoc new file mode 100644 index 0000000..3721245 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/project/set-tempo.adoc @@ -0,0 +1,5 @@ += Target "Project: Set tempo" + +Sets REAPER's master tempo. + +This target is not learnable anymore via the "Learn target" button and also not eligible for the xref:targets/global/last-touched.adoc[] because it causes too many "false positives". diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn.adoc new file mode 100644 index 0000000..a3b774a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn.adoc @@ -0,0 +1,2 @@ + += ReaLearn targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/browse-group-mappings.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/browse-group-mappings.adoc new file mode 100644 index 0000000..07dbd53 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/browse-group-mappings.adoc @@ -0,0 +1,33 @@ += Target "ReaLearn: Browse group mappings" + +This target lets you choose an arbitrary mapping group in this compartment and cycle through it with an encoder/fader/knob or incremental (previous/next) buttons. + +"Cycling through" means that you move from one mapping in the group to the next one by hitting the next mapping's target with the _Target Max_ value in its glue section (by default 100%). + +== Group menu + +The group that you want to browse. + +== Exclusivity menu + +Non-exclusive:: Really just hits the target of the mapping which is next in the line and doesn't do anything with the other mappings. +In many cases this is enough, e.g. if the targets of the mappings in the cycled group are the same and just "Target Max" is different. +Or if the target itself already takes care of exclusivity. + +Exclusive:: Doesn't just hit the target of the mapping which is next in the line but also hits the targets of all other mappings in the cycled group with their respective _Target Min_ value (by default 0%). +Be careful with this, you often won't need it. + +Inactive mappings are skipped! + +[TIP] +==== +A mapping group lends itself perfectly for defining things that should happen _in sequence_. +This target allows you to take advantage of that! + +- Combine it with xref:targets/realearn/enable-disable-mappings.adoc[] to browse different banks. +- Combine it with xref:targets/realearn/enable-disable-instances.adoc[] to browse completely different controller setups (or banks). +- Combine it with targets that don't provide a "Browse ..." variant themselves. +- Use it as an alternative to xref:further-concepts/glue.adoc#target-value-sequence[target value sequences] that allows you to have completely different targets within one sequence. +==== + +[split=0] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/dummy.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/dummy.adoc new file mode 100644 index 0000000..59a26c1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/dummy.adoc @@ -0,0 +1,7 @@ += Target "ReaLearn: Dummy" + +This target simply does nothing when invoked and also doesn't provide any meaningful feedback on its own. + +It's sometimes useful to have such a dummy target, e.g. combined with xref:user-interface/mapping-panel/glue-section.adoc#group-interaction[]. +Or if you want to use ReaLearn as a MIDI filter which just "eats" an incoming MIDI message. +Or if you want to send some text feedback to a hardware display, if the text is just a constant string or uses a placeholder that doesn't need target context. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-instances.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-instances.adoc new file mode 100644 index 0000000..57c1ee1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-instances.adoc @@ -0,0 +1,42 @@ += Target "ReaLearn: Enable/disable instances" + +This target allows you to flexibly enable or disable other ReaLearn instances based on instance or unit tags. + +== Tag kind menu + +Instance tags:: +A ReaLearn instance matches when at least one of its units is tagged with any of the xref:further-concepts/instance.adoc#instance-tag[instance tags] entered into the **tags** field. + +Unit tags:: +A ReaLearn instance matches when at least one of its units is tagged with any of the xref:further-concepts/unit.adoc#unit-tag[unit tags] entered into the **tags** field. ++ +WARNING: Controlling instances via unit tags can be counter-intuitive, please use instance tags instead! This option is retained for backward compatibility only. + + +== Exclusivity menu + +Non-exclusive:: If the incoming control value is greater than 0%, all matching ReaLearn instances will be enabled (on top of the already enabled instances). +If the value is 0%, all matching ReaLearn instances will be disabled. + +Exclusive:: If the incoming control value is greater than 0%, all matching ReaLearn instances will be enabled and all non-matching ones will be disabled. +If the value is 0%, it's exactly the opposite (react to button xref:user-interface/mapping-panel/glue-section.adoc#press-only[press only] if you don't want this to happen). + +Exclusive (on only):: Variation of _Exclusive_ that applies exclusivity only if the incoming control value is greater than 0%. + +== Tags field + +Here you enter the instance or unit tags, separated by commas. + +.Comma-separated tags +==== +keys, guit, voc +==== + +== Remarks + +* This affects other ReaLearn instances only. It doesn't match against this one. +* ReaLearn instances without tags won't be affected at all. +* Only affects instances in the same project. +If _this_ ReaLearn instance is on the monitoring FX chain, it only affects other instances in the monitoring FX chain. + +TIP: This target is great for switching between completely different controller setups! \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-mappings.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-mappings.adoc new file mode 100644 index 0000000..234e386 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-mappings.adoc @@ -0,0 +1,24 @@ += Target "ReaLearn: Enable/disable mappings" + +This target allows you to flexibly enable or disable other mappings in this unit based on their tags: + +== Exclusivity menu + +Non-exclusive:: If the incoming control value is greater than 0%, all matching mappings will be enabled (on top of the already enabled mappings). +If the value is 0%, all matching mappings will be disabled. + +Exclusive:: If the incoming control value is greater than 0%, all matching mappings will be enabled and all non-matching ones will be disabled. +If the value is 0%, it's exactly the opposite (react to button xref:user-interface/mapping-panel/glue-section.adoc#press-only[press only] if you don't want this to happen). + +Exclusive (on only):: Variation of _Exclusive_ that applies exclusivity only if the incoming control value is greater than 0%. + +== Tags field + +A mapping matches when it is tagged with any of the xref:further-concepts/mapping.adoc#mapping-tag[mapping tags] entered into this field (comma-separated). + +== Remarks + +* This affects other mappings only, not _this_ mapping. +* Mappings without tags won't be affected at all. + +TIP: This target is a straightforward alternative to xref:further-concepts/mapping.adoc#conditional-activation[] when it comes to bank switching! diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-units.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-units.adoc new file mode 100644 index 0000000..e11c7db --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/enable-disable-units.adoc @@ -0,0 +1,27 @@ += Target "ReaLearn: Enable/disable units" + +This target allows you to flexibly enable or disable other ReaLearn units within the same ReaLearn instance. + +== Exclusivity menu + +Non-exclusive:: If the incoming control value is greater than 0%, all matching ReaLearn units will be enabled (on top of the already enabled units). +If the value is 0%, all matching ReaLearn units will be disabled. + +Exclusive:: If the incoming control value is greater than 0%, all matching ReaLearn units will be enabled and all non-matching ones will be disabled. +If the value is 0%, it's exactly the opposite (react to button xref:user-interface/mapping-panel/glue-section.adoc#press-only[press only] if you don't want this to happen). + +Exclusive (on only):: Variation of _Exclusive_ that applies exclusivity only if the incoming control value is greater than 0%. + +== Tags field + +Here you enter the unit tags, separated by commas. + +.Comma-separated tags +==== +keys, guit, voc +==== + +== Remarks + +* This affects other ReaLearn units only. It doesn't match against this one. +* ReaLearn units without tags won't be affected at all. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/load-mapping-snapshot.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/load-mapping-snapshot.adoc new file mode 100644 index 0000000..7d2f8a1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/load-mapping-snapshot.adoc @@ -0,0 +1,35 @@ += Target "ReaLearn: Load mapping snapshot" + +Restores target values for all or certain mappings in this ReaLearn unit. + +== Snapshot menu + +Choose the snapshot that you want to load. + +:: Restores the initial target values for the mappings. + +By ID:: Restores target values contained in a snapshot that was taken via xref:targets/realearn/take-mapping-snapshot.adoc[]. +Enter the corresponding ID here. + +== Default field + +Allows you to define a default target value to restore for each participating mapping whenever the snapshot either doesn't exist or doesn't contain a value for that mapping. +If that participating mapping has reverse checked, the inverse of the default value will be loaded. + +== Tags field + +Allows you to restrict the set of mappings whose target values will be restored. + +* If this field is empty, target values of all mappings will be restored. +* If this field contains tags (comma-separated), target values will be restored only for mappings that are tagged with any of these. + +== Active mappings only checkbox + +By default, even target values for inactive (but control-enabled) mappings are restored! +If you don't like that, tick this checkbox. + +== Remarks + +* Mappings for which control is not enabled, never participate in snapshotting. +* Some targets don't report values and therefore don't participate in snapshotting. +* Feedback of this target indicates whether the desired snapshot is the one which has last been loaded (for the given tags). \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/modify-mapping.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/modify-mapping.adoc new file mode 100644 index 0000000..de59921 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/modify-mapping.adoc @@ -0,0 +1,23 @@ += Target "ReaLearn: Modify mapping" + +Triggers a modification of another ReaLearn mapping. + +== Kind menu + +The kind of modification. + +Learn target:: Switches "Learn target" on or off for the destination mapping. +Use button btn:[...] to pick the considered target types and invocations to be included in the learning process. + +Set target to last touched:: Sets the target of the destination mapping to the last-touched target. +Use button btn:[...] to pick the considered target types and invocations. + +== Unit menu + +Allows you to pick another ReaLearn unit. + +== Mapping menu + +Allows you to pick the destination mapping. + +TIP: This target is great to "pin" targets to certain control elements on demand. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/take-mapping-snapshot.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/take-mapping-snapshot.adoc new file mode 100644 index 0000000..0d37752 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/realearn/take-mapping-snapshot.adoc @@ -0,0 +1,26 @@ += Target "ReaLearn: Take mapping snapshot" + +Memorizes target values for all or certain mappings in this ReaLearn units and saves them in a snapshot of your choice. + +== Snapshot menu + +Choose the snapshot to which you want to save the mapping values. + +:: Always chooses the snapshot which is currently active (was last loaded) for the given tags. ++ +Only works if tags are not empty and if all tags have the same last-loaded snapshot. +So the best is if you always enter exactly one tag. ++ +By ID:: Enter the unique ID of the snapshot, e.g. `scene_1`. + +== Tags field + +Allows you to restrict the set of mappings whose target values will be memorized. + +* If this field is empty, target values of all mappings will be memorized. +* If this field contains tags (comma-separated), target values will be memorized only for mappings that are tagged with any of these. + +== Active mappings only checkbox + +By default, even target values of inactive (but control-enabled) mappings end up in the snapshot! +If you don't like that, tick this checkbox. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive.adoc new file mode 100644 index 0000000..08fe33c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive.adoc @@ -0,0 +1,42 @@ += Send/receive targets + +The following UI elements are available for all targets that require a send/receive. + +[[kind]] +== Kind menu + +The kind of send/receive that you want to control. + +[[send]] Send:: Send from the track above to another track of your choice. + +[[receive]] Receive:: Receive from another track of your choice to the track above (opposite direction of send). + +[[output]] Output:: Send from the track to a hardware output. + +== Send/Receive/Output section + +This lets you choose the actual send/receive/output. + +The meaning of xref:further-concepts/target.adoc#particular-selector[] and xref:further-concepts/target.adoc#named-selector[] depends on which kind you have selected: + +For kinds <> (or <>):: + +xref:further-concepts/target.adoc#particular-selector[]::: +ReaLearn will memorize the _ID_ of the destination track (or source track). +That way you will still control the correct send (or receive) even if you delete another send (receive) in that track. ++ +WARNING: If you have multiple sends to (or receives from) the same destination (or source) track, you will only be able to address the first one. +In that case, use selector xref:further-concepts/target.adoc#at-position-selector[] instead. + +xref:further-concepts/target.adoc#named-selector[]::: +ReaLearn will memorize the _name_ of the destination (or source) track. +That way you will still control the correct send (receive) even if you delete another send (receive) in that track. ++ +WARNING: If you have multiple sends to (or receives from) the same destination (or source) track, you will only be able to address the first one. +In that case, use selector xref:further-concepts/target.adoc#at-position-selector[] instead. + +For kind <>:: + +xref:further-concepts/target.adoc#particular-selector[]::: +ReaLearn will memorize the _position_ of the hardware output because hardware outputs don't have unique IDs. +In other words, this works just like xref:further-concepts/target.adoc#at-position-selector[] internally! \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/automation-mode.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/automation-mode.adoc new file mode 100644 index 0000000..9598c4b --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/automation-mode.adoc @@ -0,0 +1,3 @@ += Target "Send: Automation mode" + +Sets the track send to a specific automation mode if the incoming control value is greater than 0%, otherwise sets it back to REAPER's default automation mode "Trim/Read". \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/mono-stereo.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/mono-stereo.adoc new file mode 100644 index 0000000..6caaf2e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/mono-stereo.adoc @@ -0,0 +1,3 @@ += Target "Send: Mono/stereo" + +Sets the track send to mono or back to stereo. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/mute-unmute.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/mute-unmute.adoc new file mode 100644 index 0000000..7fcc98c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/mute-unmute.adoc @@ -0,0 +1,3 @@ += Target "Send: Mute/unmute" + +Mutes/unmutes the track send. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/phase-invert-normal.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/phase-invert-normal.adoc new file mode 100644 index 0000000..42c751a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/phase-invert-normal.adoc @@ -0,0 +1,3 @@ += Target "Send: Phase invert/normal" + +Inverts the track send phase or switches it back to normal. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-automation-touch-state.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-automation-touch-state.adoc new file mode 100644 index 0000000..164d8dd --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-automation-touch-state.adoc @@ -0,0 +1,3 @@ += Target "Send: Set automation touch state" + +This does the same as xref:targets/track/set-automation-touch-state.adoc[] but for send volume or pan adjustments. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-pan.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-pan.adoc new file mode 100644 index 0000000..ab49707 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-pan.adoc @@ -0,0 +1,3 @@ += Target "Send: Set pan" + +Sets the track send's pan value. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-volume.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-volume.adoc new file mode 100644 index 0000000..fed52c5 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/send-receive/set-volume.adoc @@ -0,0 +1,3 @@ += Target "Send: Set volume" + +Sets the track send's volume. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track.adoc new file mode 100644 index 0000000..f838b7e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track.adoc @@ -0,0 +1,2 @@ + += Track targets diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/arm-disarm.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/arm-disarm.adoc new file mode 100644 index 0000000..8b2dcd5 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/arm-disarm.adoc @@ -0,0 +1,5 @@ += Target "Track: Arm/disarm" + +Arms the track for recording if the incoming absolute control value is greater than 0%, otherwise disarms the track. +This disables "Automatic record-arm when track selected". +If you don't want that, use xref:targets/track/select-unselect.adoc[] instead. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/enable-disable-all-fx.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/enable-disable-all-fx.adoc new file mode 100644 index 0000000..0a065e1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/enable-disable-all-fx.adoc @@ -0,0 +1,3 @@ += Target "Track: Enable/disable all FX" + +Enables all the track's FX instances if the incoming absolute control value is greater than 0%, otherwise disables them. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/enable-disable-parent-send.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/enable-disable-parent-send.adoc new file mode 100644 index 0000000..ea85efa --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/enable-disable-parent-send.adoc @@ -0,0 +1,3 @@ += Target "Track: Enable/disable parent send" + +Enables the parent send routing of the track if the incoming absolute control value is greater than 0%, otherwise disables it. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/mute-unmute.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/mute-unmute.adoc new file mode 100644 index 0000000..1681851 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/mute-unmute.adoc @@ -0,0 +1,3 @@ += Target "Track: Mute/unmute" + +Mutes the track if the incoming absolute control value is greater than 0%, otherwise unmutes the track. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/peak.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/peak.adoc new file mode 100644 index 0000000..2f06557 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/peak.adoc @@ -0,0 +1,16 @@ += Target "Track: Peak" + +This is a feedback-only target! +It turns your feedback-capable controller into a VU meter by constantly reporting the current volume of the configured track to it. + +In addition to connecting it with a LED ring or motor fader source (which should be obvious), it can also be used with a single LED to build a clipping indicator: + +. Set _Target Min_ to the minimum dB value that should make your clipping LED turn on. +Leave _Target Max_ at 12.00 dB. +. Make sure the xref:user-interface/mapping-panel/glue-section.adoc#out-of-range-behavior[] is set to "Min or max". +. If you have an LED that supports multiple colors, you will probably see a rainbow of colors flashing up which can be quite confusing. +Use the feedback transformation formula `x = ceil(y)` to restrict the feedback to just two values: +Min (0%) or Max (100%). +You can then use xref:user-interface/mapping-panel/glue-section.adoc#source-min-max[] to adjust the off/on LED colors. + +At the moment this target only reports peak volume, not RMS. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/phase-invert-normal.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/phase-invert-normal.adoc new file mode 100644 index 0000000..2373981 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/phase-invert-normal.adoc @@ -0,0 +1,3 @@ += Target "Track: Phase invert/normal" + +Inverts the track phase if the incoming absolute control value is greater than 0%, otherwise switches the track phase back to normal. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/select-unselect.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/select-unselect.adoc new file mode 100644 index 0000000..5faaad0 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/select-unselect.adoc @@ -0,0 +1,14 @@ += Target "Track: Select/unselect" + +Selects the track if the incoming absolute control value is greater than 0%, otherwise unselects the track. + +This target stops being learnable if you activate the REAPER preference "Mouse click on volume/pan faders and track buttons changes track selection" (because this preference would generate too many false positives). +If you change the preference, ReaLearn will take it into consideration the next time you restart REAPER. + +== Scroll TCP checkbox + +Also scrolls the track control panel to the desired track. + +== Scroll mixer checkbox + +Also scrolls the mixer control panel to the desired track. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-automation-mode.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-automation-mode.adoc new file mode 100644 index 0000000..3f94cd2 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-automation-mode.adoc @@ -0,0 +1,7 @@ += Target "Track: Set automation mode" + +Sets the track to a specific automation mode if the incoming control value is greater than 0%, otherwise sets it back to REAPER's default track automation mode "Trim/Read". + +== Mode menu + +Here you can pick the desired automation mode. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-automation-touch-state.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-automation-touch-state.adoc new file mode 100644 index 0000000..6eca7e4 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-automation-touch-state.adoc @@ -0,0 +1,14 @@ += Target "Track: Set automation touch state" + +When you use REAPER's "Touch" automation mode, REAPER needs a way to know if you are currently touching the control element which is bound to the automation envelope or not. +As long as you keep touching it, it will overwrite existing automation. +As soon as you release it, REAPER will leave the envelope untouched. + +Classical control surfaces implement this very intuitively by providing touch-sensitive faders. +With this target, you can easily reproduce exactly this behavior via ReaLearn. +You do this by mapping the touch event (which is usually nothing else than a MIDI note on/off message) to this target. +The touch state is scoped to a particular track and parameter type which you can choose in the *Type* dropdown. + +However, ReaLearn wouldn't be ReaLearn if it wouldn't allow you to let totally different sources take control of the touch state. +For example, if you have a push encoder, you could map the "push" event to the touch state, allowing you to write automation only while you are touching the encoder. +Or if you don't have a push encoder, you could just use some spare button. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-monitoring-mode.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-monitoring-mode.adoc new file mode 100644 index 0000000..0a3fb45 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-monitoring-mode.adoc @@ -0,0 +1,7 @@ += Target "Track: Set monitoring mode" + +Sets the track to a specific input monitoring mode if the incoming control value is greater than 0%, otherwise sets it back to "Off". + +== Mode menu + +Here you can pick the desired monitoring mode. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-pan.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-pan.adoc new file mode 100644 index 0000000..029e6ce --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-pan.adoc @@ -0,0 +1,14 @@ += Target "Track: Set pan" + +Sets the track's pan value. + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +|target.pan.mcu | String | Pan value tailored to one line on a Mackie Control LCD +|=== \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-stereo-pan-width.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-stereo-pan-width.adoc new file mode 100644 index 0000000..542ff98 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-stereo-pan-width.adoc @@ -0,0 +1,14 @@ += Target "Track: Set stereo pan width" + +Sets the track's width value (applicable if the track is in stereo pan mode). + +== Target-specific properties + +This target supports the following additional xref:further-concepts/target.adoc#target-property[target properties]. + +[cols="m,1,3"] +|=== +|Name|Type|Description + +|target.width.mcu | String | Width value tailored to one line on a Mackie Control LCD +|=== diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-volume.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-volume.adoc new file mode 100644 index 0000000..cf13913 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/set-volume.adoc @@ -0,0 +1,3 @@ += Target "Track: Set volume" + +Sets the track's volume. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/show-hide.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/show-hide.adoc new file mode 100644 index 0000000..7d428da --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/show-hide.adoc @@ -0,0 +1,7 @@ += Target "Track: Show/hide" + +Shows the track if the incoming absolute control value is greater than 0%, otherwise hides it. + +== Area menu + +Lets you decide if you want it to show/hide in the track control panel or the mixer. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/solo-unsolo.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/solo-unsolo.adoc new file mode 100644 index 0000000..52fe9a9 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/solo-unsolo.adoc @@ -0,0 +1,14 @@ += Target "Track: Solo/unsolo" + +Soloes the track if the incoming absolute control value is greater than 0%, otherwise unsoloes the track. + +== Behavior menu + +See the REAPER user guide for details. + +Solo in place:: Soloes the track while respecting REAPER's routing. +This is REAPER's default and since ReaLearn v2.4.0 also ReaLearn's default. +Solo (ignore routing):: Soloes the track muting everything else, no matter the routing. +Use REAPER preference:: Follows whatever is set in the REAPER preferences. + +Learning this target by pressing the "Solo" button of the _master_ track is currently not possible but of course you can just select it manually in the dropdown menu. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/track.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/track.adoc new file mode 100644 index 0000000..66d03b8 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/track/track.adoc @@ -0,0 +1,24 @@ += Target "Track" + +A target that allows you to define a track. + +== Act/Tags controls + +**Act/Tags** stands for "Action / Unit tags" and decides what happens when a control messages arrives, e.g. a button press. + +=== Action menu + +None (feedback only):: With this setting, nothing will happen. +It's suited very well as neutral target for textual feedback with an expression that contains a track property, e.g. `{{ target.track.name }}`. + +Set (as unit track):: The button press will set the track defined in this target as xref:further-concepts/unit.adoc#unit-track[] _without resolving it before_. +For example, if this target defines to use the currently selected track (xref:further-concepts/target.adoc#selected-selector[]), pressing the button will make the unit track dynamically reflect whatever track is selected. + +Pin (as unit track):: The button press will resolve the track defined in this target and set the result as xref:further-concepts/unit.adoc#unit-track[]. +For example, if this target defines to use the currently selected track, pressing the button will check which track is currently selected and set the unit track to exactly this track. +It will stay that way even if the user selects another track. + +=== Unit tags field + +The text field lets you define xref:further-concepts/unit.adoc#unit-tag[unit tags] to determine for which xref:key-concepts.adoc#unit[units] the xref:further-concepts/unit.adoc#unit-track[] should be changed. +If it's empty, the current unit will be affected. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/virtual.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/virtual.adoc new file mode 100644 index 0000000..01377ea --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/targets/virtual.adoc @@ -0,0 +1,9 @@ += Target "Virtual" + +This is exactly the counterpart of the possible xref:further-concepts/source.adoc#virtual-source[virtual sources] in the xref:key-concepts.adoc#main-compartment[]. +Choosing a xref:further-concepts/target.adoc#virtual-target[] here is like placing cables between a xref:further-concepts/compartment.adoc#real-control-element[] and all corresponding main mappings that use this xref:further-concepts/compartment.adoc#virtual-control-element[] as source. + +== Learnable checkbox + +If you disable this checkbox, this virtual source will not be learnable via xref:key-concepts.adoc#learn-source[] in the main compartment. +This can be useful for rather unimportant xref:key-concepts.adoc#control-element-interaction[control element interactions] such as _Fader touch_ that would otherwise make it very hard to learn more important sources such as _Fader movement_. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface.adoc new file mode 100644 index 0000000..59861fb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface.adoc @@ -0,0 +1,7 @@ += User interface + +This section describes all user interface elements in Helgobox that are relevant to ReaLearn. + +The majority of this section focuses on the xref:helgobox::plug-in/user-interface.adoc[native plug-in user interface], as this is where the majority of ReaLearn's functionality is located. + +For details on source- or target-specific elements, refer to xref:sources.adoc[] and xref:targets.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/controller-dialog.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/controller-dialog.adoc new file mode 100644 index 0000000..362803a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/controller-dialog.adoc @@ -0,0 +1,21 @@ += Controller dialog + +[[feature-realearn-customize]] +include::partial$available-in-app.adoc[] + +This dialog appears when clicking a controller in xref:user-interface/settings-dialog.adoc#controllers[]. It allows you to configure a xref:key-concepts.adoc#managed-controller[] and define how it should be used. + +image::generated/screenshots/main/controller.png[] + +include::partial$generated/elements/settings/controller/basics.adoc[] + +include::partial$generated/elements/settings/controller/name.adoc[] +include::partial$generated/elements/settings/controller/color.adoc[] +include::partial$generated/elements/settings/controller/enabled.adoc[] +include::partial$generated/elements/settings/controller/connection.adoc[] + +include::partial$generated/elements/settings/controller/usage.adoc[] +include::partial$generated/elements/settings/controller/main-preset.adoc[] + +include::partial$generated/elements/settings/controller/advanced.adoc[] +include::partial$generated/elements/settings/controller/default-controller-preset.adoc[] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel.adoc new file mode 100644 index 0000000..de6f920 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel.adoc @@ -0,0 +1,6 @@ += Main panel + +The main panel is what you see immediately when you open the Helgobox plug-in window. + +.Main panel +image::realearn/screenshots/main-panel.png[Screenshot] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/bottom-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/bottom-section.adoc new file mode 100644 index 0000000..892d4d1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/bottom-section.adoc @@ -0,0 +1,44 @@ += Bottom section + +image:realearn/screenshots/main-panel-bottom.png[Screenshot] + +== Unit enabled/disabled checkbox + +This checkbox lets you enable or disable the currently displayed unit. You can even do this dynamically by using the xref:targets/realearn/enable-disable-units.adoc[]. + +== Unit menu + +Press the button will reveal a menu with the following actions to manage xref:key-concepts.adoc#unit[Units]: + +Remove current unit:: Removes the current unit. +This can't be undone! +_List of units_:: Switch to an arbitrary unit by clicking on it. +Add unit:: Adds a new unit within this instance. +The new unit will automatically be named after the randomly-generated unit key. +You can change the name by pressing the <>. + +== Info area + +In the center you can see the info area, made up of 3 rows: + +Row 1:: +* The current scroll position. +* The xref:helgobox:ROOT:key-concepts.adoc#instance-id[] of this Helgobox xref:helgobox:ROOT:key-concepts.adoc#instance[]. +* The xref:further-concepts/unit.adoc#unit-key[] of the currently visible ReaLearn xref:key-concepts.adoc#unit[]. +* xref:further-concepts/unit.adoc#unit-tag[] assigned to this ReaLearn xref:key-concepts.adoc#unit[]. + +Row 2:: +* Information about the current xref:further-concepts/unit.adoc#unit-track[] and xref:further-concepts/unit.adoc#unit-fx[]. +* Information whether control and/or feedback is currently inactive unit-wide. + +Row 3:: +* Information about what version of Helgobox is running. + +[[set-unit-data]] +== Unit data… button + +Press this button to change various key-value data of this ReaLearn unit as a whole. + +Unit key…:: Allows you to change the xref:further-concepts/unit.adoc#unit-key[] of this xref:key-concepts.adoc#unit[]. +Unit name:: The display name of this xref:key-concepts.adoc#unit[]. +Tags:: Lets you assign xref:further-concepts/unit.adoc#unit-tag[] to this xref:key-concepts.adoc#unit[] as a comma-separated list. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/input-output-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/input-output-section.adoc new file mode 100644 index 0000000..8ebcc29 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/input-output-section.adoc @@ -0,0 +1,136 @@ += Input/output section + +image:realearn/screenshots/main-panel-input-output.png[Screenshot] + +Use this section to connect ReaLearn to a specific controller. + +Also see xref:best-practices.adoc#best-practices-input-output[]. + +[#input] +== Input menu + +Allows you to select the xref:key-concepts.adoc#input-port[] to which this ReaLearn unit listens. +ReaLearn works with MIDI or OSC input. +In addition, it can listen to keyboard input. + +[[fx-input]] MIDI: :: +When selected, ReaLearn captures all MIDI events that flow into this ReaLearn VSTi FX instance, for example coming from track MIDI items, track MIDI receives or the track input. +This is the default selection. + +MIDI: _Some input device_:: +When selected, ReaLearn captures events from the given MIDI device directly, before it reaches REAPER's tracks. ++ +This will only work if _Enable input from this device_ is checked for the selected MIDI input device in REAPER's preferences (menu:Options[Settings > MIDI Inputs]). +The device name is prefixed with the device ID, which also shows up in the REAPER preferences. + +Unavailable MIDI input devices:: +This submenu contains MIDI input devices which are currently disconnected or not enabled in the REAPER preferences. + +OSC: _Some device_:: +When selected, ReaLearn captures events from the given OSC device. +Before any device shows up here, you need to add it via <>. + +Unavailable OSC devices:: +This submenu contains OSC devices for which control is currently disabled. + + +[[manage-osc-devices]] Manage OSC devices:: +Lets you display and modify the list of OSC devices (globally). + +::: +Opens a dialog window for adding a new OSC device. +See <>. + +_Some OSC device_::: + +Edit:::: +Lets you edit the device. +See <>. + +Remove:::: +Removes the device. +This is a global action. +If you remove a device, all existing ReaLearn instances which use this device will point to a device that doesn't exist anymore. ++ +CAUTION: This can't be undone! + +Enabled for control:::: If you disable this, ReaLearn will stop listening to this device. +This can save resources, so you should do this with each device that is not in use (as an alternative for removing it forever). + +Enabled for feedback:::: If you disable this, ReaLearn won't send anything to this device. + +Can deal with bundles:::: By default, ReaLearn aggregates multiple OSC messages into so-called OSC bundles. +Some devices (e.g. from Behringer) can't deal with OSC bundles. +Untick the checkbox in this case and ReaLearn will send single OSC messages. + +Stream Deck: :: +Disables Stream Deck control. + +Stream Deck: _Some device_:: +When selected, this ReaLearn instance will _additionally_ listen to button presses of the chosen Stream Deck device. See xref:sources/stream-deck.adoc[] for details. ++ +Unlike MIDI or OSC, choosing a Stream Deck device automatically enables feedback to the Stream Deck buttons, so you won't find a Stream Deck entry in the <>. + +_Unavailable Stream Deck devices_:: +This submenu contains supported Stream Deck devices that are currently not connected. + +[[computer-keyboard]] Computer keyboard:: + +If enabled, this ReaLearn instance will _additionally_ listen to key press and release events. + +[#output] +== Output menu + +Here you can choose to which xref:key-concepts.adoc#output-port[] ReaLearn should send MIDI/OSC xref:key-concepts.adoc#feedback[]. + +:: +This means, ReaLearn won't send any feedback to MIDI or OSC devices. +This is the default. + +[[fx-output]] MIDI: :: +This makes feedback MIDI events stream down to the next FX in the chain or to the track's hardware MIDI output. ++ +CAUTION: Under normal circumstances, it is better to use <> instead. See xref:best-practices.adoc#best-practices-input-output[]. + +[[midi-some-output-device]] MIDI: _Some output device_:: +If selected, ReaLearn will send feedback to the given MIDI output device. +This only works if _Enable output to this device_ is checked in REAPER's preferences (menu:Options[Settings > MIDI Outputs]). + +OSC: _Some device_:: +When selected, ReaLearn will send feedback to the given OSC device. +Before any device shows up here, you need to add it via <>. + +Unavailable OSC devices:: +This submenu contains OSC devices for which feedback is currently disabled. + +Manage OSC devices:: +See <> in the input section of the menu. + +[#osc-device-dialog] +== OSC device dialog + +The OSC device dialog lets you edit the settings of a ReaLearn OSC device and can be opened via <>. + +Name:: A descriptive name of the device, e.g. "TouchOSC on my Android phone". + +Local port:: Required for control. +The UDP port on which ReaLearn should listen for OSC control messages. ++ +This port must be reserved exclusively for ReaLearn! +If you already use this port in another application (e.g. in REAPER's own OSC control surface) it won't work and ReaLearn will bless you with an "unable to connect" message in the "Input" dropdown. + +Device host:: Required for feedback only. +It's the IP address of the OSC device to which ReaLearn should send feedback messages. +This address is usually displayed on your OSC device (e.g. as "Local IP address"). +When targeting an OSC software that runs on the same computer as REAPER and ReaLearn, enter the special IP address `127.0.0.1` (or `localhost`). ++ +When you configure your OSC connection on the device side, you must provide a _host_ as well. +There you should enter the IP address of the computer which runs REAPER and ReaLearn. +You can easily find it by pressing the xref:further-concepts/unit.adoc#projection[] button in ReaLearn, choosing the old browser-based projection and scrolling down a bit. +It's the value next to *Host* and should start with `192.168.`. + +Device port:: Required for feedback only. +The UDP port on which the OSC device listens for OSC feedback messages. + +All OSC device configurations will be saved in the REAPER resource directory (menu:Options[Show REAPER resource path in explorer/finder]) in the file +`Helgoboss/ReaLearn/osc.json`. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/let-through-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/let-through-section.adoc new file mode 100644 index 0000000..ed8b11d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/let-through-section.adoc @@ -0,0 +1,13 @@ += Let MIDI through section + +image:realearn/screenshots/main-panel-let-through-checkboxes.png[Screenshot] + +See xref:further-concepts/unit.adoc#letting-through-events[]. + +Matched events:: If checked, even MIDI events matched by at least one mapping are let through. +If unchecked (default), such events are filtered out. + +Unmatched events:: If checked (default), MIDI events that are not matched by any mappings are let through. +If unchecked, such events are filtered out. + +Check out xref:user-interface/main-panel/menu-bar.adoc#match-even-inactive-mappings[] to fine-tune matching behavior. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mapping-group-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mapping-group-section.adoc new file mode 100644 index 0000000..2e06886 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mapping-group-section.adoc @@ -0,0 +1,36 @@ += Mapping group section + +image:realearn/screenshots/main-panel-group.png[Screenshot] + +[[mapping-group]] +== Group menu + +See xref:further-concepts/mapping.adoc#group[]. + +The group menu contains the following options: + +:: Displays all mappings in the compartment, no matter to which group they belong. +In this view, you will see the name of the group on the right side of a mapping row. + +:: Displays mappings that belong to the _default_ group. +This is where mappings end up if you don't care about grouping. +This is a special group that can't be removed. + +_Custom group_:: +Displays all mappings in your custom group. + +== Add button + +Allows you to add a group and give it a specific name. + +== Remove button + +Removes the currently displayed group. +It will ask you if you want to remove all the mappings in that group as well. +Alternatively they will automatically be moved to the default group. + +== Edit button + +Opens the group panel, which allows you to change xref:further-concepts/target.adoc#group-properties[]. + +image:realearn/screenshots/group-panel.png[Group panel] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mapping-row.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mapping-row.adoc new file mode 100644 index 0000000..854ec2d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mapping-row.adoc @@ -0,0 +1,73 @@ + += Mapping row + +image:realearn/screenshots/main-panel-mapping-row.png[Screenshot] + +Each mapping row represents one ReaLearn mapping. + +The mapping, source and target labels of a mapping row are greyed out whenever the mapping is _off_. +See xref:key-concepts.adoc#mapping[]. + +== Mapping-enabled checkbox + +This checkbox at the top left of the mapping row enables or disables the mapping as a whole. + +== Activity indicator (●) + +This indicator at the very left of the mapping row lights up on incoming control messages whenever they match the mapping source. + +Attention: This doesn't necessarily mean that the message will reach the target, although it often does. +There are certain settings in the xref:user-interface/mapping-panel/glue-section.adoc[] section which allow you to filter messages even they matched the source (e.g. xref:user-interface/mapping-panel/glue-section.adoc#source-min-max[]). + +== Up/down buttons + +Use these buttons to move this mapping up or down the list. + +== Control/feedback-enabled checkboxes (→/←) + +Use these checkboxes to enable/disable control and/or feedback for this mapping. +Disabling both has the same effect as disabling the mapping as a whole. + +[[row-edit]] +== Edit button + +Opens the mapping panel for this mapping. + +== Duplicate button + +Creates a new mapping just like this one right below. + +== Remove button + +Removes this mapping from the list. + +[[learn-source]] +== Learn source button + +Starts or stops learning the source of this mapping. +See xref:key-concepts.adoc#learn-source[]. + +[[learn-target]] +== Learn target button + +Starts or stops learning the target of this mapping. + +Learning a target that is currently being automated is not possible at the moment because ReaLearn can't know if the value change notification is coming from the automation or your touch interaction. + +== Right-click menu + +Each mapping row provides a right-click menu for accessing the following functions: + +Copy:: Copies this mapping to the clipboard. +Paste (replace):: Replaces this mapping with the mapping in the clipboard. +If the clipboard contains just a part of a mapping (source, mode or target), then just this part gets replaced. +Paste (insert below):: Creates a new mapping that's like the mapping in the clipboard and places it below this mapping. +Copy part:: Copies just a part of the mapping (activation condition, source, mode or target). +Move to group:: Lets you move this mapping to another mapping group. +Advanced:: Provides access to expert features. +Copy as Lua::: Copies this mapping as Lua/Luau code. +This is an indispensable tool if you want to build your mappings in Luau because it gives you a readily executable code snippet that you can adjust as desired. +Copy as Lua (include default values)::: Includes even default values. +Paste from Lua (replaces)::: Like _Paste (replace)_ but treats the clipboard content as Luau code. +Paste from Lua (insert below)::: Like _Paste (insert below)_ but treats the clipboard content as Luau code. +Log debug info (now)::: Logs debug information about this particular mapping. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mappings-toolbar.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mappings-toolbar.adoc new file mode 100644 index 0000000..176e350 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/mappings-toolbar.adoc @@ -0,0 +1,61 @@ + += Mappings toolbar + +image:realearn/screenshots/main-panel-mapping-toolbar.png[Screenshot] + +[[add-one]] +== Add one button + +Adds a new mapping at the end of the current mapping list. + +[[learn-many]] +== Learn many button + +Allows you to add and learn many new mappings in a convenient batch mode. +Click this button and follow the on-screen instructions. +Click _Stop_ when you are finished with your bulk learning strike. + +[#search] +== Search field + +Enter text here in order to display just mappings whose name matches the text. + +You can search for mappings that have a certain tag by entering the tag name prefixed with the hash character `#`. +For example, you can search for all mappings tagged with the tag `mixing` by entering `#mixing`. + +The search expression also supports wildcards `\*` and `?` for doing blurry searches. `*` stands for zero or more arbitrary characters and `?` stands for one arbitrary character. + +[[filter-source]] +== Filter source button + +When you press this button, ReaLearn will start listening to incoming MIDI/OSC events and temporarily disable all target control. +You can play around freely on your controller without having to worry about messing up target parameters. +Whenever ReaLearn detects a valid source, it will filter the mapping list by showing only mappings which have that source. + +This is a great way to find out what a specific knob/fader/button etc. is mapped to. +Please note that the list can end up empty (if no mapping has that source). + +As soon as you press btn:[Stop], the current filter setting will get locked. +This in turn is useful for temporarily focusing on mappings with a particular source. + +When you are done, and you want to see all mappings again, press the btn:[X] +button to the right, which clears the filter. + +TIP: Before you freak out thinking that ReaLearn doesn't work anymore because it won't let you control targets, have a quick look at this button. +ReaLearn might still be in "filter source" mode. +Then just calm down and press btn:[Stop]. +It's easy to forget. + +[[filter-target]] +== Filter target button + +If you want to find out what mappings exist for a particular target, press this button and touch something in REAPER. + +As soon as you have touched a valid target, the list will show all mappings with that target. + +Unlike <>, ReaLearn will automatically stop learning as soon as a target was touched. + +Press the btn:[X] button to clear the filter and show all mappings again. + +The mapping rows area consists of multiple mapping rows. +One for each mapping. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/menu-bar.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/menu-bar.adoc new file mode 100644 index 0000000..d9f1f65 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/menu-bar.adoc @@ -0,0 +1,301 @@ += Menu bar + +image:realearn/screenshots/main-panel-menu-bar.png[Screenshot] + +== Menu button + +This opens the main menu of Helgobox/ReaLearn. +The same menu opens when you right-click an empty area. + +It provides the following entries. + +[[copy-listed-mappings]] Copy listed mappings:: +Copies all mappings that are visible in the current mapping list (respecting group, search field and filters) to the clipboard. +You can insert them by opening the right-click menu of a xref:user-interface/main-panel/mapping-row.adoc[]. + +[[paste-mappings-replace-all-in-group]] Paste mappings (replace all in group):: +Replaces all mappings in the current group with the mappings in the clipboard. + +Modify multiple mappings:: + +Auto-name listed mappings::: +Clears the names of all listed mappings. +As a result, ReaLearn will display dynamically auto-generated mapping names instead. + +Name listed mappings after source::: +Sets the names of each listed mapping to the first line of its source label. + +Make sources of all main mappings virtual::: +Attempts to make the sources in the main compartment virtual by matching them with the sources in the controller compartment. +This is useful if you already learned a bunch of MIDI/OSC/keyboard sources in the main compartment, just to find out later that you would like to use a controller preset that exposes virtual sources. + +Make targets of listed mappings sticky::: +Changes the targets of all currently listed mappings so that they use xref:further-concepts/target.adoc#sticky-selectors[] object selectors by attempting to resolve the objects from xref:further-concepts/target.adoc#non-sticky-selectors[]. + +Make targets of listed mappings non-sticky::: +Changes the targets of all currently listed mappings so that they use the desired xref:further-concepts/target.adoc#non-sticky-selectors[]. +You can see this in action in link:https://www.youtube.com/watch?v=kebF_NKAweA&list=PL0bFMT0iEtAgKY2BUSyjEO1I4s20lZa5G&index=9[tutorial video 9]. ++ +The general procedure is: ++ +1. Choose the desired track selector +2. Choose the desired FX selector +3. ReaLearn will change the selectors for all targets where this is applicable + +Move listed mappings to group:: +Moves all currently listed mappings to the specified group. + +Advanced:: +Provides access to expert features. + +Copy listed mappings as Lua::: +Like <> but generates Lua/Luau code instead. + +Copy listed mappings as Lua (include default values)::: +Generates Lua/Luau code that contains even those properties that correspond to ReaLearn's defaults. + +Paste from Lua (replace all in group)::: +Like <> but treats the clipboard content as Luau code. + +[[dry-run-lua-script]] Dry-run Lua script from clipboard::: +Executes the Luau code in the clipboard and opens the returned data structure in a link:https://en.wikipedia.org/wiki/Text_editor[text editor]. +See xref:best-practices.adoc#troubleshooting-luau-import[] to learn in which situations this can be useful. + +[[compartment-parameters,Compartment parameters menu]] Compartment parameters:: +This shows all parameters of the current compartment and makes it possible to customize them. +See xref:further-concepts/compartment.adoc#compartment-parameter[] to learn what such parameters are good for. ++ +Each parameter provides the following customization options: + +Name::: +Changes the name of this parameter. + +[[set-compartment-parameter-value-count]]Value count::: +Lets you enter the maximum number of values. +This automatically turn this parameter into a discrete parameter. +See xref:further-concepts/compartment.adoc#continuous-vs-discrete-compartment-parameters[]. + +Compartment presets:: + +Create compartment preset workspace::: +Exports the Luau SDK files of this ReaLearn version into a new compartment preset workspace directory with a random name. +See section xref:further-concepts/compartment.adoc#writing-presets-with-luau[] for details. + +Create compartment preset workspace (including factory presets)::: +Exports the Luau SDK files of this ReaLearn version and all factory presets for this compartment into a new compartment preset workspace directory with a random name. +See section xref:further-concepts/compartment.adoc#writing-presets-with-luau[] for details. + +Open compartment preset folder::: +Opens the ReaLearn preset folder for this compartment in a file manager. + +[[reload-all-presets]] Reload all compartment presets from disk::: +If you made direct changes to preset files, you should choose this entry in order to reflect these changes in the preset list of this xref:key-concepts.adoc#compartment[]. ++ +This *will not* apply an adjusted preset to the current compartment, it will just reload the list. +If you want to apply a preset that has been changed on disk, you need to reload it by selecting it in the preset menu once again! + +Compartment tools:: + +Convert toolbar to Stream Deck mappings::: +This takes a toolbar of your choice and converts it to a list of mappings that will project the toolbar on your Stream Deck via xref::sources/stream-deck.adoc[]. ++ +This feature is experimental and subject to improvement. ++ +NOTE: It will work best with custom toolbars that use non-default icons. +Many of the default toolbar icons are not in the `Data/toolbar_icons` folder and therefore can't yet be loaded by ReaLearn. +However, in this case, you will see a descriptive text instead. + + +[[edit-compartment-wide-lua-code]] Edit compartment-wide Lua code:: +Allows you to edit the compartment-wide Lua code. +See section xref:further-concepts/compartment.adoc#compartment-wide-lua-code[]. + +[[unit-options]] Unit options:: + +[[match-even-inactive-mappings]] Match even inactive mappings::: +By default, when encountering an incoming MIDI or keyboard event that matches at least one mapping, ReaLearn filters it out so that it doesn't reach other parts of REAPER anymore. Normally, xref:further-concepts/mapping.adoc#mapping-activation-state[inactive] mappings are not relevant here. Inactive mappings never match. ++ +Consider a mapping with a xref:targets/track.adoc[track target], for example. As long as the corresponding track exists, the target and its mapping are active. Each incoming MIDI event which triggers that target will be filtered out. But as soon as you remove that track, the target and its mapping turn inactive. Consequently, the same type of MIDI events will not be filtered out anymore. ++ +This behavior can be undesired. To get a more predictable MIDI and keyboard filtering behavior, you can enable this checkbox. If you do that, even inactive mappings will participate in matching. That way, temporarily inactive mappings will not let unwanted events "leak through". + +[[send-feedback-only-if-track-armed]] Send feedback only if track armed::: +Here you can tell ReaLearn to only send feedback when the track is armed. ++ +At the moment, this can only be unchecked if ReaLearn is on the normal FX chain. +If it's on the input FX chain, unarming forcefully disables feedback because REAPER generally excludes input FX from audio/MIDI processing while a track is unarmed (this is subject to change in the future). ++ +[TIP] +.Recommendations +==== +* If input is set to xref:user-interface/main-panel/input-output-section.adoc#fx-input[], you probably want to enable this option. +Rationale: Unarming will naturally disable control, so disabling feedback is just consequent. +* If input is set to a specific MIDI or OSC device, you probably want to disable this option in order to allow feedback even when unarmed. +==== + +Reset feedback when releasing source::: +When using ReaLearn the normal way, it's usually desired that feedback is reset when the corresponding sources are not in use anymore (e.g. lights are switched off, displays are cleared, motor faders are pulled down). ++ +You can prevent this unit from doing that by disabling this option. +This can be useful e.g. when using REAPER/ReaLearn just in feedback direction, in order to take control of a hardware device (= using ReaLearn the other way around, "controlling from target to source"). + +[[make-unit-superior]] Make unit superior::: +Makes this unit superior. +See xref:further-concepts/unit.adoc#superior-units[] to learn more about this feature. + +Use unit-wide FX-to-preset links only::: +By default, xref:further-concepts/unit.adoc#unit-fx-to-preset-link[unit-wide links] are applied *in addition* to the global links and take precedence over them. +This checkbox makes sure that *only* unit-wide links are used for this xref:key-concepts.adoc#unit[]. + +Stay active when project in background::: +Determines if and under which conditions this ReaLearn unit should stay active when the containing project tab is not the active one. +Applies to in-project ReaLearn instances only, not to monitoring FX instances! + +Never:::: +Will only be active when its project tab is active. +Only if background project is running:::: +Follows REAPER's project tab settings ("Run background projects" and "Run stopped background projects"). +Always (more or less):::: +Attempts to stay active no matter what. +Please note that this is technically not always possible when input is set to xref:user-interface/main-panel/input-output-section.adoc#fx-input[] or output to xref:user-interface/main-panel/input-output-section.adoc#fx-output[], when the background project is not running. + +[[unit-fx-to-preset-links]] Unit-wide FX-to-preset links:: +Manage a unit-wide list of links from FXs (plug-ins or JS effects) to ReaLearn main compartment presets. +Covered in link:https://www.youtube.com/watch?v=HtmWO7QNpgE&list=PL0bFMT0iEtAgKY2BUSyjEO1I4s20lZa5G&index=10[video tutorial 10]. + +Add link from last focused FX to preset::: +This lets you link whatever FX window was focused before focusing ReaLearn, to an arbitrary main compartment preset. +This only works if an FX has been focused before. + +_Arbitrary FX ID_::: +If you have added a link already, you will see it here in the list. +In particular, you see the _FX ID_, which by default simply corresponds to the plug-in's original name (e.g. menu:Name: VSTi: ReaSynth (Cockos)[]). + +[[edit-fx-id]] :::: +With this, you can edit the FX ID manually. +See <> for details. + +:::: +(Globally) removes this FX-to-preset link. + +_Arbitrary main preset_:::: +The rest of the submenu tells you to which main preset the FX ID is linked. +You can change the linked preset by choosing another one in the list. + +[[logging,Logging Menu]] Logging:: + +Log debug info (now)::: Logs some information about ReaLearn's internal state. +Can be interesting for investigating bugs or understanding how this plug-in works. +Log real control messages::: When enabled, all incoming MIDI messages, OSC messages or key presses will be logged to the console. +See xref:further-concepts/unit.adoc#logging-of-real-control-messages[]. +Log virtual control messages::: When enabled, all triggered virtual control elements and their values will be logged (see xref:key-concepts.adoc#controller-compartment[]). +Log target control::: When enabled, all target invocations (parameter changes etc.) will be logged. +Log virtual feedback messages::: When enabled, all feedback events to virtual control elements will be logged (see xref:key-concepts.adoc#controller-compartment[]). +Log real feedback messages::: When enabled, all outgoing MIDI or OSC messages will be logged to the console. +See xref:further-concepts/unit.adoc#logging-of-real-feedback-messages[]. + +Send feedback now:: +Usually ReaLearn sends feedback whenever something changed to keep the LEDs or motorized faders of your controller in sync with REAPER at all times. +There might be situations where it doesn't work though. +In this case you can send feedback manually for this xref:key-concepts.adoc#unit[] by selecting this menu entry. ++ +There's a related REAPER action xref:reaper-actions.adoc#send-feedback-for-all-instances[] which sends feedback for all instances. + +Instance options:: + +[[enable-global-control]] Enable global control::: +If you enable this option, this xref:key-concepts.adoc#instance[] will start to automatically add/remove units based on connected controllers. +See xref:further-concepts/instance.adoc#auto-units[]. + +[[open-pot-browser]] Open Pot Browser:: +See xref:helgobox::plug-in/user-interface/menu-bar.adoc#open-pot-browser[]. + +Show App:: +See xref:helgobox::plug-in/user-interface/menu-bar.adoc#show-app[]. + +Close App:: +See xref:helgobox::plug-in/user-interface/menu-bar.adoc#close-app[]. + +User interface:: + +Background colors::: +Enables/disables the usage of background colors in the ReaLearn user interface (enabled by default). + +Server:: +See xref:helgobox::plug-in/user-interface/menu-bar.adoc#server[]. + +[[global-fx-to-preset-links]] Global FX-to-preset links:: +Lets you manage xref:further-concepts/unit.adoc#global-fx-to-preset-link[global FX-to-preset links]. +Works exactly as the <> menu. + +[[fx-id-dialog]] +== FX ID dialog + +The FX ID dialog is used to edit which properties of a FX trigger a preset change. +It is opened via menu action <>. + +FX name:: +Allows you to adjust the (original) plug-in name that triggers the preset change. + +FX file name:: +Allows you to adjust the plug-in file name that triggers the preset change. + +FX preset name:: +Maybe the FX name or file name is not enough for you to decide which preset you want to load. +You can add a preset name as additional criteria. ++ +.Samplers +==== +If you have a sampler, you can load different ReaLearn presets depending on which sample library is loaded into your sampler. +Just add two links with the same FX file name (e.g. `Kontakt 5.dll`) but different preset names. +==== + +All above-mentioned fields support wildcards. +You can use `*` for matching zero or arbitrary many characters and `?` for matching exactly one arbitrary character. + +.Matching both VST2 and VST3 plug-ins +==== +Instead of relying on the original plug-in name you could match plug-ins with similar file names (e.g. VST2 and VST3 at once): `Pianoteq 7 STAGE.*` would match both `Pianoteq 7 STAGE.dll` (VST2) and `Pianoteq 7 STAGE.vst3` (VST3). +==== + +[[export-to-clipboard-button]] +== Export to clipboard button + +Pressing the export button allows you to copy ReaLearn's settings to the clipboard so you can import them in another instance/unit or edit them in a link:https://en.wikipedia.org/wiki/Text_editor[text editor]. +See xref:further-concepts/general.adoc#import-export[]. + +The following list only describes the ReaLearn-related menu entries. For the rest, see xref:helgobox::plug-in/user-interface/menu-bar.adoc#export-to-clipboard[] in the Helgobox Reference. + +Export instance as JSON:: +Copies a _complete_ dump of this xref:helgobox:ROOT:key-concepts.adoc#instance[] to the clipboard in JSON format. + +Export main/controller compartment as JSON:: +Copies a dump of the currently visible compartment to the clipboard. +It contains about the same data that a compartment preset would contain. + +Export main/controller compartment as Lua:: +Copies a dump of the currently visible compartment to the clipboard as Lua/Luau code. +This form of Lua/Luau export skips properties that correspond to ReaLearn's default values, resulting in a minimal result. +Perfect for pasting into a forum or programming ReaLearn with focus on only those properties that matter to you. + +Export main/controller compartment as Lua (include default values):: +This Lua/Luau export includes even those properties that correspond to ReaLearn's default values, resulting in more text. +This gives you the perfect starting point if you want to extensively modify the current compartment (using the Luau programming language) or build a compartment from scratch, using even properties that you haven't touched yet in the user interface! + +[[import-from-clipboard]] +== Import from clipboard button + +Pressing the import button applies whatever ReaLearn dump is currently in the clipboard. +It supports JSON or Luau. +See xref:further-concepts/general.adoc#import-export[]. + +[#projection] +== Projection button + +Click this button to enter ReaLearn's xref:further-concepts/unit.adoc#projection[] feature. +You can choose between the old browser-based projection (which is going to disappear at some point) and the new projection that is part of the xref:helgobox::app.adoc[] (but not yet fully functional). +Hopefully, the transition to the Helgobox App, including mobile versions of that App, will soon be finished. + +== Help button (?) + +See xref:helgobox::plug-in/user-interface/menu-bar.adoc#help[]. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/notes-button.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/notes-button.adoc new file mode 100644 index 0000000..9b2adeb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/notes-button.adoc @@ -0,0 +1,7 @@ + += Notes button + +image:realearn/screenshots/main-panel-notes-button.png[Screenshot] + +Allows you to save custom notes/comments for the current compartment. +These notes are also included in compartment presets. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/preset-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/preset-section.adoc new file mode 100644 index 0000000..4cafd9f --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/preset-section.adoc @@ -0,0 +1,53 @@ += Preset section + +image:realearn/screenshots/main-panel-preset.png[Screenshot] + +== Preset menu + +This menu makes it possible to load compartment presets for the currently shown compartment. +If you select a preset in this list, its corresponding mappings will be loaded and immediately get active. + +The following menu entries are available: + +[[none-preset]] :: +This entry is selected by default. +It means that no particular preset is active. ++ +Selecting this will always clear the complete compartment, including all mappings! + +Factory:: Contains available xref:further-concepts/compartment.adoc#factory-compartment-preset[factory presets]. + +User (...):: Contains available xref:further-concepts/compartment.adoc#user-compartment-preset[user presets]. +Multiple of such submenus may exist. +Each one represents a different preset namespace/workspace. +The namespace named after you (macOS/Linux/Windows username) is your personal user namespace. ++ +For more information about preset workspaces/namespaces, see +xref:further-concepts/compartment.adoc#writing-presets-with-luau[]. + +User (Unsorted):: +This submenu contains top-level presets which are not part of a particular preset namespace/workspace. +This was common in older versions of ReaLearn, when namespaces/workspaces were not yet available. + +== Save button + +If you made changes to a user preset, you can save them by pressing this button. + +== Save as… button + +This allows you to save all currently visible mappings as a new preset. +Please choose a descriptive name. + +== Delete button + +This permanently deletes the currently chosen user preset. + +== Auto-load button + +Activates or deactivates xref:further-concepts/unit.adoc#auto-load[] mode for this ReaLearn unit. +This button is only available for the xref:key-concepts.adoc#main-compartment[] because auto-load is only about loading xref:further-concepts/compartment.adoc#main-preset[main presets]. + +Off:: Disables auto-load mode (the default). + +[[auto-load-based-on-unit-fx,Auto-load based on unit FX]] Based on unit FX:: +Switches auto-load mode on, letting ReaLearn decide about which main preset to load depending on the currently active xref:further-concepts/unit.adoc#unit-fx[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/show-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/show-section.adoc new file mode 100644 index 0000000..e6caf21 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/main-panel/show-section.adoc @@ -0,0 +1,6 @@ += Show section + +image:realearn/screenshots/main-panel-show-buttons.png[Screenshot] + +This lets you choose which mapping compartment is displayed. +See xref:key-concepts.adoc#compartment[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel.adoc new file mode 100644 index 0000000..da47b22 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel.adoc @@ -0,0 +1,6 @@ += Mapping panel + +When you press the btn:[Edit] button of a mapping row, the _mapping panel_ appears, which lets you look at the corresponding mapping in detail and modify it. + +.Mapping panel +image::realearn/screenshots/mapping-panel.png[Screenshot] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/advanced-settings-dialog.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/advanced-settings-dialog.adoc new file mode 100644 index 0000000..be3cf57 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/advanced-settings-dialog.adoc @@ -0,0 +1,120 @@ += Advanced settings dialog + +[[advanced-settings-dialog]] +CAUTION: Expert level! + +This dialog allows access to advanced configuration by entering text in the https://en.wikipedia.org/wiki/YAML[YAML] configuration language. +It can be opened using the xref:user-interface/mapping-panel/top-section.adoc#advanced-settings[] of the mapping panel. + +== The YAML language + +This is not a programming language, so you can't write loops, conditions or anything like that. +Instead, think of it as a language for writing configuration. +Do you know INI files? +REAPER uses INI files to save configuration. +YAML is a bit like that, just much more expressive because it allows you to not only express flat key-value pairs (e.g. `edit_fontsize=29`) but also deeply nested configuration data and lists. + +[IMPORTANT] +.YAML is indentation-sensitive +==== +Indentation matters! +The bright side of this is that it always looks clean. +The dark side is that ReaLearn will refuse to save your settings if you messed up the indentation. + +Therefore: Be consistent with your indentation (e.g. use always an indentation of 2 spaces for nesting) and have an utmost attention to detail when doing copy and paste from the examples in this section! +==== + +[IMPORTANT] +.Verbatim text is not saved +==== +When you close the text editor and ReaLearn saves your advanced settings as part of the mapping, it will not save the text that you have entered _verbatim_. +It will save a structural representation of what you entered. +Plus, it will strip comments!. +That means if you open the advanced settings again, your text could look a bit different, in particular it can have a different formatting. +But don't worry, it _means_ exactly the same to ReaLearn. +==== + +== Supported configuration properties + +In this section you will find examples that cover all currently supported configuration properties. +You can copy and paste the stuff you need to the link:https://en.wikipedia.org/wiki/Text_editor[text editor], remove the parts that you don't need and adjust the rest. +Comments (lines starting with `#`) will be removed automatically. + +[#mapping-lifecycle-actions] +=== Mapping lifecycle actions + +ReaLearn allows you to define MIDI messages to be sent to the output whenever a mapping turns active or inactive. +See xref:further-concepts/mapping.adoc#mapping-activation-state[]. + +Example use cases: + +* Accessing device-specific features via system-exclusive MIDI messages. +* Choosing a different LED color/style depending on the active mapping. +* Initializing a sys-ex-controllable display with some mapping-specific text (more difficult). + +These are the available configuration properties: + +[source,yaml] +---- +# Contains stuff to be done whenever this mapping becomes active. +on_activate: + # A list of MIDI messages to be sent to the output when this mapping becomes active. + # + # At the moment, only messages of type "raw" are supported. Although this covers all possible types + # of MIDI messages, it's a bit hard to express e.g. simple NOTE ON or CC messages with this notation. + # In particular, you would need to know how MIDI messages are presented as byte sequences. Future ReaLearn + # versions will provide more convenient ways to describe simple MIDI messages. + send_midi_feedback: + # This is an example of a system-exclusive message ("SysEx"). It's usually expressed in hexadecimal string + # notation. Make sure to include the leading F0 and trailing F7, which is the begin and end marker of all + # system-exclusive messages! + - raw: F0 00 20 6B 7F 42 02 00 10 77 01 F7 + # Instead of above hexadecimal string notation, you could also use an array of decimal numbers to describe a raw + # message. The following is a NOTE ON of note 74 on channel 1 with velocity 100. + - raw: + # NOTE ON on channel 1 + - 144 + # Note number 74 + - 74 + # Note velocity 100 + - 100 + +# Contains stuff to be done whenever this mapping becomes inactive. +on_deactivate: + # A list of MIDI messages to be sent to the output when this mapping becomes inactive. + send_midi_feedback: + # Supports exactly the same kinds of messages as described above in "on_activate". + - raw: F0 00 20 6B 7F 42 02 00 10 77 14 F7 +---- + +Please remember that YAML comments (e.g. `# The following line does this and that`) _will not be saved_! +In case you want to explain something, you need to write it as YAML property, such as in the following example: + +[source,yaml] +---- +comment: "The following configuration makes the rightmost pad of the MiniLab mkII light up in red color." +on_activate: + send_midi_feedback: + - raw: F0 00 20 6B 7F 42 02 00 10 77 01 F7 +---- + +ReaLearn will ignore any unknown properties. + +TIP: If you use input xref:user-interface/main-panel/input-output-section.adoc#fx-input[] and find that MIDI lifecycle messages aren't sent, no matter what, make sure "Send feedback only if track armed" is disabled (see xref:user-interface/main-panel/menu-bar.adoc#unit-options[])! + +WARNING: Disabling the complete ReaLearn instance will cause all mappings in all units of that instance to deactivate. +However, sending MIDI messages on deactivation in this case will only work if the output is a device! +If it is xref:user-interface/main-panel/input-output-section.adoc#fx-output[], it will not send anything because REAPER will not give that ReaLearn instance any chance to output MIDI messages once it's disabled. +Instead, the MIDI message will queue up and be sent once you enable that instance again ... which is probably not what you want. + +== Open in text editor button (Windows and Linux only) + +Opens the settings in the system link:https://en.wikipedia.org/wiki/Text_editor[text editor] or whatever program is associated with YAML files. +It depends on your system setup if this works or not. +If it does and if your link:https://en.wikipedia.org/wiki/Text_editor[text editor] is good, this can make editing larger YAML snippets more convenient (e.g. by providing syntax highlighting). + +As soon as you save the file and close the editor, the text will automatically appear in the "Advanced settings" text area. + +== Help button + +Will open an online version of the user guide section that describes the available configuration properties. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/bottom-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/bottom-section.adoc new file mode 100644 index 0000000..8011f18 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/bottom-section.adoc @@ -0,0 +1,46 @@ += Bottom section + +image:realearn/screenshots/mapping-panel-bottom.png[Screenshot] + +The section at the bottom has the following functions: + +- To provide context-sensitive help for the glue section +- To provide control information, feedback information and error reporting +- To provide navigation and general mapping control + +== Help + +Context-sensitive help for the glue section. +Whenever you touch a setting in the glue section, some text will appear which explains what this element does, both for the _control_ and for the +_feedback_ direction (if applicable). + +== Activity info area + +The left text area shows information about how an incoming control value was handled and possible target control errors. + +NOTE: If the target supports MIDI real-time control and the source is a MIDI source, this currently only works if "Log target control" is enabled (see xref:user-interface/main-panel/menu-bar.adoc#logging[]). + +The right text area shows information about which feedback values are sent from the glue section to the source. + +[#beep-on-success] +== Beep on success checkbox + +Makes the mapping play a sound whenever the target has been invoked successfully. +Nice for trigger-like targets such as xref:targets/realearn/take-mapping-snapshot.adoc[] for which there's no other good way to know if it worked. + +[#previous] +== Previous button + +Allows you to jump to the previous mapping. +Considers only mappings that are currently visible in the mapping rows panel. + +[#next] +== Next button + +Allows you to jump to the next mapping. +Considers only mappings that are currently visible in the mapping rows panel. + +[#enabled] +== Enabled checkbox + +Enables or disables the mapping as a whole. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/glue-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/glue-section.adoc new file mode 100644 index 0000000..d049bcb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/glue-section.adoc @@ -0,0 +1,643 @@ += Glue section +:control: Control → +:feedback: Feedback ← +:momentary-button: Momentary button +:velocity-sensitive-button: Velocity-sensitive button (key, pad, ...) +:incremental-velocity-sensitive-button: Incremental velocity-sensitive button (key, pad, ...) +:incremental-button: Incremental button +:range-element: Range element (fader, knob, ...) +:encoder: Rotary endless encoder +:led: LED +:value-indicator: Value indicator (LED ring, motor fader, ...) + +image:realearn/screenshots/mapping-panel-glue.png[Screenshot] + +The xref:key-concepts.adoc#glue[] section is divided into several subsections some of which make sense for all kinds of sources and others only for some. +Having so many settings available at the same time can be a bit daunting. +ReaLearn helps you by hiding settings that don't make sense in the current context. + +It shows or hides them based on criteria like this: + +* Is control and/or feedback enabled for the mapping? +* What are the characteristics of the source and target? +* What's the current setting of <> and <>? + +== Reset to defaults button + +Resets the settings to some sensible defaults. + +[#reverse] +== Reverse checkbox + +{control}:: +include::partial$glue/reverse/control.txt[] + +{feedback}:: +include::partial$glue/reverse/feedback.txt[] + +include::partial$glue/usage-dependent-effect.adoc[] + +|{momentary-button} +|{control} +|Switches the target off when pressed and on when released + +|{range-element} +|{control} +|The higher the fader position, the lower the target value + +|{incremental-button} +|{control} +|Decreases the target value on press instead of increasing it + +|{encoder} +|{control} +|Decreases the target value when turning clockwise and decreases it when turning counter-clockwise + +|{led} +|{feedback} +|Uses *off* LED color if target is on and *on* LED color if target is off + +|{value-indicator} +|{feedback} +|The higher the target value, the lower the indicated value + +|=== + +[#target-min-max] +== Target Min/Max controls + +{control}:: +include::partial$glue/target-min-max/control.txt[] + +{feedback}:: +include::partial$glue/target-min-max/feedback.txt[] + +.Squeezing the track volume value range +==== +If you set this to "-6 dB to 0 dB" for a _Track volume_ target, the volume will always stay within that dB range when controlled via this mapping. +However, it wouldn't prevent the volume from exceeding that range if changed e.g. in REAPER itself. +==== + +This setting can be used with all targets that work with xref:further-concepts/mapping.adoc#absolute-control-value[absolute control values] (all targets except xref:targets/project/invoke-reaper-action.adoc[] with relative xref:targets/project/invoke-reaper-action.adoc#invocation-type[]). + +[#value-sequence] +== Value sequence field + +{control}:: +include::partial$glue/value-sequence/control.txt[] + +Allows you to define a xref:further-concepts/glue.adoc#target-value-sequence[]. +All values are entered comma-separated using the target unit specified with the xref:user-interface/mapping-panel/target-section.adoc#display-unit[]. + +You can provide only one of <> or <>. + +[#group-interaction] +== Group interaction + +{control}:: +include::partial$glue/group-interaction/control.txt[] + +See xref:further-concepts/mapping.adoc#group[]. + +TIP: If you want to control _other_ mappings only and not _this_ mapping, just pick a target that doesn't have any effect, for example the xref:targets/realearn/dummy.adoc[]. + +None:: +Switches group interaction off. +This is the default. +Incoming control events will just affect _this_ +mapping, not others. + +[[same-control]] Same control:: +This will broadcast any incoming control value to all other mappings in the same group. +The glue section of this mapping will be ignored when controlling the other mappings. +The glue sections of the other mappings will be respected, including the <>. + +[[same-target-value]] Same target value:: +This will set the target value of each other mapping in the same group to the target value of this mapping. +Nice: It will respect the <> of both this mapping and the other mappings. +All other settings of the glue section will not be processed. +Needless to say, this kind of control is always absolute, which means it can lead to parameter jumps. +Therefore, it's most suited for on/off targets. +If you don't like this, choose <> instead. + +[[inverse-control]] Inverse control:: +This is like <> but broadcasts the _inverse_ of the incoming control value. + +[[inverse-target-value]] Inverse target value:: +This is like <> but sets the target values of the other mappings to the +_inverse_ value. +This is very useful in practice with buttons because it essentially gives you exclusivity within one group. +It's a great alternative to the xref:user-interface/mapping-panel/target-section.adoc#exclusive[] which is available for some targets. +Unlike the latter, <> allows for exclusivity between completely different target types and completely custom groupings - independent of e.g. organization of tracks into folders. + +Inverse target value (on only):: +Variation of <> that applies the inverse only when the target value is > 0%. + +Inverse target value (off only):: +Variation of <> that applies the inverse only when the target value is 0%. + +[[feedback-type-controls]] +== Feedback type controls + +{feedback}:: +include::partial$glue/feedback-type/feedback.txt[] + +See xref:further-concepts/glue.adoc#feedback-type[]. + +[#feedback-style] +== Feedback style menu (...) + +The ... button provides options to change the _feedback style_. +At the moment, it's all about setting colors. + +TIP: If you use xref:further-concepts/glue.adoc#dynamic-feedback[], changes made here don't have any effect because you are supposed to provide style properties as part of the Luau script result (which is much more flexible). + +Color / Background color:: +With this you can define the color and background color of the displayed text. +Of course this will only work if the source supports it! + +::: +Chooses the default color, that is the one which is preferred for the corresponding controller and display type. + +::: +Opens a color picker so you can choose the color of your choice. + +_Property name_::: Maybe you don't want a fixed color but a dynamic one that changes whenever your target changes. +Choose one of the properties to make that happen. +Do a full-text search in the reference to learn about the meaning of the property. + +[[source-min-max]] +== Source Min/Max controls + +{control}:: +include::partial$glue/source-min-max/control.txt[] + +{feedback}:: +include::partial$glue/source-min-max/feedback.txt[] + +Doesn't have an effect on xref:further-concepts/mapping.adoc#relative-control-value[relative control values]. + +include::partial$glue/usage-dependent-effect.adoc[] + +|{momentary-button} +|{control} +|If min > 0 and <> is <>, button releases are ignored. +Because this also affects {feedback}, it's usually better to use the <> instead! + +|{velocity-sensitive-button} +|{control} +|Defines the observed velocity range, for example to react to only the lower velocity layer of a key press. + +|{range-element} +|{control} +|Defines the observed value range. +For example to react only to the upper half of a fader. + +|{led} +|{feedback} +|On many controllers which support colored LEDs, *Min* sets the *off* color and *Max* sets the *on* color. + +|{value-indicator} +|{feedback} +|Sets the lowest/highest indicated value. + +|=== + +By restricting that range, you basically tell ReaLearn to or only the lower velocity layer of a key press. + +This range also determines the minimum and maximum xref:key-concepts.adoc#feedback[] value. + +[#out-of-range-behavior] +== Out-of-range behavior menu + +{control}:: +include::partial$glue/out-of-range-behavior/control.txt[] + +{feedback}:: +include::partial$glue/out-of-range-behavior/feedback.txt[] + +See <> and <>. + +There are the following options: + +[cols="h,d,d"] +|=== +| | {control} | {feedback} + +| Min or max | If the source value is < _Source Min_, ReaLearn will behave as if _Source Min_ was received (or 0% if _Source Min_ = _Source Max_). + +If the source value is > _Source Max_, ReaLearn will behave as if _Source Max_ was received (or 100% if _Source Min_ = _Source Max_). | If the target value is < _Target Min_, ReaLearn will behave as if _Target Min_ was detected (or 0% if _Target Min_ = _Target Max_). + +If the target value is > _Target Max_, ReaLearn will behave as if _Target Max_ was detected (or 100% if _Target Min_ = _Target Max_). + +| Min | ReaLearn will behave as if _Source Min_ was received (or 0% if _Source Min_ = _Source Max_). | ReaLearn will behave as if _Target Min_ was detected (or 0% if _Target Min_ = _Target Max_). Useful for getting radio-button-like feedback. + +|[[out-of-range-behavior-ignore,Ignore]] Ignore | Target value won't be touched. | No feedback will be sent. +|=== + +[[absolute-mode]] +== Mode ("Absolute mode") menu + +{control}:: +include::partial$glue/absolute-mode/control.txt[] + +TIP: Not all modes make sense at all times! +It mostly depends on the character of the source. +If a mode doesn't make sense given the current source, it will be marked as `NOT APPLICABLE`. +In this case, you should choose another mode or change the source. + +[[absolute-mode-normal]] Normal:: + +Takes and optionally transforms absolute source control values _the normal way_. _Normal_ means that the current target value is irrelevant and the target will just be set to whatever absolute control value is coming in (potentially transformed). + +[[incremental-button,Incremental button mode]] Incremental button:: +With this you can "go relative" with buttons instead of encoders in a "previous/next fashion". ++ +Let's assume you use the _MIDI Note velocity_ and select _Incremental button_ mode. +Then it works like this: Each time you press the key, the target value will increase, according to the mode's settings. +You can even make the amount of change velocity-sensitive! +If you want the target value to decrease, just check the <>. + +[[toggle-button-mode,Toggle button mode]] +Toggle button:: +Toggle button mode is used to toggle a target between on and off states. +It only makes sense for momentary buttons (which fire a value > 0 on each press). ++ +Here's how it works in detail: ++ +* If the current target value is within the first half of the target min/max range, it's considered as _off_ and will therefore be switched _on_ (set to _target max_). +If it's within the second half, it's considered as _on_ and will therefore be switched _off_ (set to _target min_). +* It works a bit differently if _target min_ and _target max_ have the same value (which is a common technique to set the target to a specific value on the press of a button). +Instead of toggling between _target min_ and _target max_, this mode now toggles between this specific value (= _target min_ = _target max_) and 0%. +This is useful whenever you have a set of buttons each of which sets the same target to a different value, and you want them to toggle between the specified value and an initial value (0%). ++ +This mode is not supported for controller mappings that have a virtual target. ++ +[TIP] +==== +Sometimes the controller itself provides a toggle mode for buttons. *Don't use it!* + +Always set up your controller buttons to work in momentary mode! +It's impossible for the controller to know which state (on/off) a target currently has. +Therefore, if you use the controller's built-in toggle function, it's quite likely that it gets out of sync with the actual target state at some point. + +ReaLearn's own toggle mode has a clear advantage here. +==== + +[[make-relative]] Make relative:: +This converts incoming absolute fader/knob movements into relative adjustments of the target value. +It somewhat resembles takeover mode <> but has important differences: + +- It's guaranteed that a full fader/knob swipe from 0% to 100% always results in a swipe over the full target range (assuming the target was at 0% initially). +- It doesn't need to know the current target value. +Which means it also works for mappings with xref:further-concepts/target.adoc#virtual-target[virtual targets]. + +[[performance-control]] Performance control:: +This mode emulates the behavior of a typical soft synth modulation matrix mapping: It uses the target value that has been set in REAPER (not via this ReaLearn mapping) as an offset and starts changing it from there. + +[#round-target-value] +== Round target value checkbox + +{control}:: +include::partial$glue/round-target-value/control.txt[] + +Only a few targets support that, such as xref::targets/project/set-tempo.adoc[]. + +[#takeover-mode] +== Takeover mode menu + +{control}:: +include::partial$glue/takeover-mode/control.txt[] + +If you are not using motorized faders, absolute mode is inherently prone to parameter jumps. +A parameter jump occurs if you touch a control element (e.g. fader) whose position in no way reflects the current target value. +This can result in audible jumps because the value is changed abruptly instead of continuously. +You can deal with this by setting the right takeover mode. + +ReaLearn provides multiple takeover modes that decide how to deal with situations when a target parameter jump would occur. + +Off:: + +The default setting: Jumps allowed. + +[[pick-up]] Pick up:: +This is the same as _Soft takeover_ in REAPER's built-in MIDI learn. +It prevents jumps by not changing the target value until your control element reaches it. ++ +In certain cases, this mode can cause the target value to get stuck. +This happens with faders/knobs that cause jumps themselves when moved very rapidly. +If you don't like that, you might want to try <>. + +[[pick-up-tolerant]] Pick up (tolerant):: +This is like <> but makes extra sure that the target value doesn't get stuck. ++ +However, unlike <>, this mode will jump if you cause a jump on your controller! +Imagine using a touch strip. +This kind of control element allows you to jump to arbitrary values at any time. +Tolerant mode will not prevent this kind of jumps! + +Long time no see:: +This is similar to <> with the difference that the current target value will gradually "come your way". +This results in seamless and fast reunification of control and target value, but it can feel weird because the target value can temporarily move in the opposite direction of the fader movement. +In older ReaLearn versions this was called "Slowly approach if jump too big". + +[[takeover-mode-parallel]] Parallel:: +With this mode, the target will simply follow your fader moves, in exactly the same tempo - without any scaling. +Reunification only happens when both control and target value meet at the "borders". + +Catch up:: +This mode is sometimes called "Proportional" or "Value scaling" mode. +It's like "Parallel" mode but the target value is allowed to move slower than the control value - hence the control can catch up (converge) faster. + +[#control-transformation] +== Control transformation (EEL) field + +{control}:: +include::partial$glue/control-transformation/control.txt[] + +This feature allows you to write a formula that transforms incoming control values. +While very powerful because it allows for arbitrary transformations (velocity curves, random values - you name it), it's not everybody's cup of tea to write something like that. +The formula must be written in the language https://www.cockos.com/EEL2/[EEL2]. +Some REAPER power users might be familiar with it because REAPER's JSFX uses the same language. + +Luckily, ReaLearn has a fancy editor which visualizes the formula and has some predefined templates built-in (available on Windows and macOS only at the moment). +Press the "*...*" button to open the editor. +Code changes are applied immediately. + +The most simple formula is `y = x`, which means there will be no transformation at all. `y = x / 2` means that incoming control values will be halved. +You get the idea: `y` represents the desired target control value (= output value) and `x` the incoming source control value (= input value). +Both are 64-bit floating point numbers between 0.0 (0%) and 1.0 (100%). + +The script can be much more complicated than the mentioned examples and make use of all built-in EEL2 language features. +The important thing is to assign the desired value to `y` at some point. + +The following variables/functions are available in the formula: + +`y`:: Initially contains the _current_ target value. +You can use that value in order to calculate the new value. +With this, you can essentially craft your own relative mode! + +`y_type`:: This contains the type of the produced output value. +By default, it's 0. The following types are currently supported: + +`0`::: Absolute continuous value (a value between 0.0 and 1.0) +`1`::: Relative discrete value (an integer, e.g. -5 for 5 decrements) + +`realearn_timestamp`:: This contains the precise timestamp of the incoming control event in seconds. +This can be used to calculate the delta time between two consecutive control events, which in turn can be used to simulate encoder acceleration, for example. ++ +The timestamp has audio block precision. +For MIDI events, it even takes the intra-block offset into account - which is as precise as it can get, even if you use a high audio buffer size in the REAPER audio device settings. + +`y_last`:: This contains the last value of the target before it was affected by this particular mapping. ++ +Allows you to come up with a performance control mode typical for synth parameter mappings, just like the built-in <> mode but more customizable. +Try this for example: `y = y_last + x * (1 - y_last)` + +`rel_time`:: This contains the number of milliseconds since this mapping has last been triggered with a control message coming from the source. ++ +As soon as you use this and a control message comes in, ReaLearn will start invoking your formula _repeatedly_! +That means, this variable is your entrance ticket to smooth transitions and continuous parameter modulation. ++ +A few examples: ++ +* Smooth transition from current value to control value: `rel_time; y = abs(x - y) < 0.05 ? stop : y + 0.1 * (x - y)` +* Sinus LFO: `y = (sin(rel_time / 500) + 1) / 2` +* Linear transition to control value (1 second): `y = abs(x - y) < 0.05 ? stop : x * min(rel_time / 500, 1)` +* 2 seconds chaos: `y = rel_time < 2000 ? rand(1) : stop` +* Setting a value with delay: `y = rel_time < 2000 ? none : stop(0.5)` + +`stop` and `stop(...)`:: +In combination with `rel_time`, this stops repeated invocation of the formula until the mapping is triggered again. ++ +Good for building transitions with a defined end. ++ +Stopping the invocation at some point is also important if the same parameter should be controlled by other mappings as well. +Otherwise, if multiple mappings continuously change the target parameter, only the last one wins. ++ +This also exists as a function, which lets you do both, returning a target value *and* stopping the transition. +Pass the desired value in the parentheses, e.g. `stop(0.5)`. + +`none`:: +Usually, each repeated (see `rel_time`) invocation always results in a target invocation (unless the target is not retriggerable and already has the desired value). +Sometimes this is not desired. +In this case, one can return `none`, in which case the target will not be touched. ++ +Good for transitions that are not continuous, especially if other mappings want to control the parameter as well from time to time. + +`realearn_last_feedback_value`:: +Contains the last numeric feedback value sent through this mapping. +Before that, it's zero. +This can come in handy for mappings with xref:further-concepts/target.adoc#virtual-target[], for which the variable `y` is always zero because virtual targets don't know the concept of a current value. ++ +WARNING: This variable is experimental, better don't rely on it! + +`realearn_dbg()`:: +This is a function which takes a floating-point value as parameter. +It prints that floating-point value to `stdout` (*not* the ReaScript debug console!). +Useful for debugging. + +Learn more about the order in which ReaLearn processes the elements in the glue section by looking at the xref:glue-signal-flow.adoc[] diagrams. + +[#step-size-min-max] +== Step size Min/Max controls + +{control}:: +include::partial$glue/step-size-min-max/control.txt[] + +When you deal with relative adjustments of target values in terms of increments/decrements, then you have great flexibility because you can influence the _amount_ of those increments/decrements. +This is done via the _Step size_ setting, which is available for all +_continuous_ targets. + +Step size Min:: Specifies how much to increase/decrease the target value when an increment/decrement is received. ++ +include::partial$glue/usage-dependent-effect.adoc[] + +|{incremental-button} +|{control} +|Sets the target value change amount when button pressed + +|{incremental-velocity-sensitive-button} +|{control} +|Sets the target value change amount when button pressed with the lowest velocity + +|{encoder} +|{control} +|Sets the target value change amount for an incoming non-accelerated increment/decrement + +|{encoder} with <> +|{control} +|Sets the amount added/subtracted to calculate the simulated absolute value from an incoming non-accelerated increment/decrement + +|=== + +Step size Max:: Sets the maximum amount by which to increase/decrease the target value with one interaction. If you set this to the same value as _Min_, encoder acceleration or changes in velocity will have no effect on the incrementation/decrementation amount. +If you set it to 100%, the effect is maximized. ++ +include::partial$glue/usage-dependent-effect.adoc[] + +|{incremental-velocity-sensitive-button} +|{control} +|Sets the target value change amount when button pressed with the highest velocity + +|{encoder} +|{control} +|If the hardware encoder supports acceleration, this sets the target value change amount for the most accelerated increment/decrement + +|=== + +[#speed-min-max] +== Speed Min/Max controls + +{control}:: +include::partial$glue/speed-min-max/control.txt[] + +When you choose a target with a xref:further-concepts/target.adoc#discrete-value-range[], the <> label will change into +_Speed_. +If a target is discrete, it cannot have arbitrarily small step sizes. +It rather has one predefined atomic step size. +Allowing arbitrary step size adjustment wouldn't make sense. +That's why _Speed_ allows you to _multiply_ (positive numbers) or _"divide"_ (negative numbers) value increments with a factor instead. +Negative numbers are most useful for rotary encoders because they will essentially lower their sensitivity. +Virtual targets are always discrete. + +.Speed example +==== +Let's assume you selected the discrete xref:targets/fx/browse-presets.adoc[], which is considered discrete because an FX with for example 5 presets has 6 well-defined possible values (including the option), there's nothing inbetween. +And let's also assume that you have a controller like Midi Fighter Twister whose rotary encoders don't support built-in acceleration. + +Now you slightly move an encoder clock-wise and your controller sends an increment +1. If the _Speed Min_ slider was at 1 (default), this will just navigate to the next preset (+1). +If the _Speed Min_ slider was at 2, this will jump to the 2nd-next preset (+2). +And so on. +==== + +Remarks: + +* There are FX plug-ins out there which report their parameter as discrete with an insanely small step size (e.g. some Native Instrument plug-ins). +This kind of defeats the purpose of discrete parameters and one can argue that those parameters should actually be continuous. +In such a case, moving your rotary encoder might need _a lot_ of turning even if you set _Speed_ to the apparent maximum of 100! In this case you will be happy to know that the text field next to the slider allows you to enter values higher than 100. +* You can set the "Speed" slider to a negative value, e.g. -2. This is the opposite. +It means you need to make your encoder send 2 increments in order to move to the next preset. +Or -5: You need to make your encoder send 5 increments to move to the next preset. +This is like slowing down the encoder movement. + +[#encoder-filter] +== Encoder filter menu + +{control}:: +include::partial$glue/encoder-filter/control.txt[] + +For example, if you want to invoke one action on clockwise movement and another one on counter-clockwise movement. +Or if you want to use different step sizes for different movements. + +Increment & decrement:: ReaLearn will process both increments and decrements. +Increment only:: ReaLearn will ignore decrements. +Decrement only:: ReaLearn will ignore increments. + +[#wrap] +== Wrap checkbox + +{control}:: +include::partial$glue/wrap/control.txt[] + +If unchecked, the target value will not change anymore if there's an incoming decrement but the target already reached its minimum value. +If checked, the target value will jump to its maximum value instead. +It works analogously if there's an incoming increment and the target already reached its maximum value. + +If this flag is enabled for controller mappings which have a virtual target, every main mapping controlled by that virtual control element will wrap - even if the main mapping itself doesn't have <> enabled. + +[[make-absolute]] +== Make absolute + +{control}:: +include::partial$glue/make-absolute/control.txt[] + +This is useful if you have configured your controller to be relative all the way (which is good!) but you want to use a control transformation EEL formula - which is not possible if you change the target with relative increments. +It works by keeping an internal absolute value, incrementing or decrementing it accordingly and then processing it just like normal absolute control values. + +By checking this box: + +* You lose the possibility to be perfectly free of parameter jumps (but you can try to mitigate that loss by using the jump settings). +* You gain support for control-direction EEL transformation, non-continuous target value sequences and source range. +* You can still use some of the relative-only features: Step size and rotate! + +[#fire-mode] +== Fire mode menu + +{control}:: +include::partial$glue/fire-mode/control.txt[] + +Normally, when a button gets pressed, it controls the target immediately. +However, by using this dropdown and by changing the values below it, you can change this behavior. +This dropdown provides different fire modes that decide how exactly ReaLearn should cope with button presses. + +Fire on press (or release if > 0 ms):: +This mode is essential in order to be able to distinguish between different press durations. ++ +* *Min* and *Max* decide how long a button needs to be pressed to have an effect. +* By default, both min and max will be at 0 ms, which means that the duration doesn't matter and both press (> 0%) and release (0%) will be instantly forwarded. +If you change _Min_ to e.g. 1000 ms and _Max_ to 5000 ms, it will behave as follows: +* If you press the control element and instantly release it, nothing will happen. +* If you press the control element, wait for a maximum of 5 seconds and then release it, the control value of the press (> 0%) will be forwarded. +* It will never forward the control value of a release (0%), so this is probably only useful for targets with trigger character. +* The main use case of this setting is to assign multiple functions to one control element, depending on how long it has been pressed. +For this, use settings like the following: +* Short press: 0 ms - 250 ms +* Long press: 250 ms - 5000 ms + +Fire after timeout:: +This mode is more "satisfying" because it will let ReaLearn "fire" immediately once a certain time has passed since the press of the button. +However, obviously it doesn't have the concept of a "Maximum" press duration, so it can't be used to execute different things depending on different press durations (or only as the last part in the press duration chain, so to say). + +Timeout::: Sets the timeout in milliseconds. +If this is zero, everything will behave as usual. + +[[fire-after-timeout-keep-firing]]Fire after timeout, keep firing (turbo):: +Welcome to turbo mode. +It will keep hitting your target (always with the initial button press velocity) at a specific rate. +Optionally with an initial delay. +Epic! + +Timeout::: This is the initial delay before anything happens. +Can be zero, then turbo stage is entered instantly on press. + +Rate::: This is how frequently the target will be hit once the timeout has passed. +In practice, it won't happen more frequently than once every 30 ms (REAPER's main thread loop frequency). + +Fire on double press:: +This reacts to double presses of a button (analog to double-clicks with the mouse). + +Fire after single press (if hold < Max ms):: + +If you want to do something in response to a double press, chances are that you want to do something _else_ in response to just a single press. +The _Normal_ fire mode will fire no matter what! +That's why there's an additional _Single press_ mode that will not respond to double presses. +The response happens _slightly_ delayed - because ReaLearn needs to wait a bit to see if it's going to be a double press or not. + +Max::: With this, it's even possible to distinguish between single, double _and_ long press. +In order to do that, you must set the _Max_ value of the _Single press_ mapping to a value that is lower than the _Timeout_ value of your _After timeout_ mapping. +That way you can use one button for 3 different actions! ++ +==== +* Mapping 1 "Single press" with Max = 499ms +* Mapping 2 "Double press" +* Mapping 3 "After timeout" with Timeout = 500ms +==== + +[#button-filter] +== Button filter menu + +{control}:: +include::partial$glue/button-filter/control.txt[] + +Press & release:: +ReaLearn will process both button presses (control value = 0%) and button releases (control value > 0%). +This is the default. + +[[press-only,press-only]] Press only:: +Makes ReaLearn ignore the release of the button. +The same thing can be achieved by setting +_Source Min_ to 1. However, doing so would also affect the feedback direction, which is often undesirable because it will mess with the button LED color or on/off state. + +Release only:: Makes ReaLearn ignore the press of the button (just processing its release). +Rare, but possible. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/source-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/source-section.adoc new file mode 100644 index 0000000..0c941a8 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/source-section.adoc @@ -0,0 +1,21 @@ += Source section + +image:realearn/screenshots/mapping-panel-source.png[Screenshot] + +All xref:sources.adoc[] share the following UI elements. + +[#learn] +== Learn button + +Starts or stops learning the xref:key-concepts.adoc#source[] of this mapping. + +[#category] +== Category menu + +Lets you choose the source category. + +[#type] +== Type menu + +Lets you choose the source type within that category. +See xref:sources.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/target-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/target-section.adoc new file mode 100644 index 0000000..3bab4e9 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/target-section.adoc @@ -0,0 +1,140 @@ += Target section + +image:realearn/screenshots/mapping-panel-target.png[Screenshot] + +All xref:targets.adoc[] share the following UI elements. + +[#learn] +== Learn button + +Starts or stops learning the target of this mapping. + +[#menu] +== Menu + +Opens a small menu related to the target section: + +Pick recently touched target (by type):: +Gives you a list of recently touched parameters or executed actions in REAPER. +When you click one of it, the target will be populated accordingly. +It's an alternative to btn:[Learn]. ++ +Please note that not all targets can be picked that way, some have to be configured manually. + +Go there (if supported):: If applicable, this makes the target of this mapping visible in REAPER. +E.g. if the target is a track FX parameter, the corresponding track FX window will be displayed. + +[#category] +== Category menu + +Lets you choose the target category. + +[#type] +== Type menu + +Lets you choose a target type within that category. +See xref:targets.adoc[]. + +[#current-value] +== Value section and field + +Reflects the current value of this mapping target and lets you change it (either via slider and text field or via buttons, depending on the target character). + +If the target can't be resolved at the moment, it will show "Target currently inactive!" instead. + +[[display-unit]] +== Display unit button + +On the right side of the current value you will see a button with a label such as `1. dB (%)`. +This button displays the currently selected target unit (unrelated to the xref:key-concepts.adoc#unit[] concept) which is used for displaying and entering target values. + +The number in the parentheses denotes the unit which is used for displaying and entering target step sizes. + +Clicking the button switches between available target units. +Currently, there are two options: + +(1) Use native target units:: +Uses the target-specific unit, e.g. dB for volume targets. +If the target doesn't have any specific units, it will be displayed as `1. - (-)`. + +(2) Use percentages:: +Uses percentages for everything, which can be nice to get a uniform way of displaying/entering values instead of having to deal with the sometimes clunky target-specific units. + +== Common elements for track targets + +When choosing a track, the following additional elements are available. + +[[track-must-be-selected]] +=== Track must be selected checkbox + +If checked, this mapping will be active only if the track set in _Track_ is currently selected. +See xref:further-concepts/target.adoc#target-activation-condition[]. + +=== Selection ganging checkbox + +If checked and if the track in question is selected, all other selected tracks will be adjusted as well. +This uses REAPER's built-in selection-ganging feature and therefore should behave exactly like it. + +=== Respect grouping checkbox + +If checked, track grouping will be taken into account when adjusting the value. +This uses REAPER's built-in track grouping feature and therefore should behave exactly like it. + +NOTE: In older REAPER versions (< 6.69+dev1102), this can only be enabled together with selection ganging when using it on volume, pan or width targets. + +== Common elements for on/off targets + +Targets which control an on/off-style property of tracks (e.g. xref:targets/track/solo-unsolo.adoc[]) additionally provide the following elements. + +[[exclusive]] +=== Exclusive menu + +By default, this is set to <>. + +[[exclusive-no]] No:: Makes the track target affect just this track. +Within project:: Switches the property on (off) for this track and off (on) for all other tracks in the project. +Within folder:: Switches the property on (off) for this track and off (on) for all other tracks in the same folder and same level. +Within project (on only):: Variation of _Within project_ that applies exclusivity only when switching the property on for this track. +In other words, it never switches the property on for other tracks. +Within folder (on only):: Variation of _Within folder_ that applies exclusivity only when switching the property on for this track. +In other words, it never switches the property on for other tracks. + +== Common elements for FX targets + +The following elements and selectors are available for targets associated with a particular FX instance. + +=== FX section + +The FX instance associated with this target. +ReaLearn will search for the FX in the output or input FX chain of the above selected track. + +=== Input FX checkbox + +If unchecked, the _FX_ dropdown will show FX instances in the track's normal FX chain. +If checked, it will show FX instances in the track's input FX chain. + +=== Monitoring FX checkbox + +This appears instead of the input FX checkbox if you select track ``. +If you check this, you can target FX instances on REAPER's global monitoring FX chain. + +WARNING: Because of a limitation in the REAPER API, learning and feedback for monitoring FX doesn't work! + +[[fx-must-have-focus]] +=== FX must have focus checkbox + +If checked, this mapping will be active only if the selected FX instance is currently _focused_. + +If the FX instance is displayed in a floating window, _focused_ means that the floating window is active. +If it's displayed within the FX chain window, _focused_ means that the FX chain window is currently open and the FX instance is the currently selected FX in that FX chain. + +Of course, this flag doesn't have any effect if you chose the xref:further-concepts/target.adoc#fx-focused-selector[]. + +== Common elements for pollable targets + +The following elements are available only for the few targets that might need polling (= regular value querying) in order to support automatic feedback in all cases. + +=== Poll for feedback checkbox + +Enables or disables xref:further-concepts/target.adoc#target-value-polling[]. +In the probably rare case that the polling causes performance issues, you can untick this checkbox. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/top-section.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/top-section.adoc new file mode 100644 index 0000000..c27fc28 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/mapping-panel/top-section.adoc @@ -0,0 +1,87 @@ += Top section + +image:realearn/screenshots/mapping-panel-general.png[Screenshot] + +This section provides the following mapping-related elements. + +[#name] +== Name field + +Here you can enter a descriptive name for the mapping. +This is especially useful in combination with the search function if there are many mappings to keep track of. + +If you clear the name, ReaLearn will name the mapping automatically based on its target. + +[#tags] +== Tags field + +Use this to assign arbitrary xref:further-concepts/mapping.adoc#mapping-tag[mapping tags] to this mapping (comma-separated). + +[#control-enabled] +== Control-enabled checkbox (→) + +Use this to enable/disable control for this mapping. + +[#feedback-enabled] +== Feedback-enabled checkbox (←) + +Use this to enable/disable feedback for this mapping. + +Disabling both control and feedback has the same effect as disabling the mapping as a whole. + +[#active] +== Active menu + +This dropdown can be used to enable xref:further-concepts/mapping.adoc#conditional-activation[] for this mapping. + +[#feedback-mode] +== Feedback menu + +Normal:: +Makes ReaLearn send feedback whenever the target value changes. +This is the recommended option in most cases. + +Prevent echo feedback:: +This option mainly exists for motorized faders that don't like getting feedback while being moved. +If checked, ReaLearn won't send feedback if the target value change was caused by incoming source events of this mapping. +However, it will still send feedback if the target value change was caused by something else, e.g. a mouse action within REAPER itself. ++ +Since ReaLearn 2.16.12, this also works in controller mappings with virtual targets. + +Send feedback after control:: +This checkbox mainly exists for "fixing" controllers which allow their LEDs to be controlled via incoming MIDI/OSC _but at the same time_ insist on controlling these LEDs themselves. +For example, some Behringer X-Touch Compact buttons exhibit this behavior in MIDI mode. +Such a behavior can lead to wrong LED states which don't reflect the actual state in REAPER. ++ +If this option is not selected (the normal case and recommended for most controllers), ReaLearn will send feedback to the controller _only_ if the target value has changed. +For example, if you use a button to toggle a target value on and off, the target value will change only when pressing the button, not when releasing it. +As a consequence, feedback will be sent only when pressing the button, not when releasing it. ++ +If this option is selected, ReaLearn will send feedback even after releasing the button - although the target value has not been changed by it. ++ +Another case where this option comes in handy is if you use a target which doesn't support proper feedback because REAPER doesn't notify ReaLearn about value changes (e.g. "Track FX all enable"), and you have "Poll for feedback" disabled. +By choosing this option, ReaLearn will send feedback whenever the target value change was caused by ReaLearn itself, which improves the situation at least a bit. + +[#show-in-projection] +== Show in projection checkbox + +When unticked, this mapping will not show up in xref:further-concepts/unit.adoc#projection[]. +Useful e.g. for feedback-only mappings or buttons with multiple assignments. + +[[advanced-settings]] +== Advanced settings button + +This button is for experts. +There are some advanced mapping-related settings in ReaLearn that are not adjustable via its graphical user interface but only by writing text-based configuration. +Pressing this button should open a small window in which you can write the configuration for this mapping. + +If the button label ends with a number, that number denotes the number of top-level configuration properties set for that mapping. +That way you can immediately see if a mapping has advanced settings or not. + +You can learn more about the available properties in the section +xref:user-interface/mapping-panel/advanced-settings-dialog.adoc[]. + +[#find-in-mapping-list] +== Find in mapping list button + +Scrolls the mapping rows panel so that the corresponding mapping row for this mapping gets visible. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/projection.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/projection.adoc new file mode 100644 index 0000000..145752b --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/projection.adoc @@ -0,0 +1,93 @@ += Projection + +This section is about the _ReaLearn Companion_ app, which is a separate software that powers ReaLearn's xref:further-concepts/unit.adoc#projection[] feature. + +At the moment, it comes as https://play.google.com/store/apps/details?id=org.helgoboss.realearn_companion[Android app] +and https://realearn.helgoboss.org/[web app]. +The iOS app has not been published yet. +The source code is available https://github.com/helgoboss/realearn-companion[here at GitHub]. + +NOTE: The new xref:helgobox::app.adoc[] will gradually replace the companion app. + +== Connecting to ReaLearn + +The start screen lets you connect to a specific ReaLearn instance by scanning the QR code that pops up when pressing ReaLearn's <>. +It's also possible to enter the connection data manually, in case your device doesn't have a camera or in case you are using the web app (in which QR code scanning often doesn't work so well). +If you are experiencing issues, follow the instructions given by the app and the setup guide which is displayed when pressing the <> button! + +Please note, if all you want is to open the web app on the computer that also runs REAPER/ReaLearn, you don't need to bother with QR codes or connection data at all. +Just follow the link that is displayed in the setup guide. + +ReaLearn allows many Companion apps to connect to it simultaneously, there's no artificial limit. + +== Viewing the controller projection + +As soon as you have connected, you should be able to see the controller projection, which consists of both the controller layout and the current mapping of its control elements. +If not, the app will give you a hint what's missing. +The control element labels will reflect the labels of your main mappings. + +You can tap the screen to make the app bar disappear or reappear. +There's a menu on the right side of the app bar which let's you change various aspects of the appearance. +Just give it a try! +Dark mode combined with high-contrast is especially nice on devices with OLED displays! +All of these settings will be saved on your device, not in ReaLearn's controller preset. + +Another thing you can do here is applying two-finger gestures in order to zoom/pinch. + +== Editing the controller layout + +Pressing the pencil button in the app bar let's you enter edit mode. +As soon as you do that, the control element labels will reflect the labels of your controller mappings and a palette will appear on the side of the screen. + +=== Editing basics + +You can drag the controls freely from the palette onto the scene and back. +Pressing a control element opens a panel which lets you change its appearance. +The two labels mentioned there are used in the following way: + +. If the control element is a composite control element (see below, e.g. push encoder), the first label represents the mapping of the first inner control element (e.g. the encoder) and the second label represents the mapping of the second inner control element (e.g. the button). +See the _Midi Fighter Twister_ <> for a real-world usage of this feature. +. If the control element is just a normal control element, the second label is usually empty. +Except this control element has more than one main mapping assigned: In that case the second label shows the second main mapping. + +Whenever you press the save button (floppy disk) in the app bar, the layout is saved - not on your specific device but as part of ReaLearn's controller preset! +So this same layout will automatically be available to all other connected Companion apps. + +You can leave the edit mode by pressing the pencil button again. +This gives you a preview of your current changes. + +*Attention:* If you leave the controller projection view (e.g. by pressing the upper left arrow) or if you change your controller preset from within ReaLearn, all non-saved controller layout changes will be lost! +So it's a good idea to save often. +Once saved, there's no undo though. +You can back up temporary states by copying the corresponding controller preset files (on the computer running ReaLearn) to a temporary backup location (see _Save as…_ button documentation in the <> section). + +=== Composite control elements + +If you want one visual control element to contain 2 logical control elements (e.g. a push encoder = encoder + button), just move one control element onto another one - and they will merge into a composite control element. +If you want to undo this merging, move the merged control element back on the palette - they will split up and you can drag them onto the scene again. + +=== Batch-editing control elements + +Sometimes it's a bit tedious to edit each control element separately. +As soon as you long-press one control element, the Companion app will enter multi-edit mode and you can start adding/removing other control elements to/from the selection by just tapping them. +When you move one element of the selection, all others will also be moved. +You can open the control element appearance panel by long-pressing an element. +All changes made in the panel will immediately be applied to all selected elements. + +You can leave multi-edit mode either by unselecting all elements or by (temporarily) leaving the edit mode. + +_Known issue:_ In the web app, multi-edit mode currently doesn't work, there's a graphical glitch. + +=== Dealing with the grid + +You can hide the grid using the app bar menu. +The grid will still have an effect though. + +One way to get more fine-grained positioning is by decreasing the grid size. +However, it doesn't go below a certain minimum and changing the grid size after already having positioned lots of elements might not be the best idea. +Usually, the better way is to just expand the scene. +Don't worry, your layout will always fit on the screen, no matter how large the scene actually is in terms of grid squares! + +You can enlarge the scene by slightly moving a control element out of the scene. +Do so in small steps and you will automatically have more space at your disposal. +The scene will always be as big as the imaginary rectangle from the top-left control element to the bottom-right control element! diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/settings-dialog.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/settings-dialog.adoc new file mode 100644 index 0000000..b66af59 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/pages/user-interface/settings-dialog.adoc @@ -0,0 +1,20 @@ += Settings dialog + +include::partial$available-in-app.adoc[] + +This dialog opens when pressing the xref:helgobox:ROOT:app/user-interface/title-bar.adoc#title-bar-settings[] button in the title bar and navigating to the ReaLearn section. + +image::generated/screenshots/main/settings.png[] + +[#controllers] +== Controllers + +Clicking one of the controllers opens the corresponding xref:user-interface/controller-dialog.adoc[]. + +include::partial$generated/elements/settings/controllers/add.adoc[] + +include::partial$generated/elements/settings/controllers/delete.adoc[] + +== Related videos + +video::tkyNCGOVumw[youtube] \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/available-in-app.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/available-in-app.adoc new file mode 100644 index 0000000..9ccdcf9 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/available-in-app.adoc @@ -0,0 +1 @@ +This part of ReaLearn is located in the xref:helgobox::app.adoc[]. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/glue/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/glue/general.txt new file mode 100644 index 0000000..5a2c36d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/glue/general.txt @@ -0,0 +1 @@ +sits between source and target and filters or transforms control and feedback streams \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/mapping/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/mapping/general.txt new file mode 100644 index 0000000..6be1636 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/mapping/general.txt @@ -0,0 +1 @@ +connects a control element on your controller with an action or parameter in REAPER \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/source/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/source/control.txt new file mode 100644 index 0000000..35a00fd --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/source/control.txt @@ -0,0 +1 @@ +identifies and describes the control element on your controller \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/source/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/source/feedback.txt new file mode 100644 index 0000000..5855aa7 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/source/feedback.txt @@ -0,0 +1 @@ +identifies and describes the feedback element on your controller \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/target/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/target/control.txt new file mode 100644 index 0000000..9e29869 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/target/control.txt @@ -0,0 +1 @@ +is something in REAPER that should be controlled \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/target/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/target/feedback.txt new file mode 100644 index 0000000..c5faa81 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/concept/target/feedback.txt @@ -0,0 +1 @@ +is something in REAPER that provides feedback \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/advanced.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/advanced.adoc new file mode 100644 index 0000000..0425120 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/advanced.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-controller-advanced,Advanced ⊖]]] +ifndef::pdf-theme[[[settings-controller-advanced,Advanced ⊖]]] +== Advanced ⊖ + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/basics.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/basics.adoc new file mode 100644 index 0000000..2e37121 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/basics.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-controller-basics,Basics]]] +ifndef::pdf-theme[[[settings-controller-basics,Basics]]] +== Basics + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/color.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/color.adoc new file mode 100644 index 0000000..ab3f9f2 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/color.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-controller-color,Color]]] +ifndef::pdf-theme[[[settings-controller-color,Color]]] +=== Color + +image::realearn::generated/screenshots/elements/settings/controller/color.png[Color] + +Pick a color for this controller. + +This will be used e.g. for the control unit frame in Playtime. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/connection.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/connection.adoc new file mode 100644 index 0000000..665b615 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/connection.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-controller-connection,Connection]]] +ifndef::pdf-theme[[[settings-controller-connection,Connection]]] +=== Connection + + + +How the controller is connected to this machine. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/default-controller-preset.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/default-controller-preset.adoc new file mode 100644 index 0000000..345f733 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/default-controller-preset.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-controller-default-controller-preset,Default controller preset]]] +ifndef::pdf-theme[[[settings-controller-default-controller-preset,Default controller preset]]] +=== Default controller preset + +image::realearn::generated/screenshots/elements/settings/controller/default-controller-preset.png[Default controller preset] + +In general, ReaLearn will figure out the correct controller preset based on the main preset's needs and the connected controller. + +**Only set this if you came up with your own controller preset and want to use that instead of the auto-detected one!** Even then, ReaLearn might choose a different controller preset if your one doesn't provide at least one of the virtual control schemes required by the main preset. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/enabled.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/enabled.adoc new file mode 100644 index 0000000..5bd8ec3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/enabled.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-controller-enabled,Enabled]]] +ifndef::pdf-theme[[[settings-controller-enabled,Enabled]]] +=== Enabled + +image::realearn::generated/screenshots/elements/settings/controller/enabled.png[Enabled] + +If disabled, the controller will not have any effect. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/main-preset.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/main-preset.adoc new file mode 100644 index 0000000..a91a8d6 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/main-preset.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-controller-main-preset,Main preset]]] +ifndef::pdf-theme[[[settings-controller-main-preset,Main preset]]] +=== Main preset + +image::realearn::generated/screenshots/elements/settings/controller/main-preset.png[Main preset] + +This preset decides how your controller will be used, i.e. what exactly it should control. + +If you don't choose any main preset or if the main preset is not compatible with your controller, the controller will not be used at all. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/name.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/name.adoc new file mode 100644 index 0000000..a15b2af --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/name.adoc @@ -0,0 +1,10 @@ +ifdef::pdf-theme[[[settings-controller-name,Name]]] +ifndef::pdf-theme[[[settings-controller-name,Name]]] +=== Name + +image::realearn::generated/screenshots/elements/settings/controller/name.png[Name] + +Descriptive name of the controller. + +If you use multiple controllers of the same kind, the name should make clear which particular controller instance we are talking about. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/usage.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/usage.adoc new file mode 100644 index 0000000..1f8c0db --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controller/usage.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-controller-usage,Usage]]] +ifndef::pdf-theme[[[settings-controller-usage,Usage]]] +== Usage + + + + + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controllers/add.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controllers/add.adoc new file mode 100644 index 0000000..d923c80 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controllers/add.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-controllers-add,Add controller]]] +ifndef::pdf-theme[[[settings-controllers-add,Add controller image:realearn::generated/screenshots/elements/settings/controllers/add.png[width=50, pdfwidth=8mm]]]] +=== Add controller + +image::realearn::generated/screenshots/elements/settings/controllers/add.png[Add controller, role="related thumb right", float=right] + +Adds a new controller. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controllers/delete.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controllers/delete.adoc new file mode 100644 index 0000000..2b0d61a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/generated/elements/settings/controllers/delete.adoc @@ -0,0 +1,8 @@ +ifdef::pdf-theme[[[settings-controllers-delete,Delete controller]]] +ifndef::pdf-theme[[[settings-controllers-delete,Delete controller image:realearn::generated/screenshots/elements/settings/controllers/delete.png[width=50, pdfwidth=8mm]]]] +=== Delete controller + +image::realearn::generated/screenshots/elements/settings/controllers/delete.png[Delete controller, role="related thumb right", float=right] + +Deletes the controller. This can't be undone. + diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/absolute-mode/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/absolute-mode/control.txt new file mode 100644 index 0000000..8198450 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/absolute-mode/control.txt @@ -0,0 +1 @@ +specifies how incoming absolute control values are interpreted and handled \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/button-filter/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/button-filter/control.txt new file mode 100644 index 0000000..36ed247 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/button-filter/control.txt @@ -0,0 +1 @@ +allows reacting to either button press or button release events \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/control-transformation/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/control-transformation/control.txt new file mode 100644 index 0000000..026273e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/control-transformation/control.txt @@ -0,0 +1 @@ +applies highly customizable transformations or filtering to incoming absolute control values \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/encoder-filter/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/encoder-filter/control.txt new file mode 100644 index 0000000..2fd3b1e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/encoder-filter/control.txt @@ -0,0 +1 @@ +allows reacting to either clockwise or counter-clockwise encoder turns \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/feedback-type/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/feedback-type/feedback.txt new file mode 100644 index 0000000..a03dcc0 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/feedback-type/feedback.txt @@ -0,0 +1 @@ +specifies whether to send simple numeric, simple text or script-generated feedback values to the source \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/fire-mode/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/fire-mode/control.txt new file mode 100644 index 0000000..3d0bfb8 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/fire-mode/control.txt @@ -0,0 +1 @@ +allows differentiating between long-press, short-press, and double-press actions \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/group-interaction/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/group-interaction/control.txt new file mode 100644 index 0000000..db1bc41 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/group-interaction/control.txt @@ -0,0 +1 @@ +allows controlling not only this mapping but also all other mappings in the same group \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/make-absolute/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/make-absolute/control.txt new file mode 100644 index 0000000..a88166d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/make-absolute/control.txt @@ -0,0 +1 @@ +emulates an absolute control element with a relative encoder or with -/+ buttons (= incremental buttons) \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/out-of-range-behavior/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/out-of-range-behavior/control.txt new file mode 100644 index 0000000..163347e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/out-of-range-behavior/control.txt @@ -0,0 +1 @@ +determines what to do if the absolute control value coming from the source is not within source min/max \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/out-of-range-behavior/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/out-of-range-behavior/feedback.txt new file mode 100644 index 0000000..c6a30c4 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/out-of-range-behavior/feedback.txt @@ -0,0 +1 @@ +determines what to do if the feedback value is not within target min/max \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/reverse/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/reverse/control.txt new file mode 100644 index 0000000..a17faf3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/reverse/control.txt @@ -0,0 +1 @@ +inverses incoming absolute control values and changes the direction of incoming relative control values \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/reverse/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/reverse/feedback.txt new file mode 100644 index 0000000..2f739ec --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/reverse/feedback.txt @@ -0,0 +1 @@ +inverses the feedback value \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/round-target-value/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/round-target-value/control.txt new file mode 100644 index 0000000..1b6265e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/round-target-value/control.txt @@ -0,0 +1 @@ +rounds target values to the nearest integer \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/source-min-max/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/source-min-max/control.txt new file mode 100644 index 0000000..c1812b6 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/source-min-max/control.txt @@ -0,0 +1 @@ +sets the observed range of absolute control values coming from the source \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/source-min-max/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/source-min-max/feedback.txt new file mode 100644 index 0000000..5bbdd18 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/source-min-max/feedback.txt @@ -0,0 +1 @@ +sets the minimum and maximum feedback value \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/speed-min-max/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/speed-min-max/control.txt new file mode 100644 index 0000000..b5b4804 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/speed-min-max/control.txt @@ -0,0 +1 @@ +specifies how often to increase or decrease the target value when an increment or decrement is received \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/step-size-min-max/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/step-size-min-max/control.txt new file mode 100644 index 0000000..1421d93 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/step-size-min-max/control.txt @@ -0,0 +1 @@ +specifies by which amount to increase the target value when an increment or decrement is received \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/takeover-mode/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/takeover-mode/control.txt new file mode 100644 index 0000000..aac84bb --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/takeover-mode/control.txt @@ -0,0 +1 @@ +defines how to deal with potential control value jumps \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/target-min-max/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/target-min-max/control.txt new file mode 100644 index 0000000..099ae50 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/target-min-max/control.txt @@ -0,0 +1 @@ +sets the controlled value range of the target, making sure that the target value ends up within that range \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/target-min-max/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/target-min-max/feedback.txt new file mode 100644 index 0000000..9d1544d --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/target-min-max/feedback.txt @@ -0,0 +1 @@ +sets the observed range of feedback values coming from the target \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/usage-dependent-effect.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/usage-dependent-effect.adoc new file mode 100644 index 0000000..b08988c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/usage-dependent-effect.adoc @@ -0,0 +1,4 @@ +Depending on the context, this can have different effects: + +|=== +| Element | Direction | Effect diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/value-sequence/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/value-sequence/control.txt new file mode 100644 index 0000000..ba77450 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/value-sequence/control.txt @@ -0,0 +1 @@ +sets discrete target values to step through \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/wrap/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/wrap/control.txt new file mode 100644 index 0000000..9cc5aac --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/glue/wrap/control.txt @@ -0,0 +1 @@ +makes the target value jump back to target min when target max is reached \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/beep-on-success/control.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/beep-on-success/control.txt new file mode 100644 index 0000000..33ab9dc --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/beep-on-success/control.txt @@ -0,0 +1 @@ +plays a sound whenever the target has been invoked successfully \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/enabled/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/enabled/general.txt new file mode 100644 index 0000000..77c01fe --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/enabled/general.txt @@ -0,0 +1 @@ +enables or disables the mapping as a whole \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/next/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/next/general.txt new file mode 100644 index 0000000..a3093b3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/next/general.txt @@ -0,0 +1 @@ +jumps to the next mapping in the list, respecting search and filter settings \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/previous/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/previous/general.txt new file mode 100644 index 0000000..6ed631c --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-bottom/previous/general.txt @@ -0,0 +1 @@ +jumps to the previous mapping in the list, respecting search and filter settings \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/active/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/active/general.txt new file mode 100644 index 0000000..abcbac6 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/active/general.txt @@ -0,0 +1 @@ +allows defining conditions under which this mapping will become active (conditional activation) \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/advanced-settings/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/advanced-settings/general.txt new file mode 100644 index 0000000..a060fd9 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/advanced-settings/general.txt @@ -0,0 +1 @@ +opens a dialog for accessing advanced mapping features via text-based configuration \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/control-enabled/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/control-enabled/general.txt new file mode 100644 index 0000000..db243c1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/control-enabled/general.txt @@ -0,0 +1 @@ +lets you disable this mapping's control direction \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/feedback-enabled/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/feedback-enabled/general.txt new file mode 100644 index 0000000..6353849 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/feedback-enabled/general.txt @@ -0,0 +1 @@ +lets you disable this mapping's feedback direction \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/feedback-mode/feedback.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/feedback-mode/feedback.txt new file mode 100644 index 0000000..1b6b711 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/feedback-mode/feedback.txt @@ -0,0 +1 @@ +lets you adjust when ReaLearn sends feedback to improve compatibility with certain controllers \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/find-in-mapping-list/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/find-in-mapping-list/general.txt new file mode 100644 index 0000000..f671c78 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/find-in-mapping-list/general.txt @@ -0,0 +1 @@ +scrolls the mapping list so that the corresponding row for this mapping becomes visible \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/name/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/name/general.txt new file mode 100644 index 0000000..eb735f2 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/name/general.txt @@ -0,0 +1 @@ +allows giving the mapping a descriptive name \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/show-in-projection/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/show-in-projection/general.txt new file mode 100644 index 0000000..ac52ea7 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/show-in-projection/general.txt @@ -0,0 +1 @@ +allows excluding this mapping from ReaLearn's projection view \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/tags/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/tags/general.txt new file mode 100644 index 0000000..5ecd117 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/mapping-top/tags/general.txt @@ -0,0 +1 @@ +allows assigning arbitrary comma-separated tags to the mapping \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/category/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/category/general.txt new file mode 100644 index 0000000..6000643 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/category/general.txt @@ -0,0 +1 @@ +lets you pick the source category \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/learn/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/learn/general.txt new file mode 100644 index 0000000..ec639f3 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/learn/general.txt @@ -0,0 +1 @@ +starts or stops learning of this mapping's source \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/type/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/type/general.txt new file mode 100644 index 0000000..20280ee --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/source/type/general.txt @@ -0,0 +1 @@ +lets you pick the type of source that you want to observe (or send feedback to) \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/category/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/category/general.txt new file mode 100644 index 0000000..629c832 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/category/general.txt @@ -0,0 +1 @@ +lets you pick the target category \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/current-value/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/current-value/general.txt new file mode 100644 index 0000000..f1d007b --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/current-value/general.txt @@ -0,0 +1 @@ +reflects the target's current value and allows changing it \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/display-unit/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/display-unit/general.txt new file mode 100644 index 0000000..c63fb82 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/display-unit/general.txt @@ -0,0 +1 @@ +selects the target display unit, used for displaying and entering target values \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/learn/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/learn/general.txt new file mode 100644 index 0000000..7267f2e --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/learn/general.txt @@ -0,0 +1 @@ +starts or stops learning of this mapping's target \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/menu/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/menu/general.txt new file mode 100644 index 0000000..c28259a --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/menu/general.txt @@ -0,0 +1 @@ +allows picking a recently touched target and opening the target object \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/type/general.txt b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/type/general.txt new file mode 100644 index 0000000..95e3b27 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/target/type/general.txt @@ -0,0 +1 @@ +lets you pick the type of target that you want to control (or get feedback from) \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/targets/feedback-frequency-menu.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/targets/feedback-frequency-menu.adoc new file mode 100644 index 0000000..6fd8a62 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/targets/feedback-frequency-menu.adoc @@ -0,0 +1,7 @@ +== Feedback menu + +Determines how frequently ReaLearn captures feedback and sends it to your feedback output. + +Beat:: Roughly every beat. + +Fast:: As fast as possible, thereby giving the satisfying feeling of continuity. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/targets/playtime/slot-menu.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/targets/playtime/slot-menu.adoc new file mode 100644 index 0000000..13b15f1 --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/targets/playtime/slot-menu.adoc @@ -0,0 +1,3 @@ +== Slot menu + +The xref:playtime::key-concepts.adoc#slot[] on which to carry out this action. See xref:targets/playtime.adoc#slot-selector[]. diff --git a/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/under-construction.adoc b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/under-construction.adoc new file mode 100644 index 0000000..00555af --- /dev/null +++ b/plugin-reaper-realearn/doc/realearn/modules/ROOT/partials/under-construction.adoc @@ -0,0 +1 @@ +NOTE: Under construction! \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/svg-gen/.gitignore b/plugin-reaper-realearn/doc/svg-gen/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/plugin-reaper-realearn/doc/svg-gen/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/svg-gen/components.dot b/plugin-reaper-realearn/doc/svg-gen/components.dot new file mode 100644 index 0000000..079560b --- /dev/null +++ b/plugin-reaper-realearn/doc/svg-gen/components.dot @@ -0,0 +1,94 @@ +digraph components { + node [shape = "box"]; + compound = true; + // newrank = true; + rankdir = "TB"; + + MidiInputDevice; + MidiOutputDevice; + FxChain; + Reaper; + + subgraph cluster_realearn { + App [class = "layer-infrastructure"]; + Server [class = "layer-infrastructure"]; + Backbone [class = "layer-processing"]; + RealearnControlSurfaceMiddleware [class = "layer-processing"]; + RealearnAudioHook [class = "layer-processing"]; + OscOutputDevice [class = "layer-processing"]; + + subgraph cluster_instance { + infrastructure_layer [shape = point style = invis]; + application_layer [shape = point style = invis]; + processing_layer [shape = point style = invis]; + RealearnPlugin [class = "layer-infrastructure"]; + RealearnUi [class = "layer-infrastructure"]; + Session [class = "layer-management"]; + InstanceState [class = "layer-processing"]; + RealTimeProcessor [width = 2, height = 3, class = "layer-processing"]; + MainProcessor [width = 2, height = 3, class = "layer-processing"]; + + { + rank = same; + infrastructure_layer; + RealearnUi; + } + { + rank = same; + application_layer; + Session; + } + { + rank = same; + processing_layer; + RealTimeProcessor; + MainProcessor; + InstanceState; + } + + label = "Instance"; + class = "level-2"; + infrastructure_layer -> application_layer -> processing_layer [style = invis]; + } + + label = "ReaLearn"; + class = "level-1"; + } + + + // General + Session -> MainProcessor [label = "Sync data", class = "com-async role-sync-data"] + MainProcessor -> RealTimeProcessor [label = "Sync data", class = "com-async role-sync-data"] + MainProcessor -> Session [label = "Events", class = "com-sync role-notify"] + Session -> RealearnUi [label = "Events", class = "com-sync role-notify"] + + // Feedback direction + Reaper -> RealearnControlSurfaceMiddleware [label = "REAPER change events", class = "com-sync dir-feedback type-general role-realtime-general"] + InstanceState -> RealearnControlSurfaceMiddleware [label = "Instance change events", class = "com-async dir-feedback type-general role-realtime-general"] + RealearnControlSurfaceMiddleware -> MainProcessor [label = "REAPER and instance change events", class = "com-sync dir-feedback type-general role-realtime-general"] + + // Control direction (OSC) + RealearnControlSurfaceMiddleware -> MainProcessor [label = "OSC control events", class = "com-sync dir-control type-osc role-realtime-osc"] + + // Feedback direction (OSC) + MainProcessor -> OscOutputDevice [label = "OSC feedback events", class = "com-async dir-feedback type-osc role-realtime-osc"] + + // Control direction (MIDI) + RealTimeProcessor -> MainProcessor [label = "Control values (from MIDI events)", class = "com-async dir-control type-midi-general role-realtime-midi-general"] + + // Control direction (MIDI, FX input) + FxChain -> RealearnPlugin [label = "MIDI control events (FX input)", class = "com-sync dir-control type-midi-fx role-realtime-midi-fx"] + RealearnPlugin -> RealTimeProcessor [label = "MIDI control events (FX input)", class = "com-sync dir-control type-midi-fx role-realtime-midi-fx"] + + // Feedback direction (MIDI, FX output) + MainProcessor -> RealTimeProcessor [label = "MIDI feedback events (FX output)", class = "com-async dir-feedback type-midi-fx role-realtime-midi-fx"] + RealTimeProcessor -> FxChain [label = "MIDI feedback events (FX output)", constraint = false, class = "com-sync dir-feedback type-midi-fx role-realtime-midi-fx"] + + // Control direction (MIDI, device input) + MidiInputDevice -> RealearnAudioHook [label = "MIDI control events (device input)", class = "com-sync dir-control type-midi-device role-realtime-midi-device"] + RealearnAudioHook -> RealTimeProcessor [label = "MIDI control events (device input)", class = "com-sync dir-control type-midi-device role-realtime-midi-device"] + + // Feedback direction (MIDI, device output) + MainProcessor -> RealearnAudioHook [label = "MIDI feedback events (device output)", class = "com-async dir-feedback type-midi-device role-realtime-midi-device"] + RealearnAudioHook -> MidiOutputDevice [label = "MIDI feedback events (device output)", class = "com-sync dir-feedback type-midi-device role-realtime-midi-device"] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/svg-gen/index.js b/plugin-reaper-realearn/doc/svg-gen/index.js new file mode 100644 index 0000000..df3f4fe --- /dev/null +++ b/plugin-reaper-realearn/doc/svg-gen/index.js @@ -0,0 +1,236 @@ +import {createSVGWindow} from "svgdom"; +import svgjs from "@svgdotjs/svg.js"; + +const {SVG, registerWindow} = svgjs; +import * as fs from 'fs'; +import * as path from 'path'; +import pako from "pako"; +import got from "got"; +import '@svgdotjs/svg.topath.js'; + +const script_dir = path.resolve('doc/svg-gen'); + +// We need to embed the CSS into the SVG, otherwise the browser won't load it, tried it. +// (see https://stackoverflow.com/questions/18434094/how-to-style-svg-with-external-css). +const stylesheet = fs.readFileSync(path.resolve(script_dir, 'styles.css')); + +generate() + +async function generate() { + fs.writeFileSync('doc/architecture/images/modules.svg', await generateModulesDiagram()) + fs.writeFileSync('doc/architecture/images/onion-layers.svg', generateOnionLayersDiagram()) + fs.writeFileSync('doc/architecture/images/components.svg', await generateComponentDiagram()) + fs.writeFileSync('doc/architecture/images/components-midi-fx.svg', await generateComponentDiagram([ + 'role-realtime-general', + 'role-realtime-midi-general', + 'role-realtime-midi-fx' + ])) + fs.writeFileSync('doc/architecture/images/components-midi-device.svg', await generateComponentDiagram([ + 'role-realtime-general', + 'role-realtime-midi-general', + 'role-realtime-midi-device' + ])) + fs.writeFileSync('doc/architecture/images/components-osc.svg', await generateComponentDiagram([ + 'role-realtime-general', + 'role-realtime-osc', + ])) + fs.writeFileSync('doc/architecture/images/components-management.svg', await generateComponentDiagram([ + 'role-sync-data', + 'role-notify', + ])) +} + +async function generateModulesDiagram() { + const draw = await loadDotToSvgCanvas('modules.dot'); + return draw.svg(); +} + +async function generateComponentDiagram(shownEdgeClasses = undefined) { + const draw = await loadDotToSvgCanvas('components.dot'); + if (shownEdgeClasses) { + draw.style('.edge', {visibility: 'hidden'}) + for (let edgeClass of shownEdgeClasses) { + draw.style(`.edge.${edgeClass}`, {visibility: 'visible'}) + } + } + return draw.svg(); +} + +function generateOnionLayersDiagram() { + const draw = initSvgCanvas(); + const width = 410; + const height = 410; + draw.size(width, height); + embedStylesheet(draw); + + // Default attributes + const defaultFontSize = 15; + const defaultArrowColor = 'black'; + const defaultArrowWidth = 2; + const baseFont = { + size: defaultFontSize, + } + const arrowPattern = createArrowPattern('rule-arrow'); + const defaultArrowHead = arrowHead(); + + // Layers + const infrastructureLayer = layer(4, 'layer-infrastructure', 'infrastructure', [ + 'GUI', + 'API', + 'Persistence', + 'Server', + ]); + layer(3, 'layer-management', 'management'); + layer(2, 'layer-processing', 'processing'); + const baseLayer = layer(1, 'layer-base', 'base'); + + // Arrows + drawArrow(infrastructureLayer.x(), infrastructureLayer.cy(), baseLayer.cx(), baseLayer.cy(), { + text: 'may use code in', + patternOrColor: arrowPattern, + width: 10, + drawHead: false, + cssClass: 'rule-arrow', + useClipping: true, + }); + + function arrowHead() { + return draw.marker(10, 7, (add) => { + add.polygon('0,0 10,3.5 0,7').addClass('arrow-head'); + }); + } + + function arcPath(radius, cx, cy, sweep) { + return [ + // Start at 9 + ['M', cx - radius, cy], + // Go to 3 + ['A', -radius, -radius, 0, 0, sweep, cx + radius, cy], + ]; + } + + function layer(index, cssClass, label, components = []) { + const g = draw.group().addClass(cssClass); + const spacing = 50; + const radius = index * spacing; + const circle = g + .circle(radius * 2) + .center(width / 2, height / 2) + .fill('none') + .stroke({color: 'black'}) + .addClass('layerpart-circle') + const pathRadius = radius - spacing / 2; + const radiusFix = defaultFontSize / 3; + const upperArc = arcPath( + pathRadius - radiusFix, + circle.cx(), + circle.cy(), + 1 + ); + g.textPath(label, upperArc) + .attr('text-anchor', 'middle') + .attr('startOffset', '50%') + .font(baseFont) + .addClass('layerpart-label') + const lowerArc = arcPath( + pathRadius + radiusFix, + circle.cx(), + circle.cy(), + 0 + ); + for (let i = 0; i < components.length; i++) { + const segmentLength = (1 / components.length) * 100; + const offset = i * segmentLength + segmentLength / 2; + g.textPath(components[i], lowerArc) + .attr('text-anchor', 'middle') + .attr('startOffset', `${offset}%`) + .font(baseFont) + .addClass('layerpart-secondary-label'); + } + return circle; + } + + function createArrowPattern(cssClass = undefined) { + return draw.pattern(30, 20, (add) => { + const g = add.group().addClass(cssClass); + g.line(0, 3.5, 10, 3.5) + .stroke({color: defaultArrowColor, width: 1}) + .addClass('arrow-pattern-path'); + g.polygon('10,0 20,3.5 10,7').addClass('arrow-pattern-head'); + }); + } + + function drawArrow(x1, y1, x2, y2, { + patternOrColor = defaultArrowColor, + width = defaultArrowWidth, + head = defaultArrowHead, + text, + drawHead = true, + cssClass = undefined, + useClipping = false, + }) { + // Group for clipping + const outer_group = draw.group().addClass(cssClass); + const inner_group = outer_group.group(); + // Arrow itself + const line = useClipping ? inner_group.line(x1 - 100, y1, x2 + 100, y2) : inner_group.line(x1, y1, x2, y2); + const path = line.toPath().addClass('arrow-path'); + path.stroke({color: patternOrColor, width}); + // Head + if (drawHead) { + path.marker('end', head.size(8, 8)) + } + // Text + outer_group.textPath() + .plot(path.array()) + .text(add => { + add.tspan(text).dy(-10) + }) + .addClass('arrowpart-label') + .font({...baseFont, anchor: 'middle', startOffset: '50%'}); + // Clip (useful for keeping CSS transform animation within bounds) + if (useClipping) { + const clip = inner_group.clip() + .add(inner_group.polygon().plot([[x1, y1 - width], [x2, y2 - width], [x2, y1 + width], [12, y2 + width]])); + inner_group.clipWith(clip); + } + return outer_group; + } + + return draw.svg(); +} + +async function loadDotToSvgCanvas(dotFileName) { + const dot = fs.readFileSync(path.resolve(script_dir, dotFileName)); + const svg = await convertDotToSvg(dot); + const qualifiedSvg = svg.replace(/<(!DOCTYPE[^>\[]+(\[[^\]]+)?)+[^>]+>/g, ''); + const window = createSVGWindow() + const document = window.document + registerWindow(window, document) + const originalDraw = SVG(document.documentElement); + originalDraw.svg(qualifiedSvg, true); + const draw = SVG(document.documentElement); + embedStylesheet(draw); + return draw; +} + +async function convertDotToSvg(dot) { + const data = Buffer.from(dot, 'utf8') + const compressed = pako.deflate(data, {level: 9}) + const body = Buffer.from(compressed) + .toString('base64') + .replace(/\+/g, '-').replace(/\//g, '_') + const response = await got(`https://kroki.io/graphviz/svg/${body}`); + return response.body +} + +function initSvgCanvas() { + const window = createSVGWindow() + const document = window.document + registerWindow(window, document) + return SVG(document.documentElement); +} + +function embedStylesheet(draw) { + draw.element('style').words(stylesheet) +} \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/svg-gen/modules.dot b/plugin-reaper-realearn/doc/svg-gen/modules.dot new file mode 100644 index 0000000..31faf33 --- /dev/null +++ b/plugin-reaper-realearn/doc/svg-gen/modules.dot @@ -0,0 +1,22 @@ +digraph modules { + node [shape = "box"]; + + reaper_rs [label = "reaper-rs"] + helgoboss_learn [label = "helgoboss-learn"] + helgoboss_midi [label = "helgoboss-midi"] + + subgraph cluster_realearn { + label = "ReaLearn"; + main; + swell_ui [label = "swell-ui"]; + api; + } + + main -> api; + main -> swell_ui; + main -> reaper_rs; + main -> helgoboss_learn; + main -> helgoboss_midi; + reaper_rs -> helgoboss_midi; + helgoboss_learn -> helgoboss_midi; +} \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/svg-gen/package-lock.json b/plugin-reaper-realearn/doc/svg-gen/package-lock.json new file mode 100644 index 0000000..9a3e1b7 --- /dev/null +++ b/plugin-reaper-realearn/doc/svg-gen/package-lock.json @@ -0,0 +1,1088 @@ +{ + "name": "svg-gen", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sindresorhus/is": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" + }, + "@svgdotjs/svg.js": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.1.1.tgz", + "integrity": "sha512-73FggAUBS+zuHhJOMZiAsuE5qpwA4pmWUbLuvof2g3YnWEc3QhXA3tjqZlJJukBobSA23a/avf1Vb1U1QbER1Q==" + }, + "@svgdotjs/svg.topath.js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@svgdotjs/svg.topath.js/-/svg.topath.js-2.0.3.tgz", + "integrity": "sha512-vZG+4DpzjhkjCT9LJqg+cMCekJIGSUTpOEfbQN/wueSaiP7N04JFXYW4X3lj1MlZDNU9IcE1GsWEuwdhLJJGuA==", + "requires": { + "@svgdotjs/svg.js": "^3.0.10" + } + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, + "@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", + "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==" + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "optional": true + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=" + }, + "ast-transform": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/ast-transform/-/ast-transform-0.0.0.tgz", + "integrity": "sha1-dJRAWIh9goPhidlUYAlHvJj+AGI=", + "requires": { + "escodegen": "~1.2.0", + "esprima": "~1.0.4", + "through": "~2.3.4" + }, + "dependencies": { + "escodegen": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz", + "integrity": "sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E=", + "requires": { + "esprima": "~1.0.4", + "estraverse": "~1.5.0", + "esutils": "~1.0.0", + "source-map": "~0.1.30" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=" + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=" + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "ast-types": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.7.8.tgz", + "integrity": "sha1-kC0uDWDQcb3NRtwRXhgJ7RHBOKk=" + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "brfs": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brfs/-/brfs-2.0.2.tgz", + "integrity": "sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==", + "requires": { + "quote-stream": "^1.0.1", + "resolve": "^1.1.5", + "static-module": "^3.0.2", + "through2": "^2.0.0" + } + }, + "brotli": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz", + "integrity": "sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=", + "requires": { + "base64-js": "^1.1.2" + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + } + } + }, + "browserify-optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-optional/-/browserify-optional-1.0.1.tgz", + "integrity": "sha1-HhNyLP3g2F8SFnbCpyztUzoBiGk=", + "requires": { + "ast-transform": "0.0.0", + "ast-types": "^0.7.0", + "browser-resolve": "^1.8.1" + } + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "cacheable-lookup": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz", + "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==" + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dash-ast": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-2.0.1.tgz", + "integrity": "sha512-5TXltWJGc+RdnabUGzhRae1TRq6m4gr+3K2wQX0is5/F2yS6MJXJvLyI3ErAnsAXuJoGqvfVD5icRgim07DrxQ==" + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "estree-is-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-is-function/-/estree-is-function-1.0.0.tgz", + "integrity": "sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "requires": { + "type": "^2.5.0" + }, + "dependencies": { + "type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + } + } + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fontkit": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.8.1.tgz", + "integrity": "sha512-BsNCjDoYRxmNWFdAuK1y9bQt+igIxGtTC9u/jSFjR9MKhmI00rP1fwSvERt+5ddE82544l0XH5mzXozQVUy2Tw==", + "requires": { + "babel-runtime": "^6.26.0", + "brfs": "^2.0.0", + "brotli": "^1.2.0", + "browserify-optional": "^1.0.1", + "clone": "^1.0.4", + "deep-equal": "^1.0.0", + "dfa": "^1.2.0", + "restructure": "^0.5.3", + "tiny-inflate": "^1.0.2", + "unicode-properties": "^1.2.2", + "unicode-trie": "^0.3.0" + } + }, + "form-data-encoder": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz", + "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "got": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-12.0.0.tgz", + "integrity": "sha512-gNNNghQ1yw0hyzie1FLK6gY90BQlXU9zSByyRygnbomHPruKQ6hAKKbpO1RfNZp8b+qNzNipGeRG3tUelKcVsA==", + "requires": { + "@sindresorhus/is": "^4.2.0", + "@szmarczak/http-timer": "^5.0.1", + "@types/cacheable-request": "^6.0.2", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^6.0.4", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "form-data-encoder": "1.7.1", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.9", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http2-wrapper": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.10.tgz", + "integrity": "sha512-QHgsdYkieKp+6JbXP25P+tepqiHYd+FVnDwXpxi/BlUcoIB0nsmTOymTNvETuTO+pDuwcSklPE72VR3DqV+Haw==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, + "image-size": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz", + "integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==", + "requires": { + "queue": "6.0.2" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", + "integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==" + }, + "magic-string": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", + "requires": { + "sourcemap-codec": "^1.4.1" + } + }, + "merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" + }, + "pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "requires": { + "inherits": "~2.0.3" + } + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "quote-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", + "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", + "requires": { + "buffer-equal": "0.0.1", + "minimist": "^1.1.3", + "through2": "^2.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "regexp.prototype.flags": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + }, + "dependencies": { + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, + "restructure": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-0.5.4.tgz", + "integrity": "sha1-9U591WNZD7NP1r9Vh2EJrsyyjeg=", + "requires": { + "browserify-optional": "^1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scope-analyzer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.1.2.tgz", + "integrity": "sha512-5cfCmsTYV/wPaRIItNxatw02ua/MThdIUNnUOCYp+3LSEJvnG804ANw2VLaavNILIfWXF1D1G2KNANkBBvInwQ==", + "requires": { + "array-from": "^2.1.1", + "dash-ast": "^2.0.1", + "es6-map": "^0.1.5", + "es6-set": "^0.1.5", + "es6-symbol": "^3.1.1", + "estree-is-function": "^1.0.0", + "get-assigned-identifiers": "^1.1.0" + } + }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "static-eval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz", + "integrity": "sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==", + "requires": { + "escodegen": "^1.11.1" + } + }, + "static-module": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/static-module/-/static-module-3.0.4.tgz", + "integrity": "sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw==", + "requires": { + "acorn-node": "^1.3.0", + "concat-stream": "~1.6.0", + "convert-source-map": "^1.5.1", + "duplexer2": "~0.1.4", + "escodegen": "^1.11.1", + "has": "^1.0.1", + "magic-string": "0.25.1", + "merge-source-map": "1.0.4", + "object-inspect": "^1.6.0", + "readable-stream": "~2.3.3", + "scope-analyzer": "^2.0.1", + "shallow-copy": "~0.0.1", + "static-eval": "^2.0.5", + "through2": "~2.0.3" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "svgdom": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/svgdom/-/svgdom-0.1.10.tgz", + "integrity": "sha512-MMx2owmn+V8xYc+29MWVEsKZi7ZGxvnV4U/QNyX+miSH4F+veOL7gFO86kKawIfx1K+CogSK9ZAtod7dsf8LSA==", + "requires": { + "fontkit": "^1.8.1", + "image-size": "^1.0.0", + "sax": "^1.2.4" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "unicode-properties": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.3.1.tgz", + "integrity": "sha512-nIV3Tf3LcUEZttY/2g4ZJtGXhWwSkuLL+rCu0DIAMbjyVPj+8j5gNVz4T/sVbnQybIsd5SFGkPKg/756OY6jlA==", + "requires": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + }, + "dependencies": { + "unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + }, + "dependencies": { + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + } + } + } + } + }, + "unicode-trie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", + "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=", + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + }, + "dependencies": { + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/plugin-reaper-realearn/doc/svg-gen/package.json b/plugin-reaper-realearn/doc/svg-gen/package.json new file mode 100644 index 0000000..07e3d01 --- /dev/null +++ b/plugin-reaper-realearn/doc/svg-gen/package.json @@ -0,0 +1,19 @@ +{ + "name": "svg-gen", + "type": "module", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@svgdotjs/svg.js": "^3.1.1", + "@svgdotjs/svg.topath.js": "^2.0.3", + "got": "^12.0.0", + "pako": "^2.0.4", + "svgdom": "^0.1.10" + } +} diff --git a/plugin-reaper-realearn/doc/svg-gen/styles.css b/plugin-reaper-realearn/doc/svg-gen/styles.css new file mode 100644 index 0000000..991d400 --- /dev/null +++ b/plugin-reaper-realearn/doc/svg-gen/styles.css @@ -0,0 +1,452 @@ +/* Animations */ + +@keyframes go-right { + from { + transform: translate(0px, 0px); + } + to { + transform: translate(30px, 0px); + } +} + +/* Global appearance */ + +text { + font-family: sans-serif; + fill: var(--md-grey-900); +} + +/* Graphviz diagrams */ + +.graph text { + font-size: 12px; +} + +/* ReaLearn component diagram */ + +.cluster.level-1 > polygon { + fill: var(--md-grey-50); +} + +.cluster.level-2 > polygon { + fill: var(--md-grey-100); +} + +.node polygon { + fill: var(--md-grey-200); +} + +.node.layer-infrastructure polygon { + fill: var(--layer-infrastructure-color); +} + +.node.layer-management polygon { + fill: var(--layer-management-color); +} + +.node.layer-processing polygon { + fill: var(--layer-processing-color); +} + +.node.layer-base polygon { + fill: var(--layer-base-color); +} + +.edge.com-async path { + stroke-dasharray: 5, 2; +} + +.edge.dir-control text, .edge.dir-control polygon { + fill: var(--dir-control-color); +} + +.edge.dir-feedback text, .edge.dir-feedback polygon { + fill: var(--dir-feedback-color); +} + +.edge.dir-control path, .edge.dir-control polygon { + stroke: var(--dir-control-color); +} + +.edge.dir-feedback path, .edge.dir-feedback polygon { + stroke: var(--dir-feedback-color); +} + +.edge.type-general, .edge.type-midi-general { + opacity: 0.5; +} + +.edge { + stroke-width: 2; +} + +.edge.type-general path, .edge.type-midi-general path { + stroke-width: 1; +} + +/* Onion diagrams */ + +.layerpart-circle { + fill-opacity: 1.0; + stroke-width: 2; + stroke: var(--md-grey-400); +} + +.layerpart-label, .layerpart-secondary-label { + letter-spacing: 1px; +} + +.layerpart-secondary-label tspan { + fill: var(--md-grey-500); +} + +.arrowpart-label { + filter: drop-shadow( 3px 3px 2px rgba(0, 0, 0, .2)); + font-weight: bold; + fill: var(--md-grey-700); +} + +.rule-arrow .arrow-pattern-path { + stroke: var(--md-green-900); +} + +.rule-arrow .arrow-path { + animation: go-right 2s linear infinite; + stroke-width: 10; +} + +.rule-arrow .arrow-head, .rule-arrow .arrow-pattern-head { + fill: var(--md-green-900); +} + +/* ReaLearn onion diagram */ + +.layer-infrastructure .layerpart-circle { + fill: var(--layer-infrastructure-color); +} + +.layer-management .layerpart-circle { + fill: var(--layer-management-color); +} + +.layer-processing .layerpart-circle { + fill: var(--layer-processing-color); +} + +.layer-base .layerpart-circle { + fill: var(--layer-base-color); +} + +/* Custom color palette */ + +:root { + /* Layer colors */ + --layer-infrastructure-color: var(--md-yellow-100); + --layer-management-color: var(--md-light-green-100); + --layer-processing-color: var(--md-light-blue-50); + --layer-base-color: var(--md-blue-grey-100); + + /* Direction colors */ + --dir-control-color: var(--md-amber-900); + --dir-feedback-color: var(--md-cyan-900); +} + + +/* Material Design color palette */ + +:root { + --md-red-50: #ffebee; + --md-red-100: #ffcdd2; + --md-red-200: #ef9a9a; + --md-red-300: #e57373; + --md-red-400: #ef5350; + --md-red-500: #f44336; + --md-red-600: #e53935; + --md-red-700: #d32f2f; + --md-red-800: #c62828; + --md-red-900: #b71c1c; + --md-red-a100: #ff8a80; + --md-red-a200: #ff5252; + --md-red-a400: #ff1744; + --md-red-a700: #d50000; + + --md-pink-50: #fce4ec; + --md-pink-100: #f8bbd0; + --md-pink-200: #f48fb1; + --md-pink-300: #f06292; + --md-pink-400: #ec407a; + --md-pink-500: #e91e63; + --md-pink-600: #d81b60; + --md-pink-700: #c2185b; + --md-pink-800: #ad1457; + --md-pink-900: #880e4f; + --md-pink-a100: #ff80ab; + --md-pink-a200: #ff4081; + --md-pink-a400: #f50057; + --md-pink-a700: #c51162; + + --md-purple-50: #f3e5f5; + --md-purple-100: #e1bee7; + --md-purple-200: #ce93d8; + --md-purple-300: #ba68c8; + --md-purple-400: #ab47bc; + --md-purple-500: #9c27b0; + --md-purple-600: #8e24aa; + --md-purple-700: #7b1fa2; + --md-purple-800: #6a1b9a; + --md-purple-900: #4a148c; + --md-purple-a100: #ea80fc; + --md-purple-a200: #e040fb; + --md-purple-a400: #d500f9; + --md-purple-a700: #aa00ff; + + --md-deep-purple-50: #ede7f6; + --md-deep-purple-100: #d1c4e9; + --md-deep-purple-200: #b39ddb; + --md-deep-purple-300: #9575cd; + --md-deep-purple-400: #7e57c2; + --md-deep-purple-500: #673ab7; + --md-deep-purple-600: #5e35b1; + --md-deep-purple-700: #512da8; + --md-deep-purple-800: #4527a0; + --md-deep-purple-900: #311b92; + --md-deep-purple-a100: #b388ff; + --md-deep-purple-a200: #7c4dff; + --md-deep-purple-a400: #651fff; + --md-deep-purple-a700: #6200ea; + + --md-indigo-50: #e8eaf6; + --md-indigo-100: #c5cae9; + --md-indigo-200: #9fa8da; + --md-indigo-300: #7986cb; + --md-indigo-400: #5c6bc0; + --md-indigo-500: #3f51b5; + --md-indigo-600: #3949ab; + --md-indigo-700: #303f9f; + --md-indigo-800: #283593; + --md-indigo-900: #1a237e; + --md-indigo-a100: #8c9eff; + --md-indigo-a200: #536dfe; + --md-indigo-a400: #3d5afe; + --md-indigo-a700: #304ffe; + + --md-blue-50: #e3f2fd; + --md-blue-100: #bbdefb; + --md-blue-200: #90caf9; + --md-blue-300: #64b5f6; + --md-blue-400: #42a5f5; + --md-blue-500: #2196f3; + --md-blue-600: #1e88e5; + --md-blue-700: #1976d2; + --md-blue-800: #1565c0; + --md-blue-900: #0d47a1; + --md-blue-a100: #82b1ff; + --md-blue-a200: #448aff; + --md-blue-a400: #2979ff; + --md-blue-a700: #2962ff; + + --md-light-blue-50: #e1f5fe; + --md-light-blue-100: #466b86; + --md-light-blue-200: #81d4fa; + --md-light-blue-300: #4fc3f7; + --md-light-blue-400: #29b6f6; + --md-light-blue-500: #03a9f4; + --md-light-blue-600: #039be5; + --md-light-blue-700: #0288d1; + --md-light-blue-800: #0277bd; + --md-light-blue-900: #01579b; + --md-light-blue-a100: #80d8ff; + --md-light-blue-a200: #40c4ff; + --md-light-blue-a400: #00b0ff; + --md-light-blue-a700: #0091ea; + + --md-cyan-50: #e0f7fa; + --md-cyan-100: #b2ebf2; + --md-cyan-200: #80deea; + --md-cyan-300: #4dd0e1; + --md-cyan-400: #26c6da; + --md-cyan-500: #00bcd4; + --md-cyan-600: #00acc1; + --md-cyan-700: #0097a7; + --md-cyan-800: #00838f; + --md-cyan-900: #006064; + --md-cyan-a100: #84ffff; + --md-cyan-a200: #18ffff; + --md-cyan-a400: #00e5ff; + --md-cyan-a700: #00b8d4; + + --md-teal-50: #e0f2f1; + --md-teal-100: #b2dfdb; + --md-teal-200: #80cbc4; + --md-teal-300: #4db6ac; + --md-teal-400: #26a69a; + --md-teal-500: #009688; + --md-teal-600: #00897b; + --md-teal-700: #00796b; + --md-teal-800: #00695c; + --md-teal-900: #004d40; + --md-teal-a100: #a7ffeb; + --md-teal-a200: #64ffda; + --md-teal-a400: #1de9b6; + --md-teal-a700: #00bfa5; + + --md-green-50: #e8f5e9; + --md-green-100: #c8e6c9; + --md-green-200: #a5d6a7; + --md-green-300: #81c784; + --md-green-400: #66bb6a; + --md-green-500: #4caf50; + --md-green-600: #43a047; + --md-green-700: #388e3c; + --md-green-800: #2e7d32; + --md-green-900: #1b5e20; + --md-green-a100: #b9f6ca; + --md-green-a200: #69f0ae; + --md-green-a400: #00e676; + --md-green-a700: #00c853; + + --md-light-green-50: #f1f8e9; + --md-light-green-100: #dcedc8; + --md-light-green-200: #c5e1a5; + --md-light-green-300: #aed581; + --md-light-green-400: #9ccc65; + --md-light-green-500: #8bc34a; + --md-light-green-600: #7cb342; + --md-light-green-700: #689f38; + --md-light-green-800: #558b2f; + --md-light-green-900: #33691e; + --md-light-green-a100: #ccff90; + --md-light-green-a200: #b2ff59; + --md-light-green-a400: #76ff03; + --md-light-green-a700: #64dd17; + + --md-lime-50: #f9fbe7; + --md-lime-100: #f0f4c3; + --md-lime-200: #e6ee9c; + --md-lime-300: #dce775; + --md-lime-400: #d4e157; + --md-lime-500: #cddc39; + --md-lime-600: #c0ca33; + --md-lime-700: #afb42b; + --md-lime-800: #9e9d24; + --md-lime-900: #827717; + --md-lime-a100: #f4ff81; + --md-lime-a200: #eeff41; + --md-lime-a400: #c6ff00; + --md-lime-a700: #aeea00; + + --md-yellow-50: #fffde7; + --md-yellow-100: #fff9c4; + --md-yellow-200: #fff59d; + --md-yellow-300: #fff176; + --md-yellow-400: #ffee58; + --md-yellow-500: #ffeb3b; + --md-yellow-600: #fdd835; + --md-yellow-700: #fbc02d; + --md-yellow-800: #f9a825; + --md-yellow-900: #f57f17; + --md-yellow-a100: #ffff8d; + --md-yellow-a200: #ffff00; + --md-yellow-a400: #ffea00; + --md-yellow-a700: #ffd600; + + --md-amber-50: #fff8e1; + --md-amber-100: #ffecb3; + --md-amber-200: #ffe082; + --md-amber-300: #ffd54f; + --md-amber-400: #ffca28; + --md-amber-500: #ffc107; + --md-amber-600: #ffb300; + --md-amber-700: #ffa000; + --md-amber-800: #ff8f00; + --md-amber-900: #ff6f00; + --md-amber-a100: #ffe57f; + --md-amber-a200: #ffd740; + --md-amber-a400: #ffc400; + --md-amber-a700: #ffab00; + + --md-orange-50: #fff3e0; + --md-orange-100: #ffe0b2; + --md-orange-200: #ffcc80; + --md-orange-300: #ffb74d; + --md-orange-400: #ffa726; + --md-orange-500: #ff9800; + --md-orange-600: #fb8c00; + --md-orange-700: #f57c00; + --md-orange-800: #ef6c00; + --md-orange-900: #e65100; + --md-orange-a100: #ffd180; + --md-orange-a200: #ffab40; + --md-orange-a400: #ff9100; + --md-orange-a700: #ff6d00; + + --md-deep-orange-50: #fbe9e7; + --md-deep-orange-100: #ffccbc; + --md-deep-orange-200: #ffab91; + --md-deep-orange-300: #ff8a65; + --md-deep-orange-400: #ff7043; + --md-deep-orange-500: #ff5722; + --md-deep-orange-600: #f4511e; + --md-deep-orange-700: #e64a19; + --md-deep-orange-800: #d84315; + --md-deep-orange-900: #bf360c; + --md-deep-orange-a100: #ff9e80; + --md-deep-orange-a200: #ff6e40; + --md-deep-orange-a400: #ff3d00; + --md-deep-orange-a700: #dd2c00; + + --md-brown-50: #efebe9; + --md-brown-100: #d7ccc8; + --md-brown-200: #bcaaa4; + --md-brown-300: #a1887f; + --md-brown-400: #8d6e63; + --md-brown-500: #795548; + --md-brown-600: #6d4c41; + --md-brown-700: #5d4037; + --md-brown-800: #4e342e; + --md-brown-900: #3e2723; + + --md-grey-50: #fafafa; + --md-grey-100: #f5f5f5; + --md-grey-200: #eeeeee; + --md-grey-300: #e0e0e0; + --md-grey-400: #bdbdbd; + --md-grey-500: #9e9e9e; + --md-grey-600: #757575; + --md-grey-700: #616161; + --md-grey-800: #424242; + --md-grey-900: #212121; + + --md-blue-grey-50: #eceff1; + --md-blue-grey-100: #cfd8dc; + --md-blue-grey-200: #b0bec5; + --md-blue-grey-300: #90a4ae; + --md-blue-grey-400: #78909c; + --md-blue-grey-500: #607d8b; + --md-blue-grey-600: #546e7a; + --md-blue-grey-700: #455a64; + --md-blue-grey-800: #37474f; + --md-blue-grey-900: #263238; + + --md-black: #000000; + + --md-white: #ffffff; + + --md-dark-text-primary: rgba(0, 0, 0, 0.87); + --md-dark-text-secondary: rgba(0, 0, 0, 0.54); + --md-dark-text-disabled: rgba(0, 0, 0, 0.38); + --md-dark-text-dividers: rgba(0, 0, 0, 0.12); + + --md-light-text-primary: rgba(255, 255, 255, 1); + --md-light-text-secondary: rgba(255, 255, 255, 0.7); + --md-light-text-disabled: rgba(255, 255, 255, 0.5); + --md-light-text-dividers: rgba(255, 255, 255, 0.12); + + --md-dark-icons-active: rgba(0, 0, 0, 0.54); + --md-dark-icons-inactive: rgba(0, 0, 0, 0.38); + + --md-light-icons-active: rgba(255, 255, 255, 1); + --md-light-icons-inactive: rgba(255, 255, 255, 0.5); + + +} diff --git a/plugin-reaper-realearn/doc/user-guide.adoc b/plugin-reaper-realearn/doc/user-guide.adoc new file mode 100644 index 0000000..d52397f --- /dev/null +++ b/plugin-reaper-realearn/doc/user-guide.adoc @@ -0,0 +1 @@ +The ReaLearn Reference is now available at https://docs.helgoboss.org/realearn. \ No newline at end of file diff --git a/plugin-reaper-realearn/doc/user-guide.md b/plugin-reaper-realearn/doc/user-guide.md new file mode 100644 index 0000000..d52397f --- /dev/null +++ b/plugin-reaper-realearn/doc/user-guide.md @@ -0,0 +1 @@ +The ReaLearn Reference is now available at https://docs.helgoboss.org/realearn. \ No newline at end of file diff --git a/plugin-reaper-realearn/extension/Cargo.toml b/plugin-reaper-realearn/extension/Cargo.toml new file mode 100644 index 0000000..0b7382a --- /dev/null +++ b/plugin-reaper-realearn/extension/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "helgobox-extension" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +reaper-fluent.workspace = true +reaper-medium.workspace = true +reaper-low.workspace = true +reaper-macros.workspace = true +anyhow.workspace = true +libloading.workspace = true + +[lib] +name = "reaper_helgobox" +crate-type = ["cdylib"] + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/extension/src/lib.rs b/plugin-reaper-realearn/extension/src/lib.rs new file mode 100644 index 0000000..01811a6 --- /dev/null +++ b/plugin-reaper-realearn/extension/src/lib.rs @@ -0,0 +1,112 @@ +use anyhow::{bail, Context, Result}; +use libloading::{Library, Symbol}; +use reaper_fluent::Reaper; +use reaper_low::{PluginContext, TypeSpecificPluginContext}; +use reaper_macros::reaper_extension_plugin; +use reaper_medium::ReaperSession; +use std::error::Error; +use std::fs; +use std::sync::OnceLock; + +// Executing Drop not important because extensions always live until REAPER ends. +static EXTENSION: OnceLock = OnceLock::new(); + +#[reaper_extension_plugin] +fn plugin_main(context: PluginContext) -> std::result::Result<(), Box> { + let _ = EXTENSION.set(HelgoboxExtension::load(context)?); + Ok(()) +} + +type ReaperPluginEntry = unsafe extern "C" fn( + h_instance: ::reaper_low::raw::HINSTANCE, + rec: *mut ::reaper_low::raw::reaper_plugin_info_t, +) -> ::std::os::raw::c_int; + +#[cfg(target_os = "linux")] +type SwellDllMain = unsafe extern "C" fn( + hinstance: reaper_low::raw::HINSTANCE, + reason: u32, + get_func: Option< + unsafe extern "C" fn(name: *const std::os::raw::c_char) -> *mut std::os::raw::c_void, + >, +) -> ::std::os::raw::c_int; + +struct HelgoboxExtension { + /// Just for RAII. + _plugin_library: Option, +} + +impl HelgoboxExtension { + pub fn load(context: PluginContext) -> Result { + // Install reaper-fluent for global use + let _ = Reaper::install_globally(ReaperSession::load(context)); + // Return extension + let extension = Self { + _plugin_library: eagerly_load_plugin_lib(&context).ok(), + }; + Ok(extension) + } + + #[allow(dead_code)] + pub fn get() -> &'static HelgoboxExtension { + EXTENSION + .get() + .expect("Helgobox extension not yet initialized") + } +} + +/// Loads the Helgobox plug-in library eagerly (Justin's idea, awesome!) +fn eagerly_load_plugin_lib(context: &PluginContext) -> Result { + // Find correct shared library file + let resource_path = Reaper::get() + .medium_reaper() + .get_resource_path(|path| path.to_path_buf()); + let plugin_library_path = fs::read_dir(resource_path.join("UserPlugins/FX"))? + .flatten() + .find_map(|item| { + let file_type = item.file_type().ok()?; + if !file_type.is_file() && !file_type.is_symlink() { + return None; + } + let file_name = item.file_name().to_str()?.to_lowercase(); + let extension = if cfg!(target_os = "windows") { + ".dll" + } else if cfg!(target_os = "macos") { + ".vst.dylib" + } else { + ".so" + }; + let matches = file_name.starts_with("helgobox") && file_name.ends_with(extension); + if !matches { + return None; + } + Some(item.path()) + }) + .context("couldn't find plug-in library")?; + // Load shared library + let plugin_library = unsafe { Library::new(plugin_library_path)? }; + #[cfg(target_os = "linux")] + { + // Linux only: Run SWELL entry point of library (on Windows, SWELL is not necessary, and on + // macOS, SWELL is obtained differently) + let swell_dll_main: Symbol = unsafe { plugin_library.get(b"SWELL_dllMain")? }; + unsafe { + swell_dll_main( + context.h_instance(), + reaper_low::raw::DLL_PROCESS_ATTACH, + context.swell_function_provider(), + ); + } + } + // Run extension entry point of library + let reaper_plugin_entry: Symbol = + unsafe { plugin_library.get(b"ReaperPluginEntry")? }; + let TypeSpecificPluginContext::Extension(ctx) = context.type_specific() else { + bail!("unexpected plug-in context type for extension"); + }; + let mut original_info_struct = ctx.to_raw(); + unsafe { + reaper_plugin_entry(context.h_instance(), &mut original_info_struct as *mut _); + } + Ok(plugin_library) +} diff --git a/plugin-reaper-realearn/macros/Cargo.toml b/plugin-reaper-realearn/macros/Cargo.toml new file mode 100644 index 0000000..9eb2473 --- /dev/null +++ b/plugin-reaper-realearn/macros/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "helgobox-macros" +version = "0.1.0" +edition = "2021" +publish = false + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/macros/src/lib.rs b/plugin-reaper-realearn/macros/src/lib.rs new file mode 100644 index 0000000..20059c4 --- /dev/null +++ b/plugin-reaper-realearn/macros/src/lib.rs @@ -0,0 +1,2 @@ +#[macro_use] +mod reaper_api; diff --git a/plugin-reaper-realearn/macros/src/reaper_api.rs b/plugin-reaper-realearn/macros/src/reaper_api.rs new file mode 100644 index 0000000..c3af237 --- /dev/null +++ b/plugin-reaper-realearn/macros/src/reaper_api.rs @@ -0,0 +1,81 @@ +#[macro_export] +macro_rules! reaper_api { + ( + $trait_name:ident, $pointer_struct_name:ident, $session_struct_name:ident, $reg_func_name:ident + { + $( + $( #[doc = $doc:expr] )* + $func_name:ident ($( $param_name:ident: $param_type:ty ),*) $( -> $ret_type:ty )?; + )+ + } + ) => { + #[derive(Default)] + pub struct $pointer_struct_name { + $( + $func_name: Option $ret_type )?> + ),+ + } + + impl $pointer_struct_name { + pub fn load(plugin_context: &reaper_low::PluginContext) -> Option { + let mut pointers = Self::default(); + let mut load_count = 0; + unsafe { + $( + pointers.$func_name = std::mem::transmute(plugin_context.GetFunc( + concat!(stringify!($func_name), "\0").as_ptr() as *const std::ffi::c_char, + )); + if pointers.$func_name.is_some() { + load_count += 1; + } + )+ + } + if load_count == 0 { + return None; + } + Some(pointers) + } + } + + pub struct $session_struct_name { + pointers: $pointer_struct_name, + } + + impl $session_struct_name { + pub fn load(plugin_context: &reaper_low::PluginContext) -> Option { + $pointer_struct_name::load(plugin_context).map(Self::new) + } + + pub fn new(pointers: $pointer_struct_name) -> Self { + Self { + pointers + } + } + + $( + $( #[doc = $doc] )* + pub fn $func_name(&self, $( $param_name: $param_type ),*) $( -> $ret_type )? { + self.pointers.$func_name.unwrap()($( $param_name ),*) + } + )+ + } + + pub trait $trait_name { + $( + extern "C" fn $func_name($( $param_name: $param_type ),*) $( -> $ret_type )?; + )+ + } + + pub fn $reg_func_name(mut register_api_fn: impl FnMut(&std::ffi::CStr, *mut std::ffi::c_void) -> Result<(), E> ) -> Result<(), E> { + unsafe { + $( + register_api_fn( + std::ffi::CStr::from_ptr(concat!(stringify!($func_name), "\0").as_ptr() as *const std::ffi::c_char), + T::$func_name as *mut std::ffi::c_void, + )?; + )+ + } + Ok(()) + } + }; +} diff --git a/plugin-reaper-realearn/main/Cargo.toml b/plugin-reaper-realearn/main/Cargo.toml new file mode 100644 index 0000000..94fe159 --- /dev/null +++ b/plugin-reaper-realearn/main/Cargo.toml @@ -0,0 +1,258 @@ +[package] +name = "helgobox" +version = "2.18.3-pre.1" +authors = ["Benjamin Klum "] +edition = "2021" +build = "build.rs" +license = "GPL-3.0" +rust-version = "1.84.0" +publish = false + +[features] +default = [] + +# Builds ReaLearn together with the Playtime Clip Engine. This should only be enabled if you have access to the +# private playtime-clip-engine repository, otherwise the build will fail. +playtime = ["dep:playtime-clip-engine"] + +# Activates license checks for Playtime Clip Engine. If not enabled, license checks will always fail. +licensing = ["playtime", "playtime-clip-engine/licensing"] + +# Enables a bunch of features that need the egui user interface framework to work. +egui = ["dep:baseview", "dep:egui-baseview", "dep:egui", "dep:egui_extras", "pot-browser"] + +# Regenerate bindings (for dialog resource IDs and EEL functions) +generate = [] + +[dependencies] +# Own +base.workspace = true +reaper-rx.workspace = true +reaper-high = { workspace = true, features = ["sentry"] } +reaper-medium.workspace = true +reaper-common-types = { workspace = true, features = ["color-macros", "palette"] } +reaper-low.workspace = true +reaper-macros.workspace = true +swell-ui.workspace = true +rx-util.workspace = true +pot.workspace = true +helgobox-api.workspace = true +# For obtaining dialog constants from dialog crate +helgobox-dialogs.workspace = true +# In future (when helgoboss-learn has matured), this will become a crates.io dependency +helgoboss-learn.workspace = true +helgoboss-midi.workspace = true +helgoboss-license-api.workspace = true +playtime-api.workspace = true +playtime-clip-engine = { workspace = true, optional = true } + +# 3rd-party +# For easier error handling +anyhow.workspace = true +# For being able to (de)serialize using FromStr and Display +serde_with.workspace = true +# For panicking when accessing something from the wrong thread +fragile.workspace = true +# For acting as DAW plug-in +vst.workspace = true +rxrust.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_repr.workspace = true +derive_more.workspace = true +num_enum.workspace = true +crossbeam-channel.workspace = true +scopeguard.workspace = true +uuid = { workspace = true, features = ["v4", "serde"] } +# New logging (for Playtime matrix) +tracing.workspace = true +tracing-core.workspace = true +tracing-subscriber = { workspace = true, features = ["env-filter"] } +# For interacting with the OS clipboard +arboard.workspace = true +once_cell.workspace = true +# For being able to exclude fields from the derived Debug implementation +derivative.workspace = true +chrono.workspace = true +smallvec.workspace = true +regex.workspace = true +enum-map.workspace = true +# For generating controller file names from controller names +slug.workspace = true +# For generating random session IDs +nanoid.workspace = true +# For ReaLearn's servers +futures.workspace = true +tokio.workspace = true +tokio-stream.workspace = true +tower.workspace = true +# For ReaLearn's http server (used for projection) +axum = { workspace = true, features = ["ws"] } +# Use "tls-rustls-no-provider" instead of "tls-rustls" because we want to use "ring" for +# cryptography instead of "aws-lc-rs" (because the latter causes build issues on Windows) +axum-server = { workspace = true, features = ["tls-rustls-no-provider"] } +rustls = { workspace = true, features = ["ring"] } +tower-http = { workspace = true, features = ["cors"] } +# For ReaLearn's gRPC server +tonic.workspace = true +prost.workspace = true +# For generating projection QR code +qrcode.workspace = true +# For rendering projection QR code to PNG +image = { workspace = true, features = ["png"] } +# For generating self-signed certificate for projection web server +rcgen.workspace = true +# For showing different ways of connecting to this computer (projection feature) +dns-lookup.workspace = true +hostname.workspace = true +# For rendering projection setup HTML page template +askama.workspace = true +# For persisting app configuration +serde_ini.workspace = true +# For opening projection setup page and companion app in local browser +webbrowser.workspace = true +# For writing generated projection setup page to disk +tempfile.workspace = true +# For adding firewall rules +runas.workspace = true +# For building URls, e.g. companion web app URL +url.workspace = true +# For grouping mappings by virtual control elements and grouping outgoing OSC messages by device +itertools.workspace = true +# For being able to configure logging via environment variable RUST_LOG +env_logger.workspace = true +# To open user guide in local PDF app +open.workspace = true +# For comparing ReaLearn versions in order to determine if preset migration is necessary +semver.workspace = true +# Maybe better asynchronous channel than in futures or tokio (basically the async version of crossbeam) +async-channel.workspace = true +# For displaying snapshot sizes +bytesize.workspace = true +# For OSC +rosc.workspace = true +# For letting the user edit advanced mapping settings +edit.workspace = true +# For parsing/formatting advanced mapping settings +serde_yaml.workspace = true +# For parsing hexadecimal data notation to byte vector (for system-exclusive lifecycle MIDI messages) +hex.workspace = true +# For evaluation of formulas +fasteval = { workspace = true, default-features = false } +# For wildcard support when using "By name" selector +wildmatch.workspace = true +# For creating HashSet literals for projection feedback and in unit tests +maplit.workspace = true +# For easier support of named virtual control elements +ascii.workspace = true +# For using bit flags in the reaper-rs API. +enumflags2.workspace = true +# For detecting undesired (de)allocation in real-time threads. +helgobox-allocator.workspace = true +# For being able to cleanly implement targets in separate files without needing to resort to dynamic dispatch. +enum_dispatch.workspace = true +# For processing mappings in user-defined order +indexmap = { workspace = true } +# For comparing floating point numbers in tests +approx.workspace = true +# For the API (ReaLearn Script) and MIDI scripts +mlua.workspace = true +# For convenient converting prop key to prop enum variant +strum.workspace = true +metrics.workspace = true +metrics-exporter-prometheus = { workspace = true, default-features = false } +# For parsing OSC argument value ranges +nom.workspace = true +# For being able to return iterators of different types +either.workspace = true +# For reading preset directories recursively +walkdir.workspace = true +# For egui graphics +egui-baseview = { workspace = true, optional = true } +baseview = { workspace = true, optional = true } +egui = { workspace = true, optional = true } +egui_extras = { workspace = true, optional = true } +# Pot Browser +pot-browser = { workspace = true, optional = true } +# For decompressing App +zstd = "0.13.0" +tar = "0.4.40" +# For loading App +libloading.workspace = true +# For embedding factory presets into the binary +include_dir.workspace = true +# For prefixing preset IDs with the user name +whoami.workspace = true +# For passing MIDI device inquiry replies from audio thread to main thread +tinyvec.workspace = true +# For not having to deal with non-UTF8 paths if we are sure we don't have them +camino.workspace = true +# For not having to manually implement Lua module finder trait for smart pointers +auto_impl.workspace = true +# For some color in the otherwise boring GUI +palette.workspace = true +# For parsing/formatting enum-like target values from/to a stable and ID-like representation +serde_plain.workspace = true +# For sharing variables between main thread and real-time threads +atomic.workspace = true +# For making sure that sharing global audio state uses atomics +static_assertions.workspace = true +# For Stream Deck support +streamdeck.workspace = true +# For Stream Deck support +ab_glyph.workspace = true +# For Stream Deck support +hidapi.workspace = true +# For caching button images for Stream Deck +cached.workspace = true +# For drawing knobs for Stream Deck button +imageproc.workspace = true +# For relativizing paths for Stream Deck button images +pathdiff.workspace = true +# For fetching Helgobox remote config. +# Important to not use default features because we want to avoid the libssl dependency on Linux. +reqwest = { workspace = true, default-features = false, features = ["rustls-tls-no-provider"] } +# For MIDI clock processing +simple_moving_average.workspace = true + +[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies] +# For speech source +tts.workspace = true + +[target.'cfg(windows)'.dependencies] +# For detecting the Windows version (to determine whether special characters can be displayed) +sys-info.workspace = true +# For converting virtual umlaut/special keys into character codes (keyboard source) +winapi = { workspace = true, features = ["winuser"] } + +[target.'cfg(target_os = "macos")'.dependencies] +# For taking screenshots for docs +xcap.workspace = true +# For taking screenshots for docs +dirs.workspace = true + + +[build-dependencies] +# For making Git commit hash available to code +built = { workspace = true, features = ["git2", "chrono"] } +# For generating bindings (for dialog resource IDs and EEL functions) +bindgen.workspace = true +# For compiling EEL and SWELL dialog resources +cc.workspace = true +# For embedding dialog resource files on Windows +embed-resource.workspace = true +# For RC dialog file generation +helgobox-dialogs.workspace = true + +[dev-dependencies] +# For the EEL memory consumption test +sysinfo.workspace = true + +[lib] +name = "helgobox" +crate-type = ["cdylib"] + + +[lints.clippy] +# TODO-high-refactoring Activate at some point. For now it's just too much to handle. +#enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/main/build.rs b/plugin-reaper-realearn/main/build.rs new file mode 100644 index 0000000..c48b287 --- /dev/null +++ b/plugin-reaper-realearn/main/build.rs @@ -0,0 +1,198 @@ +use std::error::Error; +use std::fs; +use std::path::{Path, PathBuf}; + +fn main() -> Result<(), Box> { + // Generate "built" file (containing build-time information) + built::write_built_file().expect("Failed to acquire build-time information"); + + // Generate GUI dialog files (rc file and C header) + let generated_dir = PathBuf::from("../target/generated"); + let dialog_rc_file = generated_dir.join("msvc.rc"); + generate_gui_dialogs(&generated_dir, &dialog_rc_file)?; + + // Optionally generate bindings (e.g. from Cockos EEL) + #[cfg(feature = "generate")] + codegen::generate_bindings(); + + // Embed (Windows) or compile (Linux/macOS) dialogs + #[cfg(target_family = "windows")] + embed_dialog_resources(&dialog_rc_file); + #[cfg(target_family = "unix")] + compile_dialogs(); + + // Compile WDL EEL + compile_eel(); + + Ok(()) +} + +#[allow(unused_variables)] +fn generate_gui_dialogs( + generated_dir: impl AsRef, + dialog_rc_file: impl AsRef, +) -> Result<(), Box> { + let bindings_file = "src/infrastructure/ui/bindings.rs"; + fs::create_dir_all(generated_dir.as_ref())?; + helgobox_dialogs::generate_dialog_files(generated_dir.as_ref(), bindings_file); + // On macOS and Linux, try to generate SWELL dialogs (needs PHP) + #[cfg(target_family = "unix")] + if let Err(e) = generate_dialogs(dialog_rc_file.as_ref()) { + println!("cargo:warning={e}"); + } + Ok(()) +} + +fn compile_eel() { + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + let asm_object_file = if cfg!(target_os = "windows") { + match target_arch.as_str() { + "x86_64" => Some("lib/WDL/WDL/eel2/asm-nseel-x64.obj"), + "arm64ec" => Some("lib/WDL/WDL/eel2/asm-nseel-arm64ec.obj"), + _ => None, + } + } else if cfg!(target_os = "macos") { + if target_arch == "x86_64" { + Some("lib/WDL/WDL/eel2/asm-nseel-x64-macho.o") + } else { + None + } + } else if cfg!(target_os = "linux") { + if target_arch == "x86_64" { + // Generate asm-nseel-x64.o + std::process::Command::new("make") + .current_dir( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib/WDL/WDL/eel2"), + ) + .arg("asm-nseel-x64-sse.o") + .output() + .expect("Failed to generate asm-nseel-x64.o. Maybe 'nasm' is not installed."); + Some("lib/WDL/WDL/eel2/asm-nseel-x64-sse.o") + } else { + None + } + } else { + None + }; + let mut build = cc::Build::new(); + build + .warnings(false) + // To make it compile for ARM targets (armv7 and aarch64) whose char type is unsigned. + .define("WDL_ALLOW_UNSIGNED_DEFAULT_CHAR", None) + // To make it compile for ARM targets (armv7) + .define("_FILE_OFFSET_BITS", "64") + .file("lib/WDL/WDL/eel2/nseel-cfunc.c") + .file("lib/WDL/WDL/eel2/nseel-compiler.c") + .file("lib/WDL/WDL/eel2/nseel-caltab.c") + .file("lib/WDL/WDL/eel2/nseel-eval.c") + .file("lib/WDL/WDL/eel2/nseel-lextab.c") + .file("lib/WDL/WDL/eel2/nseel-ram.c") + .file("lib/WDL/WDL/eel2/nseel-yylex.c"); + if target_arch == "arm" { + // To make it compile for Linux armv7 targets. + build.flag_if_supported("-marm"); + } + if let Some(f) = asm_object_file { + build.object(f); + } + build.compile("wdl-eel"); +} + +/// Compiles dialog windows code which was previously generated via PHP script. +#[cfg(target_family = "unix")] +fn compile_dialogs() { + // Compile the C++ file resulting from the PHP script execution in the generate step. + let mut build = cc::Build::new(); + build + .cpp(true) + .warnings(false) + .file("src/infrastructure/ui/dialogs.cpp") + // Important when building with Rust 1.61, otherwise missing GUI. + .link_lib_modifier("+whole-archive"); + if let Some(stdlib) = util::determine_cpp_stdlib() { + // Settings this to None on Linux causes the linker to automatically link against C++ + // anymore, so we just invoke that on macOS. + build.cpp_set_stdlib(stdlib); + } + build.compile("dialogs"); +} + +/// On Windows we can directly embed the dialog resource file produced by ResEdit. +#[cfg(target_family = "windows")] +fn embed_dialog_resources(rc_file: impl AsRef) { + let target = std::env::var("TARGET").unwrap(); + if let Some(tool) = cc::windows_registry::find_tool(target.as_str(), "cl.exe") { + for (key, value) in tool.env() { + std::env::set_var(key, value); + } + } + embed_resource::compile(rc_file, embed_resource::NONE); +} + +#[cfg(feature = "generate")] +mod codegen { + use crate::util; + use std::error::Error; + + /// Generates Rust bindings for a couple of C stuff. + pub fn generate_bindings() { + generate_core_bindings(); + } + + fn generate_core_bindings() { + println!("cargo:rerun-if-changed=src/base/wrapper.hpp"); + let mut builder = bindgen::Builder::default() + .header("src/base/wrapper.hpp") + .clang_arg("-xc++") + .enable_cxx_namespaces() + .raw_line("#![allow(non_upper_case_globals)]") + .raw_line("#![allow(non_camel_case_types)]") + .raw_line("#![allow(non_snake_case)]") + .raw_line("#![allow(dead_code)]") + .raw_line("#![allow(deref_nullptr)]") + .allowlist_function("NSEEL_.*") + .allowlist_var("NSEEL_.*") + .parse_callbacks(Box::new(bindgen::CargoCallbacks)); + if let Some(stdlib) = util::determine_cpp_stdlib() { + builder = builder.clang_arg(format!("-stdlib=lib{}", stdlib)); + } + let bindings = builder.generate().expect("Unable to generate bindings"); + let out_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + bindings + .write_to_file(out_path.join("src/base/bindings.rs")) + .expect("Couldn't write bindings!"); + } +} + +/// Generates dialog window C++ code from resource file using SWELL's PHP-based dialog generator +/// (too obscure to be ported to Rust). +/// +/// # Errors +/// +/// Returns an error if PHP is not installed. +#[cfg(target_family = "unix")] +pub fn generate_dialogs(rc_file: impl AsRef) -> Result<(), Box> { + // Use PHP to translate SWELL-compatible RC file to C++ + let result = std::process::Command::new("php") + .arg("lib/WDL/WDL/swell/swell_resgen.php") + .arg(rc_file.as_ref()) + .output() + .map_err(|_| { + "PHP not available, is necessary on macOS and Linux to generate GUI dialogs" + })?; + if !result.status.success() { + panic!("PHP dialog generation failed (PHP available but script failed)"); + } + Ok(()) +} + +mod util { + #[cfg(any(feature = "generate", target_family = "unix"))] + pub fn determine_cpp_stdlib() -> Option<&'static str> { + if cfg!(target_os = "macos") { + Some("c++") + } else { + None + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/.gitattributes b/plugin-reaper-realearn/main/lib/WDL/WDL/.gitattributes new file mode 100644 index 0000000..d21ee29 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/.gitattributes @@ -0,0 +1,26 @@ +* -text +*.c text=auto +*.cpp text=auto +*.cc text=auto +*.h text=auto +*.hpp text=auto +*.m text=auto +*.mm text=auto + +*.eel text=auto +*.php text=auto +*.txt text=auto + +*.bat text eol=crlf +*.cmd text eol=crlf + +*.rc text eol=crlf +*.dsp text eol=crlf +*.dsw text eol=crlf +*.sln text eol=crlf +*.vcxproj text eol=crlf +*.vcxproj.filters text eol=crlf + +*.sh text eol=lf +*.pbxproj text eol=lf +Makefile text eol=lf diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/MersenneTwister.h b/plugin-reaper-realearn/main/lib/WDL/WDL/MersenneTwister.h new file mode 100644 index 0000000..3af5ab5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/MersenneTwister.h @@ -0,0 +1,413 @@ +// Taken from http://www-personal.engin.umich.edu/~wagnerr/MersenneTwister.html + + +// MersenneTwister.h +// Mersenne Twister random number generator -- a C++ class MTRand +// Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus +// Richard J. Wagner v1.0 15 May 2003 rjwagner@writeme.com + +// The Mersenne Twister is an algorithm for generating random numbers. It +// was designed with consideration of the flaws in various other generators. +// The period, 2^19937-1, and the order of equidistribution, 623 dimensions, +// are far greater. The generator is also fast; it avoids multiplication and +// division, and it benefits from caches and pipelines. For more information +// see the inventors' web page at http://www.math.keio.ac.jp/~matumoto/emt.html + +// Reference +// M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-Dimensionally +// Equidistributed Uniform Pseudo-Random Number Generator", ACM Transactions on +// Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3-30. + +// Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, +// Copyright (C) 2000 - 2003, Richard J. Wagner +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The names of its contributors may not be used to endorse or promote +// products derived from this software without specific prior written +// permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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. + +// The original code included the following notice: +// +// When you use this, send an email to: matumoto@math.keio.ac.jp +// with an appropriate reference to your work. +// +// It would be nice to CC: rjwagner@writeme.com and Cokus@math.washington.edu +// when you write. + +#ifndef _MERSENNETWISTER_H_ +#define _MERSENNETWISTER_H_ + +// Not thread safe (unless auto-initialization is avoided and each thread has +// its own MTRand object) + +#include +#include +#include +#include + +class MTRand { +// Data +public: + typedef unsigned int uint32; // unsigned integer type, at least 32 bits + + enum { N = 624 }; // length of state vector + enum { SAVE = N + 1 }; // length of array for save() + +protected: + enum { M = 397 }; // period parameter + + uint32 state[N]; // internal state + uint32 *pNext; // next value to get from state + int left; // number of values left before reload needed + + +//Methods +public: + MTRand( const uint32& oneSeed ); // initialize with a simple uint32 + MTRand( uint32 *const bigSeed, uint32 const seedLength = N ); // or an array + MTRand(); // auto-initialize with /dev/urandom or time() and clock() + + // Do NOT use for CRYPTOGRAPHY without securely hashing several returned + // values together, otherwise the generator state can be learned after + // reading 624 consecutive values. + + // Access to 32-bit random numbers + double rand(); // real number in [0,1] + double rand( const double& n ); // real number in [0,n] + double randExc(); // real number in [0,1) + double randExc( const double& n ); // real number in [0,n) + double randDblExc(); // real number in (0,1) + double randDblExc( const double& n ); // real number in (0,n) + uint32 randInt(); // integer in [0,2^32-1] + uint32 randInt( const uint32& n ); // integer in [0,n] for n < 2^32 + double operator()() { return rand(); } // same as rand() + + // Access to 53-bit random numbers (capacity of IEEE double precision) + double rand53(); // real number in [0,1) + + // Access to nonuniform random number distributions + double randNorm( const double& mean = 0.0, const double& variance = 0.0 ); + + // Re-seeding functions with same behavior as initializers + void seed( const uint32 oneSeed ); + void seed( uint32 *const bigSeed, const uint32 seedLength = N ); + void seed(); + + // Saving and loading generator state + void save( uint32* saveArray ) const; // to array of size SAVE + void load( uint32 *const loadArray ); // from such array + +protected: + void initialize( const uint32 oneSeed ); + void reload(); + uint32 hiBit( const uint32& u ) const { return u & 0x80000000UL; } + uint32 loBit( const uint32& u ) const { return u & 0x00000001UL; } + uint32 loBits( const uint32& u ) const { return u & 0x7fffffffUL; } + uint32 mixBits( const uint32& u, const uint32& v ) const + { return hiBit(u) | loBits(v); } + uint32 twist( const uint32& m, const uint32& s0, const uint32& s1 ) const + { return m ^ (mixBits(s0,s1)>>1) ^ (-((int)loBit(s1)) & 0x9908b0dfUL); } + +public: + // This was protected, but we need it exposed so FIRan1.h can use it for seeding + static uint32 hash( time_t t, clock_t c ); +}; + + +inline MTRand::MTRand( const uint32& oneSeed ) + { seed(oneSeed); } + +inline MTRand::MTRand( uint32 *const bigSeed, const uint32 seedLength ) + { seed(bigSeed,seedLength); } + +inline MTRand::MTRand() + { seed(); } + +inline double MTRand::rand() + { return double(randInt()) * (1.0/4294967295.0); } + +inline double MTRand::rand( const double& n ) + { return rand() * n; } + +inline double MTRand::randExc() + { return double(randInt()) * (1.0/4294967296.0); } + +inline double MTRand::randExc( const double& n ) + { return randExc() * n; } + +inline double MTRand::randDblExc() + { return ( double(randInt()) + 0.5 ) * (1.0/4294967296.0); } + +inline double MTRand::randDblExc( const double& n ) + { return randDblExc() * n; } + +inline double MTRand::rand53() +{ + uint32 a = randInt() >> 5, b = randInt() >> 6; + return ( a * 67108864.0 + b ) * (1.0/9007199254740992.0); // by Isaku Wada +} + +inline double MTRand::randNorm( const double& mean, const double& variance ) +{ + // Return a real number from a normal (Gaussian) distribution with given + // mean and variance by Box-Muller method + double r = sqrt( -2.0 * log( 1.0-randDblExc()) ) * variance; + double phi = 2.0 * 3.14159265358979323846264338328 * randExc(); + return mean + r * cos(phi); +} + +inline MTRand::uint32 MTRand::randInt() +{ + // Pull a 32-bit integer from the generator state + // Every other access function simply transforms the numbers extracted here + + if( left == 0 ) reload(); + --left; + + uint32 s1; + s1 = *pNext++; + s1 ^= (s1 >> 11); + s1 ^= (s1 << 7) & 0x9d2c5680UL; + s1 ^= (s1 << 15) & 0xefc60000UL; + return ( s1 ^ (s1 >> 18) ); +} + +inline MTRand::uint32 MTRand::randInt( const uint32& n ) +{ + // Find which bits are used in n + // Optimized by Magnus Jonsson (magnus@smartelectronix.com) + uint32 used = n; + used |= used >> 1; + used |= used >> 2; + used |= used >> 4; + used |= used >> 8; + used |= used >> 16; + + // Draw numbers until one is found in [0,n] + uint32 i; + do + i = randInt() & used; // toss unused bits to shorten search + while( i > n ); + return i; +} + + +inline void MTRand::seed( const uint32 oneSeed ) +{ + // Seed the generator with a simple uint32 + initialize(oneSeed); + reload(); +} + + +inline void MTRand::seed( uint32 *const bigSeed, const uint32 seedLength ) +{ + // Seed the generator with an array of uint32's + // There are 2^19937-1 possible initial states. This function allows + // all of those to be accessed by providing at least 19937 bits (with a + // default seed length of N = 624 uint32's). Any bits above the lower 32 + // in each element are discarded. + // Just call seed() if you want to get array from /dev/urandom + initialize(19650218UL); + int i = 1; + uint32 j = 0; + int k = ( N > seedLength ? N : seedLength ); + for( ; k; --k ) + { + state[i] = + state[i] ^ ( (state[i-1] ^ (state[i-1] >> 30)) * 1664525UL ); + state[i] += ( bigSeed[j] & 0xffffffffUL ) + j; + state[i] &= 0xffffffffUL; + ++i; ++j; + if( i >= N ) { state[0] = state[N-1]; i = 1; } + if( j >= seedLength ) j = 0; + } + for( k = N - 1; k; --k ) + { + state[i] = + state[i] ^ ( (state[i-1] ^ (state[i-1] >> 30)) * 1566083941UL ); + state[i] -= i; + state[i] &= 0xffffffffUL; + ++i; + if( i >= N ) { state[0] = state[N-1]; i = 1; } + } + state[0] = 0x80000000UL; // MSB is 1, assuring non-zero initial array + reload(); +} + + +inline void MTRand::seed() +{ + // Seed the generator with an array from /dev/urandom if available + // Otherwise use a hash of time() and clock() values + + // No point in trying this on Windows machines - won't work, so it just slows things down +#ifndef WIN32 +#ifndef WDL_MTRAND_FASTSEED + // First try getting an array from /dev/urandom + FILE* urandom = fopen( "/dev/urandom", "rb" ); + if( urandom ) + { + uint32 bigSeed[N]; + uint32 *s = bigSeed; + int i = N; + bool success = true; + while( success && i-- ) + success = fread( s++, sizeof(uint32), 1, urandom ); + fclose(urandom); + if( success ) { seed( bigSeed, N ); return; } + } +#endif +#endif + + // Was not successful, so use time() and clock() instead + seed( hash( time(NULL), clock() ) ); +} + + +inline void MTRand::initialize( const uint32 seedv ) +{ + // Initialize generator state with seed + // See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier. + // In previous versions, most significant bits (MSBs) of the seed affect + // only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto. + uint32 *s = state; + uint32 *r = state; + int i = 1; + *s++ = seedv & 0xffffffffUL; + for( ; i < N; ++i ) + { + *s++ = ( 1812433253UL * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffUL; + r++; + } +} + + +inline void MTRand::reload() +{ + // Generate N new values in state + // Made clearer and faster by Matthew Bellew (matthew.bellew@home.com) + uint32 *p = state; + int i; + for( i = int(N) - int(M); i--; ++p ) + *p = twist( p[M], p[0], p[1] ); + for( i = M; --i; ++p ) + *p = twist( p[int(M)-int(N)], p[0], p[1] ); + *p = twist( p[int(M)-int(N)], p[0], state[0] ); + + left = N, pNext = state; +} + + +inline MTRand::uint32 MTRand::hash( time_t t, clock_t c ) +{ + // Get a uint32 from t and c + // Better than uint32(x) in case x is floating point in [0,1] + // Based on code by Lawrence Kirby (fred@genesis.demon.co.uk) + + static uint32 differ = 0; // guarantee time-based seeds will change + + uint32 h1 = 0; + unsigned char *p = (unsigned char *) &t; + for( size_t i = 0; i < sizeof(t); ++i ) + { + h1 *= UCHAR_MAX + 2U; + h1 += p[i]; + } + uint32 h2 = 0; + p = (unsigned char *) &c; + for( size_t j = 0; j < sizeof(c); ++j ) + { + h2 *= UCHAR_MAX + 2U; + h2 += p[j]; + } + return ( h1 + differ++ ) ^ h2; +} + + +inline void MTRand::save( uint32* saveArray ) const +{ + uint32 *sa = saveArray; + const uint32 *s = state; + int i = N; + for( ; i--; *sa++ = *s++ ) {} + *sa = left; +} + + +inline void MTRand::load( uint32 *const loadArray ) +{ + uint32 *s = state; + uint32 *la = loadArray; + int i = N; + for( ; i--; *s++ = *la++ ) {} + left = *la; + pNext = &state[N-left]; +} + + + +#endif // MERSENNETWISTER_H + +// Change log: +// +// v0.1 - First release on 15 May 2000 +// - Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus +// - Translated from C to C++ +// - Made completely ANSI compliant +// - Designed convenient interface for initialization, seeding, and +// obtaining numbers in default or user-defined ranges +// - Added automatic seeding from /dev/urandom or time() and clock() +// - Provided functions for saving and loading generator state +// +// v0.2 - Fixed bug which reloaded generator one step too late +// +// v0.3 - Switched to clearer, faster reload() code from Matthew Bellew +// +// v0.4 - Removed trailing newline in saved generator format to be consistent +// with output format of built-in types +// +// v0.5 - Improved portability by replacing static const int's with enum's and +// clarifying return values in seed(); suggested by Eric Heimburg +// - Removed MAXINT constant; use 0xffffffffUL instead +// +// v0.6 - Eliminated seed overflow when uint32 is larger than 32 bits +// - Changed integer [0,n] generator to give better uniformity +// +// v0.7 - Fixed operator precedence ambiguity in reload() +// - Added access for real numbers in (0,1) and (0,n) +// +// v0.8 - Included time.h header to properly support time_t and clock_t +// +// v1.0 - Revised seeding to match 26 Jan 2002 update of Nishimura and Matsumoto +// - Allowed for seeding with arrays of any length +// - Added access for real numbers in [0,1) with 53-bit resolution +// - Added access for real numbers from normal (Gaussian) distributions +// - Increased overall speed by optimizing twist() +// - Doubled speed of integer [0,n] generation +// - Fixed out-of-range number generation on 64-bit machines +// - Improved portability by substituting literal constants for long enum's +// - Changed license from GNU LGPL to BSD diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/adpcm_decode.h b/plugin-reaper-realearn/main/lib/WDL/WDL/adpcm_decode.h new file mode 100644 index 0000000..f97195c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/adpcm_decode.h @@ -0,0 +1,319 @@ +#ifndef _WDL_ADPCM_DECODE_H_ +#define _WDL_ADPCM_DECODE_H_ + +#include "queue.h" + +#define MSADPCM_TYPE 2 +#define IMAADPCM_TYPE 0x11 +#define CADPCM2_TYPE 0xac0c + +class WDL_adpcm_decoder +{ + typedef struct + { + int cf1,cf2,deltas,spl1,spl2; + } WDL_adpcm_decode_chanctx; + +public: + enum { MSADPCM_PREAMBLELEN=7, IMA_PREAMBLELEN=4 }; + static INT64 sampleLengthFromBytes(INT64 nbytes, int blockalign, int nch, int type, int bps) + { + if (!bps||type!=CADPCM2_TYPE) bps=4; + // remove overhead of headers + INT64 nblocks=((nbytes+blockalign-1)/blockalign); + + // remove preambles + if (type==IMAADPCM_TYPE||type==CADPCM2_TYPE) nbytes -= nblocks*IMA_PREAMBLELEN*nch; + else nbytes -= nblocks*MSADPCM_PREAMBLELEN*nch; + + // scale from bytes to samples + nbytes = (nbytes*8)/(nch*bps); + + if (type==IMAADPCM_TYPE||type==CADPCM2_TYPE) nbytes++; // IMA has just one initial sample + else nbytes+=2; // msadpcm has 2 initial sample values + + return nbytes; + } + + WDL_adpcm_decoder(int blockalign,int nch, int type, int bps) + { + m_bps=0; + m_type=0; + m_nch=0; + m_blockalign=0; + m_srcbuf=0; + m_chans=0; + setParameters(blockalign,nch,type,bps); + } + ~WDL_adpcm_decoder() + { + free(m_srcbuf); + free(m_chans); + } + + + void resetState() + { + if (m_chans) memset(m_chans,0,m_nch*sizeof(WDL_adpcm_decode_chanctx)); + m_srcbuf_valid=0; + samplesOut.Clear(); + samplesOut.Compact(); + } + + void setParameters(int ba, int nch, int type, int bps) + { + if (m_blockalign != ba||nch != m_nch||type!=m_type||bps != m_bps) + { + free(m_srcbuf); + free(m_chans); + m_bps=bps; + m_blockalign=ba; + m_nch=nch; + m_srcbuf_valid=0; + m_srcbuf=(unsigned char*)malloc(ba); + m_chans=(WDL_adpcm_decode_chanctx*)malloc(sizeof(WDL_adpcm_decode_chanctx)*nch); + m_type=type; + resetState(); + } + } + + int blockAlign() { return m_blockalign; } + + int samplesPerBlock() + { + if (m_type==IMAADPCM_TYPE||m_type==CADPCM2_TYPE) + { + if (m_bps == 2) return (m_blockalign/m_nch - IMA_PREAMBLELEN)*4 + 1; + return (m_blockalign/m_nch - IMA_PREAMBLELEN)*2 + 1; //4 bit + } + return (m_blockalign/m_nch - MSADPCM_PREAMBLELEN)*2 + 2; // 4 bit + } + + INT64 samplesToSourceBytes(INT64 outlen_samples) // length in samplepairs + { + outlen_samples -= samplesOut.Available()/m_nch; + if (outlen_samples<1) return 0; // no data required + + int spls_block = samplesPerBlock(); + if (spls_block<1) return 0; + INT64 nblocks = (outlen_samples+spls_block-1)/spls_block; + INT64 v=nblocks * m_blockalign; + + v -= m_srcbuf_valid; + + return wdl_max(v,0); + } + + void AddInput(void *buf, int len, short *parm_cotab=NULL) + { + unsigned char *rdbuf = (unsigned char *)buf; + if (m_srcbuf_valid) + { + int v=m_blockalign-m_srcbuf_valid; + if (v>len) v=len; + + memcpy(m_srcbuf+m_srcbuf_valid,rdbuf,v); + len-=v; + rdbuf+=v; + if ((m_srcbuf_valid+=v)>=m_blockalign) + { + DecodeBlock(m_srcbuf,parm_cotab); + m_srcbuf_valid=0; + } + } + + while (len >= m_blockalign) + { + DecodeBlock(rdbuf,parm_cotab); + rdbuf+=m_blockalign; + len-=m_blockalign; + } + if (len>0) memcpy(m_srcbuf,rdbuf,m_srcbuf_valid=len); + } + + + int sourceBytesQueued() { return m_srcbuf_valid; } + WDL_TypedQueue samplesOut; + +private: + static int getwordsigned(unsigned char **rdptr) + { + int s = (*rdptr)[0] + ((*rdptr)[1]<<8); + (*rdptr)+=2; + if (s & 0x8000) s -= 0x10000; + return s; + } + + bool DecodeBlockIMA(unsigned char *buf) + { + int samples_block = samplesPerBlock(); + + int nch=m_nch; + int ch; + short *outptr = samplesOut.Add(NULL,samples_block * nch); + + for (ch=0;ch>2; break; + default: nib=lastbyte>>4; bstate=0; break; + } + nib &= 8|4; + } + else + { + if ((bstate^=1)) nib=(lastbyte=*buf++)&0xf; + else nib=lastbyte>>4; + } + + int step_index=m_chans[ch].cf1; + if (step_index<0)step_index=0; + else if (step_index>88)step_index=88; + + int step=step_table[step_index]; + + int diff = ((nib&7)*step)/4 + step/8; + + int v=m_chans[ch].spl1 + ((nib&8) ? -diff : diff); + + if (v<-32768)v=-32768; + else if (v>32767)v=32767; + + outptr[wrpos]=(short)v; + wrpos+=nch; + + m_chans[ch].spl1=v; + + m_chans[ch].cf1=step_index + index_table[nib&7]; + + + // advance channelcounts + if (++cnt==chunksize) + { + if (++ch>=nch) + { + ch=0; + outptr += chunksize*nch; + } + wrpos = ch; + cnt=0; + } + } + + return true; + } + + bool DecodeBlock(unsigned char *buf, short *parm_cotab=NULL) + { + if (m_type==IMAADPCM_TYPE||m_type==CADPCM2_TYPE) return DecodeBlockIMA(buf); + static short cotab[14] = { 256,0, 512,-256, 0,0, 192,64, 240, 0,460, -208, 392, -232 }; + static short adtab[16] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; + + short *use_cotab = parm_cotab ? parm_cotab : cotab; + int nch = m_nch; + int ch; + for(ch=0;ch 6) return false; + c*=2; + m_chans[ch].cf1 = use_cotab[c]; + m_chans[ch].cf2 = use_cotab[c+1]; + } + for(ch=0;ch>4; + else nib=lastbyte&0xf; + + int sn=nib; + if (sn & 8) sn -= 16; + + int pred = ( ((m_chans[ch].spl1 * m_chans[ch].cf1) + + (m_chans[ch].spl2 * m_chans[ch].cf2)) / 256) + + (sn * m_chans[ch].deltas); + + m_chans[ch].spl2 = m_chans[ch].spl1; + + if (pred < -32768) pred=-32768; + else if (pred > 32767) pred=32767; + + *outptr++ = m_chans[ch].spl1 = pred; + + int i= (adtab[nib] * m_chans[ch].deltas) / 256; + if (i <= 16) m_chans[ch].deltas=16; + else m_chans[ch].deltas = i; + } + } + return true; + } + + WDL_adpcm_decode_chanctx *m_chans; + unsigned char *m_srcbuf; + int m_srcbuf_valid; + + int m_blockalign,m_nch,m_type,m_bps; + + +}; + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/adpcm_encode.h b/plugin-reaper-realearn/main/lib/WDL/WDL/adpcm_encode.h new file mode 100644 index 0000000..77ff749 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/adpcm_encode.h @@ -0,0 +1,137 @@ +#ifndef _WDL_ADPCM_ENCODE_H_ +#define _WDL_ADPCM_ENCODE_H_ + + +#include "pcmfmtcvt.h" + +void WDL_adpcm_encode_IMA(PCMFMTCVT_DBL_TYPE *samples, int numsamples, int nch, int bps, + unsigned char *bufout, int *bufout_used, short **predState); + +#define WDL_adpcm_encode_IMA_samplesneededbytes(bytes,bps) ((((bytes)-4)*8)/(bps)+1) + + +// untested. also probably slow. +#ifdef WDL_ADPCM_ENCODE_IMPL + + +static signed char ima_adpcm_index_table[8] = { -1, -1, -1, -1, 2, 4, 6, 8, }; +static short ima_adpcm_step_table[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + +static char calcBestNibble(int *initial_step_index, int lastSpl, int thisSpl, short *lastsplout, int bps) +{ + + int step=ima_adpcm_step_table[*initial_step_index]; + + char sign=0; + int adiff = thisSpl - lastSpl; + if (adiff<0) { adiff=-adiff; sign=8; } + + // adiff == (nib*step)/4 + step/8 + // adiff - step/8 = nib*step/4 + // nib = 4*(adiff-step/8)/step = 4*adiff/step - 0.5 + int nib = step ? ((4 * adiff - step/2) / step) : 0; + if (nib<0) nib=0; + else if(nib>7) nib=7; + + if (bps==2) nib&=4; + + int diff = (nib*step)/4 + step/8; + *lastsplout = lastSpl + (sign?-diff:diff); + + + *initial_step_index += ima_adpcm_index_table[nib]; + + if (*initial_step_index<0)*initial_step_index=0; + else if (*initial_step_index>88)*initial_step_index=88; + + return (char)nib|sign; +} + + + +void WDL_adpcm_encode_IMA(PCMFMTCVT_DBL_TYPE *samples, int numsamples, int nch, int bps, + unsigned char *bufout, int *bufout_used, short **predState) +{ + int x; + if (!*predState) *predState=(short *)calloc(nch,sizeof(short)); + + short *pstate = *predState; + for(x=0;x>8; + spl+=nch; + + *wrptr++ = step_index&0xff; *wrptr++ = step_index>>8; + + wrptr += (nch-1)*4; + + + int outpos=0; + const int outblocklen = bps == 2 ? 16 : 8; + unsigned char buildchar=0; + + while (left-->0) + { + short this_spl; + double_TO_INT16(this_spl,*spl); + char nib = calcBestNibble(&step_index,last_out,this_spl,&last_out,bps); + + spl+=nch; + + // update output + if (bps == 2) + { + nib>>=2; + switch (outpos&3) + { + case 0: buildchar = nib; break; + case 1: buildchar |= nib<<2; break; + case 2: buildchar |= nib<<4; break; + case 3: *wrptr++ = buildchar | (nib<<6); break; + } + } + else + { + if (!(outpos&1)) buildchar = nib; + else *wrptr++ = buildchar | (nib<<4); + } + + // skip other channels + if (++outpos == outblocklen) + { + wrptr += ((nch-1)*outblocklen*bps)/8; + outpos=0; + } + } + pstate[x] = step_index; + + } + *bufout_used = (((numsamples-1)*bps)/8 + 4)*nch; + + +} + +#endif + + +#endif//_WDL_ADPCM_ENCODE_H_ \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/assocarray.h b/plugin-reaper-realearn/main/lib/WDL/WDL/assocarray.h new file mode 100644 index 0000000..b3d0498 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/assocarray.h @@ -0,0 +1,499 @@ +#ifndef _WDL_ASSOCARRAY_H_ +#define _WDL_ASSOCARRAY_H_ + +#include "heapbuf.h" +#include "mergesort.h" +#include "wdlcstring.h" + +// on all of these, if valdispose is set, the array will dispose of values as needed. +// if keydup/keydispose are set, copies of (any) key data will be made/destroyed as necessary + + +// WDL_AssocArrayImpl can be used on its own, and can contain structs for keys or values +template class WDL_AssocArrayImpl +{ + WDL_AssocArrayImpl(const WDL_AssocArrayImpl &cp) { CopyContents(cp); } + + WDL_AssocArrayImpl &operator=(const WDL_AssocArrayImpl &cp) { CopyContents(cp); return *this; } + +public: + + explicit WDL_AssocArrayImpl(int (*keycmp)(KEY *k1, KEY *k2), + KEY (*keydup)(KEY)=NULL, + void (*keydispose)(KEY)=NULL, + void (*valdispose)(VAL)=NULL) + { + m_keycmp = keycmp; + m_keydup = keydup; + m_keydispose = keydispose; + m_valdispose = valdispose; + } + + ~WDL_AssocArrayImpl() + { + DeleteAll(); + } + + void Prealloc(int sz) { m_data.Prealloc(sz*sizeof(KeyVal)); } + + VAL* GetPtr(KEY key, KEY *keyPtrOut=NULL) const + { + bool ismatch = false; + int i = LowerBound(key, &ismatch); + if (ismatch) + { + KeyVal* kv = m_data.Get()+i; + if (keyPtrOut) *keyPtrOut = kv->key; + return &(kv->val); + } + return 0; + } + + bool Exists(KEY key) const + { + bool ismatch = false; + LowerBound(key, &ismatch); + return ismatch; + } + + int Insert(KEY key, VAL val) + { + bool ismatch = false; + int i = LowerBound(key, &ismatch); + if (ismatch) + { + KeyVal* kv = m_data.Get()+i; + if (m_valdispose) m_valdispose(kv->val); + kv->val = val; + } + else + { + KeyVal *kv = m_data.ResizeOK(m_data.GetSize()+1); + if (WDL_NORMALLY(kv != NULL)) + { + memmove(kv+i+1, kv+i, (m_data.GetSize()-i-1)*sizeof(KeyVal)); + if (m_keydup) key = m_keydup(key); + kv[i].key = key; + kv[i].val = val; + } + } + return i; + } + + void Delete(KEY key) + { + bool ismatch = false; + int i = LowerBound(key, &ismatch); + if (ismatch) + { + KeyVal* kv = m_data.Get()+i; + if (m_keydispose) m_keydispose(kv->key); + if (m_valdispose) m_valdispose(kv->val); + m_data.Delete(i); + } + } + + void DeleteByIndex(int idx) + { + if (idx >= 0 && idx < m_data.GetSize()) + { + KeyVal* kv = m_data.Get()+idx; + if (m_keydispose) m_keydispose(kv->key); + if (m_valdispose) m_valdispose(kv->val); + m_data.Delete(idx); + } + } + + void DeleteAll(bool resizedown=false) + { + if (m_keydispose || m_valdispose) + { + int i; + for (i = 0; i < m_data.GetSize(); ++i) + { + KeyVal* kv = m_data.Get()+i; + if (m_keydispose) m_keydispose(kv->key); + if (m_valdispose) m_valdispose(kv->val); + } + } + m_data.Resize(0, resizedown); + } + + int GetSize() const + { + return m_data.GetSize(); + } + + VAL* EnumeratePtr(int i, KEY* key=NULL) const + { + if (i >= 0 && i < m_data.GetSize()) + { + KeyVal* kv = m_data.Get()+i; + if (key) *key = kv->key; + return &(kv->val); + } + return 0; + } + + KEY* ReverseLookupPtr(VAL val) const + { + int i; + for (i = 0; i < m_data.GetSize(); ++i) + { + KeyVal* kv = m_data.Get()+i; + if (kv->val == val) return &kv->key; + } + return 0; + } + + void ChangeKey(KEY oldkey, KEY newkey) + { + bool ismatch=false; + int i=LowerBound(oldkey, &ismatch); + if (ismatch) ChangeKeyByIndex(i, newkey, true); + } + + void ChangeKeyByIndex(int idx, KEY newkey, bool needsort) + { + if (idx >= 0 && idx < m_data.GetSize()) + { + KeyVal* kv=m_data.Get()+idx; + if (!needsort) + { + if (m_keydispose) m_keydispose(kv->key); + if (m_keydup) newkey=m_keydup(newkey); + kv->key=newkey; + } + else + { + VAL val=kv->val; + m_data.Delete(idx); + Insert(newkey, val); + } + } + } + + // fast add-block mode + void AddUnsorted(KEY key, VAL val) + { + int i=m_data.GetSize(); + KeyVal *kv = m_data.ResizeOK(i+1); + if (WDL_NORMALLY(kv != NULL)) + { + if (m_keydup) key = m_keydup(key); + kv[i].key = key; + kv[i].val = val; + } + } + + void Resort(int (*new_keycmp)(KEY *k1, KEY *k2)=NULL) + { + if (new_keycmp) m_keycmp = new_keycmp; + if (m_data.GetSize() > 1 && m_keycmp) + { + qsort(m_data.Get(), m_data.GetSize(), sizeof(KeyVal), + (int(*)(const void*, const void*))m_keycmp); + if (!new_keycmp) + RemoveDuplicateKeys(); + } + } + + void ResortStable() + { + if (m_data.GetSize() > 1 && m_keycmp) + { + char *tmp=(char*)malloc(m_data.GetSize()*sizeof(KeyVal)); + if (WDL_NORMALLY(tmp)) + { + WDL_mergesort(m_data.Get(), m_data.GetSize(), sizeof(KeyVal), + (int(*)(const void*, const void*))m_keycmp, tmp); + free(tmp); + } + else + { + qsort(m_data.Get(), m_data.GetSize(), sizeof(KeyVal), + (int(*)(const void*, const void*))m_keycmp); + } + + RemoveDuplicateKeys(); + } + } + + int LowerBound(KEY key, bool* ismatch) const + { + int a = 0; + int c = m_data.GetSize(); + while (a != c) + { + int b = (a+c)/2; + KeyVal* kv=m_data.Get()+b; + int cmp = m_keycmp(&key, &kv->key); + if (cmp > 0) a = b+1; + else if (cmp < 0) c = b; + else + { + *ismatch = true; + return b; + } + } + *ismatch = false; + return a; + } + + int GetIdx(KEY key) const + { + bool ismatch=false; + int i = LowerBound(key, &ismatch); + if (ismatch) return i; + return -1; + } + + void SetGranul(int gran) + { + m_data.SetGranul(gran); + } + + void CopyContents(const WDL_AssocArrayImpl &cp) + { + m_data=cp.m_data; + m_keycmp = cp.m_keycmp; + m_keydup = cp.m_keydup; + m_keydispose = m_keydup ? cp.m_keydispose : NULL; + m_valdispose = NULL; // avoid disposing of values twice, since we don't have a valdup, we can't have a fully valid copy + if (m_keydup) + { + const int n=m_data.GetSize(); + for (int x=0;xkey = m_keydup(kv->key); + } + } + } + + void CopyContentsAsReference(const WDL_AssocArrayImpl &cp) + { + DeleteAll(true); + m_keycmp = cp.m_keycmp; + m_keydup = NULL; // this no longer can own any data + m_keydispose = NULL; + m_valdispose = NULL; + + m_data=cp.m_data; + } + + +// private data, but exposed in case the caller wants to manipulate at its own risk + struct KeyVal + { + KEY key; + VAL val; + }; + WDL_TypedBuf m_data; + + static int keycmp_ptr(KEY *a, KEY *b) { return (INT_PTR)*a > (INT_PTR)*b ? 1 : (INT_PTR)*a < (INT_PTR)*b ? -1 : 0; } + +protected: + + int (*m_keycmp)(KEY *k1, KEY *k2); + KEY (*m_keydup)(KEY); + void (*m_keydispose)(KEY); + void (*m_valdispose)(VAL); + +private: + + void RemoveDuplicateKeys() // after resorting + { + const int sz = m_data.GetSize(); + + int cnt = 1; + KeyVal *rd = m_data.Get() + 1, *wr = rd; + for (int x = 1; x < sz; x ++) + { + if (m_keycmp(&rd->key, &wr[-1].key)) + { + if (rd != wr) *wr=*rd; + wr++; + cnt++; + } + else + { + if (m_keydispose) m_keydispose(rd->key); + if (m_valdispose) m_valdispose(rd->val); + } + rd++; + } + if (cnt < sz) m_data.Resize(cnt,false); + } +}; + + +// WDL_AssocArray adds useful functions but cannot contain structs for keys or values +template class WDL_AssocArray : public WDL_AssocArrayImpl +{ +public: + + explicit WDL_AssocArray(int (*keycmp)(KEY *k1, KEY *k2), + KEY (*keydup)(KEY)=NULL, + void (*keydispose)(KEY)=NULL, void (*valdispose)(VAL)=NULL) + : WDL_AssocArrayImpl(keycmp, keydup, keydispose, valdispose) + { + } + + VAL Get(KEY key, VAL notfound=0) const + { + VAL* p = this->GetPtr(key); + if (p) return *p; + return notfound; + } + + VAL Enumerate(int i, KEY* key=NULL, VAL notfound=0) const + { + VAL* p = this->EnumeratePtr(i, key); + if (p) return *p; + return notfound; + } + + KEY ReverseLookup(VAL val, KEY notfound=0) const + { + KEY* p=this->ReverseLookupPtr(val); + if (p) return *p; + return notfound; + } +}; + + +template class WDL_IntKeyedArray : public WDL_AssocArray +{ +public: + + explicit WDL_IntKeyedArray(void (*valdispose)(VAL)=NULL) + : WDL_AssocArray(cmpint, NULL, NULL, valdispose) {} + + static int cmpint(int *a, int *b) { return *a > *b ? 1 : *a < *b ? -1 : 0; } +}; + +template class WDL_IntKeyedArray2 : public WDL_AssocArrayImpl +{ +public: + + explicit WDL_IntKeyedArray2(void (*valdispose)(VAL)=NULL) + : WDL_AssocArrayImpl(cmpint, NULL, NULL, valdispose) {} + + static int cmpint(int *a, int *b) { return *a > *b ? 1 : *a < *b ? -1 : 0; } +}; + +template class WDL_StringKeyedArray : public WDL_AssocArray +{ +public: + + explicit WDL_StringKeyedArray(bool caseSensitive=true, void (*valdispose)(VAL)=NULL) + : WDL_AssocArray(caseSensitive?cmpstr:cmpistr, dupstr, freestr, valdispose) {} + + static const char *dupstr(const char *s) { return strdup(s); } // these might not be necessary but depending on the libc maybe... + static int cmpstr(const char **s1, const char **s2) { return strcmp(*s1, *s2); } + static int cmpistr(const char **a, const char **b) { return stricmp(*a,*b); } + static void freestr(const char* s) { free((void*)s); } + static void freecharptr(char *p) { free(p); } +}; + + +template class WDL_StringKeyedArray2 : public WDL_AssocArrayImpl +{ +public: + + explicit WDL_StringKeyedArray2(bool caseSensitive=true, void (*valdispose)(VAL)=NULL) + : WDL_AssocArrayImpl(caseSensitive?cmpstr:cmpistr, dupstr, freestr, valdispose) {} + + ~WDL_StringKeyedArray2() { } + + static const char *dupstr(const char *s) { return strdup(s); } // these might not be necessary but depending on the libc maybe... + static int cmpstr(const char **s1, const char **s2) { return strcmp(*s1, *s2); } + static int cmpistr(const char **a, const char **b) { return stricmp(*a,*b); } + static void freestr(const char* s) { free((void*)s); } + static void freecharptr(char *p) { free(p); } +}; + +// sorts text as text, sorts anything that looks like a number as a number +template class WDL_LogicalSortStringKeyedArray : public WDL_StringKeyedArray +{ +public: + + explicit WDL_LogicalSortStringKeyedArray(bool caseSensitive=true, void (*valdispose)(VAL)=NULL) + : WDL_StringKeyedArray(caseSensitive, valdispose) + { + WDL_StringKeyedArray::m_keycmp = caseSensitive?cmpstr:cmpistr; // override + } + + ~WDL_LogicalSortStringKeyedArray() { } + + static int cmpstr(const char **a, const char **b) + { + int r=WDL_strcmp_logical_ex(*a, *b, 1, WDL_STRCMP_LOGICAL_EX_FLAG_UTF8CONVERT); + return r?r:strcmp(*a,*b); + } + static int cmpistr(const char **a, const char **b) + { + int r=WDL_strcmp_logical_ex(*a, *b, 0, WDL_STRCMP_LOGICAL_EX_FLAG_UTF8CONVERT); + return r?r:stricmp(*a,*b); + } +}; + + +template class WDL_PtrKeyedArray : public WDL_AssocArray +{ +public: + explicit WDL_PtrKeyedArray(void (*valdispose)(VAL)=NULL) + : WDL_AssocArray(WDL_AssocArray::keycmp_ptr, NULL, NULL, valdispose) {} +}; + +template class WDL_PointerKeyedArray : public WDL_AssocArray +{ +public: + explicit WDL_PointerKeyedArray(void (*valdispose)(VAL)=NULL) + : WDL_AssocArray(WDL_AssocArray::keycmp_ptr, NULL, NULL, valdispose) {} +}; + +struct WDL_Set_DummyRec { }; +template class WDL_Set : public WDL_AssocArrayImpl +{ + public: + explicit WDL_Set(int (*keycmp)(KEY *k1, KEY *k2), + KEY (*keydup)(KEY)=NULL, + void (*keydispose)(KEY)=NULL + ) + : WDL_AssocArrayImpl(keycmp,keydup,keydispose) + { + } + + int Insert(KEY key) + { + WDL_Set_DummyRec r; + return WDL_AssocArrayImpl::Insert(key,r); + } + void AddUnsorted(KEY key) + { + WDL_Set_DummyRec r; + WDL_AssocArrayImpl::AddUnsorted(key,r); + } + + bool Get(KEY key) const + { + return WDL_AssocArrayImpl::Exists(key); + } + bool Enumerate(int i, KEY *key=NULL) + { + return WDL_AssocArrayImpl::EnumeratePtr(i,key) != NULL; + } +}; + +template class WDL_PtrSet : public WDL_Set +{ +public: + explicit WDL_PtrSet() : WDL_Set( WDL_AssocArray::keycmp_ptr ) { } +}; + + + +#endif + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/audiobuffercontainer.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/audiobuffercontainer.cpp new file mode 100644 index 0000000..d68d837 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/audiobuffercontainer.cpp @@ -0,0 +1,243 @@ +#include "audiobuffercontainer.h" +#include "queue.h" +#include + +void ChannelPinMapper::Reset() +{ + for (int i=0; i < CHANNELPINMAPPER_MAXPINS; ++i) + m_mapping[i].set_excl(i); +} + +void ChannelPinMapper::SetNPins(int nPins) +{ + if (nPins<0) nPins=0; + else if (nPins>CHANNELPINMAPPER_MAXPINS) nPins=CHANNELPINMAPPER_MAXPINS; + int i; + for (i = m_nPins; i < nPins; ++i) + { + ClearPin(i); + if (i < m_nCh) + { + SetPin(i, i, true); + } + } + m_nPins = nPins; +} + +void ChannelPinMapper::SetNChannels(int nCh, bool auto_passthru) +{ + if (auto_passthru) for (int i = m_nCh; i < nCh && i < m_nPins; ++i) { + SetPin(i, i, true); + } + m_nCh = nCh; +} + +void ChannelPinMapper::Init(const PinMapPin * pMapping, int nPins) +{ + if (nPins<0) nPins=0; + else if (nPins>CHANNELPINMAPPER_MAXPINS) nPins=CHANNELPINMAPPER_MAXPINS; + memcpy(m_mapping, pMapping, nPins*sizeof(PinMapPin)); + memset(m_mapping+nPins, 0, (CHANNELPINMAPPER_MAXPINS-nPins)*sizeof(PinMapPin)); + m_nPins = m_nCh = nPins; +} + +#define BITMASK64(bitIdx) (((WDL_UINT64)1)<<(bitIdx)) + +void ChannelPinMapper::ClearPin(int pinIdx) +{ + if (pinIdx >=0 && pinIdx < CHANNELPINMAPPER_MAXPINS) m_mapping[pinIdx].clear(); +} + +void ChannelPinMapper::SetPin(int pinIdx, int chIdx, bool on) +{ + if (pinIdx >=0 && pinIdx < CHANNELPINMAPPER_MAXPINS) + { + if (on) + { + m_mapping[pinIdx].set_chan(chIdx); + } + else + { + m_mapping[pinIdx].clear_chan(chIdx); + } + } +} + +bool ChannelPinMapper::TogglePin(int pinIdx, int chIdx) +{ + bool on = GetPin(pinIdx, chIdx); + on = !on; + SetPin(pinIdx, chIdx, on); + return on; +} + +bool ChannelPinMapper::GetPin(int pinIdx, int chIdx) const +{ + if (pinIdx >= 0 && pinIdx < CHANNELPINMAPPER_MAXPINS) + { + return m_mapping[pinIdx].has_chan(chIdx); + } + return false; +} + +bool ChannelPinMapper::IsStraightPassthrough() const +{ + if (m_nCh != m_nPins) return false; + PinMapPin tmp; + tmp.clear(); + for (int i = 0; i < m_nPins; ++i) + { + tmp.set_chan(i); + if (!tmp.equal_to(m_mapping[i])) return false; + tmp.clear_chan(i); + } + return true; +} + +#define PINMAPPER_MAGIC 1000 + +const char *ChannelPinMapper::SaveStateNew(int* pLen) +{ + m_cfgret.Clear(); + int magic = PINMAPPER_MAGIC; + WDL_Queue__AddToLE(&m_cfgret, &magic); + WDL_Queue__AddToLE(&m_cfgret, &m_nCh); + WDL_Queue__AddToLE(&m_cfgret, &m_nPins); + const int num64 = wdl_max(1,(wdl_min(m_nCh,CHANNELPINMAPPER_MAXPINS) + 63)/64); + for (int y = 0; y < num64; y ++) + { + for (int x = 0; x < m_nPins; x ++) + { + const WDL_UINT64 v = m_mapping[x].get_64(y); + WDL_Queue__AddToLE(&m_cfgret, &v); + } + } + *pLen = m_cfgret.GetSize(); + return (const char*)m_cfgret.Get(); +} + +bool ChannelPinMapper::LoadState(const char* buf, int len) +{ + WDL_Queue chunk; + chunk.Add(buf, len); + int* pMagic = WDL_Queue__GetTFromLE(&chunk, (int*)0); + if (!pMagic || *pMagic != PINMAPPER_MAGIC) return false; + int* pNCh = WDL_Queue__GetTFromLE(&chunk, (int*) 0); + int* pNPins = WDL_Queue__GetTFromLE(&chunk, (int*) 0); + if (!pNCh || !pNPins) return false; + const int src_pins = *pNPins; + SetNPins(src_pins); + SetNChannels(*pNCh); + const int num64 = wdl_max(1,(wdl_min(m_nCh,CHANNELPINMAPPER_MAXPINS)+63)/64); + const int maplen = src_pins * sizeof(WDL_UINT64); + for (int y = 0; y < num64; y ++) + { + if (chunk.Available() < maplen) return y>0; + const WDL_UINT64 *pMap = (const WDL_UINT64 *)WDL_Queue__GetDataFromLE(&chunk, maplen, sizeof(WDL_UINT64)); + const int sz = wdl_min(m_nPins,src_pins); + for (int x = 0; x < sz; x ++) + { + m_mapping[x].set_64(pMap[x], y); + } + } + + return true; +} + + +AudioBufferContainer::AudioBufferContainer() +{ + m_nCh = 0; + m_nFrames = 0; + m_fmt = FMT_32FP; + m_interleaved = true; + m_hasData = false; +} + +// converts interleaved buffer to interleaved buffer, using min(len_in,len_out) and zeroing any extra samples +// isInput means it reads from track channels and writes to plugin pins +// wantZeroExcessOutput=false means that untouched channels will be preserved in buf_out +void PinMapperConvertBuffers(const double *buf, int len_in, int nch_in, + double *buf_out, int len_out, int nch_out, + const ChannelPinMapper *pinmap, bool isInput, bool wantZeroExcessOutput) +{ + + if (pinmap->IsStraightPassthrough() || !pinmap->GetNPins()) + { + int x; + char *op = (char *)buf_out; + const char *ip = (const char *)buf; + + const int ip_adv = nch_in * sizeof(double); + + const int clen = wdl_min(nch_in, nch_out) * sizeof(double); + const int zlen = nch_out > nch_in ? (nch_out - nch_in) * sizeof(double) : 0; + + const int cplen = wdl_min(len_in,len_out); + + for (x=0;xGetNPins(),isInput ? nch_out : nch_in); + const int nchan = isInput ? nch_in : nch_out; + + int p; + PinMapPin clearmask; + clearmask.clear(); + for (p = 0; p < npins; p ++) + { + const PinMapPin &map = pinmap->m_mapping[p]; + for (unsigned int x = 0; map.enum_chans(&x,nchan); x ++) + { + int i=len_in; + const double *ip = buf + (isInput ? x : p); + const int out_idx = (isInput ? p : x); + + bool want_zero=false; + if (!wantZeroExcessOutput) + { + if (!clearmask.has_chan(out_idx)) + { + clearmask.set_chan(out_idx); + want_zero=true; + } + } + + double *op = buf_out + out_idx; + + if (want_zero) + { + while (i-- > 0) + { + *op = *ip; + op += nch_out; + ip += nch_in; + } + } + else + { + while (i-- > 0) + { + *op += *ip; + op += nch_out; + ip += nch_in; + } + } + } + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/audiobuffercontainer.h b/plugin-reaper-realearn/main/lib/WDL/WDL/audiobuffercontainer.h new file mode 100644 index 0000000..b11a63c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/audiobuffercontainer.h @@ -0,0 +1,228 @@ +#ifndef _AUDIOBUFFERCONTAINER_ +#define _AUDIOBUFFERCONTAINER_ + +#include "wdltypes.h" +#include +#include +#include "ptrlist.h" +#include "queue.h" + + +#define CHANNELPINMAPPER_MAXPINS 128 + + +struct PinMapPin +{ + enum { PINMAP_PIN_MAX_CHANNELS = CHANNELPINMAPPER_MAXPINS }; + + enum { STATE_ENT_BITS=64, STATE_SIZE=(PINMAP_PIN_MAX_CHANNELS + STATE_ENT_BITS - 1) / STATE_ENT_BITS }; + WDL_UINT64 state[STATE_SIZE]; + static WDL_UINT64 make_mask(unsigned int idx) { return WDL_UINT64_CONST(1) << (idx & (STATE_ENT_BITS-1)); } + static WDL_UINT64 full_mask() { return ~WDL_UINT64_CONST(0); } + + WDL_UINT64 get_64(unsigned int offs=0) const { + return WDL_NORMALLY(offs < STATE_SIZE) ? state[offs] : 0; + } + void set_64(WDL_UINT64 s, unsigned int offs=0) { + if (WDL_NORMALLY(offs < STATE_SIZE)) state[offs]=s; + } + unsigned int get_64_max() const { return STATE_SIZE; } + unsigned int get_64_top(unsigned int minv=0) const { + unsigned int x = STATE_SIZE; + while (x > minv && !state[x-1]) x--; + return x; + } + + void clear() { memset(state,0,sizeof(state)); } + void clear_chan(unsigned int ch) { if (WDL_NORMALLY(ch < PINMAP_PIN_MAX_CHANNELS)) state[ch/STATE_ENT_BITS] &= ~make_mask(ch); } + void set_chan(unsigned int ch) { if (WDL_NORMALLY(ch < PINMAP_PIN_MAX_CHANNELS)) state[ch/STATE_ENT_BITS] |= make_mask(ch); } + void tog_chan(unsigned int ch) { if (WDL_NORMALLY(ch < PINMAP_PIN_MAX_CHANNELS)) state[ch/STATE_ENT_BITS] ^= make_mask(ch); } + void set_chan_lt(unsigned int cnt) + { + if (WDL_NOT_NORMALLY(cnt > PINMAP_PIN_MAX_CHANNELS)) cnt = PINMAP_PIN_MAX_CHANNELS; + for (int x = 0; cnt && x < STATE_SIZE; x ++) + { + if (cnt < STATE_ENT_BITS) { state[x] |= make_mask(cnt)-1; cnt=0; } + else { state[x] = full_mask(); cnt -= STATE_ENT_BITS; } + } + } + void set_excl(unsigned int ch) { clear(); set_chan(ch); } + + bool has_chan(unsigned int ch) const { return WDL_NORMALLY(ch < PINMAP_PIN_MAX_CHANNELS) && (state[ch/STATE_ENT_BITS] & make_mask(ch)); } + bool has_chan_lt(unsigned int cnt) const + { + if (WDL_NOT_NORMALLY(cnt > PINMAP_PIN_MAX_CHANNELS)) cnt = PINMAP_PIN_MAX_CHANNELS; + for (int x = 0; cnt && x < STATE_SIZE; x ++) + { + if (cnt < STATE_ENT_BITS) return (state[x] & (make_mask(cnt)-1)); + if (state[x]) return true; + cnt -= STATE_ENT_BITS; + } + return false; + } + + // call with 0, then increment after each call (returns false when done) + bool enum_chans(unsigned int *ch, unsigned int maxch=PINMAP_PIN_MAX_CHANNELS) const + { + if (WDL_NOT_NORMALLY(maxch > PINMAP_PIN_MAX_CHANNELS)) + maxch = PINMAP_PIN_MAX_CHANNELS; + + unsigned int x = *ch; + if (x >= maxch) return false; + + WDL_UINT64 s = state[x / STATE_ENT_BITS] >> (x & (STATE_ENT_BITS-1)); + for (;;) + { + if (s) + { + do + { + if (s&1) { *ch = x; return true; } + s>>=1; + x++; + WDL_ASSERT(x & (STATE_ENT_BITS-1)); // we should never run out of bits! + } + while (x < maxch); + break; + } + x = (x & ~(STATE_ENT_BITS-1)) + STATE_ENT_BITS; + if (x >= maxch) break; + s = state[x / STATE_ENT_BITS]; + } + + *ch = x; + return false; + } + + PinMapPin & operator |= (const PinMapPin &v) + { + for (int x = 0; x < STATE_SIZE; x ++) state[x]|=v.state[x]; + return *this; + } + + PinMapPin & operator &= (const PinMapPin &v) + { + for (int x = 0; x < STATE_SIZE; x ++) state[x]&=v.state[x]; + return *this; + } + + void invert() + { + for (int x = 0; x < STATE_SIZE; x ++) state[x]^=full_mask(); + } + + bool equal_to(const PinMapPin &v, unsigned int nch_top = PINMAP_PIN_MAX_CHANNELS) const + { + if (WDL_NOT_NORMALLY(nch_top > PINMAP_PIN_MAX_CHANNELS)) nch_top = PINMAP_PIN_MAX_CHANNELS; + for (unsigned int x = 0; x < nch_top; x += STATE_ENT_BITS) + { + if ((v.state[x/STATE_ENT_BITS]^state[x/STATE_ENT_BITS]) & + (((nch_top-x) < STATE_ENT_BITS) ? (make_mask(nch_top-x)-1) : full_mask())) + return false; + } + return true; + } +}; + + +class ChannelPinMapper +{ +public: + + ChannelPinMapper() : m_nCh(0), m_nPins(0) { Reset(); } + ~ChannelPinMapper() {} + + void SetNPins(int nPins); + void SetNChannels(int nCh, bool auto_passthru=true); + // or ... + void Init(const PinMapPin * pMapping, int nPins); + // or ... + void Reset(); // set to full passthrough + + int GetNPins() const { return m_nPins; } + int GetNChannels() const { return m_nCh; } + + void ClearPin(int pinIdx); + void SetPin(int pinIdx, int chIdx, bool on); + bool TogglePin(int pinIdx, int chIdx); + + // true if this pin is mapped to this channel + bool GetPin(int pinIdx, int chIdx) const; + + // true if this mapper is a straight 1:1 passthrough + bool IsStraightPassthrough() const; + + const char *SaveStateNew(int* pLen); // owned + bool LoadState(const char* buf, int len); + + PinMapPin m_mapping[CHANNELPINMAPPER_MAXPINS]; + int m_nCh, m_nPins; + +private: + + WDL_Queue m_cfgret; +}; + +// converts interleaved buffer to interleaved buffer, using min(len_in,len_out) and zeroing any extra samples +// isInput means it reads from track channels and writes to plugin pins +// wantZeroExcessOutput=false means that untouched channels will be preserved in buf_out +void PinMapperConvertBuffers(const double *buf, int len_in, int nch_in, + double *buf_out, int len_out, int nch_out, + const ChannelPinMapper *pinmap, bool isInput, bool wantZeroExcessOutput); + +// use for float and double only ... ints will break it +class AudioBufferContainer +{ +public: + + AudioBufferContainer(); + ~AudioBufferContainer() {} + + enum + { + FMT_32FP=4, + FMT_64FP=8 + }; + + static bool BufConvert(void* dest, const void* src, int destFmt, int srcFmt, int nFrames, int destStride, int srcStride); + + int GetNChannels() const { return m_nCh; } + int GetNFrames() const { return m_nFrames; } + int GetFormat() const { return m_fmt; } + + void Resize(int nCh, int nFrames, bool preserveData); + // call Reformat(GetFormat(), false) to discard current data (for efficient repopulating) + void Reformat(int fmt, bool preserveData); + + // src=NULL to memset(0) + void* SetAllChannels(int fmt, const void* src, int nCh, int nFrames); + + // src=NULL to memset(0) + void* SetChannel(int fmt, const void* src, int chIdx, int nFrames); + + void* MixChannel(int fmt, const void* src, int chIdx, int nFrames, bool addToDest, double wt_start, double wt_end); + + void* GetAllChannels(int fmt, bool preserveData); + void* GetChannel(int fmt, int chIdx, bool preserveData); + + void CopyFrom(const AudioBufferContainer* rhs); + +private: + + void ReLeave(bool interleave, bool preserveData); + + WDL_HeapBuf m_data; + int m_nCh; + int m_nFrames; + + int m_fmt; + bool m_interleaved; + bool m_hasData; +} WDL_FIXALIGN; + + +void SetPinsFromChannels(AudioBufferContainer* dest, AudioBufferContainer* src, const ChannelPinMapper* mapper, int forceMinChanCnt=0); +void SetChannelsFromPins(AudioBufferContainer* dest, AudioBufferContainer* src, const ChannelPinMapper* mapper, double wt_start=1.0, double wt_end=1.0); + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/bitfield.h b/plugin-reaper-realearn/main/lib/WDL/WDL/bitfield.h new file mode 100644 index 0000000..bd08514 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/bitfield.h @@ -0,0 +1,34 @@ +#ifndef _WDL_BITFIELD_H_ +#define _WDL_BITFIELD_H_ + +#include "heapbuf.h" + +class WDL_BitField // ultra simple bit field +{ +public: + bool SetSize(int sz) // clears state + { + void *b=m_hb.ResizeOK((sz+7)/8); + if (b) memset(b,0,m_hb.GetSize()); + return !!b; + } + int GetApproxSize() const { return m_hb.GetSize()*8; } // may return slightly greater than the size set + + bool IsSet(unsigned int idx) const + { + const unsigned char mask = 1<<(idx&7); + idx>>=3; + return idx < (unsigned int)m_hb.GetSize() && (((unsigned char *)m_hb.Get())[idx]&mask); + } + void Set(unsigned int idx) + { + const unsigned char mask = 1<<(idx&7); + idx>>=3; + if (idx < (unsigned int)m_hb.GetSize()) ((unsigned char *)m_hb.Get())[idx] |= mask; + } + +private: + WDL_HeapBuf m_hb; +}; + +#endif //_WDL_BITFIELD_H_ \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/chunkalloc.h b/plugin-reaper-realearn/main/lib/WDL/WDL/chunkalloc.h new file mode 100644 index 0000000..a4aa5b5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/chunkalloc.h @@ -0,0 +1,103 @@ +#ifndef _WDL_CHUNKALLOC_H_ +#define _WDL_CHUNKALLOC_H_ + +#include "wdltypes.h" + +class WDL_ChunkAlloc +{ + struct _hdr + { + struct _hdr *_next; + char data[16]; + }; + + _hdr *m_chunks; + int m_chunksize, m_chunkused; + + public: + + WDL_ChunkAlloc(int chunksize=65500) { m_chunks=NULL; m_chunkused=0; m_chunksize=chunksize>16?chunksize:16; } + ~WDL_ChunkAlloc() { Free(); } + + void Free() + { + _hdr *a = m_chunks; + m_chunks=0; + m_chunkused=0; + while (a) { _hdr *f=a; a=a->_next; free(f); } + } + + void *Alloc(int sz, int align=0) + { + if (sz<1) return NULL; + + if (align < 1 || (align & (align-1))) align=1; + + if (m_chunks) + { + int use_sz=sz; + char *p = m_chunks->data + m_chunkused; + int a = ((int) (INT_PTR)p) & (align-1); + if (a) + { + use_sz += align-a; + p += align-a; + } + if (use_sz <= m_chunksize - m_chunkused) + { + m_chunkused += use_sz; + return p; + } + } + + // we assume that malloc always gives at least 8 byte alignment, and our _next ptr may offset that by 4, + // so no need to allocate extra if less than 4 bytes of alignment requested + int use_align = (align>=4 ? align : 0); + int alloc_sz=sz+use_align; + if (alloc_sz < m_chunksize) + { + // if existing chunk has less free space in it than we would at chunksize, allocate chunksize + if (!m_chunks || m_chunkused > alloc_sz) alloc_sz=m_chunksize; + } + _hdr *nc = (_hdr *)malloc(sizeof(_hdr) + alloc_sz - 16); + if (!nc) return NULL; + + int use_sz=sz; + char *ret = nc->data; + int a = ((int) (INT_PTR)ret) & (align-1); + if (a) + { + use_sz += align-a; + ret += align-a; + } + + if (m_chunks && (m_chunksize-m_chunkused) >= (alloc_sz - use_sz)) + { + // current chunk has as much or more free space than our chunk, put our chunk on the list second + nc->_next = m_chunks->_next; + m_chunks->_next=nc; + } + else + { + // push our chunk to the top of the list + nc->_next = m_chunks; + m_chunks=nc; + m_chunkused = alloc_sz >= m_chunksize ? use_sz : m_chunksize; + } + + return ret; + } + + char *StrDup(const char *s) + { + if (!s) return NULL; + const int l = (int) strlen(s)+1; + char *ret = (char*)Alloc(l); + if (!ret) return NULL; + memcpy(ret,s,l); + return ret; + } + +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/circbuf.h b/plugin-reaper-realearn/main/lib/WDL/WDL/circbuf.h new file mode 100644 index 0000000..25f8165 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/circbuf.h @@ -0,0 +1,275 @@ +/* + WDL - circbuf.h + Copyright (C) 2005 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. + +*/ + +/* + + This file provides a simple class for a circular FIFO queue of bytes. + +*/ + +#ifndef _WDL_CIRCBUF_H_ +#define _WDL_CIRCBUF_H_ + +#include "heapbuf.h" + +class WDL_CircBuf +{ +public: + WDL_CircBuf() + { + m_inbuf = m_wrptr = 0; + m_buf = NULL; + m_alloc = 0; + } + ~WDL_CircBuf() + { + free(m_buf); + } + void SetSize(int size) + { + if (size<0) size=0; + m_inbuf = m_wrptr = 0; + if (size != m_alloc || !m_buf) + { + m_alloc = size; + free(m_buf); + m_buf = size ? (char*)malloc(size) : NULL; + } + } + + void SetSizePreserveContents(int newsz) + { + if (newsz < NbInBuf()) newsz = NbInBuf(); // do not allow destructive resize down + const int oldsz = m_alloc, dsize = newsz - oldsz; + if (!dsize) return; + if (!m_inbuf||!m_buf) { SetSize(newsz); return; } + + const int div1 = m_inbuf - m_wrptr; // div1>0 is size of end block, div1<0 is offset of start block + char *buf=NULL; + if (dsize > 0) + { + buf = (char *)realloc(m_buf, newsz); + if (WDL_NORMALLY(buf) && div1 > 0) // block crossing loop, need to shuffle some data + { + if (div1 > m_wrptr) // m_wrptr is size of start block, div1 is size of end block + { + // end block is larger than start, move some of start block to end of end block and shuffle forward start + if (dsize >= m_wrptr) + { + if (m_wrptr>0) memmove(buf+oldsz,buf,m_wrptr); + m_wrptr += oldsz; + } + else + { + memmove(buf + oldsz, buf, dsize); + m_wrptr -= dsize; + memmove(buf, buf+dsize, m_wrptr); + } + } + else // end block is smaller, move it to the new end of buffer + { + memmove(buf + newsz - div1, buf + oldsz - div1, div1); + } + } + } + else if (div1 < 0) // shrinking, and not a wrapped buffer + { + if (m_wrptr > newsz) + { + memmove(m_buf,m_buf-div1, m_inbuf); + m_wrptr = m_inbuf; + } + buf = (char *)realloc(m_buf, newsz); + } + + if (!buf) // failed realloc(), or sizing down with block crossing loop boundary + { + buf = (char *)malloc(newsz); + if (WDL_NOT_NORMALLY(!buf)) return; + const int peeked = Peek(buf,0,m_inbuf); + if (peeked != m_inbuf) { WDL_ASSERT(peeked == m_inbuf); } + free(m_buf); + m_wrptr = m_inbuf = peeked; + } + if (m_wrptr > newsz) { WDL_ASSERT(m_wrptr <= newsz); } + if (m_wrptr >= newsz) m_wrptr=0; + m_alloc = newsz; + m_buf = buf; + } + void Reset() { m_inbuf = m_wrptr = 0; } + int Add(const void *buf, int l) + { + if (!m_buf) return 0; + const int bf = m_alloc - m_inbuf; + if (l>bf) l = bf; + if (l > 0) + { + m_wrptr = __write_bytes(m_wrptr,l,buf); + m_inbuf += l; + } + return l; + } + void UnAdd(int amt) + { + if (amt > 0) + { + if (amt > m_inbuf) amt=m_inbuf; + m_wrptr -= amt; + if (m_wrptr < 0) m_wrptr += m_alloc; + m_inbuf -= amt; + } + } + + void Skip(int l) // can be used to rewind read pointer + { + m_inbuf -= l; + if (m_inbuf<0) m_inbuf=0; + else if (m_inbuf>m_alloc) m_inbuf=m_alloc; + } + + int Peek(void *buf, int offs, int len) const + { + if (offs<0||!m_buf) return 0; + const int ibo = m_inbuf-offs; + if (len > ibo) len = ibo; + if (len > 0) + { + int rp = m_wrptr - ibo; + if (rp < 0) rp += m_alloc; + const int wr1 = m_alloc - rp; + char * const rd = m_buf; + if (wr1 < len) + { + memcpy(buf,rd+rp,wr1); + memcpy((char*)buf+wr1,rd,len-wr1); + } + else + { + memcpy(buf,rd+rp,len); + } + } + return len; + } + + void WriteAtReadPointer(const void *buf, int len, int offs=0) + { + if (WDL_NOT_NORMALLY(offs<0) || WDL_NOT_NORMALLY(offs>=m_inbuf)) return; + if (!m_buf || len<1) return; + if (offs+len > m_inbuf) len = m_inbuf-offs; + + int write_offs = m_wrptr - m_inbuf + offs; + if (write_offs < 0) write_offs += m_alloc; + __write_bytes(write_offs, len, buf); + } + + int Get(void *buf, int l) + { + const int amt = Peek(buf,0,l); + m_inbuf -= amt; + return amt; + } + int NbFree() const { return m_alloc - m_inbuf; } // formerly Available() + int NbInBuf() const { return m_inbuf; } + int GetTotalSize() const { return m_alloc; } + +private: + int __write_bytes(int wrptr, int l, const void *buf) // no bounds checking, return end offset + { + const int wr1 = m_alloc-wrptr; + char * const p = m_buf, * const pw = p + wrptr; + if (wr1 < l) + { + if (buf) + { + memcpy(pw, buf, wr1); + memcpy(p, (char*)buf + wr1, l-wr1); + } + else + { + memset(pw, 0, wr1); + memset(p, 0, l-wr1); + } + return l-wr1; + } + + if (buf) memcpy(pw, buf, l); + else memset(pw, 0, l); + return wr1 == l ? 0 : wrptr+l; + } + + char *m_buf; + int m_inbuf, m_wrptr,m_alloc; +} WDL_FIXALIGN; + + +template +class WDL_TypedCircBuf +{ +public: + + WDL_TypedCircBuf() {} + ~WDL_TypedCircBuf() {} + + void SetSize(int size) + { + mBuf.SetSize(size * sizeof(T)); + } + void SetSizePreserveContents(int size) + { + mBuf.SetSizePreserveContents(size*sizeof(T)); + } + + void Reset() + { + mBuf.Reset(); + } + + void UnAdd(int l) { mBuf.UnAdd(l*sizeof(T)); } + + int Add(const T* buf, int l) + { + return mBuf.Add(buf, l * sizeof(T)) / sizeof(T); + } + + int Get(T* buf, int l) + { + return mBuf.Get(buf, l * sizeof(T)) / sizeof(T); + } + + int Peek(T* buf, int offs, int l) + { + return mBuf.Peek(buf, offs*sizeof(T), l * sizeof(T)) / sizeof(T); + } + void Skip(int l) { mBuf.Skip(l*sizeof(T)); } + + void WriteAtReadPointer(const void *buf, int len, int offs=0) { mBuf.WriteAtReadPointer(buf,len*sizeof(T),offs*sizeof(T)); } + + int NbFree() const { return mBuf.NbFree() / sizeof(T); } // formerly Available() + int ItemsInQueue() const { return mBuf.NbInBuf() / sizeof(T); } + int NbInBuf() const { return mBuf.NbInBuf() / sizeof(T); } + int GetTotalSize() const { return mBuf.GetTotalSize() / sizeof(T); } + +private: + WDL_CircBuf mBuf; +} WDL_FIXALIGN; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/bessel_polynomial.h b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/bessel_polynomial.h new file mode 100644 index 0000000..030d32b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/bessel_polynomial.h @@ -0,0 +1,75 @@ +/* + bessel_polynomial.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + algorithm to calculate coefficients for a bessel polynomial from krall & fink + series. +*/ + +#ifndef _BESSEL_POLYNOMIAL_H_ +#define _BESSEL_POLYNOMIAL_H_ + +#include "custom_math.h" +#include "factorial.h" + +#ifdef _BESSEL_USE_INLINE_ + #define _BESSEL_INLINE _CMATH_INLINE +#else + #define _BESSEL_INLINE +#endif + +#ifndef _CMATH_ANSI + #define _BESSEL_MAX_ORDER 10 +#else + #define _BESSEL_MAX_ORDER 3 +#endif + +/* return a coefficient */ +_BESSEL_INLINE cmath_std_int_t +bessel_coefficient(const cmath_uint16_t k, const cmath_uint16_t n) +{ + register cmath_std_int_t c; + const cmath_uint16_t nmk = (cmath_uint16_t)(n - k); + c = factorial(2*n - k); + c /= (factorial(nmk)*factorial(k)) * (1 << nmk); + return c; +} + +/* calculate all coefficients for n-th order polynomial */ +_BESSEL_INLINE +void bessel_polynomial( cmath_std_int_t *coeff, + const cmath_uint16_t order, + const cmath_uint16_t reverse ) +{ + register cmath_uint16_t i = (cmath_uint16_t)(order + 1); + if (reverse) + { + while (i--) + coeff[order-i] = bessel_coefficient(i, order); + } + else + { + while (i--) + coeff[i] = bessel_coefficient(i, order); + } +} + +#endif /* _BESSEL_POLYNOMIAL_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/complex_number.h b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/complex_number.h new file mode 100644 index 0000000..c5be193 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/complex_number.h @@ -0,0 +1,368 @@ +/* + complex_number.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + portable complex number operations +*/ + +#ifndef _COMPLEX_NUMBER_H_ +#define _COMPLEX_NUMBER_H_ + +#include "custom_math.h" + +/* settings */ +#ifndef _CNUM_NO_INLINE + #define _CNUM_INLINE _CMATH_INLINE +#else + #define _CNUM_INLINE +#endif + +#ifndef _CNUM_NO_ALIAS + #define _CNUM_ALIAS _CMATH_MAY_ALIAS +#else + #define _CNUM_ALIAS +#endif + +/* types & constants */ +#ifndef cnum_t + #define cnum_t cmath_t +#endif + +typedef struct +{ + cnum_t r _CMATH_ALIGN(8); + cnum_t i _CMATH_ALIGN(8); +} _CNUM_ALIAS cnum_s; + +const cnum_s cnum_zero = {0, 0}; +const cnum_s cnum_i1 = {0, 1}; +const cnum_s cnum_r1 = {1, 0}; +const cnum_s cnum_r2 = {2, 0}; + +/* methods */ +#define _CNUM(r, i) cnum_new(r, i) +#define _CNUMD(x, r, i) cnum_s x = {r, i} + +_CNUM_INLINE +cnum_s cnum_set(cnum_s *x, const cnum_t r, const cnum_t i) +{ + x->r = r; + x->i = i; + return *x; +} + +_CNUM_INLINE +cnum_s cnum_from(cnum_s *x, const cnum_s y) +{ + x->r = y.r; + x->i = y.i; + return *x; +} + +_CNUM_INLINE +cnum_s cnum_new(const cnum_t r, const cnum_t i) +{ + cnum_s x; + x.r = r; + x.i = i; + return x; +} + +_CNUM_INLINE +cnum_s cnum_cartesian(const cnum_s x) +{ + return cnum_new(x.r * cmath_cos(x.i), x.r * cmath_sin(x.i)); +} + +_CNUM_INLINE +cnum_s cnum_polar(const cnum_s x) +{ + return cnum_new(cmath_cabs(x.r, x.i), cmath_carg(x.r, x.i)); +} + +_CNUM_INLINE +cnum_s cnum_conjugate(const cnum_s x) +{ + return cnum_new(x.r, -x.i); +} + +_CNUM_INLINE +cnum_s cnum_negative(const cnum_s x) +{ + return cnum_new(-x.r, -x.i); +} + +_CNUM_INLINE +cnum_s cnum_swap(const cnum_s x) +{ + return cnum_new(x.i, x.r); +} + +_CNUM_INLINE +cnum_s cnum_add(const cnum_s x, const cnum_s y) +{ + return cnum_new(x.r + y.r, x.i + y.i); +} + +_CNUM_INLINE +cnum_s cnum_add_r(const cnum_s x, const cnum_t y) +{ + return cnum_new(x.r + y, x.i + y); +} + +_CNUM_INLINE +cnum_s cnum_sub(const cnum_s x, const cnum_s y) +{ + return cnum_new(x.r - y.r, x.i - y.i); +} + +_CNUM_INLINE +cnum_s cnum_sub_r(register cnum_s x, const cnum_t y) +{ + return cnum_new(x.r - y, x.i - y); +} + +_CNUM_INLINE +cnum_s cnum_r_sub(const cnum_t x, register cnum_s y) +{ + return cnum_new(x - y.r, x - y.i); +} + +_CNUM_INLINE +cnum_s cnum_mul(const cnum_s x, const cnum_s y) +{ + return cnum_new(x.r*y.r - x.i*y.i, x.r*y.i + x.i*y.r); +} + +_CNUM_INLINE +cnum_s cnum_mul_r(const cnum_s x, const cnum_t y) +{ + return cnum_new(x.r*y, x.i*y); +} + +#define cnum_sqr(x) \ + cnum_mul(x, x) + +_CNUM_INLINE +cnum_s cnum_div_r(const cnum_s x, const cnum_t y) +{ + return cnum_new(x.r/y, x.i/y); +} + +_CNUM_INLINE +cnum_s cnum_r_div(const cnum_t x, const cnum_s y) +{ + return cnum_new(x/y.r, x/y.i); +} + +_CNUM_INLINE +cnum_s cnum_div(const cnum_s x, const cnum_s y) +{ + return cnum_div_r(cnum_mul(x, cnum_conjugate(y)), + (y.r*y.r + cmath_abs(y.i*y.i))); +} + +#define cnum_inv(x) \ + cnum_div(cnum_r1, x) + +#define _CNUM_CHECK_EXP_D_ \ + cmath_abs(deg - cmath_round(deg)) == 0 + +_CNUM_INLINE +cnum_s cnum_exp(const cnum_s x) +{ + cnum_t sin_i = cmath_sin(x.i); + cnum_t cos_i = cmath_cos(x.i); + const cnum_t exp_r = cmath_exp(x.r); + + #ifndef _CNUM_NO_CHECK_EXP_ + register cnum_t deg; + + if (x.r == 0) + return cnum_zero; + deg = x.i / cmath_pi; + if (_CNUM_CHECK_EXP_D_) + sin_i = 0; + deg += 0.5; + if (_CNUM_CHECK_EXP_D_) + cos_i = 0; + deg = x.i / cmath_pi2; + if (_CNUM_CHECK_EXP_D_) + cos_i = 1; + #endif + + return cnum_new(exp_r*cos_i, exp_r*sin_i); +} + +_CNUM_INLINE +cnum_s cnum_log_k(const cnum_s x, const cmath_int32_t k) +{ + return cnum_new(cmath_log(cmath_cabs(x.r, x.i)), + (cmath_carg(x.r, x.i) + (cmath_pi2*k))); +} + +#define cnum_log(x) \ + cnum_log_k(x, 0) + +#define cnum_log_b_k(x, b, k) \ + cnum_div(cnum_log_k(x, k), cnum_log_k(b, k)) + +#define cnum_log_b(b, x) \ + cnum_div(cnum_log(x), cnum_log(b)) + +#define cnum_log2(x) \ + cnum_log_b(x, 2) + +#define cnum_log2_k(x, k) \ + cnum_log_b_k(x, 2, k) + +#define cnum_log10(x) \ + cnum_log_b(x, 2) + +#define cnum_log10_k(x, k) \ + cnum_log_b_k(x, 10, k) + +#define _CNUM_CHECK_POW_C_ \ + if (x.r == 0 && x.i == 0) \ + return cnum_zero; \ + if (y.r == 0 && y.i == 0) \ + return cnum_r1 \ + +_CNUM_INLINE +cnum_s cnum_pow_c_k(const cnum_s x, const cnum_s y, const cmath_int32_t k) +{ + _CNUM_CHECK_POW_C_; + return cnum_exp(cnum_mul(cnum_log_k(x, k), y)); +} + +_CNUM_INLINE +cnum_s cnum_pow_c(const cnum_s x, const cnum_s y) +{ + _CNUM_CHECK_POW_C_; + return cnum_exp(cnum_mul(cnum_log(x), y)); +} + +_CNUM_INLINE +cnum_s cnum_pow(const cnum_s x, const cnum_t n) +{ + const cnum_t r_pow_n = cmath_pow(cmath_cabs(x.r, x.i), n); + const cnum_t theta_n = cmath_carg(x.r, x.i) * n; + if (n == 0) + return cnum_new(1, 0); + if (n == 1) + return x; + return cnum_new(r_pow_n * cmath_cos(theta_n), r_pow_n * cmath_sin(theta_n)); +} + +#define cnum_root_c_k(x, y, k) \ + cnum_exp(cnum_div(cnum_log_k(x, k), y)) + +#define cnum_root_c(x, y) \ + cnum_exp(cnum_div(cnum_log(x), y)) + +#define cnum_root(x, n) \ + cnum_pow(x, 1/n) + +#define cnum_sqrt(x) \ + cnum_pow(x, 0.5) + +#define cnum_sin(x) \ + cnum_new(cmath_sin((x).r)*cmath_cosh((x).i), \ + cmath_cos((x).r)*cmath_sinh((x).i)) + +#define cnum_sinh(x) \ + cnum_new(cmath_sinh(x.r)*cmath_cos(x.i), cmath_cosh(x.r)*sin(x.i)) + +#define cnum_cos(x) \ + cnum_new(cmath_cos(x.r)*cmath_cosh(x.i), -cmath_sin(x.r)*cmath_sinh(x.i)) + +#define cnum_cosh(x) \ + cnum_new(cmath_cosh(x.r)*cmath_cos(x.i), cmath_sinh(x.r)*cmath_sin(x.i)) + +#define cnum_tan(x) \ + cnum_div(cnum_sin(x), cnum_cos(x)) + +#define cnum_tanh(x) \ + cnum_div(cnum_sinh(x), cnum_cosh(x)) + +#define cnum_csc(x) \ + cnum_inv(cnum_sin(x)) + +#define cnum_sec(x) \ + cnum_inv(cnum_cos(x)) + +#define cnum_cotan(x) \ + cnum_inv(cnum_tan(x)) + +#define cnum_asin(x) \ + cnum_negative(cnum_mul(cnum_i1, cnum_log(cnum_add(cnum_mul(cnum_i1, x), \ + cnum_sqrt(cnum_sub(cnum_r1, cnum_sqr(x))))))) + +#define cnum_acos(x) \ + cnum_negative(cnum_mul(cnum_i1, cnum_log(cnum_add(x, cnum_mul(cnum_i1, \ + cnum_sqrt(cnum_sub(cnum_r1, cnum_sqr(x)))))))) + +#define cnum_atan(x) \ + cnum_div(cnum_mul(cnum_i1, cnum_log(cnum_div(cnum_sub(cnum_r1, \ + cnum_mul(cnum_i1, x)), cnum_add(cnum_r1, cnum_mul(cnum_i1, x))))), cnum_r2) + +#define cnum_acsc(x) \ + cnum_asin(cnum_inv(x)) + +#define cnum_asec(x) \ + cnum_acos(cnum_inv(x)) + +#define cnum_acot(x) \ + cnum_atan(cnum_inv(x)) + +#define cnum_csch(x) \ + cnum_inv(cnum_sinh(x)) + +#define cnum_sech(x) \ + cnum_inv(cnum_cosh(x)) + +#define cnum_coth(x) \ + cnum_inv(cnum_tanh(x)) + +#define cnum_asinh(x) \ + cnum_log(cnum_add(x, cnum_sqrt(cnum_add(cnum_r1, cnum_sqr(x))))) + +#define cnum_acosh(x) \ + cnum_log(cnum_add(x, cnum_mul(cnum_sqrt(cnum_add(x, cnum_r1)), \ + cnum_sqrt(cnum_sub(x, cnum_r1))))) + +#define cnum_atanh(x) \ + cnum_div(cnum_sub(cnum_log(cnum_add(cnum_r1, x)), \ + cnum_log(cnum_sub(cnum_r1, x))), cnum_r2) + +#define cnum_acsch(x) \ + cnum_asinh(cnum_inv(x)) + +#define cnum_asech(x) \ + cnum_acosh(cnum_inv(x)) + +#define cnum_asech(x) \ + cnum_acosh(cnum_inv(x)) + +#define cnum_acoth(x) \ + cnum_atanh(cnum_inv(x)) + +#endif /* _COMPLEX_NUMBER_H_ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/custom_math.h b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/custom_math.h new file mode 100644 index 0000000..a624b3a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/custom_math.h @@ -0,0 +1,209 @@ +/* + custom_math.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + portable definitions for ansi c and cross compiler compatibility. + contains: numeric constants, custom math functions, macros & other. +*/ + +#ifndef _CUSTOM_MATH_H_ +#define _CUSTOM_MATH_H_ + +#include "math.h" + +/* check for "c89" mode */ +#if (defined _MSC_VER && defined __STDC__) || \ + (defined __GNUC__ && defined __STRICT_ANSI__) + #define _CMATH_ANSI +#endif + +/* enable inline */ +#if defined __cplusplus || (!defined _CMATH_ANSI && defined _CMATH_USE_INLINE) + #ifdef _MSC_VER + #define _CMATH_INLINE __inline + #else + #define _CMATH_INLINE inline + #endif +#else + #define _CMATH_INLINE +#endif + +/* align type to size of type */ +#if defined __GNUC__ || defined __TINYC__ + #define _CMATH_ALIGN(x) __attribute__ ((aligned(x))) + #define _CMATH_ALIGN_T(x) __attribute__ ((aligned(sizeof(x)))) +#else + #define _CMATH_ALIGN(x) + #define _CMATH_ALIGN_T(x) +#endif + +/* printf max integer */ +#ifndef _CMATH_ANSI + #ifdef _WIN32 + #define _CMATH_PR_STD_UINT "I64u" + #define _CMATH_PR_STD_INT "I64i" + #define _CMATH_PR_STD_HEX "I64x" + #else + #define _CMATH_PR_STD_UINT "llu" + #define _CMATH_PR_STD_INT "lli" + #define _CMATH_PR_STD_HEX "llx" + #endif +#else + #define _CMATH_PR_STD_UINT "u" + #define _CMATH_PR_STD_INT "d" + #define _CMATH_PR_STD_HEX "x" +#endif + +/* msvc specifics */ +#ifdef _MSC_VER + #pragma warning(disable : 4514) + + #define MK_L(x) (x) + #define MK_UL(x) (x) + #define MK_LL(x) (x) + #define MK_ULL(x) (x) +#else + #define MK_L(x) (x##L) + #define MK_UL(x) (x##UL) + #ifdef _CMATH_ANSI + #define MK_LL(x) (x##L) + #define MK_ULL(x) (x##UL) + #else + #define MK_LL(x) (x##LL) + #define MK_ULL(x) (x##ULL) + #endif +#endif + +/* definitions depending on c standard */ +#ifdef _CMATH_ANSI + #define cmath_std_signbit MK_UL(0x7fffffff) + #define cmath_std_float_t float + #define cmath_std_int_t int +#else + #define cmath_std_signbit MK_ULL(0x7fffffffffffffff) + #define cmath_std_float_t double + #ifdef _MSC_VER + #define cmath_std_int_t __int64 + #else + #define cmath_std_int_t long long + #endif +#endif + +/* types and constants */ +#ifndef cmath_t + #define cmath_t double +#endif + +#define cmath_std_uint_t unsigned cmath_std_int_t + +#define cmath_pi 3.1415926535897932384626433832795 +#define cmath_pi2 6.2831853071795864769252867665590 +#define cmath_pi_2 1.5707963267948966192313216916398 +#define cmath_e 2.7182818284590452353602874713526 +#define cmath_sqrt2 1.4142135623730950488016887242097 +#define cmath_pi_180 0.0174532925199432957692369076848 +#define cmath_180_pi 57.295779513082320876798154814105 + +#define cmath_int8_t char +#define cmath_uint8_t unsigned char +#define cmath_int16_t short +#define cmath_uint16_t unsigned short +#define cmath_int32_t int +#define cmath_uint32_t unsigned int + +/* aliased types */ +#ifdef __GNUC__ + #define _CMATH_MAY_ALIAS __attribute__((__may_alias__)) +#else + #define _CMATH_MAY_ALIAS +#endif + +typedef cmath_t _CMATH_MAY_ALIAS cmath_t_a; + +/* possible approximations */ +#define cmath_sin sin +#define cmath_cos cos +#define cmath_tan tan +#define cmath_asin asin +#define cmath_acos acos +#define cmath_atan atan +#define cmath_atan2 atan2 +#define cmath_sinh sinh +#define cmath_cosh cosh +#define cmath_tanh tanh +#define cmath_exp exp +#define cmath_pow pow +#define cmath_sqrt sqrt +#define cmath_log log +#define cmath_log2 log2 +#define cmath_log10 log10 + +/* methods */ +#define cmath_array_size(x) \ + (sizeof(x) / sizeof(*(x))) + +#define poly_order(x) \ + (sizeof(x) / sizeof(*(x)) - 1) + +#define cmath_cabs(a, b) \ + cmath_sqrt((a)*(a) + (b)*(b)) + +#define cmath_carg(a, b) \ + cmath_atan2((b), (a)) + +#define cmath_radians(x) \ + ((x)*cmath_pi_180) + +#define cmath_degrees(x) \ + ((x)*cmath_180_pi) + +_CMATH_INLINE +cmath_t cmath_powi(const cmath_t x, register cmath_uint16_t n) +{ + register cmath_t result = 1; + while (n--) + result *= x; + return result; +} + +_CMATH_INLINE +cmath_t cmath_abs(const cmath_t x) +{ + register union + { + cmath_std_int_t i; + cmath_std_float_t j; + } u; + u.j = (cmath_std_float_t)x; + u.i &= cmath_std_signbit; + return u.j; +} + +_CMATH_INLINE +cmath_t cmath_round(const cmath_t x) +{ + if (x < 0.0) + return (cmath_t)(cmath_std_int_t)(x - 0.5); + else + return (cmath_t)(cmath_std_int_t)(x + 0.5); +} + +#endif /* _CUSTOM_MATH_H_ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/durand_kerner.h b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/durand_kerner.h new file mode 100644 index 0000000..36572fc --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/durand_kerner.h @@ -0,0 +1,117 @@ +/* + durand_kerner.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + durand-kerner (weierstrass) algorithm for finding complex roots + of polynomials. + accuracy depends a lot on data type precision. +*/ + +#ifndef _DURAND_KERNER_H_ +#define _DURAND_KERNER_H_ + +#include "horner.h" +#include "custom_math.h" +#include "complex_number.h" + +/* settings */ +#ifdef _DURAND_KERNER_USE_INLINE_ + #define _DURAND_KERNER_INLINE _CMATH_INLINE +#else + #define _DURAND_KERNER_INLINE +#endif + +#define DK_EPSILON 1E-16 +#define DK_MAX_ITR 1E+3 +#define DK_MAX_N 256 + +const cnum_s dk_demoivre_c = {0.4, 0.9}; + +/* accepts an array of complex numbers */ +_DURAND_KERNER_INLINE +void durand_kerner_c +(const cnum_s *coeff, cnum_s *roots, const cmath_uint16_t order) +{ + register cmath_uint16_t i, j; + register cmath_uint32_t itr; + cnum_s coeff_sc[DK_MAX_N]; + cnum_s x; + cnum_s hor; /* needs an address or breaks g++ 4.x */ + + i = 0; + while(i < order) + { + cnum_from(&roots[i], cnum_pow(dk_demoivre_c, i)); + i++; + } + + cnum_from(&coeff_sc[0], cnum_r1); + i = 1; + while(i < order+1) + { + cnum_from(&coeff_sc[i], cnum_div(coeff[i], coeff[0])); + i++; + } + + itr = 0; + while(itr < DK_MAX_ITR) + { + i = 0; + while(i < order) + { + j = 0; + x = cnum_r1; + while (j < order) + { + if (i != j) + x = cnum_mul(cnum_sub(roots[i], roots[j]), x); + j++; + } + hor = horner_eval_c(coeff_sc, roots[i], order); + x = cnum_div(hor, x); + x = cnum_sub(roots[i], x); + if (cmath_abs(cmath_abs(x.r) - cmath_abs(roots[i].r)) < DK_EPSILON && + cmath_abs(cmath_abs(x.i) - cmath_abs(roots[i].i)) < DK_EPSILON) + return; + cnum_from(&roots[i], x); + i++; + } + itr++; + } +} + +/* accepts an array of real numbers */ +_DURAND_KERNER_INLINE +void durand_kerner +(const cmath_t *coeff, cnum_s *roots, const cmath_uint16_t order) +{ + register cmath_uint16_t i; + cnum_s coeff_c[DK_MAX_N]; + i = 0; + while(i < (order+1)) + { + cnum_set(&coeff_c[i], coeff[i], 0); + i++; + } + durand_kerner_c(coeff_c, roots, order); +} + +#endif /* _DURAND_KERNER_H_ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/factorial.h b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/factorial.h new file mode 100644 index 0000000..f19803d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/factorial.h @@ -0,0 +1,114 @@ +/* + factorial.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + methods to return low-order factorials depending on allowed data types. + + 20! = 2432902008176640000 is the maximum factorial to be held + in a unsigned 64bit integer. + 13! = 479001600 is the maximum factorial to be held in a unsigned + 32bit integer. +*/ + +#ifndef _FACTORIAL_H_ +#define _FACTORIAL_H_ + +#include "custom_math.h" + +#define FACTORIAL_LOWER \ + MK_ULL(1), \ + MK_ULL(1), \ + MK_ULL(2), \ + MK_ULL(6), \ + MK_ULL(24), \ + MK_ULL(120), \ + MK_ULL(720), \ + MK_ULL(5040), \ + MK_ULL(40320), \ + MK_ULL(362880), \ + MK_ULL(3628800), \ + MK_ULL(39916800), \ + MK_ULL(479001600) + +#define FACTORIAL_HIGHER \ + MK_ULL(6227020800), \ + MK_ULL(87178291200), \ + MK_ULL(1307674368000), \ + MK_ULL(20922789888000), \ + MK_ULL(355687428096000), \ + MK_ULL(6402373705728000), \ + MK_ULL(121645100408832000), \ + MK_ULL(2432902008176640000) + +static const cmath_std_uint_t _factorials[] = +{ + #ifdef _CMATH_ANSI + FACTORIAL_LOWER + #else + FACTORIAL_LOWER, + FACTORIAL_HIGHER + #endif +}; + +static const cmath_t _inv_factorials[] = +{ + 1.00000000000000000000000000000000, + 1.00000000000000000000000000000000, + 0.50000000000000000000000000000000, + 0.16666666666666666666666666666667, + 0.04166666666666666666666666666666, + 0.00833333333333333333333333333333, + 0.00138888888888888888888888888888, + 0.00019841269841269841269841269841, + 0.00002480158730158730158730158730, + 0.00000275573192239858906525573192, + 0.00000027557319223985890652557319, + 0.00000002505210838544171877505210, + 0.00000000208767569878680989792100, + 0.00000000016059043836821614599390, + 0.00000000001147074559772972471385, + 0.00000000000076471637318198164750, + 0.00000000000004779477332387385297, + 0.00000000000000281145725434552076, + 0.00000000000000015619206968586225, + 0.00000000000000000822063524662433, + 0.00000000000000000041103176233122 +}; + +_CMATH_INLINE +cmath_std_uint_t factorial(const cmath_uint32_t x) +{ + if(x >= cmath_array_size(_factorials)) + return 0; + else + return _factorials[x]; +} + +_CMATH_INLINE +cmath_t inv_factorial(const cmath_uint32_t x) +{ + if(x >= cmath_array_size(_inv_factorials)) + return 0; + else + return _inv_factorials[x]; +} + +#endif /* _FACTORIAL_H_ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/horner.h b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/horner.h new file mode 100644 index 0000000..d943457 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/horner.h @@ -0,0 +1,71 @@ +/* + horner.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + algorithm to evaluate integer order polynomials using horner's scheme. +*/ + +#ifndef _HORNER_H_ +#define _HORNER_H_ + +#include "custom_math.h" +#include "complex_number.h" + +/* settings */ +#ifndef _HORNER_INLINE + #define _HORNER_INLINE _CMATH_INLINE +#else + #define _HORNER_INLINE +#endif + +/* real */ +_HORNER_INLINE +cmath_t horner_eval +(const cmath_t *coeff, const cmath_t x, cmath_uint16_t order) +{ + register cmath_t y = coeff[0]; + register cmath_uint16_t n = 1; + order += 1; + while(n < order) + { + y = y*x + coeff[n]; + n++; + } + return y; +} + +/* complex */ +_HORNER_INLINE +cnum_s horner_eval_c +(const cnum_s *coeff, const cnum_s x, cmath_uint16_t order) +{ + register cmath_uint16_t n = 1; + cnum_s y = coeff[0]; + order += 1; + while(n < order) + { + y = cnum_add(cnum_mul(y, x), coeff[n]); + n++; + } + return y; +} + +#endif /* _HORNER_H_ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/test_bessel.c b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/test_bessel.c new file mode 100644 index 0000000..83915a6 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/test_bessel.c @@ -0,0 +1,119 @@ +/* + test_bessel.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + test bessel_polynomial.h and other related headers + + gcc -W -Wall -Wextra -ansi pedantic + cl /W4 /Za + + reduced precisions for ansi c +*/ + +#include "stdio.h" +#include "custom_math.h" +#include "bessel_polynomial.h" +#include "durand_kerner.h" + +int main(void) +{ + register cmath_uint16_t i = 0; + register cmath_int16_t diff = 0; + + cmath_uint32_t in_order = _BESSEL_MAX_ORDER + 1; + cmath_uint16_t order; + const cmath_uint16_t reverse = 1; + + cmath_std_int_t coeff[_BESSEL_MAX_ORDER + 1]; + + cnum_t dk_coeff[_BESSEL_MAX_ORDER + 1]; + cnum_s dk_roots[_BESSEL_MAX_ORDER]; + + /* */ + #ifdef _CMATH_ANSI + puts("\n\nansi c is: on"); + #else + puts("\n\nansi c is: off"); + #endif + + /* */ + while (in_order > _BESSEL_MAX_ORDER) + { + printf("\nenter order of bessel polynomial (0 - %d): ", _BESSEL_MAX_ORDER); + scanf("%u", &in_order); + } + + order = (cmath_uint16_t)in_order; + bessel_polynomial(coeff, order, reverse); + + printf("\norder [N]: %d", order); + printf("\nreversed bessel: %d\n\n", reverse); + printf("list of coefficients:\n"); + while (i <= order) + { + printf("order[%2d]: ", (order - i)); + printf("%"_CMATH_PR_STD_INT"\n", coeff[i]); + i++; + } + puts("\npolynomial:"); + printf("y(x) = "); + + i = 0; + while (i <= order) + { + diff = (cmath_int16_t)(order - i); + if (diff > 0) + if (coeff[i] > 1) + { + printf("%"_CMATH_PR_STD_INT, coeff[i]); + if (diff > 1) + printf("*x^%d + ", diff); + else + printf("*x + "); + } + else + printf("x^%d + ", diff); + else + printf("%"_CMATH_PR_STD_INT"", coeff[i]); + i++; + } + + /* */ + puts("\n\nlist roots:"); + i = 0; + while (i < order+1) + { + dk_coeff[i] = (cnum_t)coeff[i]; + i++; + } + + durand_kerner(dk_coeff, dk_roots, order); + + i = 0; + while (i < order) + { + printf("root[%2d]: %.15f \t % .15f*i\n", + i+1, (double)dk_roots[i].r, (double)dk_roots[i].i); + i++; + } + + return 0; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/test_eval.c b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/test_eval.c new file mode 100644 index 0000000..fabf9ff --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/cmath/test_eval.c @@ -0,0 +1,87 @@ +/* + test_eval.h + Copyright (C) 2011 and later Lubomir I. Ivanov (neolit123 [at] gmail) + + 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. +*/ + +/* + test horner.h for complex numbers and other related headers + + gcc -W -Wall -Wextra -ansi pedantic + cl /W4 /Za + + reduced precisions for ansi c +*/ + +#include "stdio.h" +#include "complex_number.h" +#include "horner.h" +#include "durand_kerner.h" + +int main(void) +{ + cmath_uint16_t i = 0; + + cnum_t y[] = {2, -6, 2, -1}; + cnum_s cy[] = {{2, 0}, {-6, 0}, {2, 0}, {-1, 0}}; + + cnum_t fx = horner_eval(y, 5, poly_order(y)); + cnum_s fcx = horner_eval_c(cy, _CNUM(5, 0), poly_order(y)); + + cnum_t dk_coeff[] = {12, -7, 0.001, 0, 3, -5}; + cnum_s dk_roots[5]; + + cnum_s dk_coeff_c[] = {{12, 0}, {-7, 0}, {0.001, 0}, {0, 0}, {3, 0}, {-5, 0}}; + cnum_s dk_roots_c[5]; + + durand_kerner(dk_coeff, dk_roots, poly_order(dk_coeff)); + durand_kerner_c(dk_coeff_c, dk_roots_c, poly_order(dk_coeff_c)); + + /* */ + #ifdef _CMATH_ANSI + puts("\n\nansi c is: on"); + #else + puts("\n\nansi c is: off"); + #endif + + /* */ + puts("\n\nevaluate polynomials:\n"); + printf("* y[]: %.15f\n", (double)fx); + printf("* cy[]: %.15f \t %.15f*i\n", (double)fcx.r, (double)fcx.i); + + /* */ + puts("\nfind roots:"); + puts("\n* dk_coeff[]:"); + i = 0; + while (i < poly_order(dk_coeff)) + { + printf("root[%2d]: %.15f \t % .15f*i\n", + i+1, (double)dk_roots[i].r, (double)dk_roots[i].i); + i++; + } + i = 0; + puts("\n* dk_coeff_c[]:"); + while (i < poly_order(dk_coeff_c)) + { + printf("root[%2d]: %.15f \t % .15f*i\n", + i+1, (double)dk_roots_c[i].r, (double)dk_roots_c[i].i); + i++; + } + + return 0; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/convoengine.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/convoengine.cpp new file mode 100644 index 0000000..cbab7f6 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/convoengine.cpp @@ -0,0 +1,1065 @@ +/* + WDL - convoengine.cpp + Copyright (C) 2006 and later 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. + + +*/ + +#ifdef _WIN32 +#include +#endif +#include +#include +#include +#include +#include "convoengine.h" + +#include "denormal.h" + +//#define TIMING +#include "timing.c" + +#define CONVOENGINE_SILENCE_THRESH 1.0e-12 // -240dB +#define CONVOENGINE_IMPULSE_SILENCE_THRESH 1.0e-15 // -300dB + +static void WDL_CONVO_CplxMul2(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_CONVO_IMPULSEBUFCPLXf *b, int n) +{ + WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + if (n<2 || (n&1)) return; + + do { + t1 = a[0].re * b[0].re; + t2 = a[0].im * b[0].im; + t3 = a[0].im * b[0].re; + t4 = a[0].re * b[0].im; + t5 = a[1].re * b[1].re; + t6 = a[1].im * b[1].im; + t7 = a[1].im * b[1].re; + t8 = a[1].re * b[1].im; + t1 -= t2; + t3 += t4; + t5 -= t6; + t7 += t8; + c[0].re = t1; + c[1].re = t5; + c[0].im = t3; + c[1].im = t7; + a += 2; + b += 2; + c += 2; + } while (n -= 2); +} +static void WDL_CONVO_CplxMul3(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_CONVO_IMPULSEBUFCPLXf *b, int n) +{ + WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + if (n<2 || (n&1)) return; + + do { + t1 = a[0].re * b[0].re; + t2 = a[0].im * b[0].im; + t3 = a[0].im * b[0].re; + t4 = a[0].re * b[0].im; + t5 = a[1].re * b[1].re; + t6 = a[1].im * b[1].im; + t7 = a[1].im * b[1].re; + t8 = a[1].re * b[1].im; + t1 -= t2; + t3 += t4; + t5 -= t6; + t7 += t8; + c[0].re += t1; + c[1].re += t5; + c[0].im += t3; + c[1].im += t7; + a += 2; + b += 2; + c += 2; + } while (n -= 2); +} + +static bool CompareQueueToBuf(WDL_FastQueue *q, const void *data, int len) +{ + int offs=0; + while (len>0) + { + void *td=NULL; + int sz=q->GetPtr(offs,&td); + if (sz<1) return true; // not enough data = not equal! + if (sz>len) sz=len; + + int i=sz/sizeof(WDL_FFT_REAL); + WDL_FFT_REAL *a1=(WDL_FFT_REAL*)td; + WDL_FFT_REAL *b1=(WDL_FFT_REAL*)data; + while (i--) + { + if (fabs(*a1-*b1)>CONVOENGINE_SILENCE_THRESH) return true; + a1++; + b1++; + } + + data = ((char *)data)+sz; + offs+=sz; + len-=sz; + } + return false; +} + + +WDL_ConvolutionEngine::WDL_ConvolutionEngine() +{ + WDL_fft_init(); + m_fft_size=0; + m_impdata.Add(new ImpChannelInfo); + m_impulse_len=0; + m_proc_nch=0; +} + +WDL_ConvolutionEngine::~WDL_ConvolutionEngine() +{ + m_impdata.Empty(true); + m_proc.Empty(true); +} + +int WDL_ConvolutionEngine::SetImpulse(WDL_ImpulseBuffer *impulse, int fft_size, int impulse_sample_offset, int max_imp_size, bool forceBrute) +{ + int impulse_len=0; + int x; + int nch=impulse->GetNumChannels(); + for (x = 0; x < nch; x ++) + { + int l=impulse->impulses[x].GetSize()-impulse_sample_offset; + if (max_imp_size && l>max_imp_size) l=max_imp_size; + if (impulse_len < l) impulse_len=l; + } + + if (nch>1) // detect mono signals pretending to be multichannel + { + for (x = 1; x < nch; x ++) + { + if (impulse->impulses[x].GetSize()!=impulse->impulses[0].GetSize()|| + memcmp(impulse->impulses[x].Get(),impulse->impulses[0].Get(), + impulse->impulses[0].GetSize()*sizeof(WDL_FFT_REAL))) + break; + } + if (x >= nch) nch=1; + } + + m_impulse_len=impulse_len; + m_proc_nch=-1; + + while (m_impdata.GetSize() > nch) + m_impdata.Delete(m_impdata.GetSize()-1,true); + while (m_impdata.GetSize() < nch) + m_impdata.Add(new ImpChannelInfo); + + if (forceBrute) + { + m_fft_size=0; + + // save impulse + for (x = 0; x < m_impdata.GetSize(); x ++) + { + WDL_FFT_REAL *imp=impulse->impulses[x].Get()+impulse_sample_offset; + int lenout=impulse->impulses[x].GetSize()-impulse_sample_offset; + if (max_imp_size && lenout>max_imp_size) lenout=max_imp_size; + + WDL_CONVO_IMPULSEBUFf *impout=m_impdata.Get(x)->imp.Resize(lenout)+lenout; + while (lenout-->0) *--impout = (WDL_CONVO_IMPULSEBUFf) *imp++; + } + + for (x = 0; x < m_proc.GetSize(); x ++) + { + ProcChannelInfo *inf = m_proc.Get(x); + inf->samplesin.Clear(); + inf->samplesin2.Clear(); + inf->samplesout.Clear(); + } + + return 0; + } + + + if (fft_size<=0) + { + int msz=fft_size<=-16? -fft_size*2 : 32768; + + fft_size=32; + while (fft_size < impulse_len*2 && fft_size < msz) fft_size*=2; + } + + m_fft_size=fft_size; + + int impchunksize=fft_size/2; + int nblocks=(impulse_len+impchunksize-1)/impchunksize; + //wdl_log("il=%d, ffts=%d, cs=%d, nb=%d\n",impulse_len,fft_size,impchunksize,nblocks); + + const bool smallerSizeMode=sizeof(WDL_CONVO_IMPULSEBUFf)!=sizeof(WDL_FFT_REAL); + + WDL_FFT_REAL scale=(WDL_FFT_REAL) (1.0/fft_size); + for (x = 0; x < m_impdata.GetSize(); x ++) + { + WDL_FFT_REAL *imp=impulse->impulses[x].Get()+impulse_sample_offset; + + WDL_FFT_REAL *imp2=x < m_impdata.GetSize()-1 ? impulse->impulses[x+1].Get()+impulse_sample_offset : NULL; + + WDL_CONVO_IMPULSEBUFf *impout=m_impdata.Get(x)->imp.Resize((nblocks+!!smallerSizeMode)*fft_size*2); + char *zbuf=m_impdata.Get(x)->zflag.Resize(nblocks); + int lenout=impulse->impulses[x].GetSize()-impulse_sample_offset; + if (max_imp_size && lenout>max_imp_size) lenout=max_imp_size; + + int bl; + for (bl = 0; bl < nblocks; bl ++) + { + + int thissz=lenout; + if (thissz > impchunksize) thissz=impchunksize; + + lenout -= thissz; + int i=0; + WDL_FFT_REAL mv=0.0; + WDL_FFT_REAL mv2=0.0; + WDL_FFT_REAL *imptmp = (WDL_FFT_REAL *)impout; //-V615 + + for (; i < thissz; i ++) + { + WDL_FFT_REAL v=*imp++; + WDL_FFT_REAL v2=(WDL_FFT_REAL)fabs(v); + if (v2 > mv) mv=v2; + + imptmp[i*2]=denormal_filter_aggressive(v * scale); + + if (imp2) + { + v=*imp2++; + v2=(WDL_FFT_REAL)fabs(v); + if (v2>mv2) mv2=v2; + imptmp[i*2+1]=denormal_filter_aggressive(v*scale); + } + else imptmp[i*2+1]=0.0; + } + for (; i < fft_size; i ++) + { + imptmp[i*2]=0.0; + imptmp[i*2+1]=0.0; + } + if (mv>CONVOENGINE_IMPULSE_SILENCE_THRESH||mv2>CONVOENGINE_IMPULSE_SILENCE_THRESH) + { + *zbuf++=mv>CONVOENGINE_IMPULSE_SILENCE_THRESH ? 2 : 1; // 1 means only second channel has content + WDL_fft((WDL_FFT_COMPLEX*)impout,fft_size,0); + + if (smallerSizeMode) + { + int x,n=fft_size*2; + for(x=0;xsamplesin.Clear(); + inf->samplesin2.Clear(); + inf->samplesout.Clear(); + inf->hist_pos = 0; + memset(inf->samplehist_zflag.Get(),0,inf->samplehist_zflag.GetSize()); + memset(inf->samplehist.Get(),0,inf->samplehist.GetSize()*sizeof(WDL_FFT_REAL)); + memset(inf->overlaphist.Get(),0,inf->overlaphist.GetSize()*sizeof(WDL_FFT_REAL)); + } +} + +void WDL_ConvolutionEngine::Add(WDL_FFT_REAL **bufs, int len, int nch) +{ + while (m_proc.GetSize() < nch) m_proc.Add(new ProcChannelInfo); + while (m_proc.GetSize() > nch) m_proc.Delete(m_proc.GetSize()-1,true); + + if (m_fft_size<1) + { + m_proc_nch=nch; + + for (int ch = 0; ch < nch; ch ++) + { + int wch = ch % m_impdata.GetSize(); + WDL_CONVO_IMPULSEBUFf *imp=m_impdata.Get(wch)->imp.Get(); + int imp_len = m_impdata.Get(wch)->imp.GetSize(); + ProcChannelInfo *pinf = m_proc.Get(ch); + + if (imp_len>0) + { + if (pinf->samplesin2.Available()samplesin2.Available(); + memset(pinf->samplesin2.Add(NULL,sza),0,sza); + } + WDL_FFT_REAL *psrc; + + if (bufs && bufs[ch]) + psrc=(WDL_FFT_REAL*)pinf->samplesin2.Add(bufs[ch],len*sizeof(WDL_FFT_REAL)); + else + { + psrc=(WDL_FFT_REAL*)pinf->samplesin2.Add(NULL,len*sizeof(WDL_FFT_REAL)); + memset(psrc,0,len*sizeof(WDL_FFT_REAL)); + } + + WDL_FFT_REAL *pout=(WDL_FFT_REAL*)pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)); + int x; + int len1 = len&~1; + for (x=0; x < len1 ; x += 2) + { + int i=imp_len; + double sum=0.0,sum2=0.0; + WDL_FFT_REAL *sp=psrc+x-imp_len + 1; + WDL_CONVO_IMPULSEBUFf *ip=imp; + int j=i/4; i&=3; + while (j--) // produce 2 samples, 4 impulse samples at a time + { + double a = ip[0],b=ip[1],aa=ip[2],bb=ip[3]; + double c = sp[1],d=sp[2],cc=sp[3]; + sum+=a * sp[0] + b * c + aa * d + bb * cc; + sum2+=a * c + b * d + aa * cc + bb * sp[4]; + ip+=4; + sp+=4; + } + + while (i--) + { + double a = *ip++; + sum+=a * sp[0]; + sum2+=a * sp[1]; + sp++; + } + pout[x]=(WDL_FFT_REAL) sum; + pout[x+1]=(WDL_FFT_REAL) sum2; + } + for(;xsamplesin2.Advance(len*sizeof(WDL_FFT_REAL)); + pinf->samplesin2.Compact(); + } + else + { + if (bufs && bufs[ch]) pinf->samplesout.Add(bufs[ch],len*sizeof(WDL_FFT_REAL)); + else + { + memset(pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)),0,len*sizeof(WDL_FFT_REAL)); + } + } + + } + return; + } + + + int impchunksize=m_fft_size/2; + int nblocks=(m_impulse_len+impchunksize-1)/impchunksize; + + if (m_proc_nch != nch) + { + m_proc_nch=nch; + + int mso=0; + for (int ch = 0; ch < nch; ch ++) + { + ProcChannelInfo *pinf = m_proc.Get(ch); + pinf->hist_pos = 0; + int so=pinf->samplesin.Available() + pinf->samplesout.Available(); + if (so>mso) mso=so; + + if (m_impulse_len<1||!nblocks) + { + if (pinf->samplesin.Available()) + { + int s=pinf->samplesin.Available(); + void *buf=pinf->samplesout.Add(NULL,s); + pinf->samplesin.GetToBuf(0,buf,s); + pinf->samplesin.Clear(); + } + } + + if (so < mso) + { + memset(pinf->samplesout.Add(NULL,mso-so),0,mso-so); + } + + const int sz=nblocks*m_fft_size; + + memset(pinf->samplehist_zflag.Resize(nblocks),0,nblocks); + pinf->samplehist.Resize(sz*2); + pinf->overlaphist.Resize(m_fft_size/2); + memset(pinf->samplehist.Get(),0,pinf->samplehist.GetSize()*sizeof(WDL_FFT_REAL)); + memset(pinf->overlaphist.Get(),0,pinf->overlaphist.GetSize()*sizeof(WDL_FFT_REAL)); + } + } + + if (m_impulse_len<1||!nblocks) + { + for (int ch = 0; ch < nch; ch ++) + { + ProcChannelInfo *pinf = m_proc.Get(ch); + if (bufs && bufs[ch]) + pinf->samplesout.Add(bufs[ch],len*sizeof(WDL_FFT_REAL)); + else + memset(pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)),0,len*sizeof(WDL_FFT_REAL)); + } + // pass through + return; + } + + for (int ch = 0; ch < nch; ch ++) + { + ProcChannelInfo *pinf = m_proc.Get(ch); + if (!pinf->samplehist.GetSize()||!pinf->overlaphist.GetSize()) continue; + pinf->samplesin.Add(bufs ? bufs[ch] : NULL,len*sizeof(WDL_FFT_REAL)); + } +} + +void WDL_ConvolutionEngine::AddSilenceToOutput(int len) +{ + for (int ch = 0; ch < m_proc_nch; ch++) + { + ProcChannelInfo *pinf = m_proc.Get(ch); + memset(pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)),0,len*sizeof(WDL_FFT_REAL)); + } +} + +int WDL_ConvolutionEngine::Avail(int want) +{ + if (m_fft_size<1) + { + ProcChannelInfo *pinf = m_proc.Get(0); + return pinf ? pinf->samplesout.Available()/sizeof(WDL_FFT_REAL) : 0; + } + + const int sz=m_fft_size/2; + const int chunksize=m_fft_size/2; + const int nblocks=(m_impulse_len+chunksize-1)/chunksize; + // clear combining buffer + WDL_FFT_REAL *workbuf2 = m_combinebuf.Resize(m_fft_size*4); // temp space + + int ch; + + for (ch = 0; ch < m_proc_nch; ch ++) + { + ProcChannelInfo *pinf = m_proc.Get(ch); + ProcChannelInfo *pinf2 = ch+1 < m_proc_nch ? m_proc.Get(ch+1) : NULL; + + if (!pinf->samplehist.GetSize()||!pinf->overlaphist.GetSize()) continue; + int srcc=ch % m_impdata.GetSize(); + + bool allow_mono_input_mode=true; + bool mono_impulse_mode=false; + + if (m_impdata.GetSize()==1 && pinf2 && + pinf2->samplehist.GetSize()&&pinf2->overlaphist.GetSize() && + pinf->samplesin.Available()==pinf2->samplesin.Available() && + pinf->samplesout.Available()==pinf2->samplesout.Available() + ) + { // 2x processing mode + mono_impulse_mode=true; + allow_mono_input_mode=false; + } + + + const int in_needed=sz; + + // useSilentList[x] = 1 for mono signal, 2 for stereo, 0 for silent + char *useSilentList=pinf->samplehist_zflag.GetSize()==nblocks ? pinf->samplehist_zflag.Get() : NULL; + while (pinf->samplesin.Available()/(int)sizeof(WDL_FFT_REAL) >= sz && + pinf->samplesout.Available() < want*(int)sizeof(WDL_FFT_REAL)) + { + int histpos; + if ((histpos=++pinf->hist_pos) >= nblocks) histpos=pinf->hist_pos=0; + + // get samples from input, to history + WDL_FFT_REAL *optr = pinf->samplehist.Get()+histpos*m_fft_size*2; + + pinf->samplesin.GetToBuf(0,optr+sz,in_needed*sizeof(WDL_FFT_REAL)); + pinf->samplesin.Advance(in_needed*sizeof(WDL_FFT_REAL)); + + + bool mono_input_mode=false; + + bool nonzflag=false; + if (mono_impulse_mode) + { + if (++pinf2->hist_pos >= nblocks) pinf2->hist_pos=0; + pinf2->samplesin.GetToBuf(0,workbuf2,sz*sizeof(WDL_FFT_REAL)); + pinf2->samplesin.Advance(sz*sizeof(WDL_FFT_REAL)); + int i; + for (i = 0; i < sz; i ++) // unpack samples + { + WDL_FFT_REAL f = optr[i*2]=denormal_filter_aggressive(optr[sz+i]); + if (!nonzflag && (f<-CONVOENGINE_SILENCE_THRESH || f>CONVOENGINE_SILENCE_THRESH)) nonzflag=true; + f=optr[i*2+1]=denormal_filter_aggressive(workbuf2[i]); + if (!nonzflag && (f<-CONVOENGINE_SILENCE_THRESH || f>CONVOENGINE_SILENCE_THRESH)) nonzflag=true; + } + } + else + { + if (allow_mono_input_mode && + pinf2 && + srccsamplesin,optr+sz,sz*sizeof(WDL_FFT_REAL)) + ) + { + mono_input_mode=true; + } + else + { + allow_mono_input_mode=false; + } + + int i; + for (i = 0; i < sz; i ++) // unpack samples + { + WDL_FFT_REAL f=optr[i*2]=denormal_filter_aggressive(optr[sz+i]); + optr[i*2+1]=0.0; + if (!nonzflag && (f<-CONVOENGINE_SILENCE_THRESH || f>CONVOENGINE_SILENCE_THRESH)) nonzflag=true; + } + } + + int i; + for (i = 1; mono_input_mode && i < nblocks; i ++) // start @ 1, since hist[histpos] is no longer used for here + { + int srchistpos = histpos-i; + if (srchistpos < 0) srchistpos += nblocks; + if (!useSilentList || useSilentList[srchistpos]==2) mono_input_mode=false; + } + + if (nonzflag||!useSilentList) memset(optr+sz*2,0,sz*2*sizeof(WDL_FFT_REAL)); + + +#ifdef WDLCONVO_ZL_ACCOUNTING + m_zl_fftcnt++; +#endif + + if (nonzflag) WDL_fft((WDL_FFT_COMPLEX*)optr,m_fft_size,0); + + if (useSilentList) useSilentList[histpos]=nonzflag ? (mono_input_mode ? 1 : 2) : 0; + + int mzfl=2; + if (mono_input_mode) + { + mzfl=1; + + pinf2->samplesin.Advance(sz*sizeof(WDL_FFT_REAL)); + + // save a valid copy in sample hist incase we switch from mono to stereo + if (++pinf2->hist_pos >= nblocks) pinf2->hist_pos=0; + if (pinf2->samplehist_zflag.GetSize()==nblocks) + pinf2->samplehist_zflag.Get()[pinf2->hist_pos] = nonzflag ? 1 : 0; + + WDL_FFT_REAL *optr2 = pinf2->samplehist.Get()+pinf2->hist_pos*m_fft_size*2; + memcpy(optr2,optr,m_fft_size*2*sizeof(WDL_FFT_REAL)); + } + + int applycnt=0; + char *useImpSilentList=m_impdata.Get(srcc)->zflag.GetSize() == nblocks ? m_impdata.Get(srcc)->zflag.Get() : NULL; + + WDL_CONVO_IMPULSEBUFf *impulseptr=m_impdata.Get(srcc)->imp.Get(); + for (i = 0; i < nblocks; i ++, impulseptr+=m_fft_size*2) + { + int srchistpos = histpos-i; + if (srchistpos < 0) srchistpos += nblocks; + + if (useImpSilentList && useImpSilentList[i]samplehist.Get() + m_fft_size*srchistpos*2; + + if (applycnt++) // add to output + WDL_CONVO_CplxMul3((WDL_FFT_COMPLEX*)workbuf2,(WDL_FFT_COMPLEX*)samplehist,(WDL_CONVO_IMPULSEBUFCPLXf*)impulseptr,m_fft_size); + else // replace output + WDL_CONVO_CplxMul2((WDL_FFT_COMPLEX*)workbuf2,(WDL_FFT_COMPLEX*)samplehist,(WDL_CONVO_IMPULSEBUFCPLXf*)impulseptr,m_fft_size); + + } + if (!applycnt) + memset(workbuf2,0,m_fft_size*2*sizeof(WDL_FFT_REAL)); + else + WDL_fft((WDL_FFT_COMPLEX*)workbuf2,m_fft_size,1); + + WDL_FFT_REAL *olhist=pinf->overlaphist.Get(); // errors from last time + WDL_FFT_REAL *p1=workbuf2,*p3=workbuf2+m_fft_size,*p1o=workbuf2; + + if (mono_impulse_mode||mono_input_mode) + { + WDL_FFT_REAL *p2o=workbuf2+m_fft_size*2; + WDL_FFT_REAL *olhist2=pinf2->overlaphist.Get(); // errors from last time + int s=sz/2; + while (s--) + { + p2o[0] = p1[1]+olhist2[0]; + p2o[1] = p1[3]+olhist2[1]; + p1o[0] = p1[0]+olhist[0]; + p1o[1] = p1[2]+olhist[1]; + p1o+=2; + p2o+=2; + p1+=4; + + olhist[0]=p3[0]; + olhist[1]=p3[2]; + olhist2[0]=p3[1]; + olhist2[1]=p3[3]; + p3+=4; + + olhist+=2; + olhist2+=2; + } + // add samples to output + pinf->samplesout.Add(workbuf2,sz*sizeof(WDL_FFT_REAL)); + pinf2->samplesout.Add(workbuf2+m_fft_size*2,sz*sizeof(WDL_FFT_REAL)); + } + else + { + int s=sz/2; + while (s--) + { + p1o[0] = p1[0]+olhist[0]; + p1o[1] = p1[2]+olhist[1]; + p1o+=2; + p1+=4; + + olhist[0]=p3[0]; + olhist[1]=p3[2]; + p3+=4; + + olhist+=2; + } + // add samples to output + pinf->samplesout.Add(workbuf2,sz*sizeof(WDL_FFT_REAL)); + } + } // while available + + if (mono_impulse_mode) ch++; + } + + int mv = want; + for (ch=0;chsamplesout.Available()/sizeof(WDL_FFT_REAL) : 0; + if (!ch || vsamplesout.Get(); + return ret; +} + +void WDL_ConvolutionEngine::Advance(int len) +{ + for (int ch = 0; ch < m_proc_nch; ch ++) + { + ProcChannelInfo *pinf = m_proc.Get(ch); + pinf->samplesout.Advance(len*sizeof(WDL_FFT_REAL)); + pinf->samplesout.Compact(); + } +} + + + +/**************************************************************** +** low latency version +*/ + +WDL_ConvolutionEngine_Div::WDL_ConvolutionEngine_Div() +{ + timingInit(); + for (int x = 0; x < 2; x ++) m_sout.Add(new WDL_Queue); + m_need_feedsilence=true; +} + +int WDL_ConvolutionEngine_Div::SetImpulse(WDL_ImpulseBuffer *impulse, int maxfft_size, int known_blocksize, int max_imp_size, int impulse_offset, int latency_allowed) +{ + m_need_feedsilence=true; + + m_engines.Empty(true); + if (maxfft_size<0)maxfft_size=-maxfft_size; + maxfft_size*=2; + if (!maxfft_size || maxfft_size>32768) maxfft_size=32768; + + + const int MAX_SIZE_FOR_BRUTE=64; + + int fftsize = MAX_SIZE_FOR_BRUTE; + int impulsechunksize = MAX_SIZE_FOR_BRUTE; + + if (known_blocksize && !(known_blocksize&(known_blocksize-1)) && known_blocksize>MAX_SIZE_FOR_BRUTE*2) + { + fftsize=known_blocksize/2; + impulsechunksize=known_blocksize/2; + } + if (latency_allowed*2 > fftsize) + { + int x = 16; + while (x <= latency_allowed) x*=2; + if (x>32768) x=32768; + fftsize=impulsechunksize=x; + } + + int offs=0; + int samplesleft=impulse->impulses[0].GetSize()-impulse_offset; + if (max_imp_size>0 && samplesleft>max_imp_size) samplesleft=max_imp_size; + + do + { + WDL_ConvolutionEngine *eng=new WDL_ConvolutionEngine; + + bool wantBrute = !latency_allowed && !offs; + if (impulsechunksize*(wantBrute ? 2 : 3) >= samplesleft) impulsechunksize=samplesleft; // early-out, no point going to a larger FFT (since if we did this, we wouldnt have enough samples for a complete next pass) + if (fftsize>=maxfft_size) { impulsechunksize=samplesleft; fftsize=maxfft_size; } // if FFTs are as large as possible, finish up + + eng->SetImpulse(impulse,fftsize,offs+impulse_offset,impulsechunksize, wantBrute); + eng->m_zl_delaypos = offs; + eng->m_zl_dumpage=0; + m_engines.Add(eng); + +#ifdef WDLCONVO_ZL_ACCOUNTING + wdl_log("ce%d: offs=%d, len=%d, fftsize=%d\n",m_engines.GetSize(),offs,impulsechunksize,fftsize); +#endif + + samplesleft -= impulsechunksize; + offs+=impulsechunksize; + +#if 1 // this seems about 10% faster (maybe due to better cache use from less sized ffts used?) + impulsechunksize=offs*3; + fftsize=offs*2; +#else + impulsechunksize=fftsize; + + fftsize*=2; +#endif + } + while (samplesleft > 0); + + return GetLatency(); +} + +int WDL_ConvolutionEngine_Div::GetLatency() +{ + return m_engines.GetSize() ? m_engines.Get(0)->GetLatency() : 0; +} + + +void WDL_ConvolutionEngine_Div::Reset() +{ + int x; + for (x = 0; x < m_engines.GetSize(); x ++) + { + WDL_ConvolutionEngine *eng=m_engines.Get(x); + eng->Reset(); + } + for (x = 0; x < m_sout.GetSize(); x ++) + { + m_sout.Get(x)->Clear(); + } + + m_need_feedsilence=true; +} + +WDL_ConvolutionEngine_Div::~WDL_ConvolutionEngine_Div() +{ + timingPrint(); + m_engines.Empty(true); + m_sout.Empty(true); +} + +void WDL_ConvolutionEngine_Div::Add(WDL_FFT_REAL **bufs, int len, int nch) +{ + while (m_sout.GetSize() < nch) + m_sout.Add(new WDL_Queue); + while (m_sout.GetSize() > nch) + m_sout.Delete(m_sout.GetSize()-1,true); + + bool ns=m_need_feedsilence; + m_need_feedsilence=false; + + int x; + for (x = 0; x < m_engines.GetSize(); x ++) + { + WDL_ConvolutionEngine *eng=m_engines.Get(x); + if (ns) + { + eng->m_zl_dumpage = (x>0 && x < m_engines.GetSize()-1) ? (eng->GetLatency()/4) : 0; // reduce max number of ffts per block by staggering them + + if (eng->m_zl_dumpage>0) + eng->Add(NULL,eng->m_zl_dumpage,nch); // added silence to input (to control when fft happens) + } + + eng->Add(bufs,len,nch); + + if (ns) eng->AddSilenceToOutput(eng->m_zl_delaypos); // add silence to output (to delay output to its correct time) + + } +} +WDL_FFT_REAL **WDL_ConvolutionEngine_Div::Get() +{ + WDL_FFT_REAL **ret = m_get_tmpptrs.ResizeOK(m_sout.GetSize(),false); + if (WDL_NORMALLY(ret)) + for (int x = 0; x < m_sout.GetSize(); x ++) ret[x]=(WDL_FFT_REAL *)m_sout.Get(x)->Get(); + return ret; +} + +void WDL_ConvolutionEngine_Div::Advance(int len) +{ + int x; + for (x = 0; x < m_sout.GetSize(); x ++) + { + WDL_Queue *q = m_sout.Get(x); + q->Advance(len*sizeof(WDL_FFT_REAL)); + q->Compact(); + } +} + +int WDL_ConvolutionEngine_Div::Avail(int wantSamples) +{ + timingEnter(1); + int wso=wantSamples; + int x; +#ifdef WDLCONVO_ZL_ACCOUNTING + int cnt=0; + static int maxcnt=-1; + int h=0; +#endif + for (x = 0; x < m_engines.GetSize(); x ++) + { + WDL_ConvolutionEngine *eng=m_engines.Get(x); +#ifdef WDLCONVO_ZL_ACCOUNTING + eng->m_zl_fftcnt=0; +#endif + int a=eng->Avail(wso+eng->m_zl_dumpage) - eng->m_zl_dumpage; +#ifdef WDLCONVO_ZL_ACCOUNTING + cnt += !!eng->m_zl_fftcnt; + +#if 0 + if (eng->m_zl_fftcnt) + h|=1<m_zl_fftcnt && x==m_engines.GetSize()-1 && cnt>1) + { + wdl_log("fft flags=%08x (%08x=max)\n",h,1<maxcnt)maxcnt=cnt; + if (GetTickCount()>lastt+1000) + { + lastt=GetTickCount(); + wdl_log("maxcnt=%d\n",maxcnt); + maxcnt=-1; + } +#endif + if (wantSamples>0) + { + const int add_sz = wantSamples*sizeof(WDL_FFT_REAL); + for (x =0; x < m_sout.GetSize(); x ++) + { + void *add = m_sout.Get(x)->Add(NULL,add_sz); + if (WDL_NORMALLY(add != NULL)) memset(add,0,add_sz); + } + + for (x = 0; x < m_engines.GetSize(); x ++) + { + WDL_ConvolutionEngine *eng=m_engines.Get(x); + if (eng->m_zl_dumpage>0) { eng->Advance(eng->m_zl_dumpage); eng->m_zl_dumpage=0; } + + WDL_FFT_REAL **p=eng->Get(); + if (p) + { + int i; + for (i =0; i < m_sout.GetSize(); i ++) + { + WDL_Queue *q = m_sout.Get(i); + const int qsz = q->Available(); + if (WDL_NORMALLY(qsz >= add_sz)) + { + WDL_FFT_REAL *o=(WDL_FFT_REAL *)((char *)q->Get() + qsz - add_sz); + const WDL_FFT_REAL *in=p[i]; + int j=wantSamples; + while (j-->0) *o++ += *in++; + } + } + } + eng->Advance(wantSamples); + } + } + timingLeave(1); + + WDL_Queue *q0 = m_sout.Get(0); + int av=WDL_NORMALLY(q0 != NULL) ? (int) (q0->Available()/sizeof(WDL_FFT_REAL)) : 0; + return av>wso ? wso : av; +} + + +#ifdef WDL_TEST_CONVO + +#include + +int main(int argc, char **argv) +{ + if (argc!=5) + { + printf("usage: convoengine fftsize implen oneoffs pingoffs\n"); + return -1; + } + + int fftsize=atoi(argv[1]); + int implen = atoi(argv[2]); + int oneoffs = atoi(argv[3]); + int pingoffs=atoi(argv[4]); + + if (implen < 1 || oneoffs < 0 || oneoffs >= implen || pingoffs < 0) + { + printf("invalid parameters\n"); + return -1; + } + + WDL_ImpulseBuffer imp; + imp.nch=1; + memset(imp.impulses[0].Resize(implen),0,implen*sizeof(WDL_FFT_REAL)); + imp.impulses[0].Get()[oneoffs]=1.0; + + +#if WDL_TEST_CONVO==2 + WDL_ConvolutionEngine_Div engine; +#else + WDL_ConvolutionEngine engine; +#endif + engine.SetImpulse(&imp,fftsize); + WDL_TypedBuf m_tmpbuf; + memset(m_tmpbuf.Resize(pingoffs+1),0,pingoffs*sizeof(WDL_FFT_REAL)); + m_tmpbuf.Get()[pingoffs]=1.0; + WDL_FFT_REAL *p=m_tmpbuf.Get(); + engine.Add(&p,pingoffs+1,1); + + p=m_tmpbuf.Resize(4096); + memset(p,0,m_tmpbuf.GetSize()*sizeof(WDL_FFT_REAL)); + + int avail; + while ((avail=engine.Avail(pingoffs+oneoffs + 8192)) < pingoffs+oneoffs + 8192) + { + engine.Add(&p,4096,1); + } + WDL_FFT_REAL **output = engine.Get(); + if (!output || !*output) + { + printf("cant get output\n"); + return -1; + } + int x; + for (x = 0; x < avail; x ++) + { + WDL_FFT_REAL val=output[0][x]; + WDL_FFT_REAL expval = (x==pingoffs+oneoffs) ? 1.0:0.0; + if (fabs(val-expval)>0.000000001) + { + printf("%d: %.4fdB - %f %f\n",x,log10(max(val,0.000000000001))*20.0 - log10(max(expval,0.000000000001))*20.0,val,expval); + } + } + + return 0; +} + +#endif + + +int WDL_ImpulseBuffer::SetLength(int samples) +{ + const int nch = impulses.list.GetSize(); + if (!nch) return 0; + for (int x=0;x old_nch) + { + while (impulses.list.GetSize() < usench) + impulses.list.Add(new WDL_TypedBuf); + + const int len = SetLength(GetLength()); + + int x,ax=0; + if (duplicateExisting && len>0 && old_nch>0) for(x=old_nch;x=old_nch) ax=0; + } + } + else while (usench); + } + ~WDL_ImpulseBuffer() + { + impulses.list.Empty(true); + } + + int GetLength() { return impulses.list.GetSize() ? impulses[0].GetSize() : 0; } + int SetLength(int samples); // resizes/clears all channels accordingly, returns actual size set (can be 0 if error) + void SetNumChannels(int usench, bool duplicateExisting=true); // handles allocating/converting/etc + int GetNumChannels() { return impulses.list.GetSize(); } + + double samplerate; + struct { + WDL_PtrList > list; + + WDL_TypedBuf &operator[](size_t i) const + { + WDL_TypedBuf *p = list.Get(i); + if (WDL_NORMALLY(p != NULL)) return *p; + return *list.Get(0); // if for some reason an out of range index was passed, return first item rather than crash + } + } impulses; + +}; + +class WDL_ConvolutionEngine +{ +public: + WDL_ConvolutionEngine(); + ~WDL_ConvolutionEngine(); + + int SetImpulse(WDL_ImpulseBuffer *impulse, int fft_size=-1, int impulse_sample_offset=0, int max_imp_size=0, bool forceBrute=false); + + int GetFFTSize() { return m_fft_size; } + int GetLatency() { return m_fft_size/2; } + + void Reset(); // clears out any latent samples + + void Add(WDL_FFT_REAL **bufs, int len, int nch); + + int Avail(int wantSamples); + WDL_FFT_REAL **Get(); // returns length valid + void Advance(int len); + +private: + + struct ImpChannelInfo { + WDL_TypedBuf imp; + WDL_TypedBuf zflag; + }; + + struct ProcChannelInfo { + WDL_Queue samplesout; + WDL_Queue samplesin2; + WDL_FastQueue samplesin; + + WDL_TypedBuf samplehist; // FFT'd sample blocks per channel + WDL_TypedBuf samplehist_zflag; + WDL_TypedBuf overlaphist; + + int hist_pos; + }; + + + WDL_PtrList m_impdata; + + int m_impulse_len; + int m_fft_size; + + int m_proc_nch; + WDL_PtrList m_proc; + + + WDL_TypedBuf m_combinebuf; + WDL_TypedBuf m_get_tmpptrs; + +public: + + // _div stuff + int m_zl_delaypos; + int m_zl_dumpage; + +//#define WDLCONVO_ZL_ACCOUNTING +#ifdef WDLCONVO_ZL_ACCOUNTING + int m_zl_fftcnt;//removeme (testing of benchmarks) +#endif + void AddSilenceToOutput(int len); + +} WDL_FIXALIGN; + +// low latency version +class WDL_ConvolutionEngine_Div +{ +public: + WDL_ConvolutionEngine_Div(); + ~WDL_ConvolutionEngine_Div(); + + int SetImpulse(WDL_ImpulseBuffer *impulse, int maxfft_size=0, int known_blocksize=0, int max_imp_size=0, int impulse_offset=0, int latency_allowed=0); + + int GetLatency(); + void Reset(); + + void Add(WDL_FFT_REAL **bufs, int len, int nch); + + int Avail(int wantSamples); + WDL_FFT_REAL **Get(); // returns length valid + void Advance(int len); + +private: + WDL_PtrList m_engines; + + WDL_PtrList m_sout; + WDL_TypedBuf m_get_tmpptrs; + + bool m_need_feedsilence; + +} WDL_FIXALIGN; + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/coreaudio_channel_formats.h b/plugin-reaper-realearn/main/lib/WDL/WDL/coreaudio_channel_formats.h new file mode 100644 index 0000000..2606ca9 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/coreaudio_channel_formats.h @@ -0,0 +1,384 @@ +#ifndef _CAFCHANNELFORMATS_H_ +#define _CAFCHANNELFORMATS_H_ + +// from CoreAudioBaseTypes.h in the ios sdk, good luck finding it online + +enum +{ + // these are identical to the WAVEFORMATEXTENSIBLE bitmask + kAudioChannelBit_Left = (1U<<0), // WAVEXT: SPEAKER_FRONT_LEFT + kAudioChannelBit_Right = (1U<<1), // WAVEXT: SPEAKER_FRONT_RIGHT + kAudioChannelBit_Center = (1U<<2), // WAVEXT: SPEAKER_FRONT_CENTER + kAudioChannelBit_LFEScreen = (1U<<3), // WAVEXT: SPEAKER_LOW_FREQUENCY + kAudioChannelBit_LeftSurround = (1U<<4), // WAVEXT: SPEAKER_BACK_LEFT + kAudioChannelBit_RightSurround = (1U<<5), // WAVEXT: SPEAKER_BACK_RIGHT + kAudioChannelBit_LeftCenter = (1U<<6), // WAVEXT: SPEAKER_FRONT_LEFT_OF_CENTER + kAudioChannelBit_RightCenter = (1U<<7), // WAVEXT: SPEAKER_FROM_RIGHT_OF_CENTER + kAudioChannelBit_CenterSurround = (1U<<8), // WAVEXT: SPEAKER_BACK_CENTER + kAudioChannelBit_LeftSurroundDirect = (1U<<9), // WAVEXT: SPEAKER_SIDE_LEFT + kAudioChannelBit_RightSurroundDirect = (1U<<10), // WAVEXT: SPEAKER_SIDE_RIGHT + kAudioChannelBit_TopCenterSurround = (1U<<11), // WAVEXT: SPEAKER_TOP_CENTER + kAudioChannelBit_VerticalHeightLeft = (1U<<12), // WAVEXT: SPEAKER_TOP_FRONT_LEFT + kAudioChannelBit_VerticalHeightCenter = (1U<<13), // WAVEXT: SPEAKER_TOP_FRONT_CENTER + kAudioChannelBit_VerticalHeightRight = (1U<<14), // WAVEXT: SPEAKER_TOP_FRONT_RIGHT + kAudioChannelBit_TopBackLeft = (1U<<15), // WAVEXT: SPEAKER_TOP_BACK_LEFT + kAudioChannelBit_TopBackCenter = (1U<<16), // WAVEXT: SPEAKER_TOP_BACK_CENTER + kAudioChannelBit_TopBackRight = (1U<<17), // WAVEXT: SPEAKER_TOP_BACK_RIGHT + kAudioChannelBit_LeftTopFront = (1U<<18), + kAudioChannelBit_CenterTopFront = (1U<<19), + kAudioChannelBit_RightTopFront = (1U<<20), + kAudioChannelBit_LeftTopMiddle = (1U<<21), + kAudioChannelBit_CenterTopMiddle = (1U<<22), + kAudioChannelBit_RightTopMiddle = (1U<<23), + kAudioChannelBit_LeftTopRear = (1U<<24), + kAudioChannelBit_CenterTopRear = (1U<<25), + kAudioChannelBit_RightTopRear = (1U<<26), +}; + +enum +{ + // Some channel abbreviations used below: + // L - left + // R - right + // C - center + // Ls - left surround + // Rs - right surround + // Cs - center surround + // Rls - rear left surround + // Rrs - rear right surround + // Lw - left wide + // Rw - right wide + // Lsd - left surround direct + // Rsd - right surround direct + // Lc - left center + // Rc - right center + // Ts - top surround + // Vhl - vertical height left + // Vhc - vertical height center + // Vhr - vertical height right + // Ltf - left top front + // Ctf - center top front + // Rtf - right top front + // Ltm - left top middle + // Ctm - center top middle + // Rtm - right top middle + // Ltr - left top rear + // Ctr - center top rear + // Rtr - right top rear + // Lt - left matrix total. for matrix encoded stereo. + // Rt - right matrix total. for matrix encoded stereo. + + // General layouts + kAudioChannelLayoutTag_UseChannelDescriptions = (0U<<16) | 0, // use the array of AudioChannelDescriptions to define the mapping. + kAudioChannelLayoutTag_UseChannelBitmap = (1U<<16) | 0, // use the bitmap to define the mapping. + + kAudioChannelLayoutTag_Mono = (100U<<16) | 1, // a standard mono stream + kAudioChannelLayoutTag_Stereo = (101U<<16) | 2, // a standard stereo stream (L R) - implied playback + kAudioChannelLayoutTag_StereoHeadphones = (102U<<16) | 2, // a standard stereo stream (L R) - implied headphone playback + kAudioChannelLayoutTag_MatrixStereo = (103U<<16) | 2, // a matrix encoded stereo stream (Lt, Rt) + kAudioChannelLayoutTag_MidSide = (104U<<16) | 2, // mid/side recording + kAudioChannelLayoutTag_XY = (105U<<16) | 2, // coincident mic pair (often 2 figure 8's) + kAudioChannelLayoutTag_Binaural = (106U<<16) | 2, // binaural stereo (left, right) + kAudioChannelLayoutTag_Ambisonic_B_Format = (107U<<16) | 4, // W, X, Y, Z + + kAudioChannelLayoutTag_Quadraphonic = (108U<<16) | 4, // L R Ls Rs -- 90 degree speaker separation + kAudioChannelLayoutTag_Pentagonal = (109U<<16) | 5, // L R Ls Rs C -- 72 degree speaker separation + kAudioChannelLayoutTag_Hexagonal = (110U<<16) | 6, // L R Ls Rs C Cs -- 60 degree speaker separation + kAudioChannelLayoutTag_Octagonal = (111U<<16) | 8, // L R Ls Rs C Cs Lw Rw -- 45 degree speaker separation + kAudioChannelLayoutTag_Cube = (112U<<16) | 8, // left, right, rear left, rear right + // top left, top right, top rear left, top rear right + + // MPEG defined layouts + kAudioChannelLayoutTag_MPEG_1_0 = kAudioChannelLayoutTag_Mono, // C + kAudioChannelLayoutTag_MPEG_2_0 = kAudioChannelLayoutTag_Stereo, // L R + kAudioChannelLayoutTag_MPEG_3_0_A = (113U<<16) | 3, // L R C + kAudioChannelLayoutTag_MPEG_3_0_B = (114U<<16) | 3, // C L R + kAudioChannelLayoutTag_MPEG_4_0_A = (115U<<16) | 4, // L R C Cs + kAudioChannelLayoutTag_MPEG_4_0_B = (116U<<16) | 4, // C L R Cs + kAudioChannelLayoutTag_MPEG_5_0_A = (117U<<16) | 5, // L R C Ls Rs + kAudioChannelLayoutTag_MPEG_5_0_B = (118U<<16) | 5, // L R Ls Rs C + kAudioChannelLayoutTag_MPEG_5_0_C = (119U<<16) | 5, // L C R Ls Rs + kAudioChannelLayoutTag_MPEG_5_0_D = (120U<<16) | 5, // C L R Ls Rs + kAudioChannelLayoutTag_MPEG_5_1_A = (121U<<16) | 6, // L R C LFE Ls Rs + kAudioChannelLayoutTag_MPEG_5_1_B = (122U<<16) | 6, // L R Ls Rs C LFE + kAudioChannelLayoutTag_MPEG_5_1_C = (123U<<16) | 6, // L C R Ls Rs LFE + kAudioChannelLayoutTag_MPEG_5_1_D = (124U<<16) | 6, // C L R Ls Rs LFE + kAudioChannelLayoutTag_MPEG_6_1_A = (125U<<16) | 7, // L R C LFE Ls Rs Cs + kAudioChannelLayoutTag_MPEG_7_1_A = (126U<<16) | 8, // L R C LFE Ls Rs Lc Rc + kAudioChannelLayoutTag_MPEG_7_1_B = (127U<<16) | 8, // C Lc Rc L R Ls Rs LFE (doc: IS-13818-7 MPEG2-AAC Table 3.1) + kAudioChannelLayoutTag_MPEG_7_1_C = (128U<<16) | 8, // L R C LFE Ls Rs Rls Rrs + kAudioChannelLayoutTag_Emagic_Default_7_1 = (129U<<16) | 8, // L R Ls Rs C LFE Lc Rc + kAudioChannelLayoutTag_SMPTE_DTV = (130U<<16) | 8, // L R C LFE Ls Rs Lt Rt + // (kAudioChannelLayoutTag_ITU_5_1 plus a matrix encoded stereo mix) + // ITU defined layouts + kAudioChannelLayoutTag_ITU_1_0 = kAudioChannelLayoutTag_Mono, // C + kAudioChannelLayoutTag_ITU_2_0 = kAudioChannelLayoutTag_Stereo, // L R + + kAudioChannelLayoutTag_ITU_2_1 = (131U<<16) | 3, // L R Cs + kAudioChannelLayoutTag_ITU_2_2 = (132U<<16) | 4, // L R Ls Rs + kAudioChannelLayoutTag_ITU_3_0 = kAudioChannelLayoutTag_MPEG_3_0_A, // L R C + kAudioChannelLayoutTag_ITU_3_1 = kAudioChannelLayoutTag_MPEG_4_0_A, // L R C Cs + + kAudioChannelLayoutTag_ITU_3_2 = kAudioChannelLayoutTag_MPEG_5_0_A, // L R C Ls Rs + kAudioChannelLayoutTag_ITU_3_2_1 = kAudioChannelLayoutTag_MPEG_5_1_A, // L R C LFE Ls Rs + kAudioChannelLayoutTag_ITU_3_4_1 = kAudioChannelLayoutTag_MPEG_7_1_C, // L R C LFE Ls Rs Rls Rrs + + // DVD defined layouts + kAudioChannelLayoutTag_DVD_0 = kAudioChannelLayoutTag_Mono, // C (mono) + kAudioChannelLayoutTag_DVD_1 = kAudioChannelLayoutTag_Stereo, // L R + kAudioChannelLayoutTag_DVD_2 = kAudioChannelLayoutTag_ITU_2_1, // L R Cs + kAudioChannelLayoutTag_DVD_3 = kAudioChannelLayoutTag_ITU_2_2, // L R Ls Rs + kAudioChannelLayoutTag_DVD_4 = (133U<<16) | 3, // L R LFE + kAudioChannelLayoutTag_DVD_5 = (134U<<16) | 4, // L R LFE Cs + kAudioChannelLayoutTag_DVD_6 = (135U<<16) | 5, // L R LFE Ls Rs + kAudioChannelLayoutTag_DVD_7 = kAudioChannelLayoutTag_MPEG_3_0_A, // L R C + kAudioChannelLayoutTag_DVD_8 = kAudioChannelLayoutTag_MPEG_4_0_A, // L R C Cs + kAudioChannelLayoutTag_DVD_9 = kAudioChannelLayoutTag_MPEG_5_0_A, // L R C Ls Rs + kAudioChannelLayoutTag_DVD_10 = (136U<<16) | 4, // L R C LFE + kAudioChannelLayoutTag_DVD_11 = (137U<<16) | 5, // L R C LFE Cs + kAudioChannelLayoutTag_DVD_12 = kAudioChannelLayoutTag_MPEG_5_1_A, // L R C LFE Ls Rs + // 13 through 17 are duplicates of 8 through 12. + kAudioChannelLayoutTag_DVD_13 = kAudioChannelLayoutTag_DVD_8, // L R C Cs + kAudioChannelLayoutTag_DVD_14 = kAudioChannelLayoutTag_DVD_9, // L R C Ls Rs + kAudioChannelLayoutTag_DVD_15 = kAudioChannelLayoutTag_DVD_10, // L R C LFE + kAudioChannelLayoutTag_DVD_16 = kAudioChannelLayoutTag_DVD_11, // L R C LFE Cs + kAudioChannelLayoutTag_DVD_17 = kAudioChannelLayoutTag_DVD_12, // L R C LFE Ls Rs + kAudioChannelLayoutTag_DVD_18 = (138U<<16) | 5, // L R Ls Rs LFE + kAudioChannelLayoutTag_DVD_19 = kAudioChannelLayoutTag_MPEG_5_0_B, // L R Ls Rs C + kAudioChannelLayoutTag_DVD_20 = kAudioChannelLayoutTag_MPEG_5_1_B, // L R Ls Rs C LFE + + // These layouts are recommended for AudioUnit usage + // These are the symmetrical layouts + kAudioChannelLayoutTag_AudioUnit_4 = kAudioChannelLayoutTag_Quadraphonic, + kAudioChannelLayoutTag_AudioUnit_5 = kAudioChannelLayoutTag_Pentagonal, + kAudioChannelLayoutTag_AudioUnit_6 = kAudioChannelLayoutTag_Hexagonal, + kAudioChannelLayoutTag_AudioUnit_8 = kAudioChannelLayoutTag_Octagonal, + // These are the surround-based layouts + kAudioChannelLayoutTag_AudioUnit_5_0 = kAudioChannelLayoutTag_MPEG_5_0_B, // L R Ls Rs C + kAudioChannelLayoutTag_AudioUnit_6_0 = (139U<<16) | 6, // L R Ls Rs C Cs + kAudioChannelLayoutTag_AudioUnit_7_0 = (140U<<16) | 7, // L R Ls Rs C Rls Rrs + kAudioChannelLayoutTag_AudioUnit_7_0_Front = (148U<<16) | 7, // L R Ls Rs C Lc Rc + kAudioChannelLayoutTag_AudioUnit_5_1 = kAudioChannelLayoutTag_MPEG_5_1_A, // L R C LFE Ls Rs + kAudioChannelLayoutTag_AudioUnit_6_1 = kAudioChannelLayoutTag_MPEG_6_1_A, // L R C LFE Ls Rs Cs + kAudioChannelLayoutTag_AudioUnit_7_1 = kAudioChannelLayoutTag_MPEG_7_1_C, // L R C LFE Ls Rs Rls Rrs + kAudioChannelLayoutTag_AudioUnit_7_1_Front = kAudioChannelLayoutTag_MPEG_7_1_A, // L R C LFE Ls Rs Lc Rc + + kAudioChannelLayoutTag_AAC_3_0 = kAudioChannelLayoutTag_MPEG_3_0_B, // C L R + kAudioChannelLayoutTag_AAC_Quadraphonic = kAudioChannelLayoutTag_Quadraphonic, // L R Ls Rs + kAudioChannelLayoutTag_AAC_4_0 = kAudioChannelLayoutTag_MPEG_4_0_B, // C L R Cs + kAudioChannelLayoutTag_AAC_5_0 = kAudioChannelLayoutTag_MPEG_5_0_D, // C L R Ls Rs + kAudioChannelLayoutTag_AAC_5_1 = kAudioChannelLayoutTag_MPEG_5_1_D, // C L R Ls Rs Lfe + kAudioChannelLayoutTag_AAC_6_0 = (141U<<16) | 6, // C L R Ls Rs Cs + kAudioChannelLayoutTag_AAC_6_1 = (142U<<16) | 7, // C L R Ls Rs Cs Lfe + kAudioChannelLayoutTag_AAC_7_0 = (143U<<16) | 7, // C L R Ls Rs Rls Rrs + kAudioChannelLayoutTag_AAC_7_1 = kAudioChannelLayoutTag_MPEG_7_1_B, // C Lc Rc L R Ls Rs Lfe + kAudioChannelLayoutTag_AAC_7_1_B = (183U<<16) | 8, // C L R Ls Rs Rls Rrs LFE + kAudioChannelLayoutTag_AAC_7_1_C = (184U<<16) | 8, // C L R Ls Rs LFE Vhl Vhr + kAudioChannelLayoutTag_AAC_Octagonal = (144U<<16) | 8, // C L R Ls Rs Rls Rrs Cs + + kAudioChannelLayoutTag_TMH_10_2_std = (145U<<16) | 16, // L R C Vhc Lsd Rsd Ls Rs Vhl Vhr Lw Rw Csd Cs LFE1 LFE2 + kAudioChannelLayoutTag_TMH_10_2_full = (146U<<16) | 21, // TMH_10_2_std plus: Lc Rc HI VI Haptic + + kAudioChannelLayoutTag_AC3_1_0_1 = (149U<<16) | 2, // C LFE + kAudioChannelLayoutTag_AC3_3_0 = (150U<<16) | 3, // L C R + kAudioChannelLayoutTag_AC3_3_1 = (151U<<16) | 4, // L C R Cs + kAudioChannelLayoutTag_AC3_3_0_1 = (152U<<16) | 4, // L C R LFE + kAudioChannelLayoutTag_AC3_2_1_1 = (153U<<16) | 4, // L R Cs LFE + kAudioChannelLayoutTag_AC3_3_1_1 = (154U<<16) | 5, // L C R Cs LFE + + kAudioChannelLayoutTag_EAC_6_0_A = (155U<<16) | 6, // L C R Ls Rs Cs + kAudioChannelLayoutTag_EAC_7_0_A = (156U<<16) | 7, // L C R Ls Rs Rls Rrs + + kAudioChannelLayoutTag_EAC3_6_1_A = (157U<<16) | 7, // L C R Ls Rs LFE Cs + kAudioChannelLayoutTag_EAC3_6_1_B = (158U<<16) | 7, // L C R Ls Rs LFE Ts + kAudioChannelLayoutTag_EAC3_6_1_C = (159U<<16) | 7, // L C R Ls Rs LFE Vhc + kAudioChannelLayoutTag_EAC3_7_1_A = (160U<<16) | 8, // L C R Ls Rs LFE Rls Rrs + kAudioChannelLayoutTag_EAC3_7_1_B = (161U<<16) | 8, // L C R Ls Rs LFE Lc Rc + kAudioChannelLayoutTag_EAC3_7_1_C = (162U<<16) | 8, // L C R Ls Rs LFE Lsd Rsd + kAudioChannelLayoutTag_EAC3_7_1_D = (163U<<16) | 8, // L C R Ls Rs LFE Lw Rw + kAudioChannelLayoutTag_EAC3_7_1_E = (164U<<16) | 8, // L C R Ls Rs LFE Vhl Vhr + + kAudioChannelLayoutTag_EAC3_7_1_F = (165U<<16) | 8, // L C R Ls Rs LFE Cs Ts + kAudioChannelLayoutTag_EAC3_7_1_G = (166U<<16) | 8, // L C R Ls Rs LFE Cs Vhc + kAudioChannelLayoutTag_EAC3_7_1_H = (167U<<16) | 8, // L C R Ls Rs LFE Ts Vhc + + kAudioChannelLayoutTag_DTS_3_1 = (168U<<16) | 4, // C L R LFE + kAudioChannelLayoutTag_DTS_4_1 = (169U<<16) | 5, // C L R Cs LFE + kAudioChannelLayoutTag_DTS_6_0_A = (170U<<16) | 6, // Lc Rc L R Ls Rs + kAudioChannelLayoutTag_DTS_6_0_B = (171U<<16) | 6, // C L R Rls Rrs Ts + kAudioChannelLayoutTag_DTS_6_0_C = (172U<<16) | 6, // C Cs L R Rls Rrs + kAudioChannelLayoutTag_DTS_6_1_A = (173U<<16) | 7, // Lc Rc L R Ls Rs LFE + kAudioChannelLayoutTag_DTS_6_1_B = (174U<<16) | 7, // C L R Rls Rrs Ts LFE + kAudioChannelLayoutTag_DTS_6_1_C = (175U<<16) | 7, // C Cs L R Rls Rrs LFE + kAudioChannelLayoutTag_DTS_7_0 = (176U<<16) | 7, // Lc C Rc L R Ls Rs + kAudioChannelLayoutTag_DTS_7_1 = (177U<<16) | 8, // Lc C Rc L R Ls Rs LFE + kAudioChannelLayoutTag_DTS_8_0_A = (178U<<16) | 8, // Lc Rc L R Ls Rs Rls Rrs + kAudioChannelLayoutTag_DTS_8_0_B = (179U<<16) | 8, // Lc C Rc L R Ls Cs Rs + kAudioChannelLayoutTag_DTS_8_1_A = (180U<<16) | 9, // Lc Rc L R Ls Rs Rls Rrs LFE + kAudioChannelLayoutTag_DTS_8_1_B = (181U<<16) | 9, // Lc C Rc L R Ls Cs Rs LFE + kAudioChannelLayoutTag_DTS_6_1_D = (182U<<16) | 7, // C L R Ls Rs LFE Cs + + kAudioChannelLayoutTag_WAVE_2_1 = kAudioChannelLayoutTag_DVD_4, // 3 channels, L R LFE + kAudioChannelLayoutTag_WAVE_3_0 = kAudioChannelLayoutTag_MPEG_3_0_A, // 3 channels, L R C + kAudioChannelLayoutTag_WAVE_4_0_A = kAudioChannelLayoutTag_ITU_2_2, // 4 channels, L R Ls Rs + kAudioChannelLayoutTag_WAVE_4_0_B = (185U<<16) | 4, // 4 channels, L R Rls Rrs + kAudioChannelLayoutTag_WAVE_5_0_A = kAudioChannelLayoutTag_MPEG_5_0_A, // 5 channels, L R C Ls Rs + kAudioChannelLayoutTag_WAVE_5_0_B = (186U<<16) | 5, // 5 channels, L R C Rls Rrs + kAudioChannelLayoutTag_WAVE_5_1_A = kAudioChannelLayoutTag_MPEG_5_1_A, // 6 channels, L R C LFE Ls Rs + kAudioChannelLayoutTag_WAVE_5_1_B = (187U<<16) | 6, // 6 channels, L R C LFE Rls Rrs + kAudioChannelLayoutTag_WAVE_6_1 = (188U<<16) | 7, // 7 channels, L R C LFE Cs Ls Rs + kAudioChannelLayoutTag_WAVE_7_1 = (189U<<16) | 8, // 8 channels, L R C LFE Rls Rrs Ls Rs + + kAudioChannelLayoutTag_HOA_ACN_SN3D = (190U<<16) | 0, // Higher Order Ambisonics, Ambisonics Channel Number, SN3D normalization + // needs to be ORed with the actual number of channels (not the HOA order) + kAudioChannelLayoutTag_HOA_ACN_N3D = (191U<<16) | 0, // Higher Order Ambisonics, Ambisonics Channel Number, N3D normalization + // needs to be ORed with the actual number of channels (not the HOA order) + + kAudioChannelLayoutTag_Atmos_5_1_2 = (194U<<16) | 8, ///< L R C LFE Ls Rs Ltm Rtm + kAudioChannelLayoutTag_Atmos_5_1_4 = (195U<<16) | 10, ///< L R C LFE Ls Rs Vhl Vhr Ltr Rtr + kAudioChannelLayoutTag_Atmos_7_1_2 = (196U<<16) | 10, ///< L R C LFE Ls Rs Rls Rrs Ltm Rtm + kAudioChannelLayoutTag_Atmos_7_1_4 = (192U<<16) | 12, ///< L R C LFE Ls Rs Rls Rrs Vhl Vhr Ltr Rtr + kAudioChannelLayoutTag_Atmos_9_1_6 = (193U<<16) | 16, ///< L R C LFE Ls Rs Rls Rrs Lw Rw Vhl Vhr Ltm Rtm Ltr Rtr // L R C LFE Ls Rs Ltm Rtm + + kAudioChannelLayoutTag_DiscreteInOrder = (147U<<16) | 0, ///< needs to be ORed with the actual number of channels // needs to be ORed with the actual number of channels + + kAudioChannelLayoutTag_BeginReserved = 0xF0000000, // Channel layout tag values in this range are reserved for internal use + kAudioChannelLayoutTag_EndReserved = 0xFFFEFFFF, + + kAudioChannelLayoutTag_Unknown = 0xFFFF0000 // needs to be ORed with the actual number of channels +}; + +enum +{ + kAudioChannelLabel_Unknown = 0xFFFFFFFF, // unknown or unspecified other use + kAudioChannelLabel_Unused = 0, // channel is present, but has no intended use or destination + kAudioChannelLabel_UseCoordinates = 100, // channel is described by the mCoordinates fields. + + kAudioChannelLabel_Left = 1, + kAudioChannelLabel_Right = 2, + kAudioChannelLabel_Center = 3, + kAudioChannelLabel_LFEScreen = 4, + kAudioChannelLabel_LeftSurround = 5, + kAudioChannelLabel_RightSurround = 6, + kAudioChannelLabel_LeftCenter = 7, + kAudioChannelLabel_RightCenter = 8, + kAudioChannelLabel_CenterSurround = 9, // WAVE: "Back Center" or plain "Rear Surround" + kAudioChannelLabel_LeftSurroundDirect = 10, + kAudioChannelLabel_RightSurroundDirect = 11, + kAudioChannelLabel_TopCenterSurround = 12, + kAudioChannelLabel_VerticalHeightLeft = 13, // WAVE: "Top Front Left" + kAudioChannelLabel_VerticalHeightCenter = 14, // WAVE: "Top Front Center" + kAudioChannelLabel_VerticalHeightRight = 15, // WAVE: "Top Front Right" + + kAudioChannelLabel_TopBackLeft = 16, + kAudioChannelLabel_TopBackCenter = 17, + kAudioChannelLabel_TopBackRight = 18, + + kAudioChannelLabel_RearSurroundLeft = 33, + kAudioChannelLabel_RearSurroundRight = 34, + kAudioChannelLabel_LeftWide = 35, + kAudioChannelLabel_RightWide = 36, + kAudioChannelLabel_LFE2 = 37, + kAudioChannelLabel_LeftTotal = 38, // matrix encoded 4 channels + kAudioChannelLabel_RightTotal = 39, // matrix encoded 4 channels + kAudioChannelLabel_HearingImpaired = 40, + kAudioChannelLabel_Narration = 41, + kAudioChannelLabel_Mono = 42, + kAudioChannelLabel_DialogCentricMix = 43, + + kAudioChannelLabel_CenterSurroundDirect = 44, // back center, non diffuse + + kAudioChannelLabel_Haptic = 45, + + kAudioChannelLabel_LeftTopFront = 46, + kAudioChannelLabel_CenterTopFront = 47, + kAudioChannelLabel_RightTopFront = 48, + kAudioChannelLabel_LeftTopMiddle = 49, + kAudioChannelLabel_CenterTopMiddle = 50, + kAudioChannelLabel_RightTopMiddle = 51, + kAudioChannelLabel_LeftTopRear = 52, + kAudioChannelLabel_CenterTopRear = 53, + kAudioChannelLabel_RightTopRear = 54, + + // first order ambisonic channels + kAudioChannelLabel_Ambisonic_W = 200, + kAudioChannelLabel_Ambisonic_X = 201, + kAudioChannelLabel_Ambisonic_Y = 202, + kAudioChannelLabel_Ambisonic_Z = 203, + + // Mid/Side Recording + kAudioChannelLabel_MS_Mid = 204, + kAudioChannelLabel_MS_Side = 205, + + // X-Y Recording + kAudioChannelLabel_XY_X = 206, + kAudioChannelLabel_XY_Y = 207, + + // Binaural Recording + kAudioChannelLabel_BinauralLeft = 208, + kAudioChannelLabel_BinauralRight = 209, + + // other + kAudioChannelLabel_HeadphonesLeft = 301, + kAudioChannelLabel_HeadphonesRight = 302, + kAudioChannelLabel_ClickTrack = 304, + kAudioChannelLabel_ForeignLanguage = 305, + + // generic discrete channel + kAudioChannelLabel_Discrete = 400, + + // numbered discrete channel + kAudioChannelLabel_Discrete_0 = (1U<<16) | 0, + kAudioChannelLabel_Discrete_1 = (1U<<16) | 1, + kAudioChannelLabel_Discrete_2 = (1U<<16) | 2, + kAudioChannelLabel_Discrete_3 = (1U<<16) | 3, + kAudioChannelLabel_Discrete_4 = (1U<<16) | 4, + kAudioChannelLabel_Discrete_5 = (1U<<16) | 5, + kAudioChannelLabel_Discrete_6 = (1U<<16) | 6, + kAudioChannelLabel_Discrete_7 = (1U<<16) | 7, + kAudioChannelLabel_Discrete_8 = (1U<<16) | 8, + kAudioChannelLabel_Discrete_9 = (1U<<16) | 9, + kAudioChannelLabel_Discrete_10 = (1U<<16) | 10, + kAudioChannelLabel_Discrete_11 = (1U<<16) | 11, + kAudioChannelLabel_Discrete_12 = (1U<<16) | 12, + kAudioChannelLabel_Discrete_13 = (1U<<16) | 13, + kAudioChannelLabel_Discrete_14 = (1U<<16) | 14, + kAudioChannelLabel_Discrete_15 = (1U<<16) | 15, + kAudioChannelLabel_Discrete_65535 = (1U<<16) | 65535, + + // generic HOA ACN channel + kAudioChannelLabel_HOA_ACN = 500, + + // numbered HOA ACN channels + kAudioChannelLabel_HOA_ACN_0 = (2U << 16) | 0, + kAudioChannelLabel_HOA_ACN_1 = (2U << 16) | 1, + kAudioChannelLabel_HOA_ACN_2 = (2U << 16) | 2, + kAudioChannelLabel_HOA_ACN_3 = (2U << 16) | 3, + kAudioChannelLabel_HOA_ACN_4 = (2U << 16) | 4, + kAudioChannelLabel_HOA_ACN_5 = (2U << 16) | 5, + kAudioChannelLabel_HOA_ACN_6 = (2U << 16) | 6, + kAudioChannelLabel_HOA_ACN_7 = (2U << 16) | 7, + kAudioChannelLabel_HOA_ACN_8 = (2U << 16) | 8, + kAudioChannelLabel_HOA_ACN_9 = (2U << 16) | 9, + kAudioChannelLabel_HOA_ACN_10 = (2U << 16) | 10, + kAudioChannelLabel_HOA_ACN_11 = (2U << 16) | 11, + kAudioChannelLabel_HOA_ACN_12 = (2U << 16) | 12, + kAudioChannelLabel_HOA_ACN_13 = (2U << 16) | 13, + kAudioChannelLabel_HOA_ACN_14 = (2U << 16) | 14, + kAudioChannelLabel_HOA_ACN_15 = (2U << 16) | 15, + kAudioChannelLabel_HOA_ACN_65024 = (2U << 16) | 65024, // 254th order uses 65025 channels + + kAudioChannelLabel_BeginReserved = 0xF0000000, // Channel label values in this range are reserved for internal use + kAudioChannelLabel_EndReserved = 0xFFFFFFFE +}; + +enum +{ + kAudioChannelFlags_AllOff = 0, + kAudioChannelFlags_RectangularCoordinates = (1U<<0), + kAudioChannelFlags_SphericalCoordinates = (1U<<1), + kAudioChannelFlags_Meters = (1U<<2) +}; + + +#endif // _CAFCHANNELFORMATS_H_ \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/db2val.h b/plugin-reaper-realearn/main/lib/WDL/WDL/db2val.h new file mode 100644 index 0000000..a0a0df1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/db2val.h @@ -0,0 +1,22 @@ +#ifndef _WDL_DB2VAL_H_ +#define _WDL_DB2VAL_H_ + +#include + +#define TWENTY_OVER_LN10 8.6858896380650365530225783783321 +#define LN10_OVER_TWENTY 0.11512925464970228420089957273422 +#define DB2VAL(x) exp((x)*LN10_OVER_TWENTY) + +static inline double VAL2DB(double x) +{ + if (x < 0.0000000298023223876953125) return -150.0; + double v=log(x)*TWENTY_OVER_LN10; + return v<-150.0?-150.0:v; +} + +static inline double VAL2DB_EX(double x, double mindb) +{ + return x <= DB2VAL(mindb) ? mindb : (log(x)*TWENTY_OVER_LN10); +} + +#endif \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/delay_line.h b/plugin-reaper-realearn/main/lib/WDL/WDL/delay_line.h new file mode 100644 index 0000000..f44adcd --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/delay_line.h @@ -0,0 +1,180 @@ +#ifndef _WDL_DELAY_LINE_H_ +#define _WDL_DELAY_LINE_H_ + +#include "circbuf.h" + +template class WDL_DelayLine { +public: + WDL_DelayLine() : m_nch(1) { } + ~WDL_DelayLine() { } + + // call before accessing anything. + // total_size is maximum delay line length required (preserves contents on resize) + // if valid_size >=0, ensures delay line has exactly valid_size pairs set (must be <= total_size!) + void set_nch_length(int nch, int total_size, int valid_size=-1) + { + if (WDL_NOT_NORMALLY(valid_size > total_size)) valid_size=total_size; + + int avail = get_avail_pairs(); + const int minsz = valid_size >= 0 ? valid_size : total_size; + if (avail > minsz) + { + skip_pairs(avail-minsz); + avail = minsz; + } + + if (m_nch != nch && WDL_NORMALLY(nch>0)) + { + if (nch > m_nch) m_q.SetSizePreserveContents(total_size*nch); + + SampleType work[2048]; + const int chunk = 2048 / wdl_max(nch,m_nch); + int nleft = avail; + while (nleft > 0) + { + const int a = wdl_min(chunk,nleft); + nleft-=a; + + m_q.Get(work,a*m_nch); + VALIDATE_BUFFER(work,a*m_nch); + reinterleave_buffer(work,m_nch,nch,a); + m_q.Add(work,a*nch); + } + + if (nch < m_nch) m_q.SetSizePreserveContents(total_size*nch); + + m_nch=nch; + } + else + { + const int newsz = total_size*nch, cursz = m_q.GetTotalSize(); + if (newsz > cursz || newsz < cursz/2) m_q.SetSizePreserveContents(newsz); + } + + if (valid_size > 0) + { + const int need_extra = valid_size - get_avail_pairs(); + if (need_extra > 0) + { + // insert zero data at read pointer in delay line + m_q.Skip(-need_extra*nch); + m_q.WriteAtReadPointer(NULL,need_extra*nch); + } + } + } + + void add_pairs(const SampleType *buf, int pairs) // pushes data off old end of queue + { + WDL_ASSERT(pairs>=0); + if (pairs <= 0) return; + + VALIDATE_BUFFER(buf,pairs*m_nch); + + int add_sz = pairs*m_nch; + if (add_sz > m_q.NbFree()) + { + if (add_sz > m_q.GetTotalSize()) + { + if (buf) buf += add_sz - m_q.GetTotalSize(); + add_sz = m_q.GetTotalSize(); + } + m_q.Skip(add_sz - m_q.NbFree()); + } + + const int added = m_q.Add(buf,add_sz); + if (added != add_sz) { WDL_ASSERT(added == add_sz); } + } + + void unadd_pairs(int pairs) + { + WDL_ASSERT(pairs>=0); + if (pairs>0) m_q.UnAdd(pairs*m_nch); + } + + int peek_pairs(SampleType *buf, int pairs, int offs=0) // allow to request more than available, returns amt returned + { + WDL_ASSERT(offs >= 0); + const int avail = get_avail_pairs(); + const int rdsize = wdl_min(avail - offs, pairs); + if (rdsize <= 0) return 0; + + int ret = m_q.Peek(buf,offs*m_nch,rdsize*m_nch); + WDL_ASSERT(ret == rdsize*m_nch); + VALIDATE_BUFFER(buf,rdsize); + return ret/m_nch; + } + + void get_pairs(SampleType *buf, int pairs) // asserts if pairs > available + { + int amt; + WDL_ASSERT(pairs <= get_avail_pairs()); + if (buf) + { + amt = peek_pairs(buf,pairs,0); + WDL_ASSERT(amt == pairs); + VALIDATE_BUFFER(buf,amt); + } + else + { + amt = pairs; + } + skip_pairs(amt); + } + + void skip_pairs(int samt) { if (samt > 0) m_q.Skip(samt*m_nch); } + int get_avail_pairs() const { return m_q.NbInBuf()/m_nch; } + + void free_memory() { m_q.SetSize(0); } // frees memory + void clear() { m_q.Reset(); } // keeps max buffer size intact but clears contents + + static void reinterleave_buffer(SampleType *rdptr, int in_nch, int out_nch, int len) + { + if (len < 1 || !rdptr) return; + + int x=len-1; + SampleType *wrptr = rdptr; + if (out_nch < in_nch) + { + const int sz1=out_nch*sizeof(SampleType); + while (x--) + { + rdptr += in_nch; + wrptr += out_nch; + memmove(wrptr,rdptr,sz1); + } + } + else if (out_nch > in_nch) + { + const int sz1=in_nch*sizeof(SampleType); + const int sz2=(out_nch-in_nch)*sizeof(SampleType); + rdptr += in_nch*x; + wrptr += out_nch*x; + while(x--) + { + memmove(wrptr,rdptr,sz1); + memset(wrptr+in_nch,0,sz2); + + rdptr-=in_nch; + wrptr-=out_nch; + } + memset(wrptr+in_nch,0,sz2); // last iteration doesnt need memcpy (but does need clear) + } + } + +private: + WDL_TypedCircBuf m_q; + int m_nch; + + static void VALIDATE_BUFFER(const SampleType *buf, int cnt) + { +#ifdef _DEBUG + if (buf) for (int x = 0; x < cnt; x ++) + { + double v = buf[x]; + WDL_ASSERT(v >= -40000.0 && v < 40000.0); + } +#endif + } +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/denormal.h b/plugin-reaper-realearn/main/lib/WDL/WDL/denormal.h new file mode 100644 index 0000000..e9dfb04 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/denormal.h @@ -0,0 +1,248 @@ +#ifndef _WDL_DENORMAL_H_ +#define _WDL_DENORMAL_H_ + +#include +#include "wdltypes.h" +// note: the _aggressive versions filter out anything less than around 1.0e-16 or so (approximately) to 0.0, including -0.0 (becomes 0.0) +// note: new! the _aggressive versions also filter inf and NaN to 0.0 + +#ifdef __cplusplus +#define WDL_DENORMAL_INLINE inline +#elif defined(_MSC_VER) +#define WDL_DENORMAL_INLINE __inline +#else + #ifdef WDL_STATICFUNC_UNUSED + #define WDL_DENORMAL_INLINE WDL_STATICFUNC_UNUSED + #else + #define WDL_DENORMAL_INLINE + #endif +#endif + +static WDL_DENORMAL_INLINE unsigned int WDL_DENORMAL_FLOAT_W(const float *a) { unsigned int v; memcpy(&v,a,sizeof(v)); return v; } +static WDL_DENORMAL_INLINE unsigned int WDL_DENORMAL_DOUBLE_HW(const double *a) { WDL_UINT64 v; memcpy(&v,(char*)a,sizeof(v)); return (unsigned int) (v>>32); } + +#define WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF 0x3cA00000 // 0x3B8000000 maybe instead? that's 10^-5 smaller or so +#define WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF 0x25000000 + + +// define WDL_DENORMAL_WANTS_SCOPED_FTZ, and then use a WDL_denormal_ftz_scope in addition to denormal_*(), then +// if FTZ is available it will be used instead... +// +#ifdef WDL_DENORMAL_WANTS_SCOPED_FTZ + +#if defined(__SSE2__) || _M_IX86_FP >= 2 || defined(_M_X64) + #define WDL_DENORMAL_FTZMODE + #define WDL_DENORMAL_FTZSTATE_TYPE unsigned int + #ifdef _MSC_VER + #include + #else + #include + #endif + #define wdl_denorm_mm_getcsr() _mm_getcsr() + #define wdl_denorm_mm_setcsr(x) _mm_setcsr(x) + #if defined(__SSE3__) + #define wdl_denorm_mm_csr_mask ((1<<15)|(1<<11) | (1<<8) | (1<<6)) // FTZ, underflow, denormal mask, DAZ + #else + #define wdl_denorm_mm_csr_mask ((1<<15)|(1<<11)) // FTZ and underflow only (target SSE2) + #endif +#elif defined(__arm__) || defined(__aarch64__) + #define WDL_DENORMAL_FTZMODE + #define WDL_DENORMAL_FTZSTATE_TYPE unsigned long + static unsigned long __attribute__((unused)) wdl_denorm_mm_getcsr() + { + unsigned long rv; +#ifdef __aarch64__ + asm volatile ( "mrs %0, fpcr" : "=r" (rv)); +#else + asm volatile ( "fmrx %0, fpscr" : "=r" (rv)); +#endif + return rv; + } + static void __attribute__((unused)) wdl_denorm_mm_setcsr(unsigned long v) + { +#ifdef __aarch64__ + asm volatile ( "msr fpcr, %0" :: "r"(v)); +#else + asm volatile ( "fmxr fpscr, %0" :: "r"(v)); +#endif + } + #define wdl_denorm_mm_csr_mask (1<<24) +#endif + +class WDL_denormal_ftz_scope +{ + public: + WDL_denormal_ftz_scope() + { +#ifdef WDL_DENORMAL_FTZMODE + const WDL_DENORMAL_FTZSTATE_TYPE b = wdl_denorm_mm_csr_mask; + old_state = wdl_denorm_mm_getcsr(); + if ((need_restore = (old_state & b) != b)) + wdl_denorm_mm_setcsr(old_state|b); +#endif + } + ~WDL_denormal_ftz_scope() + { +#ifdef WDL_DENORMAL_FTZMODE + if (need_restore) wdl_denorm_mm_setcsr(old_state); +#endif + } + +#ifdef WDL_DENORMAL_FTZMODE + WDL_DENORMAL_FTZSTATE_TYPE old_state; + bool need_restore; +#endif + +}; + + +#endif + + +#if !defined(WDL_DENORMAL_FTZMODE) && !defined(WDL_DENORMAL_DO_NOT_FILTER) + +static double WDL_DENORMAL_INLINE denormal_filter_double(double a) +{ + return (WDL_DENORMAL_DOUBLE_HW(&a)&0x7ff00000) ? a : 0.0; +} + +static double WDL_DENORMAL_INLINE denormal_filter_double2(double a) +{ + return ((WDL_DENORMAL_DOUBLE_HW(&a)+0x100000)&0x7ff00000) > 0x100000 ? a : 0.0; +} + +static double WDL_DENORMAL_INLINE denormal_filter_double_aggressive(double a) +{ + return ((WDL_DENORMAL_DOUBLE_HW(&a)+0x100000)&0x7ff00000) >= WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF ? a : 0.0; +} + +static float WDL_DENORMAL_INLINE denormal_filter_float(float a) +{ + return (WDL_DENORMAL_FLOAT_W(&a)&0x7f800000) ? a : 0.0f; +} + +static float WDL_DENORMAL_INLINE denormal_filter_float2(float a) +{ + return ((WDL_DENORMAL_FLOAT_W(&a)+0x800000)&0x7f800000) > 0x800000 ? a : 0.0f; +} + + +static float WDL_DENORMAL_INLINE denormal_filter_float_aggressive(float a) +{ + return ((WDL_DENORMAL_FLOAT_W(&a)+0x800000)&0x7f800000) >= WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF ? a : 0.0f; +} +static void WDL_DENORMAL_INLINE denormal_fix_double(double *a) +{ + if (!(WDL_DENORMAL_DOUBLE_HW(a)&0x7ff00000)) *a=0.0; +} + +static void WDL_DENORMAL_INLINE denormal_fix_double_aggressive(double *a) +{ + if (((WDL_DENORMAL_DOUBLE_HW(a)+0x100000)&0x7ff00000) < WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF) *a=0.0; +} + +static void WDL_DENORMAL_INLINE denormal_fix_float(float *a) +{ + if (!(WDL_DENORMAL_FLOAT_W(a)&0x7f800000)) *a=0.0f; +} +static void WDL_DENORMAL_INLINE denormal_fix_float_aggressive(float *a) +{ + if (((WDL_DENORMAL_FLOAT_W(a)+0x800000)&0x7f800000) < WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF) *a=0.0f; +} + + + +#ifdef __cplusplus // automatic typed versions (though one should probably use the explicit versions... + + +static double WDL_DENORMAL_INLINE denormal_filter(double a) +{ + return (WDL_DENORMAL_DOUBLE_HW(&a)&0x7ff00000) ? a : 0.0; +} +static double WDL_DENORMAL_INLINE denormal_filter_aggressive(double a) +{ + return ((WDL_DENORMAL_DOUBLE_HW(&a)+0x100000)&0x7ff00000) >= WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF ? a : 0.0; +} + +static float WDL_DENORMAL_INLINE denormal_filter(float a) +{ + return (WDL_DENORMAL_FLOAT_W(&a)&0x7f800000) ? a : 0.0f; +} + +static float WDL_DENORMAL_INLINE denormal_filter_aggressive(float a) +{ + return ((WDL_DENORMAL_FLOAT_W(&a)+0x800000)&0x7f800000) >= WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF ? a : 0.0f; +} + +static void WDL_DENORMAL_INLINE denormal_fix(double *a) +{ + if (!(WDL_DENORMAL_DOUBLE_HW(a)&0x7ff00000)) *a=0.0; +} +static void WDL_DENORMAL_INLINE denormal_fix_aggressive(double *a) +{ + if (((WDL_DENORMAL_DOUBLE_HW(a)+0x100000)&0x7ff00000) < WDL_DENORMAL_DOUBLE_AGGRESSIVE_CUTOFF) *a=0.0; +} +static void WDL_DENORMAL_INLINE denormal_fix(float *a) +{ + if (!(WDL_DENORMAL_FLOAT_W(a)&0x7f800000)) *a=0.0f; +} +static void WDL_DENORMAL_INLINE denormal_fix_aggressive(float *a) +{ + if (((WDL_DENORMAL_FLOAT_W(a)+0x800000)&0x7f800000) < WDL_DENORMAL_FLOAT_AGGRESSIVE_CUTOFF) *a=0.0f; +} + + + +#endif // cplusplus versions + +#else // end of !WDL_DENORMAL_DO_NOT_FILTER (and other platform-specific checks) + +#define denormal_filter(x) (x) +#define denormal_filter2(x) (x) +#define denormal_filter_double(x) (x) +#define denormal_filter_double2(x) (x) +#define denormal_filter_double_aggressive(x) (x) +#define denormal_filter_float(x) (x) +#define denormal_filter_float2(x) (x) +#define denormal_filter_float_aggressive(x) (x) +#define denormal_filter_aggressive(x) (x) +#define denormal_fix(x) do { } while(0) +#define denormal_fix_aggressive(x) do { } while(0) +#define denormal_fix_double(x) do { } while(0) +#define denormal_fix_double_aggressive(x) do { } while(0) +#define denormal_fix_float(x) do { } while(0) +#define denormal_fix_float_aggressive(x) do { } while(0) + +#endif + + +//////////////////// +// this isnt a denormal function but it is similar, so we'll put it here as a bonus + +static void WDL_DENORMAL_INLINE GetDoubleMaxAbsValue(double *out, const double *in) // note: the value pointed to by "out" must be >=0.0, __NOT__ <= -0.0 +{ + WDL_UINT64 i, o; + memcpy(&i,in,sizeof(i)); + memcpy(&o,out,sizeof(o)); + i &= WDL_UINT64_CONST(0x7fffffffffffffff); + if (i > o) memcpy(out,&i,sizeof(i)); +} + +static void WDL_DENORMAL_INLINE GetFloatMaxAbsValue(float *out, const float *in) // note: the value pointed to by "out" must be >=0.0, __NOT__ <= -0.0 +{ + unsigned int i, o; + memcpy(&i, in, sizeof(i)); + memcpy(&o, out, sizeof(o)); + i &= 0x7fffffff; + if (i > o) memcpy(out, &i, sizeof(i)); +} + + +#ifdef __cplusplus +static void WDL_DENORMAL_INLINE GetFloatMaxAbsValue(double *out, const double *in) // note: the value pointed to by "out" must be >=0.0, __NOT__ <= -0.0 +{ + GetDoubleMaxAbsValue(out,in); +} +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/destroycheck.h b/plugin-reaper-realearn/main/lib/WDL/WDL/destroycheck.h new file mode 100644 index 0000000..2fa1247 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/destroycheck.h @@ -0,0 +1,65 @@ +#ifndef _WDL_DESTROYCHECK_H_ +#define _WDL_DESTROYCHECK_H_ + +// this is a useful class for verifying that an object (usually "this") hasn't been destroyed: +// to use it you add a WDL_DestroyState as a member of your class, then use the WDL_DestroyCheck +// helper class (creating it when the pointer is known valid, and checking it later to see if it +// is still valid). +// +// example: +// class myClass { +// WDL_DestroyState dest; +// ... +// }; +// +// calling code (on myClass *classInstnace): +// WDL_DestroyCheck chk(&classInstance->dest); +// somefunction(); +// if (!chk.isOK()) printf("classInstance got deleted!\n"); +// +// NOTE: only use this when these objects will be accessed from the same thread -- it will fail miserably +// in a multithreaded environment + + + + +class WDL_DestroyCheck +{ + public: + class WDL_DestroyStateNextRec { public: WDL_DestroyCheck *next; }; + + WDL_DestroyStateNextRec n, *prev; + WDL_DestroyCheck(WDL_DestroyStateNextRec *state) + { + n.next=NULL; + if ((prev=state)) + { + if ((n.next=prev->next)) n.next->prev = &n; + prev->next=this; + } + } + ~WDL_DestroyCheck() + { + if (prev) + { + prev->next = n.next; + if (n.next) n.next->prev = prev; + } + } + + bool isOK() { return !!prev; } +}; + +class WDL_DestroyState : public WDL_DestroyCheck::WDL_DestroyStateNextRec +{ + public: + WDL_DestroyState() { next=NULL; } + + ~WDL_DestroyState() + { + WDL_DestroyCheck *p = next; + while (p) { WDL_DestroyCheck *np = p->n.next; p->prev=NULL; p->n.next=NULL; p=np; } + } +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/diffcalc.h b/plugin-reaper-realearn/main/lib/WDL/WDL/diffcalc.h new file mode 100644 index 0000000..bb69561 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/diffcalc.h @@ -0,0 +1,163 @@ +#ifndef _WDL_DIFFCALC_H_ +#define _WDL_DIFFCALC_H_ + +#include "assocarray.h" + + + +// Based on "An O(ND) Difference Algorithm and Its Variations", Myers +// http://xmailserver.org/diff2.pdf + + +template class WDL_DiffCalc +{ +public: + WDL_DiffCalc() {} + virtual ~WDL_DiffCalc() {} + + // cmp() returns 0 if the elements are equal. + // returns the length of the merged list and populates m_rx, m_ry. + int Diff(const T* x, const T* y, int nx, int ny, int (*cmp)(const T*, const T*)) + { + m_rx.Resize(0, false); + m_ry.Resize(0, false); + ClearV(); + + if (!nx && !ny) return 0; + if (!nx || !ny) + { + int i, n=max(nx, ny); + for (i=0; i < n; ++i) + { + m_rx.Add(nx ? i : -1); + m_ry.Add(ny ? i : -1); + } + return n; + } + + if (!cmp(x, y)) // special case + { + int i, n; + for (n=1; n < min(nx, ny); ++n) + { + if (cmp(x+n, y+n)) break; + } + int len=Diff(x+n, y+n, nx-n, ny-n, cmp); + int *rx=m_rx.Get(), *ry=m_ry.Get(); + for (i=0; i < len; ++i) + { + if (rx[i] >= 0) rx[i] += n; + if (ry[i] >= 0) ry[i] += n; + } + len += n; + while (n--) + { + m_rx.Insert(n, 0); + m_ry.Insert(n, 0); + } + return len; + } + + SetV(0, 1, 0); + int d, k, xi, yi; + for (d=0; d <= nx+ny; ++d) + { + for (k=-d; k <= d ; k += 2) + { + if (k == -d || (k != d && GetV(d, k-1) < GetV(d, k+1))) + { + xi=GetV(d, k+1); + } + else + { + xi=GetV(d, k-1)+1; + } + yi=xi-k; + while (xi < nx && yi < ny && !cmp(x+xi, y+yi)) + { + ++xi; + ++yi; + } + SetV(d+1, k, xi); + if (xi >= nx && yi >= ny) break; + } + if (xi >= nx && yi >= ny) break; + } + + int len=(nx+ny+d)/2; + int *rx=m_rx.Resize(len); + int *ry=m_ry.Resize(len); + int pos=len; + + while (d) + { + while (xi > 0 && yi > 0 && !cmp(x+xi-1, y+yi-1)) + { + --pos; + rx[pos]=--xi; + ry[pos]=--yi; + } + --pos; + if (k == -d || (k != d && GetV(d, k-1) < GetV(d, k+1))) + { + ++k; + rx[pos]=-1; + ry[pos]=--yi; + } + else + { + --k; + rx[pos]=--xi; + ry[pos]=-1; + } + --d; + } + + return len; + } + + // m_rx, m_ry hold the index of each merged list element in x and y, + // or -1 if the merged list element is not an element in that source list. + // example: X="ABCABBA", Y="CBABAC" + // 0123456 012345 + // WDL_Merge() returns "ABCBABBAC" + // 012 3456 + // 0123 45 + // m_rx={ 0, 1, 2, -1, 3, 4, 5, 6, -1} + // m_ry={-1, -1, 0, 1, 2, 3, -1, 4, 5} + WDL_TypedBuf m_rx, m_ry; + +private: + + WDL_IntKeyedArray m_v; // x coord of d-contour on row k + void ClearV() + { + m_v.DeleteAll(); + } + void SetV(int d, int k, int xi) + { + m_v.Insert(_key(d, k), xi); + } + int GetV(int d, int k) + { + return m_v.Get(_key(d, k)); + } + static int _key(int d, int k) { return (d<<16)|(k+(1<<15)); } +}; + + +// this is a separate function from WDL_DiffCalc only because it requires T::operator= +template int WDL_Merge(const T* x, const T* y, int nx, int ny, + int (*cmp)(const T*, const T*), T* list) +{ + WDL_DiffCalc dc; + int i, n=dc.Diff(x, y, nx, ny, cmp); + int *rx=dc.m_rx.Get(), *ry=dc.m_ry.Get(); + for (i=0; i < n; ++i) + { + if (list) list[i]=(rx[i] >= 0 ? x[rx[i]] : y[ry[i]]); + } + return n; +} + +#endif \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/dirscan.h b/plugin-reaper-realearn/main/lib/WDL/WDL/dirscan.h new file mode 100644 index 0000000..81c0556 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/dirscan.h @@ -0,0 +1,329 @@ +/* + WDL - dirscan.h + Copyright (C) 2005 and later 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. + +*/ + +/* + + This file provides the interface and implementation for WDL_DirScan, a simple + (and somewhat portable) directory reading class. On non-Win32 systems it wraps + opendir()/readdir()/etc. On Win32, it uses FindFirst*, and supports wildcards as + well. + + +*/ + + +#ifndef _WDL_DIRSCAN_H_ +#define _WDL_DIRSCAN_H_ + +#include "wdlstring.h" + +#ifndef _WIN32 +#include +#include +#include +extern struct stat wdl_stat_chk; +// if this fails on linux, use CFLAGS += -D_FILE_OFFSET_BITS=64 +typedef char wdl_dirscan_assert_failed_stat_not_64[sizeof(wdl_stat_chk.st_size)!=8 ? -1 : 1]; +#endif + +class WDL_DirScan +{ + public: + WDL_DirScan() : +#ifdef _WIN32 + m_h(INVALID_HANDLE_VALUE) + #ifndef WDL_NO_SUPPORT_UTF8 + , m_wcmode(false) + #endif +#else + m_h(NULL), m_ent(NULL) +#endif + { + } + + ~WDL_DirScan() + { + Close(); + } + + int First(const char *dirname +#ifdef _WIN32 + , int isExactSpec=0 +#endif + ) // returns 0 if success + { + WDL_FastString scanstr(dirname); + const int l = scanstr.GetLength(); + if (l < 1) return -1; + +#ifdef _WIN32 + if (!isExactSpec) + { + if (dirname[l-1] == '\\' || dirname[l-1] == '/') scanstr.SetLen(l-1); + m_leading_path = scanstr; + scanstr.Append("\\*"); + } + else + { + m_leading_path = scanstr; + + // remove trailing wildcards and directory separator from m_leading_path + const char *sp = m_leading_path.Get(); + int idx = m_leading_path.GetLength() - 1; + while (idx > 0 && sp[idx] != '/' && sp[idx] != '\\') idx--; + if (idx > 0) m_leading_path.SetLen(idx); + } +#else + if (dirname[l-1] == '\\' || dirname[l-1] == '/') scanstr.SetLen(l-1); + m_leading_path = scanstr; + if (!scanstr.GetLength()) scanstr.Set("/"); // fix for scanning / +#endif + + Close(); +#ifdef _WIN32 + #ifndef WDL_NO_SUPPORT_UTF8 + m_h=INVALID_HANDLE_VALUE; + #ifdef WDL_SUPPORT_WIN9X + m_wcmode = GetVersion()< 0x80000000; + #else + m_wcmode = true; + #endif + + if (m_wcmode) + { + int reqbuf = MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,scanstr.Get(),-1,NULL,0); + if (reqbuf > 1000) + { + WDL_TypedBuf tmp; + tmp.Resize(reqbuf+20); + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,scanstr.Get(),-1,tmp.Get(),tmp.GetSize()-10)) + { + correctlongpath(tmp.Get()); + m_h=FindFirstFileW(tmp.Get(),&m_fd); + } + } + else + { + WCHAR wfilename[1024]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,scanstr.Get(),-1,wfilename,1024-10)) + { + correctlongpath(wfilename); + m_h=FindFirstFileW(wfilename,&m_fd); + } + } + } + + if (m_h==INVALID_HANDLE_VALUE) m_wcmode=false; + + if (m_h==INVALID_HANDLE_VALUE) + #endif + m_h=FindFirstFileA(scanstr.Get(),(WIN32_FIND_DATAA*)&m_fd); + return (m_h == INVALID_HANDLE_VALUE); +#else + m_ent=0; + m_h=opendir(scanstr.Get()); + return !m_h || Next(); +#endif + } + int Next() // returns 0 on success + { +#ifdef _WIN32 + if (m_h == INVALID_HANDLE_VALUE) return -1; + #ifndef WDL_NO_SUPPORT_UTF8 + if (m_wcmode) return !FindNextFileW(m_h,&m_fd); + #endif + return !FindNextFileA(m_h,(WIN32_FIND_DATAA*)&m_fd); +#else + if (!m_h) return -1; + return !(m_ent=readdir(m_h)); +#endif + } + void Close() + { +#ifdef _WIN32 + if (m_h != INVALID_HANDLE_VALUE) FindClose(m_h); + m_h=INVALID_HANDLE_VALUE; +#else + if (m_h) closedir(m_h); + m_h=0; m_ent=0; +#endif + } + +#ifdef _WIN32 + const char *GetCurrentFN() + { +#ifndef WDL_NO_SUPPORT_UTF8 + if (m_wcmode) + { + if (!WideCharToMultiByte(CP_UTF8,0,m_fd.cFileName,-1,m_tmpbuf,sizeof(m_tmpbuf),NULL,NULL)) + m_tmpbuf[0]=0; + return m_tmpbuf; + } +#endif + return ((WIN32_FIND_DATAA *)&m_fd)->cFileName; + } +#else + const char *GetCurrentFN() const { return m_ent?m_ent->d_name : ""; } +#endif + template void GetCurrentFullFN(T *str) + { + str->Set(m_leading_path.Get()); +#ifdef _WIN32 + str->Append("\\"); +#else + str->Append("/"); +#endif + str->Append(GetCurrentFN()); + } + int GetCurrentIsDirectory() const // returns 1 if dir, 2 if symlink to dir, 4 if possibly-recursive symlink to dir + { +#ifdef _WIN32 + return !!(m_fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +#else + char tmp[2048]; + if (m_ent) switch (m_ent->d_type) + { + case DT_DIR: return 1; + case DT_LNK: + { + snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),m_ent->d_name); + char *rp = realpath(tmp,NULL); + if (!rp) return 0; + + struct stat sb; + int ret = (!stat(rp,&sb) && (sb.st_mode & S_IFMT) == S_IFDIR) ? 2 : 0; + if (ret) + { + // treat symlinks of /path/to/foo -> /path from being resolved (avoiding obvious feedback loops) + const int rpl = (int) strlen(rp); + if ( +#ifdef __APPLE__ + !strnicmp(rp,m_leading_path.Get(),rpl) +#else + !strncmp(rp,m_leading_path.Get(),rpl) +#endif + && (m_leading_path.Get()[rpl] == '/' || m_leading_path.Get()[rpl] == 0) + ) ret = 4; + } + free(rp); + return ret; + } + case DT_UNKNOWN: + { + snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),m_ent->d_name); + DIR *d = opendir(tmp); + if (d) { closedir(d); return 1; } + return 0; + } + } + return 0; +#endif + } + + // these are somewhat windows specific calls, eh +#ifdef _WIN32 + DWORD GetCurrentFileSize(DWORD *HighWord=NULL) const { if (HighWord) *HighWord = m_fd.nFileSizeHigh; return m_fd.nFileSizeLow; } + void GetCurrentLastWriteTime(FILETIME *ft) const { *ft = m_fd.ftLastWriteTime; } + void GetCurrentLastAccessTime(FILETIME *ft) const { *ft = m_fd.ftLastAccessTime; } + void GetCurrentCreationTime(FILETIME *ft) const { *ft = m_fd.ftCreationTime; } + DWORD GetFileAttributes() const { return m_fd.dwFileAttributes; } +#elif defined(_WDL_SWELL_H_) + + void GetCurrentCreationTime(FILETIME *ft) + { + char tmp[2048]; + snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),GetCurrentFN()); + struct stat st={0,}; + stat(tmp,&st); + unsigned long long a=(unsigned long long)st.st_ctime; // seconds since january 1st, 1970 + a+=11644473600ull; // 1601->1970 + a*=10000000; // seconds to 1/10th microseconds (100 nanoseconds) + ft->dwLowDateTime=a & 0xffffffff; + ft->dwHighDateTime=a>>32; + } + + void GetCurrentLastWriteTime(FILETIME *ft) + { + char tmp[2048]; + snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),GetCurrentFN()); + struct stat st={0,}; + stat(tmp,&st); + unsigned long long a=(unsigned long long)st.st_mtime; // seconds since january 1st, 1970 + a+=11644473600ull; // 1601->1970 + a*=10000000; // seconds to 1/10th microseconds (100 nanoseconds) + ft->dwLowDateTime=a & 0xffffffff; + ft->dwHighDateTime=a>>32; + } + DWORD GetCurrentFileSize(DWORD *HighWord=NULL) + { + char tmp[2048]; + snprintf(tmp,sizeof(tmp),"%s/%s",m_leading_path.Get(),GetCurrentFN()); + struct stat st={0,}; + stat(tmp,&st); + + if (HighWord) *HighWord = (DWORD)(st.st_size>>32); + return (DWORD)(st.st_size&0xffffffff); + } + +#endif + + private: +#ifdef _WIN32 + +#ifndef WDL_NO_SUPPORT_UTF8 + bool m_wcmode; + WIN32_FIND_DATAW m_fd; + char m_tmpbuf[MAX_PATH*5]; // even if each byte gets encoded as 4 utf-8 bytes this should be plenty ;) +#else + WIN32_FIND_DATAA m_fd; +#endif + HANDLE m_h; +#else + DIR *m_h; + struct dirent *m_ent; +#endif + WDL_FastString m_leading_path; + +#ifdef _WIN32 + static void correctlongpath(WCHAR *buf) // this also exists as wdl_utf8_correctlongpath + { + const WCHAR *insert; + WCHAR *wr; + int skip = 0; + if (!buf || !buf[0] || wcslen(buf) < 256) return; + if (buf[1] == ':') insert=L"\\\\?\\"; + else if (buf[0] == '\\' && buf[1] == '\\') { insert = L"\\\\?\\UNC\\"; skip=2; } + else return; + + wr = buf + wcslen(insert); + memmove(wr, buf + skip, (wcslen(buf+skip)+1)*2); + memmove(buf,insert,wcslen(insert)*2); + while (*wr) + { + if (*wr == '/') *wr = '\\'; + wr++; + } + } +#endif +} WDL_FIXALIGN; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/.gitignore b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/.gitignore new file mode 100644 index 0000000..fd2f028 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/.gitignore @@ -0,0 +1,8 @@ +/*.obj +/asm-nseel-x64-macho.asm +/asm-nseel-x64.asm +/loose_eel.exe + +!/asm-nseel-x64-macho.o +!/asm-nseel-x64.obj +!/asm-nseel-arm64ec.obj diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/Makefile b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/Makefile new file mode 100644 index 0000000..694ab4f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/Makefile @@ -0,0 +1,167 @@ +CC=gcc +CFLAGS=-g -DWDL_FFT_REALSIZE=8 -Wall -Wno-unused-function -Wno-multichar -Wno-unused-result -Wshadow -Wtype-limits +LFLAGS= +CXX=g++ + +ifdef DEBUG +CFLAGS += -D_DEBUG -O0 -DWDL_CHECK_FOR_NON_UTF8_FOPEN +else +CFLAGS += -DNDEBUG -O +endif + +CFLAGS += -D_FILE_OFFSET_BITS=64 + +OBJS=nseel-caltab.o nseel-compiler.o nseel-eval.o nseel-lextab.o nseel-ram.o nseel-yylex.o nseel-cfunc.o fft.o + +SWELL_OBJS= +LICE_OBJS= + +OBJS2= + +UNAME_S := $(shell uname -s) +ARCH := $(shell uname -m) + +ifeq ($(ARCH), aarch64) + ifeq ($(shell $(CC) -dumpmachine | cut -f 1 -d -), arm) + # helper for armv7l userspace on aarch64 cpu + ARCH := armv7l + endif +endif + +ifeq ($(UNAME_S),Darwin) + CC=clang + CXX=clang++ + CFLAGS += -arch $(ARCH) +endif + +ifeq ($(ARCH),arm64) + CFLAGS += -fsigned-char +else + ifneq ($(filter arm%,$(ARCH)),) + CFLAGS += -fsigned-char -mfpu=vfp -march=armv6t2 -marm + endif + ifeq ($(ARCH),aarch64) + CFLAGS += -fsigned-char + endif +endif + +ifndef ALLOW_WARNINGS + ifneq ($(UNAME_S),Darwin) + CFLAGS += -Werror + endif +endif +ifndef DEPRECATED_WARNINGS + CFLAGS += -Wno-deprecated-declarations +endif + + +default: loose_eel eel_pp + +nseel-compiler.o: glue*.h ns-eel*.h +nseel-cfunc.o: asm*.c ns-eel*.h +loose_eel.o: eel*.h ns-eel*.h +nseel-*.o: ns-eel*.h + +vpath %.cpp ../lice ../swell +vpath %.mm ../swell +vpath %.c ../ + +ifdef MAXLOOP + CFLAGS += -DNSEEL_LOOPFUNC_SUPPORT_MAXLEN=$(MAXLOOP) +else + CFLAGS += -DNSEEL_LOOPFUNC_SUPPORT_MAXLEN=0 +endif + +ifdef DISASSEMBLE + CFLAGS += -DEELSCRIPT_DO_DISASSEMBLE +endif + +ifndef NO_GFX + LICE_OBJS += lice.o lice_image.o lice_line.o lice_ico.o lice_bmp.o lice_textnew.o lice_text.o lice_arc.o + CFLAGS += -DEEL_LICE_WANT_STANDALONE + + ifeq ($(UNAME_S),Darwin) + CLANG_VER := $(shell clang --version|head -n 1| sed 's/.*version \([0-9][0-9]*\).*/\1/' ) + CLANG_GT_9 := $(shell [ $(CLANG_VER) -gt 9 ] && echo true ) + ifeq ($(CLANG_GT_9),true) + CFLAGS += -mmacosx-version-min=10.7 -stdlib=libc++ + else + CFLAGS += -mmacosx-version-min=10.5 + endif + SWELL_OBJS += swell-wnd.o swell-gdi.o swell.o swell-misc.o swell-dlg.o swell-menu.o swell-kb.o + LFLAGS += -lobjc -framework Cocoa -framework Carbon + else + + CFLAGS += -DSWELL_LICE_GDI -DSWELL_EXTRA_MINIMAL + ifdef GDK2 + CFLAGS += -DSWELL_TARGET_GDK=2 $(shell pkg-config --cflags gdk-2.0) + LFLAGS += $(shell pkg-config --libs gdk-2.0) -lX11 -lXi + else + CFLAGS += -DSWELL_TARGET_GDK=3 $(shell pkg-config --cflags gdk-3.0) + LFLAGS += $(shell pkg-config --libs gdk-3.0) -lX11 -lXi + endif + ifndef NOFREETYPE + CFLAGS += -DSWELL_FREETYPE $(shell pkg-config --cflags freetype2) + LFLAGS += $(shell pkg-config --libs freetype2) + endif + + SWELL_OBJS += swell-wnd-generic.o swell-gdi-lice.o swell.o swell-misc-generic.o \ + swell-dlg-generic.o swell-menu-generic.o swell-kb-generic.o \ + swell-gdi-generic.o swell-ini.o swell-generic-gdk.o + + LFLAGS += -ldl -lGL + endif + +endif + +ifdef PORTABLE + CFLAGS += -DEEL_TARGET_PORTABLE +else + ifeq ($(UNAME_S),Darwin) + ifeq ($(ARCH),x86_64) + ASM_FMT = macho64 + NASM_OPTS = --prefix _ + OBJS2 += asm-nseel-x64-sse.o + endif + endif + ifeq ($(UNAME_S),Linux) + ifeq ($(ARCH),x86_64) + ASM_FMT = elf64 + NASM_OPTS = + OBJS2 += asm-nseel-x64-sse.o + endif + endif + +asm-nseel-x64-sse.o: asm-nseel-x64-sse.asm + nasm -D AMD64ABI -f $(ASM_FMT) $(NASM_OPTS) asm-nseel-x64-sse.asm + +endif +CXXFLAGS=$(CFLAGS) + +ifeq ($(CXX),g++) + GCC_VER := $(shell $(CXX) --version|head -n 1| sed 's/.* \([0-9][0-9]*\)[.][0-9.]*/\1/' ) + GCC_GT_10 := $(shell [ "$(GCC_VER)" -gt 10 ] && echo true ) + ifeq ($(GCC_GT_10),true) + CXXFLAGS += -std=c++03 + endif +endif + +gen-yacc: + yacc -v -d eel2.y + +gen-lex: # the output of this, lex.nseel.c, is unused because we have a handwritten parser instead + flex eel2.l + +%.o : %.mm + $(CXX) $(CXXFLAGS) -c -o $@ $^ + +loose_eel: loose_eel.o $(OBJS) $(OBJS2) $(SWELL_OBJS) $(LICE_OBJS) + g++ -o $@ $^ $(CXXFLAGS) $(LFLAGS) + +eel_pp: eel_pp.o $(OBJS) $(OBJS2) + g++ -o $@ $^ $(CXXFLAGS) $(LFLAGS) + +clean: + -rm -f -- loose_eel loose_eel.o eel_pp.o eel_pp $(OBJS) $(SWELL_OBJS) $(LICE_OBJS) + +.PHONY: clean gen-lex gen-yacc diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/a2i.php b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/a2i.php new file mode 100644 index 0000000..1c94150 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/a2i.php @@ -0,0 +1,222 @@ +1) + $end_restore = substr($line,1-strlen($lastchunk)); + else $end_restore=""; + + $sline = substr($sline,1,strlen($sline)-1-strlen($lastchunk)); + + // get rid of chars we can ignore + $sline=preg_replace("/%\d+/","__TEMP_REPLACE__", $sline); + + $sline=str_replace("\\n","", $sline); + $sline=str_replace("\"","", $sline); + $sline=str_replace("$","", $sline); + $sline=str_replace("%","", $sline); + + + // get rid of excess whitespace, especially around commas + $sline=str_replace(" "," ", $sline); + $sline=str_replace(" "," ", $sline); + $sline=str_replace(" "," ", $sline); + $sline=str_replace(", ",",", $sline); + $sline=str_replace(" ,",",", $sline); + + $sline=preg_replace("/st\\(([0-9]+)\\)/","FPREG_$1",$sline); + + + if (preg_match("/^([0-9]+):/",trim($sline))) + { + $d = (int) $sline; + $a = strstr($sline,":"); + if ($a) $sline = substr($a,1); + + if (isset($btfut[$d]) && $btfut[$d] != "") $thislbl = $btfut[$d]; + else $thislbl = "label_" . $labelcnt++; + + $btfut[$d]=""; + $bthist[$d] = $thislbl; + + fputs($out,$thislbl . ":\n"); + } + + $sploded = explode(" ",trim($sline)); + if ($sline != "" && count($sploded)>0) + { + $inst = trim($sploded[0]); + $suffix = ""; + + $instline = strstr($sline,$inst); + $beg_restore .= substr($sline,0,-strlen($instline)); + + $parms = trim(substr($instline,strlen($inst))); + + if ($inst=="j") $inst="jmp"; + + //if ($inst == "fdiv" && $parms == "") $inst="fdivr"; + + if ($inst != "call" && substr($inst,-2) == "ll") $suffix = "ll"; + else if ($inst != "call" && $inst != "fmul" && substr($inst,-1) == "l") $suffix = "l"; + else if (substr($inst,0,1)=="f" && $inst != "fcos" && $inst != "fsincos" && $inst != "fabs" && $inst != "fchs" && substr($inst,-1) == "s") $suffix = "s"; + + + if ($suffix != "" && $inst != "jl") $inst = substr($inst,0,-strlen($suffix)); + + $parms = preg_replace("/\\((.{2,3}),(.{2,3})\\)/","($1+$2)",$parms); + + $parms=preg_replace("/EEL_F_SUFFIX (-?[0-9]+)\\((.*)\\)/","qword ptr [$2+$1]",$parms); + $parms=preg_replace("/EEL_F_SUFFIX \\((.*)\\)/","qword ptr [$1]",$parms); + + if ($inst == "sh" && $suffix == "ll") { $suffix="l"; $inst="shl"; } + + if ($suffix == "ll" || ($suffix == "l" && substr($inst,0,1) == "f" && substr($inst,0,2) != "fi")) $suffixstr = "qword ptr "; + else if ($suffix == "l") $suffixstr = "dword ptr "; + else if ($suffix == "s") $suffixstr = "dword ptr "; + else $suffixstr = ""; + $parms=preg_replace("/(-?[0-9]+)\\((.*)\\)/",$suffixstr . "[$2+$1]",$parms); + $parms=preg_replace("/\\((.*)\\)/",$suffixstr . "[$1]",$parms); + + + $parms=str_replace("EEL_F_SUFFIX","qword ptr", $parms); + + $plist = explode(",",$parms); + if (count($plist) > 2) echo "Warning: too many parameters $parms!\n"; + else if (count($plist)==2) + { + $parms = trim($plist[1]) . ", " . trim($plist[0]); + } + else + { + } + + if ($inst=="fsts") $inst="fstsw"; + if ($inst=="call" && substr($parms,0,1) == "*") $parms=substr($parms,1); + if (substr($inst,0,1) == "j") + { + if (substr($parms,-1) == "f") + { + $d = (int) substr($parms,0,-1); + if (isset($btfut[$d]) && $btfut[$d] != "") $thislbl = $btfut[$d]; + else $btfut[$d] = $thislbl = "label_" . $labelcnt++; + $parms = $thislbl; + } + else if (substr($parms,-1) == "b") + { + $d = (int) substr($parms,0,-1); + if ($bthist[$d]=="") echo "Error resolving label $parms\n"; + $parms = $bthist[$d]; + } + } + if (stristr($parms,"[0xfefefefe]")) + { + if ($inst == "fmul" || $inst=="fadd" || $inst == "fcomp") + { + if ($inst=="fmul") $hdr="0x0D"; + if ($inst=="fadd") $hdr="0x05"; + if ($inst=="fcomp") $hdr="0x1D"; + + fputs($out,"_emit 0xDC; // $inst qword ptr [0xfefefefe]\n"); + fputs($out,"_emit $hdr;\n"); + fputs($out,"_emit 0xFE;\n"); + fputs($out,"_emit 0xFE;\n"); + fputs($out,"_emit 0xFE;\n"); + fputs($out,"_emit 0xFE;\n"); + $nowrite=1; + } + } + + + $sline = $inst; + if ($parms !="") $sline .= " " . $parms; + $sline .= ";"; + + } + + $sline=preg_replace("/FPREG_([0-9]+)/","st($1)",$sline); + $line = $beg_restore . $sline . $end_restore; + + } + + + } + } + + if (!$nowrite) + { + if (strstr($line,"__TEMP_REPLACE__")) + { + $a = strstr($line,"//REPLACE="); + if ($a === false) die ("__TEMP_REPLACE__ found, no REPLACE=\n"); + $line=str_replace("__TEMP_REPLACE__",substr($a,10),$line); + } + fputs($out,$line . "\n"); + } +} + +if ($inblock) echo "Error (ended in __asm__ block???)\n"; + + +fclose($in); +fclose($out); + +}; + +process_file("asm-nseel-x86-gcc.c" , "asm-nseel-x86-msvc.c"); +// process_file("asm-miscfunc-x86-gcc.c" , "asm-miscfunc-x86-msvc.c"); +//process_file("asm-megabuf-x86-gcc.c" , "asm-megabuf-x86-msvc.c"); + +?> diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-aarch64-gcc.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-aarch64-gcc.c new file mode 100644 index 0000000..c822107 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-aarch64-gcc.c @@ -0,0 +1,1270 @@ +#define FUNCTION_MARKER "mov x0, x0\n" \ + "mov x1, x1\n" \ + "mov x2, x2\n" + +#if EEL_F_SIZE == 8 + +void nseel_asm_1pdd(void) +{ + + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "str x30, [sp, #-16]!\n" + "blr x3\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: ); +} + +void nseel_asm_2pdd(void) +{ + + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "fmov d2, d0\n" + "fmov d0, d1\n" + "fmov d1, d2\n" + "str x30, [sp, #-16]!\n" + "blr x3\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: ); +}; + +void nseel_asm_2pdds(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "stp x1, x30, [sp, #-16]!\n" + "fmov d1, d0\n" + "ldr d0, [x1]\n" + "blr x3\n" + "ldp x0, x30, [sp], 16\n" + "str d0, [x0]\n" + FUNCTION_MARKER + :: ); +} + +#else // 32 bit floating point calls + +#error no 32 bit float support + +#endif + +//--------------------------------------------------------------------------------------------------------------- + + +void nseel_asm_invsqrt(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x0, 0x59df\n" + "movk x0, 0x5f37, lsl 16\n" + "fcvt s2, d0\n" + "ldr d3, [x21, #32]\n" + "fmov w1, s2\n" + "fmul d0, d0, d3\n" + "asr w1, w1, #1\n" + + "sub w0, w0, w1\n" + + "fmov s4, w0\n" + "fcvt d1, s4\n" + + "ldr d2, [x21, #40]\n" + "fmul d0, d0, d1\n" + "fmul d0, d0, d1\n" + "fadd d0, d0, d2\n" + "fmul d0, d0, d1\n" + + FUNCTION_MARKER + ); +} + +void nseel_asm_dbg_getstackptr(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x0, sp\n" + "ucvtf d0, x0\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_sqr(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fmul d0, d0, d0\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_abs(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fabs d0, d0\n" + FUNCTION_MARKER + ); +} + + +#define FLUSH_TO_ZERO \ + "ldr x1, [x1]\n" \ + "mov x3, #0x10000000000000\n" \ + "mov x2, #0x20000000000000\n" \ + "add x1, x1, x3\n" \ + "and x1, x1, #0x7ff0000000000000\n" \ + "cmp x1, x2\n" \ + "bgt 0f\n" \ + "str xzr, [x0]\n" \ + "0:\n" + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d0, [x0]\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign_fromfp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x0, x1\n" + "str d0, [x1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d0, [x0]\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign_fast_fromfp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_add(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fadd d0, d1, d0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_add_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fadd d0, d1, d0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +void nseel_asm_add_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fadd d0, d1, d0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_sub(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fsub d0, d1, d0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_sub_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fsub d0, d1, d0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +void nseel_asm_sub_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fsub d0, d1, d0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_mul(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fmul d0, d1, d0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_mul_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fmul d0, d0, d1\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +void nseel_asm_mul_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fmul d0, d0, d1\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_div(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fdiv d0, d1, d0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_div_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fdiv d0, d1, d0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +void nseel_asm_div_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fdiv d0, d1, d0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_mod(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fabs d0, d0\n" + "fabs d1, d1\n" + "fcvtzu x1, d0\n" + "fcvtzu x0, d1\n" + "udiv x2, x0, x1\n" + "msub x0, x2, x1, x0\n" + "ucvtf d0, x0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_shl(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcvtzs w0, d1\n" + "fcvtzs w1, d0\n" + "lsl w0, w0, w1\n" + "scvtf d0, w0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_shr(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcvtzs w0, d1\n" + "fcvtzs w1, d0\n" + "asr w0, w0, w1\n" + "scvtf d0, w0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_mod_op(void) +{ + + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fabs d0, d0\n" + "fabs d1, d1\n" + "fcvtzu x3, d0\n" + "fcvtzu x0, d1\n" + "udiv x2, x0, x3\n" + "msub x0, x2, x3, x0\n" + "ucvtf d0, x0\n" + + "str d0, [x1]\n" + "mov x0, x1\n" + FUNCTION_MARKER + ); + +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_or(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcvtzs x0, d0\n" + "fcvtzs x1, d1\n" + "orr x0, x0, x1\n" + "scvtf d0, x0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_or0(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcvtzs x0, d0\n" + "scvtf d0, x0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_or_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fcvtzs x0, d0\n" + "fcvtzs x3, d1\n" + "orr x0, x0, x3\n" + "scvtf d0, x0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_xor(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcvtzs x0, d0\n" + "fcvtzs x1, d1\n" + "eor x0, x0, x1\n" + "scvtf d0, x0\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_xor_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fcvtzs x0, d0\n" + "fcvtzs x3, d1\n" + "eor x0, x0, x3\n" + "scvtf d0, x0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_and(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcvtzs x0, d0\n" + "fcvtzs x1, d1\n" + "and x0, x0, x1\n" + "scvtf d0, x0\n" + FUNCTION_MARKER + );} + +void nseel_asm_and_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x1]\n" + "fcvtzs x0, d0\n" + "fcvtzs x3, d1\n" + "and x0, x0, x3\n" + "scvtf d0, x0\n" + "mov x0, x1\n" + "str d0, [x1]\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_uminus(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fneg d0, d0\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_sign(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fmov d1, #-1.0\n" + "fmov d2, #1.0\n" + "fcmpe d0, #0.0\n" + "fcsel d0, d0, d1, gt\n" + "fcsel d0, d0, d2, lt\n" + FUNCTION_MARKER + :: + ); +} + + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_bnot(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp w0, #0\n" + "cset w0, eq\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_if(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" + "mov x1, 0xdead\n" + "movk x1, 0xbeef, lsl 16\n" + "movk x1, 0xbeef, lsl 32\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "cmp w0, #0\n" + "csel x1, x1, x2, ne\n" + "blr x1\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_repeat(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcvtzs w3, d0\n" + "cmp w3, #0\n" + "ble 0f\n" +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + "mov x2, %0\n" + "movk x2, %1, lsl 16\n" + "cmp w3, w2\n" + "csel w3, w2, w3, gt\n" +#endif + "stp x24, x30, [sp, #-16]!\n" + + "mov x24, 0xdead\n" + "movk x24, 0xbeef, lsl 16\n" + "movk x24, 0xbeef, lsl 32\n" + "1:\n" + "stp x3, x22, [sp, #-16]!\n" + "blr x24\n" + "ldp x3, x22, [sp], 16\n" + "sub x3, x3, #1\n" + "cmp x3, #0\n" + "bgt 1b\n" + "ldp x24, x30, [sp], 16\n" + "0:\n" + FUNCTION_MARKER +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + ::"g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN&65535), + "g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16) +#endif + ); +} + +void nseel_asm_repeatwhile(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + "mov x3, %0\n" + "movk x3, %1, lsl 16\n" +#endif + "stp x24, x30, [sp, #-16]!\n" + + "mov x24, 0xdead\n" + "movk x24, 0xbeef, lsl 16\n" + "movk x24, 0xbeef, lsl 32\n" + "0:\n" + "stp x3, x22, [sp, #-16]!\n" + "blr x24\n" + "ldp x3, x22, [sp], 16\n" + "cmp w0, #0\n" +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + "beq 0f\n" + "sub x3, x3, #1\n" + "cmp x3, #0\n" + "bne 0b\n" + "0:\n" +#else + "bne 0b\n" +#endif + "ldp x24, x30, [sp], 16\n" + + FUNCTION_MARKER +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + ::"g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN&65535), + "g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16) +#endif + ); +} + + +void nseel_asm_band(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp w0, #0\n" + "beq 0f\n" + "mov x3, 0xdead\n" + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "str x30, [sp, #-16]!\n" + "blr x3\n" + "ldr x30, [sp], #16\n" + "0:\n" + FUNCTION_MARKER + :: ); +} + +void nseel_asm_bor(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp w0, #0\n" + "bne 0f\n" + "mov x3, 0xdead\n" + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "str x30, [sp, #-16]!\n" + "blr x3\n" + "ldr x30, [sp], #16\n" + "0:\n" + FUNCTION_MARKER + :: ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_equal(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d2, [x21]\n" + "fsub d0, d0, d1\n" + "fabs d0, d0\n" + "fcmp d0, d2\n" + "cset w0, lt\n" + FUNCTION_MARKER + :: + ); +} +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_equal_exact(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "cset w0, eq\n" + FUNCTION_MARKER + :: + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_notequal_exact(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "cset w0, ne\n" + FUNCTION_MARKER + :: + ); +} +// +// +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_notequal(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d2, [x21]\n" + "fsub d0, d0, d1\n" + "fabs d0, d0\n" + "fcmp d0, d2\n" + "cset w0, ge\n" + FUNCTION_MARKER + :: + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_below(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "cset w0, lt\n" + FUNCTION_MARKER + :: + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_beloweq(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "cset w0, le\n" + FUNCTION_MARKER + :: + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_above(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "cset w0, gt\n" + FUNCTION_MARKER + :: + ); +} + +void nseel_asm_aboveeq(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "cset w0, ge\n" + FUNCTION_MARKER + :: + ); +} + + + +void nseel_asm_min(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d0, [x0]\n" + "ldr d1, [x1]\n" + "fcmp d1, d0\n" + "csel x0, x1, x0, lt\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_max(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d0, [x0]\n" + "ldr d1, [x1]\n" + "fcmp d1, d0\n" + "csel x0, x1, x0, gt\n" + FUNCTION_MARKER + ); +} + + + +void nseel_asm_min_fp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "fcsel d0, d1, d0, lt\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_max_fp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmp d1, d0\n" + "fcsel d0, d1, d0, gt\n" + FUNCTION_MARKER + ); +} + + + + + + + +void _asm_generic3parm(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" // input: r0 last, r1=second to last, r2=third to last + // output: r0=context, r1=r2, r2=r1, r3=r0 + "mov x3, x0\n" // r0 (last parameter) -> r3 + + "mov x0, 0xdead\n" // r0 is first parm (context) + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x4, 0xdead\n" + "movk x4, 0xbeef, lsl 16\n" + "movk x4, 0xbeef, lsl 32\n" + + "mov x5, x1\n" // swap x1/x2 + "mov x1, x2\n" + "mov x2, x5\n" + + "blr x4\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: + ); +} + +void _asm_generic3parm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" // input: r0 last, r1=second to last, r2=third to last + "mov x3, x0\n" // r0 (last parameter) -> r3 + + "mov x0, 0xdead\n" // r0 is first parm (context) + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x4, 0xdead\n" + "movk x4, 0xbeef, lsl 16\n" + "movk x4, 0xbeef, lsl 32\n" + + "mov x5, x1\n" // swap x1/x2 + "mov x1, x2\n" + "mov x2, x5\n" + + "blr x4\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: + ); +} + + +void _asm_generic2parm(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" // input: r0 last, r1=second to last + "mov x2, x0\n" + + "mov x0, 0xdead\n" // r0 is first parm (context) + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x4, 0xdead\n" + "movk x4, 0xbeef, lsl 16\n" + "movk x4, 0xbeef, lsl 32\n" + + "blr x4\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: + ); +} + + +void _asm_generic2parm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" // input: r0 last, r1=second to last + "mov x2, x0\n" + + "mov x0, 0xdead\n" // r0 is first parm (context) + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x4, 0xdead\n" + "movk x4, 0xbeef, lsl 16\n" + "movk x4, 0xbeef, lsl 32\n" + + "blr x4\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: + ); +} + +void _asm_generic2xparm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" // input: r0 last, r1=second to last + "mov x2, x1\n" + "mov x3, x0\n" + + "mov x0, 0xdead\n" // r0 is first parm (context) + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x1, 0xdead\n" // second parm + "movk x1, 0xbeef, lsl 16\n" + "movk x1, 0xbeef, lsl 32\n" + + "mov x4, 0xdead\n" + "movk x4, 0xbeef, lsl 16\n" + "movk x4, 0xbeef, lsl 32\n" + + "blr x4\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: + ); +} + + +void _asm_generic1parm(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" + "mov x1, x0\n" + + "mov x0, 0xdead\n" // r0 is first parm (context) + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x4, 0xdead\n" + "movk x4, 0xbeef, lsl 16\n" + "movk x4, 0xbeef, lsl 32\n" + + "blr x4\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + :: + ); +} + + + +void _asm_generic1parm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str x30, [sp, #-16]!\n" + "mov x1, x0\n" + + "mov x0, 0xdead\n" // r0 is first parm (context) + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x4, 0xdead\n" + "movk x4, 0xbeef, lsl 16\n" + "movk x4, 0xbeef, lsl 32\n" + + "blr x4\n" + "ldr x30, [sp], #16\n" + + FUNCTION_MARKER + :: + ); +} + + + + +void _asm_megabuf(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x20, #-8]\n" + "fadd d0, d0, d1\n" + "fcvtzu w3, d0\n" + "asr w2, w3, %0\n" + "bic w2, w2, #7\n" // r2 is page index*8 + "cmp w2, %1\n" + "bhs 0f\n" + + "add x2, x2, x20\n" + "ldr x2, [x2]\n" + "cmp x2, #0\n" + "beq 0f\n" + + "mov x0, %2\n" + "and x3, x3, x0\n" // r3 mask item in slot + "add x0, x2, x3, lsl #3\n" // set result + "b 1f\n" + "0:\n" + + // failed, call stub function + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "str x30, [sp, #-16]!\n" + "mov x0, x20\n" // first parameter: blocks + "mov x1, x3\n" // second parameter: slot index + "blr x2\n" + "ldr x30, [sp], #16\n" + "1:\n" + + FUNCTION_MARKER + :: + "i" (NSEEL_RAM_ITEMSPERBLOCK_LOG2 - 3/*log2(sizeof(void*))*/), + "i" (NSEEL_RAM_BLOCKS*sizeof(void*)), + "i" (NSEEL_RAM_ITEMSPERBLOCK-1) + ); +} + + +void _asm_gmegabuf(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x0, 0xdead\n" + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + + "ldr d1, [x20, #-8]\n" + "fadd d0, d0, d1\n" + + "fcvtzu w1, d0\n" + + "str x30, [sp, #-16]!\n" + + "blr x2\n" + + "ldr x30, [sp], #16\n" + + FUNCTION_MARKER + :: + ); +} + + +void nseel_asm_fcall(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x0, 0xdead\n" + "movk x0, 0xbeef, lsl 16\n" + "movk x0, 0xbeef, lsl 32\n" + "str x30, [sp, #-16]!\n" + "blr x0\n" + "ldr x30, [sp], #16\n" + FUNCTION_MARKER + ); +} + + + +void nseel_asm_stack_push(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d0, [x0]\n" + + "mov x3, 0xdead\n" // r3 is stack + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "ldr x0, [x3]\n" + + "add x0, x0, #8\n" + + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "and x0, x0, x2\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "orr x0, x0, x2\n" + + "str x0, [x3]\n" + "str d0, [x0]\n" + + FUNCTION_MARKER + ); +} + +void nseel_asm_stack_pop(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" // r3 is stack + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "ldr x1, [x3]\n" + "ldr d0, [x1]\n" + "sub x1, x1, #8\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "str d0, [x0]\n" + "and x1, x1, x2\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "orr x1, x1, x2\n" + + "str x1, [x3]\n" + FUNCTION_MARKER + ); +} + + + +void nseel_asm_stack_pop_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" // r3 is stack + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "ldr x1, [x3]\n" + "mov x0, x1\n" + "sub x1, x1, #8\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "and x1, x1, x2\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "orr x1, x1, x2\n" + "str x1, [x3]\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_stack_peek(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" // r3 is stack + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + + "fcvtzs w2, d0\n" + + "ldr x1, [x3]\n" + "sub x1, x1, x2, lsl #3\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "and x1, x1, x2\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "orr x0, x1, x2\n" + FUNCTION_MARKER + ); +} + + +void nseel_asm_stack_peek_top(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" // r3 is stack + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "ldr x0, [x3]\n" + FUNCTION_MARKER + ); +} + + +void nseel_asm_stack_peek_int(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" // r3 is stack + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + + "ldr x1, [x3]\n" + "sub x1, x1, x2\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "and x1, x1, x2\n" + "mov x2, 0xdead\n" + "movk x2, 0xbeef, lsl 16\n" + "movk x2, 0xbeef, lsl 32\n" + "orr x0, x1, x2\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_stack_exch(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov x3, 0xdead\n" // r3 is stack + "movk x3, 0xbeef, lsl 16\n" + "movk x3, 0xbeef, lsl 32\n" + "ldr x1, [x3]\n" + "ldr d0, [x0]\n" + "ldr d1, [x1]\n" + "str d0, [x1]\n" + "str d1, [x0]\n" + FUNCTION_MARKER + ); +} + + +void nseel_asm_booltofp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp w0, #0\n" + "fmov d0, #1.0\n" + "movi d1, #0\n" + "fcsel d0, d0, d1, ne\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_fptobool(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x21]\n" + "fabs d0, d0\n" + "fcmp d0, d1\n" + "cset w0, ge\n" + FUNCTION_MARKER + :: + ); +} + +void nseel_asm_fptobool_rev(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ldr d1, [x21]\n" + "fabs d0, d0\n" + "fcmp d0, d1\n" + "cset w0, lt\n" + FUNCTION_MARKER + :: + ); +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-aarch64-msvc.asm b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-aarch64-msvc.asm new file mode 100644 index 0000000..a585912 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-aarch64-msvc.asm @@ -0,0 +1,1418 @@ +NSEEL_RAM_ITEMSPERBLOCK_LOG2 equ 16 +NSEEL_RAM_BLOCKS_LOG2 equ 9 +NSEEL_LOOPFUNC_SUPPORT_MAXLEN equ 1048576 + +NSEEL_RAM_BLOCKS equ (1 << NSEEL_RAM_BLOCKS_LOG2) +NSEEL_RAM_ITEMSPERBLOCK equ (1< 0 + mov x2, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN & 65535) + movk x2, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16), lsl 16 + cmp w3, w2 + csel w3, w2, w3, gt + ] + stp fp, lr, [sp, #-48]! + mov fp, sp + str x24, [sp, #16] + + mov x24, 0xdead + movk x24, 0xbeef, lsl 16 + movk x24, 0xbeef, lsl 32 +|rep_lz| + stp x3, x22, [sp, #32] + blr x24 + ldp x3, x22, [sp, #32] + sub x3, x3, #1 + cmp x3, #0 + bgt |rep_lz| + ldr x24, [sp, #16] + ldp fp, lr, [sp], #48 +|rep_lz2| + mov x1, x1 + + ENDP + +|nseel_asm_repeatwhile| PROC + + + mov x1, x1 + [ NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + mov x3, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN & 65535) + movk x3, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16), lsl 16 + ] + stp fp, lr, [sp, #-48]! + str x24, [sp, #16] + + mov x24, 0xdead + movk x24, 0xbeef, lsl 16 + movk x24, 0xbeef, lsl 32 +|repw_lz| + stp x3, x22, [sp, #32] + blr x24 + ldp x3, x22, [sp, #32] + cmp w0, #0 + [ NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + beq |repw_lz2| + sub x3, x3, #1 + cmp x3, #0 + bne |repw_lz| +|repw_lz2| + ] + [ NSEEL_LOOPFUNC_SUPPORT_MAXLEN == 0 + bne |repw_lz| + ] + ldr x24, [sp, #16] + ldp fp, lr, [sp], #48 + + mov x1, x1 + + ENDP + + +|nseel_asm_band| PROC + + + mov x1, x1 + cmp w0, #0 + beq |band_chk| + mov x3, 0xdead + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + blr x3 + ldp fp, lr, [sp], #16 +|band_chk| + mov x1, x1 + ENDP + +|nseel_asm_bor| PROC + + + mov x1, x1 + cmp w0, #0 + bne |bor_chk| + mov x3, 0xdead + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + blr x3 + ldp fp, lr, [sp], #16 +|bor_chk| + mov x1, x1 + ENDP + + +|nseel_asm_equal| PROC + + + mov x1, x1 + ldr d2, [x21] + fsub d0, d0, d1 + fabs d0, d0 + fcmp d0, d2 + cset w0, lt + mov x1, x1 + + ENDP + +|nseel_asm_equal_exact| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, eq + mov x1, x1 + + ENDP + + +|nseel_asm_notequal_exact| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, ne + mov x1, x1 + + ENDP + + + + +|nseel_asm_notequal| PROC + + + mov x1, x1 + ldr d2, [x21] + fsub d0, d0, d1 + fabs d0, d0 + fcmp d0, d2 + cset w0, ge + mov x1, x1 + + ENDP + + + +|nseel_asm_below| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, lt + mov x1, x1 + + ENDP + + +|nseel_asm_beloweq| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, le + mov x1, x1 + + ENDP + + + +|nseel_asm_above| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, gt + mov x1, x1 + + ENDP + +|nseel_asm_aboveeq| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, ge + mov x1, x1 + + ENDP + + + +|nseel_asm_min| PROC + + + mov x1, x1 + ldr d0, [x0] + ldr d1, [x1] + fcmp d1, d0 + csel x0, x1, x0, lt + mov x1, x1 + + ENDP + +|nseel_asm_max| PROC + + + mov x1, x1 + ldr d0, [x0] + ldr d1, [x1] + fcmp d1, d0 + csel x0, x1, x0, gt + mov x1, x1 + + ENDP + + + +|nseel_asm_min_fp| PROC + + + mov x1, x1 + fcmp d1, d0 + fcsel d0, d1, d0, lt + mov x1, x1 + + ENDP + +|nseel_asm_max_fp| PROC + + + mov x1, x1 + fcmp d1, d0 + fcsel d0, d1, d0, gt + mov x1, x1 + + ENDP + + + + + + + +|_asm_generic3parm| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last, r2=third to last + mov fp, sp + + mov x3, x0 ; r0 (last parameter) -> r3 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + mov x5, x1 ; swap x1/x2 + mov x1, x2 + mov x2, x5 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + +|_asm_generic3parm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last, r2=third to last + mov fp, sp + mov x3, x0 ; r0 (last parameter) -> r3 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + mov x5, x1 ; // swap x1/x2 + mov x1, x2 + mov x2, x5 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + +|_asm_generic2parm| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last + mov fp, sp + mov x2, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + +|_asm_generic2parm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last + mov fp, sp + mov x2, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + +|_asm_generic2xparm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last + mov fp, sp + mov x2, x1 + mov x3, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x1, 0xdead ; second parm + movk x1, 0xbeef, lsl 16 + movk x1, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + +|_asm_generic1parm| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! + mov fp, sp + mov x1, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + + +|_asm_generic1parm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! + mov fp, sp + mov x1, x0 + + mov x0, 0xdead ; // r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + + mov x1, x1 + + ENDP + + + + +|_asm_megabuf| PROC + + + mov x1, x1 + add x0, x20, #-8 + ldr d1, [x0] + fadd d0, d0, d1 + fcvtzu w3, d0 + asr w2, w3, (NSEEL_RAM_ITEMSPERBLOCK_LOG2 - 3) + bic w2, w2, #7 ; r2 is page index*8 + mov w0, (NSEEL_RAM_BLOCKS * 8) + cmp w2, w0 + bhs |mbchk1| + + add x2, x2, x20 + ldr x2, [x2] + cmp x2, #0 + beq |mbchk1| + + mov x0, (NSEEL_RAM_ITEMSPERBLOCK - 1) + and x3, x3, x0 ; r3 mask item in slot + add x0, x2, x3, lsl #3 ; set result + b |mbchk2| +|mbchk1| + + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + mov x0, x20 ; first parameter: blocks + mov x1, x3 ; second parameter: slot index + blr x2 + ldp fp, lr, [sp], #16 +|mbchk2| + + mov x1, x1 + + ENDP + + +|_asm_gmegabuf| PROC + + + mov x1, x1 + add x2, x20, #-8 + ldr d1, [x2] + + mov x0, 0xdead + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + + fadd d0, d0, d1 + + fcvtzu w1, d0 + + stp fp, lr, [sp, #-16]! + mov fp, sp + + blr x2 + + ldp fp, lr, [sp], #16 + + mov x1, x1 + + ENDP + + +|nseel_asm_fcall| PROC + + + mov x1, x1 + mov x0, 0xdead + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + blr x0 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + + +|nseel_asm_stack_push| PROC + + + mov x1, x1 + ldr d0, [x0] + + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x0, [x3] + + add x0, x0, #8 + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x0, x0, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x0, x0, x2 + + str x0, [x3] + str d0, [x0] + + mov x1, x1 + + ENDP + +|nseel_asm_stack_pop| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x1, [x3] + ldr d0, [x1] + sub x1, x1, #8 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + str d0, [x0] + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x1, x1, x2 + + str x1, [x3] + mov x1, x1 + + ENDP + + + +|nseel_asm_stack_pop_fast| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x1, [x3] + mov x0, x1 + sub x1, x1, #8 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x1, x1, x2 + str x1, [x3] + mov x1, x1 + + ENDP + +|nseel_asm_stack_peek| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + + fcvtzs w2, d0 + + ldr x1, [x3] + sub x1, x1, x2, lsl #3 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x0, x1, x2 + mov x1, x1 + + ENDP + + +|nseel_asm_stack_peek_top| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x0, [x3] + mov x1, x1 + + ENDP + + +|nseel_asm_stack_peek_int| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + + ldr x1, [x3] + sub x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x0, x1, x2 + mov x1, x1 + + ENDP + +|nseel_asm_stack_exch| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x1, [x3] + ldr d0, [x0] + ldr d1, [x1] + str d0, [x1] + str d1, [x0] + mov x1, x1 + + ENDP + + +|nseel_asm_booltofp| PROC + + + mov x1, x1 + cmp w0, #0 + fmov d0, #1.0 + movi d1, #0 + fcsel d0, d0, d1, ne + mov x1, x1 + + ENDP + +|nseel_asm_fptobool| PROC + + + mov x1, x1 + ldr d1, [x21] + fabs d0, d0 + fcmp d0, d1 + cset w0, ge + mov x1, x1 + + ENDP + +|nseel_asm_fptobool_rev| PROC + + + mov x1, x1 + ldr d1, [x21] + fabs d0, d0 + fcmp d0, d1 + cset w0, lt + mov x1, x1 + + ENDP + +|eel_callcode64| PROC + stp fp, lr, [sp, #-128]! + mov fp, sp + + stp d13, d12, [sp, #16] + stp d11, d10, [sp, #32] + stp d9, d8, [sp, #48] + stp d15, d14, [sp, #64] + stp x20, x19, [sp, #80] + stp x18, x21, [sp, #96] + stp x22, x23, [sp, #112] + + mov x19, x0 + mov x20, x2 + mov x21, x3 + blr x1 + + ldp d13, d12, [sp, #16] + ldp d11, d10, [sp, #32] + ldp d9, d8, [sp, #48] + ldp d15, d14, [sp, #64] + ldp x20, x19, [sp, #80] + ldp x18, x21, [sp, #96] + ldp x22, x23, [sp, #112] + ldp fp, lr, [sp], #128 + ret + + ENDP + +|glue_getscr| PROC + mrs x0, FPCR + ret + ENDP + +|glue_setscr| PROC + msr FPCR, x0 + ret + ENDP + + END diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm-gcc.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm-gcc.c new file mode 100644 index 0000000..8893796 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm-gcc.c @@ -0,0 +1,1308 @@ +#define FUNCTION_MARKER "mov r0, r0\n" \ + "mov r1, r1\n" \ + "mov r2, r2\n" + +#if EEL_F_SIZE == 8 + +__attribute__((naked)) void nseel_asm_1pdd(void) +{ + + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "str lr, [sp, #-8]!\n" + "blx r3\n" + "ldr lr, [sp], #8\n" + FUNCTION_MARKER + :: ); +} + +__attribute__((naked)) void nseel_asm_2pdd(void) +{ + + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "fcpyd d2, d0\n" + "fcpyd d0, d1\n" + "fcpyd d1, d2\n" + "str lr, [sp, #-8]!\n" + "blx r3\n" + "ldr lr, [sp], #8\n" + FUNCTION_MARKER + :: ); +}; + +__attribute__((naked)) void nseel_asm_2pdds(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "push {r1, lr}\n" + "fcpyd d1, d0\n" + "fldd d0, [r1]\n" + "blx r3\n" + "pop {r0, lr}\n" + "fstd d0, [r0]\n" + FUNCTION_MARKER + :: ); +} + +#else // 32 bit floating point calls + +#error no 32 bit float support + +#endif + +//--------------------------------------------------------------------------------------------------------------- + + +__attribute__((naked)) void nseel_asm_invsqrt(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r0, 0x59df\n" + "movt r0, 0x5f37\n" + "fcvtsd s2, d0\n" + "fldd d3, [r6, #32]\n" + "fmrs r1, s2\n" + "fmuld d0, d0, d3\n" + "mov r1, r1, asr #1\n" + + "sub r0, r0, r1\n" + + "fmsr s4, r0\n" + "fcvtds d1, s4\n" + + "fldd d2, [r6, #40]\n" + "fmuld d0, d0, d1\n" + "fmuld d0, d0, d1\n" + "faddd d0, d0, d2\n" + "fmuld d0, d0, d1\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_dbg_getstackptr(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fmsr s0, sp\n" + "fsitod d0, s0\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_sqr(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fmuld d0, d0, d0\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_abs(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fabsd d0, d0\n" + FUNCTION_MARKER + ); +} + +#define FLUSH_TO_ZERO \ + "ldr r1, [r0, #4]\n" \ + "movs r2, #0\n" \ + "movt r2, #0x7ff0\n" \ + "add r1, r1, #0x00100000\n" \ + "ands r1, r1, r2\n" \ + "cmp r1, #0x00200000\n" \ + "bgt 0f\n" \ + "movs r2, #0\n" \ + "movs r3, #0\n" \ + "strd r2, [r0]\n" \ + "0:\n" + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_assign(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d0, [r0]\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_assign_fromfp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov r0, r1\n" + "fstd d0, [r1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_assign_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d0, [r0]\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_assign_fast_fromfp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_add(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "faddd d0, d1, d0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_add_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "faddd d0, d1, d0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_add_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "faddd d0, d1, d0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_sub(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fsubd d0, d1, d0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_sub_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "fsubd d0, d1, d0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_sub_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "fsubd d0, d1, d0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_mul(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fmuld d0, d0, d1\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_mul_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "fmuld d0, d0, d1\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_mul_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "fmuld d0, d0, d1\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_div(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fdivd d0, d1, d0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_div_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "fdivd d0, d1, d0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_div_op_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "fdivd d0, d1, d0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_mod(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ftouizd s0, d0\n" // round to unsigned integers + "fmrs r3, s0\n" + "fuitod d0, s0\n" // divisor + "ftouizd s2, d1\n" + + "cmp r3, #0\n" + "beq 0f\n" + + "fuitod d1, s2\n" // value + "fdivd d2, d1, d0\n" + "ftouizd s4, d2\n" + "fuitod d2, s4\n" + "fmuld d2, d2, d0\n" + "fsubd d0, d1, d2\n" + "0:\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_shl(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "mov r3, r2, asl r3\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_shr(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "mov r3, r2, asr r3\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_mod_op(void) +{ + + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "ftouizd s0, d0\n" // round to unsigned integers + "fmrs r3, s0\n" + "fuitod d0, s0\n" // divisor + "ftouizd s2, d1\n" + + "cmp r3, #0\n" + "beq 0f\n" + + "fuitod d1, s2\n" // value + "fdivd d2, d1, d0\n" + "ftouizd s4, d2\n" + "fuitod d2, s4\n" + "fmuld d2, d2, d0\n" + "fsubd d0, d1, d2\n" + "0:\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FLUSH_TO_ZERO + FUNCTION_MARKER + ); + +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_or(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "orr r3, r3, r2\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_or0(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "fsitod d0, s0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_or_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "orr r3, r3, r2\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_xor(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "eor r3, r3, r2\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_xor_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "eor r3, r3, r2\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_and(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "and r3, r3, r2\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + FUNCTION_MARKER + );} + +__attribute__((naked)) void nseel_asm_and_op(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r1]\n" + "ftosizd s0, d0\n" + "ftosizd s1, d1\n" + "fmrs r3, s0\n" + "fmrs r2, s1\n" + "and r3, r3, r2\n" + "fmsr s0, r3\n" + "fsitod d0, s0\n" + "mov r0, r1\n" + "fstd d0, [r1]\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_uminus(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fnegd d0, d0\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_sign(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpzd d0\n" + "fmstat\n" + "flddgt d0, [r6, #16]\n" + "flddlt d0, [r6, #24]\n" + FUNCTION_MARKER + :: + ); +} + + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_bnot(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp r0, #0\n" + "movne r0, #0\n" + "moveq r0, #1\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_if(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str lr, [sp, #-8]!\n" + "movw r1, 0xdead\n" + "movt r1, 0xbeef\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "cmp r0, #0\n" + "moveq r1, r2\n" + "blx r1\n" + "ldr lr, [sp], #8\n" + FUNCTION_MARKER + :: ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_repeat(void) +{ +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "fmrs r3, s0\n" + "cmp r3, #0\n" + "ble 0f\n" + "movw r2, %0\n" + "movt r2, %1\n" + "cmp r3, r2\n" + "movgt r3, r2\n" + "push {r10,lr}\n" + + "movw r10, 0xdead\n" + "movt r10, 0xbeef\n" + "1:\n" + "push {r3,r5}\n" // save counter + worktable + "blx r10\n" + "pop {r3,r5}\n" + "sub r3, r3, #1\n" + "cmp r3, #0\n" + "bgt 1b\n" + "pop {r10,lr}\n" + + "0:\n" + FUNCTION_MARKER + ::"g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN&65535), + "g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16) + ); +#else + __asm__ __volatile__( + FUNCTION_MARKER + "ftosizd s0, d0\n" + "fmrs r3, s0\n" + "cmp r3, #0\n" + "ble 0f\n" + "push {r10,lr}\n" + + "movw r10, 0xdead\n" + "movt r10, 0xbeef\n" + "1:\n" + "push {r3,r5}\n" // save counter + worktable + "blx r10\n" + "pop {r3,r5}\n" + "sub r3, r3, #1\n" + "cmp r3, #0\n" + "bgt 1b\n" + "pop {r10,lr}\n" + + "0:\n" + FUNCTION_MARKER + ); +#endif +} + +__attribute__((naked)) void nseel_asm_repeatwhile(void) +{ +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, %0\n" + "movt r3, %1\n" + "push {r10,lr}\n" + + "movw r10, 0xdead\n" + "movt r10, 0xbeef\n" + "0:\n" + "push {r3,r5}\n" // save counter + worktable + "blx r10\n" + "pop {r3,r5}\n" + "sub r3, r3, #1\n" + "cmp r0, #0\n" + "cmpne r3, #0\n" + "bne 0b\n" + "pop {r10,lr}\n" + + FUNCTION_MARKER + ::"g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN&65535), + "g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16) + ); +#else + __asm__ __volatile__( + FUNCTION_MARKER + "push {r10,lr}\n" + + "movw r10, 0xdead\n" + "movt r10, 0xbeef\n" + "0:\n" + "push {r3,r5}\n" // save worktable (r3 just for alignment) + "blx r10\n" + "pop {r3,r5}\n" + "cmp r0, #0\n" + "bne 0b\n" + "pop {r10,lr}\n" + + "0:\n" + FUNCTION_MARKER + ); +#endif +} + + +__attribute__((naked)) void nseel_asm_band(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp r0, #0\n" + "beq 0f\n" + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "push {r3, lr}\n" + "blx r3\n" + "pop {r3, lr}\n" + "0:\n" + FUNCTION_MARKER + :: ); +} + +__attribute__((naked)) void nseel_asm_bor(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp r0, #0\n" + "bne 0f\n" + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "push {r3, lr}\n" + "blx r3\n" + "pop {r3, lr}\n" + "0:\n" + FUNCTION_MARKER + :: ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_equal(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d2, [r6]\n" + "fsubd d0, d0, d1\n" + "fabsd d0, d0\n" + "fcmpd d2, d0\n" + "fmstat\n" + "movlt r0, #0\n" + "movge r0, #1\n" + FUNCTION_MARKER + :: + ); +} +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_equal_exact(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d0, d1\n" + "fmstat\n" + "movne r0, #0\n" + "moveq r0, #1\n" + FUNCTION_MARKER + :: + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_notequal_exact(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d0, d1\n" + "fmstat\n" + "moveq r0, #0\n" + "movne r0, #1\n" + FUNCTION_MARKER + :: + ); +} +// +// +// +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_notequal(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d2, [r6]\n" + "fsubd d0, d0, d1\n" + "fabsd d0, d0\n" + "fcmpd d2, d0\n" + "fmstat\n" + "movlt r0, #1\n" + "movge r0, #0\n" + FUNCTION_MARKER + :: + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_below(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d1, d0\n" + "fmstat\n" + "movlt r0, #1\n" + "movge r0, #0\n" + FUNCTION_MARKER + :: + ); +} + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_beloweq(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d1, d0\n" + "fmstat\n" + "movle r0, #1\n" + "movgt r0, #0\n" + FUNCTION_MARKER + :: + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +__attribute__((naked)) void nseel_asm_above(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d1, d0\n" + "fmstat\n" + "movgt r0, #1\n" + "movle r0, #0\n" + FUNCTION_MARKER + :: + ); +} + +__attribute__((naked)) void nseel_asm_aboveeq(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d1, d0\n" + "fmstat\n" + "movge r0, #1\n" + "movlt r0, #0\n" + FUNCTION_MARKER + :: + ); +} + + + +__attribute__((naked)) void nseel_asm_min(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d0, [r0]\n" + "fldd d1, [r1]\n" + "fcmpd d1, d0\n" + "fmstat\n" + "movlt r0, r1\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_max(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d0, [r0]\n" + "fldd d1, [r1]\n" + "fcmpd d1, d0\n" + "fmstat\n" + "movge r0, r1\n" + FUNCTION_MARKER + ); +} + + + +__attribute__((naked)) void nseel_asm_min_fp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d1, d0\n" + "fmstat\n" + "fcpydlt d0, d1\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_max_fp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fcmpd d1, d0\n" + "fmstat\n" + "fcpydge d0, d1\n" + FUNCTION_MARKER + ); +} + + + + + + + +__attribute__((naked)) void _asm_generic3parm(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "push {r4, lr}\n" // input: r0 last, r1=second to last, r2=third to last + // output: r0=context, r1=r2, r2=r1, r3=r0 + "mov r3, r0\n" // r0 (last parameter) -> r3 + "mov r4, r1\n" // r1 (second to last parameter) r1->r4->r2 + + "movw r0, 0xdead\n" // r0 is first parm (context) + "movt r0, 0xbeef\n" + + "mov r1, r2\n" // r2->r1 + "mov r2, r4\n" // r1->r2 + + "movw r4, 0xdead\n" + "movt r4, 0xbeef\n" + + "blx r4\n" + "pop {r4, lr}\n" + FUNCTION_MARKER + :: + ); +} + +__attribute__((naked)) void _asm_generic3parm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "push {r4, lr}\n" // input: r0 last, r1=second to last, r2=third to last + // output: r0=context, r1=r2, r2=r1, r3=r0 + "mov r3, r0\n" // r0 (last parameter) -> r3 + "mov r4, r1\n" // r1 (second to last parameter) r1->r4->r2 + + "movw r0, 0xdead\n" // r0 is first parm (context) + "movt r0, 0xbeef\n" + + "mov r1, r2\n" // r2->r1 + "mov r2, r4\n" // r1->r2 + + "movw r4, 0xdead\n" + "movt r4, 0xbeef\n" + + "blx r4\n" + "pop {r4, lr}\n" + FUNCTION_MARKER + :: + ); +} + + +__attribute__((naked)) void _asm_generic2parm(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str lr, [sp, #-8]!\n" + "mov r2, r0\n" // r0 -> r2, r1-r1, + "movw r0, 0xdead\n" // r0 is first parm + "movt r0, 0xbeef\n" + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "blx r3\n" + "ldr lr, [sp], #8\n" + FUNCTION_MARKER + :: + ); +} + + +__attribute__((naked)) void _asm_generic2parm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str lr, [sp, #-8]!\n" + "mov r2, r0\n" // r0 -> r2, r1-r1, + "movw r0, 0xdead\n" // r0 is first parm + "movt r0, 0xbeef\n" + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "blx r3\n" + "ldr lr, [sp], #8\n" + FUNCTION_MARKER + :: + ); +} + + +__attribute__((naked)) void _asm_generic2xparm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "push {r4, lr}\n" + "mov r3, r0\n" // r0 is last parameter + "mov r2, r1\n" // + "movw r0, 0xdead\n" // r0 is ctx + "movt r0, 0xbeef\n" + "movw r1, 0xdead\n" // r1 is second ctx + "movt r1, 0xbeef\n" + "movw r4, 0xdead\n" + "movt r4, 0xbeef\n" + "blx r4\n" + "pop {r4, lr}\n" + FUNCTION_MARKER + :: + ); +} + +__attribute__((naked)) void _asm_generic1parm(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str lr, [sp, #-8]!\n" + "mov r1, r0\n" // r0 -> r1 + "movw r0, 0xdead\n" // r0 is first parm + "movt r0, 0xbeef\n" + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "blx r3\n" + "ldr lr, [sp], #8\n" + FUNCTION_MARKER + :: + ); +} + + + +__attribute__((naked)) void _asm_generic1parm_retd(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "str lr, [sp, #-8]!\n" + "mov r1, r0\n" // r0 -> r1 + "movw r0, 0xdead\n" // r0 is first parm + "movt r0, 0xbeef\n" + "movw r3, 0xdead\n" + "movt r3, 0xbeef\n" + "blx r3\n" + "ldr lr, [sp], #8\n" + FUNCTION_MARKER + :: + ); +} + + + + +__attribute__((naked)) void _asm_megabuf(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r7, #-8]\n" + "faddd d0, d0, d1\n" + "ftouizd s0, d0\n" + "fmrs r3, s0\n" // r3 is slot index + "mov r2, r3, asr %0\n" + "bic r2, r2, #3\n" // r2 is page index*4 + "cmp r2, %1\n" + "bhs 0f\n" + + "add r2, r2, r7\n" + "ldr r2, [r2]\n" + "cmp r2, #0\n" + "beq 0f\n" + + "movw r0, %2\n" + "and r3, r3, r0\n" // r3 mask item in slot + "add r0, r2, r3, asl #3\n" // set result + "b 1f\n" + "0:\n" + + // failed, call stub function + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "str lr, [sp, #-8]!\n" + "mov r0, r7\n" // first parameter: blocks + "mov r1, r3\n" // second parameter: slot index + "blx r2\n" + "ldr lr, [sp], #8\n" + + "1:\n" + + FUNCTION_MARKER + :: + "i" (NSEEL_RAM_ITEMSPERBLOCK_LOG2 - 2/*log2(sizeof(void*))*/), + "i" (NSEEL_RAM_BLOCKS*4 /*(sizeof(void*))*/), + "i" (NSEEL_RAM_ITEMSPERBLOCK-1) + ); +} + + +__attribute__((naked)) void _asm_gmegabuf(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r0, 0xdead\n" + "movt r0, 0xbeef\n" + + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + + "fldd d1, [r7, #-8]\n" + "faddd d0, d0, d1\n" + "ftouizd s0, d0\n" + "fmrs r1, s0\n" // r1 is slot index + + "push {r4, lr}\n" + + "blx r2\n" + + "pop {r4, lr}\n" + + FUNCTION_MARKER + :: + ); +} + + +__attribute__((naked)) void nseel_asm_fcall(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r0, 0xdead\n" + "movt r0, 0xbeef\n" + "push {r4, lr}\n" + "blx r0\n" + "pop {r4, lr}\n" + FUNCTION_MARKER + ); +} + + + +__attribute__((naked)) void nseel_asm_stack_push(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d0, [r0]\n" + + "movw r3, 0xdead\n" // r3 is stack + "movt r3, 0xbeef\n" + "ldr r0, [r3]\n" + + "add r0, r0, #8\n" + + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "and r0, r0, r2\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "orr r0, r0, r2\n" + + "str r0, [r3]\n" + "fstd d0, [r0]\n" + + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_stack_pop(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" // r3 is stack + "movt r3, 0xbeef\n" + "ldr r1, [r3]\n" + "fldd d0, [r1]\n" + "sub r1, r1, #8\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "fstd d0, [r0]\n" + "and r1, r1, r2\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "orr r1, r1, r2\n" + + "str r1, [r3]\n" + FUNCTION_MARKER + ); +} + + + +__attribute__((naked)) void nseel_asm_stack_pop_fast(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" // r3 is stack + "movt r3, 0xbeef\n" + "ldr r1, [r3]\n" + "mov r0, r1\n" + "sub r1, r1, #8\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "and r1, r1, r2\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "orr r1, r1, r2\n" + "str r1, [r3]\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_stack_peek(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" // r3 is stack + "movt r3, 0xbeef\n" + + "ftosizd s0, d0\n" + "fmrs r2, s0\n" // r2 is index in stack + + "ldr r1, [r3]\n" + "sub r1, r1, r2, asl #3\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "and r1, r1, r2\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "orr r0, r1, r2\n" + FUNCTION_MARKER + ); +} + + +__attribute__((naked)) void nseel_asm_stack_peek_top(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" // r3 is stack + "movt r3, 0xbeef\n" + "ldr r0, [r3]\n" + FUNCTION_MARKER + ); +} + + +__attribute__((naked)) void nseel_asm_stack_peek_int(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" // r3 is stack + "movt r3, 0xbeef\n" + + "movw r2, 0xdead\n" // r3 is stack + "movt r2, 0xbeef\n" + + "ldr r1, [r3]\n" + "sub r1, r1, r2\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "and r1, r1, r2\n" + "movw r2, 0xdead\n" + "movt r2, 0xbeef\n" + "orr r0, r1, r2\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_stack_exch(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "movw r3, 0xdead\n" // r3 is stack + "movt r3, 0xbeef\n" + "ldr r1, [r3]\n" + "fldd d0, [r0]\n" + "fldd d1, [r1]\n" + "fstd d0, [r1]\n" + "fstd d1, [r0]\n" + FUNCTION_MARKER + ); +} + + +__attribute__((naked)) void nseel_asm_booltofp(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "cmp r0, #0\n" + "flddne d0, [r6, #16]\n" + "flddeq d0, [r6, #8]\n" + FUNCTION_MARKER + ); +} + +__attribute__((naked)) void nseel_asm_fptobool(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r6]\n" + "fabsd d0, d0\n" + "fcmpd d0, d1\n" + "fmstat\n" + "movgt r0, #1\n" + "movlt r0, #0\n" + FUNCTION_MARKER + :: + ); +} + +__attribute__((naked)) void nseel_asm_fptobool_rev(void) +{ + __asm__ __volatile__( + FUNCTION_MARKER + "fldd d1, [r6]\n" + "fabsd d0, d0\n" + "fcmpd d0, d1\n" + "fmstat\n" + "movgt r0, #0\n" + "movlt r0, #1\n" + FUNCTION_MARKER + :: + ); +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm64ec.asm b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm64ec.asm new file mode 100644 index 0000000..cfee5cb --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm64ec.asm @@ -0,0 +1,1416 @@ +NSEEL_RAM_ITEMSPERBLOCK_LOG2 equ 16 +NSEEL_RAM_BLOCKS_LOG2 equ 9 +NSEEL_LOOPFUNC_SUPPORT_MAXLEN equ 1048576 + +NSEEL_RAM_BLOCKS equ (1 << NSEEL_RAM_BLOCKS_LOG2) +NSEEL_RAM_ITEMSPERBLOCK equ (1< 0 + mov x2, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN & 65535) + movk x2, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16), lsl 16 + cmp w3, w2 + csel w3, w2, w3, gt + ] + stp fp, lr, [sp, #-48]! + mov fp, sp + str x25, [sp, #16] + + mov x25, 0xdead + movk x25, 0xbeef, lsl 16 + movk x25, 0xbeef, lsl 32 +|rep_lz| + stp x3, x22, [sp, #32] + blr x25 + ldp x3, x22, [sp, #32] + sub x3, x3, #1 + cmp x3, #0 + bgt |rep_lz| + ldr x25, [sp, #16] + ldp fp, lr, [sp], #48 +|rep_lz2| + mov x1, x1 + + ENDP + +|#nseel_asm_repeatwhile| PROC + + + mov x1, x1 + [ NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + mov x3, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN & 65535) + movk x3, (NSEEL_LOOPFUNC_SUPPORT_MAXLEN>>16), lsl 16 + ] + stp fp, lr, [sp, #-48]! + str x25, [sp, #16] + + mov x25, 0xdead + movk x25, 0xbeef, lsl 16 + movk x25, 0xbeef, lsl 32 +|repw_lz| + stp x3, x22, [sp, #32] + blr x25 + ldp x3, x22, [sp, #32] + cmp w0, #0 + [ NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + beq |repw_lz2| + sub x3, x3, #1 + cmp x3, #0 + bne |repw_lz| +|repw_lz2| + ] + [ NSEEL_LOOPFUNC_SUPPORT_MAXLEN == 0 + bne |repw_lz| + ] + ldr x25, [sp, #16] + ldp fp, lr, [sp], #48 + + mov x1, x1 + + ENDP + + +|#nseel_asm_band| PROC + + + mov x1, x1 + cmp w0, #0 + beq |band_chk| + mov x3, 0xdead + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + blr x3 + ldp fp, lr, [sp], #16 +|band_chk| + mov x1, x1 + ENDP + +|#nseel_asm_bor| PROC + + + mov x1, x1 + cmp w0, #0 + bne |bor_chk| + mov x3, 0xdead + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + blr x3 + ldp fp, lr, [sp], #16 +|bor_chk| + mov x1, x1 + ENDP + + +|#nseel_asm_equal| PROC + + + mov x1, x1 + ldr d2, [x21] + fsub d0, d0, d1 + fabs d0, d0 + fcmp d0, d2 + cset w0, lt + mov x1, x1 + + ENDP + +|#nseel_asm_equal_exact| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, eq + mov x1, x1 + + ENDP + + +|#nseel_asm_notequal_exact| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, ne + mov x1, x1 + + ENDP + + + + +|#nseel_asm_notequal| PROC + + + mov x1, x1 + ldr d2, [x21] + fsub d0, d0, d1 + fabs d0, d0 + fcmp d0, d2 + cset w0, ge + mov x1, x1 + + ENDP + + + +|#nseel_asm_below| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, lt + mov x1, x1 + + ENDP + + +|#nseel_asm_beloweq| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, le + mov x1, x1 + + ENDP + + + +|#nseel_asm_above| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, gt + mov x1, x1 + + ENDP + +|#nseel_asm_aboveeq| PROC + + + mov x1, x1 + fcmp d1, d0 + cset w0, ge + mov x1, x1 + + ENDP + + + +|#nseel_asm_min| PROC + + + mov x1, x1 + ldr d0, [x0] + ldr d1, [x1] + fcmp d1, d0 + csel x0, x1, x0, lt + mov x1, x1 + + ENDP + +|#nseel_asm_max| PROC + + + mov x1, x1 + ldr d0, [x0] + ldr d1, [x1] + fcmp d1, d0 + csel x0, x1, x0, gt + mov x1, x1 + + ENDP + + + +|#nseel_asm_min_fp| PROC + + + mov x1, x1 + fcmp d1, d0 + fcsel d0, d1, d0, lt + mov x1, x1 + + ENDP + +|#nseel_asm_max_fp| PROC + + + mov x1, x1 + fcmp d1, d0 + fcsel d0, d1, d0, gt + mov x1, x1 + + ENDP + + + + + + + +|#_asm_generic3parm| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last, r2=third to last + mov fp, sp + + mov x3, x0 ; r0 (last parameter) -> r3 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + mov x5, x1 ; swap x1/x2 + mov x1, x2 + mov x2, x5 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + +|#_asm_generic3parm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last, r2=third to last + mov fp, sp + mov x3, x0 ; r0 (last parameter) -> r3 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + mov x5, x1 ; // swap x1/x2 + mov x1, x2 + mov x2, x5 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + +|#_asm_generic2parm| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last + mov fp, sp + mov x2, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + +|#_asm_generic2parm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last + mov fp, sp + mov x2, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + +|#_asm_generic2xparm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! ; input: r0 last, r1=second to last + mov fp, sp + mov x2, x1 + mov x3, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x1, 0xdead ; second parm + movk x1, 0xbeef, lsl 16 + movk x1, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + +|#_asm_generic1parm| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! + mov fp, sp + mov x1, x0 + + mov x0, 0xdead ; r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + + +|#_asm_generic1parm_retd| PROC + + + mov x1, x1 + stp fp, lr, [sp, #-16]! + mov fp, sp + mov x1, x0 + + mov x0, 0xdead ; // r0 is first parm (context) + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x4, 0xdead + movk x4, 0xbeef, lsl 16 + movk x4, 0xbeef, lsl 32 + + blr x4 + ldp fp, lr, [sp], #16 + + mov x1, x1 + + ENDP + + + + +|#_asm_megabuf| PROC + + + mov x1, x1 + add x0, x20, #-8 + ldr d1, [x0] + fadd d0, d0, d1 + fcvtzu w3, d0 + asr w2, w3, (NSEEL_RAM_ITEMSPERBLOCK_LOG2 - 3) + bic w2, w2, #7 ; r2 is page index*8 + mov w0, (NSEEL_RAM_BLOCKS * 8) + cmp w2, w0 + bhs |mbchk1| + + add x2, x2, x20 + ldr x2, [x2] + cmp x2, #0 + beq |mbchk1| + + mov x0, (NSEEL_RAM_ITEMSPERBLOCK - 1) + and x3, x3, x0 ; r3 mask item in slot + add x0, x2, x3, lsl #3 ; set result + b |mbchk2| +|mbchk1| + + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + mov x0, x20 ; first parameter: blocks + mov x1, x3 ; second parameter: slot index + blr x2 + ldp fp, lr, [sp], #16 +|mbchk2| + + mov x1, x1 + + ENDP + + +|#_asm_gmegabuf| PROC + + + mov x1, x1 + add x2, x20, #-8 + ldr d1, [x2] + + mov x0, 0xdead + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + + fadd d0, d0, d1 + + fcvtzu w1, d0 + + stp fp, lr, [sp, #-16]! + mov fp, sp + + blr x2 + + ldp fp, lr, [sp], #16 + + mov x1, x1 + + ENDP + + +|#nseel_asm_fcall| PROC + + + mov x1, x1 + mov x0, 0xdead + movk x0, 0xbeef, lsl 16 + movk x0, 0xbeef, lsl 32 + stp fp, lr, [sp, #-16]! + mov fp, sp + blr x0 + ldp fp, lr, [sp], #16 + mov x1, x1 + + ENDP + + + +|#nseel_asm_stack_push| PROC + + + mov x1, x1 + ldr d0, [x0] + + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x0, [x3] + + add x0, x0, #8 + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x0, x0, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x0, x0, x2 + + str x0, [x3] + str d0, [x0] + + mov x1, x1 + + ENDP + +|#nseel_asm_stack_pop| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x1, [x3] + ldr d0, [x1] + sub x1, x1, #8 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + str d0, [x0] + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x1, x1, x2 + + str x1, [x3] + mov x1, x1 + + ENDP + + + +|#nseel_asm_stack_pop_fast| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x1, [x3] + mov x0, x1 + sub x1, x1, #8 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x1, x1, x2 + str x1, [x3] + mov x1, x1 + + ENDP + +|#nseel_asm_stack_peek| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + + fcvtzs w2, d0 + + ldr x1, [x3] + sub x1, x1, x2, lsl #3 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x0, x1, x2 + mov x1, x1 + + ENDP + + +|#nseel_asm_stack_peek_top| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x0, [x3] + mov x1, x1 + + ENDP + + +|#nseel_asm_stack_peek_int| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + + ldr x1, [x3] + sub x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + and x1, x1, x2 + mov x2, 0xdead + movk x2, 0xbeef, lsl 16 + movk x2, 0xbeef, lsl 32 + orr x0, x1, x2 + mov x1, x1 + + ENDP + +|#nseel_asm_stack_exch| PROC + + + mov x1, x1 + mov x3, 0xdead ; r3 is stack + movk x3, 0xbeef, lsl 16 + movk x3, 0xbeef, lsl 32 + ldr x1, [x3] + ldr d0, [x0] + ldr d1, [x1] + str d0, [x1] + str d1, [x0] + mov x1, x1 + + ENDP + + +|#nseel_asm_booltofp| PROC + + + mov x1, x1 + cmp w0, #0 + fmov d0, #1.0 + movi d1, #0 + fcsel d0, d0, d1, ne + mov x1, x1 + + ENDP + +|#nseel_asm_fptobool| PROC + + + mov x1, x1 + ldr d1, [x21] + fabs d0, d0 + fcmp d0, d1 + cset w0, ge + mov x1, x1 + + ENDP + +|#nseel_asm_fptobool_rev| PROC + + + mov x1, x1 + ldr d1, [x21] + fabs d0, d0 + fcmp d0, d1 + cset w0, lt + mov x1, x1 + + ENDP + +|#eel_callcode64| PROC + stp fp, lr, [sp, #-128]! + mov fp, sp + + stp d13, d12, [sp, #16] + stp d11, d10, [sp, #32] + stp d9, d8, [sp, #48] + stp d15, d14, [sp, #64] + stp x20, x19, [sp, #80] + stp x22, x21, [sp, #96] + + mov x19, x0 + mov x20, x2 + mov x21, x3 + blr x1 + + ldp d13, d12, [sp, #16] + ldp d11, d10, [sp, #32] + ldp d9, d8, [sp, #48] + ldp d15, d14, [sp, #64] + ldp x20, x19, [sp, #80] + ldp x22, x21, [sp, #96] + ldp fp, lr, [sp], #128 + ret + + ENDP + +|#glue_getscr| PROC + mrs x0, FPCR + ret + ENDP + +|#glue_setscr| PROC + msr FPCR, x0 + ret + ENDP + + END diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm64ec.obj b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm64ec.obj new file mode 100644 index 0000000..f0f0843 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-arm64ec.obj differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-multi-macho.o b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-multi-macho.o new file mode 100644 index 0000000..c689364 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-multi-macho.o differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-ppc-gcc.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-ppc-gcc.c new file mode 100644 index 0000000..11aa25e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-ppc-gcc.c @@ -0,0 +1,1436 @@ +#define FUNCTION_MARKER "mr r0, r0\n" \ + "mr r1, r1\n" \ + "mr r2, r2\n" + +#if EEL_F_SIZE == 8 + +void nseel_asm_1pdd(void) +{ + + __asm__( + FUNCTION_MARKER + "addis r5, 0, 0xdead\n" + "ori r5, r5, 0xbeef\n" + "mtctr r5\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: ); +} + +void nseel_asm_2pdd(void) +{ + + __asm__( + FUNCTION_MARKER + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "fmr f2, f1\n" + "lfd f1, 0(r14)\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: ); +}; + +void nseel_asm_2pdds(void) +{ + __asm__( + FUNCTION_MARKER + "addis r5, 0, 0xdead\n" + "ori r5, r5, 0xbeef\n" + "fmr f2, f1\n" + "lfd f1, 0(r14)\n" + "mtctr r5\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + "stfd f1, 0(r14)\n" + "mr r3, r14\n" + FUNCTION_MARKER + :: ); +} + +#else // 32 bit floating point calls + +#error no 32 bit float support + +#endif + +//--------------------------------------------------------------------------------------------------------------- + + +void nseel_asm_invsqrt(void) +{ + __asm__( + FUNCTION_MARKER + "frsqrte f1, f1\n" // less accurate than our x86 equivilent, but invsqrt() is inherently inaccurate anyway + FUNCTION_MARKER + ); +} + +void nseel_asm_dbg_getstackptr(void) +{ + __asm__( + FUNCTION_MARKER + "addis r11, 0, 0x4330\n" + "xoris r10, r1, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_sqr(void) +{ + __asm__( + FUNCTION_MARKER + "fmul f1, f1, f1\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_abs(void) +{ + __asm__( + FUNCTION_MARKER + "fabs f1, f1\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f1, 0(r3)\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign_fromfp(void) +{ + __asm__( + FUNCTION_MARKER + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign_fast(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f1, 0(r3)\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_assign_fast_fromfp(void) +{ + __asm__( + FUNCTION_MARKER + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_add(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fadd f1, f1, f2\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_add_op(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fadd f1, f1, f2\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_add_op_fast(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fadd f1, f1, f2\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_sub(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fsub f1, f2, f1\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_sub_op(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fsub f1, f2, f1\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_sub_op_fast(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fsub f1, f2, f1\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_mul(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fmul f1, f2, f1\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_mul_op(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fmul f1, f2, f1\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_mul_op_fast(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fmul f1, f2, f1\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_div(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fdiv f1, f2, f1\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_div_op(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fdiv f1, f2, f1\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_div_op_fast(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fdiv f1, f2, f1\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_mod(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fabs f1, f1\n" + "fabs f2, f2\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + + "divw r12, r11, r10\n" + "mullw r12, r12, r10\n" + "subf r10, r12, r11\n" + + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_shl(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "slw r10, r11, r10\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_shr(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "sraw r10, r11, r10\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_mod_op(void) +{ + + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fabs f1, f1\n" + "fabs f2, f2\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + + "divw r12, r11, r10\n" + "mullw r12, r12, r10\n" + "subf r10, r12, r11\n" + + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); + +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_or(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "or r10, r10, r11\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_or0(void) +{ + __asm__( + FUNCTION_MARKER + "fctiwz f1, f1\n" + "addis r11, 0, 0x4330\n" + "stfd f1, -8(r1)\n" + "lwz r10, -4(r1)\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_or_op(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "or r10, r10, r11\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_xor(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "xor r10, r10, r11\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_xor_op(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "xor r10, r10, r11\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_and(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "and r10, r10, r11\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + FUNCTION_MARKER + );} + +void nseel_asm_and_op(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fctiwz f1, f1\n" + "fctiwz f2, f2\n" + "stfd f1, -8(r1)\n" + "stfd f2, -16(r1)\n" + "lwz r10, -4(r1)\n" + "lwz r11, -12(r1)\n" //r11 and r12 have the integers + "and r10, r10, r11\n" // r10 has the result + "addis r11, 0, 0x4330\n" + "xoris r10, r10, 0x8000\n" + "stw r11, -8(r1)\n" // 0x43300000 + "stw r10, -4(r1)\n" // our integer sign flipped + "lfd f1, -8(r1)\n" + "fsub f1, f1, f30\n" + "mr r3, r14\n" + "stfd f1, 0(r14)\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_uminus(void) +{ + __asm__( + FUNCTION_MARKER + "fneg f1, f1\n" + FUNCTION_MARKER + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_sign(void) +{ + __asm__( + FUNCTION_MARKER + "li r9, 0\n" + "stw r9, -4(r1)\n" + "lis r9, 0xbf80\n" // -1 in float + "lfs f2, -4(r1)\n" + + "fcmpu cr7, f1, f2\n" + "blt- cr7, 0f\n" + "ble- cr7, 1f\n" + " lis r9, 0x3f80\n" // 1 in float + "0:\n" + " stw r9, -4(r1)\n" + " lfs f1, -4(r1)\n" + "1:\n" + FUNCTION_MARKER + :: + ); +} + + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_bnot(void) +{ + __asm__( + FUNCTION_MARKER + "cmpwi cr0, r3, 0\n" + "addis r3, 0, 0\n" + "bne cr0, 0f\n" + "addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_if(void) +{ + __asm__( + FUNCTION_MARKER + "cmpwi cr0, r3, 0\n" + "beq cr0, 0f\n" + " addis r6, 0, 0xdead\n" + " ori r6, r6, 0xbeef\n" + " mtctr r6\n" + " bctrl\n" + "b 1f\n" + "0:\n" + " addis r6, 0, 0xdead\n" + " ori r6, r6, 0xbeef\n" + " mtctr r6\n" + " bctrl\n" + "1:\n" + FUNCTION_MARKER + :: ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_repeat(void) +{ +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + __asm__( + FUNCTION_MARKER + "fctiwz f1, f1\n" + "stfd f1, -8(r1)\n" + "lwz r5, -4(r1)\n" // r5 has count now + "cmpwi cr0, r5, 0\n" + "ble cr0, 1f\n" // skip the loop + + "addis r7, 0, ha16(%0)\n" + "addi r7, r7, lo16(%0)\n" + + "stwu r16, -16(r1)\n" // set up the stack for the loop, save r16 + + "cmpw cr0, r7, r5\n" + "bge cr0, 0f\n" + "mr r5, r7\n" // set r5 to max if we have to +"0:\n" + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" + + "addi r5, r5, -1\n" + "stw r5, 4(r1)\n" + + "mtctr r6\n" + "bctrl\n" + + "lwz r16, 0(r1)\n" + "lwz r5, 4(r1)\n" + + "cmpwi cr0, r5, 0\n" + "bgt cr0, 0b\n" + + "addi r1, r1, 16\n" // restore old stack + + "1:\n" + FUNCTION_MARKER + ::"g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN) + ); +#else + __asm__( + FUNCTION_MARKER + "fctiwz f1, f1\n" + "stfd f1, -8(r1)\n" + "lwz r5, -4(r1)\n" // r5 has count now + "cmpwi cr0, r5, 0\n" + "ble cr0, 1f\n" // skip the loop + + "stwu r16, -16(r1)\n" // set up the stack for the loop, save r16 + +"0:\n" + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" + + "addi r5, r5, -1\n" + "stw r5, 4(r1)\n" + + "mtctr r6\n" + "bctrl\n" + + "lwz r16, 0(r1)\n" + "lwz r5, 4(r1)\n" + + "cmpwi cr0, r5, 0\n" + "bgt cr0, 0b\n" + + "addi r1, r1, 16\n" // restore old stack + + "1:\n" + FUNCTION_MARKER + :: + ); +#endif +} + +void nseel_asm_repeatwhile(void) +{ +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + __asm__( + FUNCTION_MARKER + "stwu r16, -16(r1)\n" // save r16 to stack, update stack + "addis r5, 0, ha16(%0)\n" + "addi r5, r5, lo16(%0)\n" +"0:\n" + + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" + "stw r5, 4(r1)\n" // save maxcnt + + "mtctr r6\n" + "bctrl\n" + + "lwz r16, 0(r1)\n" // restore r16 + "lwz r5, 4(r1)\n" // restore, check maxcnt + + "cmpwi cr7, r3, 0\n" // check return value + "addi r5, r5, -1\n" + + "beq cr7, 1f\n" + + "cmpwi cr0, r5, 0\n" + "bgt cr0, 0b\n" + "1:\n" + "addi r1, r1, 16\n" // restore stack + FUNCTION_MARKER + ::"g" (NSEEL_LOOPFUNC_SUPPORT_MAXLEN) + ); +#else + __asm__( + FUNCTION_MARKER + "stwu r16, -16(r1)\n" // save r16 to stack, update stack +"0:\n" + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" + + "mtctr r6\n" + "bctrl\n" + + "lwz r16, 0(r1)\n" // restore r16 + + "cmpwi cr7, r3, 0\n" // check return value + "bne cr7, 0b\n" + "addi r1, r1, 16\n" // restore stack + FUNCTION_MARKER + :: + ); + +#endif +} + + +void nseel_asm_band(void) +{ + __asm__( + FUNCTION_MARKER + "cmpwi cr7, r3, 0\n" + "beq cr7, 0f\n" + " addis r6, 0, 0xdead\n" + " ori r6, r6, 0xbeef\n" + " mtctr r6\n" + " bctrl\n" + "0:\n" + FUNCTION_MARKER + :: ); +} + +void nseel_asm_bor(void) +{ + __asm__( + FUNCTION_MARKER + "cmpwi cr7, r3, 0\n" + "bne cr7, 0f\n" + " addis r6, 0, 0xdead\n" + " ori r6, r6, 0xbeef\n" + " mtctr r6\n" + " bctrl\n" + "0:\n" + FUNCTION_MARKER + :: ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_equal(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fsub f1, f1, f2\n" + "fabs f1, f1\n" + "fcmpu cr7, f1, f31\n" + "addis r3, 0, 0\n" + "bge cr7, 0f\n" + "addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_equal_exact(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f1, f2\n" + "addis r3, 0, 0\n" + "bne cr7, 0f\n" + "addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_notequal_exact(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f1, f2\n" + "addis r3, 0, 0\n" + "beq cr7, 0f\n" + "addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} +// +// +// +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_notequal(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fsub f1, f1, f2\n" + "fabs f1, f1\n" + "fcmpu cr7, f1, f31\n" + "addis r3, 0, 0\n" + "blt cr7, 0f\n" + " addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_below(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f1, f2\n" + "addis r3, 0, 0\n" + "ble cr7, 0f\n" + "addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_beloweq(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f1, f2\n" + "addis r3, 0, 0\n" + "blt cr7, 0f\n" + " addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} + + +//--------------------------------------------------------------------------------------------------------------- +void nseel_asm_above(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f1, f2\n" + "addis r3, 0, 0\n" + "bge cr7, 0f\n" + "addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} + +void nseel_asm_aboveeq(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f1, f2\n" + "addis r3, 0, 0\n" + "bgt cr7, 0f\n" + "addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} + + + +void nseel_asm_min(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f1, 0(r3)\n" + "lfd f2, 0(r14)\n" + "fcmpu cr7, f2, f1\n" + "bgt cr7, 0f\n" + "mr r3, r14\n" + "0:\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_max(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f1, 0(r3)\n" + "lfd f2, 0(r14)\n" + "fcmpu cr7, f2, f1\n" + "blt cr7, 0f\n" + "mr r3, r14\n" + "0:\n" + FUNCTION_MARKER + ); +} + + + +void nseel_asm_min_fp(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f2, f1\n" + "bgt cr7, 0f\n" + "fmr f1, f2\n" + "0:\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_max_fp(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, 0(r14)\n" + "fcmpu cr7, f2, f1\n" + "blt cr7, 0f\n" + "fmr f1, f2\n" + "0:\n" + FUNCTION_MARKER + ); +} + + + + + + + +void _asm_generic3parm(void) +{ + __asm__( + FUNCTION_MARKER + "mr r6, r3\n" + "addis r3, 0, 0xdead\n" + "ori r3, r3, 0xbeef\n" + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mr r4, r15\n" + "mr r5, r14\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + +void _asm_generic3parm_retd(void) +{ + __asm__( + FUNCTION_MARKER + "mr r6, r3\n" + "addis r3, 0, 0xdead\n" + "ori r3, r3, 0xbeef\n" + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mr r4, r15\n" + "mr r5, r14\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + + +void _asm_generic2parm(void) // this prob neds to be fixed for ppc +{ + __asm__( + FUNCTION_MARKER + "mr r5, r3\n" + "addis r3, 0, 0xdead\n" + "ori r3, r3, 0xbeef\n" + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mr r4, r14\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + + +void _asm_generic2parm_retd(void) +{ + __asm__( + FUNCTION_MARKER + "mr r5, r3\n" + "addis r3, 0, 0xdead\n" + "ori r3, r3, 0xbeef\n" + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mr r4, r14\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + +void _asm_generic2xparm_retd(void) +{ + __asm__( + FUNCTION_MARKER + "mr r6, r3\n" + "addis r3, 0, 0xdead\n" + "ori r3, r3, 0xbeef\n" + "addis r4, 0, 0xdead\n" + "ori r4, r4, 0xbeef\n" + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mr r5, r14\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + +void _asm_generic1parm(void) // this prob neds to be fixed for ppc +{ + __asm__( + FUNCTION_MARKER + "mr r4, r3\n" + "addis r3, 0, 0xdead\n" + "ori r3, r3, 0xbeef\n" + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + + + +void _asm_generic1parm_retd(void) +{ + __asm__( + FUNCTION_MARKER + "mr r4, r3\n" + "addis r3, 0, 0xdead\n" + "ori r3, r3, 0xbeef\n" + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + + + + +void _asm_megabuf(void) +{ + __asm__( + FUNCTION_MARKER + "lfd f2, -8(r13)\n" + "mr r3, r13\n" + + "fadd f1, f2, f1\n" + + // f1 has (float) index of array, r3 has EEL_F ** + "fctiwz f1, f1\n" + "stfd f1, -8(r1)\n" + "lwz r4, -4(r1)\n" // r4 is index of array + + "andis. r15, r4, %0\n" // check to see if it has any bits in 0xFF800000, which is 0xFFFFFFFF - (NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK - 1) + "bne cr0, 0f\n" // out of range, jump to error + + // shr 14 (16 for NSEEL_RAM_ITEMSPERBLOCK, minus two for pointer size), which is rotate 18 + // mask 7 bits (NSEEL_RAM_BLOCKS), but leave two empty bits (pointer size) + "rlwinm r15, r4, %1, %2, 29\n" + "lwzx r15, r3, r15\n" // r15 = (r3+r15) + "cmpi cr0, r15, 0\n" + "bne cr0, 1f\n" // if nonzero, jump to final calculation + + "0:\n" + // set up function call + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + "mtctr r7\n" + "subi r1, r1, 64\n" + "bctrl\n" + "addi r1, r1, 64\n" + "b 2f\n" + "1:\n" + // good news: we can do a direct addr return + // bad news: more rlwinm ugliness! + // shift left by 3 (sizeof(EEL_F)), mask off lower 3 bits, only allow 16 bits (NSEEL_RAM_ITEMSPERBLOCK) through + "rlwinm r3, r4, 3, %3, 28\n" + + // add offset of loaded block + "add r3, r3, r15\n" + + "2:\n" + FUNCTION_MARKER + :: + "i" ((0xFFFFFFFF - (NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK - 1))>>16), + "i" (32 - NSEEL_RAM_ITEMSPERBLOCK_LOG2 + 2), + "i" (30 - NSEEL_RAM_BLOCKS_LOG2), + "i" (28 - NSEEL_RAM_ITEMSPERBLOCK_LOG2 + 1) + ); +} + + +void _asm_gmegabuf(void) +{ + __asm__( + FUNCTION_MARKER + "fadd f1, f31, f1\n" + "addis r3, 0, 0xdead\n" // set up context pointer + "ori r3, r3, 0xbeef\n" + + "fctiwz f1, f1\n" + "subi r1, r1, 64\n" + + "addis r7, 0, 0xdead\n" + "ori r7, r7, 0xbeef\n" + + "stfd f1, 8(r1)\n" + "mtctr r7\n" + + "lwz r4, 12(r1)\n" + + "bctrl\n" + "addi r1, r1, 64\n" + FUNCTION_MARKER + :: + ); +} + + +void nseel_asm_fcall(void) +{ + __asm__( + FUNCTION_MARKER + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" + "mtctr r6\n" + "bctrl\n" + FUNCTION_MARKER + ); +} + + + +void nseel_asm_stack_push(void) +{ + __asm__( + FUNCTION_MARKER + + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" // r6 is stack + + "lfd f1, 0(r3)\n" // f1 is value to copy to stack + "lwz r3, 0(r6)\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "addi r3, r3, 0x8\n" + + "and r3, r3, r14\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "or r3, r3, r14\n" + + "stfd f1, 0(r3)\n" // copy parameter to stack + + "stw r3, 0(r6)\n" // update stack state + FUNCTION_MARKER + ); +} + +void nseel_asm_stack_pop(void) +{ + __asm__( + FUNCTION_MARKER + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" // r6 is stack + "lwz r15, 0(r6)\n" // return the old stack pointer + + "lfd f1, 0(r15)\n" + "subi r15, r15, 0x8\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "and r15, r15, r14\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "or r15, r15, r14\n" + "stw r15, 0(r6)\n" + + "stfd f1, 0(r3)\n" + FUNCTION_MARKER + ); +} + + + +void nseel_asm_stack_pop_fast(void) +{ + __asm__( + FUNCTION_MARKER + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" // r6 is stack + "lwz r3, 0(r6)\n" // return the old stack pointer + + "mr r15, r3\n" // update stack pointer + "subi r15, r15, 0x8\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "and r15, r15, r14\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "or r15, r15, r14\n" + "stw r15, 0(r6)\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_stack_peek(void) +{ + __asm__( + FUNCTION_MARKER + "fctiwz f1, f1\n" + "stfd f1, -8(r1)\n" + + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" // r6 is stack + + "lwz r14, -4(r1)\n" + "rlwinm r14, r14, 3, 0, 28\n" // slwi r14, r14, 3 -- 3 is log2(sizeof(EEL_F)) -- 28 represents 31-3 + "lwz r3, 0(r6)\n" // return the old stack pointer + + "sub r3, r3, r14\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "and r3, r3, r14\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "or r3, r3, r14\n" + FUNCTION_MARKER + ); +} + + +void nseel_asm_stack_peek_top(void) +{ + __asm__( + FUNCTION_MARKER + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" // r6 is stack + "lwz r3, 0(r6)\n" // return the old stack pointer + FUNCTION_MARKER + ); +} + + +void nseel_asm_stack_peek_int(void) +{ + __asm__( + FUNCTION_MARKER + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" // r6 is stack + "lwz r3, 0(r6)\n" // return the old stack pointer + + "addis r14, 0, 0xdead\n" // add manual offset + "ori r14, r14, 0xbeef\n" + "sub r3, r3, r14\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "and r3, r3, r14\n" + + "addis r14, 0, 0xdead\n" + "ori r14, r14, 0xbeef\n" + "or r3, r3, r14\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_stack_exch(void) +{ + __asm__( + FUNCTION_MARKER + "addis r6, 0, 0xdead\n" + "ori r6, r6, 0xbeef\n" // r6 is stack + "lfd f1, 0(r3)\n" + "lwz r14, 0(r6)\n" + "lfd f2, 0(r14)\n" + + "stfd f1, 0(r14)\n" + "stfd f2, 0(r3)\n" + FUNCTION_MARKER + ); +} + + +void nseel_asm_booltofp(void) +{ + __asm__( + FUNCTION_MARKER + "cmpwi cr7, r3, 0\n" + "li r14, 0\n" + "beq cr7, 0f\n" + "addis r14, 0, 0x3f80\n" + "0:\n" + "stw r14, -8(r1)\n" + "lfs f1, -8(r1)\n" + FUNCTION_MARKER + ); +} + +void nseel_asm_fptobool(void) +{ + __asm__( + FUNCTION_MARKER + "fabs f1, f1\n" + "fcmpu cr7, f1, f31\n" + "addis r3, 0, 1\n" + "bge cr7, 0f\n" + " addis r3, 0, 0\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} + +void nseel_asm_fptobool_rev(void) +{ + __asm__( + FUNCTION_MARKER + "fabs f1, f1\n" + "fcmpu cr7, f1, f31\n" + "addis r3, 0, 0\n" + "bge cr7, 0f\n" + " addis r3, 0, 1\n" + "0:\n" + FUNCTION_MARKER + :: + ); +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-x64-macho.o b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-x64-macho.o new file mode 100644 index 0000000..b019fc2 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-x64-macho.o differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-x64-sse.asm b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-x64-sse.asm new file mode 100644 index 0000000..2a71c1c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/asm-nseel-x64-sse.asm @@ -0,0 +1,1357 @@ +; these must be synced with any changes in ns-eel-int.h +%define NSEEL_RAM_BLOCKS_DEFAULTMAX 128 +%define NSEEL_RAM_BLOCKS_LOG2 9 +%define NSEEL_RAM_ITEMSPERBLOCK_LOG2 16 +%define NSEEL_RAM_BLOCKS (1 << NSEEL_RAM_BLOCKS_LOG2) +%define NSEEL_RAM_ITEMSPERBLOCK (1< +#include + +#define YY_USER_ACTION yylloc->first_line = yylineno; + +#define YY_FATAL_ERROR(msg) { ((struct yyguts_t*)yyscanner)->yyextra_r->errVar=1; } +#define YY_INPUT(buf,result,max_size) { (result) = nseel_gets(yyextra,(buf),max_size); } + +#define YY_EXTRA_TYPE compileContext * + +#undef YY_BUF_SIZE +#define YY_BUF_SIZE (NSEEL_MAX_VARIABLE_NAMELEN*2) + +#undef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE (NSEEL_MAX_VARIABLE_NAMELEN) + +#include "y.tab.h" + +#ifdef _WIN32 +#define YY_NO_UNISTD_H +#endif + +#include "ns-eel-int.h" + +int nseel_gets(compileContext *ctx, char *buf, size_t sz); + +#define PARSENUM *yylval = nseel_translate(yyextra,yytext, 0); return VALUE; +#define EEL_ACTION(x) return x; + +#ifdef stdin +#undef stdin +#endif +#define stdin (0) + +#ifdef stdout +#undef stdout +#endif +#define stdout (0) + +static int g_fake_errno; +#ifdef errno +#undef errno +#endif + +#define errno g_fake_errno + +static void comment(yyscan_t yyscanner); + +%} + +%% + +[0-9]+\.?[0-9]* PARSENUM; +\.[0-9]+ PARSENUM; +0[xX][0-9a-fA-F]* PARSENUM; +\$[xX][0-9a-fA-F]* PARSENUM; +\$\~[0-9]* PARSENUM; +\$[Ee] PARSENUM; +\$[Pp][Ii] PARSENUM; +\$[Pp][Hh][Ii] PARSENUM; +\$\'.\' PARSENUM; +\#[a-zA-Z0-9\._]* *yylval = nseel_translate(yyextra,yytext, 0); return STRING_IDENTIFIER; +\<\< return TOKEN_SHL; +\>\> return TOKEN_SHR; +\<= return TOKEN_LTE; +\>= return TOKEN_GTE; +== return TOKEN_EQ; +=== return TOKEN_EQ_EXACT; +\!= return TOKEN_NE; +\!== return TOKEN_NE_EXACT; +\&\& return TOKEN_LOGICAL_AND; +\|\| return TOKEN_LOGICAL_OR; +\+= return TOKEN_ADD_OP; +-= return TOKEN_SUB_OP; +%= return TOKEN_MOD_OP; +\|= return TOKEN_OR_OP; +\&= return TOKEN_AND_OP; +\~= return TOKEN_XOR_OP; +\/= return TOKEN_DIV_OP; +\*= return TOKEN_MUL_OP; +\^= return TOKEN_POW_OP; + +[a-zA-Z_][a-zA-Z0-9\._]* &yylval = nseel_createCompiledValuePtr((compileContext *)yyextra, NULL, yytext); return IDENTIFIER; + +[ \t\r\n]+ /* whitespace */ +\/\/.*$ /* comment */ +"/*" { comment(yyscanner); } + +. return (int)yytext[0]; + +%% + +static void comment(yyscan_t yyscanner) +{ + int c,lc=0; + + while (0 != (c = input(yyscanner))) + { + if (c == '/' && lc == '*') return; + lc = c; + } + // end of file, ignore for now +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel2.vim b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel2.vim new file mode 100644 index 0000000..bd77e45 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel2.vim @@ -0,0 +1,370 @@ +" Vim syntax file +" This needs a lot of C-specific stuff removed, please help if you care =) +" Language: EEL2, based on: +" +" +" Language: C - Maintainer: Bram Moolenaar - Last Change: 2009 Nov 17 + +" Quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +" A bunch of useful C keywords +syn keyword cStatement function globals global local instance +syn keyword cRepeat while loop +syn keyword cRepeat sin cos tan sqrt log log10 asin acos atan atan2 exp abs sqr min max sign rand floor ceil invsqrt freembuf memcpy memset stack_psuh stack_pop stack_peek stack_exch +syn keyword cRepeat atomic_setifequal atomic_exch atomic_add atomic_set atomic_get convolve_c fft ifft fft_permute fft_ipermute fopen fread fgets fgetc fwrite fprintf fseek ftell feof fflush fclose +syn keyword cRepeat gfx_lineto gfx_lineto gfx_rectto gfx_rect gfx_line gfx_gradrect gfx_muladdrect gfx_deltablit gfx_transformblit gfx_blurto gfx_drawnumber gfx_drawchar gfx_drawstr gfx_measurestr gfx_printf gfx_setpixel gfx_getpixel gfx_getimgdim gfx_setimgdim gfx_loadimg gfx_blit gfx_blitext gfx_blit gfx_setfont gfx_getfont gfx_init gfx_quit gfx_getchar +syn keyword cRepeat mdct imdct sleep time time_precise tcp_listen tcp_listen_end tcp_connect tcp_send tcp_recv tcp_set_block tcp_close strlen +syn keyword cRepeat strcat strcpy strcmp stricmp strncat strncpy strncmp strnicmp str_setlen strcpy_from strcpy_substr strcpy_substr str_getchar str_setchar str_getchar str_setchar str_insert str_delsub sprintf printf match matchi + +syn keyword cTodo contained TODO FIXME XXX + +" It's easy to accidentally add a space after a backslash that was intended +" for line continuation. Some compilers allow it, which makes it +" unpredicatable and should be avoided. +syn match cBadContinuation contained "\\\s\+$" + +" cCommentGroup allows adding matches for special things in comments +syn cluster cCommentGroup contains=cTodo,cBadContinuation + +" String and Character constants +" Highlight special characters (those which have a backslash) differently +syn match cSpecial display contained "\\\(x\x\+\|\o\{1,3}\|.\|$\)" +if !exists("c_no_utf") + syn match cSpecial display contained "\\\(u\x\{4}\|U\x\{8}\)" +endif +if exists("c_no_cformat") + syn region cString start=+L\="+ skip=+\\\\\|\\"+ end=+"+ contains=cSpecial,@Spell + " cCppString: same as cString, but ends at end of line + syn region cCppString start=+L\="+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end='$' contains=cSpecial,@Spell +else + if !exists("c_no_c99") " ISO C99 + syn match cFormat display "%\(\d\+\$\)\=[-+' #0*]*\(\d*\|\*\|\*\d\+\$\)\(\.\(\d*\|\*\|\*\d\+\$\)\)\=\([hlLjzt]\|ll\|hh\)\=\([aAbdiuoxXDOUfFeEgGcCsSpn]\|\[\^\=.[^]]*\]\)" contained + else + syn match cFormat display "%\(\d\+\$\)\=[-+' #0*]*\(\d*\|\*\|\*\d\+\$\)\(\.\(\d*\|\*\|\*\d\+\$\)\)\=\([hlL]\|ll\)\=\([bdiuoxXDOUfeEgGcCsSpn]\|\[\^\=.[^]]*\]\)" contained + endif + syn match cFormat display "%%" contained + syn region cString start=+L\="+ skip=+\\\\\|\\"+ end=+"+ contains=cSpecial,cFormat,@Spell + " cCppString: same as cString, but ends at end of line + syn region cCppString start=+L\="+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end='$' contains=cSpecial,cFormat,@Spell +endif + +syn match cCharacter "L\='[^\\]'" +syn match cCharacter "L'[^']*'" contains=cSpecial +syn match cCharacter "'[^']*'" contains=cSpecial +if exists("c_gnu") + syn match cSpecialError "L\='\\[^'\"?\\abefnrtv]'" + syn match cSpecialCharacter "L\='\\['\"?\\abefnrtv]'" +else + syn match cSpecialError "L\='\\[^'\"?\\abfnrtv]'" + syn match cSpecialCharacter "L\='\\['\"?\\abfnrtv]'" +endif +syn match cSpecialCharacter display "L\='\\\o\{1,3}'" +syn match cSpecialCharacter display "'\\x\x\{1,2}'" +syn match cSpecialCharacter display "L'\\x\x\+'" + +"when wanted, highlight trailing white space +if exists("c_space_errors") + if !exists("c_no_trail_space_error") + syn match cSpaceError display excludenl "\s\+$" + endif + if !exists("c_no_tab_space_error") + syn match cSpaceError display " \+\t"me=e-1 + endif +endif + +" This should be before cErrInParen to avoid problems with #define ({ xxx }) +if exists("c_curly_error") + syntax match cCurlyError "}" + syntax region cBlock start="{" end="}" contains=ALLBUT,cCurlyError,@cParenGroup,cErrInParen,cCppParen,cErrInBracket,cCppBracket,cCppString,@Spell fold +else + syntax region cBlock start="{" end="}" transparent fold +endif + +"catch errors caused by wrong parenthesis and brackets +" also accept <% for {, %> for }, <: for [ and :> for ] (C99) +" But avoid matching <::. +syn cluster cParenGroup contains=cParenError,cIncluded,cSpecial,cCommentSkip,cCommentString,cComment2String,@cCommentGroup,cCommentStartError,cUserCont,cUserLabel,cOctalZero,cCppOut,cCppOut2,cCppSkip,cFormat,cNumber,cFloat,cOctal,cOctalError,cNumbersCom +if exists("c_no_curly_error") + syn region cParen transparent start='(' end=')' contains=ALLBUT,@cParenGroup,cCppParen,cCppString,@Spell + " cCppParen: same as cParen but ends at end-of-line; used in cDefine + syn region cCppParen transparent start='(' skip='\\$' excludenl end=')' end='$' contained contains=ALLBUT,@cParenGroup,cParen,cString,@Spell + syn match cParenError display ")" + syn match cErrInParen display contained "^[{}]\|^<%\|^%>" +elseif exists("c_no_bracket_error") + syn region cParen transparent start='(' end=')' contains=ALLBUT,@cParenGroup,cCppParen,cCppString,@Spell + " cCppParen: same as cParen but ends at end-of-line; used in cDefine + syn region cCppParen transparent start='(' skip='\\$' excludenl end=')' end='$' contained contains=ALLBUT,@cParenGroup,cParen,cString,@Spell + syn match cParenError display ")" + syn match cErrInParen display contained "[{}]\|<%\|%>" +else + syn region cParen transparent start='(' end=')' contains=ALLBUT,@cParenGroup,cCppParen,cErrInBracket,cCppBracket,cCppString,@Spell + " cCppParen: same as cParen but ends at end-of-line; used in cDefine + syn region cCppParen transparent start='(' skip='\\$' excludenl end=')' end='$' contained contains=ALLBUT,@cParenGroup,cErrInBracket,cParen,cBracket,cString,@Spell + syn match cParenError display "[\])]" + syn match cErrInParen display contained "[\]{}]\|<%\|%>" + syn region cBracket transparent start='\[\|<::\@!' end=']\|:>' contains=ALLBUT,@cParenGroup,cErrInParen,cCppParen,cCppBracket,cCppString,@Spell + " cCppBracket: same as cParen but ends at end-of-line; used in cDefine + syn region cCppBracket transparent start='\[\|<::\@!' skip='\\$' excludenl end=']\|:>' end='$' contained contains=ALLBUT,@cParenGroup,cErrInParen,cParen,cBracket,cString,@Spell + syn match cErrInBracket display contained "[){}]\|<%\|%>" +endif + +"integer number, or floating point number without a dot and with "f". +syn case ignore +syn match cNumbers display transparent "\<\d\|\.\d" contains=cNumber,cFloat,cOctalError,cOctal +" Same, but without octal error (for comments) +syn match cNumbersCom display contained transparent "\<\d\|\.\d" contains=cNumber,cFloat,cOctal +syn match cNumber display contained "\d\+\(u\=l\{0,2}\|ll\=u\)\>" +"hex number +syn match cNumber display contained "0x\x\+\(u\=l\{0,2}\|ll\=u\)\>" +" Flag the first zero of an octal number as something special +syn match cOctal display contained "0\o\+\(u\=l\{0,2}\|ll\=u\)\>" contains=cOctalZero +syn match cOctalZero display contained "\<0" +syn match cFloat display contained "\d\+f" +"floating point number, with dot, optional exponent +syn match cFloat display contained "\d\+\.\d*\(e[-+]\=\d\+\)\=[fl]\=" +"floating point number, starting with a dot, optional exponent +syn match cFloat display contained "\.\d\+\(e[-+]\=\d\+\)\=[fl]\=\>" +"floating point number, without dot, with exponent +syn match cFloat display contained "\d\+e[-+]\=\d\+[fl]\=\>" +if !exists("c_no_c99") + "hexadecimal floating point number, optional leading digits, with dot, with exponent + syn match cFloat display contained "0x\x*\.\x\+p[-+]\=\d\+[fl]\=\>" + "hexadecimal floating point number, with leading digits, optional dot, with exponent + syn match cFloat display contained "0x\x\+\.\=p[-+]\=\d\+[fl]\=\>" +endif + +" flag an octal number with wrong digits +syn match cOctalError display contained "0\o*[89]\d*" +syn case match + +if exists("c_comment_strings") + " A comment can contain cString, cCharacter and cNumber. + " But a "*/" inside a cString in a cComment DOES end the comment! So we + " need to use a special type of cString: cCommentString, which also ends on + " "*/", and sees a "*" at the start of the line as comment again. + " Unfortunately this doesn't very well work for // type of comments :-( + syntax match cCommentSkip contained "^\s*\*\($\|\s\+\)" + syntax region cCommentString contained start=+L\=\\\@" skip="\\$" end="$" keepend contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError +syn match cPreCondit display "^\s*\(%:\|#\)\s*\(else\|endif\)\>" +if !exists("c_no_if0") + if !exists("c_no_if0_fold") + syn region cCppOut start="^\s*\(%:\|#\)\s*if\s\+0\+\>" end=".\@=\|$" contains=cCppOut2 fold + else + syn region cCppOut start="^\s*\(%:\|#\)\s*if\s\+0\+\>" end=".\@=\|$" contains=cCppOut2 + endif + syn region cCppOut2 contained start="0" end="^\s*\(%:\|#\)\s*\(endif\>\|else\>\|elif\>\)" contains=cSpaceError,cCppSkip + syn region cCppSkip contained start="^\s*\(%:\|#\)\s*\(if\>\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\(%:\|#\)\s*endif\>" contains=cSpaceError,cCppSkip +endif +syn region cIncluded display contained start=+"+ skip=+\\\\\|\\"+ end=+"+ +syn match cIncluded display contained "<[^>]*>" +syn match cInclude display "^\s*\(%:\|#\)\s*include\>\s*["<]" contains=cIncluded +"syn match cLineSkip "\\$" +syn cluster cPreProcGroup contains=cPreCondit,cIncluded,cInclude,cDefine,cErrInParen,cErrInBracket,cUserLabel,cSpecial,cOctalZero,cCppOut,cCppOut2,cCppSkip,cFormat,cNumber,cFloat,cOctal,cOctalError,cNumbersCom,cString,cCommentSkip,cCommentString,cComment2String,@cCommentGroup,cCommentStartError,cParen,cBracket,cMulti +syn region cDefine start="^\s*\(%:\|#\)\s*\(define\|undef\)\>" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell +syn region cPreProc start="^\s*\(%:\|#\)\s*\(pragma\>\|line\>\|warning\>\|warn\>\|error\>\)" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell + +" Highlight User Labels +syn cluster cMultiGroup contains=cIncluded,cSpecial,cCommentSkip,cCommentString,cComment2String,@cCommentGroup,cCommentStartError,cUserCont,cUserLabel,cOctalZero,cCppOut,cCppOut2,cCppSkip,cFormat,cNumber,cFloat,cOctal,cOctalError,cNumbersCom,cCppParen,cCppBracket,cCppString +syn region cMulti transparent start='?' skip='::' end=':' end=')' end=';' contains=ALLBUT,@cMultiGroup,@Spell +" Avoid matching foo::bar() in C++ by requiring that the next char is not ':' + +syn match cUserLabel display "\I\i*" contained + + +if exists("c_minlines") + let b:c_minlines = c_minlines +else + if !exists("c_no_if0") + let b:c_minlines = 50 " #if 0 constructs can be long + else + let b:c_minlines = 15 " mostly for () constructs + endif +endif +if exists("c_curly_error") + syn sync fromstart +else + exec "syn sync ccomment cComment minlines=" . b:c_minlines +endif + +" Define the default highlighting. +" Only used when an item doesn't have highlighting yet +hi def link cFormat cSpecial +hi def link cCppString cString +hi def link cCommentL cComment +hi def link cCommentStart cComment +hi def link cUserLabel Label +hi def link cRepeat Repeat +hi def link cCharacter Character +hi def link cSpecialCharacter cSpecial +hi def link cNumber Number +hi def link cOctal Number +hi def link cOctalZero PreProc " link this to Error if you want +hi def link cFloat Float +hi def link cOctalError cError +hi def link cParenError cError +hi def link cErrInParen cError +hi def link cErrInBracket cError +hi def link cCommentError cError +hi def link cCommentStartError cError +hi def link cSpaceError cError +hi def link cSpecialError cError +hi def link cCurlyError cError +hi def link cOperator Operator +hi def link cStructure Structure +hi def link cStorageClass StorageClass +hi def link cInclude Include +hi def link cPreProc PreProc +hi def link cDefine Macro +hi def link cIncluded cString +hi def link cError Error +hi def link cStatement Statement +hi def link cPreCondit PreCondit +hi def link cType Type +hi def link cConstant Constant +hi def link cCommentString cString +hi def link cComment2String cString +hi def link cCommentSkip cComment +hi def link cString String +hi def link cComment Comment +hi def link cSpecial SpecialChar +hi def link cTodo Todo +hi def link cBadContinuation Error +hi def link cCppSkip cCppOut +hi def link cCppOut2 cCppOut +hi def link cCppOut Comment + +let b:current_syntax = "eel2" + +" vim: ts=8 diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel2.y b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel2.y new file mode 100644 index 0000000..c7bf561 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel2.y @@ -0,0 +1,376 @@ +%pure-parser +%name-prefix="nseel" +%parse-param { compileContext* context } +%lex-param { void* scanner } + + +/* this will prevent y.tab.c from ever calling yydestruct(), since we do not use it and it is a waste */ +%destructor { + #define yydestruct(a,b,c,d,e) +} VALUE + + +%{ +#ifdef _WIN32 +#include +#endif +#include +#include +#include +#include + +#include "y.tab.h" +#include "ns-eel-int.h" + +#define scanner context->scanner +#define YY_(x) ("") + +%} + +%token VALUE IDENTIFIER TOKEN_SHL TOKEN_SHR +%token TOKEN_LTE TOKEN_GTE TOKEN_EQ TOKEN_EQ_EXACT TOKEN_NE TOKEN_NE_EXACT TOKEN_LOGICAL_AND TOKEN_LOGICAL_OR +%token TOKEN_ADD_OP TOKEN_SUB_OP TOKEN_MOD_OP TOKEN_OR_OP TOKEN_AND_OP TOKEN_XOR_OP TOKEN_DIV_OP TOKEN_MUL_OP TOKEN_POW_OP +%token STRING_LITERAL STRING_IDENTIFIER +%expect 75 +%start program + +%% + +more_params: + expression + | expression ',' more_params + { + $$ = nseel_createMoreParametersOpcode(context,$1,$3); + } + ; + +string: + STRING_LITERAL + | STRING_LITERAL string + { + ((struct eelStringSegmentRec *)$1)->_next = (struct eelStringSegmentRec *)$2; + $$ = $1; + } + ; + +assignable_value: + IDENTIFIER + { + if (!($$ = nseel_resolve_named_symbol(context, $1, -1, NULL))) /* convert from purely named to namespace-relative, etc */ + { + yyerror(&yyloc, context, ""); + YYERROR; + } + } + /* we used to have VALUE in here rather than rvalue, to allow 1=1 1+=2 etc, but silly to, + though this breaks Vmorph, which does 1=1 for a nop, and Jonas DrumReaplacer, which does x = 0 = y = 0 */ + | '(' expression ')' + { + $$ = $2; + } + | IDENTIFIER '(' expression ')' '(' expression ')' + { + int err; + if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, 0, 0, $6, &err))) + { + if (err == -1) yyerror(&yylsp[-2], context, ""); + else if (err == 0) yyerror(&yylsp[-6], context, ""); + else yyerror(&yylsp[-3], context, ""); // parameter count wrong + + YYERROR; + } + } + | IDENTIFIER '(' expression ')' + { + int err; + if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, 0, 0, 0, &err))) + { + if (err == 0) yyerror(&yylsp[-3], context, ""); + else yyerror(&yylsp[0], context, ""); // parameter count wrong + YYERROR; + } + } + | IDENTIFIER '(' ')' + { + int err; + if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, nseel_createCompiledValue(context,0.0), 0, 0, 0,&err))) + { + if (err == 0) yyerror(&yylsp[-2], context, ""); // function not found + else yyerror(&yylsp[0], context, ""); // parameter count wrong + YYERROR; + } + } + | IDENTIFIER '(' expression ',' expression ')' + { + int err; + if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, $5, 0, 0,&err))) + { + if (err == 0) yyerror(&yylsp[-5], context, ""); + else if (err == 2) yyerror(&yylsp[0], context, ""); // needs more than 2 parameters + else yyerror(&yylsp[-2], context, ""); // less than 2 + YYERROR; + } + } + | IDENTIFIER '(' expression ',' expression ',' more_params ')' + { + int err; + if (!($$ = nseel_setCompiledFunctionCallParameters(context,$1, $3, $5, $7, 0, &err))) + { + if (err == 0) yyerror(&yylsp[-7], context, ""); + else if (err==2) yyerror(&yylsp[0], context, ""); // needs more parameters + else if (err==4) yyerror(&yylsp[-4], context, ""); // needs single parameter + else yyerror(&yylsp[-2], context, ""); // less parm + YYERROR; + } + } + | rvalue '[' ']' + { + $$ = nseel_createMemoryAccess(context,$1,0); + } + | rvalue '[' expression ']' + { + $$ = nseel_createMemoryAccess(context,$1,$3); + } + ; + +rvalue: + VALUE + | STRING_IDENTIFIER + | string + { + $$ = nseel_eelMakeOpcodeFromStringSegments(context,(struct eelStringSegmentRec *)$1); + } + | assignable_value + ; + + +assignment: + rvalue + | assignable_value '=' if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_ASSIGN,2,$1,$3); + } + | assignable_value TOKEN_ADD_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_ADD_OP,2,$1,$3); + } + | assignable_value TOKEN_SUB_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_SUB_OP,2,$1,$3); + } + | assignable_value TOKEN_MOD_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_MOD_OP,2,$1,$3); + } + | assignable_value TOKEN_OR_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_OR_OP,2,$1,$3); + } + | assignable_value TOKEN_AND_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_AND_OP,2,$1,$3); + } + | assignable_value TOKEN_XOR_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_XOR_OP,2,$1,$3); + } + | assignable_value TOKEN_DIV_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_DIV_OP,2,$1,$3); + } + | assignable_value TOKEN_MUL_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_MUL_OP,2,$1,$3); + } + | assignable_value TOKEN_POW_OP if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_POW_OP,2,$1,$3); + } + | STRING_IDENTIFIER '=' if_else_expr + { + $$ = nseel_createFunctionByName(context,"strcpy",2,$1,$3,NULL); + } + | STRING_IDENTIFIER TOKEN_ADD_OP if_else_expr + { + $$ = nseel_createFunctionByName(context,"strcat",2,$1,$3,NULL); + } + ; + +unary_expr: + assignment + | '+' unary_expr + { + $$ = $2; + } + | '-' unary_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_UMINUS,1,$2,0); + } + | '!' unary_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_NOT,1,$2,0); + } + ; + +pow_expr: + unary_expr + | pow_expr '^' unary_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_POW,2,$1,$3); + } + ; + +mod_expr: + pow_expr + | mod_expr '%' pow_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_MOD,2,$1,$3); + } + | mod_expr TOKEN_SHL pow_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_SHL,2,$1,$3); + } + | mod_expr TOKEN_SHR pow_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_SHR,2,$1,$3); + } + ; + +div_expr: + mod_expr + | div_expr '/' mod_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_DIVIDE,2,$1,$3); + } + ; + + +mul_expr: + div_expr + | mul_expr '*' div_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_MULTIPLY,2,$1,$3); + } + ; + + +sub_expr: + mul_expr + | sub_expr '-' mul_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_SUB,2,$1,$3); + } + ; + +add_expr: + sub_expr + | add_expr '+' sub_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_ADD,2,$1,$3); + } + ; + +andor_expr: + add_expr + | andor_expr '&' add_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_AND,2,$1,$3); + } + | andor_expr '|' add_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_OR,2,$1,$3); + } + | andor_expr '~' add_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_XOR,2,$1,$3); + } + ; + +cmp_expr: + andor_expr + | cmp_expr '<' andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_LT,2,$1,$3); + } + | cmp_expr '>' andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_GT,2,$1,$3); + } + | cmp_expr TOKEN_LTE andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_LTE,2,$1,$3); + } + | cmp_expr TOKEN_GTE andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_GTE,2,$1,$3); + } + | cmp_expr TOKEN_EQ andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_EQ,2,$1,$3); + } + | cmp_expr TOKEN_EQ_EXACT andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_EQ_EXACT,2,$1,$3); + } + | cmp_expr TOKEN_NE andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_NE,2,$1,$3); + } + | cmp_expr TOKEN_NE_EXACT andor_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_NE_EXACT,2,$1,$3); + } + ; + +logical_and_or_expr: + cmp_expr + | logical_and_or_expr TOKEN_LOGICAL_AND cmp_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_LOGICAL_AND,2,$1,$3); + } + | logical_and_or_expr TOKEN_LOGICAL_OR cmp_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_LOGICAL_OR,2,$1,$3); + } + ; + +if_else_expr: + logical_and_or_expr + | logical_and_or_expr '?' if_else_expr ':' if_else_expr + { + $$ = nseel_createIfElse(context, $1, $3, $5); + } + | logical_and_or_expr '?' ':' if_else_expr + { + $$ = nseel_createIfElse(context, $1, 0, $4); + } + | logical_and_or_expr '?' if_else_expr + { + $$ = nseel_createIfElse(context, $1, $3, 0); + } + ; + + +expression: + if_else_expr + | expression ';' if_else_expr + { + $$ = nseel_createSimpleCompiledFunction(context,FN_JOIN_STATEMENTS,2,$1,$3); + } + | expression ';' + { + $$ = $1; + } + ; + + +program: + expression + { + if (@1.first_line) { } + context->result = $1; + } + ; + + +%% diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_atomic.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_atomic.h new file mode 100644 index 0000000..f71d1c2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_atomic.h @@ -0,0 +1,71 @@ +#ifndef __EEL_ATOMIC_H__ +#define __EEL_ATOMIC_H__ + +// requires these to be defined +//#define EEL_ATOMIC_SET_SCOPE(opaque) WDL_Mutex *mutex = (opaque?&((effectProcessor *)opaque)->m_atomic_mutex:&atomic_mutex); +//#define EEL_ATOMIC_ENTER mutex->Enter() +//#define EEL_ATOMIC_LEAVE mutex->Leave() + +static EEL_F NSEEL_CGEN_CALL atomic_setifeq(void *opaque, EEL_F *a, EEL_F *cmp, EEL_F *nd) +{ + EEL_F ret; + EEL_ATOMIC_SET_SCOPE(opaque) + EEL_ATOMIC_ENTER; + ret = *a; + if (fabs(ret - *cmp) < NSEEL_CLOSEFACTOR) *a = *nd; + EEL_ATOMIC_LEAVE; + return ret; +} + +static EEL_F NSEEL_CGEN_CALL atomic_exch(void *opaque, EEL_F *a, EEL_F *b) +{ + EEL_F tmp; + EEL_ATOMIC_SET_SCOPE(opaque) + EEL_ATOMIC_ENTER; + tmp = *b; + *b = *a; + *a = tmp; + EEL_ATOMIC_LEAVE; + return tmp; +} + +static EEL_F NSEEL_CGEN_CALL atomic_add(void *opaque, EEL_F *a, EEL_F *b) +{ + EEL_F tmp; + EEL_ATOMIC_SET_SCOPE(opaque) + EEL_ATOMIC_ENTER; + tmp = (*a += *b); + EEL_ATOMIC_LEAVE; + return tmp; +} + +static EEL_F NSEEL_CGEN_CALL atomic_set(void *opaque, EEL_F *a, EEL_F *b) +{ + EEL_F tmp; + EEL_ATOMIC_SET_SCOPE(opaque) + EEL_ATOMIC_ENTER; + tmp = *a = *b; + EEL_ATOMIC_LEAVE; + return tmp; +} + +static EEL_F NSEEL_CGEN_CALL atomic_get(void *opaque, EEL_F *a) +{ + EEL_F tmp; + EEL_ATOMIC_SET_SCOPE(opaque) + EEL_ATOMIC_ENTER; + tmp = *a; + EEL_ATOMIC_LEAVE; + return tmp; +} + +static void EEL_atomic_register() +{ + NSEEL_addfunc_retval("atomic_setifequal",3, NSEEL_PProc_THIS, &atomic_setifeq); + NSEEL_addfunc_retval("atomic_exch",2, NSEEL_PProc_THIS, &atomic_exch); + NSEEL_addfunc_retval("atomic_add",2, NSEEL_PProc_THIS, &atomic_add); + NSEEL_addfunc_retval("atomic_set",2, NSEEL_PProc_THIS, &atomic_set); + NSEEL_addfunc_retval("atomic_get",1, NSEEL_PProc_THIS, &atomic_get); +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_eval.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_eval.h new file mode 100644 index 0000000..8c04e96 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_eval.h @@ -0,0 +1,81 @@ +#ifndef _EEL_EVAL_H_ +#define _EEL_EVAL_H_ + +#ifndef EEL_EVAL_GET_CACHED +#define EEL_EVAL_GET_CACHED(str, ch) (NULL) +#endif + +#ifndef EEL_EVAL_SET_CACHED +#define EEL_EVAL_SET_CACHED(sv, ch) { NSEEL_code_free(ch); free(sv); } +#endif + +#ifndef EEL_EVAL_SCOPE_ENTER +#define EEL_EVAL_SCOPE_ENTER 1 +#define EEL_EVAL_SCOPE_LEAVE +#endif + +static EEL_F NSEEL_CGEN_CALL _eel_eval(void *opaque, EEL_F *s) +{ + NSEEL_VMCTX r = EEL_EVAL_GET_VMCTX(opaque); + NSEEL_CODEHANDLE ch = NULL; + char *sv=NULL; + if (r) + { + EEL_STRING_MUTEXLOCK_SCOPE + const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); +#ifdef EEL_STRING_DEBUGOUT + if (!str) + { + EEL_STRING_DEBUGOUT("eval() passed invalid string handle %f",*s); + } +#endif + if (str && *str) + { + sv=EEL_EVAL_GET_CACHED(str,ch); + if (!sv) sv=strdup(str); + } + } + if (sv) + { + if (!ch) ch = NSEEL_code_compile(r,sv,0); + if (ch) + { + if (EEL_EVAL_SCOPE_ENTER) + { + NSEEL_code_execute(ch); + EEL_EVAL_SCOPE_LEAVE + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("eval() reentrancy limit reached"); +#endif + } + + EEL_EVAL_SET_CACHED(sv,ch); + return 1.0; + } + else + { +#ifdef EEL_STRING_DEBUGOUT + const char *err=NSEEL_code_getcodeerror(r); + if (err) EEL_STRING_DEBUGOUT("eval() error: %s",err); +#endif + } + free(sv); + } + return 0.0; +} + +void EEL_eval_register() +{ + NSEEL_addfunc_retval("eval",1,NSEEL_PProc_THIS,&_eel_eval); +} + +#ifdef EEL_WANT_DOCUMENTATION +static const char *eel_eval_function_reference = + "eval\t\"code\"\tExecutes code passed in. Code can use functions, but functions created in code can't be used elsewhere.\0" +; +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_fft.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_fft.h new file mode 100644 index 0000000..6b64d03 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_fft.h @@ -0,0 +1,396 @@ +#ifndef __EEL_FFT_H_ +#define __EEL_FFT_H_ + +#include "../fft.h" +#if WDL_FFT_REALSIZE != EEL_F_SIZE +#error WDL_FFT_REALSIZE -- EEL_F_SIZE size mismatch +#endif + +#ifndef EEL_FFT_MINBITLEN +#define EEL_FFT_MINBITLEN 4 +#endif + +#ifndef EEL_FFT_MAXBITLEN +#define EEL_FFT_MAXBITLEN 15 +#endif + +#ifndef EEL_FFT_MINBITLEN_REORDER +#define EEL_FFT_MINBITLEN_REORDER (EEL_FFT_MINBITLEN-1) +#endif + +//#define EEL_SUPER_FAST_FFT_REORDERING // quite a bit faster (50-100%) than "normal", but uses a 256kb lookup +//#define EEL_SLOW_FFT_REORDERING // 20%-80% slower than normal, alloca() use, no reason to ever use this + +#ifdef EEL_SUPER_FAST_FFT_REORDERING +static int *fft_reorder_table_for_bitsize(int bitsz) +{ + static int s_tab[ (2 << EEL_FFT_MAXBITLEN) + 24*(EEL_FFT_MAXBITLEN-EEL_FFT_MINBITLEN_REORDER+1) ]; // big 256kb table, ugh + if (bitsz<=EEL_FFT_MINBITLEN_REORDER) return s_tab; + return s_tab + (1<= 4 && dir < 8) + { + if (dir == 4 || dir == 5) + { + //timingEnter(0); +#if defined(EEL_SUPER_FAST_FFT_REORDERING) || !defined(EEL_SLOW_FFT_REORDERING) + fft_reorder_buffer(sizebits,(WDL_FFT_COMPLEX*)data,dir==4); +#else + // old blech + const int flen=1<= 0 && dir < 2) + { + WDL_fft((WDL_FFT_COMPLEX*)data,1<= 2 && dir < 4) + { + WDL_real_fft((WDL_FFT_REAL*)data,1<1 && bitl < EEL_FFT_MAXBITLEN) + { + bitl++; + l>>=1; + } + if (bitl < ((dir&4) ? EEL_FFT_MINBITLEN_REORDER : EEL_FFT_MINBITLEN)) // smallest FFT is 16 item, smallest reorder is 8 item + { + return start; + } + ilen=1< NSEEL_RAM_ITEMSPERBLOCK || dest_offs < 0 || src_offs < 0 || + dest_offs >= NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK || src_offs >= NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK) return dest; + if ((dest_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)) + len > NSEEL_RAM_ITEMSPERBLOCK) return dest; + if ((src_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)) + len > NSEEL_RAM_ITEMSPERBLOCK) return dest; + + srcptr = __NSEEL_RAMAlloc(blocks,src_offs); + if (!srcptr || srcptr==&nseel_ramalloc_onfail) return dest; + destptr = __NSEEL_RAMAlloc(blocks,dest_offs); + if (!destptr || destptr==&nseel_ramalloc_onfail) return dest; + + + WDL_fft_complexmul((WDL_FFT_COMPLEX*)destptr,(WDL_FFT_COMPLEX*)srcptr,(len/2)&~1); + + return dest; +} + +void EEL_fft_register() +{ + WDL_fft_init(); +#if defined(EEL_SUPER_FAST_FFT_REORDERING) + if (!fft_reorder_table_for_bitsize(EEL_FFT_MINBITLEN_REORDER)[0]) + { + int x; + for (x=EEL_FFT_MINBITLEN_REORDER;x<=EEL_FFT_MAXBITLEN;x++) fft_make_reorder_table(x,fft_reorder_table_for_bitsize(x)); + } +#endif + NSEEL_addfunc_retptr("convolve_c",3,NSEEL_PProc_RAM,&eel_convolve_c); + NSEEL_addfunc_retptr("fft",2,NSEEL_PProc_RAM,&eel_fft); + NSEEL_addfunc_retptr("ifft",2,NSEEL_PProc_RAM,&eel_ifft); + NSEEL_addfunc_retptr("fft_real",2,NSEEL_PProc_RAM,&eel_fft_real); + NSEEL_addfunc_retptr("ifft_real",2,NSEEL_PProc_RAM,&eel_ifft_real); + NSEEL_addfunc_retptr("fft_permute",2,NSEEL_PProc_RAM,&eel_fft_permute); + NSEEL_addfunc_retptr("fft_ipermute",2,NSEEL_PProc_RAM,&eel_ifft_permute); +} + +#ifdef EEL_WANT_DOCUMENTATION +static const char *eel_fft_function_reference = +"convolve_c\tdest,src,size\tMultiplies each of size complex pairs in dest by the complex pairs in src. Often used for convolution.\0" +"fft\tbuffer,size\tPerforms a FFT on the data in the local memory buffer at the offset specified by the first parameter. The size of the FFT is specified " + "by the second parameter, which must be 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, or 32768. The outputs are permuted, so if " + "you plan to use them in-order, call fft_permute(buffer, size) before and fft_ipermute(buffer,size) after your in-order use. Your inputs or " + "outputs will need to be scaled down by 1/size, if used.\n" + "Note that fft()/ifft() require real / imaginary input pairs, so a 256 point FFT actually works with 512 items.\n" + "Note that fft()/ifft() must NOT cross a 65,536 item boundary, so be sure to specify the offset accordingly.\0" +"ifft\tbuffer,size\tPerform an inverse FFT. For more information see fft().\0" +"fft_real\tbuffer,size\tPerforms an FFT, but takes size input samples and produces size/2 complex output pairs. Usually used along with fft_permute(size/2). Inputs/outputs will need to be scaled by 0.5/size.\0" +"ifft_real\tbuffer,size\tPerforms an inverse FFT, but takes size/2 complex input pairs and produces size real output values. Usually used along with fft_ipermute(size/2).\0" +"fft_permute\tbuffer,size\tPermute the output of fft() to have bands in-order. See fft() for more information.\0" +"fft_ipermute\tbuffer,size\tPermute the input for ifft(), taking bands from in-order to the order ifft() requires. See fft() for more information.\0" +; +#endif + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_files.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_files.h new file mode 100644 index 0000000..7b5dac2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_files.h @@ -0,0 +1,262 @@ +#ifndef __EEL_FILES_H__ +#define __EEL_FILES_H__ + +// should include eel_strings.h before this, probably +//#define EEL_FILE_OPEN(fn,mode) ((instance)opaque)->OpenFile(fn,mode) +//#define EEL_FILE_GETFP(fp) ((instance)opaque)->GetFileFP(fp) +//#define EEL_FILE_CLOSE(fpindex) ((instance)opaque)->CloseFile(fpindex) + +static EEL_F NSEEL_CGEN_CALL _eel_fopen(void *opaque, EEL_F *fn_index, EEL_F *mode_index) +{ + EEL_STRING_MUTEXLOCK_SCOPE + const char *fn = EEL_STRING_GET_FOR_INDEX(*fn_index,NULL); + const char *mode = EEL_STRING_GET_FOR_INDEX(*mode_index,NULL); + if (!fn || !mode) return 0; + return (EEL_F) EEL_FILE_OPEN(fn,mode); +} +static EEL_F NSEEL_CGEN_CALL _eel_fclose(void *opaque, EEL_F *fpp) +{ + EEL_F ret=EEL_FILE_CLOSE((int)*fpp); +#ifdef EEL_STRING_DEBUGOUT + if (ret < 0) EEL_STRING_DEBUGOUT("fclose(): file handle %f not valid",*fpp); +#endif + return ret; +} +static EEL_F NSEEL_CGEN_CALL _eel_fgetc(void *opaque, EEL_F *fpp) +{ + EEL_STRING_MUTEXLOCK_SCOPE + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (fp) return (EEL_F)fgetc(fp); +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fgetc(): file handle %f not valid",*fpp); +#endif + return -1.0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_ftell(void *opaque, EEL_F *fpp) +{ + EEL_STRING_MUTEXLOCK_SCOPE + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (fp) return (EEL_F)ftell(fp); +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("ftell(): file handle %f not valid",*fpp); +#endif + return -1.0; +} +static EEL_F NSEEL_CGEN_CALL _eel_fflush(void *opaque, EEL_F *fpp) +{ + EEL_STRING_MUTEXLOCK_SCOPE + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (fp) { fflush(fp); return 0.0; } +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fflush(): file handle %f not valid",*fpp); +#endif + return -1.0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_feof(void *opaque, EEL_F *fpp) +{ + EEL_STRING_MUTEXLOCK_SCOPE + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (fp) return feof(fp) ? 1.0 : 0.0; +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("feof(): file handle %f not valid",*fpp); +#endif + return -1.0; +} +static EEL_F NSEEL_CGEN_CALL _eel_fseek(void *opaque, EEL_F *fpp, EEL_F *offset, EEL_F *wh) +{ + EEL_STRING_MUTEXLOCK_SCOPE + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (fp) return fseek(fp, (int) *offset, *wh<0 ? SEEK_SET : *wh > 0 ? SEEK_END : SEEK_CUR); +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fseek(): file handle %f not valid",*fpp); +#endif + return -1.0; +} +static EEL_F NSEEL_CGEN_CALL _eel_fgets(void *opaque, EEL_F *fpp, EEL_F *strOut) +{ + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (!fp) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fgets(): file handle %f not valid",*fpp); +#endif + if (wr) wr->Set(""); + return 0.0; + } + char buf[16384]; + buf[0]=0; + fgets(buf,sizeof(buf),fp); + if (wr) + { + wr->Set(buf); + return (EEL_F)wr->GetLength(); + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fgets: bad destination specifier passed %f, throwing away %d bytes",*strOut, (int)strlen(buf)); +#endif + return (int)strlen(buf); + } +} +static EEL_F NSEEL_CGEN_CALL _eel_fread(void *opaque, EEL_F *fpp, EEL_F *strOut, EEL_F *flen) +{ + int use_len = (int) *flen; + if (use_len < 1) return 0.0; + + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fread: bad destination specifier passed %f, not reading %d bytes",*strOut, use_len); +#endif + return -1; + } + + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (!fp) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fread(): file handle %f not valid",*fpp); +#endif + if (wr) wr->Set(""); + return 0.0; + } + + wr->SetLen(use_len); + if (wr->GetLength() != use_len) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fread: error allocating storage for read of %d bytes", use_len); +#endif + return -1.0; + } + use_len = (int)fread((char *)wr->Get(),1,use_len,fp); + wr->SetLen(use_len > 0 ? use_len : 0, true); + return (EEL_F) use_len; +} + +static EEL_F NSEEL_CGEN_CALL _eel_fwrite(void *opaque, EEL_F *fpp, EEL_F *strOut, EEL_F *flen) +{ + EEL_STRING_MUTEXLOCK_SCOPE + int use_len = (int) *flen; + + EEL_STRING_STORAGECLASS *wr=NULL; + const char *str=EEL_STRING_GET_FOR_INDEX(*strOut, &wr); + if (!wr && !str) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fwrite: bad source specifier passed %f, not writing %d bytes",*strOut, use_len); +#endif + return -1.0; + } + if (!wr) + { + const int ssl = (int)strlen(str); + if (use_len < 1 || use_len > ssl) use_len = ssl; + } + else + { + if (use_len < 1 || use_len > wr->GetLength()) use_len = wr->GetLength(); + } + + if (use_len < 1) return 0.0; + + FILE *fp = EEL_FILE_GETFP((int)*fpp); + if (!fp) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fwrite(): file handle %f not valid",*fpp); +#endif + return 0.0; + } + + return (EEL_F) fwrite(str,1,use_len,fp); +} + +static EEL_F NSEEL_CGEN_CALL _eel_fprintf(void *opaque, INT_PTR nparam, EEL_F **parm) +{ + if (opaque && nparam > 1) + { + EEL_STRING_MUTEXLOCK_SCOPE + FILE *fp = EEL_FILE_GETFP((int)*(parm[0])); + if (!fp) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fprintf(): file handle %f not valid",parm[0][0]); +#endif + return 0.0; + } + + EEL_STRING_STORAGECLASS *wr_src=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parm[1]),&wr_src); + if (fmt) + { + char buf[16384]; + const int len = eel_format_strings(opaque,fmt,wr_src?(fmt+wr_src->GetLength()) : NULL, buf,(int)sizeof(buf),(int)nparam-2,parm+2); + + if (len >= 0) + { + return (EEL_F) fwrite(buf,1,len,fp); + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fprintf: bad format string %s",fmt); +#endif + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("fprintf: bad format specifier passed %f",*(parm[1])); +#endif + } + } + return 0.0; +} + +void EEL_file_register() +{ + NSEEL_addfunc_retval("fopen",2,NSEEL_PProc_THIS,&_eel_fopen); + + NSEEL_addfunc_retval("fread",3,NSEEL_PProc_THIS,&_eel_fread); + NSEEL_addfunc_retval("fgets",2,NSEEL_PProc_THIS,&_eel_fgets); + NSEEL_addfunc_retval("fgetc",1,NSEEL_PProc_THIS,&_eel_fgetc); + + NSEEL_addfunc_retval("fwrite",3,NSEEL_PProc_THIS,&_eel_fwrite); + NSEEL_addfunc_varparm("fprintf",2,NSEEL_PProc_THIS,&_eel_fprintf); + + NSEEL_addfunc_retval("fseek",3,NSEEL_PProc_THIS,&_eel_fseek); + NSEEL_addfunc_retval("ftell",1,NSEEL_PProc_THIS,&_eel_ftell); + NSEEL_addfunc_retval("feof",1,NSEEL_PProc_THIS,&_eel_feof); + NSEEL_addfunc_retval("fflush",1,NSEEL_PProc_THIS,&_eel_fflush); + NSEEL_addfunc_retval("fclose",1,NSEEL_PProc_THIS,&_eel_fclose); +} + +#ifdef EEL_WANT_DOCUMENTATION +static const char *eel_file_function_reference = +"fopen\t\"fn\",\"mode\"\tOpens a file \"fn\" with mode \"mode\". For read, use \"r\" or \"rb\", write \"w\" or \"wb\". Returns a positive integer on success.\0" +"fclose\tfp\tCloses a file previously opened with fopen().\0" +"fread\tfp,#str,length\tReads from file fp into #str, up to length bytes. Returns actual length read, or negative if error.\0" +"fgets\tfp,#str\tReads a line from file fp into #str. Returns length of #str read.\0" +"fgetc\tfp\tReads a character from file fp, returns -1 if EOF.\0" +"fwrite\tfp,#str,len\tWrites up to len characters of #str to file fp. If len is less than 1, the full contents of #str will be written. Returns the number of bytes written to file.\0" +"fprintf\tfp,\"format\"[,...]\tFormats a string and writes it to file fp. For more information on format specifiers, see sprintf(). Returns bytes written to file.\0" +"fseek\tfp,offset,whence\tSeeks file fp, offset bytes from whence reference. Whence negative specifies start of file, positive whence specifies end of file, and zero whence specifies current file position.\0" +"ftell\tfp\tRetunrs the current file position.\0" +"feof\tfp\tReturns nonzero if the file fp is at the end of file.\0" +"fflush\tfp\tIf file fp is open for writing, flushes out any buffered data to disk.\0" +; +#endif + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_import.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_import.h new file mode 100644 index 0000000..46e2176 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_import.h @@ -0,0 +1,104 @@ +/******************************************************************************************* + * imported EEL + ******************************************************************************************/ + +void (*NSEEL_addfunc_ret_type)(const char *name, int np, int ret_type, NSEEL_PPPROC pproc, void *fptr, eel_function_table *destination); // ret_type=-1 for bool, 1 for value, 0 for ptr +void (*NSEEL_addfunc_varparm_ex)(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination); + + +NSEEL_VMCTX (*NSEEL_VM_alloc)(); // return a handle +void (*NSEEL_VM_SetGRAM)(NSEEL_VMCTX, void **); +void (*NSEEL_VM_free)(NSEEL_VMCTX ctx); // free when done with a VM and ALL of its code have been freed, as well +void (*NSEEL_VM_SetFunctionTable)(NSEEL_VMCTX, eel_function_table *tab); // use NULL to use default (global) table +EEL_F *(*NSEEL_VM_regvar)(NSEEL_VMCTX ctx, const char *name); // register a variable (before compilation) + +void (*NSEEL_VM_SetCustomFuncThis)(NSEEL_VMCTX ctx, void *thisptr); +NSEEL_CODEHANDLE (*NSEEL_code_compile_ex)(NSEEL_VMCTX ctx, const char *code, int lineoffs, int flags); +void (*NSEEL_VM_set_var_resolver)(NSEEL_VMCTX ctx, EEL_F *(*res)(void *userctx, const char *name), void *userctx); +char *(*NSEEL_code_getcodeerror)(NSEEL_VMCTX ctx); +void (*NSEEL_code_execute)(NSEEL_CODEHANDLE code); +void (*NSEEL_code_free)(NSEEL_CODEHANDLE code); +EEL_F *(*nseel_int_register_var)(compileContext *ctx, const char *name, int isReg, const char **namePtrOut); +void (*NSEEL_VM_enumallvars)(NSEEL_VMCTX ctx, int (*func)(const char *name, EEL_F *val, void *ctx), void *userctx); +EEL_F *(*NSEEL_VM_getramptr)(NSEEL_VMCTX ctx, unsigned int offs, int *validAmt); +void ** (*eel_gmem_attach)(const char *nm, bool is_alloc); +void (*eel_fft_register)(eel_function_table*); + +struct eelStringSegmentRec { + struct eelStringSegmentRec *_next; + const char *str_start; // escaped characters, including opening/trailing characters + int str_len; +}; +void (*NSEEL_VM_SetStringFunc)(NSEEL_VMCTX ctx, + EEL_F (*onString)(void *caller_this, struct eelStringSegmentRec *list), + EEL_F (*onNamedString)(void *caller_this, const char *name)); + +// call with NULL to calculate size, or non-null to generate to buffer (returning size used -- will not null terminate, caller responsibility) +int (*nseel_stringsegments_tobuf)(char *bufOut, int bufout_sz, struct eelStringSegmentRec *list); + +void *(*NSEEL_PProc_RAM)(void *data, int data_size, struct _compileContext *ctx); +void *(*NSEEL_PProc_THIS)(void *data, int data_size, struct _compileContext *ctx); + +void (*eel_enterfp)(int s[2]); +void (*eel_leavefp)(int s[2]); + + +eel_function_table g_eel_function_table; +#define NSEEL_ADDFUNC_DESTINATION (&g_eel_function_table) + +// +// adds a function that returns a value (EEL_F) +#define NSEEL_addfunc_retval(name,np,pproc,fptr) \ + NSEEL_addfunc_ret_type(name,np,1,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) + +// adds a function that returns a pointer (EEL_F*) +#define NSEEL_addfunc_retptr(name,np,pproc,fptr) \ + NSEEL_addfunc_ret_type(name,np,0,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) + +// adds a void or bool function +#define NSEEL_addfunc_retbool(name,np,pproc,fptr) \ + NSEEL_addfunc_ret_type(name,np,-1,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) + +// adds a function that takes min_np or more parameters (func sig needs to be EEL_F func(void *ctx, INT_PTR np, EEL_F **parms) +#define NSEEL_addfunc_varparm(name, min_np, pproc, fptr) \ + NSEEL_addfunc_varparm_ex(name,min_np,0,pproc,fptr,NSEEL_ADDFUNC_DESTINATION) + +// adds a function that takes np parameters via func: sig needs to be EEL_F func(void *ctx, INT_PTR np, EEL_F **parms) +#define NSEEL_addfunc_exparms(name, np, pproc, fptr) \ + NSEEL_addfunc_varparm_ex(name,np,1,pproc,fptr,NSEEL_ADDFUNC_DESTINATION) + +class eel_string_context_state; + +#define __NS_EELINT_H__ + +#define EEL_IMPORT_ALL(IMPORT_FUNC) \ + IMPORT_FUNC(NSEEL_addfunc_ret_type) \ + IMPORT_FUNC(NSEEL_addfunc_varparm_ex) \ + IMPORT_FUNC(NSEEL_VM_free) \ + IMPORT_FUNC(NSEEL_VM_SetFunctionTable) \ + IMPORT_FUNC(NSEEL_VM_regvar) \ + IMPORT_FUNC(NSEEL_VM_SetCustomFuncThis) \ + IMPORT_FUNC(NSEEL_code_compile_ex) \ + IMPORT_FUNC(NSEEL_code_getcodeerror) \ + IMPORT_FUNC(NSEEL_code_execute) \ + IMPORT_FUNC(NSEEL_code_free) \ + IMPORT_FUNC(NSEEL_PProc_THIS) \ + IMPORT_FUNC(NSEEL_PProc_RAM) \ + IMPORT_FUNC(NSEEL_VM_SetStringFunc) \ + IMPORT_FUNC(NSEEL_VM_enumallvars) \ + IMPORT_FUNC(NSEEL_VM_getramptr) \ + IMPORT_FUNC(NSEEL_VM_SetGRAM) \ + IMPORT_FUNC(eel_gmem_attach) \ + IMPORT_FUNC(eel_fft_register) \ + IMPORT_FUNC(nseel_stringsegments_tobuf) \ + IMPORT_FUNC(nseel_int_register_var) \ + IMPORT_FUNC(eel_leavefp) \ + IMPORT_FUNC(eel_enterfp) \ + IMPORT_FUNC(NSEEL_VM_set_var_resolver) \ + IMPORT_FUNC(NSEEL_VM_alloc) /* keep NSEEL_VM_alloc last */ + + + +/******************************************************************************************* + * END of imported EEL + ******************************************************************************************/ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_lice.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_lice.h new file mode 100644 index 0000000..02500a0 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_lice.h @@ -0,0 +1,3223 @@ +#ifndef _EEL_LICE_H_ +#define _EEL_LICE_H_ + +// #define EEL_LICE_GET_FILENAME_FOR_STRING(idx, fs, p) (((sInst*)opaque)->GetFilenameForParameter(idx,fs,p)) +// #define EEL_LICE_GET_CONTEXT(opaque) (((opaque) ? (((sInst *)opaque)->m_gfx_state) : NULL) + + +void eel_lice_register(); + +#ifdef DYNAMIC_LICE + #define LICE_IBitmap LICE_IBitmap_disabledAPI + #include "../lice/lice.h" + + #undef LICE_IBitmap + typedef void LICE_IBitmap; // prevent us from using LICE api directly, in case it ever changes + +class LICE_IFont; + +#ifdef EEL_LICE_API_ONLY +#define EEL_LICE_FUNCDEF extern +#else +#define EEL_LICE_FUNCDEF +#endif + +#define LICE_FUNCTION_VALID(x) (x) + +EEL_LICE_FUNCDEF LICE_IBitmap *(*__LICE_CreateBitmap)(int, int, int); +EEL_LICE_FUNCDEF void (*__LICE_Clear)(LICE_IBitmap *dest, LICE_pixel color); +EEL_LICE_FUNCDEF void (*__LICE_Line)(LICE_IBitmap *dest, int x1, int y1, int x2, int y2, LICE_pixel color, float alpha, int mode, bool aa); +EEL_LICE_FUNCDEF bool (*__LICE_ClipLine)(int* pX1, int* pY1, int* pX2, int* pY2, int xLo, int yLo, int xHi, int yHi); +EEL_LICE_FUNCDEF void (*__LICE_DrawText)(LICE_IBitmap *bm, int x, int y, const char *string, + LICE_pixel color, float alpha, int mode); +EEL_LICE_FUNCDEF void (*__LICE_DrawChar)(LICE_IBitmap *bm, int x, int y, char c, + LICE_pixel color, float alpha, int mode); +EEL_LICE_FUNCDEF void (*__LICE_MeasureText)(const char *string, int *w, int *h); +EEL_LICE_FUNCDEF void (*__LICE_PutPixel)(LICE_IBitmap *bm, int x, int y, LICE_pixel color, float alpha, int mode); +EEL_LICE_FUNCDEF LICE_pixel (*__LICE_GetPixel)(LICE_IBitmap *bm, int x, int y); +EEL_LICE_FUNCDEF void (*__LICE_FillRect)(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel color, float alpha, int mode); +EEL_LICE_FUNCDEF void (*__LICE_DrawRect)(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel color, float alpha, int mode); +EEL_LICE_FUNCDEF LICE_IBitmap *(*__LICE_LoadImage)(const char* filename, LICE_IBitmap* bmp, bool tryIgnoreExtension); +EEL_LICE_FUNCDEF void (*__LICE_Blur)(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, int srcx, int srcy, int srcw, int srch); // src and dest can overlap, however it may look fudgy if they do +EEL_LICE_FUNCDEF void (*__LICE_ScaledBlit)(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, int dstw, int dsth, + float srcx, float srcy, float srcw, float srch, float alpha, int mode); +EEL_LICE_FUNCDEF void (*__LICE_Circle)(LICE_IBitmap* dest, float cx, float cy, float r, LICE_pixel color, float alpha, int mode, bool aa); +EEL_LICE_FUNCDEF void (*__LICE_FillCircle)(LICE_IBitmap* dest, float cx, float cy, float r, LICE_pixel color, float alpha, int mode, bool aa); +EEL_LICE_FUNCDEF void (*__LICE_FillTriangle)(LICE_IBitmap* dest, int x1, int y1, int x2, int y2, int x3, int y3, LICE_pixel color, float alpha, int mode); +EEL_LICE_FUNCDEF void (*__LICE_FillConvexPolygon)(LICE_IBitmap* dest, const int* x, const int* y, int npoints, LICE_pixel color, float alpha, int mode); +EEL_LICE_FUNCDEF void (*__LICE_RoundRect)(LICE_IBitmap *drawbm, float xpos, float ypos, float w, float h, int cornerradius, LICE_pixel col, float alpha, int mode, bool aa); +EEL_LICE_FUNCDEF void (*__LICE_Arc)(LICE_IBitmap* dest, float cx, float cy, float r, float minAngle, float maxAngle, LICE_pixel color, float alpha, int mode, bool aa); + +// if cliptosourcerect is false, then areas outside the source rect can get in (otherwise they are not drawn) +EEL_LICE_FUNCDEF void (*__LICE_RotatedBlit)(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + float srcx, float srcy, float srcw, float srch, + float angle, + bool cliptosourcerect, float alpha, int mode, + float rotxcent, float rotycent); // these coordinates are offset from the center of the image, in source pixel coordinates + + +EEL_LICE_FUNCDEF void (*__LICE_MultiplyAddRect)(LICE_IBitmap *dest, int x, int y, int w, int h, + float rsc, float gsc, float bsc, float asc, // 0-1, or -100 .. +100 if you really are insane + float radd, float gadd, float badd, float aadd); // 0-255 is the normal range on these.. of course its clamped + +EEL_LICE_FUNCDEF void (*__LICE_GradRect)(LICE_IBitmap *dest, int dstx, int dsty, int dstw, int dsth, + float ir, float ig, float ib, float ia, + float drdx, float dgdx, float dbdx, float dadx, + float drdy, float dgdy, float dbdy, float dady, + int mode); + +EEL_LICE_FUNCDEF void (*__LICE_TransformBlit2)(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + double *srcpoints, int div_w, int div_h, // srcpoints coords should be div_w*div_h*2 long, and be in source image coordinates + float alpha, int mode); + +EEL_LICE_FUNCDEF void (*__LICE_DeltaBlit)(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + float srcx, float srcy, float srcw, float srch, + double dsdx, double dtdx, double dsdy, double dtdy, + double dsdxdy, double dtdxdy, + bool cliptosourcerect, float alpha, int mode); + + +#define LICE_Blur __LICE_Blur +#define LICE_Clear __LICE_Clear +#define LICE_Line __LICE_Line +#define LICE_ClipLine __LICE_ClipLine +#define LICE_FillRect __LICE_FillRect +#define LICE_DrawRect __LICE_DrawRect +#define LICE_PutPixel __LICE_PutPixel +#define LICE_GetPixel __LICE_GetPixel +#define LICE_DrawText __LICE_DrawText +#define LICE_DrawChar __LICE_DrawChar +#define LICE_MeasureText __LICE_MeasureText +#define LICE_LoadImage __LICE_LoadImage +#define LICE_RotatedBlit __LICE_RotatedBlit +#define LICE_ScaledBlit __LICE_ScaledBlit +#define LICE_MultiplyAddRect __LICE_MultiplyAddRect +#define LICE_GradRect __LICE_GradRect +#define LICE_TransformBlit2 __LICE_TransformBlit2 +#define LICE_DeltaBlit __LICE_DeltaBlit +#define LICE_Circle __LICE_Circle +#define LICE_FillCircle __LICE_FillCircle +#define LICE_FillTriangle __LICE_FillTriangle +#define LICE_FillConvexPolygon __LICE_FillConvexPolygon +#define LICE_RoundRect __LICE_RoundRect +#define LICE_Arc __LICE_Arc + +EEL_LICE_FUNCDEF HDC (*LICE__GetDC)(LICE_IBitmap *bm); +EEL_LICE_FUNCDEF int (*LICE__GetWidth)(LICE_IBitmap *bm); +EEL_LICE_FUNCDEF int (*LICE__GetHeight)(LICE_IBitmap *bm); +EEL_LICE_FUNCDEF void (*LICE__Destroy)(LICE_IBitmap *bm); +EEL_LICE_FUNCDEF bool (*LICE__resize)(LICE_IBitmap *bm, int w, int h); + +EEL_LICE_FUNCDEF void (*LICE__DestroyFont)(LICE_IFont* font); +EEL_LICE_FUNCDEF LICE_IFont *(*LICE_CreateFont)(); +EEL_LICE_FUNCDEF void (*LICE__SetFromHFont)(LICE_IFont* ifont, HFONT font, int flags); +EEL_LICE_FUNCDEF LICE_pixel (*LICE__SetTextColor)(LICE_IFont* ifont, LICE_pixel color); +EEL_LICE_FUNCDEF void (*LICE__SetTextCombineMode)(LICE_IFont* ifont, int mode, float alpha); +EEL_LICE_FUNCDEF int (*LICE__DrawText)(LICE_IFont* ifont, LICE_IBitmap *bm, const char *str, int strcnt, RECT *rect, UINT dtFlags); + +#else + +#include "../lice/lice.h" +#include "../lice/lice_text.h" + +#define LICE_FUNCTION_VALID(x) (sizeof(int) > 0) + +static HDC LICE__GetDC(LICE_IBitmap *bm) +{ + return bm->getDC(); +} +static int LICE__GetWidth(LICE_IBitmap *bm) +{ + return bm->getWidth(); +} +static int LICE__GetHeight(LICE_IBitmap *bm) +{ + return bm->getHeight(); +} +static void LICE__Destroy(LICE_IBitmap *bm) +{ + delete bm; +} +static void LICE__SetFromHFont(LICE_IFont * ifont, HFONT font, int flags) +{ + if (ifont) ifont->SetFromHFont(font,flags); +} +static LICE_pixel LICE__SetTextColor(LICE_IFont* ifont, LICE_pixel color) +{ + if (ifont) return ifont->SetTextColor(color); + return 0; +} +static void LICE__SetTextCombineMode(LICE_IFont* ifont, int mode, float alpha) +{ + if (ifont) ifont->SetCombineMode(mode, alpha); +} +static int LICE__DrawText(LICE_IFont* ifont, LICE_IBitmap *bm, const char *str, int strcnt, RECT *rect, UINT dtFlags) +{ + if (ifont) return ifont->DrawText(bm, str, strcnt, rect, dtFlags); + return 0; +} + + +static LICE_IFont *LICE_CreateFont() +{ + return new LICE_CachedFont(); +} +static void LICE__DestroyFont(LICE_IFont *bm) +{ + delete bm; +} +static bool LICE__resize(LICE_IBitmap *bm, int w, int h) +{ + return bm->resize(w,h); +} + +static LICE_IBitmap *__LICE_CreateBitmap(int mode, int w, int h) +{ + if (mode==1) return new LICE_SysBitmap(w,h); + return new LICE_MemBitmap(w,h); +} + + +#endif + +#include "../wdlutf8.h" + + +class eel_lice_state +{ +public: + + eel_lice_state(NSEEL_VMCTX vm, void *ctx, int image_slots, int font_slots); + ~eel_lice_state(); + + void resetVarsToStock() + { + if (m_gfx_a&&m_gfx_r&&m_gfx_g&&m_gfx_b) *m_gfx_r=*m_gfx_g=*m_gfx_b=*m_gfx_a=1.0; + if (m_gfx_a2) *m_gfx_a2=1.0; + if (m_gfx_dest) *m_gfx_dest=-1.0; + if (m_mouse_wheel) *m_mouse_wheel=0.0; + if (m_mouse_hwheel) *m_mouse_hwheel=0.0; + // todo: reset others? + } + + LICE_IBitmap *m_framebuffer, *m_framebuffer_extra; + int m_framebuffer_dirty; + + struct img_shared_state + { + LICE_IBitmap *bm; + int refcnt; + + img_shared_state(LICE_IBitmap *b) : bm(b), refcnt(1) { } + + void release() + { + if (wdl_atomic_decr(&refcnt)==0) delete this; + } + + private: + + ~img_shared_state() + { + s_img_cache_mutex.Enter(); + for (int x = 0; x < s_img_cache.GetSize(); x ++) + { + if (s_img_cache.Enumerate(x) == this) + { + s_img_cache.DeleteByIndex(x); + break; + } + } + s_img_cache_mutex.Leave(); + if (LICE_FUNCTION_VALID(LICE__Destroy)) + LICE__Destroy(bm); + } + }; + + static WDL_StringKeyedArray s_img_cache; + static WDL_Mutex s_img_cache_mutex; + + struct img_state + { + LICE_IBitmap *bm; + img_shared_state *shared; + + void clear(LICE_IBitmap *bmnew=NULL, img_shared_state *sharednew=NULL) + { + if (shared) shared->release(); + if (bm && LICE_FUNCTION_VALID(LICE__Destroy)) + LICE__Destroy(bm); + bm = bmnew; + shared = sharednew; + } + void on_write() + { + if (shared && WDL_NORMALLY(shared->bm)) + { + const int bmw = LICE__GetWidth(shared->bm), bmh = LICE__GetHeight(shared->bm); + bm = __LICE_CreateBitmap(1,bmw,bmh); + LICE_ScaledBlit(bm,shared->bm, // copy the entire image + 0,0,bmw,bmh, + 0.0f,0.0f,(float)bmw,(float)bmh, + 1.0f,LICE_BLIT_MODE_COPY); + shared->release(); + shared = NULL; + } + } + }; + + bool do_load_image(int img, const char *str); + + WDL_TypedBuf m_gfx_images; + struct gfxFontStruct { + LICE_IFont *font; + char last_fontname[128]; + char actual_fontname[128]; + int last_fontsize; + int last_fontflag; + + int use_fonth; + }; + WDL_TypedBuf m_gfx_fonts; + enum { + EELFONT_FLAG_BOLD = (1<<24), + EELFONT_FLAG_ITALIC = (2<<24), + EELFONT_FLAG_UNDERLINE = (4<<24), + EELFONT_FLAG_MASK = EELFONT_FLAG_BOLD|EELFONT_FLAG_ITALIC|EELFONT_FLAG_UNDERLINE + }; + + int m_gfx_font_active; // -1 for default, otherwise index into gfx_fonts (NOTE: this differs from the exposed API, which defines 0 as default, 1-n) + LICE_IFont *GetActiveFont() { return m_gfx_font_active>=0&&m_gfx_font_active-2.0) + { + if (idx < 0.0) return m_framebuffer; + + const int a = (int)idx; + if (a >= 0 && a < m_gfx_images.GetSize()) + { + img_state *rec = m_gfx_images.Get() + a; + if (is_wr) rec->on_write(); + return rec->shared ? rec->shared->bm : rec->bm; + } + } + return NULL; + }; + + void SetImageDirty(LICE_IBitmap *bm) + { + if (bm == m_framebuffer && !m_framebuffer_dirty) + { + if (m_gfx_clear && *m_gfx_clear > -1.0) + { + const int a=(int)*m_gfx_clear; + if (LICE_FUNCTION_VALID(LICE_Clear)) LICE_Clear(m_framebuffer,LICE_RGBA((a&0xff),((a>>8)&0xff),((a>>16)&0xff),0)); + } + m_framebuffer_dirty=1; + } + } + + // R, G, B, A, w, h, x, y, mode(1=add,0=copy) + EEL_F *m_gfx_r, *m_gfx_g, *m_gfx_b, *m_gfx_w, *m_gfx_h, *m_gfx_a, *m_gfx_x, *m_gfx_y, *m_gfx_mode, *m_gfx_clear, *m_gfx_texth,*m_gfx_dest, *m_gfx_a2; + EEL_F *m_mouse_x, *m_mouse_y, *m_mouse_cap, *m_mouse_wheel, *m_mouse_hwheel; + EEL_F *m_gfx_ext_retina; + + NSEEL_VMCTX m_vmref; + void *m_user_ctx; + + int setup_frame(HWND hwnd, RECT r, int _mouse_x=0, int _mouse_y=0, int has_dpi=0); // mouse_x/y used only if hwnd is NULL + void finish_draw(); + + void gfx_lineto(EEL_F xpos, EEL_F ypos, EEL_F aaflag); + void gfx_rectto(EEL_F xpos, EEL_F ypos); + void gfx_line(int np, EEL_F **parms); + void gfx_rect(int np, EEL_F **parms); + void gfx_roundrect(int np, EEL_F **parms); + void gfx_arc(int np, EEL_F **parms); + void gfx_set(int np, EEL_F **parms); + void gfx_grad_or_muladd_rect(int mode, int np, EEL_F **parms); + void gfx_setpixel(EEL_F r, EEL_F g, EEL_F b); + void gfx_getpixel(EEL_F *r, EEL_F *g, EEL_F *b); + void gfx_drawnumber(EEL_F n, EEL_F ndigits); + void gfx_drawchar(EEL_F ch); + void gfx_getimgdim(EEL_F img, EEL_F *w, EEL_F *h); + EEL_F gfx_setimgdim(int img, EEL_F *w, EEL_F *h); + void gfx_blurto(EEL_F x, EEL_F y); + void gfx_blitext(EEL_F img, EEL_F *coords, EEL_F angle); + void gfx_blitext2(int np, EEL_F **parms, int mode); // 0=blit, 1=deltablit + void gfx_transformblit(EEL_F **parms, int div_w, int div_h, EEL_F *tab); // parms[0]=src, 1-4=x,y,w,h + void gfx_circle(float x, float y, float r, bool fill, bool aaflag); + void gfx_triangle(EEL_F** parms, int nparms); + void gfx_drawstr(void *opaque, EEL_F **parms, int nparms, int formatmode); // formatmode=1 for format, 2 for purely measure no format, 3 for measure char + EEL_F gfx_loadimg(void *opaque, int img, EEL_F loadFrom); + EEL_F gfx_setfont(void *opaque, int np, EEL_F **parms); + EEL_F gfx_getfont(void *opaque, int np, EEL_F **parms); + EEL_F gfx_getdropfile(void *opaque, int np, EEL_F **parms); + + LICE_pixel getCurColor(); + int getCurMode(); + int getCurModeForBlit(bool isFBsrc); + +#ifdef EEL_LICE_WANT_STANDALONE + HWND create_wnd(HWND par, int isChild); + HWND hwnd_standalone; + int hwnd_standalone_kb_state[32]; // pressed keys, if any + + // these have to be **parms because of the hack for getting string from parm index + EEL_F gfx_showmenu(void* opaque, EEL_F** parms, int nparms); + EEL_F gfx_setcursor(void* opaque, EEL_F** parms, int nparms); + + int m_kb_queue[64]; + unsigned char m_kb_queue_valid; + unsigned char m_kb_queue_pos; + HCURSOR m_cursor; + int m_cursor_resid; +#ifdef EEL_LICE_LOADTHEMECURSOR + char m_cursor_name[128]; +#endif + +#ifndef EEL_LICE_STANDALONE_NOINITQUIT + RECT m_last_undocked_r; +#endif + +#endif + + DWORD m_last_menu_time; + int m_last_menu_cnt; + + int m_has_cap; // high 16 bits are current capture state, low 16 bits are temporary flags from mousedown + bool m_has_had_getch; // set on first gfx_getchar(), makes mouse_cap updated with modifiers even when no mouse click is down + + WDL_PtrList m_ddrop_files; +}; + + +#ifndef EEL_LICE_API_ONLY + +WDL_StringKeyedArray eel_lice_state::s_img_cache(true); +WDL_Mutex eel_lice_state::s_img_cache_mutex; + +eel_lice_state::eel_lice_state(NSEEL_VMCTX vm, void *ctx, int image_slots, int font_slots) +{ +#ifdef EEL_LICE_WANT_STANDALONE + hwnd_standalone=NULL; + memset(hwnd_standalone_kb_state,0,sizeof(hwnd_standalone_kb_state)); + m_kb_queue_valid=0; + m_cursor_resid=0; + m_cursor = NULL; +#ifndef EEL_LICE_STANDALONE_NOINITQUIT + memset(&m_last_undocked_r,0,sizeof(m_last_undocked_r)); +#endif + +#ifdef EEL_LICE_LOADTHEMECURSOR + m_cursor_name[0]=0; +#endif +#endif + m_user_ctx=ctx; + m_vmref= vm; + m_gfx_font_active=-1; + m_gfx_fonts.Resize(font_slots); + memset(m_gfx_fonts.Get(),0,m_gfx_fonts.GetSize()*sizeof(m_gfx_fonts.Get()[0])); + + m_gfx_images.Resize(image_slots); + memset(m_gfx_images.Get(),0,m_gfx_images.GetSize()*sizeof(m_gfx_images.Get()[0])); + m_framebuffer=m_framebuffer_extra=0; + m_framebuffer_dirty=0; + + m_gfx_r = NSEEL_VM_regvar(vm,"gfx_r"); + m_gfx_g = NSEEL_VM_regvar(vm,"gfx_g"); + m_gfx_b = NSEEL_VM_regvar(vm,"gfx_b"); + m_gfx_a = NSEEL_VM_regvar(vm,"gfx_a"); + m_gfx_a2 = NSEEL_VM_regvar(vm,"gfx_a2"); + + m_gfx_w = NSEEL_VM_regvar(vm,"gfx_w"); + m_gfx_h = NSEEL_VM_regvar(vm,"gfx_h"); + m_gfx_x = NSEEL_VM_regvar(vm,"gfx_x"); + m_gfx_y = NSEEL_VM_regvar(vm,"gfx_y"); + m_gfx_mode = NSEEL_VM_regvar(vm,"gfx_mode"); + m_gfx_clear = NSEEL_VM_regvar(vm,"gfx_clear"); + m_gfx_texth = NSEEL_VM_regvar(vm,"gfx_texth"); + m_gfx_dest = NSEEL_VM_regvar(vm,"gfx_dest"); + m_gfx_ext_retina = NSEEL_VM_regvar(vm,"gfx_ext_retina"); + + m_mouse_x = NSEEL_VM_regvar(vm,"mouse_x"); + m_mouse_y = NSEEL_VM_regvar(vm,"mouse_y"); + m_mouse_cap = NSEEL_VM_regvar(vm,"mouse_cap"); + m_mouse_wheel=NSEEL_VM_regvar(vm,"mouse_wheel"); + m_mouse_hwheel=NSEEL_VM_regvar(vm,"mouse_hwheel"); + + if (m_gfx_texth) *m_gfx_texth=8; + + m_has_cap=0; + m_has_had_getch=false; + m_last_menu_time = GetTickCount() - 100000; + m_last_menu_cnt = 0; +} +eel_lice_state::~eel_lice_state() +{ +#ifdef EEL_LICE_WANT_STANDALONE + if (hwnd_standalone) DestroyWindow(hwnd_standalone); +#endif + if (LICE_FUNCTION_VALID(LICE__Destroy)) + { + LICE__Destroy(m_framebuffer_extra); + LICE__Destroy(m_framebuffer); + } + for (int x=0;x>4)&0xf; + if (sm > LICE_BLIT_MODE_COPY && sm <= LICE_BLIT_MODE_HSVADJ) return sm; + + return (gmode&1) ? LICE_BLIT_MODE_ADD : LICE_BLIT_MODE_COPY; +} +int eel_lice_state::getCurModeForBlit(bool isFBsrc) +{ + const int gmode = (int) (*m_gfx_mode); + + const int sm=(gmode>>4)&0xf; + + int mode; + if (sm > LICE_BLIT_MODE_COPY && sm <= LICE_BLIT_MODE_HSVADJ) mode=sm; + else mode=((gmode&1) ? LICE_BLIT_MODE_ADD : LICE_BLIT_MODE_COPY); + + + if (!isFBsrc && !(gmode&2)) mode|=LICE_BLIT_USE_ALPHA; + if (!(gmode&4)) mode|=LICE_BLIT_FILTER_BILINEAR; + + return mode; +} +LICE_pixel eel_lice_state::getCurColor() +{ + int red=(int) (*m_gfx_r*255.0); + int green=(int) (*m_gfx_g*255.0); + int blue=(int) (*m_gfx_b*255.0); + int a2=(int) (*m_gfx_a2*255.0); + if (red<0) red=0;else if (red>255)red=255; + if (green<0) green=0;else if (green>255)green=255; + if (blue<0) blue=0; else if (blue>255) blue=255; + if (a2<0) a2=0; else if (a2>255) a2=255; + return LICE_RGBA(red,green,blue,a2); +} + + +static EEL_F * NSEEL_CGEN_CALL _gfx_lineto(void *opaque, EEL_F *xpos, EEL_F *ypos, EEL_F *useaa) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_lineto(*xpos, *ypos, *useaa); + return xpos; +} +static EEL_F * NSEEL_CGEN_CALL _gfx_lineto2(void *opaque, EEL_F *xpos, EEL_F *ypos) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_lineto(*xpos, *ypos, 1.0f); + return xpos; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_rectto(void *opaque, EEL_F *xpos, EEL_F *ypos) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_rectto(*xpos, *ypos); + return xpos; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_line(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_line((int)np,parms); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_rect(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_rect((int)np,parms); + return 0.0; +} +static EEL_F NSEEL_CGEN_CALL _gfx_roundrect(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_roundrect((int)np,parms); + return 0.0; +} +static EEL_F NSEEL_CGEN_CALL _gfx_arc(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_arc((int)np,parms); + return 0.0; +} +static EEL_F NSEEL_CGEN_CALL _gfx_set(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_set((int)np,parms); + return 0.0; +} +static EEL_F NSEEL_CGEN_CALL _gfx_gradrect(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_grad_or_muladd_rect(0,(int)np,parms); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_muladdrect(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_grad_or_muladd_rect(1,(int)np,parms); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_deltablit(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_blitext2((int)np,parms,1); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_transformblit(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { +#ifndef EEL_LICE_NO_RAM + const int divw = (int) (parms[5][0]+0.5); + const int divh = (int) (parms[6][0]+0.5); + if (divw < 1 || divh < 1) return 0.0; + const int sz = divw*divh*2; + +#ifdef EEL_LICE_RAMFUNC + EEL_F *d = EEL_LICE_RAMFUNC(opaque,7,sz); + if (!d) return 0.0; +#else + EEL_F **blocks = ctx->m_vmref ? ((compileContext*)ctx->m_vmref)->ram_state->blocks : 0; + if (!blocks || np < 8) return 0.0; + + const int addr1= (int) (parms[7][0]+0.5); + EEL_F *d=__NSEEL_RAMAlloc(blocks,addr1); + if (sz>NSEEL_RAM_ITEMSPERBLOCK) + { + int x; + for(x=NSEEL_RAM_ITEMSPERBLOCK;xgfx_transformblit(parms,divw,divh,d); +#endif + } + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_circle(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + bool aa = true, fill = false; + if (np>3) fill = parms[3][0] > 0.5; + if (np>4) aa = parms[4][0] > 0.5; + if (ctx) ctx->gfx_circle((float)parms[0][0], (float)parms[1][0], (float)parms[2][0], fill, aa); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_triangle(void* opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_triangle(parms, (int)np); + return 0.0; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_drawnumber(void *opaque, EEL_F *n, EEL_F *nd) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_drawnumber(*n, *nd); + return n; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_drawchar(void *opaque, EEL_F *n) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_drawchar(*n); + return n; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_measurestr(void *opaque, EEL_F *str, EEL_F *xOut, EEL_F *yOut) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { + EEL_F *p[3]={str,xOut,yOut}; + ctx->gfx_drawstr(opaque,p,3,2); + } + return str; +} +static EEL_F * NSEEL_CGEN_CALL _gfx_measurechar(void *opaque, EEL_F *str, EEL_F *xOut, EEL_F *yOut) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { + EEL_F *p[3]={str,xOut,yOut}; + ctx->gfx_drawstr(opaque,p,3,3); + } + return str; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_drawstr(void *opaque, INT_PTR nparms, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_drawstr(opaque,parms,(int)nparms,0); + return parms[0][0]; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_printf(void *opaque, INT_PTR nparms, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx && nparms>0) + { + EEL_F v= **parms; + ctx->gfx_drawstr(opaque,parms,(int)nparms,1); + return v; + } + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_showmenu(void* opaque, INT_PTR nparms, EEL_F **parms) +{ + eel_lice_state* ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) return ctx->gfx_showmenu(opaque, parms, (int)nparms); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_setcursor(void* opaque, INT_PTR nparms, EEL_F **parms) +{ + eel_lice_state* ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) return ctx->gfx_setcursor(opaque, parms, (int)nparms); + return 0.0; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_setpixel(void *opaque, EEL_F *r, EEL_F *g, EEL_F *b) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_setpixel(*r, *g, *b); + return r; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_getpixel(void *opaque, EEL_F *r, EEL_F *g, EEL_F *b) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_getpixel(r, g, b); + return r; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_setfont(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) return ctx->gfx_setfont(opaque,(int)np,parms); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_getfont(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { + const int idx=ctx->m_gfx_font_active; + if (idx>=0 && idx < ctx->m_gfx_fonts.GetSize()) + { + eel_lice_state::gfxFontStruct* f=ctx->m_gfx_fonts.Get()+idx; + + EEL_STRING_MUTEXLOCK_SCOPE + +#ifdef NOT_EEL_STRING_UPDATE_STRING + NOT_EEL_STRING_UPDATE_STRING(parms[0][0],f->actual_fontname); +#else + WDL_FastString *fs=NULL; + EEL_STRING_GET_FOR_WRITE(parms[0][0],&fs); + if (fs) fs->Set(f->actual_fontname); +#endif + } + return idx; + } + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_blit2(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx && np>=3) + { + ctx->gfx_blitext2((int)np,parms,0); + return *(parms[0]); + } + return 0.0; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_blitext(void *opaque, EEL_F *img, EEL_F *coordidx, EEL_F *rotate) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { +#ifndef EEL_LICE_NO_RAM +#ifdef EEL_LICE_RAMFUNC + EEL_F *buf = EEL_LICE_RAMFUNC(opaque,1,10); + if (!buf) return img; +#else + EEL_F fc = *coordidx; + if (fc < -0.5 || fc >= NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK) return img; + int a=(int)fc; + if (a<0) return img; + + EEL_F buf[10]; + int x; + EEL_F **blocks = ctx->m_vmref ? ((compileContext*)ctx->m_vmref)->ram_state->blocks : 0; + if (!blocks) return img; + for (x = 0;x < 10; x ++) + { + EEL_F *d=__NSEEL_RAMAlloc(blocks,a++); + if (!d || d==&nseel_ramalloc_onfail) return img; + buf[x]=*d; + } +#endif + // read megabuf + ctx->gfx_blitext(*img,buf,*rotate); +#endif + } + return img; +} + + +static EEL_F * NSEEL_CGEN_CALL _gfx_blurto(void *opaque, EEL_F *x, EEL_F *y) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_blurto(*x,*y); + return x; +} + +static EEL_F * NSEEL_CGEN_CALL _gfx_getimgdim(void *opaque, EEL_F *img, EEL_F *w, EEL_F *h) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) ctx->gfx_getimgdim(*img,w,h); + return img; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_loadimg(void *opaque, EEL_F *img, EEL_F *fr) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) return ctx->gfx_loadimg(opaque,(int)*img,*fr); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_getdropfile(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) return ctx->gfx_getdropfile(opaque,(int) np, parms); + return 0.0; +} +static EEL_F NSEEL_CGEN_CALL _gfx_setimgdim(void *opaque, EEL_F *img, EEL_F *w, EEL_F *h) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) return ctx->gfx_setimgdim((int)*img,w,h); + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_getsyscol(void* ctxe, INT_PTR np, EEL_F **parms) +{ + return (EEL_F)LICE_RGBA_FROMNATIVE(GetSysColor(COLOR_3DFACE)); +} + +void eel_lice_state::gfx_lineto(EEL_F xpos, EEL_F ypos, EEL_F aaflag) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_lineto"); + if (!dest) return; + + int x1=(int)floor(xpos),y1=(int)floor(ypos),x2=(int)floor(*m_gfx_x), y2=(int)floor(*m_gfx_y); + if (LICE_FUNCTION_VALID(LICE__GetWidth) && LICE_FUNCTION_VALID(LICE__GetHeight) && LICE_FUNCTION_VALID(LICE_Line) && + LICE_FUNCTION_VALID(LICE_ClipLine) && + LICE_ClipLine(&x1,&y1,&x2,&y2,0,0,LICE__GetWidth(dest),LICE__GetHeight(dest))) + { + SetImageDirty(dest); + LICE_Line(dest,x1,y1,x2,y2,getCurColor(),(float) *m_gfx_a,getCurMode(),aaflag > 0.5); + } + *m_gfx_x = xpos; + *m_gfx_y = ypos; + +} + +void eel_lice_state::gfx_circle(float x, float y, float r, bool fill, bool aaflag) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_circle"); + if (!dest) return; + + if (LICE_FUNCTION_VALID(LICE_Circle) && LICE_FUNCTION_VALID(LICE_FillCircle)) + { + SetImageDirty(dest); + if(fill) + LICE_FillCircle(dest, x, y, r, getCurColor(), (float) *m_gfx_a, getCurMode(), aaflag); + else + LICE_Circle(dest, x, y, r, getCurColor(), (float) *m_gfx_a, getCurMode(), aaflag); + } +} + +void eel_lice_state::gfx_triangle(EEL_F** parms, int np) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest, "gfx_triangle"); + if (np >= 6) + { + np &= ~1; + SetImageDirty(dest); + if (np == 6) + { + if (!LICE_FUNCTION_VALID(LICE_FillTriangle)) return; + + LICE_FillTriangle(dest, (int)parms[0][0], (int)parms[1][0], (int)parms[2][0], (int)parms[3][0], + (int)parms[4][0], (int)parms[5][0], getCurColor(), (float)*m_gfx_a, getCurMode()); + } + else + { + if (!LICE_FUNCTION_VALID(LICE_FillConvexPolygon)) return; + + const int maxpt = 512; + const int n = wdl_min(np/2, maxpt); + int i, rdi=0; + int x[maxpt], y[maxpt]; + for (i=0; i < n; i++) + { + x[i]=(int)parms[rdi++][0]; + y[i]=(int)parms[rdi++][0]; + } + + LICE_FillConvexPolygon(dest, x, y, n, getCurColor(), (float)*m_gfx_a, getCurMode()); + } + } +} + +void eel_lice_state::gfx_rectto(EEL_F xpos, EEL_F ypos) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_rectto"); + if (!dest) return; + + EEL_F x1=xpos,y1=ypos,x2=*m_gfx_x, y2=*m_gfx_y; + if (x2 0.5 && y2-y1 > 0.5) + { + SetImageDirty(dest); + LICE_FillRect(dest,(int)x1,(int)y1,(int)(x2-x1),(int)(y2-y1),getCurColor(),(float)*m_gfx_a,getCurMode()); + } + *m_gfx_x = xpos; + *m_gfx_y = ypos; +} + + +void eel_lice_state::gfx_line(int np, EEL_F **parms) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_line"); + if (!dest) return; + + int x1=(int)floor(parms[0][0]),y1=(int)floor(parms[1][0]),x2=(int)floor(parms[2][0]), y2=(int)floor(parms[3][0]); + if (LICE_FUNCTION_VALID(LICE__GetWidth) && + LICE_FUNCTION_VALID(LICE__GetHeight) && + LICE_FUNCTION_VALID(LICE_Line) && + LICE_FUNCTION_VALID(LICE_ClipLine) && LICE_ClipLine(&x1,&y1,&x2,&y2,0,0,LICE__GetWidth(dest),LICE__GetHeight(dest))) + { + SetImageDirty(dest); + LICE_Line(dest,x1,y1,x2,y2,getCurColor(),(float)*m_gfx_a,getCurMode(),np< 5 || parms[4][0] > 0.5); + } +} + +void eel_lice_state::gfx_rect(int np, EEL_F **parms) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_rect"); + if (!dest) return; + + int x1=(int)floor(parms[0][0]),y1=(int)floor(parms[1][0]),w=(int)floor(parms[2][0]),h=(int)floor(parms[3][0]); + int filled=(np < 5 || parms[4][0] > 0.5); + + if (LICE_FUNCTION_VALID(LICE_FillRect) && LICE_FUNCTION_VALID(LICE_DrawRect) && w>0 && h>0) + { + SetImageDirty(dest); + if (filled) LICE_FillRect(dest,x1,y1,w,h,getCurColor(),(float)*m_gfx_a,getCurMode()); + else LICE_DrawRect(dest, x1, y1, w-1, h-1, getCurColor(), (float)*m_gfx_a, getCurMode()); + } +} + +void eel_lice_state::gfx_roundrect(int np, EEL_F **parms) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_roundrect"); + if (!dest) return; + + const bool aa = np <= 5 || parms[5][0]>0.5; + + if (LICE_FUNCTION_VALID(LICE_RoundRect) && parms[2][0]>0 && parms[3][0]>0) + { + SetImageDirty(dest); + LICE_RoundRect(dest, (float)parms[0][0], (float)parms[1][0], (float)parms[2][0], (float)parms[3][0], (int)parms[4][0], getCurColor(), (float)*m_gfx_a, getCurMode(), aa); + } +} + +void eel_lice_state::gfx_arc(int np, EEL_F **parms) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_arc"); + if (!dest) return; + + const bool aa = np <= 5 || parms[5][0]>0.5; + + if (LICE_FUNCTION_VALID(LICE_Arc)) + { + SetImageDirty(dest); + LICE_Arc(dest, (float)parms[0][0], (float)parms[1][0], (float)parms[2][0], (float)parms[3][0], (float)parms[4][0], getCurColor(), (float)*m_gfx_a, getCurMode(), aa); + } +} + +void eel_lice_state::gfx_grad_or_muladd_rect(int whichmode, int np, EEL_F **parms) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,whichmode==0?"gfx_gradrect":"gfx_muladdrect"); + if (!dest) return; + + const int x1=(int)floor(parms[0][0]),y1=(int)floor(parms[1][0]),w=(int)floor(parms[2][0]), h=(int)floor(parms[3][0]); + + if (w>0 && h>0) + { + SetImageDirty(dest); + if (whichmode==0 && LICE_FUNCTION_VALID(LICE_GradRect) && np > 7) + { + LICE_GradRect(dest,x1,y1,w,h,(float)parms[4][0],(float)parms[5][0],(float)parms[6][0],(float)parms[7][0], + np > 8 ? (float)parms[8][0]:0.0f, np > 9 ? (float)parms[9][0]:0.0f, np > 10 ? (float)parms[10][0]:0.0f, np > 11 ? (float)parms[11][0]:0.0f, + np > 12 ? (float)parms[12][0]:0.0f, np > 13 ? (float)parms[13][0]:0.0f, np > 14 ? (float)parms[14][0]:0.0f, np > 15 ? (float)parms[15][0]:0.0f, + getCurMode()); + } + else if (whichmode==1 && LICE_FUNCTION_VALID(LICE_MultiplyAddRect) && np > 6) + { + const double sc = 255.0; + LICE_MultiplyAddRect(dest,x1,y1,w,h,(float)parms[4][0],(float)parms[5][0],(float)parms[6][0],np>7 ? (float)parms[7][0]:1.0f, + (float)(np > 8 ? sc*parms[8][0]:0.0), (float)(np > 9 ? sc*parms[9][0]:0.0), (float)(np > 10 ? sc*parms[10][0]:0.0), (float)(np > 11 ? sc*parms[11][0]:0.0)); + } + } +} + + + +void eel_lice_state::gfx_setpixel(EEL_F r, EEL_F g, EEL_F b) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_setpixel"); + if (!dest) return; + + int red=(int) (r*255.0); + int green=(int) (g*255.0); + int blue=(int) (b*255.0); + if (red<0) red=0;else if (red>255)red=255; + if (green<0) green=0;else if (green>255)green=255; + if (blue<0) blue=0; else if (blue>255) blue=255; + + if (LICE_FUNCTION_VALID(LICE_PutPixel)) + { + SetImageDirty(dest); + LICE_PutPixel(dest,(int)*m_gfx_x, (int)*m_gfx_y,LICE_RGBA(red,green,blue,255), (float)*m_gfx_a,getCurMode()); + } +} + +void eel_lice_state::gfx_getimgdim(EEL_F img, EEL_F *w, EEL_F *h) +{ + *w=*h=0; +#ifdef DYNAMIC_LICE + if (!LICE__GetWidth || !LICE__GetHeight) return; +#endif + + LICE_IBitmap *bm=GetImageForIndex(img,"gfx_getimgdim",false); + if (bm) + { + *w=LICE__GetWidth(bm); + *h=LICE__GetHeight(bm); + } +} + +EEL_F eel_lice_state::gfx_getdropfile(void *opaque, int np, EEL_F **parms) +{ + const int idx = (int) parms[0][0]; + if (idx<0) m_ddrop_files.Empty(true,free); + if (idx < 0 || idx >= m_ddrop_files.GetSize()) return 0.0; + +#ifdef NOT_EEL_STRING_UPDATE_STRING + NOT_EEL_STRING_UPDATE_STRING(parms[1][0],m_ddrop_files.Get(idx)); +#else + if (np > 1) + { + EEL_STRING_MUTEXLOCK_SCOPE + WDL_FastString *fs=NULL; + EEL_STRING_GET_FOR_WRITE(parms[1][0], &fs); + if (fs) fs->Set(m_ddrop_files.Get(idx)); + } +#endif + return 1.0; +} + +bool eel_lice_state::do_load_image(int img, const char *str) +{ + s_img_cache_mutex.Enter(); + img_shared_state *s = s_img_cache.Get(str); + if (s) + { + wdl_atomic_incr(&s->refcnt); + } + else + { + s_img_cache_mutex.Leave(); + + LICE_IBitmap *bm = LICE_LoadImage(str,NULL,false); + if (!bm) return false; + + s = new img_shared_state(bm); + s_img_cache_mutex.Enter(); + s_img_cache.Insert(str,s); + } + s_img_cache_mutex.Leave(); + m_gfx_images.Get()[img].clear(NULL,s); + + return true; +} + +EEL_F eel_lice_state::gfx_loadimg(void *opaque, int img, EEL_F loadFrom) +{ +#ifdef DYNAMIC_LICE + if (!__LICE_LoadImage || !LICE__Destroy) return 0.0; +#endif + + if (img >= 0 && img < m_gfx_images.GetSize()) + { + WDL_FastString fs; + bool ok = EEL_LICE_GET_FILENAME_FOR_STRING(loadFrom,&fs,0); + + if (ok && fs.GetLength()) + { + if (do_load_image(img,fs.Get())) + return img; + } + } + return -1.0; + +} + +EEL_F eel_lice_state::gfx_setimgdim(int img, EEL_F *w, EEL_F *h) +{ + int rv=0; +#ifdef DYNAMIC_LICE + if (!LICE__resize ||!LICE__GetWidth || !LICE__GetHeight||!__LICE_CreateBitmap) return 0.0; +#endif + + int use_w = (int)*w; + int use_h = (int)*h; + if (use_w<1 || use_h < 1) use_w=use_h=0; + if (use_w > 8192) use_w=8192; + if (use_h > 8192) use_h=8192; + + LICE_IBitmap *bm=NULL; + if (img >= 0 && img < m_gfx_images.GetSize()) + { + m_gfx_images.Get()[img].on_write(); + bm=m_gfx_images.Get()[img].bm; + if (!bm) + { + m_gfx_images.Get()[img].bm = bm = __LICE_CreateBitmap(1,use_w,use_h); + rv=!!bm; + } + else + { + rv=LICE__resize(bm,use_w,use_h); + } + } + + return rv?1.0:0.0; +} + +void eel_lice_state::gfx_blurto(EEL_F x, EEL_F y) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_blurto"); + if (!dest +#ifdef DYNAMIC_LICE + ||!LICE_Blur +#endif + ) return; + + SetImageDirty(dest); + + int srcx = (int)x; + int srcy = (int)y; + int srcw=(int) (*m_gfx_x-x); + int srch=(int) (*m_gfx_y-y); + if (srch < 0) { srch=-srch; srcy = (int)*m_gfx_y; } + if (srcw < 0) { srcw=-srcw; srcx = (int)*m_gfx_x; } + LICE_Blur(dest,dest,srcx,srcy,srcx,srcy,srcw,srch); + *m_gfx_x = x; + *m_gfx_y = y; +} + +static bool CoordsSrcDestOverlap(EEL_F *coords) +{ + if (coords[0]+coords[2] < coords[4]) return false; + if (coords[0] > coords[4] + coords[6]) return false; + if (coords[1]+coords[3] < coords[5]) return false; + if (coords[1] > coords[5] + coords[7]) return false; + return true; +} + +void eel_lice_state::gfx_transformblit(EEL_F **parms, int div_w, int div_h, EEL_F *tab) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_transformblit"); + + if (!dest +#ifdef DYNAMIC_LICE + ||!LICE_ScaledBlit || !LICE_TransformBlit2 ||!LICE__GetWidth||!LICE__GetHeight +#endif + ) return; + + LICE_IBitmap *bm=GetImageForIndex(parms[0][0],"gfx_transformblit:src",false); + if (!bm) return; + + const int bmw=LICE__GetWidth(bm); + const int bmh=LICE__GetHeight(bm); + + const bool isFromFB = bm==m_framebuffer; + + SetImageDirty(dest); + + if (bm == dest) + { + if (!m_framebuffer_extra && LICE_FUNCTION_VALID(__LICE_CreateBitmap)) m_framebuffer_extra=__LICE_CreateBitmap(0,bmw,bmh); + if (m_framebuffer_extra) + { + + LICE__resize(bm=m_framebuffer_extra,bmw,bmh); + LICE_ScaledBlit(bm,dest, // copy the entire image + 0,0,bmw,bmh, + 0.0f,0.0f,(float)bmw,(float)bmh, + 1.0f,LICE_BLIT_MODE_COPY); + } + } + LICE_TransformBlit2(dest,bm,(int)floor(parms[1][0]),(int)floor(parms[2][0]),(int)floor(parms[3][0]),(int)floor(parms[4][0]),tab,div_w,div_h, (float)*m_gfx_a,getCurModeForBlit(isFromFB)); +} + +EEL_F eel_lice_state::gfx_setfont(void *opaque, int np, EEL_F **parms) +{ + int a = np>0 ? ((int)floor(parms[0][0]))-1 : -1; + + if (a>=0 && a < m_gfx_fonts.GetSize()) + { + gfxFontStruct *s = m_gfx_fonts.Get()+a; + if (np>1 && LICE_FUNCTION_VALID(LICE_CreateFont) && LICE_FUNCTION_VALID(LICE__SetFromHFont)) + { + const int sz=np>2 ? (int)parms[2][0] : 10; + + bool doCreate=false; + int fontflag=0; + if (!s->font) s->actual_fontname[0]=0; + + { + EEL_STRING_MUTEXLOCK_SCOPE + + const char *face=EEL_STRING_GET_FOR_INDEX(parms[1][0],NULL); + #ifdef EEL_STRING_DEBUGOUT + if (!face) EEL_STRING_DEBUGOUT("gfx_setfont: invalid string identifier %f",parms[1][0]); + #endif + if (!face || !*face) face="Arial"; + + { + unsigned int c = np > 3 ? (unsigned int) parms[3][0] : 0; + while (c) + { + switch (toupper(c&0xff)) + { + case 'B': fontflag|=EELFONT_FLAG_BOLD; break; + case 'I': fontflag|=EELFONT_FLAG_ITALIC; break; + case 'U': fontflag|=EELFONT_FLAG_UNDERLINE; break; + case 'R': fontflag|=16; break; //LICE_FONT_FLAG_FX_BLUR + case 'V': fontflag|=32; break; //LICE_FONT_FLAG_FX_INVERT + case 'M': fontflag|=64; break; //LICE_FONT_FLAG_FX_MONO + case 'S': fontflag|=128; break; //LICE_FONT_FLAG_FX_SHADOW + case 'O': fontflag|=256; break; //LICE_FONT_FLAG_FX_OUTLINE + case 'Z': fontflag|=1; break; //LICE_FONT_FLAG_VERTICAL + case 'Y': fontflag|=1|2; break; //LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP + } + c>>=8; + } + } + + + if (fontflag != s->last_fontflag || sz!=s->last_fontsize || strncmp(s->last_fontname,face,sizeof(s->last_fontname)-1)) + { + lstrcpyn_safe(s->last_fontname,face,sizeof(s->last_fontname)); + s->last_fontsize=sz; + s->last_fontflag=fontflag; + doCreate=1; + } + } + + if (doCreate) + { + s->actual_fontname[0]=0; + if (!s->font) s->font=LICE_CreateFont(); + if (s->font) + { + const int fw = (fontflag&EELFONT_FLAG_BOLD) ? FW_BOLD : FW_NORMAL; + const bool italic = !!(fontflag&EELFONT_FLAG_ITALIC); + const bool underline = !!(fontflag&EELFONT_FLAG_UNDERLINE); + HFONT hf=NULL; +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + WCHAR wf[256]; + if (WDL_DetectUTF8(s->last_fontname)>0 && + GetVersion()<0x80000000 && + MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,s->last_fontname,-1,wf,256)) + { + hf = CreateFontW(sz,0,0,0,fw,italic,underline,FALSE,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,wf); + } +#endif + if (!hf) hf = CreateFont(sz,0,0,0,fw,italic,underline,FALSE,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,s->last_fontname); + + if (!hf) + { + s->use_fonth=0; // disable this font + } + else + { + TEXTMETRIC tm; + tm.tmHeight = sz; + + if (!m_framebuffer && LICE_FUNCTION_VALID(__LICE_CreateBitmap)) m_framebuffer=__LICE_CreateBitmap(1,64,64); + + if (m_framebuffer && LICE_FUNCTION_VALID(LICE__GetDC)) + { + HGDIOBJ oldFont = 0; + HDC hdc=LICE__GetDC(m_framebuffer); + if (hdc) + { + oldFont = SelectObject(hdc,hf); + GetTextMetrics(hdc,&tm); + +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + if (GetVersion()<0x80000000 && + GetTextFaceW(hdc,sizeof(wf)/sizeof(wf[0]),wf) && + WideCharToMultiByte(CP_UTF8,0,wf,-1,s->actual_fontname,sizeof(s->actual_fontname),NULL,NULL)) + { + s->actual_fontname[sizeof(s->actual_fontname)-1]=0; + } + else +#endif + GetTextFace(hdc, sizeof(s->actual_fontname), s->actual_fontname); + SelectObject(hdc,oldFont); + } + } + + s->use_fonth=wdl_max(tm.tmHeight,1); + LICE__SetFromHFont(s->font,hf, (fontflag & ~EELFONT_FLAG_MASK) | 512 /*LICE_FONT_FLAG_OWNS_HFONT*/); + } + } + } + } + + + if (s->font && s->use_fonth) + { + m_gfx_font_active=a; + if (m_gfx_texth) *m_gfx_texth=s->use_fonth; + return 1.0; + } + // try to init this font + } + #ifdef EEL_STRING_DEBUGOUT + if (a >= m_gfx_fonts.GetSize()) EEL_STRING_DEBUGOUT("gfx_setfont: invalid font %d specified",a); + #endif + + if (a<0||a>=m_gfx_fonts.GetSize()||!m_gfx_fonts.Get()[a].font) + { + m_gfx_font_active=-1; + if (m_gfx_texth) *m_gfx_texth=8; + return 1.0; + } + return 0.0; +} + +void eel_lice_state::gfx_blitext2(int np, EEL_F **parms, int blitmode) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_blitext2"); + + if (!dest +#ifdef DYNAMIC_LICE + ||!LICE_ScaledBlit || !LICE_RotatedBlit||!LICE__GetWidth||!LICE__GetHeight +#endif + ) return; + + LICE_IBitmap *bm=GetImageForIndex(parms[0][0],"gfx_blitext2:src",false); + if (!bm) return; + + const int bmw=LICE__GetWidth(bm); + const int bmh=LICE__GetHeight(bm); + + // 0=img, 1=scale, 2=rotate + double coords[8]; + const double sc = blitmode==0 && np > 1 ? parms[1][0] : 1.0, + angle = blitmode==0 && np > 2 ? parms[2][0] : 0.0; + if (blitmode==0) + { + parms+=2; + np -= 2; + } + + coords[0]=np > 1 ? parms[1][0] : 0.0f; + coords[1]=np > 2 ? parms[2][0] : 0.0f; + coords[2]=np > 3 ? parms[3][0] : bmw; + coords[3]=np > 4 ? parms[4][0] : bmh; + coords[4]=np > 5 ? parms[5][0] : *m_gfx_x; + coords[5]=np > 6 ? parms[6][0] : *m_gfx_y; + coords[6]=np > 7 ? parms[7][0] : coords[2]*sc; + coords[7]=np > 8 ? parms[8][0] : coords[3]*sc; + + const bool isFromFB = bm == m_framebuffer; + SetImageDirty(dest); + + if (bm == dest && + (blitmode != 0 || np > 1) && // legacy behavior to matech previous gfx_blit(3parm), do not use temp buffer + CoordsSrcDestOverlap(coords)) + { + if (!m_framebuffer_extra && LICE_FUNCTION_VALID(__LICE_CreateBitmap)) m_framebuffer_extra=__LICE_CreateBitmap(0,bmw,bmh); + if (m_framebuffer_extra) + { + + LICE__resize(bm=m_framebuffer_extra,bmw,bmh); + LICE_ScaledBlit(bm,dest, // copy the source portion + (int)coords[0],(int)coords[1],(int)coords[2],(int)coords[3], + (float)coords[0],(float)coords[1],(float)coords[2],(float)coords[3], + 1.0f,LICE_BLIT_MODE_COPY); + } + } + + if (blitmode==1) + { + if (LICE_FUNCTION_VALID(LICE_DeltaBlit)) + LICE_DeltaBlit(dest,bm,(int)coords[4],(int)coords[5],(int)coords[6],(int)coords[7], + (float)coords[0],(float)coords[1],(float)coords[2],(float)coords[3], + np > 9 ? (float)parms[9][0]:1.0f, // dsdx + np > 10 ? (float)parms[10][0]:0.0f, // dtdx + np > 11 ? (float)parms[11][0]:0.0f, // dsdy + np > 12 ? (float)parms[12][0]:1.0f, // dtdy + np > 13 ? (float)parms[13][0]:0.0f, // dsdxdy + np > 14 ? (float)parms[14][0]:0.0f, // dtdxdy + np <= 15 || parms[15][0] > 0.5, (float)*m_gfx_a,getCurModeForBlit(isFromFB)); + } + else if (fabs(angle)>0.000000001) + { + LICE_RotatedBlit(dest,bm,(int)coords[4],(int)coords[5],(int)coords[6],(int)coords[7], + (float)coords[0],(float)coords[1],(float)coords[2],(float)coords[3], + (float)angle,true, (float)*m_gfx_a,getCurModeForBlit(isFromFB), + np > 9 ? (float)parms[9][0] : 0.0f, + np > 10 ? (float)parms[10][0] : 0.0f); + } + else + { + LICE_ScaledBlit(dest,bm,(int)coords[4],(int)coords[5],(int)coords[6],(int)coords[7], + (float)coords[0],(float)coords[1],(float)coords[2],(float)coords[3], (float)*m_gfx_a,getCurModeForBlit(isFromFB)); + } +} + +void eel_lice_state::gfx_blitext(EEL_F img, EEL_F *coords, EEL_F angle) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_blitext"); + + if (!dest +#ifdef DYNAMIC_LICE + ||!LICE_ScaledBlit || !LICE_RotatedBlit||!LICE__GetWidth||!LICE__GetHeight +#endif + ) return; + + LICE_IBitmap *bm=GetImageForIndex(img,"gfx_blitext:src",false); + if (!bm) return; + + SetImageDirty(dest); + const bool isFromFB = bm == m_framebuffer; + + int bmw=LICE__GetWidth(bm); + int bmh=LICE__GetHeight(bm); + + if (bm == dest && CoordsSrcDestOverlap(coords)) + { + if (!m_framebuffer_extra && LICE_FUNCTION_VALID(__LICE_CreateBitmap)) m_framebuffer_extra=__LICE_CreateBitmap(0,bmw,bmh); + if ( m_framebuffer_extra) + { + + LICE__resize(bm=m_framebuffer_extra,bmw,bmh); + LICE_ScaledBlit(bm,dest, // copy the source portion + (int)coords[0],(int)coords[1],(int)coords[2],(int)coords[3], + (float)coords[0],(float)coords[1],(float)coords[2],(float)coords[3], + 1.0f,LICE_BLIT_MODE_COPY); + } + } + + if (fabs(angle)>0.000000001) + { + LICE_RotatedBlit(dest,bm,(int)coords[4],(int)coords[5],(int)coords[6],(int)coords[7], + (float)coords[0],(float)coords[1],(float)coords[2],(float)coords[3],(float)angle, + true, (float)*m_gfx_a,getCurModeForBlit(isFromFB), + (float)coords[8],(float)coords[9]); + } + else + { + LICE_ScaledBlit(dest,bm,(int)coords[4],(int)coords[5],(int)coords[6],(int)coords[7], + (float)coords[0],(float)coords[1],(float)coords[2],(float)coords[3], (float)*m_gfx_a,getCurModeForBlit(isFromFB)); + } +} + +void eel_lice_state::gfx_set(int np, EEL_F **parms) +{ + if (np < 1) return; + if (m_gfx_r) *m_gfx_r = parms[0][0]; + if (m_gfx_g) *m_gfx_g = np > 1 ? parms[1][0] : parms[0][0]; + if (m_gfx_b) *m_gfx_b = np > 2 ? parms[2][0] : parms[0][0]; + if (m_gfx_a) *m_gfx_a = np > 3 ? parms[3][0] : 1.0; + if (m_gfx_mode) *m_gfx_mode = np > 4 ? parms[4][0] : 0; + if (np > 5 && m_gfx_dest) *m_gfx_dest = parms[5][0]; + if (m_gfx_a2) *m_gfx_a2 = np > 6 ? parms[6][0] : 1.0; +} + +void eel_lice_state::gfx_getpixel(EEL_F *r, EEL_F *g, EEL_F *b) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_getpixel",false); + if (!dest) return; + + int ret=LICE_FUNCTION_VALID(LICE_GetPixel)?LICE_GetPixel(dest,(int)*m_gfx_x, (int)*m_gfx_y):0; + + *r=LICE_GETR(ret)/255.0; + *g=LICE_GETG(ret)/255.0; + *b=LICE_GETB(ret)/255.0; + +} + + +static int __drawTextWithFont(LICE_IBitmap *dest, const RECT *rect, LICE_IFont *font, const char *buf, int buflen, + int fg, int mode, float alpha, int flags, EEL_F *wantYoutput, EEL_F **measureOnly) +{ + if (font && LICE_FUNCTION_VALID(LICE__DrawText)) + { + RECT tr=*rect; + LICE__SetTextColor(font,fg); + LICE__SetTextCombineMode(font,mode,alpha); + + int maxx=0; + RECT r={0,0,tr.left,0}; + while (buflen>0) + { + int thislen = 0; + while (thislen < buflen && buf[thislen] != '\n') thislen++; + memset(&r,0,sizeof(r)); + int lineh = LICE__DrawText(font,dest,buf,thislen?thislen:1,&r,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT); + if (!measureOnly) + { + r.right += tr.left; + lineh = LICE__DrawText(font,dest,buf,thislen?thislen:1,&tr,DT_SINGLELINE|DT_NOPREFIX|flags); + if (wantYoutput) *wantYoutput = tr.top; + } + else + { + if (r.right > maxx) maxx=r.right; + } + tr.top += lineh; + + buflen -= thislen+1; + buf += thislen+1; + } + if (measureOnly) + { + measureOnly[0][0] = maxx; + measureOnly[1][0] = tr.top; + } + return r.right; + } + else + { + int xpos=rect->left, ypos=rect->top; + int x; + int maxx=0,maxy=0; + + LICE_SubBitmap sbm( +#ifdef DYNAMIC_LICE + (LICE_IBitmap_disabledAPI*) +#endif + dest,rect->left,rect->top,rect->right-rect->left,rect->bottom-rect->top); + + if (!measureOnly) + { + if (!(flags & DT_NOCLIP)) + { + if (rect->right <= rect->left || rect->bottom <= rect->top) return 0; // invalid clip rect hm + + xpos = ypos = 0; + dest = &sbm; + } + if (flags & (DT_RIGHT|DT_BOTTOM|DT_CENTER|DT_VCENTER)) + { + EEL_F w=0.0,h=0.0; + EEL_F *mo[2] = { &w,&h}; + RECT tr={0,}; + __drawTextWithFont(dest,&tr,NULL,buf,buflen,0,0,0.0f,0,NULL,mo); + + if (flags & DT_RIGHT) xpos += (rect->right-rect->left) - (int)floor(w); + else if (flags & DT_CENTER) xpos += (rect->right-rect->left)/2 - (int)floor(w*.5); + + if (flags & DT_BOTTOM) ypos += (rect->bottom-rect->top) - (int)floor(h); + else if (flags & DT_VCENTER) ypos += (rect->bottom-rect->top)/2 - (int)floor(h*.5); + } + } + const int sxpos = xpos; + + if (LICE_FUNCTION_VALID(LICE_DrawChar)) for(x=0;x maxx) maxx=xpos; + maxy = ypos + 8; + break; + } + } + if (measureOnly) + { + measureOnly[0][0]=maxx; + measureOnly[1][0]=maxy; + } + else + { + if (wantYoutput) *wantYoutput=ypos; + } + return xpos; + } +} + +static HMENU PopulateMenuFromStr(const char** str, int* startid) +{ + HMENU hm=CreatePopupMenu(); + int pos=0; + int id=*startid; + + char buf[1024]; + const char* p=*str; + const char* sep=strchr(p, '|'); + while (sep || *p) + { + int len = (int)(sep ? sep-p : strlen(p)); + int destlen=wdl_min(len, (int)sizeof(buf)-1); + lstrcpyn(buf, p, destlen+1); + p += len; + if (sep) sep=strchr(++p, '|'); + + const char* q=buf; + HMENU subm=NULL; + bool done=false; + int flags=MF_BYPOSITION|MF_STRING; + while (strspn(q, ">#!<")) + { + if (*q == '>' && !subm) + { + subm=PopulateMenuFromStr(&p, &id); + sep=strchr(p, '|'); + } + if (*q == '#') flags |= MF_GRAYED; + if (*q == '!') flags |= MF_CHECKED; + if (*q == '<') done=true; + ++q; + } + if (subm) flags |= MF_POPUP; + if (*q) InsertMenu(hm, pos++, flags, (subm ? (INT_PTR)subm : (INT_PTR)id++), q); + else if (!done) InsertMenu(hm, pos++, MF_BYPOSITION|MF_SEPARATOR, 0, NULL); + if (done) break; + } + + *str=p; + *startid=id; + + if (!pos) + { + DestroyMenu(hm); + return NULL; + } + return hm; +} + +EEL_F eel_lice_state::gfx_showmenu(void* opaque, EEL_F** parms, int nparms) +{ + const char* p=EEL_STRING_GET_FOR_INDEX(parms[0][0], NULL); + if (!p || !p[0]) return 0.0; + + if ((GetTickCount()-m_last_menu_time) < (m_last_menu_cnt>=5 ? 3000 : 500)) + { + if (m_last_menu_cnt >= 5) return 0; + m_last_menu_cnt++; + } + else + { + m_last_menu_cnt=0; + } + + int id=1; + HMENU hm=PopulateMenuFromStr(&p, &id); + + int ret=0; + if (hm) + { + POINT pt; + HWND par = hwnd_standalone; + if (par) + { +#ifdef __APPLE__ + if (*m_gfx_ext_retina > 1.0) + { + pt.x = (short)(*m_gfx_x * .5); + pt.y = (short)(*m_gfx_y * .5); + } + else +#endif + { + pt.x = (short)*m_gfx_x; + pt.y = (short)*m_gfx_y; + } + ClientToScreen(par, &pt); + } + else + { +#ifdef EEL_LICE_STANDALONE_PARENT + par = EEL_LICE_STANDALONE_PARENT(opaque); +#endif + GetCursorPos(&pt); + } + + ret=TrackPopupMenu(hm, TPM_NONOTIFY|TPM_RETURNCMD, pt.x, pt.y, 0, par, NULL); + m_last_menu_time = GetTickCount(); + if (ret) m_last_menu_cnt = 0; + DestroyMenu(hm); + } + return (EEL_F)ret; +} + +EEL_F eel_lice_state::gfx_setcursor(void* opaque, EEL_F** parms, int nparms) +{ + if (!hwnd_standalone) return 0.0; + + bool chg = false; + const int nc = (int)parms[0][0]; + if (m_cursor_resid != nc) + { + m_cursor_resid = nc; + chg = true; + } + + const char *p = NULL; +#ifdef EEL_LICE_LOADTHEMECURSOR + if (nparms > 1) p=EEL_STRING_GET_FOR_INDEX(parms[1][0], NULL); + + if (strncmp(p?p:"",m_cursor_name,sizeof(m_cursor_name)-1)) + { + lstrcpyn(m_cursor_name, p?p:"", sizeof(m_cursor_name)); + chg = true; + } +#endif + + if (chg) + { + m_cursor = NULL; + if (!p || !*p) m_cursor = m_cursor_resid > 0 ? LoadCursor(NULL, MAKEINTRESOURCE(m_cursor_resid)) : NULL; +#ifdef EEL_LICE_LOADTHEMECURSOR + else m_cursor = EEL_LICE_LOADTHEMECURSOR(m_cursor_resid, p); +#endif + + bool do_set = GetCapture() == hwnd_standalone; + if (!do_set && GetFocus() == hwnd_standalone) + { + POINT pt; + RECT r; + GetCursorPos(&pt); + ScreenToClient(hwnd_standalone,&pt); + GetClientRect(hwnd_standalone,&r); + do_set = PtInRect(&r,pt)!=0; + } + + if (do_set) + { + SetCursor(m_cursor ? m_cursor : LoadCursor(NULL,IDC_ARROW)); + } + } + + return 1.0; +} + + +void eel_lice_state::gfx_drawstr(void *opaque, EEL_F **parms, int nparms, int formatmode)// formatmode=1 for format, 2 for purely measure no format +{ + int nfmtparms = nparms-1; + EEL_F **fmtparms = parms+1; + const char *funcname = formatmode==1?"gfx_printf": + formatmode==2?"gfx_measurestr": + formatmode==3?"gfx_measurechar" : "gfx_drawstr"; + + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,funcname); + if (!dest) return; + +#ifdef DYNAMIC_LICE + if (!LICE__GetWidth || !LICE__GetHeight) return; +#endif + + EEL_STRING_MUTEXLOCK_SCOPE + + WDL_FastString *fs=NULL; + char buf[4096]; + int s_len=0; + + const char *s; + if (formatmode==3) + { + s_len = WDL_MakeUTFChar(buf, (int)parms[0][0], sizeof(buf)); + s=buf; + } + else + { + s=EEL_STRING_GET_FOR_INDEX(parms[0][0],&fs); + #ifdef EEL_STRING_DEBUGOUT + if (!s) EEL_STRING_DEBUGOUT("gfx_%s: invalid string identifier %f",funcname,parms[0][0]); + #endif + if (!s) + { + s=""; + s_len = 12; + } + else if (formatmode==1) + { + extern int eel_format_strings(void *, const char *s, const char *ep, char *, int, int, EEL_F **); + s_len = eel_format_strings(opaque,s,fs?(s+fs->GetLength()):NULL,buf,sizeof(buf),nfmtparms,fmtparms); + if (s_len<1) return; + s=buf; + } + else + { + s_len = fs?fs->GetLength():(int)strlen(s); + } + } + + if (s_len) + { + if (formatmode>=2) + { + if (nfmtparms==2) + { + RECT r={0,0,0,0}; + __drawTextWithFont(dest,&r,GetActiveFont(),s,s_len, + getCurColor(),getCurMode(),(float)*m_gfx_a,0,NULL,fmtparms); + } + } + else + { + RECT r={(int)floor(*m_gfx_x),(int)floor(*m_gfx_y),0,0}; + int flags=DT_NOCLIP; + if (formatmode == 0 && nparms >= 4) + { + flags=(int)*parms[1]; + flags &= (DT_CENTER|DT_RIGHT|DT_VCENTER|DT_BOTTOM|DT_NOCLIP); + r.right=(int)*parms[2]; + r.bottom=(int)*parms[3]; + } + SetImageDirty(dest); + *m_gfx_x=__drawTextWithFont(dest,&r,GetActiveFont(),s,s_len, + getCurColor(),getCurMode(),(float)*m_gfx_a,flags,m_gfx_y,NULL); + } + } +} + +void eel_lice_state::gfx_drawchar(EEL_F ch) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_drawchar"); + if (!dest) return; + + SetImageDirty(dest); + + int a=(int)(ch+0.5); + if (a == '\r' || a=='\n') a=' '; + + char buf[32]; + const int buflen = WDL_MakeUTFChar(buf, a, sizeof(buf)); + + RECT r={(int)floor(*m_gfx_x),(int)floor(*m_gfx_y),0,0}; + *m_gfx_x = __drawTextWithFont(dest,&r, + GetActiveFont(),buf,buflen, + getCurColor(),getCurMode(),(float)*m_gfx_a,DT_NOCLIP,NULL,NULL); + +} + + +void eel_lice_state::gfx_drawnumber(EEL_F n, EEL_F ndigits) +{ + LICE_IBitmap *dest = GetImageForIndex(*m_gfx_dest,"gfx_drawnumber"); + if (!dest) return; + + SetImageDirty(dest); + + char buf[512]; + int a=(int)(ndigits+0.5); + if (a <0)a=0; + else if (a > 16) a=16; + snprintf(buf,sizeof(buf),"%.*f",a,n); + + RECT r={(int)floor(*m_gfx_x),(int)floor(*m_gfx_y),0,0}; + *m_gfx_x = __drawTextWithFont(dest,&r, + GetActiveFont(),buf,(int)strlen(buf), + getCurColor(),getCurMode(),(float)*m_gfx_a,DT_NOCLIP,NULL,NULL); +} + +int eel_lice_state::setup_frame(HWND hwnd, RECT r, int _mouse_x, int _mouse_y, int has_dpi) +{ + int use_w = r.right - r.left; + int use_h = r.bottom - r.top; + + POINT pt = { _mouse_x, _mouse_y }; + if (hwnd) + { + GetCursorPos(&pt); + ScreenToClient(hwnd,&pt); + } + + *m_mouse_x=pt.x-r.left; + *m_mouse_y=pt.y-r.top; + + if (has_dpi>0 && *m_gfx_ext_retina > 0.0) + { + *m_gfx_ext_retina = has_dpi/256.0; + } + else if (*m_gfx_ext_retina > 0.0) + { +#ifdef __APPLE__ + *m_gfx_ext_retina = (hwnd && SWELL_IsRetinaHWND(hwnd)) ? 2.0 : 1.0; + if (*m_gfx_ext_retina > 1.0) + { + *m_mouse_x *= 2.0; + *m_mouse_y *= 2.0; + use_w*=2; + use_h*=2; + } +#else + *m_gfx_ext_retina = 1.0; + #ifdef _WIN32 + static UINT (WINAPI *__GetDpiForWindow)(HWND); + if (!__GetDpiForWindow) + { + HINSTANCE h = LoadLibrary("user32.dll"); + if (h) *(void **)&__GetDpiForWindow = GetProcAddress(h,"GetDpiForWindow"); + if (!__GetDpiForWindow) + *(void **)&__GetDpiForWindow = (void*)(INT_PTR)1; + } + if (hwnd && (UINT_PTR)__GetDpiForWindow > (UINT_PTR)1) + { + int dpi = __GetDpiForWindow(hwnd); + if (dpi != 96) + *m_gfx_ext_retina = dpi / 96.0; + } + #else + const int rsc = SWELL_GetScaling256(); + if (rsc > 256) *m_gfx_ext_retina = rsc/256.0; + #endif +#endif + } + int dr=0; + if (!m_framebuffer && LICE_FUNCTION_VALID(__LICE_CreateBitmap)) + { + m_framebuffer=__LICE_CreateBitmap(1,use_w,use_h); + dr=1; + } + + if (!m_framebuffer || !LICE_FUNCTION_VALID(LICE__GetHeight) || !LICE_FUNCTION_VALID(LICE__GetWidth)) return -1; + + if (use_w != LICE__GetWidth(m_framebuffer) || use_h != LICE__GetHeight(m_framebuffer)) + { + LICE__resize(m_framebuffer,use_w,use_h); + dr=1; + } + *m_gfx_w = use_w; + *m_gfx_h = use_h; + + if (*m_gfx_clear > -1.0 && dr) + { + const int a=(int)*m_gfx_clear; + if (LICE_FUNCTION_VALID(LICE_Clear)) LICE_Clear(m_framebuffer,LICE_RGBA((a&0xff),((a>>8)&0xff),((a>>16)&0xff),0)); + } + m_framebuffer_dirty = dr; + + int vflags=0; + + if (m_has_cap) + { + bool swap = false; +#ifdef _WIN32 + swap = !!GetSystemMetrics(SM_SWAPBUTTON); +#endif + vflags|=m_has_cap&0xffff; + if (GetAsyncKeyState(VK_LBUTTON)&0x8000) vflags|=swap?2:1; + if (GetAsyncKeyState(VK_RBUTTON)&0x8000) vflags|=swap?1:2; + if (GetAsyncKeyState(VK_MBUTTON)&0x8000) vflags|=64; + } + if (m_has_cap || (m_has_had_getch && hwnd && GetFocus()==hwnd)) + { + if (GetAsyncKeyState(VK_CONTROL)&0x8000) vflags|=4; + if (GetAsyncKeyState(VK_SHIFT)&0x8000) vflags|=8; + if (GetAsyncKeyState(VK_MENU)&0x8000) vflags|=16; + if (GetAsyncKeyState(VK_LWIN)&0x8000) vflags|=32; + } + m_has_cap &= 0xf0000; + + *m_mouse_cap=(EEL_F)vflags; + + *m_gfx_dest = -1.0; // m_framebuffer + *m_gfx_a2 = *m_gfx_a = 1.0; // default to full alpha every call + int fh; + if (m_gfx_font_active>=0&&m_gfx_font_active0) + *m_gfx_texth=fh; + else + *m_gfx_texth = 8; + + return dr; +} + +void eel_lice_state::finish_draw() +{ + if (hwnd_standalone && m_framebuffer_dirty) + { +#ifdef __APPLE__ + void *p = SWELL_InitAutoRelease(); +#endif + + InvalidateRect(hwnd_standalone,NULL,FALSE); + UpdateWindow(hwnd_standalone); + +#ifdef __APPLE__ + SWELL_QuitAutoRelease(p); +#endif + m_framebuffer_dirty = 0; + } +} + +#ifndef EEL_LICE_NO_REGISTER +void eel_lice_register() +{ + NSEEL_addfunc_retptr("gfx_lineto",3,NSEEL_PProc_THIS,&_gfx_lineto); + NSEEL_addfunc_retptr("gfx_lineto",2,NSEEL_PProc_THIS,&_gfx_lineto2); + NSEEL_addfunc_retptr("gfx_rectto",2,NSEEL_PProc_THIS,&_gfx_rectto); + NSEEL_addfunc_varparm("gfx_rect",4,NSEEL_PProc_THIS,&_gfx_rect); + NSEEL_addfunc_varparm("gfx_line",4,NSEEL_PProc_THIS,&_gfx_line); // 5th param is optionally AA + NSEEL_addfunc_varparm("gfx_gradrect",8,NSEEL_PProc_THIS,&_gfx_gradrect); + NSEEL_addfunc_varparm("gfx_muladdrect",7,NSEEL_PProc_THIS,&_gfx_muladdrect); + NSEEL_addfunc_varparm("gfx_deltablit",9,NSEEL_PProc_THIS,&_gfx_deltablit); + NSEEL_addfunc_exparms("gfx_transformblit",8,NSEEL_PProc_THIS,&_gfx_transformblit); + NSEEL_addfunc_varparm("gfx_circle",3,NSEEL_PProc_THIS,&_gfx_circle); + NSEEL_addfunc_varparm("gfx_triangle", 6, NSEEL_PProc_THIS, &_gfx_triangle); + NSEEL_addfunc_varparm("gfx_roundrect",5,NSEEL_PProc_THIS,&_gfx_roundrect); + NSEEL_addfunc_varparm("gfx_arc",5,NSEEL_PProc_THIS,&_gfx_arc); + NSEEL_addfunc_retptr("gfx_blurto",2,NSEEL_PProc_THIS,&_gfx_blurto); + NSEEL_addfunc_exparms("gfx_showmenu",1,NSEEL_PProc_THIS,&_gfx_showmenu); + NSEEL_addfunc_varparm("gfx_setcursor",1, NSEEL_PProc_THIS, &_gfx_setcursor); + NSEEL_addfunc_retptr("gfx_drawnumber",2,NSEEL_PProc_THIS,&_gfx_drawnumber); + NSEEL_addfunc_retptr("gfx_drawchar",1,NSEEL_PProc_THIS,&_gfx_drawchar); + NSEEL_addfunc_varparm("gfx_drawstr",1,NSEEL_PProc_THIS,&_gfx_drawstr); + NSEEL_addfunc_retptr("gfx_measurestr",3,NSEEL_PProc_THIS,&_gfx_measurestr); + NSEEL_addfunc_retptr("gfx_measurechar",3,NSEEL_PProc_THIS,&_gfx_measurechar); + NSEEL_addfunc_varparm("gfx_printf",1,NSEEL_PProc_THIS,&_gfx_printf); + NSEEL_addfunc_retptr("gfx_setpixel",3,NSEEL_PProc_THIS,&_gfx_setpixel); + NSEEL_addfunc_retptr("gfx_getpixel",3,NSEEL_PProc_THIS,&_gfx_getpixel); + NSEEL_addfunc_retptr("gfx_getimgdim",3,NSEEL_PProc_THIS,&_gfx_getimgdim); + NSEEL_addfunc_retval("gfx_setimgdim",3,NSEEL_PProc_THIS,&_gfx_setimgdim); + NSEEL_addfunc_retval("gfx_loadimg",2,NSEEL_PProc_THIS,&_gfx_loadimg); + NSEEL_addfunc_retptr("gfx_blitext",3,NSEEL_PProc_THIS,&_gfx_blitext); + NSEEL_addfunc_varparm("gfx_blit",1,NSEEL_PProc_THIS,&_gfx_blit2); + NSEEL_addfunc_varparm("gfx_setfont",1,NSEEL_PProc_THIS,&_gfx_setfont); + NSEEL_addfunc_varparm("gfx_getfont",1,NSEEL_PProc_THIS,&_gfx_getfont); + NSEEL_addfunc_varparm("gfx_set",1,NSEEL_PProc_THIS,&_gfx_set); + NSEEL_addfunc_varparm("gfx_getdropfile",1,NSEEL_PProc_THIS,&_gfx_getdropfile); + NSEEL_addfunc_varparm("gfx_getsyscol",0,NSEEL_PProc_THIS,&_gfx_getsyscol); +} +#endif + +#ifdef EEL_LICE_WANT_STANDALONE + +#ifdef _WIN32 +static HINSTANCE eel_lice_hinstance; +#endif +static const char *eel_lice_standalone_classname; + +#ifdef EEL_LICE_WANT_STANDALONE_UPDATE +static EEL_F * NSEEL_CGEN_CALL _gfx_update(void *opaque, EEL_F *n) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { + ctx->m_ddrop_files.Empty(true,free); + if (ctx->hwnd_standalone) + { +#ifndef EEL_LICE_WANT_STANDALONE_UPDATE_NO_SETUPFRAME + ctx->finish_draw(); +#endif + + // run message pump +#ifndef EEL_LICE_WANT_STANDALONE_UPDATE_NO_MSGPUMP + +#ifdef _WIN32 + MSG msg; + while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#else + void SWELL_RunEvents(); + SWELL_RunEvents(); +#endif +#endif +#ifndef EEL_LICE_WANT_STANDALONE_UPDATE_NO_SETUPFRAME + RECT r; + GetClientRect(ctx->hwnd_standalone,&r); + ctx->setup_frame(ctx->hwnd_standalone,r); +#endif + } + } + return n; +} +#endif + + + +static EEL_F NSEEL_CGEN_CALL _gfx_getchar(void *opaque, INT_PTR np, EEL_F **plist) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (np > 1) plist[1][0] = 0.0; + if (ctx) + { + EEL_F *p = plist[0]; + ctx->m_has_had_getch=true; + if (*p >= 2.0) + { + if (*p == 65536.0 || *p == 65537.0) + { + int rv = 1; + if (ctx->hwnd_standalone) + { + if (ctx->hwnd_standalone==GetFocus()) rv|=2; + if (IsWindowVisible(ctx->hwnd_standalone)) + { + rv|=4; + if (*p != 65537.0) + { + POINT pt; + GetCursorPos(&pt); + RECT r; + GetWindowRect(ctx->hwnd_standalone,&r); + if (r.top > r.bottom) + { + const int a = r.top; + r.top = r.bottom; + r.bottom = a; + } + if (PtInRect(&r,pt) && WindowFromPoint(pt) == ctx->hwnd_standalone) rv|=8; + } + } + } + return rv; + } + int x; + const int n = sizeof(ctx->hwnd_standalone_kb_state) / sizeof(ctx->hwnd_standalone_kb_state[0]); + int *st = ctx->hwnd_standalone_kb_state; + int a = (int)*p; + for (x=0;xhwnd_standalone) return -1.0; + + if (ctx->m_kb_queue_valid) + { + const int qsize = sizeof(ctx->m_kb_queue)/sizeof(ctx->m_kb_queue[0]); + int a = ctx->m_kb_queue[ctx->m_kb_queue_pos & (qsize-1)]; + ctx->m_kb_queue_pos++; + ctx->m_kb_queue_valid--; + +#ifdef _WIN32 + if (np > 1 && (a>>24) != 'u' && ctx->m_kb_queue_valid > 0) + { + int a2 = ctx->m_kb_queue[ctx->m_kb_queue_pos & (qsize-1)]; + if ((a2>>24)=='u') + { + ctx->m_kb_queue_pos++; + ctx->m_kb_queue_valid--; + plist[1][0] = (a2&0xffffff); + } + return a; + } +#else + if (a > 32 && a < 256 && np > 1) + plist[1][0] = a; +#endif + if ((a>>24) == 'u') + { + if ((a&0xffffff) < 256) + a &= 0xff; + if (np > 1) plist[1][0] = a&0xffffff; + } + + return a; + } + } + return 0.0; +} + +static int eel_lice_key_xlate(int msg, int wParam, int lParam, bool *isAltOut) +{ +#define EEL_MB_C(a) (sizeof(a)<=2 ? a[0] : \ + sizeof(a)==3 ? (((a[0])<<8)+(a[1])) : \ + sizeof(a)==4 ? (((a[0])<<16)+((a[1])<<8)+(a[2])) : \ + (((a[0])<<24)+((a[1])<<16)+((a[2])<<8)+(a[3]))) + + if (msg != WM_CHAR) + { +#ifndef _WIN32 + if (lParam & FVIRTKEY) +#endif + switch (wParam) + { + case VK_HOME: return EEL_MB_C("home"); + case VK_UP: return EEL_MB_C("up"); + case VK_PRIOR: return EEL_MB_C("pgup"); + case VK_LEFT: return EEL_MB_C("left"); + case VK_RIGHT: return EEL_MB_C("rght"); + case VK_END: return EEL_MB_C("end"); + case VK_DOWN: return EEL_MB_C("down"); + case VK_NEXT: return EEL_MB_C("pgdn"); + case VK_INSERT: return EEL_MB_C("ins"); + case VK_DELETE: return EEL_MB_C("del"); + case VK_F1: return EEL_MB_C("f1"); + case VK_F2: return EEL_MB_C("f2"); + case VK_F3: return EEL_MB_C("f3"); + case VK_F4: return EEL_MB_C("f4"); + case VK_F5: return EEL_MB_C("f5"); + case VK_F6: return EEL_MB_C("f6"); + case VK_F7: return EEL_MB_C("f7"); + case VK_F8: return EEL_MB_C("f8"); + case VK_F9: return EEL_MB_C("f9"); + case VK_F10: return EEL_MB_C("f10"); + case VK_F11: return EEL_MB_C("f11"); + case VK_F12: return EEL_MB_C("f12"); +#ifndef _WIN32 + case VK_SUBTRACT: return '-'; // numpad - + case VK_ADD: return '+'; + case VK_MULTIPLY: return '*'; + case VK_DIVIDE: return '/'; + case VK_DECIMAL: return '.'; + case VK_NUMPAD0: return '0'; + case VK_NUMPAD1: return '1'; + case VK_NUMPAD2: return '2'; + case VK_NUMPAD3: return '3'; + case VK_NUMPAD4: return '4'; + case VK_NUMPAD5: return '5'; + case VK_NUMPAD6: return '6'; + case VK_NUMPAD7: return '7'; + case VK_NUMPAD8: return '8'; + case VK_NUMPAD9: return '9'; + case (32768|VK_RETURN): return VK_RETURN; +#endif + } + + switch (wParam) + { + case VK_RETURN: + case VK_BACK: + case VK_TAB: + case VK_ESCAPE: + return wParam; + + case VK_CONTROL: break; + + default: + { + const bool isctrl = !!(GetAsyncKeyState(VK_CONTROL)&0x8000); + const bool isalt = !!(GetAsyncKeyState(VK_MENU)&0x8000); + +#ifdef __APPLE__ + // SWELL_MacKeyToWindowsKeyEx maps control(FLWIN)+A-Z to 1-26 + if (wParam > 0 && wParam <= 26 && !(lParam&FVIRTKEY)) + { + *isAltOut=isalt; + return wParam; + } +#endif + + if(isctrl || isalt) + { + + if (wParam>='a' && wParam<='z') + { + if (isctrl) wParam += 1-'a'; + if (isalt) wParam += 256; + *isAltOut=isalt; + return wParam; + } + if (wParam>='A' && wParam<='Z') + { + if (isctrl) wParam += 1-'A'; + if (isalt) wParam += 256; + *isAltOut=isalt; + return wParam; + } + } + } + break; + } + } + + if(wParam>=32) + { + #ifdef _WIN32 + if (msg == WM_CHAR) + { + return (wParam&0xffffff) | ('u'<<24); // windows encodes all wm_chars as 'u'<<24 + } + #else + if (!(GetAsyncKeyState(VK_SHIFT)&0x8000)) + { + if (wParam>='A' && wParam<='Z') + { + if ((GetAsyncKeyState(VK_LWIN)&0x8000)) wParam -= 'A'-1; + else + wParam += 'a'-'A'; + } + } + if (wParam >= 0x100) + return (wParam&0xffffff) | ('u'<<24); + return wParam; + #endif + } + return 0; +} +#undef EEL_MB_C + +static LRESULT WINAPI eel_lice_wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +#ifdef __APPLE__ +extern "C" +{ + void *objc_getClass(const char *p); +#ifndef _OBJC_OBJC_H_ + void *sel_getUid(const char *p); +#endif + void objc_msgSend(void); +}; +#endif + + +HWND eel_lice_state::create_wnd(HWND par, int isChild) +{ + if (hwnd_standalone) return hwnd_standalone; +#ifdef _WIN32 + return CreateWindowEx(WS_EX_ACCEPTFILES,eel_lice_standalone_classname,"", + isChild ? (WS_CHILD|WS_TABSTOP) : (WS_POPUP|WS_CAPTION|WS_THICKFRAME|WS_SYSMENU),CW_USEDEFAULT,CW_USEDEFAULT,100,100,par,NULL,eel_lice_hinstance,this); +#else + HWND h = SWELL_CreateDialog(NULL,isChild ? NULL : ((const char *)(INT_PTR)0x400001),par,(DLGPROC)eel_lice_wndproc,(LPARAM)this); + if (h) + { + SWELL_SetClassName(h,eel_lice_standalone_classname); + SWELL_EnableMetal(h,1); + } + return h; +#endif +} + +#ifdef EEL_LICE_WANTDOCK +#ifndef ID_DOCKWINDOW +#define ID_DOCKWINDOW 40269 +#endif + +static EEL_F NSEEL_CGEN_CALL _gfx_dock(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { + if (np > 0 && parms[0][0] >= 0.0 && ctx->hwnd_standalone) EEL_LICE_WANTDOCK(ctx,(int)parms[0][0]); + + if (np > 1 && parms[1]) parms[1][0] = ctx->m_last_undocked_r.left; + if (np > 2 && parms[2]) parms[2][0] = ctx->m_last_undocked_r.top; + if (np > 3 && parms[3]) parms[3][0] = ctx->m_last_undocked_r.right; + if (np > 4 && parms[4]) parms[4][0] = ctx->m_last_undocked_r.bottom; + +#ifdef EEL_LICE_ISDOCKED + return EEL_LICE_ISDOCKED(ctx); +#endif + } + return 0.0; +} + +#endif //EEL_LICE_WANTDOCK + + +#ifndef EEL_LICE_STANDALONE_NOINITQUIT + +static EEL_F * NSEEL_CGEN_CALL _gfx_quit(void *opaque, EEL_F *n) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx) + { + if (ctx->hwnd_standalone) + { + DestroyWindow(ctx->hwnd_standalone); + } + ctx->hwnd_standalone=0; + } + return n; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_init(void *opaque, INT_PTR np, EEL_F **parms) +{ +#ifdef EEL_LICE_GET_CONTEXT_INIT + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT_INIT(opaque); +#else + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); +#endif + if (ctx) + { + bool wantShow=false, wantResize=true; + int sug_w = np > 1 ? (int)parms[1][0] : 640; + int sug_h = np > 2 ? (int)parms[2][0] : 480; + if (sug_w <1 && sug_h < 1 && ctx->hwnd_standalone) + { + RECT r; + GetClientRect(ctx->hwnd_standalone,&r); + sug_w = r.right; + sug_h = r.bottom; + } + #ifdef EEL_LICE_WANTDOCK + const int pos_offs = 4; + #else + const int pos_offs = 3; + #endif + + if (sug_w < 16) sug_w=16; + else if (sug_w > 8192) sug_w=8192; + if (sug_h < 16) sug_h=16; + else if (sug_h > 8192) sug_h=8192; + + if (!ctx->hwnd_standalone) + { + #ifdef __APPLE__ + void *(*send_msg)(void *, void *) = (void *(*)(void *, void *))objc_msgSend; + void (*send_msg_longparm)(void *, void *, long) = (void (*)(void *, void *, long))objc_msgSend; // long = NSInteger + + void *nsapp=send_msg( objc_getClass("NSApplication"), sel_getUid("sharedApplication")); + send_msg_longparm(nsapp,sel_getUid("setActivationPolicy:"), 0); + send_msg_longparm(nsapp,sel_getUid("activateIgnoringOtherApps:"), 1); + + #endif + + #ifdef EEL_LICE_STANDALONE_PARENT + HWND par = EEL_LICE_STANDALONE_PARENT(opaque); + #elif defined(_WIN32) + HWND par=GetDesktopWindow(); + #else + HWND par=NULL; + #endif + + ctx->create_wnd(par,0); + // resize client + + if (ctx->hwnd_standalone) + { + int px=0,py=0; + if (np >= pos_offs+2) + { + px = (int) floor(parms[pos_offs][0] + 0.5); + py = (int) floor(parms[pos_offs+1][0] + 0.5); +#ifdef EEL_LICE_VALIDATE_RECT_ON_SCREEN + RECT r = {px,py,px+sug_w,py+sug_h}; + EEL_LICE_VALIDATE_RECT_ON_SCREEN(r); + px=r.left; py=r.top; sug_w = r.right-r.left; sug_h = r.bottom-r.top; +#endif + ctx->m_last_undocked_r.left = px; + ctx->m_last_undocked_r.top = py; + ctx->m_last_undocked_r.right = sug_w; + ctx->m_last_undocked_r.bottom = sug_h; + } + + RECT r1,r2; + GetWindowRect(ctx->hwnd_standalone,&r1); + GetClientRect(ctx->hwnd_standalone,&r2); + sug_w += (r1.right-r1.left) - r2.right; + sug_h += abs(r1.bottom-r1.top) - r2.bottom; + + SetWindowPos(ctx->hwnd_standalone,NULL,px,py,sug_w,sug_h,(np >= pos_offs+2 ? 0:SWP_NOMOVE)|SWP_NOZORDER|SWP_NOACTIVATE); + + wantShow=true; + #ifdef EEL_LICE_WANTDOCK + if (np > 3) EEL_LICE_WANTDOCK(ctx,parms[3][0]); + #endif + #ifdef EEL_LICE_WANT_STANDALONE_UPDATE + { + RECT r; + GetClientRect(ctx->hwnd_standalone,&r); + ctx->setup_frame(ctx->hwnd_standalone,r); + } + #endif + } + wantResize=false; + } + if (!ctx->hwnd_standalone) return 0; + + if (np>0) + { + EEL_STRING_MUTEXLOCK_SCOPE + const char *title=EEL_STRING_GET_FOR_INDEX(parms[0][0],NULL); + #ifdef EEL_STRING_DEBUGOUT + if (!title) EEL_STRING_DEBUGOUT("gfx_init: invalid string identifier %f",parms[0][0]); + #endif + if (title&&*title) + { + SetWindowText(ctx->hwnd_standalone,title); + wantResize=false; // ignore resize if we're setting title + } + } + if (wantShow) + ShowWindow(ctx->hwnd_standalone,SW_SHOW); + if (wantResize && np>2 && !(GetWindowLong(ctx->hwnd_standalone,GWL_STYLE)&WS_CHILD)) + { + RECT r1,r2; + GetWindowRect(ctx->hwnd_standalone,&r1); + GetClientRect(ctx->hwnd_standalone,&r2); + const bool do_size = sug_w != r2.right || sug_h != r2.bottom; + + sug_w += (r1.right-r1.left) - r2.right; + sug_h += abs(r1.bottom-r1.top) - r2.bottom; + + int px=0,py=0; + const bool do_move=(np >= pos_offs+2); + if (do_move) + { + px = (int) floor(parms[pos_offs][0] + 0.5); + py = (int) floor(parms[pos_offs+1][0] + 0.5); +#ifdef EEL_LICE_VALIDATE_RECT_ON_SCREEN + RECT r = {px,py,px+sug_w,py+sug_h}; + EEL_LICE_VALIDATE_RECT_ON_SCREEN(r); + px=r.left; py=r.top; sug_w = r.right-r.left; sug_h = r.bottom-r.top; +#endif + } + if (do_size || do_move) + SetWindowPos(ctx->hwnd_standalone,NULL,px,py,sug_w,sug_h, + (do_size ? 0 : SWP_NOSIZE)|(do_move? 0:SWP_NOMOVE)|SWP_NOZORDER|SWP_NOACTIVATE); + } + return 1; + } + return 0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_screentoclient(void *opaque, EEL_F *x, EEL_F *y) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx && ctx->hwnd_standalone) + { + POINT pt={(int) *x, (int) *y}; + ScreenToClient(ctx->hwnd_standalone,&pt); + *x = pt.x; + *y = pt.y; + return 1.0; + } + return 0.0; +} + +static EEL_F NSEEL_CGEN_CALL _gfx_clienttoscreen(void *opaque, EEL_F *x, EEL_F *y) +{ + eel_lice_state *ctx=EEL_LICE_GET_CONTEXT(opaque); + if (ctx && ctx->hwnd_standalone) + { + POINT pt={(int) *x, (int) *y}; + ClientToScreen(ctx->hwnd_standalone,&pt); + *x = pt.x; + *y = pt.y; + return 1.0; + } + return 0.0; +} + +#endif // !EEL_LICE_STANDALONE_NOINITQUIT + + +#ifndef WM_MOUSEWHEEL +#define WM_MOUSEWHEEL 0x20A +#endif +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x20E +#endif + +LRESULT WINAPI eel_lice_wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ +#ifdef WIN32 + static UINT Scroll_Message; + static bool sm_init; + if (!sm_init) + { + sm_init=true; + Scroll_Message = RegisterWindowMessage("MSWHEEL_ROLLMSG"); + } + if (Scroll_Message && uMsg == Scroll_Message) + { + uMsg=WM_MOUSEWHEEL; + wParam<<=16; + } +#endif + + switch (uMsg) + { + case WM_CREATE: + { +#ifdef _WIN32 + LPCREATESTRUCT lpcs= (LPCREATESTRUCT )lParam; + eel_lice_state *ctx=(eel_lice_state*)lpcs->lpCreateParams; + SetWindowLongPtr(hwnd,GWLP_USERDATA,(LPARAM)lpcs->lpCreateParams); +#else + eel_lice_state *ctx=(eel_lice_state*)lParam; + SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); + SetWindowLong(hwnd,GWL_EXSTYLE, GetWindowLong(hwnd,GWL_EXSTYLE) | WS_EX_ACCEPTFILES); +#endif + ctx->m_kb_queue_valid=0; + ctx->hwnd_standalone=hwnd; + } + return 0; +#ifndef _WIN32 + case WM_CLOSE: + DestroyWindow(hwnd); + return 0; +#endif + case WM_DESTROY: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (ctx) + { +#ifdef EEL_LICE_WANTDOCK + EEL_LICE_WANTDOCK(ctx,0); +#endif + ctx->hwnd_standalone=NULL; + } + } + return 0; + case WM_ACTIVATE: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (ctx) memset(&ctx->hwnd_standalone_kb_state,0,sizeof(ctx->hwnd_standalone_kb_state)); + } + break; + case WM_SETCURSOR: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (ctx && ctx->m_cursor) + { + POINT p; + GetCursorPos(&p); + ScreenToClient(hwnd, &p); + RECT r; + GetClientRect(hwnd, &r); + if (PtInRect(&r,p)) + { + SetCursor(ctx->m_cursor); + return TRUE; + } + } + } + break; +#ifdef EEL_LICE_WANTDOCK + case WM_CONTEXTMENU: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (ctx) + { + char title[512], buf[1024]; + GetWindowText(hwnd, title, sizeof(title)-1); + if (!title[0]) strcpy(title, "ReaScript"); + + HMENU hm=CreatePopupMenu(); + int pos=0; + + int flag=((EEL_LICE_ISDOCKED(ctx)&1) ? MF_CHECKED : 0); + snprintf(buf, sizeof(buf), "Dock %s window in Docker", title); + InsertMenu(hm, pos++, MF_BYPOSITION|MF_STRING|flag, ID_DOCKWINDOW, buf); + snprintf(buf, sizeof(buf), "Close %s window", title); + InsertMenu(hm, pos++, MF_BYPOSITION|MF_STRING, IDCANCEL, buf); + + POINT pt; + GetCursorPos(&pt); + TrackPopupMenu(hm, 0, pt.x, pt.y, 0, hwnd, NULL); + DestroyMenu(hm); + } + } + return 0; +#endif + case WM_COMMAND: + switch (LOWORD(wParam)) + { +#ifdef EEL_LICE_WANTDOCK + case ID_DOCKWINDOW: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (ctx) EEL_LICE_WANTDOCK(ctx, EEL_LICE_ISDOCKED(ctx)^1); + } + return 0; +#endif + case IDCANCEL: + DestroyWindow(hwnd); + return 0; + } + break; + case WM_USER+1001: + if (wParam == 0xdddd && lParam) + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (ctx) + { + ctx->m_ddrop_files.Empty(true,free); + const char *r = (const char *)lParam; + while (*r) + { + ctx->m_ddrop_files.Add(strdup(r)); + r += strlen(r)+1; + } + } + } + return 0; + case WM_DROPFILES: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (ctx && wParam) + { + ctx->m_ddrop_files.Empty(true,free); + + HDROP hDrop = (HDROP) wParam; + const int n=DragQueryFile(hDrop,-1,NULL,0); + for (int x=0;xm_ddrop_files.Add(strdup(buf)); + } + DragFinish(hDrop); + } + } + return 0; + case WM_MOUSEHWHEEL: + case WM_MOUSEWHEEL: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (ctx) + { + EEL_F *p= uMsg==WM_MOUSEHWHEEL ? ctx->m_mouse_hwheel : ctx->m_mouse_wheel; + if (p) *p += (EEL_F) (short)HIWORD(wParam); + } + } + return -1; +#ifdef _WIN32 + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: +#endif + case WM_KEYDOWN: + case WM_KEYUP: + case WM_CHAR: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd,GWLP_USERDATA); +#ifdef __APPLE__ + { + int f=0; + wParam = SWELL_MacKeyToWindowsKeyEx(NULL,&f,1); + lParam=f; + } +#endif + + bool hadAltAdj=false; + int a=eel_lice_key_xlate(uMsg,(int)wParam,(int)lParam, &hadAltAdj); + if (a == -1) return 0; +#ifdef _WIN32 + if (!a && (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)) + { + // will not end up in queue, just for state tracking + if (wParam >= 'A' && wParam <= 'Z') a=(int)wParam + 'a' - 'A'; + else a = (int)MapVirtualKey((UINT)wParam,2/*MAPVK_VK_TO_CHAR*/); + } +#endif + const int mask = hadAltAdj ? ~256 : ~0; + + if (a & mask) + { + int a_no_alt = (a&mask); + const int lowera = a_no_alt >= 1 && a_no_alt < 27 ? (a_no_alt+'a'-1) : a_no_alt >= 'A' && a_no_alt <= 'Z' ? a_no_alt+'a'-'A' : a_no_alt; + + int *st = ctx->hwnd_standalone_kb_state; + + const int n = sizeof(ctx->hwnd_standalone_kb_state) / sizeof(ctx->hwnd_standalone_kb_state[0]); + int zp=n-1,x; + + for (x=0;xm_kb_queue)/sizeof(ctx->m_kb_queue[0]); + if (ctx->m_kb_queue_valid>=qsize) // queue full, dump an old event! + { + ctx->m_kb_queue_valid--; + ctx->m_kb_queue_pos++; + } + ctx->m_kb_queue[(ctx->m_kb_queue_pos + ctx->m_kb_queue_valid++) & (qsize-1)] = a; + if (ctx->m_last_menu_cnt && (GetTickCount()-ctx->m_last_menu_time)>250) ctx->m_last_menu_cnt = 0; + } + + } + return 0; + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_LBUTTONDOWN: + { + POINT p = { (short)LOWORD(lParam), (short)HIWORD(lParam) }; + RECT r; + GetClientRect(hwnd, &r); + if (p.x >= r.left && p.x < r.right && p.y >= r.top && p.y < r.bottom) + { + if (GetCapture()!=hwnd) SetFocus(hwnd); + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (ctx) + { + if (GetCapture()!=hwnd) SetCapture(hwnd); + int f = 0; + if (uMsg == WM_LBUTTONDBLCLK || uMsg == WM_LBUTTONDOWN) f=0x10001; + else if (uMsg == WM_RBUTTONDBLCLK || uMsg == WM_RBUTTONDOWN) f=0x20002; + else if (uMsg == WM_MBUTTONDBLCLK || uMsg == WM_MBUTTONDOWN) f=0x40040; + + if (GetAsyncKeyState(VK_CONTROL)&0x8000) f|=4; + if (GetAsyncKeyState(VK_SHIFT)&0x8000) f|=8; + if (GetAsyncKeyState(VK_MENU)&0x8000) f|=16; + if (GetAsyncKeyState(VK_LWIN)&0x8000) f|=32; + + ctx->m_has_cap|=f; + if (ctx->m_last_menu_cnt && (GetTickCount()-ctx->m_last_menu_time)>250) ctx->m_last_menu_cnt = 0; + } + } + } + return 1; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_CAPTURECHANGED: + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (ctx) + { + if (uMsg == WM_CAPTURECHANGED) + { + ctx->m_has_cap &= 0xffff; + } + else + { + if (ctx->m_last_menu_cnt && (GetTickCount()-ctx->m_last_menu_time)>250) ctx->m_last_menu_cnt = 0; + if (uMsg == WM_LBUTTONUP) ctx->m_has_cap &= ~0x10000; + else if (uMsg == WM_RBUTTONUP) ctx->m_has_cap &= ~0x20000; + else if (uMsg == WM_MBUTTONUP) ctx->m_has_cap &= ~0x40000; + + if (!(ctx->m_has_cap & 0xf0000)) + { + ReleaseCapture(); + } + } + } + } + return 1; +#ifdef _WIN32 + case WM_GETDLGCODE: + if (GetWindowLong(hwnd,GWL_STYLE)&WS_CHILD) return DLGC_WANTALLKEYS; + break; + case 0x02E0: //WM_DPICHANGED + if (!(GetWindowLong(hwnd,GWL_STYLE)&WS_CHILD)) + { + RECT *prcNewWindow = (RECT*)lParam; + SetWindowPos(hwnd, + NULL, + prcNewWindow ->left, + prcNewWindow ->top, + prcNewWindow->right - prcNewWindow->left, + prcNewWindow->bottom - prcNewWindow->top, + SWP_NOZORDER | SWP_NOACTIVATE); + } + break; +#endif + case WM_SIZE: + // fall through +#ifndef EEL_LICE_STANDALONE_NOINITQUIT + case WM_MOVE: + if (uMsg != WM_SIZE || wParam != SIZE_MINIMIZED) + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (ctx +#ifdef EEL_LICE_ISDOCKED + && !(GetWindowLong(hwnd,GWL_STYLE)&WS_CHILD) +#endif + ) + { + RECT r; + GetWindowRect(hwnd,&ctx->m_last_undocked_r); + GetClientRect(hwnd,&r); + if (ctx->m_last_undocked_r.bottom < ctx->m_last_undocked_r.top) ctx->m_last_undocked_r.top = ctx->m_last_undocked_r.bottom; + ctx->m_last_undocked_r.right = r.right; + ctx->m_last_undocked_r.bottom = r.bottom; + } + + } +#endif + + break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + eel_lice_state *ctx=(eel_lice_state*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (ctx && ctx->m_framebuffer && + LICE_FUNCTION_VALID(LICE__GetDC) && LICE_FUNCTION_VALID(LICE__GetWidth) && LICE_FUNCTION_VALID(LICE__GetHeight)) + { + int w = LICE__GetWidth(ctx->m_framebuffer); + int h = LICE__GetHeight(ctx->m_framebuffer); +#ifdef __APPLE__ + if (*ctx->m_gfx_ext_retina > 1.0) + { + StretchBlt(ps.hdc,0,0,w/2,h/2,LICE__GetDC(ctx->m_framebuffer),0,0,w,h,SRCCOPY); + } + else +#endif + BitBlt(ps.hdc,0,0,w,h,LICE__GetDC(ctx->m_framebuffer),0,0,SRCCOPY); + } + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_GETMINMAXINFO: + { + LPMINMAXINFO p=(LPMINMAXINFO)lParam; + if (p->ptMinTrackSize.x > 10) p->ptMinTrackSize.x = 10; + if (p->ptMinTrackSize.y > 10) p->ptMinTrackSize.y = 10; + } + return 0; + } + +#ifdef _WIN32 + return DefWindowProcW(hwnd,uMsg,wParam,lParam); +#else + return DefWindowProc(hwnd,uMsg,wParam,lParam); +#endif +} + + + +void eel_lice_register_standalone(HINSTANCE hInstance, const char *classname, HWND hwndPar, HICON icon) +{ + eel_lice_standalone_classname=classname && *classname ? classname : "EEL_LICE_gfx_standalone"; +#ifdef _WIN32 + static bool reg; + if (!reg) + { + eel_lice_hinstance=hInstance; + WCHAR tmp[256]; + int x = 0; + while (eel_lice_standalone_classname[x]) + { + if (WDL_NOT_NORMALLY(x == 255)) break; + tmp[x] = eel_lice_standalone_classname[x]; + x++; + } + tmp[x]=0; + + WNDCLASSW wc={CS_DBLCLKS,eel_lice_wndproc,0,0,hInstance,icon,LoadCursor(NULL,IDC_ARROW), NULL, NULL,tmp}; + RegisterClassW(&wc); + reg = true; + } +#endif + +#ifndef EEL_LICE_NO_REGISTER + // gfx_init(title[, w,h, flags]) +#ifndef EEL_LICE_STANDALONE_NOINITQUIT + NSEEL_addfunc_varparm("gfx_init",1,NSEEL_PProc_THIS,&_gfx_init); + NSEEL_addfunc_retptr("gfx_quit",1,NSEEL_PProc_THIS,&_gfx_quit); + + NSEEL_addfunc_retval("gfx_screentoclient",2,NSEEL_PProc_THIS,&_gfx_screentoclient); + NSEEL_addfunc_retval("gfx_clienttoscreen",2,NSEEL_PProc_THIS,&_gfx_clienttoscreen); + +#endif +#ifdef EEL_LICE_WANTDOCK + NSEEL_addfunc_varparm("gfx_dock",1,NSEEL_PProc_THIS,&_gfx_dock); +#endif + +#ifdef EEL_LICE_WANT_STANDALONE_UPDATE + NSEEL_addfunc_retptr("gfx_update",1,NSEEL_PProc_THIS,&_gfx_update); +#endif + + NSEEL_addfunc_varparm("gfx_getchar",1,NSEEL_PProc_THIS,&_gfx_getchar); +#endif +} + + +#endif + +#endif//!EEL_LICE_API_ONLY + + + + +#ifdef DYNAMIC_LICE +static WDL_STATICFUNC_UNUSED void eel_lice_initfuncs(void *(*getFunc)(const char *name)) +{ + if (!getFunc) return; + + *(void **)&__LICE_CreateBitmap = getFunc("LICE_CreateBitmap"); + *(void **)&LICE_Clear = getFunc("LICE_Clear"); + *(void **)&LICE_Line = getFunc("LICE_LineInt"); + *(void **)&LICE_ClipLine = getFunc("LICE_ClipLine"); + *(void **)&LICE_FillRect = getFunc("LICE_FillRect"); + *(void **)&LICE_DrawRect = getFunc("LICE_DrawRect"); + *(void **)&LICE_PutPixel = getFunc("LICE_PutPixel"); + *(void **)&LICE_GetPixel = getFunc("LICE_GetPixel"); + *(void **)&LICE_DrawText = getFunc("LICE_DrawText"); + *(void **)&LICE_DrawChar = getFunc("LICE_DrawChar"); + *(void **)&LICE_MeasureText = getFunc("LICE_MeasureText"); + *(void **)&LICE_LoadImage = getFunc("LICE_LoadImage"); + *(void **)&LICE__GetDC = getFunc("LICE__GetDC"); + *(void **)&LICE__Destroy = getFunc("LICE__Destroy"); + *(void **)&LICE__GetWidth = getFunc("LICE__GetWidth"); + *(void **)&LICE__GetHeight = getFunc("LICE__GetHeight"); + *(void **)&LICE__resize = getFunc("LICE__resize"); + *(void **)&LICE_Blur = getFunc("LICE_Blur"); + *(void **)&LICE_RotatedBlit = getFunc("LICE_RotatedBlit"); + *(void **)&LICE_ScaledBlit = getFunc("LICE_ScaledBlit"); + *(void **)&LICE_Circle = getFunc("LICE_Circle"); + *(void **)&LICE_FillCircle = getFunc("LICE_FillCircle"); + *(void **)&LICE_FillTriangle=getFunc("LICE_FillTriangle"); + *(void **)&LICE_FillConvexPolygon=getFunc("LICE_FillConvexPolygon"); + *(void **)&LICE_RoundRect = getFunc("LICE_RoundRect"); + *(void **)&LICE_Arc = getFunc("LICE_Arc"); + + *(void **)&LICE_MultiplyAddRect = getFunc("LICE_MultiplyAddRect"); + *(void **)&LICE_GradRect = getFunc("LICE_GradRect"); + *(void **)&LICE_TransformBlit2 = getFunc("LICE_TransformBlit2"); + *(void **)&LICE_DeltaBlit = getFunc("LICE_DeltaBlit"); + + *(void **)&LICE__DestroyFont = getFunc("LICE__DestroyFont"); + *(void **)&LICE_CreateFont = getFunc("LICE_CreateFont"); + *(void **)&LICE__SetFromHFont = getFunc("LICE__SetFromHFont2"); + + *(void **)&LICE__SetTextColor = getFunc("LICE__SetTextColor"); + *(void **)&LICE__SetTextCombineMode = getFunc("LICE__SetTextCombineMode"); + *(void **)&LICE__DrawText = getFunc("LICE__DrawText"); +} +#endif + +#ifdef EEL_WANT_DOCUMENTATION + +#ifdef EELSCRIPT_LICE_MAX_IMAGES +#define MKSTR2(x) #x +#define MKSTR(x) MKSTR2(x) +#define EEL_LICE_DOC_MAXHANDLE MKSTR(EELSCRIPT_LICE_MAX_IMAGES-1) +#else +#define EEL_LICE_DOC_MAXHANDLE "127" +#endif + + +static const char *eel_lice_function_reference = +#ifdef EEL_LICE_WANT_STANDALONE +#ifndef EEL_LICE_STANDALONE_NOINITQUIT +#ifdef EEL_LICE_WANTDOCK + "gfx_init\t\"name\"[,width,height,dockstate,xpos,ypos]\tInitializes the graphics window with title name. Suggested width and height can be specified. If window is already open, a non-empty name will re-title window, or an empty title will resize window. \n\n" +#else + "gfx_init\t\"name\"[,width,height,xpos,ypos]\tInitializes the graphics window with title name. Suggested width and height can be specified. If window is already open, a non-empty name will re-title window, or an empty title will resize window.\n\n" +#endif + "Once the graphics window is open, gfx_update() should be called periodically. \0" + "gfx_quit\t\tCloses the graphics window.\0" +#endif +#ifdef EEL_LICE_WANT_STANDALONE_UPDATE + "gfx_update\t\tUpdates the graphics display, if opened\0" +#endif +#endif +#ifdef EEL_LICE_WANTDOCK + "gfx_dock\tv[,wx,wy,ww,wh]\tCall with v=-1 to query docked state, otherwise v>=0 to set docked state. State is &1 if docked, second byte is docker index (or last docker index if undocked). If wx-wh are specified, they will be filled with the undocked window position/size\0" +#endif + "gfx_aaaaa\t\t" + "The following global variables are special and will be used by the graphics system:\n\n\3" + // we depend on the formatting here -- following gfx_aaaaa, search for \4[gfx_*|mouse_*]- for syntax highlight etc + "\4gfx_r - current red component (0..1) used by drawing operations.\n" + "\4gfx_g - current green component (0..1) used by drawing operations.\n" + "\4gfx_b - current blue component (0..1) used by drawing operations.\n" + "\4gfx_a2 - current alpha component (0..1) used by drawing operations when writing solid colors (normally ignored but useful when creating transparent images).\n" + "\4gfx_a - alpha for drawing (1=normal).\n" + "\4gfx_mode - blend mode for drawing. Set mode to 0 for default options. Add 1.0 for additive blend mode (if you wish to do subtractive, set gfx_a to negative and use gfx_mode as additive). Add 2.0 to disable source alpha for gfx_blit(). Add 4.0 to disable filtering for gfx_blit(). \n" + "\4gfx_w - width of the UI framebuffer. \n" + "\4gfx_h - height of the UI framebuffer. \n" + "\4gfx_x - current graphics position X. Some drawing functions use as start position and update. \n" + "\4gfx_y - current graphics position Y. Some drawing functions use as start position and update. \n" + "\4gfx_clear - if greater than -1.0, framebuffer will be cleared to that color. the color for this one is packed RGB (0..255), i.e. red+green*256+blue*65536. The default is 0 (black). \n" + "\4gfx_dest - destination for drawing operations, -1 is main framebuffer, set to 0.." EEL_LICE_DOC_MAXHANDLE " to have drawing operations go to an offscreen buffer (or loaded image).\n" + "\4gfx_texth - the (READ-ONLY) height of a line of text in the current font. Do not modify this variable.\n" + "\4gfx_ext_retina - to support hidpi/retina, callers should set to 1.0 on initialization, this value will be updated to value greater than 1.0 (such as 2.0) if retina/hidpi. On macOS gfx_w/gfx_h/etc will be doubled, but on other systems gfx_w/gfx_h will remain the same and gfx_ext_retina is a scaling hint for drawing.\n" + "\4mouse_x - current X coordinate of the mouse relative to the graphics window.\n" + "\4mouse_y - current Y coordinate of the mouse relative to the graphics window.\n" + "\4mouse_wheel - wheel position, will change typically by 120 or a multiple thereof, the caller should clear the state to 0 after reading it.\n" + "\4mouse_hwheel - horizontal wheel positions, will change typically by 120 or a multiple thereof, the caller should clear the state to 0 after reading it.\n" + "\4mouse_cap - a bitfield of mouse and keyboard modifier state. Note that a script must call gfx_getchar() at least once in order to get modifier state when the mouse is not captured by the window. Bitfield bits:\3" + "\4" "1: left mouse button\n" + "\4" "2: right mouse button\n" +#ifdef __APPLE__ + "\4" "4: Command key\n" + "\4" "8: Shift key\n" + "\4" "16: Option key\n" + "\4" "32: Control key\n" +#else + "\4" "4: Control key\n" + "\4" "8: Shift key\n" + "\4" "16: Alt key\n" + "\4" "32: Windows key\n" +#endif + "\4" "64: middle mouse button\n" + "\2" + "\2\0" + +"gfx_getchar\t[char, unichar]\tIf char is 0 or omitted, returns a character from the keyboard queue, or 0 if no character is available, or -1 if the graphics window is not open. " + "If char is specified and nonzero, that character's status will be checked, and the function will return greater than 0 if it is pressed. Note that calling gfx_getchar() at least once causes mouse_cap to reflect keyboard modifiers even when the mouse is not captured.\n\n" + "Common values are standard ASCII, such as 'a', 'A', '=' and '1', but for many keys multi-byte values are used, including 'home', 'up', 'down', 'left', 'rght', 'f1'.. 'f12', 'pgup', 'pgdn', 'ins', and 'del'. \n\n" + "Modified and special keys can also be returned, including:\3\n" + "\4Ctrl/Cmd+A..Ctrl+Z as 1..26\n" + "\4Ctrl/Cmd+Alt+A..Z as 257..282\n" + "\4Alt+A..Z as 'A'+256..'Z'+256\n" + "\4" "27 for ESC\n" + "\4" "13 for Enter\n" + "\4' ' for space\n" + "\4" "65536 for query of special flags, returns: &1 (supported), &2=window has focus, &4=window is visible, &8=mouse click would hit window. 65537 queries special flags but does not do the mouse click hit testing (faster).\n" + "\4If unichar is specified, it will be set to the unicode value of the key if available (and the return value may be the unicode value or a raw key value as described above, depending). If unichar is not specified, unicode codepoints greater than 255 will be returned as 'u'<<24 + value\n" + "\2\0" + + "gfx_showmenu\t\"str\"\tShows a popup menu at gfx_x,gfx_y. str is a list of fields separated by | characters. " + "Each field represents a menu item.\nFields can start with special characters:\n\n" + "# : grayed out\n" + "! : checked\n" + "> : this menu item shows a submenu\n" + "< : last item in the current submenu\n\n" + "An empty field will appear as a separator in the menu. " + "gfx_showmenu returns 0 if the user selected nothing from the menu, 1 if the first field is selected, etc.\nExample:\n\n" + "gfx_showmenu(\"first item, followed by separator||!second item, checked|>third item which spawns a submenu|#first item in submenu, grayed out| +#endif +#include +#include +#include + +#define EEL_DCT_MINBITLEN 5 +#define EEL_DCT_MAXBITLEN 12 + + +#define PI 3.1415926535897932384626433832795 + +typedef struct { + int n; + int log2n; + EEL_F *trig; + int *bitrev; + EEL_F scale; + EEL_F *window; +} mdct_lookup; + +static void mdct(EEL_F *in, EEL_F *out, int len) +{ + int k; + EEL_F pioverlen = PI * 0.5 / (EEL_F)len; + for (k = 0; k < len / 2; k ++) + { + int i; + EEL_F d = 0.0; + for (i = 0; i < len; i ++) + { + d += in[i] * cos(pioverlen * (2.0 * i + 1.0 + len * 0.5) * (2.0 * k + 1.0)); + } + out[k] = (EEL_F)d; + } +} + +static void imdct(EEL_F *in, EEL_F *out, int len) +{ + int k; + EEL_F fourovern = 4.0 / (EEL_F)len; + EEL_F pioverlen = PI * 0.5 / (EEL_F)len; + for (k = 0; k < len; k ++) + { + int i; + EEL_F d = 0.0; + for (i = 0; i < len / 2; i ++) + { + d += in[i] * cos(pioverlen * (2.0 * k + 1.0 + len * 0.5) * (2 * i + 1.0)); + } + out[k] = (EEL_F)(d * fourovern); + } +} + + +// MDCT/iMDCT borrowed from Vorbis, thanks xiph! + + +#define cPI3_8 .38268343236508977175 +#define cPI2_8 .70710678118654752441 +#define cPI1_8 .92387953251128675613 + +#define FLOAT_CONV(x) ((EEL_F) ( x )) +#define MULT_NORM(x) (x) +#define HALVE(x) ((x)*.5f) + + + +/* 8 point butterfly (in place, 4 register) */ +static void mdct_butterfly_8(EEL_F *x) { + EEL_F r0 = x[6] + x[2]; + EEL_F r1 = x[6] - x[2]; + EEL_F r2 = x[4] + x[0]; + EEL_F r3 = x[4] - x[0]; + + x[6] = r0 + r2; + x[4] = r0 - r2; + + r0 = x[5] - x[1]; + r2 = x[7] - x[3]; + x[0] = r1 + r0; + x[2] = r1 - r0; + + r0 = x[5] + x[1]; + r1 = x[7] + x[3]; + x[3] = r2 + r3; + x[1] = r2 - r3; + x[7] = r1 + r0; + x[5] = r1 - r0; + +} + +/* 16 point butterfly (in place, 4 register) */ +static void mdct_butterfly_16(EEL_F *x) { + EEL_F r0 = x[1] - x[9]; + EEL_F r1 = x[0] - x[8]; + + x[8] += x[0]; + x[9] += x[1]; + x[0] = MULT_NORM((r0 + r1) * cPI2_8); + x[1] = MULT_NORM((r0 - r1) * cPI2_8); + + r0 = x[3] - x[11]; + r1 = x[10] - x[2]; + x[10] += x[2]; + x[11] += x[3]; + x[2] = r0; + x[3] = r1; + + r0 = x[12] - x[4]; + r1 = x[13] - x[5]; + x[12] += x[4]; + x[13] += x[5]; + x[4] = MULT_NORM((r0 - r1) * cPI2_8); + x[5] = MULT_NORM((r0 + r1) * cPI2_8); + + r0 = x[14] - x[6]; + r1 = x[15] - x[7]; + x[14] += x[6]; + x[15] += x[7]; + x[6] = r0; + x[7] = r1; + + mdct_butterfly_8(x); + mdct_butterfly_8(x + 8); +} + +/* 32 point butterfly (in place, 4 register) */ +static void mdct_butterfly_32(EEL_F *x) { + EEL_F r0 = x[30] - x[14]; + EEL_F r1 = x[31] - x[15]; + + x[30] += x[14]; + x[31] += x[15]; + x[14] = r0; + x[15] = r1; + + r0 = x[28] - x[12]; + r1 = x[29] - x[13]; + x[28] += x[12]; + x[29] += x[13]; + x[12] = MULT_NORM( r0 * cPI1_8 - r1 * cPI3_8 ); + x[13] = MULT_NORM( r0 * cPI3_8 + r1 * cPI1_8 ); + + r0 = x[26] - x[10]; + r1 = x[27] - x[11]; + x[26] += x[10]; + x[27] += x[11]; + x[10] = MULT_NORM(( r0 - r1 ) * cPI2_8); + x[11] = MULT_NORM(( r0 + r1 ) * cPI2_8); + + r0 = x[24] - x[8]; + r1 = x[25] - x[9]; + x[24] += x[8]; + x[25] += x[9]; + x[8] = MULT_NORM( r0 * cPI3_8 - r1 * cPI1_8 ); + x[9] = MULT_NORM( r1 * cPI3_8 + r0 * cPI1_8 ); + + r0 = x[22] - x[6]; + r1 = x[7] - x[23]; + x[22] += x[6]; + x[23] += x[7]; + x[6] = r1; + x[7] = r0; + + r0 = x[4] - x[20]; + r1 = x[5] - x[21]; + x[20] += x[4]; + x[21] += x[5]; + x[4] = MULT_NORM( r1 * cPI1_8 + r0 * cPI3_8 ); + x[5] = MULT_NORM( r1 * cPI3_8 - r0 * cPI1_8 ); + + r0 = x[2] - x[18]; + r1 = x[3] - x[19]; + x[18] += x[2]; + x[19] += x[3]; + x[2] = MULT_NORM(( r1 + r0 ) * cPI2_8); + x[3] = MULT_NORM(( r1 - r0 ) * cPI2_8); + + r0 = x[0] - x[16]; + r1 = x[1] - x[17]; + x[16] += x[0]; + x[17] += x[1]; + x[0] = MULT_NORM( r1 * cPI3_8 + r0 * cPI1_8 ); + x[1] = MULT_NORM( r1 * cPI1_8 - r0 * cPI3_8 ); + + mdct_butterfly_16(x); + mdct_butterfly_16(x + 16); + +} + +/* N point first stage butterfly (in place, 2 register) */ +static void mdct_butterfly_first(EEL_F *T, + EEL_F *x, + int points) { + + EEL_F *x1 = x + points - 8; + EEL_F *x2 = x + (points >> 1) - 8; + EEL_F r0; + EEL_F r1; + + do { + + r0 = x1[6] - x2[6]; + r1 = x1[7] - x2[7]; + x1[6] += x2[6]; + x1[7] += x2[7]; + x2[6] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[7] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + r0 = x1[4] - x2[4]; + r1 = x1[5] - x2[5]; + x1[4] += x2[4]; + x1[5] += x2[5]; + x2[4] = MULT_NORM(r1 * T[5] + r0 * T[4]); + x2[5] = MULT_NORM(r1 * T[4] - r0 * T[5]); + + r0 = x1[2] - x2[2]; + r1 = x1[3] - x2[3]; + x1[2] += x2[2]; + x1[3] += x2[3]; + x2[2] = MULT_NORM(r1 * T[9] + r0 * T[8]); + x2[3] = MULT_NORM(r1 * T[8] - r0 * T[9]); + + r0 = x1[0] - x2[0]; + r1 = x1[1] - x2[1]; + x1[0] += x2[0]; + x1[1] += x2[1]; + x2[0] = MULT_NORM(r1 * T[13] + r0 * T[12]); + x2[1] = MULT_NORM(r1 * T[12] - r0 * T[13]); + + x1 -= 8; + x2 -= 8; + T += 16; + + } while(x2 >= x); +} + +/* N/stage point generic N stage butterfly (in place, 2 register) */ +static void mdct_butterfly_generic(EEL_F *T, + EEL_F *x, + int points, + int trigint) { + + EEL_F *x1 = x + points - 8; + EEL_F *x2 = x + (points >> 1) - 8; + EEL_F r0; + EEL_F r1; + + do { + + r0 = x1[6] - x2[6]; + r1 = x1[7] - x2[7]; + x1[6] += x2[6]; + x1[7] += x2[7]; + x2[6] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[7] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T += trigint; + + r0 = x1[4] - x2[4]; + r1 = x1[5] - x2[5]; + x1[4] += x2[4]; + x1[5] += x2[5]; + x2[4] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[5] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T += trigint; + + r0 = x1[2] - x2[2]; + r1 = x1[3] - x2[3]; + x1[2] += x2[2]; + x1[3] += x2[3]; + x2[2] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[3] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T += trigint; + + r0 = x1[0] - x2[0]; + r1 = x1[1] - x2[1]; + x1[0] += x2[0]; + x1[1] += x2[1]; + x2[0] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[1] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T += trigint; + x1 -= 8; + x2 -= 8; + + } while(x2 >= x); +} + +static void mdct_butterflies(mdct_lookup *init, + EEL_F *x, + int points) { + + EEL_F *T = init->trig; + int stages = init->log2n - 5; + int i, j; + + if(--stages > 0) { + mdct_butterfly_first(T, x, points); + } + + for(i = 1; --stages > 0; i++) { + for(j = 0; j < (1 << i); j++) + mdct_butterfly_generic(T, x + (points >> i)*j, points >> i, 4 << i); + } + + for(j = 0; j < points; j += 32) + mdct_butterfly_32(x + j); + +} + +static void mdct_bitreverse(mdct_lookup *init, + EEL_F *x) { + int n = init->n; + int *bit = init->bitrev; + EEL_F *w0 = x; + EEL_F *w1 = x = w0 + (n >> 1); + EEL_F *T = init->trig + n; + + do { + EEL_F *x0 = x + bit[0]; + EEL_F *x1 = x + bit[1]; + + EEL_F r0 = x0[1] - x1[1]; + EEL_F r1 = x0[0] + x1[0]; + EEL_F r2 = MULT_NORM(r1 * T[0] + r0 * T[1]); + EEL_F r3 = MULT_NORM(r1 * T[1] - r0 * T[0]); + + w1 -= 4; + + r0 = HALVE(x0[1] + x1[1]); + r1 = HALVE(x0[0] - x1[0]); + + w0[0] = r0 + r2; + w1[2] = r0 - r2; + w0[1] = r1 + r3; + w1[3] = r3 - r1; + + x0 = x + bit[2]; + x1 = x + bit[3]; + + r0 = x0[1] - x1[1]; + r1 = x0[0] + x1[0]; + r2 = MULT_NORM(r1 * T[2] + r0 * T[3]); + r3 = MULT_NORM(r1 * T[3] - r0 * T[2]); + + r0 = HALVE(x0[1] + x1[1]); + r1 = HALVE(x0[0] - x1[0]); + + w0[2] = r0 + r2; + w1[0] = r0 - r2; + w0[3] = r1 + r3; + w1[1] = r3 - r1; + + T += 4; + bit += 4; + w0 += 4; + + } while(w0 < w1); +} + +static void megabuf_mdct_apply_window(void *init, EEL_F *inbuf, EEL_F *outbuf) +{ + mdct_lookup *p = (mdct_lookup *)init; + EEL_F *w; + int cnt; + if (!p) return; + + w = p->window; + if (!w) return; + + cnt = p->n / 2; + while (cnt--) *outbuf++ = *inbuf++ * *w++; + cnt = p->n / 2; + while (cnt--) *outbuf++ = *inbuf++ * *--w; +} + + + +static void *megabuf_mdct_init(int n) { + mdct_lookup *lookup = (mdct_lookup *)calloc(sizeof(mdct_lookup), 1); + int i; + EEL_F c = (PI / (EEL_F) n); + int *bitrev; + EEL_F *T; + int n2, log2n; + if (!lookup) return 0; + + lookup->n = n; + lookup->window = (EEL_F *)calloc(sizeof(EEL_F), n / 2); + if (!lookup->window) return lookup; + + for (i = 0; i < n / 2; i ++) + { + lookup->window[i] = sin(c * (i + 0.5)); + } + + if (n <= 32) return lookup; + bitrev = (int*)calloc(sizeof(int), (n / 4)); + lookup->bitrev = bitrev; + if (!bitrev) return lookup; + + T = (EEL_F*)calloc(sizeof(EEL_F), (n + n / 4)); + lookup->trig = T; + if (!T) return lookup; + + n2 = n >> 1; + log2n = lookup->log2n = (int)(log((double)n) / log(2.0) + 0.5); + + /* trig lookups... */ + + for(i = 0; i < n / 4; i++) { + T[i * 2] = FLOAT_CONV(cos((PI / n) * (4 * i))); + T[i * 2 + 1] = FLOAT_CONV(-sin((PI / n) * (4 * i))); + T[n2 + i * 2] = FLOAT_CONV(cos((PI / (2 * n)) * (2 * i + 1))); + T[n2 + i * 2 + 1] = FLOAT_CONV(sin((PI / (2 * n)) * (2 * i + 1))); + } + for(i = 0; i < n / 8; i++) { + T[n + i * 2] = FLOAT_CONV(cos((PI / n) * (4 * i + 2)) * .5); + T[n + i * 2 + 1] = FLOAT_CONV(-sin((PI / n) * (4 * i + 2)) * .5); + } + + /* bitreverse lookup... */ + + { + int mask = (1 << (log2n - 1)) - 1, j; + int msb = 1 << (log2n - 2); + for(i = 0; i < n / 8; i++) { + int acc = 0; + for(j = 0; msb >> j; j++) + if((msb >> j)&i)acc |= 1 << j; + bitrev[i * 2] = ((~acc)&mask) - 1; + bitrev[i * 2 + 1] = acc; + + } + } + lookup->scale = FLOAT_CONV(4.f / n); + return lookup; +} + +static void megabuf_mdct_backward(void *init, EEL_F *in, EEL_F *out) { + mdct_lookup *lookup = (mdct_lookup *)init; + int n, n2, n4; + EEL_F *iX, *oX, *T; + if (!lookup) return; + n = lookup->n; + if (n <= 32 || !lookup->bitrev || !lookup->trig) + { + imdct(in, out, n); + return; + } + n2 = n >> 1; + n4 = n >> 2; + + /* rotate */ + + iX = in + n2 - 7; + oX = out + n2 + n4; + T = lookup->trig + n4; + + do { + oX -= 4; + oX[0] = MULT_NORM(-iX[2] * T[3] - iX[0] * T[2]); + oX[1] = MULT_NORM (iX[0] * T[3] - iX[2] * T[2]); + oX[2] = MULT_NORM(-iX[6] * T[1] - iX[4] * T[0]); + oX[3] = MULT_NORM (iX[4] * T[1] - iX[6] * T[0]); + iX -= 8; + T += 4; + } while(iX >= in); + + iX = in + n2 - 8; + oX = out + n2 + n4; + T = lookup->trig + n4; + + do { + T -= 4; + oX[0] = MULT_NORM (iX[4] * T[3] + iX[6] * T[2]); + oX[1] = MULT_NORM (iX[4] * T[2] - iX[6] * T[3]); + oX[2] = MULT_NORM (iX[0] * T[1] + iX[2] * T[0]); + oX[3] = MULT_NORM (iX[0] * T[0] - iX[2] * T[1]); + iX -= 8; + oX += 4; + } while(iX >= in); + + mdct_butterflies(lookup, out + n2, n2); + mdct_bitreverse(lookup, out); + + /* roatate + window */ + + { + EEL_F *oX1 = out + n2 + n4; + EEL_F *oX2 = out + n2 + n4; + iX = out; + T = lookup->trig + n2; + + do { + oX1 -= 4; + + oX1[3] = MULT_NORM (iX[0] * T[1] - iX[1] * T[0]); + oX2[0] = -MULT_NORM (iX[0] * T[0] + iX[1] * T[1]); + + oX1[2] = MULT_NORM (iX[2] * T[3] - iX[3] * T[2]); + oX2[1] = -MULT_NORM (iX[2] * T[2] + iX[3] * T[3]); + + oX1[1] = MULT_NORM (iX[4] * T[5] - iX[5] * T[4]); + oX2[2] = -MULT_NORM (iX[4] * T[4] + iX[5] * T[5]); + + oX1[0] = MULT_NORM (iX[6] * T[7] - iX[7] * T[6]); + oX2[3] = -MULT_NORM (iX[6] * T[6] + iX[7] * T[7]); + + oX2 += 4; + iX += 8; + T += 8; + } while(iX < oX1); + + iX = out + n2 + n4; + oX1 = out + n4; + oX2 = oX1; + + do { + oX1 -= 4; + iX -= 4; + + oX2[0] = -(oX1[3] = iX[3]); + oX2[1] = -(oX1[2] = iX[2]); + oX2[2] = -(oX1[1] = iX[1]); + oX2[3] = -(oX1[0] = iX[0]); + + oX2 += 4; + } while(oX2 < iX); + + iX = out + n2 + n4; + oX1 = out + n2 + n4; + oX2 = out + n2; + do { + oX1 -= 4; + oX1[0] = iX[3]; + oX1[1] = iX[2]; + oX1[2] = iX[1]; + oX1[3] = iX[0]; + iX += 4; + } while(oX1 > oX2); + } +} + + +static void megabuf_mdct_forward(void *init, EEL_F *in, EEL_F *out) { + mdct_lookup *lookup = (mdct_lookup *)init; + int n, n2, n4, n8; + EEL_F *w, *w2; + if (!lookup) return; + + n = lookup->n; + if (n <= 32 || !lookup->bitrev || !lookup->trig) + { + mdct(in, out, n); + return; + } + n2 = n >> 1; + n4 = n >> 2; + n8 = n >> 3; + EEL_F oldw[1<trig + n2; + + int i = 0; + + for(i = 0; i < n8; i += 2) { + x0 -= 4; + T -= 2; + r0 = x0[2] + x1[0]; + r1 = x0[0] + x1[2]; + w2[i] = MULT_NORM(r1 * T[1] + r0 * T[0]); + w2[i + 1] = MULT_NORM(r1 * T[0] - r0 * T[1]); + x1 += 4; + } + + x1 = in + 1; + + for(; i < n2 - n8; i += 2) { + T -= 2; + x0 -= 4; + r0 = x0[2] - x1[0]; + r1 = x0[0] - x1[2]; + w2[i] = MULT_NORM(r1 * T[1] + r0 * T[0]); + w2[i + 1] = MULT_NORM(r1 * T[0] - r0 * T[1]); + x1 += 4; + } + + x0 = in + n; + + for(; i < n2; i += 2) { + T -= 2; + x0 -= 4; + r0 = -x0[2] - x1[0]; + r1 = -x0[0] - x1[2]; + w2[i] = MULT_NORM(r1 * T[1] + r0 * T[0]); + w2[i + 1] = MULT_NORM(r1 * T[0] - r0 * T[1]); + x1 += 4; + } + + + mdct_butterflies(lookup, w + n2, n2); + mdct_bitreverse(lookup, w); + + /* roatate + window */ + + T = lookup->trig + n2; + x0 = out + n2; + + for(i = 0; i < n4; i++) { + x0--; + out[i] = MULT_NORM((w[0] * T[0] + w[1] * T[1]) * lookup->scale); + x0[0] = MULT_NORM((w[0] * T[1] - w[1] * T[0]) * lookup->scale); + w += 2; + T += 2; + } + } +} + +#if 0 + +static void dct(EEL_F *in, EEL_F *out, int len) +{ + int k; + EEL_F wk = sqrt(2.0 / len); + EEL_F overtwolen = 0.5 / (EEL_F)len; + for (k = 0; k < len; k ++) + { + int n; + EEL_F d = 0.0; + for (n = 0; n < len; n ++) + { + int an = n + 1; + d += in[n] * cos(PI * (2.0 * n + 1.0) * (EEL_F)k * overtwolen); + } + if (!k) d /= sqrt(len); + else d *= wk; + out[k] = (EEL_F)d; + } +} + + +static void idct(EEL_F *in, EEL_F *out, int len) +{ + int n; + EEL_F dd0 = 1.0 / sqrt(len); + EEL_F dd1 = sqrt(2.0 / len); + EEL_F overtwolen = 0.5 / len; + for (n = 0; n < len; n ++) + { + int k; + EEL_F d = 0.0; + for (k = 0; k < len; k ++) + { + EEL_F dd; + if (!k) dd = dd0 * in[k]; + else dd = dd1 * in[k]; + d += dd * cos(PI * (2.0 * n + 1.0) * k * overtwolen); + } + out[n] = (EEL_F)d; + } +} +#endif + + +// 0 is megabuf blocks +// 1 is need_free flag + + +static EEL_F * NSEEL_CGEN_CALL mdct_func(int dir, EEL_F **blocks, EEL_F *start, EEL_F *length) +{ + int l = (int)(*length + 0.0001); + int offs = (int)(*start + 0.0001); + int bitl = 0; + int ilen; + int bidx; + EEL_F *ptr; + while (l > 1 && bitl < EEL_DCT_MAXBITLEN) + { + bitl++; + l >>= 1; + } + if (bitl < EEL_DCT_MINBITLEN) + { + return start; + } + ilen = 1 << bitl; + + bidx = bitl - EEL_DCT_MINBITLEN; + + + // check to make sure we don't cross a boundary + if (offs / NSEEL_RAM_ITEMSPERBLOCK != (offs + ilen * 2 - 1) / NSEEL_RAM_ITEMSPERBLOCK) + { + return start; + } + + ptr = __NSEEL_RAMAlloc(blocks, offs); + if (!ptr || ptr == &nseel_ramalloc_onfail) + { + return start; + } + + if (ilen > 1) + { + static void *mdct_ctxs[1 + EEL_DCT_MAXBITLEN - EEL_DCT_MINBITLEN]; + + if (!mdct_ctxs[bidx]) + { + NSEEL_HOSTSTUB_EnterMutex(); + if (!mdct_ctxs[bidx]) + mdct_ctxs[bidx] = megabuf_mdct_init(ilen); + NSEEL_HOSTSTUB_LeaveMutex(); + } + + if (mdct_ctxs[bidx]) + { + EEL_F buf[1 << EEL_DCT_MAXBITLEN]; + if (dir < 0) + { + megabuf_mdct_backward(mdct_ctxs[bidx], ptr, buf); + megabuf_mdct_apply_window(mdct_ctxs[bidx], buf, ptr); + } + else + { + megabuf_mdct_apply_window(mdct_ctxs[bidx], ptr, buf); + megabuf_mdct_forward(mdct_ctxs[bidx], buf, ptr); + } + } + } + return start; +} + + + +static EEL_F * NSEEL_CGEN_CALL megabuf_mdct(EEL_F **blocks, EEL_F *start, EEL_F *length) +{ + return mdct_func(0, blocks, start, length); +} + +static EEL_F * NSEEL_CGEN_CALL megabuf_imdct(EEL_F **blocks, EEL_F *start, EEL_F *length) +{ + return mdct_func(-1, blocks, start, length); +} + +void EEL_mdct_register() +{ + NSEEL_addfunc_retptr("mdct", 2, NSEEL_PProc_RAM, &megabuf_mdct); + NSEEL_addfunc_retptr("imdct", 2, NSEEL_PProc_RAM, &megabuf_imdct); +} + +#ifdef EEL_WANT_DOCUMENTATION +static const char *eel_mdct_function_reference = +"mdct\tbuffer,length\tPerforms a windowed modified DCT, taking length inputs and producing length/2 outputs. buffer must not cross a 65,536 item boundary, and length must be 64, 128, 256, 512, 2048 or 4096.\0" +"imdct\tbuffer,length\tPerforms a windowed inverse modified DCT, taking length/2 inputs and producing length outputs. buffer must not cross a 65,536 item boundary, and length must be 64, 128, 256, 512, 2048 or 4096.\0" +; +#endif + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_misc.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_misc.h new file mode 100644 index 0000000..a819a8f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_misc.h @@ -0,0 +1,73 @@ +#ifndef _EEL_MISC_H_ +#define _EEL_MISC_H_ + + +#ifndef _WIN32 +#include +#endif +#include +// some generic EEL functions for things like time + +#ifndef EEL_MISC_NO_SLEEP +static EEL_F NSEEL_CGEN_CALL _eel_sleep(void *opaque, EEL_F *amt) +{ + if (*amt >= 0.0) + { + #ifdef _WIN32 + if (*amt > 30000000.0) Sleep(30000000); + else Sleep((DWORD)(*amt+0.5)); + #else + if (*amt > 30000000.0) usleep(((useconds_t)30000000)*1000); + else usleep((useconds_t)(*amt*1000.0+0.5)); + #endif + } + return 0.0; +} +#endif + +static EEL_F * NSEEL_CGEN_CALL _eel_time(void *opaque, EEL_F *v) +{ + *v = (EEL_F) time(NULL); + return v; +} + +static EEL_F * NSEEL_CGEN_CALL _eel_time_precise(void *opaque, EEL_F *v) +{ +#ifdef _WIN32 + LARGE_INTEGER freq,now; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&now); + *v = (double)now.QuadPart / (double)freq.QuadPart; + // *v = (EEL_F)timeGetTime() * 0.001; +#else + struct timeval tm={0,}; + gettimeofday(&tm,NULL); + *v = tm.tv_sec + tm.tv_usec*0.000001; +#endif + return v; +} + + +void EEL_misc_register() +{ +#ifndef EEL_MISC_NO_SLEEP + NSEEL_addfunc_retval("sleep",1,NSEEL_PProc_THIS,&_eel_sleep); +#endif + NSEEL_addfunc_retptr("time",1,NSEEL_PProc_THIS,&_eel_time); + NSEEL_addfunc_retptr("time_precise",1,NSEEL_PProc_THIS,&_eel_time_precise); +} + +#ifdef EEL_WANT_DOCUMENTATION +static const char *eel_misc_function_reference = +#ifndef EEL_MISC_NO_SLEEP + "sleep\tms\tYields the CPU for the millisecond count specified, calling Sleep() on Windows or usleep() on other platforms.\0" +#endif + "time\t[&val]\tSets the parameter (or a temporary buffer if omitted) to the number of seconds since January 1, 1970, and returns a reference to that value. " + "The granularity of the value returned is 1 second.\0" + "time_precise\t[&val]\tSets the parameter (or a temporary buffer if omitted) to a system-local timestamp in seconds, and returns a reference to that value. " + "The granularity of the value returned is system defined (but generally significantly smaller than one second).\0" +; +#endif + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_net.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_net.h new file mode 100644 index 0000000..a796223 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_net.h @@ -0,0 +1,564 @@ +#ifndef _EEL_NET_H_ +#define _EEL_NET_H_ + +// x = tcp_listen(port[,interface, connected_ip_out]) poll this, returns connection id > 0, or <0 on error, or 0 if no new connect -- interface only valid on first call (or after tcp_listen_end(port)) +// tcp_listen_end(port); + +// connection = tcp_connect(host, port[, block]) // connection id > 0 on ok +// tcp_set_block(connection, block?) +// tcp_close(connection) + +// tcp_send(connection, string[, length]) // can return 0 if block, -1 if error, otherwise returns length sent +// tcp_recv(connection, string[, maxlength]) // 0 on nothing, -1 on error, otherwise returns length recv'd + + +// need: +// #define EEL_NET_GET_CONTEXT(opaque) (((sInst *)opaque)->m_net_state) + +// you must pass a JNL_AsyncDNS object to eel_net_state to support nonblocking connect with DNS resolution, otherwise DNS will block +// #define EEL_NET_NO_SYNC_DNS -- never ever call gethostbyname() synchronously, may disable DNS for blocking connect, or if a JNL_IAsyncDNS is not provided. + +#ifndef EEL_NET_MAXSEND +#define EEL_NET_MAXSEND (EEL_STRING_MAXUSERSTRING_LENGTH_HINT+4096) +#endif + + +#include "../jnetlib/netinc.h" +#define JNL_NO_IMPLEMENTATION +#include "../jnetlib/asyncdns.h" + +class eel_net_state +{ + public: + enum { STATE_FREE=0, STATE_RESOLVING, STATE_CONNECTED, STATE_ERR }; + enum { CONNECTION_ID_BASE=0x110000 }; + + eel_net_state(int max_con, JNL_IAsyncDNS *dns); + ~eel_net_state(); + + struct connection_state { + char *hostname; // set during resolve only + SOCKET sock; + int state; // STATE_RESOLVING... + int port; + bool blockmode; + }; + WDL_TypedBuf m_cons; + WDL_IntKeyedArray m_listens; + JNL_IAsyncDNS *m_dns; + + EEL_F onConnect(char *hostNameOwned, int port, int block); + EEL_F onClose(void *opaque, EEL_F handle); + EEL_F set_block(void *opaque, EEL_F handle, bool block); + EEL_F onListen(void *opaque, EEL_F handle, int mode, EEL_F *ifStr, EEL_F *ipOut); + + int __run_connect(connection_state *cs, unsigned int ip); + int __run(connection_state *cs); + int do_send(void *opaque, EEL_F h, const char *src, int len); + int do_recv(void *opaque, EEL_F h, char *buf, int maxlen); + #ifdef _WIN32 + bool m_had_socketlib_init; + #endif +}; + +eel_net_state::eel_net_state(int max_con, JNL_IAsyncDNS *dns) +{ + #ifdef _WIN32 + m_had_socketlib_init=false; + #endif + + m_cons.Resize(max_con); + int x; + for (x=0;xstate == STATE_FREE) + { + unsigned int ip=inet_addr(hostNameOwned); + if (m_dns && ip == INADDR_NONE && !block) + { + const int r=m_dns->resolve(hostNameOwned,&ip); + if (r<0) break; // error! + + if (r>0) ip = INADDR_NONE; + } +#ifndef EEL_NET_NO_SYNC_DNS + else if (ip == INADDR_NONE) + { + struct hostent *he = gethostbyname(hostNameOwned); + if (he) ip = *(int *)he->h_addr; + } +#endif + if (hostNameOwned || ip != INADDR_NONE) + { + if (ip != INADDR_NONE) + { + free(hostNameOwned); + hostNameOwned=NULL; + } + + s->state = STATE_RESOLVING; + s->hostname = hostNameOwned; + s->blockmode = !!block; + s->port = port; + if (hostNameOwned || __run_connect(s,ip)) return x + CONNECTION_ID_BASE; + + s->state=STATE_FREE; + s->hostname=NULL; + } + break; + } + } + free(hostNameOwned); + return -1; +} + +EEL_F eel_net_state::onListen(void *opaque, EEL_F handle, int mode, EEL_F *ifStr, EEL_F *ipOut) +{ + const int port = (int) handle; + if (port < 1 || port > 65535) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_listen(%d): invalid port specified, will never succeed",port); +#endif + return 0.0; + } +#ifdef _WIN32 + if (!m_had_socketlib_init) + { + m_had_socketlib_init=1; + WSADATA wsaData; + WSAStartup(MAKEWORD(1, 1), &wsaData); + } +#endif + SOCKET *sockptr = m_listens.GetPtr(port); + if (mode<0) + { + if (!sockptr) return -1.0; + + SOCKET ss=*sockptr; + m_listens.Delete(port); + if (ss != INVALID_SOCKET) + { + shutdown(ss, SHUT_RDWR); + closesocket(ss); + } + return 0.0; + } + if (!sockptr) + { + struct sockaddr_in sin; + memset((char *) &sin, 0,sizeof(sin)); + if (ifStr) + { + EEL_STRING_MUTEXLOCK_SCOPE + const char *fn = EEL_STRING_GET_FOR_INDEX(*ifStr,NULL); +#ifdef EEL_STRING_DEBUGOUT + if (!fn) EEL_STRING_DEBUGOUT("tcp_listen(%d): bad string identifier %f for second parameter (interface)",port,*ifStr); +#endif + if (fn && *fn) sin.sin_addr.s_addr=inet_addr(fn); + + } + if (!sin.sin_addr.s_addr || sin.sin_addr.s_addr==INADDR_NONE) sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_family = AF_INET; + sin.sin_port = htons( (short) port ); + SOCKET sock = socket(AF_INET,SOCK_STREAM,0); + if (sock != INVALID_SOCKET) + { + SET_SOCK_DEFAULTS(sock); + SET_SOCK_BLOCK(sock,0); + if (bind(sock,(struct sockaddr *)&sin,sizeof(sin)) || listen(sock,8)==-1) + { + shutdown(sock, SHUT_RDWR); + closesocket(sock); + sock=INVALID_SOCKET; + } + } +#ifdef EEL_STRING_DEBUGOUT + //if (sock == INVALID_SOCKET) EEL_STRING_DEBUGOUT("tcp_listen(%d): failed listening on port",port); + // we report -1 to the caller, no need to error message +#endif + m_listens.Insert(port,sock); + sockptr = m_listens.GetPtr(port); + } + if (!sockptr || *sockptr == INVALID_SOCKET) return -1; + + struct sockaddr_in saddr; + socklen_t length = sizeof(struct sockaddr_in); + SOCKET newsock = accept(*sockptr, (struct sockaddr *) &saddr, &length); + if (newsock == INVALID_SOCKET) + { + return 0; // nothing to report here + } + SET_SOCK_DEFAULTS(newsock); + + int x; + for(x=0;xstate == STATE_FREE) + { + cs->state=STATE_CONNECTED; + free(cs->hostname); + cs->hostname=NULL; + cs->sock = newsock; + cs->blockmode=true; + cs->port=0; + if (ipOut) + { + EEL_STRING_MUTEXLOCK_SCOPE + WDL_FastString *ws=NULL; + EEL_STRING_GET_FOR_WRITE(*ipOut,&ws); + if (ws) + { + const unsigned int a = ntohl(saddr.sin_addr.s_addr); + ws->SetFormatted(128,"%d.%d.%d.%d",(a>>24)&0xff,(a>>16)&0xff,(a>>8)&0xff,a&0xff); + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_listen(%d): bad string identifier %f for third parameter (IP-out)",port,*ipOut); +#endif + } + } + return x + CONNECTION_ID_BASE; + } + } + shutdown(newsock, SHUT_RDWR); + closesocket(newsock); + return -1; + + +} + +int eel_net_state::__run_connect(connection_state *cs, unsigned int ip) +{ + SOCKET s=socket(AF_INET,SOCK_STREAM,0); + if (s == INVALID_SOCKET) return 0; + SET_SOCK_DEFAULTS(s); + + if (!cs->blockmode) SET_SOCK_BLOCK(s,0); + + struct sockaddr_in sa={0,}; + sa.sin_family=AF_INET; + sa.sin_addr.s_addr = ip; + sa.sin_port = htons(cs->port); + if (!connect(s,(struct sockaddr *)&sa,16) || (!cs->blockmode && JNL_ERRNO == JNL_EINPROGRESS)) + { + cs->state = STATE_CONNECTED; + cs->sock = s; + return 1; + } + shutdown(s, SHUT_RDWR); + closesocket(s); + return 0; +} + +int eel_net_state::__run(connection_state *cs) +{ + if (cs->sock != INVALID_SOCKET) return 0; + + if (!cs->hostname) return -1; + + unsigned int ip=INADDR_NONE; + const int r=m_dns ? m_dns->resolve(cs->hostname,&ip) : -1; + if (r>0) return 0; + + free(cs->hostname); + cs->hostname=NULL; + + if (r<0 || !__run_connect(cs,ip)) + { + cs->state = STATE_ERR; + return -1; + } + + return 0; +} + +int eel_net_state::do_recv(void *opaque, EEL_F h, char *buf, int maxlen) +{ + const int idx=(int)h-CONNECTION_ID_BASE; + if (idx>=0 && idxsock == INVALID_SOCKET && !s->hostname) + EEL_STRING_DEBUGOUT("tcp_recv: connection identifier %f is not open",h); +#endif + if (__run(s) || s->sock == INVALID_SOCKET) return s->state == STATE_ERR ? -1 : 0; + + if (maxlen == 0) return 0; + + const int rv=(int)recv(s->sock,buf,maxlen,0); + if (rv < 0 && !s->blockmode && (JNL_ERRNO == JNL_EWOULDBLOCK || JNL_ERRNO == JNL_ENOTCONN)) return 0; + + if (!rv) return -1; // TCP, 0=connection terminated + + return rv; + } +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_recv: connection identifier %f is out of range",h); +#endif + return -1; +} + +int eel_net_state::do_send(void *opaque, EEL_F h, const char *src, int len) +{ + const int idx=(int)h-CONNECTION_ID_BASE; + if (idx>=0 && idxsock == INVALID_SOCKET && !s->hostname) + EEL_STRING_DEBUGOUT("tcp_send: connection identifier %f is not open",h); +#endif + if (__run(s) || s->sock == INVALID_SOCKET) return s->state == STATE_ERR ? -1 : 0; + const int rv=(int)send(s->sock,src,len,0); + if (rv < 0 && !s->blockmode && (JNL_ERRNO == JNL_EWOULDBLOCK || JNL_ERRNO == JNL_ENOTCONN)) return 0; + return rv; + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_send: connection identifier %f out of range",h); +#endif + } + return -1; +} + +EEL_F eel_net_state::set_block(void *opaque, EEL_F handle, bool block) +{ + int idx=(int)handle-CONNECTION_ID_BASE; + if (idx>=0 && idxblockmode != block) + { + s->blockmode=block; + if (s->sock != INVALID_SOCKET) + { + SET_SOCK_BLOCK(s->sock,(block?1:0)); + } + else + { +#ifdef EEL_STRING_DEBUGOUT + if (!s->hostname) EEL_STRING_DEBUGOUT("tcp_set_block: connection identifier %f is not open",handle); +#endif + } + return 1; + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_set_block: connection identifier %f out of range",handle); +#endif + } + return 0; +} + +EEL_F eel_net_state::onClose(void *opaque, EEL_F handle) +{ + int idx=(int)handle-CONNECTION_ID_BASE; + if (idx>=0 && idxhostname; + free(s->hostname); + s->hostname = NULL; + s->state = STATE_ERR; + if (s->sock != INVALID_SOCKET) + { + shutdown(s->sock,SHUT_RDWR); + closesocket(s->sock); + s->sock = INVALID_SOCKET; + return 1.0; + } + else if (!hadhn) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_close: connection identifier %f is not open",handle); +#endif + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_close: connection identifier %f is out of range",handle); +#endif + } + return 0.0; +} + + +static EEL_F NSEEL_CGEN_CALL _eel_tcp_connect(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_net_state *ctx; + if (np > 1 && NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) + { + char *dest=NULL; + { + EEL_STRING_MUTEXLOCK_SCOPE + const char *fn = EEL_STRING_GET_FOR_INDEX(parms[0][0],NULL); + if (fn) dest=strdup(fn); + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("tcp_connect(): host string identifier %f invalid",parms[0][0]); +#endif + } + } + if (dest) return ctx->onConnect(dest, (int) (parms[1][0]+0.5), np < 3 || parms[2][0] >= 0.5); + } + return -1.0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_tcp_set_block(void *opaque, EEL_F *handle, EEL_F *bl) +{ + eel_net_state *ctx; + if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->set_block(opaque,*handle, *bl >= 0.5); + return 0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_tcp_close(void *opaque, EEL_F *handle) +{ + eel_net_state *ctx; + if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->onClose(opaque,*handle); + return 0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_tcp_recv(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_net_state *ctx; + if (np > 1 && NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) + { + char buf[EEL_STRING_MAXUSERSTRING_LENGTH_HINT]; + int ml = np > 2 ? (int)parms[2][0] : 4096; + if (ml < 0 || ml > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) ml = EEL_STRING_MAXUSERSTRING_LENGTH_HINT; + + ml=ctx->do_recv(opaque,parms[0][0],buf,ml); + + { + EEL_STRING_MUTEXLOCK_SCOPE + WDL_FastString *ws=NULL; + EEL_STRING_GET_FOR_WRITE(parms[1][0],&ws); + if (ws) + { + if (ml<=0) ws->Set(""); + else ws->SetRaw(buf,ml); + } + } + return ml; + } + return -1; +} + +static EEL_F NSEEL_CGEN_CALL _eel_tcp_send(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_net_state *ctx; + if (np > 1 && NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) + { + char buf[EEL_NET_MAXSEND]; + + int l; + { + EEL_STRING_MUTEXLOCK_SCOPE + WDL_FastString *ws=NULL; + const char *fn = EEL_STRING_GET_FOR_INDEX(parms[1][0],&ws); + l = ws ? ws->GetLength() : (int) strlen(fn); + if (np > 2) + { + int al=(int)parms[2][0]; + if (al<0) al=0; + if (al 0) memcpy(buf,fn,l); + } + if (l>0) return ctx->do_send(opaque,parms[0][0],buf,l); + return 0; + } + return -1; +} + +static EEL_F NSEEL_CGEN_CALL _eel_tcp_listen(void *opaque, INT_PTR np, EEL_F **parms) +{ + eel_net_state *ctx; + if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->onListen(opaque,parms[0][0],1,np>1?parms[1]:NULL,np>2?parms[2]:NULL); + return 0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_tcp_listen_end(void *opaque, EEL_F *handle) +{ + eel_net_state *ctx; + if (NULL != (ctx=EEL_NET_GET_CONTEXT(opaque))) return ctx->onListen(opaque,*handle,-1,NULL,NULL); + return 0; +} + + +void EEL_tcp_register() +{ + NSEEL_addfunc_varparm("tcp_listen",1,NSEEL_PProc_THIS,&_eel_tcp_listen); + NSEEL_addfunc_retval("tcp_listen_end",1,NSEEL_PProc_THIS,&_eel_tcp_listen_end); + + NSEEL_addfunc_varparm("tcp_connect",2,NSEEL_PProc_THIS,&_eel_tcp_connect); + NSEEL_addfunc_varparm("tcp_send",2,NSEEL_PProc_THIS,&_eel_tcp_send); + NSEEL_addfunc_varparm("tcp_recv",2,NSEEL_PProc_THIS,&_eel_tcp_recv); + + NSEEL_addfunc_retval("tcp_set_block",2,NSEEL_PProc_THIS,&_eel_tcp_set_block); + NSEEL_addfunc_retval("tcp_close",1,NSEEL_PProc_THIS,&_eel_tcp_close); +} + +#ifdef EEL_WANT_DOCUMENTATION +const char *eel_net_function_reference = + "tcp_listen\tport[,\"interface\",#ip_out]\tListens on port specified. Returns less than 0 if could not listen, 0 if no new connection available, or greater than 0 (as a TCP connection ID) if a new connection was made. If a connection made and #ip_out specified, it will be set to the remote IP. interface can be empty for all interfaces, otherwise an interface IP as a string.\0" + "tcp_listen_end\tport\tEnds listening on port specified.\0" + "tcp_connect\t\"address\",port[,block]\tCreate a new TCP connection to address:port. If block is specified and 0, connection will be made nonblocking. Returns TCP connection ID greater than 0 on success.\0" + "tcp_send\tconnection,\"str\"[,len]\tSends a string to connection. Returns -1 on error, 0 if connection is non-blocking and would block, otherwise returns length sent. If len is specified and not less than 1, only the first len bytes of the string parameter will be sent.\0" + "tcp_recv\tconnection,#str[,maxlen]\tReceives data from a connection to #str. If maxlen is specified, no more than maxlen bytes will be received. If non-blocking, 0 will be returned if would block. Returns less than 0 if error.\0" + "tcp_set_block\tconnection,block\tSets whether a connection blocks.\0" + "tcp_close\tconnection\tCloses a TCP connection created by tcp_listen() or tcp_connect().\0" +; +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_pp.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_pp.cpp new file mode 100644 index 0000000..dbca994 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_pp.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include + +#include "../wdlstring.h" +#include "../ptrlist.h" +#include "eel_pproc.h" + +void NSEEL_HOSTSTUB_EnterMutex() { } +void NSEEL_HOSTSTUB_LeaveMutex() { } + +int main(int argc, char **argv) +{ + if (argc != 2) + { + fprintf(stderr,"Usage: %s [scriptfile | -]\n",argv[0]); + return 1; + } + FILE *fp = strcmp(argv[1],"-") ? fopen(argv[1],"rb") : stdin; + if (!fp) + { + fprintf(stderr,"Error: could not open %s\n",argv[1]); + return 1; + } + WDL_FastString file_str, pp_str; + for (;;) + { + char buf[4096]; + if (!fgets(buf,sizeof(buf),fp)) break; + file_str.Append(buf); + } + if (fp != stdin) fclose(fp); + + EEL2_PreProcessor pproc; + const char *err = pproc.preprocess(file_str.Get(),&pp_str); + if (err) + { + fprintf(stderr,"Error: %s\n",err); + return 1; + } + + printf("%s",pp_str.Get()); + + return 0; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_pproc.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_pproc.h new file mode 100644 index 0000000..3f469b5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_pproc.h @@ -0,0 +1,469 @@ +#ifndef _EEL2_PREPROC_H_ +#define _EEL2_PREPROC_H_ +#include "ns-eel-int.h" +#include "../win32_utf8.h" + +#define EEL2_PREPROCESS_OPEN_TOKEN " 0.0; + + int lc = 0; + const char *tag = str; + while (*tag && strncmp(tag,EEL2_PREPROCESS_OPEN_TOKEN,2)) if (*tag++ == '\n') lc++; + + if (lc) + { + input_linecnt += lc; + if (suppress) + add_line_inf(m_output_linecnt,lc); + else + m_output_linecnt += lc; + } + + if (!*tag) + { + if (!suppress) fs->Append(str); + return NULL; + } + if (!suppress && tag > str) fs->Append(str,(int)(tag-str)); + + tag += 2; + while (*tag == ' ' || *tag == '\t') tag++; + str = tag; + lc = 0; + while (*str && strncmp(str,"?>",2)) if (*str++ == '\n') lc++; + + if (!*str) + { + m_tmp.SetFormatted(512, "%d: unterminated preprocessor " EEL2_PREPROCESS_OPEN_TOKEN " block", input_linecnt+1); + return m_tmp.Get(); + } + + if (lc) + { + input_linecnt += lc; + add_line_inf(m_output_linecnt,lc); + } + if (str > tag) + { + m_tmp.Set(tag,(int)(str-tag)); + NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(m_vm, m_tmp.Get(), 0, NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); + if (!ch) + { + const char *err = NSEEL_code_getcodeerror(m_vm); + if (err) + { + const int line_ref = atoi(err); + while (*err >= '0' && *err <= '9') err++; + m_tmp.SetFormatted(512,"%d: preprocessor%s%s",input_linecnt+line_ref,*err && *err != ':' ? ": ":"",err); + return m_tmp.Get(); + } + } + else + { + lc = 0; + const int oldlen = fs->GetLength(); + m_fsout = fs; + NSEEL_code_execute(ch); + m_fsout = NULL; + m_code_handles.Add(ch); + for (int x = oldlen; x < fs->GetLength(); x ++) if (fs->Get()[x] == '\n') lc++; + if (lc) + { + add_line_inf(m_output_linecnt,-lc); + m_output_linecnt += lc; + } + } + } + str += 2; + } + } + + const char *translate_error_line(const char *err_line) + { + if (m_line_tab.GetSize()<2) return err_line; + int l = atoi(err_line)-1; + if (l<0) return err_line; + + // tab is a list of pairs + // [, delta] + // delta>0 if input lines were skipped + // delta<0 if output lines were added + + int nl = l; + for (int x = m_line_tab.GetSize()-2; x >= 0; x -= 2) + { + int p = m_line_tab.Get()[x]; + if (nl > p) + { + int delta = m_line_tab.Get()[x+1]; + nl += delta; + if (nl < p) nl = p; + } + } + + if (l == nl) return err_line; + + while (*err_line >= '0' && *err_line <= '9') err_line++; + if (*err_line == ':') err_line++; + m_tmp.SetFormatted(512,"%d:%s",1 + nl,err_line); + return m_tmp.Get(); + } + + NSEEL_VMCTX m_vm; + WDL_PtrList m_literal_strings; + WDL_FastString m_tmp, *m_fsout; + WDL_TypedBuf m_line_tab; // expose this in case the caller wants to keep copies around + EEL_F *m_suppress; + int m_max_sz; + int m_cur_depth, m_max_include_depth; + int m_output_linecnt; + static eel_function_table m_ftab; + WDL_PtrList m_code_handles; + WDL_PtrList m_include_paths; + + void add_line_inf(int output_linecnt, int lc) + { + if (!m_cur_depth) + { + m_line_tab.Add(output_linecnt); // log lc lines of input skipped + m_line_tab.Add(lc); + } + } + + static EEL_F addStringCallback(void *opaque, struct eelStringSegmentRec *list) + { + EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque; + if (!_this) return -1.0; + + const int sz = nseel_stringsegments_tobuf(NULL,0,list); + char *ns = (char *)malloc(sz+1); + if (WDL_NOT_NORMALLY(!ns)) return -1.0; + nseel_stringsegments_tobuf(ns,sz,list); + + const int nstr = _this->m_literal_strings.GetSize(); + for (int x=0;xm_literal_strings.Get(x); + if (!strcmp(s,ns)) + { + free(ns); + return x + LITERAL_BASE; + } + } + _this->m_literal_strings.Add(ns); + return nstr + LITERAL_BASE; + } + + const char *GetString(EEL_F v) + { + if (v >= LITERAL_BASE && v < LITERAL_BASE + m_literal_strings.GetSize()) + return m_literal_strings.Get((int) (v - LITERAL_BASE)); + return NULL; + } + + static int eel_validate_format_specifier(const char *fmt_in, char *typeOut, + char *fmtOut, int fmtOut_sz, + char *varOut, int varOut_sz, + int *varOut_used + ) + { + const char *fmt = fmt_in+1; + int state=0; + if (fmt_in[0] != '%') return 0; // ugh passed a non-specifier + + *varOut_used = 0; + *varOut = 0; + + if (fmtOut_sz-- < 2) return 0; + *fmtOut++ = '%'; + + while (*fmt) + { + const char c = *fmt++; + if (fmtOut_sz < 2) return 0; + + if (c == 'f'|| c=='e' || c=='E' || c=='g' || c=='G' || c == 'd' || c == 'u' || + c == 'x' || c == 'X' || c == 'c' || c == 'C' || c =='s' || c=='S' || c=='i') + { + *typeOut = c; + fmtOut[0] = c; + fmtOut[1] = 0; + return (int) (fmt - fmt_in); + } + else if (c == '.') + { + *fmtOut++ = c; fmtOut_sz--; + if (state&(2)) break; + state |= 2; + } + else if (c == '+') + { + *fmtOut++ = c; fmtOut_sz--; + if (state&(32|16|8|4)) break; + state |= 8; + } + else if (c == '-' || c == ' ') + { + *fmtOut++ = c; fmtOut_sz--; + if (state&(32|16|8|4)) break; + state |= 16; + } + else if (c >= '0' && c <= '9') + { + *fmtOut++ = c; fmtOut_sz--; + state|=4; + } + else if (c == '{') + { + if (state & 64) break; + state|=64; + if (*fmt == '.' || (*fmt >= '0' && *fmt <= '9')) return 0; // symbol name can't start with 0-9 or . + + while (*fmt != '}') + { + if ((*fmt >= 'a' && *fmt <= 'z') || + (*fmt >= 'A' && *fmt <= 'Z') || + (*fmt >= '0' && *fmt <= '9') || + *fmt == '_' || *fmt == '.' || *fmt == '#') + { + if (varOut_sz < 2) return 0; + *varOut++ = *fmt++; + varOut_sz -- ; + } + else + { + return 0; // bad character in variable name + } + } + fmt++; + *varOut = 0; + *varOut_used=1; + } + else + { + break; + } + } + return 0; + } + + static int eel_format_strings(void *opaque, const char *fmt, const char *fmt_end, char *buf, int buf_sz, int num_fmt_parms, EEL_F **fmt_parms) + { + EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque; + int fmt_parmpos = 0; + char *op = buf; + while ((fmt_end ? fmt < fmt_end : *fmt) && op < buf+buf_sz-128) + { + if (fmt[0] == '%' && fmt[1] == '%') + { + *op++ = '%'; + fmt+=2; + } + else if (fmt[0] == '%') + { + char ct=0; + char fs[128]; + char varname[128]; + int varname_used=0; + const int l=eel_validate_format_specifier(fmt,&ct,fs,sizeof(fs),varname,sizeof(varname),&varname_used); + if (!l || !ct) + { + *op=0; + return -1; + } + + const EEL_F *varptr = NULL; + if (!varname_used) + { + if (fmt_parmpos < num_fmt_parms) varptr = fmt_parms[fmt_parmpos]; + fmt_parmpos++; + } + double v = varptr ? (double)*varptr : 0.0; + + if (ct == 's' || ct=='S') + { + const char *str = _this->GetString(v); + const int maxl=(int) (buf+buf_sz - 2 - op); + snprintf(op,maxl,fs,str ? str : ""); + } + else + { + if (ct == 'x' || ct == 'X' || ct == 'd' || ct == 'u' || ct=='i') + { + snprintf(op,64,fs,(int) (v)); + } + else if (ct == 'c') + { + *op++=(char) (int)v; + *op=0; + } + else if (ct == 'C') + { + const unsigned int iv = (unsigned int) v; + int bs = 0; + if (iv & 0xff000000) bs=24; + else if (iv & 0x00ff0000) bs=16; + else if (iv & 0x0000ff00) bs=8; + while (bs>=0) + { + const char c=(char) (iv>>bs); + *op++=c?c:' '; + bs-=8; + } + *op=0; + } + else + { + snprintf(op,64,fs,v); + } + } + + while (*op) op++; + + fmt += l; + } + else + { + *op++ = *fmt++; + } + + } + *op=0; + return (int) (op - buf); + } + + static EEL_F NSEEL_CGEN_CALL pp_printf(void *opaque, INT_PTR num_param, EEL_F **parms) + { + if (num_param>0 && opaque) + { + EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque; + const char *fmt = _this->GetString(parms[0][0]); + if (fmt) + { + char buf[16384]; + const int len = eel_format_strings(opaque,fmt,NULL,buf,(int)sizeof(buf), (int)num_param-1, parms+1); + + if (len >= 0) + { + if (_this->m_fsout && _this->m_fsout->GetLength() < _this->m_max_sz) + { + _this->m_fsout->Append(buf,len); + } + return 1.0; + } + } + } + return 0.0; + } + + static EEL_F NSEEL_CGEN_CALL pp_include(void *opaque, INT_PTR num_param, EEL_F **parms) + { + if (num_param>0 && opaque) + { + EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque; + if (_this->m_cur_depth >= _this->m_max_include_depth) return -1.0; + + const char *fn = _this->GetString(parms[0][0]); + if (!fn || !*fn) return -2.0; + + WDL_FastString fullfn; + for (int x = _this->m_include_paths.GetSize()-1; x >= 0; x--) + { + const char *p = _this->m_include_paths.Get(x); + if (p && *p) + { + fullfn.Set(p); + fullfn.Append(WDL_DIRCHAR_STR); + fullfn.Append(fn); + FILE *fp = fopenUTF8(fullfn.Get(),"rb"); + if (fp) + { + double rv = 0.0; + _this->m_cur_depth++; + fullfn.Set(""); + while (fullfn.GetLength() < (4<<20)) + { + char buf[512]; + if (!fgets(buf,sizeof(buf),fp)) break; + fullfn.Append(buf); + } + fclose(fp); + + WDL_FastString *outp = _this->m_fsout; + if (_this->preprocess(fullfn.Get(),outp)) + { + rv = -3.0; + } + _this->m_fsout = outp; + + _this->m_cur_depth--; + return rv; + } + } + } + return -4.0; + } + return 0.0; + } + +}; + +eel_function_table EEL2_PreProcessor::m_ftab; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_strings.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_strings.h new file mode 100644 index 0000000..7991721 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eel_strings.h @@ -0,0 +1,1668 @@ +#ifndef __EEL__STRINGS_H__ +#define __EEL__STRINGS_H__ + +#include "ns-eel-int.h" +#include "../wdlcstring.h" +#include "../wdlstring.h" + +// required for context +// #define EEL_STRING_GET_CONTEXT_POINTER(opaque) (((sInst *)opaque)->m_eel_string_state) + + +/* + // writeable user-strings are 0..1023 (EEL_STRING_MAX_USER_STRINGS-1), and can be up to about EEL_STRING_MAXUSERSTRING_LENGTH_HINT bytes long + + printf("string %d blah"); -- output to log, allows %d %u %f etc, if host implements formats [1] + sprintf(str,"string %d blah"); -- output to str [1] + strlen(str); -- returns string length + match("*test*", "this is a test") -- search for first parameter regex-style in second parameter + matchi("*test*", "this is a test") -- search for first parameter regex-style in second parameter (case insensitive) + // %s means 1 or more chars + // %0s means 0 or more chars + // %5s means exactly 5 chars + // %5-s means 5 or more chars + // %-10s means 1-10 chars + // %3-5s means 3-5 chars. + // %0-5s means 0-5 chars. + // %S (uppercase) indicates lazy match (%D, %F, %X, etc) + + strcpy(str, srcstr); -- replaces str with srcstr + strcat(str, srcstr); -- appends srcstr to str + strcmp(str, str2) -- compares strings + stricmp(str, str2) -- compares strings (ignoring case) + strncmp(str, str2, maxlen) -- compares strings up to maxlen bytes + strnicmp(str, str2, maxlen) -- compares strings (ignoring case) up to maxlen bytes + strncpy(str, srcstr, maxlen); -- replaces str with srcstr, up to maxlen (-1 for unlimited) + strncat(str, srcstr, maxlen); -- appends up to maxlen of srcstr to str (-1 for unlimited) + strcpy_from(str,srcstr, offset); -- copies srcstr to str, but starts reading srcstr at offset offset + strcpy_substr(str, srcstr, offs, ml) -- php-style (start at offs, offs<0 means from end, ml for maxlen, ml<0 = reduce length by this amt) + str_getchar(str, offset[, type]); -- returns value at offset offset, type can be omitted or 0 or 'c', 's', 'S', 'i', 'I', 'f', 'F', 'd', 'D', 'uc', 'us', 'US', 'ui', 'UI' + -- negative offset is offset from end of string + str_setchar(str, offset, val[, type]); - sets value at offset offset, type optional. offset must be [0,length], if length it can lengthen string, if >length then call fails + -- negative offset is offset from end of string + + str_setlen(str, len); -- sets length of string (if increasing, will be space-padded) + str_delsub(str, pos, len); -- deletes len chars at pos + str_insert(str, srcstr, pos); -- inserts srcstr at pos + + [1]: note: printf/sprintf are NOT binary safe when using %s with modifiers (such as %100s etc) -- the source string being formatted + will terminate at the first NULL character + ); + + + + */ + +// define this to allow modifying literal strings via code, i.e. foobar = strcat("foo","bar"); +// disabled by default, so literals can be pooled/reused/etc +// #define EEL_STRINGS_MUTABLE_LITERALS + +#ifndef EEL_STRING_MAXUSERSTRING_LENGTH_HINT +#define EEL_STRING_MAXUSERSTRING_LENGTH_HINT 16384 +#endif + +#ifndef EEL_STRING_MAX_USER_STRINGS +// strings 0...x-1 +#define EEL_STRING_MAX_USER_STRINGS 1024 +#endif + +#ifndef EEL_STRING_LITERAL_BASE +// strings defined by "xyz" +#define EEL_STRING_LITERAL_BASE 10000 +#endif + +// base for named mutable strings (#xyz) +#ifndef EEL_STRING_NAMED_BASE +#define EEL_STRING_NAMED_BASE 90000 +#endif + +// base for unnamed mutable strings (#) +#ifndef EEL_STRING_UNNAMED_BASE +#define EEL_STRING_UNNAMED_BASE 190000 +#endif + +// define EEL_STRING_MUTEXLOCK_SCOPE for custom, otherwise EEL_STRING_WANT_MUTEX for builtin locking +#ifndef EEL_STRING_MUTEXLOCK_SCOPE + #ifdef EEL_STRING_WANT_MUTEX + #include "../mutex.h" + #define EEL_STRING_MUTEXLOCK_SCOPE WDL_MutexLock __lock(&(EEL_STRING_GET_CONTEXT_POINTER(opaque)->m_mutex)); + #else + #define EEL_STRING_MUTEXLOCK_SCOPE + #endif +#endif + +// allow overriding behavior +#ifndef EEL_STRING_GET_FOR_INDEX +#define EEL_STRING_GET_FOR_INDEX(x, wr) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetStringForIndex(x, wr, false)) +#endif + +#ifndef EEL_STRING_GET_FOR_WRITE +#define EEL_STRING_GET_FOR_WRITE(x, wr) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetStringForIndex(x, wr, true)) +#endif + +#ifndef EEL_STRING_GETFMTVAR +#define EEL_STRING_GETFMTVAR(x) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetVarForFormat(x)) +#endif + +#ifndef EEL_STRING_GETNAMEDVAR +#define EEL_STRING_GETNAMEDVAR(x,createOK,altOut) (EEL_STRING_GET_CONTEXT_POINTER(opaque)->GetNamedVar(x,createOK,altOut)) +#endif + + + + + +#ifndef EEL_STRING_STORAGECLASS +#define EEL_STRING_STORAGECLASS WDL_FastString +#endif + + + +class eel_string_context_state +{ + public: + static int cmpistr(const char **a, const char **b) { return stricmp(*a,*b); } + + eel_string_context_state() : m_named_strings_names(false), m_varname_cache(cmpistr) + { + m_vm=0; + memset(m_user_strings,0,sizeof(m_user_strings)); + } + ~eel_string_context_state() + { + clear_state(true); + } + + void clear_state(bool full) + { + if (full) + { + int x; + for (x=0;x=0 && idx < EEL_STRING_MAX_USER_STRINGS) + { + if (stringContainerOut) + { + if (!m_user_strings[idx]) m_user_strings[idx] = new EEL_STRING_STORAGECLASS; + *stringContainerOut = m_user_strings[idx]; + } + return m_user_strings[idx]?m_user_strings[idx]->Get():""; + } + EEL_STRING_STORAGECLASS *s; + s = m_unnamed_strings.Get(idx - EEL_STRING_UNNAMED_BASE); + if (!s) s= m_named_strings.Get(idx - EEL_STRING_NAMED_BASE); + + if (s) + { + // mutable string + if (stringContainerOut) *stringContainerOut=s; + } + else + { + s = m_literal_strings.Get(idx - EEL_STRING_LITERAL_BASE); + #ifdef EEL_STRINGS_MUTABLE_LITERALS + if (stringContainerOut) *stringContainerOut=s; + #else + if (stringContainerOut) *stringContainerOut=is_for_write ? NULL : s; + #endif + } + return s ? s->Get() : NULL; + } + + WDL_PtrList m_literal_strings; // "this kind", normally immutable + WDL_PtrList m_unnamed_strings; // # + WDL_PtrList m_named_strings; // #xyz by index, but stringkeyed below for names + WDL_StringKeyedArray m_named_strings_names; // #xyz->index + + EEL_STRING_STORAGECLASS *m_user_strings[EEL_STRING_MAX_USER_STRINGS]; // indices 0-1023 (etc) + WDL_AssocArray m_varname_cache; // cached pointers when using %{xyz}s, %{#xyz}s bypasses + + NSEEL_VMCTX m_vm; +#ifdef EEL_STRING_WANT_MUTEX + WDL_Mutex m_mutex; +#endif + + static EEL_F addNamedStringCallback(void *opaque, const char *name) + { + if (!opaque) return -1.0; + eel_string_context_state *_this = EEL_STRING_GET_CONTEXT_POINTER(opaque); + if (!_this) return -1.0; +#ifdef EEL_STRING_NAMEDSTRINGCALLBACK_HOOK + EEL_STRING_NAMEDSTRINGCALLBACK_HOOK +#endif + + EEL_STRING_MUTEXLOCK_SCOPE + if (!name || !name[0]) + { + _this->m_unnamed_strings.Add(new EEL_STRING_STORAGECLASS); + return (EEL_F) (_this->m_unnamed_strings.GetSize()-1 + EEL_STRING_UNNAMED_BASE); + } + + int a = _this->m_named_strings_names.Get(name); + if (a) return (EEL_F)a; + + a = _this->m_named_strings.GetSize() + EEL_STRING_NAMED_BASE; + _this->m_named_strings.Add(new EEL_STRING_STORAGECLASS); + _this->m_named_strings_names.Insert(name,a); + + return (EEL_F)a; + } + + static EEL_F addStringCallback(void *opaque, struct eelStringSegmentRec *list) + { + if (!opaque) return -1.0; + + eel_string_context_state *_this = EEL_STRING_GET_CONTEXT_POINTER(opaque); + if (!_this) return -1.0; + + EEL_STRING_STORAGECLASS *ns = new EEL_STRING_STORAGECLASS; + // could probably do a faster implementation using AddRaw() etc but this should also be OK + int sz=nseel_stringsegments_tobuf(NULL,0,list); + ns->SetLen(sz+32); + sz=nseel_stringsegments_tobuf((char *)ns->Get(),sz,list); + ns->SetLen(sz); + EEL_STRING_MUTEXLOCK_SCOPE + return (EEL_F)_this->AddString(ns); + } + int AddString(EEL_STRING_STORAGECLASS *ns) + { +#ifdef EEL_STRINGS_MUTABLE_LITERALS + m_literal_strings.Add(ns); + return m_literal_strings.GetSize()-1+EEL_STRING_LITERAL_BASE; +#else + const int l = ns->GetLength(); + const int sz=m_literal_strings.GetSize(); + int x; + for (x=0;xGetLength() == l && !strcmp(s->Get(),ns->Get())) break; + } + if (xm_varname_cache.AddUnsorted(name,val); + return 1; + } + +}; + + + +static int eel_validate_format_specifier(const char *fmt_in, char *typeOut, + char *fmtOut, int fmtOut_sz, + char *varOut, int varOut_sz, + int *varOut_used + ) +{ + const char *fmt = fmt_in+1; + int state=0; + if (fmt_in[0] != '%') return 0; // ugh passed a non-specifier + + *varOut_used = 0; + *varOut = 0; + + if (fmtOut_sz-- < 2) return 0; + *fmtOut++ = '%'; + + while (*fmt) + { + const char c = *fmt++; + if (fmtOut_sz < 2) return 0; + + if (c == 'f'|| c=='e' || c=='E' || c=='g' || c=='G' || c == 'd' || c == 'u' || + c == 'x' || c == 'X' || c == 'c' || c == 'C' || c =='s' || c=='S' || c=='i') + { + *typeOut = c; + fmtOut[0] = c; + fmtOut[1] = 0; + return (int) (fmt - fmt_in); + } + else if (c == '.') + { + *fmtOut++ = c; fmtOut_sz--; + if (state&(2)) break; + state |= 2; + } + else if (c == '+') + { + *fmtOut++ = c; fmtOut_sz--; + if (state&(32|16|8|4)) break; + state |= 8; + } + else if (c == '-' || c == ' ') + { + *fmtOut++ = c; fmtOut_sz--; + if (state&(32|16|8|4)) break; + state |= 16; + } + else if (c >= '0' && c <= '9') + { + *fmtOut++ = c; fmtOut_sz--; + state|=4; + } + else if (c == '{') + { + if (state & 64) break; + state|=64; + if (*fmt == '.' || (*fmt >= '0' && *fmt <= '9')) return 0; // symbol name can't start with 0-9 or . + + while (*fmt != '}') + { + if ((*fmt >= 'a' && *fmt <= 'z') || + (*fmt >= 'A' && *fmt <= 'Z') || + (*fmt >= '0' && *fmt <= '9') || + *fmt == '_' || *fmt == '.' || *fmt == '#') + { + if (varOut_sz < 2) return 0; + *varOut++ = *fmt++; + varOut_sz -- ; + } + else + { + return 0; // bad character in variable name + } + } + fmt++; + *varOut = 0; + *varOut_used=1; + } + else + { + break; + } + } + return 0; +} + +int eel_format_strings(void *opaque, const char *fmt, const char *fmt_end, char *buf, int buf_sz, int num_fmt_parms, EEL_F **fmt_parms) +{ + int fmt_parmpos = 0; + char *op = buf; + while ((fmt_end ? fmt < fmt_end : *fmt) && op < buf+buf_sz-128) + { + if (fmt[0] == '%' && fmt[1] == '%') + { + *op++ = '%'; + fmt+=2; + } + else if (fmt[0] == '%') + { + char ct=0; + char fs[128]; + char varname[128]; + int varname_used=0; + const int l=eel_validate_format_specifier(fmt,&ct,fs,sizeof(fs),varname,sizeof(varname),&varname_used); + if (!l || !ct) + { + *op=0; + return -1; + } + + EEL_F vv=0.0; + const EEL_F *varptr = NULL; + if (varname_used) + { +#ifdef EEL_STRING_GETNAMEDVAR + if (varname[0]) varptr=EEL_STRING_GETNAMEDVAR(varname,0,&vv); +#endif + } + else + { + if (fmt_parmpos < num_fmt_parms) varptr = fmt_parms[fmt_parmpos]; +#ifdef EEL_STRING_GETFMTVAR + if (!varptr) varptr = EEL_STRING_GETFMTVAR(fmt_parmpos); +#endif + fmt_parmpos++; + } + double v = varptr ? (double)*varptr : 0.0; + + if (ct == 's' || ct=='S') + { + EEL_STRING_STORAGECLASS *wr=NULL; + const char *str = EEL_STRING_GET_FOR_INDEX(v,&wr); + const int maxl=(int) (buf+buf_sz - 2 - op); + if (wr && !fs[2]) // %s or %S -- todo: implement padding modes for binary compat too? + { + int wl = wr->GetLength(); + if (wl > maxl) wl=maxl; + memcpy(op,wr->Get(),wl); + op += wl; + *op=0; + } + else + { + snprintf(op,maxl,fs,str ? str : ""); + } + } + else + { + if (varptr == &vv) // passed %{#str}d etc, convert to float + { + const char *str = EEL_STRING_GET_FOR_INDEX(v,NULL); + v = str ? atof(str) : 0.0; + } + + if (ct == 'x' || ct == 'X' || ct == 'd' || ct == 'u' || ct=='i') + { + snprintf(op,64,fs,(int) (v)); + } + else if (ct == 'c') + { + *op++=(char) (int)v; + *op=0; + } + else if (ct == 'C') + { + const unsigned int iv = (unsigned int) v; + int bs = 0; + if (iv & 0xff000000) bs=24; + else if (iv & 0x00ff0000) bs=16; + else if (iv & 0x0000ff00) bs=8; + while (bs>=0) + { + const char c=(char) (iv>>bs); + *op++=c?c:' '; + bs-=8; + } + *op=0; + } + else + { + snprintf(op,64,fs,v); + } + } + + while (*op) op++; + + fmt += l; + } + else + { + *op++ = *fmt++; + } + + } + *op=0; + return (int) (op - buf); +} + + + +static int eel_string_match(void *opaque, const char *fmt, const char *msg, int match_fmt_pos, int ignorecase, const char *fmt_endptr, const char *msg_endptr, int num_fmt_parms, EEL_F **fmt_parms) +{ + // check for match, updating EEL_STRING_GETFMTVAR(*) as necessary + // %d=12345 + // %f=12345[.678] + // %c=any nonzero char, ascii value + // %x=12354ab + // %*, %?, %+, %% literals + // * ? + match minimal groups of 0+,1, or 1+ chars + for (;;) + { + if (fmt>=fmt_endptr) + { + if (msg>=msg_endptr) return 1; + return 0; // format ends before matching string + } + + // if string ends and format is not on a wildcard, early-out to 0 + if (msg>=msg_endptr && *fmt != '*' && *fmt != '%') return 0; + + switch (*fmt) + { + case '*': + case '+': + // if last char of search pattern, we're done! + if (fmt+1>=fmt_endptr || (fmt[1] == '?' && fmt+2>=fmt_endptr)) return *fmt == '*' || msg= 0 && !eel_string_match(opaque,fmt, msg+len,match_fmt_pos,ignorecase,fmt_endptr, msg_endptr,num_fmt_parms,fmt_parms)) len--; + return len >= 0; + } + break; + case '?': + fmt++; + msg++; + break; + case '%': + { + fmt++; + unsigned short fmt_minlen = 1, fmt_maxlen = 0; + if (*fmt >= '0' && *fmt <= '9') + { + fmt_minlen = *fmt++ - '0'; + while (*fmt >= '0' && *fmt <= '9') fmt_minlen = fmt_minlen * 10 + (*fmt++ - '0'); + fmt_maxlen = fmt_minlen; + } + if (*fmt == '-') + { + fmt++; + fmt_maxlen = 0; + while (*fmt >= '0' && *fmt <= '9') fmt_maxlen = fmt_maxlen * 10 + (*fmt++ - '0'); + } + const char *dest_varname=NULL; + if (*fmt == '{') + { + dest_varname=++fmt; + while (*fmt && fmt < fmt_endptr && *fmt != '}') fmt++; + if (fmt >= fmt_endptr-1 || *fmt != '}') return 0; // malformed %{var}s + fmt++; // skip '}' + } + + char fmt_char = *fmt++; + if (!fmt_char) return 0; // malformed + + if (fmt_char == '*' || + fmt_char == '?' || + fmt_char == '+' || + fmt_char == '%') + { + if (*msg++ != fmt_char) return 0; + } + else if (fmt_char == 'c') + { + EEL_F *varOut = NULL; + EEL_F vv=0.0; + if (!dest_varname) + { + if (match_fmt_pos < num_fmt_parms) varOut = fmt_parms[match_fmt_pos]; +#ifdef EEL_STRING_GETFMTVAR + if (!varOut) varOut = EEL_STRING_GETFMTVAR(match_fmt_pos); +#endif + match_fmt_pos++; + } + else + { +#ifdef EEL_STRING_GETNAMEDVAR + char tmp[128]; + int idx=0; + while (dest_varname < fmt_endptr && *dest_varname && *dest_varname != '}' && idx<(int)sizeof(tmp)-1) tmp[idx++] = *dest_varname++; + tmp[idx]=0; + if (idx>0) varOut = EEL_STRING_GETNAMEDVAR(tmp,1,&vv); +#endif + } + if (msg >= msg_endptr) return 0; // out of chars + + if (varOut) + { + if (varOut == &vv) // %{#foo}c + { + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(vv, &wr); + if (wr) wr->Set(msg,1); + } + else + { + *varOut = (EEL_F)*(unsigned char *)msg; + } + } + msg++; + } + else + { + int len=0; + int lazy=0; + if (fmt_char>='A'&&fmt_char<='Z') { lazy=1; fmt_char += 'a' - 'A'; } + + if (fmt_char == 's') + { + len = (int) (msg_endptr-msg); + } + else if (fmt_char == 'x') + { + while ((msg[len] >= '0' && msg[len] <= '9') || + (msg[len] >= 'A' && msg[len] <= 'F') || + (msg[len] >= 'a' && msg[len] <= 'f')) len++; + } + else if (fmt_char == 'f') + { + if (msg[len] == '-') len++; + while (msg[len] >= '0' && msg[len] <= '9') len++; + if (msg[len] == '.') + { + len++; + while (msg[len] >= '0' && msg[len] <= '9') len++; + } + } + else if (fmt_char == 'd' || fmt_char == 'u' || fmt_char == 'i') + { + if (fmt_char != 'u' && msg[len] == '-') len++; + while (msg[len] >= '0' && msg[len] <= '9') len++; + } + else + { + // bad format + return 0; + } + + if (fmt_maxlen>0 && len > fmt_maxlen) len = fmt_maxlen; + + if (!dest_varname) match_fmt_pos++; + + if (lazy) + { + if (fmt_maxlen<1 || fmt_maxlen>len) fmt_maxlen=len; + len=fmt_minlen; + while (len <= fmt_maxlen && !eel_string_match(opaque,fmt, msg+len,match_fmt_pos,ignorecase,fmt_endptr, msg_endptr,num_fmt_parms,fmt_parms)) len++; + if (len > fmt_maxlen) return 0; + } + else + { + while (len >= fmt_minlen && !eel_string_match(opaque,fmt, msg+len,match_fmt_pos,ignorecase,fmt_endptr, msg_endptr,num_fmt_parms,fmt_parms)) len--; + if (len < fmt_minlen) return 0; + } + + EEL_F vv=0.0; + EEL_F *varOut = NULL; + if (!dest_varname) + { + if (match_fmt_pos>0 && match_fmt_pos-1 < num_fmt_parms) varOut = fmt_parms[match_fmt_pos-1]; +#ifdef EEL_STRING_GETFMTVAR + if (!varOut) varOut = EEL_STRING_GETFMTVAR(match_fmt_pos-1); +#endif + } + else + { +#ifdef EEL_STRING_GETNAMEDVAR + char tmp[128]; + int idx=0; + while (dest_varname < fmt_endptr && *dest_varname && *dest_varname != '}' && idx<(int)sizeof(tmp)-1) tmp[idx++] = *dest_varname++; + tmp[idx]=0; + if (idx>0) varOut = EEL_STRING_GETNAMEDVAR(tmp,1,&vv); +#endif + } + if (varOut) + { + if (fmt_char == 's') + { + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(*varOut, &wr); + if (wr) + { + if (msg_endptr >= wr->Get() && msg_endptr <= wr->Get() + wr->GetLength()) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("match: destination specifier passed is also haystack, will not update"); +#endif + } + else if (fmt_endptr >= wr->Get() && fmt_endptr <= wr->Get() + wr->GetLength()) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("match: destination specifier passed is also format, will not update"); +#endif + } + else + { + wr->SetRaw(msg,len); + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("match: bad destination specifier passed as %d: %f",match_fmt_pos,*varOut); +#endif + } + } + else + { + char tmp[128]; + lstrcpyn_safe(tmp,msg,wdl_min(len+1,(int)sizeof(tmp))); + if (varOut == &vv) + { + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(vv, &wr); + if (wr) wr->Set(tmp); + } + else + { + char *bl=(char*)msg; + if (fmt_char == 'u') + *varOut = (EEL_F)strtoul(tmp,&bl,10); + else if (fmt_char == 'x') + *varOut = (EEL_F)strtoul(msg,&bl,16); + else + *varOut = (EEL_F)atof(tmp); + } + } + } + return 1; + } + } + break; + default: + if (ignorecase ? (toupper(*fmt) != toupper(*msg)) : (*fmt!= *msg)) return 0; + fmt++; + msg++; + break; + } + } +} + + + +static EEL_F NSEEL_CGEN_CALL _eel_sprintf(void *opaque, INT_PTR num_param, EEL_F **parms) +{ + if (num_param<2) return 0.0; + + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(*(parms[0]), &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("sprintf: bad destination specifier passed %f",*(parms[0])); +#endif + } + else + { + EEL_STRING_STORAGECLASS *wr_src=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[1]),&wr_src); + if (fmt) + { + char buf[16384]; + const int fmt_len = eel_format_strings(opaque,fmt,wr_src?(fmt+wr_src->GetLength()):NULL,buf,(int)sizeof(buf), (int)num_param-2, parms+2); + + if (fmt_len>=0) + { + wr->SetRaw(buf,fmt_len); + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("sprintf: bad format string %s",fmt); +#endif + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("sprintf: bad format specifier passed %f",*(parms[1])); +#endif + } + } + } + return *(parms[0]); +} + + +static EEL_F NSEEL_CGEN_CALL _eel_strncat(void *opaque, EEL_F *strOut, EEL_F *fmt_index, EEL_F *maxlen) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str%scat: bad destination specifier passed %f",maxlen ? "n":"",*strOut); +#endif + } + else + { + const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr_src); + if (fmt||wr_src) + { + if (wr->GetLength() > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str%scat: will not grow string since it is already %d bytes",maxlen ? "n":"",wr->GetLength()); +#endif + } + else + { + int ml=0; + if (maxlen && *maxlen > 0) ml = (int)*maxlen; + if (wr_src) + { + EEL_STRING_STORAGECLASS tmp; + if (wr_src == wr) *(wr_src=&tmp) = *wr; + wr->AppendRaw(wr_src->Get(), ml > 0 && ml < wr_src->GetLength() ? ml : wr_src->GetLength()); + } + else + wr->Append(fmt, ml); + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str%scat: bad format specifier passed %f",maxlen ? "n":"",*fmt_index); +#endif + } + } + } + return *strOut; +} + +static EEL_F NSEEL_CGEN_CALL _eel_strcpysubstr(void *opaque, INT_PTR nparm, EEL_F **parms) //EEL_F *strOut, EEL_F *fmt_index, EEL_F *offs +{ + if (opaque && nparm>=3) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; + EEL_STRING_GET_FOR_WRITE(parms[0][0], &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("strcpy_substr: bad destination specifier passed %f",parms[0][0]); +#endif + } + else + { + const char *fmt = EEL_STRING_GET_FOR_INDEX(parms[1][0],&wr_src); + if (fmt) + { + const int fmt_len = wr_src ? wr_src->GetLength() : (int) strlen(fmt); + int maxlen, o = (int) parms[2][0]; + if (o < 0) + { + o = fmt_len + o; + if (o < 0) o=0; + } + maxlen = fmt_len - o; + if (nparm >= 4) + { + const int a = (int) parms[3][0]; + if (a<0) maxlen += a; + else if (a= fmt_len) + { + wr->Set(""); + } + else + { + if (wr_src==wr) + { + wr->DeleteSub(0,o); + if (wr->GetLength() > maxlen) wr->SetLen(maxlen); + } + else + { + wr->SetRaw(fmt+o,maxlen); + } + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("strcpy_substr: bad format specifier passed %f",parms[1][0]); +#endif + } + } + return parms[0][0]; + } + return 0.0; +} + + +static EEL_F NSEEL_CGEN_CALL _eel_strncpy(void *opaque, EEL_F *strOut, EEL_F *fmt_index, EEL_F *maxlen) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str%scpy: bad destination specifier passed %f",maxlen ? "n":"",*strOut); +#endif + } + else + { + const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr_src); + if (fmt) + { + int ml=-1; + if (maxlen && *maxlen >= 0) ml = (int)*maxlen; + + if (wr_src == wr) + { + if (ml>=0 && ml < wr->GetLength()) wr->SetLen(ml); // shorten string if strncpy(x,x,len) and len >=0 + return *strOut; + } + + if (wr_src) wr->SetRaw(fmt, ml>0 && ml < wr_src->GetLength() ? ml : wr_src->GetLength()); + else wr->Set(fmt,ml); + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str%scpy: bad format specifier passed %f",maxlen ? "n":"",*fmt_index); +#endif + } + } + } + return *strOut; +} + +static EEL_F _eel_strcmp_int(const char *a, int a_len, const char *b, int b_len, int ml, bool ignorecase) +{ + // binary-safe comparison (at least if a_len>=0 etc) + int pos = 0; + for (;;) + { + if (ml > 0 && pos == ml) return 0.0; + const bool a_end = a_len >= 0 ? pos == a_len : !a[pos]; + const bool b_end = b_len >= 0 ? pos == b_len : !b[pos]; + if (a_end || b_end) + { + if (!b_end) return -1.0; // b[pos] is nonzero, a[pos] is zero + if (!a_end) return 1.0; + return 0.0; + } + char av = a[pos]; + char bv = b[pos]; + if (ignorecase) + { + av=toupper(av); + bv=toupper(bv); + } + if (bv > av) return -1.0; + if (av > bv) return 1.0; + + pos++; + } +} + +static EEL_F NSEEL_CGEN_CALL _eel_strncmp(void *opaque, EEL_F *aa, EEL_F *bb, EEL_F *maxlen) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr_a=NULL,*wr_b=NULL; + const char *a = EEL_STRING_GET_FOR_INDEX(*aa,&wr_a); + const char *b = EEL_STRING_GET_FOR_INDEX(*bb,&wr_b); + if (!a || !b) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str%scmp: bad specifier(s) passed %f/%f",maxlen ? "n" : "",*aa,*bb); +#endif + } + else + { + const int ml = maxlen ? (int) *maxlen : -1; + if (!ml || a==b) return 0; // strncmp(x,y,0) == 0 + + return _eel_strcmp_int(a,wr_a ? wr_a->GetLength() : -1,b,wr_b ? wr_b->GetLength() : -1, ml, false); + } + } + return -1.0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_strnicmp(void *opaque, EEL_F *aa, EEL_F *bb, EEL_F *maxlen) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr_a=NULL,*wr_b=NULL; + const char *a = EEL_STRING_GET_FOR_INDEX(*aa,&wr_a); + const char *b = EEL_STRING_GET_FOR_INDEX(*bb,&wr_b); + if (!a || !b) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str%sicmp: bad specifier(s) passed %f/%f",maxlen ? "n" : "",*aa,*bb); +#endif + } + else + { + const int ml = maxlen ? (int) *maxlen : -1; + if (!ml || a==b) return 0; // strncmp(x,y,0) == 0 + return _eel_strcmp_int(a,wr_a ? wr_a->GetLength() : -1,b,wr_b ? wr_b->GetLength() : -1, ml, true); + } + } + return -1.0; +} + + +static EEL_F NSEEL_CGEN_CALL _eel_strcat(void *opaque, EEL_F *strOut, EEL_F *fmt_index) +{ + return _eel_strncat(opaque,strOut,fmt_index,NULL); +} + +static EEL_F NSEEL_CGEN_CALL _eel_strcpy(void *opaque, EEL_F *strOut, EEL_F *fmt_index) +{ + return _eel_strncpy(opaque,strOut,fmt_index,NULL); +} + + +static EEL_F NSEEL_CGEN_CALL _eel_strcmp(void *opaque, EEL_F *strOut, EEL_F *fmt_index) +{ + return _eel_strncmp(opaque,strOut,fmt_index,NULL); +} + +static EEL_F NSEEL_CGEN_CALL _eel_stricmp(void *opaque, EEL_F *strOut, EEL_F *fmt_index) +{ + return _eel_strnicmp(opaque,strOut,fmt_index,NULL); +} + + +static EEL_F NSEEL_CGEN_CALL _eel_strgetchar(void *opaque, EEL_F *strOut, EEL_F *idx) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(*strOut, &wr); + if (!fmt) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_getchar: bad specifier passed %f",*strOut); +#endif + } + else + { + const int wl=(wr?wr->GetLength():(int)strlen(fmt)); + int l = (int) *idx; + if (*idx < 0.0) l+=wl; + if (l>=0 && l < wl) return ((unsigned char *)fmt)[l]; + } + } + return 0; +} + +#define EEL_GETCHAR_FLAG_ENDIANSWAP 0x10 +#define EEL_GETCHAR_FLAG_UNSIGNED 0x20 +#define EEL_GETCHAR_FLAG_FLOAT 0x40 +static int eel_getchar_flag(int type) +{ +#ifdef __ppc__ + int ret=EEL_GETCHAR_FLAG_ENDIANSWAP; // default to LE +#else + int ret=0; +#endif + + if (toupper((type>>8)&0xff) == 'U') ret|=EEL_GETCHAR_FLAG_UNSIGNED; + else if (type>255 && toupper(type&0xff) == 'U') { ret|=EEL_GETCHAR_FLAG_UNSIGNED; type>>=8; } + type&=0xff; + + if (isupper(type)) ret^=EEL_GETCHAR_FLAG_ENDIANSWAP; + else type += 'A'-'a'; + + switch (type) + { + case 'F': return ret|4|EEL_GETCHAR_FLAG_FLOAT; + case 'D': return ret|8|EEL_GETCHAR_FLAG_FLOAT; + case 'S': return ret|2; + case 'I': return ret|4; + } + + return ret|1; +} + +static void eel_setchar_do(int flag, char *dest, EEL_F val) +{ + union + { + char buf[8]; + float asFloat; + double asDouble; + int asInt; + short asShort; + char asChar; + unsigned int asUInt; + unsigned short asUShort; + unsigned char asUChar; + } a; + const int type_sz=flag&0xf; + + if (flag & EEL_GETCHAR_FLAG_FLOAT) + { + if (type_sz==8) a.asDouble=val; + else a.asFloat=(float)val; + } + else if (flag & EEL_GETCHAR_FLAG_UNSIGNED) + { + if (type_sz==4) a.asUInt=(unsigned int)val; + else if (type_sz==2) a.asUShort=(unsigned short)val; + else a.asUChar=(unsigned char)val; + } + else if (type_sz==4) a.asInt=(int)val; + else if (type_sz==2) a.asShort=(short)val; + else a.asChar=(char)val; + + if (flag & EEL_GETCHAR_FLAG_ENDIANSWAP) + { + dest += type_sz; + int x; + for(x=0;x=3) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(parms[0][0], &wr); + if (!fmt) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_getchar: bad specifier passed %f",parms[0][0]); +#endif + } + else + { + const int wl=(wr?wr->GetLength():(int)strlen(fmt)); + const int flags=eel_getchar_flag((int) parms[2][0]); + int l = (int) parms[1][0]; + if (parms[1][0] < 0.0) l+=wl; + + if (l>=0 && l <= wl-(flags&0xf)) + { + return eel_getchar_do(flags,fmt+l); + } + } + } + return 0; +} + +static EEL_F NSEEL_CGEN_CALL _eel_strsetchar2(void *opaque, INT_PTR np, EEL_F **parms) +{ + if (opaque && np>=4) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(parms[0][0], &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_setchar: bad destination specifier passed %f",parms[0][0]); +#endif + } + else + { + const int wl=wr->GetLength(); + + int l = (int) parms[1][0]; + if (parms[1][0] < 0.0) l+=wl; + if (l>=0 && l <= wl) + { + const int flags=eel_getchar_flag((int) parms[3][0]); + if (l==wl) + { + if (wl > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_setchar: will not grow string since it is already %d bytes",wl); +#endif + } + else + { + char buf[32]; + eel_setchar_do(flags,buf,parms[2][0]); + wr->AppendRaw(buf,flags&0xf); + } + } + else + eel_setchar_do(flags,(char*)wr->Get()+l,parms[2][0]); + } + } + } + return parms[0][0]; +} + +static EEL_F NSEEL_CGEN_CALL _eel_strsetchar(void *opaque, EEL_F *strOut, EEL_F *idx, EEL_F *val) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_setchar: bad destination specifier passed %f",*strOut); +#endif + } + else + { + const int wl=wr->GetLength(); + int l = (int) *idx; + if (*idx < 0.0) l+=wl; + if (l>=0 && l <= wl) + { + const unsigned char v=((int)*val)&255; + if (l==wl) + { + if (wl > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_setchar: will not grow string since it is already %d bytes",wl); +#endif + } + else + wr->AppendRaw((const char*)&v,1); + } + else + ((unsigned char *)wr->Get())[l]=v; // allow putting nulls in string, strlen() will still get the full size + } + } + } + return *strOut; +} + +static EEL_F NSEEL_CGEN_CALL _eel_strinsert(void *opaque, EEL_F *strOut, EEL_F *fmt_index, EEL_F *pos) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL, *wr_src=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_insert: bad destination specifier passed %f",*strOut); +#endif + } + else + { + const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr_src); + if (fmt) + { + EEL_STRING_STORAGECLASS tmp; + if (wr_src == wr) *(wr_src=&tmp) = *wr; // insert from copy + + // if wr_src, fmt is guaranteed to be wr_src.Get() + int p = (int)*pos; + int insert_l = wr_src ? wr_src->GetLength() : (int)strlen(fmt); + if (p < 0) + { + insert_l += p; // decrease insert_l + fmt -= p; // advance fmt -- if fmt gets advanced past NULL term, insert_l will be < 0 anyway + p=0; + } + + if (insert_l>0) + { + if (wr->GetLength() > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_insert: will not grow string since it is already %d bytes",wr->GetLength()); +#endif + } + else + { + if (wr_src) + { + wr->InsertRaw(fmt,p, insert_l); + } + else + { + wr->Insert(fmt,p); + } + } + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_insert: bad source specifier passed %f",*fmt_index); +#endif + } + } + } + return *strOut; +} + +static EEL_F NSEEL_CGEN_CALL _eel_strdelsub(void *opaque, EEL_F *strOut, EEL_F *pos, EEL_F *len) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_delsub: bad destination specifier passed %f",*strOut); +#endif + } + else + { + int p=(int)*pos; + int l=(int)*len; + if (p<0) + { + l+=p; + p=0; + } + if (l>0) + wr->DeleteSub(p,l); + } + } + return *strOut; +} + +static EEL_F NSEEL_CGEN_CALL _eel_strsetlen(void *opaque, EEL_F *strOut, EEL_F *newlen) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + EEL_STRING_GET_FOR_WRITE(*strOut, &wr); + if (!wr) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_setlen: bad destination specifier passed %f",*strOut); +#endif + } + else + { + int l = (int) *newlen; + if (l < 0) l=0; + if (l > EEL_STRING_MAXUSERSTRING_LENGTH_HINT) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("str_setlen: clamping requested length of %d to %d",l,EEL_STRING_MAXUSERSTRING_LENGTH_HINT); +#endif + l=EEL_STRING_MAXUSERSTRING_LENGTH_HINT; + } + wr->SetLen(l); + + } + } + return *strOut; +} + + +static EEL_F NSEEL_CGEN_CALL _eel_strlen(void *opaque, EEL_F *fmt_index) +{ + if (opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(*fmt_index,&wr); + if (wr) return (EEL_F) wr->GetLength(); + if (fmt) return (EEL_F) strlen(fmt); + } + return 0.0; +} + + + + +static EEL_F NSEEL_CGEN_CALL _eel_printf(void *opaque, INT_PTR num_param, EEL_F **parms) +{ + if (num_param>0 && opaque) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *wr_src=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[0]),&wr_src); + if (fmt) + { + char buf[16384]; + const int len = eel_format_strings(opaque,fmt,wr_src?(fmt+wr_src->GetLength()):NULL,buf,(int)sizeof(buf), (int)num_param-1, parms+1); + + if (len >= 0) + { +#ifdef EEL_STRING_STDOUT_WRITE + EEL_STRING_STDOUT_WRITE(buf,len); +#endif + return 1.0; + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("printf: bad format string %s",fmt); +#endif + } + } + else + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("printf: bad format specifier passed %f",*(parms[0])); +#endif + } + } + return 0.0; +} + + + +static EEL_F NSEEL_CGEN_CALL _eel_match(void *opaque, INT_PTR num_parms, EEL_F **parms) +{ + if (opaque && num_parms >= 2) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *fmt_wr=NULL, *msg_wr=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[0]),&fmt_wr); + const char *msg = EEL_STRING_GET_FOR_INDEX(*(parms[1]),&msg_wr); + + if (fmt && msg) return eel_string_match(opaque,fmt,msg,0,0, fmt + (fmt_wr?fmt_wr->GetLength():strlen(fmt)), msg + (msg_wr?msg_wr->GetLength():strlen(msg)),(int)num_parms-2,parms+2) ? 1.0 : 0.0; + } + return 0.0; +} +static EEL_F NSEEL_CGEN_CALL _eel_matchi(void *opaque, INT_PTR num_parms, EEL_F **parms) +{ + if (opaque && num_parms >= 2) + { + EEL_STRING_MUTEXLOCK_SCOPE + EEL_STRING_STORAGECLASS *fmt_wr=NULL, *msg_wr=NULL; + const char *fmt = EEL_STRING_GET_FOR_INDEX(*(parms[0]),&fmt_wr); + const char *msg = EEL_STRING_GET_FOR_INDEX(*(parms[1]),&msg_wr); + + if (fmt && msg) return eel_string_match(opaque,fmt,msg,0,1, fmt + (fmt_wr?fmt_wr->GetLength():strlen(fmt)), msg + (msg_wr?msg_wr->GetLength():strlen(msg)),(int)num_parms-2,parms+2) ? 1.0 : 0.0; + } + return 0.0; +} + +void EEL_string_register() +{ + NSEEL_addfunc_retval("strlen",1,NSEEL_PProc_THIS,&_eel_strlen); + + NSEEL_addfunc_retval("strcat",2,NSEEL_PProc_THIS,&_eel_strcat); + NSEEL_addfunc_retval("strcpy",2,NSEEL_PProc_THIS,&_eel_strcpy); + NSEEL_addfunc_retval("strcmp",2,NSEEL_PProc_THIS,&_eel_strcmp); + NSEEL_addfunc_retval("stricmp",2,NSEEL_PProc_THIS,&_eel_stricmp); + + NSEEL_addfunc_retval("strncat",3,NSEEL_PProc_THIS,&_eel_strncat); + NSEEL_addfunc_retval("strncpy",3,NSEEL_PProc_THIS,&_eel_strncpy); + NSEEL_addfunc_retval("strncmp",3,NSEEL_PProc_THIS,&_eel_strncmp); + NSEEL_addfunc_retval("strnicmp",3,NSEEL_PProc_THIS,&_eel_strnicmp); + NSEEL_addfunc_retval("str_setlen",2,NSEEL_PProc_THIS,&_eel_strsetlen); + NSEEL_addfunc_exparms("strcpy_from",3,NSEEL_PProc_THIS,&_eel_strcpysubstr); // alias + NSEEL_addfunc_exparms("strcpy_substr",3,NSEEL_PProc_THIS,&_eel_strcpysubstr); // only allow 3 or 4 parms + NSEEL_addfunc_exparms("strcpy_substr",4,NSEEL_PProc_THIS,&_eel_strcpysubstr); + + NSEEL_addfunc_retval("str_getchar",2,NSEEL_PProc_THIS,&_eel_strgetchar); + NSEEL_addfunc_retval("str_setchar",3,NSEEL_PProc_THIS,&_eel_strsetchar); + NSEEL_addfunc_exparms("str_getchar",3,NSEEL_PProc_THIS,&_eel_strgetchar2); + NSEEL_addfunc_exparms("str_setchar",4,NSEEL_PProc_THIS,&_eel_strsetchar2); + + NSEEL_addfunc_retval("str_insert",3,NSEEL_PProc_THIS,&_eel_strinsert); + NSEEL_addfunc_retval("str_delsub",3,NSEEL_PProc_THIS,&_eel_strdelsub); + + NSEEL_addfunc_varparm("sprintf",2,NSEEL_PProc_THIS,&_eel_sprintf); + NSEEL_addfunc_varparm("printf",1,NSEEL_PProc_THIS,&_eel_printf); + + NSEEL_addfunc_varparm("match",2,NSEEL_PProc_THIS,&_eel_match); + NSEEL_addfunc_varparm("matchi",2,NSEEL_PProc_THIS,&_eel_matchi); + +} +void eel_string_initvm(NSEEL_VMCTX vm) +{ + NSEEL_VM_SetStringFunc(vm, eel_string_context_state::addStringCallback, eel_string_context_state::addNamedStringCallback); +} + +#ifdef EEL_WANT_DOCUMENTATION +static const char *eel_strings_function_reference = +#ifdef EEL_STRING_STDOUT_WRITE +"printf\t\"format\"[, ...]\tOutput formatted string to system-specific destination, see sprintf() for more information\0" +#endif +"sprintf\t#dest,\"format\"[, ...]\tFormats a string and stores it in #dest. Format specifiers begin with %, and may include:\3\n" + "\4 %% = %\n" + "\4 %s = string from parameter\n" + "\4 %d = parameter as integer\n" + "\4 %i = parameter as integer\n" + "\4 %u = parameter as unsigned integer\n" + "\4 %x = parameter as hex (lowercase) integer\n" + "\4 %X = parameter as hex (uppercase) integer\n" + "\4 %c = parameter as character\n" + "\4 %f = parameter as floating point\n" + "\4 %e = parameter as floating point (scientific notation, lowercase)\n" + "\4 %E = parameter as floating point (scientific notation, uppercase)\n" + "\4 %g = parameter as floating point (shortest representation, lowercase)\n" + "\4 %G = parameter as floating point (shortest representation, uppercase)\n" + "\2\n" + "Many standard C printf() modifiers can be used, including:\3\n" + "\4 %.10s = string, but only print up to 10 characters\n" + "\4 %-10s = string, left justified to 10 characters\n" + "\4 %10s = string, right justified to 10 characters\n" + "\4 %+f = floating point, always show sign\n" + "\4 %.4f = floating point, minimum of 4 digits after decimal point\n" + "\4 %10d = integer, minimum of 10 digits (space padded)\n" + "\4 %010f = integer, minimum of 10 digits (zero padded)\n" + "\2\n" + "Values for format specifiers can be specified as additional parameters to sprintf, or within {} in the format specifier (such as %{varname}d, in that case a global variable is always used).\0" + + "matchi\t\"needle\",\"haystack\"[, ...]\tCase-insensitive version of match().\0" + "match\t\"needle\",\"haystack\"[, ...]\tSearches for the first parameter in the second parameter, using a simplified regular expression syntax.\3\n" + "\4* = match 0 or more characters\n" + "\4*? = match 0 or more characters, lazy\n" + "\4+ = match 1 or more characters\n" + "\4+? = match 1 or more characters, lazy\n" + "\4? = match one character\n" + "\2\n" + "You can also use format specifiers to match certain types of data, and optionally put that into a variable:\3\n" + "\4%s means 1 or more chars\n" + "\4%0s means 0 or more chars\n" + "\4%5s means exactly 5 chars\n" + "\4%5-s means 5 or more chars\n" + "\4%-10s means 1-10 chars\n" + "\4%3-5s means 3-5 chars\n" + "\4%0-5s means 0-5 chars\n" + "\4%x, %d, %u, and %f are available for use similarly\n" + "\4%c can be used, but can't take any length modifiers\n" + "\4Use uppercase (%S, %D, etc) for lazy matching\n" + "\2\n" + "See also sprintf() for other notes, including specifying direct variable references via {}.\0" + + "strlen\t\"str\"\tReturns the length of the string passed as a parameter\0" + "strcpy\t#str,\"srcstr\"\tCopies the contents of srcstr to #str, and returns #str\0" + "strcat\t#str,\"srcstr\"\tAppends srcstr to #str, and returns #str\0" + "strcmp\t\"str\",\"str2\"\tCompares strings, returning 0 if equal\0" + "stricmp\t\"str\",\"str2\"\tCompares strings ignoring case, returning 0 if equal\0" + "strncmp\t\"str\",\"str2\",maxlen\tCompares strings giving up after maxlen characters, returning 0 if equal\0" + "strnicmp\t\"str\",\"str2\",maxlen\tCompares strings giving up after maxlen characters, ignoring case, returning 0 if equal\0" + "strncpy\t#str,\"srcstr\",maxlen\tCopies srcstr to #str, stopping after maxlen characters. Returns #str.\0" + "strncat\t#str,\"srcstr\",maxlen\tAppends srcstr to #str, stopping after maxlen characters of srcstr. Returns #str.\0" + "strcpy_from\t#str,\"srcstr\",offset\tCopies srcstr to #str, but starts reading srcstr at offset offset\0" + "strcpy_substr\t#str,\"srcstr\",offs,ml)\tPHP-style (start at offs, offs<0 means from end, ml for maxlen, ml<0 = reduce length by this amt)\0" + "str_getchar\t\"str\",offset[,type]\tReturns the data at byte-offset offset of str. If offset is negative, position is relative to end of string." + "type defaults to signed char, but can be specified to read raw binary data in other formats (note the single quotes, these are single/multi-byte characters):\3\n" + "\4'c' - signed char\n" + "\4'cu' - unsigned char\n" + "\4's' - signed short\n" + "\4'S' - signed short, big endian\n" + "\4'su' - unsigned short\n" + "\4'Su' - unsigned short, big endian\n" + "\4'i' - signed int\n" + "\4'I' - signed int, big endian\n" + "\4'iu' - unsigned int\n" + "\4'Iu' - unsigned int, big endian\n" + "\4'f' - float\n" + "\4'F' - float, big endian\n" + "\4'd' - double\n" + "\4'D' - double, big endian\n" + "\2\0" + "str_setchar\t#str,offset,val[,type])\tSets value at offset offset, type optional. offset may be negative to refer to offset relative to end of string, or between 0 and length, inclusive, and if set to length it will lengthen string. See str_getchar() for more information on types.\0" + "str_setlen\t#str,len\tSets length of #str (if increasing, will be space-padded), and returns #str.\0" + "str_delsub\t#str,pos,len\tDeletes len characters at offset pos from #str, and returns #str.\0" + "str_insert\t#str,\"srcstr\",pos\tInserts srcstr into #str at offset pos. Returns #str\0" +; +#endif + + +#endif + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eelscript.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eelscript.h new file mode 100644 index 0000000..8d9e66a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/eelscript.h @@ -0,0 +1,834 @@ +#ifndef _WIN32 +#include + #ifndef EELSCRIPT_NO_LICE + #include "../swell/swell.h" + #endif +#endif + +#include "../wdltypes.h" +#include "../ptrlist.h" +#include "../wdlstring.h" +#include "../assocarray.h" +#include "../queue.h" +#include "../mutex.h" +#include "../win32_utf8.h" +#include "ns-eel.h" + + +#ifndef EELSCRIPT_MAX_FILE_HANDLES +#define EELSCRIPT_MAX_FILE_HANDLES 512 +#endif +#ifndef EELSCRIPT_FILE_HANDLE_INDEX_BASE +#define EELSCRIPT_FILE_HANDLE_INDEX_BASE 1000000 +#endif +#ifndef EEL_STRING_MAXUSERSTRING_LENGTH_HINT +#define EEL_STRING_MAXUSERSTRING_LENGTH_HINT (1<<16) // 64KB per string max +#endif +#ifndef EEL_STRING_MAX_USER_STRINGS +#define EEL_STRING_MAX_USER_STRINGS 32768 +#endif +#ifndef EEL_STRING_LITERAL_BASE +#define EEL_STRING_LITERAL_BASE 2000000 +#endif +#ifndef EELSCRIPT_LICE_MAX_IMAGES +#define EELSCRIPT_LICE_MAX_IMAGES 1024 +#endif + +#ifndef EELSCRIPT_LICE_MAX_FONTS +#define EELSCRIPT_LICE_MAX_FONTS 128 +#endif + +#ifndef EELSCRIPT_NET_MAXCON +#define EELSCRIPT_NET_MAXCON 4096 +#endif + +#ifndef EELSCRIPT_LICE_CLASSNAME +#define EELSCRIPT_LICE_CLASSNAME "eelscript_gfx" +#endif + + +// #define EELSCRIPT_NO_NET +// #define EELSCRIPT_NO_LICE +// #define EELSCRIPT_NO_FILE +// #define EELSCRIPT_NO_FFT +// #define EELSCRIPT_NO_MDCT +// #define EELSCRIPT_NO_EVAL + +class eel_string_context_state; +#ifndef EELSCRIPT_NO_NET +class eel_net_state; +#endif +#ifndef EELSCRIPT_NO_LICE +class eel_lice_state; +#endif + +#ifndef EELSCRIPT_NO_PREPROC +#include "eel_pproc.h" +#endif + +class eelScriptInst { + public: + + static int init(); + + eelScriptInst(); + virtual ~eelScriptInst(); + + NSEEL_CODEHANDLE compile_code(const char *code, const char **err); + int runcode(const char *code, int showerr, const char *showerrfn, bool canfree, bool ignoreEndOfInputChk, bool doExec); + int loadfile(const char *fn, const char *callerfn, bool allowstdin); + + NSEEL_VMCTX m_vm; + + WDL_PtrList m_code_freelist; + +#ifndef EELSCRIPT_NO_FILE + FILE *m_handles[EELSCRIPT_MAX_FILE_HANDLES]; + virtual EEL_F OpenFile(const char *fn, const char *mode) + { + if (!*fn || !*mode) return 0.0; +#ifndef EELSCRIPT_NO_STDIO + if (!strcmp(fn,"stdin")) return 1; + if (!strcmp(fn,"stdout")) return 2; + if (!strcmp(fn,"stderr")) return 3; +#endif + + WDL_FastString fnstr(fn); + if (!translateFilename(&fnstr,mode)) return 0.0; + + int x; + for (x=0;x= EELSCRIPT_MAX_FILE_HANDLES) return 0.0; + + FILE *fp = fopenUTF8(fnstr.Get(),mode); + if (!fp) return 0.0; + m_handles[x]=fp; + return x + EELSCRIPT_FILE_HANDLE_INDEX_BASE; + } + virtual EEL_F CloseFile(int fp_idx) + { + fp_idx-=EELSCRIPT_FILE_HANDLE_INDEX_BASE; + if (fp_idx>=0 && fp_idx=0 && fp_idx m_eval_cache; + virtual char *evalCacheGet(const char *str, NSEEL_CODEHANDLE *ch); + virtual void evalCacheDispose(char *key, NSEEL_CODEHANDLE ch); + WDL_Queue m_defer_eval, m_atexit_eval; + void runCodeQ(WDL_Queue *q, const char *fname); + void runAtExitCode() + { + runCodeQ(&m_atexit_eval,"atexit"); + m_atexit_eval.Clear(); // make sure nothing gets added in atexit(), in case the user called runAtExitCode before destroying + } +#endif + virtual bool run_deferred(); // requires eval support to be useful + virtual bool has_deferred(); + + + WDL_StringKeyedArray m_loaded_fnlist; // imported file list (to avoid repeats) + +#ifndef EELSCRIPT_NO_PREPROC + EEL2_PreProcessor m_preproc; +#endif +}; + +//#define EEL_STRINGS_MUTABLE_LITERALS +//#define EEL_STRING_WANT_MUTEX + + +#define EEL_STRING_GET_CONTEXT_POINTER(opaque) (((eelScriptInst *)opaque)->m_string_context) +#ifndef EEL_STRING_STDOUT_WRITE + #ifndef EELSCRIPT_NO_STDIO + #define EEL_STRING_STDOUT_WRITE(x,len) { fwrite(x,len,1,stdout); fflush(stdout); } + #endif +#endif +#include "eel_strings.h" + +#include "eel_misc.h" + + +#ifndef EELSCRIPT_NO_FILE + #define EEL_FILE_OPEN(fn,mode) ((eelScriptInst*)opaque)->OpenFile(fn,mode) + #define EEL_FILE_GETFP(fp) ((eelScriptInst*)opaque)->GetFileFP(fp) + #define EEL_FILE_CLOSE(fpindex) ((eelScriptInst*)opaque)->CloseFile(fpindex) + + #include "eel_files.h" +#endif + +#ifndef EELSCRIPT_NO_FFT + #include "eel_fft.h" +#endif + +#ifndef EELSCRIPT_NO_MDCT + #include "eel_mdct.h" +#endif + +#ifndef EELSCRIPT_NO_NET + #define EEL_NET_GET_CONTEXT(opaque) (((eelScriptInst *)opaque)->m_net_state) + #include "eel_net.h" +#endif + +#ifndef EELSCRIPT_NO_LICE + #ifndef EEL_LICE_WANT_STANDALONE + #define EEL_LICE_WANT_STANDALONE + #endif + #ifndef EELSCRIPT_LICE_NOUPDATE + #define EEL_LICE_WANT_STANDALONE_UPDATE // gfx_update() which runs message pump and updates screen etc + #endif + + #define EEL_LICE_GET_FILENAME_FOR_STRING(idx, fs, p) (((eelScriptInst*)opaque)->GetFilenameForParameter(idx,fs,p)) + #define EEL_LICE_GET_CONTEXT(opaque) ((opaque) ? (((eelScriptInst *)opaque)->m_gfx_state) : NULL) + #include "eel_lice.h" +#endif + +#ifndef EELSCRIPT_NO_EVAL + #define EEL_EVAL_GET_CACHED(str, ch) ((eelScriptInst *)opaque)->evalCacheGet(str,&(ch)) + #define EEL_EVAL_SET_CACHED(str, ch) ((eelScriptInst *)opaque)->evalCacheDispose(str,ch) + #define EEL_EVAL_GET_VMCTX(opaque) (((eelScriptInst *)opaque)->m_vm) + #define EEL_EVAL_SCOPE_ENTER (((eelScriptInst *)opaque)->m_eval_depth < 3 ? \ + ++((eelScriptInst *)opaque)->m_eval_depth : 0) + #define EEL_EVAL_SCOPE_LEAVE ((eelScriptInst *)opaque)->m_eval_depth--; + #include "eel_eval.h" + +static EEL_F NSEEL_CGEN_CALL _eel_defer(void *opaque, EEL_F *s) +{ + EEL_STRING_MUTEXLOCK_SCOPE + const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); + if (str && *str && *s >= EEL_STRING_MAX_USER_STRINGS) // don't allow defer(0) etc + { + eelScriptInst *inst = (eelScriptInst *)opaque; + if (inst->m_defer_eval.Available() < EEL_STRING_MAXUSERSTRING_LENGTH_HINT) + { + inst->m_defer_eval.Add(str,strlen(str)+1); + return 1.0; + } +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("defer(): too much defer() code already added, ignoring"); +#endif + } +#ifdef EEL_STRING_DEBUGOUT + else if (!str) + { + EEL_STRING_DEBUGOUT("defer(): invalid string identifier specified %f",*s); + } + else if (*s < EEL_STRING_MAX_USER_STRINGS) + { + EEL_STRING_DEBUGOUT("defer(): user string identifier %f specified but not allowed",*s); + } +#endif + return 0.0; +} +static EEL_F NSEEL_CGEN_CALL _eel_atexit(void *opaque, EEL_F *s) +{ + EEL_STRING_MUTEXLOCK_SCOPE + const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); + if (str && *str && *s >= EEL_STRING_MAX_USER_STRINGS) // don't allow atexit(0) etc + { + eelScriptInst *inst = (eelScriptInst *)opaque; + if (inst->m_atexit_eval.Available() < EEL_STRING_MAXUSERSTRING_LENGTH_HINT) + { + inst->m_atexit_eval.Add(str,strlen(str)+1); + return 1.0; + } +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("atexit(): too much atexit() code already added, ignoring"); +#endif + } +#ifdef EEL_STRING_DEBUGOUT + else if (!str) + { + EEL_STRING_DEBUGOUT("atexit(): invalid string identifier specified %f",*s); + } + else if (*s < EEL_STRING_MAX_USER_STRINGS) + { + EEL_STRING_DEBUGOUT("atexit(): user string identifier %f specified but not allowed",*s); + } +#endif + return 0.0; +} +#endif + + +#define opaque ((void *)this) + +eelScriptInst::eelScriptInst() : m_loaded_fnlist(false) +{ +#ifndef EELSCRIPT_NO_FILE + memset(m_handles,0,sizeof(m_handles)); +#endif + m_vm = NSEEL_VM_alloc(); +#ifdef EEL_STRING_DEBUGOUT + if (!m_vm) EEL_STRING_DEBUGOUT("NSEEL_VM_alloc(): failed"); +#endif + NSEEL_VM_SetCustomFuncThis(m_vm,this); +#ifdef NSEEL_ADDFUNC_DESTINATION + NSEEL_VM_SetFunctionTable(m_vm,NSEEL_ADDFUNC_DESTINATION); +#endif + + m_string_context = new eel_string_context_state; + eel_string_initvm(m_vm); +#ifndef EELSCRIPT_NO_NET + m_net_state = new eel_net_state(EELSCRIPT_NET_MAXCON,NULL); +#endif +#ifndef EELSCRIPT_NO_LICE + m_gfx_state = new eel_lice_state(m_vm,this,EELSCRIPT_LICE_MAX_IMAGES,EELSCRIPT_LICE_MAX_FONTS); + + m_gfx_state->resetVarsToStock(); +#endif +#ifndef EELSCRIPT_NO_EVAL + m_eval_depth=0; +#endif +} + +eelScriptInst::~eelScriptInst() +{ +#ifndef EELSCRIPT_NO_EVAL + if (m_atexit_eval.GetSize()>0) runAtExitCode(); +#endif + int x; + m_code_freelist.Empty((void (*)(void *))NSEEL_code_free); +#ifndef EELSCRIPT_NO_EVAL + for (x=0;xSet(fmt); + return translateFilename(fs,iswrite?"w":"r"); +} + +NSEEL_CODEHANDLE eelScriptInst::compile_code(const char *code, const char **err) +{ + if (!m_vm) + { + *err = "EEL VM not initialized"; + return NULL; + } + +#ifndef EELSCRIPT_NO_PREPROC + WDL_FastString str; + if (strstr(code,EEL2_PREPROCESS_OPEN_TOKEN)) + { + const char *pperr = m_preproc.preprocess(code,&str); + if (pperr) + { + *err = pperr; + return NULL; + } + code = str.Get(); + } + else + m_preproc.clear_line_info(); +#endif + + NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(m_vm, code, 0, NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); + if (ch) + { + m_string_context->update_named_vars(m_vm); + m_code_freelist.Add((void*)ch); + return ch; + } + *err = NSEEL_code_getcodeerror(m_vm); +#ifndef EELSCRIPT_NO_PREPROC + if (*err) *err = m_preproc.translate_error_line(*err); +#endif + return NULL; +} + +int eelScriptInst::runcode(const char *codeptr, int showerr, const char *showerrfn, bool canfree, bool ignoreEndOfInputChk, bool doExec) +{ + if (m_vm) + { + const char *err = NULL; + NSEEL_CODEHANDLE code = NULL; +#ifndef EELSCRIPT_NO_PREPROC + WDL_FastString str; + if (strstr(codeptr,EEL2_PREPROCESS_OPEN_TOKEN)) + { + err = m_preproc.preprocess(codeptr,&str); + if (err) goto on_preproc_error; + codeptr = str.Get(); + } + else + m_preproc.clear_line_info(); +#endif + + code = NSEEL_code_compile_ex(m_vm,codeptr,0,canfree ? 0 : NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); + if (code) m_string_context->update_named_vars(m_vm); + + if (!code && (err=NSEEL_code_getcodeerror(m_vm))) + { + if (!ignoreEndOfInputChk && (NSEEL_code_geterror_flag(m_vm)&1)) return 1; +#ifndef EELSCRIPT_NO_PREPROC + err = m_preproc.translate_error_line(err); +on_preproc_error: +#endif + if (showerr) + { +#ifdef EEL_STRING_DEBUGOUT + if (showerr==2) + { + EEL_STRING_DEBUGOUT("Warning: %s:%s",WDL_get_filepart(showerrfn),err); + } + else + { + EEL_STRING_DEBUGOUT("%s:%s",WDL_get_filepart(showerrfn),err); + } +#endif + } + return -1; + } + else + { + if (code) + { +#ifdef EELSCRIPT_DO_DISASSEMBLE + codeHandleType *p = (codeHandleType*)code; + + char buf[512]; + buf[0]=0; +#ifdef _WIN32 + GetTempPath(sizeof(buf)-64,buf); + lstrcatn(buf,"jsfx-out",sizeof(buf)); +#else + lstrcpyn_safe(buf,"/tmp/jsfx-out",sizeof(buf)); +#endif + FILE *fp = fopenUTF8(buf,"wb"); + if (fp) + { + fwrite(p->code,1,p->code_size,fp); + fclose(fp); + char buf2[2048]; +#ifdef _WIN32 + snprintf(buf2,sizeof(buf2),"disasm \"%s\"",buf); +#else + #ifdef __aarch64__ + snprintf(buf2,sizeof(buf2), "objdump -D -b binary -maarch64 \"%s\"",buf); + #elif defined(__arm__) + snprintf(buf2,sizeof(buf2), "objdump -D -b binary -m arm \"%s\"",buf); + #elif defined(__LP64__) + #ifdef __APPLE__ + snprintf(buf2,sizeof(buf2),"distorm3 --b64 \"%s\"",buf); + #else + snprintf(buf2,sizeof(buf2),"objdump -D -b binary -m i386:x86-64 \"%s\"",buf); + #endif + #else + snprintf(buf2,sizeof(buf2),"distorm3 --b32 \"%s\"",buf); + #endif +#endif + system(buf2); + } +#endif + + if (doExec) NSEEL_code_execute(code); + if (canfree) NSEEL_code_free(code); + else m_code_freelist.Add((void*)code); + } + return 0; + } + } + return -1; +} + + +FILE *eelscript_resolvePath(WDL_FastString &usefn, const char *fn, const char *callerfn) +{ + // resolve path relative to current + int x; + bool had_abs=false; + for (x=0;x<2; x ++) + { +#ifdef _WIN32 + if (!x && ((fn[0] == '\\' && fn[1] == '\\') || (fn[0] && fn[1] == ':'))) +#else + if (!x && fn[0] == '/') +#endif + { + usefn.Set(fn); + had_abs=true; + } + else + { + const char *fnu = fn; + if (x) + { + while (*fnu) fnu++; + while (fnu >= fn && *fnu != '\\' && *fnu != '/') fnu--; + if (fnu < fn) break; + fnu++; + } + + usefn.Set(callerfn); + int l=usefn.GetLength(); + while (l > 0 && usefn.Get()[l-1] != '\\' && usefn.Get()[l-1] != '/') l--; + if (l > 0) + { + usefn.SetLen(l); + usefn.Append(fnu); + } + else + { + usefn.Set(fnu); + } + int last_slash_pos=-1; + for (l = 0; l < usefn.GetLength(); l ++) + { + if (usefn.Get()[l] == '/' || usefn.Get()[l] == '\\') + { + if (usefn.Get()[l+1] == '.' && usefn.Get()[l+2] == '.' && + (usefn.Get()[l+3] == '/' || usefn.Get()[l+3] == '\\')) + { + if (last_slash_pos >= 0) + usefn.DeleteSub(last_slash_pos, l+3-last_slash_pos); + else + usefn.DeleteSub(0,l+3+1); + } + else + { + last_slash_pos=l; + } + } + // take currentfn, remove filename part, add fnu + } + } + + FILE *fp = fopenUTF8(usefn.Get(),"r"); + if (fp) return fp; + } + if (had_abs) usefn.Set(fn); + return NULL; +} + +int eelScriptInst::loadfile(const char *fn, const char *callerfn, bool allowstdin) +{ + WDL_FastString usefn; + FILE *fp = NULL; + if (!strcmp(fn,"-")) + { + if (callerfn) + { +#ifdef EEL_STRING_DEBUGOUT + EEL_STRING_DEBUGOUT("@import: can't import \"-\" (stdin)"); +#endif + return -1; + } + if (allowstdin) + { + fp = stdin; + fn = "(stdin)"; + } + } + else if (!callerfn) + { + fp = fopenUTF8(fn,"r"); + if (fp) m_loaded_fnlist.Insert(fn,true); + } + else + { + fp = eelscript_resolvePath(usefn,fn,callerfn); + if (fp) + { + if (m_loaded_fnlist.Get(usefn.Get())) + { + fclose(fp); + return 0; + } + m_loaded_fnlist.Insert(usefn.Get(),true); + fn = usefn.Get(); + } + } + + if (!fp) + { +#ifdef EEL_STRING_DEBUGOUT + if (callerfn) + EEL_STRING_DEBUGOUT("Warning: @import could not open '%s'",fn); + else + EEL_STRING_DEBUGOUT("Error opening %s",fn); +#endif + return -1; + } + +#ifndef EELSCRIPT_NO_PREPROC + WDL_FastString incpath(fn); + incpath.remove_filepart(); + m_preproc.m_include_paths.Add(incpath.Get()); +#endif + + WDL_FastString code; + char line[4096]; + for (;;) + { + line[0]=0; + fgets(line,sizeof(line),fp); + if (!line[0]) break; + if (!strnicmp(line,"@import",7) && isspace((unsigned char)line[7])) + { + char *p=line+7; + while (isspace((unsigned char)*p)) p++; + + char *ep=p; + while (*ep) ep++; + while (ep>p && isspace((unsigned char)ep[-1])) ep--; + *ep=0; + + if (*p) loadfile(p,fn,false); + } + else + { + code.Append(line); + } + } + if (fp != stdin) fclose(fp); + + int rv = runcode(code.Get(),callerfn ? 2 : 1, fn,false,true,!callerfn); + +#ifndef EELSCRIPT_NO_PREPROC + m_preproc.m_include_paths.Delete(m_preproc.m_include_paths.GetSize()-1); +#endif + + return rv; +} + +char *eelScriptInst::evalCacheGet(const char *str, NSEEL_CODEHANDLE *ch) +{ + // should mutex protect if multiple threads access this eelScriptInst context + int x=m_eval_cache.GetSize(); + while (--x >= 0) + { + char *ret; + if (!strcmp(ret=m_eval_cache.Get()[x].str, str)) + { + *ch = m_eval_cache.Get()[x].ch; + m_eval_cache.Delete(x); + return ret; + } + } + return NULL; +} + +void eelScriptInst::evalCacheDispose(char *key, NSEEL_CODEHANDLE ch) +{ + // should mutex protect if multiple threads access this eelScriptInst context + evalCacheEnt ecc; + ecc.str= key; + ecc.ch = ch; + if (m_eval_cache.GetSize() > 1024) + { + NSEEL_code_free(m_eval_cache.Get()->ch); + free(m_eval_cache.Get()->str); + m_eval_cache.Delete(0); + } + m_eval_cache.Add(ecc); +} + +int eelScriptInst::init() +{ + EEL_string_register(); +#ifndef EELSCRIPT_NO_FILE + EEL_file_register(); +#endif +#ifndef EELSCRIPT_NO_FFT + EEL_fft_register(); +#endif +#ifndef EELSCRIPT_NO_MDCT + EEL_mdct_register(); +#endif + EEL_misc_register(); +#ifndef EELSCRIPT_NO_EVAL + EEL_eval_register(); + NSEEL_addfunc_retval("defer",1,NSEEL_PProc_THIS,&_eel_defer); + NSEEL_addfunc_retval("runloop", 1, NSEEL_PProc_THIS, &_eel_defer); + NSEEL_addfunc_retval("atexit",1,NSEEL_PProc_THIS,&_eel_atexit); +#endif +#ifndef EELSCRIPT_NO_NET + EEL_tcp_register(); +#endif +#ifndef EELSCRIPT_NO_LICE + eel_lice_register(); + #ifdef _WIN32 + eel_lice_register_standalone(GetModuleHandle(NULL),EELSCRIPT_LICE_CLASSNAME,NULL,NULL); + #else + eel_lice_register_standalone(NULL,EELSCRIPT_LICE_CLASSNAME,NULL,NULL); + #endif +#endif + return 0; +} + +bool eelScriptInst::has_deferred() +{ +#ifndef EELSCRIPT_NO_EVAL + return m_defer_eval.Available() && m_vm; +#else + return false; +#endif +} + +#ifndef EELSCRIPT_NO_EVAL +void eelScriptInst::runCodeQ(WDL_Queue *q, const char *callername) +{ + const int endptr = q->Available(); + int offs = 0; + while (offs < endptr) + { + if (q->Available() < endptr) break; // should never happen, but safety first! + + const char *ptr = (const char *)q->Get() + offs; + offs += strlen(ptr)+1; + + NSEEL_CODEHANDLE ch=NULL; + char *sv=evalCacheGet(ptr,&ch); + + if (!sv) sv=strdup(ptr); + if (!ch) ch=NSEEL_code_compile(m_vm,sv,0); + if (!ch) + { + free(sv); +#ifdef EEL_STRING_DEBUGOUT + const char *err = NSEEL_code_getcodeerror(m_vm); + if (err) EEL_STRING_DEBUGOUT("%s: error in code: %s",callername,err); +#endif + } + else + { + NSEEL_code_execute(ch); + evalCacheDispose(sv,ch); + } + } + q->Advance(endptr); +} +#endif + +bool eelScriptInst::run_deferred() +{ +#ifndef EELSCRIPT_NO_EVAL + if (!m_defer_eval.Available()||!m_vm) return false; + + runCodeQ(&m_defer_eval,"defer"); + m_defer_eval.Compact(); + return m_defer_eval.Available()>0; +#else + return false; +#endif +} + +#ifdef EEL_WANT_DOCUMENTATION +#include "ns-eel-func-ref.h" + +void EELScript_GenerateFunctionList(WDL_PtrList *fs) +{ + const char *p = nseel_builtin_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } + p = eel_strings_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } + p = eel_misc_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } +#ifndef EELSCRIPT_NO_EVAL + fs->Add("atexit\t\"code\"\t" +#ifndef EELSCRIPT_HELP_NO_DEFER_DESC + "Adds code to be executed when the script finishes." +#endif + ); + fs->Add("defer\t\"code\"\t" +#ifndef EELSCRIPT_HELP_NO_DEFER_DESC + "Adds code which will be executed some small amount of time after the current code finishes. Identical to runloop()" +#endif + ); + fs->Add("runloop\t\"code\"\t" +#ifndef EELSCRIPT_HELP_NO_DEFER_DESC + "Adds code which will be executed some small amount of time after the current code finishes. Identical to defer()" +#endif + ); + + p = eel_eval_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } +#endif +#ifndef EELSCRIPT_NO_NET + p = eel_net_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } +#endif +#ifndef EELSCRIPT_NO_FFT + p = eel_fft_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } +#endif +#ifndef EELSCRIPT_NO_FILE + p = eel_file_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } +#endif +#ifndef EELSCRIPT_NO_MDCT + p = eel_mdct_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } +#endif +#ifndef EELSCRIPT_NO_LICE + p = eel_lice_function_reference; + while (*p) { fs->Add(p); p += strlen(p) + 1; } +#endif + +} + + +#endif + +#undef opaque diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_aarch64.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_aarch64.h new file mode 100644 index 0000000..671fe65 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_aarch64.h @@ -0,0 +1,448 @@ +#ifndef _NSEEL_GLUE_AARCH64_H_ +#define _NSEEL_GLUE_AARCH64_H_ + +#define GLUE_MOD_IS_64 + +// x0=return value, first parm, x1-x2 parms (x3-x7 more params) +// x8 return struct? +// x9-x15 temporary +// x16-x17 = PLT, linker +// x18 reserved (TLS) +// x19-x28 callee-saved +// x19 = worktable +// x20 = ramtable +// x21 = consttab +// x22 = worktable ptr +// x23-x28 spare +// x29 frame pointer +// x30 link register +// x31 SP/zero + +// x0=p1 +// x1=p2 +// x2=p3 + +// d0 is return value for fp? +// d/v/f0-7 = arguments/results +// 8-15 callee saved +// 16-31 temporary + +// v8-v15 spill registers +#define GLUE_MAX_SPILL_REGS 8 +#define GLUE_SAVE_TO_SPILL_SIZE(x) (4) +#define GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(x) (4) + +static void GLUE_RESTORE_SPILL_TO_FPREG2(void *b, int ws) +{ + *(unsigned int *)b = 0x1e604101 + (ws<<5); // fmov d1, d8+ws +} +static void GLUE_SAVE_TO_SPILL(void *b, int ws) +{ + *(unsigned int *)b = 0x1e604008 + ws; // fmov d8+ws, d0 +} + + +#define GLUE_HAS_FPREG2 1 + +static const unsigned int GLUE_COPY_FPSTACK_TO_FPREG2[] = { 0x1e604001 }; // fmov d1, d0 +static unsigned int GLUE_POP_STACK_TO_FPREG2[] = { + 0xfc4107e1 // ldr d1, [sp], #16 +}; + +#define GLUE_MAX_FPSTACK_SIZE 0 // no stack support +#define GLUE_MAX_JMPSIZE ((1<<20) - 1024) // maximum relative jump size + +// endOfInstruction is end of jump with relative offset, offset passed in is offset from end of dest instruction. +// 0 = current instruction +static void GLUE_JMP_SET_OFFSET(void *endOfInstruction, int offset) +{ + unsigned int *a = (unsigned int*) endOfInstruction - 1; + offset += 4; + offset >>= 2; // as dwords + if ((a[0] & 0xFC000000) == 0x14000000) + { + // NC b = 0x14 + 26 bit offset + a[0] = 0x14000000 | (offset & 0x3FFFFFF); + } + else if ((a[0] & 0xFF000000) == 0x54000000) + { + // condb = 0x54 + 20 bit offset + 5 bit condition: 0=eq, 1=ne, b=lt, c=gt, d=le, a=ge + a[0] = 0x54000000 | (a[0] & 0xF) | ((offset & 0x7FFFF) << 5); + } +} + +static const unsigned int GLUE_JMP_NC[] = { 0x14000000 }; + +static const unsigned int GLUE_JMP_IF_P1_Z[]= +{ + 0x7100001f, // cmp w0, #0 + 0x54000000, // b.eq +}; +static const unsigned int GLUE_JMP_IF_P1_NZ[]= +{ + 0x7100001f, // cmp w0, #0 + 0x54000001, // b.ne +}; + +#define GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE 16 // wr=-2, sets d1 +#define GLUE_MOV_PX_DIRECTVALUE_SIZE 12 +static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wv) +{ + static const unsigned int tab[3] = { + 0xd2800000, // mov x0, #0000 (val<<5) | reg + 0xf2a00000, // movk x0, #0000, lsl 16 (val<<5) | reg + 0xf2c00000, // movk x0, #0000, lsl 32 (val<<5) | reg + }; + // 0xABAAA, B is register, A are bits of word + unsigned int *p=(unsigned int *)b; + int wvo = wv; + if (wv<0) wv=0; + p[0] = tab[0] | wv | ((v&0xFFFF)<<5); + p[1] = tab[1] | wv | (((v>>16)&0xFFFF)<<5); + p[2] = tab[2] | wv | (((v>>32)&0xFFFF)<<5); + if (wvo == -2) p[3] = 0xfd400001; // ldr d1, [x0] +} + +const static unsigned int GLUE_FUNC_ENTER[2] = { 0xa9bf7bfd, 0x910003fd }; // stp x29, x30, [sp, #-16]! ; mov x29, sp +#define GLUE_FUNC_ENTER_SIZE 4 +const static unsigned int GLUE_FUNC_LEAVE[1] = { 0 }; // let GLUE_RET pop +#define GLUE_FUNC_LEAVE_SIZE 0 +const static unsigned int GLUE_RET[]={ 0xa8c17bfd, 0xd65f03c0 }; // ldp x29,x30, [sp], #16 ; ret + +static int GLUE_RESET_WTP(unsigned char *out, void *ptr) +{ + const static unsigned int GLUE_SET_WTP_FROM_R19 = 0xaa1303f6; // mov r22, r19 + if (out) memcpy(out,&GLUE_SET_WTP_FROM_R19,sizeof(GLUE_SET_WTP_FROM_R19)); + return 4; +} + + +const static unsigned int GLUE_PUSH_P1[1]={ 0xf81f0fe0 }; // str x0, [sp, #-16]! + +#define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(offs) ((offs)>=32768 ? 8 : 4) +static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) +{ + if (offs >= 32768) + { + // add x1, sp, (offs/4096) lsl 12 + *(unsigned int *)b = 0x914003e1 + ((offs>>12)<<10); + + // str x0, [x1, #offs & 4095] + offs &= 4095; + offs <<= 10-3; + offs &= 0x7FFC00; + ((unsigned int *)b)[1] = 0xf9000020 + offs; + } + else + { + // str x0, [sp, #offs] + offs <<= 10-3; + offs &= 0x7FFC00; + *(unsigned int *)b = 0xf90003e0 + offs; + } +} + +#define GLUE_MOVE_PX_STACKPTR_SIZE 4 +static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) +{ + // mov xX, sp + *(unsigned int *)b = 0x910003e0 + wv; +} + +#define GLUE_MOVE_STACK_SIZE 4 +static void GLUE_MOVE_STACK(void *b, int amt) +{ + if (amt>=0) + { + if (amt >= 4096) + *(unsigned int*)b = 0x914003ff | (((amt+4095)>>12)<<10); + else + *(unsigned int*)b = 0x910003ff | (amt << 10); + } + else + { + amt = -amt; + if (amt >= 4096) + *(unsigned int*)b = 0xd14003ff | (((amt+4095)>>12)<<10); + else + *(unsigned int*)b = 0xd10003ff | (amt << 10); + } +} + +#define GLUE_POP_PX_SIZE 4 +static void GLUE_POP_PX(void *b, int wv) +{ + ((unsigned int *)b)[0] = 0xf84107e0 | wv; // ldr x, [sp], 16 +} + +#define GLUE_SET_PX_FROM_P1_SIZE 4 +static void GLUE_SET_PX_FROM_P1(void *b, int wv) +{ + *(unsigned int *)b = 0xaa0003e0 | wv; +} + + +static const unsigned int GLUE_PUSH_P1PTR_AS_VALUE[] = +{ + 0xfd400007, // ldr d7, [x0] + 0xfc1f0fe7, // str d7, [sp, #-16]! +}; + +static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) +{ + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + *bufptr++ = 0xfc4107e7; // ldr d7, [sp], #16 + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + *bufptr++ = 0xfd000007; // str d7, [x0] + } + return 2*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; +} + +static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) +{ + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + *bufptr++ = 0xfd400007; // ldr d7, [x0] + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + *bufptr++ = 0xfd000007; // str d7, [x0] + } + return 2*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; +} + + +#define GLUE_CALL_CODE(bp, cp, rt) do { \ + GLUE_SCR_TYPE f; \ + static const double consttab[] = { \ + NSEEL_CLOSEFACTOR, \ + 0.0, \ + 1.0, \ + -1.0, \ + -0.5, /* for invsqrt */ \ + 1.5, \ + }; \ + if (!(h->compile_flags&NSEEL_CODE_COMPILE_FLAG_NOFPSTATE) && \ + !((f=glue_getscr())&(1<<24))) { \ + glue_setscr(f|(1<<24)); \ + eel_callcode64(bp, cp, rt, (void *)consttab); \ + glue_setscr(f); \ + } else eel_callcode64(bp, cp, rt, (void *)consttab);\ + } while(0) + +#ifndef _MSC_VER +static void eel_callcode64(INT_PTR bp, INT_PTR cp, INT_PTR rt, void *consttab) +{ + __asm__( + "mov x1, %2\n" + "mov x2, %3\n" + "mov x3, %1\n" + "mov x0, %0\n" + "stp x29, x30, [sp, #-64]!\n" + "stp x18, x20, [sp, 16]\n" + "stp x21, x19, [sp, 32]\n" + "stp x22, x23, [sp, 48]\n" + "mov x29, sp\n" + "mov x19, x3\n" + "mov x20, x1\n" + "mov x21, x2\n" + "blr x0\n" + "ldp x29, x30, [sp], 16\n" + "ldp x18, x20, [sp], 16\n" + "ldp x21, x19, [sp], 16\n" + "ldp x22, x23, [sp], 16\n" + ::"r" (cp), "r" (bp), "r" (rt), "r" (consttab) :"x0","x1","x2","x3","x4","x5","x6","x7", + "x8","x9","x10","x11","x12","x13","x14","x15", + "v8","v9","v10","v11","v12","v13","v14","v15"); + +}; +#else +void eel_callcode64(INT_PTR bp, INT_PTR cp, INT_PTR rt, void *consttab); +#endif + +static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) +{ + unsigned int *p=(unsigned int *)_p; + WDL_ASSERT(!(newv>>48)); +// 0xd2800000, // mov x0, #0000 (val<<5) | reg + // 0xf2a00000, // movk x0, #0000, lsl 16 (val<<5) | reg + // 0xf2c00000, // movk x0, #0000, lsl 32 (val<<5) | reg + while (((p[0]>>5)&0xffff)!=0xdead || + ((p[1]>>5)&0xffff)!=0xbeef || + ((p[2]>>5)&0xffff)!=0xbeef) p++; + + p[0] = (p[0] & 0xFFE0001F) | ((newv&0xffff)<<5); + p[1] = (p[1] & 0xFFE0001F) | (((newv>>16)&0xffff)<<5); + p[2] = (p[2] & 0xFFE0001F) | (((newv>>32)&0xffff)<<5); + + return (unsigned char *)(p+2); +} + +#define GLUE_SET_PX_FROM_WTP_SIZE sizeof(int) +static void GLUE_SET_PX_FROM_WTP(void *b, int wv) +{ + *(unsigned int *)b = 0xaa1603e0 + wv; // mov x, x22 +} + +static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) +{ + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + + *bufptr++ = 0xfd000000; // str d0, [x0] + } + return GLUE_MOV_PX_DIRECTVALUE_SIZE + sizeof(int); +} + +#define GLUE_POP_FPSTACK_SIZE 0 +static const unsigned int GLUE_POP_FPSTACK[1] = { 0 }; // no need to pop, not a stack + +static const unsigned int GLUE_POP_FPSTACK_TOSTACK[] = { + 0xfc1f0fe0, // str d0, [sp, #-16]! + +}; + +static const unsigned int GLUE_POP_FPSTACK_TO_WTP[] = { + 0xfc0086c0, // str d0, [x22], #8 +}; + +#define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE 4 +static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) +{ + *(unsigned int *)b = 0xfd400000 + (wv<<5); // ldr d0, [xX] +} + +#define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (sizeof(GLUE_POP_FPSTACK_TO_WTP) + GLUE_SET_PX_FROM_WTP_SIZE) +static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) +{ + GLUE_SET_PX_FROM_WTP(buf,wv); + memcpy(buf + GLUE_SET_PX_FROM_WTP_SIZE,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); +}; + +static const unsigned int GLUE_SET_P1_Z[] = { 0x52800000 }; // mov w0, #0 +static const unsigned int GLUE_SET_P1_NZ[] = { 0x52800020 }; // mov w0, #1 + + +static void *GLUE_realAddress(void *fn, int *size) +{ + while ((*(int*)fn & 0xFC000000) == 0x14000000) + { + int offset = (*(int*)fn & 0x3FFFFFF); + if (offset & 0x2000000) + offset |= 0xFC000000; + + fn = (int*)fn + offset; + } + static const unsigned int sig[] = { +#ifndef _MSC_VER + 0xaa0003e0, +#endif + 0xaa0103e1, +#ifndef _MSC_VER + 0xaa0203e2 +#endif + }; + unsigned char *p = (unsigned char *)fn; + + while (memcmp(p,sig,sizeof(sig))) p+=4; + p+=sizeof(sig); + fn = p; + + while (memcmp(p,sig,sizeof(sig))) p+=4; + *size = p - (unsigned char *)fn; + return fn; +} + + + +#ifndef _MSC_VER +#define GLUE_SCR_TYPE unsigned long +static unsigned long __attribute__((unused)) glue_getscr() +{ + unsigned long rv; + asm volatile ( "mrs %0, fpcr" : "=r" (rv)); + return rv; +} +static void __attribute__((unused)) glue_setscr(unsigned long v) +{ + asm volatile ( "msr fpcr, %0" :: "r"(v)); +} +#else +#define GLUE_SCR_TYPE unsigned long long +GLUE_SCR_TYPE glue_getscr(); +void glue_setscr(unsigned long long); +#endif + +void eel_enterfp(int _s[2]) +{ + GLUE_SCR_TYPE *s = (GLUE_SCR_TYPE*)_s; + s[0] = glue_getscr(); + glue_setscr(s[0] | (1<<24)); +} +void eel_leavefp(int _s[2]) +{ + const GLUE_SCR_TYPE *s = (GLUE_SCR_TYPE*)_s; + glue_setscr(s[0]); +} + +#define GLUE_HAS_FUSE 1 +static int GLUE_FUSE(compileContext *ctx, unsigned char *code, int left_size, int right_size, int fuse_flags, int spill_reg) +{ + if (left_size>=4 && right_size == 4) + { + unsigned int instr = ((unsigned int *)code)[-1]; + if (spill_reg >= 0 && (instr & 0xfffffc1f) == 0x1e604001) // fmov d1, dX + { + const int src_reg = (instr>>5)&0x1f; + if (src_reg == spill_reg + 8) + { + instr = ((unsigned int *)code)[0]; + if ((instr & 0xffffcfff) == 0x1e600820) + { + ((unsigned int *)code)[-1] = instr + ((src_reg-1) << 5); + return -4; + } + } + } + } + return 0; +} + +#ifdef _M_ARM64EC +#define DEF_F1(n) static double eel_##n(double a) { return n(a); } +#define DEF_F2(n) static double eel_##n(double a, double b) { return n(a,b); } +DEF_F1(cos) +#define cos eel_cos +DEF_F1(sin) +#define sin eel_sin +DEF_F1(tan) +#define tan eel_tan +DEF_F1(log) +#define log eel_log +DEF_F1(log10) +#define log10 eel_log10 +DEF_F1(acos) +#define acos eel_acos +DEF_F1(asin) +#define asin eel_asin +DEF_F1(atan) +#define atan eel_atan +DEF_F1(exp) +#define exp eel_exp +DEF_F2(pow) +#define pow eel_pow +DEF_F2(atan2) +#define atan2 eel_atan2 +// ceil and floor will be wrapped by defs in nseel-compiler.c + +#pragma comment(lib,"onecore.lib") +#endif + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_arm.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_arm.h new file mode 100644 index 0000000..ab9ec73 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_arm.h @@ -0,0 +1,335 @@ +#ifndef _NSEEL_GLUE_ARM_H_ +#define _NSEEL_GLUE_ARM_H_ + +// r0=return value, first parm, r1-r2 parms +// r3+ should be reserved +// blx addr +// stmfd sp!, {register list, lr} +// ldmfd sp!, {register list, pc} + +// let's make r8 = worktable +// let's make r7 = ramtable +// r6 = consttab +// r5 = worktable ptr + +// r0=p1 +// r1=p2 +// r2=p3 + +// d0 is return value? + + +#define GLUE_HAS_FPREG2 1 + +static const unsigned int GLUE_COPY_FPSTACK_TO_FPREG2[] = { + 0xeeb01b40 // fcpyd d1, d0 +}; + +static unsigned int GLUE_POP_STACK_TO_FPREG2[] = { + 0xed9d1b00,// vldr d1, [sp] + 0xe28dd008,// add sp, sp, #8 +}; + +#define GLUE_MAX_SPILL_REGS 8 +#define GLUE_SAVE_TO_SPILL_SIZE(x) (4) +#define GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(x) (4) + +static void GLUE_RESTORE_SPILL_TO_FPREG2(void *b, int ws) +{ + *(unsigned int *)b = 0xeeb01b48 + ws; // fcpyd d1, d8+ws +} +static void GLUE_SAVE_TO_SPILL(void *b, int ws) +{ + *(unsigned int *)b = 0xeeb08b40 + (ws<<12); // fcpyd d8+ws, d0 +} + + +#define GLUE_MAX_FPSTACK_SIZE 0 // no stack support +#define GLUE_MAX_JMPSIZE ((1<<25) - 1024) // maximum relative jump size + +// endOfInstruction is end of jump with relative offset, offset passed in is offset from end of dest instruction. +// TODO: verify, but offset probably from next instruction (PC is ahead) +#define GLUE_JMP_SET_OFFSET(endOfInstruction,offset) (((int *)(endOfInstruction))[-1] = (((int *)(endOfInstruction))[-1]&0xFF000000)|((((offset)>>2)-1))) + + // /=conditional=always = 0xE + // |/= 101(L), so 8+2+0 = 10 = A +static const unsigned int GLUE_JMP_NC[] = { 0xEA000000 }; + +static const unsigned int GLUE_JMP_IF_P1_Z[]= +{ + 0xe1100000, // tst r0, r0 + 0x0A000000, // branch if Z set +}; +static const unsigned int GLUE_JMP_IF_P1_NZ[]= +{ + 0xe1100000, // tst r0, r0 + 0x1A000000, // branch if Z clear +}; + +#define GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE 12 // wr=-2, sets d1 +#define GLUE_MOV_PX_DIRECTVALUE_SIZE 8 +static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wv) +{ + // requires ARMv6thumb2 or later + const unsigned int reg_add = wdl_max(wv,0) << 12; + static const unsigned int tab[2] = { + 0xe3000000, // movw r0, #0000 + 0xe3400000, // movt r0, #0000 + }; + // 0xABAAA, B is register, A are bits of word + unsigned int *p=(unsigned int *)b; + p[0] = tab[0] | reg_add | (v&0xfff) | ((v&0xf000)<<4); + p[1] = tab[1] | reg_add | ((v>>16)&0xfff) | ((v&0xf0000000)>>12); + if (wv == -2) p[2] = 0xed901b00; // fldd d1, [r0] +} + +const static unsigned int GLUE_FUNC_ENTER[1] = { 0xe92d4010 }; // push {r4, lr} +#define GLUE_FUNC_ENTER_SIZE 4 +const static unsigned int GLUE_FUNC_LEAVE[1] = { 0 }; // let GLUE_RET pop +#define GLUE_FUNC_LEAVE_SIZE 0 +const static unsigned int GLUE_RET[]={ 0xe8bd8010 }; // pop {r4, pc} + +static int GLUE_RESET_WTP(unsigned char *out, void *ptr) +{ + const static unsigned int GLUE_SET_WTP_FROM_R8 = 0xe1a05008; // mov r5, r8 + if (out) memcpy(out,&GLUE_SET_WTP_FROM_R8,sizeof(GLUE_SET_WTP_FROM_R8)); + return sizeof(GLUE_SET_WTP_FROM_R8); +} + + +const static unsigned int GLUE_PUSH_P1[1]={ 0xe52d0008 }; // push {r0}, aligned to 8 + + +static int arm_encode_constforalu(int amt) +{ + int nrot = 16; + while (amt >= 0x100 && nrot > 1) + { + // ARM encodes integers for ALU operations as rotated right by third nibble*2 + amt = (amt + 3)>>2; + nrot--; + } + return ((nrot&15) << 8) | amt; +} + + +#define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(x) ((x)>=4096 ? 8 : 4) +static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) +{ + if (offs >= 4096) + { + // add r2, sp, (offs&~4095) + *(unsigned int *)b = 0xe28d2000 | arm_encode_constforalu(offs&~4095); + // str r0, [r2, offs&4095] + ((unsigned int *)b)[1] = 0xe5820000 + (offs&4095); + } + else + { + // str r0, [sp, #offs] + *(unsigned int *)b = 0xe58d0000 + offs; + } +} + +#define GLUE_MOVE_PX_STACKPTR_SIZE 4 +static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) +{ + // mov rX, sp + *(unsigned int *)b = 0xe1a0000d + (wv<<12); +} + +#define GLUE_MOVE_STACK_SIZE 4 +static void GLUE_MOVE_STACK(void *b, int amt) +{ + unsigned int instr = 0xe28dd000; + if (amt < 0) + { + instr = 0xe24dd000; + amt=-amt; + } + *(unsigned int*)b = instr | arm_encode_constforalu(amt); +} + +#define GLUE_POP_PX_SIZE 4 +static void GLUE_POP_PX(void *b, int wv) +{ + ((unsigned int *)b)[0] = 0xe49d0008 | (wv<<12); // pop {rX}, aligned to 8 +} + +#define GLUE_SET_PX_FROM_P1_SIZE 4 +static void GLUE_SET_PX_FROM_P1(void *b, int wv) +{ + *(unsigned int *)b = 0xe1a00000 | (wv<<12); // mov rX, r0 +} + + +static const unsigned int GLUE_PUSH_P1PTR_AS_VALUE[] = +{ + 0xed907b00, // fldd d7, [r0] + 0xe24dd008, // sub sp, sp, #8 + 0xed8d7b00, // fstd d7, [sp] +}; + +static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) +{ + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + *bufptr++ = 0xed9d7b00; // fldd d7, [sp] + *bufptr++ = 0xe28dd008; // add sp, sp, #8 + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + *bufptr++ = 0xed807b00; // fstd d7, [r0] + } + return 3*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; +} + +static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) +{ + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + *bufptr++ = 0xed907b00; // fldd d7, [r0] + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + *bufptr++ = 0xed807b00; // fstd d7, [r0] + } + return 2*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE; +} + + +#ifndef _MSC_VER +#define GLUE_CALL_CODE(bp, cp, rt) do { \ + unsigned int f; \ + if (!(h->compile_flags&NSEEL_CODE_COMPILE_FLAG_NOFPSTATE) && \ + !((f=glue_getscr())&(1<<24))) { \ + glue_setscr(f|(1<<24)); \ + eel_callcode32(bp, cp, rt); \ + glue_setscr(f); \ + } else eel_callcode32(bp, cp, rt);\ + } while(0) + +static const double __consttab[] = { + NSEEL_CLOSEFACTOR, + 0.0, + 1.0, + -1.0, + -0.5, // for invsqrt + 1.5, + }; + +static void eel_callcode32(INT_PTR bp, INT_PTR cp, INT_PTR rt) +{ + __asm__ volatile( + "mov r7, %2\n" + "mov r6, %3\n" + "mov r8, %1\n" + "mov r0, %0\n" + "mov r1, sp\n" + "bic sp, sp, #7\n" + "push {r1, lr}\n" + "blx r0\n" + "pop {r1, lr}\n" + "mov sp, r1\n" + ::"r" (cp), "r" (bp), "r" (rt), "r" (__consttab) : + "r5", "r6", "r7", "r8", "r10", + "d8","d9","d10","d11","d12","d13","d14","d15"); +}; +#endif + +static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) +{ + unsigned int *p=(unsigned int *)_p; + while ((p[0]&0x000F0FFF) != 0x000d0ead && + (p[1]&0x000F0FFF) != 0x000b0eef) p++; + p[0] = (p[0]&0xFFF0F000) | (newv&0xFFF) | ((newv << 4) & 0xF0000); + p[1] = (p[1]&0xFFF0F000) | ((newv>>16)&0xFFF) | ((newv >> 12)&0xF0000); + + return (unsigned char *)(p+1); +} + +#define GLUE_SET_PX_FROM_WTP_SIZE sizeof(int) +static void GLUE_SET_PX_FROM_WTP(void *b, int wv) +{ + *(unsigned int *)b = 0xe1a00005 + (wv<<12); // mov rX, r5 +} + +static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) +{ + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + + *bufptr++ = 0xed800b00; // fstd d0, [r0] + } + return GLUE_MOV_PX_DIRECTVALUE_SIZE + sizeof(int); +} + +#define GLUE_POP_FPSTACK_SIZE 0 +static const unsigned int GLUE_POP_FPSTACK[1] = { 0 }; // no need to pop, not a stack + +static const unsigned int GLUE_POP_FPSTACK_TOSTACK[] = { + 0xe24dd008, // sub sp, sp, #8 + 0xed8d0b00, // fstd d0, [sp] +}; + +static const unsigned int GLUE_POP_FPSTACK_TO_WTP[] = { + 0xed850b00, // fstd d0, [r5] + 0xe2855008, // add r5, r5, #8 +}; + +#define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE 4 +static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) +{ + *(unsigned int *)b = 0xed900b00 + (wv<<16); // fldd d0, [rX] +} + +#define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (sizeof(GLUE_POP_FPSTACK_TO_WTP) + GLUE_SET_PX_FROM_WTP_SIZE) +static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) +{ + GLUE_SET_PX_FROM_WTP(buf,wv); + memcpy(buf + GLUE_SET_PX_FROM_WTP_SIZE,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); +}; + +static const unsigned int GLUE_SET_P1_Z[] = { 0xe3a00000 }; // mov r0, #0 +static const unsigned int GLUE_SET_P1_NZ[] = { 0xe3a00001 }; // mov r0, #1 + + +static void *GLUE_realAddress(void *fn, int *size) +{ + static const unsigned int sig[3] = { 0xe1a00000, 0xe1a01001, 0xe1a02002 }; + unsigned char *p = (unsigned char *)fn; + + while (memcmp(p,sig,sizeof(sig))) p+=4; + p+=sizeof(sig); + fn = p; + + while (memcmp(p,sig,sizeof(sig))) p+=4; + *size = p - (unsigned char *)fn; + return fn; +} + +static unsigned int __attribute__((unused)) glue_getscr() +{ + unsigned int rv; + asm volatile ( "fmrx %0, fpscr" : "=r" (rv)); + return rv; +} +static void __attribute__((unused)) glue_setscr(unsigned int v) +{ + asm volatile ( "fmxr fpscr, %0" :: "r"(v)); +} + +void eel_enterfp(int s[2]) +{ + s[0] = glue_getscr(); + glue_setscr(s[0] | (1<<24)); // could also do 3<<22 for RTZ +} +void eel_leavefp(int s[2]) +{ + glue_setscr(s[0]); +} + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_port.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_port.h new file mode 100644 index 0000000..e7d06f3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_port.h @@ -0,0 +1,1029 @@ +#ifndef _EEL_GLUE_PORTABLE_H_ +#define _EEL_GLUE_PORTABLE_H_ + +#define GLUE_MOD_IS_64 + +#define DECL_ASMFUNC(x) +#define GLUE_JMP_TYPE int +#define GLUE_JMP_SET_OFFSET(endOfInstruction,offset) (((GLUE_JMP_TYPE *)(endOfInstruction))[-1] = (offset)) + +#define GLUE_MAX_FPSTACK_SIZE 64 +#define BIF_FPSTACKUSE(x) (0) // fp stack is not used within functions +#define BIF_GETFPSTACKUSE(x) (1) + +enum { + EEL_BC_NOP=1, + EEL_BC_RET, + EEL_BC_JMP_NC, // followed by GLUE_JMP_TYPE + EEL_BC_JMP_IF_P1_Z, + EEL_BC_JMP_IF_P1_NZ, + + EEL_BC_MOV_FPTOP_DV, + EEL_BC_MOV_P1_DV, // followed by INT_PTR ptr + EEL_BC_MOV_P2_DV, + EEL_BC_MOV_P3_DV, + EEL_BC__RESET_WTP, + + EEL_BC_PUSH_P1, + EEL_BC_PUSH_P1PTR_AS_VALUE, + EEL_BC_POP_P1, + EEL_BC_POP_P2, + EEL_BC_POP_P3, + EEL_BC_POP_VALUE_TO_ADDR, + + EEL_BC_MOVE_STACK, + EEL_BC_STORE_P1_TO_STACK_AT_OFFS, + EEL_BC_MOVE_STACKPTR_TO_P1, + EEL_BC_MOVE_STACKPTR_TO_P2, + EEL_BC_MOVE_STACKPTR_TO_P3, + + EEL_BC_SET_P2_FROM_P1, + EEL_BC_SET_P3_FROM_P1, + EEL_BC_COPY_VALUE_AT_P1_TO_ADDR, + EEL_BC_SET_P1_FROM_WTP, + EEL_BC_SET_P2_FROM_WTP, + EEL_BC_SET_P3_FROM_WTP, + + EEL_BC_POP_FPSTACK_TO_PTR, + EEL_BC_POP_FPSTACK_TOSTACK, + + EEL_BC_PUSH_VAL_AT_P1_TO_FPSTACK, + EEL_BC_PUSH_VAL_AT_P2_TO_FPSTACK, + EEL_BC_PUSH_VAL_AT_P3_TO_FPSTACK, + EEL_BC_POP_FPSTACK_TO_WTP, + EEL_BC_SET_P1_Z, + EEL_BC_SET_P1_NZ, + + + EEL_BC_LOOP_LOADCNT, + EEL_BC_LOOP_END, + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + EEL_BC_WHILE_SETUP, +#endif + + EEL_BC_WHILE_BEGIN, + EEL_BC_WHILE_END, + EEL_BC_WHILE_CHECK_RV, + + + + EEL_BC_BNOT, + EEL_BC_EQUAL, + EEL_BC_EQUAL_EXACT, + EEL_BC_NOTEQUAL, + EEL_BC_NOTEQUAL_EXACT, + EEL_BC_ABOVE, + EEL_BC_BELOWEQ, + + + EEL_BC_ADD, + EEL_BC_SUB, + EEL_BC_MUL, + EEL_BC_DIV, + EEL_BC_AND, + EEL_BC_OR, + EEL_BC_OR0, + EEL_BC_XOR, + + EEL_BC_ADD_OP, + EEL_BC_SUB_OP, + EEL_BC_ADD_OP_FAST, + EEL_BC_SUB_OP_FAST, + EEL_BC_MUL_OP, + EEL_BC_DIV_OP, + EEL_BC_MUL_OP_FAST, + EEL_BC_DIV_OP_FAST, + EEL_BC_AND_OP, + EEL_BC_OR_OP, + EEL_BC_XOR_OP, + + EEL_BC_UMINUS, + + EEL_BC_ASSIGN, + EEL_BC_ASSIGN_FAST, + EEL_BC_ASSIGN_FAST_FROMFP, + EEL_BC_ASSIGN_FROMFP, + EEL_BC_MOD, + EEL_BC_MOD_OP, + EEL_BC_SHR, + EEL_BC_SHL, + + EEL_BC_SQR, + EEL_BC_MIN, + EEL_BC_MAX, + EEL_BC_MIN_FP, + EEL_BC_MAX_FP, + EEL_BC_ABS, + EEL_BC_SIGN, + EEL_BC_INVSQRT, + + EEL_BC_FXCH, + EEL_BC_POP_FPSTACK, + + EEL_BC_FCALL, + EEL_BC_BOOLTOFP, + EEL_BC_FPTOBOOL, + EEL_BC_FPTOBOOL_REV, + + EEL_BC_CFUNC_1PDD, + EEL_BC_CFUNC_2PDD, + EEL_BC_CFUNC_2PDDS, + + EEL_BC_MEGABUF, + EEL_BC_GMEGABUF, + + EEL_BC_GENERIC1PARM, + EEL_BC_GENERIC2PARM, + EEL_BC_GENERIC3PARM, + EEL_BC_GENERIC1PARM_RETD, + EEL_BC_GENERIC2PARM_RETD, + EEL_BC_GENERIC2XPARM_RETD, + EEL_BC_GENERIC3PARM_RETD, + + EEL_BC_USERSTACK_PUSH, + EEL_BC_USERSTACK_POP, + EEL_BC_USERSTACK_POPFAST, + EEL_BC_USERSTACK_PEEK, + EEL_BC_USERSTACK_PEEK_INT, + EEL_BC_USERSTACK_PEEK_TOP, + EEL_BC_USERSTACK_EXCH, + + EEL_BC_DBG_GETSTACKPTR, + +}; + +#define BC_DECL(x) static const EEL_BC_TYPE GLUE_##x[] = { EEL_BC_##x }; +#define BC_DECL_JMP(x) static const EEL_BC_TYPE GLUE_##x[1 + sizeof(GLUE_JMP_TYPE) / sizeof(EEL_BC_TYPE)] = { EEL_BC_##x }; +BC_DECL_JMP(JMP_NC) +BC_DECL_JMP(JMP_IF_P1_Z) +BC_DECL_JMP(JMP_IF_P1_NZ) +BC_DECL(RET) +BC_DECL(FXCH) +BC_DECL(POP_FPSTACK) +#define GLUE_POP_FPSTACK_SIZE sizeof(EEL_BC_TYPE) +BC_DECL(PUSH_P1) +BC_DECL(PUSH_P1PTR_AS_VALUE) +BC_DECL(POP_FPSTACK_TOSTACK) +BC_DECL(POP_FPSTACK_TO_WTP) +BC_DECL(SET_P1_Z) +BC_DECL(SET_P1_NZ) +BC_DECL_JMP(LOOP_LOADCNT) + +BC_DECL_JMP(LOOP_END) + +#define GLUE_LOOP_BEGIN_SIZE 0 +#define GLUE_LOOP_BEGIN ((void*)"") +#define GLUE_LOOP_CLAMPCNT_SIZE 0 +#define GLUE_LOOP_CLAMPCNT ((void*)"") + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + BC_DECL(WHILE_SETUP) + #define GLUE_WHILE_SETUP_SIZE sizeof(GLUE_WHILE_SETUP) + BC_DECL_JMP(WHILE_END) +#else + #define GLUE_WHILE_SETUP_SIZE 0 + #define GLUE_WHILE_SETUP ((void *)"") + #define GLUE_WHILE_END_NOJUMP + BC_DECL(WHILE_END) +#endif + +BC_DECL(WHILE_BEGIN); +BC_DECL_JMP(WHILE_CHECK_RV) + +#define GLUE_MOV_PX_DIRECTVALUE_SIZE (sizeof(EEL_BC_TYPE) + sizeof(INT_PTR)) +#define GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE GLUE_MOV_PX_DIRECTVALUE_SIZE +static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wv) +{ + static const EEL_BC_TYPE tab[] = { + EEL_BC_MOV_FPTOP_DV, + EEL_BC_MOV_P1_DV, + EEL_BC_MOV_P2_DV, + EEL_BC_MOV_P3_DV, + }; + *(EEL_BC_TYPE *)b = tab[wv+1]; + *(INT_PTR *) ((char *)b + sizeof(EEL_BC_TYPE)) = v; +} + +#define GLUE_FUNC_ENTER_SIZE 0 +#define GLUE_FUNC_LEAVE_SIZE 0 +static const EEL_BC_TYPE GLUE_FUNC_ENTER[1]={-1}; +static const EEL_BC_TYPE GLUE_FUNC_LEAVE[1]={-1}; + +static int GLUE_RESET_WTP(unsigned char *out, void *ptr) +{ + BC_DECL(_RESET_WTP) + if (out) memcpy(out,&GLUE__RESET_WTP,sizeof(GLUE__RESET_WTP)); + if (out) *(void **) (out+sizeof(GLUE__RESET_WTP)) = ptr; + return sizeof(GLUE__RESET_WTP) + sizeof(void *); +} + +#define GLUE_POP_PX_SIZE sizeof(EEL_BC_TYPE) +static void GLUE_POP_PX(void *b, int wv) +{ + static const EEL_BC_TYPE tab[3] ={ + EEL_BC_POP_P1, + EEL_BC_POP_P2, + EEL_BC_POP_P3, + }; + *(EEL_BC_TYPE *)b = tab[wv]; +} + +#define GLUE_SET_PX_FROM_P1_SIZE sizeof(EEL_BC_TYPE) +static void GLUE_SET_PX_FROM_P1(void *b, int wv) +{ + static const unsigned int tab[3]={ + EEL_BC_NOP, + EEL_BC_SET_P2_FROM_P1, + EEL_BC_SET_P3_FROM_P1, + }; + *(EEL_BC_TYPE *)b = tab[wv]; +} + +#define GLUE_MOVE_STACK_SIZE (sizeof(EEL_BC_TYPE) + sizeof(int)) +static void GLUE_MOVE_STACK(void *b, int amt) +{ + *(EEL_BC_TYPE *)b = EEL_BC_MOVE_STACK; + *(int *)(((EEL_BC_TYPE *)b)+1) = amt; +} +#define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(x) (sizeof(EEL_BC_TYPE) + sizeof(int)) +static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) +{ + *(EEL_BC_TYPE *)b = EEL_BC_STORE_P1_TO_STACK_AT_OFFS; + *(int *)(((EEL_BC_TYPE *)b)+1) = offs; +} + +#define GLUE_MOVE_PX_STACKPTR_SIZE sizeof(EEL_BC_TYPE) +static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) +{ + static const EEL_BC_TYPE tab[3] = { + EEL_BC_MOVE_STACKPTR_TO_P1, + EEL_BC_MOVE_STACKPTR_TO_P2, + EEL_BC_MOVE_STACKPTR_TO_P3 + }; + *(EEL_BC_TYPE *)b = tab[wv]; +} + + +static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) +{ + if (buf) + { + *(EEL_BC_TYPE *)buf = EEL_BC_POP_VALUE_TO_ADDR; + *(void **) (buf+sizeof(EEL_BC_TYPE)) = destptr; + } + return sizeof(EEL_BC_TYPE) + sizeof(void *); +} + +static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) +{ + if (buf) + { + *(EEL_BC_TYPE *)buf = EEL_BC_COPY_VALUE_AT_P1_TO_ADDR; + *(void **) (buf+sizeof(EEL_BC_TYPE)) = destptr; + } + return sizeof(EEL_BC_TYPE) + sizeof(void *); +} + + + + +static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) +{ + int mv=5; + char *p=(char*)_p; + p+=sizeof(EEL_BC_TYPE); + while (*(INT_PTR*)p && mv-- > 0) p++; + if (!mv) return (unsigned char *)p; + + *(INT_PTR *)p = newv; + return (unsigned char *) p + sizeof(INT_PTR) - sizeof(EEL_BC_TYPE); +} + +#define GLUE_SET_PX_FROM_WTP_SIZE sizeof(EEL_BC_TYPE) +static void GLUE_SET_PX_FROM_WTP(void *b, int wv) +{ + static const EEL_BC_TYPE tab[3]={ + EEL_BC_SET_P1_FROM_WTP, + EEL_BC_SET_P2_FROM_WTP, + EEL_BC_SET_P3_FROM_WTP, + }; + *(EEL_BC_TYPE *)b = tab[wv]; +} + +static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) +{ + if (buf) + { + *(EEL_BC_TYPE *)buf = EEL_BC_POP_FPSTACK_TO_PTR; + *(void **) (buf+sizeof(EEL_BC_TYPE)) = destptr; + } + return sizeof(EEL_BC_TYPE) + sizeof(void *); +} + + #define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE sizeof(EEL_BC_TYPE) + static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) + { + static const EEL_BC_TYPE tab[3] = { + EEL_BC_PUSH_VAL_AT_P1_TO_FPSTACK, + EEL_BC_PUSH_VAL_AT_P2_TO_FPSTACK, + EEL_BC_PUSH_VAL_AT_P3_TO_FPSTACK, + }; + *(EEL_BC_TYPE *)b = tab[wv]; + } + +#define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (sizeof(GLUE_POP_FPSTACK_TO_WTP) + GLUE_SET_PX_FROM_WTP_SIZE) +static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) +{ + GLUE_SET_PX_FROM_WTP(buf,wv); + memcpy(buf + GLUE_SET_PX_FROM_WTP_SIZE,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); +}; + +static unsigned char GLUE_POP_STACK_TO_FPSTACK[1] = { 0 }; // todo + +#define GLUE_INLINE_LOOPS + +// end of bytecode glue, now for stubbage + + +#define BC_DECL_OPCODESZ(n) (1 + ((n)*sizeof(INT_PTR))/sizeof(EEL_BC_TYPE)) +#define BC_DECLASM_N(x,y,n) static EEL_BC_TYPE nseel_asm_##x[1 + BC_DECL_OPCODESZ(n)]={BC_DECL_OPCODESZ(n),EEL_BC_##y }; +#define BC_DECLASM_N2(x,y,n) static EEL_BC_TYPE _asm_##x[1 + BC_DECL_OPCODESZ(n)]={BC_DECL_OPCODESZ(n),EEL_BC_##y }; +#define BC_DECLASM_N_EXPORT(x,y,n) EEL_BC_TYPE _asm_##x[1 + BC_DECL_OPCODESZ(n)]={BC_DECL_OPCODESZ(n),EEL_BC_##y }; +#define BC_DECLASM(x,y) BC_DECLASM_N(x,y,0) + +BC_DECLASM(band,NOP) +BC_DECLASM(bor,NOP) + +BC_DECLASM(bnot,BNOT) +BC_DECLASM(equal,EQUAL) +BC_DECLASM(equal_exact,EQUAL_EXACT) +BC_DECLASM(notequal_exact,NOTEQUAL_EXACT) +BC_DECLASM(notequal,NOTEQUAL) +BC_DECLASM(above,ABOVE) +BC_DECLASM(beloweq,BELOWEQ) + +BC_DECLASM(add,ADD) +BC_DECLASM(sub,SUB) +BC_DECLASM(mul,MUL) +BC_DECLASM(div,DIV) +BC_DECLASM(and,AND) +BC_DECLASM(or,OR) +BC_DECLASM(or0,OR0) +BC_DECLASM(xor,XOR) + +BC_DECLASM(add_op,ADD_OP) +BC_DECLASM(sub_op,SUB_OP) +BC_DECLASM(add_op_fast,ADD_OP_FAST) +BC_DECLASM(sub_op_fast,SUB_OP_FAST) +BC_DECLASM(mul_op,MUL_OP) +BC_DECLASM(div_op,DIV_OP) +BC_DECLASM(mul_op_fast,MUL_OP_FAST) +BC_DECLASM(div_op_fast,DIV_OP_FAST) +BC_DECLASM(and_op,AND_OP) +BC_DECLASM(or_op,OR_OP) +BC_DECLASM(xor_op,XOR_OP) + +BC_DECLASM(uminus,UMINUS) + +BC_DECLASM(assign,ASSIGN) +BC_DECLASM(assign_fast,ASSIGN_FAST) +BC_DECLASM(assign_fast_fromfp,ASSIGN_FAST_FROMFP) +BC_DECLASM(assign_fromfp,ASSIGN_FROMFP) +BC_DECLASM(mod,MOD) +BC_DECLASM(mod_op,MOD_OP) +BC_DECLASM(shr,SHR) +BC_DECLASM(shl,SHL) +BC_DECLASM(sqr,SQR) + +BC_DECLASM(min,MIN) +BC_DECLASM(max,MAX) +BC_DECLASM(min_fp,MIN_FP) +BC_DECLASM(max_fp,MAX_FP) +BC_DECLASM(abs,ABS) +BC_DECLASM(sign,SIGN) +BC_DECLASM(invsqrt,INVSQRT) +BC_DECLASM(dbg_getstackptr,DBG_GETSTACKPTR) + +BC_DECLASM(booltofp,BOOLTOFP) +BC_DECLASM(fptobool,FPTOBOOL) +BC_DECLASM(fptobool_rev,FPTOBOOL_REV) + +BC_DECLASM_N(stack_push,USERSTACK_PUSH,3) +BC_DECLASM_N(stack_pop,USERSTACK_POP,3) +BC_DECLASM_N(stack_pop_fast,USERSTACK_POPFAST,3) +BC_DECLASM_N(stack_peek,USERSTACK_PEEK,3) + +BC_DECLASM_N(stack_peek_int,USERSTACK_PEEK_INT,4) + +BC_DECLASM_N(stack_peek_top,USERSTACK_PEEK_TOP,1) +BC_DECLASM_N(stack_exch,USERSTACK_EXCH,1) + +BC_DECLASM_N(fcall,FCALL,1) + +BC_DECLASM_N(1pdd,CFUNC_1PDD,1) +BC_DECLASM_N(2pdd,CFUNC_2PDD,1) +BC_DECLASM_N(2pdds,CFUNC_2PDDS,1) + +BC_DECLASM_N2(megabuf,MEGABUF,0) +BC_DECLASM_N2(gmegabuf,GMEGABUF,2) + +BC_DECLASM_N_EXPORT(generic1parm,GENERIC1PARM,2) +BC_DECLASM_N_EXPORT(generic2parm,GENERIC2PARM,2) +BC_DECLASM_N_EXPORT(generic3parm,GENERIC3PARM,2) +BC_DECLASM_N_EXPORT(generic1parm_retd,GENERIC1PARM_RETD,2) +BC_DECLASM_N_EXPORT(generic2parm_retd,GENERIC2PARM_RETD,2) +BC_DECLASM_N_EXPORT(generic2xparm_retd,GENERIC2XPARM_RETD,3) +BC_DECLASM_N_EXPORT(generic3parm_retd,GENERIC3PARM_RETD,2) + + + +static void *GLUE_realAddress(void *fn, int *size) +{ + EEL_BC_TYPE *rd = (EEL_BC_TYPE *)fn; + *size = rd[0]*sizeof(EEL_BC_TYPE); + return rd+1; +} + +#define EEL_BC_STACKSIZE (65536) + +// todo: check for stack overflows! we could determine if this is possible at compile time. +#define EEL_BC_STACK_POP_SIZE 8 +#define EEL_BC_STACK_PUSH(type, val) (*(type *)(stackptr -= EEL_BC_STACK_POP_SIZE)) = (val) +#define EEL_BC_STACK_POP() (stackptr += EEL_BC_STACK_POP_SIZE) + +#define EEL_BC_TRUE ((EEL_F*)(INT_PTR)1) + + + + +static void GLUE_CALL_CODE(INT_PTR bp, INT_PTR cp, INT_PTR rt) +{ + char __stack[EEL_BC_STACKSIZE]; + char *iptr = (char*)cp; + char *stackptr=__stack + EEL_BC_STACKSIZE; + EEL_F *p1 = NULL, *p2 = NULL, *p3 = NULL, *wtp = (EEL_F*)bp; +#define fp_top (_fpstacktop[0]) +#define fp_top2 (_fpstacktop[-1]) +#define fp_push(x) *++_fpstacktop=(x) +#define fp_pop() (*_fpstacktop--) +#define fp_rewind(x) (_fpstacktop -= (x)) + + EEL_F fpstack[GLUE_MAX_FPSTACK_SIZE]; + EEL_F *_fpstacktop=fpstack-1; + for (;;) + { + EEL_BC_TYPE inst = *(EEL_BC_TYPE *)iptr; + iptr += sizeof(EEL_BC_TYPE); + switch (inst) + { + case EEL_BC_FXCH: + { + EEL_F a = fp_top; + fp_top=fp_top2; + fp_top2=a; + } + break; + case EEL_BC_POP_FPSTACK: fp_rewind(1); break; + case EEL_BC_NOP: break; + case EEL_BC_RET: + if (EEL_BC_STACK_POP() > __stack+EEL_BC_STACKSIZE) + { + return; + } + iptr = *(void **)(stackptr - EEL_BC_STACK_POP_SIZE); + break; + case EEL_BC_JMP_NC: + iptr += sizeof(GLUE_JMP_TYPE)+*(GLUE_JMP_TYPE *)iptr; + break; + case EEL_BC_JMP_IF_P1_Z: + iptr += p1 ? sizeof(GLUE_JMP_TYPE) : sizeof(GLUE_JMP_TYPE)+*(GLUE_JMP_TYPE *)iptr; + break; + case EEL_BC_JMP_IF_P1_NZ: + iptr += p1 ? sizeof(GLUE_JMP_TYPE)+*(GLUE_JMP_TYPE *)iptr : sizeof(GLUE_JMP_TYPE); + break; + case EEL_BC_MOV_FPTOP_DV: + fp_push(**(EEL_F **)iptr); + iptr += sizeof(void*); + break; + case EEL_BC_MOV_P1_DV: + p1 = *(void **)iptr; + iptr += sizeof(void*); + break; + case EEL_BC_MOV_P2_DV: + p2 = *(void **)iptr; + iptr += sizeof(void*); + break; + case EEL_BC_MOV_P3_DV: + p3 = *(void **)iptr; + iptr += sizeof(void*); + break; + case EEL_BC__RESET_WTP: + wtp = *(void **)iptr; + iptr += sizeof(void*); + break; + case EEL_BC_PUSH_P1: + EEL_BC_STACK_PUSH(void *, p1); + break; + case EEL_BC_PUSH_P1PTR_AS_VALUE: + EEL_BC_STACK_PUSH(EEL_F, *p1); + break; + case EEL_BC_POP_P1: + p1 = *(EEL_F **) stackptr; + EEL_BC_STACK_POP(); + break; + case EEL_BC_POP_P2: + p2 = *(EEL_F **) stackptr; + EEL_BC_STACK_POP(); + break; + case EEL_BC_POP_P3: + p3 = *(EEL_F **) stackptr; + EEL_BC_STACK_POP(); + break; + case EEL_BC_POP_VALUE_TO_ADDR: + **(EEL_F**)iptr = *(EEL_F *)stackptr; + EEL_BC_STACK_POP(); + iptr += sizeof(void*); + break; + case EEL_BC_MOVE_STACK: + stackptr += *(int *)iptr; + iptr += sizeof(int); + break; + case EEL_BC_STORE_P1_TO_STACK_AT_OFFS: + *(void **) (stackptr + *(int *)iptr) = p1; + iptr += sizeof(int); + break; + case EEL_BC_MOVE_STACKPTR_TO_P1: + p1 = (double *)stackptr; + break; + case EEL_BC_MOVE_STACKPTR_TO_P2: + p2 = (double *)stackptr; + break; + case EEL_BC_MOVE_STACKPTR_TO_P3: + p3 = (double *)stackptr; + break; + case EEL_BC_SET_P2_FROM_P1: + p2=p1; + break; + case EEL_BC_SET_P3_FROM_P1: + p3=p1; + break; + case EEL_BC_COPY_VALUE_AT_P1_TO_ADDR: + **(EEL_F **)iptr = *p1; + iptr += sizeof(void*); + break; + case EEL_BC_SET_P1_FROM_WTP: + p1 = wtp; + break; + case EEL_BC_SET_P2_FROM_WTP: + p2 = wtp; + break; + case EEL_BC_SET_P3_FROM_WTP: + p3 = wtp; + break; + case EEL_BC_POP_FPSTACK_TO_PTR: + **((EEL_F **)iptr) = fp_pop(); + iptr += sizeof(void *); + break; + case EEL_BC_POP_FPSTACK_TOSTACK: + EEL_BC_STACK_PUSH(EEL_F, fp_pop()); + break; + case EEL_BC_PUSH_VAL_AT_P1_TO_FPSTACK: + fp_push(*p1); + break; + case EEL_BC_PUSH_VAL_AT_P2_TO_FPSTACK: + fp_push(*p2); + break; + case EEL_BC_PUSH_VAL_AT_P3_TO_FPSTACK: + fp_push(*p3); + break; + case EEL_BC_POP_FPSTACK_TO_WTP: + *wtp++ = fp_pop(); + break; + case EEL_BC_SET_P1_Z: + p1=NULL; + break; + case EEL_BC_SET_P1_NZ: + p1 = EEL_BC_TRUE; + break; + + case EEL_BC_LOOP_LOADCNT: + if ((EEL_BC_STACK_PUSH(int, (int)fp_pop())) < 1) + { + EEL_BC_STACK_POP(); + iptr+= sizeof(GLUE_JMP_TYPE)+*(GLUE_JMP_TYPE *)iptr; + } + else + { + iptr += sizeof(GLUE_JMP_TYPE); +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + if ((*(int *)stackptr) > NSEEL_LOOPFUNC_SUPPORT_MAXLEN) (*(int *)stackptr) = NSEEL_LOOPFUNC_SUPPORT_MAXLEN; +#endif + EEL_BC_STACK_PUSH(void *, wtp); + } + break; + case EEL_BC_LOOP_END: + wtp = *(void **) (stackptr); + if (--(*(int *)(stackptr+EEL_BC_STACK_POP_SIZE)) <= 0) + { + stackptr += EEL_BC_STACK_POP_SIZE*2; + iptr += sizeof(GLUE_JMP_TYPE); + } + else + { + iptr += sizeof(GLUE_JMP_TYPE)+*(GLUE_JMP_TYPE *)iptr; // back to the start! + } + break; + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + case EEL_BC_WHILE_SETUP: + EEL_BC_STACK_PUSH(int,NSEEL_LOOPFUNC_SUPPORT_MAXLEN); + break; +#endif + case EEL_BC_WHILE_BEGIN: + EEL_BC_STACK_PUSH(void *, wtp); + break; + case EEL_BC_WHILE_END: + wtp = *(EEL_F **) stackptr; + EEL_BC_STACK_POP(); + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + if (--(*(int *)stackptr) <= 0) + { + EEL_BC_STACK_POP(); + iptr += sizeof(GLUE_JMP_TYPE)+*(GLUE_JMP_TYPE *)iptr; // endpt + } + else + { + iptr += sizeof(GLUE_JMP_TYPE); + } +#endif + break; + case EEL_BC_WHILE_CHECK_RV: + if (p1) + { + iptr += sizeof(GLUE_JMP_TYPE)+*(GLUE_JMP_TYPE *)iptr; // loop + } + else + { + // done +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 + EEL_BC_STACK_POP(); +#endif + iptr += sizeof(GLUE_JMP_TYPE); + } + break; + case EEL_BC_BNOT: + p1 = p1 ? NULL : EEL_BC_TRUE; + break; + case EEL_BC_EQUAL: + p1 = fabs(fp_top - fp_top2) < NSEEL_CLOSEFACTOR ? EEL_BC_TRUE : NULL; + fp_rewind(2); + break; + case EEL_BC_EQUAL_EXACT: + p1 = fp_top == fp_top2 ? EEL_BC_TRUE : NULL; + fp_rewind(2); + break; + case EEL_BC_NOTEQUAL: + p1 = fabs(fp_top - fp_top2) >= NSEEL_CLOSEFACTOR ? EEL_BC_TRUE : NULL; + fp_rewind(2); + break; + case EEL_BC_NOTEQUAL_EXACT: + p1 = fp_top != fp_top2 ? EEL_BC_TRUE : NULL; + fp_rewind(2); + break; + case EEL_BC_ABOVE: + p1 = fp_top < fp_top2 ? EEL_BC_TRUE : NULL; + fp_rewind(2); + break; + case EEL_BC_BELOWEQ: + p1 = fp_top >= fp_top2 ? EEL_BC_TRUE : NULL; + fp_rewind(2); + break; + + case EEL_BC_ADD: + fp_top2 += fp_top; + fp_rewind(1); + break; + case EEL_BC_SUB: + fp_top2 -= fp_top; + fp_rewind(1); + break; + case EEL_BC_MUL: + fp_top2 *= fp_top; + fp_rewind(1); + break; + case EEL_BC_DIV: + fp_top2 /= fp_top; + fp_rewind(1); + break; + case EEL_BC_AND: + fp_top2 = (EEL_F) (((WDL_INT64)fp_top) & (WDL_INT64)(fp_top2)); + fp_rewind(1); + break; + case EEL_BC_OR: + fp_top2 = (EEL_F) (((WDL_INT64)fp_top) | (WDL_INT64)(fp_top2)); + fp_rewind(1); + break; + case EEL_BC_OR0: + fp_top = (EEL_F) ((WDL_INT64)(fp_top)); + break; + case EEL_BC_XOR: + fp_top2 = (EEL_F) (((WDL_INT64)fp_top) ^ (WDL_INT64)(fp_top2)); + fp_rewind(1); + break; + + case EEL_BC_ADD_OP: + *(p1 = p2) = denormal_filter_double2(*p2 + fp_pop()); + break; + case EEL_BC_SUB_OP: + *(p1 = p2) = denormal_filter_double2(*p2 - fp_pop()); + break; + case EEL_BC_ADD_OP_FAST: + *(p1 = p2) += fp_pop(); + break; + case EEL_BC_SUB_OP_FAST: + *(p1 = p2) -= fp_pop(); + break; + case EEL_BC_MUL_OP: + *(p1 = p2) = denormal_filter_double2(*p2 * fp_pop()); + break; + case EEL_BC_DIV_OP: + *(p1 = p2) = denormal_filter_double2(*p2 / fp_pop()); + break; + case EEL_BC_MUL_OP_FAST: + *(p1 = p2) *= fp_pop(); + break; + case EEL_BC_DIV_OP_FAST: + *(p1 = p2) /= fp_pop(); + break; + case EEL_BC_AND_OP: + p1 = p2; + *p2 = (EEL_F) (((WDL_INT64)*p2) & (WDL_INT64)fp_pop()); + break; + case EEL_BC_OR_OP: + p1 = p2; + *p2 = (EEL_F) (((WDL_INT64)*p2) | (WDL_INT64)fp_pop()); + break; + case EEL_BC_XOR_OP: + p1 = p2; + *p2 = (EEL_F) (((WDL_INT64)*p2) ^ (WDL_INT64)fp_pop()); + break; + case EEL_BC_UMINUS: + fp_top = -fp_top; + break; + case EEL_BC_ASSIGN: + *p2 = denormal_filter_double2(*p1); + p1 = p2; + break; + case EEL_BC_ASSIGN_FAST: + *p2 = *p1; + p1 = p2; + break; + case EEL_BC_ASSIGN_FAST_FROMFP: + *p2 = fp_pop(); + p1 = p2; + break; + case EEL_BC_ASSIGN_FROMFP: + *p2 = denormal_filter_double2(fp_pop()); + p1 = p2; + break; + case EEL_BC_MOD: + { + int a = (int) fabs(fp_pop()); + fp_top = a ? (EEL_F) (((WDL_INT64)fabs(fp_top)) % a) : 0.0; + } + break; + case EEL_BC_MOD_OP: + { + int a = (int) fabs(fp_pop()); + *p2 = a ? (EEL_F) (((WDL_INT64)fabs(*p2)) % a) : 0.0; + p1=p2; + + } + break; + case EEL_BC_SHR: + fp_top2 = (EEL_F) (((int)fp_top2) >> (int)fp_top); + fp_rewind(1); + break; + case EEL_BC_SHL: + fp_top2 = (EEL_F) (((int)fp_top2) << (int)fp_top); + fp_rewind(1); + break; + case EEL_BC_SQR: + fp_top *= fp_top; + break; + case EEL_BC_MIN: + if (*p1 > *p2) p1 = p2; + break; + case EEL_BC_MAX: + if (*p1 < *p2) p1 = p2; + break; + case EEL_BC_MIN_FP: + { + EEL_F a=fp_pop(); + if (afp_top) fp_top=a; + } + break; + case EEL_BC_ABS: + fp_top = fabs(fp_top); + break; + case EEL_BC_SIGN: + if (fp_top<0.0) fp_top=-1.0; + else if (fp_top>0.0) fp_top=1.0; + break; + case EEL_BC_DBG_GETSTACKPTR: + fp_top = (int)(stackptr - __stack); + break; + case EEL_BC_INVSQRT: + { + float y = (float)fp_top; + int i = 0x5f3759df - ( (* (int *) &y) >> 1 ); + y = *(float *) &i; + fp_top = y * ( 1.5F - ( (fp_top * 0.5) * y * y ) ); + } + break; + case EEL_BC_FCALL: + { + char *newiptr = *(char **)iptr; + EEL_BC_STACK_PUSH(void *, (iptr += sizeof(void *))); + iptr = newiptr; + } + break; + case EEL_BC_BOOLTOFP: + fp_push(p1 ? 1.0 : 0.0); + break; + case EEL_BC_FPTOBOOL: + p1 = fabs(fp_pop()) >= NSEEL_CLOSEFACTOR ? EEL_BC_TRUE : NULL; + break; + case EEL_BC_FPTOBOOL_REV: + p1 = fabs(fp_pop()) < NSEEL_CLOSEFACTOR ? EEL_BC_TRUE : NULL; + break; + + case EEL_BC_CFUNC_1PDD: + { + double (*f)(double) = *(double (**)(double)) iptr; + fp_top = f(fp_top); + iptr += sizeof(void *); + } + break; + case EEL_BC_CFUNC_2PDD: + { + double (*f)(double,double) = *(double (**)(double,double))iptr; + fp_top2 = f(fp_top2,fp_top); + fp_rewind(1); + iptr += sizeof(void *); + } + break; + case EEL_BC_CFUNC_2PDDS: + { + double (*f)(double,double) = *(double (**)(double,double))iptr; + *p2 = f(*p2,fp_pop()); + p1 = p2; + iptr += sizeof(void *); + } + break; + + case EEL_BC_MEGABUF: + { + unsigned int idx=(unsigned int) (fp_pop() + NSEEL_CLOSEFACTOR); + EEL_F **f = (EEL_F **)rt,*f2; + p1 = (idx < NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK && (f2=f[idx/NSEEL_RAM_ITEMSPERBLOCK])) ? + (f2 + (idx&(NSEEL_RAM_ITEMSPERBLOCK-1))) : + __NSEEL_RAMAlloc((void*)rt,idx); + } + break; + case EEL_BC_GMEGABUF: + { + p1 = __NSEEL_RAMAllocGMEM(*(EEL_F ****)iptr,(int) (fp_pop() + NSEEL_CLOSEFACTOR)); + iptr += sizeof(void *)*2; // also includes ptr to __NSEEL_RAMAllocGMEM, which we ignore + } + break; + case EEL_BC_GENERIC1PARM: + { + EEL_F *(*f)(void *,EEL_F*) = *(EEL_F *(**)(void *, EEL_F *)) (iptr+sizeof(void *)); + p1 = f(*(void **)iptr,p1); + iptr += sizeof(void *)*2; + } + break; + case EEL_BC_GENERIC2PARM: + { + EEL_F *(*f)(void *,EEL_F*,EEL_F*) = *(EEL_F *(**)(void *, EEL_F *, EEL_F *)) (iptr+sizeof(void *)); + p1 = f(*(void **)iptr,p2, p1); + iptr += sizeof(void *)*2; + } + break; + case EEL_BC_GENERIC3PARM: + { + EEL_F *(*f)(void *,EEL_F*,EEL_F*,EEL_F*) = *(EEL_F *(**)(void *, EEL_F *, EEL_F *, EEL_F *)) (iptr+sizeof(void *)); + p1 = f(*(void **)iptr,p3, p2, p1); + iptr += sizeof(void *)*2; + } + break; + case EEL_BC_GENERIC1PARM_RETD: + { + EEL_F (*f)(void *,EEL_F*) = *(EEL_F (**)(void *, EEL_F *)) (iptr+sizeof(void *)); + fp_push(f(*(void **)iptr,p1)); + iptr += sizeof(void *)*2; + } + break; + case EEL_BC_GENERIC2PARM_RETD: + { + EEL_F (*f)(void *,EEL_F*,EEL_F*) = *(EEL_F (**)(void *, EEL_F *, EEL_F *)) (iptr+sizeof(void *)); + fp_push(f(*(void **)iptr,p2, p1)); + iptr += sizeof(void *)*2; + } + break; + case EEL_BC_GENERIC2XPARM_RETD: + { + EEL_F (*f)(void *,void *,EEL_F*,EEL_F*) = *(EEL_F (**)(void *, void *, EEL_F *, EEL_F *)) (iptr+2*sizeof(void *)); + fp_push(f(*(void **)iptr,((void **)iptr)[1],p2, p1)); + iptr += sizeof(void *)*3; + } + break; + case EEL_BC_GENERIC3PARM_RETD: + { + EEL_F (*f)(void *,EEL_F*,EEL_F*,EEL_F*) = *(EEL_F (**)(void *, EEL_F *, EEL_F *, EEL_F *)) (iptr+sizeof(void *)); + fp_push(f(*(void **)iptr,p3, p2, p1)); + iptr += sizeof(void *)*2; + } + break; + + case EEL_BC_USERSTACK_PUSH: + { + UINT_PTR *sptr = *(UINT_PTR **)iptr; + (*sptr) += 8; + (*sptr) &= *(UINT_PTR*)(iptr+sizeof(void *)); + (*sptr) |= *(UINT_PTR*)(iptr+2*sizeof(void *)); + *(EEL_F *)*sptr = *p1; + } + iptr += sizeof(void*)*3; + break; + case EEL_BC_USERSTACK_POP: + { + UINT_PTR *sptr = *(UINT_PTR **)iptr; + *p1 = *(EEL_F *)*sptr; + (*sptr) -= 8; + (*sptr) &= *(UINT_PTR*)(iptr+sizeof(void *)); + (*sptr) |= *(UINT_PTR*)(iptr+2*sizeof(void *)); + } + iptr += sizeof(void*)*3; + break; + case EEL_BC_USERSTACK_POPFAST: + { + UINT_PTR *sptr = *(UINT_PTR **)iptr; + p1 = (EEL_F *)*sptr; + (*sptr) -= 8; + (*sptr) &= *(UINT_PTR*)(iptr+sizeof(void *)); + (*sptr) |= *(UINT_PTR*)(iptr+2*sizeof(void *)); + } + iptr += sizeof(void*)*3; + break; + case EEL_BC_USERSTACK_PEEK: + { + UINT_PTR sptr = **(UINT_PTR **)iptr; + sptr -= sizeof(EEL_F) * (int)(fp_pop()); + sptr &= *(UINT_PTR*)(iptr+sizeof(void *)); + sptr |= *(UINT_PTR*)(iptr+2*sizeof(void *)); + p1 = (EEL_F *)sptr; + } + iptr += sizeof(void*)*3; + break; + case EEL_BC_USERSTACK_PEEK_INT: + { + UINT_PTR sptr = **(UINT_PTR **)iptr; + sptr -= *(UINT_PTR*)(iptr+sizeof(void*)); + sptr &= *(UINT_PTR*)(iptr+2*sizeof(void *)); + sptr |= *(UINT_PTR*)(iptr+3*sizeof(void *)); + p1 = (EEL_F *)sptr; + } + iptr += sizeof(void*)*4; + break; + case EEL_BC_USERSTACK_PEEK_TOP: + p1 = **(EEL_F ***)iptr; + iptr += sizeof(void*); + break; + case EEL_BC_USERSTACK_EXCH: + { + EEL_F *p=**(EEL_F ***)iptr; + EEL_F a=*p; + *p=*p1; + *p1=a; + } + iptr += sizeof(void*); + break; + } + } +#undef fp_top +#undef fp_top2 +#undef fp_pop +#undef fp_push +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_ppc.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_ppc.h new file mode 100644 index 0000000..204959e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_ppc.h @@ -0,0 +1,285 @@ +#ifndef _NSEEL_GLUE_PPC_H_ +#define _NSEEL_GLUE_PPC_H_ + +#define GLUE_MAX_FPSTACK_SIZE 0 // no stack support +#define GLUE_MAX_JMPSIZE 30000 // maximum relative jump size for this arch (if not defined, any jump is possible) + + +// endOfInstruction is end of jump with relative offset, offset passed in is offset from end of dest instruction. +// on PPC the offset needs to be from the start of the instruction (hence +4), and also the low two bits are flags so +// we make sure they are clear (they should always be clear, anyway, since we always generate 4 byte instructions) +#define GLUE_JMP_SET_OFFSET(endOfInstruction,offset) (((short *)(endOfInstruction))[-1] = ((offset) + 4) & 0xFFFC) + +static const unsigned char GLUE_JMP_NC[] = { 0x48,0, 0, 0, }; // b + +static const unsigned int GLUE_JMP_IF_P1_Z[]= +{ + 0x2f830000, //cmpwi cr7, r3, 0 + 0x419e0000, // beq cr7, offset-bytes-from-startofthisinstruction +}; +static const unsigned int GLUE_JMP_IF_P1_NZ[]= +{ + 0x2f830000, //cmpwi cr7, r3, 0 + 0x409e0000, // bne cr7, offset-bytes-from-startofthisinstruction +}; + + +#define GLUE_MOV_PX_DIRECTVALUE_SIZE 8 +static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wv) +{ + static const unsigned short tab[3][2] = { + {0x3C60, 0x6063}, // addis r3, r0, hw -- ori r3,r3, lw + {0x3DC0, 0x61CE}, // addis r14, r0, hw -- ori r14, r14, lw + {0x3DE0, 0x61EF}, // addis r15, r0, hw -- oris r15, r15, lw + }; + unsigned int uv=(unsigned int)v; + unsigned short *p=(unsigned short *)b; + + *p++ = tab[wv][0]; // addis rX, r0, hw + *p++ = (uv>>16)&0xffff; + *p++ = tab[wv][1]; // ori rX, rX, lw + *p++ = uv&0xffff; +} + + +// mflr r5 +// stwu r5, -16(r1) +const static unsigned int GLUE_FUNC_ENTER[2] = { 0x7CA802A6, 0x94A1FFF0 }; +#define GLUE_FUNC_ENTER_SIZE 8 + +// lwz r5, 0(r1) +// addi r1, r1, 16 +// mtlr r5 +const static unsigned int GLUE_FUNC_LEAVE[3] = { 0x80A10000, 0x38210010, 0x7CA803A6 }; +#define GLUE_FUNC_LEAVE_SIZE 12 + +const static unsigned int GLUE_RET[]={0x4E800020}; // blr + +static int GLUE_RESET_WTP(unsigned char *out, void *ptr) +{ + const static unsigned int GLUE_SET_WTP_FROM_R17=0x7E308B78; // mr r16 (dest), r17 (src) + if (out) memcpy(out,&GLUE_SET_WTP_FROM_R17,sizeof(GLUE_SET_WTP_FROM_R17)); + return sizeof(GLUE_SET_WTP_FROM_R17); + +} + + + +// stwu r3, -16(r1) +const static unsigned int GLUE_PUSH_P1[1]={ 0x9461FFF0}; + + +#define GLUE_POP_PX_SIZE 8 +static void GLUE_POP_PX(void *b, int wv) +{ + static const unsigned int tab[3] ={ + 0x80610000, // lwz r3, 0(r1) + 0x81c10000, // lwz r14, 0(r1) + 0x81e10000, // lwz r15, 0(r1) + }; + ((unsigned int *)b)[0] = tab[wv]; + ((unsigned int *)b)[1] = 0x38210010; // addi r1,r1, 16 +} + +#define GLUE_SET_PX_FROM_P1_SIZE 4 +static void GLUE_SET_PX_FROM_P1(void *b, int wv) +{ + static const unsigned int tab[3]={ + 0x7c631b78, // never used: mr r3, r3 + 0x7c6e1b78, // mr r14, r3 + 0x7c6f1b78, // mr r15, r3 + }; + *(unsigned int *)b = tab[wv]; +} + + + +// lfd f2, 0(r3) +// stfdu f2, -16(r1) +static const unsigned int GLUE_PUSH_P1PTR_AS_VALUE[] = { 0xC8430000, 0xDC41FFF0 }; + +static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) +{ + // lfd f2, 0(r1) + // addi r1,r1,16 + // GLUE_MOV_PX_DIRECTVALUE_GEN / GLUE_MOV_PX_DIRECTVALUE_SIZE (r3) + // stfd f2, 0(r3) + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + *bufptr++ = 0xC8410000; + *bufptr++ = 0x38210010; + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + *bufptr++ = 0xd8430000; + } + return 2*4 + GLUE_MOV_PX_DIRECTVALUE_SIZE + 4; +} + +static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) +{ + // lfd f2, 0(r3) + // GLUE_MOV_PX_DIRECTVALUE_GEN / GLUE_MOV_PX_DIRECTVALUE_SIZE (r3) + // stfd f2, 0(r3) + + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + *bufptr++ = 0xc8430000; + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + *bufptr++ = 0xd8430000; + } + + return 4 + GLUE_MOV_PX_DIRECTVALUE_SIZE + 4; +} + + +static void GLUE_CALL_CODE(INT_PTR bp, INT_PTR cp, INT_PTR rt) +{ + static const double consttab[] = { + NSEEL_CLOSEFACTOR, + 4503601774854144.0 /* 0x43300000, 0x80000000, used for integer conversion*/, + }; + // we could have r18 refer to the current user-stack pointer, someday, perhaps + __asm__( + "subi r1, r1, 128\n" + "stfd f31, 8(r1)\n" + "stfd f30, 16(r1)\n" + "stmw r13, 32(r1)\n" + "mtctr %0\n" + "mr r17, %1\n" + "mr r13, %2\n" + "lfd f31, 0(%3)\n" + "lfd f30, 8(%3)\n" + "subi r17, r17, 8\n" + "mflr r0\n" + "stw r0, 24(r1)\n" + "bctrl\n" + "lwz r0, 24(r1)\n" + "mtlr r0\n" + "lmw r13, 32(r1)\n" + "lfd f31, 8(r1)\n" + "lfd f30, 16(r1)\n" + "addi r1, r1, 128\n" + ::"r" (cp), "r" (bp), "r" (rt), "r" (consttab)); +}; + +static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) +{ + // 64 bit ppc would take some work + unsigned int *p=(unsigned int *)_p; + while ((p[0]&0x0000FFFF) != 0x0000dead && + (p[1]&0x0000FFFF) != 0x0000beef) p++; + p[0] = (p[0]&0xFFFF0000) | (((newv)>>16)&0xFFFF); + p[1] = (p[1]&0xFFFF0000) | ((newv)&0xFFFF); + + return (unsigned char *)(p+1); +} + + #define GLUE_SET_PX_FROM_WTP_SIZE sizeof(int) + static void GLUE_SET_PX_FROM_WTP(void *b, int wv) + { + static const unsigned int tab[3]={ + 0x7e038378, // mr r3, r16 + 0x7e0e8378, // mr r14, r16 + 0x7e0f8378, // mr r15, r16 + }; + *(unsigned int *)b = tab[wv]; + } + static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) + { + // set r3 to destptr + // stfd f1, 0(r3) + if (buf) + { + unsigned int *bufptr = (unsigned int *)buf; + GLUE_MOV_PX_DIRECTVALUE_GEN(bufptr, (INT_PTR)destptr,0); + bufptr += GLUE_MOV_PX_DIRECTVALUE_SIZE/4; + + *bufptr++ = 0xD8230000; // stfd f1, 0(r3) + } + return GLUE_MOV_PX_DIRECTVALUE_SIZE + sizeof(int); + } + #define GLUE_POP_FPSTACK_SIZE 0 + static const unsigned int GLUE_POP_FPSTACK[1] = { 0 }; // no need to pop, not a stack + + static const unsigned int GLUE_POP_FPSTACK_TOSTACK[] = { + 0xdc21fff0, // stfdu f1, -16(r1) + }; + + static const unsigned int GLUE_POP_FPSTACK_TO_WTP[] = { + 0xdc300008, // stfdu f1, 8(r16) + }; + + #define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE 4 + static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) + { + static const unsigned int tab[3] = { + 0xC8230000, // lfd f1, 0(r3) + 0xC82E0000, // lfd f1, 0(r14) + 0xC82F0000, // lfd f1, 0(r15) + }; + *(unsigned int *)b = tab[wv]; + } + +#define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (sizeof(GLUE_POP_FPSTACK_TO_WTP) + GLUE_SET_PX_FROM_WTP_SIZE) +static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) +{ + memcpy(buf,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); + GLUE_SET_PX_FROM_WTP(buf + sizeof(GLUE_POP_FPSTACK_TO_WTP),wv); // ppc preincs the WTP, so we do this after +}; + +static unsigned int GLUE_POP_STACK_TO_FPSTACK[1] = { 0 }; // todo + + +static const unsigned int GLUE_SET_P1_Z[] = { 0x38600000 }; // li r3, 0 +static const unsigned int GLUE_SET_P1_NZ[] = { 0x38600001 }; // li r3, 1 + + +static void *GLUE_realAddress(void *fn, int *size) +{ + // magic numbers: mr r0,r0 ; mr r1,r1 ; mr r2, r2 + static const unsigned char sig[12] = { 0x7c, 0x00, 0x03, 0x78, 0x7c, 0x21, 0x0b, 0x78, 0x7c, 0x42, 0x13, 0x78 }; + unsigned char *p = (unsigned char *)fn; + + while (memcmp(p,sig,sizeof(sig))) p+=4; + p+=sizeof(sig); + fn = p; + + while (memcmp(p,sig,sizeof(sig))) p+=4; + *size = p - (unsigned char *)fn; + return fn; +} + + #define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(x) 4 + static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) + { + // limited to 32k offset + *(unsigned int *)b = 0x90610000 + (offs&0xffff); + } + + #define GLUE_MOVE_PX_STACKPTR_SIZE 4 + static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) + { + static const unsigned int tab[3] = + { + 0x7c230b78, // mr r3, r1 + 0x7c2e0b78, // mr r14, r1 + 0x7c2f0b78, // mr r15, r1 + }; + * (unsigned int *)b = tab[wv]; + } + + #define GLUE_MOVE_STACK_SIZE 4 + static void GLUE_MOVE_STACK(void *b, int amt) + { + // this should be updated to allow for more than 32k moves, but no real need + ((unsigned int *)b)[0] = 0x38210000 + (amt&0xffff); // addi r1,r1, amt + } + + + +// end of ppc + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_x86.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_x86.h new file mode 100644 index 0000000..d987e6a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_x86.h @@ -0,0 +1,678 @@ +#ifndef _NSEEL_GLUE_X86_H_ +#define _NSEEL_GLUE_X86_H_ + +#define GLUE_MAX_FPSTACK_SIZE 8 + +// endOfInstruction is end of jump with relative offset, offset is offset from end of instruction to jump to +#define GLUE_JMP_SET_OFFSET(endOfInstruction,offset) (((int *)(endOfInstruction))[-1] = (offset)) + +static const unsigned char GLUE_JMP_NC[] = { 0xE9, 0,0,0,0, }; // jmp +static const unsigned char GLUE_JMP_IF_P1_Z[] = {0x85, 0xC0, 0x0F, 0x84, 0,0,0,0 }; // test eax, eax, jz +static const unsigned char GLUE_JMP_IF_P1_NZ[] = {0x85, 0xC0, 0x0F, 0x85, 0,0,0,0 }; // test eax, eax, jnz + +#define GLUE_FUNC_ENTER_SIZE 0 +#define GLUE_FUNC_LEAVE_SIZE 0 +const static unsigned int GLUE_FUNC_ENTER[1]; +const static unsigned int GLUE_FUNC_LEAVE[1]; + + // x86 + // stack is 16 byte aligned + // when pushing values to stack, alignment pushed first, then value (value is at the lower address) + // when pushing pointers to stack, alignment pushed first, then pointer (pointer is at the lower address) + + static const unsigned char GLUE_PUSH_P1PTR_AS_VALUE[] = + { + 0x83, 0xEC, 8, /* sub esp, 8 */ + 0xff, 0x70, 0x4, /* push dword [eax+4] */ + 0xff, 0x30, /* push dword [eax] */ + }; + + static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) + { + if (buf) + { + *buf++ = 0xB8; *(void **) buf = destptr; buf+=4; // mov eax, directvalue + + *buf++ = 0x8f; *buf++ = 0x00; // pop dword [eax] + *buf++ = 0x8f; *buf++ = 0x40; *buf++ = 4; // pop dword [eax+4] + + *buf++ = 0x59; // pop ecx (alignment) + *buf++ = 0x59; // pop ecx (alignment) + } + + return 12; + } + + static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) + { + if (buf) + { + *buf++ = 0x8B; *buf++ = 0x38; // mov edi, [eax] + *buf++ = 0x8B; *buf++ = 0x48; *buf++ = 0x04; // mov ecx, [eax+4] + + + *buf++ = 0xB8; *(void **) buf = destptr; buf+=4; // mov eax, directvalue + *buf++ = 0x89; *buf++ = 0x38; // mov [eax], edi + *buf++ = 0x89; *buf++ = 0x48; *buf++ = 0x04; // mov [eax+4], ecx + } + + return 2 + 3 + 5 + 2 + 3; + } + + static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) + { + if (buf) + { + *buf++ = 0xB8; *(void **) buf = destptr; buf+=4; // mov eax, directvalue + *buf++ = 0xDD; *buf++ = 0x18; // fstp qword [eax] + } + return 1+4+2; + } + + + #define GLUE_MOV_PX_DIRECTVALUE_SIZE 5 + #define GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE 6 // length when wv == -1 + + static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wv) + { + if (wv==-1) + { + const static unsigned char t[2] = {0xDD, 0x05}; + memcpy(b,t,2); + b= ((unsigned char *)b)+2; + } + else + { + const static unsigned char tab[3] = { + 0xB8 /* mov eax, dv*/, + 0xBF /* mov edi, dv */ , + 0xB9 /* mov ecx, dv */ + }; + *((unsigned char *)b) = tab[wv]; // mov eax, dv + b= ((unsigned char *)b)+1; + } + *(INT_PTR *)b = v; + } + const static unsigned char GLUE_PUSH_P1[4]={0x83, 0xEC, 12, 0x50}; // sub esp, 12, push eax + + #define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(x) 7 + static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) + { + ((unsigned char *)b)[0] = 0x89; // mov [esp+offs], eax + ((unsigned char *)b)[1] = 0x84; + ((unsigned char *)b)[2] = 0x24; + *(int *)((unsigned char *)b+3) = offs; + } + + #define GLUE_MOVE_PX_STACKPTR_SIZE 2 + static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) + { + static const unsigned char tab[3][GLUE_MOVE_PX_STACKPTR_SIZE]= + { + { 0x89, 0xe0 }, // mov eax, esp + { 0x89, 0xe7 }, // mov edi, esp + { 0x89, 0xe1 }, // mov ecx, esp + }; + memcpy(b,tab[wv],GLUE_MOVE_PX_STACKPTR_SIZE); + } + + #define GLUE_MOVE_STACK_SIZE 6 + static void GLUE_MOVE_STACK(void *b, int amt) + { + ((unsigned char *)b)[0] = 0x81; + if (amt <0) + { + ((unsigned char *)b)[1] = 0xEC; + *(int *)((char*)b+2) = -amt; // sub esp, -amt + } + else + { + ((unsigned char *)b)[1] = 0xc4; + *(int *)((char*)b+2) = amt; // add esp, amt + } + } + + + #define GLUE_POP_PX_SIZE 4 + static void GLUE_POP_PX(void *b, int wv) + { + static const unsigned char tab[3][GLUE_POP_PX_SIZE]= + { + {0x58,/*pop eax*/ 0x83, 0xC4, 12 /* add esp, 12*/}, + {0x5F,/*pop edi*/ 0x83, 0xC4, 12}, + {0x59,/*pop ecx*/ 0x83, 0xC4, 12}, + }; + memcpy(b,tab[wv],GLUE_POP_PX_SIZE); + } + + #define GLUE_SET_PX_FROM_P1_SIZE 2 + static void GLUE_SET_PX_FROM_P1(void *b, int wv) + { + static const unsigned char tab[3][GLUE_SET_PX_FROM_P1_SIZE]={ + {0x90,0x90}, // should never be used! (nopnop) + {0x89,0xC7}, // mov edi, eax + {0x89,0xC1}, // mov ecx, eax + }; + memcpy(b,tab[wv],GLUE_SET_PX_FROM_P1_SIZE); + } + + #define GLUE_POP_FPSTACK_SIZE 2 + static const unsigned char GLUE_POP_FPSTACK[2] = { 0xDD, 0xD8 }; // fstp st0 + + static const unsigned char GLUE_POP_FPSTACK_TOSTACK[] = { + 0x83, 0xEC, 16, // sub esp, 16 + 0xDD, 0x1C, 0x24 // fstp qword (%esp) + }; + + static const unsigned char GLUE_POP_STACK_TO_FPSTACK[] = { + 0xDD, 0x04, 0x24, // fld qword (%esp) + 0x83, 0xC4, 16 // add esp, 16 + }; + + static const unsigned char GLUE_POP_FPSTACK_TO_WTP[] = { + 0xDD, 0x1E, /* fstp qword [esi] */ + 0x83, 0xC6, 8, /* add esi, 8 */ + }; + + #define GLUE_SET_PX_FROM_WTP_SIZE 2 + static void GLUE_SET_PX_FROM_WTP(void *b, int wv) + { + static const unsigned char tab[3][GLUE_SET_PX_FROM_WTP_SIZE]={ + {0x89,0xF0}, // mov eax, esi + {0x89,0xF7}, // mov edi, esi + {0x89,0xF1}, // mov ecx, esi + }; + memcpy(b,tab[wv],GLUE_SET_PX_FROM_WTP_SIZE); + } + + #define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE 2 + static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) + { + static const unsigned char tab[3][GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE]={ + {0xDD,0x00}, // fld qword [eax] + {0xDD,0x07}, // fld qword [edi] + {0xDD,0x01}, // fld qword [ecx] + }; + memcpy(b,tab[wv],GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE); + } + +#define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (GLUE_SET_PX_FROM_WTP_SIZE + sizeof(GLUE_POP_FPSTACK_TO_WTP)) +static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) +{ + GLUE_SET_PX_FROM_WTP(buf,wv); + memcpy(buf + GLUE_SET_PX_FROM_WTP_SIZE,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); +}; + + +const static unsigned char GLUE_RET=0xC3; + +static int GLUE_RESET_WTP(unsigned char *out, void *ptr) +{ + if (out) + { + *out++ = 0xBE; // mov esi, constant + memcpy(out,&ptr,sizeof(void *)); + out+=sizeof(void *); + } + return 1+sizeof(void *); +} + + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4731) +#endif + +#define GLUE_TABPTR_IGNORED +#define GLUE_CALL_CODE(bp, cp, rt) do { \ + if (h->compile_flags&NSEEL_CODE_COMPILE_FLAG_NOFPSTATE) eel_callcode32_fast(cp, rt); \ + else eel_callcode32(cp, rt);\ + } while(0) + +static void eel_callcode32(INT_PTR cp, INT_PTR ramptr) +{ + #ifndef NSEEL_EEL1_COMPAT_MODE + short oldsw, newsw; + #endif + #ifdef _MSC_VER + + __asm + { +#ifndef NSEEL_EEL1_COMPAT_MODE + fnstcw [oldsw] + mov ax, [oldsw] + or ax, 0x23F // 53 or 64 bit precision (depending on whether 0x100 is set), and masking all exceptions + mov [newsw], ax + fldcw [newsw] +#endif + + mov eax, cp + mov ebx, ramptr + + pushad + mov ebp, esp + and esp, -16 + + // on win32, which _MSC_VER implies, we keep things aligned to 16 bytes, and if we call a win32 function, + // the stack is 16 byte aligned before the call, meaning that if calling a function with no frame pointer, + // the stack would be aligned to a 16 byte boundary +4, which isn't good for performance. Having said that, + // normally we compile with frame pointers (which brings that to 16 byte + 8, which is fine), or ICC, which + // for nontrivial functions will align the stack itself (for very short functions, it appears to weigh the + // cost of aligning the stack vs that of the slower misaligned double accesses). + + // it may be worthwhile (at some point) to put some logic in the code that calls out to functions + // (generic1parm etc) to detect which alignment would be most optimal. + sub esp, 12 + call eax + mov esp, ebp + popad +#ifndef NSEEL_EEL1_COMPAT_MODE + fldcw [oldsw] +#endif + }; + + #else // gcc x86 + __asm__( +#ifndef NSEEL_EEL1_COMPAT_MODE + "fnstcw %2\n" + "movw %2, %%ax\n" + "orw $0x23F, %%ax\n" // 53 or 64 bit precision (depending on whether 0x100 is set), and masking all exceptions + "movw %%ax, %3\n" + "fldcw %3\n" +#endif + "pushl %%ebx\n" + "movl %%ecx, %%ebx\n" + "pushl %%ebp\n" + "movl %%esp, %%ebp\n" + "andl $-16, %%esp\n" // align stack to 16 bytes + "subl $12, %%esp\n" // call will push 4 bytes on stack, align for that + "call *%%edx\n" + "leave\n" + "popl %%ebx\n" +#ifndef NSEEL_EEL1_COMPAT_MODE + "fldcw %2\n" +#endif + :: + "d" (cp), "c" (ramptr) +#ifndef NSEEL_EEL1_COMPAT_MODE + , "m" (oldsw), "m" (newsw) +#endif + : "%eax","%esi","%edi"); + #endif //gcc x86 +} + +void eel_enterfp(int s[2]) +{ + #ifdef _MSC_VER + __asm + { + mov ecx, s + fnstcw [ecx] + mov ax, [ecx] + or ax, 0x23F // 53 or 64 bit precision (depending on whether 0x100 is set), and masking all exceptions + mov [ecx+4], ax + fldcw [ecx+4] + }; + #else + __asm__( + "fnstcw (%%ecx)\n" + "movw (%%ecx), %%ax\n" + "orw $0x23F, %%ax\n" // 53 or 64 bit precision (depending on whether 0x100 is set), and masking all exceptions + "movw %%ax, 4(%%ecx)\n" + "fldcw 4(%%ecx)\n" + :: "c" (s) : "%eax"); + #endif +} +void eel_leavefp(int s[2]) +{ + #ifdef _MSC_VER + __asm + { + mov ecx, s + fldcw [ecx] + }; + #else + __asm__( + "fldcw (%%ecx)\n" + :: "c" (s) : "%eax"); + #endif +} + +static void eel_callcode32_fast(INT_PTR cp, INT_PTR ramptr) +{ + #ifdef _MSC_VER + + __asm + { + mov eax, cp + mov ebx, ramptr + + pushad + mov ebp, esp + and esp, -16 + + // on win32, which _MSC_VER implies, we keep things aligned to 16 bytes, and if we call a win32 function, + // the stack is 16 byte aligned before the call, meaning that if calling a function with no frame pointer, + // the stack would be aligned to a 16 byte boundary +4, which isn't good for performance. Having said that, + // normally we compile with frame pointers (which brings that to 16 byte + 8, which is fine), or ICC, which + // for nontrivial functions will align the stack itself (for very short functions, it appears to weigh the + // cost of aligning the stack vs that of the slower misaligned double accesses). + + // it may be worthwhile (at some point) to put some logic in the code that calls out to functions + // (generic1parm etc) to detect which alignment would be most optimal. + sub esp, 12 + call eax + mov esp, ebp + popad + }; + + #else // gcc x86 + __asm__( + "pushl %%ebx\n" + "movl %%ecx, %%ebx\n" + "pushl %%ebp\n" + "movl %%esp, %%ebp\n" + "andl $-16, %%esp\n" // align stack to 16 bytes + "subl $12, %%esp\n" // call will push 4 bytes on stack, align for that + "call *%%edx\n" + "leave\n" + "popl %%ebx\n" + :: + "d" (cp), "c" (ramptr) + : "%eax","%esi","%edi"); + #endif //gcc x86 +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) +{ + char *p=(char*)_p; + INT_PTR scan = 0xFEFEFEFE; + while (*(INT_PTR *)p != scan) p++; + *(INT_PTR *)p = newv; + return (unsigned char *) (((INT_PTR*)p)+1); +} + +#define INT_TO_LECHARS(x) ((x)&0xff),(((x)>>8)&0xff), (((x)>>16)&0xff), (((x)>>24)&0xff) + + +#define GLUE_INLINE_LOOPS + +#define GLUE_LOOP_LOADCNT_SIZE (nseel_has_sse3() ? sizeof(GLUE_LOOP_LOADCNT_SSE3) : sizeof(GLUE_LOOP_LOADCNT_NOSSE3)) +#define GLUE_LOOP_LOADCNT (nseel_has_sse3() ? GLUE_LOOP_LOADCNT_SSE3 : GLUE_LOOP_LOADCNT_NOSSE3) +static const unsigned char GLUE_LOOP_LOADCNT_SSE3[]={ + 0xdb, 0x0e, // fisttp dword [esi] + 0x8B, 0x0E, // mov ecx, [esi] + 0x81, 0xf9, 1,0,0,0, // cmp ecx, 1 + 0x0F, 0x8C, 0,0,0,0, // JL +}; + +static const unsigned char GLUE_LOOP_LOADCNT_NOSSE3[]={ + 0xd9, 0x7e, 0x04, // fnstcw [esi+4] + 0x66, 0x8b, 0x46, 0x04, // mov ax, [esi+4] + 0x66, 0x0d, 0x00, 0x0c, // or ax, 0xC00 + 0x66, 0x89, 0x46, 0x08, // mov [esi+8], ax + 0xd9, 0x6e, 0x08, // fldcw [esi+8] + 0xDB, 0x1E, // fistp dword [esi] + 0xd9, 0x6e, 0x04, // fldcw [esi+4] + 0x8B, 0x0E, // mov ecx, [esi] + 0x81, 0xf9, 1,0,0,0, // cmp ecx, 1 + 0x0F, 0x8C, 0,0,0,0, // JL +}; + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 +#define GLUE_LOOP_CLAMPCNT_SIZE sizeof(GLUE_LOOP_CLAMPCNT) +static const unsigned char GLUE_LOOP_CLAMPCNT[]={ + 0x81, 0xf9, INT_TO_LECHARS(NSEEL_LOOPFUNC_SUPPORT_MAXLEN), // cmp ecx, NSEEL_LOOPFUNC_SUPPORT_MAXLEN + 0x0F, 0x8C, 5,0,0,0, // JL over-the-mov + 0xB9, INT_TO_LECHARS(NSEEL_LOOPFUNC_SUPPORT_MAXLEN), // mov ecx, NSEEL_LOOPFUNC_SUPPORT_MAXLEN +}; +#else + +#define GLUE_LOOP_CLAMPCNT_SIZE 0 +#define GLUE_LOOP_CLAMPCNT "" + +#endif + +#define GLUE_LOOP_BEGIN_SIZE sizeof(GLUE_LOOP_BEGIN) +static const unsigned char GLUE_LOOP_BEGIN[]={ + 0x56, //push esi + 0x51, // push ecx + 0x81, 0xEC, 0x08, 0,0,0, // sub esp, 8 +}; +static const unsigned char GLUE_LOOP_END[]={ + 0x81, 0xC4, 0x08, 0,0,0, // add esp, 8 + 0x59, //pop ecx + 0x5E, // pop esi + 0x49, // dec ecx + 0x0f, 0x85, 0,0,0,0, // jnz ... +}; + + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 +#define GLUE_WHILE_SETUP_SIZE sizeof(GLUE_WHILE_SETUP) +static const unsigned char GLUE_WHILE_SETUP[]={ + 0xB9, INT_TO_LECHARS(NSEEL_LOOPFUNC_SUPPORT_MAXLEN), // mov ecx, NSEEL_LOOPFUNC_SUPPORT_MAXLEN +}; +static const unsigned char GLUE_WHILE_BEGIN[]={ + 0x56, //push esi + 0x51, // push ecx + 0x81, 0xEC, 0x08, 0,0,0, // sub esp, 8 +}; +static const unsigned char GLUE_WHILE_END[]={ + 0x81, 0xC4, 0x08, 0,0,0, // add esp, 8 + 0x59, //pop ecx + 0x5E, // pop esi + + + 0x49, // dec ecx + 0x0f, 0x84, 0,0,0,0, // jz endpt +}; + + +#else + +#define GLUE_WHILE_SETUP_SIZE 0 +#define GLUE_WHILE_SETUP "" +#define GLUE_WHILE_END_NOJUMP +static const unsigned char GLUE_WHILE_BEGIN[]={ + 0x56, //push esi + 0x81, 0xEC, 12, 0,0,0, // sub esp, 12 +}; +static const unsigned char GLUE_WHILE_END[]={ + 0x81, 0xC4, 12, 0,0,0, // add esp, 12 + 0x5E, // pop esi +}; + +#endif + +static const unsigned char GLUE_WHILE_CHECK_RV[] = { + 0x85, 0xC0, // test eax, eax + 0x0F, 0x85, 0,0,0,0 // jnz looppt +}; + +static const unsigned char GLUE_SET_P1_Z[] = { 0x29, 0xC0 }; // sub eax, eax +static const unsigned char GLUE_SET_P1_NZ[] = { 0xb0, 0x01 }; // mov al, 1 + +static const unsigned char GLUE_FXCH[] = {0xd9, 0xc9}; + +#define GLUE_HAS_FLDZ +static const unsigned char GLUE_FLDZ[] = {0xd9, 0xee}; +#define GLUE_HAS_FLD1 +static const unsigned char GLUE_FLD1[] = {0xd9, 0xe8}; + +static EEL_F negativezeropointfive=-0.5f; +static EEL_F onepointfive=1.5f; +#define GLUE_INVSQRT_NEEDREPL &negativezeropointfive, &onepointfive, + + +#define GLUE_HAS_NATIVE_TRIGSQRTLOG + + +void nseel_asm_or(void); +void nseel_asm_or0(void); +void nseel_asm_or_op(void); +void nseel_asm_and(void); +void nseel_asm_and_op(void); +void nseel_asm_xor(void); +void nseel_asm_xor_op(void); +void nseel_asm_shl(void); +void nseel_asm_shr(void); +void nseel_asm_mod(void); +void nseel_asm_mod_op(void); +void nseel_asm_stack_peek(void); +void _asm_gmegabuf(void); +void _asm_megabuf(void); + +static struct roundinftab { + void *fn; + char istores; // number of fistp's + char flag; // 0=fistpll, 1=fistpl, 2=fistpl 4(%esp) + int newsz; + void *newfn; +} s_round_fixes[] = { + { nseel_asm_or, 2, }, + { nseel_asm_or_op, 2, }, + { nseel_asm_or0, 1, }, + { nseel_asm_and, 2, }, + { nseel_asm_and_op, 2, }, + { nseel_asm_xor, 2, }, + { nseel_asm_xor_op, 2, }, + { nseel_asm_shl, 2, 1, }, + { nseel_asm_shr, 2, 1, }, + { nseel_asm_mod, 2, 1, }, + { nseel_asm_mod_op, 2, 1, }, + { nseel_asm_stack_peek, 1, 1, }, + { _asm_megabuf, 1, 1, }, + { _asm_gmegabuf, 1, 2, }, +}; + +static void eel_fixup_sse3(unsigned char *p, unsigned char *endp, int np, int flag) +{ + const int isz = flag == 2 ? 3 : 2; + while (p+isz <= endp && np > 0) + { + if (flag == 0 && p[0] == 0xdf && (p[1]&0xbe) == 0x3e) + { + *p++ = 0xdd; + *p -= 0x30; + if (*p & 0x40) p++; + np--; + } + else if (flag == 1 && p[0] == 0xdb && (p[1]&0xbe) == 0x1e) + { + *++p -= 0x10; + if (*p & 0x40) p++; + np--; + } + else if (flag == 2 && p[0] == 0xdb && (p[1]&0xbf) == 0x1c && p[2] == 0x24) + { + *++p -= 0x10; + if (*p & 0x40) p++; + p++; + np--; + } + p++; + } + WDL_ASSERT(np == 0); +} + +static int nseel_has_sse3() +{ + static char c; + if (!c) + { + int features = 1; + #ifdef _MSC_VER + __asm { + mov eax, 1 + cpuid + mov [features], ecx + }; + #else + __asm__( + "movl $1, %%eax\n" + "pushl %%ebx\n" + "pushl %%edx\n" + "cpuid\n" + "popl %%edx\n" + "popl %%ebx\n" + : "=c" (features) : : "%eax"); + #endif + c=(features&1) ? 1 : -1; + } + return c>0; +} +static void *GLUE_realAddress(void *fn, int *size) +{ + static const unsigned char sig[12] = { 0x89, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; + unsigned char *p = (unsigned char *)fn; + + size_t rmatch; + const size_t nmatch = sizeof(s_round_fixes) / sizeof(s_round_fixes[0]); + for (rmatch = 0; rmatch < nmatch && s_round_fixes[rmatch].fn != fn; rmatch++); + if (rmatch < nmatch && s_round_fixes[rmatch].newfn && s_round_fixes[rmatch].newsz) + { + *size = s_round_fixes[rmatch].newsz; + return s_round_fixes[rmatch].newfn; + } + + #if defined(_DEBUG) && defined(_MSC_VER) + if (*p == 0xE9) // this means jump to the following address (debug stub) + { + p += 5 + *(int *)(p+1); + } + #endif + + while (memcmp(p,sig,sizeof(sig))) p++; + p+=sizeof(sig); + fn = p; + + while (memcmp(p,sig,sizeof(sig))) p++; + *size = p - (unsigned char *)fn; + + if (rmatch < nmatch) + { + static const unsigned char prefix[] = { + 0xd9, 0x7e, 0x10, // fnstcw [esi+16] + 0x66, 0x8b, 0x46, 0x10, // mov ax, [esi+16] + 0x66, 0x0d, 0x00, 0x0c, // or ax, 0xC00 + 0x66, 0x89, 0x46, 0x14, // mov [esi+20], ax + 0xd9, 0x6e, 0x14, // fldcw [esi+20] + }; + static const unsigned char postfix[] = { + 0xd9, 0x6e, 0x10, // fldcw [esi+16] + }; + + const int has_sse = nseel_has_sse3(); + + unsigned char *tmp = (unsigned char *) malloc(*size + (has_sse ? 0 : sizeof(prefix) + sizeof(postfix))); + if (tmp) + { + if (has_sse) + { + memcpy(tmp,fn,*size); + eel_fixup_sse3(tmp,tmp + *size,s_round_fixes[rmatch].istores, s_round_fixes[rmatch].flag); + } + else + { + memcpy(tmp,prefix,sizeof(prefix)); + memcpy(tmp+sizeof(prefix),fn,*size); + memcpy(tmp+sizeof(prefix)+*size,postfix,sizeof(postfix)); + + *size += sizeof(prefix) + sizeof(postfix); + } + fn = tmp; + s_round_fixes[rmatch].newsz = *size; + s_round_fixes[rmatch].newfn = fn; + } + } + + return fn; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_x86_64_sse.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_x86_64_sse.h new file mode 100644 index 0000000..651efa6 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/glue_x86_64_sse.h @@ -0,0 +1,638 @@ +#ifndef _NSEEL_GLUE_X86_64_SSE_H_ +#define _NSEEL_GLUE_X86_64_SSE_H_ + +// SSE version (needs the appropriate .o linked!) +#define GLUE_MOD_IS_64 +#define GLUE_PREFER_NONFP_DV_ASSIGNS +#define GLUE_HAS_FPREG2 1 + +static const unsigned int GLUE_COPY_FPSTACK_TO_FPREG2[] = { 0xc8100ff2 }; // movsd %xmm0,%xmm1 +static unsigned char GLUE_POP_STACK_TO_FPREG2[] = { + 0xf2, 0x0f, 0x10, 0x0c, 0x24, // movsd (%rsp), %xmm1 + 0x48, 0x81, 0xC4, 16, 0,0,0, // add rsp, 16 +}; + +// spill registers +#define GLUE_MAX_SPILL_REGS 4 +#ifdef _WIN32 + // win64: xmm6-xmm15 are nonvolatile, so we use xmm6-xmm9 as spill registers (xmm8/xmm9 are 5 byte encodings) + #define GLUE_SAVE_TO_SPILL_SIZE(x) (4 + ((x)>1)) + #define GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(x) (4 + ((x)>1)) + + static void GLUE_RESTORE_SPILL_TO_FPREG2(void *b, int ws) + { + if (ws < 2) + { + *(unsigned int *)b = 0xce100ff2 + (ws<<24); // movsd xmm1, xmm6+ws + } + else + { + // movsd xmm1, xmm8 + (ws-2) + *(unsigned int *)b = 0x100f41f2; + ((unsigned char *)b)[4] = 0xc8 + (ws-2); + } + } + static void GLUE_SAVE_TO_SPILL(void *b, int ws) + { + if (ws < 2) + { + *(unsigned int *)b = 0xf0100ff2 + (ws<<27); // movsd xmm6+ws, xmm0 + } + else + { + // movsd xmm8+(ws-2), xmm0 + *(unsigned int *)b = 0x100f44f2; + ((unsigned char *)b)[4] = 0xc0 + ((ws-2)<<3); + } + } +#else + // non-win32: our function stubs preserve xmm4-xmm7 + +#ifdef _DEBUG +#define GLUE_VALIDATE_SPILLS +#endif + +#ifdef GLUE_VALIDATE_SPILLS + +static unsigned char save_validate[]={ +0x48,0x83,0xec,0x10, // subq $16, %rsp +0xf2,0x0f,0x11,0x04,0x24, // movsd %xmm0, (%rsp) +0x66,0x48,0x0f,0x6e,0xe4, // movq %rsp, %xmm4 (+ws<<3) +}; + +static unsigned char restore_validate[] = { + 0xf2, 0x0f, 0x10, 0xcc, // movsd %xmm7, %xmm1 (+ws) + 0x66, 0x48, 0x0f, 0x6e, 0xdc, // movq %rsp, %xmm3 + 0x66, 0x0f, 0x2e, 0xd9, // ucomisd %xmm1, %xmm3 + 0x74, 0x02, // je 2 + 0xcd, 0x03, // int $3 + 0xf2, 0x0f, 0x10, 0x0c, 0x24, // movsd (%rsp), %xmm1 + 0x48, 0x83, 0xc4, 0x10, // addq $16, %rsp +}; + #define GLUE_SAVE_TO_SPILL_SIZE(x) (sizeof(save_validate)) + #define GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(x) (sizeof(restore_validate)) + +#else + + #define GLUE_SAVE_TO_SPILL_SIZE(x) (4) + #define GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(x) (4) + +#endif + + static void GLUE_RESTORE_SPILL_TO_FPREG2(void *b, int ws) + { +#ifdef GLUE_VALIDATE_SPILLS + char *p = (char*) b; + memcpy(p,restore_validate,sizeof(restore_validate)); + p[3] += ws; +#else + *(unsigned int *)b = 0xcc100ff2 + (ws<<24); // movsd xmm1, xmm4+ws +#endif + } + static void GLUE_SAVE_TO_SPILL(void *b, int ws) + { +#ifdef GLUE_VALIDATE_SPILLS + char *p = (char*) b; + memcpy(p,save_validate,sizeof(save_validate)); + p[sizeof(save_validate)-1] += ws<<3; +#else + *(unsigned int *)b = 0xe0100ff2 + (ws<<27); // movsd xmm4+ws, xmm0 +#endif + } +#endif + +#define GLUE_MAX_FPSTACK_SIZE 0 +#define GLUE_JMP_SET_OFFSET(endOfInstruction,offset) (((int *)(endOfInstruction))[-1] = (int) (offset)) + +static const unsigned char GLUE_JMP_NC[] = { 0xE9, 0,0,0,0, }; // jmp +static const unsigned char GLUE_JMP_IF_P1_Z[] = {0x85, 0xC0, 0x0F, 0x84, 0,0,0,0 }; // test eax, eax, jz +static const unsigned char GLUE_JMP_IF_P1_NZ[] = {0x85, 0xC0, 0x0F, 0x85, 0,0,0,0 }; // test eax, eax, jnz + + +#define GLUE_FUNC_ENTER_SIZE 0 +#define GLUE_FUNC_LEAVE_SIZE 0 +const static unsigned int GLUE_FUNC_ENTER[1]; +const static unsigned int GLUE_FUNC_LEAVE[1]; + + // on x86-64: + // stack is always 16 byte aligned + // pushing values to the stack (for eel functions) has alignment pushed first, then value (value is at the lower address) + // pushing pointers to the stack has the pointer pushed first, then the alignment (pointer is at the higher address) + #define GLUE_MOV_PX_DIRECTVALUE_SIZE 10 + #define GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE 14 // wr=-1, sets xmm0 + #define GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE 14 // wr=-2, sets xmm1 + static void GLUE_MOV_PX_DIRECTVALUE_GEN(void *b, INT_PTR v, int wr) { + const static unsigned short tab[3] = + { + 0xB848 /* mov rax, dv*/, + 0xBF48 /* mov rdi, dv */ , + 0xB948 /* mov rcx, dv */ + }; + unsigned short *bb = (unsigned short *)b; + *bb++ = tab[wdl_max(wr,0)]; // mov rax, directvalue + *(INT_PTR *)bb = v; + if (wr == -2) *(unsigned int *)(bb + 4) = 0x08100ff2; // movsd (%rax), %xmm1 + else if (wr == -1) *(unsigned int *)(bb + 4) = 0x00100ff2; // movsd (%rax), %xmm0 + } + + const static unsigned char GLUE_PUSH_P1[2]={ 0x50,0x50}; // push rax (pointer); push rax (alignment) + + #define GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(x) 8 + static void GLUE_STORE_P1_TO_STACK_AT_OFFS(void *b, int offs) + { + ((unsigned char *)b)[0] = 0x48; // mov [rsp+offs], rax + ((unsigned char *)b)[1] = 0x89; + ((unsigned char *)b)[2] = 0x84; + ((unsigned char *)b)[3] = 0x24; + *(int *)((unsigned char *)b+4) = offs; + } + + #define GLUE_MOVE_PX_STACKPTR_SIZE 3 + static void GLUE_MOVE_PX_STACKPTR_GEN(void *b, int wv) + { + static const unsigned char tab[3][GLUE_MOVE_PX_STACKPTR_SIZE]= + { + { 0x48, 0x89, 0xe0 }, // mov rax, rsp + { 0x48, 0x89, 0xe7 }, // mov rdi, rsp + { 0x48, 0x89, 0xe1 }, // mov rcx, rsp + }; + memcpy(b,tab[wv],GLUE_MOVE_PX_STACKPTR_SIZE); + } + + #define GLUE_MOVE_STACK_SIZE 7 + static void GLUE_MOVE_STACK(void *b, int amt) + { + ((unsigned char *)b)[0] = 0x48; + ((unsigned char *)b)[1] = 0x81; + if (amt < 0) + { + ((unsigned char *)b)[2] = 0xEC; + *(int *)((char*)b+3) = -amt; // sub rsp, -amt32 + } + else + { + ((unsigned char *)b)[2] = 0xc4; + *(int *)((char*)b+3) = amt; // add rsp, amt32 + } + } + + #define GLUE_POP_PX_SIZE 2 + static void GLUE_POP_PX(void *b, int wv) + { + static const unsigned char tab[3][GLUE_POP_PX_SIZE]= + { + {0x58,/*pop rax*/ 0x58}, // pop alignment, then pop pointer + {0x5F,/*pop rdi*/ 0x5F}, + {0x59,/*pop rcx*/ 0x59}, + }; + memcpy(b,tab[wv],GLUE_POP_PX_SIZE); + } + + static const unsigned char GLUE_PUSH_P1PTR_AS_VALUE[] = + { + 0x50, /*push rax - for alignment */ + 0xff, 0x30, /* push qword [rax] */ + }; + + static int GLUE_POP_VALUE_TO_ADDR(unsigned char *buf, void *destptr) // trashes P2 (rdi) and P3 (rcx) + { + if (buf) + { + *buf++ = 0x48; *buf++ = 0xB9; *(void **) buf = destptr; buf+=8; // mov rcx, directvalue + *buf++ = 0x8f; *buf++ = 0x01; // pop qword [rcx] + *buf++ = 0x5F ; // pop rdi (alignment, safe to trash rdi though) + } + return 1+10+2; + } + + static int GLUE_COPY_VALUE_AT_P1_TO_PTR(unsigned char *buf, void *destptr) // trashes P2/P3 + { + if (buf) + { + *buf++ = 0x48; *buf++ = 0xB9; *(void **) buf = destptr; buf+=8; // mov rcx, directvalue + *buf++ = 0x48; *buf++ = 0x8B; *buf++ = 0x38; // mov rdi, [rax] + *buf++ = 0x48; *buf++ = 0x89; *buf++ = 0x39; // mov [rcx], rdi + } + + return 3 + 10 + 3; + } + + static int GLUE_POP_FPSTACK_TO_PTR(unsigned char *buf, void *destptr) + { + if (buf) + { + *buf++ = 0x48; + *buf++ = 0xB8; + *(void **) buf = destptr; buf+=8; // mov rax, directvalue + + *buf++ = 0xf2; // movsd %xmm0, (%rax) + *buf++ = 0x0f; + *buf++ = 0x11; + *buf++ = 0x00; + } + return 2+8+4; + } + + + #define GLUE_SET_PX_FROM_P1_SIZE 3 + static void GLUE_SET_PX_FROM_P1(void *b, int wv) + { + static const unsigned char tab[3][GLUE_SET_PX_FROM_P1_SIZE]={ + {0x90,0x90,0x90}, // should never be used! (nopnop) + {0x48,0x89,0xC7}, // mov rdi, rax + {0x48,0x89,0xC1}, // mov rcx, rax + }; + memcpy(b,tab[wv],GLUE_SET_PX_FROM_P1_SIZE); + } + + + #define GLUE_POP_FPSTACK_SIZE 0 + static const unsigned char GLUE_POP_FPSTACK[1] = { 0 }; + + static const unsigned char GLUE_POP_FPSTACK_TOSTACK[] = { + 0x48, 0x81, 0xEC, 16, 0,0,0, // sub rsp, 16 + 0xf2, 0x0f, 0x11, 0x04, 0x24, // movsd xmm0, (%rsp) + }; + + static const unsigned char GLUE_POP_FPSTACK_TO_WTP[] = { + 0xf2, 0x0f, 0x11, 0x06, // movsd xmm0, (%rsi) + 0x48, 0x81, 0xC6, 8, 0,0,0,/* add rsi, 8 */ + }; + + #define GLUE_SET_PX_FROM_WTP_SIZE 3 + static void GLUE_SET_PX_FROM_WTP(void *b, int wv) + { + static const unsigned char tab[3][GLUE_SET_PX_FROM_WTP_SIZE]={ + {0x48, 0x89,0xF0}, // mov rax, rsi + {0x48, 0x89,0xF7}, // mov rdi, rsi + {0x48, 0x89,0xF1}, // mov rcx, rsi + }; + memcpy(b,tab[wv],GLUE_SET_PX_FROM_WTP_SIZE); + } + + #define GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE 4 + static void GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(void *b, int wv) + { + static const unsigned char tab[3][GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE]={ + {0xf2, 0x0f, 0x10, 0x00}, // movsd (%rax), %xmm0 + {0xf2, 0x0f, 0x10, 0x07}, // movsd (%rdi), %xmm0 + {0xf2, 0x0f, 0x10, 0x01}, // movsd (%rcx), %xmm0 + }; + memcpy(b,tab[wv],GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE); + } + +#define GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE (GLUE_SET_PX_FROM_WTP_SIZE + sizeof(GLUE_POP_FPSTACK_TO_WTP)) +static void GLUE_POP_FPSTACK_TO_WTP_TO_PX(unsigned char *buf, int wv) +{ + GLUE_SET_PX_FROM_WTP(buf,wv); + memcpy(buf + GLUE_SET_PX_FROM_WTP_SIZE,GLUE_POP_FPSTACK_TO_WTP,sizeof(GLUE_POP_FPSTACK_TO_WTP)); +}; + + +const static unsigned char GLUE_RET=0xC3; + +static int GLUE_RESET_WTP(unsigned char *out, void *ptr) +{ + if (out) + { + *out++ = 0x48; + *out++ = 0xBE; // mov rsi, constant64 + *(void **)out = ptr; + out+=sizeof(void *); + } + return 2+sizeof(void *); +} + +extern void eel_callcode64(INT_PTR code, INT_PTR ram_tab); +extern void eel_callcode64_fast(INT_PTR code, INT_PTR ram_tab); +#define GLUE_CALL_CODE(bp, cp, rt) do { \ + if (h->compile_flags&NSEEL_CODE_COMPILE_FLAG_NOFPSTATE) eel_callcode64_fast(cp, rt); \ + else eel_callcode64(cp, rt);\ + } while(0) +#define GLUE_TABPTR_IGNORED + +static unsigned char *EEL_GLUE_set_immediate(void *_p, INT_PTR newv) +{ + char *p=(char*)_p; + INT_PTR scan = 0xFEFEFEFEFEFEFEFE; + while (*(INT_PTR *)p != scan) p++; + *(INT_PTR *)p = newv; + return (unsigned char *) (((INT_PTR*)p)+1); +} + +#define INT_TO_LECHARS(x) ((x)&0xff),(((x)>>8)&0xff), (((x)>>16)&0xff), (((x)>>24)&0xff) + +#define GLUE_INLINE_LOOPS + +static const unsigned char GLUE_LOOP_LOADCNT[]={ + 0xf2, 0x48, 0x0f, 0x2c, 0xc8, // cvttsd2si %xmm0, %rcx + 0x48, 0x81, 0xf9, 1,0,0,0, // cmp rcx, 1 + 0x0F, 0x8C, 0,0,0,0, // JL +}; + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 +#define GLUE_LOOP_CLAMPCNT_SIZE sizeof(GLUE_LOOP_CLAMPCNT) +static const unsigned char GLUE_LOOP_CLAMPCNT[]={ + 0x48, 0x81, 0xf9, INT_TO_LECHARS(NSEEL_LOOPFUNC_SUPPORT_MAXLEN), // cmp rcx, NSEEL_LOOPFUNC_SUPPORT_MAXLEN + 0x0F, 0x8C, 10,0,0,0, // JL over-the-mov + 0x48, 0xB9, INT_TO_LECHARS(NSEEL_LOOPFUNC_SUPPORT_MAXLEN), 0,0,0,0, // mov rcx, NSEEL_LOOPFUNC_SUPPORT_MAXLEN +}; +#else +#define GLUE_LOOP_CLAMPCNT_SIZE 0 +#define GLUE_LOOP_CLAMPCNT "" +#endif + +#define GLUE_LOOP_BEGIN_SIZE sizeof(GLUE_LOOP_BEGIN) +static const unsigned char GLUE_LOOP_BEGIN[]={ + 0x56, //push rsi + 0x51, // push rcx +}; +static const unsigned char GLUE_LOOP_END[]={ + 0x59, //pop rcx + 0x5E, // pop rsi + 0xff, 0xc9, // dec rcx + 0x0f, 0x85, 0,0,0,0, // jnz ... +}; + + + +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN > 0 +static const unsigned char GLUE_WHILE_SETUP[]={ + 0x48, 0xB9, INT_TO_LECHARS(NSEEL_LOOPFUNC_SUPPORT_MAXLEN), 0,0,0,0, // mov rcx, NSEEL_LOOPFUNC_SUPPORT_MAXLEN +}; +#define GLUE_WHILE_SETUP_SIZE sizeof(GLUE_WHILE_SETUP) + +static const unsigned char GLUE_WHILE_BEGIN[]={ + 0x56, //push rsi + 0x51, // push rcx +}; +static const unsigned char GLUE_WHILE_END[]={ + 0x59, //pop rcx + 0x5E, // pop rsi + + 0xff, 0xc9, // dec rcx + 0x0f, 0x84, 0,0,0,0, // jz endpt +}; + + +#else +#define GLUE_WHILE_SETUP "" +#define GLUE_WHILE_SETUP_SIZE 0 +#define GLUE_WHILE_END_NOJUMP + +static const unsigned char GLUE_WHILE_BEGIN[]={ + 0x56, //push rsi + 0x51, // push rcx +}; +static const unsigned char GLUE_WHILE_END[]={ + 0x59, //pop rcx + 0x5E, // pop rsi +}; + +#endif + + +static const unsigned char GLUE_WHILE_CHECK_RV[] = { + 0x85, 0xC0, // test eax, eax + 0x0F, 0x85, 0,0,0,0 // jnz looppt +}; + +static const unsigned char GLUE_SET_P1_Z[] = { 0x48, 0x29, 0xC0 }; // sub rax, rax +static const unsigned char GLUE_SET_P1_NZ[] = { 0xb0, 0x01 }; // mov al, 1 + + +#define GLUE_HAS_FLDZ +static const unsigned char GLUE_FLDZ[] = { + 0x0f, 0x57, 0xc0 //xorps %xmm0, %xmm0 +}; + + +static EEL_F negativezeropointfive=-0.5f; +static EEL_F onepointfive=1.5f; +#define GLUE_INVSQRT_NEEDREPL &negativezeropointfive, &onepointfive, + + +static void *GLUE_realAddress(void *fn, int *size) +{ + static const unsigned char new_sig[8] = { 0x89, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x00 }; + int sz = 0; + while (memcmp((char*)fn + sz,new_sig,sizeof(new_sig))) sz++; + *size = sz; + return fn; +} + +#define GLUE_HAS_FUSE 1 +static int GLUE_FUSE(compileContext *ctx, unsigned char *code, int left_size, int right_size, int fuse_flags, int spill_reg) +{ + const UINT_PTR base = (UINT_PTR) ctx->ram_state->blocks; + const int is_sse_op = right_size == 4 && // add/mul/sub/min/max + code[0] == 0xf2 && + code[1] == 0x0f && + code[3] == 0xc1 && // low nibble is xmm1 + (code[2] == 0x58 || code[2] == 0x59 || code[2] == 0x5c || code[2]==0x5d || code[2] == 0x5f); + + if (spill_reg >= 0) + { +#ifndef GLUE_VALIDATE_SPILLS + if (is_sse_op) + { + char tmp[32]; + const int sz = GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(spill_reg); + GLUE_RESTORE_SPILL_TO_FPREG2(tmp,spill_reg); + if (left_size>=sz && !memcmp(code-sz,tmp,sz)) + { + code[-2] = code[2]; // modify the movsd into an addsd + code[-1] -= 8; // movsd uses 0xc8+(xmmX&7), addsd etc use 0xc0 + return -4; + } + } +#endif + } + else + { + if (left_size==28) + { + // if const64_1 is within a 32-bit offset of ctx->ram_blocks->blocks, we can use [r12+offs] + if (code[-28] == 0x48 && code[-27] == 0xb8 && // mov rax, const64_1 + *(int *)(code - 18) == 0x08100ff2 && // movsd xmm1, [rax] + code[-14] == 0x48 && code[-13] == 0xb8 && // mov rax, const64_2 + *(int *)(code - 4) == 0x00100ff2 // movsd xmm0, [rax] + ) + { + UINT_PTR c1, c2; + INT_PTR c2offs,c1offs; + unsigned char opc[3]; + int wrpos = -28; + if (is_sse_op) memcpy(opc,code,3); + memcpy(&c1,code-26,8); + memcpy(&c2,code-12,8); + +#define PTR_32_OK(x) ((x) == (INT_PTR)(int)(x)) + c2offs = c2-base; + if (!PTR_32_OK(c2offs)) + { + code[wrpos++] = 0x48; + code[wrpos++] = 0xb8; + memcpy(code+wrpos,&c2,8); // mov rax, const64_2 + wrpos += 8; + } + + c1offs = c1-base; + if (!PTR_32_OK(c1offs)) + { + code[wrpos++] = 0x48; + code[wrpos++] = 0xbf; + memcpy(code+wrpos,&c1,8); // mov rdi, const64_1 + wrpos += 8; + } + + if (!PTR_32_OK(c2offs)) + { + *(int *)(code+wrpos) = 0x00100ff2; // movsd xmm0, [rax] + wrpos += 4; + } + else + { + // movsd xmm0, [r12+offs] + code[wrpos++] = 0xf2; + code[wrpos++] = 0x41; + code[wrpos++] = 0x0f; + code[wrpos++] = 0x10; + code[wrpos++] = 0x84; + code[wrpos++] = 0x24; + *(int *)(code+wrpos) = (int)c2offs; + wrpos += 4; + } + + if (!is_sse_op) + { + // load xmm1 from rdi/c1offs + if (!PTR_32_OK(c1offs)) + { + *(int *)(code+wrpos) = 0x0f100ff2; // movsd xmm1, [rdi] + wrpos += 4; + } + else + { + // movsd xmm1, [r12+offs] + code[wrpos++] = 0xf2; + code[wrpos++] = 0x41; + code[wrpos++] = 0x0f; + code[wrpos++] = 0x10; + code[wrpos++] = 0x8c; + code[wrpos++] = 0x24; + *(int *)(code+wrpos) = (int)c1offs; + wrpos += 4; + } + if (wrpos<0) memmove(code+wrpos,code,right_size); + return wrpos; + } + + // fuse to sse op + if (!PTR_32_OK(c1offs)) + { + memcpy(code+wrpos,opc,3); + code[wrpos+3] = 0x07; // [rdi] + wrpos += 4; + } + else + { + // mul/add/sub/min/max/sd xmm0, [r12+offs] + code[wrpos++] = opc[0]; // 0xf2 + code[wrpos++] = 0x41; + code[wrpos++] = opc[1]; // 0x0f + code[wrpos++] = opc[2]; // 0x58 etc + code[wrpos++] = 0x84; + code[wrpos++] = 0x24; + *(int *)(code+wrpos) = (int)c1offs; + wrpos += 4; + } + return wrpos - right_size; + } + } + if ((fuse_flags&1) && left_size >= 14) + { + if (code[-14] == 0x48 && code[-13] == 0xb8 && // mov rax, const64_2 + *(int *)(code - 4) == 0x00100ff2) // movsd xmm0, [rax] + { + INT_PTR c1; + memcpy(&c1,code-12,8); + c1 -= base; + if (PTR_32_OK(c1)) + { + // movsd xmm0, [r12+offs] + int wrpos = -14; + code[wrpos++] = 0xf2; + code[wrpos++] = 0x41; + code[wrpos++] = 0x0f; + code[wrpos++] = 0x10; + code[wrpos++] = 0x84; + code[wrpos++] = 0x24; + *(int *)(code+wrpos) = (int)c1; + wrpos += 4; + if (wrpos<0) memmove(code+wrpos,code,right_size); + return wrpos; + } + } + } + + if (left_size == 20 && right_size == 9 && + code[-20]==0x48 && code[-19] == 0xbf && // mov rdi, const64_1 + code[-10]==0x48 && code[-9] == 0xb8 // mov rax, const64_2 + ) + { + static unsigned char assign_copy[9] = { 0x48, 0x8b, 0x10, // mov rdx, [rax] + 0x48, 0x89, 0x17, // mov [rdi], rdx + 0x48, 0x89, 0xf8, // mov rax, rdi + }; + if (!memcmp(code,assign_copy,9)) + { + int wrpos = -20; + INT_PTR c1,c2; // c1 is dest, c2 is src + memcpy(&c1,code-18,8); + memcpy(&c2,code-8,8); + + if (!PTR_32_OK(c2-base)) + { + code[wrpos++] = 0x48; // mov rdi, src + code[wrpos++] = 0xbf; + memcpy(code+wrpos,&c2,8); + wrpos +=8; + } + + code[wrpos++] = 0x48; // mov rax, dest + code[wrpos++] = 0xb8; + memcpy(code+wrpos,&c1,8); + wrpos +=8; + + if (PTR_32_OK(c2-base)) + { + // mov rdx, [r12+offs] + code[wrpos++] = 0x49; + code[wrpos++] = 0x8b; + code[wrpos++] = 0x94; + code[wrpos++] = 0x24; + *(int *)(code+wrpos) = (int)(c2-base); + wrpos += 4; + } + else + { + code[wrpos++] = 0x48; // mov rdx, [rdi] + code[wrpos++] = 0x8b; + code[wrpos++] = 0x17; + } + + code[wrpos++] = 0x48; // mov [rax], rdx + code[wrpos++] = 0x89; + code[wrpos++] = 0x10; + + return wrpos - right_size; + } + } + + + } + return 0; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.cpp new file mode 100644 index 0000000..b41211c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include "../mutex.h" + +int g_verbose, g_interactive; + +static void writeToStandardError(const char *fmt, ...) +{ + va_list arglist; + va_start(arglist, fmt); + vfprintf(stderr,fmt,arglist); + fprintf(stderr,"\n"); + fflush(stderr); + va_end(arglist); +} +#define EEL_STRING_DEBUGOUT writeToStandardError // no parameters, since it takes varargs + +#ifndef EEL_LICE_WANT_STANDALONE + #define EELSCRIPT_NO_LICE +#endif + +#include "eelscript.h" + + +void NSEEL_HOSTSTUB_EnterMutex() { } +void NSEEL_HOSTSTUB_LeaveMutex() { } + + +int main(int argc, char **argv) +{ + bool want_args = true; + int argpos = 1; + const char *scriptfn = argv[0]; + while (argpos < argc && argv[argpos][0] == '-' && argv[argpos][1]) + { + if (!strcmp(argv[argpos],"-v")) g_verbose++; + else if (!strcmp(argv[argpos],"-i")) g_interactive++; + else if (!strcmp(argv[argpos],"--no-args")) want_args=false; + else + { + fprintf(stderr,"Usage: %s [-v] [--no-args] [-i | scriptfile | -]\n",argv[0]); + return -1; + } + argpos++; + } + if (argpos < argc && !g_interactive) + { + scriptfn = argv[argpos++]; + } + else + { +#ifndef _WIN32 + if (!g_interactive && isatty(0)) +#else + if (1) +#endif + g_interactive=1; + } + + if (eelScriptInst::init()) + { + fprintf(stderr,"NSEEL_init(): error initializing\n"); + return -1; + } + + +#ifndef EELSCRIPT_NO_LICE + #ifdef __APPLE__ + SWELL_InitAutoRelease(); + #endif +#endif + + WDL_FastString code,t; + + eelScriptInst inst; + if (want_args) + { +#ifndef EELSCRIPT_DO_DISASSEMBLE + const int argv_offs = 1<<22; + code.SetFormatted(64,"argc=0; argv=%d;\n",argv_offs); + int x; + for (x=argpos-1;xAddString(new WDL_FastString(xm_gfx_clear) inst.m_gfx_state->m_gfx_clear[0] = -1; +#endif + + printf("EEL interactive mode, type quit to quit, abort to abort multiline entry\n"); + EEL_F *resultVar = NSEEL_VM_regvar(inst.m_vm,"__result"); + code.Set(""); + char line[4096]; + for (;;) + { +#ifndef EELSCRIPT_NO_LICE + _gfx_update(&inst,NULL); +#endif + if (!code.Get()[0]) printf("EEL> "); + else printf("> "); + fflush(stdout); + line[0]=0; + fgets(line,sizeof(line),stdin); + if (!line[0]) break; + code.Append(line); + while (line[0] && ( + line[strlen(line)-1] == '\r' || + line[strlen(line)-1] == '\n' || + line[strlen(line)-1] == '\t' || + line[strlen(line)-1] == ' ' + )) line[strlen(line)-1]=0; + + if (!strcmp(line,"quit")) break; + if (!strcmp(line,"abort")) code.Set(""); + +#ifndef EELSCRIPT_DO_DISASSEMBLE + t.Set("__result = ("); +#else + t.Set(""); +#endif + t.Append(code.Get()); +#ifndef EELSCRIPT_DO_DISASSEMBLE + t.Append(");"); +#endif + int res=inst.runcode(t.Get(),false,"",true,true,true); // allow free, since functions can't be defined locally + if (!res) + { + if (resultVar) printf("=%g ",*resultVar); + code.Set(""); + } + else // try compiling again allowing function definitions (and not allowing free) + // but show errors if not continuation + { + res=inst.runcode(code.Get(),true,"(stdin)", false,false,true); + if (res<=0) code.Set(""); + // res>0 means need more lines + } + while (inst.run_deferred()); + } + } + else + { + inst.loadfile(scriptfn,NULL,true); + while (inst.run_deferred()); + } + + return 0; +} + +#ifndef _WIN32 +INT_PTR SWELLAppMain(int msg, INT_PTR parm1, INT_PTR parm2) +{ + return 0; +} +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.dsp b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.dsp new file mode 100644 index 0000000..123789a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.dsp @@ -0,0 +1,194 @@ +# Microsoft Developer Studio Project File - Name="loose_eel" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=loose_eel - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "loose_eel.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "loose_eel.mak" CFG="loose_eel - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "loose_eel - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "loose_eel - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "loose_eel - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D WDL_FFT_REALSIZE=8 /D NSEEL_LOOPFUNC_SUPPORT_MAXLEN=0 /D "EEL_LICE_WANT_STANDALONE" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "loose_eel - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D WDL_FFT_REALSIZE=8 /D NSEEL_LOOPFUNC_SUPPORT_MAXLEN=0 /D "EEL_LICE_WANT_STANDALONE" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "loose_eel - Win32 Release" +# Name "loose_eel - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Group "eel2" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=".\ns-eel-addfuncs.h" +# End Source File +# Begin Source File + +SOURCE=".\ns-eel-int.h" +# End Source File +# Begin Source File + +SOURCE=".\ns-eel.h" +# End Source File +# Begin Source File + +SOURCE=".\nseel-caltab.c" +# End Source File +# Begin Source File + +SOURCE=".\nseel-cfunc.c" +# End Source File +# Begin Source File + +SOURCE=".\nseel-compiler.c" +# End Source File +# Begin Source File + +SOURCE=".\nseel-eval.c" +# End Source File +# Begin Source File + +SOURCE=".\nseel-lextab.c" +# End Source File +# Begin Source File + +SOURCE=".\nseel-ram.c" +# End Source File +# Begin Source File + +SOURCE=".\nseel-yylex.c" +# End Source File +# End Group +# Begin Group "lice" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\lice\lice.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_arc.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_bmp.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_ico.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_image.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_line.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_lvg.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_pcx.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_text.cpp +# End Source File +# Begin Source File + +SOURCE=..\lice\lice_textnew.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=..\fft.c +# End Source File +# Begin Source File + +SOURCE=.\loose_eel.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.dsw b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.dsw new file mode 100644 index 0000000..9d0726e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "loose_eel"=.\loose_eel.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.sln b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.sln new file mode 100644 index 0000000..9eb0142 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.30110.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "loose_eel", "loose_eel.vcxproj", "{893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}" +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 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Debug|Win32.ActiveCfg = Debug|Win32 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Debug|Win32.Build.0 = Debug|Win32 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Debug|x64.ActiveCfg = Debug|x64 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Debug|x64.Build.0 = Debug|x64 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Release|Win32.ActiveCfg = Release|Win32 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Release|Win32.Build.0 = Release|Win32 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Release|x64.ActiveCfg = Release|x64 + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.vcxproj b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.vcxproj new file mode 100644 index 0000000..d1c88e4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.vcxproj @@ -0,0 +1,190 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {893B9C0E-73CA-4843-A3BC-8A3FF9055FA8} + Win32Proj + loose_eel + + + + Application + true + v120 + MultiByte + + + Application + true + v120 + MultiByte + + + Application + false + v120 + true + MultiByte + + + Application + false + v120 + true + MultiByte + + + + + + + + + + + + + + + + + + + true + $(Configuration)_new\ + $(SolutionDir)$(Configuration)_new\ + .exe + + + true + .exe + + + false + $(Configuration)_new\ + $(SolutionDir)$(Configuration)_new\ + .exe + + + false + .exe + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;WDL_FFT_REALSIZE=8;EEL_LICE_WANT_STANDALONE;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + false + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;comctl32.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;WDL_FFT_REALSIZE=8;EEL_LICE_WANT_STANDALONE;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + false + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;comctl32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;WDL_FFT_REALSIZE=8;EEL_LICE_WANT_STANDALONE;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;comctl32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;WDL_FFT_REALSIZE=8;EEL_LICE_WANT_STANDALONE;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;comctl32.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.vcxproj.filters b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.vcxproj.filters new file mode 100644 index 0000000..46afb87 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/loose_eel.vcxproj.filters @@ -0,0 +1,95 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;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 + + + {3A983C0A-E7C1-449D-B869-8CC1CE792C2F} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/makefile.vc b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/makefile.vc new file mode 100644 index 0000000..03aa237 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/makefile.vc @@ -0,0 +1,63 @@ + +EEL_SOURCE = nseel-caltab.c nseel-cfunc.c nseel-compiler.c nseel-eval.c nseel-lextab.c nseel-ram.c nseel-yylex.c + +LICE_SOURCE = ../lice/lice.cpp ../lice/lice_image.cpp ../lice/lice_line.cpp ../lice/lice_ico.cpp ../lice/lice_bmp.cpp ../lice/lice_textnew.cpp ../lice/lice_text.cpp ../lice/lice_arc.cpp + +FFT_SOURCE = ../fft.c +CFLAGS = /DWDL_FFT_REALSIZE=8 /DEEL_LICE_WANT_STANDALONE + +CFLAGS = $(CFLAGS) -DNSEEL_LOOPFUNC_SUPPORT_MAXLEN=0 + +LFLAGS = shell32.lib user32.lib comdlg32.lib + +!ifndef VC6 +CFLAGS = $(CFLAGS) /MT +!else +CFLAGS = $(CFLAGS) /MD +LFLAGS = $(LFLAGS) /OPT:NOWIN98 +!endif + +!ifdef x64 +!ifndef PORTABLE +EEL_SOURCE = $(EEL_SOURCE) asm-nseel-x64.obj +!endif +!endif + +!ifdef arm64 +!ifndef PORTABLE +EEL_SOURCE = $(EEL_SOURCE) asm-nseel-aarch64-msvc.obj +!endif +!endif + +!ifdef arm64ec +!ifndef PORTABLE +EEL_SOURCE = $(EEL_SOURCE) asm-nseel-arm64ec.obj +!endif +!endif + + +default: loose_eel.exe + +!ifdef PORTABLE +CFLAGS = $(CFLAGS) -DEEL_TARGET_PORTABLE +!endif + +loose_eel.cpp: eel*.h ns-eel*.h + +$(EEL_SOURCE): ns-eel*.h + + +loose_eel.exe: loose_eel.cpp $(EEL_SOURCE) $(FFT_SOURCE) $(LICE_SOURCE) ..\win32_utf8.c + cl $(CFLAGS) $** /link wsock32.lib user32.lib gdi32.lib advapi32.lib $(LFLAGS) /out:$@ + +eel_disasm.exe: eel_disasm.cpp $(EEL_SOURCE) + cl $(CFLAGS) $** /link wsock32.lib user32.lib gdi32.lib advapi32.lib $(LFLAGS) /out:$@ + +asm-nseel-aarch64-msvc.obj: asm-nseel-aarch64-msvc.asm + armasm64 asm-nseel-aarch64-msvc.asm + +asm-nseel-arm64ec.obj: asm-nseel-arm64ec.asm + armasm64 /machine arm64EC asm-nseel-arm64ec.asm + +test: eel_disasm.exe + eel_disasm "buf[a] = x*y" diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-addfuncs.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-addfuncs.h new file mode 100644 index 0000000..ffb92a0 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-addfuncs.h @@ -0,0 +1,63 @@ +/* + Nullsoft Expression Evaluator Library (NS-EEL) + Copyright (C) 1999-2003 Nullsoft, Inc. + + ns-eel-addfuncs.h: defines macros useful for adding functions to the compiler + + 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. +*/ + +#ifndef __NS_EEL_ADDFUNCS_H__ +#define __NS_EEL_ADDFUNCS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct _compileContext; + +void *NSEEL_PProc_RAM(void *data, int data_size, struct _compileContext *ctx); +void *NSEEL_PProc_THIS(void *data, int data_size, struct _compileContext *ctx); + + +#ifdef EEL_TARGET_PORTABLE + +extern EEL_BC_TYPE _asm_generic3parm[]; // 3 double * parms, returning double * +extern EEL_BC_TYPE _asm_generic3parm_retd[]; // 3 double * parms, returning double +extern EEL_BC_TYPE _asm_generic2parm[]; // 2 double * parms, returning double * +extern EEL_BC_TYPE _asm_generic2parm_retd[]; // 2 double * parms, returning double +extern EEL_BC_TYPE _asm_generic2xparm_retd[]; // 2 double * parms, returning double +extern EEL_BC_TYPE _asm_generic1parm[]; // 1 double * parms, returning double * +extern EEL_BC_TYPE _asm_generic1parm_retd[]; // 1 double * parms, returning double + +#else + +void _asm_generic3parm(void); // 3 double * parms, returning double * +void _asm_generic3parm_retd(void); // 3 double * parms, returning double +void _asm_generic2parm(void); // 2 double * parms, returning double * +void _asm_generic2parm_retd(void); // 2 double * parms, returning double +void _asm_generic2xparm_retd(void); // 2 double * parms, returning double +void _asm_generic1parm(void); // 1 double * parms, returning double * +void _asm_generic1parm_retd(void); // 1 double * parms, returning double + +#endif + +#ifdef __cplusplus +}; + +#endif +#endif//__NS_EEL_ADDFUNCS_H__ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-func-ref.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-func-ref.h new file mode 100644 index 0000000..c0d7593 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-func-ref.h @@ -0,0 +1,64 @@ +#ifndef _NSEEL_FUNC_REF_H_ +#define _NSEEL_FUNC_REF_H_ + +#include "ns-eel.h" +#define TMP_MKSTR2(x) #x +#define TMP_MKSTR(x) TMP_MKSTR2(x) + +const char *nseel_builtin_function_reference= + "while\texpression\tExecutes expression until expression evaluates to zero" +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN + ", or until " TMP_MKSTR(NSEEL_LOOPFUNC_SUPPORT_MAXLEN) "iterations occur" +#endif + ". An alternate and more useful syntax is while (expression) ( statements ), which evaluates statements after " + "every non-zero evaluation of expression.\0" + "loop\tcount,expression\tEvaluates count once, and then executes expression count" +#if NSEEL_LOOPFUNC_SUPPORT_MAXLEN + ", but not more than " TMP_MKSTR(NSEEL_LOOPFUNC_SUPPORT_MAXLEN) "," +#endif + " times.\0" + "sin\tangle\tReturns the sine of the angle specified (specified in radians -- to convert from degrees to radians, multiply by $pi/180, or 0.017453).\0" + "cos\tangle\tReturns the cosine of the angle specified (specified in radians).\0" + "tan\tangle\tReturns the tangent of the angle specified (specified in radians).\0" + "sqrt\tvalue\tReturns the square root of the parameter. If the parameter is negative, the return value is undefined.\0" + "log\tvalue\tReturns the natural logarithm (base e) of the parameter. If the value is not greater than 0, the return value is undefined.\0" + "log10\tvalue\tReturns the base-10 logarithm of the parameter. If the value is not greater than 0, the return value is undefined.\0" + "asin\tvalue\tReturns the arc sine of the value specified (return value is in radians). If the parameter is not between -1.0 and 1.0 inclusive, the return value is undefined.\0" + "acos\tvalue\tReturns the arc cosine of the value specified (return value is in radians). If the parameter is not between -1.0 and 1.0 inclusive, the return value is undefined.\0" + "atan\tvalue\tReturns the arc tangent of the value specified (return value is in radians). If the parameter is not between -1.0 and 1.0 inclusive, the return value is undefined.\0" + "atan2\tnumerator,denominator\tReturns the arc tangent of the numerator divided by the denominator, allowing the denominator to be 0, and using their signs to produce a more meaningful result.\0" + "exp\texponent\tReturns the number e ($e, approximately 2.718) raised to the parameter-th power. This function is significantly faster than pow() or the ^ operator.\0" + "abs\tvalue\tReturns the absolute value of the parameter.\0" + "sqr\tvalue\tReturns the square of the parameter (similar to value*value, but only evaluating value once).\0" + "min\t&value,&value\tReturns (by reference) the minimum value of the two parameters. Since min() returns by reference, expressions such as min(x,y) = 5 are possible.\0" + "max\t&value,&value\tReturns (by reference) the maximum value of the two parameters. Since max() returns by reference, expressions such as max(x,y) = 5 are possible.\0" + "sign\tvalue\tReturns 1.0 if the parameter is greater than 0, -1.0 if the parameter is less than 0, or 0 if the parameter is 0.\0" + "floor\tvalue\tReturns the value rounded to the next lowest integer (floor(3.9)==3, floor(-3.1)==-4).\0" + "ceil\tvalue\tReturns the value rounded to the next highest integer (ceil(3.1)==4, ceil(-3.9)==-3).\0" + "invsqrt\tvalue\tReturns a fast inverse square root (1/sqrt(x)) approximation of the parameter.\0" + "freembuf\taddress\tHints the runtime that memory above the address specified may no longer be used. The runtime may, at its leisure, choose to lose the contents of memory above the address specified.\0" + "memcpy\tdest,src,length\tCopies length items of memory from src to dest. Regions are permitted to overlap.\0" + "memset\toffset,value,length\tSets length items of memory at offset to value.\0" + "mem_get_values\toffset, ...\tReads values from memory starting at offset into variables specified. Slower than regular memory reads for less than a few variables, faster for more than a few. Undefined behavior if used with more than 32767 variables.\0" + "mem_set_values\toffset, ...\tWrites values to memory starting at offset from variables specified. Slower than regular memory writes for less than a few variables, faster for more than a few. Undefined behavior if used with more than 32767 variables.\0" + "mem_multiply_sum\tsrc1,src2,length\tCalculates the sum of the products of values pointed to by src1 and src2. If src1 is -1, then calculates the sum of squares of src2, if -2, the sum of the absolute values of src2, if -3, calculates the sum of the values of src2. Other negative values are undefined.\0" + "mem_insert_shuffle\tbuf,len,value\tShuffles contents of buf right by 1, inserts value at buf[0], returns previous buf[len-1].\0" + "stack_push\t&value\tPushes value onto the user stack, returns a reference to the parameter.\0" + "stack_pop\t&value\tPops a value from the user stack into value, or into a temporary buffer if value is not specified, and returns a reference to where the stack was popped. Note that no checking is done to determine if the stack is empty, and as such stack_pop() will never fail.\0" + "stack_peek\tindex\tReturns a reference to the item on the top of the stack (if index is 0), or to the Nth item on the stack if index is greater than 0. \0" + "stack_exch\t&value\tExchanges a value with the top of the stack, and returns a reference to the parameter (with the new value).\0" +#ifdef NSEEL_EEL1_COMPAT_MODE + "rand\tmax\tReturns a pseudorandom non-negative integer number less than the parameter.\0" + "sigmoid\tvalue,constraint\tReturns 1.0/(1+exp(-x * (constraint))), or 0 if a divide by 0 would occur.\0" + "band\tx,y\tReturns 1 if both x and y evaluate to nonzero, 0 if otherwise. Both parameters are always evaluated.\0" + "bor\tx,y\tReturns 1 if either x or y evaluate to nonzero, 0 if otherwise. Both parameters are always evaluated.\0" + "exec2\tx,y\tEvaluates x, then evaluates and returns y.\0" + "exec3\tx,y,z\tEvaluates x, evaluates y, then evaluates and returns z.\0" +#else + "rand\t[max]\tReturns a pseudorandom real number between 0 and the parameter, inclusive. If the parameter is omitted or less than 1.0, 1.0 is used as a maximum instead.\0" + +#endif +; +#undef TMP_MKSTR + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-int.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-int.h new file mode 100644 index 0000000..83b53ed --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel-int.h @@ -0,0 +1,331 @@ +/* + Nullsoft Expression Evaluator Library (NS-EEL) + Copyright (C) 1999-2003 Nullsoft, Inc. + + ns-eel-int.h: internal code definition header. + + 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. +*/ + +#ifndef __NS_EELINT_H__ +#define __NS_EELINT_H__ + +#ifdef _WIN32 +#include +#else +#include "../wdltypes.h" +#endif + +#include "ns-eel.h" +#include "ns-eel-addfuncs.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +enum { + + // these ignore fn in opcodes, just use fntype to determine function + FN_MULTIPLY=0, + FN_DIVIDE, + FN_JOIN_STATEMENTS, + FN_DENORMAL_LIKELY, + FN_DENORMAL_UNLIKELY, + FN_ADD, + FN_SUB, + FN_AND, + FN_OR, + FN_UMINUS, + FN_NOT, + FN_NOTNOT, + FN_XOR, + FN_SHL, + FN_SHR, + FN_MOD, + FN_POW, + FN_LT, + FN_GT, + FN_LTE, + FN_GTE, + FN_EQ, + FN_EQ_EXACT, + FN_NE, + FN_NE_EXACT, + FN_LOGICAL_AND, + FN_LOGICAL_OR, + FN_IF_ELSE, + FN_MEMORY, + FN_GMEMORY, + FN_NONCONST_BEGIN, + FN_ASSIGN=FN_NONCONST_BEGIN, + + FN_ADD_OP, + FN_SUB_OP, + FN_MOD_OP, + FN_OR_OP, + FN_AND_OP, + FN_XOR_OP, + FN_DIV_OP, + FN_MUL_OP, + FN_POW_OP, + + FN_WHILE, + FN_LOOP, + + FUNCTYPE_SIMPLEMAX, + + + FUNCTYPE_FUNCTIONTYPEREC=1000, // fn is a functionType * + FUNCTYPE_EELFUNC, // fn is a _codeHandleFunctionRec * +}; + + + +#define YYSTYPE opcodeRec * + +#define NSEEL_CLOSEFACTOR 0.00001 + + +typedef struct opcodeRec opcodeRec; + +typedef struct _codeHandleFunctionRec +{ + struct _codeHandleFunctionRec *next; // main linked list (only used for high level functions) + struct _codeHandleFunctionRec *derivedCopies; // separate linked list, head being the main function, other copies being derived versions + + void *startptr; // compiled code (may be cleared + recompiled when shared) + opcodeRec *opcodes; + + int startptr_size; // 0=no code. -1 = needs calculation. >0 = size. + int startptr_base_size; // initially calculated size of root function + int tmpspace_req; + + int num_params; + + int rvMode; // RETURNVALUE_* + int fpStackUsage; // 0-8, usually + int canHaveDenormalOutput; + + // local storage's first items are the parameters, then locals. Note that the opcodes will reference localstorage[] via VARPTRPTR, but + // the values localstorage[x] points are reallocated from context-to-context, if it is a common function. + + // separately allocated list of pointers, the contents of the list should be zeroed on context changes if a common function + // note that when making variations on a function (context), it is shared, but since it is zeroed on context changes, it is context-local + int localstorage_size; + EEL_F **localstorage; + + int isCommonFunction; + int usesNamespaces; + unsigned int parameterAsNamespaceMask; + + char fname[NSEEL_MAX_FUNCSIG_NAME+1]; +} _codeHandleFunctionRec; + +typedef struct _llBlock { + struct _llBlock *next; + int sizeused; + int sizealloc; + // data follows +} llBlock; + +typedef struct { + llBlock *blocks_code, *blocks_data; + void *workTable; // references a chunk in blocks_data + + void *code; + int code_size; // in case the caller wants to write it out + int code_stats[4]; + + int want_stack; + void *stack; // references a chunk in blocks_data, somewhere within the complete NSEEL_STACK_SIZE aligned at NSEEL_STACK_SIZE + + void *ramPtr; + + int workTable_size; // size (minus padding/extra space) of workTable -- only used if EEL_VALIDATE_WORKTABLE_USE set, but might be handy to have around too + int compile_flags; +} codeHandleType; + +typedef struct +{ + EEL_F *value; + int refcnt; + char isreg; + char str[1]; +} varNameRec; + +typedef struct +{ + void *ptr; + int size, alloc; +} eel_growbuf; +#define EEL_GROWBUF(type) union { eel_growbuf _growbuf; type *_tval; } +#define EEL_GROWBUF_RESIZE(gb, newsz) __growbuf_resize(&(gb)->_growbuf, (newsz)*(int)sizeof((gb)->_tval[0])) // <0 to free, does not realloc down otherwise +#define EEL_GROWBUF_GET(gb) ((gb)->_tval) +#define EEL_GROWBUF_GET_SIZE(gb) ((gb)->_growbuf.size/(int)sizeof((gb)->_tval[0])) + +typedef struct _compileContext +{ + eel_function_table *registered_func_tab; + const char *(*func_check)(const char *fn_name, void *user); // return error message if not permitted + void *func_check_user; + + EEL_GROWBUF(varNameRec *) varNameList; + EEL_F *varValueStore; + int varValueStore_left; + + int errVar,gotEndOfInput; + opcodeRec *result; + char last_error_string[256]; + + void *scanner; + const char *rdbuf_start, *rdbuf, *rdbuf_end; + + llBlock *tmpblocks, // used while compiling, and freed after compiling + + *blocks_head_code, // used while compiling, transferred to code context (whole pages marked as executable) + *blocks_head_data, // used while compiling, transferred to code context + + *ctx_pblocks; // persistent blocks, stores data used by varTable_Names, varTable_Values, etc. + + int l_stats[4]; // source bytes, static code bytes, call code bytes, data bytes + int has_used_global_vars; + + _codeHandleFunctionRec *functions_local, *functions_common; + + // state used while generating functions + int optimizeDisableFlags; + int current_compile_flags; + struct opcodeRec *directValueCache; // linked list using fn as next + + int isSharedFunctions; + int isGeneratingCommonFunction; + int function_usesNamespaces; + int function_globalFlag; // set if restrict globals to function_localTable_Names[2] + // [0] is parameter+local symbols (combined space) + // [1] is symbols which get implied "this." if used + // [2] is globals permitted + int function_localTable_Size[3]; // for parameters only + char **function_localTable_Names[3]; // lists of pointers + EEL_F **function_localTable_ValuePtrs; + const char *function_curName; // name of current function + + EEL_F (*onString)(void *caller_this, struct eelStringSegmentRec *list); + EEL_F (*onNamedString)(void *caller_this, const char *name); + + EEL_F *(*getVariable)(void *userctx, const char *name); + void *getVariable_userctx; + + codeHandleType *tmpCodeHandle; + + struct + { + WDL_UINT64 sign_mask[2]; + WDL_UINT64 abs_mask[2]; + int needfree; + int maxblocks; + double closefact; + EEL_F *blocks[NSEEL_RAM_BLOCKS]; + } *ram_state; // allocated from blocks with 16 byte alignment + + void *gram_blocks; + + void *caller_this; +} +compileContext; + +#define NSEEL_NPARAMS_FLAG_CONST 0x80000 +typedef struct functionType { + const char *name; + void *afunc; + int nParams; + void *replptrs[4]; + NSEEL_PPPROC pProc; +} functionType; + +functionType *nseel_getFunctionByName(compileContext *ctx, const char *name, int *mchk); // sets mchk (if non-NULL) to how far allowed to scan forward for duplicate names +functionType *nseel_enumFunctions(compileContext *ctx, int idx); + +opcodeRec *nseel_createCompiledValue(compileContext *ctx, EEL_F value); +opcodeRec *nseel_createCompiledValuePtr(compileContext *ctx, EEL_F *addrValue, const char *namestr); + +opcodeRec *nseel_createMoreParametersOpcode(compileContext *ctx, opcodeRec *code1, opcodeRec *code2); +opcodeRec *nseel_createSimpleCompiledFunction(compileContext *ctx, int fn, int np, opcodeRec *code1, opcodeRec *code2); +opcodeRec *nseel_createMemoryAccess(compileContext *ctx, opcodeRec *code1, opcodeRec *code2); +opcodeRec *nseel_createIfElse(compileContext *ctx, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3); +opcodeRec *nseel_createFunctionByName(compileContext *ctx, const char *name, int np, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3); + +// converts a generic identifier (VARPTR) opcode into either an actual variable reference (parmcnt = -1), +// or if parmcnt >= 0, to a function call (see nseel_setCompiledFunctionCallParameters()) +opcodeRec *nseel_resolve_named_symbol(compileContext *ctx, opcodeRec *rec, int parmcnt, int *errOut); + +// sets parameters and calculates parameter count for opcode, and calls nseel_resolve_named_symbol() with the right +// parameter count +opcodeRec *nseel_setCompiledFunctionCallParameters(compileContext *ctx, opcodeRec *fn, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3, opcodeRec *postCode, int *errOut); +// errOut will be set if return NULL: +// -1 if postCode set when not wanted (i.e. not while()) +// 0 if func not found, +// 1 if function requires 2+ parameters but was given more +// 2 if function needs more parameters +// 4 if function requires 1 parameter but was given more + + + +struct eelStringSegmentRec *nseel_createStringSegmentRec(compileContext *ctx, const char *str, int len); +opcodeRec *nseel_eelMakeOpcodeFromStringSegments(compileContext *ctx, struct eelStringSegmentRec *rec); + +EEL_F *nseel_int_register_var(compileContext *ctx, const char *name, int isReg, const char **namePtrOut); +_codeHandleFunctionRec *eel_createFunctionNamespacedInstance(compileContext *ctx, _codeHandleFunctionRec *fr, const char *nameptr); + +typedef struct nseel_globalVarItem +{ + EEL_F data; + struct nseel_globalVarItem *_next; + char name[1]; // varlen, does not include _global. prefix +} nseel_globalVarItem; + +extern nseel_globalVarItem *nseel_globalreg_list; // if NSEEL_EEL1_COMPAT_MODE, must use NSEEL_getglobalregs() for regxx values + +#include "y.tab.h" + +// nseel_simple_tokenizer will return comments as tokens if state is non-NULL +const char *nseel_simple_tokenizer(const char **ptr, const char *endptr, int *lenOut, int *state); +int nseel_filter_escaped_string(char *outbuf, int outbuf_sz, const char *rdptr, size_t rdptr_size, char delim_char); // returns length used, minus NUL char + +opcodeRec *nseel_translate(compileContext *ctx, const char *tmp, size_t tmplen); // tmplen=0 for nul-term +int nseel_lookup(compileContext *ctx, opcodeRec **opOut, const char *sname); + +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAMAlloc(EEL_F **blocks, unsigned int w); +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAMAllocGMEM(EEL_F ***blocks, unsigned int w); +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemSet(EEL_F **blocks,EEL_F *dest, EEL_F *v, EEL_F *lenptr); +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemFree(void *blocks, EEL_F *which); +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemTop(void *blocks, EEL_F *which); +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemCpy(EEL_F **blocks,EEL_F *dest, EEL_F *src, EEL_F *lenptr); +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_MemSumProducts(EEL_F **blocks,EEL_F *dest, EEL_F *src, EEL_F *lenptr); +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_MemInsertShuffle(EEL_F **blocks,EEL_F *buf, EEL_F *len, EEL_F *value); +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_Mem_SetValues(EEL_F **blocks, INT_PTR np, EEL_F **parms); +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_Mem_GetValues(EEL_F **blocks, INT_PTR np, EEL_F **parms); + +extern EEL_F nseel_ramalloc_onfail; // address returned by __NSEEL_RAMAlloc et al on failure +extern EEL_F * volatile nseel_gmembuf_default; // can free/zero this on DLL unload if needed + +#ifdef __cplusplus +} +#endif + + +#endif//__NS_EELINT_H__ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel.h new file mode 100644 index 0000000..8e1a372 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/ns-eel.h @@ -0,0 +1,264 @@ +/* + Nullsoft Expression Evaluator Library (NS-EEL) + Copyright (C) 1999-2003 Nullsoft, Inc. + + ns-eel.h: main application interface header + + 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. +*/ + + +#ifndef __NS_EEL_H__ +#define __NS_EEL_H__ + +// put standard includes here +#include +#include + +#ifndef EEL_F_SIZE +#define EEL_F_SIZE 8 +#endif + +#include "../wdltypes.h" + +typedef double EEL_F WDL_FIXALIGN; +typedef double *EEL_F_PTR; + +#ifdef _MSC_VER +#define NSEEL_CGEN_CALL __cdecl +#else +#define NSEEL_CGEN_CALL +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +// host should implement these (can be empty stub functions if no VM will execute code in multiple threads at once) + + // implement if you will be running the code in same VM from multiple threads, + // or VMs that have the same GRAM pointer from different threads, or multiple + // VMs that have a NULL GRAM pointer from multiple threads. + // if you give each VM it's own unique GRAM and only run each VM in one thread, then you can leave it blank. + + // or if you're daring.... + +void NSEEL_HOSTSTUB_EnterMutex(); +void NSEEL_HOSTSTUB_LeaveMutex(); + + +int NSEEL_init(); // returns nonzero on failure (only if EEL_VALIDATE_FSTUBS defined), otherwise the same as NSEEL_quit(), and completely optional +void NSEEL_quit(); // clears any added functions + + +// adds a function that returns a value (EEL_F) +#define NSEEL_addfunc_retval(name,np,pproc,fptr) \ + NSEEL_addfunc_ret_type(name,np,1,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) + +// adds a function that returns a pointer (EEL_F*) +#define NSEEL_addfunc_retptr(name,np,pproc,fptr) \ + NSEEL_addfunc_ret_type(name,np,0,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) + +// adds a void or bool function +#define NSEEL_addfunc_retbool(name,np,pproc,fptr) \ + NSEEL_addfunc_ret_type(name,np,-1,pproc,(void *)(fptr),NSEEL_ADDFUNC_DESTINATION) + +// adds a function that takes min_np or more parameters (func sig needs to be EEL_F func(void *ctx, INT_PTR np, EEL_F **parms) +#define NSEEL_addfunc_varparm(name, min_np, pproc, fptr) \ + NSEEL_addfunc_varparm_ex(name,min_np,0,pproc,fptr,NSEEL_ADDFUNC_DESTINATION) + +// adds a function that takes np parameters via func: sig needs to be EEL_F func(void *ctx, INT_PTR np, EEL_F **parms) +#define NSEEL_addfunc_exparms(name, np, pproc, fptr) \ + NSEEL_addfunc_varparm_ex(name,np,1,pproc,fptr,NSEEL_ADDFUNC_DESTINATION) + + +// deprecated +#define NSEEL_addfunction(name,nparms,code,len) NSEEL_addfunctionex((name),(nparms),(code),(len),0,0) +#define NSEEL_addfunctionex(name,nparms,code,len,pproc,fptr) NSEEL_addfunctionex2((name),(nparms),(code),(len),(pproc),(fptr),0, NSEEL_ADDFUNC_DESTINATION) + +#ifndef NSEEL_ADDFUNC_DESTINATION +#define NSEEL_ADDFUNC_DESTINATION (NULL) +#endif + +struct functionType; + +typedef struct +{ + struct functionType *list; + int list_size; +} eel_function_table; + +struct _compileContext; +typedef void *(*NSEEL_PPPROC)(void *data, int data_size, struct _compileContext *userfunc_data); +void NSEEL_addfunctionex2(const char *name, int nparms, char *code_startaddr, int code_len, NSEEL_PPPROC pproc, void *fptr, void *fptr2, eel_function_table *destination); + +void NSEEL_addfunc_ret_type(const char *name, int np, int ret_type, NSEEL_PPPROC pproc, void *fptr, eel_function_table *destination); // ret_type=-1 for bool, 1 for value, 0 for ptr +void NSEEL_addfunc_varparm_ex(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination); +void NSEEL_addfunc_varparm_ctxptr(const char *name, int min_np, int want_exact, void *ctxptr, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination); +void NSEEL_addfunc_varparm_ctxptr2(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, void *ctx, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, void *,INT_PTR, EEL_F **), eel_function_table *destination); + +int *NSEEL_getstats(); // returns a pointer to 5 ints... source bytes, static code bytes, call code bytes, data bytes, number of code handles + +typedef void *NSEEL_VMCTX; +typedef void *NSEEL_CODEHANDLE; + +NSEEL_VMCTX NSEEL_VM_alloc(); // return a handle +void NSEEL_VM_free(NSEEL_VMCTX ctx); // free when done with a VM and ALL of its code have been freed, as well + +void NSEEL_VM_SetFunctionTable(NSEEL_VMCTX, eel_function_table *tab); // use NULL to use default (global) table + +// validateFunc can return error message if not permitted +void NSEEL_VM_SetFunctionValidator(NSEEL_VMCTX, const char * (*validateFunc)(const char *fn_name, void *user), void *user); + +void NSEEL_VM_remove_unused_vars(NSEEL_VMCTX _ctx); +void NSEEL_VM_clear_var_refcnts(NSEEL_VMCTX _ctx); +void NSEEL_VM_remove_all_nonreg_vars(NSEEL_VMCTX _ctx); +void NSEEL_VM_enumallvars(NSEEL_VMCTX ctx, int (*func)(const char *name, EEL_F *val, void *ctx), void *userctx); // return false from func to stop + +EEL_F *NSEEL_VM_regvar(NSEEL_VMCTX ctx, const char *name); // register a variable (before compilation) +EEL_F *NSEEL_VM_getvar(NSEEL_VMCTX ctx, const char *name); // get a variable (if registered or created by code) +int NSEEL_VM_get_var_refcnt(NSEEL_VMCTX _ctx, const char *name); // returns -1 if not registered, or >=0 +void NSEEL_VM_set_var_resolver(NSEEL_VMCTX ctx, EEL_F *(*res)(void *userctx, const char *name), void *userctx); + +void NSEEL_VM_freeRAM(NSEEL_VMCTX ctx); // clears and frees all (VM) RAM used +void NSEEL_VM_freeRAMIfCodeRequested(NSEEL_VMCTX); // call after code to free the script-requested memory +int NSEEL_VM_wantfreeRAM(NSEEL_VMCTX ctx); // want NSEEL_VM_freeRAMIfCodeRequested? + +// if you set this, it uses a local GMEM context. +// Must be set before compilation. +// void *p=NULL; +// NSEEL_VM_SetGRAM(ctx,&p); +// .. do stuff +// NSEEL_VM_FreeGRAM(&p); +void NSEEL_VM_SetGRAM(NSEEL_VMCTX ctx, void **gram); +void NSEEL_VM_FreeGRAM(void **ufd); // frees a gmem context. +void NSEEL_VM_SetCustomFuncThis(NSEEL_VMCTX ctx, void *thisptr); + +EEL_F *NSEEL_VM_getramptr(NSEEL_VMCTX ctx, unsigned int offs, int *validCount); +EEL_F *NSEEL_VM_getramptr_noalloc(NSEEL_VMCTX ctx, unsigned int offs, int *validCount); + + +// set 0 to query. returns actual value used (limits, granularity apply -- see NSEEL_RAM_BLOCKS) +int NSEEL_VM_setramsize(NSEEL_VMCTX ctx, int maxent); +void NSEEL_VM_preallocram(NSEEL_VMCTX ctx, int maxent); // maxent=-1 for all allocated + + +struct eelStringSegmentRec { + struct eelStringSegmentRec *_next; + const char *str_start; // escaped characters, including opening/trailing characters + int str_len; +}; +void NSEEL_VM_SetStringFunc(NSEEL_VMCTX ctx, + EEL_F (*onString)(void *caller_this, struct eelStringSegmentRec *list), + EEL_F (*onNamedString)(void *caller_this, const char *name)); + +// call with NULL to calculate size, or non-null to generate to buffer (returning size used -- will not null terminate, caller responsibility) +int nseel_stringsegments_tobuf(char *bufOut, int bufout_sz, struct eelStringSegmentRec *list); + + +NSEEL_CODEHANDLE NSEEL_code_compile(NSEEL_VMCTX ctx, const char *code, int lineoffs); +#define NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS 1 // allows that code's functions to be used in other code (note you shouldn't destroy that codehandle without destroying others first if used) +#define NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS_RESET 2 // resets common code functions +#define NSEEL_CODE_COMPILE_FLAG_NOFPSTATE 4 // hint that the FPU/SSE state should be good-to-go +#define NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS 8 // very restrictive mode (only math functions really) + +NSEEL_CODEHANDLE NSEEL_code_compile_ex(NSEEL_VMCTX ctx, const char *code, int lineoffs, int flags); + +char *NSEEL_code_getcodeerror(NSEEL_VMCTX ctx); +int NSEEL_code_geterror_flag(NSEEL_VMCTX ctx); +void NSEEL_code_execute(NSEEL_CODEHANDLE code); +void NSEEL_code_free(NSEEL_CODEHANDLE code); +int *NSEEL_code_getstats(NSEEL_CODEHANDLE code); // 4 ints...source bytes, static code bytes, call code bytes, data bytes + + +// global memory control/view +extern unsigned int NSEEL_RAM_limitmem; // if nonzero, memory limit for user data, in bytes +extern unsigned int NSEEL_RAM_memused; +extern int NSEEL_RAM_memused_errors; + + + +// configuration: + +// use the handwritten lexer -- the flex (eel2.l generated) lexer mostly works, but doesn't support string parsing at the moment +// this mode is faster and uses less ram than eel2.l anyway, so leave it on +#define NSEEL_SUPER_MINIMAL_LEXER + + // #define NSEEL_EEL1_COMPAT_MODE // supports old behaviors (continue after failed compile), old functions _bnot etc. disables string support (strings were used as comments in eel1 etc) + +#define NSEEL_MAX_VARIABLE_NAMELEN 128 // define this to override the max variable length +#define NSEEL_MAX_EELFUNC_PARAMETERS 40 +#define NSEEL_MAX_FUNCSIG_NAME 2048 // longer than variable maxlen, due to multiple namespaces + +// maximum loop length (0 for unlimited) +#ifndef NSEEL_LOOPFUNC_SUPPORT_MAXLEN +#define NSEEL_LOOPFUNC_SUPPORT_MAXLEN 1048576 +#endif + +#define NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE 2048 + +// when a VM ctx doesn't have a GRAM context set, make the global one this big +#define NSEEL_SHARED_GRAM_SIZE (1<<20) + +//#define EEL_DUMP_OPS // used for testing frontend parser/logic changes + +// note: if you wish to change NSEEL_RAM_*, and your target is x86-64, you will +// need to edit asm-nseel-x64-sse.asm to match + +// 512 * 65536 = 32 million entries maximum (256MB RAM) +// default is limited to 128 * 65536 = 8 million entries (64MB RAM) + +// default to 8 million entries, use NSEEL_VM_setramsize() to change at runtime +#define NSEEL_RAM_BLOCKS_DEFAULTMAX 128 + +// 512 entry block table maximum (2k/4k per VM) +#define NSEEL_RAM_BLOCKS_LOG2 9 + + // 65536 items per block (512KB) +#define NSEEL_RAM_ITEMSPERBLOCK_LOG2 16 + +#define NSEEL_RAM_BLOCKS (1 << NSEEL_RAM_BLOCKS_LOG2) +#define NSEEL_RAM_ITEMSPERBLOCK (1< +#include + + + +// these are used by our assembly code + + +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UPPER_MASK 0x80000000UL /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffffUL /* least significant r bits */ + +static unsigned int genrand_int32(void) +{ + + unsigned int y; + static unsigned int mag01[2]={0x0UL, MATRIX_A}; + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + static unsigned int mt[N]; /* the array for the state vector */ + static unsigned int __idx; + + unsigned int mti = __idx; + + if (!mti) + { + unsigned int s=0x4141f00d; + mt[0]= s & 0xffffffffUL; + for (mti=1; mti> 30)) + mti); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + mt[mti] &= 0xffffffffUL; + /* for >32 bit machines */ + } + __idx = N; // mti = N (from loop) + } + + if (mti >= N) { /* generate N words at one time */ + int kk; + __idx = 1; + + for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; + } + for (;kk> 1) ^ mag01[y & 0x1UL]; + } + y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + + mti = 0; + } + else + __idx++; + + y = mt[mti]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} + + + +//--------------------------------------------------------------------------------------------------------------- +EEL_F NSEEL_CGEN_CALL nseel_int_rand(EEL_F f) +{ + EEL_F x=floor(f); + if (x < 1.0) x=1.0; + +#ifdef NSEEL_EEL1_COMPAT_MODE + return (EEL_F)(genrand_int32()%(int)x); +#else + return (EEL_F) (genrand_int32()*(1.0/(double)0xFFFFFFFF)*x); +#endif +} + +//--------------------------------------------------------------------------------------------------------------- + + +#ifndef EEL_TARGET_PORTABLE + +#ifdef __ppc__ +#include "asm-nseel-ppc-gcc.c" +#elif defined(__aarch64__) +#include "asm-nseel-aarch64-gcc.c" +#elif defined(_M_ARM64) || defined(_M_ARM64EC) +// add asm-nseel-aarch64-msvc.obj / asm-nseel-arm64ec.obj to project +#elif defined(__arm__) +#include "asm-nseel-arm-gcc.c" +#elif defined (_M_ARM) && _M_ARM == 7 + // vc on ARM, tbd +#else + #ifdef _MSC_VER + #ifdef _WIN64 + //nasm + #else + #include "asm-nseel-x86-msvc.c" + #endif + #elif !defined(__LP64__) && !defined(_WIN64) + #define EEL_F_SUFFIX "l" + #define FUNCTION_MARKER "\n.byte 0x89,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90\n" + #include "asm-nseel-x86-gcc.c" + #endif +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-compiler.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-compiler.c new file mode 100644 index 0000000..a0c39cb --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-compiler.c @@ -0,0 +1,5819 @@ +/* + Expression Evaluator Library (NS-EEL) v2 + Copyright (C) 2004-2013 Cockos Incorporated + Copyright (C) 1999-2003 Nullsoft, Inc. + + nseel-compiler.c + + 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. +*/ + +#include "ns-eel-int.h" + +#include "../denormal.h" + +#include +#include +#include +#include + +#include "../wdlcstring.h" + +#if !defined(EEL_TARGET_PORTABLE) && !defined(_WIN32) +#include +#include +#include +#endif + +#ifdef __APPLE__ +#include +#endif + +#define NSEEL_VARS_MALLOC_CHUNKSIZE 8 + +//#define LOG_OPT +//#define EEL_PRINT_FAILS +//#define EEL_VALIDATE_WORKTABLE_USE + + +#ifdef EEL_PRINT_FAILS + #define RET_MINUS1_FAIL(x) { wdl_log("%s\n",x); return -1; } +#else +#define RET_MINUS1_FAIL(x) return -1; +#endif + +#ifdef EEL_DUMP_OPS +FILE *g_eel_dump_fp, *g_eel_dump_fp2; +#endif + +#ifdef EEL_VALIDATE_WORKTABLE_USE + #define MIN_COMPUTABLE_SIZE 0 + #define COMPUTABLE_EXTRA_SPACE 64 // safety buffer, if EEL_VALIDATE_WORKTABLE_USE set, used for magic-value-checking +#else + #define MIN_COMPUTABLE_SIZE 32 // always use at least this big of a temp storage table (and reset the temp ptr when it goes past this boundary) + #define COMPUTABLE_EXTRA_SPACE 16 // safety buffer, if EEL_VALIDATE_WORKTABLE_USE set, used for magic-value-checking +#endif + + +/* + P1 is rightmost parameter + P2 is second rightmost, if any + P3 is third rightmost, if any + registers on x86 are (RAX etc on x86-64) + P1(ret) EAX + P2 EDI + P3 ECX + WTP RSI + x86_64: r12 is a pointer to ram_state->blocks + x86_64: r13 is a pointer to closenessfactor + + registers on PPC are: + P1(ret) r3 + P2 r14 + P3 r15 + WTP r16 (r17 has the original value) + r13 is a pointer to ram_state->blocks + + ppc uses f31 and f30 and others for certain constants + + */ + + +#ifdef EEL_TARGET_PORTABLE + +#define EEL_DOESNT_NEED_EXEC_PERMS + +#ifdef EEL_PORTABLE_TAILCALL +#include "glue_port_new.h" +#else +#include "glue_port.h" +#endif + +#elif defined(__ppc__) + +#include "glue_ppc.h" + +#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) + +#include "glue_aarch64.h" + +#elif defined(__arm__) || (defined (_M_ARM) && _M_ARM == 7) + +#include "glue_arm.h" + +#elif defined(_WIN64) || defined(__LP64__) + +#include "glue_x86_64_sse.h" + +#else + +#include "glue_x86.h" + +#endif + +#ifndef GLUE_INVSQRT_NEEDREPL +#define GLUE_INVSQRT_NEEDREPL 0 +#endif + + +// used by //#eel-no-optimize:xxx, in ctx->optimizeDisableFlags +#define OPTFLAG_NO_OPTIMIZE 1 +#define OPTFLAG_NO_FPSTACK 2 +#define OPTFLAG_NO_INLINEFUNC 4 +#define OPTFLAG_FULL_DENORMAL_CHECKS 8 // if set, denormals/NaN are always filtered on assign +#define OPTFLAG_NO_DENORMAL_CHECKS 16 // if set and FULL not set, denormals/NaN are never filtered on assign + + +#define DENORMAL_CLEARING_THRESHOLD 1.0e-50 // when adding/subtracting a constant, assume if it's greater than this, it will clear denormal (the actual value is probably 10^-290...) + + +#define MAX_SUB_NAMESPACES 32 +typedef struct +{ + const char *namespacePathToThis; + const char *subParmInfo[MAX_SUB_NAMESPACES]; +} namespaceInformation; + + + + +static int nseel_evallib_stats[5]; // source bytes, static code bytes, call code bytes, data bytes, segments +int *NSEEL_getstats() +{ + return nseel_evallib_stats; +} + +static int findLineNumber(const char *exp, int byteoffs) +{ + int lc=0; + while (byteoffs-->0 && *exp) if (*exp++ =='\n') lc++; + return lc; +} + + +static int nseel_vms_referencing_globallist_cnt; +nseel_globalVarItem *nseel_globalreg_list; +static EEL_F *get_global_var(compileContext *ctx, const char *gv, int addIfNotPresent); + +static void *__newBlock_align(llBlock **start,int size, int align, int code_page_size); + +#define OPCODE_IS_TRIVIAL(x) ((x)->opcodeType <= OPCODETYPE_VARPTRPTR) +enum { + OPCODETYPE_DIRECTVALUE=0, + OPCODETYPE_DIRECTVALUE_TEMPSTRING, // like directvalue, but will generate a new tempstring value on generate + OPCODETYPE_VALUE_FROM_NAMESPACENAME, // this.* or namespace.* are encoded this way + OPCODETYPE_VARPTR, + OPCODETYPE_VARPTRPTR, + OPCODETYPE_FUNC1, + OPCODETYPE_FUNC2, + OPCODETYPE_FUNC3, + OPCODETYPE_FUNCX, + + OPCODETYPE_MOREPARAMS, + + OPCODETYPE_INVALID, +}; + +struct opcodeRec +{ + int opcodeType; + int fntype; + void *fn; + + union { + struct opcodeRec *parms[3]; + struct { + double directValue; + EEL_F *valuePtr; // if direct value, valuePtr can be cached + } dv; + } parms; + + int namespaceidx; + + // OPCODETYPE_VALUE_FROM_NAMESPACENAME (relname is either empty or blah) + // OPCODETYPE_VARPTR if it represents a global variable, will be nonempty + // OPCODETYPE_FUNC* with fntype=FUNCTYPE_EELFUNC + const char *relname; +}; + + + + + +static opcodeRec *newOpCode(compileContext *ctx, const char *str, int opType) +{ + const size_t strszfull = str ? strlen(str) : 0; + const size_t str_sz = wdl_min(NSEEL_MAX_VARIABLE_NAMELEN, strszfull); + + opcodeRec *rec = (opcodeRec*)__newBlock_align(ctx->isSharedFunctions ? &ctx->blocks_head_data : &ctx->tmpblocks, + (int) (sizeof(opcodeRec) + (str_sz>0 ? str_sz+1 : 0)), + 8, 0); + if (rec) + { + memset(rec,0,sizeof(*rec)); + rec->opcodeType = opType; + + if (str_sz > 0) + { + char *p = (char *)(rec+1); + memcpy(p,str,str_sz); + p[str_sz]=0; + + rec->relname = p; + } + else + { + rec->relname = ""; + } + } + + return rec; +} + +#ifndef EEL_DOESNT_NEED_EXEC_PERMS +static int eel_get_page_size(void) +{ + static int pagesize; + if (!pagesize) + { +#ifndef _WIN32 + const int ps = (int)sysconf(_SC_PAGESIZE); + pagesize = wdl_max(ps, 4096); +#else + SYSTEM_INFO inf = { 0 }; + GetSystemInfo(&inf); + pagesize = wdl_max(inf.dwPageSize, 4096); +#endif + } + return pagesize; +} +#endif + +#define newCodeBlock(x,a) __newBlock_align(&ctx->blocks_head_code,x,a, 1) +#define newDataBlock(x,a) __newBlock_align(&ctx->blocks_head_data,x,a,0) +#define newCtxDataBlock(x,a) __newBlock_align(&ctx->ctx_pblocks,x,a,0) +#define newTmpBlock(ctx, size) __newBlock_align(&(ctx)->tmpblocks, size, 8,0) + +static char *eel_get_llblock_buffer(llBlock *llb) { return (char *) (llb+1); } + +#ifndef EEL_DOESNT_NEED_EXEC_PERMS +static void eel_set_blocks_allow_execute(llBlock *llb, int exec) +{ + while (llb) + { + const size_t sz = sizeof(*llb) + llb->sizealloc; + #ifdef _WIN32 + DWORD ov; + VirtualProtect(llb,sz,exec ? (PAGE_EXECUTE_READ) : (PAGE_READWRITE),&ov); + FlushInstructionCache(GetCurrentProcess(),llb,sz); + #else + mprotect(llb,sz,exec ? (PROT_READ|PROT_EXEC) : (PROT_READ|PROT_WRITE)); + #ifdef __APPLE__ + if (exec) sys_icache_invalidate(llb,sz); + #endif + #endif + WDL_ASSERT((((INT_PTR)llb) & (eel_get_page_size()-1)) == 0); + WDL_ASSERT((sz & (eel_get_page_size()-1)) == 0); + llb = llb->next; + } +} +#endif + +static void freeBlocks(llBlock **start, int is_code); + +static int __growbuf_resize(eel_growbuf *buf, int newsize) +{ + if (newsize<0) + { + free(buf->ptr); + buf->ptr=NULL; + buf->alloc=buf->size=0; + return 0; + } + + if (newsize > buf->alloc) + { + const int newalloc = newsize + 4096 + newsize/2; + void *newptr = realloc(buf->ptr,newalloc); + if (!newptr) + { + newptr = malloc(newalloc); + if (!newptr) return 1; + if (buf->ptr && buf->size) memcpy(newptr,buf->ptr,buf->size); + free(buf->ptr); + buf->ptr=newptr; + } + else + buf->ptr = newptr; + + buf->alloc=newalloc; + } + buf->size = newsize; + return 0; +} + + +#ifndef DECL_ASMFUNC +#define DECL_ASMFUNC(x) void nseel_asm_##x(void); + + +void _asm_megabuf(void); +void _asm_gmegabuf(void); + +#endif + + + DECL_ASMFUNC(booltofp) + DECL_ASMFUNC(fptobool) + DECL_ASMFUNC(fptobool_rev) + DECL_ASMFUNC(sin) + DECL_ASMFUNC(cos) + DECL_ASMFUNC(tan) + DECL_ASMFUNC(1pdd) + DECL_ASMFUNC(2pdd) + DECL_ASMFUNC(2pdds) + DECL_ASMFUNC(1pp) + DECL_ASMFUNC(2pp) + DECL_ASMFUNC(sqr) + DECL_ASMFUNC(sqrt) + DECL_ASMFUNC(log) + DECL_ASMFUNC(log10) + DECL_ASMFUNC(abs) + DECL_ASMFUNC(min) + DECL_ASMFUNC(max) + DECL_ASMFUNC(min_fp) + DECL_ASMFUNC(max_fp) + DECL_ASMFUNC(sig) + DECL_ASMFUNC(sign) + DECL_ASMFUNC(band) + DECL_ASMFUNC(bor) + DECL_ASMFUNC(bnot) + DECL_ASMFUNC(if) + DECL_ASMFUNC(fcall) + DECL_ASMFUNC(repeat) + DECL_ASMFUNC(repeatwhile) + DECL_ASMFUNC(equal) + DECL_ASMFUNC(equal_exact) + DECL_ASMFUNC(notequal_exact) + DECL_ASMFUNC(notequal) + DECL_ASMFUNC(below) + DECL_ASMFUNC(above) + DECL_ASMFUNC(beloweq) + DECL_ASMFUNC(aboveeq) + DECL_ASMFUNC(assign) + DECL_ASMFUNC(assign_fromfp) + DECL_ASMFUNC(assign_fast) + DECL_ASMFUNC(assign_fast_fromfp) + DECL_ASMFUNC(add) + DECL_ASMFUNC(sub) + DECL_ASMFUNC(add_op) + DECL_ASMFUNC(sub_op) + DECL_ASMFUNC(add_op_fast) + DECL_ASMFUNC(sub_op_fast) + DECL_ASMFUNC(mul) + DECL_ASMFUNC(div) + DECL_ASMFUNC(mul_op) + DECL_ASMFUNC(div_op) + DECL_ASMFUNC(mul_op_fast) + DECL_ASMFUNC(div_op_fast) + DECL_ASMFUNC(mod) + DECL_ASMFUNC(shl) + DECL_ASMFUNC(shr) + DECL_ASMFUNC(mod_op) + DECL_ASMFUNC(or) + DECL_ASMFUNC(or0) + DECL_ASMFUNC(xor) + DECL_ASMFUNC(xor_op) + DECL_ASMFUNC(and) + DECL_ASMFUNC(or_op) + DECL_ASMFUNC(and_op) + DECL_ASMFUNC(uminus) + DECL_ASMFUNC(invsqrt) + DECL_ASMFUNC(dbg_getstackptr) + + DECL_ASMFUNC(stack_push) + DECL_ASMFUNC(stack_pop) + DECL_ASMFUNC(stack_pop_fast) // just returns value, doesn't mod param + DECL_ASMFUNC(stack_peek) + DECL_ASMFUNC(stack_peek_int) + DECL_ASMFUNC(stack_peek_top) + DECL_ASMFUNC(stack_exch) + +static void *NSEEL_PProc_GRAM(void *data, int data_size, compileContext *ctx) +{ + if (data_size>0) data=EEL_GLUE_set_immediate(data, (INT_PTR)ctx->gram_blocks); + return data; +} + +static void *NSEEL_PProc_Stack(void *data, int data_size, compileContext *ctx) +{ + codeHandleType *ch=ctx->tmpCodeHandle; + + if (data_size>0) + { + UINT_PTR m1=(UINT_PTR)(NSEEL_STACK_SIZE * sizeof(EEL_F) - 1); + UINT_PTR stackptr = ((UINT_PTR) (&ch->stack)); + + ch->want_stack=1; + if (!ch->stack) ch->stack = newDataBlock(NSEEL_STACK_SIZE*sizeof(EEL_F),NSEEL_STACK_SIZE*sizeof(EEL_F)); // stack functions need this alignment + + data=EEL_GLUE_set_immediate(data, stackptr); + data=EEL_GLUE_set_immediate(data, m1); // and + data=EEL_GLUE_set_immediate(data, ((UINT_PTR)ch->stack&~m1)); //or + } + return data; +} + +static void *NSEEL_PProc_Stack_PeekInt(void *data, int data_size, compileContext *ctx, INT_PTR offs) +{ + codeHandleType *ch=ctx->tmpCodeHandle; + + if (data_size>0) + { + UINT_PTR m1=(UINT_PTR)(NSEEL_STACK_SIZE * sizeof(EEL_F) - 1); + UINT_PTR stackptr = ((UINT_PTR) (&ch->stack)); + + ch->want_stack=1; + if (!ch->stack) ch->stack = newDataBlock(NSEEL_STACK_SIZE*sizeof(EEL_F),NSEEL_STACK_SIZE*sizeof(EEL_F)); // stack functions need this alignment + + data=EEL_GLUE_set_immediate(data, stackptr); + data=EEL_GLUE_set_immediate(data, offs); + data=EEL_GLUE_set_immediate(data, m1); // and + data=EEL_GLUE_set_immediate(data, ((UINT_PTR)ch->stack&~m1)); //or + } + return data; +} +static void *NSEEL_PProc_Stack_PeekTop(void *data, int data_size, compileContext *ctx) +{ + codeHandleType *ch=ctx->tmpCodeHandle; + + if (data_size>0) + { + UINT_PTR stackptr = ((UINT_PTR) (&ch->stack)); + + ch->want_stack=1; + if (!ch->stack) ch->stack = newDataBlock(NSEEL_STACK_SIZE*sizeof(EEL_F),NSEEL_STACK_SIZE*sizeof(EEL_F)); // stack functions need this alignment + + data=EEL_GLUE_set_immediate(data, stackptr); + } + return data; +} + +#if defined(_MSC_VER) && _MSC_VER >= 1400 +static double eel__floor(double a) { return floor(a); } +static double eel__ceil(double a) { return ceil(a); } +#define floor eel__floor +#define ceil eel__ceil +#endif + + +#ifdef NSEEL_EEL1_COMPAT_MODE +static double eel1band(double a, double b) +{ + return (fabs(a)>NSEEL_CLOSEFACTOR && fabs(b) > NSEEL_CLOSEFACTOR) ? 1.0 : 0.0; +} +static double eel1bor(double a, double b) +{ + return (fabs(a)>NSEEL_CLOSEFACTOR || fabs(b) > NSEEL_CLOSEFACTOR) ? 1.0 : 0.0; +} + +static double eel1sigmoid(double x, double constraint) +{ + double t = (1+exp(-x * (constraint))); + return fabs(t)>NSEEL_CLOSEFACTOR ? 1.0/t : 0; +} + +#endif + + + +#define FUNCTIONTYPE_PARAMETERCOUNTMASK 0xff + +#define BIF_NPARAMS_MASK 0x7ffff00 +#define BIF_RETURNSONSTACK 0x0000100 +#define BIF_LASTPARMONSTACK 0x0000200 +#define BIF_RETURNSBOOL 0x0000400 +#define BIF_LASTPARM_ASBOOL 0x0000800 +// 0x00?0000 -- taken by FP stack flags +#define BIF_TAKES_VARPARM 0x0400000 +#define BIF_TAKES_VARPARM_EX 0x0C00000 // this is like varparm but check count exactly +#define BIF_WONTMAKEDENORMAL 0x0100000 +#define BIF_CLEARDENORMAL 0x0200000 + +#if GLUE_HAS_FPREG2 > 0 && GLUE_MAX_FPSTACK_SIZE > 0 +#error GLUE_HAS_FPREG2 and GLUE_MAX_FPSTACK_SIZE are exclusive +#endif + +#if GLUE_MAX_SPILL_REGS > 0 && GLUE_HAS_FPREG2 <= 0 +#error GLUE_MAX_SPILL_REGS requires GLUE_HAS_FPREG2 +#endif + +#if GLUE_MAX_FPSTACK_SIZE > 0 || GLUE_HAS_FPREG2 > 0 + #define BIF_SECONDLASTPARMST 0x0001000 // use with BIF_LASTPARMONSTACK only (last two parameters get passed on fp stack) + #define BIF_LAZYPARMORDERING 0x0002000 // allow optimizer to avoid fxch when using BIF_TWOPARMSONFPSTACK_LAZY etc +#else + #define BIF_SECONDLASTPARMST 0 + #define BIF_LAZYPARMORDERING 0 +#endif + +#if GLUE_MAX_FPSTACK_SIZE > 0 + #define BIF_REVERSEFPORDER 0x0004000 // force a fxch (reverse order of last two parameters on fp stack, used by comparison functions) + #ifndef BIF_FPSTACKUSE + #define BIF_FPSTACKUSE(x) (((x)>=0&&(x)<8) ? ((7-(x))<<16):0) + #endif + #ifndef BIF_GETFPSTACKUSE + #define BIF_GETFPSTACKUSE(x) (7 - (((x)>>16)&7)) + #endif +#else + #define BIF_REVERSEFPORDER 0 + #define BIF_FPSTACKUSE(x) 0 + #define BIF_GETFPSTACKUSE(x) 0 +#endif + +#define BIF_TWOPARMSONFPSTACK (BIF_SECONDLASTPARMST|BIF_LASTPARMONSTACK) +#define BIF_TWOPARMSONFPSTACK_LAZY (BIF_LAZYPARMORDERING|BIF_SECONDLASTPARMST|BIF_LASTPARMONSTACK) + + +#ifndef GLUE_HAS_NATIVE_TRIGSQRTLOG +static double sqrt_fabs(double a) { return sqrt(fabs(a)); } +#endif + + +EEL_F NSEEL_CGEN_CALL nseel_int_rand(EEL_F f); + +#define FNPTR_HAS_CONDITIONAL_EXEC(op) \ + (op->fntype == FN_LOGICAL_AND || \ + op->fntype == FN_LOGICAL_OR || \ + op->fntype == FN_IF_ELSE || \ + op->fntype == FN_WHILE || \ + op->fntype == FN_LOOP) + +static functionType fnTable1[] = { +#ifndef GLUE_HAS_NATIVE_TRIGSQRTLOG + { "sin", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL, {&sin} }, + { "cos", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&cos} }, + { "tan", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&tan} }, + { "sqrt", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL, {&sqrt_fabs}, }, + { "log", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&log} }, + { "log10", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&log10} }, +#else + { "sin", nseel_asm_sin, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL|BIF_FPSTACKUSE(1) }, + { "cos", nseel_asm_cos, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL|BIF_FPSTACKUSE(1) }, + { "tan", nseel_asm_tan, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1) }, + { "sqrt", nseel_asm_sqrt, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1)|BIF_WONTMAKEDENORMAL }, + { "log", nseel_asm_log, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(3), }, + { "log10", nseel_asm_log10, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(3), }, +#endif + + + { "asin", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&asin}, }, + { "acos", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&acos}, }, + { "atan", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&atan}, }, + { "atan2", nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK, {&atan2}, }, + { "exp", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&exp}, }, + { "abs", nseel_asm_abs, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(0)|BIF_WONTMAKEDENORMAL }, + { "sqr", nseel_asm_sqr, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1) }, + { "min", nseel_asm_min, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_FPSTACKUSE(3)|BIF_WONTMAKEDENORMAL }, + { "max", nseel_asm_max, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_FPSTACKUSE(3)|BIF_WONTMAKEDENORMAL }, + { "sign", nseel_asm_sign, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL, }, + { "rand", nseel_asm_1pdd, 1|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&nseel_int_rand}, }, + + { "floor", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&floor} }, + { "ceil", nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&ceil} }, + + { "invsqrt", nseel_asm_invsqrt, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(3), {GLUE_INVSQRT_NEEDREPL} }, + + { "__dbg_getstackptr", nseel_asm_dbg_getstackptr, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1), }, + +#ifdef NSEEL_EEL1_COMPAT_MODE + { "sigmoid", nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK, {&eel1sigmoid}, }, + + // these differ from _and/_or, they always evaluate both... + { "band", nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_CLEARDENORMAL , {&eel1band}, }, + { "bor", nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_CLEARDENORMAL , {&eel1bor}, }, + + {"exec2", NULL, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_WONTMAKEDENORMAL}, + {"exec3", NULL, 3|NSEEL_NPARAMS_FLAG_CONST|BIF_WONTMAKEDENORMAL}, +#endif // end EEL1 compat + + + {"freembuf",_asm_generic1parm,1,{&__NSEEL_RAM_MemFree},NSEEL_PProc_RAM}, + {"memcpy",_asm_generic3parm, 3,{&__NSEEL_RAM_MemCpy},NSEEL_PProc_RAM}, + {"memset",_asm_generic3parm, 3,{&__NSEEL_RAM_MemSet},NSEEL_PProc_RAM}, + {"__memtop",_asm_generic1parm,1,{&__NSEEL_RAM_MemTop},NSEEL_PProc_RAM}, + {"mem_set_values",_asm_generic2parm_retd,2|BIF_TAKES_VARPARM|BIF_RETURNSONSTACK,{&__NSEEL_RAM_Mem_SetValues},NSEEL_PProc_RAM}, + {"mem_get_values",_asm_generic2parm_retd,2|BIF_TAKES_VARPARM|BIF_RETURNSONSTACK,{&__NSEEL_RAM_Mem_GetValues},NSEEL_PProc_RAM}, + {"mem_multiply_sum",_asm_generic3parm_retd, 3|BIF_RETURNSONSTACK,{&__NSEEL_RAM_MemSumProducts},NSEEL_PProc_RAM}, + {"mem_insert_shuffle",_asm_generic3parm_retd, 3|BIF_RETURNSONSTACK, {&__NSEEL_RAM_MemInsertShuffle},NSEEL_PProc_RAM}, + + {"stack_push",nseel_asm_stack_push,1|BIF_FPSTACKUSE(0),{0,},NSEEL_PProc_Stack}, + {"stack_pop",nseel_asm_stack_pop, 1|BIF_FPSTACKUSE(1),{0,},NSEEL_PProc_Stack}, + {"stack_peek",nseel_asm_stack_peek,1|NSEEL_NPARAMS_FLAG_CONST|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(0),{0,},NSEEL_PProc_Stack}, + {"stack_exch",nseel_asm_stack_exch,1|BIF_FPSTACKUSE(1), {0,},NSEEL_PProc_Stack_PeekTop}, +}; +static functionType fn_min2 = { "min2", nseel_asm_min_fp, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_WONTMAKEDENORMAL }; +static functionType fn_max2 = { "max2", nseel_asm_max_fp, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_WONTMAKEDENORMAL }; +static functionType fn_or0 = { "or0", nseel_asm_or0, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_LASTPARMONSTACK|BIF_RETURNSONSTACK|BIF_CLEARDENORMAL }; + +static eel_function_table default_user_funcs; + +static int functable_lowerbound(functionType *list, int list_sz, const char *name, int *ismatch) +{ + int a = 0, c = list_sz; + while (a != c) + { + const int b = (a+c)/2; + const int cmp = stricmp(name,list[b].name); + if (cmp > 0) a = b+1; + else if (cmp < 0) c = b; + else + { + *ismatch = 1; + return b; + } + } + *ismatch = 0; + return a; +} + +static int funcTypeCmp(const void *a, const void *b) { return stricmp(((functionType*)a)->name,((functionType*)b)->name); } + +functionType *nseel_getFunctionByName(compileContext *ctx, const char *name, int *mchk) +{ + eel_function_table *tab = ctx && ctx->registered_func_tab ? ctx->registered_func_tab : &default_user_funcs; + static char sorted; + const int fn1size = (int) (sizeof(fnTable1)/sizeof(fnTable1[0])); + int idx,match; + if (!sorted) + { + NSEEL_HOSTSTUB_EnterMutex(); + if (!sorted) qsort(fnTable1,fn1size,sizeof(fnTable1[0]),funcTypeCmp); + sorted=1; + NSEEL_HOSTSTUB_LeaveMutex(); + } + idx=functable_lowerbound(fnTable1,fn1size,name,&match); + if (match) return fnTable1+idx; + + if ((!ctx || !(ctx->current_compile_flags&NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS)) && tab->list) + { + idx=functable_lowerbound(tab->list,tab->list_size,name,&match); + if (match) + { + if (mchk) + { + while (idx>0 && !stricmp(tab->list[idx-1].name,name)) idx--; + *mchk = tab->list_size - 1 - idx; + } + return tab->list + idx; + } + } + + return NULL; +} + +functionType *nseel_enumFunctions(compileContext *ctx, int idx) +{ + eel_function_table *tab = ctx && ctx->registered_func_tab ? ctx->registered_func_tab : &default_user_funcs; + const int fn1size = (int) (sizeof(fnTable1)/sizeof(fnTable1[0])); + if (idx >= 0 && idx < fn1size) return fnTable1 + idx; + if ((!ctx || !(ctx->current_compile_flags&NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS)) && tab->list) + { + idx -= fn1size; + if (idx>=0 && idx < tab->list_size) + return tab->list + idx; + } + + return NULL; +} + +int NSEEL_init() // returns 0 on success +{ + NSEEL_quit(); + return 0; +} + +void NSEEL_quit() +{ + free(default_user_funcs.list); + default_user_funcs.list = NULL; + default_user_funcs.list_size = 0; +} + +void NSEEL_addfunc_varparm_ex(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination) +{ + NSEEL_addfunctionex2(name,min_np|(want_exact?BIF_TAKES_VARPARM_EX:BIF_TAKES_VARPARM),(char *)_asm_generic2parm_retd,0,pproc,fptr,NULL,destination); +} + +void NSEEL_addfunc_varparm_ctxptr(const char *name, int min_np, int want_exact, void *ctxptr, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination) +{ + NSEEL_addfunctionex2(name,min_np|(want_exact?BIF_TAKES_VARPARM_EX:BIF_TAKES_VARPARM),(char *)_asm_generic2parm_retd,0,NULL,ctxptr,fptr,destination); +} + +void NSEEL_addfunc_varparm_ctxptr2(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, void *ctx, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, void *, INT_PTR, EEL_F **), eel_function_table *destination) +{ + NSEEL_addfunctionex2(name,min_np|(want_exact?BIF_TAKES_VARPARM_EX:BIF_TAKES_VARPARM),(char *)_asm_generic2xparm_retd,0,pproc,ctx,fptr,destination); +} + +void NSEEL_addfunc_ret_type(const char *name, int np, int ret_type, NSEEL_PPPROC pproc, void *fptr, eel_function_table *destination) // ret_type=-1 for bool, 1 for value, 0 for ptr +{ + char *stub=NULL; + int stubsz=0; +#define DOSTUB(np) { \ + stub = (ret_type == 1 ? (char*)_asm_generic##np##parm_retd : (char*)_asm_generic##np##parm); \ + } + + WDL_ASSERT(np >= 1 && np <= 3); // use np=1 if you want "zero" parameters + + if (np == 1) DOSTUB(1) + else if (np == 2) DOSTUB(2) + else if (np == 3) DOSTUB(3) +#undef DOSTUB + + if (stub) NSEEL_addfunctionex2(name,np|(ret_type == -1 ? BIF_RETURNSBOOL:0), stub, stubsz, pproc,fptr,NULL,destination); +} + +void NSEEL_addfunctionex2(const char *name, int nparms, char *code_startaddr, int code_len /* ignored*/, + NSEEL_PPPROC pproc, void *fptr, void *fptr2, eel_function_table *destination) +{ + const int list_size_chunk = 128; + functionType *r; + if (!destination) destination = &default_user_funcs; + + if (!destination->list || !(destination->list_size & (list_size_chunk-1))) + { + void *nv = realloc(destination->list, (destination->list_size + list_size_chunk)*sizeof(functionType)); + if (!nv) return; + destination->list = (functionType *)nv; + } + if (destination->list) + { + int match,idx; + + idx=functable_lowerbound(destination->list,destination->list_size,name,&match); + + r = destination->list + idx; + if (idx < destination->list_size) + memmove(r + 1, r, (destination->list_size - idx) * sizeof(functionType)); + destination->list_size++; + + memset(r, 0, sizeof(functionType)); + + if (!(nparms & BIF_RETURNSBOOL)) + { + if (code_startaddr == (void *)&_asm_generic1parm_retd || + code_startaddr == (void *)&_asm_generic2parm_retd || + code_startaddr == (void *)&_asm_generic2xparm_retd || + code_startaddr == (void *)&_asm_generic3parm_retd) + { + nparms |= BIF_RETURNSONSTACK; + } + } + r->nParams = nparms; + r->name = name; + r->afunc = code_startaddr; + r->pProc = pproc; + r->replptrs[0] = fptr; + r->replptrs[1] = fptr2; + } +} + + +//--------------------------------------------------------------------------------------------------------------- +static void freeBlocks(llBlock **start, int is_code) +{ + llBlock *s=*start; + *start=0; + while (s) + { + llBlock *llB = s->next; +#ifndef EEL_DOESNT_NEED_EXEC_PERMS + if (is_code) + { + #ifdef _WIN32 + VirtualFree(s, 0, MEM_RELEASE); + #else + munmap(s,sizeof(*s) + s->sizealloc); + #endif + } + else +#endif + { + free(s); + } + s=llB; + } +} + + +//--------------------------------------------------------------------------------------------------------------- +static void *__newBlock_align(llBlock **start, int size, int align, int is_for_code) +{ + llBlock *llb = *start; + int alloc_amt, align_pos, scan_cnt=8; + if (WDL_NOT_NORMALLY(align < 1)) align = 1; + + while (llb && --scan_cnt > 0) + { + const int sizeused = llb->sizeused; + if (sizeused + size <= llb->sizealloc) + { + align_pos = (int) (((INT_PTR)eel_get_llblock_buffer(llb) + sizeused)&(align-1)); + if (align_pos) align_pos = align - align_pos; + + if (sizeused + size + align_pos <= llb->sizealloc) + { + llb->sizeused += size + align_pos; + return eel_get_llblock_buffer(llb) + sizeused + align_pos; + } + } + llb = llb->next; + } + +#ifndef EEL_DOESNT_NEED_EXEC_PERMS + if (is_for_code) + { + const int code_page_size = eel_get_page_size(); + alloc_amt = (sizeof(*llb) + size + code_page_size - 1) & ~(code_page_size-1); + #ifdef _WIN32 + #ifdef _M_ARM64EC + { + MEM_EXTENDED_PARAMETER ext; + memset(&ext,0,sizeof(ext)); + ext.Type = MemExtendedParameterAttributeFlags; + ext.ULong64 = MEM_EXTENDED_PARAMETER_EC_CODE; + llb = (llBlock *)VirtualAlloc2(NULL,NULL,alloc_amt,MEM_COMMIT,PAGE_EXECUTE_READ,&ext,1); + if (WDL_NORMALLY(llb)) + { + DWORD ov; + VirtualProtect(llb, alloc_amt, PAGE_READWRITE, &ov); + } + } + #else + llb = (llBlock *)VirtualAlloc(NULL,alloc_amt,MEM_COMMIT,PAGE_READWRITE); + #endif + if (llb == NULL) return NULL; + #else + #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) + llb = (llBlock *)mmap(NULL,alloc_amt, PROT_READ|PROT_WRITE,MAP_ANON|MAP_PRIVATE,-1,0); + #else + llb = (llBlock *)mmap(NULL,alloc_amt, PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,-1,0); + #endif + if (llb == MAP_FAILED) return NULL; + #endif + alloc_amt -= sizeof(*llb); + align_pos = 0; + + WDL_ASSERT(((INT_PTR)llb & (code_page_size - 1))==0); + WDL_ASSERT(((INT_PTR)(eel_get_llblock_buffer(llb) + alloc_amt) & (code_page_size - 1))==0); + } + else +#endif + { + // data block, allocate in larger chunks + alloc_amt = (size + align - 1 + 31)&~31; + if (alloc_amt < 65536-64) alloc_amt = 65536-64; + + llb = (llBlock *)malloc(sizeof(*llb) + alloc_amt); + if (!llb) return NULL; + align_pos = (int) (((INT_PTR)eel_get_llblock_buffer(llb))&(align-1)); + if (align_pos) align_pos = align - align_pos; + } + + WDL_ASSERT(size+align_pos <= alloc_amt); + llb->sizeused = size + align_pos; + llb->sizealloc = alloc_amt; + llb->next = *start; + *start = llb; + return eel_get_llblock_buffer(llb) + align_pos; +} + + +//--------------------------------------------------------------------------------------------------------------- +opcodeRec *nseel_createCompiledValue(compileContext *ctx, EEL_F value) +{ + opcodeRec *r=newOpCode(ctx,NULL,OPCODETYPE_DIRECTVALUE); + if (r) + { + r->parms.dv.directValue = value; + } + return r; +} + +opcodeRec *nseel_createCompiledValuePtr(compileContext *ctx, EEL_F *addrValue, const char *namestr) +{ + opcodeRec *r=newOpCode(ctx,namestr,OPCODETYPE_VARPTR); + if (!r) return 0; + + r->parms.dv.valuePtr=addrValue; + + return r; +} + +static int validate_varname_for_function(compileContext *ctx, const char *name) +{ + if (!ctx->function_curName || !ctx->function_globalFlag) return 1; + + if (ctx->function_localTable_Size[2] > 0 && ctx->function_localTable_Names[2]) + { + char * const * const namelist = ctx->function_localTable_Names[2]; + const int namelist_sz = ctx->function_localTable_Size[2]; + int i; + const size_t name_len = strlen(name); + + for (i=0;i 1 && nmchk[l-1] == '*') + { + if (name_len >= l && !strnicmp(nmchk,name,l-1) && name[l-1]=='.') return 1; + } + else + { + if (name_len == l && !stricmp(nmchk,name)) return 1; + } + } + } + + return 0; +} + +opcodeRec *nseel_resolve_named_symbol(compileContext *ctx, opcodeRec *rec, int parmcnt, int *errOut) +{ + const int isFunctionMode = parmcnt >= 0; + int rel_prefix_len=0; + int rel_prefix_idx=-2; + int i; + char match_parmcnt[4]={-1,-1,-1,-1}; // [3] is guess + unsigned char match_parmcnt_pos=0; + char *sname = (char *)rec->relname; + int is_string_prefix = parmcnt < 0 && sname[0] == '#'; + const char *prevent_function_calls = NULL; + + if (errOut) *errOut = 0; + + if (sname) sname += is_string_prefix; + + if (rec->opcodeType != OPCODETYPE_VARPTR || !sname || !sname[0]) return NULL; + + if (!isFunctionMode && !is_string_prefix && !strnicmp(sname,"reg",3) && isdigit(sname[3]) && isdigit(sname[4]) && !sname[5]) + { + EEL_F *a=get_global_var(ctx,sname,1); + if (a) + { + rec->parms.dv.valuePtr = a; + sname[0]=0; // for dump_ops compat really, but this shouldn't be needed anyway + } + return rec; + } + + if (ctx->function_curName) + { + if (!strnicmp(sname,"this.",5)) + { + rel_prefix_len=5; + rel_prefix_idx=-1; + } + else if (!stricmp(sname,"this")) + { + rel_prefix_len=4; + rel_prefix_idx=-1; + } + + // scan for parameters/local variables before user functions + if (rel_prefix_idx < -1 && + ctx->function_localTable_Size[0] > 0 && + ctx->function_localTable_Names[0] && + ctx->function_localTable_ValuePtrs) + { + char * const * const namelist = ctx->function_localTable_Names[0]; + const int namelist_sz = ctx->function_localTable_Size[0]; + for (i=0; i < namelist_sz; i++) + { + const char *p = namelist[i]; + if (p) + { + if (!isFunctionMode && !is_string_prefix && !strnicmp(p,sname,NSEEL_MAX_VARIABLE_NAMELEN)) + { + rec->opcodeType = OPCODETYPE_VARPTRPTR; + rec->parms.dv.valuePtr=(EEL_F *)(ctx->function_localTable_ValuePtrs+i); + rec->parms.dv.directValue=0.0; + return rec; + } + else + { + const size_t plen = strlen(p); + if (plen > 1 && p[plen-1] == '*' && !strnicmp(p,sname,plen-1) && ((sname[plen-1] == '.'&&sname[plen]) || !sname[plen-1])) + { + rel_prefix_len=(int) (sname[plen-1] ? plen : plen-1); + rel_prefix_idx=i; + break; + } + } + } + } + } + // if instance name set, translate sname or sname.* into "this.sname.*" + if (rel_prefix_idx < -1 && + ctx->function_localTable_Size[1] > 0 && + ctx->function_localTable_Names[1]) + { + char * const * const namelist = ctx->function_localTable_Names[1]; + const int namelist_sz = ctx->function_localTable_Size[1]; + const char *full_sname = rec->relname; // include # in checks + for (i=0; i < namelist_sz; i++) + { + const char *p = namelist[i]; + if (p && *p) + { + const size_t tl = strlen(p); + if (!strnicmp(p,full_sname,tl) && (full_sname[tl] == 0 || full_sname[tl] == '.')) + { + rel_prefix_len=0; // treat as though this. prefixes is present + rel_prefix_idx=-1; + break; + } + } + } + } + if (rel_prefix_idx >= -1) + { + ctx->function_usesNamespaces=1; + } + } // ctx->function_curName + + if (!isFunctionMode) + { + // instance variables + if (rel_prefix_idx >= -1) + { + rec->opcodeType = OPCODETYPE_VALUE_FROM_NAMESPACENAME; + rec->namespaceidx = rel_prefix_idx; + if (rel_prefix_len > 0) + { + if (is_string_prefix) sname[-1] = '#'; + memmove(sname, sname+rel_prefix_len, strlen(sname + rel_prefix_len) + 1); + } + } + else + { + // no namespace index, so it must be a global + if (!validate_varname_for_function(ctx,rec->relname)) + { + if (errOut) *errOut = 1; + if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string)); + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"global '%s' inaccessible",rec->relname); + return NULL; + } + } + + return rec; + } + + if (ctx->func_check) + prevent_function_calls = ctx->func_check(sname,ctx->func_check_user); + + ////////// function mode + // first off, while() and loop() are special and can't be overridden + // + if (parmcnt == 1 && !stricmp("while",sname) && !prevent_function_calls) + { + rec->opcodeType = OPCODETYPE_FUNC1; + rec->fntype = FN_WHILE; + return rec; + } + if (parmcnt == 2 && !stricmp("loop",sname) && !prevent_function_calls) + { + rec->opcodeType = OPCODETYPE_FUNC2; + rec->fntype = FN_LOOP; + return rec; + } + + // + // resolve user function names before builtin functions -- this allows the user to override default functions + if (!(ctx->current_compile_flags & NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS)) + { + _codeHandleFunctionRec *best=NULL; + size_t bestlen=0; + const char * const ourcall = sname+rel_prefix_len; + const size_t ourcall_len = strlen(ourcall); + int pass; + for (pass=0;pass<2;pass++) + { + _codeHandleFunctionRec *fr = pass ? ctx->functions_common : ctx->functions_local; + // sname is [namespace.[ns.]]function, find best match of function that matches the right end + while (fr) + { + int this_np = fr->num_params; + const char *thisfunc = fr->fname; + const size_t thisfunc_len = strlen(thisfunc); + if (this_np < 1) this_np=1; + if (thisfunc_len == ourcall_len && !stricmp(thisfunc,ourcall)) + { + if (this_np == parmcnt) + { + bestlen = thisfunc_len; + best = fr; + break; // found exact match, finished + } + else + { + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = fr->num_params; + } + } + + if (thisfunc_len > bestlen && thisfunc_len < ourcall_len && ourcall[ourcall_len - thisfunc_len - 1] == '.' && !stricmp(thisfunc,ourcall + ourcall_len - thisfunc_len)) + { + if (this_np == parmcnt) + { + bestlen = thisfunc_len; + best = fr; + } + else + if (match_parmcnt[3]<0) match_parmcnt[3]=fr->num_params; + } + fr=fr->next; + } + if (fr) break; // found exact match, finished + } + + if (best) + { + switch (parmcnt) + { + case 0: + case 1: rec->opcodeType = OPCODETYPE_FUNC1; break; + case 2: rec->opcodeType = OPCODETYPE_FUNC2; break; + case 3: rec->opcodeType = OPCODETYPE_FUNC3; break; + default: rec->opcodeType = OPCODETYPE_FUNCX; break; + } + if (ourcall != rec->relname) memmove((char *)rec->relname, ourcall, strlen(ourcall)+1); + + if (ctx->function_curName && rel_prefix_idx<0) + { + // if no namespace specified, and this.commonprefix.func() called, remove common prefixes and set prefixidx to be this + const char *p=ctx->function_curName; + if (*p) p++; + while (*p && *p != '.') p++; + if (*p && p[1]) // we have a dot! + { + while (p[1]) p++; // go to last char of string, which doesn't allow possible trailing dot to be checked + + while (--p > ctx->function_curName) // do not check possible leading dot + { + if (*p == '.') + { + const size_t cmplen = p+1-ctx->function_curName; + if (!strnicmp(rec->relname,ctx->function_curName,cmplen) && rec->relname[cmplen]) + { + const char *src=rec->relname + cmplen; + memmove((char *)rec->relname, src, strlen(src)+1); + rel_prefix_idx=-1; + ctx->function_usesNamespaces=1; + break; + } + } + } + } + } + + if (ctx->function_curName && rel_prefix_idx < -1 && + strchr(rec->relname,'.') && !validate_varname_for_function(ctx,rec->relname)) + { + if (errOut) *errOut = 1; + if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string)); + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"namespaced function '%s' inaccessible",rec->relname); + return NULL; + } + + rec->namespaceidx = rel_prefix_idx; + rec->fntype = FUNCTYPE_EELFUNC; + rec->fn = best; + return rec; + } + } + + if (prevent_function_calls) + { + if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string)); + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"'%.30s': %s",sname, prevent_function_calls); + if (errOut) *errOut = 0; + return NULL; + } + +#ifdef NSEEL_EEL1_COMPAT_MODE + if (!stricmp(sname,"assign")) + { + if (parmcnt == 2) + { + rec->opcodeType = OPCODETYPE_FUNC2; + rec->fntype = FN_ASSIGN; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2; + } + else if (!stricmp(sname,"if")) + { + if (parmcnt == 3) + { + rec->opcodeType = OPCODETYPE_FUNC3; + rec->fntype = FN_IF_ELSE; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 3; + } + else if (!stricmp(sname,"equal")) + { + if (parmcnt == 2) + { + rec->opcodeType = OPCODETYPE_FUNC2; + rec->fntype = FN_EQ; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2; + } + else if (!stricmp(sname,"below")) + { + if (parmcnt == 2) + { + rec->opcodeType = OPCODETYPE_FUNC2; + rec->fntype = FN_LT; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2; + } + else if (!stricmp(sname,"above")) + { + if (parmcnt == 2) + { + rec->opcodeType = OPCODETYPE_FUNC2; + rec->fntype = FN_GT; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2; + } + else if (!stricmp(sname,"bnot")) + { + if (parmcnt == 1) + { + rec->opcodeType = OPCODETYPE_FUNC1; + rec->fntype = FN_NOT; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 1; + } + else if (!stricmp(sname,"megabuf")) + { + if (parmcnt == 1) + { + rec->opcodeType = OPCODETYPE_FUNC1; + rec->fntype = FN_MEMORY; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 1; + } + else if (!stricmp(sname,"gmegabuf")) + { + if (parmcnt == 1) + { + rec->opcodeType = OPCODETYPE_FUNC1; + rec->fntype = FN_GMEMORY; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 1; + } + else +#endif + // convert legacy pow() to FN_POW + if (!stricmp("pow",sname)) + { + if (parmcnt == 2) + { + rec->opcodeType = OPCODETYPE_FUNC2; + rec->fntype = FN_POW; + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2; + } + else if (!stricmp("__denormal_likely",sname) || !stricmp("__denormal_unlikely",sname)) + { + if (parmcnt == 1) + { + rec->opcodeType = OPCODETYPE_FUNC1; + rec->fntype = !stricmp("__denormal_likely",sname) ? FN_DENORMAL_LIKELY : FN_DENORMAL_UNLIKELY; + return rec; + } + } + + { + int chkamt=0; + functionType *f=nseel_getFunctionByName(ctx,sname,&chkamt); + if (f) while (chkamt-->=0) + { + const int pc_needed=(f->nParams&FUNCTIONTYPE_PARAMETERCOUNTMASK); + if ((f->nParams&BIF_TAKES_VARPARM_EX)==BIF_TAKES_VARPARM ? (parmcnt >= pc_needed) : (parmcnt == pc_needed)) + { + rec->fntype = FUNCTYPE_FUNCTIONTYPEREC; + rec->fn = (void *)f; + switch (parmcnt) + { + case 0: + case 1: rec->opcodeType = OPCODETYPE_FUNC1; break; + case 2: rec->opcodeType = OPCODETYPE_FUNC2; break; + case 3: rec->opcodeType = OPCODETYPE_FUNC3; break; + default: rec->opcodeType = OPCODETYPE_FUNCX; break; + } + return rec; + } + if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = (f->nParams&FUNCTIONTYPE_PARAMETERCOUNTMASK); + f++; + if (stricmp(f->name,sname)) break; + } + } + if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string)); + if (match_parmcnt[3] >= 0) + { + if (match_parmcnt_pos<3) match_parmcnt[match_parmcnt_pos] = match_parmcnt[3]; + match_parmcnt_pos++; + } + + if (!match_parmcnt_pos) + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"'%.30s' undefined",sname); + else + { + int x; + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"'%.30s' needs ",sname); + for (x = 0; x < match_parmcnt_pos; x++) + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"%s%d",x==0?"" : x == match_parmcnt_pos-1?" or ":",",match_parmcnt[x]); + lstrcatn(ctx->last_error_string," parms",sizeof(ctx->last_error_string)); + } + if (errOut) *errOut = match_parmcnt_pos > 0 ? parmcntopcodeType != OPCODETYPE_VARPTR || !fn->relname || !fn->relname[0]) + { + return NULL; + } + fn->parms.parms[0] = code1; + fn->parms.parms[1] = code2; + fn->parms.parms[2] = code3; + + for (x=0;x<3;x++) + { + opcodeRec *prni=fn->parms.parms[x]; + while (prni && np < NSEEL_MAX_EELFUNC_PARAMETERS) + { + const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS; + np++; + if (!isMP) break; + prni = prni->parms.parms[1]; + } + } + r = nseel_resolve_named_symbol(ctx, fn, np<1 ? 1 : np ,errOut); + if (postCode && r) + { + if (code1 && r->opcodeType == OPCODETYPE_FUNC1 && r->fntype == FN_WHILE) + { + // change while(x) (postcode) to be + // while ((x) ? (postcode;1) : 0); + + r->parms.parms[0] = + nseel_createIfElse(ctx,r->parms.parms[0], + nseel_createSimpleCompiledFunction(ctx,FN_JOIN_STATEMENTS,2,postCode,nseel_createCompiledValue(ctx,1.0f)), + NULL); // NULL defaults to 0.0 + + } + else + { + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"syntax error following function"); + *errOut = -1; + return NULL; + } + } + return r; +} + + +struct eelStringSegmentRec *nseel_createStringSegmentRec(compileContext *ctx, const char *str, int len) +{ + struct eelStringSegmentRec *r = newTmpBlock(ctx,sizeof(struct eelStringSegmentRec)); + if (r) + { + r->_next=0; + r->str_start=str; + r->str_len = len; + } + return r; +} + +opcodeRec *nseel_eelMakeOpcodeFromStringSegments(compileContext *ctx, struct eelStringSegmentRec *rec) +{ + if (ctx && ctx->onString) + { + return nseel_createCompiledValue(ctx, ctx->onString(ctx->caller_this,rec)); + } + + return NULL; +} + +opcodeRec *nseel_createMoreParametersOpcode(compileContext *ctx, opcodeRec *code1, opcodeRec *code2) +{ + opcodeRec *r=code1 && code2 ? newOpCode(ctx,NULL,OPCODETYPE_MOREPARAMS) : NULL; + if (r) + { + r->parms.parms[0] = code1; + r->parms.parms[1] = code2; + } + return r; +} + + +opcodeRec *nseel_createIfElse(compileContext *ctx, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3) +{ + opcodeRec *r=code1 ? newOpCode(ctx,NULL,OPCODETYPE_FUNC3) : NULL; + if (r) + { + if (!code2) code2 = nseel_createCompiledValue(ctx,0.0); + if (!code3) code3 = nseel_createCompiledValue(ctx,0.0); + if (!code2||!code3) return NULL; + + r->fntype = FN_IF_ELSE; + r->parms.parms[0] = code1; + r->parms.parms[1] = code2; + r->parms.parms[2] = code3; + } + return r; +} + + +opcodeRec *nseel_createMemoryAccess(compileContext *ctx, opcodeRec *code1, opcodeRec *code2) +{ + if (code1 && code1->opcodeType == OPCODETYPE_VARPTR && !stricmp(code1->relname,"gmem")) + { + return nseel_createSimpleCompiledFunction(ctx, FN_GMEMORY,1,code2?code2:nseel_createCompiledValue(ctx,0.0),0); + } + if (code2 && (code2->opcodeType != OPCODETYPE_DIRECTVALUE || code2->parms.dv.directValue != 0.0)) + { + code1 = nseel_createSimpleCompiledFunction(ctx,FN_ADD,2,code1,code2); + } + return nseel_createSimpleCompiledFunction(ctx, FN_MEMORY,1,code1,0); +} + +opcodeRec *nseel_createSimpleCompiledFunction(compileContext *ctx, int fn, int np, opcodeRec *code1, opcodeRec *code2) +{ + opcodeRec *r=code1 && (np<2 || code2) ? newOpCode(ctx,NULL,np>=2 ? OPCODETYPE_FUNC2:OPCODETYPE_FUNC1) : NULL; + if (r) + { + r->fntype = fn; + r->parms.parms[0] = code1; + r->parms.parms[1] = code2; + if (fn == FN_JOIN_STATEMENTS) + { + r->fn = r; // for joins, fn is temporarily used for tail pointers + if (code1 && code1->opcodeType == OPCODETYPE_FUNC2 && code1->fntype == fn) + { + opcodeRec *t = (opcodeRec *)code1->fn; + // keep joins in the form of dosomething->morestuff. + // in this instance, code1 is previous stuff to do, code2 is new stuff to do + r->parms.parms[0] = t->parms.parms[1]; + + code1->fn = (t->parms.parms[1] = r); + return code1; + } + } + } + return r; +} + + +// these are bitmasks; on request you can tell what is supported, and compileOpcodes will return one of them +#define RETURNVALUE_IGNORE 0 // ignore return value +#define RETURNVALUE_NORMAL 1 // pointer +#define RETURNVALUE_FPSTACK 2 +#define RETURNVALUE_BOOL 4 // P1 is nonzero if true +#define RETURNVALUE_BOOL_REVERSED 8 // P1 is zero if true +#define RETURNVALUE_CACHEABLE 16 // only to be used when (at least) RETURNVALUE_NORMAL is set +#if GLUE_HAS_FPREG2 > 0 +#define RETURNVALUE_FPREG2 32 // only usable for compileOpcodes() on a trivial opcode +#endif + + + +static int compileOpcodes(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTable, const namespaceInformation *namespacePathToThis, + int supportedReturnValues, int *rvType, int *fpStackUsage, int *canHaveDenormalOutput); + + +static unsigned char *compileCodeBlockWithRet(compileContext *ctx, opcodeRec *rec, int *computTableSize, const namespaceInformation *namespacePathToThis, + int supportedReturnValues, int *rvType, int *fpStackUse, int *canHaveDenormalOutput); + +_codeHandleFunctionRec *eel_createFunctionNamespacedInstance(compileContext *ctx, _codeHandleFunctionRec *fr, const char *nameptr) +{ + size_t n; + _codeHandleFunctionRec *subfr = + fr->isCommonFunction ? + ctx->isSharedFunctions ? newDataBlock(sizeof(_codeHandleFunctionRec),8) : + newCtxDataBlock(sizeof(_codeHandleFunctionRec),8) : // if common function, but derived version is in non-common context, set ownership to VM rather than us + newTmpBlock(ctx,sizeof(_codeHandleFunctionRec)); + + if (!subfr) return 0; + // fr points to functionname()'s rec, nameptr to blah.functionname() + + *subfr = *fr; + n = strlen(nameptr); + if (n > sizeof(subfr->fname)-1) n=sizeof(subfr->fname)-1; + memcpy(subfr->fname,nameptr,n); + subfr->fname[n]=0; + + subfr->next = NULL; + subfr->startptr=0; // make sure this code gets recompiled (with correct member ptrs) for this instance! + subfr->startptr_size=-1; + + // subfr->derivedCopies already points to the right place + fr->derivedCopies = subfr; + + return subfr; + +} +static void combineNamespaceFields(char *nm, const namespaceInformation *namespaceInfo, const char *relname, int thisctx) // nm must be NSEEL_MAX_VARIABLE_NAMELEN+1 bytes +{ + const char *prefix = namespaceInfo ? + thisctx<0 ? (thisctx == -1 ? namespaceInfo->namespacePathToThis : NULL) : (thisctx < MAX_SUB_NAMESPACES ? namespaceInfo->subParmInfo[thisctx] : NULL) + : NULL; + int lfp = 0, lrn=relname ? (int)strlen(relname) : 0; + if (prefix) while (prefix[lfp] && prefix[lfp] != ':' && lfp < NSEEL_MAX_VARIABLE_NAMELEN) lfp++; + if (!relname) relname = ""; + + while (*relname == '.') // if relname begins with ., then remove a chunk of context from prefix + { + relname++; + while (lfp>0 && prefix[lfp-1] != '.') lfp--; + if (lfp>0) lfp--; + } + + if (lfp > NSEEL_MAX_VARIABLE_NAMELEN-3) lfp=NSEEL_MAX_VARIABLE_NAMELEN-3; + if (lfp>0) memcpy(nm,prefix,lfp); + + if (lrn > NSEEL_MAX_VARIABLE_NAMELEN - lfp - (lfp>0)) lrn=NSEEL_MAX_VARIABLE_NAMELEN - lfp - (lfp>0); + if (lrn > 0) + { + if (lfp>0) nm[lfp++] = '.'; + memcpy(nm+lfp,relname,lrn); + lfp+=lrn; + } + nm[lfp++]=0; +} + + +//--------------------------------------------------------------------------------------------------------------- +static void *nseel_getBuiltinFunctionAddress(compileContext *ctx, + int fntype, void *fn, + NSEEL_PPPROC *pProc, void ***replList, + int *abiInfo, int preferredReturnValues, const EEL_F *hasConstParm1, const EEL_F *hasConstParm2) +{ + const EEL_F *firstConstParm = hasConstParm1 ? hasConstParm1 : hasConstParm2; + static void *pow_replptrs[4]={&pow,}; + + switch (fntype) + { +#define RF(x) return (void*)nseel_asm_##x + + case FN_MUL_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(mul_op); + case FN_DIV_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(div_op); + case FN_OR_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(or_op); + case FN_XOR_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(xor_op); + case FN_AND_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(and_op); + case FN_MOD_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(mod_op); + case FN_ADD_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(add_op); + case FN_SUB_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(sub_op); + case FN_POW_OP: + *abiInfo=BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL; + *replList = pow_replptrs; + RF(2pdds); + case FN_POW: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK;//BIF_FPSTACKUSE(2) might be safe, need to look at pow()'s implementation, but safer bet is to disallow fp stack caching for this expression + *replList = pow_replptrs; + RF(2pdd); + case FN_ADD: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2); + // for x +- non-denormal-constant, we can set BIF_CLEARDENORMAL + if (firstConstParm && fabs(*firstConstParm) > DENORMAL_CLEARING_THRESHOLD) *abiInfo |= BIF_CLEARDENORMAL; + RF(add); + case FN_SUB: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2); + // for x +- non-denormal-constant, we can set BIF_CLEARDENORMAL + if (firstConstParm && fabs(*firstConstParm) > DENORMAL_CLEARING_THRESHOLD) *abiInfo |= BIF_CLEARDENORMAL; + RF(sub); + case FN_MULTIPLY: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2); + // for x*constant-greater-than-eq-1, we can set BIF_WONTMAKEDENORMAL + if (firstConstParm && fabs(*firstConstParm) >= 1.0) *abiInfo |= BIF_WONTMAKEDENORMAL; + RF(mul); + case FN_DIVIDE: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2); + // for x/constant-less-than-eq-1, we can set BIF_WONTMAKEDENORMAL + if (firstConstParm && fabs(*firstConstParm) <= 1.0) *abiInfo |= BIF_WONTMAKEDENORMAL; + RF(div); + case FN_MOD: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL; + RF(mod); + case FN_ASSIGN: + *abiInfo = BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL; + RF(assign); + case FN_AND: *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; RF(and); + case FN_OR: *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; RF(or); + case FN_XOR: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(xor); + case FN_SHR: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(shr); + case FN_SHL: + *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; + RF(shl); + case FN_NOTNOT: *abiInfo = BIF_LASTPARM_ASBOOL|BIF_RETURNSBOOL|BIF_FPSTACKUSE(1); break; + case FN_UMINUS: *abiInfo = BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL; RF(uminus); + case FN_NOT: *abiInfo = BIF_LASTPARM_ASBOOL|BIF_RETURNSBOOL|BIF_FPSTACKUSE(1); RF(bnot); + + case FN_EQ: + *abiInfo = BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2); + RF(equal); + case FN_EQ_EXACT: + *abiInfo=BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2); + RF(equal_exact); + case FN_NE: + *abiInfo=BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2); + RF(notequal); + case FN_NE_EXACT: + *abiInfo=BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2); + RF(notequal_exact); + case FN_LOGICAL_AND: + *abiInfo = BIF_RETURNSBOOL; + RF(band); + case FN_LOGICAL_OR: + *abiInfo = BIF_RETURNSBOOL; + RF(bor); + + case FN_GT: + *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2); + RF(above); + case FN_GTE: + *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_REVERSEFPORDER|BIF_FPSTACKUSE(2); +#if BIF_REVERSEFPORDER > 0 + RF(beloweq); +#else + RF(aboveeq); +#endif + case FN_LT: + *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_REVERSEFPORDER|BIF_FPSTACKUSE(2); +#if BIF_REVERSEFPORDER > 0 + RF(above); +#else + RF(below); +#endif + case FN_LTE: + *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2); + RF(beloweq); + +#undef RF +#define RF(x) return (void*)_asm_##x + + case FN_MEMORY: + { + static void *replptrs[4]={&__NSEEL_RAMAlloc,}; + *replList = replptrs; + *abiInfo = BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL; + #ifdef GLUE_MEM_NEEDS_PPROC + *pProc = NSEEL_PProc_RAM; + #endif + RF(megabuf); + } + break; + case FN_GMEMORY: + { + static void *replptrs[4]={&__NSEEL_RAMAllocGMEM,}; + *replList = replptrs; + *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL; + *pProc=NSEEL_PProc_GRAM; + RF(gmegabuf); + } + break; +#undef RF + + case FUNCTYPE_FUNCTIONTYPEREC: + if (fn) + { + functionType *p=(functionType *)fn; + + // if prefers fpstack or bool, or ignoring value, then use fp-stack versions + if ((preferredReturnValues&(RETURNVALUE_BOOL|RETURNVALUE_FPSTACK)) || !preferredReturnValues) + { + if (p->afunc == (void*)nseel_asm_min) p = &fn_min2; + else if (p->afunc == (void*)nseel_asm_max) p = &fn_max2; + } + + *replList=p->replptrs; + *pProc=p->pProc; + *abiInfo = p->nParams & BIF_NPARAMS_MASK; + if (firstConstParm) + { + const char *name=p->name; + if (!strcmp(name,"min") && *firstConstParm < -1.0e-10) *abiInfo |= BIF_CLEARDENORMAL; + else if (!strcmp(name,"max") && *firstConstParm > 1.0e-10) *abiInfo |= BIF_CLEARDENORMAL; + } + return p->afunc; + } + break; + } + + return NULL; +} + + + +static void *nseel_getEELFunctionAddress(compileContext *ctx, + opcodeRec *op, + int *customFuncParmSize, int *customFuncLocalStorageSize, + EEL_F ***customFuncLocalStorage, int *computTableTop, + void **endP, int *isRaw, int wantCodeGenerated, + const namespaceInformation *namespacePathToThis, int *rvMode, int *fpStackUse, int *canHaveDenormalOutput, + opcodeRec **ordered_parmptrs, int num_ordered_parmptrs + ) // if wantCodeGenerated is false, can return bogus pointers in raw mode +{ + _codeHandleFunctionRec *fn = (_codeHandleFunctionRec*)op->fn; + + namespaceInformation local_namespace={NULL}; + char prefix_buf[NSEEL_MAX_VARIABLE_NAMELEN+1], nm[NSEEL_MAX_FUNCSIG_NAME+1]; + if (!fn) return NULL; + + // op->relname ptr is [whatever.]funcname + if (fn->parameterAsNamespaceMask || fn->usesNamespaces) + { + if (wantCodeGenerated) + { + char *p = prefix_buf; + combineNamespaceFields(nm,namespacePathToThis,op->relname,op->namespaceidx); + lstrcpyn_safe(prefix_buf,nm,sizeof(prefix_buf)); + local_namespace.namespacePathToThis = prefix_buf; + // nm is full path of function, prefix_buf will be the path not including function name (unless function name only) + while (*p) p++; + while (p >= prefix_buf && *p != '.') p--; + if (p > prefix_buf) *p=0; + } + if (fn->parameterAsNamespaceMask) + { + int x; + for(x=0;xnum_params;x++) + { + if (fn->parameterAsNamespaceMask & (((unsigned int)1)<opcodeType == OPCODETYPE_VARPTR) + { + rn=ordered_parmptrs[x]->relname; + } + else if (ordered_parmptrs[x]->opcodeType == OPCODETYPE_VALUE_FROM_NAMESPACENAME) + { + const char *p=ordered_parmptrs[x]->relname; + if (*p == '#') p++; + combineNamespaceFields(tmp,namespacePathToThis,p,ordered_parmptrs[x]->namespaceidx); + rn = tmp; + } + } + + if (!rn) + { + // todo: figure out how to give correct line number/offset (ugh) + snprintf(ctx->last_error_string,sizeof(ctx->last_error_string),"parameter %d to %.120s() must be namespace",x+1,fn->fname); + return NULL; + } + + lstrcatn(nm,":",sizeof(nm)); + + local_namespace.subParmInfo[x] = nm+strlen(nm); + lstrcatn(nm,rn,sizeof(nm)); + } + ordered_parmptrs[x] = NULL; // prevent caller from bothering generating parameters + } + } + } + if (wantCodeGenerated) + { + _codeHandleFunctionRec *fr = fn; + // find namespace-adjusted function (if generating code, otherwise assume size is the same) + fn = 0; // if this gets re-set, it will be the new function + while (fr && !fn) + { + if (!stricmp(fr->fname,nm)) fn = fr; + fr=fr->derivedCopies; + } + if (!fn) // generate copy of function + { + fn = eel_createFunctionNamespacedInstance(ctx,(_codeHandleFunctionRec*)op->fn,nm); + } + } + } + if (!fn) return NULL; + + if (!fn->startptr && fn->opcodes && fn->startptr_size != 0) + { + int sz = fn->startptr_size; + + if (sz < 0) + { + fn->tmpspace_req=0; + fn->rvMode = RETURNVALUE_IGNORE; + fn->canHaveDenormalOutput=0; + + sz = compileOpcodes(ctx,fn->opcodes,NULL,128*1024*1024,&fn->tmpspace_req, + wantCodeGenerated ? &local_namespace : NULL,RETURNVALUE_FPSTACK, + &fn->rvMode,&fn->fpStackUsage,&fn->canHaveDenormalOutput); + if (sz<0) return NULL; + + fn->startptr_base_size = fn->startptr_size = sz; + } + + if (!wantCodeGenerated) + { + // don't compile anything for now, just give stats + if (computTableTop) *computTableTop += fn->tmpspace_req; + *customFuncParmSize = fn->num_params; + *customFuncLocalStorage = fn->localstorage; + *customFuncLocalStorageSize = fn->localstorage_size; + *rvMode = fn->rvMode; + *fpStackUse = fn->fpStackUsage; + if (canHaveDenormalOutput) *canHaveDenormalOutput=fn->canHaveDenormalOutput; + + if (fn->startptr_base_size <= NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE && + !(ctx->optimizeDisableFlags&OPTFLAG_NO_INLINEFUNC)) + { + *isRaw = 1; + *endP = ((char *)1) + fn->startptr_base_size; + return (char *)1; + } + return (void*)nseel_asm_fcall; + } + + if (fn->startptr_base_size <= NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE && + !(ctx->optimizeDisableFlags&OPTFLAG_NO_INLINEFUNC)) + { + void *p=newTmpBlock(ctx,sz); + fn->tmpspace_req=0; + if (p) + { + fn->canHaveDenormalOutput=0; + if (fn->isCommonFunction) ctx->isGeneratingCommonFunction++; + sz=compileOpcodes(ctx,fn->opcodes,(unsigned char*)p,sz,&fn->tmpspace_req,&local_namespace,RETURNVALUE_FPSTACK,&fn->rvMode,&fn->fpStackUsage,&fn->canHaveDenormalOutput); + if (fn->isCommonFunction) ctx->isGeneratingCommonFunction--; + // recompile function with native context pointers + if (sz>0) + { + fn->startptr_size=sz; + fn->startptr=p; + } + } + } + else + { + unsigned char *codeCall; + fn->tmpspace_req=0; + fn->fpStackUsage=0; + fn->canHaveDenormalOutput=0; + if (fn->isCommonFunction) ctx->isGeneratingCommonFunction++; + codeCall=compileCodeBlockWithRet(ctx,fn->opcodes,&fn->tmpspace_req,&local_namespace,RETURNVALUE_FPSTACK,&fn->rvMode,&fn->fpStackUsage,&fn->canHaveDenormalOutput); + if (fn->isCommonFunction) ctx->isGeneratingCommonFunction--; + if (codeCall) + { + void *f=GLUE_realAddress(nseel_asm_fcall,&sz); + fn->startptr = newTmpBlock(ctx,sz); + if (fn->startptr) + { + memcpy(fn->startptr,f,sz); + EEL_GLUE_set_immediate(fn->startptr,(INT_PTR)codeCall); + fn->startptr_size = sz; + } + } + } + } + + if (fn->startptr) + { + if (computTableTop) *computTableTop += fn->tmpspace_req; + *customFuncParmSize = fn->num_params; + *customFuncLocalStorage = fn->localstorage; + *customFuncLocalStorageSize = fn->localstorage_size; + *rvMode = fn->rvMode; + *fpStackUse = fn->fpStackUsage; + if (canHaveDenormalOutput) *canHaveDenormalOutput= fn->canHaveDenormalOutput; + *endP = (char*)fn->startptr + fn->startptr_size; + if (!wantCodeGenerated && + fn->startptr_base_size <= NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE && + !(ctx->optimizeDisableFlags&OPTFLAG_NO_INLINEFUNC)) + { + // report the correct maximum base length for the calculation pass + *endP = (char*)fn->startptr + fn->startptr_base_size; + } + *isRaw=1; + return fn->startptr; + } + + return 0; +} + + + +// returns true if does something (other than calculating and throwing away a value) +static char optimizeOpcodes(compileContext *ctx, opcodeRec *op, int needsResult) +{ + opcodeRec *lastJoinOp=NULL; + char retv, retv_parm[3], joined_retv=0; + while (op && op->opcodeType == OPCODETYPE_FUNC2 && op->fntype == FN_JOIN_STATEMENTS) + { + if (!optimizeOpcodes(ctx,op->parms.parms[0], 0) || OPCODE_IS_TRIVIAL(op->parms.parms[0])) + { + // direct value, can skip ourselves + memcpy(op,op->parms.parms[1],sizeof(*op)); + } + else + { + joined_retv |= 1; + lastJoinOp = op; + op = op->parms.parms[1]; + } + } +goto start_over; + +#define RESTART_DIRECTVALUE(X) { op->parms.dv.directValue = (X); goto start_over_directvalue; } +start_over_directvalue: + op->opcodeType = OPCODETYPE_DIRECTVALUE; + op->parms.dv.valuePtr=NULL; + +start_over: // when an opcode changed substantially in optimization, goto here to reprocess it + + retv = retv_parm[0]=retv_parm[1]=retv_parm[2]=0; + + if (!op || // should never really happen + OPCODE_IS_TRIVIAL(op) || // should happen often (vars) + op->opcodeType < 0 || op->opcodeType >= OPCODETYPE_INVALID // should never happen (assert would be appropriate heh) + ) return joined_retv; + + if (!needsResult) + { + if (op->fntype == FUNCTYPE_EELFUNC) + { + needsResult=1; // assume eel functions are non-const for now + } + else if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC) + { + functionType *pfn = (functionType *)op->fn; + if (!pfn || !(pfn->nParams&NSEEL_NPARAMS_FLAG_CONST)) needsResult=1; + } + else if (op->fntype >= FN_NONCONST_BEGIN && op->fntype < FUNCTYPE_SIMPLEMAX) + { + needsResult=1; + } + } + + if (op->opcodeType>=OPCODETYPE_FUNC2) retv_parm[1] = optimizeOpcodes(ctx,op->parms.parms[1], needsResult); + if (op->opcodeType>=OPCODETYPE_FUNC3) retv_parm[2] = optimizeOpcodes(ctx,op->parms.parms[2], needsResult); + + retv_parm[0] = optimizeOpcodes(ctx,op->parms.parms[0], needsResult || + (FNPTR_HAS_CONDITIONAL_EXEC(op) && (retv_parm[1] || retv_parm[2] || op->opcodeType <= OPCODETYPE_FUNC1)) ); + + if (op->opcodeType != OPCODETYPE_MOREPARAMS) + { + if (op->fntype >= 0 && op->fntype < FUNCTYPE_SIMPLEMAX) + { + if (op->opcodeType == OPCODETYPE_FUNC1) // within FUNCTYPE_SIMPLE + { + if (op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE) + { + switch (op->fntype) + { + case FN_NOTNOT: RESTART_DIRECTVALUE(fabs(op->parms.parms[0]->parms.dv.directValue)>=NSEEL_CLOSEFACTOR ? 1.0 : 0.0); + case FN_NOT: RESTART_DIRECTVALUE(fabs(op->parms.parms[0]->parms.dv.directValue)>=NSEEL_CLOSEFACTOR ? 0.0 : 1.0); + case FN_UMINUS: RESTART_DIRECTVALUE(- op->parms.parms[0]->parms.dv.directValue); + } + } + else if (op->fntype == FN_NOT || op->fntype == FN_NOTNOT) + { + if (op->parms.parms[0]->opcodeType == OPCODETYPE_FUNC1) + { + switch (op->parms.parms[0]->fntype) + { + case FN_UMINUS: + case FN_NOTNOT: // ignore any NOTNOTs UMINUS or UPLUS, they would have no effect anyway + op->parms.parms[0] = op->parms.parms[0]->parms.parms[0]; + goto start_over; + + case FN_NOT: + op->fntype = op->fntype==FN_NOT ? FN_NOTNOT : FN_NOT; // switch between FN_NOT and FN_NOTNOT + op->parms.parms[0] = op->parms.parms[0]->parms.parms[0]; + goto start_over; + } + } + else if (op->parms.parms[0]->opcodeType == OPCODETYPE_FUNC2) + { + int repl_type = -1; + switch (op->parms.parms[0]->fntype) + { + case FN_EQ: repl_type = FN_NE; break; + case FN_NE: repl_type = FN_EQ; break; + case FN_EQ_EXACT: repl_type = FN_NE_EXACT; break; + case FN_NE_EXACT: repl_type = FN_EQ_EXACT; break; + case FN_LT: repl_type = FN_GTE; break; + case FN_LTE: repl_type = FN_GT; break; + case FN_GT: repl_type = FN_LTE; break; + case FN_GTE: repl_type = FN_LT; break; + } + if (repl_type != -1) + { + const int oldtype = op->fntype; + memcpy(op,op->parms.parms[0],sizeof(*op)); + if (oldtype == FN_NOT) op->fntype = repl_type; + goto start_over; + } + } + } + } + else if (op->opcodeType == OPCODETYPE_FUNC2) // within FUNCTYPE_SIMPLE + { + const int dv0 = op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE; + const int dv1 = op->parms.parms[1]->opcodeType == OPCODETYPE_DIRECTVALUE; + if (dv0 && dv1) + { + int reval = -1; + switch (op->fntype) + { + case FN_MOD: + { + EEL_F ret = 0.0; + int a = (int) fabs(op->parms.parms[1]->parms.dv.directValue); + if (a) + { +#ifdef GLUE_MOD_IS_64 + ret = ((WDL_INT64) fabs(op->parms.parms[0]->parms.dv.directValue)) % a; +#else + ret = ((int) fabs(op->parms.parms[0]->parms.dv.directValue)) % a; +#endif + if (WDL_NOT_NORMALLY(ret<0)) ret = -ret; + } + RESTART_DIRECTVALUE(ret); + } + break; + case FN_SHL: RESTART_DIRECTVALUE(((int)op->parms.parms[0]->parms.dv.directValue) << ((int)op->parms.parms[1]->parms.dv.directValue)); + case FN_SHR: RESTART_DIRECTVALUE(((int)op->parms.parms[0]->parms.dv.directValue) >> ((int)op->parms.parms[1]->parms.dv.directValue)); + case FN_POW: RESTART_DIRECTVALUE(pow(op->parms.parms[0]->parms.dv.directValue, op->parms.parms[1]->parms.dv.directValue)); + case FN_DIVIDE: RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue / op->parms.parms[1]->parms.dv.directValue); + case FN_MULTIPLY: RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue * op->parms.parms[1]->parms.dv.directValue); + + case FN_ADD: RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue + op->parms.parms[1]->parms.dv.directValue); + case FN_SUB: RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue - op->parms.parms[1]->parms.dv.directValue); + case FN_AND: RESTART_DIRECTVALUE((double) (((WDL_INT64)op->parms.parms[0]->parms.dv.directValue) & ((WDL_INT64)op->parms.parms[1]->parms.dv.directValue))); + case FN_OR: RESTART_DIRECTVALUE((double) (((WDL_INT64)op->parms.parms[0]->parms.dv.directValue) | ((WDL_INT64)op->parms.parms[1]->parms.dv.directValue))); + case FN_XOR: RESTART_DIRECTVALUE((double) (((WDL_INT64)op->parms.parms[0]->parms.dv.directValue) ^ ((WDL_INT64)op->parms.parms[1]->parms.dv.directValue))); + + case FN_EQ: reval = fabs(op->parms.parms[0]->parms.dv.directValue - op->parms.parms[1]->parms.dv.directValue) < NSEEL_CLOSEFACTOR; break; + case FN_NE: reval = fabs(op->parms.parms[0]->parms.dv.directValue - op->parms.parms[1]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; break; + case FN_EQ_EXACT: reval = op->parms.parms[0]->parms.dv.directValue == op->parms.parms[1]->parms.dv.directValue; break; + case FN_NE_EXACT: reval = op->parms.parms[0]->parms.dv.directValue != op->parms.parms[1]->parms.dv.directValue; break; + case FN_LT: reval = op->parms.parms[0]->parms.dv.directValue < op->parms.parms[1]->parms.dv.directValue; break; + case FN_LTE: reval = op->parms.parms[0]->parms.dv.directValue <= op->parms.parms[1]->parms.dv.directValue; break; + case FN_GT: reval = op->parms.parms[0]->parms.dv.directValue > op->parms.parms[1]->parms.dv.directValue; break; + case FN_GTE: reval = op->parms.parms[0]->parms.dv.directValue >= op->parms.parms[1]->parms.dv.directValue; break; + case FN_LOGICAL_AND: reval = fabs(op->parms.parms[0]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR && fabs(op->parms.parms[1]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; break; + case FN_LOGICAL_OR: reval = fabs(op->parms.parms[0]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR || fabs(op->parms.parms[1]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; break; + } + + if (reval >= 0) RESTART_DIRECTVALUE((EEL_F) reval); + } + else if (dv0 || dv1) + { + double dvalue = op->parms.parms[!dv0]->parms.dv.directValue; + switch (op->fntype) + { + case FN_OR: + case FN_XOR: + if (!(WDL_INT64)dvalue) + { + // replace with or0 + op->opcodeType = OPCODETYPE_FUNC1; + op->fntype = FUNCTYPE_FUNCTIONTYPEREC; + op->fn = &fn_or0; + if (dv0) op->parms.parms[0] = op->parms.parms[1]; + goto start_over; + } + break; + case FN_SUB: + if (dv0) + { + if (dvalue == 0.0) + { + op->opcodeType = OPCODETYPE_FUNC1; + op->fntype = FN_UMINUS; + op->parms.parms[0] = op->parms.parms[1]; + goto start_over; + } + break; + } + WDL_FALLTHROUGH; // fall through, if dv1 we can remove +0.0 + + case FN_ADD: + if (dvalue == 0.0) + { + memcpy(op,op->parms.parms[!!dv0],sizeof(*op)); + goto start_over; + } + break; + case FN_AND: + if ((WDL_INT64)dvalue) break; + dvalue = 0.0; // treat x&0 as x*0, which optimizes to 0 + + WDL_FALLTHROUGH; // fall through + case FN_MULTIPLY: + if (dvalue == 0.0) // remove multiply by 0.0 (using 0.0 direct value as replacement), unless the nonzero side did something + { + if (!retv_parm[!!dv0]) + { + memcpy(op,op->parms.parms[!dv0],sizeof(*op)); // set to 0 if other action wouldn't do anything + goto start_over; + } + else + { + // this is 0.0 * oldexpressionthatmustbeprocessed or oldexpressionthatmustbeprocessed*0.0 + op->fntype = FN_JOIN_STATEMENTS; + + if (dv0) // 0.0*oldexpression, reverse the order so that 0 is returned + { + // set to (oldexpression;0) + opcodeRec *tmp = op->parms.parms[1]; + op->parms.parms[1] = op->parms.parms[0]; + op->parms.parms[0] = tmp; + } + goto start_over; + } + } + else if (dvalue == 1.0) // remove multiply by 1.0 (using non-1.0 value as replacement) + { + memcpy(op,op->parms.parms[!!dv0],sizeof(*op)); + goto start_over; + } + break; + case FN_POW: + if (dv1) + { + // x^0 = 1 + if (fabs(dvalue) < 1e-30) + { + RESTART_DIRECTVALUE(1.0); + } + // x^1 = x + if (fabs(dvalue-1.0) < 1e-30) + { + memcpy(op,op->parms.parms[0],sizeof(*op)); + goto start_over; + } + } + else if (dv0) + { + // pow(constant, x) = exp((x) * ln(constant)), if constant>0 + // opcodeRec *parm0 = op->parms.parms[0]; + if (dvalue > 0.0) + { + static functionType *expcpy; + if (!expcpy) + { + expcpy = nseel_getFunctionByName(NULL,"exp",NULL); + if (WDL_NOT_NORMALLY(!expcpy)) break; + } + + // 1^x = 1 + if (fabs(dvalue-1.0) < 1e-30) + { + RESTART_DIRECTVALUE(1.0); + } + + dvalue=log(dvalue); + + if (fabs(dvalue-1.0) < 1e-9) + { + // caller wanted e^x + op->parms.parms[0]=op->parms.parms[1]; + } + else + { + // it would be nice to replace 10^x with exp(log(10)*x) or 2^x with exp(log(2),x), but + // doing so breaks rounding. we could maybe only allow 10^x, which is used for dB conversion, + // but for now we should just force the programmer do it exp(log(10)*x) themselves. + break; + + /* + parm0->opcodeType = OPCODETYPE_FUNC2; + parm0->fntype = FN_MULTIPLY; + parm0->parms.parms[0] = nseel_createCompiledValue(ctx,dvalue); + parm0->parms.parms[1] = op->parms.parms[1]; + */ + } + + op->opcodeType = OPCODETYPE_FUNC1; + op->fntype = FUNCTYPE_FUNCTIONTYPEREC; + op->fn = expcpy; + goto start_over; + } + } + break; + case FN_MOD: + if (dv1) + { + const int a = (int) dvalue; + if (!a) + { + RESTART_DIRECTVALUE(0.0); + } + } + break; + case FN_DIVIDE: + if (dv1) + { + if (dvalue == 1.0) // remove divide by 1.0 (using non-1.0 value as replacement) + { + memcpy(op,op->parms.parms[!!dv0],sizeof(*op)); + goto start_over; + } + else + { + // change to a multiply + if (dvalue == 0.0) + { + op->fntype = FN_MULTIPLY; + goto start_over; + } + else + { + double d = 1.0/dvalue; + WDL_UINT64 w; + memcpy(&w,&d,sizeof(d)); + // allow conversion to multiply if reciprocal is exact + // we could also just look to see if the last few digits of the mantissa were 0, which would probably be good + // enough, but if the user really wants it they should do * (1/x) instead to force precalculation of reciprocal. + if (!(w & WDL_UINT64_CONST(0xfffffffffffff))) + { + op->fntype = FN_MULTIPLY; + op->parms.parms[1]->parms.dv.directValue = d; + op->parms.parms[1]->parms.dv.valuePtr=NULL; + goto start_over; + } + } + } + } + else if (dvalue == 0.0) + { + if (!retv_parm[!!dv0]) + { + // if 0/x set to always 0. + // this is 0.0 / (oldexpression that can be eliminated) + memcpy(op,op->parms.parms[!dv0],sizeof(*op)); // set to 0 if other action wouldn't do anything + } + else + { + opcodeRec *tmp; + // this is 0.0 / oldexpressionthatmustbeprocessed + op->fntype = FN_JOIN_STATEMENTS; + tmp = op->parms.parms[1]; + op->parms.parms[1] = op->parms.parms[0]; + op->parms.parms[0] = tmp; + // set to (oldexpression;0) + } + goto start_over; + } + break; + case FN_EQ: + if (dvalue == 0.0) + { + // convert x == 0.0 to !x + op->opcodeType=OPCODETYPE_FUNC1; + op->fntype = FN_NOT; + if (dv0) op->parms.parms[0]=op->parms.parms[1]; + goto start_over; + } + break; + case FN_NE: + if (dvalue == 0.0) + { + // convert x != 0.0 to !! + op->opcodeType=OPCODETYPE_FUNC1; + op->fntype = FN_NOTNOT; + if (dv0) op->parms.parms[0]=op->parms.parms[1]; + goto start_over; + } + break; + case FN_LOGICAL_AND: + if (dv0) + { + // dvalue && expr + if (fabs(dvalue) < NSEEL_CLOSEFACTOR) + { + // 0 && expr, replace with 0 + RESTART_DIRECTVALUE(0.0); + } + else + { + // 1 && expr, replace with 0 != expr + op->fntype = FN_NE; + op->parms.parms[0]->parms.dv.valuePtr=NULL; + op->parms.parms[0]->parms.dv.directValue = 0.0; + } + } + else + { + // expr && dvalue + if (fabs(dvalue) < NSEEL_CLOSEFACTOR) + { + // expr && 0 + if (!retv_parm[0]) + { + // expr has no consequence, drop it + RESTART_DIRECTVALUE(0.0); + } + else + { + // replace with (expr; 0) + op->fntype = FN_JOIN_STATEMENTS; + op->parms.parms[1]->parms.dv.valuePtr=NULL; + op->parms.parms[1]->parms.dv.directValue = 0.0; + } + } + else + { + // expr && 1, replace with expr != 0 + op->fntype = FN_NE; + op->parms.parms[1]->parms.dv.valuePtr=NULL; + op->parms.parms[1]->parms.dv.directValue = 0.0; + } + } + goto start_over; + case FN_LOGICAL_OR: + if (dv0) + { + // dvalue || expr + if (fabs(dvalue) >= NSEEL_CLOSEFACTOR) + { + // 1 || expr, replace with 1 + RESTART_DIRECTVALUE(1.0); + } + else + { + // 0 || expr, replace with 0 != expr + op->fntype = FN_NE; + op->parms.parms[0]->parms.dv.valuePtr=NULL; + op->parms.parms[0]->parms.dv.directValue = 0.0; + } + } + else + { + // expr || dvalue + if (fabs(dvalue) >= NSEEL_CLOSEFACTOR) + { + // expr || 1 + if (!retv_parm[0]) + { + // expr has no consequence, drop it and return 1 + RESTART_DIRECTVALUE(1.0); + } + else + { + // replace with (expr; 1) + op->fntype = FN_JOIN_STATEMENTS; + op->parms.parms[1]->parms.dv.valuePtr=NULL; + op->parms.parms[1]->parms.dv.directValue = 1.0; + } + } + else + { + // expr || 0, replace with expr != 0 + op->fntype = FN_NE; + op->parms.parms[1]->parms.dv.valuePtr=NULL; + op->parms.parms[1]->parms.dv.directValue = 0.0; + } + } + goto start_over; + } + } // dv0 || dv1 + + // general optimization of two parameters + switch (op->fntype) + { + case FN_MULTIPLY: + { + opcodeRec *first_parm = op->parms.parms[0],*second_parm = op->parms.parms[1]; + + if (second_parm->opcodeType == first_parm->opcodeType) + { + switch(first_parm->opcodeType) + { + case OPCODETYPE_VALUE_FROM_NAMESPACENAME: + if (first_parm->namespaceidx != second_parm->namespaceidx) break; + WDL_FALLTHROUGH; // fall through + case OPCODETYPE_VARPTR: + if (first_parm->relname && second_parm->relname && !stricmp(second_parm->relname,first_parm->relname)) second_parm=NULL; + break; + case OPCODETYPE_VARPTRPTR: + if (first_parm->parms.dv.valuePtr && first_parm->parms.dv.valuePtr==second_parm->parms.dv.valuePtr) second_parm=NULL; + break; + + } + if (!second_parm) // switch from x*x to sqr(x) + { + static functionType *sqrcpy; + if (!sqrcpy) sqrcpy = nseel_getFunctionByName(NULL,"sqr",NULL); + if (WDL_NORMALLY(sqrcpy)) + { + op->opcodeType = OPCODETYPE_FUNC1; + op->fntype = FUNCTYPE_FUNCTIONTYPEREC; + op->fn = sqrcpy; + goto start_over; + } + } + } + } + break; + case FN_POW: + { + opcodeRec *first_parm = op->parms.parms[0]; + if (first_parm->opcodeType == op->opcodeType && first_parm->fntype == FN_POW) + { + // since first_parm is a pow too, we can multiply the exponents. + + // set our base to be the base of the inner pow + op->parms.parms[0] = first_parm->parms.parms[0]; + + // make the old extra pow be a multiply of the exponents + first_parm->fntype = FN_MULTIPLY; + first_parm->parms.parms[0] = op->parms.parms[1]; + + // put that as the exponent + op->parms.parms[1] = first_parm; + + goto start_over; + } + } + break; + case FN_LOGICAL_AND: + case FN_LOGICAL_OR: + if (op->parms.parms[0]->fntype == FN_NOTNOT) + { + // remove notnot, unnecessary for input to &&/|| operators + op->parms.parms[0] = op->parms.parms[0]->parms.parms[0]; + goto start_over; + } + if (op->parms.parms[1]->fntype == FN_NOTNOT) + { + // remove notnot, unnecessary for input to &&/|| operators + op->parms.parms[1] = op->parms.parms[1]->parms.parms[0]; + goto start_over; + } + break; + } + } + else if (op->opcodeType==OPCODETYPE_FUNC3) // within FUNCTYPE_SIMPLE + { + if (op->fntype == FN_IF_ELSE) + { + if (op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE) + { + int s = fabs(op->parms.parms[0]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; + memcpy(op,op->parms.parms[s ? 1 : 2],sizeof(opcodeRec)); + goto start_over; + } + if (op->parms.parms[0]->opcodeType == OPCODETYPE_FUNC1) + { + if (op->parms.parms[0]->fntype == FN_NOTNOT) + { + // remove notnot, unnecessary for input to ? operator + op->parms.parms[0] = op->parms.parms[0]->parms.parms[0]; + goto start_over; + } + } + } + } + if (op->fntype >= FN_NONCONST_BEGIN && op->fntype < FUNCTYPE_SIMPLEMAX) retv|=1; + + // FUNCTYPE_SIMPLE + } + else if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC && op->fn) + { + + /* + probably worth doing reduction on: + _divop (constant change to multiply) + _and + _or + abs + + maybe: + min + max + + + also, optimize should (recursively or maybe iteratively?) search transitive functions (mul/div) for more constant reduction possibilities + + + */ + + + functionType *pfn = (functionType *)op->fn; + + if (!(pfn->nParams&NSEEL_NPARAMS_FLAG_CONST)) retv|=1; + + if (op->opcodeType==OPCODETYPE_FUNC1) // within FUNCTYPE_FUNCTIONTYPEREC + { + if (op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE) + { + int suc=1; + EEL_F v = op->parms.parms[0]->parms.dv.directValue; + #define DOF(x) if (!strcmp(pfn->name,#x)) v = x(v); else + #define DOF2(x,y) if (!strcmp(pfn->name,#x)) v = x(y); else + DOF(sin) + DOF(cos) + DOF(tan) + DOF(asin) + DOF(acos) + DOF(atan) + DOF2(sqrt, fabs(v)) + DOF(exp) + DOF(log) + DOF(log10) + /* else */ suc=0; + #undef DOF + #undef DOF2 + if (suc) + { + RESTART_DIRECTVALUE(v); + } + + + } + } + else if (op->opcodeType==OPCODETYPE_FUNC2) // within FUNCTYPE_FUNCTIONTYPEREC + { + const int dv0=op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE; + const int dv1=op->parms.parms[1]->opcodeType == OPCODETYPE_DIRECTVALUE; + if (dv0 && dv1) + { + if (!strcmp(pfn->name,"atan2")) + { + RESTART_DIRECTVALUE(atan2(op->parms.parms[0]->parms.dv.directValue, op->parms.parms[1]->parms.dv.directValue)); + } + } + } + // FUNCTYPE_FUNCTIONTYPEREC + } + else + { + // unknown or eel func, assume non-const + retv |= 1; + } + } + + // if we need results, or our function has effects itself, then finish + if (retv || needsResult) + { + return retv || joined_retv || retv_parm[0] || retv_parm[1] || retv_parm[2]; + } + + // we don't need results here, and our function is const, which means we can remove it + { + int cnt=0, idx1=0, idx2=0, x; + for (x=0;x<3;x++) if (retv_parm[x]) { if (!cnt++) idx1=x; else idx2=x; } + + if (!cnt) // none of the parameters do anything, remove this opcode + { + if (lastJoinOp) + { + // replace previous join with its first linked opcode, removing this opcode completely + memcpy(lastJoinOp,lastJoinOp->parms.parms[0],sizeof(*lastJoinOp)); + } + else if (op->opcodeType != OPCODETYPE_DIRECTVALUE) + { + // allow caller to easily detect this as trivial and remove it + op->opcodeType = OPCODETYPE_DIRECTVALUE; + op->parms.dv.valuePtr=NULL; + op->parms.dv.directValue=0.0; + } + // return joined_retv below + } + else + { + // if parameters are non-const, and we're a conditional, preserve our function + if (FNPTR_HAS_CONDITIONAL_EXEC(op)) return 1; + + // otherwise, condense into either the non-const statement, or a join + if (cnt==1) + { + memcpy(op,op->parms.parms[idx1],sizeof(*op)); + } + else if (cnt == 2) + { + op->opcodeType = OPCODETYPE_FUNC2; + op->fntype = FN_JOIN_STATEMENTS; + op->fn = op; + op->parms.parms[0] = op->parms.parms[idx1]; + op->parms.parms[1] = op->parms.parms[idx2]; + op->parms.parms[2] = NULL; + } + else + { + // todo need to create a new opcodeRec here, for now just leave as is + // (non-conditional const 3 parameter functions are rare anyway) + } + return 1; + } + } + return joined_retv; +} + + +static int generateValueToReg(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int whichReg, const namespaceInformation *functionPrefix, int allowCache) +{ + EEL_F *b=NULL; + if (op->opcodeType==OPCODETYPE_VALUE_FROM_NAMESPACENAME) + { + char nm[NSEEL_MAX_VARIABLE_NAMELEN+1]; + const char *p = op->relname; + combineNamespaceFields(nm,functionPrefix,p+(*p == '#'),op->namespaceidx); + if (!nm[0]) return -1; + if (*p == '#') + { + if (ctx->isGeneratingCommonFunction) + b = newCtxDataBlock(sizeof(EEL_F),sizeof(EEL_F)); + else + b = newDataBlock(sizeof(EEL_F),sizeof(EEL_F)); + + if (!b) RET_MINUS1_FAIL("error creating storage for str") + + if (!ctx->onNamedString) return -1; // should never happen, will not generate OPCODETYPE_VALUE_FROM_NAMESPACENAME with # prefix if !onNamedString + + *b = ctx->onNamedString(ctx->caller_this,nm); + } + else + { + b = nseel_int_register_var(ctx,nm,0,NULL); + if (!b) RET_MINUS1_FAIL("error registering var") + } + } + else + { + if (op->opcodeType != OPCODETYPE_DIRECTVALUE) allowCache=0; + + if (op->opcodeType==OPCODETYPE_DIRECTVALUE_TEMPSTRING && ctx->onNamedString) + { + op->parms.dv.directValue = ctx->onNamedString(ctx->caller_this,""); + op->parms.dv.valuePtr = NULL; + } + + b=op->parms.dv.valuePtr; + if (!b && op->opcodeType == OPCODETYPE_VARPTR && op->relname && op->relname[0]) + { + op->parms.dv.valuePtr = b = nseel_int_register_var(ctx,op->relname,0,NULL); + } + + if (b && op->opcodeType == OPCODETYPE_VARPTRPTR) b = *(EEL_F **)b; + if (!b && allowCache) + { + int n=50; // only scan last X items + opcodeRec *r = ctx->directValueCache; + while (r && n--) + { + if (r->parms.dv.directValue == op->parms.dv.directValue && (b=r->parms.dv.valuePtr)) break; + r=(opcodeRec*)r->fn; + } + } + if (!b) + { + ctx->l_stats[3]++; + if (ctx->isGeneratingCommonFunction) + b = newCtxDataBlock(sizeof(EEL_F),sizeof(EEL_F)); + else + b = newDataBlock(sizeof(EEL_F),sizeof(EEL_F)); + + if (!b) RET_MINUS1_FAIL("error allocating data block") + + if (op->opcodeType != OPCODETYPE_VARPTRPTR) op->parms.dv.valuePtr = b; + *b = denormal_filter_double2(op->parms.dv.directValue); + + if (allowCache) + { + op->fn = ctx->directValueCache; + ctx->directValueCache = op; + } + } + } + + GLUE_MOV_PX_DIRECTVALUE_GEN(bufOut,(INT_PTR)b,whichReg); + return GLUE_MOV_PX_DIRECTVALUE_SIZE; +} + + +unsigned char *compileCodeBlockWithRet(compileContext *ctx, opcodeRec *rec, int *computTableSize, const namespaceInformation *namespacePathToThis, + int supportedReturnValues, int *rvType, int *fpStackUsage, int *canHaveDenormalOutput) +{ + unsigned char *p, *newblock2; + // generate code call + int funcsz=compileOpcodes(ctx,rec,NULL,1024*1024*128,NULL,namespacePathToThis,supportedReturnValues, rvType,fpStackUsage, NULL); + if (funcsz<0) return NULL; + + p = newblock2 = newCodeBlock(funcsz+ sizeof(GLUE_RET)+GLUE_FUNC_ENTER_SIZE+GLUE_FUNC_LEAVE_SIZE,32); + if (!newblock2) return NULL; + #if GLUE_FUNC_ENTER_SIZE > 0 + memcpy(p,&GLUE_FUNC_ENTER,GLUE_FUNC_ENTER_SIZE); + p += GLUE_FUNC_ENTER_SIZE; + #endif + *fpStackUsage=0; + funcsz=compileOpcodes(ctx,rec,p, funcsz, computTableSize,namespacePathToThis,supportedReturnValues, rvType,fpStackUsage, canHaveDenormalOutput); + if (funcsz<0) return NULL; + p+=funcsz; + + #if GLUE_FUNC_LEAVE_SIZE > 0 + memcpy(p,&GLUE_FUNC_LEAVE,GLUE_FUNC_LEAVE_SIZE); + p+=GLUE_FUNC_LEAVE_SIZE; + #endif + memcpy(p,&GLUE_RET,sizeof(GLUE_RET)); p+=sizeof(GLUE_RET); +#if defined(__arm__) || defined(__aarch64__) + __clear_cache(newblock2,p); +#endif + + ctx->l_stats[2]+=funcsz+2; + return newblock2; +} + +static int compileNativeFunctionCall(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, + int *rvMode, int *fpStackUsage, int preferredReturnValues, int *canHaveDenormalOutput) +{ + // builtin function generation + int func_size=0; + int cfunc_abiinfo=0; + int local_fpstack_use=0; // how many items we have pushed onto the fp stack + int parm_size=0; + int restore_stack_amt=0; +#ifdef GLUE_HAS_FUSE + int fuse_flags = 0; // 1 = last parm was trivial (likely mov rax, movsd xmm0 ,[rax]) +#endif +#if GLUE_MAX_SPILL_REGS > 0 + int spill_reg = -1; +#endif + + NSEEL_PPPROC preProc=0; + void **repl=NULL; + + int n_params= 1 + op->opcodeType - OPCODETYPE_FUNC1; + + const int parm0_dv = op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE; + const int parm1_dv = n_params > 1 && op->parms.parms[1]->opcodeType == OPCODETYPE_DIRECTVALUE; + + void *func = nseel_getBuiltinFunctionAddress(ctx, op->fntype, op->fn, &preProc,&repl, + &cfunc_abiinfo,preferredReturnValues, + parm0_dv ? &op->parms.parms[0]->parms.dv.directValue : NULL, + parm1_dv ? &op->parms.parms[1]->parms.dv.directValue : NULL + ); + + *fpStackUsage=BIF_GETFPSTACKUSE(cfunc_abiinfo); + *rvMode = RETURNVALUE_NORMAL; + + if (cfunc_abiinfo & BIF_TAKES_VARPARM) + { +#if defined(__arm__) || (defined (_M_ARM) && _M_ARM == 7) + const int max_params=16384; // arm uses up to two instructions, should be good for at leaast 64k (16384*4) +#elif defined(__ppc__) + const int max_params=4096; // 32kb max offset addressing for stack, so 4096*4 = 16384, should be safe +#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) + const int max_params=32768; +#else + const int max_params=32768; // sanity check, the stack is free to grow on x86/x86-64 +#endif + int x; + // this mode is less efficient in that it creates a list of pointers on the stack to pass to the function + // but it is more flexible and works for >3 parameters. + if (op->opcodeType == OPCODETYPE_FUNCX) + { + n_params=0; + for (x=0;x<3;x++) + { + opcodeRec *prni=op->parms.parms[x]; + while (prni) + { + const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS; + n_params++; + if (!isMP||n_params>=max_params) break; + prni = prni->parms.parms[1]; + } + } + } + + restore_stack_amt = (sizeof(void *) * n_params + 15)&~15; + + if (restore_stack_amt) + { + int offs = restore_stack_amt; + while (offs > 0) + { + int amt = offs; + if (amt > 4096) amt=4096; + + if (bufOut_len < parm_size+GLUE_MOVE_STACK_SIZE) RET_MINUS1_FAIL("insufficient size for varparm") + if (bufOut) GLUE_MOVE_STACK(bufOut+parm_size, - amt); + parm_size += GLUE_MOVE_STACK_SIZE; + offs -= amt; + + if (offs>0) // make sure this page is in memory + { + if (bufOut_len < parm_size+GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(0)) + RET_MINUS1_FAIL("insufficient size for varparm stackchk") + if (bufOut) GLUE_STORE_P1_TO_STACK_AT_OFFS(bufOut+parm_size,0); + parm_size += GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(0); + } + } + } + + if (op->opcodeType == OPCODETYPE_FUNCX) + { + n_params=0; + for (x=0;x<3;x++) + { + opcodeRec *prni=op->parms.parms[x]; + while (prni) + { + const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS; + opcodeRec *r = isMP ? prni->parms.parms[0] : prni; + if (r) + { + int canHaveDenorm=0; + int rvt=RETURNVALUE_NORMAL; + int subfpstackuse=0, use_offs; + + int lsz = compileOpcodes(ctx,r,bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, rvt,&rvt, &subfpstackuse, &canHaveDenorm); + if (canHaveDenorm && canHaveDenormalOutput) *canHaveDenormalOutput = 1; + + if (lsz<0) RET_MINUS1_FAIL("call coc for varparmX failed") + if (rvt != RETURNVALUE_NORMAL) RET_MINUS1_FAIL("call coc for varparmX gave bad type back"); + + parm_size += lsz; + use_offs = n_params*(int) sizeof(void *); + + if (bufOut_len < parm_size+GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs)) + RET_MINUS1_FAIL("call coc for varparmX size"); + if (bufOut) GLUE_STORE_P1_TO_STACK_AT_OFFS(bufOut + parm_size, use_offs); + parm_size+=GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs); + + if (subfpstackuse+local_fpstack_use > *fpStackUsage) *fpStackUsage = subfpstackuse+local_fpstack_use; + } + else RET_MINUS1_FAIL("zero parameter varparmX") + + n_params++; + + if (!isMP||n_params>=max_params) break; + prni = prni->parms.parms[1]; + } + } + } + else for (x=0;xparms.parms[x]; + if (r) + { + int canHaveDenorm=0; + int subfpstackuse=0; + int rvt=RETURNVALUE_NORMAL; + int use_offs; + + int lsz = compileOpcodes(ctx,r,bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, rvt,&rvt, &subfpstackuse, &canHaveDenorm); + if (canHaveDenorm && canHaveDenormalOutput) *canHaveDenormalOutput = 1; + + if (lsz<0) RET_MINUS1_FAIL("call coc for varparm123 failed") + if (rvt != RETURNVALUE_NORMAL) RET_MINUS1_FAIL("call coc for varparm123 gave bad type back"); + + parm_size += lsz; + + use_offs = x*(int)sizeof(void *); + if (bufOut_len < parm_size+GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs)) + RET_MINUS1_FAIL("call coc for varparm123 size"); + if (bufOut) GLUE_STORE_P1_TO_STACK_AT_OFFS(bufOut + parm_size, use_offs); + parm_size+=GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs); + + if (subfpstackuse+local_fpstack_use > *fpStackUsage) *fpStackUsage = subfpstackuse+local_fpstack_use; + } + else RET_MINUS1_FAIL("zero parameter for varparm123"); + } + + if (bufOut_len < parm_size+GLUE_MOV_PX_DIRECTVALUE_SIZE+GLUE_MOVE_PX_STACKPTR_SIZE) RET_MINUS1_FAIL("insufficient size for varparm p1") + if (bufOut) GLUE_MOV_PX_DIRECTVALUE_GEN(bufOut+parm_size, (INT_PTR)n_params,1); + parm_size+=GLUE_MOV_PX_DIRECTVALUE_SIZE; + if (bufOut) GLUE_MOVE_PX_STACKPTR_GEN(bufOut+parm_size, 0); + parm_size+=GLUE_MOVE_PX_STACKPTR_SIZE; + + } + else // not varparm + { + int pn; + #if GLUE_MAX_FPSTACK_SIZE > 0 + int need_fxch=0; + #endif + int last_nt_parm=-1, last_nt_parm_type=-1; + + if (op->opcodeType == OPCODETYPE_FUNCX) + { + // this is not yet supported (calling conventions will need to be sorted, among other things) + RET_MINUS1_FAIL("funcx for native functions requires BIF_TAKES_VARPARM or BIF_TAKES_VARPARM_EX") + } + + if (parm0_dv) + { + if (func == nseel_asm_stack_pop) + { + func = GLUE_realAddress(nseel_asm_stack_pop_fast,&func_size); + if (!func || bufOut_len < func_size) RET_MINUS1_FAIL(func?"failed on popfast size":"failed on popfast addr") + + if (bufOut) + { + memcpy(bufOut,func,func_size); + NSEEL_PProc_Stack(bufOut,func_size,ctx); + } + return func_size; + } + else if (func == nseel_asm_stack_peek) + { + int f = (int) op->parms.parms[0]->parms.dv.directValue; + if (!f) + { + func = GLUE_realAddress(nseel_asm_stack_peek_top,&func_size); + if (!func || bufOut_len < func_size) RET_MINUS1_FAIL(func?"failed on peek size":"failed on peek addr") + + if (bufOut) + { + memcpy(bufOut,func,func_size); + NSEEL_PProc_Stack_PeekTop(bufOut,func_size,ctx); + } + return func_size; + } + else + { + func = GLUE_realAddress(nseel_asm_stack_peek_int,&func_size); + if (!func || bufOut_len < func_size) RET_MINUS1_FAIL(func?"failed on peekint size":"failed on peekint addr") + + if (bufOut) + { + memcpy(bufOut,func,func_size); + NSEEL_PProc_Stack_PeekInt(bufOut,func_size,ctx,f*sizeof(EEL_F)); + } + return func_size; + } + } + } + // end of built-in function specific special casing + + + // first pass, calculate any non-trivial parameters + for (pn=0; pn < n_params; pn++) + { + if (!OPCODE_IS_TRIVIAL(op->parms.parms[pn])) + { + int canHaveDenorm=0; + int subfpstackuse=0; + int lsz=0; + int rvt=RETURNVALUE_NORMAL; + int may_need_fppush=-1; + if (last_nt_parm>=0) + { + if (last_nt_parm_type==RETURNVALUE_FPSTACK) + { + may_need_fppush= parm_size; + } + else + { + // push last result + if (bufOut_len < parm_size + (int)sizeof(GLUE_PUSH_P1)) RET_MINUS1_FAIL("failed on size, pushp1") + if (bufOut) memcpy(bufOut + parm_size, &GLUE_PUSH_P1, sizeof(GLUE_PUSH_P1)); + parm_size += sizeof(GLUE_PUSH_P1); + } + } + + if (func == nseel_asm_bnot) rvt=RETURNVALUE_BOOL_REVERSED|RETURNVALUE_BOOL; + else if (pn == n_params - 1) + { + if (cfunc_abiinfo&BIF_LASTPARMONSTACK) rvt=RETURNVALUE_FPSTACK; + else if (cfunc_abiinfo&BIF_LASTPARM_ASBOOL) rvt=RETURNVALUE_BOOL; + else if (func == nseel_asm_assign) rvt=RETURNVALUE_FPSTACK|RETURNVALUE_NORMAL; + } + else if (pn == n_params -2 && (cfunc_abiinfo&BIF_SECONDLASTPARMST)) + { + rvt=RETURNVALUE_FPSTACK; + } + + lsz = compileOpcodes(ctx,op->parms.parms[pn],bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, rvt,&rvt, &subfpstackuse, &canHaveDenorm); + + if (lsz<0) RET_MINUS1_FAIL("call coc failed") + + if (func == nseel_asm_bnot && rvt==RETURNVALUE_BOOL_REVERSED) + { + // remove bnot, compileOpcodes() used fptobool_rev + func = NULL; + rvt = RETURNVALUE_BOOL; + } + + if (canHaveDenorm && canHaveDenormalOutput) *canHaveDenormalOutput = 1; + + parm_size += lsz; + + if (may_need_fppush>=0) + { +#if GLUE_MAX_SPILL_REGS > 0 + if (local_fpstack_use+subfpstackuse < GLUE_MAX_SPILL_REGS && pn == n_params - 1 && !(ctx->optimizeDisableFlags&OPTFLAG_NO_FPSTACK)) + { + int spill_sz; + spill_reg = local_fpstack_use+subfpstackuse; + spill_sz = GLUE_SAVE_TO_SPILL_SIZE(spill_reg); + if (bufOut_len < parm_size + spill_sz) RET_MINUS1_FAIL("failed on size, savetospilll") + + if (bufOut) + { + memmove(bufOut + may_need_fppush + spill_sz, bufOut + may_need_fppush, parm_size - may_need_fppush); + GLUE_SAVE_TO_SPILL(bufOut + may_need_fppush, spill_reg); + } + parm_size += spill_sz; + local_fpstack_use++; + } + else +#endif + if (local_fpstack_use+subfpstackuse >= (GLUE_MAX_FPSTACK_SIZE-1) || (ctx->optimizeDisableFlags&OPTFLAG_NO_FPSTACK)) + { + if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_FPSTACK_TOSTACK)) + RET_MINUS1_FAIL("failed on size, popfpstacktostack") + + if (bufOut) + { + memmove(bufOut + may_need_fppush + sizeof(GLUE_POP_FPSTACK_TOSTACK), bufOut + may_need_fppush, parm_size - may_need_fppush); + memcpy(bufOut + may_need_fppush, &GLUE_POP_FPSTACK_TOSTACK, sizeof(GLUE_POP_FPSTACK_TOSTACK)); + + } + parm_size += sizeof(GLUE_POP_FPSTACK_TOSTACK); + } + else + { + local_fpstack_use++; + } + } + + if (subfpstackuse+local_fpstack_use > *fpStackUsage) *fpStackUsage = subfpstackuse+local_fpstack_use; + + last_nt_parm = pn; + last_nt_parm_type = rvt; + + if (pn == n_params - 1 && func == nseel_asm_assign) + { + if (!(ctx->optimizeDisableFlags & OPTFLAG_FULL_DENORMAL_CHECKS) && + (!canHaveDenorm || (ctx->optimizeDisableFlags & OPTFLAG_NO_DENORMAL_CHECKS))) + { + if (rvt == RETURNVALUE_FPSTACK) + { + cfunc_abiinfo |= BIF_LASTPARMONSTACK; + func = nseel_asm_assign_fast_fromfp; + } + else + { + func = nseel_asm_assign_fast; + } + } + else + { + if (rvt == RETURNVALUE_FPSTACK) + { + cfunc_abiinfo |= BIF_LASTPARMONSTACK; + func = nseel_asm_assign_fromfp; + } + } + + } + } + } + + pn = last_nt_parm; + + if (pn >= 0) // if the last thing executed doesn't go to the last parameter, move it there + { + if ((cfunc_abiinfo&BIF_SECONDLASTPARMST) && pn == n_params-2) + { + // do nothing, things are in the right place + } + else if (pn != n_params-1) + { + // generate mov p1->pX + if (bufOut_len < parm_size + GLUE_SET_PX_FROM_P1_SIZE) RET_MINUS1_FAIL("size, pxfromp1") + if (bufOut) GLUE_SET_PX_FROM_P1(bufOut + parm_size,n_params - 1 - pn); + parm_size += GLUE_SET_PX_FROM_P1_SIZE; + } + } + + // pop any pushed parameters + while (--pn >= 0) + { + if (!OPCODE_IS_TRIVIAL(op->parms.parms[pn])) + { + if ((cfunc_abiinfo&BIF_SECONDLASTPARMST) && pn == n_params-2) + { + if (!local_fpstack_use) + { + #if GLUE_HAS_FPREG2 > 0 + if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_STACK_TO_FPREG2)) RET_MINUS1_FAIL("size, popstacktofpstack2 2") + if (bufOut) memcpy(bufOut+parm_size,GLUE_POP_STACK_TO_FPREG2,sizeof(GLUE_POP_STACK_TO_FPREG2)); + parm_size += sizeof(GLUE_POP_STACK_TO_FPREG2); + #else + if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_STACK_TO_FPSTACK)) RET_MINUS1_FAIL("size, popstacktofpstack 2") + if (bufOut) memcpy(bufOut+parm_size,GLUE_POP_STACK_TO_FPSTACK,sizeof(GLUE_POP_STACK_TO_FPSTACK)); + parm_size += sizeof(GLUE_POP_STACK_TO_FPSTACK); + #endif + #if GLUE_MAX_FPSTACK_SIZE > 0 + need_fxch = 1; + #endif + } + else + { +#if GLUE_MAX_SPILL_REGS > 0 + if (spill_reg<0) RET_MINUS1_FAIL("spill reg not allocated"); + + if (bufOut_len < parm_size + GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(spill_reg)) RET_MINUS1_FAIL("size, copy fps to fpreg2") + if (bufOut) GLUE_RESTORE_SPILL_TO_FPREG2(bufOut+parm_size,spill_reg); + parm_size += GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(spill_reg); +#endif + local_fpstack_use--; + } + } + else + { + if (bufOut_len < parm_size + GLUE_POP_PX_SIZE) RET_MINUS1_FAIL("size, poppx") + if (bufOut) GLUE_POP_PX(bufOut + parm_size,n_params - 1 - pn); + parm_size += GLUE_POP_PX_SIZE; + } + } + } + + // finally, set trivial pointers + for (pn=0; pn < n_params; pn++) + { + if (OPCODE_IS_TRIVIAL(op->parms.parms[pn])) + { + if (pn == n_params-2 && (cfunc_abiinfo&(BIF_SECONDLASTPARMST))) // second to last parameter + { + #if GLUE_HAS_FPREG2 > 0 + const int req = RETURNVALUE_FPREG2; + #else + const int req = RETURNVALUE_FPSTACK; + #endif + int a = compileOpcodes(ctx,op->parms.parms[pn],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size,computTableSize,namespacePathToThis, + req,NULL,NULL,canHaveDenormalOutput); + if (a<0) RET_MINUS1_FAIL("coc call here 2") + parm_size+=a; + + #if GLUE_MAX_FPSTACK_SIZE > 0 + need_fxch = 1; + #endif + } + else if (pn == n_params-1) // last parameter, but we should call compileOpcodes to get it in the right format (compileOpcodes can optimize that process if it needs to) + { + int rvt=0, a; + int wantFpStack = func == nseel_asm_assign; + #ifdef GLUE_PREFER_NONFP_DV_ASSIGNS // x86-64, and maybe others, prefer to avoid the fp stack for a simple copy + if (wantFpStack && + (op->parms.parms[pn]->opcodeType != OPCODETYPE_DIRECTVALUE || + op->parms.parms[pn]->parms.dv.directValue != 0.0)) + { + wantFpStack=-1; // cacheable but non-FP stack + } + #endif + #if GLUE_HAS_FPREG2 > 0 + if (pn > 0 && (cfunc_abiinfo&(BIF_SECONDLASTPARMST) && !OPCODE_IS_TRIVIAL(op->parms.parms[pn-1]))) + { + if (bufOut_len < parm_size+(int)sizeof(GLUE_COPY_FPSTACK_TO_FPREG2)) RET_MINUS1_FAIL("fptofpstack2tfp"); + if (bufOut) memcpy(bufOut+parm_size,GLUE_COPY_FPSTACK_TO_FPREG2,sizeof(GLUE_COPY_FPSTACK_TO_FPREG2)); + parm_size += sizeof(GLUE_COPY_FPSTACK_TO_FPREG2); + } + #endif + + a = compileOpcodes(ctx,op->parms.parms[pn],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size,computTableSize,namespacePathToThis, + func == nseel_asm_bnot ? (RETURNVALUE_BOOL_REVERSED|RETURNVALUE_BOOL) : + (cfunc_abiinfo & BIF_LASTPARMONSTACK) ? RETURNVALUE_FPSTACK : + (cfunc_abiinfo & BIF_LASTPARM_ASBOOL) ? RETURNVALUE_BOOL : + wantFpStack < 0 ? (RETURNVALUE_CACHEABLE|RETURNVALUE_NORMAL) : + wantFpStack ? (RETURNVALUE_FPSTACK|RETURNVALUE_NORMAL) : + RETURNVALUE_NORMAL, + &rvt, NULL,canHaveDenormalOutput); + + if (a<0) RET_MINUS1_FAIL("coc call here 3") +#ifdef GLUE_HAS_FUSE + if (rvt == RETURNVALUE_FPSTACK) fuse_flags |= 1; +#endif + + if (func == nseel_asm_bnot && rvt == RETURNVALUE_BOOL_REVERSED) + { + // remove bnot, compileOpcodes() used fptobool_rev + // case: !b ? 2 : 3, for example + func = NULL; + rvt = RETURNVALUE_BOOL; + } + + parm_size+=a; + #if GLUE_MAX_FPSTACK_SIZE > 0 + need_fxch = 0; + #endif + + if (func == nseel_asm_assign) + { + if (rvt == RETURNVALUE_FPSTACK) + { + if (!(ctx->optimizeDisableFlags & OPTFLAG_FULL_DENORMAL_CHECKS)) + { + func = nseel_asm_assign_fast_fromfp; + } + else + { + func = nseel_asm_assign_fromfp; + } + } + else if (!(ctx->optimizeDisableFlags & OPTFLAG_FULL_DENORMAL_CHECKS)) + { + // assigning a value (from a variable or other non-computer), can use a fast assign (no denormal/result checking) + func = nseel_asm_assign_fast; + } + } + } + else + { + if (bufOut_len < parm_size + GLUE_MOV_PX_DIRECTVALUE_SIZE) RET_MINUS1_FAIL("size, pxdvsz") + if (bufOut) + { + if (generateValueToReg(ctx,op->parms.parms[pn],bufOut + parm_size,n_params - 1 - pn,namespacePathToThis, 0/*nocaching, function gets pointer*/)<0) RET_MINUS1_FAIL("gvtr") + } + parm_size += GLUE_MOV_PX_DIRECTVALUE_SIZE; + } + } + } + + #if GLUE_MAX_FPSTACK_SIZE > 0 + if ((cfunc_abiinfo&(BIF_SECONDLASTPARMST)) && !(cfunc_abiinfo&(BIF_LAZYPARMORDERING))&& + ((!!need_fxch)^!!(cfunc_abiinfo&BIF_REVERSEFPORDER)) + ) + { + // emit fxch + if (bufOut_len < sizeof(GLUE_FXCH)) RET_MINUS1_FAIL("len,fxch") + if (bufOut) + { + memcpy(bufOut+parm_size,GLUE_FXCH,sizeof(GLUE_FXCH)); + } + parm_size+=sizeof(GLUE_FXCH); + } + #endif + + if (!*canHaveDenormalOutput) + { + // if add_op or sub_op, and constant non-denormal input, safe to omit denormal checks + if (func == (void*)nseel_asm_add_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) >= DENORMAL_CLEARING_THRESHOLD) + { + func = nseel_asm_add_op_fast; + } + else if (func == (void*)nseel_asm_sub_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) >= DENORMAL_CLEARING_THRESHOLD) + { + func = nseel_asm_sub_op_fast; + } + // or if mul/div by a fixed value of >= or <= 1.0 + else if (func == (void *)nseel_asm_mul_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) >= 1.0) + { + func = nseel_asm_mul_op_fast; + } + else if (func == (void *)nseel_asm_div_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) <= 1.0) + { + func = nseel_asm_div_op_fast; + } + } + } // not varparm + + if (cfunc_abiinfo & (BIF_CLEARDENORMAL | BIF_RETURNSBOOL) ) *canHaveDenormalOutput=0; + else if (!(cfunc_abiinfo & BIF_WONTMAKEDENORMAL)) *canHaveDenormalOutput=1; + + if (func) + { + func = GLUE_realAddress(func,&func_size); + if (!func) RET_MINUS1_FAIL("failrealladdrfunc") + } + else + { + func_size = 0; + } + + if (bufOut_len < parm_size + func_size) RET_MINUS1_FAIL("funcsz") + + if (bufOut) + { + unsigned char *p=bufOut + parm_size; + memcpy(p, func, func_size); +#if GLUE_HAS_FUSE + parm_size += GLUE_FUSE(ctx,p,parm_size,func_size,fuse_flags, +#if GLUE_MAX_SPILL_REGS > 0 + spill_reg +#else + -1 +#endif + ); + p = bufOut + parm_size; +#endif + if (preProc) p=preProc(p,func_size,ctx); + if (repl) + { + if (repl[0]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[0]); + if (repl[1]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[1]); + if (repl[2]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[2]); + if (repl[3]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[3]); + } + } + + if (restore_stack_amt) + { + int rem = restore_stack_amt; + while (rem > 0) + { + int amt = rem; + if (amt > 4096) amt=4096; + rem -= amt; + + if (bufOut_len < parm_size + func_size + GLUE_MOVE_STACK_SIZE) RET_MINUS1_FAIL("insufficient size for varparm") + if (bufOut) GLUE_MOVE_STACK(bufOut + parm_size + func_size, amt); + parm_size += GLUE_MOVE_STACK_SIZE; + } + } + + if (cfunc_abiinfo&BIF_RETURNSONSTACK) *rvMode = RETURNVALUE_FPSTACK; + else if (cfunc_abiinfo&BIF_RETURNSBOOL) *rvMode=RETURNVALUE_BOOL; + + return parm_size + func_size; +} + +static int compileEelFunctionCall(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, + int *rvMode, int *fpStackUse, int *canHaveDenormalOutput) +{ + int func_size=0, parm_size=0; + int pn; + int last_nt_parm=-1,last_nt_parm_mode=0; + void *func_e=NULL; + int n_params; + opcodeRec *parmptrs[NSEEL_MAX_EELFUNC_PARAMETERS]; + int cfp_numparams=-1; + int cfp_statesize=0; + EEL_F **cfp_ptrs=NULL; + int func_raw=0; + int do_parms; + int x; + + void *func; + + for (x=0; x < 3; x ++) parmptrs[x] = op->parms.parms[x]; + + if (op->opcodeType == OPCODETYPE_FUNCX) + { + n_params=0; + for (x=0;x<3;x++) + { + opcodeRec *prni=op->parms.parms[x]; + while (prni && n_params < NSEEL_MAX_EELFUNC_PARAMETERS) + { + const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS; + parmptrs[n_params++] = isMP ? prni->parms.parms[0] : prni; + if (!isMP) break; + prni = prni->parms.parms[1]; + } + } + } + else + { + n_params = 1 + op->opcodeType - OPCODETYPE_FUNC1; + } + + *fpStackUse = 0; + func = nseel_getEELFunctionAddress(ctx, op, + &cfp_numparams,&cfp_statesize,&cfp_ptrs, + computTableSize, + &func_e, &func_raw, + !!bufOut,namespacePathToThis,rvMode,fpStackUse,canHaveDenormalOutput, parmptrs, n_params); + + if (func_raw) func_size = (int) ((char*)func_e - (char*)func); + else if (func) func = GLUE_realAddress(func,&func_size); + + if (!func) RET_MINUS1_FAIL("eelfuncaddr") + +#if GLUE_MAX_FPSTACK_SIZE > 0 + *fpStackUse += 1; +#endif + + if (cfp_numparams>0 && n_params != cfp_numparams) + { + RET_MINUS1_FAIL("eelfuncnp") + } + + // user defined function + do_parms = cfp_numparams>0 && cfp_ptrs && cfp_statesize>0; + + // if function local/parameter state is zero, we need to allocate storage for it + if (cfp_statesize>0 && cfp_ptrs && !cfp_ptrs[0]) + { + EEL_F *pstate = newDataBlock(sizeof(EEL_F)*cfp_statesize,8); + if (!pstate) RET_MINUS1_FAIL("eelfuncdb") + + for (pn=0;pn= 0 && do_parms) + { + if (last_nt_parm_mode == RETURNVALUE_FPSTACK) + { + if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_FPSTACK_TOSTACK)) RET_MINUS1_FAIL("eelfunc_size popfpstacktostack") + if (bufOut) memcpy(bufOut + parm_size,GLUE_POP_FPSTACK_TOSTACK,sizeof(GLUE_POP_FPSTACK_TOSTACK)); + parm_size+=sizeof(GLUE_POP_FPSTACK_TOSTACK); + } + else + { + if (bufOut_len < parm_size + (int)sizeof(GLUE_PUSH_P1PTR_AS_VALUE)) RET_MINUS1_FAIL("eelfunc_size pushp1ptrasval") + + // push + if (bufOut) memcpy(bufOut + parm_size,&GLUE_PUSH_P1PTR_AS_VALUE,sizeof(GLUE_PUSH_P1PTR_AS_VALUE)); + parm_size+=sizeof(GLUE_PUSH_P1PTR_AS_VALUE); + } + } + + last_nt_parm_mode=0; + lsz = compileOpcodes(ctx,parmptrs[pn],bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, + do_parms ? (RETURNVALUE_FPSTACK|RETURNVALUE_NORMAL) : RETURNVALUE_IGNORE,&last_nt_parm_mode,&sUse, &needDenorm); + + // todo: if needDenorm, denorm convert when copying parameter + + if (lsz<0) RET_MINUS1_FAIL("eelfunc, coc fail") + + if (last_nt_parm_mode == RETURNVALUE_FPSTACK) sUse++; + if (sUse > *fpStackUse) *fpStackUse=sUse; + parm_size += lsz; + + last_nt_parm = pn; + } + // pop non-trivial results into place + if (last_nt_parm >=0 && do_parms) + { + while (--pn >= 0) + { + if (!parmptrs[pn] || OPCODE_IS_TRIVIAL(parmptrs[pn])) continue; // skip and process after + if (pn == last_nt_parm) + { + if (last_nt_parm_mode == RETURNVALUE_FPSTACK) + { + // pop to memory directly + const int cpsize = GLUE_POP_FPSTACK_TO_PTR(NULL,NULL); + if (bufOut_len < parm_size + cpsize) RET_MINUS1_FAIL("eelfunc size popfpstacktoptr") + + if (bufOut) GLUE_POP_FPSTACK_TO_PTR((unsigned char *)bufOut + parm_size,cfp_ptrs[pn]); + parm_size += cpsize; + } + else + { + // copy direct p1ptr to mem + const int cpsize = GLUE_COPY_VALUE_AT_P1_TO_PTR(NULL,NULL); + if (bufOut_len < parm_size + cpsize) RET_MINUS1_FAIL("eelfunc size copyvalueatp1toptr") + + if (bufOut) GLUE_COPY_VALUE_AT_P1_TO_PTR((unsigned char *)bufOut + parm_size,cfp_ptrs[pn]); + parm_size += cpsize; + } + } + else + { + const int popsize = GLUE_POP_VALUE_TO_ADDR(NULL,NULL); + if (bufOut_len < parm_size + popsize) RET_MINUS1_FAIL("eelfunc size pop value to addr") + + if (bufOut) GLUE_POP_VALUE_TO_ADDR((unsigned char *)bufOut + parm_size,cfp_ptrs[pn]); + parm_size+=popsize; + + } + } + } + + // finally, set any trivial parameters + if (do_parms) + { + const int cpsize = GLUE_MOV_PX_DIRECTVALUE_SIZE + GLUE_COPY_VALUE_AT_P1_TO_PTR(NULL,NULL); + for (pn=0; pn < n_params; pn++) + { + if (!parmptrs[pn] || !OPCODE_IS_TRIVIAL(parmptrs[pn])) continue; // set trivial values, we already set nontrivials + + if (bufOut_len < parm_size + cpsize) RET_MINUS1_FAIL("eelfunc size trivial set") + + if (bufOut) + { + if (generateValueToReg(ctx,parmptrs[pn],bufOut + parm_size,0,namespacePathToThis, 1)<0) RET_MINUS1_FAIL("eelfunc gvr fail") + GLUE_COPY_VALUE_AT_P1_TO_PTR(bufOut + parm_size + GLUE_MOV_PX_DIRECTVALUE_SIZE,cfp_ptrs[pn]); + } + parm_size += cpsize; + + } + } + + if (bufOut_len < parm_size + func_size) RET_MINUS1_FAIL("eelfunc size combined") + + if (bufOut) memcpy(bufOut + parm_size, func, func_size); + + return parm_size + func_size; + // end of EEL function generation +} + +#ifdef DUMP_OPS_DURING_COMPILE +void dumpOp(compileContext *ctx, opcodeRec *op, int start); +#endif + +#ifdef EEL_DUMP_OPS +void dumpOpcodeTree(compileContext *ctx, FILE *fp, opcodeRec *op, int indent_amt) +{ + const char *fname=""; + fprintf(fp,"%*sOP TYPE %d", indent_amt, "", + op->opcodeType==OPCODETYPE_DIRECTVALUE_TEMPSTRING ? 10000 : // remap around OPCODETYPE_DIRECTVALUE_TEMPSTRING + op->opcodeType > OPCODETYPE_DIRECTVALUE_TEMPSTRING ? op->opcodeType - 1 : + op->opcodeType); + + if ((op->opcodeType == OPCODETYPE_FUNC1 || + op->opcodeType == OPCODETYPE_FUNC2 || + op->opcodeType == OPCODETYPE_FUNC3 || + op->opcodeType == OPCODETYPE_FUNCX)) + { + if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC) + { + functionType *fn_ptr = (functionType *)op->fn; + fname = fn_ptr->name; + } + else if (op->fntype == FUNCTYPE_EELFUNC) + { + fname = op->relname; + } + if (!fname) fname =""; + } + + switch (op->opcodeType) + { + case OPCODETYPE_DIRECTVALUE: + fprintf(fp," DV=%f\r\n",op->parms.dv.directValue); + break; + case OPCODETYPE_VALUE_FROM_NAMESPACENAME: // this.* or namespace.* are encoded this way + fprintf(fp," NSN=%s(%d)\r\n",op->relname?op->relname : "(null)",op->namespaceidx); + break; + case OPCODETYPE_VARPTR: + { + const char *nm = op->relname; + if (!nm || !*nm) + { + int wb; + for (wb = 0; wb < ctx->varTable_numBlocks; wb ++) + { + char **plist=ctx->varTable_Names[wb]; + if (!plist) break; + + if (op->parms.dv.valuePtr >= ctx->varTable_Values[wb] && op->parms.dv.valuePtr < ctx->varTable_Values[wb] + NSEEL_VARS_PER_BLOCK) + { + nm = plist[op->parms.dv.valuePtr - ctx->varTable_Values[wb]]; + break; + } + } + } + fprintf(fp," VP=%s\r\n", nm?nm : "(null)"); + } + break; + case OPCODETYPE_VARPTRPTR: + fprintf(fp, " VPP?\r\n"); + break; + case OPCODETYPE_FUNC1: + if (op->fntype == FN_NOT) + fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_not"); + else if (op->fntype == FN_NOTNOT) + fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_notnot"); + else if (op->fntype == FN_MEMORY) + fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_mem"); + else if (op->fntype == FN_GMEMORY) + fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_gmem"); + else if (op->fntype == FN_WHILE) + fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "while"); + else + fprintf(fp," FUNC1 %d %s {\r\n",op->fntype, fname); + + if (op->parms.parms[0]) + dumpOpcodeTree(ctx,fp,op->parms.parms[0],indent_amt+2); + else + fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,""); + fprintf(fp,"%*s}\r\n", indent_amt, ""); + break; + case OPCODETYPE_MOREPARAMS: + case OPCODETYPE_FUNC2: + if (op->opcodeType == OPCODETYPE_MOREPARAMS) + fprintf(fp," MOREPARAMS {\r\n"); + else + { + if (op->fntype == FN_POW) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "pow"); + else if (op->fntype == FN_MOD) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_mod"); + else if (op->fntype == FN_XOR) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_xor"); + else if (op->fntype == FN_SHL) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_shl"); + else if (op->fntype == FN_SHR) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_shr"); + else if (op->fntype == FN_LT) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_below"); + else if (op->fntype == FN_GT) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_above"); + else if (op->fntype == FN_LTE) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_beleq"); + else if (op->fntype == FN_GTE) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_aboeq"); + else if (op->fntype == FN_EQ) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_equal"); + else if (op->fntype == FN_NE) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_noteq"); + else if (op->fntype == FN_EQ_EXACT) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_equal_exact"); + else if (op->fntype == FN_NE_EXACT) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_noteq_exact"); + else if (op->fntype == FN_LOGICAL_AND) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_and"); + else if (op->fntype == FN_LOGICAL_OR) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_or"); + else if (op->fntype == FN_ASSIGN) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_set"); + else if (op->fntype == FN_ADD_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_addop"); + else if (op->fntype == FN_SUB_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_subop"); + else if (op->fntype == FN_MUL_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_mulop"); + else if (op->fntype == FN_DIV_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_divop"); + else if (op->fntype == FN_OR_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_orop"); + else if (op->fntype == FN_AND_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_andop"); + else if (op->fntype == FN_XOR_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_xorop"); + else if (op->fntype == FN_MOD_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_modop"); + else if (op->fntype == FN_POW_OP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_powop"); + else if (op->fntype == FN_LOOP) + fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "loop"); + else + fprintf(fp," FUNC2 %d %s {\r\n",op->fntype, fname); + } + if (op->parms.parms[0]) + dumpOpcodeTree(ctx,fp,op->parms.parms[0],indent_amt+2); + else + fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,""); + + if (op->parms.parms[1]) + dumpOpcodeTree(ctx,fp,op->parms.parms[1],indent_amt+2); + else + fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,""); + fprintf(fp,"%*s}\r\n", indent_amt, ""); + break; + case OPCODETYPE_FUNCX: + case OPCODETYPE_FUNC3: + if (op->opcodeType == OPCODETYPE_FUNCX) + fprintf(fp," FUNCX %d %s {\r\n",op->fntype, fname); + else if (op->fntype == FN_IF_ELSE) + fprintf(fp," FUNC3 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_if"); + else + fprintf(fp," FUNC3 %d %s {\r\n",op->fntype, fname); + if (op->parms.parms[0]) + dumpOpcodeTree(ctx,fp,op->parms.parms[0],indent_amt+2); + else + fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,""); + + if (op->parms.parms[1]) + dumpOpcodeTree(ctx,fp,op->parms.parms[1],indent_amt+2); + else + fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,""); + + if (op->parms.parms[2]) + dumpOpcodeTree(ctx,fp,op->parms.parms[2],indent_amt+2); + else + fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,""); + fprintf(fp,"%*s}\r\n", indent_amt, ""); + + break; + } +} + +#endif + +#ifdef GLUE_MAX_JMPSIZE +#define CHECK_SIZE_FORJMP(x,y) if ((x)<0 || (x)>=GLUE_MAX_JMPSIZE) goto y; +#define RET_MINUS1_FAIL_FALLBACK(err,j) goto j; +#else +#define CHECK_SIZE_FORJMP(x,y) +#define RET_MINUS1_FAIL_FALLBACK(err,j) RET_MINUS1_FAIL(err) +#endif +static int compileOpcodesInternal(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, int *calledRvType, int preferredReturnValues, int *fpStackUse, int *canHaveDenormalOutput) +{ + int rv_offset=0, denormal_force=-1; + if (!op) RET_MINUS1_FAIL("coi !op") + + *fpStackUse=0; + for (;;) + { + // special case: statement delimiting means we can process the left side into place, and iteratively do the second parameter without recursing + // also we don't need to save/restore anything to the stack (which the normal 2 parameter function processing does) + if (op->opcodeType == OPCODETYPE_FUNC2 && op->fntype == FN_JOIN_STATEMENTS) + { + int fUse1; + int parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_IGNORE, NULL,&fUse1,NULL); + if (parm_size < 0) RET_MINUS1_FAIL("coc join fail") + op = op->parms.parms[1]; + if (!op) RET_MINUS1_FAIL("join got to null") + + if (fUse1>*fpStackUse) *fpStackUse=fUse1; + if (bufOut) bufOut += parm_size; + bufOut_len -= parm_size; + rv_offset += parm_size; +#ifdef DUMP_OPS_DURING_COMPILE + if (op->opcodeType != OPCODETYPE_FUNC2 || op->fntype != FN_JOIN_STATEMENTS) dumpOp(ctx,op,0); +#endif + denormal_force=-1; + } + // special case: __denormal_likely(), __denormal_unlikely() + else if (op->opcodeType == OPCODETYPE_FUNC1 && (op->fntype == FN_DENORMAL_LIKELY || op->fntype == FN_DENORMAL_UNLIKELY)) + { + denormal_force = op->fntype == FN_DENORMAL_LIKELY; + op = op->parms.parms[0]; + } + else + { + break; + } + } + if (denormal_force >= 0 && canHaveDenormalOutput) + { + *canHaveDenormalOutput = denormal_force; + canHaveDenormalOutput = &denormal_force; // prevent it from being changed by functions below + } + + // special case: BAND/BOR + if (op->opcodeType == OPCODETYPE_FUNC2 && (op->fntype == FN_LOGICAL_AND || op->fntype == FN_LOGICAL_OR)) + { + int fUse1=0; + int parm_size; +#ifdef GLUE_MAX_JMPSIZE + int parm_size_pre; +#endif + int retType=RETURNVALUE_IGNORE; + if (preferredReturnValues != RETURNVALUE_IGNORE) retType = RETURNVALUE_BOOL; + + *calledRvType = retType; + + parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_BOOL, NULL, &fUse1, NULL); + if (parm_size < 0) RET_MINUS1_FAIL("loop band/bor coc fail") + + if (fUse1 > *fpStackUse) *fpStackUse=fUse1; + + +#ifdef GLUE_MAX_JMPSIZE + parm_size_pre=parm_size; +#endif + + { + int sz2, fUse2=0; + const int testsz=op->fntype == FN_LOGICAL_OR ? sizeof(GLUE_JMP_IF_P1_NZ) : sizeof(GLUE_JMP_IF_P1_Z); + if (bufOut_len < parm_size+testsz) RET_MINUS1_FAIL_FALLBACK("band/bor size fail",doNonInlinedAndOr_) + + if (bufOut) memcpy(bufOut+parm_size,op->fntype == FN_LOGICAL_OR ? GLUE_JMP_IF_P1_NZ : GLUE_JMP_IF_P1_Z,testsz); + parm_size += testsz; + + sz2 = compileOpcodes(ctx,op->parms.parms[1],bufOut?bufOut+parm_size:NULL,bufOut_len-parm_size, computTableSize, namespacePathToThis, retType, NULL,&fUse2, NULL); + + CHECK_SIZE_FORJMP(sz2,doNonInlinedAndOr_) + if (sz2<0) RET_MINUS1_FAIL("band/bor coc fail") + + if (bufOut) GLUE_JMP_SET_OFFSET(bufOut + parm_size, sz2); + parm_size+=sz2; + + if (fUse2 > *fpStackUse) *fpStackUse=fUse2; + return rv_offset + parm_size; + } +#ifdef GLUE_MAX_JMPSIZE + if (0) + { + void *stub; + int stubsize; + unsigned char *newblock2, *p; + + // encode as function call +doNonInlinedAndOr_: + parm_size = parm_size_pre; + + if (op->fntype == FN_LOGICAL_AND) + { + stub = GLUE_realAddress(nseel_asm_band,&stubsize); + } + else + { + stub = GLUE_realAddress(nseel_asm_bor,&stubsize); + } + + if (bufOut_len < parm_size + stubsize) RET_MINUS1_FAIL("band/bor len fail") + + if (bufOut) + { + int fUse2=0; + newblock2 = compileCodeBlockWithRet(ctx,op->parms.parms[1],computTableSize,namespacePathToThis, retType, NULL, &fUse2, NULL); + if (!newblock2) RET_MINUS1_FAIL("band/bor ccbwr fail") + + if (fUse2 > *fpStackUse) *fpStackUse=fUse2; + + p = bufOut + parm_size; + memcpy(p, stub, stubsize); + + p=EEL_GLUE_set_immediate(p,(INT_PTR)newblock2); + } + return rv_offset + parm_size + stubsize; + } +#endif + } + + if (op->opcodeType == OPCODETYPE_FUNC3 && op->fntype == FN_IF_ELSE) // special case: IF + { + int fUse1=0; +#ifdef GLUE_MAX_JMPSIZE + int parm_size_pre; +#endif + int use_rv = RETURNVALUE_IGNORE; + int rvMode=0; + int parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_BOOL|RETURNVALUE_BOOL_REVERSED, &rvMode,&fUse1, NULL); + if (parm_size < 0) RET_MINUS1_FAIL("if coc fail") + if (fUse1 > *fpStackUse) *fpStackUse=fUse1; + + if (preferredReturnValues & RETURNVALUE_NORMAL) use_rv=RETURNVALUE_NORMAL; + else if (preferredReturnValues & RETURNVALUE_FPSTACK) use_rv=RETURNVALUE_FPSTACK; + else if (preferredReturnValues & RETURNVALUE_BOOL) use_rv=RETURNVALUE_BOOL; + + *calledRvType = use_rv; +#ifdef GLUE_MAX_JMPSIZE + parm_size_pre = parm_size; +#endif + + { + int csz,hasSecondHalf; + if (rvMode & RETURNVALUE_BOOL_REVERSED) + { + if (bufOut_len < parm_size + (int)sizeof(GLUE_JMP_IF_P1_NZ)) RET_MINUS1_FAIL_FALLBACK("if size fail",doNonInlineIf_) + if (bufOut) memcpy(bufOut+parm_size,GLUE_JMP_IF_P1_NZ,sizeof(GLUE_JMP_IF_P1_NZ)); + parm_size += sizeof(GLUE_JMP_IF_P1_NZ); + } + else + { + if (bufOut_len < parm_size + (int)sizeof(GLUE_JMP_IF_P1_Z)) RET_MINUS1_FAIL_FALLBACK("if size fail",doNonInlineIf_) + if (bufOut) memcpy(bufOut+parm_size,GLUE_JMP_IF_P1_Z,sizeof(GLUE_JMP_IF_P1_Z)); + parm_size += sizeof(GLUE_JMP_IF_P1_Z); + } + csz=compileOpcodes(ctx,op->parms.parms[1],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, use_rv, NULL,&fUse1, canHaveDenormalOutput); + if (fUse1 > *fpStackUse) *fpStackUse=fUse1; + hasSecondHalf = preferredReturnValues || !OPCODE_IS_TRIVIAL(op->parms.parms[2]); + + CHECK_SIZE_FORJMP(csz,doNonInlineIf_) + if (csz<0) RET_MINUS1_FAIL("if coc fial") + + if (bufOut) GLUE_JMP_SET_OFFSET(bufOut + parm_size, csz + (hasSecondHalf?sizeof(GLUE_JMP_NC):0)); + parm_size+=csz; + + if (hasSecondHalf) + { + if (bufOut_len < parm_size + (int)sizeof(GLUE_JMP_NC)) RET_MINUS1_FAIL_FALLBACK("if len fail",doNonInlineIf_) + if (bufOut) memcpy(bufOut+parm_size,GLUE_JMP_NC,sizeof(GLUE_JMP_NC)); + parm_size+=sizeof(GLUE_JMP_NC); + + csz=compileOpcodes(ctx,op->parms.parms[2],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, use_rv, NULL, &fUse1, canHaveDenormalOutput); + + CHECK_SIZE_FORJMP(csz,doNonInlineIf_) + if (csz<0) RET_MINUS1_FAIL("if coc 2 fail") + + // update jump address + if (bufOut) GLUE_JMP_SET_OFFSET(bufOut + parm_size,csz); + parm_size+=csz; + if (fUse1 > *fpStackUse) *fpStackUse=fUse1; + } + return rv_offset + parm_size; + } +#ifdef GLUE_MAX_JMPSIZE + if (0) + { + unsigned char *newblock2,*newblock3,*ptr; + void *stub; + int stubsize; +doNonInlineIf_: + parm_size = parm_size_pre; + stub = GLUE_realAddress(nseel_asm_if,&stubsize); + + if (!stub || bufOut_len < parm_size + stubsize) RET_MINUS1_FAIL(stub ? "if sz fail" : "if addr fail") + + if (bufOut) + { + int fUse2=0; + newblock2 = compileCodeBlockWithRet(ctx,op->parms.parms[1],computTableSize,namespacePathToThis, use_rv, NULL,&fUse2, canHaveDenormalOutput); + if (fUse2 > *fpStackUse) *fpStackUse=fUse2; + newblock3 = compileCodeBlockWithRet(ctx,op->parms.parms[2],computTableSize,namespacePathToThis, use_rv, NULL,&fUse2, canHaveDenormalOutput); + if (fUse2 > *fpStackUse) *fpStackUse=fUse2; + if (!newblock2 || !newblock3) RET_MINUS1_FAIL("if subblock gen fail") + + ptr = bufOut + parm_size; + memcpy(ptr, stub, stubsize); + + ptr=EEL_GLUE_set_immediate(ptr,(INT_PTR)newblock2); + EEL_GLUE_set_immediate(ptr,(INT_PTR)newblock3); + } + return rv_offset + parm_size + stubsize; + } +#endif + } + + { + // special case: while + if (op->opcodeType == OPCODETYPE_FUNC1 && op->fntype == FN_WHILE) + { + *calledRvType = RETURNVALUE_BOOL; + +#ifndef GLUE_INLINE_LOOPS + // todo: PPC looping support when loop length is small enough + { + unsigned char *pwr=bufOut; + unsigned char *newblock2; + int stubsz; + void *stubfunc = GLUE_realAddress(nseel_asm_repeatwhile,&stubsz); + if (!stubfunc || bufOut_len < stubsz) RET_MINUS1_FAIL(stubfunc ? "repeatwhile size fail" :"repeatwhile addr fail") + + if (bufOut) + { + int fUse1 = 0; + newblock2=compileCodeBlockWithRet(ctx,op->parms.parms[0],computTableSize,namespacePathToThis, RETURNVALUE_BOOL, NULL, &fUse1, NULL); + if (!newblock2) RET_MINUS1_FAIL("repeatwhile ccbwr fail") + + if (fUse1 > *fpStackUse) *fpStackUse = fUse1; + memcpy(pwr,stubfunc,stubsz); + pwr=EEL_GLUE_set_immediate(pwr,(INT_PTR)newblock2); + } + + return rv_offset+stubsz; + } +#else + { +#ifndef GLUE_WHILE_END_NOJUMP + unsigned char *jzoutpt; +#endif + unsigned char *looppt; + int parm_size=0,subsz, fUse1=0; + if (bufOut_len < parm_size + (int)(GLUE_WHILE_SETUP_SIZE + sizeof(GLUE_WHILE_BEGIN))) RET_MINUS1_FAIL("while size fail 1") + + if (bufOut) memcpy(bufOut + parm_size,GLUE_WHILE_SETUP,GLUE_WHILE_SETUP_SIZE); + parm_size+=GLUE_WHILE_SETUP_SIZE; + + looppt = bufOut + parm_size; + if (bufOut) memcpy(bufOut + parm_size,GLUE_WHILE_BEGIN,sizeof(GLUE_WHILE_BEGIN)); + parm_size+=sizeof(GLUE_WHILE_BEGIN); + + subsz = compileOpcodes(ctx,op->parms.parms[0],bufOut ? (bufOut + parm_size) : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, RETURNVALUE_BOOL, NULL,&fUse1, NULL); + if (fUse1 > *fpStackUse) *fpStackUse = fUse1; + if (subsz<0) RET_MINUS1_FAIL("while coc fail") + + if (bufOut_len < parm_size + (int)(sizeof(GLUE_WHILE_END) + sizeof(GLUE_WHILE_CHECK_RV))) RET_MINUS1_FAIL("which size fial 2") + + parm_size+=subsz; + if (bufOut) memcpy(bufOut + parm_size, GLUE_WHILE_END, sizeof(GLUE_WHILE_END)); + parm_size+=sizeof(GLUE_WHILE_END); +#ifndef GLUE_WHILE_END_NOJUMP + jzoutpt = bufOut + parm_size; +#endif + + if (bufOut) memcpy(bufOut + parm_size, GLUE_WHILE_CHECK_RV, sizeof(GLUE_WHILE_CHECK_RV)); + parm_size+=sizeof(GLUE_WHILE_CHECK_RV); + if (bufOut) + { + GLUE_JMP_SET_OFFSET(bufOut + parm_size,(looppt - (bufOut+parm_size)) ); +#ifndef GLUE_WHILE_END_NOJUMP + GLUE_JMP_SET_OFFSET(jzoutpt, (bufOut + parm_size) - jzoutpt); +#endif + } + return rv_offset+parm_size; + } + +#endif + } + + // special case: loop + if (op->opcodeType == OPCODETYPE_FUNC2 && op->fntype == FN_LOOP) + { + int fUse1; + int parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_FPSTACK, NULL,&fUse1, NULL); + if (parm_size < 0) RET_MINUS1_FAIL("loop coc fail") + + *calledRvType = RETURNVALUE_BOOL; + if (fUse1 > *fpStackUse) *fpStackUse=fUse1; + +#ifndef GLUE_INLINE_LOOPS + // todo: PPC looping support when loop length is small enough + { + void *stub; + int stubsize; + unsigned char *newblock2, *p; + stub = GLUE_realAddress(nseel_asm_repeat,&stubsize); + if (bufOut_len < parm_size + stubsize) RET_MINUS1_FAIL("loop size fail") + if (bufOut) + { + newblock2 = compileCodeBlockWithRet(ctx,op->parms.parms[1],computTableSize,namespacePathToThis, RETURNVALUE_IGNORE, NULL,fpStackUse, NULL); + + p = bufOut + parm_size; + memcpy(p, stub, stubsize); + + p=EEL_GLUE_set_immediate(p,(INT_PTR)newblock2); + } + return rv_offset + parm_size + stubsize; + } +#else + { + int subsz; + int fUse2=0; + unsigned char *skipptr1,*loopdest; + +#ifndef GLUE_LOOP_LOADCNT_SIZE +#define GLUE_LOOP_LOADCNT_SIZE sizeof(GLUE_LOOP_LOADCNT) +#endif + if (bufOut_len < parm_size + (int)(GLUE_LOOP_LOADCNT_SIZE + GLUE_LOOP_CLAMPCNT_SIZE + GLUE_LOOP_BEGIN_SIZE)) RET_MINUS1_FAIL("loop size fail") + + // store, convert to int, compare against 1, if less than, skip to end + if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_LOADCNT,GLUE_LOOP_LOADCNT_SIZE); + parm_size += GLUE_LOOP_LOADCNT_SIZE; + skipptr1 = bufOut+parm_size; + + // compare aginst max loop length, jump to loop start if not above it + if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_CLAMPCNT,GLUE_LOOP_CLAMPCNT_SIZE); + parm_size += GLUE_LOOP_CLAMPCNT_SIZE; + + // loop code: + loopdest = bufOut + parm_size; + + if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_BEGIN,GLUE_LOOP_BEGIN_SIZE); + parm_size += GLUE_LOOP_BEGIN_SIZE; + + subsz = compileOpcodes(ctx,op->parms.parms[1],bufOut ? (bufOut + parm_size) : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, RETURNVALUE_IGNORE, NULL, &fUse2, NULL); + if (subsz<0) RET_MINUS1_FAIL("loop coc fail") + if (fUse2 > *fpStackUse) *fpStackUse=fUse2; + + parm_size += subsz; + + if (bufOut_len < parm_size + (int)sizeof(GLUE_LOOP_END)) RET_MINUS1_FAIL("loop size fail 2") + + if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_END,sizeof(GLUE_LOOP_END)); + parm_size += sizeof(GLUE_LOOP_END); + + if (bufOut) + { + GLUE_JMP_SET_OFFSET(bufOut + parm_size,loopdest - (bufOut+parm_size)); + GLUE_JMP_SET_OFFSET(skipptr1, (bufOut+parm_size) - skipptr1); + } + + return rv_offset + parm_size; + + } +#endif + } + } + + switch (op->opcodeType) + { + case OPCODETYPE_DIRECTVALUE: + if (preferredReturnValues == RETURNVALUE_BOOL) + { + int w = fabs(op->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; + int wsz=(w?sizeof(GLUE_SET_P1_NZ):sizeof(GLUE_SET_P1_Z)); + + *calledRvType = RETURNVALUE_BOOL; + if (bufOut_len < wsz) RET_MINUS1_FAIL("direct bool size fail3") + if (bufOut) memcpy(bufOut,w?GLUE_SET_P1_NZ:GLUE_SET_P1_Z,wsz); + return rv_offset+wsz; + } + else if (preferredReturnValues & RETURNVALUE_FPSTACK) + { +#ifdef GLUE_HAS_FLDZ + if (op->parms.dv.directValue == 0.0) + { +#if GLUE_MAX_FPSTACK_SIZE > 0 + if (*fpStackUse < 1) *fpStackUse = 1; +#endif + *calledRvType = RETURNVALUE_FPSTACK; + if (bufOut_len < sizeof(GLUE_FLDZ)) RET_MINUS1_FAIL("direct fp fail 1") + if (bufOut) memcpy(bufOut,GLUE_FLDZ,sizeof(GLUE_FLDZ)); + return rv_offset+sizeof(GLUE_FLDZ); + } +#endif +#ifdef GLUE_HAS_FLD1 + if (op->parms.dv.directValue == 1.0) + { +#if GLUE_MAX_FPSTACK_SIZE > 0 + if (*fpStackUse < 1) *fpStackUse = 1; +#endif + *calledRvType = RETURNVALUE_FPSTACK; + if (bufOut_len < sizeof(GLUE_FLD1)) RET_MINUS1_FAIL("direct fp fail 1") + if (bufOut) memcpy(bufOut,GLUE_FLD1,sizeof(GLUE_FLD1)); + return rv_offset+sizeof(GLUE_FLD1); + } +#endif + } + WDL_FALLTHROUGH; // fall through + case OPCODETYPE_DIRECTVALUE_TEMPSTRING: + case OPCODETYPE_VALUE_FROM_NAMESPACENAME: + case OPCODETYPE_VARPTR: + case OPCODETYPE_VARPTRPTR: + + #if GLUE_HAS_FPREG2 > 0 + if (preferredReturnValues & RETURNVALUE_FPREG2) + { + if (preferredReturnValues != RETURNVALUE_FPREG2) RET_MINUS1_FAIL("RETURNVALUE_FPREG2 but not exact hm") + if (bufOut_len < GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE) RET_MINUS1_FAIL("direct fp2 fail 2") + if (bufOut) + { + if (generateValueToReg(ctx,op,bufOut,-2,namespacePathToThis, 1 /*allow caching*/)<0) RET_MINUS1_FAIL("direct fp2 fail gvr") + } + *calledRvType = RETURNVALUE_FPREG2; + return rv_offset+GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE; + } + #endif + + #ifdef GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE + if (OPCODE_IS_TRIVIAL(op)) + { + if (preferredReturnValues & RETURNVALUE_FPSTACK) + { +#if GLUE_MAX_FPSTACK_SIZE > 0 + if (*fpStackUse < 1) *fpStackUse = 1; +#endif + if (bufOut_len < GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE) RET_MINUS1_FAIL("direct fp fail 2") + if (bufOut) + { + if (generateValueToReg(ctx,op,bufOut,-1,namespacePathToThis, 1 /*allow caching*/)<0) RET_MINUS1_FAIL("direct fp fail gvr") + } + *calledRvType = RETURNVALUE_FPSTACK; + return rv_offset+GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE; + } + } + #endif + + if (bufOut_len < GLUE_MOV_PX_DIRECTVALUE_SIZE) + { + RET_MINUS1_FAIL("direct value fail 1") + } + if (bufOut) + { + if (generateValueToReg(ctx,op,bufOut,0,namespacePathToThis, + (preferredReturnValues&(RETURNVALUE_FPSTACK|RETURNVALUE_CACHEABLE))!=0)<0) + { + RET_MINUS1_FAIL("direct value gvr fail3") + } + } + return rv_offset + GLUE_MOV_PX_DIRECTVALUE_SIZE; + + case OPCODETYPE_FUNCX: + case OPCODETYPE_FUNC1: + case OPCODETYPE_FUNC2: + case OPCODETYPE_FUNC3: + + if (op->fntype == FUNCTYPE_EELFUNC) + { + int a, fpUse1=0; + + a = compileEelFunctionCall(ctx,op,bufOut,bufOut_len,computTableSize,namespacePathToThis, calledRvType,&fpUse1,canHaveDenormalOutput); + if (a<0) return a; + if (fpUse1 > *fpStackUse) *fpStackUse = fpUse1; + rv_offset += a; + } + else + { + int a, fpUse1=0; + a = compileNativeFunctionCall(ctx,op,bufOut,bufOut_len,computTableSize,namespacePathToThis, calledRvType,&fpUse1,preferredReturnValues,canHaveDenormalOutput); + if (a<0)return a; + if (fpUse1 > *fpStackUse) *fpStackUse = fpUse1; + rv_offset += a; + } + return rv_offset; + } + + RET_MINUS1_FAIL("default opcode fail") +} + +#ifdef DUMP_OPS_DURING_COMPILE +FILE *g_debugfp; +int g_debugfp_indent; +int g_debugfp_histsz=0; + +void dumpOp(compileContext *ctx, opcodeRec *op, int start) +{ + if (start>=0) + { + if (g_debugfp) + { + static opcodeRec **hist; + + int x; + int hit=0; + if (!hist) hist = (opcodeRec**) calloc(1024,1024*sizeof(opcodeRec*)); + for(x=0;x=100) *(char *)1=0; + fprintf(g_debugfp,"%*s{ %p : %d%s: ",g_debugfp_indent," ",op,op->opcodeType, hit ? " -- DUPLICATE" : ""); + switch (op->opcodeType) + { + case OPCODETYPE_DIRECTVALUE: + fprintf(g_debugfp,"dv %f",op->parms.dv.directValue); + break; + case OPCODETYPE_VARPTR: + if (op->relname && op->relname[0]) + { + fprintf(g_debugfp,"var %s",op->relname); + } + else + { + int wb; + for (wb = 0; wb < ctx->varTable_numBlocks; wb ++) + { + char **plist=ctx->varTable_Names[wb]; + if (!plist) break; + + if (op->parms.dv.valuePtr >= ctx->varTable_Values[wb] && op->parms.dv.valuePtr < ctx->varTable_Values[wb] + NSEEL_VARS_PER_BLOCK) + { + fprintf(g_debugfp,"var %s",plist[op->parms.dv.valuePtr - ctx->varTable_Values[wb]]); + break; + } + } + } + break; + case OPCODETYPE_FUNC1: + case OPCODETYPE_FUNC2: + case OPCODETYPE_FUNC3: + case OPCODETYPE_FUNCX: + if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC) + { + functionType *p=(functionType*)op->fn; + fprintf(g_debugfp,"func %d: %s",p->nParams&0xff,p->name); + } + else + fprintf(g_debugfp,"sf %d",op->fntype); + break; + + } + fprintf(g_debugfp,"\n"); + g_debugfp_indent+=2; + } + } + else + { + if (g_debugfp) + { + g_debugfp_indent-=2; + fprintf(g_debugfp,"%*s}%p\n",g_debugfp_indent," ",op); + } + } +} +#endif + +int compileOpcodes(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, + int supportedReturnValues, int *rvType, int *fpStackUse, int *canHaveDenormalOutput) +{ + int code_returns=RETURNVALUE_NORMAL; + int fpsu=0; + int codesz; + int denorm=0; + +#ifdef DUMP_OPS_DURING_COMPILE + dumpOp(ctx,op,1); +#endif + + codesz = compileOpcodesInternal(ctx,op,bufOut,bufOut_len,computTableSize,namespacePathToThis,&code_returns, supportedReturnValues,&fpsu,&denorm); + if (denorm && canHaveDenormalOutput) *canHaveDenormalOutput=1; + +#ifdef DUMP_OPS_DURING_COMPILE + dumpOp(ctx,op,-1); +#endif +#ifdef EEL_DUMP_OPS + // dump opcode trees for verification, after optimizing + if (g_eel_dump_fp2) + { + fprintf(g_eel_dump_fp2,"-- compileOpcodes generated %d bytes of code!\r\n",codesz); + } +#endif + if (codesz < 0) return codesz; + +#if GLUE_HAS_FPREG2 > 0 + if (supportedReturnValues & RETURNVALUE_FPREG2) + { + if (code_returns != RETURNVALUE_FPREG2) + RET_MINUS1_FAIL("fpstack2 not return correct fail"); + } +#endif + + /* + { + wdl_log("opcode %d %d (%s): fpu use: %d\n",op->opcodeType,op->fntype, + op->opcodeType >= OPCODETYPE_FUNC1 && op->fntype == FUNCTYPE_FUNCTIONTYPEREC ? ( + ((functionType *)op->fn)->name + ) : "", + fpsu); + } + */ + + if (fpStackUse) *fpStackUse=fpsu; + + if (bufOut) bufOut += codesz; + bufOut_len -= codesz; + + + if (code_returns == RETURNVALUE_BOOL && !(supportedReturnValues & RETURNVALUE_BOOL) && supportedReturnValues) + { + int stubsize; + void *stub = GLUE_realAddress(nseel_asm_booltofp,&stubsize); + if (!stub || bufOut_len < stubsize) RET_MINUS1_FAIL(stub?"booltofp size":"booltfp addr") + if (bufOut) + { + memcpy(bufOut,stub,stubsize); + bufOut += stubsize; + } + codesz+=stubsize; + bufOut_len -= stubsize; + + code_returns = RETURNVALUE_FPSTACK; + } + + + // default processing of code_returns to meet return value requirements + if (supportedReturnValues & code_returns) + { + if (rvType) *rvType = code_returns; + return codesz; + } + + + if (rvType) *rvType = RETURNVALUE_IGNORE; + + + if (code_returns == RETURNVALUE_NORMAL) + { + if (supportedReturnValues & (RETURNVALUE_FPSTACK|RETURNVALUE_BOOL)) + { + if (bufOut_len < GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE) RET_MINUS1_FAIL("pushvalatpxtofpstack,size") + if (bufOut) + { + GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(bufOut,0); // always fld qword [eax] but we might change that later + bufOut += GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE; + } + codesz += GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE; + bufOut_len -= GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE; + + if (supportedReturnValues & RETURNVALUE_BOOL) + { + code_returns = RETURNVALUE_FPSTACK; + } + else + { + if (rvType) *rvType = RETURNVALUE_FPSTACK; + } + } + } + + if (code_returns == RETURNVALUE_FPSTACK) + { + if (supportedReturnValues & (RETURNVALUE_BOOL|RETURNVALUE_BOOL_REVERSED)) + { + int stubsize; + void *stub; + + if (supportedReturnValues & RETURNVALUE_BOOL_REVERSED) + { + if (rvType) *rvType = RETURNVALUE_BOOL_REVERSED; + stub = GLUE_realAddress(nseel_asm_fptobool_rev,&stubsize); + } + else + { + if (rvType) *rvType = RETURNVALUE_BOOL; + stub = GLUE_realAddress(nseel_asm_fptobool,&stubsize); + } + + + if (!stub || bufOut_len < stubsize) RET_MINUS1_FAIL(stub?"fptobool size":"fptobool addr") + if (bufOut) + { + memcpy(bufOut,stub,stubsize); + bufOut += stubsize; + } + codesz+=stubsize; + bufOut_len -= stubsize; + } + else if (supportedReturnValues & RETURNVALUE_NORMAL) + { + if (computTableSize) (*computTableSize) ++; + + if (bufOut_len < GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE) RET_MINUS1_FAIL("popfpstacktowtptopxsize") + + // generate fp-pop to temp space + if (bufOut) GLUE_POP_FPSTACK_TO_WTP_TO_PX(bufOut,0); + codesz+=GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE; + if (rvType) *rvType = RETURNVALUE_NORMAL; + } + else + { + // toss return value that will be ignored + if (bufOut_len < GLUE_POP_FPSTACK_SIZE) RET_MINUS1_FAIL("popfpstack size") + if (bufOut) memcpy(bufOut,GLUE_POP_FPSTACK,GLUE_POP_FPSTACK_SIZE); + codesz+=GLUE_POP_FPSTACK_SIZE; + } + } + + return codesz; +} + + +#if 0 +static void movestringover(char *str, int amount) +{ + char tmp[1024+8]; + + int l=(int)strlen(str); + l=wdl_min(1024-amount-1,l); + + memcpy(tmp,str,l+1); + + while (l >= 0 && tmp[l]!='\n') l--; + l++; + + tmp[l]=0;//ensure we null terminate + + memcpy(str+amount,tmp,l+1); +} +#endif + +//------------------------------------------------------------------------------ +NSEEL_CODEHANDLE NSEEL_code_compile(NSEEL_VMCTX _ctx, const char *_expression, int lineoffs) +{ + return NSEEL_code_compile_ex(_ctx,_expression,lineoffs,0); +} + +typedef struct topLevelCodeSegmentRec { + struct topLevelCodeSegmentRec *_next; + void *code; + int codesz; + int tmptable_use; +} topLevelCodeSegmentRec; + + +NSEEL_CODEHANDLE NSEEL_code_compile_ex(NSEEL_VMCTX _ctx, const char *_expression, int lineoffs, int compile_flags) +{ + compileContext *ctx = (compileContext *)_ctx; + const char *endptr; + const char *_expression_end; + codeHandleType *handle; + topLevelCodeSegmentRec *startpts_tail=NULL; + topLevelCodeSegmentRec *startpts=NULL; + _codeHandleFunctionRec *oldCommonFunctionList; + int curtabptr_sz=0; + void *curtabptr=NULL; + int had_err=0; + + if (!ctx) return 0; + + ctx->directValueCache=0; + ctx->optimizeDisableFlags=0; + ctx->gotEndOfInput=0; + ctx->current_compile_flags = compile_flags; + + if (compile_flags & NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS_RESET) + { + ctx->functions_common=NULL; // reset common function list + } + else + { + // reset common compiled function code, forcing a recompile if shared + _codeHandleFunctionRec *a = ctx->functions_common; + while (a) + { + _codeHandleFunctionRec *b = a->derivedCopies; + + if (a->localstorage) + { + // force local storage actual values to be reallocated if used again + memset(a->localstorage,0,sizeof(EEL_F *) * a->localstorage_size); + } + + a->startptr = NULL; // force this copy to be recompiled + a->startptr_size = -1; + + while (b) + { + b->startptr = NULL; // force derived copies to get recompiled + b->startptr_size = -1; + // no need to reset b->localstorage, since it points to a->localstorage + b=b->derivedCopies; + } + + a=a->next; + } + } + + ctx->last_error_string[0]=0; + + if (!_expression || !*_expression) return 0; + + _expression_end = _expression + strlen(_expression); + + oldCommonFunctionList = ctx->functions_common; + + ctx->isGeneratingCommonFunction=0; + ctx->isSharedFunctions = !!(compile_flags & NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); + ctx->functions_local = NULL; + + freeBlocks(&ctx->tmpblocks,0); + freeBlocks(&ctx->blocks_head_code,1); + freeBlocks(&ctx->blocks_head_data,0); + memset(ctx->l_stats,0,sizeof(ctx->l_stats)); + + handle = (codeHandleType*)newDataBlock(sizeof(codeHandleType),8); + + if (!handle) + { + return 0; + } + + + memset(handle,0,sizeof(codeHandleType)); + + ctx->l_stats[0] += (int)(_expression_end - _expression); + ctx->tmpCodeHandle = handle; + endptr=_expression; + + while (*endptr) + { + int computTableTop = 0; + int startptr_size=0; + void *startptr=NULL; + opcodeRec *start_opcode=NULL; + const char *expr=endptr; + + int function_numparms=0; + char is_fname[NSEEL_MAX_VARIABLE_NAMELEN+1]; + is_fname[0]=0; + + memset(ctx->function_localTable_Size,0,sizeof(ctx->function_localTable_Size)); + memset(ctx->function_localTable_Names,0,sizeof(ctx->function_localTable_Names)); + ctx->function_localTable_ValuePtrs=0; + ctx->function_usesNamespaces=0; + ctx->function_curName=NULL; + ctx->function_globalFlag=0; + + ctx->errVar=0; + + // single out top level segment + { + int had_something = 0, pcnt=0, pcnt2=0; + int state=0; + for (;;) + { + int l; + const char *p=nseel_simple_tokenizer(&endptr,_expression_end,&l,&state); + if (!p) + { + if (pcnt || pcnt2) ctx->gotEndOfInput|=4; + break; + } + + if (*p == ';') + { + if (had_something && !pcnt && !pcnt2) break; + } + else if (*p == '/' && l > 1 && (p[1] == '/' || p[1] == '*')) + { + if (l > 19 && !strnicmp(p,"//#eel-no-optimize:",19)) + ctx->optimizeDisableFlags = atoi(p+19); + } + else + { + if (!had_something) + { + expr = p; + had_something = 1; + } + + if (*p == '(') pcnt++; + else if (*p == ')') { if (--pcnt<0) pcnt=0; } + else if (*p == '[') pcnt2++; + else if (*p == ']') { if (--pcnt2<0) pcnt2=0; } + } + } + if (!*expr || !had_something) break; + } + + // parse + + { + int tmplen,funcname_len; + const char *p = expr; + const char *tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL); + const char *funcname = nseel_simple_tokenizer(&p,endptr,&funcname_len,NULL); + if (tok1 && funcname && tmplen == 8 && !strnicmp(tok1,"function",8) && (isalpha((unsigned char)funcname[0]) || funcname[0] == '_')) + { + int had_parms_locals=0; + if (funcname_len > sizeof(is_fname)-1) funcname_len=sizeof(is_fname)-1; + memcpy(is_fname, funcname, funcname_len); + is_fname[funcname_len]=0; + ctx->function_curName = is_fname; // only assigned for the duration of the loop, cleared later //-V507 + + while (NULL != (tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL))) + { + int is_parms = 0, localTableContext = 0; + int maxcnt=0; + const char *sp_save; + + if (tok1[0] == '(') + { + if (had_parms_locals) + { + expr = p-1; // begin compilation at this code! + break; + } + is_parms = 1; + } + else + { + if (tmplen == 5 && !strnicmp(tok1,"local",tmplen)) localTableContext=0; + else if (tmplen == 6 && !strnicmp(tok1,"static",tmplen)) localTableContext=0; + else if (tmplen == 8 && !strnicmp(tok1,"instance",tmplen)) localTableContext=1; + else if ((tmplen == 7 && !strnicmp(tok1,"globals",tmplen)) || + (tmplen == 6 && !strnicmp(tok1,"global",tmplen))) + { + ctx->function_globalFlag = 1; + localTableContext=2; + } + else break; // unknown token! + + tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL); + if (!tok1 || tok1[0] != '(') break; + } + had_parms_locals = 1; + + + sp_save=p; + + while (NULL != (tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL))) + { + if (tok1[0] == ')') break; + if (*tok1 == '#' && localTableContext!=1 && localTableContext!=2) + { + ctx->errVar = (int) (tok1 - _expression); + lstrcpyn_safe(ctx->last_error_string,"#string can only be in instance() or globals()",sizeof(ctx->last_error_string)); + goto had_error; + } + + if (isalpha((unsigned char)*tok1) || *tok1 == '_' || *tok1 == '#') + { + maxcnt++; + if (p < endptr && *p == '*') + { + if (!is_parms && localTableContext!=2) + { + ctx->errVar = (int) (p - _expression); + lstrcpyn_safe(ctx->last_error_string,"namespace* can only be used in parameters or globals()",sizeof(ctx->last_error_string)); + goto had_error; + } + p++; + } + } + else if (*tok1 != ',') + { + ctx->errVar = (int)(tok1 - _expression); + lstrcpyn_safe(ctx->last_error_string,"unknown character in function parameters",sizeof(ctx->last_error_string)); + goto had_error; + } + } + + if (tok1 && maxcnt > 0) + { + char **ot = ctx->function_localTable_Names[localTableContext]; + const int osz = ctx->function_localTable_Size[localTableContext]; + + maxcnt += osz; + + ctx->function_localTable_Names[localTableContext] = (char **)newTmpBlock(ctx,sizeof(char *) * maxcnt); + + if (ctx->function_localTable_Names[localTableContext]) + { + int i=osz; + if (osz && ot) memcpy(ctx->function_localTable_Names[localTableContext],ot,sizeof(char *) * osz); + p=sp_save; + + while (NULL != (tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL))) + { + if (tok1[0] == ')') break; + if (isalpha((unsigned char)*tok1) || *tok1 == '_' || *tok1 == '#') + { + char *newstr; + int l = tmplen; + if (*p == '*') // xyz* for namespace + { + p++; + l++; + } + if (l > NSEEL_MAX_VARIABLE_NAMELEN) l = NSEEL_MAX_VARIABLE_NAMELEN; + newstr = newTmpBlock(ctx,l+1); + if (newstr) + { + memcpy(newstr,tok1,l); + newstr[l]=0; + ctx->function_localTable_Names[localTableContext][i++] = newstr; + } + } + } + ctx->function_localTable_Size[localTableContext]=i; + if (is_parms) function_numparms = i; + } + } + } + } + } + if (ctx->function_localTable_Size[0]>0) + { + ctx->function_localTable_ValuePtrs = + ctx->isSharedFunctions ? newDataBlock(ctx->function_localTable_Size[0] * sizeof(EEL_F *),8) : + newTmpBlock(ctx,ctx->function_localTable_Size[0] * sizeof(EEL_F *)); + if (!ctx->function_localTable_ValuePtrs) + { + ctx->function_localTable_Size[0]=0; + function_numparms=0; + } + else + { + memset(ctx->function_localTable_ValuePtrs,0,sizeof(EEL_F *) * ctx->function_localTable_Size[0]); // force values to be allocated + } + } + + { + int nseelparse(compileContext* context); + void nseelrestart (void *input_file ,void *yyscanner ); + + ctx->rdbuf_start = _expression; + +#ifdef NSEEL_SUPER_MINIMAL_LEXER + + ctx->rdbuf = expr; + ctx->rdbuf_end = endptr; + if (!nseelparse(ctx) && !ctx->errVar) + { + start_opcode = ctx->result; + } +#else + + nseelrestart(NULL,ctx->scanner); + + ctx->rdbuf = expr; + ctx->rdbuf_end = endptr; + + if (!nseelparse(ctx) && !ctx->errVar) + { + start_opcode = ctx->result; + } + if (ctx->errVar) + { + const char *p=expr; + ctx->errVar += expr-_expression; + } +#endif + ctx->rdbuf = NULL; + } + + if (start_opcode) + { + int rvMode=0, fUse=0; + +#ifdef LOG_OPT + int sd=0; + wdl_log("pre opt sz=%d (tsackDepth=%d)\n",compileOpcodes(ctx,start_opcode,NULL,1024*1024*256,NULL, NULL,RETURNVALUE_IGNORE,NULL,&sd,NULL),sd); +#endif + +#ifdef EEL_DUMP_OPS + // dump opcode trees for verification, before optimizing + if (g_eel_dump_fp) + { + fprintf(g_eel_dump_fp,"-- opcode chunk --\r\n"); + dumpOpcodeTree(ctx,g_eel_dump_fp,start_opcode,2); + } +#endif + + if (!(ctx->optimizeDisableFlags&OPTFLAG_NO_OPTIMIZE)) optimizeOpcodes(ctx,start_opcode,is_fname[0] ? 1 : 0); +#ifdef LOG_OPT + wdl_log("post opt sz=%d, stack depth=%d\n",compileOpcodes(ctx,start_opcode,NULL,1024*1024*256,NULL,NULL, RETURNVALUE_IGNORE,NULL,&sd,NULL),sd); +#endif + +#ifdef EEL_DUMP_OPS + // dump opcode trees for verification, after optimizing + if (g_eel_dump_fp2) + { + fprintf(g_eel_dump_fp2,"-- POST-OPTIMIZED opcode chunk --\r\n"); + dumpOpcodeTree(ctx,g_eel_dump_fp2,start_opcode,2); + } +#endif + + if (is_fname[0]) + { + _codeHandleFunctionRec *fr = ctx->isSharedFunctions ? newDataBlock(sizeof(_codeHandleFunctionRec),8) : + newTmpBlock(ctx,sizeof(_codeHandleFunctionRec)); + if (fr) + { + memset(fr,0,sizeof(_codeHandleFunctionRec)); + fr->startptr_size = -1; + fr->opcodes = start_opcode; + + if (ctx->function_localTable_Size[0] > 0 && ctx->function_localTable_ValuePtrs) + { + if (ctx->function_localTable_Names[0]) + { + int i; + for(i=0;ifunction_localTable_Names[0][i]; + if (nptr && *nptr && nptr[strlen(nptr)-1] == '*') + { + fr->parameterAsNamespaceMask |= ((unsigned int)1)<num_params=function_numparms; + fr->localstorage = ctx->function_localTable_ValuePtrs; + fr->localstorage_size = ctx->function_localTable_Size[0]; + } + + fr->usesNamespaces = ctx->function_usesNamespaces; + fr->isCommonFunction = ctx->isSharedFunctions; + + lstrcpyn_safe(fr->fname,is_fname,sizeof(fr->fname)); + + if (ctx->isSharedFunctions) + { + fr->next = ctx->functions_common; + ctx->functions_common = fr; + } + else + { + fr->next = ctx->functions_local; + ctx->functions_local = fr; + } + } + continue; + } + +#ifdef DUMP_OPS_DURING_COMPILE + g_debugfp_indent=0; + g_debugfp_histsz=0; + g_debugfp = fopen("C:/temp/foo.txt","w"); +#endif + startptr_size = compileOpcodes(ctx,start_opcode,NULL,1024*1024*256,NULL, NULL, + is_fname[0] ? (RETURNVALUE_NORMAL|RETURNVALUE_FPSTACK) : RETURNVALUE_IGNORE, &rvMode, &fUse, NULL); // if not a function, force return value as address (avoid having to pop it ourselves + // if a function, allow the code to decide how return values are generated + +#ifdef DUMP_OPS_DURING_COMPILE + if (g_debugfp) fclose(g_debugfp); + g_debugfp=0; +#endif + + + if (!startptr_size) continue; // optimized away + if (startptr_size>0) + { + startptr = newTmpBlock(ctx,startptr_size); + if (startptr) + { + startptr_size=compileOpcodes(ctx,start_opcode,(unsigned char*)startptr,startptr_size,&computTableTop, NULL, RETURNVALUE_IGNORE, NULL,NULL, NULL); + if (startptr_size<=0) startptr = NULL; + + } + } + } + + if (!startptr) + { +had_error: +#ifdef NSEEL_EEL1_COMPAT_MODE + continue; + +#else + //if (!ctx->last_error_string[0]) + { + int byteoffs = ctx->errVar; + int linenumber; + char cur_err[sizeof(ctx->last_error_string)]; + lstrcpyn_safe(cur_err,ctx->last_error_string,sizeof(cur_err)); + if (cur_err[0]) lstrcatn(cur_err,": ",sizeof(cur_err)); + else lstrcpyn_safe(cur_err,"syntax error: ",sizeof(cur_err)); + + if (_expression + byteoffs >= _expression_end) + { + if (ctx->gotEndOfInput&4) byteoffs = (int)(expr-_expression); + else byteoffs=(int)(_expression_end-_expression); + } + + if (byteoffs < 0) byteoffs=0; + + linenumber=findLineNumber(_expression,byteoffs)+1; + + if (ctx->gotEndOfInput&4) + { + snprintf(ctx->last_error_string,sizeof(ctx->last_error_string),"%d: %.200smissing ) or ]",linenumber+lineoffs,cur_err); + } + else + { + const char *p = _expression + byteoffs; + int x=1, right_amt_nospace=0, left_amt_nospace=0; + while (x < 32 && p-x >= _expression && p[-x] != '\r' && p[-x] != '\n') + { + if (!isspace((unsigned char)p[-x])) left_amt_nospace=x; + x++; + } + x=0; + while (x < 60 && p[x] && p[x] != '\r' && p[x] != '\n') + { + if (!isspace((unsigned char)p[x])) right_amt_nospace=x+1; + x++; + } + + // display left_amt >>>> right_amt_nospace + snprintf(ctx->last_error_string,sizeof(ctx->last_error_string),"%d: %.200s'%.*s %.*s'",linenumber+lineoffs,cur_err, + left_amt_nospace, p-left_amt_nospace, + right_amt_nospace ? right_amt_nospace : 5,right_amt_nospace ? p : ""); + } + } + + startpts=NULL; + startpts_tail=NULL; + had_err=1; + break; +#endif + } + + if (!is_fname[0]) // redundant check (if is_fname[0] is set and we succeeded, it should continue) + // but we'll be on the safe side + { + topLevelCodeSegmentRec *p = newTmpBlock(ctx,sizeof(topLevelCodeSegmentRec)); + p->_next=0; + p->code = startptr; + p->codesz = startptr_size; + p->tmptable_use = computTableTop; + + if (!startpts_tail) startpts_tail=startpts=p; + else + { + startpts_tail->_next=p; + startpts_tail=p; + } + + if (curtabptr_sz < computTableTop) + { + curtabptr_sz=computTableTop; + } + } + } + + memset(ctx->function_localTable_Size,0,sizeof(ctx->function_localTable_Size)); + memset(ctx->function_localTable_Names,0,sizeof(ctx->function_localTable_Names)); + ctx->function_localTable_ValuePtrs=0; + ctx->function_usesNamespaces=0; + ctx->function_curName=NULL; + ctx->function_globalFlag=0; + + ctx->tmpCodeHandle = NULL; + + if (handle->want_stack) + { + if (!handle->stack) startpts=NULL; + } + + if (startpts) + { + curtabptr_sz += 2; // many functions use the worktable for temporary storage of up to 2 EEL_F's + + handle->workTable_size = curtabptr_sz; + handle->workTable = curtabptr = newDataBlock((curtabptr_sz+MIN_COMPUTABLE_SIZE + COMPUTABLE_EXTRA_SPACE) * sizeof(EEL_F),32); + +#ifdef EEL_VALIDATE_WORKTABLE_USE + if (curtabptr) memset(curtabptr,0x3a,(curtabptr_sz+MIN_COMPUTABLE_SIZE + COMPUTABLE_EXTRA_SPACE) * sizeof(EEL_F)); +#endif + if (!curtabptr) startpts=NULL; + } + + + if (startpts || (!had_err && (compile_flags & NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS))) + { + unsigned char *writeptr; + topLevelCodeSegmentRec *p=startpts; + int size=sizeof(GLUE_RET)+GLUE_FUNC_ENTER_SIZE+GLUE_FUNC_LEAVE_SIZE; // for ret at end :) + int wtpos=0; + + // now we build one big code segment out of our list of them, inserting a mov esi, computable before each item as necessary + while (p) + { + if (wtpos <= 0) + { + wtpos=MIN_COMPUTABLE_SIZE; + size += GLUE_RESET_WTP(NULL,0); + } + size+=p->codesz; + wtpos -= p->tmptable_use; + p=p->_next; + } + handle->code = newCodeBlock(size,32); + if (handle->code) + { + writeptr=(unsigned char *)handle->code; + #if GLUE_FUNC_ENTER_SIZE > 0 + memcpy(writeptr,&GLUE_FUNC_ENTER,GLUE_FUNC_ENTER_SIZE); + writeptr += GLUE_FUNC_ENTER_SIZE; + #endif + p=startpts; + wtpos=0; + while (p) + { + if (wtpos <= 0) + { + wtpos=MIN_COMPUTABLE_SIZE; + writeptr+=GLUE_RESET_WTP(writeptr,curtabptr); + } + memcpy(writeptr,(char*)p->code,p->codesz); + writeptr += p->codesz; + wtpos -= p->tmptable_use; + + p=p->_next; + } + #if GLUE_FUNC_LEAVE_SIZE > 0 + memcpy(writeptr,&GLUE_FUNC_LEAVE,GLUE_FUNC_LEAVE_SIZE); + writeptr += GLUE_FUNC_LEAVE_SIZE; + #endif + memcpy(writeptr,&GLUE_RET,sizeof(GLUE_RET)); writeptr += sizeof(GLUE_RET); + ctx->l_stats[1]=size; + handle->code_size = (int) (writeptr - (unsigned char *)handle->code); +#if defined(__arm__) || defined(__aarch64__) + __clear_cache(handle->code,writeptr); +#endif + } + + handle->blocks_code = ctx->blocks_head_code; +#ifndef EEL_DOESNT_NEED_EXEC_PERMS + eel_set_blocks_allow_execute(handle->blocks_code,1); +#endif + handle->blocks_data = ctx->blocks_head_data; + ctx->blocks_head_code=0; + ctx->blocks_head_data=0; + } + else + { + // failed compiling, or failed calloc() + handle=NULL; + } + + + ctx->directValueCache=0; + ctx->functions_local = NULL; + + ctx->isGeneratingCommonFunction=0; + ctx->isSharedFunctions=0; + + freeBlocks(&ctx->tmpblocks,0); + freeBlocks(&ctx->blocks_head_code,1); + freeBlocks(&ctx->blocks_head_data,0); + + if (handle) + { + handle->compile_flags = compile_flags; + handle->ramPtr = ctx->ram_state->blocks; + memcpy(handle->code_stats,ctx->l_stats,sizeof(ctx->l_stats)); + nseel_evallib_stats[0]+=ctx->l_stats[0]; + nseel_evallib_stats[1]+=ctx->l_stats[1]; + nseel_evallib_stats[2]+=ctx->l_stats[2]; + nseel_evallib_stats[3]+=ctx->l_stats[3]; + nseel_evallib_stats[4]++; + } + else + { + ctx->functions_common = oldCommonFunctionList; // failed compiling, remove any added common functions from the list + + // remove any derived copies of functions due to error, since we may have added some that have been freed + while (oldCommonFunctionList) + { + oldCommonFunctionList->derivedCopies=NULL; + oldCommonFunctionList=oldCommonFunctionList->next; + } + } + memset(ctx->l_stats,0,sizeof(ctx->l_stats)); + + return (NSEEL_CODEHANDLE)handle; +} + +//------------------------------------------------------------------------------ +void NSEEL_code_execute(NSEEL_CODEHANDLE code) +{ +#ifndef GLUE_TABPTR_IGNORED + INT_PTR tabptr; +#endif + INT_PTR codeptr; + codeHandleType *h = (codeHandleType *)code; + if (!h || !h->code) return; + + codeptr = (INT_PTR) h->code; +#if 0 + { + unsigned int *p=(unsigned int *)codeptr; + while (*p != GLUE_RET[0]) + { + printf("instr:%04X:%04X\n",*p>>16,*p&0xffff); + p++; + } + } +#endif + +#ifndef GLUE_TABPTR_IGNORED + tabptr=(INT_PTR)h->workTable; +#endif + //printf("calling code!\n"); + GLUE_CALL_CODE(tabptr,codeptr,(INT_PTR)h->ramPtr); + +} + +int NSEEL_code_geterror_flag(NSEEL_VMCTX ctx) +{ + compileContext *c=(compileContext *)ctx; + if (c) return (c->gotEndOfInput ? 1 : 0); + return 0; +} + +char *NSEEL_code_getcodeerror(NSEEL_VMCTX ctx) +{ + compileContext *c=(compileContext *)ctx; + if (ctx && c->last_error_string[0]) return c->last_error_string; + return 0; +} + +//------------------------------------------------------------------------------ +void NSEEL_code_free(NSEEL_CODEHANDLE code) +{ + codeHandleType *h = (codeHandleType *)code; + if (h != NULL) + { +#ifdef EEL_VALIDATE_WORKTABLE_USE + if (h->workTable) + { + char *p = ((char*)h->workTable) + h->workTable_size*sizeof(EEL_F); + int x; + for(x=COMPUTABLE_EXTRA_SPACE*sizeof(EEL_F) - 1;x >= 0; x --) + if (p[x] != 0x3a) + { + wdl_log("worktable overrun at byte %d (wts=%d), value = %f\n",x,h->workTable_size, *(EEL_F*)(p+(x&~(sizeof(EEL_F)-1)))); + break; + } + } +#endif + + nseel_evallib_stats[0]-=h->code_stats[0]; + nseel_evallib_stats[1]-=h->code_stats[1]; + nseel_evallib_stats[2]-=h->code_stats[2]; + nseel_evallib_stats[3]-=h->code_stats[3]; + nseel_evallib_stats[4]--; + + freeBlocks(&h->blocks_code,1); + freeBlocks(&h->blocks_data,0); + } + +} + +//------------------------------------------------------------------------------ + +NSEEL_VMCTX NSEEL_VM_alloc() // return a handle +{ + compileContext *ctx=calloc(1,sizeof(compileContext)); + + #ifdef NSEEL_SUPER_MINIMAL_LEXER + if (ctx) ctx->scanner = ctx; + #else + if (ctx) + { + int nseellex_init(void ** ptr_yy_globals); + void nseelset_extra(void *user_defined , void *yyscanner); + if (nseellex_init(&ctx->scanner)) + { + free(ctx); + return NULL; + } + nseelset_extra(ctx,ctx->scanner); + } + #endif + + if (ctx) + { + ctx->ram_state = __newBlock_align(&ctx->ctx_pblocks,sizeof(*ctx->ram_state),16,0); + memset(ctx->ram_state,0,sizeof(*ctx->ram_state)); + ctx->ram_state->sign_mask[0] = ctx->ram_state->sign_mask[1] = WDL_UINT64_CONST(0x8000000000000000); + ctx->ram_state->abs_mask[0] = ctx->ram_state->abs_mask[1] = WDL_UINT64_CONST(0x7FFFFFFFFFFFFFFF); + ctx->ram_state->maxblocks = NSEEL_RAM_BLOCKS_DEFAULTMAX; + ctx->ram_state->closefact = NSEEL_CLOSEFACTOR; + } + return ctx; +} + +int NSEEL_VM_setramsize(NSEEL_VMCTX _ctx, int maxent) +{ + compileContext *ctx = (compileContext *)_ctx; + if (!ctx) return 0; + if (maxent > 0) + { + maxent = (maxent + NSEEL_RAM_ITEMSPERBLOCK - 1)/NSEEL_RAM_ITEMSPERBLOCK; + if (maxent > NSEEL_RAM_BLOCKS) maxent = NSEEL_RAM_BLOCKS; + ctx->ram_state->maxblocks = maxent; + } + + return ctx->ram_state->maxblocks * NSEEL_RAM_ITEMSPERBLOCK; +} + +void NSEEL_VM_preallocram(NSEEL_VMCTX _ctx, int maxent) +{ + compileContext *ctx = (compileContext *)_ctx; + int x; + if (!ctx || !maxent) return; + + if (maxent < 0) + { + maxent = ctx->ram_state->maxblocks; + } + else + { + maxent = (maxent + NSEEL_RAM_ITEMSPERBLOCK - 1)/NSEEL_RAM_ITEMSPERBLOCK; + if (maxent > ctx->ram_state->maxblocks) maxent = ctx->ram_state->maxblocks; + } + for (x = 0; x < maxent; x ++) + __NSEEL_RAMAlloc(ctx->ram_state->blocks,x * NSEEL_RAM_ITEMSPERBLOCK); +} + +void NSEEL_VM_SetFunctionValidator(NSEEL_VMCTX _ctx, const char * (*validateFunc)(const char *fn_name, void *user), void *user) +{ + if (_ctx) + { + compileContext *ctx = (compileContext *)_ctx; + ctx->func_check = validateFunc; + ctx->func_check_user = user; + } +} + +void NSEEL_VM_SetFunctionTable(NSEEL_VMCTX _ctx, eel_function_table *tab) +{ + if (_ctx) + { + compileContext *ctx = (compileContext *)_ctx; + ctx->registered_func_tab = tab; + } +} +void NSEEL_VM_free(NSEEL_VMCTX _ctx) // free when done with a VM and ALL of its code have been freed, as well +{ + + if (_ctx) + { + compileContext *ctx=(compileContext *)_ctx; + EEL_GROWBUF_RESIZE(&ctx->varNameList,-1); + NSEEL_VM_freeRAM(_ctx); + + freeBlocks(&ctx->ctx_pblocks,0); + + // these should be 0 normally but just in case + freeBlocks(&ctx->tmpblocks,0); + freeBlocks(&ctx->blocks_head_code,1); + freeBlocks(&ctx->blocks_head_data,0); + + + #ifndef NSEEL_SUPER_MINIMAL_LEXER + if (ctx->scanner) + { + int nseellex_destroy(void *yyscanner); + nseellex_destroy(ctx->scanner); + } + #endif + ctx->scanner=0; + if (ctx->has_used_global_vars) + { + nseel_globalVarItem *p = NULL; + NSEEL_HOSTSTUB_EnterMutex(); + if (--nseel_vms_referencing_globallist_cnt == 0) + { + // clear and free globals + p = nseel_globalreg_list; + nseel_globalreg_list=0; + } + NSEEL_HOSTSTUB_LeaveMutex(); + + while (p) + { + nseel_globalVarItem *op = p; + p=p->_next; + free(op); + } + } + free(ctx); + } + +} + +int *NSEEL_code_getstats(NSEEL_CODEHANDLE code) +{ + codeHandleType *h = (codeHandleType *)code; + if (h) + { + return h->code_stats; + } + return 0; +} + +void NSEEL_VM_SetStringFunc(NSEEL_VMCTX ctx, + EEL_F (*onString)(void *caller_this, struct eelStringSegmentRec *list), + EEL_F (*onNamedString)(void *caller_this, const char *name)) +{ + if (ctx) + { + compileContext *c=(compileContext*)ctx; + c->onString = onString; + c->onNamedString = onNamedString; + } +} + +void NSEEL_VM_SetCustomFuncThis(NSEEL_VMCTX ctx, void *thisptr) +{ + if (ctx) + { + compileContext *c=(compileContext*)ctx; + c->caller_this=thisptr; + } +} + + + + + +void *NSEEL_PProc_RAM(void *data, int data_size, compileContext *ctx) +{ + if (data_size>0) data=EEL_GLUE_set_immediate(data, (INT_PTR)ctx->ram_state->blocks); + return data; +} + +void *NSEEL_PProc_THIS(void *data, int data_size, compileContext *ctx) +{ + if (data_size>0) data=EEL_GLUE_set_immediate(data, (INT_PTR)ctx->caller_this); + return data; +} + +static int vartable_lowerbound(compileContext *ctx, const char *name, int *ismatch) +{ + int a = 0, c = EEL_GROWBUF_GET_SIZE(&ctx->varNameList); + varNameRec **list = EEL_GROWBUF_GET(&ctx->varNameList); + while (a != c) + { + const int b = (a+c)/2; + const int cmp = strnicmp(name,list[b]->str,NSEEL_MAX_VARIABLE_NAMELEN); + if (cmp > 0) a = b+1; + else if (cmp < 0) c = b; + else + { + *ismatch = 1; + return b; + } + } + *ismatch = 0; + return a; +} + +static void vartable_cull_list(compileContext *ctx, int refcnt_chk) +{ + const int ni = EEL_GROWBUF_GET_SIZE(&ctx->varNameList); + int i = ni, ndel = 0; + varNameRec **rd = EEL_GROWBUF_GET(&ctx->varNameList), **wr=rd; + while (i--) + { + varNameRec *v = rd[0]; + if ((!refcnt_chk || !v->refcnt) && !v->isreg) + { + ndel++; + } + else + { + if (wr != rd) *wr = *rd; + wr++; + } + rd++; + } + if (ndel) EEL_GROWBUF_RESIZE(&ctx->varNameList,ni - ndel); +} + +void NSEEL_VM_remove_unused_vars(NSEEL_VMCTX _ctx) +{ + compileContext *ctx = (compileContext *)_ctx; + if (ctx) vartable_cull_list(ctx,1); +} + +void NSEEL_VM_remove_all_nonreg_vars(NSEEL_VMCTX _ctx) +{ + compileContext *ctx = (compileContext *)_ctx; + if (ctx) vartable_cull_list(ctx,0); +} + +void NSEEL_VM_clear_var_refcnts(NSEEL_VMCTX _ctx) +{ + compileContext *ctx = (compileContext *)_ctx; + if (ctx) + { + int i = EEL_GROWBUF_GET_SIZE(&ctx->varNameList); + varNameRec **rd = EEL_GROWBUF_GET(&ctx->varNameList); + while (i--) + { + rd[0]->refcnt=0; + rd++; + } + } +} + + +#ifdef NSEEL_EEL1_COMPAT_MODE +static EEL_F __nseel_global_regs[100]; +double *NSEEL_getglobalregs() { return __nseel_global_regs; } +#endif + +EEL_F *get_global_var(compileContext *ctx, const char *gv, int addIfNotPresent) +{ + nseel_globalVarItem *p; +#ifdef NSEEL_EEL1_COMPAT_MODE + if (!strnicmp(gv,"reg",3) && gv[3]>='0' && gv[3] <= '9' && gv[4] >= '0' && gv[4] <= '9' && !gv[5]) + { + return __nseel_global_regs + atoi(gv+3); + } +#endif + + NSEEL_HOSTSTUB_EnterMutex(); + if (!ctx->has_used_global_vars) + { + ctx->has_used_global_vars++; + nseel_vms_referencing_globallist_cnt++; + } + + p = nseel_globalreg_list; + while (p) + { + if (!stricmp(p->name,gv)) break; + p=p->_next; + } + + if (!p && addIfNotPresent) + { + size_t gvl = strlen(gv); + p = (nseel_globalVarItem*)malloc(sizeof(nseel_globalVarItem) + gvl); + if (p) + { + p->data=0.0; + strcpy(p->name,gv); + p->_next = nseel_globalreg_list; + nseel_globalreg_list=p; + } + } + NSEEL_HOSTSTUB_LeaveMutex(); + return p ? &p->data : NULL; +} + + + +EEL_F *nseel_int_register_var(compileContext *ctx, const char *name, int isReg, const char **namePtrOut) +{ + int slot, match; + + if (isReg == 0 && ctx->getVariable) + { + EEL_F *ret = ctx->getVariable(ctx->getVariable_userctx, name); + if (ret) return ret; + } + + if (!strnicmp(name,"_global.",8) && name[8]) + { + EEL_F *a=get_global_var(ctx,name+8,isReg >= 0); + if (a) return a; + } + + slot = vartable_lowerbound(ctx,name, &match); + if (match) + { + varNameRec *v = EEL_GROWBUF_GET(&ctx->varNameList)[slot]; + if (isReg >= 0) + { + v->refcnt++; + if (isReg) v->isreg=isReg; + if (namePtrOut) *namePtrOut = v->str; + } + return v->value; + } + if (isReg < 0) return NULL; + + if (ctx->varValueStore_left<1) + { + const int sz=500; + ctx->varValueStore_left = sz; + ctx->varValueStore = (EEL_F *)newCtxDataBlock((int)sizeof(EEL_F)*sz,8); + } + if (ctx->varValueStore) + { + int listsz = EEL_GROWBUF_GET_SIZE(&ctx->varNameList); + size_t l = strlen(name); + varNameRec *vh; + if (l > NSEEL_MAX_VARIABLE_NAMELEN) l = NSEEL_MAX_VARIABLE_NAMELEN; + vh = (varNameRec*) newCtxDataBlock( (int) (sizeof(varNameRec) + l),8); + if (!vh || EEL_GROWBUF_RESIZE(&ctx->varNameList, (listsz+1))) return NULL; // alloc fail + + (vh->value = ctx->varValueStore++)[0]=0.0; + ctx->varValueStore_left--; + + vh->refcnt=1; + vh->isreg=isReg; + memcpy(vh->str,name,l); + vh->str[l] = 0; + if (namePtrOut) *namePtrOut = vh->str; + + if (slot < listsz) + { + memmove(EEL_GROWBUF_GET(&ctx->varNameList) + slot+1, + EEL_GROWBUF_GET(&ctx->varNameList) + slot, (listsz - slot) * sizeof(EEL_GROWBUF_GET(&ctx->varNameList)[0])); + } + EEL_GROWBUF_GET(&ctx->varNameList)[slot] = vh; + + return vh->value; + } + return NULL; +} + + +//------------------------------------------------------------------------------ + +void NSEEL_VM_enumallvars(NSEEL_VMCTX ctx, int (*func)(const char *name, EEL_F *val, void *ctx), void *userctx) +{ + compileContext *tctx = (compileContext *) ctx; + int ni; + varNameRec **rd; + if (!tctx) return; + + ni = EEL_GROWBUF_GET_SIZE(&tctx->varNameList); + rd = EEL_GROWBUF_GET(&tctx->varNameList); + while (ni--) + { + if (!func(rd[0]->str,rd[0]->value,userctx)) break; + rd++; + } +} + + +//------------------------------------------------------------------------------ +EEL_F *NSEEL_VM_regvar(NSEEL_VMCTX _ctx, const char *var) +{ + compileContext *ctx = (compileContext *)_ctx; + if (!ctx) return 0; + + if (!strnicmp(var,"reg",3) && strlen(var) == 5 && isdigit(var[3]) && isdigit(var[4])) + { + EEL_F *a=get_global_var(ctx,var,1); + if (a) return a; + } + + return nseel_int_register_var(ctx,var,1,NULL); +} + +EEL_F *NSEEL_VM_getvar(NSEEL_VMCTX _ctx, const char *var) +{ + compileContext *ctx = (compileContext *)_ctx; + if (!ctx) return 0; + + if (!strnicmp(var,"reg",3) && strlen(var) == 5 && isdigit(var[3]) && isdigit(var[4])) + { + EEL_F *a=get_global_var(ctx,var,0); + if (a) return a; + } + + return nseel_int_register_var(ctx,var,-1,NULL); +} + +int NSEEL_VM_get_var_refcnt(NSEEL_VMCTX _ctx, const char *name) +{ + compileContext *ctx = (compileContext *)_ctx; + int slot,match; + if (!ctx) return -1; + slot = vartable_lowerbound(ctx,name, &match); + return match ? EEL_GROWBUF_GET(&ctx->varNameList)[slot]->refcnt : -1; +} + + + + +opcodeRec *nseel_createFunctionByName(compileContext *ctx, const char *name, int np, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3) +{ + int chkamt=0; + functionType *f=nseel_getFunctionByName(ctx,name,&chkamt); + if (f) while (chkamt-->=0) + { + if ((f->nParams&FUNCTIONTYPE_PARAMETERCOUNTMASK) == np) + { + opcodeRec *o=newOpCode(ctx,NULL, np==3?OPCODETYPE_FUNC3:np==2?OPCODETYPE_FUNC2:OPCODETYPE_FUNC1); + if (o) + { + o->fntype = FUNCTYPE_FUNCTIONTYPEREC; + o->fn = f; + o->parms.parms[0]=code1; + o->parms.parms[1]=code2; + o->parms.parms[2]=code3; + } + return o; + } + f++; + if (stricmp(f->name,name)) break; + } + return NULL; +} + + + + +//------------------------------------------------------------------------------ +opcodeRec *nseel_translate(compileContext *ctx, const char *tmp, size_t tmplen) // tmplen 0 = null term +{ + // this depends on the string being nul terminated eventually, tmplen is used more as a hint than anything else + if ((tmp[0] == '0' || tmp[0] == '$') && toupper(tmp[1])=='X') + { + char *p; + return nseel_createCompiledValue(ctx,(EEL_F)strtoul(tmp+2,&p,16)); + } + else if (tmp[0] == '$') + { + if (tmp[1] == '~') + { + char *p=(char*)tmp+2; + unsigned int v=(unsigned int) strtoul(tmp+2,&p,10); + if (v>53) v=53; + return nseel_createCompiledValue(ctx,(EEL_F)((((WDL_INT64)1) << v) - 1)); + } + else if (!tmplen ? !stricmp(tmp,"$E") : (tmplen == 2 && !strnicmp(tmp,"$E",2))) + return nseel_createCompiledValue(ctx,(EEL_F)2.718281828459045); + else if (!tmplen ? !stricmp(tmp, "$PI") : (tmplen == 3 && !strnicmp(tmp, "$PI", 3))) + return nseel_createCompiledValue(ctx,(EEL_F)3.141592653589793); + else if (!tmplen ? !stricmp(tmp, "$PHI") : (tmplen == 4 && !strnicmp(tmp, "$PHI", 4))) + return nseel_createCompiledValue(ctx,(EEL_F)1.6180339887498948); + else if ((!tmplen || tmplen == 4) && tmp[1] == '\'' && tmp[2] && tmp[3] == '\'') + return nseel_createCompiledValue(ctx,(EEL_F)tmp[2]); + else return NULL; + } + else if (tmp[0] == '\'') + { + char b[64]; + int x,sz; + unsigned int rv=0; + + if (!tmplen) // nul terminated tmplen, calculate a workable length + { + // faster than strlen(tmp) if tmp is large, we'll never need more than ~18 chars anyway + while (tmplen < 32 && tmp[tmplen]) tmplen++; + } + + sz = tmplen > 0 ? nseel_filter_escaped_string(b,sizeof(b),tmp+1, tmplen - 1, '\'') : 0; + + if (sz > 4) + { + if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string)); + snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"multi-byte character '%.5s...' too long",b); + return NULL; // do not allow 'xyzxy', limit to 4 bytes + } + + for (x=0;x sizeof(buf)-1) tmplen = sizeof(buf)-1; + memcpy(buf,tmp,tmplen); + buf[tmplen]=0; + if (ctx->onNamedString) + { + if (tmplen>0 && buf[1]&&ctx->function_curName) + { + int err=0; + opcodeRec *r = nseel_resolve_named_symbol(ctx,nseel_createCompiledValuePtr(ctx,NULL,buf),-1, &err); + if (r) + { + if (r->opcodeType!=OPCODETYPE_VALUE_FROM_NAMESPACENAME) + { + r->opcodeType = OPCODETYPE_DIRECTVALUE; + r->parms.dv.directValue = ctx->onNamedString(ctx->caller_this,buf+1); + r->parms.dv.valuePtr=NULL; + } + return r; + } + if (err) return NULL; + } + + // if not namespaced symbol, return directly + if (!buf[1]) + { + opcodeRec *r=newOpCode(ctx,NULL,OPCODETYPE_DIRECTVALUE_TEMPSTRING); + if (r) r->parms.dv.directValue = -10000.0; + return r; + } + return nseel_createCompiledValue(ctx,ctx->onNamedString(ctx->caller_this,buf+1)); + } + } + return nseel_createCompiledValue(ctx,(EEL_F)atof(tmp)); +} + +void NSEEL_VM_set_var_resolver(NSEEL_VMCTX _ctx, EEL_F *(*res)(void *userctx, const char *name), void *userctx) +{ + compileContext *ctx = (compileContext *)_ctx; + if (ctx) + { + ctx->getVariable = res; + ctx->getVariable_userctx = userctx; + } +} + + +#if defined(__ppc__) || defined(EEL_TARGET_PORTABLE) + // blank stubs + void eel_enterfp(int s[2]) {} + void eel_leavefp(int s[2]) {} +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-eval.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-eval.c new file mode 100644 index 0000000..f92028b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-eval.c @@ -0,0 +1,469 @@ +/* + Expression Evaluator Library (NS-EEL) v2 + Copyright (C) 2004-2013 Cockos Incorporated + Copyright (C) 1999-2003 Nullsoft, Inc. + + nseel-eval.c + + 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. +*/ + +#include +#include +#include "ns-eel-int.h" +#include "../wdlcstring.h" + + +static const char *nseel_skip_space_and_comments(const char *p, const char *endptr) +{ + for (;;) + { + while (p < endptr && isspace((unsigned char)p[0])) p++; + if (p >= endptr-1 || *p != '/') return p; + + if (p[1]=='/') + { + while (p < endptr && *p != '\r' && *p != '\n') p++; + } + else if (p[1] == '*') + { + p+=2; + while (p < endptr-1 && (p[0] != '*' || p[1] != '/')) p++; + p+=2; + if (p>=endptr) return endptr; + } + else return p; + } +} + +// removes any escaped characters, also will convert pairs delim_char into single delim_chars +int nseel_filter_escaped_string(char *outbuf, int outbuf_sz, const char *rdptr, size_t rdptr_size, char delim_char) +{ + int outpos = 0; + const char *rdptr_end = rdptr + rdptr_size; + while (rdptr < rdptr_end && outpos < outbuf_sz-1) + { + char thisc=*rdptr; + if (thisc == '\\' && rdptr < rdptr_end-1) + { + const char nc = rdptr[1]; + if (nc == 'r' || nc == 'R') { thisc = '\r'; } + else if (nc == 'n' || nc == 'N') { thisc = '\n'; } + else if (nc == 't' || nc == 'T') { thisc = '\t'; } + else if (nc == 'b' || nc == 'B') { thisc = '\b'; } + else if ((nc >= '0' && nc <= '9') || nc == 'x' || nc == 'X') + { + unsigned char c=0; + char base_shift = 3; + char num_top = '7'; + + rdptr++; // skip backslash + if (nc > '9') // implies xX + { + base_shift = 4; + num_top = '9'; + rdptr ++; // skip x + } + + while (rdptr < rdptr_end) + { + char tc=*rdptr; + if (tc >= '0' && tc <= num_top) + { + c = (c<= 'a' && tc <= 'f') + { + c = (c<= 'A' && tc <= 'F') + { + c = (c<str_len; + } + else if (list->str_len > 1) + { + if (pos >= bufout_sz) break; + pos += nseel_filter_escaped_string(bufOut + pos, bufout_sz-pos, list->str_start+1, list->str_len-1, list->str_start[0]); + } + list = list->_next; + } + return pos; +} + + + +// state can be NULL, it will be set if finished with unterminated thing: 1 for multiline comment, ' or " for string +const char *nseel_simple_tokenizer(const char **ptr, const char *endptr, int *lenOut, int *state) +{ + const char *p = *ptr; + const char *rv = p; + char delim; + + if (state) // if state set, returns comments as tokens + { + if (*state == 1) goto in_comment; + + #ifndef NSEEL_EEL1_COMPAT_MODE + if (*state == '\'' || *state == '\"') + { + delim = (char)*state; + goto in_string; + } + #endif + + // skip any whitespace + while (p < endptr && isspace((unsigned char)p[0])) p++; + } + else + { + // state not passed, skip comments (do not return them as tokens) + p = nseel_skip_space_and_comments(p,endptr); + } + + if (p >= endptr) + { + *ptr = endptr; + *lenOut = 0; + return NULL; + } + + rv=p; + + if (*p == '$' && p+3 < endptr && p[1] == '\'' && p[3] == '\'') + { + p+=4; + } + else if (state && *p == '/' && p < endptr-1 && (p[1] == '/' || p[1] == '*')) + { + if (p[1] == '/') + { + while (p < endptr && *p != '\r' && *p != '\n') p++; // advance to end of line + } + else + { + if (state) *state=1; + p+=2; +in_comment: + while (p < endptr) + { + const char c = *p++; + if (c == '*' && p < endptr && *p == '/') + { + p++; + if (state) *state=0; + break; + } + } + + } + } + else if (isalnum((unsigned char)*p) || *p == '_' || *p == '#' || *p == '$' || (*p == '.' && p < endptr-1 && p[1] >= '0' && p[1] <= '9')) + { + if (*p == '$' && p < endptr-1 && p[1] == '~') p++; + p++; + while (p < endptr && (isalnum((unsigned char)*p) || *p == '_' || *p == '.')) p++; + } +#ifndef NSEEL_EEL1_COMPAT_MODE + else if (*p == '\'' || *p == '\"') + { + delim = *p++; + if (state) *state=delim; +in_string: + + while (p < endptr) + { + const char c = *p++; + if (p < endptr && c == '\\') p++; // skip escaped characters + else if (c == delim) + { + if (state) *state=0; + break; + } + } + } +#endif + else + { + p++; + } + *ptr = p; + *lenOut = (int) (p - rv); + return p>rv ? rv : NULL; +} + + + +#ifdef NSEEL_SUPER_MINIMAL_LEXER + + int nseellex(opcodeRec **output, YYLTYPE * yylloc_param, compileContext *scctx) + { + int rv=0,toklen=0; + const char *rdptr = scctx->rdbuf; + const char *endptr = scctx->rdbuf_end; + const char *tok = nseel_simple_tokenizer(&rdptr,endptr,&toklen,NULL); + *output = 0; + if (tok) + { + rv = tok[0]; + if ((rv == '0' || rv == '$') && toklen > 1 && (tok[1] == 'x' || tok[1] == 'X')) // 0xf00 or $xf00 + { + int x; + for (x = 2; x < toklen; x ++) + if (!((tok[x] >= '0' && tok[x] <= '9') || + (tok[x] >= 'a' && tok[x] <= 'f') || + (tok[x] >= 'A' && tok[x] <= 'F'))) + { + tok += x; + break; + } + + *output = x == toklen && toklen > 2 ? nseel_translate(scctx,tok,toklen) : NULL; + if (*output) rv=VALUE; + } +#ifndef NSEEL_EEL1_COMPAT_MODE + else if (rv == '#' && scctx->onNamedString) + { + *output = nseel_translate(scctx,tok,toklen); + if (*output) rv=STRING_IDENTIFIER; + } + else if (rv == '\'') + { + if (toklen > 1 && tok[toklen-1] == '\'') + { + *output = nseel_translate(scctx, tok, toklen); + if (*output) rv = VALUE; + } + else scctx->gotEndOfInput|=8; + } + else if (rv == '\"' && scctx->onString) + { + if (toklen > 1 && tok[toklen-1] == '\"') + { + *output = (opcodeRec *)nseel_createStringSegmentRec(scctx,tok,toklen); + if (*output) rv = STRING_LITERAL; + } + else scctx->gotEndOfInput|=16; + } +#endif + else if (isalpha((unsigned char)rv) || rv == '_') + { + char buf[NSEEL_MAX_VARIABLE_NAMELEN*2]; + if (toklen > sizeof(buf) - 1) toklen=sizeof(buf) - 1; + memcpy(buf,tok,toklen); + buf[toklen]=0; + *output = nseel_createCompiledValuePtr(scctx, NULL, buf); + if (*output) rv = IDENTIFIER; + } + else if ((rv >= '0' && rv <= '9') || (rv == '.' && toklen > 1 && tok[1] >= '0' && tok[1] <= '9')) // 123.45 or .45 + { + int x, pcnt = 0; + for (x = 0; x < toklen; x ++) + { + if (tok[x] == '.' ? (++pcnt > 1) : (tok[x] < '0' || tok[x] > '9')) + { + tok += x; + break; + } + } + *output = x == toklen ? nseel_translate(scctx,tok,toklen) : NULL; + if (*output) rv=VALUE; + } + else if (rv == '$' && toklen > 1) + { + if (tok[1] == '~') + { + int x; + for (x = 2; x < toklen; x ++) + if (tok[x] < '0' || tok[x] > '9') + { + tok += x; + break; + } + + if (x 1 ? nseel_translate(scctx,tok,toklen) : NULL; + if (*output) rv=VALUE; + } + else if (rv == '<') + { + const char nc=*rdptr; + if (nc == '<') + { + rdptr++; + rv=TOKEN_SHL; + } + else if (nc == '=') + { + rdptr++; + rv=TOKEN_LTE; + } + } + else if (rv == '>') + { + const char nc=*rdptr; + if (nc == '>') + { + rdptr++; + rv=TOKEN_SHR; + } + else if (nc == '=') + { + rdptr++; + rv=TOKEN_GTE; + } + } + else if (rv == '&' && *rdptr == '&') + { + rdptr++; + rv = TOKEN_LOGICAL_AND; + } + else if (rv == '|' && *rdptr == '|') + { + rdptr++; + rv = TOKEN_LOGICAL_OR; + } + else if (*rdptr == '=') + { + switch (rv) + { + case '+': rv=TOKEN_ADD_OP; rdptr++; break; + case '-': rv=TOKEN_SUB_OP; rdptr++; break; + case '%': rv=TOKEN_MOD_OP; rdptr++; break; + case '|': rv=TOKEN_OR_OP; rdptr++; break; + case '&': rv=TOKEN_AND_OP; rdptr++; break; + case '~': rv=TOKEN_XOR_OP; rdptr++; break; + case '/': rv=TOKEN_DIV_OP; rdptr++; break; + case '*': rv=TOKEN_MUL_OP; rdptr++; break; + case '^': rv=TOKEN_POW_OP; rdptr++; break; + case '!': + rdptr++; + if (rdptr < endptr && *rdptr == '=') + { + rdptr++; + rv=TOKEN_NE_EXACT; + } + else + rv=TOKEN_NE; + break; + case '=': + rdptr++; + if (rdptr < endptr && *rdptr == '=') + { + rdptr++; + rv=TOKEN_EQ_EXACT; + } + else + rv=TOKEN_EQ; + break; + } + } + } + + scctx->rdbuf = rdptr; + yylloc_param->first_column = (int)(tok - scctx->rdbuf_start); + return rv; + } + + + void nseelerror(YYLTYPE *pos,compileContext *ctx, const char *str) + { + ctx->errVar=pos->first_column>0?pos->first_column:(int)(ctx->rdbuf_end - ctx->rdbuf_start); + } + + +#else + + int nseel_gets(compileContext *ctx, char *buf, size_t sz) + { + int n=0; + const char *endptr = ctx->rdbuf_end; + const char *rdptr = ctx->rdbuf; + if (!rdptr) return 0; + + while (n < sz && rdptr < endptr) buf[n++] = *rdptr++; + ctx->rdbuf=rdptr; + return n; + + } + + + //#define EEL_TRACE_LEX + + #ifdef EEL_TRACE_LEX + #define nseellex nseellex2 + + #endif + #include "lex.nseel.c" + + #ifdef EEL_TRACE_LEX + + #undef nseellex + + int nseellex(YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) + { + int a=nseellex2(yylval_param,yylloc_param,yyscanner); + + wdl_log("tok: %c (%d)\n",a,a); + return a; + } + #endif//EEL_TRACE_LEX + + + void nseelerror(YYLTYPE *pos,compileContext *ctx, const char *str) + { + ctx->errVar=pos->first_column>0?pos->first_column:(int)(ctx->rdbuf_end - ctx->rdbuf_start); + } +#endif // !NSEEL_SUPER_MINIMAL_LEXER diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-lextab.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-lextab.c new file mode 100644 index 0000000..6138b65 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-lextab.c @@ -0,0 +1 @@ +// no longer used \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-ram.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-ram.c new file mode 100644 index 0000000..ccc8580 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-ram.c @@ -0,0 +1,580 @@ +/* + Expression Evaluator Library (NS-EEL) v2 + Copyright (C) 2004-2013 Cockos Incorporated + Copyright (C) 1999-2003 Nullsoft, 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. +*/ + +#include "ns-eel.h" +#include "ns-eel-int.h" +#include +#include +#include +#include + + +#ifdef _WIN32 +#include +#ifdef _MSC_VER +#define inline __inline +#endif + +#endif + +unsigned int NSEEL_RAM_limitmem=0; +unsigned int NSEEL_RAM_memused=0; +int NSEEL_RAM_memused_errors=0; + + + +int NSEEL_VM_wantfreeRAM(NSEEL_VMCTX ctx) +{ + if (ctx) + { + compileContext *c=(compileContext*)ctx; + if (c->ram_state->needfree) + return 1; + } + return 0; +} + +void NSEEL_VM_freeRAMIfCodeRequested(NSEEL_VMCTX ctx) // check to see if our free flag was set +{ + if (ctx) + { + compileContext *c=(compileContext*)ctx; + if (c->ram_state->needfree) + { + NSEEL_HOSTSTUB_EnterMutex(); + { + INT_PTR startpos=((INT_PTR)c->ram_state->needfree)-1; + EEL_F **blocks = c->ram_state->blocks; + INT_PTR pos=0; + int x; + for (x = 0; x < NSEEL_RAM_BLOCKS; x ++) + { + if (pos >= startpos) + { + if (blocks[x]) + { + if (NSEEL_RAM_memused >= sizeof(EEL_F) * NSEEL_RAM_ITEMSPERBLOCK) + NSEEL_RAM_memused -= sizeof(EEL_F) * NSEEL_RAM_ITEMSPERBLOCK; + else NSEEL_RAM_memused_errors++; + free(blocks[x]); + blocks[x]=0; + } + } + pos+=NSEEL_RAM_ITEMSPERBLOCK; + } + c->ram_state->needfree=0; + } + NSEEL_HOSTSTUB_LeaveMutex(); + } + + } +} + +EEL_F nseel_ramalloc_onfail; +EEL_F * volatile nseel_gmembuf_default; + + +void *(*nseel_gmem_calloc)(size_t a, size_t b); + +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAMAllocGMEM(EEL_F ***blocks, unsigned int w) +{ + if (blocks) + { + EEL_F **pblocks=*blocks; + + if (w < NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK) + { + const unsigned int whichblock = w/NSEEL_RAM_ITEMSPERBLOCK; + EEL_F *p=NULL; + if (!pblocks || !(p=pblocks[whichblock])) + { + NSEEL_HOSTSTUB_EnterMutex(); + if (!nseel_gmem_calloc) nseel_gmem_calloc=calloc; + + if (!(pblocks=*blocks)) pblocks = *blocks = (EEL_F **)nseel_gmem_calloc(sizeof(EEL_F *),NSEEL_RAM_BLOCKS); + else p = pblocks[whichblock]; + + if (!p && pblocks) + { + p=pblocks[whichblock]=(EEL_F *)nseel_gmem_calloc(sizeof(EEL_F),NSEEL_RAM_ITEMSPERBLOCK); + } + NSEEL_HOSTSTUB_LeaveMutex(); + } + if (p) return p + (w&(NSEEL_RAM_ITEMSPERBLOCK-1)); + } + return &nseel_ramalloc_onfail; + } + + if (!nseel_gmembuf_default) + { + NSEEL_HOSTSTUB_EnterMutex(); + if (!nseel_gmembuf_default) nseel_gmembuf_default=(EEL_F*)calloc(sizeof(EEL_F),NSEEL_SHARED_GRAM_SIZE); + NSEEL_HOSTSTUB_LeaveMutex(); + if (!nseel_gmembuf_default) return &nseel_ramalloc_onfail; + } + + return nseel_gmembuf_default+(((unsigned int)w)&((NSEEL_SHARED_GRAM_SIZE)-1)); +} + + +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAMAlloc(EEL_F **pblocks, unsigned int w) +{ +// fprintf(stderr,"got request at %d, %d\n",w/NSEEL_RAM_ITEMSPERBLOCK, w&(NSEEL_RAM_ITEMSPERBLOCK-1)); + if (w < NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK) + { + unsigned int whichblock = w/NSEEL_RAM_ITEMSPERBLOCK; + EEL_F *p=pblocks[whichblock]; + if (!p && whichblock < ((unsigned int *)pblocks)[-3]) // pblocks -1/-2 are closefact, -3 is maxblocks + { + NSEEL_HOSTSTUB_EnterMutex(); + + if (!(p=pblocks[whichblock])) + { + + const int msize=sizeof(EEL_F) * NSEEL_RAM_ITEMSPERBLOCK; + if (!NSEEL_RAM_limitmem || NSEEL_RAM_memused+msize < NSEEL_RAM_limitmem) + { + p=pblocks[whichblock]=(EEL_F *)calloc(sizeof(EEL_F),NSEEL_RAM_ITEMSPERBLOCK); + if (p) NSEEL_RAM_memused+=msize; + } + } + NSEEL_HOSTSTUB_LeaveMutex(); + } + if (p) return p + (w&(NSEEL_RAM_ITEMSPERBLOCK-1)); + } +// fprintf(stderr,"ret 0\n"); + return &nseel_ramalloc_onfail; +} + + +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemFree(void *blocks, EEL_F *which) +{ + // blocks points to ram_state->blocks, so back it up past closefact and maxblocks to needfree + int *flag = (int *)((char *)blocks - sizeof(double) - 2*sizeof(int)); + int d=(int)(*which); + if (d < 0) d=0; + if (d < flag[1]*NSEEL_RAM_ITEMSPERBLOCK) flag[0]=1+d; + return which; +} + +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemTop(void *blocks, EEL_F *which) +{ + // blocks points to ram_state->blocks, so back it up past closefact to maxblocks + const int *flag = (int *)((char *)blocks - sizeof(double) - sizeof(int)); + *which = flag[0]*NSEEL_RAM_ITEMSPERBLOCK; + return which; +} + + +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_MemInsertShuffle(EEL_F **blocks,EEL_F *buf, EEL_F *lenptr, EEL_F *value) +{ + int src_offs = (int)*buf; + int len = (int)*lenptr; + int copy_len; + + EEL_F ret = *value; + + unsigned int sbidx = (unsigned int)src_offs / NSEEL_RAM_ITEMSPERBLOCK; + + if (len < 1 || src_offs < 0) return 0.0; + + src_offs = src_offs&(NSEEL_RAM_ITEMSPERBLOCK-1); + copy_len = wdl_min(len,NSEEL_RAM_ITEMSPERBLOCK - src_offs); + + for (;;) + { + EEL_F *srcptr; + if (sbidx >= NSEEL_RAM_BLOCKS) break; + + srcptr = blocks[sbidx]; + + if (WDL_unlikely(!srcptr)) + { + srcptr = __NSEEL_RAMAlloc(blocks,sbidx * NSEEL_RAM_ITEMSPERBLOCK); + if (srcptr==&nseel_ramalloc_onfail) break; + } + + len-=copy_len; + srcptr += src_offs; + while (copy_len-- > 0) + { + EEL_F v = *srcptr; + *srcptr++ = ret; + ret = v; + } + if (!len) break; + + sbidx++; + src_offs=0; + copy_len = wdl_min(len,NSEEL_RAM_ITEMSPERBLOCK); + } + return ret; +} + +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_MemSumProducts(EEL_F **blocks,EEL_F *dest, EEL_F *src, EEL_F *lenptr) +{ + int src_offs = (int)*src; + int len = (int)*lenptr; + + EEL_F sum = 0.0; + + if (len < 1 || src_offs < 0) return 0.0; + + if (*dest < 0.0) + { + int copy_len; + unsigned int sbidx = (unsigned int)src_offs / NSEEL_RAM_ITEMSPERBLOCK; + src_offs = src_offs&(NSEEL_RAM_ITEMSPERBLOCK-1); + copy_len = wdl_min(len,NSEEL_RAM_ITEMSPERBLOCK - src_offs); + for (;;) + { + const EEL_F *srcptr; + if (sbidx >= NSEEL_RAM_BLOCKS) break; + + srcptr = blocks[sbidx]; + + if (WDL_likely(srcptr)) + { + int i; + srcptr += src_offs; + if (*dest == -1.0) + for (i = 0; i < copy_len; i ++) sum += srcptr[i] * srcptr[i]; + else if (*dest == -2.0) + for (i = 0; i < copy_len; i ++) sum += fabs(srcptr[i]); + else + for (i = 0; i < copy_len; i ++) sum += srcptr[i]; + } + len-=copy_len; + if (!len) break; + + sbidx++; + src_offs=0; + copy_len = wdl_min(len,NSEEL_RAM_ITEMSPERBLOCK); + } + } + else + { + unsigned int dest_offs = (unsigned int) (int)*dest; + for (;;) + { + unsigned int sbidx = (unsigned int)src_offs / NSEEL_RAM_ITEMSPERBLOCK; + const int sbo = (src_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)); + unsigned int dbidx = dest_offs / NSEEL_RAM_ITEMSPERBLOCK; + const int dbo = (dest_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)); + + const int copy_len = wdl_min(len,NSEEL_RAM_ITEMSPERBLOCK - wdl_max(dbo,sbo)); + + const EEL_F *srcptr, *destptr; + + if (sbidx >= NSEEL_RAM_BLOCKS || dbidx >= NSEEL_RAM_BLOCKS) break; + srcptr = blocks[sbidx]; + destptr = blocks[dbidx]; + if (WDL_likely(srcptr && destptr)) + { + int i; + srcptr += sbo; + destptr += dbo; + for (i = 0; i < copy_len; i ++) sum += destptr[i] * srcptr[i]; + } + len-=copy_len; + if (!len) break; + + src_offs += copy_len; + dest_offs += copy_len; + } + } + return sum; +} + + +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemCpy(EEL_F **blocks,EEL_F *dest, EEL_F *src, EEL_F *lenptr) +{ + const int mem_size=NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK; + int dest_offs = (int)(*dest + 0.0001); + int src_offs = (int)(*src + 0.0001); + int len = (int)(*lenptr + 0.0001); + int want_mmove=0; + + // trim to front + if (src_offs<0) + { + len += src_offs; + dest_offs -= src_offs; + src_offs=0; + } + if (dest_offs<0) + { + len += dest_offs; + src_offs -= dest_offs; + dest_offs=0; + } + if (src_offs + len > mem_size) len = mem_size-src_offs; + if (dest_offs + len > mem_size) len = mem_size-dest_offs; + + if (src_offs == dest_offs || len < 1) return dest; + + if (src_offs < dest_offs && src_offs+len > dest_offs) + { + // if src_offs < dest_offs and overlapping, must copy right to left + if ((dest_offs - src_offs) < NSEEL_RAM_ITEMSPERBLOCK) want_mmove = 1; + src_offs += len; + dest_offs += len; + while (len > 0) + { + const int maxdlen=((dest_offs-1)&(NSEEL_RAM_ITEMSPERBLOCK-1)) + 1; + const int maxslen=((src_offs-1)&(NSEEL_RAM_ITEMSPERBLOCK-1)) + 1; + int copy_len = len; + EEL_F *srcptr,*destptr; + + if (copy_len > maxdlen) copy_len=maxdlen; + if (copy_len > maxslen) copy_len=maxslen; + + srcptr = __NSEEL_RAMAlloc(blocks,src_offs - copy_len); + destptr = __NSEEL_RAMAlloc(blocks,dest_offs - copy_len); + if (srcptr==&nseel_ramalloc_onfail || destptr==&nseel_ramalloc_onfail) break; + + if (want_mmove) memmove(destptr,srcptr,sizeof(EEL_F)*copy_len); + else memcpy(destptr,srcptr,sizeof(EEL_F)*copy_len); + src_offs-=copy_len; + dest_offs-=copy_len; + len-=copy_len; + } + return dest; + } + + if (dest_offs < src_offs && dest_offs+len > src_offs) + { + // if dest_offs < src_offs and overlapping, and less than NSEEL_RAM_ITEMSPERBLOCK apart, use memmove() + if ((src_offs-dest_offs) < NSEEL_RAM_ITEMSPERBLOCK) want_mmove = 1; + } + + while (len > 0) + { + const int maxdlen=NSEEL_RAM_ITEMSPERBLOCK - (dest_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)); + const int maxslen=NSEEL_RAM_ITEMSPERBLOCK - (src_offs&(NSEEL_RAM_ITEMSPERBLOCK-1)); + int copy_len = len; + EEL_F *srcptr,*destptr; + + if (copy_len > maxdlen) copy_len=maxdlen; + if (copy_len > maxslen) copy_len=maxslen; + + srcptr = __NSEEL_RAMAlloc(blocks,src_offs); + destptr = __NSEEL_RAMAlloc(blocks,dest_offs); + if (srcptr==&nseel_ramalloc_onfail || destptr==&nseel_ramalloc_onfail) break; + + if (want_mmove) memmove(destptr,srcptr,sizeof(EEL_F)*copy_len); + else memcpy(destptr,srcptr,sizeof(EEL_F)*copy_len); + src_offs+=copy_len; + dest_offs+=copy_len; + len-=copy_len; + } + return dest; +} + +EEL_F * NSEEL_CGEN_CALL __NSEEL_RAM_MemSet(EEL_F **blocks,EEL_F *dest, EEL_F *v, EEL_F *lenptr) +{ + int offs = (int)(*dest + 0.0001); + int len = (int)(*lenptr + 0.0001); + EEL_F t; + if (offs<0) + { + len += offs; + offs=0; + } + if (offs >= NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK) return dest; + + if (offs+len > NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK) len = NSEEL_RAM_BLOCKS*NSEEL_RAM_ITEMSPERBLOCK - offs; + + if (len < 1) return dest; + + + t=*v; // set value + +// int lastBlock=-1; + while (len > 0) + { + int lcnt; + EEL_F *ptr=__NSEEL_RAMAlloc(blocks,offs); + if (ptr==&nseel_ramalloc_onfail) break; + + lcnt=NSEEL_RAM_ITEMSPERBLOCK-(offs&(NSEEL_RAM_ITEMSPERBLOCK-1)); + if (lcnt > len) lcnt=len; + + len -= lcnt; + offs += lcnt; + + while (lcnt--) + { + *ptr++=t; + } + } + return dest; +} + + +static inline int __getset_values(EEL_F **blocks, int isset, int len, EEL_F **parms) +{ + int offs, lout=0; + unsigned int pageidx, sub_offs; + if (--len < 1) return 0; + offs = (int)(parms++[0][0] + 0.0001); + + if (offs<=0) + { + len += offs; + parms -= offs; + offs=0; + pageidx=sub_offs=0; + if (len<1) return 0; + } + else + { + sub_offs = ((unsigned int)offs) & (NSEEL_RAM_ITEMSPERBLOCK-1); + pageidx = ((unsigned int)offs)>>NSEEL_RAM_ITEMSPERBLOCK_LOG2; + if (pageidx>=NSEEL_RAM_BLOCKS) return 0; + } + + for (;;) + { + int lcnt=NSEEL_RAM_ITEMSPERBLOCK-sub_offs; + EEL_F *ptr=blocks[pageidx]; + if (!ptr) + { + ptr = __NSEEL_RAMAlloc(blocks,offs + lout); + if (ptr==&nseel_ramalloc_onfail) return lout; + } + else + { + ptr += sub_offs; + } + + if (lcnt >= len) + { + // this page satisfies the request (normal behavior) + lout += len; + if (isset) while (len--) *ptr++=parms++[0][0]; + else while (len--) parms++[0][0] = *ptr++; + return lout; + } + + // crossing a page boundary + len -= lcnt; + lout += lcnt; + if (isset) while (lcnt--) *ptr++=parms++[0][0]; + else while (lcnt--) parms++[0][0] = *ptr++; + + if (len <= 0 || ++pageidx >= NSEEL_RAM_BLOCKS) return lout; + sub_offs=0; + } +} + +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_Mem_SetValues(EEL_F **blocks, INT_PTR np, EEL_F **parms) +{ + return __getset_values(blocks,1,(int)np,parms); +} + +EEL_F NSEEL_CGEN_CALL __NSEEL_RAM_Mem_GetValues(EEL_F **blocks, INT_PTR np, EEL_F **parms) +{ + return __getset_values(blocks,0,(int)np,parms); +} + +void NSEEL_VM_SetGRAM(NSEEL_VMCTX ctx, void **gram) +{ + if (ctx) + { + compileContext *c=(compileContext*)ctx; + c->gram_blocks = gram; + } +} + + +void NSEEL_VM_freeRAM(NSEEL_VMCTX ctx) +{ + if (ctx) + { + int x; + compileContext *c=(compileContext*)ctx; + EEL_F **blocks = c->ram_state->blocks; + for (x = 0; x < NSEEL_RAM_BLOCKS; x ++) + { + if (blocks[x]) + { + if (NSEEL_RAM_memused >= sizeof(EEL_F) * NSEEL_RAM_ITEMSPERBLOCK) + NSEEL_RAM_memused -= sizeof(EEL_F) * NSEEL_RAM_ITEMSPERBLOCK; + else NSEEL_RAM_memused_errors++; + free(blocks[x]); + blocks[x]=0; + } + } + c->ram_state->needfree=0; // no need to free anymore + } +} + +void NSEEL_VM_FreeGRAM(void **ufd) +{ + if (ufd[0]) + { + EEL_F **blocks = (EEL_F **)ufd[0]; + int x; + for (x = 0; x < NSEEL_RAM_BLOCKS; x ++) + { + if (blocks[x]) + { + if (NSEEL_RAM_memused >= sizeof(EEL_F) * NSEEL_RAM_ITEMSPERBLOCK) + NSEEL_RAM_memused -= sizeof(EEL_F) * NSEEL_RAM_ITEMSPERBLOCK; + else NSEEL_RAM_memused_errors++; + } + free(blocks[x]); + blocks[x]=0; + } + free(blocks); + ufd[0]=0; + } +} + +EEL_F *NSEEL_VM_getramptr(NSEEL_VMCTX ctx, unsigned int offs, int *validCount) +{ + EEL_F *d=__NSEEL_RAMAlloc(ctx ? ((compileContext*)ctx)->ram_state->blocks : 0,offs); + if (!d || d == &nseel_ramalloc_onfail) return NULL; + if (validCount) *validCount = NSEEL_RAM_ITEMSPERBLOCK - (offs%NSEEL_RAM_ITEMSPERBLOCK); + + return d; +} + +EEL_F *NSEEL_VM_getramptr_noalloc(NSEEL_VMCTX ctx, unsigned int offs, int *validCount) +{ + EEL_F *d; + compileContext *cc = (compileContext *)ctx; + + if (!cc || + offs >= NSEEL_RAM_ITEMSPERBLOCK*NSEEL_RAM_BLOCKS || + NULL == (d = cc->ram_state->blocks[offs/NSEEL_RAM_ITEMSPERBLOCK]) + ) + { + if (validCount) *validCount = 0; + return NULL; + } + + offs %= NSEEL_RAM_ITEMSPERBLOCK; + if (validCount) *validCount = NSEEL_RAM_ITEMSPERBLOCK - offs; + return d + offs; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-yylex.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-yylex.c new file mode 100644 index 0000000..84a29de --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/nseel-yylex.c @@ -0,0 +1,43 @@ +/* + Expression Evaluator Library (NS-EEL) + Copyright (C) 2004-2013 Cockos Incorporated + Copyright (C) 1999-2003 Nullsoft, Inc. + + nseel-yylex.c + + 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. +*/ + + +#include "ns-eel-int.h" + + + +# define YYMALLOC malloc +# define YYFREE free + +int nseellex(void * yylval_param,void * yylloc_param ,void *yyscanner); +void nseelerror(void *pos,compileContext *ctx, const char *str); + +// inhibit a warning: +static void WDL_STATICFUNC_UNUSED yydestruct(const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, compileContext* context); + +#include +#include + +#include "y.tab.c" + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/regenerate-x64-objects.sh b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/regenerate-x64-objects.sh new file mode 100644 index 0000000..7d3aa2d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/regenerate-x64-objects.sh @@ -0,0 +1,8 @@ +# regenerates x86_64 objects +rm -f asm-nseel-x64-macho.o asm-nseel-x64.obj foo.o +nasm -f win64 asm-nseel-x64-sse.asm -o asm-nseel-x64.obj || echo "error assembling win64 object" +nasm -D AMD64ABI -f macho64 --prefix _ asm-nseel-x64-sse.asm -o asm-nseel-x64-macho.o || echo "error assembling macOS x86_64" +echo > foo.c +clang -arch arm64 -c -o foo.o foo.c || echo "error compiling arm64 stub" +lipo foo.o asm-nseel-x64-macho.o -create -output asm-nseel-multi-macho.o || echo "error making asm-nseel-multi-macho.o" +rm -f -- foo.c foo.o diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/circle.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/circle.eel new file mode 100644 index 0000000..0917bc7 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/circle.eel @@ -0,0 +1,50 @@ +gfx_init("vis",1024,768); + +zp=-0.84; +fill=0; +radj=1; +while ((c=gfx_getchar())!=27 && c >= 0) +( + c == 'f' ? fill=!fill; + c == 'j' ? jitter=!jitter; + c == 'm' ? radj = !radj; + + gfx_r=gfx_g=gfx_b=1; + gfx_a=1; + gfx_x=gfx_y=0; + gfx_printf("[f]ill=%s, [j]itter=%s, [m]ove=%s [%f] %d,%d",fill?"on":"off",jitter?"on":"off",radj?"on":"off",zp,mouse_x,mouse_y); + gfx_a=0.25; + + + radj ? zp+=0.03; + gfx_getchar('up') ? zp+=0.03; + gfx_getchar('down') ? zp-=0.03; + zp2+=0.1; + rd = (1+sin(zp*1.3))*(gfx_w/8-16) + 3; + jitter ? ( + xoffs=0.5+sin(zp2*6.7)*0.5; + yoffs=0.5+sin(zp2*7.7)*0.5; + rd|=0; + rd += 0.5+sin(zp2*3.1)*0.5 + ) : ( xoffs=yoffs=0; rd|= 0;); + + + gfx_circle(xoffs+(gfx_w/4)|0,yoffs+(gfx_h/2)|0,rd, fill,0); + gfx_circle(xoffs+(gfx_w*3/4)|0,yoffs+(gfx_h/2)|0,rd, fill,1); + + gfx_mode=4+(1<<4); // filtering off, additive + gfx_a=1; + zsz=20; + outsz=gfx_w/4; + gfx_blit(-1,0,0, + gfx_w/4-zsz, gfx_h/2-zsz, zsz*2,zsz*2, + 0,gfx_h-outsz,outsz,outsz); + + gfx_blit(-1,0,0, + gfx_w*3/4-zsz, gfx_h/2-zsz, zsz*2,zsz*2, + gfx_w-outsz,gfx_h-outsz,outsz,outsz); + gfx_mode=0; + + gfx_update(); + sleep(30); +); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/eval_test.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/eval_test.eel new file mode 100644 index 0000000..17fba0c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/eval_test.eel @@ -0,0 +1,20 @@ +argc < 2 ? ( + printf("Usage: %s [script]\n",argv[0]); + +) : ( + printf("loading '%s'\n",argv[1]); + x = fopen(argv[1],"r"); + !x ? ( + printf("Error opening '%s'\n",argv[1]) + ) : ( + #str=""; + while (fgets(x,#line)) ( #str += #line; ); + fclose(x); + loop(30, + start_t = time_precise(); + eval(#str) || printf("error evaluating script\n"); + start_t = time_precise()-start_t; + printf("finished in %f milliseconds\n",start_t*1000.0); + ); + ); +); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gfx_test.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gfx_test.eel new file mode 100644 index 0000000..90a55db --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gfx_test.eel @@ -0,0 +1,24 @@ +gfx_init("vis",1024,768); + +gfx_clear=-1; +while ((c=gfx_getchar())!=27 && c >= 0) +( + c == ' ' ? (mode += 1) >= 2 ? mode=0; + t=time_precise(); + zsc = 0.01*cos(t*0.73); +// gfx_x=gfx_y=0; gfx_blurto(gfx_w,gfx_h); + gfx_blit(-1,0,0.3*(sin(t*0.3)^2),gfx_w*zsc,gfx_h*zsc, gfx_w*(1-2*zsc),gfx_h*(1-2*zsc), 0,0,gfx_w,gfx_h); + gfx_r=(cos(t)+1.0)*0.5; + gfx_g=(cos(t*1.74)+1.0)*0.5; + gfx_b=(cos(t*1.2+0.56)+1.0)*0.5; + gfx_a=0.15; + sz=gfx_w*0.03; + loop(20, + mode == 1 ? + gfx_circle(rand(gfx_w-sz),rand(gfx_h-sz),sz,1) : + gfx_rect(rand(gfx_w-sz),rand(gfx_h-sz),sz,sz); + ); + + + gfx_update(); +); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gfx_test_defer.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gfx_test_defer.eel new file mode 100644 index 0000000..a5af640 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gfx_test_defer.eel @@ -0,0 +1,29 @@ +gfx_init("vis",1024,768); + +gfx_clear=-1; + +function frame() +( + c=gfx_getchar(); + c == ' ' ? (mode += 1) >= 2 ? mode=0; + t=time_precise(); + zsc = 0.01*cos(t*0.73); +// gfx_x=gfx_y=0; gfx_blurto(gfx_w,gfx_h); + gfx_blit(-1,0,0.3*(sin(t*0.3)^2),gfx_w*zsc,gfx_h*zsc, gfx_w*(1-2*zsc),gfx_h*(1-2*zsc), 0,0,gfx_w,gfx_h); + gfx_r=(cos(t)+1.0)*0.5; + gfx_g=(cos(t*1.74)+1.0)*0.5; + gfx_b=(cos(t*1.2+0.56)+1.0)*0.5; + gfx_a=0.15; + sz=gfx_w*0.03; + loop(20, + mode == 1 ? + gfx_circle(rand(gfx_w-sz),rand(gfx_h-sz),sz,1) : + gfx_rect(rand(gfx_w-sz),rand(gfx_h-sz),sz,sz); + ); + + + gfx_update(); + c>=0 && c != 27 ? defer("frame()"); +); + +frame(); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gpx_edit.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gpx_edit.eel new file mode 100644 index 0000000..8517f5d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/gpx_edit.eel @@ -0,0 +1,208 @@ +// does not work for all .gpx files +// writes to output on every mouseup after edit +// move points with mouse, alt+click to delete +// click/drag to pan +// mousewheel to zoom + +tab = 10000; +tabsz = 0; + +is_dirty = 0; +circle_size = 5; + +function scale(v) global() ( (v - this.min) / (this.max-this.min) ); +function unscale(v) global() ( v * (this.max-this.min) + this.min); +function zoom(sc) global() local(c h) ( + h = (this.max - this.min) * sc; + c = (this.max + this.min) * .5; + this.max = c + h*.5; + this.min = c - h*.5; +); +function include(v) global() ( this.min = min(v,this.min); this.max = max(v,this.max); ); + +function scroll(amt) global() ( amt *= (this.max-this.min); this.max += amt; this.min += amt; ); +function zoom_view(sc) ( v_lon.zoom(sc); v_lat.zoom(sc); ); +function scroll_view(dx, dy) ( v_lat.scroll(-dy/gfx_h); v_lon.scroll(-dx/gfx_w); ); + +function hit_test(x,y,p) global(tab tabsz circle_size gfx_w gfx_h v_lat.scale v_lon.scale) local(p hit) ( + hit = -1; + while (p < tabsz && hit < 0) ( + sqr(v_lat.scale(tab[p*2+1])*gfx_h-y)+sqr(v_lon.scale(tab[p*2])*gfx_w-x) < circle_size*circle_size ? hit = p; + p += 1; + ); + hit +); + +function linearize(p,sz, slon, slat, elon, elat, rev) local(x) global() +( + rev ? ( x=slon; slon=elon; elon=x; x=slat; slat=elat; elat=x; ); + elon = (elon - slon) / sz; + elat = (elat - slat) / sz; + loop(sz, + p[0] = slon; + p[1] = slat; + slon += elon; + slat += elat; + p += 2; + ); +); + +function make_lowpass(f filtsize filtpos) global() local(windowpos sincpos x) +( + x = 0; + loop(filtsize, + x == filtsize/2 ? ( + f[x] = 1.0; + ) : ( + windowpos = 2 * x * $pi / filtsize; + sincpos = filtpos * $pi * (x - filtsize/2); + + // blackman-harris * window + f[x] = (0.35875 - 0.48829 * cos(windowpos) + 0.14128 * cos(2*windowpos) - 0.01168 * cos(3*windowpos)) * sin(sincpos) / sincpos; + ); + x+=1; + ); +); + +function lowpass() local(src sz i j lpf_pos v sx sy filt p) global(tab tabsz) +( + lpf_pos>0.001 ? lpf_pos *= 0.95 : lpf_pos = 0.9; + sz = 64; + src = tab+tabsz*2 + sz*4 + 1024; + filt = src + tabsz*2 + sz*4 + 1024; + make_lowpass(filt, sz, lpf_pos); + memcpy(src, tab, tabsz*2); + i = src; + j = src + tabsz*2; + loop(sz/2, + memcpy(i-2,i,2); + memcpy(j,j-2,2); + j+=2; + i-=2; + ); + i = 0; + loop(tabsz, + tab[i*2] != 10000 ? ( + p = j = sx = sy = 0; + loop(sz, + v = src[(i+j-sz/2)*2]; + v != 10000 ? ( + p += filt[j]; + sx += v * filt[j]; + sy += src[(i+j-sz/2)*2 + 1] * filt[j]; + ); + j+=1; + ); + tab[i*2] = sx/p; + tab[i*2+1] = sy/p; + ); + i+=1; + ); + +); + +function do_file(srcfn, destfn) local(fp fpout p lat lon skipping) ( + (fp = fopen(srcfn,"r")) > 0 ? ( + destfn < 0 || (fpout = fopen(destfn,"w")) > 0 ? ( + p = tab; + skipping = 0; + while (fgets(fp,#line)) ( + match("%s%s",#line,#lead,lat,lon,#trail) ? ( + destfn < 0 ? ( tabsz+=1; v_lat.include(p[1] = -lat); v_lon.include(p[0] = lon);) : + ( !(skipping = p[0] == 10000) ? fprintf(fpout,"%s%s",#lead,-p[1],p[0],#trail); ); + p += 2; + ) : destfn >= 0 ? ( skipping ? ( match("**",#line) ? skipping = 0; ) : fwrite(fpout,#line,0) ); + ); + destfn >= 0 ? fclose(fpout); + ); + fclose(fp); + ); +); + + +function run() ( + mouse_wheel ? ( zoom_view(mouse_wheel < 0 ? 1.1 : 1/1.1); mouse_wheel = 0;); + (mouse_cap&1)? ( + !(last_mouse_cap & 1) ? ( + cap_mode >= 0 && (mouse_cap&4) ? ( + tmp = hit_test(mouse_x,mouse_y,0); + tmp >= 0 ? ( + tmp < cap_mode ? ( + cap_cnt = cap_mode+cap_cnt - tmp; + cap_mode = tmp; + ) : ( + cap_cnt = max(cap_mode+cap_cnt, tmp) - cap_mode; + ); + ) : cap_mode = tmp; + ) : ( + cap_mode = hit_test(mouse_x,mouse_y,0); + cap_mode >= 0 ? ( + cap_cnt = 1; + while (hit_test(mouse_x,mouse_y,cap_mode+cap_cnt) >= 0) ( cap_cnt += 1; ); + ); + ); + cap_start_lon = v_lon.unscale(mouse_x/gfx_w); + cap_start_lat = v_lat.unscale(mouse_y/gfx_h); + ) : cap_mode >= 0 ? ( + cap_cnt > 1 && (mouse_cap&4) ? ( + linearize(tab + cap_mode*2,cap_cnt, cap_start_lon, cap_start_lat, + v_lon.unscale(mouse_x/gfx_w), v_lat.unscale(mouse_y/gfx_h), + mouse_cap&8); + ) : ( + tab[cap_mode*2] = v_lon.unscale(mouse_x/gfx_w); + tab[cap_mode*2+1] = v_lat.unscale(mouse_y/gfx_h); + ); + is_dirty = 1; + ) : scroll_view(mouse_x-last_mouse_x,mouse_y-last_mouse_y); + ) : ( + (last_mouse_cap & 1) && (last_mouse_cap & 16) && cap_mode >= 0 ? ( + tab[cap_mode*2] = 10000; + is_dirty = 1; + ); + is_dirty ? ( do_file(argv[1], argv[2]); is_dirty = 0; ); + ); + last_mouse_cap = mouse_cap; + last_mouse_x = mouse_x; + last_mouse_y = mouse_y; + + p = tab; + hadprev = dist = 0; + loop(tabsz, + p[0] != 10000 ? ( + x = v_lon.scale(p[0])*gfx_w; + y = v_lat.scale(p[1])*gfx_h; + hadprev ? ( + gfx_set(0,.5,.7); + gfx_line(v_lon.scale(llon)*gfx_w,v_lat.scale(llat)*gfx_h,x,y); + dist += sqrt(sqr((p[0] - llon) * cos(llat*$pi/180.0)*69.172) + sqr((p[1] - llat)*60)); + ); + cap_mode == (p-tab)/2 ? gfx_set(1,0,1) : gfx_set(0,1,0); + gfx_circle(x,y,circle_size); + llon = p[0]; + llat = p[1]; + hadprev = 1; + ); + p += 2; + ); + + gfx_x=gfx_y=0; + gfx_set(1,1,0); + gfx_printf("distance (approx) %.2f mi\n",dist); + + gfx_update(); + c = gfx_getchar(); + c == 'l' ? ( lowpass(); is_dirty = 1; ); + c >= 0 && c != 27 ? defer("run()"); +); + +argc < 2 ? printf("Usage: %s file.gpx output.gpx\n",argv[0]) : ( + v_lat.min = v_lon.min = 100000; + v_lat.max = v_lon.max = -100000; + do_file(argv[1],-1); + tabsz > 0 ? ( + zoom_view(1.3); + gfx_init("gpx edit",1024,768); + defer("run()"); + ) : printf("Error: %s has no = 0 && conid < this.maxcon ? ( + parm == 'sock' ? this.tab + conid*this.recsz + 0 : // socket + parm == 'st' ? this.tab + conid*this.recsz + 1 : // state + parm == 'fh' ? this.tab + conid*this.recsz + 2 : // file handle + 0) + : 0 +); + +function httpd.set(conid, parm, value) local(x) global() +( + x = httpd.getrecval(conid,parm); + x>=0 ? x[]=value; +); +function httpd.get(conid, parm) local(x) global() +( + x=httpd.getrecval(conid,parm); + x>=0?x[]; +); +function httpd.getbuf(conid, w) global() +( + conid >=0 && conid < this.maxcon ? this.stringtab + this.string_recsz*conid + w : -1; +); +function httpd.close(conid) global() +( + conid >=0 && conid < this.maxcon ? + ( + tcp_close(httpd.get(conid,'sock')); + fclose(httpd.get(conid,'fh')); + httpd.set(conid,'sock',0); + httpd.set(conid,'fh',0); + ); +); +function httpd.file_for_req(req, response) local(fn,fp) global() ( + !strcmp(req,"") || !strcmp(req,"/") ? req = "/index.html"; + + str_getchar(req,0) == '/' && + !match("*..*",req) && + (fp=fopen(fn = strcat(#=#this.wwwroot,req),"rb")) > 0 ? + ( + fseek(fp,0,1); + printf("GET %s -- 200: %s\n",req,fn); + strcat(response,sprintf(#, "HTTP/1.1 200 OK\r\n" + "Content-length: %d\r\n" + "Content-type: text/html\r\n" + "\r\n", + ftell(fp))); + fseek(fp,0,-1); + fp; + ) : ( + printf("GET %s -- 404\n",req); + + fn = "The file specified could not be found."; + strcat(response,sprintf(#, "HTTP/1.1 404 NOT FOUND\r\n" + "Content-length: %d\r\n" + "Content-type: text/plain\r\n" + "\r\n" + "%s", strlen(fn),fn)); + 0; + ); +); + +// run input and output buffers +function httpd.runcon(idx) local(sock,x,str,rv,buffers) global() ( + rv=0; + sock = httpd.get(idx,'sock'); + sock > 0 ? + ( + x=tcp_recv(sock,str=#); + x<0 ? ( + httpd.close(idx); + ) : ( + buffers=httpd.getbuf(idx,0); + x>0 ? strcat(buffers,str); + rv+=x; + + strlen(buffers+1)>0 ? + ( + (x=tcp_send(sock,buffers+1)) > 0 ? + ( + rv+=1; + str_delsub(buffers+1.1,0,x); + ); + ); + ); + ); + rv; +); + + +function httpd.run() local(sock, x, i, str, rv, t,hdrs,httpdver) global() ( + str=#; + hdrs=#; + rv=0; + sock=tcp_listen(this.port,"",str); + sock > 0 ? + ( + i=0; + while (i < this.maxcon && httpd.get(i,'sock')) ( i += 1; ); + i < this.maxcon ? ( + tcp_set_block(sock,0); + httpd.set(i,'sock',sock); + httpd.set(i,'st',0); + + x=0; + loop(this.string_recsz, strcpy(httpd.getbuf(i,x), ""); x+=1; ); + printf("httpd.run(): connection from '%s'\n",str); + rv+=1; + + ) : ( + printf("httpd.run(): dropping connect from '%s', slots full\n",str); + tcp_close(x); + ); + ); + + i=0; + while (i < this.maxcon) + ( + this.httpd.runcon(i) ? + ( + t = httpd.getbuf(i,0); + this.httpd.get(i,'st') == 0 ? + ( + match("GET %S HTTP/1.%1d%S",t,str,httpdver,hdrs) && + str_getchar(hdrs,0)=='\r' && str_getchar(hdrs,1)=='\n' && + !strcmp(strcpy_substr(#,hdrs,-4),"\r\n\r\n") + ? + ( + httpd.set(i,'st',1 | + ((httpdver==0 || matchi("\r\nConnection:*close\r\n",hdrs))?2:0) + ); + + httpd.set(i,'fh',httpd.file_for_req(str,t+1)); + ); + ); + + httpd.get(i,'fh') && !strlen(t+1) ? fread(httpd.get(i,'fh'),t+1,4096); + + httpd.get(i,'st') == 3 ? ( + strlen(t+1) == 0 ? ( + httpd.close(i); + printf("sent data, closing\n"); + ); + ); + + rv+=1; + ); + i+=1; + ); + + + rv; +); + +argc != 3 || strlen(argv[1])<1 || !match("%d",argv[2],port) ? ( + printf("Usage: \n\t%s www_root port\n",argv[0]); +) : ( + srv.httpd.init(port, 100, 100000, 10000, argv[1]); + // string [conidx] = send buffe + while(1) + ( + srv.httpd.run() || Sleep(10); + ); +); + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/pproc_test.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/pproc_test.eel new file mode 100644 index 0000000..ad1432c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/pproc_test.eel @@ -0,0 +1,10 @@ + + + + + + +printf("this will not get compiled\n"); + + +printf("apparently y is now %d but x is %d\n",y,x); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/sinewave_text.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/sinewave_text.eel new file mode 100644 index 0000000..bbf67cf --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/sinewave_text.eel @@ -0,0 +1,17 @@ +width = 80; +lx=-1; +loop(1000000, + x = ((1+sin(pos))*0.5 * width)|0; + lx >= -1 && lx!=x ? ( + loop(min(x,lx), printf(" ")); + loop(abs(x-lx), printf("*")); + loop(width-max(x,lx), printf(" ")); + ) : ( + loop(x, printf(" ")); + printf("*"); + loop(width-1-x, printf(" ")); + ); + lx=x; + printf("\n"); + pos += 0.25 + sin(pos*0.32)*0.2; +); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/space.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/space.eel new file mode 100644 index 0000000..c517aa1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/space.eel @@ -0,0 +1,73 @@ + + +gfx_init("space",640,480); + +function enemylist.init(buf, rows, cols, maxwid) global() +( + this.buf = buf; + this.rows=rows; + this.cols=cols; + this.maxwid = maxwid; + loop(rows*cols, buf[]=1; buf += 1; ); +); + + +function enemylist.draw(pos, vpos, destextent*) + local(rad,buf,xpos,ypos,dxpos) + global(gfx_w,gfx_h,gfx_r,gfx_g,gfx_b,gfx_a) +( + gfx_r=gfx_a=1; + gfx_g=gfx_b=0; + dxpos = this.maxwid/this.cols*gfx_w; + buf=this.buf; + rad = dxpos*0.3; + destextent.right = 0; + destextent.bottom = 0; + destextent.left=2^20; + destextent.top=2^20; + + ypos=dxpos*0.5 + gfx_h*vpos; + loop(this.rows, + xpos=pos*gfx_w + dxpos*0.5; + loop(this.cols, + buf[] ? ( + destextent.left = min(destextent.left,xpos-rad); + destextent.right = max(destextent.right,xpos+rad); + destextent.top = min(destextent.top,ypos-rad); + destextent.bottom = max(destextent.bottom,ypos+rad); + gfx_circle(xpos,ypos,rad); + ); + xpos += dxpos; + buf += 1; + ); + ypos += dxpos; + ); +); + + +enemylist.init(0, 4, 8, 0.8); + + +last_time = time_precise(); +enemy_pos = 0; +enemy_vpos = 0; +enemy_vel = 0.05; +while ((curchar = gfx_getchar()) != 27) +( + now = time_precise(); + enemylist.draw(enemy_pos, enemy_vpos, area); + enemy_vel > 0 ? ( + area.right >= gfx_w ? ( enemy_vel = -enemy_vel; enemy_vpos += abs(enemy_vel); enemy_vel *= 1.3; ); + ) : ( + area.left <= 0 ? ( enemy_vel = -enemy_vel; enemy_vpos += abs(enemy_vel); enemy_vel *= 1.3; ); + ); + + enemy_pos += (now-last_time)*enemy_vel; + + + + + last_time = now; + sleep(33); + gfx_update(); +); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/test_a.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/test_a.eel new file mode 100644 index 0000000..923c122 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/test_a.eel @@ -0,0 +1,22 @@ +sum=0; +a= 1; +b = .1; +c= .2; +d = .5; +e = 4; +f = -0.2; +g = 0.3; +h = .37; + +loop(62000, + i = 0; + sum=0.0; + loop(8192, + sum += i*(3+ a * b + c*d + e * (f - b*(-c) + g*(e-g*(-g)*g)) ); + sum2 += (a*b)+(a*b)+((a*b)*((b*c)*((h*i)*(f*g)))); + i += 0.001; + i2+=1; + ); +); +printf("sum error %.18f %.18f\n", sum - 245333.476966294896556064, sum2 - 101488440.519564077258110046); +printf("sum error per inst %.18f %.18f\n", (sum - 245333.476966294896556064) / i, (sum2 - 101488440.519564077258110046)/i2); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/test_or64.eel b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/test_or64.eel new file mode 100644 index 0000000..084caeb --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/scripts/test_or64.eel @@ -0,0 +1,35 @@ +v1 = 2^50; +v2 = 2^50; +z=0; + +(floor(v1)/v2) !== 1 ? printf("fail test floor\n"); +(floor(v1)/(2^50)) !== 1 ? printf("fail test floor/const\n"); +(floor(2^50)/v2) !== 1 ? printf("fail test floor-const\n"); +(floor(2^50)/(2^50)) !== 1 ? printf("fail test floor-const/const\n"); +(floor(v1+1)/(v2+1)) !== 1 ? printf("fail test floor+1\n"); +(floor(v1+1)/(2^50+1)) !== 1 ? printf("fail test floor+1/const\n"); +(floor(2^50 + 1)/(v2+1)) !== 1 ? printf("fail test floor-const+1"); +(floor(2^50 + 1)/(2^50 + 1)) !== 1 ? printf("fail test floor-const+1/const\n"); + +((v1|0)/v2) !== 1 ? printf("fail test |0\n"); +((v1|0)/(2^50)) !== 1 ? printf("fail test |0/const\n"); +(((2^50)|0)/v2) !== 1 ? printf("fail test const|0\n"); +(((2^50)|0)/(2^50)) !== 1 ? printf("fail test const|0/const\n"); +((v1|1)/(v2+1)) !== 1 ? printf("fail test |1\n"); +((v1|1)/(2^50+1)) !== 1 ? printf("fail test |1/const\n"); +(((2^50)|1)/(v2 + 1)) !== 1 ? printf("fail test const|1\n"); +(((2^50)|1)/(2^50 + 1)) !== 1 ? printf("fail test const|1/const\n"); + +((v1~1)/(v2+1)) !== 1 ? printf("fail test ~1\n"); +((v1~1)/(2^50+1)) !== 1 ? printf("fail test ~1/const\n"); +(((2^50)~1)/(v2 + 1)) !== 1 ? printf("fail test const~1\n"); +(((2^50)~1)/(2^50 + 1)) !== 1 ? printf("fail test const~1/const\n"); + +c = v1; c |= v1; +c !== v1 ? printf("fail test |= \n"); +c = v1; c |= 0; +c !== v1 ? printf("fail test |= 0\n"); +c = v1; c &= v1; +c !== v1 ? printf("fail test &=\n"); +c = v1; c ~= z; +c !== v1 ? printf("fail test ~= \n"); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/y.tab.c b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/y.tab.c new file mode 100644 index 0000000..6b264b7 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/y.tab.c @@ -0,0 +1,2186 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse nseelparse +#define yylex nseellex +#define yyerror nseelerror +#define yylval nseellval +#define yychar nseelchar +#define yydebug nseeldebug +#define yynerrs nseelnerrs +#define yylloc nseellloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + VALUE = 258, + IDENTIFIER = 259, + TOKEN_SHL = 260, + TOKEN_SHR = 261, + TOKEN_LTE = 262, + TOKEN_GTE = 263, + TOKEN_EQ = 264, + TOKEN_EQ_EXACT = 265, + TOKEN_NE = 266, + TOKEN_NE_EXACT = 267, + TOKEN_LOGICAL_AND = 268, + TOKEN_LOGICAL_OR = 269, + TOKEN_ADD_OP = 270, + TOKEN_SUB_OP = 271, + TOKEN_MOD_OP = 272, + TOKEN_OR_OP = 273, + TOKEN_AND_OP = 274, + TOKEN_XOR_OP = 275, + TOKEN_DIV_OP = 276, + TOKEN_MUL_OP = 277, + TOKEN_POW_OP = 278, + STRING_LITERAL = 279, + STRING_IDENTIFIER = 280 + }; +#endif +/* Tokens. */ +#define VALUE 258 +#define IDENTIFIER 259 +#define TOKEN_SHL 260 +#define TOKEN_SHR 261 +#define TOKEN_LTE 262 +#define TOKEN_GTE 263 +#define TOKEN_EQ 264 +#define TOKEN_EQ_EXACT 265 +#define TOKEN_NE 266 +#define TOKEN_NE_EXACT 267 +#define TOKEN_LOGICAL_AND 268 +#define TOKEN_LOGICAL_OR 269 +#define TOKEN_ADD_OP 270 +#define TOKEN_SUB_OP 271 +#define TOKEN_MOD_OP 272 +#define TOKEN_OR_OP 273 +#define TOKEN_AND_OP 274 +#define TOKEN_XOR_OP 275 +#define TOKEN_DIV_OP 276 +#define TOKEN_MUL_OP 277 +#define TOKEN_POW_OP 278 +#define STRING_LITERAL 279 +#define STRING_IDENTIFIER 280 + + + + +/* Copy the first part of user declarations. */ +#line 13 "eel2.y" + +#ifdef _WIN32 +#include +#endif +#include +#include +#include +#include + +#include "y.tab.h" +#include "ns-eel-int.h" + +#define scanner context->scanner +#define YY_(x) ("") + + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef int YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ +#line 193 "y.tab.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 68 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 141 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 47 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 19 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 73 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 127 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 280 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 34, 2, 2, 2, 36, 39, 2, + 27, 28, 38, 32, 26, 33, 2, 37, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 45, 46, + 42, 31, 43, 44, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 29, 2, 30, 35, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 40, 2, 41, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint16 yyprhs[] = +{ + 0, 0, 3, 5, 9, 11, 14, 16, 20, 28, + 33, 37, 44, 53, 57, 62, 64, 66, 68, 70, + 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, + 112, 116, 120, 122, 125, 128, 131, 133, 137, 139, + 143, 147, 151, 153, 157, 159, 163, 165, 169, 171, + 175, 177, 181, 185, 189, 191, 195, 199, 203, 207, + 211, 215, 219, 223, 225, 229, 233, 235, 241, 246, + 250, 252, 256, 259 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 65, 0, -1, 64, -1, 64, 26, 48, -1, 24, + -1, 24, 49, -1, 4, -1, 27, 64, 28, -1, + 4, 27, 64, 28, 27, 64, 28, -1, 4, 27, + 64, 28, -1, 4, 27, 28, -1, 4, 27, 64, + 26, 64, 28, -1, 4, 27, 64, 26, 64, 26, + 48, 28, -1, 51, 29, 30, -1, 51, 29, 64, + 30, -1, 3, -1, 25, -1, 49, -1, 50, -1, + 51, -1, 50, 31, 63, -1, 50, 15, 63, -1, + 50, 16, 63, -1, 50, 17, 63, -1, 50, 18, + 63, -1, 50, 19, 63, -1, 50, 20, 63, -1, + 50, 21, 63, -1, 50, 22, 63, -1, 50, 23, + 63, -1, 25, 31, 63, -1, 25, 15, 63, -1, + 52, -1, 32, 53, -1, 33, 53, -1, 34, 53, + -1, 53, -1, 54, 35, 53, -1, 54, -1, 55, + 36, 54, -1, 55, 5, 54, -1, 55, 6, 54, + -1, 55, -1, 56, 37, 55, -1, 56, -1, 57, + 38, 56, -1, 57, -1, 58, 33, 57, -1, 58, + -1, 59, 32, 58, -1, 59, -1, 60, 39, 59, + -1, 60, 40, 59, -1, 60, 41, 59, -1, 60, + -1, 61, 42, 60, -1, 61, 43, 60, -1, 61, + 7, 60, -1, 61, 8, 60, -1, 61, 9, 60, + -1, 61, 10, 60, -1, 61, 11, 60, -1, 61, + 12, 60, -1, 61, -1, 62, 13, 61, -1, 62, + 14, 61, -1, 62, -1, 62, 44, 63, 45, 63, + -1, 62, 44, 45, 63, -1, 62, 44, 63, -1, + 63, -1, 64, 46, 63, -1, 64, 46, -1, 64, + -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 40, 40, 41, 48, 49, 57, 67, 71, 83, + 93, 103, 114, 126, 130, 137, 138, 139, 143, 148, + 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, + 189, 193, 200, 201, 205, 209, 216, 217, 224, 225, + 229, 233, 240, 241, 249, 250, 258, 259, 266, 267, + 274, 275, 279, 283, 290, 291, 295, 299, 303, 307, + 311, 315, 319, 326, 327, 331, 338, 339, 343, 347, + 355, 356, 360, 368 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "VALUE", "IDENTIFIER", "TOKEN_SHL", + "TOKEN_SHR", "TOKEN_LTE", "TOKEN_GTE", "TOKEN_EQ", "TOKEN_EQ_EXACT", + "TOKEN_NE", "TOKEN_NE_EXACT", "TOKEN_LOGICAL_AND", "TOKEN_LOGICAL_OR", + "TOKEN_ADD_OP", "TOKEN_SUB_OP", "TOKEN_MOD_OP", "TOKEN_OR_OP", + "TOKEN_AND_OP", "TOKEN_XOR_OP", "TOKEN_DIV_OP", "TOKEN_MUL_OP", + "TOKEN_POW_OP", "STRING_LITERAL", "STRING_IDENTIFIER", "','", "'('", + "')'", "'['", "']'", "'='", "'+'", "'-'", "'!'", "'^'", "'%'", "'/'", + "'*'", "'&'", "'|'", "'~'", "'<'", "'>'", "'?'", "':'", "';'", "$accept", + "more_params", "string", "assignable_value", "rvalue", "assignment", + "unary_expr", "pow_expr", "mod_expr", "div_expr", "mul_expr", "sub_expr", + "add_expr", "andor_expr", "cmp_expr", "logical_and_or_expr", + "if_else_expr", "expression", "program", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 44, 40, 41, 91, + 93, 61, 43, 45, 33, 94, 37, 47, 42, 38, + 124, 126, 60, 62, 63, 58, 59 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 47, 48, 48, 49, 49, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 51, 51, 51, 51, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 53, 53, 53, 53, 54, 54, 55, 55, + 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, + 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, + 61, 61, 61, 62, 62, 62, 63, 63, 63, 63, + 64, 64, 64, 65 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 3, 1, 2, 1, 3, 7, 4, + 3, 6, 8, 3, 4, 1, 1, 1, 1, 1, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 1, 2, 2, 2, 1, 3, 1, 3, + 3, 3, 1, 3, 1, 3, 1, 3, 1, 3, + 1, 3, 3, 3, 1, 3, 3, 3, 3, 3, + 3, 3, 3, 1, 3, 3, 1, 5, 4, 3, + 1, 3, 2, 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 15, 6, 4, 16, 0, 0, 0, 0, 17, + 18, 19, 32, 36, 38, 42, 44, 46, 48, 50, + 54, 63, 66, 70, 73, 0, 0, 5, 0, 0, + 0, 33, 34, 35, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 72, 1, 10, + 0, 31, 30, 7, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 20, 13, 0, 37, 40, 41, 39, + 43, 45, 47, 49, 51, 52, 53, 57, 58, 59, + 60, 61, 62, 55, 56, 64, 65, 0, 69, 71, + 0, 9, 14, 68, 0, 0, 0, 67, 0, 11, + 0, 0, 2, 8, 12, 0, 3 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 121, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 122, 25 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -38 +static const yytype_int8 yypact[] = +{ + 70, -38, -21, 12, -11, 70, 70, 70, 70, -38, + 102, 6, -38, -38, 50, 19, 4, 8, 14, 25, + 51, 22, 40, -38, 47, 96, 34, -38, 70, 70, + 43, -38, -38, -38, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 45, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 18, 70, -38, -38, + 55, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, 30, -38, 50, 50, 50, + 19, 4, 8, 14, 25, 25, 25, 51, 51, 51, + 51, 51, 51, 51, 51, 22, 22, 70, 53, -38, + 70, 72, -38, -38, 70, 60, 70, -38, 70, -38, + 54, 77, -23, -38, -38, 70, -38 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -38, -10, 111, -38, -38, -38, 11, 61, 79, 76, + 80, 75, 58, 78, -37, -38, -27, 0, -38 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 24, 71, 72, 125, 28, 30, 26, 74, 75, 76, + 77, 78, 79, 80, 81, 82, 83, 31, 32, 33, + 29, 1, 2, 67, 46, 47, 70, 105, 106, 56, + 57, 58, 59, 60, 61, 44, 3, 1, 2, 108, + 109, 49, 3, 4, 85, 5, 50, 51, 1, 2, + 6, 7, 8, 64, 65, 48, 86, 52, 3, 4, + 112, 5, 69, 107, 62, 63, 6, 7, 8, 3, + 4, 73, 5, 1, 2, 84, 67, 6, 7, 8, + 113, 110, 123, 111, 66, 45, 118, 117, 119, 67, + 53, 54, 55, 67, 3, 4, 68, 5, 114, 116, + 67, 67, 6, 7, 8, 124, 67, 87, 88, 89, + 115, 94, 95, 96, 27, 126, 120, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 91, 93, 90, 0, + 0, 92, 0, 43, 97, 98, 99, 100, 101, 102, + 103, 104 +}; + +static const yytype_int8 yycheck[] = +{ + 0, 28, 29, 26, 15, 5, 27, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 6, 7, 8, + 31, 3, 4, 46, 5, 6, 26, 64, 65, 7, + 8, 9, 10, 11, 12, 29, 24, 3, 4, 66, + 67, 37, 24, 25, 44, 27, 38, 33, 3, 4, + 32, 33, 34, 13, 14, 36, 45, 32, 24, 25, + 30, 27, 28, 45, 42, 43, 32, 33, 34, 24, + 25, 28, 27, 3, 4, 30, 46, 32, 33, 34, + 107, 26, 28, 28, 44, 35, 26, 114, 28, 46, + 39, 40, 41, 46, 24, 25, 0, 27, 45, 27, + 46, 46, 32, 33, 34, 28, 46, 46, 47, 48, + 110, 53, 54, 55, 3, 125, 116, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 50, 52, 49, -1, + -1, 51, -1, 31, 56, 57, 58, 59, 60, 61, + 62, 63 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 3, 4, 24, 25, 27, 32, 33, 34, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 27, 49, 15, 31, + 64, 53, 53, 53, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 31, 29, 35, 5, 6, 36, 37, + 38, 33, 32, 39, 40, 41, 7, 8, 9, 10, + 11, 12, 42, 43, 13, 14, 44, 46, 0, 28, + 64, 63, 63, 28, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 30, 64, 53, 54, 54, 54, + 55, 56, 57, 58, 59, 59, 59, 60, 60, 60, + 60, 60, 60, 60, 60, 61, 61, 45, 63, 63, + 26, 28, 30, 63, 45, 64, 27, 63, 26, 28, + 64, 48, 64, 28, 28, 26, 48 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, context, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc, scanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, context); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, compileContext* context) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + compileContext* context; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (context); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, compileContext* context) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + compileContext* context; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, compileContext* context) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, context) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + compileContext* context; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , context); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, context); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, compileContext* context) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, context) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + compileContext* context; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (context); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 3: /* "VALUE" */ +#line 8 "eel2.y" + { + #define yydestruct(a,b,c,d,e) +}; +#line 1222 "y.tab.c" + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (compileContext* context); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (compileContext* context) +#else +int +yyparse (context) + compileContext* context; +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 3: +#line 42 "eel2.y" + { + (yyval) = nseel_createMoreParametersOpcode(context,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 5: +#line 50 "eel2.y" + { + ((struct eelStringSegmentRec *)(yyvsp[(1) - (2)]))->_next = (struct eelStringSegmentRec *)(yyvsp[(2) - (2)]); + (yyval) = (yyvsp[(1) - (2)]); + } + break; + + case 6: +#line 58 "eel2.y" + { + if (!((yyval) = nseel_resolve_named_symbol(context, (yyvsp[(1) - (1)]), -1, NULL))) /* convert from purely named to namespace-relative, etc */ + { + yyerror(&yyloc, context, ""); + YYERROR; + } + } + break; + + case 7: +#line 68 "eel2.y" + { + (yyval) = (yyvsp[(2) - (3)]); + } + break; + + case 8: +#line 72 "eel2.y" + { + int err; + if (!((yyval) = nseel_setCompiledFunctionCallParameters(context,(yyvsp[(1) - (7)]), (yyvsp[(3) - (7)]), 0, 0, (yyvsp[(6) - (7)]), &err))) + { + if (err == -1) yyerror(&yylsp[-2], context, ""); + else if (err == 0) yyerror(&yylsp[-6], context, ""); + else yyerror(&yylsp[-3], context, ""); // parameter count wrong + + YYERROR; + } + } + break; + + case 9: +#line 84 "eel2.y" + { + int err; + if (!((yyval) = nseel_setCompiledFunctionCallParameters(context,(yyvsp[(1) - (4)]), (yyvsp[(3) - (4)]), 0, 0, 0, &err))) + { + if (err == 0) yyerror(&yylsp[-3], context, ""); + else yyerror(&yylsp[0], context, ""); // parameter count wrong + YYERROR; + } + } + break; + + case 10: +#line 94 "eel2.y" + { + int err; + if (!((yyval) = nseel_setCompiledFunctionCallParameters(context,(yyvsp[(1) - (3)]), nseel_createCompiledValue(context,0.0), 0, 0, 0,&err))) + { + if (err == 0) yyerror(&yylsp[-2], context, ""); // function not found + else yyerror(&yylsp[0], context, ""); // parameter count wrong + YYERROR; + } + } + break; + + case 11: +#line 104 "eel2.y" + { + int err; + if (!((yyval) = nseel_setCompiledFunctionCallParameters(context,(yyvsp[(1) - (6)]), (yyvsp[(3) - (6)]), (yyvsp[(5) - (6)]), 0, 0,&err))) + { + if (err == 0) yyerror(&yylsp[-5], context, ""); + else if (err == 2) yyerror(&yylsp[0], context, ""); // needs more than 2 parameters + else yyerror(&yylsp[-2], context, ""); // less than 2 + YYERROR; + } + } + break; + + case 12: +#line 115 "eel2.y" + { + int err; + if (!((yyval) = nseel_setCompiledFunctionCallParameters(context,(yyvsp[(1) - (8)]), (yyvsp[(3) - (8)]), (yyvsp[(5) - (8)]), (yyvsp[(7) - (8)]), 0, &err))) + { + if (err == 0) yyerror(&yylsp[-7], context, ""); + else if (err==2) yyerror(&yylsp[0], context, ""); // needs more parameters + else if (err==4) yyerror(&yylsp[-4], context, ""); // needs single parameter + else yyerror(&yylsp[-2], context, ""); // less parm + YYERROR; + } + } + break; + + case 13: +#line 127 "eel2.y" + { + (yyval) = nseel_createMemoryAccess(context,(yyvsp[(1) - (3)]),0); + } + break; + + case 14: +#line 131 "eel2.y" + { + (yyval) = nseel_createMemoryAccess(context,(yyvsp[(1) - (4)]),(yyvsp[(3) - (4)])); + } + break; + + case 17: +#line 140 "eel2.y" + { + (yyval) = nseel_eelMakeOpcodeFromStringSegments(context,(struct eelStringSegmentRec *)(yyvsp[(1) - (1)])); + } + break; + + case 20: +#line 150 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_ASSIGN,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 21: +#line 154 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_ADD_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 22: +#line 158 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_SUB_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 23: +#line 162 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_MOD_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 24: +#line 166 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_OR_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 25: +#line 170 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_AND_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 26: +#line 174 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_XOR_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 27: +#line 178 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_DIV_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 28: +#line 182 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_MUL_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 29: +#line 186 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_POW_OP,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 30: +#line 190 "eel2.y" + { + (yyval) = nseel_createFunctionByName(context,"strcpy",2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)]),NULL); + } + break; + + case 31: +#line 194 "eel2.y" + { + (yyval) = nseel_createFunctionByName(context,"strcat",2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)]),NULL); + } + break; + + case 33: +#line 202 "eel2.y" + { + (yyval) = (yyvsp[(2) - (2)]); + } + break; + + case 34: +#line 206 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_UMINUS,1,(yyvsp[(2) - (2)]),0); + } + break; + + case 35: +#line 210 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_NOT,1,(yyvsp[(2) - (2)]),0); + } + break; + + case 37: +#line 218 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_POW,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 39: +#line 226 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_MOD,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 40: +#line 230 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_SHL,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 41: +#line 234 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_SHR,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 43: +#line 242 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_DIVIDE,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 45: +#line 251 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_MULTIPLY,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 47: +#line 260 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_SUB,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 49: +#line 268 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_ADD,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 51: +#line 276 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_AND,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 52: +#line 280 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_OR,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 53: +#line 284 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_XOR,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 55: +#line 292 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_LT,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 56: +#line 296 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_GT,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 57: +#line 300 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_LTE,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 58: +#line 304 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_GTE,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 59: +#line 308 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_EQ,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 60: +#line 312 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_EQ_EXACT,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 61: +#line 316 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_NE,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 62: +#line 320 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_NE_EXACT,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 64: +#line 328 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_LOGICAL_AND,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 65: +#line 332 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_LOGICAL_OR,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 67: +#line 340 "eel2.y" + { + (yyval) = nseel_createIfElse(context, (yyvsp[(1) - (5)]), (yyvsp[(3) - (5)]), (yyvsp[(5) - (5)])); + } + break; + + case 68: +#line 344 "eel2.y" + { + (yyval) = nseel_createIfElse(context, (yyvsp[(1) - (4)]), 0, (yyvsp[(4) - (4)])); + } + break; + + case 69: +#line 348 "eel2.y" + { + (yyval) = nseel_createIfElse(context, (yyvsp[(1) - (3)]), (yyvsp[(3) - (3)]), 0); + } + break; + + case 71: +#line 357 "eel2.y" + { + (yyval) = nseel_createSimpleCompiledFunction(context,FN_JOIN_STATEMENTS,2,(yyvsp[(1) - (3)]),(yyvsp[(3) - (3)])); + } + break; + + case 72: +#line 361 "eel2.y" + { + (yyval) = (yyvsp[(1) - (2)]); + } + break; + + case 73: +#line 369 "eel2.y" + { + if ((yylsp[(1) - (1)]).first_line) { } + context->result = (yyvsp[(1) - (1)]); + } + break; + + +/* Line 1267 of yacc.c. */ +#line 1965 "y.tab.c" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, context, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, context, yymsg); + } + else + { + yyerror (&yylloc, context, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, context); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, context); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, context, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, context); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, context); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + +#line 376 "eel2.y" + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/y.tab.h b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/y.tab.h new file mode 100644 index 0000000..c02dace --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/eel2/y.tab.h @@ -0,0 +1,117 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + VALUE = 258, + IDENTIFIER = 259, + TOKEN_SHL = 260, + TOKEN_SHR = 261, + TOKEN_LTE = 262, + TOKEN_GTE = 263, + TOKEN_EQ = 264, + TOKEN_EQ_EXACT = 265, + TOKEN_NE = 266, + TOKEN_NE_EXACT = 267, + TOKEN_LOGICAL_AND = 268, + TOKEN_LOGICAL_OR = 269, + TOKEN_ADD_OP = 270, + TOKEN_SUB_OP = 271, + TOKEN_MOD_OP = 272, + TOKEN_OR_OP = 273, + TOKEN_AND_OP = 274, + TOKEN_XOR_OP = 275, + TOKEN_DIV_OP = 276, + TOKEN_MUL_OP = 277, + TOKEN_POW_OP = 278, + STRING_LITERAL = 279, + STRING_IDENTIFIER = 280 + }; +#endif +/* Tokens. */ +#define VALUE 258 +#define IDENTIFIER 259 +#define TOKEN_SHL 260 +#define TOKEN_SHR 261 +#define TOKEN_LTE 262 +#define TOKEN_GTE 263 +#define TOKEN_EQ 264 +#define TOKEN_EQ_EXACT 265 +#define TOKEN_NE 266 +#define TOKEN_NE_EXACT 267 +#define TOKEN_LOGICAL_AND 268 +#define TOKEN_LOGICAL_OR 269 +#define TOKEN_ADD_OP 270 +#define TOKEN_SUB_OP 271 +#define TOKEN_MOD_OP 272 +#define TOKEN_OR_OP 273 +#define TOKEN_AND_OP 274 +#define TOKEN_XOR_OP 275 +#define TOKEN_DIV_OP 276 +#define TOKEN_MUL_OP 277 +#define TOKEN_POW_OP 278 +#define STRING_LITERAL 279 +#define STRING_IDENTIFIER 280 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef int YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/fastqueue.h b/plugin-reaper-realearn/main/lib/WDL/WDL/fastqueue.h new file mode 100644 index 0000000..415b13a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/fastqueue.h @@ -0,0 +1,322 @@ +/* + WDL - fastqueue.h + Copyright (C) 2006 and later 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. + + + This file defines and implements a class which can queue arbitrary amounts of data. + It is optimized for lots of reads and writes with a significant queue (i.e. it doesnt + have to shuffle much memory around). + + The downside is that you can't just ask for a pointer to specific bytes, it may have to peice + it together into a buffer of your choosing (or you can step through the buffers using GetPtr()). + + +*/ + +#ifndef _WDL_FASTQUEUE_H_ +#define _WDL_FASTQUEUE_H_ + + +#include "ptrlist.h" + +#define WDL_FASTQUEUE_ADD_NOZEROBUF ((void *)(INT_PTR)0xf0) + +class WDL_FastQueue +{ + struct fqBuf + { + int alloc_size; + int used; + char data[8]; + }; +public: + WDL_FastQueue(int bsize=65536-64, int maxemptieskeep=-1) + { + m_avail=0; + m_bsize=bsize<32?32:bsize; + m_offs=0; + m_maxemptieskeep=maxemptieskeep; + } + ~WDL_FastQueue() + { + m_queue.Empty(true,free); + m_empties.Empty(true,free); + } + + void AddInBlocks(const void *buf, int len, int reqbsize) + { + int pass=0; + if (reqbsize<128) reqbsize=128; + while (len > 0) + { + int amt = reqbsize; + fqBuf *qb; + if (!pass++ && (qb=m_queue.Get(m_queue.GetSize()-1)) && qb->used < qb->alloc_size) + amt = qb->alloc_size - qb->used; + else + { + qb=m_empties.Get(m_empties.GetSize()-1); + if (qb) amt = qb->alloc_size; + } + + if (amt > len) amt=len; + Add(buf,amt); + if (buf) buf = (char*)buf + amt; + len -= amt; + } + } + + void *Add(const void *buf, int len) // buf can be NULL to add zeroes + { + if (len < 1) return NULL; + + fqBuf *qb=m_queue.Get(m_queue.GetSize()-1); + if (!qb || (qb->used + len) > qb->alloc_size) + { + const int esz=m_empties.GetSize()-1; + qb=m_empties.Get(esz); + m_empties.Delete(esz); + if (qb && qb->alloc_size < len) // spare buffer is not big enough, toss it + { + free(qb); + qb=NULL; + } + if (!qb) + { + const int sz=len < m_bsize ? m_bsize : len; + qb=(fqBuf *)malloc(sz + sizeof(fqBuf) - sizeof(qb->data)); + if (!qb) return NULL; + qb->alloc_size = sz; + } + qb->used=0; + m_queue.Add(qb); + } + + void *ret = qb->data + qb->used; + if (buf) + { + if (buf != WDL_FASTQUEUE_ADD_NOZEROBUF) + { + memcpy(ret, buf, len); + } + } + else + { + memset(ret, 0, len); + } + + qb->used += len; + m_avail+=len; + return ret; + } + + void Clear(int limitmaxempties=-1) + { + int x=m_queue.GetSize(); + if (limitmaxempties<0) limitmaxempties = m_maxemptieskeep; + while (x > 0) + { + if (limitmaxempties<0 || m_empties.GetSize()used; + if (m_offs < sz) break; + m_offs -= sz; + + if (m_maxemptieskeep<0 || m_empties.GetSize()used; + if (offset < sz) + { + *buf = (char *)mq->data + offset; + return sz-offset; + } + x++; + offset -= sz; + } + *buf=NULL; + return 0; + } + + int SetFromBuf(int offs, void *buf, int len) // returns length set + { + int pos=0; + while (len > 0) + { + void *p=NULL; + int l=GetPtr(offs+pos,&p); + if (!l || !p) break; + if (l > len) l=len; + memcpy(p,(char *)buf + pos,l); + pos += l; + len -= l; + } + return pos; + } + + int GetToBuf(int offs, void *buf, int len) const + { + int pos=0; + while (len > 0) + { + void *p=NULL; + int l=GetPtr(offs+pos,&p); + if (!l || !p) break; + if (l > len) l=len; + memcpy((char *)buf + pos,p,l); + pos += l; + len -= l; + } + return pos; + } + + void UnAdd(int len) + { + len=wdl_clamp(len, 0, m_avail); + if (!len) return; + m_avail -= len; + while (fqBuf *qb=m_queue.Get(m_queue.GetSize()-1)) + { + if (qb->used > len) + { + qb->used -= len; + break; + } + len -= qb->used; + if (m_maxemptieskeep < 0 || m_empties.GetSize() < m_maxemptieskeep) + { + m_empties.Add(qb); + } + else + { + free(qb); + } + m_queue.Delete(m_queue.GetSize()-1); + if (!len) break; + } + } + + void *PushFront(const void *buf, int len) + { + fqBuf *qb=NULL; + if (m_offs && m_queue.GetSize()) + { + qb=m_queue.Get(0); + const int sz=wdl_min(m_offs, len); + if (!buf) + { + memset(qb->data+m_offs-sz, 0, sz); + } + else if (buf != WDL_FASTQUEUE_ADD_NOZEROBUF) + { + memcpy(qb->data+m_offs-sz, (unsigned char*)buf+len-sz, sz); + } + m_offs -= sz; + len -= sz; + m_avail += sz; + } + if (len) + { + const int esz=m_empties.GetSize()-1; + qb=m_empties.Get(esz); + m_empties.Delete(esz); + if (qb && qb->alloc_size < len) + { + free(qb); + qb=NULL; + } + if (!qb) + { + const int sz = len < m_bsize ? m_bsize : len; + qb=(fqBuf*)malloc(sz+sizeof(fqBuf)-sizeof(qb->data)); + if (!qb) return NULL; + qb->alloc_size=sz; + } + if (!buf) + { + memset(qb->data+qb->alloc_size-len, 0, len); + } + else if (buf != WDL_FASTQUEUE_ADD_NOZEROBUF) + { + memcpy(qb->data+qb->alloc_size-len, (unsigned char*)buf, len); + } + qb->used=qb->alloc_size; + m_queue.Insert(0, qb); + m_offs=qb->used-len; + m_avail += len; + } + return qb ? qb->data+m_offs : NULL; + } + +private: + + WDL_PtrList m_queue, m_empties; + int m_offs; + int m_avail; + int m_bsize; + int m_maxemptieskeep; +} WDL_FIXALIGN; + + +#endif //_WDL_FASTQUEUE_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/fft.c b/plugin-reaper-realearn/main/lib/WDL/WDL/fft.c new file mode 100644 index 0000000..8b15d6c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/fft.c @@ -0,0 +1,1199 @@ +/* + WDL - fft.cpp + Copyright (C) 2006 and later Cockos Incorporated + Copyright 1999 D. J. Bernstein + + 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. + + + + This file implements the WDL FFT library. These routines are based on the + DJBFFT library, which are Copyright 1999 D. J. Bernstein, djb@pobox.com + + The DJB FFT web page is: http://cr.yp.to/djbfft.html + +*/ + + +// this is based on djbfft + +#include +#include "fft.h" + + +#define FFT_MAXBITLEN 15 + +#ifdef _MSC_VER +#define inline __inline +#endif + +#define PI 3.1415926535897932384626433832795 + +static WDL_FFT_COMPLEX d16[3]; +static WDL_FFT_COMPLEX d32[7]; +static WDL_FFT_COMPLEX d64[15]; +static WDL_FFT_COMPLEX d128[31]; +static WDL_FFT_COMPLEX d256[63]; +static WDL_FFT_COMPLEX d512[127]; +static WDL_FFT_COMPLEX d1024[127]; +static WDL_FFT_COMPLEX d2048[255]; +static WDL_FFT_COMPLEX d4096[511]; +static WDL_FFT_COMPLEX d8192[1023]; +static WDL_FFT_COMPLEX d16384[2047]; +static WDL_FFT_COMPLEX d32768[4095]; + + +#define sqrthalf (d16[1].re) + +#define VOL *(volatile WDL_FFT_REAL *)& + +#define TRANSFORM(a0,a1,a2,a3,wre,wim) { \ + t6 = a2.re; \ + t1 = a0.re - t6; \ + t6 += a0.re; \ + a0.re = t6; \ + t3 = a3.im; \ + t4 = a1.im - t3; \ + t8 = t1 - t4; \ + t1 += t4; \ + t3 += a1.im; \ + a1.im = t3; \ + t5 = wre; \ + t7 = t8 * t5; \ + t4 = t1 * t5; \ + t8 *= wim; \ + t2 = a3.re; \ + t3 = a1.re - t2; \ + t2 += a1.re; \ + a1.re = t2; \ + t1 *= wim; \ + t6 = a2.im; \ + t2 = a0.im - t6; \ + t6 += a0.im; \ + a0.im = t6; \ + t6 = t2 + t3; \ + t2 -= t3; \ + t3 = t6 * wim; \ + t7 -= t3; \ + a2.re = t7; \ + t6 *= t5; \ + t6 += t8; \ + a2.im = t6; \ + t5 *= t2; \ + t5 -= t1; \ + a3.im = t5; \ + t2 *= wim; \ + t4 += t2; \ + a3.re = t4; \ + } + +#define TRANSFORMHALF(a0,a1,a2,a3) { \ + t1 = a2.re; \ + t5 = a0.re - t1; \ + t1 += a0.re; \ + a0.re = t1; \ + t4 = a3.im; \ + t8 = a1.im - t4; \ + t1 = t5 - t8; \ + t5 += t8; \ + t4 += a1.im; \ + a1.im = t4; \ + t3 = a3.re; \ + t7 = a1.re - t3; \ + t3 += a1.re; \ + a1.re = t3; \ + t8 = a2.im; \ + t6 = a0.im - t8; \ + t2 = t6 + t7; \ + t6 -= t7; \ + t8 += a0.im; \ + a0.im = t8; \ + t4 = t6 + t5; \ + t3 = sqrthalf; \ + t4 *= t3; \ + a3.re = t4; \ + t6 -= t5; \ + t6 *= t3; \ + a3.im = t6; \ + t7 = t1 - t2; \ + t7 *= t3; \ + a2.re = t7; \ + t2 += t1; \ + t2 *= t3; \ + a2.im = t2; \ + } + +#define TRANSFORMZERO(a0,a1,a2,a3) { \ + t5 = a2.re; \ + t1 = a0.re - t5; \ + t5 += a0.re; \ + a0.re = t5; \ + t8 = a3.im; \ + t4 = a1.im - t8; \ + t7 = a3.re; \ + t6 = t1 - t4; \ + a2.re = t6; \ + t1 += t4; \ + a3.re = t1; \ + t8 += a1.im; \ + a1.im = t8; \ + t3 = a1.re - t7; \ + t7 += a1.re; \ + a1.re = t7; \ + t6 = a2.im; \ + t2 = a0.im - t6; \ + t7 = t2 + t3; \ + a2.im = t7; \ + t2 -= t3; \ + a3.im = t2; \ + t6 += a0.im; \ + a0.im = t6; \ + } + +#define UNTRANSFORM(a0,a1,a2,a3,wre,wim) { \ + t6 = VOL wre; \ + t1 = VOL a2.re; \ + t1 *= t6; \ + t8 = VOL wim; \ + t3 = VOL a2.im; \ + t3 *= t8; \ + t2 = VOL a2.im; \ + t4 = VOL a2.re; \ + t5 = VOL a3.re; \ + t5 *= t6; \ + t7 = VOL a3.im; \ + t1 += t3; \ + t7 *= t8; \ + t5 -= t7; \ + t3 = t5 + t1; \ + t5 -= t1; \ + t2 *= t6; \ + t6 *= a3.im; \ + t4 *= t8; \ + t2 -= t4; \ + t8 *= a3.re; \ + t6 += t8; \ + t1 = a0.re - t3; \ + t3 += a0.re; \ + a0.re = t3; \ + t7 = a1.im - t5; \ + t5 += a1.im; \ + a1.im = t5; \ + t4 = t2 - t6; \ + t6 += t2; \ + t8 = a1.re - t4; \ + t4 += a1.re; \ + a1.re = t4; \ + t2 = a0.im - t6; \ + t6 += a0.im; \ + a0.im = t6; \ + a2.re = t1; \ + a3.im = t7; \ + a3.re = t8; \ + a2.im = t2; \ + } + + +#define UNTRANSFORMHALF(a0,a1,a2,a3) { \ + t6 = sqrthalf; \ + t1 = a2.re; \ + t2 = a2.im - t1; \ + t2 *= t6; \ + t1 += a2.im; \ + t1 *= t6; \ + t4 = a3.im; \ + t3 = a3.re - t4; \ + t3 *= t6; \ + t4 += a3.re; \ + t4 *= t6; \ + t8 = t3 - t1; \ + t7 = t2 - t4; \ + t1 += t3; \ + t2 += t4; \ + t4 = a1.im - t8; \ + a3.im = t4; \ + t8 += a1.im; \ + a1.im = t8; \ + t3 = a1.re - t7; \ + a3.re = t3; \ + t7 += a1.re; \ + a1.re = t7; \ + t5 = a0.re - t1; \ + a2.re = t5; \ + t1 += a0.re; \ + a0.re = t1; \ + t6 = a0.im - t2; \ + a2.im = t6; \ + t2 += a0.im; \ + a0.im = t2; \ + } + +#define UNTRANSFORMZERO(a0,a1,a2,a3) { \ + t2 = a3.im; \ + t3 = a2.im - t2; \ + t2 += a2.im; \ + t1 = a2.re; \ + t4 = a3.re - t1; \ + t1 += a3.re; \ + t5 = a0.re - t1; \ + a2.re = t5; \ + t6 = a0.im - t2; \ + a2.im = t6; \ + t7 = a1.re - t3; \ + a3.re = t7; \ + t8 = a1.im - t4; \ + a3.im = t8; \ + t1 += a0.re; \ + a0.re = t1; \ + t2 += a0.im; \ + a0.im = t2; \ + t3 += a1.re; \ + a1.re = t3; \ + t4 += a1.im; \ + a1.im = t4; \ + } + +static void c2(register WDL_FFT_COMPLEX *a) +{ + register WDL_FFT_REAL t1; + + t1 = a[1].re; + a[1].re = a[0].re - t1; + a[0].re += t1; + + t1 = a[1].im; + a[1].im = a[0].im - t1; + a[0].im += t1; +} + +static inline void c4(register WDL_FFT_COMPLEX *a) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + + t5 = a[2].re; + t1 = a[0].re - t5; + t7 = a[3].re; + t5 += a[0].re; + t3 = a[1].re - t7; + t7 += a[1].re; + t8 = t5 + t7; + a[0].re = t8; + t5 -= t7; + a[1].re = t5; + t6 = a[2].im; + t2 = a[0].im - t6; + t6 += a[0].im; + t5 = a[3].im; + a[2].im = t2 + t3; + t2 -= t3; + a[3].im = t2; + t4 = a[1].im - t5; + a[3].re = t1 + t4; + t1 -= t4; + a[2].re = t1; + t5 += a[1].im; + a[0].im = t6 + t5; + t6 -= t5; + a[1].im = t6; +} + +static void c8(register WDL_FFT_COMPLEX *a) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + + t7 = a[4].im; + t4 = a[0].im - t7; + t7 += a[0].im; + a[0].im = t7; + + t8 = a[6].re; + t5 = a[2].re - t8; + t8 += a[2].re; + a[2].re = t8; + + t7 = a[6].im; + a[6].im = t4 - t5; + t4 += t5; + a[4].im = t4; + + t6 = a[2].im - t7; + t7 += a[2].im; + a[2].im = t7; + + t8 = a[4].re; + t3 = a[0].re - t8; + t8 += a[0].re; + a[0].re = t8; + + a[4].re = t3 - t6; + t3 += t6; + a[6].re = t3; + + t7 = a[5].re; + t3 = a[1].re - t7; + t7 += a[1].re; + a[1].re = t7; + + t8 = a[7].im; + t6 = a[3].im - t8; + t8 += a[3].im; + a[3].im = t8; + t1 = t3 - t6; + t3 += t6; + + t7 = a[5].im; + t4 = a[1].im - t7; + t7 += a[1].im; + a[1].im = t7; + + t8 = a[7].re; + t5 = a[3].re - t8; + t8 += a[3].re; + a[3].re = t8; + + t2 = t4 - t5; + t4 += t5; + + t6 = t1 - t4; + t8 = sqrthalf; + t6 *= t8; + a[5].re = a[4].re - t6; + t1 += t4; + t1 *= t8; + a[5].im = a[4].im - t1; + t6 += a[4].re; + a[4].re = t6; + t1 += a[4].im; + a[4].im = t1; + + t5 = t2 - t3; + t5 *= t8; + a[7].im = a[6].im - t5; + t2 += t3; + t2 *= t8; + a[7].re = a[6].re - t2; + t2 += a[6].re; + a[6].re = t2; + t5 += a[6].im; + a[6].im = t5; + + c4(a); +} + +static void c16(register WDL_FFT_COMPLEX *a) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + + TRANSFORMZERO(a[0],a[4],a[8],a[12]); + TRANSFORM(a[1],a[5],a[9],a[13],d16[0].re,d16[0].im); + TRANSFORMHALF(a[2],a[6],a[10],a[14]); + TRANSFORM(a[3],a[7],a[11],a[15],d16[0].im,d16[0].re); + c4(a + 8); + c4(a + 12); + + c8(a); +} + +/* a[0...8n-1], w[0...2n-2]; n >= 2 */ +static void cpass(register WDL_FFT_COMPLEX *a,register const WDL_FFT_COMPLEX *w,register unsigned int n) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + register WDL_FFT_COMPLEX *a1; + register WDL_FFT_COMPLEX *a2; + register WDL_FFT_COMPLEX *a3; + + a2 = a + 4 * n; + a1 = a + 2 * n; + a3 = a2 + 2 * n; + --n; + + TRANSFORMZERO(a[0],a1[0],a2[0],a3[0]); + TRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im); + + for (;;) { + TRANSFORM(a[2],a1[2],a2[2],a3[2],w[1].re,w[1].im); + TRANSFORM(a[3],a1[3],a2[3],a3[3],w[2].re,w[2].im); + if (!--n) break; + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + w += 2; + } +} + +static void c32(register WDL_FFT_COMPLEX *a) +{ + cpass(a,d32,4); + c8(a + 16); + c8(a + 24); + c16(a); +} + +static void c64(register WDL_FFT_COMPLEX *a) +{ + cpass(a,d64,8); + c16(a + 32); + c16(a + 48); + c32(a); +} + +static void c128(register WDL_FFT_COMPLEX *a) +{ + cpass(a,d128,16); + c32(a + 64); + c32(a + 96); + c64(a); +} + +static void c256(register WDL_FFT_COMPLEX *a) +{ + cpass(a,d256,32); + c64(a + 128); + c64(a + 192); + c128(a); +} + +static void c512(register WDL_FFT_COMPLEX *a) +{ + cpass(a,d512,64); + c128(a + 384); + c128(a + 256); + c256(a); +} + +/* a[0...8n-1], w[0...n-2]; n even, n >= 4 */ +static void cpassbig(register WDL_FFT_COMPLEX *a,register const WDL_FFT_COMPLEX *w,register unsigned int n) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + register WDL_FFT_COMPLEX *a1; + register WDL_FFT_COMPLEX *a2; + register WDL_FFT_COMPLEX *a3; + register unsigned int k; + + a2 = a + 4 * n; + a1 = a + 2 * n; + a3 = a2 + 2 * n; + k = n - 2; + + TRANSFORMZERO(a[0],a1[0],a2[0],a3[0]); + TRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + + do { + TRANSFORM(a[0],a1[0],a2[0],a3[0],w[1].re,w[1].im); + TRANSFORM(a[1],a1[1],a2[1],a3[1],w[2].re,w[2].im); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + w += 2; + } while (k -= 2); + + TRANSFORMHALF(a[0],a1[0],a2[0],a3[0]); + TRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].im,w[0].re); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + + k = n - 2; + do { + TRANSFORM(a[0],a1[0],a2[0],a3[0],w[-1].im,w[-1].re); + TRANSFORM(a[1],a1[1],a2[1],a3[1],w[-2].im,w[-2].re); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + w -= 2; + } while (k -= 2); +} + + +static void c1024(register WDL_FFT_COMPLEX *a) +{ + cpassbig(a,d1024,128); + c256(a + 768); + c256(a + 512); + c512(a); +} + +static void c2048(register WDL_FFT_COMPLEX *a) +{ + cpassbig(a,d2048,256); + c512(a + 1536); + c512(a + 1024); + c1024(a); +} + +static void c4096(register WDL_FFT_COMPLEX *a) +{ + cpassbig(a,d4096,512); + c1024(a + 3072); + c1024(a + 2048); + c2048(a); +} + +static void c8192(register WDL_FFT_COMPLEX *a) +{ + cpassbig(a,d8192,1024); + c2048(a + 6144); + c2048(a + 4096); + c4096(a); +} + +static void c16384(register WDL_FFT_COMPLEX *a) +{ + cpassbig(a,d16384,2048); + c4096(a + 8192 + 4096); + c4096(a + 8192); + c8192(a); +} + +static void c32768(register WDL_FFT_COMPLEX *a) +{ + cpassbig(a,d32768,4096); + c8192(a + 16384 + 8192); + c8192(a + 16384); + c16384(a); +} + + +/* n even, n > 0 */ +void WDL_fft_complexmul(WDL_FFT_COMPLEX *a,WDL_FFT_COMPLEX *b,int n) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + if (n<2 || (n&1)) return; + + do { + t1 = a[0].re * b[0].re; + t2 = a[0].im * b[0].im; + t3 = a[0].im * b[0].re; + t4 = a[0].re * b[0].im; + t5 = a[1].re * b[1].re; + t6 = a[1].im * b[1].im; + t7 = a[1].im * b[1].re; + t8 = a[1].re * b[1].im; + t1 -= t2; + t3 += t4; + t5 -= t6; + t7 += t8; + a[0].re = t1; + a[1].re = t5; + a[0].im = t3; + a[1].im = t7; + a += 2; + b += 2; + } while (n -= 2); +} + +void WDL_fft_complexmul2(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_FFT_COMPLEX *b, int n) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + if (n<2 || (n&1)) return; + + do { + t1 = a[0].re * b[0].re; + t2 = a[0].im * b[0].im; + t3 = a[0].im * b[0].re; + t4 = a[0].re * b[0].im; + t5 = a[1].re * b[1].re; + t6 = a[1].im * b[1].im; + t7 = a[1].im * b[1].re; + t8 = a[1].re * b[1].im; + t1 -= t2; + t3 += t4; + t5 -= t6; + t7 += t8; + c[0].re = t1; + c[1].re = t5; + c[0].im = t3; + c[1].im = t7; + a += 2; + b += 2; + c += 2; + } while (n -= 2); +} +void WDL_fft_complexmul3(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_FFT_COMPLEX *b, int n) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + if (n<2 || (n&1)) return; + + do { + t1 = a[0].re * b[0].re; + t2 = a[0].im * b[0].im; + t3 = a[0].im * b[0].re; + t4 = a[0].re * b[0].im; + t5 = a[1].re * b[1].re; + t6 = a[1].im * b[1].im; + t7 = a[1].im * b[1].re; + t8 = a[1].re * b[1].im; + t1 -= t2; + t3 += t4; + t5 -= t6; + t7 += t8; + c[0].re += t1; + c[1].re += t5; + c[0].im += t3; + c[1].im += t7; + a += 2; + b += 2; + c += 2; + } while (n -= 2); +} + + +static inline void u4(register WDL_FFT_COMPLEX *a) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + + t1 = VOL a[1].re; + t3 = a[0].re - t1; + t6 = VOL a[2].re; + t1 += a[0].re; + t8 = a[3].re - t6; + t6 += a[3].re; + a[0].re = t1 + t6; + t1 -= t6; + a[2].re = t1; + + t2 = VOL a[1].im; + t4 = a[0].im - t2; + t2 += a[0].im; + t5 = VOL a[3].im; + a[1].im = t4 + t8; + t4 -= t8; + a[3].im = t4; + + t7 = a[2].im - t5; + t5 += a[2].im; + a[1].re = t3 + t7; + t3 -= t7; + a[3].re = t3; + a[0].im = t2 + t5; + t2 -= t5; + a[2].im = t2; +} + +static void u8(register WDL_FFT_COMPLEX *a) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + + u4(a); + + t1 = a[5].re; + a[5].re = a[4].re - t1; + t1 += a[4].re; + + t3 = a[7].re; + a[7].re = a[6].re - t3; + t3 += a[6].re; + + t8 = t3 - t1; + t1 += t3; + + t6 = a[2].im - t8; + t8 += a[2].im; + a[2].im = t8; + + t5 = a[0].re - t1; + a[4].re = t5; + t1 += a[0].re; + a[0].re = t1; + + t2 = a[5].im; + a[5].im = a[4].im - t2; + t2 += a[4].im; + + t4 = a[7].im; + a[7].im = a[6].im - t4; + t4 += a[6].im; + + a[6].im = t6; + + t7 = t2 - t4; + t2 += t4; + + t3 = a[2].re - t7; + a[6].re = t3; + t7 += a[2].re; + a[2].re = t7; + + t6 = a[0].im - t2; + a[4].im = t6; + t2 += a[0].im; + a[0].im = t2; + + t6 = sqrthalf; + + t1 = a[5].re; + t2 = a[5].im - t1; + t2 *= t6; + t1 += a[5].im; + t1 *= t6; + t4 = a[7].im; + t3 = a[7].re - t4; + t3 *= t6; + t4 += a[7].re; + t4 *= t6; + + t8 = t3 - t1; + t1 += t3; + t7 = t2 - t4; + t2 += t4; + + t4 = a[3].im - t8; + a[7].im = t4; + t5 = a[1].re - t1; + a[5].re = t5; + t3 = a[3].re - t7; + a[7].re = t3; + t6 = a[1].im - t2; + a[5].im = t6; + + t8 += a[3].im; + a[3].im = t8; + t1 += a[1].re; + a[1].re = t1; + t7 += a[3].re; + a[3].re = t7; + t2 += a[1].im; + a[1].im = t2; +} + +static void u16(register WDL_FFT_COMPLEX *a) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + + u8(a); + u4(a + 8); + u4(a + 12); + + UNTRANSFORMZERO(a[0],a[4],a[8],a[12]); + UNTRANSFORMHALF(a[2],a[6],a[10],a[14]); + UNTRANSFORM(a[1],a[5],a[9],a[13],d16[0].re,d16[0].im); + UNTRANSFORM(a[3],a[7],a[11],a[15],d16[0].im,d16[0].re); +} + +/* a[0...8n-1], w[0...2n-2] */ +static void upass(register WDL_FFT_COMPLEX *a,register const WDL_FFT_COMPLEX *w,register unsigned int n) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + register WDL_FFT_COMPLEX *a1; + register WDL_FFT_COMPLEX *a2; + register WDL_FFT_COMPLEX *a3; + + a2 = a + 4 * n; + a1 = a + 2 * n; + a3 = a2 + 2 * n; + n -= 1; + + UNTRANSFORMZERO(a[0],a1[0],a2[0],a3[0]); + UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im); + + for (;;) { + UNTRANSFORM(a[2],a1[2],a2[2],a3[2],w[1].re,w[1].im); + UNTRANSFORM(a[3],a1[3],a2[3],a3[3],w[2].re,w[2].im); + if (!--n) break; + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + w += 2; + } +} + +static void u32(register WDL_FFT_COMPLEX *a) +{ + u16(a); + u8(a + 16); + u8(a + 24); + upass(a,d32,4); +} + +static void u64(register WDL_FFT_COMPLEX *a) +{ + u32(a); + u16(a + 32); + u16(a + 48); + upass(a,d64,8); +} + +static void u128(register WDL_FFT_COMPLEX *a) +{ + u64(a); + u32(a + 64); + u32(a + 96); + upass(a,d128,16); +} + +static void u256(register WDL_FFT_COMPLEX *a) +{ + u128(a); + u64(a + 128); + u64(a + 192); + upass(a,d256,32); +} + +static void u512(register WDL_FFT_COMPLEX *a) +{ + u256(a); + u128(a + 256); + u128(a + 384); + upass(a,d512,64); +} + + +/* a[0...8n-1], w[0...n-2]; n even, n >= 4 */ +static void upassbig(register WDL_FFT_COMPLEX *a,register const WDL_FFT_COMPLEX *w,register unsigned int n) +{ + register WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; + register WDL_FFT_COMPLEX *a1; + register WDL_FFT_COMPLEX *a2; + register WDL_FFT_COMPLEX *a3; + register unsigned int k; + + a2 = a + 4 * n; + a1 = a + 2 * n; + a3 = a2 + 2 * n; + k = n - 2; + + UNTRANSFORMZERO(a[0],a1[0],a2[0],a3[0]); + UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + + do { + UNTRANSFORM(a[0],a1[0],a2[0],a3[0],w[1].re,w[1].im); + UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[2].re,w[2].im); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + w += 2; + } while (k -= 2); + + UNTRANSFORMHALF(a[0],a1[0],a2[0],a3[0]); + UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].im,w[0].re); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + + k = n - 2; + do { + UNTRANSFORM(a[0],a1[0],a2[0],a3[0],w[-1].im,w[-1].re); + UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[-2].im,w[-2].re); + a += 2; + a1 += 2; + a2 += 2; + a3 += 2; + w -= 2; + } while (k -= 2); +} + + + +static void u1024(register WDL_FFT_COMPLEX *a) +{ + u512(a); + u256(a + 512); + u256(a + 768); + upassbig(a,d1024,128); +} + +static void u2048(register WDL_FFT_COMPLEX *a) +{ + u1024(a); + u512(a + 1024); + u512(a + 1536); + upassbig(a,d2048,256); +} + + +static void u4096(register WDL_FFT_COMPLEX *a) +{ + u2048(a); + u1024(a + 2048); + u1024(a + 3072); + upassbig(a,d4096,512); +} + +static void u8192(register WDL_FFT_COMPLEX *a) +{ + u4096(a); + u2048(a + 4096); + u2048(a + 6144); + upassbig(a,d8192,1024); +} + +static void u16384(register WDL_FFT_COMPLEX *a) +{ + u8192(a); + u4096(a + 8192); + u4096(a + 8192 + 4096); + upassbig(a,d16384,2048); +} + +static void u32768(register WDL_FFT_COMPLEX *a) +{ + u16384(a); + u8192(a + 16384); + u8192(a + 16384 + 8192 ); + upassbig(a,d32768,4096); +} + + +static void __fft_gen(WDL_FFT_COMPLEX *buf, const WDL_FFT_COMPLEX *buf2, int sz, int isfull) +{ + int x; + double div=PI*0.25/(sz+1); + + if (isfull) div*=2.0; + + for (x = 0; x < sz; x ++) + { + if (!(x & 1) || !buf2) + { + buf[x].re = (WDL_FFT_REAL) cos((x+1)*div); + buf[x].im = (WDL_FFT_REAL) sin((x+1)*div); + } + else + { + buf[x].re = buf2[x >> 1].re; + buf[x].im = buf2[x >> 1].im; + } + } +} + +#ifndef WDL_FFT_NO_PERMUTE + +static unsigned int fftfreq_c(unsigned int i,unsigned int n) +{ + unsigned int m; + + if (n <= 2) return i; + + m = n >> 1; + if (i < m) return fftfreq_c(i,m) << 1; + + i -= m; + m >>= 1; + if (i < m) return (fftfreq_c(i,m) << 2) + 1; + i -= m; + return ((fftfreq_c(i,m) << 2) - 1) & (n - 1); +} + +static int _idxperm[2<> 1, quart = half >> 1, eighth = quart >> 1; + const int *permute = WDL_fft_permute_tab(half); + unsigned int i, j; + + WDL_FFT_COMPLEX *p, *q, tw, sum, diff; + WDL_FFT_REAL tw1, tw2; + + if (!isInverse) + { + WDL_fft((WDL_FFT_COMPLEX*)buf, half, isInverse); + r2(buf); + } + else + { + v2(buf); + } + + /* Source: http://www.katjaas.nl/realFFT/realFFT2.html */ + + for (i = 1; i < quart; ++i) + { + p = (WDL_FFT_COMPLEX*)buf + permute[i]; + q = (WDL_FFT_COMPLEX*)buf + permute[half - i]; + +/* tw.re = cos(2*PI * i / len); + tw.im = sin(2*PI * i / len); */ + + if (i < eighth) + { + j = i - 1; + tw.re = d[j].re; + tw.im = d[j].im; + } + else if (i > eighth) + { + j = quart - i - 1; + tw.re = d[j].im; + tw.im = d[j].re; + } + else + { + tw.re = tw.im = sqrthalf; + } + + if (!isInverse) tw.re = -tw.re; + + sum.re = p->re + q->re; + sum.im = p->im + q->im; + diff.re = p->re - q->re; + diff.im = p->im - q->im; + + tw1 = tw.re * sum.im + tw.im * diff.re; + tw2 = tw.im * sum.im - tw.re * diff.re; + + p->re = sum.re - tw1; + p->im = diff.im - tw2; + q->re = sum.re + tw1; + q->im = -(diff.im + tw2); + } + + p = (WDL_FFT_COMPLEX*)buf + permute[i]; + p->re *= 2; + p->im *= -2; + + if (isInverse) WDL_fft((WDL_FFT_COMPLEX*)buf, half, isInverse); +} + +void WDL_real_fft(WDL_FFT_REAL* buf, int len, int isInverse) +{ + switch (len) + { + case 2: if (!isInverse) r2(buf); else v2(buf); break; + case 4: case 8: two_for_one(buf, 0, len, isInverse); break; +#define TMP(x) case x: two_for_one(buf, d##x, len, isInverse); break; + TMP(16) + TMP(32) + TMP(64) + TMP(128) + TMP(256) + TMP(512) + TMP(1024) + TMP(2048) + TMP(4096) + TMP(8192) + TMP(16384) + TMP(32768) +#undef TMP + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/fft.h b/plugin-reaper-realearn/main/lib/WDL/WDL/fft.h new file mode 100644 index 0000000..d2be245 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/fft.h @@ -0,0 +1,77 @@ +/* + WDL - fft.h + Copyright (C) 2006 and later 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. + + + + This file defines the interface to the WDL FFT library. These routines are based on the + DJBFFT library, which are Copyright 1999 D. J. Bernstein, djb@pobox.com + + The DJB FFT web page is: http://cr.yp.to/djbfft.html + +*/ + +#ifndef _WDL_FFT_H_ +#define _WDL_FFT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WDL_FFT_REALSIZE +#define WDL_FFT_REALSIZE 4 +#endif + +#if WDL_FFT_REALSIZE == 4 +typedef float WDL_FFT_REAL; +#elif WDL_FFT_REALSIZE == 8 +typedef double WDL_FFT_REAL; +#else +#error invalid FFT item size +#endif + +typedef struct { + WDL_FFT_REAL re; + WDL_FFT_REAL im; +} WDL_FFT_COMPLEX; + +extern void WDL_fft_init(); + +extern void WDL_fft_complexmul(WDL_FFT_COMPLEX *dest, WDL_FFT_COMPLEX *src, int len); +extern void WDL_fft_complexmul2(WDL_FFT_COMPLEX *dest, WDL_FFT_COMPLEX *src, WDL_FFT_COMPLEX *src2, int len); +extern void WDL_fft_complexmul3(WDL_FFT_COMPLEX *destAdd, WDL_FFT_COMPLEX *src, WDL_FFT_COMPLEX *src2, int len); + +/* Expects WDL_FFT_COMPLEX input[0..len-1] scaled by 1.0/len, returns +WDL_FFT_COMPLEX output[0..len-1] order by WDL_fft_permute(len). */ +extern void WDL_fft(WDL_FFT_COMPLEX *, int len, int isInverse); + +/* Expects WDL_FFT_REAL input[0..len-1] scaled by 0.5/len, returns +WDL_FFT_COMPLEX output[0..len/2-1], for len >= 4 order by +WDL_fft_permute(len/2). Note that output[len/2].re is stored in +output[0].im. */ +extern void WDL_real_fft(WDL_FFT_REAL *, int len, int isInverse); + +extern int WDL_fft_permute(int fftsize, int idx); +extern int *WDL_fft_permute_tab(int fftsize); + +#ifdef __cplusplus +}; +#endif + +#endif \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/filebrowse.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/filebrowse.cpp new file mode 100644 index 0000000..f2d979e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/filebrowse.cpp @@ -0,0 +1,493 @@ +// todo: support win7/vista extensions rather than GetOpenFileName? -- merge win7filedialog into here. + + +#include "filebrowse.h" + +#include "win32_utf8.h" +#include "wdlcstring.h" + + +#ifdef _WIN32 + #ifdef _MSC_VER // todo: win7filedialog.cpp support for mingw32 + #define WDL_FILEBROWSE_WIN7VISTAMODE + #endif +#endif + + + + +#ifdef WDL_FILEBROWSE_WIN7VISTAMODE // win7/vista file dialog support + #include "win7filedialog.cpp" + + // stuff since win7filedialog.h collides with shlobj.h below + #define tagSHCONTF tagSHCONTF___ + #define SHCONTF SHCONTF___ + #define SHCONTF_FOLDERS SHCONTF_FOLDERS___ + #define SHCONTF_NONFOLDERS SHCONTF_NONFOLDERS___ + #define SHCONTF_INCLUDEHIDDEN SHCONTF_INCLUDEHIDDEN___ + #define SHCONTF_SHAREABLE SHCONTF_SHAREABLE__ + #define SHCONTF_INIT_ON_FIRST_NEXT SHCONTF_INIT_ON_FIRST_NEXT__ + #define SHCONTF_NETPRINTERSRCH SHCONTF_NETPRINTERSRCH__ + #define SHCONTF_STORAGE SHCONTF_STORAGE__ +#endif + + +#ifdef _WIN32 +// include after win7filedialog.* + + #include + #include +#endif + +#ifndef BIF_NEWDIALOGSTYLE + #define BIF_NEWDIALOGSTYLE 0x40 +#endif + + +static void WDL_fixfnforopenfn(char *buf) +{ + char *p=buf; + while (*p) + { + if (WDL_IS_DIRCHAR(*p)) *p = WDL_DIRCHAR; + p++; + } +#ifdef _WIN32 + if (buf[0] && buf[1] == ':') + { + p=buf+2; + char *op=p; + while (*p) + { + while (p[0]==WDL_DIRCHAR && p[1] == WDL_DIRCHAR) p++; + *op++ = *p++; + } + *op=0; + } +#endif +} + + +#ifdef _WIN32 +static int CALLBACK WINAPI WDL_BrowseCallbackProc( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + switch (uMsg) + { + case BFFM_INITIALIZED: + { + if (lpData && ((char *)lpData)[0]) + { +#ifndef WDL_NO_SUPPORT_UTF8 + WDL_UTF8_SendBFFM_SETSEL(hwnd, (const char *)lpData); +#else + SendMessage(hwnd, BFFM_SETSELECTION, 1, lpData); +#endif + } + break; + } + } + return 0; +} +#endif + + +bool WDL_ChooseDirectory(HWND parent, const char *text, const char *initialdir, char *fn, int fnsize, bool preservecwd) +{ + char olddir[2048]; + GetCurrentDirectory(sizeof(olddir),olddir); +#ifdef _WIN32 + char name[4096]; + lstrcpyn_safe(name,initialdir?initialdir:"",sizeof(name)); + BROWSEINFO bi={parent,NULL, name, text, BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE, WDL_BrowseCallbackProc, (LPARAM)name,}; + LPITEMIDLIST idlist = SHBrowseForFolderUTF8( &bi ); + if (idlist && SHGetPathFromIDListUTF8(idlist, name, sizeof(name))) + { + IMalloc *m; + SHGetMalloc(&m); + m->Free(idlist); + lstrcpyn_safe(fn,name,fnsize); + return true; + } + return false; + +#else + bool r = BrowseForDirectory(text,initialdir,fn,fnsize); + if (preservecwd) SetCurrentDirectory(olddir); + return r; +#endif +} + + +#ifdef _WIN32 +struct WDL_FileBrowse_Dis { + enum { DLSZ = 10 }; + WDL_FileBrowse_Dis(HWND par) + { + m_par = par; + memset(m_dislist,0,sizeof(m_dislist)); + if (par) EnableOwnerWnds(par,0); + } + ~WDL_FileBrowse_Dis() + { + if (m_dislist[0]) EnableOwnerWnds(m_par, 1); + } + void EnableOwnerWnds(HWND p, int en) + { + int limit = 0, dlsz = 0; + HWND t; + while (NULL != (t = GetParent(p)) && limit++ < 20) + { + p = t; + if (!(GetWindowLong(p,GWL_STYLE)&WS_CHILD) && !en != !IsWindowEnabled(p)) + { + if (!en) + { + EnableWindow(p,0); + m_dislist[dlsz] = p; + if (++dlsz == DLSZ) break; + } + else + { + for (dlsz = 0; dlsz < DLSZ && m_dislist[dlsz] != p && m_dislist[dlsz]; dlsz++); + if (dlsz < DLSZ && m_dislist[dlsz] == p) EnableWindow(p,1); + } + } + } + } + HWND m_par, m_dislist[DLSZ]; +}; +#endif + +#ifdef _WIN32 +bool wdl_use_legacy_filebrowse; +#endif + +bool WDL_ChooseFileForSave(HWND parent, + const char *text, + const char *initialdir, + const char *initialfile, + const char *extlist, + const char *defext, + bool preservecwd, + char *fn, + int fnsize, + const char *dlgid, + void *dlgProc, +#ifdef _WIN32 + HINSTANCE hInstance +#else + struct SWELL_DialogResourceIndex *reshead +#endif + ) +{ + char cwd[2048]; + GetCurrentDirectory(sizeof(cwd),cwd); + + while (defext && *defext == '.') defext++; // this function can be passed defext of either .wav or wav, we always want it to be the latter + +#ifdef _WIN32 + WDL_FileBrowse_Dis win32disfix(parent); + char temp[4096]; + memset(temp,0,sizeof(temp)); + if (initialfile) lstrcpyn_safe(temp,initialfile,sizeof(temp)); + WDL_fixfnforopenfn(temp); + +#ifdef WDL_FILEBROWSE_WIN7VISTAMODE + if (!wdl_use_legacy_filebrowse) + { + Win7FileDialog fd(text, 1); + if(fd.inited()) + { + fd.addOptions(FOS_DONTADDTORECENT); + //vista+ file open dialog + char olddir[2048]; + GetCurrentDirectory(sizeof(olddir),olddir); + + fd.setFilterList(extlist); + if (defext) + { + fd.setDefaultExtension(defext); + + int i = 0; + const char *p = extlist; + while(*p) + { + if(*p) p+=strlen(p)+1; + if(!*p) break; + const char *ext = WDL_get_fileext(p); + if (*ext == '.') ext++; + if(!stricmp(ext,defext)) + { + fd.setFileTypeIndex(i+1); + break; + } + i++; + p+=strlen(p)+1; + } + } + fd.setFolder(initialdir?initialdir:olddir, 0); + if(initialfile) + { + //check for folder name + if (WDL_remove_filepart(temp)) + { + //folder found + fd.setFolder(temp, 0); + fd.setFilename(temp + strlen(temp) + 1); + } + else + fd.setFilename(*temp ? temp : initialfile); + } + fd.setTemplate(hInstance, dlgid, (LPOFNHOOKPROC)dlgProc); + + if(fd.show(parent)) + { + //ifilesavedialog saves the last folder automatically + fd.getResult(fn, fnsize); + + if (preservecwd) SetCurrentDirectory(olddir); + return true; + } + + if (preservecwd) SetCurrentDirectory(olddir); + return NULL; + } + } +#endif + + + OPENFILENAME l={sizeof(l),parent, hInstance, extlist, NULL,0, 0, temp, sizeof(temp)-1, NULL, 0, initialdir&&initialdir[0] ? initialdir : cwd, text, + OFN_HIDEREADONLY|OFN_EXPLORER|OFN_OVERWRITEPROMPT,0,0,defext, 0, (LPOFNHOOKPROC)dlgProc, dlgid}; + + if (hInstance&&dlgProc&&dlgid) l.Flags |= OFN_ENABLEHOOK|OFN_ENABLETEMPLATE|OFN_ENABLESIZING; + if (preservecwd) l.Flags |= OFN_NOCHANGEDIR; + + if (!GetSaveFileName(&l)||!temp[0]) + { + if (preservecwd) SetCurrentDirectory(cwd); + return false; + } + if (preservecwd) SetCurrentDirectory(cwd); + lstrcpyn_safe(fn,temp,fnsize); + return true; + +#else + BrowseFile_SetTemplate(dlgid,(DLGPROC)dlgProc,reshead); + char if_temp[4096]; + if (initialfile && *initialfile) + { + lstrcpyn_safe(if_temp,initialfile,sizeof(if_temp)); + WDL_fixfnforopenfn(if_temp); + initialfile = if_temp; + } + else + { + if (defext && *defext) + { + snprintf(if_temp,sizeof(if_temp),".%s",defext); // SWELL treats initialfile of ".ext" as default extension + initialfile = if_temp; + } + } + + bool r = BrowseForSaveFile(text,initialdir,initialfile,extlist,fn,fnsize); + + if (preservecwd) SetCurrentDirectory(cwd); + + return r; +#endif +} + + +char *WDL_ChooseFileForOpen2(HWND parent, + const char *text, + const char *initialdir, + const char *initialfile, + const char *extlist, + const char *defext, + + bool preservecwd, + int allowmul, + + const char *dlgid, + void *dlgProc, +#ifdef _WIN32 + HINSTANCE hInstance +#else + struct SWELL_DialogResourceIndex *reshead +#endif + ) +{ + char olddir[2048]; + GetCurrentDirectory(sizeof(olddir),olddir); + +#ifdef _WIN32 + WDL_FileBrowse_Dis win32disfix(parent); + +#ifdef WDL_FILEBROWSE_WIN7VISTAMODE + if (!wdl_use_legacy_filebrowse && allowmul!=1) + { + Win7FileDialog fd(text); + if(fd.inited()) + { + //vista+ file open dialog + fd.addOptions(FOS_FILEMUSTEXIST|(allowmul?FOS_ALLOWMULTISELECT:0)); + fd.setFilterList(extlist); + if (defext) + { + fd.setDefaultExtension(defext); + + int i = 0; + const char *p = extlist; + while(*p) + { + if(*p) p+=strlen(p)+1; + if(!*p) break; + if(WDL_stristr(p, defext)) + { + fd.setFileTypeIndex(i+1); + break; + } + i++; + p+=strlen(p)+1; + } + } + fd.setFolder(initialdir?initialdir:olddir, 0); + fd.setTemplate(hInstance, dlgid, (LPOFNHOOKPROC)dlgProc); + if(initialfile) + { + char temp[4096]; + lstrcpyn_safe(temp,initialfile,sizeof(temp)); + WDL_fixfnforopenfn(temp); + //check for folder name + if (WDL_remove_filepart(temp)) + { + //folder found + fd.setFolder(temp, 0); + fd.setFilename(temp + strlen(temp) + 1); + } + else + fd.setFilename(temp); + } + + if(fd.show(parent)) + { + char *ret=NULL; + char temp[4096]; + temp[0]=0; + + //ifileopendialog saves the last folder automatically + if (!allowmul) + { + fd.getResult(temp, sizeof(temp)-1); + ret= temp[0] ? strdup(temp) : NULL; + } + else + { + char* p=NULL; + int totallen=0, cnt=fd.getResultCount(); + if (cnt>1) + { + // sets an empty path as 1st returned string for multipath support + // (when selecting files among search results for ex.) + ret = strdup(""); + totallen=1; + } + + int i; + for (i=0; i +#else +#include "swell/swell.h" +#endif + +bool WDL_ChooseDirectory(HWND parent, const char *text, const char *initialdir, char *fn, int fnsize, bool preservecwd); +bool WDL_ChooseFileForSave(HWND parent, + const char *text, + const char *initialdir, + const char *initialfile, + const char *extlist, + const char *defext, + bool preservecwd, + char *fn, + int fnsize, + const char *dlgid=NULL, + void *dlgProc=NULL, +#ifdef _WIN32 + HINSTANCE hInstance=NULL +#else + struct SWELL_DialogResourceIndex *reshead=NULL +#endif + ); + + +char *WDL_ChooseFileForOpen(HWND parent, + const char *text, + const char *initialdir, + const char *initialfile, + const char *extlist, + const char *defext, + + bool preservecwd, + bool allowmul, + + const char *dlgid=NULL, + void *dlgProc=NULL, +#ifdef _WIN32 + HINSTANCE hInstance=NULL +#else + struct SWELL_DialogResourceIndex *reshead=NULL +#endif + ); + + +// allowmul=1: multi-selection in same folder, +// the double NULL terminated return value of WDL_ChooseFileForOpen2 is like: +// "some/single/path\0bla1.txt\0bla2.txt\0bla3.txt\0\0" +// allowmul=2: multi-selection with multipath support (vista+) +// the double NULL terminated return value of WDL_ChooseFileForOpen2 is like: +// "\0/some/path1/bla1.txt\0/some/path2/bla2.txt\0/some/path3/bla3.txt\0\0" +char *WDL_ChooseFileForOpen2(HWND parent, + const char *text, + const char *initialdir, + const char *initialfile, + const char *extlist, + const char *defext, + + bool preservecwd, + int allowmul, + + const char *dlgid=NULL, + void *dlgProc=NULL, +#ifdef _WIN32 + HINSTANCE hInstance=NULL +#else + struct SWELL_DialogResourceIndex *reshead=NULL +#endif + ); + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/filename.h b/plugin-reaper-realearn/main/lib/WDL/WDL/filename.h new file mode 100644 index 0000000..bcad07b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/filename.h @@ -0,0 +1,81 @@ +/* + WDL - filename.h + Copyright (C) 2005 and later, 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. + +*/ + +#ifndef _WDL_FILENAME_H_ +#define _WDL_FILENAME_H_ + +#include "wdltypes.h" + +static WDL_STATICFUNC_UNUSED char WDL_filename_filterchar(char p, char repl='_', bool filterSlashes=true) +{ + if (p == '?' || + p == '*' || + (p > 0 && p <= 0x1f) || + p == ':' || + p == '\"' || + p == '|' || + p == '<' || + p == '>') + { + return repl; + } + + if (filterSlashes && (p == '/' || p == '\\' )) + { + return repl; + } + + return p; +} + +static WDL_STATICFUNC_UNUSED void WDL_filename_filterstr(char *rd, char repl='_', int path_filter_mode=1) +{ + char *wr, lc = WDL_DIRCHAR; + // path_filter_mode: + // 0 = remove leading slashes, consolidate duplicate slashes (use when filtering the filepart but allowing subdirectories) + // >0 = filter slashes (use when filtering the filepart, disallowing subdirectories) + // <0 = allow absolute paths, consolidate duplicate slashes (filtering a full path) + if (path_filter_mode<0) + { + #ifdef _WIN32 + if (rd[0] && rd[1] == ':' && WDL_IS_DIRCHAR(rd[2])) rd += 3; + else if (WDL_IS_DIRCHAR(rd[0]) && WDL_IS_DIRCHAR(rd[1])) rd += 2; + #else + if (WDL_IS_DIRCHAR(*rd)) rd++; + #endif + } + wr = rd; + while (*rd) + { + char r=WDL_filename_filterchar(*rd++,repl,path_filter_mode>0); + if (!r || (WDL_IS_DIRCHAR(r) && WDL_IS_DIRCHAR(lc))) continue; // filter multiple slashes in a row, or leading slash +#ifdef _WIN32 + if (r == ' ' && WDL_IS_DIRCHAR(lc)) continue; // filter space after slash (problematic on win32, allowed on macOS/linux) +#endif + *wr++ = lc = r; + } + *wr=0; +} + + + +#endif // _WDL_FILENAME_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/fileread.h b/plugin-reaper-realearn/main/lib/WDL/WDL/fileread.h new file mode 100644 index 0000000..60dd954 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/fileread.h @@ -0,0 +1,847 @@ +/* + WDL - fileread.h + Copyright (C) 2005 and later 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. + + + This file provides the WDL_FileRead object, which can be used to read files. + On windows systems it supports reading synchronous, asynchronous, memory mapped, and asynchronous unbuffered. + On non-windows systems it acts as a wrapper for fopen()/etc. + + +*/ + + +#ifndef _WDL_FILEREAD_H_ +#define _WDL_FILEREAD_H_ + + + + +#include "ptrlist.h" + + + +#if defined(_WIN32) && !defined(WDL_NO_WIN32_FILEREAD) + #ifndef WDL_WIN32_NATIVE_READ + #define WDL_WIN32_NATIVE_READ + #endif +#else + #ifdef WDL_WIN32_NATIVE_READ + #undef WDL_WIN32_NATIVE_READ + #endif + + #if !defined(WDL_NO_POSIX_FILEREAD) + #define WDL_POSIX_NATIVE_READ + #include + #include + #include + #include + #include + #ifdef __APPLE__ + #include + #include + #endif + extern struct stat wdl_stat_chk; + // if this fails on linux, use CFLAGS += -D_FILE_OFFSET_BITS=64 + typedef char wdl_fileread_assert_failed_stat_not_64[sizeof(wdl_stat_chk.st_size)!=8 ? -1 : 1]; + typedef char wdl_fileread_assert_failed_off_t_64[sizeof(off_t)!=8 ? -1 : 1]; + #endif + +#endif + + + +#ifdef _MSC_VER +#define WDL_FILEREAD_POSTYPE __int64 +#else +#define WDL_FILEREAD_POSTYPE long long +#endif +class WDL_FileRead +{ + +#ifdef WDL_WIN32_NATIVE_READ + + +class WDL_FileRead__ReadEnt +{ +public: + WDL_FileRead__ReadEnt(int sz, char *buf) + { + m_size=0; + memset(&m_ol,0,sizeof(m_ol)); + m_ol.hEvent=CreateEvent(NULL,TRUE,TRUE,NULL); + m_buf=buf; + } + ~WDL_FileRead__ReadEnt() + { + CloseHandle(m_ol.hEvent); + } + + OVERLAPPED m_ol; + DWORD m_size; + LPVOID m_buf; +}; + +#endif + +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + BOOL HasUTF8(const char *_str) + { + const unsigned char *str = (const unsigned char *)_str; + if (!str) return FALSE; + while (*str) + { + unsigned char c = *str; + if (c >= 0xC2) + { + if (c <= 0xDF && str[1] >=0x80 && str[1] <= 0xBF) return TRUE; + else if (c <= 0xEF && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + else if (c <= 0xF4 && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + } + str++; + if (((const char *)str-_str) >= 256) return TRUE; // long filenames get converted to wide + } + return FALSE; + } +#endif + +public: + // allow_async=1 for unbuffered async, 2 for buffered async, =-1 for unbuffered sync + // async aspect is unused on OS X, but the buffered mode affects F_NOCACHE + WDL_FileRead(const char *filename, int allow_async=1, int bufsize=8192, int nbufs=4, unsigned int mmap_minsize=0, unsigned int mmap_maxsize=0) : m_bufspace(4096) + { + m_async_hashaderr=false; + m_sync_bufmode_used=m_sync_bufmode_pos=0; + m_async_readpos=m_file_position=0; + m_fsize=0; + m_fsize_maychange=false; + m_syncrd_firstbuf=true; + m_mmap_view=0; + m_mmap_totalbufmode=0; + +#define WDL_UNBUF_ALIGN 8192 + if (bufsize&(WDL_UNBUF_ALIGN-1)) bufsize=(bufsize&~(WDL_UNBUF_ALIGN-1))+WDL_UNBUF_ALIGN; // ensure bufsize is multiple of 4kb + +#ifdef WDL_WIN32_NATIVE_READ + + m_mmap_fmap=0; + + #ifdef WDL_SUPPORT_WIN9X + const bool isNT = GetVersion()<0x80000000; + #else + const bool isNT = true; + #endif + m_async = isNT ? allow_async : 0; + + int flags=FILE_ATTRIBUTE_NORMAL; + if (m_async>0) + { + flags|=FILE_FLAG_OVERLAPPED; + if (m_async==1) flags|=FILE_FLAG_NO_BUFFERING; + } + else if (nbufs*bufsize>=WDL_UNBUF_ALIGN && !mmap_maxsize && m_async==-1) + flags|=FILE_FLAG_NO_BUFFERING; // non-async mode unbuffered if we do our own buffering + +#ifndef WDL_NO_SUPPORT_UTF8 + m_fh = INVALID_HANDLE_VALUE; + if (isNT && HasUTF8(filename)) // only convert to wide if there are UTF-8 chars + { + int szreq=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,NULL,0); + if (szreq > 1000) + { + WDL_TypedBuf wfilename; + wfilename.Resize(szreq+20); + + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wfilename.Get(),wfilename.GetSize()-10)) + { + correctlongpath(wfilename.Get()); + m_fh = CreateFileW(wfilename.Get(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,flags,NULL); + if (m_fh == INVALID_HANDLE_VALUE && GetLastError()==ERROR_SHARING_VIOLATION) + { + m_fh = CreateFileW(wfilename.Get(),GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,flags,NULL); + m_fsize_maychange=true; + } + } + } + else + { + WCHAR wfilename[1024]; + + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wfilename,1024-10)) + { + correctlongpath(wfilename); + m_fh = CreateFileW(wfilename,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,flags,NULL); + if (m_fh == INVALID_HANDLE_VALUE && GetLastError()==ERROR_SHARING_VIOLATION) + { + m_fh = CreateFileW(wfilename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,flags,NULL); + m_fsize_maychange=true; + } + } + } + } + if (m_fh == INVALID_HANDLE_VALUE) +#endif + { + m_fh = CreateFileA(filename,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,flags,NULL); + if (m_fh == INVALID_HANDLE_VALUE && GetLastError()==ERROR_SHARING_VIOLATION) + { + m_fh = CreateFileA(filename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,flags,NULL); + m_fsize_maychange=true; + } + } + + if (m_fh != INVALID_HANDLE_VALUE) + { + DWORD h=0; + DWORD l=GetFileSize(m_fh,&h); + m_fsize=(((WDL_FILEREAD_POSTYPE)h)<<32)|l; + if (m_fsize<0 || (l == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)) m_fsize=0; + + if (!h && l < mmap_maxsize && m_async<=0) + { + if (l >= mmap_minsize) + { + m_mmap_fmap=CreateFileMapping(m_fh,NULL,PAGE_READONLY,NULL,0,NULL); + if (m_mmap_fmap) + { + m_mmap_view=MapViewOfFile(m_mmap_fmap,FILE_MAP_READ,0,0,(int)m_fsize); + if (!m_mmap_view) + { + CloseHandle(m_mmap_fmap); + m_mmap_fmap=0; + } + else m_fsize_maychange=false; + } + } + else if (l>0) + { + m_mmap_totalbufmode = malloc(l); + if (m_mmap_totalbufmode) + { + DWORD sz; + ReadFile(m_fh,m_mmap_totalbufmode,l,&sz,NULL); + } + m_fsize_maychange=false; + } + } + + if (m_async>0) + { + m_async_bufsize=bufsize; + int x; + char *bptr=(char *)m_bufspace.Resize(nbufs*bufsize + (WDL_UNBUF_ALIGN-1)); + int a=((int)(INT_PTR)bptr)&(WDL_UNBUF_ALIGN-1); + if (a) bptr += WDL_UNBUF_ALIGN-a; + for (x = 0; x < nbufs; x ++) + { + WDL_FileRead__ReadEnt *t=new WDL_FileRead__ReadEnt(m_async_bufsize,bptr); + m_empties.Add(t); + bptr+=m_async_bufsize; + } + } + else if (!m_mmap_view && !m_mmap_totalbufmode && nbufs*bufsize>=WDL_UNBUF_ALIGN) + { + m_bufspace.Resize(nbufs*bufsize+(WDL_UNBUF_ALIGN-1)); + } + } + +#elif defined(WDL_POSIX_NATIVE_READ) + m_filedes_locked=false; + m_filedes_rdpos=0; + m_filedes=open(filename,O_RDONLY + // todo: use fcntl() for platforms when O_CLOEXEC is not available (if we ever need to support them) + // (currently the only platform that meets this criteria is macOS w/ old SDK, but we don't use execve() + // there +#ifdef O_CLOEXEC + | O_CLOEXEC +#endif + ); + if (m_filedes>=0) + { + if (flock(m_filedes,LOCK_SH|LOCK_NB)>=0) // get shared lock + m_filedes_locked=true; + else + m_fsize_maychange=true; // if couldnt get shared lock, then it may change + +#ifdef __APPLE__ + if (allow_async==1 || allow_async==-1) + { + struct statfs sfs; + if (fstatfs(m_filedes,&sfs)||(sfs.f_flags&MNT_LOCAL)) // don't use F_NOCACHE on nfs/smb/afp mounts, we need caching there! + fcntl(m_filedes,F_NOCACHE,1); + } +#endif + m_fsize=lseek(m_filedes,0,SEEK_END); + lseek(m_filedes,0,SEEK_SET); + if (m_fsize<0) m_fsize=0; + + if (m_fsize < mmap_maxsize) + { + if (m_fsize >= mmap_minsize) + { + m_mmap_view = mmap(NULL,(size_t)m_fsize,PROT_READ,MAP_SHARED,m_filedes,0); + if (m_mmap_view == MAP_FAILED) m_mmap_view = 0; + else m_fsize_maychange=false; + } + else + { + m_mmap_totalbufmode = malloc((size_t)m_fsize); + if (m_mmap_totalbufmode) + m_fsize = pread(m_filedes,m_mmap_totalbufmode,(size_t)m_fsize,0); + m_fsize_maychange=false; + } + } + } + if (!m_mmap_view && !m_mmap_totalbufmode && m_filedes>=0 && nbufs*bufsize>=WDL_UNBUF_ALIGN) + m_bufspace.Resize(nbufs*bufsize+(WDL_UNBUF_ALIGN-1)); + +#else + m_fp=fopen(filename,"rb"); + if(m_fp) + { + fseek(m_fp,0,SEEK_END); + m_fsize=ftell(m_fp); + fseek(m_fp,0,SEEK_SET); + } + if (m_fp && nbufs*bufsize>=WDL_UNBUF_ALIGN) + m_bufspace.Resize(nbufs*bufsize+(WDL_UNBUF_ALIGN-1)); +#endif + } + + ~WDL_FileRead() + { + free(m_mmap_totalbufmode); + m_mmap_totalbufmode=0; + +#ifdef WDL_WIN32_NATIVE_READ + int x; + for (x = 0; x < m_empties.GetSize();x ++) delete m_empties.Get(x); + m_empties.Empty(); + for (x = 0; x < m_full.GetSize();x ++) delete m_full.Get(x); + m_full.Empty(); + for (x = 0; x < m_pending.GetSize();x ++) + { + WaitForSingleObject(m_pending.Get(x)->m_ol.hEvent,INFINITE); + delete m_pending.Get(x); + } + m_pending.Empty(); + + if (m_mmap_view) UnmapViewOfFile(m_mmap_view); + m_mmap_view=0; + + if (m_mmap_fmap) CloseHandle(m_mmap_fmap); + m_mmap_fmap=0; + + if (m_fh != INVALID_HANDLE_VALUE) CloseHandle(m_fh); + m_fh=INVALID_HANDLE_VALUE; +#elif defined(WDL_POSIX_NATIVE_READ) + if (m_mmap_view) munmap(m_mmap_view,(size_t)m_fsize); + m_mmap_view=0; + if (m_filedes>=0) + { + if (m_filedes_locked) flock(m_filedes,LOCK_UN); // release shared lock + close(m_filedes); + } + m_filedes=-1; +#else + if (m_fp) fclose(m_fp); + m_fp=0; +#endif + + } + + bool IsOpen() + { +#ifdef WDL_WIN32_NATIVE_READ + return (m_fh != INVALID_HANDLE_VALUE); +#elif defined(WDL_POSIX_NATIVE_READ) + return m_filedes >= 0; +#else + return m_fp != NULL; +#endif + } + + void CloseHandlesIfFullyInMemory() + { + if (!m_mmap_totalbufmode) return; +#ifdef WDL_WIN32_NATIVE_READ + if (m_fh != INVALID_HANDLE_VALUE) + { + CloseHandle(m_fh); + m_fh=INVALID_HANDLE_VALUE; + } +#elif defined(WDL_POSIX_NATIVE_READ) + if (m_filedes>=0) + { + if (m_filedes_locked) flock(m_filedes,LOCK_UN); // release shared lock + close(m_filedes); + m_filedes=-1; + } +#else + if (m_fp) + { + fclose(m_fp); + m_fp=NULL; + } +#endif + } + +#ifdef WDL_WIN32_NATIVE_READ + + int RunReads() + { + while (m_pending.GetSize()) + { + WDL_FileRead__ReadEnt *ent=m_pending.Get(0); + DWORD s=0; + + if (!ent->m_size && !GetOverlappedResult(m_fh,&ent->m_ol,&s,FALSE)) break; + m_pending.Delete(0); + if (!ent->m_size) ent->m_size=s; + m_full.Add(ent); + } + + + if (m_empties.GetSize()>0) + { + if (m_async_readpos < m_file_position) m_async_readpos = m_file_position; + + if (m_async==1) m_async_readpos &= ~((WDL_FILEREAD_POSTYPE) WDL_UNBUF_ALIGN-1); + + if (m_async_readpos >= m_fsize) return 0; + + const int rdidx=m_empties.GetSize()-1; + WDL_FileRead__ReadEnt *t=m_empties.Get(rdidx); + + ResetEvent(t->m_ol.hEvent); + + *(WDL_FILEREAD_POSTYPE *)&t->m_ol.Offset = m_async_readpos; + + m_async_readpos += m_async_bufsize; + DWORD dw; + if (ReadFile(m_fh,t->m_buf,m_async_bufsize,&dw,&t->m_ol)) + { + if (!dw) return 1; + } + else + { + if (GetLastError() != ERROR_IO_PENDING) return 1; + dw=0; + } + t->m_size=dw; + m_empties.Delete(rdidx); + m_pending.Add(t); + } + return 0; + } + + int AsyncRead(char *buf, int maxlen) + { + int lenout=0; + if (m_file_position+maxlen > m_fsize) + { + maxlen=(int) (m_fsize-m_file_position); + } + if (maxlen<1) return 0; + + int errcnt=!!m_async_hashaderr; + do + { + while (m_full.GetSize() > 0) + { + WDL_FileRead__ReadEnt *ti=m_full.Get(0); + WDL_FILEREAD_POSTYPE tiofs=*(WDL_FILEREAD_POSTYPE *)&ti->m_ol.Offset; + if (m_file_position >= tiofs && m_file_position < tiofs + ti->m_size) + { + if (maxlen < 1) break; + + int l=ti->m_size-(int) (m_file_position-tiofs); + if (l > maxlen) l=maxlen; + + memcpy(buf,(char *)ti->m_buf+m_file_position - tiofs,l); + buf += l; + m_file_position += l; + maxlen -= l; + lenout += l; + } + else + { + m_empties.Add(ti); + m_full.Delete(0); + } + } + + if (maxlen > 0 && m_async_readpos != m_file_position) + { + int x; + for (x = 0; x < m_pending.GetSize(); x ++) + { + WDL_FileRead__ReadEnt *ent=m_pending.Get(x); + WDL_FILEREAD_POSTYPE tiofs=*(WDL_FILEREAD_POSTYPE *)&ent->m_ol.Offset; + if (m_file_position >= tiofs && m_file_position < tiofs + m_async_bufsize) break; + } + if (x == m_pending.GetSize()) + { + m_async_readpos=m_file_position; + } + } + + errcnt+=RunReads(); + + if (maxlen > 0 && m_pending.GetSize() && !m_full.GetSize()) + { + WDL_FileRead__ReadEnt *ent=m_pending.Get(0); + m_pending.Delete(0); + + if (ent->m_size) m_full.Add(ent); + else + { +// WaitForSingleObject(ent->m_ol.hEvent,INFINITE); + + DWORD s=0; + if (GetOverlappedResult(m_fh,&ent->m_ol,&s,TRUE) && s) + { + ent->m_size=s; + m_full.Add(ent); + } + else // failed read, set the error flag + { + errcnt++; + ent->m_size=0; + m_empties.Add(ent); + } + } + } + } + while (maxlen > 0 && (m_pending.GetSize()||m_full.GetSize()) && !errcnt); + if (!errcnt) RunReads(); + else m_async_hashaderr=true; + + return lenout; + } + +#endif + + void *GetMappedView(int offs, int *len) + { + if (!m_mmap_view && !m_mmap_totalbufmode) return 0; + + int maxl=(int) (m_fsize-(WDL_FILEREAD_POSTYPE)offs); + if (*len > maxl) *len=maxl; + if (m_mmap_view) + return (char *)m_mmap_view + offs; + else + return (char *)m_mmap_totalbufmode + offs; + } + + int Read(void *buf, int len) + { + if (m_mmap_view||m_mmap_totalbufmode) + { + int maxl=(int) (m_fsize-m_file_position); + if (maxl > len) maxl=len; + if (maxl < 0) maxl=0; + if (maxl>0) + { + if (m_mmap_view) + memcpy(buf,(char *)m_mmap_view + (int)m_file_position,maxl); + else + memcpy(buf,(char *)m_mmap_totalbufmode + (int)m_file_position,maxl); + + } + m_file_position+=maxl; + return maxl; + } + + if (m_fsize_maychange) GetSize(); // update m_fsize + +#ifdef WDL_WIN32_NATIVE_READ + if (m_fh == INVALID_HANDLE_VALUE||len<1) return 0; + + if (m_async>0) + { + return AsyncRead((char *)buf,len); + } +#elif defined(WDL_POSIX_NATIVE_READ) + if (m_filedes<0 || len<1) return 0; + +#else + if (!m_fp || len<1) return 0; + +#endif + + if (m_bufspace.GetSize()>=WDL_UNBUF_ALIGN*2-1) + { + int rdout=0; + int sz=m_bufspace.GetSize()-(WDL_UNBUF_ALIGN-1); + char *srcbuf=(char *)m_bufspace.Get(); // read size + if (((int)(INT_PTR)srcbuf)&(WDL_UNBUF_ALIGN-1)) srcbuf += WDL_UNBUF_ALIGN-(((int)(INT_PTR)srcbuf)&(WDL_UNBUF_ALIGN-1)); + while (len > rdout) + { + int a=m_sync_bufmode_used-m_sync_bufmode_pos; + if (a>(len-rdout)) a=(len-rdout); + if (a>0) + { + memcpy((char*)buf+rdout,srcbuf+m_sync_bufmode_pos,a); + rdout+=a; + m_sync_bufmode_pos+=a; + m_file_position+=a; + } + + if (len > rdout) + { + m_sync_bufmode_used=0; + m_sync_bufmode_pos=0; + + int thissz=sz; + if (m_syncrd_firstbuf) // this is a scheduling mechanism to avoid having reads on various files always happening at the same time -- not needed in async modes, only in sync with large buffers + { + m_syncrd_firstbuf=false; + const int blocks = thissz/WDL_UNBUF_ALIGN; + if (blocks > 1) + { + static int rrs; // may not be ideal on multithread, but having it incorrect isnt a big deal. + if (blocks>7) thissz >>= (rrs++)&3; + else thissz>>= (rrs++)&1; + } + } + + #ifdef WDL_WIN32_NATIVE_READ + DWORD o; + if (m_async==-1) + { + if (m_file_position&(WDL_UNBUF_ALIGN-1)) + { + int offs = (int)(m_file_position&(WDL_UNBUF_ALIGN-1)); + LONG high=(LONG) ((m_file_position-offs)>>32); + SetFilePointer(m_fh,(LONG)((m_file_position-offs)&((WDL_FILEREAD_POSTYPE)0xFFFFFFFF)),&high,FILE_BEGIN); + m_sync_bufmode_pos=offs; + } + } + if (!ReadFile(m_fh,srcbuf,thissz,&o,NULL) || o<1 || m_sync_bufmode_pos>=(int)o) + { + break; + } + #elif defined(WDL_POSIX_NATIVE_READ) + int o=(int)pread(m_filedes,srcbuf,thissz,m_filedes_rdpos); + if (o>0) m_filedes_rdpos+=o; + if (o<1 || m_sync_bufmode_pos>=o) break; + + #else + int o=(int)fread(srcbuf,1,thissz,m_fp); + if (o<1 || m_sync_bufmode_pos>=o) break; + #endif + m_sync_bufmode_used=o; + } + + } + return rdout; + } + else + { + #ifdef WDL_WIN32_NATIVE_READ + DWORD dw=0; + ReadFile(m_fh,buf,len,&dw,NULL); + m_file_position+=dw; + return dw; + #elif defined(WDL_POSIX_NATIVE_READ) + + int ret=(int)pread(m_filedes,buf,len,m_filedes_rdpos); + if (ret>0) m_filedes_rdpos+=ret; + m_file_position+=ret; + return ret; + #else + int ret=fread(buf,1,len,m_fp); + m_file_position+=ret; + return ret; + #endif + } + + } + + WDL_FILEREAD_POSTYPE GetSize() + { + if (m_mmap_totalbufmode) return m_fsize; +#ifdef WDL_WIN32_NATIVE_READ + if (m_fh == INVALID_HANDLE_VALUE) return 0; +#elif defined(WDL_POSIX_NATIVE_READ) + if (m_filedes<0) return -1; + +#else + if (!m_fp) return -1; +#endif + + if (m_fsize_maychange) + { +#ifdef WDL_WIN32_NATIVE_READ + DWORD h=0; + DWORD l=GetFileSize(m_fh,&h); + m_fsize=(((WDL_FILEREAD_POSTYPE)h)<<32)|l; +#elif defined(WDL_POSIX_NATIVE_READ) + struct stat st; + if (!fstat(m_filedes,&st)) m_fsize = st.st_size; +#endif + } + + return m_fsize; + } + + WDL_FILEREAD_POSTYPE GetPosition() + { + if (m_mmap_totalbufmode) return m_file_position; +#ifdef WDL_WIN32_NATIVE_READ + if (m_fh == INVALID_HANDLE_VALUE) return -1; +#elif defined(WDL_POSIX_NATIVE_READ) + if (m_filedes<0) return -1; +#else + if (!m_fp) return -1; +#endif + return m_file_position; + } + + bool SetPosition(WDL_FILEREAD_POSTYPE pos) // returns 0 on success + { + m_async_hashaderr=false; + + if (!m_mmap_totalbufmode) + { + #ifdef WDL_WIN32_NATIVE_READ + if (m_fh == INVALID_HANDLE_VALUE) return true; + #elif defined(WDL_POSIX_NATIVE_READ) + if (m_filedes<0) return true; + #else + if (!m_fp) return true; + #endif + } + + if (m_fsize_maychange) GetSize(); + + if (pos < 0) pos=0; + if (pos > m_fsize) pos=m_fsize; + WDL_FILEREAD_POSTYPE oldpos=m_file_position; + if (m_file_position!=pos) m_file_position=pos; + else return false; + + if (m_mmap_view||m_mmap_totalbufmode) return false; + +#ifdef WDL_WIN32_NATIVE_READ + if (m_async>0) + { + WDL_FileRead__ReadEnt *ent; + + if (pos > m_async_readpos || !(ent=m_full.Get(0)) || pos < *(WDL_FILEREAD_POSTYPE *)&ent->m_ol.Offset) + { + m_async_readpos=pos; + } + + return FALSE; + } +#endif + + + if (m_bufspace.GetSize()>=WDL_UNBUF_ALIGN*2-1) + { + if (pos >= oldpos-m_sync_bufmode_pos && pos < oldpos-m_sync_bufmode_pos + m_sync_bufmode_used) + { + int diff=(int) (pos-oldpos); + m_sync_bufmode_pos+=diff; + + return 0; + } + m_sync_bufmode_pos=m_sync_bufmode_used=0; + } + + m_syncrd_firstbuf=true; +#ifdef WDL_WIN32_NATIVE_READ + LONG high=(LONG) (m_file_position>>32); + return SetFilePointer(m_fh,(LONG)(m_file_position&((WDL_FILEREAD_POSTYPE)0xFFFFFFFF)),&high,FILE_BEGIN)==0xFFFFFFFF && GetLastError() != NO_ERROR; +#elif defined(WDL_POSIX_NATIVE_READ) + m_filedes_rdpos = m_file_position; + return false; +#else + return !!fseek(m_fp,m_file_position,SEEK_SET); +#endif + } + + WDL_HeapBuf m_bufspace; + int m_sync_bufmode_used, m_sync_bufmode_pos; + + WDL_FILEREAD_POSTYPE m_file_position,m_async_readpos; + WDL_FILEREAD_POSTYPE m_fsize; + + void *m_mmap_view; + void *m_mmap_totalbufmode; + +#ifdef WDL_WIN32_NATIVE_READ + HANDLE GetHandle() { return m_fh; } + HANDLE m_fh; + HANDLE m_mmap_fmap; + int m_mmap_size; + int m_async; // 1=nobuf, 2=buffered async, -1=unbuffered sync + + int m_async_bufsize; + WDL_PtrList m_empties; + WDL_PtrList m_pending; + WDL_PtrList m_full; + +#elif defined(WDL_POSIX_NATIVE_READ) + WDL_FILEREAD_POSTYPE m_filedes_rdpos; + int m_filedes; + bool m_filedes_locked; + + int GetHandle() { return m_filedes; } +#else + FILE *m_fp; + + int GetHandle() { return fileno(m_fp); } +#endif + + bool m_fsize_maychange; + bool m_syncrd_firstbuf; + bool m_async_hashaderr; + +#ifdef _WIN32 + static void correctlongpath(WCHAR *buf) // this also exists as wdl_utf8_correctlongpath + { + const WCHAR *insert; + WCHAR *wr; + int skip = 0; + if (!buf || !buf[0] || wcslen(buf) < 256) return; + if (buf[1] == ':') insert=L"\\\\?\\"; + else if (buf[0] == '\\' && buf[1] == '\\') { insert = L"\\\\?\\UNC\\"; skip=2; } + else return; + + wr = buf + wcslen(insert); + memmove(wr, buf + skip, (wcslen(buf+skip)+1)*2); + memmove(buf,insert,wcslen(insert)*2); + while (*wr) + { + if (*wr == '/') *wr = '\\'; + wr++; + } + } +#endif +} WDL_FIXALIGN; + + + + + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/filewrite.h b/plugin-reaper-realearn/main/lib/WDL/WDL/filewrite.h new file mode 100644 index 0000000..21e711b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/filewrite.h @@ -0,0 +1,710 @@ +/* + WDL - filewrite.h + Copyright (C) 2005 and later 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. + + + This file provides the WDL_FileWrite object, which can be used to create/write files. + On windows systems it supports writing synchronously, asynchronously, and asynchronously without buffering. + On windows systems it supports files larger than 4gb. + On non-windows systems it acts as a wrapper for fopen()/etc. + + +*/ + + +#ifndef _WDL_FILEWRITE_H_ +#define _WDL_FILEWRITE_H_ + +#ifndef WDL_FILEWRITE_ON_ERROR +#define WDL_FILEWRITE_ON_ERROR(is_full) +#endif + +#include "ptrlist.h" + + + +#if defined(_WIN32) && !defined(WDL_NO_WIN32_FILEWRITE) + #ifndef WDL_WIN32_NATIVE_WRITE + #define WDL_WIN32_NATIVE_WRITE + #endif +#else + #ifdef WDL_WIN32_NATIVE_WRITE + #undef WDL_WIN32_NATIVE_WRITE + #endif + #if !defined(WDL_NO_POSIX_FILEWRITE) + #include + #include + #include + #include + #define WDL_POSIX_NATIVE_WRITE + extern struct stat wdl_stat_chk; + // if this fails on linux, use CFLAGS += -D_FILE_OFFSET_BITS=64 + typedef char wdl_filewrite_assert_failed_stat_not_64[sizeof(wdl_stat_chk.st_size)!=8 ? -1 : 1]; + typedef char wdl_filewrite_assert_failed_off_t_64[sizeof(off_t)!=8 ? -1 : 1]; + #endif +#endif + + + +#ifdef _MSC_VER +#define WDL_FILEWRITE_POSTYPE __int64 +#else +#define WDL_FILEWRITE_POSTYPE long long +#endif + +class WDL_FileWrite +{ +#ifdef WDL_WIN32_NATIVE_WRITE + +class WDL_FileWrite__WriteEnt +{ +public: + WDL_FileWrite__WriteEnt(int sz) + { + m_last_writepos=0; + m_bufused=0; + m_bufsz=sz; + m_bufptr = (char *)__buf.Resize(sz+4095); + int a=((int)(INT_PTR)m_bufptr)&4095; + if (a) m_bufptr += 4096-a; + + memset(&m_ol,0,sizeof(m_ol)); + m_ol.hEvent=CreateEvent(NULL,TRUE,TRUE,NULL); + } + ~WDL_FileWrite__WriteEnt() + { + CloseHandle(m_ol.hEvent); + } + + WDL_FILEWRITE_POSTYPE m_last_writepos; + + int m_bufused,m_bufsz; + OVERLAPPED m_ol; + char *m_bufptr; + WDL_TypedBuf __buf; +}; + +#endif + +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + BOOL HasUTF8(const char *_str) + { + const unsigned char *str = (const unsigned char *)_str; + if (!str) return FALSE; + while (*str) + { + unsigned char c = *str; + if (c >= 0xC2) + { + if (c <= 0xDF && str[1] >=0x80 && str[1] <= 0xBF) return TRUE; + else if (c <= 0xEF && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + else if (c <= 0xF4 && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + } + str++; + if (((const char *)str-_str) >= 256) return TRUE; // long filenames get converted to wide + } + return FALSE; + } +#endif + + +public: + // async==2 is write-through + // async==3 is non-buffered (win32-only) + WDL_FileWrite(const char *filename, int allow_async=1, int bufsize=8192, int minbufs=16, int maxbufs=16, bool wantAppendTo=false, bool noFileLocking=false) + { + m_file_position=0; + m_file_max_position=0; + if(!filename) + { +#ifdef WDL_WIN32_NATIVE_WRITE + m_fh = INVALID_HANDLE_VALUE; + m_async = 0; +#elif defined(WDL_POSIX_NATIVE_WRITE) + m_filedes_locked=false; + m_filedes=-1; + m_bufspace_used=0; +#else + m_fp = NULL; +#endif + return; + } + +#ifdef WDL_WIN32_NATIVE_WRITE + #ifdef WDL_SUPPORT_WIN9X + const bool isNT = (GetVersion()<0x80000000); + #else + const bool isNT = true; + #endif + m_async = allow_async && isNT ? 1 : 0; + if (m_async && allow_async == 3 && !wantAppendTo) + { + m_async = 3; + bufsize = (bufsize+4095)&~4095; + if (bufsize<4096) bufsize=4096; + } + + int rwflag = GENERIC_WRITE; + int createFlag= wantAppendTo?OPEN_ALWAYS:CREATE_ALWAYS; + int shareFlag = noFileLocking ? (FILE_SHARE_READ|FILE_SHARE_WRITE) : FILE_SHARE_READ; + int flag = FILE_ATTRIBUTE_NORMAL; + + if (m_async) + { + rwflag |= GENERIC_READ; + if (m_async == 3) + flag |= FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH; + else + flag |= FILE_FLAG_OVERLAPPED|(allow_async>1 ? FILE_FLAG_WRITE_THROUGH: 0); + } + + { +#ifndef WDL_NO_SUPPORT_UTF8 + bool allow_ansi = true; + m_fh=INVALID_HANDLE_VALUE; + if (isNT && HasUTF8(filename)) + { + int szreq=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,NULL,0); + if (szreq > 1000) + { + WDL_TypedBuf wfilename; + wfilename.Resize(szreq+20); + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wfilename.Get(),wfilename.GetSize()-10)) + { + correctlongpath(wfilename.Get()); + allow_ansi = false; + m_fh = CreateFileW(wfilename.Get(),rwflag,shareFlag,NULL,createFlag,flag,NULL); + } + } + else + { + WCHAR wfilename[1024]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wfilename,1024-10)) + { + correctlongpath(wfilename); + allow_ansi = false; + m_fh = CreateFileW(wfilename,rwflag,shareFlag,NULL,createFlag,flag,NULL); + } + } + } + + if (m_fh == INVALID_HANDLE_VALUE && allow_ansi) +#endif + m_fh = CreateFileA(filename,rwflag,shareFlag,NULL,createFlag,flag,NULL); + } + + if (m_async && m_fh != INVALID_HANDLE_VALUE) + { + m_async_bufsize=bufsize; + m_async_maxbufs=maxbufs; + m_async_minbufs=minbufs; + int x; + for (x = 0; x < m_async_minbufs; x ++) + { + WDL_FileWrite__WriteEnt *t=new WDL_FileWrite__WriteEnt(m_async_bufsize); + m_empties.Add(t); + } + } + + if (m_fh != INVALID_HANDLE_VALUE && wantAppendTo) + SetPosition(GetSize()); + +#elif defined(WDL_POSIX_NATIVE_WRITE) + m_bufspace_used=0; + m_filedes_locked=false; + m_filedes=open(filename,O_WRONLY|O_CREAT + // todo: use fcntl() for platforms when O_CLOEXEC is not available (if we ever need to support them) + // (currently the only platform that meets this criteria is macOS w/ old SDK, but we don't use execve() + // there +#ifdef O_CLOEXEC + | O_CLOEXEC +#endif + ,0644); + if (m_filedes>=0) + { + + if (!noFileLocking) + { + m_filedes_locked = !flock(m_filedes,LOCK_EX|LOCK_NB); + if (!m_filedes_locked) + { + // this check might not be necessary, it might be sufficient to just fail and close if no exclusive lock possible + if (errno == EWOULDBLOCK) + { + // FAILED exclusive locking because someone else has a lock + close(m_filedes); + m_filedes=-1; + } + else // failed for some other reason, try to keep a shared lock at least + { + m_filedes_locked = !flock(m_filedes,LOCK_SH|LOCK_NB); + } + } + } + + if (m_filedes>=0) + { + if (!wantAppendTo) + { + if (ftruncate(m_filedes,0) < 0) + { + WDL_ASSERT( false /* ftruncate() failed in WDL_FileWrite */ ); + } + } + else + { + struct stat st; + if (!fstat(m_filedes,&st)) SetPosition(st.st_size); + } + } + + +#ifdef __APPLE__ + if (m_filedes >= 0 && allow_async>1) fcntl(m_filedes,F_NOCACHE,1); +#endif + } + if (minbufs * bufsize >= 16384) m_bufspace.Resize((minbufs*bufsize+4095)&~4095); +#else + m_fp=fopen(filename,wantAppendTo ? "a+b" : "wb"); + if (wantAppendTo && m_fp) + fseek(m_fp,0,SEEK_END); +#endif + } + + ~WDL_FileWrite() + { +#ifdef WDL_WIN32_NATIVE_WRITE + // todo, async close stuff? + if (m_fh != INVALID_HANDLE_VALUE && m_async) + { + SyncOutput(true); + } + + m_empties.Empty(true); + m_pending.Empty(true); + + if (m_fh != INVALID_HANDLE_VALUE) CloseHandle(m_fh); + m_fh=INVALID_HANDLE_VALUE; +#elif defined(WDL_POSIX_NATIVE_WRITE) + if (m_filedes >= 0) + { + if (m_bufspace.GetSize() > 0 && m_bufspace_used>0) + { + int v=(int)pwrite(m_filedes,m_bufspace.Get(),m_bufspace_used,m_file_position); + if (v>0) m_file_position+=v; + if (m_file_position > m_file_max_position) m_file_max_position=m_file_position; + m_bufspace_used=0; + } + if (m_filedes_locked) flock(m_filedes,LOCK_UN); + close(m_filedes); + } + m_filedes=-1; + +#else + if (m_fp) fclose(m_fp); + m_fp=0; +#endif + + } + + bool IsOpen() + { +#ifdef WDL_WIN32_NATIVE_WRITE + return (m_fh != INVALID_HANDLE_VALUE); +#elif defined(WDL_POSIX_NATIVE_WRITE) + return m_filedes >= 0; +#else + return m_fp != NULL; +#endif + } + + + int Write(const void *buf, int len) + { +#ifdef WDL_WIN32_NATIVE_WRITE + if (m_fh == INVALID_HANDLE_VALUE) return 0; + + if (m_async) + { + int rdpos = 0; + while (len > 0) + { + if (!m_empties.GetSize()) + { + WDL_FileWrite__WriteEnt *ent=m_pending.Get(0); + DWORD s=0; + if (ent) + { + bool wasabort=false; + DWORD err; + if (GetOverlappedResult(m_fh,&ent->m_ol,&s,FALSE)|| + (wasabort=((err=GetLastError())==ERROR_OPERATION_ABORTED))) + { + m_pending.Delete(0); + + if (wasabort) + { + if (!RunAsyncWrite(ent,false)) m_empties.Add(ent); + } + else + { + m_empties.Add(ent); + ent->m_bufused=0; + } + } + else if (err != ERROR_IO_PENDING && err != ERROR_IO_INCOMPLETE) + { + WDL_FILEWRITE_ON_ERROR(err == ERROR_DISK_FULL) + } + } + } + + + WDL_FileWrite__WriteEnt *ent=m_empties.Get(0); + if (!ent) + { + if (m_pending.GetSize()>=m_async_maxbufs) + { + SyncOutput(false); + } + + if (!(ent=m_empties.Get(0))) + m_empties.Add(ent = new WDL_FileWrite__WriteEnt(m_async_bufsize)); // new buffer + + + } + + int ml=ent->m_bufsz-ent->m_bufused; + if (ml>len) ml=len; + memcpy(ent->m_bufptr+ent->m_bufused,(const char *)buf + rdpos,ml); + + ent->m_bufused+=ml; + len-=ml; + rdpos+=ml; + + if (ent->m_bufused >= ent->m_bufsz) + { + if (RunAsyncWrite(ent,true)) m_empties.Delete(0); // if queued remove from list + } + } + return rdpos; + } + else + { + DWORD dw=0; + if (!WriteFile(m_fh,buf,len,&dw,NULL)) + { + WDL_FILEWRITE_ON_ERROR(GetLastError() == ERROR_DISK_FULL) + } + m_file_position+=dw; + if (m_file_position>m_file_max_position) m_file_max_position=m_file_position; + return dw; + } +#elif defined(WDL_POSIX_NATIVE_WRITE) + if (m_bufspace.GetSize()>0) + { + char *rdptr = (char *)buf; + int rdlen = len; + while (rdlen>0) + { + int amt = m_bufspace.GetSize() - m_bufspace_used; + if (amt>0) + { + if (amt>rdlen) amt=rdlen; + memcpy((char *)m_bufspace.Get()+m_bufspace_used,rdptr,amt); + m_bufspace_used += amt; + rdptr+=amt; + rdlen -= amt; + + if (m_file_position+m_bufspace_used > m_file_max_position) m_file_max_position=m_file_position + m_bufspace_used; + } + if (m_bufspace_used >= m_bufspace.GetSize()) + { + int v=(int)pwrite(m_filedes,m_bufspace.Get(),m_bufspace_used,m_file_position); + if (v != m_bufspace_used) { WDL_FILEWRITE_ON_ERROR(v>=0 || errno == EDQUOT || errno == ENOSPC) } + if (v>0) m_file_position+=v; + m_bufspace_used=0; + } + } + return len; + } + else + { + int v=(int)pwrite(m_filedes,buf,len,m_file_position); + if (v != len) { WDL_FILEWRITE_ON_ERROR(v>=0 || errno == EDQUOT || errno == ENOSPC) } + if (v>0) m_file_position+=v; + if (m_file_position > m_file_max_position) m_file_max_position=m_file_position; + return v; + } +#else + int written = (int)fwrite(buf,1,len,m_fp); + if (written != len) { WDL_FILEWRITE_ON_ERROR(false) } + return written; +#endif + + + } + + WDL_FILEWRITE_POSTYPE GetSize() + { +#ifdef WDL_WIN32_NATIVE_WRITE + if (m_fh == INVALID_HANDLE_VALUE) return 0; + DWORD h=0; + DWORD l=GetFileSize(m_fh,&h); + WDL_FILEWRITE_POSTYPE tmp=(((WDL_FILEWRITE_POSTYPE)h)<<32)|l; + WDL_FILEWRITE_POSTYPE tmp2=GetPosition(); + if (tmpm_bufused; + } + return pos; +#elif defined(WDL_POSIX_NATIVE_WRITE) + if (m_filedes < 0) return -1; + return m_file_position + m_bufspace_used; +#else + if (!m_fp) return -1; + return ftell(m_fp); + +#endif + } + +#ifdef WDL_WIN32_NATIVE_WRITE + + bool RunAsyncWrite(WDL_FileWrite__WriteEnt *ent, bool updatePosition) // returns true if ent is added to pending + { + if (ent && ent->m_bufused>0) + { + if (updatePosition) + { + ent->m_last_writepos = m_file_position; + m_file_position += ent->m_bufused; + if (m_file_position>m_file_max_position) m_file_max_position=m_file_position; + } + + if (m_async == 3 && (ent->m_bufused&4095)) + { + int offs=(ent->m_bufused&4095); + char tmp[4096]; + memset(tmp,0,4096); + + *(WDL_FILEWRITE_POSTYPE *)&ent->m_ol.Offset = ent->m_last_writepos + ent->m_bufused - offs; + ResetEvent(ent->m_ol.hEvent); + + DWORD dw=0; + if (!ReadFile(m_fh,tmp,4096,&dw,&ent->m_ol)) + { + if (GetLastError() == ERROR_IO_PENDING) + WaitForSingleObject(ent->m_ol.hEvent,INFINITE); + } + memcpy(ent->m_bufptr+ent->m_bufused,tmp+offs,4096-offs); + + ent->m_bufused += 4096-offs; + } + + DWORD d=0; + + *(WDL_FILEWRITE_POSTYPE *)&ent->m_ol.Offset = ent->m_last_writepos; + + ResetEvent(ent->m_ol.hEvent); + + if (!WriteFile(m_fh,ent->m_bufptr,ent->m_bufused,&d,&ent->m_ol)) + { + if (GetLastError()==ERROR_IO_PENDING) + { + m_pending.Add(ent); + return true; + } + else { WDL_FILEWRITE_ON_ERROR(GetLastError()==ERROR_DISK_FULL) } + } + ent->m_bufused=0; + } + return false; + } + + void SyncOutput(bool syncall) + { + if (syncall) + { + if (RunAsyncWrite(m_empties.Get(0),true)) m_empties.Delete(0); + } + for (;;) + { + WDL_FileWrite__WriteEnt *ent=m_pending.Get(0); + if (!ent) break; + DWORD s=0; + m_pending.Delete(0); + BOOL ok = GetOverlappedResult(m_fh,&ent->m_ol,&s,TRUE); + int errcode; + if (!ok && (errcode=GetLastError())==ERROR_OPERATION_ABORTED) + { + // rewrite this one + if (!RunAsyncWrite(ent,false)) m_empties.Add(ent); + } + else + { + if (!ok) { WDL_FILEWRITE_ON_ERROR(errcode==ERROR_DISK_FULL) } + m_empties.Add(ent); + ent->m_bufused=0; + if (!syncall) break; + } + } + } + +#endif + + + bool SetPosition(WDL_FILEWRITE_POSTYPE pos) // returns 0 on success + { +#ifdef WDL_WIN32_NATIVE_WRITE + if (m_fh == INVALID_HANDLE_VALUE) return true; + if (m_async) + { + SyncOutput(true); + m_file_position=pos; + if (m_file_position>m_file_max_position) m_file_max_position=m_file_position; + + if (m_async==3 && (m_file_position&4095)) + { + WDL_FileWrite__WriteEnt *ent=m_empties.Get(0); + if (ent) + { + int psz=(int) (m_file_position&4095); + + m_file_position -= psz; + *(WDL_FILEWRITE_POSTYPE *)&ent->m_ol.Offset = m_file_position; + ResetEvent(ent->m_ol.hEvent); + + DWORD dwo=0; + if (!ReadFile(m_fh,ent->m_bufptr,4096,&dwo,&ent->m_ol)) + { + if (GetLastError() == ERROR_IO_PENDING) + WaitForSingleObject(ent->m_ol.hEvent,INFINITE); + } + ent->m_bufused=(int)psz; + } + } + return false; + } + + m_file_position=pos; + if (m_file_position>m_file_max_position) m_file_max_position=m_file_position; + + LONG high=(LONG) (m_file_position>>32); + return SetFilePointer(m_fh,(LONG)(m_file_position&((WDL_FILEWRITE_POSTYPE)0xFFFFFFFF)),&high,FILE_BEGIN)==0xFFFFFFFF && GetLastError() != NO_ERROR; +#elif defined(WDL_POSIX_NATIVE_WRITE) + + if (m_filedes < 0) return true; + if (m_bufspace.GetSize() > 0 && m_bufspace_used>0) + { + int v=(int)pwrite(m_filedes,m_bufspace.Get(),m_bufspace_used,m_file_position); + if (v>0) m_file_position+=v; + if (m_file_position > m_file_max_position) m_file_max_position=m_file_position; + m_bufspace_used=0; + } + + m_file_position = pos; // seek! + if (m_file_position>m_file_max_position) m_file_max_position=m_file_position; + return false; +#else + if (!m_fp) return true; + return !!fseek(m_fp,pos,SEEK_SET); +#endif + } + + WDL_FILEWRITE_POSTYPE m_file_position, m_file_max_position; + +#ifdef WDL_WIN32_NATIVE_WRITE + HANDLE GetHandle() { return m_fh; } + HANDLE m_fh; + int m_async; // 3 = unbuffered + + int m_async_bufsize, m_async_minbufs, m_async_maxbufs; + + WDL_PtrList m_empties; + WDL_PtrList m_pending; + +#elif defined(WDL_POSIX_NATIVE_WRITE) + int GetHandle() { return m_filedes; } + + WDL_HeapBuf m_bufspace; + int m_bufspace_used; + int m_filedes; + + bool m_filedes_locked; + +#else + int GetHandle() { return fileno(m_fp); } + + FILE *m_fp; +#endif + +#ifdef _WIN32 + static void correctlongpath(WCHAR *buf) // this also exists as wdl_utf8_correctlongpath + { + const WCHAR *insert; + WCHAR *wr; + int skip = 0; + if (!buf || !buf[0] || wcslen(buf) < 256) return; + if (buf[1] == ':') insert=L"\\\\?\\"; + else if (buf[0] == '\\' && buf[1] == '\\') { insert = L"\\\\?\\UNC\\"; skip=2; } + else return; + + wr = buf + wcslen(insert); + memmove(wr, buf + skip, (wcslen(buf+skip)+1)*2); + memmove(buf,insert,wcslen(insert)*2); + while (*wr) + { + if (*wr == '/') *wr = '\\'; + wr++; + } + } +#endif +} WDL_FIXALIGN; + + + + + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/fnv64.h b/plugin-reaper-realearn/main/lib/WDL/WDL/fnv64.h new file mode 100644 index 0000000..701bce1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/fnv64.h @@ -0,0 +1,17 @@ +#ifndef _WDL_FNV64_H_ +#define _WDL_FNV64_H_ + +#include "wdltypes.h" + +#define WDL_FNV64_IV WDL_UINT64_CONST(0xCBF29CE484222325) + +static WDL_STATICFUNC_UNUSED WDL_UINT64 WDL_FNV64(WDL_UINT64 h, const unsigned char* data, int sz) +{ + for (int i=0; i < sz; ++i) + { + h *= WDL_UINT64_CONST(0x00000100000001B3); + h ^= data[i]; + } + return h; +} +#endif \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/AUTHORS b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/AUTHORS new file mode 100644 index 0000000..43dee90 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/AUTHORS @@ -0,0 +1,36 @@ +Lennie Araki + Windows code providing a nicer interface and example program + +Michael Brown + callbacks to write data via user defined function + +Daniel Eisenbud + Fixes for crashes with invalid gif files and double freeing of + colormaps + +Gershon Elber + original giflib code + +Marc Ewing + spec file (for rpms) updates + +Toshio Kuratomi + uncompressed gif writing code + autoconf/automake process + current maintainer + +marek + Gif initialization fix + windows build code + +Peter Mehlitz + callbacks to read data from arbitrary sources (like libjpeg/libpng) + +Dick Porter + int/pointer fixes for Alpha + +Eric Raymond + long time maintainer of giflib code + +Georg Schwarz + IRIX fixes diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/COPYING b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/COPYING new file mode 100644 index 0000000..da54357 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/COPYING @@ -0,0 +1,19 @@ +The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION 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/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/ChangeLog b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/ChangeLog new file mode 100644 index 0000000..4186f25 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/ChangeLog @@ -0,0 +1,549 @@ +2005-10-09 Toshio Kuratomi - r94 + * Sync with libungif r93. + * ChangeLog: Update to r92. + * NEWS: Update with combined libungif/giflib changes. + +2005-10-09 Toshio Kuratomi - r92 + * lib/gif_lib.h: Change GifPrefixType to unsigned. + +2005-10-09 Toshio Kuratomi - r91 + * ChangeLog: Update to r90. + * NEWS: Update on GBA and Windows fixes. + +2005-10-06 Toshio Kuratomi - r90 + Changes from Lennie Araki: + * gba/giftest.mak: Prefix the names of defines for the GBA build with _GBA. + * lib/dgif_lib.c, lib/gif_lib_private.h, lib/gif_err.c: + - When Compiling for Game Boy Advance, file functions are not needed so + exclude DGifOpenFileName(), DGifOpenFileHandle(), DGifSlurp(), and + PrintGifError(). + - On Game Boy Advance we need to reduce memory usage. Change values to + short int where appropriate. + * lib/gif_lib.h: + - Handle te GBA changes by defining GifPrefixType and GifWord to int + unless compiling on GBA. Then use unsigned short and short + respectively. + - Fix a problem with the API on _WIN32. DrawText conflicts with the + Windows API. Call it DrawGifText instead. + +2005-09-27 Toshio Kuratomi - r86 + * Sync with libungif r85. + +2005-09-27 Toshio Kuratomi - r82 + * AUTHORS: Add Daniel Eisenbud. Obscure email addresses. + * libungif.spec: Bump to version 4.1.4. + * configure.ac: Bump to 4.1.4. No longer check for ranlib. + * doc/lzgif.txt: Change dos line encoding to UNIX. + * lib/dgif_lib.c: (eisenbud) + - Set GifFile's ColorMaps to NULL when we free a colormap object. + - Detect some cases of corrupted GIFs which were crashing the library. + * lib/egif_lib.c: Set ColorMaps to NULL when we free a colormap object. + * lib/gifalloc.c: Set ColorMaps to NULL when we free a colormap object. + * lib/dev2gif.c: Fix redefinition problem on IRIX. + * NEWS: Update to 4.1.4 + * util/gifcomb.c: Set a olorMap to NULL. + +2004-07-11 Toshio Kuratomi - r79 + * gif2iris.c: Fixes from Georg Schwarz . + - stdlib.h is available and needs to be included on IRIX. + - ColorMapSize was being set from non-existent variables. + +2004-05-29 Toshio Kuratomi - r76 + * Sync with libungif-4.1.3. + +2004-05-29 Toshio Kuratomi - r74 + * ChangeLog, prop=lastlog: Sync with the subversion logs. + +2004-05-29 Toshio Kuratomi - r73 + * test-unx: Add a test of extension code. + * lib/egif_lib.c: Remove a debugging statement + +2004-05-29 Toshio Kuratomi - r72 + * Makefile.am, doc/Makefile.am, pic/Makefile.am: Change wildcarded entries + into explicit filenames so make distcheck will succeed. + +2004-05-29 Toshio Kuratomi - r71 + * ChangeLog, prop=lastlog: Sync the ChangeLog for the release. + +2004-05-29 Toshio Kuratomi - r70 + * AUTHORS: Add Lennie Araki to the list of contributers. + * windows: The windows subdirectory and all files under it are contributions + from Lennie Araki to provide a nice interface on MS Windows. + * README: Redundancy fix. + * doc/gif_lib.html: Add EGifPutExtension{First,Next,Last} to the documentation + so people know they should use it instead of EGifPutExtension. + * Makefile.am: Mark the windows files to be distributed. + * NEWS: Complete the NEWS item for 4.1.3. + +2004-05-29 Toshio Kuratomi - r69 + * libungif.spec: Some updates from the latest RedHat spec. + * configure.ac: Bump version to 4.1.3. + * lib/gifalloc.c: Add to my comments on ESR's note about Extension blocks. + * lib/egif_lib.c: + - EGifPutComment(): reimplemented using EGifPutExtensionFirst, Next, and + Last so that it won't break on unusually long comments. + - EGifPutExtension{First,Next,Last}: Changed fwrites to WRITE so any + user defined write function will get called properly. + - EGifPutExtensionLast: if the Extension block is empty (Zero length) + then don't attempt to output a last extension block, just output the + block terminator. + - EGifPutExtension: Comment that this function does not work when there + are multiple subblocks in an Extension block. Use the functions + EGifPutExtension{First,Next,Last} instead. + - EGifSpew: Reimplement to use EGifPutExtension{First,Next,Last} so we + don't output broken GIFs when there are multiple sub-blocks on an + extension. + * lib/Makefile.am: Bump version to 4.1.3. + * NEWS: Begin writing an entry for 4.1.3. + * util/icon2gif.c: Few casting fixes to make gcc -Wall happy. + * util/gif2ps.c: printf format string corrections. + +2004-05-26 Toshio Kuratomi - r67 + * Clean up some typos. + +2004-05-25 Toshio Kuratomi - r66 + * Sync with libungif-4.1.2. + +2004-03-03 Toshio Kuratomi - r64 + Last minute updates to the release notes in various files. + +2004-03-03 Toshio Kuratomi - r63 + * Set property lastlog to remind me when I last synced the ChangeLog + +2004-03-03 Toshio Kuratomi - r62 + * ChangeLog: Update + +2004-03-03 Toshio Kuratomi - r61 + * configure.ac: Bump version to 4.1.2 + +2004-02-22 Toshio Kuratomi - r59 + * configure.ac, lib/Makefile.am: Bump version. Forgot to do this for 4.1.1... + +2004-02-22 Toshio Kuratomi - r58 + * TODO: Take out -Wall as that's all ready now. + +2004-02-22 Toshio Kuratomi - r57 + Merge changes to the code from branch indent-audit r55 + * README: MakeExtension deprecation note. + * TODO: Bunch of things I need to fix or check that I saw while doing the + indentation of the code. + * lib/getarg.h: indent changes + * lib/dgif_lib.c: indent changes + - Move stdlib.h out of #ifdef's as it's included on all platforms. + - Add checks to be sure malloc and MakeMapObject succeed. + * lib/quantize.c: indent changes + - Move stdlib.h out of #ifdef's as it's included on all platforms. + - _GifError already pulled in through gif_lib_private.h. Remove decl. + - Make Count in NewColorMapType be unsigned. + - Separated mallocs from conditionals in a few places. Easier reading. + * lib/gifalloc.c: indent changes + - Added four FIXME's where I think the code might not be doing what we + want. Need to do more research to figure out. + - Add note to MakeExtension that I think it needs to be deprecated. + - Separated mallocs from conditionals in a few places. Easier reading. + - FreeLastSavedImage: New private function to free the last image in a + GifFile structure. Used to back out when unable to completely + allocate a new SavedImage structure. + - check for NULL values before deallocating in Free* functions and make + sure all Free* functions set the pointer to NULL after they deallocate + the memory. + * lib/egif_lib.c: indent changes + - EGifPutScreenDesc: If we have no colormap, output a default value for + its size instead of trying to reference its unallocated BitsPerPixel + field. (Fixes bug noted in r46) + * lib/gif_lib.h: indent changes + - Condense the #else #if VARARGS to #elif VARARGS check. + * lib/qprintf.c: indent changes + - Condense the #else #if VARARGS to #elif VARARGS check. + * lib/dev2gif.c: indent changes + * lib/getarg.c: indent changes + * lib/gif_lib_private.h: indent changes + * lib/gif_font.c: indent changes + * lib/gif_err.c: indent changes + +2004-02-22 Toshio Kuratomi - r56 + * lib/Makefile.am, util/Makefile.am: Add -Wall to the compilation flags so + we can keep the code from acquiring too much bad style. + +2004-02-20 Toshio Kuratomi - r46 + * egif_lib.c: Note for a bug fix (Can wait until after indent because + there's no patch.) + * gif_lib.h, dev2gif.c: Change int type to explicit long type in + DumpScreen2Gif. + * util/gifinto.c: Give the fprintf back its %d format. + GifFile->ImageCount is used as the Image number. + +2004-02-20 Toshio Kuratomi - r45 + * README: add varargs to the deprecation list + +2004-02-20 Toshio Kuratomi - r44 + * test-unx: Quote the program names. + * lib/dgif_lib.c: + - Make sure memory was allocated for the colormap + - Some reformatting of code but no syntactic changes. + * lib/gif_lib.h: + - C++ extern "C" fix + - Fix typo with EGifOpen + * lib/qprintf.c, lib/getarg.c: Update the varargs code. Some users reported + that not all systems can handle the hybridized varargs parameter lists + we had. Need to use old-style declarations instead. + +2004-02-20 Toshio Kuratomi - r43 + * NEWS: Note bugfixes and deprecations + * README: Deprecation list is now being compiled in this file. + * TODO: Notes about interlace bug, -Wall status, merging of old bug status + +2004-02-19 Toshio Kuratomi - r42 + * Makefile.am: Disable testing for now because gif2x11 is broken so none + of the tests _appear_ to complete successfully. + +2004-02-19 Toshio Kuratomi - r38 + Merge -Wall fixes from branches/Wall-audit r29 + * configure.ac: + - Make the stdarg vs varargs check simpler by relying on + AC_CHECK_HEADERS() magic. + - Check for unistd.h + * dgif_lib.c, gif_lib.h, egif_lib.c, gifalloc.c, quantize.c, dev2gif.c, + getarg.c, gif_lib_private.h, gif_font.c gif_err.c, gifinto.c, icon2gif.c, + raw2gif.c, gifcolor.c, gifasm.c, gif2epsn.c, gif2iris.c, gifrotat.c, + gifovly.c, gif2x11.c, rle2gif.c, gif2rle.c, text2gif.c, gifspnge.c, + gifclrmp.c, giffiltr.c, giftext.c, gifinfo.c, rgb2gif.c, gif2rgb.c, gif2ps.c + - Changes to get rid of -Wall compile warnings. + + Casting of types + + New header includes for unistd.h and fcntl.h + + Explicit declaration of many types to unsigned + + Removed unused variables and functions + + Removed VersionStr from every library file. Instead include it via + gif_lib_private.h + * gif_lib.h, gif_lib_private.h: Moved the VersionStr into gif_lib_private.h + and made it a #define instead of a static char *. + +2004-02-19 Toshio Kuratomi - r37 + Deprecation notes + +2004-02-19 Toshio Kuratomi - r36 + Add notes about security things to do and giflib syncing + +2004-02-18 Toshio Kuratomi - r32 + * TODO: Add notes about how to go about syncing Wall-audit and indent changes + into giflib. It won't be pretty. + * svn:ignore: Change the tarball names from libungif to giflib + +2004-02-18 Toshio Kuratomi - r31 + Add config.h include to gif_hash.c + +2004-02-17 Toshio Kuratomi - r30 + Sync up with libungif 4.1.1 + +2004-02-17 Toshio Kuratomi - r26 + Updated ChangeLog + +2004-02-17 Toshio Kuratomi + * Updated libungif.spec to look more like fedora core spec + * Updated version numbers in all files + +2004-02-17 Toshio Kuratomi + * Add the libungif*.tar.bz2 distribution tarball to the ignored files + * configure.ac, lib/getarg.c, lib/getarg.h, lib/gif_lib.h, lib/qprintf.c: + Prefer stdarg.h over vararg.h + * TODO: Add information about functions that will go away in 5.0 + (In reality, I don't think new software uses libungif, so there may never + be a 5.0 release.) + * lib/gif_lib.h: Change version from 4.0 to 4.1 + * NEWS: add deprecation warning for the qprintf stuff: GifQuietPrint var and + GifQprintf function. + +2004-02-16 Toshio Kuratomi + * util/gif2iris.c, util/gif2rle.c, util/gifinfo.c: Fix problems with fprintf error statements in the utils + +2004-02-16 Toshio Kuratomi + Add DEVELOPERS file to the distribution. + +2004-02-16 Toshio Kuratomi + * AUTHORS, libungif.spec, libungif.lsm, README, BUGS, NEWS: + Lots of changes to my email address and the website/download. (libungif is + moving to sourceforge.) + * TODO: Few notes on cleanups that need to happen. State what needs to be done + for 4.1.1 to be released. + +2004-02-15 Toshio Kuratomi + Changes imported from last cvs checkout + * TODO: note to check return of malloc everywhere + * lib/dgif_lib.c, lib/egif_lib.c: Fix some deallocation bugs + * lib/gifalloc.c: Fix a colormap allocation problem + * lib/gif_font.c: Fix to drawing text + +2004-02-15 Toshio Kuratomi + Added libgetarg.a to the ignore list. + +2004-02-15 Toshio Kuratomi + Changes to the build infrastructure to build under current libtool, automake, + and libtool. + * configure.in: renamed to configure.ac + * acconfig.h: deleted. Functionality moved into the configure.ac + * autogen.sh: now runs libtoolize --automake + * lib/Makefile.am, util/Makefile.am: CFLAGS=>AM_CFLAGS; INCLUDES=>AM_CPPFLAGS + * configure.ac: + - initialization macros for automake and autoconf have changed + - removed checks for C++ compiler and Awk + - acconfig.h functionality moved here. + - add other X11 libraries to the X11_LIB define + +2004-02-15 Toshio Kuratomi + * Remove INSTALL file as it's autogenerated.\n* Add stamp-h1 to ignored files + +2004-02-15 Toshio Kuratomi + Additional adds and deletes to make version 4.1.0b1 + +2004-02-15 Toshio Kuratomi + Import of version 4.1.0b1 + +2004-02-15 Toshio Kuratomi - r10 + Import giflib 4.1.0 + +2004-02-15 Toshio Kuratomi - r9 + Copy the 4.1.0 libungif release to be the base of the 4.1.0 giflib release. + +2004-02-15 Toshio Kuratomi - r7 + Release 4.1.0 + +2004-02-15 Toshio Kuratomi - r6 + Import of version 4.1.0 + +2004-02-15 Toshio Kuratomi - r5 + Set ignore patterns on the project directories. + +2004-02-15 Toshio Kuratomi - r3 + Remove a Makefile.in that was left in in the first commit. + +2004-02-14 Toshio Kuratomi - r2 + Commit revision 3.1.0 to subversion + +2004-02-14 Toshio Kuratomi - r1 + Initial SVN Repository Layout + +2000 6 Feb Toshio Kuratomi + * configure.in: Change to using config.h + - Every .c file: Change to using config.h. + * configure.in: added check for varargs header. + * lib/getarg.c: Changed the ifdef USE_VARARGS to ifdef HAVE_VARARGS_H. + - lib/getarg.h: Ditto. + - lib/gif_lib.h: Ditto. + - lib/qprintf.h: Ditto. + +2000 6 Feb Toshio Kuratomi + * lib/getarg.h: Prepend an underscore to the header file define. + * lib/gif_lib.h: Ditto + * lib/gif_lib_private.h: Ditto + * lib/getarg.c: ifdef'd MyMalloc so it actually won't define if it already + is. + +2000 3 Feb Toshio Kuratomi + * A new cvs repository based my private tree from home. It now goes back + to giflib-3.0. + * Updated the cvs repository to make multiple developers possible. + * Merge all of Michael's patches into the distribution. + * DEVELOPER: Updated to reflect the new versions of + autoconf/automake/libtool we're using. + * libungif.spec: Updated a few things from the latest redhat spec file. + +1999 5 Dec Toshio Kuratomi + * Update links to the web pages as I have reorganized them somewhat. + * Add the welcome2.gif to the pic directory and a test that utilizes + it to test-unx. + +1999 17 Nov Toshio Kuratomi + * New cvs Repository. Hopefully I've got everything that was in the + old one. This one is available on anonymous cvs. + * Update to libtool 1.3.3, automake 1.4, and autoconf 2.13 + +1999 23 May Michael R Brown + * Lots of 'const' qualifiers added, thanks Alexis + Wilke for finding these. + +1999 22 Mar Michael R Brown + * util/gif2x11.c: Patch by (who?) to fix lots of memory leeks. + * util/*.c: + lib/dgif_lib.c: + Makefile.in: + Patch by David Kaelbling to compile on IRIX 6.x. Basically fixing + lots of bad/missing parameter passing to printf, scanf and similar. + * Added pics/welcome2.gif, from Peter Merz which provokes a bug prior + to patch 19990224 to do with colour map management. There is still + a problem with util/gifspnge processing this image, so it will not + be added to test-unx yet. + +1999 05 Mar Michael R Brown + * lib/getarg.c: Lines 107 and 189 + Added ifdef's to use stdarg when available. On dec-alpha the + default code was causing programs to crash, probably because + it assumes a stack that grows-up. + +1999 24 Feb Michael R Brown + * lib/dgif_lib.c: Lines 363 and 367 + Bug reported by Steve Sanders, where &'s where causing the + memcpy to overwrite the pointers. Fixed by removing the &'s + so that memcpy overwrote the memory pointed to. + +1999 09 Feb Toshio Kuratomi + * Release 4.1.0 + +1999 09 Feb Toshio Kuratomi + * Merge libungif changes into the giflib tree: + - upgrade to libtool 1.2b + - util/Makefile.am: Minor change to allow compilation outside the + source_dir. + - lib/egif_lib.c: FILE_STATE_WRITE, FILE_STATE_SCREEN, + FILE_STATE_IMAGE, IS_WRITEABLE are now in gif_lib_private.h + - lib/dgif_lib.c: FILE_STATE_READ and IS_READABLE are now in + gif_lib_private.h + - lib/gif_lib_private.h: Above mentioned constants and macros are now + here. FILE_STATE_READ is now 0x08 instead of 0x00. + - configure.in: Update version to 4.1.0 + - lib/Makefile.am: Update libtool version to 5:0:1 (libtool) + - giflib.spec: Update for version 4.1.0 (Add libungif-4.1 + compatibility stuff and change version.) + - giflib.lsm: Update for version 4.1.0 + - lib/egif_lib.c: (WRITE) change from a function to a macro. + - lib/dgif_lib.c: (DGifOpenFileName) close FileHandle on error. + - lib/dgif_lib.c: (DGifOpenFileHandle) make sure the FILE stream is + closed if we hit an error. + - lib/dev2gif.c, lib/quantize.c, lib/gif_err.c, lib/gif_lib_private.h: + Reflect Eric's copyright notice rather than Gershon's + +1999 14 Jan Michael R Brown + * lib/gif_lib.h: Add OutputFunc type + * lib/gif_lib.h: Add EGifOpen for user supplied output function + * lib/egif_lib.c: (EGifOpenFileName) Fixed wasted memory when an + error occurs in EGifOpenFileHandle + * lib/egif_lib.c: Add EGifOpen, WRITE, and lots of changes to + support user supplied output function. Basically changing + all fwrite's to WRITE, and then all of the knock on effects. + +1998 17 Dec Toshio Kuratomi + * configure.in: Change references to libungif to giflib. + * libungif.lsm: Rename to giflib.lsm and change to reflect giflib + rather than libungif. + * libungif.spec: Rename to giflib.spec and change to reflect giflib + rather than libungif. + * UNCOMPRESSED_GIF: Removed from this branch. + * PATENT_PROBLEMS: Add file explaining Unisys's patent claims. + * Makefile.am: Replace libungif with giflib. + * README: Adapted language to giflib. + * lib/Makefile.am: Changed references to libungif to libgif. + * util/Makefile.am: Changed references to libungif to libgif. + +1998 17 Dec Toshio Kuratomi + * lib/egif_lib.c: Merge LZW stuff into this branch of the library. + This includes numerous changes to initialize the hash table as well + as the code forthe encoder. + * lib/gif_hash.c: Functions needed for the LZW encoder. + * lib/gif_hash.h: Functions needed for the LZW encoder. + * lib/Makefile.am: Add gif_hash.c gif_hash.h to the list of sources. + +1998 15 Dec Toshio Kuratomi + * lib/dgif_lib.c: (DGifSlurp) Fix a Seg Fault when an image contains + no extension blocks. + +1998 14 Dec Toshio Kuratomi + * configure.in: Update version to 4.0 + * lib/Makefile.am: Update libtool version to 4:0:0 (libtool) + * libungif.spec: Update for version 4.0 (not binary compatible with + giflib, change version.) + * lib/gif_lib_private.h: (PrivateType) New header for common stuff + private to the library. Currently, this is only the Private struct. + * lib/dgif_lib.c: (PrivateType) Extract the Private struct to + gif_lib_private.h + * lib/egif_lib.c: (PrivateType) Extract the Private struct to + gif_lib_private.h + * lib/Makefile.am: Add gif_lib_private.h to the list of source files. + * lib/gif_lib.h: (ExtensionBlock) Add a Function entry to the + ExtensionBlock record. Note that this is not entirely correct: + the GifLib ExtensionBlock structure is actually a data sub-block + record. By adding the function entry here, we are pushing the + ExtensionBlockType in with the DataSubBlock. + Sometime in the future, we need to change the API to have true + ExtensionBlocks which have DataSubBlocks belonging to them. + * lib/gif_lib.h: (ExtensionBlock) Deprecate the use of Function in + the SavedImage struct. Use ExtensionBlock's Function instead. + * lib/egif_lib.c: (EGifSpew) Changes to use the new Function variable. + * lib/dgif_lib.c: (DGifSlurp) Changes to put data into the new + Function variable. + +1998 3 Dec Toshio Kuratomi + * lib/dgif_lib.c: (DGifSlurp) Three changes: + - No longer allocate SaveImage in this function. All allocations + of SaveImage take place in DGifGetImageDesc. + - Extension blocks are now associated with the Image Block that is + read in subsequent to them, not before. This should now be + conformant to the gif89a specification. + - Fix an off-by-one error when copying extension data from structure + to structure. + * lib/dgif_lib.c: (DGifGetImageDesc) Change the function to do its own + allocation of space for the SavedImage structure no matter what. + * lib/egif_lib.c: (EGifSpew) The function now spits out + ExtensionBlocks before the associated Image Block to conform with + the gif89a specification. + * lib/egif_lib.c: (EGifOpenFileHandle) Move the write of the + GifVersion (gif87a or gif89a) from this function into + EGifPutScreenDesc so that it can be controlled by EGifSpew. Note + that this is still a hack as the GifVersion write doesn't really + belong in either of these functions. + * lib/egif_lib.c: (EGifPutScreenDesc) Moved writing the version + (gif87a or gif89a) into the file into this function from + EGifOpenFileHandle. + * test-unx: Now test the extension code. + * pic/x-trans.gif: New image with Comments and transparency to test + the extension code with. + +1998 29 Nov Toshio Kuratomi + * lib/dgif_lib.c: (DGifSlurp) Fix a few of the minor bugs plaguing + this function. At this point, the function should no longer cause + a Seg Fault. It is now losing all extension data however. I know + how to hack a fix in, but I need to commit these changes first. + * lib/dgif_lib.c: (DGifGetImageDesc) Fix my bug fix: the colormap is + now only copied if it exists :-). + +1998 10 Nov Toshio Kuratomi + * test-unx: Add a test for DGifSlurp and EGifSpew + +1998 14 Oct Toshio Kuratomi + * lib/dgif_lib.c: (DGifGetImageDesc) Fix a bug where the Colormap for + the image description and the SaveImage were pointers to the same + structure, causing a SegV when DGifClosing the file. + +1998 9 Oct Toshio Kuratomi + * lib/dgif_lib.c: (DGifSlurp) memory for the extensions was not being + allocated. Now I call AddExtensionBlock when I add an extension to + the structure. Additionally, fix a memory leak here. + * configure.in, NEWS, lib/Makefile.am: Update to version 3.1.1 + * ltmain.sh, ltconfig: removed from the cvs repository + * BUGS: add the BUGS file to list unresolved BUGS. + +1998 9 Sep Toshio Kuratomi + * libungif.spec: Fix wrong version in %files and %install section. + +1998 8 Sep Toshio Kuratomi + * lib/gif_hash.c, lib/gif_hash.h: Removed these because a hash table + is not needed to create uncompressed gifs. + * lib/egif_lib.c: Remove all references to the hash functions. + * lib/Makefile.am: Remove gif_hash.c gif_hash.h from the source files. + * libungif.lsm: added this file + +1998 7 Sep Toshio Kuratomi + * lib/dgif_lib.c, lib/gif_lib.h: (DGifOpen) Add callback to read gif + image through user supplied function (Peter Mehlitz). + +1998 6 Sep Toshio Kuratomi + * util/*.{gif.rle}: removed files that were left by my testing + process and shouldn't have been in the distribution. + * UNCOMPRESSED_GIF: add section on why software that can decode + LZW compressed gifs (but not write them) is legal. + * .cvsignore: added .cvsignore files to ignore Makefiles and other + generated files in my cvs repository. + * Makefile.am's: Fixes to allow the dist* family of targets to work + correctly. Preliminary support for make check as well. + * configure.in: Update version to 3.1.0 + * lib/Makefile.am: Update libtool version to 4:0:1 libtool) + * libungif-3.0.spec: Update from Marc Ewing. + * Add int/pointer Alpha fixes from Dick Porter to many source files. diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/DEVELOPERS b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/DEVELOPERS new file mode 100644 index 0000000..2d3f6de --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/DEVELOPERS @@ -0,0 +1,29 @@ +Things to tell developers of libungif.... + +======= +Build Tools: +libungif presently uses autoconf, automake, and libtool in order to build +shared libraries for a wide variety of platforms. The distributed tarball has +files prebuilt from these tools. The cvs repository does not. If you want to +build libungif you will need to have these tools available. I currently run +with the following versions: +autoconf 2.57 +automake 1.7.8 +libtool 1.5 + +(P.S. I use the autogen.sh script in the top level directory to generate +configure, Makefile.in, etc using these tools.) + +======= +cvs: +I currently do my work in a subversion repository. Since sourceforge is still +using cvs and not subversion, there's nothing in the sourceforge tree. + +If someone else wants to do development we can definitely talk about the best +way to share version control access. + +======= +I create the distribution tar ball by using the `make dist` command.... After +hacking all I want, I check my sources into cvs, checkout a clean tree, run +./autogen.sh; make dist and then attempt to compile from the libungif-*.tar.gz +file that is generated. diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/README b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/README new file mode 100644 index 0000000..90e8dbf --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/README @@ -0,0 +1,69 @@ +This is giflib version 4.1.2, a library for manipulating gif files. It is based +on Eric S. Raymond's giflib-3.0 with bugfixes and changes generated for the +libungif library. + +PLEASE BE AWARE OF POSSIBLE LEGAL PROBLEMS WITH USING THIS LIBRARY: READ +THE PATENT_PROBLEMS FILE NOW! + +======= +Latest versions of giflib, as well as a library which does not have the +mentioned patent problems (libungif), are available from: + http://sourceforge.net/projects/libungif + +==== +Building this package should be as simple as: + + ./configure + gmake + gmake install + +==== +Deprecation list. Will be removed in giflib 5.0: +* GIF_ERROR and GIF_MESSAGE are on the deprecation list as they are also + utility helper functions rather than essential to the functioning of the + library. +* The qprintf methods of the library are now deprecated. Do not use + GifQuietPrint or GifQprintf. These should have been pushed out into the + utility helper library instead of sitting around in the library proper at + the same time as the getarg functions were moved out. Getting rid of these + will let us get rid of our dependence on stdarg.h/varargs.h (Which a Gif + reading library has no business requiring.) +* In the SavedImage struct: int Function will be removed. Use + SavedImage.ExtensionBlocks[x].Function instead. +* In gifalloc.c: MakeExtension is deprecated as well. Use AddExtensionBlock + instead. (This and the previous int Function were deprecated because they + only handle one Extension per image. The new code handles multiple + extensions.) +* varargs style interface in qprintf and getarg: It's a mistake to have two + different interfaces that depend on compile time choices between varargs + and stdargs. The future is to get rid of varargs style altogether. + (Also: these are probably going strictly into the utility functions so + the library won't have to worry about them at all.) +==== + +I have found that automake currently generates Makefile's containing some +GNUmake specific syntax. If you're having troubles building with your +system provided make, please install GNU make and try rebuilding. + +==== +This package uses autoconf, automake, and libtool to create the configure +script, so if you need to edit the configure.ac or change a makefile target +you should read the DEVELOPER file for hints on recreating the distribution +using these tools. + +Good luck! +-Toshio Kuratomi + +==== READ.ME file for giflib version 3.0: + + READ ME for GIFLIB + +For complete documentation on the package, point a web browser at +doc/index.html. See the file INSTALL for instructions on how to +install and test the package. + +GIFLIB has a home page at http://www.ccil.org/~esr/giflib. + + Eric S. Raymond + esr@snark.thyrsus.com. + (http://www.ccil.org/~esr) diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/config.h b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/config.h new file mode 100644 index 0000000..ba731f4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/config.h @@ -0,0 +1,14 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ +#ifdef _WIN32 +#include +#include +#define _OPEN_BINARY +#else +typedef unsigned int UINT32; +#include +#include +#include +#endif +#include +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/dgif_lib.c b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/dgif_lib.c new file mode 100644 index 0000000..c460616 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/dgif_lib.c @@ -0,0 +1,962 @@ +/****************************************************************************** + * "Gif-Lib" - Yet another gif library. + * + * Written by: Gershon Elber IBM PC Ver 1.1, Aug. 1990 + ****************************************************************************** + * The kernel of the GIF Decoding process can be found here. + ****************************************************************************** + * History: + * 16 Jun 89 - Version 1.0 by Gershon Elber. + * 3 Sep 90 - Version 1.1 by Gershon Elber (Support for Gif89, Unique names). + *****************************************************************************/ + +#include "config.h" + +#include +#if defined (__MSDOS__) && !defined(__DJGPP__) && !defined(__GNUC__) +#include +#include +#include +#else +#include +#include +#endif /* __MSDOS__ */ + +#ifdef HAVE_IO_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif /* HAVE_FCNTL_H */ +#ifdef HAVE_UNISTD_H +#include +#endif /* HAVE_UNISTD_H */ +#include +#include +#include "gif_lib.h" +#include "gif_lib_private.h" + +#define COMMENT_EXT_FUNC_CODE 0xfe /* Extension function code for + comment. */ + +/* avoid extra function call in case we use fread (TVT) */ +#define READ(_gif,_buf,_len) \ + (((GifFilePrivateType*)_gif->Private)->Read ? \ + ((GifFilePrivateType*)_gif->Private)->Read(_gif,_buf,_len) : \ + fread(_buf,1,_len,((GifFilePrivateType*)_gif->Private)->File)) + +static int DGifGetWord(GifFileType *GifFile, GifWord *Word); +static int DGifSetupDecompress(GifFileType *GifFile); +static int DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, + int LineLen); +static int DGifGetPrefixChar(GifPrefixType *Prefix, int Code, int ClearCode); +static int DGifDecompressInput(GifFileType *GifFile, int *Code); +static int DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, + GifByteType *NextByte); +#ifndef _GBA_NO_FILEIO + +/****************************************************************************** + * Open a new gif file for read, given by its name. + * Returns GifFileType pointer dynamically allocated which serves as the gif + * info record. _GifError is cleared if succesfull. + *****************************************************************************/ +GifFileType * +DGifOpenFileName(const char *FileName) { + int FileHandle; + GifFileType *GifFile; + + if ((FileHandle = open(FileName, O_RDONLY +#if defined(__MSDOS__) || defined(_OPEN_BINARY) + | O_BINARY +#endif /* __MSDOS__ || _OPEN_BINARY */ + )) == -1) { + _GifError = D_GIF_ERR_OPEN_FAILED; + return NULL; + } + + GifFile = DGifOpenFileHandle(FileHandle); + if (GifFile == (GifFileType *)NULL) + close(FileHandle); + return GifFile; +} + +/****************************************************************************** + * Update a new gif file, given its file handle. + * Returns GifFileType pointer dynamically allocated which serves as the gif + * info record. _GifError is cleared if succesfull. + *****************************************************************************/ +GifFileType * +DGifOpenFileHandle(int FileHandle) { + + unsigned char Buf[GIF_STAMP_LEN + 1]; + GifFileType *GifFile; + GifFilePrivateType *Private; + FILE *f; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + _GifError = D_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + _GifError = D_GIF_ERR_NOT_ENOUGH_MEM; + free((char *)GifFile); + return NULL; + } +#ifdef __MSDOS__ + setmode(FileHandle, O_BINARY); /* Make sure it is in binary mode. */ +#endif /* __MSDOS__ */ + + f = fdopen(FileHandle, "rb"); /* Make it into a stream: */ + +#ifdef __MSDOS__ + setvbuf(f, NULL, _IOFBF, GIF_FILE_BUFFER_SIZE); /* And inc. stream + buffer. */ +#endif /* __MSDOS__ */ + + GifFile->Private = (VoidPtr)Private; + Private->FileHandle = FileHandle; + Private->File = f; + Private->FileState = FILE_STATE_READ; + Private->Read = 0; /* don't use alternate input method (TVT) */ + GifFile->UserData = 0; /* TVT */ + + /* Lets see if this is a GIF file: */ + if (READ(GifFile, Buf, GIF_STAMP_LEN) != GIF_STAMP_LEN) { + _GifError = D_GIF_ERR_READ_FAILED; + fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + /* The GIF Version number is ignored at this time. Maybe we should do + * something more useful with it. */ + Buf[GIF_STAMP_LEN] = 0; + if (strncmp(GIF_STAMP, (char*)Buf, GIF_VERSION_POS) != 0) { + _GifError = D_GIF_ERR_NOT_GIF_FILE; + fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + if (DGifGetScreenDesc(GifFile) == GIF_ERROR) { + fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + _GifError = 0; + + return GifFile; +} + +#endif /* _GBA_NO_FILEIO */ + +/****************************************************************************** + * GifFileType constructor with user supplied input function (TVT) + *****************************************************************************/ +GifFileType * +DGifOpen(void *userData, + InputFunc readFunc) { + + unsigned char Buf[GIF_STAMP_LEN + 1]; + GifFileType *GifFile; + GifFilePrivateType *Private; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + _GifError = D_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (!Private) { + _GifError = D_GIF_ERR_NOT_ENOUGH_MEM; + free((char *)GifFile); + return NULL; + } + + GifFile->Private = (VoidPtr)Private; + Private->FileHandle = 0; + Private->File = 0; + Private->FileState = FILE_STATE_READ; + + Private->Read = readFunc; /* TVT */ + GifFile->UserData = userData; /* TVT */ + + /* Lets see if this is a GIF file: */ + if (READ(GifFile, Buf, GIF_STAMP_LEN) != GIF_STAMP_LEN) { + _GifError = D_GIF_ERR_READ_FAILED; + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + /* The GIF Version number is ignored at this time. Maybe we should do + * something more useful with it. */ + Buf[GIF_STAMP_LEN] = 0; + if (strncmp(GIF_STAMP, (char*)Buf, GIF_VERSION_POS) != 0) { + _GifError = D_GIF_ERR_NOT_GIF_FILE; + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + if (DGifGetScreenDesc(GifFile) == GIF_ERROR) { + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + _GifError = 0; + + return GifFile; +} + +/****************************************************************************** + * This routine should be called before any other DGif calls. Note that + * this routine is called automatically from DGif file open routines. + *****************************************************************************/ +int +DGifGetScreenDesc(GifFileType * GifFile) { + + int i, BitsPerPixel; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* Put the screen descriptor into the file: */ + if (DGifGetWord(GifFile, &GifFile->SWidth) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->SHeight) == GIF_ERROR) + return GIF_ERROR; + + if (READ(GifFile, Buf, 3) != 3) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + GifFile->SColorResolution = (((Buf[0] & 0x70) + 1) >> 4) + 1; + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->SBackGroundColor = Buf[1]; + if (Buf[0] & 0x80) { /* Do we have global color map? */ + + GifFile->SColorMap = MakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->SColorMap == NULL) { + _GifError = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the global color map: */ + for (i = 0; i < GifFile->SColorMap->ColorCount; i++) { + if (READ(GifFile, Buf, 3) != 3) { + FreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + GifFile->SColorMap->Colors[i].Red = Buf[0]; + GifFile->SColorMap->Colors[i].Green = Buf[1]; + GifFile->SColorMap->Colors[i].Blue = Buf[2]; + } + } else { + GifFile->SColorMap = NULL; + } + + return GIF_OK; +} + +/****************************************************************************** + * This routine should be called before any attempt to read an image. + *****************************************************************************/ +int +DGifGetRecordType(GifFileType * GifFile, + GifRecordType * Type) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (READ(GifFile, &Buf, 1) != 1) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + switch (Buf) { + case ',': + *Type = IMAGE_DESC_RECORD_TYPE; + break; + case '!': + *Type = EXTENSION_RECORD_TYPE; + break; + case ';': + *Type = TERMINATE_RECORD_TYPE; + break; + default: + *Type = UNDEFINED_RECORD_TYPE; + _GifError = D_GIF_ERR_WRONG_RECORD; + return GIF_ERROR; + } + + return GIF_OK; +} + +/****************************************************************************** + * This routine should be called before any attempt to read an image. + * Note it is assumed the Image desc. header (',') has been read. + *****************************************************************************/ +int +DGifGetImageDesc(GifFileType * GifFile) { + + int i, BitsPerPixel; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifGetWord(GifFile, &GifFile->Image.Left) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Top) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Width) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Height) == GIF_ERROR) + return GIF_ERROR; + if (READ(GifFile, Buf, 1) != 1) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->Image.Interlace = (Buf[0] & 0x40); + if (Buf[0] & 0x80) { /* Does this image have local color map? */ + + if (GifFile->Image.ColorMap) + FreeMapObject(GifFile->Image.ColorMap); + + GifFile->Image.ColorMap = MakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->Image.ColorMap == NULL) { + _GifError = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the image local color map: */ + for (i = 0; i < GifFile->Image.ColorMap->ColorCount; i++) { + if (READ(GifFile, Buf, 3) != 3) { + FreeMapObject(GifFile->Image.ColorMap); + _GifError = D_GIF_ERR_READ_FAILED; + GifFile->Image.ColorMap = NULL; + return GIF_ERROR; + } + GifFile->Image.ColorMap->Colors[i].Red = Buf[0]; + GifFile->Image.ColorMap->Colors[i].Green = Buf[1]; + GifFile->Image.ColorMap->Colors[i].Blue = Buf[2]; + } + } else if (GifFile->Image.ColorMap) { + FreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + + GifFile->ImageCount++; + + Private->PixelCount = (long)GifFile->Image.Width * + (long)GifFile->Image.Height; + + DGifSetupDecompress(GifFile); /* Reset decompress algorithm parameters. */ + + return GIF_OK; +} + +/****************************************************************************** + * Get one full scanned line (Line) of length LineLen from GIF file. + *****************************************************************************/ +int +DGifGetLine(GifFileType * GifFile, + GifPixelType * Line, + int LineLen) { + + GifByteType *Dummy; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (!LineLen) + LineLen = GifFile->Image.Width; + +#if defined(__MSDOS__) || defined(__GNUC__) + if ((Private->PixelCount -= LineLen) > 0xffff0000UL) { +#else + if ((Private->PixelCount -= LineLen) > 0xffff0000) { +#endif /* __MSDOS__ */ + _GifError = D_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + + if (DGifDecompressLine(GifFile, Line, LineLen) == GIF_OK) { + if (Private->PixelCount == 0) { + /* We probably would not be called any more, so lets clean + * everything before we return: need to flush out all rest of + * image until empty block (size 0) detected. We use GetCodeNext. */ + do + if (DGifGetCodeNext(GifFile, &Dummy) == GIF_ERROR) + return GIF_ERROR; + while (Dummy != NULL) ; + } + return GIF_OK; + } else + return GIF_ERROR; +} + +/****************************************************************************** + * Put one pixel (Pixel) into GIF file. + *****************************************************************************/ +int +DGifGetPixel(GifFileType * GifFile, + GifPixelType Pixel) { + + GifByteType *Dummy; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } +#if defined(__MSDOS__) || defined(__GNUC__) + if (--Private->PixelCount > 0xffff0000UL) +#else + if (--Private->PixelCount > 0xffff0000) +#endif /* __MSDOS__ */ + { + _GifError = D_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + + if (DGifDecompressLine(GifFile, &Pixel, 1) == GIF_OK) { + if (Private->PixelCount == 0) { + /* We probably would not be called any more, so lets clean + * everything before we return: need to flush out all rest of + * image until empty block (size 0) detected. We use GetCodeNext. */ + do + if (DGifGetCodeNext(GifFile, &Dummy) == GIF_ERROR) + return GIF_ERROR; + while (Dummy != NULL) ; + } + return GIF_OK; + } else + return GIF_ERROR; +} + +/****************************************************************************** + * Get an extension block (see GIF manual) from gif file. This routine only + * returns the first data block, and DGifGetExtensionNext should be called + * after this one until NULL extension is returned. + * The Extension should NOT be freed by the user (not dynamically allocated). + * Note it is assumed the Extension desc. header ('!') has been read. + *****************************************************************************/ +int +DGifGetExtension(GifFileType * GifFile, + int *ExtCode, + GifByteType ** Extension) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (READ(GifFile, &Buf, 1) != 1) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *ExtCode = Buf; + + return DGifGetExtensionNext(GifFile, Extension); +} + +/****************************************************************************** + * Get a following extension block (see GIF manual) from gif file. This + * routine should be called until NULL Extension is returned. + * The Extension should NOT be freed by the user (not dynamically allocated). + *****************************************************************************/ +int +DGifGetExtensionNext(GifFileType * GifFile, + GifByteType ** Extension) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (READ(GifFile, &Buf, 1) != 1) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + if (Buf > 0) { + *Extension = Private->Buf; /* Use private unused buffer. */ + (*Extension)[0] = Buf; /* Pascal strings notation (pos. 0 is len.). */ + if (READ(GifFile, &((*Extension)[1]), Buf) != Buf) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else + *Extension = NULL; + + return GIF_OK; +} + +/****************************************************************************** + * This routine should be called last, to close the GIF file. + *****************************************************************************/ +int +DGifCloseFile(GifFileType * GifFile) { + + GifFilePrivateType *Private; + FILE *File; + + if (GifFile == NULL) + return GIF_ERROR; + + Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + File = Private->File; + + if (GifFile->Image.ColorMap) { + FreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + + if (GifFile->SColorMap) { + FreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + } + + if (Private) { + free((char *)Private); + Private = NULL; + } + + free(GifFile); + + if (File && (fclose(File) != 0)) { + _GifError = D_GIF_ERR_CLOSE_FAILED; + return GIF_ERROR; + } + return GIF_OK; +} + +/****************************************************************************** + * Get 2 bytes (word) from the given file: + *****************************************************************************/ +static int +DGifGetWord(GifFileType * GifFile, + GifWord *Word) { + + unsigned char c[2]; + + if (READ(GifFile, c, 2) != 2) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + *Word = (((unsigned int)c[1]) << 8) + c[0]; + return GIF_OK; +} + +/****************************************************************************** + * Get the image code in compressed form. This routine can be called if the + * information needed to be piped out as is. Obviously this is much faster + * than decoding and encoding again. This routine should be followed by calls + * to DGifGetCodeNext, until NULL block is returned. + * The block should NOT be freed by the user (not dynamically allocated). + *****************************************************************************/ +int +DGifGetCode(GifFileType * GifFile, + int *CodeSize, + GifByteType ** CodeBlock) { + + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + *CodeSize = Private->BitsPerPixel; + + return DGifGetCodeNext(GifFile, CodeBlock); +} + +/****************************************************************************** + * Continue to get the image code in compressed form. This routine should be + * called until NULL block is returned. + * The block should NOT be freed by the user (not dynamically allocated). + *****************************************************************************/ +int +DGifGetCodeNext(GifFileType * GifFile, + GifByteType ** CodeBlock) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (READ(GifFile, &Buf, 1) != 1) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + if (Buf > 0) { + *CodeBlock = Private->Buf; /* Use private unused buffer. */ + (*CodeBlock)[0] = Buf; /* Pascal strings notation (pos. 0 is len.). */ + if (READ(GifFile, &((*CodeBlock)[1]), Buf) != Buf) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else { + *CodeBlock = NULL; + Private->Buf[0] = 0; /* Make sure the buffer is empty! */ + Private->PixelCount = 0; /* And local info. indicate image read. */ + } + + return GIF_OK; +} + +/****************************************************************************** + * Setup the LZ decompression for this image: + *****************************************************************************/ +static int +DGifSetupDecompress(GifFileType * GifFile) { + + int i, BitsPerPixel; + GifByteType CodeSize; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + READ(GifFile, &CodeSize, 1); /* Read Code size from file. */ + BitsPerPixel = CodeSize; + + Private->Buf[0] = 0; /* Input Buffer empty. */ + Private->BitsPerPixel = BitsPerPixel; + Private->ClearCode = (1 << BitsPerPixel); + Private->EOFCode = Private->ClearCode + 1; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ + Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ + Private->StackPtr = 0; /* No pixels on the pixel stack. */ + Private->LastCode = NO_SUCH_CODE; + Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ + Private->CrntShiftDWord = 0; + + Prefix = Private->Prefix; + for (i = 0; i <= LZ_MAX_CODE; i++) + Prefix[i] = NO_SUCH_CODE; + + return GIF_OK; +} + +/****************************************************************************** + * The LZ decompression routine: + * This version decompress the given gif file into Line of length LineLen. + * This routine can be called few times (one per scan line, for example), in + * order the complete the whole image. + *****************************************************************************/ +static int +DGifDecompressLine(GifFileType * GifFile, + GifPixelType * Line, + int LineLen) { + + int i = 0; + int j, CrntCode, EOFCode, ClearCode, CrntPrefix, LastCode, StackPtr; + GifByteType *Stack, *Suffix; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + StackPtr = Private->StackPtr; + Prefix = Private->Prefix; + Suffix = Private->Suffix; + Stack = Private->Stack; + EOFCode = Private->EOFCode; + ClearCode = Private->ClearCode; + LastCode = Private->LastCode; + + if (StackPtr != 0) { + /* Let pop the stack off before continueing to read the gif file: */ + while (StackPtr != 0 && i < LineLen) + Line[i++] = Stack[--StackPtr]; + } + + while (i < LineLen) { /* Decode LineLen items. */ + if (DGifDecompressInput(GifFile, &CrntCode) == GIF_ERROR) + return GIF_ERROR; + + if (CrntCode == EOFCode) { + /* Note however that usually we will not be here as we will stop + * decoding as soon as we got all the pixel, or EOF code will + * not be read at all, and DGifGetLine/Pixel clean everything. */ + if (i != LineLen - 1 || Private->PixelCount != 0) { + _GifError = D_GIF_ERR_EOF_TOO_SOON; + return GIF_ERROR; + } + i++; + } else if (CrntCode == ClearCode) { + /* We need to start over again: */ + for (j = 0; j <= LZ_MAX_CODE; j++) + Prefix[j] = NO_SUCH_CODE; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + LastCode = Private->LastCode = NO_SUCH_CODE; + } else { + /* Its regular code - if in pixel range simply add it to output + * stream, otherwise trace to codes linked list until the prefix + * is in pixel range: */ + if (CrntCode < ClearCode) { + /* This is simple - its pixel scalar, so add it to output: */ + Line[i++] = CrntCode; + } else { + /* Its a code to needed to be traced: trace the linked list + * until the prefix is a pixel, while pushing the suffix + * pixels on our stack. If we done, pop the stack in reverse + * (thats what stack is good for!) order to output. */ + if (Prefix[CrntCode] == NO_SUCH_CODE) { + /* Only allowed if CrntCode is exactly the running code: + * In that case CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! */ + if (CrntCode == Private->RunningCode - 2) { + CrntPrefix = LastCode; + Suffix[Private->RunningCode - 2] = + Stack[StackPtr++] = DGifGetPrefixChar(Prefix, + LastCode, + ClearCode); + } else { + _GifError = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + } else + CrntPrefix = CrntCode; + + /* Now (if image is O.K.) we should not get an NO_SUCH_CODE + * During the trace. As we might loop forever, in case of + * defective image, we count the number of loops we trace + * and stop if we got LZ_MAX_CODE. obviously we can not + * loop more than that. */ + j = 0; + while (j++ <= LZ_MAX_CODE && + CrntPrefix > ClearCode && CrntPrefix <= LZ_MAX_CODE) { + Stack[StackPtr++] = Suffix[CrntPrefix]; + CrntPrefix = Prefix[CrntPrefix]; + } + if (j >= LZ_MAX_CODE || CrntPrefix > LZ_MAX_CODE) { + _GifError = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + /* Push the last character on stack: */ + Stack[StackPtr++] = CrntPrefix; + + /* Now lets pop all the stack into output: */ + while (StackPtr != 0 && i < LineLen) + Line[i++] = Stack[--StackPtr]; + } + if (LastCode != NO_SUCH_CODE) { + Prefix[Private->RunningCode - 2] = LastCode; + + if (CrntCode == Private->RunningCode - 2) { + /* Only allowed if CrntCode is exactly the running code: + * In that case CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! */ + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, LastCode, ClearCode); + } else { + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, CrntCode, ClearCode); + } + } + LastCode = CrntCode; + } + } + + Private->LastCode = LastCode; + Private->StackPtr = StackPtr; + + return GIF_OK; +} + +/****************************************************************************** + * Routine to trace the Prefixes linked list until we get a prefix which is + * not code, but a pixel value (less than ClearCode). Returns that pixel value. + * If image is defective, we might loop here forever, so we limit the loops to + * the maximum possible if image O.k. - LZ_MAX_CODE times. + *****************************************************************************/ +static int +DGifGetPrefixChar(GifPrefixType *Prefix, + int Code, + int ClearCode) { + + int i = 0; + + while (Code > ClearCode && i++ <= LZ_MAX_CODE) + Code = Prefix[Code]; + return Code; +} + +/****************************************************************************** + * Interface for accessing the LZ codes directly. Set Code to the real code + * (12bits), or to -1 if EOF code is returned. + *****************************************************************************/ +int +DGifGetLZCodes(GifFileType * GifFile, + int *Code) { + + GifByteType *CodeBlock; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + _GifError = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifDecompressInput(GifFile, Code) == GIF_ERROR) + return GIF_ERROR; + + if (*Code == Private->EOFCode) { + /* Skip rest of codes (hopefully only NULL terminating block): */ + do { + if (DGifGetCodeNext(GifFile, &CodeBlock) == GIF_ERROR) + return GIF_ERROR; + } while (CodeBlock != NULL) ; + + *Code = -1; + } else if (*Code == Private->ClearCode) { + /* We need to start over again: */ + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + } + + return GIF_OK; +} + +/****************************************************************************** + * The LZ decompression input routine: + * This routine is responsable for the decompression of the bit stream from + * 8 bits (bytes) packets, into the real codes. + * Returns GIF_OK if read succesfully. + *****************************************************************************/ +static int +DGifDecompressInput(GifFileType * GifFile, + int *Code) { + + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + GifByteType NextByte; + static unsigned short CodeMasks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, + 0x000f, 0x001f, 0x003f, 0x007f, + 0x00ff, 0x01ff, 0x03ff, 0x07ff, + 0x0fff + }; + /* The image can't contain more than LZ_BITS per code. */ + if (Private->RunningBits > LZ_BITS) { + _GifError = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + + while (Private->CrntShiftState < Private->RunningBits) { + /* Needs to get more bytes from input stream for next code: */ + if (DGifBufferedInput(GifFile, Private->Buf, &NextByte) == GIF_ERROR) { + return GIF_ERROR; + } + Private->CrntShiftDWord |= + ((unsigned long)NextByte) << Private->CrntShiftState; + Private->CrntShiftState += 8; + } + *Code = Private->CrntShiftDWord & CodeMasks[Private->RunningBits]; + + Private->CrntShiftDWord >>= Private->RunningBits; + Private->CrntShiftState -= Private->RunningBits; + + /* If code cannot fit into RunningBits bits, must raise its size. Note + * however that codes above 4095 are used for special signaling. + * If we're using LZ_BITS bits already and we're at the max code, just + * keep using the table as it is, don't increment Private->RunningCode. + */ + if (Private->RunningCode < LZ_MAX_CODE + 2 && + ++Private->RunningCode > Private->MaxCode1 && + Private->RunningBits < LZ_BITS) { + Private->MaxCode1 <<= 1; + Private->RunningBits++; + } + return GIF_OK; +} + +/****************************************************************************** + * This routines read one gif data block at a time and buffers it internally + * so that the decompression routine could access it. + * The routine returns the next byte from its internal buffer (or read next + * block in if buffer empty) and returns GIF_OK if succesful. + *****************************************************************************/ +static int +DGifBufferedInput(GifFileType * GifFile, + GifByteType * Buf, + GifByteType * NextByte) { + + if (Buf[0] == 0) { + /* Needs to read the next buffer - this one is empty: */ + if (READ(GifFile, Buf, 1) != 1) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + /* There shouldn't be any empty data blocks here as the LZW spec + * says the LZW termination code should come first. Therefore we + * shouldn't be inside this routine at that point. + */ + if (Buf[0] == 0) { + _GifError = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + if (READ(GifFile, &Buf[1], Buf[0]) != Buf[0]) { + _GifError = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *NextByte = Buf[1]; + Buf[1] = 2; /* We use now the second place as last char read! */ + Buf[0]--; + } else { + *NextByte = Buf[Buf[1]++]; + Buf[0]--; + } + + return GIF_OK; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/egif_lib.c b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/egif_lib.c new file mode 100644 index 0000000..79108ae --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/egif_lib.c @@ -0,0 +1,998 @@ +/****************************************************************************** + * "Gif-Lib" - Yet another gif library. + * + * Written by: Gershon Elber Ver 1.1, Aug. 1990 + ****************************************************************************** + * The kernel of the GIF Encoding process can be found here. + ****************************************************************************** + * History: + * 14 Jun 89 - Version 1.0 by Gershon Elber. + * 3 Sep 90 - Version 1.1 by Gershon Elber (Support for Gif89, Unique names). + * 26 Jun 96 - Version 3.0 by Eric S. Raymond (Full GIF89 support) + *****************************************************************************/ + +#include "config.h" + +/* Find a thirty-two bit int type */ +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_STDINT_H +#include +#endif + +#ifdef __MSDOS__ +#include +#include +#include +#else +#include +#include +#ifdef R6000 + +/* FIXME: What is sys/mode.h? Can we substitute a check for this file rather + * than a check based on machine type? + */ +#include +#endif +#endif /* __MSDOS__ */ + +#ifdef HAVE_IO_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif /* HAVE_FCNTL_H */ +#ifdef HAVE_UNISTD_H +#include +#endif /* HAVE_UNISTD_H */ +#include +#include +#include +#include "gif_lib.h" +#include "gif_lib_private.h" + +/* #define DEBUG_NO_PREFIX Dump only compressed data. */ + +/* Masks given codes to BitsPerPixel, to make sure all codes are in range: */ +static GifPixelType CodeMask[] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff +}; + +static char GifVersionPrefix[GIF_STAMP_LEN + 1] = GIF87_STAMP; + +#define WRITE(_gif,_buf,_len) \ + (((GifFilePrivateType*)_gif->Private)->Write ? \ + ((GifFilePrivateType*)_gif->Private)->Write(_gif,_buf,_len) : \ + fwrite(_buf, 1, _len, ((GifFilePrivateType*)_gif->Private)->File)) + +static int EGifPutWord(int Word, GifFileType * GifFile); +static int EGifSetupCompress(GifFileType * GifFile); +static int EGifCompressLine(GifFileType * GifFile, GifPixelType * Line, + int LineLen); +static int EGifCompressOutput(GifFileType * GifFile, int Code); +static int EGifBufferedOutput(GifFileType * GifFile, GifByteType * Buf, + int c); + +/****************************************************************************** + * Open a new gif file for write, given by its name. If TestExistance then + * if the file exists this routines fails (returns NULL). + * Returns GifFileType pointer dynamically allocated which serves as the gif + * info record. _GifError is cleared if succesfull. + *****************************************************************************/ +GifFileType * +EGifOpenFileName(const char *FileName, + int TestExistance) { + + int FileHandle; + GifFileType *GifFile; + + if (TestExistance) + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_EXCL +#if defined(__MSDOS__) || defined(_WIN32) + | O_BINARY +#endif /* __MSDOS__ */ + , 0644 ); + else + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_TRUNC +#if defined(__MSDOS__) || defined(_WIN32) + | O_BINARY +#endif /* __MSDOS__ */ + , 0644 ); + + if (FileHandle == -1) { + _GifError = E_GIF_ERR_OPEN_FAILED; + return NULL; + } + GifFile = EGifOpenFileHandle(FileHandle); + if (GifFile == (GifFileType *) NULL) + close(FileHandle); + return GifFile; +} + +/****************************************************************************** + * Update a new gif file, given its file handle, which must be opened for + * write in binary mode. + * Returns GifFileType pointer dynamically allocated which serves as the gif + * info record. _GifError is cleared if succesfull. + *****************************************************************************/ +GifFileType * +EGifOpenFileHandle(int FileHandle) { + + GifFileType *GifFile; + GifFilePrivateType *Private; + FILE *f; + + GifFile = (GifFileType *) malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + free(GifFile); + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + if ((Private->HashTable = _InitHashTable()) == NULL) { + free(GifFile); + free(Private); + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + +#ifdef __MSDOS__ + setmode(FileHandle, O_BINARY); /* Make sure it is in binary mode. */ +#endif /* __MSDOS__ */ + + f = fdopen(FileHandle, "wb"); /* Make it into a stream: */ + +#ifdef __MSDOS__ + setvbuf(f, NULL, _IOFBF, GIF_FILE_BUFFER_SIZE); /* And inc. stream + * buffer. */ +#endif /* __MSDOS__ */ + + GifFile->Private = (VoidPtr)Private; + Private->FileHandle = FileHandle; + Private->File = f; + Private->FileState = FILE_STATE_WRITE; + + Private->Write = (OutputFunc) 0; /* No user write routine (MRB) */ + GifFile->UserData = (VoidPtr) 0; /* No user write handle (MRB) */ + + _GifError = 0; + + return GifFile; +} + +/****************************************************************************** + * Output constructor that takes user supplied output function. + * Basically just a copy of EGifOpenFileHandle. (MRB) + *****************************************************************************/ +GifFileType * +EGifOpen(void *userData, + OutputFunc writeFunc) { + + GifFileType *GifFile; + GifFilePrivateType *Private; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + free(GifFile); + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + Private->HashTable = _InitHashTable(); + if (Private->HashTable == NULL) { + free (GifFile); + free (Private); + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + GifFile->Private = (VoidPtr) Private; + Private->FileHandle = 0; + Private->File = (FILE *) 0; + Private->FileState = FILE_STATE_WRITE; + + Private->Write = writeFunc; /* User write routine (MRB) */ + GifFile->UserData = userData; /* User write handle (MRB) */ + + _GifError = 0; + + return GifFile; +} + +/****************************************************************************** + * Routine to set current GIF version. All files open for write will be + * using this version until next call to this routine. Version consists of + * 3 characters as "87a" or "89a". No test is made to validate the version. + *****************************************************************************/ +void +EGifSetGifVersion(const char *Version) { + strncpy(GifVersionPrefix + GIF_VERSION_POS, Version, 3); +} + +/****************************************************************************** + * This routine should be called before any other EGif calls, immediately + * follows the GIF file openning. + *****************************************************************************/ +int +EGifPutScreenDesc(GifFileType * GifFile, + int Width, + int Height, + int ColorRes, + int BackGround, + const ColorMapObject * ColorMap) { + + int i; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (Private->FileState & FILE_STATE_SCREEN) { + /* If already has screen descriptor - something is wrong! */ + _GifError = E_GIF_ERR_HAS_SCRN_DSCR; + return GIF_ERROR; + } + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + +/* First write the version prefix into the file. */ +#ifndef DEBUG_NO_PREFIX + if (WRITE(GifFile, (unsigned char *)GifVersionPrefix, + strlen(GifVersionPrefix)) != strlen(GifVersionPrefix)) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } +#endif /* DEBUG_NO_PREFIX */ + + GifFile->SWidth = Width; + GifFile->SHeight = Height; + GifFile->SColorResolution = ColorRes; + GifFile->SBackGroundColor = BackGround; + if (ColorMap) { + GifFile->SColorMap = MakeMapObject(ColorMap->ColorCount, + ColorMap->Colors); + if (GifFile->SColorMap == NULL) { + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } else + GifFile->SColorMap = NULL; + + /* + * Put the logical screen descriptor into the file: + */ + /* Logical Screen Descriptor: Dimensions */ + EGifPutWord(Width, GifFile); + EGifPutWord(Height, GifFile); + + /* Logical Screen Descriptor: Packed Fields */ + /* Note: We have actual size of the color table default to the largest + * possible size (7+1 == 8 bits) because the decoder can use it to decide + * how to display the files. + */ + Buf[0] = (ColorMap ? 0x80 : 0x00) | /* Yes/no global colormap */ + ((ColorRes - 1) << 4) | /* Bits allocated to each primary color */ + (ColorMap ? ColorMap->BitsPerPixel - 1 : 0x07 ); /* Actual size of the + color table. */ + Buf[1] = BackGround; /* Index into the ColorTable for background color */ + Buf[2] = 0; /* Pixel Aspect Ratio */ +#ifndef DEBUG_NO_PREFIX + WRITE(GifFile, Buf, 3); +#endif /* DEBUG_NO_PREFIX */ + + /* If we have Global color map - dump it also: */ +#ifndef DEBUG_NO_PREFIX + if (ColorMap != NULL) + for (i = 0; i < ColorMap->ColorCount; i++) { + /* Put the ColorMap out also: */ + Buf[0] = ColorMap->Colors[i].Red; + Buf[1] = ColorMap->Colors[i].Green; + Buf[2] = ColorMap->Colors[i].Blue; + if (WRITE(GifFile, Buf, 3) != 3) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } +#endif /* DEBUG_NO_PREFIX */ + + /* Mark this file as has screen descriptor, and no pixel written yet: */ + Private->FileState |= FILE_STATE_SCREEN; + + return GIF_OK; +} + +/****************************************************************************** + * This routine should be called before any attempt to dump an image - any + * call to any of the pixel dump routines. + *****************************************************************************/ +int +EGifPutImageDesc(GifFileType * GifFile, + int Left, + int Top, + int Width, + int Height, + int Interlace, + const ColorMapObject * ColorMap) { + + int i; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (Private->FileState & FILE_STATE_IMAGE && +#if defined(__MSDOS__) || defined(__GNUC__) + Private->PixelCount > 0xffff0000UL) { +#else + Private->PixelCount > 0xffff0000) { +#endif /* __MSDOS__ */ + /* If already has active image descriptor - something is wrong! */ + _GifError = E_GIF_ERR_HAS_IMAG_DSCR; + return GIF_ERROR; + } + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + GifFile->Image.Left = Left; + GifFile->Image.Top = Top; + GifFile->Image.Width = Width; + GifFile->Image.Height = Height; + GifFile->Image.Interlace = Interlace; + if (ColorMap) { + GifFile->Image.ColorMap = MakeMapObject(ColorMap->ColorCount, + ColorMap->Colors); + if (GifFile->Image.ColorMap == NULL) { + _GifError = E_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } else { + GifFile->Image.ColorMap = NULL; + } + + /* Put the image descriptor into the file: */ + Buf[0] = ','; /* Image seperator character. */ +#ifndef DEBUG_NO_PREFIX + WRITE(GifFile, Buf, 1); +#endif /* DEBUG_NO_PREFIX */ + EGifPutWord(Left, GifFile); + EGifPutWord(Top, GifFile); + EGifPutWord(Width, GifFile); + EGifPutWord(Height, GifFile); + Buf[0] = (ColorMap ? 0x80 : 0x00) | + (Interlace ? 0x40 : 0x00) | + (ColorMap ? ColorMap->BitsPerPixel - 1 : 0); +#ifndef DEBUG_NO_PREFIX + WRITE(GifFile, Buf, 1); +#endif /* DEBUG_NO_PREFIX */ + + /* If we have Global color map - dump it also: */ +#ifndef DEBUG_NO_PREFIX + if (ColorMap != NULL) + for (i = 0; i < ColorMap->ColorCount; i++) { + /* Put the ColorMap out also: */ + Buf[0] = ColorMap->Colors[i].Red; + Buf[1] = ColorMap->Colors[i].Green; + Buf[2] = ColorMap->Colors[i].Blue; + if (WRITE(GifFile, Buf, 3) != 3) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } +#endif /* DEBUG_NO_PREFIX */ + if (GifFile->SColorMap == NULL && GifFile->Image.ColorMap == NULL) { + _GifError = E_GIF_ERR_NO_COLOR_MAP; + return GIF_ERROR; + } + + /* Mark this file as has screen descriptor: */ + Private->FileState |= FILE_STATE_IMAGE; + Private->PixelCount = (long)Width *(long)Height; + + EGifSetupCompress(GifFile); /* Reset compress algorithm parameters. */ + + return GIF_OK; +} + +/****************************************************************************** + * Put one full scanned line (Line) of length LineLen into GIF file. + *****************************************************************************/ +int +EGifPutLine(GifFileType * GifFile, + GifPixelType * Line, + int LineLen) { + + int i; + GifPixelType Mask; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (!LineLen) + LineLen = GifFile->Image.Width; + if (Private->PixelCount < (unsigned)LineLen) { + _GifError = E_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + Private->PixelCount -= LineLen; + + /* Make sure the codes are not out of bit range, as we might generate + * wrong code (because of overflow when we combine them) in this case: */ + Mask = CodeMask[Private->BitsPerPixel]; + for (i = 0; i < LineLen; i++) + Line[i] &= Mask; + + return EGifCompressLine(GifFile, Line, LineLen); +} + +/****************************************************************************** + * Put one pixel (Pixel) into GIF file. + *****************************************************************************/ +int +EGifPutPixel(GifFileType * GifFile, + GifPixelType Pixel) { + + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (Private->PixelCount == 0) { + _GifError = E_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + --Private->PixelCount; + + /* Make sure the code is not out of bit range, as we might generate + * wrong code (because of overflow when we combine them) in this case: */ + Pixel &= CodeMask[Private->BitsPerPixel]; + + return EGifCompressLine(GifFile, &Pixel, 1); +} + +/****************************************************************************** + * Put a comment into GIF file using the GIF89 comment extension block. + *****************************************************************************/ +int +EGifPutComment(GifFileType * GifFile, + const char *Comment) { + + unsigned int length = strlen(Comment); + char *buf; + + length = strlen(Comment); + if (length <= 255) { + return EGifPutExtension(GifFile, COMMENT_EXT_FUNC_CODE, + length, Comment); + } else { + buf = (char *)Comment; + if (EGifPutExtensionFirst(GifFile, COMMENT_EXT_FUNC_CODE, 255, buf) + == GIF_ERROR) { + return GIF_ERROR; + } + length -= 255; + buf = buf + 255; + + /* Break the comment into 255 byte sub blocks */ + while (length > 255) { + if (EGifPutExtensionNext(GifFile, 0, 255, buf) == GIF_ERROR) { + return GIF_ERROR; + } + buf = buf + 255; + length -= 255; + } + /* Output any partial block and the clear code. */ + if (length > 0) { + if (EGifPutExtensionLast(GifFile, 0, length, buf) == GIF_ERROR) { + return GIF_ERROR; + } + } else { + if (EGifPutExtensionLast(GifFile, 0, 0, NULL) == GIF_ERROR) { + return GIF_ERROR; + } + } + } + return GIF_OK; +} + +/****************************************************************************** + * Put a first extension block (see GIF manual) into gif file. Here more + * extensions can be dumped using EGifPutExtensionNext until + * EGifPutExtensionLast is invoked. + *****************************************************************************/ +int +EGifPutExtensionFirst(GifFileType * GifFile, + int ExtCode, + int ExtLen, + const VoidPtr Extension) { + + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (ExtCode == 0) { + WRITE(GifFile, (GifByteType *)&ExtLen, 1); + } else { + Buf[0] = '!'; + Buf[1] = ExtCode; + Buf[2] = ExtLen; + WRITE(GifFile, Buf, 3); + } + + WRITE(GifFile, Extension, ExtLen); + + return GIF_OK; +} + +/****************************************************************************** + * Put a middle extension block (see GIF manual) into gif file. + *****************************************************************************/ +int +EGifPutExtensionNext(GifFileType * GifFile, + int ExtCode, + int ExtLen, + const VoidPtr Extension) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + Buf = ExtLen; + WRITE(GifFile, &Buf, 1); + WRITE(GifFile, Extension, ExtLen); + + return GIF_OK; +} + +/****************************************************************************** + * Put a last extension block (see GIF manual) into gif file. + *****************************************************************************/ +int +EGifPutExtensionLast(GifFileType * GifFile, + int ExtCode, + int ExtLen, + const VoidPtr Extension) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + /* If we are given an extension sub-block output it now. */ + if (ExtLen > 0) { + Buf = ExtLen; + WRITE(GifFile, &Buf, 1); + WRITE(GifFile, Extension, ExtLen); + } + + /* Write the block terminator */ + Buf = 0; + WRITE(GifFile, &Buf, 1); + + return GIF_OK; +} + +/****************************************************************************** + * Put an extension block (see GIF manual) into gif file. + * Warning: This function is only useful for Extension blocks that have at + * most one subblock. Extensions with more than one subblock need to use the + * EGifPutExtension{First,Next,Last} functions instead. + *****************************************************************************/ +int +EGifPutExtension(GifFileType * GifFile, + int ExtCode, + int ExtLen, + const VoidPtr Extension) { + + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (ExtCode == 0) + WRITE(GifFile, (GifByteType *)&ExtLen, 1); + else { + Buf[0] = '!'; /* Extension Introducer 0x21 */ + Buf[1] = ExtCode; /* Extension Label */ + Buf[2] = ExtLen; /* Extension length */ + WRITE(GifFile, Buf, ExtCode==0xff ? 2 : 3); + } + WRITE(GifFile, Extension, ExtLen); + Buf[0] = 0; + WRITE(GifFile, Buf, 1); + + return GIF_OK; +} + +/****************************************************************************** + * Put the image code in compressed form. This routine can be called if the + * information needed to be piped out as is. Obviously this is much faster + * than decoding and encoding again. This routine should be followed by calls + * to EGifPutCodeNext, until NULL block is given. + * The block should NOT be freed by the user (not dynamically allocated). + *****************************************************************************/ +int +EGifPutCode(GifFileType * GifFile, + int CodeSize, + const GifByteType * CodeBlock) { + + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + /* No need to dump code size as Compression set up does any for us: */ + /* + * Buf = CodeSize; + * if (WRITE(GifFile, &Buf, 1) != 1) { + * _GifError = E_GIF_ERR_WRITE_FAILED; + * return GIF_ERROR; + * } + */ + + return EGifPutCodeNext(GifFile, CodeBlock); +} + +/****************************************************************************** + * Continue to put the image code in compressed form. This routine should be + * called with blocks of code as read via DGifGetCode/DGifGetCodeNext. If + * given buffer pointer is NULL, empty block is written to mark end of code. + *****************************************************************************/ +int +EGifPutCodeNext(GifFileType * GifFile, + const GifByteType * CodeBlock) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (CodeBlock != NULL) { + if (WRITE(GifFile, CodeBlock, CodeBlock[0] + 1) + != (unsigned)(CodeBlock[0] + 1)) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } else { + Buf = 0; + if (WRITE(GifFile, &Buf, 1) != 1) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + Private->PixelCount = 0; /* And local info. indicate image read. */ + } + + return GIF_OK; +} + +/****************************************************************************** + * This routine should be called last, to close GIF file. + *****************************************************************************/ +int +EGifCloseFile(GifFileType * GifFile) { + + GifByteType Buf; + GifFilePrivateType *Private; + FILE *File; + + if (GifFile == NULL) + return GIF_ERROR; + + Private = (GifFilePrivateType *) GifFile->Private; + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + _GifError = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + File = Private->File; + + Buf = ';'; + WRITE(GifFile, &Buf, 1); + + if (GifFile->Image.ColorMap) { + FreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + if (GifFile->SColorMap) { + FreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + } + if (Private) { + if (Private->HashTable) { + free((char *) Private->HashTable); + } + free((char *) Private); + } + free(GifFile); + + if (File && fclose(File) != 0) { + _GifError = E_GIF_ERR_CLOSE_FAILED; + return GIF_ERROR; + } + return GIF_OK; +} + +/****************************************************************************** + * Put 2 bytes (word) into the given file: + *****************************************************************************/ +static int +EGifPutWord(int Word, + GifFileType * GifFile) { + + unsigned char c[2]; + + c[0] = Word & 0xff; + c[1] = (Word >> 8) & 0xff; +#ifndef DEBUG_NO_PREFIX + if (WRITE(GifFile, c, 2) == 2) + return GIF_OK; + else + return GIF_ERROR; +#else + return GIF_OK; +#endif /* DEBUG_NO_PREFIX */ +} + +/****************************************************************************** + * Setup the LZ compression for this image: + *****************************************************************************/ +static int +EGifSetupCompress(GifFileType * GifFile) { + + int BitsPerPixel; + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + /* Test and see what color map to use, and from it # bits per pixel: */ + if (GifFile->Image.ColorMap) + BitsPerPixel = GifFile->Image.ColorMap->BitsPerPixel; + else if (GifFile->SColorMap) + BitsPerPixel = GifFile->SColorMap->BitsPerPixel; + else { + _GifError = E_GIF_ERR_NO_COLOR_MAP; + return GIF_ERROR; + } + + Buf = BitsPerPixel = (BitsPerPixel < 2 ? 2 : BitsPerPixel); + WRITE(GifFile, &Buf, 1); /* Write the Code size to file. */ + + Private->Buf[0] = 0; /* Nothing was output yet. */ + Private->BitsPerPixel = BitsPerPixel; + Private->ClearCode = (1 << BitsPerPixel); + Private->EOFCode = Private->ClearCode + 1; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ + Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ + Private->CrntCode = FIRST_CODE; /* Signal that this is first one! */ + Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ + Private->CrntShiftDWord = 0; + + /* Clear hash table and send Clear to make sure the decoder do the same. */ + _ClearHashTable(Private->HashTable); + + if (EGifCompressOutput(GifFile, Private->ClearCode) == GIF_ERROR) { + _GifError = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + return GIF_OK; +} + +/****************************************************************************** + * The LZ compression routine: + * This version compresses the given buffer Line of length LineLen. + * This routine can be called a few times (one per scan line, for example), in + * order to complete the whole image. +******************************************************************************/ +static int +EGifCompressLine(GifFileType * GifFile, + GifPixelType * Line, + int LineLen) { + + int i = 0, CrntCode, NewCode; + unsigned long NewKey; + GifPixelType Pixel; + GifHashTableType *HashTable; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + HashTable = Private->HashTable; + + if (Private->CrntCode == FIRST_CODE) /* Its first time! */ + CrntCode = Line[i++]; + else + CrntCode = Private->CrntCode; /* Get last code in compression. */ + + while (i < LineLen) { /* Decode LineLen items. */ + Pixel = Line[i++]; /* Get next pixel from stream. */ + /* Form a new unique key to search hash table for the code combines + * CrntCode as Prefix string with Pixel as postfix char. + */ + NewKey = (((UINT32) CrntCode) << 8) + Pixel; + if ((NewCode = _ExistsHashTable(HashTable, NewKey)) >= 0) { + /* This Key is already there, or the string is old one, so + * simple take new code as our CrntCode: + */ + CrntCode = NewCode; + } else { + /* Put it in hash table, output the prefix code, and make our + * CrntCode equal to Pixel. + */ + if (EGifCompressOutput(GifFile, CrntCode) == GIF_ERROR) { + _GifError = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + CrntCode = Pixel; + + /* If however the HashTable if full, we send a clear first and + * Clear the hash table. + */ + if (Private->RunningCode >= LZ_MAX_CODE) { + /* Time to do some clearance: */ + if (EGifCompressOutput(GifFile, Private->ClearCode) + == GIF_ERROR) { + _GifError = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + _ClearHashTable(HashTable); + } else { + /* Put this unique key with its relative Code in hash table: */ + _InsertHashTable(HashTable, NewKey, Private->RunningCode++); + } + } + + } + + /* Preserve the current state of the compression algorithm: */ + Private->CrntCode = CrntCode; + + if (Private->PixelCount == 0) { + /* We are done - output last Code and flush output buffers: */ + if (EGifCompressOutput(GifFile, CrntCode) == GIF_ERROR) { + _GifError = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + if (EGifCompressOutput(GifFile, Private->EOFCode) == GIF_ERROR) { + _GifError = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + if (EGifCompressOutput(GifFile, FLUSH_OUTPUT) == GIF_ERROR) { + _GifError = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + } + + return GIF_OK; +} + +/****************************************************************************** + * The LZ compression output routine: + * This routine is responsible for the compression of the bit stream into + * 8 bits (bytes) packets. + * Returns GIF_OK if written succesfully. + *****************************************************************************/ +static int +EGifCompressOutput(GifFileType * GifFile, + int Code) { + + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + int retval = GIF_OK; + + if (Code == FLUSH_OUTPUT) { + while (Private->CrntShiftState > 0) { + /* Get Rid of what is left in DWord, and flush it. */ + if (EGifBufferedOutput(GifFile, Private->Buf, + Private->CrntShiftDWord & 0xff) == GIF_ERROR) + retval = GIF_ERROR; + Private->CrntShiftDWord >>= 8; + Private->CrntShiftState -= 8; + } + Private->CrntShiftState = 0; /* For next time. */ + if (EGifBufferedOutput(GifFile, Private->Buf, + FLUSH_OUTPUT) == GIF_ERROR) + retval = GIF_ERROR; + } else { + Private->CrntShiftDWord |= ((long)Code) << Private->CrntShiftState; + Private->CrntShiftState += Private->RunningBits; + while (Private->CrntShiftState >= 8) { + /* Dump out full bytes: */ + if (EGifBufferedOutput(GifFile, Private->Buf, + Private->CrntShiftDWord & 0xff) == GIF_ERROR) + retval = GIF_ERROR; + Private->CrntShiftDWord >>= 8; + Private->CrntShiftState -= 8; + } + } + + /* If code cannt fit into RunningBits bits, must raise its size. Note */ + /* however that codes above 4095 are used for special signaling. */ + if (Private->RunningCode >= Private->MaxCode1 && Code <= 4095) { + Private->MaxCode1 = 1 << ++Private->RunningBits; + } + + return retval; +} + +/****************************************************************************** + * This routines buffers the given characters until 255 characters are ready + * to be output. If Code is equal to -1 the buffer is flushed (EOF). + * The buffer is Dumped with first byte as its size, as GIF format requires. + * Returns GIF_OK if written succesfully. + *****************************************************************************/ +static int +EGifBufferedOutput(GifFileType * GifFile, + GifByteType * Buf, + int c) { + + if (c == FLUSH_OUTPUT) { + /* Flush everything out. */ + if (Buf[0] != 0 + && WRITE(GifFile, Buf, Buf[0] + 1) != (unsigned)(Buf[0] + 1)) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + /* Mark end of compressed data, by an empty block (see GIF doc): */ + Buf[0] = 0; + if (WRITE(GifFile, Buf, 1) != 1) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } else { + if (Buf[0] == 255) { + /* Dump out this buffer - it is full: */ + if (WRITE(GifFile, Buf, Buf[0] + 1) != (unsigned)(Buf[0] + 1)) { + _GifError = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + Buf[0] = 0; + } + Buf[++Buf[0]] = c; + } + + return GIF_OK; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_hash.c b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_hash.c new file mode 100644 index 0000000..cc99f7a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_hash.c @@ -0,0 +1,152 @@ +/***************************************************************************** +* "Gif-Lib" - Yet another gif library. * +* * +* Written by: Gershon Elber IBM PC Ver 0.1, Jun. 1989 * +****************************************************************************** +* Module to support the following operations: * +* * +* 1. InitHashTable - initialize hash table. * +* 2. ClearHashTable - clear the hash table to an empty state. * +* 2. InsertHashTable - insert one item into data structure. * +* 3. ExistsHashTable - test if item exists in data structure. * +* * +* This module is used to hash the GIF codes during encoding. * +****************************************************************************** +* History: * +* 14 Jun 89 - Version 1.0 by Gershon Elber. * +*****************************************************************************/ + +#include "config.h" + +/* Find a thirty-two bit int type */ +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef __MSDOS__ +#include +#include +#include +#else +#include +#include +#endif /* __MSDOS__ */ + +#ifdef HAVE_FCNTL_H +#include +#endif /* HAVE_FCNTL_H */ +#include +#include +#include "gif_lib.h" +#include "gif_hash.h" +#include "gif_lib_private.h" + +/* #define DEBUG_HIT_RATE Debug number of misses per hash Insert/Exists. */ + +#ifdef DEBUG_HIT_RATE +static long NumberOfTests = 0, + NumberOfMisses = 0; +#endif /* DEBUG_HIT_RATE */ + +static int KeyItem(UINT32 Item); + +/****************************************************************************** +* Initialize HashTable - allocate the memory needed and clear it. * +******************************************************************************/ +GifHashTableType *_InitHashTable(void) +{ + GifHashTableType *HashTable; + + if ((HashTable = (GifHashTableType *) malloc(sizeof(GifHashTableType))) + == NULL) + return NULL; + + _ClearHashTable(HashTable); + + return HashTable; +} + +/****************************************************************************** +* Routine to clear the HashTable to an empty state. * +* This part is a little machine depended. Use the commented part otherwise. * +******************************************************************************/ +void _ClearHashTable(GifHashTableType *HashTable) +{ + memset(HashTable -> HTable, 0xFF, HT_SIZE * sizeof(UINT32)); +} + +/****************************************************************************** +* Routine to insert a new Item into the HashTable. The data is assumed to be * +* new one. * +******************************************************************************/ +void _InsertHashTable(GifHashTableType *HashTable, UINT32 Key, int Code) +{ + int HKey = KeyItem(Key); + UINT32 *HTable = HashTable -> HTable; + +#ifdef DEBUG_HIT_RATE + NumberOfTests++; + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + + while (HT_GET_KEY(HTable[HKey]) != 0xFFFFFL) { +#ifdef DEBUG_HIT_RATE + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + HKey = (HKey + 1) & HT_KEY_MASK; + } + HTable[HKey] = HT_PUT_KEY(Key) | HT_PUT_CODE(Code); +} + +/****************************************************************************** +* Routine to test if given Key exists in HashTable and if so returns its code * +* Returns the Code if key was found, -1 if not. * +******************************************************************************/ +int _ExistsHashTable(GifHashTableType *HashTable, UINT32 Key) +{ + int HKey = KeyItem(Key); + UINT32 *HTable = HashTable -> HTable, HTKey; + +#ifdef DEBUG_HIT_RATE + NumberOfTests++; + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + + while ((HTKey = HT_GET_KEY(HTable[HKey])) != 0xFFFFFL) { +#ifdef DEBUG_HIT_RATE + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + if (Key == HTKey) return HT_GET_CODE(HTable[HKey]); + HKey = (HKey + 1) & HT_KEY_MASK; + } + + return -1; +} + +/****************************************************************************** +* Routine to generate an HKey for the hashtable out of the given unique key. * +* The given Key is assumed to be 20 bits as follows: lower 8 bits are the * +* new postfix character, while the upper 12 bits are the prefix code. * +* Because the average hit ratio is only 2 (2 hash references per entry), * +* evaluating more complex keys (such as twin prime keys) does not worth it! * +******************************************************************************/ +static int KeyItem(UINT32 Item) +{ + return ((Item >> 12) ^ Item) & HT_KEY_MASK; +} + +#ifdef DEBUG_HIT_RATE +/****************************************************************************** +* Debugging routine to print the hit ratio - number of times the hash table * +* was tested per operation. This routine was used to test the KeyItem routine * +******************************************************************************/ +void HashTablePrintHitRatio(void) +{ + printf("Hash Table Hit Ratio is %ld/%ld = %ld%%.\n", + NumberOfMisses, NumberOfTests, + NumberOfMisses * 100 / NumberOfTests); +} +#endif /* DEBUG_HIT_RATE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_hash.h b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_hash.h new file mode 100644 index 0000000..e7fa1e2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_hash.h @@ -0,0 +1,50 @@ +/****************************************************************************** +* Declarations, global to other of the GIF-HASH.C module. * +* * +* Written by Gershon Elber, Jun 1989 * +******************************************************************************* +* History: * +* 14 Jun 89 - Version 1.0 by Gershon Elber. * +******************************************************************************/ + +#ifndef _GIF_HASH_H_ +#define _GIF_HASH_H_ + +#include "config.h" + +/* Find a thirty-two bit int type */ +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_BASETSD_H +#include +#endif + +#define HT_SIZE 8192 /* 12bits = 4096 or twice as big! */ +#define HT_KEY_MASK 0x1FFF /* 13bits keys */ +#define HT_KEY_NUM_BITS 13 /* 13bits keys */ +#define HT_MAX_KEY 8191 /* 13bits - 1, maximal code possible */ +#define HT_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ + +/* The 32 bits of the long are divided into two parts for the key & code: */ +/* 1. The code is 12 bits as our compression algorithm is limited to 12bits */ +/* 2. The key is 12 bits Prefix code + 8 bit new char or 20 bits. */ +/* The key is the upper 20 bits. The code is the lower 12. */ +#define HT_GET_KEY(l) (l >> 12) +#define HT_GET_CODE(l) (l & 0x0FFF) +#define HT_PUT_KEY(l) (l << 12) +#define HT_PUT_CODE(l) (l & 0x0FFF) + +typedef struct GifHashTableType { + UINT32 HTable[HT_SIZE]; +} GifHashTableType; + +GifHashTableType *_InitHashTable(void); +void _ClearHashTable(GifHashTableType *HashTable); +void _InsertHashTable(GifHashTableType *HashTable, UINT32 Key, int Code); +int _ExistsHashTable(GifHashTableType *HashTable, UINT32 Key); + +#endif /* _GIF_HASH_H_ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_lib.h b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_lib.h new file mode 100644 index 0000000..ac9d90e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_lib.h @@ -0,0 +1,277 @@ +/****************************************************************************** + * In order to make life a little bit easier when using the GIF file format, + * this library was written, and which does all the dirty work... + * + * Written by Gershon Elber, Jun. 1989 + * Hacks by Eric S. Raymond, Sep. 1992 + ****************************************************************************** + * History: + * 14 Jun 89 - Version 1.0 by Gershon Elber. + * 3 Sep 90 - Version 1.1 by Gershon Elber (Support for Gif89, Unique names) + * 15 Sep 90 - Version 2.0 by Eric S. Raymond (Changes to suoport GIF slurp) + * 26 Jun 96 - Version 3.0 by Eric S. Raymond (Full GIF89 support) + * 17 Dec 98 - Version 4.0 by Toshio Kuratomi (Fix extension writing code) + *****************************************************************************/ + +#ifndef _GIF_LIB_H_ +#define _GIF_LIB_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define GIF_LIB_VERSION " Version 4.1, " + +#define GIF_ERROR 0 +#define GIF_OK 1 + +#ifndef TRUE +#define TRUE 1 +#endif /* TRUE */ +#ifndef FALSE +#define FALSE 0 +#endif /* FALSE */ + +#ifndef NULL +#define NULL 0 +#endif /* NULL */ + +#define GIF_STAMP "GIFVER" /* First chars in file - GIF stamp. */ +#define GIF_STAMP_LEN sizeof(GIF_STAMP) - 1 +#define GIF_VERSION_POS 3 /* Version first character in stamp. */ +#define GIF87_STAMP "GIF87a" /* First chars in file - GIF stamp. */ +#define GIF89_STAMP "GIF89a" /* First chars in file - GIF stamp. */ + +#define GIF_FILE_BUFFER_SIZE 16384 /* Files uses bigger buffers than usual. */ + +typedef int GifBooleanType; +typedef unsigned char GifPixelType; +typedef unsigned char *GifRowType; +typedef unsigned char GifByteType; +#ifdef _GBA_OPTMEM + typedef unsigned short GifPrefixType; + typedef short GifWord; +#else + typedef unsigned int GifPrefixType; + typedef int GifWord; +#endif + +#define GIF_MESSAGE(Msg) +#define GIF_EXIT(Msg) + +#ifdef SYSV +#define VoidPtr char * +#else +#define VoidPtr void * +#endif /* SYSV */ + +typedef struct GifColorType { + GifByteType Red, Green, Blue; +} GifColorType; + +typedef struct ColorMapObject { + int ColorCount; + int BitsPerPixel; + GifColorType *Colors; /* on malloc(3) heap */ +} ColorMapObject; + +typedef struct GifImageDesc { + GifWord Left, Top, Width, Height, /* Current image dimensions. */ + Interlace; /* Sequential/Interlaced lines. */ + ColorMapObject *ColorMap; /* The local color map */ +} GifImageDesc; + +typedef struct GifFileType { + GifWord SWidth, SHeight, /* Screen dimensions. */ + SColorResolution, /* How many colors can we generate? */ + SBackGroundColor; /* I hope you understand this one... */ + ColorMapObject *SColorMap; /* NULL if not exists. */ + int ImageCount; /* Number of current image */ + GifImageDesc Image; /* Block describing current image */ + VoidPtr UserData; /* hook to attach user data (TVT) */ + VoidPtr Private; /* Don't mess with this! */ +} GifFileType; + +typedef enum { + UNDEFINED_RECORD_TYPE, + SCREEN_DESC_RECORD_TYPE, + IMAGE_DESC_RECORD_TYPE, /* Begin with ',' */ + EXTENSION_RECORD_TYPE, /* Begin with '!' */ + TERMINATE_RECORD_TYPE /* Begin with ';' */ +} GifRecordType; + +/* DumpScreen2Gif routine constants identify type of window/screen to dump. + * Note all values below 1000 are reserved for the IBMPC different display + * devices (it has many!) and are compatible with the numbering TC2.0 + * (Turbo C 2.0 compiler for IBM PC) gives to these devices. + */ +typedef enum { + GIF_DUMP_SGI_WINDOW = 1000, + GIF_DUMP_X_WINDOW = 1001 +} GifScreenDumpType; + +/* func type to read gif data from arbitrary sources (TVT) */ +typedef int (*InputFunc) (GifFileType *, GifByteType *, int); + +/* func type to write gif data ro arbitrary targets. + * Returns count of bytes written. (MRB) + */ +typedef int (*OutputFunc) (GifFileType *, const GifByteType *, int); + +/****************************************************************************** + * GIF89 extension function codes +******************************************************************************/ + +#define COMMENT_EXT_FUNC_CODE 0xfe /* comment */ +#define GRAPHICS_EXT_FUNC_CODE 0xf9 /* graphics control */ +#define PLAINTEXT_EXT_FUNC_CODE 0x01 /* plaintext */ +#define APPLICATION_EXT_FUNC_CODE 0xff /* application block */ + +/****************************************************************************** + * O.K., here are the routines one can access in order to encode GIF file: + * (GIF_LIB file EGIF_LIB.C). +******************************************************************************/ + +GifFileType *EGifOpenFileName(const char *GifFileName, + int GifTestExistance); +GifFileType *EGifOpenFileHandle(int GifFileHandle); +GifFileType *EGifOpen(void *userPtr, OutputFunc writeFunc); + +int EGifSpew(GifFileType * GifFile); +void EGifSetGifVersion(const char *Version); +int EGifPutScreenDesc(GifFileType * GifFile, + int GifWidth, int GifHeight, int GifColorRes, + int GifBackGround, + const ColorMapObject * GifColorMap); +int EGifPutImageDesc(GifFileType * GifFile, int GifLeft, int GifTop, + int Width, int GifHeight, int GifInterlace, + const ColorMapObject * GifColorMap); +int EGifPutLine(GifFileType * GifFile, GifPixelType * GifLine, + int GifLineLen); +int EGifPutPixel(GifFileType * GifFile, GifPixelType GifPixel); +int EGifPutComment(GifFileType * GifFile, const char *GifComment); +int EGifPutExtensionFirst(GifFileType * GifFile, int GifExtCode, + int GifExtLen, const VoidPtr GifExtension); +int EGifPutExtensionNext(GifFileType * GifFile, int GifExtCode, + int GifExtLen, const VoidPtr GifExtension); +int EGifPutExtensionLast(GifFileType * GifFile, int GifExtCode, + int GifExtLen, const VoidPtr GifExtension); +int EGifPutExtension(GifFileType * GifFile, int GifExtCode, int GifExtLen, + const VoidPtr GifExtension); +int EGifPutCode(GifFileType * GifFile, int GifCodeSize, + const GifByteType * GifCodeBlock); +int EGifPutCodeNext(GifFileType * GifFile, + const GifByteType * GifCodeBlock); +int EGifCloseFile(GifFileType * GifFile); + +#define E_GIF_ERR_OPEN_FAILED 1 /* And EGif possible errors. */ +#define E_GIF_ERR_WRITE_FAILED 2 +#define E_GIF_ERR_HAS_SCRN_DSCR 3 +#define E_GIF_ERR_HAS_IMAG_DSCR 4 +#define E_GIF_ERR_NO_COLOR_MAP 5 +#define E_GIF_ERR_DATA_TOO_BIG 6 +#define E_GIF_ERR_NOT_ENOUGH_MEM 7 +#define E_GIF_ERR_DISK_IS_FULL 8 +#define E_GIF_ERR_CLOSE_FAILED 9 +#define E_GIF_ERR_NOT_WRITEABLE 10 + +/****************************************************************************** + * O.K., here are the routines one can access in order to decode GIF file: + * (GIF_LIB file DGIF_LIB.C). + *****************************************************************************/ +#ifndef _GBA_NO_FILEIO +GifFileType *DGifOpenFileName(const char *GifFileName); +GifFileType *DGifOpenFileHandle(int GifFileHandle); +int DGifSlurp(GifFileType * GifFile); +#endif /* _GBA_NO_FILEIO */ +GifFileType *DGifOpen(void *userPtr, InputFunc readFunc); /* new one + * (TVT) */ +int DGifGetScreenDesc(GifFileType * GifFile); +int DGifGetRecordType(GifFileType * GifFile, GifRecordType * GifType); +int DGifGetImageDesc(GifFileType * GifFile); +int DGifGetLine(GifFileType * GifFile, GifPixelType * GifLine, int GifLineLen); +int DGifGetPixel(GifFileType * GifFile, GifPixelType GifPixel); +int DGifGetComment(GifFileType * GifFile, char *GifComment); +int DGifGetExtension(GifFileType * GifFile, int *GifExtCode, + GifByteType ** GifExtension); +int DGifGetExtensionNext(GifFileType * GifFile, GifByteType ** GifExtension); +int DGifGetCode(GifFileType * GifFile, int *GifCodeSize, + GifByteType ** GifCodeBlock); +int DGifGetCodeNext(GifFileType * GifFile, GifByteType ** GifCodeBlock); +int DGifGetLZCodes(GifFileType * GifFile, int *GifCode); +int DGifCloseFile(GifFileType * GifFile); + +#define D_GIF_ERR_OPEN_FAILED 101 /* And DGif possible errors. */ +#define D_GIF_ERR_READ_FAILED 102 +#define D_GIF_ERR_NOT_GIF_FILE 103 +#define D_GIF_ERR_NO_SCRN_DSCR 104 +#define D_GIF_ERR_NO_IMAG_DSCR 105 +#define D_GIF_ERR_NO_COLOR_MAP 106 +#define D_GIF_ERR_WRONG_RECORD 107 +#define D_GIF_ERR_DATA_TOO_BIG 108 +#define D_GIF_ERR_NOT_ENOUGH_MEM 109 +#define D_GIF_ERR_CLOSE_FAILED 110 +#define D_GIF_ERR_NOT_READABLE 111 +#define D_GIF_ERR_IMAGE_DEFECT 112 +#define D_GIF_ERR_EOF_TOO_SOON 113 + +/****************************************************************************** + * O.K., here are the routines from GIF_LIB file QUANTIZE.C. +******************************************************************************/ +int QuantizeBuffer(unsigned int Width, unsigned int Height, + int *ColorMapSize, GifByteType * RedInput, + GifByteType * GreenInput, GifByteType * BlueInput, + GifByteType * OutputBuffer, + GifColorType * OutputColorMap); + +/****************************************************************************** + * O.K., here are the routines from GIF_LIB file QPRINTF.C. +******************************************************************************/ +extern int GifQuietPrint; + +#ifdef HAVE_STDARG_H + extern void GifQprintf(char *Format, ...); +#elif defined (HAVE_VARARGS_H) + extern void GifQprintf(); +#endif /* HAVE_STDARG_H */ + +/****************************************************************************** + * O.K., here are the routines from GIF_LIB file GIF_ERR.C. +******************************************************************************/ +#ifndef _GBA_NO_FILEIO +extern void PrintGifError(void); +#endif /* _GBA_NO_FILEIO */ +extern int GifLastError(void); + +/****************************************************************************** + * O.K., here are the routines from GIF_LIB file DEV2GIF.C. +******************************************************************************/ +extern int DumpScreen2Gif(const char *FileName, + int ReqGraphDriver, + long ReqGraphMode1, + long ReqGraphMode2, + long ReqGraphMode3); + +/***************************************************************************** + * + * Everything below this point is new after version 1.2, supporting `slurp + * mode' for doing I/O in two big belts with all the image-bashing in core. + * + *****************************************************************************/ + +/****************************************************************************** + * Color Map handling from ALLOCGIF.C + *****************************************************************************/ + +extern ColorMapObject *MakeMapObject(int ColorCount, + const GifColorType * ColorMap); +extern void FreeMapObject(ColorMapObject * Object); +extern ColorMapObject *UnionColorMap(const ColorMapObject * ColorIn1, + const ColorMapObject * ColorIn2, + GifPixelType ColorTransIn2[]); +extern int BitSize(int n); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _GIF_LIB_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_lib_private.h b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_lib_private.h new file mode 100644 index 0000000..b31c9c5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gif_lib_private.h @@ -0,0 +1,59 @@ +#ifndef _GIF_LIB_PRIVATE_H +#define _GIF_LIB_PRIVATE_H + +#include "gif_lib.h" +#include "gif_hash.h" + +#define PROGRAM_NAME "GIFLIB" + +#ifdef SYSV +#define VersionStr "Gif library module,\t\tEric S. Raymond\n\ + (C) Copyright 1997 Eric S. Raymond\n" +#else +#define VersionStr PROGRAM_NAME " IBMPC " GIF_LIB_VERSION \ + " Eric S. Raymond, " __DATE__ ", " \ + __TIME__ "\n" "(C) Copyright 1997 Eric S. Raymond\n" +#endif /* SYSV */ + +#define LZ_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ +#define LZ_BITS 12 + +#define FLUSH_OUTPUT 4096 /* Impossible code, to signal flush. */ +#define FIRST_CODE 4097 /* Impossible code, to signal first. */ +#define NO_SUCH_CODE 4098 /* Impossible code, to signal empty. */ + +#define FILE_STATE_WRITE 0x01 +#define FILE_STATE_SCREEN 0x02 +#define FILE_STATE_IMAGE 0x04 +#define FILE_STATE_READ 0x08 + +#define IS_READABLE(Private) (Private->FileState & FILE_STATE_READ) +#define IS_WRITEABLE(Private) (Private->FileState & FILE_STATE_WRITE) + +typedef struct GifFilePrivateType { + GifWord FileState, FileHandle, /* Where all this data goes to! */ + BitsPerPixel, /* Bits per pixel (Codes uses at least this + 1). */ + ClearCode, /* The CLEAR LZ code. */ + EOFCode, /* The EOF LZ code. */ + RunningCode, /* The next code algorithm can generate. */ + RunningBits, /* The number of bits required to represent RunningCode. */ + MaxCode1, /* 1 bigger than max. possible code, in RunningBits bits. */ + LastCode, /* The code before the current code. */ + CrntCode, /* Current algorithm code. */ + StackPtr, /* For character stack (see below). */ + CrntShiftState; /* Number of bits in CrntShiftDWord. */ + unsigned long CrntShiftDWord; /* For bytes decomposition into codes. */ + unsigned long PixelCount; /* Number of pixels in image. */ + FILE *File; /* File as stream. */ + InputFunc Read; /* function to read gif input (TVT) */ + OutputFunc Write; /* function to write gif output (MRB) */ + GifByteType Buf[256]; /* Compressed input is buffered here. */ + GifByteType Stack[LZ_MAX_CODE]; /* Decoded pixels are stacked here. */ + GifByteType Suffix[LZ_MAX_CODE + 1]; /* So we can trace the codes. */ + GifPrefixType Prefix[LZ_MAX_CODE + 1]; + GifHashTableType *HashTable; +} GifFilePrivateType; + +extern int _GifError; + +#endif /* _GIF_LIB_PRIVATE_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gifalloc.c b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gifalloc.c new file mode 100644 index 0000000..4d6160a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/giflib/gifalloc.c @@ -0,0 +1,212 @@ +/***************************************************************************** + * "Gif-Lib" - Yet another gif library. + * + * Written by: Gershon Elber Ver 0.1, Jun. 1989 + * Extensively hacked by: Eric S. Raymond Ver 1.?, Sep 1992 + ***************************************************************************** + * GIF construction tools + ***************************************************************************** + * History: + * 15 Sep 92 - Version 1.0 by Eric Raymond. + ****************************************************************************/ + +#include "config.h" + +#include +#include +#include +#include "gif_lib.h" + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) + +/****************************************************************************** + * Miscellaneous utility functions + *****************************************************************************/ + +/* return smallest bitfield size n will fit in */ +int +BitSize(int n) { + + register int i; + + for (i = 1; i <= 8; i++) + if ((1 << i) >= n) + break; + return (i); +} + +/****************************************************************************** + * Color map object functions + *****************************************************************************/ + +/* + * Allocate a color map of given size; initialize with contents of + * ColorMap if that pointer is non-NULL. + */ +ColorMapObject * +MakeMapObject(int ColorCount, + const GifColorType * ColorMap) { + + ColorMapObject *Object; + + /*** FIXME: Our ColorCount has to be a power of two. Is it necessary to + * make the user know that or should we automatically round up instead? */ + if (ColorCount != (1 << BitSize(ColorCount))) { + return ((ColorMapObject *) NULL); + } + + Object = (ColorMapObject *)malloc(sizeof(ColorMapObject)); + if (Object == (ColorMapObject *) NULL) { + return ((ColorMapObject *) NULL); + } + + Object->Colors = (GifColorType *)calloc(ColorCount, sizeof(GifColorType)); + if (Object->Colors == (GifColorType *) NULL) { + return ((ColorMapObject *) NULL); + } + + Object->ColorCount = ColorCount; + Object->BitsPerPixel = BitSize(ColorCount); + + if (ColorMap) { + memcpy((char *)Object->Colors, + (char *)ColorMap, ColorCount * sizeof(GifColorType)); + } + + return (Object); +} + +/* + * Free a color map object + */ +void +FreeMapObject(ColorMapObject * Object) { + + if (Object != NULL) { + free(Object->Colors); + free(Object); + /*** FIXME: + * When we are willing to break API we need to make this function + * FreeMapObject(ColorMapObject **Object) + * and do this assignment to NULL here: + * *Object = NULL; + */ + } +} + +#ifdef DEBUG +void +DumpColorMap(ColorMapObject * Object, + FILE * fp) { + + if (Object) { + int i, j, Len = Object->ColorCount; + + for (i = 0; i < Len; i += 4) { + for (j = 0; j < 4 && j < Len; j++) { + fprintf(fp, "%3d: %02x %02x %02x ", i + j, + Object->Colors[i + j].Red, + Object->Colors[i + j].Green, + Object->Colors[i + j].Blue); + } + fprintf(fp, "\n"); + } + } +} +#endif /* DEBUG */ + +/* + * Compute the union of two given color maps and return it. If result can't + * fit into 256 colors, NULL is returned, the allocated union otherwise. + * ColorIn1 is copied as is to ColorUnion, while colors from ColorIn2 are + * copied iff they didn't exist before. ColorTransIn2 maps the old + * ColorIn2 into ColorUnion color map table. + */ +ColorMapObject * +UnionColorMap(const ColorMapObject * ColorIn1, + const ColorMapObject * ColorIn2, + GifPixelType ColorTransIn2[]) { + + int i, j, CrntSlot, RoundUpTo, NewBitSize; + ColorMapObject *ColorUnion; + + /* + * Allocate table which will hold the result for sure. + */ + ColorUnion = MakeMapObject(MAX(ColorIn1->ColorCount, + ColorIn2->ColorCount) * 2, NULL); + + if (ColorUnion == NULL) + return (NULL); + + /* Copy ColorIn1 to ColorUnionSize; */ + /*** FIXME: What if there are duplicate entries into the colormap to begin + * with? */ + for (i = 0; i < ColorIn1->ColorCount; i++) + ColorUnion->Colors[i] = ColorIn1->Colors[i]; + CrntSlot = ColorIn1->ColorCount; + + /* + * Potentially obnoxious hack: + * + * Back CrntSlot down past all contiguous {0, 0, 0} slots at the end + * of table 1. This is very useful if your display is limited to + * 16 colors. + */ + while (ColorIn1->Colors[CrntSlot - 1].Red == 0 + && ColorIn1->Colors[CrntSlot - 1].Green == 0 + && ColorIn1->Colors[CrntSlot - 1].Blue == 0) + CrntSlot--; + + /* Copy ColorIn2 to ColorUnionSize (use old colors if they exist): */ + for (i = 0; i < ColorIn2->ColorCount && CrntSlot <= 256; i++) { + /* Let's see if this color already exists: */ + /*** FIXME: Will it ever occur that ColorIn2 will contain duplicate + * entries? So we should search from 0 to CrntSlot rather than + * ColorIn1->ColorCount? + */ + for (j = 0; j < ColorIn1->ColorCount; j++) + if (memcmp (&ColorIn1->Colors[j], &ColorIn2->Colors[i], + sizeof(GifColorType)) == 0) + break; + + if (j < ColorIn1->ColorCount) + ColorTransIn2[i] = j; /* color exists in Color1 */ + else { + /* Color is new - copy it to a new slot: */ + ColorUnion->Colors[CrntSlot] = ColorIn2->Colors[i]; + ColorTransIn2[i] = CrntSlot++; + } + } + + if (CrntSlot > 256) { + FreeMapObject(ColorUnion); + return ((ColorMapObject *) NULL); + } + + NewBitSize = BitSize(CrntSlot); + RoundUpTo = (1 << NewBitSize); + + if (RoundUpTo != ColorUnion->ColorCount) { + register GifColorType *Map = ColorUnion->Colors; + + /* + * Zero out slots up to next power of 2. + * We know these slots exist because of the way ColorUnion's + * start dimension was computed. + */ + for (j = CrntSlot; j < RoundUpTo; j++) + Map[j].Red = Map[j].Green = Map[j].Blue = 0; + + /* perhaps we can shrink the map? */ + if (RoundUpTo < ColorUnion->ColorCount) + ColorUnion->Colors = (GifColorType *)realloc(Map, + sizeof(GifColorType) * RoundUpTo); + } + + ColorUnion->ColorCount = RoundUpTo; + ColorUnion->BitsPerPixel = NewBitSize; + + return (ColorUnion); +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/has_strings.h b/plugin-reaper-realearn/main/lib/WDL/WDL/has_strings.h new file mode 100644 index 0000000..b50cd67 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/has_strings.h @@ -0,0 +1,557 @@ +#ifndef _WDL_HASSTRINGS_H_ +#define _WDL_HASSTRINGS_H_ + +#ifndef WDL_HASSTRINGS_EXPORT +#define WDL_HASSTRINGS_EXPORT +#endif + +WDL_HASSTRINGS_EXPORT const char *hasStrings_rewutf8(const char *str, const char *base) +{ +#ifdef WDL_HASSTRINGS_REWUTF8_HOOK + WDL_HASSTRINGS_REWUTF8_HOOK(str, base) +#endif + while (str > base && (*(unsigned char *)str & 0xC0) == 0x80) str--; + return str; +} +WDL_HASSTRINGS_EXPORT int hasStrings_isNonWordChar(const char *cptr) +{ + // treat non-alnum non-utf-8 as whitespace when searching for " foo " + const unsigned char c = *(const unsigned char *)cptr; + if (c < 128) + { + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9')) + { + return 0; + } + + return 1; // non-alnum ascii are non-word chars + } + + // most UTF-8 characters are word characters + if (c == 0xC2) + { + // except UTF-8 U+A1 to U+B1, U+B4, U+B6, U+B7, U+BB, U+BF + const unsigned char c2 = ((unsigned char*)cptr)[1]; + if (c2 >= 0xa1) + { + switch (c2) + { + case 0xB4: case 0xB6: case 0xB7: case 0xBB: case 0xBF: + return 1; + default: + if (c2 <= 0xB1) return 1; + break; + } + } + } + else if (c == 0xE2) + { + unsigned char c2; + // except UTF-8 U+2000-U+206F and U+20A0-U+20BF + switch (((unsigned char*)cptr)[1]) + { + case 0x80: + c2 = ((unsigned char*)cptr)[2]; + if (c2 >= 0x80 && c2 <= 0xBF) return 1; + break; + case 0x81: + c2 = ((unsigned char*)cptr)[2]; + if (c2 >= 0x80 && c2 <= 0xAF) return 1; + break; + case 0x82: // U+20A0-U+20BF (currency signs) + c2 = ((unsigned char*)cptr)[2]; + if (c2 >= 0xa0 && c2 <= 0xBF) return 1; + break; + } + } + + return 0; +} + +#include "utf8_extended.h" + +// returns negative if does not match but more of a is available to search +// returns 0 if done searching without match +// returns >0 if matches (return bytelen of match) +// note that this assumes that b was preprocessed by WDL_makeSearchFilter and that strlen(b) >= n +WDL_HASSTRINGS_EXPORT int hasStrings_utf8cmp(const unsigned char * const a, const unsigned char *b, unsigned int n) +{ + int aidx=0; + while (n) + { + int ca=a[aidx], cb=b[0]; + // ca may be any character (including A-Z), utf8, cb will never be A-Z or NUL + WDL_ASSERT(cb != 0 && !(cb >= 'A' && cb <= 'Z')); + cb -= ca; + // if ca is A, and cb is a, cb will be 'a'-'A' + if (cb) + { + if (cb != 'a'-'A') + { + if (ca < 0xc3 || ca > 0xc5) + { + if (ca == 0xE2 && cb == ('\''-0xE2) && a[aidx+1] == 0x80 && (a[aidx+2]&~1) == 0x98) + { + aidx+=3; + ++b; + --n; + continue; + } + const int skipl = WDL_IS_UTF8_SKIPPABLE(ca,a[aidx+1]); + if (skipl) + { + aidx += skipl; + continue; + } + return -ca; + } + + const int ccf = a[++aidx]; + if (ccf < 0x80) return -ca; + + if (ca == 0xc3) + { + // latin-1 supplemental + const int cc = ccf & ~0x20; + switch (*b) + { +#define SCAN(ch, CH) case ch: if (!WDL_IS_UTF8_BYTE2_LATIN1S_##CH(cc,ccf)) return -ca; break; + SCAN('a',A) + SCAN('c',C) + SCAN('e',E) + SCAN('i',I) + SCAN('n',N) + SCAN('o',O) + SCAN('u',U) + SCAN('y',Y) + default: return -ca; break; +#undef SCAN + } + } + else + { + // latin extended A + switch (*b) + { +#define SCAN(ch, CH) case ch: if (!WDL_IS_UTF8_EXT1A_##CH(ca,ccf)) return -ca; break; + SCAN('a',A) + SCAN('c',C) + SCAN('d',D) + SCAN('e',E) + SCAN('g',G) + SCAN('h',H) + SCAN('i',I) + SCAN('j',J) + SCAN('k',K) + SCAN('l',L) + SCAN('n',N) + SCAN('o',O) + SCAN('r',R) + SCAN('s',S) + SCAN('t',T) + SCAN('u',U) + SCAN('w',W) + SCAN('y',Y) + SCAN('z',Z) + default: return -ca; break; +#undef SCAN + } + } + } + else if (ca < 'A' || ca > 'Z') return -ca; + } + ++aidx; + ++b; + --n; + } + return aidx; +} + +static const char *hasStrings_scan_for_char_match(const char *p, char v) +{ + if (v < 'a' || v > 'z') + for (;;) + { + char c = *p; + if (!c) return NULL; + if (c == v) return p; + p++; + } + + switch (v) + { + case '\'': + for (;;) { + unsigned char c = *(const unsigned char *)p; + if (!c) return NULL; + if (c == '\'') return p; + if (c == 0xE2 && ((unsigned char*)p)[1] == 0x80 && (((unsigned char*)p)[2]&~1) == 0x98) + return p; + p++; + } + +#define SCAN(ch, CH) case (ch): for (;;) { \ + unsigned char c = *(const unsigned char *)p; \ + if (!c) return NULL; \ + if ((c|0x20) == (ch)) return p; \ + if (c >= 0xc3) { \ + if (c == 0xc3) { \ + const unsigned char ccf = ((const unsigned char*)p)[1]; \ + const unsigned char cc = ccf & ~0x20; \ + if (WDL_IS_UTF8_BYTE2_LATIN1S_##CH(cc,ccf)) return p; \ + } else { \ + if (WDL_IS_UTF8_EXT1A_##CH(c, ((const unsigned char*)p)[1])) return p; \ + } \ + } \ + p++; \ + } + SCAN('a',A) + SCAN('c',C) + SCAN('e',E) + SCAN('i',I) + SCAN('n',N) + SCAN('o',O) + SCAN('u',U) + SCAN('y',Y) +#undef SCAN + + // latin extended A only +#define SCAN(ch, CH) case (ch): for (;;) { \ + unsigned char c = *(const unsigned char *)p; \ + if (!c) return NULL; \ + if ((c|0x20) == (ch)) return p; \ + if (WDL_IS_UTF8_EXT1A_##CH(c, ((const unsigned char*)p)[1])) return p; \ + p++; \ + } + + SCAN('d',D) + SCAN('g',G) + SCAN('h',H) + SCAN('j',J) + SCAN('k',K) + SCAN('l',L) + SCAN('r',R) + SCAN('s',S) + SCAN('t',T) + SCAN('w',W) + SCAN('z',Z) +#undef SCAN + + } + + for (;;) + { + char c = *p; + if (!c) return NULL; + if ((c|0x20) == v) return p; + p++; + } +} + +WDL_HASSTRINGS_EXPORT const char *hasStrings_skipSkippable(const char *cptr) +{ + int skip; + while ((skip=WDL_IS_UTF8_SKIPPABLE(((unsigned char*)cptr)[0],((unsigned char*)cptr)[1]))>0) cptr+=skip; + return cptr; +} + +WDL_HASSTRINGS_EXPORT bool WDL_hasStringsEx2(const char **name_list, int name_list_size, const LineParser *lp +#ifdef WDL_HASSTRINGS_EXTRA_PARAMETERS + WDL_HASSTRINGS_EXTRA_PARAMETERS +#endif + ) +{ + if (!lp) return true; + const int ntok = lp->getnumtokens(); + if (ntok<1) return true; + + char stack_[1024]; // &1=not bit, 0x10 = ignoring subscopes, &2= state when 0x10 set + int stacktop = 0, stacktop_v; +#define TOP_OF_STACK stacktop_v +#define PUSH_STACK(x) do { if (stacktop < (int)sizeof(stack_) - 1) stack_[stacktop++] = stacktop_v&0xff; stacktop_v = (x); } while(0) +#define POP_STACK() (stacktop_v = stack_[--stacktop]) + TOP_OF_STACK = 0; + + char matched_local=-1; // -1 = first eval for scope, 0=did not pass scope, 1=OK, 2=ignore rest of scope + for (int x = 0; x < ntok; x ++) + { + const char *n=lp->gettoken_str(x); + + if (n[0] == '(' && !n[1] && !lp->gettoken_quotingchar(x)) + { + if (!(matched_local&1)) + { + TOP_OF_STACK |= matched_local | 0x10; + matched_local=2; // ignore subscope + } + else + { + matched_local = -1; // new scope + } + + PUSH_STACK(0); + } + else if (n[0] == ')' && !n[1] && stacktop && !lp->gettoken_quotingchar(x)) + { + if (POP_STACK()&0x10) + { + // restore state + matched_local = TOP_OF_STACK&2; + } + else + { + matched_local = (matched_local != 0 ? 1 : 0) ^ (TOP_OF_STACK&1); + } + TOP_OF_STACK = 0; + } + else if (n[0] == 'O' && n[1] == 'R' && !n[2] && matched_local != 2 && !lp->gettoken_quotingchar(x)) + { + matched_local = (matched_local > 0) ? 2 : -1; + TOP_OF_STACK = 0; + } + else if (matched_local&1) // matches 1, -1 + { + int ln = (int)strlen(n); + if (ln>0) + { + // ^foo -- string starts (or follows \1 separator with) foo + // foo$ -- string ends with foo (or is immediately followed by \1 separator) + // " foo ", "foo ", " foo" include end of string/start of string has whitespace + int wc_left = 0; // 1=require \1 or start of string, 2=require space or \1 or start + int wc_right = 0; // 1=require \1 or \0, 2 = require space or \1 or \0 + // perhaps wc_left/wc_right of 2 should also match non-alnum characters in addition to space? + if (ln>1) + { + switch (*n) + { + case ' ': + if (*++n != ' ') wc_left=2; + // else { multiple characters of whitespace = literal whitespace search (two spaces requires a single space, etc) } + + ln--; + break; + case '^': + ln--; + n++; + wc_left=1; + break; + // upper case being here implies it is almost certainly NOT/AND due to postprocessing in WDL_makeSearchFilter + case 'N': + if (WDL_NORMALLY(!strcmp(n,"NOT") && !lp->gettoken_quotingchar(x))) + { + TOP_OF_STACK^=1; + continue; + } + break; + case 'A': + if (WDL_NORMALLY(!strcmp(n,"AND") && !lp->gettoken_quotingchar(x))) + { + // ignore unquoted uppercase AND + continue; + } + break; + } + } + if (ln>1) + { + switch (n[ln-1]) + { + case ' ': + if (n[--ln - 1] != ' ') wc_right=2; + // else { multiple characters of whitespace = literal whitespace search (two spaces requires a single space, etc) } + break; + case '$': + ln--; + wc_right++; + break; + } + } + + if (!wc_left && !wc_right && *n) + { + switch (lp->gettoken_quotingchar(x)) + { + case '\'': + case '"': + { // if a quoted string has no whitespace in it, treat as whole word search + const char *p = n; + while (*p && *p != ' ' && *p != '\t') p++; + if (!*p) + { + wc_left=wc_right=2; + } + } + break; + } + } + + bool matched = false; + +#ifdef WDL_HASSTRINGS_PRE_MATCH + if (!wc_left && !wc_right && WDL_HASSTRINGS_PRE_MATCH(n)) + matched = true; + else +#endif + for (int i = 0; i < name_list_size; i ++) + { + const char *name = name_list[i]; + const char *t = name; + +#define MATCH_RIGHT_CHECK_WORD(SZ) \ + (wc_right == 0 || \ + ((const unsigned char*)(t))[SZ] < 2 || \ + (wc_right > 1 && hasStrings_isNonWordChar(hasStrings_skipSkippable((t)+(SZ)))) \ + ) + +#define MATCH_LEFT_SKIP_TO_WORD() do { \ + if (*(unsigned char*)t < 2) { t++; break; } \ + if (wc_left>1) { const int l = hasStrings_isNonWordChar(t); if (l > 0) { t+=l; break; } } \ + t++; \ + } while (t[0]) + + { + const char n0 = n[0]; + if (wc_left>0) + { + for (;;) + { + t = hasStrings_scan_for_char_match(t,n0); + if (!t) break; + if (t==name || t[-1] == 1 || (wc_left>1 && hasStrings_isNonWordChar(hasStrings_rewutf8(t-1,name)))) + { + const int v = hasStrings_utf8cmp((const unsigned char *)t,(const unsigned char *)n,ln); + if (v>=0) + { + if (!v) break; + if (MATCH_RIGHT_CHECK_WORD(v)) { matched = true; break; } + } + } + t++; + } + } + else + { + for (;;) + { + t = hasStrings_scan_for_char_match(t,n0); + if (!t) break; + const int v = hasStrings_utf8cmp((const unsigned char *)t,(const unsigned char *)n,ln); + if (v>=0) + { + if (!v) break; + if (MATCH_RIGHT_CHECK_WORD(v)) { matched = true; break; } + } + t++; + } + } + } +#undef MATCH_RIGHT_CHECK_WORD +#undef MATCH_LEFT_SKIP_TO_WORD + if (matched) break; + } + + matched_local = (matched?1:0) ^ (TOP_OF_STACK&1); + TOP_OF_STACK=0; + } + } + } + while (stacktop > 0) + { + if (POP_STACK() & 0x10) matched_local=TOP_OF_STACK&2; + else matched_local = (matched_local > 0 ? 1 : 0) ^ (TOP_OF_STACK&1); + } + + return matched_local!=0; +#undef TOP_OF_STACK +#undef POP_STACK +#undef PUSH_STACK +} + +#ifndef WDL_HASSTRINGS_EXTRA_PARAMETERS +WDL_HASSTRINGS_EXPORT bool WDL_hasStringsEx(const char *name, const LineParser *lp) +{ + return WDL_hasStringsEx2(&name,1,lp); +} + +WDL_HASSTRINGS_EXPORT bool WDL_hasStrings(const char *name, const LineParser *lp) +{ + return WDL_hasStringsEx2(&name,1,lp); +} +#endif + +WDL_HASSTRINGS_EXPORT char *WDL_hasstrings_preproc_searchitem(char *wr, const char *src) +{ + while (*src) + { + unsigned char c = *(unsigned char*)src++; + if (c >= 'A' && c <= 'Z') c+='a'-'A'; + else if (c == 0xC3) + { + const unsigned char ccf = *(unsigned char*)src; + const unsigned char cc = ccf & ~0x20; + if (WDL_IS_UTF8_BYTE2_LATIN1S_A(cc,ccf)) c = 'a'; + else if (WDL_IS_UTF8_BYTE2_LATIN1S_C(cc,ccf)) c = 'c'; + else if (WDL_IS_UTF8_BYTE2_LATIN1S_E(cc,ccf)) c = 'e'; + else if (WDL_IS_UTF8_BYTE2_LATIN1S_I(cc,ccf)) c = 'i'; + else if (WDL_IS_UTF8_BYTE2_LATIN1S_N(cc,ccf)) c = 'n'; + else if (WDL_IS_UTF8_BYTE2_LATIN1S_O(cc,ccf)) c = 'o'; + else if (WDL_IS_UTF8_BYTE2_LATIN1S_U(cc,ccf)) c = 'u'; + else if (WDL_IS_UTF8_BYTE2_LATIN1S_Y(cc,ccf)) c = 'y'; + + if (c != 0xC3) src++; + } + else if (c == 0xE2) + { + // convert u+2018/2019 to ' + if (*(unsigned char*)src == 0x80 && (((unsigned char*)src)[1]&~1) == 0x98) + { + c = '\''; + src+=2; + } + } + else + { + const int skipl = WDL_IS_UTF8_SKIPPABLE(c, *(unsigned char*)src); + if (skipl > 0) + { + src += skipl-1; + continue; + } + } + + // we could also convert latin extended A characters to ascii here, but meh + *wr++ = c; + } + *wr=0; + return wr; +} + +WDL_HASSTRINGS_EXPORT bool WDL_makeSearchFilter(const char *flt, LineParser *lp) +{ + if (WDL_NOT_NORMALLY(!lp)) return false; + + if (WDL_NOT_NORMALLY(!flt)) flt=""; + +#ifdef WDL_LINEPARSER_HAS_LINEPARSERINT + if (lp->parse_ex(flt,true,false,true)) // allow unterminated quotes +#else + if (lp->parse_ex(flt,true,false)) +#endif + { + if (*flt) lp->set_one_token(flt); // failed parsing search string, search as a single token + } + for (int x = 0; x < lp->getnumtokens(); x ++) + { + char *p = (char *)lp->gettoken_str(x); + if (lp->gettoken_quotingchar(x) || (strcmp(p,"NOT") && strcmp(p,"AND") && strcmp(p,"OR"))) + { + WDL_hasstrings_preproc_searchitem(p, p); + } + } + + return lp->getnumtokens()>0; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/heapbuf.h b/plugin-reaper-realearn/main/lib/WDL/WDL/heapbuf.h new file mode 100644 index 0000000..da4b3ae --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/heapbuf.h @@ -0,0 +1,362 @@ +/* + WDL - heapbuf.h + Copyright (C) 2005 and later 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. + +*/ + +/* + + This file provides the interface and implementation for WDL_HeapBuf, a simple + malloc() wrapper for resizeable blocks. + + Also in this file is WDL_TypedBuf which is a templated version WDL_HeapBuf + that manages type and type-size. + +*/ + +#ifndef _WDL_HEAPBUF_H_ +#define _WDL_HEAPBUF_H_ + +#ifndef WDL_HEAPBUF_IMPL_ONLY + +#include "wdltypes.h" + +class WDL_HeapBuf +{ + public: + // interface +#ifdef WDL_HEAPBUF_INTF_ONLY + void *Resize(int newsize, bool resizedown=true); + void CopyFrom(const WDL_HeapBuf *hb, bool exactCopyOfConfig=false); +#endif + void *Get() const { return m_size?m_buf:NULL; } // returns NULL if size is 0 + void *GetFast() const { return m_buf; } // returns last buffer if size is 0 + int GetSize() const { return m_size; } + void *GetAligned(int align) const { return (void *)(((UINT_PTR)Get() + (align-1)) & ~(UINT_PTR)(align-1)); } + + void SetGranul(int granul) { m_granul = granul; } + int GetGranul() const { return m_granul; } + void Prealloc(int sz) { if (m_alloc < sz && WDL_NORMALLY(m_size < sz)) { const int oldsz = m_size; Resize(sz,false); m_size=oldsz; } } + + void *ResizeOK(int newsize, bool resizedown = true) { void *p=Resize(newsize, resizedown); return GetSize() == newsize ? p : NULL; } + + WDL_HeapBuf(const WDL_HeapBuf &cp) + { + m_buf=0; + CopyFrom(&cp,true); + } + WDL_HeapBuf &operator=(const WDL_HeapBuf &cp) + { + CopyFrom(&cp,false); + return *this; + } + + + + explicit WDL_HeapBuf(int granul=4096) : m_buf(NULL), m_alloc(0), m_size(0), m_granul(granul) + { + } + ~WDL_HeapBuf() + { +#ifdef WDL_HEAPBUF__TRACE_ACTION + if (m_buf) WDL_HEAPBUF__TRACE_ACTION(heapbuf_delete_free); +#endif + free(m_buf); + } + +#endif // !WDL_HEAPBUF_IMPL_ONLY + + // implementation bits +#ifndef WDL_HEAPBUF_INTF_ONLY + #ifdef WDL_HEAPBUF_IMPL_ONLY + void *WDL_HeapBuf::Resize(int newsize, bool resizedown) + #else + void *Resize(int newsize, bool resizedown=true) + #endif + { + if (newsize<0) newsize=0; + #ifdef DEBUG_TIGHT_ALLOC // horribly slow, do not use for release builds + if (newsize == m_size) return m_buf; + + int a = newsize; + if (a > m_size) a=m_size; + void *newbuf = newsize ? malloc(newsize) : 0; + if (!newbuf && newsize) + { + #ifdef WDL_HEAPBUF_ONMALLOCFAIL + WDL_HEAPBUF_ONMALLOCFAIL(newsize) + #endif + return m_buf; + } + if (newbuf&&m_buf) memcpy(newbuf,m_buf,a); + m_size=m_alloc=newsize; + free(m_buf); + return m_buf=newbuf; + #endif + + if (newsize!=m_size || (resizedown && newsize < m_alloc/2)) + { + int resizedown_under = 0; + if (resizedown && newsize < m_size) + { + // shrinking buffer: only shrink if allocation decreases to min(alloc/2, alloc-granul*4) or 0 + resizedown_under = m_alloc - (m_granul<<2); + if (resizedown_under > m_alloc/2) resizedown_under = m_alloc/2; + if (resizedown_under < 1) resizedown_under=1; + } + + if (newsize > m_alloc || newsize < resizedown_under) + { + int granul=newsize/2; + int newalloc; + if (granul < m_granul) granul=m_granul; + + if (newsize<1) newalloc=0; + else if (m_granul<4096) newalloc=newsize+granul; + else + { + granul &= ~4095; + if (granul< 4096) granul=4096; + else if (granul>4*1024*1024) granul=4*1024*1024; + newalloc = ((newsize + granul + 96)&~4095)-96; + } + + if (newalloc != m_alloc) + { + + if (newalloc <= 0) + { +#ifdef WDL_HEAPBUF__TRACE_ACTION + if (m_buf) WDL_HEAPBUF__TRACE_ACTION(heapbuf_free); +#endif + free(m_buf); + m_buf=0; + m_alloc=0; + m_size=0; + return 0; + } +#ifdef WDL_HEAPBUF__TRACE_ACTION + if (m_buf) WDL_HEAPBUF__TRACE_ACTION(heapbuf_realloc); + else WDL_HEAPBUF__TRACE_ACTION(heapbuf_malloc); +#endif + void *nbuf=realloc(m_buf,newalloc); + if (!nbuf) + { +#ifdef WDL_HEAPBUF__TRACE_ACTION + WDL_HEAPBUF__TRACE_ACTION(heapbuf_mallocfree); +#endif + if (!(nbuf=malloc(newalloc))) + { + #ifdef WDL_HEAPBUF_ONMALLOCFAIL + WDL_HEAPBUF_ONMALLOCFAIL(newalloc); + #endif + return m_size?m_buf:0; // failed, do not resize + } + + if (m_buf) + { + int sz=newsize0) memcpy(nbuf,m_buf,sz); + free(m_buf); + } + } + + m_buf=nbuf; + m_alloc=newalloc; + } // alloc size change + } // need size up or down + m_size=newsize; + } // size change + return m_size?m_buf:0; + } + + #ifdef WDL_HEAPBUF_IMPL_ONLY + void WDL_HeapBuf::CopyFrom(const WDL_HeapBuf *hb, bool exactCopyOfConfig) + #else + void CopyFrom(const WDL_HeapBuf *hb, bool exactCopyOfConfig=false) + #endif + { + if (exactCopyOfConfig) // copy all settings + { + free(m_buf); + + m_granul = hb->m_granul; + + m_size=m_alloc=0; + m_buf=hb->m_buf && hb->m_alloc>0 ? malloc(m_alloc = hb->m_alloc) : NULL; + #ifdef WDL_HEAPBUF_ONMALLOCFAIL + if (!m_buf && m_alloc) { WDL_HEAPBUF_ONMALLOCFAIL(m_alloc) } ; + #endif + if (m_buf) memcpy(m_buf,hb->m_buf,m_size = hb->m_size); + else m_alloc=0; + } + else // copy just the data + size + { + const int newsz=hb->GetSize(); + Resize(newsz,true); + if (GetSize()!=newsz) Resize(0); + else memcpy(Get(),hb->Get(),newsz); + } + } + +#endif // ! WDL_HEAPBUF_INTF_ONLY + +#ifndef WDL_HEAPBUF_IMPL_ONLY + + private: + void *m_buf; + int m_alloc; + int m_size; + int m_granul; + + #if defined(_WIN64) || defined(__LP64__) + public: + int ___pad; // keep size 8 byte aligned + #endif + +}; + +template class WDL_TypedBuf +{ + public: + PTRTYPE *Get() const { return (PTRTYPE *) m_hb.Get(); } + PTRTYPE *GetFast() const { return (PTRTYPE *) m_hb.GetFast(); } + int GetSize() const { return m_hb.GetSize()/(unsigned int)sizeof(PTRTYPE); } + int GetSizeBytes() const { return m_hb.GetSize(); } + + PTRTYPE *Resize(int newsize, bool resizedown = true) { return (PTRTYPE *)m_hb.Resize(newsize*sizeof(PTRTYPE),resizedown); } + PTRTYPE *ResizeOK(int newsize, bool resizedown = true) { return (PTRTYPE *)m_hb.ResizeOK(newsize*sizeof(PTRTYPE), resizedown); } + + void Prealloc(int sz) { return m_hb.Prealloc(sz*sizeof(PTRTYPE)); } + + void SetToZero() { memset(m_hb.Get(), 0, m_hb.GetSize()); } + + PTRTYPE *GetAligned(int align) const { return (PTRTYPE *) m_hb.GetAligned(align); } + + PTRTYPE *Add(PTRTYPE val) + { + const int sz=GetSize(); + PTRTYPE* p=ResizeOK(sz+1,false); + if (p) + { + p[sz]=val; + return p+sz; + } + return NULL; + } + PTRTYPE *Add(const PTRTYPE *buf, int bufsz) + { + if (bufsz>0) + { + const int sz=GetSize(); + PTRTYPE* p=ResizeOK(sz+bufsz,false); + if (p) + { + p+=sz; + if (buf) memcpy(p,buf,bufsz*sizeof(PTRTYPE)); + else memset((char*)p,0,bufsz*sizeof(PTRTYPE)); + return p; + } + } + return NULL; + } + PTRTYPE *Set(const PTRTYPE *buf, int bufsz) + { + if (bufsz>=0) + { + PTRTYPE* p=ResizeOK(bufsz,false); + if (p) + { + if (buf) memcpy(p,buf,bufsz*sizeof(PTRTYPE)); + else memset((char*)p,0,bufsz*sizeof(PTRTYPE)); + return p; + } + } + return NULL; + } + PTRTYPE* Insert(PTRTYPE val, int idx) + { + const int sz=GetSize(); + if (idx >= 0 && idx <= sz) + { + PTRTYPE* p=ResizeOK(sz+1,false); + if (p) + { + memmove(p+idx+1, p+idx, (sz-idx)*sizeof(PTRTYPE)); + p[idx]=val; + return p+idx; + } + } + return NULL; + } + + void Delete(int idx) + { + PTRTYPE* p=Get(); + const int sz=GetSize(); + if (idx >= 0 && idx < sz) + { + memmove(p+idx, p+idx+1, (sz-idx-1)*sizeof(PTRTYPE)); + Resize(sz-1,false); + } + } + + void SetGranul(int gran) { m_hb.SetGranul(gran); } + + int Find(PTRTYPE val) const + { + const PTRTYPE* p=Get(); + const int sz=GetSize(); + int i; + for (i=0; i < sz; ++i) if (p[i] == val) return i; + return -1; + } + + explicit WDL_TypedBuf(int granul=4096) : m_hb(granul) { } + ~WDL_TypedBuf() { } + + WDL_HeapBuf *GetHeapBuf() { return &m_hb; } + const WDL_HeapBuf *GetHeapBuf() const { return &m_hb; } + + int DeleteBatch(bool (*proc)(PTRTYPE *p, void *ctx), void *ctx=NULL) // proc returns true to delete item. returns number deleted + { + const int sz = GetSize(); + int cnt=0; + PTRTYPE *rd = Get(), *wr = rd; + for (int x = 0; x < sz; x ++) + { + if (!proc(rd,ctx)) + { + if (rd != wr) *wr=*rd; + wr++; + cnt++; + } + rd++; + } + if (cnt < sz) Resize(cnt,false); + return sz - cnt; + } + + private: + WDL_HeapBuf m_hb; +}; + +#endif // ! WDL_HEAPBUF_IMPL_ONLY + +#endif // _WDL_HEAPBUF_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/Makefile b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/Makefile new file mode 100644 index 0000000..10d9fe8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/Makefile @@ -0,0 +1,20 @@ +# freebsd3 makefile +default: jnl.a + +LDFLAGS = -pthread +CFLAGS = -s -O2 -Wall -DTHREAD_SAFE -D_THREAD_SAFE -D_REENTRANT +CC = gcc +CPP = g++ +CXX = g++ + +OBJS = asyncdns.o connection.o httpget.o httpserv.o listen.o util.o sercon.o + +jnl.a: ${OBJS} + -rm -f jnl.a + ar rcs jnl.a ${OBJS} + +test: ${OBJS} test.o + $(CC) ${CFLAGS} -o test test.o ${OBJS} ${LDFLAGS} + +clean: + -rm -f ${OBJS} test jnl.a test.o diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/asyncdns.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/asyncdns.cpp new file mode 100644 index 0000000..dea5e77 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/asyncdns.cpp @@ -0,0 +1,257 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: asyncdns.cpp - JNL portable asynchronous DNS implementation +** License: see jnetlib.h +*/ + +#include "netinc.h" +#include "util.h" +#include "asyncdns.h" +#ifdef _WIN32 +#include +#endif +#include "../wdlcstring.h" + +JNL_AsyncDNS::JNL_AsyncDNS(int max_cache_entries) +{ + m_thread_kill=1; + m_thread=0; + m_cache_size=max_cache_entries; + m_cache=(cache_entry *)::malloc(sizeof(cache_entry)*m_cache_size); + if (m_cache) memset(m_cache,0,sizeof(cache_entry)*m_cache_size); + else m_cache_size = 0; +} + +JNL_AsyncDNS::~JNL_AsyncDNS() +{ +#ifndef NO_DNS_SUPPORT + m_thread_kill=1; + +#ifdef _WIN32 + if (m_thread) + { + WaitForSingleObject(m_thread,INFINITE); + CloseHandle(m_thread); + } +#else + if (m_thread) + { + void *p; + pthread_join(m_thread,&p); + } +#endif//!_WIN32 +#endif//NO_DNS_SUPPORT + free(m_cache); +} + +#ifdef _WIN32 +unsigned WINAPI JNL_AsyncDNS::_threadfunc(void *_d) +#else +unsigned int JNL_AsyncDNS::_threadfunc(void *_d) +#endif +{ +#ifndef NO_DNS_SUPPORT + int nowinsock=JNL::open_socketlib(); + JNL_AsyncDNS *_this=(JNL_AsyncDNS*)_d; + int x; + for (x = 0; x < _this->m_cache_size && !_this->m_thread_kill; x ++) + { + if (_this->m_cache[x].last_used && !_this->m_cache[x].resolved) + { + if (!nowinsock) + { + if (_this->m_cache[x].mode==0) + { + struct hostent *hostentry; + hostentry=::gethostbyname(_this->m_cache[x].hostname); + if (hostentry) + { + memcpy(&_this->m_cache[x].addr,hostentry->h_addr,sizeof(int)); + } + else + _this->m_cache[x].addr=INADDR_NONE; + } + else if (_this->m_cache[x].mode==1) + { + struct hostent *ent; + ent=::gethostbyaddr((const char *)&_this->m_cache[x].addr,4,AF_INET); + lstrcpyn_safe(_this->m_cache[x].hostname,ent ? ent->h_name : "",sizeof(_this->m_cache[x].hostname)); + } + _this->m_cache[x].resolved=1; + } + else + { + if (_this->m_cache[x].mode==0) + { + _this->m_cache[x].addr=INADDR_NONE; + _this->m_cache[x].resolved=1; + } + else if (_this->m_cache[x].mode==1) + { + _this->m_cache[x].hostname[0]=0; + _this->m_cache[x].resolved=1; + } + } + } + } + if (!nowinsock) JNL::close_socketlib(); + _this->m_thread_kill=1; +#endif // NO_DNS_SUPPORT + + return 0; +} + +int JNL_AsyncDNS::resolve(const char *hostname, unsigned int *addr) +{ + // return 0 on success, 1 on wait, -1 on unresolvable + int x; + unsigned int ip=inet_addr(hostname); + if (ip != INADDR_NONE) + { + *addr=ip; + return 0; + } +#ifndef NO_DNS_SUPPORT + + for (x = 0; x < m_cache_size; x ++) + { + if (!stricmp(m_cache[x].hostname,hostname) && m_cache[x].mode==0) + { + m_cache[x].last_used=time(NULL); + if (m_cache[x].resolved) + { + if (m_cache[x].addr == INADDR_NONE) + { + return -1; + } + *addr=m_cache[x].addr; + return 0; + } + makesurethreadisrunning(); + return 1; + } + } + // add to resolve list + int oi=-1; + for (x = 0; x < m_cache_size; x ++) + { + if (!m_cache[x].last_used) + { + oi=x; + break; + } + if ((oi==-1 || m_cache[x].last_used < m_cache[oi].last_used) && m_cache[x].resolved) + { + oi=x; + } + } + if (oi == -1) + { + return -1; + } + strcpy(m_cache[oi].hostname,hostname); + m_cache[oi].mode=0; + m_cache[oi].addr=INADDR_NONE; + m_cache[oi].resolved=0; + m_cache[oi].last_used=time(NULL); + + makesurethreadisrunning(); + return 1; +#else + return -1; +#endif +} + +int JNL_AsyncDNS::reverse(unsigned int addr, char *hostname) +{ + // return 0 on success, 1 on wait, -1 on unresolvable + int x; + if (addr == INADDR_NONE) + { + return -1; + } +#ifndef NO_DNS_SUPPORT + for (x = 0; x < m_cache_size; x ++) + { + if (m_cache[x].addr==addr && m_cache[x].mode==1) + { + m_cache[x].last_used=time(NULL); + if (m_cache[x].resolved) + { + if (!m_cache[x].hostname[0]) + { + return -1; + } + lstrcpyn_safe(hostname,m_cache[x].hostname,256); + return 0; + } + makesurethreadisrunning(); + return 1; + } + } + // add to resolve list + int oi=-1; + for (x = 0; x < m_cache_size; x ++) + { + if (!m_cache[x].last_used) + { + oi=x; + break; + } + if ((oi==-1 || m_cache[x].last_used < m_cache[oi].last_used) && m_cache[x].resolved) + { + oi=x; + } + } + if (oi == -1) + { + return -1; + } + m_cache[oi].addr=addr; + m_cache[oi].hostname[0]=0; + m_cache[oi].resolved=0; + m_cache[oi].mode=1; + m_cache[oi].last_used=time(NULL); + + makesurethreadisrunning(); + return 1; +#else + return -1; +#endif +} + + +void JNL_AsyncDNS::makesurethreadisrunning(void) +{ +#ifndef NO_DNS_SUPPORT + if (m_thread_kill) + { + #ifdef _WIN32 + if (m_thread) + { + WaitForSingleObject(m_thread,INFINITE); + CloseHandle(m_thread); + } + unsigned id; + m_thread_kill=0; + m_thread=(HANDLE)_beginthreadex(NULL,0,_threadfunc,(void *)this,0,&id); + if (!m_thread) + { + #else + if (m_thread) + { + void *p; + pthread_join(m_thread,&p); + } + m_thread_kill=0; + if (pthread_create(&m_thread,NULL,(void *(*) (void *))_threadfunc,(void*)this) != 0) + { + #endif + m_thread_kill=1; + } + } +#endif//NO_DNS_SUPPORT +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/asyncdns.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/asyncdns.h new file mode 100644 index 0000000..b9098f6 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/asyncdns.h @@ -0,0 +1,76 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: asyncdns.h - JNL portable asynchronous DNS interface +** License: see jnetlib.h +** +** Usage: +** 1. Create JNL_AsyncDNS object, optionally with the number of cache entries. +** 2. call resolve() to resolve a hostname into an address. The return value of +** resolve is 0 on success (host successfully resolved), 1 on wait (meaning +** try calling resolve() with the same hostname in a few hundred milliseconds +** or so), or -1 on error (i.e. the host can't resolve). +** 3. call reverse() to do reverse dns (ala resolve()). +** 4. enjoy. +*/ + +#ifndef _ASYNCDNS_H_ +#define _ASYNCDNS_H_ + +#include + +#ifndef JNL_NO_DEFINE_INTERFACES +class JNL_IAsyncDNS +{ +public: + virtual ~JNL_IAsyncDNS() { } + virtual int resolve(const char *hostname, unsigned int *addr)=0; // return 0 on success, 1 on wait, -1 on unresolvable + virtual int reverse(unsigned int addr, char *hostname)=0; // return 0 on success, 1 on wait, -1 on unresolvable. hostname must be at least 256 bytes. +}; +#define JNL_AsyncDNS_PARENTDEF : public JNL_IAsyncDNS +#else +#define JNL_IAsyncDNS JNL_AsyncDNS +#define JNL_AsyncDNS_PARENTDEF +#endif + + +#ifndef JNL_NO_IMPLEMENTATION + +class JNL_AsyncDNS JNL_AsyncDNS_PARENTDEF +{ +public: + JNL_AsyncDNS(int max_cache_entries=64); + ~JNL_AsyncDNS(); + + int resolve(const char *hostname, unsigned int *addr); // return 0 on success, 1 on wait, -1 on unresolvable + int reverse(unsigned int addr, char *hostname); // return 0 on success, 1 on wait, -1 on unresolvable. hostname must be at least 256 bytes. + +private: + typedef struct + { + time_t last_used; // timestamp. + char resolved; + char mode; // 1=reverse + char hostname[256]; + unsigned int addr; + } + cache_entry; + + cache_entry *m_cache; + int m_cache_size; + volatile int m_thread_kill; +#ifdef _WIN32 + HANDLE m_thread; + static unsigned WINAPI _threadfunc(void *_d); +#else + pthread_t m_thread; + static unsigned int _threadfunc(void *_d); +#endif + void makesurethreadisrunning(void); + +}; +#endif // !JNL_NO_IMPLEMENTATION + +#endif //_ASYNCDNS_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/connection.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/connection.cpp new file mode 100644 index 0000000..710d909 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/connection.cpp @@ -0,0 +1,505 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: connection.cpp - JNL TCP connection implementation +** License: see jnetlib.h +*/ + +#include "netinc.h" +#include "util.h" +#include "connection.h" +#include "../wdlcstring.h" + + +JNL_Connection::JNL_Connection(JNL_IAsyncDNS *dns, int sendbufsize, int recvbufsize) +{ + m_errorstr=""; + if (dns == JNL_CONNECTION_AUTODNS) + { + m_dns=new JNL_AsyncDNS(); + m_dns_owned=1; + } + else + { + m_dns=dns; + m_dns_owned=0; + } + m_recv_buffer.Resize(recvbufsize); + m_send_buffer.Resize(sendbufsize); + m_socket=INVALID_SOCKET; + m_remote_port=0; + m_state=STATE_NOCONNECTION; + m_localinterfacereq=INADDR_ANY; + m_recv_len=m_recv_pos=0; + m_send_len=m_send_pos=0; + m_host[0]=0; + m_saddr = new struct sockaddr_in; + memset(m_saddr,0,sizeof(struct sockaddr_in)); +} + +void JNL_Connection::connect(SOCKET s, struct sockaddr_in *loc) +{ + close(1); + m_socket=s; + m_remote_port=0; + m_dns=NULL; + if (loc) *m_saddr=*loc; + else memset(m_saddr,0,sizeof(struct sockaddr_in)); + if (m_socket != INVALID_SOCKET) + { + SET_SOCK_DEFAULTS(m_socket); + SET_SOCK_BLOCK(m_socket,0); + m_state=STATE_CONNECTED; + } + else + { + m_errorstr="invalid socket passed to connect"; + m_state=STATE_ERROR; + } +} + +void JNL_Connection::connect(const char *hostname, int port) +{ + close(1); + m_remote_port=(short)port; + m_socket=::socket(AF_INET,SOCK_STREAM,0); + if (m_socket==INVALID_SOCKET) + { + m_errorstr="creating socket"; + m_state=STATE_ERROR; + } + else + { + if (m_localinterfacereq != INADDR_ANY) + { + sockaddr_in sa={0,}; + sa.sin_family=AF_INET; + sa.sin_addr.s_addr=m_localinterfacereq; + bind(m_socket,(struct sockaddr *)&sa,16); + } + SET_SOCK_DEFAULTS(m_socket); + SET_SOCK_BLOCK(m_socket,0); + lstrcpyn_safe(m_host,hostname,sizeof(m_host)); + memset(m_saddr,0,sizeof(struct sockaddr_in)); + if (!m_host[0]) + { + m_errorstr="empty hostname"; + m_state=STATE_ERROR; + } + else + { + m_state=STATE_RESOLVING; + m_saddr->sin_family=AF_INET; + m_saddr->sin_port=htons((unsigned short)port); + m_saddr->sin_addr.s_addr=inet_addr(hostname); + } + } +} + +JNL_Connection::~JNL_Connection() +{ + if (m_socket != INVALID_SOCKET) + { + ::shutdown(m_socket, SHUT_RDWR); + ::closesocket(m_socket); + m_socket=INVALID_SOCKET; + } + if (m_dns_owned) + { + delete m_dns; + } + delete m_saddr; +} + +void JNL_Connection::run(int max_send_bytes, int max_recv_bytes, int *bytes_sent, int *bytes_rcvd) +{ + int bytes_allowed_to_send=(max_send_bytes<0)?m_send_buffer.GetSize():max_send_bytes; + int bytes_allowed_to_recv=(max_recv_bytes<0)?m_recv_buffer.GetSize():max_recv_bytes; + + if (bytes_sent) *bytes_sent=0; + if (bytes_rcvd) *bytes_rcvd=0; + + switch (m_state) + { + case STATE_RESOLVING: + if (m_saddr->sin_addr.s_addr == INADDR_NONE) + { + int a=m_dns?m_dns->resolve(m_host,(unsigned int *)&m_saddr->sin_addr.s_addr):-1; + if (!a) { m_state=STATE_CONNECTING; } + else if (a == 1) + { + m_state=STATE_RESOLVING; + break; + } + else + { + m_errorstr="resolving hostname"; + m_state=STATE_ERROR; + return; + } + } + if (!::connect(m_socket,(struct sockaddr *)m_saddr,16)) + { + m_state=STATE_CONNECTED; + } + else if (JNL_ERRNO!=JNL_EINPROGRESS) + { + m_errorstr="connecting to host"; + m_state=STATE_ERROR; + } + else { m_state=STATE_CONNECTING; } + break; + case STATE_CONNECTING: + { +#ifdef _WIN32 + fd_set f[3]; + FD_ZERO(&f[0]); + FD_ZERO(&f[1]); + FD_ZERO(&f[2]); + FD_SET(m_socket,&f[0]); + FD_SET(m_socket,&f[1]); + FD_SET(m_socket,&f[2]); + struct timeval tv; + memset(&tv,0,sizeof(tv)); + if (select(0,&f[0],&f[1],&f[2],&tv)==-1) + { + m_errorstr="connecting to host (calling select())"; + m_state=STATE_ERROR; + } + else if (FD_ISSET(m_socket,&f[1])) + { + m_state=STATE_CONNECTED; + } + else if (FD_ISSET(m_socket,&f[2])) + { + m_errorstr="connecting to host"; + m_state=STATE_ERROR; + } +#else + struct pollfd pl = { m_socket, POLLERR|POLLHUP|POLLOUT, 0 }; + int res = poll(&pl,1,0); + if (res < 0 || (pl.revents & (POLLERR|POLLHUP))) + { + m_errorstr="connecting to host"; + m_state=STATE_ERROR; + } + else if (res>0 || (pl.revents&POLLOUT)) + m_state=STATE_CONNECTED; +#endif + } + break; + case STATE_CONNECTED: + case STATE_CLOSING: + if (m_send_len>0 && bytes_allowed_to_send>0) + { + int len=m_send_buffer.GetSize()-m_send_pos; + if (len > m_send_len) len=m_send_len; + if (len > bytes_allowed_to_send) len=bytes_allowed_to_send; + if (len > 0) + { + int res=(int)::send(m_socket,(char*)m_send_buffer.Get()+m_send_pos,len,0); + if (res==-1 && JNL_ERRNO != JNL_EWOULDBLOCK) + { +// m_state=STATE_CLOSED; +// return; + } + if (res>0) + { + bytes_allowed_to_send-=res; + if (bytes_sent) *bytes_sent+=res; + m_send_pos+=res; + m_send_len-=res; + } + } + if (m_send_pos>=m_send_buffer.GetSize()) + { + m_send_pos=0; + if (m_send_len>0) + { + len=m_send_buffer.GetSize()-m_send_pos; + if (len > m_send_len) len=m_send_len; + if (len > bytes_allowed_to_send) len=bytes_allowed_to_send; + int res=(int)::send(m_socket,(char*)m_send_buffer.Get()+m_send_pos,len,0); + if (res==-1 && JNL_ERRNO != JNL_EWOULDBLOCK) + { +// m_state=STATE_CLOSED; + } + if (res>0) + { + bytes_allowed_to_send-=res; + if (bytes_sent) *bytes_sent+=res; + m_send_pos+=res; + m_send_len-=res; + } + } + } + } + if (m_recv_len m_recv_buffer.GetSize()-m_recv_len) len=m_recv_buffer.GetSize()-m_recv_len; + if (len > bytes_allowed_to_recv) len=bytes_allowed_to_recv; + if (len>0) + { + int res=(int)::recv(m_socket,(char*)m_recv_buffer.Get()+m_recv_pos,len,0); + if (res == 0 || (res < 0 && JNL_ERRNO != JNL_EWOULDBLOCK)) + { + m_state=STATE_CLOSED; + break; + } + if (res > 0) + { + bytes_allowed_to_recv-=res; + if (bytes_rcvd) *bytes_rcvd+=res; + m_recv_pos+=res; + m_recv_len+=res; + } + } + if (m_recv_pos >= m_recv_buffer.GetSize()) + { + m_recv_pos=0; + if (m_recv_len < m_recv_buffer.GetSize()) + { + len=m_recv_buffer.GetSize()-m_recv_len; + if (len > bytes_allowed_to_recv) len=bytes_allowed_to_recv; + if (len > 0) + { + int res=(int)::recv(m_socket,(char*)m_recv_buffer.Get()+m_recv_pos,len,0); + if (res == 0 || (res < 0 && JNL_ERRNO != JNL_EWOULDBLOCK)) + { + m_state=STATE_CLOSED; + break; + } + if (res > 0) + { + bytes_allowed_to_recv-=res; + if (bytes_rcvd) *bytes_rcvd+=res; + m_recv_pos+=res; + m_recv_len+=res; + } + } + } + } + } + if (m_state == STATE_CLOSING) + { + if (m_send_len < 1) m_state = STATE_CLOSED; + } + break; + default: break; + } +} + +void JNL_Connection::close(int quick) +{ + if (quick || m_state == STATE_RESOLVING || m_state == STATE_CONNECTING) + { + m_state=STATE_CLOSED; + if (m_socket != INVALID_SOCKET) + { + ::shutdown(m_socket, SHUT_RDWR); + ::closesocket(m_socket); + } + m_socket=INVALID_SOCKET; + m_remote_port=0; + m_recv_len=m_recv_pos=0; + m_send_len=m_send_pos=0; + m_host[0]=0; + memset(m_saddr,0,sizeof(struct sockaddr_in)); + } + else + { + if (m_state == STATE_CONNECTED) m_state=STATE_CLOSING; + } +} + +int JNL_Connection::send_bytes_in_queue(void) +{ + return m_send_len; +} + +int JNL_Connection::send_bytes_available(void) +{ + return m_send_buffer.GetSize()-m_send_len; +} + +int JNL_Connection::send(const void *_data, int length) +{ + const char *data = static_cast(_data); + if (length > send_bytes_available()) + { + return -1; + } + + int write_pos=m_send_pos+m_send_len; + if (write_pos >= m_send_buffer.GetSize()) + { + write_pos-=m_send_buffer.GetSize(); + } + + int len=m_send_buffer.GetSize()-write_pos; + if (len > length) + { + len=length; + } + + memcpy(m_send_buffer.Get()+write_pos,data,len); + if (length > len) + { + memcpy(m_send_buffer.Get(),data+len,length-len); + } + m_send_len+=length; + return 0; +} + +int JNL_Connection::send_string(const char *line) +{ + return send(line,(int)strlen(line)); +} + +int JNL_Connection::recv_bytes_available(void) +{ + return m_recv_len; +} + +int JNL_Connection::peek_bytes(void *data, int maxlength) +{ + if (maxlength > m_recv_len) + { + maxlength=m_recv_len; + } + int read_pos=m_recv_pos-m_recv_len; + if (read_pos < 0) + { + read_pos += m_recv_buffer.GetSize(); + } + int len=m_recv_buffer.GetSize()-read_pos; + if (len > maxlength) + { + len=maxlength; + } + if (data != NULL) { + memcpy(data,m_recv_buffer.Get()+read_pos,len); + if (len < maxlength) + { + memcpy((char*)data+len,m_recv_buffer.Get(),maxlength-len); + } + } + + return maxlength; +} + +int JNL_Connection::recv_bytes(void *_data, int maxlength) +{ + char *data = static_cast(_data); + + int ml=peek_bytes(data,maxlength); + m_recv_len-=ml; + return ml; +} + +int JNL_Connection::getbfromrecv(int pos, int remove) +{ + int read_pos=m_recv_pos-m_recv_len + pos; + if (pos < 0 || pos > m_recv_len) return -1; + if (read_pos < 0) + { + read_pos += m_recv_buffer.GetSize(); + } + if (read_pos >= m_recv_buffer.GetSize()) + { + read_pos-=m_recv_buffer.GetSize(); + } + if (remove) m_recv_len--; + return m_recv_buffer.Get()[read_pos]; +} + +int JNL_Connection::recv_lines_available(void) +{ + int l=recv_bytes_available(); + int lcount=0; + int lastch=0; + int pos; + for (pos=0; pos < l; pos ++) + { + int t=getbfromrecv(pos,0); + if (t == -1) return lcount; + if ((t=='\r' || t=='\n') &&( + (lastch != '\r' && lastch != '\n') || lastch==t + )) lcount++; + lastch=t; + } + return lcount; +} + +int JNL_Connection::recv_get_linelen() +{ + int l = 0; + while (l < m_recv_len) + { + int t=getbfromrecv(l,0); + if (t<0) return 0; + + if (t == '\r' || t == '\n') + { + int r=getbfromrecv(++l,0); + if ((r == '\r' || r == '\n') && r != t) l++; + return l; + } + l++; + } + return 0; +} + +int JNL_Connection::recv_line(char *line, int maxlength) +{ + maxlength--; // room for trailing NUL + if (maxlength > m_recv_len) maxlength=m_recv_len; + while (maxlength-- > 0) + { + int t=getbfromrecv(0,1); + if (t == -1) + { + *line=0; + return 0; + } + if (t == '\r' || t == '\n') + { + int r=getbfromrecv(0,0); + if ((r == '\r' || r == '\n') && r != t) getbfromrecv(0,1); + *line=0; + return 0; + } + *line++=(char)t; + } + *line=0; + return 1; +} + +void JNL_Connection::set_interface(int useInterface) // call before connect if needed +{ + m_localinterfacereq = useInterface; +} + + +unsigned int JNL_Connection::get_interface(void) +{ + if (m_socket==INVALID_SOCKET) return 0; + struct sockaddr_in sin; + memset(&sin,0,sizeof(sin)); + socklen_t len=16; + if (::getsockname(m_socket,(struct sockaddr *)&sin,&len)) return 0; + return (unsigned int) sin.sin_addr.s_addr; +} + +unsigned int JNL_Connection::get_remote() +{ + return m_saddr->sin_addr.s_addr; +} + +short JNL_Connection::get_remote_port() +{ + return m_remote_port; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/connection.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/connection.h new file mode 100644 index 0000000..eab9ae2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/connection.h @@ -0,0 +1,193 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: connection.h - JNL TCP connection interface +** License: see jnetlib.h +** +** Usage: +** 1. Create a JNL_Connection object, optionally specifying a JNL_IAsyncDNS +** object to use (or NULL for none, or JNL_CONNECTION_AUTODNS for auto), +** and the send and receive buffer sizes. +** 2. Call connect() to have it connect to a host/port (the hostname will be +** resolved if possible). +** 3. call run() with the maximum send/recv amounts, and optionally parameters +** so you can tell how much has been send/received. You want to do this a lot, while: +** 4. check get_state() to check the state of the connection. The states are: +** JNL_Connection::STATE_ERROR +** - an error has occured on the connection. the connection has closed, +** and you can no longer write to the socket (there still might be +** data in the receive buffer - use recv_bytes_available()). +** JNL_Connection::STATE_NOCONNECTION +** - no connection has been made yet. call connect() already! :) +** JNL_Connection::STATE_RESOLVING +** - the connection is still waiting for a JNL_AsycnDNS to resolve the +** host. +** JNL_Connection::STATE_CONNECTING +** - the asynchronous call to connect() is still running. +** JNL_Connection::STATE_CONNECTED +** - the connection has connected, all is well. +** JNL_Connection::STATE_CLOSING +** - the connection is closing. This happens after a call to close, +** without the quick parameter set. This means that the connection +** will close once the data in the send buffer is sent (data could +** still be being received when it would be closed). After it is +** closed, the state will transition to: +** JNL_Connection::STATE_CLOSED +** - the connection has closed, generally without error. There still +** might be data in the receieve buffer, use recv_bytes_available(). +** 5. Use send() and send_string() to send data. You can use +** send_bytes_in_queue() to see how much has yet to go out, or +** send_bytes_available() to see how much you can write. If you use send() +** or send_string() and not enough room is available, both functions will +** return error ( < 0) +** 6. Use recv() and recv_line() to get data. If you want to see how much data +** there is, use recv_bytes_available() and recv_lines_available(). If you +** call recv() and not enough data is available, recv() will return how much +** data was actually read. See comments at the function defs. +** +** 7. To close, call close(1) for a quick close, or close() for a close that will +** make the socket close after sending all the data sent. +** +** 8. delete ye' ol' object. +*/ + +#ifndef _CONNECTION_H_ +#define _CONNECTION_H_ + +#include "asyncdns.h" +#include "netinc.h" +#include "../heapbuf.h" + +#define JNL_CONNECTION_AUTODNS ((JNL_IAsyncDNS*)-1) + +struct sockaddr_in; + +#ifndef JNL_NO_DEFINE_INTERFACES +class JNL_IConnection +{ + public: + virtual ~JNL_IConnection() { } + virtual void connect(const char *hostname, int port)=0; + virtual void connect(SOCKET sock, struct sockaddr_in *loc=NULL)=0; // used by the listen object, usually not needed by users. + + virtual void run(int max_send_bytes=-1, int max_recv_bytes=-1, int *bytes_sent=NULL, int *bytes_rcvd=NULL)=0; + virtual int get_state()=0; + virtual const char *get_errstr()=0; + + virtual void close(int quick=0)=0; + virtual void flush_send(void)=0; + + virtual int send_bytes_in_queue(void)=0; + virtual int send_bytes_available(void)=0; + virtual int send(const void *data, int length)=0; // returns -1 if not enough room + virtual int send_bytes(const void *data, int length)=0; + virtual int send_string(const char *line)=0; // returns -1 if not enough room + + virtual int recv_bytes_available(void)=0; + virtual int recv_bytes(void *data, int maxlength)=0; // returns actual bytes read + virtual int recv_lines_available(void)=0; + virtual int recv_line(char *line, int maxlength)=0; // returns 0 if the line was terminated with a \r or \n, 1 if not. + // (i.e. if you specify maxlength=10, and the line is 12 bytes long + // it will return 1. or if there is no \r or \n and that's all the data + // the connection has.) + virtual int recv_get_linelen()=0; // length in bytes for current line (including \r and/or \n), or 0 if no newline in buffer + virtual int peek_bytes(void *data, int maxlength)=0; // returns bytes peeked + + virtual unsigned int get_interface(void)=0; // this returns the interface the connection is on + virtual unsigned int get_remote(void)=0; // remote host ip. + virtual short get_remote_port(void)=0; // this returns the remote port of connection + + virtual void set_interface(int useInterface)=0; // call before connect if needed + virtual SOCKET get_socket() const = 0; + }; + + #define JNL_Connection_PARENTDEF : public JNL_IConnection +#else + #define JNL_IConnection JNL_Connection + #define JNL_Connection_PARENTDEF +#endif + +#ifndef JNL_NO_IMPLEMENTATION + +class JNL_Connection JNL_Connection_PARENTDEF +{ + public: + typedef enum + { + STATE_ERROR, + STATE_NOCONNECTION, + STATE_RESOLVING, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_CLOSING, + STATE_CLOSED + } state; + + JNL_Connection(JNL_IAsyncDNS *dns=JNL_CONNECTION_AUTODNS, int sendbufsize=8192, int recvbufsize=8192); + ~JNL_Connection(); + + void connect(const char *hostname, int port); + void connect(SOCKET sock, struct sockaddr_in *loc=NULL); // used by the listen object, usually not needed by users. + + void run(int max_send_bytes=-1, int max_recv_bytes=-1, int *bytes_sent=NULL, int *bytes_rcvd=NULL); + int get_state() { return m_state; } + const char *get_errstr() { return m_errorstr; } + + void close(int quick=0); + void flush_send(void) { m_send_len=m_send_pos=0; } + + int send_bytes_in_queue(void); + int send_bytes_available(void); + int send(const void *data, int length); // returns -1 if not enough room + inline int send_bytes(const void *data, int length) { return send(data, length); } + int send_string(const char *line); // returns -1 if not enough room + + + int recv_bytes_available(void); + int recv_bytes(void *data, int maxlength); // returns actual bytes read + int recv_lines_available(void); + int recv_line(char *line, int maxlength); // returns 0 if the line was terminated with a \r or \n, 1 if not. + // (i.e. if you specify maxlength=10, and the line is 12 bytes long + // it will return 1. or if there is no \r or \n and that's all the data + // the connection has.) + int recv_get_linelen(); // length in bytes for current line (including \r and/or \n), or 0 if no newline in buffer + int peek_bytes(void *data, int maxlength); // returns bytes peeked + + unsigned int get_interface(void); // this returns the interface the connection is on + unsigned int get_remote(void); // remote host ip. + short get_remote_port(void); // this returns the remote port of connection + + void set_interface(int useInterface); // call before connect if needed + + SOCKET get_socket() const { return m_socket; } + + protected: + SOCKET m_socket; + short m_remote_port; + WDL_TypedBuf m_recv_buffer; + WDL_TypedBuf m_send_buffer; + + int m_recv_pos; + int m_recv_len; + int m_send_pos; + int m_send_len; + + int m_localinterfacereq; + struct sockaddr_in *m_saddr; + char m_host[256]; + + JNL_IAsyncDNS *m_dns; + int m_dns_owned; + + state m_state; + const char *m_errorstr; + + int getbfromrecv(int pos, int remove); // used by recv_line* + +}; + +#endif + +#endif // _Connection_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpget.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpget.cpp new file mode 100644 index 0000000..8ad1c29 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpget.cpp @@ -0,0 +1,446 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: httpget.cpp - JNL HTTP GET implementation +** License: see jnetlib.h +*/ + +#include "netinc.h" +#include "util.h" +#include "httpget.h" + + +JNL_HTTPGet::JNL_HTTPGet(JNL_IAsyncDNS *dns, int recvbufsize, char *proxy) +{ + m_recvbufsize=recvbufsize; + m_dns=dns; + m_con=NULL; + m_http_proxylpinfo=0; + m_http_proxyhost=0; + m_http_proxyport=0; + if (proxy && *proxy) + { + char *p=(char*)malloc(strlen(proxy)+1); + if (p) + { + char *r=NULL; + strcpy(p,proxy); + do_parse_url(p,&m_http_proxyhost,&m_http_proxyport,&r,&m_http_proxylpinfo); + free(r); + free(p); + } + } + m_sendheaders=NULL; + reinit(); +} + +void JNL_HTTPGet::reinit() +{ + m_errstr=0; + m_recvheaders=NULL; + m_recvheaders_size=0; + m_http_state=0; + m_http_port=0; + m_http_url=0; + m_reply=0; + m_http_host=m_http_lpinfo=m_http_request=NULL; +} + +void JNL_HTTPGet::deinit() +{ + delete m_con; m_con = NULL; + free(m_recvheaders); + + free(m_http_url); + free(m_http_host); + free(m_http_lpinfo); + free(m_http_request); + free(m_errstr); + free(m_reply); + reinit(); +} + +JNL_HTTPGet::~JNL_HTTPGet() +{ + deinit(); + free(m_sendheaders); + free(m_http_proxylpinfo); + free(m_http_proxyhost); + +} + + +void JNL_HTTPGet::addheader(const char *header) +{ + if (strstr(header,"\r") || strstr(header,"\n")) return; + if (!m_sendheaders) + { + m_sendheaders=(char*)malloc(strlen(header)+3); + if (m_sendheaders) + { + strcpy(m_sendheaders,header); + strcat(m_sendheaders,"\r\n"); + } + } + else + { + char *t=(char*)malloc(strlen(header)+strlen(m_sendheaders)+1+2); + if (t) + { + strcpy(t,m_sendheaders); + strcat(t,header); + strcat(t,"\r\n"); + free(m_sendheaders); + m_sendheaders=t; + } + } +} + +void JNL_HTTPGet::do_encode_mimestr(char *in, char *out) +{ + char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int shift = 0; + int accum = 0; + + while (*in) + { + if (*in) + { + accum <<= 8; + shift += 8; + accum |= *in++; + } + while ( shift >= 6 ) + { + shift -= 6; + *out++ = alphabet[(accum >> shift) & 0x3F]; + } + } + if (shift == 4) + { + *out++ = alphabet[(accum & 0xF)<<2]; + *out++='='; + } + else if (shift == 2) + { + *out++ = alphabet[(accum & 0x3)<<4]; + *out++='='; + *out++='='; + } + + *out++=0; +} + + +void JNL_HTTPGet::connect(const char *url, int ver, const char *requestmethod) +{ + deinit(); + m_http_url=(char*)malloc(strlen(url)+1); + strcpy(m_http_url,url); + do_parse_url(m_http_url,&m_http_host,&m_http_port,&m_http_request, &m_http_lpinfo); + strcpy(m_http_url,url); + if (!m_http_host || !m_http_host[0] || !m_http_port) + { + m_http_state=-1; + seterrstr("invalid URL"); + return; + } + + size_t sendbufferlen=0; + + if (!m_http_proxyhost || !m_http_proxyhost[0]) + { + sendbufferlen += strlen(requestmethod)+1 /* GET */ + strlen(m_http_request) + 9 /* HTTP/1.0 */ + 2; + } + else + { + sendbufferlen += strlen(requestmethod)+1 /* GET */ + strlen(m_http_url) + 9 /* HTTP/1.0 */ + 2; + if (m_http_proxylpinfo&&m_http_proxylpinfo[0]) + { + sendbufferlen+=58+strlen(m_http_proxylpinfo)*2; // being safe here + } + } + sendbufferlen += 5 /* Host: */ + strlen(m_http_host) + 2; + + if (m_http_lpinfo&&m_http_lpinfo[0]) + { + sendbufferlen+=46+strlen(m_http_lpinfo)*2; // being safe here + } + + if (m_sendheaders) sendbufferlen+=strlen(m_sendheaders); + + char *str=(char*)malloc(sendbufferlen+1024); + if (!str) + { + seterrstr("error allocating memory"); + m_http_state=-1; + } + + if (!m_http_proxyhost || !m_http_proxyhost[0]) + { + sprintf(str,"%s %s HTTP/1.%d\r\n",requestmethod,m_http_request,ver%10); + } + else + { + sprintf(str,"%s %s HTTP/1.%d\r\n",requestmethod, m_http_url,ver%10); + } + + sprintf(str+strlen(str),"Host:%s\r\n",m_http_host); + + if (m_http_lpinfo&&m_http_lpinfo[0]) + { + strcat(str,"Authorization: Basic "); + do_encode_mimestr(m_http_lpinfo,str+strlen(str)); + strcat(str,"\r\n"); + } + if (m_http_proxylpinfo&&m_http_proxylpinfo[0]) + { + strcat(str,"Proxy-Authorization: Basic "); + do_encode_mimestr(m_http_proxylpinfo,str+strlen(str)); + strcat(str,"\r\n"); + } + + if (m_sendheaders) strcat(str,m_sendheaders); + strcat(str,"\r\n"); + + int a=m_recvbufsize; + if (a < 4096) a=4096; + m_con=new JNL_Connection(m_dns,(int)strlen(str)+4,a); + if (m_con) + { + if (!m_http_proxyhost || !m_http_proxyhost[0]) + { + m_con->connect(m_http_host,m_http_port); + } + else + { + m_con->connect(m_http_proxyhost,m_http_proxyport); + } + m_con->send_string(str); + } + else + { + m_http_state=-1; + seterrstr("could not create connection object"); + } + free(str); + +} + +void JNL_HTTPGet::do_parse_url(char *url, char **host, int *port, char **req, char **lp) +{ + char *p,*np; + free(*host); *host=0; + free(*req); *req=0; + free(*lp); *lp=0; + + if (strstr(url,"://")) np=p=strstr(url,"://")+3; + else np=p=url; + while (*np != '/' && *np) np++; + if (*np) + { + *req=(char*)malloc(strlen(np)+1); + if (*req) strcpy(*req,np); + *np++=0; + } + else + { + *req=(char*)malloc(2); + if (*req) strcpy(*req,"/"); + } + + np=p; + while (*np != '@' && *np) np++; + if (*np) + { + *np++=0; + *lp=(char*)malloc(strlen(p)+1); + if (*lp) strcpy(*lp,p); + p=np; + } + else + { + *lp=(char*)malloc(1); + if (*lp) strcpy(*lp,""); + } + np=p; + while (*np != ':' && *np) np++; + if (*np) + { + *np++=0; + *port=atoi(np); + } else *port=80; + *host=(char*)malloc(strlen(p)+1); + if (*host) strcpy(*host,p); +} + + +const char *JNL_HTTPGet::getallheaders() +{ // double null terminated, null delimited list + if (m_recvheaders) return m_recvheaders; + else return "\0\0"; +} + +const char *JNL_HTTPGet::getheader(const char *headername) +{ + if (!headername || !m_recvheaders) return NULL; + + size_t headername_len = strlen(headername); + if (headername_len<1) return NULL; + + if (headername[headername_len - 1] == ':') headername_len--; + + const char *p=m_recvheaders; + while (*p) + { + if (!strnicmp(headername,p,headername_len) && p[headername_len] == ':') + { + p += headername_len + 1; + while (*p == ' ') p++; + return p; + } + p+=strlen(p)+1; + } + return NULL; +} + +int JNL_HTTPGet::run() +{ + int cnt=0; + if (m_http_state==-1||!m_con) return -1; // error + + +run_again: + m_con->run(); + + if (m_con->get_state()==JNL_Connection::STATE_ERROR) + { + seterrstr(m_con->get_errstr()); + return -1; + } + if (m_con->get_state()==JNL_Connection::STATE_CLOSED) return 1; + + if (m_http_state==0) // connected, waiting for reply + { + if (m_con->recv_lines_available()>0) + { + char buf[4096]; + m_con->recv_line(buf,4095); + buf[4095]=0; + m_reply=(char*)malloc(strlen(buf)+1); + strcpy(m_reply,buf); + + int code=getreplycode(); + if (code == 200 || code==206) m_http_state=2; // proceed to read headers normally + else if (code == 301 || code==302) + { + m_http_state=1; // redirect city + } + else + { + seterrstr(buf); + m_http_state=-1; + return -1; + } + cnt=0; + } + else if (!cnt++) goto run_again; + } + if (m_http_state == 1) // redirect + { + while (m_con->recv_lines_available() > 0) + { + char buf[4096]; + m_con->recv_line(buf,4096); + if (!buf[0]) + { + m_http_state=-1; + return -1; + } + if (!strnicmp(buf,"Location:",9)) + { + const char *p=buf+9; while (*p== ' ') p++; + if (*p) + { + connect(p); + return 0; + } + } + } + } + if (m_http_state==2) + { + if (!cnt++ && m_con->recv_lines_available() < 1) goto run_again; + while (m_con->recv_lines_available() > 0) + { + char buf[4096]; + m_con->recv_line(buf,4096); + if (!buf[0]) { m_http_state=3; break; } + if (!m_recvheaders) + { + m_recvheaders_size=(int)strlen(buf)+1; + m_recvheaders=(char*)malloc(m_recvheaders_size+1); + if (m_recvheaders) + { + strcpy(m_recvheaders,buf); + m_recvheaders[m_recvheaders_size]=0; + } + } + else + { + int oldsize=m_recvheaders_size; + m_recvheaders_size+=(int)strlen(buf)+1; + char *n=(char*)malloc(m_recvheaders_size+1); + if (n) + { + memcpy(n,m_recvheaders,oldsize); + strcpy(n+oldsize,buf); + n[m_recvheaders_size]=0; + free(m_recvheaders); + m_recvheaders=n; + } + } + } + } + if (m_http_state==3) + { + } + return 0; +} + +int JNL_HTTPGet::get_status() // returns 0 if connecting, 1 if reading headers, + // 2 if reading content, -1 if error. +{ + if (m_http_state < 0) return -1; + if (m_http_state < 2) return 0; + if (m_http_state == 2) return 1; + if (m_http_state == 3) return 2; + return -1; +} + +int JNL_HTTPGet::getreplycode()// returns 0 if none yet, otherwise returns http reply code. +{ + if (!m_reply) return 0; + char *p=m_reply; + while (*p && *p != ' ') p++; // skip over HTTP/x.x + if (!*p) return 0; + return atoi(++p); +} + +int JNL_HTTPGet::bytes_available() +{ + if (m_con && m_http_state==3) return m_con->recv_bytes_available(); + return 0; +} +int JNL_HTTPGet::get_bytes(char *buf, int len) +{ + if (m_con && m_http_state==3) return m_con->recv_bytes(buf,len); + return 0; +} +int JNL_HTTPGet::peek_bytes(char *buf, int len) +{ + if (m_con && m_http_state==3) return m_con->peek_bytes(buf,len); + return 0; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpget.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpget.h new file mode 100644 index 0000000..43b565d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpget.h @@ -0,0 +1,152 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: httpget.h - JNL interface for doing HTTP GETs. +** License: see jnetlib.h +** +** Usage: +** 1. Create a JNL_HTTPGet object, optionally specifying a JNL_AsyncDNS +** object to use (or NULL for none, or JNL_CONNECTION_AUTODNS for auto), +** and the receive buffer size, and a string specifying proxy (or NULL +** for none). See note on proxy string below. +** 2. call addheader() to add whatever headers you want. It is recommended to +** add at least the following two: +** addheader("User-Agent:MyApp (Mozilla)"); +*/// addheader("Accept:*/*"); +/* ( the comment weirdness is there so I Can do the star-slash :) +** 3. Call connect() with the URL you wish to GET (see URL string note below) +** 4. Call run() once in a while, checking to see if it returns -1 +** (if it does return -1, call geterrorstr() to see what the error is). +** (if it returns 1, no big deal, the connection has closed). +** 5. While you're at it, you can call bytes_available() to see if any data +** from the http stream is available, or getheader() to see if any headers +** are available, or getreply() to see the HTTP reply, or getallheaders() +** to get a double null terminated, null delimited list of headers returned. +** 6. If you want to read from the stream, call get_bytes (which returns how much +** was actually read). +** 7. content_length() is a helper function that uses getheader() to check the +** content-length header. +** 8. Delete ye' ol' object when done. +** +** Proxy String: +** should be in the format of host:port, or user@host:port, or +** user:password@host:port. if port is not specified, 80 is assumed. +** URL String: +** should be in the format of http://user:pass@host:port/requestwhatever +** note that user, pass, port, and /requestwhatever are all optional :) +** note that also, http:// is really not important. if you do poo:// +** or even leave out the http:// altogether, it will still work. +*/ + +#ifndef _HTTPGET_H_ +#define _HTTPGET_H_ + +#include "connection.h" + +#ifndef JNL_NO_DEFINE_INTERFACES + class JNL_IHTTPGet + { + public: + + virtual ~JNL_IHTTPGet() { } + + virtual void addheader(const char *header)=0; + + virtual void connect(const char *url, int ver=0, const char *requestmethod="GET")=0; + + virtual int run()=0; // returns: 0 if all is OK. -1 if error (call geterrorstr()). 1 if connection closed. + + virtual int get_status()=0; // returns 0 if connecting, 1 if reading headers, + // 2 if reading content, -1 if error. + + virtual const char *getallheaders()=0; // double null terminated, null delimited list + virtual const char *getheader(const char *headername)=0; + virtual const char *getreply()=0; + virtual int getreplycode()=0; // returns 0 if none yet, otherwise returns http reply code. + + virtual const char *geterrorstr()=0; + + virtual int bytes_available()=0; + virtual int get_bytes(char *buf, int len)=0; + virtual int peek_bytes(char *buf, int len)=0; + + virtual int content_length()=0; + + virtual JNL_IConnection *get_con()=0; + }; + #define JNL_HTTPGet_PARENTDEF : public JNL_IHTTPGet +#else + #define JNL_IHTTPGet JNL_HTTPGet + #define JNL_HTTPGet_PARENTDEF +#endif + +#ifndef JNL_NO_IMPLEMENTATION + +class JNL_HTTPGet JNL_HTTPGet_PARENTDEF +{ + public: + JNL_HTTPGet(JNL_IAsyncDNS *dns=JNL_CONNECTION_AUTODNS, int recvbufsize=16384, char *proxy=NULL); + ~JNL_HTTPGet(); + + void addheader(const char *header); + + void connect(const char *url, int ver=0, const char *requestmethod="GET"); + + int run(); // returns: 0 if all is OK. -1 if error (call geterrorstr()). 1 if connection closed. + + int get_status(); // returns 0 if connecting, 1 if reading headers, + // 2 if reading content, -1 if error. + + const char *getallheaders(); // double null terminated, null delimited list + const char *getheader(const char *headername); + const char *getreply() { return m_reply; } + int getreplycode(); // returns 0 if none yet, otherwise returns http reply code. + + const char *geterrorstr() { return m_errstr;} + + int bytes_available(); + int get_bytes(char *buf, int len); + int peek_bytes(char *buf, int len); + + int content_length() { const char *p=getheader("content-length"); if (p) return atoi(p); return 0; } + + JNL_IConnection *get_con() { return m_con; } + + + + static void do_parse_url(char *url, char **host, int *port, char **req, char **lp); // url gets thrashed, and host/req/lp are freed/allocated + static void do_encode_mimestr(char *in, char *out); + + protected: + void reinit(); + void deinit(); + void seterrstr(const char *str) { if (m_errstr) free(m_errstr); m_errstr=(char*)malloc(strlen(str)+1); strcpy(m_errstr,str); } + + JNL_IAsyncDNS *m_dns; + JNL_IConnection *m_con; + int m_recvbufsize; + + int m_http_state; + + int m_http_port; + char *m_http_url; + char *m_http_host; + char *m_http_lpinfo; + char *m_http_request; + + char *m_http_proxylpinfo; + char *m_http_proxyhost; + int m_http_proxyport; + + char *m_sendheaders; + char *m_recvheaders; + int m_recvheaders_size; + char *m_reply; + + char *m_errstr; +}; +#endif + +#endif // _HTTPGET_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpserv.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpserv.cpp new file mode 100644 index 0000000..7d5acb3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpserv.cpp @@ -0,0 +1,262 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: httpserv.cpp - JNL HTTP GET/POST serving implementation +** License: see jnetlib.h +** +** This class just manages the http reply/sending, not where the data +** comes from, etc. +*/ + +#include "netinc.h" +#include "util.h" + +#include "httpserv.h" + +/* + States for m_state: + -1 error (connection closed, etc) + 0 not read request yet. + 1 reading headers + 2 headers read, have not sent reply + 3 sent reply + 4 closed +*/ + +JNL_HTTPServ::JNL_HTTPServ(JNL_IConnection *con) +{ + m_usechunk = false; + m_keepalive = true; + m_con=con; + m_state=0; + m_reply_ready=0; +} + +JNL_HTTPServ::~JNL_HTTPServ() +{ + delete m_con; +} + +void JNL_HTTPServ::write_bytes(const char *bytes, int length) +{ + if (m_usechunk) + { + char buf[32]; + sprintf(buf,"%x\r\n",length); + m_con->send_string(buf); + } + m_con->send(bytes,length); + if (m_usechunk) m_con->send_string("\r\n"); +} + +bool JNL_HTTPServ::want_keepalive_reset() +{ + if (m_state >= 2 && m_con && m_con->get_state() == JNL_Connection::STATE_CONNECTED) + { + m_usechunk = false; + m_state = 0; + m_reply_ready = 0; + m_errstr.Set(""); + m_reply_headers.Set(""); + m_reply_string.Set(""); + m_recvheaders.Clear(); + m_recv_request.Resize(0,false); + return true; + } + return false; +} + +int JNL_HTTPServ::run() +{ // returns: < 0 on error, 0 on connection close, 1 if reading request, 2 if reply not sent, 3 if reply sent, sending data. + int cnt=0; +run_again: + m_con->run(); + if (m_con->get_state()==JNL_Connection::STATE_ERROR) + { + seterrstr(m_con->get_errstr()); + return -1; + } + if (m_con->get_state()==JNL_Connection::STATE_CLOSED) return 4; + + if (m_state == 0) + { + int reqlen = m_con->recv_get_linelen(); + if (reqlen>0) + { + if (!m_recv_request.ResizeOK(reqlen+2,false)) + { + seterrstr("malloc fail"); + return -1; + } + + reqlen = m_con->recv_bytes(m_recv_request.Get(),reqlen); + char *buf = m_recv_request.Get() + reqlen; + *buf=0; + while (buf > m_recv_request.Get() && (buf[-1] == '\r' || buf[-1]=='\n')) *--buf=0; + const char *endptr = buf; + while (buf >= m_recv_request.Get() && *buf != ' ') buf--; + + if (buf < m_recv_request.Get() || strncmp(buf+1,"HTTP",4) || strncmp(m_recv_request.Get(),"GET ",4)) + { + seterrstr("malformed HTTP request"); + m_state=-1; + buf=m_recv_request.Get(); + buf[0]=buf[1]=0; + } + else + { + if (endptr[-1]=='0') m_keepalive = false; // old http 1.0 + m_state=1; + cnt=0; + buf[0]=buf[1]=0; + + buf=strstr(m_recv_request.Get(),"?"); + if (buf) + { + *buf++=0; // change &'s into 0s now. + char *t=buf; + int stat=1; + while (*t) + { + if (*t == '&' && !stat) { stat=1; *t=0; } + else stat=0; + t++; + } + } + } + } + else if (!cnt++) goto run_again; + } + if (m_state == 1) + { + if (!cnt++ && m_con->recv_lines_available()<1) goto run_again; + while (m_con->recv_get_linelen()>0) + { + char buf[4096]; + buf[0]=0; + m_con->recv_line(buf,4096); + if (!buf[0]) { m_state=2; break; } + + if (!strnicmp(buf,"Connection:",11)) + { + const char *p=buf+11; + while (*p && strnicmp(p,"close",5)) p++; + if (*p) m_keepalive = false; + } + + if (m_recvheaders.GetSize()) m_recvheaders.Add(NULL,-1); // remove doublenull + m_recvheaders.Add(buf,strlen(buf)+1); + m_recvheaders.Add("",1); + } + } + if (m_state == 2) + { + if (m_reply_ready) + { + // send reply + m_con->send_string((char*)(m_reply_string.GetLength()?m_reply_string.Get():"HTTP/1.1 200 OK")); + m_con->send_string("\r\n"); + if (m_reply_headers.GetLength()) m_con->send_string(m_reply_headers.Get()); + if (m_keepalive) + { + const char *p = m_reply_headers.Get(); + bool had_cl=false,had_con=false; + while (*p && (!had_cl || !had_con)) + { + if (!strnicmp(p,"Content-Length:",15)) had_cl=true; + else if (!strnicmp(p,"Connection:",11)) had_con=true; + + while (*p && *p != '\r' && *p != '\n') p++; + while (*p == '\r' || *p == '\n') p++; + } + if (!had_con) m_con->send_string("Connection: keep-alive\r\n"); + if (!had_cl) + { + m_usechunk = true; + m_con->send_string("Transfer-Encoding: chunked\r\n"); + } + } + m_con->send_string("\r\n"); + m_state=3; + } + } + if (m_state == 3) + { + // nothing. + } + + return m_state; +} + +const char *JNL_HTTPServ::get_request_file() +{ + // file portion of http request + char *t=m_recv_request.Get(); + if (!t) return NULL; + + while (*t != ' ' && *t) t++; + if (!*t) return NULL; + while (*t == ' ') t++; + return t; +} + +const char *JNL_HTTPServ::get_request_parm(const char *parmname) // parameter portion (after ?) +{ + const char *t=m_recv_request.Get(); + if (!t) return NULL; + + while (*t) t++; + t++; + while (*t) + { + while (*t == '&') t++; + if (!strnicmp(t,parmname,strlen(parmname)) && t[strlen(parmname)] == '=') + { + return t+strlen(parmname)+1; + } + t+=strlen(t)+1; + } + return NULL; +} + +const char *JNL_HTTPServ::getheader(const char *headername) +{ + const char *ret=NULL; + if (strlen(headername)<1||!m_recvheaders.Available()) return NULL; + const char *p=m_recvheaders.Get(); + const int hdrlen = (int) strlen(headername); + while (*p) + { + if (!strnicmp(headername,p,hdrlen) && p[hdrlen] == ':') + { + ret=p+hdrlen+1; + while (*ret == ' ') ret++; + break; + } + while (*p) p++; + p++; + } + return ret; +} + +void JNL_HTTPServ::set_reply_string(const char *reply_string) // should be HTTP/1.1 OK or the like +{ + m_reply_string.Set(reply_string); +} + +void JNL_HTTPServ::set_reply_size(int sz) // if set, size will also add keep-alive etc +{ + if (sz>=0) + { + char buf[512]; + sprintf(buf,"Content-length: %d",sz); + set_reply_header(buf); + } +} +void JNL_HTTPServ::set_reply_header(const char *header) // "Connection: close" for example +{ + m_reply_headers.Append(header); + m_reply_headers.Append("\r\n"); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpserv.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpserv.h new file mode 100644 index 0000000..9329478 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/httpserv.h @@ -0,0 +1,118 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: httpserv.h - JNL interface for doing HTTP GET/POST serving. +** License: see jnetlib.h +** This class just manages the http reply/sending, not where the data +** comes from, etc. +** for a mini-web server see webserver.h +*/ + +#ifndef _HTTPSERV_H_ +#define _HTTPSERV_H_ + +#include "connection.h" + +#include "../wdlstring.h" +#include "../queue.h" + +#ifndef JNL_NO_DEFINE_INTERFACES +class JNL_IHTTPServ +{ + public: + + virtual ~JNL_IHTTPServ() { } + + virtual int run()=0; // returns: < 0 on error, 0 on request not read yet, 1 if reading headers, 2 if reply not sent, 3 if reply sent, sending data. 4 on connection closed. + + virtual const char *geterrorstr()=0; + + // use these when state returned by run() is 2 + virtual const char *get_request_file()=0; // file portion of http request + virtual const char *get_request_parm(const char *parmname)=0; // parameter portion (after ?) + virtual const char *getallheaders()=0; + virtual const char *getheader(const char *headername)=0; + + virtual void set_reply_string(const char *reply_string)=0; // should be HTTP/1.1 OK or the like + virtual void set_reply_header(const char *header)=0; // i.e. "content-size: 12345" + virtual void set_reply_size(int sz)=0; // if set, size will also add keep-alive etc + + virtual void send_reply()=0; + + ////////// sending data /////////////// + virtual int bytes_inqueue()=0; + virtual int bytes_cansend()=0; + virtual void write_bytes(const char *bytes, int length)=0; + + virtual void close(int quick)=0; + + virtual JNL_IConnection *get_con()=0; + virtual JNL_IConnection *steal_con()=0; + virtual bool want_keepalive_reset()=0; + + virtual bool canKeepAlive()=0; +}; + #define JNL_HTTPServ_PARENTDEF : public JNL_IHTTPServ +#else + #define JNL_IHTTPServ JNL_HTTPServ + #define JNL_HTTPServ_PARENTDEF +#endif + + +#ifndef JNL_NO_IMPLEMENTATION + +class JNL_HTTPServ JNL_HTTPServ_PARENTDEF +{ + public: + JNL_HTTPServ(JNL_IConnection *con); + ~JNL_HTTPServ(); + + int run(); // returns: < 0 on error, 0 on request not read yet, 1 if reading headers, 2 if reply not sent, 3 if reply sent, sending data. 4 on connection closed. + + const char *geterrorstr() { return m_errstr.Get()[0] ? m_errstr.Get() : NULL; } + + // use these when state returned by run() is 2 + const char *get_request_file(); // file portion of http request + const char *get_request_parm(const char *parmname); // parameter portion (after ?) + const char *getallheaders() { return m_recvheaders.Get(); } // double null terminated, null delimited list + const char *getheader(const char *headername); + + void set_reply_string(const char *reply_string); // should be HTTP/1.1 OK or the like + void set_reply_header(const char *header); // i.e. "content-size: 12345" + void set_reply_size(int sz); // if set, size will also add keep-alive etc + + void send_reply() { m_reply_ready=1; } // send reply, state will advance to 3. + + ////////// sending data /////////////// + int bytes_inqueue() { if (m_state == 3 || m_state == -1 || m_state ==4) return m_con->send_bytes_in_queue(); else return 0; } + int bytes_cansend() { if (m_state == 3) return m_con->send_bytes_available() - (m_usechunk?16:0); else return 0; } + void write_bytes(const char *bytes, int length); + + void close(int quick) { m_con->close(quick); m_state=4; } + + JNL_IConnection *get_con() { return m_con; } + JNL_IConnection *steal_con() { JNL_IConnection *ret= m_con; m_con=0; return ret; } + bool want_keepalive_reset(); + + bool canKeepAlive() { return m_keepalive; } + + protected: + void seterrstr(const char *str) { m_errstr.Set(str); } + + int m_reply_ready; + int m_state; + bool m_keepalive, m_usechunk; + + WDL_FastString m_errstr; + WDL_FastString m_reply_headers; + WDL_FastString m_reply_string; + WDL_TypedQueue m_recvheaders; + WDL_TypedBuf m_recv_request; // either double-null terminated, or may contain parameters after first null. + JNL_IConnection *m_con; +}; + +#endif + +#endif // _HTTPSERV_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/irc_util.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/irc_util.h new file mode 100644 index 0000000..2bec8e1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/irc_util.h @@ -0,0 +1,59 @@ +#ifndef _WDL_JNL_IRC_UTIL_H_ +#define _WDL_JNL_IRC_UTIL_H_ + +#include "netinc.h" + +static void FormatIRCMessage(char *bufout, const char *fmt, ...) // bufout should be 1024 bytes to be safe +{ + va_list arglist; + va_start(arglist, fmt); + #ifdef _WIN32 + int written = _vsnprintf(bufout, 1024-16, fmt, arglist); + #else + int written = vsnprintf(bufout, 1024-16, fmt, arglist); + #endif + if (written < 0) written = 0; + else if (written > 510) written=510; + bufout[written]=0; + va_end(arglist); + + strcat(bufout,"\r\n"); +} + + +static void ParseIRCMessage(char *buf, char **prefix, char *tokens[16], int *tokensvalid, bool *lastHadColon) // destroys buf +{ + if (lastHadColon) *lastHadColon=false; + *tokensvalid=0; + if (prefix) *prefix=NULL; + if (*buf==':') + { + if (prefix) *prefix=buf; + while (*buf && *buf != ' ') buf++; + if (*buf==' ') + { + *buf++=0; + while (*buf== ' ') buf++; + } + } + + while (*buf && *tokensvalid < 16) + { + tokens[(*tokensvalid)++] = buf[0] == ':' ? buf+1 : buf; + if (buf[0] == ':' || *tokensvalid == 16) + { + if (buf[0] == ':' && lastHadColon) *lastHadColon=true; + break; + } + + // skip over parameter + while (*buf && *buf != ' ') buf++; + if (*buf == ' ') + { + *buf++=0; + while (*buf== ' ') buf++; + } + + } +} +#endif \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/jnetlib.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/jnetlib.h new file mode 100644 index 0000000..a159e9e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/jnetlib.h @@ -0,0 +1,47 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2003 Nullsoft, Inc. +** Author: Justin Frankel +** File: jnetlib.h - JNL main include file (not really necessary). +** +** For documentation, look at the following files: +** Generic network initialization: netinc.h +** DNS: asyncdns.h +** TCP connections: connection.h +** HTTP GET connections: httpget.h +** TCP listen: listen.h +** +** license: +** +** 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. +** +*/ + +#ifndef _JNETLIB_H_ +#define _JNETLIB_H_ + +#include "netinc.h" +#include "util.h" + +#include "asyncdns.h" +#include "connection.h" +#include "httpget.h" +#include "httpserv.h" +#include "listen.h" + +#endif//_JNETLIB_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/listen.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/listen.cpp new file mode 100644 index 0000000..683bce2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/listen.cpp @@ -0,0 +1,75 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: listen.cpp - JNL TCP listen implementation +** License: see jnetlib.h +*/ + +#include "netinc.h" +#include "util.h" +#include "listen.h" + +JNL_Listen::JNL_Listen(short port, unsigned int which_interface) +{ + m_port=port; + m_socket = ::socket(AF_INET,SOCK_STREAM,0); + if (m_socket == INVALID_SOCKET) + { + } + else + { + struct sockaddr_in sin; + SET_SOCK_DEFAULTS(m_socket); + SET_SOCK_BLOCK(m_socket,0); + int bflag = 1; + setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&bflag, sizeof(bflag)); + memset((char *) &sin, 0,sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons( (short) port ); + sin.sin_addr.s_addr = which_interface?which_interface:INADDR_ANY; + if (::bind(m_socket,(struct sockaddr *)&sin,sizeof(sin))) + { + shutdown(m_socket, SHUT_RDWR); + closesocket(m_socket); + m_socket=INVALID_SOCKET; + } + else + { + if (::listen(m_socket,8)==-1) + { + shutdown(m_socket, SHUT_RDWR); + closesocket(m_socket); + m_socket=INVALID_SOCKET; + } + } + } +} + +JNL_Listen::~JNL_Listen() +{ + if (m_socket!=INVALID_SOCKET) + { + shutdown(m_socket, SHUT_RDWR); + closesocket(m_socket); + } +} + +JNL_IConnection *JNL_Listen::get_connect(int sendbufsize, int recvbufsize) +{ + if (m_socket == INVALID_SOCKET) + { + return NULL; + } + struct sockaddr_in saddr; + socklen_t length = sizeof(struct sockaddr_in); + SOCKET s = accept(m_socket, (struct sockaddr *) &saddr, &length); + if (s != INVALID_SOCKET) + { + JNL_IConnection *c=new JNL_Connection(NULL,sendbufsize, recvbufsize); + c->connect(s,&saddr); + return c; + } + return NULL; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/listen.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/listen.h new file mode 100644 index 0000000..0301fed --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/listen.h @@ -0,0 +1,62 @@ +/* +** JNetLib +** Copyright (C) 2008 Cockos Inc +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: listen.h - JNL interface for opening a TCP listen +** License: see jnetlib.h +** +** Usage: +** 1. create a JNL_Listen object with the port and (optionally) the interface +** to listen on. +** 2. call get_connect() to get any new connections (optionally specifying what +** buffer sizes the connection should be created with) +** 3. check is_error() to see if an error has occured +** 4. call port() if you forget what port the listener is on. +** +*/ + +#ifndef _LISTEN_H_ +#define _LISTEN_H_ +#include "connection.h" + +#ifndef JNL_NO_DEFINE_INTERFACES + + class JNL_IListen + { + public: + + virtual ~JNL_IListen() { } + + virtual JNL_IConnection *get_connect(int sendbufsize=8192, int recvbufsize=8192)=0; + virtual short port(void)=0; + virtual int is_error(void)=0; + }; + + #define JNL_Listen_PARENTDEF : public JNL_IListen +#else + #define JNL_IListen JNL_Listen + #define JNL_Listen_PARENTDEF +#endif + +#ifndef JNL_NO_IMPLEMENTATION + + +class JNL_Listen JNL_Listen_PARENTDEF +{ + public: + JNL_Listen(short port, unsigned int which_interface=0); + ~JNL_Listen(); + + JNL_IConnection *get_connect(int sendbufsize=8192, int recvbufsize=8192); + short port(void) { return m_port; } + int is_error(void) { return (m_socket == INVALID_SOCKET); } + + protected: + SOCKET m_socket; + short m_port; +}; + +#endif + +#endif //_LISTEN_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/netinc.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/netinc.h new file mode 100644 index 0000000..0fc7d8e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/netinc.h @@ -0,0 +1,90 @@ +/* +** JNetLib +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: netinc.h - network includes and portability defines (used internally) +** License: see jnetlib.h +*/ + +#ifndef _NETINC_H_ +#define _NETINC_H_ + +#ifdef _WIN32 + +#include +#include +#include +#define JNL_ERRNO (WSAGetLastError()) +#define SET_SOCK_BLOCK(s,block) { unsigned long __i=block?0:1; ioctlsocket(s,FIONBIO,&__i); } +#define SET_SOCK_DEFAULTS(s) do { } while (0) +#define JNL_EWOULDBLOCK WSAEWOULDBLOCK +#define JNL_EINPROGRESS WSAEWOULDBLOCK +#define JNL_ENOTCONN WSAENOTCONN + +typedef int socklen_t; + +#else + +#ifndef THREAD_SAFE +#define THREAD_SAFE +#endif +#ifndef _REENTRANT +#define _REENTRANT +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define JNL_ERRNO ((errno)|0) +#define closesocket(s) close(s) +#define SET_SOCK_BLOCK(s,block) { int __flags; if ((__flags = fcntl(s, F_GETFL, 0)) != -1) { if (!block) __flags |= O_NONBLOCK; else __flags &= ~O_NONBLOCK; fcntl(s, F_SETFL, __flags); } } +#ifdef __APPLE__ +#define SET_SOCK_DEFAULTS(s) do { int __flags = 1; setsockopt((s), SOL_SOCKET, SO_NOSIGPIPE, &__flags, sizeof(__flags)); } while (0) +#else +#define SET_SOCK_DEFAULTS(s) do { } while (0) +#endif + +typedef int SOCKET; +#define INVALID_SOCKET (-1) + +#define JNL_EWOULDBLOCK EWOULDBLOCK +#define JNL_EINPROGRESS EINPROGRESS +#define JNL_ENOTCONN ENOTCONN + +#ifndef stricmp +#define stricmp(x,y) strcasecmp(x,y) +#endif +#ifndef strnicmp +#define strnicmp(x,y,z) strncasecmp(x,y,z) +#endif + +#endif // !_WIN32 + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#ifndef INADDR_ANY +#define INADDR_ANY 0 +#endif + +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + +#endif //_NETINC_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/test.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/test.cpp new file mode 100644 index 0000000..619c20f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/test.cpp @@ -0,0 +1,555 @@ +/* +** JNetLib +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: test.cpp - JNL test code +** License: see jnetlib.h +*/ + +#ifdef _WIN32 +#include +#else +#define Sleep(x) usleep((x)*1000) +#endif +#include +#include "jnetlib.h" + + +#define TEST_ASYNCDNS 0 +#define TEST_CONNECTION 0 +#define TEST_LISTEN 0 +#define TEST_TELNET_GATEWAY 0 +#define TEST_HTTPGET 0 +#define TEST_WEBSERVER 1 + +#define TEST_UDP 0 //udp is not done yet tho :) + + + +#if (TEST_WEBSERVER) +#include "webserver.h" + + +class MemPageGenerator : public IPageGenerator +{ + public: + virtual ~MemPageGenerator() { free(m_buf); } + MemPageGenerator(char *buf, int buf_len=-1) { m_buf=buf; if (buf_len >= 0) m_buf_size=buf_len; else m_buf_size=strlen(buf); m_buf_pos=0; } + virtual int GetData(char *buf, int size) // return 0 when done + { + int a=m_buf_size-m_buf_pos; + if (a < size) size=a; + memcpy(buf,m_buf+m_buf_pos,size); + m_buf_pos+=size; + return size; + } + + private: + char *m_buf; + int m_buf_size; + int m_buf_pos; +}; + +class wwwServer : public WebServerBaseClass +{ +public: + wwwServer() { } + virtual IPageGenerator *onConnection(JNL_HTTPServ *serv, int port) + { + serv->set_reply_header("Server:jnetlib_test/0.0"); + if (!strcmp(serv->get_request_file(),"/")) + { + serv->set_reply_string("HTTP/1.1 200 OK"); + serv->set_reply_header("Content-Type:text/html"); + serv->send_reply(); + + return new MemPageGenerator(strdup("Test Web Server v0.0")); + } + else + { + serv->set_reply_string("HTTP/1.1 404 NOT FOUND"); + serv->send_reply(); + return 0; // no data + } + } +}; + + +int main(int argc, char **argv) +{ + JNL::open_socketlib(); + { + wwwServer foo; + foo.addListenPort(8080); + while (1) + { + foo.run(); + Sleep(10); + } + } + JNL::close_socketlib(); + return 0; +} + +#endif + + +#if (TEST_HTTPGET) +int main(int argc, char **argv) +{ + + if (argc != 3) + { + printf("usage: httpget \n"); + exit(0); + } + + JNL_HTTPGet get; + JNL::open_socketlib(); + + get.addheader("User-Agent:PooHead (Mozilla)"); + get.addheader("Accept:*/*"); + get.connect(argv[1]); + + FILE *fp=fopen(argv[2],"wb"); + int headerstate=0; + int has_printed_headers=0; + int has_printed_reply=0; + while (1) + { + int st=get.run(); + if (st<0) + { + printf("HTTPGet error: %s\n",get.geterrorstr()); + break; + } + if (get.get_status()>0) + { + if (!has_printed_reply) + { + has_printed_reply=1; + printf("reply: %s (code:%d)\n",get.getreply(),get.getreplycode()); + } + if (get.get_status()==2) + { + int len; + if (!has_printed_headers) + { + has_printed_headers=1; + printf("headers:\n"); + char *p=get.getallheaders(); + while (p&&*p) + { + printf("%s\n",p); + p+=strlen(p)+1; + } + } + while ((len=get.bytes_available()) > 0) + { + char buf[4096]; + if (len > 4096) len=4096; + len=get.get_bytes(buf,len); + if (len>0)fwrite(buf,len,1,fp); + } + } + } + if (st==1) // 1 means connection closed + { + printf("HTTPGet done!\n"); + break; + } + } + if (fp) fclose(fp); + JNL::close_socketlib(); + return 0; +} + +#endif + + +#if (TEST_TELNET_GATEWAY) + +int main() +{ + JNL_Connection *cons[32]={0,}; + JNL_Connection *outcons[32]={0,}; + char textpos[32][256]; + int n_cons=0; + int states[32]={0,}; + + JNL::open_socketlib(); + JNL_AsyncDNS dns; + JNL_Listen l(23); + while (!l.is_error()) + { + Sleep(30); + if (n_cons<32) + { + JNL_Connection *con=l.get_connect(); + if (con) + { + int x; + for (x = 0; x < 32; x ++) + { + if (!cons[x]) + { + cons[x]=con; + outcons[x]=0; + states[x]=0; + n_cons++; + break; + } + } + } + } + int x; + for (x = 0; x < 32; x ++) + { + if (cons[x]) + { + cons[x]->run(); + if (outcons[x]) outcons[x]->run(); + + if (cons[x]->get_state() == JNL_Connection::STATE_ERROR || cons[x]->get_state()==JNL_Connection::STATE_CLOSED || + (outcons[x] && (cons[x]->get_state() == JNL_Connection::STATE_ERROR || cons[x]->get_state()==JNL_Connection::STATE_CLOSED))) + { + delete cons[x]; + if (outcons[x]) delete outcons[x]; + outcons[x]=0; + cons[x]=0; + states[x]=0; + n_cons--; + } + else + { + if (states[x]==0) + { + cons[x]->send_string("\r\nwelcome "); + states[x]++; + } + if (states[x]==1) + { + char hoststr[256]; + int ret=dns.reverse(cons[x]->get_remote(),hoststr); + if (ret==0) + { + cons[x]->send_string(hoststr); + cons[x]->send_string(". host: "); + states[x]++; + textpos[x][0]=0; + } + if (ret==-1) + { + JNL::addr_to_ipstr(cons[x]->get_remote(),hoststr,256); + cons[x]->send_string(hoststr); + cons[x]->send_string(". host: "); + states[x]++; + textpos[x][0]=0; + } + } + if (states[x]==2) + { + char b; + while (cons[x]->recv_bytes(&b,1) && states[x]==2) + { + if (b == '\r' || b == '\n') + { + if (strlen(textpos[x])) + { + char *p=strstr(textpos[x],":"); + int port=23; + if (p) + { + *p++=0; + if (atoi(p)) port=atoi(p); + } + outcons[x]=new JNL_Connection(&dns); + outcons[x]->connect(textpos[x],port); + + char str[512]; + sprintf(str,"\r\nconnecting to port %d of %s\r\n",port,textpos[x]); + cons[x]->send_string(str); + states[x]++; + } + else states[x]=0; + } + else if (b == '\b') + { + if (textpos[x][0]) + { + textpos[x][strlen(textpos[x])-1]=0; + cons[x]->send_string("\b \b"); + } + } + else + { + textpos[x][strlen(textpos[x])+1]=0; + textpos[x][strlen(textpos[x])]=b; + cons[x]->send(&b,1); + } + } + } + if (states[x]==3) + { + char buf[1024]; + outcons[x]->run(); + int l=cons[x]->recv_bytes(buf,1024); + if (l) outcons[x]->send(buf,l); + l=outcons[x]->recv_bytes(buf,1024); + if (l) cons[x]->send(buf,l); + } + } + } + } + } + JNL::close_socketlib(); + return 0; +} + + +#endif + +#if (TEST_LISTEN) + +int main() +{ + JNL_HTTPServ *cons[32]={0,}; + char *contents[32]={0,}; + int n_cons=0; + + JNL::open_socketlib(); + JNL_AsyncDNS dns; + JNL_Listen l(8000); + while (!l.is_error()) + { + Sleep(100); + if (n_cons<32) + { + JNL_Connection *con=l.get_connect(); + if (con) + { + int x; + for (x = 0; x < 32; x ++) + { + if (!cons[x]) + { + cons[x]=new JNL_HTTPServ(con); + n_cons++; + break; + } + } + } + } + int x; + for (x = 0; x < 32; x ++) + { + if (cons[x]) + { + int r=cons[x]->run(); + if (r == -1 || r == 4) + { + if (r == -1) printf("error:%s\n",cons[x]->geterrorstr()); + delete cons[x]; + cons[x]=0; + free(contents[x]); + contents[x]=0; + n_cons--; + } + if (r == 2) + { + cons[x]->set_reply_string("HTTP/1.1 200 OK"); + cons[x]->set_reply_header("Content-type:text/plain"); + cons[x]->set_reply_header("Server:JNLTest"); + contents[x]=(char*)malloc(32768); + char *poop=cons[x]->get_request_parm("poop"); + sprintf(contents[x],"test, sucka\r\n%s\r\n\r\n",poop?poop:"no poop"); + cons[x]->send_reply(); + } + if (r == 3) + { + if (contents[x] && cons[x]->bytes_cansend()>strlen(contents[x])) + { + cons[x]->write_bytes(contents[x],strlen(contents[x])); + cons[x]->close(0); + free(contents[x]); + contents[x]=0; + } + } + } + } + } + JNL::close_socketlib(); + return 0; +} + +#endif + +#if (TEST_CONNECTION) +int main() +{ + JNL::open_socketlib(); + { + JNL_AsyncDNS dns; + JNL_Connection con(&dns); + con.connect("localhost",80); + FILE *fp=fopen("c:\\hi.raw","wb"); + while (1) + { + con.run(); + if (con.get_state()==JNL_Connection::STATE_ERROR) + { + printf("error %s\n",con.get_errstr()); + } + if (con.get_state()==JNL_Connection::STATE_CLOSED) + { + } + while (con.recv_bytes_available()>0) + { + char buf[1024]; + int a=con.recv_bytes_available(); + if (a > 1024) a=1024; + con.recv_bytes(buf,a); + fwrite(buf,a,1,fp); + } + } + if (fp) fclose(fp); + } + JNL::close_socketlib(); + return 0; +} +#endif + +#if (TEST_ASYNCDNS) +int main() +{ + JNL_AsyncDNS dns; + char *hosts[]= + { + "www.firehose.net", + "gnutella.com", + "207.48.52.200", + "www.slashdot.org", + "www.google.com", + "www.winamp.com", + "www.genekan.com", + }; + char *reverses[]= + { + "64.0.160.98", + "205.188.245.120", + "207.48.52.222", + "207.48.52.200", + }; + int n=0; + int pass=0; + while (n\n"); + printf("mode: 0 for client, 1 for server\n"); + exit(0); + } + int mode=atoi(argv[1]); + + JNL::open_socketlib(); + + switch(mode) + { + case 0: // client mode + { + JNL_AsyncDNS dns; + JNL_UDPConnection con(0,&dns); // 0 chooses a random port + con.setpeer("localhost",80); + printf("Sending message...\n"); + con.send("blah",5); + while (1) + { + con.run(); + if (con.get_state()==JNL_UDPConnection::STATE_ERROR) + { + printf("error %s\n",con.get_errstr()); + } + while (con.recv_bytes_available()>0) + { + char buf[1024]; + int s=min(con.recv_bytes_available(), sizeof(buf)); + con.recv_bytes(buf,s); + printf("received message: %s\n", buf); + } + } + } + break; + case 1: // server (listening) mode + { + JNL_UDPConnection con(80); + printf("Waiting for messages...\n"); + while(1) + { + con.run(); + if (con.get_state()==JNL_UDPConnection::STATE_ERROR) + { + printf("error %s\n",con.get_errstr()); + } + while (con.recv_bytes_available()>0) + { + char buf[1024]; + int s=min(con.recv_bytes_available(), sizeof(buf)); + con.recv_bytes(buf,s); + printf("message received: %s. Replying...\n", buf); + // reply on the addr:port from sender + struct sockaddr from; + con.get_last_recv_msg_addr(&from); + con.setpeer(&from); + con.send("blorp",6); + } + } + } + break; + } + + JNL::close_socketlib(); + return 0; +} +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/testbnc.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/testbnc.cpp new file mode 100644 index 0000000..f7019a7 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/testbnc.cpp @@ -0,0 +1,102 @@ +/* +** JNetLib +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: testbnc.cpp - JNL network bounce test code +** License: see jnetlib.h +*/ + +#ifdef _WIN32 +#include +#else +#define Sleep(x) usleep((x)*1000) +#endif +#include +#include "jnetlib.h" + + +int main(int argc, char *argv[]) +{ + JNL_Connection *cons[32]={0,}; + JNL_Connection *outcons[32]={0,}; + int n_cons=0; + + if (argc != 4 || !atoi(argv[1]) || !atoi(argv[3]) || !argv[2][0]) + { + printf("usage: redir localport host remoteport\n"); + exit(1); + } + + JNL::open_socketlib(); + JNL_AsyncDNS dns; + JNL_Listen l((short)atoi(argv[1])); + printf("running...\n"); + while (!l.is_error()) + { + Sleep(10); + if (n_cons<32) + { + JNL_Connection *con=l.get_connect(); + if (con) + { + int x; + for (x = 0; x < 32; x ++) + { + if (!cons[x]) + { + outcons[x]=new JNL_Connection(); + outcons[x]->connect(argv[2],atoi(argv[3])); + cons[x]=con; + char host[256]; + JNL::addr_to_ipstr(cons[x]->get_remote(),host,sizeof(host)); + n_cons++; + printf("Connection %d (%s) opened (%d).\n",x,host,n_cons); + break; + } + } + } + } + int x; + for (x = 0; x < 32; x ++) + { + if (cons[x]) + { + cons[x]->run(); + outcons[x]->run(); + + int cerr=(cons[x]->get_state() == JNL_Connection::STATE_ERROR || cons[x]->get_state()==JNL_Connection::STATE_CLOSED); + int oerr=(outcons[x]->get_state() == JNL_Connection::STATE_ERROR || outcons[x]->get_state()==JNL_Connection::STATE_CLOSED); + + if ((!outcons[x]->send_bytes_in_queue() && !cons[x]->recv_bytes_available() && cerr) || + (!cons[x]->send_bytes_in_queue() && !outcons[x]->recv_bytes_available() && oerr) || + (cerr && oerr)) + { + char host[256]; + JNL::addr_to_ipstr(cons[x]->get_remote(),host,sizeof(host)); + delete cons[x]; + delete outcons[x]; + outcons[x]=0; + cons[x]=0; + n_cons--; + printf("Connection %d (%s) closed (%d)\n",x,host,n_cons); + } + else + { + char buf[4096]; + int l; + l=outcons[x]->send_bytes_available(); + if (l > 4096) l=4096; + if (l) l=cons[x]->recv_bytes(buf,l); + if (l) outcons[x]->send(buf,l); + + l=cons[x]->send_bytes_available(); + if (l > 4096) l=4096; + if (l) l=outcons[x]->recv_bytes(buf,l); + if (l) cons[x]->send(buf,l); + } + } + } + } + JNL::close_socketlib(); + return 0; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/util.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/util.cpp new file mode 100644 index 0000000..fedd911 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/util.cpp @@ -0,0 +1,38 @@ +/* +** JNetLib +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: util.cpp - JNL implementation of basic network utilities +** License: see jnetlib.h +*/ + +#include "netinc.h" + +#include "util.h" +#include "../wdlcstring.h" + +int JNL::open_socketlib() +{ +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(1, 1), &wsaData)) return 1; +#endif + return 0; +} +void JNL::close_socketlib() +{ +#ifdef _WIN32 + WSACleanup(); +#endif +} +unsigned int JNL::ipstr_to_addr(const char *cp) +{ + return ::inet_addr(cp); +} + +void JNL::addr_to_ipstr(unsigned int addr, char *host, int maxhostlen) +{ + struct in_addr a; a.s_addr=addr; + char *p=::inet_ntoa(a); + lstrcpyn_safe(host,p?p:"",maxhostlen); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/util.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/util.h new file mode 100644 index 0000000..a817970 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/util.h @@ -0,0 +1,39 @@ +/* +** JNetLib +** Copyright (C) 2000-2001 Nullsoft, Inc. +** Author: Justin Frankel +** File: util.h - JNL interface for basic network utilities +** License: see jnetlib.h +** +** routines you may be interested in: +** JNL::open_socketlib(); +** opens the socket library. Call this once before using any network +** code. If you create a new thread, call this again. Only really an +** issue for Win32 support, but use it anyway for portability/ +** +** JNL::close_Socketlib(); +** closes the socketlib. Call this when you're done with the network, +** after all your JNetLib objects have been destroyed. +** +** unsigned int JNL::ipstr_to_addr(const char *cp); +** gives you the integer representation of a ip address in dotted +** decimal form. +** +** JNL::addr_to_ipstr(unsigned int addr, char *host, int maxhostlen); +** gives you the dotted decimal notation of an integer ip address. +** +*/ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +class JNL +{ + public: + static int open_socketlib(); + static void close_socketlib(); + static unsigned int ipstr_to_addr(const char *cp); + static void addr_to_ipstr(unsigned int addr, char *host, int maxhostlen); +}; + +#endif //_UTIL_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/webserver.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/webserver.cpp new file mode 100644 index 0000000..e593d65 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/webserver.cpp @@ -0,0 +1,318 @@ +/* +** JNetLib +** Copyright (C) 2000-2003 Nullsoft, Inc. +** Author: Justin Frankel +** File: webserver.cpp - Generic simple webserver baseclass +** License: see jnetlib.h +** see test.cpp for an example of how to use this class +*/ + +#ifdef _WIN32 +#include +#endif +#include "jnetlib.h" +#include "webserver.h" + + +WebServerBaseClass::~WebServerBaseClass() +{ + m_connections.Empty(true); + m_listeners.Empty(true); +} + +WebServerBaseClass::WebServerBaseClass() +{ + m_listener_rot=0; + m_timeout_s=30; + m_max_con=100; +} + + +void WebServerBaseClass::setMaxConnections(int max_con) +{ + m_max_con=max_con; +} + +void WebServerBaseClass::setRequestTimeout(int timeout_s) +{ + m_timeout_s=timeout_s; +} + +int WebServerBaseClass::addListenPort(int port, unsigned int which_interface) +{ + removeListenPort(port); + + JNL_IListen *p=new JNL_Listen(port,which_interface); + m_listeners.Add(p); + if (p->is_error()) return -1; + return 0; +} + +void WebServerBaseClass::removeListenPort(int port) +{ + int x; + for (x = 0; x < m_listeners.GetSize(); x ++) + { + JNL_IListen *p=m_listeners.Get(x); + if (p->port()==port) + { + m_listeners.Delete(x,true); + break; + } + } +} + +void WebServerBaseClass::removeListenIdx(int idx) +{ + m_listeners.Delete(idx,true); +} + +int WebServerBaseClass::getListenPort(int idx, int *err) +{ + JNL_IListen *p=m_listeners.Get(idx); + if (p) + { + if (err) *err=p->is_error(); + return p->port(); + } + return 0; +} + +void WebServerBaseClass::attachConnection(JNL_IConnection *con, int port) +{ + m_connections.Add(new WS_conInst(con,port)); +} + +void WebServerBaseClass::run(void) +{ + int nl; + if (m_connections.GetSize() < m_max_con && (nl=m_listeners.GetSize())) + { + JNL_IListen *l=m_listeners.Get(m_listener_rot++ % nl); + JNL_IConnection *c=l->get_connect(); + if (c) + { +// char buf[512]; +// sprintf(buf,"got new connection at %.3f",GetTickCount()/1000.0); +// OutputDebugString(buf); + attachConnection(c,l->port()); + } + } + int x; + for (x = 0; x < m_connections.GetSize(); x ++) + { + WS_conInst *ci = m_connections.Get(x); + int rv=0; + for (int y = 0; y < 4 && !(rv=run_connection(ci)); y ++); // keep latency down + + if (rv==-1) + { + if (ci->m_serv.want_keepalive_reset()) + { + time(&ci->m_connect_time); + delete ci->m_pagegen; + ci->m_pagegen=0; + continue; + } + } + + if (rv>0) + { + m_connections.Delete(x--,true); + } + } +} + +int WebServerBaseClass::run_connection(WS_conInst *con) +{ + int s=con->m_serv.run(); + if (s < 0) + { + // m_serv.geterrorstr() + return 1; + } + if (s < 2) + { + // return 1 if we timed out + return time(NULL)-con->m_connect_time > m_timeout_s; + } + if (s < 3) + { + con->m_pagegen=onConnection(&con->m_serv,con->m_port); + return 0; + } + if (s < 4) + { + if (!con->m_pagegen) + { + if (con->m_serv.canKeepAlive()) return -1; + + return !con->m_serv.bytes_inqueue(); + } + char buf[16384]; + int l=con->m_serv.bytes_cansend(); + if (l > 0) + { + if (l > (int)sizeof(buf)) l=(int)sizeof(buf); + l=con->m_pagegen->GetData(buf,l); + if (l < (con->m_pagegen->IsNonBlocking() ? 0 : 1)) // if nonblocking, this is l < 0, otherwise it's l<1 + { + if (con->m_serv.canKeepAlive()) + { + con->m_serv.write_bytes("",0); + return -1; + } + return !con->m_serv.bytes_inqueue(); + } + if (l>0) + con->m_serv.write_bytes(buf,l); + } + return l > 0 ? 0 : -2; // -2 = no more data to send, but all is well + } + if (con->m_serv.canKeepAlive()) return -1; + return 1; // we're done by this point +} + + + +void WebServerBaseClass::url_encode(const char *in, char *out, int max_out) +{ + while (*in && max_out > 4) + { + if ((*in >= 'A' && *in <= 'Z')|| + (*in >= 'a' && *in <= 'z')|| + (*in >= '0' && *in <= '9')|| *in == '.' || *in == '_' || *in == '-') + { + *out++=*in++; + max_out--; + } + else + { + int i=*in++; + *out++ = '%'; + int b=(i>>4)&15; + if (b < 10) *out++='0'+b; + else *out++='A'+b-10; + b=i&15; + if (b < 10) *out++='0'+b; + else *out++='A'+b-10; + max_out-=3; + } + } + *out=0; +} + + +void WebServerBaseClass::url_decode(const char *in, char *out, int maxlen) +{ + while (*in && maxlen>1) + { + if (*in == '+') + { + in++; + *out++=' '; + } + else if (*in == '%' && in[1] != '%' && in[1]) + { + int a=0; + int b=0; + for ( b = 0; b < 2; b ++) + { + int r=in[1+b]; + if (r>='0'&&r<='9') r-='0'; + else if (r>='a'&&r<='f') r-='a'-10; + else if (r>='A'&&r<='F') r-='A'-10; + else break; + a*=16; + a+=r; + } + if (b < 2) *out++=*in++; + else { *out++=a; in += 3;} + } + else *out++=*in++; + maxlen--; + } + *out=0; +} + + + + + +void WebServerBaseClass::base64decode(const char *src, char *dest, int destsize) +{ + int accum=0; + int nbits=0; + while (*src) + { + int x=0; + char c=*src++; + if (c >= 'A' && c <= 'Z') x=c-'A'; + else if (c >= 'a' && c <= 'z') x=c-'a' + 26; + else if (c >= '0' && c <= '9') x=c-'0' + 52; + else if (c == '+') x=62; + else if (c == '/') x=63; + else break; + + accum <<= 6; + accum |= x; + nbits += 6; + + while (nbits >= 8) + { + if (--destsize<=0) break; + nbits-=8; + *dest++ = (char)((accum>>nbits)&0xff); + } + + } + *dest=0; +} + +void WebServerBaseClass::base64encode(const char *in, char *out) +{ + char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int shift = 0; + int accum = 0; + + while (*in) + { + if (*in) + { + accum <<= 8; + shift += 8; + accum |= *in++; + } + while ( shift >= 6 ) + { + shift -= 6; + *out++ = alphabet[(accum >> shift) & 0x3F]; + } + } + if (shift == 4) + { + *out++ = alphabet[(accum & 0xF)<<2]; + *out++='='; + } + else if (shift == 2) + { + *out++ = alphabet[(accum & 0x3)<<4]; + *out++='='; + *out++='='; + } + + *out++=0; +} + +int WebServerBaseClass::parseAuth(const char *auth_header, char *out, int out_len)//returns 0 on unknown auth, 1 on basic +{ + const char *authstr=auth_header; + *out=0; + if (!auth_header || !*auth_header) return 0; + while (*authstr == ' ') authstr++; + if (strnicmp(authstr,"basic ",6)) return 0; + authstr+=6; + while (*authstr == ' ') authstr++; + base64decode(authstr,out,out_len); + return 1; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/webserver.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/webserver.h new file mode 100644 index 0000000..9254c46 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jnetlib/webserver.h @@ -0,0 +1,243 @@ +/* +** JNetLib +** Copyright (C) 2008-2014 Cockos Inc +** Copyright (C) 2003 Nullsoft, Inc. +** Author: Justin Frankel +** File: webserver.h - Generic simple webserver baseclass +** License: see jnetlib.h +** +** You can derive your object from WebServerBaseClass to do simple web serving. Example: + + class wwwServer : public WebServerBaseClass + { + public: + wwwServer() { } + virtual IPageGenerator *onConnection(JNL_HTTPServ *serv, int port) + { + serv->set_reply_header("Server:jnetlib_test/0.0"); + if (!strcmp(serv->get_request_file(),"/")) + { + serv->set_reply_string("HTTP/1.1 200 OK"); + serv->set_reply_header("Content-Type:text/html"); + serv->send_reply(); + + return new MemPageGenerator(strdup("Test Web Server v0.0")); + } + else + { + serv->set_reply_string("HTTP/1.1 404 NOT FOUND"); + serv->send_reply(); + return 0; // no data + } + } + }; + + + wwwServer foo; + foo.addListenPort(8080); + while (1) + { + foo.run(); + Sleep(10); + } + + You will also need to derive from the IPageGenerator interface to provide a data stream, here is an + example of MemPageGenerator: + + class MemPageGenerator : public IPageGenerator + { + public: + virtual ~MemPageGenerator() { free(m_buf); } + MemPageGenerator(char *buf, int buf_len=-1) { m_buf=buf; if (buf_len >= 0) m_buf_size=buf_len; else m_buf_size=strlen(buf); m_buf_pos=0; } + virtual int GetData(char *buf, int size) // return 0 when done + { + int a=m_buf_size-m_buf_pos; + if (a < size) size=a; + memcpy(buf,m_buf+m_buf_pos,size); + m_buf_pos+=size; + return size; + } + + private: + char *m_buf; + int m_buf_size; + int m_buf_pos; + }; + + +** +*/ + + +#ifndef _JNL_WEBSERVER_H_ +#define _JNL_WEBSERVER_H_ + +#include "httpserv.h" +#include "../wdlcstring.h" +#include "../ptrlist.h" + +class IPageGenerator +{ +public: + virtual ~IPageGenerator() { }; + virtual int IsNonBlocking() { return 0; } // override this and return 1 if GetData should be allowed to return 0 + virtual int GetData(char *buf, int size)=0; // return < 0 when done (or 0 if IsNonBlocking() is 1) +}; + + +class WebServerBaseClass +{ +protected: // never create one of these directly, always derive + WebServerBaseClass(); + +public: + virtual ~WebServerBaseClass(); + + // stuff for setting limits/timeouts + void setMaxConnections(int max_con); + void setRequestTimeout(int timeout_s); + + // stuff for setting listener port + int addListenPort(int port, unsigned int which_interface=0); + int getListenPort(int idx, int *err=0); + void removeListenPort(int port); + void removeListenIdx(int idx); + + // call this a lot :) + void run(void); + + // if you want to manually attach a connection, use this: + // you need to specify the port it came in on so the web server can build + // links + void attachConnection(JNL_IConnection *con, int port); + + // derived classes need to override this one =) + virtual IPageGenerator *onConnection(JNL_HTTPServ *serv, int port)=0; + + // stats getting functions + + // these can be used externally, as well as are used by the web server + static void url_encode(const char *in, char *out, int max_out); + static void url_decode(const char *in, char *out, int maxlen); + static void base64decode(const char *src, char *dest, int destsize); + static void base64encode(const char *in, char *out); + + static int parseAuth(const char *auth_header, char *out, int out_len);//returns 0 on unknown auth, 1 on basic + + +protected: + + class WS_conInst + { + public: + WS_conInst(JNL_IConnection *c, int which_port) : m_serv(c), m_pagegen(NULL), m_port(which_port) + { + time(&m_connect_time); + } + ~WS_conInst() + { + delete m_pagegen; + } + + // these will be used by WebServerBaseClass::onConnection yay + JNL_HTTPServ m_serv; + IPageGenerator *m_pagegen; + + int m_port; // port this came in on + time_t m_connect_time; + }; + + int run_connection(WS_conInst *con); + + int m_timeout_s; + int m_max_con; + + JNL_AsyncDNS m_dns; + + WDL_PtrList m_listeners; + WDL_PtrList m_connections; + int m_listener_rot; +}; + + + +#ifdef JNETLIB_WEBSERVER_WANT_UTILS + +#include "../fileread.h" +#include "../wdlstring.h" + +class JNL_FilePageGenerator : public IPageGenerator +{ + public: + JNL_FilePageGenerator(WDL_FileRead *fr) { m_file = fr; } + virtual ~JNL_FilePageGenerator() { delete m_file; } + virtual int GetData(char *buf, int size) { return m_file ? m_file->Read(buf,size) : -1; } + + private: + + WDL_FileRead *m_file; +}; +class JNL_StringPageGenerator : public IPageGenerator +{ + public: + JNL_StringPageGenerator() { m_pos=0; } + virtual ~JNL_StringPageGenerator() { } + virtual int GetData(char *buf, int size) + { + if (size > str.GetLength() - m_pos) size=str.GetLength()-m_pos; + if (size>0) + { + memcpy(buf,str.Get()+m_pos,size); + m_pos+=size; + } + return size; + } + + WDL_FastString str; // set this before sending it off + + private: + int m_pos; +}; + +static void JNL_get_mime_type_for_file(const char *fn, char *strout, int stroutsz) +{ + const char *ext = fn; + while (*ext) ext++; + while (ext > fn && *ext != '.' && *ext != '/' && *ext != '\\') ext--; + + const char *type = "application/octet-stream"; + + if (!stricmp(ext,".jpg")) type = "image/jpeg"; + else if (!stricmp(ext,".png")) type = "image/png"; + else if (!stricmp(ext,".gif")) type = "image/gif"; + else if (!stricmp(ext,".txt")) type = "text/plain"; + else if (!strnicmp(ext,".htm",4)) type = "text/html"; + else if (!stricmp(ext,".js")) type = "application/javascript"; + else if (!stricmp(ext,".css")) type = "text/css"; + else if (!stricmp(ext,".xml")) type = "text/xml"; + else if (!stricmp(ext,".svg")) type = "image/svg+xml"; + + lstrcpyn_safe(strout,type,stroutsz); +} + +static void JNL_Format_RFC1123(time_t t, char *buf) +{ + + buf[0]=0; + static const char days[] = { "SunMonTueWedThuFriSat" }; + static const char mons[] = { "JanFebMarAprMayJunJulAugSepOctNovDec" }; + + struct tm *tm = gmtime(&t); + if (!tm) return; + memcpy(buf, days + (tm->tm_wday%7)*3, 3); + strcpy(buf+3,", "); + char *p=buf+5; + strftime(p, 64, "%d xxx %Y %H:%M:%S GMT", tm); + while (*p && *p != 'x') p++; + if (*p) memcpy(p, mons + (tm->tm_mon%12)*3, 3); +} + +#endif //JNETLIB_WEBSERVER_WANT_UTILS + + +#endif//_JNL_WEBSERVER_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/README b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/README new file mode 100644 index 0000000..911d0e8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/README @@ -0,0 +1,385 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 6b of 27-Mar-1998 +==================================== + +This distribution contains the sixth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +Serious users of this software (particularly those incorporating it into +larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to +our electronic mailing list. Mailing list members are notified of updates +and have a chance to participate in technical discussions, etc. + +This software is the work of Tom Lane, Philip Gladstone, Jim Boucher, +Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, +Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG +Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +RELATED SOFTWARE Other stuff you should get. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.doc How to configure and install the IJG software. + usage.doc Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.doc). + wizard.doc Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.doc How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.doc Overview of the JPEG library's internal structure. + filelist.doc Road map of IJG files. + coderules.doc Coding style rules --- please read if you contribute code. + +Please read at least the files install.doc and usage.doc. Useful information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image compression and +decompression. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. JPEG is intended for compressing +"real-world" scenes; line drawings, cartoons and other non-realistic images +are not its strong suit. JPEG is lossy, meaning that the output image is not +exactly identical to the input image. Hence you must not use JPEG if you +have to have identical output bits. However, on typical photographic images, +very good compression levels can be obtained with no visible change, and +remarkably high compression levels are possible if you can tolerate a +low-quality image. For more details, see the references, or just experiment +with various compression settings. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +For legal reasons, we are not distributing code for the arithmetic-coding +variants of JPEG; see LEGAL ISSUES. We have made no provision for supporting +the hierarchical or lossless processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. We have also included +"jpegtran", a utility for lossless transcoding between different JPEG +processes, and "rdjpgcom" and "wrjpgcom", two simple applications for +inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-1998, Thomas G. Lane. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltconfig, ltmain.sh). Another support script, install-sh, is copyright +by M.I.T. but is also freely distributable. + +It appears that the arithmetic coding option of the JPEG spec is covered by +patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot +legally be used without obtaining one or more licenses. For this reason, +support for arithmetic coding has been removed from the free JPEG software. +(Since arithmetic coding provides only a marginal gain over the unpatented +Huffman mode, it is unlikely that very many implementations will support it.) +So far as we are aware, there are no patent restrictions on the remaining +code. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We highly recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best full description of JPEG is the textbook "JPEG Still Image Data +Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published +by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. Price US$59.95, 638 pp. +The book includes the complete text of the ISO JPEG standards (DIS 10918-1 +and draft DIS 10918-2). This is by far the most complete exposition of JPEG +in existence, and we highly recommend it. + +The JPEG standard itself is not available electronically; you must order a +paper copy through ISO or ITU. (Unless you feel a need to own a certified +official copy, we recommend buying the Pennebaker and Mitchell book instead; +it's much cheaper and includes a great deal of useful explanatory material.) +In the USA, copies of the standard may be ordered from ANSI Sales at (212) +642-4900, or from Global Engineering Documents at (800) 854-7179. (ANSI +doesn't take credit card orders, but Global does.) It's not cheap: as of +1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7% +shipping/handling. The standard is divided into two parts, Part 1 being the +actual specification, while Part 2 covers compliance testing methods. Part 1 +is titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. + +Some extensions to the original JPEG standard are defined in JPEG Part 3, +a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84. IJG +currently does not support any Part 3 extensions. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. A copy of the JFIF spec is available from: + Literature Department + C-Cube Microsystems, Inc. + 1778 McCarthy Blvd. + Milpitas, CA 95035 + phone (408) 944-6300, fax (408) 944-6314 +A PostScript version of this document is available by FTP at +ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz. There is also a plain text +version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing +the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from ftp.sgi.com or +from ftp://ftp.uu.net/graphics/jpeg/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. libtiff is available +from ftp://ftp.sgi.com/graphics/tiff/. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is ftp.uu.net (Internet +address 192.48.96.9). The most recent released version can always be found +there in directory graphics/jpeg. This particular version will be archived +as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz. If you don't have +direct Internet access, UUNET's archives are also available via UUCP; contact +help@uunet.uu.net for information on retrieving files that way. + +Numerous Internet sites maintain copies of the UUNET files. However, only +ftp.uu.net is guaranteed to have the latest official version. + +You can also obtain this software in DOS-compatible "zip" archive format from +the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or +on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12 +"JPEG Tools". Again, these versions may sometimes lag behind the ftp.uu.net +release. + +The JPEG FAQ (Frequently Asked Questions) article is a useful source of +general information about JPEG. It is updated constantly and therefore is +not included in this distribution. The FAQ is posted every two weeks to +Usenet newsgroups comp.graphics.misc, news.answers, and other groups. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +RELATED SOFTWARE +================ + +Numerous viewing and image manipulation programs now support JPEG. (Quite a +few of them use this library to do so.) The JPEG FAQ described above lists +some of the more popular free and shareware viewers, and tells where to +obtain them on Internet. + +If you are on a Unix machine, we highly recommend Jef Poskanzer's free +PBMPLUS software, which provides many useful operations on PPM-format image +files. In particular, it can convert PPM images to and from a wide range of +other formats, thus making cjpeg/djpeg considerably more useful. The latest +version is distributed by the NetPBM group, and is available from numerous +sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/. +Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is; +you are likely to have difficulty making it work on any non-Unix machine. + +A different free JPEG implementation, written by the PVRG group at Stanford, +is available from ftp://havefun.stanford.edu/pub/jpeg/. This program +is designed for research and experimentation rather than production use; +it is slower, harder to use, and less portable than the IJG code, but it +is easier to read and modify. Also, the PVRG code supports lossless JPEG, +which we do not. (On the other hand, it doesn't do progressive JPEG.) + + +FILE FORMAT WARS +================ + +Some JPEG programs produce files that are not compatible with our library. +The root of the problem is that the ISO JPEG committee failed to specify a +concrete file format. Some vendors "filled in the blanks" on their own, +creating proprietary formats that no one else could read. (For example, none +of the early commercial JPEG implementations for the Macintosh were able to +exchange compressed files.) + +The file format we have adopted is called JFIF (see REFERENCES). This format +has been agreed to by a number of major commercial JPEG vendors, and it has +become the de facto standard. JFIF is a minimal or "low end" representation. +We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF +Technical Note #2) for "high end" applications that need to record a lot of +additional data about an image. TIFF/JPEG is fairly new and not yet widely +supported, unfortunately. + +The upcoming JPEG Part 3 standard defines a file format called SPIFF. +SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should +be able to read the most common variant of SPIFF. SPIFF has some technical +advantages over JFIF, but its major claim to fame is simply that it is an +official standard rather than an informal one. At this point it is unclear +whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto +standard. IJG intends to support SPIFF once the standard is frozen, but we +have not decided whether it should become our default output format or not. +(In any case, our decoder will remain capable of reading JFIF indefinitely.) + +Various proprietary file formats incorporating JPEG compression also exist. +We have little or no sympathy for the existence of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, open format standards for JPEG files. Don't +use a proprietary file format! + + +TO DO +===== + +The major thrust for v7 will probably be improvement of visual quality. +The current method for scaling the quantization tables is known not to be +very good at low Q values. We also intend to investigate block boundary +smoothing, "poor man's variable quantization", and other means of improving +quality-vs-file-size performance without sacrificing compatibility. + +In future versions, we are considering supporting some of the upcoming JPEG +Part 3 extensions --- principally, variable quantization and the SPIFF file +format. + +As always, speeding things up is of great interest. + +Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net. diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/example.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/example.c new file mode 100644 index 0000000..7fc354f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/example.c @@ -0,0 +1,433 @@ +/* + * example.c + * + * This file illustrates how to use the IJG code as a subroutine library + * to read or write JPEG image files. You should look at this code in + * conjunction with the documentation file libjpeg.doc. + * + * This code will not do anything useful as-is, but it may be helpful as a + * skeleton for constructing routines that call the JPEG library. + * + * We present these routines in the same coding style used in the JPEG code + * (ANSI function definitions, etc); but you are of course free to code your + * routines in a different style if you prefer. + */ + +#include + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#include "jpeglib.h" + +/* + * is used for the optional error recovery mechanism shown in + * the second part of the example. + */ + +#include + + + +/******************** JPEG COMPRESSION SAMPLE INTERFACE *******************/ + +/* This half of the example shows how to feed data into the JPEG compressor. + * We present a minimal version that does not worry about refinements such + * as error recovery (the JPEG code will just exit() if it gets an error). + */ + + +/* + * IMAGE DATA FORMATS: + * + * The standard input image format is a rectangular array of pixels, with + * each pixel having the same number of "component" values (color channels). + * Each pixel row is an array of JSAMPLEs (which typically are unsigned chars). + * If you are working with color data, then the color values for each pixel + * must be adjacent in the row; for example, R,G,B,R,G,B,R,G,B,... for 24-bit + * RGB color. + * + * For this example, we'll assume that this data structure matches the way + * our application has stored the image in memory, so we can just pass a + * pointer to our image buffer. In particular, let's say that the image is + * RGB color and is described by: + */ + +extern JSAMPLE * image_buffer; /* Points to large array of R,G,B-order data */ +extern int image_height; /* Number of rows in image */ +extern int image_width; /* Number of columns in image */ + + +/* + * Sample routine for JPEG compression. We assume that the target file name + * and a compression quality factor are passed in. + */ + +GLOBAL(void) +write_JPEG_file (char * filename, int quality) +{ + /* This struct contains the JPEG compression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + * It is possible to have several such structures, representing multiple + * compression/decompression processes, in existence at once. We refer + * to any one struct (and its associated working data) as a "JPEG object". + */ + struct jpeg_compress_struct cinfo; + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + FILE * outfile; /* target file */ + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + int row_stride; /* physical row width in image buffer */ + + /* Step 1: allocate and initialize JPEG compression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + + /* Here we use the library-supplied code to send compressed data to a + * stdio stream. You can also write your own code to do something else. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to write binary files. + */ + if ((outfile = fopen(filename, "wb")) == NULL) { + fprintf(stderr, "can't open %s\n", filename); + exit(1); + } + jpeg_stdio_dest(&cinfo, outfile); + + /* Step 3: set parameters for compression */ + + /* First we supply a description of the input image. + * Four fields of the cinfo struct must be filled in: + */ + cinfo.image_width = image_width; /* image width and height, in pixels */ + cinfo.image_height = image_height; + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + /* Now use the library's routine to set default compression parameters. + * (You must set at least cinfo.in_color_space before calling this, + * since the defaults depend on the source color space.) + */ + jpeg_set_defaults(&cinfo); + /* Now you can set any non-default parameters you wish to. + * Here we just illustrate the use of quality (quantization table) scaling: + */ + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + + /* Step 4: Start compressor */ + + /* TRUE ensures that we will write a complete interchange-JPEG file. + * Pass TRUE unless you are very sure of what you're doing. + */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + + /* Here we use the library's state variable cinfo.next_scanline as the + * loop counter, so that we don't have to keep track ourselves. + * To keep things simple, we pass one scanline per call; you can pass + * more if you wish, though. + */ + row_stride = image_width * 3; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride]; + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + /* Step 6: Finish compression */ + + jpeg_finish_compress(&cinfo); + /* After finish_compress, we can close the output file. */ + fclose(outfile); + + /* Step 7: release JPEG compression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_compress(&cinfo); + + /* And we're done! */ +} + + +/* + * SOME FINE POINTS: + * + * In the above loop, we ignored the return value of jpeg_write_scanlines, + * which is the number of scanlines actually written. We could get away + * with this because we were only relying on the value of cinfo.next_scanline, + * which will be incremented correctly. If you maintain additional loop + * variables then you should be careful to increment them properly. + * Actually, for output to a stdio stream you needn't worry, because + * then jpeg_write_scanlines will write all the lines passed (or else exit + * with a fatal error). Partial writes can only occur if you use a data + * destination module that can demand suspension of the compressor. + * (If you don't know what that's for, you don't need it.) + * + * If the compressor requires full-image buffers (for entropy-coding + * optimization or a multi-scan JPEG file), it will create temporary + * files for anything that doesn't fit within the maximum-memory setting. + * (Note that temp files are NOT needed if you use the default parameters.) + * On some systems you may need to set up a signal handler to ensure that + * temporary files are deleted if the program is interrupted. See libjpeg.doc. + * + * Scanlines MUST be supplied in top-to-bottom order if you want your JPEG + * files to be compatible with everyone else's. If you cannot readily read + * your data in that order, you'll need an intermediate array to hold the + * image. See rdtarga.c or rdbmp.c for examples of handling bottom-to-top + * source data using the JPEG code's internal virtual-array mechanisms. + */ + + + +/******************** JPEG DECOMPRESSION SAMPLE INTERFACE *******************/ + +/* This half of the example shows how to read data from the JPEG decompressor. + * It's a bit more refined than the above, in that we show: + * (a) how to modify the JPEG library's standard error-reporting behavior; + * (b) how to allocate workspace using the library's memory manager. + * + * Just to make this example a little different from the first one, we'll + * assume that we do not intend to put the whole image into an in-memory + * buffer, but to send it line-by-line someplace else. We need a one- + * scanline-high JSAMPLE array as a work buffer, and we will let the JPEG + * memory manager allocate it for us. This approach is actually quite useful + * because we don't need to remember to deallocate the buffer separately: it + * will go away automatically when the JPEG object is cleaned up. + */ + + +/* + * ERROR HANDLING: + * + * The JPEG library's standard error handler (jerror.c) is divided into + * several "methods" which you can override individually. This lets you + * adjust the behavior without duplicating a lot of code, which you might + * have to update with each future release. + * + * Our example here shows how to override the "error_exit" method so that + * control is returned to the library's caller when a fatal error occurs, + * rather than calling exit() as the standard error_exit method does. + * + * We use C's setjmp/longjmp facility to return control. This means that the + * routine which calls the JPEG library must first execute a setjmp() call to + * establish the return point. We want the replacement error_exit to do a + * longjmp(). But we need to make the setjmp buffer accessible to the + * error_exit routine. To do this, we make a private extension of the + * standard JPEG error handler object. (If we were using C++, we'd say we + * were making a subclass of the regular error handler.) + * + * Here's the extended error handler struct: + */ + +struct my_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + + jmp_buf setjmp_buffer; /* for return to caller */ +}; + +typedef struct my_error_mgr * my_error_ptr; + +/* + * Here's the routine that will replace the standard error_exit method: + */ + +METHODDEF(void) +my_error_exit (j_common_ptr cinfo) +{ + /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + + /* Always display the message. */ + /* We could postpone this until after returning, if we chose. */ + (*cinfo->err->output_message) (cinfo); + + /* Return control to the setjmp point */ + longjmp(myerr->setjmp_buffer, 1); +} + + +/* + * Sample routine for JPEG decompression. We assume that the source file name + * is passed in. We want to return 1 on success, 0 on error. + */ + + +GLOBAL(int) +read_JPEG_file (char * filename) +{ + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct my_error_mgr jerr; + /* More stuff */ + FILE * infile; /* source file */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + if ((infile = fopen(filename, "rb")) == NULL) { + fprintf(stderr, "can't open %s\n", filename); + return 0; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We set up the normal JPEG error routines, then override error_exit. */ + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp(jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return 0; + } + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src(&cinfo, infile); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + /* Make a one-row-high sample array that will go away when done with image */ + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + /* Assume put_scanline_someplace wants a pointer and sample count. */ + put_scanline_someplace(buffer[0], row_stride); + } + + /* Step 7: Finish decompression */ + + (void) jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + fclose(infile); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ + return 1; +} + + +/* + * SOME FINE POINTS: + * + * In the above code, we ignored the return value of jpeg_read_scanlines, + * which is the number of scanlines actually read. We could get away with + * this because we asked for only one line at a time and we weren't using + * a suspending data source. See libjpeg.doc for more info. + * + * We cheated a bit by calling alloc_sarray() after jpeg_start_decompress(); + * we should have done it beforehand to ensure that the space would be + * counted against the JPEG max_memory setting. In some systems the above + * code would risk an out-of-memory error. However, in general we don't + * know the output image dimensions before jpeg_start_decompress(), unless we + * call jpeg_calc_output_dimensions(). See libjpeg.doc for more about this. + * + * Scanlines are returned in the same order as they appear in the JPEG file, + * which is standardly top-to-bottom. If you must emit data bottom-to-top, + * you can use one of the virtual arrays provided by the JPEG memory manager + * to invert the data. See wrbmp.c for an example. + * + * As with compression, some operating modes may require temporary files. + * On some systems you may need to set up a signal handler to ensure that + * temporary files are deleted if the program is interrupted. See libjpeg.doc. + */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcapimin.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcapimin.c new file mode 100644 index 0000000..54fb8c5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcapimin.c @@ -0,0 +1,280 @@ +/* + * jcapimin.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-compression case or the transcoding-only + * case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jcapistd.c. But also see jcparam.c for + * parameter-setup helper routines, jcomapi.c for routines shared by + * compression and decompression, and jctrans.c for the transcoding case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG compression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL(void) +jpeg_CreateCompress (j_compress_ptr cinfo, int version, size_t structsize) +{ + int i; + + /* Guard against version mismatches between library and caller. */ + cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */ + if (version != JPEG_LIB_VERSION) + ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); + if (structsize != SIZEOF(struct jpeg_compress_struct)) + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + (int) SIZEOF(struct jpeg_compress_struct), (int) structsize); + + /* For debugging purposes, we zero the whole master structure. + * But the application has already set the err pointer, and may have set + * client_data, so we have to save and restore those fields. + * Note: if application hasn't set client_data, tools like Purify may + * complain here. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + void * client_data = cinfo->client_data; /* ignore Purify complaint here */ + MEMZERO(cinfo, SIZEOF(struct jpeg_compress_struct)); + cinfo->err = err; + cinfo->client_data = client_data; + } + cinfo->is_decompressor = FALSE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->dest = NULL; + + cinfo->comp_info = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + cinfo->script_space = NULL; + + cinfo->input_gamma = 1.0; /* in case application forgets */ + + /* OK, I'm ready */ + cinfo->global_state = CSTATE_START; +} + + +/* + * Destruction of a JPEG compression object + */ + +GLOBAL(void) +jpeg_destroy_compress (j_compress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG compression operation, + * but don't destroy the object itself. + */ + +GLOBAL(void) +jpeg_abort_compress (j_compress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Forcibly suppress or un-suppress all quantization and Huffman tables. + * Marks all currently defined tables as already written (if suppress) + * or not written (if !suppress). This will control whether they get emitted + * by a subsequent jpeg_start_compress call. + * + * This routine is exported for use by applications that want to produce + * abbreviated JPEG datastreams. It logically belongs in jcparam.c, but + * since it is called by jpeg_start_compress, we put it here --- otherwise + * jcparam.o would be linked whether the application used it or not. + */ + +GLOBAL(void) +jpeg_suppress_tables (j_compress_ptr cinfo, boolean suppress) +{ + int i; + JQUANT_TBL * qtbl; + JHUFF_TBL * htbl; + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if ((qtbl = cinfo->quant_tbl_ptrs[i]) != NULL) + qtbl->sent_table = suppress; + } + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if ((htbl = cinfo->dc_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + if ((htbl = cinfo->ac_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + } +} + + +/* + * Finish JPEG compression. + * + * If a multipass operating mode was selected, this may do a great deal of + * work including most of the actual output. + */ + +GLOBAL(void) +jpeg_finish_compress (j_compress_ptr cinfo) +{ + JDIMENSION iMCU_row; + + if (cinfo->global_state == CSTATE_SCANNING || + cinfo->global_state == CSTATE_RAW_OK) { + /* Terminate first pass */ + if (cinfo->next_scanline < cinfo->image_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_pass) (cinfo); + } else if (cinfo->global_state != CSTATE_WRCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any remaining passes */ + while (! cinfo->master->is_last_pass) { + (*cinfo->master->prepare_for_pass) (cinfo); + for (iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++) { + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) iMCU_row; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* We bypass the main controller and invoke coef controller directly; + * all work is being done from the coefficient buffer. + */ + if (! (*cinfo->coef->compress_data) (cinfo, (JSAMPIMAGE) NULL)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } + (*cinfo->master->finish_pass) (cinfo); + } + /* Write EOI, do final cleanup */ + (*cinfo->marker->write_file_trailer) (cinfo); + (*cinfo->dest->term_destination) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); +} + + +/* + * Write a special marker. + * This is only recommended for writing COM or APPn markers. + * Must be called after jpeg_start_compress() and before + * first call to jpeg_write_scanlines() or jpeg_write_raw_data(). + */ + +GLOBAL(void) +jpeg_write_marker (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen) +{ + JMETHOD(void, write_marker_byte, (j_compress_ptr info, int val)); + + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_marker_header) (cinfo, marker, datalen); + write_marker_byte = cinfo->marker->write_marker_byte; /* copy for speed */ + while (datalen--) { + (*write_marker_byte) (cinfo, *dataptr); + dataptr++; + } +} + +/* Same, but piecemeal. */ + +GLOBAL(void) +jpeg_write_m_header (j_compress_ptr cinfo, int marker, unsigned int datalen) +{ + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_marker_header) (cinfo, marker, datalen); +} + +GLOBAL(void) +jpeg_write_m_byte (j_compress_ptr cinfo, int val) +{ + (*cinfo->marker->write_marker_byte) (cinfo, val); +} + + +/* + * Alternate compression function: just write an abbreviated table file. + * Before calling this, all parameters and a data destination must be set up. + * + * To produce a pair of files containing abbreviated tables and abbreviated + * image data, one would proceed as follows: + * + * initialize JPEG object + * set JPEG parameters + * set destination to table file + * jpeg_write_tables(cinfo); + * set destination to image file + * jpeg_start_compress(cinfo, FALSE); + * write data... + * jpeg_finish_compress(cinfo); + * + * jpeg_write_tables has the side effect of marking all tables written + * (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress + * will not re-emit the tables unless it is passed write_all_tables=TRUE. + */ + +GLOBAL(void) +jpeg_write_tables (j_compress_ptr cinfo) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Initialize the marker writer ... bit of a crock to do it here. */ + jinit_marker_writer(cinfo); + /* Write them tables! */ + (*cinfo->marker->write_tables_only) (cinfo); + /* And clean up. */ + (*cinfo->dest->term_destination) (cinfo); + /* + * In library releases up through v6a, we called jpeg_abort() here to free + * any working memory allocated by the destination manager and marker + * writer. Some applications had a problem with that: they allocated space + * of their own from the library memory manager, and didn't want it to go + * away during write_tables. So now we do nothing. This will cause a + * memory leak if an app calls write_tables repeatedly without doing a full + * compression cycle or otherwise resetting the JPEG object. However, that + * seems less bad than unexpectedly freeing memory in the normal case. + * An app that prefers the old behavior can call jpeg_abort for itself after + * each call to jpeg_write_tables(). + */ +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcapistd.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcapistd.c new file mode 100644 index 0000000..c0320b1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcapistd.c @@ -0,0 +1,161 @@ +/* + * jcapistd.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-compression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_compress, it will end up linking in the entire compressor. + * We thus must separate this file from jcapimin.c to avoid linking the + * whole compression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL(void) +jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (write_all_tables) + jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */ + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + jinit_compress_master(cinfo); + /* Set up for the first pass */ + (*cinfo->master->prepare_for_pass) (cinfo); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL(JDIMENSION) +jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) +{ + JDIMENSION row_ctr, rows_left; + + if (cinfo->global_state != CSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if (num_lines > rows_left) + num_lines = rows_left; + + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to write raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL(JDIMENSION) +jpeg_write_raw_data (j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != CSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_raw_data. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_raw_data. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Verify that at least one iMCU row has been passed. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE; + if (num_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Directly compress the row. */ + if (! (*cinfo->coef->compress_data) (cinfo, data)) { + /* If compressor did not consume the whole row, suspend processing. */ + return 0; + } + + /* OK, we processed one iMCU row. */ + cinfo->next_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jccoefct.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jccoefct.c new file mode 100644 index 0000000..1963ddb --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jccoefct.c @@ -0,0 +1,449 @@ +/* + * jccoefct.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for compression. + * This controller is the top level of the JPEG compressor proper. + * The coefficient buffer lies between forward-DCT and entropy encoding steps. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* We use a full-image coefficient buffer when doing Huffman optimization, + * and also for writing multiple-scan JPEG files. In all cases, the DCT + * step is run during the first pass, and subsequent passes need only read + * the buffered coefficients. + */ +#ifdef ENTROPY_OPT_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#else +#ifdef C_MULTISCAN_FILES_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#endif +#endif + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* For single-pass compression, it's sufficient to buffer just one MCU + * (although this may prove a bit slow in practice). We allocate a + * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each + * MCU constructed and sent. (On 80x86, the workspace is FAR even though + * it's not really very big; this is to keep the module interfaces unchanged + * when a large coefficient buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays. + */ + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +/* Forward declarations */ +METHODDEF(boolean) compress_data + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#ifdef FULL_COEF_BUFFER_SUPPORTED +METHODDEF(boolean) compress_first_pass + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +METHODDEF(boolean) compress_output + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#endif + + +LOCAL(void) +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (coef->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_data; + break; +#ifdef FULL_COEF_BUFFER_SUPPORTED + case JBUF_SAVE_AND_PASS: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_first_pass; + break; + case JBUF_CRANK_DEST: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_output; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data in the single-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf contains a plane for each component in image, + * which we index according to the component's SOF position. + */ + +METHODDEF(boolean) +compress_data (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, bi, ci, yindex, yoffset, blockcnt; + JDIMENSION ypos, xpos; + jpeg_component_info *compptr; + + /* Loop to write as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Determine where data comes from in input_buf and do the DCT thing. + * Each call on forward_DCT processes a horizontal row of DCT blocks + * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks + * sequentially. Dummy blocks at the right or bottom edge are filled in + * specially. The data in them does not matter for image reconstruction, + * so we fill them with values that will encode to the smallest amount of + * data, viz: all zeroes in the AC entries, DC entries equal to previous + * block's DC value. (Thanks to Thomas Kinsman for this idea.) + */ + blkn = 0; + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + xpos = MCU_col_num * compptr->MCU_sample_width; + ypos = yoffset * DCTSIZE; /* ypos == (yoffset+yindex) * DCTSIZE */ + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[compptr->component_index], + coef->MCU_buffer[blkn], + ypos, xpos, (JDIMENSION) blockcnt); + if (blockcnt < compptr->MCU_width) { + /* Create some dummy blocks at the right edge of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn + blockcnt], + (compptr->MCU_width - blockcnt) * SIZEOF(JBLOCK)); + for (bi = blockcnt; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn+bi-1][0][0]; + } + } + } else { + /* Create a row of dummy blocks at the bottom of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn], + compptr->MCU_width * SIZEOF(JBLOCK)); + for (bi = 0; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn-1][0][0]; + } + } + blkn += compptr->MCU_width; + ypos += DCTSIZE; + } + } + /* Try to write the MCU. In event of a suspension failure, we will + * re-DCT the MCU on restart (a bit inefficient, could be fixed...) + */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +#ifdef FULL_COEF_BUFFER_SUPPORTED + +/* + * Process some data in the first pass of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * This amount of data is read from the source buffer, DCT'd and quantized, + * and saved into the virtual arrays. We also generate suitable dummy blocks + * as needed at the right and lower edges. (The dummy blocks are constructed + * in the virtual arrays, which have been padded appropriately.) This makes + * it possible for subsequent passes not to worry about real vs. dummy blocks. + * + * We must also emit the data to the entropy encoder. This is conveniently + * done by calling compress_output() after we've loaded the current strip + * of the virtual arrays. + * + * NB: input_buf contains a plane for each component in image. All + * components are DCT'd and loaded into the virtual arrays in this pass. + * However, it may be that only a subset of the components are emitted to + * the entropy encoder during this first pass; be careful about looking + * at the scan-dependent variables (MCU dimensions, etc). + */ + +METHODDEF(boolean) +compress_first_pass (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION blocks_across, MCUs_across, MCUindex; + int bi, ci, h_samp_factor, block_row, block_rows, ndummy; + JCOEF lastDC; + jpeg_component_info *compptr; + JBLOCKARRAY buffer; + JBLOCKROW thisblockrow, lastblockrow; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (coef->iMCU_row_num < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here, since may not be set! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + blocks_across = compptr->width_in_blocks; + h_samp_factor = compptr->h_samp_factor; + /* Count number of dummy blocks to be added at the right margin. */ + ndummy = (int) (blocks_across % h_samp_factor); + if (ndummy > 0) + ndummy = h_samp_factor - ndummy; + /* Perform DCT for all non-dummy blocks in this iMCU row. Each call + * on forward_DCT processes a complete horizontal row of DCT blocks. + */ + for (block_row = 0; block_row < block_rows; block_row++) { + thisblockrow = buffer[block_row]; + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[ci], thisblockrow, + (JDIMENSION) (block_row * DCTSIZE), + (JDIMENSION) 0, blocks_across); + if (ndummy > 0) { + /* Create dummy blocks at the right edge of the image. */ + thisblockrow += blocks_across; /* => first dummy block */ + jzero_far((void FAR *) thisblockrow, ndummy * SIZEOF(JBLOCK)); + lastDC = thisblockrow[-1][0]; + for (bi = 0; bi < ndummy; bi++) { + thisblockrow[bi][0] = lastDC; + } + } + } + /* If at end of image, create dummy block rows as needed. + * The tricky part here is that within each MCU, we want the DC values + * of the dummy blocks to match the last real block's DC value. + * This squeezes a few more bytes out of the resulting file... + */ + if (coef->iMCU_row_num == last_iMCU_row) { + blocks_across += ndummy; /* include lower right corner */ + MCUs_across = blocks_across / h_samp_factor; + for (block_row = block_rows; block_row < compptr->v_samp_factor; + block_row++) { + thisblockrow = buffer[block_row]; + lastblockrow = buffer[block_row-1]; + jzero_far((void FAR *) thisblockrow, + (size_t) (blocks_across * SIZEOF(JBLOCK))); + for (MCUindex = 0; MCUindex < MCUs_across; MCUindex++) { + lastDC = lastblockrow[h_samp_factor-1][0]; + for (bi = 0; bi < h_samp_factor; bi++) { + thisblockrow[bi][0] = lastDC; + } + thisblockrow += h_samp_factor; /* advance to next MCU in row */ + lastblockrow += h_samp_factor; + } + } + } + } + /* NB: compress_output will increment iMCU_row_num if successful. + * A suspension return will result in redoing all the work above next time. + */ + + /* Emit data to the entropy encoder, sharing code with subsequent passes */ + return compress_output(cinfo, input_buf); +} + + +/* + * Process some data in subsequent passes of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF(boolean) +compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. + * NB: during first pass, this is safe only because the buffers will + * already be aligned properly, so jmemmgr.c won't need to do any I/O. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + +#endif /* FULL_COEF_BUFFER_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL(void) +jinit_c_coef_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef FULL_COEF_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + int ci; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->whole_image[0] = NULL; /* flag for no virtual arrays */ + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jccolor.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jccolor.c new file mode 100644 index 0000000..0a8a4b5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jccolor.c @@ -0,0 +1,459 @@ +/* + * jccolor.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_converter pub; /* public fields */ + + /* Private state for RGB->YCC conversion */ + INT32 * rgb_ycc_tab; /* => table for RGB to YCbCr conversion */ +} my_color_converter; + +typedef my_color_converter * my_cconvert_ptr; + + +/**************** RGB -> YCbCr conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B + * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE + * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2, + * rather than CENTERJSAMPLE, for Cb and Cr. This gave equal positive and + * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0) + * were not represented exactly. Now we sacrifice exact representation of + * maximum red and maximum blue in order to get exact grayscales. + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times R,G,B for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included + * in the tables to save adding them separately in the inner loop. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define CBCR_OFFSET ((INT32) CENTERJSAMPLE << SCALEBITS) +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L< Y section */ +#define G_Y_OFF (1*(MAXJSAMPLE+1)) /* offset to G => Y section */ +#define B_Y_OFF (2*(MAXJSAMPLE+1)) /* etc. */ +#define R_CB_OFF (3*(MAXJSAMPLE+1)) +#define G_CB_OFF (4*(MAXJSAMPLE+1)) +#define B_CB_OFF (5*(MAXJSAMPLE+1)) +#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */ +#define G_CR_OFF (6*(MAXJSAMPLE+1)) +#define B_CR_OFF (7*(MAXJSAMPLE+1)) +#define TABLE_SIZE (8*(MAXJSAMPLE+1)) + + +/* + * Initialize for RGB->YCC colorspace conversion. + */ + +METHODDEF(void) +rgb_ycc_start (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + INT32 * rgb_ycc_tab; + INT32 i; + + /* Allocate and fill in the conversion tables. */ + cconvert->rgb_ycc_tab = rgb_ycc_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (TABLE_SIZE * SIZEOF(INT32))); + + for (i = 0; i <= MAXJSAMPLE; i++) { + rgb_ycc_tab[i+R_Y_OFF] = FIX(0.29900) * i; + rgb_ycc_tab[i+G_Y_OFF] = FIX(0.58700) * i; + rgb_ycc_tab[i+B_Y_OFF] = FIX(0.11400) * i + ONE_HALF; + rgb_ycc_tab[i+R_CB_OFF] = (-FIX(0.16874)) * i; + rgb_ycc_tab[i+G_CB_OFF] = (-FIX(0.33126)) * i; + /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr. + * This ensures that the maximum output will round to MAXJSAMPLE + * not MAXJSAMPLE+1, and thus that we don't have to range-limit. + */ + rgb_ycc_tab[i+B_CB_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +/* B=>Cb and R=>Cr tables are the same + rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +*/ + rgb_ycc_tab[i+G_CR_OFF] = (-FIX(0.41869)) * i; + rgb_ycc_tab[i+B_CR_OFF] = (-FIX(0.08131)) * i; + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * + * Note that we change from the application's interleaved-pixel format + * to our internal noninterleaved, one-plane-per-component format. + * The input buffer is therefore three times as wide as the output buffer. + * + * A starting row offset is provided only for the output buffer. The caller + * can easily adjust the passed input_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF(void) +rgb_ycc_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/**************** Cases other than RGB -> YCbCr **************/ + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles RGB->grayscale conversion, which is the same + * as the RGB->Y portion of RGB->YCbCr. + * We assume rgb_ycc_start has been called (we only use the Y tables). + */ + +METHODDEF(void) +rgb_gray_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* Y */ + outptr[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles Adobe-style CMYK->YCCK conversion, + * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same + * conversion as above, while passing K (black) unchanged. + * We assume rgb_ycc_start has been called. + */ + +METHODDEF(void) +cmyk_ycck_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2, outptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + outptr3 = output_buf[3][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = MAXJSAMPLE - GETJSAMPLE(inptr[0]); + g = MAXJSAMPLE - GETJSAMPLE(inptr[1]); + b = MAXJSAMPLE - GETJSAMPLE(inptr[2]); + /* K passes through as-is */ + outptr3[col] = inptr[3]; /* don't need GETJSAMPLE here */ + inptr += 4; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles grayscale output with no conversion. + * The source can be either plain grayscale or YCbCr (since Y == gray). + */ + +METHODDEF(void) +grayscale_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + int instride = cinfo->input_components; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */ + inptr += instride; + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles multi-component colorspaces without conversion. + * We assume input_components == num_components. + */ + +METHODDEF(void) +null_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + register int ci; + int nc = cinfo->num_components; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + /* It seems fastest to make a separate pass for each component. */ + for (ci = 0; ci < nc; ci++) { + inptr = *input_buf; + outptr = output_buf[ci][output_row]; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */ + inptr += nc; + } + } + input_buf++; + output_row++; + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF(void) +null_method (j_compress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for input colorspace conversion. + */ + +GLOBAL(void) +jinit_color_converter (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_converter)); + cinfo->cconvert = (struct jpeg_color_converter *) cconvert; + /* set start_pass to null method until we find out differently */ + cconvert->pub.start_pass = null_method; + + /* Make sure input_components agrees with in_color_space */ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + if (cinfo->input_components != 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + if (cinfo->input_components != RGB_PIXELSIZE) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; +#endif /* else share code with YCbCr */ + + case JCS_YCbCr: + if (cinfo->input_components != 3) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->input_components != 4) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->input_components < 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + } + + /* Check num_components, set conversion method based on requested space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_GRAYSCALE) + cconvert->pub.color_convert = grayscale_convert; + else if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_gray_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = grayscale_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_ycc_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = cmyk_ycck_convert; + } else if (cinfo->in_color_space == JCS_YCCK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: /* allow null conversion of JCS_UNKNOWN */ + if (cinfo->jpeg_color_space != cinfo->in_color_space || + cinfo->num_components != cinfo->input_components) + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + cconvert->pub.color_convert = null_convert; + break; + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcdctmgr.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcdctmgr.c new file mode 100644 index 0000000..61fa79b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcdctmgr.c @@ -0,0 +1,387 @@ +/* + * jcdctmgr.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the forward-DCT management logic. + * This code selects a particular DCT implementation to be used, + * and it performs related housekeeping chores including coefficient + * quantization. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_forward_dct pub; /* public fields */ + + /* Pointer to the DCT routine actually in use */ + forward_DCT_method_ptr do_dct; + + /* The actual post-DCT divisors --- not identical to the quant table + * entries, because of scaling (especially for an unnormalized DCT). + * Each table is given in normal array order. + */ + DCTELEM * divisors[NUM_QUANT_TBLS]; + +#ifdef DCT_FLOAT_SUPPORTED + /* Same as above for the floating-point case. */ + float_DCT_method_ptr do_float_dct; + FAST_FLOAT * float_divisors[NUM_QUANT_TBLS]; +#endif +} my_fdct_controller; + +typedef my_fdct_controller * my_fdct_ptr; + + +/* + * Initialize for a processing pass. + * Verify that all referenced Q-tables are present, and set up + * the divisor table for each one. + * In the current implementation, DCT of all components is done during + * the first pass, even if only some components will be output in the + * first scan. Hence all components should be examined here. + */ + +METHODDEF(void) +start_pass_fdctmgr (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + int ci, qtblno, i; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + DCTELEM * dtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + qtblno = compptr->quant_tbl_no; + /* Make sure specified quantization table is present */ + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + qtbl = cinfo->quant_tbl_ptrs[qtblno]; + /* Compute divisors for this quant table */ + /* We may do this more than once for same table, but it's not a big deal */ + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + /* For LL&M IDCT method, divisors are equal to raw quantization + * coefficients multiplied by 8 (to counteract scaling). + */ + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = ((DCTELEM) qtbl->quantval[i]) << 3; + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + */ +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = (DCTELEM) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], + (INT32) aanscales[i]), + CONST_BITS-3); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + FAST_FLOAT * fdtbl; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + if (fdct->float_divisors[qtblno] == NULL) { + fdct->float_divisors[qtblno] = (FAST_FLOAT *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(FAST_FLOAT)); + } + fdtbl = fdct->float_divisors[qtblno]; + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fdtbl[i] = (FAST_FLOAT) + (1.0 / (((double) qtbl->quantval[i] * + aanscalefactor[row] * aanscalefactor[col] * 8.0))); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Perform forward DCT on one or more blocks of a component. + * + * The input samples are taken from the sample_data[] array starting at + * position start_row/start_col, and moving to the right for any additional + * blocks. The quantized coefficients are returned in coef_blocks[]. + */ + +METHODDEF(void) +forward_DCT (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for integer DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + forward_DCT_method_ptr do_dct = fdct->do_dct; + DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no]; + DCTELEM workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register DCTELEM *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register DCTELEM temp, qval; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + qval = divisors[i]; + temp = workspace[i]; + /* Divide the coefficient value by qval, ensuring proper rounding. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * + * In most files, at least half of the output values will be zero + * (at default quantization settings, more like three-quarters...) + * so we should ensure that this case is fast. On many machines, + * a comparison is enough cheaper than a divide to make a special test + * a win. Since both inputs will be nonnegative, we need only test + * for a < b to discover whether a/b is 0. + * If your machine's division is fast enough, define FAST_DIVIDE. + */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY(a,b) a /= b +#else +#define DIVIDE_BY(a,b) if (a >= b) a /= b; else a = 0 +#endif + if (temp < 0) { + temp = -temp; + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + temp = -temp; + } else { + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + } + output_ptr[i] = (JCOEF) temp; + } + } + } +} + + +#ifdef DCT_FLOAT_SUPPORTED + +METHODDEF(void) +forward_DCT_float (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for floating-point DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + float_DCT_method_ptr do_dct = fdct->do_float_dct; + FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no]; + FAST_FLOAT workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register FAST_FLOAT *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = (FAST_FLOAT) + (GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register FAST_FLOAT temp; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + /* Apply the quantization and scaling factor */ + temp = workspace[i] * divisors[i]; + /* Round to nearest integer. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * The maximum coefficient size is +-16K (for 12-bit data), so this + * code should work for either 16-bit or 32-bit ints. + */ + output_ptr[i] = (JCOEF) ((int) (temp + (FAST_FLOAT) 16384.5) - 16384); + } + } + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ + + +/* + * Initialize FDCT manager. + */ + +GLOBAL(void) +jinit_forward_dct (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct; + int i; + + fdct = (my_fdct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_fdct_controller)); + cinfo->fdct = (struct jpeg_forward_dct *) fdct; + fdct->pub.start_pass = start_pass_fdctmgr; + + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_islow; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_ifast; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + fdct->pub.forward_DCT = forward_DCT_float; + fdct->do_float_dct = jpeg_fdct_float; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + + /* Mark divisor tables unallocated */ + for (i = 0; i < NUM_QUANT_TBLS; i++) { + fdct->divisors[i] = NULL; +#ifdef DCT_FLOAT_SUPPORTED + fdct->float_divisors[i] = NULL; +#endif + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jchuff.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jchuff.c new file mode 100644 index 0000000..f235250 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jchuff.c @@ -0,0 +1,909 @@ +/* + * jchuff.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines. + * + * Much of the complexity here has to do with supporting output suspension. + * If the data destination module demands suspension, we want to be able to + * back up to the start of the current MCU. To do this, we copy state + * variables into local working storage, and update them back to the + * permanent JPEG objects only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jcphuff.c */ + + +/* Expanded entropy encoder object for Huffman encoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).put_buffer = (src).put_buffer, \ + (dest).put_bits = (src).put_bits, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + savable_state saved; /* Bit buffer & DC state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + +#ifdef ENTROPY_OPT_SUPPORTED /* Statistics tables for optimization */ + long * dc_count_ptrs[NUM_HUFF_TBLS]; + long * ac_count_ptrs[NUM_HUFF_TBLS]; +#endif +} huff_entropy_encoder; + +typedef huff_entropy_encoder * huff_entropy_ptr; + +/* Working state while writing an MCU. + * This struct contains all the fields that are needed by subroutines. + */ + +typedef struct { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + savable_state cur; /* Current bit buffer & DC state */ + j_compress_ptr cinfo; /* dump_buffer needs access to this */ +} working_state; + + +/* Forward declarations */ +METHODDEF(boolean) encode_mcu_huff JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(void) finish_pass_huff JPP((j_compress_ptr cinfo)); +#ifdef ENTROPY_OPT_SUPPORTED +METHODDEF(boolean) encode_mcu_gather JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(void) finish_pass_gather JPP((j_compress_ptr cinfo)); +#endif + + +/* + * Initialize for a Huffman-compressed scan. + * If gather_statistics is TRUE, we do not output anything during the scan, + * just count the Huffman symbols used and generate Huffman code tables. + */ + +METHODDEF(void) +start_pass_huff (j_compress_ptr cinfo, boolean gather_statistics) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + entropy->pub.encode_mcu = encode_mcu_gather; + entropy->pub.finish_pass = finish_pass_gather; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + entropy->pub.encode_mcu = encode_mcu_huff; + entropy->pub.finish_pass = finish_pass_huff; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + /* Check for invalid table indexes */ + /* (make_c_derived_tbl does this in the other path) */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->dc_count_ptrs[dctbl] == NULL) + entropy->dc_count_ptrs[dctbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->dc_count_ptrs[dctbl], 257 * SIZEOF(long)); + if (entropy->ac_count_ptrs[actbl] == NULL) + entropy->ac_count_ptrs[actbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->ac_count_ptrs[actbl], 257 * SIZEOF(long)); +#endif + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl(cinfo, TRUE, dctbl, + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_c_derived_tbl(cinfo, FALSE, actbl, + & entropy->ac_derived_tbls[actbl]); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bit buffer to empty */ + entropy->saved.put_buffer = 0; + entropy->saved.put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Compute the derived values for a Huffman table. + * This routine also performs some validation checks on the table. + * + * Note this is also used by jcphuff.c. + */ + +GLOBAL(void) +jpeg_make_c_derived_tbl (j_compress_ptr cinfo, boolean isDC, int tblno, + c_derived_tbl ** pdtbl) +{ + JHUFF_TBL *htbl; + c_derived_tbl *dtbl; + int p, i, l, lastp, si, maxsymbol; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Note that huffsize[] and huffcode[] are filled in code-length order, + * paralleling the order of the symbols themselves in htbl->huffval[]. + */ + + /* Find the input Huffman table */ + if (tblno < 0 || tblno >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + htbl = + isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno]; + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (c_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(c_derived_tbl)); + dtbl = *pdtbl; + + /* Figure C.1: make table of Huffman code length for each symbol */ + + p = 0; + for (l = 1; l <= 16; l++) { + i = (int) htbl->bits[l]; + if (i < 0 || p + i > 256) /* protect against table overrun */ + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + while (i--) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + lastp = p; + + /* Figure C.2: generate the codes themselves */ + /* We also validate that the counts represent a legal Huffman code tree. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + /* code is now 1 more than the last code used for codelength si; but + * it must still fit in si bits, since no code is allowed to be all ones. + */ + if (((INT32) code) >= (((INT32) 1) << si)) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + code <<= 1; + si++; + } + + /* Figure C.3: generate encoding tables */ + /* These are code and size indexed by symbol value */ + + /* Set all codeless symbols to have code length 0; + * this lets us detect duplicate VAL entries here, and later + * allows emit_bits to detect any attempt to emit such symbols. + */ + MEMZERO(dtbl->ehufsi, SIZEOF(dtbl->ehufsi)); + + /* This is also a convenient place to check for out-of-range + * and duplicated VAL entries. We allow 0..255 for AC symbols + * but only 0..15 for DC. (We could constrain them further + * based on data depth and mode, but this seems enough.) + */ + maxsymbol = isDC ? 15 : 255; + + for (p = 0; p < lastp; p++) { + i = htbl->huffval[p]; + if (i < 0 || i > maxsymbol || dtbl->ehufsi[i]) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + dtbl->ehufco[i] = huffcode[p]; + dtbl->ehufsi[i] = huffsize[p]; + } +} + + +/* Outputting bytes to the file */ + +/* Emit a byte, taking 'action' if must suspend. */ +#define emit_byte(state,val,action) \ + { *(state)->next_output_byte++ = (JOCTET) (val); \ + if (--(state)->free_in_buffer == 0) \ + if (! dump_buffer(state)) \ + { action; } } + + +LOCAL(boolean) +dump_buffer (working_state * state) +/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */ +{ + struct jpeg_destination_mgr * dest = state->cinfo->dest; + + if (! (*dest->empty_output_buffer) (state->cinfo)) + return FALSE; + /* After a successful buffer dump, must reset buffer pointers */ + state->next_output_byte = dest->next_output_byte; + state->free_in_buffer = dest->free_in_buffer; + return TRUE; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL(boolean) +emit_bits (working_state * state, unsigned int code, int size) +/* Emit some bits; return TRUE if successful, FALSE if must suspend */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = state->cur.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(state->cinfo, JERR_HUFF_MISSING_CODE); + + put_buffer &= (((INT32) 1)<cur.put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(state, c, return FALSE); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(state, 0, return FALSE); + } + put_buffer <<= 8; + put_bits -= 8; + } + + state->cur.put_buffer = put_buffer; /* update state variables */ + state->cur.put_bits = put_bits; + + return TRUE; +} + + +LOCAL(boolean) +flush_bits (working_state * state) +{ + if (! emit_bits(state, 0x7F, 7)) /* fill any partial byte with ones */ + return FALSE; + state->cur.put_buffer = 0; /* and reset bit-buffer to empty */ + state->cur.put_bits = 0; + return TRUE; +} + + +/* Encode a single block's worth of coefficients */ + +LOCAL(boolean) +encode_one_block (working_state * state, JCOEFPTR block, int last_dc_val, + c_derived_tbl *dctbl, c_derived_tbl *actbl) +{ + register int temp, temp2; + register int nbits; + register int k, r, i; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = temp2 = block[0] - last_dc_val; + + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(state->cinfo, JERR_BAD_DCT_COEF); + + /* Emit the Huffman-coded symbol for the number of bits */ + if (! emit_bits(state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + if (! emit_bits(state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0])) + return FALSE; + r -= 16; + } + + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(state->cinfo, JERR_BAD_DCT_COEF); + + /* Emit Huffman symbol for run length / number of bits */ + i = (r << 4) + nbits; + if (! emit_bits(state, actbl->ehufco[i], actbl->ehufsi[i])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + if (! emit_bits(state, actbl->ehufco[0], actbl->ehufsi[0])) + return FALSE; + + return TRUE; +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL(boolean) +emit_restart (working_state * state, int restart_num) +{ + int ci; + + if (! flush_bits(state)) + return FALSE; + + emit_byte(state, 0xFF, return FALSE); + emit_byte(state, JPEG_RST0 + restart_num, return FALSE); + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < state->cinfo->comps_in_scan; ci++) + state->cur.last_dc_val[ci] = 0; + + /* The restart counter is not updated until we successfully write the MCU. */ + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of Huffman-compressed coefficients. + */ + +METHODDEF(boolean) +encode_mcu_huff (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + int blkn, ci; + jpeg_component_info * compptr; + + /* Load up working state */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! emit_restart(&state, entropy->next_restart_num)) + return FALSE; + } + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + if (! encode_one_block(&state, + MCU_data[blkn][0], state.cur.last_dc_val[ci], + entropy->dc_derived_tbls[compptr->dc_tbl_no], + entropy->ac_derived_tbls[compptr->ac_tbl_no])) + return FALSE; + /* Update last_dc_val */ + state.cur.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + /* Completed MCU, so update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed scan. + */ + +METHODDEF(void) +finish_pass_huff (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + + /* Load up working state ... flush_bits needs it */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Flush out the last data */ + if (! flush_bits(&state)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + + /* Update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); +} + + +/* + * Huffman coding optimization. + * + * We first scan the supplied data and count the number of uses of each symbol + * that is to be Huffman-coded. (This process MUST agree with the code above.) + * Then we build a Huffman coding tree for the observed counts. + * Symbols which are not needed at all for the particular image are not + * assigned any code, which saves space in the DHT marker as well as in + * the compressed data. + */ + +#ifdef ENTROPY_OPT_SUPPORTED + + +/* Process a single block's worth of coefficients */ + +LOCAL(void) +htest_one_block (j_compress_ptr cinfo, JCOEFPTR block, int last_dc_val, + long dc_counts[], long ac_counts[]) +{ + register int temp; + register int nbits; + register int k, r; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = block[0] - last_dc_val; + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count the Huffman symbol for the number of bits */ + dc_counts[nbits]++; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + ac_counts[0xF0]++; + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count Huffman symbol for run length / number of bits */ + ac_counts[(r << 4) + nbits]++; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + ac_counts[0]++; +} + + +/* + * Trial-encode one MCU's worth of Huffman-compressed coefficients. + * No data is actually output, so no suspension return is possible. + */ + +METHODDEF(boolean) +encode_mcu_gather (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn, ci; + jpeg_component_info * compptr; + + /* Take care of restart intervals if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Update restart state */ + entropy->restarts_to_go = cinfo->restart_interval; + } + entropy->restarts_to_go--; + } + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + htest_one_block(cinfo, MCU_data[blkn][0], entropy->saved.last_dc_val[ci], + entropy->dc_count_ptrs[compptr->dc_tbl_no], + entropy->ac_count_ptrs[compptr->ac_tbl_no]); + entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + return TRUE; +} + + +/* + * Generate the best Huffman code table for the given counts, fill htbl. + * Note this is also used by jcphuff.c. + * + * The JPEG standard requires that no symbol be assigned a codeword of all + * one bits (so that padding bits added at the end of a compressed segment + * can't look like a valid code). Because of the canonical ordering of + * codewords, this just means that there must be an unused slot in the + * longest codeword length category. Section K.2 of the JPEG spec suggests + * reserving such a slot by pretending that symbol 256 is a valid symbol + * with count 1. In theory that's not optimal; giving it count zero but + * including it in the symbol set anyway should give a better Huffman code. + * But the theoretically better code actually seems to come out worse in + * practice, because it produces more all-ones bytes (which incur stuffed + * zero bytes in the final file). In any case the difference is tiny. + * + * The JPEG standard requires Huffman codes to be no more than 16 bits long. + * If some symbols have a very small but nonzero probability, the Huffman tree + * must be adjusted to meet the code length restriction. We currently use + * the adjustment method suggested in JPEG section K.2. This method is *not* + * optimal; it may not choose the best possible limited-length code. But + * typically only very-low-frequency symbols will be given less-than-optimal + * lengths, so the code is almost optimal. Experimental comparisons against + * an optimal limited-length-code algorithm indicate that the difference is + * microscopic --- usually less than a hundredth of a percent of total size. + * So the extra complexity of an optimal algorithm doesn't seem worthwhile. + */ + +GLOBAL(void) +jpeg_gen_optimal_table (j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[]) +{ +#define MAX_CLEN 32 /* assumed maximum initial code length */ + UINT8 bits[MAX_CLEN+1]; /* bits[k] = # of symbols with code length k */ + int codesize[257]; /* codesize[k] = code length of symbol k */ + int others[257]; /* next symbol in current branch of tree */ + int c1, c2; + int p, i, j; + long v; + + /* This algorithm is explained in section K.2 of the JPEG standard */ + + MEMZERO(bits, SIZEOF(bits)); + MEMZERO(codesize, SIZEOF(codesize)); + for (i = 0; i < 257; i++) + others[i] = -1; /* init links to empty */ + + freq[256] = 1; /* make sure 256 has a nonzero count */ + /* Including the pseudo-symbol 256 in the Huffman procedure guarantees + * that no real symbol is given code-value of all ones, because 256 + * will be placed last in the largest codeword category. + */ + + /* Huffman's basic algorithm to assign optimal code lengths to symbols */ + + for (;;) { + /* Find the smallest nonzero frequency, set c1 = its symbol */ + /* In case of ties, take the larger symbol number */ + c1 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v) { + v = freq[i]; + c1 = i; + } + } + + /* Find the next smallest nonzero frequency, set c2 = its symbol */ + /* In case of ties, take the larger symbol number */ + c2 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v && i != c1) { + v = freq[i]; + c2 = i; + } + } + + /* Done if we've merged everything into one frequency */ + if (c2 < 0) + break; + + /* Else merge the two counts/trees */ + freq[c1] += freq[c2]; + freq[c2] = 0; + + /* Increment the codesize of everything in c1's tree branch */ + codesize[c1]++; + while (others[c1] >= 0) { + c1 = others[c1]; + codesize[c1]++; + } + + others[c1] = c2; /* chain c2 onto c1's tree branch */ + + /* Increment the codesize of everything in c2's tree branch */ + codesize[c2]++; + while (others[c2] >= 0) { + c2 = others[c2]; + codesize[c2]++; + } + } + + /* Now count the number of symbols of each code length */ + for (i = 0; i <= 256; i++) { + if (codesize[i]) { + /* The JPEG standard seems to think that this can't happen, */ + /* but I'm paranoid... */ + if (codesize[i] > MAX_CLEN) + ERREXIT(cinfo, JERR_HUFF_CLEN_OVERFLOW); + + bits[codesize[i]]++; + } + } + + /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure + * Huffman procedure assigned any such lengths, we must adjust the coding. + * Here is what the JPEG spec says about how this next bit works: + * Since symbols are paired for the longest Huffman code, the symbols are + * removed from this length category two at a time. The prefix for the pair + * (which is one bit shorter) is allocated to one of the pair; then, + * skipping the BITS entry for that prefix length, a code word from the next + * shortest nonzero BITS entry is converted into a prefix for two code words + * one bit longer. + */ + + for (i = MAX_CLEN; i > 16; i--) { + while (bits[i] > 0) { + j = i - 2; /* find length of new prefix to be used */ + while (bits[j] == 0) + j--; + + bits[i] -= 2; /* remove two symbols */ + bits[i-1]++; /* one goes in this length */ + bits[j+1] += 2; /* two new symbols in this length */ + bits[j]--; /* symbol of this length is now a prefix */ + } + } + + /* Remove the count for the pseudo-symbol 256 from the largest codelength */ + while (bits[i] == 0) /* find largest codelength still in use */ + i--; + bits[i]--; + + /* Return final symbol counts (only for lengths 0..16) */ + MEMCOPY(htbl->bits, bits, SIZEOF(htbl->bits)); + + /* Return a list of the symbols sorted by code length */ + /* It's not real clear to me why we don't need to consider the codelength + * changes made above, but the JPEG spec seems to think this works. + */ + p = 0; + for (i = 1; i <= MAX_CLEN; i++) { + for (j = 0; j <= 255; j++) { + if (codesize[j] == i) { + htbl->huffval[p] = (UINT8) j; + p++; + } + } + } + + /* Set sent_table FALSE so updated table will be written to JPEG file. */ + htbl->sent_table = FALSE; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF(void) +finish_pass_gather (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did_dc[NUM_HUFF_TBLS]; + boolean did_ac[NUM_HUFF_TBLS]; + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did_dc, SIZEOF(did_dc)); + MEMZERO(did_ac, SIZEOF(did_ac)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if (! did_dc[dctbl]) { + htblptr = & cinfo->dc_huff_tbl_ptrs[dctbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->dc_count_ptrs[dctbl]); + did_dc[dctbl] = TRUE; + } + if (! did_ac[actbl]) { + htblptr = & cinfo->ac_huff_tbl_ptrs[actbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->ac_count_ptrs[actbl]); + did_ac[actbl] = TRUE; + } + } +} + + +#endif /* ENTROPY_OPT_SUPPORTED */ + + +/* + * Module initialization routine for Huffman entropy encoding. + */ + +GLOBAL(void) +jinit_huff_encoder (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_huff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; +#ifdef ENTROPY_OPT_SUPPORTED + entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL; +#endif + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jchuff.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jchuff.h new file mode 100644 index 0000000..a9599fc --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jchuff.h @@ -0,0 +1,47 @@ +/* + * jchuff.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy encoding routines + * that are shared between the sequential encoder (jchuff.c) and the + * progressive encoder (jcphuff.c). No other modules need to see these. + */ + +/* The legal range of a DCT coefficient is + * -1024 .. +1023 for 8-bit data; + * -16384 .. +16383 for 12-bit data. + * Hence the magnitude should always fit in 10 or 14 bits respectively. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MAX_COEF_BITS 10 +#else +#define MAX_COEF_BITS 14 +#endif + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_c_derived_tbl jMkCDerived +#define jpeg_gen_optimal_table jGenOptTbl +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Expand a Huffman table definition into the derived format */ +EXTERN(void) jpeg_make_c_derived_tbl + JPP((j_compress_ptr cinfo, boolean isDC, int tblno, + c_derived_tbl ** pdtbl)); + +/* Generate an optimal table definition given the specified counts */ +EXTERN(void) jpeg_gen_optimal_table + JPP((j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[])); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcinit.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcinit.c new file mode 100644 index 0000000..5efffe3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcinit.c @@ -0,0 +1,72 @@ +/* + * jcinit.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains initialization logic for the JPEG compressor. + * This routine is in charge of selecting the modules to be executed and + * making an initialization call to each one. + * + * Logically, this code belongs in jcmaster.c. It's split out because + * linking this routine implies linking the entire compression library. + * For a transcoding-only application, we want to be able to use jcmaster.c + * without linking in the whole library. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Master selection of compression modules. + * This is done once at the start of processing an image. We determine + * which modules will be used and give them appropriate initialization calls. + */ + +GLOBAL(void) +jinit_compress_master (j_compress_ptr cinfo) +{ + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, FALSE /* full compression */); + + /* Preprocessing */ + if (! cinfo->raw_data_in) { + jinit_color_converter(cinfo); + jinit_downsampler(cinfo); + jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */); + } + /* Forward DCT */ + jinit_forward_dct(cinfo); + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_encoder(cinfo); + } + + /* Need a full-image coefficient buffer in any multi-pass mode. */ + jinit_c_coef_controller(cinfo, + (boolean) (cinfo->num_scans > 1 || cinfo->optimize_coding)); + jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmainct.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmainct.c new file mode 100644 index 0000000..e0279a7 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmainct.c @@ -0,0 +1,293 @@ +/* + * jcmainct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for compression. + * The main buffer lies between the pre-processor and the JPEG + * compressor proper; it holds downsampled data in the JPEG colorspace. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Note: currently, there is no operating mode in which a full-image buffer + * is needed at this step. If there were, that mode could not be used with + * "raw data" input, since this module is bypassed in that case. However, + * we've left the code here for possible use in special applications. + */ +#undef FULL_MAIN_BUFFER_SUPPORTED + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_main_controller pub; /* public fields */ + + JDIMENSION cur_iMCU_row; /* number of current iMCU row */ + JDIMENSION rowgroup_ctr; /* counts row groups received in iMCU row */ + boolean suspended; /* remember if we suspended output */ + J_BUF_MODE pass_mode; /* current operating mode */ + + /* If using just a strip buffer, this points to the entire set of buffers + * (we allocate one for each component). In the full-image case, this + * points to the currently accessible strips of the virtual arrays. + */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* If using full-image storage, this array holds pointers to virtual-array + * control blocks for each component. Unused if not full-image storage. + */ + jvirt_sarray_ptr whole_image[MAX_COMPONENTS]; +#endif +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + + +/* Forward declarations */ +METHODDEF(void) process_data_simple_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#ifdef FULL_MAIN_BUFFER_SUPPORTED +METHODDEF(void) process_data_buffer_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_main (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + /* Do nothing in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + main->cur_iMCU_row = 0; /* initialize counters */ + main->rowgroup_ctr = 0; + main->suspended = FALSE; + main->pass_mode = pass_mode; /* save mode for use by process_data */ + + switch (pass_mode) { + case JBUF_PASS_THRU: +#ifdef FULL_MAIN_BUFFER_SUPPORTED + if (main->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + main->pub.process_data = process_data_simple_main; + break; +#ifdef FULL_MAIN_BUFFER_SUPPORTED + case JBUF_SAVE_SOURCE: + case JBUF_CRANK_DEST: + case JBUF_SAVE_AND_PASS: + if (main->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + main->pub.process_data = process_data_buffer_main; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This routine handles the simple pass-through mode, + * where we have only a strip buffer. + */ + +METHODDEF(void) +process_data_simple_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + while (main->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Read input data if we haven't filled the main buffer yet */ + if (main->rowgroup_ctr < DCTSIZE) + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + + /* If we don't have a full iMCU row buffered, return to application for + * more data. Note that preprocessor will always pad to fill the iMCU row + * at the bottom of the image. + */ + if (main->rowgroup_ctr != DCTSIZE) + return; + + /* Send the completed row to the compressor */ + if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main->suspended) { + (*in_row_ctr)--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main->suspended) { + (*in_row_ctr)++; + main->suspended = FALSE; + } + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + +/* + * Process some data. + * This routine handles all of the modes that use a full-size buffer. + */ + +METHODDEF(void) +process_data_buffer_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci; + jpeg_component_info *compptr; + boolean writing = (main->pass_mode != JBUF_CRANK_DEST); + + while (main->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Realign the virtual buffers if at the start of an iMCU row. */ + if (main->rowgroup_ctr == 0) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->buffer[ci] = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, main->whole_image[ci], + main->cur_iMCU_row * (compptr->v_samp_factor * DCTSIZE), + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE), writing); + } + /* In a read pass, pretend we just read some source data. */ + if (! writing) { + *in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE; + main->rowgroup_ctr = DCTSIZE; + } + } + + /* If a write pass, read input data until the current iMCU row is full. */ + /* Note: preprocessor will pad if necessary to fill the last iMCU row. */ + if (writing) { + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + /* Return to application if we need more data to fill the iMCU row. */ + if (main->rowgroup_ctr < DCTSIZE) + return; + } + + /* Emit data, unless this is a sink-only pass. */ + if (main->pass_mode != JBUF_SAVE_SOURCE) { + if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main->suspended) { + (*in_row_ctr)--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main->suspended) { + (*in_row_ctr)++; + main->suspended = FALSE; + } + } + + /* If get here, we are done with this iMCU row. Mark buffer empty. */ + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + +#endif /* FULL_MAIN_BUFFER_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL(void) +jinit_c_main_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main; + int ci; + jpeg_component_info *compptr; + + main = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_c_main_controller *) main; + main->pub.start_pass = start_pass_main; + + /* We don't need to create a buffer in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + /* Create the buffer. It holds downsampled data, so each component + * may be of a different size. + */ + if (need_full_buffer) { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component */ + /* Note we pad the bottom to a multiple of the iMCU height */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->whole_image[ci] = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor) * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + main->whole_image[0] = NULL; /* flag for no virtual arrays */ +#endif + /* Allocate a strip buffer for each component */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmarker.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmarker.c new file mode 100644 index 0000000..3d1e6c6 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmarker.c @@ -0,0 +1,664 @@ +/* + * jcmarker.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to write JPEG datastream markers. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* Private state */ + +typedef struct { + struct jpeg_marker_writer pub; /* public fields */ + + unsigned int last_restart_interval; /* last DRI value emitted; 0 after SOI */ +} my_marker_writer; + +typedef my_marker_writer * my_marker_ptr; + + +/* + * Basic output routines. + * + * Note that we do not support suspension while writing a marker. + * Therefore, an application using suspension must ensure that there is + * enough buffer space for the initial markers (typ. 600-700 bytes) before + * calling jpeg_start_compress, and enough space to write the trailing EOI + * (a few bytes) before calling jpeg_finish_compress. Multipass compression + * modes are not supported at all with suspension, so those two are the only + * points where markers will be written. + */ + +LOCAL(void) +emit_byte (j_compress_ptr cinfo, int val) +/* Emit a byte */ +{ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *(dest->next_output_byte)++ = (JOCTET) val; + if (--dest->free_in_buffer == 0) { + if (! (*dest->empty_output_buffer) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } +} + + +LOCAL(void) +emit_marker (j_compress_ptr cinfo, JPEG_MARKER mark) +/* Emit a marker code */ +{ + emit_byte(cinfo, 0xFF); + emit_byte(cinfo, (int) mark); +} + + +LOCAL(void) +emit_2bytes (j_compress_ptr cinfo, int value) +/* Emit a 2-byte integer; these are always MSB first in JPEG files */ +{ + emit_byte(cinfo, (value >> 8) & 0xFF); + emit_byte(cinfo, value & 0xFF); +} + + +/* + * Routines to write specific marker types. + */ + +LOCAL(int) +emit_dqt (j_compress_ptr cinfo, int index) +/* Emit a DQT marker */ +/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */ +{ + JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index]; + int prec; + int i; + + if (qtbl == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, index); + + prec = 0; + for (i = 0; i < DCTSIZE2; i++) { + if (qtbl->quantval[i] > 255) + prec = 1; + } + + if (! qtbl->sent_table) { + emit_marker(cinfo, M_DQT); + + emit_2bytes(cinfo, prec ? DCTSIZE2*2 + 1 + 2 : DCTSIZE2 + 1 + 2); + + emit_byte(cinfo, index + (prec<<4)); + + for (i = 0; i < DCTSIZE2; i++) { + /* The table entries must be emitted in zigzag order. */ + unsigned int qval = qtbl->quantval[jpeg_natural_order[i]]; + if (prec) + emit_byte(cinfo, (int) (qval >> 8)); + emit_byte(cinfo, (int) (qval & 0xFF)); + } + + qtbl->sent_table = TRUE; + } + + return prec; +} + + +LOCAL(void) +emit_dht (j_compress_ptr cinfo, int index, boolean is_ac) +/* Emit a DHT marker */ +{ + JHUFF_TBL * htbl; + int length, i; + + if (is_ac) { + htbl = cinfo->ac_huff_tbl_ptrs[index]; + index += 0x10; /* output index has AC bit set */ + } else { + htbl = cinfo->dc_huff_tbl_ptrs[index]; + } + + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, index); + + if (! htbl->sent_table) { + emit_marker(cinfo, M_DHT); + + length = 0; + for (i = 1; i <= 16; i++) + length += htbl->bits[i]; + + emit_2bytes(cinfo, length + 2 + 1 + 16); + emit_byte(cinfo, index); + + for (i = 1; i <= 16; i++) + emit_byte(cinfo, htbl->bits[i]); + + for (i = 0; i < length; i++) + emit_byte(cinfo, htbl->huffval[i]); + + htbl->sent_table = TRUE; + } +} + + +LOCAL(void) +emit_dac (j_compress_ptr cinfo) +/* Emit a DAC marker */ +/* Since the useful info is so small, we want to emit all the tables in */ +/* one DAC marker. Therefore this routine does its own scan of the table. */ +{ +#ifdef C_ARITH_CODING_SUPPORTED + char dc_in_use[NUM_ARITH_TBLS]; + char ac_in_use[NUM_ARITH_TBLS]; + int length, i; + jpeg_component_info *compptr; + + for (i = 0; i < NUM_ARITH_TBLS; i++) + dc_in_use[i] = ac_in_use[i] = 0; + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + dc_in_use[compptr->dc_tbl_no] = 1; + ac_in_use[compptr->ac_tbl_no] = 1; + } + + length = 0; + for (i = 0; i < NUM_ARITH_TBLS; i++) + length += dc_in_use[i] + ac_in_use[i]; + + emit_marker(cinfo, M_DAC); + + emit_2bytes(cinfo, length*2 + 2); + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + if (dc_in_use[i]) { + emit_byte(cinfo, i); + emit_byte(cinfo, cinfo->arith_dc_L[i] + (cinfo->arith_dc_U[i]<<4)); + } + if (ac_in_use[i]) { + emit_byte(cinfo, i + 0x10); + emit_byte(cinfo, cinfo->arith_ac_K[i]); + } + } +#endif /* C_ARITH_CODING_SUPPORTED */ +} + + +LOCAL(void) +emit_dri (j_compress_ptr cinfo) +/* Emit a DRI marker */ +{ + emit_marker(cinfo, M_DRI); + + emit_2bytes(cinfo, 4); /* fixed length */ + + emit_2bytes(cinfo, (int) cinfo->restart_interval); +} + + +LOCAL(void) +emit_sof (j_compress_ptr cinfo, JPEG_MARKER code) +/* Emit a SOF marker */ +{ + int ci; + jpeg_component_info *compptr; + + emit_marker(cinfo, code); + + emit_2bytes(cinfo, 3 * cinfo->num_components + 2 + 5 + 1); /* length */ + + /* Make sure image isn't bigger than SOF field can handle */ + if ((long) cinfo->image_height > 65535L || + (long) cinfo->image_width > 65535L) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535); + + emit_byte(cinfo, cinfo->data_precision); + emit_2bytes(cinfo, (int) cinfo->image_height); + emit_2bytes(cinfo, (int) cinfo->image_width); + + emit_byte(cinfo, cinfo->num_components); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + emit_byte(cinfo, compptr->component_id); + emit_byte(cinfo, (compptr->h_samp_factor << 4) + compptr->v_samp_factor); + emit_byte(cinfo, compptr->quant_tbl_no); + } +} + + +LOCAL(void) +emit_sos (j_compress_ptr cinfo) +/* Emit a SOS marker */ +{ + int i, td, ta; + jpeg_component_info *compptr; + + emit_marker(cinfo, M_SOS); + + emit_2bytes(cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3); /* length */ + + emit_byte(cinfo, cinfo->comps_in_scan); + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + emit_byte(cinfo, compptr->component_id); + td = compptr->dc_tbl_no; + ta = compptr->ac_tbl_no; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan; + * furthermore, Huffman coding of DC refinement uses no table at all. + * We emit 0 for unused field(s); this is recommended by the P&M text + * but does not seem to be specified in the standard. + */ + if (cinfo->Ss == 0) { + ta = 0; /* DC scan */ + if (cinfo->Ah != 0 && !cinfo->arith_code) + td = 0; /* no DC table either */ + } else { + td = 0; /* AC scan */ + } + } + emit_byte(cinfo, (td << 4) + ta); + } + + emit_byte(cinfo, cinfo->Ss); + emit_byte(cinfo, cinfo->Se); + emit_byte(cinfo, (cinfo->Ah << 4) + cinfo->Al); +} + + +LOCAL(void) +emit_jfif_app0 (j_compress_ptr cinfo) +/* Emit a JFIF-compliant APP0 marker */ +{ + /* + * Length of APP0 block (2 bytes) + * Block ID (4 bytes - ASCII "JFIF") + * Zero byte (1 byte to terminate the ID string) + * Version Major, Minor (2 bytes - major first) + * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) + * Xdpu (2 bytes - dots per unit horizontal) + * Ydpu (2 bytes - dots per unit vertical) + * Thumbnail X size (1 byte) + * Thumbnail Y size (1 byte) + */ + + emit_marker(cinfo, M_APP0); + + emit_2bytes(cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */ + + emit_byte(cinfo, 0x4A); /* Identifier: ASCII "JFIF" */ + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0x49); + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0); + emit_byte(cinfo, cinfo->JFIF_major_version); /* Version fields */ + emit_byte(cinfo, cinfo->JFIF_minor_version); + emit_byte(cinfo, cinfo->density_unit); /* Pixel size information */ + emit_2bytes(cinfo, (int) cinfo->X_density); + emit_2bytes(cinfo, (int) cinfo->Y_density); + emit_byte(cinfo, 0); /* No thumbnail image */ + emit_byte(cinfo, 0); +} + + +LOCAL(void) +emit_adobe_app14 (j_compress_ptr cinfo) +/* Emit an Adobe APP14 marker */ +{ + /* + * Length of APP14 block (2 bytes) + * Block ID (5 bytes - ASCII "Adobe") + * Version Number (2 bytes - currently 100) + * Flags0 (2 bytes - currently 0) + * Flags1 (2 bytes - currently 0) + * Color transform (1 byte) + * + * Although Adobe TN 5116 mentions Version = 101, all the Adobe files + * now in circulation seem to use Version = 100, so that's what we write. + * + * We write the color transform byte as 1 if the JPEG color space is + * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with + * whether the encoder performed a transformation, which is pretty useless. + */ + + emit_marker(cinfo, M_APP14); + + emit_2bytes(cinfo, 2 + 5 + 2 + 2 + 2 + 1); /* length */ + + emit_byte(cinfo, 0x41); /* Identifier: ASCII "Adobe" */ + emit_byte(cinfo, 0x64); + emit_byte(cinfo, 0x6F); + emit_byte(cinfo, 0x62); + emit_byte(cinfo, 0x65); + emit_2bytes(cinfo, 100); /* Version */ + emit_2bytes(cinfo, 0); /* Flags0 */ + emit_2bytes(cinfo, 0); /* Flags1 */ + switch (cinfo->jpeg_color_space) { + case JCS_YCbCr: + emit_byte(cinfo, 1); /* Color transform = 1 */ + break; + case JCS_YCCK: + emit_byte(cinfo, 2); /* Color transform = 2 */ + break; + default: + emit_byte(cinfo, 0); /* Color transform = 0 */ + break; + } +} + + +/* + * These routines allow writing an arbitrary marker with parameters. + * The only intended use is to emit COM or APPn markers after calling + * write_file_header and before calling write_frame_header. + * Other uses are not guaranteed to produce desirable results. + * Counting the parameter bytes properly is the caller's responsibility. + */ + +METHODDEF(void) +write_marker_header (j_compress_ptr cinfo, int marker, unsigned int datalen) +/* Emit an arbitrary marker header */ +{ + if (datalen > (unsigned int) 65533) /* safety check */ + ERREXIT(cinfo, JERR_BAD_LENGTH); + + emit_marker(cinfo, (JPEG_MARKER) marker); + + emit_2bytes(cinfo, (int) (datalen + 2)); /* total length */ +} + +METHODDEF(void) +write_marker_byte (j_compress_ptr cinfo, int val) +/* Emit one byte of marker parameters following write_marker_header */ +{ + emit_byte(cinfo, val); +} + + +/* + * Write datastream header. + * This consists of an SOI and optional APPn markers. + * We recommend use of the JFIF marker, but not the Adobe marker, + * when using YCbCr or grayscale data. The JFIF marker should NOT + * be used for any other JPEG colorspace. The Adobe marker is helpful + * to distinguish RGB, CMYK, and YCCK colorspaces. + * Note that an application can write additional header markers after + * jpeg_start_compress returns. + */ + +METHODDEF(void) +write_file_header (j_compress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + + emit_marker(cinfo, M_SOI); /* first the SOI */ + + /* SOI is defined to reset restart interval to 0 */ + marker->last_restart_interval = 0; + + if (cinfo->write_JFIF_header) /* next an optional JFIF APP0 */ + emit_jfif_app0(cinfo); + if (cinfo->write_Adobe_marker) /* next an optional Adobe APP14 */ + emit_adobe_app14(cinfo); +} + + +/* + * Write frame header. + * This consists of DQT and SOFn markers. + * Note that we do not emit the SOF until we have emitted the DQT(s). + * This avoids compatibility problems with incorrect implementations that + * try to error-check the quant table numbers as soon as they see the SOF. + */ + +METHODDEF(void) +write_frame_header (j_compress_ptr cinfo) +{ + int ci, prec; + boolean is_baseline; + jpeg_component_info *compptr; + + /* Emit DQT for each quantization table. + * Note that emit_dqt() suppresses any duplicate tables. + */ + prec = 0; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prec += emit_dqt(cinfo, compptr->quant_tbl_no); + } + /* now prec is nonzero iff there are any 16-bit quant tables. */ + + /* Check for a non-baseline specification. + * Note we assume that Huffman table numbers won't be changed later. + */ + if (cinfo->arith_code || cinfo->progressive_mode || + cinfo->data_precision != 8) { + is_baseline = FALSE; + } else { + is_baseline = TRUE; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1) + is_baseline = FALSE; + } + if (prec && is_baseline) { + is_baseline = FALSE; + /* If it's baseline except for quantizer size, warn the user */ + TRACEMS(cinfo, 0, JTRC_16BIT_TABLES); + } + } + + /* Emit the proper SOF marker */ + if (cinfo->arith_code) { + emit_sof(cinfo, M_SOF9); /* SOF code for arithmetic coding */ + } else { + if (cinfo->progressive_mode) + emit_sof(cinfo, M_SOF2); /* SOF code for progressive Huffman */ + else if (is_baseline) + emit_sof(cinfo, M_SOF0); /* SOF code for baseline implementation */ + else + emit_sof(cinfo, M_SOF1); /* SOF code for non-baseline Huffman file */ + } +} + + +/* + * Write scan header. + * This consists of DHT or DAC markers, optional DRI, and SOS. + * Compressed data will be written following the SOS. + */ + +METHODDEF(void) +write_scan_header (j_compress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + int i; + jpeg_component_info *compptr; + + if (cinfo->arith_code) { + /* Emit arith conditioning info. We may have some duplication + * if the file has multiple scans, but it's so small it's hardly + * worth worrying about. + */ + emit_dac(cinfo); + } else { + /* Emit Huffman tables. + * Note that emit_dht() suppresses any duplicate tables. + */ + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan */ + if (cinfo->Ss == 0) { + if (cinfo->Ah == 0) /* DC needs no table for refinement scan */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + } else { + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } else { + /* Sequential mode: need both DC and AC tables */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } + } + + /* Emit DRI if required --- note that DRI value could change for each scan. + * We avoid wasting space with unnecessary DRIs, however. + */ + if (cinfo->restart_interval != marker->last_restart_interval) { + emit_dri(cinfo); + marker->last_restart_interval = cinfo->restart_interval; + } + + emit_sos(cinfo); +} + + +/* + * Write datastream trailer. + */ + +METHODDEF(void) +write_file_trailer (j_compress_ptr cinfo) +{ + emit_marker(cinfo, M_EOI); +} + + +/* + * Write an abbreviated table-specification datastream. + * This consists of SOI, DQT and DHT tables, and EOI. + * Any table that is defined and not marked sent_table = TRUE will be + * emitted. Note that all tables will be marked sent_table = TRUE at exit. + */ + +METHODDEF(void) +write_tables_only (j_compress_ptr cinfo) +{ + int i; + + emit_marker(cinfo, M_SOI); + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if (cinfo->quant_tbl_ptrs[i] != NULL) + (void) emit_dqt(cinfo, i); + } + + if (! cinfo->arith_code) { + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if (cinfo->dc_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, FALSE); + if (cinfo->ac_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, TRUE); + } + } + + emit_marker(cinfo, M_EOI); +} + + +/* + * Initialize the marker writer module. + */ + +GLOBAL(void) +jinit_marker_writer (j_compress_ptr cinfo) +{ + my_marker_ptr marker; + + /* Create the subobject */ + marker = (my_marker_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_marker_writer)); + cinfo->marker = (struct jpeg_marker_writer *) marker; + /* Initialize method pointers */ + marker->pub.write_file_header = write_file_header; + marker->pub.write_frame_header = write_frame_header; + marker->pub.write_scan_header = write_scan_header; + marker->pub.write_file_trailer = write_file_trailer; + marker->pub.write_tables_only = write_tables_only; + marker->pub.write_marker_header = write_marker_header; + marker->pub.write_marker_byte = write_marker_byte; + /* Initialize private state */ + marker->last_restart_interval = 0; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmaster.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmaster.c new file mode 100644 index 0000000..aab4020 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcmaster.c @@ -0,0 +1,590 @@ +/* + * jcmaster.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG compressor. + * These routines are concerned with parameter validation, initial setup, + * and inter-pass control (determining the number of passes and the work + * to be done in each pass). + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef enum { + main_pass, /* input data, also do first output step */ + huff_opt_pass, /* Huffman code optimization pass */ + output_pass /* data output pass */ +} c_pass_type; + +typedef struct { + struct jpeg_comp_master pub; /* public fields */ + + c_pass_type pass_type; /* the type of the current pass */ + + int pass_number; /* # of passes completed */ + int total_passes; /* total # of passes needed */ + + int scan_number; /* current index in scan_info[] */ +} my_comp_master; + +typedef my_comp_master * my_master_ptr; + + +/* + * Support routines that do various essential calculations. + */ + +LOCAL(void) +initial_setup (j_compress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ + int ci; + jpeg_component_info *compptr; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Sanity check on image dimensions */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0 || cinfo->input_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* Width of an input scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Fill in the correct component_index value; don't rely on application */ + compptr->component_index = ci; + /* For compression, we never do DCT scaling. */ + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed (this flag isn't actually used for compression) */ + compptr->component_needed = TRUE; + } + + /* Compute number of fully interleaved MCU rows (number of times that + * main controller will call coefficient controller). + */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); +} + + +#ifdef C_MULTISCAN_FILES_SUPPORTED + +LOCAL(void) +validate_script (j_compress_ptr cinfo) +/* Verify that the scan script in cinfo->scan_info[] is valid; also + * determine whether it uses progressive JPEG, and set cinfo->progressive_mode. + */ +{ + const jpeg_scan_info * scanptr; + int scanno, ncomps, ci, coefi, thisi; + int Ss, Se, Ah, Al; + boolean component_sent[MAX_COMPONENTS]; +#ifdef C_PROGRESSIVE_SUPPORTED + int * last_bitpos_ptr; + int last_bitpos[MAX_COMPONENTS][DCTSIZE2]; + /* -1 until that coefficient has been seen; then last Al for it */ +#endif + + if (cinfo->num_scans <= 0) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0); + + /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1; + * for progressive JPEG, no scan can have this. + */ + scanptr = cinfo->scan_info; + if (scanptr->Ss != 0 || scanptr->Se != DCTSIZE2-1) { +#ifdef C_PROGRESSIVE_SUPPORTED + cinfo->progressive_mode = TRUE; + last_bitpos_ptr = & last_bitpos[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (coefi = 0; coefi < DCTSIZE2; coefi++) + *last_bitpos_ptr++ = -1; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + for (ci = 0; ci < cinfo->num_components; ci++) + component_sent[ci] = FALSE; + } + + for (scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++) { + /* Validate component indexes */ + ncomps = scanptr->comps_in_scan; + if (ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN); + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (thisi < 0 || thisi >= cinfo->num_components) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + /* Components must appear in SOF order within each scan */ + if (ci > 0 && thisi <= scanptr->component_index[ci-1]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + } + /* Validate progression parameters */ + Ss = scanptr->Ss; + Se = scanptr->Se; + Ah = scanptr->Ah; + Al = scanptr->Al; + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* The JPEG spec simply gives the ranges 0..13 for Ah and Al, but that + * seems wrong: the upper bound ought to depend on data precision. + * Perhaps they really meant 0..N+1 for N-bit precision. + * Here we allow 0..10 for 8-bit data; Al larger than 10 results in + * out-of-range reconstructed DC values during the first DC scan, + * which might cause problems for some decoders. + */ +#if BITS_IN_JSAMPLE == 8 +#define MAX_AH_AL 10 +#else +#define MAX_AH_AL 13 +#endif + if (Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 || + Ah < 0 || Ah > MAX_AH_AL || Al < 0 || Al > MAX_AH_AL) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + if (Ss == 0) { + if (Se != 0) /* DC and AC together not OK */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + if (ncomps != 1) /* AC scans must be for only one component */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + for (ci = 0; ci < ncomps; ci++) { + last_bitpos_ptr = & last_bitpos[scanptr->component_index[ci]][0]; + if (Ss != 0 && last_bitpos_ptr[0] < 0) /* AC without prior DC scan */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + for (coefi = Ss; coefi <= Se; coefi++) { + if (last_bitpos_ptr[coefi] < 0) { + /* first scan of this coefficient */ + if (Ah != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + /* not first scan */ + if (Ah != last_bitpos_ptr[coefi] || Al != Ah-1) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + last_bitpos_ptr[coefi] = Al; + } + } +#endif + } else { + /* For sequential JPEG, all progression parameters must be these: */ + if (Ss != 0 || Se != DCTSIZE2-1 || Ah != 0 || Al != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + /* Make sure components are not sent twice */ + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (component_sent[thisi]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + component_sent[thisi] = TRUE; + } + } + } + + /* Now verify that everything got sent. */ + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* For progressive mode, we only check that at least some DC data + * got sent for each component; the spec does not require that all bits + * of all coefficients be transmitted. Would it be wiser to enforce + * transmission of all coefficient bits?? + */ + for (ci = 0; ci < cinfo->num_components; ci++) { + if (last_bitpos[ci][0] < 0) + ERREXIT(cinfo, JERR_MISSING_DATA); + } +#endif + } else { + for (ci = 0; ci < cinfo->num_components; ci++) { + if (! component_sent[ci]) + ERREXIT(cinfo, JERR_MISSING_DATA); + } + } +} + +#endif /* C_MULTISCAN_FILES_SUPPORTED */ + + +LOCAL(void) +select_scan_parameters (j_compress_ptr cinfo) +/* Set up the scan parameters for the current scan */ +{ + int ci; + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if (cinfo->scan_info != NULL) { + /* Prepare for current scan --- the script is already validated */ + my_master_ptr master = (my_master_ptr) cinfo->master; + const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number; + + cinfo->comps_in_scan = scanptr->comps_in_scan; + for (ci = 0; ci < scanptr->comps_in_scan; ci++) { + cinfo->cur_comp_info[ci] = + &cinfo->comp_info[scanptr->component_index[ci]]; + } + cinfo->Ss = scanptr->Ss; + cinfo->Se = scanptr->Se; + cinfo->Ah = scanptr->Ah; + cinfo->Al = scanptr->Al; + } + else +#endif + { + /* Prepare for single sequential-JPEG scan containing all components */ + if (cinfo->num_components > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPS_IN_SCAN); + cinfo->comps_in_scan = cinfo->num_components; + for (ci = 0; ci < cinfo->num_components; ci++) { + cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci]; + } + cinfo->Ss = 0; + cinfo->Se = DCTSIZE2-1; + cinfo->Ah = 0; + cinfo->Al = 0; + } +} + + +LOCAL(void) +per_scan_setup (j_compress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = DCTSIZE; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * DCTSIZE; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } + + /* Convert restart specified in rows to actual MCU count. */ + /* Note that count must fit in 16 bits, so we provide limiting. */ + if (cinfo->restart_in_rows > 0) { + long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row; + cinfo->restart_interval = (unsigned int) MIN(nominal, 65535L); + } +} + + +/* + * Per-pass setup. + * This is called at the beginning of each pass. We determine which modules + * will be active during this pass and give them appropriate start_pass calls. + * We also set is_last_pass to indicate whether any more passes will be + * required. + */ + +METHODDEF(void) +prepare_for_pass (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + switch (master->pass_type) { + case main_pass: + /* Initial pass: will collect input data, and do either Huffman + * optimization or data output for the first scan. + */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (! cinfo->raw_data_in) { + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->downsample->start_pass) (cinfo); + (*cinfo->prep->start_pass) (cinfo, JBUF_PASS_THRU); + } + (*cinfo->fdct->start_pass) (cinfo); + (*cinfo->entropy->start_pass) (cinfo, cinfo->optimize_coding); + (*cinfo->coef->start_pass) (cinfo, + (master->total_passes > 1 ? + JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + if (cinfo->optimize_coding) { + /* No immediate data output; postpone writing frame/scan headers */ + master->pub.call_pass_startup = FALSE; + } else { + /* Will write frame/scan headers at first jpeg_write_scanlines call */ + master->pub.call_pass_startup = TRUE; + } + break; +#ifdef ENTROPY_OPT_SUPPORTED + case huff_opt_pass: + /* Do Huffman optimization for a scan after the first one. */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (cinfo->Ss != 0 || cinfo->Ah == 0 || cinfo->arith_code) { + (*cinfo->entropy->start_pass) (cinfo, TRUE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + master->pub.call_pass_startup = FALSE; + break; + } + /* Special case: Huffman DC refinement scans need no Huffman table + * and therefore we can skip the optimization pass for them. + */ + master->pass_type = output_pass; + master->pass_number++; + /*FALLTHROUGH*/ +#endif + case output_pass: + /* Do a data-output pass. */ + /* We need not repeat per-scan setup if prior optimization pass did it. */ + if (! cinfo->optimize_coding) { + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + } + (*cinfo->entropy->start_pass) (cinfo, FALSE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + /* We emit frame/scan headers now */ + if (master->scan_number == 0) + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); + master->pub.call_pass_startup = FALSE; + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + } + + master->pub.is_last_pass = (master->pass_number == master->total_passes-1); + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->total_passes; + } +} + + +/* + * Special start-of-pass hook. + * This is called by jpeg_write_scanlines if call_pass_startup is TRUE. + * In single-pass processing, we need this hook because we don't want to + * write frame/scan headers during jpeg_start_compress; we want to let the + * application write COM markers etc. between jpeg_start_compress and the + * jpeg_write_scanlines loop. + * In multi-pass processing, this routine is not used. + */ + +METHODDEF(void) +pass_startup (j_compress_ptr cinfo) +{ + cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */ + + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); +} + + +/* + * Finish up at end of pass. + */ + +METHODDEF(void) +finish_pass_master (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* The entropy coder always needs an end-of-pass call, + * either to analyze statistics or to flush its output buffer. + */ + (*cinfo->entropy->finish_pass) (cinfo); + + /* Update state for next pass */ + switch (master->pass_type) { + case main_pass: + /* next pass is either output of scan 0 (after optimization) + * or output of scan 1 (if no optimization). + */ + master->pass_type = output_pass; + if (! cinfo->optimize_coding) + master->scan_number++; + break; + case huff_opt_pass: + /* next pass is always output of current scan */ + master->pass_type = output_pass; + break; + case output_pass: + /* next pass is either optimization or output of next scan */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + master->scan_number++; + break; + } + + master->pass_number++; +} + + +/* + * Initialize master compression control. + */ + +GLOBAL(void) +jinit_c_master_control (j_compress_ptr cinfo, boolean transcode_only) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_comp_master)); + cinfo->master = (struct jpeg_comp_master *) master; + master->pub.prepare_for_pass = prepare_for_pass; + master->pub.pass_startup = pass_startup; + master->pub.finish_pass = finish_pass_master; + master->pub.is_last_pass = FALSE; + + /* Validate parameters, determine derived values */ + initial_setup(cinfo); + + if (cinfo->scan_info != NULL) { +#ifdef C_MULTISCAN_FILES_SUPPORTED + validate_script(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + cinfo->num_scans = 1; + } + + if (cinfo->progressive_mode) /* TEMPORARY HACK ??? */ + cinfo->optimize_coding = TRUE; /* assume default tables no good for progressive mode */ + + /* Initialize my private state */ + if (transcode_only) { + /* no main pass in transcoding */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + else + master->pass_type = output_pass; + } else { + /* for normal compression, first pass is always this type: */ + master->pass_type = main_pass; + } + master->scan_number = 0; + master->pass_number = 0; + if (cinfo->optimize_coding) + master->total_passes = cinfo->num_scans * 2; + else + master->total_passes = cinfo->num_scans; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcomapi.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcomapi.c new file mode 100644 index 0000000..9b1fa75 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcomapi.c @@ -0,0 +1,106 @@ +/* + * jcomapi.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL(void) +jpeg_abort (j_common_ptr cinfo) +{ + int pool; + + /* Do nothing if called on a not-initialized or destroyed JPEG object. */ + if (cinfo->mem == NULL) + return; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) { + (*cinfo->mem->free_pool) (cinfo, pool); + } + + /* Reset overall state for possible reuse of object */ + if (cinfo->is_decompressor) { + cinfo->global_state = DSTATE_START; + /* Try to keep application from accessing now-deleted marker list. + * A bit kludgy to do it here, but this is the most central place. + */ + ((j_decompress_ptr) cinfo)->marker_list = NULL; + } else { + cinfo->global_state = CSTATE_START; + } +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL(void) +jpeg_destroy (j_common_ptr cinfo) +{ + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if (cinfo->mem != NULL) + (*cinfo->mem->self_destruct) (cinfo); + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL(JQUANT_TBL *) +jpeg_alloc_quant_table (j_common_ptr cinfo) +{ + JQUANT_TBL *tbl; + + tbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL(JHUFF_TBL *) +jpeg_alloc_huff_table (j_common_ptr cinfo) +{ + JHUFF_TBL *tbl; + + tbl = (JHUFF_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jconfig.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jconfig.h new file mode 100644 index 0000000..1d1d6fd --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jconfig.h @@ -0,0 +1,15 @@ +#define HAVE_PROTOTYPES +#define HAVE_STDLIB_H +#define HAVE_UNSIGNED_SHORT +#define HAVE_UNSIGNED_CHAR +#ifndef _WIN32 +#define NEED_SYS_TYPES_H +#define HAVE_STDDEF_H +#else +#include +#define XMD_H +#undef FAR +#define HAVE_BOOLEAN +typedef short INT16; +#endif + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcparam.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcparam.c new file mode 100644 index 0000000..6fc48f5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcparam.c @@ -0,0 +1,610 @@ +/* + * jcparam.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains optional default-setting code for the JPEG compressor. + * Applications do not have to use this file, but those that don't use it + * must know a lot more about the innards of the JPEG code. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Quantization table setup routines + */ + +GLOBAL(void) +jpeg_add_quant_table (j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline) +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ +{ + JQUANT_TBL ** qtblptr; + int i; + long temp; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (which_tbl < 0 || which_tbl >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, which_tbl); + + qtblptr = & cinfo->quant_tbl_ptrs[which_tbl]; + + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) cinfo); + + for (i = 0; i < DCTSIZE2; i++) { + temp = ((long) basic_table[i] * scale_factor + 50L) / 100L; + /* limit the values to the valid range */ + if (temp <= 0L) temp = 1L; + if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */ + if (force_baseline && temp > 255L) + temp = 255L; /* limit to baseline range if requested */ + (*qtblptr)->quantval[i] = (UINT16) temp; + } + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*qtblptr)->sent_table = FALSE; +} + + +GLOBAL(void) +jpeg_set_linear_quality (j_compress_ptr cinfo, int scale_factor, + boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables + * and a straight percentage-scaling quality scale. In most cases it's better + * to use jpeg_set_quality (below); this entry point is provided for + * applications that insist on a linear percentage scaling. + */ +{ + /* These are the sample quantization tables given in JPEG spec section K.1. + * The spec says that the values given produce "good" quality, and + * when divided by 2, "very good" quality. + */ + static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 + }; + static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + }; + + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl, + scale_factor, force_baseline); + jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl, + scale_factor, force_baseline); +} + + +GLOBAL(int) +jpeg_quality_scaling (int quality) +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +{ + /* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if (quality <= 0) quality = 1; + if (quality > 100) quality = 100; + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table + * to make all the table entries 1 (hence, minimum quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if (quality < 50) + quality = 5000 / quality; + else + quality = 200 - quality*2; + + return quality; +} + + +GLOBAL(void) +jpeg_set_quality (j_compress_ptr cinfo, int quality, boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +{ + /* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling(quality); + + /* Set up standard quality tables */ + jpeg_set_linear_quality(cinfo, quality, force_baseline); +} + + +/* + * Huffman table setup routines + */ + +LOCAL(void) +add_huff_table (j_compress_ptr cinfo, + JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val) +/* Define a Huffman table */ +{ + int nsymbols, len; + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + /* Copy the number-of-symbols-of-each-code-length counts */ + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + + /* Validate the counts. We do this here mainly so we can copy the right + * number of symbols from the val[] array, without risking marching off + * the end of memory. jchuff.c will do a more thorough test later. + */ + nsymbols = 0; + for (len = 1; len <= 16; len++) + nsymbols += bits[len]; + if (nsymbols < 1 || nsymbols > 256) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + + MEMCOPY((*htblptr)->huffval, val, nsymbols * SIZEOF(UINT8)); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*htblptr)->sent_table = FALSE; +} + + +LOCAL(void) +std_huff_tables (j_compress_ptr cinfo) +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ +{ + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + static const UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[0], + bits_dc_luminance, val_dc_luminance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[0], + bits_ac_luminance, val_ac_luminance); + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[1], + bits_dc_chrominance, val_dc_chrominance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[1], + bits_ac_chrominance, val_ac_chrominance); +} + + +/* + * Default parameter setup for compression. + * + * Applications that don't choose to use this routine must do their + * own setup of all these parameters. Alternately, you can call this + * to establish defaults and then alter parameters selectively. This + * is the recommended approach since, if we add any new parameters, + * your code will still work (they'll be set to reasonable defaults). + */ + +GLOBAL(void) +jpeg_set_defaults (j_compress_ptr cinfo) +{ + int i; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Allocate comp_info array large enough for maximum component count. + * Array is made permanent in case application wants to compress + * multiple images at same param settings. + */ + if (cinfo->comp_info == NULL) + cinfo->comp_info = (jpeg_component_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + MAX_COMPONENTS * SIZEOF(jpeg_component_info)); + + /* Initialize everything not dependent on the color space */ + + cinfo->data_precision = BITS_IN_JSAMPLE; + /* Set up two quantization tables using default quality of 75 */ + jpeg_set_quality(cinfo, 75, TRUE); + /* Set up two Huffman tables */ + std_huff_tables(cinfo); + + /* Initialize default arithmetic coding conditioning */ + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + + /* Default is no multiple-scan output */ + cinfo->scan_info = NULL; + cinfo->num_scans = 0; + + /* Expect normal source image, not raw downsampled data */ + cinfo->raw_data_in = FALSE; + + /* Use Huffman coding, not arithmetic coding, by default */ + cinfo->arith_code = FALSE; + + /* By default, don't do extra passes to optimize entropy coding */ + cinfo->optimize_coding = FALSE; + /* The standard Huffman tables are only valid for 8-bit data precision. + * If the precision is higher, force optimization on so that usable + * tables will be computed. This test can be removed if default tables + * are supplied that are valid for the desired precision. + */ + if (cinfo->data_precision > 8) + cinfo->optimize_coding = TRUE; + + /* By default, use the simpler non-cosited sampling alignment */ + cinfo->CCIR601_sampling = FALSE; + + /* No input smoothing */ + cinfo->smoothing_factor = 0; + + /* DCT algorithm preference */ + cinfo->dct_method = JDCT_DEFAULT; + + /* No restart markers */ + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + + /* Fill in default JFIF marker parameters. Note that whether the marker + * will actually be written is determined by jpeg_set_colorspace. + * + * By default, the library emits JFIF version code 1.01. + * An application that wants to emit JFIF 1.02 extension markers should set + * JFIF_minor_version to 2. We could probably get away with just defaulting + * to 1.02, but there may still be some decoders in use that will complain + * about that; saying 1.01 should minimize compatibility problems. + */ + cinfo->JFIF_major_version = 1; /* Default JFIF version = 1.01 */ + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; /* Pixel size is unknown by default */ + cinfo->X_density = 1; /* Pixel aspect ratio is square by default */ + cinfo->Y_density = 1; + + /* Choose JPEG colorspace based on input space, set defaults accordingly */ + + jpeg_default_colorspace(cinfo); +} + + +/* + * Select an appropriate JPEG colorspace for in_color_space. + */ + +GLOBAL(void) +jpeg_default_colorspace (j_compress_ptr cinfo) +{ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); + break; + case JCS_RGB: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_YCbCr: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_CMYK: + jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */ + break; + case JCS_YCCK: + jpeg_set_colorspace(cinfo, JCS_YCCK); + break; + case JCS_UNKNOWN: + jpeg_set_colorspace(cinfo, JCS_UNKNOWN); + break; + default: + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + } +} + + +/* + * Set the JPEG colorspace, and choose colorspace-dependent default values. + */ + +GLOBAL(void) +jpeg_set_colorspace (j_compress_ptr cinfo, J_COLOR_SPACE colorspace) +{ + jpeg_component_info * compptr; + int ci; + +#define SET_COMP(index,id,hsamp,vsamp,quant,dctbl,actbl) \ + (compptr = &cinfo->comp_info[index], \ + compptr->component_id = (id), \ + compptr->h_samp_factor = (hsamp), \ + compptr->v_samp_factor = (vsamp), \ + compptr->quant_tbl_no = (quant), \ + compptr->dc_tbl_no = (dctbl), \ + compptr->ac_tbl_no = (actbl) ) + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* For all colorspaces, we use Q and Huff tables 0 for luminance components, + * tables 1 for chrominance components. + */ + + cinfo->jpeg_color_space = colorspace; + + cinfo->write_JFIF_header = FALSE; /* No marker for non-JFIF colorspaces */ + cinfo->write_Adobe_marker = FALSE; /* write no Adobe marker by default */ + + switch (colorspace) { + case JCS_GRAYSCALE: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 1; + /* JFIF specifies component ID 1 */ + SET_COMP(0, 1, 1,1, 0, 0,0); + break; + case JCS_RGB: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag RGB */ + cinfo->num_components = 3; + SET_COMP(0, 0x52 /* 'R' */, 1,1, 0, 0,0); + SET_COMP(1, 0x47 /* 'G' */, 1,1, 0, 0,0); + SET_COMP(2, 0x42 /* 'B' */, 1,1, 0, 0,0); + break; + case JCS_YCbCr: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 3; + /* JFIF specifies component IDs 1,2,3 */ + /* We default to 2x2 subsamples of chrominance */ + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + break; + case JCS_CMYK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */ + cinfo->num_components = 4; + SET_COMP(0, 0x43 /* 'C' */, 1,1, 0, 0,0); + SET_COMP(1, 0x4D /* 'M' */, 1,1, 0, 0,0); + SET_COMP(2, 0x59 /* 'Y' */, 1,1, 0, 0,0); + SET_COMP(3, 0x4B /* 'K' */, 1,1, 0, 0,0); + break; + case JCS_YCCK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag YCCK */ + cinfo->num_components = 4; + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + SET_COMP(3, 4, 2,2, 0, 0,0); + break; + case JCS_UNKNOWN: + cinfo->num_components = cinfo->input_components; + if (cinfo->num_components < 1 || cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + for (ci = 0; ci < cinfo->num_components; ci++) { + SET_COMP(ci, ci, 1,1, 0, 0,0); + } + break; + default: + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + } +} + + +#ifdef C_PROGRESSIVE_SUPPORTED + +LOCAL(jpeg_scan_info *) +fill_a_scan (jpeg_scan_info * scanptr, int ci, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for specified component */ +{ + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + return scanptr; +} + +LOCAL(jpeg_scan_info *) +fill_scans (jpeg_scan_info * scanptr, int ncomps, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for each component */ +{ + int ci; + + for (ci = 0; ci < ncomps; ci++) { + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } + return scanptr; +} + +LOCAL(jpeg_scan_info *) +fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al) +/* Support routine: generate interleaved DC scan if possible, else N scans */ +{ + int ci; + + if (ncomps <= MAX_COMPS_IN_SCAN) { + /* Single interleaved DC scan */ + scanptr->comps_in_scan = ncomps; + for (ci = 0; ci < ncomps; ci++) + scanptr->component_index[ci] = ci; + scanptr->Ss = scanptr->Se = 0; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } else { + /* Noninterleaved DC scan for each component */ + scanptr = fill_scans(scanptr, ncomps, 0, 0, Ah, Al); + } + return scanptr; +} + + +/* + * Create a recommended progressive-JPEG script. + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +GLOBAL(void) +jpeg_simple_progression (j_compress_ptr cinfo) +{ + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Figure space needed for script. Calculation must match code below! */ + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + nscans = 10; + } else { + /* All-purpose script for other color spaces. */ + if (ncomps > MAX_COMPS_IN_SCAN) + nscans = 6 * ncomps; /* 2 DC + 4 AC scans per component */ + else + nscans = 2 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } + + /* Allocate space for script. + * We need to put it in the permanent pool in case the application performs + * multiple compressions without changing the settings. To avoid a memory + * leak if jpeg_simple_progression is called repeatedly for the same JPEG + * object, we try to re-use previously allocated space, and we allocate + * enough space to handle YCbCr even if initially asked for grayscale. + */ + if (cinfo->script_space == NULL || cinfo->script_space_size < nscans) { + cinfo->script_space_size = MAX(nscans, 10); + cinfo->script_space = (jpeg_scan_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + cinfo->script_space_size * SIZEOF(jpeg_scan_info)); + } + scanptr = cinfo->script_space; + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + /* Initial DC scan */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + /* Initial AC scan: get some luma data out in a hurry */ + scanptr = fill_a_scan(scanptr, 0, 1, 5, 0, 2); + /* Chroma data is too small to be worth expending many scans on */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 0, 1); + /* Complete spectral selection for luma AC */ + scanptr = fill_a_scan(scanptr, 0, 6, 63, 0, 2); + /* Refine next bit of luma AC */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 2, 1); + /* Finish DC successive approximation */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + /* Finish AC successive approximation */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 1, 0); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 1, 0); + /* Luma bottom bit comes last since it's usually largest scan */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 1, 0); + } else { + /* All-purpose script for other color spaces. */ + /* Successive approximation first pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + scanptr = fill_scans(scanptr, ncomps, 1, 5, 0, 2); + scanptr = fill_scans(scanptr, ncomps, 6, 63, 0, 2); + /* Successive approximation second pass */ + scanptr = fill_scans(scanptr, ncomps, 1, 63, 2, 1); + /* Successive approximation final pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + scanptr = fill_scans(scanptr, ncomps, 1, 63, 1, 0); + } +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcphuff.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcphuff.c new file mode 100644 index 0000000..07f9178 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcphuff.c @@ -0,0 +1,833 @@ +/* + * jcphuff.c + * + * Copyright (C) 1995-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines for progressive JPEG. + * + * We do not support output suspension in this module, since the library + * currently does not allow multiple-scan files to be written with output + * suspension. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jchuff.c */ + +#ifdef C_PROGRESSIVE_SUPPORTED + +/* Expanded entropy encoder object for progressive Huffman encoding. */ + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + /* Mode flag: TRUE for optimization, FALSE for actual data output */ + boolean gather_statistics; + + /* Bit-level coding status. + * next_output_byte/free_in_buffer are local copies of cinfo->dest fields. + */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + j_compress_ptr cinfo; /* link to cinfo (needed for dump_buffer) */ + + /* Coding status for DC components */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + + /* Coding status for AC components */ + int ac_tbl_no; /* the table number of the single component */ + unsigned int EOBRUN; /* run length of EOBs */ + unsigned int BE; /* # of buffered correction bits before MCU */ + char * bit_buffer; /* buffer for correction bits (1 per char) */ + /* packing correction bits tightly would save some space but cost time... */ + + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan). + * Since any one scan codes only DC or only AC, we only need one set + * of tables, not one for DC and one for AC. + */ + c_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + /* Statistics tables for optimization; again, one set is enough */ + long * count_ptrs[NUM_HUFF_TBLS]; +} phuff_entropy_encoder; + +typedef phuff_entropy_encoder * phuff_entropy_ptr; + +/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit + * buffer can hold. Larger sizes may slightly improve compression, but + * 1000 is already well into the realm of overkill. + * The minimum safe size is 64 bits. + */ + +#define MAX_CORR_BITS 1000 /* Max # of correction bits I can buffer */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~0) << (16-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +/* Forward declarations */ +METHODDEF(boolean) encode_mcu_DC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) encode_mcu_AC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) encode_mcu_DC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) encode_mcu_AC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(void) finish_pass_phuff JPP((j_compress_ptr cinfo)); +METHODDEF(void) finish_pass_gather_phuff JPP((j_compress_ptr cinfo)); + + +/* + * Initialize for a Huffman-compressed scan using progressive JPEG. + */ + +METHODDEF(void) +start_pass_phuff (j_compress_ptr cinfo, boolean gather_statistics) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + + entropy->cinfo = cinfo; + entropy->gather_statistics = gather_statistics; + + is_DC_band = (cinfo->Ss == 0); + + /* We assume jcmaster.c already validated the scan parameters. */ + + /* Select execution routines */ + if (cinfo->Ah == 0) { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_first; + else + entropy->pub.encode_mcu = encode_mcu_AC_first; + } else { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_refine; + else { + entropy->pub.encode_mcu = encode_mcu_AC_refine; + /* AC refinement needs a correction bit buffer */ + if (entropy->bit_buffer == NULL) + entropy->bit_buffer = (char *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + MAX_CORR_BITS * SIZEOF(char)); + } + } + if (gather_statistics) + entropy->pub.finish_pass = finish_pass_gather_phuff; + else + entropy->pub.finish_pass = finish_pass_phuff; + + /* Only DC coefficients may be interleaved, so cinfo->comps_in_scan = 1 + * for AC coefficients. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + /* Get table index */ + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + } else { + entropy->ac_tbl_no = tbl = compptr->ac_tbl_no; + } + if (gather_statistics) { + /* Check for invalid table index */ + /* (make_c_derived_tbl does this in the other path) */ + if (tbl < 0 || tbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tbl); + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->count_ptrs[tbl] == NULL) + entropy->count_ptrs[tbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->count_ptrs[tbl], 257 * SIZEOF(long)); + } else { + /* Compute derived values for Huffman table */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl(cinfo, is_DC_band, tbl, + & entropy->derived_tbls[tbl]); + } + } + + /* Initialize AC stuff */ + entropy->EOBRUN = 0; + entropy->BE = 0; + + /* Initialize bit buffer to empty */ + entropy->put_buffer = 0; + entropy->put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* Outputting bytes to the file. + * NB: these must be called only when actually outputting, + * that is, entropy->gather_statistics == FALSE. + */ + +/* Emit a byte */ +#define emit_byte(entropy,val) \ + { *(entropy)->next_output_byte++ = (JOCTET) (val); \ + if (--(entropy)->free_in_buffer == 0) \ + dump_buffer(entropy); } + + +LOCAL(void) +dump_buffer (phuff_entropy_ptr entropy) +/* Empty the output buffer; we do not support suspension in this module. */ +{ + struct jpeg_destination_mgr * dest = entropy->cinfo->dest; + + if (! (*dest->empty_output_buffer) (entropy->cinfo)) + ERREXIT(entropy->cinfo, JERR_CANT_SUSPEND); + /* After a successful buffer dump, must reset buffer pointers */ + entropy->next_output_byte = dest->next_output_byte; + entropy->free_in_buffer = dest->free_in_buffer; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL(void) +emit_bits (phuff_entropy_ptr entropy, unsigned int code, int size) +/* Emit some bits, unless we are in gather mode */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = entropy->put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + if (entropy->gather_statistics) + return; /* do nothing if we're only getting stats */ + + put_buffer &= (((INT32) 1)<put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(entropy, c); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(entropy, 0); + } + put_buffer <<= 8; + put_bits -= 8; + } + + entropy->put_buffer = put_buffer; /* update variables */ + entropy->put_bits = put_bits; +} + + +LOCAL(void) +flush_bits (phuff_entropy_ptr entropy) +{ + emit_bits(entropy, 0x7F, 7); /* fill any partial byte with ones */ + entropy->put_buffer = 0; /* and reset bit-buffer to empty */ + entropy->put_bits = 0; +} + + +/* + * Emit (or just count) a Huffman symbol. + */ + +INLINE +LOCAL(void) +emit_symbol (phuff_entropy_ptr entropy, int tbl_no, int symbol) +{ + if (entropy->gather_statistics) + entropy->count_ptrs[tbl_no][symbol]++; + else { + c_derived_tbl * tbl = entropy->derived_tbls[tbl_no]; + emit_bits(entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol]); + } +} + + +/* + * Emit bits from a correction bit buffer. + */ + +LOCAL(void) +emit_buffered_bits (phuff_entropy_ptr entropy, char * bufstart, + unsigned int nbits) +{ + if (entropy->gather_statistics) + return; /* no real work */ + + while (nbits > 0) { + emit_bits(entropy, (unsigned int) (*bufstart), 1); + bufstart++; + nbits--; + } +} + + +/* + * Emit any pending EOBRUN symbol. + */ + +LOCAL(void) +emit_eobrun (phuff_entropy_ptr entropy) +{ + register int temp, nbits; + + if (entropy->EOBRUN > 0) { /* if there is any pending EOBRUN */ + temp = entropy->EOBRUN; + nbits = 0; + while ((temp >>= 1)) + nbits++; + /* safety check: shouldn't happen given limited correction-bit buffer */ + if (nbits > 14) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + emit_symbol(entropy, entropy->ac_tbl_no, nbits << 4); + if (nbits) + emit_bits(entropy, entropy->EOBRUN, nbits); + + entropy->EOBRUN = 0; + + /* Emit any buffered correction bits */ + emit_buffered_bits(entropy, entropy->bit_buffer, entropy->BE); + entropy->BE = 0; + } +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL(void) +emit_restart (phuff_entropy_ptr entropy, int restart_num) +{ + int ci; + + emit_eobrun(entropy); + + if (! entropy->gather_statistics) { + flush_bits(entropy); + emit_byte(entropy, 0xFF); + emit_byte(entropy, JPEG_RST0 + restart_num); + } + + if (entropy->cinfo->Ss == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < entropy->cinfo->comps_in_scan; ci++) + entropy->last_dc_val[ci] = 0; + } else { + /* Re-initialize all AC-related fields to 0 */ + entropy->EOBRUN = 0; + entropy->BE = 0; + } +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_DC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + int blkn, ci; + int Al = cinfo->Al; + JBLOCKROW block; + jpeg_component_info * compptr; + ISHIFT_TEMPS + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + temp2 = IRIGHT_SHIFT((int) ((*block)[0]), Al); + + /* DC differences are figured on the point-transformed values. */ + temp = temp2 - entropy->last_dc_val[ci]; + entropy->last_dc_val[ci] = temp2; + + /* Encode the DC coefficient difference per section G.1.2.1 */ + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count/emit the Huffman-coded symbol for the number of bits */ + emit_symbol(entropy, compptr->dc_tbl_no, nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + emit_bits(entropy, (unsigned int) temp2, nbits); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_AC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + register int r, k; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ + + r = 0; /* r = run length of zeros */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = (*block)[jpeg_natural_order[k]]) == 0) { + r++; + continue; + } + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value; so the code is + * interwoven with finding the abs value (temp) and output bits (temp2). + */ + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ + temp2 = ~temp; + } else { + temp >>= Al; /* apply the point transform */ + temp2 = temp; + } + /* Watch out for case that nonzero coef is zero after point transform */ + if (temp == 0) { + r++; + continue; + } + + /* Emit any pending EOBRUN */ + if (entropy->EOBRUN > 0) + emit_eobrun(entropy); + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + emit_bits(entropy, (unsigned int) temp2, nbits); + + r = 0; /* reset zero run length */ + } + + if (r > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + if (entropy->EOBRUN == 0x7FFF) + emit_eobrun(entropy); /* force it out to avoid overflow */ + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF(boolean) +encode_mcu_DC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + int blkn; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* We simply emit the Al'th bit of the DC coefficient value. */ + temp = (*block)[0]; + emit_bits(entropy, (unsigned int) (temp >> Al), 1); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +encode_mcu_AC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + register int r, k; + int EOB; + char *BR_buffer; + unsigned int BR; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + int absvalues[DCTSIZE2]; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* It is convenient to make a pre-pass to determine the transformed + * coefficients' absolute values and the EOB position. + */ + EOB = 0; + for (k = cinfo->Ss; k <= Se; k++) { + temp = (*block)[jpeg_natural_order[k]]; + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if (temp < 0) + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + absvalues[k] = temp; /* save abs value for main pass */ + if (temp == 1) + EOB = k; /* EOB = index of last newly-nonzero coef */ + } + + /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ + + r = 0; /* r = run length of zeros */ + BR = 0; /* BR = count of buffered bits added now */ + BR_buffer = entropy->bit_buffer + entropy->BE; /* Append bits to buffer */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = absvalues[k]) == 0) { + r++; + continue; + } + + /* Emit any required ZRLs, but not if they can be folded into EOB */ + while (r > 15 && k <= EOB) { + /* emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + /* Emit ZRL */ + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + /* Emit buffered correction bits that must be associated with ZRL */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + } + + /* If the coef was previously nonzero, it only needs a correction bit. + * NOTE: a straight translation of the spec's figure G.7 would suggest + * that we also need to test r > 15. But if r > 15, we can only get here + * if k > EOB, which implies that this coefficient is not 1. + */ + if (temp > 1) { + /* The correction bit is the next bit of the absolute value. */ + BR_buffer[BR++] = (char) (temp & 1); + continue; + } + + /* Emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + 1); + + /* Emit output bit for newly-nonzero coef */ + temp = ((*block)[jpeg_natural_order[k]] < 0) ? 0 : 1; + emit_bits(entropy, (unsigned int) temp, 1); + + /* Emit buffered correction bits that must be associated with this code */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + r = 0; /* reset zero run length */ + } + + if (r > 0 || BR > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + entropy->BE += BR; /* concat my correction bits to older ones */ + /* We force out the EOB if we risk either: + * 1. overflow of the EOB counter; + * 2. overflow of the correction bit buffer during the next MCU. + */ + if (entropy->EOBRUN == 0x7FFF || entropy->BE > (MAX_CORR_BITS-DCTSIZE2+1)) + emit_eobrun(entropy); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed progressive scan. + */ + +METHODDEF(void) +finish_pass_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Flush out any buffered data */ + emit_eobrun(entropy); + flush_bits(entropy); + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF(void) +finish_pass_gather_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did[NUM_HUFF_TBLS]; + + /* Flush out buffered data (all we care about is counting the EOB symbol) */ + emit_eobrun(entropy); + + is_DC_band = (cinfo->Ss == 0); + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did, SIZEOF(did)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + } else { + tbl = compptr->ac_tbl_no; + } + if (! did[tbl]) { + if (is_DC_band) + htblptr = & cinfo->dc_huff_tbl_ptrs[tbl]; + else + htblptr = & cinfo->ac_huff_tbl_ptrs[tbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->count_ptrs[tbl]); + did[tbl] = TRUE; + } + } +} + + +/* + * Module initialization routine for progressive Huffman entropy encoding. + */ + +GLOBAL(void) +jinit_phuff_encoder (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy; + int i; + + entropy = (phuff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(phuff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_phuff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + entropy->count_ptrs[i] = NULL; + } + entropy->bit_buffer = NULL; /* needed only in AC refinement scan */ +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcprepct.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcprepct.c new file mode 100644 index 0000000..fa93333 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcprepct.c @@ -0,0 +1,354 @@ +/* + * jcprepct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the compression preprocessing controller. + * This controller manages the color conversion, downsampling, + * and edge expansion steps. + * + * Most of the complexity here is associated with buffering input rows + * as required by the downsampler. See the comments at the head of + * jcsample.c for the downsampler's needs. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* At present, jcsample.c can request context rows only for smoothing. + * In the future, we might also need context rows for CCIR601 sampling + * or other more-complex downsampling procedures. The code to support + * context rows should be compiled only if needed. + */ +#ifdef INPUT_SMOOTHING_SUPPORTED +#define CONTEXT_ROWS_SUPPORTED +#endif + + +/* + * For the simple (no-context-row) case, we just need to buffer one + * row group's worth of pixels for the downsampling step. At the bottom of + * the image, we pad to a full row group by replicating the last pixel row. + * The downsampler's last output row is then replicated if needed to pad + * out to a full iMCU row. + * + * When providing context rows, we must buffer three row groups' worth of + * pixels. Three row groups are physically allocated, but the row pointer + * arrays are made five row groups high, with the extra pointers above and + * below "wrapping around" to point to the last and first real row groups. + * This allows the downsampler to access the proper context rows. + * At the top and bottom of the image, we create dummy context rows by + * copying the first or last real pixel row. This copying could be avoided + * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the + * trouble on the compression side. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_prep_controller pub; /* public fields */ + + /* Downsampling input buffer. This buffer holds color-converted data + * until we have enough to do a downsample step. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + JDIMENSION rows_to_go; /* counts rows remaining in source image */ + int next_buf_row; /* index of next row to store in color_buf */ + +#ifdef CONTEXT_ROWS_SUPPORTED /* only needed for context case */ + int this_row_group; /* starting row index of group to process */ + int next_buf_stop; /* downsample when we reach this index */ +#endif +} my_prep_controller; + +typedef my_prep_controller * my_prep_ptr; + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_prep (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + + if (pass_mode != JBUF_PASS_THRU) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Initialize total-height counter for detecting bottom of image */ + prep->rows_to_go = cinfo->image_height; + /* Mark the conversion buffer empty */ + prep->next_buf_row = 0; +#ifdef CONTEXT_ROWS_SUPPORTED + /* Preset additional state variables for context mode. + * These aren't used in non-context mode, so we needn't test which mode. + */ + prep->this_row_group = 0; + /* Set next_buf_stop to stop after two row groups have been read in. */ + prep->next_buf_stop = 2 * cinfo->max_v_samp_factor; +#endif +} + + +/* + * Expand an image vertically from height input_rows to height output_rows, + * by duplicating the bottom row. + */ + +LOCAL(void) +expand_bottom_edge (JSAMPARRAY image_data, JDIMENSION num_cols, + int input_rows, int output_rows) +{ + register int row; + + for (row = input_rows; row < output_rows; row++) { + jcopy_sample_rows(image_data, input_rows-1, image_data, row, + 1, num_cols); + } +} + + +/* + * Process some data in the simple no-context case. + * + * Preprocessor output data is counted in "row groups". A row group + * is defined to be v_samp_factor sample rows of each component. + * Downsampling will produce this much data from each max_v_samp_factor + * input rows. + */ + +METHODDEF(void) +pre_process_data (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while (*in_row_ctr < in_rows_avail && + *out_row_group_ctr < out_row_groups_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = cinfo->max_v_samp_factor - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + /* If at bottom of image, pad to fill the conversion buffer. */ + if (prep->rows_to_go == 0 && + prep->next_buf_row < cinfo->max_v_samp_factor) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, cinfo->max_v_samp_factor); + } + prep->next_buf_row = cinfo->max_v_samp_factor; + } + /* If we've filled the conversion buffer, empty it. */ + if (prep->next_buf_row == cinfo->max_v_samp_factor) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, (JDIMENSION) 0, + output_buf, *out_row_group_ctr); + prep->next_buf_row = 0; + (*out_row_group_ctr)++; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if (prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + expand_bottom_edge(output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) (*out_row_group_ctr * compptr->v_samp_factor), + (int) (out_row_groups_avail * compptr->v_samp_factor)); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +#ifdef CONTEXT_ROWS_SUPPORTED + +/* + * Process some data in the context case. + */ + +METHODDEF(void) +pre_process_context (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + int buf_height = cinfo->max_v_samp_factor * 3; + JDIMENSION inrows; + + while (*out_row_group_ctr < out_row_groups_avail) { + if (*in_row_ctr < in_rows_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = prep->next_buf_stop - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + /* Pad at top of image, if first time through */ + if (prep->rows_to_go == cinfo->image_height) { + for (ci = 0; ci < cinfo->num_components; ci++) { + int row; + for (row = 1; row <= cinfo->max_v_samp_factor; row++) { + jcopy_sample_rows(prep->color_buf[ci], 0, + prep->color_buf[ci], -row, + 1, cinfo->image_width); + } + } + } + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + } else { + /* Return for more data, unless we are at the bottom of the image. */ + if (prep->rows_to_go != 0) + break; + /* When at bottom of image, pad to fill the conversion buffer. */ + if (prep->next_buf_row < prep->next_buf_stop) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, prep->next_buf_stop); + } + prep->next_buf_row = prep->next_buf_stop; + } + } + /* If we've gotten enough data, downsample a row group. */ + if (prep->next_buf_row == prep->next_buf_stop) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, + (JDIMENSION) prep->this_row_group, + output_buf, *out_row_group_ctr); + (*out_row_group_ctr)++; + /* Advance pointers with wraparound as necessary. */ + prep->this_row_group += cinfo->max_v_samp_factor; + if (prep->this_row_group >= buf_height) + prep->this_row_group = 0; + if (prep->next_buf_row >= buf_height) + prep->next_buf_row = 0; + prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor; + } + } +} + + +/* + * Create the wrapped-around downsampling input buffer needed for context mode. + */ + +LOCAL(void) +create_context_buffer (j_compress_ptr cinfo) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int rgroup_height = cinfo->max_v_samp_factor; + int ci, i; + jpeg_component_info * compptr; + JSAMPARRAY true_buffer, fake_buffer; + + /* Grab enough space for fake row pointers for all the components; + * we need five row groups' worth of pointers for each component. + */ + fake_buffer = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (cinfo->num_components * 5 * rgroup_height) * + SIZEOF(JSAMPROW)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate the actual buffer space (3 row groups) for this component. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + true_buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) (3 * rgroup_height)); + /* Copy true buffer row pointers into the middle of the fake row array */ + MEMCOPY(fake_buffer + rgroup_height, true_buffer, + 3 * rgroup_height * SIZEOF(JSAMPROW)); + /* Fill in the above and below wraparound pointers */ + for (i = 0; i < rgroup_height; i++) { + fake_buffer[i] = true_buffer[2 * rgroup_height + i]; + fake_buffer[4 * rgroup_height + i] = true_buffer[i]; + } + prep->color_buf[ci] = fake_buffer + rgroup_height; + fake_buffer += 5 * rgroup_height; /* point to space for next component */ + } +} + +#endif /* CONTEXT_ROWS_SUPPORTED */ + + +/* + * Initialize preprocessing controller. + */ + +GLOBAL(void) +jinit_c_prep_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_prep_ptr prep; + int ci; + jpeg_component_info * compptr; + + if (need_full_buffer) /* safety check */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + prep = (my_prep_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_prep_controller)); + cinfo->prep = (struct jpeg_c_prep_controller *) prep; + prep->pub.start_pass = start_pass_prep; + + /* Allocate the color conversion buffer. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + if (cinfo->downsample->need_context_rows) { + /* Set up to provide context rows */ +#ifdef CONTEXT_ROWS_SUPPORTED + prep->pub.pre_process_data = pre_process_context; + create_context_buffer(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* No context, just make it tall enough for one row group */ + prep->pub.pre_process_data = pre_process_data; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prep->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcsample.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcsample.c new file mode 100644 index 0000000..212ec87 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jcsample.c @@ -0,0 +1,519 @@ +/* + * jcsample.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains downsampling routines. + * + * Downsampling input data is counted in "row groups". A row group + * is defined to be max_v_samp_factor pixel rows of each component, + * from which the downsampler produces v_samp_factor sample rows. + * A single row group is processed in each call to the downsampler module. + * + * The downsampler is responsible for edge-expansion of its output data + * to fill an integral number of DCT blocks horizontally. The source buffer + * may be modified if it is helpful for this purpose (the source buffer is + * allocated wide enough to correspond to the desired output width). + * The caller (the prep controller) is responsible for vertical padding. + * + * The downsampler may request "context rows" by setting need_context_rows + * during startup. In this case, the input arrays will contain at least + * one row group's worth of pixels above and below the passed-in data; + * the caller will create dummy rows at image top and bottom by replicating + * the first or last real pixel row. + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + * + * The downsampling algorithm used here is a simple average of the source + * pixels covered by the output pixel. The hi-falutin sampling literature + * refers to this as a "box filter". In general the characteristics of a box + * filter are not very good, but for the specific cases we normally use (1:1 + * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not + * nearly so bad. If you intend to use other sampling ratios, you'd be well + * advised to improve this code. + * + * A simple input-smoothing capability is provided. This is mainly intended + * for cleaning up color-dithered GIF input files (if you find it inadequate, + * we suggest using an external filtering program such as pnmconvol). When + * enabled, each input pixel P is replaced by a weighted sum of itself and its + * eight neighbors. P's weight is 1-8*SF and each neighbor's weight is SF, + * where SF = (smoothing_factor / 1024). + * Currently, smoothing is only supported for 2h2v sampling factors. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to downsample a single component */ +typedef JMETHOD(void, downsample1_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data)); + +/* Private subobject */ + +typedef struct { + struct jpeg_downsampler pub; /* public fields */ + + /* Downsampling method pointers, one per component */ + downsample1_ptr methods[MAX_COMPONENTS]; +} my_downsampler; + +typedef my_downsampler * my_downsample_ptr; + + +/* + * Initialize for a downsampling pass. + */ + +METHODDEF(void) +start_pass_downsample (j_compress_ptr cinfo) +{ + /* no work for now */ +} + + +/* + * Expand a component horizontally from width input_cols to width output_cols, + * by duplicating the rightmost samples. + */ + +LOCAL(void) +expand_right_edge (JSAMPARRAY image_data, int num_rows, + JDIMENSION input_cols, JDIMENSION output_cols) +{ + register JSAMPROW ptr; + register JSAMPLE pixval; + register int count; + int row; + int numcols = (int) (output_cols - input_cols); + + if (numcols > 0) { + for (row = 0; row < num_rows; row++) { + ptr = image_data[row] + input_cols; + pixval = ptr[-1]; /* don't need GETJSAMPLE() here */ + for (count = numcols; count > 0; count--) + *ptr++ = pixval; + } + } +} + + +/* + * Do downsampling for a whole row group (all components). + * + * In this version we simply downsample each component independently. + */ + +METHODDEF(void) +sep_downsample (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, JDIMENSION out_row_group_index) +{ + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int ci; + jpeg_component_info * compptr; + JSAMPARRAY in_ptr, out_ptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + in_ptr = input_buf[ci] + in_row_index; + out_ptr = output_buf[ci] + (out_row_group_index * compptr->v_samp_factor); + (*downsample->methods[ci]) (cinfo, compptr, in_ptr, out_ptr); + } +} + + +/* + * Downsample pixel values of a single component. + * One row group is processed per call. + * This version handles arbitrary integral sampling ratios, without smoothing. + * Note that this version is not actually used for customary sampling ratios. + */ + +METHODDEF(void) +int_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v; + JDIMENSION outcol, outcol_h; /* outcol_h == outcol*h_expand */ + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + JSAMPROW inptr, outptr; + INT32 outvalue; + + h_expand = cinfo->max_h_samp_factor / compptr->h_samp_factor; + v_expand = cinfo->max_v_samp_factor / compptr->v_samp_factor; + numpix = h_expand * v_expand; + numpix2 = numpix/2; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * h_expand); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + for (outcol = 0, outcol_h = 0; outcol < output_cols; + outcol++, outcol_h += h_expand) { + outvalue = 0; + for (v = 0; v < v_expand; v++) { + inptr = input_data[inrow+v] + outcol_h; + for (h = 0; h < h_expand; h++) { + outvalue += (INT32) GETJSAMPLE(*inptr++); + } + } + *outptr++ = (JSAMPLE) ((outvalue + numpix2) / numpix); + } + inrow += v_expand; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * without smoothing. + */ + +METHODDEF(void) +fullsize_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + /* Copy the data */ + jcopy_sample_rows(input_data, 0, output_data, 0, + cinfo->max_v_samp_factor, cinfo->image_width); + /* Edge-expand */ + expand_right_edge(output_data, cinfo->max_v_samp_factor, + cinfo->image_width, compptr->width_in_blocks * DCTSIZE); +} + + +/* + * Downsample pixel values of a single component. + * This version handles the common case of 2:1 horizontal and 1:1 vertical, + * without smoothing. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF(void) +h2v1_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + bias = 0; /* bias = 0,1,0,1,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr) + GETJSAMPLE(inptr[1]) + + bias) >> 1); + bias ^= 1; /* 0=>1, 1=>0 */ + inptr += 2; + } + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * without smoothing. + */ + +METHODDEF(void) +h2v2_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + bias = 1; /* bias = 1,2,1,2,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]) + + bias) >> 2); + bias ^= 3; /* 1=>2, 2=>1 */ + inptr0 += 2; inptr1 += 2; + } + inrow += 2; + } +} + + +#ifdef INPUT_SMOOTHING_SUPPORTED + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * with smoothing. One row of context is required. + */ + +METHODDEF(void) +h2v2_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols * 2); + + /* We don't bother to form the individual "smoothed" input pixel values; + * we can directly compute the output which is the average of the four + * smoothed values. Each of the four member pixels contributes a fraction + * (1-8*SF) to its own smoothed image and a fraction SF to each of the three + * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final + * output. The four corner-adjacent neighbor pixels contribute a fraction + * SF to just one smoothed pixel, or SF/4 to the final output; while the + * eight edge-adjacent neighbors contribute SF to each of two smoothed + * pixels, or SF/2 overall. In order to use integer arithmetic, these + * factors are scaled by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 16384 - cinfo->smoothing_factor * 80; /* scaled (1-5*SF)/4 */ + neighscale = cinfo->smoothing_factor * 16; /* scaled SF/4 */ + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + above_ptr = input_data[inrow-1]; + below_ptr = input_data[inrow+2]; + + /* Special case for first column: pretend column -1 is same as column 0 */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[2]); + neighsum += neighsum; + neighsum += GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[2]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + /* sum of pixels directly mapped to this output element */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + /* sum of edge-neighbor pixels */ + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[2]); + /* The edge-neighbors count twice as much as corner-neighbors */ + neighsum += neighsum; + /* Add in the corner-neighbors */ + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[2]); + /* form final output scaled up by 2^16 */ + membersum = membersum * memberscale + neighsum * neighscale; + /* round, descale and output it */ + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[1]); + neighsum += neighsum; + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[1]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + inrow += 2; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * with smoothing. One row of context is required. + */ + +METHODDEF(void) +fullsize_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info *compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + int colsum, lastcolsum, nextcolsum; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols); + + /* Each of the eight neighbor pixels contributes a fraction SF to the + * smoothed pixel, while the main pixel contributes (1-8*SF). In order + * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 65536L - cinfo->smoothing_factor * 512L; /* scaled 1-8*SF */ + neighscale = cinfo->smoothing_factor * 64; /* scaled SF */ + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + above_ptr = input_data[outrow-1]; + below_ptr = input_data[outrow+1]; + + /* Special case for first column */ + colsum = GETJSAMPLE(*above_ptr++) + GETJSAMPLE(*below_ptr++) + + GETJSAMPLE(*inptr); + membersum = GETJSAMPLE(*inptr++); + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = colsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + membersum = GETJSAMPLE(*inptr++); + above_ptr++; below_ptr++; + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + colsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + } +} + +#endif /* INPUT_SMOOTHING_SUPPORTED */ + + +/* + * Module initialization routine for downsampling. + * Note that we must select a routine for each component. + */ + +GLOBAL(void) +jinit_downsampler (j_compress_ptr cinfo) +{ + my_downsample_ptr downsample; + int ci; + jpeg_component_info * compptr; + boolean smoothok = TRUE; + + downsample = (my_downsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_downsampler)); + cinfo->downsample = (struct jpeg_downsampler *) downsample; + downsample->pub.start_pass = start_pass_downsample; + downsample->pub.downsample = sep_downsample; + downsample->pub.need_context_rows = FALSE; + + if (cinfo->CCIR601_sampling) + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* Verify we can handle the sampling factors, and set up method pointers */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = fullsize_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = fullsize_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { + smoothok = FALSE; + downsample->methods[ci] = h2v1_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor * 2 == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = h2v2_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = h2v2_downsample; + } else if ((cinfo->max_h_samp_factor % compptr->h_samp_factor) == 0 && + (cinfo->max_v_samp_factor % compptr->v_samp_factor) == 0) { + smoothok = FALSE; + downsample->methods[ci] = int_downsample; + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + } + +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor && !smoothok) + TRACEMS(cinfo, 0, JTRC_SMOOTH_NOTIMPL); +#endif +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jctrans.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jctrans.c new file mode 100644 index 0000000..0e6d707 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jctrans.c @@ -0,0 +1,388 @@ +/* + * jctrans.c + * + * Copyright (C) 1995-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding compression, + * that is, writing raw DCT coefficient arrays to an output JPEG file. + * The routines in jcapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(void) transencode_master_selection + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); +LOCAL(void) transencode_coef_controller + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); + + +/* + * Compression initialization for writing raw-coefficient data. + * Before calling this, all parameters and a data destination must be set up. + * Call jpeg_finish_compress() to actually write the data. + * + * The number of passed virtual arrays must match cinfo->num_components. + * Note that the virtual arrays need not be filled or even realized at + * the time write_coefficients is called; indeed, if the virtual arrays + * were requested from this compression object's memory manager, they + * typically will be realized during this routine and filled afterwards. + */ + +GLOBAL(void) +jpeg_write_coefficients (j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Mark all tables to be written */ + jpeg_suppress_tables(cinfo, FALSE); + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + transencode_master_selection(cinfo, coef_arrays); + /* Wait for jpeg_finish_compress() call */ + cinfo->next_scanline = 0; /* so jpeg_write_marker works */ + cinfo->global_state = CSTATE_WRCOEFS; +} + + +/* + * Initialize the compression object with default parameters, + * then copy from the source object all parameters needed for lossless + * transcoding. Parameters that can be varied without loss (such as + * scan script and Huffman optimization) are left in their default states. + */ + +GLOBAL(void) +jpeg_copy_critical_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo) +{ + JQUANT_TBL ** qtblptr; + jpeg_component_info *incomp, *outcomp; + JQUANT_TBL *c_quant, *slot_quant; + int tblno, ci, coefi; + + /* Safety check to ensure start_compress not called yet. */ + if (dstinfo->global_state != CSTATE_START) + ERREXIT1(dstinfo, JERR_BAD_STATE, dstinfo->global_state); + /* Copy fundamental image dimensions */ + dstinfo->image_width = srcinfo->image_width; + dstinfo->image_height = srcinfo->image_height; + dstinfo->input_components = srcinfo->num_components; + dstinfo->in_color_space = srcinfo->jpeg_color_space; + /* Initialize all parameters to default values */ + jpeg_set_defaults(dstinfo); + /* jpeg_set_defaults may choose wrong colorspace, eg YCbCr if input is RGB. + * Fix it to get the right header markers for the image colorspace. + */ + jpeg_set_colorspace(dstinfo, srcinfo->jpeg_color_space); + dstinfo->data_precision = srcinfo->data_precision; + dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling; + /* Copy the source's quantization tables. */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + if (srcinfo->quant_tbl_ptrs[tblno] != NULL) { + qtblptr = & dstinfo->quant_tbl_ptrs[tblno]; + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) dstinfo); + MEMCOPY((*qtblptr)->quantval, + srcinfo->quant_tbl_ptrs[tblno]->quantval, + SIZEOF((*qtblptr)->quantval)); + (*qtblptr)->sent_table = FALSE; + } + } + /* Copy the source's per-component info. + * Note we assume jpeg_set_defaults has allocated the dest comp_info array. + */ + dstinfo->num_components = srcinfo->num_components; + if (dstinfo->num_components < 1 || dstinfo->num_components > MAX_COMPONENTS) + ERREXIT2(dstinfo, JERR_COMPONENT_COUNT, dstinfo->num_components, + MAX_COMPONENTS); + for (ci = 0, incomp = srcinfo->comp_info, outcomp = dstinfo->comp_info; + ci < dstinfo->num_components; ci++, incomp++, outcomp++) { + outcomp->component_id = incomp->component_id; + outcomp->h_samp_factor = incomp->h_samp_factor; + outcomp->v_samp_factor = incomp->v_samp_factor; + outcomp->quant_tbl_no = incomp->quant_tbl_no; + /* Make sure saved quantization table for component matches the qtable + * slot. If not, the input file re-used this qtable slot. + * IJG encoder currently cannot duplicate this. + */ + tblno = outcomp->quant_tbl_no; + if (tblno < 0 || tblno >= NUM_QUANT_TBLS || + srcinfo->quant_tbl_ptrs[tblno] == NULL) + ERREXIT1(dstinfo, JERR_NO_QUANT_TABLE, tblno); + slot_quant = srcinfo->quant_tbl_ptrs[tblno]; + c_quant = incomp->quant_table; + if (c_quant != NULL) { + for (coefi = 0; coefi < DCTSIZE2; coefi++) { + if (c_quant->quantval[coefi] != slot_quant->quantval[coefi]) + ERREXIT1(dstinfo, JERR_MISMATCHED_QUANT_TABLE, tblno); + } + } + /* Note: we do not copy the source's Huffman table assignments; + * instead we rely on jpeg_set_colorspace to have made a suitable choice. + */ + } + /* Also copy JFIF version and resolution information, if available. + * Strictly speaking this isn't "critical" info, but it's nearly + * always appropriate to copy it if available. In particular, + * if the application chooses to copy JFIF 1.02 extension markers from + * the source file, we need to copy the version to make sure we don't + * emit a file that has 1.02 extensions but a claimed version of 1.01. + * We will *not*, however, copy version info from mislabeled "2.01" files. + */ + if (srcinfo->saw_JFIF_marker) { + if (srcinfo->JFIF_major_version == 1) { + dstinfo->JFIF_major_version = srcinfo->JFIF_major_version; + dstinfo->JFIF_minor_version = srcinfo->JFIF_minor_version; + } + dstinfo->density_unit = srcinfo->density_unit; + dstinfo->X_density = srcinfo->X_density; + dstinfo->Y_density = srcinfo->Y_density; + } +} + + +/* + * Master selection of compression modules for transcoding. + * This substitutes for jcinit.c's initialization of the full compressor. + */ + +LOCAL(void) +transencode_master_selection (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + /* Although we don't actually use input_components for transcoding, + * jcmaster.c's initial_setup will complain if input_components is 0. + */ + cinfo->input_components = 1; + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, TRUE /* transcode only */); + + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_encoder(cinfo); + } + + /* We need a special coefficient buffer controller. */ + transencode_coef_controller(cinfo, coef_arrays); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI, JFIF) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} + + +/* + * The rest of this file is a special implementation of the coefficient + * buffer controller. This is similar to jccoefct.c, but it handles only + * output from presupplied virtual arrays. Furthermore, we generate any + * dummy padding blocks on-the-fly rather than expecting them to be present + * in the arrays. + */ + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* Virtual block array for each component. */ + jvirt_barray_ptr * whole_image; + + /* Workspace for constructing dummy blocks at right/bottom edges. */ + JBLOCKROW dummy_buffer[C_MAX_BLOCKS_IN_MCU]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +LOCAL(void) +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + if (pass_mode != JBUF_CRANK_DEST) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); +} + + +/* + * Process some data. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF(boolean) +compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, blockcnt; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yindex+yoffset < compptr->last_row_height) { + /* Fill in pointers to real blocks in this row */ + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < blockcnt; xindex++) + MCU_buffer[blkn++] = buffer_ptr++; + } else { + /* At bottom of image, need a whole row of dummy blocks */ + xindex = 0; + } + /* Fill in any dummy blocks needed in this row. + * Dummy blocks are filled in the same way as in jccoefct.c: + * all zeroes in the AC entries, DC entries equal to previous + * block's DC value. The init routine has already zeroed the + * AC entries, so we need only set the DC entries correctly. + */ + for (; xindex < compptr->MCU_width; xindex++) { + MCU_buffer[blkn] = coef->dummy_buffer[blkn]; + MCU_buffer[blkn][0][0] = MCU_buffer[blkn-1][0][0]; + blkn++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +/* + * Initialize coefficient buffer controller. + * + * Each passed coefficient array must be the right size for that + * coefficient: width_in_blocks wide and height_in_blocks high, + * with unitheight at least v_samp_factor. + */ + +LOCAL(void) +transencode_coef_controller (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + my_coef_ptr coef; + JBLOCKROW buffer; + int i; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + coef->pub.compress_data = compress_output; + + /* Save pointer to virtual arrays */ + coef->whole_image = coef_arrays; + + /* Allocate and pre-zero space for dummy DCT blocks. */ + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + jzero_far((void FAR *) buffer, C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->dummy_buffer[i] = buffer + i; + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdapimin.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdapimin.c new file mode 100644 index 0000000..cadb59f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdapimin.c @@ -0,0 +1,395 @@ +/* + * jdapimin.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL(void) +jpeg_CreateDecompress (j_decompress_ptr cinfo, int version, size_t structsize) +{ + int i; + + /* Guard against version mismatches between library and caller. */ + cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */ + if (version != JPEG_LIB_VERSION) + ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); + if (structsize != SIZEOF(struct jpeg_decompress_struct)) + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + (int) SIZEOF(struct jpeg_decompress_struct), (int) structsize); + + /* For debugging purposes, we zero the whole master structure. + * But the application has already set the err pointer, and may have set + * client_data, so we have to save and restore those fields. + * Note: if application hasn't set client_data, tools like Purify may + * complain here. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + void * client_data = cinfo->client_data; /* ignore Purify complaint here */ + MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct)); + cinfo->err = err; + cinfo->client_data = client_data; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + cinfo->marker_list = NULL; + jinit_marker_reader(cinfo); + + /* And initialize the overall input controller. */ + jinit_input_controller(cinfo); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL(void) +jpeg_destroy_decompress (j_decompress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL(void) +jpeg_abort_decompress (j_decompress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Set default decompression parameters. + */ + +LOCAL(void) +default_decompress_parms (j_decompress_ptr cinfo) +{ + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch (cinfo->num_components) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if (cinfo->saw_JFIF_marker) { + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + } else if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if (cid0 == 1 && cid1 == 2 && cid2 == 3) + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + else if (cid0 == 82 && cid1 == 71 && cid2 == 66) + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + else { + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL(int) +jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) +{ + int retcode; + + if (cinfo->global_state != DSTATE_START && + cinfo->global_state != DSTATE_INHEADER) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + retcode = jpeg_consume_input(cinfo); + + switch (retcode) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if (require_image) /* Complain if application wanted an image */ + ERREXIT(cinfo, JERR_NO_IMAGE); + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL(int) +jpeg_consume_input (j_decompress_ptr cinfo) +{ + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch (cinfo->global_state) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + (*cinfo->inputctl->reset_input_controller) (cinfo); + /* Initialize application's data source module */ + (*cinfo->src->init_source) (cinfo); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms(cinfo); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + break; + default: + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL(boolean) +jpeg_input_complete (j_decompress_ptr cinfo) +{ + /* Check for valid jpeg object */ + if (cinfo->global_state < DSTATE_START || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL(boolean) +jpeg_has_multiple_scans (j_decompress_ptr cinfo) +{ + /* Only valid after jpeg_read_header completes */ + if (cinfo->global_state < DSTATE_READY || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_finish_decompress (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) { + /* Terminate final pass of non-buffered mode */ + if (cinfo->output_scanline < cinfo->output_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state == DSTATE_BUFIMAGE) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state != DSTATE_STOPPING) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read until EOI */ + while (! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + /* Do final cleanup */ + (*cinfo->src->term_source) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); + return TRUE; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdapistd.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdapistd.c new file mode 100644 index 0000000..c8e3fa0 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdapistd.c @@ -0,0 +1,275 @@ +/* + * jdapistd.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(boolean) output_pass_setup JPP((j_decompress_ptr cinfo)); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_start_decompress (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress(cinfo); + if (cinfo->buffered_image) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if (cinfo->global_state == DSTATE_PRELOAD) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if (cinfo->inputctl->has_multiple_scans) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return FALSE; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if (cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup(cinfo); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL(boolean) +output_pass_setup (j_decompress_ptr cinfo) +{ + if (cinfo->global_state != DSTATE_PRESCAN) { + /* First call: do pass setup */ + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while (cinfo->master->is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while (cinfo->output_scanline < cinfo->output_height) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0); + if (cinfo->output_scanline == last_scanline) + return FALSE; /* No progress made, must suspend */ + } + /* Finish up dummy pass, and set up for another one */ + (*cinfo->master->finish_output_pass) (cinfo); + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL(JDIMENSION) +jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) +{ + JDIMENSION row_ctr; + + if (cinfo->global_state != DSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Process some data */ + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL(JDIMENSION) +jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != DSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + if (max_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Decompress directly into user's buffer. */ + if (! (*cinfo->coef->decompress_data) (cinfo, data)) + return 0; /* suspension forced, can do nothing more */ + + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL(boolean) +jpeg_start_output (j_decompress_ptr cinfo, int scan_number) +{ + if (cinfo->global_state != DSTATE_BUFIMAGE && + cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Limit scan number to valid range */ + if (scan_number <= 0) + scan_number = 1; + if (cinfo->inputctl->eoi_reached && + scan_number > cinfo->input_scan_number) + scan_number = cinfo->input_scan_number; + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup(cinfo); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_finish_output (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_BUFPOST; + } else if (cinfo->global_state != DSTATE_BUFPOST) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read markers looking for SOS or EOI */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdatadst.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdatadst.c new file mode 100644 index 0000000..a8f6fb0 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdatadst.c @@ -0,0 +1,151 @@ +/* + * jdatadst.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains compression data destination routines for the case of + * emitting JPEG data to a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * destination manager. + * IMPORTANT: we assume that fwrite() will correctly transcribe an array of + * JOCTETs into 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + FILE * outfile; /* target stream */ + JOCTET * buffer; /* start of buffer */ +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +METHODDEF(void) +init_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + /* Allocate the output buffer --- it will be released when done with image */ + dest->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + OUTPUT_BUF_SIZE * SIZEOF(JOCTET)); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +METHODDEF(boolean) +empty_output_buffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) != + (size_t) OUTPUT_BUF_SIZE) + ERREXIT(cinfo, JERR_FILE_WRITE); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; + + return TRUE; +} + + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF(void) +term_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if (datacount > 0) { + if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount) + ERREXIT(cinfo, JERR_FILE_WRITE); + } + fflush(dest->outfile); + /* Make sure we wrote the output file OK */ + if (ferror(dest->outfile)) + ERREXIT(cinfo, JERR_FILE_WRITE); +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +GLOBAL(void) +jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdatasrc.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdatasrc.c new file mode 100644 index 0000000..2c1d71a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdatasrc.c @@ -0,0 +1,273 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + FILE * infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF(void) +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + +METHODDEF(void) +init_mem_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF(boolean) +fill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + size_t nbytes; + + nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE); + + if (nbytes <= 0) { + if (src->start_of_file) /* Treat empty input file as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); + WARNMS(cinfo, JWRN_JPEG_EOF); + /* Insert a fake EOI marker */ + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + + return TRUE; +} + +METHODDEF(boolean) +fill_mem_input_buffer (j_decompress_ptr cinfo) +{ + static const JOCTET mybuffer[4] = { + (JOCTET) 0xFF, (JOCTET) JPEG_EOI, 0, 0 + }; + + /* The whole JPEG data is expected to reside in the supplied memory + * buffer, so any request for more data beyond the given buffer size + * is treated as an error. + */ + WARNMS(cinfo, JWRN_JPEG_EOF); + + /* Insert a fake EOI marker */ + + cinfo->src->next_input_byte = mybuffer; + cinfo->src->bytes_in_buffer = 2; + + return TRUE; +} + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF(void) +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF(void) +term_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL(void) +jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF(JOCTET)); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} + + +/* + * Prepare for input from a supplied memory buffer. + * The buffer must contain the whole JPEG data. + */ + +GLOBAL(void) +jpeg_mem_src (j_decompress_ptr cinfo, + unsigned char * inbuffer, unsigned long insize) +{ + struct jpeg_source_mgr * src; + + if (inbuffer == NULL || insize == 0) /* Treat empty input as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); + + /* The source object is made permanent so that a series of JPEG images + * can be read from the same buffer by calling jpeg_mem_src only before + * the first one. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(struct jpeg_source_mgr)); + } + + src = cinfo->src; + src->init_source = init_mem_source; + src->fill_input_buffer = fill_mem_input_buffer; + src->skip_input_data = skip_input_data; + src->resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->term_source = term_source; + src->bytes_in_buffer = (size_t) insize; + src->next_input_byte = (JOCTET *) inbuffer; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdcoefct.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdcoefct.c new file mode 100644 index 0000000..4938d20 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdcoefct.c @@ -0,0 +1,736 @@ +/* + * jdcoefct.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub; /* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + +/* Forward declarations */ +METHODDEF(int) decompress_onepass + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF(int) decompress_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL(boolean) smoothing_ok JPP((j_decompress_ptr cinfo)); +METHODDEF(int) decompress_smooth_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif + + +LOCAL(void) +start_iMCU_row (j_decompress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row (input side) */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF(void) +start_input_pass (j_decompress_ptr cinfo) +{ + cinfo->input_iMCU_row = 0; + start_iMCU_row(cinfo); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF(void) +start_output_pass (j_decompress_ptr cinfo) +{ +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if (coef->pub.coef_arrays != NULL) { + if (cinfo->do_block_smoothing && smoothing_ok(cinfo)) + coef->pub.decompress_data = decompress_smooth_data; + else + coef->pub.decompress_data = decompress_data; + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image, + * which we index according to the component's SOF position. + */ + +METHODDEF(int) +decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far((void FAR *) coef->MCU_buffer[0], + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[compptr->component_index] + + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (cinfo->input_iMCU_row < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + output_col = start_col; + for (xindex = 0; xindex < useful_width; xindex++) { + (*inverse_DCT) (cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + output_ptr, output_col); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF(int) +dummy_consume_data (j_decompress_ptr cinfo) +{ + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF(int) +consume_data (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF(int) +decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number < cinfo->output_scan_number || + (cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* Natural-order array positions of the first 5 zigzag-order coefficients */ +#define Q01_POS 1 +#define Q10_POS 8 +#define Q20_POS 16 +#define Q11_POS 9 +#define Q02_POS 2 + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL(boolean) +smoothing_ok (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info *compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if (! cinfo->progressive_mode || cinfo->coef_bits == NULL) + return FALSE; + + /* Allocate latch area if not already done */ + if (coef->coef_bits_latch == NULL) + coef->coef_bits_latch = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + (SAVED_COEFS * SIZEOF(int))); + coef_bits_latch = coef->coef_bits_latch; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* All components' quantization values must already be latched. */ + if ((qtable = compptr->quant_table) == NULL) + return FALSE; + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + if (qtable->quantval[0] == 0 || + qtable->quantval[Q01_POS] == 0 || + qtable->quantval[Q10_POS] == 0 || + qtable->quantval[Q20_POS] == 0 || + qtable->quantval[Q11_POS] == 0 || + qtable->quantval[Q02_POS] == 0) + return FALSE; + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if (coef_bits[0] < 0) + return FALSE; + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for (coefi = 1; coefi <= 5; coefi++) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if (coef_bits[coefi] != 0) + smoothing_useful = TRUE; + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF(int) +decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int *coef_bits; + JQUANT_TBL *quanttbl; + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if (cinfo->input_scan_number == cinfo->output_scan_number) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; + if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) + break; + } + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2; /* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + access_rows = block_rows; /* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if (cinfo->output_iMCU_row > 0) { + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[Q01_POS]; + Q10 = quanttbl->quantval[Q10_POS]; + Q20 = quanttbl->quantval[Q20_POS]; + Q11 = quanttbl->quantval[Q11_POS]; + Q02 = quanttbl->quantval[Q02_POS]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + if (first_row && block_row == 0) + prev_block_row = buffer_ptr; + else + prev_block_row = buffer[block_row-1]; + if (last_row && block_row == block_rows-1) + next_block_row = buffer_ptr; + else + next_block_row = buffer[block_row+1]; + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for (block_num = 0; block_num <= last_block_column; block_num++) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + /* Update DC values */ + if (block_num < last_block_column) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + num = 36 * Q00 * (DC4 - DC6); + if (num >= 0) { + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL(void) +jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if (cinfo->progressive_mode) + access_rows *= 3; +#endif + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) access_rows); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdcolor.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdcolor.c new file mode 100644 index 0000000..6c04dfe --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdcolor.c @@ -0,0 +1,396 @@ +/* + * jdcolor.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub; /* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter; + +typedef my_color_deconverter * my_cconvert_ptr; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + */ + +LOCAL(void) +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + cconvert->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF(void) +ycc_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS))]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF(void) +null_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while (--num_rows >= 0) { + for (ci = 0; ci < num_components; ci++) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for (count = num_cols; count > 0; count--) { + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF(void) +grayscale_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width); +} + + +/* + * Convert grayscale to RGB: just duplicate the graylevel three times. + * This is provided to support applications that don't want to cope + * with grayscale as a separate case. + */ + +METHODDEF(void) +gray_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + + while (--num_rows >= 0) { + inptr = input_buf[0][input_row++]; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + /* We can dispense with GETJSAMPLE() here */ + outptr[RGB_RED] = outptr[RGB_GREEN] = outptr[RGB_BLUE] = inptr[col]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF(void) +ycck_cmyk_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS)))]; + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF(void) +start_pass_dcolor (j_decompress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL(void) +jinit_color_deconverter (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + int ci; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_deconverter)); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_RGB: + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->num_components < 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = grayscale_convert; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for (ci = 1; ci < cinfo->num_components; ci++) + cinfo->comp_info[ci].component_needed = FALSE; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if (cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { + cconvert->pub.color_convert = gray_rgb_convert; + } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if (cinfo->jpeg_color_space == JCS_YCCK) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: + /* Permit null conversion to same output space */ + if (cinfo->out_color_space == cinfo->jpeg_color_space) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert; + } else /* unsupported non-null conversion */ + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + } + + if (cinfo->quantize_colors) + cinfo->output_components = 1; /* single colormapped output component */ + else + cinfo->output_components = cinfo->out_color_components; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdct.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdct.h new file mode 100644 index 0000000..04192a2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdct.h @@ -0,0 +1,176 @@ +/* + * jdct.h + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + * implementations use an array of type FAST_FLOAT, instead.) + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_scaled_size * DCT_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN(void) jpeg_fdct_islow JPP((DCTELEM * data)); +EXTERN(void) jpeg_fdct_ifast JPP((DCTELEM * data)); +EXTERN(void) jpeg_fdct_float JPP((FAST_FLOAT * data)); + +EXTERN(void) jpeg_idct_islow + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_ifast + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_float + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_4x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_2x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_1x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ((INT32) 1) +#define CONST_SCALE (ONE << CONST_BITS) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16(var,const) ((var) * (const)) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jddctmgr.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jddctmgr.c new file mode 100644 index 0000000..bbf8d0e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jddctmgr.c @@ -0,0 +1,269 @@ +/* + * jddctmgr.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub; /* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF(void) +start_pass (j_decompress_ptr cinfo) +{ + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info *compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Select the proper IDCT routine for this component's scaling */ + switch (compptr->DCT_scaled_size) { +#ifdef IDCT_SCALING_SUPPORTED + case 1: + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 2: + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 4: + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; +#endif + case DCTSIZE: + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + break; + default: + ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if (! compptr->component_needed || idct->cur_method[ci] == method) + continue; + qtbl = compptr->quant_table; + if (qtbl == NULL) /* happens if no data yet for component */ + continue; + idct->cur_method[ci] = method; + switch (method) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored as ints to ensure access efficiency. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for (i = 0; i < DCTSIZE2; i++) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[i]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for (i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], + (INT32) aanscales[i]), + CONST_BITS-IFAST_SCALE_BITS); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ((double) qtbl->quantval[i] * + aanscalefactor[row] * aanscalefactor[col]); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL(void) +jinit_inverse_dct (j_decompress_ptr cinfo) +{ + my_idct_ptr idct; + int ci; + jpeg_component_info *compptr; + + idct = (my_idct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_idct_controller)); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(multiplier_table)); + MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdhuff.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdhuff.c new file mode 100644 index 0000000..b5ba39f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdhuff.c @@ -0,0 +1,651 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + + /* Precalculated info set up by start_pass for use in decode_mcu: */ + + /* Pointers to derived tables to be used for each block within an MCU */ + d_derived_tbl * dc_cur_tbls[D_MAX_BLOCKS_IN_MCU]; + d_derived_tbl * ac_cur_tbls[D_MAX_BLOCKS_IN_MCU]; + /* Whether we care about the DC and AC coefficient values for each block */ + boolean dc_needed[D_MAX_BLOCKS_IN_MCU]; + boolean ac_needed[D_MAX_BLOCKS_IN_MCU]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF(void) +start_pass_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, blkn, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2-1 || + cinfo->Ah != 0 || cinfo->Al != 0) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl(cinfo, TRUE, dctbl, + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_d_derived_tbl(cinfo, FALSE, actbl, + & entropy->ac_derived_tbls[actbl]); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Precalculate decoding info for each block in an MCU of this scan */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + /* Precalculate which table to use for each block */ + entropy->dc_cur_tbls[blkn] = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + entropy->ac_cur_tbls[blkn] = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + /* Decide whether we really care about the coefficient values */ + if (compptr->component_needed) { + entropy->dc_needed[blkn] = TRUE; + /* we don't need the ACs if producing a 1/8th-size image */ + entropy->ac_needed[blkn] = (compptr->DCT_scaled_size > 1); + } else { + entropy->dc_needed[blkn] = entropy->ac_needed[blkn] = FALSE; + } + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->pub.insufficient_data = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * This routine also performs some validation checks on the table. + * + * Note this is also used by jdphuff.c. + */ + +GLOBAL(void) +jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, + d_derived_tbl ** pdtbl) +{ + JHUFF_TBL *htbl; + d_derived_tbl *dtbl; + int p, i, l, si, numsymbols; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Note that huffsize[] and huffcode[] are filled in code-length order, + * paralleling the order of the symbols themselves in htbl->huffval[]. + */ + + /* Find the input Huffman table */ + if (tblno < 0 || tblno >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + htbl = + isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno]; + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (d_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(d_derived_tbl)); + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + + p = 0; + for (l = 1; l <= 16; l++) { + i = (int) htbl->bits[l]; + if (i < 0 || p + i > 256) /* protect against table overrun */ + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + while (i--) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + numsymbols = p; + + /* Figure C.2: generate the codes themselves */ + /* We also validate that the counts represent a legal Huffman code tree. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + /* code is now 1 more than the last code used for codelength si; but + * it must still fit in si bits, since no code is allowed to be all ones. + */ + if (((INT32) code) >= (((INT32) 1) << si)) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for (l = 1; l <= 16; l++) { + if (htbl->bits[l]) { + /* valoffset[l] = huffval[] index of 1st symbol of code length l, + * minus the minimum code of length l + */ + dtbl->valoffset[l] = (INT32) p - (INT32) huffcode[p]; + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits)); + + p = 0; + for (l = 1; l <= HUFF_LOOKAHEAD; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++, p++) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); + for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } + + /* Validate symbols as being reasonable. + * For AC tables, we make no check, but accept all byte values 0..255. + * For DC tables, we require the symbols to be in range 0..15. + * (Tighter bounds could be applied depending on the data depth and mode, + * but this is sufficient to ensure safe decoding.) + */ + if (isDC) { + for (i = 0; i < numsymbols; i++) { + int sym = htbl->huffval[i]; + if (sym < 0 || sym > 15) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS (BIT_BUF_SIZE-7) +#endif + + +GLOBAL(boolean) +jpeg_fill_bit_buffer (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits) +/* Load up the bit buffer to a depth of at least nbits */ +{ + /* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + j_decompress_ptr cinfo = state->cinfo; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + /* We fail to do so only if we hit a marker or are forced to suspend. */ + + if (cinfo->unread_marker == 0) { /* cannot advance past a marker */ + while (bits_left < MIN_GET_BITS) { + register int c; + + /* Attempt to read a byte */ + if (bytes_in_buffer == 0) { + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if (c == 0xFF) { + /* Loop here to discard any padding FF's on terminating marker, + * so that we can save a valid unread_marker value. NOTE: we will + * accept multiple FF's followed by a 0 as meaning a single FF data + * byte. This data pattern is not valid according to the standard. + */ + do { + if (bytes_in_buffer == 0) { + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. + * Save the marker code for later use. + * Fine point: it might appear that we should save the marker into + * bitread working state, not straight into permanent state. But + * once we have hit a marker, we cannot need to suspend within the + * current MCU, because we will read no more bytes from the data + * source. So it is OK to update permanent state right away. + */ + cinfo->unread_marker = c; + /* See if we need to insert some fake zero bits. */ + goto no_more_bytes; + } + } + + /* OK, load c into get_buffer */ + get_buffer = (get_buffer << 8) | c; + bits_left += 8; + } /* end while */ + } else { + no_more_bytes: + /* We get here if we've read the marker that terminates the compressed + * data segment. There should be enough bits in the buffer register + * to satisfy the request; if so, no problem. + */ + if (nbits > bits_left) { + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * We use a nonvolatile flag to ensure that only one warning message + * appears per data segment. + */ + if (! cinfo->entropy->insufficient_data) { + WARNMS(cinfo, JWRN_HIT_MARKER); + cinfo->entropy->insufficient_data = TRUE; + } + /* Fill the buffer with zero bits */ + get_buffer <<= MIN_GET_BITS - bits_left; + bits_left = MIN_GET_BITS; + } + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL(int) +jpeg_huff_decode (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits) +{ + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER(*state, l, return -1); + code = GET_BITS(l); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while (code > htbl->maxcode[l]) { + code <<= 1; + CHECK_BIT_BUFFER(*state, 1, return -1); + code |= GET_BITS(1); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if (l > 16) { + WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ (int) (code + htbl->valoffset[l]) ]; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL(boolean) +process_restart (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Reset out-of-data flag, unless read_restart_marker left us smack up + * against a marker. In that case we will end up treating the next data + * segment as empty, and we can avoid producing bogus output pixels by + * leaving the flag set. + */ + if (cinfo->unread_marker == 0) + entropy->pub.insufficient_data = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF(boolean) +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn; + BITREAD_STATE_VARS; + savable_state state; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + JBLOCKROW block = MCU_data[blkn]; + d_derived_tbl * dctbl = entropy->dc_cur_tbls[blkn]; + d_derived_tbl * actbl = entropy->ac_cur_tbls[blkn]; + register int s, k, r; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, dctbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + if (entropy->dc_needed[blkn]) { + /* Convert DC difference to actual value, update last_dc_val */ + int ci = cinfo->MCU_membership[blkn]; + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + (*block)[0] = (JCOEF) s; + } + + if (entropy->ac_needed[blkn]) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + break; + k += 15; + } + } + + } else { + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL(void) +jinit_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdhuff.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdhuff.h new file mode 100644 index 0000000..ae19b6c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdhuff.h @@ -0,0 +1,201 @@ +/* + * jdhuff.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + INT32 valoffset[17]; /* huffval[] offset for codes of length k */ + /* valoffset[k] = huffval[] index of 1st symbol of code length k, less + * the smallest code of length k; so given a code of length k, the + * corresponding symbol is huffval[code + valoffset[k]] + */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't define the size + * with something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* Current data source location */ + /* We need a copy, rather than munging the original, in case of suspension */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + /* Bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* Pointer needed by jpeg_fill_bit_buffer. */ + j_decompress_ptr cinfo; /* back link to decompress master record */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN(boolean) jpeg_fill_bit_buffer + JPP((bitread_working_state * state, register bit_buf_type get_buffer, + register int bits_left, int nbits)); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + +/* Out-of-line case for Huffman code fetching */ +EXTERN(int) jpeg_huff_decode + JPP((bitread_working_state * state, register bit_buf_type get_buffer, + register int bits_left, d_derived_tbl * htbl, int min_bits)); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdinput.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdinput.c new file mode 100644 index 0000000..0c2ac8f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdinput.c @@ -0,0 +1,381 @@ +/* + * jdinput.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub; /* public fields */ + + boolean inheaders; /* TRUE until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF(int) consume_markers JPP((j_decompress_ptr cinfo)); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + +LOCAL(void) +initial_setup (j_decompress_ptr cinfo) +/* Called once, when first SOS marker is reached */ +{ + int ci; + jpeg_component_info *compptr; + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + * In the full decompressor, this will be overridden by jdmaster.c; + * but in the transcoder, jdmaster.c is not used, so we must do it here. + */ + cinfo->min_DCT_scaled_size = DCTSIZE; + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + /* Decide whether file contains multiple scans */ + if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) + cinfo->inputctl->has_multiple_scans = TRUE; + else + cinfo->inputctl->has_multiple_scans = FALSE; +} + + +LOCAL(void) +per_scan_setup (j_decompress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL(void) +latch_quant_tables (j_decompress_ptr cinfo) +{ + int ci, qtblno; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if (compptr->quant_table != NULL) + continue; + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + /* OK, save away the quantization table */ + qtbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(JQUANT_TBL)); + MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF(void) +start_input_pass (j_decompress_ptr cinfo) +{ + per_scan_setup(cinfo); + latch_quant_tables(cinfo); + (*cinfo->entropy->start_pass) (cinfo); + (*cinfo->coef->start_input_pass) (cinfo); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF(void) +finish_input_pass (j_decompress_ptr cinfo) +{ + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + */ + +METHODDEF(int) +consume_markers (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + + val = (*cinfo->marker->read_markers) (cinfo); + + switch (val) { + case JPEG_REACHED_SOS: /* Found SOS */ + if (inputctl->inheaders) { /* 1st SOS */ + initial_setup(cinfo); + inputctl->inheaders = FALSE; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapimin.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if (! inputctl->pub.has_multiple_scans) + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + start_input_pass(cinfo); + } + break; + case JPEG_REACHED_EOI: /* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_NO_SOS); + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if (cinfo->output_scan_number > cinfo->input_scan_number) + cinfo->output_scan_number = cinfo->input_scan_number; + } + break; + case JPEG_SUSPENDED: + break; + } + + return val; +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF(void) +reset_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; + /* Reset other modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->marker->reset_marker_reader) (cinfo); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL(void) +jinit_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_input_controller)); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmainct.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmainct.c new file mode 100644 index 0000000..13c956f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmainct.c @@ -0,0 +1,512 @@ +/* + * jdmainct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub; /* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF(void) process_data_simple_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +METHODDEF(void) process_data_context_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF(void) process_data_crank_post + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#endif + + +LOCAL(void) +alloc_funny_pointers (j_decompress_ptr cinfo) +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + main->xbuffer[0] = (JSAMPIMAGE) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + main->xbuffer[1] = main->xbuffer[0] + cinfo->num_components; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + xbuf += rgroup; /* want one row group at negative offsets */ + main->xbuffer[0][ci] = xbuf; + xbuf += rgroup * (M + 4); + main->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL(void) +make_funny_pointers (j_decompress_ptr cinfo) +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = main->buffer[ci]; + for (i = 0; i < rgroup * (M + 2); i++) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for (i = 0; i < rgroup * 2; i++) { + xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i]; + xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL(void) +set_wraparound_pointers (j_decompress_ptr cinfo) +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; + xbuf0[rgroup*(M+2) + i] = xbuf0[i]; + xbuf1[rgroup*(M+2) + i] = xbuf1[i]; + } + } +} + + +LOCAL(void) +set_bottom_pointers (j_decompress_ptr cinfo) +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight); + if (rows_left == 0) rows_left = iMCUheight; + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if (ci == 0) { + main->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = main->xbuffer[main->whichptr][ci]; + for (i = 0; i < rgroup * 2; i++) { + xbuf[rows_left + i] = xbuf[rows_left-1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->upsample->need_context_rows) { + main->pub.process_data = process_data_context_main; + make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ + main->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + main->context_state = CTX_PREPARE_FOR_IMCU; + main->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + main->pub.process_data = process_data_simple_main; + } + main->buffer_full = FALSE; /* Mark buffer empty */ + main->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + main->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF(void) +process_data_simple_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, main->buffer)) + return; /* suspension forced, can do nothing more */ + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + (*cinfo->post->post_process_data) (cinfo, main->buffer, + &main->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if (main->rowgroup_ctr >= rowgroups_avail) { + main->buffer_full = FALSE; + main->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF(void) +process_data_context_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, + main->xbuffer[main->whichptr])) + return; /* suspension forced, can do nothing more */ + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + main->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch (main->context_state) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main->rowgroup_ctr < main->rowgroups_avail) + return; /* Need to suspend */ + main->context_state = CTX_PREPARE_FOR_IMCU; + if (*out_row_ctr >= out_rows_avail) + return; /* Postprocessor exactly filled output buf */ + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + main->rowgroup_ctr = 0; + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if (main->iMCU_row_ctr == cinfo->total_iMCU_rows) + set_bottom_pointers(cinfo); + main->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main->rowgroup_ctr < main->rowgroups_avail) + return; /* Need to suspend */ + /* After the first iMCU, change wraparound pointers to normal state */ + if (main->iMCU_row_ctr == 1) + set_wraparound_pointers(cinfo); + /* Prepare to load new iMCU row using other xbuffer list */ + main->whichptr ^= 1; /* 0=>1 or 1=>0 */ + main->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + main->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); + main->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF(void) +process_data_crank_post (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL(void) +jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main; + int ci, rgroup, ngroups; + jpeg_component_info *compptr; + + main = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_d_main_controller *) main; + main->pub.start_pass = start_pass_main; + + if (need_full_buffer) /* shouldn't happen */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if (cinfo->upsample->need_context_rows) { + if (cinfo->min_DCT_scaled_size < 2) /* unsupported, see comments above */ + ERREXIT(cinfo, JERR_NOTIMPL); + alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_scaled_size; + } + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + main->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) (rgroup * ngroups)); + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmarker.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmarker.c new file mode 100644 index 0000000..f4cca8c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmarker.c @@ -0,0 +1,1360 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* Private state */ + +typedef struct { + struct jpeg_marker_reader pub; /* public fields */ + + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* Limit on marker data length to save for each marker type */ + unsigned int length_limit_COM; + unsigned int length_limit_APPn[16]; + + /* Status of COM/APPn marker saving */ + jpeg_saved_marker_ptr cur_marker; /* NULL if not processing a marker */ + unsigned int bytes_read; /* data bytes read so far in marker */ + /* Note: cur_marker is not linked into marker_list until it's all read. */ +} my_marker_reader; + +typedef my_marker_reader * my_marker_ptr; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS(cinfo) \ + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC(cinfo) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- used only in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD(cinfo) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL(cinfo,action) \ + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + INPUT_RELOAD(cinfo); \ + } + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = GETJOCTET(*next_input_byte++); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V += GETJOCTET(*next_input_byte++); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters + * can fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments + * that might not fit. If we are simply dropping such a marker, we use + * skip_input_data to get past it, and thereby put the problem on the + * source manager's shoulders. If we are saving the marker's contents + * into memory, we use a slightly different convention: when forced to + * suspend, the marker processor updates the restart point to the end of + * what it's consumed (ie, the end of the buffer) before returning FALSE. + * On resumption, cinfo->unread_marker still contains the marker code, + * but the data source will point to the next chunk of marker data. + * The marker processor must retain internal state to deal with this. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL(boolean) +get_soi (j_decompress_ptr cinfo) +/* Process an SOI marker */ +{ + int i; + + TRACEMS(cinfo, 1, JTRC_SOI); + + if (cinfo->marker->saw_SOI) + ERREXIT(cinfo, JERR_SOI_DUPLICATE); + + /* Reset all parameters that are defined to be reset by SOI */ + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->JFIF_major_version = 1; /* set default JFIF APP0 values */ + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL(boolean) +get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) +/* Process a SOFn marker */ +{ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE); + INPUT_BYTE(cinfo, cinfo->num_components, return FALSE); + + length -= 8; + + TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); + + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_DUPLICATE); + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + if (length != (cinfo->num_components * 3)) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->component_index = ci; + INPUT_BYTE(cinfo, compptr->component_id, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + compptr->h_samp_factor = (c >> 4) & 15; + compptr->v_samp_factor = (c ) & 15; + INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); + + TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_sos (j_decompress_ptr cinfo) +/* Process a SOS marker */ +{ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + if (! cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOS_NO_SOF); + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */ + + TRACEMS1(cinfo, 1, JTRC_SOS, n); + + if (length != (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for (i = 0; i < n; i++) { + INPUT_BYTE(cinfo, cc, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (cc == compptr->component_id) + goto id_found; + } + + ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); + + id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = (c >> 4) & 15; + compptr->ac_tbl_no = (c ) & 15; + + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ss = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Se = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ah = (c >> 4) & 15; + cinfo->Al = (c ) & 15; + + TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +#ifdef D_ARITH_CODING_SUPPORTED + +LOCAL(boolean) +get_dac (j_decompress_ptr cinfo) +/* Process a DAC marker */ +{ + INT32 length; + int index, val; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + INPUT_BYTE(cinfo, val, return FALSE); + + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_DAC, index, val); + + if (index < 0 || index >= (2*NUM_ARITH_TBLS)) + ERREXIT1(cinfo, JERR_DAC_INDEX, index); + + if (index >= NUM_ARITH_TBLS) { /* define AC table */ + cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); + cinfo->arith_dc_U[index] = (UINT8) (val >> 4); + if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) + ERREXIT1(cinfo, JERR_DAC_VALUE, val); + } + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + +#else /* ! D_ARITH_CODING_SUPPORTED */ + +#define get_dac(cinfo) skip_variable(cinfo) + +#endif /* D_ARITH_CODING_SUPPORTED */ + + +LOCAL(boolean) +get_dht (j_decompress_ptr cinfo) +/* Process a DHT marker */ +{ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 16) { + INPUT_BYTE(cinfo, index, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DHT, index); + + bits[0] = 0; + count = 0; + for (i = 1; i <= 16; i++) { + INPUT_BYTE(cinfo, bits[i], return FALSE); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); + + /* Here we just do minimal validation of the counts to avoid walking + * off the end of our table space. jdhuff.c will check more carefully. + */ + if (count > 256 || ((INT32) count) > length) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + + for (i = 0; i < count; i++) + INPUT_BYTE(cinfo, huffval[i], return FALSE); + + length -= count; + + if (index & 0x10) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_dqt (j_decompress_ptr cinfo) +/* Process a DQT marker */ +{ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, n, return FALSE); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2(cinfo, 1, JTRC_DQT, n, prec); + + if (n >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, n); + + if (cinfo->quant_tbl_ptrs[n] == NULL) + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + INPUT_2BYTES(cinfo, tmp, return FALSE); + else + INPUT_BYTE(cinfo, tmp, return FALSE); + /* We convert the zigzag-order table to natural array order. */ + quant_ptr->quantval[jpeg_natural_order[i]] = (UINT16) tmp; + } + + if (cinfo->err->trace_level >= 2) { + for (i = 0; i < DCTSIZE2; i += 8) { + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + } + } + + length -= DCTSIZE2+1; + if (prec) length -= DCTSIZE2; + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_dri (j_decompress_ptr cinfo) +/* Process a DRI marker */ +{ + INT32 length; + unsigned int tmp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + if (length != 4) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_2BYTES(cinfo, tmp, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DRI, tmp); + + cinfo->restart_interval = tmp; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Routines for processing APPn and COM markers. + * These are either saved in memory or discarded, per application request. + * APP0 and APP14 are specially checked to see if they are + * JFIF and Adobe markers, respectively. + */ + +#define APP0_DATA_LEN 14 /* Length of interesting data in APP0 */ +#define APP14_DATA_LEN 12 /* Length of interesting data in APP14 */ +#define APPN_DATA_LEN 14 /* Must be the largest of the above!! */ + + +LOCAL(void) +examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data, + unsigned int datalen, INT32 remaining) +/* Examine first few bytes from an APP0. + * Take appropriate action if it is a JFIF marker. + * datalen is # of bytes at data[], remaining is length of rest of marker data. + */ +{ + INT32 totallen = (INT32) datalen + remaining; + + if (datalen >= APP0_DATA_LEN && + GETJOCTET(data[0]) == 0x4A && + GETJOCTET(data[1]) == 0x46 && + GETJOCTET(data[2]) == 0x49 && + GETJOCTET(data[3]) == 0x46 && + GETJOCTET(data[4]) == 0) { + /* Found JFIF APP0 marker: save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->JFIF_major_version = GETJOCTET(data[5]); + cinfo->JFIF_minor_version = GETJOCTET(data[6]); + cinfo->density_unit = GETJOCTET(data[7]); + cinfo->X_density = (GETJOCTET(data[8]) << 8) + GETJOCTET(data[9]); + cinfo->Y_density = (GETJOCTET(data[10]) << 8) + GETJOCTET(data[11]); + /* Check version. + * Major version must be 1, anything else signals an incompatible change. + * (We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec.) + * Minor version should be 0..2, but process anyway if newer. + */ + if (cinfo->JFIF_major_version != 1) + WARNMS2(cinfo, JWRN_JFIF_MAJOR, + cinfo->JFIF_major_version, cinfo->JFIF_minor_version); + /* Generate trace messages */ + TRACEMS5(cinfo, 1, JTRC_JFIF, + cinfo->JFIF_major_version, cinfo->JFIF_minor_version, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + /* Validate thumbnail dimensions and issue appropriate messages */ + if (GETJOCTET(data[12]) | GETJOCTET(data[13])) + TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, + GETJOCTET(data[12]), GETJOCTET(data[13])); + totallen -= APP0_DATA_LEN; + if (totallen != + ((INT32)GETJOCTET(data[12]) * (INT32)GETJOCTET(data[13]) * (INT32) 3)) + TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) totallen); + } else if (datalen >= 6 && + GETJOCTET(data[0]) == 0x4A && + GETJOCTET(data[1]) == 0x46 && + GETJOCTET(data[2]) == 0x58 && + GETJOCTET(data[3]) == 0x58 && + GETJOCTET(data[4]) == 0) { + /* Found JFIF "JFXX" extension APP0 marker */ + /* The library doesn't actually do anything with these, + * but we try to produce a helpful trace message. + */ + switch (GETJOCTET(data[5])) { + case 0x10: + TRACEMS1(cinfo, 1, JTRC_THUMB_JPEG, (int) totallen); + break; + case 0x11: + TRACEMS1(cinfo, 1, JTRC_THUMB_PALETTE, (int) totallen); + break; + case 0x13: + TRACEMS1(cinfo, 1, JTRC_THUMB_RGB, (int) totallen); + break; + default: + TRACEMS2(cinfo, 1, JTRC_JFIF_EXTENSION, + GETJOCTET(data[5]), (int) totallen); + break; + } + } else { + /* Start of APP0 does not match "JFIF" or "JFXX", or too short */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) totallen); + } +} + + +LOCAL(void) +examine_app14 (j_decompress_ptr cinfo, JOCTET FAR * data, + unsigned int datalen, INT32 remaining) +/* Examine first few bytes from an APP14. + * Take appropriate action if it is an Adobe marker. + * datalen is # of bytes at data[], remaining is length of rest of marker data. + */ +{ + unsigned int version, flags0, flags1, transform; + + if (datalen >= APP14_DATA_LEN && + GETJOCTET(data[0]) == 0x41 && + GETJOCTET(data[1]) == 0x64 && + GETJOCTET(data[2]) == 0x6F && + GETJOCTET(data[3]) == 0x62 && + GETJOCTET(data[4]) == 0x65) { + /* Found Adobe APP14 marker */ + version = (GETJOCTET(data[5]) << 8) + GETJOCTET(data[6]); + flags0 = (GETJOCTET(data[7]) << 8) + GETJOCTET(data[8]); + flags1 = (GETJOCTET(data[9]) << 8) + GETJOCTET(data[10]); + transform = GETJOCTET(data[11]); + TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe", or too short */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) (datalen + remaining)); + } +} + + +METHODDEF(boolean) +get_interesting_appn (j_decompress_ptr cinfo) +/* Process an APP0 or APP14 marker without saving it */ +{ + INT32 length; + JOCTET b[APPN_DATA_LEN]; + unsigned int i, numtoread; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* get the interesting part of the marker data */ + if (length >= APPN_DATA_LEN) + numtoread = APPN_DATA_LEN; + else if (length > 0) + numtoread = (unsigned int) length; + else + numtoread = 0; + for (i = 0; i < numtoread; i++) + INPUT_BYTE(cinfo, b[i], return FALSE); + length -= numtoread; + + /* process it */ + switch (cinfo->unread_marker) { + case M_APP0: + examine_app0(cinfo, (JOCTET FAR *) b, numtoread, length); + break; + case M_APP14: + examine_app14(cinfo, (JOCTET FAR *) b, numtoread, length); + break; + default: + /* can't get here unless jpeg_save_markers chooses wrong processor */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + + /* skip any remaining data -- could be lots */ + INPUT_SYNC(cinfo); + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +#ifdef SAVE_MARKERS_SUPPORTED + +METHODDEF(boolean) +save_marker (j_decompress_ptr cinfo) +/* Save an APPn or COM marker into the marker list */ +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + jpeg_saved_marker_ptr cur_marker = marker->cur_marker; + unsigned int bytes_read, data_length; + JOCTET FAR * data; + INT32 length = 0; + INPUT_VARS(cinfo); + + if (cur_marker == NULL) { + /* begin reading a marker */ + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + if (length >= 0) { /* watch out for bogus length word */ + /* figure out how much we want to save */ + unsigned int limit; + if (cinfo->unread_marker == (int) M_COM) + limit = marker->length_limit_COM; + else + limit = marker->length_limit_APPn[cinfo->unread_marker - (int) M_APP0]; + if ((unsigned int) length < limit) + limit = (unsigned int) length; + /* allocate and initialize the marker item */ + cur_marker = (jpeg_saved_marker_ptr) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(struct jpeg_marker_struct) + limit); + cur_marker->next = NULL; + cur_marker->marker = (UINT8) cinfo->unread_marker; + cur_marker->original_length = (unsigned int) length; + cur_marker->data_length = limit; + /* data area is just beyond the jpeg_marker_struct */ + data = cur_marker->data = (JOCTET FAR *) (cur_marker + 1); + marker->cur_marker = cur_marker; + marker->bytes_read = 0; + bytes_read = 0; + data_length = limit; + } else { + /* deal with bogus length word */ + bytes_read = data_length = 0; + data = NULL; + } + } else { + /* resume reading a marker */ + bytes_read = marker->bytes_read; + data_length = cur_marker->data_length; + data = cur_marker->data + bytes_read; + } + + while (bytes_read < data_length) { + INPUT_SYNC(cinfo); /* move the restart point to here */ + marker->bytes_read = bytes_read; + /* If there's not at least one byte in buffer, suspend */ + MAKE_BYTE_AVAIL(cinfo, return FALSE); + /* Copy bytes with reasonable rapidity */ + while (bytes_read < data_length && bytes_in_buffer > 0) { + *data++ = *next_input_byte++; + bytes_in_buffer--; + bytes_read++; + } + } + + /* Done reading what we want to read */ + if (cur_marker != NULL) { /* will be NULL if bogus length word */ + /* Add new marker to end of list */ + if (cinfo->marker_list == NULL) { + cinfo->marker_list = cur_marker; + } else { + jpeg_saved_marker_ptr prev = cinfo->marker_list; + while (prev->next != NULL) + prev = prev->next; + prev->next = cur_marker; + } + /* Reset pointer & calc remaining data length */ + data = cur_marker->data; + length = cur_marker->original_length - data_length; + } + /* Reset to initial state for next marker */ + marker->cur_marker = NULL; + + /* Process the marker if interesting; else just make a generic trace msg */ + switch (cinfo->unread_marker) { + case M_APP0: + examine_app0(cinfo, data, data_length, length); + break; + case M_APP14: + examine_app14(cinfo, data, data_length, length); + break; + default: + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, + (int) (data_length + length)); + break; + } + + /* skip any remaining data -- could be lots */ + INPUT_SYNC(cinfo); /* do before skip_input_data */ + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + +#endif /* SAVE_MARKERS_SUPPORTED */ + + +METHODDEF(boolean) +skip_variable (j_decompress_ptr cinfo) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + INT32 length; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); + + INPUT_SYNC(cinfo); /* do before skip_input_data */ + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL(boolean) +next_marker (j_decompress_ptr cinfo) +{ + int c; + INPUT_VARS(cinfo); + + for (;;) { + INPUT_BYTE(cinfo, c, return FALSE); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while (c != 0xFF) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC(cinfo); + INPUT_BYTE(cinfo, c, return FALSE); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE(cinfo, c, return FALSE); + } while (c == 0xFF); + if (c != 0) + break; /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC(cinfo); + } + + if (cinfo->marker->discarded_bytes != 0) { + WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +first_marker (j_decompress_ptr cinfo) +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ +{ + int c, c2; + INPUT_VARS(cinfo); + + INPUT_BYTE(cinfo, c, return FALSE); + INPUT_BYTE(cinfo, c2, return FALSE); + if (c != 0xFF || c2 != (int) M_SOI) + ERREXIT2(cinfo, JERR_NO_SOI, c, c2); + + cinfo->unread_marker = c2; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF(int) +read_markers (j_decompress_ptr cinfo) +{ + /* Outer loop repeats once for each marker. */ + for (;;) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if (cinfo->unread_marker == 0) { + if (! cinfo->marker->saw_SOI) { + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; + } else { + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch (cinfo->unread_marker) { + case M_SOI: + if (! get_soi(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + if (! get_sof(cinfo, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF2: /* Progressive, Huffman */ + if (! get_sof(cinfo, TRUE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if (! get_sof(cinfo, FALSE, TRUE)) + return JPEG_SUSPENDED; + break; + + case M_SOF10: /* Progressive, arithmetic */ + if (! get_sof(cinfo, TRUE, TRUE)) + return JPEG_SUSPENDED; + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); + break; + + case M_SOS: + if (! get_sos(cinfo)) + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS(cinfo, 1, JTRC_EOI); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if (! get_dac(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DHT: + if (! get_dht(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DQT: + if (! get_dqt(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DRI: + if (! get_dri(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (! (*((my_marker_ptr) cinfo->marker)->process_APPn[ + cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_COM: + if (! (*((my_marker_ptr) cinfo->marker)->process_COM) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if (! skip_variable(cinfo)) + return JPEG_SUSPENDED; + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF(boolean) +read_restart_marker (j_decompress_ptr cinfo) +{ + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if (cinfo->unread_marker == 0) { + if (! next_marker(cinfo)) + return FALSE; + } + + if (cinfo->unread_marker == + ((int) M_RST0 + cinfo->marker->next_restart_num)) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1(cinfo, 3, JTRC_RST, cinfo->marker->next_restart_num); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if (! (*cinfo->src->resync_to_restart) (cinfo, + cinfo->marker->next_restart_num)) + return FALSE; + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL(boolean) +jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) +{ + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;;) { + if (marker < (int) M_SOF0) + action = 2; /* invalid marker */ + else if (marker < (int) M_RST0 || marker > (int) M_RST7) + action = 3; /* valid non-restart marker */ + else { + if (marker == ((int) M_RST0 + ((desired+1) & 7)) || + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ + else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ + else + action = 1; /* desired restart or too far away */ + } + TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); + switch (action) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if (! next_marker(cinfo)) + return FALSE; + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF(void) +reset_marker_reader (j_decompress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + marker->pub.saw_SOI = FALSE; /* set internal state too */ + marker->pub.saw_SOF = FALSE; + marker->pub.discarded_bytes = 0; + marker->cur_marker = NULL; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL(void) +jinit_marker_reader (j_decompress_ptr cinfo) +{ + my_marker_ptr marker; + int i; + + /* Create subobject in permanent pool */ + marker = (my_marker_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_marker_reader)); + cinfo->marker = (struct jpeg_marker_reader *) marker; + /* Initialize public method pointers */ + marker->pub.reset_marker_reader = reset_marker_reader; + marker->pub.read_markers = read_markers; + marker->pub.read_restart_marker = read_restart_marker; + /* Initialize COM/APPn processing. + * By default, we examine and then discard APP0 and APP14, + * but simply discard COM and all other APPn. + */ + marker->process_COM = skip_variable; + marker->length_limit_COM = 0; + for (i = 0; i < 16; i++) { + marker->process_APPn[i] = skip_variable; + marker->length_limit_APPn[i] = 0; + } + marker->process_APPn[0] = get_interesting_appn; + marker->process_APPn[14] = get_interesting_appn; + /* Reset marker processing state */ + reset_marker_reader(cinfo); +} + + +/* + * Control saving of COM and APPn markers into marker_list. + */ + +#ifdef SAVE_MARKERS_SUPPORTED + +GLOBAL(void) +jpeg_save_markers (j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + long maxlength; + jpeg_marker_parser_method processor; + + /* Length limit mustn't be larger than what we can allocate + * (should only be a concern in a 16-bit environment). + */ + maxlength = cinfo->mem->max_alloc_chunk - SIZEOF(struct jpeg_marker_struct); + if (((long) length_limit) > maxlength) + length_limit = (unsigned int) maxlength; + + /* Choose processor routine to use. + * APP0/APP14 have special requirements. + */ + if (length_limit) { + processor = save_marker; + /* If saving APP0/APP14, save at least enough for our internal use. */ + if (marker_code == (int) M_APP0 && length_limit < APP0_DATA_LEN) + length_limit = APP0_DATA_LEN; + else if (marker_code == (int) M_APP14 && length_limit < APP14_DATA_LEN) + length_limit = APP14_DATA_LEN; + } else { + processor = skip_variable; + /* If discarding APP0/APP14, use our regular on-the-fly processor. */ + if (marker_code == (int) M_APP0 || marker_code == (int) M_APP14) + processor = get_interesting_appn; + } + + if (marker_code == (int) M_COM) { + marker->process_COM = processor; + marker->length_limit_COM = length_limit; + } else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15) { + marker->process_APPn[marker_code - (int) M_APP0] = processor; + marker->length_limit_APPn[marker_code - (int) M_APP0] = length_limit; + } else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} + +#endif /* SAVE_MARKERS_SUPPORTED */ + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL(void) +jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + + if (marker_code == (int) M_COM) + marker->process_COM = routine; + else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15) + marker->process_APPn[marker_code - (int) M_APP0] = routine; + else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmaster.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmaster.c new file mode 100644 index 0000000..2802c5b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmaster.c @@ -0,0 +1,557 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL(boolean) +use_merged_upsample (j_decompress_ptr cinfo) +{ +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling) + return FALSE; + /* jdmerge.c only supports YCC=>RGB color conversion */ + if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE) + return FALSE; + /* and it only handles 2h1v or 2h2v sampling ratios */ + if (cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1) + return FALSE; + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if (cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size) + return FALSE; + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL(void) +jpeg_calc_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ +#ifdef IDCT_SCALING_SUPPORTED + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if (cinfo->scale_num * 8 <= cinfo->scale_denom) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 8L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 8L); + cinfo->min_DCT_scaled_size = 1; + } else if (cinfo->scale_num * 4 <= cinfo->scale_denom) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 4L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 4L); + cinfo->min_DCT_scaled_size = 2; + } else if (cinfo->scale_num * 2 <= cinfo->scale_denom) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 2L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 2L); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + int ssize = cinfo->min_DCT_scaled_size; + while (ssize < DCTSIZE && + (compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && + (compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * + (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * + (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = (cinfo->quantize_colors ? 1 : + cinfo->out_color_components); + + /* See if upsampler will want to emit more than one row at a time */ + if (use_merged_upsample(cinfo)) + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + else + cinfo->rec_outbuf_height = 1; +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL(void) +prepare_range_limit_table (j_decompress_ptr cinfo) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO(table + (2 * (MAXJSAMPLE+1)), + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL(void) +master_selection (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions(cinfo); + prepare_range_limit_table(cinfo); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample(cinfo); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if (! cinfo->quantize_colors || ! cinfo->buffered_image) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if (cinfo->quantize_colors) { + if (cinfo->raw_data_out) + ERREXIT(cinfo, JERR_NOTIMPL); + /* 2-pass quantizer only works in 3-component color space. */ + if (cinfo->out_color_components != 3) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if (cinfo->colormap != NULL) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if (cinfo->enable_1pass_quant) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer(cinfo); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer(cinfo); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if (! cinfo->raw_data_out) { + if (master->using_merged_upsample) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler(cinfo); /* does color conversion too */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + jinit_color_deconverter(cinfo); + jinit_upsampler(cinfo); + } + jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant); + } + /* Inverse DCT */ + jinit_inverse_dct(cinfo); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller(cinfo, use_c_buffer); + + if (! cinfo->raw_data_out) + jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if (cinfo->progress != NULL && ! cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapistd.c will crank the pass to completion.) + */ + +METHODDEF(void) +prepare_for_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (master->pub.is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + (*cinfo->cquantize->start_pass) (cinfo, FALSE); + (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if (cinfo->quantize_colors && cinfo->colormap == NULL) { + /* Select new quantization method */ + if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if (cinfo->enable_1pass_quant) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT(cinfo, JERR_MODE_CHANGE); + } + } + (*cinfo->idct->start_pass) (cinfo); + (*cinfo->coef->start_output_pass) (cinfo); + if (! cinfo->raw_data_out) { + if (! master->using_merged_upsample) + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->upsample->start_pass) (cinfo); + if (cinfo->quantize_colors) + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->post->start_pass) (cinfo, + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + } + } + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + (master->pub.is_dummy_pass ? 2 : 1); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) { + cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF(void) +finish_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (cinfo->quantize_colors) + (*cinfo->cquantize->finish_pass) (cinfo); + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL(void) +jpeg_new_colormap (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_BUFIMAGE) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + (*cinfo->cquantize->new_color_map) (cinfo); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL(void) +jinit_master_decompress (j_decompress_ptr cinfo) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_decomp_master)); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection(cinfo); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmerge.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmerge.c new file mode 100644 index 0000000..3744446 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdmerge.c @@ -0,0 +1,400 @@ +/* + * jdmerge.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains code for merged upsampling/color conversion. + * + * This file combines functions from jdsample.c and jdcolor.c; + * read those files first to understand what's going on. + * + * When the chroma components are to be upsampled by simple replication + * (ie, box filtering), we can save some work in color conversion by + * calculating all the output pixels corresponding to a pair of chroma + * samples at one time. In the conversion equations + * R = Y + K1 * Cr + * G = Y + K2 * Cb + K3 * Cr + * B = Y + K4 * Cb + * only the Y term varies among the group of pixels corresponding to a pair + * of chroma samples, so the rest of the terms can be calculated just once. + * At typical sampling ratios, this eliminates half or three-quarters of the + * multiplications needed for color conversion. + * + * This file currently provides implementations for the following cases: + * YCbCr => RGB color conversion only. + * Sampling ratios of 2h1v or 2h2v. + * No scaling needed at upsample time. + * Corner-aligned (non-CCIR601) sampling alignment. + * Other special cases could be added, but in most applications these are + * the only common cases. (For uncommon cases we fall back on the more + * general code in jdsample.c and jdcolor.c.) + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef UPSAMPLE_MERGING_SUPPORTED + + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Pointer to routine to do actual upsampling/conversion of one row group */ + JMETHOD(void, upmethod, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf)); + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ + + /* For 2:1 vertical sampling, we produce two output rows at a time. + * We need a "spare" row buffer to hold the second output row if the + * application provides just a one-row buffer; we also use the spare + * to discard the dummy last row if the image height is odd. + */ + JSAMPROW spare_row; + boolean spare_full; /* T if spare buffer is occupied */ + + JDIMENSION out_row_width; /* samples per output row */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + * This is taken directly from jdcolor.c; see that file for more info. + */ + +LOCAL(void) +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int i; + INT32 x; + SHIFT_TEMPS + + upsample->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + upsample->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + upsample->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + upsample->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + upsample->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + upsample->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF(void) +start_pass_merged_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the spare buffer empty */ + upsample->spare_full = FALSE; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * The control routine just handles the row buffering considerations. + */ + +METHODDEF(void) +merged_2v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +/* 2:1 vertical sampling case: may need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPROW work_ptrs[2]; + JDIMENSION num_rows; /* number of rows returned to caller */ + + if (upsample->spare_full) { + /* If we have a spare row saved from a previous cycle, just return it. */ + jcopy_sample_rows(& upsample->spare_row, 0, output_buf + *out_row_ctr, 0, + 1, upsample->out_row_width); + num_rows = 1; + upsample->spare_full = FALSE; + } else { + /* Figure number of rows to return to caller. */ + num_rows = 2; + /* Not more than the distance to the end of the image. */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + /* Create output pointer array for upsampler. */ + work_ptrs[0] = output_buf[*out_row_ctr]; + if (num_rows > 1) { + work_ptrs[1] = output_buf[*out_row_ctr + 1]; + } else { + work_ptrs[1] = upsample->spare_row; + upsample->spare_full = TRUE; + } + /* Now do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, work_ptrs); + } + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (! upsample->spare_full) + (*in_row_group_ctr)++; +} + + +METHODDEF(void) +merged_1v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +/* 1:1 vertical sampling case: much easier, never need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Just do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, + output_buf + *out_row_ctr); + /* Adjust counts */ + (*out_row_ctr)++; + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by the control routines to do + * the actual upsampling/conversion. One row group is processed per call. + * + * Note: since we may be writing directly into application-supplied buffers, + * we have to be honest about the output width; we can't assume the buffer + * has been rounded up to an even width. + */ + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 1:1 vertical. + */ + +METHODDEF(void) +h2v1_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr; + JSAMPROW inptr0, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr0 = input_buf[0][in_row_group_ctr]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr = output_buf[0]; + /* Loop for each pair of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 2 Y values and emit 2 pixels */ + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr0); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical. + */ + +METHODDEF(void) +h2v2_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr0, outptr1; + JSAMPROW inptr00, inptr01, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr00 = input_buf[0][in_row_group_ctr*2]; + inptr01 = input_buf[0][in_row_group_ctr*2 + 1]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr0 = output_buf[0]; + outptr1 = output_buf[1]; + /* Loop for each group of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 4 Y values and emit 4 pixels */ + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr00); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + y = GETJSAMPLE(*inptr01); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Module initialization routine for merged upsampling/color conversion. + * + * NB: this is called under the conditions determined by use_merged_upsample() + * in jdmaster.c. That routine MUST correspond to the actual capabilities + * of this module; no safety checks are made here. + */ + +GLOBAL(void) +jinit_merged_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_merged_upsample; + upsample->pub.need_context_rows = FALSE; + + upsample->out_row_width = cinfo->output_width * cinfo->out_color_components; + + if (cinfo->max_v_samp_factor == 2) { + upsample->pub.upsample = merged_2v_upsample; + upsample->upmethod = h2v2_merged_upsample; + /* Allocate a spare row buffer */ + upsample->spare_row = (JSAMPROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) (upsample->out_row_width * SIZEOF(JSAMPLE))); + } else { + upsample->pub.upsample = merged_1v_upsample; + upsample->upmethod = h2v1_merged_upsample; + /* No spare row needed */ + upsample->spare_row = NULL; + } + + build_ycc_rgb_table(cinfo); +} + +#endif /* UPSAMPLE_MERGING_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdphuff.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdphuff.c new file mode 100644 index 0000000..2267809 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdphuff.c @@ -0,0 +1,668 @@ +/* + * jdphuff.c + * + * Copyright (C) 1995-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines for progressive JPEG. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdhuff.c */ + + +#ifdef D_PROGRESSIVE_SUPPORTED + +/* + * Expanded entropy decoder object for progressive Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + unsigned int EOBRUN; /* remaining EOBs in EOBRUN */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).EOBRUN = (src).EOBRUN, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + d_derived_tbl * ac_derived_tbl; /* active table during an AC scan */ +} phuff_entropy_decoder; + +typedef phuff_entropy_decoder * phuff_entropy_ptr; + +/* Forward declarations */ +METHODDEF(boolean) decode_mcu_DC_first JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) decode_mcu_AC_first JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) decode_mcu_DC_refine JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) decode_mcu_AC_refine JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF(void) +start_pass_phuff_decoder (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band, bad; + int ci, coefi, tbl; + int *coef_bit_ptr; + jpeg_component_info * compptr; + + is_DC_band = (cinfo->Ss == 0); + + /* Validate scan parameters */ + bad = FALSE; + if (is_DC_band) { + if (cinfo->Se != 0) + bad = TRUE; + } else { + /* need not check Ss/Se < 0 since they came from unsigned bytes */ + if (cinfo->Ss > cinfo->Se || cinfo->Se >= DCTSIZE2) + bad = TRUE; + /* AC scans may have only one component */ + if (cinfo->comps_in_scan != 1) + bad = TRUE; + } + if (cinfo->Ah != 0) { + /* Successive approximation refinement scan: must have Al = Ah-1. */ + if (cinfo->Al != cinfo->Ah-1) + bad = TRUE; + } + if (cinfo->Al > 13) /* need not check for < 0 */ + bad = TRUE; + /* Arguably the maximum Al value should be less than 13 for 8-bit precision, + * but the spec doesn't say so, and we try to be liberal about what we + * accept. Note: large Al values could result in out-of-range DC + * coefficients during early scans, leading to bizarre displays due to + * overflows in the IDCT math. But we won't crash. + */ + if (bad) + ERREXIT4(cinfo, JERR_BAD_PROGRESSION, + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); + /* Update progression status, and verify that scan order is legal. + * Note that inter-scan inconsistencies are treated as warnings + * not fatal errors ... not clear if this is right way to behave. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + int cindex = cinfo->cur_comp_info[ci]->component_index; + coef_bit_ptr = & cinfo->coef_bits[cindex][0]; + if (!is_DC_band && coef_bit_ptr[0] < 0) /* AC without prior DC scan */ + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0); + for (coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++) { + int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi]; + if (cinfo->Ah != expected) + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi); + coef_bit_ptr[coefi] = cinfo->Al; + } + } + + /* Select MCU decoding routine */ + if (cinfo->Ah == 0) { + if (is_DC_band) + entropy->pub.decode_mcu = decode_mcu_DC_first; + else + entropy->pub.decode_mcu = decode_mcu_AC_first; + } else { + if (is_DC_band) + entropy->pub.decode_mcu = decode_mcu_DC_refine; + else + entropy->pub.decode_mcu = decode_mcu_AC_refine; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Make sure requested tables are present, and compute derived tables. + * We may build same derived table more than once, but it's not expensive. + */ + if (is_DC_band) { + if (cinfo->Ah == 0) { /* DC refinement needs no table */ + tbl = compptr->dc_tbl_no; + jpeg_make_d_derived_tbl(cinfo, TRUE, tbl, + & entropy->derived_tbls[tbl]); + } + } else { + tbl = compptr->ac_tbl_no; + jpeg_make_d_derived_tbl(cinfo, FALSE, tbl, + & entropy->derived_tbls[tbl]); + /* remember the single active table */ + entropy->ac_derived_tbl = entropy->derived_tbls[tbl]; + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->pub.insufficient_data = FALSE; + + /* Initialize private state variables */ + entropy->saved.EOBRUN = 0; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL(boolean) +process_restart (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Re-init EOB run count, too */ + entropy->saved.EOBRUN = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Reset out-of-data flag, unless read_restart_marker left us smack up + * against a marker. In that case we will end up treating the next data + * segment as empty, and we can avoid producing bogus output pixels by + * leaving the flag set. + */ + if (cinfo->unread_marker == 0) + entropy->pub.insufficient_data = FALSE; + + return TRUE; +} + + +/* + * Huffman MCU decoding. + * Each of these routines decodes and returns one MCU's worth of + * Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER. + * + * We return FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * spectral selection, since we'll just re-assign them on the next call. + * Successive approximation AC refinement has to be more careful, however.) + */ + +/* + * MCU decoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Al = cinfo->Al; + register int s, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * tbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + tbl = entropy->derived_tbls[compptr->dc_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, tbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Scale and output the coefficient (assumes jpeg_natural_order[0]=0) */ + (*block)[0] = (JCOEF) (s << Al); + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Se = cinfo->Se; + int Al = cinfo->Al; + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state. + * We can avoid loading/saving bitread state if in an EOB run. + */ + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ + + /* There is always only one block per MCU */ + + if (EOBRUN > 0) /* if it's a band of zeroes... */ + EOBRUN--; /* ...process it now (we do nothing) */ + else { + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + for (k = cinfo->Ss; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, return FALSE, label2); + r = s >> 4; + s &= 15; + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Scale and output coefficient in natural (dezigzagged) order */ + (*block)[jpeg_natural_order[k]] = (JCOEF) (s << Al); + } else { + if (r == 15) { /* ZRL */ + k += 15; /* skip 15 zeroes in band */ + } else { /* EOBr, run length is 2^r + appended bits */ + EOBRUN = 1 << r; + if (r) { /* EOBr, r > 0 */ + CHECK_BIT_BUFFER(br_state, r, return FALSE); + r = GET_BITS(r); + EOBRUN += r; + } + EOBRUN--; /* this band is processed at this moment */ + break; /* force end-of-band */ + } + } + } + + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + } + + /* Completed MCU, so update state */ + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF(boolean) +decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int blkn; + JBLOCKROW block; + BITREAD_STATE_VARS; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Not worth the cycles to check insufficient_data here, + * since we will not change the data anyway if we read zeroes. + */ + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* Encoded data is simply the next bit of the two's-complement DC value */ + CHECK_BIT_BUFFER(br_state, 1, return FALSE); + if (GET_BITS(1)) + (*block)[0] |= p1; + /* Note: since we use |=, repeating the assignment later is safe */ + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Se = cinfo->Se; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int m1 = (-1) << cinfo->Al; /* -1 in the bit position being coded */ + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + JCOEFPTR thiscoef; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + int num_newnz; + int newnz_pos[DCTSIZE2]; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, don't modify the MCU. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ + + /* There is always only one block per MCU */ + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + /* If we are forced to suspend, we must undo the assignments to any newly + * nonzero coefficients in the block, because otherwise we'd get confused + * next time about which coefficients were already nonzero. + * But we need not undo addition of bits to already-nonzero coefficients; + * instead, we can test the current bit to see if we already did it. + */ + num_newnz = 0; + + /* initialize coefficient loop counter to start of band */ + k = cinfo->Ss; + + if (EOBRUN == 0) { + for (; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, goto undoit, label3); + r = s >> 4; + s &= 15; + if (s) { + if (s != 1) /* size of new coef should always be 1 */ + WARNMS(cinfo, JWRN_HUFF_BAD_CODE); + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) + s = p1; /* newly nonzero coef is positive */ + else + s = m1; /* newly nonzero coef is negative */ + } else { + if (r != 15) { + EOBRUN = 1 << r; /* EOBr, run length is 2^r + appended bits */ + if (r) { + CHECK_BIT_BUFFER(br_state, r, goto undoit); + r = GET_BITS(r); + EOBRUN += r; + } + break; /* rest of block is handled by EOB logic */ + } + /* note s = 0 for processing ZRL */ + } + /* Advance over already-nonzero coefs and r still-zero coefs, + * appending correction bits to the nonzeroes. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + do { + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already set it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } else { + if (--r < 0) + break; /* reached target zero coefficient */ + } + k++; + } while (k <= Se); + if (s) { + int pos = jpeg_natural_order[k]; + /* Output newly nonzero coefficient */ + (*block)[pos] = (JCOEF) s; + /* Remember its position in case we have to suspend */ + newnz_pos[num_newnz++] = pos; + } + } + } + + if (EOBRUN > 0) { + /* Scan any remaining coefficient positions after the end-of-band + * (the last newly nonzero coefficient, if any). Append a correction + * bit to each already-nonzero coefficient. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + for (; k <= Se; k++) { + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } + } + /* Count one block completed in EOB run */ + EOBRUN--; + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; + +undoit: + /* Re-zero any output coefficients that we made newly nonzero */ + while (num_newnz > 0) + (*block)[newnz_pos[--num_newnz]] = 0; + + return FALSE; +} + + +/* + * Module initialization routine for progressive Huffman entropy decoding. + */ + +GLOBAL(void) +jinit_phuff_decoder (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr entropy; + int *coef_bit_ptr; + int ci, i; + + entropy = (phuff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(phuff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_phuff_decoder; + + /* Mark derived tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + } + + /* Create progression status table */ + cinfo->coef_bits = (int (*)[DCTSIZE2]) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components*DCTSIZE2*SIZEOF(int)); + coef_bit_ptr = & cinfo->coef_bits[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (i = 0; i < DCTSIZE2; i++) + *coef_bit_ptr++ = -1; +} + +#endif /* D_PROGRESSIVE_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdpostct.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdpostct.c new file mode 100644 index 0000000..571563d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdpostct.c @@ -0,0 +1,290 @@ +/* + * jdpostct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub; /* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF(void) post_process_1pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF(void) post_process_prepass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +METHODDEF(void) post_process_2pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->quantize_colors) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if (post->buffer == NULL) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF(void) +post_process_1pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if (max_rows > post->strip_height) + max_rows = post->strip_height; + num_rows = 0; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows); + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF(void) +post_process_prepass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if (post->next_row > old_next_row) { + num_rows = post->next_row - old_next_row; + (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF(void) +post_process_2pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row; /* available in strip */ + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + if (num_rows > max_rows) + num_rows = max_rows; + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if (num_rows > max_rows) + num_rows = max_rows; + + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL(void) +jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_post_ptr post; + + post = (my_post_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_post_controller)); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if (cinfo->quantize_colors) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if (need_full_buffer) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up((long) cinfo->output_height, + (long) post->strip_height), + post->strip_height); +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height); + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdsample.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdsample.c new file mode 100644 index 0000000..80ffefb --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdsample.c @@ -0,0 +1,478 @@ +/* + * jdsample.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD(void, upsample1_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF(void) +start_pass_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF(void) +sep_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + (*upsample->methods[ci]) (cinfo, compptr, + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + upsample->color_buf + ci); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + + (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF(void) +fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF(void) +noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = NULL; /* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF(void) +int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + for (h = h_expand; h > 0; h--) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if (v_expand > 1) { + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + v_expand-1, cinfo->output_width); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF(void) +h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF(void) +h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + 1, cinfo->output_width); + inrow++; + outrow += 2; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + * + * The upsampling algorithm is linear interpolation between pixel centers, + * also known as a "triangle filter". This is a good compromise between + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + * of the way between input pixel centers. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF(void) +h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register int invalue; + register JDIMENSION colctr; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + /* Special case for first column */ + invalue = GETJSAMPLE(*inptr++); + *outptr++ = (JSAMPLE) invalue; + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(*inptr) + 2) >> 2); + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + invalue = GETJSAMPLE(*inptr++) * 3; + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(inptr[-2]) + 1) >> 2); + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(*inptr) + 2) >> 2); + } + + /* Special case for last column */ + invalue = GETJSAMPLE(*inptr); + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(inptr[-1]) + 1) >> 2); + *outptr++ = (JSAMPLE) invalue; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + * Again a triangle filter; see comments for h2v1 case, above. + * + * It is OK for us to reference the adjacent input rows because we demanded + * context from the main buffer controller (see initialization code). + */ + +METHODDEF(void) +h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr0, inptr1, outptr; +#if BITS_IN_JSAMPLE == 8 + register int thiscolsum, lastcolsum, nextcolsum; +#else + register INT32 thiscolsum, lastcolsum, nextcolsum; +#endif + register JDIMENSION colctr; + int inrow, outrow, v; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + for (v = 0; v < 2; v++) { + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + inptr0 = input_data[inrow]; + if (v == 0) /* next nearest is row above */ + inptr1 = input_data[inrow-1]; + else /* next nearest is row below */ + inptr1 = input_data[inrow+1]; + outptr = output_data[outrow++]; + + /* Special case for first column */ + thiscolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + } + + /* Special case for last column */ + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 7) >> 4); + } + inrow++; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL(void) +jinit_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer, do_fancy; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + if (cinfo->CCIR601_sampling) /* this isn't supported */ + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + * so don't ask for it. + */ + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + need_buffer = TRUE; + if (! compptr->component_needed) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if (h_in_group == h_out_group && v_in_group == v_out_group) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if (h_in_group * 2 == h_out_group && + v_in_group == v_out_group) { + /* Special cases for 2h1v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) + upsample->methods[ci] = h2v1_fancy_upsample; + else + upsample->methods[ci] = h2v1_upsample; + } else if (h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group) { + /* Special cases for 2h2v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) { + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; + } else + upsample->methods[ci] = h2v2_upsample; + } else if ((h_out_group % h_in_group) == 0 && + (v_out_group % v_in_group) == 0) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); + upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group); + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + if (need_buffer) { + upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up((long) cinfo->output_width, + (long) cinfo->max_h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdtrans.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdtrans.c new file mode 100644 index 0000000..6c0ab71 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jdtrans.c @@ -0,0 +1,143 @@ +/* + * jdtrans.c + * + * Copyright (C) 1995-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(void) transdecode_master_selection JPP((j_decompress_ptr cinfo)); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * An alternative usage is to simply obtain access to the coefficient arrays + * during a buffered-image-mode decompression operation. This is allowed + * after any jpeg_finish_output() call. The arrays can be accessed until + * jpeg_finish_decompress() is called. (Note that any call to the library + * may reposition the arrays, so don't rely on access_virt_barray() results + * to stay valid across library calls.) + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL(jvirt_barray_ptr *) +jpeg_read_coefficients (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize active modules */ + transdecode_master_selection(cinfo); + cinfo->global_state = DSTATE_RDCOEFS; + } + if (cinfo->global_state == DSTATE_RDCOEFS) { + /* Absorb whole file into the coef buffer */ + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return NULL; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + } + /* At this point we should be in state DSTATE_STOPPING if being used + * standalone, or in state DSTATE_BUFIMAGE if being invoked to get access + * to the coefficients during a full buffered-image-mode decompression. + */ + if ((cinfo->global_state == DSTATE_STOPPING || + cinfo->global_state == DSTATE_BUFIMAGE) && cinfo->buffered_image) { + return cinfo->coef->coef_arrays; + } + /* Oops, improper usage */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return NULL; /* keep compiler happy */ +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL(void) +transdecode_master_selection (j_decompress_ptr cinfo) +{ + /* This is effectively a buffered-image operation. */ + cinfo->buffered_image = TRUE; + + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller(cinfo, TRUE); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + + /* Initialize progress monitoring. */ + if (cinfo->progress != NULL) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if (cinfo->inputctl->has_multiple_scans) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jerror.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jerror.c new file mode 100644 index 0000000..3da7be8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jerror.c @@ -0,0 +1,252 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * If you define USE_WINDOWS_MESSAGEBOX in jconfig.h or in the makefile, + * you get a Windows-specific hack to display error messages in a dialog box. + * It ain't much, but it beats dropping error messages into the bit bucket, + * which is what happens to output to stderr under most Windows C compilers. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +#ifdef USE_WINDOWS_MESSAGEBOX +#include +#endif + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE(code,string) string , + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF(void) +error_exit (j_common_ptr cinfo) +{ + /* Always display the message */ + (*cinfo->err->output_message) (cinfo); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + exit(EXIT_FAILURE); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + * + * On Windows, printing to stderr is generally completely useless, + * so we provide optional code to produce an error-dialog popup. + * Most Windows applications will still prefer to override this routine, + * but if they don't, it'll do something at least marginally useful. + * + * NOTE: to use the library in an environment that doesn't support the + * C stdio library, you may have to delete the call to fprintf() entirely, + * not just not use this routine. + */ + +METHODDEF(void) +output_message (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + +#ifdef USE_WINDOWS_MESSAGEBOX + /* Display it in a message dialog box */ + MessageBox(GetActiveWindow(), buffer, "JPEG Library Error", + MB_OK | MB_ICONERROR); +#else + /* Send it to stderr, adding a newline */ + fprintf(stderr, "%s\n", buffer); +#endif +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF(void) +emit_message (j_common_ptr cinfo, int msg_level) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if (err->num_warnings == 0 || err->trace_level >= 3) + (*err->output_message) (cinfo); + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if (err->trace_level >= msg_level) + (*err->output_message) (cinfo); + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF(void) +format_message (j_common_ptr cinfo, char * buffer) +{ + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if (msg_code > 0 && msg_code <= err->last_jpeg_message) { + msgtext = err->jpeg_message_table[msg_code]; + } else if (err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if (msgtext == NULL) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ((ch = *msgptr++) != '\0') { + if (ch == '%') { + if (*msgptr == 's') isstring = TRUE; + break; + } + } + + /* Format the message into the passed buffer */ + if (isstring) + sprintf(buffer, msgtext, err->msg_parm.s); + else + sprintf(buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7]); +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF(void) +reset_error_mgr (j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL(struct jpeg_error_mgr *) +jpeg_std_error (struct jpeg_error_mgr * err) +{ + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jerror.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jerror.h new file mode 100644 index 0000000..fc2fffe --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jerror.h @@ -0,0 +1,291 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding") +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_DCT_COEF, "DCT coefficient out of range") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +JMESSAGE(JERR_BAD_HUFF_TABLE, "Bogus Huffman table definition") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_LIB_VERSION, + "Wrong JPEG library version: library is %d, caller expects %d") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_STRUCT_SIZE, + "JPEG parameter struct mismatch: library thinks size is %u, caller expects %u") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker: version %d.%02d, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_EXTENSION, + "JFIF extension marker: type 0x%02x, length %u") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Miscellaneous marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_THUMB_JPEG, + "JFIF extension marker: JPEG-compressed thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_PALETTE, + "JFIF extension marker: palette thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_RGB, + "JFIF extension marker: RGB thumbnail image, length %u") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS5(cinfo,lvl,code,p1,p2,p3,p4,p5) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctflt.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctflt.c new file mode 100644 index 0000000..79d7a00 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctflt.c @@ -0,0 +1,168 @@ +/* + * jfdctflt.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_float (FAST_FLOAT * data) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctfst.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctfst.c new file mode 100644 index 0000000..ccb378a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctfst.c @@ -0,0 +1,224 @@ +/* + * jfdctfst.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jfdctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * Again to save a few shifts, the intermediate results between pass 1 and + * pass 2 are not upscaled, but are represented only to integral precision. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#define CONST_BITS 8 + + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_0_382683433 ((INT32) 98) /* FIX(0.382683433) */ +#define FIX_0_541196100 ((INT32) 139) /* FIX(0.541196100) */ +#define FIX_0_707106781 ((INT32) 181) /* FIX(0.707106781) */ +#define FIX_1_306562965 ((INT32) 334) /* FIX(1.306562965) */ +#else +#define FIX_0_382683433 FIX(0.382683433) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_707106781 FIX(0.707106781) +#define FIX_1_306562965 FIX(1.306562965) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_ifast (DCTELEM * data) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z1, z2, z3, z4, z5, z11, z13; + DCTELEM *dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctint.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctint.c new file mode 100644 index 0000000..0a78b64 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jfdctint.c @@ -0,0 +1,283 @@ +/* + * jfdctint.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D DCT step produces outputs which are a factor of sqrt(N) + * larger than the true DCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D DCT, + * because the y0 and y4 outputs need not be divided by sqrt(N). + * In the IJG code, this factor of 8 is removed by the quantization step + * (in jcdctmgr.c), NOT in this module. + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (For 12-bit sample data, the intermediate + * array is INT32 anyway.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_islow (DCTELEM * data) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + DCTELEM *dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = (DCTELEM) ((tmp10 + tmp11) << PASS1_BITS); + dataptr[4] = (DCTELEM) ((tmp10 - tmp11) << PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865), + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065), + CONST_BITS-PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[7] = (DCTELEM) DESCALE(tmp4 + z1 + z3, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp5 + z2 + z4, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp6 + z2 + z3, CONST_BITS-PASS1_BITS); + dataptr[1] = (DCTELEM) DESCALE(tmp7 + z1 + z4, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = (DCTELEM) DESCALE(tmp10 + tmp11, PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp10 - tmp11, PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865), + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065), + CONST_BITS+PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp4 + z1 + z3, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp5 + z2 + z4, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp6 + z2 + z3, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp7 + z1 + z4, + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctflt.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctflt.c new file mode 100644 index 0000000..0188ce3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctflt.c @@ -0,0 +1,242 @@ +/* + * jidctflt.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE(coef,quantval) (((FAST_FLOAT) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = tmp0 + tmp7; + wsptr[DCTSIZE*7] = tmp0 - tmp7; + wsptr[DCTSIZE*1] = tmp1 + tmp6; + wsptr[DCTSIZE*6] = tmp1 - tmp6; + wsptr[DCTSIZE*2] = tmp2 + tmp5; + wsptr[DCTSIZE*5] = tmp2 - tmp5; + wsptr[DCTSIZE*4] = tmp3 + tmp4; + wsptr[DCTSIZE*3] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + tmp10 = wsptr[0] + wsptr[4]; + tmp11 = wsptr[0] - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctfst.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctfst.c new file mode 100644 index 0000000..dba4216 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctfst.c @@ -0,0 +1,368 @@ +/* + * jidctfst.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jidctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * The dequantized coefficients are not integers because the AA&N scaling + * factors have been incorporated. We represent them scaled up by PASS1_BITS, + * so that the first and second IDCT rounds have the same input scaling. + * For 8-bit JSAMPLEs, we choose IFAST_SCALE_BITS = PASS1_BITS so as to + * avoid a descaling shift; this compromises accuracy rather drastically + * for small quantization table entries, but it saves a lot of shifts. + * For 12-bit JSAMPLEs, there's no hope of using 16x16 multiplies anyway, + * so we use a much larger scaling factor to preserve accuracy. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 8 +#define PASS1_BITS 2 +#else +#define CONST_BITS 8 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_1_082392200 ((INT32) 277) /* FIX(1.082392200) */ +#define FIX_1_414213562 ((INT32) 362) /* FIX(1.414213562) */ +#define FIX_1_847759065 ((INT32) 473) /* FIX(1.847759065) */ +#define FIX_2_613125930 ((INT32) 669) /* FIX(2.613125930) */ +#else +#define FIX_1_082392200 FIX(1.082392200) +#define FIX_1_414213562 FIX(1.414213562) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_2_613125930 FIX(2.613125930) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a DCTELEM result. For 8-bit data a 16x16->16 + * multiplication will do. For 12-bit data, the multiplier table is + * declared INT32, so a 32-bit multiply will be used. + */ + +#if BITS_IN_JSAMPLE == 8 +#define DEQUANTIZE(coef,quantval) (((IFAST_MULT_TYPE) (coef)) * (quantval)) +#else +#define DEQUANTIZE(coef,quantval) \ + DESCALE((coef)*(quantval), IFAST_SCALE_BITS-PASS1_BITS) +#endif + + +/* Like DESCALE, but applies to a DCTELEM and produces an int. + * We assume that int right shift is unsigned if INT32 right shift is. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS DCTELEM ishift_temp; +#if BITS_IN_JSAMPLE == 8 +#define DCTELEMBITS 16 /* DCTELEM may be 16 or 32 bits */ +#else +#define DCTELEMBITS 32 /* DCTELEM must be 32 bits */ +#endif +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~((DCTELEM) 0)) << (DCTELEMBITS-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +#ifdef USE_ACCURATE_ROUNDING +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT((x) + (1 << ((n)-1)), n)) +#else +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT(x, n)) +#endif + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z5, z10, z11, z12, z13; + JCOEFPTR inptr; + IFAST_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS /* for DESCALE */ + ISHIFT_TEMPS /* for IDESCALE */ + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (IFAST_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = (int) DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = (int) (tmp0 + tmp7); + wsptr[DCTSIZE*7] = (int) (tmp0 - tmp7); + wsptr[DCTSIZE*1] = (int) (tmp1 + tmp6); + wsptr[DCTSIZE*6] = (int) (tmp1 - tmp6); + wsptr[DCTSIZE*2] = (int) (tmp2 + tmp5); + wsptr[DCTSIZE*5] = (int) (tmp2 - tmp5); + wsptr[DCTSIZE*4] = (int) (tmp3 + tmp4); + wsptr[DCTSIZE*3] = (int) (tmp3 - tmp4); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[IDESCALE(wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((DCTELEM) wsptr[0] + (DCTELEM) wsptr[4]); + tmp11 = ((DCTELEM) wsptr[0] - (DCTELEM) wsptr[4]); + + tmp13 = ((DCTELEM) wsptr[2] + (DCTELEM) wsptr[6]); + tmp12 = MULTIPLY((DCTELEM) wsptr[2] - (DCTELEM) wsptr[6], FIX_1_414213562) + - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = (DCTELEM) wsptr[5] + (DCTELEM) wsptr[3]; + z10 = (DCTELEM) wsptr[5] - (DCTELEM) wsptr[3]; + z11 = (DCTELEM) wsptr[1] + (DCTELEM) wsptr[7]; + z12 = (DCTELEM) wsptr[1] - (DCTELEM) wsptr[7]; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[IDESCALE(tmp0 + tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[IDESCALE(tmp0 - tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[IDESCALE(tmp1 + tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[IDESCALE(tmp1 - tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[IDESCALE(tmp2 + tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[IDESCALE(tmp2 - tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[IDESCALE(tmp3 + tmp4, PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[IDESCALE(tmp3 - tmp4, PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctint.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctint.c new file mode 100644 index 0000000..a72b320 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctint.c @@ -0,0 +1,389 @@ +/* + * jidctint.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D IDCT step produces outputs which are a factor of sqrt(N) + * larger than the true IDCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D IDCT, + * because the y0 and y4 inputs need not be divided by sqrt(N). + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (To scale up 12-bit sample data further, an + * intermediate INT32 array would be needed.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE(coef,quantval) (((ISLOW_MULT_TYPE) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + z2 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + + tmp0 = (z2 + z3) << CONST_BITS; + tmp1 = (z2 - z3) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*7] = (int) DESCALE(tmp10 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp11 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*6] = (int) DESCALE(tmp11 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*5] = (int) DESCALE(tmp12 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*3] = (int) DESCALE(tmp13 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*4] = (int) DESCALE(tmp13 - tmp0, CONST_BITS-PASS1_BITS); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + tmp0 = ((INT32) wsptr[0] + (INT32) wsptr[4]) << CONST_BITS; + tmp1 = ((INT32) wsptr[0] - (INT32) wsptr[4]) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = (INT32) wsptr[7]; + tmp1 = (INT32) wsptr[5]; + tmp2 = (INT32) wsptr[3]; + tmp3 = (INT32) wsptr[1]; + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE(tmp10 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp11 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE(tmp11 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE(tmp12 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE(tmp12 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE(tmp13 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE(tmp13 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctred.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctred.c new file mode 100644 index 0000000..421f3c7 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jidctred.c @@ -0,0 +1,398 @@ +/* + * jidctred.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains inverse-DCT routines that produce reduced-size output: + * either 4x4, 2x2, or 1x1 pixels from an 8x8 DCT block. + * + * The implementation is based on the Loeffler, Ligtenberg and Moschytz (LL&M) + * algorithm used in jidctint.c. We simply replace each 8-to-8 1-D IDCT step + * with an 8-to-4 step that produces the four averages of two adjacent outputs + * (or an 8-to-2 step producing two averages of four outputs, for 2x2 output). + * These steps were derived by computing the corresponding values at the end + * of the normal LL&M code, then simplifying as much as possible. + * + * 1x1 is trivial: just take the DC coefficient divided by 8. + * + * See jidctint.c for additional comments. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef IDCT_SCALING_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling is the same as in jidctint.c. */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_211164243 ((INT32) 1730) /* FIX(0.211164243) */ +#define FIX_0_509795579 ((INT32) 4176) /* FIX(0.509795579) */ +#define FIX_0_601344887 ((INT32) 4926) /* FIX(0.601344887) */ +#define FIX_0_720959822 ((INT32) 5906) /* FIX(0.720959822) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_850430095 ((INT32) 6967) /* FIX(0.850430095) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_061594337 ((INT32) 8697) /* FIX(1.061594337) */ +#define FIX_1_272758580 ((INT32) 10426) /* FIX(1.272758580) */ +#define FIX_1_451774981 ((INT32) 11893) /* FIX(1.451774981) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_2_172734803 ((INT32) 17799) /* FIX(2.172734803) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_624509785 ((INT32) 29692) /* FIX(3.624509785) */ +#else +#define FIX_0_211164243 FIX(0.211164243) +#define FIX_0_509795579 FIX(0.509795579) +#define FIX_0_601344887 FIX(0.601344887) +#define FIX_0_720959822 FIX(0.720959822) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_850430095 FIX(0.850430095) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_061594337 FIX(1.061594337) +#define FIX_1_272758580 FIX(1.272758580) +#define FIX_1_451774981 FIX(1.451774981) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_2_172734803 FIX(2.172734803) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_624509785 FIX(3.624509785) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE(coef,quantval) (((ISLOW_MULT_TYPE) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 4x4 output block. + */ + +GLOBAL(void) +jpeg_idct_4x4 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp2, tmp10, tmp12; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE*4]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) { + /* Don't bother to process column 4, because second pass won't use it */ + if (ctr == DCTSIZE-4) + continue; + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*5] == 0 && + inptr[DCTSIZE*6] == 0 && inptr[DCTSIZE*7] == 0) { + /* AC terms all zero; we need not examine term 4 for 4x4 output */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= (CONST_BITS+1); + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp2 = MULTIPLY(z2, FIX_1_847759065) + MULTIPLY(z3, - FIX_0_765366865); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + z2 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */ + + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */ + + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp2, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*3] = (int) DESCALE(tmp10 - tmp2, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp12 + tmp0, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 - tmp0, CONST_BITS-PASS1_BITS+1); + } + + /* Pass 2: process 4 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp0 = ((INT32) wsptr[0]) << (CONST_BITS+1); + + tmp2 = MULTIPLY((INT32) wsptr[2], FIX_1_847759065) + + MULTIPLY((INT32) wsptr[6], - FIX_0_765366865); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = (INT32) wsptr[7]; + z2 = (INT32) wsptr[5]; + z3 = (INT32) wsptr[3]; + z4 = (INT32) wsptr[1]; + + tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */ + + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */ + + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp2, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE(tmp10 - tmp2, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp12 + tmp0, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE(tmp12 - tmp0, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 2x2 output block. + */ + +GLOBAL(void) +jpeg_idct_2x2 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp10, z1; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE*2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) { + /* Don't bother to process columns 2,4,6 */ + if (ctr == DCTSIZE-2 || ctr == DCTSIZE-4 || ctr == DCTSIZE-6) + continue; + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*3] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*7] == 0) { + /* AC terms all zero; we need not examine terms 2,4,6 for 2x2 output */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + + continue; + } + + /* Even part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp10 = z1 << (CONST_BITS+2); + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp0 = MULTIPLY(z1, - FIX_0_720959822); /* sqrt(2) * (c7-c5+c3-c1) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp0 += MULTIPLY(z1, FIX_0_850430095); /* sqrt(2) * (-c1+c3+c5+c7) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp0 += MULTIPLY(z1, - FIX_1_272758580); /* sqrt(2) * (-c1+c3-c5-c7) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp0 += MULTIPLY(z1, FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp0, CONST_BITS-PASS1_BITS+2); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp10 - tmp0, CONST_BITS-PASS1_BITS+2); + } + + /* Pass 2: process 2 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 2; ctr++) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[3] == 0 && wsptr[5] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((INT32) wsptr[0]) << (CONST_BITS+2); + + /* Odd part */ + + tmp0 = MULTIPLY((INT32) wsptr[7], - FIX_0_720959822) /* sqrt(2) * (c7-c5+c3-c1) */ + + MULTIPLY((INT32) wsptr[5], FIX_0_850430095) /* sqrt(2) * (-c1+c3+c5+c7) */ + + MULTIPLY((INT32) wsptr[3], - FIX_1_272758580) /* sqrt(2) * (-c1+c3-c5-c7) */ + + MULTIPLY((INT32) wsptr[1], FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3+2) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3+2) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 1x1 output block. + */ + +GLOBAL(void) +jpeg_idct_1x1 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + int dcval; + ISLOW_MULT_TYPE * quantptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + SHIFT_TEMPS + + /* We hardly need an inverse DCT routine for this: just take the + * average pixel value, which is one-eighth of the DC coefficient. + */ + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + dcval = DEQUANTIZE(coef_block[0], quantptr[0]); + dcval = (int) DESCALE((INT32) dcval, 3); + + output_buf[0][output_col] = range_limit[dcval & RANGE_MASK]; +} + +#endif /* IDCT_SCALING_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jinclude.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jinclude.h new file mode 100644 index 0000000..0a4f151 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemmgr.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemmgr.c new file mode 100644 index 0000000..d801b32 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemmgr.c @@ -0,0 +1,1118 @@ +/* + * jmemmgr.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP((const char * name)); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL(void) +print_mem_stats (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf(stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated); + + for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next) { + fprintf(stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used); + } + + for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next) { + fprintf(stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left); + } +} + +#endif /* MEM_STATS */ + + +LOCAL(void) +out_of_memory (j_common_ptr cinfo, int which) +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +{ +#ifdef MEM_STATS + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ +#endif + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +{ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +{ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF(void *) +alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "small" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* See if space is available in any existing pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while (hdr_ptr != NULL) { + if (hdr_ptr->hdr.bytes_left >= sizeofobject) + break; /* found pool with enough space */ + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if (hdr_ptr == NULL) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF(small_pool_hdr); + if (prev_hdr_ptr == NULL) /* first pool in class? */ + slop = first_pool_slop[pool_id]; + else + slop = extra_pool_slop[pool_id]; + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request)) + slop = (size_t) (MAX_ALLOC_CHUNK-min_request); + /* Try to get space, if fail reduce slop and try again */ + for (;;) { + hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); + if (hdr_ptr != NULL) + break; + slop /= 2; + if (slop < MIN_SLOP) /* give up when it gets real small */ + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if (prev_hdr_ptr == NULL) /* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + else + prev_hdr_ptr->hdr.next = hdr_ptr; + } + + /* OK, allocate the object from the current pool */ + data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF(void FAR *) +alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "large" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* Always make a new pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + + SIZEOF(large_pool_hdr)); + if (hdr_ptr == NULL) + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF(JSAMPARRAY) +alloc_sarray (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows) +/* Allocate a 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) samplesperrow * SIZEOF(JSAMPLE)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JSAMPROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JSAMPROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF(JSAMPLE))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF(JBLOCKARRAY) +alloc_barray (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows) +/* Allocate a 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) blocksperrow * SIZEOF(JBLOCK)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JBLOCKROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF(JBLOCK))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF(jvirt_sarray_ptr) +request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_sarray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF(jvirt_barray_ptr) +request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_barray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF(void) +realize_virt_arrays (j_common_ptr cinfo) +/* Allocate the in-memory buffers for any unrealized virtual arrays */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + } + } + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + } + } + + if (space_per_minheight <= 0) + return; /* no unrealized arrays, no work */ + + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if (avail_mem >= maximum_space) + max_minheights = 1000000000L; + else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if (max_minheights <= 0) + max_minheights = 1; + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF(JSAMPLE)); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF(JBLOCK)); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL(void) +do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual sample array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +LOCAL(void) +do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual coefficient-block array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +METHODDEF(JSAMPARRAY) +access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_sarray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +METHODDEF(JBLOCKARRAY) +access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_barray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF(void) +free_pool (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + +#ifdef MEM_STATS + if (cinfo->err->trace_level > 1) + print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */ +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if (pool_id == JPOOL_IMAGE) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->b_s_open) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + } + } + mem->virt_sarray_list = NULL; + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->b_s_open) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while (lhdr_ptr != NULL) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF(large_pool_hdr); + jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while (shdr_ptr != NULL) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF(small_pool_hdr); + jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF(void) +self_destruct (j_common_ptr cinfo) +{ + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + free_pool(cinfo, pool); + } + + /* Release the memory manager control block too. */ + jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL(void) +jinit_memory_mgr (j_common_ptr cinfo) +{ + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0) + ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE); + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ((long) test_mac != MAX_ALLOC_CHUNK || + (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0) + ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK); + + max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); + + if (mem == NULL) { + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Make MAX_ALLOC_CHUNK accessible to other modules */ + mem->pub.max_alloc_chunk = MAX_ALLOC_CHUNK; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF(my_memory_mgr); + + /* Declare ourselves open for business */ + cinfo->mem = & mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ((memenv = getenv("JPEGMEM")) != NULL) { + char ch = 'x'; + + if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { + if (ch == 'm' || ch == 'M') + max_to_use *= 1000L; + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemnobs.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemnobs.c new file mode 100644 index 0000000..eb8c337 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemnobs.c @@ -0,0 +1,109 @@ +/* + * jmemnobs.c + * + * Copyright (C) 1992-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +extern void * malloc JPP((size_t size)); +extern void free JPP((void *ptr)); +#endif + + +/* + * Memory allocation and freeing are controlled by the regular library + * routines malloc() and free(). + */ + +GLOBAL(void *) +jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void *) malloc(sizeofobject); +} + +GLOBAL(void) +jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL(void FAR *) +jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void FAR *) malloc(sizeofobject); +} + +GLOBAL(void) +jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL(long) +jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated) +{ + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL(void) +jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed) +{ + ERREXIT(cinfo, JERR_NO_BACKING_STORE); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL(long) +jpeg_mem_init (j_common_ptr cinfo) +{ + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL(void) +jpeg_mem_term (j_common_ptr cinfo) +{ + /* no work */ +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemsys.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemsys.h new file mode 100644 index 0000000..6c3c6d3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmemsys.h @@ -0,0 +1,198 @@ +/* + * jmemsys.h + * + * Copyright (C) 1992-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR + * and USE_MAC_MEMMGR. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN(void *) jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); +EXTERN(void) jpeg_free_small JPP((j_common_ptr cinfo, void * object, + size_t sizeofobject)); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN(void FAR *) jpeg_get_large JPP((j_common_ptr cinfo, + size_t sizeofobject)); +EXTERN(void) jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + size_t sizeofobject)); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN(long) jpeg_mem_available JPP((j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +#ifdef USE_MAC_MEMMGR /* Mac-specific junk */ +#include +#endif /* USE_MAC_MEMMGR */ + + +typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + backing_store_ptr info)); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else +#ifdef USE_MAC_MEMMGR + /* For the Mac manager (jmemmac.c), we need: */ + short temp_file; /* file reference number to temp file */ + FSSpec tempSpec; /* the FSSpec for the temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +#endif +} backing_store_info; + + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN(void) jpeg_open_backing_store JPP((j_common_ptr cinfo, + backing_store_ptr info, + long total_bytes_needed)); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN(long) jpeg_mem_init JPP((j_common_ptr cinfo)); +EXTERN(void) jpeg_mem_term JPP((j_common_ptr cinfo)); diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmorecfg.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmorecfg.h new file mode 100644 index 0000000..54a7d1c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jmorecfg.h @@ -0,0 +1,363 @@ +/* + * jmorecfg.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE(value) ((int) (value)) +#else +#define GETJSAMPLE(value) ((int) (value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +typedef long INT32; +#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These macros are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions; + * in particular, you'll need to do that to make the library a Windows DLL. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +/* a function called through method pointers: */ +#define METHODDEF(type) static type +/* a function used only in its module: */ +#define LOCAL(type) static type +/* a function referenced thru EXTERNs: */ +#define GLOBAL(type) type +/* a reference to a GLOBAL function: */ +#define EXTERN(type) extern type + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + * Again, you can customize this if you need special linkage keywords. + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifdef NEED_FAR_POINTERS +#define FAR far +#else +#define FAR +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +#ifndef HAVE_BOOLEAN +typedef int boolean; +#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + +/* Capability options common to encoder and decoder: */ + +#define DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#define DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */ +#define BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#define UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jpegint.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jpegint.h new file mode 100644 index 0000000..95b00d4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jpegint.h @@ -0,0 +1,392 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + /* This is here to share code between baseline and progressive decoders; */ + /* other modules probably should not use it */ + boolean insufficient_data; /* set TRUE after emitting warning */ +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jpeglib.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jpeglib.h new file mode 100644 index 0000000..c7d9e21 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jpeglib.h @@ -0,0 +1,1100 @@ +/* + * jpeglib.h + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "jconfig.h" /* widely used configuration options */ +#endif +#include "jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 62 /* Version 6b */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This array gives the coefficient quantizers in natural array order + * (not the zigzag order in which they are stored in a JPEG DQT marker). + * CAUTION: IJG versions prior to v6a kept this array in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is currently used only for decompression. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + +/* The decompressor can save APPn and COM markers in a list of these: */ + +typedef struct jpeg_marker_struct FAR * jpeg_saved_marker_ptr; + +struct jpeg_marker_struct { + jpeg_saved_marker_ptr next; /* next in list, or NULL */ + UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */ + unsigned int original_length; /* # bytes of data in the file */ + unsigned int data_length; /* # bytes of data saved at data[] */ + JOCTET FAR * data; /* the data contained in the marker */ + /* the marker length word is not counted in data_length or original_length */ +}; + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + void * client_data; /* Available for use by application */\ + boolean is_decompressor; /* So common code can tell which is which */\ + int global_state /* For checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + UINT8 JFIF_major_version; /* What to write for the JFIF version number */ + UINT8 JFIF_minor_version; + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; + jpeg_scan_info * script_space; /* workspace for jpeg_simple_progression */ + int script_space_size; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */ + UINT8 JFIF_major_version; /* JFIF version number */ + UINT8 JFIF_minor_version; + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Aside from the specific data retained from APPn markers known to the + * library, the uninterpreted contents of any or all APPn and COM markers + * can be saved in a list for examination by the application. + */ + jpeg_saved_marker_ptr marker_list; /* Head of list of saved markers */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; + + /* Maximum allocation request accepted by alloc_large. */ + long max_alloc_chunk; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_CreateCompress jCreaCompress +#define jpeg_CreateDecompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_m_header jWrtMHeader +#define jpeg_write_m_byte jWrtMByte +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_save_markers jSaveMarkers +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN(struct jpeg_error_mgr *) jpeg_std_error + JPP((struct jpeg_error_mgr * err)); + +/* Initialization of JPEG compression objects. + * jpeg_create_compress() and jpeg_create_decompress() are the exported + * names that applications should call. These expand to calls on + * jpeg_CreateCompress and jpeg_CreateDecompress with additional information + * passed for version mismatch checking. + * NB: you must set up the error-manager BEFORE calling jpeg_create_xxx. + */ +#define jpeg_create_compress(cinfo) \ + jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ + (size_t) sizeof(struct jpeg_compress_struct)) +#define jpeg_create_decompress(cinfo) \ + jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \ + (size_t) sizeof(struct jpeg_decompress_struct)) +EXTERN(void) jpeg_CreateCompress JPP((j_compress_ptr cinfo, + int version, size_t structsize)); +EXTERN(void) jpeg_CreateDecompress JPP((j_decompress_ptr cinfo, + int version, size_t structsize)); +/* Destruction of JPEG compression objects */ +EXTERN(void) jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile)); + +EXTERN(void) jpeg_mem_src JPP((j_decompress_ptr cinfo, + unsigned char * inbuffer, + unsigned long insize)); + +/* Default parameter setup for compression */ +EXTERN(void) jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN(void) jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN(void) jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN(void) jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN(void) jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN(int) jpeg_quality_scaling JPP((int quality)); +EXTERN(void) jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN(void) jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN(JDIMENSION) jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN(void) jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN(void) jpeg_write_marker + JPP((j_compress_ptr cinfo, int marker, + const JOCTET * dataptr, unsigned int datalen)); +/* Same, but piecemeal. */ +EXTERN(void) jpeg_write_m_header + JPP((j_compress_ptr cinfo, int marker, unsigned int datalen)); +EXTERN(void) jpeg_write_m_byte + JPP((j_compress_ptr cinfo, int val)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN(void) jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN(int) jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN(boolean) jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(JDIMENSION) jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN(boolean) jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN(boolean) jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN(boolean) jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN(boolean) jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN(boolean) jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN(void) jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN(int) jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN(void) jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Control saving of COM and APPn markers into marker_list. */ +EXTERN(void) jpeg_save_markers + JPP((j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN(void) jpeg_set_marker_processor + JPP((j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN(void) jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN(void) jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN(void) jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN(void) jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN(void) jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN(boolean) jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "jpegint.h" /* fetch private declarations */ +#include "jerror.h" /* fetch error codes too */ +#endif + +#endif /* JPEGLIB_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jquant1.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jquant1.c new file mode 100644 index 0000000..b2f96aa --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jquant1.c @@ -0,0 +1,856 @@ +/* + * jquant1.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 1-pass color quantization (color mapping) routines. + * These routines provide mapping to a fixed color map using equally spaced + * color values. Optional Floyd-Steinberg or ordered dithering is available. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_1PASS_SUPPORTED + + +/* + * The main purpose of 1-pass quantization is to provide a fast, if not very + * high quality, colormapped output capability. A 2-pass quantizer usually + * gives better visual quality; however, for quantized grayscale output this + * quantizer is perfectly adequate. Dithering is highly recommended with this + * quantizer, though you can turn it off if you really want to. + * + * In 1-pass quantization the colormap must be chosen in advance of seeing the + * image. We use a map consisting of all combinations of Ncolors[i] color + * values for the i'th component. The Ncolors[] values are chosen so that + * their product, the total number of colors, is no more than that requested. + * (In most cases, the product will be somewhat less.) + * + * Since the colormap is orthogonal, the representative value for each color + * component can be determined without considering the other components; + * then these indexes can be combined into a colormap index by a standard + * N-dimensional-array-subscript calculation. Most of the arithmetic involved + * can be precalculated and stored in the lookup table colorindex[]. + * colorindex[i][j] maps pixel value j in component i to the nearest + * representative value (grid plane) for that component; this index is + * multiplied by the array stride for component i, so that the + * index of the colormap entry closest to a given pixel value is just + * sum( colorindex[component-number][pixel-component-value] ) + * Aside from being fast, this scheme allows for variable spacing between + * representative values with no additional lookup cost. + * + * If gamma correction has been applied in color conversion, it might be wise + * to adjust the color grid spacing so that the representative colors are + * equidistant in linear space. At this writing, gamma correction is not + * implemented by jdcolor, so nothing is done here. + */ + + +/* Declarations for ordered dithering. + * + * We use a standard 16x16 ordered dither array. The basic concept of ordered + * dithering is described in many references, for instance Dale Schumacher's + * chapter II.2 of Graphics Gems II (James Arvo, ed. Academic Press, 1991). + * In place of Schumacher's comparisons against a "threshold" value, we add a + * "dither" value to the input pixel and then round the result to the nearest + * output value. The dither value is equivalent to (0.5 - threshold) times + * the distance between output values. For ordered dithering, we assume that + * the output colors are equally spaced; if not, results will probably be + * worse, since the dither may be too much or too little at a given point. + * + * The normal calculation would be to form pixel value + dither, range-limit + * this to 0..MAXJSAMPLE, and then index into the colorindex table as usual. + * We can skip the separate range-limiting step by extending the colorindex + * table in both directions. + */ + +#define ODITHER_SIZE 16 /* dimension of dither matrix */ +/* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */ +#define ODITHER_CELLS (ODITHER_SIZE*ODITHER_SIZE) /* # cells in matrix */ +#define ODITHER_MASK (ODITHER_SIZE-1) /* mask for wrapping around counters */ + +typedef int ODITHER_MATRIX[ODITHER_SIZE][ODITHER_SIZE]; +typedef int (*ODITHER_MATRIX_PTR)[ODITHER_SIZE]; + +static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = { + /* Bayer's order-4 dither array. Generated by the code given in + * Stephen Hawley's article "Ordered Dithering" in Graphics Gems I. + * The values in this array must range from 0 to ODITHER_CELLS-1. + */ + { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, + { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, + { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, + { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, + { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, + { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, + { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, + { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, + { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, + { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, + { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, + { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, + { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, + { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, + { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, + { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } +}; + + +/* Declarations for Floyd-Steinberg dithering. + * + * Errors are accumulated into the array fserrors[], at a resolution of + * 1/16th of a pixel count. The error at a given pixel is propagated + * to its not-yet-processed neighbors using the standard F-S fractions, + * ... (here) 7/16 + * 3/16 5/16 1/16 + * We work left-to-right on even rows, right-to-left on odd rows. + * + * We can get away with a single array (holding one row's worth of errors) + * by using it to store the current row's errors at pixel columns not yet + * processed, but the next row's errors at columns already processed. We + * need only a few extra variables to hold the errors immediately around the + * current column. (If we are lucky, those variables are in registers, but + * even if not, they're probably cheaper to access than array elements are.) + * + * The fserrors[] array is indexed [component#][position]. + * We provide (#columns + 2) entries per component; the extra entry at each + * end saves us from special-casing the first and last pixels. + * + * Note: on a wide image, we might not have enough room in a PC's near data + * segment to hold the error array; so it is allocated with alloc_large. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef INT16 FSERROR; /* 16 bits should be enough */ +typedef int LOCFSERROR; /* use 'int' for calculation temps */ +#else +typedef INT32 FSERROR; /* may need more than 16 bits */ +typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ +#endif + +typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */ + + +/* Private subobject */ + +#define MAX_Q_COMPS 4 /* max components I can handle */ + +typedef struct { + struct jpeg_color_quantizer pub; /* public fields */ + + /* Initially allocated colormap is saved here */ + JSAMPARRAY sv_colormap; /* The color map as a 2-D pixel array */ + int sv_actual; /* number of entries in use */ + + JSAMPARRAY colorindex; /* Precomputed mapping for speed */ + /* colorindex[i][j] = index of color closest to pixel value j in component i, + * premultiplied as described above. Since colormap indexes must fit into + * JSAMPLEs, the entries of this array will too. + */ + boolean is_padded; /* is the colorindex padded for odither? */ + + int Ncolors[MAX_Q_COMPS]; /* # of values alloced to each component */ + + /* Variables for ordered dithering */ + int row_index; /* cur row's vertical index in dither matrix */ + ODITHER_MATRIX_PTR odither[MAX_Q_COMPS]; /* one dither array per component */ + + /* Variables for Floyd-Steinberg dithering */ + FSERRPTR fserrors[MAX_Q_COMPS]; /* accumulated errors */ + boolean on_odd_row; /* flag to remember which row we are on */ +} my_cquantizer; + +typedef my_cquantizer * my_cquantize_ptr; + + +/* + * Policy-making subroutines for create_colormap and create_colorindex. + * These routines determine the colormap to be used. The rest of the module + * only assumes that the colormap is orthogonal. + * + * * select_ncolors decides how to divvy up the available colors + * among the components. + * * output_value defines the set of representative values for a component. + * * largest_input_value defines the mapping from input values to + * representative values for a component. + * Note that the latter two routines may impose different policies for + * different components, though this is not currently done. + */ + + +LOCAL(int) +select_ncolors (j_decompress_ptr cinfo, int Ncolors[]) +/* Determine allocation of desired colors to components, */ +/* and fill in Ncolors[] array to indicate choice. */ +/* Return value is total number of colors (product of Ncolors[] values). */ +{ + int nc = cinfo->out_color_components; /* number of color components */ + int max_colors = cinfo->desired_number_of_colors; + int total_colors, iroot, i, j; + boolean changed; + long temp; + static const int RGB_order[3] = { RGB_GREEN, RGB_RED, RGB_BLUE }; + + /* We can allocate at least the nc'th root of max_colors per component. */ + /* Compute floor(nc'th root of max_colors). */ + iroot = 1; + do { + iroot++; + temp = iroot; /* set temp = iroot ** nc */ + for (i = 1; i < nc; i++) + temp *= iroot; + } while (temp <= (long) max_colors); /* repeat till iroot exceeds root */ + iroot--; /* now iroot = floor(root) */ + + /* Must have at least 2 color values per component */ + if (iroot < 2) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, (int) temp); + + /* Initialize to iroot color values for each component */ + total_colors = 1; + for (i = 0; i < nc; i++) { + Ncolors[i] = iroot; + total_colors *= iroot; + } + /* We may be able to increment the count for one or more components without + * exceeding max_colors, though we know not all can be incremented. + * Sometimes, the first component can be incremented more than once! + * (Example: for 16 colors, we start at 2*2*2, go to 3*2*2, then 4*2*2.) + * In RGB colorspace, try to increment G first, then R, then B. + */ + do { + changed = FALSE; + for (i = 0; i < nc; i++) { + j = (cinfo->out_color_space == JCS_RGB ? RGB_order[i] : i); + /* calculate new total_colors if Ncolors[j] is incremented */ + temp = total_colors / Ncolors[j]; + temp *= Ncolors[j]+1; /* done in long arith to avoid oflo */ + if (temp > (long) max_colors) + break; /* won't fit, done with this pass */ + Ncolors[j]++; /* OK, apply the increment */ + total_colors = (int) temp; + changed = TRUE; + } + } while (changed); + + return total_colors; +} + + +LOCAL(int) +output_value (j_decompress_ptr cinfo, int ci, int j, int maxj) +/* Return j'th output value, where j will range from 0 to maxj */ +/* The output values must fall in 0..MAXJSAMPLE in increasing order */ +{ + /* We always provide values 0 and MAXJSAMPLE for each component; + * any additional values are equally spaced between these limits. + * (Forcing the upper and lower values to the limits ensures that + * dithering can't produce a color outside the selected gamut.) + */ + return (int) (((INT32) j * MAXJSAMPLE + maxj/2) / maxj); +} + + +LOCAL(int) +largest_input_value (j_decompress_ptr cinfo, int ci, int j, int maxj) +/* Return largest input value that should map to j'th output value */ +/* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */ +{ + /* Breakpoints are halfway between values returned by output_value */ + return (int) (((INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj)); +} + + +/* + * Create the colormap. + */ + +LOCAL(void) +create_colormap (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colormap; /* Created colormap */ + int total_colors; /* Number of distinct output colors */ + int i,j,k, nci, blksize, blkdist, ptr, val; + + /* Select number of colors for each component */ + total_colors = select_ncolors(cinfo, cquantize->Ncolors); + + /* Report selected color counts */ + if (cinfo->out_color_components == 3) + TRACEMS4(cinfo, 1, JTRC_QUANT_3_NCOLORS, + total_colors, cquantize->Ncolors[0], + cquantize->Ncolors[1], cquantize->Ncolors[2]); + else + TRACEMS1(cinfo, 1, JTRC_QUANT_NCOLORS, total_colors); + + /* Allocate and fill in the colormap. */ + /* The colors are ordered in the map in standard row-major order, */ + /* i.e. rightmost (highest-indexed) color changes most rapidly. */ + + colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) total_colors, (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + /* blkdist is distance between groups of identical entries for a component */ + blkdist = total_colors; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colormap entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blkdist / nci; + for (j = 0; j < nci; j++) { + /* Compute j'th output value (out of nci) for component */ + val = output_value(cinfo, i, j, nci-1); + /* Fill in all colormap entries that have this value of this component */ + for (ptr = j * blksize; ptr < total_colors; ptr += blkdist) { + /* fill in blksize entries beginning at ptr */ + for (k = 0; k < blksize; k++) + colormap[i][ptr+k] = (JSAMPLE) val; + } + } + blkdist = blksize; /* blksize of this color is blkdist of next */ + } + + /* Save the colormap in private storage, + * where it will survive color quantization mode changes. + */ + cquantize->sv_colormap = colormap; + cquantize->sv_actual = total_colors; +} + + +/* + * Create the color index table. + */ + +LOCAL(void) +create_colorindex (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPROW indexptr; + int i,j,k, nci, blksize, val, pad; + + /* For ordered dither, we pad the color index tables by MAXJSAMPLE in + * each direction (input index values can be -MAXJSAMPLE .. 2*MAXJSAMPLE). + * This is not necessary in the other dithering modes. However, we + * flag whether it was done in case user changes dithering mode. + */ + if (cinfo->dither_mode == JDITHER_ORDERED) { + pad = MAXJSAMPLE*2; + cquantize->is_padded = TRUE; + } else { + pad = 0; + cquantize->is_padded = FALSE; + } + + cquantize->colorindex = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (MAXJSAMPLE+1 + pad), + (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + blksize = cquantize->sv_actual; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colorindex entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blksize / nci; + + /* adjust colorindex pointers to provide padding at negative indexes. */ + if (pad) + cquantize->colorindex[i] += MAXJSAMPLE; + + /* in loop, val = index of current output value, */ + /* and k = largest j that maps to current val */ + indexptr = cquantize->colorindex[i]; + val = 0; + k = largest_input_value(cinfo, i, 0, nci-1); + for (j = 0; j <= MAXJSAMPLE; j++) { + while (j > k) /* advance val if past boundary */ + k = largest_input_value(cinfo, i, ++val, nci-1); + /* premultiply so that no multiplication needed in main processing */ + indexptr[j] = (JSAMPLE) (val * blksize); + } + /* Pad at both ends if necessary */ + if (pad) + for (j = 1; j <= MAXJSAMPLE; j++) { + indexptr[-j] = indexptr[0]; + indexptr[MAXJSAMPLE+j] = indexptr[MAXJSAMPLE]; + } + } +} + + +/* + * Create an ordered-dither array for a component having ncolors + * distinct output values. + */ + +LOCAL(ODITHER_MATRIX_PTR) +make_odither_array (j_decompress_ptr cinfo, int ncolors) +{ + ODITHER_MATRIX_PTR odither; + int j,k; + INT32 num,den; + + odither = (ODITHER_MATRIX_PTR) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(ODITHER_MATRIX)); + /* The inter-value distance for this color is MAXJSAMPLE/(ncolors-1). + * Hence the dither value for the matrix cell with fill order f + * (f=0..N-1) should be (N-1-2*f)/(2*N) * MAXJSAMPLE/(ncolors-1). + * On 16-bit-int machine, be careful to avoid overflow. + */ + den = 2 * ODITHER_CELLS * ((INT32) (ncolors - 1)); + for (j = 0; j < ODITHER_SIZE; j++) { + for (k = 0; k < ODITHER_SIZE; k++) { + num = ((INT32) (ODITHER_CELLS-1 - 2*((int)base_dither_matrix[j][k]))) + * MAXJSAMPLE; + /* Ensure round towards zero despite C's lack of consistency + * about rounding negative values in integer division... + */ + odither[j][k] = (int) (num<0 ? -((-num)/den) : num/den); + } + } + return odither; +} + + +/* + * Create the ordered-dither tables. + * Components having the same number of representative colors may + * share a dither table. + */ + +LOCAL(void) +create_odither_tables (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + ODITHER_MATRIX_PTR odither; + int i, j, nci; + + for (i = 0; i < cinfo->out_color_components; i++) { + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + odither = NULL; /* search for matching prior component */ + for (j = 0; j < i; j++) { + if (nci == cquantize->Ncolors[j]) { + odither = cquantize->odither[j]; + break; + } + } + if (odither == NULL) /* need a new table? */ + odither = make_odither_array(cinfo, nci); + cquantize->odither[i] = odither; + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF(void) +color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colorindex = cquantize->colorindex; + register int pixcode, ci; + register JSAMPROW ptrin, ptrout; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + register int nc = cinfo->out_color_components; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = 0; + for (ci = 0; ci < nc; ci++) { + pixcode += GETJSAMPLE(colorindex[ci][GETJSAMPLE(*ptrin++)]); + } + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF(void) +color_quantize3 (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW ptrin, ptrout; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*ptrin++)]); + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF(void) +quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + int * dither; /* points to active row of dither matrix */ + int row_index, col_index; /* current indexes into dither matrix */ + int nc = cinfo->out_color_components; + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + row_index = cquantize->row_index; + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + colorindex_ci = cquantize->colorindex[ci]; + dither = cquantize->odither[ci][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + /* Form pixel value + dither, range-limit to 0..MAXJSAMPLE, + * select output value, accumulate into output code for this pixel. + * Range-limiting need not be done explicitly, as we have extended + * the colorindex table to produce the right answers for out-of-range + * inputs. The maximum dither is +- MAXJSAMPLE; this sets the + * required amount of padding. + */ + *output_ptr += colorindex_ci[GETJSAMPLE(*input_ptr)+dither[col_index]]; + input_ptr += nc; + output_ptr++; + col_index = (col_index + 1) & ODITHER_MASK; + } + } + /* Advance row index for next row */ + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF(void) +quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int * dither0; /* points to active row of dither matrix */ + int * dither1; + int * dither2; + int row_index, col_index; /* current indexes into dither matrix */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + row_index = cquantize->row_index; + input_ptr = input_buf[row]; + output_ptr = output_buf[row]; + dither0 = cquantize->odither[0][row_index]; + dither1 = cquantize->odither[1][row_index]; + dither2 = cquantize->odither[2][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*input_ptr++) + + dither0[col_index]]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*input_ptr++) + + dither1[col_index]]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*input_ptr++) + + dither2[col_index]]); + *output_ptr++ = (JSAMPLE) pixcode; + col_index = (col_index + 1) & ODITHER_MASK; + } + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF(void) +quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with Floyd-Steinberg dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register LOCFSERROR cur; /* current error or pixel value */ + LOCFSERROR belowerr; /* error for pixel below cur */ + LOCFSERROR bpreverr; /* error for below/prev col */ + LOCFSERROR bnexterr; /* error for below/next col */ + LOCFSERROR delta; + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + JSAMPROW colormap_ci; + int pixcode; + int nc = cinfo->out_color_components; + int dir; /* 1 for left-to-right, -1 for right-to-left */ + int dirnc; /* dir * nc */ + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + input_ptr += (width-1) * nc; /* so point to rightmost pixel */ + output_ptr += width-1; + dir = -1; + dirnc = -nc; + errorptr = cquantize->fserrors[ci] + (width+1); /* => entry after last column */ + } else { + /* work left to right in this row */ + dir = 1; + dirnc = nc; + errorptr = cquantize->fserrors[ci]; /* => entry before first column */ + } + colorindex_ci = cquantize->colorindex[ci]; + colormap_ci = cquantize->sv_colormap[ci]; + /* Preset error values: no error propagated to first pixel from left */ + cur = 0; + /* and no error propagated to row below yet */ + belowerr = bpreverr = 0; + + for (col = width; col > 0; col--) { + /* cur holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur = RIGHT_SHIFT(cur + errorptr[dir] + 8, 4); + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE; this sets the required size + * of the range_limit array. + */ + cur += GETJSAMPLE(*input_ptr); + cur = GETJSAMPLE(range_limit[cur]); + /* Select output value, accumulate into output code for this pixel */ + pixcode = GETJSAMPLE(colorindex_ci[cur]); + *output_ptr += (JSAMPLE) pixcode; + /* Compute actual representation error at this pixel */ + /* Note: we can do this even though we don't have the final */ + /* pixel code, because the colormap is orthogonal. */ + cur -= GETJSAMPLE(colormap_ci[pixcode]); + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + bnexterr = cur; + delta = cur * 2; + cur += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr + cur); + cur += delta; /* form error * 5 */ + bpreverr = belowerr + cur; + belowerr = bnexterr; + cur += delta; /* form error * 7 */ + /* At this point cur contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + input_ptr += dirnc; /* advance input ptr to next column */ + output_ptr += dir; /* advance output ptr to next column */ + errorptr += dir; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error value into the + * final fserrors[] entry. Note we need not unload belowerr because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr; /* unload prev err into array */ + } + cquantize->on_odd_row = (cquantize->on_odd_row ? FALSE : TRUE); + } +} + + +/* + * Allocate workspace for Floyd-Steinberg errors. + */ + +LOCAL(void) +alloc_fs_workspace (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) { + cquantize->fserrors[i] = (FSERRPTR) + (*cinfo->mem->alloc_large)((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + } +} + + +/* + * Initialize for one-pass color quantization. + */ + +METHODDEF(void) +start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + /* Install my colormap. */ + cinfo->colormap = cquantize->sv_colormap; + cinfo->actual_number_of_colors = cquantize->sv_actual; + + /* Initialize for desired dithering mode. */ + switch (cinfo->dither_mode) { + case JDITHER_NONE: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = color_quantize3; + else + cquantize->pub.color_quantize = color_quantize; + break; + case JDITHER_ORDERED: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = quantize3_ord_dither; + else + cquantize->pub.color_quantize = quantize_ord_dither; + cquantize->row_index = 0; /* initialize state for ordered dither */ + /* If user changed to ordered dither from another mode, + * we must recreate the color index table with padding. + * This will cost extra space, but probably isn't very likely. + */ + if (! cquantize->is_padded) + create_colorindex(cinfo); + /* Create ordered-dither tables if we didn't already. */ + if (cquantize->odither[0] == NULL) + create_odither_tables(cinfo); + break; + case JDITHER_FS: + cquantize->pub.color_quantize = quantize_fs_dither; + cquantize->on_odd_row = FALSE; /* initialize state for F-S dither */ + /* Allocate Floyd-Steinberg workspace if didn't already. */ + if (cquantize->fserrors[0] == NULL) + alloc_fs_workspace(cinfo); + /* Initialize the propagated errors to zero. */ + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) + jzero_far((void FAR *) cquantize->fserrors[i], arraysize); + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } +} + + +/* + * Finish up at the end of the pass. + */ + +METHODDEF(void) +finish_pass_1_quant (j_decompress_ptr cinfo) +{ + /* no work in 1-pass case */ +} + + +/* + * Switch to a new external colormap between output passes. + * Shouldn't get to this module! + */ + +METHODDEF(void) +new_color_map_1_quant (j_decompress_ptr cinfo) +{ + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + + +/* + * Module initialization routine for 1-pass color quantization. + */ + +GLOBAL(void) +jinit_1pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize; + + cquantize = (my_cquantize_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_1_quant; + cquantize->pub.finish_pass = finish_pass_1_quant; + cquantize->pub.new_color_map = new_color_map_1_quant; + cquantize->fserrors[0] = NULL; /* Flag FS workspace not allocated */ + cquantize->odither[0] = NULL; /* Also flag odither arrays not allocated */ + + /* Make sure my internal arrays won't overflow */ + if (cinfo->out_color_components > MAX_Q_COMPS) + ERREXIT1(cinfo, JERR_QUANT_COMPONENTS, MAX_Q_COMPS); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (cinfo->desired_number_of_colors > (MAXJSAMPLE+1)) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXJSAMPLE+1); + + /* Create the colormap and color index table. */ + create_colormap(cinfo); + create_colorindex(cinfo); + + /* Allocate Floyd-Steinberg workspace now if requested. + * We do this now since it is FAR storage and may affect the memory + * manager's space calculations. If the user changes to FS dither + * mode in a later pass, we will allocate the space then, and will + * possibly overrun the max_memory_to_use setting. + */ + if (cinfo->dither_mode == JDITHER_FS) + alloc_fs_workspace(cinfo); +} + +#endif /* QUANT_1PASS_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jquant2.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jquant2.c new file mode 100644 index 0000000..af601e3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jquant2.c @@ -0,0 +1,1310 @@ +/* + * jquant2.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 2-pass color quantization (color mapping) routines. + * These routines provide selection of a custom color map for an image, + * followed by mapping of the image to that color map, with optional + * Floyd-Steinberg dithering. + * It is also possible to use just the second pass to map to an arbitrary + * externally-given color map. + * + * Note: ordered dithering is not supported, since there isn't any fast + * way to compute intercolor distances; it's unclear that ordered dither's + * fundamental assumptions even hold with an irregularly spaced color map. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_2PASS_SUPPORTED + + +/* + * This module implements the well-known Heckbert paradigm for color + * quantization. Most of the ideas used here can be traced back to + * Heckbert's seminal paper + * Heckbert, Paul. "Color Image Quantization for Frame Buffer Display", + * Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304. + * + * In the first pass over the image, we accumulate a histogram showing the + * usage count of each possible color. To keep the histogram to a reasonable + * size, we reduce the precision of the input; typical practice is to retain + * 5 or 6 bits per color, so that 8 or 4 different input values are counted + * in the same histogram cell. + * + * Next, the color-selection step begins with a box representing the whole + * color space, and repeatedly splits the "largest" remaining box until we + * have as many boxes as desired colors. Then the mean color in each + * remaining box becomes one of the possible output colors. + * + * The second pass over the image maps each input pixel to the closest output + * color (optionally after applying a Floyd-Steinberg dithering correction). + * This mapping is logically trivial, but making it go fast enough requires + * considerable care. + * + * Heckbert-style quantizers vary a good deal in their policies for choosing + * the "largest" box and deciding where to cut it. The particular policies + * used here have proved out well in experimental comparisons, but better ones + * may yet be found. + * + * In earlier versions of the IJG code, this module quantized in YCbCr color + * space, processing the raw upsampled data without a color conversion step. + * This allowed the color conversion math to be done only once per colormap + * entry, not once per pixel. However, that optimization precluded other + * useful optimizations (such as merging color conversion with upsampling) + * and it also interfered with desired capabilities such as quantizing to an + * externally-supplied colormap. We have therefore abandoned that approach. + * The present code works in the post-conversion color space, typically RGB. + * + * To improve the visual quality of the results, we actually work in scaled + * RGB space, giving G distances more weight than R, and R in turn more than + * B. To do everything in integer math, we must use integer scale factors. + * The 2/3/1 scale factors used here correspond loosely to the relative + * weights of the colors in the NTSC grayscale equation. + * If you want to use this code to quantize a non-RGB color space, you'll + * probably need to change these scale factors. + */ + +#define R_SCALE 2 /* scale R distances by this much */ +#define G_SCALE 3 /* scale G distances by this much */ +#define B_SCALE 1 /* and B by this much */ + +/* Relabel R/G/B as components 0/1/2, respecting the RGB ordering defined + * in jmorecfg.h. As the code stands, it will do the right thing for R,G,B + * and B,G,R orders. If you define some other weird order in jmorecfg.h, + * you'll get compile errors until you extend this logic. In that case + * you'll probably want to tweak the histogram sizes too. + */ + +#if RGB_RED == 0 +#define C0_SCALE R_SCALE +#endif +#if RGB_BLUE == 0 +#define C0_SCALE B_SCALE +#endif +#if RGB_GREEN == 1 +#define C1_SCALE G_SCALE +#endif +#if RGB_RED == 2 +#define C2_SCALE R_SCALE +#endif +#if RGB_BLUE == 2 +#define C2_SCALE B_SCALE +#endif + + +/* + * First we have the histogram data structure and routines for creating it. + * + * The number of bits of precision can be adjusted by changing these symbols. + * We recommend keeping 6 bits for G and 5 each for R and B. + * If you have plenty of memory and cycles, 6 bits all around gives marginally + * better results; if you are short of memory, 5 bits all around will save + * some space but degrade the results. + * To maintain a fully accurate histogram, we'd need to allocate a "long" + * (preferably unsigned long) for each cell. In practice this is overkill; + * we can get by with 16 bits per cell. Few of the cell counts will overflow, + * and clamping those that do overflow to the maximum value will give close- + * enough results. This reduces the recommended histogram size from 256Kb + * to 128Kb, which is a useful savings on PC-class machines. + * (In the second pass the histogram space is re-used for pixel mapping data; + * in that capacity, each cell must be able to store zero to the number of + * desired colors. 16 bits/cell is plenty for that too.) + * Since the JPEG code is intended to run in small memory model on 80x86 + * machines, we can't just allocate the histogram in one chunk. Instead + * of a true 3-D array, we use a row of pointers to 2-D arrays. Each + * pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and + * each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries. Note that + * on 80x86 machines, the pointer row is in near memory but the actual + * arrays are in far memory (same arrangement as we use for image arrays). + */ + +#define MAXNUMCOLORS (MAXJSAMPLE+1) /* maximum size of colormap */ + +/* These will do the right thing for either R,G,B or B,G,R color order, + * but you may not like the results for other color orders. + */ +#define HIST_C0_BITS 5 /* bits of precision in R/B histogram */ +#define HIST_C1_BITS 6 /* bits of precision in G histogram */ +#define HIST_C2_BITS 5 /* bits of precision in B/R histogram */ + +/* Number of elements along histogram axes. */ +#define HIST_C0_ELEMS (1<cquantize; + register JSAMPROW ptr; + register histptr histp; + register hist3d histogram = cquantize->histogram; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptr = input_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the histogram */ + histp = & histogram[GETJSAMPLE(ptr[0]) >> C0_SHIFT] + [GETJSAMPLE(ptr[1]) >> C1_SHIFT] + [GETJSAMPLE(ptr[2]) >> C2_SHIFT]; + /* increment, check for overflow and undo increment if so. */ + if (++(*histp) <= 0) + (*histp)--; + ptr += 3; + } + } +} + + +/* + * Next we have the really interesting routines: selection of a colormap + * given the completed histogram. + * These routines work with a list of "boxes", each representing a rectangular + * subset of the input color space (to histogram precision). + */ + +typedef struct { + /* The bounds of the box (inclusive); expressed as histogram indexes */ + int c0min, c0max; + int c1min, c1max; + int c2min, c2max; + /* The volume (actually 2-norm) of the box */ + INT32 volume; + /* The number of nonzero histogram cells within this box */ + long colorcount; +} box; + +typedef box * boxptr; + + +LOCAL(boxptr) +find_biggest_color_pop (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest color population */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register long maxc = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->colorcount > maxc && boxp->volume > 0) { + which = boxp; + maxc = boxp->colorcount; + } + } + return which; +} + + +LOCAL(boxptr) +find_biggest_volume (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest (scaled) volume */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register INT32 maxv = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->volume > maxv) { + which = boxp; + maxv = boxp->volume; + } + } + return which; +} + + +LOCAL(void) +update_box (j_decompress_ptr cinfo, boxptr boxp) +/* Shrink the min/max bounds of a box to enclose only nonzero elements, */ +/* and recompute its volume and population */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + INT32 dist0,dist1,dist2; + long ccount; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + if (c0max > c0min) + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0min = c0min = c0; + goto have_c0min; + } + } + have_c0min: + if (c0max > c0min) + for (c0 = c0max; c0 >= c0min; c0--) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0max = c0max = c0; + goto have_c0max; + } + } + have_c0max: + if (c1max > c1min) + for (c1 = c1min; c1 <= c1max; c1++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1min = c1min = c1; + goto have_c1min; + } + } + have_c1min: + if (c1max > c1min) + for (c1 = c1max; c1 >= c1min; c1--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1max = c1max = c1; + goto have_c1max; + } + } + have_c1max: + if (c2max > c2min) + for (c2 = c2min; c2 <= c2max; c2++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2min = c2min = c2; + goto have_c2min; + } + } + have_c2min: + if (c2max > c2min) + for (c2 = c2max; c2 >= c2min; c2--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2max = c2max = c2; + goto have_c2max; + } + } + have_c2max: + + /* Update box volume. + * We use 2-norm rather than real volume here; this biases the method + * against making long narrow boxes, and it has the side benefit that + * a box is splittable iff norm > 0. + * Since the differences are expressed in histogram-cell units, + * we have to shift back to JSAMPLE units to get consistent distances; + * after which, we scale according to the selected distance scale factors. + */ + dist0 = ((c0max - c0min) << C0_SHIFT) * C0_SCALE; + dist1 = ((c1max - c1min) << C1_SHIFT) * C1_SCALE; + dist2 = ((c2max - c2min) << C2_SHIFT) * C2_SCALE; + boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2; + + /* Now scan remaining volume of box and compute population */ + ccount = 0; + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++, histp++) + if (*histp != 0) { + ccount++; + } + } + boxp->colorcount = ccount; +} + + +LOCAL(int) +median_cut (j_decompress_ptr cinfo, boxptr boxlist, int numboxes, + int desired_colors) +/* Repeatedly select and split the largest box until we have enough boxes */ +{ + int n,lb; + int c0,c1,c2,cmax; + register boxptr b1,b2; + + while (numboxes < desired_colors) { + /* Select box to split. + * Current algorithm: by population for first half, then by volume. + */ + if (numboxes*2 <= desired_colors) { + b1 = find_biggest_color_pop(boxlist, numboxes); + } else { + b1 = find_biggest_volume(boxlist, numboxes); + } + if (b1 == NULL) /* no splittable boxes left! */ + break; + b2 = &boxlist[numboxes]; /* where new box will go */ + /* Copy the color bounds to the new box. */ + b2->c0max = b1->c0max; b2->c1max = b1->c1max; b2->c2max = b1->c2max; + b2->c0min = b1->c0min; b2->c1min = b1->c1min; b2->c2min = b1->c2min; + /* Choose which axis to split the box on. + * Current algorithm: longest scaled axis. + * See notes in update_box about scaling distances. + */ + c0 = ((b1->c0max - b1->c0min) << C0_SHIFT) * C0_SCALE; + c1 = ((b1->c1max - b1->c1min) << C1_SHIFT) * C1_SCALE; + c2 = ((b1->c2max - b1->c2min) << C2_SHIFT) * C2_SCALE; + /* We want to break any ties in favor of green, then red, blue last. + * This code does the right thing for R,G,B or B,G,R color orders only. + */ +#if RGB_RED == 0 + cmax = c1; n = 1; + if (c0 > cmax) { cmax = c0; n = 0; } + if (c2 > cmax) { n = 2; } +#else + cmax = c1; n = 1; + if (c2 > cmax) { cmax = c2; n = 2; } + if (c0 > cmax) { n = 0; } +#endif + /* Choose split point along selected axis, and update box bounds. + * Current algorithm: split at halfway point. + * (Since the box has been shrunk to minimum volume, + * any split will produce two nonempty subboxes.) + * Note that lb value is max for lower box, so must be < old max. + */ + switch (n) { + case 0: + lb = (b1->c0max + b1->c0min) / 2; + b1->c0max = lb; + b2->c0min = lb+1; + break; + case 1: + lb = (b1->c1max + b1->c1min) / 2; + b1->c1max = lb; + b2->c1min = lb+1; + break; + case 2: + lb = (b1->c2max + b1->c2min) / 2; + b1->c2max = lb; + b2->c2min = lb+1; + break; + } + /* Update stats for boxes */ + update_box(cinfo, b1); + update_box(cinfo, b2); + numboxes++; + } + return numboxes; +} + + +LOCAL(void) +compute_color (j_decompress_ptr cinfo, boxptr boxp, int icolor) +/* Compute representative color for a box, put it in colormap[icolor] */ +{ + /* Current algorithm: mean weighted by pixels (not colors) */ + /* Note it is important to get the rounding correct! */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + long count; + long total = 0; + long c0total = 0; + long c1total = 0; + long c2total = 0; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) { + if ((count = *histp++) != 0) { + total += count; + c0total += ((c0 << C0_SHIFT) + ((1<>1)) * count; + c1total += ((c1 << C1_SHIFT) + ((1<>1)) * count; + c2total += ((c2 << C2_SHIFT) + ((1<>1)) * count; + } + } + } + + cinfo->colormap[0][icolor] = (JSAMPLE) ((c0total + (total>>1)) / total); + cinfo->colormap[1][icolor] = (JSAMPLE) ((c1total + (total>>1)) / total); + cinfo->colormap[2][icolor] = (JSAMPLE) ((c2total + (total>>1)) / total); +} + + +LOCAL(void) +select_colors (j_decompress_ptr cinfo, int desired_colors) +/* Master routine for color selection */ +{ + boxptr boxlist; + int numboxes; + int i; + + /* Allocate workspace for box list */ + boxlist = (boxptr) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, desired_colors * SIZEOF(box)); + /* Initialize one box containing whole space */ + numboxes = 1; + boxlist[0].c0min = 0; + boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT; + boxlist[0].c1min = 0; + boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT; + boxlist[0].c2min = 0; + boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT; + /* Shrink it to actually-used volume and set its statistics */ + update_box(cinfo, & boxlist[0]); + /* Perform median-cut to produce final box list */ + numboxes = median_cut(cinfo, boxlist, numboxes, desired_colors); + /* Compute the representative color for each box, fill colormap */ + for (i = 0; i < numboxes; i++) + compute_color(cinfo, & boxlist[i], i); + cinfo->actual_number_of_colors = numboxes; + TRACEMS1(cinfo, 1, JTRC_QUANT_SELECTED, numboxes); +} + + +/* + * These routines are concerned with the time-critical task of mapping input + * colors to the nearest color in the selected colormap. + * + * We re-use the histogram space as an "inverse color map", essentially a + * cache for the results of nearest-color searches. All colors within a + * histogram cell will be mapped to the same colormap entry, namely the one + * closest to the cell's center. This may not be quite the closest entry to + * the actual input color, but it's almost as good. A zero in the cache + * indicates we haven't found the nearest color for that cell yet; the array + * is cleared to zeroes before starting the mapping pass. When we find the + * nearest color for a cell, its colormap index plus one is recorded in the + * cache for future use. The pass2 scanning routines call fill_inverse_cmap + * when they need to use an unfilled entry in the cache. + * + * Our method of efficiently finding nearest colors is based on the "locally + * sorted search" idea described by Heckbert and on the incremental distance + * calculation described by Spencer W. Thomas in chapter III.1 of Graphics + * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that + * the distances from a given colormap entry to each cell of the histogram can + * be computed quickly using an incremental method: the differences between + * distances to adjacent cells themselves differ by a constant. This allows a + * fairly fast implementation of the "brute force" approach of computing the + * distance from every colormap entry to every histogram cell. Unfortunately, + * it needs a work array to hold the best-distance-so-far for each histogram + * cell (because the inner loop has to be over cells, not colormap entries). + * The work array elements have to be INT32s, so the work array would need + * 256Kb at our recommended precision. This is not feasible in DOS machines. + * + * To get around these problems, we apply Thomas' method to compute the + * nearest colors for only the cells within a small subbox of the histogram. + * The work array need be only as big as the subbox, so the memory usage + * problem is solved. Furthermore, we need not fill subboxes that are never + * referenced in pass2; many images use only part of the color gamut, so a + * fair amount of work is saved. An additional advantage of this + * approach is that we can apply Heckbert's locality criterion to quickly + * eliminate colormap entries that are far away from the subbox; typically + * three-fourths of the colormap entries are rejected by Heckbert's criterion, + * and we need not compute their distances to individual cells in the subbox. + * The speed of this approach is heavily influenced by the subbox size: too + * small means too much overhead, too big loses because Heckbert's criterion + * can't eliminate as many colormap entries. Empirically the best subbox + * size seems to be about 1/512th of the histogram (1/8th in each direction). + * + * Thomas' article also describes a refined method which is asymptotically + * faster than the brute-force method, but it is also far more complex and + * cannot efficiently be applied to small subboxes. It is therefore not + * useful for programs intended to be portable to DOS machines. On machines + * with plenty of memory, filling the whole histogram in one shot with Thomas' + * refined method might be faster than the present code --- but then again, + * it might not be any faster, and it's certainly more complicated. + */ + + +/* log2(histogram cells in update box) for each axis; this can be adjusted */ +#define BOX_C0_LOG (HIST_C0_BITS-3) +#define BOX_C1_LOG (HIST_C1_BITS-3) +#define BOX_C2_LOG (HIST_C2_BITS-3) + +#define BOX_C0_ELEMS (1<actual_number_of_colors; + int maxc0, maxc1, maxc2; + int centerc0, centerc1, centerc2; + int i, x, ncolors; + INT32 minmaxdist, min_dist, max_dist, tdist; + INT32 mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */ + + /* Compute true coordinates of update box's upper corner and center. + * Actually we compute the coordinates of the center of the upper-corner + * histogram cell, which are the upper bounds of the volume we care about. + * Note that since ">>" rounds down, the "center" values may be closer to + * min than to max; hence comparisons to them must be "<=", not "<". + */ + maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT)); + centerc0 = (minc0 + maxc0) >> 1; + maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT)); + centerc1 = (minc1 + maxc1) >> 1; + maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT)); + centerc2 = (minc2 + maxc2) >> 1; + + /* For each color in colormap, find: + * 1. its minimum squared-distance to any point in the update box + * (zero if color is within update box); + * 2. its maximum squared-distance to any point in the update box. + * Both of these can be found by considering only the corners of the box. + * We save the minimum distance for each color in mindist[]; + * only the smallest maximum distance is of interest. + */ + minmaxdist = 0x7FFFFFFFL; + + for (i = 0; i < numcolors; i++) { + /* We compute the squared-c0-distance term, then add in the other two. */ + x = GETJSAMPLE(cinfo->colormap[0][i]); + if (x < minc0) { + tdist = (x - minc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else if (x > maxc0) { + tdist = (x - maxc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + min_dist = 0; + if (x <= centerc0) { + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[1][i]); + if (x < minc1) { + tdist = (x - minc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc1) { + tdist = (x - maxc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc1) { + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[2][i]); + if (x < minc2) { + tdist = (x - minc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc2) { + tdist = (x - maxc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc2) { + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } + } + + mindist[i] = min_dist; /* save away the results */ + if (max_dist < minmaxdist) + minmaxdist = max_dist; + } + + /* Now we know that no cell in the update box is more than minmaxdist + * away from some colormap entry. Therefore, only colors that are + * within minmaxdist of some part of the box need be considered. + */ + ncolors = 0; + for (i = 0; i < numcolors; i++) { + if (mindist[i] <= minmaxdist) + colorlist[ncolors++] = (JSAMPLE) i; + } + return ncolors; +} + + +LOCAL(void) +find_best_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2, + int numcolors, JSAMPLE colorlist[], JSAMPLE bestcolor[]) +/* Find the closest colormap entry for each cell in the update box, + * given the list of candidate colors prepared by find_nearby_colors. + * Return the indexes of the closest entries in the bestcolor[] array. + * This routine uses Thomas' incremental distance calculation method to + * find the distance from a colormap entry to successive cells in the box. + */ +{ + int ic0, ic1, ic2; + int i, icolor; + register INT32 * bptr; /* pointer into bestdist[] array */ + JSAMPLE * cptr; /* pointer into bestcolor[] array */ + INT32 dist0, dist1; /* initial distance values */ + register INT32 dist2; /* current distance in inner loop */ + INT32 xx0, xx1; /* distance increments */ + register INT32 xx2; + INT32 inc0, inc1, inc2; /* initial values for increments */ + /* This array holds the distance to the nearest-so-far color for each cell */ + INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Initialize best-distance for each cell of the update box */ + bptr = bestdist; + for (i = BOX_C0_ELEMS*BOX_C1_ELEMS*BOX_C2_ELEMS-1; i >= 0; i--) + *bptr++ = 0x7FFFFFFFL; + + /* For each color selected by find_nearby_colors, + * compute its distance to the center of each cell in the box. + * If that's less than best-so-far, update best distance and color number. + */ + + /* Nominal steps between cell centers ("x" in Thomas article) */ +#define STEP_C0 ((1 << C0_SHIFT) * C0_SCALE) +#define STEP_C1 ((1 << C1_SHIFT) * C1_SCALE) +#define STEP_C2 ((1 << C2_SHIFT) * C2_SCALE) + + for (i = 0; i < numcolors; i++) { + icolor = GETJSAMPLE(colorlist[i]); + /* Compute (square of) distance from minc0/c1/c2 to this color */ + inc0 = (minc0 - GETJSAMPLE(cinfo->colormap[0][icolor])) * C0_SCALE; + dist0 = inc0*inc0; + inc1 = (minc1 - GETJSAMPLE(cinfo->colormap[1][icolor])) * C1_SCALE; + dist0 += inc1*inc1; + inc2 = (minc2 - GETJSAMPLE(cinfo->colormap[2][icolor])) * C2_SCALE; + dist0 += inc2*inc2; + /* Form the initial difference increments */ + inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0; + inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1; + inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2; + /* Now loop over all cells in box, updating distance per Thomas method */ + bptr = bestdist; + cptr = bestcolor; + xx0 = inc0; + for (ic0 = BOX_C0_ELEMS-1; ic0 >= 0; ic0--) { + dist1 = dist0; + xx1 = inc1; + for (ic1 = BOX_C1_ELEMS-1; ic1 >= 0; ic1--) { + dist2 = dist1; + xx2 = inc2; + for (ic2 = BOX_C2_ELEMS-1; ic2 >= 0; ic2--) { + if (dist2 < *bptr) { + *bptr = dist2; + *cptr = (JSAMPLE) icolor; + } + dist2 += xx2; + xx2 += 2 * STEP_C2 * STEP_C2; + bptr++; + cptr++; + } + dist1 += xx1; + xx1 += 2 * STEP_C1 * STEP_C1; + } + dist0 += xx0; + xx0 += 2 * STEP_C0 * STEP_C0; + } + } +} + + +LOCAL(void) +fill_inverse_cmap (j_decompress_ptr cinfo, int c0, int c1, int c2) +/* Fill the inverse-colormap entries in the update box that contains */ +/* histogram cell c0/c1/c2. (Only that one cell MUST be filled, but */ +/* we can fill as many others as we wish.) */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int minc0, minc1, minc2; /* lower left corner of update box */ + int ic0, ic1, ic2; + register JSAMPLE * cptr; /* pointer into bestcolor[] array */ + register histptr cachep; /* pointer into main cache array */ + /* This array lists the candidate colormap indexes. */ + JSAMPLE colorlist[MAXNUMCOLORS]; + int numcolors; /* number of candidate colors */ + /* This array holds the actually closest colormap index for each cell. */ + JSAMPLE bestcolor[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Convert cell coordinates to update box ID */ + c0 >>= BOX_C0_LOG; + c1 >>= BOX_C1_LOG; + c2 >>= BOX_C2_LOG; + + /* Compute true coordinates of update box's origin corner. + * Actually we compute the coordinates of the center of the corner + * histogram cell, which are the lower bounds of the volume we care about. + */ + minc0 = (c0 << BOX_C0_SHIFT) + ((1 << C0_SHIFT) >> 1); + minc1 = (c1 << BOX_C1_SHIFT) + ((1 << C1_SHIFT) >> 1); + minc2 = (c2 << BOX_C2_SHIFT) + ((1 << C2_SHIFT) >> 1); + + /* Determine which colormap entries are close enough to be candidates + * for the nearest entry to some cell in the update box. + */ + numcolors = find_nearby_colors(cinfo, minc0, minc1, minc2, colorlist); + + /* Determine the actually nearest colors. */ + find_best_colors(cinfo, minc0, minc1, minc2, numcolors, colorlist, + bestcolor); + + /* Save the best color numbers (plus 1) in the main cache array */ + c0 <<= BOX_C0_LOG; /* convert ID back to base cell indexes */ + c1 <<= BOX_C1_LOG; + c2 <<= BOX_C2_LOG; + cptr = bestcolor; + for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) { + for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) { + cachep = & histogram[c0+ic0][c1+ic1][c2]; + for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) { + *cachep++ = (histcell) (GETJSAMPLE(*cptr++) + 1); + } + } + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF(void) +pass2_no_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register JSAMPROW inptr, outptr; + register histptr cachep; + register int c0, c1, c2; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the cache */ + c0 = GETJSAMPLE(*inptr++) >> C0_SHIFT; + c1 = GETJSAMPLE(*inptr++) >> C1_SHIFT; + c2 = GETJSAMPLE(*inptr++) >> C2_SHIFT; + cachep = & histogram[c0][c1][c2]; + /* If we have not seen this color before, find nearest colormap entry */ + /* and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, c0,c1,c2); + /* Now emit the colormap index for this cell */ + *outptr++ = (JSAMPLE) (*cachep - 1); + } + } +} + + +METHODDEF(void) +pass2_fs_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs Floyd-Steinberg dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register LOCFSERROR cur0, cur1, cur2; /* current error or pixel value */ + LOCFSERROR belowerr0, belowerr1, belowerr2; /* error for pixel below cur */ + LOCFSERROR bpreverr0, bpreverr1, bpreverr2; /* error for below/prev col */ + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + JSAMPROW inptr; /* => current input pixel */ + JSAMPROW outptr; /* => current output pixel */ + histptr cachep; + int dir; /* +1 or -1 depending on direction */ + int dir3; /* 3*dir, for advancing inptr & errorptr */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + int *error_limit = cquantize->error_limiter; + JSAMPROW colormap0 = cinfo->colormap[0]; + JSAMPROW colormap1 = cinfo->colormap[1]; + JSAMPROW colormap2 = cinfo->colormap[2]; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + inptr += (width-1) * 3; /* so point to rightmost pixel */ + outptr += width-1; + dir = -1; + dir3 = -3; + errorptr = cquantize->fserrors + (width+1)*3; /* => entry after last column */ + cquantize->on_odd_row = FALSE; /* flip for next time */ + } else { + /* work left to right in this row */ + dir = 1; + dir3 = 3; + errorptr = cquantize->fserrors; /* => entry before first real column */ + cquantize->on_odd_row = TRUE; /* flip for next time */ + } + /* Preset error values: no error propagated to first pixel from left */ + cur0 = cur1 = cur2 = 0; + /* and no error propagated to row below yet */ + belowerr0 = belowerr1 = belowerr2 = 0; + bpreverr0 = bpreverr1 = bpreverr2 = 0; + + for (col = width; col > 0; col--) { + /* curN holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur0 = RIGHT_SHIFT(cur0 + errorptr[dir3+0] + 8, 4); + cur1 = RIGHT_SHIFT(cur1 + errorptr[dir3+1] + 8, 4); + cur2 = RIGHT_SHIFT(cur2 + errorptr[dir3+2] + 8, 4); + /* Limit the error using transfer function set by init_error_limit. + * See comments with init_error_limit for rationale. + */ + cur0 = error_limit[cur0]; + cur1 = error_limit[cur1]; + cur2 = error_limit[cur2]; + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE (or less with error limiting); + * this sets the required size of the range_limit array. + */ + cur0 += GETJSAMPLE(inptr[0]); + cur1 += GETJSAMPLE(inptr[1]); + cur2 += GETJSAMPLE(inptr[2]); + cur0 = GETJSAMPLE(range_limit[cur0]); + cur1 = GETJSAMPLE(range_limit[cur1]); + cur2 = GETJSAMPLE(range_limit[cur2]); + /* Index into the cache with adjusted pixel value */ + cachep = & histogram[cur0>>C0_SHIFT][cur1>>C1_SHIFT][cur2>>C2_SHIFT]; + /* If we have not seen this color before, find nearest colormap */ + /* entry and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, cur0>>C0_SHIFT,cur1>>C1_SHIFT,cur2>>C2_SHIFT); + /* Now emit the colormap index for this cell */ + { register int pixcode = *cachep - 1; + *outptr = (JSAMPLE) pixcode; + /* Compute representation error for this pixel */ + cur0 -= GETJSAMPLE(colormap0[pixcode]); + cur1 -= GETJSAMPLE(colormap1[pixcode]); + cur2 -= GETJSAMPLE(colormap2[pixcode]); + } + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + { register LOCFSERROR bnexterr, delta; + + bnexterr = cur0; /* Process component 0 */ + delta = cur0 * 2; + cur0 += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr0 + cur0); + cur0 += delta; /* form error * 5 */ + bpreverr0 = belowerr0 + cur0; + belowerr0 = bnexterr; + cur0 += delta; /* form error * 7 */ + bnexterr = cur1; /* Process component 1 */ + delta = cur1 * 2; + cur1 += delta; /* form error * 3 */ + errorptr[1] = (FSERROR) (bpreverr1 + cur1); + cur1 += delta; /* form error * 5 */ + bpreverr1 = belowerr1 + cur1; + belowerr1 = bnexterr; + cur1 += delta; /* form error * 7 */ + bnexterr = cur2; /* Process component 2 */ + delta = cur2 * 2; + cur2 += delta; /* form error * 3 */ + errorptr[2] = (FSERROR) (bpreverr2 + cur2); + cur2 += delta; /* form error * 5 */ + bpreverr2 = belowerr2 + cur2; + belowerr2 = bnexterr; + cur2 += delta; /* form error * 7 */ + } + /* At this point curN contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + inptr += dir3; /* Advance pixel pointers to next column */ + outptr += dir; + errorptr += dir3; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error values into the + * final fserrors[] entry. Note we need not unload belowerrN because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr0; /* unload prev errs into array */ + errorptr[1] = (FSERROR) bpreverr1; + errorptr[2] = (FSERROR) bpreverr2; + } +} + + +/* + * Initialize the error-limiting transfer function (lookup table). + * The raw F-S error computation can potentially compute error values of up to + * +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be + * much less, otherwise obviously wrong pixels will be created. (Typical + * effects include weird fringes at color-area boundaries, isolated bright + * pixels in a dark area, etc.) The standard advice for avoiding this problem + * is to ensure that the "corners" of the color cube are allocated as output + * colors; then repeated errors in the same direction cannot cause cascading + * error buildup. However, that only prevents the error from getting + * completely out of hand; Aaron Giles reports that error limiting improves + * the results even with corner colors allocated. + * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty + * well, but the smoother transfer function used below is even better. Thanks + * to Aaron Giles for this idea. + */ + +LOCAL(void) +init_error_limit (j_decompress_ptr cinfo) +/* Allocate and fill in the error_limiter table */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + int * table; + int in, out; + + table = (int *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, (MAXJSAMPLE*2+1) * SIZEOF(int)); + table += MAXJSAMPLE; /* so can index -MAXJSAMPLE .. +MAXJSAMPLE */ + cquantize->error_limiter = table; + +#define STEPSIZE ((MAXJSAMPLE+1)/16) + /* Map errors 1:1 up to +- MAXJSAMPLE/16 */ + out = 0; + for (in = 0; in < STEPSIZE; in++, out++) { + table[in] = out; table[-in] = -out; + } + /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */ + for (; in < STEPSIZE*3; in++, out += (in&1) ? 0 : 1) { + table[in] = out; table[-in] = -out; + } + /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */ + for (; in <= MAXJSAMPLE; in++) { + table[in] = out; table[-in] = -out; + } +#undef STEPSIZE +} + + +/* + * Finish up at the end of each pass. + */ + +METHODDEF(void) +finish_pass1 (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Select the representative colors and fill in cinfo->colormap */ + cinfo->colormap = cquantize->sv_colormap; + select_colors(cinfo, cquantize->desired); + /* Force next pass to zero the color index table */ + cquantize->needs_zeroed = TRUE; +} + + +METHODDEF(void) +finish_pass2 (j_decompress_ptr cinfo) +{ + /* no work */ +} + + +/* + * Initialize for each processing pass. + */ + +METHODDEF(void) +start_pass_2_quant (j_decompress_ptr cinfo, boolean is_pre_scan) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int i; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + if (is_pre_scan) { + /* Set up method pointers */ + cquantize->pub.color_quantize = prescan_quantize; + cquantize->pub.finish_pass = finish_pass1; + cquantize->needs_zeroed = TRUE; /* Always zero histogram */ + } else { + /* Set up method pointers */ + if (cinfo->dither_mode == JDITHER_FS) + cquantize->pub.color_quantize = pass2_fs_dither; + else + cquantize->pub.color_quantize = pass2_no_dither; + cquantize->pub.finish_pass = finish_pass2; + + /* Make sure color count is acceptable */ + i = cinfo->actual_number_of_colors; + if (i < 1) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 1); + if (i > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + + if (cinfo->dither_mode == JDITHER_FS) { + size_t arraysize = (size_t) ((cinfo->output_width + 2) * + (3 * SIZEOF(FSERROR))); + /* Allocate Floyd-Steinberg workspace if we didn't already. */ + if (cquantize->fserrors == NULL) + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + /* Initialize the propagated errors to zero. */ + jzero_far((void FAR *) cquantize->fserrors, arraysize); + /* Make the error-limit table if we didn't already. */ + if (cquantize->error_limiter == NULL) + init_error_limit(cinfo); + cquantize->on_odd_row = FALSE; + } + + } + /* Zero the histogram or inverse color map, if necessary */ + if (cquantize->needs_zeroed) { + for (i = 0; i < HIST_C0_ELEMS; i++) { + jzero_far((void FAR *) histogram[i], + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = FALSE; + } +} + + +/* + * Switch to a new external colormap between output passes. + */ + +METHODDEF(void) +new_color_map_2_quant (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Reset the inverse color map */ + cquantize->needs_zeroed = TRUE; +} + + +/* + * Module initialization routine for 2-pass color quantization. + */ + +GLOBAL(void) +jinit_2pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize; + int i; + + cquantize = (my_cquantize_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_2_quant; + cquantize->pub.new_color_map = new_color_map_2_quant; + cquantize->fserrors = NULL; /* flag optional arrays not allocated */ + cquantize->error_limiter = NULL; + + /* Make sure jdmaster didn't give me a case I can't handle */ + if (cinfo->out_color_components != 3) + ERREXIT(cinfo, JERR_NOTIMPL); + + /* Allocate the histogram/inverse colormap storage */ + cquantize->histogram = (hist3d) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, HIST_C0_ELEMS * SIZEOF(hist2d)); + for (i = 0; i < HIST_C0_ELEMS; i++) { + cquantize->histogram[i] = (hist2d) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = TRUE; /* histogram is garbage now */ + + /* Allocate storage for the completed colormap, if required. + * We do this now since it is FAR storage and may affect + * the memory manager's space calculations. + */ + if (cinfo->enable_2pass_quant) { + /* Make sure color count is acceptable */ + int desired = cinfo->desired_number_of_colors; + /* Lower bound on # of colors ... somewhat arbitrary as long as > 0 */ + if (desired < 8) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 8); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (desired > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + cquantize->sv_colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo,JPOOL_IMAGE, (JDIMENSION) desired, (JDIMENSION) 3); + cquantize->desired = desired; + } else + cquantize->sv_colormap = NULL; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + /* Allocate Floyd-Steinberg workspace if necessary. + * This isn't really needed until pass 2, but again it is FAR storage. + * Although we will cope with a later change in dither_mode, + * we do not promise to honor max_memory_to_use if dither_mode changes. + */ + if (cinfo->dither_mode == JDITHER_FS) { + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) ((cinfo->output_width + 2) * (3 * SIZEOF(FSERROR)))); + /* Might as well create the error-limiting table too. */ + init_error_limit(cinfo); + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jutils.c b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jutils.c new file mode 100644 index 0000000..d18a955 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jutils.c @@ -0,0 +1,179 @@ +/* + * jutils.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +#if 0 /* This table is not actually needed in v6a */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; + +#endif + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL(long) +jdiv_round_up (long a, long b) +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ +{ + return (a + b - 1L) / b; +} + + +GLOBAL(long) +jround_up (long a, long b) +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ +{ + a += b - 1L; + return a - (a % b); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) +#define FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#endif +#endif + + +GLOBAL(void) +jcopy_sample_rows (JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols) +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ +{ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for (row = num_rows; row > 0; row--) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY(outptr, inptr, count); +#else + for (count = num_cols; count > 0; count--) + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL(void) +jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks) +/* Copy a row of coefficient blocks from one place to another. */ +{ +#ifdef FMEMCOPY + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL(void) +jzero_far (void FAR * target, size_t bytestozero) +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +{ +#ifdef FMEMZERO + FMEMZERO(target, bytestozero); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for (count = bytestozero; count > 0; count--) { + *ptr++ = 0; + } +#endif +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jversion.h b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jversion.h new file mode 100644 index 0000000..6472c58 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/jpeglib/jversion.h @@ -0,0 +1,14 @@ +/* + * jversion.h + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "6b 27-Mar-1998" + +#define JCOPYRIGHT "Copyright (C) 1998, Thomas G. Lane" diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lameencdec.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lameencdec.cpp new file mode 100644 index 0000000..dec614b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lameencdec.cpp @@ -0,0 +1,467 @@ +/* + WDL - lameencdec.cpp + Copyright (C) 2005 and later 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. + + + +*/ + + + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#include +#include +#include + +#include "wdlcstring.h" +#include "lameencdec.h" +#include "win32_utf8.h" +#include "ptrlist.h" + +#ifdef __APPLE__ + #include +#endif +#ifndef _WIN32 + #include +#endif + +// note: calling code should include WDL/metadata.h from somewhere to implement these functions +struct ID3RawTag; +int PackID3Chunk(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata, + bool want_embed_otherschemes, int *ixml_lenwritten, int ixml_padtolen, + WDL_PtrList *rawtags=NULL); +int PackApeChunk(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata); +int PackXMPChunk(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata); +bool ParseUserDefMetadata(const char *id, const char *val, + const char **k, const char **v, int *klen, int *vlen); +bool HasScheme(const char *scheme, WDL_StringKeyedArray *metadata); + + +typedef enum MPEG_mode_e { + STEREO = 0, + JOINT_STEREO, + DUAL_CHANNEL, /* LAME doesn't supports this! */ + MONO, + NOT_SET, + MAX_INDICATOR /* Don't use this! It's used for sanity checks. */ +} MPEG_mode; + +typedef void *lame_t; + +static struct { + int (*close)(lame_t); + lame_t (*init)(); + int (*set_in_samplerate)(lame_t, int); + int (*set_num_channels)(lame_t,int); + int (*set_out_samplerate)(lame_t,int); + int (*set_quality)(lame_t,int); + int (*set_mode)(lame_t,MPEG_mode); + int (*set_brate)(lame_t, int); + int (*init_params)(lame_t); + int (*get_framesize)(lame_t); + + int (*encode_buffer_float)(lame_t, + const float buffer_l [], /* PCM data for left channel */ + const float buffer_r [], /* PCM data for right channel */ + const int nsamples, /* number of samples per channel */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + const int mp3buf_size ); + int (*encode_flush)(lame_t,unsigned char* mp3buf, int size); + +// these are optional + int (*set_VBR)(lame_t, int); + int (*set_VBR_q)(lame_t, int); + int (*set_VBR_mean_bitrate_kbps)(lame_t, int); + int (*set_VBR_min_bitrate_kbps)(lame_t, int); + int (*set_VBR_max_bitrate_kbps)(lame_t, int); + size_t (*get_lametag_frame)(lame_t, unsigned char *, size_t); + const char *(*get_lame_version)(); + int (*set_findReplayGain)(lame_t, int); + +} lame; + +#if 1 +#define LAME_DEBUG_LOADING(x) +#else +#define LAME_DEBUG_LOADING(x) OutputDebugString(x) +#endif + +static char s_last_dll_file[128]; + +static bool tryLoadDLL2(const char *name) +{ +#ifdef _WIN32 + HINSTANCE dll = LoadLibrary(name); +#else + void *dll=dlopen(name,RTLD_NOW|RTLD_LOCAL); +#endif + + if (!dll) return false; + + LAME_DEBUG_LOADING("trying to load"); + LAME_DEBUG_LOADING(name); + int errcnt = 0; + #ifdef _WIN32 + #define GETITEM(x) if (NULL == (*(void **)&lame.x = GetProcAddress((HINSTANCE)dll,"lame_" #x))) errcnt++; + #define GETITEM_NP(x) if (NULL == (*(void **)&lame.x = GetProcAddress((HINSTANCE)dll, #x))) errcnt++; + #else + #define GETITEM(x) if (NULL == (*(void **)&lame.x = dlsym(dll,"lame_" #x))) errcnt++; + #define GETITEM_NP(x) if (NULL == (*(void **)&lame.x = dlsym(dll,#x))) errcnt++; + #endif + GETITEM(close) + GETITEM(init) + GETITEM(set_in_samplerate) + GETITEM(set_num_channels) + GETITEM(set_out_samplerate) + GETITEM(set_quality) + GETITEM(set_mode) + GETITEM(set_brate) + GETITEM(init_params) + GETITEM(get_framesize) + GETITEM(encode_buffer_float) + GETITEM(encode_flush) + + int errcnt2 = errcnt; + GETITEM(set_VBR) + GETITEM(set_VBR_q) + GETITEM(set_VBR_mean_bitrate_kbps) + GETITEM(set_VBR_min_bitrate_kbps) + GETITEM(set_VBR_max_bitrate_kbps) + GETITEM(get_lametag_frame) + GETITEM(set_findReplayGain) + GETITEM_NP(get_lame_version) + + #undef GETITEM + #undef GETITEM_NP + if (errcnt2) + { + memset(&lame, 0, sizeof(lame)); + +#ifdef _WIN32 + FreeLibrary(dll); +#else + dlclose(dll); +#endif + return false; + } + + LAME_DEBUG_LOADING("loaded normal mode"); + + lstrcpyn_safe(s_last_dll_file, name, sizeof(s_last_dll_file)); +#ifdef _WIN32 +// if (!strstr(name,"\\")) + GetModuleFileName(dll,s_last_dll_file, (DWORD)sizeof(s_last_dll_file)); +#else +// if (!strstr(name,"/")) + { + Dl_info inf={0,}; + dladdr((void*)lame.init,&inf); + if (inf.dli_fname) + lstrcpyn_safe(s_last_dll_file,inf.dli_fname,sizeof(s_last_dll_file)); + } +#endif + + return true; +} + + + +void LameEncoder::InitDLL(const char *extrapath, bool forceRetry) +{ + static int a; + if (a<0) return; + + if (forceRetry) a=0; + else if (a > 30) return; // give up + + a++; + + char me[1024]; +#ifdef _WIN32 + const char *dll = "libmp3lame.dll"; +#elif defined(__APPLE__) + const char *dll = "libmp3lame.dylib"; +#else + const char *dll = "libmp3lame.so.0"; +#endif + + if (extrapath) + { + snprintf(me,sizeof(me),"%s%c%s",extrapath,WDL_DIRCHAR,dll); + if (tryLoadDLL2(me)) { a = -1; return; } + } + + if (tryLoadDLL2(dll)) a=-1; + +} + +const char *LameEncoder::GetLibName() { return s_last_dll_file; } + +const char *LameEncoder::GetInfo() +{ + static char buf[128]; + if (!CheckDLL()) return NULL; + const char *p = lame.get_lame_version ? lame.get_lame_version() : NULL; + if (p && *p) + { + snprintf(buf, sizeof(buf), "LAME %s", p); + return buf; + } + return "LAME ?.??"; +} + +int LameEncoder::CheckDLL() // returns 1 for lame API, 2 for Blade, 0 for none +{ + InitDLL(); + if (!lame.close|| + !lame.init|| + !lame.set_in_samplerate|| + !lame.set_num_channels|| + !lame.set_out_samplerate|| + !lame.set_quality|| + !lame.set_mode|| + !lame.set_brate|| + !lame.init_params|| + !lame.get_framesize|| + !lame.encode_buffer_float|| + !lame.encode_flush) + { + return 0; + } + + return 1; + +} + +LameEncoder::LameEncoder(int srate, int nch, int bitrate, int stereomode, int quality, + int vbrmethod, int vbrquality, int vbrmax, int abr, int rpgain, + WDL_StringKeyedArray *metadata) +{ + m_lamestate=0; + if (!CheckDLL()) + { + errorstat=1; + return; + } + + errorstat=0; + m_nch=nch; + m_encoder_nch = stereomode == 3 ? 1 : m_nch; + m_id3_len=m_ixml_len=0; + + m_lamestate=lame.init(); + if (!m_lamestate) + { + errorstat=1; + return; + } + + lame.set_in_samplerate(m_lamestate, srate); + lame.set_num_channels(m_lamestate,m_encoder_nch); + int outrate=srate; + + int maxbr = (vbrmethod != -1 ? vbrmax : bitrate); + if (outrate>=32000 && maxbr <= 32*m_encoder_nch) outrate/=2; + + lame.set_out_samplerate(m_lamestate,outrate); + lame.set_quality(m_lamestate,(quality>9 ||quality<0) ? 0 : quality); + if (m_encoder_nch == 1 || stereomode >= 0) + lame.set_mode(m_lamestate,(MPEG_mode) (m_encoder_nch==1?3 :stereomode )); + lame.set_brate(m_lamestate,bitrate); + + //int vbrmethod (-1 no vbr), int vbrquality (nVBRQuality), int vbrmax, int abr + if (vbrmethod != -1 && lame.set_VBR) + { + int vm=4; // mtrh + if (vbrmethod == 4) vm = 3; //ABR + lame.set_VBR(m_lamestate,vm); + + if (lame.set_VBR_q) lame.set_VBR_q(m_lamestate,vbrquality); + + if (vbrmethod == 4&&lame.set_VBR_mean_bitrate_kbps) + { + lame.set_VBR_mean_bitrate_kbps(m_lamestate,abr); + } + if (lame.set_VBR_max_bitrate_kbps) + { + lame.set_VBR_max_bitrate_kbps(m_lamestate,vbrmax); + } + if (lame.set_VBR_min_bitrate_kbps) + { + lame.set_VBR_min_bitrate_kbps(m_lamestate,bitrate); + } + } + if (rpgain>0 && lame.set_findReplayGain) lame.set_findReplayGain(m_lamestate,1); + + if (metadata && metadata->GetSize()) + { + WDL_HeapBuf hb; + if (PackID3Chunk(&hb, metadata, true, &m_ixml_len, 0)) + { + outqueue.Add(hb.Get(), hb.GetSize()); + m_id3_len=hb.GetSize(); + } + PackApeChunk(&m_apetag, metadata); + } + + lame.init_params(m_lamestate); + + in_size_samples=lame.get_framesize(m_lamestate); + + outtmp.Resize(65536); +} + +void LameEncoder::Encode(float *in, int in_spls, int spacing) +{ + if (errorstat) return; + + if (in_spls > 0) + { + if (m_nch > 1 && m_encoder_nch==1) + { + // downmix + int x; + int pos=0; + int adv=2*spacing; + for (x = 0; x < in_spls; x ++) + { + float f=in[pos]+in[pos+1]; + f*=16384.0f; + spltmp[0].Add(&f,sizeof(float)); + pos+=adv; + } + } + else if (m_encoder_nch > 1) // deinterleave + { + int x; + int pos=0; + int adv=2*spacing; + for (x = 0; x < in_spls; x ++) + { + float f=in[pos]; + f*=32768.0f; + spltmp[0].Add(&f,sizeof(float)); + + f=in[pos+1]; + f*=32768.0f; + spltmp[1].Add(&f,sizeof(float)); + + pos+=adv; + } + } + else + { + int x; + int pos=0; + for (x = 0; x < in_spls; x ++) + { + float f=in[pos]; + f*=32768.0f; + spltmp[0].Add(&f,sizeof(float)); + + pos+=spacing; + } + } + } + for (;;) + { + int a = spltmp[0].Available()/sizeof(float); + if (a >= in_size_samples) a = in_size_samples; + else if (a<1 || in_spls>0) break; // not enough samples available, and not flushing + + int dwo=lame.encode_buffer_float(m_lamestate,(float *)spltmp[0].Get(),(float*)spltmp[m_encoder_nch>1].Get(), a,(unsigned char *)outtmp.Get(),outtmp.GetSize()); + outqueue.Add(outtmp.Get(),dwo); + spltmp[0].Advance(a*sizeof(float)); + if (m_encoder_nch > 1) spltmp[1].Advance(a*sizeof(float)); + } + + if (in_spls<1) + { + int a=lame.encode_flush(m_lamestate,(unsigned char *)outtmp.Get(),outtmp.GetSize()); + if (a>0) outqueue.Add(outtmp.Get(),a); + } + + + + spltmp[0].Compact(); + spltmp[1].Compact(); + +} + +#ifdef _WIN32 + +static BOOL HasUTF8(const char *_str) +{ + const unsigned char *str = (const unsigned char *)_str; + if (!str) return FALSE; + while (*str) + { + unsigned char c = *str; + if (c >= 0xC2) // discard overlongs + { + if (c <= 0xDF && str[1] >=0x80 && str[1] <= 0xBF) return TRUE; + else if (c <= 0xEF && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + else if (c <= 0xF4 && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + } + str++; + } + return FALSE; +} +#endif + + +LameEncoder::~LameEncoder() +{ + if (m_lamestate) + { + if (m_vbrfile.Get()[0] && lame.get_lametag_frame) + { + unsigned char buf[16384]; + size_t a=lame.get_lametag_frame(m_lamestate,buf,sizeof(buf)); + if ((a>0 && a<=sizeof(buf)) || m_apetag.GetSize()) + { + FILE *fp = fopenUTF8(m_vbrfile.Get(),"r+b"); + if (fp) + { + if (a > 0 && a <= sizeof(buf)) + { + fseek(fp, m_id3_len, SEEK_SET); + fwrite(buf,1,a,fp); + } + if (m_apetag.GetSize()) + { + fseek(fp, 0, SEEK_END); + fwrite(m_apetag.Get(), 1, m_apetag.GetSize(), fp); + } + fclose(fp); + } + } + } + lame.close(m_lamestate); + m_lamestate=0; + } +} + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lameencdec.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lameencdec.h new file mode 100644 index 0000000..343d02f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lameencdec.h @@ -0,0 +1,87 @@ +/* + WDL - lameencdec.h + Copyright (C) 2005 and later 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. + + + This file provides a simple interface for using lame_enc/libmp3lame MP3 encoding + +*/ + + +#ifndef _LAMEENCDEC_H_ +#define _LAMEENCDEC_H_ + +#include "queue.h" +#include "wdlstring.h" +#include "assocarray.h" + +class LameEncoder +{ + public: + + LameEncoder(int srate, int nch, int bitrate, int stereomode=1, int quality=2, + int vbrmethod=-1, int vbrquality=2, int vbrmax=320, int abr=128, int rpgain=0, + WDL_StringKeyedArray *metadata=NULL); + ~LameEncoder(); + + int Status() { return errorstat; } // 1=no dll, 2=error + + void Encode(float *in, int in_spls, int spacing=1); + + WDL_Queue outqueue; + + void reinit() + { + spltmp[0].Advance(spltmp[0].Available()); + spltmp[0].Compact(); + spltmp[1].Advance(spltmp[1].Available()); + spltmp[1].Compact(); + } + + static const char *GetInfo(); + static const char *GetLibName(); + static int CheckDLL(); // returns >0 if DLL present, 1 for lame, 2 for old bladeenc + static void InitDLL(const char *extrapath=NULL, bool forceRetry=false); // call with extrapath != NULL if you want to try loading from another path + + void SetVBRFilename(const char *fn) + { + m_vbrfile.Set(fn); + } + + int GetNumChannels() { return m_encoder_nch; } + + int GetID3Len() const { return m_id3_len; } + int GetIXMLLen() const { return m_ixml_len; } + + private: + + void SetMetadata(WDL_StringKeyedArray *metadata); + int m_id3_len, m_ixml_len; + WDL_HeapBuf m_apetag; + + void *m_lamestate; + WDL_Queue spltmp[2]; + WDL_HeapBuf outtmp; + WDL_String m_vbrfile; + int errorstat; + int in_size_samples; + int m_nch,m_encoder_nch; +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/CHANGES b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/CHANGES new file mode 100644 index 0000000..5e22343 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/CHANGES @@ -0,0 +1,5424 @@ +#if 0 +CHANGES - changes for libpng + +version 0.1 [March 29, 1995] + initial work-in-progress release + +version 0.2 [April 1, 1995] + added reader into png.h + fixed small problems in stub file + +version 0.3 [April 8, 1995] + added pull reader + split up pngwrite.c to several files + added pnglib.txt + added example.c + cleaned up writer, adding a few new transformations + fixed some bugs in writer + interfaced with zlib 0.5 + added K&R support + added check for 64 KB blocks for 16 bit machines + +version 0.4 [April 26, 1995] + cleaned up code and commented code + simplified time handling into png_time + created png_color_16 and png_color_8 to handle color needs + cleaned up color type defines + fixed various bugs + made various names more consistent + interfaced with zlib 0.71 + cleaned up zTXt reader and writer (using zlib's Reset functions) + split transformations into pngrtran.c and pngwtran.c + +version 0.5 [April 30, 1995] + interfaced with zlib 0.8 + fixed many reading and writing bugs + saved using 3 spaces instead of tabs + +version 0.6 [May 1, 1995] + first beta release + added png_large_malloc() and png_large_free() + added png_size_t + cleaned up some compiler warnings + added png_start_read_image() + +version 0.7 [June 24, 1995] + cleaned up lots of bugs + finished dithering and other stuff + added test program + changed name from pnglib to libpng + +version 0.71 [June 26, 1995] + changed pngtest.png for zlib 0.93 + fixed error in libpng.txt and example.c + +version 0.8 [August 20, 1995] + cleaned up some bugs + added png_set_filler() + split up pngstub.c into pngmem.c, pngio.c, and pngerror.c + added #define's to remove unwanted code + moved png_info_init() to png.c + added old_size into png_realloc() + added functions to manually set filtering and compression info + changed compression parameters based on image type + optimized filter selection code + added version info + changed external functions passing floats to doubles (k&r problems?) + put all the configurable stuff in pngconf.h + enabled png_set_shift to work with paletted images on read + added png_read_update_info() - updates info structure with transformations + +Version 0.81 [August, 1995] + incorporated Tim Wegner's medium model code (thanks, Tim) + +Version 0.82 [September, 1995] + [unspecified changes] + +Version 0.85 [December, 1995] + added more medium model code (almost everything's a far) + added i/o, error, and memory callback functions + fixed some bugs (16-bit, 4-bit interlaced, etc.) + added first run progressive reader (barely tested) + +Version 0.86 [January, 1996] + fixed bugs + improved documentation + +Version 0.87 [January, 1996] + fixed medium model bugs + fixed other bugs introduced in 0.85 and 0.86 + added some minor documentation + +Version 0.88 [January, 1996] + fixed progressive bugs + replaced tabs with spaces + cleaned up documentation + added callbacks for read/write and warning/error functions + +Version 0.89 [June 5, 1996] + Added new initialization API to make libpng work better with shared libs + we now have png_create_read_struct(), png_create_write_struct(), + png_create_info_struct(), png_destroy_read_struct(), and + png_destroy_write_struct() instead of the separate calls to + malloc and png_read_init(), png_info_init(), and png_write_init() + Changed warning/error callback functions to fix bug - this means you + should use the new initialization API if you were using the old + png_set_message_fn() calls, and that the old API no longer exists + so that people are aware that they need to change their code + Changed filter selection API to allow selection of multiple filters + since it didn't work in previous versions of libpng anyways + Optimized filter selection code + Fixed png_set_background() to allow using an arbitrary RGB color for + paletted images + Fixed gamma and background correction for paletted images, so + png_correct_palette is not needed unless you are correcting an + external palette (you will need to #define PNG_CORRECT_PALETTE_SUPPORTED + in pngconf.h) - if nobody uses this, it may disappear in the future. + Fixed bug with Borland 64K memory allocation (Alexander Lehmann) + Fixed bug in interlace handling (Smarasderagd, I think) + Added more error checking for writing and image to reduce invalid files + Separated read and write functions so that they won't both be linked + into a binary when only reading or writing functionality is used + New pngtest image also has interlacing and zTXt + Updated documentation to reflect new API + +Version 0.89c [June 17, 1996] + Bug fixes. + +Version 0.90 [January, 1997] + Made CRC errors/warnings on critical and ancillary chunks configurable + libpng will use the zlib CRC routines by (compile-time) default + Changed DOS small/medium model memory support - needs zlib 1.04 (Tim Wegner) + Added external C++ wrapper statements to png.h (Gilles Dauphin) + Allow PNG file to be read when some or all of file signature has already + been read from the beginning of the stream. ****This affects the size + of info_struct and invalidates all programs that use a shared libpng**** + Fixed png_filler() declarations + Fixed? background color conversions + Fixed order of error function pointers to match documentation + Current chunk name is now available in png_struct to reduce the number + of nearly identical error messages (will simplify multi-lingual + support when available) + Try to get ready for unknown-chunk callback functions: + - previously read critical chunks are flagged, so the chunk handling + routines can determine if the chunk is in the right place + - all chunk handling routines have the same prototypes, so we will + be able to handle all chunks via a callback mechanism + Try to fix Linux "setjmp" buffer size problems + Removed png_large_malloc, png_large_free, and png_realloc functions. + +Version 0.95 [March, 1997] + Fixed bug in pngwutil.c allocating "up_row" twice and "avg_row" never + Fixed bug in PNG file signature compares when start != 0 + Changed parameter type of png_set_filler(...filler...) from png_byte + to png_uint_32 + Added test for MACOS to ensure that both math.h and fp.h are not #included + Added macros for libpng to be compiled as a Windows DLL (Andreas Kupries) + Added "packswap" transformation, which changes the endianness of + packed-pixel bytes (Kevin Bracey) + Added "strip_alpha" transformation, which removes the alpha channel of + input images without using it (not necessarily a good idea) + Added "swap_alpha" transformation, which puts the alpha channel in front + of the color bytes instead of after + Removed all implicit variable tests which assume NULL == 0 (I think) + Changed several variables to "png_size_t" to show 16/32-bit limitations + Added new pCAL chunk read/write support + Added experimental filter selection weighting (Greg Roelofs) + Removed old png_set_rgbx() and png_set_xrgb() functions that have been + obsolete for about 2 years now (use png_set_filler() instead) + Added macros to read 16- and 32-bit ints directly from buffer, to be + used only on those systems that support it (namely PowerPC and 680x0) + With some testing, this may become the default for MACOS/PPC systems. + Only calculate CRC on data if we are going to use it + Added macros for zTXt compression type PNG_zTXt_COMPRESSION_??? + Added macros for simple libpng debugging output selectable at compile time + Removed PNG_READ_END_MODE in progressive reader (Smarasderagd) + More description of info_struct in libpng.txt and png.h + More instructions in example.c + More chunk types tested in pngtest.c + Renamed pngrcb.c to pngset.c, and all png_read_ functions to be + png_set_. We now have corresponding png_get_ + functions in pngget.c to get information in info_ptr. This isolates + the application from the internal organization of png_info_struct + (good for shared library implementations). + +Version 0.96 [May, 1997] + Fixed serious bug with < 8bpp images introduced in 0.95 + Fixed 256-color transparency bug (Greg Roelofs) + Fixed up documentation (Greg Roelofs, Laszlo Nyul) + Fixed "error" in pngconf.h for Linux setjmp() behavior + Fixed DOS medium model support (Tim Wegner) + Fixed png_check_keyword() for case with error in static string text + Added read of CRC after IEND chunk for embedded PNGs (Laszlo Nyul) + Added typecasts to quiet compiler errors + Added more debugging info + +Version 0.97 [January, 1998] + Removed PNG_USE_OWN_CRC capability + Relocated png_set_crc_action from pngrutil.c to pngrtran.c + Fixed typecasts of "new_key", etc. (Andreas Dilger) + Added RFC 1152 [sic] date support + Fixed bug in gamma handling of 4-bit grayscale + Added 2-bit grayscale gamma handling (Glenn R-P) + Added more typecasts. 65536L becomes (png_uint_32)65536L, etc. (Glenn R-P) + Minor corrections in libpng.txt + Added simple sRGB support (Glenn R-P) + Easier conditional compiling, e.g., + define PNG_READ/WRITE_NOT_FULLY_SUPPORTED; + all configurable options can be selected from command-line instead + of having to edit pngconf.h (Glenn R-P) + Fixed memory leak in pngwrite.c (free info_ptr->text) (Glenn R-P) + Added more conditions for png_do_background, to avoid changing + black pixels to background when a background is supplied and + no pixels are transparent + Repaired PNG_NO_STDIO behavior + Tested NODIV support and made it default behavior (Greg Roelofs) + Added "-m" option and PNGTEST_DEBUG_MEMORY to pngtest (John Bowler) + Regularized version numbering scheme and bumped shared-library major + version number to 2 to avoid problems with libpng 0.89 apps + (Greg Roelofs) + +Version 0.98 [January, 1998] + Cleaned up some typos in libpng.txt and in code documentation + Fixed memory leaks in pCAL chunk processing (Glenn R-P and John Bowler) + Cosmetic change "display_gamma" to "screen_gamma" in pngrtran.c + Changed recommendation about file_gamma for PC images to .51 from .45, + in example.c and libpng.txt, added comments to distinguish between + screen_gamma, viewing_gamma, and display_gamma. + Changed all references to RFC1152 to read RFC1123 and changed the + PNG_TIME_RFC1152_SUPPORTED macro to PNG_TIME_RFC1123_SUPPORTED + Added png_invert_alpha capability (Glenn R-P -- suggestion by Jon Vincent) + Changed srgb_intent from png_byte to int to avoid compiler bugs + +Version 0.99 [January 30, 1998] + Free info_ptr->text instead of end_info_ptr->text in pngread.c (John Bowler) + Fixed a longstanding "packswap" bug in pngtrans.c + Fixed some inconsistencies in pngconf.h that prevented compiling with + PNG_READ_GAMMA_SUPPORTED and PNG_READ_hIST_SUPPORTED undefined + Fixed some typos and made other minor rearrangement of libpng.txt (Andreas) + Changed recommendation about file_gamma for PC images to .50 from .51 in + example.c and libpng.txt, and changed file_gamma for sRGB images to .45 + Added a number of functions to access information from the png structure + png_get_image_height(), etc. (Glenn R-P, suggestion by Brad Pettit) + Added TARGET_MACOS similar to zlib-1.0.8 + Define PNG_ALWAYS_EXTERN when __MWERKS__ && WIN32 are defined + Added type casting to all png_malloc() function calls + +Version 0.99a [January 31, 1998] + Added type casts and parentheses to all returns that return a value.(Tim W.) + +Version 0.99b [February 4, 1998] + Added type cast png_uint_32 on malloc function calls where needed. + Changed type of num_hist from png_uint_32 to int (same as num_palette). + Added checks for rowbytes overflow, in case png_size_t is less than 32 bits. + Renamed makefile.elf to makefile.lnx. + +Version 0.99c [February 7, 1998] + More type casting. Removed erroneous overflow test in pngmem.c. + Added png_buffered_memcpy() and png_buffered_memset(), apply them to rowbytes. + Added UNIX manual pages libpng.3 (incorporating libpng.txt) and png.5. + +Version 0.99d [February 11, 1998] + Renamed "far_to_near()" "png_far_to_near()" + Revised libpng.3 + Version 99c "buffered" operations didn't work as intended. Replaced them + with png_memcpy_check() and png_memset_check(). + Added many "if (png_ptr == NULL) return" to quell compiler warnings about + unused png_ptr, mostly in pngget.c and pngset.c. + Check for overlength tRNS chunk present when indexed-color PLTE is read. + Cleaned up spelling errors in libpng.3/libpng.txt + Corrected a problem with png_get_tRNS() which returned undefined trans array + +Version 0.99e [February 28, 1998] + Corrected png_get_tRNS() again. + Add parentheses for easier reading of pngget.c, fixed "||" should be "&&". + Touched up example.c to make more of it compileable, although the entire + file still can't be compiled (Willem van Schaik) + Fixed a bug in png_do_shift() (Bryan Tsai) + Added a space in png.h prototype for png_write_chunk_start() + Replaced pngtest.png with one created with zlib 1.1.1 + Changed pngtest to report PASS even when file size is different (Jean-loup G.) + Corrected some logic errors in png_do_invert_alpha() (Chris Patterson) + +Version 0.99f [March 5, 1998] + Corrected a bug in pngpread() introduced in version 99c (Kevin Bracey) + Moved makefiles into a "scripts" directory, and added INSTALL instruction file + Added makefile.os2 and pngos2.def (A. Zabolotny) and makefile.s2x (W. Sebok) + Added pointers to "note on libpng versions" in makefile.lnx and README + Added row callback feature when reading and writing nonprogressive rows + and added a test of this feature in pngtest.c + Added user transform callbacks, with test of the feature in pngtest.c + +Version 0.99g [March 6, 1998, morning] + Minor changes to pngtest.c to suppress compiler warnings. + Removed "beta" language from documentation. + +Version 0.99h [March 6, 1998, evening] + Minor changes to previous minor changes to pngtest.c + Changed PNG_READ_NOT_FULLY_SUPPORTED to PNG_READ_TRANSFORMS_NOT_SUPPORTED + and added PNG_PROGRESSIVE_READ_NOT_SUPPORTED macro + Added user transform capability + +Version 1.00 [March 7, 1998] + Changed several typedefs in pngrutil.c + Added makefile.wat (Pawel Mrochen), updated makefile.tc3 (Willem van Schaik) + Replaced "while(1)" with "for(;;)" + Added PNGARG() to prototypes in pngtest.c and removed some prototypes + Updated some of the makefiles (Tom Lane) + Changed some typedefs (s_start, etc.) in pngrutil.c + Fixed dimensions of "short_months" array in pngwrite.c + Replaced ansi2knr.c with the one from jpeg-v6 + +Version 1.0.0 [March 8, 1998] + Changed name from 1.00 to 1.0.0 (Adam Costello) + Added smakefile.ppc (with SCOPTIONS.ppc) for Amiga PPC (Andreas Kleinert) + +Version 1.0.0a [March 9, 1998] + Fixed three bugs in pngrtran.c to make gamma+background handling consistent + (Greg Roelofs) + Changed format of the PNG_LIBPNG_VER integer to xyyzz instead of xyz + for major, minor, and bugfix releases. This is 10001. (Adam Costello, + Tom Lane) + Make months range from 1-12 in png_convert_to_rfc1123 + +Version 1.0.0b [March 13, 1998] + Quieted compiler complaints about two empty "for" loops in pngrutil.c + Minor changes to makefile.s2x + Removed #ifdef/#endif around a png_free() in pngread.c + +Version 1.0.1 [March 14, 1998] + Changed makefile.s2x to reduce security risk of using a relative pathname + Fixed some typos in the documentation (Greg). + Fixed a problem with value of "channels" returned by png_read_update_info() + +Version 1.0.1a [April 21, 1998] + Optimized Paeth calculations by replacing abs() function calls with intrinsics + plus other loop optimizations. Improves avg decoding speed by about 20%. + Commented out i386istic "align" compiler flags in makefile.lnx. + Reduced the default warning level in some makefiles, to make them consistent. + Removed references to IJG and JPEG in the ansi2knr.c copyright statement. + Fixed a bug in png_do_strip_filler with XXRRGGBB => RRGGBB transformation. + Added grayscale and 16-bit capability to png_do_read_filler(). + Fixed a bug in pngset.c, introduced in version 0.99c, that sets rowbytes + too large when writing an image with bit_depth < 8 (Bob Dellaca). + Corrected some bugs in the experimental weighted filtering heuristics. + Moved a misplaced pngrutil code block that truncates tRNS if it has more + than num_palette entries -- test was done before num_palette was defined. + Fixed a png_convert_to_rfc1123() bug that converts day 31 to 0 (Steve Eddins). + Changed compiler flags in makefile.wat for better optimization + (Pawel Mrochen). + +Version 1.0.1b [May 2, 1998] + Relocated png_do_gray_to_rgb() within png_do_read_transformations() (Greg). + Relocated the png_composite macros from pngrtran.c to png.h (Greg). + Added makefile.sco (contributed by Mike Hopkirk). + Fixed two bugs (missing definitions of "istop") introduced in libpng-1.0.1a. + Fixed a bug in pngrtran.c that would set channels=5 under some circumstances. + More work on the Paeth-filtering, achieving imperceptible speedup + (A Kleinert). + More work on loop optimization which may help when compiled with C++ + compilers. + Added warnings when people try to use transforms they've defined out. + Collapsed 4 "i" and "c" loops into single "i" loops in pngrtran and pngwtran. + Revised paragraph about png_set_expand() in libpng.txt and libpng.3 (Greg) + +Version 1.0.1c [May 11, 1998] + Fixed a bug in pngrtran.c (introduced in libpng-1.0.1a) where the masks for + filler bytes should have been 0xff instead of 0xf. + Added max_pixel_depth=32 in pngrutil.c when using FILLER with palette images. + Moved PNG_WRITE_WEIGHTED_FILTER_SUPPORTED and PNG_WRITE_FLUSH_SUPPORTED + out of the PNG_WRITE_TRANSFORMS_NOT_SUPPORTED block of pngconf.h + Added "PNG_NO_WRITE_TRANSFORMS" etc., as alternatives for *_NOT_SUPPORTED, + for consistency, in pngconf.h + Added individual "ifndef PNG_NO_[CAPABILITY]" in pngconf.h to make it easier + to remove unwanted capabilities via the compile line + Made some corrections to grammar (which, it's) in documentation (Greg). + Corrected example.c, use of row_pointers in png_write_image(). + +Version 1.0.1d [May 24, 1998] + Corrected several statements that used side effects illegally in pngrutil.c + and pngtrans.c, that were introduced in version 1.0.1b + Revised png_read_rows() to avoid repeated if-testing for NULL (A Kleinert) + More corrections to example.c, use of row_pointers in png_write_image() + and png_read_rows(). + Added pngdll.mak and pngdef.pas to scripts directory, contributed by + Bob Dellaca, to make a png32bd.dll with Borland C++ 4.5 + Fixed error in example.c with png_set_text: num_text is 3, not 2 (Guido V.) + Changed several loops from count-down to count-up, for consistency. + +Version 1.0.1e [June 6, 1998] + Revised libpng.txt and libpng.3 description of png_set_read|write_fn(), and + added warnings when people try to set png_read_fn and png_write_fn in + the same structure. + Added a test such that png_do_gamma will be done when num_trans==0 + for truecolor images that have defined a background. This corrects an + error that was introduced in libpng-0.90 that can cause gamma processing + to be skipped. + Added tests in png.h to include "trans" and "trans_values" in structures + when PNG_READ_BACKGROUND_SUPPORTED or PNG_READ_EXPAND_SUPPORTED is defined. + Add png_free(png_ptr->time_buffer) in png_destroy_read_struct() + Moved png_convert_to_rfc_1123() from pngwrite.c to png.c + Added capability for user-provided malloc_fn() and free_fn() functions, + and revised pngtest.c to demonstrate their use, replacing the + PNGTEST_DEBUG_MEM feature. + Added makefile.w32, for Microsoft C++ 4.0 and later (Tim Wegner). + +Version 1.0.2 [June 14, 1998] + Fixed two bugs in makefile.bor . + +Version 1.0.2a [December 30, 1998] + Replaced and extended code that was removed from png_set_filler() in 1.0.1a. + Fixed a bug in png_do_filler() that made it fail to write filler bytes in + the left-most pixel of each row (Kevin Bracey). + Changed "static pngcharp tIME_string" to "static char tIME_string[30]" + in pngtest.c (Duncan Simpson). + Fixed a bug in pngtest.c that caused pngtest to try to write a tIME chunk + even when no tIME chunk was present in the source file. + Fixed a problem in pngrutil.c: gray_to_rgb didn't always work with 16-bit. + Fixed a problem in png_read_push_finish_row(), which would not skip some + passes that it should skip, for images that are less than 3 pixels high. + Interchanged the order of calls to png_do_swap() and png_do_shift() + in pngwtran.c (John Cromer). + Added #ifdef PNG_DEBUG/#endif surrounding use of PNG_DEBUG in png.h . + Changed "bad adaptive filter type" from error to warning in pngrutil.c . + Fixed a documentation error about default filtering with 8-bit indexed-color. + Separated the PNG_NO_STDIO macro into PNG_NO_STDIO and PNG_NO_CONSOLE_IO + (L. Peter Deutsch). + Added png_set_rgb_to_gray() and png_get_rgb_to_gray_status() functions. + Added png_get_copyright() and png_get_header_version() functions. + Revised comments on png_set_progressive_read_fn() in libpng.txt and example.c + Added information about debugging in libpng.txt and libpng.3 . + Changed "ln -sf" to "ln -s -f" in makefile.s2x, makefile.lnx, and + makefile.sco. + Removed lines after Dynamic Dependencies" in makefile.aco . + Revised makefile.dec to make a shared library (Jeremie Petit). + Removed trailing blanks from all files. + +Version 1.0.2a [January 6, 1999] + Removed misplaced #endif and #ifdef PNG_NO_EXTERN near the end of png.h + Added "if" tests to silence complaints about unused png_ptr in png.h and png.c + Changed "check_if_png" function in example.c to return true (nonzero) if PNG. + Changed libpng.txt to demonstrate png_sig_cmp() instead of png_check_sig() + which is obsolete. + +Version 1.0.3 [January 14, 1999] + Added makefile.hux, for Hewlett Packard HPUX 10.20 and 11.00 (Jim Rice) + Added a statement of Y2K compliance in png.h, libpng.3, and Y2KINFO. + +Version 1.0.3a [August 12, 1999] + Added check for PNG_READ_INTERLACE_SUPPORTED in pngread.c; issue a warning + if an attempt is made to read an interlaced image when it's not supported. + Added check if png_ptr->trans is defined before freeing it in pngread.c + Modified the Y2K statement to include versions back to version 0.71 + Fixed a bug in the check for valid IHDR bit_depth/color_types in pngrutil.c + Modified makefile.wat (added -zp8 flag, ".symbolic", changed some comments) + Replaced leading blanks with tab characters in makefile.hux + Changed "dworkin.wustl.edu" to "ccrc.wustl.edu" in various documents. + Changed (float)red and (float)green to (double)red, (double)green + in png_set_rgb_to_gray() to avoid "promotion" problems in AIX. + Fixed a bug in pngconf.h that omitted when PNG_DEBUG==0 (K Bracey). + Reformatted libpng.3 and libpngpf.3 with proper fonts (script by J. vanZandt). + Updated documentation to refer to the PNG-1.2 specification. + Removed ansi2knr.c and left pointers to the latest source for ansi2knr.c + in makefile.knr, INSTALL, and README (L. Peter Deutsch) + Fixed bugs in calculation of the length of rowbytes when adding alpha + channels to 16-bit images, in pngrtran.c (Chris Nokleberg) + Added function png_set_user_transform_info() to store user_transform_ptr, + user_depth, and user_channels into the png_struct, and a function + png_get_user_transform_ptr() to retrieve the pointer (Chris Nokleberg) + Added function png_set_empty_plte_permitted() to make libpng useable + in MNG applications. + Corrected the typedef for png_free_ptr in png.h (Jesse Jones). + Correct gamma with srgb is 45455 instead of 45000 in pngrutil.c, to be + consistent with PNG-1.2, and allow variance of 500 before complaining. + Added assembler code contributed by Intel in file pngvcrd.c and modified + makefile.w32 to use it (Nirav Chhatrapati, INTEL Corporation, + Gilles Vollant) + Changed "ln -s -f" to "ln -f -s" in the makefiles to make Solaris happy. + Added some aliases for png_set_expand() in pngrtran.c, namely + png_set_expand_PLTE(), png_set_expand_depth(), and png_set_expand_tRNS() + (Greg Roelofs, in "PNG: The Definitive Guide"). + Added makefile.beo for BEOS on X86, contributed by Sander Stok. + +Version 1.0.3b [August 26, 1999] + Replaced 2147483647L several places with PNG_MAX_UINT macro, defined in png.h + Changed leading blanks to tabs in all makefiles. + Define PNG_USE_PNGVCRD in makefile.w32, to get MMX assembler code. + Made alternate versions of png_set_expand() in pngrtran.c, namely + png_set_gray_1_2_4_to_8, png_set_palette_to_rgb, and png_set_tRNS_to_alpha + (Greg Roelofs, in "PNG: The Definitive Guide"). Deleted the 1.0.3a aliases. + Relocated start of 'extern "C"' block in png.h so it doesn't include pngconf.h + Revised calculation of num_blocks in pngmem.c to avoid a potentially + negative shift distance, whose results are undefined in the C language. + Added a check in pngset.c to prevent writing multiple tIME chunks. + Added a check in pngwrite.c to detect invalid small window_bits sizes. + +Version 1.0.3d [September 4, 1999] + Fixed type casting of igamma in pngrutil.c + Added new png_expand functions to scripts/pngdef.pas and pngos2.def + Added a demo read_user_transform_fn that examines the row filters in pngtest.c + +Version 1.0.4 [September 24, 1999, not distributed publicly] + Define PNG_ALWAYS_EXTERN in pngconf.h if __STDC__ is defined + Delete #define PNG_INTERNAL and include "png.h" from pngasmrd.h + Made several minor corrections to pngtest.c + Renamed the makefiles with longer but more user friendly extensions. + Copied the PNG copyright and license to a separate LICENSE file. + Revised documentation, png.h, and example.c to remove reference to + "viewing_gamma" which no longer appears in the PNG specification. + Revised pngvcrd.c to use MMX code for interlacing only on the final pass. + Updated pngvcrd.c to use the faster C filter algorithms from libpng-1.0.1a + Split makefile.win32vc into two versions, makefile.vcawin32 (uses MMX + assembler code) and makefile.vcwin32 (doesn't). + Added a CPU timing report to pngtest.c (enabled by defining PNGTEST_TIMING) + Added a copy of pngnow.png to the distribution. + +Version 1.0.4a [September 25, 1999] + Increase max_pixel_depth in pngrutil.c if a user transform needs it. + Changed several division operations to right-shifts in pngvcrd.c + +Version 1.0.4b [September 30, 1999] + Added parentheses in line 3732 of pngvcrd.c + Added a comment in makefile.linux warning about buggy -O3 in pgcc 2.95.1 + +Version 1.0.4c [October 1, 1999] + Added a "png_check_version" function in png.c and pngtest.c that will generate + a helpful compiler error if an old png.h is found in the search path. + Changed type of png_user_transform_depth|channels from int to png_byte. + Added "Libpng is OSI Certified Open Source Software" statement to png.h + +Version 1.0.4d [October 6, 1999] + Changed 0.45 to 0.45455 in png_set_sRGB() + Removed unused PLTE entries from pngnow.png + Re-enabled some parts of pngvcrd.c (png_combine_row) that work properly. + +Version 1.0.4e [October 10, 1999] + Fixed sign error in pngvcrd.c (Greg Roelofs) + Replaced some instances of memcpy with simple assignments in pngvcrd (GR-P) + +Version 1.0.4f [October 15, 1999] + Surrounded example.c code with #if 0 .. #endif to prevent people from + inadvertently trying to compile it. + Changed png_get_header_version() from a function to a macro in png.h + Added type casting mostly in pngrtran.c and pngwtran.c + Removed some pointless "ptr = NULL" in pngmem.c + Added a "contrib" directory containing the source code from Greg's book. + +Version 1.0.5 [October 15, 1999] + Minor editing of the INSTALL and README files. + +Version 1.0.5a [October 23, 1999] + Added contrib/pngsuite and contrib/pngminus (Willem van Schaik) + Fixed a typo in the png_set_sRGB() function call in example.c (Jan Nijtmans) + Further optimization and bugfix of pngvcrd.c + Revised pngset.c so that it does not allocate or free memory in the user's + text_ptr structure. Instead, it makes its own copy. + Created separate write_end_info_struct in pngtest.c for a more severe test. + Added code in pngwrite.c to free info_ptr->text[i].key to stop a memory leak. + +Version 1.0.5b [November 23, 1999] + Moved PNG_FLAG_HAVE_CHUNK_HEADER, PNG_FLAG_BACKGROUND_IS_GRAY and + PNG_FLAG_WROTE_tIME from flags to mode. + Added png_write_info_before_PLTE() function. + Fixed some typecasting in contrib/gregbook/*.c + Updated scripts/makevms.com and added makevms.com to contrib/gregbook + and contrib/pngminus (Martin Zinser) + +Version 1.0.5c [November 26, 1999] + Moved png_get_header_version from png.h to png.c, to accommodate ansi2knr. + Removed all global arrays (according to PNG_NO_GLOBAL_ARRAYS macro), to + accommodate making DLL's: Moved usr_png_ver from global variable to function + png_get_header_ver() in png.c. Moved png_sig to png_sig_bytes in png.c and + eliminated use of png_sig in pngwutil.c. Moved the various png_CHNK arrays + into pngtypes.h. Eliminated use of global png_pass arrays. Declared the + png_CHNK and png_pass arrays to be "const". Made the global arrays + available to applications (although none are used in libpng itself) when + PNG_NO_GLOBAL_ARRAYS is not defined or when PNG_GLOBAL_ARRAYS is defined. + Removed some extraneous "-I" from contrib/pngminus/makefile.std + Changed the PNG_sRGB_INTENT macros in png.h to be consistent with PNG-1.2. + Change PNG_SRGB_INTENT to PNG_sRGB_INTENT in libpng.txt and libpng.3 + +Version 1.0.5d [November 29, 1999] + Add type cast (png_const_charp) two places in png.c + Eliminated pngtypes.h; use macros instead to declare PNG_CHNK arrays. + Renamed "PNG_GLOBAL_ARRAYS" to "PNG_USE_GLOBAL_ARRAYS" and made available + to applications a macro "PNG_USE_LOCAL_ARRAYS". + comment out (with #ifdef) all the new declarations when + PNG_USE_GLOBAL_ARRAYS is defined. + Added PNG_EXPORT_VAR macro to accommodate making DLL's. + +Version 1.0.5e [November 30, 1999] + Added iCCP, iTXt, and sPLT support; added "lang" member to the png_text + structure; refactored the inflate/deflate support to make adding new chunks + with trailing compressed parts easier in the future, and added new functions + png_free_iCCP, png_free_pCAL, png_free_sPLT, png_free_text, png_get_iCCP, + png_get_spalettes, png_set_iCCP, png_set_spalettes (Eric S. Raymond). + NOTE: Applications that write text chunks MUST define png_text->lang + before calling png_set_text(). It must be set to NULL if you want to + write tEXt or zTXt chunks. If you want your application to be able to + run with older versions of libpng, use + + #ifdef PNG_iTXt_SUPPORTED + png_text[i].lang = NULL; + #endif + + Changed png_get_oFFs() and png_set_oFFs() to use signed rather than unsigned + offsets (Eric S. Raymond). + Combined PNG_READ_cHNK_SUPPORTED and PNG_WRITE_cHNK_SUPPORTED macros into + PNG_cHNK_SUPPORTED and combined the three types of PNG_text_SUPPORTED + macros, leaving the separate macros also available. + Removed comments on #endifs at the end of many short, non-nested #if-blocks. + +Version 1.0.5f [December 6, 1999] + Changed makefile.solaris to issue a warning about potential problems when + the ucb "ld" is in the path ahead of the ccs "ld". + Removed "- [date]" from the "synopsis" line in libpng.3 and libpngpf.3. + Added sCAL chunk support (Eric S. Raymond). + +Version 1.0.5g [December 7, 1999] + Fixed "png_free_spallettes" typo in png.h + Added code to handle new chunks in pngpread.c + Moved PNG_CHNK string macro definitions outside of PNG_NO_EXTERN block + Added "translated_key" to png_text structure and png_write_iTXt(). + Added code in pngwrite.c to work around a newly discovered zlib bug. + +Version 1.0.5h [December 10, 1999] + NOTE: regarding the note for version 1.0.5e, the following must also + be included in your code: + png_text[i].translated_key = NULL; + Unknown chunk handling is now supported. + Option to eliminate all floating point support was added. Some new + fixed-point functions such as png_set_gAMA_fixed() were added. + Expanded tabs and removed trailing blanks in source files. + +Version 1.0.5i [December 13, 1999] + Added some type casts to silence compiler warnings. + Renamed "png_free_spalette" to "png_free_spalettes" for consistency. + Removed leading blanks from a #define in pngvcrd.c + Added some parameters to the new png_set_keep_unknown_chunks() function. + Added a test for up->location != 0 in the first instance of writing + unknown chunks in pngwrite.c + Changed "num" to "i" in png_free_spalettes() and png_free_unknowns() to + prevent recursion. + Added png_free_hIST() function. + Various patches to fix bugs in the sCAL and integer cHRM processing, + and to add some convenience macros for use with sCAL. + +Version 1.0.5j [December 21, 1999] + Changed "unit" parameter of png_write_sCAL from png_byte to int, to work + around buggy compilers. + Added new type "png_fixed_point" for integers that hold float*100000 values + Restored backward compatibility of tEXt/zTXt chunk processing: + Restored the first four members of png_text to the same order as v.1.0.5d. + Added members "lang_key" and "itxt_length" to png_text struct. Set + text_length=0 when "text" contains iTXt data. Use the "compression" + member to distinguish among tEXt/zTXt/iTXt types. Added + PNG_ITXT_COMPRESSION_NONE (1) and PNG_ITXT_COMPRESSION_zTXt(2) macros. + The "Note" above, about backward incompatibility of libpng-1.0.5e, no + longer applies. + Fixed png_read|write_iTXt() to read|write parameters in the right order, + and to write the iTXt chunk after IDAT if it appears in the end_ptr. + Added pnggccrd.c, version of pngvcrd.c Intel assembler for gcc (Greg Roelofs) + Reversed the order of trying to write floating-point and fixed-point gAMA. + +Version 1.0.5k [December 27, 1999] + Added many parentheses, e.g., "if (a && b & c)" becomes "if (a && (b & c))" + Added png_handle_as_unknown() function (Glenn) + Added png_free_chunk_list() function and chunk_list and num_chunk_list members + of png_ptr. + Eliminated erroneous warnings about multiple sPLT chunks and sPLT-after-PLTE. + Fixed a libpng-1.0.5h bug in pngrutil.c that was issuing erroneous warnings + about ignoring incorrect gAMA with sRGB (gAMA was in fact not ignored) + Added png_free_tRNS(); png_set_tRNS() now malloc's its own trans array (ESR). + Define png_get_int_32 when oFFs chunk is supported as well as when pCAL is. + Changed type of proflen from png_int_32 to png_uint_32 in png_get_iCCP(). + +Version 1.0.5l [January 1, 2000] + Added functions png_set_read_user_chunk_fn() and png_get_user_chunk_ptr() + for setting a callback function to handle unknown chunks and for + retrieving the associated user pointer (Glenn). + +Version 1.0.5m [January 7, 2000] + Added high-level functions png_read_png(), png_write_png(), png_free_pixels(). + +Version 1.0.5n [January 9, 2000] + Added png_free_PLTE() function, and modified png_set_PLTE() to malloc its + own memory for info_ptr->palette. This makes it safe for the calling + application to free its copy of the palette any time after it calls + png_set_PLTE(). + +Version 1.0.5o [January 20, 2000] + Cosmetic changes only (removed some trailing blanks and TABs) + +Version 1.0.5p [January 31, 2000] + Renamed pngdll.mak to makefile.bd32 + Cosmetic changes in pngtest.c + +Version 1.0.5q [February 5, 2000] + Relocated the makefile.solaris warning about PATH problems. + Fixed pngvcrd.c bug by pushing/popping registers in mmxsupport (Bruce Oberg) + Revised makefile.gcmmx + Added PNG_SETJMP_SUPPORTED, PNG_SETJMP_NOT_SUPPORTED, and PNG_ABORT() macros + +Version 1.0.5r [February 7, 2000] + Removed superfluous prototype for png_get_itxt from png.h + Fixed a bug in pngrtran.c that improperly expanded the background color. + Return *num_text=0 from png_get_text() when appropriate, and fix documentation + of png_get_text() in libpng.txt/libpng.3. + +Version 1.0.5s [February 18, 2000] + Added "png_jmp_env()" macro to pngconf.h, to help people migrate to the + new error handler that's planned for the next libpng release, and changed + example.c, pngtest.c, and contrib programs to use this macro. + Revised some of the DLL-export macros in pngconf.h (Greg Roelofs) + Fixed a bug in png_read_png() that caused it to fail to expand some images + that it should have expanded. + Fixed some mistakes in the unused and undocumented INCH_CONVERSIONS functions + in pngget.c + Changed the allocation of palette, history, and trans arrays back to + the version 1.0.5 method (linking instead of copying) which restores + backward compatibility with version 1.0.5. Added some remarks about + that in example.c. Added "free_me" member to info_ptr and png_ptr + and added png_free_data() function. + Updated makefile.linux and makefile.gccmmx to make directories conditionally. + Made cosmetic changes to pngasmrd.h + Added png_set_rows() and png_get_rows(), for use with png_read|write_png(). + Modified png_read_png() to allocate info_ptr->row_pointers only if it + hasn't already been allocated. + +Version 1.0.5t [March 4, 2000] + Changed png_jmp_env() migration aiding macro to png_jmpbuf(). + Fixed "interlace" typo (should be "interlaced") in contrib/gregbook/read2-x.c + Fixed bug with use of PNG_BEFORE_IHDR bit in png_ptr->mode, introduced when + PNG_FLAG_HAVE_CHUNK_HEADER was moved into png_ptr->mode in version 1.0.5b + Files in contrib/gregbook were revised to use png_jmpbuf() and to select + a 24-bit visual if one is available, and to allow abbreviated options. + Files in contrib/pngminus were revised to use the png_jmpbuf() macro. + Removed spaces in makefile.linux and makefile.gcmmx, introduced in 1.0.5s + +Version 1.0.5u [March 5, 2000] + Simplified the code that detects old png.h in png.c and pngtest.c + Renamed png_spalette (_p, _pp) to png_sPLT_t (_tp, _tpp) + Increased precision of rgb_to_gray calculations from 8 to 15 bits and + added png_set_rgb_to_gray_fixed() function. + Added makefile.bc32 (32-bit Borland C++, C mode) + +Version 1.0.5v [March 11, 2000] + Added some parentheses to the png_jmpbuf macro definition. + Updated references to the zlib home page, which has moved to freesoftware.com. + Corrected bugs in documentation regarding png_read_row() and png_write_row(). + Updated documentation of png_rgb_to_gray calculations in libpng.3/libpng.txt. + Renamed makefile.borland,turboc3 back to makefile.bor,tc3 as in version 1.0.3, + revised borland makefiles; added makefile.ibmvac3 and makefile.gcc (Cosmin) + +Version 1.0.6 [March 20, 2000] + Minor revisions of makefile.bor, libpng.txt, and gregbook/rpng2-win.c + Added makefile.sggcc (SGI IRIX with gcc) + +Version 1.0.6d [April 7, 2000] + Changed sprintf() to strcpy() in png_write_sCAL_s() to work without STDIO + Added data_length parameter to png_decompress_chunk() function + Revised documentation to remove reference to abandoned png_free_chnk functions + Fixed an error in png_rgb_to_gray_fixed() + Revised example.c, usage of png_destroy_write_struct(). + Renamed makefile.ibmvac3 to makefile.ibmc, added libpng.icc IBM project file + Added a check for info_ptr->free_me&PNG_FREE_TEXT when freeing text in png.c + Simplify png_sig_bytes() function to remove use of non-ISO-C strdup(). + +Version 1.0.6e [April 9, 2000] + Added png_data_freer() function. + In the code that checks for over-length tRNS chunks, added check of + info_ptr->num_trans as well as png_ptr->num_trans (Matthias Benckmann) + Minor revisions of libpng.txt/libpng.3. + Check for existing data and free it if the free_me flag is set, in png_set_*() + and png_handle_*(). + Only define PNG_WEIGHTED_FILTERS_SUPPORTED when PNG_FLOATING_POINT_SUPPORTED + is defined. + Changed several instances of PNG_NO_CONSOLE_ID to PNG_NO_STDIO in pngrutil.c + and mentioned the purposes of the two macros in libpng.txt/libpng.3. + +Version 1.0.6f [April 14, 2000] + Revised png_set_iCCP() and png_set_rows() to avoid prematurely freeing data. + Add checks in png_set_text() for NULL members of the input text structure. + Revised libpng.txt/libpng.3. + Removed superfluous prototype for png_set_iTXt from png.h + Removed "else" from pngread.c, after png_error(), and changed "0" to "length". + Changed several png_errors about malformed ancillary chunks to png_warnings. + +Version 1.0.6g [April 24, 2000] + Added png_pass-* arrays to pnggccrd.c when PNG_USE_LOCAL_ARRAYS is defined. + Relocated paragraph about png_set_background() in libpng.3/libpng.txt + and other revisions (Matthias Benckmann) + Relocated info_ptr->free_me, png_ptr->free_me, and other info_ptr and + png_ptr members to restore binary compatibility with libpng-1.0.5 + (breaks compatibility with libpng-1.0.6). + +Version 1.0.6h [April 24, 2000] + Changed shared library so-number pattern from 2.x.y.z to xy.z (this builds + libpng.so.10 & libpng.so.10.6h instead of libpng.so.2 & libpng.so.2.1.0.6h) + This is a temporary change for test purposes. + +Version 1.0.6i [May 2, 2000] + Rearranged some members at the end of png_info and png_struct, to put + unknown_chunks_num and free_me within the original size of the png_structs + and free_me, png_read_user_fn, and png_free_fn within the original png_info, + because some old applications allocate the structs directly instead of + using png_create_*(). + Added documentation of user memory functions in libpng.txt/libpng.3 + Modified png_read_png so that it will use user_allocated row_pointers + if present, unless free_me directs that it be freed, and added description + of the use of png_set_rows() and png_get_rows() in libpng.txt/libpng.3. + Added PNG_LEGACY_SUPPORTED macro, and #ifdef out all new (since version + 1.00) members of png_struct and png_info, to regain binary compatibility + when you define this macro. Capabilities lost in this event + are user transforms (new in version 1.0.0),the user transform pointer + (new in version 1.0.2), rgb_to_gray (new in 1.0.5), iCCP, sCAL, sPLT, + the high-level interface, and unknown chunks support (all new in 1.0.6). + This was necessary because of old applications that allocate the structs + directly as authors were instructed to do in libpng-0.88 and earlier, + instead of using png_create_*(). + Added modes PNG_CREATED_READ_STRUCT and PNG_CREATED_WRITE_STRUCT which + can be used to detect codes that directly allocate the structs, and + code to check these modes in png_read_init() and png_write_init() and + generate a libpng error if the modes aren't set and PNG_LEGACY_SUPPORTED + was not defined. + Added makefile.intel and updated makefile.watcom (Pawel Mrochen) + +Version 1.0.6j [May 3, 2000] + Overloaded png_read_init() and png_write_init() with macros that convert + calls to png_read_init_2() or png_write_init_2() that check the version + and structure sizes. + +Version 1.0.7beta11 [May 7, 2000] + Removed the new PNG_CREATED_READ_STRUCT and PNG_CREATED_WRITE_STRUCT modes + which are no longer used. + Eliminated the three new members of png_text when PNG_LEGACY_SUPPORTED is + defined or when neither PNG_READ_iTXt_SUPPORTED nor PNG_WRITE_iTXT_SUPPORTED + is defined. + Made PNG_NO_READ|WRITE_iTXt the default setting, to avoid memory + overrun when old applications fill the info_ptr->text structure directly. + Added PNGAPI macro, and added it to the definitions of all exported functions. + Relocated version macro definitions ahead of the includes of zlib.h and + pngconf.h in png.h. + +Version 1.0.7beta12 [May 12, 2000] + Revised pngset.c to avoid a problem with expanding the png_debug macro. + Deleted some extraneous defines from pngconf.h + Made PNG_NO_CONSOLE_IO the default condition when PNG_BUILD_DLL is defined. + Use MSC _RPTn debugging instead of fprintf if _MSC_VER is defined. + Added png_access_version_number() function. + Check for mask&PNG_FREE_CHNK (for TEXT, SCAL, PCAL) in png_free_data(). + Expanded libpng.3/libpng.txt information about png_data_freer(). + +Version 1.0.7beta14 [May 17, 2000] (beta13 was not published) + Changed pnggccrd.c and pngvcrd.c to handle bad adaptive filter types as + warnings instead of errors, as pngrutil.c does. + Set the PNG_INFO_IDAT valid flag in png_set_rows() so png_write_png() + will actually write IDATs. + Made the default PNG_USE_LOCAL_ARRAYS depend on PNG_DLL instead of WIN32. + Make png_free_data() ignore its final parameter except when freeing data + that can have multiple instances (text, sPLT, unknowns). + Fixed a new bug in png_set_rows(). + Removed info_ptr->valid tests from png_free_data(), as in version 1.0.5. + Added png_set_invalid() function. + Fixed incorrect illustrations of png_destroy_write_struct() in example.c. + +Version 1.0.7beta15 [May 30, 2000] + Revised the deliberately erroneous Linux setjmp code in pngconf.h to produce + fewer error messages. + Rearranged checks for Z_OK to check the most likely path first in pngpread.c + and pngwutil.c. + Added checks in pngtest.c for png_create_*() returning NULL, and mentioned + in libpng.txt/libpng.3 the need for applications to check this. + Changed names of png_default_*() functions in pngtest to pngtest_*(). + Changed return type of png_get_x|y_offset_*() from png_uint_32 to png_int_32. + Fixed some bugs in the unused PNG_INCH_CONVERSIONS functions in pngget.c + Set each pointer to NULL after freeing it in png_free_data(). + Worked around a problem in pngconf.h; AIX's strings.h defines an "index" + macro that conflicts with libpng's png_color_16.index. (Dimitri + Papadapoulos) + Added "msvc" directory with MSVC++ project files (Simon-Pierre Cadieux). + +Version 1.0.7beta16 [June 4, 2000] + Revised the workaround of AIX string.h "index" bug. + Added a check for overlength PLTE chunk in pngrutil.c. + Added PNG_NO_POINTER_INDEXING macro to use array-indexing instead of pointer + indexing in pngrutil.c and pngwutil.c to accommodate a buggy compiler. + Added a warning in png_decompress_chunk() when it runs out of data, e.g. + when it tries to read an erroneous PhotoShop iCCP chunk. + Added PNG_USE_DLL macro. + Revised the copyright/disclaimer/license notice. + Added contrib/msvctest directory + +Version 1.0.7rc1 [June 9, 2000] + Corrected the definition of PNG_TRANSFORM_INVERT_ALPHA (0x0400 not 0x0200) + Added contrib/visupng directory (Willem van Schaik) + +Version 1.0.7beta18 [June 23, 2000] + Revised PNGAPI definition, and pngvcrd.c to work with __GCC__ + and do not redefine PNGAPI if it is passed in via a compiler directive. + Revised visupng/PngFile.c to remove returns from within the Try block. + Removed leading underscores from "_PNG_H" and "_PNG_SAVE_BSD_SOURCE" macros. + Updated contrib/visupng/cexcept.h to version 1.0.0. + Fixed bugs in pngwrite.c and pngwutil.c that prevented writing iCCP chunks. + +Version 1.0.7rc2 [June 28, 2000] + Updated license to include disclaimers required by UCITA. + Fixed "DJBPP" typo in pnggccrd.c introduced in beta18. + +Version 1.0.7 [July 1, 2000] + Revised the definition of "trans_values" in libpng.3/libpng.txt + +Version 1.0.8beta1 [July 8, 2000] + Added png_free(png_ptr, key) two places in pngpread.c to stop memory leaks. + Changed PNG_NO_STDIO to PNG_NO_CONSOLE_IO, several places in pngrutil.c and + pngwutil.c. + Changed PNG_EXPORT_VAR to use PNG_IMPEXP, in pngconf.h. + Removed unused "#include " from png.c + Added WindowsCE support. + Revised pnggccrd.c to work with gcc-2.95.2 and in the Cygwin environment. + +Version 1.0.8beta2 [July 10, 2000] + Added project files to the wince directory and made further revisions + of pngtest.c, pngrio.c, and pngwio.c in support of WindowsCE. + +Version 1.0.8beta3 [July 11, 2000] + Only set the PNG_FLAG_FREE_TRNS or PNG_FREE_TRNS flag in png_handle_tRNS() + for indexed-color input files to avoid potential double-freeing trans array + under some unusual conditions; problem was introduced in version 1.0.6f. + Further revisions to pngtest.c and files in the wince subdirectory. + +Version 1.0.8beta4 [July 14, 2000] + Added the files pngbar.png and pngbar.jpg to the distribution. + Added makefile.cygwin, and cygwin support in pngconf.h + Added PNG_NO_ZALLOC_ZERO macro (makes png_zalloc skip zeroing memory) + +Version 1.0.8rc1 [July 16, 2000] + Revised png_debug() macros and statements to eliminate compiler warnings. + +Version 1.0.8 [July 24, 2000] + Added png_flush() in pngwrite.c, after png_write_IEND(). + Updated makefile.hpux to build a shared library. + +Version 1.0.9beta1 [November 10, 2000] + Fixed typo in scripts/makefile.hpux + Updated makevms.com in scripts and contrib/* and contrib/* (Martin Zinser) + Fixed seqence-point bug in contrib/pngminus/png2pnm (Martin Zinser) + Changed "cdrom.com" in documentation to "libpng.org" + Revised pnggccrd.c to get it all working, and updated makefile.gcmmx (Greg). + Changed type of "params" from voidp to png_voidp in png_read|write_png(). + Make sure PNGAPI and PNG_IMPEXP are defined in pngconf.h. + Revised the 3 instances of WRITEFILE in pngtest.c. + Relocated "msvc" and "wince" project subdirectories into "dll" subdirectory. + Updated png.rc in dll/msvc project + Revised makefile.dec to define and use LIBPATH and INCPATH + Increased size of global png_libpng_ver[] array from 12 to 18 chars. + Made global png_libpng_ver[], png_sig[] and png_pass_*[] arrays const. + Removed duplicate png_crc_finish() from png_handle_bKGD() function. + Added a warning when application calls png_read_update_info() multiple times. + Revised makefile.cygwin + Fixed bugs in iCCP support in pngrutil.c and pngwutil.c. + Replaced png_set_empty_plte_permitted() with png_permit_mng_features(). + +Version 1.0.9beta2 [November 19, 2000] + Renamed the "dll" subdirectory "projects". + Added borland project files to "projects" subdirectory. + Set VS_FF_PRERELEASE and VS_FF_PATCHED flags in msvc/png.rc when appropriate. + Add error message in png_set_compression_buffer_size() when malloc fails. + +Version 1.0.9beta3 [November 23, 2000] + Revised PNG_LIBPNG_BUILD_TYPE macro in png.h, used in the msvc project. + Removed the png_flush() in pngwrite.c that crashes some applications + that don't set png_output_flush_fn. + Added makefile.macosx and makefile.aix to scripts directory. + +Version 1.0.9beta4 [December 1, 2000] + Change png_chunk_warning to png_warning in png_check_keyword(). + Increased the first part of msg buffer from 16 to 18 in png_chunk_error(). + +Version 1.0.9beta5 [December 15, 2000] + Added support for filter method 64 (for PNG datastreams embedded in MNG). + +Version 1.0.9beta6 [December 18, 2000] + Revised png_set_filter() to accept filter method 64 when appropriate. + Added new PNG_HAVE_PNG_SIGNATURE bit to png_ptr->mode and use it to + help prevent applications from using MNG features in PNG datastreams. + Added png_permit_mng_features() function. + Revised libpng.3/libpng.txt. Changed "filter type" to "filter method". + +Version 1.0.9rc1 [December 23, 2000] + Revised test for PNG_HAVE_PNG_SIGNATURE in pngrutil.c + Fixed error handling of unknown compression type in png_decompress_chunk(). + In pngconf.h, define __cdecl when _MSC_VER is defined. + +Version 1.0.9beta7 [December 28, 2000] + Changed PNG_TEXT_COMPRESSION_zTXt to PNG_COMPRESSION_TYPE_BASE several places. + Revised memory management in png_set_hIST and png_handle_hIST in a backward + compatible manner. PLTE and tRNS were revised similarly. + Revised the iCCP chunk reader to ignore trailing garbage. + +Version 1.0.9beta8 [January 12, 2001] + Moved pngasmrd.h into pngconf.h. + Improved handling of out-of-spec garbage iCCP chunks generated by PhotoShop. + +Version 1.0.9beta9 [January 15, 2001] + Added png_set_invalid, png_permit_mng_features, and png_mmx_supported to + wince and msvc project module definition files. + Minor revision of makefile.cygwin. + Fixed bug with progressive reading of narrow interlaced images in pngpread.c + +Version 1.0.9beta10 [January 16, 2001] + Do not typedef png_FILE_p in pngconf.h when PNG_NO_STDIO is defined. + Fixed "png_mmx_supported" typo in project definition files. + +Version 1.0.9beta11 [January 19, 2001] + Updated makefile.sgi to make shared library. + Removed png_mmx_support() function and disabled PNG_MNG_FEATURES_SUPPORTED + by default, for the benefit of DLL forward compatibility. These will + be re-enabled in version 1.2.0. + +Version 1.0.9rc2 [January 22, 2001] + Revised cygwin support. + +Version 1.0.9 [January 31, 2001] + Added check of cygwin's ALL_STATIC in pngconf.h + Added "-nommx" parameter to contrib/gregbook/rpng2-win and rpng2-x demos. + +Version 1.0.10beta1 [March 14, 2001] + Revised makefile.dec, makefile.sgi, and makefile.sggcc; added makefile.hpgcc. + Reformatted libpng.3 to eliminate bad line breaks. + Added checks for _mmx_supported in the read_filter_row function of pnggccrd.c + Added prototype for png_mmx_support() near the top of pnggccrd.c + Moved some error checking from png_handle_IHDR to png_set_IHDR. + Added PNG_NO_READ_SUPPORTED and PNG_NO_WRITE_SUPPORTED macros. + Revised png_mmx_support() function in pnggccrd.c + Restored version 1.0.8 PNG_WRITE_EMPTY_PLTE_SUPPORTED behavior in pngwutil.c + Fixed memory leak in contrib/visupng/PngFile.c + Fixed bugs in png_combine_row() in pnggccrd.c and pngvcrd.c (C version) + Added warnings when retrieving or setting gamma=0. + Increased the first part of msg buffer from 16 to 18 in png_chunk_warning(). + +Version 1.0.10rc1 [March 23, 2001] + Changed all instances of memcpy, strcpy, and strlen to png_memcpy, png_strcpy, + and png_strlen. + Revised png_mmx_supported() function in pnggccrd.c to return proper value. + Fixed bug in progressive reading (pngpread.c) with small images (height < 8). + +Version 1.0.10 [March 30, 2001] + Deleted extraneous space (introduced in 1.0.9) from line 42 of makefile.cygwin + Added beos project files (Chris Herborth) + +Version 1.0.11beta1 [April 3, 2001] + Added type casts on several png_malloc() calls (Dimitri Papadapoulos). + Removed a no-longer needed AIX work-around from pngconf.h + Changed several "//" single-line comments to C-style in pnggccrd.c + +Version 1.0.11beta2 [April 11, 2001] + Removed PNGAPI from several functions whose prototypes did not have PNGAPI. + Updated scripts/pngos2.def + +Version 1.0.11beta3 [April 14, 2001] + Added checking the results of many instances of png_malloc() for NULL + +Version 1.0.11beta4 [April 20, 2001] + Undid the changes from version 1.0.11beta3. Added a check for NULL return + from user's malloc_fn(). + Removed some useless type casts of the NULL pointer. + Added makefile.netbsd + +Version 1.0.11 [April 27, 2001] + Revised makefile.netbsd + +Version 1.0.12beta1 [May 14, 2001] + Test for Windows platform in pngconf.h when including malloc.h (Emmanuel Blot) + Updated makefile.cygwin and handling of Cygwin's ALL_STATIC in pngconf.h + Added some never-to-be-executed code in pnggccrd.c to quiet compiler warnings. + Eliminated the png_error about apps using png_read|write_init(). Instead, + libpng will reallocate the png_struct and info_struct if they are too small. + This retains future binary compatibility for old applications written for + libpng-0.88 and earlier. + +Version 1.2.0beta1 [May 6, 2001] + Bumped DLLNUM to 2. + Re-enabled PNG_MNG_FEATURES_SUPPORTED and enabled PNG_ASSEMBLER_CODE_SUPPORTED + by default. + Added runtime selection of MMX features. + Added png_set_strip_error_numbers function and related macros. + +Version 1.2.0beta2 [May 7, 2001] + Finished merging 1.2.0beta1 with version 1.0.11 + Added a check for attempts to read or write PLTE in grayscale PNG datastreams. + +Version 1.2.0beta3 [May 17, 2001] + Enabled user memory function by default. + Modified png_create_struct so it passes user mem_ptr to user memory allocator. + Increased png_mng_features flag from png_byte to png_uint_32. + Bumped shared-library (so-number) and dll-number to 3. + +Version 1.2.0beta4 [June 23, 2001] + Check for missing profile length field in iCCP chunk and free chunk_data + in case of truncated iCCP chunk. + Bumped shared-library number to 3 in makefile.sgi and makefile.sggcc + Bumped dll-number from 2 to 3 in makefile.cygwin + Revised contrib/gregbook/rpng*-x.c to avoid a memory leak and to exit cleanly + if user attempts to run it on an 8-bit display. + Updated contrib/gregbook + Use png_malloc instead of png_zalloc to allocate palette in pngset.c + Updated makefile.ibmc + Added some typecasts to eliminate gcc 3.0 warnings. Changed prototypes + of png_write_oFFS width and height from png_uint_32 to png_int_32. + Updated example.c + Revised prototypes for png_debug_malloc and png_debug_free in pngtest.c + +Version 1.2.0beta5 [August 8, 2001] + Revised contrib/gregbook + Revised makefile.gcmmx + Revised pnggccrd.c to conditionally compile some thread-unsafe code only + when PNG_THREAD_UNSAFE_OK is defined. + Added tests to prevent pngwutil.c from writing a bKGD or tRNS chunk with + value exceeding 2^bit_depth-1 + Revised makefile.sgi and makefile.sggcc + Replaced calls to fprintf(stderr,...) with png_warning() in pnggccrd.c + Removed restriction that do_invert_mono only operate on 1-bit opaque files + +Version 1.2.0 [September 1, 2001] + Changed a png_warning() to png_debug() in pnggccrd.c + Fixed contrib/gregbook/rpng-x.c, rpng2-x.c to avoid crash with XFreeGC(). + +Version 1.2.1beta1 [October 19, 2001] + Revised makefile.std in contrib/pngminus + Include background_1 in png_struct regardless of gamma support. + Revised makefile.netbsd and makefile.macosx, added makefile.darwin. + Revised example.c to provide more details about using row_callback(). + +Version 1.2.1beta2 [October 25, 2001] + Added type cast to each NULL appearing in a function call, except for + WINCE functions. + Added makefile.so9. + +Version 1.2.1beta3 [October 27, 2001] + Removed type casts from all NULLs. + Simplified png_create_struct_2(). + +Version 1.2.1beta4 [November 7, 2001] + Revised png_create_info_struct() and png_creat_struct_2(). + Added error message if png_write_info() was omitted. + Type cast NULLs appearing in function calls when _NO_PROTO or + PNG_TYPECAST_NULL is defined. + +Version 1.2.1rc1 [November 24, 2001] + Type cast NULLs appearing in function calls except when PNG_NO_TYPECAST_NULL + is defined. + Changed typecast of "size" argument to png_size_t in pngmem.c calls to + the user malloc_fn, to agree with the prototype in png.h + Added a pop/push operation to pnggccrd.c, to preserve Eflag (Maxim Sobolev) + Updated makefile.sgi to recognize LIBPATH and INCPATH. + Updated various makefiles so "make clean" does not remove previous major + version of the shared library. + +Version 1.2.1rc2 [December 4, 2001] + Always allocate 256-entry internal palette, hist, and trans arrays, to + avoid out-of-bounds memory reference caused by invalid PNG datastreams. + Added a check for prefix_length > data_length in iCCP chunk handler. + +Version 1.2.1 [December 7, 2001] + None. + +Version 1.2.2beta1 [February 22, 2002] + Fixed a bug with reading the length of iCCP profiles (Larry Reeves). + Revised makefile.linux, makefile.gcmmx, and makefile.sgi to generate + libpng.a, libpng12.so (not libpng.so.3), and libpng12/png.h + Revised makefile.darwin to remove "-undefined suppress" option. + Added checks for gamma and chromaticity values over 21474.83, which exceed + the limit for PNG unsigned 32-bit integers when encoded. + Revised calls to png_create_read_struct() and png_create_write_struct() + for simpler debugging. + Revised png_zalloc() so zlib handles errors (uses PNG_FLAG_MALLOC_NULL_MEM_OK) + +Version 1.2.2beta2 [February 23, 2002] + Check chunk_length and idat_size for invalid (over PNG_MAX_UINT) lengths. + Check for invalid image dimensions in png_get_IHDR. + Added missing "fi;" in the install target of the SGI makefiles. + Added install-static to all makefiles that make shared libraries. + Always do gamma compensation when image is partially transparent. + +Version 1.2.2beta3 [March 7, 2002] + Compute background.gray and background_1.gray even when color_type is RGB + in case image gets reduced to gray later. + Modified shared-library makefiles to install pkgconfig/libpngNN.pc. + Export (with PNGAPI) png_zalloc, png_zfree, and png_handle_as_unknown + Removed unused png_write_destroy_info prototype from png.h + Eliminated incorrect use of width_mmx from pnggccrd.c in pixel_bytes == 8 case + Added install-shared target to all makefiles that make shared libraries. + Stopped a double free of palette, hist, and trans when not using free_me. + Added makefile.32sunu for Sun Ultra 32 and makefile.64sunu for Sun Ultra 64. + +Version 1.2.2beta4 [March 8, 2002] + Compute background.gray and background_1.gray even when color_type is RGB + in case image gets reduced to gray later (Jason Summers). + Relocated a misplaced /bin/rm in the "install-shared" makefile targets + Added PNG_1_0_X macro which can be used to build a 1.0.x-compatible library. + +Version 1.2.2beta5 [March 26, 2002] + Added missing PNGAPI to several function definitions. + Check for invalid bit_depth or color_type in png_get_IHDR(), and + check for missing PLTE or IHDR in png_push_read_chunk() (Matthias Clasen). + Revised iTXt support to accept NULL for lang and lang_key. + Compute gamma for color components of background even when color_type is gray. + Changed "()" to "{}" in scripts/libpng.pc.in. + Revised makefiles to put png.h and pngconf.h only in $prefix/include/libpngNN + Revised makefiles to make symlink to libpng.so.NN in addition to libpngNN.so + +Version 1.2.2beta6 [March 31, 2002] + +Version 1.0.13beta1 [March 31, 2002] + Prevent png_zalloc() from trying to memset memory that it failed to acquire. + Add typecasts of PNG_MAX_UINT in pngset_cHRM_fixed() (Matt Holgate). + Ensure that the right function (user or default) is used to free the + png_struct after an error in png_create_read_struct_2(). + +Version 1.2.2rc1 [April 7, 2002] + +Version 1.0.13rc1 [April 7, 2002] + Save the ebx register in pnggccrd.c (Sami Farin) + Add "mem_ptr = png_ptr->mem_ptr" in png_destroy_write_struct() (Paul Gardner). + Updated makefiles to put headers in include/libpng and remove old include/*.h. + +Version 1.2.2 [April 15, 2002] + +Version 1.0.13 [April 15, 2002] + Revised description of png_set_filter() in libpng.3/libpng.txt. + Revised makefile.netbsd and added makefile.neNNbsd and makefile.freebsd + +Version 1.0.13patch01 [April 17, 2002] + +Version 1.2.2patch01 [April 17, 2002] + Changed ${PNGMAJ}.${PNGVER} bug to ${PNGVER} in makefile.sgi and + makefile.sggcc + Fixed VER -> PNGVER typo in makefile.macosx and added install-static to + install + Added install: target to makefile.32sunu and makefile.64sunu + +Version 1.0.13patch03 [April 18, 2002] + +Version 1.2.2patch03 [April 18, 2002] + Revised 15 makefiles to link libpng.a to libpngNN.a and the include libpng + subdirectory to libpngNN subdirectory without the full pathname. + Moved generation of libpng.pc from "install" to "all" in 15 makefiles. + +Version 1.2.3rc1 [April 28, 2002] + Added install-man target to 15 makefiles (Dimitri Papadopolous-Orfanos). + Added $(DESTDIR) feature to 24 makefiles (Tim Mooney) + Fixed bug with $prefix, should be $(prefix) in makefile.hpux. + Updated cygwin-specific portion of pngconf.h and revised makefile.cygwin + Added a link from libpngNN.pc to libpng.pc in 15 makefiles. + Added links from include/libpngNN/*.h to include/*.h in 24 makefiles. + Revised makefile.darwin to make relative links without full pathname. + Added setjmp() at the end of png_create_*_struct_2() in case user forgets + to put one in their application. + Restored png_zalloc() and png_zfree() prototypes to version 1.2.1 and + removed them from module definition files. + +Version 1.2.3rc2 [May 1, 2002] + Fixed bug in reporting number of channels in pngget.c and pngset.c, + that was introduced in version 1.2.2beta5. + Exported png_zalloc(), png_zfree(), png_default_read(), png_default_write(), + png_default_flush(), and png_push_fill_buffer() and included them in + module definition files. + Added "libpng.pc" dependency to the "install-shared" target in 15 makefiles. + +Version 1.2.3rc3 [May 1, 2002] + Revised prototype for png_default_flush() + Remove old libpng.pc and libpngNN.pc before installing new ones. + +Version 1.2.3rc4 [May 2, 2002] + Typos in *.def files (png_default_read|write -> png_default_read|write_data) + In makefiles, changed rm libpng.NN.pc to rm libpngNN.pc + Added libpng-config and libpngNN-config and modified makefiles to install + them. + Changed $(MANPATH) to $(DESTDIR)$(MANPATH) in makefiles + Added "Win32 DLL VB" configuration to projects/msvc/libpng.dsp + +Version 1.2.3rc5 [May 11, 2002] + Changed "error" and "message" in prototypes to "error_message" and + "warning_message" to avoid namespace conflict. + Revised 15 makefiles to build libpng-config from libpng-config-*.in + Once more restored png_zalloc and png_zfree to regular nonexported form. + Restored png_default_read|write_data, png_default_flush, png_read_fill_buffer + to nonexported form, but with PNGAPI, and removed them from module def + files. + +Version 1.2.3rc6 [May 14, 2002] + Removed "PNGAPI" from png_zalloc() and png_zfree() in png.c + Changed "Gz" to "Gd" in projects/msvc/libpng.dsp and zlib.dsp. + Removed leftover libpng-config "sed" script from four makefiles. + Revised libpng-config creating script in 16 makefiles. + +Version 1.2.3 [May 22, 2002] + Revised libpng-config target in makefile.cygwin. + Removed description of png_set_mem_fn() from documentation. + Revised makefile.freebsd. + Minor cosmetic changes to 15 makefiles, e.g., $(DI) = $(DESTDIR)/$(INCDIR). + Revised projects/msvc/README.txt + Changed -lpng to -lpngNN in LDFLAGS in several makefiles. + +Version 1.2.4beta1 [May 24, 2002] + Added libpng.pc and libpng-config to "all:" target in 16 makefiles. + Fixed bug in 16 makefiles: $(DESTDIR)/$(LIBPATH) to $(DESTDIR)$(LIBPATH) + Added missing "\" before closing double quote in makefile.gcmmx. + Plugged various memory leaks; added png_malloc_warn() and png_set_text_2() + functions. + +Version 1.2.4beta2 [June 25, 2002] + Plugged memory leak of png_ptr->current_text (Matt Holgate). + Check for buffer overflow before reading CRC in pngpread.c (Warwick Allison) + Added -soname to the loader flags in makefile.dec, makefile.sgi, and + makefile.sggcc. + Added "test-installed" target to makefile.linux, makefile.gcmmx, + makefile.sgi, and makefile.sggcc. + +Version 1.2.4beta3 [June 28, 2002] + Plugged memory leak of row_buf in pngtest.c when there is a png_error(). + Detect buffer overflow in pngpread.c when IDAT is corrupted with extra data. + Added "test-installed" target to makefile.32sunu, makefile.64sunu, + makefile.beos, makefile.darwin, makefile.dec, makefile.macosx, + makefile.solaris, makefile.hpux, makefile.hpgcc, and makefile.so9. + +Version 1.2.4rc1 and 1.0.14rc1 [July 2, 2002] + Added "test-installed" target to makefile.cygwin and makefile.sco. + Revised pnggccrd.c to be able to back out version 1.0.x via PNG_1_0_X macro. + +Version 1.2.4 and 1.0.14 [July 8, 2002] + Changed png_warning() to png_error() when width is too large to process. + +Version 1.2.4patch01 [July 20, 2002] + Revised makefile.cygwin to use DLL number 12 instead of 13. + +Version 1.2.5beta1 [August 6, 2002] + Added code to contrib/gregbook/readpng2.c to ignore unused chunks. + Replaced toucan.png in contrib/gregbook (it has been corrupt since 1.0.11) + Removed some stray *.o files from contrib/gregbook. + Changed png_error() to png_warning() about "Too much data" in pngpread.c + and about "Extra compressed data" in pngrutil.c. + Prevent png_ptr->pass from exceeding 7 in png_push_finish_row(). + Updated makefile.hpgcc + Updated png.c and pnggccrd.c handling of return from png_mmx_support() + +Version 1.2.5beta2 [August 15, 2002] + Only issue png_warning() about "Too much data" in pngpread.c when avail_in + is nonzero. + Updated makefiles to install a separate libpng.so.3 with its own rpath. + +Version 1.2.5rc1 and 1.0.15rc1 [August 24, 2002] + Revised makefiles to not remove previous minor versions of shared libraries. + +Version 1.2.5rc2 and 1.0.15rc2 [September 16, 2002] + Revised 13 makefiles to remove "-lz" and "-L$(ZLIBLIB)", etc., from shared + library loader directive. + Added missing "$OBJSDLL" line to makefile.gcmmx. + Added missing "; fi" to makefile.32sunu. + +Version 1.2.5rc3 and 1.0.15rc3 [September 18, 2002] + Revised libpng-config script. + +Version 1.2.5 and 1.0.15 [October 3, 2002] + Revised makefile.macosx, makefile.darwin, makefile.hpgcc, and makefile.hpux, + and makefile.aix. + Relocated two misplaced PNGAPI lines in pngtest.c + +Version 1.2.6beta1 [October 22, 2002] + Commented out warning about uninitialized mmx_support in pnggccrd.c. + Changed "IBMCPP__" flag to "__IBMCPP__" in pngconf.h. + Relocated two more misplaced PNGAPI lines in pngtest.c + Fixed memory overrun bug in png_do_read_filler() with 16-bit datastreams, + introduced in version 1.0.2. + Revised makefile.macosx, makefile.dec, makefile.aix, and makefile.32sunu. + +Version 1.2.6beta2 [November 1, 2002] + Added libpng-config "--ldopts" output. + Added "AR=ar" and "ARFLAGS=rc" and changed "ar rc" to "$(AR) $(ARFLAGS)" + in makefiles. + +Version 1.2.6beta3 [July 18, 2004] + Reverted makefile changes from version 1.2.6beta2 and some of the changes + from version 1.2.6beta1; these will be postponed until version 1.2.7. + Version 1.2.6 is going to be a simple bugfix release. + Changed the one instance of "ln -sf" to "ln -f -s" in each Sun makefile. + Fixed potential overrun in pngerror.c by using strncpy instead of memcpy. + Added "#!/bin/sh" at the top of configure, for recognition of the + 'x' flag under Cygwin (Cosmin). + Optimized vacuous tests that silence compiler warnings, in png.c (Cosmin). + Added support for PNG_USER_CONFIG, in pngconf.h (Cosmin). + Fixed the special memory handler for Borland C under DOS, in pngmem.c + (Cosmin). + Removed some spurious assignments in pngrutil.c (Cosmin). + Replaced 65536 with 65536L, and 0xffff with 0xffffL, to silence warnings + on 16-bit platforms (Cosmin). + Enclosed shift op expressions in parentheses, to silence warnings (Cosmin). + Used proper type png_fixed_point, to avoid problems on 16-bit platforms, + in png_handle_sRGB() (Cosmin). + Added compression_type to png_struct, and optimized the window size + inside the deflate stream (Cosmin). + Fixed definition of isnonalpha(), in pngerror.c and pngrutil.c (Cosmin). + Fixed handling of unknown chunks that come after IDAT (Cosmin). + Allowed png_error() and png_warning() to work even if png_ptr == NULL + (Cosmin). + Replaced row_info->rowbytes with row_bytes in png_write_find_filter() + (Cosmin). + Fixed definition of PNG_LIBPNG_VER_DLLNUM (Simon-Pierre). + Used PNG_LIBPNG_VER and PNG_LIBPNG_VER_STRING instead of the hardcoded + values in png.c (Simon-Pierre, Cosmin). + Initialized png_libpng_ver[] with PNG_LIBPNG_VER_STRING (Simon-Pierre). + Replaced PNG_LIBPNG_VER_MAJOR with PNG_LIBPNG_VER_DLLNUM in png.rc + (Simon-Pierre). + Moved the definition of PNG_HEADER_VERSION_STRING near the definitions + of the other PNG_LIBPNG_VER_... symbols in png.h (Cosmin). + Relocated #ifndef PNGAPI guards in pngconf.h (Simon-Pierre, Cosmin). + Updated scripts/makefile.vc(a)win32 (Cosmin). + Updated the MSVC project (Simon-Pierre, Cosmin). + Updated the Borland C++ Builder project (Cosmin). + Avoided access to asm_flags in pngvcrd.c, if PNG_1_0_X is defined (Cosmin). + Commented out warning about uninitialized mmx_support in pngvcrd.c (Cosmin). + Removed scripts/makefile.bd32 and scripts/pngdef.pas (Cosmin). + Added extra guard around inclusion of Turbo C memory headers, in pngconf.h + (Cosmin). + Renamed projects/msvc/ to projects/visualc6/, and projects/borland/ to + projects/cbuilder5/ (Cosmin). + Moved projects/visualc6/png32ms.def to scripts/pngw32.def, + and projects/visualc6/png.rc to scripts/pngw32.rc (Cosmin). + Added projects/visualc6/pngtest.dsp; removed contrib/msvctest/ (Cosmin). + Changed line endings to DOS style in cbuilder5 and visualc6 files, even + in the tar.* distributions (Cosmin). + Updated contrib/visupng/VisualPng.dsp (Cosmin). + Updated contrib/visupng/cexcept.h to version 2.0.0 (Cosmin). + Added a separate distribution with "configure" and supporting files (Junichi). + +Version 1.2.6beta4 [July 28, 2004] + Added user ability to change png_size_t via a PNG_SIZE_T macro. + Added png_sizeof() and png_convert_size() functions. + Added PNG_SIZE_MAX (maximum value of a png_size_t variable. + Added check in png_malloc_default() for (size_t)size != (png_uint_32)size + which would indicate an overflow. + Changed sPLT failure action from png_error to png_warning and abandon chunk. + Changed sCAL and iCCP failures from png_error to png_warning and abandon. + Added png_get_uint_31(png_ptr, buf) function. + Added PNG_UINT_32_MAX macro. + Renamed PNG_MAX_UINT to PNG_UINT_31_MAX. + Made png_zalloc() issue a png_warning and return NULL on potential + overflow. + Turn on PNG_NO_ZALLOC_ZERO by default in version 1.2.x + Revised "clobber list" in pnggccrd.c so it will compile under gcc-3.4. + Revised Borland portion of png_malloc() to return NULL or issue + png_error() according to setting of PNG_FLAG_MALLOC_NULL_MEM_OK. + Added PNG_NO_SEQUENTIAL_READ_SUPPORTED macro to conditionally remove + sequential read support. + Added some "#if PNG_WRITE_SUPPORTED" blocks. + Added #ifdef to remove some redundancy in png_malloc_default(). + Use png_malloc instead of png_zalloc to allocate the pallete. + +Version 1.0.16rc1 and 1.2.6rc1 [August 4, 2004] + Fixed buffer overflow vulnerability (CVE-2004-0597) in png_handle_tRNS(). + Fixed NULL dereference vulnerability (CVE-2004-0598) in png_handle_iCCP(). + Fixed integer overflow vulnerability (CVE-2004-0599) in png_read_png(). + Fixed some harmless bugs in png_handle_sBIT, etc, that would cause + duplicate chunk types to go undetected. + Fixed some timestamps in the -config version + Rearranged order of processing of color types in png_handle_tRNS(). + Added ROWBYTES macro to calculate rowbytes without integer overflow. + Updated makefile.darwin and removed makefile.macosx from scripts directory. + Imposed default one million column, one-million row limits on the image + dimensions, and added png_set_user_limits() function to override them. + Revised use of PNG_SET_USER_LIMITS_SUPPORTED macro. + Fixed wrong cast of returns from png_get_user_width|height_max(). + Changed some "keep the compiler happy" from empty statements to returns, + Revised libpng.txt to remove 1.2.x stuff from the 1.0.x distribution + +Version 1.0.16rc2 and 1.2.6rc2 [August 7, 2004] + Revised makefile.darwin and makefile.solaris. Removed makefile.macosx. + Revised pngtest's png_debug_malloc() to use png_malloc() instead of + png_malloc_default() which is not supposed to be exported. + Fixed off-by-one error in one of the conversions to PNG_ROWBYTES() in + pngpread.c. Bug was introduced in 1.2.6rc1. + Fixed bug in RGB to RGBX transformation introduced in 1.2.6rc1. + Fixed old bug in RGB to Gray transformation. + Fixed problem with 64-bit compilers by casting arguments to abs() + to png_int_32. + Changed "ln -sf" to "ln -f -s" in three makefiles (solaris, sco, so9). + Changed "HANDLE_CHUNK_*" to "PNG_HANDLE_CHUNK_*" (Cosmin) + Added "-@/bin/rm -f $(DL)/$(LIBNAME).so.$(PNGMAJ)" to 15 *NIX makefiles. + Added code to update the row_info->colortype in png_do_read_filler() (MSB). + +Version 1.0.16rc3 and 1.2.6rc3 [August 9, 2004] + Eliminated use of "abs()" in testing cHRM and gAMA values, to avoid + trouble with some 64-bit compilers. Created PNG_OUT_OF_RANGE() macro. + Revised documentation of png_set_keep_unknown_chunks(). + Check handle_as_unknown status in pngpread.c, as in pngread.c previously. + Moved "PNG_HANDLE_CHUNK_*" macros out of PNG_INTERNAL section of png.h + Added "rim" definitions for CONST4 and CONST6 in pnggccrd.c + +Version 1.0.16rc4 and 1.2.6rc4 [August 10, 2004] + Fixed mistake in pngtest.c introduced in 1.2.6rc2 (declaration of + "pinfo" was out of place). + +Version 1.0.16rc5 and 1.2.6rc5 [August 10, 2004] + Moved "PNG_HANDLE_CHUNK_*" macros out of PNG_ASSEMBLER_CODE_SUPPORTED + section of png.h where they were inadvertently placed in version rc3. + +Version 1.2.6 and 1.0.16 [August 15, 2004] + Revised pngtest so memory allocation testing is only done when PNG_DEBUG==1. + +Version 1.2.7beta1 [August 26, 2004] + Removed unused pngasmrd.h file. + Removed references to uu.net for archived files. Added references to + PNG Spec (second edition) and the PNG ISO/IEC Standard. + Added "test-dd" target in 15 makefiles, to run pngtest in DESTDIR. + Fixed bug with "optimized window size" in the IDAT datastream, that + causes libpng to write PNG files with incorrect zlib header bytes. + +Version 1.2.7beta2 [August 28, 2004] + Fixed bug with sCAL chunk and big-endian machines (David Munro). + Undid new code added in 1.2.6rc2 to update the color_type in + png_set_filler(). + Added png_set_add_alpha() that updates color type. + +Version 1.0.17rc1 and 1.2.7rc1 [September 4, 2004] + Revised png_set_strip_filler() to not remove alpha if color_type has alpha. + +Version 1.2.7 and 1.0.17 [September 12, 2004] + Added makefile.hp64 + Changed projects/msvc/png32ms.def to scripts/png32ms.def in makefile.cygwin + +Version 1.2.8beta1 [November 1, 2004] + Fixed bug in png_text_compress() that would fail to complete a large block. + Fixed bug, introduced in libpng-1.2.7, that overruns a buffer during + strip alpha operation in png_do_strip_filler(). + Added PNG_1_2_X definition in pngconf.h + Use #ifdef to comment out png_info_init in png.c and png_read_init in + pngread.c (as of 1.3.0) + +Version 1.2.8beta2 [November 2, 2004] + Reduce color_type to a nonalpha type after strip alpha operation in + png_do_strip_filler(). + +Version 1.2.8beta3 [November 3, 2004] + Revised definitions of PNG_MAX_UINT_32, PNG_MAX_SIZE, and PNG_MAXSUM + +Version 1.2.8beta4 [November 12, 2004] + Fixed (again) definition of PNG_LIBPNG_VER_DLLNUM in png.h (Cosmin). + Added PNG_LIBPNG_BUILD_PRIVATE in png.h (Cosmin). + Set png_ptr->zstream.data_type to Z_BINARY, to avoid unnecessary detection + of data type in deflate (Cosmin). + Deprecated but continue to support SPECIALBUILD and PRIVATEBUILD in favor of + PNG_LIBPNG_BUILD_SPECIAL_STRING and PNG_LIBPNG_BUILD_PRIVATE_STRING. + +Version 1.2.8beta5 [November 20, 2004] + Use png_ptr->flags instead of png_ptr->transformations to pass + PNG_STRIP_ALPHA info to png_do_strip_filler(), to preserve ABI + compatibility. + Revised handling of SPECIALBUILD, PRIVATEBUILD, + PNG_LIBPNG_BUILD_SPECIAL_STRING and PNG_LIBPNG_BUILD_PRIVATE_STRING. + +Version 1.2.8rc1 [November 24, 2004] + Moved handling of BUILD macros from pngconf.h to png.h + Added definition of PNG_LIBPNG_BASE_TYPE in png.h, inadvertently + omitted from beta5. + Revised scripts/pngw32.rc + Despammed mailing addresses by masking "@" with "at". + Inadvertently installed a supposedly faster test version of pngrutil.c + +Version 1.2.8rc2 [November 26, 2004] + Added two missing "\" in png.h + Change tests in pngread.c and pngpread.c to + if (png_ptr->transformations || (png_ptr->flags&PNG_FLAG_STRIP_ALPHA)) + png_do_read_transformations(png_ptr); + +Version 1.2.8rc3 [November 28, 2004] + Reverted pngrutil.c to version libpng-1.2.8beta5. + Added scripts/makefile.elf with supporting code in pngconf.h for symbol + versioning (John Bowler). + +Version 1.2.8rc4 [November 29, 2004] + Added projects/visualc7 (Simon-pierre). + +Version 1.2.8rc5 [November 29, 2004] + Fixed new typo in scripts/pngw32.rc + +Version 1.2.8 [December 3, 2004] + Removed projects/visualc7, added projects/visualc71. + +Version 1.2.9beta1 [February 21, 2006] + Initialized some structure members in pngwutil.c to avoid gcc-4.0.0 complaints + Revised man page and libpng.txt to make it clear that one should not call + png_read_end or png_write_end after png_read_png or png_write_png. + Updated references to png-mng-implement mailing list. + Fixed an incorrect typecast in pngrutil.c + Added PNG_NO_READ_SUPPORTED conditional for making a write-only library. + Added PNG_NO_WRITE_INTERLACING_SUPPORTED conditional. + Optimized alpha-inversion loops in pngwtran.c + Moved test for nonzero gamma outside of png_build_gamma_table() in pngrtran.c + Make sure num_trans is <= 256 before copying data in png_set_tRNS(). + Make sure num_palette is <= 256 before copying data in png_set_PLTE(). + Interchanged order of write_swap_alpha and write_invert_alpha transforms. + Added parentheses in the definition of PNG_LIBPNG_BUILD_TYPE (Cosmin). + Optimized zlib window flag (CINFO) in contrib/pngsuite/*.png (Cosmin). + Updated scripts/makefile.bc32 for Borland C++ 5.6 (Cosmin). + Exported png_get_uint_32, png_save_uint_32, png_get_uint_16, png_save_uint_16, + png_get_int_32, png_save_int_32, png_get_uint_31 (Cosmin). + Added type cast (png_byte) in png_write_sCAL() (Cosmin). + Fixed scripts/makefile.cygwin (Christian Biesinger, Cosmin). + Default iTXt support was inadvertently enabled. + +Version 1.2.9beta2 [February 21, 2006] + Check for png_rgb_to_gray and png_gray_to_rgb read transformations before + checking for png_read_dither in pngrtran.c + Revised checking of chromaticity limits to accommodate extended RGB + colorspace (John Denker). + Changed line endings in some of the project files to CRLF, even in the + "Unix" tar distributions (Cosmin). + Made png_get_int_32 and png_save_int_32 always available (Cosmin). + Updated scripts/pngos2.def, scripts/pngw32.def and projects/wince/png32ce.def + with the newly exported functions. + Eliminated distributions without the "configure" script. + Updated INSTALL instructions. + +Version 1.2.9beta3 [February 24, 2006] + Fixed CRCRLF line endings in contrib/visupng/VisualPng.dsp + Made libpng.pc respect EXEC_PREFIX (D. P. Kreil, J. Bowler) + Removed reference to pngasmrd.h from Makefile.am + Renamed CHANGES to ChangeLog. + Renamed LICENSE to COPYING. + Renamed ANNOUNCE to NEWS. + Created AUTHORS file. + +Version 1.2.9beta4 [March 3, 2006] + Changed definition of PKGCONFIG from $prefix/lib to $libdir in configure.ac + Reverted to filenames LICENSE and ANNOUNCE; removed AUTHORS and COPYING. + Removed newline from the end of some error and warning messages. + Removed test for sqrt() from configure.ac and configure. + Made swap tables in pngtrans.c PNG_CONST (Carlo Bramix). + Disabled default iTXt support that was inadvertently enabled in + libpng-1.2.9beta1. + Added "OS2" to list of systems that don't need underscores, in pnggccrd.c + Removed libpng version and date from *.c files. + +Version 1.2.9beta5 [March 4, 2006] + Removed trailing blanks from source files. + Put version and date of latest change in each source file, and changed + copyright year accordingly. + More cleanup of configure.ac, Makefile.am, and associated scripts. + Restored scripts/makefile.elf which was inadvertently deleted. + +Version 1.2.9beta6 [March 6, 2006] + Fixed typo (RELEASE) in configuration files. + +Version 1.2.9beta7 [March 7, 2006] + Removed libpng.vers and libpng.sym from libpng12_la_SOURCES in Makefile.am + Fixed inconsistent #ifdef's around png_sig_bytes() and png_set_sCAL_s() + in png.h. + Updated makefile.elf as suggested by debian. + Made cosmetic changes to some makefiles, adding LN_SF and other macros. + Made some makefiles accept "exec_prefix". + +Version 1.2.9beta8 [March 9, 2006] + Fixed some "#if defined (..." which should be "#if defined(..." + Bug introduced in libpng-1.2.8. + Fixed inconsistency in definition of png_default_read_data() + Restored blank that was lost from makefile.sggcc "clean" target in beta7. + Revised calculation of "current" and "major" for irix in ltmain.sh + Changed "mkdir" to "MKDIR_P" in some makefiles. + Separated PNG_EXPAND and PNG_EXPAND_tRNS. + Added png_set_expand_gray_1_2_4_to_8() and deprecated + png_set_gray_1_2_4_to_8() which also expands tRNS to alpha. + +Version 1.2.9beta9 [March 10, 2006] + Include "config.h" in pngconf.h when available. + Added some checks for NULL png_ptr or NULL info_ptr (timeless) + +Version 1.2.9beta10 [March 20, 2006] + Removed extra CR from contrib/visualpng/VisualPng.dsw (Cosmin) + Made pnggccrd.c PIC-compliant (Christian Aichinger). + Added makefile.mingw (Wolfgang Glas). + Revised pngconf.h MMX checking. + +Version 1.2.9beta11 [March 22, 2006] + Fixed out-of-order declaration in pngwrite.c that was introduced in beta9 + Simplified some makefiles by using LIBSO, LIBSOMAJ, and LIBSOVER macros. + +Version 1.2.9rc1 [March 31, 2006] + Defined PNG_USER_PRIVATEBUILD when including "pngusr.h" (Cosmin). + Removed nonsensical assertion check from pngtest.c (Cosmin). + +Version 1.2.9 [April 14, 2006] + Revised makefile.beos and added "none" selector in ltmain.sh + +Version 1.2.10beta1 [April 15, 2006] + Renamed "config.h" to "png_conf.h" and revised Makefile.am to add + -DPNG_BUILDING_LIBPNG to compile directive, and modified pngconf.h + to include png_conf.h only when PNG_BUILDING_LIBPNG is defined. + +Version 1.2.10beta2 [April 15, 2006] + Manually updated Makefile.in and configure. Changed png_conf.h.in + back to config.h. + +Version 1.2.10beta3 [April 15, 2006] + Change png_conf.h back to config.h in pngconf.h. + +Version 1.2.10beta4 [April 16, 2006] + Change PNG_BUILDING_LIBPNG to PNG_CONFIGURE_LIBPNG in config/Makefile*. + +Version 1.2.10beta5 [April 16, 2006] + Added a configure check for compiling assembler code in pnggccrd.c + +Version 1.2.10beta6 [April 17, 2006] + Revised the configure check for pnggccrd.c + Moved -DPNG_CONFIGURE_LIBPNG into @LIBPNG_DEFINES@ + Added @LIBPNG_DEFINES@ to arguments when building libpng.sym + +Version 1.2.10beta7 [April 18, 2006] + Change "exec_prefix=$prefix" to "exec_prefix=$(prefix)" in makefiles. + +Version 1.2.10rc1 [April 19, 2006] + Ensure pngconf.h doesn't define both PNG_USE_PNGGCCRD and PNG_USE_PNGVCRD + Fixed "LN_FS" typo in makefile.sco and makefile.solaris. + +Version 1.2.10rc2 [April 20, 2006] + Added a backslash between -DPNG_CONFIGURE_LIBPNG and -DPNG_NO_ASSEMBLER_CODE + in configure.ac and configure + Made the configure warning about versioned symbols less arrogant. + +Version 1.2.10rc3 [April 21, 2006] + Added a note in libpng.txt that png_set_sig_bytes(8) can be used when + writing an embedded PNG without the 8-byte signature. + Revised makefiles and configure to avoid making links to libpng.so.* + +Version 1.2.10 [April 23, 2006] + Reverted configure to "rc2" state. + +Version 1.2.11beta1 [May 31, 2006] + scripts/libpng.pc.in contained "configure" style version info and would + not work with makefiles. + The shared-library makefiles were linking to libpng.so.0 instead of + libpng.so.3 compatibility as the library. + +Version 1.2.11beta2 [June 2, 2006] + Increased sprintf buffer from 50 to 52 chars in pngrutil.c to avoid + buffer overflow. + Fixed bug in example.c (png_set_palette_rgb -> png_set_palette_to_rgb) + +Version 1.2.11beta3 [June 5, 2006] + Prepended "#! /bin/sh" to ltmail.sh and contrib/pngminus/*.sh (Cosmin). + Removed the accidental leftover Makefile.in~ (Cosmin). + Avoided potential buffer overflow and optimized buffer in + png_write_sCAL(), png_write_sCAL_s() (Cosmin). + Removed the include directories and libraries from CFLAGS and LDFLAGS + in scripts/makefile.gcc (Nelson A. de Oliveira, Cosmin). + +Version 1.2.11beta4 [June 6, 2006] + Allow zero-length IDAT chunks after the entire zlib datastream, but not + after another intervening chunk type. + +Version 1.0.19rc1, 1.2.11rc1 [June 13, 2006] + Deleted extraneous square brackets from [config.h] in configure.ac + +Version 1.0.19rc2, 1.2.11rc2 [June 14, 2006] + Added prototypes for PNG_INCH_CONVERSIONS functions to png.h + Revised INSTALL and autogen.sh + Fixed typo in several makefiles (-W1 should be -Wl) + Added typedef for png_int_32 and png_uint_32 on 64-bit systems. + +Version 1.0.19rc3, 1.2.11rc3 [June 15, 2006] + Removed the new typedefs for 64-bit systems (delay until version 1.4.0) + Added one zero element to png_gamma_shift[] array in pngrtran.c to avoid + reading out of bounds. + +Version 1.0.19rc4, 1.2.11rc4 [June 15, 2006] + Really removed the new typedefs for 64-bit systems. + +Version 1.0.19rc5, 1.2.11rc5 [June 22, 2006] + Removed png_sig_bytes entry from scripts/pngw32.def + +Version 1.0.19, 1.2.11 [June 26, 2006] + None. + +Version 1.0.20, 1.2.12 [June 27, 2006] + Really increased sprintf buffer from 50 to 52 chars in pngrutil.c to avoid + buffer overflow. + +Version 1.2.13beta1 [October 2, 2006] + Removed AC_FUNC_MALLOC from configure.ac + Work around Intel-Mac compiler bug by setting PNG_NO_MMX_CODE in pngconf.h + Change "logical" to "bitwise" throughout documentation. + Detect and fix attempt to write wrong iCCP profile length (CVE-2006-7244) + +Version 1.0.21, 1.2.13 [November 14, 2006] + Fix potential buffer overflow in sPLT chunk handler. + Fix Makefile.am to not try to link to noexistent files. + Check all exported functions for NULL png_ptr. + +Version 1.2.14beta1 [November 17, 2006] + Relocated three misplaced tests for NULL png_ptr. + Built Makefile.in with automake-1.9.6 instead of 1.9.2. + Build configure with autoconf-2.60 instead of 2.59 + +Version 1.2.14beta2 [November 17, 2006] + Added some typecasts in png_zalloc(). + +Version 1.2.14rc1 [November 20, 2006] + Changed "strtod" to "png_strtod" in pngrutil.c + +Version 1.0.22, 1.2.14 [November 27, 2006] + Added missing "$(srcdir)" in Makefile.am and Makefile.in + +Version 1.2.15beta1 [December 3, 2006] + Generated configure with autoconf-2.61 instead of 2.60 + Revised configure.ac to update libpng.pc and libpng-config. + +Version 1.2.15beta2 [December 3, 2006] + Always export MMX asm functions, just stubs if not building pnggccrd.c + +Version 1.2.15beta3 [December 4, 2006] + Add "png_bytep" typecast to profile while calculating length in pngwutil.c + +Version 1.2.15beta4 [December 7, 2006] + Added scripts/CMakeLists.txt + Changed PNG_NO_ASSEMBLER_CODE to PNG_NO_MMX_CODE in scripts, like 1.4.0beta + +Version 1.2.15beta5 [December 7, 2006] + Changed some instances of PNG_ASSEMBLER_* to PNG_MMX_* in pnggccrd.c + Revised scripts/CMakeLists.txt + +Version 1.2.15beta6 [December 13, 2006] + Revised scripts/CMakeLists.txt and configure.ac + +Version 1.2.15rc1 [December 18, 2006] + Revised scripts/CMakeLists.txt + +Version 1.2.15rc2 [December 21, 2006] + Added conditional #undef jmpbuf in pngtest.c to undo #define in AIX headers. + Added scripts/makefile.nommx + +Version 1.2.15rc3 [December 25, 2006] + Fixed shared library numbering error that was introduced in 1.2.15beta6. + +Version 1.2.15rc4 [December 27, 2006] + Fixed handling of rgb_to_gray when png_ptr->color.gray isn't set. + +Version 1.2.15rc5 [December 31, 2006] + Revised handling of rgb_to_gray. + +Version 1.2.15 [January 5, 2007] + Added some (unsigned long) typecasts in pngtest.c to avoid printing errors. + +Version 1.2.16beta1 [January 6, 2007] + Fix bugs in makefile.nommx + +Version 1.2.16beta2 [January 16, 2007] + Revised scripts/CMakeLists.txt + +Version 1.2.16 [January 31, 2007] + No changes. + +Version 1.2.17beta1 [March 6, 2007] + Revised scripts/CMakeLists.txt to install both shared and static libraries. + Deleted a redundant line from pngset.c. + +Version 1.2.17beta2 [April 26, 2007] + Relocated misplaced test for png_ptr == NULL in pngpread.c + Change "==" to "&" for testing PNG_RGB_TO_GRAY_ERR & PNG_RGB_TO_GRAY_WARN + flags. + Changed remaining instances of PNG_ASSEMBLER_* to PNG_MMX_* + Added pngerror() when write_IHDR fails in deflateInit2(). + Added "const" to some array declarations. + Mention examples of libpng usage in the libpng*.txt and libpng.3 documents. + +Version 1.2.17rc1 [May 4, 2007] + No changes. + +Version 1.2.17rc2 [May 8, 2007] + Moved several PNG_HAVE_* macros out of PNG_INTERNAL because applications + calling set_unknown_chunk_location() need them. + Changed transformation flag from PNG_EXPAND_tRNS to PNG_EXPAND in + png_set_expand_gray_1_2_4_to_8(). + Added png_ptr->unknown_chunk to hold working unknown chunk data, so it + can be free'ed in case of error. Revised unknown chunk handling in + pngrutil.c and pngpread.c to use this structure. + +Version 1.2.17rc3 [May 8, 2007] + Revised symbol-handling in configure script. + +Version 1.2.17rc4 [May 10, 2007] + Revised unknown chunk handling to avoid storing unknown critical chunks. + +Version 1.0.25 [May 15, 2007] +Version 1.2.17 [May 15, 2007] + Added "png_ptr->num_trans=0" before error return in png_handle_tRNS, + to eliminate a vulnerability (CVE-2007-2445, CERT VU#684664) + +Version 1.0.26 [May 15, 2007] +Version 1.2.18 [May 15, 2007] + Reverted the libpng-1.2.17rc3 change to symbol-handling in configure script + +Version 1.2.19beta1 [May 18, 2007] + Changed "const static" to "static PNG_CONST" everywhere, mostly undoing + change of libpng-1.2.17beta2. Changed other "const" to "PNG_CONST" + Changed some handling of unused parameters, to avoid compiler warnings. + "if (unused == NULL) return;" becomes "unused = unused". + +Version 1.2.19beta2 [May 18, 2007] + Only use the valid bits of tRNS value in png_do_expand() (Brian Cartier) + +Version 1.2.19beta3 [May 19, 2007] + Add some "png_byte" typecasts in png_check_keyword() and write new_key + instead of key in zTXt chunk (Kevin Ryde). + +Version 1.2.19beta4 [May 21, 2007] + Add png_snprintf() function and use it in place of sprint() for improved + defense against buffer overflows. + +Version 1.2.19beta5 [May 21, 2007] + Fixed png_handle_tRNS() to only use the valid bits of tRNS value. + Changed handling of more unused parameters, to avoid compiler warnings. + Removed some PNG_CONST in pngwutil.c to avoid compiler warnings. + +Version 1.2.19beta6 [May 22, 2007] + Added some #ifdef PNG_MMX_CODE_SUPPORTED where needed in pngvcrd.c + Added a special "_MSC_VER" case that defines png_snprintf to _snprintf + +Version 1.2.19beta7 [May 22, 2007] + Squelched png_squelch_warnings() in pnggccrd.c and added + an #ifdef PNG_MMX_CODE_SUPPORTED block around the declarations that caused + the warnings that png_squelch_warnings was squelching. + +Version 1.2.19beta8 [May 22, 2007] + Removed __MMX__ from test in pngconf.h. + +Version 1.2.19beta9 [May 23, 2007] + Made png_squelch_warnings() available via PNG_SQUELCH_WARNINGS macro. + Revised png_squelch_warnings() so it might work. + Updated makefile.sgcc and makefile.solaris; added makefile.solaris-x86. + +Version 1.2.19beta10 [May 24, 2007] + Resquelched png_squelch_warnings(), use "__attribute__((used))" instead. + +Version 1.4.0beta1 [April 20, 2006] + Enabled iTXt support (changes png_struct, thus requires so-number change). + Cleaned up PNG_ASSEMBLER_CODE_SUPPORTED vs PNG_MMX_CODE_SUPPORTED + Eliminated PNG_1_0_X and PNG_1_2_X macros. + Removed deprecated functions png_read_init, png_write_init, png_info_init, + png_permit_empty_plte, png_set_gray_1_2_4_to_8, png_check_sig, and + removed the deprecated macro PNG_MAX_UINT. + Moved "PNG_INTERNAL" parts of png.h and pngconf.h into pngintrn.h + Removed many WIN32_WCE #ifdefs (Cosmin). + Reduced dependency on C-runtime library when on Windows (Simon-Pierre) + Replaced sprintf() with png_sprintf() (Simon-Pierre) + +Version 1.4.0beta2 [April 20, 2006] + Revised makefiles and configure to avoid making links to libpng.so.* + Moved some leftover MMX-related defines from pngconf.h to pngintrn.h + Updated scripts/pngos2.def, pngw32.def, and projects/wince/png32ce.def + +Version 1.4.0beta3 [May 10, 2006] + Updated scripts/pngw32.def to comment out MMX functions. + Added PNG_NO_GET_INT_32 and PNG_NO_SAVE_INT_32 macros. + Scripts/libpng.pc.in contained "configure" style version info and would + not work with makefiles. + Revised pngconf.h and added pngconf.h.in, so makefiles and configure can + pass defines to libpng and applications. + +Version 1.4.0beta4 [May 11, 2006] + Revised configure.ac, Makefile.am, and many of the makefiles to write + their defines in pngconf.h. + +Version 1.4.0beta5 [May 15, 2006] + Added a missing semicolon in Makefile.am and Makefile.in + Deleted extraneous square brackets from configure.ac + +Version 1.4.0beta6 [June 2, 2006] + Increased sprintf buffer from 50 to 52 chars in pngrutil.c to avoid + buffer overflow. + Changed sonum from 0 to 1. + Removed unused prototype for png_check_sig() from png.h + +Version 1.4.0beta7 [June 16, 2006] + Exported png_write_sig (Cosmin). + Optimized buffer in png_handle_cHRM() (Cosmin). + Set pHYs = 2835 x 2835 pixels per meter, and added + sCAL = 0.352778e-3 x 0.352778e-3 meters, in pngtest.png (Cosmin). + Added png_set_benign_errors(), png_benign_error(), png_chunk_benign_error(). + Added typedef for png_int_32 and png_uint_32 on 64-bit systems. + Added "(unsigned long)" typecast on png_uint_32 variables in printf lists. + +Version 1.4.0beta8 [June 22, 2006] + Added demonstration of user chunk support in pngtest.c, to support the + public sTER chunk and a private vpAg chunk. + +Version 1.4.0beta9 [July 3, 2006] + Removed ordinals from scripts/pngw32.def and removed png_info_int and + png_set_gray_1_2_4_to_8 entries. + Inline call of png_get_uint_32() in png_get_uint_31(). + Use png_get_uint_31() to get vpAg width and height in pngtest.c + Removed WINCE and Netware projects. + Removed standalone Y2KINFO file. + +Version 1.4.0beta10 [July 12, 2006] + Eliminated automatic copy of pngconf.h to pngconf.h.in from configure and + some makefiles, because it was not working reliably. Instead, distribute + pngconf.h.in along with pngconf.h and cause configure and some of the + makefiles to update pngconf.h from pngconf.h.in. + Added pngconf.h to DEPENDENCIES in Makefile.am + +Version 1.4.0beta11 [August 19, 2006] + Removed AC_FUNC_MALLOC from configure.ac. + Added a warning when writing iCCP profile with mismatched profile length. + Patched pnggccrd.c to assemble on x86_64 platforms. + Moved chunk header reading into a separate function png_read_chunk_header() + in pngrutil.c. The chunk header (len+sig) is now serialized in a single + operation (Cosmin). + Implemented support for I/O states. Added png_ptr member io_state, and + functions png_get_io_chunk_name() and png_get_io_state() in pngget.c + (Cosmin). + Added png_get_io_chunk_name and png_get_io_state to scripts/*.def (Cosmin). + Renamed scripts/pngw32.* to scripts/pngwin.* (Cosmin). + Removed the include directories and libraries from CFLAGS and LDFLAGS + in scripts/makefile.gcc (Cosmin). + Used png_save_uint_32() to set vpAg width and height in pngtest.c (Cosmin). + Cast to proper type when getting/setting vpAg units in pngtest.c (Cosmin). + Added pngintrn.h to the Visual C++ projects (Cosmin). + Removed scripts/list (Cosmin). + Updated copyright year in scripts/pngwin.def (Cosmin). + Removed PNG_TYPECAST_NULL and used standard NULL consistently (Cosmin). + Disallowed the user to redefine png_size_t, and enforced a consistent use + of png_size_t across libpng (Cosmin). + Changed the type of png_ptr->rowbytes, PNG_ROWBYTES() and friends + to png_size_t (Cosmin). + Removed png_convert_size() and replaced png_sizeof with sizeof (Cosmin). + Removed some unnecessary type casts (Cosmin). + Changed prototype of png_get_compression_buffer_size() and + png_set_compression_buffer_size() to work with png_size_t instead of + png_uint_32 (Cosmin). + Removed png_memcpy_check() and png_memset_check() (Cosmin). + Fixed a typo (png_byte --> png_bytep) in libpng.3 and libpng.txt (Cosmin). + Clarified that png_zalloc() does not clear the allocated memory, + and png_zalloc() and png_zfree() cannot be PNGAPI (Cosmin). + Renamed png_mem_size_t to png_alloc_size_t, fixed its definition in + pngconf.h, and used it in all memory allocation functions (Cosmin). + Renamed pngintrn.h to pngpriv.h, added a comment at the top of the file + mentioning that the symbols declared in that file are private, and + updated the scripts and the Visual C++ projects accordingly (Cosmin). + Removed circular references between pngconf.h and pngconf.h.in in + scripts/makefile.vc*win32 (Cosmin). + Removing trailing '.' from the warning and error messages (Cosmin). + Added pngdefs.h that is built by makefile or configure, instead of + pngconf.h.in (Glenn). + Detect and fix attempt to write wrong iCCP profile length. + +Version 1.4.0beta12 [October 19, 2006] + Changed "logical" to "bitwise" in the documentation. + Work around Intel-Mac compiler bug by setting PNG_NO_MMX_CODE in pngconf.h + Add a typecast to stifle compiler warning in pngrutil.c + +Version 1.4.0beta13 [November 10, 2006] + Fix potential buffer overflow in sPLT chunk handler. + Fix Makefile.am to not try to link to noexistent files. + +Version 1.4.0beta14 [November 15, 2006] + Check all exported functions for NULL png_ptr. + +Version 1.4.0beta15 [November 17, 2006] + Relocated two misplaced tests for NULL png_ptr. + Built Makefile.in with automake-1.9.6 instead of 1.9.2. + Build configure with autoconf-2.60 instead of 2.59 + Add "install: all" in Makefile.am so "configure; make install" will work. + +Version 1.4.0beta16 [November 17, 2006] + Added a typecast in png_zalloc(). + +Version 1.4.0beta17 [December 4, 2006] + Changed "new_key[79] = '\0';" to "(*new_key)[79] = '\0';" in pngwutil.c + Add "png_bytep" typecast to profile while calculating length in pngwutil.c + +Version 1.4.0beta18 [December 7, 2006] + Added scripts/CMakeLists.txt + +Version 1.4.0beta19 [May 16, 2007] + Revised scripts/CMakeLists.txt + Rebuilt configure and Makefile.in with newer tools. + Added conditional #undef jmpbuf in pngtest.c to undo #define in AIX headers. + Added scripts/makefile.nommx + +Version 1.4.0beta20 [July 9, 2008] + Moved several PNG_HAVE_* macros from pngpriv.h to png.h because applications + calling set_unknown_chunk_location() need them. + Moved several macro definitions from pngpriv.h to pngconf.h + Merge with changes to the 1.2.X branch, as of 1.2.30beta04. + Deleted all use of the MMX assembler code and Intel-licensed optimizations. + Revised makefile.mingw + +Version 1.4.0beta21 [July 21, 2008] + Moved local array "chunkdata" from pngrutil.c to the png_struct, so + it will be freed by png_read_destroy() in case of a read error (Kurt + Christensen). + +Version 1.4.0beta22 [July 21, 2008] + Change "purpose" and "buffer" to png_ptr->chunkdata to avoid memory leaking. + +Version 1.4.0beta23 [July 22, 2008] + Change "chunkdata = NULL" to "png_ptr->chunkdata = NULL" several places in + png_decompress_chunk(). + +Version 1.4.0beta24 [July 25, 2008] + Change all remaining "chunkdata" to "png_ptr->chunkdata" in + png_decompress_chunk(), and remove "chunkdata" from parameter list. + Put a call to png_check_chunk_name() in png_read_chunk_header(). + Revised png_check_chunk_name() to reject a name with a lowercase 3rd byte. + Removed two calls to png_check_chunk_name() occurring later in the process. + Define PNG_NO_ERROR_NUMBERS by default in pngconf.h + +Version 1.4.0beta25 [July 30, 2008] + Added a call to png_check_chunk_name() in pngpread.c + Reverted png_check_chunk_name() to accept a name with a lowercase 3rd byte. + Added png_push_have_buffer() function to pngpread.c + Eliminated PNG_BIG_ENDIAN_SUPPORTED and associated png_get_* macros. + Made inline expansion of png_get_*() optional with PNG_USE_READ_MACROS. + Eliminated all PNG_USELESS_TESTS and PNG_CORRECT_PALETTE_SUPPORTED code. + Synced contrib directory and configure files with libpng-1.2.30beta06. + Eliminated no-longer-used pngdefs.h (but it's still built in the makefiles) + Relocated a misplaced "#endif /* PNG_NO_WRITE_FILTER */" in pngwutil.c + +Version 1.4.0beta26 [August 4, 2008] + Removed png_push_have_buffer() function in pngpread.c. It increased the + compiled library size slightly. + Changed "-Wall" to "-W -Wall" in the CFLAGS in all makefiles (Cosmin Truta) + Declared png_ptr "volatile" in pngread.c and pngwrite.c to avoid warnings. + Updated contrib/visupng/cexcept.h to version 2.0.1 + Added PNG_LITERAL_CHARACTER macros for #, [, and ]. + +Version 1.4.0beta27 [August 5, 2008] + Revised usage of PNG_LITERAL_SHARP in pngerror.c. + Moved newline character from individual png_debug messages into the + png_debug macros. + Allow user to #define their own png_debug, png_debug1, and png_debug2. + +Version 1.4.0beta28 [August 5, 2008] + Revised usage of PNG_LITERAL_SHARP in pngerror.c. + Added PNG_STRING_NEWLINE macro + +Version 1.4.0beta29 [August 9, 2008] + Revised usage of PNG_STRING_NEWLINE to work on non-ISO compilers. + Added PNG_STRING_COPYRIGHT macro. + Added non-ISO versions of png_debug macros. + +Version 1.4.0beta30 [August 14, 2008] + Added premultiplied alpha feature (Volker Wiendl). + +Version 1.4.0beta31 [August 18, 2008] + Moved png_set_premultiply_alpha from pngtrans.c to pngrtran.c + Removed extra crc check at the end of png_handle_cHRM(). Bug introduced + in libpng-1.4.0beta20. + +Version 1.4.0beta32 [August 19, 2008] + Added PNG_WRITE_FLUSH_SUPPORTED block around new png_flush() call. + Revised PNG_NO_STDIO version of png_write_flush() + +Version 1.4.0beta33 [August 20, 2008] + Added png_get|set_chunk_cache_max() to limit the total number of sPLT, + text, and unknown chunks that can be stored. + +Version 1.4.0beta34 [September 6, 2008] + Shortened tIME_string to 29 bytes in pngtest.c + Fixed off-by-one error introduced in png_push_read_zTXt() function in + libpng-1.2.30beta04/pngpread.c (Harald van Dijk) + +Version 1.4.0beta35 [October 6, 2008] + Changed "trans_values" to "trans_color". + Changed so-number from 0 to 14. Some OS do not like 0. + Revised makefile.darwin to fix shared library numbering. + Change png_set_gray_1_2_4_to_8() to png_set_expand_gray_1_2_4_to_8() + in example.c (debian bug report) + +Version 1.4.0beta36 [October 25, 2008] + Sync with tEXt vulnerability fix in libpng-1.2.33rc02. + +Version 1.4.0beta37 [November 13, 2008] + Added png_check_cHRM in png.c and moved checking from pngget.c, pngrutil.c, + and pngwrite.c + +Version 1.4.0beta38 [November 22, 2008] + Added check for zero-area RGB cHRM triangle in png_check_cHRM() and + png_check_cHRM_fixed(). + +Version 1.4.0beta39 [November 23, 2008] + Revised png_warning() to write its message on standard output by default + when warning_fn is NULL. + +Version 1.4.0beta40 [November 24, 2008] + Eliminated png_check_cHRM(). Instead, always use png_check_cHRM_fixed(). + In png_check_cHRM_fixed(), ensure white_y is > 0, and removed redundant + check for all-zero coordinates that is detected by the triangle check. + +Version 1.4.0beta41 [November 26, 2008] + Fixed string vs pointer-to-string error in png_check_keyword(). + Rearranged test expressions in png_check_cHRM_fixed() to avoid internal + overflows. + Added PNG_NO_CHECK_cHRM conditional. + +Version 1.4.0beta42, 43 [December 1, 2008] + Merge png_debug with version 1.2.34beta04. + +Version 1.4.0beta44 [December 6, 2008] + Removed redundant check for key==NULL before calling png_check_keyword() + to ensure that new_key gets initialized and removed extra warning + (Merge with version 1.2.34beta05 -- Arvan Pritchard). + +Version 1.4.0beta45 [December 9, 2008] + In png_write_png(), respect the placement of the filler bytes in an earlier + call to png_set_filler() (Jim Barry). + +Version 1.4.0beta46 [December 10, 2008] + Undid previous change and added PNG_TRANSFORM_STRIP_FILLER_BEFORE and + PNG_TRANSFORM_STRIP_FILLER_AFTER conditionals and deprecated + PNG_TRANSFORM_STRIP_FILLER (Jim Barry). + +Version 1.4.0beta47 [December 15, 2008] + Support for dithering was disabled by default, because it has never + been well tested and doesn't work very well. The code has not + been removed, however, and can be enabled by building libpng with + PNG_READ_DITHER_SUPPORTED defined. + +Version 1.4.0beta48 [February 14, 2009] + Added new exported function png_calloc(). + Combined several instances of png_malloc(); png_memset() into png_calloc(). + Removed prototype for png_freeptr() that was added in libpng-1.4.0beta24 + but was never defined. + +Version 1.4.0beta49 [February 28, 2009] + Added png_fileno() macro to pngconf.h, used in pngwio.c + Corrected order of #ifdef's in png_debug definition in png.h + Fixed bug introduced in libpng-1.4.0beta48 with the memset arguments + for pcal_params. + Fixed order of #ifdef directives in the png_debug defines in png.h + (bug introduced in libpng-1.2.34/1.4.0beta29). + Revised comments in png_set_read_fn() and png_set_write_fn(). + +Version 1.4.0beta50 [March 18, 2009] + Use png_calloc() instead of png_malloc() to allocate big_row_buf when + reading an interlaced file, to avoid a possible UMR. + Undid revision of PNG_NO_STDIO version of png_write_flush(). Users + having trouble with fflush() can build with PNG_NO_WRITE_FLUSH defined + or supply their own flush_fn() replacement. + Revised libpng*.txt and png.h documentation about use of png_write_flush() + and png_set_write_fn(). + Removed fflush() from pngtest.c. + Added "#define PNG_NO_WRITE_FLUSH" to contrib/pngminim/encoder/pngusr.h + +Version 1.4.0beta51 [March 21, 2009] + Removed new png_fileno() macro from pngconf.h . + +Version 1.4.0beta52 [March 27, 2009] + Relocated png_do_chop() ahead of building gamma tables in pngrtran.c + This avoids building 16-bit gamma tables unnecessarily. + Removed fflush() from pngtest.c. + Added "#define PNG_NO_WRITE_FLUSH" to contrib/pngminim/encoder/pngusr.h + Added a section on differences between 1.0.x and 1.2.x to libpng.3/libpng.txt + +Version 1.4.0beta53 [April 1, 2009] + Removed some remaining MMX macros from pngpriv.h + Fixed potential memory leak of "new_name" in png_write_iCCP() (Ralph Giles) + +Version 1.4.0beta54 [April 13, 2009] + Added "ifndef PNG_SKIP_SETJMP_CHECK" block in pngconf.h to allow + application code writers to bypass the check for multiple inclusion + of setjmp.h when they know that it is safe to ignore the situation. + Eliminated internal use of setjmp() in pngread.c and pngwrite.c + Reordered ancillary chunks in pngtest.png to be the same as what + pngtest now produces, and made some cosmetic changes to pngtest output. + Eliminated deprecated png_read_init_3() and png_write_init_3() functions. + +Version 1.4.0beta55 [April 15, 2009] + Simplified error handling in pngread.c and pngwrite.c by putting + the new png_read_cleanup() and png_write_cleanup() functions inline. + +Version 1.4.0beta56 [April 25, 2009] + Renamed "user_chunk_data" to "my_user_chunk_data" in pngtest.c to suppress + "shadowed declaration" warning from gcc-4.3.3. + Renamed "gamma" to "png_gamma" in pngset.c to avoid "shadowed declaration" + warning about a global "gamma" variable in math.h on some platforms. + +Version 1.4.0beta57 [May 2, 2009] + Removed prototype for png_freeptr() that was added in libpng-1.4.0beta24 + but was never defined (again). + Rebuilt configure scripts with autoconf-2.63 instead of 2.62 + Removed pngprefs.h and MMX from makefiles + +Version 1.4.0beta58 [May 14, 2009] + Changed pngw32.def to pngwin.def in makefile.mingw (typo was introduced + in beta57). + Clarified usage of sig_bit versus sig_bit_p in example.c (Vincent Torri) + +Version 1.4.0beta59 [May 15, 2009] + Reformated sources in libpng style (3-space intentation, comment format) + Fixed typo in libpng docs (PNG_FILTER_AVE should be PNG_FILTER_AVG) + Added sections about the git repository and our coding style to the + documentation + Relocated misplaced #endif in pngwrite.c, sCAL chunk handler. + +Version 1.4.0beta60 [May 19, 2009] + Conditionally compile png_read_finish_row() which is not used by + progressive readers. + Added contrib/pngminim/preader to demonstrate building minimal progressive + decoder, based on contrib/gregbook with embedded libpng and zlib. + +Version 1.4.0beta61 [May 20, 2009] + In contrib/pngminim/*, renamed "makefile.std" to "makefile", since there + is only one makefile in those directories, and revised the README files + accordingly. + More reformatting of comments, mostly to capitalize sentences. + +Version 1.4.0beta62 [June 2, 2009] + Added "#define PNG_NO_WRITE_SWAP" to contrib/pngminim/encoder/pngusr.h + and "define PNG_NO_READ_SWAP" to decoder/pngusr.h and preader/pngusr.h + Reformatted several remaining "else statement" into two lines. + Added a section to the libpng documentation about using png_get_io_ptr() + in configure scripts to detect the presence of libpng. + +Version 1.4.0beta63 [June 15, 2009] + Revised libpng*.txt and libpng.3 to mention calling png_set_IHDR() + multiple times and to specify the sample order in the tRNS chunk, + because the ISO PNG specification has a typo in the tRNS table. + Changed several PNG_UNKNOWN_CHUNK_SUPPORTED to + PNG_HANDLE_AS_UNKNOWN_SUPPORTED, to make the png_set_keep mechanism + available for ignoring known chunks even when not saving unknown chunks. + Adopted preference for consistent use of "#ifdef" and "#ifndef" versus + "#if defined()" and "if !defined()" where possible. + +Version 1.4.0beta64 [June 24, 2009] + Eliminated PNG_LEGACY_SUPPORTED code. + Moved the various unknown chunk macro definitions outside of the + PNG_READ|WRITE_ANCILLARY_CHUNK_SUPPORTED blocks. + +Version 1.4.0beta65 [June 26, 2009] + Added a reference to the libpng license in each file. + +Version 1.4.0beta66 [June 27, 2009] + Refer to the libpng license instead of the libpng license in each file. + +Version 1.4.0beta67 [July 6, 2009] + Relocated INVERT_ALPHA within png_read_png() and png_write_png(). + Added high-level API transform PNG_TRANSFORM_GRAY_TO_RGB. + Added an "xcode" project to the projects directory (Alam Arias). + +Version 1.4.0beta68 [July 19, 2009] + Avoid some tests in filter selection in pngwutil.c + +Version 1.4.0beta69 [July 25, 2009] + Simplified the new filter-selection test. This runs faster in the + common "PNG_ALL_FILTERS" and PNG_FILTER_NONE cases. + Removed extraneous declaration from the new call to png_read_gray_to_rgb() + (bug introduced in libpng-1.4.0beta67). + Fixed up xcode project (Alam Arias) + Added a prototype for png_64bit_product() in png.c + +Version 1.4.0beta70 [July 27, 2009] + Avoid a possible NULL dereference in debug build, in png_set_text_2(). + (bug introduced in libpng-0.95, discovered by Evan Rouault) + +Version 1.4.0beta71 [July 29, 2009] + Rebuilt configure scripts with autoconf-2.64. + +Version 1.4.0beta72 [August 1, 2009] + Replaced *.tar.lzma with *.tar.xz in distribution. Get the xz codec + from . + +Version 1.4.0beta73 [August 1, 2009] + Reject attempt to write iCCP chunk with negative embedded profile length + (JD Chen) (CVE-2009-5063). + +Version 1.4.0beta74 [August 8, 2009] + Changed png_ptr and info_ptr member "trans" to "trans_alpha". + +Version 1.4.0beta75 [August 21, 2009] + Removed an extra png_debug() recently added to png_write_find_filter(). + Fixed incorrect #ifdef in pngset.c regarding unknown chunk support. + +Version 1.4.0beta76 [August 22, 2009] + Moved an incorrectly located test in png_read_row() in pngread.c + +Version 1.4.0beta77 [August 27, 2009] + Removed lpXYZ.tar.bz2 (with CRLF), KNOWNBUG, libpng-x.y.z-KNOWNBUG.txt, + and the "noconfig" files from the distribution. + Moved CMakeLists.txt from scripts into the main libpng directory. + Various bugfixes and improvements to CMakeLists.txt (Philip Lowman) + +Version 1.4.0beta78 [August 31, 2009] + Converted all PNG_NO_* tests to PNG_*_SUPPORTED everywhere except pngconf.h + Eliminated PNG_NO_FREE_ME and PNG_FREE_ME_SUPPORTED macros. + Use png_malloc plus a loop instead of png_calloc() to initialize + row_pointers in png_read_png(). + +Version 1.4.0beta79 [September 1, 2009] + Eliminated PNG_GLOBAL_ARRAYS and PNG_LOCAL_ARRAYS; always use local arrays. + Eliminated PNG_CALLOC_SUPPORTED macro and always provide png_calloc(). + +Version 1.4.0beta80 [September 17, 2009] + Removed scripts/libpng.icc + Changed typecast of filler from png_byte to png_uint_16 in png_set_filler(). + (Dennis Gustafsson) + Fixed typo introduced in beta78 in pngtest.c ("#if def " should be "#ifdef ") + +Version 1.4.0beta81 [September 23, 2009] + Eliminated unused PNG_FLAG_FREE_* defines from pngpriv.h + Expanded TAB characters in pngrtran.c + Removed PNG_CONST from all "PNG_CONST PNG_CHNK" declarations to avoid + compiler complaints about doubly declaring things "const". + Changed all "#if [!]defined(X)" to "if[n]def X" where possible. + Eliminated unused png_ptr->row_buf_size + +Version 1.4.0beta82 [September 25, 2009] + Moved redundant IHDR checking into new png_check_IHDR() in png.c + and report all errors found in the IHDR data. + Eliminated useless call to png_check_cHRM() from pngset.c + +Version 1.4.0beta83 [September 25, 2009] + Revised png_check_IHDR() to eliminate bogus complaint about filter_type. + +Version 1.4.0beta84 [September 30, 2009] + Fixed some inconsistent indentation in pngconf.h + Revised png_check_IHDR() to add a test for width variable less than 32-bit. + +Version 1.4.0beta85 [October 1, 2009] + Revised png_check_IHDR() again, to check info_ptr members instead of + the contents of the returned parameters. + +Version 1.4.0beta86 [October 9, 2009] + Updated the "xcode" project (Alam Arias). + Eliminated a shadowed declaration of "pp" in png_handle_sPLT(). + +Version 1.4.0rc01 [October 19, 2009] + Trivial cosmetic changes. + +Version 1.4.0beta87 [October 30, 2009] + Moved version 1.4.0 back into beta. + +Version 1.4.0beta88 [October 30, 2009] + Revised libpng*.txt section about differences between 1.2.x and 1.4.0 + because most of the new features have now been ported back to 1.2.41 + +Version 1.4.0beta89 [November 1, 2009] + More bugfixes and improvements to CMakeLists.txt (Philip Lowman) + Removed a harmless extra png_set_invert_alpha() from pngwrite.c + Apply png_user_chunk_cache_max within png_decompress_chunk(). + Merged libpng-1.2.41.txt with libpng-1.4.0.txt where appropriate. + +Version 1.4.0beta90 [November 2, 2009] + Removed all remaining WIN32_WCE #ifdefs except those involving the + time.h "tm" structure + +Version 1.4.0beta91 [November 3, 2009] + Updated scripts/pngw32.def and projects/wince/png32ce.def + Copied projects/wince/png32ce.def to the scripts directory. + Added scripts/makefile.wce + Patched ltmain.sh for wince support. + Added PNG_CONVERT_tIME_SUPPORTED macro. + +Version 1.4.0beta92 [November 4, 2009] + Make inclusion of time.h in pngconf.h depend on PNG_CONVERT_tIME_SUPPORTED + Make #define PNG_CONVERT_tIME_SUPPORTED depend on PNG_WRITE_tIME_SUPPORTED + Revised libpng*.txt to describe differences from 1.2.40 to 1.4.0 (instead + of differences from 1.2.41 to 1.4.0) + +Version 1.4.0beta93 [November 7, 2009] + Added PNG_DEPSTRUCT, PNG_DEPRECATED, PNG_USE_RESULT, PNG_NORETURN, and + PNG_ALLOCATED macros to detect deprecated direct access to the + png_struct or info_struct members and other deprecated usage in + applications (John Bowler). + Updated scripts/makefile* to add "-DPNG_CONFIGURE_LIBPNG" to CFLAGS, + to prevent warnings about direct access to png structs by libpng + functions while building libpng. They need to be tested, especially + those using compilers other than gcc. + Updated projects/visualc6 and visualc71 with "/d PNG_CONFIGURE_LIBPNG". + They should work but still need to be updated to remove + references to pnggccrd.c or pngvcrd.c and ASM building. + Added README.txt to the beos, cbuilder5, netware, and xcode projects warning + that they need to be updated, to remove references to pnggccrd.c and + pngvcrd.c and to depend on pngpriv.h + Removed three direct references to read_info_ptr members in pngtest.c + that were detected by the new PNG_DEPSTRUCT macro. + Moved the png_debug macro definitions and the png_read_destroy(), + png_write_destroy() and png_far_to_near() prototypes from png.h + to pngpriv.h (John Bowler) + Moved the synopsis lines for png_read_destroy(), png_write_destroy() + png_debug(), png_debug1(), and png_debug2() from libpng.3 to libpngpf.3. + +Version 1.4.0beta94 [November 9, 2009] + Removed the obsolete, unused pnggccrd.c and pngvcrd.c files. + Updated CMakeLists.txt to add "-DPNG_CONFIGURE_LIBPNG" to the definitions. + Removed dependency of pngtest.o on pngpriv.h in the makefiles. + Only #define PNG_DEPSTRUCT, etc. in pngconf.h if not already defined. + +Version 1.4.0beta95 [November 10, 2009] + Changed png_check_sig() to !png_sig_cmp() in contrib programs. + Added -DPNG_CONFIGURE_LIBPNG to contrib/pngminm/*/makefile + Changed png_check_sig() to !png_sig_cmp() in contrib programs. + Corrected the png_get_IHDR() call in contrib/gregbook/readpng2.c + Changed pngminim/*/gather.sh to stop trying to remove pnggccrd.c and pngvcrd.c + Added dependency on pngpriv.h in contrib/pngminim/*/makefile + +Version 1.4.0beta96 [November 12, 2009] + Renamed scripts/makefile.wce to scripts/makefile.cegcc + Revised Makefile.am to use libpng.sys while building libpng.so + so that only PNG_EXPORT functions are exported. + Removed the deprecated png_check_sig() function/macro. + Removed recently removed function names from scripts/*.def + Revised pngtest.png to put chunks in the same order written by pngtest + (evidently the same change made in libpng-1.0beta54 was lost). + Added PNG_PRIVATE macro definition in pngconf.h for possible future use. + +Version 1.4.0beta97 [November 13, 2009] + Restored pngtest.png to the libpng-1.4.0beta7 version. + Removed projects/beos and netware.txt; no one seems to be supporting them. + Revised Makefile.in + +Version 1.4.0beta98 [November 13, 2009] + Added the "xcode" project to zip distributions, + Fixed a typo in scripts/pngwin.def introduced in beta97. + +Version 1.4.0beta99 [November 14, 2009] + Moved libpng-config.in and libpng.pc-configure.in out of the scripts + directory, to libpng-config.in and libpng-pc.in, respectively, and + modified Makefile.am and configure.ac accordingly. Now "configure" + needs nothing from the "scripts" directory. + Avoid redefining PNG_CONST in pngconf.h + +Version 1.4.0beta100 [November 14, 2009] + Removed ASM builds from projects/visualc6 and projects/visualc71 + Removed scripts/makefile.nommx and makefile.vcawin32 + Revised CMakeLists.txt to account for new location of libpng-config.in + and libpng-pc.in + Updated INSTALL to reflect removal and relocation of files. + +Version 1.4.0beta101 [November 14, 2009] + Restored the binary files (*.jpg, *.png, some project files) that were + accidentally deleted from the zip and 7z distributions when the xcode + project was added. + +Version 1.4.0beta102 [November 18, 2009] + Added libpng-config.in and libpng-pc.in to the zip and 7z distributions. + Fixed a typo in projects/visualc6/pngtest.dsp, introduced in beta100. + Moved descriptions of makefiles and other scripts out of INSTALL into + scripts/README.txt + Updated the copyright year in scripts/pngwin.rc from 2006 to 2009. + +Version 1.4.0beta103 [November 21, 2009] + Removed obsolete comments about ASM from projects/visualc71/README_zlib.txt + Align row_buf on 16-byte boundary in memory. + Restored the PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED guard around the call + to png_flush() after png_write_IEND(). See 1.4.0beta32, 1.4.0beta50 + changes above and 1.2.30, 1.2.30rc01 and rc03 in 1.2.41 CHANGES. Someone + needs this feature. + Make the 'png_jmpbuf' macro expand to a call that records the correct + longjmp function as well as returning a pointer to the setjmp + jmp_buf buffer, and marked direct access to jmpbuf 'deprecated'. + (John Bowler) + +Version 1.4.0beta104 [November 22, 2009] + Removed png_longjmp_ptr from scripts/*.def and libpng.3 + Rebuilt configure scripts with autoconf-2.65 + +Version 1.4.0beta105 [November 25, 2009] + Use fast integer PNG_DIVIDE_BY_255() or PNG_DIVIDE_BY_65535() + to accomplish alpha premultiplication when + PNG_READ_COMPOSITE_NODIV_SUPPORTED is defined. + Changed "/255" to "/255.0" in background calculations to make it clear + that the 255 is used as a double. + +Version 1.4.0beta106 [November 27, 2009] + Removed premultiplied alpha feature. + +Version 1.4.0beta107 [December 4, 2009] + Updated README + Added "#define PNG_NO_PEDANTIC_WARNINGS" in the libpng source files. + Removed "-DPNG_CONFIGURE_LIBPNG" from the makefiles and projects. + Revised scripts/makefile.netbsd, makefile.openbsd, and makefile.sco + to put png.h and pngconf.h in $prefix/include, like the other scripts, + instead of in $prefix/include/libpng. Also revised makefile.sco + to put them in $prefix/include/libpng15 instead of in + $prefix/include/libpng/libpng15. + +Version 1.4.0beta108 [December 11, 2009] + Removed leftover "-DPNG_CONFIGURE_LIBPNG" from contrib/pngminim/*/makefile + Relocated png_do_chop() to its original position in pngrtran.c; the + change in version 1.2.41beta08 caused transparency to be handled wrong + in some 16-bit datastreams (Yusaku Sugai). + +Version 1.4.0beta109 [December 13, 2009] + Added "bit_depth" parameter to the private png_build_gamma_table() function. + Pass bit_depth=8 to png_build_gamma_table() when bit_depth is 16 but the + PNG_16_TO_8 transform has been set, to avoid unnecessary build of 16-bit + tables. + +Version 1.4.0rc02 [December 20, 2009] + Declared png_cleanup_needed "volatile" in pngread.c and pngwrite.c + +Version 1.4.0rc03 [December 22, 2009] + Renamed libpng-pc.in back to libpng.pc.in and revised CMakeLists.txt + (revising the change in 1.4.0beta99) + +Version 1.4.0rc04 [December 25, 2009] + Swapped PNG_UNKNOWN_CHUNKS_SUPPORTED and PNG_HANDLE_AS_UNKNOWN_SUPPORTED + in pngset.c to be consistent with other changes in version 1.2.38. + +Version 1.4.0rc05 [December 25, 2009] + Changed "libpng-pc.in" to "libpng.pc.in" in configure.ac, configure, and + Makefile.in to be consistent with changes in libpng-1.4.0rc03 + +Version 1.4.0rc06 [December 29, 2009] + Reverted the gamma_table changes from libpng-1.4.0beta109. + Fixed some indentation errors. + +Version 1.4.0rc07 [January 1, 2010] + Revised libpng*.txt and libpng.3 about 1.2.x->1.4.x differences. + Use png_calloc() instead of png_malloc(); png_memset() in pngrutil.c + Update copyright year to 2010. + +Version 1.4.0rc08 [January 2, 2010] + Avoid deprecated references to png_ptr-io_ptr and png_ptr->error_ptr + in pngtest.c + +Version 1.4.0 [January 3, 2010] + No changes. + +Version 1.4.1beta01 [January 8, 2010] + Updated CMakeLists.txt for consistent indentation and to avoid an + unclosed if-statement warning (Philip Lowman). + Revised Makefile.am and Makefile.in to remove references to Y2KINFO, + KNOWNBUG, and libpng.la (Robert Schwebel). + Revised the makefiles to install the same files and symbolic + links as configure, except for libpng.la and libpng14.la. + Make png_set|get_compression_buffer_size() available even when + PNG_WRITE_SUPPORTED is not enabled. + Revised Makefile.am and Makefile.in to simplify their maintenance. + Revised scripts/makefile.linux to install a link to libpng14.so.14.1 + +Version 1.4.1beta02 [January 9, 2010] + Revised the rest of the makefiles to install a link to libpng14.so.14.1 + +Version 1.4.1beta03 [January 10, 2010] + Removed png_set_premultiply_alpha() from scripts/*.def + +Version 1.4.1rc01 [January 16, 2010] + No changes. + +Version 1.4.1beta04 [January 23, 2010] + Revised png_decompress_chunk() to improve speed and memory usage when + decoding large chunks. + Added png_set|get_chunk_malloc_max() functions. + +Version 1.4.1beta05 [January 26, 2010] + Relocated "int k" declaration in pngtest.c to minimize its scope. + +Version 1.4.1beta06 [January 28, 2010] + Revised png_decompress_chunk() to use a two-pass method suggested by + John Bowler. + +Version 1.4.1beta07 [February 6, 2010] + Folded some long lines in the source files. + Added defineable PNG_USER_CHUNK_CACHE_MAX, PNG_USER_CHUNK_MALLOC_MAX, + and a PNG_USER_LIMITS_SUPPORTED flag. + Eliminated use of png_ptr->irowbytes and reused the slot in png_ptr as + png_ptr->png_user_chunk_malloc_max. + Revised png_push_save_buffer() to do fewer but larger png_malloc() calls. + +Version 1.4.1beta08 [February 6, 2010] + Minor cleanup and updating of dates and copyright year. + +Version 1.5.0beta01 [February 7, 2010] + Moved declaration of png_struct into private pngstruct.h and png_info + into pnginfo.h + +Version 1.4.1beta09 and 1.5.0beta02 [February 7, 2010] + Reverted to original png_push_save_buffer() code. + +Version 1.4.1beta10 and 1.5.0beta03 [February 8, 2010] + Return allocated "old_buffer" in png_push_save_buffer() before + calling png_error(), to avoid a potential memory leak. + Updated configure script to use SO number 15. + +Version 1.5.0beta04 [February 9, 2010] + Removed malformed "incomplete struct declaration" of png_info from png.h + +Version 1.5.0beta05 [February 12, 2010] + Removed PNG_DEPSTRUCT markup in pngstruct.h and pnginfo.h, and undid the + linewrapping that it entailed. + Revised comments in pngstruct.h and pnginfo.h and added pointers to + the libpng license. + Changed PNG_INTERNAL to PNG_EXPOSE_INTERNAL_STRUCTURES + Removed the cbuilder5 project, which has not been updated to 1.4.0. + +Version 1.4.1beta12 and 1.5.0beta06 [February 14, 2010] + Fixed type declaration of png_get_chunk_malloc_max() in pngget.c (Daisuke + Nishikawa) + +Version 1.5.0beta07 [omitted] + +Version 1.5.0beta08 [February 19, 2010] + Changed #ifdef PNG_NO_STDIO_SUPPORTED to #ifdef PNG_NO_CONSOLE_IO_SUPPORTED + wherever png_snprintf() is used to construct error and warning messages. + Noted in scripts/makefile.mingw that it expects to be run under MSYS. + Removed obsolete unused MMX-querying support from contrib/gregbook + Added exported png_longjmp() function. + Removed the AIX redefinition of jmpbuf in png.h + Added -D_ALLSOURCE in configure.ac, makefile.aix, and CMakeLists.txt + when building on AIX. + +Version 1.5.0beta09 [February 19, 2010] + Removed -D_ALLSOURCE from configure.ac, makefile.aix, and CMakeLists.txt. + Changed the name of png_ptr->jmpbuf to png_ptr->png_jmpbuf in pngstruct.h + +Version 1.5.0beta10 [February 25, 2010] + Removed unused gzio.c from contrib/pngminim gather and makefile scripts + Removed replacement error handlers from contrib/gregbook. Because of + the new png_longjmp() function they are no longer needed. + +Version 1.5.0beta11 [March 6, 2010] + Removed checking for already-included setjmp.h from pngconf.h + Fixed inconsistent indentations and made numerous cosmetic changes. + Revised the "SEE ALSO" style of libpng.3, libpngpf.3, and png.5 + +Version 1.5.0beta12 [March 9, 2010] + Moved "#include png.h" inside pngpriv.h and removed "#include png.h" from + the source files, along with "#define PNG_EXPOSE_INTERNAL_STRUCTURES" + and "#define PNG_NO_PEDANTIC_WARNINGS" (John Bowler). + Created new pngdebug.h and moved debug definitions there. + +Version 1.5.0beta13 [March 10, 2010] + Protect pngstruct.h, pnginfo.h, and pngdebug.h from being included twice. + Revise the "#ifdef" blocks in png_inflate() so it will compile when neither + PNG_USER_CHUNK_MALLOC_MAX nor PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED + is defined. + Removed unused png_measure_compressed_chunk() from pngpriv.h and libpngpf.3 + Moved the 'config.h' support from pngconf.h to pngpriv.h + Removed PNGAPI from the png_longjmp_ptr typedef. + Eliminated dependence of pngtest.c on the private pngdebug.h file. + Make all png_debug macros into *unterminated* statements or + expressions (i.e. a trailing ';' must always be added) and correct + the format statements in various png_debug messages. + +Version 1.5.0beta14 [March 14, 2010] + Removed direct access to png_ptr->io_ptr from the Windows code in pngtest.c + Revised Makefile.am to account for recent additions and replacements. + Corrected CE and OS/2 DEF files (scripts/png*def) for symbols removed and + added ordinal numbers to the Windows DEF file and corrected the duplicated + ordinal numbers on CE symbols that are commented out. + Added back in export symbols that can be present in the Windows build but + are disabled by default. + PNG_EXPORT changed to include an 'ordinal' field for DEF file generation. + PNG_CALLBACK added to make callback definitions uniform. PNGAPI split + into PNGCAPI (base C form), PNGAPI (exports) and PNGCBAPI (callbacks), + and appropriate changes made to all files. Cygwin builds re-hinged to + allow procedure call standard changes and to remove the need for the DEF + file (fixes build on Cygwin). + Enabled 'attribute' warnings that are relevant to library APIs and callbacks. + Changed rules for generation of the various symbol files and added a new + rule for a DEF file (which is also added to the distribution). + Updated the symbol file generation to stop it adding spurious spaces + to EOL (coming from preprocessor macro expansion). Added a facility + to join tokens in the output and rewrite *.dfn to use this. + Eliminated scripts/*.def in favor of libpng.def; updated projects/visualc71 + and removed scripts/makefile.cygwin. + Made PNG_BUILD_DLL safe: it can be set whenever a DLL is being built. + Removed the include of sys/types.h - apparently unnecessary now on the + platforms on which it happened (all but Mac OS and RISC OS). + Moved the Mac OS test into pngpriv.h (the only place it is used.) + +Version 1.5.0beta15 [March 17, 2010] + Added symbols.chk target to Makefile.am to validate the symbols in png.h + against the new DEF file scripts/symbols.def. + Changed the default DEF file back to pngwin.def. + Removed makefile.mingw. + Eliminated PNG_NO_EXTERN and PNG_ALL_EXTERN + +Version 1.5.0beta16 [April 1, 2010] + Make png_text_struct independent of PNG_iTXt_SUPPORTED, so that + fields are initialized in all configurations. The READ/WRITE + macros (PNG_(READ|WRITE)_iTXt_SUPPORTED) still function as + before to disable code to actually read or write iTXt chunks + and iTXt_SUPPORTED can be used to detect presence of either + read or write support (but it is probably better to check for + the one actually required - read or write.) + Combined multiple png_warning() calls for a single error. + Restored the macro definition of png_check_sig(). + +Version 1.5.0beta17 [April 17, 2010] + Added some "(long)" typecasts to printf calls in png_handle_cHRM(). + Documented the fact that png_set_dither() was disabled since libpng-1.4.0. + Reenabled png_set_dither() but renamed it to png_set_quantize() to reflect + more accurately what it actually does. At the same time, renamed + the PNG_DITHER_[RED,GREEN_BLUE]_BITS macros to + PNG_QUANTIZE_[RED,GREEN,BLUE]_BITS. + Added some "(long)" typecasts to printf calls in png_handle_cHRM(). + Freeze build-time only configuration in the build. + In all prior versions of libpng most configuration options + controlled by compiler #defines had to be repeated by the + application code that used libpng. This patch changes this + so that compilation options that can only be changed at build + time are frozen in the build. Options that are compiler + dependent (and those that are system dependent) are evaluated + each time - pngconf.h holds these. Options that can be changed + per-file in the application are in png.h. Frozen options are + in the new installed header file pnglibconf.h (John Bowler) + Removed the xcode project because it has not been updated to work + with libpng-1.5.0. + Removed the ability to include optional pngusr.h + +Version 1.5.0beta18 [April 17, 2010] + Restored the ability to include optional pngusr.h + Moved replacements for png_error() and png_warning() from the + contrib/pngminim project to pngerror.c, for use when warnings or + errors are disabled via PNG_NO_WARN or PNG_NO_ERROR_TEXT, to avoid + storing unneeded error/warning text. + Updated contrib/pngminim project to work with the new pnglibconf.h + Added some PNG_NO_* defines to contrib/pngminim/*/pngusr.h to save space. + +Version 1.5.0beta19 [April 24, 2010] + Added PNG_{READ,WRITE}_INT_FUNCTIONS_SUPPORTED. This allows the functions + to read and write ints to be disabled independently of PNG_USE_READ_MACROS, + which allows libpng to be built with the functions even though the default + is to use the macros - this allows applications to choose at app build + time whether or not to use macros (previously impossible because the + functions weren't in the default build.) + Changed Windows calling convention back to __cdecl for API functions. + For Windows/x86 platforms only: + __stdcall is no longer needed for Visual Basic, so libpng-1.5.0 uses + __cdecl throughout (both API functions and callbacks) on Windows/x86 + platforms. + Replaced visualc6 and visualc71 projects with new vstudio project + Relaxed the overly-restrictive permissions of some files. + +Version 1.5.0beta20 [April 24, 2010] + Relaxed more overly-restrictive permissions of some files. + +Version 1.5.0beta21 [April 27, 2010] + Removed some unwanted binary bytes and changed CRLF to NEWLINE in the new + vstudio project files, and some trivial editing of some files in the + scripts directory. + Set PNG_NO_READ_BGR, PNG_NO_IO_STATE, and PNG_NO_TIME_RFC1123 in + contrib/pngminim/decoder/pngusr.h to make a smaller decoder application. + +Version 1.5.0beta22 [April 28, 2010] + Fixed dependencies of GET_INT_32 - it does not require READ_INT_FUNCTIONS + because it has a macro equivalent. + Improved the options.awk script; added an "everything off" option. + Revised contrib/pngminim to use the "everything off" option in pngusr.dfa. + +Version 1.5.0beta23 [April 29, 2010] + Corrected PNG_REMOVED macro to take five arguments. + The macro was documented with two arguments (name,ordinal), however + the symbol checking .dfn files assumed five arguments. The five + argument form seems more useful so it is changed to that. + Corrected PNG_UNKNOWN_CHUNKS_SUPPORTED to PNG_HANDLE_AS_UNKNOWN_SUPPORTED + in gregbook/readpng2.c + Corrected protection of png_get_user_transform_ptr. The API declaration in + png.h is removed if both READ and WRITE USER_TRANSFORM are turned off + but was left defined in pngtrans.c + Added logunsupported=1 to cause pnglibconf.h to document disabled options. + This makes the installed pnglibconf.h more readable but causes no + other change. The intention is that users of libpng will find it + easier to understand if an API they need is missing. + Include png_reset_zstream() in png.c only when PNG_READ_SUPPORTED is defined. + Removed dummy_inflate.c from contrib/pngminim/encoder + Removed contrib/pngminim/*/gather.sh; gathering is now done in the makefile. + +Version 1.5.0beta24 [May 7, 2010] + Use bitwise "&" instead of arithmetic mod in pngrutil.c calculation of the + offset of the png_ptr->rowbuf pointer into png_ptr->big_row_buf. + Added more blank lines for readability. + +Version 1.5.0beta25 [June 18, 2010] + In pngpread.c: png_push_have_row() add check for new_row > height + Removed the now-redundant check for out-of-bounds new_row from example.c + +Version 1.5.0beta26 [June 18, 2010] + In pngpread.c: png_push_process_row() add check for too many rows. + +Version 1.5.0beta27 [June 18, 2010] + Removed the check added in beta25 as it is now redundant. + +Version 1.5.0beta28 [June 20, 2010] + Rewrote png_process_IDAT_data to consistently treat extra data as warnings + and handle end conditions more cleanly. + Removed the new (beta26) check in png_push_process_row(). + +Version 1.5.0beta29 [June 21, 2010] + Revised scripts/options.awk to work on Sunos (but still doesn't work) + Added comment to options.awk and contrib/pngminim/*/makefile to try nawk. + +Version 1.5.0beta30 [June 22, 2010] + Stop memory leak when reading a malformed sCAL chunk. + +Version 1.5.0beta31 [June 26, 2010] + Revised pngpread.c patch of beta28 to avoid an endless loop. + Removed some trailing blanks. + +Version 1.5.0beta32 [June 26, 2010] + Removed leftover scripts/options.patch and scripts/options.rej + +Version 1.5.0beta33 [July 6, 3010] + Made FIXED and FLOATING options consistent in the APIs they enable and + disable. Corrected scripts/options.awk to handle both command line + options and options specified in the .dfa files. + Changed char *msg to PNG_CONST char *msg in pngrutil.c + Make png_set_sRGB_gAMA_and_cHRM set values using either the fixed or + floating point APIs, but not both. + Reversed patch to remove error handler when the jmp_buf is stored in the + main program structure, not the png_struct. + The error handler is needed because the default handler in libpng will + always use the jmp_buf in the library control structure; this is never + set. The gregbook code is a useful example because, even though it + uses setjmp/longjmp, it shows how error handling can be implemented + using control mechanisms not directly supported by libpng. The + technique will work correctly with mechanisms such as Microsoft + Structure Exceptions or C++ exceptions (compiler willing - note that gcc + does not by default support interworking of C and C++ error handling.) + Reverted changes to call png_longjmp in contrib/gregbook where it is not + appropriate. If mainprog->jmpbuf is used by setjmp, then png_longjmp + cannot be used. + Changed "extern PNG_EXPORT" to "PNG_EXPORT" in png.h (Jan Nijtmans) + Changed "extern" to "PNG_EXTERN" in pngpriv.h (except for the 'extern "C" {') + +Version 1.5.0beta34 [July 12, 2010] + Put #ifndef PNG_EXTERN, #endif around the define PNG_EXTERN in pngpriv.h + +Version 1.5.0beta35 [July 24, 2010] + Removed some newly-added TAB characters. + Added -DNO_PNG_SNPRINTF to CFLAGS in scripts/makefile.dj2 + Moved the definition of png_snprintf() outside of the enclosing + #ifdef blocks in pngconf.h + +Version 1.5.0beta36 [July 29, 2010] + Patches by John Bowler: + Fixed point APIs are now supported throughout (no missing APIs). + Internal fixed point arithmetic support exists for all internal floating + point operations. + sCAL validates the floating point strings it is passed. + Safe, albeit rudimentary, Watcom support is provided by PNG_API_RULE==2 + Two new APIs exist to get the number of passes without turning on the + PNG_INTERLACE transform and to get the number of rows in the current + pass. + A new test program, pngvalid.c, validates the gamma code. + Errors in the 16-bit gamma correction (overflows) have been corrected. + cHRM chunk testing is done consistently (previously the floating point + API bypassed it, because the test really didn't work on FP, now the test + is performed on the actual values to be stored in the PNG file so it + works in the FP case too.) + Most floating point APIs now simply call the fixed point APIs after + converting the values to the fixed point form used in the PNG file. + The standard headers no longer include zlib.h, which is currently only + required for pngstruct.h and can therefore be internal. + Revised png_get_int_32 to undo the PNG two's complement representation of + negative numbers. + +Version 1.5.0beta37 [July 30, 2010] + Added a typecast in png_get_int_32() in png.h and pngrutil.h to avoid + a compiler warning. + Replaced oFFs 0,0 with oFFs -10,20 in pngtest.png + +Version 1.5.0beta38 [July 31, 2010] + Implemented remaining "_fixed" functions. + Corrected a number of recently introduced warnings mostly resulting from + safe but uncast assignments to shorter integers. Also added a zlib + VStudio release library project because the latest zlib Official Windows + build does not include such a thing. + Revised png_get_int_16() to be similar to png_get_int_32(). + Restored projects/visualc71. + +Version 1.5.0beta39 [August 2, 2010] + VisualC/GCC warning fixes, VisualC build fixes + The changes include support for function attributes in VC in addition to + those already present in GCC - necessary because without these some + warnings are unavoidable. Fixes include signed/unsigned fixes in + pngvalid and checks with gcc -Wall -Wextra -Wunused. + VC requires function attributes on function definitions as well as + declarations, PNG_FUNCTION has been added to enable this and the + relevant function definitions changed. + +Version 1.5.0beta40 [August 6, 2010] + Correct use of _WINDOWS_ in pngconf.h + Removed png_mem_ #defines; they are no longer used. + Added the sRGB chunk to pngtest.png + +Version 1.5.0beta41 [August 11, 2010] + Added the cHRM chunk to pngtest.png + Don't try to use version-script with cygwin/mingw. + Revised contrib/gregbook to work under cygwin/mingw. + +Version 1.5.0beta42 [August 18, 2010] + Add .dll.a to the list of extensions to be symlinked by Makefile.am (Yaakov) + Made all API functions that have const arguments and constant string + literal pointers declare them (John Bowler). + +Version 1.5.0beta43 [August 20, 2010] + Removed spurious tabs, shorten long lines (no source change) + Also added scripts/chkfmt to validate the format of all the files that can + reasonably be validated (it is suggested to run "make distclean" before + checking, because some machine generated files have long lines.) + Reformatted the CHANGES file to be more consistent throughout. + Made changes to address various issues identified by GCC, mostly + signed/unsigned and shortening problems on assignment but also a few + difficult to optimize (for GCC) loops. + Fixed non-GCC fixed point builds. In png.c a declaration was misplaced + in an earlier update. Fixed to declare the auto variables at the head. + Use cexcept.h in pngvalid.c. + +Version 1.5.0beta44 [August 24, 2010] + Updated CMakeLists.txt to use CMAKE_INSTALL_LIBDIR variable; useful for + installing libpng in /usr/lib64 (Funda Wang). + Revised CMakeLists.txt to put the man pages in share/man/man* not man/man* + Revised CMakeLists.txt to make symlinks instead of copies when installing. + Changed PNG_LIB_NAME from pngNN to libpngNN in CMakeLists.txt (Philip Lowman) + Implemented memory checks within pngvalid + Reformatted/rearranged pngvalid.c to assist use of progressive reader. + Check interlaced images in pngvalid + Clarified pngusr.h comments in pnglibconf.dfa + Simplified the pngvalid error-handling code now that cexcept.h is in place. + Implemented progressive reader in pngvalid.c for standard tests + Implemented progressive read in pngvalid.c gamma tests + Turn on progressive reader in pngvalid.c by default and tidy code. + +Version 1.5.0beta45 [August 26, 2010] + Added an explicit make step to projects/vstudio for pnglibconf.h + Also corrected zlib.vcxproj into which Visual Studio had introduced + what it calls an "authoring error". The change to make pnglibconf.h + simply copies the file; in the future it may actually generate the + file from scripts/pnglibconf.dfa as the other build systems do. + Changed pngvalid to work when floating point APIs are disabled + Renamed the prebuilt scripts/pnglibconf.h to scripts/pnglibconf.h.prebuilt + Supply default values for PNG_USER_PRIVATEBUILD and PNG_USER_DLLFNAME_POSTFIX + in pngpriv.h in case the user neglected to define them in their pngusr.h + +Version 1.5.0beta46 [August 28, 2010] + Added new private header files to libpng_sources in CMakeLists.txt + Added PNG_READ_16BIT, PNG_WRITE_16BIT, and PNG_16BIT options. + Added reference to scripts/pnglibconf.h.prebuilt in the visualc71 project. + +Version 1.5.0beta47 [September 11, 2010] + Fixed a number of problems with 64-bit compilation reported by Visual + Studio 2010 (John Bowler). + +Version 1.5.0beta48 [October 4, 2010] + Updated CMakeLists.txt (Philip Lowman). + Revised autogen.sh to recognize and use $AUTOCONF, $AUTOMAKE, $AUTOHEADER, + $AUTOPOINT, $ACLOCAL and $LIBTOOLIZE + Fixed problem with symbols creation in Makefile.am which was assuming that + all versions of ccp write to standard output by default (Martin Banky). The + bug was introduced in libpng-1.2.9beta5. + Removed unused mkinstalldirs. + +Version 1.5.0beta49 [October 8, 2010] + Undid Makefile.am revision of 1.5.0beta48. + +Version 1.5.0beta50 [October 14, 2010] + Revised Makefile.in to account for mkinstalldirs being removed. + Added some "(unsigned long)" typecasts in printf statements in pngvalid.c. + Suppressed a compiler warning in png_handle_sPLT(). + Check for out-of-range text compression mode in png_set_text(). + +Version 1.5.0beta51 [October 15, 2010] + Changed embedded dates to "(PENDING RELEASE) in beta releases (and future + rc releases) to minimize the difference between releases. + +Version 1.5.0beta52 [October 16, 2010] + Restored some of the embedded dates (in png.h, png.c, documentation, etc.) + +Version 1.5.0beta53 [October 18, 2010] + Updated INSTALL to mention using "make maintainer-clean" and to remove + obsolete statement about a custom ltmain.sh + Disabled "color-tests" by default in Makefile.am so it will work with + automake versions earlier than 1.11.1 + Use document name "libpng-manual.txt" instead of "libpng-.txt" + to simplify version differences. + Removed obsolete remarks about setjmp handling from INSTALL. + Revised and renamed the typedef in png.h and png.c that was designed + to catch library and header mismatch. + +Version 1.5.0beta54 [November 10, 2010] + Require 48 bytes, not 64 bytes, for big_row_buf in overflow checks. + Used a consistent structure for the pngget.c functions. + +Version 1.5.0beta55 [November 21, 2010] + Revised png_get_uint_32, png_get_int_32, png_get_uint_16 (Cosmin) + Moved reading of file signature into png_read_sig (Cosmin) + Fixed atomicity of chunk header serialization (Cosmin) + Added test for io_state in pngtest.c (Cosmin) + Added "#!/bin/sh" at the top of contrib/pngminim/*/gather.sh scripts. + Changes to remove gcc warnings (John Bowler) + Certain optional gcc warning flags resulted in warnings in libpng code. + With these changes only -Wconversion and -Wcast-qual cannot be turned on. + Changes are trivial rearrangements of code. -Wconversion is not possible + for pngrutil.c (because of the widespread use of += et al on variables + smaller than (int) or (unsigned int)) and -Wcast-qual is not possible + with pngwio.c and pngwutil.c because the 'write' callback and zlib + compression both fail to declare their input buffers with 'const'. + +Version 1.5.0beta56 [December 7, 2010] + Added the private PNG_UNUSED() macro definition in pngpriv.h. + Added some commentary about PNG_EXPORT in png.h and pngconf.h + Revised PNG_EXPORT() macro and added PNG_EXPORTA() macro, with the + objective of simplifying and improving the cosmetic appearance of png.h. + Fixed some incorrect "=" macro names in pnglibconf.dfa + Included documentation of changes in 1.5.0 from 1.4.x in libpng-manual.txt + +Version 1.5.0beta57 [December 9, 2010] + Documented the pngvalid gamma error summary with additional comments and + print statements. + Improved missing symbol handling in checksym.awk; symbols missing in both + the old and new files can now be optionally ignored, treated as errors + or warnings. + Removed references to pngvcrd.c and pnggccrd.c from the vstudio project. + Updated "libpng14" to "libpng15" in the visualc71 project. + Enabled the strip16 tests in pngvalid.` + Don't display test results (except PASS/FAIL) when running "make test". + Instead put them in pngtest-log.txt + Added "--with-zprefix=" to configure.ac + Updated the prebuilt configuration files to autoconf version 2.68 + +Version 1.5.0beta58 [December 19, 2010] + Fixed interlace image handling and add test cases (John Bowler) + Fixed the clean rule in Makefile.am to remove pngtest-log.txt + Made minor changes to work around warnings in gcc 3.4 + +Version 1.5.0rc01 [December 27, 2010] + No changes. + +Version 1.5.0rc02 [December 27, 2010] + Eliminated references to the scripts/*.def files in project/visualc71. + +Version 1.5.0rc03 [December 28, 2010] + Eliminated scripts/*.def and revised Makefile.am accordingly + +Version 1.5.0rc04 [December 29, 2010] + Fixed bug in background transformation handling in pngrtran.c (it was + looking for the flag in png_ptr->transformations instead of in + png_ptr->flags) (David Raymond). + +Version 1.5.0rc05 [December 31, 2010] + Fixed typo in a comment in CMakeLists.txt (libpng14 => libpng15) (Cosmin) + +Version 1.5.0rc06 [January 4, 2011] + Changed the new configure option "zprefix=string" to "zlib-prefix=string" + +Version 1.5.0rc07 [January 4, 2011] + Updated copyright year. + +Version 1.5.0 [January 6, 2011] + No changes. + +version 1.5.1beta01 [January 8, 2011] + Added description of png_set_crc_action() to the manual. + Added a note in the manual that the type of the iCCP profile was changed + from png_charpp to png_bytepp in png_get_iCCP(). This change happened + in version 1.5.0beta36 but is not noted in the CHANGES. Similarly, + it was changed from png_charpp to png_const_bytepp in png_set_iCCP(). + Ensure that png_rgb_to_gray ignores palette mapped images, if libpng + internally happens to call it with one, and fixed a failure to handle + palette mapped images correctly. This fixes CVE-2690. + +Version 1.5.1beta02 [January 14, 2011] + Fixed a bug in handling of interlaced images (bero at arklinux.org). + Updated CMakeLists.txt (Clifford Yapp) + +Version 1.5.1beta03 [January 14, 2011] + Fixed typecasting of some png_debug() statements (Cosmin) + +Version 1.5.1beta04 [January 16, 2011] + Updated documentation of png_set|get_tRNS() (Thomas Klausner). + Mentioned in the documentation that applications must #include "zlib.h" + if they need access to anything in zlib.h, and that a number of + macros such as png_memset() are no longer accessible by applications. + Corrected pngvalid gamma test "sample" function to access all of the color + samples of each pixel, instead of sampling the red channel three times. + Prefixed variable names index, div, exp, gamma with "png_" to avoid "shadow" + warnings, and (mistakenly) changed png_exp() to exp(). + +Version 1.5.1beta05 [January 16, 2011] + Changed variable names png_index, png_div, png_exp, and png_gamma to + char_index, divisor, exp_b10, and gamma_val, respectively, and + changed exp() back to png_exp(). + +Version 1.5.1beta06 [January 20, 2011] + Prevent png_push_crc_skip() from hanging while reading an unknown chunk + or an over-large compressed zTXt chunk with the progressive reader. + Eliminated more GCC "shadow" warnings. + Revised png_fixed() in png.c to avoid compiler warning about reaching the + end without returning anything. + +Version 1.5.1beta07 [January 22, 2011] + In the manual, describe the png_get_IHDR() arguments in the correct order. + Added const_png_structp and const_png_infop types, and used them in + prototypes for most png_get_*() functions. + +Version 1.5.1beta08 [January 23, 2011] + Added png_get_io_chunk_type() and deprecated png_get_io_chunk_name() + Added synopses for the IO_STATE functions and other missing synopses + to the manual. Removed the synopses from libpngpf.3 because they + were out of date and no longer useful. Better information can be + obtained by reading the prototypes and comments in pngpriv.h + Attempted to fix cpp on Solaris with S. Studio 12 cc, fix build + Added a make macro DFNCPP that is a CPP that will accept the tokens in + a .dfn file and adds configure stuff to test for such a CPP. ./configure + should fail if one is not available. + Corrected const_png_ in png.h to png_const_ to avoid polluting the namespace. + Added png_get_current_row_number and png_get_current_pass_number for the + benefit of the user transform callback. + Added png_process_data_pause and png_process_data_skip for the benefit of + progressive readers that need to stop data processing or want to optimize + skipping of unread data (e.g., if the reader marks a chunk to be skipped.) + +Version 1.5.1beta09 [January 24, 2011] + Enhanced pngvalid, corrected an error in gray_to_rgb, corrected doc error. + pngvalid contains tests of transforms, which tests are currently disabled + because they are incompletely tested. gray_to_rgb was failing to expand + the bit depth for smaller bit depth images; this seems to be a long + standing error and resulted, apparently, in invalid output + (CVE-2011-0408, CERT VU#643140). The documentation did not accurately + describe what libpng really does when converting RGB to gray. + +Version 1.5.1beta10 [January 27, 2010] + Fixed incorrect examples of callback prototypes in the manual, that were + introduced in libpng-1.0.0. + In addition the order of the png_get_uint macros with respect to the + relevant function definitions has been reversed. This helps the + preprocessing of the symbol files be more robust. Furthermore, the + symbol file preprocessing now uses -DPNG_NO_USE_READ_MACROS even when + the library may actually be built with PNG_USE_READ_MACROS; this stops + the read macros interfering with the symbol file format. + Made the manual, synopses, and function prototypes use the function + argument names file_gamma, int_file_gamma, and srgb_intent consistently. + +Version 1.5.1beta11 [January 28, 2011] + Changed PNG_UNUSED from "param=param;" to "{if(param){}}". + Corrected local variable type in new API png_process_data_skip() + The type was self-evidently incorrect but only causes problems on 64-bit + architectures. + Added transform tests to pngvalid and simplified the arguments. + +Version 1.5.1rc01 [January 29, 2011] + No changes. + +Version 1.5.1rc02 [January 31, 2011] + Added a request in the manual that applications do not use "png_" or + "PNG_" to begin any of their own symbols. + Changed PNG_UNUSED to "(void)param;" and updated the commentary in pngpriv.h + +Version 1.5.1 [February 3, 2011] + No changes. + +Version 1.5.2beta01 [February 13, 2011] + More -Wshadow fixes for older gcc compilers. Older gcc versions apparently + check formal parameters names in function declarations (as well as + definitions) to see if they match a name in the global namespace. + Revised PNG_EXPORTA macro to not use an empty parameter, to accommodate the + old VisualC++ preprocessor. + Turned on interlace handling in png_read_png(). + Fixed gcc pendantic warnings. + Handle longjmp in Cygwin. + Fixed png_get_current_row_number() in the interlaced case. + Cleaned up ALPHA flags and transformations. + Implemented expansion to 16 bits. + +Version 1.5.2beta02 [February 19, 2011] + Fixed mistake in the descriptions of user read_transform and write_transform + function prototypes in the manual. The row_info struct is png_row_infop. + Reverted png_get_current_row_number() to previous (1.5.2beta01) behavior. + Corrected png_get_current_row_number documentation + Fixed the read/write row callback documentation. + This documents the current behavior, where the callback is called after + every row with information pertaining to the next row. + +Version 1.5.2beta03 [March 3, 2011] + Fixed scripts/makefile.vcwin32 + Updated contrib/pngsuite/README to add the word "modify". + Define PNG_ALLOCATED to blank when _MSC_VER<1300. + +Version 1.5.2rc01 [March 19, 2011] + Define remaining attributes to blank when MSC_VER<1300. + ifdef out mask arrays in pngread.c when interlacing is not supported. + +Version 1.5.2rc02 [March 22, 2011] + Added a hint to try CPP=/bin/cpp if "cpp -E" fails in scripts/pnglibconf.mak + and in contrib/pngminim/*/makefile, eg., on SunOS 5.10, and removed "strip" + from the makefiles. + Fixed a bug (present since libpng-1.0.7) that makes png_handle_sPLT() fail + to compile when PNG_NO_POINTER_INDEXING is defined (Chubanov Kirill) + +Version 1.5.2rc03 [March 24, 2011] + Don't include standard header files in png.h while building the symbol table, + to avoid cpp failure on SunOS (introduced PNG_BUILDING_SYMBOL_TABLE macro). + +Version 1.5.2 [March 31, 2011] + No changes. + +Version 1.5.3beta01 [April 1, 2011] + Re-initialize the zlib compressor before compressing non-IDAT chunks. + Added API functions (png_set_text_compression_level() and four others) to + set parameters for zlib compression of non-IDAT chunks. + +Version 1.5.3beta02 [April 3, 2011] + Updated scripts/symbols.def with new API functions. + Only compile the new zlib re-initializing code when text or iCCP is + supported, using PNG_WRITE_COMPRESSED_TEXT_SUPPORTED macro. + Improved the optimization of the zlib CMF byte (see libpng-1.2.6beta03). + Optimize the zlib CMF byte in non-IDAT compressed chunks + +Version 1.5.3beta03 [April 16, 2011] + Fixed gcc -ansi -pedantic compile. A strict ANSI system does not have + snprintf, and the "__STRICT_ANSI__" detects that condition more reliably + than __STDC__ (John Bowler). + Removed the PNG_PTR_NORETURN attribute because it too dangerous. It tells + the compiler that a user supplied callback (the error handler) does not + return, yet there is no guarantee in practice that the application code + will correctly implement the error handler because the compiler only + issues a warning if there is a mistake (John Bowler). + Removed the no-longer-used PNG_DEPSTRUCT macro. + Updated the zlib version to 1.2.5 in the VStudio project. + Fixed 64-bit builds where png_uint_32 is smaller than png_size_t in + pngwutil.c (John Bowler). + Fixed bug with stripping the filler or alpha channel when writing, that + was introduced in libpng-1.5.2beta01 (bug report by Andrew Church). + +Version 1.5.3beta04 [April 27, 2011] + Updated pngtest.png with the new zlib CMF optimization. + Cleaned up conditional compilation code and of background/gamma handling + Internal changes only except a new option to avoid compiling the + png_build_grayscale_palette API (which is not used at all internally.) + The main change is to move the transform tests (READ_TRANSFORMS, + WRITE_TRANSFORMS) up one level to the caller of the APIs. This avoids + calls to spurious functions if all transforms are disabled and slightly + simplifies those functions. Pngvalid modified to handle this. + A minor change is to stop the strip_16 and expand_16 interfaces from + disabling each other; this allows the future alpha premultiplication + code to use 16-bit intermediate values while still producing 8-bit output. + png_do_background and png_do_gamma have been simplified to take a single + pointer to the png_struct rather than pointers to every item required + from the png_struct. This makes no practical difference to the internal + code. + A serious bug in the pngvalid internal routine 'standard_display_init' has + been fixed - this failed to initialize the red channel and accidentally + initialized the alpha channel twice. + Changed png_struct jmp_buf member name from png_jmpbuf to tmp_jmpbuf to + avoid a possible clash with the png_jmpbuf macro on some platforms. + +Version 1.5.3beta05 [May 6, 2011] + Added the "_POSIX_SOURCE" feature test macro to ensure libpng sees the + correct API. _POSIX_SOURCE is defined in pngpriv.h, pngtest.c and + pngvalid.c to ensure that POSIX conformant systems disable non-POSIX APIs. + Removed png_snprintf and added formatted warning messages. This change adds + internal APIs to allow png_warning messages to have parameters without + requiring the host OS to implement snprintf. As a side effect the + dependency of the tIME-supporting RFC1132 code on stdio is removed and + PNG_NO_WARNINGS does actually work now. + Pass "" instead of '\0' to png_default_error() in png_err(). This mistake + was introduced in libpng-1.2.20beta01. This fixes CVE-2011-2691. + Added PNG_WRITE_OPTIMIZE_CMF_SUPPORTED macro to make the zlib "CMF" byte + optimization configureable. + IDAT compression failed if preceded by a compressed text chunk (bug + introduced in libpng-1.5.3beta01-02). This was because the attempt to + reset the zlib stream in png_write_IDAT happened after the first IDAT + chunk had been deflated - much too late. In this change internal + functions were added to claim/release the z_stream and, hopefully, make + the code more robust. Also deflateEnd checking is added - previously + libpng would ignore an error at the end of the stream. + +Version 1.5.3beta06 [May 8, 2011] + Removed the -D_ALL_SOURCE from definitions for AIX in CMakeLists.txt + Implemented premultiplied alpha support: png_set_alpha_mode API + +Version 1.5.3beta07 [May 11, 2011] + Added expand_16 support to the high level interface. + Added named value and 'flag' gamma support to png_set_gamma. Made a minor + change from the previous (unreleased) ABI/API to hide the exact value used + for Macs - it's not a good idea to embed this in the ABI! + Moved macro definitions for PNG_HAVE_IHDR, PNG_HAVE_PLTE, and PNG_AFTER_IDAT + from pngpriv.h to png.h because they must be visible to applications + that call png_set_unknown_chunks(). + Check for up->location !PNG_AFTER_IDAT when writing unknown chunks + before IDAT. + +Version 1.5.3beta08 [May 16, 2011] + Improved "pngvalid --speed" to exclude more of pngvalid from the time. + Documented png_set_alpha_mode(), other changes in libpng.3/libpng-manual.txt + The cHRM chunk now sets the defaults for png_set_rgb_to_gray() (when negative + parameters are supplied by the caller), while in the absence of cHRM + sRGB/Rec 709 values are still used. This introduced a divide-by-zero + bug in png_handle_cHRM(). + The bKGD chunk no longer overwrites the background value set by + png_set_background(), allowing the latter to be used before the file + header is read. It never performed any useful function to override + the default anyway. + Added memory overwrite and palette image checks to pngvalid.c + Previously palette image code was poorly checked. Since the transformation + code has a special palette path in most cases this was a severe weakness. + Minor cleanup and some extra checking in pngrutil.c and pngrtran.c. When + expanding an indexed image, always expand to RGBA if transparency is + present. + +Version 1.5.3beta09 [May 17, 2011] + Reversed earlier 1.5.3 change of transformation order; move png_expand_16 + back where it was. The change doesn't work because it requires 16-bit + gamma tables when the code only generates 8-bit ones. This fails + silently; the libpng code just doesn't do any gamma correction. Moving + the tests back leaves the old, inaccurate, 8-bit gamma calculations, but + these are clearly better than none! + +Version 1.5.3beta10 [May 20, 2011] + + png_set_background() and png_expand_16() did not work together correctly. + This problem is present in 1.5.2; if png_set_background is called with + need_expand false and the matching 16 bit color libpng erroneously just + treats it as an 8-bit color because of where png_do_expand_16 is in the + transform list. This simple fix reduces the supplied colour to 8-bits, + so it gets smashed, but this is better than the current behavior. + Added tests for expand16, more fixes for palette image tests to pngvalid. + Corrects the code for palette image tests and disables attempts to + validate palette colors. + +Version 1.5.3rc01 [June 3, 2011] + No changes. + +Version 1.5.3rc02 [June 8, 2011] + Fixed uninitialized memory read in png_format_buffer() (Bug report by + Frank Busse, CVE-2011-2501, related to CVE-2004-0421). + +Version 1.5.3beta11 [June 11, 2011] + Fixed png_handle_sCAL which is broken in 1.5. This fixes CVE 2011-2692. + Added sCAL to pngtest.png + Revised documentation about png_set_user_limits() to say that it also affects + png writing. + Revised handling of png_set_user_limits() so that it can increase the + limit beyond the PNG_USER_WIDTH|HEIGHT_MAX; previously it could only + reduce it. + Make the 16-to-8 scaling accurate. Dividing by 256 with no rounding is + wrong (high by one) 25% of the time. Dividing by 257 with rounding is + wrong in 128 out of 65536 cases. Getting the right answer all the time + without division is easy. + Added "_SUPPORTED" to the PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION macro. + Added projects/owatcom, an IDE project for OpenWatcom to replace + scripts/makefile.watcom. This project works with OpenWatcom 1.9. The + IDE autogenerates appropriate makefiles (libpng.mk) for batch processing. + The project is configurable, unlike the Visual Studio project, so long + as the developer has an awk. + Changed png_set_gAMA to limit the gamma value range so that the inverse + of the stored value cannot overflow the fixed point representation, + and changed other things OpenWatcom warns about. + Revised pngvalid.c to test PNG_ALPHA_MODE_SUPPORTED correctly. This allows + pngvalid to build when ALPHA_MODE is not supported, which is required if + it is to build on libpng 1.4. + Removed string/memory macros that are no longer used and are not + necessarily fully supportable, particularly png_strncpy and png_snprintf. + Added log option to pngvalid.c and attempted to improve gamma messages. + +Version 1.5.3 [omitted] + People found the presence of a beta release following an rc release + to be confusing; therefore we bump the version to libpng-1.5.4beta01 + and there will be no libpng-1.5.3 release. + +Version 1.5.4beta01 [June 14, 2011] + Made it possible to undefine PNG_READ_16_TO_8_ACCURATE_SCALE_SUPPORTED + to get the same (inaccurate) output as libpng-1.5.2 and earlier. + Moved definitions of PNG_HAVE_IHDR, PNG_AFTER_IDAT, and PNG_HAVE_PLTE + outside of an unknown-chunk block in png.h because they are also + needed for other uses. + +Version 1.5.4beta02 [June 14, 2011] + Fixed and clarified LEGACY 16-to-8 scaling code. + Added png_set_chop_16() API, to match inaccurate results from previous + libpng versions. + Removed the ACCURATE and LEGACY options (they are no longer useable) + Use the old scaling method for background if png_set_chop_16() was + called. + Made png_set_chop_16() API removeable by disabling PNG_CHOP_16_TO_8_SUPPORTED + +Version 1.5.4beta03 [June 15, 2011] + Fixed a problem in png_do_expand_palette() exposed by optimization in + 1.5.3beta06 + Also removed a spurious and confusing "trans" member ("trans") from png_info. + The palette expand optimization prevented expansion to an intermediate RGBA + form if tRNS was present but alpha was marked to be stripped; this exposed + a check for tRNS in png_do_expand_palette() which is inconsistent with the + code elsewhere in libpng. + Correction to the expand_16 code; removed extra instance of + png_set_scale_16_to_8 from pngpriv.h + +Version 1.5.4beta04 [June 16, 2011] + Added a missing "#ifdef PNG_READ_BACKGROUND_SUPPORTED/#endif" in pngrtran.c + Added PNG_TRANSFORM_CHOP_16 to the high-level read transforms. + Made PNG_READ_16_TO_8_ACCURATE_SCALE configurable again. If this is + not enabled, png_set_strip_16() and png_do_scale_16_to_8() aren't built. + Revised contrib/visupng, gregbook, and pngminim to demonstrate chop_16_to_8 + +Version 1.5.4beta05 [June 16, 2011] + Renamed png_set_strip_16() to png_set_scale_16() and renamed + png_set_chop_16() to png_set_strip(16) in an attempt to minimize the + behavior changes between libpng14 and libpng15. + +Version 1.5.4beta06 [June 18, 2011] + Fixed new bug that was causing both strip_16 and scale_16 to be applied. + +Version 1.5.4beta07 [June 19, 2011] + Fixed pngvalid, simplified macros, added checking for 0 in sCAL. + The ACCURATE scale macro is no longer defined in 1.5 - call the + png_scale_16_to_8 API. Made sure that PNG_READ_16_TO_8 is still defined + if the png_strip_16_to_8 API is present. png_check_fp_number now + maintains some state so that positive, negative and zero values are + identified. sCAL uses these to be strictly spec conformant. + +Version 1.5.4beta08 [June 23, 2011] + Fixed pngvalid if ACCURATE_SCALE is defined. + Updated scripts/pnglibconf.h.prebuilt. + +Version 1.5.4rc01 [June 30, 2011] + Define PNG_ALLOCATED to "restrict" only if MSC_VER >= 1400. + +Version 1.5.4 [July 7, 2011] + No changes. + +Version 1.5.5beta01 [July 13, 2011] + Fixed some typos and made other minor changes in the manual. + Updated contrib/pngminus/makefile.std (Samuli Souminen) + +Version 1.5.5beta02 [July 14, 2011] + Revised Makefile.am and Makefile.in to look in the right directory for + pnglibconf.h.prebuilt + +Version 1.5.5beta03 [July 27, 2011] + Enabled compilation with g++ compiler. This compiler does not recognize + the file extension, so it always compiles with C++ rules. Made minor + changes to pngrutil.c to cast results where C++ expects it but C does not. + Minor editing of libpng.3 and libpng-manual.txt. + +Version 1.5.5beta04 [July 29, 2011] + Revised CMakeLists.txt (Clifford Yapp) + Updated commentary about the png_rgb_to_gray() default coefficients + in the manual and in pngrtran.c + +Version 1.5.5beta05 [August 17, 2011] + Prevent unexpected API exports from non-libpng DLLs on Windows. The "_DLL" + is removed from the test of whether a DLL is being built (this erroneously + caused the libpng APIs to be marked as DLL exports in static builds under + Microsoft Visual Studio). Almost all of the libpng building configuration + is moved from pngconf.h to pngpriv.h, but PNG_DLL_EXPORT remains in + pngconf.h, though, so that it is colocated with the import definition (it + is no longer used anywhere in the installed headers). The VStudio project + definitions have been cleaned up: "_USRDLL" has been removed from the + static library builds (this was incorrect), and PNG_USE_DLL has been added + to pngvalid to test the functionality (pngtest does not supply it, + deliberately). The spurious "_EXPORTS" has been removed from the + libpng build (all these errors were a result of copy/paste between project + configurations.) + Added new types and internal functions for CIE RGB end point handling to + pngpriv.h (functions yet to be implemented). + +Version 1.5.5beta06 [August 26, 2011] + Ensure the CMAKE_LIBRARY_OUTPUT_DIRECTORY is set in CMakeLists.txt + (Clifford Yap) + Fixes to rgb_to_gray and cHRM XYZ APIs (John Bowler): + The rgb_to_gray code had errors when combined with gamma correction. + Some pixels were treated as true grey when they weren't and such pixels + and true grey ones were not gamma corrected (the original value of the + red component was used instead). APIs to get and set cHRM using color + space end points have been added and the rgb_to_gray code that defaults + based on cHRM, and the divide-by-zero bug in png_handle_cHRM (CERT + VU#477046, CVE-2011-3328, introduced in 1.5.4) have been corrected. + A considerable number of tests has been added to pngvalid for the + rgb_to_gray transform. + Arithmetic errors in rgb_to_gray whereby the calculated gray value was + truncated to the bit depth rather than rounded have been fixed except in + the 8-bit non-gamma-corrected case (where consistency seems more important + than correctness.) The code still has considerable inaccuracies in the + 8-bit case because 8-bit linear arithmetic is used. + +Version 1.5.5beta07 [September 7, 2011] + Added "$(ARCH)" option to makefile.darwin + Added SunOS support to configure.ac and Makefile.am + Changed png_chunk_benign_error() to png_warning() in png.c, in + png_XYZ_from_xy_checked(). + +Version 1.5.5beta08 [September 10, 2011] + Fixed 64-bit compilation errors (gcc). The errors fixed relate + to conditions where types that are 32 bits in the GCC 32-bit + world (uLong and png_size_t) become 64 bits in the 64-bit + world. This produces potential truncation errors which the + compiler correctly flags. + Relocated new HAVE_SOLARIS_LD definition in configure.ac + Constant changes for 64-bit compatibility (removal of L suffixes). The + 16-bit cases still use "L" as we don't have a 16-bit test system. + +Version 1.5.5rc01 [September 15, 2011] + Removed "L" suffixes in pngpriv.h + +Version 1.5.5 [September 22, 2011] + No changes. + +Version 1.5.6beta01 [September 22, 2011] + Fixed some 64-bit type conversion warnings in pngrtran.c + Moved row_info from png_struct to a local variable. + The various interlace mask arrays have been made into arrays of + bytes and made PNG_CONST and static (previously some arrays were + marked PNG_CONST and some weren't). + Additional checks have been added to the transform code to validate the + pixel depths after the transforms on both read and write. + Removed some redundant code from pngwrite.c, in png_destroy_write_struct(). + Changed chunk reading/writing code to use png_uint_32 instead of png_byte[4]. + This removes the need to allocate temporary strings for chunk names on + the stack in the read/write code. Unknown chunk handling still uses the + string form because this is exposed in the API. + +Version 1.5.6beta02 [September 26, 2011] + Added a note in the manual the png_read_update_info() must be called only + once with a particular info_ptr. + Fixed a typo in the definition of the new PNG_STRING_FROM_CHUNK(s,c) macro. + +Version 1.5.6beta03 [September 28, 2011] + Revised test-pngtest.sh to report FAIL when pngtest fails. + Added "--strict" option to pngtest, to report FAIL when the failure is + only because the resulting valid files are different. + Revised CMakeLists.txt to work with mingw and removed some material from + CMakeLists.txt that is no longer useful in libpng-1.5. + +Version 1.5.6beta04 [October 5, 2011] + Fixed typo in Makefile.in and Makefile.am ("-M Wl" should be "-M -Wl")." + +Version 1.5.6beta05 [October 12, 2011] + Speed up png_combine_row() for interlaced images. This reduces the generality + of the code, allowing it to be optimized for Adam7 interlace. The masks + passed to png_combine_row() are now generated internally, avoiding + some code duplication and localizing the interlace handling somewhat. + Align png_struct::row_buf - previously it was always unaligned, caused by + a bug in the code that attempted to align it; the code needs to subtract + one from the pointer to take account of the filter byte prepended to + each row. + Optimized png_combine_row() when rows are aligned. This gains a small + percentage for 16-bit and 32-bit pixels in the typical case where the + output row buffers are appropriately aligned. The optimization was not + previously possible because the png_struct buffer was always misaligned. + Fixed bug in png_write_chunk_header() debug print, introduced in 1.5.6beta01. + +Version 1.5.6beta06 [October 17, 2011] + Removed two redundant tests for unitialized row. + Fixed a relatively harmless memory overwrite in compressed text writing + with a 1 byte zlib buffer. + Add ability to call png_read_update_info multiple times to pngvalid.c. + Fixes for multiple calls to png_read_update_info. These fixes attend to + most of the errors revealed in pngvalid, however doing the gamma work + twice results in inaccuracies that can't be easily fixed. There is now + a warning in the code if this is going to happen. + Turned on multiple png_read_update_info in pngvalid transform tests. + Prevent libpng from overwriting unused bits at the end of the image when + it is not byte aligned, while reading. Prior to libpng-1.5.6 libpng would + overwrite the partial byte at the end of each row if the row width was not + an exact multiple of 8 bits and the image is not interlaced. + +Version 1.5.6beta07 [October 21, 2011] + Made png_ptr->prev_row an aligned pointer into png_ptr->big_prev_row + (Mans Rullgard). + +Version 1.5.6rc01 [October 26, 2011] + Changed misleading "Missing PLTE before cHRM" warning to "Out of place cHRM" + +Version 1.5.6rc02 [October 27, 2011] + Added LSR() macro to defend against buggy compilers that evaluate non-taken + code branches and complain about out-of-range shifts. + +Version 1.5.6rc03 [October 28, 2011] + Renamed the LSR() macro to PNG_LSR() and added PNG_LSL() macro. + Fixed compiler warnings with Intel and MSYS compilers. The logical shift + fix for Microsoft Visual C is required by other compilers, so this + enables that fix for all compilers when using compile-time constants. + Under MSYS 'byte' is a name declared in a system header file, so we + changed the name of a local variable to avoid the warnings that result. + Added #define PNG_ALIGN_TYPE PNG_ALIGN_NONE to contrib/pngminim/*/pngusr.h + +Version 1.5.6 [November 3, 2011] + No changes. + +Version 1.5.7beta01 [November 4, 2011] + Added support for ARM processor, when decoding all PNG up-filtered rows + and any other-filtered rows with 3 or 4 bytes per pixel (Mans Rullgard). + Fixed bug in pngvalid on early allocation failure; fixed type cast in + pngmem.c; pngvalid would attempt to call png_error() if the allocation + of a png_struct or png_info failed. This would probably have led to a + crash. The pngmem.c implementation of png_malloc() included a cast + to png_size_t which would fail on large allocations on 16-bit systems. + Fix for the preprocessor of the Intel C compiler. The preprocessor + splits adjacent @ signs with a space; this changes the concatentation + token from @-@-@ to PNG_JOIN; that should work with all compiler + preprocessors. + Paeth filter speed improvements from work by Siarhei Siamashka. This + changes the 'Paeth' reconstruction function to improve the GCC code + generation on x86. The changes are only part of the suggested ones; + just the changes that definitely improve speed and remain simple. + The changes also slightly increase the clarity of the code. + +Version 1.5.7beta02 [November 11, 2011] + Check compression_type parameter in png_get_iCCP and remove spurious + casts. The compression_type parameter is always assigned to, so must + be non-NULL. The cast of the profile length potentially truncated the + value unnecessarily on a 16-bit int system, so the cast of the (byte) + compression type to (int) is specified by ANSI-C anyway. + Fixed FP division by zero in pngvalid.c; the 'test_pixel' code left + the sBIT fields in the test pixel as 0, which resulted in a floating + point division by zero which was irrelevant but causes systems where + FP exceptions cause a crash. Added code to pngvalid to turn on FP + exceptions if the appropriate glibc support is there to ensure this is + tested in the future. + Updated scripts/pnglibconf.mak and scripts/makefile.std to handle the + new PNG_JOIN macro. + Added versioning to pnglibconf.h comments. + Simplified read/write API initial version; basic read/write tested on + a variety of images, limited documentation (in the header file.) + Installed more accurate linear to sRGB conversion tables. The slightly + modified tables reduce the number of 16-bit values that + convert to an off-by-one 8-bit value. The "makesRGB.c" code that was used + to generate the tables is now in a contrib/sRGBtables sub-directory. + +Version 1.5.7beta03 [November 17, 2011] + Removed PNG_CONST from the sRGB table declarations in pngpriv.h and png.c + Added run-time detection of NEON support. + Added contrib/libtests; includes simplified API test and timing test and + a color conversion utility for rapid checking of failed 'pngstest' results. + Multiple transform bug fixes plus a work-round for double gamma correction. + libpng does not support more than one transform that requires linear data + at once - if this is tried typically the results is double gamma + correction. Since the simplified APIs can need rgb to gray combined with + a compose operation it is necessary to do one of these outside the main + libpng transform code. This check-in also contains fixes to various bugs + in the simplified APIs themselves and to some bugs in compose and rgb to + gray (on palette) itself. + Fixes for C++ compilation using g++ When libpng source is compiled + using g++. The compiler imposes C++ rules on the C source; thus it + is desireable to make the source work with either C or C++ rules + without throwing away useful error information. This change adds + png_voidcast to allow C semantic (void*) cases or the corresponding + C++ static_cast operation, as appropriate. + Added --noexecstack to assembler file compilation. GCC does not set + this on assembler compilation, even though it does on C compilation. + This creates security issues if assembler code is enabled; the + work-around is to set it by default in the flags for $(CCAS) + Work around compilers that don't support declaration of const data. Some + compilers fault 'extern const' data declarations (because the data is + not initialized); this turns on const-ness only for compilers where + this is known to work. + +Version 1.5.7beta04 [November 17, 2011] + Since the gcc driver does not recognize the --noexecstack flag, we must + use the -Wa prefix to have it passed through to the assembler. + Also removed a duplicate setting of this flag. + Added files that were omitted from the libpng-1.5.7beta03 zip distribution. + +Version 1.5.7beta05 [November 25, 2011] + Removed "zTXt" from warning in generic chunk decompression function. + Validate time settings passed to png_set_tIME() and png_convert_to_rfc1123() + (Frank Busse). Note: This prevented CVE-2015-7981 from affecting + libpng-1.5.7 and later. + Added MINGW support to CMakeLists.txt + Reject invalid compression flag or method when reading the iTXt chunk. + Backed out 'simplified' API changes. The API seems too complex and there + is a lack of consensus or enthusiasm for the proposals. The API also + reveals significant bugs inside libpng (double gamma correction and the + known bug of being unable to retrieve a corrected palette). It seems + better to wait until the bugs, at least, are corrected. + Moved pngvalid.c into contrib/libtests + Rebuilt Makefile.in, configure, etc., with autoconf-2.68 + +Version 1.5.7rc01 [December 1, 2011] + Replaced an "#if" with "#ifdef" in pngrtran.c + Revised #if PNG_DO_BC block in png.c (use #ifdef and add #else) + +Version 1.5.7rc02 [December 5, 2011] + Revised project files and contrib/pngvalid/pngvalid.c to account for + the relocation of pngvalid into contrib/libtests. + Revised pngconf.h to use " __declspec(restrict)" only when MSC_VER >= 1400, + as in libpng-1.5.4. + Put CRLF line endings in the owatcom project files. + +Version 1.5.7rc03 [December 7, 2011] + Updated CMakeLists.txt to account for the relocation of pngvalid.c + +Version 1.5.7 [December 15, 2011] + Minor fixes to pngvalid.c for gcc 4.6.2 compatibility to remove warnings + reported by earlier versions. + Fixed minor memset/sizeof errors in pngvalid.c. + +Version 1.6.0beta01 [December 15, 2011] + Removed machine-generated configure files from the GIT repository (they will + continue to appear in the tarball distributions and in the libpng15 and + earlier GIT branches). + Restored the new 'simplified' API, which was started in libpng-1.5.7beta02 + but later deleted from libpng-1.5.7beta05. + Added example programs for the new 'simplified' API. + Added ANSI-C (C90) headers and require them, and take advantage of the + change. Also fixed some of the projects/* and contrib/* files that needed + updates for libpng16 and the move of pngvalid.c. + With this change the required ANSI-C header files are assumed to exist: the + implementation must provide float.h, limits.h, stdarg.h and stddef.h and + libpng relies on limits.h and stddef.h existing and behaving as defined + (the other two required headers aren't used). Non-ANSI systems that don't + have stddef.h or limits.h will have to provide an appropriate fake + containing the relevant types and #defines. + Dropped support for 16-bit platforms. The use of FAR/far has been eliminated + and the definition of png_alloc_size_t is now controlled by a flag so + that 'small size_t' systems can select it if necessary. Libpng 1.6 may + not currently work on such systems -- it seems likely that it will + ask 'malloc' for more than 65535 bytes with any image that has a + sufficiently large row size (rather than simply failing to read such + images). + New tools directory containing tools used to generate libpng code. + Fixed race conditions in parallel make builds. With higher degrees of + parallelism during 'make' the use of the same temporary file names such + as 'dfn*' can result in a race where a temporary file from one arm of the + build is deleted or overwritten in another arm. This changes the + temporary files for suffix rules to always use $* and ensures that the + non-suffix rules use unique file names. + +Version 1.6.0beta02 [December 21, 2011] + Correct configure builds where build and source directories are separate. + The include path of 'config.h' was erroneously made relative in pngvalid.c + in libpng 1.5.7. + +Version 1.6.0beta03 [December 22, 2011] + Start-up code size improvements, error handler flexibility. These changes + alter how the tricky allocation of the initial png_struct and png_info + structures are handled. png_info is now handled in pretty much the same + way as everything else, except that the allocations handle NULL return + silently. png_struct is changed in a similar way on allocation and on + deallocation a 'safety' error handler is put in place (which should never + be required). The error handler itself is changed to permit mismatches + in the application and libpng error buffer size; however, this means a + silent change to the API to return the jmp_buf if the size doesn't match + the size from the libpng compilation; libpng now allocates the memory and + this may fail. Overall these changes result in slight code size + reductions; however, this is a reduction in code that is always executed + so is particularly valuable. Overall on a 64-bit system the libpng DLL + decreases in code size by 1733 bytes. pngerror.o increases in size by + about 465 bytes because of the new functionality. + Added png_convert_to_rfc1123_buffer() and deprecated png_convert_to_rfc1123() + to avoid including a spurious buffer in the png_struct. + +Version 1.6.0beta04 [December 30, 2011] + Regenerated configure scripts with automake-1.11.2 + Eliminated png_info_destroy(). It is now used only in png.c and only calls + one other internal function and memset(). + Enabled png_get_sCAL_fixed() if floating point APIs are enabled. Previously + it was disabled whenever internal fixed point arithmetic was selected, + which meant it didn't exist even on systems where FP was available but not + preferred. + Added pngvalid.c compile time checks for const APIs. + Implemented 'restrict' for png_info and png_struct. Because of the way + libpng works both png_info and png_struct are always accessed via a + single pointer. This means adding C99 'restrict' to the pointer gives + the compiler some opportunity to optimize the code. This change allows + that. + Moved AC_MSG_CHECKING([if libraries can be versioned]) later to the proper + location in configure.ac (Gilles Espinasse). + Changed png_memcpy to C assignment where appropriate. Changed all those + uses of png_memcpy that were doing a simple assignment to assignments + (all those cases where the thing being copied is a non-array C L-value). + Added some error checking to png_set_*() routines. + Removed the reference to the non-exported function png_memcpy() from + example.c. + Fixed the Visual C 64-bit build - it requires jmp_buf to be aligned, but + it had become misaligned. + Revised contrib/pngminus/pnm2png.c to avoid warnings when png_uint_32 + and unsigned long are of different sizes. + +Version 1.6.0beta05 [January 15, 2012] + Updated manual with description of the simplified API (copied from png.h) + Fix bug in pngerror.c: some long warnings were being improperly truncated + (CVE-2011-3464, bug introduced in libpng-1.5.3beta05). + +Version 1.6.0beta06 [January 24, 2012] + Added palette support to the simplified APIs. This commit + changes some of the macro definitions in png.h, app code + may need corresponding changes. + Increased the formatted warning buffer to 192 bytes. + Added color-map support to simplified API. This is an initial version for + review; the documentation has not yet been updated. + Fixed Min/GW uninstall to remove libpng.dll.a + +Version 1.6.0beta07 [January 28, 2012] + Eliminated Intel icc/icl compiler warnings. The Intel (GCC derived) + compiler issues slightly different warnings from those issued by the + current vesions of GCC. This eliminates those warnings by + adding/removing casts and small code rewrites. + Updated configure.ac from autoupdate: added --enable-werror option. + Also some layout regularization and removal of introduced tab characters + (replaced with 3-character indentation). Obsolete macros identified by + autoupdate have been removed; the replacements are all in 2.59 so + the pre-req hasn't been changed. --enable-werror checks for support + for -Werror (or the given argument) in the compiler. This mimics the + gcc configure option by allowing -Werror to be turned on safely; without + the option the tests written in configure itself fail compilation because + they cause compiler warnings. + Rewrote autogen.sh to run autoreconf instead of running tools one-by-one. + Conditionalize the install rules for MINGW and CYGWIN in CMakeLists.txt and + set CMAKE_LIBRARY_OUTPUT_DIRECTORY to "lib" on all platforms (C. Yapp). + Freeze libtool files in the 'scripts' directory. This version of autogen.sh + attempts to dissuade people from running it when it is not, or should not, + be necessary. In fact, autogen.sh does not work when run in a libpng + directory extracted from a tar distribution anymore. You must run it in + a GIT clone instead. + Added two images to contrib/pngsuite (1-bit and 2-bit transparent grayscale), + and renamed three whose names were inconsistent with those in + pngsuite/README.txt. + +Version 1.6.0beta08 [February 1, 2012] + Fixed Image::colormap misalignment in pngstest.c + Check libtool/libtoolize version number (2.4.2) in configure.ac + Divide test-pngstest.sh into separate pngstest runs for basic and + transparent images. + Moved automake options to AM_INIT_AUTOMAKE in configure.ac + Added color-tests, silent-rules (Not yet implemented in Makefile.am) and + version checking to configure.ac + Improved pngstest speed by not doing redundant tests and add const to + the background parameter of png_image_finish_read. The --background + option is now done automagically only when required, so that commandline + option no longer exists. + Cleaned up pngpriv.h to consistently declare all functions and data. + Also eliminated PNG_CONST_DATA, which is apparently not needed but we + can't be sure until it is gone. + Added symbol prefixing that allows all the libpng external symbols + to be prefixed (suggested by Reuben Hawkins). + Updated "ftbb*.png" list in the owatcom and vstudio projects. + Fixed 'prefix' builds on clean systems. The generation of pngprefix.h + should not require itself. + Updated INSTALL to explain that autogen.sh must be run in a GIT clone, + not in a libpng directory extracted from a tar distribution. + +Version 1.6.0beta09 [February 1, 2012] + Reverted the prebuilt configure files to libpng-1.6.0beta05 condition. + +Version 1.6.0beta10 [February 3, 2012] + Added Z_SOLO for zlib-1.2.6+ and correct pngstest tests + Updated list of test images in CMakeLists.txt + Updated the prebuilt configure files to current condition. + Revised INSTALL information about autogen.sh; it works in tar distributions. + +Version 1.6.0beta11 [February 16, 2012] + Fix character count in pngstest command in projects/owatcom/pngstest.tgt + Revised test-pngstest.sh to report PASS/FAIL for each image. + Updated documentation about the simplified API. + Corrected estimate of error in libpng png_set_rgb_to_gray API. The API is + extremely inaccurate for sRGB conversions because it uses an 8-bit + intermediate linear value and it does not use the sRGB transform, so it + suffers from the known instability in gamma transforms for values close + to 0 (see Poynton). The net result is that the calculation has a maximum + error of 14.99/255; 0.5/255^(1/2.2). pngstest now uses 15 for the + permitted 8-bit error. This may still not be enough because of arithmetic + error. + Removed some unused arrays (with #ifdef) from png_read_push_finish_row(). + Fixed a memory overwrite bug in simplified read of RGB PNG with + non-linear gamma Also bugs in the error checking in pngread.c and changed + quite a lot of the checks in pngstest.c to be correct; either correctly + written or not over-optimistic. The pngstest changes are insufficient to + allow all possible RGB transforms to be passed; pngstest cmppixel needs + to be rewritten to make it clearer which errors it allows and then changed + to permit known inaccuracies. + Removed tests for no-longer-used *_EMPTY_PLTE_SUPPORTED from pngstruct.h + Fixed fixed/float API export conditionals. 1) If FIXED_POINT or + FLOATING_POINT options were switched off, png.h ended up with lone ';' + characters. This is not valid ANSI-C outside a function. The ';' + characters have been moved inside the definition of PNG_FP_EXPORT and + PNG_FIXED_EXPORT. 2) If either option was switched off, the declaration + of the corresponding functions were completely omitted, even though some + of them are still used internally. The result is still valid, but + produces warnings from gcc with some warning options (including -Wall). The + fix is to cause png.h to declare the functions with PNG_INTERNAL_FUNCTION + when png.h is included from pngpriv.h. + Check for invalid palette index while reading paletted PNG. When one is + found, issue a warning and increase png_ptr->num_palette accordingly. + Apps are responsible for checking to see if that happened. + +Version 1.6.0beta12 [February 18, 2012] + Do not increase num_palette on invalid_index. + Relocated check for invalid palette index to pngrtran.c, after unpacking + the sub-8-bit pixels. + Fixed CVE-2011-3026 buffer overrun bug. This bug was introduced when + iCCP chunk support was added at libpng-1.0.6. Deal more correctly with the + test on iCCP chunk length. Also removed spurious casts that may hide + problems on 16-bit systems. + +Version 1.6.0beta13 [February 24, 2012] + Eliminated redundant png_push_read_tEXt|zTXt|iTXt|unknown code from + pngpread.c and use the sequential png_handle_tEXt, etc., in pngrutil.c; + now that png_ptr->buffer is inaccessible to applications, the special + handling is no longer useful. + Added PNG_SAFE_LIMITS feature to pnglibconf.dfa, pngpriv.h, and new + pngusr.dfa to reset the user limits to safe ones if PNG_SAFE_LIMITS is + defined. To enable, use "CPPFLAGS=-DPNG_SAFE_LIMITS_SUPPORTED=1" on the + configure command or put #define PNG_SAFE_LIMITS_SUPPORTED in + pnglibconf.h.prebuilt and pnglibconf.h. + +Version 1.6.0beta14 [February 27, 2012] + Added information about the new limits in the manual. + Updated Makefile.in + +Version 1.6.0beta15 [March 2, 2012] + Removed unused "current_text" members of png_struct and the png_free() + of png_ptr->current_text from pngread.c + Rewrote pngstest.c for substantial speed improvement. + Fixed transparent pixel and 16-bit rgb tests in pngstest and removed a + spurious check in pngwrite.c + Added PNG_IMAGE_FLAG_FAST for the benefit of applications that store + intermediate files, or intermediate in-memory data, while processing + image data with the simplified API. The option makes the files larger + but faster to write and read. pngstest now uses this by default; this + can be disabled with the --slow option. + Improved pngstest fine tuning of error numbers, new test file generator. + The generator generates images that test the full range of sample values, + allow the error numbers in pngstest to be tuned and checked. makepng + also allows generation of images with extra chunks, although this is + still work-in-progress. + Added check for invalid palette index while reading. + Fixed some bugs in ICC profile writing. The code should now accept + all potentially valid ICC profiles and reject obviously invalid ones. + It now uses png_error() to do so rather than casually writing a PNG + without the necessary color data. + Removed whitespace from the end of lines in all source files and scripts. + +Version 1.6.0beta16 [March 6, 2012] + Relocated palette-index checking function from pngrutil.c to pngtrans.c + Added palette-index checking while writing. + Changed png_inflate() and calling routines to avoid overflow problems. + This is an intermediate check-in that solves the immediate problems and + introduces one performance improvement (avoiding a copy via png_ptr->zbuf.) + Further changes will be made to make ICC profile handling more secure. + Fixed build warnings (MSVC, GCC, GCC v3). Cygwin GCC with default options + declares 'index' as a global, causing a warning if it is used as a local + variable. GCC 64-bit warns about assigning a (size_t) (unsigned 64-bit) + to an (int) (signed 32-bit). MSVC, however, warns about using the + unary '-' operator on an unsigned value (even though it is well defined + by ANSI-C to be ~x+1). The padding calculation was changed to use a + different method. Removed the tests on png_ptr->pass. + Added contrib/libtests/tarith.c to test internal arithmetic functions from + png.c. This is a libpng maintainer program used to validate changes to the + internal arithmetic functions. + Made read 'inflate' handling like write 'deflate' handling. The read + code now claims and releases png_ptr->zstream, like the write code. + The bug whereby the progressive reader failed to release the zstream + is now fixed, all initialization is delayed, and the code checks for + changed parameters on deflate rather than always calling + deflatedEnd/deflateInit. + Validate the zTXt strings in pngvalid. + Added code to validate the windowBits value passed to deflateInit2(). + If the call to deflateInit2() is wrong a png_warning will be issued + (in fact this is harmless, but the PNG data produced may be sub-optimal). + +Version 1.6.0beta17 [March 10, 2012] + Fixed PNG_LIBPNG_BUILD_BASE_TYPE definition. + Reject all iCCP chunks after the first, even if the first one is invalid. + Deflate/inflate was reworked to move common zlib calls into single + functions [rw]util.c. A new shared keyword check routine was also added + and the 'zbuf' is no longer allocated on progressive read. It is now + possible to call png_inflate() incrementally. A warning is no longer + issued if the language tag or translated keyword in the iTXt chunk + has zero length. + If benign errors are disabled use maximum window on ancilliary inflate. + This works round a bug introduced in 1.5.4 where compressed ancillary + chunks could end up with a too-small windowBits value in the deflate + header. + +Version 1.6.0beta18 [March 16, 2012] + Issue a png_benign_error() instead of png_warning() about bad palette index. + In pngtest, treat benign errors as errors if "-strict" is present. + Fixed an off-by-one error in the palette index checking function. + Fixed a compiler warning under Cygwin (Windows-7, 32-bit system) + Revised example.c to put text strings in a temporary character array + instead of directly assigning string constants to png_textp members. + This avoids compiler warnings when -Wwrite-strings is enabled. + Added output flushing to aid debugging under Visual Studio. Unfortunately + this is necessary because the VS2010 output window otherwise simply loses + the error messages on error (they weren't flushed to the window before + the process exited, apparently!) + Added configuration support for benign errors and changed the read + default. Also changed some warnings in the iCCP and sRGB handling + from to benign errors. Configuration now makes read benign + errors warnings and write benign errors to errors by default (thus + changing the behavior on read). The simplified API always forces + read benign errors to warnings (regardless of the system default, unless + this is disabled in which case the simplified API can't be built.) + +Version 1.6.0beta19 [March 18, 2012] + Work around for duplicate row start calls; added warning messages. + This turns on PNG_FLAG_DETECT_UNINITIALIZED to detect app code that + fails to call one of the 'start' routines (not enabled in libpng-1.5 + because it is technically an API change, since it did normally work + before.) It also makes duplicate calls to png_read_start_row (an + internal function called at the start of the image read) benign, as + they were before changes to use png_inflate_claim. Somehow webkit is + causing this to happen; this is probably a mis-feature in the zlib + changes so this commit is only a work-round. + Removed erroneous setting of DETECT_UNINITIALIZED and added more + checks. The code now does a png_error if an attempt is made to do the + row initialization twice; this is an application error and it has + serious consequences because the transform data in png_struct is + changed by each call. + Added application error reporting and added chunk names to read + benign errors; also added --strict to pngstest - not enabled + yet because a warning is produced. + Avoid the double gamma correction warning in the simplified API. + This allows the --strict option to pass in the pngstest checks + +Version 1.6.0beta20 [March 29, 2012] + Changed chunk handler warnings into benign errors, incrementally load iCCP + Added checksum-icc.c to contrib/tools + Prevent PNG_EXPAND+PNG_SHIFT doing the shift twice. + Recognize known sRGB ICC profiles while reading; prefer writing the + iCCP profile over writing the sRGB chunk, controlled by the + PNG_sRGB_PROFILE_CHECKS option. + Revised png_set_text_2() to avoid potential memory corruption (fixes + CVE-2011-3048, also known as CVE-2012-3425). + +Version 1.6.0beta21 [April 27, 2012] + Revised scripts/makefile.darwin: use system zlib; remove quotes around + architecture list; add missing ppc architecture; add architecture options + to shared library link; don't try to create a shared lib based on missing + RELEASE variable. + Enable png_set_check_for_invalid_index() for both read and write. + Removed #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED in pngpriv.h around + declaration of png_handle_unknown(). + Added -lssp_nonshared in a comment in scripts/makefile.freebsd + and changed deprecated NOOBJ and NOPROFILE to NO_OBJ and NO_PROFILE. + +Version 1.6.0beta22 [May 23, 2012] + Removed need for -Wno-cast-align with clang. clang correctly warns on + alignment increasing pointer casts when -Wcast-align is passed. This + fixes the cases that clang warns about either by eliminating the + casts from png_bytep to png_uint_16p (pngread.c), or, for pngrutil.c + where the cast is previously verified or pngstest.c where it is OK, by + introducing new png_aligncast macros to do the cast in a way that clang + accepts. + +Version 1.6.0beta23 [June 6, 2012] + Revised CMakeLists.txt to not attempt to make a symlink under mingw. + Made fixes for new optimization warnings from gcc 4.7.0. The compiler + performs an optimization which is safe; however it then warns about it. + Changing the type of 'palette_number' in pngvalid.c removes the warning. + Do not depend upon a GCC feature macro being available for use in generating + the linker mapfile symbol prefix. + Improved performance of new do_check_palette_indexes() function (only + update the value when it actually increases, move test for whether + the check is wanted out of the function. + +Version 1.6.0beta24 [June 7, 2012] + Don't check palette indexes if num_palette is 0 (as it can be in MNG files). + +Version 1.6.0beta25 [June 16, 2012] + Revised png_set_keep_unknown_chunks() so num_chunks < 0 means ignore all + unknown chunks and all known chunks except for IHDR, PLTE, tRNS, IDAT, + and IEND. Previously it only meant ignore all unknown chunks, the + same as num_chunks == 0. Revised png_image_skip_unused_chunks() to + provide a list of chunks to be processed instead of a list of chunks to + ignore. Revised contrib/gregbook/readpng2.c accordingly. + +Version 1.6.0beta26 [July 10, 2012] + Removed scripts/makefile.cegcc from the *.zip and *.7z distributions; it + depends on configure, which is not included in those archives. + Moved scripts/chkfmt to contrib/tools. + Changed "a+w" to "u+w" in Makefile.in to fix CVE-2012-3386. + +Version 1.6.0beta27 [August 11, 2012] + Do not compile PNG_DEPRECATED, PNG_ALLOC and PNG_PRIVATE when __GNUC__ < 3. + Do not use __restrict when GNUC is <= 3.1 + Removed references to png_zalloc() and png_zfree() from the manual. + Fixed configurations where floating point is completely disabled. Because + of the changes to support symbol prefixing PNG_INTERNAL_FUNCTION declares + floating point APIs during libpng builds even if they are completely + disabled. This requires the png floating point types (png_double*) to be + declared even though the functions are never actually defined. This + change provides a dummy definition so that the declarations work, yet any + implementation will fail to compile because of an incomplete type. + Re-eliminated the use of strcpy() in pngtest.c. An unncessary use of + strcpy() was accidentally re-introduced in libpng16; this change replaces + it with strncpy(). + Eliminated use of png_sizeof(); use sizeof() instead. + Use a consistent style for (sizeof type) and (sizeof (array)) + Cleanup of png_set_filler(). This function does very different things on + read and write. In libpng 1.6 the two cases can be distinguished and + considerable code cleanup, and extra error checking, is possible. This + makes calls on the write side that have no effect be ignored with a + png_app_error(), which can be disabled in the app using + png_set_benign_errors(), and removes the spurious use of usr_channels + on the read side. + Insist on autotools 1.12.1 for git builds because there are security issues + with 1.12 and insisting on anything less would allow 1.12 to be used. + Removed info_ptr->signature[8] from WRITE-only builds. + Add some conditions for compiling png_fixed(). This is a small function + but it requires "-lm" on some platforms. + Cause pngtest --strict to fail on any warning from libpng (not just errors) + and cause it not to fail at the comparison step if libpng lacks support + for writing chunks that it reads from the input (currently only implemented + for compressed text chunks). + Make all three "make check" test programs work without READ or WRITE support. + Now "make check" will succeed even if libpng is compiled with -DPNG_NO_READ + or -DPNG_NO_WRITE. The tests performed are reduced, but the basic reading + and writing of a PNG file is always tested by one or more of the tests. + Consistently use strlen(), memset(), memcpy(), and memcmp() instead of the + png_strlen(), png_memset(), png_memcpy(), and png_memcmp() macros. + Removed the png_sizeof(), png_strlen(), png_memset(), png_memcpy(), and + png_memcmp() macros. + Work around gcc 3.x and Microsoft Visual Studio 2010 complaints. Both object + to the split initialization of num_chunks. + +Version 1.6.0beta28 [August 29, 2012] + Unknown handling fixes and clean up. This adds more correct option + control of the unknown handling, corrects the pre-existing bug where + the per-chunk 'keep' setting is ignored and makes it possible to skip + IDAT chunks in the sequential reader (broken in earlier 1.6 versions). + There is a new test program, test-unknown.c, which is a work in progress + (not currently part of the test suite). Comments in the header files now + explain how the unknown handling works. + Allow fine grain control of unknown chunk APIs. This change allows + png_set_keep_unknown_chunks() to be turned off if not required and causes + both read and write to behave appropriately (on read this is only possible + if the user callback is used to handle unknown chunks). The change + also removes the support for storing unknown chunks in the info_struct + if the only unknown handling enabled is via the callback, allowing libpng + to be configured with callback reading and none of the unnecessary code. + Corrected fix for unknown handling in pngtest. This reinstates the + libpng handling of unknown chunks other than vpAg and sTER (including + unsafe-to-copy chunks which were dropped before) and eliminates the + repositioning of vpAg and sTER in pngtest.png by changing pngtest.png + (so the chunks are where libpng would put them). + Added "tunknown" test and corrected a logic error in png_handle_unknown() + when SAVE support is absent. Moved the shell test scripts for + contrib/libtests from the libpng top directory to contrib/libtests. + png_handle_unknown() must always read or skip the chunk, if + SAVE_UNKNOWN_CHUNKS is turned off *and* the application does not set + a user callback an unknown chunk will not be read, leading to a read + error, which was revealed by the "tunknown" test. + Cleaned up and corrected ICC profile handling. + contrib/libtests/makepng: corrected 'rgb' and 'gray' cases. profile_error + messages could be truncated; made a correct buffer size calculation and + adjusted pngerror.c appropriately. png_icc_check_* checking improved; + changed the functions to receive the correct color type of the PNG on read + or write and check that it matches the color space of the profile (despite + what the comments said before, there is danger in assuming the app will + cope correctly with an RGB profile on a grayscale image and, since it + violates the PNG spec, allowing it is certain to produce inconsistent + app behavior and might even cause app crashes.) Check that profiles + contain the tags needed to process the PNG (tags all required by the ICC + spec). Removed unused PNG_STATIC from pngpriv.h. + +Version 1.6.0beta29 [September 4, 2012] + Fixed the simplified API example programs to add the *colormap parameter + to several of he API and improved the error message if the version field + is not set. + Added contrib/examples/* to the *.zip and *.7z distributions. + Updated simplified API synopses and description of the png_image structure + in the manual. + Made makepng and pngtest produce identical PNGs, add "--relaxed" option + to pngtest. The "--relaxed" option turns off the benign errors that are + enabled by default in pre-RC builds. makepng can now write ICC profiles + where the length has not been extended to a multiple of 4, and pngtest + now intercepts all libpng errors, allowing the previously-introduced + "--strict test" on no warnings to actually work. + Improved ICC profile handling including cHRM chunk generation and fixed + Cygwin+MSVC build errors. The ICC profile handling now includes more + checking. Several errors that caused rejection of the profile are now + handled with a warning in such a way that the invalid profiles will be + read by default in release (but not pre-RC) builds but will not be + written by default. The easy part of handling the cHRM chunk is written, + where the ICC profile contains the required data. The more difficult + part plus guessing a gAMA value requires code to pass selected RGB values + through the profile. + +Version 1.6.0beta30 [October 24, 2012] + Changed ICC profile matrix/vector types to not depend on array type rules. + By the ANSI-C standard the new types should be identical to the previous + versions, and all known versions of gcc tested with the previous versions + except for GCC-4.2.1 work with this version. The change makes the ANSI-C + rule that const applied to an array of elements applies instead to the + elements in the array moot by explicitly applying const to the base + elements of the png_icc_matrix and png_icc_vector types. The accidental + (harmless) 'const' previously applied to the parameters of two of the + functions have also been removed. + Added a work around for GCC 4.2 optimization bug. + Marked the broken (bad white point) original HP sRGB profiles correctly and + correct comments. + Added -DZ_SOLO to contrib/pngminim/*/makefile to work with zlib-1.2.7 + Use /MDd for vstudio debug builds. Also added pngunkown to the vstudio + builds, fixed build errors and corrected a minor exit code error in + pngvalid if the 'touch' file name is invalid. + Add updated WARNING file to projects/vstudio from libpng 1.5/vstudio + Fixed build when using #define PNG_NO_READ_GAMMA in png_do_compose() in + pngrtran.c (Domani Hannes). + +Version 1.6.0beta31 [November 1, 2012] + Undid the erroneous change to vstudio/pngvalid build in libpng-1.6.0beta30. + Made pngvalid so that it will build outside the libpng source tree. + Made builds -DPNG_NO_READ_GAMMA compile (the unit tests still fail). + Made PNG_NO_READ_GAMMA switch off interfaces that depend on READ_GAMMA. + Prior to 1.6.0 switching off READ_GAMMA did unpredictable things to the + interfaces that use it (specifically, png_do_background in 1.4 would + simply display composite for grayscale images but do composition + with the incorrect arithmetic for color ones). In 1.6 the semantic + of -DPNG_NO_READ_GAMMA is changed to simply disable any interface that + depends on it; this obliges people who set it to consider whether they + really want it off if they happen to use any of the interfaces in + question (typically most users who disable it won't). + Fixed GUIDs in projects/vstudio. Some were duplicated or missing, + resulting in VS2010 having to update the files. + Removed non-working ICC profile support code that was mostly added to + libpng-1.6.0beta29 and beta30. There was too much code for too little + gain; implementing full ICC color correction may be desireable but is left + up to applications. + +Version 1.6.0beta32 [November 25, 2012] + Fixed an intermittent SEGV in pngstest due to an uninitialized array element. + Added the ability for contrib/libtests/makepng.c to make a PNG with just one + color. This is useful for debugging pngstest color inaccuracy reports. + Fixed error checking in the simplified write API (Olaf van der Spek) + Made png_user_version_check() ok to use with libpng version 1.10.x and later. + +Version 1.6.0beta33 [December 15, 2012] + Fixed typo in png.c (PNG_SET_CHUNK_MALLOC_MAX should be PNG_CHUNK_MALLOC_MAX) + that causes the MALLOC_MAX limit not to work (John Bowler) + Change png_warning() to png_app_error() in pngwrite.c and comment the + fall-through condition. + Change png_warning() to png_app_warning() in png_write_tRNS(). + Rearranged the ARM-NEON optimizations: Isolated the machine specific code + to the hardware subdirectory and added comments to pngrutil.c so that + implementors of other optimizations know what to do. + Fixed cases of unquoted DESTDIR in Makefile.am + Rebuilt Makefile.in, etc., with autoconf-2.69 and automake-1.12.5. + +Version 1.6.0beta34 [December 19, 2012] + Cleaned up whitespace in the synopsis portion of the manpage "libpng.3" + Disassembled the version number in scripts/options.awk (necessary for + building on SunOs). + +Version 1.6.0beta35 [December 23, 2012] + Made default Zlib compression settings be configurable. This adds #defines to + pnglibconf.h to control the defaults. + Fixed Windows build issues, enabled ARM compilation. Various warnings issued + by earlier versions of GCC fixed for Cygwin and Min/GW (which both use old + GCCs.) ARM support is enabled by default in zlib.props (unsupported by + Microsoft) and ARM compilation is made possible by deleting the check for + x86. The test programs cannot be run because they are not signed. + +Version 1.6.0beta36 [January 2, 2013] + Discontinued distributing libpng-1.x.x.tar.bz2. + Discontinued distributing libpng-1.7.0-1.6.0-diff.txt and similar. + Rebuilt configure with autoconf-2.69 (inadvertently not done in beta33) + Fixed 'make distcheck' on SUN OS - libpng.so was not being removed + +Version 1.6.0beta37 [January 10, 2013] + Fixed conceivable but difficult to repro overflow. Also added two test + programs to generate and test a PNG which should have the problem. + +Version 1.6.0beta39 [January 19, 2013] + Again corrected attempt at overflow detection in png_set_unknown_chunks() + (CVE-2013-7353). Added overflow detection in png_set_sPLT() and + png_set_text_2() (CVE-2013-7354). + +Version 1.6.0beta40 [January 20, 2013] + Use consistent handling of overflows in text, sPLT and unknown png_set_* APIs + +Version 1.6.0rc01 [January 26, 2013] + No changes. + +Version 1.6.0rc02 [February 4, 2013] + Added png_get_palette_max() function. + +Version 1.6.0rc03 [February 5, 2013] + Fixed the png_get_palette_max API. + +Version 1.6.0rc04 [February 7, 2013] + Turn serial tests back on (recently turned off by autotools upgrade). + +Version 1.6.0rc05 [February 8, 2013] + Update manual about png_get_palette_max(). + +Version 1.6.0rc06 [February 9, 2013] + Fixed missing dependency in --prefix builds The intermediate + internal 'prefix.h' file can only be generated correctly after + pnglibconf.h, however the dependency was not in Makefile.am. The + symptoms are unpredictable depending on the order make chooses to + build pngprefix.h and pnglibconf.h, often the error goes unnoticed + because there is a system pnglibconf.h to use instead. + +Version 1.6.0rc07 [February 10, 2013] + Enclosed the new png_get_palette_max in #ifdef PNG_GET_PALETTE_MAX_SUPPORTED + block, and revised pnglibconf.h and pnglibconf.h.prebuilt accordingly. + +Version 1.6.0rc08 [February 10, 2013] + Fix typo in png.h #ifdef + +Version 1.6.0 [February 14, 2013] + No changes. + +Version 1.6.1beta01 [February 16, 2013] + Made symbol prefixing work with the ARM neon optimizations. Also allow + pngpriv.h to be included for preprocessor definitions only, so it can + be used in non-C/C++ files. Back ported from libpng 1.7. + Made sRGB check numbers consistent. + Ported libpng 1.5 options.awk/dfn file handling to 1.6, fixed one bug. + Removed cc -E workround, corrected png_get_palette_max API Tested on + SUN OS cc 5.9, which demonstrates the tokenization problem previously + avoided by using /lib/cpp. Since all .dfn output is now protected in + double quotes unless it is to be macro substituted the fix should + work everywhere. + Enabled parallel tests - back ported from libpng-1.7. + scripts/pnglibconf.dfa formatting improvements back ported from libpng17. + Fixed a race condition in the creation of the build 'scripts' directory + while building with a parallel make. + Use approved/supported Android method to check for NEON, use Linux/POSIX + 1003.1 API to check /proc/self/auxv avoiding buffer allocation and other + library calls (ported from libpng15). + +Version 1.6.1beta02 [February 19, 2013] + Use parentheses more consistently in "#if defined(MACRO)" tests. + Folded long lines. + Reenabled code to allow zero length PLTE chunks for MNG. + +Version 1.6.1beta03 [February 22, 2013] + Fixed ALIGNED_MEMORY support. + Added a new configure option: + --enable-arm-neon=always will stop the run-time checks. New checks + within arm/arm_init.c will cause the code not to be compiled unless + __ARM_NEON__ is set. This should make it fail safe (if someone asks + for it on then the build will fail if it can't be done.) + Updated the INSTALL document. + +Version 1.6.1beta04 [February 27, 2013] + Revised INSTALL to recommend using CPPFLAGS instead of INCLUDES. + Revised scripts/makefile.freebsd to respect ZLIBLIB and ZLIBINC. + Revised scripts/dfn.awk to work with the buggy MSYS awk that has trouble + with CRLF line endings. + +Version 1.6.1beta05 [March 1, 2013] + Avoid a possible memory leak in contrib/gregbook/readpng.c + +Version 1.6.1beta06 [March 4, 2013] + Better documentation of unknown handling API interactions. + Corrected Android builds and corrected libpng.vers with symbol + prefixing. It also makes those tests compile and link on Android. + Added an API png_set_option() to set optimization options externally, + providing an alternative and general solution for the non-portable + run-time tests used by the ARM Neon code, using the PNG_ARM_NEON option. + The order of settings vs options in pnglibconf.h is reversed to allow + settings to depend on options and options can now set (or override) the + defaults for settings. + +Version 1.6.1beta07 [March 7, 2013] + Corrected simplified API default gamma for color-mapped output, added + a flag to change default. In 1.6.0 when the simplified API was used + to produce color-mapped output from an input image with no gamma + information the gamma assumed for the input could be different from + that assumed for non-color-mapped output. In particular 16-bit depth + input files were assumed to be sRGB encoded, whereas in the 'direct' + case they were assumed to have linear data. This was an error. The + fix makes the simplified API treat all input files the same way and + adds a new flag to the png_image::flags member to allow the + application/user to specify that 16-bit files contain sRGB data + rather than the default linear. + Fixed bugs in the pngpixel and makepng test programs. + +Version 1.6.1beta08 [March 7, 2013] + Fixed CMakelists.txt to allow building a single variant of the library + (Claudio Bley): + Introduced a PNG_LIB_TARGETS variable that lists all activated library + targets. It is an error if this variable ends up empty, ie. you have + to build at least one library variant. + Made the *_COPY targets only depend on library targets actually being build. + Use PNG_LIB_TARGETS to unify a code path. + Changed the CREATE_SYMLINK macro to expect the full path to a file as the + first argument. When symlinking the filename component of that path is + determined and used as the link target. + Use copy_if_different in the CREATE_SYMLINK macro. + +Version 1.6.1beta09 [March 13, 2013] + Eliminated two warnings from the Intel C compiler. The warnings are + technically valid, although a reasonable treatment of division would + show it to be incorrect. + +Version 1.6.1rc01 [March 21, 2013] + No changes. + +Version 1.6.1 [March 28, 2013] + No changes. + +Version 1.6.2beta01 [April 14, 2013] + Updated documentation of 1.5.x to 1.6.x changes in iCCP chunk handling. + Fixed incorrect warning of excess deflate data. End condition - the + warning would be produced if the end of the deflate stream wasn't read + in the last row. The warning is harmless. + Corrected the test on user transform changes on read. It was in the + png_set of the transform function, but that doesn't matter unless the + transform function changes the rowbuf size, and that is only valid if + transform_info is called. + Corrected a misplaced closing bracket in contrib/libtests/pngvalid.c + (Flavio Medeiros). + Corrected length written to uncompressed iTXt chunks (Samuli Suominen). + Bug was introduced in libpng-1.6.0. + +Version 1.6.2rc01 [April 18, 2013] + Added contrib/tools/fixitxt.c, to repair the erroneous iTXt chunk length + written by libpng-1.6.0 and 1.6.1. + Disallow storing sRGB information when the sRGB is not supported. + +Version 1.6.2rc02 [April 18, 2013] + Merge pngtest.c with libpng-1.7.0 + +Version 1.6.2rc03 [April 22, 2013] + Trivial spelling cleanup. + +Version 1.6.2rc04 and 1.6.2rc05 [omitted] + +Version 1.6.2rc06 [April 24, 2013] + Reverted to version 1.6.2rc03. Recent changes to arm/neon support + have been ported to libpng-1.7.0beta09 and will reappear in version + 1.6.3beta01. + +Version 1.6.2 [April 25, 2013] + No changes. + +Version 1.6.3beta01 [April 25, 2013] + Revised stack marking in arm/filter_neon.S and configure.ac. + Ensure that NEON filter stuff is completely disabled when switched 'off'. + Previously the ARM NEON specific files were still built if the option + was switched 'off' as opposed to being explicitly disabled. + +Version 1.6.3beta02 [April 26, 2013] + Test for 'arm*' not just 'arm' in the host_cpu configure variable. + Rebuilt the configure scripts. + +Version 1.6.3beta03 [April 30, 2013] + Expanded manual paragraph about writing private chunks, particularly + the need to call png_set_keep_unknown_chunks() when writing them. + Avoid dereferencing NULL pointer possibly returned from + png_create_write_struct() (Andrew Church). + +Version 1.6.3beta05 [May 9, 2013] + Calculate our own zlib windowBits when decoding rather than trusting the + CMF bytes in the PNG datastream. + Added an option to force maximum window size for inflating, which was + the behavior of libpng15 and earlier, via a new PNG_MAXIMUM_INFLATE_WINDOW + option for png_set_options(). + Added png-fix-itxt and png-fix-too-far-back to the built programs and + removed warnings from the source code and timepng that are revealed as + a result. + Detect wrong libpng versions linked to png-fix-too-far-back, which currently + only works with libpng versions that can be made to reliably fail when + the deflate data contains an out-of-window reference. This means only + 1.6 and later. + Fixed gnu issues: g++ needs a static_cast, gcc 4.4.7 has a broken warning + message which it is easier to work round than ignore. + Updated contrib/pngminus/pnm2png.c (Paul Stewart): + Check for EOF + Ignore "#" delimited comments in input file to pnm2png.c. + Fixed whitespace handling + Added a call to png_set_packing() + Initialize dimension values so if sscanf fails at least we have known + invalid values. + Attempt to detect configuration issues with png-fix-too-far-back, which + requires both the correct libpng and the correct zlib to function + correctly. + Check ZLIB_VERNUM for mismatches, enclose #error in quotes + Added information in the documentation about problems with and fixes for + the bad CRC and bad iTXt chunk situations. + +Version 1.6.3beta06 [May 12, 2013] + Allow contrib/pngminus/pnm2png.c to compile without WRITE_INVERT and + WRITE_PACK supported (writes error message that it can't read P1 or + P4 PBM files). + Improved png-fix-too-far-back usage message, added --suffix option. + Revised contrib/pngminim/*/makefile to generate pnglibconf.h with the + right zlib header files. + Separated CPPFLAGS and CFLAGS in contrib/pngminim/*/makefile + +Version 1.6.3beta07 [June 8, 2013] + Removed a redundant test in png_set_IHDR(). + Added set(CMAKE_CONFIGURATION_TYPES ...) to CMakeLists.txt (Andrew Hundt) + Deleted set(CMAKE_BUILD_TYPE) block from CMakeLists.txt + Enclose the prototypes for the simplified write API in + #ifdef PNG_STDIO_SUPPORTED/#endif + Make ARM NEON support work at compile time (not just configure time). + This moves the test on __ARM_NEON__ into pngconf.h to avoid issues when + using a compiler that compiles for multiple architectures at one time. + Removed PNG_FILTER_OPTIMIZATIONS and PNG_ARM_NEON_SUPPORTED from + pnglibconf.h, allowing more of the decisions to be made internally + (pngpriv.h) during the compile. Without this, symbol prefixing is broken + under certain circumstances on ARM platforms. Now only the API parts of + the optimizations ('check' vs 'api') are exposed in the public header files + except that the new setting PNG_ARM_NEON_OPT documents how libpng makes the + decision about whether or not to use the optimizations. + Protect symbol prefixing against CC/CPPFLAGS/CFLAGS useage. + Previous iOS/Xcode fixes for the ARM NEON optimizations moved the test + on __ARM_NEON__ from configure time to compile time. This breaks symbol + prefixing because the definition of the special png_init_filter_functions + call was hidden at configure time if the relevant compiler arguments are + passed in CFLAGS as opposed to CC. This change attempts to avoid all + the confusion that would result by declaring the init function even when + it is not used, so that it will always get prefixed. + +Version 1.6.3beta08 [June 18, 2013] + Revised libpng.3 so that "doclifter" can process it. + +Version 1.6.3beta09 [June 27, 2013] + Revised example.c to illustrate use of PNG_DEFAULT_sRGB and PNG_GAMMA_MAC_18 + as parameters for png_set_gamma(). These have been available since + libpng-1.5.4. + Renamed contrib/tools/png-fix-too-far-back.c to pngfix.c and revised it + to check all compressed chunks known to libpng. + +Version 1.6.3beta10 [July 5, 2013] + Updated documentation to show default behavior of benign errors correctly. + Only compile ARM code when PNG_READ_SUPPORTED is defined. + Fixed undefined behavior in contrib/tools/pngfix.c and added new strip + option. pngfix relied on undefined behavior and even a simple change from + gcc to g++ caused it to fail. The new strip option 'unsafe' has been + implemented and is the default if --max is given. Option names have + been clarified, with --strip=transform now stripping the bKGD chunk, + which was stripped previously with --strip=unused. + Added all documented chunk types to pngpriv.h + Unified pngfix.c source with libpng17. + +Version 1.6.3rc01 [July 11, 2013] + No changes. + +Version 1.6.3 [July 18, 2013] + Revised manual about changes in iTXt chunk handling made in libpng-1.6.0. + Added "/* SAFE */" comments in pngrutil.c and pngrtran.c where warnings + may be erroneously issued by code-checking applications. + +Version 1.6.4beta01 [August 21, 2013] + Added information about png_set_options() to the manual. + Delay calling png_init_filter_functions() until a row with nonzero filter + is found. + +Version 1.6.4beta02 [August 30, 2013] + Fixed inconsistent conditional compilation of png_chunk_unknown_handling() + prototype, definition, and usage. Made it depend on + PNG_HANDLE_AS_UNKNOWN_SUPPORTED everywhere. + +Version 1.6.4rc01 [September 5, 2013] + No changes. + +Version 1.6.4 [September 12, 2013] + No changes. + +Version 1.6.5 [September 14, 2013] + Removed two stray lines of code from arm/arm_init.c. + +Version 1.6.6 [September 16, 2013] + Removed two stray lines of code from arm/arm_init.c, again. + +Version 1.6.7beta01 [September 30, 2013] + Revised unknown chunk code to correct several bugs in the NO_SAVE_/NO_WRITE + combination + Allow HANDLE_AS_UNKNOWN to work when other options are configured off. Also + fixed the pngminim makefiles to work when $(MAKEFLAGS) contains stuff + which terminates the make options (as by default in recent versions of + Gentoo). + Avoid up-cast warnings in pngvalid.c. On ARM the alignment requirements of + png_modifier are greater than that of png_store and as a consequence + compilation of pngvalid.c results in a warning about increased alignment + requirements because of the bare cast to (png_modifier*). The code is safe, + because the pointer is known to point to a stack allocated png_modifier, + but this change avoids the warning. + Fixed default behavior of ARM_NEON_API. If the ARM NEON API option was + compiled without the CHECK option it defaulted to on, not off. + Check user callback behavior in pngunknown.c. Previous versions compiled + if SAVE_UNKNOWN was not available but did nothing since the callback + was never implemented. + Merged pngunknown.c with 1.7 version and back ported 1.7 improvements/fixes + +Version 1.6.7beta02 [October 12, 2013] + Made changes for compatibility with automake 1.14: + 1) Added the 'compile' program to the list of programs that must be cleaned + in autogen.sh + 2) Added 'subdir-objects' which causes .c files in sub-directories to be + compiled such that the corresponding .o files are also in the + sub-directory. This is because automake 1.14 warns that the + current behavior of compiling to the top level directory may be removed + in the future. + 3) Updated dependencies on pnglibconf.h to match the new .o locations and + added all the files in contrib/libtests and contrib/tools that depend + on pnglibconf.h + 4) Added 'BUILD_SOURCES = pnglibconf.h'; this is the automake recommended + way of handling the dependencies of sources that are machine generated; + unfortunately it only works if the user does 'make all' or 'make check', + so the dependencies (3) are still required. + Cleaned up (char*) casts of zlib messages. The latest version of the Intel C + compiler complains about casting a string literal as (char*), so copied the + treatment of z_const from the library code into pngfix.c + Simplified error message code in pngunknown. The simplification has the + useful side effect of avoiding a bogus warning generated by the latest + version of the Intel C compiler (it objects to + condition ? string-literal : string-literal). + Make autogen.sh work with automake 1.13 as well as 1.14. Do this by always + removing the 1.14 'compile' script but never checking for it. + +Version 1.6.7beta03 [October 19, 2013] + Added ARMv8 support (James Yu ). Added file + arm/filter_neon_intrinsics.c; enable with -mfpu=neon. + Revised pngvalid to generate size images with as many filters as it can + manage, limited by the number of rows. + Cleaned up ARM NEON compilation handling. The tests are now in pngpriv.h + and detect the broken GCC compilers. + +Version 1.6.7beta04 [October 26, 2013] + Allow clang derived from older GCC versions to use ARM intrinsics. This + causes all clang builds that use -mfpu=neon to use the intrinsics code, + not the assembler code. This has only been tested on iOS 7. It may be + necessary to exclude some earlier clang versions but this seems unlikely. + Changed NEON implementation selection mechanism. This allows assembler + or intrinsics to be turned on at compile time during the build by defining + PNG_ARM_NEON_IMPLEMENTATION to the correct value (2 or 1). This macro + is undefined by default and the build type is selected in pngpriv.h. + +Version 1.6.7rc01 [November 2, 2013] + No changes. + +Version 1.6.7rc02 [November 7, 2013] + Fixed #include in filter_neon_intrinsics.c and ctype macros. The ctype char + checking macros take an unsigned char argument, not a signed char. + +Version 1.6.7 [November 14, 2013] + No changes. + +Version 1.6.8beta01 [November 24, 2013] + Moved prototype for png_handle_unknown() in pngpriv.h outside of + the #ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED/#endif block. + Added "-Wall" to CFLAGS in contrib/pngminim/*/makefile + Conditionally compile some unused functions reported by -Wall in + pngminim. + Fixed 'minimal' builds. Various obviously useful minimal configurations + don't build because of missing contrib/libtests test programs and + overly complex dependencies in scripts/pnglibconf.dfa. This change + adds contrib/conftest/*.dfa files that can be used in automatic build + scripts to ensure that these configurations continue to build. + Enabled WRITE_INVERT and WRITE_PACK in contrib/pngminim/encoder. + Fixed pngvalid 'fail' function declaration on the Intel C Compiler. + This reverts to the previous 'static' implementation and works round + the 'unused static function' warning by using PNG_UNUSED(). + +Version 1.6.8beta02 [November 30, 2013] + Removed or marked PNG_UNUSED some harmless "dead assignments" reported + by clang scan-build. + Changed tabs to 3 spaces in png_debug macros and changed '"%s"m' + to '"%s" m' to improve portability among compilers. + Changed png_free_default() to free() in pngtest.c + +Version 1.6.8rc01 [December 12, 2013] + Tidied up pngfix inits and fixed pngtest no-write builds. + +Version 1.6.8rc02 [December 14, 2013] + Handle zero-length PLTE chunk or NULL palette with png_error() + instead of png_chunk_report(), which by default issues a warning + rather than an error, leading to later reading from a NULL pointer + (png_ptr->palette) in png_do_expand_palette(). This is CVE-2013-6954 + and VU#650142. Libpng-1.6.1 through 1.6.7 are vulnerable. + Libpng-1.6.0 and earlier do not have this bug. + +Version 1.6.8 [December 19, 2013] + No changes. + +Version 1.6.9beta01 [December 26, 2013] + Bookkeeping: Moved functions around (no changes). Moved transform + function definitions before the place where they are called so that + they can be made static. Move the intrapixel functions and the + grayscale palette builder out of the png?tran.c files. The latter + isn't a transform function and is no longer used internally, and the + former MNG specific functions are better placed in pngread/pngwrite.c + Made transform implementation functions static. This makes the internal + functions called by png_do_{read|write}_transformations static. On an + x86-64 DLL build (Gentoo Linux) this reduces the size of the text + segment of the DLL by 1208 bytes, about 0.6%. It also simplifies + maintenance by removing the declarations from pngpriv.h and allowing + easier changes to the internal interfaces. + Rebuilt configure scripts with automake-1.14.1 and autoconf-2.69 + in the tar distributions. + +Version 1.6.9beta02 [January 1, 2014] + Added checks for libpng 1.5 to pngvalid.c. This supports the use of + this version of pngvalid in libpng 1.5 + Merged with pngvalid.c from libpng-1.7 changes to create a single + pngvalid.c + Removed #error macro from contrib/tools/pngfix.c (Thomas Klausner). + Merged pngrio.c, pngtrans.c, pngwio.c, and pngerror.c with libpng-1.7.0 + Merged libpng-1.7.0 changes to make no-interlace configurations work + with test programs. + Revised pngvalid.c to support libpng 1.5, which does not support the + PNG_MAXIMUM_INFLATE_WINDOW option, so #define it out when appropriate in + pngvalid.c + Allow unversioned links created on install to be disabled in configure. + In configure builds 'make install' changes/adds links like png.h + and libpng.a to point to the newly installed, versioned, files (e.g. + libpng17/png.h and libpng17.a). Three new configure options and some + rearrangement of Makefile.am allow creation of these links to be disabled. + +Version 1.6.9beta03 [January 10, 2014] + Removed potentially misleading warning from png_check_IHDR(). + +Version 1.6.9beta04 [January 20, 2014] + Updated scripts/makefile.* to use CPPFLAGS (Cosmin). + Added clang attribute support (Cosmin). + +Version 1.6.9rc01 [January 28, 2014] + No changes. + +Version 1.6.9rc02 [January 30, 2014] + Quiet an uninitialized memory warning from VC2013 in png_get_png(). + +Version 1.6.9 [February 6, 2014] + +Version 1.6.10beta01 [February 9, 2014] + Backported changes from libpng-1.7.0beta30 and beta31: + Fixed a large number of instances where PNGCBAPI was omitted from + function definitions. + Added pngimage test program for png_read_png() and png_write_png() + with two new test scripts. + Removed dependence on !PNG_READ_EXPAND_SUPPORTED for calling + png_set_packing() in png_read_png(). + Fixed combination of ~alpha with shift. On read invert alpha, processing + occurred after shift processing, which causes the final values to be + outside the range that should be produced by the shift. Reversing the + order on read makes the two transforms work together correctly and mirrors + the order used on write. + Do not read invalid sBIT chunks. Previously libpng only checked sBIT + values on write, so a malicious PNG writer could therefore cause + the read code to return an invalid sBIT chunk, which might lead to + application errors or crashes. Such chunks are now skipped (with + chunk_benign_error). + Make png_read_png() and png_write_png() prototypes in png.h depend + upon PNG_READ_SUPPORTED and PNG_WRITE_SUPPORTED. + Support builds with unsupported PNG_TRANSFORM_* values. All of the + PNG_TRANSFORM_* values are always defined in png.h and, because they + are used for both read and write in some cases, it is not reliable + to #if out ones that are totally unsupported. This change adds error + detection in png_read_image() and png_write_image() to do a + png_app_error() if the app requests something that cannot be done + and it adds corresponding code to pngimage.c to handle such options + by not attempting to test them. + +Version 1.6.10beta02 [February 23, 2014] + Moved redefines of png_error(), png_warning(), png_chunk_error(), + and png_chunk_warning() from pngpriv.h to png.h to make them visible + to libpng-calling applications. + Moved OS dependent code from arm/arm_init.c, to allow the included + implementation of the ARM NEON discovery function to be set at + build-time and provide sample implementations from the current code in the + contrib/arm-neon subdirectory. The __linux__ code has also been changed to + compile and link on Android by using /proc/cpuinfo, and the old linux code + is in contrib/arm-neon/linux-auxv.c. The new code avoids POSIX and Linux + dependencies apart from opening /proc/cpuinfo and is C90 compliant. + Check for info_ptr == NULL early in png_read_end() so we don't need to + run all the png_handle_*() and depend on them to return if info_ptr == NULL. + This improves the performance of png_read_end(png_ptr, NULL) and makes + it more robust against future programming errors. + Check for __has_extension before using it in pngconf.h, to + support older Clang versions (Jeremy Sequoia). + Treat CRC error handling with png_set_crc_action(), instead of with + png_set_benign_errors(), which has been the case since libpng-1.6.0beta18. + Use a user warning handler in contrib/gregbook/readpng2.c instead of default, + so warnings will be put on stderr even if libpng has CONSOLE_IO disabled. + Added png_ptr->process_mode = PNG_READ_IDAT_MODE in png_push_read_chunk + after recognizing the IDAT chunk, which avoids an infinite loop while + reading a datastream whose first IDAT chunk is of zero-length. + This fixes CERT VU#684412 and CVE-2014-0333. + Don't recognize known sRGB profiles as sRGB if they have been hacked, + but don't reject them and don't issue a copyright violation warning. + +Version 1.6.10beta03 [February 25, 2014] + Moved some documentation from png.h to libpng.3 and libpng-manual.txt + Minor editing of contrib/arm-neon/README and contrib/examples/*.c + +Version 1.6.10rc01 [February 27, 2014] + Fixed typos in the manual and in scripts/pnglibconf.dfa (CFLAGS -> CPPFLAGS + and PNG_USR_CONFIG -> PNG_USER_CONFIG). + +Version 1.6.10rc02 [February 28, 2014] + Removed unreachable return statement after png_chunk_error() + in pngrutil.c + +Version 1.6.10rc03 [March 4, 2014] + Un-deprecated png_data_freer(). + +Version 1.6.10 [March 6, 2014] + No changes. + +Version 1.6.11beta01 [March 17, 2014] + Use "if (value != 0)" instead of "if (value)" consistently. + Changed ZlibSrcDir from 1.2.5 to 1.2.8 in projects/vstudio. + Moved configuration information from the manual to the INSTALL file. + +Version 1.6.11beta02 [April 6, 2014] + Removed #if/#else/#endif from inside two pow() calls in pngvalid.c because + they were handled improperly by Portland Group's PGI-14.1 - PGI-14.3 + when using its "__builtin_pow()" function. + Silence 'unused parameter' build warnings (Cosmin Truta). + $(CP) is now used alongside $(RM_F). Also, use 'copy' instead of 'cp' + where applicable, and applied other minor makefile changes (Cosmin). + Don't warn about invalid dimensions exceeding user limits (Cosmin). + Allow an easy replacement of the default pre-built configuration + header with a custom header, via the make PNGLIBCONF_H_PREBUILT + macro (Cosmin). + +Version 1.6.11beta03 [April 6, 2014] + Fixed a typo in pngrutil.c, introduced in libpng-1.5.6, that interferes + with "blocky" expansion of sub-8-bit interlaced PNG files (Eric Huss). + Optionally use __builtin_bswap16() in png_do_swap(). + +Version 1.6.11beta04 [April 19, 2014] + Made progressive reading of interlaced images consistent with the + behavior of the sequential reader and consistent with the manual, by + moving some code out of the PNG_READ_INTERLACING_SUPPORTED blocks. The + row_callback now receives the proper pass number and unexpanded rows, when + png_combine_row() isn't built or used, and png_set_interlace_handling() + is not called. + Allow PNG_sRGB_PROFILE_CHECKING = (-1) to mean no sRGB profile checking. + +Version 1.6.11beta05 [April 26, 2014] + Do not reject ICC V2 profiles that lack padding (Kai-Uwe Behrmann). + Relocated closing bracket of the sRGB profile test loop to avoid getting + "Not recognizing known sRGB profile that has been edited" warning for + ICC V2 profiles that lack the MD5 signature in the profile header. + +Version 1.6.11beta06 [May 19, 2014] + Added PNG_SKIP_sRGB_CHECK_PROFILE choice for png_set_option(). + +Version 1.6.11rc01 [May 27, 2014] + No changes. + +Version 1.6.11rc02 [June 3, 2014] + Test ZLIB_VERNUM instead of PNG_ZLIB_VERNUM in contrib/tools/pngfix.c + +Version 1.6.11 [June 5, 2014] + No changes. + +Version 1.6.12rc01 [June 6, 2014] + Relocated new code from 1.6.11beta06 in png.c to a point after the + declarations (Max Stepin). + +Version 1.6.12rc02 [June 7, 2014] + Changed file permissions of contrib/tools/intgamma.sh, + test-driver, and compile from 0644 to 0755 (Cosmin). + +Version 1.6.12rc03 [June 8, 2014] + Ensure "__has_attribute()" macro exists before trying to use it with + old clang compilers (MacPorts Ticket #43939). + +Version 1.6.12 [June 12, 2014] + No changes. + +Version 1.6.13beta01 [July 4, 2014] + Quieted -Wsign-compare and -Wclobber compiler warnings in + contrib/pngminus/*.c + Added "(void) png_ptr;" where needed in contrib/gregbook to quiet + compiler complaints about unused pointers. + Split a long output string in contrib/gregbook/rpng2-x.c. + Added "PNG_SET_OPTION" requirement for sRGB chunk support to pnglibconf.dfa, + Needed for write-only support (John Bowler). + Changed "if defined(__ARM_NEON__)" to + "if (defined(__ARM_NEON__) || defined(__ARM_NEON))" (James Wu). + Fixed clang no-warning builds: png_digit was defined but never used. + +Version 1.6.13beta02 [July 21, 2014] + Fixed an incorrect separator ("/" should be "\") in scripts/makefile.vcwin32 + (bug report from Wolfgang S. Kechel). Bug was introduced in libpng-1.6.11. + Also fixed makefile.bc32, makefile.bor, makefile.msc, makefile.intel, and + makefile.tc3 similarly. + +Version 1.6.13beta03 [August 3, 2014] + Removed scripts/makefile.elf. It has not worked since libpng-1.5.0beta14 + due to elimination of the PNG_FUNCTION_EXPORT and PNG_DATA_EXPORT + definitions from pngconf.h. + Ensure that CMakeLists.txt makes the target "lib" directory before making + symbolic link into it (SourceForge bug report #226 by Rolf Timmermans). + +Version 1.6.13beta04 [August 8, 2014] + Added opinion that the ECCN (Export Control Classification Number) for + libpng is EAR99 to the README file. + Eliminated use of "$<" in makefile explicit rules, when copying + $PNGLIBCONF_H_PREBUILT. This does not work on some versions of make; + bug introduced in libpng version 1.6.11. + +Version 1.6.13rc01 [August 14, 2014] + Made "ccopts" agree with "CFLAGS" in scripts/makefile.hp* and makefile.*sunu + +Version 1.6.13 [August 21, 2014] + No changes. + +Version 1.6.14beta01 [September 14, 2014] + Guard usage of png_ptr->options with #ifdef PNG_SET_OPTION_SUPPORTED. + Do not build contrib/tools/pngfix.c when PNG_SETJMP_NOT_SUPPORTED, + to allow "make" to complete without setjmp support (bug report by + Claudio Fontana) + Add "#include " to contrib/tools/pngfix.c (John Bowler) + +Version 1.6.14beta02 [September 18, 2014] + Use nanosleep() instead of usleep() in contrib/gregbook/rpng2-x.c + because usleep() is deprecated. + Define usleep() in contrib/gregbook/rpng2-x.c if not already defined + in unistd.h and nanosleep() is not available; fixes error introduced + in libpng-1.6.13. + Disable floating point exception handling in pngvalid.c when + PNG_FLOATING_ARITHMETIC is not supported (bug report by "zootus + at users.sourceforge.net"). + +Version 1.6.14beta03 [September 19, 2014] + Define FE_DIVBYZERO, FE_INVALID, and FE_OVERFLOW in pngvalid.c if not + already defined. Revert floating point exception handling in pngvalid.c + to version 1.6.14beta01 behavior. + +Version 1.6.14beta04 [September 27, 2014] + Fixed incorrect handling of the iTXt compression flag in pngrutil.c + (bug report by Shunsaku Hirata). Bug was introduced in libpng-1.6.0. + +Version 1.6.14beta05 [October 1, 2014] + Added "option READ_iCCP enables READ_COMPRESSED_TEXT" to pnglibconf.dfa + +Version 1.6.14beta06 [October 5, 2014] + Removed unused "text_len" parameter from private function png_write_zTXt(). + Conditionally compile some code in png_deflate_claim(), when + PNG_WARNINGS_SUPPORTED and PNG_ERROR_TEXT_SUPPORTED are disabled. + Replaced repeated code in pngpread.c with PNG_PUSH_SAVE_BUFFER_IF_FULL. + Added "chunk iTXt enables TEXT" and "chunk zTXt enables TEXT" + to pnglibconf.dfa. + Removed "option READ_COMPRESSED_TEXT enables READ_TEXT" from pnglibconf.dfa, + to make it possible to configure a libpng that supports iCCP but not TEXT. + +Version 1.6.14beta07 [October 7, 2014] + Removed "option WRITE_COMPRESSED_TEXT enables WRITE_TEXT" from pnglibconf.dfa + Only mark text chunks as written after successfully writing them. + +Version 1.6.14rc01 [October 15, 2014] + Fixed some typos in comments. + +Version 1.6.14rc02 [October 17, 2014] + Changed png_convert_to_rfc_1123() to png_convert_to_rfc_1123_buffer() + in the manual, to reflect the change made in libpng-1.6.0. + Updated README file to explain that direct access to the png_struct + and info_struct members has not been permitted since libpng-1.5.0. + +Version 1.6.14 [October 23, 2014] + No changes. + +Version 1.6.15beta01 [October 29, 2014] + Changed "if (!x)" to "if (x == 0)" and "if (x)" to "if (x != 0)" + Simplified png_free_data(). + Added missing "ptr = NULL" after some instances of png_free(). + +Version 1.6.15beta02 [November 1, 2014] + Changed remaining "if (!x)" to "if (x == 0)" and "if (x)" to "if (x != 0)" + +Version 1.6.15beta03 [November 3, 2014] + Added PNG_USE_ARM_NEON configuration flag (Marcin Juszkiewicz). + +Version 1.6.15beta04 [November 4, 2014] + Removed new PNG_USE_ARM_NEON configuration flag and made a one-line + revision to configure.ac to support ARM on aarch64 instead (John Bowler). + +Version 1.6.15beta05 [November 5, 2014] + Use png_get_libpng_ver(NULL) instead of PNG_LIBPNG_VER_STRING in + example.c, pngtest.c, and applications in the contrib directory. + Avoid out-of-bounds memory access in png_user_version_check(). + Simplified and future-proofed png_user_version_check(). + Fixed GCC unsigned int->float warnings. Various versions of GCC + seem to generate warnings when an unsigned value is implicitly + converted to double. This is probably a GCC bug but this change + avoids the issue by explicitly converting to (int) where safe. + Free all allocated memory in pngimage. The file buffer cache was left + allocated at the end of the program, harmless but it causes memory + leak reports from clang. + Fixed array size calculations to avoid warnings. At various points + in the code the number of elements in an array is calculated using + sizeof. This generates a compile time constant of type (size_t) which + is then typically assigned to an (unsigned int) or (int). Some versions + of GCC on 64-bit systems warn about the apparent narrowing, even though + the same compiler does apparently generate the correct, in-range, + numeric constant. This adds appropriate, safe, casts to make the + warnings go away. + +Version 1.6.15beta06 [November 6, 2014] + Reverted use png_get_libpng_ver(NULL) instead of PNG_LIBPNG_VER_STRING + in the manual, example.c, pngtest.c, and applications in the contrib + directory. It was incorrect advice. + +Version 1.6.15beta07 [November 7, 2014] + Removed #ifdef PNG_16BIT_SUPPORTED/#endif around png_product2(); it is + needed by png_reciprocal2(). + Added #ifdef PNG_16BIT_SUPPORTED/#endif around png_log16bit() and + png_do_swap(). + Changed all "#endif /* PNG_FEATURE_SUPPORTED */" to "#endif /* FEATURE */" + +Version 1.6.15beta08 [November 8, 2014] + More housecleaning in *.h + +Version 1.6.15rc01 [November 13, 2014] + +Version 1.6.15rc02 [November 14, 2014] + The macros passed in the command line to Borland make were ignored if + similarly-named macros were already defined in makefiles. This behavior + is different from POSIX make and other make programs. Surround the + macro definitions with ifndef guards (Cosmin). + +Version 1.6.15rc03 [November 16, 2014] + Added "-D_CRT_SECURE_NO_WARNINGS" to CFLAGS in scripts/makefile.vcwin32. + Removed the obsolete $ARCH variable from scripts/makefile.darwin. + +Version 1.6.15 [November 20, 2014] + No changes. + +Version 1.6.16beta01 [December 14, 2014] + Added ".align 2" to arm/filter_neon.S to support old GAS assemblers that + don't do alignment correctly. + Revised Makefile.am and scripts/symbols.dfn to work with MinGW/MSYS + (Bob Friesenhahn). + +Version 1.6.16beta02 [December 15, 2014] + Revised Makefile.am and scripts/*.dfn again to work with MinGW/MSYS; + renamed scripts/*.dfn to scripts/*.c (John Bowler). + +Version 1.6.16beta03 [December 21, 2014] + Quiet a "comparison always true" warning in pngstest.c (John Bowler). + +Version 1.6.16rc01 [December 21, 2014] + Restored a test on width that was removed from png.c at libpng-1.6.9 + (Bug report by Alex Eubanks, CVE-2015-0973). + +Version 1.6.16rc02 [December 21, 2014] + Undid the update to pngrutil.c in 1.6.16rc01. + +Version 1.6.16rc03 [December 21, 2014] + Fixed an overflow in png_combine_row() with very wide interlaced images + (Bug report and fix by John Bowler, CVE-2014-9495). + +Version 1.6.16 [December 22, 2014] + No changes. + +Version 1.6.17beta01 [January 29, 2015] + Removed duplicate PNG_SAFE_LIMITS_SUPPORTED handling from pngconf.h + Corrected the width limit calculation in png_check_IHDR(). + Removed user limits from pngfix. Also pass NULL pointers to + png_read_row to skip the unnecessary row de-interlace stuff. + Added testing of png_set_packing() to pngvalid.c + Regenerated configure scripts in the *.tar distributions with libtool-2.4.4 + Implement previously untested cases of libpng transforms in pngvalid.c + Fixed byte order in png_do_read_filler() with 16-bit input. Previously + the high and low bytes of the filler, from png_set_filler() or from + png_set_add_alpha(), were read in the wrong order. + Made the check for out-of-range values in png_set_tRNS() detect + values that are exactly 2^bit_depth, and work on 16-bit platforms. + Merged some parts of libpng-1.6.17beta01 and libpng-1.7.0beta47. + Added #ifndef __COVERITY__ where needed in png.c, pngrutil.c and + pngset.c to avoid warnings about dead code. + Added "& 0xff" to many instances of expressions that are typecast + to (png_byte), to avoid Coverity warnings. + +Version 1.6.17beta02 [February 7, 2015] + Work around one more Coverity-scan dead-code warning. + Do not build png_product2() when it is unused. + +Version 1.6.17beta03 [February 17, 2015] + Display user limits in the output from pngtest. + Eliminated the PNG_SAFE_LIMITS macro and restored the 1-million-column + and 1-million-row default limits in pnglibconf.dfa, that can be reset + by the user at build time or run time. This provides a more robust + defense against DOS and as-yet undiscovered overflows. + +Version 1.6.17beta04 [February 21, 2015] + Added PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED macro, on by default. + Allow user to call png_get_IHDR() with NULL arguments (Reuben Hawkins). + Rebuilt configure scripts with automake-1.15 and libtool-2.4.6 + +Version 1.6.17beta05 [February 25, 2015] + Restored compiling of png_reciprocal2 with PNG_NO_16BIT. + +Version 1.6.17beta06 [February 27, 2015] + Moved png_set_filter() prototype into a PNG_WRITE_SUPPORTED block + of png.h. + Avoid runtime checks when converting integer to png_byte with + Visual Studio (Sergey Kosarevsky) + +Version 1.6.17rc01 [March 4, 2015] + No changes. + +Version 1.6.17rc02 [March 9, 2015] + Removed some comments that the configure script did not handle + properly from scripts/pnglibconf.dfa and pnglibconf.h.prebuilt. + Free the unknown_chunks structure even when it contains no data. + +Version 1.6.17rc03 [March 12, 2015] + Updated CMakeLists.txt to add OSX framework, change YES/NO to ON/OFF + for consistency, and remove some useless tests (Alexey Petruchik). + +Version 1.6.17rc04 [March 16, 2015] + Remove pnglibconf.h, pnglibconf.c, and pnglibconf.out instead of + pnglibconf.* in "make clean" (Cosmin). + Fix bug in calculation of maxbits, in png_write_sBIT, introduced + in libpng-1.6.17beta01 (John Bowler). + +Version 1.6.17rc05 [March 21, 2015] + Define PNG_FILTER_* and PNG_FILTER_VALUE_* in png.h even when WRITE + is not supported (John Bowler). This fixes an error introduced in + libpng-1.6.17beta06. + Reverted "& 0xff" additions of version 1.6.17beta01. Libpng passes + the Coverity scan without them. + +Version 1.6.17rc06 [March 23, 2015] + Remove pnglibconf.dfn and pnglibconf.pre with "make clean". + Reformatted some "&0xff" instances to "& 0xff". + Fixed simplified 8-bit-linear to sRGB alpha. The calculated alpha + value was wrong. It's not clear if this affected the final stored + value; in the obvious code path the upper and lower 8-bits of the + alpha value were identical and the alpha was truncated to 8-bits + rather than dividing by 257 (John Bowler). + +Version 1.6.17 [March 26, 2015] + No changes. + +Version 1.6.18beta01 [April 1, 2015] + Removed PNG_SET_CHUNK_[CACHE|MALLOC]_LIMIT_SUPPORTED macros. They + have been combined with PNG_SET_USER_LIMITS_SUPPORTED (resolves + bug report by Andrew Church). + Fixed rgb_to_gray checks and added tRNS checks to pngvalid.c. This + fixes some arithmetic errors that caused some tests to fail on + some 32-bit platforms (Bug reports by Peter Breitenlohner [i686] + and Petr Gajdos [i586]). + +Version 1.6.18beta02 [April 26, 2015] + Suppressed some warnings from the Borland C++ 5.5.1/5.82 compiler + (Bug report by Viktor Szakats). + +Version 1.6.18beta03 [May 6, 2015] + Replaced "unexpected" with an integer (0xabadca11) in pngset.c + where a long was expected, to avoid a compiler warning when PNG_DEBUG > 1. + Added contrib/examples/simpleover.c, to demonstrate how to handle + alpha compositing of multiple images, using the "simplified API" + and an example PNG generation tool, contrib/examples/genpng.c + (John Bowler). + +Version 1.6.18beta04 [May 20, 2015] + PNG_RELEASE_BUILD replaces tests where the code depended on the build base + type and can be defined on the command line, allowing testing in beta + builds (John Bowler). + Avoid Coverity issue 80858 (REVERSE NULL) in pngtest.c PNG_DEBUG builds. + Avoid a harmless potential integer overflow in png_XYZ_from_xy() (Bug + report from Christopher Ferris). + +Version 1.6.18beta05 [May 31, 2015] + Backport filter selection code from libpng-1.7.0beta51, to combine + sub_row, up_row, avg_row, and paeth_row into try_row and tst_row. + Changed png_voidcast(), etc., to voidcast(), etc., in contrib/tools/pngfix.c + to avoid confusion with the libpng private macros. + Fixed old cut&paste bug in the weighted filter selection code in + pngwutil.c, introduced in libpng-0.95, March 1997. + +Version 1.6.18beta06 [June 1, 2015] + Removed WRITE_WEIGHTED_FILTERED code, to save a few kbytes of the + compiled library size. It never worked properly and as far as we can + tell, no one uses it. The png_set_filter_heuristics() and + png_set_filter_heuristics_fixed() APIs are retained but deprecated + and do nothing. + +Version 1.6.18beta07 [June 6, 2015] + Removed non-working progressive reader 'skip' function. This + function has apparently never been used. It was implemented + to support back-door modification of png_struct in libpng-1.4.x + but (because it does nothing and cannot do anything) was apparently + never tested (John Bowler). + Fixed cexcept.h in which GCC 5 now reports that one of the auto + variables in the Try macro needs to be volatile to prevent value + being lost over the setjmp (John Bowler). + Fixed NO_WRITE_FILTER and -Wconversion build breaks (John Bowler). + Fix g++ build breaks (John Bowler). + Quieted some Coverity issues in pngfix.c, png-fix-itxt.c, pngvalid.c, + pngstest.c, and pngimage.c. Most seem harmless, but png-fix-itxt + would only work with iTXt chunks with length 255 or less. + Added #ifdef's to contrib/examples programs so people don't try + to compile them without the minimum required support enabled + (suggested by Flavio Medeiros). + +Version 1.6.18beta08 [June 30, 2015] + Eliminated the final two Coverity defects (insecure temporary file + handling in contrib/libtests/pngstest.c; possible overflow of + unsigned char in contrib/tools/png-fix-itxt.c). To use the "secure" + file handling, define PNG_USE_MKSTEMP, otherwise "tmpfile()" will + be used. + Removed some unused WEIGHTED_FILTER macros from png.h and pngstruct.h + +Version 1.6.18beta09 [July 5, 2015] + Removed some useless typecasts from contrib/tools/png-fix-itxt.c + Fixed a new signed-unsigned comparison in pngrtran.c (Max Stepin). + Replaced arbitrary use of 'extern' with #define PNG_LINKAGE_*. To + preserve API compatibility, the new defines all default to "extern" + (requested by Jan Nijtmans). + +Version 1.6.18rc01 [July 9, 2015] + Belatedly added Mans Rullgard and James Yu to the list of Contributing + Authors. + +Version 1.6.18rc02 [July 12, 2015] + Restored unused FILTER_HEURISTIC macros removed at libpng-1.6.18beta08 + to png.h to avoid compatibility warnings. + +Version 1.6.18rc03 [July 15, 2015] + Minor changes to the man page + +Version 1.6.18 [July 23, 2015] + No changes. + +Version 1.6.19beta01 [July 30, 2015] + Updated obsolete information about the simplified API macros in the + manual pages (Bug report by Arc Riley). + Avoid potentially dereferencing NULL info_ptr in png_info_init_3(). + Rearranged png.h to put the major sections in the same order as + in libpng17. + Eliminated unused PNG_COST_SHIFT, PNG_WEIGHT_SHIFT, PNG_COST_FACTOR, and + PNG_WEIGHT_FACTOR macros. + Suppressed some warnings from the Borland C++ 5.5.1/5.82 compiler + (Bug report by Viktor Szakats). Several warnings remain and are + unavoidable, where we test for overflow. + Fixed potential leak of png_pixels in contrib/pngminus/pnm2png.c + Fixed uninitialized variable in contrib/gregbook/rpng2-x.c + +Version 1.6.19beta02 [August 19, 2015] + Moved config.h.in~ from the "libpng_autotools_files" list to the + "libpng_autotools_extra" list in autogen.sh because it was causing a + false positive for missing files (bug report by Robert C. Seacord). + Removed unreachable "break" statements in png.c, pngread.c, and pngrtran.c + to suppress clang warnings (Bug report by Viktor Szakats). + Fixed some bad links in the man page. + Changed "n bit" to "n-bit" in comments. + Added signed/unsigned 16-bit safety net. This removes the dubious + 0x8000 flag definitions on 16-bit systems. They aren't supported + yet the defs *probably* work, however it seems much safer to do this + and be advised if anyone, contrary to advice, is building libpng 1.6 + on a 16-bit system. It also adds back various switch default clauses + for GCC; GCC errors out if they are not present (with an appropriately + high level of warnings). + Safely convert num_bytes to a png_byte in png_set_sig_bytes() (Robert + Seacord). + Fixed the recently reported 1's complement security issue by replacing + the value that is illegal in the PNG spec, in both signed and unsigned + values, with 0. Illegal unsigned values (anything greater than or equal + to 0x80000000) can still pass through, but since these are not illegal + in ANSI-C (unlike 0x80000000 in the signed case) the checking that + occurs later can catch them (John Bowler). + +Version 1.6.19beta03 [September 26, 2015] + Fixed png_save_int_32 when int is not 2's complement (John Bowler). + Updated libpng16 with all the recent test changes from libpng17, + including changes to pngvalid.c to ensure that the original, + distributed, version of contrib/visupng/cexcept.h can be used + (John Bowler). + pngvalid contains the correction to the use of SAVE/STORE_ + UNKNOWN_CHUNKS; a bug revealed by changes in libpng 1.7. More + tests contain the --strict option to detect warnings and the + pngvalid-standard test has been corrected so that it does not + turn on progressive-read. There is a separate test which does + that. (John Bowler) + Also made some signed/unsigned fixes. + Make pngstest error limits version specific. Splitting the machine + generated error structs out to a file allows the values to be updated + without changing pngstest.c itself. Since libpng 1.6 and 1.7 have + slightly different error limits this simplifies maintenance. The + makepngs.sh script has also been updated to more accurately reflect + current problems in libpng 1.7 (John Bowler). + Incorporated new test PNG files into make check. tests/pngstest-* + are changed so that the new test files are divided into 8 groups by + gamma and alpha channel. These tests have considerably better code + and pixel-value coverage than contrib/pngsuite; however,coverage is + still incomplete (John Bowler). + Removed the '--strict' in 1.6 because of the double-gamma-correction + warning, updated pngstest-errors.h for the errors detected with the + new contrib/testspngs PNG test files (John Bowler). + +Version 1.6.19beta04 [October 15, 2015] + Worked around rgb-to-gray issues in libpng 1.6. The previous + attempts to ignore the errors in the code aren't quite enough to + deal with the 'channel selection' encoding added to libpng 1.7; abort. + pngvalid.c is changed to drop this encoding in prior versions. + Fixed 'pow' macros in pngvalid.c. It is legal for 'pow' to be a + macro, therefore the argument list cannot contain preprocessing + directives. Make sure pow is a function where this happens. This is + a minimal safe fix, the issue only arises in non-performance-critical + code (bug report by Curtis Leach, fix by John Bowler). + Added sPLT support to pngtest.c + +Version 1.6.19rc01 [October 23, 2015] + No changes. + +Version 1.6.19rc02 [October 31, 2015] + Prevent setting or writing over-length PLTE chunk (Cosmin Truta). + Silently truncate over-length PLTE chunk while reading. + Libpng incorrectly calculated the output rowbytes when the application + decreased either the number of channels or the bit depth (or both) in + a user transform. This was safe; libpng overallocated buffer space + (potentially by quite a lot; up to 4 times the amount required) but, + from 1.5.4 on, resulted in a png_error (John Bowler). + +Version 1.6.19rc03 [November 3, 2015] + Fixed some inconsequential cut-and-paste typos in png_set_cHRM_XYZ_fixed(). + Clarified COPYRIGHT information to state explicitly that versions + are derived from previous versions. + Removed much of the long list of previous versions from png.h and + libpng.3. + +Version 1.6.19rc04 [November 5, 2015] + Fixed new bug with CRC error after reading an over-length palette + (bug report by Cosmin Truta). + +Version 1.6.19 [November 12, 2015] + Cleaned up coding style in png_handle_PLTE(). + +Send comments/corrections/commendations to png-mng-implement at lists.sf.net +(subscription required; visit +https://lists.sourceforge.net/lists/listinfo/png-mng-implement +to subscribe) +or to glennrp at users.sourceforge.net + +Glenn R-P +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/LICENSE b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/LICENSE new file mode 100644 index 0000000..2433b50 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/LICENSE @@ -0,0 +1,112 @@ + +This copy of the libpng notices is provided for your convenience. In case of +any discrepancy between this copy and the notices in the file png.h that is +included in the libpng distribution, the latter shall prevail. + +COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: + +If you modify libpng you may insert additional notices immediately following +this sentence. + +This code is released under the libpng license. + +libpng versions 1.0.7, July 1, 2000, through 1.6.19, November 12, 2015, are +Copyright (c) 2000-2002, 2004, 2006-2015 Glenn Randers-Pehrson, are +derived from libpng-1.0.6, and are distributed according to the same +disclaimer and license as libpng-1.0.6 with the following individuals +added to the list of Contributing Authors: + + Simon-Pierre Cadieux + Eric S. Raymond + Mans Rullgard + Cosmin Truta + Gilles Vollant + James Yu + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of the + library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is with + the user. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from +libpng-0.96, and are distributed according to the same disclaimer and +license as libpng-0.96, with the following individuals added to the list +of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, +and are distributed according to the same disclaimer and license as +libpng-0.88, with the following individuals added to the list of +Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +The PNG Reference Library is supplied "AS IS". The Contributing Authors +and Group 42, Inc. disclaim all warranties, expressed or implied, +including, without limitation, the warranties of merchantability and of +fitness for any purpose. The Contributing Authors and Group 42, Inc. +assume no liability for direct, indirect, incidental, special, exemplary, +or consequential damages, which may result from the use of the PNG +Reference Library, even if advised of the possibility of such damage. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, without fee, subject +to the following restrictions: + + 1. The origin of this source code must not be misrepresented. + + 2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + +The Contributing Authors and Group 42, Inc. specifically permit, without +fee, and encourage the use of this source code as a component to +supporting the PNG file format in commercial products. If you use this +source code in a product, acknowledgment is not required but would be +appreciated. + +END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE. + +A "png_get_copyright" function is available, for convenient use in "about" +boxes and the like: + + printf("%s", png_get_copyright(NULL)); + +Also, the PNG logo (in PNG format, of course) is supplied in the +files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31). + +Libpng is OSI Certified Open Source Software. OSI Certified Open Source is +a certification mark of the Open Source Initiative. OSI has not addressed +the additional disclaimers inserted at version 1.0.7. + +Glenn Randers-Pehrson +glennrp at users.sourceforge.net +November 12, 2015 diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/README b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/README new file mode 100644 index 0000000..a9e64d4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/README @@ -0,0 +1,215 @@ +README for libpng version 1.6.19 - November 12, 2015 (shared library 16.0) +See the note about version numbers near the top of png.h + +See INSTALL for instructions on how to install libpng. + +Libpng comes in several distribution formats. Get libpng-*.tar.gz or +libpng-*.tar.xz or if you want UNIX-style line endings in the text files, +or lpng*.7z or lpng*.zip if you want DOS-style line endings. + +Version 0.89 was the first official release of libpng. Don't let the +fact that it's the first release fool you. The libpng library has been in +extensive use and testing since mid-1995. By late 1997 it had +finally gotten to the stage where there hadn't been significant +changes to the API in some time, and people have a bad feeling about +libraries with versions < 1.0. Version 1.0.0 was released in +March 1998. + +**** +Note that some of the changes to the png_info structure render this +version of the library binary incompatible with libpng-0.89 or +earlier versions if you are using a shared library. The type of the +"filler" parameter for png_set_filler() has changed from png_byte to +png_uint_32, which will affect shared-library applications that use +this function. + +To avoid problems with changes to the internals of png info_struct, +new APIs have been made available in 0.95 to avoid direct application +access to info_ptr. These functions are the png_set_ and +png_get_ functions. These functions should be used when +accessing/storing the info_struct data, rather than manipulating it +directly, to avoid such problems in the future. + +It is important to note that the APIs did not make current programs +that access the info struct directly incompatible with the new +library, through libpng-1.2.x. In libpng-1.4.x, which was meant to +be a transitional release, members of the png_struct and the +info_struct can still be accessed, but the compiler will issue a +warning about deprecated usage. Since libpng-1.5.0, direct access +to these structs is not allowed, and the definitions of the structs +reside in private pngstruct.h and pnginfo.h header files that are not +accessible to applications. It is strongly suggested that new +programs use the new APIs (as shown in example.c and pngtest.c), and +older programs be converted to the new format, to facilitate upgrades +in the future. +**** + +Additions since 0.90 include the ability to compile libpng as a +Windows DLL, and new APIs for accessing data in the info struct. +Experimental functions include the ability to set weighting and cost +factors for row filter selection, direct reads of integers from buffers +on big-endian processors that support misaligned data access, faster +methods of doing alpha composition, and more accurate 16->8 bit color +conversion. + +The additions since 0.89 include the ability to read from a PNG stream +which has had some (or all) of the signature bytes read by the calling +application. This also allows the reading of embedded PNG streams that +do not have the PNG file signature. As well, it is now possible to set +the library action on the detection of chunk CRC errors. It is possible +to set different actions based on whether the CRC error occurred in a +critical or an ancillary chunk. + +The changes made to the library, and bugs fixed are based on discussions +on the PNG-implement mailing list and not on material submitted +privately to Guy, Andreas, or Glenn. They will forward any good +suggestions to the list. + +For a detailed description on using libpng, read libpng-manual.txt. For +examples of libpng in a program, see example.c and pngtest.c. For usage +information and restrictions (what little they are) on libpng, see +png.h. For a description on using zlib (the compression library used by +libpng) and zlib's restrictions, see zlib.h + +I have included a general makefile, as well as several machine and +compiler specific ones, but you may have to modify one for your own needs. + +You should use zlib 1.0.4 or later to run this, but it MAY work with +versions as old as zlib 0.95. Even so, there are bugs in older zlib +versions which can cause the output of invalid compression streams for +some images. You will definitely need zlib 1.0.4 or later if you are +taking advantage of the MS-DOS "far" structure allocation for the small +and medium memory models. You should also note that zlib is a +compression library that is useful for more things than just PNG files. +You can use zlib as a drop-in replacement for fread() and fwrite() if +you are so inclined. + +zlib should be available at the same place that libpng is, or at zlib.net. + +You may also want a copy of the PNG specification. It is available +as an RFC, a W3C Recommendation, and an ISO/IEC Standard. You can find +these at http://www.libpng.org/pub/png/documents/ + +This code is currently being archived at libpng.sf.net in the +[DOWNLOAD] area, and at ftp://ftp.simplesystems.org. If you can't find it +in any of those places, e-mail me, and I'll help you find it. + +I am not a lawyer, but I believe that the Export Control Classification +Number (ECCN) for libpng is EAR99, which means not subject to export +controls or International Traffic in Arms Regulations (ITAR) because it +is open source, publicly available software, that does not contain any +encryption software. See the EAR, paragraphs 734.3(b)(3) and 734.7(b). + +If you have any code changes, requests, problems, etc., please e-mail +them to me. Also, I'd appreciate any make files or project files, +and any modifications you needed to make to get libpng to compile, +along with a #define variable to tell what compiler/system you are on. +If you needed to add transformations to libpng, or wish libpng would +provide the image in a different way, drop me a note (and code, if +possible), so I can consider supporting the transformation. +Finally, if you get any warning messages when compiling libpng +(note: not zlib), and they are easy to fix, I'd appreciate the +fix. Please mention "libpng" somewhere in the subject line. Thanks. + +This release was created and will be supported by myself (of course +based in a large way on Guy's and Andreas' earlier work), and the PNG +development group. + +Send comments/corrections/commendations to png-mng-implement at +lists.sourceforge.net (subscription required; visit +https://lists.sourceforge.net/lists/listinfo/png-mng-implement +to subscribe) or to glennrp at users.sourceforge.net + +You can't reach Guy, the original libpng author, at the addresses +given in previous versions of this document. He and Andreas will +read mail addressed to the png-implement list, however. + +Please do not send general questions about PNG. Send them to +png-mng-misc at lists.sf.net (subscription required; visit +https://lists.sourceforge.net/lists/listinfo/png-mng-misc to +subscribe). If you have a question about something +in the PNG specification that is related to using libpng, send it +to me. Send me any questions that start with "I was using libpng, +and ...". If in doubt, send questions to me. I'll bounce them +to others, if necessary. + +Please do not send suggestions on how to change PNG. We have +been discussing PNG for twenty years now, and it is official and +finished. If you have suggestions for libpng, however, I'll +gladly listen. Even if your suggestion is not used immediately, +it may be used later. + +Files in this distribution: + + ANNOUNCE => Announcement of this version, with recent changes + CHANGES => Description of changes between libpng versions + KNOWNBUG => List of known bugs and deficiencies + LICENSE => License to use and redistribute libpng + README => This file + TODO => Things not implemented in the current library + Y2KINFO => Statement of Y2K compliance + example.c => Example code for using libpng functions + libpng.3 => manual page for libpng (includes libpng-manual.txt) + libpng-manual.txt => Description of libpng and its functions + libpngpf.3 => manual page for libpng's private functions + png.5 => manual page for the PNG format + png.c => Basic interface functions common to library + png.h => Library function and interface declarations (public) + pngpriv.h => Library function and interface declarations (private) + pngconf.h => System specific library configuration (public) + pngstruct.h => png_struct declaration (private) + pnginfo.h => png_info struct declaration (private) + pngdebug.h => debugging macros (private) + pngerror.c => Error/warning message I/O functions + pngget.c => Functions for retrieving info from struct + pngmem.c => Memory handling functions + pngbar.png => PNG logo, 88x31 + pngnow.png => PNG logo, 98x31 + pngpread.c => Progressive reading functions + pngread.c => Read data/helper high-level functions + pngrio.c => Lowest-level data read I/O functions + pngrtran.c => Read data transformation functions + pngrutil.c => Read data utility functions + pngset.c => Functions for storing data into the info_struct + pngtest.c => Library test program + pngtest.png => Library test sample image + pngtrans.c => Common data transformation functions + pngwio.c => Lowest-level write I/O functions + pngwrite.c => High-level write functions + pngwtran.c => Write data transformations + pngwutil.c => Write utility functions + arm => Contains optimized code for the ARM platform + contrib => Contributions + examples => Example programs + gregbook => source code for PNG reading and writing, from + Greg Roelofs' "PNG: The Definitive Guide", + O'Reilly, 1999 + libtests => Test programs + pngminim => Minimal decoder, encoder, and progressive decoder + programs demonstrating use of pngusr.dfa + pngminus => Simple pnm2png and png2pnm programs + pngsuite => Test images + tools => Various tools + visupng => Contains a MSVC workspace for VisualPng + projects => Contains project files and workspaces for + building a DLL + owatcom => Contains a WATCOM project for building libpng + visualc71 => Contains a Microsoft Visual C++ (MSVC) + workspace for building libpng and zlib + vstudio => Contains a Microsoft Visual C++ (MSVC) + workspace for building libpng and zlib + scripts => Directory containing scripts for building libpng: + (see scripts/README.txt for the list of scripts) + +Good luck, and happy coding. + +-Glenn Randers-Pehrson (current maintainer, since 1998) + Internet: glennrp at users.sourceforge.net + +-Andreas Eric Dilger (former maintainer, 1996-1997) + Internet: adilger at enel.ucalgary.ca + Web: http://www-mddsp.enel.ucalgary.ca/People/adilger/ + +-Guy Eric Schalnat (original author and former maintainer, 1995-1996) + (formerly of Group 42, Inc) + Internet: gschal at infinet.com diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/png.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/png.c new file mode 100644 index 0000000..757c755 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/png.c @@ -0,0 +1,4607 @@ + +/* png.c - location for general purpose libpng functions + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +/* Generate a compiler error if there is an old png.h in the search path. */ +typedef png_libpng_version_1_6_37 Your_png_h_is_not_version_1_6_37; + +#ifdef __GNUC__ +/* The version tests may need to be added to, but the problem warning has + * consistently been fixed in GCC versions which obtain wide-spread release. + * The problem is that many versions of GCC rearrange comparison expressions in + * the optimizer in such a way that the results of the comparison will change + * if signed integer overflow occurs. Such comparisons are not permitted in + * ANSI C90, however GCC isn't clever enough to work out that that do not occur + * below in png_ascii_from_fp and png_muldiv, so it produces a warning with + * -Wextra. Unfortunately this is highly dependent on the optimizer and the + * machine architecture so the warning comes and goes unpredictably and is + * impossible to "fix", even were that a good idea. + */ +#if __GNUC__ == 7 && __GNUC_MINOR__ == 1 +#define GCC_STRICT_OVERFLOW 1 +#endif /* GNU 7.1.x */ +#endif /* GNU */ +#ifndef GCC_STRICT_OVERFLOW +#define GCC_STRICT_OVERFLOW 0 +#endif + +/* Tells libpng that we have already handled the first "num_bytes" bytes + * of the PNG file signature. If the PNG data is embedded into another + * stream we can set num_bytes = 8 so that libpng will not attempt to read + * or write any of the magic bytes before it starts on the IHDR. + */ + +#ifdef PNG_READ_SUPPORTED +void PNGAPI +png_set_sig_bytes(png_structrp png_ptr, int num_bytes) +{ + unsigned int nb = (unsigned int)num_bytes; + + png_debug(1, "in png_set_sig_bytes"); + + if (png_ptr == NULL) + return; + + if (num_bytes < 0) + nb = 0; + + if (nb > 8) + png_error(png_ptr, "Too many bytes for PNG signature"); + + png_ptr->sig_bytes = (png_byte)nb; +} + +/* Checks whether the supplied bytes match the PNG signature. We allow + * checking less than the full 8-byte signature so that those apps that + * already read the first few bytes of a file to determine the file type + * can simply check the remaining bytes for extra assurance. Returns + * an integer less than, equal to, or greater than zero if sig is found, + * respectively, to be less than, to match, or be greater than the correct + * PNG signature (this is the same behavior as strcmp, memcmp, etc). + */ +int PNGAPI +png_sig_cmp(png_const_bytep sig, size_t start, size_t num_to_check) +{ + png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + + if (num_to_check > 8) + num_to_check = 8; + + else if (num_to_check < 1) + return (-1); + + if (start > 7) + return (-1); + + if (start + num_to_check > 8) + num_to_check = 8 - start; + + return ((int)(memcmp(&sig[start], &png_signature[start], num_to_check))); +} + +#endif /* READ */ + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +/* Function to allocate memory for zlib */ +PNG_FUNCTION(voidpf /* PRIVATE */, +png_zalloc,(voidpf png_ptr, uInt items, uInt size),PNG_ALLOCATED) +{ + png_alloc_size_t num_bytes = size; + + if (png_ptr == NULL) + return NULL; + + if (items >= (~(png_alloc_size_t)0)/size) + { + png_warning (png_voidcast(png_structrp, png_ptr), + "Potential overflow in png_zalloc()"); + return NULL; + } + + num_bytes *= items; + return png_malloc_warn(png_voidcast(png_structrp, png_ptr), num_bytes); +} + +/* Function to free memory for zlib */ +void /* PRIVATE */ +png_zfree(voidpf png_ptr, voidpf ptr) +{ + png_free(png_voidcast(png_const_structrp,png_ptr), ptr); +} + +/* Reset the CRC variable to 32 bits of 1's. Care must be taken + * in case CRC is > 32 bits to leave the top bits 0. + */ +void /* PRIVATE */ +png_reset_crc(png_structrp png_ptr) +{ + /* The cast is safe because the crc is a 32-bit value. */ + png_ptr->crc = (png_uint_32)crc32(0, Z_NULL, 0); +} + +/* Calculate the CRC over a section of data. We can only pass as + * much data to this routine as the largest single buffer size. We + * also check that this data will actually be used before going to the + * trouble of calculating it. + */ +void /* PRIVATE */ +png_calculate_crc(png_structrp png_ptr, png_const_bytep ptr, size_t length) +{ + int need_crc = 1; + + if (PNG_CHUNK_ANCILLARY(png_ptr->chunk_name) != 0) + { + if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_MASK) == + (PNG_FLAG_CRC_ANCILLARY_USE | PNG_FLAG_CRC_ANCILLARY_NOWARN)) + need_crc = 0; + } + + else /* critical */ + { + if ((png_ptr->flags & PNG_FLAG_CRC_CRITICAL_IGNORE) != 0) + need_crc = 0; + } + + /* 'uLong' is defined in zlib.h as unsigned long; this means that on some + * systems it is a 64-bit value. crc32, however, returns 32 bits so the + * following cast is safe. 'uInt' may be no more than 16 bits, so it is + * necessary to perform a loop here. + */ + if (need_crc != 0 && length > 0) + { + uLong crc = png_ptr->crc; /* Should never issue a warning */ + + do + { + uInt safe_length = (uInt)length; +#ifndef __COVERITY__ + if (safe_length == 0) + safe_length = (uInt)-1; /* evil, but safe */ +#endif + + crc = crc32(crc, ptr, safe_length); + + /* The following should never issue compiler warnings; if they do the + * target system has characteristics that will probably violate other + * assumptions within the libpng code. + */ + ptr += safe_length; + length -= safe_length; + } + while (length > 0); + + /* And the following is always safe because the crc is only 32 bits. */ + png_ptr->crc = (png_uint_32)crc; + } +} + +/* Check a user supplied version number, called from both read and write + * functions that create a png_struct. + */ +int +png_user_version_check(png_structrp png_ptr, png_const_charp user_png_ver) +{ + /* Libpng versions 1.0.0 and later are binary compatible if the version + * string matches through the second '.'; we must recompile any + * applications that use any older library version. + */ + + if (user_png_ver != NULL) + { + int i = -1; + int found_dots = 0; + + do + { + i++; + if (user_png_ver[i] != PNG_LIBPNG_VER_STRING[i]) + png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH; + if (user_png_ver[i] == '.') + found_dots++; + } while (found_dots < 2 && user_png_ver[i] != 0 && + PNG_LIBPNG_VER_STRING[i] != 0); + } + + else + png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH; + + if ((png_ptr->flags & PNG_FLAG_LIBRARY_MISMATCH) != 0) + { +#ifdef PNG_WARNINGS_SUPPORTED + size_t pos = 0; + char m[128]; + + pos = png_safecat(m, (sizeof m), pos, + "Application built with libpng-"); + pos = png_safecat(m, (sizeof m), pos, user_png_ver); + pos = png_safecat(m, (sizeof m), pos, " but running with "); + pos = png_safecat(m, (sizeof m), pos, PNG_LIBPNG_VER_STRING); + PNG_UNUSED(pos) + + png_warning(png_ptr, m); +#endif + +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + png_ptr->flags = 0; +#endif + + return 0; + } + + /* Success return. */ + return 1; +} + +/* Generic function to create a png_struct for either read or write - this + * contains the common initialization. + */ +PNG_FUNCTION(png_structp /* PRIVATE */, +png_create_png_struct,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr, + png_malloc_ptr malloc_fn, png_free_ptr free_fn),PNG_ALLOCATED) +{ + png_struct create_struct; +# ifdef PNG_SETJMP_SUPPORTED + jmp_buf create_jmp_buf; +# endif + + /* This temporary stack-allocated structure is used to provide a place to + * build enough context to allow the user provided memory allocator (if any) + * to be called. + */ + memset(&create_struct, 0, (sizeof create_struct)); + + /* Added at libpng-1.2.6 */ +# ifdef PNG_USER_LIMITS_SUPPORTED + create_struct.user_width_max = PNG_USER_WIDTH_MAX; + create_struct.user_height_max = PNG_USER_HEIGHT_MAX; + +# ifdef PNG_USER_CHUNK_CACHE_MAX + /* Added at libpng-1.2.43 and 1.4.0 */ + create_struct.user_chunk_cache_max = PNG_USER_CHUNK_CACHE_MAX; +# endif + +# ifdef PNG_USER_CHUNK_MALLOC_MAX + /* Added at libpng-1.2.43 and 1.4.1, required only for read but exists + * in png_struct regardless. + */ + create_struct.user_chunk_malloc_max = PNG_USER_CHUNK_MALLOC_MAX; +# endif +# endif + + /* The following two API calls simply set fields in png_struct, so it is safe + * to do them now even though error handling is not yet set up. + */ +# ifdef PNG_USER_MEM_SUPPORTED + png_set_mem_fn(&create_struct, mem_ptr, malloc_fn, free_fn); +# else + PNG_UNUSED(mem_ptr) + PNG_UNUSED(malloc_fn) + PNG_UNUSED(free_fn) +# endif + + /* (*error_fn) can return control to the caller after the error_ptr is set, + * this will result in a memory leak unless the error_fn does something + * extremely sophisticated. The design lacks merit but is implicit in the + * API. + */ + png_set_error_fn(&create_struct, error_ptr, error_fn, warn_fn); + +# ifdef PNG_SETJMP_SUPPORTED + if (!setjmp(create_jmp_buf)) +# endif + { +# ifdef PNG_SETJMP_SUPPORTED + /* Temporarily fake out the longjmp information until we have + * successfully completed this function. This only works if we have + * setjmp() support compiled in, but it is safe - this stuff should + * never happen. + */ + create_struct.jmp_buf_ptr = &create_jmp_buf; + create_struct.jmp_buf_size = 0; /*stack allocation*/ + create_struct.longjmp_fn = longjmp; +# endif + /* Call the general version checker (shared with read and write code): + */ + if (png_user_version_check(&create_struct, user_png_ver) != 0) + { + png_structrp png_ptr = png_voidcast(png_structrp, + png_malloc_warn(&create_struct, (sizeof *png_ptr))); + + if (png_ptr != NULL) + { + /* png_ptr->zstream holds a back-pointer to the png_struct, so + * this can only be done now: + */ + create_struct.zstream.zalloc = png_zalloc; + create_struct.zstream.zfree = png_zfree; + create_struct.zstream.opaque = png_ptr; + +# ifdef PNG_SETJMP_SUPPORTED + /* Eliminate the local error handling: */ + create_struct.jmp_buf_ptr = NULL; + create_struct.jmp_buf_size = 0; + create_struct.longjmp_fn = 0; +# endif + + *png_ptr = create_struct; + + /* This is the successful return point */ + return png_ptr; + } + } + } + + /* A longjmp because of a bug in the application storage allocator or a + * simple failure to allocate the png_struct. + */ + return NULL; +} + +/* Allocate the memory for an info_struct for the application. */ +PNG_FUNCTION(png_infop,PNGAPI +png_create_info_struct,(png_const_structrp png_ptr),PNG_ALLOCATED) +{ + png_inforp info_ptr; + + png_debug(1, "in png_create_info_struct"); + + if (png_ptr == NULL) + return NULL; + + /* Use the internal API that does not (or at least should not) error out, so + * that this call always returns ok. The application typically sets up the + * error handling *after* creating the info_struct because this is the way it + * has always been done in 'example.c'. + */ + info_ptr = png_voidcast(png_inforp, png_malloc_base(png_ptr, + (sizeof *info_ptr))); + + if (info_ptr != NULL) + memset(info_ptr, 0, (sizeof *info_ptr)); + + return info_ptr; +} + +/* This function frees the memory associated with a single info struct. + * Normally, one would use either png_destroy_read_struct() or + * png_destroy_write_struct() to free an info struct, but this may be + * useful for some applications. From libpng 1.6.0 this function is also used + * internally to implement the png_info release part of the 'struct' destroy + * APIs. This ensures that all possible approaches free the same data (all of + * it). + */ +void PNGAPI +png_destroy_info_struct(png_const_structrp png_ptr, png_infopp info_ptr_ptr) +{ + png_inforp info_ptr = NULL; + + png_debug(1, "in png_destroy_info_struct"); + + if (png_ptr == NULL) + return; + + if (info_ptr_ptr != NULL) + info_ptr = *info_ptr_ptr; + + if (info_ptr != NULL) + { + /* Do this first in case of an error below; if the app implements its own + * memory management this can lead to png_free calling png_error, which + * will abort this routine and return control to the app error handler. + * An infinite loop may result if it then tries to free the same info + * ptr. + */ + *info_ptr_ptr = NULL; + + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + memset(info_ptr, 0, (sizeof *info_ptr)); + png_free(png_ptr, info_ptr); + } +} + +/* Initialize the info structure. This is now an internal function (0.89) + * and applications using it are urged to use png_create_info_struct() + * instead. Use deprecated in 1.6.0, internal use removed (used internally it + * is just a memset). + * + * NOTE: it is almost inconceivable that this API is used because it bypasses + * the user-memory mechanism and the user error handling/warning mechanisms in + * those cases where it does anything other than a memset. + */ +PNG_FUNCTION(void,PNGAPI +png_info_init_3,(png_infopp ptr_ptr, size_t png_info_struct_size), + PNG_DEPRECATED) +{ + png_inforp info_ptr = *ptr_ptr; + + png_debug(1, "in png_info_init_3"); + + if (info_ptr == NULL) + return; + + if ((sizeof (png_info)) > png_info_struct_size) + { + *ptr_ptr = NULL; + /* The following line is why this API should not be used: */ + free(info_ptr); + info_ptr = png_voidcast(png_inforp, png_malloc_base(NULL, + (sizeof *info_ptr))); + if (info_ptr == NULL) + return; + *ptr_ptr = info_ptr; + } + + /* Set everything to 0 */ + memset(info_ptr, 0, (sizeof *info_ptr)); +} + +/* The following API is not called internally */ +void PNGAPI +png_data_freer(png_const_structrp png_ptr, png_inforp info_ptr, + int freer, png_uint_32 mask) +{ + png_debug(1, "in png_data_freer"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (freer == PNG_DESTROY_WILL_FREE_DATA) + info_ptr->free_me |= mask; + + else if (freer == PNG_USER_WILL_FREE_DATA) + info_ptr->free_me &= ~mask; + + else + png_error(png_ptr, "Unknown freer parameter in png_data_freer"); +} + +void PNGAPI +png_free_data(png_const_structrp png_ptr, png_inforp info_ptr, png_uint_32 mask, + int num) +{ + png_debug(1, "in png_free_data"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + +#ifdef PNG_TEXT_SUPPORTED + /* Free text item num or (if num == -1) all text items */ + if (info_ptr->text != NULL && + ((mask & PNG_FREE_TEXT) & info_ptr->free_me) != 0) + { + if (num != -1) + { + png_free(png_ptr, info_ptr->text[num].key); + info_ptr->text[num].key = NULL; + } + + else + { + int i; + + for (i = 0; i < info_ptr->num_text; i++) + png_free(png_ptr, info_ptr->text[i].key); + + png_free(png_ptr, info_ptr->text); + info_ptr->text = NULL; + info_ptr->num_text = 0; + info_ptr->max_text = 0; + } + } +#endif + +#ifdef PNG_tRNS_SUPPORTED + /* Free any tRNS entry */ + if (((mask & PNG_FREE_TRNS) & info_ptr->free_me) != 0) + { + info_ptr->valid &= ~PNG_INFO_tRNS; + png_free(png_ptr, info_ptr->trans_alpha); + info_ptr->trans_alpha = NULL; + info_ptr->num_trans = 0; + } +#endif + +#ifdef PNG_sCAL_SUPPORTED + /* Free any sCAL entry */ + if (((mask & PNG_FREE_SCAL) & info_ptr->free_me) != 0) + { + png_free(png_ptr, info_ptr->scal_s_width); + png_free(png_ptr, info_ptr->scal_s_height); + info_ptr->scal_s_width = NULL; + info_ptr->scal_s_height = NULL; + info_ptr->valid &= ~PNG_INFO_sCAL; + } +#endif + +#ifdef PNG_pCAL_SUPPORTED + /* Free any pCAL entry */ + if (((mask & PNG_FREE_PCAL) & info_ptr->free_me) != 0) + { + png_free(png_ptr, info_ptr->pcal_purpose); + png_free(png_ptr, info_ptr->pcal_units); + info_ptr->pcal_purpose = NULL; + info_ptr->pcal_units = NULL; + + if (info_ptr->pcal_params != NULL) + { + int i; + + for (i = 0; i < info_ptr->pcal_nparams; i++) + png_free(png_ptr, info_ptr->pcal_params[i]); + + png_free(png_ptr, info_ptr->pcal_params); + info_ptr->pcal_params = NULL; + } + info_ptr->valid &= ~PNG_INFO_pCAL; + } +#endif + +#ifdef PNG_iCCP_SUPPORTED + /* Free any profile entry */ + if (((mask & PNG_FREE_ICCP) & info_ptr->free_me) != 0) + { + png_free(png_ptr, info_ptr->iccp_name); + png_free(png_ptr, info_ptr->iccp_profile); + info_ptr->iccp_name = NULL; + info_ptr->iccp_profile = NULL; + info_ptr->valid &= ~PNG_INFO_iCCP; + } +#endif + +#ifdef PNG_sPLT_SUPPORTED + /* Free a given sPLT entry, or (if num == -1) all sPLT entries */ + if (info_ptr->splt_palettes != NULL && + ((mask & PNG_FREE_SPLT) & info_ptr->free_me) != 0) + { + if (num != -1) + { + png_free(png_ptr, info_ptr->splt_palettes[num].name); + png_free(png_ptr, info_ptr->splt_palettes[num].entries); + info_ptr->splt_palettes[num].name = NULL; + info_ptr->splt_palettes[num].entries = NULL; + } + + else + { + int i; + + for (i = 0; i < info_ptr->splt_palettes_num; i++) + { + png_free(png_ptr, info_ptr->splt_palettes[i].name); + png_free(png_ptr, info_ptr->splt_palettes[i].entries); + } + + png_free(png_ptr, info_ptr->splt_palettes); + info_ptr->splt_palettes = NULL; + info_ptr->splt_palettes_num = 0; + info_ptr->valid &= ~PNG_INFO_sPLT; + } + } +#endif + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + if (info_ptr->unknown_chunks != NULL && + ((mask & PNG_FREE_UNKN) & info_ptr->free_me) != 0) + { + if (num != -1) + { + png_free(png_ptr, info_ptr->unknown_chunks[num].data); + info_ptr->unknown_chunks[num].data = NULL; + } + + else + { + int i; + + for (i = 0; i < info_ptr->unknown_chunks_num; i++) + png_free(png_ptr, info_ptr->unknown_chunks[i].data); + + png_free(png_ptr, info_ptr->unknown_chunks); + info_ptr->unknown_chunks = NULL; + info_ptr->unknown_chunks_num = 0; + } + } +#endif + +#ifdef PNG_eXIf_SUPPORTED + /* Free any eXIf entry */ + if (((mask & PNG_FREE_EXIF) & info_ptr->free_me) != 0) + { +# ifdef PNG_READ_eXIf_SUPPORTED + if (info_ptr->eXIf_buf) + { + png_free(png_ptr, info_ptr->eXIf_buf); + info_ptr->eXIf_buf = NULL; + } +# endif + if (info_ptr->exif) + { + png_free(png_ptr, info_ptr->exif); + info_ptr->exif = NULL; + } + info_ptr->valid &= ~PNG_INFO_eXIf; + } +#endif + +#ifdef PNG_hIST_SUPPORTED + /* Free any hIST entry */ + if (((mask & PNG_FREE_HIST) & info_ptr->free_me) != 0) + { + png_free(png_ptr, info_ptr->hist); + info_ptr->hist = NULL; + info_ptr->valid &= ~PNG_INFO_hIST; + } +#endif + + /* Free any PLTE entry that was internally allocated */ + if (((mask & PNG_FREE_PLTE) & info_ptr->free_me) != 0) + { + png_free(png_ptr, info_ptr->palette); + info_ptr->palette = NULL; + info_ptr->valid &= ~PNG_INFO_PLTE; + info_ptr->num_palette = 0; + } + +#ifdef PNG_INFO_IMAGE_SUPPORTED + /* Free any image bits attached to the info structure */ + if (((mask & PNG_FREE_ROWS) & info_ptr->free_me) != 0) + { + if (info_ptr->row_pointers != NULL) + { + png_uint_32 row; + for (row = 0; row < info_ptr->height; row++) + png_free(png_ptr, info_ptr->row_pointers[row]); + + png_free(png_ptr, info_ptr->row_pointers); + info_ptr->row_pointers = NULL; + } + info_ptr->valid &= ~PNG_INFO_IDAT; + } +#endif + + if (num != -1) + mask &= ~PNG_FREE_MUL; + + info_ptr->free_me &= ~mask; +} +#endif /* READ || WRITE */ + +/* This function returns a pointer to the io_ptr associated with the user + * functions. The application should free any memory associated with this + * pointer before png_write_destroy() or png_read_destroy() are called. + */ +png_voidp PNGAPI +png_get_io_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return (NULL); + + return (png_ptr->io_ptr); +} + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +# ifdef PNG_STDIO_SUPPORTED +/* Initialize the default input/output functions for the PNG file. If you + * use your own read or write routines, you can call either png_set_read_fn() + * or png_set_write_fn() instead of png_init_io(). If you have defined + * PNG_NO_STDIO or otherwise disabled PNG_STDIO_SUPPORTED, you must use a + * function of your own because "FILE *" isn't necessarily available. + */ +void PNGAPI +png_init_io(png_structrp png_ptr, png_FILE_p fp) +{ + png_debug(1, "in png_init_io"); + + if (png_ptr == NULL) + return; + + png_ptr->io_ptr = (png_voidp)fp; +} +# endif + +# ifdef PNG_SAVE_INT_32_SUPPORTED +/* PNG signed integers are saved in 32-bit 2's complement format. ANSI C-90 + * defines a cast of a signed integer to an unsigned integer either to preserve + * the value, if it is positive, or to calculate: + * + * (UNSIGNED_MAX+1) + integer + * + * Where UNSIGNED_MAX is the appropriate maximum unsigned value, so when the + * negative integral value is added the result will be an unsigned value + * correspnding to the 2's complement representation. + */ +void PNGAPI +png_save_int_32(png_bytep buf, png_int_32 i) +{ + png_save_uint_32(buf, (png_uint_32)i); +} +# endif + +# ifdef PNG_TIME_RFC1123_SUPPORTED +/* Convert the supplied time into an RFC 1123 string suitable for use in + * a "Creation Time" or other text-based time string. + */ +int PNGAPI +png_convert_to_rfc1123_buffer(char out[29], png_const_timep ptime) +{ + static const char short_months[12][4] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + if (out == NULL) + return 0; + + if (ptime->year > 9999 /* RFC1123 limitation */ || + ptime->month == 0 || ptime->month > 12 || + ptime->day == 0 || ptime->day > 31 || + ptime->hour > 23 || ptime->minute > 59 || + ptime->second > 60) + return 0; + + { + size_t pos = 0; + char number_buf[5]; /* enough for a four-digit year */ + +# define APPEND_STRING(string) pos = png_safecat(out, 29, pos, (string)) +# define APPEND_NUMBER(format, value)\ + APPEND_STRING(PNG_FORMAT_NUMBER(number_buf, format, (value))) +# define APPEND(ch) if (pos < 28) out[pos++] = (ch) + + APPEND_NUMBER(PNG_NUMBER_FORMAT_u, (unsigned)ptime->day); + APPEND(' '); + APPEND_STRING(short_months[(ptime->month - 1)]); + APPEND(' '); + APPEND_NUMBER(PNG_NUMBER_FORMAT_u, ptime->year); + APPEND(' '); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->hour); + APPEND(':'); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->minute); + APPEND(':'); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->second); + APPEND_STRING(" +0000"); /* This reliably terminates the buffer */ + PNG_UNUSED (pos) + +# undef APPEND +# undef APPEND_NUMBER +# undef APPEND_STRING + } + + return 1; +} + +# if PNG_LIBPNG_VER < 10700 +/* To do: remove the following from libpng-1.7 */ +/* Original API that uses a private buffer in png_struct. + * Deprecated because it causes png_struct to carry a spurious temporary + * buffer (png_struct::time_buffer), better to have the caller pass this in. + */ +png_const_charp PNGAPI +png_convert_to_rfc1123(png_structrp png_ptr, png_const_timep ptime) +{ + if (png_ptr != NULL) + { + /* The only failure above if png_ptr != NULL is from an invalid ptime */ + if (png_convert_to_rfc1123_buffer(png_ptr->time_buffer, ptime) == 0) + png_warning(png_ptr, "Ignoring invalid time value"); + + else + return png_ptr->time_buffer; + } + + return NULL; +} +# endif /* LIBPNG_VER < 10700 */ +# endif /* TIME_RFC1123 */ + +#endif /* READ || WRITE */ + +png_const_charp PNGAPI +png_get_copyright(png_const_structrp png_ptr) +{ + PNG_UNUSED(png_ptr) /* Silence compiler warning about unused png_ptr */ +#ifdef PNG_STRING_COPYRIGHT + return PNG_STRING_COPYRIGHT +#else + return PNG_STRING_NEWLINE \ + "libpng version 1.6.37" PNG_STRING_NEWLINE \ + "Copyright (c) 2018-2019 Cosmin Truta" PNG_STRING_NEWLINE \ + "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \ + PNG_STRING_NEWLINE \ + "Copyright (c) 1996-1997 Andreas Dilger" PNG_STRING_NEWLINE \ + "Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc." \ + PNG_STRING_NEWLINE; +#endif +} + +/* The following return the library version as a short string in the + * format 1.0.0 through 99.99.99zz. To get the version of *.h files + * used with your application, print out PNG_LIBPNG_VER_STRING, which + * is defined in png.h. + * Note: now there is no difference between png_get_libpng_ver() and + * png_get_header_ver(). Due to the version_nn_nn_nn typedef guard, + * it is guaranteed that png.c uses the correct version of png.h. + */ +png_const_charp PNGAPI +png_get_libpng_ver(png_const_structrp png_ptr) +{ + /* Version of *.c files used when building libpng */ + return png_get_header_ver(png_ptr); +} + +png_const_charp PNGAPI +png_get_header_ver(png_const_structrp png_ptr) +{ + /* Version of *.h files used when building libpng */ + PNG_UNUSED(png_ptr) /* Silence compiler warning about unused png_ptr */ + return PNG_LIBPNG_VER_STRING; +} + +png_const_charp PNGAPI +png_get_header_version(png_const_structrp png_ptr) +{ + /* Returns longer string containing both version and date */ + PNG_UNUSED(png_ptr) /* Silence compiler warning about unused png_ptr */ +#ifdef __STDC__ + return PNG_HEADER_VERSION_STRING +# ifndef PNG_READ_SUPPORTED + " (NO READ SUPPORT)" +# endif + PNG_STRING_NEWLINE; +#else + return PNG_HEADER_VERSION_STRING; +#endif +} + +#ifdef PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +/* NOTE: this routine is not used internally! */ +/* Build a grayscale palette. Palette is assumed to be 1 << bit_depth + * large of png_color. This lets grayscale images be treated as + * paletted. Most useful for gamma correction and simplification + * of code. This API is not used internally. + */ +void PNGAPI +png_build_grayscale_palette(int bit_depth, png_colorp palette) +{ + int num_palette; + int color_inc; + int i; + int v; + + png_debug(1, "in png_do_build_grayscale_palette"); + + if (palette == NULL) + return; + + switch (bit_depth) + { + case 1: + num_palette = 2; + color_inc = 0xff; + break; + + case 2: + num_palette = 4; + color_inc = 0x55; + break; + + case 4: + num_palette = 16; + color_inc = 0x11; + break; + + case 8: + num_palette = 256; + color_inc = 1; + break; + + default: + num_palette = 0; + color_inc = 0; + break; + } + + for (i = 0, v = 0; i < num_palette; i++, v += color_inc) + { + palette[i].red = (png_byte)(v & 0xff); + palette[i].green = (png_byte)(v & 0xff); + palette[i].blue = (png_byte)(v & 0xff); + } +} +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +int PNGAPI +png_handle_as_unknown(png_const_structrp png_ptr, png_const_bytep chunk_name) +{ + /* Check chunk_name and return "keep" value if it's on the list, else 0 */ + png_const_bytep p, p_end; + + if (png_ptr == NULL || chunk_name == NULL || png_ptr->num_chunk_list == 0) + return PNG_HANDLE_CHUNK_AS_DEFAULT; + + p_end = png_ptr->chunk_list; + p = p_end + png_ptr->num_chunk_list*5; /* beyond end */ + + /* The code is the fifth byte after each four byte string. Historically this + * code was always searched from the end of the list, this is no longer + * necessary because the 'set' routine handles duplicate entries correctly. + */ + do /* num_chunk_list > 0, so at least one */ + { + p -= 5; + + if (memcmp(chunk_name, p, 4) == 0) + return p[4]; + } + while (p > p_end); + + /* This means that known chunks should be processed and unknown chunks should + * be handled according to the value of png_ptr->unknown_default; this can be + * confusing because, as a result, there are two levels of defaulting for + * unknown chunks. + */ + return PNG_HANDLE_CHUNK_AS_DEFAULT; +} + +#if defined(PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) ||\ + defined(PNG_HANDLE_AS_UNKNOWN_SUPPORTED) +int /* PRIVATE */ +png_chunk_unknown_handling(png_const_structrp png_ptr, png_uint_32 chunk_name) +{ + png_byte chunk_string[5]; + + PNG_CSTRING_FROM_CHUNK(chunk_string, chunk_name); + return png_handle_as_unknown(png_ptr, chunk_string); +} +#endif /* READ_UNKNOWN_CHUNKS || HANDLE_AS_UNKNOWN */ +#endif /* SET_UNKNOWN_CHUNKS */ + +#ifdef PNG_READ_SUPPORTED +/* This function, added to libpng-1.0.6g, is untested. */ +int PNGAPI +png_reset_zstream(png_structrp png_ptr) +{ + if (png_ptr == NULL) + return Z_STREAM_ERROR; + + /* WARNING: this resets the window bits to the maximum! */ + return (inflateReset(&png_ptr->zstream)); +} +#endif /* READ */ + +/* This function was added to libpng-1.0.7 */ +png_uint_32 PNGAPI +png_access_version_number(void) +{ + /* Version of *.c files used when building libpng */ + return((png_uint_32)PNG_LIBPNG_VER); +} + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +/* Ensure that png_ptr->zstream.msg holds some appropriate error message string. + * If it doesn't 'ret' is used to set it to something appropriate, even in cases + * like Z_OK or Z_STREAM_END where the error code is apparently a success code. + */ +void /* PRIVATE */ +png_zstream_error(png_structrp png_ptr, int ret) +{ + /* Translate 'ret' into an appropriate error string, priority is given to the + * one in zstream if set. This always returns a string, even in cases like + * Z_OK or Z_STREAM_END where the error code is a success code. + */ + if (png_ptr->zstream.msg == NULL) switch (ret) + { + default: + case Z_OK: + png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected zlib return code"); + break; + + case Z_STREAM_END: + /* Normal exit */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected end of LZ stream"); + break; + + case Z_NEED_DICT: + /* This means the deflate stream did not have a dictionary; this + * indicates a bogus PNG. + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("missing LZ dictionary"); + break; + + case Z_ERRNO: + /* gz APIs only: should not happen */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("zlib IO error"); + break; + + case Z_STREAM_ERROR: + /* internal libpng error */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("bad parameters to zlib"); + break; + + case Z_DATA_ERROR: + png_ptr->zstream.msg = PNGZ_MSG_CAST("damaged LZ stream"); + break; + + case Z_MEM_ERROR: + png_ptr->zstream.msg = PNGZ_MSG_CAST("insufficient memory"); + break; + + case Z_BUF_ERROR: + /* End of input or output; not a problem if the caller is doing + * incremental read or write. + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("truncated"); + break; + + case Z_VERSION_ERROR: + png_ptr->zstream.msg = PNGZ_MSG_CAST("unsupported zlib version"); + break; + + case PNG_UNEXPECTED_ZLIB_RETURN: + /* Compile errors here mean that zlib now uses the value co-opted in + * pngpriv.h for PNG_UNEXPECTED_ZLIB_RETURN; update the switch above + * and change pngpriv.h. Note that this message is "... return", + * whereas the default/Z_OK one is "... return code". + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected zlib return"); + break; + } +} + +/* png_convert_size: a PNGAPI but no longer in png.h, so deleted + * at libpng 1.5.5! + */ + +/* Added at libpng version 1.2.34 and 1.4.0 (moved from pngset.c) */ +#ifdef PNG_GAMMA_SUPPORTED /* always set if COLORSPACE */ +static int +png_colorspace_check_gamma(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_fixed_point gAMA, int from) + /* This is called to check a new gamma value against an existing one. The + * routine returns false if the new gamma value should not be written. + * + * 'from' says where the new gamma value comes from: + * + * 0: the new gamma value is the libpng estimate for an ICC profile + * 1: the new gamma value comes from a gAMA chunk + * 2: the new gamma value comes from an sRGB chunk + */ +{ + png_fixed_point gtest; + + if ((colorspace->flags & PNG_COLORSPACE_HAVE_GAMMA) != 0 && + (png_muldiv(>est, colorspace->gamma, PNG_FP_1, gAMA) == 0 || + png_gamma_significant(gtest) != 0)) + { + /* Either this is an sRGB image, in which case the calculated gamma + * approximation should match, or this is an image with a profile and the + * value libpng calculates for the gamma of the profile does not match the + * value recorded in the file. The former, sRGB, case is an error, the + * latter is just a warning. + */ + if ((colorspace->flags & PNG_COLORSPACE_FROM_sRGB) != 0 || from == 2) + { + png_chunk_report(png_ptr, "gamma value does not match sRGB", + PNG_CHUNK_ERROR); + /* Do not overwrite an sRGB value */ + return from == 2; + } + + else /* sRGB tag not involved */ + { + png_chunk_report(png_ptr, "gamma value does not match libpng estimate", + PNG_CHUNK_WARNING); + return from == 1; + } + } + + return 1; +} + +void /* PRIVATE */ +png_colorspace_set_gamma(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_fixed_point gAMA) +{ + /* Changed in libpng-1.5.4 to limit the values to ensure overflow can't + * occur. Since the fixed point representation is asymmetrical it is + * possible for 1/gamma to overflow the limit of 21474 and this means the + * gamma value must be at least 5/100000 and hence at most 20000.0. For + * safety the limits here are a little narrower. The values are 0.00016 to + * 6250.0, which are truly ridiculous gamma values (and will produce + * displays that are all black or all white.) + * + * In 1.6.0 this test replaces the ones in pngrutil.c, in the gAMA chunk + * handling code, which only required the value to be >0. + */ + png_const_charp errmsg; + + if (gAMA < 16 || gAMA > 625000000) + errmsg = "gamma value out of range"; + +# ifdef PNG_READ_gAMA_SUPPORTED + /* Allow the application to set the gamma value more than once */ + else if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0 && + (colorspace->flags & PNG_COLORSPACE_FROM_gAMA) != 0) + errmsg = "duplicate"; +# endif + + /* Do nothing if the colorspace is already invalid */ + else if ((colorspace->flags & PNG_COLORSPACE_INVALID) != 0) + return; + + else + { + if (png_colorspace_check_gamma(png_ptr, colorspace, gAMA, + 1/*from gAMA*/) != 0) + { + /* Store this gamma value. */ + colorspace->gamma = gAMA; + colorspace->flags |= + (PNG_COLORSPACE_HAVE_GAMMA | PNG_COLORSPACE_FROM_gAMA); + } + + /* At present if the check_gamma test fails the gamma of the colorspace is + * not updated however the colorspace is not invalidated. This + * corresponds to the case where the existing gamma comes from an sRGB + * chunk or profile. An error message has already been output. + */ + return; + } + + /* Error exit - errmsg has been set. */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_chunk_report(png_ptr, errmsg, PNG_CHUNK_WRITE_ERROR); +} + +void /* PRIVATE */ +png_colorspace_sync_info(png_const_structrp png_ptr, png_inforp info_ptr) +{ + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) != 0) + { + /* Everything is invalid */ + info_ptr->valid &= ~(PNG_INFO_gAMA|PNG_INFO_cHRM|PNG_INFO_sRGB| + PNG_INFO_iCCP); + +# ifdef PNG_COLORSPACE_SUPPORTED + /* Clean up the iCCP profile now if it won't be used. */ + png_free_data(png_ptr, info_ptr, PNG_FREE_ICCP, -1/*not used*/); +# else + PNG_UNUSED(png_ptr) +# endif + } + + else + { +# ifdef PNG_COLORSPACE_SUPPORTED + /* Leave the INFO_iCCP flag set if the pngset.c code has already set + * it; this allows a PNG to contain a profile which matches sRGB and + * yet still have that profile retrievable by the application. + */ + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_MATCHES_sRGB) != 0) + info_ptr->valid |= PNG_INFO_sRGB; + + else + info_ptr->valid &= ~PNG_INFO_sRGB; + + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + info_ptr->valid |= PNG_INFO_cHRM; + + else + info_ptr->valid &= ~PNG_INFO_cHRM; +# endif + + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) != 0) + info_ptr->valid |= PNG_INFO_gAMA; + + else + info_ptr->valid &= ~PNG_INFO_gAMA; + } +} + +#ifdef PNG_READ_SUPPORTED +void /* PRIVATE */ +png_colorspace_sync(png_const_structrp png_ptr, png_inforp info_ptr) +{ + if (info_ptr == NULL) /* reduce code size; check here not in the caller */ + return; + + info_ptr->colorspace = png_ptr->colorspace; + png_colorspace_sync_info(png_ptr, info_ptr); +} +#endif +#endif /* GAMMA */ + +#ifdef PNG_COLORSPACE_SUPPORTED +/* Added at libpng-1.5.5 to support read and write of true CIEXYZ values for + * cHRM, as opposed to using chromaticities. These internal APIs return + * non-zero on a parameter error. The X, Y and Z values are required to be + * positive and less than 1.0. + */ +static int +png_xy_from_XYZ(png_xy *xy, const png_XYZ *XYZ) +{ + png_int_32 d, dwhite, whiteX, whiteY; + + d = XYZ->red_X + XYZ->red_Y + XYZ->red_Z; + if (png_muldiv(&xy->redx, XYZ->red_X, PNG_FP_1, d) == 0) + return 1; + if (png_muldiv(&xy->redy, XYZ->red_Y, PNG_FP_1, d) == 0) + return 1; + dwhite = d; + whiteX = XYZ->red_X; + whiteY = XYZ->red_Y; + + d = XYZ->green_X + XYZ->green_Y + XYZ->green_Z; + if (png_muldiv(&xy->greenx, XYZ->green_X, PNG_FP_1, d) == 0) + return 1; + if (png_muldiv(&xy->greeny, XYZ->green_Y, PNG_FP_1, d) == 0) + return 1; + dwhite += d; + whiteX += XYZ->green_X; + whiteY += XYZ->green_Y; + + d = XYZ->blue_X + XYZ->blue_Y + XYZ->blue_Z; + if (png_muldiv(&xy->bluex, XYZ->blue_X, PNG_FP_1, d) == 0) + return 1; + if (png_muldiv(&xy->bluey, XYZ->blue_Y, PNG_FP_1, d) == 0) + return 1; + dwhite += d; + whiteX += XYZ->blue_X; + whiteY += XYZ->blue_Y; + + /* The reference white is simply the sum of the end-point (X,Y,Z) vectors, + * thus: + */ + if (png_muldiv(&xy->whitex, whiteX, PNG_FP_1, dwhite) == 0) + return 1; + if (png_muldiv(&xy->whitey, whiteY, PNG_FP_1, dwhite) == 0) + return 1; + + return 0; +} + +static int +png_XYZ_from_xy(png_XYZ *XYZ, const png_xy *xy) +{ + png_fixed_point red_inverse, green_inverse, blue_scale; + png_fixed_point left, right, denominator; + + /* Check xy and, implicitly, z. Note that wide gamut color spaces typically + * have end points with 0 tristimulus values (these are impossible end + * points, but they are used to cover the possible colors). We check + * xy->whitey against 5, not 0, to avoid a possible integer overflow. + */ + if (xy->redx < 0 || xy->redx > PNG_FP_1) return 1; + if (xy->redy < 0 || xy->redy > PNG_FP_1-xy->redx) return 1; + if (xy->greenx < 0 || xy->greenx > PNG_FP_1) return 1; + if (xy->greeny < 0 || xy->greeny > PNG_FP_1-xy->greenx) return 1; + if (xy->bluex < 0 || xy->bluex > PNG_FP_1) return 1; + if (xy->bluey < 0 || xy->bluey > PNG_FP_1-xy->bluex) return 1; + if (xy->whitex < 0 || xy->whitex > PNG_FP_1) return 1; + if (xy->whitey < 5 || xy->whitey > PNG_FP_1-xy->whitex) return 1; + + /* The reverse calculation is more difficult because the original tristimulus + * value had 9 independent values (red,green,blue)x(X,Y,Z) however only 8 + * derived values were recorded in the cHRM chunk; + * (red,green,blue,white)x(x,y). This loses one degree of freedom and + * therefore an arbitrary ninth value has to be introduced to undo the + * original transformations. + * + * Think of the original end-points as points in (X,Y,Z) space. The + * chromaticity values (c) have the property: + * + * C + * c = --------- + * X + Y + Z + * + * For each c (x,y,z) from the corresponding original C (X,Y,Z). Thus the + * three chromaticity values (x,y,z) for each end-point obey the + * relationship: + * + * x + y + z = 1 + * + * This describes the plane in (X,Y,Z) space that intersects each axis at the + * value 1.0; call this the chromaticity plane. Thus the chromaticity + * calculation has scaled each end-point so that it is on the x+y+z=1 plane + * and chromaticity is the intersection of the vector from the origin to the + * (X,Y,Z) value with the chromaticity plane. + * + * To fully invert the chromaticity calculation we would need the three + * end-point scale factors, (red-scale, green-scale, blue-scale), but these + * were not recorded. Instead we calculated the reference white (X,Y,Z) and + * recorded the chromaticity of this. The reference white (X,Y,Z) would have + * given all three of the scale factors since: + * + * color-C = color-c * color-scale + * white-C = red-C + green-C + blue-C + * = red-c*red-scale + green-c*green-scale + blue-c*blue-scale + * + * But cHRM records only white-x and white-y, so we have lost the white scale + * factor: + * + * white-C = white-c*white-scale + * + * To handle this the inverse transformation makes an arbitrary assumption + * about white-scale: + * + * Assume: white-Y = 1.0 + * Hence: white-scale = 1/white-y + * Or: red-Y + green-Y + blue-Y = 1.0 + * + * Notice the last statement of the assumption gives an equation in three of + * the nine values we want to calculate. 8 more equations come from the + * above routine as summarised at the top above (the chromaticity + * calculation): + * + * Given: color-x = color-X / (color-X + color-Y + color-Z) + * Hence: (color-x - 1)*color-X + color.x*color-Y + color.x*color-Z = 0 + * + * This is 9 simultaneous equations in the 9 variables "color-C" and can be + * solved by Cramer's rule. Cramer's rule requires calculating 10 9x9 matrix + * determinants, however this is not as bad as it seems because only 28 of + * the total of 90 terms in the various matrices are non-zero. Nevertheless + * Cramer's rule is notoriously numerically unstable because the determinant + * calculation involves the difference of large, but similar, numbers. It is + * difficult to be sure that the calculation is stable for real world values + * and it is certain that it becomes unstable where the end points are close + * together. + * + * So this code uses the perhaps slightly less optimal but more + * understandable and totally obvious approach of calculating color-scale. + * + * This algorithm depends on the precision in white-scale and that is + * (1/white-y), so we can immediately see that as white-y approaches 0 the + * accuracy inherent in the cHRM chunk drops off substantially. + * + * libpng arithmetic: a simple inversion of the above equations + * ------------------------------------------------------------ + * + * white_scale = 1/white-y + * white-X = white-x * white-scale + * white-Y = 1.0 + * white-Z = (1 - white-x - white-y) * white_scale + * + * white-C = red-C + green-C + blue-C + * = red-c*red-scale + green-c*green-scale + blue-c*blue-scale + * + * This gives us three equations in (red-scale,green-scale,blue-scale) where + * all the coefficients are now known: + * + * red-x*red-scale + green-x*green-scale + blue-x*blue-scale + * = white-x/white-y + * red-y*red-scale + green-y*green-scale + blue-y*blue-scale = 1 + * red-z*red-scale + green-z*green-scale + blue-z*blue-scale + * = (1 - white-x - white-y)/white-y + * + * In the last equation color-z is (1 - color-x - color-y) so we can add all + * three equations together to get an alternative third: + * + * red-scale + green-scale + blue-scale = 1/white-y = white-scale + * + * So now we have a Cramer's rule solution where the determinants are just + * 3x3 - far more tractible. Unfortunately 3x3 determinants still involve + * multiplication of three coefficients so we can't guarantee to avoid + * overflow in the libpng fixed point representation. Using Cramer's rule in + * floating point is probably a good choice here, but it's not an option for + * fixed point. Instead proceed to simplify the first two equations by + * eliminating what is likely to be the largest value, blue-scale: + * + * blue-scale = white-scale - red-scale - green-scale + * + * Hence: + * + * (red-x - blue-x)*red-scale + (green-x - blue-x)*green-scale = + * (white-x - blue-x)*white-scale + * + * (red-y - blue-y)*red-scale + (green-y - blue-y)*green-scale = + * 1 - blue-y*white-scale + * + * And now we can trivially solve for (red-scale,green-scale): + * + * green-scale = + * (white-x - blue-x)*white-scale - (red-x - blue-x)*red-scale + * ----------------------------------------------------------- + * green-x - blue-x + * + * red-scale = + * 1 - blue-y*white-scale - (green-y - blue-y) * green-scale + * --------------------------------------------------------- + * red-y - blue-y + * + * Hence: + * + * red-scale = + * ( (green-x - blue-x) * (white-y - blue-y) - + * (green-y - blue-y) * (white-x - blue-x) ) / white-y + * ------------------------------------------------------------------------- + * (green-x - blue-x)*(red-y - blue-y)-(green-y - blue-y)*(red-x - blue-x) + * + * green-scale = + * ( (red-y - blue-y) * (white-x - blue-x) - + * (red-x - blue-x) * (white-y - blue-y) ) / white-y + * ------------------------------------------------------------------------- + * (green-x - blue-x)*(red-y - blue-y)-(green-y - blue-y)*(red-x - blue-x) + * + * Accuracy: + * The input values have 5 decimal digits of accuracy. The values are all in + * the range 0 < value < 1, so simple products are in the same range but may + * need up to 10 decimal digits to preserve the original precision and avoid + * underflow. Because we are using a 32-bit signed representation we cannot + * match this; the best is a little over 9 decimal digits, less than 10. + * + * The approach used here is to preserve the maximum precision within the + * signed representation. Because the red-scale calculation above uses the + * difference between two products of values that must be in the range -1..+1 + * it is sufficient to divide the product by 7; ceil(100,000/32767*2). The + * factor is irrelevant in the calculation because it is applied to both + * numerator and denominator. + * + * Note that the values of the differences of the products of the + * chromaticities in the above equations tend to be small, for example for + * the sRGB chromaticities they are: + * + * red numerator: -0.04751 + * green numerator: -0.08788 + * denominator: -0.2241 (without white-y multiplication) + * + * The resultant Y coefficients from the chromaticities of some widely used + * color space definitions are (to 15 decimal places): + * + * sRGB + * 0.212639005871510 0.715168678767756 0.072192315360734 + * Kodak ProPhoto + * 0.288071128229293 0.711843217810102 0.000085653960605 + * Adobe RGB + * 0.297344975250536 0.627363566255466 0.075291458493998 + * Adobe Wide Gamut RGB + * 0.258728243040113 0.724682314948566 0.016589442011321 + */ + /* By the argument, above overflow should be impossible here. The return + * value of 2 indicates an internal error to the caller. + */ + if (png_muldiv(&left, xy->greenx-xy->bluex, xy->redy - xy->bluey, 7) == 0) + return 2; + if (png_muldiv(&right, xy->greeny-xy->bluey, xy->redx - xy->bluex, 7) == 0) + return 2; + denominator = left - right; + + /* Now find the red numerator. */ + if (png_muldiv(&left, xy->greenx-xy->bluex, xy->whitey-xy->bluey, 7) == 0) + return 2; + if (png_muldiv(&right, xy->greeny-xy->bluey, xy->whitex-xy->bluex, 7) == 0) + return 2; + + /* Overflow is possible here and it indicates an extreme set of PNG cHRM + * chunk values. This calculation actually returns the reciprocal of the + * scale value because this allows us to delay the multiplication of white-y + * into the denominator, which tends to produce a small number. + */ + if (png_muldiv(&red_inverse, xy->whitey, denominator, left-right) == 0 || + red_inverse <= xy->whitey /* r+g+b scales = white scale */) + return 1; + + /* Similarly for green_inverse: */ + if (png_muldiv(&left, xy->redy-xy->bluey, xy->whitex-xy->bluex, 7) == 0) + return 2; + if (png_muldiv(&right, xy->redx-xy->bluex, xy->whitey-xy->bluey, 7) == 0) + return 2; + if (png_muldiv(&green_inverse, xy->whitey, denominator, left-right) == 0 || + green_inverse <= xy->whitey) + return 1; + + /* And the blue scale, the checks above guarantee this can't overflow but it + * can still produce 0 for extreme cHRM values. + */ + blue_scale = png_reciprocal(xy->whitey) - png_reciprocal(red_inverse) - + png_reciprocal(green_inverse); + if (blue_scale <= 0) + return 1; + + + /* And fill in the png_XYZ: */ + if (png_muldiv(&XYZ->red_X, xy->redx, PNG_FP_1, red_inverse) == 0) + return 1; + if (png_muldiv(&XYZ->red_Y, xy->redy, PNG_FP_1, red_inverse) == 0) + return 1; + if (png_muldiv(&XYZ->red_Z, PNG_FP_1 - xy->redx - xy->redy, PNG_FP_1, + red_inverse) == 0) + return 1; + + if (png_muldiv(&XYZ->green_X, xy->greenx, PNG_FP_1, green_inverse) == 0) + return 1; + if (png_muldiv(&XYZ->green_Y, xy->greeny, PNG_FP_1, green_inverse) == 0) + return 1; + if (png_muldiv(&XYZ->green_Z, PNG_FP_1 - xy->greenx - xy->greeny, PNG_FP_1, + green_inverse) == 0) + return 1; + + if (png_muldiv(&XYZ->blue_X, xy->bluex, blue_scale, PNG_FP_1) == 0) + return 1; + if (png_muldiv(&XYZ->blue_Y, xy->bluey, blue_scale, PNG_FP_1) == 0) + return 1; + if (png_muldiv(&XYZ->blue_Z, PNG_FP_1 - xy->bluex - xy->bluey, blue_scale, + PNG_FP_1) == 0) + return 1; + + return 0; /*success*/ +} + +static int +png_XYZ_normalize(png_XYZ *XYZ) +{ + png_int_32 Y; + + if (XYZ->red_Y < 0 || XYZ->green_Y < 0 || XYZ->blue_Y < 0 || + XYZ->red_X < 0 || XYZ->green_X < 0 || XYZ->blue_X < 0 || + XYZ->red_Z < 0 || XYZ->green_Z < 0 || XYZ->blue_Z < 0) + return 1; + + /* Normalize by scaling so the sum of the end-point Y values is PNG_FP_1. + * IMPLEMENTATION NOTE: ANSI requires signed overflow not to occur, therefore + * relying on addition of two positive values producing a negative one is not + * safe. + */ + Y = XYZ->red_Y; + if (0x7fffffff - Y < XYZ->green_X) + return 1; + Y += XYZ->green_Y; + if (0x7fffffff - Y < XYZ->blue_X) + return 1; + Y += XYZ->blue_Y; + + if (Y != PNG_FP_1) + { + if (png_muldiv(&XYZ->red_X, XYZ->red_X, PNG_FP_1, Y) == 0) + return 1; + if (png_muldiv(&XYZ->red_Y, XYZ->red_Y, PNG_FP_1, Y) == 0) + return 1; + if (png_muldiv(&XYZ->red_Z, XYZ->red_Z, PNG_FP_1, Y) == 0) + return 1; + + if (png_muldiv(&XYZ->green_X, XYZ->green_X, PNG_FP_1, Y) == 0) + return 1; + if (png_muldiv(&XYZ->green_Y, XYZ->green_Y, PNG_FP_1, Y) == 0) + return 1; + if (png_muldiv(&XYZ->green_Z, XYZ->green_Z, PNG_FP_1, Y) == 0) + return 1; + + if (png_muldiv(&XYZ->blue_X, XYZ->blue_X, PNG_FP_1, Y) == 0) + return 1; + if (png_muldiv(&XYZ->blue_Y, XYZ->blue_Y, PNG_FP_1, Y) == 0) + return 1; + if (png_muldiv(&XYZ->blue_Z, XYZ->blue_Z, PNG_FP_1, Y) == 0) + return 1; + } + + return 0; +} + +static int +png_colorspace_endpoints_match(const png_xy *xy1, const png_xy *xy2, int delta) +{ + /* Allow an error of +/-0.01 (absolute value) on each chromaticity */ + if (PNG_OUT_OF_RANGE(xy1->whitex, xy2->whitex,delta) || + PNG_OUT_OF_RANGE(xy1->whitey, xy2->whitey,delta) || + PNG_OUT_OF_RANGE(xy1->redx, xy2->redx, delta) || + PNG_OUT_OF_RANGE(xy1->redy, xy2->redy, delta) || + PNG_OUT_OF_RANGE(xy1->greenx, xy2->greenx,delta) || + PNG_OUT_OF_RANGE(xy1->greeny, xy2->greeny,delta) || + PNG_OUT_OF_RANGE(xy1->bluex, xy2->bluex, delta) || + PNG_OUT_OF_RANGE(xy1->bluey, xy2->bluey, delta)) + return 0; + return 1; +} + +/* Added in libpng-1.6.0, a different check for the validity of a set of cHRM + * chunk chromaticities. Earlier checks used to simply look for the overflow + * condition (where the determinant of the matrix to solve for XYZ ends up zero + * because the chromaticity values are not all distinct.) Despite this it is + * theoretically possible to produce chromaticities that are apparently valid + * but that rapidly degrade to invalid, potentially crashing, sets because of + * arithmetic inaccuracies when calculations are performed on them. The new + * check is to round-trip xy -> XYZ -> xy and then check that the result is + * within a small percentage of the original. + */ +static int +png_colorspace_check_xy(png_XYZ *XYZ, const png_xy *xy) +{ + int result; + png_xy xy_test; + + /* As a side-effect this routine also returns the XYZ endpoints. */ + result = png_XYZ_from_xy(XYZ, xy); + if (result != 0) + return result; + + result = png_xy_from_XYZ(&xy_test, XYZ); + if (result != 0) + return result; + + if (png_colorspace_endpoints_match(xy, &xy_test, + 5/*actually, the math is pretty accurate*/) != 0) + return 0; + + /* Too much slip */ + return 1; +} + +/* This is the check going the other way. The XYZ is modified to normalize it + * (another side-effect) and the xy chromaticities are returned. + */ +static int +png_colorspace_check_XYZ(png_xy *xy, png_XYZ *XYZ) +{ + int result; + png_XYZ XYZtemp; + + result = png_XYZ_normalize(XYZ); + if (result != 0) + return result; + + result = png_xy_from_XYZ(xy, XYZ); + if (result != 0) + return result; + + XYZtemp = *XYZ; + return png_colorspace_check_xy(&XYZtemp, xy); +} + +/* Used to check for an endpoint match against sRGB */ +static const png_xy sRGB_xy = /* From ITU-R BT.709-3 */ +{ + /* color x y */ + /* red */ 64000, 33000, + /* green */ 30000, 60000, + /* blue */ 15000, 6000, + /* white */ 31270, 32900 +}; + +static int +png_colorspace_set_xy_and_XYZ(png_const_structrp png_ptr, + png_colorspacerp colorspace, const png_xy *xy, const png_XYZ *XYZ, + int preferred) +{ + if ((colorspace->flags & PNG_COLORSPACE_INVALID) != 0) + return 0; + + /* The consistency check is performed on the chromaticities; this factors out + * variations because of the normalization (or not) of the end point Y + * values. + */ + if (preferred < 2 && + (colorspace->flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + { + /* The end points must be reasonably close to any we already have. The + * following allows an error of up to +/-.001 + */ + if (png_colorspace_endpoints_match(xy, &colorspace->end_points_xy, + 100) == 0) + { + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_benign_error(png_ptr, "inconsistent chromaticities"); + return 0; /* failed */ + } + + /* Only overwrite with preferred values */ + if (preferred == 0) + return 1; /* ok, but no change */ + } + + colorspace->end_points_xy = *xy; + colorspace->end_points_XYZ = *XYZ; + colorspace->flags |= PNG_COLORSPACE_HAVE_ENDPOINTS; + + /* The end points are normally quoted to two decimal digits, so allow +/-0.01 + * on this test. + */ + if (png_colorspace_endpoints_match(xy, &sRGB_xy, 1000) != 0) + colorspace->flags |= PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB; + + else + colorspace->flags &= PNG_COLORSPACE_CANCEL( + PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB); + + return 2; /* ok and changed */ +} + +int /* PRIVATE */ +png_colorspace_set_chromaticities(png_const_structrp png_ptr, + png_colorspacerp colorspace, const png_xy *xy, int preferred) +{ + /* We must check the end points to ensure they are reasonable - in the past + * color management systems have crashed as a result of getting bogus + * colorant values, while this isn't the fault of libpng it is the + * responsibility of libpng because PNG carries the bomb and libpng is in a + * position to protect against it. + */ + png_XYZ XYZ; + + switch (png_colorspace_check_xy(&XYZ, xy)) + { + case 0: /* success */ + return png_colorspace_set_xy_and_XYZ(png_ptr, colorspace, xy, &XYZ, + preferred); + + case 1: + /* We can't invert the chromaticities so we can't produce value XYZ + * values. Likely as not a color management system will fail too. + */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_benign_error(png_ptr, "invalid chromaticities"); + break; + + default: + /* libpng is broken; this should be a warning but if it happens we + * want error reports so for the moment it is an error. + */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_error(png_ptr, "internal error checking chromaticities"); + } + + return 0; /* failed */ +} + +int /* PRIVATE */ +png_colorspace_set_endpoints(png_const_structrp png_ptr, + png_colorspacerp colorspace, const png_XYZ *XYZ_in, int preferred) +{ + png_XYZ XYZ = *XYZ_in; + png_xy xy; + + switch (png_colorspace_check_XYZ(&xy, &XYZ)) + { + case 0: + return png_colorspace_set_xy_and_XYZ(png_ptr, colorspace, &xy, &XYZ, + preferred); + + case 1: + /* End points are invalid. */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_benign_error(png_ptr, "invalid end points"); + break; + + default: + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_error(png_ptr, "internal error checking chromaticities"); + } + + return 0; /* failed */ +} + +#if defined(PNG_sRGB_SUPPORTED) || defined(PNG_iCCP_SUPPORTED) +/* Error message generation */ +static char +png_icc_tag_char(png_uint_32 byte) +{ + byte &= 0xff; + if (byte >= 32 && byte <= 126) + return (char)byte; + else + return '?'; +} + +static void +png_icc_tag_name(char *name, png_uint_32 tag) +{ + name[0] = '\''; + name[1] = png_icc_tag_char(tag >> 24); + name[2] = png_icc_tag_char(tag >> 16); + name[3] = png_icc_tag_char(tag >> 8); + name[4] = png_icc_tag_char(tag ); + name[5] = '\''; +} + +static int +is_ICC_signature_char(png_alloc_size_t it) +{ + return it == 32 || (it >= 48 && it <= 57) || (it >= 65 && it <= 90) || + (it >= 97 && it <= 122); +} + +static int +is_ICC_signature(png_alloc_size_t it) +{ + return is_ICC_signature_char(it >> 24) /* checks all the top bits */ && + is_ICC_signature_char((it >> 16) & 0xff) && + is_ICC_signature_char((it >> 8) & 0xff) && + is_ICC_signature_char(it & 0xff); +} + +static int +png_icc_profile_error(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_alloc_size_t value, png_const_charp reason) +{ + size_t pos; + char message[196]; /* see below for calculation */ + + if (colorspace != NULL) + colorspace->flags |= PNG_COLORSPACE_INVALID; + + pos = png_safecat(message, (sizeof message), 0, "profile '"); /* 9 chars */ + pos = png_safecat(message, pos+79, pos, name); /* Truncate to 79 chars */ + pos = png_safecat(message, (sizeof message), pos, "': "); /* +2 = 90 */ + if (is_ICC_signature(value) != 0) + { + /* So 'value' is at most 4 bytes and the following cast is safe */ + png_icc_tag_name(message+pos, (png_uint_32)value); + pos += 6; /* total +8; less than the else clause */ + message[pos++] = ':'; + message[pos++] = ' '; + } +# ifdef PNG_WARNINGS_SUPPORTED + else + { + char number[PNG_NUMBER_BUFFER_SIZE]; /* +24 = 114*/ + + pos = png_safecat(message, (sizeof message), pos, + png_format_number(number, number+(sizeof number), + PNG_NUMBER_FORMAT_x, value)); + pos = png_safecat(message, (sizeof message), pos, "h: "); /*+2 = 116*/ + } +# endif + /* The 'reason' is an arbitrary message, allow +79 maximum 195 */ + pos = png_safecat(message, (sizeof message), pos, reason); + PNG_UNUSED(pos) + + /* This is recoverable, but make it unconditionally an app_error on write to + * avoid writing invalid ICC profiles into PNG files (i.e., we handle them + * on read, with a warning, but on write unless the app turns off + * application errors the PNG won't be written.) + */ + png_chunk_report(png_ptr, message, + (colorspace != NULL) ? PNG_CHUNK_ERROR : PNG_CHUNK_WRITE_ERROR); + + return 0; +} +#endif /* sRGB || iCCP */ + +#ifdef PNG_sRGB_SUPPORTED +int /* PRIVATE */ +png_colorspace_set_sRGB(png_const_structrp png_ptr, png_colorspacerp colorspace, + int intent) +{ + /* sRGB sets known gamma, end points and (from the chunk) intent. */ + /* IMPORTANT: these are not necessarily the values found in an ICC profile + * because ICC profiles store values adapted to a D50 environment; it is + * expected that the ICC profile mediaWhitePointTag will be D50; see the + * checks and code elsewhere to understand this better. + * + * These XYZ values, which are accurate to 5dp, produce rgb to gray + * coefficients of (6968,23435,2366), which are reduced (because they add up + * to 32769 not 32768) to (6968,23434,2366). These are the values that + * libpng has traditionally used (and are the best values given the 15bit + * algorithm used by the rgb to gray code.) + */ + static const png_XYZ sRGB_XYZ = /* D65 XYZ (*not* the D50 adapted values!) */ + { + /* color X Y Z */ + /* red */ 41239, 21264, 1933, + /* green */ 35758, 71517, 11919, + /* blue */ 18048, 7219, 95053 + }; + + /* Do nothing if the colorspace is already invalidated. */ + if ((colorspace->flags & PNG_COLORSPACE_INVALID) != 0) + return 0; + + /* Check the intent, then check for existing settings. It is valid for the + * PNG file to have cHRM or gAMA chunks along with sRGB, but the values must + * be consistent with the correct values. If, however, this function is + * called below because an iCCP chunk matches sRGB then it is quite + * conceivable that an older app recorded incorrect gAMA and cHRM because of + * an incorrect calculation based on the values in the profile - this does + * *not* invalidate the profile (though it still produces an error, which can + * be ignored.) + */ + if (intent < 0 || intent >= PNG_sRGB_INTENT_LAST) + return png_icc_profile_error(png_ptr, colorspace, "sRGB", + (png_alloc_size_t)intent, "invalid sRGB rendering intent"); + + if ((colorspace->flags & PNG_COLORSPACE_HAVE_INTENT) != 0 && + colorspace->rendering_intent != intent) + return png_icc_profile_error(png_ptr, colorspace, "sRGB", + (png_alloc_size_t)intent, "inconsistent rendering intents"); + + if ((colorspace->flags & PNG_COLORSPACE_FROM_sRGB) != 0) + { + png_benign_error(png_ptr, "duplicate sRGB information ignored"); + return 0; + } + + /* If the standard sRGB cHRM chunk does not match the one from the PNG file + * warn but overwrite the value with the correct one. + */ + if ((colorspace->flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0 && + !png_colorspace_endpoints_match(&sRGB_xy, &colorspace->end_points_xy, + 100)) + png_chunk_report(png_ptr, "cHRM chunk does not match sRGB", + PNG_CHUNK_ERROR); + + /* This check is just done for the error reporting - the routine always + * returns true when the 'from' argument corresponds to sRGB (2). + */ + (void)png_colorspace_check_gamma(png_ptr, colorspace, PNG_GAMMA_sRGB_INVERSE, + 2/*from sRGB*/); + + /* intent: bugs in GCC force 'int' to be used as the parameter type. */ + colorspace->rendering_intent = (png_uint_16)intent; + colorspace->flags |= PNG_COLORSPACE_HAVE_INTENT; + + /* endpoints */ + colorspace->end_points_xy = sRGB_xy; + colorspace->end_points_XYZ = sRGB_XYZ; + colorspace->flags |= + (PNG_COLORSPACE_HAVE_ENDPOINTS|PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB); + + /* gamma */ + colorspace->gamma = PNG_GAMMA_sRGB_INVERSE; + colorspace->flags |= PNG_COLORSPACE_HAVE_GAMMA; + + /* Finally record that we have an sRGB profile */ + colorspace->flags |= + (PNG_COLORSPACE_MATCHES_sRGB|PNG_COLORSPACE_FROM_sRGB); + + return 1; /* set */ +} +#endif /* sRGB */ + +#ifdef PNG_iCCP_SUPPORTED +/* Encoded value of D50 as an ICC XYZNumber. From the ICC 2010 spec the value + * is XYZ(0.9642,1.0,0.8249), which scales to: + * + * (63189.8112, 65536, 54060.6464) + */ +static const png_byte D50_nCIEXYZ[12] = + { 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d }; + +static int /* bool */ +icc_check_length(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length) +{ + if (profile_length < 132) + return png_icc_profile_error(png_ptr, colorspace, name, profile_length, + "too short"); + return 1; +} + +#ifdef PNG_READ_iCCP_SUPPORTED +int /* PRIVATE */ +png_icc_check_length(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length) +{ + if (!icc_check_length(png_ptr, colorspace, name, profile_length)) + return 0; + + /* This needs to be here because the 'normal' check is in + * png_decompress_chunk, yet this happens after the attempt to + * png_malloc_base the required data. We only need this on read; on write + * the caller supplies the profile buffer so libpng doesn't allocate it. See + * the call to icc_check_length below (the write case). + */ +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + else if (png_ptr->user_chunk_malloc_max > 0 && + png_ptr->user_chunk_malloc_max < profile_length) + return png_icc_profile_error(png_ptr, colorspace, name, profile_length, + "exceeds application limits"); +# elif PNG_USER_CHUNK_MALLOC_MAX > 0 + else if (PNG_USER_CHUNK_MALLOC_MAX < profile_length) + return png_icc_profile_error(png_ptr, colorspace, name, profile_length, + "exceeds libpng limits"); +# else /* !SET_USER_LIMITS */ + /* This will get compiled out on all 32-bit and better systems. */ + else if (PNG_SIZE_MAX < profile_length) + return png_icc_profile_error(png_ptr, colorspace, name, profile_length, + "exceeds system limits"); +# endif /* !SET_USER_LIMITS */ + + return 1; +} +#endif /* READ_iCCP */ + +int /* PRIVATE */ +png_icc_check_header(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length, + png_const_bytep profile/* first 132 bytes only */, int color_type) +{ + png_uint_32 temp; + + /* Length check; this cannot be ignored in this code because profile_length + * is used later to check the tag table, so even if the profile seems over + * long profile_length from the caller must be correct. The caller can fix + * this up on read or write by just passing in the profile header length. + */ + temp = png_get_uint_32(profile); + if (temp != profile_length) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "length does not match profile"); + + temp = (png_uint_32) (*(profile+8)); + if (temp > 3 && (profile_length & 3)) + return png_icc_profile_error(png_ptr, colorspace, name, profile_length, + "invalid length"); + + temp = png_get_uint_32(profile+128); /* tag count: 12 bytes/tag */ + if (temp > 357913930 || /* (2^32-4-132)/12: maximum possible tag count */ + profile_length < 132+12*temp) /* truncated tag table */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "tag count too large"); + + /* The 'intent' must be valid or we can't store it, ICC limits the intent to + * 16 bits. + */ + temp = png_get_uint_32(profile+64); + if (temp >= 0xffff) /* The ICC limit */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid rendering intent"); + + /* This is just a warning because the profile may be valid in future + * versions. + */ + if (temp >= PNG_sRGB_INTENT_LAST) + (void)png_icc_profile_error(png_ptr, NULL, name, temp, + "intent outside defined range"); + + /* At this point the tag table can't be checked because it hasn't necessarily + * been loaded; however, various header fields can be checked. These checks + * are for values permitted by the PNG spec in an ICC profile; the PNG spec + * restricts the profiles that can be passed in an iCCP chunk (they must be + * appropriate to processing PNG data!) + */ + + /* Data checks (could be skipped). These checks must be independent of the + * version number; however, the version number doesn't accommodate changes in + * the header fields (just the known tags and the interpretation of the + * data.) + */ + temp = png_get_uint_32(profile+36); /* signature 'ascp' */ + if (temp != 0x61637370) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid signature"); + + /* Currently the PCS illuminant/adopted white point (the computational + * white point) are required to be D50, + * however the profile contains a record of the illuminant so perhaps ICC + * expects to be able to change this in the future (despite the rationale in + * the introduction for using a fixed PCS adopted white.) Consequently the + * following is just a warning. + */ + if (memcmp(profile+68, D50_nCIEXYZ, 12) != 0) + (void)png_icc_profile_error(png_ptr, NULL, name, 0/*no tag value*/, + "PCS illuminant is not D50"); + + /* The PNG spec requires this: + * "If the iCCP chunk is present, the image samples conform to the colour + * space represented by the embedded ICC profile as defined by the + * International Color Consortium [ICC]. The colour space of the ICC profile + * shall be an RGB colour space for colour images (PNG colour types 2, 3, and + * 6), or a greyscale colour space for greyscale images (PNG colour types 0 + * and 4)." + * + * This checking code ensures the embedded profile (on either read or write) + * conforms to the specification requirements. Notice that an ICC 'gray' + * color-space profile contains the information to transform the monochrome + * data to XYZ or L*a*b (according to which PCS the profile uses) and this + * should be used in preference to the standard libpng K channel replication + * into R, G and B channels. + * + * Previously it was suggested that an RGB profile on grayscale data could be + * handled. However it it is clear that using an RGB profile in this context + * must be an error - there is no specification of what it means. Thus it is + * almost certainly more correct to ignore the profile. + */ + temp = png_get_uint_32(profile+16); /* data colour space field */ + switch (temp) + { + case 0x52474220: /* 'RGB ' */ + if ((color_type & PNG_COLOR_MASK_COLOR) == 0) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "RGB color space not permitted on grayscale PNG"); + break; + + case 0x47524159: /* 'GRAY' */ + if ((color_type & PNG_COLOR_MASK_COLOR) != 0) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "Gray color space not permitted on RGB PNG"); + break; + + default: + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid ICC profile color space"); + } + + /* It is up to the application to check that the profile class matches the + * application requirements; the spec provides no guidance, but it's pretty + * weird if the profile is not scanner ('scnr'), monitor ('mntr'), printer + * ('prtr') or 'spac' (for generic color spaces). Issue a warning in these + * cases. Issue an error for device link or abstract profiles - these don't + * contain the records necessary to transform the color-space to anything + * other than the target device (and not even that for an abstract profile). + * Profiles of these classes may not be embedded in images. + */ + temp = png_get_uint_32(profile+12); /* profile/device class */ + switch (temp) + { + case 0x73636e72: /* 'scnr' */ + case 0x6d6e7472: /* 'mntr' */ + case 0x70727472: /* 'prtr' */ + case 0x73706163: /* 'spac' */ + /* All supported */ + break; + + case 0x61627374: /* 'abst' */ + /* May not be embedded in an image */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid embedded Abstract ICC profile"); + + case 0x6c696e6b: /* 'link' */ + /* DeviceLink profiles cannot be interpreted in a non-device specific + * fashion, if an app uses the AToB0Tag in the profile the results are + * undefined unless the result is sent to the intended device, + * therefore a DeviceLink profile should not be found embedded in a + * PNG. + */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "unexpected DeviceLink ICC profile class"); + + case 0x6e6d636c: /* 'nmcl' */ + /* A NamedColor profile is also device specific, however it doesn't + * contain an AToB0 tag that is open to misinterpretation. Almost + * certainly it will fail the tests below. + */ + (void)png_icc_profile_error(png_ptr, NULL, name, temp, + "unexpected NamedColor ICC profile class"); + break; + + default: + /* To allow for future enhancements to the profile accept unrecognized + * profile classes with a warning, these then hit the test below on the + * tag content to ensure they are backward compatible with one of the + * understood profiles. + */ + (void)png_icc_profile_error(png_ptr, NULL, name, temp, + "unrecognized ICC profile class"); + break; + } + + /* For any profile other than a device link one the PCS must be encoded + * either in XYZ or Lab. + */ + temp = png_get_uint_32(profile+20); + switch (temp) + { + case 0x58595a20: /* 'XYZ ' */ + case 0x4c616220: /* 'Lab ' */ + break; + + default: + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "unexpected ICC PCS encoding"); + } + + return 1; +} + +int /* PRIVATE */ +png_icc_check_tag_table(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length, + png_const_bytep profile /* header plus whole tag table */) +{ + png_uint_32 tag_count = png_get_uint_32(profile+128); + png_uint_32 itag; + png_const_bytep tag = profile+132; /* The first tag */ + + /* First scan all the tags in the table and add bits to the icc_info value + * (temporarily in 'tags'). + */ + for (itag=0; itag < tag_count; ++itag, tag += 12) + { + png_uint_32 tag_id = png_get_uint_32(tag+0); + png_uint_32 tag_start = png_get_uint_32(tag+4); /* must be aligned */ + png_uint_32 tag_length = png_get_uint_32(tag+8);/* not padded */ + + /* The ICC specification does not exclude zero length tags, therefore the + * start might actually be anywhere if there is no data, but this would be + * a clear abuse of the intent of the standard so the start is checked for + * being in range. All defined tag types have an 8 byte header - a 4 byte + * type signature then 0. + */ + + /* This is a hard error; potentially it can cause read outside the + * profile. + */ + if (tag_start > profile_length || tag_length > profile_length - tag_start) + return png_icc_profile_error(png_ptr, colorspace, name, tag_id, + "ICC profile tag outside profile"); + + if ((tag_start & 3) != 0) + { + /* CNHP730S.icc shipped with Microsoft Windows 64 violates this; it is + * only a warning here because libpng does not care about the + * alignment. + */ + (void)png_icc_profile_error(png_ptr, NULL, name, tag_id, + "ICC profile tag start not a multiple of 4"); + } + } + + return 1; /* success, maybe with warnings */ +} + +#ifdef PNG_sRGB_SUPPORTED +#if PNG_sRGB_PROFILE_CHECKS >= 0 +/* Information about the known ICC sRGB profiles */ +static const struct +{ + png_uint_32 adler, crc, length; + png_uint_32 md5[4]; + png_byte have_md5; + png_byte is_broken; + png_uint_16 intent; + +# define PNG_MD5(a,b,c,d) { a, b, c, d }, (a!=0)||(b!=0)||(c!=0)||(d!=0) +# define PNG_ICC_CHECKSUM(adler, crc, md5, intent, broke, date, length, fname)\ + { adler, crc, length, md5, broke, intent }, + +} png_sRGB_checks[] = +{ + /* This data comes from contrib/tools/checksum-icc run on downloads of + * all four ICC sRGB profiles from www.color.org. + */ + /* adler32, crc32, MD5[4], intent, date, length, file-name */ + PNG_ICC_CHECKSUM(0x0a3fd9f6, 0x3b8772b9, + PNG_MD5(0x29f83dde, 0xaff255ae, 0x7842fae4, 0xca83390d), 0, 0, + "2009/03/27 21:36:31", 3048, "sRGB_IEC61966-2-1_black_scaled.icc") + + /* ICC sRGB v2 perceptual no black-compensation: */ + PNG_ICC_CHECKSUM(0x4909e5e1, 0x427ebb21, + PNG_MD5(0xc95bd637, 0xe95d8a3b, 0x0df38f99, 0xc1320389), 1, 0, + "2009/03/27 21:37:45", 3052, "sRGB_IEC61966-2-1_no_black_scaling.icc") + + PNG_ICC_CHECKSUM(0xfd2144a1, 0x306fd8ae, + PNG_MD5(0xfc663378, 0x37e2886b, 0xfd72e983, 0x8228f1b8), 0, 0, + "2009/08/10 17:28:01", 60988, "sRGB_v4_ICC_preference_displayclass.icc") + + /* ICC sRGB v4 perceptual */ + PNG_ICC_CHECKSUM(0x209c35d2, 0xbbef7812, + PNG_MD5(0x34562abf, 0x994ccd06, 0x6d2c5721, 0xd0d68c5d), 0, 0, + "2007/07/25 00:05:37", 60960, "sRGB_v4_ICC_preference.icc") + + /* The following profiles have no known MD5 checksum. If there is a match + * on the (empty) MD5 the other fields are used to attempt a match and + * a warning is produced. The first two of these profiles have a 'cprt' tag + * which suggests that they were also made by Hewlett Packard. + */ + PNG_ICC_CHECKSUM(0xa054d762, 0x5d5129ce, + PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 1, 0, + "2004/07/21 18:57:42", 3024, "sRGB_IEC61966-2-1_noBPC.icc") + + /* This is a 'mntr' (display) profile with a mediaWhitePointTag that does not + * match the D50 PCS illuminant in the header (it is in fact the D65 values, + * so the white point is recorded as the un-adapted value.) The profiles + * below only differ in one byte - the intent - and are basically the same as + * the previous profile except for the mediaWhitePointTag error and a missing + * chromaticAdaptationTag. + */ + PNG_ICC_CHECKSUM(0xf784f3fb, 0x182ea552, + PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 0, 1/*broken*/, + "1998/02/09 06:49:00", 3144, "HP-Microsoft sRGB v2 perceptual") + + PNG_ICC_CHECKSUM(0x0398f3fc, 0xf29e526d, + PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 1, 1/*broken*/, + "1998/02/09 06:49:00", 3144, "HP-Microsoft sRGB v2 media-relative") +}; + +static int +png_compare_ICC_profile_with_sRGB(png_const_structrp png_ptr, + png_const_bytep profile, uLong adler) +{ + /* The quick check is to verify just the MD5 signature and trust the + * rest of the data. Because the profile has already been verified for + * correctness this is safe. png_colorspace_set_sRGB will check the 'intent' + * field too, so if the profile has been edited with an intent not defined + * by sRGB (but maybe defined by a later ICC specification) the read of + * the profile will fail at that point. + */ + + png_uint_32 length = 0; + png_uint_32 intent = 0x10000; /* invalid */ +#if PNG_sRGB_PROFILE_CHECKS > 1 + uLong crc = 0; /* the value for 0 length data */ +#endif + unsigned int i; + +#ifdef PNG_SET_OPTION_SUPPORTED + /* First see if PNG_SKIP_sRGB_CHECK_PROFILE has been set to "on" */ + if (((png_ptr->options >> PNG_SKIP_sRGB_CHECK_PROFILE) & 3) == + PNG_OPTION_ON) + return 0; +#endif + + for (i=0; i < (sizeof png_sRGB_checks) / (sizeof png_sRGB_checks[0]); ++i) + { + if (png_get_uint_32(profile+84) == png_sRGB_checks[i].md5[0] && + png_get_uint_32(profile+88) == png_sRGB_checks[i].md5[1] && + png_get_uint_32(profile+92) == png_sRGB_checks[i].md5[2] && + png_get_uint_32(profile+96) == png_sRGB_checks[i].md5[3]) + { + /* This may be one of the old HP profiles without an MD5, in that + * case we can only use the length and Adler32 (note that these + * are not used by default if there is an MD5!) + */ +# if PNG_sRGB_PROFILE_CHECKS == 0 + if (png_sRGB_checks[i].have_md5 != 0) + return 1+png_sRGB_checks[i].is_broken; +# endif + + /* Profile is unsigned or more checks have been configured in. */ + if (length == 0) + { + length = png_get_uint_32(profile); + intent = png_get_uint_32(profile+64); + } + + /* Length *and* intent must match */ + if (length == (png_uint_32) png_sRGB_checks[i].length && + intent == (png_uint_32) png_sRGB_checks[i].intent) + { + /* Now calculate the adler32 if not done already. */ + if (adler == 0) + { + adler = adler32(0, NULL, 0); + adler = adler32(adler, profile, length); + } + + if (adler == png_sRGB_checks[i].adler) + { + /* These basic checks suggest that the data has not been + * modified, but if the check level is more than 1 perform + * our own crc32 checksum on the data. + */ +# if PNG_sRGB_PROFILE_CHECKS > 1 + if (crc == 0) + { + crc = crc32(0, NULL, 0); + crc = crc32(crc, profile, length); + } + + /* So this check must pass for the 'return' below to happen. + */ + if (crc == png_sRGB_checks[i].crc) +# endif + { + if (png_sRGB_checks[i].is_broken != 0) + { + /* These profiles are known to have bad data that may cause + * problems if they are used, therefore attempt to + * discourage their use, skip the 'have_md5' warning below, + * which is made irrelevant by this error. + */ + png_chunk_report(png_ptr, "known incorrect sRGB profile", + PNG_CHUNK_ERROR); + } + + /* Warn that this being done; this isn't even an error since + * the profile is perfectly valid, but it would be nice if + * people used the up-to-date ones. + */ + else if (png_sRGB_checks[i].have_md5 == 0) + { + png_chunk_report(png_ptr, + "out-of-date sRGB profile with no signature", + PNG_CHUNK_WARNING); + } + + return 1+png_sRGB_checks[i].is_broken; + } + } + +# if PNG_sRGB_PROFILE_CHECKS > 0 + /* The signature matched, but the profile had been changed in some + * way. This probably indicates a data error or uninformed hacking. + * Fall through to "no match". + */ + png_chunk_report(png_ptr, + "Not recognizing known sRGB profile that has been edited", + PNG_CHUNK_WARNING); + break; +# endif + } + } + } + + return 0; /* no match */ +} + +void /* PRIVATE */ +png_icc_set_sRGB(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_bytep profile, uLong adler) +{ + /* Is this profile one of the known ICC sRGB profiles? If it is, just set + * the sRGB information. + */ + if (png_compare_ICC_profile_with_sRGB(png_ptr, profile, adler) != 0) + (void)png_colorspace_set_sRGB(png_ptr, colorspace, + (int)/*already checked*/png_get_uint_32(profile+64)); +} +#endif /* PNG_sRGB_PROFILE_CHECKS >= 0 */ +#endif /* sRGB */ + +int /* PRIVATE */ +png_colorspace_set_ICC(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length, png_const_bytep profile, + int color_type) +{ + if ((colorspace->flags & PNG_COLORSPACE_INVALID) != 0) + return 0; + + if (icc_check_length(png_ptr, colorspace, name, profile_length) != 0 && + png_icc_check_header(png_ptr, colorspace, name, profile_length, profile, + color_type) != 0 && + png_icc_check_tag_table(png_ptr, colorspace, name, profile_length, + profile) != 0) + { +# if defined(PNG_sRGB_SUPPORTED) && PNG_sRGB_PROFILE_CHECKS >= 0 + /* If no sRGB support, don't try storing sRGB information */ + png_icc_set_sRGB(png_ptr, colorspace, profile, 0); +# endif + return 1; + } + + /* Failure case */ + return 0; +} +#endif /* iCCP */ + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +void /* PRIVATE */ +png_colorspace_set_rgb_coefficients(png_structrp png_ptr) +{ + /* Set the rgb_to_gray coefficients from the colorspace. */ + if (png_ptr->rgb_to_gray_coefficients_set == 0 && + (png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + { + /* png_set_background has not been called, get the coefficients from the Y + * values of the colorspace colorants. + */ + png_fixed_point r = png_ptr->colorspace.end_points_XYZ.red_Y; + png_fixed_point g = png_ptr->colorspace.end_points_XYZ.green_Y; + png_fixed_point b = png_ptr->colorspace.end_points_XYZ.blue_Y; + png_fixed_point total = r+g+b; + + if (total > 0 && + r >= 0 && png_muldiv(&r, r, 32768, total) && r >= 0 && r <= 32768 && + g >= 0 && png_muldiv(&g, g, 32768, total) && g >= 0 && g <= 32768 && + b >= 0 && png_muldiv(&b, b, 32768, total) && b >= 0 && b <= 32768 && + r+g+b <= 32769) + { + /* We allow 0 coefficients here. r+g+b may be 32769 if two or + * all of the coefficients were rounded up. Handle this by + * reducing the *largest* coefficient by 1; this matches the + * approach used for the default coefficients in pngrtran.c + */ + int add = 0; + + if (r+g+b > 32768) + add = -1; + else if (r+g+b < 32768) + add = 1; + + if (add != 0) + { + if (g >= r && g >= b) + g += add; + else if (r >= g && r >= b) + r += add; + else + b += add; + } + + /* Check for an internal error. */ + if (r+g+b != 32768) + png_error(png_ptr, + "internal error handling cHRM coefficients"); + + else + { + png_ptr->rgb_to_gray_red_coeff = (png_uint_16)r; + png_ptr->rgb_to_gray_green_coeff = (png_uint_16)g; + } + } + + /* This is a png_error at present even though it could be ignored - + * it should never happen, but it is important that if it does, the + * bug is fixed. + */ + else + png_error(png_ptr, "internal error handling cHRM->XYZ"); + } +} +#endif /* READ_RGB_TO_GRAY */ + +#endif /* COLORSPACE */ + +#ifdef __GNUC__ +/* This exists solely to work round a warning from GNU C. */ +static int /* PRIVATE */ +png_gt(size_t a, size_t b) +{ + return a > b; +} +#else +# define png_gt(a,b) ((a) > (b)) +#endif + +void /* PRIVATE */ +png_check_IHDR(png_const_structrp png_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_type, int compression_type, + int filter_type) +{ + int error = 0; + + /* Check for width and height valid values */ + if (width == 0) + { + png_warning(png_ptr, "Image width is zero in IHDR"); + error = 1; + } + + if (width > PNG_UINT_31_MAX) + { + png_warning(png_ptr, "Invalid image width in IHDR"); + error = 1; + } + + if (png_gt(((width + 7) & (~7U)), + ((PNG_SIZE_MAX + - 48 /* big_row_buf hack */ + - 1) /* filter byte */ + / 8) /* 8-byte RGBA pixels */ + - 1)) /* extra max_pixel_depth pad */ + { + /* The size of the row must be within the limits of this architecture. + * Because the read code can perform arbitrary transformations the + * maximum size is checked here. Because the code in png_read_start_row + * adds extra space "for safety's sake" in several places a conservative + * limit is used here. + * + * NOTE: it would be far better to check the size that is actually used, + * but the effect in the real world is minor and the changes are more + * extensive, therefore much more dangerous and much more difficult to + * write in a way that avoids compiler warnings. + */ + png_warning(png_ptr, "Image width is too large for this architecture"); + error = 1; + } + +#ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (width > png_ptr->user_width_max) +#else + if (width > PNG_USER_WIDTH_MAX) +#endif + { + png_warning(png_ptr, "Image width exceeds user limit in IHDR"); + error = 1; + } + + if (height == 0) + { + png_warning(png_ptr, "Image height is zero in IHDR"); + error = 1; + } + + if (height > PNG_UINT_31_MAX) + { + png_warning(png_ptr, "Invalid image height in IHDR"); + error = 1; + } + +#ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (height > png_ptr->user_height_max) +#else + if (height > PNG_USER_HEIGHT_MAX) +#endif + { + png_warning(png_ptr, "Image height exceeds user limit in IHDR"); + error = 1; + } + + /* Check other values */ + if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && + bit_depth != 8 && bit_depth != 16) + { + png_warning(png_ptr, "Invalid bit depth in IHDR"); + error = 1; + } + + if (color_type < 0 || color_type == 1 || + color_type == 5 || color_type > 6) + { + png_warning(png_ptr, "Invalid color type in IHDR"); + error = 1; + } + + if (((color_type == PNG_COLOR_TYPE_PALETTE) && bit_depth > 8) || + ((color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA || + color_type == PNG_COLOR_TYPE_RGB_ALPHA) && bit_depth < 8)) + { + png_warning(png_ptr, "Invalid color type/bit depth combination in IHDR"); + error = 1; + } + + if (interlace_type >= PNG_INTERLACE_LAST) + { + png_warning(png_ptr, "Unknown interlace method in IHDR"); + error = 1; + } + + if (compression_type != PNG_COMPRESSION_TYPE_BASE) + { + png_warning(png_ptr, "Unknown compression method in IHDR"); + error = 1; + } + +#ifdef PNG_MNG_FEATURES_SUPPORTED + /* Accept filter_method 64 (intrapixel differencing) only if + * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and + * 2. Libpng did not read a PNG signature (this filter_method is only + * used in PNG datastreams that are embedded in MNG datastreams) and + * 3. The application called png_permit_mng_features with a mask that + * included PNG_FLAG_MNG_FILTER_64 and + * 4. The filter_method is 64 and + * 5. The color_type is RGB or RGBA + */ + if ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) != 0 && + png_ptr->mng_features_permitted != 0) + png_warning(png_ptr, "MNG features are not allowed in a PNG datastream"); + + if (filter_type != PNG_FILTER_TYPE_BASE) + { + if (!((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) != 0 && + (filter_type == PNG_INTRAPIXEL_DIFFERENCING) && + ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) == 0) && + (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_RGB_ALPHA))) + { + png_warning(png_ptr, "Unknown filter method in IHDR"); + error = 1; + } + + if ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) != 0) + { + png_warning(png_ptr, "Invalid filter method in IHDR"); + error = 1; + } + } + +#else + if (filter_type != PNG_FILTER_TYPE_BASE) + { + png_warning(png_ptr, "Unknown filter method in IHDR"); + error = 1; + } +#endif + + if (error == 1) + png_error(png_ptr, "Invalid IHDR data"); +} + +#if defined(PNG_sCAL_SUPPORTED) || defined(PNG_pCAL_SUPPORTED) +/* ASCII to fp functions */ +/* Check an ASCII formatted floating point value, see the more detailed + * comments in pngpriv.h + */ +/* The following is used internally to preserve the sticky flags */ +#define png_fp_add(state, flags) ((state) |= (flags)) +#define png_fp_set(state, value) ((state) = (value) | ((state) & PNG_FP_STICKY)) + +int /* PRIVATE */ +png_check_fp_number(png_const_charp string, size_t size, int *statep, + png_size_tp whereami) +{ + int state = *statep; + size_t i = *whereami; + + while (i < size) + { + int type; + /* First find the type of the next character */ + switch (string[i]) + { + case 43: type = PNG_FP_SAW_SIGN; break; + case 45: type = PNG_FP_SAW_SIGN + PNG_FP_NEGATIVE; break; + case 46: type = PNG_FP_SAW_DOT; break; + case 48: type = PNG_FP_SAW_DIGIT; break; + case 49: case 50: case 51: case 52: + case 53: case 54: case 55: case 56: + case 57: type = PNG_FP_SAW_DIGIT + PNG_FP_NONZERO; break; + case 69: + case 101: type = PNG_FP_SAW_E; break; + default: goto PNG_FP_End; + } + + /* Now deal with this type according to the current + * state, the type is arranged to not overlap the + * bits of the PNG_FP_STATE. + */ + switch ((state & PNG_FP_STATE) + (type & PNG_FP_SAW_ANY)) + { + case PNG_FP_INTEGER + PNG_FP_SAW_SIGN: + if ((state & PNG_FP_SAW_ANY) != 0) + goto PNG_FP_End; /* not a part of the number */ + + png_fp_add(state, type); + break; + + case PNG_FP_INTEGER + PNG_FP_SAW_DOT: + /* Ok as trailer, ok as lead of fraction. */ + if ((state & PNG_FP_SAW_DOT) != 0) /* two dots */ + goto PNG_FP_End; + + else if ((state & PNG_FP_SAW_DIGIT) != 0) /* trailing dot? */ + png_fp_add(state, type); + + else + png_fp_set(state, PNG_FP_FRACTION | type); + + break; + + case PNG_FP_INTEGER + PNG_FP_SAW_DIGIT: + if ((state & PNG_FP_SAW_DOT) != 0) /* delayed fraction */ + png_fp_set(state, PNG_FP_FRACTION | PNG_FP_SAW_DOT); + + png_fp_add(state, type | PNG_FP_WAS_VALID); + + break; + + case PNG_FP_INTEGER + PNG_FP_SAW_E: + if ((state & PNG_FP_SAW_DIGIT) == 0) + goto PNG_FP_End; + + png_fp_set(state, PNG_FP_EXPONENT); + + break; + + /* case PNG_FP_FRACTION + PNG_FP_SAW_SIGN: + goto PNG_FP_End; ** no sign in fraction */ + + /* case PNG_FP_FRACTION + PNG_FP_SAW_DOT: + goto PNG_FP_End; ** Because SAW_DOT is always set */ + + case PNG_FP_FRACTION + PNG_FP_SAW_DIGIT: + png_fp_add(state, type | PNG_FP_WAS_VALID); + break; + + case PNG_FP_FRACTION + PNG_FP_SAW_E: + /* This is correct because the trailing '.' on an + * integer is handled above - so we can only get here + * with the sequence ".E" (with no preceding digits). + */ + if ((state & PNG_FP_SAW_DIGIT) == 0) + goto PNG_FP_End; + + png_fp_set(state, PNG_FP_EXPONENT); + + break; + + case PNG_FP_EXPONENT + PNG_FP_SAW_SIGN: + if ((state & PNG_FP_SAW_ANY) != 0) + goto PNG_FP_End; /* not a part of the number */ + + png_fp_add(state, PNG_FP_SAW_SIGN); + + break; + + /* case PNG_FP_EXPONENT + PNG_FP_SAW_DOT: + goto PNG_FP_End; */ + + case PNG_FP_EXPONENT + PNG_FP_SAW_DIGIT: + png_fp_add(state, PNG_FP_SAW_DIGIT | PNG_FP_WAS_VALID); + + break; + + /* case PNG_FP_EXPONEXT + PNG_FP_SAW_E: + goto PNG_FP_End; */ + + default: goto PNG_FP_End; /* I.e. break 2 */ + } + + /* The character seems ok, continue. */ + ++i; + } + +PNG_FP_End: + /* Here at the end, update the state and return the correct + * return code. + */ + *statep = state; + *whereami = i; + + return (state & PNG_FP_SAW_DIGIT) != 0; +} + + +/* The same but for a complete string. */ +int +png_check_fp_string(png_const_charp string, size_t size) +{ + int state=0; + size_t char_index=0; + + if (png_check_fp_number(string, size, &state, &char_index) != 0 && + (char_index == size || string[char_index] == 0)) + return state /* must be non-zero - see above */; + + return 0; /* i.e. fail */ +} +#endif /* pCAL || sCAL */ + +#ifdef PNG_sCAL_SUPPORTED +# ifdef PNG_FLOATING_POINT_SUPPORTED +/* Utility used below - a simple accurate power of ten from an integral + * exponent. + */ +static double +png_pow10(int power) +{ + int recip = 0; + double d = 1; + + /* Handle negative exponent with a reciprocal at the end because + * 10 is exact whereas .1 is inexact in base 2 + */ + if (power < 0) + { + if (power < DBL_MIN_10_EXP) return 0; + recip = 1; power = -power; + } + + if (power > 0) + { + /* Decompose power bitwise. */ + double mult = 10; + do + { + if (power & 1) d *= mult; + mult *= mult; + power >>= 1; + } + while (power > 0); + + if (recip != 0) d = 1/d; + } + /* else power is 0 and d is 1 */ + + return d; +} + +/* Function to format a floating point value in ASCII with a given + * precision. + */ +#if GCC_STRICT_OVERFLOW +#pragma GCC diagnostic push +/* The problem arises below with exp_b10, which can never overflow because it + * comes, originally, from frexp and is therefore limited to a range which is + * typically +/-710 (log2(DBL_MAX)/log2(DBL_MIN)). + */ +#pragma GCC diagnostic warning "-Wstrict-overflow=2" +#endif /* GCC_STRICT_OVERFLOW */ +void /* PRIVATE */ +png_ascii_from_fp(png_const_structrp png_ptr, png_charp ascii, size_t size, + double fp, unsigned int precision) +{ + /* We use standard functions from math.h, but not printf because + * that would require stdio. The caller must supply a buffer of + * sufficient size or we will png_error. The tests on size and + * the space in ascii[] consumed are indicated below. + */ + if (precision < 1) + precision = DBL_DIG; + + /* Enforce the limit of the implementation precision too. */ + if (precision > DBL_DIG+1) + precision = DBL_DIG+1; + + /* Basic sanity checks */ + if (size >= precision+5) /* See the requirements below. */ + { + if (fp < 0) + { + fp = -fp; + *ascii++ = 45; /* '-' PLUS 1 TOTAL 1 */ + --size; + } + + if (fp >= DBL_MIN && fp <= DBL_MAX) + { + int exp_b10; /* A base 10 exponent */ + double base; /* 10^exp_b10 */ + + /* First extract a base 10 exponent of the number, + * the calculation below rounds down when converting + * from base 2 to base 10 (multiply by log10(2) - + * 0.3010, but 77/256 is 0.3008, so exp_b10 needs to + * be increased. Note that the arithmetic shift + * performs a floor() unlike C arithmetic - using a + * C multiply would break the following for negative + * exponents. + */ + (void)frexp(fp, &exp_b10); /* exponent to base 2 */ + + exp_b10 = (exp_b10 * 77) >> 8; /* <= exponent to base 10 */ + + /* Avoid underflow here. */ + base = png_pow10(exp_b10); /* May underflow */ + + while (base < DBL_MIN || base < fp) + { + /* And this may overflow. */ + double test = png_pow10(exp_b10+1); + + if (test <= DBL_MAX) + { + ++exp_b10; base = test; + } + + else + break; + } + + /* Normalize fp and correct exp_b10, after this fp is in the + * range [.1,1) and exp_b10 is both the exponent and the digit + * *before* which the decimal point should be inserted + * (starting with 0 for the first digit). Note that this + * works even if 10^exp_b10 is out of range because of the + * test on DBL_MAX above. + */ + fp /= base; + while (fp >= 1) + { + fp /= 10; ++exp_b10; + } + + /* Because of the code above fp may, at this point, be + * less than .1, this is ok because the code below can + * handle the leading zeros this generates, so no attempt + * is made to correct that here. + */ + + { + unsigned int czero, clead, cdigits; + char exponent[10]; + + /* Allow up to two leading zeros - this will not lengthen + * the number compared to using E-n. + */ + if (exp_b10 < 0 && exp_b10 > -3) /* PLUS 3 TOTAL 4 */ + { + czero = 0U-exp_b10; /* PLUS 2 digits: TOTAL 3 */ + exp_b10 = 0; /* Dot added below before first output. */ + } + else + czero = 0; /* No zeros to add */ + + /* Generate the digit list, stripping trailing zeros and + * inserting a '.' before a digit if the exponent is 0. + */ + clead = czero; /* Count of leading zeros */ + cdigits = 0; /* Count of digits in list. */ + + do + { + double d; + + fp *= 10; + /* Use modf here, not floor and subtract, so that + * the separation is done in one step. At the end + * of the loop don't break the number into parts so + * that the final digit is rounded. + */ + if (cdigits+czero+1 < precision+clead) + fp = modf(fp, &d); + + else + { + d = floor(fp + .5); + + if (d > 9) + { + /* Rounding up to 10, handle that here. */ + if (czero > 0) + { + --czero; d = 1; + if (cdigits == 0) --clead; + } + else + { + while (cdigits > 0 && d > 9) + { + int ch = *--ascii; + + if (exp_b10 != (-1)) + ++exp_b10; + + else if (ch == 46) + { + ch = *--ascii; ++size; + /* Advance exp_b10 to '1', so that the + * decimal point happens after the + * previous digit. + */ + exp_b10 = 1; + } + + --cdigits; + d = ch - 47; /* I.e. 1+(ch-48) */ + } + + /* Did we reach the beginning? If so adjust the + * exponent but take into account the leading + * decimal point. + */ + if (d > 9) /* cdigits == 0 */ + { + if (exp_b10 == (-1)) + { + /* Leading decimal point (plus zeros?), if + * we lose the decimal point here it must + * be reentered below. + */ + int ch = *--ascii; + + if (ch == 46) + { + ++size; exp_b10 = 1; + } + + /* Else lost a leading zero, so 'exp_b10' is + * still ok at (-1) + */ + } + else + ++exp_b10; + + /* In all cases we output a '1' */ + d = 1; + } + } + } + fp = 0; /* Guarantees termination below. */ + } + + if (d == 0) + { + ++czero; + if (cdigits == 0) ++clead; + } + else + { + /* Included embedded zeros in the digit count. */ + cdigits += czero - clead; + clead = 0; + + while (czero > 0) + { + /* exp_b10 == (-1) means we just output the decimal + * place - after the DP don't adjust 'exp_b10' any + * more! + */ + if (exp_b10 != (-1)) + { + if (exp_b10 == 0) + { + *ascii++ = 46; --size; + } + /* PLUS 1: TOTAL 4 */ + --exp_b10; + } + *ascii++ = 48; --czero; + } + + if (exp_b10 != (-1)) + { + if (exp_b10 == 0) + { + *ascii++ = 46; --size; /* counted above */ + } + + --exp_b10; + } + *ascii++ = (char)(48 + (int)d); ++cdigits; + } + } + while (cdigits+czero < precision+clead && fp > DBL_MIN); + + /* The total output count (max) is now 4+precision */ + + /* Check for an exponent, if we don't need one we are + * done and just need to terminate the string. At this + * point, exp_b10==(-1) is effectively a flag: it got + * to '-1' because of the decrement, after outputting + * the decimal point above. (The exponent required is + * *not* -1.) + */ + if (exp_b10 >= (-1) && exp_b10 <= 2) + { + /* The following only happens if we didn't output the + * leading zeros above for negative exponent, so this + * doesn't add to the digit requirement. Note that the + * two zeros here can only be output if the two leading + * zeros were *not* output, so this doesn't increase + * the output count. + */ + while (exp_b10-- > 0) *ascii++ = 48; + + *ascii = 0; + + /* Total buffer requirement (including the '\0') is + * 5+precision - see check at the start. + */ + return; + } + + /* Here if an exponent is required, adjust size for + * the digits we output but did not count. The total + * digit output here so far is at most 1+precision - no + * decimal point and no leading or trailing zeros have + * been output. + */ + size -= cdigits; + + *ascii++ = 69; --size; /* 'E': PLUS 1 TOTAL 2+precision */ + + /* The following use of an unsigned temporary avoids ambiguities in + * the signed arithmetic on exp_b10 and permits GCC at least to do + * better optimization. + */ + { + unsigned int uexp_b10; + + if (exp_b10 < 0) + { + *ascii++ = 45; --size; /* '-': PLUS 1 TOTAL 3+precision */ + uexp_b10 = 0U-exp_b10; + } + + else + uexp_b10 = 0U+exp_b10; + + cdigits = 0; + + while (uexp_b10 > 0) + { + exponent[cdigits++] = (char)(48 + uexp_b10 % 10); + uexp_b10 /= 10; + } + } + + /* Need another size check here for the exponent digits, so + * this need not be considered above. + */ + if (size > cdigits) + { + while (cdigits > 0) *ascii++ = exponent[--cdigits]; + + *ascii = 0; + + return; + } + } + } + else if (!(fp >= DBL_MIN)) + { + *ascii++ = 48; /* '0' */ + *ascii = 0; + return; + } + else + { + *ascii++ = 105; /* 'i' */ + *ascii++ = 110; /* 'n' */ + *ascii++ = 102; /* 'f' */ + *ascii = 0; + return; + } + } + + /* Here on buffer too small. */ + png_error(png_ptr, "ASCII conversion buffer too small"); +} +#if GCC_STRICT_OVERFLOW +#pragma GCC diagnostic pop +#endif /* GCC_STRICT_OVERFLOW */ + +# endif /* FLOATING_POINT */ + +# ifdef PNG_FIXED_POINT_SUPPORTED +/* Function to format a fixed point value in ASCII. + */ +void /* PRIVATE */ +png_ascii_from_fixed(png_const_structrp png_ptr, png_charp ascii, + size_t size, png_fixed_point fp) +{ + /* Require space for 10 decimal digits, a decimal point, a minus sign and a + * trailing \0, 13 characters: + */ + if (size > 12) + { + png_uint_32 num; + + /* Avoid overflow here on the minimum integer. */ + if (fp < 0) + { + *ascii++ = 45; num = (png_uint_32)(-fp); + } + else + num = (png_uint_32)fp; + + if (num <= 0x80000000) /* else overflowed */ + { + unsigned int ndigits = 0, first = 16 /* flag value */; + char digits[10]; + + while (num) + { + /* Split the low digit off num: */ + unsigned int tmp = num/10; + num -= tmp*10; + digits[ndigits++] = (char)(48 + num); + /* Record the first non-zero digit, note that this is a number + * starting at 1, it's not actually the array index. + */ + if (first == 16 && num > 0) + first = ndigits; + num = tmp; + } + + if (ndigits > 0) + { + while (ndigits > 5) *ascii++ = digits[--ndigits]; + /* The remaining digits are fractional digits, ndigits is '5' or + * smaller at this point. It is certainly not zero. Check for a + * non-zero fractional digit: + */ + if (first <= 5) + { + unsigned int i; + *ascii++ = 46; /* decimal point */ + /* ndigits may be <5 for small numbers, output leading zeros + * then ndigits digits to first: + */ + i = 5; + while (ndigits < i) + { + *ascii++ = 48; --i; + } + while (ndigits >= first) *ascii++ = digits[--ndigits]; + /* Don't output the trailing zeros! */ + } + } + else + *ascii++ = 48; + + /* And null terminate the string: */ + *ascii = 0; + return; + } + } + + /* Here on buffer too small. */ + png_error(png_ptr, "ASCII conversion buffer too small"); +} +# endif /* FIXED_POINT */ +#endif /* SCAL */ + +#if defined(PNG_FLOATING_POINT_SUPPORTED) && \ + !defined(PNG_FIXED_POINT_MACRO_SUPPORTED) && \ + (defined(PNG_gAMA_SUPPORTED) || defined(PNG_cHRM_SUPPORTED) || \ + defined(PNG_sCAL_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED)) || \ + (defined(PNG_sCAL_SUPPORTED) && \ + defined(PNG_FLOATING_ARITHMETIC_SUPPORTED)) +png_fixed_point +png_fixed(png_const_structrp png_ptr, double fp, png_const_charp text) +{ + double r = floor(100000 * fp + .5); + + if (r > 2147483647. || r < -2147483648.) + png_fixed_error(png_ptr, text); + +# ifndef PNG_ERROR_TEXT_SUPPORTED + PNG_UNUSED(text) +# endif + + return (png_fixed_point)r; +} +#endif + +#if defined(PNG_GAMMA_SUPPORTED) || defined(PNG_COLORSPACE_SUPPORTED) ||\ + defined(PNG_INCH_CONVERSIONS_SUPPORTED) || defined(PNG_READ_pHYs_SUPPORTED) +/* muldiv functions */ +/* This API takes signed arguments and rounds the result to the nearest + * integer (or, for a fixed point number - the standard argument - to + * the nearest .00001). Overflow and divide by zero are signalled in + * the result, a boolean - true on success, false on overflow. + */ +#if GCC_STRICT_OVERFLOW /* from above */ +/* It is not obvious which comparison below gets optimized in such a way that + * signed overflow would change the result; looking through the code does not + * reveal any tests which have the form GCC complains about, so presumably the + * optimizer is moving an add or subtract into the 'if' somewhere. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic warning "-Wstrict-overflow=2" +#endif /* GCC_STRICT_OVERFLOW */ +int +png_muldiv(png_fixed_point_p res, png_fixed_point a, png_int_32 times, + png_int_32 divisor) +{ + /* Return a * times / divisor, rounded. */ + if (divisor != 0) + { + if (a == 0 || times == 0) + { + *res = 0; + return 1; + } + else + { +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = a; + r *= times; + r /= divisor; + r = floor(r+.5); + + /* A png_fixed_point is a 32-bit integer. */ + if (r <= 2147483647. && r >= -2147483648.) + { + *res = (png_fixed_point)r; + return 1; + } +#else + int negative = 0; + png_uint_32 A, T, D; + png_uint_32 s16, s32, s00; + + if (a < 0) + negative = 1, A = -a; + else + A = a; + + if (times < 0) + negative = !negative, T = -times; + else + T = times; + + if (divisor < 0) + negative = !negative, D = -divisor; + else + D = divisor; + + /* Following can't overflow because the arguments only + * have 31 bits each, however the result may be 32 bits. + */ + s16 = (A >> 16) * (T & 0xffff) + + (A & 0xffff) * (T >> 16); + /* Can't overflow because the a*times bit is only 30 + * bits at most. + */ + s32 = (A >> 16) * (T >> 16) + (s16 >> 16); + s00 = (A & 0xffff) * (T & 0xffff); + + s16 = (s16 & 0xffff) << 16; + s00 += s16; + + if (s00 < s16) + ++s32; /* carry */ + + if (s32 < D) /* else overflow */ + { + /* s32.s00 is now the 64-bit product, do a standard + * division, we know that s32 < D, so the maximum + * required shift is 31. + */ + int bitshift = 32; + png_fixed_point result = 0; /* NOTE: signed */ + + while (--bitshift >= 0) + { + png_uint_32 d32, d00; + + if (bitshift > 0) + d32 = D >> (32-bitshift), d00 = D << bitshift; + + else + d32 = 0, d00 = D; + + if (s32 > d32) + { + if (s00 < d00) --s32; /* carry */ + s32 -= d32, s00 -= d00, result += 1<= d00) + s32 = 0, s00 -= d00, result += 1<= (D >> 1)) + ++result; + + if (negative != 0) + result = -result; + + /* Check for overflow. */ + if ((negative != 0 && result <= 0) || + (negative == 0 && result >= 0)) + { + *res = result; + return 1; + } + } +#endif + } + } + + return 0; +} +#if GCC_STRICT_OVERFLOW +#pragma GCC diagnostic pop +#endif /* GCC_STRICT_OVERFLOW */ +#endif /* READ_GAMMA || INCH_CONVERSIONS */ + +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_INCH_CONVERSIONS_SUPPORTED) +/* The following is for when the caller doesn't much care about the + * result. + */ +png_fixed_point +png_muldiv_warn(png_const_structrp png_ptr, png_fixed_point a, png_int_32 times, + png_int_32 divisor) +{ + png_fixed_point result; + + if (png_muldiv(&result, a, times, divisor) != 0) + return result; + + png_warning(png_ptr, "fixed point overflow ignored"); + return 0; +} +#endif + +#ifdef PNG_GAMMA_SUPPORTED /* more fixed point functions for gamma */ +/* Calculate a reciprocal, return 0 on div-by-zero or overflow. */ +png_fixed_point +png_reciprocal(png_fixed_point a) +{ +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = floor(1E10/a+.5); + + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; +#else + png_fixed_point res; + + if (png_muldiv(&res, 100000, 100000, a) != 0) + return res; +#endif + + return 0; /* error/overflow */ +} + +/* This is the shared test on whether a gamma value is 'significant' - whether + * it is worth doing gamma correction. + */ +int /* PRIVATE */ +png_gamma_significant(png_fixed_point gamma_val) +{ + return gamma_val < PNG_FP_1 - PNG_GAMMA_THRESHOLD_FIXED || + gamma_val > PNG_FP_1 + PNG_GAMMA_THRESHOLD_FIXED; +} +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +#ifdef PNG_16BIT_SUPPORTED +/* A local convenience routine. */ +static png_fixed_point +png_product2(png_fixed_point a, png_fixed_point b) +{ + /* The required result is 1/a * 1/b; the following preserves accuracy. */ +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = a * 1E-5; + r *= b; + r = floor(r+.5); + + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; +#else + png_fixed_point res; + + if (png_muldiv(&res, a, b, 100000) != 0) + return res; +#endif + + return 0; /* overflow */ +} +#endif /* 16BIT */ + +/* The inverse of the above. */ +png_fixed_point +png_reciprocal2(png_fixed_point a, png_fixed_point b) +{ + /* The required result is 1/a * 1/b; the following preserves accuracy. */ +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + if (a != 0 && b != 0) + { + double r = 1E15/a; + r /= b; + r = floor(r+.5); + + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; + } +#else + /* This may overflow because the range of png_fixed_point isn't symmetric, + * but this API is only used for the product of file and screen gamma so it + * doesn't matter that the smallest number it can produce is 1/21474, not + * 1/100000 + */ + png_fixed_point res = png_product2(a, b); + + if (res != 0) + return png_reciprocal(res); +#endif + + return 0; /* overflow */ +} +#endif /* READ_GAMMA */ + +#ifdef PNG_READ_GAMMA_SUPPORTED /* gamma table code */ +#ifndef PNG_FLOATING_ARITHMETIC_SUPPORTED +/* Fixed point gamma. + * + * The code to calculate the tables used below can be found in the shell script + * contrib/tools/intgamma.sh + * + * To calculate gamma this code implements fast log() and exp() calls using only + * fixed point arithmetic. This code has sufficient precision for either 8-bit + * or 16-bit sample values. + * + * The tables used here were calculated using simple 'bc' programs, but C double + * precision floating point arithmetic would work fine. + * + * 8-bit log table + * This is a table of -log(value/255)/log(2) for 'value' in the range 128 to + * 255, so it's the base 2 logarithm of a normalized 8-bit floating point + * mantissa. The numbers are 32-bit fractions. + */ +static const png_uint_32 +png_8bit_l2[128] = +{ + 4270715492U, 4222494797U, 4174646467U, 4127164793U, 4080044201U, 4033279239U, + 3986864580U, 3940795015U, 3895065449U, 3849670902U, 3804606499U, 3759867474U, + 3715449162U, 3671346997U, 3627556511U, 3584073329U, 3540893168U, 3498011834U, + 3455425220U, 3413129301U, 3371120137U, 3329393864U, 3287946700U, 3246774933U, + 3205874930U, 3165243125U, 3124876025U, 3084770202U, 3044922296U, 3005329011U, + 2965987113U, 2926893432U, 2888044853U, 2849438323U, 2811070844U, 2772939474U, + 2735041326U, 2697373562U, 2659933400U, 2622718104U, 2585724991U, 2548951424U, + 2512394810U, 2476052606U, 2439922311U, 2404001468U, 2368287663U, 2332778523U, + 2297471715U, 2262364947U, 2227455964U, 2192742551U, 2158222529U, 2123893754U, + 2089754119U, 2055801552U, 2022034013U, 1988449497U, 1955046031U, 1921821672U, + 1888774511U, 1855902668U, 1823204291U, 1790677560U, 1758320682U, 1726131893U, + 1694109454U, 1662251657U, 1630556815U, 1599023271U, 1567649391U, 1536433567U, + 1505374214U, 1474469770U, 1443718700U, 1413119487U, 1382670639U, 1352370686U, + 1322218179U, 1292211689U, 1262349810U, 1232631153U, 1203054352U, 1173618059U, + 1144320946U, 1115161701U, 1086139034U, 1057251672U, 1028498358U, 999877854U, + 971388940U, 943030410U, 914801076U, 886699767U, 858725327U, 830876614U, + 803152505U, 775551890U, 748073672U, 720716771U, 693480120U, 666362667U, + 639363374U, 612481215U, 585715177U, 559064263U, 532527486U, 506103872U, + 479792461U, 453592303U, 427502463U, 401522014U, 375650043U, 349885648U, + 324227938U, 298676034U, 273229066U, 247886176U, 222646516U, 197509248U, + 172473545U, 147538590U, 122703574U, 97967701U, 73330182U, 48790236U, + 24347096U, 0U + +#if 0 + /* The following are the values for 16-bit tables - these work fine for the + * 8-bit conversions but produce very slightly larger errors in the 16-bit + * log (about 1.2 as opposed to 0.7 absolute error in the final value). To + * use these all the shifts below must be adjusted appropriately. + */ + 65166, 64430, 63700, 62976, 62257, 61543, 60835, 60132, 59434, 58741, 58054, + 57371, 56693, 56020, 55352, 54689, 54030, 53375, 52726, 52080, 51439, 50803, + 50170, 49542, 48918, 48298, 47682, 47070, 46462, 45858, 45257, 44661, 44068, + 43479, 42894, 42312, 41733, 41159, 40587, 40020, 39455, 38894, 38336, 37782, + 37230, 36682, 36137, 35595, 35057, 34521, 33988, 33459, 32932, 32408, 31887, + 31369, 30854, 30341, 29832, 29325, 28820, 28319, 27820, 27324, 26830, 26339, + 25850, 25364, 24880, 24399, 23920, 23444, 22970, 22499, 22029, 21562, 21098, + 20636, 20175, 19718, 19262, 18808, 18357, 17908, 17461, 17016, 16573, 16132, + 15694, 15257, 14822, 14390, 13959, 13530, 13103, 12678, 12255, 11834, 11415, + 10997, 10582, 10168, 9756, 9346, 8937, 8531, 8126, 7723, 7321, 6921, 6523, + 6127, 5732, 5339, 4947, 4557, 4169, 3782, 3397, 3014, 2632, 2251, 1872, 1495, + 1119, 744, 372 +#endif +}; + +static png_int_32 +png_log8bit(unsigned int x) +{ + unsigned int lg2 = 0; + /* Each time 'x' is multiplied by 2, 1 must be subtracted off the final log, + * because the log is actually negate that means adding 1. The final + * returned value thus has the range 0 (for 255 input) to 7.994 (for 1 + * input), return -1 for the overflow (log 0) case, - so the result is + * always at most 19 bits. + */ + if ((x &= 0xff) == 0) + return -1; + + if ((x & 0xf0) == 0) + lg2 = 4, x <<= 4; + + if ((x & 0xc0) == 0) + lg2 += 2, x <<= 2; + + if ((x & 0x80) == 0) + lg2 += 1, x <<= 1; + + /* result is at most 19 bits, so this cast is safe: */ + return (png_int_32)((lg2 << 16) + ((png_8bit_l2[x-128]+32768)>>16)); +} + +/* The above gives exact (to 16 binary places) log2 values for 8-bit images, + * for 16-bit images we use the most significant 8 bits of the 16-bit value to + * get an approximation then multiply the approximation by a correction factor + * determined by the remaining up to 8 bits. This requires an additional step + * in the 16-bit case. + * + * We want log2(value/65535), we have log2(v'/255), where: + * + * value = v' * 256 + v'' + * = v' * f + * + * So f is value/v', which is equal to (256+v''/v') since v' is in the range 128 + * to 255 and v'' is in the range 0 to 255 f will be in the range 256 to less + * than 258. The final factor also needs to correct for the fact that our 8-bit + * value is scaled by 255, whereas the 16-bit values must be scaled by 65535. + * + * This gives a final formula using a calculated value 'x' which is value/v' and + * scaling by 65536 to match the above table: + * + * log2(x/257) * 65536 + * + * Since these numbers are so close to '1' we can use simple linear + * interpolation between the two end values 256/257 (result -368.61) and 258/257 + * (result 367.179). The values used below are scaled by a further 64 to give + * 16-bit precision in the interpolation: + * + * Start (256): -23591 + * Zero (257): 0 + * End (258): 23499 + */ +#ifdef PNG_16BIT_SUPPORTED +static png_int_32 +png_log16bit(png_uint_32 x) +{ + unsigned int lg2 = 0; + + /* As above, but now the input has 16 bits. */ + if ((x &= 0xffff) == 0) + return -1; + + if ((x & 0xff00) == 0) + lg2 = 8, x <<= 8; + + if ((x & 0xf000) == 0) + lg2 += 4, x <<= 4; + + if ((x & 0xc000) == 0) + lg2 += 2, x <<= 2; + + if ((x & 0x8000) == 0) + lg2 += 1, x <<= 1; + + /* Calculate the base logarithm from the top 8 bits as a 28-bit fractional + * value. + */ + lg2 <<= 28; + lg2 += (png_8bit_l2[(x>>8)-128]+8) >> 4; + + /* Now we need to interpolate the factor, this requires a division by the top + * 8 bits. Do this with maximum precision. + */ + x = ((x << 16) + (x >> 9)) / (x >> 8); + + /* Since we divided by the top 8 bits of 'x' there will be a '1' at 1<<24, + * the value at 1<<16 (ignoring this) will be 0 or 1; this gives us exactly + * 16 bits to interpolate to get the low bits of the result. Round the + * answer. Note that the end point values are scaled by 64 to retain overall + * precision and that 'lg2' is current scaled by an extra 12 bits, so adjust + * the overall scaling by 6-12. Round at every step. + */ + x -= 1U << 24; + + if (x <= 65536U) /* <= '257' */ + lg2 += ((23591U * (65536U-x)) + (1U << (16+6-12-1))) >> (16+6-12); + + else + lg2 -= ((23499U * (x-65536U)) + (1U << (16+6-12-1))) >> (16+6-12); + + /* Safe, because the result can't have more than 20 bits: */ + return (png_int_32)((lg2 + 2048) >> 12); +} +#endif /* 16BIT */ + +/* The 'exp()' case must invert the above, taking a 20-bit fixed point + * logarithmic value and returning a 16 or 8-bit number as appropriate. In + * each case only the low 16 bits are relevant - the fraction - since the + * integer bits (the top 4) simply determine a shift. + * + * The worst case is the 16-bit distinction between 65535 and 65534. This + * requires perhaps spurious accuracy in the decoding of the logarithm to + * distinguish log2(65535/65534.5) - 10^-5 or 17 bits. There is little chance + * of getting this accuracy in practice. + * + * To deal with this the following exp() function works out the exponent of the + * fractional part of the logarithm by using an accurate 32-bit value from the + * top four fractional bits then multiplying in the remaining bits. + */ +static const png_uint_32 +png_32bit_exp[16] = +{ + /* NOTE: the first entry is deliberately set to the maximum 32-bit value. */ + 4294967295U, 4112874773U, 3938502376U, 3771522796U, 3611622603U, 3458501653U, + 3311872529U, 3171459999U, 3037000500U, 2908241642U, 2784941738U, 2666869345U, + 2553802834U, 2445529972U, 2341847524U, 2242560872U +}; + +/* Adjustment table; provided to explain the numbers in the code below. */ +#if 0 +for (i=11;i>=0;--i){ print i, " ", (1 - e(-(2^i)/65536*l(2))) * 2^(32-i), "\n"} + 11 44937.64284865548751208448 + 10 45180.98734845585101160448 + 9 45303.31936980687359311872 + 8 45364.65110595323018870784 + 7 45395.35850361789624614912 + 6 45410.72259715102037508096 + 5 45418.40724413220722311168 + 4 45422.25021786898173001728 + 3 45424.17186732298419044352 + 2 45425.13273269940811464704 + 1 45425.61317555035558641664 + 0 45425.85339951654943850496 +#endif + +static png_uint_32 +png_exp(png_fixed_point x) +{ + if (x > 0 && x <= 0xfffff) /* Else overflow or zero (underflow) */ + { + /* Obtain a 4-bit approximation */ + png_uint_32 e = png_32bit_exp[(x >> 12) & 0x0f]; + + /* Incorporate the low 12 bits - these decrease the returned value by + * multiplying by a number less than 1 if the bit is set. The multiplier + * is determined by the above table and the shift. Notice that the values + * converge on 45426 and this is used to allow linear interpolation of the + * low bits. + */ + if (x & 0x800) + e -= (((e >> 16) * 44938U) + 16U) >> 5; + + if (x & 0x400) + e -= (((e >> 16) * 45181U) + 32U) >> 6; + + if (x & 0x200) + e -= (((e >> 16) * 45303U) + 64U) >> 7; + + if (x & 0x100) + e -= (((e >> 16) * 45365U) + 128U) >> 8; + + if (x & 0x080) + e -= (((e >> 16) * 45395U) + 256U) >> 9; + + if (x & 0x040) + e -= (((e >> 16) * 45410U) + 512U) >> 10; + + /* And handle the low 6 bits in a single block. */ + e -= (((e >> 16) * 355U * (x & 0x3fU)) + 256U) >> 9; + + /* Handle the upper bits of x. */ + e >>= x >> 16; + return e; + } + + /* Check for overflow */ + if (x <= 0) + return png_32bit_exp[0]; + + /* Else underflow */ + return 0; +} + +static png_byte +png_exp8bit(png_fixed_point lg2) +{ + /* Get a 32-bit value: */ + png_uint_32 x = png_exp(lg2); + + /* Convert the 32-bit value to 0..255 by multiplying by 256-1. Note that the + * second, rounding, step can't overflow because of the first, subtraction, + * step. + */ + x -= x >> 8; + return (png_byte)(((x + 0x7fffffU) >> 24) & 0xff); +} + +#ifdef PNG_16BIT_SUPPORTED +static png_uint_16 +png_exp16bit(png_fixed_point lg2) +{ + /* Get a 32-bit value: */ + png_uint_32 x = png_exp(lg2); + + /* Convert the 32-bit value to 0..65535 by multiplying by 65536-1: */ + x -= x >> 16; + return (png_uint_16)((x + 32767U) >> 16); +} +#endif /* 16BIT */ +#endif /* FLOATING_ARITHMETIC */ + +png_byte +png_gamma_8bit_correct(unsigned int value, png_fixed_point gamma_val) +{ + if (value > 0 && value < 255) + { +# ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + /* 'value' is unsigned, ANSI-C90 requires the compiler to correctly + * convert this to a floating point value. This includes values that + * would overflow if 'value' were to be converted to 'int'. + * + * Apparently GCC, however, does an intermediate conversion to (int) + * on some (ARM) but not all (x86) platforms, possibly because of + * hardware FP limitations. (E.g. if the hardware conversion always + * assumes the integer register contains a signed value.) This results + * in ANSI-C undefined behavior for large values. + * + * Other implementations on the same machine might actually be ANSI-C90 + * conformant and therefore compile spurious extra code for the large + * values. + * + * We can be reasonably sure that an unsigned to float conversion + * won't be faster than an int to float one. Therefore this code + * assumes responsibility for the undefined behavior, which it knows + * can't happen because of the check above. + * + * Note the argument to this routine is an (unsigned int) because, on + * 16-bit platforms, it is assigned a value which might be out of + * range for an (int); that would result in undefined behavior in the + * caller if the *argument* ('value') were to be declared (int). + */ + double r = floor(255*pow((int)/*SAFE*/value/255.,gamma_val*.00001)+.5); + return (png_byte)r; +# else + png_int_32 lg2 = png_log8bit(value); + png_fixed_point res; + + if (png_muldiv(&res, gamma_val, lg2, PNG_FP_1) != 0) + return png_exp8bit(res); + + /* Overflow. */ + value = 0; +# endif + } + + return (png_byte)(value & 0xff); +} + +#ifdef PNG_16BIT_SUPPORTED +png_uint_16 +png_gamma_16bit_correct(unsigned int value, png_fixed_point gamma_val) +{ + if (value > 0 && value < 65535) + { +# ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + /* The same (unsigned int)->(double) constraints apply here as above, + * however in this case the (unsigned int) to (int) conversion can + * overflow on an ANSI-C90 compliant system so the cast needs to ensure + * that this is not possible. + */ + double r = floor(65535*pow((png_int_32)value/65535., + gamma_val*.00001)+.5); + return (png_uint_16)r; +# else + png_int_32 lg2 = png_log16bit(value); + png_fixed_point res; + + if (png_muldiv(&res, gamma_val, lg2, PNG_FP_1) != 0) + return png_exp16bit(res); + + /* Overflow. */ + value = 0; +# endif + } + + return (png_uint_16)value; +} +#endif /* 16BIT */ + +/* This does the right thing based on the bit_depth field of the + * png_struct, interpreting values as 8-bit or 16-bit. While the result + * is nominally a 16-bit value if bit depth is 8 then the result is + * 8-bit (as are the arguments.) + */ +png_uint_16 /* PRIVATE */ +png_gamma_correct(png_structrp png_ptr, unsigned int value, + png_fixed_point gamma_val) +{ + if (png_ptr->bit_depth == 8) + return png_gamma_8bit_correct(value, gamma_val); + +#ifdef PNG_16BIT_SUPPORTED + else + return png_gamma_16bit_correct(value, gamma_val); +#else + /* should not reach this */ + return 0; +#endif /* 16BIT */ +} + +#ifdef PNG_16BIT_SUPPORTED +/* Internal function to build a single 16-bit table - the table consists of + * 'num' 256 entry subtables, where 'num' is determined by 'shift' - the amount + * to shift the input values right (or 16-number_of_signifiant_bits). + * + * The caller is responsible for ensuring that the table gets cleaned up on + * png_error (i.e. if one of the mallocs below fails) - i.e. the *table argument + * should be somewhere that will be cleaned. + */ +static void +png_build_16bit_table(png_structrp png_ptr, png_uint_16pp *ptable, + unsigned int shift, png_fixed_point gamma_val) +{ + /* Various values derived from 'shift': */ + unsigned int num = 1U << (8U - shift); +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + /* CSE the division and work round wacky GCC warnings (see the comments + * in png_gamma_8bit_correct for where these come from.) + */ + double fmax = 1.0 / (((png_int_32)1 << (16U - shift)) - 1); +#endif + unsigned int max = (1U << (16U - shift)) - 1U; + unsigned int max_by_2 = 1U << (15U - shift); + unsigned int i; + + png_uint_16pp table = *ptable = + (png_uint_16pp)png_calloc(png_ptr, num * (sizeof (png_uint_16p))); + + for (i = 0; i < num; i++) + { + png_uint_16p sub_table = table[i] = + (png_uint_16p)png_malloc(png_ptr, 256 * (sizeof (png_uint_16))); + + /* The 'threshold' test is repeated here because it can arise for one of + * the 16-bit tables even if the others don't hit it. + */ + if (png_gamma_significant(gamma_val) != 0) + { + /* The old code would overflow at the end and this would cause the + * 'pow' function to return a result >1, resulting in an + * arithmetic error. This code follows the spec exactly; ig is + * the recovered input sample, it always has 8-16 bits. + * + * We want input * 65535/max, rounded, the arithmetic fits in 32 + * bits (unsigned) so long as max <= 32767. + */ + unsigned int j; + for (j = 0; j < 256; j++) + { + png_uint_32 ig = (j << (8-shift)) + i; +# ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + /* Inline the 'max' scaling operation: */ + /* See png_gamma_8bit_correct for why the cast to (int) is + * required here. + */ + double d = floor(65535.*pow(ig*fmax, gamma_val*.00001)+.5); + sub_table[j] = (png_uint_16)d; +# else + if (shift != 0) + ig = (ig * 65535U + max_by_2)/max; + + sub_table[j] = png_gamma_16bit_correct(ig, gamma_val); +# endif + } + } + else + { + /* We must still build a table, but do it the fast way. */ + unsigned int j; + + for (j = 0; j < 256; j++) + { + png_uint_32 ig = (j << (8-shift)) + i; + + if (shift != 0) + ig = (ig * 65535U + max_by_2)/max; + + sub_table[j] = (png_uint_16)ig; + } + } + } +} + +/* NOTE: this function expects the *inverse* of the overall gamma transformation + * required. + */ +static void +png_build_16to8_table(png_structrp png_ptr, png_uint_16pp *ptable, + unsigned int shift, png_fixed_point gamma_val) +{ + unsigned int num = 1U << (8U - shift); + unsigned int max = (1U << (16U - shift))-1U; + unsigned int i; + png_uint_32 last; + + png_uint_16pp table = *ptable = + (png_uint_16pp)png_calloc(png_ptr, num * (sizeof (png_uint_16p))); + + /* 'num' is the number of tables and also the number of low bits of low + * bits of the input 16-bit value used to select a table. Each table is + * itself indexed by the high 8 bits of the value. + */ + for (i = 0; i < num; i++) + table[i] = (png_uint_16p)png_malloc(png_ptr, + 256 * (sizeof (png_uint_16))); + + /* 'gamma_val' is set to the reciprocal of the value calculated above, so + * pow(out,g) is an *input* value. 'last' is the last input value set. + * + * In the loop 'i' is used to find output values. Since the output is + * 8-bit there are only 256 possible values. The tables are set up to + * select the closest possible output value for each input by finding + * the input value at the boundary between each pair of output values + * and filling the table up to that boundary with the lower output + * value. + * + * The boundary values are 0.5,1.5..253.5,254.5. Since these are 9-bit + * values the code below uses a 16-bit value in i; the values start at + * 128.5 (for 0.5) and step by 257, for a total of 254 values (the last + * entries are filled with 255). Start i at 128 and fill all 'last' + * table entries <= 'max' + */ + last = 0; + for (i = 0; i < 255; ++i) /* 8-bit output value */ + { + /* Find the corresponding maximum input value */ + png_uint_16 out = (png_uint_16)(i * 257U); /* 16-bit output value */ + + /* Find the boundary value in 16 bits: */ + png_uint_32 bound = png_gamma_16bit_correct(out+128U, gamma_val); + + /* Adjust (round) to (16-shift) bits: */ + bound = (bound * max + 32768U)/65535U + 1U; + + while (last < bound) + { + table[last & (0xffU >> shift)][last >> (8U - shift)] = out; + last++; + } + } + + /* And fill in the final entries. */ + while (last < (num << 8)) + { + table[last & (0xff >> shift)][last >> (8U - shift)] = 65535U; + last++; + } +} +#endif /* 16BIT */ + +/* Build a single 8-bit table: same as the 16-bit case but much simpler (and + * typically much faster). Note that libpng currently does no sBIT processing + * (apparently contrary to the spec) so a 256-entry table is always generated. + */ +static void +png_build_8bit_table(png_structrp png_ptr, png_bytepp ptable, + png_fixed_point gamma_val) +{ + unsigned int i; + png_bytep table = *ptable = (png_bytep)png_malloc(png_ptr, 256); + + if (png_gamma_significant(gamma_val) != 0) + for (i=0; i<256; i++) + table[i] = png_gamma_8bit_correct(i, gamma_val); + + else + for (i=0; i<256; ++i) + table[i] = (png_byte)(i & 0xff); +} + +/* Used from png_read_destroy and below to release the memory used by the gamma + * tables. + */ +void /* PRIVATE */ +png_destroy_gamma_table(png_structrp png_ptr) +{ + png_free(png_ptr, png_ptr->gamma_table); + png_ptr->gamma_table = NULL; + +#ifdef PNG_16BIT_SUPPORTED + if (png_ptr->gamma_16_table != NULL) + { + int i; + int istop = (1 << (8 - png_ptr->gamma_shift)); + for (i = 0; i < istop; i++) + { + png_free(png_ptr, png_ptr->gamma_16_table[i]); + } + png_free(png_ptr, png_ptr->gamma_16_table); + png_ptr->gamma_16_table = NULL; + } +#endif /* 16BIT */ + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + png_free(png_ptr, png_ptr->gamma_from_1); + png_ptr->gamma_from_1 = NULL; + png_free(png_ptr, png_ptr->gamma_to_1); + png_ptr->gamma_to_1 = NULL; + +#ifdef PNG_16BIT_SUPPORTED + if (png_ptr->gamma_16_from_1 != NULL) + { + int i; + int istop = (1 << (8 - png_ptr->gamma_shift)); + for (i = 0; i < istop; i++) + { + png_free(png_ptr, png_ptr->gamma_16_from_1[i]); + } + png_free(png_ptr, png_ptr->gamma_16_from_1); + png_ptr->gamma_16_from_1 = NULL; + } + if (png_ptr->gamma_16_to_1 != NULL) + { + int i; + int istop = (1 << (8 - png_ptr->gamma_shift)); + for (i = 0; i < istop; i++) + { + png_free(png_ptr, png_ptr->gamma_16_to_1[i]); + } + png_free(png_ptr, png_ptr->gamma_16_to_1); + png_ptr->gamma_16_to_1 = NULL; + } +#endif /* 16BIT */ +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ +} + +/* We build the 8- or 16-bit gamma tables here. Note that for 16-bit + * tables, we don't make a full table if we are reducing to 8-bit in + * the future. Note also how the gamma_16 tables are segmented so that + * we don't need to allocate > 64K chunks for a full 16-bit table. + */ +void /* PRIVATE */ +png_build_gamma_table(png_structrp png_ptr, int bit_depth) +{ + png_debug(1, "in png_build_gamma_table"); + + /* Remove any existing table; this copes with multiple calls to + * png_read_update_info. The warning is because building the gamma tables + * multiple times is a performance hit - it's harmless but the ability to + * call png_read_update_info() multiple times is new in 1.5.6 so it seems + * sensible to warn if the app introduces such a hit. + */ + if (png_ptr->gamma_table != NULL || png_ptr->gamma_16_table != NULL) + { + png_warning(png_ptr, "gamma table being rebuilt"); + png_destroy_gamma_table(png_ptr); + } + + if (bit_depth <= 8) + { + png_build_8bit_table(png_ptr, &png_ptr->gamma_table, + png_ptr->screen_gamma > 0 ? + png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma) : PNG_FP_1); + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + if ((png_ptr->transformations & (PNG_COMPOSE | PNG_RGB_TO_GRAY)) != 0) + { + png_build_8bit_table(png_ptr, &png_ptr->gamma_to_1, + png_reciprocal(png_ptr->colorspace.gamma)); + + png_build_8bit_table(png_ptr, &png_ptr->gamma_from_1, + png_ptr->screen_gamma > 0 ? + png_reciprocal(png_ptr->screen_gamma) : + png_ptr->colorspace.gamma/* Probably doing rgb_to_gray */); + } +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ + } +#ifdef PNG_16BIT_SUPPORTED + else + { + png_byte shift, sig_bit; + + if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0) + { + sig_bit = png_ptr->sig_bit.red; + + if (png_ptr->sig_bit.green > sig_bit) + sig_bit = png_ptr->sig_bit.green; + + if (png_ptr->sig_bit.blue > sig_bit) + sig_bit = png_ptr->sig_bit.blue; + } + else + sig_bit = png_ptr->sig_bit.gray; + + /* 16-bit gamma code uses this equation: + * + * ov = table[(iv & 0xff) >> gamma_shift][iv >> 8] + * + * Where 'iv' is the input color value and 'ov' is the output value - + * pow(iv, gamma). + * + * Thus the gamma table consists of up to 256 256-entry tables. The table + * is selected by the (8-gamma_shift) most significant of the low 8 bits + * of the color value then indexed by the upper 8 bits: + * + * table[low bits][high 8 bits] + * + * So the table 'n' corresponds to all those 'iv' of: + * + * ..<(n+1 << gamma_shift)-1> + * + */ + if (sig_bit > 0 && sig_bit < 16U) + /* shift == insignificant bits */ + shift = (png_byte)((16U - sig_bit) & 0xff); + + else + shift = 0; /* keep all 16 bits */ + + if ((png_ptr->transformations & (PNG_16_TO_8 | PNG_SCALE_16_TO_8)) != 0) + { + /* PNG_MAX_GAMMA_8 is the number of bits to keep - effectively + * the significant bits in the *input* when the output will + * eventually be 8 bits. By default it is 11. + */ + if (shift < (16U - PNG_MAX_GAMMA_8)) + shift = (16U - PNG_MAX_GAMMA_8); + } + + if (shift > 8U) + shift = 8U; /* Guarantees at least one table! */ + + png_ptr->gamma_shift = shift; + + /* NOTE: prior to 1.5.4 this test used to include PNG_BACKGROUND (now + * PNG_COMPOSE). This effectively smashed the background calculation for + * 16-bit output because the 8-bit table assumes the result will be + * reduced to 8 bits. + */ + if ((png_ptr->transformations & (PNG_16_TO_8 | PNG_SCALE_16_TO_8)) != 0) + png_build_16to8_table(png_ptr, &png_ptr->gamma_16_table, shift, + png_ptr->screen_gamma > 0 ? png_product2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma) : PNG_FP_1); + + else + png_build_16bit_table(png_ptr, &png_ptr->gamma_16_table, shift, + png_ptr->screen_gamma > 0 ? png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma) : PNG_FP_1); + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + if ((png_ptr->transformations & (PNG_COMPOSE | PNG_RGB_TO_GRAY)) != 0) + { + png_build_16bit_table(png_ptr, &png_ptr->gamma_16_to_1, shift, + png_reciprocal(png_ptr->colorspace.gamma)); + + /* Notice that the '16 from 1' table should be full precision, however + * the lookup on this table still uses gamma_shift, so it can't be. + * TODO: fix this. + */ + png_build_16bit_table(png_ptr, &png_ptr->gamma_16_from_1, shift, + png_ptr->screen_gamma > 0 ? png_reciprocal(png_ptr->screen_gamma) : + png_ptr->colorspace.gamma/* Probably doing rgb_to_gray */); + } +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ + } +#endif /* 16BIT */ +} +#endif /* READ_GAMMA */ + +/* HARDWARE OR SOFTWARE OPTION SUPPORT */ +#ifdef PNG_SET_OPTION_SUPPORTED +int PNGAPI +png_set_option(png_structrp png_ptr, int option, int onoff) +{ + if (png_ptr != NULL && option >= 0 && option < PNG_OPTION_NEXT && + (option & 1) == 0) + { + png_uint_32 mask = 3U << option; + png_uint_32 setting = (2U + (onoff != 0)) << option; + png_uint_32 current = png_ptr->options; + + png_ptr->options = (png_uint_32)((current & ~mask) | setting); + + return (int)(current & mask) >> option; + } + + return PNG_OPTION_INVALID; +} +#endif + +/* sRGB support */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +/* sRGB conversion tables; these are machine generated with the code in + * contrib/tools/makesRGB.c. The actual sRGB transfer curve defined in the + * specification (see the article at https://en.wikipedia.org/wiki/SRGB) + * is used, not the gamma=1/2.2 approximation use elsewhere in libpng. + * The sRGB to linear table is exact (to the nearest 16-bit linear fraction). + * The inverse (linear to sRGB) table has accuracies as follows: + * + * For all possible (255*65535+1) input values: + * + * error: -0.515566 - 0.625971, 79441 (0.475369%) of readings inexact + * + * For the input values corresponding to the 65536 16-bit values: + * + * error: -0.513727 - 0.607759, 308 (0.469978%) of readings inexact + * + * In all cases the inexact readings are only off by one. + */ + +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +/* The convert-to-sRGB table is only currently required for read. */ +const png_uint_16 png_sRGB_table[256] = +{ + 0,20,40,60,80,99,119,139, + 159,179,199,219,241,264,288,313, + 340,367,396,427,458,491,526,562, + 599,637,677,718,761,805,851,898, + 947,997,1048,1101,1156,1212,1270,1330, + 1391,1453,1517,1583,1651,1720,1790,1863, + 1937,2013,2090,2170,2250,2333,2418,2504, + 2592,2681,2773,2866,2961,3058,3157,3258, + 3360,3464,3570,3678,3788,3900,4014,4129, + 4247,4366,4488,4611,4736,4864,4993,5124, + 5257,5392,5530,5669,5810,5953,6099,6246, + 6395,6547,6700,6856,7014,7174,7335,7500, + 7666,7834,8004,8177,8352,8528,8708,8889, + 9072,9258,9445,9635,9828,10022,10219,10417, + 10619,10822,11028,11235,11446,11658,11873,12090, + 12309,12530,12754,12980,13209,13440,13673,13909, + 14146,14387,14629,14874,15122,15371,15623,15878, + 16135,16394,16656,16920,17187,17456,17727,18001, + 18277,18556,18837,19121,19407,19696,19987,20281, + 20577,20876,21177,21481,21787,22096,22407,22721, + 23038,23357,23678,24002,24329,24658,24990,25325, + 25662,26001,26344,26688,27036,27386,27739,28094, + 28452,28813,29176,29542,29911,30282,30656,31033, + 31412,31794,32179,32567,32957,33350,33745,34143, + 34544,34948,35355,35764,36176,36591,37008,37429, + 37852,38278,38706,39138,39572,40009,40449,40891, + 41337,41785,42236,42690,43147,43606,44069,44534, + 45002,45473,45947,46423,46903,47385,47871,48359, + 48850,49344,49841,50341,50844,51349,51858,52369, + 52884,53401,53921,54445,54971,55500,56032,56567, + 57105,57646,58190,58737,59287,59840,60396,60955, + 61517,62082,62650,63221,63795,64372,64952,65535 +}; +#endif /* SIMPLIFIED_READ */ + +/* The base/delta tables are required for both read and write (but currently + * only the simplified versions.) + */ +const png_uint_16 png_sRGB_base[512] = +{ + 128,1782,3383,4644,5675,6564,7357,8074, + 8732,9346,9921,10463,10977,11466,11935,12384, + 12816,13233,13634,14024,14402,14769,15125,15473, + 15812,16142,16466,16781,17090,17393,17690,17981, + 18266,18546,18822,19093,19359,19621,19879,20133, + 20383,20630,20873,21113,21349,21583,21813,22041, + 22265,22487,22707,22923,23138,23350,23559,23767, + 23972,24175,24376,24575,24772,24967,25160,25352, + 25542,25730,25916,26101,26284,26465,26645,26823, + 27000,27176,27350,27523,27695,27865,28034,28201, + 28368,28533,28697,28860,29021,29182,29341,29500, + 29657,29813,29969,30123,30276,30429,30580,30730, + 30880,31028,31176,31323,31469,31614,31758,31902, + 32045,32186,32327,32468,32607,32746,32884,33021, + 33158,33294,33429,33564,33697,33831,33963,34095, + 34226,34357,34486,34616,34744,34873,35000,35127, + 35253,35379,35504,35629,35753,35876,35999,36122, + 36244,36365,36486,36606,36726,36845,36964,37083, + 37201,37318,37435,37551,37668,37783,37898,38013, + 38127,38241,38354,38467,38580,38692,38803,38915, + 39026,39136,39246,39356,39465,39574,39682,39790, + 39898,40005,40112,40219,40325,40431,40537,40642, + 40747,40851,40955,41059,41163,41266,41369,41471, + 41573,41675,41777,41878,41979,42079,42179,42279, + 42379,42478,42577,42676,42775,42873,42971,43068, + 43165,43262,43359,43456,43552,43648,43743,43839, + 43934,44028,44123,44217,44311,44405,44499,44592, + 44685,44778,44870,44962,45054,45146,45238,45329, + 45420,45511,45601,45692,45782,45872,45961,46051, + 46140,46229,46318,46406,46494,46583,46670,46758, + 46846,46933,47020,47107,47193,47280,47366,47452, + 47538,47623,47709,47794,47879,47964,48048,48133, + 48217,48301,48385,48468,48552,48635,48718,48801, + 48884,48966,49048,49131,49213,49294,49376,49458, + 49539,49620,49701,49782,49862,49943,50023,50103, + 50183,50263,50342,50422,50501,50580,50659,50738, + 50816,50895,50973,51051,51129,51207,51285,51362, + 51439,51517,51594,51671,51747,51824,51900,51977, + 52053,52129,52205,52280,52356,52432,52507,52582, + 52657,52732,52807,52881,52956,53030,53104,53178, + 53252,53326,53400,53473,53546,53620,53693,53766, + 53839,53911,53984,54056,54129,54201,54273,54345, + 54417,54489,54560,54632,54703,54774,54845,54916, + 54987,55058,55129,55199,55269,55340,55410,55480, + 55550,55620,55689,55759,55828,55898,55967,56036, + 56105,56174,56243,56311,56380,56448,56517,56585, + 56653,56721,56789,56857,56924,56992,57059,57127, + 57194,57261,57328,57395,57462,57529,57595,57662, + 57728,57795,57861,57927,57993,58059,58125,58191, + 58256,58322,58387,58453,58518,58583,58648,58713, + 58778,58843,58908,58972,59037,59101,59165,59230, + 59294,59358,59422,59486,59549,59613,59677,59740, + 59804,59867,59930,59993,60056,60119,60182,60245, + 60308,60370,60433,60495,60558,60620,60682,60744, + 60806,60868,60930,60992,61054,61115,61177,61238, + 61300,61361,61422,61483,61544,61605,61666,61727, + 61788,61848,61909,61969,62030,62090,62150,62211, + 62271,62331,62391,62450,62510,62570,62630,62689, + 62749,62808,62867,62927,62986,63045,63104,63163, + 63222,63281,63340,63398,63457,63515,63574,63632, + 63691,63749,63807,63865,63923,63981,64039,64097, + 64155,64212,64270,64328,64385,64443,64500,64557, + 64614,64672,64729,64786,64843,64900,64956,65013, + 65070,65126,65183,65239,65296,65352,65409,65465 +}; + +const png_byte png_sRGB_delta[512] = +{ + 207,201,158,129,113,100,90,82,77,72,68,64,61,59,56,54, + 52,50,49,47,46,45,43,42,41,40,39,39,38,37,36,36, + 35,34,34,33,33,32,32,31,31,30,30,30,29,29,28,28, + 28,27,27,27,27,26,26,26,25,25,25,25,24,24,24,24, + 23,23,23,23,23,22,22,22,22,22,22,21,21,21,21,21, + 21,20,20,20,20,20,20,20,20,19,19,19,19,19,19,19, + 19,18,18,18,18,18,18,18,18,18,18,17,17,17,17,17, + 17,17,17,17,17,17,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; +#endif /* SIMPLIFIED READ/WRITE sRGB support */ + +/* SIMPLIFIED READ/WRITE SUPPORT */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +static int +png_image_free_function(png_voidp argument) +{ + png_imagep image = png_voidcast(png_imagep, argument); + png_controlp cp = image->opaque; + png_control c; + + /* Double check that we have a png_ptr - it should be impossible to get here + * without one. + */ + if (cp->png_ptr == NULL) + return 0; + + /* First free any data held in the control structure. */ +# ifdef PNG_STDIO_SUPPORTED + if (cp->owned_file != 0) + { + FILE *fp = png_voidcast(FILE*, cp->png_ptr->io_ptr); + cp->owned_file = 0; + + /* Ignore errors here. */ + if (fp != NULL) + { + cp->png_ptr->io_ptr = NULL; + (void)fclose(fp); + } + } +# endif + + /* Copy the control structure so that the original, allocated, version can be + * safely freed. Notice that a png_error here stops the remainder of the + * cleanup, but this is probably fine because that would indicate bad memory + * problems anyway. + */ + c = *cp; + image->opaque = &c; + png_free(c.png_ptr, cp); + + /* Then the structures, calling the correct API. */ + if (c.for_write != 0) + { +# ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED + png_destroy_write_struct(&c.png_ptr, &c.info_ptr); +# else + png_error(c.png_ptr, "simplified write not supported"); +# endif + } + else + { +# ifdef PNG_SIMPLIFIED_READ_SUPPORTED + png_destroy_read_struct(&c.png_ptr, &c.info_ptr, NULL); +# else + png_error(c.png_ptr, "simplified read not supported"); +# endif + } + + /* Success. */ + return 1; +} + +void PNGAPI +png_image_free(png_imagep image) +{ + /* Safely call the real function, but only if doing so is safe at this point + * (if not inside an error handling context). Otherwise assume + * png_safe_execute will call this API after the return. + */ + if (image != NULL && image->opaque != NULL && + image->opaque->error_buf == NULL) + { + png_image_free_function(image); + image->opaque = NULL; + } +} + +int /* PRIVATE */ +png_image_error(png_imagep image, png_const_charp error_message) +{ + /* Utility to log an error. */ + png_safecat(image->message, (sizeof image->message), 0, error_message); + image->warning_or_error |= PNG_IMAGE_ERROR; + png_image_free(image); + return 0; +} + +#endif /* SIMPLIFIED READ/WRITE */ +#endif /* READ || WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/png.h b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/png.h new file mode 100644 index 0000000..139eb0d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/png.h @@ -0,0 +1,3247 @@ + +/* png.h - header file for PNG reference library + * + * libpng version 1.6.37 - April 14, 2019 + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. (See LICENSE, below.) + * + * Authors and maintainers: + * libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat + * libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger + * libpng versions 0.97, January 1998, through 1.6.35, July 2018: + * Glenn Randers-Pehrson + * libpng versions 1.6.36, December 2018, through 1.6.37, April 2019: + * Cosmin Truta + * See also "Contributing Authors", below. + */ + +/* + * COPYRIGHT NOTICE, DISCLAIMER, and LICENSE + * ========================================= + * + * PNG Reference Library License version 2 + * --------------------------------------- + * + * * Copyright (c) 1995-2019 The PNG Reference Library Authors. + * * Copyright (c) 2018-2019 Cosmin Truta. + * * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. + * * Copyright (c) 1996-1997 Andreas Dilger. + * * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * The software is supplied "as is", without warranty of any kind, + * express or implied, including, without limitation, the warranties + * of merchantability, fitness for a particular purpose, title, and + * non-infringement. In no event shall the Copyright owners, or + * anyone distributing the software, be liable for any damages or + * other liability, whether in contract, tort or otherwise, arising + * from, out of, or in connection with the software, or the use or + * other dealings in the software, even if advised of the possibility + * of such damage. + * + * Permission is hereby granted to use, copy, modify, and distribute + * this software, or portions hereof, for any purpose, without fee, + * 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 Copyright notice may not be removed or altered from any + * source or altered source distribution. + * + * + * PNG Reference Library License version 1 (for libpng 0.5 through 1.6.35) + * ----------------------------------------------------------------------- + * + * libpng versions 1.0.7, July 1, 2000, through 1.6.35, July 15, 2018 are + * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are + * derived from libpng-1.0.6, and are distributed according to the same + * disclaimer and license as libpng-1.0.6 with the following individuals + * added to the list of Contributing Authors: + * + * Simon-Pierre Cadieux + * Eric S. Raymond + * Mans Rullgard + * Cosmin Truta + * Gilles Vollant + * James Yu + * Mandar Sahastrabuddhe + * Google Inc. + * Vadim Barkov + * + * and with the following additions to the disclaimer: + * + * There is no warranty against interference with your enjoyment of + * the library or against infringement. There is no warranty that our + * efforts or the library will fulfill any of your particular purposes + * or needs. This library is provided with all faults, and the entire + * risk of satisfactory quality, performance, accuracy, and effort is + * with the user. + * + * Some files in the "contrib" directory and some configure-generated + * files that are distributed with libpng have other copyright owners, and + * are released under other open source licenses. + * + * libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are + * Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from + * libpng-0.96, and are distributed according to the same disclaimer and + * license as libpng-0.96, with the following individuals added to the + * list of Contributing Authors: + * + * Tom Lane + * Glenn Randers-Pehrson + * Willem van Schaik + * + * libpng versions 0.89, June 1996, through 0.96, May 1997, are + * Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, + * and are distributed according to the same disclaimer and license as + * libpng-0.88, with the following individuals added to the list of + * Contributing Authors: + * + * John Bowler + * Kevin Bracey + * Sam Bushell + * Magnus Holmgren + * Greg Roelofs + * Tom Tanner + * + * Some files in the "scripts" directory have other copyright owners, + * but are released under this license. + * + * libpng versions 0.5, May 1995, through 0.88, January 1996, are + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * For the purposes of this copyright and license, "Contributing Authors" + * is defined as the following set of individuals: + * + * Andreas Dilger + * Dave Martindale + * Guy Eric Schalnat + * Paul Schmidt + * Tim Wegner + * + * The PNG Reference Library is supplied "AS IS". The Contributing + * Authors and Group 42, Inc. disclaim all warranties, expressed or + * implied, including, without limitation, the warranties of + * merchantability and of fitness for any purpose. The Contributing + * Authors and Group 42, Inc. assume no liability for direct, indirect, + * incidental, special, exemplary, or consequential damages, which may + * result from the use of the PNG Reference Library, even if advised of + * the possibility of such damage. + * + * Permission is hereby granted to use, copy, modify, and distribute this + * source code, or portions hereof, for any purpose, without fee, subject + * to the following restrictions: + * + * 1. The origin of this source code must not be misrepresented. + * + * 2. Altered versions must be plainly marked as such and must not + * be misrepresented as being the original source. + * + * 3. This Copyright notice may not be removed or altered from any + * source or altered source distribution. + * + * The Contributing Authors and Group 42, Inc. specifically permit, + * without fee, and encourage the use of this source code as a component + * to supporting the PNG file format in commercial products. If you use + * this source code in a product, acknowledgment is not required but would + * be appreciated. + * + * END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE. + * + * TRADEMARK + * ========= + * + * The name "libpng" has not been registered by the Copyright owners + * as a trademark in any jurisdiction. However, because libpng has + * been distributed and maintained world-wide, continually since 1995, + * the Copyright owners claim "common-law trademark protection" in any + * jurisdiction where common-law trademark is recognized. + */ + +/* + * A "png_get_copyright" function is available, for convenient use in "about" + * boxes and the like: + * + * printf("%s", png_get_copyright(NULL)); + * + * Also, the PNG logo (in PNG format, of course) is supplied in the + * files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31). + */ + +/* + * The contributing authors would like to thank all those who helped + * with testing, bug fixes, and patience. This wouldn't have been + * possible without all of you. + * + * Thanks to Frank J. T. Wojcik for helping with the documentation. + */ + +/* Note about libpng version numbers: + * + * Due to various miscommunications, unforeseen code incompatibilities + * and occasional factors outside the authors' control, version numbering + * on the library has not always been consistent and straightforward. + * The following table summarizes matters since version 0.89c, which was + * the first widely used release: + * + * source png.h png.h shared-lib + * version string int version + * ------- ------ ----- ---------- + * 0.89c "1.0 beta 3" 0.89 89 1.0.89 + * 0.90 "1.0 beta 4" 0.90 90 0.90 [should have been 2.0.90] + * 0.95 "1.0 beta 5" 0.95 95 0.95 [should have been 2.0.95] + * 0.96 "1.0 beta 6" 0.96 96 0.96 [should have been 2.0.96] + * 0.97b "1.00.97 beta 7" 1.00.97 97 1.0.1 [should have been 2.0.97] + * 0.97c 0.97 97 2.0.97 + * 0.98 0.98 98 2.0.98 + * 0.99 0.99 98 2.0.99 + * 0.99a-m 0.99 99 2.0.99 + * 1.00 1.00 100 2.1.0 [100 should be 10000] + * 1.0.0 (from here on, the 100 2.1.0 [100 should be 10000] + * 1.0.1 png.h string is 10001 2.1.0 + * 1.0.1a-e identical to the 10002 from here on, the shared library + * 1.0.2 source version) 10002 is 2.V where V is the source code + * 1.0.2a-b 10003 version, except as noted. + * 1.0.3 10003 + * 1.0.3a-d 10004 + * 1.0.4 10004 + * 1.0.4a-f 10005 + * 1.0.5 (+ 2 patches) 10005 + * 1.0.5a-d 10006 + * 1.0.5e-r 10100 (not source compatible) + * 1.0.5s-v 10006 (not binary compatible) + * 1.0.6 (+ 3 patches) 10006 (still binary incompatible) + * 1.0.6d-f 10007 (still binary incompatible) + * 1.0.6g 10007 + * 1.0.6h 10007 10.6h (testing xy.z so-numbering) + * 1.0.6i 10007 10.6i + * 1.0.6j 10007 2.1.0.6j (incompatible with 1.0.0) + * 1.0.7beta11-14 DLLNUM 10007 2.1.0.7beta11-14 (binary compatible) + * 1.0.7beta15-18 1 10007 2.1.0.7beta15-18 (binary compatible) + * 1.0.7rc1-2 1 10007 2.1.0.7rc1-2 (binary compatible) + * 1.0.7 1 10007 (still compatible) + * ... + * 1.0.69 10 10069 10.so.0.69[.0] + * ... + * 1.2.59 13 10259 12.so.0.59[.0] + * ... + * 1.4.20 14 10420 14.so.0.20[.0] + * ... + * 1.5.30 15 10530 15.so.15.30[.0] + * ... + * 1.6.37 16 10637 16.so.16.37[.0] + * + * Henceforth the source version will match the shared-library major and + * minor numbers; the shared-library major version number will be used for + * changes in backward compatibility, as it is intended. + * The PNG_LIBPNG_VER macro, which is not used within libpng but is + * available for applications, is an unsigned integer of the form XYYZZ + * corresponding to the source version X.Y.Z (leading zeros in Y and Z). + * Beta versions were given the previous public release number plus a + * letter, until version 1.0.6j; from then on they were given the upcoming + * public release number plus "betaNN" or "rcNN". + * + * Binary incompatibility exists only when applications make direct access + * to the info_ptr or png_ptr members through png.h, and the compiled + * application is loaded with a different version of the library. + * + * DLLNUM will change each time there are forward or backward changes + * in binary compatibility (e.g., when a new feature is added). + * + * See libpng.txt or libpng.3 for more information. The PNG specification + * is available as a W3C Recommendation and as an ISO/IEC Standard; see + * + */ + +#ifndef PNG_H +#define PNG_H + +/* This is not the place to learn how to use libpng. The file libpng-manual.txt + * describes how to use libpng, and the file example.c summarizes it + * with some code on which to build. This file is useful for looking + * at the actual function definitions and structure components. If that + * file has been stripped from your copy of libpng, you can find it at + * + * + * If you just need to read a PNG file and don't want to read the documentation + * skip to the end of this file and read the section entitled 'simplified API'. + */ + +/* Version information for png.h - this should match the version in png.c */ +#define PNG_LIBPNG_VER_STRING "1.6.37" +#define PNG_HEADER_VERSION_STRING " libpng version 1.6.37 - April 14, 2019\n" + +#define PNG_LIBPNG_VER_SONUM 16 +#define PNG_LIBPNG_VER_DLLNUM 16 + +/* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */ +#define PNG_LIBPNG_VER_MAJOR 1 +#define PNG_LIBPNG_VER_MINOR 6 +#define PNG_LIBPNG_VER_RELEASE 37 + +/* This should be zero for a public release, or non-zero for a + * development version. [Deprecated] + */ +#define PNG_LIBPNG_VER_BUILD 0 + +/* Release Status */ +#define PNG_LIBPNG_BUILD_ALPHA 1 +#define PNG_LIBPNG_BUILD_BETA 2 +#define PNG_LIBPNG_BUILD_RC 3 +#define PNG_LIBPNG_BUILD_STABLE 4 +#define PNG_LIBPNG_BUILD_RELEASE_STATUS_MASK 7 + +/* Release-Specific Flags */ +#define PNG_LIBPNG_BUILD_PATCH 8 /* Can be OR'ed with + PNG_LIBPNG_BUILD_STABLE only */ +#define PNG_LIBPNG_BUILD_PRIVATE 16 /* Cannot be OR'ed with + PNG_LIBPNG_BUILD_SPECIAL */ +#define PNG_LIBPNG_BUILD_SPECIAL 32 /* Cannot be OR'ed with + PNG_LIBPNG_BUILD_PRIVATE */ + +#define PNG_LIBPNG_BUILD_BASE_TYPE PNG_LIBPNG_BUILD_STABLE + +/* Careful here. At one time, Guy wanted to use 082, but that + * would be octal. We must not include leading zeros. + * Versions 0.7 through 1.0.0 were in the range 0 to 100 here + * (only version 1.0.0 was mis-numbered 100 instead of 10000). + * From version 1.0.1 it is: + * XXYYZZ, where XX=major, YY=minor, ZZ=release + */ +#define PNG_LIBPNG_VER 10637 /* 1.6.37 */ + +/* Library configuration: these options cannot be changed after + * the library has been built. + */ +#ifndef PNGLCONF_H +/* If pnglibconf.h is missing, you can + * copy scripts/pnglibconf.h.prebuilt to pnglibconf.h + */ +# include "pnglibconf.h" +#endif + +#ifndef PNG_VERSION_INFO_ONLY +/* Machine specific configuration. */ +# include "pngconf.h" +#endif + +/* + * Added at libpng-1.2.8 + * + * Ref MSDN: Private as priority over Special + * VS_FF_PRIVATEBUILD File *was not* built using standard release + * procedures. If this value is given, the StringFileInfo block must + * contain a PrivateBuild string. + * + * VS_FF_SPECIALBUILD File *was* built by the original company using + * standard release procedures but is a variation of the standard + * file of the same version number. If this value is given, the + * StringFileInfo block must contain a SpecialBuild string. + */ + +#ifdef PNG_USER_PRIVATEBUILD /* From pnglibconf.h */ +# define PNG_LIBPNG_BUILD_TYPE \ + (PNG_LIBPNG_BUILD_BASE_TYPE | PNG_LIBPNG_BUILD_PRIVATE) +#else +# ifdef PNG_LIBPNG_SPECIALBUILD +# define PNG_LIBPNG_BUILD_TYPE \ + (PNG_LIBPNG_BUILD_BASE_TYPE | PNG_LIBPNG_BUILD_SPECIAL) +# else +# define PNG_LIBPNG_BUILD_TYPE (PNG_LIBPNG_BUILD_BASE_TYPE) +# endif +#endif + +#ifndef PNG_VERSION_INFO_ONLY + +/* Inhibit C++ name-mangling for libpng functions but not for system calls. */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Version information for C files, stored in png.c. This had better match + * the version above. + */ +#define png_libpng_ver png_get_header_ver(NULL) + +/* This file is arranged in several sections: + * + * 1. [omitted] + * 2. Any configuration options that can be specified by for the application + * code when it is built. (Build time configuration is in pnglibconf.h) + * 3. Type definitions (base types are defined in pngconf.h), structure + * definitions. + * 4. Exported library functions. + * 5. Simplified API. + * 6. Implementation options. + * + * The library source code has additional files (principally pngpriv.h) that + * allow configuration of the library. + */ + +/* Section 1: [omitted] */ + +/* Section 2: run time configuration + * See pnglibconf.h for build time configuration + * + * Run time configuration allows the application to choose between + * implementations of certain arithmetic APIs. The default is set + * at build time and recorded in pnglibconf.h, but it is safe to + * override these (and only these) settings. Note that this won't + * change what the library does, only application code, and the + * settings can (and probably should) be made on a per-file basis + * by setting the #defines before including png.h + * + * Use macros to read integers from PNG data or use the exported + * functions? + * PNG_USE_READ_MACROS: use the macros (see below) Note that + * the macros evaluate their argument multiple times. + * PNG_NO_USE_READ_MACROS: call the relevant library function. + * + * Use the alternative algorithm for compositing alpha samples that + * does not use division? + * PNG_READ_COMPOSITE_NODIV_SUPPORTED: use the 'no division' + * algorithm. + * PNG_NO_READ_COMPOSITE_NODIV: use the 'division' algorithm. + * + * How to handle benign errors if PNG_ALLOW_BENIGN_ERRORS is + * false? + * PNG_ALLOW_BENIGN_ERRORS: map calls to the benign error + * APIs to png_warning. + * Otherwise the calls are mapped to png_error. + */ + +/* Section 3: type definitions, including structures and compile time + * constants. + * See pngconf.h for base types that vary by machine/system + */ + +/* This triggers a compiler error in png.c, if png.c and png.h + * do not agree upon the version number. + */ +typedef char* png_libpng_version_1_6_37; + +/* Basic control structions. Read libpng-manual.txt or libpng.3 for more info. + * + * png_struct is the cache of information used while reading or writing a single + * PNG file. One of these is always required, although the simplified API + * (below) hides the creation and destruction of it. + */ +typedef struct png_struct_def png_struct; +typedef const png_struct * png_const_structp; +typedef png_struct * png_structp; +typedef png_struct * * png_structpp; + +/* png_info contains information read from or to be written to a PNG file. One + * or more of these must exist while reading or creating a PNG file. The + * information is not used by libpng during read but is used to control what + * gets written when a PNG file is created. "png_get_" function calls read + * information during read and "png_set_" functions calls write information + * when creating a PNG. + * been moved into a separate header file that is not accessible to + * applications. Read libpng-manual.txt or libpng.3 for more info. + */ +typedef struct png_info_def png_info; +typedef png_info * png_infop; +typedef const png_info * png_const_infop; +typedef png_info * * png_infopp; + +/* Types with names ending 'p' are pointer types. The corresponding types with + * names ending 'rp' are identical pointer types except that the pointer is + * marked 'restrict', which means that it is the only pointer to the object + * passed to the function. Applications should not use the 'restrict' types; + * it is always valid to pass 'p' to a pointer with a function argument of the + * corresponding 'rp' type. Different compilers have different rules with + * regard to type matching in the presence of 'restrict'. For backward + * compatibility libpng callbacks never have 'restrict' in their parameters and, + * consequentially, writing portable application code is extremely difficult if + * an attempt is made to use 'restrict'. + */ +typedef png_struct * PNG_RESTRICT png_structrp; +typedef const png_struct * PNG_RESTRICT png_const_structrp; +typedef png_info * PNG_RESTRICT png_inforp; +typedef const png_info * PNG_RESTRICT png_const_inforp; + +/* Three color definitions. The order of the red, green, and blue, (and the + * exact size) is not important, although the size of the fields need to + * be png_byte or png_uint_16 (as defined below). + */ +typedef struct png_color_struct +{ + png_byte red; + png_byte green; + png_byte blue; +} png_color; +typedef png_color * png_colorp; +typedef const png_color * png_const_colorp; +typedef png_color * * png_colorpp; + +typedef struct png_color_16_struct +{ + png_byte index; /* used for palette files */ + png_uint_16 red; /* for use in red green blue files */ + png_uint_16 green; + png_uint_16 blue; + png_uint_16 gray; /* for use in grayscale files */ +} png_color_16; +typedef png_color_16 * png_color_16p; +typedef const png_color_16 * png_const_color_16p; +typedef png_color_16 * * png_color_16pp; + +typedef struct png_color_8_struct +{ + png_byte red; /* for use in red green blue files */ + png_byte green; + png_byte blue; + png_byte gray; /* for use in grayscale files */ + png_byte alpha; /* for alpha channel files */ +} png_color_8; +typedef png_color_8 * png_color_8p; +typedef const png_color_8 * png_const_color_8p; +typedef png_color_8 * * png_color_8pp; + +/* + * The following two structures are used for the in-core representation + * of sPLT chunks. + */ +typedef struct png_sPLT_entry_struct +{ + png_uint_16 red; + png_uint_16 green; + png_uint_16 blue; + png_uint_16 alpha; + png_uint_16 frequency; +} png_sPLT_entry; +typedef png_sPLT_entry * png_sPLT_entryp; +typedef const png_sPLT_entry * png_const_sPLT_entryp; +typedef png_sPLT_entry * * png_sPLT_entrypp; + +/* When the depth of the sPLT palette is 8 bits, the color and alpha samples + * occupy the LSB of their respective members, and the MSB of each member + * is zero-filled. The frequency member always occupies the full 16 bits. + */ + +typedef struct png_sPLT_struct +{ + png_charp name; /* palette name */ + png_byte depth; /* depth of palette samples */ + png_sPLT_entryp entries; /* palette entries */ + png_int_32 nentries; /* number of palette entries */ +} png_sPLT_t; +typedef png_sPLT_t * png_sPLT_tp; +typedef const png_sPLT_t * png_const_sPLT_tp; +typedef png_sPLT_t * * png_sPLT_tpp; + +#ifdef PNG_TEXT_SUPPORTED +/* png_text holds the contents of a text/ztxt/itxt chunk in a PNG file, + * and whether that contents is compressed or not. The "key" field + * points to a regular zero-terminated C string. The "text" fields can be a + * regular C string, an empty string, or a NULL pointer. + * However, the structure returned by png_get_text() will always contain + * the "text" field as a regular zero-terminated C string (possibly + * empty), never a NULL pointer, so it can be safely used in printf() and + * other string-handling functions. Note that the "itxt_length", "lang", and + * "lang_key" members of the structure only exist when the library is built + * with iTXt chunk support. Prior to libpng-1.4.0 the library was built by + * default without iTXt support. Also note that when iTXt *is* supported, + * the "lang" and "lang_key" fields contain NULL pointers when the + * "compression" field contains * PNG_TEXT_COMPRESSION_NONE or + * PNG_TEXT_COMPRESSION_zTXt. Note that the "compression value" is not the + * same as what appears in the PNG tEXt/zTXt/iTXt chunk's "compression flag" + * which is always 0 or 1, or its "compression method" which is always 0. + */ +typedef struct png_text_struct +{ + int compression; /* compression value: + -1: tEXt, none + 0: zTXt, deflate + 1: iTXt, none + 2: iTXt, deflate */ + png_charp key; /* keyword, 1-79 character description of "text" */ + png_charp text; /* comment, may be an empty string (ie "") + or a NULL pointer */ + size_t text_length; /* length of the text string */ + size_t itxt_length; /* length of the itxt string */ + png_charp lang; /* language code, 0-79 characters + or a NULL pointer */ + png_charp lang_key; /* keyword translated UTF-8 string, 0 or more + chars or a NULL pointer */ +} png_text; +typedef png_text * png_textp; +typedef const png_text * png_const_textp; +typedef png_text * * png_textpp; +#endif + +/* Supported compression types for text in PNG files (tEXt, and zTXt). + * The values of the PNG_TEXT_COMPRESSION_ defines should NOT be changed. */ +#define PNG_TEXT_COMPRESSION_NONE_WR -3 +#define PNG_TEXT_COMPRESSION_zTXt_WR -2 +#define PNG_TEXT_COMPRESSION_NONE -1 +#define PNG_TEXT_COMPRESSION_zTXt 0 +#define PNG_ITXT_COMPRESSION_NONE 1 +#define PNG_ITXT_COMPRESSION_zTXt 2 +#define PNG_TEXT_COMPRESSION_LAST 3 /* Not a valid value */ + +/* png_time is a way to hold the time in an machine independent way. + * Two conversions are provided, both from time_t and struct tm. There + * is no portable way to convert to either of these structures, as far + * as I know. If you know of a portable way, send it to me. As a side + * note - PNG has always been Year 2000 compliant! + */ +typedef struct png_time_struct +{ + png_uint_16 year; /* full year, as in, 1995 */ + png_byte month; /* month of year, 1 - 12 */ + png_byte day; /* day of month, 1 - 31 */ + png_byte hour; /* hour of day, 0 - 23 */ + png_byte minute; /* minute of hour, 0 - 59 */ + png_byte second; /* second of minute, 0 - 60 (for leap seconds) */ +} png_time; +typedef png_time * png_timep; +typedef const png_time * png_const_timep; +typedef png_time * * png_timepp; + +#if defined(PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED) ||\ + defined(PNG_USER_CHUNKS_SUPPORTED) +/* png_unknown_chunk is a structure to hold queued chunks for which there is + * no specific support. The idea is that we can use this to queue + * up private chunks for output even though the library doesn't actually + * know about their semantics. + * + * The data in the structure is set by libpng on read and used on write. + */ +typedef struct png_unknown_chunk_t +{ + png_byte name[5]; /* Textual chunk name with '\0' terminator */ + png_byte *data; /* Data, should not be modified on read! */ + size_t size; + + /* On write 'location' must be set using the flag values listed below. + * Notice that on read it is set by libpng however the values stored have + * more bits set than are listed below. Always treat the value as a + * bitmask. On write set only one bit - setting multiple bits may cause the + * chunk to be written in multiple places. + */ + png_byte location; /* mode of operation at read time */ +} +png_unknown_chunk; + +typedef png_unknown_chunk * png_unknown_chunkp; +typedef const png_unknown_chunk * png_const_unknown_chunkp; +typedef png_unknown_chunk * * png_unknown_chunkpp; +#endif + +/* Flag values for the unknown chunk location byte. */ +#define PNG_HAVE_IHDR 0x01 +#define PNG_HAVE_PLTE 0x02 +#define PNG_AFTER_IDAT 0x08 + +/* Maximum positive integer used in PNG is (2^31)-1 */ +#define PNG_UINT_31_MAX ((png_uint_32)0x7fffffffL) +#define PNG_UINT_32_MAX ((png_uint_32)(-1)) +#define PNG_SIZE_MAX ((size_t)(-1)) + +/* These are constants for fixed point values encoded in the + * PNG specification manner (x100000) + */ +#define PNG_FP_1 100000 +#define PNG_FP_HALF 50000 +#define PNG_FP_MAX ((png_fixed_point)0x7fffffffL) +#define PNG_FP_MIN (-PNG_FP_MAX) + +/* These describe the color_type field in png_info. */ +/* color type masks */ +#define PNG_COLOR_MASK_PALETTE 1 +#define PNG_COLOR_MASK_COLOR 2 +#define PNG_COLOR_MASK_ALPHA 4 + +/* color types. Note that not all combinations are legal */ +#define PNG_COLOR_TYPE_GRAY 0 +#define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE) +#define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR) +#define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA) +#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA) +/* aliases */ +#define PNG_COLOR_TYPE_RGBA PNG_COLOR_TYPE_RGB_ALPHA +#define PNG_COLOR_TYPE_GA PNG_COLOR_TYPE_GRAY_ALPHA + +/* This is for compression type. PNG 1.0-1.2 only define the single type. */ +#define PNG_COMPRESSION_TYPE_BASE 0 /* Deflate method 8, 32K window */ +#define PNG_COMPRESSION_TYPE_DEFAULT PNG_COMPRESSION_TYPE_BASE + +/* This is for filter type. PNG 1.0-1.2 only define the single type. */ +#define PNG_FILTER_TYPE_BASE 0 /* Single row per-byte filtering */ +#define PNG_INTRAPIXEL_DIFFERENCING 64 /* Used only in MNG datastreams */ +#define PNG_FILTER_TYPE_DEFAULT PNG_FILTER_TYPE_BASE + +/* These are for the interlacing type. These values should NOT be changed. */ +#define PNG_INTERLACE_NONE 0 /* Non-interlaced image */ +#define PNG_INTERLACE_ADAM7 1 /* Adam7 interlacing */ +#define PNG_INTERLACE_LAST 2 /* Not a valid value */ + +/* These are for the oFFs chunk. These values should NOT be changed. */ +#define PNG_OFFSET_PIXEL 0 /* Offset in pixels */ +#define PNG_OFFSET_MICROMETER 1 /* Offset in micrometers (1/10^6 meter) */ +#define PNG_OFFSET_LAST 2 /* Not a valid value */ + +/* These are for the pCAL chunk. These values should NOT be changed. */ +#define PNG_EQUATION_LINEAR 0 /* Linear transformation */ +#define PNG_EQUATION_BASE_E 1 /* Exponential base e transform */ +#define PNG_EQUATION_ARBITRARY 2 /* Arbitrary base exponential transform */ +#define PNG_EQUATION_HYPERBOLIC 3 /* Hyperbolic sine transformation */ +#define PNG_EQUATION_LAST 4 /* Not a valid value */ + +/* These are for the sCAL chunk. These values should NOT be changed. */ +#define PNG_SCALE_UNKNOWN 0 /* unknown unit (image scale) */ +#define PNG_SCALE_METER 1 /* meters per pixel */ +#define PNG_SCALE_RADIAN 2 /* radians per pixel */ +#define PNG_SCALE_LAST 3 /* Not a valid value */ + +/* These are for the pHYs chunk. These values should NOT be changed. */ +#define PNG_RESOLUTION_UNKNOWN 0 /* pixels/unknown unit (aspect ratio) */ +#define PNG_RESOLUTION_METER 1 /* pixels/meter */ +#define PNG_RESOLUTION_LAST 2 /* Not a valid value */ + +/* These are for the sRGB chunk. These values should NOT be changed. */ +#define PNG_sRGB_INTENT_PERCEPTUAL 0 +#define PNG_sRGB_INTENT_RELATIVE 1 +#define PNG_sRGB_INTENT_SATURATION 2 +#define PNG_sRGB_INTENT_ABSOLUTE 3 +#define PNG_sRGB_INTENT_LAST 4 /* Not a valid value */ + +/* This is for text chunks */ +#define PNG_KEYWORD_MAX_LENGTH 79 + +/* Maximum number of entries in PLTE/sPLT/tRNS arrays */ +#define PNG_MAX_PALETTE_LENGTH 256 + +/* These determine if an ancillary chunk's data has been successfully read + * from the PNG header, or if the application has filled in the corresponding + * data in the info_struct to be written into the output file. The values + * of the PNG_INFO_ defines should NOT be changed. + */ +#define PNG_INFO_gAMA 0x0001U +#define PNG_INFO_sBIT 0x0002U +#define PNG_INFO_cHRM 0x0004U +#define PNG_INFO_PLTE 0x0008U +#define PNG_INFO_tRNS 0x0010U +#define PNG_INFO_bKGD 0x0020U +#define PNG_INFO_hIST 0x0040U +#define PNG_INFO_pHYs 0x0080U +#define PNG_INFO_oFFs 0x0100U +#define PNG_INFO_tIME 0x0200U +#define PNG_INFO_pCAL 0x0400U +#define PNG_INFO_sRGB 0x0800U /* GR-P, 0.96a */ +#define PNG_INFO_iCCP 0x1000U /* ESR, 1.0.6 */ +#define PNG_INFO_sPLT 0x2000U /* ESR, 1.0.6 */ +#define PNG_INFO_sCAL 0x4000U /* ESR, 1.0.6 */ +#define PNG_INFO_IDAT 0x8000U /* ESR, 1.0.6 */ +#define PNG_INFO_eXIf 0x10000U /* GR-P, 1.6.31 */ + +/* This is used for the transformation routines, as some of them + * change these values for the row. It also should enable using + * the routines for other purposes. + */ +typedef struct png_row_info_struct +{ + png_uint_32 width; /* width of row */ + size_t rowbytes; /* number of bytes in row */ + png_byte color_type; /* color type of row */ + png_byte bit_depth; /* bit depth of row */ + png_byte channels; /* number of channels (1, 2, 3, or 4) */ + png_byte pixel_depth; /* bits per pixel (depth * channels) */ +} png_row_info; + +typedef png_row_info * png_row_infop; +typedef png_row_info * * png_row_infopp; + +/* These are the function types for the I/O functions and for the functions + * that allow the user to override the default I/O functions with his or her + * own. The png_error_ptr type should match that of user-supplied warning + * and error functions, while the png_rw_ptr type should match that of the + * user read/write data functions. Note that the 'write' function must not + * modify the buffer it is passed. The 'read' function, on the other hand, is + * expected to return the read data in the buffer. + */ +typedef PNG_CALLBACK(void, *png_error_ptr, (png_structp, png_const_charp)); +typedef PNG_CALLBACK(void, *png_rw_ptr, (png_structp, png_bytep, size_t)); +typedef PNG_CALLBACK(void, *png_flush_ptr, (png_structp)); +typedef PNG_CALLBACK(void, *png_read_status_ptr, (png_structp, png_uint_32, + int)); +typedef PNG_CALLBACK(void, *png_write_status_ptr, (png_structp, png_uint_32, + int)); + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +typedef PNG_CALLBACK(void, *png_progressive_info_ptr, (png_structp, png_infop)); +typedef PNG_CALLBACK(void, *png_progressive_end_ptr, (png_structp, png_infop)); + +/* The following callback receives png_uint_32 row_number, int pass for the + * png_bytep data of the row. When transforming an interlaced image the + * row number is the row number within the sub-image of the interlace pass, so + * the value will increase to the height of the sub-image (not the full image) + * then reset to 0 for the next pass. + * + * Use PNG_ROW_FROM_PASS_ROW(row, pass) and PNG_COL_FROM_PASS_COL(col, pass) to + * find the output pixel (x,y) given an interlaced sub-image pixel + * (row,col,pass). (See below for these macros.) + */ +typedef PNG_CALLBACK(void, *png_progressive_row_ptr, (png_structp, png_bytep, + png_uint_32, int)); +#endif + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) +typedef PNG_CALLBACK(void, *png_user_transform_ptr, (png_structp, png_row_infop, + png_bytep)); +#endif + +#ifdef PNG_USER_CHUNKS_SUPPORTED +typedef PNG_CALLBACK(int, *png_user_chunk_ptr, (png_structp, + png_unknown_chunkp)); +#endif +#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED +/* not used anywhere */ +/* typedef PNG_CALLBACK(void, *png_unknown_chunk_ptr, (png_structp)); */ +#endif + +#ifdef PNG_SETJMP_SUPPORTED +/* This must match the function definition in , and the application + * must include this before png.h to obtain the definition of jmp_buf. The + * function is required to be PNG_NORETURN, but this is not checked. If the + * function does return the application will crash via an abort() or similar + * system level call. + * + * If you get a warning here while building the library you may need to make + * changes to ensure that pnglibconf.h records the calling convention used by + * your compiler. This may be very difficult - try using a different compiler + * to build the library! + */ +PNG_FUNCTION(void, (PNGCAPI *png_longjmp_ptr), PNGARG((jmp_buf, int)), typedef); +#endif + +/* Transform masks for the high-level interface */ +#define PNG_TRANSFORM_IDENTITY 0x0000 /* read and write */ +#define PNG_TRANSFORM_STRIP_16 0x0001 /* read only */ +#define PNG_TRANSFORM_STRIP_ALPHA 0x0002 /* read only */ +#define PNG_TRANSFORM_PACKING 0x0004 /* read and write */ +#define PNG_TRANSFORM_PACKSWAP 0x0008 /* read and write */ +#define PNG_TRANSFORM_EXPAND 0x0010 /* read only */ +#define PNG_TRANSFORM_INVERT_MONO 0x0020 /* read and write */ +#define PNG_TRANSFORM_SHIFT 0x0040 /* read and write */ +#define PNG_TRANSFORM_BGR 0x0080 /* read and write */ +#define PNG_TRANSFORM_SWAP_ALPHA 0x0100 /* read and write */ +#define PNG_TRANSFORM_SWAP_ENDIAN 0x0200 /* read and write */ +#define PNG_TRANSFORM_INVERT_ALPHA 0x0400 /* read and write */ +#define PNG_TRANSFORM_STRIP_FILLER 0x0800 /* write only */ +/* Added to libpng-1.2.34 */ +#define PNG_TRANSFORM_STRIP_FILLER_BEFORE PNG_TRANSFORM_STRIP_FILLER +#define PNG_TRANSFORM_STRIP_FILLER_AFTER 0x1000 /* write only */ +/* Added to libpng-1.4.0 */ +#define PNG_TRANSFORM_GRAY_TO_RGB 0x2000 /* read only */ +/* Added to libpng-1.5.4 */ +#define PNG_TRANSFORM_EXPAND_16 0x4000 /* read only */ +#if INT_MAX >= 0x8000 /* else this might break */ +#define PNG_TRANSFORM_SCALE_16 0x8000 /* read only */ +#endif + +/* Flags for MNG supported features */ +#define PNG_FLAG_MNG_EMPTY_PLTE 0x01 +#define PNG_FLAG_MNG_FILTER_64 0x04 +#define PNG_ALL_MNG_FEATURES 0x05 + +/* NOTE: prior to 1.5 these functions had no 'API' style declaration, + * this allowed the zlib default functions to be used on Windows + * platforms. In 1.5 the zlib default malloc (which just calls malloc and + * ignores the first argument) should be completely compatible with the + * following. + */ +typedef PNG_CALLBACK(png_voidp, *png_malloc_ptr, (png_structp, + png_alloc_size_t)); +typedef PNG_CALLBACK(void, *png_free_ptr, (png_structp, png_voidp)); + +/* Section 4: exported functions + * Here are the function definitions most commonly used. This is not + * the place to find out how to use libpng. See libpng-manual.txt for the + * full explanation, see example.c for the summary. This just provides + * a simple one line description of the use of each function. + * + * The PNG_EXPORT() and PNG_EXPORTA() macros used below are defined in + * pngconf.h and in the *.dfn files in the scripts directory. + * + * PNG_EXPORT(ordinal, type, name, (args)); + * + * ordinal: ordinal that is used while building + * *.def files. The ordinal value is only + * relevant when preprocessing png.h with + * the *.dfn files for building symbol table + * entries, and are removed by pngconf.h. + * type: return type of the function + * name: function name + * args: function arguments, with types + * + * When we wish to append attributes to a function prototype we use + * the PNG_EXPORTA() macro instead. + * + * PNG_EXPORTA(ordinal, type, name, (args), attributes); + * + * ordinal, type, name, and args: same as in PNG_EXPORT(). + * attributes: function attributes + */ + +/* Returns the version number of the library */ +PNG_EXPORT(1, png_uint_32, png_access_version_number, (void)); + +/* Tell lib we have already handled the first magic bytes. + * Handling more than 8 bytes from the beginning of the file is an error. + */ +PNG_EXPORT(2, void, png_set_sig_bytes, (png_structrp png_ptr, int num_bytes)); + +/* Check sig[start] through sig[start + num_to_check - 1] to see if it's a + * PNG file. Returns zero if the supplied bytes match the 8-byte PNG + * signature, and non-zero otherwise. Having num_to_check == 0 or + * start > 7 will always fail (ie return non-zero). + */ +PNG_EXPORT(3, int, png_sig_cmp, (png_const_bytep sig, size_t start, + size_t num_to_check)); + +/* Simple signature checking function. This is the same as calling + * png_check_sig(sig, n) := !png_sig_cmp(sig, 0, n). + */ +#define png_check_sig(sig, n) !png_sig_cmp((sig), 0, (n)) + +/* Allocate and initialize png_ptr struct for reading, and any other memory. */ +PNG_EXPORTA(4, png_structp, png_create_read_struct, + (png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn), + PNG_ALLOCATED); + +/* Allocate and initialize png_ptr struct for writing, and any other memory */ +PNG_EXPORTA(5, png_structp, png_create_write_struct, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn), + PNG_ALLOCATED); + +PNG_EXPORT(6, size_t, png_get_compression_buffer_size, + (png_const_structrp png_ptr)); + +PNG_EXPORT(7, void, png_set_compression_buffer_size, (png_structrp png_ptr, + size_t size)); + +/* Moved from pngconf.h in 1.4.0 and modified to ensure setjmp/longjmp + * match up. + */ +#ifdef PNG_SETJMP_SUPPORTED +/* This function returns the jmp_buf built in to *png_ptr. It must be + * supplied with an appropriate 'longjmp' function to use on that jmp_buf + * unless the default error function is overridden in which case NULL is + * acceptable. The size of the jmp_buf is checked against the actual size + * allocated by the library - the call will return NULL on a mismatch + * indicating an ABI mismatch. + */ +PNG_EXPORT(8, jmp_buf*, png_set_longjmp_fn, (png_structrp png_ptr, + png_longjmp_ptr longjmp_fn, size_t jmp_buf_size)); +# define png_jmpbuf(png_ptr) \ + (*png_set_longjmp_fn((png_ptr), longjmp, (sizeof (jmp_buf)))) +#else +# define png_jmpbuf(png_ptr) \ + (LIBPNG_WAS_COMPILED_WITH__PNG_NO_SETJMP) +#endif +/* This function should be used by libpng applications in place of + * longjmp(png_ptr->jmpbuf, val). If longjmp_fn() has been set, it + * will use it; otherwise it will call PNG_ABORT(). This function was + * added in libpng-1.5.0. + */ +PNG_EXPORTA(9, void, png_longjmp, (png_const_structrp png_ptr, int val), + PNG_NORETURN); + +#ifdef PNG_READ_SUPPORTED +/* Reset the compression stream */ +PNG_EXPORTA(10, int, png_reset_zstream, (png_structrp png_ptr), PNG_DEPRECATED); +#endif + +/* New functions added in libpng-1.0.2 (not enabled by default until 1.2.0) */ +#ifdef PNG_USER_MEM_SUPPORTED +PNG_EXPORTA(11, png_structp, png_create_read_struct_2, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn, + png_voidp mem_ptr, png_malloc_ptr malloc_fn, png_free_ptr free_fn), + PNG_ALLOCATED); +PNG_EXPORTA(12, png_structp, png_create_write_struct_2, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn, + png_voidp mem_ptr, png_malloc_ptr malloc_fn, png_free_ptr free_fn), + PNG_ALLOCATED); +#endif + +/* Write the PNG file signature. */ +PNG_EXPORT(13, void, png_write_sig, (png_structrp png_ptr)); + +/* Write a PNG chunk - size, type, (optional) data, CRC. */ +PNG_EXPORT(14, void, png_write_chunk, (png_structrp png_ptr, png_const_bytep + chunk_name, png_const_bytep data, size_t length)); + +/* Write the start of a PNG chunk - length and chunk name. */ +PNG_EXPORT(15, void, png_write_chunk_start, (png_structrp png_ptr, + png_const_bytep chunk_name, png_uint_32 length)); + +/* Write the data of a PNG chunk started with png_write_chunk_start(). */ +PNG_EXPORT(16, void, png_write_chunk_data, (png_structrp png_ptr, + png_const_bytep data, size_t length)); + +/* Finish a chunk started with png_write_chunk_start() (includes CRC). */ +PNG_EXPORT(17, void, png_write_chunk_end, (png_structrp png_ptr)); + +/* Allocate and initialize the info structure */ +PNG_EXPORTA(18, png_infop, png_create_info_struct, (png_const_structrp png_ptr), + PNG_ALLOCATED); + +/* DEPRECATED: this function allowed init structures to be created using the + * default allocation method (typically malloc). Use is deprecated in 1.6.0 and + * the API will be removed in the future. + */ +PNG_EXPORTA(19, void, png_info_init_3, (png_infopp info_ptr, + size_t png_info_struct_size), PNG_DEPRECATED); + +/* Writes all the PNG information before the image. */ +PNG_EXPORT(20, void, png_write_info_before_PLTE, + (png_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(21, void, png_write_info, + (png_structrp png_ptr, png_const_inforp info_ptr)); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the information before the actual image data. */ +PNG_EXPORT(22, void, png_read_info, + (png_structrp png_ptr, png_inforp info_ptr)); +#endif + +#ifdef PNG_TIME_RFC1123_SUPPORTED + /* Convert to a US string format: there is no localization support in this + * routine. The original implementation used a 29 character buffer in + * png_struct, this will be removed in future versions. + */ +#if PNG_LIBPNG_VER < 10700 +/* To do: remove this from libpng17 (and from libpng17/png.c and pngstruct.h) */ +PNG_EXPORTA(23, png_const_charp, png_convert_to_rfc1123, (png_structrp png_ptr, + png_const_timep ptime),PNG_DEPRECATED); +#endif +PNG_EXPORT(241, int, png_convert_to_rfc1123_buffer, (char out[29], + png_const_timep ptime)); +#endif + +#ifdef PNG_CONVERT_tIME_SUPPORTED +/* Convert from a struct tm to png_time */ +PNG_EXPORT(24, void, png_convert_from_struct_tm, (png_timep ptime, + const struct tm * ttime)); + +/* Convert from time_t to png_time. Uses gmtime() */ +PNG_EXPORT(25, void, png_convert_from_time_t, (png_timep ptime, time_t ttime)); +#endif /* CONVERT_tIME */ + +#ifdef PNG_READ_EXPAND_SUPPORTED +/* Expand data to 24-bit RGB, or 8-bit grayscale, with alpha if available. */ +PNG_EXPORT(26, void, png_set_expand, (png_structrp png_ptr)); +PNG_EXPORT(27, void, png_set_expand_gray_1_2_4_to_8, (png_structrp png_ptr)); +PNG_EXPORT(28, void, png_set_palette_to_rgb, (png_structrp png_ptr)); +PNG_EXPORT(29, void, png_set_tRNS_to_alpha, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED +/* Expand to 16-bit channels, forces conversion of palette to RGB and expansion + * of a tRNS chunk if present. + */ +PNG_EXPORT(221, void, png_set_expand_16, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +/* Use blue, green, red order for pixels. */ +PNG_EXPORT(30, void, png_set_bgr, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +/* Expand the grayscale to 24-bit RGB if necessary. */ +PNG_EXPORT(31, void, png_set_gray_to_rgb, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +/* Reduce RGB to grayscale. */ +#define PNG_ERROR_ACTION_NONE 1 +#define PNG_ERROR_ACTION_WARN 2 +#define PNG_ERROR_ACTION_ERROR 3 +#define PNG_RGB_TO_GRAY_DEFAULT (-1)/*for red/green coefficients*/ + +PNG_FP_EXPORT(32, void, png_set_rgb_to_gray, (png_structrp png_ptr, + int error_action, double red, double green)) +PNG_FIXED_EXPORT(33, void, png_set_rgb_to_gray_fixed, (png_structrp png_ptr, + int error_action, png_fixed_point red, png_fixed_point green)) + +PNG_EXPORT(34, png_byte, png_get_rgb_to_gray_status, (png_const_structrp + png_ptr)); +#endif + +#ifdef PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +PNG_EXPORT(35, void, png_build_grayscale_palette, (int bit_depth, + png_colorp palette)); +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +/* How the alpha channel is interpreted - this affects how the color channels + * of a PNG file are returned to the calling application when an alpha channel, + * or a tRNS chunk in a palette file, is present. + * + * This has no effect on the way pixels are written into a PNG output + * datastream. The color samples in a PNG datastream are never premultiplied + * with the alpha samples. + * + * The default is to return data according to the PNG specification: the alpha + * channel is a linear measure of the contribution of the pixel to the + * corresponding composited pixel, and the color channels are unassociated + * (not premultiplied). The gamma encoded color channels must be scaled + * according to the contribution and to do this it is necessary to undo + * the encoding, scale the color values, perform the composition and re-encode + * the values. This is the 'PNG' mode. + * + * The alternative is to 'associate' the alpha with the color information by + * storing color channel values that have been scaled by the alpha. + * image. These are the 'STANDARD', 'ASSOCIATED' or 'PREMULTIPLIED' modes + * (the latter being the two common names for associated alpha color channels). + * + * For the 'OPTIMIZED' mode, a pixel is treated as opaque only if the alpha + * value is equal to the maximum value. + * + * The final choice is to gamma encode the alpha channel as well. This is + * broken because, in practice, no implementation that uses this choice + * correctly undoes the encoding before handling alpha composition. Use this + * choice only if other serious errors in the software or hardware you use + * mandate it; the typical serious error is for dark halos to appear around + * opaque areas of the composited PNG image because of arithmetic overflow. + * + * The API function png_set_alpha_mode specifies which of these choices to use + * with an enumerated 'mode' value and the gamma of the required output: + */ +#define PNG_ALPHA_PNG 0 /* according to the PNG standard */ +#define PNG_ALPHA_STANDARD 1 /* according to Porter/Duff */ +#define PNG_ALPHA_ASSOCIATED 1 /* as above; this is the normal practice */ +#define PNG_ALPHA_PREMULTIPLIED 1 /* as above */ +#define PNG_ALPHA_OPTIMIZED 2 /* 'PNG' for opaque pixels, else 'STANDARD' */ +#define PNG_ALPHA_BROKEN 3 /* the alpha channel is gamma encoded */ + +PNG_FP_EXPORT(227, void, png_set_alpha_mode, (png_structrp png_ptr, int mode, + double output_gamma)) +PNG_FIXED_EXPORT(228, void, png_set_alpha_mode_fixed, (png_structrp png_ptr, + int mode, png_fixed_point output_gamma)) +#endif + +#if defined(PNG_GAMMA_SUPPORTED) || defined(PNG_READ_ALPHA_MODE_SUPPORTED) +/* The output_gamma value is a screen gamma in libpng terminology: it expresses + * how to decode the output values, not how they are encoded. + */ +#define PNG_DEFAULT_sRGB -1 /* sRGB gamma and color space */ +#define PNG_GAMMA_MAC_18 -2 /* Old Mac '1.8' gamma and color space */ +#define PNG_GAMMA_sRGB 220000 /* Television standards--matches sRGB gamma */ +#define PNG_GAMMA_LINEAR PNG_FP_1 /* Linear */ +#endif + +/* The following are examples of calls to png_set_alpha_mode to achieve the + * required overall gamma correction and, where necessary, alpha + * premultiplication. + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); + * This is the default libpng handling of the alpha channel - it is not + * pre-multiplied into the color components. In addition the call states + * that the output is for a sRGB system and causes all PNG files without gAMA + * chunks to be assumed to be encoded using sRGB. + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC); + * In this case the output is assumed to be something like an sRGB conformant + * display preceded by a power-law lookup table of power 1.45. This is how + * early Mac systems behaved. + * + * png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_GAMMA_LINEAR); + * This is the classic Jim Blinn approach and will work in academic + * environments where everything is done by the book. It has the shortcoming + * of assuming that input PNG data with no gamma information is linear - this + * is unlikely to be correct unless the PNG files where generated locally. + * Most of the time the output precision will be so low as to show + * significant banding in dark areas of the image. + * + * png_set_expand_16(pp); + * png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_DEFAULT_sRGB); + * This is a somewhat more realistic Jim Blinn inspired approach. PNG files + * are assumed to have the sRGB encoding if not marked with a gamma value and + * the output is always 16 bits per component. This permits accurate scaling + * and processing of the data. If you know that your input PNG files were + * generated locally you might need to replace PNG_DEFAULT_sRGB with the + * correct value for your system. + * + * png_set_alpha_mode(pp, PNG_ALPHA_OPTIMIZED, PNG_DEFAULT_sRGB); + * If you just need to composite the PNG image onto an existing background + * and if you control the code that does this you can use the optimization + * setting. In this case you just copy completely opaque pixels to the + * output. For pixels that are not completely transparent (you just skip + * those) you do the composition math using png_composite or png_composite_16 + * below then encode the resultant 8-bit or 16-bit values to match the output + * encoding. + * + * Other cases + * If neither the PNG nor the standard linear encoding work for you because + * of the software or hardware you use then you have a big problem. The PNG + * case will probably result in halos around the image. The linear encoding + * will probably result in a washed out, too bright, image (it's actually too + * contrasty.) Try the ALPHA_OPTIMIZED mode above - this will probably + * substantially reduce the halos. Alternatively try: + * + * png_set_alpha_mode(pp, PNG_ALPHA_BROKEN, PNG_DEFAULT_sRGB); + * This option will also reduce the halos, but there will be slight dark + * halos round the opaque parts of the image where the background is light. + * In the OPTIMIZED mode the halos will be light halos where the background + * is dark. Take your pick - the halos are unavoidable unless you can get + * your hardware/software fixed! (The OPTIMIZED approach is slightly + * faster.) + * + * When the default gamma of PNG files doesn't match the output gamma. + * If you have PNG files with no gamma information png_set_alpha_mode allows + * you to provide a default gamma, but it also sets the output gamma to the + * matching value. If you know your PNG files have a gamma that doesn't + * match the output you can take advantage of the fact that + * png_set_alpha_mode always sets the output gamma but only sets the PNG + * default if it is not already set: + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC); + * The first call sets both the default and the output gamma values, the + * second call overrides the output gamma without changing the default. This + * is easier than achieving the same effect with png_set_gamma. You must use + * PNG_ALPHA_PNG for the first call - internal checking in png_set_alpha will + * fire if more than one call to png_set_alpha_mode and png_set_background is + * made in the same read operation, however multiple calls with PNG_ALPHA_PNG + * are ignored. + */ + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED +PNG_EXPORT(36, void, png_set_strip_alpha, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_SWAP_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED) +PNG_EXPORT(37, void, png_set_swap_alpha, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_INVERT_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED) +PNG_EXPORT(38, void, png_set_invert_alpha, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED) +/* Add a filler byte to 8-bit or 16-bit Gray or 24-bit or 48-bit RGB images. */ +PNG_EXPORT(39, void, png_set_filler, (png_structrp png_ptr, png_uint_32 filler, + int flags)); +/* The values of the PNG_FILLER_ defines should NOT be changed */ +# define PNG_FILLER_BEFORE 0 +# define PNG_FILLER_AFTER 1 +/* Add an alpha byte to 8-bit or 16-bit Gray or 24-bit or 48-bit RGB images. */ +PNG_EXPORT(40, void, png_set_add_alpha, (png_structrp png_ptr, + png_uint_32 filler, int flags)); +#endif /* READ_FILLER || WRITE_FILLER */ + +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +/* Swap bytes in 16-bit depth files. */ +PNG_EXPORT(41, void, png_set_swap, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_PACK_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED) +/* Use 1 byte per pixel in 1, 2, or 4-bit depth files. */ +PNG_EXPORT(42, void, png_set_packing, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED) || \ + defined(PNG_WRITE_PACKSWAP_SUPPORTED) +/* Swap packing order of pixels in bytes. */ +PNG_EXPORT(43, void, png_set_packswap, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED) +/* Converts files to legal bit depths. */ +PNG_EXPORT(44, void, png_set_shift, (png_structrp png_ptr, png_const_color_8p + true_bits)); +#endif + +#if defined(PNG_READ_INTERLACING_SUPPORTED) || \ + defined(PNG_WRITE_INTERLACING_SUPPORTED) +/* Have the code handle the interlacing. Returns the number of passes. + * MUST be called before png_read_update_info or png_start_read_image, + * otherwise it will not have the desired effect. Note that it is still + * necessary to call png_read_row or png_read_rows png_get_image_height + * times for each pass. +*/ +PNG_EXPORT(45, int, png_set_interlace_handling, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED) +/* Invert monochrome files */ +PNG_EXPORT(46, void, png_set_invert_mono, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_BACKGROUND_SUPPORTED +/* Handle alpha and tRNS by replacing with a background color. Prior to + * libpng-1.5.4 this API must not be called before the PNG file header has been + * read. Doing so will result in unexpected behavior and possible warnings or + * errors if the PNG file contains a bKGD chunk. + */ +PNG_FP_EXPORT(47, void, png_set_background, (png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, double background_gamma)) +PNG_FIXED_EXPORT(215, void, png_set_background_fixed, (png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, png_fixed_point background_gamma)) +#endif +#ifdef PNG_READ_BACKGROUND_SUPPORTED +# define PNG_BACKGROUND_GAMMA_UNKNOWN 0 +# define PNG_BACKGROUND_GAMMA_SCREEN 1 +# define PNG_BACKGROUND_GAMMA_FILE 2 +# define PNG_BACKGROUND_GAMMA_UNIQUE 3 +#endif + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED +/* Scale a 16-bit depth file down to 8-bit, accurately. */ +PNG_EXPORT(229, void, png_set_scale_16, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED +#define PNG_READ_16_TO_8_SUPPORTED /* Name prior to 1.5.4 */ +/* Strip the second byte of information from a 16-bit depth file. */ +PNG_EXPORT(48, void, png_set_strip_16, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +/* Turn on quantizing, and reduce the palette to the number of colors + * available. + */ +PNG_EXPORT(49, void, png_set_quantize, (png_structrp png_ptr, + png_colorp palette, int num_palette, int maximum_colors, + png_const_uint_16p histogram, int full_quantize)); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* The threshold on gamma processing is configurable but hard-wired into the + * library. The following is the floating point variant. + */ +#define PNG_GAMMA_THRESHOLD (PNG_GAMMA_THRESHOLD_FIXED*.00001) + +/* Handle gamma correction. Screen_gamma=(display_exponent). + * NOTE: this API simply sets the screen and file gamma values. It will + * therefore override the value for gamma in a PNG file if it is called after + * the file header has been read - use with care - call before reading the PNG + * file for best results! + * + * These routines accept the same gamma values as png_set_alpha_mode (described + * above). The PNG_GAMMA_ defines and PNG_DEFAULT_sRGB can be passed to either + * API (floating point or fixed.) Notice, however, that the 'file_gamma' value + * is the inverse of a 'screen gamma' value. + */ +PNG_FP_EXPORT(50, void, png_set_gamma, (png_structrp png_ptr, + double screen_gamma, double override_file_gamma)) +PNG_FIXED_EXPORT(208, void, png_set_gamma_fixed, (png_structrp png_ptr, + png_fixed_point screen_gamma, png_fixed_point override_file_gamma)) +#endif + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +/* Set how many lines between output flushes - 0 for no flushing */ +PNG_EXPORT(51, void, png_set_flush, (png_structrp png_ptr, int nrows)); +/* Flush the current PNG output buffer */ +PNG_EXPORT(52, void, png_write_flush, (png_structrp png_ptr)); +#endif + +/* Optional update palette with requested transformations */ +PNG_EXPORT(53, void, png_start_read_image, (png_structrp png_ptr)); + +/* Optional call to update the users info structure */ +PNG_EXPORT(54, void, png_read_update_info, (png_structrp png_ptr, + png_inforp info_ptr)); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read one or more rows of image data. */ +PNG_EXPORT(55, void, png_read_rows, (png_structrp png_ptr, png_bytepp row, + png_bytepp display_row, png_uint_32 num_rows)); +#endif + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read a row of data. */ +PNG_EXPORT(56, void, png_read_row, (png_structrp png_ptr, png_bytep row, + png_bytep display_row)); +#endif + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the whole image into memory at once. */ +PNG_EXPORT(57, void, png_read_image, (png_structrp png_ptr, png_bytepp image)); +#endif + +/* Write a row of image data */ +PNG_EXPORT(58, void, png_write_row, (png_structrp png_ptr, + png_const_bytep row)); + +/* Write a few rows of image data: (*row) is not written; however, the type + * is declared as writeable to maintain compatibility with previous versions + * of libpng and to allow the 'display_row' array from read_rows to be passed + * unchanged to write_rows. + */ +PNG_EXPORT(59, void, png_write_rows, (png_structrp png_ptr, png_bytepp row, + png_uint_32 num_rows)); + +/* Write the image data */ +PNG_EXPORT(60, void, png_write_image, (png_structrp png_ptr, png_bytepp image)); + +/* Write the end of the PNG file. */ +PNG_EXPORT(61, void, png_write_end, (png_structrp png_ptr, + png_inforp info_ptr)); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the end of the PNG file. */ +PNG_EXPORT(62, void, png_read_end, (png_structrp png_ptr, png_inforp info_ptr)); +#endif + +/* Free any memory associated with the png_info_struct */ +PNG_EXPORT(63, void, png_destroy_info_struct, (png_const_structrp png_ptr, + png_infopp info_ptr_ptr)); + +/* Free any memory associated with the png_struct and the png_info_structs */ +PNG_EXPORT(64, void, png_destroy_read_struct, (png_structpp png_ptr_ptr, + png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr)); + +/* Free any memory associated with the png_struct and the png_info_structs */ +PNG_EXPORT(65, void, png_destroy_write_struct, (png_structpp png_ptr_ptr, + png_infopp info_ptr_ptr)); + +/* Set the libpng method of handling chunk CRC errors */ +PNG_EXPORT(66, void, png_set_crc_action, (png_structrp png_ptr, int crit_action, + int ancil_action)); + +/* Values for png_set_crc_action() say how to handle CRC errors in + * ancillary and critical chunks, and whether to use the data contained + * therein. Note that it is impossible to "discard" data in a critical + * chunk. For versions prior to 0.90, the action was always error/quit, + * whereas in version 0.90 and later, the action for CRC errors in ancillary + * chunks is warn/discard. These values should NOT be changed. + * + * value action:critical action:ancillary + */ +#define PNG_CRC_DEFAULT 0 /* error/quit warn/discard data */ +#define PNG_CRC_ERROR_QUIT 1 /* error/quit error/quit */ +#define PNG_CRC_WARN_DISCARD 2 /* (INVALID) warn/discard data */ +#define PNG_CRC_WARN_USE 3 /* warn/use data warn/use data */ +#define PNG_CRC_QUIET_USE 4 /* quiet/use data quiet/use data */ +#define PNG_CRC_NO_CHANGE 5 /* use current value use current value */ + +#ifdef PNG_WRITE_SUPPORTED +/* These functions give the user control over the scan-line filtering in + * libpng and the compression methods used by zlib. These functions are + * mainly useful for testing, as the defaults should work with most users. + * Those users who are tight on memory or want faster performance at the + * expense of compression can modify them. See the compression library + * header file (zlib.h) for an explination of the compression functions. + */ + +/* Set the filtering method(s) used by libpng. Currently, the only valid + * value for "method" is 0. + */ +PNG_EXPORT(67, void, png_set_filter, (png_structrp png_ptr, int method, + int filters)); +#endif /* WRITE */ + +/* Flags for png_set_filter() to say which filters to use. The flags + * are chosen so that they don't conflict with real filter types + * below, in case they are supplied instead of the #defined constants. + * These values should NOT be changed. + */ +#define PNG_NO_FILTERS 0x00 +#define PNG_FILTER_NONE 0x08 +#define PNG_FILTER_SUB 0x10 +#define PNG_FILTER_UP 0x20 +#define PNG_FILTER_AVG 0x40 +#define PNG_FILTER_PAETH 0x80 +#define PNG_FAST_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP) +#define PNG_ALL_FILTERS (PNG_FAST_FILTERS | PNG_FILTER_AVG | PNG_FILTER_PAETH) + +/* Filter values (not flags) - used in pngwrite.c, pngwutil.c for now. + * These defines should NOT be changed. + */ +#define PNG_FILTER_VALUE_NONE 0 +#define PNG_FILTER_VALUE_SUB 1 +#define PNG_FILTER_VALUE_UP 2 +#define PNG_FILTER_VALUE_AVG 3 +#define PNG_FILTER_VALUE_PAETH 4 +#define PNG_FILTER_VALUE_LAST 5 + +#ifdef PNG_WRITE_SUPPORTED +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* DEPRECATED */ +PNG_FP_EXPORT(68, void, png_set_filter_heuristics, (png_structrp png_ptr, + int heuristic_method, int num_weights, png_const_doublep filter_weights, + png_const_doublep filter_costs)) +PNG_FIXED_EXPORT(209, void, png_set_filter_heuristics_fixed, + (png_structrp png_ptr, int heuristic_method, int num_weights, + png_const_fixed_point_p filter_weights, + png_const_fixed_point_p filter_costs)) +#endif /* WRITE_WEIGHTED_FILTER */ + +/* The following are no longer used and will be removed from libpng-1.7: */ +#define PNG_FILTER_HEURISTIC_DEFAULT 0 /* Currently "UNWEIGHTED" */ +#define PNG_FILTER_HEURISTIC_UNWEIGHTED 1 /* Used by libpng < 0.95 */ +#define PNG_FILTER_HEURISTIC_WEIGHTED 2 /* Experimental feature */ +#define PNG_FILTER_HEURISTIC_LAST 3 /* Not a valid value */ + +/* Set the library compression level. Currently, valid values range from + * 0 - 9, corresponding directly to the zlib compression levels 0 - 9 + * (0 - no compression, 9 - "maximal" compression). Note that tests have + * shown that zlib compression levels 3-6 usually perform as well as level 9 + * for PNG images, and do considerably fewer caclulations. In the future, + * these values may not correspond directly to the zlib compression levels. + */ +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +PNG_EXPORT(69, void, png_set_compression_level, (png_structrp png_ptr, + int level)); + +PNG_EXPORT(70, void, png_set_compression_mem_level, (png_structrp png_ptr, + int mem_level)); + +PNG_EXPORT(71, void, png_set_compression_strategy, (png_structrp png_ptr, + int strategy)); + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +PNG_EXPORT(72, void, png_set_compression_window_bits, (png_structrp png_ptr, + int window_bits)); + +PNG_EXPORT(73, void, png_set_compression_method, (png_structrp png_ptr, + int method)); +#endif /* WRITE_CUSTOMIZE_COMPRESSION */ + +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +/* Also set zlib parameters for compressing non-IDAT chunks */ +PNG_EXPORT(222, void, png_set_text_compression_level, (png_structrp png_ptr, + int level)); + +PNG_EXPORT(223, void, png_set_text_compression_mem_level, (png_structrp png_ptr, + int mem_level)); + +PNG_EXPORT(224, void, png_set_text_compression_strategy, (png_structrp png_ptr, + int strategy)); + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +PNG_EXPORT(225, void, png_set_text_compression_window_bits, + (png_structrp png_ptr, int window_bits)); + +PNG_EXPORT(226, void, png_set_text_compression_method, (png_structrp png_ptr, + int method)); +#endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */ +#endif /* WRITE */ + +/* These next functions are called for input/output, memory, and error + * handling. They are in the file pngrio.c, pngwio.c, and pngerror.c, + * and call standard C I/O routines such as fread(), fwrite(), and + * fprintf(). These functions can be made to use other I/O routines + * at run time for those applications that need to handle I/O in a + * different manner by calling png_set_???_fn(). See libpng-manual.txt for + * more information. + */ + +#ifdef PNG_STDIO_SUPPORTED +/* Initialize the input/output for the PNG file to the default functions. */ +PNG_EXPORT(74, void, png_init_io, (png_structrp png_ptr, png_FILE_p fp)); +#endif + +/* Replace the (error and abort), and warning functions with user + * supplied functions. If no messages are to be printed you must still + * write and use replacement functions. The replacement error_fn should + * still do a longjmp to the last setjmp location if you are using this + * method of error handling. If error_fn or warning_fn is NULL, the + * default function will be used. + */ + +PNG_EXPORT(75, void, png_set_error_fn, (png_structrp png_ptr, + png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warning_fn)); + +/* Return the user pointer associated with the error functions */ +PNG_EXPORT(76, png_voidp, png_get_error_ptr, (png_const_structrp png_ptr)); + +/* Replace the default data output functions with a user supplied one(s). + * If buffered output is not used, then output_flush_fn can be set to NULL. + * If PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile time + * output_flush_fn will be ignored (and thus can be NULL). + * It is probably a mistake to use NULL for output_flush_fn if + * write_data_fn is not also NULL unless you have built libpng with + * PNG_WRITE_FLUSH_SUPPORTED undefined, because in this case libpng's + * default flush function, which uses the standard *FILE structure, will + * be used. + */ +PNG_EXPORT(77, void, png_set_write_fn, (png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn)); + +/* Replace the default data input function with a user supplied one. */ +PNG_EXPORT(78, void, png_set_read_fn, (png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr read_data_fn)); + +/* Return the user pointer associated with the I/O functions */ +PNG_EXPORT(79, png_voidp, png_get_io_ptr, (png_const_structrp png_ptr)); + +PNG_EXPORT(80, void, png_set_read_status_fn, (png_structrp png_ptr, + png_read_status_ptr read_row_fn)); + +PNG_EXPORT(81, void, png_set_write_status_fn, (png_structrp png_ptr, + png_write_status_ptr write_row_fn)); + +#ifdef PNG_USER_MEM_SUPPORTED +/* Replace the default memory allocation functions with user supplied one(s). */ +PNG_EXPORT(82, void, png_set_mem_fn, (png_structrp png_ptr, png_voidp mem_ptr, + png_malloc_ptr malloc_fn, png_free_ptr free_fn)); +/* Return the user pointer associated with the memory functions */ +PNG_EXPORT(83, png_voidp, png_get_mem_ptr, (png_const_structrp png_ptr)); +#endif + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED +PNG_EXPORT(84, void, png_set_read_user_transform_fn, (png_structrp png_ptr, + png_user_transform_ptr read_user_transform_fn)); +#endif + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED +PNG_EXPORT(85, void, png_set_write_user_transform_fn, (png_structrp png_ptr, + png_user_transform_ptr write_user_transform_fn)); +#endif + +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +PNG_EXPORT(86, void, png_set_user_transform_info, (png_structrp png_ptr, + png_voidp user_transform_ptr, int user_transform_depth, + int user_transform_channels)); +/* Return the user pointer associated with the user transform functions */ +PNG_EXPORT(87, png_voidp, png_get_user_transform_ptr, + (png_const_structrp png_ptr)); +#endif + +#ifdef PNG_USER_TRANSFORM_INFO_SUPPORTED +/* Return information about the row currently being processed. Note that these + * APIs do not fail but will return unexpected results if called outside a user + * transform callback. Also note that when transforming an interlaced image the + * row number is the row number within the sub-image of the interlace pass, so + * the value will increase to the height of the sub-image (not the full image) + * then reset to 0 for the next pass. + * + * Use PNG_ROW_FROM_PASS_ROW(row, pass) and PNG_COL_FROM_PASS_COL(col, pass) to + * find the output pixel (x,y) given an interlaced sub-image pixel + * (row,col,pass). (See below for these macros.) + */ +PNG_EXPORT(217, png_uint_32, png_get_current_row_number, (png_const_structrp)); +PNG_EXPORT(218, png_byte, png_get_current_pass_number, (png_const_structrp)); +#endif + +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED +/* This callback is called only for *unknown* chunks. If + * PNG_HANDLE_AS_UNKNOWN_SUPPORTED is set then it is possible to set known + * chunks to be treated as unknown, however in this case the callback must do + * any processing required by the chunk (e.g. by calling the appropriate + * png_set_ APIs.) + * + * There is no write support - on write, by default, all the chunks in the + * 'unknown' list are written in the specified position. + * + * The integer return from the callback function is interpreted thus: + * + * negative: An error occurred; png_chunk_error will be called. + * zero: The chunk was not handled, the chunk will be saved. A critical + * chunk will cause an error at this point unless it is to be saved. + * positive: The chunk was handled, libpng will ignore/discard it. + * + * See "INTERACTION WITH USER CHUNK CALLBACKS" below for important notes about + * how this behavior will change in libpng 1.7 + */ +PNG_EXPORT(88, void, png_set_read_user_chunk_fn, (png_structrp png_ptr, + png_voidp user_chunk_ptr, png_user_chunk_ptr read_user_chunk_fn)); +#endif + +#ifdef PNG_USER_CHUNKS_SUPPORTED +PNG_EXPORT(89, png_voidp, png_get_user_chunk_ptr, (png_const_structrp png_ptr)); +#endif + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +/* Sets the function callbacks for the push reader, and a pointer to a + * user-defined structure available to the callback functions. + */ +PNG_EXPORT(90, void, png_set_progressive_read_fn, (png_structrp png_ptr, + png_voidp progressive_ptr, png_progressive_info_ptr info_fn, + png_progressive_row_ptr row_fn, png_progressive_end_ptr end_fn)); + +/* Returns the user pointer associated with the push read functions */ +PNG_EXPORT(91, png_voidp, png_get_progressive_ptr, + (png_const_structrp png_ptr)); + +/* Function to be called when data becomes available */ +PNG_EXPORT(92, void, png_process_data, (png_structrp png_ptr, + png_inforp info_ptr, png_bytep buffer, size_t buffer_size)); + +/* A function which may be called *only* within png_process_data to stop the + * processing of any more data. The function returns the number of bytes + * remaining, excluding any that libpng has cached internally. A subsequent + * call to png_process_data must supply these bytes again. If the argument + * 'save' is set to true the routine will first save all the pending data and + * will always return 0. + */ +PNG_EXPORT(219, size_t, png_process_data_pause, (png_structrp, int save)); + +/* A function which may be called *only* outside (after) a call to + * png_process_data. It returns the number of bytes of data to skip in the + * input. Normally it will return 0, but if it returns a non-zero value the + * application must skip than number of bytes of input data and pass the + * following data to the next call to png_process_data. + */ +PNG_EXPORT(220, png_uint_32, png_process_data_skip, (png_structrp)); + +/* Function that combines rows. 'new_row' is a flag that should come from + * the callback and be non-NULL if anything needs to be done; the library + * stores its own version of the new data internally and ignores the passed + * in value. + */ +PNG_EXPORT(93, void, png_progressive_combine_row, (png_const_structrp png_ptr, + png_bytep old_row, png_const_bytep new_row)); +#endif /* PROGRESSIVE_READ */ + +PNG_EXPORTA(94, png_voidp, png_malloc, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED); +/* Added at libpng version 1.4.0 */ +PNG_EXPORTA(95, png_voidp, png_calloc, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED); + +/* Added at libpng version 1.2.4 */ +PNG_EXPORTA(96, png_voidp, png_malloc_warn, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED); + +/* Frees a pointer allocated by png_malloc() */ +PNG_EXPORT(97, void, png_free, (png_const_structrp png_ptr, png_voidp ptr)); + +/* Free data that was allocated internally */ +PNG_EXPORT(98, void, png_free_data, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_32 free_me, int num)); + +/* Reassign responsibility for freeing existing data, whether allocated + * by libpng or by the application; this works on the png_info structure passed + * in, it does not change the state for other png_info structures. + * + * It is unlikely that this function works correctly as of 1.6.0 and using it + * may result either in memory leaks or double free of allocated data. + */ +PNG_EXPORT(99, void, png_data_freer, (png_const_structrp png_ptr, + png_inforp info_ptr, int freer, png_uint_32 mask)); + +/* Assignments for png_data_freer */ +#define PNG_DESTROY_WILL_FREE_DATA 1 +#define PNG_SET_WILL_FREE_DATA 1 +#define PNG_USER_WILL_FREE_DATA 2 +/* Flags for png_ptr->free_me and info_ptr->free_me */ +#define PNG_FREE_HIST 0x0008U +#define PNG_FREE_ICCP 0x0010U +#define PNG_FREE_SPLT 0x0020U +#define PNG_FREE_ROWS 0x0040U +#define PNG_FREE_PCAL 0x0080U +#define PNG_FREE_SCAL 0x0100U +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +# define PNG_FREE_UNKN 0x0200U +#endif +/* PNG_FREE_LIST 0x0400U removed in 1.6.0 because it is ignored */ +#define PNG_FREE_PLTE 0x1000U +#define PNG_FREE_TRNS 0x2000U +#define PNG_FREE_TEXT 0x4000U +#define PNG_FREE_EXIF 0x8000U /* Added at libpng-1.6.31 */ +#define PNG_FREE_ALL 0xffffU +#define PNG_FREE_MUL 0x4220U /* PNG_FREE_SPLT|PNG_FREE_TEXT|PNG_FREE_UNKN */ + +#ifdef PNG_USER_MEM_SUPPORTED +PNG_EXPORTA(100, png_voidp, png_malloc_default, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED PNG_DEPRECATED); +PNG_EXPORTA(101, void, png_free_default, (png_const_structrp png_ptr, + png_voidp ptr), PNG_DEPRECATED); +#endif + +#ifdef PNG_ERROR_TEXT_SUPPORTED +/* Fatal error in PNG image of libpng - can't continue */ +PNG_EXPORTA(102, void, png_error, (png_const_structrp png_ptr, + png_const_charp error_message), PNG_NORETURN); + +/* The same, but the chunk name is prepended to the error string. */ +PNG_EXPORTA(103, void, png_chunk_error, (png_const_structrp png_ptr, + png_const_charp error_message), PNG_NORETURN); + +#else +/* Fatal error in PNG image of libpng - can't continue */ +PNG_EXPORTA(104, void, png_err, (png_const_structrp png_ptr), PNG_NORETURN); +# define png_error(s1,s2) png_err(s1) +# define png_chunk_error(s1,s2) png_err(s1) +#endif + +#ifdef PNG_WARNINGS_SUPPORTED +/* Non-fatal error in libpng. Can continue, but may have a problem. */ +PNG_EXPORT(105, void, png_warning, (png_const_structrp png_ptr, + png_const_charp warning_message)); + +/* Non-fatal error in libpng, chunk name is prepended to message. */ +PNG_EXPORT(106, void, png_chunk_warning, (png_const_structrp png_ptr, + png_const_charp warning_message)); +#else +# define png_warning(s1,s2) ((void)(s1)) +# define png_chunk_warning(s1,s2) ((void)(s1)) +#endif + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +/* Benign error in libpng. Can continue, but may have a problem. + * User can choose whether to handle as a fatal error or as a warning. */ +PNG_EXPORT(107, void, png_benign_error, (png_const_structrp png_ptr, + png_const_charp warning_message)); + +#ifdef PNG_READ_SUPPORTED +/* Same, chunk name is prepended to message (only during read) */ +PNG_EXPORT(108, void, png_chunk_benign_error, (png_const_structrp png_ptr, + png_const_charp warning_message)); +#endif + +PNG_EXPORT(109, void, png_set_benign_errors, + (png_structrp png_ptr, int allowed)); +#else +# ifdef PNG_ALLOW_BENIGN_ERRORS +# define png_benign_error png_warning +# define png_chunk_benign_error png_chunk_warning +# else +# define png_benign_error png_error +# define png_chunk_benign_error png_chunk_error +# endif +#endif + +/* The png_set_ functions are for storing values in the png_info_struct. + * Similarly, the png_get_ calls are used to read values from the + * png_info_struct, either storing the parameters in the passed variables, or + * setting pointers into the png_info_struct where the data is stored. The + * png_get_ functions return a non-zero value if the data was available + * in info_ptr, or return zero and do not change any of the parameters if the + * data was not available. + * + * These functions should be used instead of directly accessing png_info + * to avoid problems with future changes in the size and internal layout of + * png_info_struct. + */ +/* Returns "flag" if chunk data is valid in info_ptr. */ +PNG_EXPORT(110, png_uint_32, png_get_valid, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 flag)); + +/* Returns number of bytes needed to hold a transformed row. */ +PNG_EXPORT(111, size_t, png_get_rowbytes, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +#ifdef PNG_INFO_IMAGE_SUPPORTED +/* Returns row_pointers, which is an array of pointers to scanlines that was + * returned from png_read_png(). + */ +PNG_EXPORT(112, png_bytepp, png_get_rows, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Set row_pointers, which is an array of pointers to scanlines for use + * by png_write_png(). + */ +PNG_EXPORT(113, void, png_set_rows, (png_const_structrp png_ptr, + png_inforp info_ptr, png_bytepp row_pointers)); +#endif + +/* Returns number of color channels in image. */ +PNG_EXPORT(114, png_byte, png_get_channels, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +#ifdef PNG_EASY_ACCESS_SUPPORTED +/* Returns image width in pixels. */ +PNG_EXPORT(115, png_uint_32, png_get_image_width, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image height in pixels. */ +PNG_EXPORT(116, png_uint_32, png_get_image_height, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image bit_depth. */ +PNG_EXPORT(117, png_byte, png_get_bit_depth, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image color_type. */ +PNG_EXPORT(118, png_byte, png_get_color_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image filter_type. */ +PNG_EXPORT(119, png_byte, png_get_filter_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image interlace_type. */ +PNG_EXPORT(120, png_byte, png_get_interlace_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image compression_type. */ +PNG_EXPORT(121, png_byte, png_get_compression_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image resolution in pixels per meter, from pHYs chunk data. */ +PNG_EXPORT(122, png_uint_32, png_get_pixels_per_meter, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(123, png_uint_32, png_get_x_pixels_per_meter, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(124, png_uint_32, png_get_y_pixels_per_meter, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +/* Returns pixel aspect ratio, computed from pHYs chunk data. */ +PNG_FP_EXPORT(125, float, png_get_pixel_aspect_ratio, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +PNG_FIXED_EXPORT(210, png_fixed_point, png_get_pixel_aspect_ratio_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) + +/* Returns image x, y offset in pixels or microns, from oFFs chunk data. */ +PNG_EXPORT(126, png_int_32, png_get_x_offset_pixels, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(127, png_int_32, png_get_y_offset_pixels, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(128, png_int_32, png_get_x_offset_microns, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(129, png_int_32, png_get_y_offset_microns, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +#endif /* EASY_ACCESS */ + +#ifdef PNG_READ_SUPPORTED +/* Returns pointer to signature string read from PNG header */ +PNG_EXPORT(130, png_const_bytep, png_get_signature, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); +#endif + +#ifdef PNG_bKGD_SUPPORTED +PNG_EXPORT(131, png_uint_32, png_get_bKGD, (png_const_structrp png_ptr, + png_inforp info_ptr, png_color_16p *background)); +#endif + +#ifdef PNG_bKGD_SUPPORTED +PNG_EXPORT(132, void, png_set_bKGD, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_color_16p background)); +#endif + +#ifdef PNG_cHRM_SUPPORTED +PNG_FP_EXPORT(133, png_uint_32, png_get_cHRM, (png_const_structrp png_ptr, + png_const_inforp info_ptr, double *white_x, double *white_y, double *red_x, + double *red_y, double *green_x, double *green_y, double *blue_x, + double *blue_y)) +PNG_FP_EXPORT(230, png_uint_32, png_get_cHRM_XYZ, (png_const_structrp png_ptr, + png_const_inforp info_ptr, double *red_X, double *red_Y, double *red_Z, + double *green_X, double *green_Y, double *green_Z, double *blue_X, + double *blue_Y, double *blue_Z)) +PNG_FIXED_EXPORT(134, png_uint_32, png_get_cHRM_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_white_x, png_fixed_point *int_white_y, + png_fixed_point *int_red_x, png_fixed_point *int_red_y, + png_fixed_point *int_green_x, png_fixed_point *int_green_y, + png_fixed_point *int_blue_x, png_fixed_point *int_blue_y)) +PNG_FIXED_EXPORT(231, png_uint_32, png_get_cHRM_XYZ_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_red_X, png_fixed_point *int_red_Y, + png_fixed_point *int_red_Z, png_fixed_point *int_green_X, + png_fixed_point *int_green_Y, png_fixed_point *int_green_Z, + png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y, + png_fixed_point *int_blue_Z)) +#endif + +#ifdef PNG_cHRM_SUPPORTED +PNG_FP_EXPORT(135, void, png_set_cHRM, (png_const_structrp png_ptr, + png_inforp info_ptr, + double white_x, double white_y, double red_x, double red_y, double green_x, + double green_y, double blue_x, double blue_y)) +PNG_FP_EXPORT(232, void, png_set_cHRM_XYZ, (png_const_structrp png_ptr, + png_inforp info_ptr, double red_X, double red_Y, double red_Z, + double green_X, double green_Y, double green_Z, double blue_X, + double blue_Y, double blue_Z)) +PNG_FIXED_EXPORT(136, void, png_set_cHRM_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, png_fixed_point int_white_x, + png_fixed_point int_white_y, png_fixed_point int_red_x, + png_fixed_point int_red_y, png_fixed_point int_green_x, + png_fixed_point int_green_y, png_fixed_point int_blue_x, + png_fixed_point int_blue_y)) +PNG_FIXED_EXPORT(233, void, png_set_cHRM_XYZ_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, png_fixed_point int_red_X, png_fixed_point int_red_Y, + png_fixed_point int_red_Z, png_fixed_point int_green_X, + png_fixed_point int_green_Y, png_fixed_point int_green_Z, + png_fixed_point int_blue_X, png_fixed_point int_blue_Y, + png_fixed_point int_blue_Z)) +#endif + +#ifdef PNG_eXIf_SUPPORTED +PNG_EXPORT(246, png_uint_32, png_get_eXIf, (png_const_structrp png_ptr, + png_inforp info_ptr, png_bytep *exif)); +PNG_EXPORT(247, void, png_set_eXIf, (png_const_structrp png_ptr, + png_inforp info_ptr, png_bytep exif)); + +PNG_EXPORT(248, png_uint_32, png_get_eXIf_1, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 *num_exif, png_bytep *exif)); +PNG_EXPORT(249, void, png_set_eXIf_1, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_32 num_exif, png_bytep exif)); +#endif + +#ifdef PNG_gAMA_SUPPORTED +PNG_FP_EXPORT(137, png_uint_32, png_get_gAMA, (png_const_structrp png_ptr, + png_const_inforp info_ptr, double *file_gamma)) +PNG_FIXED_EXPORT(138, png_uint_32, png_get_gAMA_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_file_gamma)) +#endif + +#ifdef PNG_gAMA_SUPPORTED +PNG_FP_EXPORT(139, void, png_set_gAMA, (png_const_structrp png_ptr, + png_inforp info_ptr, double file_gamma)) +PNG_FIXED_EXPORT(140, void, png_set_gAMA_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, png_fixed_point int_file_gamma)) +#endif + +#ifdef PNG_hIST_SUPPORTED +PNG_EXPORT(141, png_uint_32, png_get_hIST, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_16p *hist)); +PNG_EXPORT(142, void, png_set_hIST, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_uint_16p hist)); +#endif + +PNG_EXPORT(143, png_uint_32, png_get_IHDR, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 *width, png_uint_32 *height, + int *bit_depth, int *color_type, int *interlace_method, + int *compression_method, int *filter_method)); + +PNG_EXPORT(144, void, png_set_IHDR, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_method, int compression_method, + int filter_method)); + +#ifdef PNG_oFFs_SUPPORTED +PNG_EXPORT(145, png_uint_32, png_get_oFFs, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_int_32 *offset_x, png_int_32 *offset_y, + int *unit_type)); +#endif + +#ifdef PNG_oFFs_SUPPORTED +PNG_EXPORT(146, void, png_set_oFFs, (png_const_structrp png_ptr, + png_inforp info_ptr, png_int_32 offset_x, png_int_32 offset_y, + int unit_type)); +#endif + +#ifdef PNG_pCAL_SUPPORTED +PNG_EXPORT(147, png_uint_32, png_get_pCAL, (png_const_structrp png_ptr, + png_inforp info_ptr, png_charp *purpose, png_int_32 *X0, + png_int_32 *X1, int *type, int *nparams, png_charp *units, + png_charpp *params)); +#endif + +#ifdef PNG_pCAL_SUPPORTED +PNG_EXPORT(148, void, png_set_pCAL, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_charp purpose, png_int_32 X0, png_int_32 X1, + int type, int nparams, png_const_charp units, png_charpp params)); +#endif + +#ifdef PNG_pHYs_SUPPORTED +PNG_EXPORT(149, png_uint_32, png_get_pHYs, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 *res_x, png_uint_32 *res_y, + int *unit_type)); +#endif + +#ifdef PNG_pHYs_SUPPORTED +PNG_EXPORT(150, void, png_set_pHYs, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_32 res_x, png_uint_32 res_y, int unit_type)); +#endif + +PNG_EXPORT(151, png_uint_32, png_get_PLTE, (png_const_structrp png_ptr, + png_inforp info_ptr, png_colorp *palette, int *num_palette)); + +PNG_EXPORT(152, void, png_set_PLTE, (png_structrp png_ptr, + png_inforp info_ptr, png_const_colorp palette, int num_palette)); + +#ifdef PNG_sBIT_SUPPORTED +PNG_EXPORT(153, png_uint_32, png_get_sBIT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_color_8p *sig_bit)); +#endif + +#ifdef PNG_sBIT_SUPPORTED +PNG_EXPORT(154, void, png_set_sBIT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_color_8p sig_bit)); +#endif + +#ifdef PNG_sRGB_SUPPORTED +PNG_EXPORT(155, png_uint_32, png_get_sRGB, (png_const_structrp png_ptr, + png_const_inforp info_ptr, int *file_srgb_intent)); +#endif + +#ifdef PNG_sRGB_SUPPORTED +PNG_EXPORT(156, void, png_set_sRGB, (png_const_structrp png_ptr, + png_inforp info_ptr, int srgb_intent)); +PNG_EXPORT(157, void, png_set_sRGB_gAMA_and_cHRM, (png_const_structrp png_ptr, + png_inforp info_ptr, int srgb_intent)); +#endif + +#ifdef PNG_iCCP_SUPPORTED +PNG_EXPORT(158, png_uint_32, png_get_iCCP, (png_const_structrp png_ptr, + png_inforp info_ptr, png_charpp name, int *compression_type, + png_bytepp profile, png_uint_32 *proflen)); +#endif + +#ifdef PNG_iCCP_SUPPORTED +PNG_EXPORT(159, void, png_set_iCCP, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_charp name, int compression_type, + png_const_bytep profile, png_uint_32 proflen)); +#endif + +#ifdef PNG_sPLT_SUPPORTED +PNG_EXPORT(160, int, png_get_sPLT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_sPLT_tpp entries)); +#endif + +#ifdef PNG_sPLT_SUPPORTED +PNG_EXPORT(161, void, png_set_sPLT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_sPLT_tp entries, int nentries)); +#endif + +#ifdef PNG_TEXT_SUPPORTED +/* png_get_text also returns the number of text chunks in *num_text */ +PNG_EXPORT(162, int, png_get_text, (png_const_structrp png_ptr, + png_inforp info_ptr, png_textp *text_ptr, int *num_text)); +#endif + +/* Note while png_set_text() will accept a structure whose text, + * language, and translated keywords are NULL pointers, the structure + * returned by png_get_text will always contain regular + * zero-terminated C strings. They might be empty strings but + * they will never be NULL pointers. + */ + +#ifdef PNG_TEXT_SUPPORTED +PNG_EXPORT(163, void, png_set_text, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_textp text_ptr, int num_text)); +#endif + +#ifdef PNG_tIME_SUPPORTED +PNG_EXPORT(164, png_uint_32, png_get_tIME, (png_const_structrp png_ptr, + png_inforp info_ptr, png_timep *mod_time)); +#endif + +#ifdef PNG_tIME_SUPPORTED +PNG_EXPORT(165, void, png_set_tIME, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_timep mod_time)); +#endif + +#ifdef PNG_tRNS_SUPPORTED +PNG_EXPORT(166, png_uint_32, png_get_tRNS, (png_const_structrp png_ptr, + png_inforp info_ptr, png_bytep *trans_alpha, int *num_trans, + png_color_16p *trans_color)); +#endif + +#ifdef PNG_tRNS_SUPPORTED +PNG_EXPORT(167, void, png_set_tRNS, (png_structrp png_ptr, + png_inforp info_ptr, png_const_bytep trans_alpha, int num_trans, + png_const_color_16p trans_color)); +#endif + +#ifdef PNG_sCAL_SUPPORTED +PNG_FP_EXPORT(168, png_uint_32, png_get_sCAL, (png_const_structrp png_ptr, + png_const_inforp info_ptr, int *unit, double *width, double *height)) +#if defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) || \ + defined(PNG_FLOATING_POINT_SUPPORTED) +/* NOTE: this API is currently implemented using floating point arithmetic, + * consequently it can only be used on systems with floating point support. + * In any case the range of values supported by png_fixed_point is small and it + * is highly recommended that png_get_sCAL_s be used instead. + */ +PNG_FIXED_EXPORT(214, png_uint_32, png_get_sCAL_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit, + png_fixed_point *width, png_fixed_point *height)) +#endif +PNG_EXPORT(169, png_uint_32, png_get_sCAL_s, + (png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit, + png_charpp swidth, png_charpp sheight)); + +PNG_FP_EXPORT(170, void, png_set_sCAL, (png_const_structrp png_ptr, + png_inforp info_ptr, int unit, double width, double height)) +PNG_FIXED_EXPORT(213, void, png_set_sCAL_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, int unit, png_fixed_point width, + png_fixed_point height)) +PNG_EXPORT(171, void, png_set_sCAL_s, (png_const_structrp png_ptr, + png_inforp info_ptr, int unit, + png_const_charp swidth, png_const_charp sheight)); +#endif /* sCAL */ + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +/* Provide the default handling for all unknown chunks or, optionally, for + * specific unknown chunks. + * + * NOTE: prior to 1.6.0 the handling specified for particular chunks on read was + * ignored and the default was used, the per-chunk setting only had an effect on + * write. If you wish to have chunk-specific handling on read in code that must + * work on earlier versions you must use a user chunk callback to specify the + * desired handling (keep or discard.) + * + * The 'keep' parameter is a PNG_HANDLE_CHUNK_ value as listed below. The + * parameter is interpreted as follows: + * + * READ: + * PNG_HANDLE_CHUNK_AS_DEFAULT: + * Known chunks: do normal libpng processing, do not keep the chunk (but + * see the comments below about PNG_HANDLE_AS_UNKNOWN_SUPPORTED) + * Unknown chunks: for a specific chunk use the global default, when used + * as the default discard the chunk data. + * PNG_HANDLE_CHUNK_NEVER: + * Discard the chunk data. + * PNG_HANDLE_CHUNK_IF_SAFE: + * Keep the chunk data if the chunk is not critical else raise a chunk + * error. + * PNG_HANDLE_CHUNK_ALWAYS: + * Keep the chunk data. + * + * If the chunk data is saved it can be retrieved using png_get_unknown_chunks, + * below. Notice that specifying "AS_DEFAULT" as a global default is equivalent + * to specifying "NEVER", however when "AS_DEFAULT" is used for specific chunks + * it simply resets the behavior to the libpng default. + * + * INTERACTION WITH USER CHUNK CALLBACKS: + * The per-chunk handling is always used when there is a png_user_chunk_ptr + * callback and the callback returns 0; the chunk is then always stored *unless* + * it is critical and the per-chunk setting is other than ALWAYS. Notice that + * the global default is *not* used in this case. (In effect the per-chunk + * value is incremented to at least IF_SAFE.) + * + * IMPORTANT NOTE: this behavior will change in libpng 1.7 - the global and + * per-chunk defaults will be honored. If you want to preserve the current + * behavior when your callback returns 0 you must set PNG_HANDLE_CHUNK_IF_SAFE + * as the default - if you don't do this libpng 1.6 will issue a warning. + * + * If you want unhandled unknown chunks to be discarded in libpng 1.6 and + * earlier simply return '1' (handled). + * + * PNG_HANDLE_AS_UNKNOWN_SUPPORTED: + * If this is *not* set known chunks will always be handled by libpng and + * will never be stored in the unknown chunk list. Known chunks listed to + * png_set_keep_unknown_chunks will have no effect. If it is set then known + * chunks listed with a keep other than AS_DEFAULT will *never* be processed + * by libpng, in addition critical chunks must either be processed by the + * callback or saved. + * + * The IHDR and IEND chunks must not be listed. Because this turns off the + * default handling for chunks that would otherwise be recognized the + * behavior of libpng transformations may well become incorrect! + * + * WRITE: + * When writing chunks the options only apply to the chunks specified by + * png_set_unknown_chunks (below), libpng will *always* write known chunks + * required by png_set_ calls and will always write the core critical chunks + * (as required for PLTE). + * + * Each chunk in the png_set_unknown_chunks list is looked up in the + * png_set_keep_unknown_chunks list to find the keep setting, this is then + * interpreted as follows: + * + * PNG_HANDLE_CHUNK_AS_DEFAULT: + * Write safe-to-copy chunks and write other chunks if the global + * default is set to _ALWAYS, otherwise don't write this chunk. + * PNG_HANDLE_CHUNK_NEVER: + * Do not write the chunk. + * PNG_HANDLE_CHUNK_IF_SAFE: + * Write the chunk if it is safe-to-copy, otherwise do not write it. + * PNG_HANDLE_CHUNK_ALWAYS: + * Write the chunk. + * + * Note that the default behavior is effectively the opposite of the read case - + * in read unknown chunks are not stored by default, in write they are written + * by default. Also the behavior of PNG_HANDLE_CHUNK_IF_SAFE is very different + * - on write the safe-to-copy bit is checked, on read the critical bit is + * checked and on read if the chunk is critical an error will be raised. + * + * num_chunks: + * =========== + * If num_chunks is positive, then the "keep" parameter specifies the manner + * for handling only those chunks appearing in the chunk_list array, + * otherwise the chunk list array is ignored. + * + * If num_chunks is 0 the "keep" parameter specifies the default behavior for + * unknown chunks, as described above. + * + * If num_chunks is negative, then the "keep" parameter specifies the manner + * for handling all unknown chunks plus all chunks recognized by libpng + * except for the IHDR, PLTE, tRNS, IDAT, and IEND chunks (which continue to + * be processed by libpng. + */ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +PNG_EXPORT(172, void, png_set_keep_unknown_chunks, (png_structrp png_ptr, + int keep, png_const_bytep chunk_list, int num_chunks)); +#endif /* HANDLE_AS_UNKNOWN */ + +/* The "keep" PNG_HANDLE_CHUNK_ parameter for the specified chunk is returned; + * the result is therefore true (non-zero) if special handling is required, + * false for the default handling. + */ +PNG_EXPORT(173, int, png_handle_as_unknown, (png_const_structrp png_ptr, + png_const_bytep chunk_name)); +#endif /* SET_UNKNOWN_CHUNKS */ + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +PNG_EXPORT(174, void, png_set_unknown_chunks, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_unknown_chunkp unknowns, + int num_unknowns)); + /* NOTE: prior to 1.6.0 this routine set the 'location' field of the added + * unknowns to the location currently stored in the png_struct. This is + * invariably the wrong value on write. To fix this call the following API + * for each chunk in the list with the correct location. If you know your + * code won't be compiled on earlier versions you can rely on + * png_set_unknown_chunks(write-ptr, png_get_unknown_chunks(read-ptr)) doing + * the correct thing. + */ + +PNG_EXPORT(175, void, png_set_unknown_chunk_location, + (png_const_structrp png_ptr, png_inforp info_ptr, int chunk, int location)); + +PNG_EXPORT(176, int, png_get_unknown_chunks, (png_const_structrp png_ptr, + png_inforp info_ptr, png_unknown_chunkpp entries)); +#endif + +/* Png_free_data() will turn off the "valid" flag for anything it frees. + * If you need to turn it off for a chunk that your application has freed, + * you can use png_set_invalid(png_ptr, info_ptr, PNG_INFO_CHNK); + */ +PNG_EXPORT(177, void, png_set_invalid, (png_const_structrp png_ptr, + png_inforp info_ptr, int mask)); + +#ifdef PNG_INFO_IMAGE_SUPPORTED +/* The "params" pointer is currently not used and is for future expansion. */ +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +PNG_EXPORT(178, void, png_read_png, (png_structrp png_ptr, png_inforp info_ptr, + int transforms, png_voidp params)); +#endif +#ifdef PNG_WRITE_SUPPORTED +PNG_EXPORT(179, void, png_write_png, (png_structrp png_ptr, png_inforp info_ptr, + int transforms, png_voidp params)); +#endif +#endif + +PNG_EXPORT(180, png_const_charp, png_get_copyright, + (png_const_structrp png_ptr)); +PNG_EXPORT(181, png_const_charp, png_get_header_ver, + (png_const_structrp png_ptr)); +PNG_EXPORT(182, png_const_charp, png_get_header_version, + (png_const_structrp png_ptr)); +PNG_EXPORT(183, png_const_charp, png_get_libpng_ver, + (png_const_structrp png_ptr)); + +#ifdef PNG_MNG_FEATURES_SUPPORTED +PNG_EXPORT(184, png_uint_32, png_permit_mng_features, (png_structrp png_ptr, + png_uint_32 mng_features_permitted)); +#endif + +/* For use in png_set_keep_unknown, added to version 1.2.6 */ +#define PNG_HANDLE_CHUNK_AS_DEFAULT 0 +#define PNG_HANDLE_CHUNK_NEVER 1 +#define PNG_HANDLE_CHUNK_IF_SAFE 2 +#define PNG_HANDLE_CHUNK_ALWAYS 3 +#define PNG_HANDLE_CHUNK_LAST 4 + +/* Strip the prepended error numbers ("#nnn ") from error and warning + * messages before passing them to the error or warning handler. + */ +#ifdef PNG_ERROR_NUMBERS_SUPPORTED +PNG_EXPORT(185, void, png_set_strip_error_numbers, (png_structrp png_ptr, + png_uint_32 strip_mode)); +#endif + +/* Added in libpng-1.2.6 */ +#ifdef PNG_SET_USER_LIMITS_SUPPORTED +PNG_EXPORT(186, void, png_set_user_limits, (png_structrp png_ptr, + png_uint_32 user_width_max, png_uint_32 user_height_max)); +PNG_EXPORT(187, png_uint_32, png_get_user_width_max, + (png_const_structrp png_ptr)); +PNG_EXPORT(188, png_uint_32, png_get_user_height_max, + (png_const_structrp png_ptr)); +/* Added in libpng-1.4.0 */ +PNG_EXPORT(189, void, png_set_chunk_cache_max, (png_structrp png_ptr, + png_uint_32 user_chunk_cache_max)); +PNG_EXPORT(190, png_uint_32, png_get_chunk_cache_max, + (png_const_structrp png_ptr)); +/* Added in libpng-1.4.1 */ +PNG_EXPORT(191, void, png_set_chunk_malloc_max, (png_structrp png_ptr, + png_alloc_size_t user_chunk_cache_max)); +PNG_EXPORT(192, png_alloc_size_t, png_get_chunk_malloc_max, + (png_const_structrp png_ptr)); +#endif + +#if defined(PNG_INCH_CONVERSIONS_SUPPORTED) +PNG_EXPORT(193, png_uint_32, png_get_pixels_per_inch, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +PNG_EXPORT(194, png_uint_32, png_get_x_pixels_per_inch, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +PNG_EXPORT(195, png_uint_32, png_get_y_pixels_per_inch, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +PNG_FP_EXPORT(196, float, png_get_x_offset_inches, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +#ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */ +PNG_FIXED_EXPORT(211, png_fixed_point, png_get_x_offset_inches_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +#endif + +PNG_FP_EXPORT(197, float, png_get_y_offset_inches, (png_const_structrp png_ptr, + png_const_inforp info_ptr)) +#ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */ +PNG_FIXED_EXPORT(212, png_fixed_point, png_get_y_offset_inches_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +#endif + +# ifdef PNG_pHYs_SUPPORTED +PNG_EXPORT(198, png_uint_32, png_get_pHYs_dpi, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 *res_x, png_uint_32 *res_y, + int *unit_type)); +# endif /* pHYs */ +#endif /* INCH_CONVERSIONS */ + +/* Added in libpng-1.4.0 */ +#ifdef PNG_IO_STATE_SUPPORTED +PNG_EXPORT(199, png_uint_32, png_get_io_state, (png_const_structrp png_ptr)); + +/* Removed from libpng 1.6; use png_get_io_chunk_type. */ +PNG_REMOVED(200, png_const_bytep, png_get_io_chunk_name, (png_structrp png_ptr), + PNG_DEPRECATED) + +PNG_EXPORT(216, png_uint_32, png_get_io_chunk_type, + (png_const_structrp png_ptr)); + +/* The flags returned by png_get_io_state() are the following: */ +# define PNG_IO_NONE 0x0000 /* no I/O at this moment */ +# define PNG_IO_READING 0x0001 /* currently reading */ +# define PNG_IO_WRITING 0x0002 /* currently writing */ +# define PNG_IO_SIGNATURE 0x0010 /* currently at the file signature */ +# define PNG_IO_CHUNK_HDR 0x0020 /* currently at the chunk header */ +# define PNG_IO_CHUNK_DATA 0x0040 /* currently at the chunk data */ +# define PNG_IO_CHUNK_CRC 0x0080 /* currently at the chunk crc */ +# define PNG_IO_MASK_OP 0x000f /* current operation: reading/writing */ +# define PNG_IO_MASK_LOC 0x00f0 /* current location: sig/hdr/data/crc */ +#endif /* IO_STATE */ + +/* Interlace support. The following macros are always defined so that if + * libpng interlace handling is turned off the macros may be used to handle + * interlaced images within the application. + */ +#define PNG_INTERLACE_ADAM7_PASSES 7 + +/* Two macros to return the first row and first column of the original, + * full, image which appears in a given pass. 'pass' is in the range 0 + * to 6 and the result is in the range 0 to 7. + */ +#define PNG_PASS_START_ROW(pass) (((1&~(pass))<<(3-((pass)>>1)))&7) +#define PNG_PASS_START_COL(pass) (((1& (pass))<<(3-(((pass)+1)>>1)))&7) + +/* A macro to return the offset between pixels in the output row for a pair of + * pixels in the input - effectively the inverse of the 'COL_SHIFT' macro that + * follows. Note that ROW_OFFSET is the offset from one row to the next whereas + * COL_OFFSET is from one column to the next, within a row. + */ +#define PNG_PASS_ROW_OFFSET(pass) ((pass)>2?(8>>(((pass)-1)>>1)):8) +#define PNG_PASS_COL_OFFSET(pass) (1<<((7-(pass))>>1)) + +/* Two macros to help evaluate the number of rows or columns in each + * pass. This is expressed as a shift - effectively log2 of the number or + * rows or columns in each 8x8 tile of the original image. + */ +#define PNG_PASS_ROW_SHIFT(pass) ((pass)>2?(8-(pass))>>1:3) +#define PNG_PASS_COL_SHIFT(pass) ((pass)>1?(7-(pass))>>1:3) + +/* Hence two macros to determine the number of rows or columns in a given + * pass of an image given its height or width. In fact these macros may + * return non-zero even though the sub-image is empty, because the other + * dimension may be empty for a small image. + */ +#define PNG_PASS_ROWS(height, pass) (((height)+(((1<>PNG_PASS_ROW_SHIFT(pass)) +#define PNG_PASS_COLS(width, pass) (((width)+(((1<>PNG_PASS_COL_SHIFT(pass)) + +/* For the reader row callbacks (both progressive and sequential) it is + * necessary to find the row in the output image given a row in an interlaced + * image, so two more macros: + */ +#define PNG_ROW_FROM_PASS_ROW(y_in, pass) \ + (((y_in)<>(((7-(off))-(pass))<<2)) & 0xF) | \ + ((0x01145AF0>>(((7-(off))-(pass))<<2)) & 0xF0)) + +#define PNG_ROW_IN_INTERLACE_PASS(y, pass) \ + ((PNG_PASS_MASK(pass,0) >> ((y)&7)) & 1) +#define PNG_COL_IN_INTERLACE_PASS(x, pass) \ + ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1) + +#ifdef PNG_READ_COMPOSITE_NODIV_SUPPORTED +/* With these routines we avoid an integer divide, which will be slower on + * most machines. However, it does take more operations than the corresponding + * divide method, so it may be slower on a few RISC systems. There are two + * shifts (by 8 or 16 bits) and an addition, versus a single integer divide. + * + * Note that the rounding factors are NOT supposed to be the same! 128 and + * 32768 are correct for the NODIV code; 127 and 32767 are correct for the + * standard method. + * + * [Optimized code by Greg Roelofs and Mark Adler...blame us for bugs. :-) ] + */ + + /* fg and bg should be in `gamma 1.0' space; alpha is the opacity */ + +# define png_composite(composite, fg, alpha, bg) \ + { \ + png_uint_16 temp = (png_uint_16)((png_uint_16)(fg) \ + * (png_uint_16)(alpha) \ + + (png_uint_16)(bg)*(png_uint_16)(255 \ + - (png_uint_16)(alpha)) + 128); \ + (composite) = (png_byte)(((temp + (temp >> 8)) >> 8) & 0xff); \ + } + +# define png_composite_16(composite, fg, alpha, bg) \ + { \ + png_uint_32 temp = (png_uint_32)((png_uint_32)(fg) \ + * (png_uint_32)(alpha) \ + + (png_uint_32)(bg)*(65535 \ + - (png_uint_32)(alpha)) + 32768); \ + (composite) = (png_uint_16)(0xffff & ((temp + (temp >> 16)) >> 16)); \ + } + +#else /* Standard method using integer division */ + +# define png_composite(composite, fg, alpha, bg) \ + (composite) = \ + (png_byte)(0xff & (((png_uint_16)(fg) * (png_uint_16)(alpha) + \ + (png_uint_16)(bg) * (png_uint_16)(255 - (png_uint_16)(alpha)) + \ + 127) / 255)) + +# define png_composite_16(composite, fg, alpha, bg) \ + (composite) = \ + (png_uint_16)(0xffff & (((png_uint_32)(fg) * (png_uint_32)(alpha) + \ + (png_uint_32)(bg)*(png_uint_32)(65535 - (png_uint_32)(alpha)) + \ + 32767) / 65535)) +#endif /* READ_COMPOSITE_NODIV */ + +#ifdef PNG_READ_INT_FUNCTIONS_SUPPORTED +PNG_EXPORT(201, png_uint_32, png_get_uint_32, (png_const_bytep buf)); +PNG_EXPORT(202, png_uint_16, png_get_uint_16, (png_const_bytep buf)); +PNG_EXPORT(203, png_int_32, png_get_int_32, (png_const_bytep buf)); +#endif + +PNG_EXPORT(204, png_uint_32, png_get_uint_31, (png_const_structrp png_ptr, + png_const_bytep buf)); +/* No png_get_int_16 -- may be added if there's a real need for it. */ + +/* Place a 32-bit number into a buffer in PNG byte order (big-endian). */ +#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED +PNG_EXPORT(205, void, png_save_uint_32, (png_bytep buf, png_uint_32 i)); +#endif +#ifdef PNG_SAVE_INT_32_SUPPORTED +PNG_EXPORT(206, void, png_save_int_32, (png_bytep buf, png_int_32 i)); +#endif + +/* Place a 16-bit number into a buffer in PNG byte order. + * The parameter is declared unsigned int, not png_uint_16, + * just to avoid potential problems on pre-ANSI C compilers. + */ +#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED +PNG_EXPORT(207, void, png_save_uint_16, (png_bytep buf, unsigned int i)); +/* No png_save_int_16 -- may be added if there's a real need for it. */ +#endif + +#ifdef PNG_USE_READ_MACROS +/* Inline macros to do direct reads of bytes from the input buffer. + * The png_get_int_32() routine assumes we are using two's complement + * format for negative values, which is almost certainly true. + */ +# define PNG_get_uint_32(buf) \ + (((png_uint_32)(*(buf)) << 24) + \ + ((png_uint_32)(*((buf) + 1)) << 16) + \ + ((png_uint_32)(*((buf) + 2)) << 8) + \ + ((png_uint_32)(*((buf) + 3)))) + + /* From libpng-1.4.0 until 1.4.4, the png_get_uint_16 macro (but not the + * function) incorrectly returned a value of type png_uint_32. + */ +# define PNG_get_uint_16(buf) \ + ((png_uint_16) \ + (((unsigned int)(*(buf)) << 8) + \ + ((unsigned int)(*((buf) + 1))))) + +# define PNG_get_int_32(buf) \ + ((png_int_32)((*(buf) & 0x80) \ + ? -((png_int_32)(((png_get_uint_32(buf)^0xffffffffU)+1U)&0x7fffffffU)) \ + : (png_int_32)png_get_uint_32(buf))) + +/* If PNG_PREFIX is defined the same thing as below happens in pnglibconf.h, + * but defining a macro name prefixed with PNG_PREFIX. + */ +# ifndef PNG_PREFIX +# define png_get_uint_32(buf) PNG_get_uint_32(buf) +# define png_get_uint_16(buf) PNG_get_uint_16(buf) +# define png_get_int_32(buf) PNG_get_int_32(buf) +# endif +#else +# ifdef PNG_PREFIX + /* No macros; revert to the (redefined) function */ +# define PNG_get_uint_32 (png_get_uint_32) +# define PNG_get_uint_16 (png_get_uint_16) +# define PNG_get_int_32 (png_get_int_32) +# endif +#endif + +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +PNG_EXPORT(242, void, png_set_check_for_invalid_index, + (png_structrp png_ptr, int allowed)); +# ifdef PNG_GET_PALETTE_MAX_SUPPORTED +PNG_EXPORT(243, int, png_get_palette_max, (png_const_structp png_ptr, + png_const_infop info_ptr)); +# endif +#endif /* CHECK_FOR_INVALID_INDEX */ + +/******************************************************************************* + * Section 5: SIMPLIFIED API + ******************************************************************************* + * + * Please read the documentation in libpng-manual.txt (TODO: write said + * documentation) if you don't understand what follows. + * + * The simplified API hides the details of both libpng and the PNG file format + * itself. It allows PNG files to be read into a very limited number of + * in-memory bitmap formats or to be written from the same formats. If these + * formats do not accommodate your needs then you can, and should, use the more + * sophisticated APIs above - these support a wide variety of in-memory formats + * and a wide variety of sophisticated transformations to those formats as well + * as a wide variety of APIs to manipulate ancillary information. + * + * To read a PNG file using the simplified API: + * + * 1) Declare a 'png_image' structure (see below) on the stack, set the + * version field to PNG_IMAGE_VERSION and the 'opaque' pointer to NULL + * (this is REQUIRED, your program may crash if you don't do it.) + * 2) Call the appropriate png_image_begin_read... function. + * 3) Set the png_image 'format' member to the required sample format. + * 4) Allocate a buffer for the image and, if required, the color-map. + * 5) Call png_image_finish_read to read the image and, if required, the + * color-map into your buffers. + * + * There are no restrictions on the format of the PNG input itself; all valid + * color types, bit depths, and interlace methods are acceptable, and the + * input image is transformed as necessary to the requested in-memory format + * during the png_image_finish_read() step. The only caveat is that if you + * request a color-mapped image from a PNG that is full-color or makes + * complex use of an alpha channel the transformation is extremely lossy and the + * result may look terrible. + * + * To write a PNG file using the simplified API: + * + * 1) Declare a 'png_image' structure on the stack and memset() it to all zero. + * 2) Initialize the members of the structure that describe the image, setting + * the 'format' member to the format of the image samples. + * 3) Call the appropriate png_image_write... function with a pointer to the + * image and, if necessary, the color-map to write the PNG data. + * + * png_image is a structure that describes the in-memory format of an image + * when it is being read or defines the in-memory format of an image that you + * need to write: + */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) || \ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) + +#define PNG_IMAGE_VERSION 1 + +typedef struct png_control *png_controlp; +typedef struct +{ + png_controlp opaque; /* Initialize to NULL, free with png_image_free */ + png_uint_32 version; /* Set to PNG_IMAGE_VERSION */ + png_uint_32 width; /* Image width in pixels (columns) */ + png_uint_32 height; /* Image height in pixels (rows) */ + png_uint_32 format; /* Image format as defined below */ + png_uint_32 flags; /* A bit mask containing informational flags */ + png_uint_32 colormap_entries; + /* Number of entries in the color-map */ + + /* In the event of an error or warning the following field will be set to a + * non-zero value and the 'message' field will contain a '\0' terminated + * string with the libpng error or warning message. If both warnings and + * an error were encountered, only the error is recorded. If there + * are multiple warnings, only the first one is recorded. + * + * The upper 30 bits of this value are reserved, the low two bits contain + * a value as follows: + */ +# define PNG_IMAGE_WARNING 1 +# define PNG_IMAGE_ERROR 2 + /* + * The result is a two-bit code such that a value more than 1 indicates + * a failure in the API just called: + * + * 0 - no warning or error + * 1 - warning + * 2 - error + * 3 - error preceded by warning + */ +# define PNG_IMAGE_FAILED(png_cntrl) ((((png_cntrl).warning_or_error)&0x03)>1) + + png_uint_32 warning_or_error; + + char message[64]; +} png_image, *png_imagep; + +/* The samples of the image have one to four channels whose components have + * original values in the range 0 to 1.0: + * + * 1: A single gray or luminance channel (G). + * 2: A gray/luminance channel and an alpha channel (GA). + * 3: Three red, green, blue color channels (RGB). + * 4: Three color channels and an alpha channel (RGBA). + * + * The components are encoded in one of two ways: + * + * a) As a small integer, value 0..255, contained in a single byte. For the + * alpha channel the original value is simply value/255. For the color or + * luminance channels the value is encoded according to the sRGB specification + * and matches the 8-bit format expected by typical display devices. + * + * The color/gray channels are not scaled (pre-multiplied) by the alpha + * channel and are suitable for passing to color management software. + * + * b) As a value in the range 0..65535, contained in a 2-byte integer. All + * channels can be converted to the original value by dividing by 65535; all + * channels are linear. Color channels use the RGB encoding (RGB end-points) of + * the sRGB specification. This encoding is identified by the + * PNG_FORMAT_FLAG_LINEAR flag below. + * + * When the simplified API needs to convert between sRGB and linear colorspaces, + * the actual sRGB transfer curve defined in the sRGB specification (see the + * article at ) is used, not the gamma=1/2.2 + * approximation used elsewhere in libpng. + * + * When an alpha channel is present it is expected to denote pixel coverage + * of the color or luminance channels and is returned as an associated alpha + * channel: the color/gray channels are scaled (pre-multiplied) by the alpha + * value. + * + * The samples are either contained directly in the image data, between 1 and 8 + * bytes per pixel according to the encoding, or are held in a color-map indexed + * by bytes in the image data. In the case of a color-map the color-map entries + * are individual samples, encoded as above, and the image data has one byte per + * pixel to select the relevant sample from the color-map. + */ + +/* PNG_FORMAT_* + * + * #defines to be used in png_image::format. Each #define identifies a + * particular layout of sample data and, if present, alpha values. There are + * separate defines for each of the two component encodings. + * + * A format is built up using single bit flag values. All combinations are + * valid. Formats can be built up from the flag values or you can use one of + * the predefined values below. When testing formats always use the FORMAT_FLAG + * macros to test for individual features - future versions of the library may + * add new flags. + * + * When reading or writing color-mapped images the format should be set to the + * format of the entries in the color-map then png_image_{read,write}_colormap + * called to read or write the color-map and set the format correctly for the + * image data. Do not set the PNG_FORMAT_FLAG_COLORMAP bit directly! + * + * NOTE: libpng can be built with particular features disabled. If you see + * compiler errors because the definition of one of the following flags has been + * compiled out it is because libpng does not have the required support. It is + * possible, however, for the libpng configuration to enable the format on just + * read or just write; in that case you may see an error at run time. You can + * guard against this by checking for the definition of the appropriate + * "_SUPPORTED" macro, one of: + * + * PNG_SIMPLIFIED_{READ,WRITE}_{BGR,AFIRST}_SUPPORTED + */ +#define PNG_FORMAT_FLAG_ALPHA 0x01U /* format with an alpha channel */ +#define PNG_FORMAT_FLAG_COLOR 0x02U /* color format: otherwise grayscale */ +#define PNG_FORMAT_FLAG_LINEAR 0x04U /* 2-byte channels else 1-byte */ +#define PNG_FORMAT_FLAG_COLORMAP 0x08U /* image data is color-mapped */ + +#ifdef PNG_FORMAT_BGR_SUPPORTED +# define PNG_FORMAT_FLAG_BGR 0x10U /* BGR colors, else order is RGB */ +#endif + +#ifdef PNG_FORMAT_AFIRST_SUPPORTED +# define PNG_FORMAT_FLAG_AFIRST 0x20U /* alpha channel comes first */ +#endif + +#define PNG_FORMAT_FLAG_ASSOCIATED_ALPHA 0x40U /* alpha channel is associated */ + +/* Commonly used formats have predefined macros. + * + * First the single byte (sRGB) formats: + */ +#define PNG_FORMAT_GRAY 0 +#define PNG_FORMAT_GA PNG_FORMAT_FLAG_ALPHA +#define PNG_FORMAT_AG (PNG_FORMAT_GA|PNG_FORMAT_FLAG_AFIRST) +#define PNG_FORMAT_RGB PNG_FORMAT_FLAG_COLOR +#define PNG_FORMAT_BGR (PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_BGR) +#define PNG_FORMAT_RGBA (PNG_FORMAT_RGB|PNG_FORMAT_FLAG_ALPHA) +#define PNG_FORMAT_ARGB (PNG_FORMAT_RGBA|PNG_FORMAT_FLAG_AFIRST) +#define PNG_FORMAT_BGRA (PNG_FORMAT_BGR|PNG_FORMAT_FLAG_ALPHA) +#define PNG_FORMAT_ABGR (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_AFIRST) + +/* Then the linear 2-byte formats. When naming these "Y" is used to + * indicate a luminance (gray) channel. + */ +#define PNG_FORMAT_LINEAR_Y PNG_FORMAT_FLAG_LINEAR +#define PNG_FORMAT_LINEAR_Y_ALPHA (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_ALPHA) +#define PNG_FORMAT_LINEAR_RGB (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_COLOR) +#define PNG_FORMAT_LINEAR_RGB_ALPHA \ + (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_ALPHA) + +/* With color-mapped formats the image data is one byte for each pixel, the byte + * is an index into the color-map which is formatted as above. To obtain a + * color-mapped format it is sufficient just to add the PNG_FOMAT_FLAG_COLORMAP + * to one of the above definitions, or you can use one of the definitions below. + */ +#define PNG_FORMAT_RGB_COLORMAP (PNG_FORMAT_RGB|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_BGR_COLORMAP (PNG_FORMAT_BGR|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_RGBA_COLORMAP (PNG_FORMAT_RGBA|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_ARGB_COLORMAP (PNG_FORMAT_ARGB|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_BGRA_COLORMAP (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_ABGR_COLORMAP (PNG_FORMAT_ABGR|PNG_FORMAT_FLAG_COLORMAP) + +/* PNG_IMAGE macros + * + * These are convenience macros to derive information from a png_image + * structure. The PNG_IMAGE_SAMPLE_ macros return values appropriate to the + * actual image sample values - either the entries in the color-map or the + * pixels in the image. The PNG_IMAGE_PIXEL_ macros return corresponding values + * for the pixels and will always return 1 for color-mapped formats. The + * remaining macros return information about the rows in the image and the + * complete image. + * + * NOTE: All the macros that take a png_image::format parameter are compile time + * constants if the format parameter is, itself, a constant. Therefore these + * macros can be used in array declarations and case labels where required. + * Similarly the macros are also pre-processor constants (sizeof is not used) so + * they can be used in #if tests. + * + * First the information about the samples. + */ +#define PNG_IMAGE_SAMPLE_CHANNELS(fmt)\ + (((fmt)&(PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_ALPHA))+1) + /* Return the total number of channels in a given format: 1..4 */ + +#define PNG_IMAGE_SAMPLE_COMPONENT_SIZE(fmt)\ + ((((fmt) & PNG_FORMAT_FLAG_LINEAR) >> 2)+1) + /* Return the size in bytes of a single component of a pixel or color-map + * entry (as appropriate) in the image: 1 or 2. + */ + +#define PNG_IMAGE_SAMPLE_SIZE(fmt)\ + (PNG_IMAGE_SAMPLE_CHANNELS(fmt) * PNG_IMAGE_SAMPLE_COMPONENT_SIZE(fmt)) + /* This is the size of the sample data for one sample. If the image is + * color-mapped it is the size of one color-map entry (and image pixels are + * one byte in size), otherwise it is the size of one image pixel. + */ + +#define PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(fmt)\ + (PNG_IMAGE_SAMPLE_CHANNELS(fmt) * 256) + /* The maximum size of the color-map required by the format expressed in a + * count of components. This can be used to compile-time allocate a + * color-map: + * + * png_uint_16 colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(linear_fmt)]; + * + * png_byte colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(sRGB_fmt)]; + * + * Alternatively use the PNG_IMAGE_COLORMAP_SIZE macro below to use the + * information from one of the png_image_begin_read_ APIs and dynamically + * allocate the required memory. + */ + +/* Corresponding information about the pixels */ +#define PNG_IMAGE_PIXEL_(test,fmt)\ + (((fmt)&PNG_FORMAT_FLAG_COLORMAP)?1:test(fmt)) + +#define PNG_IMAGE_PIXEL_CHANNELS(fmt)\ + PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_CHANNELS,fmt) + /* The number of separate channels (components) in a pixel; 1 for a + * color-mapped image. + */ + +#define PNG_IMAGE_PIXEL_COMPONENT_SIZE(fmt)\ + PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_COMPONENT_SIZE,fmt) + /* The size, in bytes, of each component in a pixel; 1 for a color-mapped + * image. + */ + +#define PNG_IMAGE_PIXEL_SIZE(fmt) PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_SIZE,fmt) + /* The size, in bytes, of a complete pixel; 1 for a color-mapped image. */ + +/* Information about the whole row, or whole image */ +#define PNG_IMAGE_ROW_STRIDE(image)\ + (PNG_IMAGE_PIXEL_CHANNELS((image).format) * (image).width) + /* Return the total number of components in a single row of the image; this + * is the minimum 'row stride', the minimum count of components between each + * row. For a color-mapped image this is the minimum number of bytes in a + * row. + * + * WARNING: this macro overflows for some images with more than one component + * and very large image widths. libpng will refuse to process an image where + * this macro would overflow. + */ + +#define PNG_IMAGE_BUFFER_SIZE(image, row_stride)\ + (PNG_IMAGE_PIXEL_COMPONENT_SIZE((image).format)*(image).height*(row_stride)) + /* Return the size, in bytes, of an image buffer given a png_image and a row + * stride - the number of components to leave space for in each row. + * + * WARNING: this macro overflows a 32-bit integer for some large PNG images, + * libpng will refuse to process an image where such an overflow would occur. + */ + +#define PNG_IMAGE_SIZE(image)\ + PNG_IMAGE_BUFFER_SIZE(image, PNG_IMAGE_ROW_STRIDE(image)) + /* Return the size, in bytes, of the image in memory given just a png_image; + * the row stride is the minimum stride required for the image. + */ + +#define PNG_IMAGE_COLORMAP_SIZE(image)\ + (PNG_IMAGE_SAMPLE_SIZE((image).format) * (image).colormap_entries) + /* Return the size, in bytes, of the color-map of this image. If the image + * format is not a color-map format this will return a size sufficient for + * 256 entries in the given format; check PNG_FORMAT_FLAG_COLORMAP if + * you don't want to allocate a color-map in this case. + */ + +/* PNG_IMAGE_FLAG_* + * + * Flags containing additional information about the image are held in the + * 'flags' field of png_image. + */ +#define PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB 0x01 + /* This indicates that the RGB values of the in-memory bitmap do not + * correspond to the red, green and blue end-points defined by sRGB. + */ + +#define PNG_IMAGE_FLAG_FAST 0x02 + /* On write emphasise speed over compression; the resultant PNG file will be + * larger but will be produced significantly faster, particular for large + * images. Do not use this option for images which will be distributed, only + * used it when producing intermediate files that will be read back in + * repeatedly. For a typical 24-bit image the option will double the read + * speed at the cost of increasing the image size by 25%, however for many + * more compressible images the PNG file can be 10 times larger with only a + * slight speed gain. + */ + +#define PNG_IMAGE_FLAG_16BIT_sRGB 0x04 + /* On read if the image is a 16-bit per component image and there is no gAMA + * or sRGB chunk assume that the components are sRGB encoded. Notice that + * images output by the simplified API always have gamma information; setting + * this flag only affects the interpretation of 16-bit images from an + * external source. It is recommended that the application expose this flag + * to the user; the user can normally easily recognize the difference between + * linear and sRGB encoding. This flag has no effect on write - the data + * passed to the write APIs must have the correct encoding (as defined + * above.) + * + * If the flag is not set (the default) input 16-bit per component data is + * assumed to be linear. + * + * NOTE: the flag can only be set after the png_image_begin_read_ call, + * because that call initializes the 'flags' field. + */ + +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +/* READ APIs + * --------- + * + * The png_image passed to the read APIs must have been initialized by setting + * the png_controlp field 'opaque' to NULL (or, safer, memset the whole thing.) + */ +#ifdef PNG_STDIO_SUPPORTED +PNG_EXPORT(234, int, png_image_begin_read_from_file, (png_imagep image, + const char *file_name)); + /* The named file is opened for read and the image header is filled in + * from the PNG header in the file. + */ + +PNG_EXPORT(235, int, png_image_begin_read_from_stdio, (png_imagep image, + FILE* file)); + /* The PNG header is read from the stdio FILE object. */ +#endif /* STDIO */ + +PNG_EXPORT(236, int, png_image_begin_read_from_memory, (png_imagep image, + png_const_voidp memory, size_t size)); + /* The PNG header is read from the given memory buffer. */ + +PNG_EXPORT(237, int, png_image_finish_read, (png_imagep image, + png_const_colorp background, void *buffer, png_int_32 row_stride, + void *colormap)); + /* Finish reading the image into the supplied buffer and clean up the + * png_image structure. + * + * row_stride is the step, in byte or 2-byte units as appropriate, + * between adjacent rows. A positive stride indicates that the top-most row + * is first in the buffer - the normal top-down arrangement. A negative + * stride indicates that the bottom-most row is first in the buffer. + * + * background need only be supplied if an alpha channel must be removed from + * a png_byte format and the removal is to be done by compositing on a solid + * color; otherwise it may be NULL and any composition will be done directly + * onto the buffer. The value is an sRGB color to use for the background, + * for grayscale output the green channel is used. + * + * background must be supplied when an alpha channel must be removed from a + * single byte color-mapped output format, in other words if: + * + * 1) The original format from png_image_begin_read_from_* had + * PNG_FORMAT_FLAG_ALPHA set. + * 2) The format set by the application does not. + * 3) The format set by the application has PNG_FORMAT_FLAG_COLORMAP set and + * PNG_FORMAT_FLAG_LINEAR *not* set. + * + * For linear output removing the alpha channel is always done by compositing + * on black and background is ignored. + * + * colormap must be supplied when PNG_FORMAT_FLAG_COLORMAP is set. It must + * be at least the size (in bytes) returned by PNG_IMAGE_COLORMAP_SIZE. + * image->colormap_entries will be updated to the actual number of entries + * written to the colormap; this may be less than the original value. + */ + +PNG_EXPORT(238, void, png_image_free, (png_imagep image)); + /* Free any data allocated by libpng in image->opaque, setting the pointer to + * NULL. May be called at any time after the structure is initialized. + */ +#endif /* SIMPLIFIED_READ */ + +#ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED +/* WRITE APIS + * ---------- + * For write you must initialize a png_image structure to describe the image to + * be written. To do this use memset to set the whole structure to 0 then + * initialize fields describing your image. + * + * version: must be set to PNG_IMAGE_VERSION + * opaque: must be initialized to NULL + * width: image width in pixels + * height: image height in rows + * format: the format of the data (image and color-map) you wish to write + * flags: set to 0 unless one of the defined flags applies; set + * PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB for color format images where the RGB + * values do not correspond to the colors in sRGB. + * colormap_entries: set to the number of entries in the color-map (0 to 256) + */ +#ifdef PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED +PNG_EXPORT(239, int, png_image_write_to_file, (png_imagep image, + const char *file, int convert_to_8bit, const void *buffer, + png_int_32 row_stride, const void *colormap)); + /* Write the image to the named file. */ + +PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file, + int convert_to_8_bit, const void *buffer, png_int_32 row_stride, + const void *colormap)); + /* Write the image to the given (FILE*). */ +#endif /* SIMPLIFIED_WRITE_STDIO */ + +/* With all write APIs if image is in one of the linear formats with 16-bit + * data then setting convert_to_8_bit will cause the output to be an 8-bit PNG + * gamma encoded according to the sRGB specification, otherwise a 16-bit linear + * encoded PNG file is written. + * + * With color-mapped data formats the colormap parameter point to a color-map + * with at least image->colormap_entries encoded in the specified format. If + * the format is linear the written PNG color-map will be converted to sRGB + * regardless of the convert_to_8_bit flag. + * + * With all APIs row_stride is handled as in the read APIs - it is the spacing + * from one row to the next in component sized units (1 or 2 bytes) and if + * negative indicates a bottom-up row layout in the buffer. If row_stride is + * zero, libpng will calculate it for you from the image width and number of + * channels. + * + * Note that the write API does not support interlacing, sub-8-bit pixels or + * most ancillary chunks. If you need to write text chunks (e.g. for copyright + * notices) you need to use one of the other APIs. + */ + +PNG_EXPORT(245, int, png_image_write_to_memory, (png_imagep image, void *memory, + png_alloc_size_t * PNG_RESTRICT memory_bytes, int convert_to_8_bit, + const void *buffer, png_int_32 row_stride, const void *colormap)); + /* Write the image to the given memory buffer. The function both writes the + * whole PNG data stream to *memory and updates *memory_bytes with the count + * of bytes written. + * + * 'memory' may be NULL. In this case *memory_bytes is not read however on + * success the number of bytes which would have been written will still be + * stored in *memory_bytes. On failure *memory_bytes will contain 0. + * + * If 'memory' is not NULL it must point to memory[*memory_bytes] of + * writeable memory. + * + * If the function returns success memory[*memory_bytes] (if 'memory' is not + * NULL) contains the written PNG data. *memory_bytes will always be less + * than or equal to the original value. + * + * If the function returns false and *memory_bytes was not changed an error + * occurred during write. If *memory_bytes was changed, or is not 0 if + * 'memory' was NULL, the write would have succeeded but for the memory + * buffer being too small. *memory_bytes contains the required number of + * bytes and will be bigger that the original value. + */ + +#define png_image_write_get_memory_size(image, size, convert_to_8_bit, buffer,\ + row_stride, colormap)\ + png_image_write_to_memory(&(image), 0, &(size), convert_to_8_bit, buffer,\ + row_stride, colormap) + /* Return the amount of memory in 'size' required to compress this image. + * The png_image structure 'image' must be filled in as in the above + * function and must not be changed before the actual write call, the buffer + * and all other parameters must also be identical to that in the final + * write call. The 'size' variable need not be initialized. + * + * NOTE: the macro returns true/false, if false is returned 'size' will be + * set to zero and the write failed and probably will fail if tried again. + */ + +/* You can pre-allocate the buffer by making sure it is of sufficient size + * regardless of the amount of compression achieved. The buffer size will + * always be bigger than the original image and it will never be filled. The + * following macros are provided to assist in allocating the buffer. + */ +#define PNG_IMAGE_DATA_SIZE(image) (PNG_IMAGE_SIZE(image)+(image).height) + /* The number of uncompressed bytes in the PNG byte encoding of the image; + * uncompressing the PNG IDAT data will give this number of bytes. + * + * NOTE: while PNG_IMAGE_SIZE cannot overflow for an image in memory this + * macro can because of the extra bytes used in the PNG byte encoding. You + * need to avoid this macro if your image size approaches 2^30 in width or + * height. The same goes for the remainder of these macros; they all produce + * bigger numbers than the actual in-memory image size. + */ +#ifndef PNG_ZLIB_MAX_SIZE +# define PNG_ZLIB_MAX_SIZE(b) ((b)+(((b)+7U)>>3)+(((b)+63U)>>6)+11U) + /* An upper bound on the number of compressed bytes given 'b' uncompressed + * bytes. This is based on deflateBounds() in zlib; different + * implementations of zlib compression may conceivably produce more data so + * if your zlib implementation is not zlib itself redefine this macro + * appropriately. + */ +#endif + +#define PNG_IMAGE_COMPRESSED_SIZE_MAX(image)\ + PNG_ZLIB_MAX_SIZE((png_alloc_size_t)PNG_IMAGE_DATA_SIZE(image)) + /* An upper bound on the size of the data in the PNG IDAT chunks. */ + +#define PNG_IMAGE_PNG_SIZE_MAX_(image, image_size)\ + ((8U/*sig*/+25U/*IHDR*/+16U/*gAMA*/+44U/*cHRM*/+12U/*IEND*/+\ + (((image).format&PNG_FORMAT_FLAG_COLORMAP)?/*colormap: PLTE, tRNS*/\ + 12U+3U*(image).colormap_entries/*PLTE data*/+\ + (((image).format&PNG_FORMAT_FLAG_ALPHA)?\ + 12U/*tRNS*/+(image).colormap_entries:0U):0U)+\ + 12U)+(12U*((image_size)/PNG_ZBUF_SIZE))/*IDAT*/+(image_size)) + /* A helper for the following macro; if your compiler cannot handle the + * following macro use this one with the result of + * PNG_IMAGE_COMPRESSED_SIZE_MAX(image) as the second argument (most + * compilers should handle this just fine.) + */ + +#define PNG_IMAGE_PNG_SIZE_MAX(image)\ + PNG_IMAGE_PNG_SIZE_MAX_(image, PNG_IMAGE_COMPRESSED_SIZE_MAX(image)) + /* An upper bound on the total length of the PNG data stream for 'image'. + * The result is of type png_alloc_size_t, on 32-bit systems this may + * overflow even though PNG_IMAGE_DATA_SIZE does not overflow; the write will + * run out of buffer space but return a corrected size which should work. + */ +#endif /* SIMPLIFIED_WRITE */ +/******************************************************************************* + * END OF SIMPLIFIED API + ******************************************************************************/ +#endif /* SIMPLIFIED_{READ|WRITE} */ + +/******************************************************************************* + * Section 6: IMPLEMENTATION OPTIONS + ******************************************************************************* + * + * Support for arbitrary implementation-specific optimizations. The API allows + * particular options to be turned on or off. 'Option' is the number of the + * option and 'onoff' is 0 (off) or non-0 (on). The value returned is given + * by the PNG_OPTION_ defines below. + * + * HARDWARE: normally hardware capabilities, such as the Intel SSE instructions, + * are detected at run time, however sometimes it may be impossible + * to do this in user mode, in which case it is necessary to discover + * the capabilities in an OS specific way. Such capabilities are + * listed here when libpng has support for them and must be turned + * ON by the application if present. + * + * SOFTWARE: sometimes software optimizations actually result in performance + * decrease on some architectures or systems, or with some sets of + * PNG images. 'Software' options allow such optimizations to be + * selected at run time. + */ +#ifdef PNG_SET_OPTION_SUPPORTED +#ifdef PNG_ARM_NEON_API_SUPPORTED +# define PNG_ARM_NEON 0 /* HARDWARE: ARM Neon SIMD instructions supported */ +#endif +#define PNG_MAXIMUM_INFLATE_WINDOW 2 /* SOFTWARE: force maximum window */ +#define PNG_SKIP_sRGB_CHECK_PROFILE 4 /* SOFTWARE: Check ICC profile for sRGB */ +#ifdef PNG_MIPS_MSA_API_SUPPORTED +# define PNG_MIPS_MSA 6 /* HARDWARE: MIPS Msa SIMD instructions supported */ +#endif +#define PNG_IGNORE_ADLER32 8 +#ifdef PNG_POWERPC_VSX_API_SUPPORTED +# define PNG_POWERPC_VSX 10 /* HARDWARE: PowerPC VSX SIMD instructions supported */ +#endif +#define PNG_OPTION_NEXT 12 /* Next option - numbers must be even */ + +/* Return values: NOTE: there are four values and 'off' is *not* zero */ +#define PNG_OPTION_UNSET 0 /* Unset - defaults to off */ +#define PNG_OPTION_INVALID 1 /* Option number out of range */ +#define PNG_OPTION_OFF 2 +#define PNG_OPTION_ON 3 + +PNG_EXPORT(244, int, png_set_option, (png_structrp png_ptr, int option, + int onoff)); +#endif /* SET_OPTION */ + +/******************************************************************************* + * END OF HARDWARE AND SOFTWARE OPTIONS + ******************************************************************************/ + +/* Maintainer: Put new public prototypes here ^, in libpng.3, in project + * defs, and in scripts/symbols.def. + */ + +/* The last ordinal number (this is the *last* one already used; the next + * one to use is one more than this.) + */ +#ifdef PNG_EXPORT_LAST_ORDINAL + PNG_EXPORT_LAST_ORDINAL(249); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PNG_VERSION_INFO_ONLY */ +/* Do not put anything past this line */ +#endif /* PNG_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngconf.h b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngconf.h new file mode 100644 index 0000000..927a769 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngconf.h @@ -0,0 +1,623 @@ + +/* pngconf.h - machine-configurable file for libpng + * + * libpng version 1.6.37 + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * Any machine specific code is near the front of this file, so if you + * are configuring libpng for a machine, you may want to read the section + * starting here down to where it starts to typedef png_color, png_text, + * and png_info. + */ + +#ifndef PNGCONF_H +#define PNGCONF_H + +#ifndef PNG_BUILDING_SYMBOL_TABLE /* else includes may cause problems */ + +/* From libpng 1.6.0 libpng requires an ANSI X3.159-1989 ("ISOC90") compliant C + * compiler for correct compilation. The following header files are required by + * the standard. If your compiler doesn't provide these header files, or they + * do not match the standard, you will need to provide/improve them. + */ +#include +#include + +/* Library header files. These header files are all defined by ISOC90; libpng + * expects conformant implementations, however, an ISOC90 conformant system need + * not provide these header files if the functionality cannot be implemented. + * In this case it will be necessary to disable the relevant parts of libpng in + * the build of pnglibconf.h. + * + * Prior to 1.6.0 string.h was included here; the API changes in 1.6.0 to not + * include this unnecessary header file. + */ + +#ifdef PNG_STDIO_SUPPORTED + /* Required for the definition of FILE: */ +# include +#endif + +#ifdef PNG_SETJMP_SUPPORTED + /* Required for the definition of jmp_buf and the declaration of longjmp: */ +# include +#endif + +#ifdef PNG_CONVERT_tIME_SUPPORTED + /* Required for struct tm: */ +# include +#endif + +#endif /* PNG_BUILDING_SYMBOL_TABLE */ + +/* Prior to 1.6.0, it was possible to turn off 'const' in declarations, + * using PNG_NO_CONST. This is no longer supported. + */ +#define PNG_CONST const /* backward compatibility only */ + +/* This controls optimization of the reading of 16-bit and 32-bit + * values from PNG files. It can be set on a per-app-file basis: it + * just changes whether a macro is used when the function is called. + * The library builder sets the default; if read functions are not + * built into the library the macro implementation is forced on. + */ +#ifndef PNG_READ_INT_FUNCTIONS_SUPPORTED +# define PNG_USE_READ_MACROS +#endif +#if !defined(PNG_NO_USE_READ_MACROS) && !defined(PNG_USE_READ_MACROS) +# if PNG_DEFAULT_READ_MACROS +# define PNG_USE_READ_MACROS +# endif +#endif + +/* COMPILER SPECIFIC OPTIONS. + * + * These options are provided so that a variety of difficult compilers + * can be used. Some are fixed at build time (e.g. PNG_API_RULE + * below) but still have compiler specific implementations, others + * may be changed on a per-file basis when compiling against libpng. + */ + +/* The PNGARG macro was used in versions of libpng prior to 1.6.0 to protect + * against legacy (pre ISOC90) compilers that did not understand function + * prototypes. It is not required for modern C compilers. + */ +#ifndef PNGARG +# define PNGARG(arglist) arglist +#endif + +/* Function calling conventions. + * ============================= + * Normally it is not necessary to specify to the compiler how to call + * a function - it just does it - however on x86 systems derived from + * Microsoft and Borland C compilers ('IBM PC', 'DOS', 'Windows' systems + * and some others) there are multiple ways to call a function and the + * default can be changed on the compiler command line. For this reason + * libpng specifies the calling convention of every exported function and + * every function called via a user supplied function pointer. This is + * done in this file by defining the following macros: + * + * PNGAPI Calling convention for exported functions. + * PNGCBAPI Calling convention for user provided (callback) functions. + * PNGCAPI Calling convention used by the ANSI-C library (required + * for longjmp callbacks and sometimes used internally to + * specify the calling convention for zlib). + * + * These macros should never be overridden. If it is necessary to + * change calling convention in a private build this can be done + * by setting PNG_API_RULE (which defaults to 0) to one of the values + * below to select the correct 'API' variants. + * + * PNG_API_RULE=0 Use PNGCAPI - the 'C' calling convention - throughout. + * This is correct in every known environment. + * PNG_API_RULE=1 Use the operating system convention for PNGAPI and + * the 'C' calling convention (from PNGCAPI) for + * callbacks (PNGCBAPI). This is no longer required + * in any known environment - if it has to be used + * please post an explanation of the problem to the + * libpng mailing list. + * + * These cases only differ if the operating system does not use the C + * calling convention, at present this just means the above cases + * (x86 DOS/Windows systems) and, even then, this does not apply to + * Cygwin running on those systems. + * + * Note that the value must be defined in pnglibconf.h so that what + * the application uses to call the library matches the conventions + * set when building the library. + */ + +/* Symbol export + * ============= + * When building a shared library it is almost always necessary to tell + * the compiler which symbols to export. The png.h macro 'PNG_EXPORT' + * is used to mark the symbols. On some systems these symbols can be + * extracted at link time and need no special processing by the compiler, + * on other systems the symbols are flagged by the compiler and just + * the declaration requires a special tag applied (unfortunately) in a + * compiler dependent way. Some systems can do either. + * + * A small number of older systems also require a symbol from a DLL to + * be flagged to the program that calls it. This is a problem because + * we do not know in the header file included by application code that + * the symbol will come from a shared library, as opposed to a statically + * linked one. For this reason the application must tell us by setting + * the magic flag PNG_USE_DLL to turn on the special processing before + * it includes png.h. + * + * Four additional macros are used to make this happen: + * + * PNG_IMPEXP The magic (if any) to cause a symbol to be exported from + * the build or imported if PNG_USE_DLL is set - compiler + * and system specific. + * + * PNG_EXPORT_TYPE(type) A macro that pre or appends PNG_IMPEXP to + * 'type', compiler specific. + * + * PNG_DLL_EXPORT Set to the magic to use during a libpng build to + * make a symbol exported from the DLL. Not used in the + * public header files; see pngpriv.h for how it is used + * in the libpng build. + * + * PNG_DLL_IMPORT Set to the magic to force the libpng symbols to come + * from a DLL - used to define PNG_IMPEXP when + * PNG_USE_DLL is set. + */ + +/* System specific discovery. + * ========================== + * This code is used at build time to find PNG_IMPEXP, the API settings + * and PNG_EXPORT_TYPE(), it may also set a macro to indicate the DLL + * import processing is possible. On Windows systems it also sets + * compiler-specific macros to the values required to change the calling + * conventions of the various functions. + */ +#if defined(_Windows) || defined(_WINDOWS) || defined(WIN32) ||\ + defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) + /* Windows system (DOS doesn't support DLLs). Includes builds under Cygwin or + * MinGW on any architecture currently supported by Windows. Also includes + * Watcom builds but these need special treatment because they are not + * compatible with GCC or Visual C because of different calling conventions. + */ +# if PNG_API_RULE == 2 + /* If this line results in an error, either because __watcall is not + * understood or because of a redefine just below you cannot use *this* + * build of the library with the compiler you are using. *This* build was + * build using Watcom and applications must also be built using Watcom! + */ +# define PNGCAPI __watcall +# endif + +# if defined(__GNUC__) || (defined(_MSC_VER) && (_MSC_VER >= 800)) +# define PNGCAPI __cdecl +# if PNG_API_RULE == 1 + /* If this line results in an error __stdcall is not understood and + * PNG_API_RULE should not have been set to '1'. + */ +# define PNGAPI __stdcall +# endif +# else + /* An older compiler, or one not detected (erroneously) above, + * if necessary override on the command line to get the correct + * variants for the compiler. + */ +# ifndef PNGCAPI +# define PNGCAPI _cdecl +# endif +# if PNG_API_RULE == 1 && !defined(PNGAPI) +# define PNGAPI _stdcall +# endif +# endif /* compiler/api */ + + /* NOTE: PNGCBAPI always defaults to PNGCAPI. */ + +# if defined(PNGAPI) && !defined(PNG_USER_PRIVATEBUILD) +# error "PNG_USER_PRIVATEBUILD must be defined if PNGAPI is changed" +# endif + +# if (defined(_MSC_VER) && _MSC_VER < 800) ||\ + (defined(__BORLANDC__) && __BORLANDC__ < 0x500) + /* older Borland and MSC + * compilers used '__export' and required this to be after + * the type. + */ +# ifndef PNG_EXPORT_TYPE +# define PNG_EXPORT_TYPE(type) type PNG_IMPEXP +# endif +# define PNG_DLL_EXPORT __export +# else /* newer compiler */ +# define PNG_DLL_EXPORT __declspec(dllexport) +# ifndef PNG_DLL_IMPORT +# define PNG_DLL_IMPORT __declspec(dllimport) +# endif +# endif /* compiler */ + +#else /* !Windows */ +# if (defined(__IBMC__) || defined(__IBMCPP__)) && defined(__OS2__) +# define PNGAPI _System +# else /* !Windows/x86 && !OS/2 */ + /* Use the defaults, or define PNG*API on the command line (but + * this will have to be done for every compile!) + */ +# endif /* other system, !OS/2 */ +#endif /* !Windows/x86 */ + +/* Now do all the defaulting . */ +#ifndef PNGCAPI +# define PNGCAPI +#endif +#ifndef PNGCBAPI +# define PNGCBAPI PNGCAPI +#endif +#ifndef PNGAPI +# define PNGAPI PNGCAPI +#endif + +/* PNG_IMPEXP may be set on the compilation system command line or (if not set) + * then in an internal header file when building the library, otherwise (when + * using the library) it is set here. + */ +#ifndef PNG_IMPEXP +# if defined(PNG_USE_DLL) && defined(PNG_DLL_IMPORT) + /* This forces use of a DLL, disallowing static linking */ +# define PNG_IMPEXP PNG_DLL_IMPORT +# endif + +# ifndef PNG_IMPEXP +# define PNG_IMPEXP +# endif +#endif + +/* In 1.5.2 the definition of PNG_FUNCTION has been changed to always treat + * 'attributes' as a storage class - the attributes go at the start of the + * function definition, and attributes are always appended regardless of the + * compiler. This considerably simplifies these macros but may cause problems + * if any compilers both need function attributes and fail to handle them as + * a storage class (this is unlikely.) + */ +#ifndef PNG_FUNCTION +# define PNG_FUNCTION(type, name, args, attributes) attributes type name args +#endif + +#ifndef PNG_EXPORT_TYPE +# define PNG_EXPORT_TYPE(type) PNG_IMPEXP type +#endif + + /* The ordinal value is only relevant when preprocessing png.h for symbol + * table entries, so we discard it here. See the .dfn files in the + * scripts directory. + */ + +#ifndef PNG_EXPORTA +# define PNG_EXPORTA(ordinal, type, name, args, attributes) \ + PNG_FUNCTION(PNG_EXPORT_TYPE(type), (PNGAPI name), PNGARG(args), \ + PNG_LINKAGE_API attributes) +#endif + +/* ANSI-C (C90) does not permit a macro to be invoked with an empty argument, + * so make something non-empty to satisfy the requirement: + */ +#define PNG_EMPTY /*empty list*/ + +#define PNG_EXPORT(ordinal, type, name, args) \ + PNG_EXPORTA(ordinal, type, name, args, PNG_EMPTY) + +/* Use PNG_REMOVED to comment out a removed interface. */ +#ifndef PNG_REMOVED +# define PNG_REMOVED(ordinal, type, name, args, attributes) +#endif + +#ifndef PNG_CALLBACK +# define PNG_CALLBACK(type, name, args) type (PNGCBAPI name) PNGARG(args) +#endif + +/* Support for compiler specific function attributes. These are used + * so that where compiler support is available incorrect use of API + * functions in png.h will generate compiler warnings. + * + * Added at libpng-1.2.41. + */ + +#ifndef PNG_NO_PEDANTIC_WARNINGS +# ifndef PNG_PEDANTIC_WARNINGS_SUPPORTED +# define PNG_PEDANTIC_WARNINGS_SUPPORTED +# endif +#endif + +#ifdef PNG_PEDANTIC_WARNINGS_SUPPORTED + /* Support for compiler specific function attributes. These are used + * so that where compiler support is available, incorrect use of API + * functions in png.h will generate compiler warnings. Added at libpng + * version 1.2.41. Disabling these removes the warnings but may also produce + * less efficient code. + */ +# if defined(__clang__) && defined(__has_attribute) + /* Clang defines both __clang__ and __GNUC__. Check __clang__ first. */ +# if !defined(PNG_USE_RESULT) && __has_attribute(__warn_unused_result__) +# define PNG_USE_RESULT __attribute__((__warn_unused_result__)) +# endif +# if !defined(PNG_NORETURN) && __has_attribute(__noreturn__) +# define PNG_NORETURN __attribute__((__noreturn__)) +# endif +# if !defined(PNG_ALLOCATED) && __has_attribute(__malloc__) +# define PNG_ALLOCATED __attribute__((__malloc__)) +# endif +# if !defined(PNG_DEPRECATED) && __has_attribute(__deprecated__) +# define PNG_DEPRECATED __attribute__((__deprecated__)) +# endif +# if !defined(PNG_PRIVATE) +# ifdef __has_extension +# if __has_extension(attribute_unavailable_with_message) +# define PNG_PRIVATE __attribute__((__unavailable__(\ + "This function is not exported by libpng."))) +# endif +# endif +# endif +# ifndef PNG_RESTRICT +# define PNG_RESTRICT __restrict +# endif + +# elif defined(__GNUC__) +# ifndef PNG_USE_RESULT +# define PNG_USE_RESULT __attribute__((__warn_unused_result__)) +# endif +# ifndef PNG_NORETURN +# define PNG_NORETURN __attribute__((__noreturn__)) +# endif +# if __GNUC__ >= 3 +# ifndef PNG_ALLOCATED +# define PNG_ALLOCATED __attribute__((__malloc__)) +# endif +# ifndef PNG_DEPRECATED +# define PNG_DEPRECATED __attribute__((__deprecated__)) +# endif +# ifndef PNG_PRIVATE +# if 0 /* Doesn't work so we use deprecated instead*/ +# define PNG_PRIVATE \ + __attribute__((warning("This function is not exported by libpng."))) +# else +# define PNG_PRIVATE \ + __attribute__((__deprecated__)) +# endif +# endif +# if ((__GNUC__ > 3) || !defined(__GNUC_MINOR__) || (__GNUC_MINOR__ >= 1)) +# ifndef PNG_RESTRICT +# define PNG_RESTRICT __restrict +# endif +# endif /* __GNUC__.__GNUC_MINOR__ > 3.0 */ +# endif /* __GNUC__ >= 3 */ + +# elif defined(_MSC_VER) && (_MSC_VER >= 1300) +# ifndef PNG_USE_RESULT +# define PNG_USE_RESULT /* not supported */ +# endif +# ifndef PNG_NORETURN +# define PNG_NORETURN __declspec(noreturn) +# endif +# ifndef PNG_ALLOCATED +# if (_MSC_VER >= 1400) +# define PNG_ALLOCATED __declspec(restrict) +# endif +# endif +# ifndef PNG_DEPRECATED +# define PNG_DEPRECATED __declspec(deprecated) +# endif +# ifndef PNG_PRIVATE +# define PNG_PRIVATE __declspec(deprecated) +# endif +# ifndef PNG_RESTRICT +# if (_MSC_VER >= 1400) +# define PNG_RESTRICT __restrict +# endif +# endif + +# elif defined(__WATCOMC__) +# ifndef PNG_RESTRICT +# define PNG_RESTRICT __restrict +# endif +# endif +#endif /* PNG_PEDANTIC_WARNINGS */ + +#ifndef PNG_DEPRECATED +# define PNG_DEPRECATED /* Use of this function is deprecated */ +#endif +#ifndef PNG_USE_RESULT +# define PNG_USE_RESULT /* The result of this function must be checked */ +#endif +#ifndef PNG_NORETURN +# define PNG_NORETURN /* This function does not return */ +#endif +#ifndef PNG_ALLOCATED +# define PNG_ALLOCATED /* The result of the function is new memory */ +#endif +#ifndef PNG_PRIVATE +# define PNG_PRIVATE /* This is a private libpng function */ +#endif +#ifndef PNG_RESTRICT +# define PNG_RESTRICT /* The C99 "restrict" feature */ +#endif + +#ifndef PNG_FP_EXPORT /* A floating point API. */ +# ifdef PNG_FLOATING_POINT_SUPPORTED +# define PNG_FP_EXPORT(ordinal, type, name, args)\ + PNG_EXPORT(ordinal, type, name, args); +# else /* No floating point APIs */ +# define PNG_FP_EXPORT(ordinal, type, name, args) +# endif +#endif +#ifndef PNG_FIXED_EXPORT /* A fixed point API. */ +# ifdef PNG_FIXED_POINT_SUPPORTED +# define PNG_FIXED_EXPORT(ordinal, type, name, args)\ + PNG_EXPORT(ordinal, type, name, args); +# else /* No fixed point APIs */ +# define PNG_FIXED_EXPORT(ordinal, type, name, args) +# endif +#endif + +#ifndef PNG_BUILDING_SYMBOL_TABLE +/* Some typedefs to get us started. These should be safe on most of the common + * platforms. + * + * png_uint_32 and png_int_32 may, currently, be larger than required to hold a + * 32-bit value however this is not normally advisable. + * + * png_uint_16 and png_int_16 should always be two bytes in size - this is + * verified at library build time. + * + * png_byte must always be one byte in size. + * + * The checks below use constants from limits.h, as defined by the ISOC90 + * standard. + */ +#if CHAR_BIT == 8 && UCHAR_MAX == 255 + typedef unsigned char png_byte; +#else +# error "libpng requires 8-bit bytes" +#endif + +#if INT_MIN == -32768 && INT_MAX == 32767 + typedef int png_int_16; +#elif SHRT_MIN == -32768 && SHRT_MAX == 32767 + typedef short png_int_16; +#else +# error "libpng requires a signed 16-bit type" +#endif + +#if UINT_MAX == 65535 + typedef unsigned int png_uint_16; +#elif USHRT_MAX == 65535 + typedef unsigned short png_uint_16; +#else +# error "libpng requires an unsigned 16-bit type" +#endif + +#if INT_MIN < -2147483646 && INT_MAX > 2147483646 + typedef int png_int_32; +#elif LONG_MIN < -2147483646 && LONG_MAX > 2147483646 + typedef long int png_int_32; +#else +# error "libpng requires a signed 32-bit (or more) type" +#endif + +#if UINT_MAX > 4294967294U + typedef unsigned int png_uint_32; +#elif ULONG_MAX > 4294967294U + typedef unsigned long int png_uint_32; +#else +# error "libpng requires an unsigned 32-bit (or more) type" +#endif + +/* Prior to 1.6.0, it was possible to disable the use of size_t and ptrdiff_t. + * From 1.6.0 onwards, an ISO C90 compiler, as well as a standard-compliant + * behavior of sizeof and ptrdiff_t are required. + * The legacy typedefs are provided here for backwards compatibility. + */ +typedef size_t png_size_t; +typedef ptrdiff_t png_ptrdiff_t; + +/* libpng needs to know the maximum value of 'size_t' and this controls the + * definition of png_alloc_size_t, below. This maximum value of size_t limits + * but does not control the maximum allocations the library makes - there is + * direct application control of this through png_set_user_limits(). + */ +#ifndef PNG_SMALL_SIZE_T + /* Compiler specific tests for systems where size_t is known to be less than + * 32 bits (some of these systems may no longer work because of the lack of + * 'far' support; see above.) + */ +# if (defined(__TURBOC__) && !defined(__FLAT__)) ||\ + (defined(_MSC_VER) && defined(MAXSEG_64K)) +# define PNG_SMALL_SIZE_T +# endif +#endif + +/* png_alloc_size_t is guaranteed to be no smaller than size_t, and no smaller + * than png_uint_32. Casts from size_t or png_uint_32 to png_alloc_size_t are + * not necessary; in fact, it is recommended not to use them at all, so that + * the compiler can complain when something turns out to be problematic. + * + * Casts in the other direction (from png_alloc_size_t to size_t or + * png_uint_32) should be explicitly applied; however, we do not expect to + * encounter practical situations that require such conversions. + * + * PNG_SMALL_SIZE_T must be defined if the maximum value of size_t is less than + * 4294967295 - i.e. less than the maximum value of png_uint_32. + */ +#ifdef PNG_SMALL_SIZE_T + typedef png_uint_32 png_alloc_size_t; +#else + typedef size_t png_alloc_size_t; +#endif + +/* Prior to 1.6.0 libpng offered limited support for Microsoft C compiler + * implementations of Intel CPU specific support of user-mode segmented address + * spaces, where 16-bit pointers address more than 65536 bytes of memory using + * separate 'segment' registers. The implementation requires two different + * types of pointer (only one of which includes the segment value.) + * + * If required this support is available in version 1.2 of libpng and may be + * available in versions through 1.5, although the correctness of the code has + * not been verified recently. + */ + +/* Typedef for floating-point numbers that are converted to fixed-point with a + * multiple of 100,000, e.g., gamma + */ +typedef png_int_32 png_fixed_point; + +/* Add typedefs for pointers */ +typedef void * png_voidp; +typedef const void * png_const_voidp; +typedef png_byte * png_bytep; +typedef const png_byte * png_const_bytep; +typedef png_uint_32 * png_uint_32p; +typedef const png_uint_32 * png_const_uint_32p; +typedef png_int_32 * png_int_32p; +typedef const png_int_32 * png_const_int_32p; +typedef png_uint_16 * png_uint_16p; +typedef const png_uint_16 * png_const_uint_16p; +typedef png_int_16 * png_int_16p; +typedef const png_int_16 * png_const_int_16p; +typedef char * png_charp; +typedef const char * png_const_charp; +typedef png_fixed_point * png_fixed_point_p; +typedef const png_fixed_point * png_const_fixed_point_p; +typedef size_t * png_size_tp; +typedef const size_t * png_const_size_tp; + +#ifdef PNG_STDIO_SUPPORTED +typedef FILE * png_FILE_p; +#endif + +#ifdef PNG_FLOATING_POINT_SUPPORTED +typedef double * png_doublep; +typedef const double * png_const_doublep; +#endif + +/* Pointers to pointers; i.e. arrays */ +typedef png_byte * * png_bytepp; +typedef png_uint_32 * * png_uint_32pp; +typedef png_int_32 * * png_int_32pp; +typedef png_uint_16 * * png_uint_16pp; +typedef png_int_16 * * png_int_16pp; +typedef const char * * png_const_charpp; +typedef char * * png_charpp; +typedef png_fixed_point * * png_fixed_point_pp; +#ifdef PNG_FLOATING_POINT_SUPPORTED +typedef double * * png_doublepp; +#endif + +/* Pointers to pointers to pointers; i.e., pointer to array */ +typedef char * * * png_charppp; + +#endif /* PNG_BUILDING_SYMBOL_TABLE */ + +#endif /* PNGCONF_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngdebug.h b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngdebug.h new file mode 100644 index 0000000..00d5a45 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngdebug.h @@ -0,0 +1,153 @@ + +/* pngdebug.h - Debugging macros for libpng, also used in pngtest.c + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2013 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +/* Define PNG_DEBUG at compile time for debugging information. Higher + * numbers for PNG_DEBUG mean more debugging information. This has + * only been added since version 0.95 so it is not implemented throughout + * libpng yet, but more support will be added as needed. + * + * png_debug[1-2]?(level, message ,arg{0-2}) + * Expands to a statement (either a simple expression or a compound + * do..while(0) statement) that outputs a message with parameter + * substitution if PNG_DEBUG is defined to 2 or more. If PNG_DEBUG + * is undefined, 0 or 1 every png_debug expands to a simple expression + * (actually ((void)0)). + * + * level: level of detail of message, starting at 0. A level 'n' + * message is preceded by 'n' 3-space indentations (not implemented + * on Microsoft compilers unless PNG_DEBUG_FILE is also + * defined, to allow debug DLL compilation with no standard IO). + * message: a printf(3) style text string. A trailing '\n' is added + * to the message. + * arg: 0 to 2 arguments for printf(3) style substitution in message. + */ +#ifndef PNGDEBUG_H +#define PNGDEBUG_H +/* These settings control the formatting of messages in png.c and pngerror.c */ +/* Moved to pngdebug.h at 1.5.0 */ +# ifndef PNG_LITERAL_SHARP +# define PNG_LITERAL_SHARP 0x23 +# endif +# ifndef PNG_LITERAL_LEFT_SQUARE_BRACKET +# define PNG_LITERAL_LEFT_SQUARE_BRACKET 0x5b +# endif +# ifndef PNG_LITERAL_RIGHT_SQUARE_BRACKET +# define PNG_LITERAL_RIGHT_SQUARE_BRACKET 0x5d +# endif +# ifndef PNG_STRING_NEWLINE +# define PNG_STRING_NEWLINE "\n" +# endif + +#ifdef PNG_DEBUG +# if (PNG_DEBUG > 0) +# if !defined(PNG_DEBUG_FILE) && defined(_MSC_VER) +# include +# if (PNG_DEBUG > 1) +# ifndef _DEBUG +# define _DEBUG +# endif +# ifndef png_debug +# define png_debug(l,m) _RPT0(_CRT_WARN,m PNG_STRING_NEWLINE) +# endif +# ifndef png_debug1 +# define png_debug1(l,m,p1) _RPT1(_CRT_WARN,m PNG_STRING_NEWLINE,p1) +# endif +# ifndef png_debug2 +# define png_debug2(l,m,p1,p2) \ + _RPT2(_CRT_WARN,m PNG_STRING_NEWLINE,p1,p2) +# endif +# endif +# else /* PNG_DEBUG_FILE || !_MSC_VER */ +# ifndef PNG_STDIO_SUPPORTED +# include /* not included yet */ +# endif +# ifndef PNG_DEBUG_FILE +# define PNG_DEBUG_FILE stderr +# endif /* PNG_DEBUG_FILE */ + +# if (PNG_DEBUG > 1) +# ifdef __STDC__ +# ifndef png_debug +# define png_debug(l,m) \ + do { \ + int num_tabs=l; \ + fprintf(PNG_DEBUG_FILE,"%s" m PNG_STRING_NEWLINE,(num_tabs==1 ? " " : \ + (num_tabs==2 ? " " : (num_tabs>2 ? " " : "")))); \ + } while (0) +# endif +# ifndef png_debug1 +# define png_debug1(l,m,p1) \ + do { \ + int num_tabs=l; \ + fprintf(PNG_DEBUG_FILE,"%s" m PNG_STRING_NEWLINE,(num_tabs==1 ? " " : \ + (num_tabs==2 ? " " : (num_tabs>2 ? " " : ""))),p1); \ + } while (0) +# endif +# ifndef png_debug2 +# define png_debug2(l,m,p1,p2) \ + do { \ + int num_tabs=l; \ + fprintf(PNG_DEBUG_FILE,"%s" m PNG_STRING_NEWLINE,(num_tabs==1 ? " " : \ + (num_tabs==2 ? " " : (num_tabs>2 ? " " : ""))),p1,p2);\ + } while (0) +# endif +# else /* __STDC __ */ +# ifndef png_debug +# define png_debug(l,m) \ + do { \ + int num_tabs=l; \ + char format[256]; \ + snprintf(format,256,"%s%s%s",(num_tabs==1 ? "\t" : \ + (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))), \ + m,PNG_STRING_NEWLINE); \ + fprintf(PNG_DEBUG_FILE,format); \ + } while (0) +# endif +# ifndef png_debug1 +# define png_debug1(l,m,p1) \ + do { \ + int num_tabs=l; \ + char format[256]; \ + snprintf(format,256,"%s%s%s",(num_tabs==1 ? "\t" : \ + (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))), \ + m,PNG_STRING_NEWLINE); \ + fprintf(PNG_DEBUG_FILE,format,p1); \ + } while (0) +# endif +# ifndef png_debug2 +# define png_debug2(l,m,p1,p2) \ + do { \ + int num_tabs=l; \ + char format[256]; \ + snprintf(format,256,"%s%s%s",(num_tabs==1 ? "\t" : \ + (num_tabs==2 ? "\t\t":(num_tabs>2 ? "\t\t\t":""))), \ + m,PNG_STRING_NEWLINE); \ + fprintf(PNG_DEBUG_FILE,format,p1,p2); \ + } while (0) +# endif +# endif /* __STDC __ */ +# endif /* (PNG_DEBUG > 1) */ + +# endif /* _MSC_VER */ +# endif /* (PNG_DEBUG > 0) */ +#endif /* PNG_DEBUG */ +#ifndef png_debug +# define png_debug(l, m) ((void)0) +#endif +#ifndef png_debug1 +# define png_debug1(l, m, p1) ((void)0) +#endif +#ifndef png_debug2 +# define png_debug2(l, m, p1, p2) ((void)0) +#endif +#endif /* PNGDEBUG_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngerror.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngerror.c new file mode 100644 index 0000000..ec3a709 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngerror.c @@ -0,0 +1,963 @@ + +/* pngerror.c - stub functions for i/o and memory allocation + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2017 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all error handling. Users who + * need special error handling are expected to write replacement functions + * and use png_set_error_fn() to use those functions. See the instructions + * at each function. + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +static PNG_FUNCTION(void, png_default_error,PNGARG((png_const_structrp png_ptr, + png_const_charp error_message)),PNG_NORETURN); + +#ifdef PNG_WARNINGS_SUPPORTED +static void /* PRIVATE */ +png_default_warning PNGARG((png_const_structrp png_ptr, + png_const_charp warning_message)); +#endif /* WARNINGS */ + +/* This function is called whenever there is a fatal error. This function + * should not be changed. If there is a need to handle errors differently, + * you should supply a replacement error function and use png_set_error_fn() + * to replace the error function at run-time. + */ +#ifdef PNG_ERROR_TEXT_SUPPORTED +PNG_FUNCTION(void,PNGAPI +png_error,(png_const_structrp png_ptr, png_const_charp error_message), + PNG_NORETURN) +{ +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + char msg[16]; + if (png_ptr != NULL) + { + if ((png_ptr->flags & + (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT)) != 0) + { + if (*error_message == PNG_LITERAL_SHARP) + { + /* Strip "#nnnn " from beginning of error message. */ + int offset; + for (offset = 1; offset<15; offset++) + if (error_message[offset] == ' ') + break; + + if ((png_ptr->flags & PNG_FLAG_STRIP_ERROR_TEXT) != 0) + { + int i; + for (i = 0; i < offset - 1; i++) + msg[i] = error_message[i + 1]; + msg[i - 1] = '\0'; + error_message = msg; + } + + else + error_message += offset; + } + + else + { + if ((png_ptr->flags & PNG_FLAG_STRIP_ERROR_TEXT) != 0) + { + msg[0] = '0'; + msg[1] = '\0'; + error_message = msg; + } + } + } + } +#endif + if (png_ptr != NULL && png_ptr->error_fn != NULL) + (*(png_ptr->error_fn))(png_constcast(png_structrp,png_ptr), + error_message); + + /* If the custom handler doesn't exist, or if it returns, + use the default handler, which will not return. */ + png_default_error(png_ptr, error_message); +} +#else +PNG_FUNCTION(void,PNGAPI +png_err,(png_const_structrp png_ptr),PNG_NORETURN) +{ + /* Prior to 1.5.2 the error_fn received a NULL pointer, expressed + * erroneously as '\0', instead of the empty string "". This was + * apparently an error, introduced in libpng-1.2.20, and png_default_error + * will crash in this case. + */ + if (png_ptr != NULL && png_ptr->error_fn != NULL) + (*(png_ptr->error_fn))(png_constcast(png_structrp,png_ptr), ""); + + /* If the custom handler doesn't exist, or if it returns, + use the default handler, which will not return. */ + png_default_error(png_ptr, ""); +} +#endif /* ERROR_TEXT */ + +/* Utility to safely appends strings to a buffer. This never errors out so + * error checking is not required in the caller. + */ +size_t +png_safecat(png_charp buffer, size_t bufsize, size_t pos, + png_const_charp string) +{ + if (buffer != NULL && pos < bufsize) + { + if (string != NULL) + while (*string != '\0' && pos < bufsize-1) + buffer[pos++] = *string++; + + buffer[pos] = '\0'; + } + + return pos; +} + +#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED) +/* Utility to dump an unsigned value into a buffer, given a start pointer and + * and end pointer (which should point just *beyond* the end of the buffer!) + * Returns the pointer to the start of the formatted string. + */ +png_charp +png_format_number(png_const_charp start, png_charp end, int format, + png_alloc_size_t number) +{ + int count = 0; /* number of digits output */ + int mincount = 1; /* minimum number required */ + int output = 0; /* digit output (for the fixed point format) */ + + *--end = '\0'; + + /* This is written so that the loop always runs at least once, even with + * number zero. + */ + while (end > start && (number != 0 || count < mincount)) + { + + static const char digits[] = "0123456789ABCDEF"; + + switch (format) + { + case PNG_NUMBER_FORMAT_fixed: + /* Needs five digits (the fraction) */ + mincount = 5; + if (output != 0 || number % 10 != 0) + { + *--end = digits[number % 10]; + output = 1; + } + number /= 10; + break; + + case PNG_NUMBER_FORMAT_02u: + /* Expects at least 2 digits. */ + mincount = 2; + /* FALLTHROUGH */ + + case PNG_NUMBER_FORMAT_u: + *--end = digits[number % 10]; + number /= 10; + break; + + case PNG_NUMBER_FORMAT_02x: + /* This format expects at least two digits */ + mincount = 2; + /* FALLTHROUGH */ + + case PNG_NUMBER_FORMAT_x: + *--end = digits[number & 0xf]; + number >>= 4; + break; + + default: /* an error */ + number = 0; + break; + } + + /* Keep track of the number of digits added */ + ++count; + + /* Float a fixed number here: */ + if ((format == PNG_NUMBER_FORMAT_fixed) && (count == 5) && (end > start)) + { + /* End of the fraction, but maybe nothing was output? In that case + * drop the decimal point. If the number is a true zero handle that + * here. + */ + if (output != 0) + *--end = '.'; + else if (number == 0) /* and !output */ + *--end = '0'; + } + } + + return end; +} +#endif + +#ifdef PNG_WARNINGS_SUPPORTED +/* This function is called whenever there is a non-fatal error. This function + * should not be changed. If there is a need to handle warnings differently, + * you should supply a replacement warning function and use + * png_set_error_fn() to replace the warning function at run-time. + */ +void PNGAPI +png_warning(png_const_structrp png_ptr, png_const_charp warning_message) +{ + int offset = 0; + if (png_ptr != NULL) + { +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + if ((png_ptr->flags & + (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT)) != 0) +#endif + { + if (*warning_message == PNG_LITERAL_SHARP) + { + for (offset = 1; offset < 15; offset++) + if (warning_message[offset] == ' ') + break; + } + } + } + if (png_ptr != NULL && png_ptr->warning_fn != NULL) + (*(png_ptr->warning_fn))(png_constcast(png_structrp,png_ptr), + warning_message + offset); + else + png_default_warning(png_ptr, warning_message + offset); +} + +/* These functions support 'formatted' warning messages with up to + * PNG_WARNING_PARAMETER_COUNT parameters. In the format string the parameter + * is introduced by @, where 'number' starts at 1. This follows the + * standard established by X/Open for internationalizable error messages. + */ +void +png_warning_parameter(png_warning_parameters p, int number, + png_const_charp string) +{ + if (number > 0 && number <= PNG_WARNING_PARAMETER_COUNT) + (void)png_safecat(p[number-1], (sizeof p[number-1]), 0, string); +} + +void +png_warning_parameter_unsigned(png_warning_parameters p, int number, int format, + png_alloc_size_t value) +{ + char buffer[PNG_NUMBER_BUFFER_SIZE]; + png_warning_parameter(p, number, PNG_FORMAT_NUMBER(buffer, format, value)); +} + +void +png_warning_parameter_signed(png_warning_parameters p, int number, int format, + png_int_32 value) +{ + png_alloc_size_t u; + png_charp str; + char buffer[PNG_NUMBER_BUFFER_SIZE]; + + /* Avoid overflow by doing the negate in a png_alloc_size_t: */ + u = (png_alloc_size_t)value; + if (value < 0) + u = ~u + 1; + + str = PNG_FORMAT_NUMBER(buffer, format, u); + + if (value < 0 && str > buffer) + *--str = '-'; + + png_warning_parameter(p, number, str); +} + +void +png_formatted_warning(png_const_structrp png_ptr, png_warning_parameters p, + png_const_charp message) +{ + /* The internal buffer is just 192 bytes - enough for all our messages, + * overflow doesn't happen because this code checks! If someone figures + * out how to send us a message longer than 192 bytes, all that will + * happen is that the message will be truncated appropriately. + */ + size_t i = 0; /* Index in the msg[] buffer: */ + char msg[192]; + + /* Each iteration through the following loop writes at most one character + * to msg[i++] then returns here to validate that there is still space for + * the trailing '\0'. It may (in the case of a parameter) read more than + * one character from message[]; it must check for '\0' and continue to the + * test if it finds the end of string. + */ + while (i<(sizeof msg)-1 && *message != '\0') + { + /* '@' at end of string is now just printed (previously it was skipped); + * it is an error in the calling code to terminate the string with @. + */ + if (p != NULL && *message == '@' && message[1] != '\0') + { + int parameter_char = *++message; /* Consume the '@' */ + static const char valid_parameters[] = "123456789"; + int parameter = 0; + + /* Search for the parameter digit, the index in the string is the + * parameter to use. + */ + while (valid_parameters[parameter] != parameter_char && + valid_parameters[parameter] != '\0') + ++parameter; + + /* If the parameter digit is out of range it will just get printed. */ + if (parameter < PNG_WARNING_PARAMETER_COUNT) + { + /* Append this parameter */ + png_const_charp parm = p[parameter]; + png_const_charp pend = p[parameter] + (sizeof p[parameter]); + + /* No need to copy the trailing '\0' here, but there is no guarantee + * that parm[] has been initialized, so there is no guarantee of a + * trailing '\0': + */ + while (i<(sizeof msg)-1 && *parm != '\0' && parm < pend) + msg[i++] = *parm++; + + /* Consume the parameter digit too: */ + ++message; + continue; + } + + /* else not a parameter and there is a character after the @ sign; just + * copy that. This is known not to be '\0' because of the test above. + */ + } + + /* At this point *message can't be '\0', even in the bad parameter case + * above where there is a lone '@' at the end of the message string. + */ + msg[i++] = *message++; + } + + /* i is always less than (sizeof msg), so: */ + msg[i] = '\0'; + + /* And this is the formatted message. It may be larger than + * PNG_MAX_ERROR_TEXT, but that is only used for 'chunk' errors and these + * are not (currently) formatted. + */ + png_warning(png_ptr, msg); +} +#endif /* WARNINGS */ + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +void PNGAPI +png_benign_error(png_const_structrp png_ptr, png_const_charp error_message) +{ + if ((png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN) != 0) + { +# ifdef PNG_READ_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0 && + png_ptr->chunk_name != 0) + png_chunk_warning(png_ptr, error_message); + else +# endif + png_warning(png_ptr, error_message); + } + + else + { +# ifdef PNG_READ_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0 && + png_ptr->chunk_name != 0) + png_chunk_error(png_ptr, error_message); + else +# endif + png_error(png_ptr, error_message); + } + +# ifndef PNG_ERROR_TEXT_SUPPORTED + PNG_UNUSED(error_message) +# endif +} + +void /* PRIVATE */ +png_app_warning(png_const_structrp png_ptr, png_const_charp error_message) +{ + if ((png_ptr->flags & PNG_FLAG_APP_WARNINGS_WARN) != 0) + png_warning(png_ptr, error_message); + else + png_error(png_ptr, error_message); + +# ifndef PNG_ERROR_TEXT_SUPPORTED + PNG_UNUSED(error_message) +# endif +} + +void /* PRIVATE */ +png_app_error(png_const_structrp png_ptr, png_const_charp error_message) +{ + if ((png_ptr->flags & PNG_FLAG_APP_ERRORS_WARN) != 0) + png_warning(png_ptr, error_message); + else + png_error(png_ptr, error_message); + +# ifndef PNG_ERROR_TEXT_SUPPORTED + PNG_UNUSED(error_message) +# endif +} +#endif /* BENIGN_ERRORS */ + +#define PNG_MAX_ERROR_TEXT 196 /* Currently limited by profile_error in png.c */ +#if defined(PNG_WARNINGS_SUPPORTED) || \ + (defined(PNG_READ_SUPPORTED) && defined(PNG_ERROR_TEXT_SUPPORTED)) +/* These utilities are used internally to build an error message that relates + * to the current chunk. The chunk name comes from png_ptr->chunk_name, + * which is used to prefix the message. The message is limited in length + * to 63 bytes. The name characters are output as hex digits wrapped in [] + * if the character is invalid. + */ +#define isnonalpha(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97)) +static const char png_digit[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static void /* PRIVATE */ +png_format_buffer(png_const_structrp png_ptr, png_charp buffer, png_const_charp + error_message) +{ + png_uint_32 chunk_name = png_ptr->chunk_name; + int iout = 0, ishift = 24; + + while (ishift >= 0) + { + int c = (int)(chunk_name >> ishift) & 0xff; + + ishift -= 8; + if (isnonalpha(c) != 0) + { + buffer[iout++] = PNG_LITERAL_LEFT_SQUARE_BRACKET; + buffer[iout++] = png_digit[(c & 0xf0) >> 4]; + buffer[iout++] = png_digit[c & 0x0f]; + buffer[iout++] = PNG_LITERAL_RIGHT_SQUARE_BRACKET; + } + + else + { + buffer[iout++] = (char)c; + } + } + + if (error_message == NULL) + buffer[iout] = '\0'; + + else + { + int iin = 0; + + buffer[iout++] = ':'; + buffer[iout++] = ' '; + + while (iin < PNG_MAX_ERROR_TEXT-1 && error_message[iin] != '\0') + buffer[iout++] = error_message[iin++]; + + /* iin < PNG_MAX_ERROR_TEXT, so the following is safe: */ + buffer[iout] = '\0'; + } +} +#endif /* WARNINGS || ERROR_TEXT */ + +#if defined(PNG_READ_SUPPORTED) && defined(PNG_ERROR_TEXT_SUPPORTED) +PNG_FUNCTION(void,PNGAPI +png_chunk_error,(png_const_structrp png_ptr, png_const_charp error_message), + PNG_NORETURN) +{ + char msg[18+PNG_MAX_ERROR_TEXT]; + if (png_ptr == NULL) + png_error(png_ptr, error_message); + + else + { + png_format_buffer(png_ptr, msg, error_message); + png_error(png_ptr, msg); + } +} +#endif /* READ && ERROR_TEXT */ + +#ifdef PNG_WARNINGS_SUPPORTED +void PNGAPI +png_chunk_warning(png_const_structrp png_ptr, png_const_charp warning_message) +{ + char msg[18+PNG_MAX_ERROR_TEXT]; + if (png_ptr == NULL) + png_warning(png_ptr, warning_message); + + else + { + png_format_buffer(png_ptr, msg, warning_message); + png_warning(png_ptr, msg); + } +} +#endif /* WARNINGS */ + +#ifdef PNG_READ_SUPPORTED +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +void PNGAPI +png_chunk_benign_error(png_const_structrp png_ptr, png_const_charp + error_message) +{ + if ((png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN) != 0) + png_chunk_warning(png_ptr, error_message); + + else + png_chunk_error(png_ptr, error_message); + +# ifndef PNG_ERROR_TEXT_SUPPORTED + PNG_UNUSED(error_message) +# endif +} +#endif +#endif /* READ */ + +void /* PRIVATE */ +png_chunk_report(png_const_structrp png_ptr, png_const_charp message, int error) +{ +# ifndef PNG_WARNINGS_SUPPORTED + PNG_UNUSED(message) +# endif + + /* This is always supported, but for just read or just write it + * unconditionally does the right thing. + */ +# if defined(PNG_READ_SUPPORTED) && defined(PNG_WRITE_SUPPORTED) + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) +# endif + +# ifdef PNG_READ_SUPPORTED + { + if (error < PNG_CHUNK_ERROR) + png_chunk_warning(png_ptr, message); + + else + png_chunk_benign_error(png_ptr, message); + } +# endif + +# if defined(PNG_READ_SUPPORTED) && defined(PNG_WRITE_SUPPORTED) + else if ((png_ptr->mode & PNG_IS_READ_STRUCT) == 0) +# endif + +# ifdef PNG_WRITE_SUPPORTED + { + if (error < PNG_CHUNK_WRITE_ERROR) + png_app_warning(png_ptr, message); + + else + png_app_error(png_ptr, message); + } +# endif +} + +#ifdef PNG_ERROR_TEXT_SUPPORTED +#ifdef PNG_FLOATING_POINT_SUPPORTED +PNG_FUNCTION(void, +png_fixed_error,(png_const_structrp png_ptr, png_const_charp name),PNG_NORETURN) +{ +# define fixed_message "fixed point overflow in " +# define fixed_message_ln ((sizeof fixed_message)-1) + unsigned int iin; + char msg[fixed_message_ln+PNG_MAX_ERROR_TEXT]; + memcpy(msg, fixed_message, fixed_message_ln); + iin = 0; + if (name != NULL) + while (iin < (PNG_MAX_ERROR_TEXT-1) && name[iin] != 0) + { + msg[fixed_message_ln + iin] = name[iin]; + ++iin; + } + msg[fixed_message_ln + iin] = 0; + png_error(png_ptr, msg); +} +#endif +#endif + +#ifdef PNG_SETJMP_SUPPORTED +/* This API only exists if ANSI-C style error handling is used, + * otherwise it is necessary for png_default_error to be overridden. + */ +jmp_buf* PNGAPI +png_set_longjmp_fn(png_structrp png_ptr, png_longjmp_ptr longjmp_fn, + size_t jmp_buf_size) +{ + /* From libpng 1.6.0 the app gets one chance to set a 'jmpbuf_size' value + * and it must not change after that. Libpng doesn't care how big the + * buffer is, just that it doesn't change. + * + * If the buffer size is no *larger* than the size of jmp_buf when libpng is + * compiled a built in jmp_buf is returned; this preserves the pre-1.6.0 + * semantics that this call will not fail. If the size is larger, however, + * the buffer is allocated and this may fail, causing the function to return + * NULL. + */ + if (png_ptr == NULL) + return NULL; + + if (png_ptr->jmp_buf_ptr == NULL) + { + png_ptr->jmp_buf_size = 0; /* not allocated */ + + if (jmp_buf_size <= (sizeof png_ptr->jmp_buf_local)) + png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; + + else + { + png_ptr->jmp_buf_ptr = png_voidcast(jmp_buf *, + png_malloc_warn(png_ptr, jmp_buf_size)); + + if (png_ptr->jmp_buf_ptr == NULL) + return NULL; /* new NULL return on OOM */ + + png_ptr->jmp_buf_size = jmp_buf_size; + } + } + + else /* Already allocated: check the size */ + { + size_t size = png_ptr->jmp_buf_size; + + if (size == 0) + { + size = (sizeof png_ptr->jmp_buf_local); + if (png_ptr->jmp_buf_ptr != &png_ptr->jmp_buf_local) + { + /* This is an internal error in libpng: somehow we have been left + * with a stack allocated jmp_buf when the application regained + * control. It's always possible to fix this up, but for the moment + * this is a png_error because that makes it easy to detect. + */ + png_error(png_ptr, "Libpng jmp_buf still allocated"); + /* png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; */ + } + } + + if (size != jmp_buf_size) + { + png_warning(png_ptr, "Application jmp_buf size changed"); + return NULL; /* caller will probably crash: no choice here */ + } + } + + /* Finally fill in the function, now we have a satisfactory buffer. It is + * valid to change the function on every call. + */ + png_ptr->longjmp_fn = longjmp_fn; + return png_ptr->jmp_buf_ptr; +} + +void /* PRIVATE */ +png_free_jmpbuf(png_structrp png_ptr) +{ + if (png_ptr != NULL) + { + jmp_buf *jb = png_ptr->jmp_buf_ptr; + + /* A size of 0 is used to indicate a local, stack, allocation of the + * pointer; used here and in png.c + */ + if (jb != NULL && png_ptr->jmp_buf_size > 0) + { + + /* This stuff is so that a failure to free the error control structure + * does not leave libpng in a state with no valid error handling: the + * free always succeeds, if there is an error it gets ignored. + */ + if (jb != &png_ptr->jmp_buf_local) + { + /* Make an internal, libpng, jmp_buf to return here */ + jmp_buf free_jmp_buf; + + if (!setjmp(free_jmp_buf)) + { + png_ptr->jmp_buf_ptr = &free_jmp_buf; /* come back here */ + png_ptr->jmp_buf_size = 0; /* stack allocation */ + png_ptr->longjmp_fn = longjmp; + png_free(png_ptr, jb); /* Return to setjmp on error */ + } + } + } + + /* *Always* cancel everything out: */ + png_ptr->jmp_buf_size = 0; + png_ptr->jmp_buf_ptr = NULL; + png_ptr->longjmp_fn = 0; + } +} +#endif + +/* This is the default error handling function. Note that replacements for + * this function MUST NOT RETURN, or the program will likely crash. This + * function is used by default, or if the program supplies NULL for the + * error function pointer in png_set_error_fn(). + */ +static PNG_FUNCTION(void /* PRIVATE */, +png_default_error,(png_const_structrp png_ptr, png_const_charp error_message), + PNG_NORETURN) +{ +#ifdef PNG_CONSOLE_IO_SUPPORTED +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + /* Check on NULL only added in 1.5.4 */ + if (error_message != NULL && *error_message == PNG_LITERAL_SHARP) + { + /* Strip "#nnnn " from beginning of error message. */ + int offset; + char error_number[16]; + for (offset = 0; offset<15; offset++) + { + error_number[offset] = error_message[offset + 1]; + if (error_message[offset] == ' ') + break; + } + + if ((offset > 1) && (offset < 15)) + { + error_number[offset - 1] = '\0'; + fprintf(stderr, "libpng error no. %s: %s", + error_number, error_message + offset + 1); + fprintf(stderr, PNG_STRING_NEWLINE); + } + + else + { + fprintf(stderr, "libpng error: %s, offset=%d", + error_message, offset); + fprintf(stderr, PNG_STRING_NEWLINE); + } + } + else +#endif + { + fprintf(stderr, "libpng error: %s", error_message ? error_message : + "undefined"); + fprintf(stderr, PNG_STRING_NEWLINE); + } +#else + PNG_UNUSED(error_message) /* Make compiler happy */ +#endif + png_longjmp(png_ptr, 1); +} + +PNG_FUNCTION(void,PNGAPI +png_longjmp,(png_const_structrp png_ptr, int val),PNG_NORETURN) +{ +#ifdef PNG_SETJMP_SUPPORTED + if (png_ptr != NULL && png_ptr->longjmp_fn != NULL && + png_ptr->jmp_buf_ptr != NULL) + png_ptr->longjmp_fn(*png_ptr->jmp_buf_ptr, val); +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(val) +#endif + + /* If control reaches this point, png_longjmp() must not return. The only + * choice is to terminate the whole process (or maybe the thread); to do + * this the ANSI-C abort() function is used unless a different method is + * implemented by overriding the default configuration setting for + * PNG_ABORT(). + */ + PNG_ABORT(); +} + +#ifdef PNG_WARNINGS_SUPPORTED +/* This function is called when there is a warning, but the library thinks + * it can continue anyway. Replacement functions don't have to do anything + * here if you don't want them to. In the default configuration, png_ptr is + * not used, but it is passed in case it may be useful. + */ +static void /* PRIVATE */ +png_default_warning(png_const_structrp png_ptr, png_const_charp warning_message) +{ +#ifdef PNG_CONSOLE_IO_SUPPORTED +# ifdef PNG_ERROR_NUMBERS_SUPPORTED + if (*warning_message == PNG_LITERAL_SHARP) + { + int offset; + char warning_number[16]; + for (offset = 0; offset < 15; offset++) + { + warning_number[offset] = warning_message[offset + 1]; + if (warning_message[offset] == ' ') + break; + } + + if ((offset > 1) && (offset < 15)) + { + warning_number[offset + 1] = '\0'; + fprintf(stderr, "libpng warning no. %s: %s", + warning_number, warning_message + offset); + fprintf(stderr, PNG_STRING_NEWLINE); + } + + else + { + fprintf(stderr, "libpng warning: %s", + warning_message); + fprintf(stderr, PNG_STRING_NEWLINE); + } + } + else +# endif + + { + fprintf(stderr, "libpng warning: %s", warning_message); + fprintf(stderr, PNG_STRING_NEWLINE); + } +#else + PNG_UNUSED(warning_message) /* Make compiler happy */ +#endif + PNG_UNUSED(png_ptr) /* Make compiler happy */ +} +#endif /* WARNINGS */ + +/* This function is called when the application wants to use another method + * of handling errors and warnings. Note that the error function MUST NOT + * return to the calling routine or serious problems will occur. The return + * method used in the default routine calls longjmp(png_ptr->jmp_buf_ptr, 1) + */ +void PNGAPI +png_set_error_fn(png_structrp png_ptr, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warning_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->error_ptr = error_ptr; + png_ptr->error_fn = error_fn; +#ifdef PNG_WARNINGS_SUPPORTED + png_ptr->warning_fn = warning_fn; +#else + PNG_UNUSED(warning_fn) +#endif +} + + +/* This function returns a pointer to the error_ptr associated with the user + * functions. The application should free any memory associated with this + * pointer before png_write_destroy and png_read_destroy are called. + */ +png_voidp PNGAPI +png_get_error_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return NULL; + + return ((png_voidp)png_ptr->error_ptr); +} + + +#ifdef PNG_ERROR_NUMBERS_SUPPORTED +void PNGAPI +png_set_strip_error_numbers(png_structrp png_ptr, png_uint_32 strip_mode) +{ + if (png_ptr != NULL) + { + png_ptr->flags &= + ((~(PNG_FLAG_STRIP_ERROR_NUMBERS | + PNG_FLAG_STRIP_ERROR_TEXT))&strip_mode); + } +} +#endif + +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) + /* Currently the above both depend on SETJMP_SUPPORTED, however it would be + * possible to implement without setjmp support just so long as there is some + * way to handle the error return here: + */ +PNG_FUNCTION(void /* PRIVATE */, (PNGCBAPI +png_safe_error),(png_structp png_nonconst_ptr, png_const_charp error_message), + PNG_NORETURN) +{ + png_const_structrp png_ptr = png_nonconst_ptr; + png_imagep image = png_voidcast(png_imagep, png_ptr->error_ptr); + + /* An error is always logged here, overwriting anything (typically a warning) + * that is already there: + */ + if (image != NULL) + { + png_safecat(image->message, (sizeof image->message), 0, error_message); + image->warning_or_error |= PNG_IMAGE_ERROR; + + /* Retrieve the jmp_buf from within the png_control, making this work for + * C++ compilation too is pretty tricky: C++ wants a pointer to the first + * element of a jmp_buf, but C doesn't tell us the type of that. + */ + if (image->opaque != NULL && image->opaque->error_buf != NULL) + longjmp(png_control_jmp_buf(image->opaque), 1); + + /* Missing longjmp buffer, the following is to help debugging: */ + { + size_t pos = png_safecat(image->message, (sizeof image->message), 0, + "bad longjmp: "); + png_safecat(image->message, (sizeof image->message), pos, + error_message); + } + } + + /* Here on an internal programming error. */ + abort(); +} + +#ifdef PNG_WARNINGS_SUPPORTED +void /* PRIVATE */ PNGCBAPI +png_safe_warning(png_structp png_nonconst_ptr, png_const_charp warning_message) +{ + png_const_structrp png_ptr = png_nonconst_ptr; + png_imagep image = png_voidcast(png_imagep, png_ptr->error_ptr); + + /* A warning is only logged if there is no prior warning or error. */ + if (image->warning_or_error == 0) + { + png_safecat(image->message, (sizeof image->message), 0, warning_message); + image->warning_or_error |= PNG_IMAGE_WARNING; + } +} +#endif + +int /* PRIVATE */ +png_safe_execute(png_imagep image_in, int (*function)(png_voidp), png_voidp arg) +{ + volatile png_imagep image = image_in; + volatile int result; + volatile png_voidp saved_error_buf; + jmp_buf safe_jmpbuf; + + /* Safely execute function(arg) with png_error returning to this function. */ + saved_error_buf = image->opaque->error_buf; + result = setjmp(safe_jmpbuf) == 0; + + if (result != 0) + { + + image->opaque->error_buf = safe_jmpbuf; + result = function(arg); + } + + image->opaque->error_buf = saved_error_buf; + + /* And do the cleanup prior to any failure return. */ + if (result == 0) + png_image_free(image); + + return result; +} +#endif /* SIMPLIFIED READ || SIMPLIFIED_WRITE */ +#endif /* READ || WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngget.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngget.c new file mode 100644 index 0000000..5abf1ef --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngget.c @@ -0,0 +1,1249 @@ + +/* pngget.c - retrieval of values from info struct + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +png_uint_32 PNGAPI +png_get_valid(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 flag) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->valid & flag); + + return(0); +} + +size_t PNGAPI +png_get_rowbytes(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->rowbytes); + + return(0); +} + +#ifdef PNG_INFO_IMAGE_SUPPORTED +png_bytepp PNGAPI +png_get_rows(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->row_pointers); + + return(0); +} +#endif + +#ifdef PNG_EASY_ACCESS_SUPPORTED +/* Easy access to info, added in libpng-0.99 */ +png_uint_32 PNGAPI +png_get_image_width(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->width; + + return (0); +} + +png_uint_32 PNGAPI +png_get_image_height(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->height; + + return (0); +} + +png_byte PNGAPI +png_get_bit_depth(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->bit_depth; + + return (0); +} + +png_byte PNGAPI +png_get_color_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->color_type; + + return (0); +} + +png_byte PNGAPI +png_get_filter_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->filter_type; + + return (0); +} + +png_byte PNGAPI +png_get_interlace_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->interlace_type; + + return (0); +} + +png_byte PNGAPI +png_get_compression_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->compression_type; + + return (0); +} + +png_uint_32 PNGAPI +png_get_x_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp + info_ptr) +{ +#ifdef PNG_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs) != 0) + { + png_debug1(1, "in %s retrieval function", + "png_get_x_pixels_per_meter"); + + if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) + return (info_ptr->x_pixels_per_unit); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return (0); +} + +png_uint_32 PNGAPI +png_get_y_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp + info_ptr) +{ +#ifdef PNG_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs) != 0) + { + png_debug1(1, "in %s retrieval function", + "png_get_y_pixels_per_meter"); + + if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) + return (info_ptr->y_pixels_per_unit); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return (0); +} + +png_uint_32 PNGAPI +png_get_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs) != 0) + { + png_debug1(1, "in %s retrieval function", "png_get_pixels_per_meter"); + + if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER && + info_ptr->x_pixels_per_unit == info_ptr->y_pixels_per_unit) + return (info_ptr->x_pixels_per_unit); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return (0); +} + +#ifdef PNG_FLOATING_POINT_SUPPORTED +float PNGAPI +png_get_pixel_aspect_ratio(png_const_structrp png_ptr, png_const_inforp + info_ptr) +{ +#ifdef PNG_READ_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs) != 0) + { + png_debug1(1, "in %s retrieval function", "png_get_aspect_ratio"); + + if (info_ptr->x_pixels_per_unit != 0) + return ((float)((float)info_ptr->y_pixels_per_unit + /(float)info_ptr->x_pixels_per_unit)); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return ((float)0.0); +} +#endif + +#ifdef PNG_FIXED_POINT_SUPPORTED +png_fixed_point PNGAPI +png_get_pixel_aspect_ratio_fixed(png_const_structrp png_ptr, + png_const_inforp info_ptr) +{ +#ifdef PNG_READ_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs) != 0 && + info_ptr->x_pixels_per_unit > 0 && info_ptr->y_pixels_per_unit > 0 && + info_ptr->x_pixels_per_unit <= PNG_UINT_31_MAX && + info_ptr->y_pixels_per_unit <= PNG_UINT_31_MAX) + { + png_fixed_point res; + + png_debug1(1, "in %s retrieval function", "png_get_aspect_ratio_fixed"); + + /* The following casts work because a PNG 4 byte integer only has a valid + * range of 0..2^31-1; otherwise the cast might overflow. + */ + if (png_muldiv(&res, (png_int_32)info_ptr->y_pixels_per_unit, PNG_FP_1, + (png_int_32)info_ptr->x_pixels_per_unit) != 0) + return res; + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return 0; +} +#endif + +png_int_32 PNGAPI +png_get_x_offset_microns(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_oFFs) != 0) + { + png_debug1(1, "in %s retrieval function", "png_get_x_offset_microns"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) + return (info_ptr->x_offset); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return (0); +} + +png_int_32 PNGAPI +png_get_y_offset_microns(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_oFFs) != 0) + { + png_debug1(1, "in %s retrieval function", "png_get_y_offset_microns"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) + return (info_ptr->y_offset); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return (0); +} + +png_int_32 PNGAPI +png_get_x_offset_pixels(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_oFFs) != 0) + { + png_debug1(1, "in %s retrieval function", "png_get_x_offset_pixels"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) + return (info_ptr->x_offset); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return (0); +} + +png_int_32 PNGAPI +png_get_y_offset_pixels(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_oFFs) != 0) + { + png_debug1(1, "in %s retrieval function", "png_get_y_offset_pixels"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) + return (info_ptr->y_offset); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return (0); +} + +#ifdef PNG_INCH_CONVERSIONS_SUPPORTED +static png_uint_32 +ppi_from_ppm(png_uint_32 ppm) +{ +#if 0 + /* The conversion is *(2.54/100), in binary (32 digits): + * .00000110100000001001110101001001 + */ + png_uint_32 t1001, t1101; + ppm >>= 1; /* .1 */ + t1001 = ppm + (ppm >> 3); /* .1001 */ + t1101 = t1001 + (ppm >> 1); /* .1101 */ + ppm >>= 20; /* .000000000000000000001 */ + t1101 += t1101 >> 15; /* .1101000000000001101 */ + t1001 >>= 11; /* .000000000001001 */ + t1001 += t1001 >> 12; /* .000000000001001000000001001 */ + ppm += t1001; /* .000000000001001000001001001 */ + ppm += t1101; /* .110100000001001110101001001 */ + return (ppm + 16) >> 5;/* .00000110100000001001110101001001 */ +#else + /* The argument is a PNG unsigned integer, so it is not permitted + * to be bigger than 2^31. + */ + png_fixed_point result; + if (ppm <= PNG_UINT_31_MAX && png_muldiv(&result, (png_int_32)ppm, 127, + 5000) != 0) + return (png_uint_32)result; + + /* Overflow. */ + return 0; +#endif +} + +png_uint_32 PNGAPI +png_get_pixels_per_inch(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + return ppi_from_ppm(png_get_pixels_per_meter(png_ptr, info_ptr)); +} + +png_uint_32 PNGAPI +png_get_x_pixels_per_inch(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + return ppi_from_ppm(png_get_x_pixels_per_meter(png_ptr, info_ptr)); +} + +png_uint_32 PNGAPI +png_get_y_pixels_per_inch(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + return ppi_from_ppm(png_get_y_pixels_per_meter(png_ptr, info_ptr)); +} + +#ifdef PNG_FIXED_POINT_SUPPORTED +static png_fixed_point +png_fixed_inches_from_microns(png_const_structrp png_ptr, png_int_32 microns) +{ + /* Convert from meters * 1,000,000 to inches * 100,000, meters to + * inches is simply *(100/2.54), so we want *(10/2.54) == 500/127. + * Notice that this can overflow - a warning is output and 0 is + * returned. + */ + return png_muldiv_warn(png_ptr, microns, 500, 127); +} + +png_fixed_point PNGAPI +png_get_x_offset_inches_fixed(png_const_structrp png_ptr, + png_const_inforp info_ptr) +{ + return png_fixed_inches_from_microns(png_ptr, + png_get_x_offset_microns(png_ptr, info_ptr)); +} +#endif + +#ifdef PNG_FIXED_POINT_SUPPORTED +png_fixed_point PNGAPI +png_get_y_offset_inches_fixed(png_const_structrp png_ptr, + png_const_inforp info_ptr) +{ + return png_fixed_inches_from_microns(png_ptr, + png_get_y_offset_microns(png_ptr, info_ptr)); +} +#endif + +#ifdef PNG_FLOATING_POINT_SUPPORTED +float PNGAPI +png_get_x_offset_inches(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + /* To avoid the overflow do the conversion directly in floating + * point. + */ + return (float)(png_get_x_offset_microns(png_ptr, info_ptr) * .00003937); +} +#endif + +#ifdef PNG_FLOATING_POINT_SUPPORTED +float PNGAPI +png_get_y_offset_inches(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + /* To avoid the overflow do the conversion directly in floating + * point. + */ + return (float)(png_get_y_offset_microns(png_ptr, info_ptr) * .00003937); +} +#endif + +#ifdef PNG_pHYs_SUPPORTED +png_uint_32 PNGAPI +png_get_pHYs_dpi(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type) +{ + png_uint_32 retval = 0; + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs) != 0) + { + png_debug1(1, "in %s retrieval function", "pHYs"); + + if (res_x != NULL) + { + *res_x = info_ptr->x_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (res_y != NULL) + { + *res_y = info_ptr->y_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (unit_type != NULL) + { + *unit_type = (int)info_ptr->phys_unit_type; + retval |= PNG_INFO_pHYs; + + if (*unit_type == 1) + { + if (res_x != NULL) *res_x = (png_uint_32)(*res_x * .0254 + .50); + if (res_y != NULL) *res_y = (png_uint_32)(*res_y * .0254 + .50); + } + } + } + + return (retval); +} +#endif /* pHYs */ +#endif /* INCH_CONVERSIONS */ + +/* png_get_channels really belongs in here, too, but it's been around longer */ + +#endif /* EASY_ACCESS */ + + +png_byte PNGAPI +png_get_channels(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->channels); + + return (0); +} + +#ifdef PNG_READ_SUPPORTED +png_const_bytep PNGAPI +png_get_signature(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->signature); + + return (NULL); +} +#endif + +#ifdef PNG_bKGD_SUPPORTED +png_uint_32 PNGAPI +png_get_bKGD(png_const_structrp png_ptr, png_inforp info_ptr, + png_color_16p *background) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_bKGD) != 0 && + background != NULL) + { + png_debug1(1, "in %s retrieval function", "bKGD"); + + *background = &(info_ptr->background); + return (PNG_INFO_bKGD); + } + + return (0); +} +#endif + +#ifdef PNG_cHRM_SUPPORTED +/* The XYZ APIs were added in 1.5.5 to take advantage of the code added at the + * same time to correct the rgb grayscale coefficient defaults obtained from the + * cHRM chunk in 1.5.4 + */ +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_cHRM(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *white_x, double *white_y, double *red_x, double *red_y, + double *green_x, double *green_y, double *blue_x, double *blue_y) +{ + /* Quiet API change: this code used to only return the end points if a cHRM + * chunk was present, but the end points can also come from iCCP or sRGB + * chunks, so in 1.6.0 the png_get_ APIs return the end points regardless and + * the png_set_ APIs merely check that set end points are mutually + * consistent. + */ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + { + png_debug1(1, "in %s retrieval function", "cHRM"); + + if (white_x != NULL) + *white_x = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.whitex, "cHRM white X"); + if (white_y != NULL) + *white_y = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.whitey, "cHRM white Y"); + if (red_x != NULL) + *red_x = png_float(png_ptr, info_ptr->colorspace.end_points_xy.redx, + "cHRM red X"); + if (red_y != NULL) + *red_y = png_float(png_ptr, info_ptr->colorspace.end_points_xy.redy, + "cHRM red Y"); + if (green_x != NULL) + *green_x = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.greenx, "cHRM green X"); + if (green_y != NULL) + *green_y = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.greeny, "cHRM green Y"); + if (blue_x != NULL) + *blue_x = png_float(png_ptr, info_ptr->colorspace.end_points_xy.bluex, + "cHRM blue X"); + if (blue_y != NULL) + *blue_y = png_float(png_ptr, info_ptr->colorspace.end_points_xy.bluey, + "cHRM blue Y"); + return (PNG_INFO_cHRM); + } + + return (0); +} + +png_uint_32 PNGAPI +png_get_cHRM_XYZ(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *red_X, double *red_Y, double *red_Z, double *green_X, + double *green_Y, double *green_Z, double *blue_X, double *blue_Y, + double *blue_Z) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + { + png_debug1(1, "in %s retrieval function", "cHRM_XYZ(float)"); + + if (red_X != NULL) + *red_X = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.red_X, + "cHRM red X"); + if (red_Y != NULL) + *red_Y = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.red_Y, + "cHRM red Y"); + if (red_Z != NULL) + *red_Z = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.red_Z, + "cHRM red Z"); + if (green_X != NULL) + *green_X = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.green_X, "cHRM green X"); + if (green_Y != NULL) + *green_Y = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.green_Y, "cHRM green Y"); + if (green_Z != NULL) + *green_Z = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.green_Z, "cHRM green Z"); + if (blue_X != NULL) + *blue_X = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.blue_X, "cHRM blue X"); + if (blue_Y != NULL) + *blue_Y = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.blue_Y, "cHRM blue Y"); + if (blue_Z != NULL) + *blue_Z = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.blue_Z, "cHRM blue Z"); + return (PNG_INFO_cHRM); + } + + return (0); +} +# endif + +# ifdef PNG_FIXED_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_cHRM_XYZ_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_red_X, png_fixed_point *int_red_Y, + png_fixed_point *int_red_Z, png_fixed_point *int_green_X, + png_fixed_point *int_green_Y, png_fixed_point *int_green_Z, + png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y, + png_fixed_point *int_blue_Z) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + { + png_debug1(1, "in %s retrieval function", "cHRM_XYZ"); + + if (int_red_X != NULL) + *int_red_X = info_ptr->colorspace.end_points_XYZ.red_X; + if (int_red_Y != NULL) + *int_red_Y = info_ptr->colorspace.end_points_XYZ.red_Y; + if (int_red_Z != NULL) + *int_red_Z = info_ptr->colorspace.end_points_XYZ.red_Z; + if (int_green_X != NULL) + *int_green_X = info_ptr->colorspace.end_points_XYZ.green_X; + if (int_green_Y != NULL) + *int_green_Y = info_ptr->colorspace.end_points_XYZ.green_Y; + if (int_green_Z != NULL) + *int_green_Z = info_ptr->colorspace.end_points_XYZ.green_Z; + if (int_blue_X != NULL) + *int_blue_X = info_ptr->colorspace.end_points_XYZ.blue_X; + if (int_blue_Y != NULL) + *int_blue_Y = info_ptr->colorspace.end_points_XYZ.blue_Y; + if (int_blue_Z != NULL) + *int_blue_Z = info_ptr->colorspace.end_points_XYZ.blue_Z; + return (PNG_INFO_cHRM); + } + + return (0); +} + +png_uint_32 PNGAPI +png_get_cHRM_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *white_x, png_fixed_point *white_y, png_fixed_point *red_x, + png_fixed_point *red_y, png_fixed_point *green_x, png_fixed_point *green_y, + png_fixed_point *blue_x, png_fixed_point *blue_y) +{ + png_debug1(1, "in %s retrieval function", "cHRM"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + { + if (white_x != NULL) + *white_x = info_ptr->colorspace.end_points_xy.whitex; + if (white_y != NULL) + *white_y = info_ptr->colorspace.end_points_xy.whitey; + if (red_x != NULL) + *red_x = info_ptr->colorspace.end_points_xy.redx; + if (red_y != NULL) + *red_y = info_ptr->colorspace.end_points_xy.redy; + if (green_x != NULL) + *green_x = info_ptr->colorspace.end_points_xy.greenx; + if (green_y != NULL) + *green_y = info_ptr->colorspace.end_points_xy.greeny; + if (blue_x != NULL) + *blue_x = info_ptr->colorspace.end_points_xy.bluex; + if (blue_y != NULL) + *blue_y = info_ptr->colorspace.end_points_xy.bluey; + return (PNG_INFO_cHRM); + } + + return (0); +} +# endif +#endif + +#ifdef PNG_gAMA_SUPPORTED +# ifdef PNG_FIXED_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_gAMA_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *file_gamma) +{ + png_debug1(1, "in %s retrieval function", "gAMA"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) != 0 && + file_gamma != NULL) + { + *file_gamma = info_ptr->colorspace.gamma; + return (PNG_INFO_gAMA); + } + + return (0); +} +# endif + +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_gAMA(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *file_gamma) +{ + png_debug1(1, "in %s retrieval function", "gAMA(float)"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) != 0 && + file_gamma != NULL) + { + *file_gamma = png_float(png_ptr, info_ptr->colorspace.gamma, + "png_get_gAMA"); + return (PNG_INFO_gAMA); + } + + return (0); +} +# endif +#endif + +#ifdef PNG_sRGB_SUPPORTED +png_uint_32 PNGAPI +png_get_sRGB(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *file_srgb_intent) +{ + png_debug1(1, "in %s retrieval function", "sRGB"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sRGB) != 0 && file_srgb_intent != NULL) + { + *file_srgb_intent = info_ptr->colorspace.rendering_intent; + return (PNG_INFO_sRGB); + } + + return (0); +} +#endif + +#ifdef PNG_iCCP_SUPPORTED +png_uint_32 PNGAPI +png_get_iCCP(png_const_structrp png_ptr, png_inforp info_ptr, + png_charpp name, int *compression_type, + png_bytepp profile, png_uint_32 *proflen) +{ + png_debug1(1, "in %s retrieval function", "iCCP"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_iCCP) != 0 && + name != NULL && profile != NULL && proflen != NULL) + { + *name = info_ptr->iccp_name; + *profile = info_ptr->iccp_profile; + *proflen = png_get_uint_32(info_ptr->iccp_profile); + /* This is somewhat irrelevant since the profile data returned has + * actually been uncompressed. + */ + if (compression_type != NULL) + *compression_type = PNG_COMPRESSION_TYPE_BASE; + return (PNG_INFO_iCCP); + } + + return (0); + +} +#endif + +#ifdef PNG_sPLT_SUPPORTED +int PNGAPI +png_get_sPLT(png_const_structrp png_ptr, png_inforp info_ptr, + png_sPLT_tpp spalettes) +{ + if (png_ptr != NULL && info_ptr != NULL && spalettes != NULL) + { + *spalettes = info_ptr->splt_palettes; + return info_ptr->splt_palettes_num; + } + + return (0); +} +#endif + +#ifdef PNG_eXIf_SUPPORTED +png_uint_32 PNGAPI +png_get_eXIf(png_const_structrp png_ptr, png_inforp info_ptr, + png_bytep *exif) +{ + png_warning(png_ptr, "png_get_eXIf does not work; use png_get_eXIf_1"); + PNG_UNUSED(info_ptr) + PNG_UNUSED(exif) + return 0; +} + +png_uint_32 PNGAPI +png_get_eXIf_1(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 *num_exif, png_bytep *exif) +{ + png_debug1(1, "in %s retrieval function", "eXIf"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_eXIf) != 0 && exif != NULL) + { + *num_exif = info_ptr->num_exif; + *exif = info_ptr->exif; + return (PNG_INFO_eXIf); + } + + return (0); +} +#endif + +#ifdef PNG_hIST_SUPPORTED +png_uint_32 PNGAPI +png_get_hIST(png_const_structrp png_ptr, png_inforp info_ptr, + png_uint_16p *hist) +{ + png_debug1(1, "in %s retrieval function", "hIST"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_hIST) != 0 && hist != NULL) + { + *hist = info_ptr->hist; + return (PNG_INFO_hIST); + } + + return (0); +} +#endif + +png_uint_32 PNGAPI +png_get_IHDR(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 *width, png_uint_32 *height, int *bit_depth, + int *color_type, int *interlace_type, int *compression_type, + int *filter_type) +{ + png_debug1(1, "in %s retrieval function", "IHDR"); + + if (png_ptr == NULL || info_ptr == NULL) + return (0); + + if (width != NULL) + *width = info_ptr->width; + + if (height != NULL) + *height = info_ptr->height; + + if (bit_depth != NULL) + *bit_depth = info_ptr->bit_depth; + + if (color_type != NULL) + *color_type = info_ptr->color_type; + + if (compression_type != NULL) + *compression_type = info_ptr->compression_type; + + if (filter_type != NULL) + *filter_type = info_ptr->filter_type; + + if (interlace_type != NULL) + *interlace_type = info_ptr->interlace_type; + + /* This is redundant if we can be sure that the info_ptr values were all + * assigned in png_set_IHDR(). We do the check anyhow in case an + * application has ignored our advice not to mess with the members + * of info_ptr directly. + */ + png_check_IHDR(png_ptr, info_ptr->width, info_ptr->height, + info_ptr->bit_depth, info_ptr->color_type, info_ptr->interlace_type, + info_ptr->compression_type, info_ptr->filter_type); + + return (1); +} + +#ifdef PNG_oFFs_SUPPORTED +png_uint_32 PNGAPI +png_get_oFFs(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_int_32 *offset_x, png_int_32 *offset_y, int *unit_type) +{ + png_debug1(1, "in %s retrieval function", "oFFs"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_oFFs) != 0 && + offset_x != NULL && offset_y != NULL && unit_type != NULL) + { + *offset_x = info_ptr->x_offset; + *offset_y = info_ptr->y_offset; + *unit_type = (int)info_ptr->offset_unit_type; + return (PNG_INFO_oFFs); + } + + return (0); +} +#endif + +#ifdef PNG_pCAL_SUPPORTED +png_uint_32 PNGAPI +png_get_pCAL(png_const_structrp png_ptr, png_inforp info_ptr, + png_charp *purpose, png_int_32 *X0, png_int_32 *X1, int *type, int *nparams, + png_charp *units, png_charpp *params) +{ + png_debug1(1, "in %s retrieval function", "pCAL"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pCAL) != 0 && + purpose != NULL && X0 != NULL && X1 != NULL && type != NULL && + nparams != NULL && units != NULL && params != NULL) + { + *purpose = info_ptr->pcal_purpose; + *X0 = info_ptr->pcal_X0; + *X1 = info_ptr->pcal_X1; + *type = (int)info_ptr->pcal_type; + *nparams = (int)info_ptr->pcal_nparams; + *units = info_ptr->pcal_units; + *params = info_ptr->pcal_params; + return (PNG_INFO_pCAL); + } + + return (0); +} +#endif + +#ifdef PNG_sCAL_SUPPORTED +# ifdef PNG_FIXED_POINT_SUPPORTED +# if defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) || \ + defined(PNG_FLOATING_POINT_SUPPORTED) +png_uint_32 PNGAPI +png_get_sCAL_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *unit, png_fixed_point *width, png_fixed_point *height) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sCAL) != 0) + { + *unit = info_ptr->scal_unit; + /*TODO: make this work without FP support; the API is currently eliminated + * if neither floating point APIs nor internal floating point arithmetic + * are enabled. + */ + *width = png_fixed(png_ptr, atof(info_ptr->scal_s_width), "sCAL width"); + *height = png_fixed(png_ptr, atof(info_ptr->scal_s_height), + "sCAL height"); + return (PNG_INFO_sCAL); + } + + return(0); +} +# endif /* FLOATING_ARITHMETIC */ +# endif /* FIXED_POINT */ +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_sCAL(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *unit, double *width, double *height) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sCAL) != 0) + { + *unit = info_ptr->scal_unit; + *width = atof(info_ptr->scal_s_width); + *height = atof(info_ptr->scal_s_height); + return (PNG_INFO_sCAL); + } + + return(0); +} +# endif /* FLOATING POINT */ +png_uint_32 PNGAPI +png_get_sCAL_s(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *unit, png_charpp width, png_charpp height) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sCAL) != 0) + { + *unit = info_ptr->scal_unit; + *width = info_ptr->scal_s_width; + *height = info_ptr->scal_s_height; + return (PNG_INFO_sCAL); + } + + return(0); +} +#endif /* sCAL */ + +#ifdef PNG_pHYs_SUPPORTED +png_uint_32 PNGAPI +png_get_pHYs(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type) +{ + png_uint_32 retval = 0; + + png_debug1(1, "in %s retrieval function", "pHYs"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs) != 0) + { + if (res_x != NULL) + { + *res_x = info_ptr->x_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (res_y != NULL) + { + *res_y = info_ptr->y_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (unit_type != NULL) + { + *unit_type = (int)info_ptr->phys_unit_type; + retval |= PNG_INFO_pHYs; + } + } + + return (retval); +} +#endif /* pHYs */ + +png_uint_32 PNGAPI +png_get_PLTE(png_const_structrp png_ptr, png_inforp info_ptr, + png_colorp *palette, int *num_palette) +{ + png_debug1(1, "in %s retrieval function", "PLTE"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_PLTE) != 0 && palette != NULL) + { + *palette = info_ptr->palette; + *num_palette = info_ptr->num_palette; + png_debug1(3, "num_palette = %d", *num_palette); + return (PNG_INFO_PLTE); + } + + return (0); +} + +#ifdef PNG_sBIT_SUPPORTED +png_uint_32 PNGAPI +png_get_sBIT(png_const_structrp png_ptr, png_inforp info_ptr, + png_color_8p *sig_bit) +{ + png_debug1(1, "in %s retrieval function", "sBIT"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sBIT) != 0 && sig_bit != NULL) + { + *sig_bit = &(info_ptr->sig_bit); + return (PNG_INFO_sBIT); + } + + return (0); +} +#endif + +#ifdef PNG_TEXT_SUPPORTED +int PNGAPI +png_get_text(png_const_structrp png_ptr, png_inforp info_ptr, + png_textp *text_ptr, int *num_text) +{ + if (png_ptr != NULL && info_ptr != NULL && info_ptr->num_text > 0) + { + png_debug1(1, "in 0x%lx retrieval function", + (unsigned long)png_ptr->chunk_name); + + if (text_ptr != NULL) + *text_ptr = info_ptr->text; + + if (num_text != NULL) + *num_text = info_ptr->num_text; + + return info_ptr->num_text; + } + + if (num_text != NULL) + *num_text = 0; + + return(0); +} +#endif + +#ifdef PNG_tIME_SUPPORTED +png_uint_32 PNGAPI +png_get_tIME(png_const_structrp png_ptr, png_inforp info_ptr, + png_timep *mod_time) +{ + png_debug1(1, "in %s retrieval function", "tIME"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_tIME) != 0 && mod_time != NULL) + { + *mod_time = &(info_ptr->mod_time); + return (PNG_INFO_tIME); + } + + return (0); +} +#endif + +#ifdef PNG_tRNS_SUPPORTED +png_uint_32 PNGAPI +png_get_tRNS(png_const_structrp png_ptr, png_inforp info_ptr, + png_bytep *trans_alpha, int *num_trans, png_color_16p *trans_color) +{ + png_uint_32 retval = 0; + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_tRNS) != 0) + { + png_debug1(1, "in %s retrieval function", "tRNS"); + + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (trans_alpha != NULL) + { + *trans_alpha = info_ptr->trans_alpha; + retval |= PNG_INFO_tRNS; + } + + if (trans_color != NULL) + *trans_color = &(info_ptr->trans_color); + } + + else /* if (info_ptr->color_type != PNG_COLOR_TYPE_PALETTE) */ + { + if (trans_color != NULL) + { + *trans_color = &(info_ptr->trans_color); + retval |= PNG_INFO_tRNS; + } + + if (trans_alpha != NULL) + *trans_alpha = NULL; + } + + if (num_trans != NULL) + { + *num_trans = info_ptr->num_trans; + retval |= PNG_INFO_tRNS; + } + } + + return (retval); +} +#endif + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +int PNGAPI +png_get_unknown_chunks(png_const_structrp png_ptr, png_inforp info_ptr, + png_unknown_chunkpp unknowns) +{ + if (png_ptr != NULL && info_ptr != NULL && unknowns != NULL) + { + *unknowns = info_ptr->unknown_chunks; + return info_ptr->unknown_chunks_num; + } + + return (0); +} +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +png_byte PNGAPI +png_get_rgb_to_gray_status (png_const_structrp png_ptr) +{ + return (png_byte)(png_ptr ? png_ptr->rgb_to_gray_status : 0); +} +#endif + +#ifdef PNG_USER_CHUNKS_SUPPORTED +png_voidp PNGAPI +png_get_user_chunk_ptr(png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_chunk_ptr : NULL); +} +#endif + +size_t PNGAPI +png_get_compression_buffer_size(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return 0; + +#ifdef PNG_WRITE_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) +#endif + { +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED + return png_ptr->IDAT_read_size; +#else + return PNG_IDAT_READ_SIZE; +#endif + } + +#ifdef PNG_WRITE_SUPPORTED + else + return png_ptr->zbuffer_size; +#endif +} + +#ifdef PNG_SET_USER_LIMITS_SUPPORTED +/* These functions were added to libpng 1.2.6 and were enabled + * by default in libpng-1.4.0 */ +png_uint_32 PNGAPI +png_get_user_width_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_width_max : 0); +} + +png_uint_32 PNGAPI +png_get_user_height_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_height_max : 0); +} + +/* This function was added to libpng 1.4.0 */ +png_uint_32 PNGAPI +png_get_chunk_cache_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_chunk_cache_max : 0); +} + +/* This function was added to libpng 1.4.1 */ +png_alloc_size_t PNGAPI +png_get_chunk_malloc_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_chunk_malloc_max : 0); +} +#endif /* SET_USER_LIMITS */ + +/* These functions were added to libpng 1.4.0 */ +#ifdef PNG_IO_STATE_SUPPORTED +png_uint_32 PNGAPI +png_get_io_state (png_const_structrp png_ptr) +{ + return png_ptr->io_state; +} + +png_uint_32 PNGAPI +png_get_io_chunk_type (png_const_structrp png_ptr) +{ + return png_ptr->chunk_name; +} +#endif /* IO_STATE */ + +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +# ifdef PNG_GET_PALETTE_MAX_SUPPORTED +int PNGAPI +png_get_palette_max(png_const_structp png_ptr, png_const_infop info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return png_ptr->num_palette_max; + + return (-1); +} +# endif +#endif + +#endif /* READ || WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnginfo.h b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnginfo.h new file mode 100644 index 0000000..1f98ded --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnginfo.h @@ -0,0 +1,267 @@ + +/* pnginfo.h - header file for PNG reference library + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2013,2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + + /* png_info is a structure that holds the information in a PNG file so + * that the application can find out the characteristics of the image. + * If you are reading the file, this structure will tell you what is + * in the PNG file. If you are writing the file, fill in the information + * you want to put into the PNG file, using png_set_*() functions, then + * call png_write_info(). + * + * The names chosen should be very close to the PNG specification, so + * consult that document for information about the meaning of each field. + * + * With libpng < 0.95, it was only possible to directly set and read the + * the values in the png_info_struct, which meant that the contents and + * order of the values had to remain fixed. With libpng 0.95 and later, + * however, there are now functions that abstract the contents of + * png_info_struct from the application, so this makes it easier to use + * libpng with dynamic libraries, and even makes it possible to use + * libraries that don't have all of the libpng ancillary chunk-handing + * functionality. In libpng-1.5.0 this was moved into a separate private + * file that is not visible to applications. + * + * The following members may have allocated storage attached that should be + * cleaned up before the structure is discarded: palette, trans, text, + * pcal_purpose, pcal_units, pcal_params, hist, iccp_name, iccp_profile, + * splt_palettes, scal_unit, row_pointers, and unknowns. By default, these + * are automatically freed when the info structure is deallocated, if they were + * allocated internally by libpng. This behavior can be changed by means + * of the png_data_freer() function. + * + * More allocation details: all the chunk-reading functions that + * change these members go through the corresponding png_set_* + * functions. A function to clear these members is available: see + * png_free_data(). The png_set_* functions do not depend on being + * able to point info structure members to any of the storage they are + * passed (they make their own copies), EXCEPT that the png_set_text + * functions use the same storage passed to them in the text_ptr or + * itxt_ptr structure argument, and the png_set_rows and png_set_unknowns + * functions do not make their own copies. + */ +#ifndef PNGINFO_H +#define PNGINFO_H + +struct png_info_def +{ + /* The following are necessary for every PNG file */ + png_uint_32 width; /* width of image in pixels (from IHDR) */ + png_uint_32 height; /* height of image in pixels (from IHDR) */ + png_uint_32 valid; /* valid chunk data (see PNG_INFO_ below) */ + size_t rowbytes; /* bytes needed to hold an untransformed row */ + png_colorp palette; /* array of color values (valid & PNG_INFO_PLTE) */ + png_uint_16 num_palette; /* number of color entries in "palette" (PLTE) */ + png_uint_16 num_trans; /* number of transparent palette color (tRNS) */ + png_byte bit_depth; /* 1, 2, 4, 8, or 16 bits/channel (from IHDR) */ + png_byte color_type; /* see PNG_COLOR_TYPE_ below (from IHDR) */ + /* The following three should have been named *_method not *_type */ + png_byte compression_type; /* must be PNG_COMPRESSION_TYPE_BASE (IHDR) */ + png_byte filter_type; /* must be PNG_FILTER_TYPE_BASE (from IHDR) */ + png_byte interlace_type; /* One of PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */ + + /* The following are set by png_set_IHDR, called from the application on + * write, but the are never actually used by the write code. + */ + png_byte channels; /* number of data channels per pixel (1, 2, 3, 4) */ + png_byte pixel_depth; /* number of bits per pixel */ + png_byte spare_byte; /* to align the data, and for future use */ + +#ifdef PNG_READ_SUPPORTED + /* This is never set during write */ + png_byte signature[8]; /* magic bytes read by libpng from start of file */ +#endif + + /* The rest of the data is optional. If you are reading, check the + * valid field to see if the information in these are valid. If you + * are writing, set the valid field to those chunks you want written, + * and initialize the appropriate fields below. + */ + +#if defined(PNG_COLORSPACE_SUPPORTED) || defined(PNG_GAMMA_SUPPORTED) + /* png_colorspace only contains 'flags' if neither GAMMA or COLORSPACE are + * defined. When COLORSPACE is switched on all the colorspace-defining + * chunks should be enabled, when GAMMA is switched on all the gamma-defining + * chunks should be enabled. If this is not done it becomes possible to read + * inconsistent PNG files and assign a probably incorrect interpretation to + * the information. (In other words, by carefully choosing which chunks to + * recognize the system configuration can select an interpretation for PNG + * files containing ambiguous data and this will result in inconsistent + * behavior between different libpng builds!) + */ + png_colorspace colorspace; +#endif + +#ifdef PNG_iCCP_SUPPORTED + /* iCCP chunk data. */ + png_charp iccp_name; /* profile name */ + png_bytep iccp_profile; /* International Color Consortium profile data */ + png_uint_32 iccp_proflen; /* ICC profile data length */ +#endif + +#ifdef PNG_TEXT_SUPPORTED + /* The tEXt, and zTXt chunks contain human-readable textual data in + * uncompressed, compressed, and optionally compressed forms, respectively. + * The data in "text" is an array of pointers to uncompressed, + * null-terminated C strings. Each chunk has a keyword that describes the + * textual data contained in that chunk. Keywords are not required to be + * unique, and the text string may be empty. Any number of text chunks may + * be in an image. + */ + int num_text; /* number of comments read or comments to write */ + int max_text; /* current size of text array */ + png_textp text; /* array of comments read or comments to write */ +#endif /* TEXT */ + +#ifdef PNG_tIME_SUPPORTED + /* The tIME chunk holds the last time the displayed image data was + * modified. See the png_time struct for the contents of this struct. + */ + png_time mod_time; +#endif + +#ifdef PNG_sBIT_SUPPORTED + /* The sBIT chunk specifies the number of significant high-order bits + * in the pixel data. Values are in the range [1, bit_depth], and are + * only specified for the channels in the pixel data. The contents of + * the low-order bits is not specified. Data is valid if + * (valid & PNG_INFO_sBIT) is non-zero. + */ + png_color_8 sig_bit; /* significant bits in color channels */ +#endif + +#if defined(PNG_tRNS_SUPPORTED) || defined(PNG_READ_EXPAND_SUPPORTED) || \ +defined(PNG_READ_BACKGROUND_SUPPORTED) + /* The tRNS chunk supplies transparency data for paletted images and + * other image types that don't need a full alpha channel. There are + * "num_trans" transparency values for a paletted image, stored in the + * same order as the palette colors, starting from index 0. Values + * for the data are in the range [0, 255], ranging from fully transparent + * to fully opaque, respectively. For non-paletted images, there is a + * single color specified that should be treated as fully transparent. + * Data is valid if (valid & PNG_INFO_tRNS) is non-zero. + */ + png_bytep trans_alpha; /* alpha values for paletted image */ + png_color_16 trans_color; /* transparent color for non-palette image */ +#endif + +#if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) + /* The bKGD chunk gives the suggested image background color if the + * display program does not have its own background color and the image + * is needs to composited onto a background before display. The colors + * in "background" are normally in the same color space/depth as the + * pixel data. Data is valid if (valid & PNG_INFO_bKGD) is non-zero. + */ + png_color_16 background; +#endif + +#ifdef PNG_oFFs_SUPPORTED + /* The oFFs chunk gives the offset in "offset_unit_type" units rightwards + * and downwards from the top-left corner of the display, page, or other + * application-specific co-ordinate space. See the PNG_OFFSET_ defines + * below for the unit types. Valid if (valid & PNG_INFO_oFFs) non-zero. + */ + png_int_32 x_offset; /* x offset on page */ + png_int_32 y_offset; /* y offset on page */ + png_byte offset_unit_type; /* offset units type */ +#endif + +#ifdef PNG_pHYs_SUPPORTED + /* The pHYs chunk gives the physical pixel density of the image for + * display or printing in "phys_unit_type" units (see PNG_RESOLUTION_ + * defines below). Data is valid if (valid & PNG_INFO_pHYs) is non-zero. + */ + png_uint_32 x_pixels_per_unit; /* horizontal pixel density */ + png_uint_32 y_pixels_per_unit; /* vertical pixel density */ + png_byte phys_unit_type; /* resolution type (see PNG_RESOLUTION_ below) */ +#endif + +#ifdef PNG_eXIf_SUPPORTED + int num_exif; /* Added at libpng-1.6.31 */ + png_bytep exif; +# ifdef PNG_READ_eXIf_SUPPORTED + png_bytep eXIf_buf; /* Added at libpng-1.6.32 */ +# endif +#endif + +#ifdef PNG_hIST_SUPPORTED + /* The hIST chunk contains the relative frequency or importance of the + * various palette entries, so that a viewer can intelligently select a + * reduced-color palette, if required. Data is an array of "num_palette" + * values in the range [0,65535]. Data valid if (valid & PNG_INFO_hIST) + * is non-zero. + */ + png_uint_16p hist; +#endif + +#ifdef PNG_pCAL_SUPPORTED + /* The pCAL chunk describes a transformation between the stored pixel + * values and original physical data values used to create the image. + * The integer range [0, 2^bit_depth - 1] maps to the floating-point + * range given by [pcal_X0, pcal_X1], and are further transformed by a + * (possibly non-linear) transformation function given by "pcal_type" + * and "pcal_params" into "pcal_units". Please see the PNG_EQUATION_ + * defines below, and the PNG-Group's PNG extensions document for a + * complete description of the transformations and how they should be + * implemented, and for a description of the ASCII parameter strings. + * Data values are valid if (valid & PNG_INFO_pCAL) non-zero. + */ + png_charp pcal_purpose; /* pCAL chunk description string */ + png_int_32 pcal_X0; /* minimum value */ + png_int_32 pcal_X1; /* maximum value */ + png_charp pcal_units; /* Latin-1 string giving physical units */ + png_charpp pcal_params; /* ASCII strings containing parameter values */ + png_byte pcal_type; /* equation type (see PNG_EQUATION_ below) */ + png_byte pcal_nparams; /* number of parameters given in pcal_params */ +#endif + +/* New members added in libpng-1.0.6 */ + png_uint_32 free_me; /* flags items libpng is responsible for freeing */ + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + /* Storage for unknown chunks that the library doesn't recognize. */ + png_unknown_chunkp unknown_chunks; + + /* The type of this field is limited by the type of + * png_struct::user_chunk_cache_max, else overflow can occur. + */ + int unknown_chunks_num; +#endif + +#ifdef PNG_sPLT_SUPPORTED + /* Data on sPLT chunks (there may be more than one). */ + png_sPLT_tp splt_palettes; + int splt_palettes_num; /* Match type returned by png_get API */ +#endif + +#ifdef PNG_sCAL_SUPPORTED + /* The sCAL chunk describes the actual physical dimensions of the + * subject matter of the graphic. The chunk contains a unit specification + * a byte value, and two ASCII strings representing floating-point + * values. The values are width and height corresponding to one pixel + * in the image. Data values are valid if (valid & PNG_INFO_sCAL) is + * non-zero. + */ + png_byte scal_unit; /* unit of physical scale */ + png_charp scal_s_width; /* string containing height */ + png_charp scal_s_height; /* string containing width */ +#endif + +#ifdef PNG_INFO_IMAGE_SUPPORTED + /* Memory has been allocated if (valid & PNG_ALLOCATED_INFO_ROWS) + non-zero */ + /* Data valid if (valid & PNG_INFO_IDAT) non-zero */ + png_bytepp row_pointers; /* the image bits */ +#endif + +}; +#endif /* PNGINFO_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnglibconf.h b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnglibconf.h new file mode 100644 index 0000000..f77a01c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnglibconf.h @@ -0,0 +1,224 @@ +/* libpng 1.6.17 STANDARD API DEFINITION */ + +/* pnglibconf.h - library build configuration */ + +/* Libpng version 1.6.17 - March 26, 2015 */ + +/* Copyright (c) 1998-2014 Glenn Randers-Pehrson */ + +/* This code is released under the libpng license. */ +/* For conditions of distribution and use, see the disclaimer */ +/* and license in png.h */ + +/* pnglibconf.h */ +/* Machine generated file: DO NOT EDIT */ +/* Derived from: scripts/pnglibconf.dfa */ +#ifndef PNGLCONF_H +#define PNGLCONF_H +/* options */ +#define PNG_16BIT_SUPPORTED +#define PNG_ALIGNED_MEMORY_SUPPORTED +/*#undef PNG_ARM_NEON_API_SUPPORTED*/ +/*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/ +#define PNG_BENIGN_ERRORS_SUPPORTED +#define PNG_BENIGN_READ_ERRORS_SUPPORTED +/*#undef PNG_BENIGN_WRITE_ERRORS_SUPPORTED*/ +#define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +#define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_COLORSPACE_SUPPORTED +/*#undef PNG_CONSOLE_IO_SUPPORTED*/ +#define PNG_CONVERT_tIME_SUPPORTED +#define PNG_EASY_ACCESS_SUPPORTED +/*#undef PNG_ERROR_NUMBERS_SUPPORTED*/ +/*#undef PNG_ERROR_TEXT_SUPPORTED*/ +#define PNG_FIXED_POINT_SUPPORTED +#define PNG_FLOATING_ARITHMETIC_SUPPORTED +#define PNG_FLOATING_POINT_SUPPORTED +#define PNG_FORMAT_AFIRST_SUPPORTED +#define PNG_FORMAT_BGR_SUPPORTED +#define PNG_GAMMA_SUPPORTED +#define PNG_GET_PALETTE_MAX_SUPPORTED +#define PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#define PNG_INCH_CONVERSIONS_SUPPORTED +#define PNG_INFO_IMAGE_SUPPORTED +#define PNG_IO_STATE_SUPPORTED +#define PNG_MNG_FEATURES_SUPPORTED +#define PNG_POINTER_INDEXING_SUPPORTED +#define PNG_PROGRESSIVE_READ_SUPPORTED +#define PNG_READ_16BIT_SUPPORTED +#define PNG_READ_ALPHA_MODE_SUPPORTED +#define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_READ_BACKGROUND_SUPPORTED +#define PNG_READ_BGR_SUPPORTED +#define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_READ_COMPOSITE_NODIV_SUPPORTED +#define PNG_READ_COMPRESSED_TEXT_SUPPORTED +#define PNG_READ_EXPAND_16_SUPPORTED +#define PNG_READ_EXPAND_SUPPORTED +#define PNG_READ_FILLER_SUPPORTED +#define PNG_READ_GAMMA_SUPPORTED +#define PNG_READ_GET_PALETTE_MAX_SUPPORTED +#define PNG_READ_GRAY_TO_RGB_SUPPORTED +#define PNG_READ_INTERLACING_SUPPORTED +#define PNG_READ_INT_FUNCTIONS_SUPPORTED +#define PNG_READ_INVERT_ALPHA_SUPPORTED +#define PNG_READ_INVERT_SUPPORTED +#define PNG_READ_OPT_PLTE_SUPPORTED +#define PNG_READ_PACKSWAP_SUPPORTED +#define PNG_READ_PACK_SUPPORTED +#define PNG_READ_QUANTIZE_SUPPORTED +#define PNG_READ_RGB_TO_GRAY_SUPPORTED +#define PNG_READ_SCALE_16_TO_8_SUPPORTED +#define PNG_READ_SHIFT_SUPPORTED +#define PNG_READ_STRIP_16_TO_8_SUPPORTED +#define PNG_READ_STRIP_ALPHA_SUPPORTED +#define PNG_READ_SUPPORTED +#define PNG_READ_SWAP_ALPHA_SUPPORTED +#define PNG_READ_SWAP_SUPPORTED +#define PNG_READ_TEXT_SUPPORTED +#define PNG_READ_TRANSFORMS_SUPPORTED +#define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_READ_USER_CHUNKS_SUPPORTED +#define PNG_READ_USER_TRANSFORM_SUPPORTED +#define PNG_READ_bKGD_SUPPORTED +#define PNG_READ_cHRM_SUPPORTED +#define PNG_READ_gAMA_SUPPORTED +#define PNG_READ_hIST_SUPPORTED +#define PNG_READ_iCCP_SUPPORTED +#define PNG_READ_iTXt_SUPPORTED +#define PNG_READ_oFFs_SUPPORTED +#define PNG_READ_pCAL_SUPPORTED +#define PNG_READ_pHYs_SUPPORTED +#define PNG_READ_sBIT_SUPPORTED +#define PNG_READ_sCAL_SUPPORTED +#define PNG_READ_sPLT_SUPPORTED +#define PNG_READ_sRGB_SUPPORTED +#define PNG_READ_tEXt_SUPPORTED +#define PNG_READ_tIME_SUPPORTED +#define PNG_READ_tRNS_SUPPORTED +#define PNG_READ_zTXt_SUPPORTED +/*#undef PNG_SAVE_INT_32_SUPPORTED*/ +#define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SEQUENTIAL_READ_SUPPORTED +#define PNG_SETJMP_SUPPORTED +#define PNG_SET_CHUNK_CACHE_LIMIT_SUPPORTED +#define PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED +#define PNG_SET_OPTION_SUPPORTED +#define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SET_USER_LIMITS_SUPPORTED +/*#undef PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_READ_BGR_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_READ_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_WRITE_SUPPORTED*/ +#define PNG_STDIO_SUPPORTED +#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_TEXT_SUPPORTED +#define PNG_TIME_RFC1123_SUPPORTED +#define PNG_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_USER_CHUNKS_SUPPORTED +#define PNG_USER_LIMITS_SUPPORTED +#define PNG_USER_MEM_SUPPORTED +#define PNG_USER_TRANSFORM_INFO_SUPPORTED +#define PNG_USER_TRANSFORM_PTR_SUPPORTED +/*#undef PNG_WARNINGS_SUPPORTED*/ +/*#undef PNG_WRITE_16BIT_SUPPORTED*/ +/*#undef PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED*/ +/*#undef PNG_WRITE_BGR_SUPPORTED*/ +/*#undef PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED*/ +/*#undef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED*/ +/*#undef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED*/ +/*#undef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED*/ +/*#undef PNG_WRITE_FILLER_SUPPORTED*/ +/*#undef PNG_WRITE_FILTER_SUPPORTED*/ +/*#undef PNG_WRITE_FLUSH_SUPPORTED*/ +/*#undef PNG_WRITE_GET_PALETTE_MAX_SUPPORTED*/ +/*#undef PNG_WRITE_INTERLACING_SUPPORTED*/ +/*#undef PNG_WRITE_INT_FUNCTIONS_SUPPORTED*/ +/*#undef PNG_WRITE_INVERT_ALPHA_SUPPORTED*/ +/*#undef PNG_WRITE_INVERT_SUPPORTED*/ +/*#undef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED*/ +/*#undef PNG_WRITE_PACKSWAP_SUPPORTED*/ +/*#undef PNG_WRITE_PACK_SUPPORTED*/ +/*#undef PNG_WRITE_SHIFT_SUPPORTED*/ +/*#undef PNG_WRITE_SUPPORTED*/ +/*#undef PNG_WRITE_SWAP_ALPHA_SUPPORTED*/ +/*#undef PNG_WRITE_SWAP_SUPPORTED*/ +/*#undef PNG_WRITE_TEXT_SUPPORTED*/ +/*#undef PNG_WRITE_TRANSFORMS_SUPPORTED*/ +/*#undef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED*/ +/*#undef PNG_WRITE_USER_TRANSFORM_SUPPORTED*/ +/*#undef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED*/ +/*#undef PNG_WRITE_bKGD_SUPPORTED*/ +/*#undef PNG_WRITE_cHRM_SUPPORTED*/ +/*#undef PNG_WRITE_gAMA_SUPPORTED*/ +/*#undef PNG_WRITE_hIST_SUPPORTED*/ +/*#undef PNG_WRITE_iCCP_SUPPORTED*/ +/*#undef PNG_WRITE_iTXt_SUPPORTED*/ +/*#undef PNG_WRITE_oFFs_SUPPORTED*/ +/*#undef PNG_WRITE_pCAL_SUPPORTED*/ +/*#undef PNG_WRITE_pHYs_SUPPORTED*/ +/*#undef PNG_WRITE_sBIT_SUPPORTED*/ +/*#undef PNG_WRITE_sCAL_SUPPORTED*/ +/*#undef PNG_WRITE_sPLT_SUPPORTED*/ +/*#undef PNG_WRITE_sRGB_SUPPORTED*/ +/*#undef PNG_WRITE_tEXt_SUPPORTED*/ +/*#undef PNG_WRITE_tIME_SUPPORTED*/ +/*#undef PNG_WRITE_tRNS_SUPPORTED*/ +/*#undef PNG_WRITE_zTXt_SUPPORTED*/ +#define PNG_bKGD_SUPPORTED +#define PNG_cHRM_SUPPORTED +#define PNG_gAMA_SUPPORTED +#define PNG_hIST_SUPPORTED +#define PNG_iCCP_SUPPORTED +#define PNG_iTXt_SUPPORTED +#define PNG_oFFs_SUPPORTED +#define PNG_pCAL_SUPPORTED +#define PNG_pHYs_SUPPORTED +#define PNG_sBIT_SUPPORTED +#define PNG_sCAL_SUPPORTED +#define PNG_sPLT_SUPPORTED +#define PNG_sRGB_SUPPORTED +#define PNG_tEXt_SUPPORTED +#define PNG_tIME_SUPPORTED +#define PNG_tRNS_SUPPORTED +#define PNG_zTXt_SUPPORTED +/* end of options */ +/* settings */ +#define PNG_API_RULE 0 +#define PNG_COST_SHIFT 3 +#define PNG_DEFAULT_READ_MACROS 1 +#define PNG_GAMMA_THRESHOLD_FIXED 5000 +#define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE +#define PNG_INFLATE_BUF_SIZE 1024 +#define PNG_MAX_GAMMA_8 11 +#define PNG_QUANTIZE_BLUE_BITS 5 +#define PNG_QUANTIZE_GREEN_BITS 5 +#define PNG_QUANTIZE_RED_BITS 5 +#define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1) +#define PNG_TEXT_Z_DEFAULT_STRATEGY 0 +#define PNG_USER_CHUNK_CACHE_MAX 1000 +#define PNG_USER_CHUNK_MALLOC_MAX 8000000 +#define PNG_USER_HEIGHT_MAX 1000000 +#define PNG_USER_WIDTH_MAX 1000000 +#define PNG_WEIGHT_SHIFT 8 +#define PNG_ZBUF_SIZE 8192 +#define PNG_ZLIB_VERNUM 0 /* unknown */ +#define PNG_Z_DEFAULT_COMPRESSION (-1) +#define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0 +#define PNG_Z_DEFAULT_STRATEGY 1 +#define PNG_sCAL_PRECISION 5 +#define PNG_sRGB_PROFILE_CHECKS 2 +/* end of settings */ + +#define PNG_LINKAGE_API +#define PNG_LINKAGE_FUNCTION + +#ifdef PNG_WRITE_SUPPORTED +#define PNG_SAVE_INT_32_SUPPORTED +#define PNG_WRITE_INT_FUNCTIONS_SUPPORTED +#endif + + +#endif /* PNGLCONF_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnglibconf.h.prebuilt b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnglibconf.h.prebuilt new file mode 100644 index 0000000..1962067 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pnglibconf.h.prebuilt @@ -0,0 +1,214 @@ +/* libpng 1.6.17 STANDARD API DEFINITION */ + +/* pnglibconf.h - library build configuration */ + +/* Libpng version 1.6.17 - March 26, 2015 */ + +/* Copyright (c) 1998-2014 Glenn Randers-Pehrson */ + +/* This code is released under the libpng license. */ +/* For conditions of distribution and use, see the disclaimer */ +/* and license in png.h */ + +/* pnglibconf.h */ +/* Machine generated file: DO NOT EDIT */ +/* Derived from: scripts/pnglibconf.dfa */ +#ifndef PNGLCONF_H +#define PNGLCONF_H +/* options */ +#define PNG_16BIT_SUPPORTED +#define PNG_ALIGNED_MEMORY_SUPPORTED +/*#undef PNG_ARM_NEON_API_SUPPORTED*/ +/*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/ +#define PNG_BENIGN_ERRORS_SUPPORTED +#define PNG_BENIGN_READ_ERRORS_SUPPORTED +/*#undef PNG_BENIGN_WRITE_ERRORS_SUPPORTED*/ +#define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +#define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_COLORSPACE_SUPPORTED +#define PNG_CONSOLE_IO_SUPPORTED +#define PNG_CONVERT_tIME_SUPPORTED +#define PNG_EASY_ACCESS_SUPPORTED +/*#undef PNG_ERROR_NUMBERS_SUPPORTED*/ +#define PNG_ERROR_TEXT_SUPPORTED +#define PNG_FIXED_POINT_SUPPORTED +#define PNG_FLOATING_ARITHMETIC_SUPPORTED +#define PNG_FLOATING_POINT_SUPPORTED +#define PNG_FORMAT_AFIRST_SUPPORTED +#define PNG_FORMAT_BGR_SUPPORTED +#define PNG_GAMMA_SUPPORTED +#define PNG_GET_PALETTE_MAX_SUPPORTED +#define PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#define PNG_INCH_CONVERSIONS_SUPPORTED +#define PNG_INFO_IMAGE_SUPPORTED +#define PNG_IO_STATE_SUPPORTED +#define PNG_MNG_FEATURES_SUPPORTED +#define PNG_POINTER_INDEXING_SUPPORTED +#define PNG_PROGRESSIVE_READ_SUPPORTED +#define PNG_READ_16BIT_SUPPORTED +#define PNG_READ_ALPHA_MODE_SUPPORTED +#define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_READ_BACKGROUND_SUPPORTED +#define PNG_READ_BGR_SUPPORTED +#define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_READ_COMPOSITE_NODIV_SUPPORTED +#define PNG_READ_COMPRESSED_TEXT_SUPPORTED +#define PNG_READ_EXPAND_16_SUPPORTED +#define PNG_READ_EXPAND_SUPPORTED +#define PNG_READ_FILLER_SUPPORTED +#define PNG_READ_GAMMA_SUPPORTED +#define PNG_READ_GET_PALETTE_MAX_SUPPORTED +#define PNG_READ_GRAY_TO_RGB_SUPPORTED +#define PNG_READ_INTERLACING_SUPPORTED +#define PNG_READ_INT_FUNCTIONS_SUPPORTED +#define PNG_READ_INVERT_ALPHA_SUPPORTED +#define PNG_READ_INVERT_SUPPORTED +#define PNG_READ_OPT_PLTE_SUPPORTED +#define PNG_READ_PACKSWAP_SUPPORTED +#define PNG_READ_PACK_SUPPORTED +#define PNG_READ_QUANTIZE_SUPPORTED +#define PNG_READ_RGB_TO_GRAY_SUPPORTED +#define PNG_READ_SCALE_16_TO_8_SUPPORTED +#define PNG_READ_SHIFT_SUPPORTED +#define PNG_READ_STRIP_16_TO_8_SUPPORTED +#define PNG_READ_STRIP_ALPHA_SUPPORTED +#define PNG_READ_SUPPORTED +#define PNG_READ_SWAP_ALPHA_SUPPORTED +#define PNG_READ_SWAP_SUPPORTED +#define PNG_READ_TEXT_SUPPORTED +#define PNG_READ_TRANSFORMS_SUPPORTED +#define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_READ_USER_CHUNKS_SUPPORTED +#define PNG_READ_USER_TRANSFORM_SUPPORTED +#define PNG_READ_bKGD_SUPPORTED +#define PNG_READ_cHRM_SUPPORTED +#define PNG_READ_gAMA_SUPPORTED +#define PNG_READ_hIST_SUPPORTED +#define PNG_READ_iCCP_SUPPORTED +#define PNG_READ_iTXt_SUPPORTED +#define PNG_READ_oFFs_SUPPORTED +#define PNG_READ_pCAL_SUPPORTED +#define PNG_READ_pHYs_SUPPORTED +#define PNG_READ_sBIT_SUPPORTED +#define PNG_READ_sCAL_SUPPORTED +#define PNG_READ_sPLT_SUPPORTED +#define PNG_READ_sRGB_SUPPORTED +#define PNG_READ_tEXt_SUPPORTED +#define PNG_READ_tIME_SUPPORTED +#define PNG_READ_tRNS_SUPPORTED +#define PNG_READ_zTXt_SUPPORTED +#define PNG_SAVE_INT_32_SUPPORTED +#define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SEQUENTIAL_READ_SUPPORTED +#define PNG_SETJMP_SUPPORTED +#define PNG_SET_CHUNK_CACHE_LIMIT_SUPPORTED +#define PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED +#define PNG_SET_OPTION_SUPPORTED +#define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SET_USER_LIMITS_SUPPORTED +#define PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED +#define PNG_SIMPLIFIED_READ_BGR_SUPPORTED +#define PNG_SIMPLIFIED_READ_SUPPORTED +#define PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED +#define PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED +#define PNG_SIMPLIFIED_WRITE_SUPPORTED +#define PNG_STDIO_SUPPORTED +#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_TEXT_SUPPORTED +#define PNG_TIME_RFC1123_SUPPORTED +#define PNG_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_USER_CHUNKS_SUPPORTED +#define PNG_USER_LIMITS_SUPPORTED +#define PNG_USER_MEM_SUPPORTED +#define PNG_USER_TRANSFORM_INFO_SUPPORTED +#define PNG_USER_TRANSFORM_PTR_SUPPORTED +#define PNG_WARNINGS_SUPPORTED +#define PNG_WRITE_16BIT_SUPPORTED +#define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_WRITE_BGR_SUPPORTED +#define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +#define PNG_WRITE_FILLER_SUPPORTED +#define PNG_WRITE_FILTER_SUPPORTED +#define PNG_WRITE_FLUSH_SUPPORTED +#define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED +#define PNG_WRITE_INTERLACING_SUPPORTED +#define PNG_WRITE_INT_FUNCTIONS_SUPPORTED +#define PNG_WRITE_INVERT_ALPHA_SUPPORTED +#define PNG_WRITE_INVERT_SUPPORTED +#define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED +#define PNG_WRITE_PACKSWAP_SUPPORTED +#define PNG_WRITE_PACK_SUPPORTED +#define PNG_WRITE_SHIFT_SUPPORTED +#define PNG_WRITE_SUPPORTED +#define PNG_WRITE_SWAP_ALPHA_SUPPORTED +#define PNG_WRITE_SWAP_SUPPORTED +#define PNG_WRITE_TEXT_SUPPORTED +#define PNG_WRITE_TRANSFORMS_SUPPORTED +#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_WRITE_USER_TRANSFORM_SUPPORTED +#define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED +#define PNG_WRITE_bKGD_SUPPORTED +#define PNG_WRITE_cHRM_SUPPORTED +#define PNG_WRITE_gAMA_SUPPORTED +#define PNG_WRITE_hIST_SUPPORTED +#define PNG_WRITE_iCCP_SUPPORTED +#define PNG_WRITE_iTXt_SUPPORTED +#define PNG_WRITE_oFFs_SUPPORTED +#define PNG_WRITE_pCAL_SUPPORTED +#define PNG_WRITE_pHYs_SUPPORTED +#define PNG_WRITE_sBIT_SUPPORTED +#define PNG_WRITE_sCAL_SUPPORTED +#define PNG_WRITE_sPLT_SUPPORTED +#define PNG_WRITE_sRGB_SUPPORTED +#define PNG_WRITE_tEXt_SUPPORTED +#define PNG_WRITE_tIME_SUPPORTED +#define PNG_WRITE_tRNS_SUPPORTED +#define PNG_WRITE_zTXt_SUPPORTED +#define PNG_bKGD_SUPPORTED +#define PNG_cHRM_SUPPORTED +#define PNG_gAMA_SUPPORTED +#define PNG_hIST_SUPPORTED +#define PNG_iCCP_SUPPORTED +#define PNG_iTXt_SUPPORTED +#define PNG_oFFs_SUPPORTED +#define PNG_pCAL_SUPPORTED +#define PNG_pHYs_SUPPORTED +#define PNG_sBIT_SUPPORTED +#define PNG_sCAL_SUPPORTED +#define PNG_sPLT_SUPPORTED +#define PNG_sRGB_SUPPORTED +#define PNG_tEXt_SUPPORTED +#define PNG_tIME_SUPPORTED +#define PNG_tRNS_SUPPORTED +#define PNG_zTXt_SUPPORTED +/* end of options */ +/* settings */ +#define PNG_API_RULE 0 +#define PNG_COST_SHIFT 3 +#define PNG_DEFAULT_READ_MACROS 1 +#define PNG_GAMMA_THRESHOLD_FIXED 5000 +#define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE +#define PNG_INFLATE_BUF_SIZE 1024 +#define PNG_MAX_GAMMA_8 11 +#define PNG_QUANTIZE_BLUE_BITS 5 +#define PNG_QUANTIZE_GREEN_BITS 5 +#define PNG_QUANTIZE_RED_BITS 5 +#define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1) +#define PNG_TEXT_Z_DEFAULT_STRATEGY 0 +#define PNG_USER_CHUNK_CACHE_MAX 1000 +#define PNG_USER_CHUNK_MALLOC_MAX 8000000 +#define PNG_USER_HEIGHT_MAX 1000000 +#define PNG_USER_WIDTH_MAX 1000000 +#define PNG_WEIGHT_SHIFT 8 +#define PNG_ZBUF_SIZE 8192 +#define PNG_ZLIB_VERNUM 0 /* unknown */ +#define PNG_Z_DEFAULT_COMPRESSION (-1) +#define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0 +#define PNG_Z_DEFAULT_STRATEGY 1 +#define PNG_sCAL_PRECISION 5 +#define PNG_sRGB_PROFILE_CHECKS 2 +/* end of settings */ +#endif /* PNGLCONF_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngmem.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngmem.c new file mode 100644 index 0000000..09ed9c1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngmem.c @@ -0,0 +1,284 @@ + +/* pngmem.c - stub functions for memory allocation + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2014,2016 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all memory allocation. Users who + * need special memory handling are expected to supply replacement + * functions for png_malloc() and png_free(), and to use + * png_create_read_struct_2() and png_create_write_struct_2() to + * identify the replacement functions. + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +/* Free a png_struct */ +void /* PRIVATE */ +png_destroy_png_struct(png_structrp png_ptr) +{ + if (png_ptr != NULL) + { + /* png_free might call png_error and may certainly call + * png_get_mem_ptr, so fake a temporary png_struct to support this. + */ + png_struct dummy_struct = *png_ptr; + memset(png_ptr, 0, (sizeof *png_ptr)); + png_free(&dummy_struct, png_ptr); + +# ifdef PNG_SETJMP_SUPPORTED + /* We may have a jmp_buf left to deallocate. */ + png_free_jmpbuf(&dummy_struct); +# endif + } +} + +/* Allocate memory. For reasonable files, size should never exceed + * 64K. However, zlib may allocate more than 64K if you don't tell + * it not to. See zconf.h and png.h for more information. zlib does + * need to allocate exactly 64K, so whatever you call here must + * have the ability to do that. + */ +PNG_FUNCTION(png_voidp,PNGAPI +png_calloc,(png_const_structrp png_ptr, png_alloc_size_t size),PNG_ALLOCATED) +{ + png_voidp ret; + + ret = png_malloc(png_ptr, size); + + if (ret != NULL) + memset(ret, 0, size); + + return ret; +} + +/* png_malloc_base, an internal function added at libpng 1.6.0, does the work of + * allocating memory, taking into account limits and PNG_USER_MEM_SUPPORTED. + * Checking and error handling must happen outside this routine; it returns NULL + * if the allocation cannot be done (for any reason.) + */ +PNG_FUNCTION(png_voidp /* PRIVATE */, +png_malloc_base,(png_const_structrp png_ptr, png_alloc_size_t size), + PNG_ALLOCATED) +{ + /* Moved to png_malloc_base from png_malloc_default in 1.6.0; the DOS + * allocators have also been removed in 1.6.0, so any 16-bit system now has + * to implement a user memory handler. This checks to be sure it isn't + * called with big numbers. + */ +#ifndef PNG_USER_MEM_SUPPORTED + PNG_UNUSED(png_ptr) +#endif + + /* Some compilers complain that this is always true. However, it + * can be false when integer overflow happens. + */ + if (size > 0 && size <= PNG_SIZE_MAX +# ifdef PNG_MAX_MALLOC_64K + && size <= 65536U +# endif + ) + { +#ifdef PNG_USER_MEM_SUPPORTED + if (png_ptr != NULL && png_ptr->malloc_fn != NULL) + return png_ptr->malloc_fn(png_constcast(png_structrp,png_ptr), size); + + else +#endif + return malloc((size_t)size); /* checked for truncation above */ + } + + else + return NULL; +} + +#if defined(PNG_TEXT_SUPPORTED) || defined(PNG_sPLT_SUPPORTED) ||\ + defined(PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED) +/* This is really here only to work round a spurious warning in GCC 4.6 and 4.7 + * that arises because of the checks in png_realloc_array that are repeated in + * png_malloc_array. + */ +static png_voidp +png_malloc_array_checked(png_const_structrp png_ptr, int nelements, + size_t element_size) +{ + png_alloc_size_t req = (png_alloc_size_t)nelements; /* known to be > 0 */ + + if (req <= PNG_SIZE_MAX/element_size) + return png_malloc_base(png_ptr, req * element_size); + + /* The failure case when the request is too large */ + return NULL; +} + +PNG_FUNCTION(png_voidp /* PRIVATE */, +png_malloc_array,(png_const_structrp png_ptr, int nelements, + size_t element_size),PNG_ALLOCATED) +{ + if (nelements <= 0 || element_size == 0) + png_error(png_ptr, "internal error: array alloc"); + + return png_malloc_array_checked(png_ptr, nelements, element_size); +} + +PNG_FUNCTION(png_voidp /* PRIVATE */, +png_realloc_array,(png_const_structrp png_ptr, png_const_voidp old_array, + int old_elements, int add_elements, size_t element_size),PNG_ALLOCATED) +{ + /* These are internal errors: */ + if (add_elements <= 0 || element_size == 0 || old_elements < 0 || + (old_array == NULL && old_elements > 0)) + png_error(png_ptr, "internal error: array realloc"); + + /* Check for overflow on the elements count (so the caller does not have to + * check.) + */ + if (add_elements <= INT_MAX - old_elements) + { + png_voidp new_array = png_malloc_array_checked(png_ptr, + old_elements+add_elements, element_size); + + if (new_array != NULL) + { + /* Because png_malloc_array worked the size calculations below cannot + * overflow. + */ + if (old_elements > 0) + memcpy(new_array, old_array, element_size*(unsigned)old_elements); + + memset((char*)new_array + element_size*(unsigned)old_elements, 0, + element_size*(unsigned)add_elements); + + return new_array; + } + } + + return NULL; /* error */ +} +#endif /* TEXT || sPLT || STORE_UNKNOWN_CHUNKS */ + +/* Various functions that have different error handling are derived from this. + * png_malloc always exists, but if PNG_USER_MEM_SUPPORTED is defined a separate + * function png_malloc_default is also provided. + */ +PNG_FUNCTION(png_voidp,PNGAPI +png_malloc,(png_const_structrp png_ptr, png_alloc_size_t size),PNG_ALLOCATED) +{ + png_voidp ret; + + if (png_ptr == NULL) + return NULL; + + ret = png_malloc_base(png_ptr, size); + + if (ret == NULL) + png_error(png_ptr, "Out of memory"); /* 'm' means png_malloc */ + + return ret; +} + +#ifdef PNG_USER_MEM_SUPPORTED +PNG_FUNCTION(png_voidp,PNGAPI +png_malloc_default,(png_const_structrp png_ptr, png_alloc_size_t size), + PNG_ALLOCATED PNG_DEPRECATED) +{ + png_voidp ret; + + if (png_ptr == NULL) + return NULL; + + /* Passing 'NULL' here bypasses the application provided memory handler. */ + ret = png_malloc_base(NULL/*use malloc*/, size); + + if (ret == NULL) + png_error(png_ptr, "Out of Memory"); /* 'M' means png_malloc_default */ + + return ret; +} +#endif /* USER_MEM */ + +/* This function was added at libpng version 1.2.3. The png_malloc_warn() + * function will issue a png_warning and return NULL instead of issuing a + * png_error, if it fails to allocate the requested memory. + */ +PNG_FUNCTION(png_voidp,PNGAPI +png_malloc_warn,(png_const_structrp png_ptr, png_alloc_size_t size), + PNG_ALLOCATED) +{ + if (png_ptr != NULL) + { + png_voidp ret = png_malloc_base(png_ptr, size); + + if (ret != NULL) + return ret; + + png_warning(png_ptr, "Out of memory"); + } + + return NULL; +} + +/* Free a pointer allocated by png_malloc(). If ptr is NULL, return + * without taking any action. + */ +void PNGAPI +png_free(png_const_structrp png_ptr, png_voidp ptr) +{ + if (png_ptr == NULL || ptr == NULL) + return; + +#ifdef PNG_USER_MEM_SUPPORTED + if (png_ptr->free_fn != NULL) + png_ptr->free_fn(png_constcast(png_structrp,png_ptr), ptr); + + else + png_free_default(png_ptr, ptr); +} + +PNG_FUNCTION(void,PNGAPI +png_free_default,(png_const_structrp png_ptr, png_voidp ptr),PNG_DEPRECATED) +{ + if (png_ptr == NULL || ptr == NULL) + return; +#endif /* USER_MEM */ + + free(ptr); +} + +#ifdef PNG_USER_MEM_SUPPORTED +/* This function is called when the application wants to use another method + * of allocating and freeing memory. + */ +void PNGAPI +png_set_mem_fn(png_structrp png_ptr, png_voidp mem_ptr, png_malloc_ptr + malloc_fn, png_free_ptr free_fn) +{ + if (png_ptr != NULL) + { + png_ptr->mem_ptr = mem_ptr; + png_ptr->malloc_fn = malloc_fn; + png_ptr->free_fn = free_fn; + } +} + +/* This function returns a pointer to the mem_ptr associated with the user + * functions. The application should free any memory associated with this + * pointer before png_write_destroy and png_read_destroy are called. + */ +png_voidp PNGAPI +png_get_mem_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return NULL; + + return png_ptr->mem_ptr; +} +#endif /* USER_MEM */ +#endif /* READ || WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngpread.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngpread.c new file mode 100644 index 0000000..e283627 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngpread.c @@ -0,0 +1,1096 @@ + +/* pngpread.c - read a png file in push mode + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED + +/* Push model modes */ +#define PNG_READ_SIG_MODE 0 +#define PNG_READ_CHUNK_MODE 1 +#define PNG_READ_IDAT_MODE 2 +#define PNG_READ_tEXt_MODE 4 +#define PNG_READ_zTXt_MODE 5 +#define PNG_READ_DONE_MODE 6 +#define PNG_READ_iTXt_MODE 7 +#define PNG_ERROR_MODE 8 + +#define PNG_PUSH_SAVE_BUFFER_IF_FULL \ +if (png_ptr->push_length + 4 > png_ptr->buffer_size) \ + { png_push_save_buffer(png_ptr); return; } +#define PNG_PUSH_SAVE_BUFFER_IF_LT(N) \ +if (png_ptr->buffer_size < N) \ + { png_push_save_buffer(png_ptr); return; } + +void PNGAPI +png_process_data(png_structrp png_ptr, png_inforp info_ptr, + png_bytep buffer, size_t buffer_size) +{ + if (png_ptr == NULL || info_ptr == NULL) + return; + + png_push_restore_buffer(png_ptr, buffer, buffer_size); + + while (png_ptr->buffer_size) + { + png_process_some_data(png_ptr, info_ptr); + } +} + +size_t PNGAPI +png_process_data_pause(png_structrp png_ptr, int save) +{ + if (png_ptr != NULL) + { + /* It's easiest for the caller if we do the save; then the caller doesn't + * have to supply the same data again: + */ + if (save != 0) + png_push_save_buffer(png_ptr); + else + { + /* This includes any pending saved bytes: */ + size_t remaining = png_ptr->buffer_size; + png_ptr->buffer_size = 0; + + /* So subtract the saved buffer size, unless all the data + * is actually 'saved', in which case we just return 0 + */ + if (png_ptr->save_buffer_size < remaining) + return remaining - png_ptr->save_buffer_size; + } + } + + return 0; +} + +png_uint_32 PNGAPI +png_process_data_skip(png_structrp png_ptr) +{ +/* TODO: Deprecate and remove this API. + * Somewhere the implementation of this seems to have been lost, + * or abandoned. It was only to support some internal back-door access + * to png_struct) in libpng-1.4.x. + */ + png_app_warning(png_ptr, +"png_process_data_skip is not implemented in any current version of libpng"); + return 0; +} + +/* What we do with the incoming data depends on what we were previously + * doing before we ran out of data... + */ +void /* PRIVATE */ +png_process_some_data(png_structrp png_ptr, png_inforp info_ptr) +{ + if (png_ptr == NULL) + return; + + switch (png_ptr->process_mode) + { + case PNG_READ_SIG_MODE: + { + png_push_read_sig(png_ptr, info_ptr); + break; + } + + case PNG_READ_CHUNK_MODE: + { + png_push_read_chunk(png_ptr, info_ptr); + break; + } + + case PNG_READ_IDAT_MODE: + { + png_push_read_IDAT(png_ptr); + break; + } + + default: + { + png_ptr->buffer_size = 0; + break; + } + } +} + +/* Read any remaining signature bytes from the stream and compare them with + * the correct PNG signature. It is possible that this routine is called + * with bytes already read from the signature, either because they have been + * checked by the calling application, or because of multiple calls to this + * routine. + */ +void /* PRIVATE */ +png_push_read_sig(png_structrp png_ptr, png_inforp info_ptr) +{ + size_t num_checked = png_ptr->sig_bytes; /* SAFE, does not exceed 8 */ + size_t num_to_check = 8 - num_checked; + + if (png_ptr->buffer_size < num_to_check) + { + num_to_check = png_ptr->buffer_size; + } + + png_push_fill_buffer(png_ptr, &(info_ptr->signature[num_checked]), + num_to_check); + png_ptr->sig_bytes = (png_byte)(png_ptr->sig_bytes + num_to_check); + + if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check)) + { + if (num_checked < 4 && + png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4)) + png_error(png_ptr, "Not a PNG file"); + + else + png_error(png_ptr, "PNG file corrupted by ASCII conversion"); + } + else + { + if (png_ptr->sig_bytes >= 8) + { + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + } + } +} + +void /* PRIVATE */ +png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) +{ + png_uint_32 chunk_name; +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; /* unknown handling method */ +#endif + + /* First we make sure we have enough data for the 4-byte chunk name + * and the 4-byte chunk length before proceeding with decoding the + * chunk data. To fully decode each of these chunks, we also make + * sure we have enough data in the buffer for the 4-byte CRC at the + * end of every chunk (except IDAT, which is handled separately). + */ + if ((png_ptr->mode & PNG_HAVE_CHUNK_HEADER) == 0) + { + png_byte chunk_length[4]; + png_byte chunk_tag[4]; + + PNG_PUSH_SAVE_BUFFER_IF_LT(8) + png_push_fill_buffer(png_ptr, chunk_length, 4); + png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length); + png_reset_crc(png_ptr); + png_crc_read(png_ptr, chunk_tag, 4); + png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(chunk_tag); + png_check_chunk_name(png_ptr, png_ptr->chunk_name); + png_check_chunk_length(png_ptr, png_ptr->push_length); + png_ptr->mode |= PNG_HAVE_CHUNK_HEADER; + } + + chunk_name = png_ptr->chunk_name; + + if (chunk_name == png_IDAT) + { + if ((png_ptr->mode & PNG_AFTER_IDAT) != 0) + png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT; + + /* If we reach an IDAT chunk, this means we have read all of the + * header chunks, and we can start reading the image (or if this + * is called after the image has been read - we have an error). + */ + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_error(png_ptr, "Missing IHDR before IDAT"); + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + (png_ptr->mode & PNG_HAVE_PLTE) == 0) + png_error(png_ptr, "Missing PLTE before IDAT"); + + png_ptr->process_mode = PNG_READ_IDAT_MODE; + + if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + if ((png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT) == 0) + if (png_ptr->push_length == 0) + return; + + png_ptr->mode |= PNG_HAVE_IDAT; + + if ((png_ptr->mode & PNG_AFTER_IDAT) != 0) + png_benign_error(png_ptr, "Too many IDATs found"); + } + + if (chunk_name == png_IHDR) + { + if (png_ptr->push_length != 13) + png_error(png_ptr, "Invalid IHDR length"); + + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_IHDR(png_ptr, info_ptr, png_ptr->push_length); + } + + else if (chunk_name == png_IEND) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_IEND(png_ptr, info_ptr, png_ptr->push_length); + + png_ptr->process_mode = PNG_READ_DONE_MODE; + png_push_have_end(png_ptr, info_ptr); + } + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name)) != 0) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length, keep); + + if (chunk_name == png_PLTE) + png_ptr->mode |= PNG_HAVE_PLTE; + } +#endif + + else if (chunk_name == png_PLTE) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_PLTE(png_ptr, info_ptr, png_ptr->push_length); + } + + else if (chunk_name == png_IDAT) + { + png_ptr->idat_size = png_ptr->push_length; + png_ptr->process_mode = PNG_READ_IDAT_MODE; + png_push_have_info(png_ptr, info_ptr); + png_ptr->zstream.avail_out = + (uInt) PNG_ROWBYTES(png_ptr->pixel_depth, + png_ptr->iwidth) + 1; + png_ptr->zstream.next_out = png_ptr->row_buf; + return; + } + +#ifdef PNG_READ_gAMA_SUPPORTED + else if (png_ptr->chunk_name == png_gAMA) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_gAMA(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sBIT_SUPPORTED + else if (png_ptr->chunk_name == png_sBIT) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_sBIT(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_cHRM_SUPPORTED + else if (png_ptr->chunk_name == png_cHRM) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_cHRM(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sRGB_SUPPORTED + else if (chunk_name == png_sRGB) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_sRGB(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_iCCP_SUPPORTED + else if (png_ptr->chunk_name == png_iCCP) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_iCCP(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sPLT_SUPPORTED + else if (chunk_name == png_sPLT) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_sPLT(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_tRNS_SUPPORTED + else if (chunk_name == png_tRNS) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_tRNS(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_bKGD_SUPPORTED + else if (chunk_name == png_bKGD) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_bKGD(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_hIST_SUPPORTED + else if (chunk_name == png_hIST) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_hIST(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_pHYs_SUPPORTED + else if (chunk_name == png_pHYs) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_pHYs(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_oFFs_SUPPORTED + else if (chunk_name == png_oFFs) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_oFFs(png_ptr, info_ptr, png_ptr->push_length); + } +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED + else if (chunk_name == png_pCAL) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_pCAL(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sCAL_SUPPORTED + else if (chunk_name == png_sCAL) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_sCAL(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_tIME_SUPPORTED + else if (chunk_name == png_tIME) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_tIME(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_tEXt_SUPPORTED + else if (chunk_name == png_tEXt) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_tEXt(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_zTXt_SUPPORTED + else if (chunk_name == png_zTXt) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_zTXt(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_iTXt_SUPPORTED + else if (chunk_name == png_iTXt) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_iTXt(png_ptr, info_ptr, png_ptr->push_length); + } +#endif + + else + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length, + PNG_HANDLE_CHUNK_AS_DEFAULT); + } + + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; +} + +void PNGCBAPI +png_push_fill_buffer(png_structp png_ptr, png_bytep buffer, size_t length) +{ + png_bytep ptr; + + if (png_ptr == NULL) + return; + + ptr = buffer; + if (png_ptr->save_buffer_size != 0) + { + size_t save_size; + + if (length < png_ptr->save_buffer_size) + save_size = length; + + else + save_size = png_ptr->save_buffer_size; + + memcpy(ptr, png_ptr->save_buffer_ptr, save_size); + length -= save_size; + ptr += save_size; + png_ptr->buffer_size -= save_size; + png_ptr->save_buffer_size -= save_size; + png_ptr->save_buffer_ptr += save_size; + } + if (length != 0 && png_ptr->current_buffer_size != 0) + { + size_t save_size; + + if (length < png_ptr->current_buffer_size) + save_size = length; + + else + save_size = png_ptr->current_buffer_size; + + memcpy(ptr, png_ptr->current_buffer_ptr, save_size); + png_ptr->buffer_size -= save_size; + png_ptr->current_buffer_size -= save_size; + png_ptr->current_buffer_ptr += save_size; + } +} + +void /* PRIVATE */ +png_push_save_buffer(png_structrp png_ptr) +{ + if (png_ptr->save_buffer_size != 0) + { + if (png_ptr->save_buffer_ptr != png_ptr->save_buffer) + { + size_t i, istop; + png_bytep sp; + png_bytep dp; + + istop = png_ptr->save_buffer_size; + for (i = 0, sp = png_ptr->save_buffer_ptr, dp = png_ptr->save_buffer; + i < istop; i++, sp++, dp++) + { + *dp = *sp; + } + } + } + if (png_ptr->save_buffer_size + png_ptr->current_buffer_size > + png_ptr->save_buffer_max) + { + size_t new_max; + png_bytep old_buffer; + + if (png_ptr->save_buffer_size > PNG_SIZE_MAX - + (png_ptr->current_buffer_size + 256)) + { + png_error(png_ptr, "Potential overflow of save_buffer"); + } + + new_max = png_ptr->save_buffer_size + png_ptr->current_buffer_size + 256; + old_buffer = png_ptr->save_buffer; + png_ptr->save_buffer = (png_bytep)png_malloc_warn(png_ptr, + (size_t)new_max); + + if (png_ptr->save_buffer == NULL) + { + png_free(png_ptr, old_buffer); + png_error(png_ptr, "Insufficient memory for save_buffer"); + } + + if (old_buffer) + memcpy(png_ptr->save_buffer, old_buffer, png_ptr->save_buffer_size); + else if (png_ptr->save_buffer_size) + png_error(png_ptr, "save_buffer error"); + png_free(png_ptr, old_buffer); + png_ptr->save_buffer_max = new_max; + } + if (png_ptr->current_buffer_size) + { + memcpy(png_ptr->save_buffer + png_ptr->save_buffer_size, + png_ptr->current_buffer_ptr, png_ptr->current_buffer_size); + png_ptr->save_buffer_size += png_ptr->current_buffer_size; + png_ptr->current_buffer_size = 0; + } + png_ptr->save_buffer_ptr = png_ptr->save_buffer; + png_ptr->buffer_size = 0; +} + +void /* PRIVATE */ +png_push_restore_buffer(png_structrp png_ptr, png_bytep buffer, + size_t buffer_length) +{ + png_ptr->current_buffer = buffer; + png_ptr->current_buffer_size = buffer_length; + png_ptr->buffer_size = buffer_length + png_ptr->save_buffer_size; + png_ptr->current_buffer_ptr = png_ptr->current_buffer; +} + +void /* PRIVATE */ +png_push_read_IDAT(png_structrp png_ptr) +{ + if ((png_ptr->mode & PNG_HAVE_CHUNK_HEADER) == 0) + { + png_byte chunk_length[4]; + png_byte chunk_tag[4]; + + /* TODO: this code can be commoned up with the same code in push_read */ + PNG_PUSH_SAVE_BUFFER_IF_LT(8) + png_push_fill_buffer(png_ptr, chunk_length, 4); + png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length); + png_reset_crc(png_ptr); + png_crc_read(png_ptr, chunk_tag, 4); + png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(chunk_tag); + png_ptr->mode |= PNG_HAVE_CHUNK_HEADER; + + if (png_ptr->chunk_name != png_IDAT) + { + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) == 0) + png_error(png_ptr, "Not enough compressed data"); + + return; + } + + png_ptr->idat_size = png_ptr->push_length; + } + + if (png_ptr->idat_size != 0 && png_ptr->save_buffer_size != 0) + { + size_t save_size = png_ptr->save_buffer_size; + png_uint_32 idat_size = png_ptr->idat_size; + + /* We want the smaller of 'idat_size' and 'current_buffer_size', but they + * are of different types and we don't know which variable has the fewest + * bits. Carefully select the smaller and cast it to the type of the + * larger - this cannot overflow. Do not cast in the following test - it + * will break on either 16-bit or 64-bit platforms. + */ + if (idat_size < save_size) + save_size = (size_t)idat_size; + + else + idat_size = (png_uint_32)save_size; + + png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size); + + png_process_IDAT_data(png_ptr, png_ptr->save_buffer_ptr, save_size); + + png_ptr->idat_size -= idat_size; + png_ptr->buffer_size -= save_size; + png_ptr->save_buffer_size -= save_size; + png_ptr->save_buffer_ptr += save_size; + } + + if (png_ptr->idat_size != 0 && png_ptr->current_buffer_size != 0) + { + size_t save_size = png_ptr->current_buffer_size; + png_uint_32 idat_size = png_ptr->idat_size; + + /* We want the smaller of 'idat_size' and 'current_buffer_size', but they + * are of different types and we don't know which variable has the fewest + * bits. Carefully select the smaller and cast it to the type of the + * larger - this cannot overflow. + */ + if (idat_size < save_size) + save_size = (size_t)idat_size; + + else + idat_size = (png_uint_32)save_size; + + png_calculate_crc(png_ptr, png_ptr->current_buffer_ptr, save_size); + + png_process_IDAT_data(png_ptr, png_ptr->current_buffer_ptr, save_size); + + png_ptr->idat_size -= idat_size; + png_ptr->buffer_size -= save_size; + png_ptr->current_buffer_size -= save_size; + png_ptr->current_buffer_ptr += save_size; + } + + if (png_ptr->idat_size == 0) + { + PNG_PUSH_SAVE_BUFFER_IF_LT(4) + png_crc_finish(png_ptr, 0); + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; + png_ptr->mode |= PNG_AFTER_IDAT; + png_ptr->zowner = 0; + } +} + +void /* PRIVATE */ +png_process_IDAT_data(png_structrp png_ptr, png_bytep buffer, + size_t buffer_length) +{ + /* The caller checks for a non-zero buffer length. */ + if (!(buffer_length > 0) || buffer == NULL) + png_error(png_ptr, "No IDAT data (internal error)"); + + /* This routine must process all the data it has been given + * before returning, calling the row callback as required to + * handle the uncompressed results. + */ + png_ptr->zstream.next_in = buffer; + /* TODO: WARNING: TRUNCATION ERROR: DANGER WILL ROBINSON: */ + png_ptr->zstream.avail_in = (uInt)buffer_length; + + /* Keep going until the decompressed data is all processed + * or the stream marked as finished. + */ + while (png_ptr->zstream.avail_in > 0 && + (png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) == 0) + { + int ret; + + /* We have data for zlib, but we must check that zlib + * has someplace to put the results. It doesn't matter + * if we don't expect any results -- it may be the input + * data is just the LZ end code. + */ + if (!(png_ptr->zstream.avail_out > 0)) + { + /* TODO: WARNING: TRUNCATION ERROR: DANGER WILL ROBINSON: */ + png_ptr->zstream.avail_out = (uInt)(PNG_ROWBYTES(png_ptr->pixel_depth, + png_ptr->iwidth) + 1); + + png_ptr->zstream.next_out = png_ptr->row_buf; + } + + /* Using Z_SYNC_FLUSH here means that an unterminated + * LZ stream (a stream with a missing end code) can still + * be handled, otherwise (Z_NO_FLUSH) a future zlib + * implementation might defer output and therefore + * change the current behavior (see comments in inflate.c + * for why this doesn't happen at present with zlib 1.2.5). + */ + ret = PNG_INFLATE(png_ptr, Z_SYNC_FLUSH); + + /* Check for any failure before proceeding. */ + if (ret != Z_OK && ret != Z_STREAM_END) + { + /* Terminate the decompression. */ + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + png_ptr->zowner = 0; + + /* This may be a truncated stream (missing or + * damaged end code). Treat that as a warning. + */ + if (png_ptr->row_number >= png_ptr->num_rows || + png_ptr->pass > 6) + png_warning(png_ptr, "Truncated compressed data in IDAT"); + + else + { + if (ret == Z_DATA_ERROR) + png_benign_error(png_ptr, "IDAT: ADLER32 checksum mismatch"); + else + png_error(png_ptr, "Decompression error in IDAT"); + } + + /* Skip the check on unprocessed input */ + return; + } + + /* Did inflate output any data? */ + if (png_ptr->zstream.next_out != png_ptr->row_buf) + { + /* Is this unexpected data after the last row? + * If it is, artificially terminate the LZ output + * here. + */ + if (png_ptr->row_number >= png_ptr->num_rows || + png_ptr->pass > 6) + { + /* Extra data. */ + png_warning(png_ptr, "Extra compressed data in IDAT"); + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + png_ptr->zowner = 0; + + /* Do no more processing; skip the unprocessed + * input check below. + */ + return; + } + + /* Do we have a complete row? */ + if (png_ptr->zstream.avail_out == 0) + png_push_process_row(png_ptr); + } + + /* And check for the end of the stream. */ + if (ret == Z_STREAM_END) + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + } + + /* All the data should have been processed, if anything + * is left at this point we have bytes of IDAT data + * after the zlib end code. + */ + if (png_ptr->zstream.avail_in > 0) + png_warning(png_ptr, "Extra compression data in IDAT"); +} + +void /* PRIVATE */ +png_push_process_row(png_structrp png_ptr) +{ + /* 1.5.6: row_info moved out of png_struct to a local here. */ + png_row_info row_info; + + row_info.width = png_ptr->iwidth; /* NOTE: width of current interlaced row */ + row_info.color_type = png_ptr->color_type; + row_info.bit_depth = png_ptr->bit_depth; + row_info.channels = png_ptr->channels; + row_info.pixel_depth = png_ptr->pixel_depth; + row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width); + + if (png_ptr->row_buf[0] > PNG_FILTER_VALUE_NONE) + { + if (png_ptr->row_buf[0] < PNG_FILTER_VALUE_LAST) + png_read_filter_row(png_ptr, &row_info, png_ptr->row_buf + 1, + png_ptr->prev_row + 1, png_ptr->row_buf[0]); + else + png_error(png_ptr, "bad adaptive filter value"); + } + + /* libpng 1.5.6: the following line was copying png_ptr->rowbytes before + * 1.5.6, while the buffer really is this big in current versions of libpng + * it may not be in the future, so this was changed just to copy the + * interlaced row count: + */ + memcpy(png_ptr->prev_row, png_ptr->row_buf, row_info.rowbytes + 1); + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED + if (png_ptr->transformations != 0) + png_do_read_transformations(png_ptr, &row_info); +#endif + + /* The transformed pixel depth should match the depth now in row_info. */ + if (png_ptr->transformed_pixel_depth == 0) + { + png_ptr->transformed_pixel_depth = row_info.pixel_depth; + if (row_info.pixel_depth > png_ptr->maximum_pixel_depth) + png_error(png_ptr, "progressive row overflow"); + } + + else if (png_ptr->transformed_pixel_depth != row_info.pixel_depth) + png_error(png_ptr, "internal progressive row size calculation error"); + + +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Expand interlaced rows to full size */ + if (png_ptr->interlaced != 0 && + (png_ptr->transformations & PNG_INTERLACE) != 0) + { + if (png_ptr->pass < 6) + png_do_read_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass, + png_ptr->transformations); + + switch (png_ptr->pass) + { + case 0: + { + int i; + for (i = 0; i < 8 && png_ptr->pass == 0; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); /* Updates png_ptr->pass */ + } + + if (png_ptr->pass == 2) /* Pass 1 might be empty */ + { + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + if (png_ptr->pass == 4 && png_ptr->height <= 4) + { + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + if (png_ptr->pass == 6 && png_ptr->height <= 4) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + break; + } + + case 1: + { + int i; + for (i = 0; i < 8 && png_ptr->pass == 1; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 2) /* Skip top 4 generated rows */ + { + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + break; + } + + case 2: + { + int i; + + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 4) /* Pass 3 might be empty */ + { + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + break; + } + + case 3: + { + int i; + + for (i = 0; i < 4 && png_ptr->pass == 3; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 4) /* Skip top two generated rows */ + { + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + break; + } + + case 4: + { + int i; + + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 6) /* Pass 5 might be empty */ + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + break; + } + + case 5: + { + int i; + + for (i = 0; i < 2 && png_ptr->pass == 5; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 6) /* Skip top generated row */ + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + break; + } + + default: + case 6: + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + + if (png_ptr->pass != 6) + break; + + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + } + else +#endif + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } +} + +void /* PRIVATE */ +png_read_push_finish_row(png_structrp png_ptr) +{ +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static const png_byte png_pass_start[] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static const png_byte png_pass_inc[] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static const png_byte png_pass_ystart[] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static const png_byte png_pass_yinc[] = {8, 8, 8, 4, 4, 2, 2}; + + /* Height of interlace block. This is not currently used - if you need + * it, uncomment it here and in png.h + static const png_byte png_pass_height[] = {8, 8, 4, 4, 2, 2, 1}; + */ +#endif + + png_ptr->row_number++; + if (png_ptr->row_number < png_ptr->num_rows) + return; + +#ifdef PNG_READ_INTERLACING_SUPPORTED + if (png_ptr->interlaced != 0) + { + png_ptr->row_number = 0; + memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); + + do + { + png_ptr->pass++; + if ((png_ptr->pass == 1 && png_ptr->width < 5) || + (png_ptr->pass == 3 && png_ptr->width < 3) || + (png_ptr->pass == 5 && png_ptr->width < 2)) + png_ptr->pass++; + + if (png_ptr->pass > 7) + png_ptr->pass--; + + if (png_ptr->pass >= 7) + break; + + png_ptr->iwidth = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + + if ((png_ptr->transformations & PNG_INTERLACE) != 0) + break; + + png_ptr->num_rows = (png_ptr->height + + png_pass_yinc[png_ptr->pass] - 1 - + png_pass_ystart[png_ptr->pass]) / + png_pass_yinc[png_ptr->pass]; + + } while (png_ptr->iwidth == 0 || png_ptr->num_rows == 0); + } +#endif /* READ_INTERLACING */ +} + +void /* PRIVATE */ +png_push_have_info(png_structrp png_ptr, png_inforp info_ptr) +{ + if (png_ptr->info_fn != NULL) + (*(png_ptr->info_fn))(png_ptr, info_ptr); +} + +void /* PRIVATE */ +png_push_have_end(png_structrp png_ptr, png_inforp info_ptr) +{ + if (png_ptr->end_fn != NULL) + (*(png_ptr->end_fn))(png_ptr, info_ptr); +} + +void /* PRIVATE */ +png_push_have_row(png_structrp png_ptr, png_bytep row) +{ + if (png_ptr->row_fn != NULL) + (*(png_ptr->row_fn))(png_ptr, row, png_ptr->row_number, + (int)png_ptr->pass); +} + +#ifdef PNG_READ_INTERLACING_SUPPORTED +void PNGAPI +png_progressive_combine_row(png_const_structrp png_ptr, png_bytep old_row, + png_const_bytep new_row) +{ + if (png_ptr == NULL) + return; + + /* new_row is a flag here - if it is NULL then the app callback was called + * from an empty row (see the calls to png_struct::row_fn below), otherwise + * it must be png_ptr->row_buf+1 + */ + if (new_row != NULL) + png_combine_row(png_ptr, old_row, 1/*blocky display*/); +} +#endif /* READ_INTERLACING */ + +void PNGAPI +png_set_progressive_read_fn(png_structrp png_ptr, png_voidp progressive_ptr, + png_progressive_info_ptr info_fn, png_progressive_row_ptr row_fn, + png_progressive_end_ptr end_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->info_fn = info_fn; + png_ptr->row_fn = row_fn; + png_ptr->end_fn = end_fn; + + png_set_read_fn(png_ptr, progressive_ptr, png_push_fill_buffer); +} + +png_voidp PNGAPI +png_get_progressive_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return (NULL); + + return png_ptr->io_ptr; +} +#endif /* PROGRESSIVE_READ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngpriv.h b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngpriv.h new file mode 100644 index 0000000..76502f2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngpriv.h @@ -0,0 +1,2152 @@ + +/* pngpriv.h - private declarations for use inside libpng + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +/* The symbols declared in this file (including the functions declared + * as extern) are PRIVATE. They are not part of the libpng public + * interface, and are not recommended for use by regular applications. + * Some of them may become public in the future; others may stay private, + * change in an incompatible way, or even disappear. + * Although the libpng users are not forbidden to include this header, + * they should be well aware of the issues that may arise from doing so. + */ + +#ifndef PNGPRIV_H +#define PNGPRIV_H + +/* Feature Test Macros. The following are defined here to ensure that correctly + * implemented libraries reveal the APIs libpng needs to build and hide those + * that are not needed and potentially damaging to the compilation. + * + * Feature Test Macros must be defined before any system header is included (see + * POSIX 1003.1 2.8.2 "POSIX Symbols." + * + * These macros only have an effect if the operating system supports either + * POSIX 1003.1 or C99, or both. On other operating systems (particularly + * Windows/Visual Studio) there is no effect; the OS specific tests below are + * still required (as of 2011-05-02.) + */ +#ifndef _POSIX_SOURCE +# define _POSIX_SOURCE 1 /* Just the POSIX 1003.1 and C89 APIs */ +#endif + +#ifndef PNG_VERSION_INFO_ONLY +/* Standard library headers not required by png.h: */ +# include +# include +#endif + +#define PNGLIB_BUILD /*libpng is being built, not used*/ + +/* If HAVE_CONFIG_H is defined during the build then the build system must + * provide an appropriate "config.h" file on the include path. The header file + * must provide definitions as required below (search for "HAVE_CONFIG_H"); + * see configure.ac for more details of the requirements. The macro + * "PNG_NO_CONFIG_H" is provided for maintainers to test for dependencies on + * 'configure'; define this macro to prevent the configure build including the + * configure generated config.h. Libpng is expected to compile without *any* + * special build system support on a reasonably ANSI-C compliant system. + */ +#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) +# include + + /* Pick up the definition of 'restrict' from config.h if it was read: */ +# define PNG_RESTRICT restrict +#endif + +/* To support symbol prefixing it is necessary to know *before* including png.h + * whether the fixed point (and maybe other) APIs are exported, because if they + * are not internal definitions may be required. This is handled below just + * before png.h is included, but load the configuration now if it is available. + */ +#ifndef PNGLCONF_H +# include "pnglibconf.h" +#endif + +/* Local renames may change non-exported API functions from png.h */ +#if defined(PNG_PREFIX) && !defined(PNGPREFIX_H) +# include "pngprefix.h" +#endif + +#ifdef PNG_USER_CONFIG +# include "pngusr.h" + /* These should have been defined in pngusr.h */ +# ifndef PNG_USER_PRIVATEBUILD +# define PNG_USER_PRIVATEBUILD "Custom libpng build" +# endif +# ifndef PNG_USER_DLLFNAME_POSTFIX +# define PNG_USER_DLLFNAME_POSTFIX "Cb" +# endif +#endif + +/* Compile time options. + * ===================== + * In a multi-arch build the compiler may compile the code several times for the + * same object module, producing different binaries for different architectures. + * When this happens configure-time setting of the target host options cannot be + * done and this interferes with the handling of the ARM NEON optimizations, and + * possibly other similar optimizations. Put additional tests here; in general + * this is needed when the same option can be changed at both compile time and + * run time depending on the target OS (i.e. iOS vs Android.) + * + * NOTE: symbol prefixing does not pass $(CFLAGS) to the preprocessor, because + * this is not possible with certain compilers (Oracle SUN OS CC), as a result + * it is necessary to ensure that all extern functions that *might* be used + * regardless of $(CFLAGS) get declared in this file. The test on __ARM_NEON__ + * below is one example of this behavior because it is controlled by the + * presence or not of -mfpu=neon on the GCC command line, it is possible to do + * this in $(CC), e.g. "CC=gcc -mfpu=neon", but people who build libpng rarely + * do this. + */ +#ifndef PNG_ARM_NEON_OPT + /* ARM NEON optimizations are being controlled by the compiler settings, + * typically the target FPU. If the FPU has been set to NEON (-mfpu=neon + * with GCC) then the compiler will define __ARM_NEON__ and we can rely + * unconditionally on NEON instructions not crashing, otherwise we must + * disable use of NEON instructions. + * + * NOTE: at present these optimizations depend on 'ALIGNED_MEMORY', so they + * can only be turned on automatically if that is supported too. If + * PNG_ARM_NEON_OPT is set in CPPFLAGS (to >0) then arm/arm_init.c will fail + * to compile with an appropriate #error if ALIGNED_MEMORY has been turned + * off. + * + * Note that gcc-4.9 defines __ARM_NEON instead of the deprecated + * __ARM_NEON__, so we check both variants. + * + * To disable ARM_NEON optimizations entirely, and skip compiling the + * associated assembler code, pass --enable-arm-neon=no to configure + * or put -DPNG_ARM_NEON_OPT=0 in CPPFLAGS. + */ +# if (defined(__ARM_NEON__) || defined(__ARM_NEON)) && \ + defined(PNG_ALIGNED_MEMORY_SUPPORTED) && 0 // disabled +# define PNG_ARM_NEON_OPT 2 +# else +# define PNG_ARM_NEON_OPT 0 +# endif +#endif + +#if PNG_ARM_NEON_OPT > 0 + /* NEON optimizations are to be at least considered by libpng, so enable the + * callbacks to do this. + */ +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_neon + + /* By default the 'intrinsics' code in arm/filter_neon_intrinsics.c is used + * if possible - if __ARM_NEON__ is set and the compiler version is not known + * to be broken. This is controlled by PNG_ARM_NEON_IMPLEMENTATION which can + * be: + * + * 1 The intrinsics code (the default with __ARM_NEON__) + * 2 The hand coded assembler (the default without __ARM_NEON__) + * + * It is possible to set PNG_ARM_NEON_IMPLEMENTATION in CPPFLAGS, however + * this is *NOT* supported and may cease to work even after a minor revision + * to libpng. It *is* valid to do this for testing purposes, e.g. speed + * testing or a new compiler, but the results should be communicated to the + * libpng implementation list for incorporation in the next minor release. + */ +# ifndef PNG_ARM_NEON_IMPLEMENTATION +# if defined(__ARM_NEON__) || defined(__ARM_NEON) +# if defined(__clang__) + /* At present it is unknown by the libpng developers which versions + * of clang support the intrinsics, however some or perhaps all + * versions do not work with the assembler so this may be + * irrelevant, so just use the default (do nothing here.) + */ +# elif defined(__GNUC__) + /* GCC 4.5.4 NEON support is known to be broken. 4.6.3 is known to + * work, so if this *is* GCC, or G++, look for a version >4.5 + */ +# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6) +# define PNG_ARM_NEON_IMPLEMENTATION 2 +# endif /* no GNUC support */ +# endif /* __GNUC__ */ +# else /* !defined __ARM_NEON__ */ + /* The 'intrinsics' code simply won't compile without this -mfpu=neon: + */ +# if !defined(__aarch64__) + /* The assembler code currently does not work on ARM64 */ +# define PNG_ARM_NEON_IMPLEMENTATION 2 +# endif /* __aarch64__ */ +# endif /* __ARM_NEON__ */ +# endif /* !PNG_ARM_NEON_IMPLEMENTATION */ + +# ifndef PNG_ARM_NEON_IMPLEMENTATION + /* Use the intrinsics code by default. */ +# define PNG_ARM_NEON_IMPLEMENTATION 1 +# endif +#endif /* PNG_ARM_NEON_OPT > 0 */ + +#ifndef PNG_MIPS_MSA_OPT +# if defined(__mips_msa) && (__mips_isa_rev >= 5) && defined(PNG_ALIGNED_MEMORY_SUPPORTED) +# define PNG_MIPS_MSA_OPT 2 +# else +# define PNG_MIPS_MSA_OPT 0 +# endif +#endif + +#ifndef PNG_POWERPC_VSX_OPT +# if defined(__PPC64__) && defined(__ALTIVEC__) && defined(__VSX__) +# define PNG_POWERPC_VSX_OPT 2 +# else +# define PNG_POWERPC_VSX_OPT 0 +# endif +#endif + +#ifndef PNG_INTEL_SSE_OPT +# ifdef PNG_INTEL_SSE + /* Only check for SSE if the build configuration has been modified to + * enable SSE optimizations. This means that these optimizations will + * be off by default. See contrib/intel for more details. + */ +# if defined(__SSE4_1__) || defined(__AVX__) || defined(__SSSE3__) || \ + defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2) +# define PNG_INTEL_SSE_OPT 1 +# else +# define PNG_INTEL_SSE_OPT 0 +# endif +# else +# define PNG_INTEL_SSE_OPT 0 +# endif +#endif + +#if PNG_INTEL_SSE_OPT > 0 +# ifndef PNG_INTEL_SSE_IMPLEMENTATION +# if defined(__SSE4_1__) || defined(__AVX__) + /* We are not actually using AVX, but checking for AVX is the best + way we can detect SSE4.1 and SSSE3 on MSVC. + */ +# define PNG_INTEL_SSE_IMPLEMENTATION 3 +# elif defined(__SSSE3__) +# define PNG_INTEL_SSE_IMPLEMENTATION 2 +# elif defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2) +# define PNG_INTEL_SSE_IMPLEMENTATION 1 +# else +# define PNG_INTEL_SSE_IMPLEMENTATION 0 +# endif +# endif + +# if PNG_INTEL_SSE_IMPLEMENTATION > 0 +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_sse2 +# endif +#else +# define PNG_INTEL_SSE_IMPLEMENTATION 0 +#endif + +#if PNG_MIPS_MSA_OPT > 0 +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_msa +# ifndef PNG_MIPS_MSA_IMPLEMENTATION +# if defined(__mips_msa) +# if defined(__clang__) +# elif defined(__GNUC__) +# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7) +# define PNG_MIPS_MSA_IMPLEMENTATION 2 +# endif /* no GNUC support */ +# endif /* __GNUC__ */ +# else /* !defined __mips_msa */ +# define PNG_MIPS_MSA_IMPLEMENTATION 2 +# endif /* __mips_msa */ +# endif /* !PNG_MIPS_MSA_IMPLEMENTATION */ + +# ifndef PNG_MIPS_MSA_IMPLEMENTATION +# define PNG_MIPS_MSA_IMPLEMENTATION 1 +# endif +#endif /* PNG_MIPS_MSA_OPT > 0 */ + +#if PNG_POWERPC_VSX_OPT > 0 +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_vsx +# define PNG_POWERPC_VSX_IMPLEMENTATION 1 +#endif + + +/* Is this a build of a DLL where compilation of the object modules requires + * different preprocessor settings to those required for a simple library? If + * so PNG_BUILD_DLL must be set. + * + * If libpng is used inside a DLL but that DLL does not export the libpng APIs + * PNG_BUILD_DLL must not be set. To avoid the code below kicking in build a + * static library of libpng then link the DLL against that. + */ +#ifndef PNG_BUILD_DLL +# ifdef DLL_EXPORT + /* This is set by libtool when files are compiled for a DLL; libtool + * always compiles twice, even on systems where it isn't necessary. Set + * PNG_BUILD_DLL in case it is necessary: + */ +# define PNG_BUILD_DLL +# else +# ifdef _WINDLL + /* This is set by the Microsoft Visual Studio IDE in projects that + * build a DLL. It can't easily be removed from those projects (it + * isn't visible in the Visual Studio UI) so it is a fairly reliable + * indication that PNG_IMPEXP needs to be set to the DLL export + * attributes. + */ +# define PNG_BUILD_DLL +# else +# ifdef __DLL__ + /* This is set by the Borland C system when compiling for a DLL + * (as above.) + */ +# define PNG_BUILD_DLL +# else + /* Add additional compiler cases here. */ +# endif +# endif +# endif +#endif /* Setting PNG_BUILD_DLL if required */ + +/* See pngconf.h for more details: the builder of the library may set this on + * the command line to the right thing for the specific compilation system or it + * may be automagically set above (at present we know of no system where it does + * need to be set on the command line.) + * + * PNG_IMPEXP must be set here when building the library to prevent pngconf.h + * setting it to the "import" setting for a DLL build. + */ +#ifndef PNG_IMPEXP +# ifdef PNG_BUILD_DLL +# define PNG_IMPEXP PNG_DLL_EXPORT +# else + /* Not building a DLL, or the DLL doesn't require specific export + * definitions. + */ +# define PNG_IMPEXP +# endif +#endif + +/* No warnings for private or deprecated functions in the build: */ +#ifndef PNG_DEPRECATED +# define PNG_DEPRECATED +#endif +#ifndef PNG_PRIVATE +# define PNG_PRIVATE +#endif + +/* Symbol preprocessing support. + * + * To enable listing global, but internal, symbols the following macros should + * always be used to declare an extern data or function object in this file. + */ +#ifndef PNG_INTERNAL_DATA +# define PNG_INTERNAL_DATA(type, name, array) PNG_LINKAGE_DATA type name array +#endif + +#ifndef PNG_INTERNAL_FUNCTION +# define PNG_INTERNAL_FUNCTION(type, name, args, attributes)\ + PNG_LINKAGE_FUNCTION PNG_FUNCTION(type, name, args, PNG_EMPTY attributes) +#endif + +#ifndef PNG_INTERNAL_CALLBACK +# define PNG_INTERNAL_CALLBACK(type, name, args, attributes)\ + PNG_LINKAGE_CALLBACK PNG_FUNCTION(type, (PNGCBAPI name), args,\ + PNG_EMPTY attributes) +#endif + +/* If floating or fixed point APIs are disabled they may still be compiled + * internally. To handle this make sure they are declared as the appropriate + * internal extern function (otherwise the symbol prefixing stuff won't work and + * the functions will be used without definitions.) + * + * NOTE: although all the API functions are declared here they are not all + * actually built! Because the declarations are still made it is necessary to + * fake out types that they depend on. + */ +#ifndef PNG_FP_EXPORT +# ifndef PNG_FLOATING_POINT_SUPPORTED +# define PNG_FP_EXPORT(ordinal, type, name, args)\ + PNG_INTERNAL_FUNCTION(type, name, args, PNG_EMPTY); +# ifndef PNG_VERSION_INFO_ONLY + typedef struct png_incomplete png_double; + typedef png_double* png_doublep; + typedef const png_double* png_const_doublep; + typedef png_double** png_doublepp; +# endif +# endif +#endif +#ifndef PNG_FIXED_EXPORT +# ifndef PNG_FIXED_POINT_SUPPORTED +# define PNG_FIXED_EXPORT(ordinal, type, name, args)\ + PNG_INTERNAL_FUNCTION(type, name, args, PNG_EMPTY); +# endif +#endif + +#include "png.h" + +/* pngconf.h does not set PNG_DLL_EXPORT unless it is required, so: */ +#ifndef PNG_DLL_EXPORT +# define PNG_DLL_EXPORT +#endif + +/* This is a global switch to set the compilation for an installed system + * (a release build). It can be set for testing debug builds to ensure that + * they will compile when the build type is switched to RC or STABLE, the + * default is just to use PNG_LIBPNG_BUILD_BASE_TYPE. Set this in CPPFLAGS + * with either: + * + * -DPNG_RELEASE_BUILD Turns on the release compile path + * -DPNG_RELEASE_BUILD=0 Turns it off + * or in your pngusr.h with + * #define PNG_RELEASE_BUILD=1 Turns on the release compile path + * #define PNG_RELEASE_BUILD=0 Turns it off + */ +#ifndef PNG_RELEASE_BUILD +# define PNG_RELEASE_BUILD (PNG_LIBPNG_BUILD_BASE_TYPE >= PNG_LIBPNG_BUILD_RC) +#endif + +/* SECURITY and SAFETY: + * + * libpng is built with support for internal limits on image dimensions and + * memory usage. These are documented in scripts/pnglibconf.dfa of the + * source and recorded in the machine generated header file pnglibconf.h. + */ + +/* If you are running on a machine where you cannot allocate more + * than 64K of memory at once, uncomment this. While libpng will not + * normally need that much memory in a chunk (unless you load up a very + * large file), zlib needs to know how big of a chunk it can use, and + * libpng thus makes sure to check any memory allocation to verify it + * will fit into memory. + * + * zlib provides 'MAXSEG_64K' which, if defined, indicates the + * same limit and pngconf.h (already included) sets the limit + * if certain operating systems are detected. + */ +#if defined(MAXSEG_64K) && !defined(PNG_MAX_MALLOC_64K) +# define PNG_MAX_MALLOC_64K +#endif + +#ifndef PNG_UNUSED +/* Unused formal parameter warnings are silenced using the following macro + * which is expected to have no bad effects on performance (optimizing + * compilers will probably remove it entirely). Note that if you replace + * it with something other than whitespace, you must include the terminating + * semicolon. + */ +# define PNG_UNUSED(param) (void)param; +#endif + +/* Just a little check that someone hasn't tried to define something + * contradictory. + */ +#if (PNG_ZBUF_SIZE > 65536L) && defined(PNG_MAX_MALLOC_64K) +# undef PNG_ZBUF_SIZE +# define PNG_ZBUF_SIZE 65536L +#endif + +/* If warnings or errors are turned off the code is disabled or redirected here. + * From 1.5.4 functions have been added to allow very limited formatting of + * error and warning messages - this code will also be disabled here. + */ +#ifdef PNG_WARNINGS_SUPPORTED +# define PNG_WARNING_PARAMETERS(p) png_warning_parameters p; +#else +# define png_warning_parameter(p,number,string) ((void)0) +# define png_warning_parameter_unsigned(p,number,format,value) ((void)0) +# define png_warning_parameter_signed(p,number,format,value) ((void)0) +# define png_formatted_warning(pp,p,message) ((void)(pp)) +# define PNG_WARNING_PARAMETERS(p) +#endif +#ifndef PNG_ERROR_TEXT_SUPPORTED +# define png_fixed_error(s1,s2) png_err(s1) +#endif + +/* Some fixed point APIs are still required even if not exported because + * they get used by the corresponding floating point APIs. This magic + * deals with this: + */ +#ifdef PNG_FIXED_POINT_SUPPORTED +# define PNGFAPI PNGAPI +#else +# define PNGFAPI /* PRIVATE */ +#endif + +#ifndef PNG_VERSION_INFO_ONLY +/* Other defines specific to compilers can go here. Try to keep + * them inside an appropriate ifdef/endif pair for portability. + */ + +/* C allows up-casts from (void*) to any pointer and (const void*) to any + * pointer to a const object. C++ regards this as a type error and requires an + * explicit, static, cast and provides the static_cast<> rune to ensure that + * const is not cast away. + */ +#ifdef __cplusplus +# define png_voidcast(type, value) static_cast(value) +# define png_constcast(type, value) const_cast(value) +# define png_aligncast(type, value) \ + static_cast(static_cast(value)) +# define png_aligncastconst(type, value) \ + static_cast(static_cast(value)) +#else +# define png_voidcast(type, value) (value) +# ifdef _WIN64 +# ifdef __GNUC__ + typedef unsigned long long png_ptruint; +# else + typedef unsigned __int64 png_ptruint; +# endif +# else + typedef unsigned long png_ptruint; +# endif +# define png_constcast(type, value) ((type)(png_ptruint)(const void*)(value)) +# define png_aligncast(type, value) ((void*)(value)) +# define png_aligncastconst(type, value) ((const void*)(value)) +#endif /* __cplusplus */ + +#if defined(PNG_FLOATING_POINT_SUPPORTED) ||\ + defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) + /* png.c requires the following ANSI-C constants if the conversion of + * floating point to ASCII is implemented therein: + * + * DBL_DIG Maximum number of decimal digits (can be set to any constant) + * DBL_MIN Smallest normalized fp number (can be set to an arbitrary value) + * DBL_MAX Maximum floating point number (can be set to an arbitrary value) + */ +# include + +# if (defined(__MWERKS__) && defined(macintosh)) || defined(applec) || \ + defined(THINK_C) || defined(__SC__) || defined(TARGET_OS_MAC) + /* We need to check that hasn't already been included earlier + * as it seems it doesn't agree with , yet we should really use + * if possible. + */ +# if !defined(__MATH_H__) && !defined(__MATH_H) && !defined(__cmath__) +# include +# endif +# else +# include +# endif +# if defined(_AMIGA) && defined(__SASC) && defined(_M68881) + /* Amiga SAS/C: We must include builtin FPU functions when compiling using + * MATH=68881 + */ +# include +# endif +#endif + +/* This provides the non-ANSI (far) memory allocation routines. */ +#if defined(__TURBOC__) && defined(__MSDOS__) +# include +# include +#endif + +#if defined(WIN32) || defined(_Windows) || defined(_WINDOWS) || \ + defined(_WIN32) || defined(__WIN32__) +# include /* defines _WINDOWS_ macro */ +#endif +#endif /* PNG_VERSION_INFO_ONLY */ + +/* Moved here around 1.5.0beta36 from pngconf.h */ +/* Users may want to use these so they are not private. Any library + * functions that are passed far data must be model-independent. + */ + +/* Memory model/platform independent fns */ +#ifndef PNG_ABORT +# ifdef _WINDOWS_ +# define PNG_ABORT() ExitProcess(0) +# else +# define PNG_ABORT() abort() +# endif +#endif + +/* These macros may need to be architecture dependent. */ +#define PNG_ALIGN_NONE 0 /* do not use data alignment */ +#define PNG_ALIGN_ALWAYS 1 /* assume unaligned accesses are OK */ +#ifdef offsetof +# define PNG_ALIGN_OFFSET 2 /* use offsetof to determine alignment */ +#else +# define PNG_ALIGN_OFFSET -1 /* prevent the use of this */ +#endif +#define PNG_ALIGN_SIZE 3 /* use sizeof to determine alignment */ + +#ifndef PNG_ALIGN_TYPE + /* Default to using aligned access optimizations and requiring alignment to a + * multiple of the data type size. Override in a compiler specific fashion + * if necessary by inserting tests here: + */ +# define PNG_ALIGN_TYPE PNG_ALIGN_SIZE +#endif + +#if PNG_ALIGN_TYPE == PNG_ALIGN_SIZE + /* This is used because in some compiler implementations non-aligned + * structure members are supported, so the offsetof approach below fails. + * Set PNG_ALIGN_SIZE=0 for compiler combinations where unaligned access + * is good for performance. Do not do this unless you have tested the result + * and understand it. + */ +# define png_alignof(type) (sizeof (type)) +#else +# if PNG_ALIGN_TYPE == PNG_ALIGN_OFFSET +# define png_alignof(type) offsetof(struct{char c; type t;}, t) +# else +# if PNG_ALIGN_TYPE == PNG_ALIGN_ALWAYS +# define png_alignof(type) (1) +# endif + /* Else leave png_alignof undefined to prevent use thereof */ +# endif +#endif + +/* This implicitly assumes alignment is always to a power of 2. */ +#ifdef png_alignof +# define png_isaligned(ptr, type)\ + (((type)((const char*)ptr-(const char*)0) & \ + (type)(png_alignof(type)-1)) == 0) +#else +# define png_isaligned(ptr, type) 0 +#endif + +/* End of memory model/platform independent support */ +/* End of 1.5.0beta36 move from pngconf.h */ + +/* CONSTANTS and UTILITY MACROS + * These are used internally by libpng and not exposed in the API + */ + +/* Various modes of operation. Note that after an init, mode is set to + * zero automatically when the structure is created. Three of these + * are defined in png.h because they need to be visible to applications + * that call png_set_unknown_chunk(). + */ +/* #define PNG_HAVE_IHDR 0x01U (defined in png.h) */ +/* #define PNG_HAVE_PLTE 0x02U (defined in png.h) */ +#define PNG_HAVE_IDAT 0x04U +/* #define PNG_AFTER_IDAT 0x08U (defined in png.h) */ +#define PNG_HAVE_IEND 0x10U + /* 0x20U (unused) */ + /* 0x40U (unused) */ + /* 0x80U (unused) */ +#define PNG_HAVE_CHUNK_HEADER 0x100U +#define PNG_WROTE_tIME 0x200U +#define PNG_WROTE_INFO_BEFORE_PLTE 0x400U +#define PNG_BACKGROUND_IS_GRAY 0x800U +#define PNG_HAVE_PNG_SIGNATURE 0x1000U +#define PNG_HAVE_CHUNK_AFTER_IDAT 0x2000U /* Have another chunk after IDAT */ + /* 0x4000U (unused) */ +#define PNG_IS_READ_STRUCT 0x8000U /* Else is a write struct */ + +/* Flags for the transformations the PNG library does on the image data */ +#define PNG_BGR 0x0001U +#define PNG_INTERLACE 0x0002U +#define PNG_PACK 0x0004U +#define PNG_SHIFT 0x0008U +#define PNG_SWAP_BYTES 0x0010U +#define PNG_INVERT_MONO 0x0020U +#define PNG_QUANTIZE 0x0040U +#define PNG_COMPOSE 0x0080U /* Was PNG_BACKGROUND */ +#define PNG_BACKGROUND_EXPAND 0x0100U +#define PNG_EXPAND_16 0x0200U /* Added to libpng 1.5.2 */ +#define PNG_16_TO_8 0x0400U /* Becomes 'chop' in 1.5.4 */ +#define PNG_RGBA 0x0800U +#define PNG_EXPAND 0x1000U +#define PNG_GAMMA 0x2000U +#define PNG_GRAY_TO_RGB 0x4000U +#define PNG_FILLER 0x8000U +#define PNG_PACKSWAP 0x10000U +#define PNG_SWAP_ALPHA 0x20000U +#define PNG_STRIP_ALPHA 0x40000U +#define PNG_INVERT_ALPHA 0x80000U +#define PNG_USER_TRANSFORM 0x100000U +#define PNG_RGB_TO_GRAY_ERR 0x200000U +#define PNG_RGB_TO_GRAY_WARN 0x400000U +#define PNG_RGB_TO_GRAY 0x600000U /* two bits, RGB_TO_GRAY_ERR|WARN */ +#define PNG_ENCODE_ALPHA 0x800000U /* Added to libpng-1.5.4 */ +#define PNG_ADD_ALPHA 0x1000000U /* Added to libpng-1.2.7 */ +#define PNG_EXPAND_tRNS 0x2000000U /* Added to libpng-1.2.9 */ +#define PNG_SCALE_16_TO_8 0x4000000U /* Added to libpng-1.5.4 */ + /* 0x8000000U unused */ + /* 0x10000000U unused */ + /* 0x20000000U unused */ + /* 0x40000000U unused */ +/* Flags for png_create_struct */ +#define PNG_STRUCT_PNG 0x0001U +#define PNG_STRUCT_INFO 0x0002U + +/* Flags for the png_ptr->flags rather than declaring a byte for each one */ +#define PNG_FLAG_ZLIB_CUSTOM_STRATEGY 0x0001U +#define PNG_FLAG_ZSTREAM_INITIALIZED 0x0002U /* Added to libpng-1.6.0 */ + /* 0x0004U unused */ +#define PNG_FLAG_ZSTREAM_ENDED 0x0008U /* Added to libpng-1.6.0 */ + /* 0x0010U unused */ + /* 0x0020U unused */ +#define PNG_FLAG_ROW_INIT 0x0040U +#define PNG_FLAG_FILLER_AFTER 0x0080U +#define PNG_FLAG_CRC_ANCILLARY_USE 0x0100U +#define PNG_FLAG_CRC_ANCILLARY_NOWARN 0x0200U +#define PNG_FLAG_CRC_CRITICAL_USE 0x0400U +#define PNG_FLAG_CRC_CRITICAL_IGNORE 0x0800U +#define PNG_FLAG_ASSUME_sRGB 0x1000U /* Added to libpng-1.5.4 */ +#define PNG_FLAG_OPTIMIZE_ALPHA 0x2000U /* Added to libpng-1.5.4 */ +#define PNG_FLAG_DETECT_UNINITIALIZED 0x4000U /* Added to libpng-1.5.4 */ +/* #define PNG_FLAG_KEEP_UNKNOWN_CHUNKS 0x8000U */ +/* #define PNG_FLAG_KEEP_UNSAFE_CHUNKS 0x10000U */ +#define PNG_FLAG_LIBRARY_MISMATCH 0x20000U +#define PNG_FLAG_STRIP_ERROR_NUMBERS 0x40000U +#define PNG_FLAG_STRIP_ERROR_TEXT 0x80000U +#define PNG_FLAG_BENIGN_ERRORS_WARN 0x100000U /* Added to libpng-1.4.0 */ +#define PNG_FLAG_APP_WARNINGS_WARN 0x200000U /* Added to libpng-1.6.0 */ +#define PNG_FLAG_APP_ERRORS_WARN 0x400000U /* Added to libpng-1.6.0 */ + /* 0x800000U unused */ + /* 0x1000000U unused */ + /* 0x2000000U unused */ + /* 0x4000000U unused */ + /* 0x8000000U unused */ + /* 0x10000000U unused */ + /* 0x20000000U unused */ + /* 0x40000000U unused */ + +#define PNG_FLAG_CRC_ANCILLARY_MASK (PNG_FLAG_CRC_ANCILLARY_USE | \ + PNG_FLAG_CRC_ANCILLARY_NOWARN) + +#define PNG_FLAG_CRC_CRITICAL_MASK (PNG_FLAG_CRC_CRITICAL_USE | \ + PNG_FLAG_CRC_CRITICAL_IGNORE) + +#define PNG_FLAG_CRC_MASK (PNG_FLAG_CRC_ANCILLARY_MASK | \ + PNG_FLAG_CRC_CRITICAL_MASK) + +/* Save typing and make code easier to understand */ + +#define PNG_COLOR_DIST(c1, c2) (abs((int)((c1).red) - (int)((c2).red)) + \ + abs((int)((c1).green) - (int)((c2).green)) + \ + abs((int)((c1).blue) - (int)((c2).blue))) + +/* Added to libpng-1.6.0: scale a 16-bit value in the range 0..65535 to 0..255 + * by dividing by 257 *with rounding*. This macro is exact for the given range. + * See the discourse in pngrtran.c png_do_scale_16_to_8. The values in the + * macro were established by experiment (modifying the added value). The macro + * has a second variant that takes a value already scaled by 255 and divides by + * 65535 - this has a maximum error of .502. Over the range 0..65535*65535 it + * only gives off-by-one errors and only for 0.5% (1 in 200) of the values. + */ +#define PNG_DIV65535(v24) (((v24) + 32895) >> 16) +#define PNG_DIV257(v16) PNG_DIV65535((png_uint_32)(v16) * 255) + +/* Added to libpng-1.2.6 JB */ +#define PNG_ROWBYTES(pixel_bits, width) \ + ((pixel_bits) >= 8 ? \ + ((size_t)(width) * (((size_t)(pixel_bits)) >> 3)) : \ + (( ((size_t)(width) * ((size_t)(pixel_bits))) + 7) >> 3) ) + +/* This returns the number of trailing bits in the last byte of a row, 0 if the + * last byte is completely full of pixels. It is, in principle, (pixel_bits x + * width) % 8, but that would overflow for large 'width'. The second macro is + * the same except that it returns the number of unused bits in the last byte; + * (8-TRAILBITS), but 0 when TRAILBITS is 0. + * + * NOTE: these macros are intended to be self-evidently correct and never + * overflow on the assumption that pixel_bits is in the range 0..255. The + * arguments are evaluated only once and they can be signed (e.g. as a result of + * the integral promotions). The result of the expression always has type + * (png_uint_32), however the compiler always knows it is in the range 0..7. + */ +#define PNG_TRAILBITS(pixel_bits, width) \ + (((pixel_bits) * ((width) % (png_uint_32)8)) % 8) + +#define PNG_PADBITS(pixel_bits, width) \ + ((8 - PNG_TRAILBITS(pixel_bits, width)) % 8) + +/* PNG_OUT_OF_RANGE returns true if value is outside the range + * ideal-delta..ideal+delta. Each argument is evaluated twice. + * "ideal" and "delta" should be constants, normally simple + * integers, "value" a variable. Added to libpng-1.2.6 JB + */ +#define PNG_OUT_OF_RANGE(value, ideal, delta) \ + ( (value) < (ideal)-(delta) || (value) > (ideal)+(delta) ) + +/* Conversions between fixed and floating point, only defined if + * required (to make sure the code doesn't accidentally use float + * when it is supposedly disabled.) + */ +#ifdef PNG_FLOATING_POINT_SUPPORTED +/* The floating point conversion can't overflow, though it can and + * does lose accuracy relative to the original fixed point value. + * In practice this doesn't matter because png_fixed_point only + * stores numbers with very low precision. The png_ptr and s + * arguments are unused by default but are there in case error + * checking becomes a requirement. + */ +#define png_float(png_ptr, fixed, s) (.00001 * (fixed)) + +/* The fixed point conversion performs range checking and evaluates + * its argument multiple times, so must be used with care. The + * range checking uses the PNG specification values for a signed + * 32-bit fixed point value except that the values are deliberately + * rounded-to-zero to an integral value - 21474 (21474.83 is roughly + * (2^31-1) * 100000). 's' is a string that describes the value being + * converted. + * + * NOTE: this macro will raise a png_error if the range check fails, + * therefore it is normally only appropriate to use this on values + * that come from API calls or other sources where an out of range + * error indicates a programming error, not a data error! + * + * NOTE: by default this is off - the macro is not used - because the + * function call saves a lot of code. + */ +#ifdef PNG_FIXED_POINT_MACRO_SUPPORTED +#define png_fixed(png_ptr, fp, s) ((fp) <= 21474 && (fp) >= -21474 ?\ + ((png_fixed_point)(100000 * (fp))) : (png_fixed_error(png_ptr, s),0)) +#endif +/* else the corresponding function is defined below, inside the scope of the + * cplusplus test. + */ +#endif + +/* Constants for known chunk types. If you need to add a chunk, define the name + * here. For historical reasons these constants have the form png_; i.e. + * the prefix is lower case. Please use decimal values as the parameters to + * match the ISO PNG specification and to avoid relying on the C locale + * interpretation of character values. + * + * Prior to 1.5.6 these constants were strings, as of 1.5.6 png_uint_32 values + * are computed and a new macro (PNG_STRING_FROM_CHUNK) added to allow a string + * to be generated if required. + * + * PNG_32b correctly produces a value shifted by up to 24 bits, even on + * architectures where (int) is only 16 bits. + */ +#define PNG_32b(b,s) ((png_uint_32)(b) << (s)) +#define PNG_U32(b1,b2,b3,b4) \ + (PNG_32b(b1,24) | PNG_32b(b2,16) | PNG_32b(b3,8) | PNG_32b(b4,0)) + +/* Constants for known chunk types. + * + * MAINTAINERS: If you need to add a chunk, define the name here. + * For historical reasons these constants have the form png_; i.e. + * the prefix is lower case. Please use decimal values as the parameters to + * match the ISO PNG specification and to avoid relying on the C locale + * interpretation of character values. Please keep the list sorted. + * + * Notice that PNG_U32 is used to define a 32-bit value for the 4 byte chunk + * type. In fact the specification does not express chunk types this way, + * however using a 32-bit value means that the chunk type can be read from the + * stream using exactly the same code as used for a 32-bit unsigned value and + * can be examined far more efficiently (using one arithmetic compare). + * + * Prior to 1.5.6 the chunk type constants were expressed as C strings. The + * libpng API still uses strings for 'unknown' chunks and a macro, + * PNG_STRING_FROM_CHUNK, allows a string to be generated if required. Notice + * that for portable code numeric values must still be used; the string "IHDR" + * is not portable and neither is PNG_U32('I', 'H', 'D', 'R'). + * + * In 1.7.0 the definitions will be made public in png.h to avoid having to + * duplicate the same definitions in application code. + */ +#define png_IDAT PNG_U32( 73, 68, 65, 84) +#define png_IEND PNG_U32( 73, 69, 78, 68) +#define png_IHDR PNG_U32( 73, 72, 68, 82) +#define png_PLTE PNG_U32( 80, 76, 84, 69) +#define png_bKGD PNG_U32( 98, 75, 71, 68) +#define png_cHRM PNG_U32( 99, 72, 82, 77) +#define png_eXIf PNG_U32(101, 88, 73, 102) /* registered July 2017 */ +#define png_fRAc PNG_U32(102, 82, 65, 99) /* registered, not defined */ +#define png_gAMA PNG_U32(103, 65, 77, 65) +#define png_gIFg PNG_U32(103, 73, 70, 103) +#define png_gIFt PNG_U32(103, 73, 70, 116) /* deprecated */ +#define png_gIFx PNG_U32(103, 73, 70, 120) +#define png_hIST PNG_U32(104, 73, 83, 84) +#define png_iCCP PNG_U32(105, 67, 67, 80) +#define png_iTXt PNG_U32(105, 84, 88, 116) +#define png_oFFs PNG_U32(111, 70, 70, 115) +#define png_pCAL PNG_U32(112, 67, 65, 76) +#define png_pHYs PNG_U32(112, 72, 89, 115) +#define png_sBIT PNG_U32(115, 66, 73, 84) +#define png_sCAL PNG_U32(115, 67, 65, 76) +#define png_sPLT PNG_U32(115, 80, 76, 84) +#define png_sRGB PNG_U32(115, 82, 71, 66) +#define png_sTER PNG_U32(115, 84, 69, 82) +#define png_tEXt PNG_U32(116, 69, 88, 116) +#define png_tIME PNG_U32(116, 73, 77, 69) +#define png_tRNS PNG_U32(116, 82, 78, 83) +#define png_zTXt PNG_U32(122, 84, 88, 116) + +/* The following will work on (signed char*) strings, whereas the get_uint_32 + * macro will fail on top-bit-set values because of the sign extension. + */ +#define PNG_CHUNK_FROM_STRING(s)\ + PNG_U32(0xff & (s)[0], 0xff & (s)[1], 0xff & (s)[2], 0xff & (s)[3]) + +/* This uses (char), not (png_byte) to avoid warnings on systems where (char) is + * signed and the argument is a (char[]) This macro will fail miserably on + * systems where (char) is more than 8 bits. + */ +#define PNG_STRING_FROM_CHUNK(s,c)\ + (void)(((char*)(s))[0]=(char)(((c)>>24) & 0xff), \ + ((char*)(s))[1]=(char)(((c)>>16) & 0xff),\ + ((char*)(s))[2]=(char)(((c)>>8) & 0xff), \ + ((char*)(s))[3]=(char)((c & 0xff))) + +/* Do the same but terminate with a null character. */ +#define PNG_CSTRING_FROM_CHUNK(s,c)\ + (void)(PNG_STRING_FROM_CHUNK(s,c), ((char*)(s))[4] = 0) + +/* Test on flag values as defined in the spec (section 5.4): */ +#define PNG_CHUNK_ANCILLARY(c) (1 & ((c) >> 29)) +#define PNG_CHUNK_CRITICAL(c) (!PNG_CHUNK_ANCILLARY(c)) +#define PNG_CHUNK_PRIVATE(c) (1 & ((c) >> 21)) +#define PNG_CHUNK_RESERVED(c) (1 & ((c) >> 13)) +#define PNG_CHUNK_SAFE_TO_COPY(c) (1 & ((c) >> 5)) + +/* Gamma values (new at libpng-1.5.4): */ +#define PNG_GAMMA_MAC_OLD 151724 /* Assume '1.8' is really 2.2/1.45! */ +#define PNG_GAMMA_MAC_INVERSE 65909 +#define PNG_GAMMA_sRGB_INVERSE 45455 + +/* Almost everything below is C specific; the #defines above can be used in + * non-C code (so long as it is C-preprocessed) the rest of this stuff cannot. + */ +#ifndef PNG_VERSION_INFO_ONLY + +#include "pngstruct.h" +#include "pnginfo.h" + +/* Validate the include paths - the include path used to generate pnglibconf.h + * must match that used in the build, or we must be using pnglibconf.h.prebuilt: + */ +#if PNG_ZLIB_VERNUM != 0 && PNG_ZLIB_VERNUM != ZLIB_VERNUM +# error ZLIB_VERNUM != PNG_ZLIB_VERNUM \ + "-I (include path) error: see the notes in pngpriv.h" + /* This means that when pnglibconf.h was built the copy of zlib.h that it + * used is not the same as the one being used here. Because the build of + * libpng makes decisions to use inflateInit2 and inflateReset2 based on the + * zlib version number and because this affects handling of certain broken + * PNG files the -I directives must match. + * + * The most likely explanation is that you passed a -I in CFLAGS. This will + * not work; all the preprocessor directives and in particular all the -I + * directives must be in CPPFLAGS. + */ +#endif + +/* This is used for 16-bit gamma tables -- only the top level pointers are + * const; this could be changed: + */ +typedef const png_uint_16p * png_const_uint_16pp; + +/* Added to libpng-1.5.7: sRGB conversion tables */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +PNG_INTERNAL_DATA(const png_uint_16, png_sRGB_table, [256]); + /* Convert from an sRGB encoded value 0..255 to a 16-bit linear value, + * 0..65535. This table gives the closest 16-bit answers (no errors). + */ +#endif + +PNG_INTERNAL_DATA(const png_uint_16, png_sRGB_base, [512]); +PNG_INTERNAL_DATA(const png_byte, png_sRGB_delta, [512]); + +#define PNG_sRGB_FROM_LINEAR(linear) \ + ((png_byte)(0xff & ((png_sRGB_base[(linear)>>15] \ + + ((((linear) & 0x7fff)*png_sRGB_delta[(linear)>>15])>>12)) >> 8))) + /* Given a value 'linear' in the range 0..255*65535 calculate the 8-bit sRGB + * encoded value with maximum error 0.646365. Note that the input is not a + * 16-bit value; it has been multiplied by 255! */ +#endif /* SIMPLIFIED_READ/WRITE */ + + +/* Inhibit C++ name-mangling for libpng functions but not for system calls. */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Internal functions; these are not exported from a DLL however because they + * are used within several of the C source files they have to be C extern. + * + * All of these functions must be declared with PNG_INTERNAL_FUNCTION. + */ + +/* Zlib support */ +#define PNG_UNEXPECTED_ZLIB_RETURN (-7) +PNG_INTERNAL_FUNCTION(void, png_zstream_error,(png_structrp png_ptr, int ret), + PNG_EMPTY); + /* Used by the zlib handling functions to ensure that z_stream::msg is always + * set before they return. + */ + +#ifdef PNG_WRITE_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_free_buffer_list,(png_structrp png_ptr, + png_compression_bufferp *list),PNG_EMPTY); + /* Free the buffer list used by the compressed write code. */ +#endif + +#if defined(PNG_FLOATING_POINT_SUPPORTED) && \ + !defined(PNG_FIXED_POINT_MACRO_SUPPORTED) && \ + (defined(PNG_gAMA_SUPPORTED) || defined(PNG_cHRM_SUPPORTED) || \ + defined(PNG_sCAL_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED)) || \ + (defined(PNG_sCAL_SUPPORTED) && \ + defined(PNG_FLOATING_ARITHMETIC_SUPPORTED)) +PNG_INTERNAL_FUNCTION(png_fixed_point,png_fixed,(png_const_structrp png_ptr, + double fp, png_const_charp text),PNG_EMPTY); +#endif + +/* Check the user version string for compatibility, returns false if the version + * numbers aren't compatible. + */ +PNG_INTERNAL_FUNCTION(int,png_user_version_check,(png_structrp png_ptr, + png_const_charp user_png_ver),PNG_EMPTY); + +/* Internal base allocator - no messages, NULL on failure to allocate. This + * does, however, call the application provided allocator and that could call + * png_error (although that would be a bug in the application implementation.) + */ +PNG_INTERNAL_FUNCTION(png_voidp,png_malloc_base,(png_const_structrp png_ptr, + png_alloc_size_t size),PNG_ALLOCATED); + +#if defined(PNG_TEXT_SUPPORTED) || defined(PNG_sPLT_SUPPORTED) ||\ + defined(PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED) +/* Internal array allocator, outputs no error or warning messages on failure, + * just returns NULL. + */ +PNG_INTERNAL_FUNCTION(png_voidp,png_malloc_array,(png_const_structrp png_ptr, + int nelements, size_t element_size),PNG_ALLOCATED); + +/* The same but an existing array is extended by add_elements. This function + * also memsets the new elements to 0 and copies the old elements. The old + * array is not freed or altered. + */ +PNG_INTERNAL_FUNCTION(png_voidp,png_realloc_array,(png_const_structrp png_ptr, + png_const_voidp array, int old_elements, int add_elements, + size_t element_size),PNG_ALLOCATED); +#endif /* text, sPLT or unknown chunks */ + +/* Magic to create a struct when there is no struct to call the user supplied + * memory allocators. Because error handling has not been set up the memory + * handlers can't safely call png_error, but this is an obscure and undocumented + * restriction so libpng has to assume that the 'free' handler, at least, might + * call png_error. + */ +PNG_INTERNAL_FUNCTION(png_structp,png_create_png_struct, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn, png_voidp mem_ptr, png_malloc_ptr malloc_fn, + png_free_ptr free_fn),PNG_ALLOCATED); + +/* Free memory from internal libpng struct */ +PNG_INTERNAL_FUNCTION(void,png_destroy_png_struct,(png_structrp png_ptr), + PNG_EMPTY); + +/* Free an allocated jmp_buf (always succeeds) */ +PNG_INTERNAL_FUNCTION(void,png_free_jmpbuf,(png_structrp png_ptr),PNG_EMPTY); + +/* Function to allocate memory for zlib. PNGAPI is disallowed. */ +PNG_INTERNAL_FUNCTION(voidpf,png_zalloc,(voidpf png_ptr, uInt items, uInt size), + PNG_ALLOCATED); + +/* Function to free memory for zlib. PNGAPI is disallowed. */ +PNG_INTERNAL_FUNCTION(void,png_zfree,(voidpf png_ptr, voidpf ptr),PNG_EMPTY); + +/* Next four functions are used internally as callbacks. PNGCBAPI is required + * but not PNG_EXPORT. PNGAPI added at libpng version 1.2.3, changed to + * PNGCBAPI at 1.5.0 + */ + +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_read_data,(png_structp png_ptr, + png_bytep data, size_t length),PNG_EMPTY); + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_push_fill_buffer,(png_structp png_ptr, + png_bytep buffer, size_t length),PNG_EMPTY); +#endif + +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_write_data,(png_structp png_ptr, + png_bytep data, size_t length),PNG_EMPTY); + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +# ifdef PNG_STDIO_SUPPORTED +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_flush,(png_structp png_ptr), + PNG_EMPTY); +# endif +#endif + +/* Reset the CRC variable */ +PNG_INTERNAL_FUNCTION(void,png_reset_crc,(png_structrp png_ptr),PNG_EMPTY); + +/* Write the "data" buffer to whatever output you are using */ +PNG_INTERNAL_FUNCTION(void,png_write_data,(png_structrp png_ptr, + png_const_bytep data, size_t length),PNG_EMPTY); + +/* Read and check the PNG file signature */ +PNG_INTERNAL_FUNCTION(void,png_read_sig,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); + +/* Read the chunk header (length + type name) */ +PNG_INTERNAL_FUNCTION(png_uint_32,png_read_chunk_header,(png_structrp png_ptr), + PNG_EMPTY); + +/* Read data from whatever input you are using into the "data" buffer */ +PNG_INTERNAL_FUNCTION(void,png_read_data,(png_structrp png_ptr, png_bytep data, + size_t length),PNG_EMPTY); + +/* Read bytes into buf, and update png_ptr->crc */ +PNG_INTERNAL_FUNCTION(void,png_crc_read,(png_structrp png_ptr, png_bytep buf, + png_uint_32 length),PNG_EMPTY); + +/* Read "skip" bytes, read the file crc, and (optionally) verify png_ptr->crc */ +PNG_INTERNAL_FUNCTION(int,png_crc_finish,(png_structrp png_ptr, + png_uint_32 skip),PNG_EMPTY); + +/* Read the CRC from the file and compare it to the libpng calculated CRC */ +PNG_INTERNAL_FUNCTION(int,png_crc_error,(png_structrp png_ptr),PNG_EMPTY); + +/* Calculate the CRC over a section of data. Note that we are only + * passing a maximum of 64K on systems that have this as a memory limit, + * since this is the maximum buffer size we can specify. + */ +PNG_INTERNAL_FUNCTION(void,png_calculate_crc,(png_structrp png_ptr, + png_const_bytep ptr, size_t length),PNG_EMPTY); + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_flush,(png_structrp png_ptr),PNG_EMPTY); +#endif + +/* Write various chunks */ + +/* Write the IHDR chunk, and update the png_struct with the necessary + * information. + */ +PNG_INTERNAL_FUNCTION(void,png_write_IHDR,(png_structrp png_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, int color_type, + int compression_method, int filter_method, int interlace_method),PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_write_PLTE,(png_structrp png_ptr, + png_const_colorp palette, png_uint_32 num_pal),PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_compress_IDAT,(png_structrp png_ptr, + png_const_bytep row_data, png_alloc_size_t row_data_length, int flush), + PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_write_IEND,(png_structrp png_ptr),PNG_EMPTY); + +#ifdef PNG_WRITE_gAMA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_gAMA_fixed,(png_structrp png_ptr, + png_fixed_point file_gamma),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_sBIT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sBIT,(png_structrp png_ptr, + png_const_color_8p sbit, int color_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_cHRM_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_cHRM_fixed,(png_structrp png_ptr, + const png_xy *xy), PNG_EMPTY); + /* The xy value must have been previously validated */ +#endif + +#ifdef PNG_WRITE_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sRGB,(png_structrp png_ptr, + int intent),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_eXIf_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_eXIf,(png_structrp png_ptr, + png_bytep exif, int num_exif),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_iCCP_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_iCCP,(png_structrp png_ptr, + png_const_charp name, png_const_bytep profile), PNG_EMPTY); + /* The profile must have been previously validated for correctness, the + * length comes from the first four bytes. Only the base, deflate, + * compression is supported. + */ +#endif + +#ifdef PNG_WRITE_sPLT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sPLT,(png_structrp png_ptr, + png_const_sPLT_tp palette),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_tRNS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_tRNS,(png_structrp png_ptr, + png_const_bytep trans, png_const_color_16p values, int number, + int color_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_bKGD_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_bKGD,(png_structrp png_ptr, + png_const_color_16p values, int color_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_hIST_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_hIST,(png_structrp png_ptr, + png_const_uint_16p hist, int num_hist),PNG_EMPTY); +#endif + +/* Chunks that have keywords */ +#ifdef PNG_WRITE_tEXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_tEXt,(png_structrp png_ptr, + png_const_charp key, png_const_charp text, size_t text_len),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_zTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_zTXt,(png_structrp png_ptr, png_const_charp + key, png_const_charp text, int compression),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_iTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_iTXt,(png_structrp png_ptr, + int compression, png_const_charp key, png_const_charp lang, + png_const_charp lang_key, png_const_charp text),PNG_EMPTY); +#endif + +#ifdef PNG_TEXT_SUPPORTED /* Added at version 1.0.14 and 1.2.4 */ +PNG_INTERNAL_FUNCTION(int,png_set_text_2,(png_const_structrp png_ptr, + png_inforp info_ptr, png_const_textp text_ptr, int num_text),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_oFFs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_oFFs,(png_structrp png_ptr, + png_int_32 x_offset, png_int_32 y_offset, int unit_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_pCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_pCAL,(png_structrp png_ptr, + png_charp purpose, png_int_32 X0, png_int_32 X1, int type, int nparams, + png_const_charp units, png_charpp params),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_pHYs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_pHYs,(png_structrp png_ptr, + png_uint_32 x_pixels_per_unit, png_uint_32 y_pixels_per_unit, + int unit_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_tIME_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_tIME,(png_structrp png_ptr, + png_const_timep mod_time),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_sCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sCAL_s,(png_structrp png_ptr, + int unit, png_const_charp width, png_const_charp height),PNG_EMPTY); +#endif + +/* Called when finished processing a row of data */ +PNG_INTERNAL_FUNCTION(void,png_write_finish_row,(png_structrp png_ptr), + PNG_EMPTY); + +/* Internal use only. Called before first row of data */ +PNG_INTERNAL_FUNCTION(void,png_write_start_row,(png_structrp png_ptr), + PNG_EMPTY); + +/* Combine a row of data, dealing with alpha, etc. if requested. 'row' is an + * array of png_ptr->width pixels. If the image is not interlaced or this + * is the final pass this just does a memcpy, otherwise the "display" flag + * is used to determine whether to copy pixels that are not in the current pass. + * + * Because 'png_do_read_interlace' (below) replicates pixels this allows this + * function to achieve the documented 'blocky' appearance during interlaced read + * if display is 1 and the 'sparkle' appearance, where existing pixels in 'row' + * are not changed if they are not in the current pass, when display is 0. + * + * 'display' must be 0 or 1, otherwise the memcpy will be done regardless. + * + * The API always reads from the png_struct row buffer and always assumes that + * it is full width (png_do_read_interlace has already been called.) + * + * This function is only ever used to write to row buffers provided by the + * caller of the relevant libpng API and the row must have already been + * transformed by the read transformations. + * + * The PNG_USE_COMPILE_TIME_MASKS option causes generation of pre-computed + * bitmasks for use within the code, otherwise runtime generated masks are used. + * The default is compile time masks. + */ +#ifndef PNG_USE_COMPILE_TIME_MASKS +# define PNG_USE_COMPILE_TIME_MASKS 1 +#endif +PNG_INTERNAL_FUNCTION(void,png_combine_row,(png_const_structrp png_ptr, + png_bytep row, int display),PNG_EMPTY); + +#ifdef PNG_READ_INTERLACING_SUPPORTED +/* Expand an interlaced row: the 'row_info' describes the pass data that has + * been read in and must correspond to the pixels in 'row', the pixels are + * expanded (moved apart) in 'row' to match the final layout, when doing this + * the pixels are *replicated* to the intervening space. This is essential for + * the correct operation of png_combine_row, above. + */ +PNG_INTERNAL_FUNCTION(void,png_do_read_interlace,(png_row_infop row_info, + png_bytep row, int pass, png_uint_32 transformations),PNG_EMPTY); +#endif + +/* GRR TO DO (2.0 or whenever): simplify other internal calling interfaces */ + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED +/* Grab pixels out of a row for an interlaced pass */ +PNG_INTERNAL_FUNCTION(void,png_do_write_interlace,(png_row_infop row_info, + png_bytep row, int pass),PNG_EMPTY); +#endif + +/* Unfilter a row: check the filter value before calling this, there is no point + * calling it for PNG_FILTER_VALUE_NONE. + */ +PNG_INTERNAL_FUNCTION(void,png_read_filter_row,(png_structrp pp, png_row_infop + row_info, png_bytep row, png_const_bytep prev_row, int filter),PNG_EMPTY); + +#if PNG_ARM_NEON_OPT > 0 +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_neon,(png_row_infop row_info, + png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +#endif + +#if PNG_MIPS_MSA_OPT > 0 +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_msa,(png_row_infop row_info, + png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_msa,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_msa,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_msa,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_msa,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_msa,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_msa,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +#endif + +#if PNG_POWERPC_VSX_OPT > 0 +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_vsx,(png_row_infop row_info, + png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_vsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_vsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_vsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_vsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_vsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_vsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +#endif + +#if PNG_INTEL_SSE_IMPLEMENTATION > 0 +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_sse2,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_sse2,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_sse2,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_sse2,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_sse2,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_sse2,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +#endif + +/* Choose the best filter to use and filter the row data */ +PNG_INTERNAL_FUNCTION(void,png_write_find_filter,(png_structrp png_ptr, + png_row_infop row_info),PNG_EMPTY); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_read_IDAT_data,(png_structrp png_ptr, + png_bytep output, png_alloc_size_t avail_out),PNG_EMPTY); + /* Read 'avail_out' bytes of data from the IDAT stream. If the output buffer + * is NULL the function checks, instead, for the end of the stream. In this + * case a benign error will be issued if the stream end is not found or if + * extra data has to be consumed. + */ +PNG_INTERNAL_FUNCTION(void,png_read_finish_IDAT,(png_structrp png_ptr), + PNG_EMPTY); + /* This cleans up when the IDAT LZ stream does not end when the last image + * byte is read; there is still some pending input. + */ + +PNG_INTERNAL_FUNCTION(void,png_read_finish_row,(png_structrp png_ptr), + PNG_EMPTY); + /* Finish a row while reading, dealing with interlacing passes, etc. */ +#endif /* SEQUENTIAL_READ */ + +/* Initialize the row buffers, etc. */ +PNG_INTERNAL_FUNCTION(void,png_read_start_row,(png_structrp png_ptr),PNG_EMPTY); + +#if ZLIB_VERNUM >= 0x1240 +PNG_INTERNAL_FUNCTION(int,png_zlib_inflate,(png_structrp png_ptr, int flush), + PNG_EMPTY); +# define PNG_INFLATE(pp, flush) png_zlib_inflate(pp, flush) +#else /* Zlib < 1.2.4 */ +# define PNG_INFLATE(pp, flush) inflate(&(pp)->zstream, flush) +#endif /* Zlib < 1.2.4 */ + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +/* Optional call to update the users info structure */ +PNG_INTERNAL_FUNCTION(void,png_read_transform_info,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +#endif + +/* Shared transform functions, defined in pngtran.c */ +#if defined(PNG_WRITE_FILLER_SUPPORTED) || \ + defined(PNG_READ_STRIP_ALPHA_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_strip_channel,(png_row_infop row_info, + png_bytep row, int at_start),PNG_EMPTY); +#endif + +#ifdef PNG_16BIT_SUPPORTED +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_swap,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED) || \ + defined(PNG_WRITE_PACKSWAP_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_packswap,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_invert,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_bgr,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +/* The following decodes the appropriate chunks, and does error correction, + * then calls the appropriate callback for the chunk if it is valid. + */ + +/* Decode the IHDR chunk */ +PNG_INTERNAL_FUNCTION(void,png_handle_IHDR,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_handle_PLTE,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_handle_IEND,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); + +#ifdef PNG_READ_bKGD_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_bKGD,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_cHRM,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_eXIf_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_eXIf,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_gAMA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_gAMA,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_hIST_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_hIST,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_iCCP_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_iCCP,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif /* READ_iCCP */ + +#ifdef PNG_READ_iTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_iTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_oFFs,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_pCAL,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_pHYs,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sBIT,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sCAL,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_sPLT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sPLT,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif /* READ_sPLT */ + +#ifdef PNG_READ_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sRGB,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_tEXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_tIME_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_tIME,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_tRNS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_tRNS,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_zTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +PNG_INTERNAL_FUNCTION(void,png_check_chunk_name,(png_const_structrp png_ptr, + png_uint_32 chunk_name),PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_check_chunk_length,(png_const_structrp png_ptr, + png_uint_32 chunk_length),PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_handle_unknown,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length, int keep),PNG_EMPTY); + /* This is the function that gets called for unknown chunks. The 'keep' + * argument is either non-zero for a known chunk that has been set to be + * handled as unknown or zero for an unknown chunk. By default the function + * just skips the chunk or errors out if it is critical. + */ + +#if defined(PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) ||\ + defined(PNG_HANDLE_AS_UNKNOWN_SUPPORTED) +PNG_INTERNAL_FUNCTION(int,png_chunk_unknown_handling, + (png_const_structrp png_ptr, png_uint_32 chunk_name),PNG_EMPTY); + /* Exactly as the API png_handle_as_unknown() except that the argument is a + * 32-bit chunk name, not a string. + */ +#endif /* READ_UNKNOWN_CHUNKS || HANDLE_AS_UNKNOWN */ + +/* Handle the transformations for reading and writing */ +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_read_transformations,(png_structrp png_ptr, + png_row_infop row_info),PNG_EMPTY); +#endif +#ifdef PNG_WRITE_TRANSFORMS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_write_transformations,(png_structrp png_ptr, + png_row_infop row_info),PNG_EMPTY); +#endif + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_init_read_transformations,(png_structrp png_ptr), + PNG_EMPTY); +#endif + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_read_chunk,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_sig,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_check_crc,(png_structrp png_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_save_buffer,(png_structrp png_ptr), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_restore_buffer,(png_structrp png_ptr, + png_bytep buffer, size_t buffer_length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_IDAT,(png_structrp png_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_process_IDAT_data,(png_structrp png_ptr, + png_bytep buffer, size_t buffer_length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_process_row,(png_structrp png_ptr), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_handle_unknown,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_have_info,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_have_end,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_have_row,(png_structrp png_ptr, + png_bytep row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_end,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_process_some_data,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_push_finish_row,(png_structrp png_ptr), + PNG_EMPTY); +# ifdef PNG_READ_tEXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_handle_tEXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_tEXt,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +# endif +# ifdef PNG_READ_zTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_handle_zTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_zTXt,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +# endif +# ifdef PNG_READ_iTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_handle_iTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_iTXt,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +# endif + +#endif /* PROGRESSIVE_READ */ + +/* Added at libpng version 1.6.0 */ +#ifdef PNG_GAMMA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_colorspace_set_gamma,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_fixed_point gAMA), PNG_EMPTY); + /* Set the colorspace gamma with a value provided by the application or by + * the gAMA chunk on read. The value will override anything set by an ICC + * profile. + */ + +PNG_INTERNAL_FUNCTION(void,png_colorspace_sync_info,(png_const_structrp png_ptr, + png_inforp info_ptr), PNG_EMPTY); + /* Synchronize the info 'valid' flags with the colorspace */ + +PNG_INTERNAL_FUNCTION(void,png_colorspace_sync,(png_const_structrp png_ptr, + png_inforp info_ptr), PNG_EMPTY); + /* Copy the png_struct colorspace to the info_struct and call the above to + * synchronize the flags. Checks for NULL info_ptr and does nothing. + */ +#endif + +/* Added at libpng version 1.4.0 */ +#ifdef PNG_COLORSPACE_SUPPORTED +/* These internal functions are for maintaining the colorspace structure within + * a png_info or png_struct (or, indeed, both). + */ +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_chromaticities, + (png_const_structrp png_ptr, png_colorspacerp colorspace, const png_xy *xy, + int preferred), PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_endpoints, + (png_const_structrp png_ptr, png_colorspacerp colorspace, const png_XYZ *XYZ, + int preferred), PNG_EMPTY); + +#ifdef PNG_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_sRGB,(png_const_structrp png_ptr, + png_colorspacerp colorspace, int intent), PNG_EMPTY); + /* This does set the colorspace gAMA and cHRM values too, but doesn't set the + * flags to write them, if it returns false there was a problem and an error + * message has already been output (but the colorspace may still need to be + * synced to record the invalid flag). + */ +#endif /* sRGB */ + +#ifdef PNG_iCCP_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_ICC,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length, png_const_bytep profile, int color_type), + PNG_EMPTY); + /* The 'name' is used for information only */ + +/* Routines for checking parts of an ICC profile. */ +#ifdef PNG_READ_iCCP_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_icc_check_length,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length), PNG_EMPTY); +#endif /* READ_iCCP */ +PNG_INTERNAL_FUNCTION(int,png_icc_check_header,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length, + png_const_bytep profile /* first 132 bytes only */, int color_type), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(int,png_icc_check_tag_table,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length, + png_const_bytep profile /* header plus whole tag table */), PNG_EMPTY); +#ifdef PNG_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_icc_set_sRGB,( + png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_bytep profile, uLong adler), PNG_EMPTY); + /* 'adler' is the Adler32 checksum of the uncompressed profile data. It may + * be zero to indicate that it is not available. It is used, if provided, + * as a fast check on the profile when checking to see if it is sRGB. + */ +#endif +#endif /* iCCP */ + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_colorspace_set_rgb_coefficients, + (png_structrp png_ptr), PNG_EMPTY); + /* Set the rgb_to_gray coefficients from the colorspace Y values */ +#endif /* READ_RGB_TO_GRAY */ +#endif /* COLORSPACE */ + +/* Added at libpng version 1.4.0 */ +PNG_INTERNAL_FUNCTION(void,png_check_IHDR,(png_const_structrp png_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_type, int compression_type, + int filter_type),PNG_EMPTY); + +/* Added at libpng version 1.5.10 */ +#if defined(PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED) || \ + defined(PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_check_palette_indexes, + (png_structrp png_ptr, png_row_infop row_info),PNG_EMPTY); +#endif + +#if defined(PNG_FLOATING_POINT_SUPPORTED) && defined(PNG_ERROR_TEXT_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_fixed_error,(png_const_structrp png_ptr, + png_const_charp name),PNG_NORETURN); +#endif + +/* Puts 'string' into 'buffer' at buffer[pos], taking care never to overwrite + * the end. Always leaves the buffer nul terminated. Never errors out (and + * there is no error code.) + */ +PNG_INTERNAL_FUNCTION(size_t,png_safecat,(png_charp buffer, size_t bufsize, + size_t pos, png_const_charp string),PNG_EMPTY); + +/* Various internal functions to handle formatted warning messages, currently + * only implemented for warnings. + */ +#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED) +/* Utility to dump an unsigned value into a buffer, given a start pointer and + * and end pointer (which should point just *beyond* the end of the buffer!) + * Returns the pointer to the start of the formatted string. This utility only + * does unsigned values. + */ +PNG_INTERNAL_FUNCTION(png_charp,png_format_number,(png_const_charp start, + png_charp end, int format, png_alloc_size_t number),PNG_EMPTY); + +/* Convenience macro that takes an array: */ +#define PNG_FORMAT_NUMBER(buffer,format,number) \ + png_format_number(buffer, buffer + (sizeof buffer), format, number) + +/* Suggested size for a number buffer (enough for 64 bits and a sign!) */ +#define PNG_NUMBER_BUFFER_SIZE 24 + +/* These are the integer formats currently supported, the name is formed from + * the standard printf(3) format string. + */ +#define PNG_NUMBER_FORMAT_u 1 /* chose unsigned API! */ +#define PNG_NUMBER_FORMAT_02u 2 +#define PNG_NUMBER_FORMAT_d 1 /* chose signed API! */ +#define PNG_NUMBER_FORMAT_02d 2 +#define PNG_NUMBER_FORMAT_x 3 +#define PNG_NUMBER_FORMAT_02x 4 +#define PNG_NUMBER_FORMAT_fixed 5 /* choose the signed API */ +#endif + +#ifdef PNG_WARNINGS_SUPPORTED +/* New defines and members adding in libpng-1.5.4 */ +# define PNG_WARNING_PARAMETER_SIZE 32 +# define PNG_WARNING_PARAMETER_COUNT 8 /* Maximum 9; see pngerror.c */ + +/* An l-value of this type has to be passed to the APIs below to cache the + * values of the parameters to a formatted warning message. + */ +typedef char png_warning_parameters[PNG_WARNING_PARAMETER_COUNT][ + PNG_WARNING_PARAMETER_SIZE]; + +PNG_INTERNAL_FUNCTION(void,png_warning_parameter,(png_warning_parameters p, + int number, png_const_charp string),PNG_EMPTY); + /* Parameters are limited in size to PNG_WARNING_PARAMETER_SIZE characters, + * including the trailing '\0'. + */ +PNG_INTERNAL_FUNCTION(void,png_warning_parameter_unsigned, + (png_warning_parameters p, int number, int format, png_alloc_size_t value), + PNG_EMPTY); + /* Use png_alloc_size_t because it is an unsigned type as big as any we + * need to output. Use the following for a signed value. + */ +PNG_INTERNAL_FUNCTION(void,png_warning_parameter_signed, + (png_warning_parameters p, int number, int format, png_int_32 value), + PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_formatted_warning,(png_const_structrp png_ptr, + png_warning_parameters p, png_const_charp message),PNG_EMPTY); + /* 'message' follows the X/Open approach of using @1, @2 to insert + * parameters previously supplied using the above functions. Errors in + * specifying the parameters will simply result in garbage substitutions. + */ +#endif + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +/* Application errors (new in 1.6); use these functions (declared below) for + * errors in the parameters or order of API function calls on read. The + * 'warning' should be used for an error that can be handled completely; the + * 'error' for one which can be handled safely but which may lose application + * information or settings. + * + * By default these both result in a png_error call prior to release, while in a + * released version the 'warning' is just a warning. However if the application + * explicitly disables benign errors (explicitly permitting the code to lose + * information) they both turn into warnings. + * + * If benign errors aren't supported they end up as the corresponding base call + * (png_warning or png_error.) + */ +PNG_INTERNAL_FUNCTION(void,png_app_warning,(png_const_structrp png_ptr, + png_const_charp message),PNG_EMPTY); + /* The application provided invalid parameters to an API function or called + * an API function at the wrong time, libpng can completely recover. + */ + +PNG_INTERNAL_FUNCTION(void,png_app_error,(png_const_structrp png_ptr, + png_const_charp message),PNG_EMPTY); + /* As above but libpng will ignore the call, or attempt some other partial + * recovery from the error. + */ +#else +# define png_app_warning(pp,s) png_warning(pp,s) +# define png_app_error(pp,s) png_error(pp,s) +#endif + +PNG_INTERNAL_FUNCTION(void,png_chunk_report,(png_const_structrp png_ptr, + png_const_charp message, int error),PNG_EMPTY); + /* Report a recoverable issue in chunk data. On read this is used to report + * a problem found while reading a particular chunk and the + * png_chunk_benign_error or png_chunk_warning function is used as + * appropriate. On write this is used to report an error that comes from + * data set via an application call to a png_set_ API and png_app_error or + * png_app_warning is used as appropriate. + * + * The 'error' parameter must have one of the following values: + */ +#define PNG_CHUNK_WARNING 0 /* never an error */ +#define PNG_CHUNK_WRITE_ERROR 1 /* an error only on write */ +#define PNG_CHUNK_ERROR 2 /* always an error */ + +/* ASCII to FP interfaces, currently only implemented if sCAL + * support is required. + */ +#if defined(PNG_sCAL_SUPPORTED) +/* MAX_DIGITS is actually the maximum number of characters in an sCAL + * width or height, derived from the precision (number of significant + * digits - a build time settable option) and assumptions about the + * maximum ridiculous exponent. + */ +#define PNG_sCAL_MAX_DIGITS (PNG_sCAL_PRECISION+1/*.*/+1/*E*/+10/*exponent*/) + +#ifdef PNG_FLOATING_POINT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_ascii_from_fp,(png_const_structrp png_ptr, + png_charp ascii, size_t size, double fp, unsigned int precision), + PNG_EMPTY); +#endif /* FLOATING_POINT */ + +#ifdef PNG_FIXED_POINT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_ascii_from_fixed,(png_const_structrp png_ptr, + png_charp ascii, size_t size, png_fixed_point fp),PNG_EMPTY); +#endif /* FIXED_POINT */ +#endif /* sCAL */ + +#if defined(PNG_sCAL_SUPPORTED) || defined(PNG_pCAL_SUPPORTED) +/* An internal API to validate the format of a floating point number. + * The result is the index of the next character. If the number is + * not valid it will be the index of a character in the supposed number. + * + * The format of a number is defined in the PNG extensions specification + * and this API is strictly conformant to that spec, not anyone elses! + * + * The format as a regular expression is: + * + * [+-]?[0-9]+.?([Ee][+-]?[0-9]+)? + * + * or: + * + * [+-]?.[0-9]+(.[0-9]+)?([Ee][+-]?[0-9]+)? + * + * The complexity is that either integer or fraction must be present and the + * fraction is permitted to have no digits only if the integer is present. + * + * NOTE: The dangling E problem. + * There is a PNG valid floating point number in the following: + * + * PNG floating point numbers are not greedy. + * + * Working this out requires *TWO* character lookahead (because of the + * sign), the parser does not do this - it will fail at the 'r' - this + * doesn't matter for PNG sCAL chunk values, but it requires more care + * if the value were ever to be embedded in something more complex. Use + * ANSI-C strtod if you need the lookahead. + */ +/* State table for the parser. */ +#define PNG_FP_INTEGER 0 /* before or in integer */ +#define PNG_FP_FRACTION 1 /* before or in fraction */ +#define PNG_FP_EXPONENT 2 /* before or in exponent */ +#define PNG_FP_STATE 3 /* mask for the above */ +#define PNG_FP_SAW_SIGN 4 /* Saw +/- in current state */ +#define PNG_FP_SAW_DIGIT 8 /* Saw a digit in current state */ +#define PNG_FP_SAW_DOT 16 /* Saw a dot in current state */ +#define PNG_FP_SAW_E 32 /* Saw an E (or e) in current state */ +#define PNG_FP_SAW_ANY 60 /* Saw any of the above 4 */ + +/* These three values don't affect the parser. They are set but not used. + */ +#define PNG_FP_WAS_VALID 64 /* Preceding substring is a valid fp number */ +#define PNG_FP_NEGATIVE 128 /* A negative number, including "-0" */ +#define PNG_FP_NONZERO 256 /* A non-zero value */ +#define PNG_FP_STICKY 448 /* The above three flags */ + +/* This is available for the caller to store in 'state' if required. Do not + * call the parser after setting it (the parser sometimes clears it.) + */ +#define PNG_FP_INVALID 512 /* Available for callers as a distinct value */ + +/* Result codes for the parser (boolean - true meants ok, false means + * not ok yet.) + */ +#define PNG_FP_MAYBE 0 /* The number may be valid in the future */ +#define PNG_FP_OK 1 /* The number is valid */ + +/* Tests on the sticky non-zero and negative flags. To pass these checks + * the state must also indicate that the whole number is valid - this is + * achieved by testing PNG_FP_SAW_DIGIT (see the implementation for why this + * is equivalent to PNG_FP_OK above.) + */ +#define PNG_FP_NZ_MASK (PNG_FP_SAW_DIGIT | PNG_FP_NEGATIVE | PNG_FP_NONZERO) + /* NZ_MASK: the string is valid and a non-zero negative value */ +#define PNG_FP_Z_MASK (PNG_FP_SAW_DIGIT | PNG_FP_NONZERO) + /* Z MASK: the string is valid and a non-zero value. */ + /* PNG_FP_SAW_DIGIT: the string is valid. */ +#define PNG_FP_IS_ZERO(state) (((state) & PNG_FP_Z_MASK) == PNG_FP_SAW_DIGIT) +#define PNG_FP_IS_POSITIVE(state) (((state) & PNG_FP_NZ_MASK) == PNG_FP_Z_MASK) +#define PNG_FP_IS_NEGATIVE(state) (((state) & PNG_FP_NZ_MASK) == PNG_FP_NZ_MASK) + +/* The actual parser. This can be called repeatedly. It updates + * the index into the string and the state variable (which must + * be initialized to 0). It returns a result code, as above. There + * is no point calling the parser any more if it fails to advance to + * the end of the string - it is stuck on an invalid character (or + * terminated by '\0'). + * + * Note that the pointer will consume an E or even an E+ and then leave + * a 'maybe' state even though a preceding integer.fraction is valid. + * The PNG_FP_WAS_VALID flag indicates that a preceding substring was + * a valid number. It's possible to recover from this by calling + * the parser again (from the start, with state 0) but with a string + * that omits the last character (i.e. set the size to the index of + * the problem character.) This has not been tested within libpng. + */ +PNG_INTERNAL_FUNCTION(int,png_check_fp_number,(png_const_charp string, + size_t size, int *statep, png_size_tp whereami),PNG_EMPTY); + +/* This is the same but it checks a complete string and returns true + * only if it just contains a floating point number. As of 1.5.4 this + * function also returns the state at the end of parsing the number if + * it was valid (otherwise it returns 0.) This can be used for testing + * for negative or zero values using the sticky flag. + */ +PNG_INTERNAL_FUNCTION(int,png_check_fp_string,(png_const_charp string, + size_t size),PNG_EMPTY); +#endif /* pCAL || sCAL */ + +#if defined(PNG_GAMMA_SUPPORTED) ||\ + defined(PNG_INCH_CONVERSIONS_SUPPORTED) || defined(PNG_READ_pHYs_SUPPORTED) +/* Added at libpng version 1.5.0 */ +/* This is a utility to provide a*times/div (rounded) and indicate + * if there is an overflow. The result is a boolean - false (0) + * for overflow, true (1) if no overflow, in which case *res + * holds the result. + */ +PNG_INTERNAL_FUNCTION(int,png_muldiv,(png_fixed_point_p res, png_fixed_point a, + png_int_32 multiplied_by, png_int_32 divided_by),PNG_EMPTY); +#endif + +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_INCH_CONVERSIONS_SUPPORTED) +/* Same deal, but issue a warning on overflow and return 0. */ +PNG_INTERNAL_FUNCTION(png_fixed_point,png_muldiv_warn, + (png_const_structrp png_ptr, png_fixed_point a, png_int_32 multiplied_by, + png_int_32 divided_by),PNG_EMPTY); +#endif + +#ifdef PNG_GAMMA_SUPPORTED +/* Calculate a reciprocal - used for gamma values. This returns + * 0 if the argument is 0 in order to maintain an undefined value; + * there are no warnings. + */ +PNG_INTERNAL_FUNCTION(png_fixed_point,png_reciprocal,(png_fixed_point a), + PNG_EMPTY); + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* The same but gives a reciprocal of the product of two fixed point + * values. Accuracy is suitable for gamma calculations but this is + * not exact - use png_muldiv for that. Only required at present on read. + */ +PNG_INTERNAL_FUNCTION(png_fixed_point,png_reciprocal2,(png_fixed_point a, + png_fixed_point b),PNG_EMPTY); +#endif + +/* Return true if the gamma value is significantly different from 1.0 */ +PNG_INTERNAL_FUNCTION(int,png_gamma_significant,(png_fixed_point gamma_value), + PNG_EMPTY); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* Internal fixed point gamma correction. These APIs are called as + * required to convert single values - they don't need to be fast, + * they are not used when processing image pixel values. + * + * While the input is an 'unsigned' value it must actually be the + * correct bit value - 0..255 or 0..65535 as required. + */ +PNG_INTERNAL_FUNCTION(png_uint_16,png_gamma_correct,(png_structrp png_ptr, + unsigned int value, png_fixed_point gamma_value),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(png_uint_16,png_gamma_16bit_correct,(unsigned int value, + png_fixed_point gamma_value),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(png_byte,png_gamma_8bit_correct,(unsigned int value, + png_fixed_point gamma_value),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_destroy_gamma_table,(png_structrp png_ptr), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_build_gamma_table,(png_structrp png_ptr, + int bit_depth),PNG_EMPTY); +#endif + +/* SIMPLIFIED READ/WRITE SUPPORT */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +/* The internal structure that png_image::opaque points to. */ +typedef struct png_control +{ + png_structp png_ptr; + png_infop info_ptr; + png_voidp error_buf; /* Always a jmp_buf at present. */ + + png_const_bytep memory; /* Memory buffer. */ + size_t size; /* Size of the memory buffer. */ + + unsigned int for_write :1; /* Otherwise it is a read structure */ + unsigned int owned_file :1; /* We own the file in io_ptr */ +} png_control; + +/* Return the pointer to the jmp_buf from a png_control: necessary because C + * does not reveal the type of the elements of jmp_buf. + */ +#ifdef __cplusplus +# define png_control_jmp_buf(pc) (((jmp_buf*)((pc)->error_buf))[0]) +#else +# define png_control_jmp_buf(pc) ((pc)->error_buf) +#endif + +/* Utility to safely execute a piece of libpng code catching and logging any + * errors that might occur. Returns true on success, false on failure (either + * of the function or as a result of a png_error.) + */ +PNG_INTERNAL_CALLBACK(void,png_safe_error,(png_structp png_ptr, + png_const_charp error_message),PNG_NORETURN); + +#ifdef PNG_WARNINGS_SUPPORTED +PNG_INTERNAL_CALLBACK(void,png_safe_warning,(png_structp png_ptr, + png_const_charp warning_message),PNG_EMPTY); +#else +# define png_safe_warning 0/*dummy argument*/ +#endif + +PNG_INTERNAL_FUNCTION(int,png_safe_execute,(png_imagep image, + int (*function)(png_voidp), png_voidp arg),PNG_EMPTY); + +/* Utility to log an error; this also cleans up the png_image; the function + * always returns 0 (false). + */ +PNG_INTERNAL_FUNCTION(int,png_image_error,(png_imagep image, + png_const_charp error_message),PNG_EMPTY); + +#ifndef PNG_SIMPLIFIED_READ_SUPPORTED +/* png_image_free is used by the write code but not exported */ +PNG_INTERNAL_FUNCTION(void, png_image_free, (png_imagep image), PNG_EMPTY); +#endif /* !SIMPLIFIED_READ */ + +#endif /* SIMPLIFIED READ/WRITE */ + +/* These are initialization functions for hardware specific PNG filter + * optimizations; list these here then select the appropriate one at compile + * time using the macro PNG_FILTER_OPTIMIZATIONS. If the macro is not defined + * the generic code is used. + */ +#ifdef PNG_FILTER_OPTIMIZATIONS +PNG_INTERNAL_FUNCTION(void, PNG_FILTER_OPTIMIZATIONS, (png_structp png_ptr, + unsigned int bpp), PNG_EMPTY); + /* Just declare the optimization that will be used */ +#else + /* List *all* the possible optimizations here - this branch is required if + * the builder of libpng passes the definition of PNG_FILTER_OPTIMIZATIONS in + * CFLAGS in place of CPPFLAGS *and* uses symbol prefixing. + */ +# if PNG_ARM_NEON_OPT > 0 +PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_neon, + (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); +#endif + +#if PNG_MIPS_MSA_OPT > 0 +PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_msa, + (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); +#endif + +# if PNG_INTEL_SSE_IMPLEMENTATION > 0 +PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_sse2, + (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); +# endif +#endif + +PNG_INTERNAL_FUNCTION(png_uint_32, png_check_keyword, (png_structrp png_ptr, + png_const_charp key, png_bytep new_key), PNG_EMPTY); + +#if PNG_ARM_NEON_IMPLEMENTATION == 1 +PNG_INTERNAL_FUNCTION(void, + png_riffle_palette_neon, + (png_structrp), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(int, + png_do_expand_palette_rgba8_neon, + (png_structrp, + png_row_infop, + png_const_bytep, + const png_bytepp, + const png_bytepp), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(int, + png_do_expand_palette_rgb8_neon, + (png_structrp, + png_row_infop, + png_const_bytep, + const png_bytepp, + const png_bytepp), + PNG_EMPTY); +#endif + +/* Maintainer: Put new private prototypes here ^ */ + +#include "pngdebug.h" + +#ifdef __cplusplus +} +#endif + +#endif /* PNG_VERSION_INFO_ONLY */ +#endif /* PNGPRIV_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngread.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngread.c new file mode 100644 index 0000000..8fa7d9f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngread.c @@ -0,0 +1,4225 @@ + +/* pngread.c - read a PNG file + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file contains routines that an application calls directly to + * read a PNG file or stream. + */ + +#include "pngpriv.h" +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) && defined(PNG_STDIO_SUPPORTED) +# include +#endif + +#ifdef PNG_READ_SUPPORTED + +/* Create a PNG structure for reading, and allocate any memory needed. */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_read_struct,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn),PNG_ALLOCATED) +{ +#ifndef PNG_USER_MEM_SUPPORTED + png_structp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, NULL, NULL, NULL); +#else + return png_create_read_struct_2(user_png_ver, error_ptr, error_fn, + warn_fn, NULL, NULL, NULL); +} + +/* Alternate create PNG structure for reading, and allocate any memory + * needed. + */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_read_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr, + png_malloc_ptr malloc_fn, png_free_ptr free_fn),PNG_ALLOCATED) +{ + png_structp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, mem_ptr, malloc_fn, free_fn); +#endif /* USER_MEM */ + + if (png_ptr != NULL) + { + png_ptr->mode = PNG_IS_READ_STRUCT; + + /* Added in libpng-1.6.0; this can be used to detect a read structure if + * required (it will be zero in a write structure.) + */ +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + png_ptr->IDAT_read_size = PNG_IDAT_READ_SIZE; +# endif + +# ifdef PNG_BENIGN_READ_ERRORS_SUPPORTED + png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN; + + /* In stable builds only warn if an application error can be completely + * handled. + */ +# if PNG_RELEASE_BUILD + png_ptr->flags |= PNG_FLAG_APP_WARNINGS_WARN; +# endif +# endif + + /* TODO: delay this, it can be done in png_init_io (if the app doesn't + * do it itself) avoiding setting the default function if it is not + * required. + */ + png_set_read_fn(png_ptr, NULL, NULL); + } + + return png_ptr; +} + + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the information before the actual image data. This has been + * changed in v0.90 to allow reading a file that already has the magic + * bytes read from the stream. You can tell libpng how many bytes have + * been read from the beginning of the stream (up to the maximum of 8) + * via png_set_sig_bytes(), and we will only check the remaining bytes + * here. The application can then have access to the signature bytes we + * read if it is determined that this isn't a valid PNG file. + */ +void PNGAPI +png_read_info(png_structrp png_ptr, png_inforp info_ptr) +{ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; +#endif + + png_debug(1, "in png_read_info"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* Read and check the PNG file signature. */ + png_read_sig(png_ptr, info_ptr); + + for (;;) + { + png_uint_32 length = png_read_chunk_header(png_ptr); + png_uint_32 chunk_name = png_ptr->chunk_name; + + /* IDAT logic needs to happen here to simplify getting the two flags + * right. + */ + if (chunk_name == png_IDAT) + { + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "Missing IHDR before IDAT"); + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + (png_ptr->mode & PNG_HAVE_PLTE) == 0) + png_chunk_error(png_ptr, "Missing PLTE before IDAT"); + + else if ((png_ptr->mode & PNG_AFTER_IDAT) != 0) + png_chunk_benign_error(png_ptr, "Too many IDATs found"); + + png_ptr->mode |= PNG_HAVE_IDAT; + } + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT; + png_ptr->mode |= PNG_AFTER_IDAT; + } + + /* This should be a binary subdivision search or a hash for + * matching the chunk name rather than a linear search. + */ + if (chunk_name == png_IHDR) + png_handle_IHDR(png_ptr, info_ptr, length); + + else if (chunk_name == png_IEND) + png_handle_IEND(png_ptr, info_ptr, length); + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name)) != 0) + { + png_handle_unknown(png_ptr, info_ptr, length, keep); + + if (chunk_name == png_PLTE) + png_ptr->mode |= PNG_HAVE_PLTE; + + else if (chunk_name == png_IDAT) + { + png_ptr->idat_size = 0; /* It has been consumed */ + break; + } + } +#endif + else if (chunk_name == png_PLTE) + png_handle_PLTE(png_ptr, info_ptr, length); + + else if (chunk_name == png_IDAT) + { + png_ptr->idat_size = length; + break; + } + +#ifdef PNG_READ_bKGD_SUPPORTED + else if (chunk_name == png_bKGD) + png_handle_bKGD(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED + else if (chunk_name == png_cHRM) + png_handle_cHRM(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_eXIf_SUPPORTED + else if (chunk_name == png_eXIf) + png_handle_eXIf(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_gAMA_SUPPORTED + else if (chunk_name == png_gAMA) + png_handle_gAMA(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_hIST_SUPPORTED + else if (chunk_name == png_hIST) + png_handle_hIST(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED + else if (chunk_name == png_oFFs) + png_handle_oFFs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED + else if (chunk_name == png_pCAL) + png_handle_pCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED + else if (chunk_name == png_sCAL) + png_handle_sCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED + else if (chunk_name == png_pHYs) + png_handle_pHYs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED + else if (chunk_name == png_sBIT) + png_handle_sBIT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sRGB_SUPPORTED + else if (chunk_name == png_sRGB) + png_handle_sRGB(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iCCP_SUPPORTED + else if (chunk_name == png_iCCP) + png_handle_iCCP(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sPLT_SUPPORTED + else if (chunk_name == png_sPLT) + png_handle_sPLT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED + else if (chunk_name == png_tEXt) + png_handle_tEXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tIME_SUPPORTED + else if (chunk_name == png_tIME) + png_handle_tIME(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tRNS_SUPPORTED + else if (chunk_name == png_tRNS) + png_handle_tRNS(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED + else if (chunk_name == png_zTXt) + png_handle_zTXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iTXt_SUPPORTED + else if (chunk_name == png_iTXt) + png_handle_iTXt(png_ptr, info_ptr, length); +#endif + + else + png_handle_unknown(png_ptr, info_ptr, length, + PNG_HANDLE_CHUNK_AS_DEFAULT); + } +} +#endif /* SEQUENTIAL_READ */ + +/* Optional call to update the users info_ptr structure */ +void PNGAPI +png_read_update_info(png_structrp png_ptr, png_inforp info_ptr) +{ + png_debug(1, "in png_read_update_info"); + + if (png_ptr != NULL) + { + if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0) + { + png_read_start_row(png_ptr); + +# ifdef PNG_READ_TRANSFORMS_SUPPORTED + png_read_transform_info(png_ptr, info_ptr); +# else + PNG_UNUSED(info_ptr) +# endif + } + + /* New in 1.6.0 this avoids the bug of doing the initializations twice */ + else + png_app_error(png_ptr, + "png_read_update_info/png_start_read_image: duplicate call"); + } +} + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Initialize palette, background, etc, after transformations + * are set, but before any reading takes place. This allows + * the user to obtain a gamma-corrected palette, for example. + * If the user doesn't call this, we will do it ourselves. + */ +void PNGAPI +png_start_read_image(png_structrp png_ptr) +{ + png_debug(1, "in png_start_read_image"); + + if (png_ptr != NULL) + { + if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0) + png_read_start_row(png_ptr); + + /* New in 1.6.0 this avoids the bug of doing the initializations twice */ + else + png_app_error(png_ptr, + "png_start_read_image/png_read_update_info: duplicate call"); + } +} +#endif /* SEQUENTIAL_READ */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +#ifdef PNG_MNG_FEATURES_SUPPORTED +/* Undoes intrapixel differencing, + * NOTE: this is apparently only supported in the 'sequential' reader. + */ +static void +png_do_read_intrapixel(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_read_intrapixel"); + + if ( + (row_info->color_type & PNG_COLOR_MASK_COLOR) != 0) + { + int bytes_per_pixel; + png_uint_32 row_width = row_info->width; + + if (row_info->bit_depth == 8) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 3; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 4; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + *(rp) = (png_byte)((256 + *rp + *(rp + 1)) & 0xff); + *(rp+2) = (png_byte)((256 + *(rp + 2) + *(rp + 1)) & 0xff); + } + } + else if (row_info->bit_depth == 16) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 6; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 8; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + png_uint_32 s0 = (png_uint_32)(*(rp ) << 8) | *(rp + 1); + png_uint_32 s1 = (png_uint_32)(*(rp + 2) << 8) | *(rp + 3); + png_uint_32 s2 = (png_uint_32)(*(rp + 4) << 8) | *(rp + 5); + png_uint_32 red = (s0 + s1 + 65536) & 0xffff; + png_uint_32 blue = (s2 + s1 + 65536) & 0xffff; + *(rp ) = (png_byte)((red >> 8) & 0xff); + *(rp + 1) = (png_byte)(red & 0xff); + *(rp + 4) = (png_byte)((blue >> 8) & 0xff); + *(rp + 5) = (png_byte)(blue & 0xff); + } + } + } +} +#endif /* MNG_FEATURES */ + +void PNGAPI +png_read_row(png_structrp png_ptr, png_bytep row, png_bytep dsp_row) +{ + png_row_info row_info; + + if (png_ptr == NULL) + return; + + png_debug2(1, "in png_read_row (row %lu, pass %d)", + (unsigned long)png_ptr->row_number, png_ptr->pass); + + /* png_read_start_row sets the information (in particular iwidth) for this + * interlace pass. + */ + if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0) + png_read_start_row(png_ptr); + + /* 1.5.6: row_info moved out of png_struct to a local here. */ + row_info.width = png_ptr->iwidth; /* NOTE: width of current interlaced row */ + row_info.color_type = png_ptr->color_type; + row_info.bit_depth = png_ptr->bit_depth; + row_info.channels = png_ptr->channels; + row_info.pixel_depth = png_ptr->pixel_depth; + row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width); + +#ifdef PNG_WARNINGS_SUPPORTED + if (png_ptr->row_number == 0 && png_ptr->pass == 0) + { + /* Check for transforms that have been set but were defined out */ +#if defined(PNG_WRITE_INVERT_SUPPORTED) && !defined(PNG_READ_INVERT_SUPPORTED) + if ((png_ptr->transformations & PNG_INVERT_MONO) != 0) + png_warning(png_ptr, "PNG_READ_INVERT_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_FILLER_SUPPORTED) && !defined(PNG_READ_FILLER_SUPPORTED) + if ((png_ptr->transformations & PNG_FILLER) != 0) + png_warning(png_ptr, "PNG_READ_FILLER_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_PACKSWAP_SUPPORTED) && \ + !defined(PNG_READ_PACKSWAP_SUPPORTED) + if ((png_ptr->transformations & PNG_PACKSWAP) != 0) + png_warning(png_ptr, "PNG_READ_PACKSWAP_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_PACK_SUPPORTED) && !defined(PNG_READ_PACK_SUPPORTED) + if ((png_ptr->transformations & PNG_PACK) != 0) + png_warning(png_ptr, "PNG_READ_PACK_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_SHIFT_SUPPORTED) && !defined(PNG_READ_SHIFT_SUPPORTED) + if ((png_ptr->transformations & PNG_SHIFT) != 0) + png_warning(png_ptr, "PNG_READ_SHIFT_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_BGR_SUPPORTED) && !defined(PNG_READ_BGR_SUPPORTED) + if ((png_ptr->transformations & PNG_BGR) != 0) + png_warning(png_ptr, "PNG_READ_BGR_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_SWAP_SUPPORTED) && !defined(PNG_READ_SWAP_SUPPORTED) + if ((png_ptr->transformations & PNG_SWAP_BYTES) != 0) + png_warning(png_ptr, "PNG_READ_SWAP_SUPPORTED is not defined"); +#endif + } +#endif /* WARNINGS */ + +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* If interlaced and we do not need a new row, combine row and return. + * Notice that the pixels we have from previous rows have been transformed + * already; we can only combine like with like (transformed or + * untransformed) and, because of the libpng API for interlaced images, this + * means we must transform before de-interlacing. + */ + if (png_ptr->interlaced != 0 && + (png_ptr->transformations & PNG_INTERLACE) != 0) + { + switch (png_ptr->pass) + { + case 0: + if (png_ptr->row_number & 0x07) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + png_read_finish_row(png_ptr); + return; + } + break; + + case 1: + if ((png_ptr->row_number & 0x07) || png_ptr->width < 5) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 2: + if ((png_ptr->row_number & 0x07) != 4) + { + if (dsp_row != NULL && (png_ptr->row_number & 4)) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 3: + if ((png_ptr->row_number & 3) || png_ptr->width < 3) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 4: + if ((png_ptr->row_number & 3) != 2) + { + if (dsp_row != NULL && (png_ptr->row_number & 2)) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 5: + if ((png_ptr->row_number & 1) || png_ptr->width < 2) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + default: + case 6: + if ((png_ptr->row_number & 1) == 0) + { + png_read_finish_row(png_ptr); + return; + } + break; + } + } +#endif + + if ((png_ptr->mode & PNG_HAVE_IDAT) == 0) + png_error(png_ptr, "Invalid attempt to read row data"); + + /* Fill the row with IDAT data: */ + png_ptr->row_buf[0]=255; /* to force error if no data was found */ + png_read_IDAT_data(png_ptr, png_ptr->row_buf, row_info.rowbytes + 1); + + if (png_ptr->row_buf[0] > PNG_FILTER_VALUE_NONE) + { + if (png_ptr->row_buf[0] < PNG_FILTER_VALUE_LAST) + png_read_filter_row(png_ptr, &row_info, png_ptr->row_buf + 1, + png_ptr->prev_row + 1, png_ptr->row_buf[0]); + else + png_error(png_ptr, "bad adaptive filter value"); + } + + /* libpng 1.5.6: the following line was copying png_ptr->rowbytes before + * 1.5.6, while the buffer really is this big in current versions of libpng + * it may not be in the future, so this was changed just to copy the + * interlaced count: + */ + memcpy(png_ptr->prev_row, png_ptr->row_buf, row_info.rowbytes + 1); + +#ifdef PNG_MNG_FEATURES_SUPPORTED + if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) != 0 && + (png_ptr->filter_type == PNG_INTRAPIXEL_DIFFERENCING)) + { + /* Intrapixel differencing */ + png_do_read_intrapixel(&row_info, png_ptr->row_buf + 1); + } +#endif + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED + if (png_ptr->transformations) + png_do_read_transformations(png_ptr, &row_info); +#endif + + /* The transformed pixel depth should match the depth now in row_info. */ + if (png_ptr->transformed_pixel_depth == 0) + { + png_ptr->transformed_pixel_depth = row_info.pixel_depth; + if (row_info.pixel_depth > png_ptr->maximum_pixel_depth) + png_error(png_ptr, "sequential row overflow"); + } + + else if (png_ptr->transformed_pixel_depth != row_info.pixel_depth) + png_error(png_ptr, "internal sequential row size calculation error"); + +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Expand interlaced rows to full size */ + if (png_ptr->interlaced != 0 && + (png_ptr->transformations & PNG_INTERLACE) != 0) + { + if (png_ptr->pass < 6) + png_do_read_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass, + png_ptr->transformations); + + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + if (row != NULL) + png_combine_row(png_ptr, row, 0/*row*/); + } + + else +#endif + { + if (row != NULL) + png_combine_row(png_ptr, row, -1/*ignored*/); + + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, -1/*ignored*/); + } + png_read_finish_row(png_ptr); + + if (png_ptr->read_row_fn != NULL) + (*(png_ptr->read_row_fn))(png_ptr, png_ptr->row_number, png_ptr->pass); + +} +#endif /* SEQUENTIAL_READ */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read one or more rows of image data. If the image is interlaced, + * and png_set_interlace_handling() has been called, the rows need to + * contain the contents of the rows from the previous pass. If the + * image has alpha or transparency, and png_handle_alpha()[*] has been + * called, the rows contents must be initialized to the contents of the + * screen. + * + * "row" holds the actual image, and pixels are placed in it + * as they arrive. If the image is displayed after each pass, it will + * appear to "sparkle" in. "display_row" can be used to display a + * "chunky" progressive image, with finer detail added as it becomes + * available. If you do not want this "chunky" display, you may pass + * NULL for display_row. If you do not want the sparkle display, and + * you have not called png_handle_alpha(), you may pass NULL for rows. + * If you have called png_handle_alpha(), and the image has either an + * alpha channel or a transparency chunk, you must provide a buffer for + * rows. In this case, you do not have to provide a display_row buffer + * also, but you may. If the image is not interlaced, or if you have + * not called png_set_interlace_handling(), the display_row buffer will + * be ignored, so pass NULL to it. + * + * [*] png_handle_alpha() does not exist yet, as of this version of libpng + */ + +void PNGAPI +png_read_rows(png_structrp png_ptr, png_bytepp row, + png_bytepp display_row, png_uint_32 num_rows) +{ + png_uint_32 i; + png_bytepp rp; + png_bytepp dp; + + png_debug(1, "in png_read_rows"); + + if (png_ptr == NULL) + return; + + rp = row; + dp = display_row; + if (rp != NULL && dp != NULL) + for (i = 0; i < num_rows; i++) + { + png_bytep rptr = *rp++; + png_bytep dptr = *dp++; + + png_read_row(png_ptr, rptr, dptr); + } + + else if (rp != NULL) + for (i = 0; i < num_rows; i++) + { + png_bytep rptr = *rp; + png_read_row(png_ptr, rptr, NULL); + rp++; + } + + else if (dp != NULL) + for (i = 0; i < num_rows; i++) + { + png_bytep dptr = *dp; + png_read_row(png_ptr, NULL, dptr); + dp++; + } +} +#endif /* SEQUENTIAL_READ */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the entire image. If the image has an alpha channel or a tRNS + * chunk, and you have called png_handle_alpha()[*], you will need to + * initialize the image to the current image that PNG will be overlaying. + * We set the num_rows again here, in case it was incorrectly set in + * png_read_start_row() by a call to png_read_update_info() or + * png_start_read_image() if png_set_interlace_handling() wasn't called + * prior to either of these functions like it should have been. You can + * only call this function once. If you desire to have an image for + * each pass of a interlaced image, use png_read_rows() instead. + * + * [*] png_handle_alpha() does not exist yet, as of this version of libpng + */ +void PNGAPI +png_read_image(png_structrp png_ptr, png_bytepp image) +{ + png_uint_32 i, image_height; + int pass, j; + png_bytepp rp; + + png_debug(1, "in png_read_image"); + + if (png_ptr == NULL) + return; + +#ifdef PNG_READ_INTERLACING_SUPPORTED + if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0) + { + pass = png_set_interlace_handling(png_ptr); + /* And make sure transforms are initialized. */ + png_start_read_image(png_ptr); + } + else + { + if (png_ptr->interlaced != 0 && + (png_ptr->transformations & PNG_INTERLACE) == 0) + { + /* Caller called png_start_read_image or png_read_update_info without + * first turning on the PNG_INTERLACE transform. We can fix this here, + * but the caller should do it! + */ + png_warning(png_ptr, "Interlace handling should be turned on when " + "using png_read_image"); + /* Make sure this is set correctly */ + png_ptr->num_rows = png_ptr->height; + } + + /* Obtain the pass number, which also turns on the PNG_INTERLACE flag in + * the above error case. + */ + pass = png_set_interlace_handling(png_ptr); + } +#else + if (png_ptr->interlaced) + png_error(png_ptr, + "Cannot read interlaced image -- interlace handler disabled"); + + pass = 1; +#endif + + image_height=png_ptr->height; + + for (j = 0; j < pass; j++) + { + rp = image; + for (i = 0; i < image_height; i++) + { + png_read_row(png_ptr, *rp, NULL); + rp++; + } + } +} +#endif /* SEQUENTIAL_READ */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the end of the PNG file. Will not read past the end of the + * file, will verify the end is accurate, and will read any comments + * or time information at the end of the file, if info is not NULL. + */ +void PNGAPI +png_read_end(png_structrp png_ptr, png_inforp info_ptr) +{ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; +#endif + + png_debug(1, "in png_read_end"); + + if (png_ptr == NULL) + return; + + /* If png_read_end is called in the middle of reading the rows there may + * still be pending IDAT data and an owned zstream. Deal with this here. + */ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + if (png_chunk_unknown_handling(png_ptr, png_IDAT) == 0) +#endif + png_read_finish_IDAT(png_ptr); + +#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Report invalid palette index; added at libng-1.5.10 */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + png_ptr->num_palette_max > png_ptr->num_palette) + png_benign_error(png_ptr, "Read palette index exceeding num_palette"); +#endif + + do + { + png_uint_32 length = png_read_chunk_header(png_ptr); + png_uint_32 chunk_name = png_ptr->chunk_name; + + if (chunk_name != png_IDAT) + png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT; + + if (chunk_name == png_IEND) + png_handle_IEND(png_ptr, info_ptr, length); + + else if (chunk_name == png_IHDR) + png_handle_IHDR(png_ptr, info_ptr, length); + + else if (info_ptr == NULL) + png_crc_finish(png_ptr, length); + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name)) != 0) + { + if (chunk_name == png_IDAT) + { + if ((length > 0 && !(png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED)) + || (png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT) != 0) + png_benign_error(png_ptr, ".Too many IDATs found"); + } + png_handle_unknown(png_ptr, info_ptr, length, keep); + if (chunk_name == png_PLTE) + png_ptr->mode |= PNG_HAVE_PLTE; + } +#endif + + else if (chunk_name == png_IDAT) + { + /* Zero length IDATs are legal after the last IDAT has been + * read, but not after other chunks have been read. 1.6 does not + * always read all the deflate data; specifically it cannot be relied + * upon to read the Adler32 at the end. If it doesn't ignore IDAT + * chunks which are longer than zero as well: + */ + if ((length > 0 && !(png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED)) + || (png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT) != 0) + png_benign_error(png_ptr, "..Too many IDATs found"); + + png_crc_finish(png_ptr, length); + } + else if (chunk_name == png_PLTE) + png_handle_PLTE(png_ptr, info_ptr, length); + +#ifdef PNG_READ_bKGD_SUPPORTED + else if (chunk_name == png_bKGD) + png_handle_bKGD(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED + else if (chunk_name == png_cHRM) + png_handle_cHRM(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_eXIf_SUPPORTED + else if (chunk_name == png_eXIf) + png_handle_eXIf(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_gAMA_SUPPORTED + else if (chunk_name == png_gAMA) + png_handle_gAMA(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_hIST_SUPPORTED + else if (chunk_name == png_hIST) + png_handle_hIST(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED + else if (chunk_name == png_oFFs) + png_handle_oFFs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED + else if (chunk_name == png_pCAL) + png_handle_pCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED + else if (chunk_name == png_sCAL) + png_handle_sCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED + else if (chunk_name == png_pHYs) + png_handle_pHYs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED + else if (chunk_name == png_sBIT) + png_handle_sBIT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sRGB_SUPPORTED + else if (chunk_name == png_sRGB) + png_handle_sRGB(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iCCP_SUPPORTED + else if (chunk_name == png_iCCP) + png_handle_iCCP(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sPLT_SUPPORTED + else if (chunk_name == png_sPLT) + png_handle_sPLT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED + else if (chunk_name == png_tEXt) + png_handle_tEXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tIME_SUPPORTED + else if (chunk_name == png_tIME) + png_handle_tIME(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tRNS_SUPPORTED + else if (chunk_name == png_tRNS) + png_handle_tRNS(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED + else if (chunk_name == png_zTXt) + png_handle_zTXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iTXt_SUPPORTED + else if (chunk_name == png_iTXt) + png_handle_iTXt(png_ptr, info_ptr, length); +#endif + + else + png_handle_unknown(png_ptr, info_ptr, length, + PNG_HANDLE_CHUNK_AS_DEFAULT); + } while ((png_ptr->mode & PNG_HAVE_IEND) == 0); +} +#endif /* SEQUENTIAL_READ */ + +/* Free all memory used in the read struct */ +static void +png_read_destroy(png_structrp png_ptr) +{ + png_debug(1, "in png_read_destroy"); + +#ifdef PNG_READ_GAMMA_SUPPORTED + png_destroy_gamma_table(png_ptr); +#endif + + png_free(png_ptr, png_ptr->big_row_buf); + png_ptr->big_row_buf = NULL; + png_free(png_ptr, png_ptr->big_prev_row); + png_ptr->big_prev_row = NULL; + png_free(png_ptr, png_ptr->read_buffer); + png_ptr->read_buffer = NULL; + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + png_free(png_ptr, png_ptr->palette_lookup); + png_ptr->palette_lookup = NULL; + png_free(png_ptr, png_ptr->quantize_index); + png_ptr->quantize_index = NULL; +#endif + + if ((png_ptr->free_me & PNG_FREE_PLTE) != 0) + { + png_zfree(png_ptr, png_ptr->palette); + png_ptr->palette = NULL; + } + png_ptr->free_me &= ~PNG_FREE_PLTE; + +#if defined(PNG_tRNS_SUPPORTED) || \ + defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) + if ((png_ptr->free_me & PNG_FREE_TRNS) != 0) + { + png_free(png_ptr, png_ptr->trans_alpha); + png_ptr->trans_alpha = NULL; + } + png_ptr->free_me &= ~PNG_FREE_TRNS; +#endif + + inflateEnd(&png_ptr->zstream); + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED + png_free(png_ptr, png_ptr->save_buffer); + png_ptr->save_buffer = NULL; +#endif + +#if defined(PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED) && \ + defined(PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) + png_free(png_ptr, png_ptr->unknown_chunk.data); + png_ptr->unknown_chunk.data = NULL; +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + png_free(png_ptr, png_ptr->chunk_list); + png_ptr->chunk_list = NULL; +#endif + +#if defined(PNG_READ_EXPAND_SUPPORTED) && \ + defined(PNG_ARM_NEON_IMPLEMENTATION) + png_free(png_ptr, png_ptr->riffled_palette); + png_ptr->riffled_palette = NULL; +#endif + + /* NOTE: the 'setjmp' buffer may still be allocated and the memory and error + * callbacks are still set at this point. They are required to complete the + * destruction of the png_struct itself. + */ +} + +/* Free all memory used by the read */ +void PNGAPI +png_destroy_read_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, + png_infopp end_info_ptr_ptr) +{ + png_structrp png_ptr = NULL; + + png_debug(1, "in png_destroy_read_struct"); + + if (png_ptr_ptr != NULL) + png_ptr = *png_ptr_ptr; + + if (png_ptr == NULL) + return; + + /* libpng 1.6.0: use the API to destroy info structs to ensure consistent + * behavior. Prior to 1.6.0 libpng did extra 'info' destruction in this API. + * The extra was, apparently, unnecessary yet this hides memory leak bugs. + */ + png_destroy_info_struct(png_ptr, end_info_ptr_ptr); + png_destroy_info_struct(png_ptr, info_ptr_ptr); + + *png_ptr_ptr = NULL; + png_read_destroy(png_ptr); + png_destroy_png_struct(png_ptr); +} + +void PNGAPI +png_set_read_status_fn(png_structrp png_ptr, png_read_status_ptr read_row_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->read_row_fn = read_row_fn; +} + + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +#ifdef PNG_INFO_IMAGE_SUPPORTED +void PNGAPI +png_read_png(png_structrp png_ptr, png_inforp info_ptr, + int transforms, voidp params) +{ + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* png_read_info() gives us all of the information from the + * PNG file before the first IDAT (image data chunk). + */ + png_read_info(png_ptr, info_ptr); + if (info_ptr->height > PNG_UINT_32_MAX/(sizeof (png_bytep))) + png_error(png_ptr, "Image is too high to process with png_read_png()"); + + /* -------------- image transformations start here ------------------- */ + /* libpng 1.6.10: add code to cause a png_app_error if a selected TRANSFORM + * is not implemented. This will only happen in de-configured (non-default) + * libpng builds. The results can be unexpected - png_read_png may return + * short or mal-formed rows because the transform is skipped. + */ + + /* Tell libpng to strip 16-bit/color files down to 8 bits per color. + */ + if ((transforms & PNG_TRANSFORM_SCALE_16) != 0) + /* Added at libpng-1.5.4. "strip_16" produces the same result that it + * did in earlier versions, while "scale_16" is now more accurate. + */ +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_set_scale_16(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SCALE_16 not supported"); +#endif + + /* If both SCALE and STRIP are required pngrtran will effectively cancel the + * latter by doing SCALE first. This is ok and allows apps not to check for + * which is supported to get the right answer. + */ + if ((transforms & PNG_TRANSFORM_STRIP_16) != 0) +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + png_set_strip_16(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_STRIP_16 not supported"); +#endif + + /* Strip alpha bytes from the input data without combining with + * the background (not recommended). + */ + if ((transforms & PNG_TRANSFORM_STRIP_ALPHA) != 0) +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + png_set_strip_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_STRIP_ALPHA not supported"); +#endif + + /* Extract multiple pixels with bit depths of 1, 2, or 4 from a single + * byte into separate bytes (useful for paletted and grayscale images). + */ + if ((transforms & PNG_TRANSFORM_PACKING) != 0) +#ifdef PNG_READ_PACK_SUPPORTED + png_set_packing(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKING not supported"); +#endif + + /* Change the order of packed pixels to least significant bit first + * (not useful if you are using png_set_packing). + */ + if ((transforms & PNG_TRANSFORM_PACKSWAP) != 0) +#ifdef PNG_READ_PACKSWAP_SUPPORTED + png_set_packswap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKSWAP not supported"); +#endif + + /* Expand paletted colors into true RGB triplets + * Expand grayscale images to full 8 bits from 1, 2, or 4 bits/pixel + * Expand paletted or RGB images with transparency to full alpha + * channels so the data will be available as RGBA quartets. + */ + if ((transforms & PNG_TRANSFORM_EXPAND) != 0) +#ifdef PNG_READ_EXPAND_SUPPORTED + png_set_expand(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_EXPAND not supported"); +#endif + + /* We don't handle background color or gamma transformation or quantizing. + */ + + /* Invert monochrome files to have 0 as white and 1 as black + */ + if ((transforms & PNG_TRANSFORM_INVERT_MONO) != 0) +#ifdef PNG_READ_INVERT_SUPPORTED + png_set_invert_mono(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_MONO not supported"); +#endif + + /* If you want to shift the pixel values from the range [0,255] or + * [0,65535] to the original [0,7] or [0,31], or whatever range the + * colors were originally in: + */ + if ((transforms & PNG_TRANSFORM_SHIFT) != 0) +#ifdef PNG_READ_SHIFT_SUPPORTED + if ((info_ptr->valid & PNG_INFO_sBIT) != 0) + png_set_shift(png_ptr, &info_ptr->sig_bit); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SHIFT not supported"); +#endif + + /* Flip the RGB pixels to BGR (or RGBA to BGRA) */ + if ((transforms & PNG_TRANSFORM_BGR) != 0) +#ifdef PNG_READ_BGR_SUPPORTED + png_set_bgr(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_BGR not supported"); +#endif + + /* Swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */ + if ((transforms & PNG_TRANSFORM_SWAP_ALPHA) != 0) +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED + png_set_swap_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ALPHA not supported"); +#endif + + /* Swap bytes of 16-bit files to least significant byte first */ + if ((transforms & PNG_TRANSFORM_SWAP_ENDIAN) != 0) +#ifdef PNG_READ_SWAP_SUPPORTED + png_set_swap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ENDIAN not supported"); +#endif + +/* Added at libpng-1.2.41 */ + /* Invert the alpha channel from opacity to transparency */ + if ((transforms & PNG_TRANSFORM_INVERT_ALPHA) != 0) +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + png_set_invert_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_ALPHA not supported"); +#endif + +/* Added at libpng-1.2.41 */ + /* Expand grayscale image to RGB */ + if ((transforms & PNG_TRANSFORM_GRAY_TO_RGB) != 0) +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + png_set_gray_to_rgb(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_GRAY_TO_RGB not supported"); +#endif + +/* Added at libpng-1.5.4 */ + if ((transforms & PNG_TRANSFORM_EXPAND_16) != 0) +#ifdef PNG_READ_EXPAND_16_SUPPORTED + png_set_expand_16(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_EXPAND_16 not supported"); +#endif + + /* We don't handle adding filler bytes */ + + /* We use png_read_image and rely on that for interlace handling, but we also + * call png_read_update_info therefore must turn on interlace handling now: + */ + (void)png_set_interlace_handling(png_ptr); + + /* Optional call to gamma correct and add the background to the palette + * and update info structure. REQUIRED if you are expecting libpng to + * update the palette for you (i.e., you selected such a transform above). + */ + png_read_update_info(png_ptr, info_ptr); + + /* -------------- image transformations end here ------------------- */ + + png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0); + if (info_ptr->row_pointers == NULL) + { + png_uint_32 iptr; + + info_ptr->row_pointers = png_voidcast(png_bytepp, png_malloc(png_ptr, + info_ptr->height * (sizeof (png_bytep)))); + + for (iptr=0; iptrheight; iptr++) + info_ptr->row_pointers[iptr] = NULL; + + info_ptr->free_me |= PNG_FREE_ROWS; + + for (iptr = 0; iptr < info_ptr->height; iptr++) + info_ptr->row_pointers[iptr] = png_voidcast(png_bytep, + png_malloc(png_ptr, info_ptr->rowbytes)); + } + + png_read_image(png_ptr, info_ptr->row_pointers); + info_ptr->valid |= PNG_INFO_IDAT; + + /* Read rest of file, and get additional chunks in info_ptr - REQUIRED */ + png_read_end(png_ptr, info_ptr); + + PNG_UNUSED(params) +} +#endif /* INFO_IMAGE */ +#endif /* SEQUENTIAL_READ */ + +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +/* SIMPLIFIED READ + * + * This code currently relies on the sequential reader, though it could easily + * be made to work with the progressive one. + */ +/* Arguments to png_image_finish_read: */ + +/* Encoding of PNG data (used by the color-map code) */ +# define P_NOTSET 0 /* File encoding not yet known */ +# define P_sRGB 1 /* 8-bit encoded to sRGB gamma */ +# define P_LINEAR 2 /* 16-bit linear: not encoded, NOT pre-multiplied! */ +# define P_FILE 3 /* 8-bit encoded to file gamma, not sRGB or linear */ +# define P_LINEAR8 4 /* 8-bit linear: only from a file value */ + +/* Color-map processing: after libpng has run on the PNG image further + * processing may be needed to convert the data to color-map indices. + */ +#define PNG_CMAP_NONE 0 +#define PNG_CMAP_GA 1 /* Process GA data to a color-map with alpha */ +#define PNG_CMAP_TRANS 2 /* Process GA data to a background index */ +#define PNG_CMAP_RGB 3 /* Process RGB data */ +#define PNG_CMAP_RGB_ALPHA 4 /* Process RGBA data */ + +/* The following document where the background is for each processing case. */ +#define PNG_CMAP_NONE_BACKGROUND 256 +#define PNG_CMAP_GA_BACKGROUND 231 +#define PNG_CMAP_TRANS_BACKGROUND 254 +#define PNG_CMAP_RGB_BACKGROUND 256 +#define PNG_CMAP_RGB_ALPHA_BACKGROUND 216 + +typedef struct +{ + /* Arguments: */ + png_imagep image; + png_voidp buffer; + png_int_32 row_stride; + png_voidp colormap; + png_const_colorp background; + /* Local variables: */ + png_voidp local_row; + png_voidp first_row; + ptrdiff_t row_bytes; /* step between rows */ + int file_encoding; /* E_ values above */ + png_fixed_point gamma_to_linear; /* For P_FILE, reciprocal of gamma */ + int colormap_processing; /* PNG_CMAP_ values above */ +} png_image_read_control; + +/* Do all the *safe* initialization - 'safe' means that png_error won't be + * called, so setting up the jmp_buf is not required. This means that anything + * called from here must *not* call png_malloc - it has to call png_malloc_warn + * instead so that control is returned safely back to this routine. + */ +static int +png_image_read_init(png_imagep image) +{ + if (image->opaque == NULL) + { + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, image, + png_safe_error, png_safe_warning); + + /* And set the rest of the structure to NULL to ensure that the various + * fields are consistent. + */ + memset(image, 0, (sizeof *image)); + image->version = PNG_IMAGE_VERSION; + + if (png_ptr != NULL) + { + png_infop info_ptr = png_create_info_struct(png_ptr); + + if (info_ptr != NULL) + { + png_controlp control = png_voidcast(png_controlp, + png_malloc_warn(png_ptr, (sizeof *control))); + + if (control != NULL) + { + memset(control, 0, (sizeof *control)); + + control->png_ptr = png_ptr; + control->info_ptr = info_ptr; + control->for_write = 0; + + image->opaque = control; + return 1; + } + + /* Error clean up */ + png_destroy_info_struct(png_ptr, &info_ptr); + } + + png_destroy_read_struct(&png_ptr, NULL, NULL); + } + + return png_image_error(image, "png_image_read: out of memory"); + } + + return png_image_error(image, "png_image_read: opaque pointer not NULL"); +} + +/* Utility to find the base format of a PNG file from a png_struct. */ +static png_uint_32 +png_image_format(png_structrp png_ptr) +{ + png_uint_32 format = 0; + + if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0) + format |= PNG_FORMAT_FLAG_COLOR; + + if ((png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0) + format |= PNG_FORMAT_FLAG_ALPHA; + + /* Use png_ptr here, not info_ptr, because by examination png_handle_tRNS + * sets the png_struct fields; that's all we are interested in here. The + * precise interaction with an app call to png_set_tRNS and PNG file reading + * is unclear. + */ + else if (png_ptr->num_trans > 0) + format |= PNG_FORMAT_FLAG_ALPHA; + + if (png_ptr->bit_depth == 16) + format |= PNG_FORMAT_FLAG_LINEAR; + + if ((png_ptr->color_type & PNG_COLOR_MASK_PALETTE) != 0) + format |= PNG_FORMAT_FLAG_COLORMAP; + + return format; +} + +/* Is the given gamma significantly different from sRGB? The test is the same + * one used in pngrtran.c when deciding whether to do gamma correction. The + * arithmetic optimizes the division by using the fact that the inverse of the + * file sRGB gamma is 2.2 + */ +static int +png_gamma_not_sRGB(png_fixed_point g) +{ + if (g < PNG_FP_1) + { + /* An uninitialized gamma is assumed to be sRGB for the simplified API. */ + if (g == 0) + return 0; + + return png_gamma_significant((g * 11 + 2)/5 /* i.e. *2.2, rounded */); + } + + return 1; +} + +/* Do the main body of a 'png_image_begin_read' function; read the PNG file + * header and fill in all the information. This is executed in a safe context, + * unlike the init routine above. + */ +static int +png_image_read_header(png_voidp argument) +{ + png_imagep image = png_voidcast(png_imagep, argument); + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED + png_set_benign_errors(png_ptr, 1/*warn*/); +#endif + png_read_info(png_ptr, info_ptr); + + /* Do this the fast way; just read directly out of png_struct. */ + image->width = png_ptr->width; + image->height = png_ptr->height; + + { + png_uint_32 format = png_image_format(png_ptr); + + image->format = format; + +#ifdef PNG_COLORSPACE_SUPPORTED + /* Does the colorspace match sRGB? If there is no color endpoint + * (colorant) information assume yes, otherwise require the + * 'ENDPOINTS_MATCHP_sRGB' colorspace flag to have been set. If the + * colorspace has been determined to be invalid ignore it. + */ + if ((format & PNG_FORMAT_FLAG_COLOR) != 0 && ((png_ptr->colorspace.flags + & (PNG_COLORSPACE_HAVE_ENDPOINTS|PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB| + PNG_COLORSPACE_INVALID)) == PNG_COLORSPACE_HAVE_ENDPOINTS)) + image->flags |= PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB; +#endif + } + + /* We need the maximum number of entries regardless of the format the + * application sets here. + */ + { + png_uint_32 cmap_entries; + + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_GRAY: + cmap_entries = 1U << png_ptr->bit_depth; + break; + + case PNG_COLOR_TYPE_PALETTE: + cmap_entries = (png_uint_32)png_ptr->num_palette; + break; + + default: + cmap_entries = 256; + break; + } + + if (cmap_entries > 256) + cmap_entries = 256; + + image->colormap_entries = cmap_entries; + } + + return 1; +} + +#ifdef PNG_STDIO_SUPPORTED +int PNGAPI +png_image_begin_read_from_stdio(png_imagep image, FILE* file) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file != NULL) + { + if (png_image_read_init(image) != 0) + { + /* This is slightly evil, but png_init_io doesn't do anything other + * than this and we haven't changed the standard IO functions so + * this saves a 'safe' function. + */ + image->opaque->png_ptr->io_ptr = file; + return png_safe_execute(image, png_image_read_header, image); + } + } + + else + return png_image_error(image, + "png_image_begin_read_from_stdio: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_begin_read_from_stdio: incorrect PNG_IMAGE_VERSION"); + + return 0; +} + +int PNGAPI +png_image_begin_read_from_file(png_imagep image, const char *file_name) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file_name != NULL) + { + FILE *fp = fopen(file_name, "rb"); + + if (fp != NULL) + { + if (png_image_read_init(image) != 0) + { + image->opaque->png_ptr->io_ptr = fp; + image->opaque->owned_file = 1; + return png_safe_execute(image, png_image_read_header, image); + } + + /* Clean up: just the opened file. */ + (void)fclose(fp); + } + + else + return png_image_error(image, strerror(errno)); + } + + else + return png_image_error(image, + "png_image_begin_read_from_file: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_begin_read_from_file: incorrect PNG_IMAGE_VERSION"); + + return 0; +} +#endif /* STDIO */ + +static void PNGCBAPI +png_image_memory_read(png_structp png_ptr, png_bytep out, size_t need) +{ + if (png_ptr != NULL) + { + png_imagep image = png_voidcast(png_imagep, png_ptr->io_ptr); + if (image != NULL) + { + png_controlp cp = image->opaque; + if (cp != NULL) + { + png_const_bytep memory = cp->memory; + size_t size = cp->size; + + if (memory != NULL && size >= need) + { + memcpy(out, memory, need); + cp->memory = memory + need; + cp->size = size - need; + return; + } + + png_error(png_ptr, "read beyond end of data"); + } + } + + png_error(png_ptr, "invalid memory read"); + } +} + +int PNGAPI png_image_begin_read_from_memory(png_imagep image, + png_const_voidp memory, size_t size) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (memory != NULL && size > 0) + { + if (png_image_read_init(image) != 0) + { + /* Now set the IO functions to read from the memory buffer and + * store it into io_ptr. Again do this in-place to avoid calling a + * libpng function that requires error handling. + */ + image->opaque->memory = png_voidcast(png_const_bytep, memory); + image->opaque->size = size; + image->opaque->png_ptr->io_ptr = image; + image->opaque->png_ptr->read_data_fn = png_image_memory_read; + + return png_safe_execute(image, png_image_read_header, image); + } + } + + else + return png_image_error(image, + "png_image_begin_read_from_memory: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_begin_read_from_memory: incorrect PNG_IMAGE_VERSION"); + + return 0; +} + +/* Utility function to skip chunks that are not used by the simplified image + * read functions and an appropriate macro to call it. + */ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +static void +png_image_skip_unused_chunks(png_structrp png_ptr) +{ + /* Prepare the reader to ignore all recognized chunks whose data will not + * be used, i.e., all chunks recognized by libpng except for those + * involved in basic image reading: + * + * IHDR, PLTE, IDAT, IEND + * + * Or image data handling: + * + * tRNS, bKGD, gAMA, cHRM, sRGB, [iCCP] and sBIT. + * + * This provides a small performance improvement and eliminates any + * potential vulnerability to security problems in the unused chunks. + * + * At present the iCCP chunk data isn't used, so iCCP chunk can be ignored + * too. This allows the simplified API to be compiled without iCCP support, + * however if the support is there the chunk is still checked to detect + * errors (which are unfortunately quite common.) + */ + { + static const png_byte chunks_to_process[] = { + 98, 75, 71, 68, '\0', /* bKGD */ + 99, 72, 82, 77, '\0', /* cHRM */ + 103, 65, 77, 65, '\0', /* gAMA */ +# ifdef PNG_READ_iCCP_SUPPORTED + 105, 67, 67, 80, '\0', /* iCCP */ +# endif + 115, 66, 73, 84, '\0', /* sBIT */ + 115, 82, 71, 66, '\0', /* sRGB */ + }; + + /* Ignore unknown chunks and all other chunks except for the + * IHDR, PLTE, tRNS, IDAT, and IEND chunks. + */ + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, + NULL, -1); + + /* But do not ignore image data handling chunks */ + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_AS_DEFAULT, + chunks_to_process, (int)/*SAFE*/(sizeof chunks_to_process)/5); + } +} + +# define PNG_SKIP_CHUNKS(p) png_image_skip_unused_chunks(p) +#else +# define PNG_SKIP_CHUNKS(p) ((void)0) +#endif /* HANDLE_AS_UNKNOWN */ + +/* The following macro gives the exact rounded answer for all values in the + * range 0..255 (it actually divides by 51.2, but the rounding still generates + * the correct numbers 0..5 + */ +#define PNG_DIV51(v8) (((v8) * 5 + 130) >> 8) + +/* Utility functions to make particular color-maps */ +static void +set_file_encoding(png_image_read_control *display) +{ + png_fixed_point g = display->image->opaque->png_ptr->colorspace.gamma; + if (png_gamma_significant(g) != 0) + { + if (png_gamma_not_sRGB(g) != 0) + { + display->file_encoding = P_FILE; + display->gamma_to_linear = png_reciprocal(g); + } + + else + display->file_encoding = P_sRGB; + } + + else + display->file_encoding = P_LINEAR8; +} + +static unsigned int +decode_gamma(png_image_read_control *display, png_uint_32 value, int encoding) +{ + if (encoding == P_FILE) /* double check */ + encoding = display->file_encoding; + + if (encoding == P_NOTSET) /* must be the file encoding */ + { + set_file_encoding(display); + encoding = display->file_encoding; + } + + switch (encoding) + { + case P_FILE: + value = png_gamma_16bit_correct(value*257, display->gamma_to_linear); + break; + + case P_sRGB: + value = png_sRGB_table[value]; + break; + + case P_LINEAR: + break; + + case P_LINEAR8: + value *= 257; + break; + +#ifdef __GNUC__ + default: + png_error(display->image->opaque->png_ptr, + "unexpected encoding (internal error)"); +#endif + } + + return value; +} + +static png_uint_32 +png_colormap_compose(png_image_read_control *display, + png_uint_32 foreground, int foreground_encoding, png_uint_32 alpha, + png_uint_32 background, int encoding) +{ + /* The file value is composed on the background, the background has the given + * encoding and so does the result, the file is encoded with P_FILE and the + * file and alpha are 8-bit values. The (output) encoding will always be + * P_LINEAR or P_sRGB. + */ + png_uint_32 f = decode_gamma(display, foreground, foreground_encoding); + png_uint_32 b = decode_gamma(display, background, encoding); + + /* The alpha is always an 8-bit value (it comes from the palette), the value + * scaled by 255 is what PNG_sRGB_FROM_LINEAR requires. + */ + f = f * alpha + b * (255-alpha); + + if (encoding == P_LINEAR) + { + /* Scale to 65535; divide by 255, approximately (in fact this is extremely + * accurate, it divides by 255.00000005937181414556, with no overflow.) + */ + f *= 257; /* Now scaled by 65535 */ + f += f >> 16; + f = (f+32768) >> 16; + } + + else /* P_sRGB */ + f = PNG_sRGB_FROM_LINEAR(f); + + return f; +} + +/* NOTE: P_LINEAR values to this routine must be 16-bit, but P_FILE values must + * be 8-bit. + */ +static void +png_create_colormap_entry(png_image_read_control *display, + png_uint_32 ip, png_uint_32 red, png_uint_32 green, png_uint_32 blue, + png_uint_32 alpha, int encoding) +{ + png_imagep image = display->image; + int output_encoding = (image->format & PNG_FORMAT_FLAG_LINEAR) != 0 ? + P_LINEAR : P_sRGB; + int convert_to_Y = (image->format & PNG_FORMAT_FLAG_COLOR) == 0 && + (red != green || green != blue); + + if (ip > 255) + png_error(image->opaque->png_ptr, "color-map index out of range"); + + /* Update the cache with whether the file gamma is significantly different + * from sRGB. + */ + if (encoding == P_FILE) + { + if (display->file_encoding == P_NOTSET) + set_file_encoding(display); + + /* Note that the cached value may be P_FILE too, but if it is then the + * gamma_to_linear member has been set. + */ + encoding = display->file_encoding; + } + + if (encoding == P_FILE) + { + png_fixed_point g = display->gamma_to_linear; + + red = png_gamma_16bit_correct(red*257, g); + green = png_gamma_16bit_correct(green*257, g); + blue = png_gamma_16bit_correct(blue*257, g); + + if (convert_to_Y != 0 || output_encoding == P_LINEAR) + { + alpha *= 257; + encoding = P_LINEAR; + } + + else + { + red = PNG_sRGB_FROM_LINEAR(red * 255); + green = PNG_sRGB_FROM_LINEAR(green * 255); + blue = PNG_sRGB_FROM_LINEAR(blue * 255); + encoding = P_sRGB; + } + } + + else if (encoding == P_LINEAR8) + { + /* This encoding occurs quite frequently in test cases because PngSuite + * includes a gAMA 1.0 chunk with most images. + */ + red *= 257; + green *= 257; + blue *= 257; + alpha *= 257; + encoding = P_LINEAR; + } + + else if (encoding == P_sRGB && + (convert_to_Y != 0 || output_encoding == P_LINEAR)) + { + /* The values are 8-bit sRGB values, but must be converted to 16-bit + * linear. + */ + red = png_sRGB_table[red]; + green = png_sRGB_table[green]; + blue = png_sRGB_table[blue]; + alpha *= 257; + encoding = P_LINEAR; + } + + /* This is set if the color isn't gray but the output is. */ + if (encoding == P_LINEAR) + { + if (convert_to_Y != 0) + { + /* NOTE: these values are copied from png_do_rgb_to_gray */ + png_uint_32 y = (png_uint_32)6968 * red + (png_uint_32)23434 * green + + (png_uint_32)2366 * blue; + + if (output_encoding == P_LINEAR) + y = (y + 16384) >> 15; + + else + { + /* y is scaled by 32768, we need it scaled by 255: */ + y = (y + 128) >> 8; + y *= 255; + y = PNG_sRGB_FROM_LINEAR((y + 64) >> 7); + alpha = PNG_DIV257(alpha); + encoding = P_sRGB; + } + + blue = red = green = y; + } + + else if (output_encoding == P_sRGB) + { + red = PNG_sRGB_FROM_LINEAR(red * 255); + green = PNG_sRGB_FROM_LINEAR(green * 255); + blue = PNG_sRGB_FROM_LINEAR(blue * 255); + alpha = PNG_DIV257(alpha); + encoding = P_sRGB; + } + } + + if (encoding != output_encoding) + png_error(image->opaque->png_ptr, "bad encoding (internal error)"); + + /* Store the value. */ + { +# ifdef PNG_FORMAT_AFIRST_SUPPORTED + int afirst = (image->format & PNG_FORMAT_FLAG_AFIRST) != 0 && + (image->format & PNG_FORMAT_FLAG_ALPHA) != 0; +# else +# define afirst 0 +# endif +# ifdef PNG_FORMAT_BGR_SUPPORTED + int bgr = (image->format & PNG_FORMAT_FLAG_BGR) != 0 ? 2 : 0; +# else +# define bgr 0 +# endif + + if (output_encoding == P_LINEAR) + { + png_uint_16p entry = png_voidcast(png_uint_16p, display->colormap); + + entry += ip * PNG_IMAGE_SAMPLE_CHANNELS(image->format); + + /* The linear 16-bit values must be pre-multiplied by the alpha channel + * value, if less than 65535 (this is, effectively, composite on black + * if the alpha channel is removed.) + */ + switch (PNG_IMAGE_SAMPLE_CHANNELS(image->format)) + { + case 4: + entry[afirst ? 0 : 3] = (png_uint_16)alpha; + /* FALLTHROUGH */ + + case 3: + if (alpha < 65535) + { + if (alpha > 0) + { + blue = (blue * alpha + 32767U)/65535U; + green = (green * alpha + 32767U)/65535U; + red = (red * alpha + 32767U)/65535U; + } + + else + red = green = blue = 0; + } + entry[afirst + (2 ^ bgr)] = (png_uint_16)blue; + entry[afirst + 1] = (png_uint_16)green; + entry[afirst + bgr] = (png_uint_16)red; + break; + + case 2: + entry[1 ^ afirst] = (png_uint_16)alpha; + /* FALLTHROUGH */ + + case 1: + if (alpha < 65535) + { + if (alpha > 0) + green = (green * alpha + 32767U)/65535U; + + else + green = 0; + } + entry[afirst] = (png_uint_16)green; + break; + + default: + break; + } + } + + else /* output encoding is P_sRGB */ + { + png_bytep entry = png_voidcast(png_bytep, display->colormap); + + entry += ip * PNG_IMAGE_SAMPLE_CHANNELS(image->format); + + switch (PNG_IMAGE_SAMPLE_CHANNELS(image->format)) + { + case 4: + entry[afirst ? 0 : 3] = (png_byte)alpha; + /* FALLTHROUGH */ + case 3: + entry[afirst + (2 ^ bgr)] = (png_byte)blue; + entry[afirst + 1] = (png_byte)green; + entry[afirst + bgr] = (png_byte)red; + break; + + case 2: + entry[1 ^ afirst] = (png_byte)alpha; + /* FALLTHROUGH */ + case 1: + entry[afirst] = (png_byte)green; + break; + + default: + break; + } + } + +# ifdef afirst +# undef afirst +# endif +# ifdef bgr +# undef bgr +# endif + } +} + +static int +make_gray_file_colormap(png_image_read_control *display) +{ + unsigned int i; + + for (i=0; i<256; ++i) + png_create_colormap_entry(display, i, i, i, i, 255, P_FILE); + + return (int)i; +} + +static int +make_gray_colormap(png_image_read_control *display) +{ + unsigned int i; + + for (i=0; i<256; ++i) + png_create_colormap_entry(display, i, i, i, i, 255, P_sRGB); + + return (int)i; +} +#define PNG_GRAY_COLORMAP_ENTRIES 256 + +static int +make_ga_colormap(png_image_read_control *display) +{ + unsigned int i, a; + + /* Alpha is retained, the output will be a color-map with entries + * selected by six levels of alpha. One transparent entry, 6 gray + * levels for all the intermediate alpha values, leaving 230 entries + * for the opaque grays. The color-map entries are the six values + * [0..5]*51, the GA processing uses PNG_DIV51(value) to find the + * relevant entry. + * + * if (alpha > 229) // opaque + * { + * // The 231 entries are selected to make the math below work: + * base = 0; + * entry = (231 * gray + 128) >> 8; + * } + * else if (alpha < 26) // transparent + * { + * base = 231; + * entry = 0; + * } + * else // partially opaque + * { + * base = 226 + 6 * PNG_DIV51(alpha); + * entry = PNG_DIV51(gray); + * } + */ + i = 0; + while (i < 231) + { + unsigned int gray = (i * 256 + 115) / 231; + png_create_colormap_entry(display, i++, gray, gray, gray, 255, P_sRGB); + } + + /* 255 is used here for the component values for consistency with the code + * that undoes premultiplication in pngwrite.c. + */ + png_create_colormap_entry(display, i++, 255, 255, 255, 0, P_sRGB); + + for (a=1; a<5; ++a) + { + unsigned int g; + + for (g=0; g<6; ++g) + png_create_colormap_entry(display, i++, g*51, g*51, g*51, a*51, + P_sRGB); + } + + return (int)i; +} + +#define PNG_GA_COLORMAP_ENTRIES 256 + +static int +make_rgb_colormap(png_image_read_control *display) +{ + unsigned int i, r; + + /* Build a 6x6x6 opaque RGB cube */ + for (i=r=0; r<6; ++r) + { + unsigned int g; + + for (g=0; g<6; ++g) + { + unsigned int b; + + for (b=0; b<6; ++b) + png_create_colormap_entry(display, i++, r*51, g*51, b*51, 255, + P_sRGB); + } + } + + return (int)i; +} + +#define PNG_RGB_COLORMAP_ENTRIES 216 + +/* Return a palette index to the above palette given three 8-bit sRGB values. */ +#define PNG_RGB_INDEX(r,g,b) \ + ((png_byte)(6 * (6 * PNG_DIV51(r) + PNG_DIV51(g)) + PNG_DIV51(b))) + +static int +png_image_read_colormap(png_voidp argument) +{ + png_image_read_control *display = + png_voidcast(png_image_read_control*, argument); + png_imagep image = display->image; + + png_structrp png_ptr = image->opaque->png_ptr; + png_uint_32 output_format = image->format; + int output_encoding = (output_format & PNG_FORMAT_FLAG_LINEAR) != 0 ? + P_LINEAR : P_sRGB; + + unsigned int cmap_entries; + unsigned int output_processing; /* Output processing option */ + unsigned int data_encoding = P_NOTSET; /* Encoding libpng must produce */ + + /* Background information; the background color and the index of this color + * in the color-map if it exists (else 256). + */ + unsigned int background_index = 256; + png_uint_32 back_r, back_g, back_b; + + /* Flags to accumulate things that need to be done to the input. */ + int expand_tRNS = 0; + + /* Exclude the NYI feature of compositing onto a color-mapped buffer; it is + * very difficult to do, the results look awful, and it is difficult to see + * what possible use it is because the application can't control the + * color-map. + */ + if (((png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0 || + png_ptr->num_trans > 0) /* alpha in input */ && + ((output_format & PNG_FORMAT_FLAG_ALPHA) == 0) /* no alpha in output */) + { + if (output_encoding == P_LINEAR) /* compose on black */ + back_b = back_g = back_r = 0; + + else if (display->background == NULL /* no way to remove it */) + png_error(png_ptr, + "background color must be supplied to remove alpha/transparency"); + + /* Get a copy of the background color (this avoids repeating the checks + * below.) The encoding is 8-bit sRGB or 16-bit linear, depending on the + * output format. + */ + else + { + back_g = display->background->green; + if ((output_format & PNG_FORMAT_FLAG_COLOR) != 0) + { + back_r = display->background->red; + back_b = display->background->blue; + } + else + back_b = back_r = back_g; + } + } + + else if (output_encoding == P_LINEAR) + back_b = back_r = back_g = 65535; + + else + back_b = back_r = back_g = 255; + + /* Default the input file gamma if required - this is necessary because + * libpng assumes that if no gamma information is present the data is in the + * output format, but the simplified API deduces the gamma from the input + * format. + */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) == 0) + { + /* Do this directly, not using the png_colorspace functions, to ensure + * that it happens even if the colorspace is invalid (though probably if + * it is the setting will be ignored) Note that the same thing can be + * achieved at the application interface with png_set_gAMA. + */ + if (png_ptr->bit_depth == 16 && + (image->flags & PNG_IMAGE_FLAG_16BIT_sRGB) == 0) + png_ptr->colorspace.gamma = PNG_GAMMA_LINEAR; + + else + png_ptr->colorspace.gamma = PNG_GAMMA_sRGB_INVERSE; + + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + } + + /* Decide what to do based on the PNG color type of the input data. The + * utility function png_create_colormap_entry deals with most aspects of the + * output transformations; this code works out how to produce bytes of + * color-map entries from the original format. + */ + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_GRAY: + if (png_ptr->bit_depth <= 8) + { + /* There at most 256 colors in the output, regardless of + * transparency. + */ + unsigned int step, i, val, trans = 256/*ignore*/, back_alpha = 0; + + cmap_entries = 1U << png_ptr->bit_depth; + if (cmap_entries > image->colormap_entries) + png_error(png_ptr, "gray[8] color-map: too few entries"); + + step = 255 / (cmap_entries - 1); + output_processing = PNG_CMAP_NONE; + + /* If there is a tRNS chunk then this either selects a transparent + * value or, if the output has no alpha, the background color. + */ + if (png_ptr->num_trans > 0) + { + trans = png_ptr->trans_color.gray; + + if ((output_format & PNG_FORMAT_FLAG_ALPHA) == 0) + back_alpha = output_encoding == P_LINEAR ? 65535 : 255; + } + + /* png_create_colormap_entry just takes an RGBA and writes the + * corresponding color-map entry using the format from 'image', + * including the required conversion to sRGB or linear as + * appropriate. The input values are always either sRGB (if the + * gamma correction flag is 0) or 0..255 scaled file encoded values + * (if the function must gamma correct them). + */ + for (i=val=0; ibit_depth < 8) + png_set_packing(png_ptr); + } + + else /* bit depth is 16 */ + { + /* The 16-bit input values can be converted directly to 8-bit gamma + * encoded values; however, if a tRNS chunk is present 257 color-map + * entries are required. This means that the extra entry requires + * special processing; add an alpha channel, sacrifice gray level + * 254 and convert transparent (alpha==0) entries to that. + * + * Use libpng to chop the data to 8 bits. Convert it to sRGB at the + * same time to minimize quality loss. If a tRNS chunk is present + * this means libpng must handle it too; otherwise it is impossible + * to do the exact match on the 16-bit value. + * + * If the output has no alpha channel *and* the background color is + * gray then it is possible to let libpng handle the substitution by + * ensuring that the corresponding gray level matches the background + * color exactly. + */ + data_encoding = P_sRGB; + + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray[16] color-map: too few entries"); + + cmap_entries = (unsigned int)make_gray_colormap(display); + + if (png_ptr->num_trans > 0) + { + unsigned int back_alpha; + + if ((output_format & PNG_FORMAT_FLAG_ALPHA) != 0) + back_alpha = 0; + + else + { + if (back_r == back_g && back_g == back_b) + { + /* Background is gray; no special processing will be + * required. + */ + png_color_16 c; + png_uint_32 gray = back_g; + + if (output_encoding == P_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry + * matches. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 65535, P_LINEAR); + } + + /* The background passed to libpng, however, must be the + * sRGB value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + /* NOTE: does this work without expanding tRNS to alpha? + * It should be the color->gray case below apparently + * doesn't. + */ + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_NONE; + break; + } +#ifdef __COVERITY__ + /* Coverity claims that output_encoding cannot be 2 (P_LINEAR) + * here. + */ + back_alpha = 255; +#else + back_alpha = output_encoding == P_LINEAR ? 65535 : 255; +#endif + } + + /* output_processing means that the libpng-processed row will be + * 8-bit GA and it has to be processing to single byte color-map + * values. Entry 254 is replaced by either a completely + * transparent entry or by the background color at full + * precision (and the background color is not a simple gray + * level in this case.) + */ + expand_tRNS = 1; + output_processing = PNG_CMAP_TRANS; + background_index = 254; + + /* And set (overwrite) color-map entry 254 to the actual + * background color at full precision. + */ + png_create_colormap_entry(display, 254, back_r, back_g, back_b, + back_alpha, output_encoding); + } + + else + output_processing = PNG_CMAP_NONE; + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + /* 8-bit or 16-bit PNG with two channels - gray and alpha. A minimum + * of 65536 combinations. If, however, the alpha channel is to be + * removed there are only 256 possibilities if the background is gray. + * (Otherwise there is a subset of the 65536 possibilities defined by + * the triangle between black, white and the background color.) + * + * Reduce 16-bit files to 8-bit and sRGB encode the result. No need to + * worry about tRNS matching - tRNS is ignored if there is an alpha + * channel. + */ + data_encoding = P_sRGB; + + if ((output_format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray+alpha color-map: too few entries"); + + cmap_entries = (unsigned int)make_ga_colormap(display); + + background_index = PNG_CMAP_GA_BACKGROUND; + output_processing = PNG_CMAP_GA; + } + + else /* alpha is removed */ + { + /* Alpha must be removed as the PNG data is processed when the + * background is a color because the G and A channels are + * independent and the vector addition (non-parallel vectors) is a + * 2-D problem. + * + * This can be reduced to the same algorithm as above by making a + * colormap containing gray levels (for the opaque grays), a + * background entry (for a transparent pixel) and a set of four six + * level color values, one set for each intermediate alpha value. + * See the comments in make_ga_colormap for how this works in the + * per-pixel processing. + * + * If the background is gray, however, we only need a 256 entry gray + * level color map. It is sufficient to make the entry generated + * for the background color be exactly the color specified. + */ + if ((output_format & PNG_FORMAT_FLAG_COLOR) == 0 || + (back_r == back_g && back_g == back_b)) + { + /* Background is gray; no special processing will be required. */ + png_color_16 c; + png_uint_32 gray = back_g; + + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray-alpha color-map: too few entries"); + + cmap_entries = (unsigned int)make_gray_colormap(display); + + if (output_encoding == P_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry matches. */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 65535, P_LINEAR); + } + + /* The background passed to libpng, however, must be the sRGB + * value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_NONE; + } + + else + { + png_uint_32 i, a; + + /* This is the same as png_make_ga_colormap, above, except that + * the entries are all opaque. + */ + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "ga-alpha color-map: too few entries"); + + i = 0; + while (i < 231) + { + png_uint_32 gray = (i * 256 + 115) / 231; + png_create_colormap_entry(display, i++, gray, gray, gray, + 255, P_sRGB); + } + + /* NOTE: this preserves the full precision of the application + * background color. + */ + background_index = i; + png_create_colormap_entry(display, i++, back_r, back_g, back_b, +#ifdef __COVERITY__ + /* Coverity claims that output_encoding + * cannot be 2 (P_LINEAR) here. + */ 255U, +#else + output_encoding == P_LINEAR ? 65535U : 255U, +#endif + output_encoding); + + /* For non-opaque input composite on the sRGB background - this + * requires inverting the encoding for each component. The input + * is still converted to the sRGB encoding because this is a + * reasonable approximate to the logarithmic curve of human + * visual sensitivity, at least over the narrow range which PNG + * represents. Consequently 'G' is always sRGB encoded, while + * 'A' is linear. We need the linear background colors. + */ + if (output_encoding == P_sRGB) /* else already linear */ + { + /* This may produce a value not exactly matching the + * background, but that's ok because these numbers are only + * used when alpha != 0 + */ + back_r = png_sRGB_table[back_r]; + back_g = png_sRGB_table[back_g]; + back_b = png_sRGB_table[back_b]; + } + + for (a=1; a<5; ++a) + { + unsigned int g; + + /* PNG_sRGB_FROM_LINEAR expects a 16-bit linear value scaled + * by an 8-bit alpha value (0..255). + */ + png_uint_32 alpha = 51 * a; + png_uint_32 back_rx = (255-alpha) * back_r; + png_uint_32 back_gx = (255-alpha) * back_g; + png_uint_32 back_bx = (255-alpha) * back_b; + + for (g=0; g<6; ++g) + { + png_uint_32 gray = png_sRGB_table[g*51] * alpha; + + png_create_colormap_entry(display, i++, + PNG_sRGB_FROM_LINEAR(gray + back_rx), + PNG_sRGB_FROM_LINEAR(gray + back_gx), + PNG_sRGB_FROM_LINEAR(gray + back_bx), 255, P_sRGB); + } + } + + cmap_entries = i; + output_processing = PNG_CMAP_GA; + } + } + break; + + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGB_ALPHA: + /* Exclude the case where the output is gray; we can always handle this + * with the cases above. + */ + if ((output_format & PNG_FORMAT_FLAG_COLOR) == 0) + { + /* The color-map will be grayscale, so we may as well convert the + * input RGB values to a simple grayscale and use the grayscale + * code above. + * + * NOTE: calling this apparently damages the recognition of the + * transparent color in background color handling; call + * png_set_tRNS_to_alpha before png_set_background_fixed. + */ + png_set_rgb_to_gray_fixed(png_ptr, PNG_ERROR_ACTION_NONE, -1, + -1); + data_encoding = P_sRGB; + + /* The output will now be one or two 8-bit gray or gray+alpha + * channels. The more complex case arises when the input has alpha. + */ + if ((png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) && + (output_format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + /* Both input and output have an alpha channel, so no background + * processing is required; just map the GA bytes to the right + * color-map entry. + */ + expand_tRNS = 1; + + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb[ga] color-map: too few entries"); + + cmap_entries = (unsigned int)make_ga_colormap(display); + background_index = PNG_CMAP_GA_BACKGROUND; + output_processing = PNG_CMAP_GA; + } + + else + { + /* Either the input or the output has no alpha channel, so there + * will be no non-opaque pixels in the color-map; it will just be + * grayscale. + */ + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb[gray] color-map: too few entries"); + + /* Ideally this code would use libpng to do the gamma correction, + * but if an input alpha channel is to be removed we will hit the + * libpng bug in gamma+compose+rgb-to-gray (the double gamma + * correction bug). Fix this by dropping the gamma correction in + * this case and doing it in the palette; this will result in + * duplicate palette entries, but that's better than the + * alternative of double gamma correction. + */ + if ((png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) && + png_gamma_not_sRGB(png_ptr->colorspace.gamma) != 0) + { + cmap_entries = (unsigned int)make_gray_file_colormap(display); + data_encoding = P_FILE; + } + + else + cmap_entries = (unsigned int)make_gray_colormap(display); + + /* But if the input has alpha or transparency it must be removed + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) + { + png_color_16 c; + png_uint_32 gray = back_g; + + /* We need to ensure that the application background exists in + * the colormap and that completely transparent pixels map to + * it. Achieve this simply by ensuring that the entry + * selected for the background really is the background color. + */ + if (data_encoding == P_FILE) /* from the fixup above */ + { + /* The app supplied a gray which is in output_encoding, we + * need to convert it to a value of the input (P_FILE) + * encoding then set this palette entry to the required + * output encoding. + */ + if (output_encoding == P_sRGB) + gray = png_sRGB_table[gray]; /* now P_LINEAR */ + + gray = PNG_DIV257(png_gamma_16bit_correct(gray, + png_ptr->colorspace.gamma)); /* now P_FILE */ + + /* And make sure the corresponding palette entry contains + * exactly the required sRGB value. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 0/*unused*/, output_encoding); + } + + else if (output_encoding == P_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry matches. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 0/*unused*/, P_LINEAR); + } + + /* The background passed to libpng, however, must be the + * output (normally sRGB) value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + /* NOTE: the following is apparently a bug in libpng. Without + * it the transparent color recognition in + * png_set_background_fixed seems to go wrong. + */ + expand_tRNS = 1; + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + } + + output_processing = PNG_CMAP_NONE; + } + } + + else /* output is color */ + { + /* We could use png_quantize here so long as there is no transparent + * color or alpha; png_quantize ignores alpha. Easier overall just + * to do it once and using PNG_DIV51 on the 6x6x6 reduced RGB cube. + * Consequently we always want libpng to produce sRGB data. + */ + data_encoding = P_sRGB; + + /* Is there any transparency or alpha? */ + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) + { + /* Is there alpha in the output too? If so all four channels are + * processed into a special RGB cube with alpha support. + */ + if ((output_format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + png_uint_32 r; + + if (PNG_RGB_COLORMAP_ENTRIES+1+27 > image->colormap_entries) + png_error(png_ptr, "rgb+alpha color-map: too few entries"); + + cmap_entries = (unsigned int)make_rgb_colormap(display); + + /* Add a transparent entry. */ + png_create_colormap_entry(display, cmap_entries, 255, 255, + 255, 0, P_sRGB); + + /* This is stored as the background index for the processing + * algorithm. + */ + background_index = cmap_entries++; + + /* Add 27 r,g,b entries each with alpha 0.5. */ + for (r=0; r<256; r = (r << 1) | 0x7f) + { + png_uint_32 g; + + for (g=0; g<256; g = (g << 1) | 0x7f) + { + png_uint_32 b; + + /* This generates components with the values 0, 127 and + * 255 + */ + for (b=0; b<256; b = (b << 1) | 0x7f) + png_create_colormap_entry(display, cmap_entries++, + r, g, b, 128, P_sRGB); + } + } + + expand_tRNS = 1; + output_processing = PNG_CMAP_RGB_ALPHA; + } + + else + { + /* Alpha/transparency must be removed. The background must + * exist in the color map (achieved by setting adding it after + * the 666 color-map). If the standard processing code will + * pick up this entry automatically that's all that is + * required; libpng can be called to do the background + * processing. + */ + unsigned int sample_size = + PNG_IMAGE_SAMPLE_SIZE(output_format); + png_uint_32 r, g, b; /* sRGB background */ + + if (PNG_RGB_COLORMAP_ENTRIES+1+27 > image->colormap_entries) + png_error(png_ptr, "rgb-alpha color-map: too few entries"); + + cmap_entries = (unsigned int)make_rgb_colormap(display); + + png_create_colormap_entry(display, cmap_entries, back_r, + back_g, back_b, 0/*unused*/, output_encoding); + + if (output_encoding == P_LINEAR) + { + r = PNG_sRGB_FROM_LINEAR(back_r * 255); + g = PNG_sRGB_FROM_LINEAR(back_g * 255); + b = PNG_sRGB_FROM_LINEAR(back_b * 255); + } + + else + { + r = back_r; + g = back_g; + b = back_g; + } + + /* Compare the newly-created color-map entry with the one the + * PNG_CMAP_RGB algorithm will use. If the two entries don't + * match, add the new one and set this as the background + * index. + */ + if (memcmp((png_const_bytep)display->colormap + + sample_size * cmap_entries, + (png_const_bytep)display->colormap + + sample_size * PNG_RGB_INDEX(r,g,b), + sample_size) != 0) + { + /* The background color must be added. */ + background_index = cmap_entries++; + + /* Add 27 r,g,b entries each with created by composing with + * the background at alpha 0.5. + */ + for (r=0; r<256; r = (r << 1) | 0x7f) + { + for (g=0; g<256; g = (g << 1) | 0x7f) + { + /* This generates components with the values 0, 127 + * and 255 + */ + for (b=0; b<256; b = (b << 1) | 0x7f) + png_create_colormap_entry(display, cmap_entries++, + png_colormap_compose(display, r, P_sRGB, 128, + back_r, output_encoding), + png_colormap_compose(display, g, P_sRGB, 128, + back_g, output_encoding), + png_colormap_compose(display, b, P_sRGB, 128, + back_b, output_encoding), + 0/*unused*/, output_encoding); + } + } + + expand_tRNS = 1; + output_processing = PNG_CMAP_RGB_ALPHA; + } + + else /* background color is in the standard color-map */ + { + png_color_16 c; + + c.index = 0; /*unused*/ + c.red = (png_uint_16)back_r; + c.gray = c.green = (png_uint_16)back_g; + c.blue = (png_uint_16)back_b; + + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_RGB; + } + } + } + + else /* no alpha or transparency in the input */ + { + /* Alpha in the output is irrelevant, simply map the opaque input + * pixels to the 6x6x6 color-map. + */ + if (PNG_RGB_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb color-map: too few entries"); + + cmap_entries = (unsigned int)make_rgb_colormap(display); + output_processing = PNG_CMAP_RGB; + } + } + break; + + case PNG_COLOR_TYPE_PALETTE: + /* It's already got a color-map. It may be necessary to eliminate the + * tRNS entries though. + */ + { + unsigned int num_trans = png_ptr->num_trans; + png_const_bytep trans = num_trans > 0 ? png_ptr->trans_alpha : NULL; + png_const_colorp colormap = png_ptr->palette; + int do_background = trans != NULL && + (output_format & PNG_FORMAT_FLAG_ALPHA) == 0; + unsigned int i; + + /* Just in case: */ + if (trans == NULL) + num_trans = 0; + + output_processing = PNG_CMAP_NONE; + data_encoding = P_FILE; /* Don't change from color-map indices */ + cmap_entries = (unsigned int)png_ptr->num_palette; + if (cmap_entries > 256) + cmap_entries = 256; + + if (cmap_entries > (unsigned int)image->colormap_entries) + png_error(png_ptr, "palette color-map: too few entries"); + + for (i=0; i < cmap_entries; ++i) + { + if (do_background != 0 && i < num_trans && trans[i] < 255) + { + if (trans[i] == 0) + png_create_colormap_entry(display, i, back_r, back_g, + back_b, 0, output_encoding); + + else + { + /* Must compose the PNG file color in the color-map entry + * on the sRGB color in 'back'. + */ + png_create_colormap_entry(display, i, + png_colormap_compose(display, colormap[i].red, + P_FILE, trans[i], back_r, output_encoding), + png_colormap_compose(display, colormap[i].green, + P_FILE, trans[i], back_g, output_encoding), + png_colormap_compose(display, colormap[i].blue, + P_FILE, trans[i], back_b, output_encoding), + output_encoding == P_LINEAR ? trans[i] * 257U : + trans[i], + output_encoding); + } + } + + else + png_create_colormap_entry(display, i, colormap[i].red, + colormap[i].green, colormap[i].blue, + i < num_trans ? trans[i] : 255U, P_FILE/*8-bit*/); + } + + /* The PNG data may have indices packed in fewer than 8 bits, it + * must be expanded if so. + */ + if (png_ptr->bit_depth < 8) + png_set_packing(png_ptr); + } + break; + + default: + png_error(png_ptr, "invalid PNG color type"); + /*NOT REACHED*/ + } + + /* Now deal with the output processing */ + if (expand_tRNS != 0 && png_ptr->num_trans > 0 && + (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) == 0) + png_set_tRNS_to_alpha(png_ptr); + + switch (data_encoding) + { + case P_sRGB: + /* Change to 8-bit sRGB */ + png_set_alpha_mode_fixed(png_ptr, PNG_ALPHA_PNG, PNG_GAMMA_sRGB); + /* FALLTHROUGH */ + + case P_FILE: + if (png_ptr->bit_depth > 8) + png_set_scale_16(png_ptr); + break; + +#ifdef __GNUC__ + default: + png_error(png_ptr, "bad data option (internal error)"); +#endif + } + + if (cmap_entries > 256 || cmap_entries > image->colormap_entries) + png_error(png_ptr, "color map overflow (BAD internal error)"); + + image->colormap_entries = cmap_entries; + + /* Double check using the recorded background index */ + switch (output_processing) + { + case PNG_CMAP_NONE: + if (background_index != PNG_CMAP_NONE_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_GA: + if (background_index != PNG_CMAP_GA_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_TRANS: + if (background_index >= cmap_entries || + background_index != PNG_CMAP_TRANS_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_RGB: + if (background_index != PNG_CMAP_RGB_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_RGB_ALPHA: + if (background_index != PNG_CMAP_RGB_ALPHA_BACKGROUND) + goto bad_background; + break; + + default: + png_error(png_ptr, "bad processing option (internal error)"); + + bad_background: + png_error(png_ptr, "bad background index (internal error)"); + } + + display->colormap_processing = (int)output_processing; + + return 1/*ok*/; +} + +/* The final part of the color-map read called from png_image_finish_read. */ +static int +png_image_read_and_map(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + int passes; + + /* Called when the libpng data must be transformed into the color-mapped + * form. There is a local row buffer in display->local and this routine must + * do the interlace handling. + */ + switch (png_ptr->interlaced) + { + case PNG_INTERLACE_NONE: + passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + passes = PNG_INTERLACE_ADAM7_PASSES; + break; + + default: + png_error(png_ptr, "unknown interlace type"); + } + + { + png_uint_32 height = image->height; + png_uint_32 width = image->width; + int proc = display->colormap_processing; + png_bytep first_row = png_voidcast(png_bytep, display->first_row); + ptrdiff_t step_row = display->row_bytes; + int pass; + + for (pass = 0; pass < passes; ++pass) + { + unsigned int startx, stepx, stepy; + png_uint_32 y; + + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass); + stepx = PNG_PASS_COL_OFFSET(pass); + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = stepy = 1; + } + + for (; ylocal_row); + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; + + /* Read read the libpng data into the temporary buffer. */ + png_read_row(png_ptr, inrow, NULL); + + /* Now process the row according to the processing option, note + * that the caller verifies that the format of the libpng output + * data is as required. + */ + outrow += startx; + switch (proc) + { + case PNG_CMAP_GA: + for (; outrow < end_row; outrow += stepx) + { + /* The data is always in the PNG order */ + unsigned int gray = *inrow++; + unsigned int alpha = *inrow++; + unsigned int entry; + + /* NOTE: this code is copied as a comment in + * make_ga_colormap above. Please update the + * comment if you change this code! + */ + if (alpha > 229) /* opaque */ + { + entry = (231 * gray + 128) >> 8; + } + else if (alpha < 26) /* transparent */ + { + entry = 231; + } + else /* partially opaque */ + { + entry = 226 + 6 * PNG_DIV51(alpha) + PNG_DIV51(gray); + } + + *outrow = (png_byte)entry; + } + break; + + case PNG_CMAP_TRANS: + for (; outrow < end_row; outrow += stepx) + { + png_byte gray = *inrow++; + png_byte alpha = *inrow++; + + if (alpha == 0) + *outrow = PNG_CMAP_TRANS_BACKGROUND; + + else if (gray != PNG_CMAP_TRANS_BACKGROUND) + *outrow = gray; + + else + *outrow = (png_byte)(PNG_CMAP_TRANS_BACKGROUND+1); + } + break; + + case PNG_CMAP_RGB: + for (; outrow < end_row; outrow += stepx) + { + *outrow = PNG_RGB_INDEX(inrow[0], inrow[1], inrow[2]); + inrow += 3; + } + break; + + case PNG_CMAP_RGB_ALPHA: + for (; outrow < end_row; outrow += stepx) + { + unsigned int alpha = inrow[3]; + + /* Because the alpha entries only hold alpha==0.5 values + * split the processing at alpha==0.25 (64) and 0.75 + * (196). + */ + + if (alpha >= 196) + *outrow = PNG_RGB_INDEX(inrow[0], inrow[1], + inrow[2]); + + else if (alpha < 64) + *outrow = PNG_CMAP_RGB_ALPHA_BACKGROUND; + + else + { + /* Likewise there are three entries for each of r, g + * and b. We could select the entry by popcount on + * the top two bits on those architectures that + * support it, this is what the code below does, + * crudely. + */ + unsigned int back_i = PNG_CMAP_RGB_ALPHA_BACKGROUND+1; + + /* Here are how the values map: + * + * 0x00 .. 0x3f -> 0 + * 0x40 .. 0xbf -> 1 + * 0xc0 .. 0xff -> 2 + * + * So, as above with the explicit alpha checks, the + * breakpoints are at 64 and 196. + */ + if (inrow[0] & 0x80) back_i += 9; /* red */ + if (inrow[0] & 0x40) back_i += 9; + if (inrow[0] & 0x80) back_i += 3; /* green */ + if (inrow[0] & 0x40) back_i += 3; + if (inrow[0] & 0x80) back_i += 1; /* blue */ + if (inrow[0] & 0x40) back_i += 1; + + *outrow = (png_byte)back_i; + } + + inrow += 4; + } + break; + + default: + break; + } + } + } + } + + return 1; +} + +static int +png_image_read_colormapped(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_controlp control = image->opaque; + png_structrp png_ptr = control->png_ptr; + png_inforp info_ptr = control->info_ptr; + + int passes = 0; /* As a flag */ + + PNG_SKIP_CHUNKS(png_ptr); + + /* Update the 'info' structure and make sure the result is as required; first + * make sure to turn on the interlace handling if it will be required + * (because it can't be turned on *after* the call to png_read_update_info!) + */ + if (display->colormap_processing == PNG_CMAP_NONE) + passes = png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + /* The expected output can be deduced from the colormap_processing option. */ + switch (display->colormap_processing) + { + case PNG_CMAP_NONE: + /* Output must be one channel and one byte per pixel, the output + * encoding can be anything. + */ + if ((info_ptr->color_type == PNG_COLOR_TYPE_PALETTE || + info_ptr->color_type == PNG_COLOR_TYPE_GRAY) && + info_ptr->bit_depth == 8) + break; + + goto bad_output; + + case PNG_CMAP_TRANS: + case PNG_CMAP_GA: + /* Output must be two channels and the 'G' one must be sRGB, the latter + * can be checked with an exact number because it should have been set + * to this number above! + */ + if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 256) + break; + + goto bad_output; + + case PNG_CMAP_RGB: + /* Output must be 8-bit sRGB encoded RGB */ + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 216) + break; + + goto bad_output; + + case PNG_CMAP_RGB_ALPHA: + /* Output must be 8-bit sRGB encoded RGBA */ + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 244 /* 216 + 1 + 27 */) + break; + + goto bad_output; + + default: + bad_output: + png_error(png_ptr, "bad color-map processing (internal error)"); + } + + /* Now read the rows. Do this here if it is possible to read directly into + * the output buffer, otherwise allocate a local row buffer of the maximum + * size libpng requires and call the relevant processing routine safely. + */ + { + png_voidp first_row = display->buffer; + ptrdiff_t row_bytes = display->row_stride; + + /* The following expression is designed to work correctly whether it gives + * a signed or an unsigned result. + */ + if (row_bytes < 0) + { + char *ptr = png_voidcast(char*, first_row); + ptr += (image->height-1) * (-row_bytes); + first_row = png_voidcast(png_voidp, ptr); + } + + display->first_row = first_row; + display->row_bytes = row_bytes; + } + + if (passes == 0) + { + int result; + png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr)); + + display->local_row = row; + result = png_safe_execute(image, png_image_read_and_map, display); + display->local_row = NULL; + png_free(png_ptr, row); + + return result; + } + + else + { + png_alloc_size_t row_bytes = (png_alloc_size_t)display->row_bytes; + + while (--passes >= 0) + { + png_uint_32 y = image->height; + png_bytep row = png_voidcast(png_bytep, display->first_row); + + for (; y > 0; --y) + { + png_read_row(png_ptr, row, NULL); + row += row_bytes; + } + } + + return 1; + } +} + +/* Just the row reading part of png_image_read. */ +static int +png_image_read_composite(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + int passes; + + switch (png_ptr->interlaced) + { + case PNG_INTERLACE_NONE: + passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + passes = PNG_INTERLACE_ADAM7_PASSES; + break; + + default: + png_error(png_ptr, "unknown interlace type"); + } + + { + png_uint_32 height = image->height; + png_uint_32 width = image->width; + ptrdiff_t step_row = display->row_bytes; + unsigned int channels = + (image->format & PNG_FORMAT_FLAG_COLOR) != 0 ? 3 : 1; + int pass; + + for (pass = 0; pass < passes; ++pass) + { + unsigned int startx, stepx, stepy; + png_uint_32 y; + + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass) * channels; + stepx = PNG_PASS_COL_OFFSET(pass) * channels; + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = channels; + stepy = 1; + } + + for (; ylocal_row); + png_bytep outrow; + png_const_bytep end_row; + + /* Read the row, which is packed: */ + png_read_row(png_ptr, inrow, NULL); + + outrow = png_voidcast(png_bytep, display->first_row); + outrow += y * step_row; + end_row = outrow + width * channels; + + /* Now do the composition on each pixel in this row. */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_byte alpha = inrow[channels]; + + if (alpha > 0) /* else no change to the output */ + { + unsigned int c; + + for (c=0; cimage; + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + png_uint_32 height = image->height; + png_uint_32 width = image->width; + int pass, passes; + + /* Double check the convoluted logic below. We expect to get here with + * libpng doing rgb to gray and gamma correction but background processing + * left to the png_image_read_background function. The rows libpng produce + * might be 8 or 16-bit but should always have two channels; gray plus alpha. + */ + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) == 0) + png_error(png_ptr, "lost rgb to gray"); + + if ((png_ptr->transformations & PNG_COMPOSE) != 0) + png_error(png_ptr, "unexpected compose"); + + if (png_get_channels(png_ptr, info_ptr) != 2) + png_error(png_ptr, "lost/gained channels"); + + /* Expect the 8-bit case to always remove the alpha channel */ + if ((image->format & PNG_FORMAT_FLAG_LINEAR) == 0 && + (image->format & PNG_FORMAT_FLAG_ALPHA) != 0) + png_error(png_ptr, "unexpected 8-bit transformation"); + + switch (png_ptr->interlaced) + { + case PNG_INTERLACE_NONE: + passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + passes = PNG_INTERLACE_ADAM7_PASSES; + break; + + default: + png_error(png_ptr, "unknown interlace type"); + } + + /* Use direct access to info_ptr here because otherwise the simplified API + * would require PNG_EASY_ACCESS_SUPPORTED (just for this.) Note this is + * checking the value after libpng expansions, not the original value in the + * PNG. + */ + switch (info_ptr->bit_depth) + { + case 8: + /* 8-bit sRGB gray values with an alpha channel; the alpha channel is + * to be removed by composing on a background: either the row if + * display->background is NULL or display->background->green if not. + * Unlike the code above ALPHA_OPTIMIZED has *not* been done. + */ + { + png_bytep first_row = png_voidcast(png_bytep, display->first_row); + ptrdiff_t step_row = display->row_bytes; + + for (pass = 0; pass < passes; ++pass) + { + png_bytep row = png_voidcast(png_bytep, display->first_row); + unsigned int startx, stepx, stepy; + png_uint_32 y; + + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass); + stepx = PNG_PASS_COL_OFFSET(pass); + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = stepy = 1; + } + + if (display->background == NULL) + { + for (; ylocal_row); + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; + + /* Read the row, which is packed: */ + png_read_row(png_ptr, inrow, NULL); + + /* Now do the composition on each pixel in this row. */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_byte alpha = inrow[1]; + + if (alpha > 0) /* else no change to the output */ + { + png_uint_32 component = inrow[0]; + + if (alpha < 255) /* else just use component */ + { + /* Since PNG_OPTIMIZED_ALPHA was not set it is + * necessary to invert the sRGB transfer + * function and multiply the alpha out. + */ + component = png_sRGB_table[component] * alpha; + component += png_sRGB_table[outrow[0]] * + (255-alpha); + component = PNG_sRGB_FROM_LINEAR(component); + } + + outrow[0] = (png_byte)component; + } + + inrow += 2; /* gray and alpha channel */ + } + } + } + + else /* constant background value */ + { + png_byte background8 = display->background->green; + png_uint_16 background = png_sRGB_table[background8]; + + for (; ylocal_row); + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; + + /* Read the row, which is packed: */ + png_read_row(png_ptr, inrow, NULL); + + /* Now do the composition on each pixel in this row. */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_byte alpha = inrow[1]; + + if (alpha > 0) /* else use background */ + { + png_uint_32 component = inrow[0]; + + if (alpha < 255) /* else just use component */ + { + component = png_sRGB_table[component] * alpha; + component += background * (255-alpha); + component = PNG_sRGB_FROM_LINEAR(component); + } + + outrow[0] = (png_byte)component; + } + + else + outrow[0] = background8; + + inrow += 2; /* gray and alpha channel */ + } + + row += display->row_bytes; + } + } + } + } + break; + + case 16: + /* 16-bit linear with pre-multiplied alpha; the pre-multiplication must + * still be done and, maybe, the alpha channel removed. This code also + * handles the alpha-first option. + */ + { + png_uint_16p first_row = png_voidcast(png_uint_16p, + display->first_row); + /* The division by two is safe because the caller passed in a + * stride which was multiplied by 2 (below) to get row_bytes. + */ + ptrdiff_t step_row = display->row_bytes / 2; + unsigned int preserve_alpha = (image->format & + PNG_FORMAT_FLAG_ALPHA) != 0; + unsigned int outchannels = 1U+preserve_alpha; + int swap_alpha = 0; + +# ifdef PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED + if (preserve_alpha != 0 && + (image->format & PNG_FORMAT_FLAG_AFIRST) != 0) + swap_alpha = 1; +# endif + + for (pass = 0; pass < passes; ++pass) + { + unsigned int startx, stepx, stepy; + png_uint_32 y; + + /* The 'x' start and step are adjusted to output components here. + */ + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass) * outchannels; + stepx = PNG_PASS_COL_OFFSET(pass) * outchannels; + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = outchannels; + stepy = 1; + } + + for (; ylocal_row), NULL); + inrow = png_voidcast(png_const_uint_16p, display->local_row); + + /* Now do the pre-multiplication on each pixel in this row. + */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_uint_32 component = inrow[0]; + png_uint_16 alpha = inrow[1]; + + if (alpha > 0) /* else 0 */ + { + if (alpha < 65535) /* else just use component */ + { + component *= alpha; + component += 32767; + component /= 65535; + } + } + + else + component = 0; + + outrow[swap_alpha] = (png_uint_16)component; + if (preserve_alpha != 0) + outrow[1 ^ swap_alpha] = alpha; + + inrow += 2; /* components and alpha channel */ + } + } + } + } + break; + +#ifdef __GNUC__ + default: + png_error(png_ptr, "unexpected bit depth"); +#endif + } + + return 1; +} + +/* The guts of png_image_finish_read as a png_safe_execute callback. */ +static int +png_image_read_direct(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + + png_uint_32 format = image->format; + int linear = (format & PNG_FORMAT_FLAG_LINEAR) != 0; + int do_local_compose = 0; + int do_local_background = 0; /* to avoid double gamma correction bug */ + int passes = 0; + + /* Add transforms to ensure the correct output format is produced then check + * that the required implementation support is there. Always expand; always + * need 8 bits minimum, no palette and expanded tRNS. + */ + png_set_expand(png_ptr); + + /* Now check the format to see if it was modified. */ + { + png_uint_32 base_format = png_image_format(png_ptr) & + ~PNG_FORMAT_FLAG_COLORMAP /* removed by png_set_expand */; + png_uint_32 change = format ^ base_format; + png_fixed_point output_gamma; + int mode; /* alpha mode */ + + /* Do this first so that we have a record if rgb to gray is happening. */ + if ((change & PNG_FORMAT_FLAG_COLOR) != 0) + { + /* gray<->color transformation required. */ + if ((format & PNG_FORMAT_FLAG_COLOR) != 0) + png_set_gray_to_rgb(png_ptr); + + else + { + /* libpng can't do both rgb to gray and + * background/pre-multiplication if there is also significant gamma + * correction, because both operations require linear colors and + * the code only supports one transform doing the gamma correction. + * Handle this by doing the pre-multiplication or background + * operation in this code, if necessary. + * + * TODO: fix this by rewriting pngrtran.c (!) + * + * For the moment (given that fixing this in pngrtran.c is an + * enormous change) 'do_local_background' is used to indicate that + * the problem exists. + */ + if ((base_format & PNG_FORMAT_FLAG_ALPHA) != 0) + do_local_background = 1/*maybe*/; + + png_set_rgb_to_gray_fixed(png_ptr, PNG_ERROR_ACTION_NONE, + PNG_RGB_TO_GRAY_DEFAULT, PNG_RGB_TO_GRAY_DEFAULT); + } + + change &= ~PNG_FORMAT_FLAG_COLOR; + } + + /* Set the gamma appropriately, linear for 16-bit input, sRGB otherwise. + */ + { + png_fixed_point input_gamma_default; + + if ((base_format & PNG_FORMAT_FLAG_LINEAR) != 0 && + (image->flags & PNG_IMAGE_FLAG_16BIT_sRGB) == 0) + input_gamma_default = PNG_GAMMA_LINEAR; + else + input_gamma_default = PNG_DEFAULT_sRGB; + + /* Call png_set_alpha_mode to set the default for the input gamma; the + * output gamma is set by a second call below. + */ + png_set_alpha_mode_fixed(png_ptr, PNG_ALPHA_PNG, input_gamma_default); + } + + if (linear != 0) + { + /* If there *is* an alpha channel in the input it must be multiplied + * out; use PNG_ALPHA_STANDARD, otherwise just use PNG_ALPHA_PNG. + */ + if ((base_format & PNG_FORMAT_FLAG_ALPHA) != 0) + mode = PNG_ALPHA_STANDARD; /* associated alpha */ + + else + mode = PNG_ALPHA_PNG; + + output_gamma = PNG_GAMMA_LINEAR; + } + + else + { + mode = PNG_ALPHA_PNG; + output_gamma = PNG_DEFAULT_sRGB; + } + + if ((change & PNG_FORMAT_FLAG_ASSOCIATED_ALPHA) != 0) + { + mode = PNG_ALPHA_OPTIMIZED; + change &= ~PNG_FORMAT_FLAG_ASSOCIATED_ALPHA; + } + + /* If 'do_local_background' is set check for the presence of gamma + * correction; this is part of the work-round for the libpng bug + * described above. + * + * TODO: fix libpng and remove this. + */ + if (do_local_background != 0) + { + png_fixed_point gtest; + + /* This is 'png_gamma_threshold' from pngrtran.c; the test used for + * gamma correction, the screen gamma hasn't been set on png_struct + * yet; it's set below. png_struct::gamma, however, is set to the + * final value. + */ + if (png_muldiv(>est, output_gamma, png_ptr->colorspace.gamma, + PNG_FP_1) != 0 && png_gamma_significant(gtest) == 0) + do_local_background = 0; + + else if (mode == PNG_ALPHA_STANDARD) + { + do_local_background = 2/*required*/; + mode = PNG_ALPHA_PNG; /* prevent libpng doing it */ + } + + /* else leave as 1 for the checks below */ + } + + /* If the bit-depth changes then handle that here. */ + if ((change & PNG_FORMAT_FLAG_LINEAR) != 0) + { + if (linear != 0 /*16-bit output*/) + png_set_expand_16(png_ptr); + + else /* 8-bit output */ + png_set_scale_16(png_ptr); + + change &= ~PNG_FORMAT_FLAG_LINEAR; + } + + /* Now the background/alpha channel changes. */ + if ((change & PNG_FORMAT_FLAG_ALPHA) != 0) + { + /* Removing an alpha channel requires composition for the 8-bit + * formats; for the 16-bit it is already done, above, by the + * pre-multiplication and the channel just needs to be stripped. + */ + if ((base_format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + /* If RGB->gray is happening the alpha channel must be left and the + * operation completed locally. + * + * TODO: fix libpng and remove this. + */ + if (do_local_background != 0) + do_local_background = 2/*required*/; + + /* 16-bit output: just remove the channel */ + else if (linear != 0) /* compose on black (well, pre-multiply) */ + png_set_strip_alpha(png_ptr); + + /* 8-bit output: do an appropriate compose */ + else if (display->background != NULL) + { + png_color_16 c; + + c.index = 0; /*unused*/ + c.red = display->background->red; + c.green = display->background->green; + c.blue = display->background->blue; + c.gray = display->background->green; + + /* This is always an 8-bit sRGB value, using the 'green' channel + * for gray is much better than calculating the luminance here; + * we can get off-by-one errors in that calculation relative to + * the app expectations and that will show up in transparent + * pixels. + */ + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + } + + else /* compose on row: implemented below. */ + { + do_local_compose = 1; + /* This leaves the alpha channel in the output, so it has to be + * removed by the code below. Set the encoding to the 'OPTIMIZE' + * one so the code only has to hack on the pixels that require + * composition. + */ + mode = PNG_ALPHA_OPTIMIZED; + } + } + + else /* output needs an alpha channel */ + { + /* This is tricky because it happens before the swap operation has + * been accomplished; however, the swap does *not* swap the added + * alpha channel (weird API), so it must be added in the correct + * place. + */ + png_uint_32 filler; /* opaque filler */ + int where; + + if (linear != 0) + filler = 65535; + + else + filler = 255; + +#ifdef PNG_FORMAT_AFIRST_SUPPORTED + if ((format & PNG_FORMAT_FLAG_AFIRST) != 0) + { + where = PNG_FILLER_BEFORE; + change &= ~PNG_FORMAT_FLAG_AFIRST; + } + + else +#endif + where = PNG_FILLER_AFTER; + + png_set_add_alpha(png_ptr, filler, where); + } + + /* This stops the (irrelevant) call to swap_alpha below. */ + change &= ~PNG_FORMAT_FLAG_ALPHA; + } + + /* Now set the alpha mode correctly; this is always done, even if there is + * no alpha channel in either the input or the output because it correctly + * sets the output gamma. + */ + png_set_alpha_mode_fixed(png_ptr, mode, output_gamma); + +# ifdef PNG_FORMAT_BGR_SUPPORTED + if ((change & PNG_FORMAT_FLAG_BGR) != 0) + { + /* Check only the output format; PNG is never BGR; don't do this if + * the output is gray, but fix up the 'format' value in that case. + */ + if ((format & PNG_FORMAT_FLAG_COLOR) != 0) + png_set_bgr(png_ptr); + + else + format &= ~PNG_FORMAT_FLAG_BGR; + + change &= ~PNG_FORMAT_FLAG_BGR; + } +# endif + +# ifdef PNG_FORMAT_AFIRST_SUPPORTED + if ((change & PNG_FORMAT_FLAG_AFIRST) != 0) + { + /* Only relevant if there is an alpha channel - it's particularly + * important to handle this correctly because do_local_compose may + * be set above and then libpng will keep the alpha channel for this + * code to remove. + */ + if ((format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + /* Disable this if doing a local background, + * TODO: remove this when local background is no longer required. + */ + if (do_local_background != 2) + png_set_swap_alpha(png_ptr); + } + + else + format &= ~PNG_FORMAT_FLAG_AFIRST; + + change &= ~PNG_FORMAT_FLAG_AFIRST; + } +# endif + + /* If the *output* is 16-bit then we need to check for a byte-swap on this + * architecture. + */ + if (linear != 0) + { + png_uint_16 le = 0x0001; + + if ((*(png_const_bytep) & le) != 0) + png_set_swap(png_ptr); + } + + /* If change is not now 0 some transformation is missing - error out. */ + if (change != 0) + png_error(png_ptr, "png_read_image: unsupported transformation"); + } + + PNG_SKIP_CHUNKS(png_ptr); + + /* Update the 'info' structure and make sure the result is as required; first + * make sure to turn on the interlace handling if it will be required + * (because it can't be turned on *after* the call to png_read_update_info!) + * + * TODO: remove the do_local_background fixup below. + */ + if (do_local_compose == 0 && do_local_background != 2) + passes = png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + { + png_uint_32 info_format = 0; + + if ((info_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0) + info_format |= PNG_FORMAT_FLAG_COLOR; + + if ((info_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0) + { + /* do_local_compose removes this channel below. */ + if (do_local_compose == 0) + { + /* do_local_background does the same if required. */ + if (do_local_background != 2 || + (format & PNG_FORMAT_FLAG_ALPHA) != 0) + info_format |= PNG_FORMAT_FLAG_ALPHA; + } + } + + else if (do_local_compose != 0) /* internal error */ + png_error(png_ptr, "png_image_read: alpha channel lost"); + + if ((format & PNG_FORMAT_FLAG_ASSOCIATED_ALPHA) != 0) { + info_format |= PNG_FORMAT_FLAG_ASSOCIATED_ALPHA; + } + + if (info_ptr->bit_depth == 16) + info_format |= PNG_FORMAT_FLAG_LINEAR; + +#ifdef PNG_FORMAT_BGR_SUPPORTED + if ((png_ptr->transformations & PNG_BGR) != 0) + info_format |= PNG_FORMAT_FLAG_BGR; +#endif + +#ifdef PNG_FORMAT_AFIRST_SUPPORTED + if (do_local_background == 2) + { + if ((format & PNG_FORMAT_FLAG_AFIRST) != 0) + info_format |= PNG_FORMAT_FLAG_AFIRST; + } + + if ((png_ptr->transformations & PNG_SWAP_ALPHA) != 0 || + ((png_ptr->transformations & PNG_ADD_ALPHA) != 0 && + (png_ptr->flags & PNG_FLAG_FILLER_AFTER) == 0)) + { + if (do_local_background == 2) + png_error(png_ptr, "unexpected alpha swap transformation"); + + info_format |= PNG_FORMAT_FLAG_AFIRST; + } +# endif + + /* This is actually an internal error. */ + if (info_format != format) + png_error(png_ptr, "png_read_image: invalid transformations"); + } + + /* Now read the rows. If do_local_compose is set then it is necessary to use + * a local row buffer. The output will be GA, RGBA or BGRA and must be + * converted to G, RGB or BGR as appropriate. The 'local_row' member of the + * display acts as a flag. + */ + { + png_voidp first_row = display->buffer; + ptrdiff_t row_bytes = display->row_stride; + + if (linear != 0) + row_bytes *= 2; + + /* The following expression is designed to work correctly whether it gives + * a signed or an unsigned result. + */ + if (row_bytes < 0) + { + char *ptr = png_voidcast(char*, first_row); + ptr += (image->height-1) * (-row_bytes); + first_row = png_voidcast(png_voidp, ptr); + } + + display->first_row = first_row; + display->row_bytes = row_bytes; + } + + if (do_local_compose != 0) + { + int result; + png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr)); + + display->local_row = row; + result = png_safe_execute(image, png_image_read_composite, display); + display->local_row = NULL; + png_free(png_ptr, row); + + return result; + } + + else if (do_local_background == 2) + { + int result; + png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr)); + + display->local_row = row; + result = png_safe_execute(image, png_image_read_background, display); + display->local_row = NULL; + png_free(png_ptr, row); + + return result; + } + + else + { + png_alloc_size_t row_bytes = (png_alloc_size_t)display->row_bytes; + + while (--passes >= 0) + { + png_uint_32 y = image->height; + png_bytep row = png_voidcast(png_bytep, display->first_row); + + for (; y > 0; --y) + { + png_read_row(png_ptr, row, NULL); + row += row_bytes; + } + } + + return 1; + } +} + +int PNGAPI +png_image_finish_read(png_imagep image, png_const_colorp background, + void *buffer, png_int_32 row_stride, void *colormap) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + /* Check for row_stride overflow. This check is not performed on the + * original PNG format because it may not occur in the output PNG format + * and libpng deals with the issues of reading the original. + */ + unsigned int channels = PNG_IMAGE_PIXEL_CHANNELS(image->format); + + /* The following checks just the 'row_stride' calculation to ensure it + * fits in a signed 32-bit value. Because channels/components can be + * either 1 or 2 bytes in size the length of a row can still overflow 32 + * bits; this is just to verify that the 'row_stride' argument can be + * represented. + */ + if (image->width <= 0x7fffffffU/channels) /* no overflow */ + { + png_uint_32 check; + png_uint_32 png_row_stride = image->width * channels; + + if (row_stride == 0) + row_stride = (png_int_32)/*SAFE*/png_row_stride; + + if (row_stride < 0) + check = (png_uint_32)(-row_stride); + + else + check = (png_uint_32)row_stride; + + /* This verifies 'check', the absolute value of the actual stride + * passed in and detects overflow in the application calculation (i.e. + * if the app did actually pass in a non-zero 'row_stride'. + */ + if (image->opaque != NULL && buffer != NULL && check >= png_row_stride) + { + /* Now check for overflow of the image buffer calculation; this + * limits the whole image size to 32 bits for API compatibility with + * the current, 32-bit, PNG_IMAGE_BUFFER_SIZE macro. + * + * The PNG_IMAGE_BUFFER_SIZE macro is: + * + * (PNG_IMAGE_PIXEL_COMPONENT_SIZE(fmt)*height*(row_stride)) + * + * And the component size is always 1 or 2, so make sure that the + * number of *bytes* that the application is saying are available + * does actually fit into a 32-bit number. + * + * NOTE: this will be changed in 1.7 because PNG_IMAGE_BUFFER_SIZE + * will be changed to use png_alloc_size_t; bigger images can be + * accommodated on 64-bit systems. + */ + if (image->height <= + 0xffffffffU/PNG_IMAGE_PIXEL_COMPONENT_SIZE(image->format)/check) + { + if ((image->format & PNG_FORMAT_FLAG_COLORMAP) == 0 || + (image->colormap_entries > 0 && colormap != NULL)) + { + int result; + png_image_read_control display; + + memset(&display, 0, (sizeof display)); + display.image = image; + display.buffer = buffer; + display.row_stride = row_stride; + display.colormap = colormap; + display.background = background; + display.local_row = NULL; + + /* Choose the correct 'end' routine; for the color-map case + * all the setup has already been done. + */ + if ((image->format & PNG_FORMAT_FLAG_COLORMAP) != 0) + result = + png_safe_execute(image, + png_image_read_colormap, &display) && + png_safe_execute(image, + png_image_read_colormapped, &display); + + else + result = + png_safe_execute(image, + png_image_read_direct, &display); + + png_image_free(image); + return result; + } + + else + return png_image_error(image, + "png_image_finish_read[color-map]: no color-map"); + } + + else + return png_image_error(image, + "png_image_finish_read: image too large"); + } + + else + return png_image_error(image, + "png_image_finish_read: invalid argument"); + } + + else + return png_image_error(image, + "png_image_finish_read: row_stride too large"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_finish_read: damaged PNG_IMAGE_VERSION"); + + return 0; +} + +#endif /* SIMPLIFIED_READ */ +#endif /* READ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrio.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrio.c new file mode 100644 index 0000000..7946358 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrio.c @@ -0,0 +1,120 @@ + +/* pngrio.c - functions for data input + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all input. Users who need + * special handling are expected to write a function that has the same + * arguments as this and performs a similar function, but that possibly + * has a different input method. Note that you shouldn't change this + * function, but rather write a replacement function and then make + * libpng use it at run time with png_set_read_fn(...). + */ + +#include "pngpriv.h" + +#ifdef PNG_READ_SUPPORTED + +/* Read the data from whatever input you are using. The default routine + * reads from a file pointer. Note that this routine sometimes gets called + * with very small lengths, so you should implement some kind of simple + * buffering if you are using unbuffered reads. This should never be asked + * to read more than 64K on a 16-bit machine. + */ +void /* PRIVATE */ +png_read_data(png_structrp png_ptr, png_bytep data, size_t length) +{ + png_debug1(4, "reading %d bytes", (int)length); + + if (png_ptr->read_data_fn != NULL) + (*(png_ptr->read_data_fn))(png_ptr, data, length); + + else + png_error(png_ptr, "Call to NULL read function"); +} + +#ifdef PNG_STDIO_SUPPORTED +/* This is the function that does the actual reading of data. If you are + * not reading from a standard C stream, you should create a replacement + * read_data function and use it at run time with png_set_read_fn(), rather + * than changing the library. + */ +void PNGCBAPI +png_default_read_data(png_structp png_ptr, png_bytep data, size_t length) +{ + size_t check; + + if (png_ptr == NULL) + return; + + /* fread() returns 0 on error, so it is OK to store this in a size_t + * instead of an int, which is what fread() actually returns. + */ + check = fread(data, 1, length, png_voidcast(png_FILE_p, png_ptr->io_ptr)); + + if (check != length) + png_error(png_ptr, "Read Error"); +} +#endif + +/* This function allows the application to supply a new input function + * for libpng if standard C streams aren't being used. + * + * This function takes as its arguments: + * + * png_ptr - pointer to a png input data structure + * + * io_ptr - pointer to user supplied structure containing info about + * the input functions. May be NULL. + * + * read_data_fn - pointer to a new input function that takes as its + * arguments a pointer to a png_struct, a pointer to + * a location where input data can be stored, and a 32-bit + * unsigned int that is the number of bytes to be read. + * To exit and output any fatal error messages the new write + * function should call png_error(png_ptr, "Error msg"). + * May be NULL, in which case libpng's default function will + * be used. + */ +void PNGAPI +png_set_read_fn(png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr read_data_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->io_ptr = io_ptr; + +#ifdef PNG_STDIO_SUPPORTED + if (read_data_fn != NULL) + png_ptr->read_data_fn = read_data_fn; + + else + png_ptr->read_data_fn = png_default_read_data; +#else + png_ptr->read_data_fn = read_data_fn; +#endif + +#ifdef PNG_WRITE_SUPPORTED + /* It is an error to write to a read device */ + if (png_ptr->write_data_fn != NULL) + { + png_ptr->write_data_fn = NULL; + png_warning(png_ptr, + "Can't set both read_data_fn and write_data_fn in the" + " same structure"); + } +#endif + +#ifdef PNG_WRITE_FLUSH_SUPPORTED + png_ptr->output_flush_fn = NULL; +#endif +} +#endif /* READ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrtran.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrtran.c new file mode 100644 index 0000000..1725631 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrtran.c @@ -0,0 +1,5044 @@ + +/* pngrtran.c - transforms the data in a row for PNG readers + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file contains functions optionally called by an application + * in order to tell libpng how to handle data when reading a PNG. + * Transformations that are used in both reading and writing are + * in pngtrans.c. + */ + +#include "pngpriv.h" + +#ifdef PNG_ARM_NEON_IMPLEMENTATION +# if PNG_ARM_NEON_IMPLEMENTATION == 1 +# define PNG_ARM_NEON_INTRINSICS_AVAILABLE +# if defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC)) +# include +# else +# include +# endif +# endif +#endif + +#ifdef PNG_READ_SUPPORTED + +/* Set the action on getting a CRC error for an ancillary or critical chunk. */ +void PNGAPI +png_set_crc_action(png_structrp png_ptr, int crit_action, int ancil_action) +{ + png_debug(1, "in png_set_crc_action"); + + if (png_ptr == NULL) + return; + + /* Tell libpng how we react to CRC errors in critical chunks */ + switch (crit_action) + { + case PNG_CRC_NO_CHANGE: /* Leave setting as is */ + break; + + case PNG_CRC_WARN_USE: /* Warn/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK; + png_ptr->flags |= PNG_FLAG_CRC_CRITICAL_USE; + break; + + case PNG_CRC_QUIET_USE: /* Quiet/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK; + png_ptr->flags |= PNG_FLAG_CRC_CRITICAL_USE | + PNG_FLAG_CRC_CRITICAL_IGNORE; + break; + + case PNG_CRC_WARN_DISCARD: /* Not a valid action for critical data */ + png_warning(png_ptr, + "Can't discard critical data on CRC error"); + /* FALLTHROUGH */ + case PNG_CRC_ERROR_QUIT: /* Error/quit */ + + case PNG_CRC_DEFAULT: + default: + png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK; + break; + } + + /* Tell libpng how we react to CRC errors in ancillary chunks */ + switch (ancil_action) + { + case PNG_CRC_NO_CHANGE: /* Leave setting as is */ + break; + + case PNG_CRC_WARN_USE: /* Warn/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_USE; + break; + + case PNG_CRC_QUIET_USE: /* Quiet/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_USE | + PNG_FLAG_CRC_ANCILLARY_NOWARN; + break; + + case PNG_CRC_ERROR_QUIT: /* Error/quit */ + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_NOWARN; + break; + + case PNG_CRC_WARN_DISCARD: /* Warn/discard data */ + + case PNG_CRC_DEFAULT: + default: + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + break; + } +} + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +/* Is it OK to set a transformation now? Only if png_start_read_image or + * png_read_update_info have not been called. It is not necessary for the IHDR + * to have been read in all cases; the need_IHDR parameter allows for this + * check too. + */ +static int +png_rtran_ok(png_structrp png_ptr, int need_IHDR) +{ + if (png_ptr != NULL) + { + if ((png_ptr->flags & PNG_FLAG_ROW_INIT) != 0) + png_app_error(png_ptr, + "invalid after png_start_read_image or png_read_update_info"); + + else if (need_IHDR && (png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_app_error(png_ptr, "invalid before the PNG header has been read"); + + else + { + /* Turn on failure to initialize correctly for all transforms. */ + png_ptr->flags |= PNG_FLAG_DETECT_UNINITIALIZED; + + return 1; /* Ok */ + } + } + + return 0; /* no png_error possible! */ +} +#endif + +#ifdef PNG_READ_BACKGROUND_SUPPORTED +/* Handle alpha and tRNS via a background color */ +void PNGFAPI +png_set_background_fixed(png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, png_fixed_point background_gamma) +{ + png_debug(1, "in png_set_background_fixed"); + + if (png_rtran_ok(png_ptr, 0) == 0 || background_color == NULL) + return; + + if (background_gamma_code == PNG_BACKGROUND_GAMMA_UNKNOWN) + { + png_warning(png_ptr, "Application must supply a known background gamma"); + return; + } + + png_ptr->transformations |= PNG_COMPOSE | PNG_STRIP_ALPHA; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + png_ptr->background = *background_color; + png_ptr->background_gamma = background_gamma; + png_ptr->background_gamma_type = (png_byte)(background_gamma_code); + if (need_expand != 0) + png_ptr->transformations |= PNG_BACKGROUND_EXPAND; + else + png_ptr->transformations &= ~PNG_BACKGROUND_EXPAND; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_background(png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, double background_gamma) +{ + png_set_background_fixed(png_ptr, background_color, background_gamma_code, + need_expand, png_fixed(png_ptr, background_gamma, "png_set_background")); +} +# endif /* FLOATING_POINT */ +#endif /* READ_BACKGROUND */ + +/* Scale 16-bit depth files to 8-bit depth. If both of these are set then the + * one that pngrtran does first (scale) happens. This is necessary to allow the + * TRANSFORM and API behavior to be somewhat consistent, and it's simpler. + */ +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED +void PNGAPI +png_set_scale_16(png_structrp png_ptr) +{ + png_debug(1, "in png_set_scale_16"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= PNG_SCALE_16_TO_8; +} +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED +/* Chop 16-bit depth files to 8-bit depth */ +void PNGAPI +png_set_strip_16(png_structrp png_ptr) +{ + png_debug(1, "in png_set_strip_16"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= PNG_16_TO_8; +} +#endif + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED +void PNGAPI +png_set_strip_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_strip_alpha"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= PNG_STRIP_ALPHA; +} +#endif + +#if defined(PNG_READ_ALPHA_MODE_SUPPORTED) || defined(PNG_READ_GAMMA_SUPPORTED) +static png_fixed_point +translate_gamma_flags(png_structrp png_ptr, png_fixed_point output_gamma, + int is_screen) +{ + /* Check for flag values. The main reason for having the old Mac value as a + * flag is that it is pretty near impossible to work out what the correct + * value is from Apple documentation - a working Mac system is needed to + * discover the value! + */ + if (output_gamma == PNG_DEFAULT_sRGB || + output_gamma == PNG_FP_1 / PNG_DEFAULT_sRGB) + { + /* If there is no sRGB support this just sets the gamma to the standard + * sRGB value. (This is a side effect of using this function!) + */ +# ifdef PNG_READ_sRGB_SUPPORTED + png_ptr->flags |= PNG_FLAG_ASSUME_sRGB; +# else + PNG_UNUSED(png_ptr) +# endif + if (is_screen != 0) + output_gamma = PNG_GAMMA_sRGB; + else + output_gamma = PNG_GAMMA_sRGB_INVERSE; + } + + else if (output_gamma == PNG_GAMMA_MAC_18 || + output_gamma == PNG_FP_1 / PNG_GAMMA_MAC_18) + { + if (is_screen != 0) + output_gamma = PNG_GAMMA_MAC_OLD; + else + output_gamma = PNG_GAMMA_MAC_INVERSE; + } + + return output_gamma; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +static png_fixed_point +convert_gamma_value(png_structrp png_ptr, double output_gamma) +{ + /* The following silently ignores cases where fixed point (times 100,000) + * gamma values are passed to the floating point API. This is safe and it + * means the fixed point constants work just fine with the floating point + * API. The alternative would just lead to undetected errors and spurious + * bug reports. Negative values fail inside the _fixed API unless they + * correspond to the flag values. + */ + if (output_gamma > 0 && output_gamma < 128) + output_gamma *= PNG_FP_1; + + /* This preserves -1 and -2 exactly: */ + output_gamma = floor(output_gamma + .5); + + if (output_gamma > PNG_FP_MAX || output_gamma < PNG_FP_MIN) + png_fixed_error(png_ptr, "gamma value"); + + return (png_fixed_point)output_gamma; +} +# endif +#endif /* READ_ALPHA_MODE || READ_GAMMA */ + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +void PNGFAPI +png_set_alpha_mode_fixed(png_structrp png_ptr, int mode, + png_fixed_point output_gamma) +{ + int compose = 0; + png_fixed_point file_gamma; + + png_debug(1, "in png_set_alpha_mode"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + output_gamma = translate_gamma_flags(png_ptr, output_gamma, 1/*screen*/); + + /* Validate the value to ensure it is in a reasonable range. The value + * is expected to be 1 or greater, but this range test allows for some + * viewing correction values. The intent is to weed out users of this API + * who use the inverse of the gamma value accidentally! Since some of these + * values are reasonable this may have to be changed: + * + * 1.6.x: changed from 0.07..3 to 0.01..100 (to accommodate the optimal 16-bit + * gamma of 36, and its reciprocal.) + */ + if (output_gamma < 1000 || output_gamma > 10000000) + png_error(png_ptr, "output gamma out of expected range"); + + /* The default file gamma is the inverse of the output gamma; the output + * gamma may be changed below so get the file value first: + */ + file_gamma = png_reciprocal(output_gamma); + + /* There are really 8 possibilities here, composed of any combination + * of: + * + * premultiply the color channels + * do not encode non-opaque pixels + * encode the alpha as well as the color channels + * + * The differences disappear if the input/output ('screen') gamma is 1.0, + * because then the encoding is a no-op and there is only the choice of + * premultiplying the color channels or not. + * + * png_set_alpha_mode and png_set_background interact because both use + * png_compose to do the work. Calling both is only useful when + * png_set_alpha_mode is used to set the default mode - PNG_ALPHA_PNG - along + * with a default gamma value. Otherwise PNG_COMPOSE must not be set. + */ + switch (mode) + { + case PNG_ALPHA_PNG: /* default: png standard */ + /* No compose, but it may be set by png_set_background! */ + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + break; + + case PNG_ALPHA_ASSOCIATED: /* color channels premultiplied */ + compose = 1; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + /* The output is linear: */ + output_gamma = PNG_FP_1; + break; + + case PNG_ALPHA_OPTIMIZED: /* associated, non-opaque pixels linear */ + compose = 1; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags |= PNG_FLAG_OPTIMIZE_ALPHA; + /* output_gamma records the encoding of opaque pixels! */ + break; + + case PNG_ALPHA_BROKEN: /* associated, non-linear, alpha encoded */ + compose = 1; + png_ptr->transformations |= PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + break; + + default: + png_error(png_ptr, "invalid alpha mode"); + } + + /* Only set the default gamma if the file gamma has not been set (this has + * the side effect that the gamma in a second call to png_set_alpha_mode will + * be ignored.) + */ + if (png_ptr->colorspace.gamma == 0) + { + png_ptr->colorspace.gamma = file_gamma; + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + } + + /* But always set the output gamma: */ + png_ptr->screen_gamma = output_gamma; + + /* Finally, if pre-multiplying, set the background fields to achieve the + * desired result. + */ + if (compose != 0) + { + /* And obtain alpha pre-multiplication by composing on black: */ + memset(&png_ptr->background, 0, (sizeof png_ptr->background)); + png_ptr->background_gamma = png_ptr->colorspace.gamma; /* just in case */ + png_ptr->background_gamma_type = PNG_BACKGROUND_GAMMA_FILE; + png_ptr->transformations &= ~PNG_BACKGROUND_EXPAND; + + if ((png_ptr->transformations & PNG_COMPOSE) != 0) + png_error(png_ptr, + "conflicting calls to set alpha mode and background"); + + png_ptr->transformations |= PNG_COMPOSE; + } +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_alpha_mode(png_structrp png_ptr, int mode, double output_gamma) +{ + png_set_alpha_mode_fixed(png_ptr, mode, convert_gamma_value(png_ptr, + output_gamma)); +} +# endif +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +/* Dither file to 8-bit. Supply a palette, the current number + * of elements in the palette, the maximum number of elements + * allowed, and a histogram if possible. If the current number + * of colors is greater than the maximum number, the palette will be + * modified to fit in the maximum number. "full_quantize" indicates + * whether we need a quantizing cube set up for RGB images, or if we + * simply are reducing the number of colors in a paletted image. + */ + +typedef struct png_dsort_struct +{ + struct png_dsort_struct * next; + png_byte left; + png_byte right; +} png_dsort; +typedef png_dsort * png_dsortp; +typedef png_dsort * * png_dsortpp; + +void PNGAPI +png_set_quantize(png_structrp png_ptr, png_colorp palette, + int num_palette, int maximum_colors, png_const_uint_16p histogram, + int full_quantize) +{ + png_debug(1, "in png_set_quantize"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= PNG_QUANTIZE; + + if (full_quantize == 0) + { + int i; + + png_ptr->quantize_index = (png_bytep)png_malloc(png_ptr, + (png_alloc_size_t)((png_uint_32)num_palette * (sizeof (png_byte)))); + for (i = 0; i < num_palette; i++) + png_ptr->quantize_index[i] = (png_byte)i; + } + + if (num_palette > maximum_colors) + { + if (histogram != NULL) + { + /* This is easy enough, just throw out the least used colors. + * Perhaps not the best solution, but good enough. + */ + + int i; + + /* Initialize an array to sort colors */ + png_ptr->quantize_sort = (png_bytep)png_malloc(png_ptr, + (png_alloc_size_t)((png_uint_32)num_palette * (sizeof (png_byte)))); + + /* Initialize the quantize_sort array */ + for (i = 0; i < num_palette; i++) + png_ptr->quantize_sort[i] = (png_byte)i; + + /* Find the least used palette entries by starting a + * bubble sort, and running it until we have sorted + * out enough colors. Note that we don't care about + * sorting all the colors, just finding which are + * least used. + */ + + for (i = num_palette - 1; i >= maximum_colors; i--) + { + int done; /* To stop early if the list is pre-sorted */ + int j; + + done = 1; + for (j = 0; j < i; j++) + { + if (histogram[png_ptr->quantize_sort[j]] + < histogram[png_ptr->quantize_sort[j + 1]]) + { + png_byte t; + + t = png_ptr->quantize_sort[j]; + png_ptr->quantize_sort[j] = png_ptr->quantize_sort[j + 1]; + png_ptr->quantize_sort[j + 1] = t; + done = 0; + } + } + + if (done != 0) + break; + } + + /* Swap the palette around, and set up a table, if necessary */ + if (full_quantize != 0) + { + int j = num_palette; + + /* Put all the useful colors within the max, but don't + * move the others. + */ + for (i = 0; i < maximum_colors; i++) + { + if ((int)png_ptr->quantize_sort[i] >= maximum_colors) + { + do + j--; + while ((int)png_ptr->quantize_sort[j] >= maximum_colors); + + palette[i] = palette[j]; + } + } + } + else + { + int j = num_palette; + + /* Move all the used colors inside the max limit, and + * develop a translation table. + */ + for (i = 0; i < maximum_colors; i++) + { + /* Only move the colors we need to */ + if ((int)png_ptr->quantize_sort[i] >= maximum_colors) + { + png_color tmp_color; + + do + j--; + while ((int)png_ptr->quantize_sort[j] >= maximum_colors); + + tmp_color = palette[j]; + palette[j] = palette[i]; + palette[i] = tmp_color; + /* Indicate where the color went */ + png_ptr->quantize_index[j] = (png_byte)i; + png_ptr->quantize_index[i] = (png_byte)j; + } + } + + /* Find closest color for those colors we are not using */ + for (i = 0; i < num_palette; i++) + { + if ((int)png_ptr->quantize_index[i] >= maximum_colors) + { + int min_d, k, min_k, d_index; + + /* Find the closest color to one we threw out */ + d_index = png_ptr->quantize_index[i]; + min_d = PNG_COLOR_DIST(palette[d_index], palette[0]); + for (k = 1, min_k = 0; k < maximum_colors; k++) + { + int d; + + d = PNG_COLOR_DIST(palette[d_index], palette[k]); + + if (d < min_d) + { + min_d = d; + min_k = k; + } + } + /* Point to closest color */ + png_ptr->quantize_index[i] = (png_byte)min_k; + } + } + } + png_free(png_ptr, png_ptr->quantize_sort); + png_ptr->quantize_sort = NULL; + } + else + { + /* This is much harder to do simply (and quickly). Perhaps + * we need to go through a median cut routine, but those + * don't always behave themselves with only a few colors + * as input. So we will just find the closest two colors, + * and throw out one of them (chosen somewhat randomly). + * [We don't understand this at all, so if someone wants to + * work on improving it, be our guest - AED, GRP] + */ + int i; + int max_d; + int num_new_palette; + png_dsortp t; + png_dsortpp hash; + + t = NULL; + + /* Initialize palette index arrays */ + png_ptr->index_to_palette = (png_bytep)png_malloc(png_ptr, + (png_alloc_size_t)((png_uint_32)num_palette * + (sizeof (png_byte)))); + png_ptr->palette_to_index = (png_bytep)png_malloc(png_ptr, + (png_alloc_size_t)((png_uint_32)num_palette * + (sizeof (png_byte)))); + + /* Initialize the sort array */ + for (i = 0; i < num_palette; i++) + { + png_ptr->index_to_palette[i] = (png_byte)i; + png_ptr->palette_to_index[i] = (png_byte)i; + } + + hash = (png_dsortpp)png_calloc(png_ptr, (png_alloc_size_t)(769 * + (sizeof (png_dsortp)))); + + num_new_palette = num_palette; + + /* Initial wild guess at how far apart the farthest pixel + * pair we will be eliminating will be. Larger + * numbers mean more areas will be allocated, Smaller + * numbers run the risk of not saving enough data, and + * having to do this all over again. + * + * I have not done extensive checking on this number. + */ + max_d = 96; + + while (num_new_palette > maximum_colors) + { + for (i = 0; i < num_new_palette - 1; i++) + { + int j; + + for (j = i + 1; j < num_new_palette; j++) + { + int d; + + d = PNG_COLOR_DIST(palette[i], palette[j]); + + if (d <= max_d) + { + + t = (png_dsortp)png_malloc_warn(png_ptr, + (png_alloc_size_t)(sizeof (png_dsort))); + + if (t == NULL) + break; + + t->next = hash[d]; + t->left = (png_byte)i; + t->right = (png_byte)j; + hash[d] = t; + } + } + if (t == NULL) + break; + } + + if (t != NULL) + for (i = 0; i <= max_d; i++) + { + if (hash[i] != NULL) + { + png_dsortp p; + + for (p = hash[i]; p; p = p->next) + { + if ((int)png_ptr->index_to_palette[p->left] + < num_new_palette && + (int)png_ptr->index_to_palette[p->right] + < num_new_palette) + { + int j, next_j; + + if (num_new_palette & 0x01) + { + j = p->left; + next_j = p->right; + } + else + { + j = p->right; + next_j = p->left; + } + + num_new_palette--; + palette[png_ptr->index_to_palette[j]] + = palette[num_new_palette]; + if (full_quantize == 0) + { + int k; + + for (k = 0; k < num_palette; k++) + { + if (png_ptr->quantize_index[k] == + png_ptr->index_to_palette[j]) + png_ptr->quantize_index[k] = + png_ptr->index_to_palette[next_j]; + + if ((int)png_ptr->quantize_index[k] == + num_new_palette) + png_ptr->quantize_index[k] = + png_ptr->index_to_palette[j]; + } + } + + png_ptr->index_to_palette[png_ptr->palette_to_index + [num_new_palette]] = png_ptr->index_to_palette[j]; + + png_ptr->palette_to_index[png_ptr->index_to_palette[j]] + = png_ptr->palette_to_index[num_new_palette]; + + png_ptr->index_to_palette[j] = + (png_byte)num_new_palette; + + png_ptr->palette_to_index[num_new_palette] = + (png_byte)j; + } + if (num_new_palette <= maximum_colors) + break; + } + if (num_new_palette <= maximum_colors) + break; + } + } + + for (i = 0; i < 769; i++) + { + if (hash[i] != NULL) + { + png_dsortp p = hash[i]; + while (p) + { + t = p->next; + png_free(png_ptr, p); + p = t; + } + } + hash[i] = 0; + } + max_d += 96; + } + png_free(png_ptr, hash); + png_free(png_ptr, png_ptr->palette_to_index); + png_free(png_ptr, png_ptr->index_to_palette); + png_ptr->palette_to_index = NULL; + png_ptr->index_to_palette = NULL; + } + num_palette = maximum_colors; + } + if (png_ptr->palette == NULL) + { + png_ptr->palette = palette; + } + png_ptr->num_palette = (png_uint_16)num_palette; + + if (full_quantize != 0) + { + int i; + png_bytep distance; + int total_bits = PNG_QUANTIZE_RED_BITS + PNG_QUANTIZE_GREEN_BITS + + PNG_QUANTIZE_BLUE_BITS; + int num_red = (1 << PNG_QUANTIZE_RED_BITS); + int num_green = (1 << PNG_QUANTIZE_GREEN_BITS); + int num_blue = (1 << PNG_QUANTIZE_BLUE_BITS); + size_t num_entries = ((size_t)1 << total_bits); + + png_ptr->palette_lookup = (png_bytep)png_calloc(png_ptr, + (png_alloc_size_t)(num_entries * (sizeof (png_byte)))); + + distance = (png_bytep)png_malloc(png_ptr, (png_alloc_size_t)(num_entries * + (sizeof (png_byte)))); + + memset(distance, 0xff, num_entries * (sizeof (png_byte))); + + for (i = 0; i < num_palette; i++) + { + int ir, ig, ib; + int r = (palette[i].red >> (8 - PNG_QUANTIZE_RED_BITS)); + int g = (palette[i].green >> (8 - PNG_QUANTIZE_GREEN_BITS)); + int b = (palette[i].blue >> (8 - PNG_QUANTIZE_BLUE_BITS)); + + for (ir = 0; ir < num_red; ir++) + { + /* int dr = abs(ir - r); */ + int dr = ((ir > r) ? ir - r : r - ir); + int index_r = (ir << (PNG_QUANTIZE_BLUE_BITS + + PNG_QUANTIZE_GREEN_BITS)); + + for (ig = 0; ig < num_green; ig++) + { + /* int dg = abs(ig - g); */ + int dg = ((ig > g) ? ig - g : g - ig); + int dt = dr + dg; + int dm = ((dr > dg) ? dr : dg); + int index_g = index_r | (ig << PNG_QUANTIZE_BLUE_BITS); + + for (ib = 0; ib < num_blue; ib++) + { + int d_index = index_g | ib; + /* int db = abs(ib - b); */ + int db = ((ib > b) ? ib - b : b - ib); + int dmax = ((dm > db) ? dm : db); + int d = dmax + dt + db; + + if (d < (int)distance[d_index]) + { + distance[d_index] = (png_byte)d; + png_ptr->palette_lookup[d_index] = (png_byte)i; + } + } + } + } + } + + png_free(png_ptr, distance); + } +} +#endif /* READ_QUANTIZE */ + +#ifdef PNG_READ_GAMMA_SUPPORTED +void PNGFAPI +png_set_gamma_fixed(png_structrp png_ptr, png_fixed_point scrn_gamma, + png_fixed_point file_gamma) +{ + png_debug(1, "in png_set_gamma_fixed"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + /* New in libpng-1.5.4 - reserve particular negative values as flags. */ + scrn_gamma = translate_gamma_flags(png_ptr, scrn_gamma, 1/*screen*/); + file_gamma = translate_gamma_flags(png_ptr, file_gamma, 0/*file*/); + + /* Checking the gamma values for being >0 was added in 1.5.4 along with the + * premultiplied alpha support; this actually hides an undocumented feature + * of the previous implementation which allowed gamma processing to be + * disabled in background handling. There is no evidence (so far) that this + * was being used; however, png_set_background itself accepted and must still + * accept '0' for the gamma value it takes, because it isn't always used. + * + * Since this is an API change (albeit a very minor one that removes an + * undocumented API feature) the following checks were only enabled in + * libpng-1.6.0. + */ + if (file_gamma <= 0) + png_error(png_ptr, "invalid file gamma in png_set_gamma"); + + if (scrn_gamma <= 0) + png_error(png_ptr, "invalid screen gamma in png_set_gamma"); + + /* Set the gamma values unconditionally - this overrides the value in the PNG + * file if a gAMA chunk was present. png_set_alpha_mode provides a + * different, easier, way to default the file gamma. + */ + png_ptr->colorspace.gamma = file_gamma; + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + png_ptr->screen_gamma = scrn_gamma; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_gamma(png_structrp png_ptr, double scrn_gamma, double file_gamma) +{ + png_set_gamma_fixed(png_ptr, convert_gamma_value(png_ptr, scrn_gamma), + convert_gamma_value(png_ptr, file_gamma)); +} +# endif /* FLOATING_POINT */ +#endif /* READ_GAMMA */ + +#ifdef PNG_READ_EXPAND_SUPPORTED +/* Expand paletted images to RGB, expand grayscale images of + * less than 8-bit depth to 8-bit depth, and expand tRNS chunks + * to alpha channels. + */ +void PNGAPI +png_set_expand(png_structrp png_ptr) +{ + png_debug(1, "in png_set_expand"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS); +} + +/* GRR 19990627: the following three functions currently are identical + * to png_set_expand(). However, it is entirely reasonable that someone + * might wish to expand an indexed image to RGB but *not* expand a single, + * fully transparent palette entry to a full alpha channel--perhaps instead + * convert tRNS to the grayscale/RGB format (16-bit RGB value), or replace + * the transparent color with a particular RGB value, or drop tRNS entirely. + * IOW, a future version of the library may make the transformations flag + * a bit more fine-grained, with separate bits for each of these three + * functions. + * + * More to the point, these functions make it obvious what libpng will be + * doing, whereas "expand" can (and does) mean any number of things. + * + * GRP 20060307: In libpng-1.2.9, png_set_gray_1_2_4_to_8() was modified + * to expand only the sample depth but not to expand the tRNS to alpha + * and its name was changed to png_set_expand_gray_1_2_4_to_8(). + */ + +/* Expand paletted images to RGB. */ +void PNGAPI +png_set_palette_to_rgb(png_structrp png_ptr) +{ + png_debug(1, "in png_set_palette_to_rgb"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS); +} + +/* Expand grayscale images of less than 8-bit depth to 8 bits. */ +void PNGAPI +png_set_expand_gray_1_2_4_to_8(png_structrp png_ptr) +{ + png_debug(1, "in png_set_expand_gray_1_2_4_to_8"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= PNG_EXPAND; +} + +/* Expand tRNS chunks to alpha channels. */ +void PNGAPI +png_set_tRNS_to_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_tRNS_to_alpha"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS); +} +#endif /* READ_EXPAND */ + +#ifdef PNG_READ_EXPAND_16_SUPPORTED +/* Expand to 16-bit channels, expand the tRNS chunk too (because otherwise + * it may not work correctly.) + */ +void PNGAPI +png_set_expand_16(png_structrp png_ptr) +{ + png_debug(1, "in png_set_expand_16"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + png_ptr->transformations |= (PNG_EXPAND_16 | PNG_EXPAND | PNG_EXPAND_tRNS); +} +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +void PNGAPI +png_set_gray_to_rgb(png_structrp png_ptr) +{ + png_debug(1, "in png_set_gray_to_rgb"); + + if (png_rtran_ok(png_ptr, 0) == 0) + return; + + /* Because rgb must be 8 bits or more: */ + png_set_expand_gray_1_2_4_to_8(png_ptr); + png_ptr->transformations |= PNG_GRAY_TO_RGB; +} +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +void PNGFAPI +png_set_rgb_to_gray_fixed(png_structrp png_ptr, int error_action, + png_fixed_point red, png_fixed_point green) +{ + png_debug(1, "in png_set_rgb_to_gray"); + + /* Need the IHDR here because of the check on color_type below. */ + /* TODO: fix this */ + if (png_rtran_ok(png_ptr, 1) == 0) + return; + + switch (error_action) + { + case PNG_ERROR_ACTION_NONE: + png_ptr->transformations |= PNG_RGB_TO_GRAY; + break; + + case PNG_ERROR_ACTION_WARN: + png_ptr->transformations |= PNG_RGB_TO_GRAY_WARN; + break; + + case PNG_ERROR_ACTION_ERROR: + png_ptr->transformations |= PNG_RGB_TO_GRAY_ERR; + break; + + default: + png_error(png_ptr, "invalid error action to rgb_to_gray"); + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) +#ifdef PNG_READ_EXPAND_SUPPORTED + png_ptr->transformations |= PNG_EXPAND; +#else + { + /* Make this an error in 1.6 because otherwise the application may assume + * that it just worked and get a memory overwrite. + */ + png_error(png_ptr, + "Cannot do RGB_TO_GRAY without EXPAND_SUPPORTED"); + + /* png_ptr->transformations &= ~PNG_RGB_TO_GRAY; */ + } +#endif + { + if (red >= 0 && green >= 0 && red + green <= PNG_FP_1) + { + png_uint_16 red_int, green_int; + + /* NOTE: this calculation does not round, but this behavior is retained + * for consistency; the inaccuracy is very small. The code here always + * overwrites the coefficients, regardless of whether they have been + * defaulted or set already. + */ + red_int = (png_uint_16)(((png_uint_32)red*32768)/100000); + green_int = (png_uint_16)(((png_uint_32)green*32768)/100000); + + png_ptr->rgb_to_gray_red_coeff = red_int; + png_ptr->rgb_to_gray_green_coeff = green_int; + png_ptr->rgb_to_gray_coefficients_set = 1; + } + + else + { + if (red >= 0 && green >= 0) + png_app_warning(png_ptr, + "ignoring out of range rgb_to_gray coefficients"); + + /* Use the defaults, from the cHRM chunk if set, else the historical + * values which are close to the sRGB/HDTV/ITU-Rec 709 values. See + * png_do_rgb_to_gray for more discussion of the values. In this case + * the coefficients are not marked as 'set' and are not overwritten if + * something has already provided a default. + */ + if (png_ptr->rgb_to_gray_red_coeff == 0 && + png_ptr->rgb_to_gray_green_coeff == 0) + { + png_ptr->rgb_to_gray_red_coeff = 6968; + png_ptr->rgb_to_gray_green_coeff = 23434; + /* png_ptr->rgb_to_gray_blue_coeff = 2366; */ + } + } + } +} + +#ifdef PNG_FLOATING_POINT_SUPPORTED +/* Convert a RGB image to a grayscale of the same width. This allows us, + * for example, to convert a 24 bpp RGB image into an 8 bpp grayscale image. + */ + +void PNGAPI +png_set_rgb_to_gray(png_structrp png_ptr, int error_action, double red, + double green) +{ + png_set_rgb_to_gray_fixed(png_ptr, error_action, + png_fixed(png_ptr, red, "rgb to gray red coefficient"), + png_fixed(png_ptr, green, "rgb to gray green coefficient")); +} +#endif /* FLOATING POINT */ + +#endif /* RGB_TO_GRAY */ + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) +void PNGAPI +png_set_read_user_transform_fn(png_structrp png_ptr, png_user_transform_ptr + read_user_transform_fn) +{ + png_debug(1, "in png_set_read_user_transform_fn"); + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + png_ptr->transformations |= PNG_USER_TRANSFORM; + png_ptr->read_user_transform_fn = read_user_transform_fn; +#endif +} +#endif + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +#ifdef PNG_READ_GAMMA_SUPPORTED +/* In the case of gamma transformations only do transformations on images where + * the [file] gamma and screen_gamma are not close reciprocals, otherwise it + * slows things down slightly, and also needlessly introduces small errors. + */ +static int /* PRIVATE */ +png_gamma_threshold(png_fixed_point screen_gamma, png_fixed_point file_gamma) +{ + /* PNG_GAMMA_THRESHOLD is the threshold for performing gamma + * correction as a difference of the overall transform from 1.0 + * + * We want to compare the threshold with s*f - 1, if we get + * overflow here it is because of wacky gamma values so we + * turn on processing anyway. + */ + png_fixed_point gtest; + return !png_muldiv(>est, screen_gamma, file_gamma, PNG_FP_1) || + png_gamma_significant(gtest); +} +#endif + +/* Initialize everything needed for the read. This includes modifying + * the palette. + */ + +/* For the moment 'png_init_palette_transformations' and + * 'png_init_rgb_transformations' only do some flag canceling optimizations. + * The intent is that these two routines should have palette or rgb operations + * extracted from 'png_init_read_transformations'. + */ +static void /* PRIVATE */ +png_init_palette_transformations(png_structrp png_ptr) +{ + /* Called to handle the (input) palette case. In png_do_read_transformations + * the first step is to expand the palette if requested, so this code must + * take care to only make changes that are invariant with respect to the + * palette expansion, or only do them if there is no expansion. + * + * STRIP_ALPHA has already been handled in the caller (by setting num_trans + * to 0.) + */ + int input_has_alpha = 0; + int input_has_transparency = 0; + + if (png_ptr->num_trans > 0) + { + int i; + + /* Ignore if all the entries are opaque (unlikely!) */ + for (i=0; inum_trans; ++i) + { + if (png_ptr->trans_alpha[i] == 255) + continue; + else if (png_ptr->trans_alpha[i] == 0) + input_has_transparency = 1; + else + { + input_has_transparency = 1; + input_has_alpha = 1; + break; + } + } + } + + /* If no alpha we can optimize. */ + if (input_has_alpha == 0) + { + /* Any alpha means background and associative alpha processing is + * required, however if the alpha is 0 or 1 throughout OPTIMIZE_ALPHA + * and ENCODE_ALPHA are irrelevant. + */ + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + if (input_has_transparency == 0) + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); + } + +#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* png_set_background handling - deals with the complexity of whether the + * background color is in the file format or the screen format in the case + * where an 'expand' will happen. + */ + + /* The following code cannot be entered in the alpha pre-multiplication case + * because PNG_BACKGROUND_EXPAND is cancelled below. + */ + if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) != 0 && + (png_ptr->transformations & PNG_EXPAND) != 0) + { + { + png_ptr->background.red = + png_ptr->palette[png_ptr->background.index].red; + png_ptr->background.green = + png_ptr->palette[png_ptr->background.index].green; + png_ptr->background.blue = + png_ptr->palette[png_ptr->background.index].blue; + +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_INVERT_ALPHA) != 0) + { + if ((png_ptr->transformations & PNG_EXPAND_tRNS) == 0) + { + /* Invert the alpha channel (in tRNS) unless the pixels are + * going to be expanded, in which case leave it for later + */ + int i, istop = png_ptr->num_trans; + + for (i = 0; i < istop; i++) + png_ptr->trans_alpha[i] = + (png_byte)(255 - png_ptr->trans_alpha[i]); + } + } +#endif /* READ_INVERT_ALPHA */ + } + } /* background expand and (therefore) no alpha association. */ +#endif /* READ_EXPAND && READ_BACKGROUND */ +} + +static void /* PRIVATE */ +png_init_rgb_transformations(png_structrp png_ptr) +{ + /* Added to libpng-1.5.4: check the color type to determine whether there + * is any alpha or transparency in the image and simply cancel the + * background and alpha mode stuff if there isn't. + */ + int input_has_alpha = (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0; + int input_has_transparency = png_ptr->num_trans > 0; + + /* If no alpha we can optimize. */ + if (input_has_alpha == 0) + { + /* Any alpha means background and associative alpha processing is + * required, however if the alpha is 0 or 1 throughout OPTIMIZE_ALPHA + * and ENCODE_ALPHA are irrelevant. + */ +# ifdef PNG_READ_ALPHA_MODE_SUPPORTED + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; +# endif + + if (input_has_transparency == 0) + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); + } + +#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* png_set_background handling - deals with the complexity of whether the + * background color is in the file format or the screen format in the case + * where an 'expand' will happen. + */ + + /* The following code cannot be entered in the alpha pre-multiplication case + * because PNG_BACKGROUND_EXPAND is cancelled below. + */ + if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) != 0 && + (png_ptr->transformations & PNG_EXPAND) != 0 && + (png_ptr->color_type & PNG_COLOR_MASK_COLOR) == 0) + /* i.e., GRAY or GRAY_ALPHA */ + { + { + /* Expand background and tRNS chunks */ + int gray = png_ptr->background.gray; + int trans_gray = png_ptr->trans_color.gray; + + switch (png_ptr->bit_depth) + { + case 1: + gray *= 0xff; + trans_gray *= 0xff; + break; + + case 2: + gray *= 0x55; + trans_gray *= 0x55; + break; + + case 4: + gray *= 0x11; + trans_gray *= 0x11; + break; + + default: + + case 8: + /* FALLTHROUGH */ /* (Already 8 bits) */ + + case 16: + /* Already a full 16 bits */ + break; + } + + png_ptr->background.red = png_ptr->background.green = + png_ptr->background.blue = (png_uint_16)gray; + + if ((png_ptr->transformations & PNG_EXPAND_tRNS) == 0) + { + png_ptr->trans_color.red = png_ptr->trans_color.green = + png_ptr->trans_color.blue = (png_uint_16)trans_gray; + } + } + } /* background expand and (therefore) no alpha association. */ +#endif /* READ_EXPAND && READ_BACKGROUND */ +} + +void /* PRIVATE */ +png_init_read_transformations(png_structrp png_ptr) +{ + png_debug(1, "in png_init_read_transformations"); + + /* This internal function is called from png_read_start_row in pngrutil.c + * and it is called before the 'rowbytes' calculation is done, so the code + * in here can change or update the transformations flags. + * + * First do updates that do not depend on the details of the PNG image data + * being processed. + */ + +#ifdef PNG_READ_GAMMA_SUPPORTED + /* Prior to 1.5.4 these tests were performed from png_set_gamma, 1.5.4 adds + * png_set_alpha_mode and this is another source for a default file gamma so + * the test needs to be performed later - here. In addition prior to 1.5.4 + * the tests were repeated for the PALETTE color type here - this is no + * longer necessary (and doesn't seem to have been necessary before.) + */ + { + /* The following temporary indicates if overall gamma correction is + * required. + */ + int gamma_correction = 0; + + if (png_ptr->colorspace.gamma != 0) /* has been set */ + { + if (png_ptr->screen_gamma != 0) /* screen set too */ + gamma_correction = png_gamma_threshold(png_ptr->colorspace.gamma, + png_ptr->screen_gamma); + + else + /* Assume the output matches the input; a long time default behavior + * of libpng, although the standard has nothing to say about this. + */ + png_ptr->screen_gamma = png_reciprocal(png_ptr->colorspace.gamma); + } + + else if (png_ptr->screen_gamma != 0) + /* The converse - assume the file matches the screen, note that this + * perhaps undesirable default can (from 1.5.4) be changed by calling + * png_set_alpha_mode (even if the alpha handling mode isn't required + * or isn't changed from the default.) + */ + png_ptr->colorspace.gamma = png_reciprocal(png_ptr->screen_gamma); + + else /* neither are set */ + /* Just in case the following prevents any processing - file and screen + * are both assumed to be linear and there is no way to introduce a + * third gamma value other than png_set_background with 'UNIQUE', and, + * prior to 1.5.4 + */ + png_ptr->screen_gamma = png_ptr->colorspace.gamma = PNG_FP_1; + + /* We have a gamma value now. */ + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + + /* Now turn the gamma transformation on or off as appropriate. Notice + * that PNG_GAMMA just refers to the file->screen correction. Alpha + * composition may independently cause gamma correction because it needs + * linear data (e.g. if the file has a gAMA chunk but the screen gamma + * hasn't been specified.) In any case this flag may get turned off in + * the code immediately below if the transform can be handled outside the + * row loop. + */ + if (gamma_correction != 0) + png_ptr->transformations |= PNG_GAMMA; + + else + png_ptr->transformations &= ~PNG_GAMMA; + } +#endif + + /* Certain transformations have the effect of preventing other + * transformations that happen afterward in png_do_read_transformations; + * resolve the interdependencies here. From the code of + * png_do_read_transformations the order is: + * + * 1) PNG_EXPAND (including PNG_EXPAND_tRNS) + * 2) PNG_STRIP_ALPHA (if no compose) + * 3) PNG_RGB_TO_GRAY + * 4) PNG_GRAY_TO_RGB iff !PNG_BACKGROUND_IS_GRAY + * 5) PNG_COMPOSE + * 6) PNG_GAMMA + * 7) PNG_STRIP_ALPHA (if compose) + * 8) PNG_ENCODE_ALPHA + * 9) PNG_SCALE_16_TO_8 + * 10) PNG_16_TO_8 + * 11) PNG_QUANTIZE (converts to palette) + * 12) PNG_EXPAND_16 + * 13) PNG_GRAY_TO_RGB iff PNG_BACKGROUND_IS_GRAY + * 14) PNG_INVERT_MONO + * 15) PNG_INVERT_ALPHA + * 16) PNG_SHIFT + * 17) PNG_PACK + * 18) PNG_BGR + * 19) PNG_PACKSWAP + * 20) PNG_FILLER (includes PNG_ADD_ALPHA) + * 21) PNG_SWAP_ALPHA + * 22) PNG_SWAP_BYTES + * 23) PNG_USER_TRANSFORM [must be last] + */ +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) != 0 && + (png_ptr->transformations & PNG_COMPOSE) == 0) + { + /* Stripping the alpha channel happens immediately after the 'expand' + * transformations, before all other transformation, so it cancels out + * the alpha handling. It has the side effect negating the effect of + * PNG_EXPAND_tRNS too: + */ + png_ptr->transformations &= ~(PNG_BACKGROUND_EXPAND | PNG_ENCODE_ALPHA | + PNG_EXPAND_tRNS); + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + /* Kill the tRNS chunk itself too. Prior to 1.5.4 this did not happen + * so transparency information would remain just so long as it wasn't + * expanded. This produces unexpected API changes if the set of things + * that do PNG_EXPAND_tRNS changes (perfectly possible given the + * documentation - which says ask for what you want, accept what you + * get.) This makes the behavior consistent from 1.5.4: + */ + png_ptr->num_trans = 0; + } +#endif /* STRIP_ALPHA supported, no COMPOSE */ + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + /* If the screen gamma is about 1.0 then the OPTIMIZE_ALPHA and ENCODE_ALPHA + * settings will have no effect. + */ + if (png_gamma_significant(png_ptr->screen_gamma) == 0) + { + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + } +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + /* Make sure the coefficients for the rgb to gray conversion are set + * appropriately. + */ + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) != 0) + png_colorspace_set_rgb_coefficients(png_ptr); +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* Detect gray background and attempt to enable optimization for + * gray --> RGB case. + * + * Note: if PNG_BACKGROUND_EXPAND is set and color_type is either RGB or + * RGB_ALPHA (in which case need_expand is superfluous anyway), the + * background color might actually be gray yet not be flagged as such. + * This is not a problem for the current code, which uses + * PNG_BACKGROUND_IS_GRAY only to decide when to do the + * png_do_gray_to_rgb() transformation. + * + * TODO: this code needs to be revised to avoid the complexity and + * interdependencies. The color type of the background should be recorded in + * png_set_background, along with the bit depth, then the code has a record + * of exactly what color space the background is currently in. + */ + if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) != 0) + { + /* PNG_BACKGROUND_EXPAND: the background is in the file color space, so if + * the file was grayscale the background value is gray. + */ + if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) == 0) + png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; + } + + else if ((png_ptr->transformations & PNG_COMPOSE) != 0) + { + /* PNG_COMPOSE: png_set_background was called with need_expand false, + * so the color is in the color space of the output or png_set_alpha_mode + * was called and the color is black. Ignore RGB_TO_GRAY because that + * happens before GRAY_TO_RGB. + */ + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) != 0) + { + if (png_ptr->background.red == png_ptr->background.green && + png_ptr->background.red == png_ptr->background.blue) + { + png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; + png_ptr->background.gray = png_ptr->background.red; + } + } + } +#endif /* READ_EXPAND && READ_BACKGROUND */ +#endif /* READ_GRAY_TO_RGB */ + + /* For indexed PNG data (PNG_COLOR_TYPE_PALETTE) many of the transformations + * can be performed directly on the palette, and some (such as rgb to gray) + * can be optimized inside the palette. This is particularly true of the + * composite (background and alpha) stuff, which can be pretty much all done + * in the palette even if the result is expanded to RGB or gray afterward. + * + * NOTE: this is Not Yet Implemented, the code behaves as in 1.5.1 and + * earlier and the palette stuff is actually handled on the first row. This + * leads to the reported bug that the palette returned by png_get_PLTE is not + * updated. + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + png_init_palette_transformations(png_ptr); + + else + png_init_rgb_transformations(png_ptr); + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) && \ + defined(PNG_READ_EXPAND_16_SUPPORTED) + if ((png_ptr->transformations & PNG_EXPAND_16) != 0 && + (png_ptr->transformations & PNG_COMPOSE) != 0 && + (png_ptr->transformations & PNG_BACKGROUND_EXPAND) == 0 && + png_ptr->bit_depth != 16) + { + /* TODO: fix this. Because the expand_16 operation is after the compose + * handling the background color must be 8, not 16, bits deep, but the + * application will supply a 16-bit value so reduce it here. + * + * The PNG_BACKGROUND_EXPAND code above does not expand to 16 bits at + * present, so that case is ok (until do_expand_16 is moved.) + * + * NOTE: this discards the low 16 bits of the user supplied background + * color, but until expand_16 works properly there is no choice! + */ +# define CHOP(x) (x)=((png_uint_16)PNG_DIV257(x)) + CHOP(png_ptr->background.red); + CHOP(png_ptr->background.green); + CHOP(png_ptr->background.blue); + CHOP(png_ptr->background.gray); +# undef CHOP + } +#endif /* READ_BACKGROUND && READ_EXPAND_16 */ + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) && \ + (defined(PNG_READ_SCALE_16_TO_8_SUPPORTED) || \ + defined(PNG_READ_STRIP_16_TO_8_SUPPORTED)) + if ((png_ptr->transformations & (PNG_16_TO_8|PNG_SCALE_16_TO_8)) != 0 && + (png_ptr->transformations & PNG_COMPOSE) != 0 && + (png_ptr->transformations & PNG_BACKGROUND_EXPAND) == 0 && + png_ptr->bit_depth == 16) + { + /* On the other hand, if a 16-bit file is to be reduced to 8-bits per + * component this will also happen after PNG_COMPOSE and so the background + * color must be pre-expanded here. + * + * TODO: fix this too. + */ + png_ptr->background.red = (png_uint_16)(png_ptr->background.red * 257); + png_ptr->background.green = + (png_uint_16)(png_ptr->background.green * 257); + png_ptr->background.blue = (png_uint_16)(png_ptr->background.blue * 257); + png_ptr->background.gray = (png_uint_16)(png_ptr->background.gray * 257); + } +#endif + + /* NOTE: below 'PNG_READ_ALPHA_MODE_SUPPORTED' is presumed to also enable the + * background support (see the comments in scripts/pnglibconf.dfa), this + * allows pre-multiplication of the alpha channel to be implemented as + * compositing on black. This is probably sub-optimal and has been done in + * 1.5.4 betas simply to enable external critique and testing (i.e. to + * implement the new API quickly, without lots of internal changes.) + */ + +#ifdef PNG_READ_GAMMA_SUPPORTED +# ifdef PNG_READ_BACKGROUND_SUPPORTED + /* Includes ALPHA_MODE */ + png_ptr->background_1 = png_ptr->background; +# endif + + /* This needs to change - in the palette image case a whole set of tables are + * built when it would be quicker to just calculate the correct value for + * each palette entry directly. Also, the test is too tricky - why check + * PNG_RGB_TO_GRAY if PNG_GAMMA is not set? The answer seems to be that + * PNG_GAMMA is cancelled even if the gamma is known? The test excludes the + * PNG_COMPOSE case, so apparently if there is no *overall* gamma correction + * the gamma tables will not be built even if composition is required on a + * gamma encoded value. + * + * In 1.5.4 this is addressed below by an additional check on the individual + * file gamma - if it is not 1.0 both RGB_TO_GRAY and COMPOSE need the + * tables. + */ + if ((png_ptr->transformations & PNG_GAMMA) != 0 || + ((png_ptr->transformations & PNG_RGB_TO_GRAY) != 0 && + (png_gamma_significant(png_ptr->colorspace.gamma) != 0 || + png_gamma_significant(png_ptr->screen_gamma) != 0)) || + ((png_ptr->transformations & PNG_COMPOSE) != 0 && + (png_gamma_significant(png_ptr->colorspace.gamma) != 0 || + png_gamma_significant(png_ptr->screen_gamma) != 0 +# ifdef PNG_READ_BACKGROUND_SUPPORTED + || (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_UNIQUE && + png_gamma_significant(png_ptr->background_gamma) != 0) +# endif + )) || ((png_ptr->transformations & PNG_ENCODE_ALPHA) != 0 && + png_gamma_significant(png_ptr->screen_gamma) != 0)) + { + png_build_gamma_table(png_ptr, png_ptr->bit_depth); + +#ifdef PNG_READ_BACKGROUND_SUPPORTED + if ((png_ptr->transformations & PNG_COMPOSE) != 0) + { + /* Issue a warning about this combination: because RGB_TO_GRAY is + * optimized to do the gamma transform if present yet do_background has + * to do the same thing if both options are set a + * double-gamma-correction happens. This is true in all versions of + * libpng to date. + */ + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) != 0) + png_warning(png_ptr, + "libpng does not support gamma+background+rgb_to_gray"); + + if ((png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) != 0) + { + /* We don't get to here unless there is a tRNS chunk with non-opaque + * entries - see the checking code at the start of this function. + */ + png_color back, back_1; + png_colorp palette = png_ptr->palette; + int num_palette = png_ptr->num_palette; + int i; + if (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_FILE) + { + + back.red = png_ptr->gamma_table[png_ptr->background.red]; + back.green = png_ptr->gamma_table[png_ptr->background.green]; + back.blue = png_ptr->gamma_table[png_ptr->background.blue]; + + back_1.red = png_ptr->gamma_to_1[png_ptr->background.red]; + back_1.green = png_ptr->gamma_to_1[png_ptr->background.green]; + back_1.blue = png_ptr->gamma_to_1[png_ptr->background.blue]; + } + else + { + png_fixed_point g, gs; + + switch (png_ptr->background_gamma_type) + { + case PNG_BACKGROUND_GAMMA_SCREEN: + g = (png_ptr->screen_gamma); + gs = PNG_FP_1; + break; + + case PNG_BACKGROUND_GAMMA_FILE: + g = png_reciprocal(png_ptr->colorspace.gamma); + gs = png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma); + break; + + case PNG_BACKGROUND_GAMMA_UNIQUE: + g = png_reciprocal(png_ptr->background_gamma); + gs = png_reciprocal2(png_ptr->background_gamma, + png_ptr->screen_gamma); + break; + default: + g = PNG_FP_1; /* back_1 */ + gs = PNG_FP_1; /* back */ + break; + } + + if (png_gamma_significant(gs) != 0) + { + back.red = png_gamma_8bit_correct(png_ptr->background.red, + gs); + back.green = png_gamma_8bit_correct(png_ptr->background.green, + gs); + back.blue = png_gamma_8bit_correct(png_ptr->background.blue, + gs); + } + + else + { + back.red = (png_byte)png_ptr->background.red; + back.green = (png_byte)png_ptr->background.green; + back.blue = (png_byte)png_ptr->background.blue; + } + + if (png_gamma_significant(g) != 0) + { + back_1.red = png_gamma_8bit_correct(png_ptr->background.red, + g); + back_1.green = png_gamma_8bit_correct( + png_ptr->background.green, g); + back_1.blue = png_gamma_8bit_correct(png_ptr->background.blue, + g); + } + + else + { + back_1.red = (png_byte)png_ptr->background.red; + back_1.green = (png_byte)png_ptr->background.green; + back_1.blue = (png_byte)png_ptr->background.blue; + } + } + + for (i = 0; i < num_palette; i++) + { + if (i < (int)png_ptr->num_trans && + png_ptr->trans_alpha[i] != 0xff) + { + if (png_ptr->trans_alpha[i] == 0) + { + palette[i] = back; + } + else /* if (png_ptr->trans_alpha[i] != 0xff) */ + { + png_byte v, w; + + v = png_ptr->gamma_to_1[palette[i].red]; + png_composite(w, v, png_ptr->trans_alpha[i], back_1.red); + palette[i].red = png_ptr->gamma_from_1[w]; + + v = png_ptr->gamma_to_1[palette[i].green]; + png_composite(w, v, png_ptr->trans_alpha[i], back_1.green); + palette[i].green = png_ptr->gamma_from_1[w]; + + v = png_ptr->gamma_to_1[palette[i].blue]; + png_composite(w, v, png_ptr->trans_alpha[i], back_1.blue); + palette[i].blue = png_ptr->gamma_from_1[w]; + } + } + else + { + palette[i].red = png_ptr->gamma_table[palette[i].red]; + palette[i].green = png_ptr->gamma_table[palette[i].green]; + palette[i].blue = png_ptr->gamma_table[palette[i].blue]; + } + } + + /* Prevent the transformations being done again. + * + * NOTE: this is highly dubious; it removes the transformations in + * place. This seems inconsistent with the general treatment of the + * transformations elsewhere. + */ + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA); + } /* color_type == PNG_COLOR_TYPE_PALETTE */ + + /* if (png_ptr->background_gamma_type!=PNG_BACKGROUND_GAMMA_UNKNOWN) */ + else /* color_type != PNG_COLOR_TYPE_PALETTE */ + { + int gs_sig, g_sig; + png_fixed_point g = PNG_FP_1; /* Correction to linear */ + png_fixed_point gs = PNG_FP_1; /* Correction to screen */ + + switch (png_ptr->background_gamma_type) + { + case PNG_BACKGROUND_GAMMA_SCREEN: + g = png_ptr->screen_gamma; + /* gs = PNG_FP_1; */ + break; + + case PNG_BACKGROUND_GAMMA_FILE: + g = png_reciprocal(png_ptr->colorspace.gamma); + gs = png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma); + break; + + case PNG_BACKGROUND_GAMMA_UNIQUE: + g = png_reciprocal(png_ptr->background_gamma); + gs = png_reciprocal2(png_ptr->background_gamma, + png_ptr->screen_gamma); + break; + + default: + png_error(png_ptr, "invalid background gamma type"); + } + + g_sig = png_gamma_significant(g); + gs_sig = png_gamma_significant(gs); + + if (g_sig != 0) + png_ptr->background_1.gray = png_gamma_correct(png_ptr, + png_ptr->background.gray, g); + + if (gs_sig != 0) + png_ptr->background.gray = png_gamma_correct(png_ptr, + png_ptr->background.gray, gs); + + if ((png_ptr->background.red != png_ptr->background.green) || + (png_ptr->background.red != png_ptr->background.blue) || + (png_ptr->background.red != png_ptr->background.gray)) + { + /* RGB or RGBA with color background */ + if (g_sig != 0) + { + png_ptr->background_1.red = png_gamma_correct(png_ptr, + png_ptr->background.red, g); + + png_ptr->background_1.green = png_gamma_correct(png_ptr, + png_ptr->background.green, g); + + png_ptr->background_1.blue = png_gamma_correct(png_ptr, + png_ptr->background.blue, g); + } + + if (gs_sig != 0) + { + png_ptr->background.red = png_gamma_correct(png_ptr, + png_ptr->background.red, gs); + + png_ptr->background.green = png_gamma_correct(png_ptr, + png_ptr->background.green, gs); + + png_ptr->background.blue = png_gamma_correct(png_ptr, + png_ptr->background.blue, gs); + } + } + + else + { + /* GRAY, GRAY ALPHA, RGB, or RGBA with gray background */ + png_ptr->background_1.red = png_ptr->background_1.green + = png_ptr->background_1.blue = png_ptr->background_1.gray; + + png_ptr->background.red = png_ptr->background.green + = png_ptr->background.blue = png_ptr->background.gray; + } + + /* The background is now in screen gamma: */ + png_ptr->background_gamma_type = PNG_BACKGROUND_GAMMA_SCREEN; + } /* color_type != PNG_COLOR_TYPE_PALETTE */ + }/* png_ptr->transformations & PNG_BACKGROUND */ + + else + /* Transformation does not include PNG_BACKGROUND */ +#endif /* READ_BACKGROUND */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + /* RGB_TO_GRAY needs to have non-gamma-corrected values! */ + && ((png_ptr->transformations & PNG_EXPAND) == 0 || + (png_ptr->transformations & PNG_RGB_TO_GRAY) == 0) +#endif + ) + { + png_colorp palette = png_ptr->palette; + int num_palette = png_ptr->num_palette; + int i; + + /* NOTE: there are other transformations that should probably be in + * here too. + */ + for (i = 0; i < num_palette; i++) + { + palette[i].red = png_ptr->gamma_table[palette[i].red]; + palette[i].green = png_ptr->gamma_table[palette[i].green]; + palette[i].blue = png_ptr->gamma_table[palette[i].blue]; + } + + /* Done the gamma correction. */ + png_ptr->transformations &= ~PNG_GAMMA; + } /* color_type == PALETTE && !PNG_BACKGROUND transformation */ + } +#ifdef PNG_READ_BACKGROUND_SUPPORTED + else +#endif +#endif /* READ_GAMMA */ + +#ifdef PNG_READ_BACKGROUND_SUPPORTED + /* No GAMMA transformation (see the hanging else 4 lines above) */ + if ((png_ptr->transformations & PNG_COMPOSE) != 0 && + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)) + { + int i; + int istop = (int)png_ptr->num_trans; + png_color back; + png_colorp palette = png_ptr->palette; + + back.red = (png_byte)png_ptr->background.red; + back.green = (png_byte)png_ptr->background.green; + back.blue = (png_byte)png_ptr->background.blue; + + for (i = 0; i < istop; i++) + { + if (png_ptr->trans_alpha[i] == 0) + { + palette[i] = back; + } + + else if (png_ptr->trans_alpha[i] != 0xff) + { + /* The png_composite() macro is defined in png.h */ + png_composite(palette[i].red, palette[i].red, + png_ptr->trans_alpha[i], back.red); + + png_composite(palette[i].green, palette[i].green, + png_ptr->trans_alpha[i], back.green); + + png_composite(palette[i].blue, palette[i].blue, + png_ptr->trans_alpha[i], back.blue); + } + } + + png_ptr->transformations &= ~PNG_COMPOSE; + } +#endif /* READ_BACKGROUND */ + +#ifdef PNG_READ_SHIFT_SUPPORTED + if ((png_ptr->transformations & PNG_SHIFT) != 0 && + (png_ptr->transformations & PNG_EXPAND) == 0 && + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)) + { + int i; + int istop = png_ptr->num_palette; + int shift = 8 - png_ptr->sig_bit.red; + + png_ptr->transformations &= ~PNG_SHIFT; + + /* significant bits can be in the range 1 to 7 for a meaningful result, if + * the number of significant bits is 0 then no shift is done (this is an + * error condition which is silently ignored.) + */ + if (shift > 0 && shift < 8) + for (i=0; ipalette[i].red; + + component >>= shift; + png_ptr->palette[i].red = (png_byte)component; + } + + shift = 8 - png_ptr->sig_bit.green; + if (shift > 0 && shift < 8) + for (i=0; ipalette[i].green; + + component >>= shift; + png_ptr->palette[i].green = (png_byte)component; + } + + shift = 8 - png_ptr->sig_bit.blue; + if (shift > 0 && shift < 8) + for (i=0; ipalette[i].blue; + + component >>= shift; + png_ptr->palette[i].blue = (png_byte)component; + } + } +#endif /* READ_SHIFT */ +} + +/* Modify the info structure to reflect the transformations. The + * info should be updated so a PNG file could be written with it, + * assuming the transformations result in valid PNG data. + */ +void /* PRIVATE */ +png_read_transform_info(png_structrp png_ptr, png_inforp info_ptr) +{ + png_debug(1, "in png_read_transform_info"); + +#ifdef PNG_READ_EXPAND_SUPPORTED + if ((png_ptr->transformations & PNG_EXPAND) != 0) + { + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + /* This check must match what actually happens in + * png_do_expand_palette; if it ever checks the tRNS chunk to see if + * it is all opaque we must do the same (at present it does not.) + */ + if (png_ptr->num_trans > 0) + info_ptr->color_type = PNG_COLOR_TYPE_RGB_ALPHA; + + else + info_ptr->color_type = PNG_COLOR_TYPE_RGB; + + info_ptr->bit_depth = 8; + info_ptr->num_trans = 0; + + if (png_ptr->palette == NULL) + png_error (png_ptr, "Palette is NULL in indexed image"); + } + else + { + if (png_ptr->num_trans != 0) + { + if ((png_ptr->transformations & PNG_EXPAND_tRNS) != 0) + info_ptr->color_type |= PNG_COLOR_MASK_ALPHA; + } + if (info_ptr->bit_depth < 8) + info_ptr->bit_depth = 8; + + info_ptr->num_trans = 0; + } + } +#endif + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + /* The following is almost certainly wrong unless the background value is in + * the screen space! + */ + if ((png_ptr->transformations & PNG_COMPOSE) != 0) + info_ptr->background = png_ptr->background; +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED + /* The following used to be conditional on PNG_GAMMA (prior to 1.5.4), + * however it seems that the code in png_init_read_transformations, which has + * been called before this from png_read_update_info->png_read_start_row + * sometimes does the gamma transform and cancels the flag. + * + * TODO: this looks wrong; the info_ptr should end up with a gamma equal to + * the screen_gamma value. The following probably results in weirdness if + * the info_ptr is used by the app after the rows have been read. + */ + info_ptr->colorspace.gamma = png_ptr->colorspace.gamma; +#endif + + if (info_ptr->bit_depth == 16) + { +# ifdef PNG_READ_16BIT_SUPPORTED +# ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + if ((png_ptr->transformations & PNG_SCALE_16_TO_8) != 0) + info_ptr->bit_depth = 8; +# endif + +# ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + if ((png_ptr->transformations & PNG_16_TO_8) != 0) + info_ptr->bit_depth = 8; +# endif + +# else + /* No 16-bit support: force chopping 16-bit input down to 8, in this case + * the app program can chose if both APIs are available by setting the + * correct scaling to use. + */ +# ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + /* For compatibility with previous versions use the strip method by + * default. This code works because if PNG_SCALE_16_TO_8 is already + * set the code below will do that in preference to the chop. + */ + png_ptr->transformations |= PNG_16_TO_8; + info_ptr->bit_depth = 8; +# else + +# ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_ptr->transformations |= PNG_SCALE_16_TO_8; + info_ptr->bit_depth = 8; +# else + + CONFIGURATION ERROR: you must enable at least one 16 to 8 method +# endif +# endif +#endif /* !READ_16BIT */ + } + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) != 0) + info_ptr->color_type = (png_byte)(info_ptr->color_type | + PNG_COLOR_MASK_COLOR); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) != 0) + info_ptr->color_type = (png_byte)(info_ptr->color_type & + ~PNG_COLOR_MASK_COLOR); +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + if ((png_ptr->transformations & PNG_QUANTIZE) != 0) + { + if (((info_ptr->color_type == PNG_COLOR_TYPE_RGB) || + (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA)) && + png_ptr->palette_lookup != 0 && info_ptr->bit_depth == 8) + { + info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; + } + } +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED + if ((png_ptr->transformations & PNG_EXPAND_16) != 0 && + info_ptr->bit_depth == 8 && + info_ptr->color_type != PNG_COLOR_TYPE_PALETTE) + { + info_ptr->bit_depth = 16; + } +#endif + +#ifdef PNG_READ_PACK_SUPPORTED + if ((png_ptr->transformations & PNG_PACK) != 0 && + (info_ptr->bit_depth < 8)) + info_ptr->bit_depth = 8; +#endif + + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + info_ptr->channels = 1; + + else if ((info_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0) + info_ptr->channels = 3; + + else + info_ptr->channels = 1; + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) != 0) + { + info_ptr->color_type = (png_byte)(info_ptr->color_type & + ~PNG_COLOR_MASK_ALPHA); + info_ptr->num_trans = 0; + } +#endif + + if ((info_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0) + info_ptr->channels++; + +#ifdef PNG_READ_FILLER_SUPPORTED + /* STRIP_ALPHA and FILLER allowed: MASK_ALPHA bit stripped above */ + if ((png_ptr->transformations & PNG_FILLER) != 0 && + (info_ptr->color_type == PNG_COLOR_TYPE_RGB || + info_ptr->color_type == PNG_COLOR_TYPE_GRAY)) + { + info_ptr->channels++; + /* If adding a true alpha channel not just filler */ + if ((png_ptr->transformations & PNG_ADD_ALPHA) != 0) + info_ptr->color_type |= PNG_COLOR_MASK_ALPHA; + } +#endif + +#if defined(PNG_USER_TRANSFORM_PTR_SUPPORTED) && \ +defined(PNG_READ_USER_TRANSFORM_SUPPORTED) + if ((png_ptr->transformations & PNG_USER_TRANSFORM) != 0) + { + if (png_ptr->user_transform_depth != 0) + info_ptr->bit_depth = png_ptr->user_transform_depth; + + if (png_ptr->user_transform_channels != 0) + info_ptr->channels = png_ptr->user_transform_channels; + } +#endif + + info_ptr->pixel_depth = (png_byte)(info_ptr->channels * + info_ptr->bit_depth); + + info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, info_ptr->width); + + /* Adding in 1.5.4: cache the above value in png_struct so that we can later + * check in png_rowbytes that the user buffer won't get overwritten. Note + * that the field is not always set - if png_read_update_info isn't called + * the application has to either not do any transforms or get the calculation + * right itself. + */ + png_ptr->info_rowbytes = info_ptr->rowbytes; + +#ifndef PNG_READ_EXPAND_SUPPORTED + if (png_ptr != NULL) + return; +#endif +} + +#ifdef PNG_READ_PACK_SUPPORTED +/* Unpack pixels of 1, 2, or 4 bits per pixel into 1 byte per pixel, + * without changing the actual values. Thus, if you had a row with + * a bit depth of 1, you would end up with bytes that only contained + * the numbers 0 or 1. If you would rather they contain 0 and 255, use + * png_do_shift() after this. + */ +static void +png_do_unpack(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_unpack"); + + if (row_info->bit_depth < 8) + { + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + switch (row_info->bit_depth) + { + case 1: + { + png_bytep sp = row + (size_t)((row_width - 1) >> 3); + png_bytep dp = row + (size_t)row_width - 1; + png_uint_32 shift = 7U - ((row_width + 7U) & 0x07); + for (i = 0; i < row_width; i++) + { + *dp = (png_byte)((*sp >> shift) & 0x01); + + if (shift == 7) + { + shift = 0; + sp--; + } + + else + shift++; + + dp--; + } + break; + } + + case 2: + { + + png_bytep sp = row + (size_t)((row_width - 1) >> 2); + png_bytep dp = row + (size_t)row_width - 1; + png_uint_32 shift = ((3U - ((row_width + 3U) & 0x03)) << 1); + for (i = 0; i < row_width; i++) + { + *dp = (png_byte)((*sp >> shift) & 0x03); + + if (shift == 6) + { + shift = 0; + sp--; + } + + else + shift += 2; + + dp--; + } + break; + } + + case 4: + { + png_bytep sp = row + (size_t)((row_width - 1) >> 1); + png_bytep dp = row + (size_t)row_width - 1; + png_uint_32 shift = ((1U - ((row_width + 1U) & 0x01)) << 2); + for (i = 0; i < row_width; i++) + { + *dp = (png_byte)((*sp >> shift) & 0x0f); + + if (shift == 4) + { + shift = 0; + sp--; + } + + else + shift = 4; + + dp--; + } + break; + } + + default: + break; + } + row_info->bit_depth = 8; + row_info->pixel_depth = (png_byte)(8 * row_info->channels); + row_info->rowbytes = row_width * row_info->channels; + } +} +#endif + +#ifdef PNG_READ_SHIFT_SUPPORTED +/* Reverse the effects of png_do_shift. This routine merely shifts the + * pixels back to their significant bits values. Thus, if you have + * a row of bit depth 8, but only 5 are significant, this will shift + * the values back to 0 through 31. + */ +static void +png_do_unshift(png_row_infop row_info, png_bytep row, + png_const_color_8p sig_bits) +{ + int color_type; + + png_debug(1, "in png_do_unshift"); + + /* The palette case has already been handled in the _init routine. */ + color_type = row_info->color_type; + + if (color_type != PNG_COLOR_TYPE_PALETTE) + { + int shift[4]; + int channels = 0; + int bit_depth = row_info->bit_depth; + + if ((color_type & PNG_COLOR_MASK_COLOR) != 0) + { + shift[channels++] = bit_depth - sig_bits->red; + shift[channels++] = bit_depth - sig_bits->green; + shift[channels++] = bit_depth - sig_bits->blue; + } + + else + { + shift[channels++] = bit_depth - sig_bits->gray; + } + + if ((color_type & PNG_COLOR_MASK_ALPHA) != 0) + { + shift[channels++] = bit_depth - sig_bits->alpha; + } + + { + int c, have_shift; + + for (c = have_shift = 0; c < channels; ++c) + { + /* A shift of more than the bit depth is an error condition but it + * gets ignored here. + */ + if (shift[c] <= 0 || shift[c] >= bit_depth) + shift[c] = 0; + + else + have_shift = 1; + } + + if (have_shift == 0) + return; + } + + switch (bit_depth) + { + default: + /* Must be 1bpp gray: should not be here! */ + /* NOTREACHED */ + break; + + case 2: + /* Must be 2bpp gray */ + /* assert(channels == 1 && shift[0] == 1) */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + + while (bp < bp_end) + { + int b = (*bp >> 1) & 0x55; + *bp++ = (png_byte)b; + } + break; + } + + case 4: + /* Must be 4bpp gray */ + /* assert(channels == 1) */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + int gray_shift = shift[0]; + int mask = 0xf >> gray_shift; + + mask |= mask << 4; + + while (bp < bp_end) + { + int b = (*bp >> gray_shift) & mask; + *bp++ = (png_byte)b; + } + break; + } + + case 8: + /* Single byte components, G, GA, RGB, RGBA */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + int channel = 0; + + while (bp < bp_end) + { + int b = *bp >> shift[channel]; + if (++channel >= channels) + channel = 0; + *bp++ = (png_byte)b; + } + break; + } + +#ifdef PNG_READ_16BIT_SUPPORTED + case 16: + /* Double byte components, G, GA, RGB, RGBA */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + int channel = 0; + + while (bp < bp_end) + { + int value = (bp[0] << 8) + bp[1]; + + value >>= shift[channel]; + if (++channel >= channels) + channel = 0; + *bp++ = (png_byte)(value >> 8); + *bp++ = (png_byte)value; + } + break; + } +#endif + } + } +} +#endif + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED +/* Scale rows of bit depth 16 down to 8 accurately */ +static void +png_do_scale_16_to_8(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_scale_16_to_8"); + + if (row_info->bit_depth == 16) + { + png_bytep sp = row; /* source */ + png_bytep dp = row; /* destination */ + png_bytep ep = sp + row_info->rowbytes; /* end+1 */ + + while (sp < ep) + { + /* The input is an array of 16-bit components, these must be scaled to + * 8 bits each. For a 16-bit value V the required value (from the PNG + * specification) is: + * + * (V * 255) / 65535 + * + * This reduces to round(V / 257), or floor((V + 128.5)/257) + * + * Represent V as the two byte value vhi.vlo. Make a guess that the + * result is the top byte of V, vhi, then the correction to this value + * is: + * + * error = floor(((V-vhi.vhi) + 128.5) / 257) + * = floor(((vlo-vhi) + 128.5) / 257) + * + * This can be approximated using integer arithmetic (and a signed + * shift): + * + * error = (vlo-vhi+128) >> 8; + * + * The approximate differs from the exact answer only when (vlo-vhi) is + * 128; it then gives a correction of +1 when the exact correction is + * 0. This gives 128 errors. The exact answer (correct for all 16-bit + * input values) is: + * + * error = (vlo-vhi+128)*65535 >> 24; + * + * An alternative arithmetic calculation which also gives no errors is: + * + * (V * 255 + 32895) >> 16 + */ + + png_int_32 tmp = *sp++; /* must be signed! */ + tmp += (((int)*sp++ - tmp + 128) * 65535) >> 24; + *dp++ = (png_byte)tmp; + } + + row_info->bit_depth = 8; + row_info->pixel_depth = (png_byte)(8 * row_info->channels); + row_info->rowbytes = row_info->width * row_info->channels; + } +} +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED +static void +/* Simply discard the low byte. This was the default behavior prior + * to libpng-1.5.4. + */ +png_do_chop(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_chop"); + + if (row_info->bit_depth == 16) + { + png_bytep sp = row; /* source */ + png_bytep dp = row; /* destination */ + png_bytep ep = sp + row_info->rowbytes; /* end+1 */ + + while (sp < ep) + { + *dp++ = *sp; + sp += 2; /* skip low byte */ + } + + row_info->bit_depth = 8; + row_info->pixel_depth = (png_byte)(8 * row_info->channels); + row_info->rowbytes = row_info->width * row_info->channels; + } +} +#endif + +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED +static void +png_do_read_swap_alpha(png_row_infop row_info, png_bytep row) +{ + png_uint_32 row_width = row_info->width; + + png_debug(1, "in png_do_read_swap_alpha"); + + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + /* This converts from RGBA to ARGB */ + if (row_info->bit_depth == 8) + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + /* This converts from RRGGBBAA to AARRGGBB */ + else + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save[2]; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save[0] = *(--sp); + save[1] = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save[0]; + *(--dp) = save[1]; + } + } +#endif + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + /* This converts from GA to AG */ + if (row_info->bit_depth == 8) + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + /* This converts from GGAA to AAGG */ + else + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save[2]; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save[0] = *(--sp); + save[1] = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save[0]; + *(--dp) = save[1]; + } + } +#endif + } +} +#endif + +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED +static void +png_do_read_invert_alpha(png_row_infop row_info, png_bytep row) +{ + png_uint_32 row_width; + png_debug(1, "in png_do_read_invert_alpha"); + + row_width = row_info->width; + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in RGBA */ + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + +/* This does nothing: + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + We can replace it with: +*/ + sp-=3; + dp=sp; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + /* This inverts the alpha channel in RRGGBBAA */ + else + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + *(--dp) = (png_byte)(255 - *(--sp)); + +/* This does nothing: + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + We can replace it with: +*/ + sp-=6; + dp=sp; + } + } +#endif + } + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in GA */ + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + *(--dp) = *(--sp); + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + else + { + /* This inverts the alpha channel in GGAA */ + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + *(--dp) = (png_byte)(255 - *(--sp)); +/* + *(--dp) = *(--sp); + *(--dp) = *(--sp); +*/ + sp-=2; + dp=sp; + } + } +#endif + } +} +#endif + +#ifdef PNG_READ_FILLER_SUPPORTED +/* Add filler channel if we have RGB color */ +static void +png_do_read_filler(png_row_infop row_info, png_bytep row, + png_uint_32 filler, png_uint_32 flags) +{ + png_uint_32 i; + png_uint_32 row_width = row_info->width; + +#ifdef PNG_READ_16BIT_SUPPORTED + png_byte hi_filler = (png_byte)(filler>>8); +#endif + png_byte lo_filler = (png_byte)filler; + + png_debug(1, "in png_do_read_filler"); + + if ( + row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + if (row_info->bit_depth == 8) + { + if ((flags & PNG_FLAG_FILLER_AFTER) != 0) + { + /* This changes the data from G to GX */ + png_bytep sp = row + (size_t)row_width; + png_bytep dp = sp + (size_t)row_width; + for (i = 1; i < row_width; i++) + { + *(--dp) = lo_filler; + *(--dp) = *(--sp); + } + *(--dp) = lo_filler; + row_info->channels = 2; + row_info->pixel_depth = 16; + row_info->rowbytes = row_width * 2; + } + + else + { + /* This changes the data from G to XG */ + png_bytep sp = row + (size_t)row_width; + png_bytep dp = sp + (size_t)row_width; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = lo_filler; + } + row_info->channels = 2; + row_info->pixel_depth = 16; + row_info->rowbytes = row_width * 2; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + if ((flags & PNG_FLAG_FILLER_AFTER) != 0) + { + /* This changes the data from GG to GGXX */ + png_bytep sp = row + (size_t)row_width * 2; + png_bytep dp = sp + (size_t)row_width * 2; + for (i = 1; i < row_width; i++) + { + *(--dp) = lo_filler; + *(--dp) = hi_filler; + *(--dp) = *(--sp); + *(--dp) = *(--sp); + } + *(--dp) = lo_filler; + *(--dp) = hi_filler; + row_info->channels = 2; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + + else + { + /* This changes the data from GG to XXGG */ + png_bytep sp = row + (size_t)row_width * 2; + png_bytep dp = sp + (size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = lo_filler; + *(--dp) = hi_filler; + } + row_info->channels = 2; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + } +#endif + } /* COLOR_TYPE == GRAY */ + else if (row_info->color_type == PNG_COLOR_TYPE_RGB) + { + if (row_info->bit_depth == 8) + { + if ((flags & PNG_FLAG_FILLER_AFTER) != 0) + { + /* This changes the data from RGB to RGBX */ + png_bytep sp = row + (size_t)row_width * 3; + png_bytep dp = sp + (size_t)row_width; + for (i = 1; i < row_width; i++) + { + *(--dp) = lo_filler; + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + } + *(--dp) = lo_filler; + row_info->channels = 4; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + + else + { + /* This changes the data from RGB to XRGB */ + png_bytep sp = row + (size_t)row_width * 3; + png_bytep dp = sp + (size_t)row_width; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = lo_filler; + } + row_info->channels = 4; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + if ((flags & PNG_FLAG_FILLER_AFTER) != 0) + { + /* This changes the data from RRGGBB to RRGGBBXX */ + png_bytep sp = row + (size_t)row_width * 6; + png_bytep dp = sp + (size_t)row_width * 2; + for (i = 1; i < row_width; i++) + { + *(--dp) = lo_filler; + *(--dp) = hi_filler; + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + } + *(--dp) = lo_filler; + *(--dp) = hi_filler; + row_info->channels = 4; + row_info->pixel_depth = 64; + row_info->rowbytes = row_width * 8; + } + + else + { + /* This changes the data from RRGGBB to XXRRGGBB */ + png_bytep sp = row + (size_t)row_width * 6; + png_bytep dp = sp + (size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = lo_filler; + *(--dp) = hi_filler; + } + + row_info->channels = 4; + row_info->pixel_depth = 64; + row_info->rowbytes = row_width * 8; + } + } +#endif + } /* COLOR_TYPE == RGB */ +} +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +/* Expand grayscale files to RGB, with or without alpha */ +static void +png_do_gray_to_rgb(png_row_infop row_info, png_bytep row) +{ + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + png_debug(1, "in png_do_gray_to_rgb"); + + if (row_info->bit_depth >= 8 && + (row_info->color_type & PNG_COLOR_MASK_COLOR) == 0) + { + if (row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + if (row_info->bit_depth == 8) + { + /* This changes G to RGB */ + png_bytep sp = row + (size_t)row_width - 1; + png_bytep dp = sp + (size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(dp--) = *sp; + *(dp--) = *sp; + *(dp--) = *(sp--); + } + } + + else + { + /* This changes GG to RRGGBB */ + png_bytep sp = row + (size_t)row_width * 2 - 1; + png_bytep dp = sp + (size_t)row_width * 4; + for (i = 0; i < row_width; i++) + { + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *(sp--); + *(dp--) = *(sp--); + } + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This changes GA to RGBA */ + png_bytep sp = row + (size_t)row_width * 2 - 1; + png_bytep dp = sp + (size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(dp--) = *(sp--); + *(dp--) = *sp; + *(dp--) = *sp; + *(dp--) = *(sp--); + } + } + + else + { + /* This changes GGAA to RRGGBBAA */ + png_bytep sp = row + (size_t)row_width * 4 - 1; + png_bytep dp = sp + (size_t)row_width * 4; + for (i = 0; i < row_width; i++) + { + *(dp--) = *(sp--); + *(dp--) = *(sp--); + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *(sp--); + *(dp--) = *(sp--); + } + } + } + row_info->channels = (png_byte)(row_info->channels + 2); + row_info->color_type |= PNG_COLOR_MASK_COLOR; + row_info->pixel_depth = (png_byte)(row_info->channels * + row_info->bit_depth); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } +} +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +/* Reduce RGB files to grayscale, with or without alpha + * using the equation given in Poynton's ColorFAQ of 1998-01-04 at + * (THIS LINK IS DEAD June 2008 but + * versions dated 1998 through November 2002 have been archived at + * https://web.archive.org/web/20000816232553/www.inforamp.net/ + * ~poynton/notes/colour_and_gamma/ColorFAQ.txt ) + * Charles Poynton poynton at poynton.com + * + * Y = 0.212671 * R + 0.715160 * G + 0.072169 * B + * + * which can be expressed with integers as + * + * Y = (6969 * R + 23434 * G + 2365 * B)/32768 + * + * Poynton's current link (as of January 2003 through July 2011): + * + * has changed the numbers slightly: + * + * Y = 0.2126*R + 0.7152*G + 0.0722*B + * + * which can be expressed with integers as + * + * Y = (6966 * R + 23436 * G + 2366 * B)/32768 + * + * Historically, however, libpng uses numbers derived from the ITU-R Rec 709 + * end point chromaticities and the D65 white point. Depending on the + * precision used for the D65 white point this produces a variety of different + * numbers, however if the four decimal place value used in ITU-R Rec 709 is + * used (0.3127,0.3290) the Y calculation would be: + * + * Y = (6968 * R + 23435 * G + 2366 * B)/32768 + * + * While this is correct the rounding results in an overflow for white, because + * the sum of the rounded coefficients is 32769, not 32768. Consequently + * libpng uses, instead, the closest non-overflowing approximation: + * + * Y = (6968 * R + 23434 * G + 2366 * B)/32768 + * + * Starting with libpng-1.5.5, if the image being converted has a cHRM chunk + * (including an sRGB chunk) then the chromaticities are used to calculate the + * coefficients. See the chunk handling in pngrutil.c for more information. + * + * In all cases the calculation is to be done in a linear colorspace. If no + * gamma information is available to correct the encoding of the original RGB + * values this results in an implicit assumption that the original PNG RGB + * values were linear. + * + * Other integer coefficients can be used via png_set_rgb_to_gray(). Because + * the API takes just red and green coefficients the blue coefficient is + * calculated to make the sum 32768. This will result in different rounding + * to that used above. + */ +static int +png_do_rgb_to_gray(png_structrp png_ptr, png_row_infop row_info, png_bytep row) +{ + int rgb_error = 0; + + png_debug(1, "in png_do_rgb_to_gray"); + + if ((row_info->color_type & PNG_COLOR_MASK_PALETTE) == 0 && + (row_info->color_type & PNG_COLOR_MASK_COLOR) != 0) + { + png_uint_32 rc = png_ptr->rgb_to_gray_red_coeff; + png_uint_32 gc = png_ptr->rgb_to_gray_green_coeff; + png_uint_32 bc = 32768 - rc - gc; + png_uint_32 row_width = row_info->width; + int have_alpha = (row_info->color_type & PNG_COLOR_MASK_ALPHA) != 0; + + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + /* Notice that gamma to/from 1 are not necessarily inverses (if + * there is an overall gamma correction). Prior to 1.5.5 this code + * checked the linearized values for equality; this doesn't match + * the documentation, the original values must be checked. + */ + if (png_ptr->gamma_from_1 != NULL && png_ptr->gamma_to_1 != NULL) + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_byte red = *(sp++); + png_byte green = *(sp++); + png_byte blue = *(sp++); + + if (red != green || red != blue) + { + red = png_ptr->gamma_to_1[red]; + green = png_ptr->gamma_to_1[green]; + blue = png_ptr->gamma_to_1[blue]; + + rgb_error |= 1; + *(dp++) = png_ptr->gamma_from_1[ + (rc*red + gc*green + bc*blue + 16384)>>15]; + } + + else + { + /* If there is no overall correction the table will not be + * set. + */ + if (png_ptr->gamma_table != NULL) + red = png_ptr->gamma_table[red]; + + *(dp++) = red; + } + + if (have_alpha != 0) + *(dp++) = *(sp++); + } + } + else +#endif + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_byte red = *(sp++); + png_byte green = *(sp++); + png_byte blue = *(sp++); + + if (red != green || red != blue) + { + rgb_error |= 1; + /* NOTE: this is the historical approach which simply + * truncates the results. + */ + *(dp++) = (png_byte)((rc*red + gc*green + bc*blue)>>15); + } + + else + *(dp++) = red; + + if (have_alpha != 0) + *(dp++) = *(sp++); + } + } + } + + else /* RGB bit_depth == 16 */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (png_ptr->gamma_16_to_1 != NULL && png_ptr->gamma_16_from_1 != NULL) + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_uint_16 red, green, blue, w; + png_byte hi,lo; + + hi=*(sp)++; lo=*(sp)++; red = (png_uint_16)((hi << 8) | (lo)); + hi=*(sp)++; lo=*(sp)++; green = (png_uint_16)((hi << 8) | (lo)); + hi=*(sp)++; lo=*(sp)++; blue = (png_uint_16)((hi << 8) | (lo)); + + if (red == green && red == blue) + { + if (png_ptr->gamma_16_table != NULL) + w = png_ptr->gamma_16_table[(red & 0xff) + >> png_ptr->gamma_shift][red >> 8]; + + else + w = red; + } + + else + { + png_uint_16 red_1 = png_ptr->gamma_16_to_1[(red & 0xff) + >> png_ptr->gamma_shift][red>>8]; + png_uint_16 green_1 = + png_ptr->gamma_16_to_1[(green & 0xff) >> + png_ptr->gamma_shift][green>>8]; + png_uint_16 blue_1 = png_ptr->gamma_16_to_1[(blue & 0xff) + >> png_ptr->gamma_shift][blue>>8]; + png_uint_16 gray16 = (png_uint_16)((rc*red_1 + gc*green_1 + + bc*blue_1 + 16384)>>15); + w = png_ptr->gamma_16_from_1[(gray16 & 0xff) >> + png_ptr->gamma_shift][gray16 >> 8]; + rgb_error |= 1; + } + + *(dp++) = (png_byte)((w>>8) & 0xff); + *(dp++) = (png_byte)(w & 0xff); + + if (have_alpha != 0) + { + *(dp++) = *(sp++); + *(dp++) = *(sp++); + } + } + } + else +#endif + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_uint_16 red, green, blue, gray16; + png_byte hi,lo; + + hi=*(sp)++; lo=*(sp)++; red = (png_uint_16)((hi << 8) | (lo)); + hi=*(sp)++; lo=*(sp)++; green = (png_uint_16)((hi << 8) | (lo)); + hi=*(sp)++; lo=*(sp)++; blue = (png_uint_16)((hi << 8) | (lo)); + + if (red != green || red != blue) + rgb_error |= 1; + + /* From 1.5.5 in the 16-bit case do the accurate conversion even + * in the 'fast' case - this is because this is where the code + * ends up when handling linear 16-bit data. + */ + gray16 = (png_uint_16)((rc*red + gc*green + bc*blue + 16384) >> + 15); + *(dp++) = (png_byte)((gray16 >> 8) & 0xff); + *(dp++) = (png_byte)(gray16 & 0xff); + + if (have_alpha != 0) + { + *(dp++) = *(sp++); + *(dp++) = *(sp++); + } + } + } + } + + row_info->channels = (png_byte)(row_info->channels - 2); + row_info->color_type = (png_byte)(row_info->color_type & + ~PNG_COLOR_MASK_COLOR); + row_info->pixel_depth = (png_byte)(row_info->channels * + row_info->bit_depth); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } + return rgb_error; +} +#endif + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) +/* Replace any alpha or transparency with the supplied background color. + * "background" is already in the screen gamma, while "background_1" is + * at a gamma of 1.0. Paletted files have already been taken care of. + */ +static void +png_do_compose(png_row_infop row_info, png_bytep row, png_structrp png_ptr) +{ +#ifdef PNG_READ_GAMMA_SUPPORTED + png_const_bytep gamma_table = png_ptr->gamma_table; + png_const_bytep gamma_from_1 = png_ptr->gamma_from_1; + png_const_bytep gamma_to_1 = png_ptr->gamma_to_1; + png_const_uint_16pp gamma_16 = png_ptr->gamma_16_table; + png_const_uint_16pp gamma_16_from_1 = png_ptr->gamma_16_from_1; + png_const_uint_16pp gamma_16_to_1 = png_ptr->gamma_16_to_1; + int gamma_shift = png_ptr->gamma_shift; + int optimize = (png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0; +#endif + + png_bytep sp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + int shift; + + png_debug(1, "in png_do_compose"); + + switch (row_info->color_type) + { + case PNG_COLOR_TYPE_GRAY: + { + switch (row_info->bit_depth) + { + case 1: + { + sp = row; + shift = 7; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x01) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x7f7f >> (7 - shift)); + tmp |= + (unsigned int)(png_ptr->background.gray << shift); + *sp = (png_byte)(tmp & 0xff); + } + + if (shift == 0) + { + shift = 7; + sp++; + } + + else + shift--; + } + break; + } + + case 2: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + shift = 6; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x03) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x3f3f >> (6 - shift)); + tmp |= + (unsigned int)png_ptr->background.gray << shift; + *sp = (png_byte)(tmp & 0xff); + } + + else + { + unsigned int p = (*sp >> shift) & 0x03; + unsigned int g = (gamma_table [p | (p << 2) | + (p << 4) | (p << 6)] >> 6) & 0x03; + unsigned int tmp = *sp & (0x3f3f >> (6 - shift)); + tmp |= (unsigned int)(g << shift); + *sp = (png_byte)(tmp & 0xff); + } + + if (shift == 0) + { + shift = 6; + sp++; + } + + else + shift -= 2; + } + } + + else +#endif + { + sp = row; + shift = 6; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x03) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x3f3f >> (6 - shift)); + tmp |= + (unsigned int)png_ptr->background.gray << shift; + *sp = (png_byte)(tmp & 0xff); + } + + if (shift == 0) + { + shift = 6; + sp++; + } + + else + shift -= 2; + } + } + break; + } + + case 4: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + shift = 4; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x0f) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x0f0f >> (4 - shift)); + tmp |= + (unsigned int)(png_ptr->background.gray << shift); + *sp = (png_byte)(tmp & 0xff); + } + + else + { + unsigned int p = (*sp >> shift) & 0x0f; + unsigned int g = (gamma_table[p | (p << 4)] >> 4) & + 0x0f; + unsigned int tmp = *sp & (0x0f0f >> (4 - shift)); + tmp |= (unsigned int)(g << shift); + *sp = (png_byte)(tmp & 0xff); + } + + if (shift == 0) + { + shift = 4; + sp++; + } + + else + shift -= 4; + } + } + + else +#endif + { + sp = row; + shift = 4; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x0f) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x0f0f >> (4 - shift)); + tmp |= + (unsigned int)(png_ptr->background.gray << shift); + *sp = (png_byte)(tmp & 0xff); + } + + if (shift == 0) + { + shift = 4; + sp++; + } + + else + shift -= 4; + } + } + break; + } + + case 8: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp++) + { + if (*sp == png_ptr->trans_color.gray) + *sp = (png_byte)png_ptr->background.gray; + + else + *sp = gamma_table[*sp]; + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp++) + { + if (*sp == png_ptr->trans_color.gray) + *sp = (png_byte)png_ptr->background.gray; + } + } + break; + } + + case 16: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_uint_16 v; + + v = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + if (v == png_ptr->trans_color.gray) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray + & 0xff); + } + + else + { + v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_uint_16 v; + + v = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + if (v == png_ptr->trans_color.gray) + { + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray + & 0xff); + } + } + } + break; + } + + default: + break; + } + break; + } + + case PNG_COLOR_TYPE_RGB: + { + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 3) + { + if (*sp == png_ptr->trans_color.red && + *(sp + 1) == png_ptr->trans_color.green && + *(sp + 2) == png_ptr->trans_color.blue) + { + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + + else + { + *sp = gamma_table[*sp]; + *(sp + 1) = gamma_table[*(sp + 1)]; + *(sp + 2) = gamma_table[*(sp + 2)]; + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 3) + { + if (*sp == png_ptr->trans_color.red && + *(sp + 1) == png_ptr->trans_color.green && + *(sp + 2) == png_ptr->trans_color.blue) + { + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + } + } + } + else /* if (row_info->bit_depth == 16) */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 6) + { + png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + png_uint_16 g = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + png_uint_16 b = (png_uint_16)(((*(sp + 4)) << 8) + + *(sp + 5)); + + if (r == png_ptr->trans_color.red && + g == png_ptr->trans_color.green && + b == png_ptr->trans_color.blue) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + + else + { + png_uint_16 v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 3) >> gamma_shift][*(sp + 2)]; + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 5) >> gamma_shift][*(sp + 4)]; + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); + } + } + } + + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 6) + { + png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + png_uint_16 g = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + png_uint_16 b = (png_uint_16)(((*(sp + 4)) << 8) + + *(sp + 5)); + + if (r == png_ptr->trans_color.red && + g == png_ptr->trans_color.green && + b == png_ptr->trans_color.blue) + { + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + } + } + } + break; + } + + case PNG_COLOR_TYPE_GRAY_ALPHA: + { + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_to_1 != NULL && gamma_from_1 != NULL && + gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_uint_16 a = *(sp + 1); + + if (a == 0xff) + *sp = gamma_table[*sp]; + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)png_ptr->background.gray; + } + + else + { + png_byte v, w; + + v = gamma_to_1[*sp]; + png_composite(w, v, a, png_ptr->background_1.gray); + if (optimize == 0) + w = gamma_from_1[w]; + *sp = w; + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_byte a = *(sp + 1); + + if (a == 0) + *sp = (png_byte)png_ptr->background.gray; + + else if (a < 0xff) + png_composite(*sp, *sp, a, png_ptr->background.gray); + } + } + } + else /* if (png_ptr->bit_depth == 16) */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL && gamma_16_from_1 != NULL && + gamma_16_to_1 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_uint_16 a = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + if (a == (png_uint_16)0xffff) + { + png_uint_16 v; + + v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + } + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + } + + else + { + png_uint_16 g, v, w; + + g = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; + png_composite_16(v, g, a, png_ptr->background_1.gray); + if (optimize != 0) + w = v; + else + w = gamma_16_from_1[(v & 0xff) >> + gamma_shift][v >> 8]; + *sp = (png_byte)((w >> 8) & 0xff); + *(sp + 1) = (png_byte)(w & 0xff); + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_uint_16 a = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + if (a == 0) + { + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + } + + else if (a < 0xffff) + { + png_uint_16 g, v; + + g = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + png_composite_16(v, g, a, png_ptr->background.gray); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + } + } + } + } + break; + } + + case PNG_COLOR_TYPE_RGB_ALPHA: + { + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_to_1 != NULL && gamma_from_1 != NULL && + gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_byte a = *(sp + 3); + + if (a == 0xff) + { + *sp = gamma_table[*sp]; + *(sp + 1) = gamma_table[*(sp + 1)]; + *(sp + 2) = gamma_table[*(sp + 2)]; + } + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + + else + { + png_byte v, w; + + v = gamma_to_1[*sp]; + png_composite(w, v, a, png_ptr->background_1.red); + if (optimize == 0) w = gamma_from_1[w]; + *sp = w; + + v = gamma_to_1[*(sp + 1)]; + png_composite(w, v, a, png_ptr->background_1.green); + if (optimize == 0) w = gamma_from_1[w]; + *(sp + 1) = w; + + v = gamma_to_1[*(sp + 2)]; + png_composite(w, v, a, png_ptr->background_1.blue); + if (optimize == 0) w = gamma_from_1[w]; + *(sp + 2) = w; + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_byte a = *(sp + 3); + + if (a == 0) + { + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + + else if (a < 0xff) + { + png_composite(*sp, *sp, a, png_ptr->background.red); + + png_composite(*(sp + 1), *(sp + 1), a, + png_ptr->background.green); + + png_composite(*(sp + 2), *(sp + 2), a, + png_ptr->background.blue); + } + } + } + } + else /* if (row_info->bit_depth == 16) */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL && gamma_16_from_1 != NULL && + gamma_16_to_1 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 8) + { + png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6)) + << 8) + (png_uint_16)(*(sp + 7))); + + if (a == (png_uint_16)0xffff) + { + png_uint_16 v; + + v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 3) >> gamma_shift][*(sp + 2)]; + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 5) >> gamma_shift][*(sp + 4)]; + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); + } + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + + else + { + png_uint_16 v, w; + + v = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; + png_composite_16(w, v, a, png_ptr->background_1.red); + if (optimize == 0) + w = gamma_16_from_1[((w & 0xff) >> gamma_shift)][w >> + 8]; + *sp = (png_byte)((w >> 8) & 0xff); + *(sp + 1) = (png_byte)(w & 0xff); + + v = gamma_16_to_1[*(sp + 3) >> gamma_shift][*(sp + 2)]; + png_composite_16(w, v, a, png_ptr->background_1.green); + if (optimize == 0) + w = gamma_16_from_1[((w & 0xff) >> gamma_shift)][w >> + 8]; + + *(sp + 2) = (png_byte)((w >> 8) & 0xff); + *(sp + 3) = (png_byte)(w & 0xff); + + v = gamma_16_to_1[*(sp + 5) >> gamma_shift][*(sp + 4)]; + png_composite_16(w, v, a, png_ptr->background_1.blue); + if (optimize == 0) + w = gamma_16_from_1[((w & 0xff) >> gamma_shift)][w >> + 8]; + + *(sp + 4) = (png_byte)((w >> 8) & 0xff); + *(sp + 5) = (png_byte)(w & 0xff); + } + } + } + + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 8) + { + png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6)) + << 8) + (png_uint_16)(*(sp + 7))); + + if (a == 0) + { + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + + else if (a < 0xffff) + { + png_uint_16 v; + + png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + png_uint_16 g = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + png_uint_16 b = (png_uint_16)(((*(sp + 4)) << 8) + + *(sp + 5)); + + png_composite_16(v, r, a, png_ptr->background.red); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + + png_composite_16(v, g, a, png_ptr->background.green); + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); + + png_composite_16(v, b, a, png_ptr->background.blue); + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); + } + } + } + } + break; + } + + default: + break; + } +} +#endif /* READ_BACKGROUND || READ_ALPHA_MODE */ + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* Gamma correct the image, avoiding the alpha channel. Make sure + * you do this after you deal with the transparency issue on grayscale + * or RGB images. If your bit depth is 8, use gamma_table, if it + * is 16, use gamma_16_table and gamma_shift. Build these with + * build_gamma_table(). + */ +static void +png_do_gamma(png_row_infop row_info, png_bytep row, png_structrp png_ptr) +{ + png_const_bytep gamma_table = png_ptr->gamma_table; + png_const_uint_16pp gamma_16_table = png_ptr->gamma_16_table; + int gamma_shift = png_ptr->gamma_shift; + + png_bytep sp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_gamma"); + + if (((row_info->bit_depth <= 8 && gamma_table != NULL) || + (row_info->bit_depth == 16 && gamma_16_table != NULL))) + { + switch (row_info->color_type) + { + case PNG_COLOR_TYPE_RGB: + { + if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp++; + *sp = gamma_table[*sp]; + sp++; + *sp = gamma_table[*sp]; + sp++; + } + } + + else /* if (row_info->bit_depth == 16) */ + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + } + } + break; + } + + case PNG_COLOR_TYPE_RGB_ALPHA: + { + if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp++; + + *sp = gamma_table[*sp]; + sp++; + + *sp = gamma_table[*sp]; + sp++; + + sp++; + } + } + + else /* if (row_info->bit_depth == 16) */ + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 4; + } + } + break; + } + + case PNG_COLOR_TYPE_GRAY_ALPHA: + { + if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp += 2; + } + } + + else /* if (row_info->bit_depth == 16) */ + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 4; + } + } + break; + } + + case PNG_COLOR_TYPE_GRAY: + { + if (row_info->bit_depth == 2) + { + sp = row; + for (i = 0; i < row_width; i += 4) + { + int a = *sp & 0xc0; + int b = *sp & 0x30; + int c = *sp & 0x0c; + int d = *sp & 0x03; + + *sp = (png_byte)( + ((((int)gamma_table[a|(a>>2)|(a>>4)|(a>>6)]) ) & 0xc0)| + ((((int)gamma_table[(b<<2)|b|(b>>2)|(b>>4)])>>2) & 0x30)| + ((((int)gamma_table[(c<<4)|(c<<2)|c|(c>>2)])>>4) & 0x0c)| + ((((int)gamma_table[(d<<6)|(d<<4)|(d<<2)|d])>>6) )); + sp++; + } + } + + if (row_info->bit_depth == 4) + { + sp = row; + for (i = 0; i < row_width; i += 2) + { + int msb = *sp & 0xf0; + int lsb = *sp & 0x0f; + + *sp = (png_byte)((((int)gamma_table[msb | (msb >> 4)]) & 0xf0) + | (((int)gamma_table[(lsb << 4) | lsb]) >> 4)); + sp++; + } + } + + else if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp++; + } + } + + else if (row_info->bit_depth == 16) + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + } + } + break; + } + + default: + break; + } + } +} +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +/* Encode the alpha channel to the output gamma (the input channel is always + * linear.) Called only with color types that have an alpha channel. Needs the + * from_1 tables. + */ +static void +png_do_encode_alpha(png_row_infop row_info, png_bytep row, png_structrp png_ptr) +{ + png_uint_32 row_width = row_info->width; + + png_debug(1, "in png_do_encode_alpha"); + + if ((row_info->color_type & PNG_COLOR_MASK_ALPHA) != 0) + { + if (row_info->bit_depth == 8) + { + png_bytep table = png_ptr->gamma_from_1; + + if (table != NULL) + { + int step = (row_info->color_type & PNG_COLOR_MASK_COLOR) ? 4 : 2; + + /* The alpha channel is the last component: */ + row += step - 1; + + for (; row_width > 0; --row_width, row += step) + *row = table[*row]; + + return; + } + } + + else if (row_info->bit_depth == 16) + { + png_uint_16pp table = png_ptr->gamma_16_from_1; + int gamma_shift = png_ptr->gamma_shift; + + if (table != NULL) + { + int step = (row_info->color_type & PNG_COLOR_MASK_COLOR) ? 8 : 4; + + /* The alpha channel is the last component: */ + row += step - 2; + + for (; row_width > 0; --row_width, row += step) + { + png_uint_16 v; + + v = table[*(row + 1) >> gamma_shift][*row]; + *row = (png_byte)((v >> 8) & 0xff); + *(row + 1) = (png_byte)(v & 0xff); + } + + return; + } + } + } + + /* Only get to here if called with a weird row_info; no harm has been done, + * so just issue a warning. + */ + png_warning(png_ptr, "png_do_encode_alpha: unexpected call"); +} +#endif + +#ifdef PNG_READ_EXPAND_SUPPORTED +/* Expands a palette row to an RGB or RGBA row depending + * upon whether you supply trans and num_trans. + */ +static void +png_do_expand_palette(png_structrp png_ptr, png_row_infop row_info, + png_bytep row, png_const_colorp palette, png_const_bytep trans_alpha, + int num_trans) +{ + int shift, value; + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_expand_palette"); + + if (row_info->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (row_info->bit_depth < 8) + { + switch (row_info->bit_depth) + { + case 1: + { + sp = row + (size_t)((row_width - 1) >> 3); + dp = row + (size_t)row_width - 1; + shift = 7 - (int)((row_width + 7) & 0x07); + for (i = 0; i < row_width; i++) + { + if ((*sp >> shift) & 0x01) + *dp = 1; + + else + *dp = 0; + + if (shift == 7) + { + shift = 0; + sp--; + } + + else + shift++; + + dp--; + } + break; + } + + case 2: + { + sp = row + (size_t)((row_width - 1) >> 2); + dp = row + (size_t)row_width - 1; + shift = (int)((3 - ((row_width + 3) & 0x03)) << 1); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x03; + *dp = (png_byte)value; + if (shift == 6) + { + shift = 0; + sp--; + } + + else + shift += 2; + + dp--; + } + break; + } + + case 4: + { + sp = row + (size_t)((row_width - 1) >> 1); + dp = row + (size_t)row_width - 1; + shift = (int)((row_width & 0x01) << 2); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x0f; + *dp = (png_byte)value; + if (shift == 4) + { + shift = 0; + sp--; + } + + else + shift += 4; + + dp--; + } + break; + } + + default: + break; + } + row_info->bit_depth = 8; + row_info->pixel_depth = 8; + row_info->rowbytes = row_width; + } + + if (row_info->bit_depth == 8) + { + { + if (num_trans > 0) + { + sp = row + (size_t)row_width - 1; + dp = row + ((size_t)row_width << 2) - 1; + + i = 0; +#ifdef PNG_ARM_NEON_INTRINSICS_AVAILABLE + if (png_ptr->riffled_palette != NULL) + { + /* The RGBA optimization works with png_ptr->bit_depth == 8 + * but sometimes row_info->bit_depth has been changed to 8. + * In these cases, the palette hasn't been riffled. + */ + i = png_do_expand_palette_rgba8_neon(png_ptr, row_info, row, + &sp, &dp); + } +#else + PNG_UNUSED(png_ptr) +#endif + + for (; i < row_width; i++) + { + if ((int)(*sp) >= num_trans) + *dp-- = 0xff; + else + *dp-- = trans_alpha[*sp]; + *dp-- = palette[*sp].blue; + *dp-- = palette[*sp].green; + *dp-- = palette[*sp].red; + sp--; + } + row_info->bit_depth = 8; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + row_info->color_type = 6; + row_info->channels = 4; + } + + else + { + sp = row + (size_t)row_width - 1; + dp = row + (size_t)(row_width * 3) - 1; + i = 0; +#ifdef PNG_ARM_NEON_INTRINSICS_AVAILABLE + i = png_do_expand_palette_rgb8_neon(png_ptr, row_info, row, + &sp, &dp); +#else + PNG_UNUSED(png_ptr) +#endif + + for (; i < row_width; i++) + { + *dp-- = palette[*sp].blue; + *dp-- = palette[*sp].green; + *dp-- = palette[*sp].red; + sp--; + } + + row_info->bit_depth = 8; + row_info->pixel_depth = 24; + row_info->rowbytes = row_width * 3; + row_info->color_type = 2; + row_info->channels = 3; + } + } + } + } +} + +/* If the bit depth < 8, it is expanded to 8. Also, if the already + * expanded transparency value is supplied, an alpha channel is built. + */ +static void +png_do_expand(png_row_infop row_info, png_bytep row, + png_const_color_16p trans_color) +{ + int shift, value; + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_expand"); + + if (row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + unsigned int gray = trans_color != NULL ? trans_color->gray : 0; + + if (row_info->bit_depth < 8) + { + switch (row_info->bit_depth) + { + case 1: + { + gray = (gray & 0x01) * 0xff; + sp = row + (size_t)((row_width - 1) >> 3); + dp = row + (size_t)row_width - 1; + shift = 7 - (int)((row_width + 7) & 0x07); + for (i = 0; i < row_width; i++) + { + if ((*sp >> shift) & 0x01) + *dp = 0xff; + + else + *dp = 0; + + if (shift == 7) + { + shift = 0; + sp--; + } + + else + shift++; + + dp--; + } + break; + } + + case 2: + { + gray = (gray & 0x03) * 0x55; + sp = row + (size_t)((row_width - 1) >> 2); + dp = row + (size_t)row_width - 1; + shift = (int)((3 - ((row_width + 3) & 0x03)) << 1); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x03; + *dp = (png_byte)(value | (value << 2) | (value << 4) | + (value << 6)); + if (shift == 6) + { + shift = 0; + sp--; + } + + else + shift += 2; + + dp--; + } + break; + } + + case 4: + { + gray = (gray & 0x0f) * 0x11; + sp = row + (size_t)((row_width - 1) >> 1); + dp = row + (size_t)row_width - 1; + shift = (int)((1 - ((row_width + 1) & 0x01)) << 2); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x0f; + *dp = (png_byte)(value | (value << 4)); + if (shift == 4) + { + shift = 0; + sp--; + } + + else + shift = 4; + + dp--; + } + break; + } + + default: + break; + } + + row_info->bit_depth = 8; + row_info->pixel_depth = 8; + row_info->rowbytes = row_width; + } + + if (trans_color != NULL) + { + if (row_info->bit_depth == 8) + { + gray = gray & 0xff; + sp = row + (size_t)row_width - 1; + dp = row + ((size_t)row_width << 1) - 1; + + for (i = 0; i < row_width; i++) + { + if ((*sp & 0xffU) == gray) + *dp-- = 0; + + else + *dp-- = 0xff; + + *dp-- = *sp--; + } + } + + else if (row_info->bit_depth == 16) + { + unsigned int gray_high = (gray >> 8) & 0xff; + unsigned int gray_low = gray & 0xff; + sp = row + row_info->rowbytes - 1; + dp = row + (row_info->rowbytes << 1) - 1; + for (i = 0; i < row_width; i++) + { + if ((*(sp - 1) & 0xffU) == gray_high && + (*(sp) & 0xffU) == gray_low) + { + *dp-- = 0; + *dp-- = 0; + } + + else + { + *dp-- = 0xff; + *dp-- = 0xff; + } + + *dp-- = *sp--; + *dp-- = *sp--; + } + } + + row_info->color_type = PNG_COLOR_TYPE_GRAY_ALPHA; + row_info->channels = 2; + row_info->pixel_depth = (png_byte)(row_info->bit_depth << 1); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, + row_width); + } + } + else if (row_info->color_type == PNG_COLOR_TYPE_RGB && + trans_color != NULL) + { + if (row_info->bit_depth == 8) + { + png_byte red = (png_byte)(trans_color->red & 0xff); + png_byte green = (png_byte)(trans_color->green & 0xff); + png_byte blue = (png_byte)(trans_color->blue & 0xff); + sp = row + (size_t)row_info->rowbytes - 1; + dp = row + ((size_t)row_width << 2) - 1; + for (i = 0; i < row_width; i++) + { + if (*(sp - 2) == red && *(sp - 1) == green && *(sp) == blue) + *dp-- = 0; + + else + *dp-- = 0xff; + + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + } + } + else if (row_info->bit_depth == 16) + { + png_byte red_high = (png_byte)((trans_color->red >> 8) & 0xff); + png_byte green_high = (png_byte)((trans_color->green >> 8) & 0xff); + png_byte blue_high = (png_byte)((trans_color->blue >> 8) & 0xff); + png_byte red_low = (png_byte)(trans_color->red & 0xff); + png_byte green_low = (png_byte)(trans_color->green & 0xff); + png_byte blue_low = (png_byte)(trans_color->blue & 0xff); + sp = row + row_info->rowbytes - 1; + dp = row + ((size_t)row_width << 3) - 1; + for (i = 0; i < row_width; i++) + { + if (*(sp - 5) == red_high && + *(sp - 4) == red_low && + *(sp - 3) == green_high && + *(sp - 2) == green_low && + *(sp - 1) == blue_high && + *(sp ) == blue_low) + { + *dp-- = 0; + *dp-- = 0; + } + + else + { + *dp-- = 0xff; + *dp-- = 0xff; + } + + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + } + } + row_info->color_type = PNG_COLOR_TYPE_RGB_ALPHA; + row_info->channels = 4; + row_info->pixel_depth = (png_byte)(row_info->bit_depth << 2); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } +} +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED +/* If the bit depth is 8 and the color type is not a palette type expand the + * whole row to 16 bits. Has no effect otherwise. + */ +static void +png_do_expand_16(png_row_infop row_info, png_bytep row) +{ + if (row_info->bit_depth == 8 && + row_info->color_type != PNG_COLOR_TYPE_PALETTE) + { + /* The row have a sequence of bytes containing [0..255] and we need + * to turn it into another row containing [0..65535], to do this we + * calculate: + * + * (input / 255) * 65535 + * + * Which happens to be exactly input * 257 and this can be achieved + * simply by byte replication in place (copying backwards). + */ + png_byte *sp = row + row_info->rowbytes; /* source, last byte + 1 */ + png_byte *dp = sp + row_info->rowbytes; /* destination, end + 1 */ + while (dp > sp) + { + dp[-2] = dp[-1] = *--sp; dp -= 2; + } + + row_info->rowbytes *= 2; + row_info->bit_depth = 16; + row_info->pixel_depth = (png_byte)(row_info->channels * 16); + } +} +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +static void +png_do_quantize(png_row_infop row_info, png_bytep row, + png_const_bytep palette_lookup, png_const_bytep quantize_lookup) +{ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_quantize"); + + if (row_info->bit_depth == 8) + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB && palette_lookup) + { + int r, g, b, p; + sp = row; + dp = row; + for (i = 0; i < row_width; i++) + { + r = *sp++; + g = *sp++; + b = *sp++; + + /* This looks real messy, but the compiler will reduce + * it down to a reasonable formula. For example, with + * 5 bits per color, we get: + * p = (((r >> 3) & 0x1f) << 10) | + * (((g >> 3) & 0x1f) << 5) | + * ((b >> 3) & 0x1f); + */ + p = (((r >> (8 - PNG_QUANTIZE_RED_BITS)) & + ((1 << PNG_QUANTIZE_RED_BITS) - 1)) << + (PNG_QUANTIZE_GREEN_BITS + PNG_QUANTIZE_BLUE_BITS)) | + (((g >> (8 - PNG_QUANTIZE_GREEN_BITS)) & + ((1 << PNG_QUANTIZE_GREEN_BITS) - 1)) << + (PNG_QUANTIZE_BLUE_BITS)) | + ((b >> (8 - PNG_QUANTIZE_BLUE_BITS)) & + ((1 << PNG_QUANTIZE_BLUE_BITS) - 1)); + + *dp++ = palette_lookup[p]; + } + + row_info->color_type = PNG_COLOR_TYPE_PALETTE; + row_info->channels = 1; + row_info->pixel_depth = row_info->bit_depth; + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA && + palette_lookup != NULL) + { + int r, g, b, p; + sp = row; + dp = row; + for (i = 0; i < row_width; i++) + { + r = *sp++; + g = *sp++; + b = *sp++; + sp++; + + p = (((r >> (8 - PNG_QUANTIZE_RED_BITS)) & + ((1 << PNG_QUANTIZE_RED_BITS) - 1)) << + (PNG_QUANTIZE_GREEN_BITS + PNG_QUANTIZE_BLUE_BITS)) | + (((g >> (8 - PNG_QUANTIZE_GREEN_BITS)) & + ((1 << PNG_QUANTIZE_GREEN_BITS) - 1)) << + (PNG_QUANTIZE_BLUE_BITS)) | + ((b >> (8 - PNG_QUANTIZE_BLUE_BITS)) & + ((1 << PNG_QUANTIZE_BLUE_BITS) - 1)); + + *dp++ = palette_lookup[p]; + } + + row_info->color_type = PNG_COLOR_TYPE_PALETTE; + row_info->channels = 1; + row_info->pixel_depth = row_info->bit_depth; + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } + + else if (row_info->color_type == PNG_COLOR_TYPE_PALETTE && + quantize_lookup) + { + sp = row; + + for (i = 0; i < row_width; i++, sp++) + { + *sp = quantize_lookup[*sp]; + } + } + } +} +#endif /* READ_QUANTIZE */ + +/* Transform the row. The order of transformations is significant, + * and is very touchy. If you add a transformation, take care to + * decide how it fits in with the other transformations here. + */ +void /* PRIVATE */ +png_do_read_transformations(png_structrp png_ptr, png_row_infop row_info) +{ + png_debug(1, "in png_do_read_transformations"); + + if (png_ptr->row_buf == NULL) + { + /* Prior to 1.5.4 this output row/pass where the NULL pointer is, but this + * error is incredibly rare and incredibly easy to debug without this + * information. + */ + png_error(png_ptr, "NULL row buffer"); + } + + /* The following is debugging; prior to 1.5.4 the code was never compiled in; + * in 1.5.4 PNG_FLAG_DETECT_UNINITIALIZED was added and the macro + * PNG_WARN_UNINITIALIZED_ROW removed. In 1.6 the new flag is set only for + * all transformations, however in practice the ROW_INIT always gets done on + * demand, if necessary. + */ + if ((png_ptr->flags & PNG_FLAG_DETECT_UNINITIALIZED) != 0 && + (png_ptr->flags & PNG_FLAG_ROW_INIT) == 0) + { + /* Application has failed to call either png_read_start_image() or + * png_read_update_info() after setting transforms that expand pixels. + * This check added to libpng-1.2.19 (but not enabled until 1.5.4). + */ + png_error(png_ptr, "Uninitialized row"); + } + +#ifdef PNG_READ_EXPAND_SUPPORTED + if ((png_ptr->transformations & PNG_EXPAND) != 0) + { + if (row_info->color_type == PNG_COLOR_TYPE_PALETTE) + { +#ifdef PNG_ARM_NEON_INTRINSICS_AVAILABLE + if ((png_ptr->num_trans > 0) && (png_ptr->bit_depth == 8)) + { + if (png_ptr->riffled_palette == NULL) + { + /* Initialize the accelerated palette expansion. */ + png_ptr->riffled_palette = + (png_bytep)png_malloc(png_ptr, 256 * 4); + png_riffle_palette_neon(png_ptr); + } + } +#endif + png_do_expand_palette(png_ptr, row_info, png_ptr->row_buf + 1, + png_ptr->palette, png_ptr->trans_alpha, png_ptr->num_trans); + } + + else + { + if (png_ptr->num_trans != 0 && + (png_ptr->transformations & PNG_EXPAND_tRNS) != 0) + png_do_expand(row_info, png_ptr->row_buf + 1, + &(png_ptr->trans_color)); + + else + png_do_expand(row_info, png_ptr->row_buf + 1, NULL); + } + } +#endif + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) != 0 && + (png_ptr->transformations & PNG_COMPOSE) == 0 && + (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) + png_do_strip_channel(row_info, png_ptr->row_buf + 1, + 0 /* at_start == false, because SWAP_ALPHA happens later */); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) != 0) + { + int rgb_error = + png_do_rgb_to_gray(png_ptr, row_info, + png_ptr->row_buf + 1); + + if (rgb_error != 0) + { + png_ptr->rgb_to_gray_status=1; + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) == + PNG_RGB_TO_GRAY_WARN) + png_warning(png_ptr, "png_do_rgb_to_gray found nongray pixel"); + + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) == + PNG_RGB_TO_GRAY_ERR) + png_error(png_ptr, "png_do_rgb_to_gray found nongray pixel"); + } + } +#endif + +/* From Andreas Dilger e-mail to png-implement, 26 March 1998: + * + * In most cases, the "simple transparency" should be done prior to doing + * gray-to-RGB, or you will have to test 3x as many bytes to check if a + * pixel is transparent. You would also need to make sure that the + * transparency information is upgraded to RGB. + * + * To summarize, the current flow is: + * - Gray + simple transparency -> compare 1 or 2 gray bytes and composite + * with background "in place" if transparent, + * convert to RGB if necessary + * - Gray + alpha -> composite with gray background and remove alpha bytes, + * convert to RGB if necessary + * + * To support RGB backgrounds for gray images we need: + * - Gray + simple transparency -> convert to RGB + simple transparency, + * compare 3 or 6 bytes and composite with + * background "in place" if transparent + * (3x compare/pixel compared to doing + * composite with gray bkgrnd) + * - Gray + alpha -> convert to RGB + alpha, composite with background and + * remove alpha bytes (3x float + * operations/pixel compared with composite + * on gray background) + * + * Greg's change will do this. The reason it wasn't done before is for + * performance, as this increases the per-pixel operations. If we would check + * in advance if the background was gray or RGB, and position the gray-to-RGB + * transform appropriately, then it would save a lot of work/time. + */ + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + /* If gray -> RGB, do so now only if background is non-gray; else do later + * for performance reasons + */ + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) != 0 && + (png_ptr->mode & PNG_BACKGROUND_IS_GRAY) == 0) + png_do_gray_to_rgb(row_info, png_ptr->row_buf + 1); +#endif + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + if ((png_ptr->transformations & PNG_COMPOSE) != 0) + png_do_compose(row_info, png_ptr->row_buf + 1, png_ptr); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED + if ((png_ptr->transformations & PNG_GAMMA) != 0 && +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + /* Because RGB_TO_GRAY does the gamma transform. */ + (png_ptr->transformations & PNG_RGB_TO_GRAY) == 0 && +#endif +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + /* Because PNG_COMPOSE does the gamma transform if there is something to + * do (if there is an alpha channel or transparency.) + */ + !((png_ptr->transformations & PNG_COMPOSE) != 0 && + ((png_ptr->num_trans != 0) || + (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0)) && +#endif + /* Because png_init_read_transformations transforms the palette, unless + * RGB_TO_GRAY will do the transform. + */ + (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE)) + png_do_gamma(row_info, png_ptr->row_buf + 1, png_ptr); +#endif + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) != 0 && + (png_ptr->transformations & PNG_COMPOSE) != 0 && + (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) + png_do_strip_channel(row_info, png_ptr->row_buf + 1, + 0 /* at_start == false, because SWAP_ALPHA happens later */); +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + if ((png_ptr->transformations & PNG_ENCODE_ALPHA) != 0 && + (row_info->color_type & PNG_COLOR_MASK_ALPHA) != 0) + png_do_encode_alpha(row_info, png_ptr->row_buf + 1, png_ptr); +#endif + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + if ((png_ptr->transformations & PNG_SCALE_16_TO_8) != 0) + png_do_scale_16_to_8(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + /* There is no harm in doing both of these because only one has any effect, + * by putting the 'scale' option first if the app asks for scale (either by + * calling the API or in a TRANSFORM flag) this is what happens. + */ + if ((png_ptr->transformations & PNG_16_TO_8) != 0) + png_do_chop(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + if ((png_ptr->transformations & PNG_QUANTIZE) != 0) + { + png_do_quantize(row_info, png_ptr->row_buf + 1, + png_ptr->palette_lookup, png_ptr->quantize_index); + + if (row_info->rowbytes == 0) + png_error(png_ptr, "png_do_quantize returned rowbytes=0"); + } +#endif /* READ_QUANTIZE */ + +#ifdef PNG_READ_EXPAND_16_SUPPORTED + /* Do the expansion now, after all the arithmetic has been done. Notice + * that previous transformations can handle the PNG_EXPAND_16 flag if this + * is efficient (particularly true in the case of gamma correction, where + * better accuracy results faster!) + */ + if ((png_ptr->transformations & PNG_EXPAND_16) != 0) + png_do_expand_16(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + /* NOTE: moved here in 1.5.4 (from much later in this list.) */ + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) != 0 && + (png_ptr->mode & PNG_BACKGROUND_IS_GRAY) != 0) + png_do_gray_to_rgb(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_INVERT_SUPPORTED + if ((png_ptr->transformations & PNG_INVERT_MONO) != 0) + png_do_invert(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_INVERT_ALPHA) != 0) + png_do_read_invert_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_SHIFT_SUPPORTED + if ((png_ptr->transformations & PNG_SHIFT) != 0) + png_do_unshift(row_info, png_ptr->row_buf + 1, + &(png_ptr->shift)); +#endif + +#ifdef PNG_READ_PACK_SUPPORTED + if ((png_ptr->transformations & PNG_PACK) != 0) + png_do_unpack(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Added at libpng-1.5.10 */ + if (row_info->color_type == PNG_COLOR_TYPE_PALETTE && + png_ptr->num_palette_max >= 0) + png_do_check_palette_indexes(png_ptr, row_info); +#endif + +#ifdef PNG_READ_BGR_SUPPORTED + if ((png_ptr->transformations & PNG_BGR) != 0) + png_do_bgr(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if ((png_ptr->transformations & PNG_PACKSWAP) != 0) + png_do_packswap(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_FILLER_SUPPORTED + if ((png_ptr->transformations & PNG_FILLER) != 0) + png_do_read_filler(row_info, png_ptr->row_buf + 1, + (png_uint_32)png_ptr->filler, png_ptr->flags); +#endif + +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_SWAP_ALPHA) != 0) + png_do_read_swap_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_16BIT_SUPPORTED +#ifdef PNG_READ_SWAP_SUPPORTED + if ((png_ptr->transformations & PNG_SWAP_BYTES) != 0) + png_do_swap(row_info, png_ptr->row_buf + 1); +#endif +#endif + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + if ((png_ptr->transformations & PNG_USER_TRANSFORM) != 0) + { + if (png_ptr->read_user_transform_fn != NULL) + (*(png_ptr->read_user_transform_fn)) /* User read transform function */ + (png_ptr, /* png_ptr */ + row_info, /* row_info: */ + /* png_uint_32 width; width of row */ + /* size_t rowbytes; number of bytes in row */ + /* png_byte color_type; color type of pixels */ + /* png_byte bit_depth; bit depth of samples */ + /* png_byte channels; number of channels (1-4) */ + /* png_byte pixel_depth; bits per pixel (depth*channels) */ + png_ptr->row_buf + 1); /* start of pixel data for row */ +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED + if (png_ptr->user_transform_depth != 0) + row_info->bit_depth = png_ptr->user_transform_depth; + + if (png_ptr->user_transform_channels != 0) + row_info->channels = png_ptr->user_transform_channels; +#endif + row_info->pixel_depth = (png_byte)(row_info->bit_depth * + row_info->channels); + + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_info->width); + } +#endif +} + +#endif /* READ_TRANSFORMS */ +#endif /* READ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrutil.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrutil.c new file mode 100644 index 0000000..d5fa08c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngrutil.c @@ -0,0 +1,4681 @@ + +/* pngrutil.c - utilities to read a PNG file + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file contains routines that are only called from within + * libpng itself during the course of reading an image. + */ + +#include "pngpriv.h" + +#ifdef PNG_READ_SUPPORTED + +png_uint_32 PNGAPI +png_get_uint_31(png_const_structrp png_ptr, png_const_bytep buf) +{ + png_uint_32 uval = png_get_uint_32(buf); + + if (uval > PNG_UINT_31_MAX) + png_error(png_ptr, "PNG unsigned integer out of range"); + + return (uval); +} + +#if defined(PNG_READ_gAMA_SUPPORTED) || defined(PNG_READ_cHRM_SUPPORTED) +/* The following is a variation on the above for use with the fixed + * point values used for gAMA and cHRM. Instead of png_error it + * issues a warning and returns (-1) - an invalid value because both + * gAMA and cHRM use *unsigned* integers for fixed point values. + */ +#define PNG_FIXED_ERROR (-1) + +static png_fixed_point /* PRIVATE */ +png_get_fixed_point(png_structrp png_ptr, png_const_bytep buf) +{ + png_uint_32 uval = png_get_uint_32(buf); + + if (uval <= PNG_UINT_31_MAX) + return (png_fixed_point)uval; /* known to be in range */ + + /* The caller can turn off the warning by passing NULL. */ + if (png_ptr != NULL) + png_warning(png_ptr, "PNG fixed point integer out of range"); + + return PNG_FIXED_ERROR; +} +#endif + +#ifdef PNG_READ_INT_FUNCTIONS_SUPPORTED +/* NOTE: the read macros will obscure these definitions, so that if + * PNG_USE_READ_MACROS is set the library will not use them internally, + * but the APIs will still be available externally. + * + * The parentheses around "PNGAPI function_name" in the following three + * functions are necessary because they allow the macros to co-exist with + * these (unused but exported) functions. + */ + +/* Grab an unsigned 32-bit integer from a buffer in big-endian format. */ +png_uint_32 (PNGAPI +png_get_uint_32)(png_const_bytep buf) +{ + png_uint_32 uval = + ((png_uint_32)(*(buf )) << 24) + + ((png_uint_32)(*(buf + 1)) << 16) + + ((png_uint_32)(*(buf + 2)) << 8) + + ((png_uint_32)(*(buf + 3)) ) ; + + return uval; +} + +/* Grab a signed 32-bit integer from a buffer in big-endian format. The + * data is stored in the PNG file in two's complement format and there + * is no guarantee that a 'png_int_32' is exactly 32 bits, therefore + * the following code does a two's complement to native conversion. + */ +png_int_32 (PNGAPI +png_get_int_32)(png_const_bytep buf) +{ + png_uint_32 uval = png_get_uint_32(buf); + if ((uval & 0x80000000) == 0) /* non-negative */ + return (png_int_32)uval; + + uval = (uval ^ 0xffffffff) + 1; /* 2's complement: -x = ~x+1 */ + if ((uval & 0x80000000) == 0) /* no overflow */ + return -(png_int_32)uval; + /* The following has to be safe; this function only gets called on PNG data + * and if we get here that data is invalid. 0 is the most safe value and + * if not then an attacker would surely just generate a PNG with 0 instead. + */ + return 0; +} + +/* Grab an unsigned 16-bit integer from a buffer in big-endian format. */ +png_uint_16 (PNGAPI +png_get_uint_16)(png_const_bytep buf) +{ + /* ANSI-C requires an int value to accommodate at least 16 bits so this + * works and allows the compiler not to worry about possible narrowing + * on 32-bit systems. (Pre-ANSI systems did not make integers smaller + * than 16 bits either.) + */ + unsigned int val = + ((unsigned int)(*buf) << 8) + + ((unsigned int)(*(buf + 1))); + + return (png_uint_16)val; +} + +#endif /* READ_INT_FUNCTIONS */ + +/* Read and check the PNG file signature */ +void /* PRIVATE */ +png_read_sig(png_structrp png_ptr, png_inforp info_ptr) +{ + size_t num_checked, num_to_check; + + /* Exit if the user application does not expect a signature. */ + if (png_ptr->sig_bytes >= 8) + return; + + num_checked = png_ptr->sig_bytes; + num_to_check = 8 - num_checked; + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_SIGNATURE; +#endif + + /* The signature must be serialized in a single I/O call. */ + png_read_data(png_ptr, &(info_ptr->signature[num_checked]), num_to_check); + png_ptr->sig_bytes = 8; + + if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check) != 0) + { + if (num_checked < 4 && + png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4)) + png_error(png_ptr, "Not a PNG file"); + else + png_error(png_ptr, "PNG file corrupted by ASCII conversion"); + } + if (num_checked < 3) + png_ptr->mode |= PNG_HAVE_PNG_SIGNATURE; +} + +/* Read the chunk header (length + type name). + * Put the type name into png_ptr->chunk_name, and return the length. + */ +png_uint_32 /* PRIVATE */ +png_read_chunk_header(png_structrp png_ptr) +{ + png_byte buf[8]; + png_uint_32 length; + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_CHUNK_HDR; +#endif + + /* Read the length and the chunk name. + * This must be performed in a single I/O call. + */ + png_read_data(png_ptr, buf, 8); + length = png_get_uint_31(png_ptr, buf); + + /* Put the chunk name into png_ptr->chunk_name. */ + png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(buf+4); + + png_debug2(0, "Reading %lx chunk, length = %lu", + (unsigned long)png_ptr->chunk_name, (unsigned long)length); + + /* Reset the crc and run it over the chunk name. */ + png_reset_crc(png_ptr); + png_calculate_crc(png_ptr, buf + 4, 4); + + /* Check to see if chunk name is valid. */ + png_check_chunk_name(png_ptr, png_ptr->chunk_name); + + /* Check for too-large chunk length */ + png_check_chunk_length(png_ptr, length); + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_CHUNK_DATA; +#endif + + return length; +} + +/* Read data, and (optionally) run it through the CRC. */ +void /* PRIVATE */ +png_crc_read(png_structrp png_ptr, png_bytep buf, png_uint_32 length) +{ + if (png_ptr == NULL) + return; + + png_read_data(png_ptr, buf, length); + png_calculate_crc(png_ptr, buf, length); +} + +/* Optionally skip data and then check the CRC. Depending on whether we + * are reading an ancillary or critical chunk, and how the program has set + * things up, we may calculate the CRC on the data and print a message. + * Returns '1' if there was a CRC error, '0' otherwise. + */ +int /* PRIVATE */ +png_crc_finish(png_structrp png_ptr, png_uint_32 skip) +{ + /* The size of the local buffer for inflate is a good guess as to a + * reasonable size to use for buffering reads from the application. + */ + while (skip > 0) + { + png_uint_32 len; + png_byte tmpbuf[PNG_INFLATE_BUF_SIZE]; + + len = (sizeof tmpbuf); + if (len > skip) + len = skip; + skip -= len; + + png_crc_read(png_ptr, tmpbuf, len); + } + + if (png_crc_error(png_ptr) != 0) + { + if (PNG_CHUNK_ANCILLARY(png_ptr->chunk_name) != 0 ? + (png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN) == 0 : + (png_ptr->flags & PNG_FLAG_CRC_CRITICAL_USE) != 0) + { + png_chunk_warning(png_ptr, "CRC error"); + } + + else + png_chunk_error(png_ptr, "CRC error"); + + return (1); + } + + return (0); +} + +/* Compare the CRC stored in the PNG file with that calculated by libpng from + * the data it has read thus far. + */ +int /* PRIVATE */ +png_crc_error(png_structrp png_ptr) +{ + png_byte crc_bytes[4]; + png_uint_32 crc; + int need_crc = 1; + + if (PNG_CHUNK_ANCILLARY(png_ptr->chunk_name) != 0) + { + if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_MASK) == + (PNG_FLAG_CRC_ANCILLARY_USE | PNG_FLAG_CRC_ANCILLARY_NOWARN)) + need_crc = 0; + } + + else /* critical */ + { + if ((png_ptr->flags & PNG_FLAG_CRC_CRITICAL_IGNORE) != 0) + need_crc = 0; + } + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_CHUNK_CRC; +#endif + + /* The chunk CRC must be serialized in a single I/O call. */ + png_read_data(png_ptr, crc_bytes, 4); + + if (need_crc != 0) + { + crc = png_get_uint_32(crc_bytes); + return ((int)(crc != png_ptr->crc)); + } + + else + return (0); +} + +#if defined(PNG_READ_iCCP_SUPPORTED) || defined(PNG_READ_iTXt_SUPPORTED) ||\ + defined(PNG_READ_pCAL_SUPPORTED) || defined(PNG_READ_sCAL_SUPPORTED) ||\ + defined(PNG_READ_sPLT_SUPPORTED) || defined(PNG_READ_tEXt_SUPPORTED) ||\ + defined(PNG_READ_zTXt_SUPPORTED) || defined(PNG_SEQUENTIAL_READ_SUPPORTED) +/* Manage the read buffer; this simply reallocates the buffer if it is not small + * enough (or if it is not allocated). The routine returns a pointer to the + * buffer; if an error occurs and 'warn' is set the routine returns NULL, else + * it will call png_error (via png_malloc) on failure. (warn == 2 means + * 'silent'). + */ +static png_bytep +png_read_buffer(png_structrp png_ptr, png_alloc_size_t new_size, int warn) +{ + png_bytep buffer = png_ptr->read_buffer; + + if (buffer != NULL && new_size > png_ptr->read_buffer_size) + { + png_ptr->read_buffer = NULL; + png_ptr->read_buffer = NULL; + png_ptr->read_buffer_size = 0; + png_free(png_ptr, buffer); + buffer = NULL; + } + + if (buffer == NULL) + { + buffer = png_voidcast(png_bytep, png_malloc_base(png_ptr, new_size)); + + if (buffer != NULL) + { + memset(buffer, 0, new_size); /* just in case */ + png_ptr->read_buffer = buffer; + png_ptr->read_buffer_size = new_size; + } + + else if (warn < 2) /* else silent */ + { + if (warn != 0) + png_chunk_warning(png_ptr, "insufficient memory to read chunk"); + + else + png_chunk_error(png_ptr, "insufficient memory to read chunk"); + } + } + + return buffer; +} +#endif /* READ_iCCP|iTXt|pCAL|sCAL|sPLT|tEXt|zTXt|SEQUENTIAL_READ */ + +/* png_inflate_claim: claim the zstream for some nefarious purpose that involves + * decompression. Returns Z_OK on success, else a zlib error code. It checks + * the owner but, in final release builds, just issues a warning if some other + * chunk apparently owns the stream. Prior to release it does a png_error. + */ +static int +png_inflate_claim(png_structrp png_ptr, png_uint_32 owner) +{ + if (png_ptr->zowner != 0) + { + char msg[64]; + + PNG_STRING_FROM_CHUNK(msg, png_ptr->zowner); + /* So the message that results is " using zstream"; this is an + * internal error, but is very useful for debugging. i18n requirements + * are minimal. + */ + (void)png_safecat(msg, (sizeof msg), 4, " using zstream"); +#if PNG_RELEASE_BUILD + png_chunk_warning(png_ptr, msg); + png_ptr->zowner = 0; +#else + png_chunk_error(png_ptr, msg); +#endif + } + + /* Implementation note: unlike 'png_deflate_claim' this internal function + * does not take the size of the data as an argument. Some efficiency could + * be gained by using this when it is known *if* the zlib stream itself does + * not record the number; however, this is an illusion: the original writer + * of the PNG may have selected a lower window size, and we really must + * follow that because, for systems with with limited capabilities, we + * would otherwise reject the application's attempts to use a smaller window + * size (zlib doesn't have an interface to say "this or lower"!). + * + * inflateReset2 was added to zlib 1.2.4; before this the window could not be + * reset, therefore it is necessary to always allocate the maximum window + * size with earlier zlibs just in case later compressed chunks need it. + */ + { + int ret; /* zlib return code */ +#if ZLIB_VERNUM >= 0x1240 + int window_bits = 0; + +# if defined(PNG_SET_OPTION_SUPPORTED) && defined(PNG_MAXIMUM_INFLATE_WINDOW) + if (((png_ptr->options >> PNG_MAXIMUM_INFLATE_WINDOW) & 3) == + PNG_OPTION_ON) + { + window_bits = 15; + png_ptr->zstream_start = 0; /* fixed window size */ + } + + else + { + png_ptr->zstream_start = 1; + } +# endif + +#endif /* ZLIB_VERNUM >= 0x1240 */ + + /* Set this for safety, just in case the previous owner left pointers to + * memory allocations. + */ + png_ptr->zstream.next_in = NULL; + png_ptr->zstream.avail_in = 0; + png_ptr->zstream.next_out = NULL; + png_ptr->zstream.avail_out = 0; + + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) != 0) + { +#if ZLIB_VERNUM >= 0x1240 + ret = inflateReset2(&png_ptr->zstream, window_bits); +#else + ret = inflateReset(&png_ptr->zstream); +#endif + } + + else + { +#if ZLIB_VERNUM >= 0x1240 + ret = inflateInit2(&png_ptr->zstream, window_bits); +#else + ret = inflateInit(&png_ptr->zstream); +#endif + + if (ret == Z_OK) + png_ptr->flags |= PNG_FLAG_ZSTREAM_INITIALIZED; + } + +#if ZLIB_VERNUM >= 0x1290 && \ + defined(PNG_SET_OPTION_SUPPORTED) && defined(PNG_IGNORE_ADLER32) + if (((png_ptr->options >> PNG_IGNORE_ADLER32) & 3) == PNG_OPTION_ON) + /* Turn off validation of the ADLER32 checksum in IDAT chunks */ + ret = inflateValidate(&png_ptr->zstream, 0); +#endif + + if (ret == Z_OK) + png_ptr->zowner = owner; + + else + png_zstream_error(png_ptr, ret); + + return ret; + } + +#ifdef window_bits +# undef window_bits +#endif +} + +#if ZLIB_VERNUM >= 0x1240 +/* Handle the start of the inflate stream if we called inflateInit2(strm,0); + * in this case some zlib versions skip validation of the CINFO field and, in + * certain circumstances, libpng may end up displaying an invalid image, in + * contrast to implementations that call zlib in the normal way (e.g. libpng + * 1.5). + */ +int /* PRIVATE */ +png_zlib_inflate(png_structrp png_ptr, int flush) +{ + if (png_ptr->zstream_start && png_ptr->zstream.avail_in > 0) + { + if ((*png_ptr->zstream.next_in >> 4) > 7) + { + png_ptr->zstream.msg = "invalid window size (libpng)"; + return Z_DATA_ERROR; + } + + png_ptr->zstream_start = 0; + } + + return inflate(&png_ptr->zstream, flush); +} +#endif /* Zlib >= 1.2.4 */ + +#ifdef PNG_READ_COMPRESSED_TEXT_SUPPORTED +#if defined(PNG_READ_zTXt_SUPPORTED) || defined (PNG_READ_iTXt_SUPPORTED) +/* png_inflate now returns zlib error codes including Z_OK and Z_STREAM_END to + * allow the caller to do multiple calls if required. If the 'finish' flag is + * set Z_FINISH will be passed to the final inflate() call and Z_STREAM_END must + * be returned or there has been a problem, otherwise Z_SYNC_FLUSH is used and + * Z_OK or Z_STREAM_END will be returned on success. + * + * The input and output sizes are updated to the actual amounts of data consumed + * or written, not the amount available (as in a z_stream). The data pointers + * are not changed, so the next input is (data+input_size) and the next + * available output is (output+output_size). + */ +static int +png_inflate(png_structrp png_ptr, png_uint_32 owner, int finish, + /* INPUT: */ png_const_bytep input, png_uint_32p input_size_ptr, + /* OUTPUT: */ png_bytep output, png_alloc_size_t *output_size_ptr) +{ + if (png_ptr->zowner == owner) /* Else not claimed */ + { + int ret; + png_alloc_size_t avail_out = *output_size_ptr; + png_uint_32 avail_in = *input_size_ptr; + + /* zlib can't necessarily handle more than 65535 bytes at once (i.e. it + * can't even necessarily handle 65536 bytes) because the type uInt is + * "16 bits or more". Consequently it is necessary to chunk the input to + * zlib. This code uses ZLIB_IO_MAX, from pngpriv.h, as the maximum (the + * maximum value that can be stored in a uInt.) It is possible to set + * ZLIB_IO_MAX to a lower value in pngpriv.h and this may sometimes have + * a performance advantage, because it reduces the amount of data accessed + * at each step and that may give the OS more time to page it in. + */ + png_ptr->zstream.next_in = PNGZ_INPUT_CAST(input); + /* avail_in and avail_out are set below from 'size' */ + png_ptr->zstream.avail_in = 0; + png_ptr->zstream.avail_out = 0; + + /* Read directly into the output if it is available (this is set to + * a local buffer below if output is NULL). + */ + if (output != NULL) + png_ptr->zstream.next_out = output; + + do + { + uInt avail; + Byte local_buffer[PNG_INFLATE_BUF_SIZE]; + + /* zlib INPUT BUFFER */ + /* The setting of 'avail_in' used to be outside the loop; by setting it + * inside it is possible to chunk the input to zlib and simply rely on + * zlib to advance the 'next_in' pointer. This allows arbitrary + * amounts of data to be passed through zlib at the unavoidable cost of + * requiring a window save (memcpy of up to 32768 output bytes) + * every ZLIB_IO_MAX input bytes. + */ + avail_in += png_ptr->zstream.avail_in; /* not consumed last time */ + + avail = ZLIB_IO_MAX; + + if (avail_in < avail) + avail = (uInt)avail_in; /* safe: < than ZLIB_IO_MAX */ + + avail_in -= avail; + png_ptr->zstream.avail_in = avail; + + /* zlib OUTPUT BUFFER */ + avail_out += png_ptr->zstream.avail_out; /* not written last time */ + + avail = ZLIB_IO_MAX; /* maximum zlib can process */ + + if (output == NULL) + { + /* Reset the output buffer each time round if output is NULL and + * make available the full buffer, up to 'remaining_space' + */ + png_ptr->zstream.next_out = local_buffer; + if ((sizeof local_buffer) < avail) + avail = (sizeof local_buffer); + } + + if (avail_out < avail) + avail = (uInt)avail_out; /* safe: < ZLIB_IO_MAX */ + + png_ptr->zstream.avail_out = avail; + avail_out -= avail; + + /* zlib inflate call */ + /* In fact 'avail_out' may be 0 at this point, that happens at the end + * of the read when the final LZ end code was not passed at the end of + * the previous chunk of input data. Tell zlib if we have reached the + * end of the output buffer. + */ + ret = PNG_INFLATE(png_ptr, avail_out > 0 ? Z_NO_FLUSH : + (finish ? Z_FINISH : Z_SYNC_FLUSH)); + } while (ret == Z_OK); + + /* For safety kill the local buffer pointer now */ + if (output == NULL) + png_ptr->zstream.next_out = NULL; + + /* Claw back the 'size' and 'remaining_space' byte counts. */ + avail_in += png_ptr->zstream.avail_in; + avail_out += png_ptr->zstream.avail_out; + + /* Update the input and output sizes; the updated values are the amount + * consumed or written, effectively the inverse of what zlib uses. + */ + if (avail_out > 0) + *output_size_ptr -= avail_out; + + if (avail_in > 0) + *input_size_ptr -= avail_in; + + /* Ensure png_ptr->zstream.msg is set (even in the success case!) */ + png_zstream_error(png_ptr, ret); + return ret; + } + + else + { + /* This is a bad internal error. The recovery assigns to the zstream msg + * pointer, which is not owned by the caller, but this is safe; it's only + * used on errors! + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("zstream unclaimed"); + return Z_STREAM_ERROR; + } +} + +/* + * Decompress trailing data in a chunk. The assumption is that read_buffer + * points at an allocated area holding the contents of a chunk with a + * trailing compressed part. What we get back is an allocated area + * holding the original prefix part and an uncompressed version of the + * trailing part (the malloc area passed in is freed). + */ +static int +png_decompress_chunk(png_structrp png_ptr, + png_uint_32 chunklength, png_uint_32 prefix_size, + png_alloc_size_t *newlength /* must be initialized to the maximum! */, + int terminate /*add a '\0' to the end of the uncompressed data*/) +{ + /* TODO: implement different limits for different types of chunk. + * + * The caller supplies *newlength set to the maximum length of the + * uncompressed data, but this routine allocates space for the prefix and + * maybe a '\0' terminator too. We have to assume that 'prefix_size' is + * limited only by the maximum chunk size. + */ + png_alloc_size_t limit = PNG_SIZE_MAX; + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_malloc_max > 0 && + png_ptr->user_chunk_malloc_max < limit) + limit = png_ptr->user_chunk_malloc_max; +# elif PNG_USER_CHUNK_MALLOC_MAX > 0 + if (PNG_USER_CHUNK_MALLOC_MAX < limit) + limit = PNG_USER_CHUNK_MALLOC_MAX; +# endif + + if (limit >= prefix_size + (terminate != 0)) + { + int ret; + + limit -= prefix_size + (terminate != 0); + + if (limit < *newlength) + *newlength = limit; + + /* Now try to claim the stream. */ + ret = png_inflate_claim(png_ptr, png_ptr->chunk_name); + + if (ret == Z_OK) + { + png_uint_32 lzsize = chunklength - prefix_size; + + ret = png_inflate(png_ptr, png_ptr->chunk_name, 1/*finish*/, + /* input: */ png_ptr->read_buffer + prefix_size, &lzsize, + /* output: */ NULL, newlength); + + if (ret == Z_STREAM_END) + { + /* Use 'inflateReset' here, not 'inflateReset2' because this + * preserves the previously decided window size (otherwise it would + * be necessary to store the previous window size.) In practice + * this doesn't matter anyway, because png_inflate will call inflate + * with Z_FINISH in almost all cases, so the window will not be + * maintained. + */ + if (inflateReset(&png_ptr->zstream) == Z_OK) + { + /* Because of the limit checks above we know that the new, + * expanded, size will fit in a size_t (let alone an + * png_alloc_size_t). Use png_malloc_base here to avoid an + * extra OOM message. + */ + png_alloc_size_t new_size = *newlength; + png_alloc_size_t buffer_size = prefix_size + new_size + + (terminate != 0); + png_bytep text = png_voidcast(png_bytep, png_malloc_base(png_ptr, + buffer_size)); + + if (text != NULL) + { + memset(text, 0, buffer_size); + + ret = png_inflate(png_ptr, png_ptr->chunk_name, 1/*finish*/, + png_ptr->read_buffer + prefix_size, &lzsize, + text + prefix_size, newlength); + + if (ret == Z_STREAM_END) + { + if (new_size == *newlength) + { + if (terminate != 0) + text[prefix_size + *newlength] = 0; + + if (prefix_size > 0) + memcpy(text, png_ptr->read_buffer, prefix_size); + + { + png_bytep old_ptr = png_ptr->read_buffer; + + png_ptr->read_buffer = text; + png_ptr->read_buffer_size = buffer_size; + text = old_ptr; /* freed below */ + } + } + + else + { + /* The size changed on the second read, there can be no + * guarantee that anything is correct at this point. + * The 'msg' pointer has been set to "unexpected end of + * LZ stream", which is fine, but return an error code + * that the caller won't accept. + */ + ret = PNG_UNEXPECTED_ZLIB_RETURN; + } + } + + else if (ret == Z_OK) + ret = PNG_UNEXPECTED_ZLIB_RETURN; /* for safety */ + + /* Free the text pointer (this is the old read_buffer on + * success) + */ + png_free(png_ptr, text); + + /* This really is very benign, but it's still an error because + * the extra space may otherwise be used as a Trojan Horse. + */ + if (ret == Z_STREAM_END && + chunklength - prefix_size != lzsize) + png_chunk_benign_error(png_ptr, "extra compressed data"); + } + + else + { + /* Out of memory allocating the buffer */ + ret = Z_MEM_ERROR; + png_zstream_error(png_ptr, Z_MEM_ERROR); + } + } + + else + { + /* inflateReset failed, store the error message */ + png_zstream_error(png_ptr, ret); + ret = PNG_UNEXPECTED_ZLIB_RETURN; + } + } + + else if (ret == Z_OK) + ret = PNG_UNEXPECTED_ZLIB_RETURN; + + /* Release the claimed stream */ + png_ptr->zowner = 0; + } + + else /* the claim failed */ if (ret == Z_STREAM_END) /* impossible! */ + ret = PNG_UNEXPECTED_ZLIB_RETURN; + + return ret; + } + + else + { + /* Application/configuration limits exceeded */ + png_zstream_error(png_ptr, Z_MEM_ERROR); + return Z_MEM_ERROR; + } +} +#endif /* READ_zTXt || READ_iTXt */ +#endif /* READ_COMPRESSED_TEXT */ + +#ifdef PNG_READ_iCCP_SUPPORTED +/* Perform a partial read and decompress, producing 'avail_out' bytes and + * reading from the current chunk as required. + */ +static int +png_inflate_read(png_structrp png_ptr, png_bytep read_buffer, uInt read_size, + png_uint_32p chunk_bytes, png_bytep next_out, png_alloc_size_t *out_size, + int finish) +{ + if (png_ptr->zowner == png_ptr->chunk_name) + { + int ret; + + /* next_in and avail_in must have been initialized by the caller. */ + png_ptr->zstream.next_out = next_out; + png_ptr->zstream.avail_out = 0; /* set in the loop */ + + do + { + if (png_ptr->zstream.avail_in == 0) + { + if (read_size > *chunk_bytes) + read_size = (uInt)*chunk_bytes; + *chunk_bytes -= read_size; + + if (read_size > 0) + png_crc_read(png_ptr, read_buffer, read_size); + + png_ptr->zstream.next_in = read_buffer; + png_ptr->zstream.avail_in = read_size; + } + + if (png_ptr->zstream.avail_out == 0) + { + uInt avail = ZLIB_IO_MAX; + if (avail > *out_size) + avail = (uInt)*out_size; + *out_size -= avail; + + png_ptr->zstream.avail_out = avail; + } + + /* Use Z_SYNC_FLUSH when there is no more chunk data to ensure that all + * the available output is produced; this allows reading of truncated + * streams. + */ + ret = PNG_INFLATE(png_ptr, *chunk_bytes > 0 ? + Z_NO_FLUSH : (finish ? Z_FINISH : Z_SYNC_FLUSH)); + } + while (ret == Z_OK && (*out_size > 0 || png_ptr->zstream.avail_out > 0)); + + *out_size += png_ptr->zstream.avail_out; + png_ptr->zstream.avail_out = 0; /* Should not be required, but is safe */ + + /* Ensure the error message pointer is always set: */ + png_zstream_error(png_ptr, ret); + return ret; + } + + else + { + png_ptr->zstream.msg = PNGZ_MSG_CAST("zstream unclaimed"); + return Z_STREAM_ERROR; + } +} +#endif /* READ_iCCP */ + +/* Read and check the IDHR chunk */ + +void /* PRIVATE */ +png_handle_IHDR(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[13]; + png_uint_32 width, height; + int bit_depth, color_type, compression_type, filter_type; + int interlace_type; + + png_debug(1, "in png_handle_IHDR"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) != 0) + png_chunk_error(png_ptr, "out of place"); + + /* Check the length */ + if (length != 13) + png_chunk_error(png_ptr, "invalid"); + + png_ptr->mode |= PNG_HAVE_IHDR; + + png_crc_read(png_ptr, buf, 13); + png_crc_finish(png_ptr, 0); + + width = png_get_uint_31(png_ptr, buf); + height = png_get_uint_31(png_ptr, buf + 4); + bit_depth = buf[8]; + color_type = buf[9]; + compression_type = buf[10]; + filter_type = buf[11]; + interlace_type = buf[12]; + + /* Set internal variables */ + png_ptr->width = width; + png_ptr->height = height; + png_ptr->bit_depth = (png_byte)bit_depth; + png_ptr->interlaced = (png_byte)interlace_type; + png_ptr->color_type = (png_byte)color_type; +#ifdef PNG_MNG_FEATURES_SUPPORTED + png_ptr->filter_type = (png_byte)filter_type; +#endif + png_ptr->compression_type = (png_byte)compression_type; + + /* Find number of channels */ + switch (png_ptr->color_type) + { + default: /* invalid, png_set_IHDR calls png_error */ + case PNG_COLOR_TYPE_GRAY: + case PNG_COLOR_TYPE_PALETTE: + png_ptr->channels = 1; + break; + + case PNG_COLOR_TYPE_RGB: + png_ptr->channels = 3; + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + png_ptr->channels = 2; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + png_ptr->channels = 4; + break; + } + + /* Set up other useful info */ + png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth * png_ptr->channels); + png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->width); + png_debug1(3, "bit_depth = %d", png_ptr->bit_depth); + png_debug1(3, "channels = %d", png_ptr->channels); + png_debug1(3, "rowbytes = %lu", (unsigned long)png_ptr->rowbytes); + png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, + color_type, interlace_type, compression_type, filter_type); +} + +/* Read and check the palette */ +void /* PRIVATE */ +png_handle_PLTE(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_color palette[PNG_MAX_PALETTE_LENGTH]; + int max_palette_length, num, i; +#ifdef PNG_POINTER_INDEXING_SUPPORTED + png_colorp pal_ptr; +#endif + + png_debug(1, "in png_handle_PLTE"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + /* Moved to before the 'after IDAT' check below because otherwise duplicate + * PLTE chunks are potentially ignored (the spec says there shall not be more + * than one PLTE, the error is not treated as benign, so this check trumps + * the requirement that PLTE appears before IDAT.) + */ + else if ((png_ptr->mode & PNG_HAVE_PLTE) != 0) + png_chunk_error(png_ptr, "duplicate"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + /* This is benign because the non-benign error happened before, when an + * IDAT was encountered in a color-mapped image with no PLTE. + */ + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + png_ptr->mode |= PNG_HAVE_PLTE; + + if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) == 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "ignored in grayscale PNG"); + return; + } + +#ifndef PNG_READ_OPT_PLTE_SUPPORTED + if (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE) + { + png_crc_finish(png_ptr, length); + return; + } +#endif + + if (length > 3*PNG_MAX_PALETTE_LENGTH || length % 3) + { + png_crc_finish(png_ptr, length); + + if (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE) + png_chunk_benign_error(png_ptr, "invalid"); + + else + png_chunk_error(png_ptr, "invalid"); + + return; + } + + /* The cast is safe because 'length' is less than 3*PNG_MAX_PALETTE_LENGTH */ + num = (int)length / 3; + + /* If the palette has 256 or fewer entries but is too large for the bit + * depth, we don't issue an error, to preserve the behavior of previous + * libpng versions. We silently truncate the unused extra palette entries + * here. + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + max_palette_length = (1 << png_ptr->bit_depth); + else + max_palette_length = PNG_MAX_PALETTE_LENGTH; + + if (num > max_palette_length) + num = max_palette_length; + +#ifdef PNG_POINTER_INDEXING_SUPPORTED + for (i = 0, pal_ptr = palette; i < num; i++, pal_ptr++) + { + png_byte buf[3]; + + png_crc_read(png_ptr, buf, 3); + pal_ptr->red = buf[0]; + pal_ptr->green = buf[1]; + pal_ptr->blue = buf[2]; + } +#else + for (i = 0; i < num; i++) + { + png_byte buf[3]; + + png_crc_read(png_ptr, buf, 3); + /* Don't depend upon png_color being any order */ + palette[i].red = buf[0]; + palette[i].green = buf[1]; + palette[i].blue = buf[2]; + } +#endif + + /* If we actually need the PLTE chunk (ie for a paletted image), we do + * whatever the normal CRC configuration tells us. However, if we + * have an RGB image, the PLTE can be considered ancillary, so + * we will act as though it is. + */ +#ifndef PNG_READ_OPT_PLTE_SUPPORTED + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) +#endif + { + png_crc_finish(png_ptr, (png_uint_32) (length - (unsigned int)num * 3)); + } + +#ifndef PNG_READ_OPT_PLTE_SUPPORTED + else if (png_crc_error(png_ptr) != 0) /* Only if we have a CRC error */ + { + /* If we don't want to use the data from an ancillary chunk, + * we have two options: an error abort, or a warning and we + * ignore the data in this chunk (which should be OK, since + * it's considered ancillary for a RGB or RGBA image). + * + * IMPLEMENTATION NOTE: this is only here because png_crc_finish uses the + * chunk type to determine whether to check the ancillary or the critical + * flags. + */ + if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_USE) == 0) + { + if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN) != 0) + return; + + else + png_chunk_error(png_ptr, "CRC error"); + } + + /* Otherwise, we (optionally) emit a warning and use the chunk. */ + else if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN) == 0) + png_chunk_warning(png_ptr, "CRC error"); + } +#endif + + /* TODO: png_set_PLTE has the side effect of setting png_ptr->palette to its + * own copy of the palette. This has the side effect that when png_start_row + * is called (this happens after any call to png_read_update_info) the + * info_ptr palette gets changed. This is extremely unexpected and + * confusing. + * + * Fix this by not sharing the palette in this way. + */ + png_set_PLTE(png_ptr, info_ptr, palette, num); + + /* The three chunks, bKGD, hIST and tRNS *must* appear after PLTE and before + * IDAT. Prior to 1.6.0 this was not checked; instead the code merely + * checked the apparent validity of a tRNS chunk inserted before PLTE on a + * palette PNG. 1.6.0 attempts to rigorously follow the standard and + * therefore does a benign error if the erroneous condition is detected *and* + * cancels the tRNS if the benign error returns. The alternative is to + * amend the standard since it would be rather hypocritical of the standards + * maintainers to ignore it. + */ +#ifdef PNG_READ_tRNS_SUPPORTED + if (png_ptr->num_trans > 0 || + (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS) != 0)) + { + /* Cancel this because otherwise it would be used if the transforms + * require it. Don't cancel the 'valid' flag because this would prevent + * detection of duplicate chunks. + */ + png_ptr->num_trans = 0; + + if (info_ptr != NULL) + info_ptr->num_trans = 0; + + png_chunk_benign_error(png_ptr, "tRNS must be after"); + } +#endif + +#ifdef PNG_READ_hIST_SUPPORTED + if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_hIST) != 0) + png_chunk_benign_error(png_ptr, "hIST must be after"); +#endif + +#ifdef PNG_READ_bKGD_SUPPORTED + if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD) != 0) + png_chunk_benign_error(png_ptr, "bKGD must be after"); +#endif +} + +void /* PRIVATE */ +png_handle_IEND(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_debug(1, "in png_handle_IEND"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0 || + (png_ptr->mode & PNG_HAVE_IDAT) == 0) + png_chunk_error(png_ptr, "out of place"); + + png_ptr->mode |= (PNG_AFTER_IDAT | PNG_HAVE_IEND); + + png_crc_finish(png_ptr, length); + + if (length != 0) + png_chunk_benign_error(png_ptr, "invalid"); + + PNG_UNUSED(info_ptr) +} + +#ifdef PNG_READ_gAMA_SUPPORTED +void /* PRIVATE */ +png_handle_gAMA(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_fixed_point igamma; + png_byte buf[4]; + + png_debug(1, "in png_handle_gAMA"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length != 4) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 4); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + igamma = png_get_fixed_point(NULL, buf); + + png_colorspace_set_gamma(png_ptr, &png_ptr->colorspace, igamma); + png_colorspace_sync(png_ptr, info_ptr); +} +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED +void /* PRIVATE */ +png_handle_sBIT(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + unsigned int truelen, i; + png_byte sample_depth; + png_byte buf[4]; + + png_debug(1, "in png_handle_sBIT"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sBIT) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + truelen = 3; + sample_depth = 8; + } + + else + { + truelen = png_ptr->channels; + sample_depth = png_ptr->bit_depth; + } + + if (length != truelen || length > 4) + { + png_chunk_benign_error(png_ptr, "invalid"); + png_crc_finish(png_ptr, length); + return; + } + + buf[0] = buf[1] = buf[2] = buf[3] = sample_depth; + png_crc_read(png_ptr, buf, truelen); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + for (i=0; i sample_depth) + { + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + } + + if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0) + { + png_ptr->sig_bit.red = buf[0]; + png_ptr->sig_bit.green = buf[1]; + png_ptr->sig_bit.blue = buf[2]; + png_ptr->sig_bit.alpha = buf[3]; + } + + else + { + png_ptr->sig_bit.gray = buf[0]; + png_ptr->sig_bit.red = buf[0]; + png_ptr->sig_bit.green = buf[0]; + png_ptr->sig_bit.blue = buf[0]; + png_ptr->sig_bit.alpha = buf[1]; + } + + png_set_sBIT(png_ptr, info_ptr, &(png_ptr->sig_bit)); +} +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED +void /* PRIVATE */ +png_handle_cHRM(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[32]; + png_xy xy; + + png_debug(1, "in png_handle_cHRM"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length != 32) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 32); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + xy.whitex = png_get_fixed_point(NULL, buf); + xy.whitey = png_get_fixed_point(NULL, buf + 4); + xy.redx = png_get_fixed_point(NULL, buf + 8); + xy.redy = png_get_fixed_point(NULL, buf + 12); + xy.greenx = png_get_fixed_point(NULL, buf + 16); + xy.greeny = png_get_fixed_point(NULL, buf + 20); + xy.bluex = png_get_fixed_point(NULL, buf + 24); + xy.bluey = png_get_fixed_point(NULL, buf + 28); + + if (xy.whitex == PNG_FIXED_ERROR || + xy.whitey == PNG_FIXED_ERROR || + xy.redx == PNG_FIXED_ERROR || + xy.redy == PNG_FIXED_ERROR || + xy.greenx == PNG_FIXED_ERROR || + xy.greeny == PNG_FIXED_ERROR || + xy.bluex == PNG_FIXED_ERROR || + xy.bluey == PNG_FIXED_ERROR) + { + png_chunk_benign_error(png_ptr, "invalid values"); + return; + } + + /* If a colorspace error has already been output skip this chunk */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) != 0) + return; + + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_FROM_cHRM) != 0) + { + png_ptr->colorspace.flags |= PNG_COLORSPACE_INVALID; + png_colorspace_sync(png_ptr, info_ptr); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + png_ptr->colorspace.flags |= PNG_COLORSPACE_FROM_cHRM; + (void)png_colorspace_set_chromaticities(png_ptr, &png_ptr->colorspace, &xy, + 1/*prefer cHRM values*/); + png_colorspace_sync(png_ptr, info_ptr); +} +#endif + +#ifdef PNG_READ_sRGB_SUPPORTED +void /* PRIVATE */ +png_handle_sRGB(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte intent; + + png_debug(1, "in png_handle_sRGB"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length != 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, &intent, 1); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + /* If a colorspace error has already been output skip this chunk */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) != 0) + return; + + /* Only one sRGB or iCCP chunk is allowed, use the HAVE_INTENT flag to detect + * this. + */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_INTENT) != 0) + { + png_ptr->colorspace.flags |= PNG_COLORSPACE_INVALID; + png_colorspace_sync(png_ptr, info_ptr); + png_chunk_benign_error(png_ptr, "too many profiles"); + return; + } + + (void)png_colorspace_set_sRGB(png_ptr, &png_ptr->colorspace, intent); + png_colorspace_sync(png_ptr, info_ptr); +} +#endif /* READ_sRGB */ + +#ifdef PNG_READ_iCCP_SUPPORTED +void /* PRIVATE */ +png_handle_iCCP(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +/* Note: this does not properly handle profiles that are > 64K under DOS */ +{ + png_const_charp errmsg = NULL; /* error message output, or no error */ + int finished = 0; /* crc checked */ + + png_debug(1, "in png_handle_iCCP"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + /* Consistent with all the above colorspace handling an obviously *invalid* + * chunk is just ignored, so does not invalidate the color space. An + * alternative is to set the 'invalid' flags at the start of this routine + * and only clear them in they were not set before and all the tests pass. + */ + + /* The keyword must be at least one character and there is a + * terminator (0) byte and the compression method byte, and the + * 'zlib' datastream is at least 11 bytes. + */ + if (length < 14) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too short"); + return; + } + + /* If a colorspace error has already been output skip this chunk */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) != 0) + { + png_crc_finish(png_ptr, length); + return; + } + + /* Only one sRGB or iCCP chunk is allowed, use the HAVE_INTENT flag to detect + * this. + */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_INTENT) == 0) + { + uInt read_length, keyword_length; + char keyword[81]; + + /* Find the keyword; the keyword plus separator and compression method + * bytes can be at most 81 characters long. + */ + read_length = 81; /* maximum */ + if (read_length > length) + read_length = (uInt)length; + + png_crc_read(png_ptr, (png_bytep)keyword, read_length); + length -= read_length; + + /* The minimum 'zlib' stream is assumed to be just the 2 byte header, + * 5 bytes minimum 'deflate' stream, and the 4 byte checksum. + */ + if (length < 11) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too short"); + return; + } + + keyword_length = 0; + while (keyword_length < 80 && keyword_length < read_length && + keyword[keyword_length] != 0) + ++keyword_length; + + /* TODO: make the keyword checking common */ + if (keyword_length >= 1 && keyword_length <= 79) + { + /* We only understand '0' compression - deflate - so if we get a + * different value we can't safely decode the chunk. + */ + if (keyword_length+1 < read_length && + keyword[keyword_length+1] == PNG_COMPRESSION_TYPE_BASE) + { + read_length -= keyword_length+2; + + if (png_inflate_claim(png_ptr, png_iCCP) == Z_OK) + { + Byte profile_header[132]={0}; + Byte local_buffer[PNG_INFLATE_BUF_SIZE]; + png_alloc_size_t size = (sizeof profile_header); + + png_ptr->zstream.next_in = (Bytef*)keyword + (keyword_length+2); + png_ptr->zstream.avail_in = read_length; + (void)png_inflate_read(png_ptr, local_buffer, + (sizeof local_buffer), &length, profile_header, &size, + 0/*finish: don't, because the output is too small*/); + + if (size == 0) + { + /* We have the ICC profile header; do the basic header checks. + */ + png_uint_32 profile_length = png_get_uint_32(profile_header); + + if (png_icc_check_length(png_ptr, &png_ptr->colorspace, + keyword, profile_length) != 0) + { + /* The length is apparently ok, so we can check the 132 + * byte header. + */ + if (png_icc_check_header(png_ptr, &png_ptr->colorspace, + keyword, profile_length, profile_header, + png_ptr->color_type) != 0) + { + /* Now read the tag table; a variable size buffer is + * needed at this point, allocate one for the whole + * profile. The header check has already validated + * that none of this stuff will overflow. + */ + png_uint_32 tag_count = + png_get_uint_32(profile_header + 128); + png_bytep profile = png_read_buffer(png_ptr, + profile_length, 2/*silent*/); + + if (profile != NULL) + { + memcpy(profile, profile_header, + (sizeof profile_header)); + + size = 12 * tag_count; + + (void)png_inflate_read(png_ptr, local_buffer, + (sizeof local_buffer), &length, + profile + (sizeof profile_header), &size, 0); + + /* Still expect a buffer error because we expect + * there to be some tag data! + */ + if (size == 0) + { + if (png_icc_check_tag_table(png_ptr, + &png_ptr->colorspace, keyword, profile_length, + profile) != 0) + { + /* The profile has been validated for basic + * security issues, so read the whole thing in. + */ + size = profile_length - (sizeof profile_header) + - 12 * tag_count; + + (void)png_inflate_read(png_ptr, local_buffer, + (sizeof local_buffer), &length, + profile + (sizeof profile_header) + + 12 * tag_count, &size, 1/*finish*/); + + if (length > 0 && !(png_ptr->flags & + PNG_FLAG_BENIGN_ERRORS_WARN)) + errmsg = "extra compressed data"; + + /* But otherwise allow extra data: */ + else if (size == 0) + { + if (length > 0) + { + /* This can be handled completely, so + * keep going. + */ + png_chunk_warning(png_ptr, + "extra compressed data"); + } + + png_crc_finish(png_ptr, length); + finished = 1; + +# if defined(PNG_sRGB_SUPPORTED) && PNG_sRGB_PROFILE_CHECKS >= 0 + /* Check for a match against sRGB */ + png_icc_set_sRGB(png_ptr, + &png_ptr->colorspace, profile, + png_ptr->zstream.adler); +# endif + + /* Steal the profile for info_ptr. */ + if (info_ptr != NULL) + { + png_free_data(png_ptr, info_ptr, + PNG_FREE_ICCP, 0); + + info_ptr->iccp_name = png_voidcast(char*, + png_malloc_base(png_ptr, + keyword_length+1)); + if (info_ptr->iccp_name != NULL) + { + memcpy(info_ptr->iccp_name, keyword, + keyword_length+1); + info_ptr->iccp_proflen = + profile_length; + info_ptr->iccp_profile = profile; + png_ptr->read_buffer = NULL; /*steal*/ + info_ptr->free_me |= PNG_FREE_ICCP; + info_ptr->valid |= PNG_INFO_iCCP; + } + + else + { + png_ptr->colorspace.flags |= + PNG_COLORSPACE_INVALID; + errmsg = "out of memory"; + } + } + + /* else the profile remains in the read + * buffer which gets reused for subsequent + * chunks. + */ + + if (info_ptr != NULL) + png_colorspace_sync(png_ptr, info_ptr); + + if (errmsg == NULL) + { + png_ptr->zowner = 0; + return; + } + } + if (errmsg == NULL) + errmsg = png_ptr->zstream.msg; + } + /* else png_icc_check_tag_table output an error */ + } + else /* profile truncated */ + errmsg = png_ptr->zstream.msg; + } + + else + errmsg = "out of memory"; + } + + /* else png_icc_check_header output an error */ + } + + /* else png_icc_check_length output an error */ + } + + else /* profile truncated */ + errmsg = png_ptr->zstream.msg; + + /* Release the stream */ + png_ptr->zowner = 0; + } + + else /* png_inflate_claim failed */ + errmsg = png_ptr->zstream.msg; + } + + else + errmsg = "bad compression method"; /* or missing */ + } + + else + errmsg = "bad keyword"; + } + + else + errmsg = "too many profiles"; + + /* Failure: the reason is in 'errmsg' */ + if (finished == 0) + png_crc_finish(png_ptr, length); + + png_ptr->colorspace.flags |= PNG_COLORSPACE_INVALID; + png_colorspace_sync(png_ptr, info_ptr); + if (errmsg != NULL) /* else already output */ + png_chunk_benign_error(png_ptr, errmsg); +} +#endif /* READ_iCCP */ + +#ifdef PNG_READ_sPLT_SUPPORTED +void /* PRIVATE */ +png_handle_sPLT(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +/* Note: this does not properly handle chunks that are > 64K under DOS */ +{ + png_bytep entry_start, buffer; + png_sPLT_t new_palette; + png_sPLT_entryp pp; + png_uint_32 data_length; + int entry_size, i; + png_uint_32 skip = 0; + png_uint_32 dl; + size_t max_dl; + + png_debug(1, "in png_handle_sPLT"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_warning(png_ptr, "No space in chunk cache for sPLT"); + png_crc_finish(png_ptr, length); + return; + } + } +#endif + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + +#ifdef PNG_MAX_MALLOC_64K + if (length > 65535U) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too large to fit in memory"); + return; + } +#endif + + buffer = png_read_buffer(png_ptr, length+1, 2/*silent*/); + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + + /* WARNING: this may break if size_t is less than 32 bits; it is assumed + * that the PNG_MAX_MALLOC_64K test is enabled in this case, but this is a + * potential breakage point if the types in pngconf.h aren't exactly right. + */ + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, skip) != 0) + return; + + buffer[length] = 0; + + for (entry_start = buffer; *entry_start; entry_start++) + /* Empty loop to find end of name */ ; + + ++entry_start; + + /* A sample depth should follow the separator, and we should be on it */ + if (length < 2U || entry_start > buffer + (length - 2U)) + { + png_warning(png_ptr, "malformed sPLT chunk"); + return; + } + + new_palette.depth = *entry_start++; + entry_size = (new_palette.depth == 8 ? 6 : 10); + /* This must fit in a png_uint_32 because it is derived from the original + * chunk data length. + */ + data_length = length - (png_uint_32)(entry_start - buffer); + + /* Integrity-check the data length */ + if ((data_length % (unsigned int)entry_size) != 0) + { + png_warning(png_ptr, "sPLT chunk has bad length"); + return; + } + + dl = (png_uint_32)(data_length / (unsigned int)entry_size); + max_dl = PNG_SIZE_MAX / (sizeof (png_sPLT_entry)); + + if (dl > max_dl) + { + png_warning(png_ptr, "sPLT chunk too long"); + return; + } + + new_palette.nentries = (png_int_32)(data_length / (unsigned int)entry_size); + + new_palette.entries = (png_sPLT_entryp)png_malloc_warn(png_ptr, + (png_alloc_size_t) new_palette.nentries * (sizeof (png_sPLT_entry))); + + if (new_palette.entries == NULL) + { + png_warning(png_ptr, "sPLT chunk requires too much memory"); + return; + } + +#ifdef PNG_POINTER_INDEXING_SUPPORTED + for (i = 0; i < new_palette.nentries; i++) + { + pp = new_palette.entries + i; + + if (new_palette.depth == 8) + { + pp->red = *entry_start++; + pp->green = *entry_start++; + pp->blue = *entry_start++; + pp->alpha = *entry_start++; + } + + else + { + pp->red = png_get_uint_16(entry_start); entry_start += 2; + pp->green = png_get_uint_16(entry_start); entry_start += 2; + pp->blue = png_get_uint_16(entry_start); entry_start += 2; + pp->alpha = png_get_uint_16(entry_start); entry_start += 2; + } + + pp->frequency = png_get_uint_16(entry_start); entry_start += 2; + } +#else + pp = new_palette.entries; + + for (i = 0; i < new_palette.nentries; i++) + { + + if (new_palette.depth == 8) + { + pp[i].red = *entry_start++; + pp[i].green = *entry_start++; + pp[i].blue = *entry_start++; + pp[i].alpha = *entry_start++; + } + + else + { + pp[i].red = png_get_uint_16(entry_start); entry_start += 2; + pp[i].green = png_get_uint_16(entry_start); entry_start += 2; + pp[i].blue = png_get_uint_16(entry_start); entry_start += 2; + pp[i].alpha = png_get_uint_16(entry_start); entry_start += 2; + } + + pp[i].frequency = png_get_uint_16(entry_start); entry_start += 2; + } +#endif + + /* Discard all chunk data except the name and stash that */ + new_palette.name = (png_charp)buffer; + + png_set_sPLT(png_ptr, info_ptr, &new_palette, 1); + + png_free(png_ptr, new_palette.entries); +} +#endif /* READ_sPLT */ + +#ifdef PNG_READ_tRNS_SUPPORTED +void /* PRIVATE */ +png_handle_tRNS(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte readbuf[PNG_MAX_PALETTE_LENGTH]; + + png_debug(1, "in png_handle_tRNS"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY) + { + png_byte buf[2]; + + if (length != 2) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 2); + png_ptr->num_trans = 1; + png_ptr->trans_color.gray = png_get_uint_16(buf); + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB) + { + png_byte buf[6]; + + if (length != 6) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, length); + png_ptr->num_trans = 1; + png_ptr->trans_color.red = png_get_uint_16(buf); + png_ptr->trans_color.green = png_get_uint_16(buf + 2); + png_ptr->trans_color.blue = png_get_uint_16(buf + 4); + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if ((png_ptr->mode & PNG_HAVE_PLTE) == 0) + { + /* TODO: is this actually an error in the ISO spec? */ + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length > (unsigned int) png_ptr->num_palette || + length > (unsigned int) PNG_MAX_PALETTE_LENGTH || + length == 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, readbuf, length); + png_ptr->num_trans = (png_uint_16)length; + } + + else + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid with alpha channel"); + return; + } + + if (png_crc_finish(png_ptr, 0) != 0) + { + png_ptr->num_trans = 0; + return; + } + + /* TODO: this is a horrible side effect in the palette case because the + * png_struct ends up with a pointer to the tRNS buffer owned by the + * png_info. Fix this. + */ + png_set_tRNS(png_ptr, info_ptr, readbuf, png_ptr->num_trans, + &(png_ptr->trans_color)); +} +#endif + +#ifdef PNG_READ_bKGD_SUPPORTED +void /* PRIVATE */ +png_handle_bKGD(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + unsigned int truelen; + png_byte buf[6]; + png_color_16 background; + + png_debug(1, "in png_handle_bKGD"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0 || + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + (png_ptr->mode & PNG_HAVE_PLTE) == 0)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + truelen = 1; + + else if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0) + truelen = 6; + + else + truelen = 2; + + if (length != truelen) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, truelen); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + /* We convert the index value into RGB components so that we can allow + * arbitrary RGB values for background when we have transparency, and + * so it is easy to determine the RGB values of the background color + * from the info_ptr struct. + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + background.index = buf[0]; + + if (info_ptr != NULL && info_ptr->num_palette != 0) + { + if (buf[0] >= info_ptr->num_palette) + { + png_chunk_benign_error(png_ptr, "invalid index"); + return; + } + + background.red = (png_uint_16)png_ptr->palette[buf[0]].red; + background.green = (png_uint_16)png_ptr->palette[buf[0]].green; + background.blue = (png_uint_16)png_ptr->palette[buf[0]].blue; + } + + else + background.red = background.green = background.blue = 0; + + background.gray = 0; + } + + else if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) == 0) /* GRAY */ + { + if (png_ptr->bit_depth <= 8) + { + if (buf[0] != 0 || buf[1] >= (unsigned int)(1 << png_ptr->bit_depth)) + { + png_chunk_benign_error(png_ptr, "invalid gray level"); + return; + } + } + + background.index = 0; + background.red = + background.green = + background.blue = + background.gray = png_get_uint_16(buf); + } + + else + { + if (png_ptr->bit_depth <= 8) + { + if (buf[0] != 0 || buf[2] != 0 || buf[4] != 0) + { + png_chunk_benign_error(png_ptr, "invalid color"); + return; + } + } + + background.index = 0; + background.red = png_get_uint_16(buf); + background.green = png_get_uint_16(buf + 2); + background.blue = png_get_uint_16(buf + 4); + background.gray = 0; + } + + png_set_bKGD(png_ptr, info_ptr, &background); +} +#endif + +#ifdef PNG_READ_eXIf_SUPPORTED +void /* PRIVATE */ +png_handle_eXIf(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + unsigned int i; + + png_debug(1, "in png_handle_eXIf"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + if (length < 2) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too short"); + return; + } + + else if (info_ptr == NULL || (info_ptr->valid & PNG_INFO_eXIf) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + info_ptr->free_me |= PNG_FREE_EXIF; + + info_ptr->eXIf_buf = png_voidcast(png_bytep, + png_malloc_warn(png_ptr, length)); + + if (info_ptr->eXIf_buf == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + for (i = 0; i < length; i++) + { + png_byte buf[1]; + png_crc_read(png_ptr, buf, 1); + info_ptr->eXIf_buf[i] = buf[0]; + if (i == 1 && buf[0] != 'M' && buf[0] != 'I' + && info_ptr->eXIf_buf[0] != buf[0]) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "incorrect byte-order specifier"); + png_free(png_ptr, info_ptr->eXIf_buf); + info_ptr->eXIf_buf = NULL; + return; + } + } + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + png_set_eXIf_1(png_ptr, info_ptr, length, info_ptr->eXIf_buf); + + png_free(png_ptr, info_ptr->eXIf_buf); + info_ptr->eXIf_buf = NULL; +} +#endif + +#ifdef PNG_READ_hIST_SUPPORTED +void /* PRIVATE */ +png_handle_hIST(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + unsigned int num, i; + png_uint_16 readbuf[PNG_MAX_PALETTE_LENGTH]; + + png_debug(1, "in png_handle_hIST"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0 || + (png_ptr->mode & PNG_HAVE_PLTE) == 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_hIST) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + num = length / 2 ; + + if (num != (unsigned int) png_ptr->num_palette || + num > (unsigned int) PNG_MAX_PALETTE_LENGTH) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + for (i = 0; i < num; i++) + { + png_byte buf[2]; + + png_crc_read(png_ptr, buf, 2); + readbuf[i] = png_get_uint_16(buf); + } + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + png_set_hIST(png_ptr, info_ptr, readbuf); +} +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED +void /* PRIVATE */ +png_handle_pHYs(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[9]; + png_uint_32 res_x, res_y; + int unit_type; + + png_debug(1, "in png_handle_pHYs"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (length != 9) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 9); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + res_x = png_get_uint_32(buf); + res_y = png_get_uint_32(buf + 4); + unit_type = buf[8]; + png_set_pHYs(png_ptr, info_ptr, res_x, res_y, unit_type); +} +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED +void /* PRIVATE */ +png_handle_oFFs(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[9]; + png_int_32 offset_x, offset_y; + int unit_type; + + png_debug(1, "in png_handle_oFFs"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (length != 9) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 9); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + offset_x = png_get_int_32(buf); + offset_y = png_get_int_32(buf + 4); + unit_type = buf[8]; + png_set_oFFs(png_ptr, info_ptr, offset_x, offset_y, unit_type); +} +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED +/* Read the pCAL chunk (described in the PNG Extensions document) */ +void /* PRIVATE */ +png_handle_pCAL(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_int_32 X0, X1; + png_byte type, nparams; + png_bytep buffer, buf, units, endptr; + png_charpp params; + int i; + + png_debug(1, "in png_handle_pCAL"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_pCAL) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + png_debug1(2, "Allocating and reading pCAL chunk data (%u bytes)", + length + 1); + + buffer = png_read_buffer(png_ptr, length+1, 2/*silent*/); + + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + buffer[length] = 0; /* Null terminate the last string */ + + png_debug(3, "Finding end of pCAL purpose string"); + for (buf = buffer; *buf; buf++) + /* Empty loop */ ; + + endptr = buffer + length; + + /* We need to have at least 12 bytes after the purpose string + * in order to get the parameter information. + */ + if (endptr - buf <= 12) + { + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_debug(3, "Reading pCAL X0, X1, type, nparams, and units"); + X0 = png_get_int_32((png_bytep)buf+1); + X1 = png_get_int_32((png_bytep)buf+5); + type = buf[9]; + nparams = buf[10]; + units = buf + 11; + + png_debug(3, "Checking pCAL equation type and number of parameters"); + /* Check that we have the right number of parameters for known + * equation types. + */ + if ((type == PNG_EQUATION_LINEAR && nparams != 2) || + (type == PNG_EQUATION_BASE_E && nparams != 3) || + (type == PNG_EQUATION_ARBITRARY && nparams != 3) || + (type == PNG_EQUATION_HYPERBOLIC && nparams != 4)) + { + png_chunk_benign_error(png_ptr, "invalid parameter count"); + return; + } + + else if (type >= PNG_EQUATION_LAST) + { + png_chunk_benign_error(png_ptr, "unrecognized equation type"); + } + + for (buf = units; *buf; buf++) + /* Empty loop to move past the units string. */ ; + + png_debug(3, "Allocating pCAL parameters array"); + + params = png_voidcast(png_charpp, png_malloc_warn(png_ptr, + nparams * (sizeof (png_charp)))); + + if (params == NULL) + { + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + /* Get pointers to the start of each parameter string. */ + for (i = 0; i < nparams; i++) + { + buf++; /* Skip the null string terminator from previous parameter. */ + + png_debug1(3, "Reading pCAL parameter %d", i); + + for (params[i] = (png_charp)buf; buf <= endptr && *buf != 0; buf++) + /* Empty loop to move past each parameter string */ ; + + /* Make sure we haven't run out of data yet */ + if (buf > endptr) + { + png_free(png_ptr, params); + png_chunk_benign_error(png_ptr, "invalid data"); + return; + } + } + + png_set_pCAL(png_ptr, info_ptr, (png_charp)buffer, X0, X1, type, nparams, + (png_charp)units, params); + + png_free(png_ptr, params); +} +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED +/* Read the sCAL chunk */ +void /* PRIVATE */ +png_handle_sCAL(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_bytep buffer; + size_t i; + int state; + + png_debug(1, "in png_handle_sCAL"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sCAL) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + /* Need unit type, width, \0, height: minimum 4 bytes */ + else if (length < 4) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_debug1(2, "Allocating and reading sCAL chunk data (%u bytes)", + length + 1); + + buffer = png_read_buffer(png_ptr, length+1, 2/*silent*/); + + if (buffer == NULL) + { + png_chunk_benign_error(png_ptr, "out of memory"); + png_crc_finish(png_ptr, length); + return; + } + + png_crc_read(png_ptr, buffer, length); + buffer[length] = 0; /* Null terminate the last string */ + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + /* Validate the unit. */ + if (buffer[0] != 1 && buffer[0] != 2) + { + png_chunk_benign_error(png_ptr, "invalid unit"); + return; + } + + /* Validate the ASCII numbers, need two ASCII numbers separated by + * a '\0' and they need to fit exactly in the chunk data. + */ + i = 1; + state = 0; + + if (png_check_fp_number((png_const_charp)buffer, length, &state, &i) == 0 || + i >= length || buffer[i++] != 0) + png_chunk_benign_error(png_ptr, "bad width format"); + + else if (PNG_FP_IS_POSITIVE(state) == 0) + png_chunk_benign_error(png_ptr, "non-positive width"); + + else + { + size_t heighti = i; + + state = 0; + if (png_check_fp_number((png_const_charp)buffer, length, + &state, &i) == 0 || i != length) + png_chunk_benign_error(png_ptr, "bad height format"); + + else if (PNG_FP_IS_POSITIVE(state) == 0) + png_chunk_benign_error(png_ptr, "non-positive height"); + + else + /* This is the (only) success case. */ + png_set_sCAL_s(png_ptr, info_ptr, buffer[0], + (png_charp)buffer+1, (png_charp)buffer+heighti); + } +} +#endif + +#ifdef PNG_READ_tIME_SUPPORTED +void /* PRIVATE */ +png_handle_tIME(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[7]; + png_time mod_time; + + png_debug(1, "in png_handle_tIME"); + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tIME) != 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + png_ptr->mode |= PNG_AFTER_IDAT; + + if (length != 7) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 7); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + mod_time.second = buf[6]; + mod_time.minute = buf[5]; + mod_time.hour = buf[4]; + mod_time.day = buf[3]; + mod_time.month = buf[2]; + mod_time.year = png_get_uint_16(buf); + + png_set_tIME(png_ptr, info_ptr, &mod_time); +} +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED +/* Note: this does not properly handle chunks that are > 64K under DOS */ +void /* PRIVATE */ +png_handle_tEXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_text text_info; + png_bytep buffer; + png_charp key; + png_charp text; + png_uint_32 skip = 0; + + png_debug(1, "in png_handle_tEXt"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + return; + } + } +#endif + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + png_ptr->mode |= PNG_AFTER_IDAT; + +#ifdef PNG_MAX_MALLOC_64K + if (length > 65535U) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too large to fit in memory"); + return; + } +#endif + + buffer = png_read_buffer(png_ptr, length+1, 1/*warn*/); + + if (buffer == NULL) + { + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, skip) != 0) + return; + + key = (png_charp)buffer; + key[length] = 0; + + for (text = key; *text; text++) + /* Empty loop to find end of key */ ; + + if (text != key + length) + text++; + + text_info.compression = PNG_TEXT_COMPRESSION_NONE; + text_info.key = key; + text_info.lang = NULL; + text_info.lang_key = NULL; + text_info.itxt_length = 0; + text_info.text = text; + text_info.text_length = strlen(text); + + if (png_set_text_2(png_ptr, info_ptr, &text_info, 1) != 0) + png_warning(png_ptr, "Insufficient memory to process text chunk"); +} +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED +/* Note: this does not correctly handle chunks that are > 64K under DOS */ +void /* PRIVATE */ +png_handle_zTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_const_charp errmsg = NULL; + png_bytep buffer; + png_uint_32 keyword_length; + + png_debug(1, "in png_handle_zTXt"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + return; + } + } +#endif + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + png_ptr->mode |= PNG_AFTER_IDAT; + + /* Note, "length" is sufficient here; we won't be adding + * a null terminator later. + */ + buffer = png_read_buffer(png_ptr, length, 2/*silent*/); + + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + /* TODO: also check that the keyword contents match the spec! */ + for (keyword_length = 0; + keyword_length < length && buffer[keyword_length] != 0; + ++keyword_length) + /* Empty loop to find end of name */ ; + + if (keyword_length > 79 || keyword_length < 1) + errmsg = "bad keyword"; + + /* zTXt must have some LZ data after the keyword, although it may expand to + * zero bytes; we need a '\0' at the end of the keyword, the compression type + * then the LZ data: + */ + else if (keyword_length + 3 > length) + errmsg = "truncated"; + + else if (buffer[keyword_length+1] != PNG_COMPRESSION_TYPE_BASE) + errmsg = "unknown compression type"; + + else + { + png_alloc_size_t uncompressed_length = PNG_SIZE_MAX; + + /* TODO: at present png_decompress_chunk imposes a single application + * level memory limit, this should be split to different values for iCCP + * and text chunks. + */ + if (png_decompress_chunk(png_ptr, length, keyword_length+2, + &uncompressed_length, 1/*terminate*/) == Z_STREAM_END) + { + png_text text; + + if (png_ptr->read_buffer == NULL) + errmsg="Read failure in png_handle_zTXt"; + else + { + /* It worked; png_ptr->read_buffer now looks like a tEXt chunk + * except for the extra compression type byte and the fact that + * it isn't necessarily '\0' terminated. + */ + buffer = png_ptr->read_buffer; + buffer[uncompressed_length+(keyword_length+2)] = 0; + + text.compression = PNG_TEXT_COMPRESSION_zTXt; + text.key = (png_charp)buffer; + text.text = (png_charp)(buffer + keyword_length+2); + text.text_length = uncompressed_length; + text.itxt_length = 0; + text.lang = NULL; + text.lang_key = NULL; + + if (png_set_text_2(png_ptr, info_ptr, &text, 1) != 0) + errmsg = "insufficient memory"; + } + } + + else + errmsg = png_ptr->zstream.msg; + } + + if (errmsg != NULL) + png_chunk_benign_error(png_ptr, errmsg); +} +#endif + +#ifdef PNG_READ_iTXt_SUPPORTED +/* Note: this does not correctly handle chunks that are > 64K under DOS */ +void /* PRIVATE */ +png_handle_iTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_const_charp errmsg = NULL; + png_bytep buffer; + png_uint_32 prefix_length; + + png_debug(1, "in png_handle_iTXt"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + return; + } + } +#endif + + if ((png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_chunk_error(png_ptr, "missing IHDR"); + + if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + png_ptr->mode |= PNG_AFTER_IDAT; + + buffer = png_read_buffer(png_ptr, length+1, 1/*warn*/); + + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, 0) != 0) + return; + + /* First the keyword. */ + for (prefix_length=0; + prefix_length < length && buffer[prefix_length] != 0; + ++prefix_length) + /* Empty loop */ ; + + /* Perform a basic check on the keyword length here. */ + if (prefix_length > 79 || prefix_length < 1) + errmsg = "bad keyword"; + + /* Expect keyword, compression flag, compression type, language, translated + * keyword (both may be empty but are 0 terminated) then the text, which may + * be empty. + */ + else if (prefix_length + 5 > length) + errmsg = "truncated"; + + else if (buffer[prefix_length+1] == 0 || + (buffer[prefix_length+1] == 1 && + buffer[prefix_length+2] == PNG_COMPRESSION_TYPE_BASE)) + { + int compressed = buffer[prefix_length+1] != 0; + png_uint_32 language_offset, translated_keyword_offset; + png_alloc_size_t uncompressed_length = 0; + + /* Now the language tag */ + prefix_length += 3; + language_offset = prefix_length; + + for (; prefix_length < length && buffer[prefix_length] != 0; + ++prefix_length) + /* Empty loop */ ; + + /* WARNING: the length may be invalid here, this is checked below. */ + translated_keyword_offset = ++prefix_length; + + for (; prefix_length < length && buffer[prefix_length] != 0; + ++prefix_length) + /* Empty loop */ ; + + /* prefix_length should now be at the trailing '\0' of the translated + * keyword, but it may already be over the end. None of this arithmetic + * can overflow because chunks are at most 2^31 bytes long, but on 16-bit + * systems the available allocation may overflow. + */ + ++prefix_length; + + if (compressed == 0 && prefix_length <= length) + uncompressed_length = length - prefix_length; + + else if (compressed != 0 && prefix_length < length) + { + uncompressed_length = PNG_SIZE_MAX; + + /* TODO: at present png_decompress_chunk imposes a single application + * level memory limit, this should be split to different values for + * iCCP and text chunks. + */ + if (png_decompress_chunk(png_ptr, length, prefix_length, + &uncompressed_length, 1/*terminate*/) == Z_STREAM_END) + buffer = png_ptr->read_buffer; + + else + errmsg = png_ptr->zstream.msg; + } + + else + errmsg = "truncated"; + + if (errmsg == NULL) + { + png_text text; + + buffer[uncompressed_length+prefix_length] = 0; + + if (compressed == 0) + text.compression = PNG_ITXT_COMPRESSION_NONE; + + else + text.compression = PNG_ITXT_COMPRESSION_zTXt; + + text.key = (png_charp)buffer; + text.lang = (png_charp)buffer + language_offset; + text.lang_key = (png_charp)buffer + translated_keyword_offset; + text.text = (png_charp)buffer + prefix_length; + text.text_length = 0; + text.itxt_length = uncompressed_length; + + if (png_set_text_2(png_ptr, info_ptr, &text, 1) != 0) + errmsg = "insufficient memory"; + } + } + + else + errmsg = "bad compression info"; + + if (errmsg != NULL) + png_chunk_benign_error(png_ptr, errmsg); +} +#endif + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +/* Utility function for png_handle_unknown; set up png_ptr::unknown_chunk */ +static int +png_cache_unknown_chunk(png_structrp png_ptr, png_uint_32 length) +{ + png_alloc_size_t limit = PNG_SIZE_MAX; + + if (png_ptr->unknown_chunk.data != NULL) + { + png_free(png_ptr, png_ptr->unknown_chunk.data); + png_ptr->unknown_chunk.data = NULL; + } + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_malloc_max > 0 && + png_ptr->user_chunk_malloc_max < limit) + limit = png_ptr->user_chunk_malloc_max; + +# elif PNG_USER_CHUNK_MALLOC_MAX > 0 + if (PNG_USER_CHUNK_MALLOC_MAX < limit) + limit = PNG_USER_CHUNK_MALLOC_MAX; +# endif + + if (length <= limit) + { + PNG_CSTRING_FROM_CHUNK(png_ptr->unknown_chunk.name, png_ptr->chunk_name); + /* The following is safe because of the PNG_SIZE_MAX init above */ + png_ptr->unknown_chunk.size = (size_t)length/*SAFE*/; + /* 'mode' is a flag array, only the bottom four bits matter here */ + png_ptr->unknown_chunk.location = (png_byte)png_ptr->mode/*SAFE*/; + + if (length == 0) + png_ptr->unknown_chunk.data = NULL; + + else + { + /* Do a 'warn' here - it is handled below. */ + png_ptr->unknown_chunk.data = png_voidcast(png_bytep, + png_malloc_warn(png_ptr, length)); + } + } + + if (png_ptr->unknown_chunk.data == NULL && length > 0) + { + /* This is benign because we clean up correctly */ + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "unknown chunk exceeds memory limits"); + return 0; + } + + else + { + if (length > 0) + png_crc_read(png_ptr, png_ptr->unknown_chunk.data, length); + png_crc_finish(png_ptr, 0); + return 1; + } +} +#endif /* READ_UNKNOWN_CHUNKS */ + +/* Handle an unknown, or known but disabled, chunk */ +void /* PRIVATE */ +png_handle_unknown(png_structrp png_ptr, png_inforp info_ptr, + png_uint_32 length, int keep) +{ + int handled = 0; /* the chunk was handled */ + + png_debug(1, "in png_handle_unknown"); + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + /* NOTE: this code is based on the code in libpng-1.4.12 except for fixing + * the bug which meant that setting a non-default behavior for a specific + * chunk would be ignored (the default was always used unless a user + * callback was installed). + * + * 'keep' is the value from the png_chunk_unknown_handling, the setting for + * this specific chunk_name, if PNG_HANDLE_AS_UNKNOWN_SUPPORTED, if not it + * will always be PNG_HANDLE_CHUNK_AS_DEFAULT and it needs to be set here. + * This is just an optimization to avoid multiple calls to the lookup + * function. + */ +# ifndef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +# ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + keep = png_chunk_unknown_handling(png_ptr, png_ptr->chunk_name); +# endif +# endif + + /* One of the following methods will read the chunk or skip it (at least one + * of these is always defined because this is the only way to switch on + * PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) + */ +# ifdef PNG_READ_USER_CHUNKS_SUPPORTED + /* The user callback takes precedence over the chunk keep value, but the + * keep value is still required to validate a save of a critical chunk. + */ + if (png_ptr->read_user_chunk_fn != NULL) + { + if (png_cache_unknown_chunk(png_ptr, length) != 0) + { + /* Callback to user unknown chunk handler */ + int ret = (*(png_ptr->read_user_chunk_fn))(png_ptr, + &png_ptr->unknown_chunk); + + /* ret is: + * negative: An error occurred; png_chunk_error will be called. + * zero: The chunk was not handled, the chunk will be discarded + * unless png_set_keep_unknown_chunks has been used to set + * a 'keep' behavior for this particular chunk, in which + * case that will be used. A critical chunk will cause an + * error at this point unless it is to be saved. + * positive: The chunk was handled, libpng will ignore/discard it. + */ + if (ret < 0) + png_chunk_error(png_ptr, "error in user chunk"); + + else if (ret == 0) + { + /* If the keep value is 'default' or 'never' override it, but + * still error out on critical chunks unless the keep value is + * 'always' While this is weird it is the behavior in 1.4.12. + * A possible improvement would be to obey the value set for the + * chunk, but this would be an API change that would probably + * damage some applications. + * + * The png_app_warning below catches the case that matters, where + * the application has not set specific save or ignore for this + * chunk or global save or ignore. + */ + if (keep < PNG_HANDLE_CHUNK_IF_SAFE) + { +# ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + if (png_ptr->unknown_default < PNG_HANDLE_CHUNK_IF_SAFE) + { + png_chunk_warning(png_ptr, "Saving unknown chunk:"); + png_app_warning(png_ptr, + "forcing save of an unhandled chunk;" + " please call png_set_keep_unknown_chunks"); + /* with keep = PNG_HANDLE_CHUNK_IF_SAFE */ + } +# endif + keep = PNG_HANDLE_CHUNK_IF_SAFE; + } + } + + else /* chunk was handled */ + { + handled = 1; + /* Critical chunks can be safely discarded at this point. */ + keep = PNG_HANDLE_CHUNK_NEVER; + } + } + + else + keep = PNG_HANDLE_CHUNK_NEVER; /* insufficient memory */ + } + + else + /* Use the SAVE_UNKNOWN_CHUNKS code or skip the chunk */ +# endif /* READ_USER_CHUNKS */ + +# ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED + { + /* keep is currently just the per-chunk setting, if there was no + * setting change it to the global default now (not that this may + * still be AS_DEFAULT) then obtain the cache of the chunk if required, + * if not simply skip the chunk. + */ + if (keep == PNG_HANDLE_CHUNK_AS_DEFAULT) + keep = png_ptr->unknown_default; + + if (keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_IF_SAFE && + PNG_CHUNK_ANCILLARY(png_ptr->chunk_name))) + { + if (png_cache_unknown_chunk(png_ptr, length) == 0) + keep = PNG_HANDLE_CHUNK_NEVER; + } + + else + png_crc_finish(png_ptr, length); + } +# else +# ifndef PNG_READ_USER_CHUNKS_SUPPORTED +# error no method to support READ_UNKNOWN_CHUNKS +# endif + + { + /* If here there is no read callback pointer set and no support is + * compiled in to just save the unknown chunks, so simply skip this + * chunk. If 'keep' is something other than AS_DEFAULT or NEVER then + * the app has erroneously asked for unknown chunk saving when there + * is no support. + */ + if (keep > PNG_HANDLE_CHUNK_NEVER) + png_app_error(png_ptr, "no unknown chunk support available"); + + png_crc_finish(png_ptr, length); + } +# endif + +# ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + /* Now store the chunk in the chunk list if appropriate, and if the limits + * permit it. + */ + if (keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_IF_SAFE && + PNG_CHUNK_ANCILLARY(png_ptr->chunk_name))) + { +# ifdef PNG_USER_LIMITS_SUPPORTED + switch (png_ptr->user_chunk_cache_max) + { + case 2: + png_ptr->user_chunk_cache_max = 1; + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + /* FALLTHROUGH */ + case 1: + /* NOTE: prior to 1.6.0 this case resulted in an unknown critical + * chunk being skipped, now there will be a hard error below. + */ + break; + + default: /* not at limit */ + --(png_ptr->user_chunk_cache_max); + /* FALLTHROUGH */ + case 0: /* no limit */ +# endif /* USER_LIMITS */ + /* Here when the limit isn't reached or when limits are compiled + * out; store the chunk. + */ + png_set_unknown_chunks(png_ptr, info_ptr, + &png_ptr->unknown_chunk, 1); + handled = 1; +# ifdef PNG_USER_LIMITS_SUPPORTED + break; + } +# endif + } +# else /* no store support: the chunk must be handled by the user callback */ + PNG_UNUSED(info_ptr) +# endif + + /* Regardless of the error handling below the cached data (if any) can be + * freed now. Notice that the data is not freed if there is a png_error, but + * it will be freed by destroy_read_struct. + */ + if (png_ptr->unknown_chunk.data != NULL) + png_free(png_ptr, png_ptr->unknown_chunk.data); + png_ptr->unknown_chunk.data = NULL; + +#else /* !PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ + /* There is no support to read an unknown chunk, so just skip it. */ + png_crc_finish(png_ptr, length); + PNG_UNUSED(info_ptr) + PNG_UNUSED(keep) +#endif /* !READ_UNKNOWN_CHUNKS */ + + /* Check for unhandled critical chunks */ + if (handled == 0 && PNG_CHUNK_CRITICAL(png_ptr->chunk_name)) + png_chunk_error(png_ptr, "unhandled critical chunk"); +} + +/* This function is called to verify that a chunk name is valid. + * This function can't have the "critical chunk check" incorporated + * into it, since in the future we will need to be able to call user + * functions to handle unknown critical chunks after we check that + * the chunk name itself is valid. + */ + +/* Bit hacking: the test for an invalid byte in the 4 byte chunk name is: + * + * ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97)) + */ + +void /* PRIVATE */ +png_check_chunk_name(png_const_structrp png_ptr, png_uint_32 chunk_name) +{ + int i; + png_uint_32 cn=chunk_name; + + png_debug(1, "in png_check_chunk_name"); + + for (i=1; i<=4; ++i) + { + int c = cn & 0xff; + + if (c < 65 || c > 122 || (c > 90 && c < 97)) + png_chunk_error(png_ptr, "invalid chunk type"); + + cn >>= 8; + } +} + +void /* PRIVATE */ +png_check_chunk_length(png_const_structrp png_ptr, png_uint_32 length) +{ + png_alloc_size_t limit = PNG_UINT_31_MAX; + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_malloc_max > 0 && + png_ptr->user_chunk_malloc_max < limit) + limit = png_ptr->user_chunk_malloc_max; +# elif PNG_USER_CHUNK_MALLOC_MAX > 0 + if (PNG_USER_CHUNK_MALLOC_MAX < limit) + limit = PNG_USER_CHUNK_MALLOC_MAX; +# endif + if (png_ptr->chunk_name == png_IDAT) + { + png_alloc_size_t idat_limit = PNG_UINT_31_MAX; + size_t row_factor = + (size_t)png_ptr->width + * (size_t)png_ptr->channels + * (png_ptr->bit_depth > 8? 2: 1) + + 1 + + (png_ptr->interlaced? 6: 0); + if (png_ptr->height > PNG_UINT_32_MAX/row_factor) + idat_limit = PNG_UINT_31_MAX; + else + idat_limit = png_ptr->height * row_factor; + row_factor = row_factor > 32566? 32566 : row_factor; + idat_limit += 6 + 5*(idat_limit/row_factor+1); /* zlib+deflate overhead */ + idat_limit=idat_limit < PNG_UINT_31_MAX? idat_limit : PNG_UINT_31_MAX; + limit = limit < idat_limit? idat_limit : limit; + } + + if (length > limit) + { + png_debug2(0," length = %lu, limit = %lu", + (unsigned long)length,(unsigned long)limit); + png_chunk_error(png_ptr, "chunk data is too large"); + } +} + +/* Combines the row recently read in with the existing pixels in the row. This + * routine takes care of alpha and transparency if requested. This routine also + * handles the two methods of progressive display of interlaced images, + * depending on the 'display' value; if 'display' is true then the whole row + * (dp) is filled from the start by replicating the available pixels. If + * 'display' is false only those pixels present in the pass are filled in. + */ +void /* PRIVATE */ +png_combine_row(png_const_structrp png_ptr, png_bytep dp, int display) +{ + unsigned int pixel_depth = png_ptr->transformed_pixel_depth; + png_const_bytep sp = png_ptr->row_buf + 1; + png_alloc_size_t row_width = png_ptr->width; + unsigned int pass = png_ptr->pass; + png_bytep end_ptr = 0; + png_byte end_byte = 0; + unsigned int end_mask; + + png_debug(1, "in png_combine_row"); + + /* Added in 1.5.6: it should not be possible to enter this routine until at + * least one row has been read from the PNG data and transformed. + */ + if (pixel_depth == 0) + png_error(png_ptr, "internal row logic error"); + + /* Added in 1.5.4: the pixel depth should match the information returned by + * any call to png_read_update_info at this point. Do not continue if we got + * this wrong. + */ + if (png_ptr->info_rowbytes != 0 && png_ptr->info_rowbytes != + PNG_ROWBYTES(pixel_depth, row_width)) + png_error(png_ptr, "internal row size calculation error"); + + /* Don't expect this to ever happen: */ + if (row_width == 0) + png_error(png_ptr, "internal row width error"); + + /* Preserve the last byte in cases where only part of it will be overwritten, + * the multiply below may overflow, we don't care because ANSI-C guarantees + * we get the low bits. + */ + end_mask = (pixel_depth * row_width) & 7; + if (end_mask != 0) + { + /* end_ptr == NULL is a flag to say do nothing */ + end_ptr = dp + PNG_ROWBYTES(pixel_depth, row_width) - 1; + end_byte = *end_ptr; +# ifdef PNG_READ_PACKSWAP_SUPPORTED + if ((png_ptr->transformations & PNG_PACKSWAP) != 0) + /* little-endian byte */ + end_mask = (unsigned int)(0xff << end_mask); + + else /* big-endian byte */ +# endif + end_mask = 0xff >> end_mask; + /* end_mask is now the bits to *keep* from the destination row */ + } + + /* For non-interlaced images this reduces to a memcpy(). A memcpy() + * will also happen if interlacing isn't supported or if the application + * does not call png_set_interlace_handling(). In the latter cases the + * caller just gets a sequence of the unexpanded rows from each interlace + * pass. + */ +#ifdef PNG_READ_INTERLACING_SUPPORTED + if (png_ptr->interlaced != 0 && + (png_ptr->transformations & PNG_INTERLACE) != 0 && + pass < 6 && (display == 0 || + /* The following copies everything for 'display' on passes 0, 2 and 4. */ + (display == 1 && (pass & 1) != 0))) + { + /* Narrow images may have no bits in a pass; the caller should handle + * this, but this test is cheap: + */ + if (row_width <= PNG_PASS_START_COL(pass)) + return; + + if (pixel_depth < 8) + { + /* For pixel depths up to 4 bpp the 8-pixel mask can be expanded to fit + * into 32 bits, then a single loop over the bytes using the four byte + * values in the 32-bit mask can be used. For the 'display' option the + * expanded mask may also not require any masking within a byte. To + * make this work the PACKSWAP option must be taken into account - it + * simply requires the pixels to be reversed in each byte. + * + * The 'regular' case requires a mask for each of the first 6 passes, + * the 'display' case does a copy for the even passes in the range + * 0..6. This has already been handled in the test above. + * + * The masks are arranged as four bytes with the first byte to use in + * the lowest bits (little-endian) regardless of the order (PACKSWAP or + * not) of the pixels in each byte. + * + * NOTE: the whole of this logic depends on the caller of this function + * only calling it on rows appropriate to the pass. This function only + * understands the 'x' logic; the 'y' logic is handled by the caller. + * + * The following defines allow generation of compile time constant bit + * masks for each pixel depth and each possibility of swapped or not + * swapped bytes. Pass 'p' is in the range 0..6; 'x', a pixel index, + * is in the range 0..7; and the result is 1 if the pixel is to be + * copied in the pass, 0 if not. 'S' is for the sparkle method, 'B' + * for the block method. + * + * With some compilers a compile time expression of the general form: + * + * (shift >= 32) ? (a >> (shift-32)) : (b >> shift) + * + * Produces warnings with values of 'shift' in the range 33 to 63 + * because the right hand side of the ?: expression is evaluated by + * the compiler even though it isn't used. Microsoft Visual C (various + * versions) and the Intel C compiler are known to do this. To avoid + * this the following macros are used in 1.5.6. This is a temporary + * solution to avoid destabilizing the code during the release process. + */ +# if PNG_USE_COMPILE_TIME_MASKS +# define PNG_LSR(x,s) ((x)>>((s) & 0x1f)) +# define PNG_LSL(x,s) ((x)<<((s) & 0x1f)) +# else +# define PNG_LSR(x,s) ((x)>>(s)) +# define PNG_LSL(x,s) ((x)<<(s)) +# endif +# define S_COPY(p,x) (((p)<4 ? PNG_LSR(0x80088822,(3-(p))*8+(7-(x))) :\ + PNG_LSR(0xaa55ff00,(7-(p))*8+(7-(x)))) & 1) +# define B_COPY(p,x) (((p)<4 ? PNG_LSR(0xff0fff33,(3-(p))*8+(7-(x))) :\ + PNG_LSR(0xff55ff00,(7-(p))*8+(7-(x)))) & 1) + + /* Return a mask for pass 'p' pixel 'x' at depth 'd'. The mask is + * little endian - the first pixel is at bit 0 - however the extra + * parameter 's' can be set to cause the mask position to be swapped + * within each byte, to match the PNG format. This is done by XOR of + * the shift with 7, 6 or 4 for bit depths 1, 2 and 4. + */ +# define PIXEL_MASK(p,x,d,s) \ + (PNG_LSL(((PNG_LSL(1U,(d)))-1),(((x)*(d))^((s)?8-(d):0)))) + + /* Hence generate the appropriate 'block' or 'sparkle' pixel copy mask. + */ +# define S_MASKx(p,x,d,s) (S_COPY(p,x)?PIXEL_MASK(p,x,d,s):0) +# define B_MASKx(p,x,d,s) (B_COPY(p,x)?PIXEL_MASK(p,x,d,s):0) + + /* Combine 8 of these to get the full mask. For the 1-bpp and 2-bpp + * cases the result needs replicating, for the 4-bpp case the above + * generates a full 32 bits. + */ +# define MASK_EXPAND(m,d) ((m)*((d)==1?0x01010101:((d)==2?0x00010001:1))) + +# define S_MASK(p,d,s) MASK_EXPAND(S_MASKx(p,0,d,s) + S_MASKx(p,1,d,s) +\ + S_MASKx(p,2,d,s) + S_MASKx(p,3,d,s) + S_MASKx(p,4,d,s) +\ + S_MASKx(p,5,d,s) + S_MASKx(p,6,d,s) + S_MASKx(p,7,d,s), d) + +# define B_MASK(p,d,s) MASK_EXPAND(B_MASKx(p,0,d,s) + B_MASKx(p,1,d,s) +\ + B_MASKx(p,2,d,s) + B_MASKx(p,3,d,s) + B_MASKx(p,4,d,s) +\ + B_MASKx(p,5,d,s) + B_MASKx(p,6,d,s) + B_MASKx(p,7,d,s), d) + +#if PNG_USE_COMPILE_TIME_MASKS + /* Utility macros to construct all the masks for a depth/swap + * combination. The 's' parameter says whether the format is PNG + * (big endian bytes) or not. Only the three odd-numbered passes are + * required for the display/block algorithm. + */ +# define S_MASKS(d,s) { S_MASK(0,d,s), S_MASK(1,d,s), S_MASK(2,d,s),\ + S_MASK(3,d,s), S_MASK(4,d,s), S_MASK(5,d,s) } + +# define B_MASKS(d,s) { B_MASK(1,d,s), B_MASK(3,d,s), B_MASK(5,d,s) } + +# define DEPTH_INDEX(d) ((d)==1?0:((d)==2?1:2)) + + /* Hence the pre-compiled masks indexed by PACKSWAP (or not), depth and + * then pass: + */ + static const png_uint_32 row_mask[2/*PACKSWAP*/][3/*depth*/][6] = + { + /* Little-endian byte masks for PACKSWAP */ + { S_MASKS(1,0), S_MASKS(2,0), S_MASKS(4,0) }, + /* Normal (big-endian byte) masks - PNG format */ + { S_MASKS(1,1), S_MASKS(2,1), S_MASKS(4,1) } + }; + + /* display_mask has only three entries for the odd passes, so index by + * pass>>1. + */ + static const png_uint_32 display_mask[2][3][3] = + { + /* Little-endian byte masks for PACKSWAP */ + { B_MASKS(1,0), B_MASKS(2,0), B_MASKS(4,0) }, + /* Normal (big-endian byte) masks - PNG format */ + { B_MASKS(1,1), B_MASKS(2,1), B_MASKS(4,1) } + }; + +# define MASK(pass,depth,display,png)\ + ((display)?display_mask[png][DEPTH_INDEX(depth)][pass>>1]:\ + row_mask[png][DEPTH_INDEX(depth)][pass]) + +#else /* !PNG_USE_COMPILE_TIME_MASKS */ + /* This is the runtime alternative: it seems unlikely that this will + * ever be either smaller or faster than the compile time approach. + */ +# define MASK(pass,depth,display,png)\ + ((display)?B_MASK(pass,depth,png):S_MASK(pass,depth,png)) +#endif /* !USE_COMPILE_TIME_MASKS */ + + /* Use the appropriate mask to copy the required bits. In some cases + * the byte mask will be 0 or 0xff; optimize these cases. row_width is + * the number of pixels, but the code copies bytes, so it is necessary + * to special case the end. + */ + png_uint_32 pixels_per_byte = 8 / pixel_depth; + png_uint_32 mask; + +# ifdef PNG_READ_PACKSWAP_SUPPORTED + if ((png_ptr->transformations & PNG_PACKSWAP) != 0) + mask = MASK(pass, pixel_depth, display, 0); + + else +# endif + mask = MASK(pass, pixel_depth, display, 1); + + for (;;) + { + png_uint_32 m; + + /* It doesn't matter in the following if png_uint_32 has more than + * 32 bits because the high bits always match those in m<<24; it is, + * however, essential to use OR here, not +, because of this. + */ + m = mask; + mask = (m >> 8) | (m << 24); /* rotate right to good compilers */ + m &= 0xff; + + if (m != 0) /* something to copy */ + { + if (m != 0xff) + *dp = (png_byte)((*dp & ~m) | (*sp & m)); + else + *dp = *sp; + } + + /* NOTE: this may overwrite the last byte with garbage if the image + * is not an exact number of bytes wide; libpng has always done + * this. + */ + if (row_width <= pixels_per_byte) + break; /* May need to restore part of the last byte */ + + row_width -= pixels_per_byte; + ++dp; + ++sp; + } + } + + else /* pixel_depth >= 8 */ + { + unsigned int bytes_to_copy, bytes_to_jump; + + /* Validate the depth - it must be a multiple of 8 */ + if (pixel_depth & 7) + png_error(png_ptr, "invalid user transform pixel depth"); + + pixel_depth >>= 3; /* now in bytes */ + row_width *= pixel_depth; + + /* Regardless of pass number the Adam 7 interlace always results in a + * fixed number of pixels to copy then to skip. There may be a + * different number of pixels to skip at the start though. + */ + { + unsigned int offset = PNG_PASS_START_COL(pass) * pixel_depth; + + row_width -= offset; + dp += offset; + sp += offset; + } + + /* Work out the bytes to copy. */ + if (display != 0) + { + /* When doing the 'block' algorithm the pixel in the pass gets + * replicated to adjacent pixels. This is why the even (0,2,4,6) + * passes are skipped above - the entire expanded row is copied. + */ + bytes_to_copy = (1<<((6-pass)>>1)) * pixel_depth; + + /* But don't allow this number to exceed the actual row width. */ + if (bytes_to_copy > row_width) + bytes_to_copy = (unsigned int)/*SAFE*/row_width; + } + + else /* normal row; Adam7 only ever gives us one pixel to copy. */ + bytes_to_copy = pixel_depth; + + /* In Adam7 there is a constant offset between where the pixels go. */ + bytes_to_jump = PNG_PASS_COL_OFFSET(pass) * pixel_depth; + + /* And simply copy these bytes. Some optimization is possible here, + * depending on the value of 'bytes_to_copy'. Special case the low + * byte counts, which we know to be frequent. + * + * Notice that these cases all 'return' rather than 'break' - this + * avoids an unnecessary test on whether to restore the last byte + * below. + */ + switch (bytes_to_copy) + { + case 1: + for (;;) + { + *dp = *sp; + + if (row_width <= bytes_to_jump) + return; + + dp += bytes_to_jump; + sp += bytes_to_jump; + row_width -= bytes_to_jump; + } + + case 2: + /* There is a possibility of a partial copy at the end here; this + * slows the code down somewhat. + */ + do + { + dp[0] = sp[0]; dp[1] = sp[1]; + + if (row_width <= bytes_to_jump) + return; + + sp += bytes_to_jump; + dp += bytes_to_jump; + row_width -= bytes_to_jump; + } + while (row_width > 1); + + /* And there can only be one byte left at this point: */ + *dp = *sp; + return; + + case 3: + /* This can only be the RGB case, so each copy is exactly one + * pixel and it is not necessary to check for a partial copy. + */ + for (;;) + { + dp[0] = sp[0]; dp[1] = sp[1]; dp[2] = sp[2]; + + if (row_width <= bytes_to_jump) + return; + + sp += bytes_to_jump; + dp += bytes_to_jump; + row_width -= bytes_to_jump; + } + + default: +#if PNG_ALIGN_TYPE != PNG_ALIGN_NONE + /* Check for double byte alignment and, if possible, use a + * 16-bit copy. Don't attempt this for narrow images - ones that + * are less than an interlace panel wide. Don't attempt it for + * wide bytes_to_copy either - use the memcpy there. + */ + if (bytes_to_copy < 16 /*else use memcpy*/ && + png_isaligned(dp, png_uint_16) && + png_isaligned(sp, png_uint_16) && + bytes_to_copy % (sizeof (png_uint_16)) == 0 && + bytes_to_jump % (sizeof (png_uint_16)) == 0) + { + /* Everything is aligned for png_uint_16 copies, but try for + * png_uint_32 first. + */ + if (png_isaligned(dp, png_uint_32) && + png_isaligned(sp, png_uint_32) && + bytes_to_copy % (sizeof (png_uint_32)) == 0 && + bytes_to_jump % (sizeof (png_uint_32)) == 0) + { + png_uint_32p dp32 = png_aligncast(png_uint_32p,dp); + png_const_uint_32p sp32 = png_aligncastconst( + png_const_uint_32p, sp); + size_t skip = (bytes_to_jump-bytes_to_copy) / + (sizeof (png_uint_32)); + + do + { + size_t c = bytes_to_copy; + do + { + *dp32++ = *sp32++; + c -= (sizeof (png_uint_32)); + } + while (c > 0); + + if (row_width <= bytes_to_jump) + return; + + dp32 += skip; + sp32 += skip; + row_width -= bytes_to_jump; + } + while (bytes_to_copy <= row_width); + + /* Get to here when the row_width truncates the final copy. + * There will be 1-3 bytes left to copy, so don't try the + * 16-bit loop below. + */ + dp = (png_bytep)dp32; + sp = (png_const_bytep)sp32; + do + *dp++ = *sp++; + while (--row_width > 0); + return; + } + + /* Else do it in 16-bit quantities, but only if the size is + * not too large. + */ + else + { + png_uint_16p dp16 = png_aligncast(png_uint_16p, dp); + png_const_uint_16p sp16 = png_aligncastconst( + png_const_uint_16p, sp); + size_t skip = (bytes_to_jump-bytes_to_copy) / + (sizeof (png_uint_16)); + + do + { + size_t c = bytes_to_copy; + do + { + *dp16++ = *sp16++; + c -= (sizeof (png_uint_16)); + } + while (c > 0); + + if (row_width <= bytes_to_jump) + return; + + dp16 += skip; + sp16 += skip; + row_width -= bytes_to_jump; + } + while (bytes_to_copy <= row_width); + + /* End of row - 1 byte left, bytes_to_copy > row_width: */ + dp = (png_bytep)dp16; + sp = (png_const_bytep)sp16; + do + *dp++ = *sp++; + while (--row_width > 0); + return; + } + } +#endif /* ALIGN_TYPE code */ + + /* The true default - use a memcpy: */ + for (;;) + { + memcpy(dp, sp, bytes_to_copy); + + if (row_width <= bytes_to_jump) + return; + + sp += bytes_to_jump; + dp += bytes_to_jump; + row_width -= bytes_to_jump; + if (bytes_to_copy > row_width) + bytes_to_copy = (unsigned int)/*SAFE*/row_width; + } + } + + /* NOT REACHED*/ + } /* pixel_depth >= 8 */ + + /* Here if pixel_depth < 8 to check 'end_ptr' below. */ + } + else +#endif /* READ_INTERLACING */ + + /* If here then the switch above wasn't used so just memcpy the whole row + * from the temporary row buffer (notice that this overwrites the end of the + * destination row if it is a partial byte.) + */ + memcpy(dp, sp, PNG_ROWBYTES(pixel_depth, row_width)); + + /* Restore the overwritten bits from the last byte if necessary. */ + if (end_ptr != NULL) + *end_ptr = (png_byte)((end_byte & end_mask) | (*end_ptr & ~end_mask)); +} + +#ifdef PNG_READ_INTERLACING_SUPPORTED +void /* PRIVATE */ +png_do_read_interlace(png_row_infop row_info, png_bytep row, int pass, + png_uint_32 transformations /* Because these may affect the byte layout */) +{ + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + /* Offset to next interlace block */ + static const unsigned int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + png_debug(1, "in png_do_read_interlace"); + if (row != NULL && row_info != NULL) + { + png_uint_32 final_width; + + final_width = row_info->width * png_pass_inc[pass]; + + switch (row_info->pixel_depth) + { + case 1: + { + png_bytep sp = row + (size_t)((row_info->width - 1) >> 3); + png_bytep dp = row + (size_t)((final_width - 1) >> 3); + unsigned int sshift, dshift; + unsigned int s_start, s_end; + int s_inc; + int jstop = (int)png_pass_inc[pass]; + png_byte v; + png_uint_32 i; + int j; + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if ((transformations & PNG_PACKSWAP) != 0) + { + sshift = ((row_info->width + 7) & 0x07); + dshift = ((final_width + 7) & 0x07); + s_start = 7; + s_end = 0; + s_inc = -1; + } + + else +#endif + { + sshift = 7 - ((row_info->width + 7) & 0x07); + dshift = 7 - ((final_width + 7) & 0x07); + s_start = 0; + s_end = 7; + s_inc = 1; + } + + for (i = 0; i < row_info->width; i++) + { + v = (png_byte)((*sp >> sshift) & 0x01); + for (j = 0; j < jstop; j++) + { + unsigned int tmp = *dp & (0x7f7f >> (7 - dshift)); + tmp |= (unsigned int)(v << dshift); + *dp = (png_byte)(tmp & 0xff); + + if (dshift == s_end) + { + dshift = s_start; + dp--; + } + + else + dshift = (unsigned int)((int)dshift + s_inc); + } + + if (sshift == s_end) + { + sshift = s_start; + sp--; + } + + else + sshift = (unsigned int)((int)sshift + s_inc); + } + break; + } + + case 2: + { + png_bytep sp = row + (png_uint_32)((row_info->width - 1) >> 2); + png_bytep dp = row + (png_uint_32)((final_width - 1) >> 2); + unsigned int sshift, dshift; + unsigned int s_start, s_end; + int s_inc; + int jstop = (int)png_pass_inc[pass]; + png_uint_32 i; + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if ((transformations & PNG_PACKSWAP) != 0) + { + sshift = (((row_info->width + 3) & 0x03) << 1); + dshift = (((final_width + 3) & 0x03) << 1); + s_start = 6; + s_end = 0; + s_inc = -2; + } + + else +#endif + { + sshift = ((3 - ((row_info->width + 3) & 0x03)) << 1); + dshift = ((3 - ((final_width + 3) & 0x03)) << 1); + s_start = 0; + s_end = 6; + s_inc = 2; + } + + for (i = 0; i < row_info->width; i++) + { + png_byte v; + int j; + + v = (png_byte)((*sp >> sshift) & 0x03); + for (j = 0; j < jstop; j++) + { + unsigned int tmp = *dp & (0x3f3f >> (6 - dshift)); + tmp |= (unsigned int)(v << dshift); + *dp = (png_byte)(tmp & 0xff); + + if (dshift == s_end) + { + dshift = s_start; + dp--; + } + + else + dshift = (unsigned int)((int)dshift + s_inc); + } + + if (sshift == s_end) + { + sshift = s_start; + sp--; + } + + else + sshift = (unsigned int)((int)sshift + s_inc); + } + break; + } + + case 4: + { + png_bytep sp = row + (size_t)((row_info->width - 1) >> 1); + png_bytep dp = row + (size_t)((final_width - 1) >> 1); + unsigned int sshift, dshift; + unsigned int s_start, s_end; + int s_inc; + png_uint_32 i; + int jstop = (int)png_pass_inc[pass]; + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if ((transformations & PNG_PACKSWAP) != 0) + { + sshift = (((row_info->width + 1) & 0x01) << 2); + dshift = (((final_width + 1) & 0x01) << 2); + s_start = 4; + s_end = 0; + s_inc = -4; + } + + else +#endif + { + sshift = ((1 - ((row_info->width + 1) & 0x01)) << 2); + dshift = ((1 - ((final_width + 1) & 0x01)) << 2); + s_start = 0; + s_end = 4; + s_inc = 4; + } + + for (i = 0; i < row_info->width; i++) + { + png_byte v = (png_byte)((*sp >> sshift) & 0x0f); + int j; + + for (j = 0; j < jstop; j++) + { + unsigned int tmp = *dp & (0xf0f >> (4 - dshift)); + tmp |= (unsigned int)(v << dshift); + *dp = (png_byte)(tmp & 0xff); + + if (dshift == s_end) + { + dshift = s_start; + dp--; + } + + else + dshift = (unsigned int)((int)dshift + s_inc); + } + + if (sshift == s_end) + { + sshift = s_start; + sp--; + } + + else + sshift = (unsigned int)((int)sshift + s_inc); + } + break; + } + + default: + { + size_t pixel_bytes = (row_info->pixel_depth >> 3); + + png_bytep sp = row + (size_t)(row_info->width - 1) + * pixel_bytes; + + png_bytep dp = row + (size_t)(final_width - 1) * pixel_bytes; + + int jstop = (int)png_pass_inc[pass]; + png_uint_32 i; + + for (i = 0; i < row_info->width; i++) + { + png_byte v[8]; /* SAFE; pixel_depth does not exceed 64 */ + int j; + + memcpy(v, sp, pixel_bytes); + + for (j = 0; j < jstop; j++) + { + memcpy(dp, v, pixel_bytes); + dp -= pixel_bytes; + } + + sp -= pixel_bytes; + } + break; + } + } + + row_info->width = final_width; + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, final_width); + } +#ifndef PNG_READ_PACKSWAP_SUPPORTED + PNG_UNUSED(transformations) /* Silence compiler warning */ +#endif +} +#endif /* READ_INTERLACING */ + +static void +png_read_filter_row_sub(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + size_t i; + size_t istop = row_info->rowbytes; + unsigned int bpp = (row_info->pixel_depth + 7) >> 3; + png_bytep rp = row + bpp; + + PNG_UNUSED(prev_row) + + for (i = bpp; i < istop; i++) + { + *rp = (png_byte)(((int)(*rp) + (int)(*(rp-bpp))) & 0xff); + rp++; + } +} + +static void +png_read_filter_row_up(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + size_t i; + size_t istop = row_info->rowbytes; + png_bytep rp = row; + png_const_bytep pp = prev_row; + + for (i = 0; i < istop; i++) + { + *rp = (png_byte)(((int)(*rp) + (int)(*pp++)) & 0xff); + rp++; + } +} + +static void +png_read_filter_row_avg(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + size_t i; + png_bytep rp = row; + png_const_bytep pp = prev_row; + unsigned int bpp = (row_info->pixel_depth + 7) >> 3; + size_t istop = row_info->rowbytes - bpp; + + for (i = 0; i < bpp; i++) + { + *rp = (png_byte)(((int)(*rp) + + ((int)(*pp++) / 2 )) & 0xff); + + rp++; + } + + for (i = 0; i < istop; i++) + { + *rp = (png_byte)(((int)(*rp) + + (int)(*pp++ + *(rp-bpp)) / 2 ) & 0xff); + + rp++; + } +} + +static void +png_read_filter_row_paeth_1byte_pixel(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + png_bytep rp_end = row + row_info->rowbytes; + int a, c; + + /* First pixel/byte */ + c = *prev_row++; + a = *row + c; + *row++ = (png_byte)a; + + /* Remainder */ + while (row < rp_end) + { + int b, pa, pb, pc, p; + + a &= 0xff; /* From previous iteration or start */ + b = *prev_row++; + + p = b - c; + pc = a - c; + +#ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +#else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +#endif + + /* Find the best predictor, the least of pa, pb, pc favoring the earlier + * ones in the case of a tie. + */ + if (pb < pa) + { + pa = pb; a = b; + } + if (pc < pa) a = c; + + /* Calculate the current pixel in a, and move the previous row pixel to c + * for the next time round the loop + */ + c = b; + a += *row; + *row++ = (png_byte)a; + } +} + +static void +png_read_filter_row_paeth_multibyte_pixel(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + unsigned int bpp = (row_info->pixel_depth + 7) >> 3; + png_bytep rp_end = row + bpp; + + /* Process the first pixel in the row completely (this is the same as 'up' + * because there is only one candidate predictor for the first row). + */ + while (row < rp_end) + { + int a = *row + *prev_row++; + *row++ = (png_byte)a; + } + + /* Remainder */ + rp_end = rp_end + (row_info->rowbytes - bpp); + + while (row < rp_end) + { + int a, b, c, pa, pb, pc, p; + + c = *(prev_row - bpp); + a = *(row - bpp); + b = *prev_row++; + + p = b - c; + pc = a - c; + +#ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +#else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +#endif + + if (pb < pa) + { + pa = pb; a = b; + } + if (pc < pa) a = c; + + a += *row; + *row++ = (png_byte)a; + } +} + +static void +png_init_filter_functions(png_structrp pp) + /* This function is called once for every PNG image (except for PNG images + * that only use PNG_FILTER_VALUE_NONE for all rows) to set the + * implementations required to reverse the filtering of PNG rows. Reversing + * the filter is the first transformation performed on the row data. It is + * performed in place, therefore an implementation can be selected based on + * the image pixel format. If the implementation depends on image width then + * take care to ensure that it works correctly if the image is interlaced - + * interlacing causes the actual row width to vary. + */ +{ + unsigned int bpp = (pp->pixel_depth + 7) >> 3; + + pp->read_filter[PNG_FILTER_VALUE_SUB-1] = png_read_filter_row_sub; + pp->read_filter[PNG_FILTER_VALUE_UP-1] = png_read_filter_row_up; + pp->read_filter[PNG_FILTER_VALUE_AVG-1] = png_read_filter_row_avg; + if (bpp == 1) + pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = + png_read_filter_row_paeth_1byte_pixel; + else + pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = + png_read_filter_row_paeth_multibyte_pixel; + +#ifdef PNG_FILTER_OPTIMIZATIONS + /* To use this define PNG_FILTER_OPTIMIZATIONS as the name of a function to + * call to install hardware optimizations for the above functions; simply + * replace whatever elements of the pp->read_filter[] array with a hardware + * specific (or, for that matter, generic) optimization. + * + * To see an example of this examine what configure.ac does when + * --enable-arm-neon is specified on the command line. + */ + PNG_FILTER_OPTIMIZATIONS(pp, bpp); +#endif +} + +void /* PRIVATE */ +png_read_filter_row(png_structrp pp, png_row_infop row_info, png_bytep row, + png_const_bytep prev_row, int filter) +{ + /* OPTIMIZATION: DO NOT MODIFY THIS FUNCTION, instead #define + * PNG_FILTER_OPTIMIZATIONS to a function that overrides the generic + * implementations. See png_init_filter_functions above. + */ + if (filter > PNG_FILTER_VALUE_NONE && filter < PNG_FILTER_VALUE_LAST) + { + if (pp->read_filter[0] == NULL) + png_init_filter_functions(pp); + + pp->read_filter[filter-1](row_info, row, prev_row); + } +} + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +void /* PRIVATE */ +png_read_IDAT_data(png_structrp png_ptr, png_bytep output, + png_alloc_size_t avail_out) +{ + /* Loop reading IDATs and decompressing the result into output[avail_out] */ + png_ptr->zstream.next_out = output; + png_ptr->zstream.avail_out = 0; /* safety: set below */ + + if (output == NULL) + avail_out = 0; + + do + { + int ret; + png_byte tmpbuf[PNG_INFLATE_BUF_SIZE]; + + if (png_ptr->zstream.avail_in == 0) + { + uInt avail_in; + png_bytep buffer; + + while (png_ptr->idat_size == 0) + { + png_crc_finish(png_ptr, 0); + + png_ptr->idat_size = png_read_chunk_header(png_ptr); + /* This is an error even in the 'check' case because the code just + * consumed a non-IDAT header. + */ + if (png_ptr->chunk_name != png_IDAT) + png_error(png_ptr, "Not enough image data"); + } + + avail_in = png_ptr->IDAT_read_size; + + if (avail_in > png_ptr->idat_size) + avail_in = (uInt)png_ptr->idat_size; + + /* A PNG with a gradually increasing IDAT size will defeat this attempt + * to minimize memory usage by causing lots of re-allocs, but + * realistically doing IDAT_read_size re-allocs is not likely to be a + * big problem. + */ + buffer = png_read_buffer(png_ptr, avail_in, 0/*error*/); + + png_crc_read(png_ptr, buffer, avail_in); + png_ptr->idat_size -= avail_in; + + png_ptr->zstream.next_in = buffer; + png_ptr->zstream.avail_in = avail_in; + } + + /* And set up the output side. */ + if (output != NULL) /* standard read */ + { + uInt out = ZLIB_IO_MAX; + + if (out > avail_out) + out = (uInt)avail_out; + + avail_out -= out; + png_ptr->zstream.avail_out = out; + } + + else /* after last row, checking for end */ + { + png_ptr->zstream.next_out = tmpbuf; + png_ptr->zstream.avail_out = (sizeof tmpbuf); + } + + /* Use NO_FLUSH; this gives zlib the maximum opportunity to optimize the + * process. If the LZ stream is truncated the sequential reader will + * terminally damage the stream, above, by reading the chunk header of the + * following chunk (it then exits with png_error). + * + * TODO: deal more elegantly with truncated IDAT lists. + */ + ret = PNG_INFLATE(png_ptr, Z_NO_FLUSH); + + /* Take the unconsumed output back. */ + if (output != NULL) + avail_out += png_ptr->zstream.avail_out; + + else /* avail_out counts the extra bytes */ + avail_out += (sizeof tmpbuf) - png_ptr->zstream.avail_out; + + png_ptr->zstream.avail_out = 0; + + if (ret == Z_STREAM_END) + { + /* Do this for safety; we won't read any more into this row. */ + png_ptr->zstream.next_out = NULL; + + png_ptr->mode |= PNG_AFTER_IDAT; + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + + if (png_ptr->zstream.avail_in > 0 || png_ptr->idat_size > 0) + png_chunk_benign_error(png_ptr, "Extra compressed data"); + break; + } + + if (ret != Z_OK) + { + png_zstream_error(png_ptr, ret); + + if (output != NULL) + png_chunk_error(png_ptr, png_ptr->zstream.msg); + + else /* checking */ + { + png_chunk_benign_error(png_ptr, png_ptr->zstream.msg); + return; + } + } + } while (avail_out > 0); + + if (avail_out > 0) + { + /* The stream ended before the image; this is the same as too few IDATs so + * should be handled the same way. + */ + if (output != NULL) + png_error(png_ptr, "Not enough image data"); + + else /* the deflate stream contained extra data */ + png_chunk_benign_error(png_ptr, "Too much image data"); + } +} + +void /* PRIVATE */ +png_read_finish_IDAT(png_structrp png_ptr) +{ + /* We don't need any more data and the stream should have ended, however the + * LZ end code may actually not have been processed. In this case we must + * read it otherwise stray unread IDAT data or, more likely, an IDAT chunk + * may still remain to be consumed. + */ + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) == 0) + { + /* The NULL causes png_read_IDAT_data to swallow any remaining bytes in + * the compressed stream, but the stream may be damaged too, so even after + * this call we may need to terminate the zstream ownership. + */ + png_read_IDAT_data(png_ptr, NULL, 0); + png_ptr->zstream.next_out = NULL; /* safety */ + + /* Now clear everything out for safety; the following may not have been + * done. + */ + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) == 0) + { + png_ptr->mode |= PNG_AFTER_IDAT; + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + } + } + + /* If the zstream has not been released do it now *and* terminate the reading + * of the final IDAT chunk. + */ + if (png_ptr->zowner == png_IDAT) + { + /* Always do this; the pointers otherwise point into the read buffer. */ + png_ptr->zstream.next_in = NULL; + png_ptr->zstream.avail_in = 0; + + /* Now we no longer own the zstream. */ + png_ptr->zowner = 0; + + /* The slightly weird semantics of the sequential IDAT reading is that we + * are always in or at the end of an IDAT chunk, so we always need to do a + * crc_finish here. If idat_size is non-zero we also need to read the + * spurious bytes at the end of the chunk now. + */ + (void)png_crc_finish(png_ptr, png_ptr->idat_size); + } +} + +void /* PRIVATE */ +png_read_finish_row(png_structrp png_ptr) +{ + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static const png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static const png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static const png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static const png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; + + png_debug(1, "in png_read_finish_row"); + png_ptr->row_number++; + if (png_ptr->row_number < png_ptr->num_rows) + return; + + if (png_ptr->interlaced != 0) + { + png_ptr->row_number = 0; + + /* TO DO: don't do this if prev_row isn't needed (requires + * read-ahead of the next row's filter byte. + */ + memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); + + do + { + png_ptr->pass++; + + if (png_ptr->pass >= 7) + break; + + png_ptr->iwidth = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + + if ((png_ptr->transformations & PNG_INTERLACE) == 0) + { + png_ptr->num_rows = (png_ptr->height + + png_pass_yinc[png_ptr->pass] - 1 - + png_pass_ystart[png_ptr->pass]) / + png_pass_yinc[png_ptr->pass]; + } + + else /* if (png_ptr->transformations & PNG_INTERLACE) */ + break; /* libpng deinterlacing sees every row */ + + } while (png_ptr->num_rows == 0 || png_ptr->iwidth == 0); + + if (png_ptr->pass < 7) + return; + } + + /* Here after at the end of the last row of the last pass. */ + png_read_finish_IDAT(png_ptr); +} +#endif /* SEQUENTIAL_READ */ + +void /* PRIVATE */ +png_read_start_row(png_structrp png_ptr) +{ + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static const png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static const png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static const png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static const png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; + + unsigned int max_pixel_depth; + size_t row_bytes; + + png_debug(1, "in png_read_start_row"); + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED + png_init_read_transformations(png_ptr); +#endif + if (png_ptr->interlaced != 0) + { + if ((png_ptr->transformations & PNG_INTERLACE) == 0) + png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 - + png_pass_ystart[0]) / png_pass_yinc[0]; + + else + png_ptr->num_rows = png_ptr->height; + + png_ptr->iwidth = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + } + + else + { + png_ptr->num_rows = png_ptr->height; + png_ptr->iwidth = png_ptr->width; + } + + max_pixel_depth = (unsigned int)png_ptr->pixel_depth; + + /* WARNING: * png_read_transform_info (pngrtran.c) performs a simpler set of + * calculations to calculate the final pixel depth, then + * png_do_read_transforms actually does the transforms. This means that the + * code which effectively calculates this value is actually repeated in three + * separate places. They must all match. Innocent changes to the order of + * transformations can and will break libpng in a way that causes memory + * overwrites. + * + * TODO: fix this. + */ +#ifdef PNG_READ_PACK_SUPPORTED + if ((png_ptr->transformations & PNG_PACK) != 0 && png_ptr->bit_depth < 8) + max_pixel_depth = 8; +#endif + +#ifdef PNG_READ_EXPAND_SUPPORTED + if ((png_ptr->transformations & PNG_EXPAND) != 0) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (png_ptr->num_trans != 0) + max_pixel_depth = 32; + + else + max_pixel_depth = 24; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY) + { + if (max_pixel_depth < 8) + max_pixel_depth = 8; + + if (png_ptr->num_trans != 0) + max_pixel_depth *= 2; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB) + { + if (png_ptr->num_trans != 0) + { + max_pixel_depth *= 4; + max_pixel_depth /= 3; + } + } + } +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED + if ((png_ptr->transformations & PNG_EXPAND_16) != 0) + { +# ifdef PNG_READ_EXPAND_SUPPORTED + /* In fact it is an error if it isn't supported, but checking is + * the safe way. + */ + if ((png_ptr->transformations & PNG_EXPAND) != 0) + { + if (png_ptr->bit_depth < 16) + max_pixel_depth *= 2; + } + else +# endif + png_ptr->transformations &= ~PNG_EXPAND_16; + } +#endif + +#ifdef PNG_READ_FILLER_SUPPORTED + if ((png_ptr->transformations & (PNG_FILLER)) != 0) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY) + { + if (max_pixel_depth <= 8) + max_pixel_depth = 16; + + else + max_pixel_depth = 32; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB || + png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (max_pixel_depth <= 32) + max_pixel_depth = 32; + + else + max_pixel_depth = 64; + } + } +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) != 0) + { + if ( +#ifdef PNG_READ_EXPAND_SUPPORTED + (png_ptr->num_trans != 0 && + (png_ptr->transformations & PNG_EXPAND) != 0) || +#endif +#ifdef PNG_READ_FILLER_SUPPORTED + (png_ptr->transformations & (PNG_FILLER)) != 0 || +#endif + png_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (max_pixel_depth <= 16) + max_pixel_depth = 32; + + else + max_pixel_depth = 64; + } + + else + { + if (max_pixel_depth <= 8) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + max_pixel_depth = 32; + + else + max_pixel_depth = 24; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + max_pixel_depth = 64; + + else + max_pixel_depth = 48; + } + } +#endif + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) && \ +defined(PNG_USER_TRANSFORM_PTR_SUPPORTED) + if ((png_ptr->transformations & PNG_USER_TRANSFORM) != 0) + { + unsigned int user_pixel_depth = png_ptr->user_transform_depth * + png_ptr->user_transform_channels; + + if (user_pixel_depth > max_pixel_depth) + max_pixel_depth = user_pixel_depth; + } +#endif + + /* This value is stored in png_struct and double checked in the row read + * code. + */ + png_ptr->maximum_pixel_depth = (png_byte)max_pixel_depth; + png_ptr->transformed_pixel_depth = 0; /* calculated on demand */ + + /* Align the width on the next larger 8 pixels. Mainly used + * for interlacing + */ + row_bytes = ((png_ptr->width + 7) & ~((png_uint_32)7)); + /* Calculate the maximum bytes needed, adding a byte and a pixel + * for safety's sake + */ + row_bytes = PNG_ROWBYTES(max_pixel_depth, row_bytes) + + 1 + ((max_pixel_depth + 7) >> 3U); + +#ifdef PNG_MAX_MALLOC_64K + if (row_bytes > (png_uint_32)65536L) + png_error(png_ptr, "This image requires a row greater than 64KB"); +#endif + + if (row_bytes + 48 > png_ptr->old_big_row_buf_size) + { + png_free(png_ptr, png_ptr->big_row_buf); + png_free(png_ptr, png_ptr->big_prev_row); + + if (png_ptr->interlaced != 0) + png_ptr->big_row_buf = (png_bytep)png_calloc(png_ptr, + row_bytes + 48); + + else + png_ptr->big_row_buf = (png_bytep)png_malloc(png_ptr, row_bytes + 48); + + png_ptr->big_prev_row = (png_bytep)png_malloc(png_ptr, row_bytes + 48); + +#ifdef PNG_ALIGNED_MEMORY_SUPPORTED + /* Use 16-byte aligned memory for row_buf with at least 16 bytes + * of padding before and after row_buf; treat prev_row similarly. + * NOTE: the alignment is to the start of the pixels, one beyond the start + * of the buffer, because of the filter byte. Prior to libpng 1.5.6 this + * was incorrect; the filter byte was aligned, which had the exact + * opposite effect of that intended. + */ + { + png_bytep temp = png_ptr->big_row_buf + 32; + int extra = (int)((temp - (png_bytep)0) & 0x0f); + png_ptr->row_buf = temp - extra - 1/*filter byte*/; + + temp = png_ptr->big_prev_row + 32; + extra = (int)((temp - (png_bytep)0) & 0x0f); + png_ptr->prev_row = temp - extra - 1/*filter byte*/; + } + +#else + /* Use 31 bytes of padding before and 17 bytes after row_buf. */ + png_ptr->row_buf = png_ptr->big_row_buf + 31; + png_ptr->prev_row = png_ptr->big_prev_row + 31; +#endif + png_ptr->old_big_row_buf_size = row_bytes + 48; + } + +#ifdef PNG_MAX_MALLOC_64K + if (png_ptr->rowbytes > 65535) + png_error(png_ptr, "This image requires a row greater than 64KB"); + +#endif + if (png_ptr->rowbytes > (PNG_SIZE_MAX - 1)) + png_error(png_ptr, "Row has too many bytes to allocate in memory"); + + memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); + + png_debug1(3, "width = %u,", png_ptr->width); + png_debug1(3, "height = %u,", png_ptr->height); + png_debug1(3, "iwidth = %u,", png_ptr->iwidth); + png_debug1(3, "num_rows = %u,", png_ptr->num_rows); + png_debug1(3, "rowbytes = %lu,", (unsigned long)png_ptr->rowbytes); + png_debug1(3, "irowbytes = %lu", + (unsigned long)PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->iwidth) + 1); + + /* The sequential reader needs a buffer for IDAT, but the progressive reader + * does not, so free the read buffer now regardless; the sequential reader + * reallocates it on demand. + */ + if (png_ptr->read_buffer != NULL) + { + png_bytep buffer = png_ptr->read_buffer; + + png_ptr->read_buffer_size = 0; + png_ptr->read_buffer = NULL; + png_free(png_ptr, buffer); + } + + /* Finally claim the zstream for the inflate of the IDAT data, use the bits + * value from the stream (note that this will result in a fatal error if the + * IDAT stream has a bogus deflate header window_bits value, but this should + * not be happening any longer!) + */ + if (png_inflate_claim(png_ptr, png_IDAT) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + png_ptr->flags |= PNG_FLAG_ROW_INIT; +} +#endif /* READ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngset.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngset.c new file mode 100644 index 0000000..ec75dbe --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngset.c @@ -0,0 +1,1802 @@ + +/* pngset.c - storage of image information into info struct + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * The functions here are used during reads to store data from the file + * into the info struct, and during writes to store application data + * into the info struct for writing into the file. This abstracts the + * info struct and allows us to change the structure in the future. + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +#ifdef PNG_bKGD_SUPPORTED +void PNGAPI +png_set_bKGD(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_color_16p background) +{ + png_debug1(1, "in %s storage function", "bKGD"); + + if (png_ptr == NULL || info_ptr == NULL || background == NULL) + return; + + info_ptr->background = *background; + info_ptr->valid |= PNG_INFO_bKGD; +} +#endif + +#ifdef PNG_cHRM_SUPPORTED +void PNGFAPI +png_set_cHRM_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + png_fixed_point white_x, png_fixed_point white_y, png_fixed_point red_x, + png_fixed_point red_y, png_fixed_point green_x, png_fixed_point green_y, + png_fixed_point blue_x, png_fixed_point blue_y) +{ + png_xy xy; + + png_debug1(1, "in %s storage function", "cHRM fixed"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + xy.redx = red_x; + xy.redy = red_y; + xy.greenx = green_x; + xy.greeny = green_y; + xy.bluex = blue_x; + xy.bluey = blue_y; + xy.whitex = white_x; + xy.whitey = white_y; + + if (png_colorspace_set_chromaticities(png_ptr, &info_ptr->colorspace, &xy, + 2/* override with app values*/) != 0) + info_ptr->colorspace.flags |= PNG_COLORSPACE_FROM_cHRM; + + png_colorspace_sync_info(png_ptr, info_ptr); +} + +void PNGFAPI +png_set_cHRM_XYZ_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + png_fixed_point int_red_X, png_fixed_point int_red_Y, + png_fixed_point int_red_Z, png_fixed_point int_green_X, + png_fixed_point int_green_Y, png_fixed_point int_green_Z, + png_fixed_point int_blue_X, png_fixed_point int_blue_Y, + png_fixed_point int_blue_Z) +{ + png_XYZ XYZ; + + png_debug1(1, "in %s storage function", "cHRM XYZ fixed"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + XYZ.red_X = int_red_X; + XYZ.red_Y = int_red_Y; + XYZ.red_Z = int_red_Z; + XYZ.green_X = int_green_X; + XYZ.green_Y = int_green_Y; + XYZ.green_Z = int_green_Z; + XYZ.blue_X = int_blue_X; + XYZ.blue_Y = int_blue_Y; + XYZ.blue_Z = int_blue_Z; + + if (png_colorspace_set_endpoints(png_ptr, &info_ptr->colorspace, + &XYZ, 2) != 0) + info_ptr->colorspace.flags |= PNG_COLORSPACE_FROM_cHRM; + + png_colorspace_sync_info(png_ptr, info_ptr); +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_cHRM(png_const_structrp png_ptr, png_inforp info_ptr, + double white_x, double white_y, double red_x, double red_y, + double green_x, double green_y, double blue_x, double blue_y) +{ + png_set_cHRM_fixed(png_ptr, info_ptr, + png_fixed(png_ptr, white_x, "cHRM White X"), + png_fixed(png_ptr, white_y, "cHRM White Y"), + png_fixed(png_ptr, red_x, "cHRM Red X"), + png_fixed(png_ptr, red_y, "cHRM Red Y"), + png_fixed(png_ptr, green_x, "cHRM Green X"), + png_fixed(png_ptr, green_y, "cHRM Green Y"), + png_fixed(png_ptr, blue_x, "cHRM Blue X"), + png_fixed(png_ptr, blue_y, "cHRM Blue Y")); +} + +void PNGAPI +png_set_cHRM_XYZ(png_const_structrp png_ptr, png_inforp info_ptr, double red_X, + double red_Y, double red_Z, double green_X, double green_Y, double green_Z, + double blue_X, double blue_Y, double blue_Z) +{ + png_set_cHRM_XYZ_fixed(png_ptr, info_ptr, + png_fixed(png_ptr, red_X, "cHRM Red X"), + png_fixed(png_ptr, red_Y, "cHRM Red Y"), + png_fixed(png_ptr, red_Z, "cHRM Red Z"), + png_fixed(png_ptr, green_X, "cHRM Green X"), + png_fixed(png_ptr, green_Y, "cHRM Green Y"), + png_fixed(png_ptr, green_Z, "cHRM Green Z"), + png_fixed(png_ptr, blue_X, "cHRM Blue X"), + png_fixed(png_ptr, blue_Y, "cHRM Blue Y"), + png_fixed(png_ptr, blue_Z, "cHRM Blue Z")); +} +# endif /* FLOATING_POINT */ + +#endif /* cHRM */ + +#ifdef PNG_eXIf_SUPPORTED +void PNGAPI +png_set_eXIf(png_const_structrp png_ptr, png_inforp info_ptr, + png_bytep eXIf_buf) +{ + png_warning(png_ptr, "png_set_eXIf does not work; use png_set_eXIf_1"); + PNG_UNUSED(info_ptr) + PNG_UNUSED(eXIf_buf) +} + +void PNGAPI +png_set_eXIf_1(png_const_structrp png_ptr, png_inforp info_ptr, + png_uint_32 num_exif, png_bytep eXIf_buf) +{ + int i; + + png_debug1(1, "in %s storage function", "eXIf"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (info_ptr->exif) + { + png_free(png_ptr, info_ptr->exif); + info_ptr->exif = NULL; + } + + info_ptr->num_exif = num_exif; + + info_ptr->exif = png_voidcast(png_bytep, png_malloc_warn(png_ptr, + info_ptr->num_exif)); + + if (info_ptr->exif == NULL) + { + png_warning(png_ptr, "Insufficient memory for eXIf chunk data"); + return; + } + + info_ptr->free_me |= PNG_FREE_EXIF; + + for (i = 0; i < (int) info_ptr->num_exif; i++) + info_ptr->exif[i] = eXIf_buf[i]; + + info_ptr->valid |= PNG_INFO_eXIf; +} +#endif /* eXIf */ + +#ifdef PNG_gAMA_SUPPORTED +void PNGFAPI +png_set_gAMA_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + png_fixed_point file_gamma) +{ + png_debug1(1, "in %s storage function", "gAMA"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + png_colorspace_set_gamma(png_ptr, &info_ptr->colorspace, file_gamma); + png_colorspace_sync_info(png_ptr, info_ptr); +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_gAMA(png_const_structrp png_ptr, png_inforp info_ptr, double file_gamma) +{ + png_set_gAMA_fixed(png_ptr, info_ptr, png_fixed(png_ptr, file_gamma, + "png_set_gAMA")); +} +# endif +#endif + +#ifdef PNG_hIST_SUPPORTED +void PNGAPI +png_set_hIST(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_uint_16p hist) +{ + int i; + + png_debug1(1, "in %s storage function", "hIST"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (info_ptr->num_palette == 0 || info_ptr->num_palette + > PNG_MAX_PALETTE_LENGTH) + { + png_warning(png_ptr, + "Invalid palette size, hIST allocation skipped"); + + return; + } + + png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0); + + /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in + * version 1.2.1 + */ + info_ptr->hist = png_voidcast(png_uint_16p, png_malloc_warn(png_ptr, + PNG_MAX_PALETTE_LENGTH * (sizeof (png_uint_16)))); + + if (info_ptr->hist == NULL) + { + png_warning(png_ptr, "Insufficient memory for hIST chunk data"); + + return; + } + + info_ptr->free_me |= PNG_FREE_HIST; + + for (i = 0; i < info_ptr->num_palette; i++) + info_ptr->hist[i] = hist[i]; + + info_ptr->valid |= PNG_INFO_hIST; +} +#endif + +void PNGAPI +png_set_IHDR(png_const_structrp png_ptr, png_inforp info_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_type, int compression_type, + int filter_type) +{ + png_debug1(1, "in %s storage function", "IHDR"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + info_ptr->width = width; + info_ptr->height = height; + info_ptr->bit_depth = (png_byte)bit_depth; + info_ptr->color_type = (png_byte)color_type; + info_ptr->compression_type = (png_byte)compression_type; + info_ptr->filter_type = (png_byte)filter_type; + info_ptr->interlace_type = (png_byte)interlace_type; + + png_check_IHDR (png_ptr, info_ptr->width, info_ptr->height, + info_ptr->bit_depth, info_ptr->color_type, info_ptr->interlace_type, + info_ptr->compression_type, info_ptr->filter_type); + + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + info_ptr->channels = 1; + + else if ((info_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0) + info_ptr->channels = 3; + + else + info_ptr->channels = 1; + + if ((info_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0) + info_ptr->channels++; + + info_ptr->pixel_depth = (png_byte)(info_ptr->channels * info_ptr->bit_depth); + + info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, width); +} + +#ifdef PNG_oFFs_SUPPORTED +void PNGAPI +png_set_oFFs(png_const_structrp png_ptr, png_inforp info_ptr, + png_int_32 offset_x, png_int_32 offset_y, int unit_type) +{ + png_debug1(1, "in %s storage function", "oFFs"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + info_ptr->x_offset = offset_x; + info_ptr->y_offset = offset_y; + info_ptr->offset_unit_type = (png_byte)unit_type; + info_ptr->valid |= PNG_INFO_oFFs; +} +#endif + +#ifdef PNG_pCAL_SUPPORTED +void PNGAPI +png_set_pCAL(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_charp purpose, png_int_32 X0, png_int_32 X1, int type, + int nparams, png_const_charp units, png_charpp params) +{ + size_t length; + int i; + + png_debug1(1, "in %s storage function", "pCAL"); + + if (png_ptr == NULL || info_ptr == NULL || purpose == NULL || units == NULL + || (nparams > 0 && params == NULL)) + return; + + length = strlen(purpose) + 1; + png_debug1(3, "allocating purpose for info (%lu bytes)", + (unsigned long)length); + + /* TODO: validate format of calibration name and unit name */ + + /* Check that the type matches the specification. */ + if (type < 0 || type > 3) + { + png_chunk_report(png_ptr, "Invalid pCAL equation type", + PNG_CHUNK_WRITE_ERROR); + return; + } + + if (nparams < 0 || nparams > 255) + { + png_chunk_report(png_ptr, "Invalid pCAL parameter count", + PNG_CHUNK_WRITE_ERROR); + return; + } + + /* Validate params[nparams] */ + for (i=0; ipcal_purpose = png_voidcast(png_charp, + png_malloc_warn(png_ptr, length)); + + if (info_ptr->pcal_purpose == NULL) + { + png_chunk_report(png_ptr, "Insufficient memory for pCAL purpose", + PNG_CHUNK_WRITE_ERROR); + return; + } + + memcpy(info_ptr->pcal_purpose, purpose, length); + + png_debug(3, "storing X0, X1, type, and nparams in info"); + info_ptr->pcal_X0 = X0; + info_ptr->pcal_X1 = X1; + info_ptr->pcal_type = (png_byte)type; + info_ptr->pcal_nparams = (png_byte)nparams; + + length = strlen(units) + 1; + png_debug1(3, "allocating units for info (%lu bytes)", + (unsigned long)length); + + info_ptr->pcal_units = png_voidcast(png_charp, + png_malloc_warn(png_ptr, length)); + + if (info_ptr->pcal_units == NULL) + { + png_warning(png_ptr, "Insufficient memory for pCAL units"); + + return; + } + + memcpy(info_ptr->pcal_units, units, length); + + info_ptr->pcal_params = png_voidcast(png_charpp, png_malloc_warn(png_ptr, + (size_t)(((unsigned int)nparams + 1) * (sizeof (png_charp))))); + + if (info_ptr->pcal_params == NULL) + { + png_warning(png_ptr, "Insufficient memory for pCAL params"); + + return; + } + + memset(info_ptr->pcal_params, 0, ((unsigned int)nparams + 1) * + (sizeof (png_charp))); + + for (i = 0; i < nparams; i++) + { + length = strlen(params[i]) + 1; + png_debug2(3, "allocating parameter %d for info (%lu bytes)", i, + (unsigned long)length); + + info_ptr->pcal_params[i] = (png_charp)png_malloc_warn(png_ptr, length); + + if (info_ptr->pcal_params[i] == NULL) + { + png_warning(png_ptr, "Insufficient memory for pCAL parameter"); + + return; + } + + memcpy(info_ptr->pcal_params[i], params[i], length); + } + + info_ptr->valid |= PNG_INFO_pCAL; + info_ptr->free_me |= PNG_FREE_PCAL; +} +#endif + +#ifdef PNG_sCAL_SUPPORTED +void PNGAPI +png_set_sCAL_s(png_const_structrp png_ptr, png_inforp info_ptr, + int unit, png_const_charp swidth, png_const_charp sheight) +{ + size_t lengthw = 0, lengthh = 0; + + png_debug1(1, "in %s storage function", "sCAL"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* Double check the unit (should never get here with an invalid + * unit unless this is an API call.) + */ + if (unit != 1 && unit != 2) + png_error(png_ptr, "Invalid sCAL unit"); + + if (swidth == NULL || (lengthw = strlen(swidth)) == 0 || + swidth[0] == 45 /* '-' */ || !png_check_fp_string(swidth, lengthw)) + png_error(png_ptr, "Invalid sCAL width"); + + if (sheight == NULL || (lengthh = strlen(sheight)) == 0 || + sheight[0] == 45 /* '-' */ || !png_check_fp_string(sheight, lengthh)) + png_error(png_ptr, "Invalid sCAL height"); + + info_ptr->scal_unit = (png_byte)unit; + + ++lengthw; + + png_debug1(3, "allocating unit for info (%u bytes)", (unsigned int)lengthw); + + info_ptr->scal_s_width = png_voidcast(png_charp, + png_malloc_warn(png_ptr, lengthw)); + + if (info_ptr->scal_s_width == NULL) + { + png_warning(png_ptr, "Memory allocation failed while processing sCAL"); + + return; + } + + memcpy(info_ptr->scal_s_width, swidth, lengthw); + + ++lengthh; + + png_debug1(3, "allocating unit for info (%u bytes)", (unsigned int)lengthh); + + info_ptr->scal_s_height = png_voidcast(png_charp, + png_malloc_warn(png_ptr, lengthh)); + + if (info_ptr->scal_s_height == NULL) + { + png_free (png_ptr, info_ptr->scal_s_width); + info_ptr->scal_s_width = NULL; + + png_warning(png_ptr, "Memory allocation failed while processing sCAL"); + + return; + } + + memcpy(info_ptr->scal_s_height, sheight, lengthh); + + info_ptr->valid |= PNG_INFO_sCAL; + info_ptr->free_me |= PNG_FREE_SCAL; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_sCAL(png_const_structrp png_ptr, png_inforp info_ptr, int unit, + double width, double height) +{ + png_debug1(1, "in %s storage function", "sCAL"); + + /* Check the arguments. */ + if (width <= 0) + png_warning(png_ptr, "Invalid sCAL width ignored"); + + else if (height <= 0) + png_warning(png_ptr, "Invalid sCAL height ignored"); + + else + { + /* Convert 'width' and 'height' to ASCII. */ + char swidth[PNG_sCAL_MAX_DIGITS+1]; + char sheight[PNG_sCAL_MAX_DIGITS+1]; + + png_ascii_from_fp(png_ptr, swidth, (sizeof swidth), width, + PNG_sCAL_PRECISION); + png_ascii_from_fp(png_ptr, sheight, (sizeof sheight), height, + PNG_sCAL_PRECISION); + + png_set_sCAL_s(png_ptr, info_ptr, unit, swidth, sheight); + } +} +# endif + +# ifdef PNG_FIXED_POINT_SUPPORTED +void PNGAPI +png_set_sCAL_fixed(png_const_structrp png_ptr, png_inforp info_ptr, int unit, + png_fixed_point width, png_fixed_point height) +{ + png_debug1(1, "in %s storage function", "sCAL"); + + /* Check the arguments. */ + if (width <= 0) + png_warning(png_ptr, "Invalid sCAL width ignored"); + + else if (height <= 0) + png_warning(png_ptr, "Invalid sCAL height ignored"); + + else + { + /* Convert 'width' and 'height' to ASCII. */ + char swidth[PNG_sCAL_MAX_DIGITS+1]; + char sheight[PNG_sCAL_MAX_DIGITS+1]; + + png_ascii_from_fixed(png_ptr, swidth, (sizeof swidth), width); + png_ascii_from_fixed(png_ptr, sheight, (sizeof sheight), height); + + png_set_sCAL_s(png_ptr, info_ptr, unit, swidth, sheight); + } +} +# endif +#endif + +#ifdef PNG_pHYs_SUPPORTED +void PNGAPI +png_set_pHYs(png_const_structrp png_ptr, png_inforp info_ptr, + png_uint_32 res_x, png_uint_32 res_y, int unit_type) +{ + png_debug1(1, "in %s storage function", "pHYs"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + info_ptr->x_pixels_per_unit = res_x; + info_ptr->y_pixels_per_unit = res_y; + info_ptr->phys_unit_type = (png_byte)unit_type; + info_ptr->valid |= PNG_INFO_pHYs; +} +#endif + +void PNGAPI +png_set_PLTE(png_structrp png_ptr, png_inforp info_ptr, + png_const_colorp palette, int num_palette) +{ + + png_uint_32 max_palette_length; + + png_debug1(1, "in %s storage function", "PLTE"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + max_palette_length = (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) ? + (1 << info_ptr->bit_depth) : PNG_MAX_PALETTE_LENGTH; + + if (num_palette < 0 || num_palette > (int) max_palette_length) + { + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + png_error(png_ptr, "Invalid palette length"); + + else + { + png_warning(png_ptr, "Invalid palette length"); + + return; + } + } + + if ((num_palette > 0 && palette == NULL) || + (num_palette == 0 +# ifdef PNG_MNG_FEATURES_SUPPORTED + && (png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE) == 0 +# endif + )) + { + png_error(png_ptr, "Invalid palette"); + } + + /* It may not actually be necessary to set png_ptr->palette here; + * we do it for backward compatibility with the way the png_handle_tRNS + * function used to do the allocation. + * + * 1.6.0: the above statement appears to be incorrect; something has to set + * the palette inside png_struct on read. + */ + png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0); + + /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead + * of num_palette entries, in case of an invalid PNG file or incorrect + * call to png_set_PLTE() with too-large sample values. + */ + png_ptr->palette = png_voidcast(png_colorp, png_calloc(png_ptr, + PNG_MAX_PALETTE_LENGTH * (sizeof (png_color)))); + + if (num_palette > 0) + memcpy(png_ptr->palette, palette, (unsigned int)num_palette * + (sizeof (png_color))); + info_ptr->palette = png_ptr->palette; + info_ptr->num_palette = png_ptr->num_palette = (png_uint_16)num_palette; + + info_ptr->free_me |= PNG_FREE_PLTE; + + info_ptr->valid |= PNG_INFO_PLTE; +} + +#ifdef PNG_sBIT_SUPPORTED +void PNGAPI +png_set_sBIT(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_color_8p sig_bit) +{ + png_debug1(1, "in %s storage function", "sBIT"); + + if (png_ptr == NULL || info_ptr == NULL || sig_bit == NULL) + return; + + info_ptr->sig_bit = *sig_bit; + info_ptr->valid |= PNG_INFO_sBIT; +} +#endif + +#ifdef PNG_sRGB_SUPPORTED +void PNGAPI +png_set_sRGB(png_const_structrp png_ptr, png_inforp info_ptr, int srgb_intent) +{ + png_debug1(1, "in %s storage function", "sRGB"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + (void)png_colorspace_set_sRGB(png_ptr, &info_ptr->colorspace, srgb_intent); + png_colorspace_sync_info(png_ptr, info_ptr); +} + +void PNGAPI +png_set_sRGB_gAMA_and_cHRM(png_const_structrp png_ptr, png_inforp info_ptr, + int srgb_intent) +{ + png_debug1(1, "in %s storage function", "sRGB_gAMA_and_cHRM"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (png_colorspace_set_sRGB(png_ptr, &info_ptr->colorspace, + srgb_intent) != 0) + { + /* This causes the gAMA and cHRM to be written too */ + info_ptr->colorspace.flags |= + PNG_COLORSPACE_FROM_gAMA|PNG_COLORSPACE_FROM_cHRM; + } + + png_colorspace_sync_info(png_ptr, info_ptr); +} +#endif /* sRGB */ + + +#ifdef PNG_iCCP_SUPPORTED +void PNGAPI +png_set_iCCP(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_charp name, int compression_type, + png_const_bytep profile, png_uint_32 proflen) +{ + png_charp new_iccp_name; + png_bytep new_iccp_profile; + size_t length; + + png_debug1(1, "in %s storage function", "iCCP"); + + if (png_ptr == NULL || info_ptr == NULL || name == NULL || profile == NULL) + return; + + if (compression_type != PNG_COMPRESSION_TYPE_BASE) + png_app_error(png_ptr, "Invalid iCCP compression method"); + + /* Set the colorspace first because this validates the profile; do not + * override previously set app cHRM or gAMA here (because likely as not the + * application knows better than libpng what the correct values are.) Pass + * the info_ptr color_type field to png_colorspace_set_ICC because in the + * write case it has not yet been stored in png_ptr. + */ + { + int result = png_colorspace_set_ICC(png_ptr, &info_ptr->colorspace, name, + proflen, profile, info_ptr->color_type); + + png_colorspace_sync_info(png_ptr, info_ptr); + + /* Don't do any of the copying if the profile was bad, or inconsistent. */ + if (result == 0) + return; + + /* But do write the gAMA and cHRM chunks from the profile. */ + info_ptr->colorspace.flags |= + PNG_COLORSPACE_FROM_gAMA|PNG_COLORSPACE_FROM_cHRM; + } + + length = strlen(name)+1; + new_iccp_name = png_voidcast(png_charp, png_malloc_warn(png_ptr, length)); + + if (new_iccp_name == NULL) + { + png_benign_error(png_ptr, "Insufficient memory to process iCCP chunk"); + + return; + } + + memcpy(new_iccp_name, name, length); + new_iccp_profile = png_voidcast(png_bytep, + png_malloc_warn(png_ptr, proflen)); + + if (new_iccp_profile == NULL) + { + png_free(png_ptr, new_iccp_name); + png_benign_error(png_ptr, + "Insufficient memory to process iCCP profile"); + + return; + } + + memcpy(new_iccp_profile, profile, proflen); + + png_free_data(png_ptr, info_ptr, PNG_FREE_ICCP, 0); + + info_ptr->iccp_proflen = proflen; + info_ptr->iccp_name = new_iccp_name; + info_ptr->iccp_profile = new_iccp_profile; + info_ptr->free_me |= PNG_FREE_ICCP; + info_ptr->valid |= PNG_INFO_iCCP; +} +#endif + +#ifdef PNG_TEXT_SUPPORTED +void PNGAPI +png_set_text(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_textp text_ptr, int num_text) +{ + int ret; + ret = png_set_text_2(png_ptr, info_ptr, text_ptr, num_text); + + if (ret != 0) + png_error(png_ptr, "Insufficient memory to store text"); +} + +int /* PRIVATE */ +png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_textp text_ptr, int num_text) +{ + int i; + + png_debug1(1, "in %lx storage function", png_ptr == NULL ? 0xabadca11U : + (unsigned long)png_ptr->chunk_name); + + if (png_ptr == NULL || info_ptr == NULL || num_text <= 0 || text_ptr == NULL) + return(0); + + /* Make sure we have enough space in the "text" array in info_struct + * to hold all of the incoming text_ptr objects. This compare can't overflow + * because max_text >= num_text (anyway, subtract of two positive integers + * can't overflow in any case.) + */ + if (num_text > info_ptr->max_text - info_ptr->num_text) + { + int old_num_text = info_ptr->num_text; + int max_text; + png_textp new_text = NULL; + + /* Calculate an appropriate max_text, checking for overflow. */ + max_text = old_num_text; + if (num_text <= INT_MAX - max_text) + { + max_text += num_text; + + /* Round up to a multiple of 8 */ + if (max_text < INT_MAX-8) + max_text = (max_text + 8) & ~0x7; + + else + max_text = INT_MAX; + + /* Now allocate a new array and copy the old members in; this does all + * the overflow checks. + */ + new_text = png_voidcast(png_textp,png_realloc_array(png_ptr, + info_ptr->text, old_num_text, max_text-old_num_text, + sizeof *new_text)); + } + + if (new_text == NULL) + { + png_chunk_report(png_ptr, "too many text chunks", + PNG_CHUNK_WRITE_ERROR); + + return 1; + } + + png_free(png_ptr, info_ptr->text); + + info_ptr->text = new_text; + info_ptr->free_me |= PNG_FREE_TEXT; + info_ptr->max_text = max_text; + /* num_text is adjusted below as the entries are copied in */ + + png_debug1(3, "allocated %d entries for info_ptr->text", max_text); + } + + for (i = 0; i < num_text; i++) + { + size_t text_length, key_len; + size_t lang_len, lang_key_len; + png_textp textp = &(info_ptr->text[info_ptr->num_text]); + + if (text_ptr[i].key == NULL) + continue; + + if (text_ptr[i].compression < PNG_TEXT_COMPRESSION_NONE || + text_ptr[i].compression >= PNG_TEXT_COMPRESSION_LAST) + { + png_chunk_report(png_ptr, "text compression mode is out of range", + PNG_CHUNK_WRITE_ERROR); + continue; + } + + key_len = strlen(text_ptr[i].key); + + if (text_ptr[i].compression <= 0) + { + lang_len = 0; + lang_key_len = 0; + } + + else +# ifdef PNG_iTXt_SUPPORTED + { + /* Set iTXt data */ + + if (text_ptr[i].lang != NULL) + lang_len = strlen(text_ptr[i].lang); + + else + lang_len = 0; + + if (text_ptr[i].lang_key != NULL) + lang_key_len = strlen(text_ptr[i].lang_key); + + else + lang_key_len = 0; + } +# else /* iTXt */ + { + png_chunk_report(png_ptr, "iTXt chunk not supported", + PNG_CHUNK_WRITE_ERROR); + continue; + } +# endif + + if (text_ptr[i].text == NULL || text_ptr[i].text[0] == '\0') + { + text_length = 0; +# ifdef PNG_iTXt_SUPPORTED + if (text_ptr[i].compression > 0) + textp->compression = PNG_ITXT_COMPRESSION_NONE; + + else +# endif + textp->compression = PNG_TEXT_COMPRESSION_NONE; + } + + else + { + text_length = strlen(text_ptr[i].text); + textp->compression = text_ptr[i].compression; + } + + textp->key = png_voidcast(png_charp,png_malloc_base(png_ptr, + key_len + text_length + lang_len + lang_key_len + 4)); + + if (textp->key == NULL) + { + png_chunk_report(png_ptr, "text chunk: out of memory", + PNG_CHUNK_WRITE_ERROR); + + return 1; + } + + png_debug2(2, "Allocated %lu bytes at %p in png_set_text", + (unsigned long)(png_uint_32) + (key_len + lang_len + lang_key_len + text_length + 4), + textp->key); + + memcpy(textp->key, text_ptr[i].key, key_len); + *(textp->key + key_len) = '\0'; + + if (text_ptr[i].compression > 0) + { + textp->lang = textp->key + key_len + 1; + memcpy(textp->lang, text_ptr[i].lang, lang_len); + *(textp->lang + lang_len) = '\0'; + textp->lang_key = textp->lang + lang_len + 1; + memcpy(textp->lang_key, text_ptr[i].lang_key, lang_key_len); + *(textp->lang_key + lang_key_len) = '\0'; + textp->text = textp->lang_key + lang_key_len + 1; + } + + else + { + textp->lang=NULL; + textp->lang_key=NULL; + textp->text = textp->key + key_len + 1; + } + + if (text_length != 0) + memcpy(textp->text, text_ptr[i].text, text_length); + + *(textp->text + text_length) = '\0'; + +# ifdef PNG_iTXt_SUPPORTED + if (textp->compression > 0) + { + textp->text_length = 0; + textp->itxt_length = text_length; + } + + else +# endif + { + textp->text_length = text_length; + textp->itxt_length = 0; + } + + info_ptr->num_text++; + png_debug1(3, "transferred text chunk %d", info_ptr->num_text); + } + + return(0); +} +#endif + +#ifdef PNG_tIME_SUPPORTED +void PNGAPI +png_set_tIME(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_timep mod_time) +{ + png_debug1(1, "in %s storage function", "tIME"); + + if (png_ptr == NULL || info_ptr == NULL || mod_time == NULL || + (png_ptr->mode & PNG_WROTE_tIME) != 0) + return; + + if (mod_time->month == 0 || mod_time->month > 12 || + mod_time->day == 0 || mod_time->day > 31 || + mod_time->hour > 23 || mod_time->minute > 59 || + mod_time->second > 60) + { + png_warning(png_ptr, "Ignoring invalid time value"); + + return; + } + + info_ptr->mod_time = *mod_time; + info_ptr->valid |= PNG_INFO_tIME; +} +#endif + +#ifdef PNG_tRNS_SUPPORTED +void PNGAPI +png_set_tRNS(png_structrp png_ptr, png_inforp info_ptr, + png_const_bytep trans_alpha, int num_trans, png_const_color_16p trans_color) +{ + png_debug1(1, "in %s storage function", "tRNS"); + + if (png_ptr == NULL || info_ptr == NULL) + + return; + + if (trans_alpha != NULL) + { + /* It may not actually be necessary to set png_ptr->trans_alpha here; + * we do it for backward compatibility with the way the png_handle_tRNS + * function used to do the allocation. + * + * 1.6.0: The above statement is incorrect; png_handle_tRNS effectively + * relies on png_set_tRNS storing the information in png_struct + * (otherwise it won't be there for the code in pngrtran.c). + */ + + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); + + if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) + { + /* Changed from num_trans to PNG_MAX_PALETTE_LENGTH in version 1.2.1 */ + info_ptr->trans_alpha = png_voidcast(png_bytep, + png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH)); + memcpy(info_ptr->trans_alpha, trans_alpha, (size_t)num_trans); + } + png_ptr->trans_alpha = info_ptr->trans_alpha; + } + + if (trans_color != NULL) + { +#ifdef PNG_WARNINGS_SUPPORTED + if (info_ptr->bit_depth < 16) + { + int sample_max = (1 << info_ptr->bit_depth) - 1; + + if ((info_ptr->color_type == PNG_COLOR_TYPE_GRAY && + trans_color->gray > sample_max) || + (info_ptr->color_type == PNG_COLOR_TYPE_RGB && + (trans_color->red > sample_max || + trans_color->green > sample_max || + trans_color->blue > sample_max))) + png_warning(png_ptr, + "tRNS chunk has out-of-range samples for bit_depth"); + } +#endif + + info_ptr->trans_color = *trans_color; + + if (num_trans == 0) + num_trans = 1; + } + + info_ptr->num_trans = (png_uint_16)num_trans; + + if (num_trans != 0) + { + info_ptr->valid |= PNG_INFO_tRNS; + info_ptr->free_me |= PNG_FREE_TRNS; + } +} +#endif + +#ifdef PNG_sPLT_SUPPORTED +void PNGAPI +png_set_sPLT(png_const_structrp png_ptr, + png_inforp info_ptr, png_const_sPLT_tp entries, int nentries) +/* + * entries - array of png_sPLT_t structures + * to be added to the list of palettes + * in the info structure. + * + * nentries - number of palette structures to be + * added. + */ +{ + png_sPLT_tp np; + + if (png_ptr == NULL || info_ptr == NULL || nentries <= 0 || entries == NULL) + return; + + /* Use the internal realloc function, which checks for all the possible + * overflows. Notice that the parameters are (int) and (size_t) + */ + np = png_voidcast(png_sPLT_tp,png_realloc_array(png_ptr, + info_ptr->splt_palettes, info_ptr->splt_palettes_num, nentries, + sizeof *np)); + + if (np == NULL) + { + /* Out of memory or too many chunks */ + png_chunk_report(png_ptr, "too many sPLT chunks", PNG_CHUNK_WRITE_ERROR); + + return; + } + + png_free(png_ptr, info_ptr->splt_palettes); + info_ptr->splt_palettes = np; + info_ptr->free_me |= PNG_FREE_SPLT; + + np += info_ptr->splt_palettes_num; + + do + { + size_t length; + + /* Skip invalid input entries */ + if (entries->name == NULL || entries->entries == NULL) + { + /* png_handle_sPLT doesn't do this, so this is an app error */ + png_app_error(png_ptr, "png_set_sPLT: invalid sPLT"); + /* Just skip the invalid entry */ + continue; + } + + np->depth = entries->depth; + + /* In the event of out-of-memory just return - there's no point keeping + * on trying to add sPLT chunks. + */ + length = strlen(entries->name) + 1; + np->name = png_voidcast(png_charp, png_malloc_base(png_ptr, length)); + + if (np->name == NULL) + break; + + memcpy(np->name, entries->name, length); + + /* IMPORTANT: we have memory now that won't get freed if something else + * goes wrong; this code must free it. png_malloc_array produces no + * warnings; use a png_chunk_report (below) if there is an error. + */ + np->entries = png_voidcast(png_sPLT_entryp, png_malloc_array(png_ptr, + entries->nentries, sizeof (png_sPLT_entry))); + + if (np->entries == NULL) + { + png_free(png_ptr, np->name); + np->name = NULL; + break; + } + + np->nentries = entries->nentries; + /* This multiply can't overflow because png_malloc_array has already + * checked it when doing the allocation. + */ + memcpy(np->entries, entries->entries, + (unsigned int)entries->nentries * sizeof (png_sPLT_entry)); + + /* Note that 'continue' skips the advance of the out pointer and out + * count, so an invalid entry is not added. + */ + info_ptr->valid |= PNG_INFO_sPLT; + ++(info_ptr->splt_palettes_num); + ++np; + ++entries; + } + while (--nentries); + + if (nentries > 0) + png_chunk_report(png_ptr, "sPLT out of memory", PNG_CHUNK_WRITE_ERROR); +} +#endif /* sPLT */ + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +static png_byte +check_location(png_const_structrp png_ptr, int location) +{ + location &= (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT); + + /* New in 1.6.0; copy the location and check it. This is an API + * change; previously the app had to use the + * png_set_unknown_chunk_location API below for each chunk. + */ + if (location == 0 && (png_ptr->mode & PNG_IS_READ_STRUCT) == 0) + { + /* Write struct, so unknown chunks come from the app */ + png_app_warning(png_ptr, + "png_set_unknown_chunks now expects a valid location"); + /* Use the old behavior */ + location = (png_byte)(png_ptr->mode & + (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT)); + } + + /* This need not be an internal error - if the app calls + * png_set_unknown_chunks on a read pointer it must get the location right. + */ + if (location == 0) + png_error(png_ptr, "invalid location in png_set_unknown_chunks"); + + /* Now reduce the location to the top-most set bit by removing each least + * significant bit in turn. + */ + while (location != (location & -location)) + location &= ~(location & -location); + + /* The cast is safe because 'location' is a bit mask and only the low four + * bits are significant. + */ + return (png_byte)location; +} + +void PNGAPI +png_set_unknown_chunks(png_const_structrp png_ptr, + png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns) +{ + png_unknown_chunkp np; + + if (png_ptr == NULL || info_ptr == NULL || num_unknowns <= 0 || + unknowns == NULL) + return; + + /* Check for the failure cases where support has been disabled at compile + * time. This code is hardly ever compiled - it's here because + * STORE_UNKNOWN_CHUNKS is set by both read and write code (compiling in this + * code) but may be meaningless if the read or write handling of unknown + * chunks is not compiled in. + */ +# if !defined(PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) && \ + defined(PNG_READ_SUPPORTED) + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) + { + png_app_error(png_ptr, "no unknown chunk support on read"); + + return; + } +# endif +# if !defined(PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED) && \ + defined(PNG_WRITE_SUPPORTED) + if ((png_ptr->mode & PNG_IS_READ_STRUCT) == 0) + { + png_app_error(png_ptr, "no unknown chunk support on write"); + + return; + } +# endif + + /* Prior to 1.6.0 this code used png_malloc_warn; however, this meant that + * unknown critical chunks could be lost with just a warning resulting in + * undefined behavior. Now png_chunk_report is used to provide behavior + * appropriate to read or write. + */ + np = png_voidcast(png_unknown_chunkp, png_realloc_array(png_ptr, + info_ptr->unknown_chunks, info_ptr->unknown_chunks_num, num_unknowns, + sizeof *np)); + + if (np == NULL) + { + png_chunk_report(png_ptr, "too many unknown chunks", + PNG_CHUNK_WRITE_ERROR); + + return; + } + + png_free(png_ptr, info_ptr->unknown_chunks); + info_ptr->unknown_chunks = np; /* safe because it is initialized */ + info_ptr->free_me |= PNG_FREE_UNKN; + + np += info_ptr->unknown_chunks_num; + + /* Increment unknown_chunks_num each time round the loop to protect the + * just-allocated chunk data. + */ + for (; num_unknowns > 0; --num_unknowns, ++unknowns) + { + memcpy(np->name, unknowns->name, (sizeof np->name)); + np->name[(sizeof np->name)-1] = '\0'; + np->location = check_location(png_ptr, unknowns->location); + + if (unknowns->size == 0) + { + np->data = NULL; + np->size = 0; + } + + else + { + np->data = png_voidcast(png_bytep, + png_malloc_base(png_ptr, unknowns->size)); + + if (np->data == NULL) + { + png_chunk_report(png_ptr, "unknown chunk: out of memory", + PNG_CHUNK_WRITE_ERROR); + /* But just skip storing the unknown chunk */ + continue; + } + + memcpy(np->data, unknowns->data, unknowns->size); + np->size = unknowns->size; + } + + /* These increments are skipped on out-of-memory for the data - the + * unknown chunk entry gets overwritten if the png_chunk_report returns. + * This is correct in the read case (the chunk is just dropped.) + */ + ++np; + ++(info_ptr->unknown_chunks_num); + } +} + +void PNGAPI +png_set_unknown_chunk_location(png_const_structrp png_ptr, png_inforp info_ptr, + int chunk, int location) +{ + /* This API is pretty pointless in 1.6.0 because the location can be set + * before the call to png_set_unknown_chunks. + * + * TODO: add a png_app_warning in 1.7 + */ + if (png_ptr != NULL && info_ptr != NULL && chunk >= 0 && + chunk < info_ptr->unknown_chunks_num) + { + if ((location & (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT)) == 0) + { + png_app_error(png_ptr, "invalid unknown chunk location"); + /* Fake out the pre 1.6.0 behavior: */ + if (((unsigned int)location & PNG_HAVE_IDAT) != 0) /* undocumented! */ + location = PNG_AFTER_IDAT; + + else + location = PNG_HAVE_IHDR; /* also undocumented */ + } + + info_ptr->unknown_chunks[chunk].location = + check_location(png_ptr, location); + } +} +#endif /* STORE_UNKNOWN_CHUNKS */ + +#ifdef PNG_MNG_FEATURES_SUPPORTED +png_uint_32 PNGAPI +png_permit_mng_features (png_structrp png_ptr, png_uint_32 mng_features) +{ + png_debug(1, "in png_permit_mng_features"); + + if (png_ptr == NULL) + return 0; + + png_ptr->mng_features_permitted = mng_features & PNG_ALL_MNG_FEATURES; + + return png_ptr->mng_features_permitted; +} +#endif + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +static unsigned int +add_one_chunk(png_bytep list, unsigned int count, png_const_bytep add, int keep) +{ + unsigned int i; + + /* Utility function: update the 'keep' state of a chunk if it is already in + * the list, otherwise add it to the list. + */ + for (i=0; i= PNG_HANDLE_CHUNK_LAST) + { + png_app_error(png_ptr, "png_set_keep_unknown_chunks: invalid keep"); + + return; + } + + if (num_chunks_in <= 0) + { + png_ptr->unknown_default = keep; + + /* '0' means just set the flags, so stop here */ + if (num_chunks_in == 0) + return; + } + + if (num_chunks_in < 0) + { + /* Ignore all unknown chunks and all chunks recognized by + * libpng except for IHDR, PLTE, tRNS, IDAT, and IEND + */ + static const png_byte chunks_to_ignore[] = { + 98, 75, 71, 68, '\0', /* bKGD */ + 99, 72, 82, 77, '\0', /* cHRM */ + 101, 88, 73, 102, '\0', /* eXIf */ + 103, 65, 77, 65, '\0', /* gAMA */ + 104, 73, 83, 84, '\0', /* hIST */ + 105, 67, 67, 80, '\0', /* iCCP */ + 105, 84, 88, 116, '\0', /* iTXt */ + 111, 70, 70, 115, '\0', /* oFFs */ + 112, 67, 65, 76, '\0', /* pCAL */ + 112, 72, 89, 115, '\0', /* pHYs */ + 115, 66, 73, 84, '\0', /* sBIT */ + 115, 67, 65, 76, '\0', /* sCAL */ + 115, 80, 76, 84, '\0', /* sPLT */ + 115, 84, 69, 82, '\0', /* sTER */ + 115, 82, 71, 66, '\0', /* sRGB */ + 116, 69, 88, 116, '\0', /* tEXt */ + 116, 73, 77, 69, '\0', /* tIME */ + 122, 84, 88, 116, '\0' /* zTXt */ + }; + + chunk_list = chunks_to_ignore; + num_chunks = (unsigned int)/*SAFE*/(sizeof chunks_to_ignore)/5U; + } + + else /* num_chunks_in > 0 */ + { + if (chunk_list == NULL) + { + /* Prior to 1.6.0 this was silently ignored, now it is an app_error + * which can be switched off. + */ + png_app_error(png_ptr, "png_set_keep_unknown_chunks: no chunk list"); + + return; + } + + num_chunks = (unsigned int)num_chunks_in; + } + + old_num_chunks = png_ptr->num_chunk_list; + if (png_ptr->chunk_list == NULL) + old_num_chunks = 0; + + /* Since num_chunks is always restricted to UINT_MAX/5 this can't overflow. + */ + if (num_chunks + old_num_chunks > UINT_MAX/5) + { + png_app_error(png_ptr, "png_set_keep_unknown_chunks: too many chunks"); + + return; + } + + /* If these chunks are being reset to the default then no more memory is + * required because add_one_chunk above doesn't extend the list if the 'keep' + * parameter is the default. + */ + if (keep != 0) + { + new_list = png_voidcast(png_bytep, png_malloc(png_ptr, + 5 * (num_chunks + old_num_chunks))); + + if (old_num_chunks > 0) + memcpy(new_list, png_ptr->chunk_list, 5*old_num_chunks); + } + + else if (old_num_chunks > 0) + new_list = png_ptr->chunk_list; + + else + new_list = NULL; + + /* Add the new chunks together with each one's handling code. If the chunk + * already exists the code is updated, otherwise the chunk is added to the + * end. (In libpng 1.6.0 order no longer matters because this code enforces + * the earlier convention that the last setting is the one that is used.) + */ + if (new_list != NULL) + { + png_const_bytep inlist; + png_bytep outlist; + unsigned int i; + + for (i=0; ichunk_list != new_list) + png_free(png_ptr, new_list); + + new_list = NULL; + } + } + + else + num_chunks = 0; + + png_ptr->num_chunk_list = num_chunks; + + if (png_ptr->chunk_list != new_list) + { + if (png_ptr->chunk_list != NULL) + png_free(png_ptr, png_ptr->chunk_list); + + png_ptr->chunk_list = new_list; + } +} +#endif + +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED +void PNGAPI +png_set_read_user_chunk_fn(png_structrp png_ptr, png_voidp user_chunk_ptr, + png_user_chunk_ptr read_user_chunk_fn) +{ + png_debug(1, "in png_set_read_user_chunk_fn"); + + if (png_ptr == NULL) + return; + + png_ptr->read_user_chunk_fn = read_user_chunk_fn; + png_ptr->user_chunk_ptr = user_chunk_ptr; +} +#endif + +#ifdef PNG_INFO_IMAGE_SUPPORTED +void PNGAPI +png_set_rows(png_const_structrp png_ptr, png_inforp info_ptr, + png_bytepp row_pointers) +{ + png_debug1(1, "in %s storage function", "rows"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (info_ptr->row_pointers != NULL && + (info_ptr->row_pointers != row_pointers)) + png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0); + + info_ptr->row_pointers = row_pointers; + + if (row_pointers != NULL) + info_ptr->valid |= PNG_INFO_IDAT; +} +#endif + +void PNGAPI +png_set_compression_buffer_size(png_structrp png_ptr, size_t size) +{ + if (png_ptr == NULL) + return; + + if (size == 0 || size > PNG_UINT_31_MAX) + png_error(png_ptr, "invalid compression buffer size"); + +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) + { + png_ptr->IDAT_read_size = (png_uint_32)size; /* checked above */ + return; + } +# endif + +# ifdef PNG_WRITE_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) == 0) + { + if (png_ptr->zowner != 0) + { + png_warning(png_ptr, + "Compression buffer size cannot be changed because it is in use"); + + return; + } + +#ifndef __COVERITY__ + /* Some compilers complain that this is always false. However, it + * can be true when integer overflow happens. + */ + if (size > ZLIB_IO_MAX) + { + png_warning(png_ptr, + "Compression buffer size limited to system maximum"); + size = ZLIB_IO_MAX; /* must fit */ + } +#endif + + if (size < 6) + { + /* Deflate will potentially go into an infinite loop on a SYNC_FLUSH + * if this is permitted. + */ + png_warning(png_ptr, + "Compression buffer size cannot be reduced below 6"); + + return; + } + + if (png_ptr->zbuffer_size != size) + { + png_free_buffer_list(png_ptr, &png_ptr->zbuffer_list); + png_ptr->zbuffer_size = (uInt)size; + } + } +# endif +} + +void PNGAPI +png_set_invalid(png_const_structrp png_ptr, png_inforp info_ptr, int mask) +{ + if (png_ptr != NULL && info_ptr != NULL) + info_ptr->valid &= (unsigned int)(~mask); +} + + +#ifdef PNG_SET_USER_LIMITS_SUPPORTED +/* This function was added to libpng 1.2.6 */ +void PNGAPI +png_set_user_limits (png_structrp png_ptr, png_uint_32 user_width_max, + png_uint_32 user_height_max) +{ + /* Images with dimensions larger than these limits will be + * rejected by png_set_IHDR(). To accept any PNG datastream + * regardless of dimensions, set both limits to 0x7fffffff. + */ + if (png_ptr == NULL) + return; + + png_ptr->user_width_max = user_width_max; + png_ptr->user_height_max = user_height_max; +} + +/* This function was added to libpng 1.4.0 */ +void PNGAPI +png_set_chunk_cache_max (png_structrp png_ptr, png_uint_32 user_chunk_cache_max) +{ + if (png_ptr != NULL) + png_ptr->user_chunk_cache_max = user_chunk_cache_max; +} + +/* This function was added to libpng 1.4.1 */ +void PNGAPI +png_set_chunk_malloc_max (png_structrp png_ptr, + png_alloc_size_t user_chunk_malloc_max) +{ + if (png_ptr != NULL) + png_ptr->user_chunk_malloc_max = user_chunk_malloc_max; +} +#endif /* ?SET_USER_LIMITS */ + + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +void PNGAPI +png_set_benign_errors(png_structrp png_ptr, int allowed) +{ + png_debug(1, "in png_set_benign_errors"); + + /* If allowed is 1, png_benign_error() is treated as a warning. + * + * If allowed is 0, png_benign_error() is treated as an error (which + * is the default behavior if png_set_benign_errors() is not called). + */ + + if (allowed != 0) + png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN | + PNG_FLAG_APP_WARNINGS_WARN | PNG_FLAG_APP_ERRORS_WARN; + + else + png_ptr->flags &= ~(PNG_FLAG_BENIGN_ERRORS_WARN | + PNG_FLAG_APP_WARNINGS_WARN | PNG_FLAG_APP_ERRORS_WARN); +} +#endif /* BENIGN_ERRORS */ + +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Whether to report invalid palette index; added at libng-1.5.10. + * It is possible for an indexed (color-type==3) PNG file to contain + * pixels with invalid (out-of-range) indexes if the PLTE chunk has + * fewer entries than the image's bit-depth would allow. We recover + * from this gracefully by filling any incomplete palette with zeros + * (opaque black). By default, when this occurs libpng will issue + * a benign error. This API can be used to override that behavior. + */ +void PNGAPI +png_set_check_for_invalid_index(png_structrp png_ptr, int allowed) +{ + png_debug(1, "in png_set_check_for_invalid_index"); + + if (allowed > 0) + png_ptr->num_palette_max = 0; + + else + png_ptr->num_palette_max = -1; +} +#endif + +#if defined(PNG_TEXT_SUPPORTED) || defined(PNG_pCAL_SUPPORTED) || \ + defined(PNG_iCCP_SUPPORTED) || defined(PNG_sPLT_SUPPORTED) +/* Check that the tEXt or zTXt keyword is valid per PNG 1.0 specification, + * and if invalid, correct the keyword rather than discarding the entire + * chunk. The PNG 1.0 specification requires keywords 1-79 characters in + * length, forbids leading or trailing whitespace, multiple internal spaces, + * and the non-break space (0x80) from ISO 8859-1. Returns keyword length. + * + * The 'new_key' buffer must be 80 characters in size (for the keyword plus a + * trailing '\0'). If this routine returns 0 then there was no keyword, or a + * valid one could not be generated, and the caller must png_error. + */ +png_uint_32 /* PRIVATE */ +png_check_keyword(png_structrp png_ptr, png_const_charp key, png_bytep new_key) +{ +#ifdef PNG_WARNINGS_SUPPORTED + png_const_charp orig_key = key; +#endif + png_uint_32 key_len = 0; + int bad_character = 0; + int space = 1; + + png_debug(1, "in png_check_keyword"); + + if (key == NULL) + { + *new_key = 0; + return 0; + } + + while (*key && key_len < 79) + { + png_byte ch = (png_byte)*key++; + + if ((ch > 32 && ch <= 126) || (ch >= 161 /*&& ch <= 255*/)) + { + *new_key++ = ch; ++key_len; space = 0; + } + + else if (space == 0) + { + /* A space or an invalid character when one wasn't seen immediately + * before; output just a space. + */ + *new_key++ = 32; ++key_len; space = 1; + + /* If the character was not a space then it is invalid. */ + if (ch != 32) + bad_character = ch; + } + + else if (bad_character == 0) + bad_character = ch; /* just skip it, record the first error */ + } + + if (key_len > 0 && space != 0) /* trailing space */ + { + --key_len; --new_key; + if (bad_character == 0) + bad_character = 32; + } + + /* Terminate the keyword */ + *new_key = 0; + + if (key_len == 0) + return 0; + +#ifdef PNG_WARNINGS_SUPPORTED + /* Try to only output one warning per keyword: */ + if (*key != 0) /* keyword too long */ + png_warning(png_ptr, "keyword truncated"); + + else if (bad_character != 0) + { + PNG_WARNING_PARAMETERS(p) + + png_warning_parameter(p, 1, orig_key); + png_warning_parameter_signed(p, 2, PNG_NUMBER_FORMAT_02x, bad_character); + + png_formatted_warning(png_ptr, p, "keyword \"@1\": bad character '0x@2'"); + } +#else /* !WARNINGS */ + PNG_UNUSED(png_ptr) +#endif /* !WARNINGS */ + + return key_len; +} +#endif /* TEXT || pCAL || iCCP || sPLT */ +#endif /* READ || WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngstruct.h b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngstruct.h new file mode 100644 index 0000000..2873d09 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngstruct.h @@ -0,0 +1,489 @@ + +/* pngstruct.h - header file for PNG reference library + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +/* The structure that holds the information to read and write PNG files. + * The only people who need to care about what is inside of this are the + * people who will be modifying the library for their own special needs. + * It should NOT be accessed directly by an application. + */ + +#ifndef PNGSTRUCT_H +#define PNGSTRUCT_H +/* zlib.h defines the structure z_stream, an instance of which is included + * in this structure and is required for decompressing the LZ compressed + * data in PNG files. + */ +#ifndef ZLIB_CONST + /* We must ensure that zlib uses 'const' in declarations. */ +# define ZLIB_CONST +#endif +#include "../zlib/zlib.h" +#ifdef const + /* zlib.h sometimes #defines const to nothing, undo this. */ +# undef const +#endif + +/* zlib.h has mediocre z_const use before 1.2.6, this stuff is for compatibility + * with older builds. + */ +#if ZLIB_VERNUM < 0x1260 +# define PNGZ_MSG_CAST(s) png_constcast(char*,s) +# define PNGZ_INPUT_CAST(b) png_constcast(png_bytep,b) +#else +# define PNGZ_MSG_CAST(s) (s) +# define PNGZ_INPUT_CAST(b) (b) +#endif + +/* zlib.h declares a magic type 'uInt' that limits the amount of data that zlib + * can handle at once. This type need be no larger than 16 bits (so maximum of + * 65535), this define allows us to discover how big it is, but limited by the + * maximum for size_t. The value can be overridden in a library build + * (pngusr.h, or set it in CPPFLAGS) and it works to set it to a considerably + * lower value (e.g. 255 works). A lower value may help memory usage (slightly) + * and may even improve performance on some systems (and degrade it on others.) + */ +#ifndef ZLIB_IO_MAX +# define ZLIB_IO_MAX ((uInt)-1) +#endif + +#ifdef PNG_WRITE_SUPPORTED +/* The type of a compression buffer list used by the write code. */ +typedef struct png_compression_buffer +{ + struct png_compression_buffer *next; + png_byte output[1]; /* actually zbuf_size */ +} png_compression_buffer, *png_compression_bufferp; + +#define PNG_COMPRESSION_BUFFER_SIZE(pp)\ + (offsetof(png_compression_buffer, output) + (pp)->zbuffer_size) +#endif + +/* Colorspace support; structures used in png_struct, png_info and in internal + * functions to hold and communicate information about the color space. + * + * PNG_COLORSPACE_SUPPORTED is only required if the application will perform + * colorspace corrections, otherwise all the colorspace information can be + * skipped and the size of libpng can be reduced (significantly) by compiling + * out the colorspace support. + */ +#ifdef PNG_COLORSPACE_SUPPORTED +/* The chromaticities of the red, green and blue colorants and the chromaticity + * of the corresponding white point (i.e. of rgb(1.0,1.0,1.0)). + */ +typedef struct png_xy +{ + png_fixed_point redx, redy; + png_fixed_point greenx, greeny; + png_fixed_point bluex, bluey; + png_fixed_point whitex, whitey; +} png_xy; + +/* The same data as above but encoded as CIE XYZ values. When this data comes + * from chromaticities the sum of the Y values is assumed to be 1.0 + */ +typedef struct png_XYZ +{ + png_fixed_point red_X, red_Y, red_Z; + png_fixed_point green_X, green_Y, green_Z; + png_fixed_point blue_X, blue_Y, blue_Z; +} png_XYZ; +#endif /* COLORSPACE */ + +#if defined(PNG_COLORSPACE_SUPPORTED) || defined(PNG_GAMMA_SUPPORTED) +/* A colorspace is all the above plus, potentially, profile information; + * however at present libpng does not use the profile internally so it is only + * stored in the png_info struct (if iCCP is supported.) The rendering intent + * is retained here and is checked. + * + * The file gamma encoding information is also stored here and gamma correction + * is done by libpng, whereas color correction must currently be done by the + * application. + */ +typedef struct png_colorspace +{ +#ifdef PNG_GAMMA_SUPPORTED + png_fixed_point gamma; /* File gamma */ +#endif + +#ifdef PNG_COLORSPACE_SUPPORTED + png_xy end_points_xy; /* End points as chromaticities */ + png_XYZ end_points_XYZ; /* End points as CIE XYZ colorant values */ + png_uint_16 rendering_intent; /* Rendering intent of a profile */ +#endif + + /* Flags are always defined to simplify the code. */ + png_uint_16 flags; /* As defined below */ +} png_colorspace, * PNG_RESTRICT png_colorspacerp; + +typedef const png_colorspace * PNG_RESTRICT png_const_colorspacerp; + +/* General flags for the 'flags' field */ +#define PNG_COLORSPACE_HAVE_GAMMA 0x0001 +#define PNG_COLORSPACE_HAVE_ENDPOINTS 0x0002 +#define PNG_COLORSPACE_HAVE_INTENT 0x0004 +#define PNG_COLORSPACE_FROM_gAMA 0x0008 +#define PNG_COLORSPACE_FROM_cHRM 0x0010 +#define PNG_COLORSPACE_FROM_sRGB 0x0020 +#define PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB 0x0040 +#define PNG_COLORSPACE_MATCHES_sRGB 0x0080 /* exact match on profile */ +#define PNG_COLORSPACE_INVALID 0x8000 +#define PNG_COLORSPACE_CANCEL(flags) (0xffff ^ (flags)) +#endif /* COLORSPACE || GAMMA */ + +struct png_struct_def +{ +#ifdef PNG_SETJMP_SUPPORTED + jmp_buf jmp_buf_local; /* New name in 1.6.0 for jmp_buf in png_struct */ + png_longjmp_ptr longjmp_fn;/* setjmp non-local goto function. */ + jmp_buf *jmp_buf_ptr; /* passed to longjmp_fn */ + size_t jmp_buf_size; /* size of the above, if allocated */ +#endif + png_error_ptr error_fn; /* function for printing errors and aborting */ +#ifdef PNG_WARNINGS_SUPPORTED + png_error_ptr warning_fn; /* function for printing warnings */ +#endif + png_voidp error_ptr; /* user supplied struct for error functions */ + png_rw_ptr write_data_fn; /* function for writing output data */ + png_rw_ptr read_data_fn; /* function for reading input data */ + png_voidp io_ptr; /* ptr to application struct for I/O functions */ + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + png_user_transform_ptr read_user_transform_fn; /* user read transform */ +#endif + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED + png_user_transform_ptr write_user_transform_fn; /* user write transform */ +#endif + +/* These were added in libpng-1.0.2 */ +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) + png_voidp user_transform_ptr; /* user supplied struct for user transform */ + png_byte user_transform_depth; /* bit depth of user transformed pixels */ + png_byte user_transform_channels; /* channels in user transformed pixels */ +#endif +#endif + + png_uint_32 mode; /* tells us where we are in the PNG file */ + png_uint_32 flags; /* flags indicating various things to libpng */ + png_uint_32 transformations; /* which transformations to perform */ + + png_uint_32 zowner; /* ID (chunk type) of zstream owner, 0 if none */ + z_stream zstream; /* decompression structure */ + +#ifdef PNG_WRITE_SUPPORTED + png_compression_bufferp zbuffer_list; /* Created on demand during write */ + uInt zbuffer_size; /* size of the actual buffer */ + + int zlib_level; /* holds zlib compression level */ + int zlib_method; /* holds zlib compression method */ + int zlib_window_bits; /* holds zlib compression window bits */ + int zlib_mem_level; /* holds zlib compression memory level */ + int zlib_strategy; /* holds zlib compression strategy */ +#endif +/* Added at libpng 1.5.4 */ +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED + int zlib_text_level; /* holds zlib compression level */ + int zlib_text_method; /* holds zlib compression method */ + int zlib_text_window_bits; /* holds zlib compression window bits */ + int zlib_text_mem_level; /* holds zlib compression memory level */ + int zlib_text_strategy; /* holds zlib compression strategy */ +#endif +/* End of material added at libpng 1.5.4 */ +/* Added at libpng 1.6.0 */ +#ifdef PNG_WRITE_SUPPORTED + int zlib_set_level; /* Actual values set into the zstream on write */ + int zlib_set_method; + int zlib_set_window_bits; + int zlib_set_mem_level; + int zlib_set_strategy; +#endif + + png_uint_32 width; /* width of image in pixels */ + png_uint_32 height; /* height of image in pixels */ + png_uint_32 num_rows; /* number of rows in current pass */ + png_uint_32 usr_width; /* width of row at start of write */ + size_t rowbytes; /* size of row in bytes */ + png_uint_32 iwidth; /* width of current interlaced row in pixels */ + png_uint_32 row_number; /* current row in interlace pass */ + png_uint_32 chunk_name; /* PNG_CHUNK() id of current chunk */ + png_bytep prev_row; /* buffer to save previous (unfiltered) row. + * While reading this is a pointer into + * big_prev_row; while writing it is separately + * allocated if needed. + */ + png_bytep row_buf; /* buffer to save current (unfiltered) row. + * While reading, this is a pointer into + * big_row_buf; while writing it is separately + * allocated. + */ +#ifdef PNG_WRITE_FILTER_SUPPORTED + png_bytep try_row; /* buffer to save trial row when filtering */ + png_bytep tst_row; /* buffer to save best trial row when filtering */ +#endif + size_t info_rowbytes; /* Added in 1.5.4: cache of updated row bytes */ + + png_uint_32 idat_size; /* current IDAT size for read */ + png_uint_32 crc; /* current chunk CRC value */ + png_colorp palette; /* palette from the input file */ + png_uint_16 num_palette; /* number of color entries in palette */ + +/* Added at libpng-1.5.10 */ +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED + int num_palette_max; /* maximum palette index found in IDAT */ +#endif + + png_uint_16 num_trans; /* number of transparency values */ + png_byte compression; /* file compression type (always 0) */ + png_byte filter; /* file filter type (always 0) */ + png_byte interlaced; /* PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */ + png_byte pass; /* current interlace pass (0 - 6) */ + png_byte do_filter; /* row filter flags (see PNG_FILTER_ in png.h ) */ + png_byte color_type; /* color type of file */ + png_byte bit_depth; /* bit depth of file */ + png_byte usr_bit_depth; /* bit depth of users row: write only */ + png_byte pixel_depth; /* number of bits per pixel */ + png_byte channels; /* number of channels in file */ +#ifdef PNG_WRITE_SUPPORTED + png_byte usr_channels; /* channels at start of write: write only */ +#endif + png_byte sig_bytes; /* magic bytes read/written from start of file */ + png_byte maximum_pixel_depth; + /* pixel depth used for the row buffers */ + png_byte transformed_pixel_depth; + /* pixel depth after read/write transforms */ +#if ZLIB_VERNUM >= 0x1240 + png_byte zstream_start; /* at start of an input zlib stream */ +#endif /* Zlib >= 1.2.4 */ +#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED) + png_uint_16 filler; /* filler bytes for pixel expansion */ +#endif + +#if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + png_byte background_gamma_type; + png_fixed_point background_gamma; + png_color_16 background; /* background color in screen gamma space */ +#ifdef PNG_READ_GAMMA_SUPPORTED + png_color_16 background_1; /* background normalized to gamma 1.0 */ +#endif +#endif /* bKGD */ + +#ifdef PNG_WRITE_FLUSH_SUPPORTED + png_flush_ptr output_flush_fn; /* Function for flushing output */ + png_uint_32 flush_dist; /* how many rows apart to flush, 0 - no flush */ + png_uint_32 flush_rows; /* number of rows written since last flush */ +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED + int gamma_shift; /* number of "insignificant" bits in 16-bit gamma */ + png_fixed_point screen_gamma; /* screen gamma value (display_exponent) */ + + png_bytep gamma_table; /* gamma table for 8-bit depth files */ + png_uint_16pp gamma_16_table; /* gamma table for 16-bit depth files */ +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + png_bytep gamma_from_1; /* converts from 1.0 to screen */ + png_bytep gamma_to_1; /* converts from file to 1.0 */ + png_uint_16pp gamma_16_from_1; /* converts from 1.0 to screen */ + png_uint_16pp gamma_16_to_1; /* converts from file to 1.0 */ +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ +#endif + +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_sBIT_SUPPORTED) + png_color_8 sig_bit; /* significant bits in each available channel */ +#endif + +#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED) + png_color_8 shift; /* shift for significant bit transformation */ +#endif + +#if defined(PNG_tRNS_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) \ + || defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) + png_bytep trans_alpha; /* alpha values for paletted files */ + png_color_16 trans_color; /* transparent color for non-paletted files */ +#endif + + png_read_status_ptr read_row_fn; /* called after each row is decoded */ + png_write_status_ptr write_row_fn; /* called after each row is encoded */ +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED + png_progressive_info_ptr info_fn; /* called after header data fully read */ + png_progressive_row_ptr row_fn; /* called after a prog. row is decoded */ + png_progressive_end_ptr end_fn; /* called after image is complete */ + png_bytep save_buffer_ptr; /* current location in save_buffer */ + png_bytep save_buffer; /* buffer for previously read data */ + png_bytep current_buffer_ptr; /* current location in current_buffer */ + png_bytep current_buffer; /* buffer for recently used data */ + png_uint_32 push_length; /* size of current input chunk */ + png_uint_32 skip_length; /* bytes to skip in input data */ + size_t save_buffer_size; /* amount of data now in save_buffer */ + size_t save_buffer_max; /* total size of save_buffer */ + size_t buffer_size; /* total amount of available input data */ + size_t current_buffer_size; /* amount of data now in current_buffer */ + int process_mode; /* what push library is currently doing */ + int cur_palette; /* current push library palette index */ + +#endif /* PROGRESSIVE_READ */ + +#if defined(__TURBOC__) && !defined(_Windows) && !defined(__FLAT__) +/* For the Borland special 64K segment handler */ + png_bytepp offset_table_ptr; + png_bytep offset_table; + png_uint_16 offset_table_number; + png_uint_16 offset_table_count; + png_uint_16 offset_table_count_free; +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + png_bytep palette_lookup; /* lookup table for quantizing */ + png_bytep quantize_index; /* index translation for palette files */ +#endif + +/* Options */ +#ifdef PNG_SET_OPTION_SUPPORTED + png_uint_32 options; /* On/off state (up to 16 options) */ +#endif + +#if PNG_LIBPNG_VER < 10700 +/* To do: remove this from libpng-1.7 */ +#ifdef PNG_TIME_RFC1123_SUPPORTED + char time_buffer[29]; /* String to hold RFC 1123 time text */ +#endif +#endif + +/* New members added in libpng-1.0.6 */ + + png_uint_32 free_me; /* flags items libpng is responsible for freeing */ + +#ifdef PNG_USER_CHUNKS_SUPPORTED + png_voidp user_chunk_ptr; +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED + png_user_chunk_ptr read_user_chunk_fn; /* user read chunk handler */ +#endif +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + int unknown_default; /* As PNG_HANDLE_* */ + unsigned int num_chunk_list; /* Number of entries in the list */ + png_bytep chunk_list; /* List of png_byte[5]; the textual chunk name + * followed by a PNG_HANDLE_* byte */ +#endif + +/* New members added in libpng-1.0.3 */ +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + png_byte rgb_to_gray_status; + /* Added in libpng 1.5.5 to record setting of coefficients: */ + png_byte rgb_to_gray_coefficients_set; + /* These were changed from png_byte in libpng-1.0.6 */ + png_uint_16 rgb_to_gray_red_coeff; + png_uint_16 rgb_to_gray_green_coeff; + /* deleted in 1.5.5: rgb_to_gray_blue_coeff; */ +#endif + +/* New member added in libpng-1.6.36 */ +#if defined(PNG_READ_EXPAND_SUPPORTED) && \ + defined(PNG_ARM_NEON_IMPLEMENTATION) + png_bytep riffled_palette; /* buffer for accelerated palette expansion */ +#endif + +/* New member added in libpng-1.0.4 (renamed in 1.0.9) */ +#if defined(PNG_MNG_FEATURES_SUPPORTED) +/* Changed from png_byte to png_uint_32 at version 1.2.0 */ + png_uint_32 mng_features_permitted; +#endif + +/* New member added in libpng-1.0.9, ifdef'ed out in 1.0.12, enabled in 1.2.0 */ +#ifdef PNG_MNG_FEATURES_SUPPORTED + png_byte filter_type; +#endif + +/* New members added in libpng-1.2.0 */ + +/* New members added in libpng-1.0.2 but first enabled by default in 1.2.0 */ +#ifdef PNG_USER_MEM_SUPPORTED + png_voidp mem_ptr; /* user supplied struct for mem functions */ + png_malloc_ptr malloc_fn; /* function for allocating memory */ + png_free_ptr free_fn; /* function for freeing memory */ +#endif + +/* New member added in libpng-1.0.13 and 1.2.0 */ + png_bytep big_row_buf; /* buffer to save current (unfiltered) row */ + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +/* The following three members were added at version 1.0.14 and 1.2.4 */ + png_bytep quantize_sort; /* working sort array */ + png_bytep index_to_palette; /* where the original index currently is + in the palette */ + png_bytep palette_to_index; /* which original index points to this + palette color */ +#endif + +/* New members added in libpng-1.0.16 and 1.2.6 */ + png_byte compression_type; + +#ifdef PNG_USER_LIMITS_SUPPORTED + png_uint_32 user_width_max; + png_uint_32 user_height_max; + + /* Added in libpng-1.4.0: Total number of sPLT, text, and unknown + * chunks that can be stored (0 means unlimited). + */ + png_uint_32 user_chunk_cache_max; + + /* Total memory that a zTXt, sPLT, iTXt, iCCP, or unknown chunk + * can occupy when decompressed. 0 means unlimited. + */ + png_alloc_size_t user_chunk_malloc_max; +#endif + +/* New member added in libpng-1.0.25 and 1.2.17 */ +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + /* Temporary storage for unknown chunk that the library doesn't recognize, + * used while reading the chunk. + */ + png_unknown_chunk unknown_chunk; +#endif + +/* New member added in libpng-1.2.26 */ + size_t old_big_row_buf_size; + +#ifdef PNG_READ_SUPPORTED +/* New member added in libpng-1.2.30 */ + png_bytep read_buffer; /* buffer for reading chunk data */ + png_alloc_size_t read_buffer_size; /* current size of the buffer */ +#endif +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED + uInt IDAT_read_size; /* limit on read buffer size for IDAT */ +#endif + +#ifdef PNG_IO_STATE_SUPPORTED +/* New member added in libpng-1.4.0 */ + png_uint_32 io_state; +#endif + +/* New member added in libpng-1.5.6 */ + png_bytep big_prev_row; + +/* New member added in libpng-1.5.7 */ + void (*read_filter[PNG_FILTER_VALUE_LAST-1])(png_row_infop row_info, + png_bytep row, png_const_bytep prev_row); + +#ifdef PNG_READ_SUPPORTED +#if defined(PNG_COLORSPACE_SUPPORTED) || defined(PNG_GAMMA_SUPPORTED) + png_colorspace colorspace; +#endif +#endif +}; +#endif /* PNGSTRUCT_H */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngtest.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngtest.c new file mode 100644 index 0000000..a715ae1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngtest.c @@ -0,0 +1,2158 @@ + +/* pngtest.c - a simple test program to test libpng + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This program reads in a PNG image, writes it out again, and then + * compares the two files. If the files are identical, this shows that + * the basic chunk handling, filtering, and (de)compression code is working + * properly. It does not currently test all of the transforms, although + * it probably should. + * + * The program will report "FAIL" in certain legitimate cases: + * 1) when the compression level or filter selection method is changed. + * 2) when the maximum IDAT size (PNG_ZBUF_SIZE in pngconf.h) is not 8192. + * 3) unknown unsafe-to-copy ancillary chunks or unknown critical chunks + * exist in the input file. + * 4) others not listed here... + * In these cases, it is best to check with another tool such as "pngcheck" + * to see what the differences between the two files are. + * + * If a filename is given on the command-line, then this file is used + * for the input, rather than the default "pngtest.png". This allows + * testing a wide variety of files easily. You can also test a number + * of files at once by typing "pngtest -m file1.png file2.png ..." + */ + +#define _POSIX_SOURCE 1 + +#include +#include +#include + +/* Defined so I can write to a file on gui/windowing platforms */ +/* #define STDERR stderr */ +#define STDERR stdout /* For DOS */ + +#include "png.h" + +/* Known chunks that exist in pngtest.png must be supported or pngtest will fail + * simply as a result of re-ordering them. This may be fixed in 1.7 + * + * pngtest allocates a single row buffer for each row and overwrites it, + * therefore if the write side doesn't support the writing of interlaced images + * nothing can be done for an interlaced image (and the code below will fail + * horribly trying to write extra data after writing garbage). + */ +#if defined PNG_READ_SUPPORTED && /* else nothing can be done */\ + defined PNG_READ_bKGD_SUPPORTED &&\ + defined PNG_READ_cHRM_SUPPORTED &&\ + defined PNG_READ_gAMA_SUPPORTED &&\ + defined PNG_READ_oFFs_SUPPORTED &&\ + defined PNG_READ_pCAL_SUPPORTED &&\ + defined PNG_READ_pHYs_SUPPORTED &&\ + defined PNG_READ_sBIT_SUPPORTED &&\ + defined PNG_READ_sCAL_SUPPORTED &&\ + defined PNG_READ_sRGB_SUPPORTED &&\ + defined PNG_READ_sPLT_SUPPORTED &&\ + defined PNG_READ_tEXt_SUPPORTED &&\ + defined PNG_READ_tIME_SUPPORTED &&\ + defined PNG_READ_zTXt_SUPPORTED &&\ + (defined PNG_WRITE_INTERLACING_SUPPORTED || PNG_LIBPNG_VER >= 10700) + +#ifdef PNG_ZLIB_HEADER +# include PNG_ZLIB_HEADER /* defined by pnglibconf.h from 1.7 */ +#else +# include "zlib.h" +#endif + +/* Copied from pngpriv.h but only used in error messages below. */ +#ifndef PNG_ZBUF_SIZE +# define PNG_ZBUF_SIZE 8192 +#endif +#define FCLOSE(file) fclose(file) + +#ifndef PNG_STDIO_SUPPORTED +typedef FILE * png_FILE_p; +#endif + +/* Makes pngtest verbose so we can find problems. */ +#ifndef PNG_DEBUG +# define PNG_DEBUG 0 +#endif + +#if PNG_DEBUG > 1 +# define pngtest_debug(m) ((void)fprintf(stderr, m "\n")) +# define pngtest_debug1(m,p1) ((void)fprintf(stderr, m "\n", p1)) +# define pngtest_debug2(m,p1,p2) ((void)fprintf(stderr, m "\n", p1, p2)) +#else +# define pngtest_debug(m) ((void)0) +# define pngtest_debug1(m,p1) ((void)0) +# define pngtest_debug2(m,p1,p2) ((void)0) +#endif + +#if !PNG_DEBUG +# define SINGLE_ROWBUF_ALLOC /* Makes buffer overruns easier to nail */ +#endif + +#ifndef PNG_UNUSED +# define PNG_UNUSED(param) (void)param; +#endif + +/* Turn on CPU timing +#define PNGTEST_TIMING +*/ + +#ifndef PNG_FLOATING_POINT_SUPPORTED +#undef PNGTEST_TIMING +#endif + +#ifdef PNGTEST_TIMING +static float t_start, t_stop, t_decode, t_encode, t_misc; +#include +#endif + +#ifdef PNG_TIME_RFC1123_SUPPORTED +#define PNG_tIME_STRING_LENGTH 29 +static int tIME_chunk_present = 0; +static char tIME_string[PNG_tIME_STRING_LENGTH] = "tIME chunk is not present"; + +#if PNG_LIBPNG_VER < 10619 +#define png_convert_to_rfc1123_buffer(ts, t) tIME_to_str(read_ptr, ts, t) + +static int +tIME_to_str(png_structp png_ptr, png_charp ts, png_const_timep t) +{ + png_const_charp str = png_convert_to_rfc1123(png_ptr, t); + + if (str == NULL) + return 0; + + strcpy(ts, str); + return 1; +} +#endif /* older libpng */ +#endif + +static int verbose = 0; +static int strict = 0; +static int relaxed = 0; +static int xfail = 0; +static int unsupported_chunks = 0; /* chunk unsupported by libpng in input */ +static int error_count = 0; /* count calls to png_error */ +static int warning_count = 0; /* count calls to png_warning */ + +/* Define png_jmpbuf() in case we are using a pre-1.0.6 version of libpng */ +#ifndef png_jmpbuf +# define png_jmpbuf(png_ptr) png_ptr->jmpbuf +#endif + +/* Defines for unknown chunk handling if required. */ +#ifndef PNG_HANDLE_CHUNK_ALWAYS +# define PNG_HANDLE_CHUNK_ALWAYS 3 +#endif +#ifndef PNG_HANDLE_CHUNK_IF_SAFE +# define PNG_HANDLE_CHUNK_IF_SAFE 2 +#endif + +/* Utility to save typing/errors, the argument must be a name */ +#define MEMZERO(var) ((void)memset(&var, 0, sizeof var)) + +/* Example of using row callbacks to make a simple progress meter */ +static int status_pass = 1; +static int status_dots_requested = 0; +static int status_dots = 1; + +static void PNGCBAPI +read_row_callback(png_structp png_ptr, png_uint_32 row_number, int pass) +{ + if (png_ptr == NULL || row_number > PNG_UINT_31_MAX) + return; + + if (status_pass != pass) + { + fprintf(stdout, "\n Pass %d: ", pass); + status_pass = pass; + status_dots = 31; + } + + status_dots--; + + if (status_dots == 0) + { + fprintf(stdout, "\n "); + status_dots=30; + } + + fprintf(stdout, "r"); +} + +#ifdef PNG_WRITE_SUPPORTED +static void PNGCBAPI +write_row_callback(png_structp png_ptr, png_uint_32 row_number, int pass) +{ + if (png_ptr == NULL || row_number > PNG_UINT_31_MAX || pass > 7) + return; + + fprintf(stdout, "w"); +} +#endif + + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED +/* Example of using a user transform callback (doesn't do anything at present). + */ +static void PNGCBAPI +read_user_callback(png_structp png_ptr, png_row_infop row_info, png_bytep data) +{ + PNG_UNUSED(png_ptr) + PNG_UNUSED(row_info) + PNG_UNUSED(data) +} +#endif + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED +/* Example of using user transform callback (we don't transform anything, + * but merely count the zero samples) + */ + +static png_uint_32 zero_samples; + +static void PNGCBAPI +count_zero_samples(png_structp png_ptr, png_row_infop row_info, png_bytep data) +{ + png_bytep dp = data; + if (png_ptr == NULL) + return; + + /* Contents of row_info: + * png_uint_32 width width of row + * png_uint_32 rowbytes number of bytes in row + * png_byte color_type color type of pixels + * png_byte bit_depth bit depth of samples + * png_byte channels number of channels (1-4) + * png_byte pixel_depth bits per pixel (depth*channels) + */ + + /* Counts the number of zero samples (or zero pixels if color_type is 3 */ + + if (row_info->color_type == 0 || row_info->color_type == 3) + { + int pos = 0; + png_uint_32 n, nstop; + + for (n = 0, nstop=row_info->width; nbit_depth == 1) + { + if (((*dp << pos++ ) & 0x80) == 0) + zero_samples++; + + if (pos == 8) + { + pos = 0; + dp++; + } + } + + if (row_info->bit_depth == 2) + { + if (((*dp << (pos+=2)) & 0xc0) == 0) + zero_samples++; + + if (pos == 8) + { + pos = 0; + dp++; + } + } + + if (row_info->bit_depth == 4) + { + if (((*dp << (pos+=4)) & 0xf0) == 0) + zero_samples++; + + if (pos == 8) + { + pos = 0; + dp++; + } + } + + if (row_info->bit_depth == 8) + if (*dp++ == 0) + zero_samples++; + + if (row_info->bit_depth == 16) + { + if ((*dp | *(dp+1)) == 0) + zero_samples++; + dp+=2; + } + } + } + else /* Other color types */ + { + png_uint_32 n, nstop; + int channel; + int color_channels = row_info->channels; + if (row_info->color_type > 3) + color_channels--; + + for (n = 0, nstop=row_info->width; nbit_depth == 8) + if (*dp++ == 0) + zero_samples++; + + if (row_info->bit_depth == 16) + { + if ((*dp | *(dp+1)) == 0) + zero_samples++; + + dp+=2; + } + } + if (row_info->color_type > 3) + { + dp++; + if (row_info->bit_depth == 16) + dp++; + } + } + } +} +#endif /* WRITE_USER_TRANSFORM */ + +#ifndef PNG_STDIO_SUPPORTED +/* START of code to validate stdio-free compilation */ +/* These copies of the default read/write functions come from pngrio.c and + * pngwio.c. They allow "don't include stdio" testing of the library. + * This is the function that does the actual reading of data. If you are + * not reading from a standard C stream, you should create a replacement + * read_data function and use it at run time with png_set_read_fn(), rather + * than changing the library. + */ + +#ifdef PNG_IO_STATE_SUPPORTED +void +pngtest_check_io_state(png_structp png_ptr, size_t data_length, + png_uint_32 io_op); +void +pngtest_check_io_state(png_structp png_ptr, size_t data_length, + png_uint_32 io_op) +{ + png_uint_32 io_state = png_get_io_state(png_ptr); + int err = 0; + + /* Check if the current operation (reading / writing) is as expected. */ + if ((io_state & PNG_IO_MASK_OP) != io_op) + png_error(png_ptr, "Incorrect operation in I/O state"); + + /* Check if the buffer size specific to the current location + * (file signature / header / data / crc) is as expected. + */ + switch (io_state & PNG_IO_MASK_LOC) + { + case PNG_IO_SIGNATURE: + if (data_length > 8) + err = 1; + break; + case PNG_IO_CHUNK_HDR: + if (data_length != 8) + err = 1; + break; + case PNG_IO_CHUNK_DATA: + break; /* no restrictions here */ + case PNG_IO_CHUNK_CRC: + if (data_length != 4) + err = 1; + break; + default: + err = 1; /* uninitialized */ + } + if (err != 0) + png_error(png_ptr, "Bad I/O state or buffer size"); +} +#endif + +static void PNGCBAPI +pngtest_read_data(png_structp png_ptr, png_bytep data, size_t length) +{ + size_t check = 0; + png_voidp io_ptr; + + /* fread() returns 0 on error, so it is OK to store this in a size_t + * instead of an int, which is what fread() actually returns. + */ + io_ptr = png_get_io_ptr(png_ptr); + if (io_ptr != NULL) + { + check = fread(data, 1, length, (png_FILE_p)io_ptr); + } + + if (check != length) + { + png_error(png_ptr, "Read Error"); + } + +#ifdef PNG_IO_STATE_SUPPORTED + pngtest_check_io_state(png_ptr, length, PNG_IO_READING); +#endif +} + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +static void PNGCBAPI +pngtest_flush(png_structp png_ptr) +{ + /* Do nothing; fflush() is said to be just a waste of energy. */ + PNG_UNUSED(png_ptr) /* Stifle compiler warning */ +} +#endif + +/* This is the function that does the actual writing of data. If you are + * not writing to a standard C stream, you should create a replacement + * write_data function and use it at run time with png_set_write_fn(), rather + * than changing the library. + */ +static void PNGCBAPI +pngtest_write_data(png_structp png_ptr, png_bytep data, size_t length) +{ + size_t check; + + check = fwrite(data, 1, length, (png_FILE_p)png_get_io_ptr(png_ptr)); + + if (check != length) + { + png_error(png_ptr, "Write Error"); + } + +#ifdef PNG_IO_STATE_SUPPORTED + pngtest_check_io_state(png_ptr, length, PNG_IO_WRITING); +#endif +} +#endif /* !STDIO */ + +/* This function is called when there is a warning, but the library thinks + * it can continue anyway. Replacement functions don't have to do anything + * here if you don't want to. In the default configuration, png_ptr is + * not used, but it is passed in case it may be useful. + */ +typedef struct +{ + const char *file_name; +} pngtest_error_parameters; + +static void PNGCBAPI +pngtest_warning(png_structp png_ptr, png_const_charp message) +{ + const char *name = "UNKNOWN (ERROR!)"; + pngtest_error_parameters *test = + (pngtest_error_parameters*)png_get_error_ptr(png_ptr); + + ++warning_count; + + if (test != NULL && test->file_name != NULL) + name = test->file_name; + + fprintf(STDERR, "\n%s: libpng warning: %s\n", name, message); +} + +/* This is the default error handling function. Note that replacements for + * this function MUST NOT RETURN, or the program will likely crash. This + * function is used by default, or if the program supplies NULL for the + * error function pointer in png_set_error_fn(). + */ +static void PNGCBAPI +pngtest_error(png_structp png_ptr, png_const_charp message) +{ + ++error_count; + + pngtest_warning(png_ptr, message); + /* We can return because png_error calls the default handler, which is + * actually OK in this case. + */ +} + +/* END of code to validate stdio-free compilation */ + +/* START of code to validate memory allocation and deallocation */ +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + +/* Allocate memory. For reasonable files, size should never exceed + * 64K. However, zlib may allocate more than 64K if you don't tell + * it not to. See zconf.h and png.h for more information. zlib does + * need to allocate exactly 64K, so whatever you call here must + * have the ability to do that. + * + * This piece of code can be compiled to validate max 64K allocations + * by setting MAXSEG_64K in zlib zconf.h *or* PNG_MAX_MALLOC_64K. + */ +typedef struct memory_information +{ + png_alloc_size_t size; + png_voidp pointer; + struct memory_information *next; +} memory_information; +typedef memory_information *memory_infop; + +static memory_infop pinformation = NULL; +static int current_allocation = 0; +static int maximum_allocation = 0; +static int total_allocation = 0; +static int num_allocations = 0; + +png_voidp PNGCBAPI png_debug_malloc PNGARG((png_structp png_ptr, + png_alloc_size_t size)); +void PNGCBAPI png_debug_free PNGARG((png_structp png_ptr, png_voidp ptr)); + +png_voidp +PNGCBAPI png_debug_malloc(png_structp png_ptr, png_alloc_size_t size) +{ + + /* png_malloc has already tested for NULL; png_create_struct calls + * png_debug_malloc directly, with png_ptr == NULL which is OK + */ + + if (size == 0) + return (NULL); + + /* This calls the library allocator twice, once to get the requested + buffer and once to get a new free list entry. */ + { + /* Disable malloc_fn and free_fn */ + memory_infop pinfo; + png_set_mem_fn(png_ptr, NULL, NULL, NULL); + pinfo = (memory_infop)png_malloc(png_ptr, + (sizeof *pinfo)); + pinfo->size = size; + current_allocation += size; + total_allocation += size; + num_allocations ++; + + if (current_allocation > maximum_allocation) + maximum_allocation = current_allocation; + + pinfo->pointer = png_malloc(png_ptr, size); + /* Restore malloc_fn and free_fn */ + + png_set_mem_fn(png_ptr, + NULL, png_debug_malloc, png_debug_free); + + if (size != 0 && pinfo->pointer == NULL) + { + current_allocation -= size; + total_allocation -= size; + png_error(png_ptr, + "out of memory in pngtest->png_debug_malloc"); + } + + pinfo->next = pinformation; + pinformation = pinfo; + /* Make sure the caller isn't assuming zeroed memory. */ + memset(pinfo->pointer, 0xdd, pinfo->size); + + if (verbose != 0) + printf("png_malloc %lu bytes at %p\n", (unsigned long)size, + pinfo->pointer); + + return (png_voidp)(pinfo->pointer); + } +} + +/* Free a pointer. It is removed from the list at the same time. */ +void PNGCBAPI +png_debug_free(png_structp png_ptr, png_voidp ptr) +{ + if (png_ptr == NULL) + fprintf(STDERR, "NULL pointer to png_debug_free.\n"); + + if (ptr == 0) + { +#if 0 /* This happens all the time. */ + fprintf(STDERR, "WARNING: freeing NULL pointer\n"); +#endif + return; + } + + /* Unlink the element from the list. */ + if (pinformation != NULL) + { + memory_infop *ppinfo = &pinformation; + + for (;;) + { + memory_infop pinfo = *ppinfo; + + if (pinfo->pointer == ptr) + { + *ppinfo = pinfo->next; + current_allocation -= pinfo->size; + if (current_allocation < 0) + fprintf(STDERR, "Duplicate free of memory\n"); + /* We must free the list element too, but first kill + the memory that is to be freed. */ + memset(ptr, 0x55, pinfo->size); + free(pinfo); + pinfo = NULL; + break; + } + + if (pinfo->next == NULL) + { + fprintf(STDERR, "Pointer %p not found\n", ptr); + break; + } + + ppinfo = &pinfo->next; + } + } + + /* Finally free the data. */ + if (verbose != 0) + printf("Freeing %p\n", ptr); + + if (ptr != NULL) + free(ptr); + ptr = NULL; +} +#endif /* USER_MEM && DEBUG */ +/* END of code to test memory allocation/deallocation */ + + +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED +/* Demonstration of user chunk support of the sTER and vpAg chunks */ + +/* (sTER is a public chunk not yet known by libpng. vpAg is a private +chunk used in ImageMagick to store "virtual page" size). */ + +static struct user_chunk_data +{ + png_const_infop info_ptr; + png_uint_32 vpAg_width, vpAg_height; + png_byte vpAg_units; + png_byte sTER_mode; + int location[2]; +} +user_chunk_data; + +/* Used for location and order; zero means nothing. */ +#define have_sTER 0x01 +#define have_vpAg 0x02 +#define before_PLTE 0x10 +#define before_IDAT 0x20 +#define after_IDAT 0x40 + +static void +init_callback_info(png_const_infop info_ptr) +{ + MEMZERO(user_chunk_data); + user_chunk_data.info_ptr = info_ptr; +} + +static int +set_location(png_structp png_ptr, struct user_chunk_data *data, int what) +{ + int location; + + if ((data->location[0] & what) != 0 || (data->location[1] & what) != 0) + return 0; /* already have one of these */ + + /* Find where we are (the code below zeroes info_ptr to indicate that the + * chunks before the first IDAT have been read.) + */ + if (data->info_ptr == NULL) /* after IDAT */ + location = what | after_IDAT; + + else if (png_get_valid(png_ptr, data->info_ptr, PNG_INFO_PLTE) != 0) + location = what | before_IDAT; + + else + location = what | before_PLTE; + + if (data->location[0] == 0) + data->location[0] = location; + + else + data->location[1] = location; + + return 1; /* handled */ +} + +static int PNGCBAPI +read_user_chunk_callback(png_struct *png_ptr, png_unknown_chunkp chunk) +{ + struct user_chunk_data *my_user_chunk_data = + (struct user_chunk_data*)png_get_user_chunk_ptr(png_ptr); + + if (my_user_chunk_data == NULL) + png_error(png_ptr, "lost user chunk pointer"); + + /* Return one of the following: + * return (-n); chunk had an error + * return (0); did not recognize + * return (n); success + * + * The unknown chunk structure contains the chunk data: + * png_byte name[5]; + * png_byte *data; + * size_t size; + * + * Note that libpng has already taken care of the CRC handling. + */ + + if (chunk->name[0] == 115 && chunk->name[1] == 84 && /* s T */ + chunk->name[2] == 69 && chunk->name[3] == 82) /* E R */ + { + /* Found sTER chunk */ + if (chunk->size != 1) + return (-1); /* Error return */ + + if (chunk->data[0] != 0 && chunk->data[0] != 1) + return (-1); /* Invalid mode */ + + if (set_location(png_ptr, my_user_chunk_data, have_sTER) != 0) + { + my_user_chunk_data->sTER_mode=chunk->data[0]; + return (1); + } + + else + return (0); /* duplicate sTER - give it to libpng */ + } + + if (chunk->name[0] != 118 || chunk->name[1] != 112 || /* v p */ + chunk->name[2] != 65 || chunk->name[3] != 103) /* A g */ + return (0); /* Did not recognize */ + + /* Found ImageMagick vpAg chunk */ + + if (chunk->size != 9) + return (-1); /* Error return */ + + if (set_location(png_ptr, my_user_chunk_data, have_vpAg) == 0) + return (0); /* duplicate vpAg */ + + my_user_chunk_data->vpAg_width = png_get_uint_31(png_ptr, chunk->data); + my_user_chunk_data->vpAg_height = png_get_uint_31(png_ptr, chunk->data + 4); + my_user_chunk_data->vpAg_units = chunk->data[8]; + + return (1); +} + +#ifdef PNG_WRITE_SUPPORTED +static void +write_sTER_chunk(png_structp write_ptr) +{ + png_byte sTER[5] = {115, 84, 69, 82, '\0'}; + + if (verbose != 0) + fprintf(STDERR, "\n stereo mode = %d\n", user_chunk_data.sTER_mode); + + png_write_chunk(write_ptr, sTER, &user_chunk_data.sTER_mode, 1); +} + +static void +write_vpAg_chunk(png_structp write_ptr) +{ + png_byte vpAg[5] = {118, 112, 65, 103, '\0'}; + + png_byte vpag_chunk_data[9]; + + if (verbose != 0) + fprintf(STDERR, " vpAg = %lu x %lu, units = %d\n", + (unsigned long)user_chunk_data.vpAg_width, + (unsigned long)user_chunk_data.vpAg_height, + user_chunk_data.vpAg_units); + + png_save_uint_32(vpag_chunk_data, user_chunk_data.vpAg_width); + png_save_uint_32(vpag_chunk_data + 4, user_chunk_data.vpAg_height); + vpag_chunk_data[8] = user_chunk_data.vpAg_units; + png_write_chunk(write_ptr, vpAg, vpag_chunk_data, 9); +} + +static void +write_chunks(png_structp write_ptr, int location) +{ + int i; + + /* Notice that this preserves the original chunk order, however chunks + * intercepted by the callback will be written *after* chunks passed to + * libpng. This will actually reverse a pair of sTER chunks or a pair of + * vpAg chunks, resulting in an error later. This is not worth worrying + * about - the chunks should not be duplicated! + */ + for (i=0; i<2; ++i) + { + if (user_chunk_data.location[i] == (location | have_sTER)) + write_sTER_chunk(write_ptr); + + else if (user_chunk_data.location[i] == (location | have_vpAg)) + write_vpAg_chunk(write_ptr); + } +} +#endif /* WRITE */ +#else /* !READ_USER_CHUNKS */ +# define write_chunks(pp,loc) ((void)0) +#endif +/* END of code to demonstrate user chunk support */ + +/* START of code to check that libpng has the required text support; this only + * checks for the write support because if read support is missing the chunk + * will simply not be reported back to pngtest. + */ +#ifdef PNG_TEXT_SUPPORTED +static void +pngtest_check_text_support(png_structp png_ptr, png_textp text_ptr, + int num_text) +{ + while (num_text > 0) + { + switch (text_ptr[--num_text].compression) + { + case PNG_TEXT_COMPRESSION_NONE: + break; + + case PNG_TEXT_COMPRESSION_zTXt: +# ifndef PNG_WRITE_zTXt_SUPPORTED + ++unsupported_chunks; + /* In libpng 1.7 this now does an app-error, so stop it: */ + text_ptr[num_text].compression = PNG_TEXT_COMPRESSION_NONE; +# endif + break; + + case PNG_ITXT_COMPRESSION_NONE: + case PNG_ITXT_COMPRESSION_zTXt: +# ifndef PNG_WRITE_iTXt_SUPPORTED + ++unsupported_chunks; + text_ptr[num_text].compression = PNG_TEXT_COMPRESSION_NONE; +# endif + break; + + default: + /* This is an error */ + png_error(png_ptr, "invalid text chunk compression field"); + break; + } + } +} +#endif +/* END of code to check that libpng has the required text support */ + +/* Test one file */ +static int +test_one_file(const char *inname, const char *outname) +{ + static png_FILE_p fpin; + static png_FILE_p fpout; /* "static" prevents setjmp corruption */ + pngtest_error_parameters error_parameters; + png_structp read_ptr; + png_infop read_info_ptr, end_info_ptr; +#ifdef PNG_WRITE_SUPPORTED + png_structp write_ptr; + png_infop write_info_ptr; + png_infop write_end_info_ptr; +#ifdef PNG_WRITE_FILTER_SUPPORTED + int interlace_preserved = 1; +#endif /* WRITE_FILTER */ +#else /* !WRITE */ + png_structp write_ptr = NULL; + png_infop write_info_ptr = NULL; + png_infop write_end_info_ptr = NULL; +#endif /* !WRITE */ + png_bytep row_buf; + png_uint_32 y; + png_uint_32 width, height; + volatile int num_passes; + int pass; + int bit_depth, color_type; + + row_buf = NULL; + error_parameters.file_name = inname; + + if ((fpin = fopen(inname, "rb")) == NULL) + { + fprintf(STDERR, "Could not find input file %s\n", inname); + return (1); + } + + if ((fpout = fopen(outname, "wb")) == NULL) + { + fprintf(STDERR, "Could not open output file %s\n", outname); + FCLOSE(fpin); + return (1); + } + + pngtest_debug("Allocating read and write structures"); +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + read_ptr = + png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, + NULL, NULL, NULL, png_debug_malloc, png_debug_free); +#else + read_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); +#endif + png_set_error_fn(read_ptr, &error_parameters, pngtest_error, + pngtest_warning); + +#ifdef PNG_WRITE_SUPPORTED +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + write_ptr = + png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, + NULL, NULL, NULL, png_debug_malloc, png_debug_free); +#else + write_ptr = + png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); +#endif + png_set_error_fn(write_ptr, &error_parameters, pngtest_error, + pngtest_warning); +#endif + pngtest_debug("Allocating read_info, write_info and end_info structures"); + read_info_ptr = png_create_info_struct(read_ptr); + end_info_ptr = png_create_info_struct(read_ptr); +#ifdef PNG_WRITE_SUPPORTED + write_info_ptr = png_create_info_struct(write_ptr); + write_end_info_ptr = png_create_info_struct(write_ptr); +#endif + +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED + init_callback_info(read_info_ptr); + png_set_read_user_chunk_fn(read_ptr, &user_chunk_data, + read_user_chunk_callback); +#endif + +#ifdef PNG_SETJMP_SUPPORTED + pngtest_debug("Setting jmpbuf for read struct"); + if (setjmp(png_jmpbuf(read_ptr))) + { + fprintf(STDERR, "%s -> %s: libpng read error\n", inname, outname); + png_free(read_ptr, row_buf); + row_buf = NULL; + if (verbose != 0) + fprintf(STDERR, " destroy read structs\n"); + png_destroy_read_struct(&read_ptr, &read_info_ptr, &end_info_ptr); +#ifdef PNG_WRITE_SUPPORTED + if (verbose != 0) + fprintf(STDERR, " destroy write structs\n"); + png_destroy_info_struct(write_ptr, &write_end_info_ptr); + png_destroy_write_struct(&write_ptr, &write_info_ptr); +#endif + FCLOSE(fpin); + FCLOSE(fpout); + return (1); + } + +#ifdef PNG_WRITE_SUPPORTED + pngtest_debug("Setting jmpbuf for write struct"); + + if (setjmp(png_jmpbuf(write_ptr))) + { + fprintf(STDERR, "%s -> %s: libpng write error\n", inname, outname); + png_free(read_ptr, row_buf); + row_buf = NULL; + if (verbose != 0) + fprintf(STDERR, " destroying read structs\n"); + png_destroy_read_struct(&read_ptr, &read_info_ptr, &end_info_ptr); + if (verbose != 0) + fprintf(STDERR, " destroying write structs\n"); + png_destroy_info_struct(write_ptr, &write_end_info_ptr); + png_destroy_write_struct(&write_ptr, &write_info_ptr); + FCLOSE(fpin); + FCLOSE(fpout); + return (1); + } +#endif +#endif + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED + if (strict != 0) + { + /* Treat png_benign_error() as errors on read */ + png_set_benign_errors(read_ptr, 0); + +# ifdef PNG_WRITE_SUPPORTED + /* Treat them as errors on write */ + png_set_benign_errors(write_ptr, 0); +# endif + + /* if strict is not set, then app warnings and errors are treated as + * warnings in release builds, but not in unstable builds; this can be + * changed with '--relaxed'. + */ + } + + else if (relaxed != 0) + { + /* Allow application (pngtest) errors and warnings to pass */ + png_set_benign_errors(read_ptr, 1); + + /* Turn off CRC checking while reading */ + png_set_crc_action(read_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + +#ifdef PNG_IGNORE_ADLER32 + /* Turn off ADLER32 checking while reading */ + png_set_option(read_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); +#endif + +# ifdef PNG_WRITE_SUPPORTED + png_set_benign_errors(write_ptr, 1); +# endif + + } +#endif /* BENIGN_ERRORS */ + + pngtest_debug("Initializing input and output streams"); +#ifdef PNG_STDIO_SUPPORTED + png_init_io(read_ptr, fpin); +# ifdef PNG_WRITE_SUPPORTED + png_init_io(write_ptr, fpout); +# endif +#else + png_set_read_fn(read_ptr, (png_voidp)fpin, pngtest_read_data); +# ifdef PNG_WRITE_SUPPORTED + png_set_write_fn(write_ptr, (png_voidp)fpout, pngtest_write_data, +# ifdef PNG_WRITE_FLUSH_SUPPORTED + pngtest_flush); +# else + NULL); +# endif +# endif +#endif + + if (status_dots_requested == 1) + { +#ifdef PNG_WRITE_SUPPORTED + png_set_write_status_fn(write_ptr, write_row_callback); +#endif + png_set_read_status_fn(read_ptr, read_row_callback); + } + + else + { +#ifdef PNG_WRITE_SUPPORTED + png_set_write_status_fn(write_ptr, NULL); +#endif + png_set_read_status_fn(read_ptr, NULL); + } + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + png_set_read_user_transform_fn(read_ptr, read_user_callback); +#endif +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED + zero_samples = 0; + png_set_write_user_transform_fn(write_ptr, count_zero_samples); +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + /* Preserve all the unknown chunks, if possible. If this is disabled then, + * even if the png_{get,set}_unknown_chunks stuff is enabled, we can't use + * libpng to *save* the unknown chunks on read (because we can't switch the + * save option on!) + * + * Notice that if SET_UNKNOWN_CHUNKS is *not* supported read will discard all + * unknown chunks and write will write them all. + */ +#ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED + png_set_keep_unknown_chunks(read_ptr, PNG_HANDLE_CHUNK_ALWAYS, + NULL, 0); +#endif +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, + NULL, 0); +#endif +#endif + + pngtest_debug("Reading info struct"); + png_read_info(read_ptr, read_info_ptr); + +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED + /* This is a bit of a hack; there is no obvious way in the callback function + * to determine that the chunks before the first IDAT have been read, so + * remove the info_ptr (which is only used to determine position relative to + * PLTE) here to indicate that we are after the IDAT. + */ + user_chunk_data.info_ptr = NULL; +#endif + + pngtest_debug("Transferring info struct"); + { + int interlace_type, compression_type, filter_type; + + if (png_get_IHDR(read_ptr, read_info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, &compression_type, &filter_type) != 0) + { + png_set_IHDR(write_ptr, write_info_ptr, width, height, bit_depth, + color_type, interlace_type, compression_type, filter_type); + /* num_passes may not be available below if interlace support is not + * provided by libpng for both read and write. + */ + switch (interlace_type) + { + case PNG_INTERLACE_NONE: + num_passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + num_passes = 7; + break; + + default: + png_error(read_ptr, "invalid interlace type"); + /*NOT REACHED*/ + } + } + + else + png_error(read_ptr, "png_get_IHDR failed"); + } +#ifdef PNG_FIXED_POINT_SUPPORTED +#ifdef PNG_cHRM_SUPPORTED + { + png_fixed_point white_x, white_y, red_x, red_y, green_x, green_y, blue_x, + blue_y; + + if (png_get_cHRM_fixed(read_ptr, read_info_ptr, &white_x, &white_y, + &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y) != 0) + { + png_set_cHRM_fixed(write_ptr, write_info_ptr, white_x, white_y, red_x, + red_y, green_x, green_y, blue_x, blue_y); + } + } +#endif +#ifdef PNG_gAMA_SUPPORTED + { + png_fixed_point gamma; + + if (png_get_gAMA_fixed(read_ptr, read_info_ptr, &gamma) != 0) + png_set_gAMA_fixed(write_ptr, write_info_ptr, gamma); + } +#endif +#else /* Use floating point versions */ +#ifdef PNG_FLOATING_POINT_SUPPORTED +#ifdef PNG_cHRM_SUPPORTED + { + double white_x, white_y, red_x, red_y, green_x, green_y, blue_x, + blue_y; + + if (png_get_cHRM(read_ptr, read_info_ptr, &white_x, &white_y, &red_x, + &red_y, &green_x, &green_y, &blue_x, &blue_y) != 0) + { + png_set_cHRM(write_ptr, write_info_ptr, white_x, white_y, red_x, + red_y, green_x, green_y, blue_x, blue_y); + } + } +#endif +#ifdef PNG_gAMA_SUPPORTED + { + double gamma; + + if (png_get_gAMA(read_ptr, read_info_ptr, &gamma) != 0) + png_set_gAMA(write_ptr, write_info_ptr, gamma); + } +#endif +#endif /* Floating point */ +#endif /* Fixed point */ +#ifdef PNG_iCCP_SUPPORTED + { + png_charp name; + png_bytep profile; + png_uint_32 proflen; + int compression_type; + + if (png_get_iCCP(read_ptr, read_info_ptr, &name, &compression_type, + &profile, &proflen) != 0) + { + png_set_iCCP(write_ptr, write_info_ptr, name, compression_type, + profile, proflen); + } + } +#endif +#ifdef PNG_sRGB_SUPPORTED + { + int intent; + + if (png_get_sRGB(read_ptr, read_info_ptr, &intent) != 0) + png_set_sRGB(write_ptr, write_info_ptr, intent); + } +#endif + { + png_colorp palette; + int num_palette; + + if (png_get_PLTE(read_ptr, read_info_ptr, &palette, &num_palette) != 0) + png_set_PLTE(write_ptr, write_info_ptr, palette, num_palette); + } +#ifdef PNG_bKGD_SUPPORTED + { + png_color_16p background; + + if (png_get_bKGD(read_ptr, read_info_ptr, &background) != 0) + { + png_set_bKGD(write_ptr, write_info_ptr, background); + } + } +#endif +#ifdef PNG_READ_eXIf_SUPPORTED + { + png_bytep exif=NULL; + png_uint_32 exif_length; + + if (png_get_eXIf_1(read_ptr, read_info_ptr, &exif_length, &exif) != 0) + { + if (exif_length > 1) + fprintf(STDERR," eXIf type %c%c, %lu bytes\n",exif[0],exif[1], + (unsigned long)exif_length); +# ifdef PNG_WRITE_eXIf_SUPPORTED + png_set_eXIf_1(write_ptr, write_info_ptr, exif_length, exif); +# endif + } + } +#endif +#ifdef PNG_hIST_SUPPORTED + { + png_uint_16p hist; + + if (png_get_hIST(read_ptr, read_info_ptr, &hist) != 0) + png_set_hIST(write_ptr, write_info_ptr, hist); + } +#endif +#ifdef PNG_oFFs_SUPPORTED + { + png_int_32 offset_x, offset_y; + int unit_type; + + if (png_get_oFFs(read_ptr, read_info_ptr, &offset_x, &offset_y, + &unit_type) != 0) + { + png_set_oFFs(write_ptr, write_info_ptr, offset_x, offset_y, unit_type); + } + } +#endif +#ifdef PNG_pCAL_SUPPORTED + { + png_charp purpose, units; + png_charpp params; + png_int_32 X0, X1; + int type, nparams; + + if (png_get_pCAL(read_ptr, read_info_ptr, &purpose, &X0, &X1, &type, + &nparams, &units, ¶ms) != 0) + { + png_set_pCAL(write_ptr, write_info_ptr, purpose, X0, X1, type, + nparams, units, params); + } + } +#endif +#ifdef PNG_pHYs_SUPPORTED + { + png_uint_32 res_x, res_y; + int unit_type; + + if (png_get_pHYs(read_ptr, read_info_ptr, &res_x, &res_y, + &unit_type) != 0) + png_set_pHYs(write_ptr, write_info_ptr, res_x, res_y, unit_type); + } +#endif +#ifdef PNG_sBIT_SUPPORTED + { + png_color_8p sig_bit; + + if (png_get_sBIT(read_ptr, read_info_ptr, &sig_bit) != 0) + png_set_sBIT(write_ptr, write_info_ptr, sig_bit); + } +#endif +#ifdef PNG_sCAL_SUPPORTED +#if defined(PNG_FLOATING_POINT_SUPPORTED) && \ + defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) + { + int unit; + double scal_width, scal_height; + + if (png_get_sCAL(read_ptr, read_info_ptr, &unit, &scal_width, + &scal_height) != 0) + { + png_set_sCAL(write_ptr, write_info_ptr, unit, scal_width, scal_height); + } + } +#else +#ifdef PNG_FIXED_POINT_SUPPORTED + { + int unit; + png_charp scal_width, scal_height; + + if (png_get_sCAL_s(read_ptr, read_info_ptr, &unit, &scal_width, + &scal_height) != 0) + { + png_set_sCAL_s(write_ptr, write_info_ptr, unit, scal_width, + scal_height); + } + } +#endif +#endif +#endif + +#ifdef PNG_sPLT_SUPPORTED + { + png_sPLT_tp entries; + + int num_entries = (int) png_get_sPLT(read_ptr, read_info_ptr, &entries); + if (num_entries) + { + png_set_sPLT(write_ptr, write_info_ptr, entries, num_entries); + } + } +#endif + +#ifdef PNG_TEXT_SUPPORTED + { + png_textp text_ptr; + int num_text; + + if (png_get_text(read_ptr, read_info_ptr, &text_ptr, &num_text) > 0) + { + pngtest_debug1("Handling %d iTXt/tEXt/zTXt chunks", num_text); + + pngtest_check_text_support(read_ptr, text_ptr, num_text); + + if (verbose != 0) + { + int i; + + fprintf(STDERR,"\n"); + for (i=0; igray > sample_max) || + (color_type == PNG_COLOR_TYPE_RGB && + ((int)trans_color->red > sample_max || + (int)trans_color->green > sample_max || + (int)trans_color->blue > sample_max)))) + png_set_tRNS(write_ptr, write_info_ptr, trans_alpha, num_trans, + trans_color); + } + } +#endif +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + { + png_unknown_chunkp unknowns; + int num_unknowns = png_get_unknown_chunks(read_ptr, read_info_ptr, + &unknowns); + + if (num_unknowns != 0) + { + png_set_unknown_chunks(write_ptr, write_info_ptr, unknowns, + num_unknowns); +#if PNG_LIBPNG_VER < 10600 + /* Copy the locations from the read_info_ptr. The automatically + * generated locations in write_end_info_ptr are wrong prior to 1.6.0 + * because they are reset from the write pointer (removed in 1.6.0). + */ + { + int i; + for (i = 0; i < num_unknowns; i++) + png_set_unknown_chunk_location(write_ptr, write_info_ptr, i, + unknowns[i].location); + } +#endif + } + } +#endif + +#ifdef PNG_WRITE_SUPPORTED + pngtest_debug("Writing info struct"); + + /* Write the info in two steps so that if we write the 'unknown' chunks here + * they go to the correct place. + */ + png_write_info_before_PLTE(write_ptr, write_info_ptr); + + write_chunks(write_ptr, before_PLTE); /* before PLTE */ + + png_write_info(write_ptr, write_info_ptr); + + write_chunks(write_ptr, before_IDAT); /* after PLTE */ + + png_write_info(write_ptr, write_end_info_ptr); + + write_chunks(write_ptr, after_IDAT); /* after IDAT */ + +#ifdef PNG_COMPRESSION_COMPAT + /* Test the 'compatibility' setting here, if it is available. */ + png_set_compression(write_ptr, PNG_COMPRESSION_COMPAT); +#endif +#endif + +#ifdef SINGLE_ROWBUF_ALLOC + pngtest_debug("Allocating row buffer..."); + row_buf = (png_bytep)png_malloc(read_ptr, + png_get_rowbytes(read_ptr, read_info_ptr)); + + pngtest_debug1("\t%p", row_buf); +#endif /* SINGLE_ROWBUF_ALLOC */ + pngtest_debug("Writing row data"); + +#if defined(PNG_READ_INTERLACING_SUPPORTED) &&\ + defined(PNG_WRITE_INTERLACING_SUPPORTED) + /* Both must be defined for libpng to be able to handle the interlace, + * otherwise it gets handled below by simply reading and writing the passes + * directly. + */ + if (png_set_interlace_handling(read_ptr) != num_passes) + png_error(write_ptr, + "png_set_interlace_handling(read): wrong pass count "); + if (png_set_interlace_handling(write_ptr) != num_passes) + png_error(write_ptr, + "png_set_interlace_handling(write): wrong pass count "); +#else /* png_set_interlace_handling not called on either read or write */ +# define calc_pass_height +#endif /* not using libpng interlace handling */ + +#ifdef PNGTEST_TIMING + t_stop = (float)clock(); + t_misc += (t_stop - t_start); + t_start = t_stop; +#endif + for (pass = 0; pass < num_passes; pass++) + { +# ifdef calc_pass_height + png_uint_32 pass_height; + + if (num_passes == 7) /* interlaced */ + { + if (PNG_PASS_COLS(width, pass) > 0) + pass_height = PNG_PASS_ROWS(height, pass); + + else + pass_height = 0; + } + + else /* not interlaced */ + pass_height = height; +# else +# define pass_height height +# endif + + pngtest_debug1("Writing row data for pass %d", pass); + for (y = 0; y < pass_height; y++) + { +#ifndef SINGLE_ROWBUF_ALLOC + pngtest_debug2("Allocating row buffer (pass %d, y = %u)...", pass, y); + + row_buf = (png_bytep)png_malloc(read_ptr, + png_get_rowbytes(read_ptr, read_info_ptr)); + + pngtest_debug2("\t%p (%lu bytes)", row_buf, + (unsigned long)png_get_rowbytes(read_ptr, read_info_ptr)); + +#endif /* !SINGLE_ROWBUF_ALLOC */ + png_read_rows(read_ptr, (png_bytepp)&row_buf, NULL, 1); + +#ifdef PNG_WRITE_SUPPORTED +#ifdef PNGTEST_TIMING + t_stop = (float)clock(); + t_decode += (t_stop - t_start); + t_start = t_stop; +#endif + png_write_rows(write_ptr, (png_bytepp)&row_buf, 1); +#ifdef PNGTEST_TIMING + t_stop = (float)clock(); + t_encode += (t_stop - t_start); + t_start = t_stop; +#endif +#endif /* WRITE */ + +#ifndef SINGLE_ROWBUF_ALLOC + pngtest_debug2("Freeing row buffer (pass %d, y = %u)", pass, y); + png_free(read_ptr, row_buf); + row_buf = NULL; +#endif /* !SINGLE_ROWBUF_ALLOC */ + } + } + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +# ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + png_free_data(read_ptr, read_info_ptr, PNG_FREE_UNKN, -1); +# endif +# ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + png_free_data(write_ptr, write_info_ptr, PNG_FREE_UNKN, -1); +# endif +#endif + + pngtest_debug("Reading and writing end_info data"); + + png_read_end(read_ptr, end_info_ptr); +#ifdef PNG_TEXT_SUPPORTED + { + png_textp text_ptr; + int num_text; + + if (png_get_text(read_ptr, end_info_ptr, &text_ptr, &num_text) > 0) + { + pngtest_debug1("Handling %d iTXt/tEXt/zTXt chunks", num_text); + + pngtest_check_text_support(read_ptr, text_ptr, num_text); + + if (verbose != 0) + { + int i; + + fprintf(STDERR,"\n"); + for (i=0; i 1) + fprintf(STDERR," eXIf type %c%c, %lu bytes\n",exif[0],exif[1], + (unsigned long)exif_length); +# ifdef PNG_WRITE_eXIf_SUPPORTED + png_set_eXIf_1(write_ptr, write_end_info_ptr, exif_length, exif); +# endif + } + } +#endif +#ifdef PNG_tIME_SUPPORTED + { + png_timep mod_time; + + if (png_get_tIME(read_ptr, end_info_ptr, &mod_time) != 0) + { + png_set_tIME(write_ptr, write_end_info_ptr, mod_time); +#ifdef PNG_TIME_RFC1123_SUPPORTED + if (png_convert_to_rfc1123_buffer(tIME_string, mod_time) != 0) + tIME_string[(sizeof tIME_string) - 1] = '\0'; + + else + { + strncpy(tIME_string, "*** invalid time ***", sizeof tIME_string); + tIME_string[(sizeof tIME_string)-1] = '\0'; + } + + tIME_chunk_present++; +#endif /* TIME_RFC1123 */ + } + } +#endif +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + { + png_unknown_chunkp unknowns; + int num_unknowns = png_get_unknown_chunks(read_ptr, end_info_ptr, + &unknowns); + + if (num_unknowns != 0) + { + png_set_unknown_chunks(write_ptr, write_end_info_ptr, unknowns, + num_unknowns); +#if PNG_LIBPNG_VER < 10600 + /* Copy the locations from the read_info_ptr. The automatically + * generated locations in write_end_info_ptr are wrong prior to 1.6.0 + * because they are reset from the write pointer (removed in 1.6.0). + */ + { + int i; + for (i = 0; i < num_unknowns; i++) + png_set_unknown_chunk_location(write_ptr, write_end_info_ptr, i, + unknowns[i].location); + } +#endif + } + } +#endif + +#ifdef PNG_WRITE_SUPPORTED +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED + /* Normally one would use Z_DEFAULT_STRATEGY for text compression. + * This is here just to make pngtest replicate the results from libpng + * versions prior to 1.5.4, and to test this new API. + */ + png_set_text_compression_strategy(write_ptr, Z_FILTERED); +#endif + + /* When the unknown vpAg/sTER chunks are written by pngtest the only way to + * do it is to write them *before* calling png_write_end. When unknown + * chunks are written by libpng, however, they are written just before IEND. + * There seems to be no way round this, however vpAg/sTER are not expected + * after IDAT. + */ + write_chunks(write_ptr, after_IDAT); + + png_write_end(write_ptr, write_end_info_ptr); +#endif + +#ifdef PNG_EASY_ACCESS_SUPPORTED + if (verbose != 0) + { + png_uint_32 iwidth, iheight; + iwidth = png_get_image_width(write_ptr, write_info_ptr); + iheight = png_get_image_height(write_ptr, write_info_ptr); + fprintf(STDERR, "\n Image width = %lu, height = %lu\n", + (unsigned long)iwidth, (unsigned long)iheight); + } +#endif + + pngtest_debug("Destroying data structs"); +#ifdef SINGLE_ROWBUF_ALLOC + pngtest_debug("destroying row_buf for read_ptr"); + png_free(read_ptr, row_buf); + row_buf = NULL; +#endif /* SINGLE_ROWBUF_ALLOC */ + pngtest_debug("destroying read_ptr, read_info_ptr, end_info_ptr"); + png_destroy_read_struct(&read_ptr, &read_info_ptr, &end_info_ptr); +#ifdef PNG_WRITE_SUPPORTED + pngtest_debug("destroying write_end_info_ptr"); + png_destroy_info_struct(write_ptr, &write_end_info_ptr); + pngtest_debug("destroying write_ptr, write_info_ptr"); + png_destroy_write_struct(&write_ptr, &write_info_ptr); +#endif + pngtest_debug("Destruction complete."); + + FCLOSE(fpin); + FCLOSE(fpout); + + /* Summarize any warnings or errors and in 'strict' mode fail the test. + * Unsupported chunks can result in warnings, in that case ignore the strict + * setting, otherwise fail the test on warnings as well as errors. + */ + if (error_count > 0) + { + /* We don't really expect to get here because of the setjmp handling + * above, but this is safe. + */ + fprintf(STDERR, "\n %s: %d libpng errors found (%d warnings)", + inname, error_count, warning_count); + + if (strict != 0) + return (1); + } + +# ifdef PNG_WRITE_SUPPORTED + /* If there is no write support nothing was written! */ + else if (unsupported_chunks > 0) + { + fprintf(STDERR, "\n %s: unsupported chunks (%d)%s", + inname, unsupported_chunks, strict ? ": IGNORED --strict!" : ""); + } +# endif + + else if (warning_count > 0) + { + fprintf(STDERR, "\n %s: %d libpng warnings found", + inname, warning_count); + + if (strict != 0) + return (1); + } + + pngtest_debug("Opening files for comparison"); + if ((fpin = fopen(inname, "rb")) == NULL) + { + fprintf(STDERR, "Could not find file %s\n", inname); + return (1); + } + + if ((fpout = fopen(outname, "rb")) == NULL) + { + fprintf(STDERR, "Could not find file %s\n", outname); + FCLOSE(fpin); + return (1); + } + +#if defined (PNG_WRITE_SUPPORTED) /* else nothing was written */ &&\ + defined (PNG_WRITE_FILTER_SUPPORTED) + if (interlace_preserved != 0) /* else the files will be changed */ + { + for (;;) + { + static int wrote_question = 0; + size_t num_in, num_out; + char inbuf[256], outbuf[256]; + + num_in = fread(inbuf, 1, sizeof inbuf, fpin); + num_out = fread(outbuf, 1, sizeof outbuf, fpout); + + if (num_in != num_out) + { + fprintf(STDERR, "\nFiles %s and %s are of a different size\n", + inname, outname); + + if (wrote_question == 0 && unsupported_chunks == 0) + { + fprintf(STDERR, + " Was %s written with the same maximum IDAT" + " chunk size (%d bytes),", + inname, PNG_ZBUF_SIZE); + fprintf(STDERR, + "\n filtering heuristic (libpng default), compression"); + fprintf(STDERR, + " level (zlib default),\n and zlib version (%s)?\n\n", + ZLIB_VERSION); + wrote_question = 1; + } + + FCLOSE(fpin); + FCLOSE(fpout); + + if (strict != 0 && unsupported_chunks == 0) + return (1); + + else + return (0); + } + + if (num_in == 0) + break; + + if (memcmp(inbuf, outbuf, num_in)) + { + fprintf(STDERR, "\nFiles %s and %s are different\n", inname, + outname); + + if (wrote_question == 0 && unsupported_chunks == 0) + { + fprintf(STDERR, + " Was %s written with the same maximum" + " IDAT chunk size (%d bytes),", + inname, PNG_ZBUF_SIZE); + fprintf(STDERR, + "\n filtering heuristic (libpng default), compression"); + fprintf(STDERR, + " level (zlib default),\n and zlib version (%s)?\n\n", + ZLIB_VERSION); + wrote_question = 1; + } + + FCLOSE(fpin); + FCLOSE(fpout); + + /* NOTE: the unsupported_chunks escape is permitted here because + * unsupported text chunk compression will result in the compression + * mode being changed (to NONE) yet, in the test case, the result + * can be exactly the same size! + */ + if (strict != 0 && unsupported_chunks == 0) + return (1); + + else + return (0); + } + } + } +#endif /* WRITE && WRITE_FILTER */ + + FCLOSE(fpin); + FCLOSE(fpout); + + return (0); +} + +/* Input and output filenames */ +#ifdef RISCOS +static const char *inname = "pngtest/png"; +static const char *outname = "pngout/png"; +#else +static const char *inname = "pngtest.png"; +static const char *outname = "pngout.png"; +#endif + +int +main(int argc, char *argv[]) +{ + int multiple = 0; + int ierror = 0; + + png_structp dummy_ptr; + + fprintf(STDERR, "\n Testing libpng version %s\n", PNG_LIBPNG_VER_STRING); + fprintf(STDERR, " with zlib version %s\n", ZLIB_VERSION); + fprintf(STDERR, "%s", png_get_copyright(NULL)); + /* Show the version of libpng used in building the library */ + fprintf(STDERR, " library (%lu):%s", + (unsigned long)png_access_version_number(), + png_get_header_version(NULL)); + + /* Show the version of libpng used in building the application */ + fprintf(STDERR, " pngtest (%lu):%s", (unsigned long)PNG_LIBPNG_VER, + PNG_HEADER_VERSION_STRING); + + /* Do some consistency checking on the memory allocation settings, I'm + * not sure this matters, but it is nice to know, the first of these + * tests should be impossible because of the way the macros are set + * in pngconf.h + */ +#if defined(MAXSEG_64K) && !defined(PNG_MAX_MALLOC_64K) + fprintf(STDERR, " NOTE: Zlib compiled for max 64k, libpng not\n"); +#endif + /* I think the following can happen. */ +#if !defined(MAXSEG_64K) && defined(PNG_MAX_MALLOC_64K) + fprintf(STDERR, " NOTE: libpng compiled for max 64k, zlib not\n"); +#endif + + if (strcmp(png_libpng_ver, PNG_LIBPNG_VER_STRING)) + { + fprintf(STDERR, + "Warning: versions are different between png.h and png.c\n"); + fprintf(STDERR, " png.h version: %s\n", PNG_LIBPNG_VER_STRING); + fprintf(STDERR, " png.c version: %s\n\n", png_libpng_ver); + ++ierror; + } + + if (argc > 1) + { + if (strcmp(argv[1], "-m") == 0) + { + multiple = 1; + status_dots_requested = 0; + } + + else if (strcmp(argv[1], "-mv") == 0 || + strcmp(argv[1], "-vm") == 0 ) + { + multiple = 1; + verbose = 1; + status_dots_requested = 1; + } + + else if (strcmp(argv[1], "-v") == 0) + { + verbose = 1; + status_dots_requested = 1; + inname = argv[2]; + } + + else if (strcmp(argv[1], "--strict") == 0) + { + status_dots_requested = 0; + verbose = 1; + inname = argv[2]; + strict++; + relaxed = 0; + multiple=1; + } + + else if (strcmp(argv[1], "--relaxed") == 0) + { + status_dots_requested = 0; + verbose = 1; + inname = argv[2]; + strict = 0; + relaxed++; + multiple=1; + } + else if (strcmp(argv[1], "--xfail") == 0) + { + status_dots_requested = 0; + verbose = 1; + inname = argv[2]; + strict = 0; + xfail++; + relaxed++; + multiple=1; + } + + else + { + inname = argv[1]; + status_dots_requested = 0; + } + } + + if (multiple == 0 && argc == 3 + verbose) + outname = argv[2 + verbose]; + + if ((multiple == 0 && argc > 3 + verbose) || + (multiple != 0 && argc < 2)) + { + fprintf(STDERR, + "usage: %s [infile.png] [outfile.png]\n\t%s -m {infile.png}\n", + argv[0], argv[0]); + fprintf(STDERR, + " reads/writes one PNG file (without -m) or multiple files (-m)\n"); + fprintf(STDERR, + " with -m %s is used as a temporary file\n", outname); + exit(1); + } + + if (multiple != 0) + { + int i; +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + int allocation_now = current_allocation; +#endif + for (i=2; i 0 + fprintf(STDERR, "\n"); +#endif + kerror = test_one_file(argv[i], outname); + if (kerror == 0) + { +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED + fprintf(STDERR, "\n PASS (%lu zero samples)\n", + (unsigned long)zero_samples); +#else + fprintf(STDERR, " PASS\n"); +#endif +#ifdef PNG_TIME_RFC1123_SUPPORTED + if (tIME_chunk_present != 0) + fprintf(STDERR, " tIME = %s\n", tIME_string); + + tIME_chunk_present = 0; +#endif /* TIME_RFC1123 */ + } + + else + { + if (xfail) + fprintf(STDERR, " XFAIL\n"); + else + { + fprintf(STDERR, " FAIL\n"); + ierror += kerror; + } + } +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + if (allocation_now != current_allocation) + fprintf(STDERR, "MEMORY ERROR: %d bytes lost\n", + current_allocation - allocation_now); + + if (current_allocation != 0) + { + memory_infop pinfo = pinformation; + + fprintf(STDERR, "MEMORY ERROR: %d bytes still allocated\n", + current_allocation); + + while (pinfo != NULL) + { + fprintf(STDERR, " %lu bytes at %p\n", + (unsigned long)pinfo->size, + pinfo->pointer); + pinfo = pinfo->next; + } + } +#endif + } +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + fprintf(STDERR, " Current memory allocation: %10d bytes\n", + current_allocation); + fprintf(STDERR, " Maximum memory allocation: %10d bytes\n", + maximum_allocation); + fprintf(STDERR, " Total memory allocation: %10d bytes\n", + total_allocation); + fprintf(STDERR, " Number of allocations: %10d\n", + num_allocations); +#endif + } + + else + { + int i; + for (i = 0; i<3; ++i) + { + int kerror; +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + int allocation_now = current_allocation; +#endif + if (i == 1) + status_dots_requested = 1; + + else if (verbose == 0) + status_dots_requested = 0; + + if (i == 0 || verbose == 1 || ierror != 0) + { + fprintf(STDERR, "\n Testing %s:", inname); +#if PNG_DEBUG > 0 + fprintf(STDERR, "\n"); +#endif + } + + kerror = test_one_file(inname, outname); + + if (kerror == 0) + { + if (verbose == 1 || i == 2) + { +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED + fprintf(STDERR, "\n PASS (%lu zero samples)\n", + (unsigned long)zero_samples); +#else + fprintf(STDERR, " PASS\n"); +#endif +#ifdef PNG_TIME_RFC1123_SUPPORTED + if (tIME_chunk_present != 0) + fprintf(STDERR, " tIME = %s\n", tIME_string); +#endif /* TIME_RFC1123 */ + } + } + + else + { + if (verbose == 0 && i != 2) + { + fprintf(STDERR, "\n Testing %s:", inname); +#if PNG_DEBUG > 0 + fprintf(STDERR, "\n"); +#endif + } + + if (xfail) + fprintf(STDERR, " XFAIL\n"); + else + { + fprintf(STDERR, " FAIL\n"); + ierror += kerror; + } + } +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + if (allocation_now != current_allocation) + fprintf(STDERR, "MEMORY ERROR: %d bytes lost\n", + current_allocation - allocation_now); + + if (current_allocation != 0) + { + memory_infop pinfo = pinformation; + + fprintf(STDERR, "MEMORY ERROR: %d bytes still allocated\n", + current_allocation); + + while (pinfo != NULL) + { + fprintf(STDERR, " %lu bytes at %p\n", + (unsigned long)pinfo->size, pinfo->pointer); + pinfo = pinfo->next; + } + } +#endif + } +#if defined(PNG_USER_MEM_SUPPORTED) && PNG_DEBUG + fprintf(STDERR, " Current memory allocation: %10d bytes\n", + current_allocation); + fprintf(STDERR, " Maximum memory allocation: %10d bytes\n", + maximum_allocation); + fprintf(STDERR, " Total memory allocation: %10d bytes\n", + total_allocation); + fprintf(STDERR, " Number of allocations: %10d\n", + num_allocations); +#endif + } + +#ifdef PNGTEST_TIMING + t_stop = (float)clock(); + t_misc += (t_stop - t_start); + t_start = t_stop; + fprintf(STDERR, " CPU time used = %.3f seconds", + (t_misc+t_decode+t_encode)/(float)CLOCKS_PER_SEC); + fprintf(STDERR, " (decoding %.3f,\n", + t_decode/(float)CLOCKS_PER_SEC); + fprintf(STDERR, " encoding %.3f ,", + t_encode/(float)CLOCKS_PER_SEC); + fprintf(STDERR, " other %.3f seconds)\n\n", + t_misc/(float)CLOCKS_PER_SEC); +#endif + + if (ierror == 0) + fprintf(STDERR, " libpng passes test\n"); + + else + fprintf(STDERR, " libpng FAILS test\n"); + + dummy_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + fprintf(STDERR, " Default limits:\n"); + fprintf(STDERR, " width_max = %lu\n", + (unsigned long) png_get_user_width_max(dummy_ptr)); + fprintf(STDERR, " height_max = %lu\n", + (unsigned long) png_get_user_height_max(dummy_ptr)); + if (png_get_chunk_cache_max(dummy_ptr) == 0) + fprintf(STDERR, " cache_max = unlimited\n"); + else + fprintf(STDERR, " cache_max = %lu\n", + (unsigned long) png_get_chunk_cache_max(dummy_ptr)); + if (png_get_chunk_malloc_max(dummy_ptr) == 0) + fprintf(STDERR, " malloc_max = unlimited\n"); + else + fprintf(STDERR, " malloc_max = %lu\n", + (unsigned long) png_get_chunk_malloc_max(dummy_ptr)); + png_destroy_read_struct(&dummy_ptr, NULL, NULL); + + return (int)(ierror != 0); +} +#else +int +main(void) +{ + fprintf(STDERR, + " test ignored because libpng was not built with read support\n"); + /* And skip this test */ + return PNG_LIBPNG_VER < 10600 ? 0 : 77; +} +#endif + +/* Generate a compiler error if there is an old png.h in the search path. */ +typedef png_libpng_version_1_6_37 Your_png_h_is_not_version_1_6_37; diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngtrans.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngtrans.c new file mode 100644 index 0000000..1100f46 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngtrans.c @@ -0,0 +1,864 @@ + +/* pngtrans.c - transforms the data in a row (used by both readers and writers) + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +/* Turn on BGR-to-RGB mapping */ +void PNGAPI +png_set_bgr(png_structrp png_ptr) +{ + png_debug(1, "in png_set_bgr"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_BGR; +} +#endif + +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +/* Turn on 16-bit byte swapping */ +void PNGAPI +png_set_swap(png_structrp png_ptr) +{ + png_debug(1, "in png_set_swap"); + + if (png_ptr == NULL) + return; + + if (png_ptr->bit_depth == 16) + png_ptr->transformations |= PNG_SWAP_BYTES; +} +#endif + +#if defined(PNG_READ_PACK_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED) +/* Turn on pixel packing */ +void PNGAPI +png_set_packing(png_structrp png_ptr) +{ + png_debug(1, "in png_set_packing"); + + if (png_ptr == NULL) + return; + + if (png_ptr->bit_depth < 8) + { + png_ptr->transformations |= PNG_PACK; +# ifdef PNG_WRITE_SUPPORTED + png_ptr->usr_bit_depth = 8; +# endif + } +} +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED)||defined(PNG_WRITE_PACKSWAP_SUPPORTED) +/* Turn on packed pixel swapping */ +void PNGAPI +png_set_packswap(png_structrp png_ptr) +{ + png_debug(1, "in png_set_packswap"); + + if (png_ptr == NULL) + return; + + if (png_ptr->bit_depth < 8) + png_ptr->transformations |= PNG_PACKSWAP; +} +#endif + +#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED) +void PNGAPI +png_set_shift(png_structrp png_ptr, png_const_color_8p true_bits) +{ + png_debug(1, "in png_set_shift"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_SHIFT; + png_ptr->shift = *true_bits; +} +#endif + +#if defined(PNG_READ_INTERLACING_SUPPORTED) || \ + defined(PNG_WRITE_INTERLACING_SUPPORTED) +int PNGAPI +png_set_interlace_handling(png_structrp png_ptr) +{ + png_debug(1, "in png_set_interlace handling"); + + if (png_ptr != 0 && png_ptr->interlaced != 0) + { + png_ptr->transformations |= PNG_INTERLACE; + return (7); + } + + return (1); +} +#endif + +#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED) +/* Add a filler byte on read, or remove a filler or alpha byte on write. + * The filler type has changed in v0.95 to allow future 2-byte fillers + * for 48-bit input data, as well as to avoid problems with some compilers + * that don't like bytes as parameters. + */ +void PNGAPI +png_set_filler(png_structrp png_ptr, png_uint_32 filler, int filler_loc) +{ + png_debug(1, "in png_set_filler"); + + if (png_ptr == NULL) + return; + + /* In libpng 1.6 it is possible to determine whether this is a read or write + * operation and therefore to do more checking here for a valid call. + */ + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) + { +# ifdef PNG_READ_FILLER_SUPPORTED + /* On read png_set_filler is always valid, regardless of the base PNG + * format, because other transformations can give a format where the + * filler code can execute (basically an 8 or 16-bit component RGB or G + * format.) + * + * NOTE: usr_channels is not used by the read code! (This has led to + * confusion in the past.) The filler is only used in the read code. + */ + png_ptr->filler = (png_uint_16)filler; +# else + png_app_error(png_ptr, "png_set_filler not supported on read"); + PNG_UNUSED(filler) /* not used in the write case */ + return; +# endif + } + + else /* write */ + { +# ifdef PNG_WRITE_FILLER_SUPPORTED + /* On write the usr_channels parameter must be set correctly at the + * start to record the number of channels in the app-supplied data. + */ + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_RGB: + png_ptr->usr_channels = 4; + break; + + case PNG_COLOR_TYPE_GRAY: + if (png_ptr->bit_depth >= 8) + { + png_ptr->usr_channels = 2; + break; + } + + else + { + /* There simply isn't any code in libpng to strip out bits + * from bytes when the components are less than a byte in + * size! + */ + png_app_error(png_ptr, + "png_set_filler is invalid for" + " low bit depth gray output"); + return; + } + + default: + png_app_error(png_ptr, + "png_set_filler: inappropriate color type"); + return; + } +# else + png_app_error(png_ptr, "png_set_filler not supported on write"); + return; +# endif + } + + /* Here on success - libpng supports the operation, set the transformation + * and the flag to say where the filler channel is. + */ + png_ptr->transformations |= PNG_FILLER; + + if (filler_loc == PNG_FILLER_AFTER) + png_ptr->flags |= PNG_FLAG_FILLER_AFTER; + + else + png_ptr->flags &= ~PNG_FLAG_FILLER_AFTER; +} + +/* Added to libpng-1.2.7 */ +void PNGAPI +png_set_add_alpha(png_structrp png_ptr, png_uint_32 filler, int filler_loc) +{ + png_debug(1, "in png_set_add_alpha"); + + if (png_ptr == NULL) + return; + + png_set_filler(png_ptr, filler, filler_loc); + /* The above may fail to do anything. */ + if ((png_ptr->transformations & PNG_FILLER) != 0) + png_ptr->transformations |= PNG_ADD_ALPHA; +} + +#endif + +#if defined(PNG_READ_SWAP_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED) +void PNGAPI +png_set_swap_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_swap_alpha"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_SWAP_ALPHA; +} +#endif + +#if defined(PNG_READ_INVERT_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED) +void PNGAPI +png_set_invert_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_invert_alpha"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_INVERT_ALPHA; +} +#endif + +#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED) +void PNGAPI +png_set_invert_mono(png_structrp png_ptr) +{ + png_debug(1, "in png_set_invert_mono"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_INVERT_MONO; +} + +/* Invert monochrome grayscale data */ +void /* PRIVATE */ +png_do_invert(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_invert"); + + /* This test removed from libpng version 1.0.13 and 1.2.0: + * if (row_info->bit_depth == 1 && + */ + if (row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + png_bytep rp = row; + size_t i; + size_t istop = row_info->rowbytes; + + for (i = 0; i < istop; i++) + { + *rp = (png_byte)(~(*rp)); + rp++; + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA && + row_info->bit_depth == 8) + { + png_bytep rp = row; + size_t i; + size_t istop = row_info->rowbytes; + + for (i = 0; i < istop; i += 2) + { + *rp = (png_byte)(~(*rp)); + rp += 2; + } + } + +#ifdef PNG_16BIT_SUPPORTED + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA && + row_info->bit_depth == 16) + { + png_bytep rp = row; + size_t i; + size_t istop = row_info->rowbytes; + + for (i = 0; i < istop; i += 4) + { + *rp = (png_byte)(~(*rp)); + *(rp + 1) = (png_byte)(~(*(rp + 1))); + rp += 4; + } + } +#endif +} +#endif + +#ifdef PNG_16BIT_SUPPORTED +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +/* Swaps byte order on 16-bit depth images */ +void /* PRIVATE */ +png_do_swap(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_swap"); + + if (row_info->bit_depth == 16) + { + png_bytep rp = row; + png_uint_32 i; + png_uint_32 istop= row_info->width * row_info->channels; + + for (i = 0; i < istop; i++, rp += 2) + { +#ifdef PNG_BUILTIN_BSWAP16_SUPPORTED + /* Feature added to libpng-1.6.11 for testing purposes, not + * enabled by default. + */ + *(png_uint_16*)rp = __builtin_bswap16(*(png_uint_16*)rp); +#else + png_byte t = *rp; + *rp = *(rp + 1); + *(rp + 1) = t; +#endif + } + } +} +#endif +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED)||defined(PNG_WRITE_PACKSWAP_SUPPORTED) +static const png_byte onebppswaptable[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, + 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, + 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, + 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, + 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, + 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, + 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, + 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, + 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, + 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, + 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, + 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF +}; + +static const png_byte twobppswaptable[256] = { + 0x00, 0x40, 0x80, 0xC0, 0x10, 0x50, 0x90, 0xD0, + 0x20, 0x60, 0xA0, 0xE0, 0x30, 0x70, 0xB0, 0xF0, + 0x04, 0x44, 0x84, 0xC4, 0x14, 0x54, 0x94, 0xD4, + 0x24, 0x64, 0xA4, 0xE4, 0x34, 0x74, 0xB4, 0xF4, + 0x08, 0x48, 0x88, 0xC8, 0x18, 0x58, 0x98, 0xD8, + 0x28, 0x68, 0xA8, 0xE8, 0x38, 0x78, 0xB8, 0xF8, + 0x0C, 0x4C, 0x8C, 0xCC, 0x1C, 0x5C, 0x9C, 0xDC, + 0x2C, 0x6C, 0xAC, 0xEC, 0x3C, 0x7C, 0xBC, 0xFC, + 0x01, 0x41, 0x81, 0xC1, 0x11, 0x51, 0x91, 0xD1, + 0x21, 0x61, 0xA1, 0xE1, 0x31, 0x71, 0xB1, 0xF1, + 0x05, 0x45, 0x85, 0xC5, 0x15, 0x55, 0x95, 0xD5, + 0x25, 0x65, 0xA5, 0xE5, 0x35, 0x75, 0xB5, 0xF5, + 0x09, 0x49, 0x89, 0xC9, 0x19, 0x59, 0x99, 0xD9, + 0x29, 0x69, 0xA9, 0xE9, 0x39, 0x79, 0xB9, 0xF9, + 0x0D, 0x4D, 0x8D, 0xCD, 0x1D, 0x5D, 0x9D, 0xDD, + 0x2D, 0x6D, 0xAD, 0xED, 0x3D, 0x7D, 0xBD, 0xFD, + 0x02, 0x42, 0x82, 0xC2, 0x12, 0x52, 0x92, 0xD2, + 0x22, 0x62, 0xA2, 0xE2, 0x32, 0x72, 0xB2, 0xF2, + 0x06, 0x46, 0x86, 0xC6, 0x16, 0x56, 0x96, 0xD6, + 0x26, 0x66, 0xA6, 0xE6, 0x36, 0x76, 0xB6, 0xF6, + 0x0A, 0x4A, 0x8A, 0xCA, 0x1A, 0x5A, 0x9A, 0xDA, + 0x2A, 0x6A, 0xAA, 0xEA, 0x3A, 0x7A, 0xBA, 0xFA, + 0x0E, 0x4E, 0x8E, 0xCE, 0x1E, 0x5E, 0x9E, 0xDE, + 0x2E, 0x6E, 0xAE, 0xEE, 0x3E, 0x7E, 0xBE, 0xFE, + 0x03, 0x43, 0x83, 0xC3, 0x13, 0x53, 0x93, 0xD3, + 0x23, 0x63, 0xA3, 0xE3, 0x33, 0x73, 0xB3, 0xF3, + 0x07, 0x47, 0x87, 0xC7, 0x17, 0x57, 0x97, 0xD7, + 0x27, 0x67, 0xA7, 0xE7, 0x37, 0x77, 0xB7, 0xF7, + 0x0B, 0x4B, 0x8B, 0xCB, 0x1B, 0x5B, 0x9B, 0xDB, + 0x2B, 0x6B, 0xAB, 0xEB, 0x3B, 0x7B, 0xBB, 0xFB, + 0x0F, 0x4F, 0x8F, 0xCF, 0x1F, 0x5F, 0x9F, 0xDF, + 0x2F, 0x6F, 0xAF, 0xEF, 0x3F, 0x7F, 0xBF, 0xFF +}; + +static const png_byte fourbppswaptable[256] = { + 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, + 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, + 0x01, 0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71, + 0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1, + 0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72, + 0x82, 0x92, 0xA2, 0xB2, 0xC2, 0xD2, 0xE2, 0xF2, + 0x03, 0x13, 0x23, 0x33, 0x43, 0x53, 0x63, 0x73, + 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3, + 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, + 0x84, 0x94, 0xA4, 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, + 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, 0x75, + 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, + 0x06, 0x16, 0x26, 0x36, 0x46, 0x56, 0x66, 0x76, + 0x86, 0x96, 0xA6, 0xB6, 0xC6, 0xD6, 0xE6, 0xF6, + 0x07, 0x17, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, + 0x87, 0x97, 0xA7, 0xB7, 0xC7, 0xD7, 0xE7, 0xF7, + 0x08, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78, + 0x88, 0x98, 0xA8, 0xB8, 0xC8, 0xD8, 0xE8, 0xF8, + 0x09, 0x19, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79, + 0x89, 0x99, 0xA9, 0xB9, 0xC9, 0xD9, 0xE9, 0xF9, + 0x0A, 0x1A, 0x2A, 0x3A, 0x4A, 0x5A, 0x6A, 0x7A, + 0x8A, 0x9A, 0xAA, 0xBA, 0xCA, 0xDA, 0xEA, 0xFA, + 0x0B, 0x1B, 0x2B, 0x3B, 0x4B, 0x5B, 0x6B, 0x7B, + 0x8B, 0x9B, 0xAB, 0xBB, 0xCB, 0xDB, 0xEB, 0xFB, + 0x0C, 0x1C, 0x2C, 0x3C, 0x4C, 0x5C, 0x6C, 0x7C, + 0x8C, 0x9C, 0xAC, 0xBC, 0xCC, 0xDC, 0xEC, 0xFC, + 0x0D, 0x1D, 0x2D, 0x3D, 0x4D, 0x5D, 0x6D, 0x7D, + 0x8D, 0x9D, 0xAD, 0xBD, 0xCD, 0xDD, 0xED, 0xFD, + 0x0E, 0x1E, 0x2E, 0x3E, 0x4E, 0x5E, 0x6E, 0x7E, + 0x8E, 0x9E, 0xAE, 0xBE, 0xCE, 0xDE, 0xEE, 0xFE, + 0x0F, 0x1F, 0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x7F, + 0x8F, 0x9F, 0xAF, 0xBF, 0xCF, 0xDF, 0xEF, 0xFF +}; + +/* Swaps pixel packing order within bytes */ +void /* PRIVATE */ +png_do_packswap(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_packswap"); + + if (row_info->bit_depth < 8) + { + png_bytep rp; + png_const_bytep end, table; + + end = row + row_info->rowbytes; + + if (row_info->bit_depth == 1) + table = onebppswaptable; + + else if (row_info->bit_depth == 2) + table = twobppswaptable; + + else if (row_info->bit_depth == 4) + table = fourbppswaptable; + + else + return; + + for (rp = row; rp < end; rp++) + *rp = table[*rp]; + } +} +#endif /* PACKSWAP || WRITE_PACKSWAP */ + +#if defined(PNG_WRITE_FILLER_SUPPORTED) || \ + defined(PNG_READ_STRIP_ALPHA_SUPPORTED) +/* Remove a channel - this used to be 'png_do_strip_filler' but it used a + * somewhat weird combination of flags to determine what to do. All the calls + * to png_do_strip_filler are changed in 1.5.2 to call this instead with the + * correct arguments. + * + * The routine isn't general - the channel must be the channel at the start or + * end (not in the middle) of each pixel. + */ +void /* PRIVATE */ +png_do_strip_channel(png_row_infop row_info, png_bytep row, int at_start) +{ + png_bytep sp = row; /* source pointer */ + png_bytep dp = row; /* destination pointer */ + png_bytep ep = row + row_info->rowbytes; /* One beyond end of row */ + + /* At the start sp will point to the first byte to copy and dp to where + * it is copied to. ep always points just beyond the end of the row, so + * the loop simply copies (channels-1) channels until sp reaches ep. + * + * at_start: 0 -- convert AG, XG, ARGB, XRGB, AAGG, XXGG, etc. + * nonzero -- convert GA, GX, RGBA, RGBX, GGAA, RRGGBBXX, etc. + */ + + /* GA, GX, XG cases */ + if (row_info->channels == 2) + { + if (row_info->bit_depth == 8) + { + if (at_start != 0) /* Skip initial filler */ + ++sp; + else /* Skip initial channel and, for sp, the filler */ + { + sp += 2; ++dp; + } + + /* For a 1 pixel wide image there is nothing to do */ + while (sp < ep) + { + *dp++ = *sp; sp += 2; + } + + row_info->pixel_depth = 8; + } + + else if (row_info->bit_depth == 16) + { + if (at_start != 0) /* Skip initial filler */ + sp += 2; + else /* Skip initial channel and, for sp, the filler */ + { + sp += 4; dp += 2; + } + + while (sp < ep) + { + *dp++ = *sp++; *dp++ = *sp; sp += 3; + } + + row_info->pixel_depth = 16; + } + + else + return; /* bad bit depth */ + + row_info->channels = 1; + + /* Finally fix the color type if it records an alpha channel */ + if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + row_info->color_type = PNG_COLOR_TYPE_GRAY; + } + + /* RGBA, RGBX, XRGB cases */ + else if (row_info->channels == 4) + { + if (row_info->bit_depth == 8) + { + if (at_start != 0) /* Skip initial filler */ + ++sp; + else /* Skip initial channels and, for sp, the filler */ + { + sp += 4; dp += 3; + } + + /* Note that the loop adds 3 to dp and 4 to sp each time. */ + while (sp < ep) + { + *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp; sp += 2; + } + + row_info->pixel_depth = 24; + } + + else if (row_info->bit_depth == 16) + { + if (at_start != 0) /* Skip initial filler */ + sp += 2; + else /* Skip initial channels and, for sp, the filler */ + { + sp += 8; dp += 6; + } + + while (sp < ep) + { + /* Copy 6 bytes, skip 2 */ + *dp++ = *sp++; *dp++ = *sp++; + *dp++ = *sp++; *dp++ = *sp++; + *dp++ = *sp++; *dp++ = *sp; sp += 3; + } + + row_info->pixel_depth = 48; + } + + else + return; /* bad bit depth */ + + row_info->channels = 3; + + /* Finally fix the color type if it records an alpha channel */ + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + row_info->color_type = PNG_COLOR_TYPE_RGB; + } + + else + return; /* The filler channel has gone already */ + + /* Fix the rowbytes value. */ + row_info->rowbytes = (size_t)(dp-row); +} +#endif + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +/* Swaps red and blue bytes within a pixel */ +void /* PRIVATE */ +png_do_bgr(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_bgr"); + + if ((row_info->color_type & PNG_COLOR_MASK_COLOR) != 0) + { + png_uint_32 row_width = row_info->width; + if (row_info->bit_depth == 8) + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 3) + { + png_byte save = *rp; + *rp = *(rp + 2); + *(rp + 2) = save; + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 4) + { + png_byte save = *rp; + *rp = *(rp + 2); + *(rp + 2) = save; + } + } + } + +#ifdef PNG_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 6) + { + png_byte save = *rp; + *rp = *(rp + 4); + *(rp + 4) = save; + save = *(rp + 1); + *(rp + 1) = *(rp + 5); + *(rp + 5) = save; + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 8) + { + png_byte save = *rp; + *rp = *(rp + 4); + *(rp + 4) = save; + save = *(rp + 1); + *(rp + 1) = *(rp + 5); + *(rp + 5) = save; + } + } + } +#endif + } +} +#endif /* READ_BGR || WRITE_BGR */ + +#if defined(PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED) || \ + defined(PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED) +/* Added at libpng-1.5.10 */ +void /* PRIVATE */ +png_do_check_palette_indexes(png_structrp png_ptr, png_row_infop row_info) +{ + if (png_ptr->num_palette < (1 << row_info->bit_depth) && + png_ptr->num_palette > 0) /* num_palette can be 0 in MNG files */ + { + /* Calculations moved outside switch in an attempt to stop different + * compiler warnings. 'padding' is in *bits* within the last byte, it is + * an 'int' because pixel_depth becomes an 'int' in the expression below, + * and this calculation is used because it avoids warnings that other + * forms produced on either GCC or MSVC. + */ + int padding = PNG_PADBITS(row_info->pixel_depth, row_info->width); + png_bytep rp = png_ptr->row_buf + row_info->rowbytes - 1; + + switch (row_info->bit_depth) + { + case 1: + { + /* in this case, all bytes must be 0 so we don't need + * to unpack the pixels except for the rightmost one. + */ + for (; rp > png_ptr->row_buf; rp--) + { + if ((*rp >> padding) != 0) + png_ptr->num_palette_max = 1; + padding = 0; + } + + break; + } + + case 2: + { + for (; rp > png_ptr->row_buf; rp--) + { + int i = ((*rp >> padding) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 2) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 4) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 6) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + padding = 0; + } + + break; + } + + case 4: + { + for (; rp > png_ptr->row_buf; rp--) + { + int i = ((*rp >> padding) & 0x0f); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 4) & 0x0f); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + padding = 0; + } + + break; + } + + case 8: + { + for (; rp > png_ptr->row_buf; rp--) + { + if (*rp > png_ptr->num_palette_max) + png_ptr->num_palette_max = (int) *rp; + } + + break; + } + + default: + break; + } + } +} +#endif /* CHECK_FOR_INVALID_INDEX */ + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +void PNGAPI +png_set_user_transform_info(png_structrp png_ptr, png_voidp + user_transform_ptr, int user_transform_depth, int user_transform_channels) +{ + png_debug(1, "in png_set_user_transform_info"); + + if (png_ptr == NULL) + return; + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0 && + (png_ptr->flags & PNG_FLAG_ROW_INIT) != 0) + { + png_app_error(png_ptr, + "info change after png_start_read_image or png_read_update_info"); + return; + } +#endif + + png_ptr->user_transform_ptr = user_transform_ptr; + png_ptr->user_transform_depth = (png_byte)user_transform_depth; + png_ptr->user_transform_channels = (png_byte)user_transform_channels; +} +#endif + +/* This function returns a pointer to the user_transform_ptr associated with + * the user transform functions. The application should free any memory + * associated with this pointer before png_write_destroy and png_read_destroy + * are called. + */ +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +png_voidp PNGAPI +png_get_user_transform_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return (NULL); + + return png_ptr->user_transform_ptr; +} +#endif + +#ifdef PNG_USER_TRANSFORM_INFO_SUPPORTED +png_uint_32 PNGAPI +png_get_current_row_number(png_const_structrp png_ptr) +{ + /* See the comments in png.h - this is the sub-image row when reading an + * interlaced image. + */ + if (png_ptr != NULL) + return png_ptr->row_number; + + return PNG_UINT_32_MAX; /* help the app not to fail silently */ +} + +png_byte PNGAPI +png_get_current_pass_number(png_const_structrp png_ptr) +{ + if (png_ptr != NULL) + return png_ptr->pass; + return 8; /* invalid */ +} +#endif /* USER_TRANSFORM_INFO */ +#endif /* READ_USER_TRANSFORM || WRITE_USER_TRANSFORM */ +#endif /* READ || WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwio.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwio.c new file mode 100644 index 0000000..10e919d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwio.c @@ -0,0 +1,168 @@ + +/* pngwio.c - functions for data output + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2014,2016,2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all output. Users who need + * special handling are expected to write functions that have the same + * arguments as these and perform similar functions, but that possibly + * use different output methods. Note that you shouldn't change these + * functions, but rather write replacement functions and then change + * them at run time with png_set_write_fn(...). + */ + +#include "pngpriv.h" + +#ifdef PNG_WRITE_SUPPORTED + +/* Write the data to whatever output you are using. The default routine + * writes to a file pointer. Note that this routine sometimes gets called + * with very small lengths, so you should implement some kind of simple + * buffering if you are using unbuffered writes. This should never be asked + * to write more than 64K on a 16-bit machine. + */ + +void /* PRIVATE */ +png_write_data(png_structrp png_ptr, png_const_bytep data, size_t length) +{ + /* NOTE: write_data_fn must not change the buffer! */ + if (png_ptr->write_data_fn != NULL ) + (*(png_ptr->write_data_fn))(png_ptr, png_constcast(png_bytep,data), + length); + + else + png_error(png_ptr, "Call to NULL write function"); +} + +#ifdef PNG_STDIO_SUPPORTED +/* This is the function that does the actual writing of data. If you are + * not writing to a standard C stream, you should create a replacement + * write_data function and use it at run time with png_set_write_fn(), rather + * than changing the library. + */ +void PNGCBAPI +png_default_write_data(png_structp png_ptr, png_bytep data, size_t length) +{ + size_t check; + + if (png_ptr == NULL) + return; + + check = fwrite(data, 1, length, (png_FILE_p)(png_ptr->io_ptr)); + + if (check != length) + png_error(png_ptr, "Write Error"); +} +#endif + +/* This function is called to output any data pending writing (normally + * to disk). After png_flush is called, there should be no data pending + * writing in any buffers. + */ +#ifdef PNG_WRITE_FLUSH_SUPPORTED +void /* PRIVATE */ +png_flush(png_structrp png_ptr) +{ + if (png_ptr->output_flush_fn != NULL) + (*(png_ptr->output_flush_fn))(png_ptr); +} + +# ifdef PNG_STDIO_SUPPORTED +void PNGCBAPI +png_default_flush(png_structp png_ptr) +{ + png_FILE_p io_ptr; + + if (png_ptr == NULL) + return; + + io_ptr = png_voidcast(png_FILE_p, (png_ptr->io_ptr)); + fflush(io_ptr); +} +# endif +#endif + +/* This function allows the application to supply new output functions for + * libpng if standard C streams aren't being used. + * + * This function takes as its arguments: + * png_ptr - pointer to a png output data structure + * io_ptr - pointer to user supplied structure containing info about + * the output functions. May be NULL. + * write_data_fn - pointer to a new output function that takes as its + * arguments a pointer to a png_struct, a pointer to + * data to be written, and a 32-bit unsigned int that is + * the number of bytes to be written. The new write + * function should call png_error(png_ptr, "Error msg") + * to exit and output any fatal error messages. May be + * NULL, in which case libpng's default function will + * be used. + * flush_data_fn - pointer to a new flush function that takes as its + * arguments a pointer to a png_struct. After a call to + * the flush function, there should be no data in any buffers + * or pending transmission. If the output method doesn't do + * any buffering of output, a function prototype must still be + * supplied although it doesn't have to do anything. If + * PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile + * time, output_flush_fn will be ignored, although it must be + * supplied for compatibility. May be NULL, in which case + * libpng's default function will be used, if + * PNG_WRITE_FLUSH_SUPPORTED is defined. This is not + * a good idea if io_ptr does not point to a standard + * *FILE structure. + */ +void PNGAPI +png_set_write_fn(png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->io_ptr = io_ptr; + +#ifdef PNG_STDIO_SUPPORTED + if (write_data_fn != NULL) + png_ptr->write_data_fn = write_data_fn; + + else + png_ptr->write_data_fn = png_default_write_data; +#else + png_ptr->write_data_fn = write_data_fn; +#endif + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +# ifdef PNG_STDIO_SUPPORTED + + if (output_flush_fn != NULL) + png_ptr->output_flush_fn = output_flush_fn; + + else + png_ptr->output_flush_fn = png_default_flush; + +# else + png_ptr->output_flush_fn = output_flush_fn; +# endif +#else + PNG_UNUSED(output_flush_fn) +#endif /* WRITE_FLUSH */ + +#ifdef PNG_READ_SUPPORTED + /* It is an error to read while writing a png file */ + if (png_ptr->read_data_fn != NULL) + { + png_ptr->read_data_fn = NULL; + + png_warning(png_ptr, + "Can't set both read_data_fn and write_data_fn in the" + " same structure"); + } +#endif +} +#endif /* WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwrite.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwrite.c new file mode 100644 index 0000000..59377a4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwrite.c @@ -0,0 +1,2395 @@ + +/* pngwrite.c - general routines to write a PNG file + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" +#ifdef PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED +# include +#endif /* SIMPLIFIED_WRITE_STDIO */ + +#ifdef PNG_WRITE_SUPPORTED + +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +/* Write out all the unknown chunks for the current given location */ +static void +write_unknown_chunks(png_structrp png_ptr, png_const_inforp info_ptr, + unsigned int where) +{ + if (info_ptr->unknown_chunks_num != 0) + { + png_const_unknown_chunkp up; + + png_debug(5, "writing extra chunks"); + + for (up = info_ptr->unknown_chunks; + up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; + ++up) + if ((up->location & where) != 0) + { + /* If per-chunk unknown chunk handling is enabled use it, otherwise + * just write the chunks the application has set. + */ +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + int keep = png_handle_as_unknown(png_ptr, up->name); + + /* NOTE: this code is radically different from the read side in the + * matter of handling an ancillary unknown chunk. In the read side + * the default behavior is to discard it, in the code below the default + * behavior is to write it. Critical chunks are, however, only + * written if explicitly listed or if the default is set to write all + * unknown chunks. + * + * The default handling is also slightly weird - it is not possible to + * stop the writing of all unsafe-to-copy chunks! + * + * TODO: REVIEW: this would seem to be a bug. + */ + if (keep != PNG_HANDLE_CHUNK_NEVER && + ((up->name[3] & 0x20) /* safe-to-copy overrides everything */ || + keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_AS_DEFAULT && + png_ptr->unknown_default == PNG_HANDLE_CHUNK_ALWAYS))) +#endif + { + /* TODO: review, what is wrong with a zero length unknown chunk? */ + if (up->size == 0) + png_warning(png_ptr, "Writing zero-length unknown chunk"); + + png_write_chunk(png_ptr, up->name, up->data, up->size); + } + } + } +} +#endif /* WRITE_UNKNOWN_CHUNKS */ + +/* Writes all the PNG information. This is the suggested way to use the + * library. If you have a new chunk to add, make a function to write it, + * and put it in the correct location here. If you want the chunk written + * after the image data, put it in png_write_end(). I strongly encourage + * you to supply a PNG_INFO_ flag, and check info_ptr->valid before writing + * the chunk, as that will keep the code from breaking if you want to just + * write a plain PNG file. If you have long comments, I suggest writing + * them in png_write_end(), and compressing them. + */ +void PNGAPI +png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) +{ + png_debug(1, "in png_write_info_before_PLTE"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if ((png_ptr->mode & PNG_WROTE_INFO_BEFORE_PLTE) == 0) + { + /* Write PNG signature */ + png_write_sig(png_ptr); + +#ifdef PNG_MNG_FEATURES_SUPPORTED + if ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) != 0 && \ + png_ptr->mng_features_permitted != 0) + { + png_warning(png_ptr, + "MNG features are not allowed in a PNG datastream"); + png_ptr->mng_features_permitted = 0; + } +#endif + + /* Write IHDR information. */ + png_write_IHDR(png_ptr, info_ptr->width, info_ptr->height, + info_ptr->bit_depth, info_ptr->color_type, info_ptr->compression_type, + info_ptr->filter_type, +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + info_ptr->interlace_type +#else + 0 +#endif + ); + + /* The rest of these check to see if the valid field has the appropriate + * flag set, and if it does, writes the chunk. + * + * 1.6.0: COLORSPACE support controls the writing of these chunks too, and + * the chunks will be written if the WRITE routine is there and + * information * is available in the COLORSPACE. (See + * png_colorspace_sync_info in png.c for where the valid flags get set.) + * + * Under certain circumstances the colorspace can be invalidated without + * syncing the info_struct 'valid' flags; this happens if libpng detects + * an error and calls png_error while the color space is being set, yet + * the application continues writing the PNG. So check the 'invalid' + * flag here too. + */ +#ifdef PNG_GAMMA_SUPPORTED +# ifdef PNG_WRITE_gAMA_SUPPORTED + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 && + (info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_gAMA) != 0 && + (info_ptr->valid & PNG_INFO_gAMA) != 0) + png_write_gAMA_fixed(png_ptr, info_ptr->colorspace.gamma); +# endif +#endif + +#ifdef PNG_COLORSPACE_SUPPORTED + /* Write only one of sRGB or an ICC profile. If a profile was supplied + * and it matches one of the known sRGB ones issue a warning. + */ +# ifdef PNG_WRITE_iCCP_SUPPORTED + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 && + (info_ptr->valid & PNG_INFO_iCCP) != 0) + { +# ifdef PNG_WRITE_sRGB_SUPPORTED + if ((info_ptr->valid & PNG_INFO_sRGB) != 0) + png_app_warning(png_ptr, + "profile matches sRGB but writing iCCP instead"); +# endif + + png_write_iCCP(png_ptr, info_ptr->iccp_name, + info_ptr->iccp_profile); + } +# ifdef PNG_WRITE_sRGB_SUPPORTED + else +# endif +# endif + +# ifdef PNG_WRITE_sRGB_SUPPORTED + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 && + (info_ptr->valid & PNG_INFO_sRGB) != 0) + png_write_sRGB(png_ptr, info_ptr->colorspace.rendering_intent); +# endif /* WRITE_sRGB */ +#endif /* COLORSPACE */ + +#ifdef PNG_WRITE_sBIT_SUPPORTED + if ((info_ptr->valid & PNG_INFO_sBIT) != 0) + png_write_sBIT(png_ptr, &(info_ptr->sig_bit), info_ptr->color_type); +#endif + +#ifdef PNG_COLORSPACE_SUPPORTED +# ifdef PNG_WRITE_cHRM_SUPPORTED + if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 && + (info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_cHRM) != 0 && + (info_ptr->valid & PNG_INFO_cHRM) != 0) + png_write_cHRM_fixed(png_ptr, &info_ptr->colorspace.end_points_xy); +# endif +#endif + +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_IHDR); +#endif + + png_ptr->mode |= PNG_WROTE_INFO_BEFORE_PLTE; + } +} + +void PNGAPI +png_write_info(png_structrp png_ptr, png_const_inforp info_ptr) +{ +#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED) + int i; +#endif + + png_debug(1, "in png_write_info"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + png_write_info_before_PLTE(png_ptr, info_ptr); + + if ((info_ptr->valid & PNG_INFO_PLTE) != 0) + png_write_PLTE(png_ptr, info_ptr->palette, + (png_uint_32)info_ptr->num_palette); + + else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + png_error(png_ptr, "Valid palette required for paletted images"); + +#ifdef PNG_WRITE_tRNS_SUPPORTED + if ((info_ptr->valid & PNG_INFO_tRNS) !=0) + { +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED + /* Invert the alpha channel (in tRNS) */ + if ((png_ptr->transformations & PNG_INVERT_ALPHA) != 0 && + info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + int j, jend; + + jend = info_ptr->num_trans; + if (jend > PNG_MAX_PALETTE_LENGTH) + jend = PNG_MAX_PALETTE_LENGTH; + + for (j = 0; jtrans_alpha[j] = + (png_byte)(255 - info_ptr->trans_alpha[j]); + } +#endif + png_write_tRNS(png_ptr, info_ptr->trans_alpha, &(info_ptr->trans_color), + info_ptr->num_trans, info_ptr->color_type); + } +#endif +#ifdef PNG_WRITE_bKGD_SUPPORTED + if ((info_ptr->valid & PNG_INFO_bKGD) != 0) + png_write_bKGD(png_ptr, &(info_ptr->background), info_ptr->color_type); +#endif + +#ifdef PNG_WRITE_eXIf_SUPPORTED + if ((info_ptr->valid & PNG_INFO_eXIf) != 0) + png_write_eXIf(png_ptr, info_ptr->exif, info_ptr->num_exif); +#endif + +#ifdef PNG_WRITE_hIST_SUPPORTED + if ((info_ptr->valid & PNG_INFO_hIST) != 0) + png_write_hIST(png_ptr, info_ptr->hist, info_ptr->num_palette); +#endif + +#ifdef PNG_WRITE_oFFs_SUPPORTED + if ((info_ptr->valid & PNG_INFO_oFFs) != 0) + png_write_oFFs(png_ptr, info_ptr->x_offset, info_ptr->y_offset, + info_ptr->offset_unit_type); +#endif + +#ifdef PNG_WRITE_pCAL_SUPPORTED + if ((info_ptr->valid & PNG_INFO_pCAL) != 0) + png_write_pCAL(png_ptr, info_ptr->pcal_purpose, info_ptr->pcal_X0, + info_ptr->pcal_X1, info_ptr->pcal_type, info_ptr->pcal_nparams, + info_ptr->pcal_units, info_ptr->pcal_params); +#endif + +#ifdef PNG_WRITE_sCAL_SUPPORTED + if ((info_ptr->valid & PNG_INFO_sCAL) != 0) + png_write_sCAL_s(png_ptr, (int)info_ptr->scal_unit, + info_ptr->scal_s_width, info_ptr->scal_s_height); +#endif /* sCAL */ + +#ifdef PNG_WRITE_pHYs_SUPPORTED + if ((info_ptr->valid & PNG_INFO_pHYs) != 0) + png_write_pHYs(png_ptr, info_ptr->x_pixels_per_unit, + info_ptr->y_pixels_per_unit, info_ptr->phys_unit_type); +#endif /* pHYs */ + +#ifdef PNG_WRITE_tIME_SUPPORTED + if ((info_ptr->valid & PNG_INFO_tIME) != 0) + { + png_write_tIME(png_ptr, &(info_ptr->mod_time)); + png_ptr->mode |= PNG_WROTE_tIME; + } +#endif /* tIME */ + +#ifdef PNG_WRITE_sPLT_SUPPORTED + if ((info_ptr->valid & PNG_INFO_sPLT) != 0) + for (i = 0; i < (int)info_ptr->splt_palettes_num; i++) + png_write_sPLT(png_ptr, info_ptr->splt_palettes + i); +#endif /* sPLT */ + +#ifdef PNG_WRITE_TEXT_SUPPORTED + /* Check to see if we need to write text chunks */ + for (i = 0; i < info_ptr->num_text; i++) + { + png_debug2(2, "Writing header text chunk %d, type %d", i, + info_ptr->text[i].compression); + /* An internationalized chunk? */ + if (info_ptr->text[i].compression > 0) + { +#ifdef PNG_WRITE_iTXt_SUPPORTED + /* Write international chunk */ + png_write_iTXt(png_ptr, + info_ptr->text[i].compression, + info_ptr->text[i].key, + info_ptr->text[i].lang, + info_ptr->text[i].lang_key, + info_ptr->text[i].text); + /* Mark this chunk as written */ + if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; + else + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; +#else + png_warning(png_ptr, "Unable to write international text"); +#endif + } + + /* If we want a compressed text chunk */ + else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_zTXt) + { +#ifdef PNG_WRITE_zTXt_SUPPORTED + /* Write compressed chunk */ + png_write_zTXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, info_ptr->text[i].compression); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; +#else + png_warning(png_ptr, "Unable to write compressed text"); +#endif + } + + else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) + { +#ifdef PNG_WRITE_tEXt_SUPPORTED + /* Write uncompressed chunk */ + png_write_tEXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, + 0); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; +#else + /* Can't get here */ + png_warning(png_ptr, "Unable to write uncompressed text"); +#endif + } + } +#endif /* tEXt */ + +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_PLTE); +#endif +} + +/* Writes the end of the PNG file. If you don't want to write comments or + * time information, you can pass NULL for info. If you already wrote these + * in png_write_info(), do not write them again here. If you have long + * comments, I suggest writing them here, and compressing them. + */ +void PNGAPI +png_write_end(png_structrp png_ptr, png_inforp info_ptr) +{ + png_debug(1, "in png_write_end"); + + if (png_ptr == NULL) + return; + + if ((png_ptr->mode & PNG_HAVE_IDAT) == 0) + png_error(png_ptr, "No IDATs written into file"); + +#ifdef PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED + if (png_ptr->num_palette_max > png_ptr->num_palette) + png_benign_error(png_ptr, "Wrote palette index exceeding num_palette"); +#endif + + /* See if user wants us to write information chunks */ + if (info_ptr != NULL) + { +#ifdef PNG_WRITE_TEXT_SUPPORTED + int i; /* local index variable */ +#endif +#ifdef PNG_WRITE_tIME_SUPPORTED + /* Check to see if user has supplied a time chunk */ + if ((info_ptr->valid & PNG_INFO_tIME) != 0 && + (png_ptr->mode & PNG_WROTE_tIME) == 0) + png_write_tIME(png_ptr, &(info_ptr->mod_time)); + +#endif +#ifdef PNG_WRITE_TEXT_SUPPORTED + /* Loop through comment chunks */ + for (i = 0; i < info_ptr->num_text; i++) + { + png_debug2(2, "Writing trailer text chunk %d, type %d", i, + info_ptr->text[i].compression); + /* An internationalized chunk? */ + if (info_ptr->text[i].compression > 0) + { +#ifdef PNG_WRITE_iTXt_SUPPORTED + /* Write international chunk */ + png_write_iTXt(png_ptr, + info_ptr->text[i].compression, + info_ptr->text[i].key, + info_ptr->text[i].lang, + info_ptr->text[i].lang_key, + info_ptr->text[i].text); + /* Mark this chunk as written */ + if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; + else + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; +#else + png_warning(png_ptr, "Unable to write international text"); +#endif + } + + else if (info_ptr->text[i].compression >= PNG_TEXT_COMPRESSION_zTXt) + { +#ifdef PNG_WRITE_zTXt_SUPPORTED + /* Write compressed chunk */ + png_write_zTXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, info_ptr->text[i].compression); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; +#else + png_warning(png_ptr, "Unable to write compressed text"); +#endif + } + + else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) + { +#ifdef PNG_WRITE_tEXt_SUPPORTED + /* Write uncompressed chunk */ + png_write_tEXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, 0); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; +#else + png_warning(png_ptr, "Unable to write uncompressed text"); +#endif + } + } +#endif + +#ifdef PNG_WRITE_eXIf_SUPPORTED + if ((info_ptr->valid & PNG_INFO_eXIf) != 0) + png_write_eXIf(png_ptr, info_ptr->exif, info_ptr->num_exif); +#endif + +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + write_unknown_chunks(png_ptr, info_ptr, PNG_AFTER_IDAT); +#endif + } + + png_ptr->mode |= PNG_AFTER_IDAT; + + /* Write end of PNG file */ + png_write_IEND(png_ptr); + + /* This flush, added in libpng-1.0.8, removed from libpng-1.0.9beta03, + * and restored again in libpng-1.2.30, may cause some applications that + * do not set png_ptr->output_flush_fn to crash. If your application + * experiences a problem, please try building libpng with + * PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED defined, and report the event to + * png-mng-implement at lists.sf.net . + */ +#ifdef PNG_WRITE_FLUSH_SUPPORTED +# ifdef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED + png_flush(png_ptr); +# endif +#endif +} + +#ifdef PNG_CONVERT_tIME_SUPPORTED +void PNGAPI +png_convert_from_struct_tm(png_timep ptime, const struct tm * ttime) +{ + png_debug(1, "in png_convert_from_struct_tm"); + + ptime->year = (png_uint_16)(1900 + ttime->tm_year); + ptime->month = (png_byte)(ttime->tm_mon + 1); + ptime->day = (png_byte)ttime->tm_mday; + ptime->hour = (png_byte)ttime->tm_hour; + ptime->minute = (png_byte)ttime->tm_min; + ptime->second = (png_byte)ttime->tm_sec; +} + +void PNGAPI +png_convert_from_time_t(png_timep ptime, time_t ttime) +{ + struct tm *tbuf; + + png_debug(1, "in png_convert_from_time_t"); + + tbuf = gmtime(&ttime); + png_convert_from_struct_tm(ptime, tbuf); +} +#endif + +/* Initialize png_ptr structure, and allocate any memory needed */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_write_struct,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn),PNG_ALLOCATED) +{ +#ifndef PNG_USER_MEM_SUPPORTED + png_structrp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, NULL, NULL, NULL); +#else + return png_create_write_struct_2(user_png_ver, error_ptr, error_fn, + warn_fn, NULL, NULL, NULL); +} + +/* Alternate initialize png_ptr structure, and allocate any memory needed */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr, + png_malloc_ptr malloc_fn, png_free_ptr free_fn),PNG_ALLOCATED) +{ + png_structrp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, mem_ptr, malloc_fn, free_fn); +#endif /* USER_MEM */ + if (png_ptr != NULL) + { + /* Set the zlib control values to defaults; they can be overridden by the + * application after the struct has been created. + */ + png_ptr->zbuffer_size = PNG_ZBUF_SIZE; + + /* The 'zlib_strategy' setting is irrelevant because png_default_claim in + * pngwutil.c defaults it according to whether or not filters will be + * used, and ignores this setting. + */ + png_ptr->zlib_strategy = PNG_Z_DEFAULT_STRATEGY; + png_ptr->zlib_level = PNG_Z_DEFAULT_COMPRESSION; + png_ptr->zlib_mem_level = 8; + png_ptr->zlib_window_bits = 15; + png_ptr->zlib_method = 8; + +#ifdef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED + png_ptr->zlib_text_strategy = PNG_TEXT_Z_DEFAULT_STRATEGY; + png_ptr->zlib_text_level = PNG_TEXT_Z_DEFAULT_COMPRESSION; + png_ptr->zlib_text_mem_level = 8; + png_ptr->zlib_text_window_bits = 15; + png_ptr->zlib_text_method = 8; +#endif /* WRITE_COMPRESSED_TEXT */ + + /* This is a highly dubious configuration option; by default it is off, + * but it may be appropriate for private builds that are testing + * extensions not conformant to the current specification, or of + * applications that must not fail to write at all costs! + */ +#ifdef PNG_BENIGN_WRITE_ERRORS_SUPPORTED + /* In stable builds only warn if an application error can be completely + * handled. + */ + png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN; +#endif + + /* App warnings are warnings in release (or release candidate) builds but + * are errors during development. + */ +#if PNG_RELEASE_BUILD + png_ptr->flags |= PNG_FLAG_APP_WARNINGS_WARN; +#endif + + /* TODO: delay this, it can be done in png_init_io() (if the app doesn't + * do it itself) avoiding setting the default function if it is not + * required. + */ + png_set_write_fn(png_ptr, NULL, NULL, NULL); + } + + return png_ptr; +} + + +/* Write a few rows of image data. If the image is interlaced, + * either you will have to write the 7 sub images, or, if you + * have called png_set_interlace_handling(), you will have to + * "write" the image seven times. + */ +void PNGAPI +png_write_rows(png_structrp png_ptr, png_bytepp row, + png_uint_32 num_rows) +{ + png_uint_32 i; /* row counter */ + png_bytepp rp; /* row pointer */ + + png_debug(1, "in png_write_rows"); + + if (png_ptr == NULL) + return; + + /* Loop through the rows */ + for (i = 0, rp = row; i < num_rows; i++, rp++) + { + png_write_row(png_ptr, *rp); + } +} + +/* Write the image. You only need to call this function once, even + * if you are writing an interlaced image. + */ +void PNGAPI +png_write_image(png_structrp png_ptr, png_bytepp image) +{ + png_uint_32 i; /* row index */ + int pass, num_pass; /* pass variables */ + png_bytepp rp; /* points to current row */ + + if (png_ptr == NULL) + return; + + png_debug(1, "in png_write_image"); + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Initialize interlace handling. If image is not interlaced, + * this will set pass to 1 + */ + num_pass = png_set_interlace_handling(png_ptr); +#else + num_pass = 1; +#endif + /* Loop through passes */ + for (pass = 0; pass < num_pass; pass++) + { + /* Loop through image */ + for (i = 0, rp = image; i < png_ptr->height; i++, rp++) + { + png_write_row(png_ptr, *rp); + } + } +} + +#ifdef PNG_MNG_FEATURES_SUPPORTED +/* Performs intrapixel differencing */ +static void +png_do_write_intrapixel(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_write_intrapixel"); + + if ((row_info->color_type & PNG_COLOR_MASK_COLOR) != 0) + { + int bytes_per_pixel; + png_uint_32 row_width = row_info->width; + if (row_info->bit_depth == 8) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 3; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 4; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + *(rp) = (png_byte)(*rp - *(rp + 1)); + *(rp + 2) = (png_byte)(*(rp + 2) - *(rp + 1)); + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 6; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 8; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + png_uint_32 s0 = (png_uint_32)(*(rp ) << 8) | *(rp + 1); + png_uint_32 s1 = (png_uint_32)(*(rp + 2) << 8) | *(rp + 3); + png_uint_32 s2 = (png_uint_32)(*(rp + 4) << 8) | *(rp + 5); + png_uint_32 red = (png_uint_32)((s0 - s1) & 0xffffL); + png_uint_32 blue = (png_uint_32)((s2 - s1) & 0xffffL); + *(rp ) = (png_byte)(red >> 8); + *(rp + 1) = (png_byte)red; + *(rp + 4) = (png_byte)(blue >> 8); + *(rp + 5) = (png_byte)blue; + } + } +#endif /* WRITE_16BIT */ + } +} +#endif /* MNG_FEATURES */ + +/* Called by user to write a row of image data */ +void PNGAPI +png_write_row(png_structrp png_ptr, png_const_bytep row) +{ + /* 1.5.6: moved from png_struct to be a local structure: */ + png_row_info row_info; + + if (png_ptr == NULL) + return; + + png_debug2(1, "in png_write_row (row %u, pass %d)", + png_ptr->row_number, png_ptr->pass); + + /* Initialize transformations and other stuff if first time */ + if (png_ptr->row_number == 0 && png_ptr->pass == 0) + { + /* Make sure we wrote the header info */ + if ((png_ptr->mode & PNG_WROTE_INFO_BEFORE_PLTE) == 0) + png_error(png_ptr, + "png_write_info was never called before png_write_row"); + + /* Check for transforms that have been set but were defined out */ +#if !defined(PNG_WRITE_INVERT_SUPPORTED) && defined(PNG_READ_INVERT_SUPPORTED) + if ((png_ptr->transformations & PNG_INVERT_MONO) != 0) + png_warning(png_ptr, "PNG_WRITE_INVERT_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_FILLER_SUPPORTED) && defined(PNG_READ_FILLER_SUPPORTED) + if ((png_ptr->transformations & PNG_FILLER) != 0) + png_warning(png_ptr, "PNG_WRITE_FILLER_SUPPORTED is not defined"); +#endif +#if !defined(PNG_WRITE_PACKSWAP_SUPPORTED) && \ + defined(PNG_READ_PACKSWAP_SUPPORTED) + if ((png_ptr->transformations & PNG_PACKSWAP) != 0) + png_warning(png_ptr, + "PNG_WRITE_PACKSWAP_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_PACK_SUPPORTED) && defined(PNG_READ_PACK_SUPPORTED) + if ((png_ptr->transformations & PNG_PACK) != 0) + png_warning(png_ptr, "PNG_WRITE_PACK_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_SHIFT_SUPPORTED) && defined(PNG_READ_SHIFT_SUPPORTED) + if ((png_ptr->transformations & PNG_SHIFT) != 0) + png_warning(png_ptr, "PNG_WRITE_SHIFT_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_BGR_SUPPORTED) && defined(PNG_READ_BGR_SUPPORTED) + if ((png_ptr->transformations & PNG_BGR) != 0) + png_warning(png_ptr, "PNG_WRITE_BGR_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_SWAP_SUPPORTED) && defined(PNG_READ_SWAP_SUPPORTED) + if ((png_ptr->transformations & PNG_SWAP_BYTES) != 0) + png_warning(png_ptr, "PNG_WRITE_SWAP_SUPPORTED is not defined"); +#endif + + png_write_start_row(png_ptr); + } + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* If interlaced and not interested in row, return */ + if (png_ptr->interlaced != 0 && + (png_ptr->transformations & PNG_INTERLACE) != 0) + { + switch (png_ptr->pass) + { + case 0: + if ((png_ptr->row_number & 0x07) != 0) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 1: + if ((png_ptr->row_number & 0x07) != 0 || png_ptr->width < 5) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 2: + if ((png_ptr->row_number & 0x07) != 4) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 3: + if ((png_ptr->row_number & 0x03) != 0 || png_ptr->width < 3) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 4: + if ((png_ptr->row_number & 0x03) != 2) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 5: + if ((png_ptr->row_number & 0x01) != 0 || png_ptr->width < 2) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 6: + if ((png_ptr->row_number & 0x01) == 0) + { + png_write_finish_row(png_ptr); + return; + } + break; + + default: /* error: ignore it */ + break; + } + } +#endif + + /* Set up row info for transformations */ + row_info.color_type = png_ptr->color_type; + row_info.width = png_ptr->usr_width; + row_info.channels = png_ptr->usr_channels; + row_info.bit_depth = png_ptr->usr_bit_depth; + row_info.pixel_depth = (png_byte)(row_info.bit_depth * row_info.channels); + row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width); + + png_debug1(3, "row_info->color_type = %d", row_info.color_type); + png_debug1(3, "row_info->width = %u", row_info.width); + png_debug1(3, "row_info->channels = %d", row_info.channels); + png_debug1(3, "row_info->bit_depth = %d", row_info.bit_depth); + png_debug1(3, "row_info->pixel_depth = %d", row_info.pixel_depth); + png_debug1(3, "row_info->rowbytes = %lu", (unsigned long)row_info.rowbytes); + + /* Copy user's row into buffer, leaving room for filter byte. */ + memcpy(png_ptr->row_buf + 1, row, row_info.rowbytes); + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Handle interlacing */ + if (png_ptr->interlaced && png_ptr->pass < 6 && + (png_ptr->transformations & PNG_INTERLACE) != 0) + { + png_do_write_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass); + /* This should always get caught above, but still ... */ + if (row_info.width == 0) + { + png_write_finish_row(png_ptr); + return; + } + } +#endif + +#ifdef PNG_WRITE_TRANSFORMS_SUPPORTED + /* Handle other transformations */ + if (png_ptr->transformations != 0) + png_do_write_transformations(png_ptr, &row_info); +#endif + + /* At this point the row_info pixel depth must match the 'transformed' depth, + * which is also the output depth. + */ + if (row_info.pixel_depth != png_ptr->pixel_depth || + row_info.pixel_depth != png_ptr->transformed_pixel_depth) + png_error(png_ptr, "internal write transform logic error"); + +#ifdef PNG_MNG_FEATURES_SUPPORTED + /* Write filter_method 64 (intrapixel differencing) only if + * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and + * 2. Libpng did not write a PNG signature (this filter_method is only + * used in PNG datastreams that are embedded in MNG datastreams) and + * 3. The application called png_permit_mng_features with a mask that + * included PNG_FLAG_MNG_FILTER_64 and + * 4. The filter_method is 64 and + * 5. The color_type is RGB or RGBA + */ + if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) != 0 && + (png_ptr->filter_type == PNG_INTRAPIXEL_DIFFERENCING)) + { + /* Intrapixel differencing */ + png_do_write_intrapixel(&row_info, png_ptr->row_buf + 1); + } +#endif + +/* Added at libpng-1.5.10 */ +#ifdef PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Check for out-of-range palette index */ + if (row_info.color_type == PNG_COLOR_TYPE_PALETTE && + png_ptr->num_palette_max >= 0) + png_do_check_palette_indexes(png_ptr, &row_info); +#endif + + /* Find a filter if necessary, filter the row and write it out. */ + png_write_find_filter(png_ptr, &row_info); + + if (png_ptr->write_row_fn != NULL) + (*(png_ptr->write_row_fn))(png_ptr, png_ptr->row_number, png_ptr->pass); +} + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +/* Set the automatic flush interval or 0 to turn flushing off */ +void PNGAPI +png_set_flush(png_structrp png_ptr, int nrows) +{ + png_debug(1, "in png_set_flush"); + + if (png_ptr == NULL) + return; + + png_ptr->flush_dist = (nrows < 0 ? 0 : (png_uint_32)nrows); +} + +/* Flush the current output buffers now */ +void PNGAPI +png_write_flush(png_structrp png_ptr) +{ + png_debug(1, "in png_write_flush"); + + if (png_ptr == NULL) + return; + + /* We have already written out all of the data */ + if (png_ptr->row_number >= png_ptr->num_rows) + return; + + png_compress_IDAT(png_ptr, NULL, 0, Z_SYNC_FLUSH); + png_ptr->flush_rows = 0; + png_flush(png_ptr); +} +#endif /* WRITE_FLUSH */ + +/* Free any memory used in png_ptr struct without freeing the struct itself. */ +static void +png_write_destroy(png_structrp png_ptr) +{ + png_debug(1, "in png_write_destroy"); + + /* Free any memory zlib uses */ + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) != 0) + deflateEnd(&png_ptr->zstream); + + /* Free our memory. png_free checks NULL for us. */ + png_free_buffer_list(png_ptr, &png_ptr->zbuffer_list); + png_free(png_ptr, png_ptr->row_buf); + png_ptr->row_buf = NULL; +#ifdef PNG_WRITE_FILTER_SUPPORTED + png_free(png_ptr, png_ptr->prev_row); + png_free(png_ptr, png_ptr->try_row); + png_free(png_ptr, png_ptr->tst_row); + png_ptr->prev_row = NULL; + png_ptr->try_row = NULL; + png_ptr->tst_row = NULL; +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + png_free(png_ptr, png_ptr->chunk_list); + png_ptr->chunk_list = NULL; +#endif + + /* The error handling and memory handling information is left intact at this + * point: the jmp_buf may still have to be freed. See png_destroy_png_struct + * for how this happens. + */ +} + +/* Free all memory used by the write. + * In libpng 1.6.0 this API changed quietly to no longer accept a NULL value for + * *png_ptr_ptr. Prior to 1.6.0 it would accept such a value and it would free + * the passed in info_structs but it would quietly fail to free any of the data + * inside them. In 1.6.0 it quietly does nothing (it has to be quiet because it + * has no png_ptr.) + */ +void PNGAPI +png_destroy_write_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr) +{ + png_debug(1, "in png_destroy_write_struct"); + + if (png_ptr_ptr != NULL) + { + png_structrp png_ptr = *png_ptr_ptr; + + if (png_ptr != NULL) /* added in libpng 1.6.0 */ + { + png_destroy_info_struct(png_ptr, info_ptr_ptr); + + *png_ptr_ptr = NULL; + png_write_destroy(png_ptr); + png_destroy_png_struct(png_ptr); + } + } +} + +/* Allow the application to select one or more row filters to use. */ +void PNGAPI +png_set_filter(png_structrp png_ptr, int method, int filters) +{ + png_debug(1, "in png_set_filter"); + + if (png_ptr == NULL) + return; + +#ifdef PNG_MNG_FEATURES_SUPPORTED + if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) != 0 && + (method == PNG_INTRAPIXEL_DIFFERENCING)) + method = PNG_FILTER_TYPE_BASE; + +#endif + if (method == PNG_FILTER_TYPE_BASE) + { + switch (filters & (PNG_ALL_FILTERS | 0x07)) + { +#ifdef PNG_WRITE_FILTER_SUPPORTED + case 5: + case 6: + case 7: png_app_error(png_ptr, "Unknown row filter for method 0"); +#endif /* WRITE_FILTER */ + /* FALLTHROUGH */ + case PNG_FILTER_VALUE_NONE: + png_ptr->do_filter = PNG_FILTER_NONE; break; + +#ifdef PNG_WRITE_FILTER_SUPPORTED + case PNG_FILTER_VALUE_SUB: + png_ptr->do_filter = PNG_FILTER_SUB; break; + + case PNG_FILTER_VALUE_UP: + png_ptr->do_filter = PNG_FILTER_UP; break; + + case PNG_FILTER_VALUE_AVG: + png_ptr->do_filter = PNG_FILTER_AVG; break; + + case PNG_FILTER_VALUE_PAETH: + png_ptr->do_filter = PNG_FILTER_PAETH; break; + + default: + png_ptr->do_filter = (png_byte)filters; break; +#else + default: + png_app_error(png_ptr, "Unknown row filter for method 0"); +#endif /* WRITE_FILTER */ + } + +#ifdef PNG_WRITE_FILTER_SUPPORTED + /* If we have allocated the row_buf, this means we have already started + * with the image and we should have allocated all of the filter buffers + * that have been selected. If prev_row isn't already allocated, then + * it is too late to start using the filters that need it, since we + * will be missing the data in the previous row. If an application + * wants to start and stop using particular filters during compression, + * it should start out with all of the filters, and then remove them + * or add them back after the start of compression. + * + * NOTE: this is a nasty constraint on the code, because it means that the + * prev_row buffer must be maintained even if there are currently no + * 'prev_row' requiring filters active. + */ + if (png_ptr->row_buf != NULL) + { + int num_filters; + png_alloc_size_t buf_size; + + /* Repeat the checks in png_write_start_row; 1 pixel high or wide + * images cannot benefit from certain filters. If this isn't done here + * the check below will fire on 1 pixel high images. + */ + if (png_ptr->height == 1) + filters &= ~(PNG_FILTER_UP|PNG_FILTER_AVG|PNG_FILTER_PAETH); + + if (png_ptr->width == 1) + filters &= ~(PNG_FILTER_SUB|PNG_FILTER_AVG|PNG_FILTER_PAETH); + + if ((filters & (PNG_FILTER_UP|PNG_FILTER_AVG|PNG_FILTER_PAETH)) != 0 + && png_ptr->prev_row == NULL) + { + /* This is the error case, however it is benign - the previous row + * is not available so the filter can't be used. Just warn here. + */ + png_app_warning(png_ptr, + "png_set_filter: UP/AVG/PAETH cannot be added after start"); + filters &= ~(PNG_FILTER_UP|PNG_FILTER_AVG|PNG_FILTER_PAETH); + } + + num_filters = 0; + + if (filters & PNG_FILTER_SUB) + num_filters++; + + if (filters & PNG_FILTER_UP) + num_filters++; + + if (filters & PNG_FILTER_AVG) + num_filters++; + + if (filters & PNG_FILTER_PAETH) + num_filters++; + + /* Allocate needed row buffers if they have not already been + * allocated. + */ + buf_size = PNG_ROWBYTES(png_ptr->usr_channels * png_ptr->usr_bit_depth, + png_ptr->width) + 1; + + if (png_ptr->try_row == NULL) + png_ptr->try_row = png_voidcast(png_bytep, + png_malloc(png_ptr, buf_size)); + + if (num_filters > 1) + { + if (png_ptr->tst_row == NULL) + png_ptr->tst_row = png_voidcast(png_bytep, + png_malloc(png_ptr, buf_size)); + } + } + png_ptr->do_filter = (png_byte)filters; +#endif + } + else + png_error(png_ptr, "Unknown custom filter method"); +} + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* DEPRECATED */ +/* Provide floating and fixed point APIs */ +#ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_filter_heuristics(png_structrp png_ptr, int heuristic_method, + int num_weights, png_const_doublep filter_weights, + png_const_doublep filter_costs) +{ + PNG_UNUSED(png_ptr) + PNG_UNUSED(heuristic_method) + PNG_UNUSED(num_weights) + PNG_UNUSED(filter_weights) + PNG_UNUSED(filter_costs) +} +#endif /* FLOATING_POINT */ + +#ifdef PNG_FIXED_POINT_SUPPORTED +void PNGAPI +png_set_filter_heuristics_fixed(png_structrp png_ptr, int heuristic_method, + int num_weights, png_const_fixed_point_p filter_weights, + png_const_fixed_point_p filter_costs) +{ + PNG_UNUSED(png_ptr) + PNG_UNUSED(heuristic_method) + PNG_UNUSED(num_weights) + PNG_UNUSED(filter_weights) + PNG_UNUSED(filter_costs) +} +#endif /* FIXED_POINT */ +#endif /* WRITE_WEIGHTED_FILTER */ + +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +void PNGAPI +png_set_compression_level(png_structrp png_ptr, int level) +{ + png_debug(1, "in png_set_compression_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_level = level; +} + +void PNGAPI +png_set_compression_mem_level(png_structrp png_ptr, int mem_level) +{ + png_debug(1, "in png_set_compression_mem_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_mem_level = mem_level; +} + +void PNGAPI +png_set_compression_strategy(png_structrp png_ptr, int strategy) +{ + png_debug(1, "in png_set_compression_strategy"); + + if (png_ptr == NULL) + return; + + /* The flag setting here prevents the libpng dynamic selection of strategy. + */ + png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_STRATEGY; + png_ptr->zlib_strategy = strategy; +} + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +void PNGAPI +png_set_compression_window_bits(png_structrp png_ptr, int window_bits) +{ + if (png_ptr == NULL) + return; + + /* Prior to 1.6.0 this would warn but then set the window_bits value. This + * meant that negative window bits values could be selected that would cause + * libpng to write a non-standard PNG file with raw deflate or gzip + * compressed IDAT or ancillary chunks. Such files can be read and there is + * no warning on read, so this seems like a very bad idea. + */ + if (window_bits > 15) + { + png_warning(png_ptr, "Only compression windows <= 32k supported by PNG"); + window_bits = 15; + } + + else if (window_bits < 8) + { + png_warning(png_ptr, "Only compression windows >= 256 supported by PNG"); + window_bits = 8; + } + + png_ptr->zlib_window_bits = window_bits; +} + +void PNGAPI +png_set_compression_method(png_structrp png_ptr, int method) +{ + png_debug(1, "in png_set_compression_method"); + + if (png_ptr == NULL) + return; + + /* This would produce an invalid PNG file if it worked, but it doesn't and + * deflate will fault it, so it is harmless to just warn here. + */ + if (method != 8) + png_warning(png_ptr, "Only compression method 8 is supported by PNG"); + + png_ptr->zlib_method = method; +} +#endif /* WRITE_CUSTOMIZE_COMPRESSION */ + +/* The following were added to libpng-1.5.4 */ +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +void PNGAPI +png_set_text_compression_level(png_structrp png_ptr, int level) +{ + png_debug(1, "in png_set_text_compression_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_text_level = level; +} + +void PNGAPI +png_set_text_compression_mem_level(png_structrp png_ptr, int mem_level) +{ + png_debug(1, "in png_set_text_compression_mem_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_text_mem_level = mem_level; +} + +void PNGAPI +png_set_text_compression_strategy(png_structrp png_ptr, int strategy) +{ + png_debug(1, "in png_set_text_compression_strategy"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_text_strategy = strategy; +} + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +void PNGAPI +png_set_text_compression_window_bits(png_structrp png_ptr, int window_bits) +{ + if (png_ptr == NULL) + return; + + if (window_bits > 15) + { + png_warning(png_ptr, "Only compression windows <= 32k supported by PNG"); + window_bits = 15; + } + + else if (window_bits < 8) + { + png_warning(png_ptr, "Only compression windows >= 256 supported by PNG"); + window_bits = 8; + } + + png_ptr->zlib_text_window_bits = window_bits; +} + +void PNGAPI +png_set_text_compression_method(png_structrp png_ptr, int method) +{ + png_debug(1, "in png_set_text_compression_method"); + + if (png_ptr == NULL) + return; + + if (method != 8) + png_warning(png_ptr, "Only compression method 8 is supported by PNG"); + + png_ptr->zlib_text_method = method; +} +#endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */ +/* end of API added to libpng-1.5.4 */ + +void PNGAPI +png_set_write_status_fn(png_structrp png_ptr, png_write_status_ptr write_row_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->write_row_fn = write_row_fn; +} + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED +void PNGAPI +png_set_write_user_transform_fn(png_structrp png_ptr, png_user_transform_ptr + write_user_transform_fn) +{ + png_debug(1, "in png_set_write_user_transform_fn"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_USER_TRANSFORM; + png_ptr->write_user_transform_fn = write_user_transform_fn; +} +#endif + + +#ifdef PNG_INFO_IMAGE_SUPPORTED +void PNGAPI +png_write_png(png_structrp png_ptr, png_inforp info_ptr, + int transforms, voidp params) +{ + if (png_ptr == NULL || info_ptr == NULL) + return; + + if ((info_ptr->valid & PNG_INFO_IDAT) == 0) + { + png_app_error(png_ptr, "no rows for png_write_image to write"); + return; + } + + /* Write the file header information. */ + png_write_info(png_ptr, info_ptr); + + /* ------ these transformations don't touch the info structure ------- */ + + /* Invert monochrome pixels */ + if ((transforms & PNG_TRANSFORM_INVERT_MONO) != 0) +#ifdef PNG_WRITE_INVERT_SUPPORTED + png_set_invert_mono(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_MONO not supported"); +#endif + + /* Shift the pixels up to a legal bit depth and fill in + * as appropriate to correctly scale the image. + */ + if ((transforms & PNG_TRANSFORM_SHIFT) != 0) +#ifdef PNG_WRITE_SHIFT_SUPPORTED + if ((info_ptr->valid & PNG_INFO_sBIT) != 0) + png_set_shift(png_ptr, &info_ptr->sig_bit); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SHIFT not supported"); +#endif + + /* Pack pixels into bytes */ + if ((transforms & PNG_TRANSFORM_PACKING) != 0) +#ifdef PNG_WRITE_PACK_SUPPORTED + png_set_packing(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKING not supported"); +#endif + + /* Swap location of alpha bytes from ARGB to RGBA */ + if ((transforms & PNG_TRANSFORM_SWAP_ALPHA) != 0) +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED + png_set_swap_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ALPHA not supported"); +#endif + + /* Remove a filler (X) from XRGB/RGBX/AG/GA into to convert it into + * RGB, note that the code expects the input color type to be G or RGB; no + * alpha channel. + */ + if ((transforms & (PNG_TRANSFORM_STRIP_FILLER_AFTER| + PNG_TRANSFORM_STRIP_FILLER_BEFORE)) != 0) + { +#ifdef PNG_WRITE_FILLER_SUPPORTED + if ((transforms & PNG_TRANSFORM_STRIP_FILLER_AFTER) != 0) + { + if ((transforms & PNG_TRANSFORM_STRIP_FILLER_BEFORE) != 0) + png_app_error(png_ptr, + "PNG_TRANSFORM_STRIP_FILLER: BEFORE+AFTER not supported"); + + /* Continue if ignored - this is the pre-1.6.10 behavior */ + png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + } + + else if ((transforms & PNG_TRANSFORM_STRIP_FILLER_BEFORE) != 0) + png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_STRIP_FILLER not supported"); +#endif + } + + /* Flip BGR pixels to RGB */ + if ((transforms & PNG_TRANSFORM_BGR) != 0) +#ifdef PNG_WRITE_BGR_SUPPORTED + png_set_bgr(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_BGR not supported"); +#endif + + /* Swap bytes of 16-bit files to most significant byte first */ + if ((transforms & PNG_TRANSFORM_SWAP_ENDIAN) != 0) +#ifdef PNG_WRITE_SWAP_SUPPORTED + png_set_swap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ENDIAN not supported"); +#endif + + /* Swap bits of 1-bit, 2-bit, 4-bit packed pixel formats */ + if ((transforms & PNG_TRANSFORM_PACKSWAP) != 0) +#ifdef PNG_WRITE_PACKSWAP_SUPPORTED + png_set_packswap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKSWAP not supported"); +#endif + + /* Invert the alpha channel from opacity to transparency */ + if ((transforms & PNG_TRANSFORM_INVERT_ALPHA) != 0) +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED + png_set_invert_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_ALPHA not supported"); +#endif + + /* ----------------------- end of transformations ------------------- */ + + /* Write the bits */ + png_write_image(png_ptr, info_ptr->row_pointers); + + /* It is REQUIRED to call this to finish writing the rest of the file */ + png_write_end(png_ptr, info_ptr); + + PNG_UNUSED(params) +} +#endif + + +#ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED +/* Initialize the write structure - general purpose utility. */ +static int +png_image_write_init(png_imagep image) +{ + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, image, + png_safe_error, png_safe_warning); + + if (png_ptr != NULL) + { + png_infop info_ptr = png_create_info_struct(png_ptr); + + if (info_ptr != NULL) + { + png_controlp control = png_voidcast(png_controlp, + png_malloc_warn(png_ptr, (sizeof *control))); + + if (control != NULL) + { + memset(control, 0, (sizeof *control)); + + control->png_ptr = png_ptr; + control->info_ptr = info_ptr; + control->for_write = 1; + + image->opaque = control; + return 1; + } + + /* Error clean up */ + png_destroy_info_struct(png_ptr, &info_ptr); + } + + png_destroy_write_struct(&png_ptr, NULL); + } + + return png_image_error(image, "png_image_write_: out of memory"); +} + +/* Arguments to png_image_write_main: */ +typedef struct +{ + /* Arguments: */ + png_imagep image; + png_const_voidp buffer; + png_int_32 row_stride; + png_const_voidp colormap; + int convert_to_8bit; + /* Local variables: */ + png_const_voidp first_row; + ptrdiff_t row_bytes; + png_voidp local_row; + /* Byte count for memory writing */ + png_bytep memory; + png_alloc_size_t memory_bytes; /* not used for STDIO */ + png_alloc_size_t output_bytes; /* running total */ +} png_image_write_control; + +/* Write png_uint_16 input to a 16-bit PNG; the png_ptr has already been set to + * do any necessary byte swapping. The component order is defined by the + * png_image format value. + */ +static int +png_write_image_16bit(png_voidp argument) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + + png_const_uint_16p input_row = png_voidcast(png_const_uint_16p, + display->first_row); + png_uint_16p output_row = png_voidcast(png_uint_16p, display->local_row); + png_uint_16p row_end; + unsigned int channels = (image->format & PNG_FORMAT_FLAG_COLOR) != 0 ? + 3 : 1; + int aindex = 0; + png_uint_32 y = image->height; + + if ((image->format & PNG_FORMAT_FLAG_ALPHA) != 0) + { +# ifdef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED + if ((image->format & PNG_FORMAT_FLAG_AFIRST) != 0) + { + aindex = -1; + ++input_row; /* To point to the first component */ + ++output_row; + } + else + aindex = (int)channels; +# else + aindex = (int)channels; +# endif + } + + else + png_error(png_ptr, "png_write_image: internal call error"); + + /* Work out the output row end and count over this, note that the increment + * above to 'row' means that row_end can actually be beyond the end of the + * row; this is correct. + */ + row_end = output_row + image->width * (channels+1); + + for (; y > 0; --y) + { + png_const_uint_16p in_ptr = input_row; + png_uint_16p out_ptr = output_row; + + while (out_ptr < row_end) + { + png_uint_16 alpha = in_ptr[aindex]; + png_uint_32 reciprocal = 0; + int c; + + out_ptr[aindex] = alpha; + + /* Calculate a reciprocal. The correct calculation is simply + * component/alpha*65535 << 15. (I.e. 15 bits of precision); this + * allows correct rounding by adding .5 before the shift. 'reciprocal' + * is only initialized when required. + */ + if (alpha > 0 && alpha < 65535) + reciprocal = ((0xffff<<15)+(alpha>>1))/alpha; + + c = (int)channels; + do /* always at least one channel */ + { + png_uint_16 component = *in_ptr++; + + /* The following gives 65535 for an alpha of 0, which is fine, + * otherwise if 0/0 is represented as some other value there is more + * likely to be a discontinuity which will probably damage + * compression when moving from a fully transparent area to a + * nearly transparent one. (The assumption here is that opaque + * areas tend not to be 0 intensity.) + */ + if (component >= alpha) + component = 65535; + + /* component 0 && alpha < 65535) + { + png_uint_32 calc = component * reciprocal; + calc += 16384; /* round to nearest */ + component = (png_uint_16)(calc >> 15); + } + + *out_ptr++ = component; + } + while (--c > 0); + + /* Skip to next component (skip the intervening alpha channel) */ + ++in_ptr; + ++out_ptr; + } + + png_write_row(png_ptr, png_voidcast(png_const_bytep, display->local_row)); + input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16)); + } + + return 1; +} + +/* Given 16-bit input (1 to 4 channels) write 8-bit output. If an alpha channel + * is present it must be removed from the components, the components are then + * written in sRGB encoding. No components are added or removed. + * + * Calculate an alpha reciprocal to reverse pre-multiplication. As above the + * calculation can be done to 15 bits of accuracy; however, the output needs to + * be scaled in the range 0..255*65535, so include that scaling here. + */ +# define UNP_RECIPROCAL(alpha) ((((0xffff*0xff)<<7)+((alpha)>>1))/(alpha)) + +static png_byte +png_unpremultiply(png_uint_32 component, png_uint_32 alpha, + png_uint_32 reciprocal/*from the above macro*/) +{ + /* The following gives 1.0 for an alpha of 0, which is fine, otherwise if 0/0 + * is represented as some other value there is more likely to be a + * discontinuity which will probably damage compression when moving from a + * fully transparent area to a nearly transparent one. (The assumption here + * is that opaque areas tend not to be 0 intensity.) + * + * There is a rounding problem here; if alpha is less than 128 it will end up + * as 0 when scaled to 8 bits. To avoid introducing spurious colors into the + * output change for this too. + */ + if (component >= alpha || alpha < 128) + return 255; + + /* component 0) + { + /* The test is that alpha/257 (rounded) is less than 255, the first value + * that becomes 255 is 65407. + * NOTE: this must agree with the PNG_DIV257 macro (which must, therefore, + * be exact!) [Could also test reciprocal != 0] + */ + if (alpha < 65407) + { + component *= reciprocal; + component += 64; /* round to nearest */ + component >>= 7; + } + + else + component *= 255; + + /* Convert the component to sRGB. */ + return (png_byte)PNG_sRGB_FROM_LINEAR(component); + } + + else + return 0; +} + +static int +png_write_image_8bit(png_voidp argument) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + + png_const_uint_16p input_row = png_voidcast(png_const_uint_16p, + display->first_row); + png_bytep output_row = png_voidcast(png_bytep, display->local_row); + png_uint_32 y = image->height; + unsigned int channels = (image->format & PNG_FORMAT_FLAG_COLOR) != 0 ? + 3 : 1; + + if ((image->format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + png_bytep row_end; + int aindex; + +# ifdef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED + if ((image->format & PNG_FORMAT_FLAG_AFIRST) != 0) + { + aindex = -1; + ++input_row; /* To point to the first component */ + ++output_row; + } + + else +# endif + aindex = (int)channels; + + /* Use row_end in place of a loop counter: */ + row_end = output_row + image->width * (channels+1); + + for (; y > 0; --y) + { + png_const_uint_16p in_ptr = input_row; + png_bytep out_ptr = output_row; + + while (out_ptr < row_end) + { + png_uint_16 alpha = in_ptr[aindex]; + png_byte alphabyte = (png_byte)PNG_DIV257(alpha); + png_uint_32 reciprocal = 0; + int c; + + /* Scale and write the alpha channel. */ + out_ptr[aindex] = alphabyte; + + if (alphabyte > 0 && alphabyte < 255) + reciprocal = UNP_RECIPROCAL(alpha); + + c = (int)channels; + do /* always at least one channel */ + *out_ptr++ = png_unpremultiply(*in_ptr++, alpha, reciprocal); + while (--c > 0); + + /* Skip to next component (skip the intervening alpha channel) */ + ++in_ptr; + ++out_ptr; + } /* while out_ptr < row_end */ + + png_write_row(png_ptr, png_voidcast(png_const_bytep, + display->local_row)); + input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16)); + } /* while y */ + } + + else + { + /* No alpha channel, so the row_end really is the end of the row and it + * is sufficient to loop over the components one by one. + */ + png_bytep row_end = output_row + image->width * channels; + + for (; y > 0; --y) + { + png_const_uint_16p in_ptr = input_row; + png_bytep out_ptr = output_row; + + while (out_ptr < row_end) + { + png_uint_32 component = *in_ptr++; + + component *= 255; + *out_ptr++ = (png_byte)PNG_sRGB_FROM_LINEAR(component); + } + + png_write_row(png_ptr, output_row); + input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16)); + } + } + + return 1; +} + +static void +png_image_set_PLTE(png_image_write_control *display) +{ + png_imagep image = display->image; + const void *cmap = display->colormap; + int entries = image->colormap_entries > 256 ? 256 : + (int)image->colormap_entries; + + /* NOTE: the caller must check for cmap != NULL and entries != 0 */ + png_uint_32 format = image->format; + unsigned int channels = PNG_IMAGE_SAMPLE_CHANNELS(format); + +# if defined(PNG_FORMAT_BGR_SUPPORTED) &&\ + defined(PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED) + int afirst = (format & PNG_FORMAT_FLAG_AFIRST) != 0 && + (format & PNG_FORMAT_FLAG_ALPHA) != 0; +# else +# define afirst 0 +# endif + +# ifdef PNG_FORMAT_BGR_SUPPORTED + int bgr = (format & PNG_FORMAT_FLAG_BGR) != 0 ? 2 : 0; +# else +# define bgr 0 +# endif + + int i, num_trans; + png_color palette[256]; + png_byte tRNS[256]; + + memset(tRNS, 255, (sizeof tRNS)); + memset(palette, 0, (sizeof palette)); + + for (i=num_trans=0; i= 3) /* RGB */ + { + palette[i].blue = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[(2 ^ bgr)]); + palette[i].green = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[1]); + palette[i].red = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[bgr]); + } + + else /* Gray */ + palette[i].blue = palette[i].red = palette[i].green = + (png_byte)PNG_sRGB_FROM_LINEAR(255 * *entry); + } + + else /* alpha */ + { + png_uint_16 alpha = entry[afirst ? 0 : channels-1]; + png_byte alphabyte = (png_byte)PNG_DIV257(alpha); + png_uint_32 reciprocal = 0; + + /* Calculate a reciprocal, as in the png_write_image_8bit code above + * this is designed to produce a value scaled to 255*65535 when + * divided by 128 (i.e. asr 7). + */ + if (alphabyte > 0 && alphabyte < 255) + reciprocal = (((0xffff*0xff)<<7)+(alpha>>1))/alpha; + + tRNS[i] = alphabyte; + if (alphabyte < 255) + num_trans = i+1; + + if (channels >= 3) /* RGB */ + { + palette[i].blue = png_unpremultiply(entry[afirst + (2 ^ bgr)], + alpha, reciprocal); + palette[i].green = png_unpremultiply(entry[afirst + 1], alpha, + reciprocal); + palette[i].red = png_unpremultiply(entry[afirst + bgr], alpha, + reciprocal); + } + + else /* gray */ + palette[i].blue = palette[i].red = palette[i].green = + png_unpremultiply(entry[afirst], alpha, reciprocal); + } + } + + else /* Color-map has sRGB values */ + { + png_const_bytep entry = png_voidcast(png_const_bytep, cmap); + + entry += (unsigned int)i * channels; + + switch (channels) + { + case 4: + tRNS[i] = entry[afirst ? 0 : 3]; + if (tRNS[i] < 255) + num_trans = i+1; + /* FALLTHROUGH */ + case 3: + palette[i].blue = entry[afirst + (2 ^ bgr)]; + palette[i].green = entry[afirst + 1]; + palette[i].red = entry[afirst + bgr]; + break; + + case 2: + tRNS[i] = entry[1 ^ afirst]; + if (tRNS[i] < 255) + num_trans = i+1; + /* FALLTHROUGH */ + case 1: + palette[i].blue = palette[i].red = palette[i].green = + entry[afirst]; + break; + + default: + break; + } + } + } + +# ifdef afirst +# undef afirst +# endif +# ifdef bgr +# undef bgr +# endif + + png_set_PLTE(image->opaque->png_ptr, image->opaque->info_ptr, palette, + entries); + + if (num_trans > 0) + png_set_tRNS(image->opaque->png_ptr, image->opaque->info_ptr, tRNS, + num_trans, NULL); + + image->colormap_entries = (png_uint_32)entries; +} + +static int +png_image_write_main(png_voidp argument) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + png_uint_32 format = image->format; + + /* The following four ints are actually booleans */ + int colormap = (format & PNG_FORMAT_FLAG_COLORMAP); + int linear = !colormap && (format & PNG_FORMAT_FLAG_LINEAR); /* input */ + int alpha = !colormap && (format & PNG_FORMAT_FLAG_ALPHA); + int write_16bit = linear && (display->convert_to_8bit == 0); + +# ifdef PNG_BENIGN_ERRORS_SUPPORTED + /* Make sure we error out on any bad situation */ + png_set_benign_errors(png_ptr, 0/*error*/); +# endif + + /* Default the 'row_stride' parameter if required, also check the row stride + * and total image size to ensure that they are within the system limits. + */ + { + unsigned int channels = PNG_IMAGE_PIXEL_CHANNELS(image->format); + + if (image->width <= 0x7fffffffU/channels) /* no overflow */ + { + png_uint_32 check; + png_uint_32 png_row_stride = image->width * channels; + + if (display->row_stride == 0) + display->row_stride = (png_int_32)/*SAFE*/png_row_stride; + + if (display->row_stride < 0) + check = (png_uint_32)(-display->row_stride); + + else + check = (png_uint_32)display->row_stride; + + if (check >= png_row_stride) + { + /* Now check for overflow of the image buffer calculation; this + * limits the whole image size to 32 bits for API compatibility with + * the current, 32-bit, PNG_IMAGE_BUFFER_SIZE macro. + */ + if (image->height > 0xffffffffU/png_row_stride) + png_error(image->opaque->png_ptr, "memory image too large"); + } + + else + png_error(image->opaque->png_ptr, "supplied row stride too small"); + } + + else + png_error(image->opaque->png_ptr, "image row stride too large"); + } + + /* Set the required transforms then write the rows in the correct order. */ + if ((format & PNG_FORMAT_FLAG_COLORMAP) != 0) + { + if (display->colormap != NULL && image->colormap_entries > 0) + { + png_uint_32 entries = image->colormap_entries; + + png_set_IHDR(png_ptr, info_ptr, image->width, image->height, + entries > 16 ? 8 : (entries > 4 ? 4 : (entries > 2 ? 2 : 1)), + PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_image_set_PLTE(display); + } + + else + png_error(image->opaque->png_ptr, + "no color-map for color-mapped image"); + } + + else + png_set_IHDR(png_ptr, info_ptr, image->width, image->height, + write_16bit ? 16 : 8, + ((format & PNG_FORMAT_FLAG_COLOR) ? PNG_COLOR_MASK_COLOR : 0) + + ((format & PNG_FORMAT_FLAG_ALPHA) ? PNG_COLOR_MASK_ALPHA : 0), + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + /* Counter-intuitively the data transformations must be called *after* + * png_write_info, not before as in the read code, but the 'set' functions + * must still be called before. Just set the color space information, never + * write an interlaced image. + */ + + if (write_16bit != 0) + { + /* The gamma here is 1.0 (linear) and the cHRM chunk matches sRGB. */ + png_set_gAMA_fixed(png_ptr, info_ptr, PNG_GAMMA_LINEAR); + + if ((image->flags & PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB) == 0) + png_set_cHRM_fixed(png_ptr, info_ptr, + /* color x y */ + /* white */ 31270, 32900, + /* red */ 64000, 33000, + /* green */ 30000, 60000, + /* blue */ 15000, 6000 + ); + } + + else if ((image->flags & PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB) == 0) + png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); + + /* Else writing an 8-bit file and the *colors* aren't sRGB, but the 8-bit + * space must still be gamma encoded. + */ + else + png_set_gAMA_fixed(png_ptr, info_ptr, PNG_GAMMA_sRGB_INVERSE); + + /* Write the file header. */ + png_write_info(png_ptr, info_ptr); + + /* Now set up the data transformations (*after* the header is written), + * remove the handled transformations from the 'format' flags for checking. + * + * First check for a little endian system if writing 16-bit files. + */ + if (write_16bit != 0) + { + png_uint_16 le = 0x0001; + + if ((*(png_const_bytep) & le) != 0) + png_set_swap(png_ptr); + } + +# ifdef PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED + if ((format & PNG_FORMAT_FLAG_BGR) != 0) + { + if (colormap == 0 && (format & PNG_FORMAT_FLAG_COLOR) != 0) + png_set_bgr(png_ptr); + format &= ~PNG_FORMAT_FLAG_BGR; + } +# endif + +# ifdef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED + if ((format & PNG_FORMAT_FLAG_AFIRST) != 0) + { + if (colormap == 0 && (format & PNG_FORMAT_FLAG_ALPHA) != 0) + png_set_swap_alpha(png_ptr); + format &= ~PNG_FORMAT_FLAG_AFIRST; + } +# endif + + /* If there are 16 or fewer color-map entries we wrote a lower bit depth + * above, but the application data is still byte packed. + */ + if (colormap != 0 && image->colormap_entries <= 16) + png_set_packing(png_ptr); + + /* That should have handled all (both) the transforms. */ + if ((format & ~(png_uint_32)(PNG_FORMAT_FLAG_COLOR | PNG_FORMAT_FLAG_LINEAR | + PNG_FORMAT_FLAG_ALPHA | PNG_FORMAT_FLAG_COLORMAP)) != 0) + png_error(png_ptr, "png_write_image: unsupported transformation"); + + { + png_const_bytep row = png_voidcast(png_const_bytep, display->buffer); + ptrdiff_t row_bytes = display->row_stride; + + if (linear != 0) + row_bytes *= (sizeof (png_uint_16)); + + if (row_bytes < 0) + row += (image->height-1) * (-row_bytes); + + display->first_row = row; + display->row_bytes = row_bytes; + } + + /* Apply 'fast' options if the flag is set. */ + if ((image->flags & PNG_IMAGE_FLAG_FAST) != 0) + { + png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_NO_FILTERS); + /* NOTE: determined by experiment using pngstest, this reflects some + * balance between the time to write the image once and the time to read + * it about 50 times. The speed-up in pngstest was about 10-20% of the + * total (user) time on a heavily loaded system. + */ +# ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED + png_set_compression_level(png_ptr, 3); +# endif + } + + /* Check for the cases that currently require a pre-transform on the row + * before it is written. This only applies when the input is 16-bit and + * either there is an alpha channel or it is converted to 8-bit. + */ + if ((linear != 0 && alpha != 0 ) || + (colormap == 0 && display->convert_to_8bit != 0)) + { + png_bytep row = png_voidcast(png_bytep, png_malloc(png_ptr, + png_get_rowbytes(png_ptr, info_ptr))); + int result; + + display->local_row = row; + if (write_16bit != 0) + result = png_safe_execute(image, png_write_image_16bit, display); + else + result = png_safe_execute(image, png_write_image_8bit, display); + display->local_row = NULL; + + png_free(png_ptr, row); + + /* Skip the 'write_end' on error: */ + if (result == 0) + return 0; + } + + /* Otherwise this is the case where the input is in a format currently + * supported by the rest of the libpng write code; call it directly. + */ + else + { + png_const_bytep row = png_voidcast(png_const_bytep, display->first_row); + ptrdiff_t row_bytes = display->row_bytes; + png_uint_32 y = image->height; + + for (; y > 0; --y) + { + png_write_row(png_ptr, row); + row += row_bytes; + } + } + + png_write_end(png_ptr, info_ptr); + return 1; +} + + +static void (PNGCBAPI +image_memory_write)(png_structp png_ptr, png_bytep/*const*/ data, size_t size) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + png_ptr->io_ptr/*backdoor: png_get_io_ptr(png_ptr)*/); + png_alloc_size_t ob = display->output_bytes; + + /* Check for overflow; this should never happen: */ + if (size <= ((png_alloc_size_t)-1) - ob) + { + /* I don't think libpng ever does this, but just in case: */ + if (size > 0) + { + if (display->memory_bytes >= ob+size) /* writing */ + memcpy(display->memory+ob, data, size); + + /* Always update the size: */ + display->output_bytes = ob+size; + } + } + + else + png_error(png_ptr, "png_image_write_to_memory: PNG too big"); +} + +static void (PNGCBAPI +image_memory_flush)(png_structp png_ptr) +{ + PNG_UNUSED(png_ptr) +} + +static int +png_image_write_memory(png_voidp argument) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + argument); + + /* The rest of the memory-specific init and write_main in an error protected + * environment. This case needs to use callbacks for the write operations + * since libpng has no built in support for writing to memory. + */ + png_set_write_fn(display->image->opaque->png_ptr, display/*io_ptr*/, + image_memory_write, image_memory_flush); + + return png_image_write_main(display); +} + +int PNGAPI +png_image_write_to_memory(png_imagep image, void *memory, + png_alloc_size_t * PNG_RESTRICT memory_bytes, int convert_to_8bit, + const void *buffer, png_int_32 row_stride, const void *colormap) +{ + /* Write the image to the given buffer, or count the bytes if it is NULL */ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (memory_bytes != NULL && buffer != NULL) + { + /* This is to give the caller an easier error detection in the NULL + * case and guard against uninitialized variable problems: + */ + if (memory == NULL) + *memory_bytes = 0; + + if (png_image_write_init(image) != 0) + { + png_image_write_control display; + int result; + + memset(&display, 0, (sizeof display)); + display.image = image; + display.buffer = buffer; + display.row_stride = row_stride; + display.colormap = colormap; + display.convert_to_8bit = convert_to_8bit; + display.memory = png_voidcast(png_bytep, memory); + display.memory_bytes = *memory_bytes; + display.output_bytes = 0; + + result = png_safe_execute(image, png_image_write_memory, &display); + png_image_free(image); + + /* write_memory returns true even if we ran out of buffer. */ + if (result) + { + /* On out-of-buffer this function returns '0' but still updates + * memory_bytes: + */ + if (memory != NULL && display.output_bytes > *memory_bytes) + result = 0; + + *memory_bytes = display.output_bytes; + } + + return result; + } + + else + return 0; + } + + else + return png_image_error(image, + "png_image_write_to_memory: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_write_to_memory: incorrect PNG_IMAGE_VERSION"); + + else + return 0; +} + +#ifdef PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED +int PNGAPI +png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, + const void *buffer, png_int_32 row_stride, const void *colormap) +{ + /* Write the image to the given (FILE*). */ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file != NULL && buffer != NULL) + { + if (png_image_write_init(image) != 0) + { + png_image_write_control display; + int result; + + /* This is slightly evil, but png_init_io doesn't do anything other + * than this and we haven't changed the standard IO functions so + * this saves a 'safe' function. + */ + image->opaque->png_ptr->io_ptr = file; + + memset(&display, 0, (sizeof display)); + display.image = image; + display.buffer = buffer; + display.row_stride = row_stride; + display.colormap = colormap; + display.convert_to_8bit = convert_to_8bit; + + result = png_safe_execute(image, png_image_write_main, &display); + png_image_free(image); + return result; + } + + else + return 0; + } + + else + return png_image_error(image, + "png_image_write_to_stdio: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_write_to_stdio: incorrect PNG_IMAGE_VERSION"); + + else + return 0; +} + +int PNGAPI +png_image_write_to_file(png_imagep image, const char *file_name, + int convert_to_8bit, const void *buffer, png_int_32 row_stride, + const void *colormap) +{ + /* Write the image to the named file. */ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file_name != NULL && buffer != NULL) + { + FILE *fp = fopen(file_name, "wb"); + + if (fp != NULL) + { + if (png_image_write_to_stdio(image, fp, convert_to_8bit, buffer, + row_stride, colormap) != 0) + { + int error; /* from fflush/fclose */ + + /* Make sure the file is flushed correctly. */ + if (fflush(fp) == 0 && ferror(fp) == 0) + { + if (fclose(fp) == 0) + return 1; + + error = errno; /* from fclose */ + } + + else + { + error = errno; /* from fflush or ferror */ + (void)fclose(fp); + } + + (void)remove(file_name); + /* The image has already been cleaned up; this is just used to + * set the error (because the original write succeeded). + */ + return png_image_error(image, strerror(error)); + } + + else + { + /* Clean up: just the opened file. */ + (void)fclose(fp); + (void)remove(file_name); + return 0; + } + } + + else + return png_image_error(image, strerror(errno)); + } + + else + return png_image_error(image, + "png_image_write_to_file: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_write_to_file: incorrect PNG_IMAGE_VERSION"); + + else + return 0; +} +#endif /* SIMPLIFIED_WRITE_STDIO */ +#endif /* SIMPLIFIED_WRITE */ +#endif /* WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwtran.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwtran.c new file mode 100644 index 0000000..49a13c1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwtran.c @@ -0,0 +1,575 @@ + +/* pngwtran.c - transforms the data in a row for PNG writers + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#ifdef PNG_WRITE_SUPPORTED +#ifdef PNG_WRITE_TRANSFORMS_SUPPORTED + +#ifdef PNG_WRITE_PACK_SUPPORTED +/* Pack pixels into bytes. Pass the true bit depth in bit_depth. The + * row_info bit depth should be 8 (one pixel per byte). The channels + * should be 1 (this only happens on grayscale and paletted images). + */ +static void +png_do_pack(png_row_infop row_info, png_bytep row, png_uint_32 bit_depth) +{ + png_debug(1, "in png_do_pack"); + + if (row_info->bit_depth == 8 && + row_info->channels == 1) + { + switch ((int)bit_depth) + { + case 1: + { + png_bytep sp, dp; + int mask, v; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + sp = row; + dp = row; + mask = 0x80; + v = 0; + + for (i = 0; i < row_width; i++) + { + if (*sp != 0) + v |= mask; + + sp++; + + if (mask > 1) + mask >>= 1; + + else + { + mask = 0x80; + *dp = (png_byte)v; + dp++; + v = 0; + } + } + + if (mask != 0x80) + *dp = (png_byte)v; + + break; + } + + case 2: + { + png_bytep sp, dp; + unsigned int shift; + int v; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + sp = row; + dp = row; + shift = 6; + v = 0; + + for (i = 0; i < row_width; i++) + { + png_byte value; + + value = (png_byte)(*sp & 0x03); + v |= (value << shift); + + if (shift == 0) + { + shift = 6; + *dp = (png_byte)v; + dp++; + v = 0; + } + + else + shift -= 2; + + sp++; + } + + if (shift != 6) + *dp = (png_byte)v; + + break; + } + + case 4: + { + png_bytep sp, dp; + unsigned int shift; + int v; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + sp = row; + dp = row; + shift = 4; + v = 0; + + for (i = 0; i < row_width; i++) + { + png_byte value; + + value = (png_byte)(*sp & 0x0f); + v |= (value << shift); + + if (shift == 0) + { + shift = 4; + *dp = (png_byte)v; + dp++; + v = 0; + } + + else + shift -= 4; + + sp++; + } + + if (shift != 4) + *dp = (png_byte)v; + + break; + } + + default: + break; + } + + row_info->bit_depth = (png_byte)bit_depth; + row_info->pixel_depth = (png_byte)(bit_depth * row_info->channels); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, + row_info->width); + } +} +#endif + +#ifdef PNG_WRITE_SHIFT_SUPPORTED +/* Shift pixel values to take advantage of whole range. Pass the + * true number of bits in bit_depth. The row should be packed + * according to row_info->bit_depth. Thus, if you had a row of + * bit depth 4, but the pixels only had values from 0 to 7, you + * would pass 3 as bit_depth, and this routine would translate the + * data to 0 to 15. + */ +static void +png_do_shift(png_row_infop row_info, png_bytep row, + png_const_color_8p bit_depth) +{ + png_debug(1, "in png_do_shift"); + + if (row_info->color_type != PNG_COLOR_TYPE_PALETTE) + { + int shift_start[4], shift_dec[4]; + unsigned int channels = 0; + + if ((row_info->color_type & PNG_COLOR_MASK_COLOR) != 0) + { + shift_start[channels] = row_info->bit_depth - bit_depth->red; + shift_dec[channels] = bit_depth->red; + channels++; + + shift_start[channels] = row_info->bit_depth - bit_depth->green; + shift_dec[channels] = bit_depth->green; + channels++; + + shift_start[channels] = row_info->bit_depth - bit_depth->blue; + shift_dec[channels] = bit_depth->blue; + channels++; + } + + else + { + shift_start[channels] = row_info->bit_depth - bit_depth->gray; + shift_dec[channels] = bit_depth->gray; + channels++; + } + + if ((row_info->color_type & PNG_COLOR_MASK_ALPHA) != 0) + { + shift_start[channels] = row_info->bit_depth - bit_depth->alpha; + shift_dec[channels] = bit_depth->alpha; + channels++; + } + + /* With low row depths, could only be grayscale, so one channel */ + if (row_info->bit_depth < 8) + { + png_bytep bp = row; + size_t i; + unsigned int mask; + size_t row_bytes = row_info->rowbytes; + + if (bit_depth->gray == 1 && row_info->bit_depth == 2) + mask = 0x55; + + else if (row_info->bit_depth == 4 && bit_depth->gray == 3) + mask = 0x11; + + else + mask = 0xff; + + for (i = 0; i < row_bytes; i++, bp++) + { + int j; + unsigned int v, out; + + v = *bp; + out = 0; + + for (j = shift_start[0]; j > -shift_dec[0]; j -= shift_dec[0]) + { + if (j > 0) + out |= v << j; + + else + out |= (v >> (-j)) & mask; + } + + *bp = (png_byte)(out & 0xff); + } + } + + else if (row_info->bit_depth == 8) + { + png_bytep bp = row; + png_uint_32 i; + png_uint_32 istop = channels * row_info->width; + + for (i = 0; i < istop; i++, bp++) + { + unsigned int c = i%channels; + int j; + unsigned int v, out; + + v = *bp; + out = 0; + + for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) + { + if (j > 0) + out |= v << j; + + else + out |= v >> (-j); + } + + *bp = (png_byte)(out & 0xff); + } + } + + else + { + png_bytep bp; + png_uint_32 i; + png_uint_32 istop = channels * row_info->width; + + for (bp = row, i = 0; i < istop; i++) + { + unsigned int c = i%channels; + int j; + unsigned int value, v; + + v = png_get_uint_16(bp); + value = 0; + + for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) + { + if (j > 0) + value |= v << j; + + else + value |= v >> (-j); + } + *bp++ = (png_byte)((value >> 8) & 0xff); + *bp++ = (png_byte)(value & 0xff); + } + } + } +} +#endif + +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED +static void +png_do_write_swap_alpha(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_write_swap_alpha"); + + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This converts from ARGB to RGBA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save; + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This converts from AARRGGBB to RRGGBBAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save[2]; + save[0] = *(sp++); + save[1] = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save[0]; + *(dp++) = save[1]; + } + } +#endif /* WRITE_16BIT */ + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This converts from AG to GA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save; + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This converts from AAGG to GGAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save[2]; + save[0] = *(sp++); + save[1] = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save[0]; + *(dp++) = save[1]; + } + } +#endif /* WRITE_16BIT */ + } + } +} +#endif + +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED +static void +png_do_write_invert_alpha(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_write_invert_alpha"); + + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in RGBA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + /* Does nothing + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + */ + sp+=3; dp = sp; + *dp = (png_byte)(255 - *(sp++)); + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This inverts the alpha channel in RRGGBBAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + /* Does nothing + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + */ + sp+=6; dp = sp; + *(dp++) = (png_byte)(255 - *(sp++)); + *dp = (png_byte)(255 - *(sp++)); + } + } +#endif /* WRITE_16BIT */ + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in GA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + *(dp++) = *(sp++); + *(dp++) = (png_byte)(255 - *(sp++)); + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This inverts the alpha channel in GGAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + /* Does nothing + *(dp++) = *(sp++); + *(dp++) = *(sp++); + */ + sp+=2; dp = sp; + *(dp++) = (png_byte)(255 - *(sp++)); + *dp = (png_byte)(255 - *(sp++)); + } + } +#endif /* WRITE_16BIT */ + } + } +} +#endif + +/* Transform the data according to the user's wishes. The order of + * transformations is significant. + */ +void /* PRIVATE */ +png_do_write_transformations(png_structrp png_ptr, png_row_infop row_info) +{ + png_debug(1, "in png_do_write_transformations"); + + if (png_ptr == NULL) + return; + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED + if ((png_ptr->transformations & PNG_USER_TRANSFORM) != 0) + if (png_ptr->write_user_transform_fn != NULL) + (*(png_ptr->write_user_transform_fn)) /* User write transform + function */ + (png_ptr, /* png_ptr */ + row_info, /* row_info: */ + /* png_uint_32 width; width of row */ + /* size_t rowbytes; number of bytes in row */ + /* png_byte color_type; color type of pixels */ + /* png_byte bit_depth; bit depth of samples */ + /* png_byte channels; number of channels (1-4) */ + /* png_byte pixel_depth; bits per pixel (depth*channels) */ + png_ptr->row_buf + 1); /* start of pixel data for row */ +#endif + +#ifdef PNG_WRITE_FILLER_SUPPORTED + if ((png_ptr->transformations & PNG_FILLER) != 0) + png_do_strip_channel(row_info, png_ptr->row_buf + 1, + !(png_ptr->flags & PNG_FLAG_FILLER_AFTER)); +#endif + +#ifdef PNG_WRITE_PACKSWAP_SUPPORTED + if ((png_ptr->transformations & PNG_PACKSWAP) != 0) + png_do_packswap(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_PACK_SUPPORTED + if ((png_ptr->transformations & PNG_PACK) != 0) + png_do_pack(row_info, png_ptr->row_buf + 1, + (png_uint_32)png_ptr->bit_depth); +#endif + +#ifdef PNG_WRITE_SWAP_SUPPORTED +# ifdef PNG_16BIT_SUPPORTED + if ((png_ptr->transformations & PNG_SWAP_BYTES) != 0) + png_do_swap(row_info, png_ptr->row_buf + 1); +# endif +#endif + +#ifdef PNG_WRITE_SHIFT_SUPPORTED + if ((png_ptr->transformations & PNG_SHIFT) != 0) + png_do_shift(row_info, png_ptr->row_buf + 1, + &(png_ptr->shift)); +#endif + +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_SWAP_ALPHA) != 0) + png_do_write_swap_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_INVERT_ALPHA) != 0) + png_do_write_invert_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_BGR_SUPPORTED + if ((png_ptr->transformations & PNG_BGR) != 0) + png_do_bgr(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_INVERT_SUPPORTED + if ((png_ptr->transformations & PNG_INVERT_MONO) != 0) + png_do_invert(row_info, png_ptr->row_buf + 1); +#endif +} +#endif /* WRITE_TRANSFORMS */ +#endif /* WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwutil.c b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwutil.c new file mode 100644 index 0000000..16345e4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/libpng/pngwutil.c @@ -0,0 +1,2781 @@ + +/* pngwutil.c - utilities to write a PNG file + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson + * Copyright (c) 1996-1997 Andreas Dilger + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#ifdef PNG_WRITE_SUPPORTED + +#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED +/* Place a 32-bit number into a buffer in PNG byte order. We work + * with unsigned numbers for convenience, although one supported + * ancillary chunk uses signed (two's complement) numbers. + */ +void PNGAPI +png_save_uint_32(png_bytep buf, png_uint_32 i) +{ + buf[0] = (png_byte)((i >> 24) & 0xffU); + buf[1] = (png_byte)((i >> 16) & 0xffU); + buf[2] = (png_byte)((i >> 8) & 0xffU); + buf[3] = (png_byte)( i & 0xffU); +} + +/* Place a 16-bit number into a buffer in PNG byte order. + * The parameter is declared unsigned int, not png_uint_16, + * just to avoid potential problems on pre-ANSI C compilers. + */ +void PNGAPI +png_save_uint_16(png_bytep buf, unsigned int i) +{ + buf[0] = (png_byte)((i >> 8) & 0xffU); + buf[1] = (png_byte)( i & 0xffU); +} +#endif + +/* Simple function to write the signature. If we have already written + * the magic bytes of the signature, or more likely, the PNG stream is + * being embedded into another stream and doesn't need its own signature, + * we should call png_set_sig_bytes() to tell libpng how many of the + * bytes have already been written. + */ +void PNGAPI +png_write_sig(png_structrp png_ptr) +{ + png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that the signature is being written */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_SIGNATURE; +#endif + + /* Write the rest of the 8 byte signature */ + png_write_data(png_ptr, &png_signature[png_ptr->sig_bytes], + (size_t)(8 - png_ptr->sig_bytes)); + + if (png_ptr->sig_bytes < 3) + png_ptr->mode |= PNG_HAVE_PNG_SIGNATURE; +} + +/* Write the start of a PNG chunk. The type is the chunk type. + * The total_length is the sum of the lengths of all the data you will be + * passing in png_write_chunk_data(). + */ +static void +png_write_chunk_header(png_structrp png_ptr, png_uint_32 chunk_name, + png_uint_32 length) +{ + png_byte buf[8]; + +#if defined(PNG_DEBUG) && (PNG_DEBUG > 0) + PNG_CSTRING_FROM_CHUNK(buf, chunk_name); + png_debug2(0, "Writing %s chunk, length = %lu", buf, (unsigned long)length); +#endif + + if (png_ptr == NULL) + return; + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that the chunk header is being written. + * PNG_IO_CHUNK_HDR requires a single I/O call. + */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_CHUNK_HDR; +#endif + + /* Write the length and the chunk name */ + png_save_uint_32(buf, length); + png_save_uint_32(buf + 4, chunk_name); + png_write_data(png_ptr, buf, 8); + + /* Put the chunk name into png_ptr->chunk_name */ + png_ptr->chunk_name = chunk_name; + + /* Reset the crc and run it over the chunk name */ + png_reset_crc(png_ptr); + + png_calculate_crc(png_ptr, buf + 4, 4); + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that chunk data will (possibly) be written. + * PNG_IO_CHUNK_DATA does NOT require a specific number of I/O calls. + */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_CHUNK_DATA; +#endif +} + +void PNGAPI +png_write_chunk_start(png_structrp png_ptr, png_const_bytep chunk_string, + png_uint_32 length) +{ + png_write_chunk_header(png_ptr, PNG_CHUNK_FROM_STRING(chunk_string), length); +} + +/* Write the data of a PNG chunk started with png_write_chunk_header(). + * Note that multiple calls to this function are allowed, and that the + * sum of the lengths from these calls *must* add up to the total_length + * given to png_write_chunk_header(). + */ +void PNGAPI +png_write_chunk_data(png_structrp png_ptr, png_const_bytep data, size_t length) +{ + /* Write the data, and run the CRC over it */ + if (png_ptr == NULL) + return; + + if (data != NULL && length > 0) + { + png_write_data(png_ptr, data, length); + + /* Update the CRC after writing the data, + * in case the user I/O routine alters it. + */ + png_calculate_crc(png_ptr, data, length); + } +} + +/* Finish a chunk started with png_write_chunk_header(). */ +void PNGAPI +png_write_chunk_end(png_structrp png_ptr) +{ + png_byte buf[4]; + + if (png_ptr == NULL) return; + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that the chunk CRC is being written. + * PNG_IO_CHUNK_CRC requires a single I/O function call. + */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_CHUNK_CRC; +#endif + + /* Write the crc in a single operation */ + png_save_uint_32(buf, png_ptr->crc); + + png_write_data(png_ptr, buf, 4); +} + +/* Write a PNG chunk all at once. The type is an array of ASCII characters + * representing the chunk name. The array must be at least 4 bytes in + * length, and does not need to be null terminated. To be safe, pass the + * pre-defined chunk names here, and if you need a new one, define it + * where the others are defined. The length is the length of the data. + * All the data must be present. If that is not possible, use the + * png_write_chunk_start(), png_write_chunk_data(), and png_write_chunk_end() + * functions instead. + */ +static void +png_write_complete_chunk(png_structrp png_ptr, png_uint_32 chunk_name, + png_const_bytep data, size_t length) +{ + if (png_ptr == NULL) + return; + + /* On 64-bit architectures 'length' may not fit in a png_uint_32. */ + if (length > PNG_UINT_31_MAX) + png_error(png_ptr, "length exceeds PNG maximum"); + + png_write_chunk_header(png_ptr, chunk_name, (png_uint_32)length); + png_write_chunk_data(png_ptr, data, length); + png_write_chunk_end(png_ptr); +} + +/* This is the API that calls the internal function above. */ +void PNGAPI +png_write_chunk(png_structrp png_ptr, png_const_bytep chunk_string, + png_const_bytep data, size_t length) +{ + png_write_complete_chunk(png_ptr, PNG_CHUNK_FROM_STRING(chunk_string), data, + length); +} + +/* This is used below to find the size of an image to pass to png_deflate_claim, + * so it only needs to be accurate if the size is less than 16384 bytes (the + * point at which a lower LZ window size can be used.) + */ +static png_alloc_size_t +png_image_size(png_structrp png_ptr) +{ + /* Only return sizes up to the maximum of a png_uint_32; do this by limiting + * the width and height used to 15 bits. + */ + png_uint_32 h = png_ptr->height; + + if (png_ptr->rowbytes < 32768 && h < 32768) + { + if (png_ptr->interlaced != 0) + { + /* Interlacing makes the image larger because of the replication of + * both the filter byte and the padding to a byte boundary. + */ + png_uint_32 w = png_ptr->width; + unsigned int pd = png_ptr->pixel_depth; + png_alloc_size_t cb_base; + int pass; + + for (cb_base=0, pass=0; pass<=6; ++pass) + { + png_uint_32 pw = PNG_PASS_COLS(w, pass); + + if (pw > 0) + cb_base += (PNG_ROWBYTES(pd, pw)+1) * PNG_PASS_ROWS(h, pass); + } + + return cb_base; + } + + else + return (png_ptr->rowbytes+1) * h; + } + + else + return 0xffffffffU; +} + +#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + /* This is the code to hack the first two bytes of the deflate stream (the + * deflate header) to correct the windowBits value to match the actual data + * size. Note that the second argument is the *uncompressed* size but the + * first argument is the *compressed* data (and it must be deflate + * compressed.) + */ +static void +optimize_cmf(png_bytep data, png_alloc_size_t data_size) +{ + /* Optimize the CMF field in the zlib stream. The resultant zlib stream is + * still compliant to the stream specification. + */ + if (data_size <= 16384) /* else windowBits must be 15 */ + { + unsigned int z_cmf = data[0]; /* zlib compression method and flags */ + + if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) + { + unsigned int z_cinfo; + unsigned int half_z_window_size; + + z_cinfo = z_cmf >> 4; + half_z_window_size = 1U << (z_cinfo + 7); + + if (data_size <= half_z_window_size) /* else no change */ + { + unsigned int tmp; + + do + { + half_z_window_size >>= 1; + --z_cinfo; + } + while (z_cinfo > 0 && data_size <= half_z_window_size); + + z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); + + data[0] = (png_byte)z_cmf; + tmp = data[1] & 0xe0; + tmp += 0x1f - ((z_cmf << 8) + tmp) % 0x1f; + data[1] = (png_byte)tmp; + } + } + } +} +#endif /* WRITE_OPTIMIZE_CMF */ + +/* Initialize the compressor for the appropriate type of compression. */ +static int +png_deflate_claim(png_structrp png_ptr, png_uint_32 owner, + png_alloc_size_t data_size) +{ + if (png_ptr->zowner != 0) + { +#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_ERROR_TEXT_SUPPORTED) + char msg[64]; + + PNG_STRING_FROM_CHUNK(msg, owner); + msg[4] = ':'; + msg[5] = ' '; + PNG_STRING_FROM_CHUNK(msg+6, png_ptr->zowner); + /* So the message that results is " using zstream"; this is an + * internal error, but is very useful for debugging. i18n requirements + * are minimal. + */ + (void)png_safecat(msg, (sizeof msg), 10, " using zstream"); +#endif +#if PNG_RELEASE_BUILD + png_warning(png_ptr, msg); + + /* Attempt sane error recovery */ + if (png_ptr->zowner == png_IDAT) /* don't steal from IDAT */ + { + png_ptr->zstream.msg = PNGZ_MSG_CAST("in use by IDAT"); + return Z_STREAM_ERROR; + } + + png_ptr->zowner = 0; +#else + png_error(png_ptr, msg); +#endif + } + + { + int level = png_ptr->zlib_level; + int method = png_ptr->zlib_method; + int windowBits = png_ptr->zlib_window_bits; + int memLevel = png_ptr->zlib_mem_level; + int strategy; /* set below */ + int ret; /* zlib return code */ + + if (owner == png_IDAT) + { + if ((png_ptr->flags & PNG_FLAG_ZLIB_CUSTOM_STRATEGY) != 0) + strategy = png_ptr->zlib_strategy; + + else if (png_ptr->do_filter != PNG_FILTER_NONE) + strategy = PNG_Z_DEFAULT_STRATEGY; + + else + strategy = PNG_Z_DEFAULT_NOFILTER_STRATEGY; + } + + else + { +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED + level = png_ptr->zlib_text_level; + method = png_ptr->zlib_text_method; + windowBits = png_ptr->zlib_text_window_bits; + memLevel = png_ptr->zlib_text_mem_level; + strategy = png_ptr->zlib_text_strategy; +#else + /* If customization is not supported the values all come from the + * IDAT values except for the strategy, which is fixed to the + * default. (This is the pre-1.6.0 behavior too, although it was + * implemented in a very different way.) + */ + strategy = Z_DEFAULT_STRATEGY; +#endif + } + + /* Adjust 'windowBits' down if larger than 'data_size'; to stop this + * happening just pass 32768 as the data_size parameter. Notice that zlib + * requires an extra 262 bytes in the window in addition to the data to be + * able to see the whole of the data, so if data_size+262 takes us to the + * next windowBits size we need to fix up the value later. (Because even + * though deflate needs the extra window, inflate does not!) + */ + if (data_size <= 16384) + { + /* IMPLEMENTATION NOTE: this 'half_window_size' stuff is only here to + * work round a Microsoft Visual C misbehavior which, contrary to C-90, + * widens the result of the following shift to 64-bits if (and, + * apparently, only if) it is used in a test. + */ + unsigned int half_window_size = 1U << (windowBits-1); + + while (data_size + 262 <= half_window_size) + { + half_window_size >>= 1; + --windowBits; + } + } + + /* Check against the previous initialized values, if any. */ + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) != 0 && + (png_ptr->zlib_set_level != level || + png_ptr->zlib_set_method != method || + png_ptr->zlib_set_window_bits != windowBits || + png_ptr->zlib_set_mem_level != memLevel || + png_ptr->zlib_set_strategy != strategy)) + { + if (deflateEnd(&png_ptr->zstream) != Z_OK) + png_warning(png_ptr, "deflateEnd failed (ignored)"); + + png_ptr->flags &= ~PNG_FLAG_ZSTREAM_INITIALIZED; + } + + /* For safety clear out the input and output pointers (currently zlib + * doesn't use them on Init, but it might in the future). + */ + png_ptr->zstream.next_in = NULL; + png_ptr->zstream.avail_in = 0; + png_ptr->zstream.next_out = NULL; + png_ptr->zstream.avail_out = 0; + + /* Now initialize if required, setting the new parameters, otherwise just + * do a simple reset to the previous parameters. + */ + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) != 0) + ret = deflateReset(&png_ptr->zstream); + + else + { + ret = deflateInit2(&png_ptr->zstream, level, method, windowBits, + memLevel, strategy); + + if (ret == Z_OK) + png_ptr->flags |= PNG_FLAG_ZSTREAM_INITIALIZED; + } + + /* The return code is from either deflateReset or deflateInit2; they have + * pretty much the same set of error codes. + */ + if (ret == Z_OK) + png_ptr->zowner = owner; + + else + png_zstream_error(png_ptr, ret); + + return ret; + } +} + +/* Clean up (or trim) a linked list of compression buffers. */ +void /* PRIVATE */ +png_free_buffer_list(png_structrp png_ptr, png_compression_bufferp *listp) +{ + png_compression_bufferp list = *listp; + + if (list != NULL) + { + *listp = NULL; + + do + { + png_compression_bufferp next = list->next; + + png_free(png_ptr, list); + list = next; + } + while (list != NULL); + } +} + +#ifdef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED +/* This pair of functions encapsulates the operation of (a) compressing a + * text string, and (b) issuing it later as a series of chunk data writes. + * The compression_state structure is shared context for these functions + * set up by the caller to allow access to the relevant local variables. + * + * compression_buffer (new in 1.6.0) is just a linked list of zbuffer_size + * temporary buffers. From 1.6.0 it is retained in png_struct so that it will + * be correctly freed in the event of a write error (previous implementations + * just leaked memory.) + */ +typedef struct +{ + png_const_bytep input; /* The uncompressed input data */ + png_alloc_size_t input_len; /* Its length */ + png_uint_32 output_len; /* Final compressed length */ + png_byte output[1024]; /* First block of output */ +} compression_state; + +static void +png_text_compress_init(compression_state *comp, png_const_bytep input, + png_alloc_size_t input_len) +{ + comp->input = input; + comp->input_len = input_len; + comp->output_len = 0; +} + +/* Compress the data in the compression state input */ +static int +png_text_compress(png_structrp png_ptr, png_uint_32 chunk_name, + compression_state *comp, png_uint_32 prefix_len) +{ + int ret; + + /* To find the length of the output it is necessary to first compress the + * input. The result is buffered rather than using the two-pass algorithm + * that is used on the inflate side; deflate is assumed to be slower and a + * PNG writer is assumed to have more memory available than a PNG reader. + * + * IMPLEMENTATION NOTE: the zlib API deflateBound() can be used to find an + * upper limit on the output size, but it is always bigger than the input + * size so it is likely to be more efficient to use this linked-list + * approach. + */ + ret = png_deflate_claim(png_ptr, chunk_name, comp->input_len); + + if (ret != Z_OK) + return ret; + + /* Set up the compression buffers, we need a loop here to avoid overflowing a + * uInt. Use ZLIB_IO_MAX to limit the input. The output is always limited + * by the output buffer size, so there is no need to check that. Since this + * is ANSI-C we know that an 'int', hence a uInt, is always at least 16 bits + * in size. + */ + { + png_compression_bufferp *end = &png_ptr->zbuffer_list; + png_alloc_size_t input_len = comp->input_len; /* may be zero! */ + png_uint_32 output_len; + + /* zlib updates these for us: */ + png_ptr->zstream.next_in = PNGZ_INPUT_CAST(comp->input); + png_ptr->zstream.avail_in = 0; /* Set below */ + png_ptr->zstream.next_out = comp->output; + png_ptr->zstream.avail_out = (sizeof comp->output); + + output_len = png_ptr->zstream.avail_out; + + do + { + uInt avail_in = ZLIB_IO_MAX; + + if (avail_in > input_len) + avail_in = (uInt)input_len; + + input_len -= avail_in; + + png_ptr->zstream.avail_in = avail_in; + + if (png_ptr->zstream.avail_out == 0) + { + png_compression_buffer *next; + + /* Chunk data is limited to 2^31 bytes in length, so the prefix + * length must be counted here. + */ + if (output_len + prefix_len > PNG_UINT_31_MAX) + { + ret = Z_MEM_ERROR; + break; + } + + /* Need a new (malloc'ed) buffer, but there may be one present + * already. + */ + next = *end; + if (next == NULL) + { + next = png_voidcast(png_compression_bufferp, png_malloc_base + (png_ptr, PNG_COMPRESSION_BUFFER_SIZE(png_ptr))); + + if (next == NULL) + { + ret = Z_MEM_ERROR; + break; + } + + /* Link in this buffer (so that it will be freed later) */ + next->next = NULL; + *end = next; + } + + png_ptr->zstream.next_out = next->output; + png_ptr->zstream.avail_out = png_ptr->zbuffer_size; + output_len += png_ptr->zstream.avail_out; + + /* Move 'end' to the next buffer pointer. */ + end = &next->next; + } + + /* Compress the data */ + ret = deflate(&png_ptr->zstream, + input_len > 0 ? Z_NO_FLUSH : Z_FINISH); + + /* Claw back input data that was not consumed (because avail_in is + * reset above every time round the loop). + */ + input_len += png_ptr->zstream.avail_in; + png_ptr->zstream.avail_in = 0; /* safety */ + } + while (ret == Z_OK); + + /* There may be some space left in the last output buffer. This needs to + * be subtracted from output_len. + */ + output_len -= png_ptr->zstream.avail_out; + png_ptr->zstream.avail_out = 0; /* safety */ + comp->output_len = output_len; + + /* Now double check the output length, put in a custom message if it is + * too long. Otherwise ensure the z_stream::msg pointer is set to + * something. + */ + if (output_len + prefix_len >= PNG_UINT_31_MAX) + { + png_ptr->zstream.msg = PNGZ_MSG_CAST("compressed data too long"); + ret = Z_MEM_ERROR; + } + + else + png_zstream_error(png_ptr, ret); + + /* Reset zlib for another zTXt/iTXt or image data */ + png_ptr->zowner = 0; + + /* The only success case is Z_STREAM_END, input_len must be 0; if not this + * is an internal error. + */ + if (ret == Z_STREAM_END && input_len == 0) + { +#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + /* Fix up the deflate header, if required */ + optimize_cmf(comp->output, comp->input_len); +#endif + /* But Z_OK is returned, not Z_STREAM_END; this allows the claim + * function above to return Z_STREAM_END on an error (though it never + * does in the current versions of zlib.) + */ + return Z_OK; + } + + else + return ret; + } +} + +/* Ship the compressed text out via chunk writes */ +static void +png_write_compressed_data_out(png_structrp png_ptr, compression_state *comp) +{ + png_uint_32 output_len = comp->output_len; + png_const_bytep output = comp->output; + png_uint_32 avail = (sizeof comp->output); + png_compression_buffer *next = png_ptr->zbuffer_list; + + for (;;) + { + if (avail > output_len) + avail = output_len; + + png_write_chunk_data(png_ptr, output, avail); + + output_len -= avail; + + if (output_len == 0 || next == NULL) + break; + + avail = png_ptr->zbuffer_size; + output = next->output; + next = next->next; + } + + /* This is an internal error; 'next' must have been NULL! */ + if (output_len > 0) + png_error(png_ptr, "error writing ancillary chunked compressed data"); +} +#endif /* WRITE_COMPRESSED_TEXT */ + +/* Write the IHDR chunk, and update the png_struct with the necessary + * information. Note that the rest of this code depends upon this + * information being correct. + */ +void /* PRIVATE */ +png_write_IHDR(png_structrp png_ptr, png_uint_32 width, png_uint_32 height, + int bit_depth, int color_type, int compression_type, int filter_type, + int interlace_type) +{ + png_byte buf[13]; /* Buffer to store the IHDR info */ + int is_invalid_depth; + + png_debug(1, "in png_write_IHDR"); + + /* Check that we have valid input data from the application info */ + switch (color_type) + { + case PNG_COLOR_TYPE_GRAY: + switch (bit_depth) + { + case 1: + case 2: + case 4: + case 8: +#ifdef PNG_WRITE_16BIT_SUPPORTED + case 16: +#endif + png_ptr->channels = 1; break; + + default: + png_error(png_ptr, + "Invalid bit depth for grayscale image"); + } + break; + + case PNG_COLOR_TYPE_RGB: + is_invalid_depth = (bit_depth != 8); +#ifdef PNG_WRITE_16BIT_SUPPORTED + is_invalid_depth = (is_invalid_depth && bit_depth != 16); +#endif + if (is_invalid_depth) + png_error(png_ptr, "Invalid bit depth for RGB image"); + + png_ptr->channels = 3; + break; + + case PNG_COLOR_TYPE_PALETTE: + switch (bit_depth) + { + case 1: + case 2: + case 4: + case 8: + png_ptr->channels = 1; + break; + + default: + png_error(png_ptr, "Invalid bit depth for paletted image"); + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + is_invalid_depth = (bit_depth != 8); +#ifdef PNG_WRITE_16BIT_SUPPORTED + is_invalid_depth = (is_invalid_depth && bit_depth != 16); +#endif + if (is_invalid_depth) + png_error(png_ptr, "Invalid bit depth for grayscale+alpha image"); + + png_ptr->channels = 2; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + is_invalid_depth = (bit_depth != 8); +#ifdef PNG_WRITE_16BIT_SUPPORTED + is_invalid_depth = (is_invalid_depth && bit_depth != 16); +#endif + if (is_invalid_depth) + png_error(png_ptr, "Invalid bit depth for RGBA image"); + + png_ptr->channels = 4; + break; + + default: + png_error(png_ptr, "Invalid image color type specified"); + } + + if (compression_type != PNG_COMPRESSION_TYPE_BASE) + { + png_warning(png_ptr, "Invalid compression type specified"); + compression_type = PNG_COMPRESSION_TYPE_BASE; + } + + /* Write filter_method 64 (intrapixel differencing) only if + * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and + * 2. Libpng did not write a PNG signature (this filter_method is only + * used in PNG datastreams that are embedded in MNG datastreams) and + * 3. The application called png_permit_mng_features with a mask that + * included PNG_FLAG_MNG_FILTER_64 and + * 4. The filter_method is 64 and + * 5. The color_type is RGB or RGBA + */ + if ( +#ifdef PNG_MNG_FEATURES_SUPPORTED + !((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) != 0 && + ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) == 0) && + (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_RGB_ALPHA) && + (filter_type == PNG_INTRAPIXEL_DIFFERENCING)) && +#endif + filter_type != PNG_FILTER_TYPE_BASE) + { + png_warning(png_ptr, "Invalid filter type specified"); + filter_type = PNG_FILTER_TYPE_BASE; + } + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + if (interlace_type != PNG_INTERLACE_NONE && + interlace_type != PNG_INTERLACE_ADAM7) + { + png_warning(png_ptr, "Invalid interlace type specified"); + interlace_type = PNG_INTERLACE_ADAM7; + } +#else + interlace_type=PNG_INTERLACE_NONE; +#endif + + /* Save the relevant information */ + png_ptr->bit_depth = (png_byte)bit_depth; + png_ptr->color_type = (png_byte)color_type; + png_ptr->interlaced = (png_byte)interlace_type; +#ifdef PNG_MNG_FEATURES_SUPPORTED + png_ptr->filter_type = (png_byte)filter_type; +#endif + png_ptr->compression_type = (png_byte)compression_type; + png_ptr->width = width; + png_ptr->height = height; + + png_ptr->pixel_depth = (png_byte)(bit_depth * png_ptr->channels); + png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, width); + /* Set the usr info, so any transformations can modify it */ + png_ptr->usr_width = png_ptr->width; + png_ptr->usr_bit_depth = png_ptr->bit_depth; + png_ptr->usr_channels = png_ptr->channels; + + /* Pack the header information into the buffer */ + png_save_uint_32(buf, width); + png_save_uint_32(buf + 4, height); + buf[8] = (png_byte)bit_depth; + buf[9] = (png_byte)color_type; + buf[10] = (png_byte)compression_type; + buf[11] = (png_byte)filter_type; + buf[12] = (png_byte)interlace_type; + + /* Write the chunk */ + png_write_complete_chunk(png_ptr, png_IHDR, buf, 13); + + if ((png_ptr->do_filter) == PNG_NO_FILTERS) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE || + png_ptr->bit_depth < 8) + png_ptr->do_filter = PNG_FILTER_NONE; + + else + png_ptr->do_filter = PNG_ALL_FILTERS; + } + + png_ptr->mode = PNG_HAVE_IHDR; /* not READY_FOR_ZTXT */ +} + +/* Write the palette. We are careful not to trust png_color to be in the + * correct order for PNG, so people can redefine it to any convenient + * structure. + */ +void /* PRIVATE */ +png_write_PLTE(png_structrp png_ptr, png_const_colorp palette, + png_uint_32 num_pal) +{ + png_uint_32 max_palette_length, i; + png_const_colorp pal_ptr; + png_byte buf[3]; + + png_debug(1, "in png_write_PLTE"); + + max_palette_length = (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) ? + (1 << png_ptr->bit_depth) : PNG_MAX_PALETTE_LENGTH; + + if (( +#ifdef PNG_MNG_FEATURES_SUPPORTED + (png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE) == 0 && +#endif + num_pal == 0) || num_pal > max_palette_length) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + png_error(png_ptr, "Invalid number of colors in palette"); + } + + else + { + png_warning(png_ptr, "Invalid number of colors in palette"); + return; + } + } + + if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) == 0) + { + png_warning(png_ptr, + "Ignoring request to write a PLTE chunk in grayscale PNG"); + + return; + } + + png_ptr->num_palette = (png_uint_16)num_pal; + png_debug1(3, "num_palette = %d", png_ptr->num_palette); + + png_write_chunk_header(png_ptr, png_PLTE, (png_uint_32)(num_pal * 3)); +#ifdef PNG_POINTER_INDEXING_SUPPORTED + + for (i = 0, pal_ptr = palette; i < num_pal; i++, pal_ptr++) + { + buf[0] = pal_ptr->red; + buf[1] = pal_ptr->green; + buf[2] = pal_ptr->blue; + png_write_chunk_data(png_ptr, buf, 3); + } + +#else + /* This is a little slower but some buggy compilers need to do this + * instead + */ + pal_ptr=palette; + + for (i = 0; i < num_pal; i++) + { + buf[0] = pal_ptr[i].red; + buf[1] = pal_ptr[i].green; + buf[2] = pal_ptr[i].blue; + png_write_chunk_data(png_ptr, buf, 3); + } + +#endif + png_write_chunk_end(png_ptr); + png_ptr->mode |= PNG_HAVE_PLTE; +} + +/* This is similar to png_text_compress, above, except that it does not require + * all of the data at once and, instead of buffering the compressed result, + * writes it as IDAT chunks. Unlike png_text_compress it *can* png_error out + * because it calls the write interface. As a result it does its own error + * reporting and does not return an error code. In the event of error it will + * just call png_error. The input data length may exceed 32-bits. The 'flush' + * parameter is exactly the same as that to deflate, with the following + * meanings: + * + * Z_NO_FLUSH: normal incremental output of compressed data + * Z_SYNC_FLUSH: do a SYNC_FLUSH, used by png_write_flush + * Z_FINISH: this is the end of the input, do a Z_FINISH and clean up + * + * The routine manages the acquire and release of the png_ptr->zstream by + * checking and (at the end) clearing png_ptr->zowner; it does some sanity + * checks on the 'mode' flags while doing this. + */ +void /* PRIVATE */ +png_compress_IDAT(png_structrp png_ptr, png_const_bytep input, + png_alloc_size_t input_len, int flush) +{ + if (png_ptr->zowner != png_IDAT) + { + /* First time. Ensure we have a temporary buffer for compression and + * trim the buffer list if it has more than one entry to free memory. + * If 'WRITE_COMPRESSED_TEXT' is not set the list will never have been + * created at this point, but the check here is quick and safe. + */ + if (png_ptr->zbuffer_list == NULL) + { + png_ptr->zbuffer_list = png_voidcast(png_compression_bufferp, + png_malloc(png_ptr, PNG_COMPRESSION_BUFFER_SIZE(png_ptr))); + png_ptr->zbuffer_list->next = NULL; + } + + else + png_free_buffer_list(png_ptr, &png_ptr->zbuffer_list->next); + + /* It is a terminal error if we can't claim the zstream. */ + if (png_deflate_claim(png_ptr, png_IDAT, png_image_size(png_ptr)) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + /* The output state is maintained in png_ptr->zstream, so it must be + * initialized here after the claim. + */ + png_ptr->zstream.next_out = png_ptr->zbuffer_list->output; + png_ptr->zstream.avail_out = png_ptr->zbuffer_size; + } + + /* Now loop reading and writing until all the input is consumed or an error + * terminates the operation. The _out values are maintained across calls to + * this function, but the input must be reset each time. + */ + png_ptr->zstream.next_in = PNGZ_INPUT_CAST(input); + png_ptr->zstream.avail_in = 0; /* set below */ + for (;;) + { + int ret; + + /* INPUT: from the row data */ + uInt avail = ZLIB_IO_MAX; + + if (avail > input_len) + avail = (uInt)input_len; /* safe because of the check */ + + png_ptr->zstream.avail_in = avail; + input_len -= avail; + + ret = deflate(&png_ptr->zstream, input_len > 0 ? Z_NO_FLUSH : flush); + + /* Include as-yet unconsumed input */ + input_len += png_ptr->zstream.avail_in; + png_ptr->zstream.avail_in = 0; + + /* OUTPUT: write complete IDAT chunks when avail_out drops to zero. Note + * that these two zstream fields are preserved across the calls, therefore + * there is no need to set these up on entry to the loop. + */ + if (png_ptr->zstream.avail_out == 0) + { + png_bytep data = png_ptr->zbuffer_list->output; + uInt size = png_ptr->zbuffer_size; + + /* Write an IDAT containing the data then reset the buffer. The + * first IDAT may need deflate header optimization. + */ +#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + if ((png_ptr->mode & PNG_HAVE_IDAT) == 0 && + png_ptr->compression_type == PNG_COMPRESSION_TYPE_BASE) + optimize_cmf(data, png_image_size(png_ptr)); +#endif + + if (size > 0) + png_write_complete_chunk(png_ptr, png_IDAT, data, size); + png_ptr->mode |= PNG_HAVE_IDAT; + + png_ptr->zstream.next_out = data; + png_ptr->zstream.avail_out = size; + + /* For SYNC_FLUSH or FINISH it is essential to keep calling zlib with + * the same flush parameter until it has finished output, for NO_FLUSH + * it doesn't matter. + */ + if (ret == Z_OK && flush != Z_NO_FLUSH) + continue; + } + + /* The order of these checks doesn't matter much; it just affects which + * possible error might be detected if multiple things go wrong at once. + */ + if (ret == Z_OK) /* most likely return code! */ + { + /* If all the input has been consumed then just return. If Z_FINISH + * was used as the flush parameter something has gone wrong if we get + * here. + */ + if (input_len == 0) + { + if (flush == Z_FINISH) + png_error(png_ptr, "Z_OK on Z_FINISH with output space"); + + return; + } + } + + else if (ret == Z_STREAM_END && flush == Z_FINISH) + { + /* This is the end of the IDAT data; any pending output must be + * flushed. For small PNG files we may still be at the beginning. + */ + png_bytep data = png_ptr->zbuffer_list->output; + uInt size = png_ptr->zbuffer_size - png_ptr->zstream.avail_out; + +#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + if ((png_ptr->mode & PNG_HAVE_IDAT) == 0 && + png_ptr->compression_type == PNG_COMPRESSION_TYPE_BASE) + optimize_cmf(data, png_image_size(png_ptr)); +#endif + + if (size > 0) + png_write_complete_chunk(png_ptr, png_IDAT, data, size); + png_ptr->zstream.avail_out = 0; + png_ptr->zstream.next_out = NULL; + png_ptr->mode |= PNG_HAVE_IDAT | PNG_AFTER_IDAT; + + png_ptr->zowner = 0; /* Release the stream */ + return; + } + + else + { + /* This is an error condition. */ + png_zstream_error(png_ptr, ret); + png_error(png_ptr, png_ptr->zstream.msg); + } + } +} + +/* Write an IEND chunk */ +void /* PRIVATE */ +png_write_IEND(png_structrp png_ptr) +{ + png_debug(1, "in png_write_IEND"); + + png_write_complete_chunk(png_ptr, png_IEND, NULL, 0); + png_ptr->mode |= PNG_HAVE_IEND; +} + +#ifdef PNG_WRITE_gAMA_SUPPORTED +/* Write a gAMA chunk */ +void /* PRIVATE */ +png_write_gAMA_fixed(png_structrp png_ptr, png_fixed_point file_gamma) +{ + png_byte buf[4]; + + png_debug(1, "in png_write_gAMA"); + + /* file_gamma is saved in 1/100,000ths */ + png_save_uint_32(buf, (png_uint_32)file_gamma); + png_write_complete_chunk(png_ptr, png_gAMA, buf, 4); +} +#endif + +#ifdef PNG_WRITE_sRGB_SUPPORTED +/* Write a sRGB chunk */ +void /* PRIVATE */ +png_write_sRGB(png_structrp png_ptr, int srgb_intent) +{ + png_byte buf[1]; + + png_debug(1, "in png_write_sRGB"); + + if (srgb_intent >= PNG_sRGB_INTENT_LAST) + png_warning(png_ptr, + "Invalid sRGB rendering intent specified"); + + buf[0]=(png_byte)srgb_intent; + png_write_complete_chunk(png_ptr, png_sRGB, buf, 1); +} +#endif + +#ifdef PNG_WRITE_iCCP_SUPPORTED +/* Write an iCCP chunk */ +void /* PRIVATE */ +png_write_iCCP(png_structrp png_ptr, png_const_charp name, + png_const_bytep profile) +{ + png_uint_32 name_len; + png_uint_32 profile_len; + png_byte new_name[81]; /* 1 byte for the compression byte */ + compression_state comp; + png_uint_32 temp; + + png_debug(1, "in png_write_iCCP"); + + /* These are all internal problems: the profile should have been checked + * before when it was stored. + */ + if (profile == NULL) + png_error(png_ptr, "No profile for iCCP chunk"); /* internal error */ + + profile_len = png_get_uint_32(profile); + + if (profile_len < 132) + png_error(png_ptr, "ICC profile too short"); + + temp = (png_uint_32) (*(profile+8)); + if (temp > 3 && (profile_len & 0x03)) + png_error(png_ptr, "ICC profile length invalid (not a multiple of 4)"); + + { + png_uint_32 embedded_profile_len = png_get_uint_32(profile); + + if (profile_len != embedded_profile_len) + png_error(png_ptr, "Profile length does not match profile"); + } + + name_len = png_check_keyword(png_ptr, name, new_name); + + if (name_len == 0) + png_error(png_ptr, "iCCP: invalid keyword"); + + new_name[++name_len] = PNG_COMPRESSION_TYPE_BASE; + + /* Make sure we include the NULL after the name and the compression type */ + ++name_len; + + png_text_compress_init(&comp, profile, profile_len); + + /* Allow for keyword terminator and compression byte */ + if (png_text_compress(png_ptr, png_iCCP, &comp, name_len) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + png_write_chunk_header(png_ptr, png_iCCP, name_len + comp.output_len); + + png_write_chunk_data(png_ptr, new_name, name_len); + + png_write_compressed_data_out(png_ptr, &comp); + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_sPLT_SUPPORTED +/* Write a sPLT chunk */ +void /* PRIVATE */ +png_write_sPLT(png_structrp png_ptr, png_const_sPLT_tp spalette) +{ + png_uint_32 name_len; + png_byte new_name[80]; + png_byte entrybuf[10]; + size_t entry_size = (spalette->depth == 8 ? 6 : 10); + size_t palette_size = entry_size * (size_t)spalette->nentries; + png_sPLT_entryp ep; +#ifndef PNG_POINTER_INDEXING_SUPPORTED + int i; +#endif + + png_debug(1, "in png_write_sPLT"); + + name_len = png_check_keyword(png_ptr, spalette->name, new_name); + + if (name_len == 0) + png_error(png_ptr, "sPLT: invalid keyword"); + + /* Make sure we include the NULL after the name */ + png_write_chunk_header(png_ptr, png_sPLT, + (png_uint_32)(name_len + 2 + palette_size)); + + png_write_chunk_data(png_ptr, (png_bytep)new_name, (size_t)(name_len + 1)); + + png_write_chunk_data(png_ptr, &spalette->depth, 1); + + /* Loop through each palette entry, writing appropriately */ +#ifdef PNG_POINTER_INDEXING_SUPPORTED + for (ep = spalette->entries; epentries + spalette->nentries; ep++) + { + if (spalette->depth == 8) + { + entrybuf[0] = (png_byte)ep->red; + entrybuf[1] = (png_byte)ep->green; + entrybuf[2] = (png_byte)ep->blue; + entrybuf[3] = (png_byte)ep->alpha; + png_save_uint_16(entrybuf + 4, ep->frequency); + } + + else + { + png_save_uint_16(entrybuf + 0, ep->red); + png_save_uint_16(entrybuf + 2, ep->green); + png_save_uint_16(entrybuf + 4, ep->blue); + png_save_uint_16(entrybuf + 6, ep->alpha); + png_save_uint_16(entrybuf + 8, ep->frequency); + } + + png_write_chunk_data(png_ptr, entrybuf, entry_size); + } +#else + ep=spalette->entries; + for (i = 0; i>spalette->nentries; i++) + { + if (spalette->depth == 8) + { + entrybuf[0] = (png_byte)ep[i].red; + entrybuf[1] = (png_byte)ep[i].green; + entrybuf[2] = (png_byte)ep[i].blue; + entrybuf[3] = (png_byte)ep[i].alpha; + png_save_uint_16(entrybuf + 4, ep[i].frequency); + } + + else + { + png_save_uint_16(entrybuf + 0, ep[i].red); + png_save_uint_16(entrybuf + 2, ep[i].green); + png_save_uint_16(entrybuf + 4, ep[i].blue); + png_save_uint_16(entrybuf + 6, ep[i].alpha); + png_save_uint_16(entrybuf + 8, ep[i].frequency); + } + + png_write_chunk_data(png_ptr, entrybuf, entry_size); + } +#endif + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_sBIT_SUPPORTED +/* Write the sBIT chunk */ +void /* PRIVATE */ +png_write_sBIT(png_structrp png_ptr, png_const_color_8p sbit, int color_type) +{ + png_byte buf[4]; + size_t size; + + png_debug(1, "in png_write_sBIT"); + + /* Make sure we don't depend upon the order of PNG_COLOR_8 */ + if ((color_type & PNG_COLOR_MASK_COLOR) != 0) + { + png_byte maxbits; + + maxbits = (png_byte)(color_type==PNG_COLOR_TYPE_PALETTE ? 8 : + png_ptr->usr_bit_depth); + + if (sbit->red == 0 || sbit->red > maxbits || + sbit->green == 0 || sbit->green > maxbits || + sbit->blue == 0 || sbit->blue > maxbits) + { + png_warning(png_ptr, "Invalid sBIT depth specified"); + return; + } + + buf[0] = sbit->red; + buf[1] = sbit->green; + buf[2] = sbit->blue; + size = 3; + } + + else + { + if (sbit->gray == 0 || sbit->gray > png_ptr->usr_bit_depth) + { + png_warning(png_ptr, "Invalid sBIT depth specified"); + return; + } + + buf[0] = sbit->gray; + size = 1; + } + + if ((color_type & PNG_COLOR_MASK_ALPHA) != 0) + { + if (sbit->alpha == 0 || sbit->alpha > png_ptr->usr_bit_depth) + { + png_warning(png_ptr, "Invalid sBIT depth specified"); + return; + } + + buf[size++] = sbit->alpha; + } + + png_write_complete_chunk(png_ptr, png_sBIT, buf, size); +} +#endif + +#ifdef PNG_WRITE_cHRM_SUPPORTED +/* Write the cHRM chunk */ +void /* PRIVATE */ +png_write_cHRM_fixed(png_structrp png_ptr, const png_xy *xy) +{ + png_byte buf[32]; + + png_debug(1, "in png_write_cHRM"); + + /* Each value is saved in 1/100,000ths */ + png_save_int_32(buf, xy->whitex); + png_save_int_32(buf + 4, xy->whitey); + + png_save_int_32(buf + 8, xy->redx); + png_save_int_32(buf + 12, xy->redy); + + png_save_int_32(buf + 16, xy->greenx); + png_save_int_32(buf + 20, xy->greeny); + + png_save_int_32(buf + 24, xy->bluex); + png_save_int_32(buf + 28, xy->bluey); + + png_write_complete_chunk(png_ptr, png_cHRM, buf, 32); +} +#endif + +#ifdef PNG_WRITE_tRNS_SUPPORTED +/* Write the tRNS chunk */ +void /* PRIVATE */ +png_write_tRNS(png_structrp png_ptr, png_const_bytep trans_alpha, + png_const_color_16p tran, int num_trans, int color_type) +{ + png_byte buf[6]; + + png_debug(1, "in png_write_tRNS"); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + if (num_trans <= 0 || num_trans > (int)png_ptr->num_palette) + { + png_app_warning(png_ptr, + "Invalid number of transparent colors specified"); + return; + } + + /* Write the chunk out as it is */ + png_write_complete_chunk(png_ptr, png_tRNS, trans_alpha, + (size_t)num_trans); + } + + else if (color_type == PNG_COLOR_TYPE_GRAY) + { + /* One 16-bit value */ + if (tran->gray >= (1 << png_ptr->bit_depth)) + { + png_app_warning(png_ptr, + "Ignoring attempt to write tRNS chunk out-of-range for bit_depth"); + + return; + } + + png_save_uint_16(buf, tran->gray); + png_write_complete_chunk(png_ptr, png_tRNS, buf, 2); + } + + else if (color_type == PNG_COLOR_TYPE_RGB) + { + /* Three 16-bit values */ + png_save_uint_16(buf, tran->red); + png_save_uint_16(buf + 2, tran->green); + png_save_uint_16(buf + 4, tran->blue); +#ifdef PNG_WRITE_16BIT_SUPPORTED + if (png_ptr->bit_depth == 8 && (buf[0] | buf[2] | buf[4]) != 0) +#else + if ((buf[0] | buf[2] | buf[4]) != 0) +#endif + { + png_app_warning(png_ptr, + "Ignoring attempt to write 16-bit tRNS chunk when bit_depth is 8"); + return; + } + + png_write_complete_chunk(png_ptr, png_tRNS, buf, 6); + } + + else + { + png_app_warning(png_ptr, "Can't write tRNS with an alpha channel"); + } +} +#endif + +#ifdef PNG_WRITE_bKGD_SUPPORTED +/* Write the background chunk */ +void /* PRIVATE */ +png_write_bKGD(png_structrp png_ptr, png_const_color_16p back, int color_type) +{ + png_byte buf[6]; + + png_debug(1, "in png_write_bKGD"); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + if ( +#ifdef PNG_MNG_FEATURES_SUPPORTED + (png_ptr->num_palette != 0 || + (png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE) == 0) && +#endif + back->index >= png_ptr->num_palette) + { + png_warning(png_ptr, "Invalid background palette index"); + return; + } + + buf[0] = back->index; + png_write_complete_chunk(png_ptr, png_bKGD, buf, 1); + } + + else if ((color_type & PNG_COLOR_MASK_COLOR) != 0) + { + png_save_uint_16(buf, back->red); + png_save_uint_16(buf + 2, back->green); + png_save_uint_16(buf + 4, back->blue); +#ifdef PNG_WRITE_16BIT_SUPPORTED + if (png_ptr->bit_depth == 8 && (buf[0] | buf[2] | buf[4]) != 0) +#else + if ((buf[0] | buf[2] | buf[4]) != 0) +#endif + { + png_warning(png_ptr, + "Ignoring attempt to write 16-bit bKGD chunk " + "when bit_depth is 8"); + + return; + } + + png_write_complete_chunk(png_ptr, png_bKGD, buf, 6); + } + + else + { + if (back->gray >= (1 << png_ptr->bit_depth)) + { + png_warning(png_ptr, + "Ignoring attempt to write bKGD chunk out-of-range for bit_depth"); + + return; + } + + png_save_uint_16(buf, back->gray); + png_write_complete_chunk(png_ptr, png_bKGD, buf, 2); + } +} +#endif + +#ifdef PNG_WRITE_eXIf_SUPPORTED +/* Write the Exif data */ +void /* PRIVATE */ +png_write_eXIf(png_structrp png_ptr, png_bytep exif, int num_exif) +{ + int i; + png_byte buf[1]; + + png_debug(1, "in png_write_eXIf"); + + png_write_chunk_header(png_ptr, png_eXIf, (png_uint_32)(num_exif)); + + for (i = 0; i < num_exif; i++) + { + buf[0] = exif[i]; + png_write_chunk_data(png_ptr, buf, 1); + } + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_hIST_SUPPORTED +/* Write the histogram */ +void /* PRIVATE */ +png_write_hIST(png_structrp png_ptr, png_const_uint_16p hist, int num_hist) +{ + int i; + png_byte buf[3]; + + png_debug(1, "in png_write_hIST"); + + if (num_hist > (int)png_ptr->num_palette) + { + png_debug2(3, "num_hist = %d, num_palette = %d", num_hist, + png_ptr->num_palette); + + png_warning(png_ptr, "Invalid number of histogram entries specified"); + return; + } + + png_write_chunk_header(png_ptr, png_hIST, (png_uint_32)(num_hist * 2)); + + for (i = 0; i < num_hist; i++) + { + png_save_uint_16(buf, hist[i]); + png_write_chunk_data(png_ptr, buf, 2); + } + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_tEXt_SUPPORTED +/* Write a tEXt chunk */ +void /* PRIVATE */ +png_write_tEXt(png_structrp png_ptr, png_const_charp key, png_const_charp text, + size_t text_len) +{ + png_uint_32 key_len; + png_byte new_key[80]; + + png_debug(1, "in png_write_tEXt"); + + key_len = png_check_keyword(png_ptr, key, new_key); + + if (key_len == 0) + png_error(png_ptr, "tEXt: invalid keyword"); + + if (text == NULL || *text == '\0') + text_len = 0; + + else + text_len = strlen(text); + + if (text_len > PNG_UINT_31_MAX - (key_len+1)) + png_error(png_ptr, "tEXt: text too long"); + + /* Make sure we include the 0 after the key */ + png_write_chunk_header(png_ptr, png_tEXt, + (png_uint_32)/*checked above*/(key_len + text_len + 1)); + /* + * We leave it to the application to meet PNG-1.0 requirements on the + * contents of the text. PNG-1.0 through PNG-1.2 discourage the use of + * any non-Latin-1 characters except for NEWLINE. ISO PNG will forbid them. + * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG. + */ + png_write_chunk_data(png_ptr, new_key, key_len + 1); + + if (text_len != 0) + png_write_chunk_data(png_ptr, (png_const_bytep)text, text_len); + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_zTXt_SUPPORTED +/* Write a compressed text chunk */ +void /* PRIVATE */ +png_write_zTXt(png_structrp png_ptr, png_const_charp key, png_const_charp text, + int compression) +{ + png_uint_32 key_len; + png_byte new_key[81]; + compression_state comp; + + png_debug(1, "in png_write_zTXt"); + + if (compression == PNG_TEXT_COMPRESSION_NONE) + { + png_write_tEXt(png_ptr, key, text, 0); + return; + } + + if (compression != PNG_TEXT_COMPRESSION_zTXt) + png_error(png_ptr, "zTXt: invalid compression type"); + + key_len = png_check_keyword(png_ptr, key, new_key); + + if (key_len == 0) + png_error(png_ptr, "zTXt: invalid keyword"); + + /* Add the compression method and 1 for the keyword separator. */ + new_key[++key_len] = PNG_COMPRESSION_TYPE_BASE; + ++key_len; + + /* Compute the compressed data; do it now for the length */ + png_text_compress_init(&comp, (png_const_bytep)text, + text == NULL ? 0 : strlen(text)); + + if (png_text_compress(png_ptr, png_zTXt, &comp, key_len) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + /* Write start of chunk */ + png_write_chunk_header(png_ptr, png_zTXt, key_len + comp.output_len); + + /* Write key */ + png_write_chunk_data(png_ptr, new_key, key_len); + + /* Write the compressed data */ + png_write_compressed_data_out(png_ptr, &comp); + + /* Close the chunk */ + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_iTXt_SUPPORTED +/* Write an iTXt chunk */ +void /* PRIVATE */ +png_write_iTXt(png_structrp png_ptr, int compression, png_const_charp key, + png_const_charp lang, png_const_charp lang_key, png_const_charp text) +{ + png_uint_32 key_len, prefix_len; + size_t lang_len, lang_key_len; + png_byte new_key[82]; + compression_state comp; + + png_debug(1, "in png_write_iTXt"); + + key_len = png_check_keyword(png_ptr, key, new_key); + + if (key_len == 0) + png_error(png_ptr, "iTXt: invalid keyword"); + + /* Set the compression flag */ + switch (compression) + { + case PNG_ITXT_COMPRESSION_NONE: + case PNG_TEXT_COMPRESSION_NONE: + compression = new_key[++key_len] = 0; /* no compression */ + break; + + case PNG_TEXT_COMPRESSION_zTXt: + case PNG_ITXT_COMPRESSION_zTXt: + compression = new_key[++key_len] = 1; /* compressed */ + break; + + default: + png_error(png_ptr, "iTXt: invalid compression"); + } + + new_key[++key_len] = PNG_COMPRESSION_TYPE_BASE; + ++key_len; /* for the keywod separator */ + + /* We leave it to the application to meet PNG-1.0 requirements on the + * contents of the text. PNG-1.0 through PNG-1.2 discourage the use of + * any non-Latin-1 characters except for NEWLINE. ISO PNG, however, + * specifies that the text is UTF-8 and this really doesn't require any + * checking. + * + * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG. + * + * TODO: validate the language tag correctly (see the spec.) + */ + if (lang == NULL) lang = ""; /* empty language is valid */ + lang_len = strlen(lang)+1; + if (lang_key == NULL) lang_key = ""; /* may be empty */ + lang_key_len = strlen(lang_key)+1; + if (text == NULL) text = ""; /* may be empty */ + + prefix_len = key_len; + if (lang_len > PNG_UINT_31_MAX-prefix_len) + prefix_len = PNG_UINT_31_MAX; + else + prefix_len = (png_uint_32)(prefix_len + lang_len); + + if (lang_key_len > PNG_UINT_31_MAX-prefix_len) + prefix_len = PNG_UINT_31_MAX; + else + prefix_len = (png_uint_32)(prefix_len + lang_key_len); + + png_text_compress_init(&comp, (png_const_bytep)text, strlen(text)); + + if (compression != 0) + { + if (png_text_compress(png_ptr, png_iTXt, &comp, prefix_len) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + } + + else + { + if (comp.input_len > PNG_UINT_31_MAX-prefix_len) + png_error(png_ptr, "iTXt: uncompressed text too long"); + + /* So the string will fit in a chunk: */ + comp.output_len = (png_uint_32)/*SAFE*/comp.input_len; + } + + png_write_chunk_header(png_ptr, png_iTXt, comp.output_len + prefix_len); + + png_write_chunk_data(png_ptr, new_key, key_len); + + png_write_chunk_data(png_ptr, (png_const_bytep)lang, lang_len); + + png_write_chunk_data(png_ptr, (png_const_bytep)lang_key, lang_key_len); + + if (compression != 0) + png_write_compressed_data_out(png_ptr, &comp); + + else + png_write_chunk_data(png_ptr, (png_const_bytep)text, comp.output_len); + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_oFFs_SUPPORTED +/* Write the oFFs chunk */ +void /* PRIVATE */ +png_write_oFFs(png_structrp png_ptr, png_int_32 x_offset, png_int_32 y_offset, + int unit_type) +{ + png_byte buf[9]; + + png_debug(1, "in png_write_oFFs"); + + if (unit_type >= PNG_OFFSET_LAST) + png_warning(png_ptr, "Unrecognized unit type for oFFs chunk"); + + png_save_int_32(buf, x_offset); + png_save_int_32(buf + 4, y_offset); + buf[8] = (png_byte)unit_type; + + png_write_complete_chunk(png_ptr, png_oFFs, buf, 9); +} +#endif +#ifdef PNG_WRITE_pCAL_SUPPORTED +/* Write the pCAL chunk (described in the PNG extensions document) */ +void /* PRIVATE */ +png_write_pCAL(png_structrp png_ptr, png_charp purpose, png_int_32 X0, + png_int_32 X1, int type, int nparams, png_const_charp units, + png_charpp params) +{ + png_uint_32 purpose_len; + size_t units_len, total_len; + png_size_tp params_len; + png_byte buf[10]; + png_byte new_purpose[80]; + int i; + + png_debug1(1, "in png_write_pCAL (%d parameters)", nparams); + + if (type >= PNG_EQUATION_LAST) + png_error(png_ptr, "Unrecognized equation type for pCAL chunk"); + + purpose_len = png_check_keyword(png_ptr, purpose, new_purpose); + + if (purpose_len == 0) + png_error(png_ptr, "pCAL: invalid keyword"); + + ++purpose_len; /* terminator */ + + png_debug1(3, "pCAL purpose length = %d", (int)purpose_len); + units_len = strlen(units) + (nparams == 0 ? 0 : 1); + png_debug1(3, "pCAL units length = %d", (int)units_len); + total_len = purpose_len + units_len + 10; + + params_len = (png_size_tp)png_malloc(png_ptr, + (png_alloc_size_t)((png_alloc_size_t)nparams * (sizeof (size_t)))); + + /* Find the length of each parameter, making sure we don't count the + * null terminator for the last parameter. + */ + for (i = 0; i < nparams; i++) + { + params_len[i] = strlen(params[i]) + (i == nparams - 1 ? 0 : 1); + png_debug2(3, "pCAL parameter %d length = %lu", i, + (unsigned long)params_len[i]); + total_len += params_len[i]; + } + + png_debug1(3, "pCAL total length = %d", (int)total_len); + png_write_chunk_header(png_ptr, png_pCAL, (png_uint_32)total_len); + png_write_chunk_data(png_ptr, new_purpose, purpose_len); + png_save_int_32(buf, X0); + png_save_int_32(buf + 4, X1); + buf[8] = (png_byte)type; + buf[9] = (png_byte)nparams; + png_write_chunk_data(png_ptr, buf, 10); + png_write_chunk_data(png_ptr, (png_const_bytep)units, (size_t)units_len); + + for (i = 0; i < nparams; i++) + { + png_write_chunk_data(png_ptr, (png_const_bytep)params[i], params_len[i]); + } + + png_free(png_ptr, params_len); + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_sCAL_SUPPORTED +/* Write the sCAL chunk */ +void /* PRIVATE */ +png_write_sCAL_s(png_structrp png_ptr, int unit, png_const_charp width, + png_const_charp height) +{ + png_byte buf[64]; + size_t wlen, hlen, total_len; + + png_debug(1, "in png_write_sCAL_s"); + + wlen = strlen(width); + hlen = strlen(height); + total_len = wlen + hlen + 2; + + if (total_len > 64) + { + png_warning(png_ptr, "Can't write sCAL (buffer too small)"); + return; + } + + buf[0] = (png_byte)unit; + memcpy(buf + 1, width, wlen + 1); /* Append the '\0' here */ + memcpy(buf + wlen + 2, height, hlen); /* Do NOT append the '\0' here */ + + png_debug1(3, "sCAL total length = %u", (unsigned int)total_len); + png_write_complete_chunk(png_ptr, png_sCAL, buf, total_len); +} +#endif + +#ifdef PNG_WRITE_pHYs_SUPPORTED +/* Write the pHYs chunk */ +void /* PRIVATE */ +png_write_pHYs(png_structrp png_ptr, png_uint_32 x_pixels_per_unit, + png_uint_32 y_pixels_per_unit, + int unit_type) +{ + png_byte buf[9]; + + png_debug(1, "in png_write_pHYs"); + + if (unit_type >= PNG_RESOLUTION_LAST) + png_warning(png_ptr, "Unrecognized unit type for pHYs chunk"); + + png_save_uint_32(buf, x_pixels_per_unit); + png_save_uint_32(buf + 4, y_pixels_per_unit); + buf[8] = (png_byte)unit_type; + + png_write_complete_chunk(png_ptr, png_pHYs, buf, 9); +} +#endif + +#ifdef PNG_WRITE_tIME_SUPPORTED +/* Write the tIME chunk. Use either png_convert_from_struct_tm() + * or png_convert_from_time_t(), or fill in the structure yourself. + */ +void /* PRIVATE */ +png_write_tIME(png_structrp png_ptr, png_const_timep mod_time) +{ + png_byte buf[7]; + + png_debug(1, "in png_write_tIME"); + + if (mod_time->month > 12 || mod_time->month < 1 || + mod_time->day > 31 || mod_time->day < 1 || + mod_time->hour > 23 || mod_time->second > 60) + { + png_warning(png_ptr, "Invalid time specified for tIME chunk"); + return; + } + + png_save_uint_16(buf, mod_time->year); + buf[2] = mod_time->month; + buf[3] = mod_time->day; + buf[4] = mod_time->hour; + buf[5] = mod_time->minute; + buf[6] = mod_time->second; + + png_write_complete_chunk(png_ptr, png_tIME, buf, 7); +} +#endif + +/* Initializes the row writing capability of libpng */ +void /* PRIVATE */ +png_write_start_row(png_structrp png_ptr) +{ +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static const png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static const png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static const png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static const png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; +#endif + + png_alloc_size_t buf_size; + int usr_pixel_depth; + +#ifdef PNG_WRITE_FILTER_SUPPORTED + png_byte filters; +#endif + + png_debug(1, "in png_write_start_row"); + + usr_pixel_depth = png_ptr->usr_channels * png_ptr->usr_bit_depth; + buf_size = PNG_ROWBYTES(usr_pixel_depth, png_ptr->width) + 1; + + /* 1.5.6: added to allow checking in the row write code. */ + png_ptr->transformed_pixel_depth = png_ptr->pixel_depth; + png_ptr->maximum_pixel_depth = (png_byte)usr_pixel_depth; + + /* Set up row buffer */ + png_ptr->row_buf = png_voidcast(png_bytep, png_malloc(png_ptr, buf_size)); + + png_ptr->row_buf[0] = PNG_FILTER_VALUE_NONE; + +#ifdef PNG_WRITE_FILTER_SUPPORTED + filters = png_ptr->do_filter; + + if (png_ptr->height == 1) + filters &= 0xff & ~(PNG_FILTER_UP|PNG_FILTER_AVG|PNG_FILTER_PAETH); + + if (png_ptr->width == 1) + filters &= 0xff & ~(PNG_FILTER_SUB|PNG_FILTER_AVG|PNG_FILTER_PAETH); + + if (filters == 0) + filters = PNG_FILTER_NONE; + + png_ptr->do_filter = filters; + + if (((filters & (PNG_FILTER_SUB | PNG_FILTER_UP | PNG_FILTER_AVG | + PNG_FILTER_PAETH)) != 0) && png_ptr->try_row == NULL) + { + int num_filters = 0; + + png_ptr->try_row = png_voidcast(png_bytep, png_malloc(png_ptr, buf_size)); + + if (filters & PNG_FILTER_SUB) + num_filters++; + + if (filters & PNG_FILTER_UP) + num_filters++; + + if (filters & PNG_FILTER_AVG) + num_filters++; + + if (filters & PNG_FILTER_PAETH) + num_filters++; + + if (num_filters > 1) + png_ptr->tst_row = png_voidcast(png_bytep, png_malloc(png_ptr, + buf_size)); + } + + /* We only need to keep the previous row if we are using one of the following + * filters. + */ + if ((filters & (PNG_FILTER_AVG | PNG_FILTER_UP | PNG_FILTER_PAETH)) != 0) + png_ptr->prev_row = png_voidcast(png_bytep, + png_calloc(png_ptr, buf_size)); +#endif /* WRITE_FILTER */ + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* If interlaced, we need to set up width and height of pass */ + if (png_ptr->interlaced != 0) + { + if ((png_ptr->transformations & PNG_INTERLACE) == 0) + { + png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 - + png_pass_ystart[0]) / png_pass_yinc[0]; + + png_ptr->usr_width = (png_ptr->width + png_pass_inc[0] - 1 - + png_pass_start[0]) / png_pass_inc[0]; + } + + else + { + png_ptr->num_rows = png_ptr->height; + png_ptr->usr_width = png_ptr->width; + } + } + + else +#endif + { + png_ptr->num_rows = png_ptr->height; + png_ptr->usr_width = png_ptr->width; + } +} + +/* Internal use only. Called when finished processing a row of data. */ +void /* PRIVATE */ +png_write_finish_row(png_structrp png_ptr) +{ +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static const png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static const png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static const png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static const png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; +#endif + + png_debug(1, "in png_write_finish_row"); + + /* Next row */ + png_ptr->row_number++; + + /* See if we are done */ + if (png_ptr->row_number < png_ptr->num_rows) + return; + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* If interlaced, go to next pass */ + if (png_ptr->interlaced != 0) + { + png_ptr->row_number = 0; + if ((png_ptr->transformations & PNG_INTERLACE) != 0) + { + png_ptr->pass++; + } + + else + { + /* Loop until we find a non-zero width or height pass */ + do + { + png_ptr->pass++; + + if (png_ptr->pass >= 7) + break; + + png_ptr->usr_width = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + + png_ptr->num_rows = (png_ptr->height + + png_pass_yinc[png_ptr->pass] - 1 - + png_pass_ystart[png_ptr->pass]) / + png_pass_yinc[png_ptr->pass]; + + if ((png_ptr->transformations & PNG_INTERLACE) != 0) + break; + + } while (png_ptr->usr_width == 0 || png_ptr->num_rows == 0); + + } + + /* Reset the row above the image for the next pass */ + if (png_ptr->pass < 7) + { + if (png_ptr->prev_row != NULL) + memset(png_ptr->prev_row, 0, + PNG_ROWBYTES(png_ptr->usr_channels * + png_ptr->usr_bit_depth, png_ptr->width) + 1); + + return; + } + } +#endif + + /* If we get here, we've just written the last row, so we need + to flush the compressor */ + png_compress_IDAT(png_ptr, NULL, 0, Z_FINISH); +} + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED +/* Pick out the correct pixels for the interlace pass. + * The basic idea here is to go through the row with a source + * pointer and a destination pointer (sp and dp), and copy the + * correct pixels for the pass. As the row gets compacted, + * sp will always be >= dp, so we should never overwrite anything. + * See the default: case for the easiest code to understand. + */ +void /* PRIVATE */ +png_do_write_interlace(png_row_infop row_info, png_bytep row, int pass) +{ + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static const png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static const png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + png_debug(1, "in png_do_write_interlace"); + + /* We don't have to do anything on the last pass (6) */ + if (pass < 6) + { + /* Each pixel depth is handled separately */ + switch (row_info->pixel_depth) + { + case 1: + { + png_bytep sp; + png_bytep dp; + unsigned int shift; + int d; + int value; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + dp = row; + d = 0; + shift = 7; + + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + sp = row + (size_t)(i >> 3); + value = (int)(*sp >> (7 - (int)(i & 0x07))) & 0x01; + d |= (value << shift); + + if (shift == 0) + { + shift = 7; + *dp++ = (png_byte)d; + d = 0; + } + + else + shift--; + + } + if (shift != 7) + *dp = (png_byte)d; + + break; + } + + case 2: + { + png_bytep sp; + png_bytep dp; + unsigned int shift; + int d; + int value; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + dp = row; + shift = 6; + d = 0; + + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + sp = row + (size_t)(i >> 2); + value = (*sp >> ((3 - (int)(i & 0x03)) << 1)) & 0x03; + d |= (value << shift); + + if (shift == 0) + { + shift = 6; + *dp++ = (png_byte)d; + d = 0; + } + + else + shift -= 2; + } + if (shift != 6) + *dp = (png_byte)d; + + break; + } + + case 4: + { + png_bytep sp; + png_bytep dp; + unsigned int shift; + int d; + int value; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + dp = row; + shift = 4; + d = 0; + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + sp = row + (size_t)(i >> 1); + value = (*sp >> ((1 - (int)(i & 0x01)) << 2)) & 0x0f; + d |= (value << shift); + + if (shift == 0) + { + shift = 4; + *dp++ = (png_byte)d; + d = 0; + } + + else + shift -= 4; + } + if (shift != 4) + *dp = (png_byte)d; + + break; + } + + default: + { + png_bytep sp; + png_bytep dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + size_t pixel_bytes; + + /* Start at the beginning */ + dp = row; + + /* Find out how many bytes each pixel takes up */ + pixel_bytes = (row_info->pixel_depth >> 3); + + /* Loop through the row, only looking at the pixels that matter */ + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + /* Find out where the original pixel is */ + sp = row + (size_t)i * pixel_bytes; + + /* Move the pixel */ + if (dp != sp) + memcpy(dp, sp, pixel_bytes); + + /* Next pixel */ + dp += pixel_bytes; + } + break; + } + } + /* Set new row width */ + row_info->width = (row_info->width + + png_pass_inc[pass] - 1 - + png_pass_start[pass]) / + png_pass_inc[pass]; + + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, + row_info->width); + } +} +#endif + + +/* This filters the row, chooses which filter to use, if it has not already + * been specified by the application, and then writes the row out with the + * chosen filter. + */ +static void /* PRIVATE */ +png_write_filtered_row(png_structrp png_ptr, png_bytep filtered_row, + size_t row_bytes); + +#ifdef PNG_WRITE_FILTER_SUPPORTED +static size_t /* PRIVATE */ +png_setup_sub_row(png_structrp png_ptr, png_uint_32 bpp, + size_t row_bytes, size_t lmins) +{ + png_bytep rp, dp, lp; + size_t i; + size_t sum = 0; + unsigned int v; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_SUB; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1; i < bpp; + i++, rp++, dp++) + { + v = *dp = *rp; +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + } + + for (lp = png_ptr->row_buf + 1; i < row_bytes; + i++, rp++, lp++, dp++) + { + v = *dp = (png_byte)(((int)*rp - (int)*lp) & 0xff); +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + + return (sum); +} + +static void /* PRIVATE */ +png_setup_sub_row_only(png_structrp png_ptr, png_uint_32 bpp, + size_t row_bytes) +{ + png_bytep rp, dp, lp; + size_t i; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_SUB; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1; i < bpp; + i++, rp++, dp++) + { + *dp = *rp; + } + + for (lp = png_ptr->row_buf + 1; i < row_bytes; + i++, rp++, lp++, dp++) + { + *dp = (png_byte)(((int)*rp - (int)*lp) & 0xff); + } +} + +static size_t /* PRIVATE */ +png_setup_up_row(png_structrp png_ptr, size_t row_bytes, size_t lmins) +{ + png_bytep rp, dp, pp; + size_t i; + size_t sum = 0; + unsigned int v; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_UP; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1, + pp = png_ptr->prev_row + 1; i < row_bytes; + i++, rp++, pp++, dp++) + { + v = *dp = (png_byte)(((int)*rp - (int)*pp) & 0xff); +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + + return (sum); +} +static void /* PRIVATE */ +png_setup_up_row_only(png_structrp png_ptr, size_t row_bytes) +{ + png_bytep rp, dp, pp; + size_t i; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_UP; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1, + pp = png_ptr->prev_row + 1; i < row_bytes; + i++, rp++, pp++, dp++) + { + *dp = (png_byte)(((int)*rp - (int)*pp) & 0xff); + } +} + +static size_t /* PRIVATE */ +png_setup_avg_row(png_structrp png_ptr, png_uint_32 bpp, + size_t row_bytes, size_t lmins) +{ + png_bytep rp, dp, pp, lp; + png_uint_32 i; + size_t sum = 0; + unsigned int v; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_AVG; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1, + pp = png_ptr->prev_row + 1; i < bpp; i++) + { + v = *dp++ = (png_byte)(((int)*rp++ - ((int)*pp++ / 2)) & 0xff); + +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + } + + for (lp = png_ptr->row_buf + 1; i < row_bytes; i++) + { + v = *dp++ = (png_byte)(((int)*rp++ - (((int)*pp++ + (int)*lp++) / 2)) + & 0xff); + +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + + return (sum); +} +static void /* PRIVATE */ +png_setup_avg_row_only(png_structrp png_ptr, png_uint_32 bpp, + size_t row_bytes) +{ + png_bytep rp, dp, pp, lp; + png_uint_32 i; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_AVG; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1, + pp = png_ptr->prev_row + 1; i < bpp; i++) + { + *dp++ = (png_byte)(((int)*rp++ - ((int)*pp++ / 2)) & 0xff); + } + + for (lp = png_ptr->row_buf + 1; i < row_bytes; i++) + { + *dp++ = (png_byte)(((int)*rp++ - (((int)*pp++ + (int)*lp++) / 2)) + & 0xff); + } +} + +static size_t /* PRIVATE */ +png_setup_paeth_row(png_structrp png_ptr, png_uint_32 bpp, + size_t row_bytes, size_t lmins) +{ + png_bytep rp, dp, pp, cp, lp; + size_t i; + size_t sum = 0; + unsigned int v; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_PAETH; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1, + pp = png_ptr->prev_row + 1; i < bpp; i++) + { + v = *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff); + +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + } + + for (lp = png_ptr->row_buf + 1, cp = png_ptr->prev_row + 1; i < row_bytes; + i++) + { + int a, b, c, pa, pb, pc, p; + + b = *pp++; + c = *cp++; + a = *lp++; + + p = b - c; + pc = a - c; + +#ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +#else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +#endif + + p = (pa <= pb && pa <=pc) ? a : (pb <= pc) ? b : c; + + v = *dp++ = (png_byte)(((int)*rp++ - p) & 0xff); + +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + + return (sum); +} +static void /* PRIVATE */ +png_setup_paeth_row_only(png_structrp png_ptr, png_uint_32 bpp, + size_t row_bytes) +{ + png_bytep rp, dp, pp, cp, lp; + size_t i; + + png_ptr->try_row[0] = PNG_FILTER_VALUE_PAETH; + + for (i = 0, rp = png_ptr->row_buf + 1, dp = png_ptr->try_row + 1, + pp = png_ptr->prev_row + 1; i < bpp; i++) + { + *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff); + } + + for (lp = png_ptr->row_buf + 1, cp = png_ptr->prev_row + 1; i < row_bytes; + i++) + { + int a, b, c, pa, pb, pc, p; + + b = *pp++; + c = *cp++; + a = *lp++; + + p = b - c; + pc = a - c; + +#ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +#else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +#endif + + p = (pa <= pb && pa <=pc) ? a : (pb <= pc) ? b : c; + + *dp++ = (png_byte)(((int)*rp++ - p) & 0xff); + } +} +#endif /* WRITE_FILTER */ + +void /* PRIVATE */ +png_write_find_filter(png_structrp png_ptr, png_row_infop row_info) +{ +#ifndef PNG_WRITE_FILTER_SUPPORTED + png_write_filtered_row(png_ptr, png_ptr->row_buf, row_info->rowbytes+1); +#else + unsigned int filter_to_do = png_ptr->do_filter; + png_bytep row_buf; + png_bytep best_row; + png_uint_32 bpp; + size_t mins; + size_t row_bytes = row_info->rowbytes; + + png_debug(1, "in png_write_find_filter"); + + /* Find out how many bytes offset each pixel is */ + bpp = (row_info->pixel_depth + 7) >> 3; + + row_buf = png_ptr->row_buf; + mins = PNG_SIZE_MAX - 256/* so we can detect potential overflow of the + running sum */; + + /* The prediction method we use is to find which method provides the + * smallest value when summing the absolute values of the distances + * from zero, using anything >= 128 as negative numbers. This is known + * as the "minimum sum of absolute differences" heuristic. Other + * heuristics are the "weighted minimum sum of absolute differences" + * (experimental and can in theory improve compression), and the "zlib + * predictive" method (not implemented yet), which does test compressions + * of lines using different filter methods, and then chooses the + * (series of) filter(s) that give minimum compressed data size (VERY + * computationally expensive). + * + * GRR 980525: consider also + * + * (1) minimum sum of absolute differences from running average (i.e., + * keep running sum of non-absolute differences & count of bytes) + * [track dispersion, too? restart average if dispersion too large?] + * + * (1b) minimum sum of absolute differences from sliding average, probably + * with window size <= deflate window (usually 32K) + * + * (2) minimum sum of squared differences from zero or running average + * (i.e., ~ root-mean-square approach) + */ + + + /* We don't need to test the 'no filter' case if this is the only filter + * that has been chosen, as it doesn't actually do anything to the data. + */ + best_row = png_ptr->row_buf; + + if (PNG_SIZE_MAX/128 <= row_bytes) + { + /* Overflow can occur in the calculation, just select the lowest set + * filter. + */ + filter_to_do &= 0U-filter_to_do; + } + else if ((filter_to_do & PNG_FILTER_NONE) != 0 && + filter_to_do != PNG_FILTER_NONE) + { + /* Overflow not possible and multiple filters in the list, including the + * 'none' filter. + */ + png_bytep rp; + size_t sum = 0; + size_t i; + unsigned int v; + + { + for (i = 0, rp = row_buf + 1; i < row_bytes; i++, rp++) + { + v = *rp; +#ifdef PNG_USE_ABS + sum += 128 - abs((int)v - 128); +#else + sum += (v < 128) ? v : 256 - v; +#endif + } + } + + mins = sum; + } + + /* Sub filter */ + if (filter_to_do == PNG_FILTER_SUB) + /* It's the only filter so no testing is needed */ + { + png_setup_sub_row_only(png_ptr, bpp, row_bytes); + best_row = png_ptr->try_row; + } + + else if ((filter_to_do & PNG_FILTER_SUB) != 0) + { + size_t sum; + size_t lmins = mins; + + sum = png_setup_sub_row(png_ptr, bpp, row_bytes, lmins); + + if (sum < mins) + { + mins = sum; + best_row = png_ptr->try_row; + if (png_ptr->tst_row != NULL) + { + png_ptr->try_row = png_ptr->tst_row; + png_ptr->tst_row = best_row; + } + } + } + + /* Up filter */ + if (filter_to_do == PNG_FILTER_UP) + { + png_setup_up_row_only(png_ptr, row_bytes); + best_row = png_ptr->try_row; + } + + else if ((filter_to_do & PNG_FILTER_UP) != 0) + { + size_t sum; + size_t lmins = mins; + + sum = png_setup_up_row(png_ptr, row_bytes, lmins); + + if (sum < mins) + { + mins = sum; + best_row = png_ptr->try_row; + if (png_ptr->tst_row != NULL) + { + png_ptr->try_row = png_ptr->tst_row; + png_ptr->tst_row = best_row; + } + } + } + + /* Avg filter */ + if (filter_to_do == PNG_FILTER_AVG) + { + png_setup_avg_row_only(png_ptr, bpp, row_bytes); + best_row = png_ptr->try_row; + } + + else if ((filter_to_do & PNG_FILTER_AVG) != 0) + { + size_t sum; + size_t lmins = mins; + + sum= png_setup_avg_row(png_ptr, bpp, row_bytes, lmins); + + if (sum < mins) + { + mins = sum; + best_row = png_ptr->try_row; + if (png_ptr->tst_row != NULL) + { + png_ptr->try_row = png_ptr->tst_row; + png_ptr->tst_row = best_row; + } + } + } + + /* Paeth filter */ + if (filter_to_do == PNG_FILTER_PAETH) + { + png_setup_paeth_row_only(png_ptr, bpp, row_bytes); + best_row = png_ptr->try_row; + } + + else if ((filter_to_do & PNG_FILTER_PAETH) != 0) + { + size_t sum; + size_t lmins = mins; + + sum = png_setup_paeth_row(png_ptr, bpp, row_bytes, lmins); + + if (sum < mins) + { + best_row = png_ptr->try_row; + if (png_ptr->tst_row != NULL) + { + png_ptr->try_row = png_ptr->tst_row; + png_ptr->tst_row = best_row; + } + } + } + + /* Do the actual writing of the filtered row data from the chosen filter. */ + png_write_filtered_row(png_ptr, best_row, row_info->rowbytes+1); + +#endif /* WRITE_FILTER */ +} + + +/* Do the actual writing of a previously filtered row. */ +static void +png_write_filtered_row(png_structrp png_ptr, png_bytep filtered_row, + size_t full_row_length/*includes filter byte*/) +{ + png_debug(1, "in png_write_filtered_row"); + + png_debug1(2, "filter = %d", filtered_row[0]); + + png_compress_IDAT(png_ptr, filtered_row, full_row_length, Z_NO_FLUSH); + +#ifdef PNG_WRITE_FILTER_SUPPORTED + /* Swap the current and previous rows */ + if (png_ptr->prev_row != NULL) + { + png_bytep tptr; + + tptr = png_ptr->prev_row; + png_ptr->prev_row = png_ptr->row_buf; + png_ptr->row_buf = tptr; + } +#endif /* WRITE_FILTER */ + + /* Finish row - updates counters and flushes zlib if last row */ + png_write_finish_row(png_ptr); + +#ifdef PNG_WRITE_FLUSH_SUPPORTED + png_ptr->flush_rows++; + + if (png_ptr->flush_dist > 0 && + png_ptr->flush_rows >= png_ptr->flush_dist) + { + png_write_flush(png_ptr); + } +#endif /* WRITE_FLUSH */ +} +#endif /* WRITE */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/curverasterbuffer.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/curverasterbuffer.h new file mode 100644 index 0000000..24dc776 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/curverasterbuffer.h @@ -0,0 +1,115 @@ +#ifndef _LICE_CURVE_RASTER_BUFFER_H_ +#define _LICE_CURVE_RASTER_BUFFER_H_ + +class CurveRasterBuffer +{ + static void CopyPixelNoClip(LICE_IBitmap *bmp, int x, int y, LICE_pixel col, double alpha) + { + LICE_pixel *p=bmp->getBits()+y*bmp->getRowSpan()+x; + if (*p == col || alpha <= 0.0) return; + + if (alpha >= 1.0) + { + *p=col; + return; + } + + const int ia=256-(int)(alpha*256.0); + int r=LICE_GETR(col), g=LICE_GETG(col), b=LICE_GETB(col); + LICE_pixel_chan *c=(LICE_pixel_chan*)p; + c[LICE_PIXEL_R]=r+((c[LICE_PIXEL_R]-r)*ia)/256; + c[LICE_PIXEL_G]=g+((c[LICE_PIXEL_G]-g)*ia)/256; + c[LICE_PIXEL_B]=b+((c[LICE_PIXEL_B]-b)*ia)/256; + } + + static void DrawSlice(LICE_IBitmap* bmp, int x, double y1, double y2, int pxh, LICE_pixel color, float alpha) + { + const int iy1=(int)y1, iy2=(int)y2; + + if (iy1 == iy2) + { + if (iy1 >= 0 && iy1 < pxh) CopyPixelNoClip(bmp, x, iy1, color, (y2-y1)*alpha); + } + else + { + if (iy1 >= 0 && iy1 < pxh) CopyPixelNoClip(bmp, x, iy1, color, (1+iy1-y1)*alpha); + + if (iy2 > iy1+1) + { + int iy; + const int n=min(iy2, pxh); + for (iy=max(iy1+1, 0); iy < n; ++iy) + { + CopyPixelNoClip(bmp, x, iy, color, alpha); + } + } + + if (iy2 >= 0 && iy2 < pxh) CopyPixelNoClip(bmp, x, iy2, color, (y2-iy2)*alpha); + } + } + + public: + int xext[2],bmw; + float *sb; + + CurveRasterBuffer(int w, WDL_TypedBuf *sbuf) + { + xext[0]=bmw=w; + xext[1]=0; + sb = sbuf->ResizeOK(w*2,false); + } + + void addpt(int xpos, double ypos) + { + if (xpos >= 0 && xpos < bmw) + { + float *p = sb + xpos*2; + const int ext0=xext[0], ext1=xext[1]; + if (ext0 <= ext1) // existing range + { + if (xpos < ext0) + { + memset(p+2, 0, (ext0-xpos-1)*2*sizeof(*p)); + p[0]=p[1]=ypos; + xext[0]=xpos; + } + else if (xpos > ext1) + { + memset(sb + (ext1+1)*2, 0, (xpos-(ext1+1))*2*sizeof(*p)); + p[0]=p[1]=ypos; + xext[1]=xpos; + } + else if (p[0] == 0.0 && p[1] == 0.0) p[0] = p[1] = ypos; + else + { + if (ypos < p[0]) p[0] = ypos; + else if (ypos > p[1]) p[1] = ypos; + } + } + else // first point + { + xext[0]=xext[1] = xpos; + p[0]=p[1]=ypos; + } + } + } + + void Draw(LICE_IBitmap *bm, LICE_pixel color, float alpha) + { + int bmh = bm->getHeight(); + const int sc = (int) (INT_PTR)bm->Extended(LICE_EXT_GET_SCALING,NULL); + if (sc > 256) bmh = bmh * sc / 256; + const int xmax = xext[1]; + int x = xext[0]; + const float *sbuf = sb+x*2; + for (; x <= xmax; x ++) + { + const double v1 = sbuf[0], v2 = sbuf[1]; + if (v2>v1) DrawSlice(bm, x,v1,v2, bmh, color,alpha); + sbuf+= 2; + } + } +}; + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/WGLEXT.H b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/WGLEXT.H new file mode 100644 index 0000000..9dcee14 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/WGLEXT.H @@ -0,0 +1,631 @@ +#ifndef __wglext_h_ +#define __wglext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2004 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +/* Header file version number */ +/* wglext.h last updated 2005/01/07 */ +/* Current version at http://oss.sgi.com/projects/ogl-sample/registry/ */ +#define WGL_WGLEXT_VERSION 6 + +#ifndef WGL_ARB_buffer_region +#define WGL_FRONT_COLOR_BUFFER_BIT_ARB 0x00000001 +#define WGL_BACK_COLOR_BUFFER_BIT_ARB 0x00000002 +#define WGL_DEPTH_BUFFER_BIT_ARB 0x00000004 +#define WGL_STENCIL_BUFFER_BIT_ARB 0x00000008 +#endif + +#ifndef WGL_ARB_multisample +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#endif + +#ifndef WGL_ARB_extensions_string +#endif + +#ifndef WGL_ARB_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#endif + +#ifndef WGL_ARB_make_current_read +#define ERROR_INVALID_PIXEL_TYPE_ARB 0x2043 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +#endif + +#ifndef WGL_ARB_pbuffer +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E +#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030 +#define WGL_PBUFFER_LARGEST_ARB 0x2033 +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_PBUFFER_LOST_ARB 0x2036 +#endif + +#ifndef WGL_ARB_render_texture +#define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_MIPMAP_TEXTURE_ARB 0x2074 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 +#define WGL_TEXTURE_1D_ARB 0x2079 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_MIPMAP_LEVEL_ARB 0x207B +#define WGL_CUBE_MAP_FACE_ARB 0x207C +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x207E +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x207F +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x2080 +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x2081 +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x2082 +#define WGL_FRONT_LEFT_ARB 0x2083 +#define WGL_FRONT_RIGHT_ARB 0x2084 +#define WGL_BACK_LEFT_ARB 0x2085 +#define WGL_BACK_RIGHT_ARB 0x2086 +#define WGL_AUX0_ARB 0x2087 +#define WGL_AUX1_ARB 0x2088 +#define WGL_AUX2_ARB 0x2089 +#define WGL_AUX3_ARB 0x208A +#define WGL_AUX4_ARB 0x208B +#define WGL_AUX5_ARB 0x208C +#define WGL_AUX6_ARB 0x208D +#define WGL_AUX7_ARB 0x208E +#define WGL_AUX8_ARB 0x208F +#define WGL_AUX9_ARB 0x2090 +#endif + +#ifndef WGL_ARB_pixel_format_float +#define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0 +#endif + +#ifndef WGL_EXT_make_current_read +#define ERROR_INVALID_PIXEL_TYPE_EXT 0x2043 +#endif + +#ifndef WGL_EXT_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000 +#define WGL_DRAW_TO_WINDOW_EXT 0x2001 +#define WGL_DRAW_TO_BITMAP_EXT 0x2002 +#define WGL_ACCELERATION_EXT 0x2003 +#define WGL_NEED_PALETTE_EXT 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006 +#define WGL_SWAP_METHOD_EXT 0x2007 +#define WGL_NUMBER_OVERLAYS_EXT 0x2008 +#define WGL_NUMBER_UNDERLAYS_EXT 0x2009 +#define WGL_TRANSPARENT_EXT 0x200A +#define WGL_TRANSPARENT_VALUE_EXT 0x200B +#define WGL_SHARE_DEPTH_EXT 0x200C +#define WGL_SHARE_STENCIL_EXT 0x200D +#define WGL_SHARE_ACCUM_EXT 0x200E +#define WGL_SUPPORT_GDI_EXT 0x200F +#define WGL_SUPPORT_OPENGL_EXT 0x2010 +#define WGL_DOUBLE_BUFFER_EXT 0x2011 +#define WGL_STEREO_EXT 0x2012 +#define WGL_PIXEL_TYPE_EXT 0x2013 +#define WGL_COLOR_BITS_EXT 0x2014 +#define WGL_RED_BITS_EXT 0x2015 +#define WGL_RED_SHIFT_EXT 0x2016 +#define WGL_GREEN_BITS_EXT 0x2017 +#define WGL_GREEN_SHIFT_EXT 0x2018 +#define WGL_BLUE_BITS_EXT 0x2019 +#define WGL_BLUE_SHIFT_EXT 0x201A +#define WGL_ALPHA_BITS_EXT 0x201B +#define WGL_ALPHA_SHIFT_EXT 0x201C +#define WGL_ACCUM_BITS_EXT 0x201D +#define WGL_ACCUM_RED_BITS_EXT 0x201E +#define WGL_ACCUM_GREEN_BITS_EXT 0x201F +#define WGL_ACCUM_BLUE_BITS_EXT 0x2020 +#define WGL_ACCUM_ALPHA_BITS_EXT 0x2021 +#define WGL_DEPTH_BITS_EXT 0x2022 +#define WGL_STENCIL_BITS_EXT 0x2023 +#define WGL_AUX_BUFFERS_EXT 0x2024 +#define WGL_NO_ACCELERATION_EXT 0x2025 +#define WGL_GENERIC_ACCELERATION_EXT 0x2026 +#define WGL_FULL_ACCELERATION_EXT 0x2027 +#define WGL_SWAP_EXCHANGE_EXT 0x2028 +#define WGL_SWAP_COPY_EXT 0x2029 +#define WGL_SWAP_UNDEFINED_EXT 0x202A +#define WGL_TYPE_RGBA_EXT 0x202B +#define WGL_TYPE_COLORINDEX_EXT 0x202C +#endif + +#ifndef WGL_EXT_pbuffer +#define WGL_DRAW_TO_PBUFFER_EXT 0x202D +#define WGL_MAX_PBUFFER_PIXELS_EXT 0x202E +#define WGL_MAX_PBUFFER_WIDTH_EXT 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_EXT 0x2030 +#define WGL_OPTIMAL_PBUFFER_WIDTH_EXT 0x2031 +#define WGL_OPTIMAL_PBUFFER_HEIGHT_EXT 0x2032 +#define WGL_PBUFFER_LARGEST_EXT 0x2033 +#define WGL_PBUFFER_WIDTH_EXT 0x2034 +#define WGL_PBUFFER_HEIGHT_EXT 0x2035 +#endif + +#ifndef WGL_EXT_depth_float +#define WGL_DEPTH_FLOAT_EXT 0x2040 +#endif + +#ifndef WGL_3DFX_multisample +#define WGL_SAMPLE_BUFFERS_3DFX 0x2060 +#define WGL_SAMPLES_3DFX 0x2061 +#endif + +#ifndef WGL_EXT_multisample +#define WGL_SAMPLE_BUFFERS_EXT 0x2041 +#define WGL_SAMPLES_EXT 0x2042 +#endif + +#ifndef WGL_I3D_digital_video_control +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_FRAMEBUFFER_I3D 0x2050 +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_VALUE_I3D 0x2051 +#define WGL_DIGITAL_VIDEO_CURSOR_INCLUDED_I3D 0x2052 +#define WGL_DIGITAL_VIDEO_GAMMA_CORRECTED_I3D 0x2053 +#endif + +#ifndef WGL_I3D_gamma +#define WGL_GAMMA_TABLE_SIZE_I3D 0x204E +#define WGL_GAMMA_EXCLUDE_DESKTOP_I3D 0x204F +#endif + +#ifndef WGL_I3D_genlock +#define WGL_GENLOCK_SOURCE_MULTIVIEW_I3D 0x2044 +#define WGL_GENLOCK_SOURCE_EXTENAL_SYNC_I3D 0x2045 +#define WGL_GENLOCK_SOURCE_EXTENAL_FIELD_I3D 0x2046 +#define WGL_GENLOCK_SOURCE_EXTENAL_TTL_I3D 0x2047 +#define WGL_GENLOCK_SOURCE_DIGITAL_SYNC_I3D 0x2048 +#define WGL_GENLOCK_SOURCE_DIGITAL_FIELD_I3D 0x2049 +#define WGL_GENLOCK_SOURCE_EDGE_FALLING_I3D 0x204A +#define WGL_GENLOCK_SOURCE_EDGE_RISING_I3D 0x204B +#define WGL_GENLOCK_SOURCE_EDGE_BOTH_I3D 0x204C +#endif + +#ifndef WGL_I3D_image_buffer +#define WGL_IMAGE_BUFFER_MIN_ACCESS_I3D 0x00000001 +#define WGL_IMAGE_BUFFER_LOCK_I3D 0x00000002 +#endif + +#ifndef WGL_I3D_swap_frame_lock +#endif + +#ifndef WGL_NV_render_depth_texture +#define WGL_BIND_TO_TEXTURE_DEPTH_NV 0x20A3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_DEPTH_NV 0x20A4 +#define WGL_DEPTH_TEXTURE_FORMAT_NV 0x20A5 +#define WGL_TEXTURE_DEPTH_COMPONENT_NV 0x20A6 +#define WGL_DEPTH_COMPONENT_NV 0x20A7 +#endif + +#ifndef WGL_NV_render_texture_rectangle +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 +#endif + +#ifndef WGL_ATI_pixel_format_float +#define WGL_TYPE_RGBA_FLOAT_ATI 0x21A0 +#endif + +#ifndef WGL_NV_float_buffer +#define WGL_FLOAT_COMPONENTS_NV 0x20B0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 +#define WGL_TEXTURE_FLOAT_R_NV 0x20B5 +#define WGL_TEXTURE_FLOAT_RG_NV 0x20B6 +#define WGL_TEXTURE_FLOAT_RGB_NV 0x20B7 +#define WGL_TEXTURE_FLOAT_RGBA_NV 0x20B8 +#endif + + +/*************************************************************/ + +#ifndef WGL_ARB_pbuffer +DECLARE_HANDLE(HPBUFFERARB); +#endif +#ifndef WGL_EXT_pbuffer +DECLARE_HANDLE(HPBUFFEREXT); +#endif + +#ifndef WGL_ARB_buffer_region +#define WGL_ARB_buffer_region 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HANDLE WINAPI wglCreateBufferRegionARB (HDC, int, UINT); +extern VOID WINAPI wglDeleteBufferRegionARB (HANDLE); +extern BOOL WINAPI wglSaveBufferRegionARB (HANDLE, int, int, int, int); +extern BOOL WINAPI wglRestoreBufferRegionARB (HANDLE, int, int, int, int, int, int); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HANDLE (WINAPI * PFNWGLCREATEBUFFERREGIONARBPROC) (HDC hDC, int iLayerPlane, UINT uType); +typedef VOID (WINAPI * PFNWGLDELETEBUFFERREGIONARBPROC) (HANDLE hRegion); +typedef BOOL (WINAPI * PFNWGLSAVEBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height); +typedef BOOL (WINAPI * PFNWGLRESTOREBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); +#endif + +#ifndef WGL_ARB_multisample +#define WGL_ARB_multisample 1 +#endif + +#ifndef WGL_ARB_extensions_string +#define WGL_ARB_extensions_string 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern const char * WINAPI wglGetExtensionsStringARB (HDC); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc); +#endif + +#ifndef WGL_ARB_pixel_format +#define WGL_ARB_pixel_format 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetPixelFormatAttribivARB (HDC, int, int, UINT, const int *, int *); +extern BOOL WINAPI wglGetPixelFormatAttribfvARB (HDC, int, int, UINT, const int *, FLOAT *); +extern BOOL WINAPI wglChoosePixelFormatARB (HDC, const int *, const FLOAT *, UINT, int *, UINT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif + +#ifndef WGL_ARB_make_current_read +#define WGL_ARB_make_current_read 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglMakeContextCurrentARB (HDC, HDC, HGLRC); +extern HDC WINAPI wglGetCurrentReadDCARB (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTARBPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCARBPROC) (void); +#endif + +#ifndef WGL_ARB_pbuffer +#define WGL_ARB_pbuffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HPBUFFERARB WINAPI wglCreatePbufferARB (HDC, int, int, int, const int *); +extern HDC WINAPI wglGetPbufferDCARB (HPBUFFERARB); +extern int WINAPI wglReleasePbufferDCARB (HPBUFFERARB, HDC); +extern BOOL WINAPI wglDestroyPbufferARB (HPBUFFERARB); +extern BOOL WINAPI wglQueryPbufferARB (HPBUFFERARB, int, int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +#endif + +#ifndef WGL_ARB_render_texture +#define WGL_ARB_render_texture 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglBindTexImageARB (HPBUFFERARB, int); +extern BOOL WINAPI wglReleaseTexImageARB (HPBUFFERARB, int); +extern BOOL WINAPI wglSetPbufferAttribARB (HPBUFFERARB, const int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int *piAttribList); +#endif + +#ifndef WGL_ARB_pixel_format_float +#define WGL_ARB_pixel_format_float 1 +#endif + +#ifndef WGL_EXT_display_color_table +#define WGL_EXT_display_color_table 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern GLboolean WINAPI wglCreateDisplayColorTableEXT (GLushort); +extern GLboolean WINAPI wglLoadDisplayColorTableEXT (const GLushort *, GLuint); +extern GLboolean WINAPI wglBindDisplayColorTableEXT (GLushort); +extern VOID WINAPI wglDestroyDisplayColorTableEXT (GLushort); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef GLboolean (WINAPI * PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef GLboolean (WINAPI * PFNWGLLOADDISPLAYCOLORTABLEEXTPROC) (const GLushort *table, GLuint length); +typedef GLboolean (WINAPI * PFNWGLBINDDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef VOID (WINAPI * PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC) (GLushort id); +#endif + +#ifndef WGL_EXT_extensions_string +#define WGL_EXT_extensions_string 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern const char * WINAPI wglGetExtensionsStringEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC) (void); +#endif + +#ifndef WGL_EXT_make_current_read +#define WGL_EXT_make_current_read 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglMakeContextCurrentEXT (HDC, HDC, HGLRC); +extern HDC WINAPI wglGetCurrentReadDCEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTEXTPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCEXTPROC) (void); +#endif + +#ifndef WGL_EXT_pbuffer +#define WGL_EXT_pbuffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HPBUFFEREXT WINAPI wglCreatePbufferEXT (HDC, int, int, int, const int *); +extern HDC WINAPI wglGetPbufferDCEXT (HPBUFFEREXT); +extern int WINAPI wglReleasePbufferDCEXT (HPBUFFEREXT, HDC); +extern BOOL WINAPI wglDestroyPbufferEXT (HPBUFFEREXT); +extern BOOL WINAPI wglQueryPbufferEXT (HPBUFFEREXT, int, int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HPBUFFEREXT (WINAPI * PFNWGLCREATEPBUFFEREXTPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); +#endif + +#ifndef WGL_EXT_pixel_format +#define WGL_EXT_pixel_format 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetPixelFormatAttribivEXT (HDC, int, int, UINT, int *, int *); +extern BOOL WINAPI wglGetPixelFormatAttribfvEXT (HDC, int, int, UINT, int *, FLOAT *); +extern BOOL WINAPI wglChoosePixelFormatEXT (HDC, const int *, const FLOAT *, UINT, int *, UINT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATEXTPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif + +#ifndef WGL_EXT_swap_control +#define WGL_EXT_swap_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglSwapIntervalEXT (int); +extern int WINAPI wglGetSwapIntervalEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); +typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void); +#endif + +#ifndef WGL_EXT_depth_float +#define WGL_EXT_depth_float 1 +#endif + +#ifndef WGL_NV_vertex_array_range +#define WGL_NV_vertex_array_range 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern void* WINAPI wglAllocateMemoryNV (GLsizei, GLfloat, GLfloat, GLfloat); +extern void WINAPI wglFreeMemoryNV (void *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef void* (WINAPI * PFNWGLALLOCATEMEMORYNVPROC) (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); +typedef void (WINAPI * PFNWGLFREEMEMORYNVPROC) (void *pointer); +#endif + +#ifndef WGL_3DFX_multisample +#define WGL_3DFX_multisample 1 +#endif + +#ifndef WGL_EXT_multisample +#define WGL_EXT_multisample 1 +#endif + +#ifndef WGL_OML_sync_control +#define WGL_OML_sync_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetSyncValuesOML (HDC, INT64 *, INT64 *, INT64 *); +extern BOOL WINAPI wglGetMscRateOML (HDC, INT32 *, INT32 *); +extern INT64 WINAPI wglSwapBuffersMscOML (HDC, INT64, INT64, INT64); +extern INT64 WINAPI wglSwapLayerBuffersMscOML (HDC, int, INT64, INT64, INT64); +extern BOOL WINAPI wglWaitForMscOML (HDC, INT64, INT64, INT64, INT64 *, INT64 *, INT64 *); +extern BOOL WINAPI wglWaitForSbcOML (HDC, INT64, INT64 *, INT64 *, INT64 *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETSYNCVALUESOMLPROC) (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLGETMSCRATEOMLPROC) (HDC hdc, INT32 *numerator, INT32 *denominator); +typedef INT64 (WINAPI * PFNWGLSWAPBUFFERSMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef INT64 (WINAPI * PFNWGLSWAPLAYERBUFFERSMSCOMLPROC) (HDC hdc, int fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef BOOL (WINAPI * PFNWGLWAITFORMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLWAITFORSBCOMLPROC) (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); +#endif + +#ifndef WGL_I3D_digital_video_control +#define WGL_I3D_digital_video_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetDigitalVideoParametersI3D (HDC, int, int *); +extern BOOL WINAPI wglSetDigitalVideoParametersI3D (HDC, int, const int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +#endif + +#ifndef WGL_I3D_gamma +#define WGL_I3D_gamma 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetGammaTableParametersI3D (HDC, int, int *); +extern BOOL WINAPI wglSetGammaTableParametersI3D (HDC, int, const int *); +extern BOOL WINAPI wglGetGammaTableI3D (HDC, int, USHORT *, USHORT *, USHORT *); +extern BOOL WINAPI wglSetGammaTableI3D (HDC, int, const USHORT *, const USHORT *, const USHORT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); +#endif + +#ifndef WGL_I3D_genlock +#define WGL_I3D_genlock 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglEnableGenlockI3D (HDC); +extern BOOL WINAPI wglDisableGenlockI3D (HDC); +extern BOOL WINAPI wglIsEnabledGenlockI3D (HDC, BOOL *); +extern BOOL WINAPI wglGenlockSourceI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSourceI3D (HDC, UINT *); +extern BOOL WINAPI wglGenlockSourceEdgeI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSourceEdgeI3D (HDC, UINT *); +extern BOOL WINAPI wglGenlockSampleRateI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSampleRateI3D (HDC, UINT *); +extern BOOL WINAPI wglGenlockSourceDelayI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSourceDelayI3D (HDC, UINT *); +extern BOOL WINAPI wglQueryGenlockMaxSourceDelayI3D (HDC, UINT *, UINT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLENABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLDISABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLISENABLEDGENLOCKI3DPROC) (HDC hDC, BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEI3DPROC) (HDC hDC, UINT uSource); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEI3DPROC) (HDC hDC, UINT *uSource); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT uEdge); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT *uEdge); +typedef BOOL (WINAPI * PFNWGLGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT uRate); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT *uRate); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT uDelay); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT *uDelay); +typedef BOOL (WINAPI * PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC) (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); +#endif + +#ifndef WGL_I3D_image_buffer +#define WGL_I3D_image_buffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern LPVOID WINAPI wglCreateImageBufferI3D (HDC, DWORD, UINT); +extern BOOL WINAPI wglDestroyImageBufferI3D (HDC, LPVOID); +extern BOOL WINAPI wglAssociateImageBufferEventsI3D (HDC, const HANDLE *, const LPVOID *, const DWORD *, UINT); +extern BOOL WINAPI wglReleaseImageBufferEventsI3D (HDC, const LPVOID *, UINT); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef LPVOID (WINAPI * PFNWGLCREATEIMAGEBUFFERI3DPROC) (HDC hDC, DWORD dwSize, UINT uFlags); +typedef BOOL (WINAPI * PFNWGLDESTROYIMAGEBUFFERI3DPROC) (HDC hDC, LPVOID pAddress); +typedef BOOL (WINAPI * PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); +typedef BOOL (WINAPI * PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const LPVOID *pAddress, UINT count); +#endif + +#ifndef WGL_I3D_swap_frame_lock +#define WGL_I3D_swap_frame_lock 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglEnableFrameLockI3D (void); +extern BOOL WINAPI wglDisableFrameLockI3D (void); +extern BOOL WINAPI wglIsEnabledFrameLockI3D (BOOL *); +extern BOOL WINAPI wglQueryFrameLockMasterI3D (BOOL *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLENABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLDISABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLISENABLEDFRAMELOCKI3DPROC) (BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMELOCKMASTERI3DPROC) (BOOL *pFlag); +#endif + +#ifndef WGL_I3D_swap_frame_usage +#define WGL_I3D_swap_frame_usage 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetFrameUsageI3D (float *); +extern BOOL WINAPI wglBeginFrameTrackingI3D (void); +extern BOOL WINAPI wglEndFrameTrackingI3D (void); +extern BOOL WINAPI wglQueryFrameTrackingI3D (DWORD *, DWORD *, float *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETFRAMEUSAGEI3DPROC) (float *pUsage); +typedef BOOL (WINAPI * PFNWGLBEGINFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLENDFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMETRACKINGI3DPROC) (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); +#endif + +#ifndef WGL_ATI_pixel_format_float +#define WGL_ATI_pixel_format_float 1 +#endif + +#ifndef WGL_NV_float_buffer +#define WGL_NV_float_buffer 1 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/glew.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/glew.h new file mode 100644 index 0000000..2014092 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/glew.h @@ -0,0 +1,12262 @@ +/* +** The OpenGL Extension Wrangler Library +** Copyright (C) 2002-2008, Milan Ikits +** Copyright (C) 2002-2008, Marcelo E. Magallon +** Copyright (C) 2002, Lev Povalahev +** 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. +** * The name of the author may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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. +*/ + +/* + * Mesa 3-D graphics library + * Version: 7.0 + * + * Copyright (C) 1999-2007 Brian Paul All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +** Copyright (c) 2007 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +#ifndef __glew_h__ +#define __glew_h__ +#define __GLEW_H__ + +#if defined(__gl_h_) || defined(__GL_H__) +#error gl.h included before glew.h +#endif +#if defined(__glext_h_) || defined(__GLEXT_H_) +#error glext.h included before glew.h +#endif +#if defined(__gl_ATI_h_) +#error glATI.h included before glew.h +#endif + +#define __gl_h_ +#define __GL_H__ +#define __glext_h_ +#define __GLEXT_H_ +#define __gl_ATI_h_ + +#if defined(_WIN32) + +/* + * GLEW does not include to avoid name space pollution. + * GL needs GLAPI and GLAPIENTRY, GLU needs APIENTRY, CALLBACK, and wchar_t + * defined properly. + */ +/* */ +#ifndef APIENTRY +#define GLEW_APIENTRY_DEFINED +# if defined(__MINGW32__) +# define APIENTRY __stdcall +# elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) || defined(__BORLANDC__) +# define APIENTRY __stdcall +# else +# define APIENTRY +# endif +#endif +#ifndef GLAPI +# if defined(__MINGW32__) +# define GLAPI extern +# endif +#endif +/* */ +#ifndef CALLBACK +#define GLEW_CALLBACK_DEFINED +# if defined(__MINGW32__) +# define CALLBACK __attribute__ ((__stdcall__)) +# elif (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) +# define CALLBACK __stdcall +# else +# define CALLBACK +# endif +#endif +/* and */ +#ifndef WINGDIAPI +#define GLEW_WINGDIAPI_DEFINED +#define WINGDIAPI __declspec(dllimport) +#endif +/* */ +#if (defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(_WCHAR_T_DEFINED) +typedef unsigned short wchar_t; +# define _WCHAR_T_DEFINED +#endif +/* */ +#if !defined(_W64) +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && defined(_MSC_VER) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif +#if !defined(_PTRDIFF_T_DEFINED) && !defined(_PTRDIFF_T_) +# ifdef _WIN64 +typedef __int64 ptrdiff_t; +# else +typedef _W64 int ptrdiff_t; +# endif +# define _PTRDIFF_T_DEFINED +# define _PTRDIFF_T_ +#endif + +#ifndef GLAPI +# if defined(__MINGW32__) +# define GLAPI extern +# else +# define GLAPI WINGDIAPI +# endif +#endif + +#ifndef GLAPIENTRY +#define GLAPIENTRY APIENTRY +#endif + +/* + * GLEW_STATIC needs to be set when using the static version. + * GLEW_BUILD is set when building the DLL version. + */ +#ifdef GLEW_STATIC +# define GLEWAPI extern +#else +# ifdef GLEW_BUILD +# define GLEWAPI extern __declspec(dllexport) +# else +# define GLEWAPI extern __declspec(dllimport) +# endif +#endif + +#else /* _UNIX */ + +/* + * Needed for ptrdiff_t in turn needed by VBO. This is defined by ISO + * C. On my system, this amounts to _3 lines_ of included code, all of + * them pretty much harmless. If you know of a way of detecting 32 vs + * 64 _targets_ at compile time you are free to replace this with + * something that's portable. For now, _this_ is the portable solution. + * (mem, 2004-01-04) + */ + +#include +#include + +#define GLEW_APIENTRY_DEFINED +#define APIENTRY +#define GLEWAPI extern + +/* */ +#ifndef GLAPI +#define GLAPI extern +#endif +#ifndef GLAPIENTRY +#define GLAPIENTRY +#endif + +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------- GL_VERSION_1_1 ---------------------------- */ + +#ifndef GL_VERSION_1_1 +#define GL_VERSION_1_1 1 + +typedef unsigned int GLenum; +typedef unsigned int GLbitfield; +typedef unsigned int GLuint; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLboolean; +typedef signed char GLbyte; +typedef short GLshort; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned long GLulong; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void GLvoid; +#if defined(_MSC_VER) +# if _MSC_VER < 1400 +typedef __int64 GLint64EXT; +typedef unsigned __int64 GLuint64EXT; +# else +typedef signed long long GLint64EXT; +typedef unsigned long long GLuint64EXT; +# endif +#else +# if defined(__MINGW32__) +#include +# endif +typedef int64_t GLint64EXT; +typedef uint64_t GLuint64EXT; +#endif + +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_TRUE 1 +#define GL_FALSE 0 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_DOUBLE 0x140A +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_VIEWPORT 0x0BA2 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_COLOR_INDEX 0x1900 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_BITMAP 0x1A00 +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_ENV 0x2300 +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_CLAMP 0x2900 +#define GL_REPEAT 0x2901 +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xffffffff +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D +#define GL_LOGIC_OP GL_INDEX_LOGIC_OP +#define GL_TEXTURE_COMPONENTS GL_TEXTURE_INTERNAL_FORMAT +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 + +GLAPI void GLAPIENTRY glAccum (GLenum op, GLfloat value); +GLAPI void GLAPIENTRY glAlphaFunc (GLenum func, GLclampf ref); +GLAPI GLboolean GLAPIENTRY glAreTexturesResident (GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI void GLAPIENTRY glArrayElement (GLint i); +GLAPI void GLAPIENTRY glBegin (GLenum mode); +GLAPI void GLAPIENTRY glBindTexture (GLenum target, GLuint texture); +GLAPI void GLAPIENTRY glBitmap (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +GLAPI void GLAPIENTRY glBlendFunc (GLenum sfactor, GLenum dfactor); +GLAPI void GLAPIENTRY glCallList (GLuint list); +GLAPI void GLAPIENTRY glCallLists (GLsizei n, GLenum type, const GLvoid *lists); +GLAPI void GLAPIENTRY glClear (GLbitfield mask); +GLAPI void GLAPIENTRY glClearAccum (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void GLAPIENTRY glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +GLAPI void GLAPIENTRY glClearDepth (GLclampd depth); +GLAPI void GLAPIENTRY glClearIndex (GLfloat c); +GLAPI void GLAPIENTRY glClearStencil (GLint s); +GLAPI void GLAPIENTRY glClipPlane (GLenum plane, const GLdouble *equation); +GLAPI void GLAPIENTRY glColor3b (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void GLAPIENTRY glColor3bv (const GLbyte *v); +GLAPI void GLAPIENTRY glColor3d (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void GLAPIENTRY glColor3dv (const GLdouble *v); +GLAPI void GLAPIENTRY glColor3f (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void GLAPIENTRY glColor3fv (const GLfloat *v); +GLAPI void GLAPIENTRY glColor3i (GLint red, GLint green, GLint blue); +GLAPI void GLAPIENTRY glColor3iv (const GLint *v); +GLAPI void GLAPIENTRY glColor3s (GLshort red, GLshort green, GLshort blue); +GLAPI void GLAPIENTRY glColor3sv (const GLshort *v); +GLAPI void GLAPIENTRY glColor3ub (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void GLAPIENTRY glColor3ubv (const GLubyte *v); +GLAPI void GLAPIENTRY glColor3ui (GLuint red, GLuint green, GLuint blue); +GLAPI void GLAPIENTRY glColor3uiv (const GLuint *v); +GLAPI void GLAPIENTRY glColor3us (GLushort red, GLushort green, GLushort blue); +GLAPI void GLAPIENTRY glColor3usv (const GLushort *v); +GLAPI void GLAPIENTRY glColor4b (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +GLAPI void GLAPIENTRY glColor4bv (const GLbyte *v); +GLAPI void GLAPIENTRY glColor4d (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +GLAPI void GLAPIENTRY glColor4dv (const GLdouble *v); +GLAPI void GLAPIENTRY glColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void GLAPIENTRY glColor4fv (const GLfloat *v); +GLAPI void GLAPIENTRY glColor4i (GLint red, GLint green, GLint blue, GLint alpha); +GLAPI void GLAPIENTRY glColor4iv (const GLint *v); +GLAPI void GLAPIENTRY glColor4s (GLshort red, GLshort green, GLshort blue, GLshort alpha); +GLAPI void GLAPIENTRY glColor4sv (const GLshort *v); +GLAPI void GLAPIENTRY glColor4ub (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +GLAPI void GLAPIENTRY glColor4ubv (const GLubyte *v); +GLAPI void GLAPIENTRY glColor4ui (GLuint red, GLuint green, GLuint blue, GLuint alpha); +GLAPI void GLAPIENTRY glColor4uiv (const GLuint *v); +GLAPI void GLAPIENTRY glColor4us (GLushort red, GLushort green, GLushort blue, GLushort alpha); +GLAPI void GLAPIENTRY glColor4usv (const GLushort *v); +GLAPI void GLAPIENTRY glColorMask (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +GLAPI void GLAPIENTRY glColorMaterial (GLenum face, GLenum mode); +GLAPI void GLAPIENTRY glColorPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void GLAPIENTRY glCopyPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +GLAPI void GLAPIENTRY glCopyTexImage1D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void GLAPIENTRY glCopyTexImage2D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void GLAPIENTRY glCopyTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void GLAPIENTRY glCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void GLAPIENTRY glCullFace (GLenum mode); +GLAPI void GLAPIENTRY glDeleteLists (GLuint list, GLsizei range); +GLAPI void GLAPIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); +GLAPI void GLAPIENTRY glDepthFunc (GLenum func); +GLAPI void GLAPIENTRY glDepthMask (GLboolean flag); +GLAPI void GLAPIENTRY glDepthRange (GLclampd zNear, GLclampd zFar); +GLAPI void GLAPIENTRY glDisable (GLenum cap); +GLAPI void GLAPIENTRY glDisableClientState (GLenum array); +GLAPI void GLAPIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count); +GLAPI void GLAPIENTRY glDrawBuffer (GLenum mode); +GLAPI void GLAPIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +GLAPI void GLAPIENTRY glDrawPixels (GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void GLAPIENTRY glEdgeFlag (GLboolean flag); +GLAPI void GLAPIENTRY glEdgeFlagPointer (GLsizei stride, const GLvoid *pointer); +GLAPI void GLAPIENTRY glEdgeFlagv (const GLboolean *flag); +GLAPI void GLAPIENTRY glEnable (GLenum cap); +GLAPI void GLAPIENTRY glEnableClientState (GLenum array); +GLAPI void GLAPIENTRY glEnd (void); +GLAPI void GLAPIENTRY glEndList (void); +GLAPI void GLAPIENTRY glEvalCoord1d (GLdouble u); +GLAPI void GLAPIENTRY glEvalCoord1dv (const GLdouble *u); +GLAPI void GLAPIENTRY glEvalCoord1f (GLfloat u); +GLAPI void GLAPIENTRY glEvalCoord1fv (const GLfloat *u); +GLAPI void GLAPIENTRY glEvalCoord2d (GLdouble u, GLdouble v); +GLAPI void GLAPIENTRY glEvalCoord2dv (const GLdouble *u); +GLAPI void GLAPIENTRY glEvalCoord2f (GLfloat u, GLfloat v); +GLAPI void GLAPIENTRY glEvalCoord2fv (const GLfloat *u); +GLAPI void GLAPIENTRY glEvalMesh1 (GLenum mode, GLint i1, GLint i2); +GLAPI void GLAPIENTRY glEvalMesh2 (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +GLAPI void GLAPIENTRY glEvalPoint1 (GLint i); +GLAPI void GLAPIENTRY glEvalPoint2 (GLint i, GLint j); +GLAPI void GLAPIENTRY glFeedbackBuffer (GLsizei size, GLenum type, GLfloat *buffer); +GLAPI void GLAPIENTRY glFinish (void); +GLAPI void GLAPIENTRY glFlush (void); +GLAPI void GLAPIENTRY glFogf (GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glFogfv (GLenum pname, const GLfloat *params); +GLAPI void GLAPIENTRY glFogi (GLenum pname, GLint param); +GLAPI void GLAPIENTRY glFogiv (GLenum pname, const GLint *params); +GLAPI void GLAPIENTRY glFrontFace (GLenum mode); +GLAPI void GLAPIENTRY glFrustum (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI GLuint GLAPIENTRY glGenLists (GLsizei range); +GLAPI void GLAPIENTRY glGenTextures (GLsizei n, GLuint *textures); +GLAPI void GLAPIENTRY glGetBooleanv (GLenum pname, GLboolean *params); +GLAPI void GLAPIENTRY glGetClipPlane (GLenum plane, GLdouble *equation); +GLAPI void GLAPIENTRY glGetDoublev (GLenum pname, GLdouble *params); +GLAPI GLenum GLAPIENTRY glGetError (void); +GLAPI void GLAPIENTRY glGetFloatv (GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetIntegerv (GLenum pname, GLint *params); +GLAPI void GLAPIENTRY glGetLightfv (GLenum light, GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetLightiv (GLenum light, GLenum pname, GLint *params); +GLAPI void GLAPIENTRY glGetMapdv (GLenum target, GLenum query, GLdouble *v); +GLAPI void GLAPIENTRY glGetMapfv (GLenum target, GLenum query, GLfloat *v); +GLAPI void GLAPIENTRY glGetMapiv (GLenum target, GLenum query, GLint *v); +GLAPI void GLAPIENTRY glGetMaterialfv (GLenum face, GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetMaterialiv (GLenum face, GLenum pname, GLint *params); +GLAPI void GLAPIENTRY glGetPixelMapfv (GLenum map, GLfloat *values); +GLAPI void GLAPIENTRY glGetPixelMapuiv (GLenum map, GLuint *values); +GLAPI void GLAPIENTRY glGetPixelMapusv (GLenum map, GLushort *values); +GLAPI void GLAPIENTRY glGetPointerv (GLenum pname, GLvoid* *params); +GLAPI void GLAPIENTRY glGetPolygonStipple (GLubyte *mask); +GLAPI const GLubyte * GLAPIENTRY glGetString (GLenum name); +GLAPI void GLAPIENTRY glGetTexEnvfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetTexEnviv (GLenum target, GLenum pname, GLint *params); +GLAPI void GLAPIENTRY glGetTexGendv (GLenum coord, GLenum pname, GLdouble *params); +GLAPI void GLAPIENTRY glGetTexGenfv (GLenum coord, GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetTexGeniv (GLenum coord, GLenum pname, GLint *params); +GLAPI void GLAPIENTRY glGetTexImage (GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +GLAPI void GLAPIENTRY glGetTexLevelParameterfv (GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetTexLevelParameteriv (GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void GLAPIENTRY glGetTexParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void GLAPIENTRY glGetTexParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void GLAPIENTRY glHint (GLenum target, GLenum mode); +GLAPI void GLAPIENTRY glIndexMask (GLuint mask); +GLAPI void GLAPIENTRY glIndexPointer (GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void GLAPIENTRY glIndexd (GLdouble c); +GLAPI void GLAPIENTRY glIndexdv (const GLdouble *c); +GLAPI void GLAPIENTRY glIndexf (GLfloat c); +GLAPI void GLAPIENTRY glIndexfv (const GLfloat *c); +GLAPI void GLAPIENTRY glIndexi (GLint c); +GLAPI void GLAPIENTRY glIndexiv (const GLint *c); +GLAPI void GLAPIENTRY glIndexs (GLshort c); +GLAPI void GLAPIENTRY glIndexsv (const GLshort *c); +GLAPI void GLAPIENTRY glIndexub (GLubyte c); +GLAPI void GLAPIENTRY glIndexubv (const GLubyte *c); +GLAPI void GLAPIENTRY glInitNames (void); +GLAPI void GLAPIENTRY glInterleavedArrays (GLenum format, GLsizei stride, const GLvoid *pointer); +GLAPI GLboolean GLAPIENTRY glIsEnabled (GLenum cap); +GLAPI GLboolean GLAPIENTRY glIsList (GLuint list); +GLAPI GLboolean GLAPIENTRY glIsTexture (GLuint texture); +GLAPI void GLAPIENTRY glLightModelf (GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glLightModelfv (GLenum pname, const GLfloat *params); +GLAPI void GLAPIENTRY glLightModeli (GLenum pname, GLint param); +GLAPI void GLAPIENTRY glLightModeliv (GLenum pname, const GLint *params); +GLAPI void GLAPIENTRY glLightf (GLenum light, GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glLightfv (GLenum light, GLenum pname, const GLfloat *params); +GLAPI void GLAPIENTRY glLighti (GLenum light, GLenum pname, GLint param); +GLAPI void GLAPIENTRY glLightiv (GLenum light, GLenum pname, const GLint *params); +GLAPI void GLAPIENTRY glLineStipple (GLint factor, GLushort pattern); +GLAPI void GLAPIENTRY glLineWidth (GLfloat width); +GLAPI void GLAPIENTRY glListBase (GLuint base); +GLAPI void GLAPIENTRY glLoadIdentity (void); +GLAPI void GLAPIENTRY glLoadMatrixd (const GLdouble *m); +GLAPI void GLAPIENTRY glLoadMatrixf (const GLfloat *m); +GLAPI void GLAPIENTRY glLoadName (GLuint name); +GLAPI void GLAPIENTRY glLogicOp (GLenum opcode); +GLAPI void GLAPIENTRY glMap1d (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI void GLAPIENTRY glMap1f (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI void GLAPIENTRY glMap2d (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI void GLAPIENTRY glMap2f (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +GLAPI void GLAPIENTRY glMapGrid1d (GLint un, GLdouble u1, GLdouble u2); +GLAPI void GLAPIENTRY glMapGrid1f (GLint un, GLfloat u1, GLfloat u2); +GLAPI void GLAPIENTRY glMapGrid2d (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +GLAPI void GLAPIENTRY glMapGrid2f (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +GLAPI void GLAPIENTRY glMaterialf (GLenum face, GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glMaterialfv (GLenum face, GLenum pname, const GLfloat *params); +GLAPI void GLAPIENTRY glMateriali (GLenum face, GLenum pname, GLint param); +GLAPI void GLAPIENTRY glMaterialiv (GLenum face, GLenum pname, const GLint *params); +GLAPI void GLAPIENTRY glMatrixMode (GLenum mode); +GLAPI void GLAPIENTRY glMultMatrixd (const GLdouble *m); +GLAPI void GLAPIENTRY glMultMatrixf (const GLfloat *m); +GLAPI void GLAPIENTRY glNewList (GLuint list, GLenum mode); +GLAPI void GLAPIENTRY glNormal3b (GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI void GLAPIENTRY glNormal3bv (const GLbyte *v); +GLAPI void GLAPIENTRY glNormal3d (GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI void GLAPIENTRY glNormal3dv (const GLdouble *v); +GLAPI void GLAPIENTRY glNormal3f (GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI void GLAPIENTRY glNormal3fv (const GLfloat *v); +GLAPI void GLAPIENTRY glNormal3i (GLint nx, GLint ny, GLint nz); +GLAPI void GLAPIENTRY glNormal3iv (const GLint *v); +GLAPI void GLAPIENTRY glNormal3s (GLshort nx, GLshort ny, GLshort nz); +GLAPI void GLAPIENTRY glNormal3sv (const GLshort *v); +GLAPI void GLAPIENTRY glNormalPointer (GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void GLAPIENTRY glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void GLAPIENTRY glPassThrough (GLfloat token); +GLAPI void GLAPIENTRY glPixelMapfv (GLenum map, GLsizei mapsize, const GLfloat *values); +GLAPI void GLAPIENTRY glPixelMapuiv (GLenum map, GLsizei mapsize, const GLuint *values); +GLAPI void GLAPIENTRY glPixelMapusv (GLenum map, GLsizei mapsize, const GLushort *values); +GLAPI void GLAPIENTRY glPixelStoref (GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glPixelStorei (GLenum pname, GLint param); +GLAPI void GLAPIENTRY glPixelTransferf (GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glPixelTransferi (GLenum pname, GLint param); +GLAPI void GLAPIENTRY glPixelZoom (GLfloat xfactor, GLfloat yfactor); +GLAPI void GLAPIENTRY glPointSize (GLfloat size); +GLAPI void GLAPIENTRY glPolygonMode (GLenum face, GLenum mode); +GLAPI void GLAPIENTRY glPolygonOffset (GLfloat factor, GLfloat units); +GLAPI void GLAPIENTRY glPolygonStipple (const GLubyte *mask); +GLAPI void GLAPIENTRY glPopAttrib (void); +GLAPI void GLAPIENTRY glPopClientAttrib (void); +GLAPI void GLAPIENTRY glPopMatrix (void); +GLAPI void GLAPIENTRY glPopName (void); +GLAPI void GLAPIENTRY glPrioritizeTextures (GLsizei n, const GLuint *textures, const GLclampf *priorities); +GLAPI void GLAPIENTRY glPushAttrib (GLbitfield mask); +GLAPI void GLAPIENTRY glPushClientAttrib (GLbitfield mask); +GLAPI void GLAPIENTRY glPushMatrix (void); +GLAPI void GLAPIENTRY glPushName (GLuint name); +GLAPI void GLAPIENTRY glRasterPos2d (GLdouble x, GLdouble y); +GLAPI void GLAPIENTRY glRasterPos2dv (const GLdouble *v); +GLAPI void GLAPIENTRY glRasterPos2f (GLfloat x, GLfloat y); +GLAPI void GLAPIENTRY glRasterPos2fv (const GLfloat *v); +GLAPI void GLAPIENTRY glRasterPos2i (GLint x, GLint y); +GLAPI void GLAPIENTRY glRasterPos2iv (const GLint *v); +GLAPI void GLAPIENTRY glRasterPos2s (GLshort x, GLshort y); +GLAPI void GLAPIENTRY glRasterPos2sv (const GLshort *v); +GLAPI void GLAPIENTRY glRasterPos3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void GLAPIENTRY glRasterPos3dv (const GLdouble *v); +GLAPI void GLAPIENTRY glRasterPos3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void GLAPIENTRY glRasterPos3fv (const GLfloat *v); +GLAPI void GLAPIENTRY glRasterPos3i (GLint x, GLint y, GLint z); +GLAPI void GLAPIENTRY glRasterPos3iv (const GLint *v); +GLAPI void GLAPIENTRY glRasterPos3s (GLshort x, GLshort y, GLshort z); +GLAPI void GLAPIENTRY glRasterPos3sv (const GLshort *v); +GLAPI void GLAPIENTRY glRasterPos4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void GLAPIENTRY glRasterPos4dv (const GLdouble *v); +GLAPI void GLAPIENTRY glRasterPos4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void GLAPIENTRY glRasterPos4fv (const GLfloat *v); +GLAPI void GLAPIENTRY glRasterPos4i (GLint x, GLint y, GLint z, GLint w); +GLAPI void GLAPIENTRY glRasterPos4iv (const GLint *v); +GLAPI void GLAPIENTRY glRasterPos4s (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void GLAPIENTRY glRasterPos4sv (const GLshort *v); +GLAPI void GLAPIENTRY glReadBuffer (GLenum mode); +GLAPI void GLAPIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +GLAPI void GLAPIENTRY glRectd (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +GLAPI void GLAPIENTRY glRectdv (const GLdouble *v1, const GLdouble *v2); +GLAPI void GLAPIENTRY glRectf (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +GLAPI void GLAPIENTRY glRectfv (const GLfloat *v1, const GLfloat *v2); +GLAPI void GLAPIENTRY glRecti (GLint x1, GLint y1, GLint x2, GLint y2); +GLAPI void GLAPIENTRY glRectiv (const GLint *v1, const GLint *v2); +GLAPI void GLAPIENTRY glRects (GLshort x1, GLshort y1, GLshort x2, GLshort y2); +GLAPI void GLAPIENTRY glRectsv (const GLshort *v1, const GLshort *v2); +GLAPI GLint GLAPIENTRY glRenderMode (GLenum mode); +GLAPI void GLAPIENTRY glRotated (GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI void GLAPIENTRY glRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI void GLAPIENTRY glScaled (GLdouble x, GLdouble y, GLdouble z); +GLAPI void GLAPIENTRY glScalef (GLfloat x, GLfloat y, GLfloat z); +GLAPI void GLAPIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void GLAPIENTRY glSelectBuffer (GLsizei size, GLuint *buffer); +GLAPI void GLAPIENTRY glShadeModel (GLenum mode); +GLAPI void GLAPIENTRY glStencilFunc (GLenum func, GLint ref, GLuint mask); +GLAPI void GLAPIENTRY glStencilMask (GLuint mask); +GLAPI void GLAPIENTRY glStencilOp (GLenum fail, GLenum zfail, GLenum zpass); +GLAPI void GLAPIENTRY glTexCoord1d (GLdouble s); +GLAPI void GLAPIENTRY glTexCoord1dv (const GLdouble *v); +GLAPI void GLAPIENTRY glTexCoord1f (GLfloat s); +GLAPI void GLAPIENTRY glTexCoord1fv (const GLfloat *v); +GLAPI void GLAPIENTRY glTexCoord1i (GLint s); +GLAPI void GLAPIENTRY glTexCoord1iv (const GLint *v); +GLAPI void GLAPIENTRY glTexCoord1s (GLshort s); +GLAPI void GLAPIENTRY glTexCoord1sv (const GLshort *v); +GLAPI void GLAPIENTRY glTexCoord2d (GLdouble s, GLdouble t); +GLAPI void GLAPIENTRY glTexCoord2dv (const GLdouble *v); +GLAPI void GLAPIENTRY glTexCoord2f (GLfloat s, GLfloat t); +GLAPI void GLAPIENTRY glTexCoord2fv (const GLfloat *v); +GLAPI void GLAPIENTRY glTexCoord2i (GLint s, GLint t); +GLAPI void GLAPIENTRY glTexCoord2iv (const GLint *v); +GLAPI void GLAPIENTRY glTexCoord2s (GLshort s, GLshort t); +GLAPI void GLAPIENTRY glTexCoord2sv (const GLshort *v); +GLAPI void GLAPIENTRY glTexCoord3d (GLdouble s, GLdouble t, GLdouble r); +GLAPI void GLAPIENTRY glTexCoord3dv (const GLdouble *v); +GLAPI void GLAPIENTRY glTexCoord3f (GLfloat s, GLfloat t, GLfloat r); +GLAPI void GLAPIENTRY glTexCoord3fv (const GLfloat *v); +GLAPI void GLAPIENTRY glTexCoord3i (GLint s, GLint t, GLint r); +GLAPI void GLAPIENTRY glTexCoord3iv (const GLint *v); +GLAPI void GLAPIENTRY glTexCoord3s (GLshort s, GLshort t, GLshort r); +GLAPI void GLAPIENTRY glTexCoord3sv (const GLshort *v); +GLAPI void GLAPIENTRY glTexCoord4d (GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void GLAPIENTRY glTexCoord4dv (const GLdouble *v); +GLAPI void GLAPIENTRY glTexCoord4f (GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void GLAPIENTRY glTexCoord4fv (const GLfloat *v); +GLAPI void GLAPIENTRY glTexCoord4i (GLint s, GLint t, GLint r, GLint q); +GLAPI void GLAPIENTRY glTexCoord4iv (const GLint *v); +GLAPI void GLAPIENTRY glTexCoord4s (GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void GLAPIENTRY glTexCoord4sv (const GLshort *v); +GLAPI void GLAPIENTRY glTexCoordPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void GLAPIENTRY glTexEnvf (GLenum target, GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glTexEnvfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void GLAPIENTRY glTexEnvi (GLenum target, GLenum pname, GLint param); +GLAPI void GLAPIENTRY glTexEnviv (GLenum target, GLenum pname, const GLint *params); +GLAPI void GLAPIENTRY glTexGend (GLenum coord, GLenum pname, GLdouble param); +GLAPI void GLAPIENTRY glTexGendv (GLenum coord, GLenum pname, const GLdouble *params); +GLAPI void GLAPIENTRY glTexGenf (GLenum coord, GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glTexGenfv (GLenum coord, GLenum pname, const GLfloat *params); +GLAPI void GLAPIENTRY glTexGeni (GLenum coord, GLenum pname, GLint param); +GLAPI void GLAPIENTRY glTexGeniv (GLenum coord, GLenum pname, const GLint *params); +GLAPI void GLAPIENTRY glTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void GLAPIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void GLAPIENTRY glTexParameterf (GLenum target, GLenum pname, GLfloat param); +GLAPI void GLAPIENTRY glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void GLAPIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param); +GLAPI void GLAPIENTRY glTexParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void GLAPIENTRY glTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void GLAPIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void GLAPIENTRY glTranslated (GLdouble x, GLdouble y, GLdouble z); +GLAPI void GLAPIENTRY glTranslatef (GLfloat x, GLfloat y, GLfloat z); +GLAPI void GLAPIENTRY glVertex2d (GLdouble x, GLdouble y); +GLAPI void GLAPIENTRY glVertex2dv (const GLdouble *v); +GLAPI void GLAPIENTRY glVertex2f (GLfloat x, GLfloat y); +GLAPI void GLAPIENTRY glVertex2fv (const GLfloat *v); +GLAPI void GLAPIENTRY glVertex2i (GLint x, GLint y); +GLAPI void GLAPIENTRY glVertex2iv (const GLint *v); +GLAPI void GLAPIENTRY glVertex2s (GLshort x, GLshort y); +GLAPI void GLAPIENTRY glVertex2sv (const GLshort *v); +GLAPI void GLAPIENTRY glVertex3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void GLAPIENTRY glVertex3dv (const GLdouble *v); +GLAPI void GLAPIENTRY glVertex3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void GLAPIENTRY glVertex3fv (const GLfloat *v); +GLAPI void GLAPIENTRY glVertex3i (GLint x, GLint y, GLint z); +GLAPI void GLAPIENTRY glVertex3iv (const GLint *v); +GLAPI void GLAPIENTRY glVertex3s (GLshort x, GLshort y, GLshort z); +GLAPI void GLAPIENTRY glVertex3sv (const GLshort *v); +GLAPI void GLAPIENTRY glVertex4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void GLAPIENTRY glVertex4dv (const GLdouble *v); +GLAPI void GLAPIENTRY glVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void GLAPIENTRY glVertex4fv (const GLfloat *v); +GLAPI void GLAPIENTRY glVertex4i (GLint x, GLint y, GLint z, GLint w); +GLAPI void GLAPIENTRY glVertex4iv (const GLint *v); +GLAPI void GLAPIENTRY glVertex4s (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void GLAPIENTRY glVertex4sv (const GLshort *v); +GLAPI void GLAPIENTRY glVertexPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void GLAPIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height); + +#define GLEW_VERSION_1_1 GLEW_GET_VAR(__GLEW_VERSION_1_1) + +#endif /* GL_VERSION_1_1 */ + +/* ---------------------------------- GLU ---------------------------------- */ + +/* this is where we can safely include GLU */ +#if defined(__APPLE__) && defined(__MACH__) +#include +#else +#include +#endif + +/* ----------------------------- GL_VERSION_1_2 ---------------------------- */ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 + +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E + +typedef void (GLAPIENTRY * PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (GLAPIENTRY * PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (GLAPIENTRY * PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); + +#define glCopyTexSubImage3D GLEW_GET_FUN(__glewCopyTexSubImage3D) +#define glDrawRangeElements GLEW_GET_FUN(__glewDrawRangeElements) +#define glTexImage3D GLEW_GET_FUN(__glewTexImage3D) +#define glTexSubImage3D GLEW_GET_FUN(__glewTexSubImage3D) + +#define GLEW_VERSION_1_2 GLEW_GET_VAR(__GLEW_VERSION_1_2) + +#endif /* GL_VERSION_1_2 */ + +/* ----------------------------- GL_VERSION_1_3 ---------------------------- */ + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 + +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_SUBTRACT 0x84E7 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#define GL_MULTISAMPLE_BIT 0x20000000 + +typedef void (GLAPIENTRY * PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (GLAPIENTRY * PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint lod, GLvoid *img); +typedef void (GLAPIENTRY * PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble m[16]); +typedef void (GLAPIENTRY * PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat m[16]); +typedef void (GLAPIENTRY * PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble m[16]); +typedef void (GLAPIENTRY * PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat m[16]); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); + +#define glActiveTexture GLEW_GET_FUN(__glewActiveTexture) +#define glClientActiveTexture GLEW_GET_FUN(__glewClientActiveTexture) +#define glCompressedTexImage1D GLEW_GET_FUN(__glewCompressedTexImage1D) +#define glCompressedTexImage2D GLEW_GET_FUN(__glewCompressedTexImage2D) +#define glCompressedTexImage3D GLEW_GET_FUN(__glewCompressedTexImage3D) +#define glCompressedTexSubImage1D GLEW_GET_FUN(__glewCompressedTexSubImage1D) +#define glCompressedTexSubImage2D GLEW_GET_FUN(__glewCompressedTexSubImage2D) +#define glCompressedTexSubImage3D GLEW_GET_FUN(__glewCompressedTexSubImage3D) +#define glGetCompressedTexImage GLEW_GET_FUN(__glewGetCompressedTexImage) +#define glLoadTransposeMatrixd GLEW_GET_FUN(__glewLoadTransposeMatrixd) +#define glLoadTransposeMatrixf GLEW_GET_FUN(__glewLoadTransposeMatrixf) +#define glMultTransposeMatrixd GLEW_GET_FUN(__glewMultTransposeMatrixd) +#define glMultTransposeMatrixf GLEW_GET_FUN(__glewMultTransposeMatrixf) +#define glMultiTexCoord1d GLEW_GET_FUN(__glewMultiTexCoord1d) +#define glMultiTexCoord1dv GLEW_GET_FUN(__glewMultiTexCoord1dv) +#define glMultiTexCoord1f GLEW_GET_FUN(__glewMultiTexCoord1f) +#define glMultiTexCoord1fv GLEW_GET_FUN(__glewMultiTexCoord1fv) +#define glMultiTexCoord1i GLEW_GET_FUN(__glewMultiTexCoord1i) +#define glMultiTexCoord1iv GLEW_GET_FUN(__glewMultiTexCoord1iv) +#define glMultiTexCoord1s GLEW_GET_FUN(__glewMultiTexCoord1s) +#define glMultiTexCoord1sv GLEW_GET_FUN(__glewMultiTexCoord1sv) +#define glMultiTexCoord2d GLEW_GET_FUN(__glewMultiTexCoord2d) +#define glMultiTexCoord2dv GLEW_GET_FUN(__glewMultiTexCoord2dv) +#define glMultiTexCoord2f GLEW_GET_FUN(__glewMultiTexCoord2f) +#define glMultiTexCoord2fv GLEW_GET_FUN(__glewMultiTexCoord2fv) +#define glMultiTexCoord2i GLEW_GET_FUN(__glewMultiTexCoord2i) +#define glMultiTexCoord2iv GLEW_GET_FUN(__glewMultiTexCoord2iv) +#define glMultiTexCoord2s GLEW_GET_FUN(__glewMultiTexCoord2s) +#define glMultiTexCoord2sv GLEW_GET_FUN(__glewMultiTexCoord2sv) +#define glMultiTexCoord3d GLEW_GET_FUN(__glewMultiTexCoord3d) +#define glMultiTexCoord3dv GLEW_GET_FUN(__glewMultiTexCoord3dv) +#define glMultiTexCoord3f GLEW_GET_FUN(__glewMultiTexCoord3f) +#define glMultiTexCoord3fv GLEW_GET_FUN(__glewMultiTexCoord3fv) +#define glMultiTexCoord3i GLEW_GET_FUN(__glewMultiTexCoord3i) +#define glMultiTexCoord3iv GLEW_GET_FUN(__glewMultiTexCoord3iv) +#define glMultiTexCoord3s GLEW_GET_FUN(__glewMultiTexCoord3s) +#define glMultiTexCoord3sv GLEW_GET_FUN(__glewMultiTexCoord3sv) +#define glMultiTexCoord4d GLEW_GET_FUN(__glewMultiTexCoord4d) +#define glMultiTexCoord4dv GLEW_GET_FUN(__glewMultiTexCoord4dv) +#define glMultiTexCoord4f GLEW_GET_FUN(__glewMultiTexCoord4f) +#define glMultiTexCoord4fv GLEW_GET_FUN(__glewMultiTexCoord4fv) +#define glMultiTexCoord4i GLEW_GET_FUN(__glewMultiTexCoord4i) +#define glMultiTexCoord4iv GLEW_GET_FUN(__glewMultiTexCoord4iv) +#define glMultiTexCoord4s GLEW_GET_FUN(__glewMultiTexCoord4s) +#define glMultiTexCoord4sv GLEW_GET_FUN(__glewMultiTexCoord4sv) +#define glSampleCoverage GLEW_GET_FUN(__glewSampleCoverage) + +#define GLEW_VERSION_1_3 GLEW_GET_VAR(__GLEW_VERSION_1_3) + +#endif /* GL_VERSION_1_3 */ + +/* ----------------------------- GL_VERSION_1_4 ---------------------------- */ + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 + +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E + +typedef void (GLAPIENTRY * PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (GLAPIENTRY * PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (GLAPIENTRY * PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (GLAPIENTRY * PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (GLAPIENTRY * PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (GLAPIENTRY * PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (GLAPIENTRY * PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (GLAPIENTRY * PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (GLAPIENTRY * PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount); +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERFVPROC) (GLenum pname, GLfloat *params); +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERIVPROC) (GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2DVPROC) (const GLdouble *p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2FVPROC) (const GLfloat *p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2IVPROC) (const GLint *p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2SVPROC) (const GLshort *p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3DVPROC) (const GLdouble *p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3FVPROC) (const GLfloat *p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3IVPROC) (const GLint *p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3SVPROC) (const GLshort *p); + +#define glBlendColor GLEW_GET_FUN(__glewBlendColor) +#define glBlendEquation GLEW_GET_FUN(__glewBlendEquation) +#define glBlendFuncSeparate GLEW_GET_FUN(__glewBlendFuncSeparate) +#define glFogCoordPointer GLEW_GET_FUN(__glewFogCoordPointer) +#define glFogCoordd GLEW_GET_FUN(__glewFogCoordd) +#define glFogCoorddv GLEW_GET_FUN(__glewFogCoorddv) +#define glFogCoordf GLEW_GET_FUN(__glewFogCoordf) +#define glFogCoordfv GLEW_GET_FUN(__glewFogCoordfv) +#define glMultiDrawArrays GLEW_GET_FUN(__glewMultiDrawArrays) +#define glMultiDrawElements GLEW_GET_FUN(__glewMultiDrawElements) +#define glPointParameterf GLEW_GET_FUN(__glewPointParameterf) +#define glPointParameterfv GLEW_GET_FUN(__glewPointParameterfv) +#define glPointParameteri GLEW_GET_FUN(__glewPointParameteri) +#define glPointParameteriv GLEW_GET_FUN(__glewPointParameteriv) +#define glSecondaryColor3b GLEW_GET_FUN(__glewSecondaryColor3b) +#define glSecondaryColor3bv GLEW_GET_FUN(__glewSecondaryColor3bv) +#define glSecondaryColor3d GLEW_GET_FUN(__glewSecondaryColor3d) +#define glSecondaryColor3dv GLEW_GET_FUN(__glewSecondaryColor3dv) +#define glSecondaryColor3f GLEW_GET_FUN(__glewSecondaryColor3f) +#define glSecondaryColor3fv GLEW_GET_FUN(__glewSecondaryColor3fv) +#define glSecondaryColor3i GLEW_GET_FUN(__glewSecondaryColor3i) +#define glSecondaryColor3iv GLEW_GET_FUN(__glewSecondaryColor3iv) +#define glSecondaryColor3s GLEW_GET_FUN(__glewSecondaryColor3s) +#define glSecondaryColor3sv GLEW_GET_FUN(__glewSecondaryColor3sv) +#define glSecondaryColor3ub GLEW_GET_FUN(__glewSecondaryColor3ub) +#define glSecondaryColor3ubv GLEW_GET_FUN(__glewSecondaryColor3ubv) +#define glSecondaryColor3ui GLEW_GET_FUN(__glewSecondaryColor3ui) +#define glSecondaryColor3uiv GLEW_GET_FUN(__glewSecondaryColor3uiv) +#define glSecondaryColor3us GLEW_GET_FUN(__glewSecondaryColor3us) +#define glSecondaryColor3usv GLEW_GET_FUN(__glewSecondaryColor3usv) +#define glSecondaryColorPointer GLEW_GET_FUN(__glewSecondaryColorPointer) +#define glWindowPos2d GLEW_GET_FUN(__glewWindowPos2d) +#define glWindowPos2dv GLEW_GET_FUN(__glewWindowPos2dv) +#define glWindowPos2f GLEW_GET_FUN(__glewWindowPos2f) +#define glWindowPos2fv GLEW_GET_FUN(__glewWindowPos2fv) +#define glWindowPos2i GLEW_GET_FUN(__glewWindowPos2i) +#define glWindowPos2iv GLEW_GET_FUN(__glewWindowPos2iv) +#define glWindowPos2s GLEW_GET_FUN(__glewWindowPos2s) +#define glWindowPos2sv GLEW_GET_FUN(__glewWindowPos2sv) +#define glWindowPos3d GLEW_GET_FUN(__glewWindowPos3d) +#define glWindowPos3dv GLEW_GET_FUN(__glewWindowPos3dv) +#define glWindowPos3f GLEW_GET_FUN(__glewWindowPos3f) +#define glWindowPos3fv GLEW_GET_FUN(__glewWindowPos3fv) +#define glWindowPos3i GLEW_GET_FUN(__glewWindowPos3i) +#define glWindowPos3iv GLEW_GET_FUN(__glewWindowPos3iv) +#define glWindowPos3s GLEW_GET_FUN(__glewWindowPos3s) +#define glWindowPos3sv GLEW_GET_FUN(__glewWindowPos3sv) + +#define GLEW_VERSION_1_4 GLEW_GET_VAR(__GLEW_VERSION_1_4) + +#endif /* GL_VERSION_1_4 */ + +/* ----------------------------- GL_VERSION_1_5 ---------------------------- */ + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 + +#define GL_FOG_COORD_SRC GL_FOG_COORDINATE_SOURCE +#define GL_FOG_COORD GL_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY GL_FOG_COORDINATE_ARRAY +#define GL_SRC0_RGB GL_SOURCE0_RGB +#define GL_FOG_COORD_ARRAY_POINTER GL_FOG_COORDINATE_ARRAY_POINTER +#define GL_FOG_COORD_ARRAY_TYPE GL_FOG_COORDINATE_ARRAY_TYPE +#define GL_SRC1_ALPHA GL_SOURCE1_ALPHA +#define GL_CURRENT_FOG_COORD GL_CURRENT_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY_STRIDE GL_FOG_COORDINATE_ARRAY_STRIDE +#define GL_SRC0_ALPHA GL_SOURCE0_ALPHA +#define GL_SRC1_RGB GL_SOURCE1_RGB +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING +#define GL_SRC2_ALPHA GL_SOURCE2_ALPHA +#define GL_SRC2_RGB GL_SOURCE2_RGB +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 + +typedef ptrdiff_t GLsizeiptr; +typedef ptrdiff_t GLintptr; + +typedef void (GLAPIENTRY * PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (GLAPIENTRY * PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (GLAPIENTRY * PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage); +typedef void (GLAPIENTRY * PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data); +typedef void (GLAPIENTRY * PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint* buffers); +typedef void (GLAPIENTRY * PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint* ids); +typedef void (GLAPIENTRY * PFNGLENDQUERYPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLGENBUFFERSPROC) (GLsizei n, GLuint* buffers); +typedef void (GLAPIENTRY * PFNGLGENQUERIESPROC) (GLsizei n, GLuint* ids); +typedef void (GLAPIENTRY * PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, GLvoid** params); +typedef void (GLAPIENTRY * PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid* data); +typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint* params); +typedef void (GLAPIENTRY * PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISBUFFERPROC) (GLuint buffer); +typedef GLboolean (GLAPIENTRY * PFNGLISQUERYPROC) (GLuint id); +typedef GLvoid* (GLAPIENTRY * PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (GLAPIENTRY * PFNGLUNMAPBUFFERPROC) (GLenum target); + +#define glBeginQuery GLEW_GET_FUN(__glewBeginQuery) +#define glBindBuffer GLEW_GET_FUN(__glewBindBuffer) +#define glBufferData GLEW_GET_FUN(__glewBufferData) +#define glBufferSubData GLEW_GET_FUN(__glewBufferSubData) +#define glDeleteBuffers GLEW_GET_FUN(__glewDeleteBuffers) +#define glDeleteQueries GLEW_GET_FUN(__glewDeleteQueries) +#define glEndQuery GLEW_GET_FUN(__glewEndQuery) +#define glGenBuffers GLEW_GET_FUN(__glewGenBuffers) +#define glGenQueries GLEW_GET_FUN(__glewGenQueries) +#define glGetBufferParameteriv GLEW_GET_FUN(__glewGetBufferParameteriv) +#define glGetBufferPointerv GLEW_GET_FUN(__glewGetBufferPointerv) +#define glGetBufferSubData GLEW_GET_FUN(__glewGetBufferSubData) +#define glGetQueryObjectiv GLEW_GET_FUN(__glewGetQueryObjectiv) +#define glGetQueryObjectuiv GLEW_GET_FUN(__glewGetQueryObjectuiv) +#define glGetQueryiv GLEW_GET_FUN(__glewGetQueryiv) +#define glIsBuffer GLEW_GET_FUN(__glewIsBuffer) +#define glIsQuery GLEW_GET_FUN(__glewIsQuery) +#define glMapBuffer GLEW_GET_FUN(__glewMapBuffer) +#define glUnmapBuffer GLEW_GET_FUN(__glewUnmapBuffer) + +#define GLEW_VERSION_1_5 GLEW_GET_VAR(__GLEW_VERSION_1_5) + +#endif /* GL_VERSION_1_5 */ + +/* ----------------------------- GL_VERSION_2_0 ---------------------------- */ + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 + +#define GL_BLEND_EQUATION_RGB GL_BLEND_EQUATION +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 + +typedef char GLchar; + +typedef void (GLAPIENTRY * PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (GLAPIENTRY * PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar* name); +typedef void (GLAPIENTRY * PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum, GLenum); +typedef void (GLAPIENTRY * PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (GLAPIENTRY * PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (GLAPIENTRY * PFNGLCREATESHADERPROC) (GLenum type); +typedef void (GLAPIENTRY * PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (GLAPIENTRY * PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (GLAPIENTRY * PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (GLAPIENTRY * PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint); +typedef void (GLAPIENTRY * PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum* bufs); +typedef void (GLAPIENTRY * PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint); +typedef void (GLAPIENTRY * PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei maxLength, GLsizei* length, GLint* size, GLenum* type, GLchar* name); +typedef void (GLAPIENTRY * PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei maxLength, GLsizei* length, GLint* size, GLenum* type, GLchar* name); +typedef void (GLAPIENTRY * PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders); +typedef GLint (GLAPIENTRY * PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar* name); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint* param); +typedef void (GLAPIENTRY * PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); +typedef void (GLAPIENTRY * PFNGLGETSHADERSOURCEPROC) (GLint obj, GLsizei maxLength, GLsizei* length, GLchar* source); +typedef void (GLAPIENTRY * PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint* param); +typedef GLint (GLAPIENTRY * PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar* name); +typedef void (GLAPIENTRY * PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint, GLenum, GLvoid*); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBDVPROC) (GLuint, GLenum, GLdouble*); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBFVPROC) (GLuint, GLenum, GLfloat*); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIVPROC) (GLuint, GLenum, GLint*); +typedef GLboolean (GLAPIENTRY * PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (GLAPIENTRY * PFNGLISSHADERPROC) (GLuint shader); +typedef void (GLAPIENTRY * PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (GLAPIENTRY * PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar** strings, const GLint* lengths); +typedef void (GLAPIENTRY * PFNGLSTENCILFUNCSEPARATEPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +typedef void (GLAPIENTRY * PFNGLSTENCILMASKSEPARATEPROC) (GLenum, GLuint); +typedef void (GLAPIENTRY * PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (GLAPIENTRY * PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (GLAPIENTRY * PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (GLAPIENTRY * PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (GLAPIENTRY * PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (GLAPIENTRY * PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (GLAPIENTRY * PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (GLAPIENTRY * PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (GLAPIENTRY * PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (GLAPIENTRY * PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (GLAPIENTRY * PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer); + +#define glAttachShader GLEW_GET_FUN(__glewAttachShader) +#define glBindAttribLocation GLEW_GET_FUN(__glewBindAttribLocation) +#define glBlendEquationSeparate GLEW_GET_FUN(__glewBlendEquationSeparate) +#define glCompileShader GLEW_GET_FUN(__glewCompileShader) +#define glCreateProgram GLEW_GET_FUN(__glewCreateProgram) +#define glCreateShader GLEW_GET_FUN(__glewCreateShader) +#define glDeleteProgram GLEW_GET_FUN(__glewDeleteProgram) +#define glDeleteShader GLEW_GET_FUN(__glewDeleteShader) +#define glDetachShader GLEW_GET_FUN(__glewDetachShader) +#define glDisableVertexAttribArray GLEW_GET_FUN(__glewDisableVertexAttribArray) +#define glDrawBuffers GLEW_GET_FUN(__glewDrawBuffers) +#define glEnableVertexAttribArray GLEW_GET_FUN(__glewEnableVertexAttribArray) +#define glGetActiveAttrib GLEW_GET_FUN(__glewGetActiveAttrib) +#define glGetActiveUniform GLEW_GET_FUN(__glewGetActiveUniform) +#define glGetAttachedShaders GLEW_GET_FUN(__glewGetAttachedShaders) +#define glGetAttribLocation GLEW_GET_FUN(__glewGetAttribLocation) +#define glGetProgramInfoLog GLEW_GET_FUN(__glewGetProgramInfoLog) +#define glGetProgramiv GLEW_GET_FUN(__glewGetProgramiv) +#define glGetShaderInfoLog GLEW_GET_FUN(__glewGetShaderInfoLog) +#define glGetShaderSource GLEW_GET_FUN(__glewGetShaderSource) +#define glGetShaderiv GLEW_GET_FUN(__glewGetShaderiv) +#define glGetUniformLocation GLEW_GET_FUN(__glewGetUniformLocation) +#define glGetUniformfv GLEW_GET_FUN(__glewGetUniformfv) +#define glGetUniformiv GLEW_GET_FUN(__glewGetUniformiv) +#define glGetVertexAttribPointerv GLEW_GET_FUN(__glewGetVertexAttribPointerv) +#define glGetVertexAttribdv GLEW_GET_FUN(__glewGetVertexAttribdv) +#define glGetVertexAttribfv GLEW_GET_FUN(__glewGetVertexAttribfv) +#define glGetVertexAttribiv GLEW_GET_FUN(__glewGetVertexAttribiv) +#define glIsProgram GLEW_GET_FUN(__glewIsProgram) +#define glIsShader GLEW_GET_FUN(__glewIsShader) +#define glLinkProgram GLEW_GET_FUN(__glewLinkProgram) +#define glShaderSource GLEW_GET_FUN(__glewShaderSource) +#define glStencilFuncSeparate GLEW_GET_FUN(__glewStencilFuncSeparate) +#define glStencilMaskSeparate GLEW_GET_FUN(__glewStencilMaskSeparate) +#define glStencilOpSeparate GLEW_GET_FUN(__glewStencilOpSeparate) +#define glUniform1f GLEW_GET_FUN(__glewUniform1f) +#define glUniform1fv GLEW_GET_FUN(__glewUniform1fv) +#define glUniform1i GLEW_GET_FUN(__glewUniform1i) +#define glUniform1iv GLEW_GET_FUN(__glewUniform1iv) +#define glUniform2f GLEW_GET_FUN(__glewUniform2f) +#define glUniform2fv GLEW_GET_FUN(__glewUniform2fv) +#define glUniform2i GLEW_GET_FUN(__glewUniform2i) +#define glUniform2iv GLEW_GET_FUN(__glewUniform2iv) +#define glUniform3f GLEW_GET_FUN(__glewUniform3f) +#define glUniform3fv GLEW_GET_FUN(__glewUniform3fv) +#define glUniform3i GLEW_GET_FUN(__glewUniform3i) +#define glUniform3iv GLEW_GET_FUN(__glewUniform3iv) +#define glUniform4f GLEW_GET_FUN(__glewUniform4f) +#define glUniform4fv GLEW_GET_FUN(__glewUniform4fv) +#define glUniform4i GLEW_GET_FUN(__glewUniform4i) +#define glUniform4iv GLEW_GET_FUN(__glewUniform4iv) +#define glUniformMatrix2fv GLEW_GET_FUN(__glewUniformMatrix2fv) +#define glUniformMatrix3fv GLEW_GET_FUN(__glewUniformMatrix3fv) +#define glUniformMatrix4fv GLEW_GET_FUN(__glewUniformMatrix4fv) +#define glUseProgram GLEW_GET_FUN(__glewUseProgram) +#define glValidateProgram GLEW_GET_FUN(__glewValidateProgram) +#define glVertexAttrib1d GLEW_GET_FUN(__glewVertexAttrib1d) +#define glVertexAttrib1dv GLEW_GET_FUN(__glewVertexAttrib1dv) +#define glVertexAttrib1f GLEW_GET_FUN(__glewVertexAttrib1f) +#define glVertexAttrib1fv GLEW_GET_FUN(__glewVertexAttrib1fv) +#define glVertexAttrib1s GLEW_GET_FUN(__glewVertexAttrib1s) +#define glVertexAttrib1sv GLEW_GET_FUN(__glewVertexAttrib1sv) +#define glVertexAttrib2d GLEW_GET_FUN(__glewVertexAttrib2d) +#define glVertexAttrib2dv GLEW_GET_FUN(__glewVertexAttrib2dv) +#define glVertexAttrib2f GLEW_GET_FUN(__glewVertexAttrib2f) +#define glVertexAttrib2fv GLEW_GET_FUN(__glewVertexAttrib2fv) +#define glVertexAttrib2s GLEW_GET_FUN(__glewVertexAttrib2s) +#define glVertexAttrib2sv GLEW_GET_FUN(__glewVertexAttrib2sv) +#define glVertexAttrib3d GLEW_GET_FUN(__glewVertexAttrib3d) +#define glVertexAttrib3dv GLEW_GET_FUN(__glewVertexAttrib3dv) +#define glVertexAttrib3f GLEW_GET_FUN(__glewVertexAttrib3f) +#define glVertexAttrib3fv GLEW_GET_FUN(__glewVertexAttrib3fv) +#define glVertexAttrib3s GLEW_GET_FUN(__glewVertexAttrib3s) +#define glVertexAttrib3sv GLEW_GET_FUN(__glewVertexAttrib3sv) +#define glVertexAttrib4Nbv GLEW_GET_FUN(__glewVertexAttrib4Nbv) +#define glVertexAttrib4Niv GLEW_GET_FUN(__glewVertexAttrib4Niv) +#define glVertexAttrib4Nsv GLEW_GET_FUN(__glewVertexAttrib4Nsv) +#define glVertexAttrib4Nub GLEW_GET_FUN(__glewVertexAttrib4Nub) +#define glVertexAttrib4Nubv GLEW_GET_FUN(__glewVertexAttrib4Nubv) +#define glVertexAttrib4Nuiv GLEW_GET_FUN(__glewVertexAttrib4Nuiv) +#define glVertexAttrib4Nusv GLEW_GET_FUN(__glewVertexAttrib4Nusv) +#define glVertexAttrib4bv GLEW_GET_FUN(__glewVertexAttrib4bv) +#define glVertexAttrib4d GLEW_GET_FUN(__glewVertexAttrib4d) +#define glVertexAttrib4dv GLEW_GET_FUN(__glewVertexAttrib4dv) +#define glVertexAttrib4f GLEW_GET_FUN(__glewVertexAttrib4f) +#define glVertexAttrib4fv GLEW_GET_FUN(__glewVertexAttrib4fv) +#define glVertexAttrib4iv GLEW_GET_FUN(__glewVertexAttrib4iv) +#define glVertexAttrib4s GLEW_GET_FUN(__glewVertexAttrib4s) +#define glVertexAttrib4sv GLEW_GET_FUN(__glewVertexAttrib4sv) +#define glVertexAttrib4ubv GLEW_GET_FUN(__glewVertexAttrib4ubv) +#define glVertexAttrib4uiv GLEW_GET_FUN(__glewVertexAttrib4uiv) +#define glVertexAttrib4usv GLEW_GET_FUN(__glewVertexAttrib4usv) +#define glVertexAttribPointer GLEW_GET_FUN(__glewVertexAttribPointer) + +#define GLEW_VERSION_2_0 GLEW_GET_VAR(__GLEW_VERSION_2_0) + +#endif /* GL_VERSION_2_0 */ + +/* ----------------------------- GL_VERSION_2_1 ---------------------------- */ + +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 + +#define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#define GL_COMPRESSED_SLUMINANCE 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B + +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX2X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX2X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX3X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX3X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX4X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX4X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); + +#define glUniformMatrix2x3fv GLEW_GET_FUN(__glewUniformMatrix2x3fv) +#define glUniformMatrix2x4fv GLEW_GET_FUN(__glewUniformMatrix2x4fv) +#define glUniformMatrix3x2fv GLEW_GET_FUN(__glewUniformMatrix3x2fv) +#define glUniformMatrix3x4fv GLEW_GET_FUN(__glewUniformMatrix3x4fv) +#define glUniformMatrix4x2fv GLEW_GET_FUN(__glewUniformMatrix4x2fv) +#define glUniformMatrix4x3fv GLEW_GET_FUN(__glewUniformMatrix4x3fv) + +#define GLEW_VERSION_2_1 GLEW_GET_VAR(__GLEW_VERSION_2_1) + +#endif /* GL_VERSION_2_1 */ + +/* ----------------------------- GL_VERSION_3_0 ---------------------------- */ + +#ifndef GL_VERSION_3_0 +#define GL_VERSION_3_0 1 + +#define GL_MAX_CLIP_DISTANCES GL_MAX_CLIP_PLANES +#define GL_CLIP_DISTANCE5 GL_CLIP_PLANE5 +#define GL_CLIP_DISTANCE1 GL_CLIP_PLANE1 +#define GL_CLIP_DISTANCE3 GL_CLIP_PLANE3 +#define GL_COMPARE_REF_TO_TEXTURE GL_COMPARE_R_TO_TEXTURE_ARB +#define GL_CLIP_DISTANCE0 GL_CLIP_PLANE0 +#define GL_CLIP_DISTANCE4 GL_CLIP_PLANE4 +#define GL_CLIP_DISTANCE2 GL_CLIP_PLANE2 +#define GL_MAX_VARYING_COMPONENTS GL_MAX_VARYING_FLOATS +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_DEPTH_BUFFER 0x8223 +#define GL_STENCIL_BUFFER 0x8224 +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_VERTEX_COLOR 0x891A +#define GL_CLAMP_FRAGMENT_COLOR 0x891B +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGB9_E5 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E +#define GL_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_ALPHA_INTEGER 0x8D97 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 + +typedef void (GLAPIENTRY * PFNGLBEGINCONDITIONALRENDERPROC) (GLuint, GLenum); +typedef void (GLAPIENTRY * PFNGLBEGINTRANSFORMFEEDBACKPROC) (GLenum); +typedef void (GLAPIENTRY * PFNGLBINDBUFFERBASEPROC) (GLenum, GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLBINDBUFFERRANGEPROC) (GLenum, GLuint, GLuint, GLintptr, GLsizeiptr); +typedef void (GLAPIENTRY * PFNGLBINDFRAGDATALOCATIONPROC) (GLuint, GLuint, const GLchar*); +typedef void (GLAPIENTRY * PFNGLCLAMPCOLORPROC) (GLenum, GLenum); +typedef void (GLAPIENTRY * PFNGLCLEARBUFFERFIPROC) (GLenum, GLint, GLfloat, GLint); +typedef void (GLAPIENTRY * PFNGLCLEARBUFFERFVPROC) (GLenum, GLint, const GLfloat*); +typedef void (GLAPIENTRY * PFNGLCLEARBUFFERIVPROC) (GLenum, GLint, const GLint*); +typedef void (GLAPIENTRY * PFNGLCLEARBUFFERUIVPROC) (GLenum, GLint, const GLuint*); +typedef void (GLAPIENTRY * PFNGLCOLORMASKIPROC) (GLuint, GLboolean, GLboolean, GLboolean, GLboolean); +typedef void (GLAPIENTRY * PFNGLDISABLEIPROC) (GLenum, GLuint); +typedef void (GLAPIENTRY * PFNGLENABLEIPROC) (GLenum, GLuint); +typedef void (GLAPIENTRY * PFNGLENDCONDITIONALRENDERPROC) (void); +typedef void (GLAPIENTRY * PFNGLENDTRANSFORMFEEDBACKPROC) (void); +typedef void (GLAPIENTRY * PFNGLGETBOOLEANI_VPROC) (GLenum, GLuint, GLboolean*); +typedef GLint (GLAPIENTRY * PFNGLGETFRAGDATALOCATIONPROC) (GLuint, const GLchar*); +typedef void (GLAPIENTRY * PFNGLGETINTEGERI_VPROC) (GLenum, GLuint, GLint*); +typedef const GLubyte* (GLAPIENTRY * PFNGLGETSTRINGIPROC) (GLenum, GLuint); +typedef void (GLAPIENTRY * PFNGLGETTEXPARAMETERIIVPROC) (GLenum, GLenum, GLint*); +typedef void (GLAPIENTRY * PFNGLGETTEXPARAMETERIUIVPROC) (GLenum, GLenum, GLuint*); +typedef void (GLAPIENTRY * PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) (GLuint, GLuint, GLint*); +typedef void (GLAPIENTRY * PFNGLGETUNIFORMUIVPROC) (GLuint, GLint, GLuint*); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIIVPROC) (GLuint, GLenum, GLint*); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIUIVPROC) (GLuint, GLenum, GLuint*); +typedef GLboolean (GLAPIENTRY * PFNGLISENABLEDIPROC) (GLenum, GLuint); +typedef void (GLAPIENTRY * PFNGLTEXPARAMETERIIVPROC) (GLenum, GLenum, const GLint*); +typedef void (GLAPIENTRY * PFNGLTEXPARAMETERIUIVPROC) (GLenum, GLenum, const GLuint*); +typedef void (GLAPIENTRY * PFNGLTRANSFORMFEEDBACKVARYINGSPROC) (GLuint, GLsizei, const GLint*, GLenum); +typedef void (GLAPIENTRY * PFNGLUNIFORM1UIPROC) (GLint, GLuint); +typedef void (GLAPIENTRY * PFNGLUNIFORM1UIVPROC) (GLint, GLsizei, const GLuint*); +typedef void (GLAPIENTRY * PFNGLUNIFORM2UIPROC) (GLint, GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLUNIFORM2UIVPROC) (GLint, GLsizei, const GLuint*); +typedef void (GLAPIENTRY * PFNGLUNIFORM3UIPROC) (GLint, GLuint, GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLUNIFORM3UIVPROC) (GLint, GLsizei, const GLuint*); +typedef void (GLAPIENTRY * PFNGLUNIFORM4UIPROC) (GLint, GLuint, GLuint, GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLUNIFORM4UIVPROC) (GLint, GLsizei, const GLuint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1IPROC) (GLuint, GLint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1IVPROC) (GLuint, const GLint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1UIPROC) (GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1UIVPROC) (GLuint, const GLuint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2IPROC) (GLuint, GLint, GLint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2IVPROC) (GLuint, const GLint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2UIPROC) (GLuint, GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2UIVPROC) (GLuint, const GLuint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3IPROC) (GLuint, GLint, GLint, GLint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3IVPROC) (GLuint, const GLint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3UIPROC) (GLuint, GLuint, GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3UIVPROC) (GLuint, const GLuint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4BVPROC) (GLuint, const GLbyte*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4IPROC) (GLuint, GLint, GLint, GLint, GLint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4IVPROC) (GLuint, const GLint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4SVPROC) (GLuint, const GLshort*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4UBVPROC) (GLuint, const GLubyte*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4UIPROC) (GLuint, GLuint, GLuint, GLuint, GLuint); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4UIVPROC) (GLuint, const GLuint*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4USVPROC) (GLuint, const GLushort*); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBIPOINTERPROC) (GLuint, GLint, GLenum, GLsizei, const GLvoid*); + +#define glBeginConditionalRender GLEW_GET_FUN(__glewBeginConditionalRender) +#define glBeginTransformFeedback GLEW_GET_FUN(__glewBeginTransformFeedback) +#define glBindBufferBase GLEW_GET_FUN(__glewBindBufferBase) +#define glBindBufferRange GLEW_GET_FUN(__glewBindBufferRange) +#define glBindFragDataLocation GLEW_GET_FUN(__glewBindFragDataLocation) +#define glClampColor GLEW_GET_FUN(__glewClampColor) +#define glClearBufferfi GLEW_GET_FUN(__glewClearBufferfi) +#define glClearBufferfv GLEW_GET_FUN(__glewClearBufferfv) +#define glClearBufferiv GLEW_GET_FUN(__glewClearBufferiv) +#define glClearBufferuiv GLEW_GET_FUN(__glewClearBufferuiv) +#define glColorMaski GLEW_GET_FUN(__glewColorMaski) +#define glDisablei GLEW_GET_FUN(__glewDisablei) +#define glEnablei GLEW_GET_FUN(__glewEnablei) +#define glEndConditionalRender GLEW_GET_FUN(__glewEndConditionalRender) +#define glEndTransformFeedback GLEW_GET_FUN(__glewEndTransformFeedback) +#define glGetBooleani_v GLEW_GET_FUN(__glewGetBooleani_v) +#define glGetFragDataLocation GLEW_GET_FUN(__glewGetFragDataLocation) +#define glGetIntegeri_v GLEW_GET_FUN(__glewGetIntegeri_v) +#define glGetStringi GLEW_GET_FUN(__glewGetStringi) +#define glGetTexParameterIiv GLEW_GET_FUN(__glewGetTexParameterIiv) +#define glGetTexParameterIuiv GLEW_GET_FUN(__glewGetTexParameterIuiv) +#define glGetTransformFeedbackVarying GLEW_GET_FUN(__glewGetTransformFeedbackVarying) +#define glGetUniformuiv GLEW_GET_FUN(__glewGetUniformuiv) +#define glGetVertexAttribIiv GLEW_GET_FUN(__glewGetVertexAttribIiv) +#define glGetVertexAttribIuiv GLEW_GET_FUN(__glewGetVertexAttribIuiv) +#define glIsEnabledi GLEW_GET_FUN(__glewIsEnabledi) +#define glTexParameterIiv GLEW_GET_FUN(__glewTexParameterIiv) +#define glTexParameterIuiv GLEW_GET_FUN(__glewTexParameterIuiv) +#define glTransformFeedbackVaryings GLEW_GET_FUN(__glewTransformFeedbackVaryings) +#define glUniform1ui GLEW_GET_FUN(__glewUniform1ui) +#define glUniform1uiv GLEW_GET_FUN(__glewUniform1uiv) +#define glUniform2ui GLEW_GET_FUN(__glewUniform2ui) +#define glUniform2uiv GLEW_GET_FUN(__glewUniform2uiv) +#define glUniform3ui GLEW_GET_FUN(__glewUniform3ui) +#define glUniform3uiv GLEW_GET_FUN(__glewUniform3uiv) +#define glUniform4ui GLEW_GET_FUN(__glewUniform4ui) +#define glUniform4uiv GLEW_GET_FUN(__glewUniform4uiv) +#define glVertexAttribI1i GLEW_GET_FUN(__glewVertexAttribI1i) +#define glVertexAttribI1iv GLEW_GET_FUN(__glewVertexAttribI1iv) +#define glVertexAttribI1ui GLEW_GET_FUN(__glewVertexAttribI1ui) +#define glVertexAttribI1uiv GLEW_GET_FUN(__glewVertexAttribI1uiv) +#define glVertexAttribI2i GLEW_GET_FUN(__glewVertexAttribI2i) +#define glVertexAttribI2iv GLEW_GET_FUN(__glewVertexAttribI2iv) +#define glVertexAttribI2ui GLEW_GET_FUN(__glewVertexAttribI2ui) +#define glVertexAttribI2uiv GLEW_GET_FUN(__glewVertexAttribI2uiv) +#define glVertexAttribI3i GLEW_GET_FUN(__glewVertexAttribI3i) +#define glVertexAttribI3iv GLEW_GET_FUN(__glewVertexAttribI3iv) +#define glVertexAttribI3ui GLEW_GET_FUN(__glewVertexAttribI3ui) +#define glVertexAttribI3uiv GLEW_GET_FUN(__glewVertexAttribI3uiv) +#define glVertexAttribI4bv GLEW_GET_FUN(__glewVertexAttribI4bv) +#define glVertexAttribI4i GLEW_GET_FUN(__glewVertexAttribI4i) +#define glVertexAttribI4iv GLEW_GET_FUN(__glewVertexAttribI4iv) +#define glVertexAttribI4sv GLEW_GET_FUN(__glewVertexAttribI4sv) +#define glVertexAttribI4ubv GLEW_GET_FUN(__glewVertexAttribI4ubv) +#define glVertexAttribI4ui GLEW_GET_FUN(__glewVertexAttribI4ui) +#define glVertexAttribI4uiv GLEW_GET_FUN(__glewVertexAttribI4uiv) +#define glVertexAttribI4usv GLEW_GET_FUN(__glewVertexAttribI4usv) +#define glVertexAttribIPointer GLEW_GET_FUN(__glewVertexAttribIPointer) + +#define GLEW_VERSION_3_0 GLEW_GET_VAR(__GLEW_VERSION_3_0) + +#endif /* GL_VERSION_3_0 */ + +/* -------------------------- GL_3DFX_multisample -------------------------- */ + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 + +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 + +#define GLEW_3DFX_multisample GLEW_GET_VAR(__GLEW_3DFX_multisample) + +#endif /* GL_3DFX_multisample */ + +/* ---------------------------- GL_3DFX_tbuffer ---------------------------- */ + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 + +typedef void (GLAPIENTRY * PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); + +#define glTbufferMask3DFX GLEW_GET_FUN(__glewTbufferMask3DFX) + +#define GLEW_3DFX_tbuffer GLEW_GET_VAR(__GLEW_3DFX_tbuffer) + +#endif /* GL_3DFX_tbuffer */ + +/* -------------------- GL_3DFX_texture_compression_FXT1 ------------------- */ + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 + +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 + +#define GLEW_3DFX_texture_compression_FXT1 GLEW_GET_VAR(__GLEW_3DFX_texture_compression_FXT1) + +#endif /* GL_3DFX_texture_compression_FXT1 */ + +/* ------------------------ GL_APPLE_client_storage ------------------------ */ + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 + +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 + +#define GLEW_APPLE_client_storage GLEW_GET_VAR(__GLEW_APPLE_client_storage) + +#endif /* GL_APPLE_client_storage */ + +/* ------------------------- GL_APPLE_element_array ------------------------ */ + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 + +#define GL_ELEMENT_ARRAY_APPLE 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x876A + +typedef void (GLAPIENTRY * PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (GLAPIENTRY * PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (GLAPIENTRY * PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const void* pointer); +typedef void (GLAPIENTRY * PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint* first, const GLsizei *count, GLsizei primcount); +typedef void (GLAPIENTRY * PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint* first, const GLsizei *count, GLsizei primcount); + +#define glDrawElementArrayAPPLE GLEW_GET_FUN(__glewDrawElementArrayAPPLE) +#define glDrawRangeElementArrayAPPLE GLEW_GET_FUN(__glewDrawRangeElementArrayAPPLE) +#define glElementPointerAPPLE GLEW_GET_FUN(__glewElementPointerAPPLE) +#define glMultiDrawElementArrayAPPLE GLEW_GET_FUN(__glewMultiDrawElementArrayAPPLE) +#define glMultiDrawRangeElementArrayAPPLE GLEW_GET_FUN(__glewMultiDrawRangeElementArrayAPPLE) + +#define GLEW_APPLE_element_array GLEW_GET_VAR(__GLEW_APPLE_element_array) + +#endif /* GL_APPLE_element_array */ + +/* ----------------------------- GL_APPLE_fence ---------------------------- */ + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 + +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B + +typedef void (GLAPIENTRY * PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint* fences); +typedef void (GLAPIENTRY * PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef void (GLAPIENTRY * PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +typedef void (GLAPIENTRY * PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint* fences); +typedef GLboolean (GLAPIENTRY * PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef void (GLAPIENTRY * PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (GLAPIENTRY * PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (GLAPIENTRY * PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); + +#define glDeleteFencesAPPLE GLEW_GET_FUN(__glewDeleteFencesAPPLE) +#define glFinishFenceAPPLE GLEW_GET_FUN(__glewFinishFenceAPPLE) +#define glFinishObjectAPPLE GLEW_GET_FUN(__glewFinishObjectAPPLE) +#define glGenFencesAPPLE GLEW_GET_FUN(__glewGenFencesAPPLE) +#define glIsFenceAPPLE GLEW_GET_FUN(__glewIsFenceAPPLE) +#define glSetFenceAPPLE GLEW_GET_FUN(__glewSetFenceAPPLE) +#define glTestFenceAPPLE GLEW_GET_FUN(__glewTestFenceAPPLE) +#define glTestObjectAPPLE GLEW_GET_FUN(__glewTestObjectAPPLE) + +#define GLEW_APPLE_fence GLEW_GET_VAR(__GLEW_APPLE_fence) + +#endif /* GL_APPLE_fence */ + +/* ------------------------- GL_APPLE_float_pixels ------------------------- */ + +#ifndef GL_APPLE_float_pixels +#define GL_APPLE_float_pixels 1 + +#define GL_HALF_APPLE 0x140B +#define GL_RGBA_FLOAT32_APPLE 0x8814 +#define GL_RGB_FLOAT32_APPLE 0x8815 +#define GL_ALPHA_FLOAT32_APPLE 0x8816 +#define GL_INTENSITY_FLOAT32_APPLE 0x8817 +#define GL_LUMINANCE_FLOAT32_APPLE 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 +#define GL_RGBA_FLOAT16_APPLE 0x881A +#define GL_RGB_FLOAT16_APPLE 0x881B +#define GL_ALPHA_FLOAT16_APPLE 0x881C +#define GL_INTENSITY_FLOAT16_APPLE 0x881D +#define GL_LUMINANCE_FLOAT16_APPLE 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F +#define GL_COLOR_FLOAT_APPLE 0x8A0F + +#define GLEW_APPLE_float_pixels GLEW_GET_VAR(__GLEW_APPLE_float_pixels) + +#endif /* GL_APPLE_float_pixels */ + +/* ---------------------- GL_APPLE_flush_buffer_range ---------------------- */ + +#ifndef GL_APPLE_flush_buffer_range +#define GL_APPLE_flush_buffer_range 1 + +#define GL_BUFFER_SERIALIZED_MODIFY_APPLE 0x8A12 +#define GL_BUFFER_FLUSHING_UNMAP_APPLE 0x8A13 + +typedef void (GLAPIENTRY * PFNGLBUFFERPARAMETERIAPPLEPROC) (GLenum target, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC) (GLenum target, GLintptr offset, GLsizeiptr size); + +#define glBufferParameteriAPPLE GLEW_GET_FUN(__glewBufferParameteriAPPLE) +#define glFlushMappedBufferRangeAPPLE GLEW_GET_FUN(__glewFlushMappedBufferRangeAPPLE) + +#define GLEW_APPLE_flush_buffer_range GLEW_GET_VAR(__GLEW_APPLE_flush_buffer_range) + +#endif /* GL_APPLE_flush_buffer_range */ + +/* ------------------------- GL_APPLE_pixel_buffer ------------------------- */ + +#ifndef GL_APPLE_pixel_buffer +#define GL_APPLE_pixel_buffer 1 + +#define GL_MIN_PBUFFER_VIEWPORT_DIMS_APPLE 0x8A10 + +#define GLEW_APPLE_pixel_buffer GLEW_GET_VAR(__GLEW_APPLE_pixel_buffer) + +#endif /* GL_APPLE_pixel_buffer */ + +/* ------------------------ GL_APPLE_specular_vector ----------------------- */ + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 + +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 + +#define GLEW_APPLE_specular_vector GLEW_GET_VAR(__GLEW_APPLE_specular_vector) + +#endif /* GL_APPLE_specular_vector */ + +/* ------------------------- GL_APPLE_texture_range ------------------------ */ + +#ifndef GL_APPLE_texture_range +#define GL_APPLE_texture_range 1 + +#define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 +#define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 +#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC +#define GL_STORAGE_PRIVATE_APPLE 0x85BD +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF + +typedef void (GLAPIENTRY * PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC) (GLenum target, GLenum pname, GLvoid **params); +typedef void (GLAPIENTRY * PFNGLTEXTURERANGEAPPLEPROC) (GLenum target, GLsizei length, GLvoid *pointer); + +#define glGetTexParameterPointervAPPLE GLEW_GET_FUN(__glewGetTexParameterPointervAPPLE) +#define glTextureRangeAPPLE GLEW_GET_FUN(__glewTextureRangeAPPLE) + +#define GLEW_APPLE_texture_range GLEW_GET_VAR(__GLEW_APPLE_texture_range) + +#endif /* GL_APPLE_texture_range */ + +/* ------------------------ GL_APPLE_transform_hint ------------------------ */ + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 + +#define GL_TRANSFORM_HINT_APPLE 0x85B1 + +#define GLEW_APPLE_transform_hint GLEW_GET_VAR(__GLEW_APPLE_transform_hint) + +#endif /* GL_APPLE_transform_hint */ + +/* ---------------------- GL_APPLE_vertex_array_object --------------------- */ + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 + +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 + +typedef void (GLAPIENTRY * PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (GLAPIENTRY * PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint* arrays); +typedef void (GLAPIENTRY * PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint* arrays); +typedef GLboolean (GLAPIENTRY * PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); + +#define glBindVertexArrayAPPLE GLEW_GET_FUN(__glewBindVertexArrayAPPLE) +#define glDeleteVertexArraysAPPLE GLEW_GET_FUN(__glewDeleteVertexArraysAPPLE) +#define glGenVertexArraysAPPLE GLEW_GET_FUN(__glewGenVertexArraysAPPLE) +#define glIsVertexArrayAPPLE GLEW_GET_FUN(__glewIsVertexArrayAPPLE) + +#define GLEW_APPLE_vertex_array_object GLEW_GET_VAR(__GLEW_APPLE_vertex_array_object) + +#endif /* GL_APPLE_vertex_array_object */ + +/* ---------------------- GL_APPLE_vertex_array_range ---------------------- */ + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 + +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_APPLE 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF + +typedef void (GLAPIENTRY * PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void* pointer); +typedef void (GLAPIENTRY * PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void* pointer); + +#define glFlushVertexArrayRangeAPPLE GLEW_GET_FUN(__glewFlushVertexArrayRangeAPPLE) +#define glVertexArrayParameteriAPPLE GLEW_GET_FUN(__glewVertexArrayParameteriAPPLE) +#define glVertexArrayRangeAPPLE GLEW_GET_FUN(__glewVertexArrayRangeAPPLE) + +#define GLEW_APPLE_vertex_array_range GLEW_GET_VAR(__GLEW_APPLE_vertex_array_range) + +#endif /* GL_APPLE_vertex_array_range */ + +/* --------------------------- GL_APPLE_ycbcr_422 -------------------------- */ + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 + +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB + +#define GLEW_APPLE_ycbcr_422 GLEW_GET_VAR(__GLEW_APPLE_ycbcr_422) + +#endif /* GL_APPLE_ycbcr_422 */ + +/* ----------------------- GL_ARB_color_buffer_float ----------------------- */ + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 + +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D + +typedef void (GLAPIENTRY * PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); + +#define glClampColorARB GLEW_GET_FUN(__glewClampColorARB) + +#define GLEW_ARB_color_buffer_float GLEW_GET_VAR(__GLEW_ARB_color_buffer_float) + +#endif /* GL_ARB_color_buffer_float */ + +/* ----------------------- GL_ARB_depth_buffer_float ----------------------- */ + +#ifndef GL_ARB_depth_buffer_float +#define GL_ARB_depth_buffer_float 1 + +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD + +#define GLEW_ARB_depth_buffer_float GLEW_GET_VAR(__GLEW_ARB_depth_buffer_float) + +#endif /* GL_ARB_depth_buffer_float */ + +/* -------------------------- GL_ARB_depth_texture ------------------------- */ + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 + +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B + +#define GLEW_ARB_depth_texture GLEW_GET_VAR(__GLEW_ARB_depth_texture) + +#endif /* GL_ARB_depth_texture */ + +/* -------------------------- GL_ARB_draw_buffers -------------------------- */ + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 + +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 + +typedef void (GLAPIENTRY * PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum* bufs); + +#define glDrawBuffersARB GLEW_GET_FUN(__glewDrawBuffersARB) + +#define GLEW_ARB_draw_buffers GLEW_GET_VAR(__GLEW_ARB_draw_buffers) + +#endif /* GL_ARB_draw_buffers */ + +/* ------------------------- GL_ARB_draw_instanced ------------------------- */ + +#ifndef GL_ARB_draw_instanced +#define GL_ARB_draw_instanced 1 + +typedef void (GLAPIENTRY * PFNGLDRAWARRAYSINSTANCEDARBPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (GLAPIENTRY * PFNGLDRAWELEMENTSINSTANCEDARBPROC) (GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei primcount); + +#define glDrawArraysInstancedARB GLEW_GET_FUN(__glewDrawArraysInstancedARB) +#define glDrawElementsInstancedARB GLEW_GET_FUN(__glewDrawElementsInstancedARB) + +#define GLEW_ARB_draw_instanced GLEW_GET_VAR(__GLEW_ARB_draw_instanced) + +#endif /* GL_ARB_draw_instanced */ + +/* ------------------------ GL_ARB_fragment_program ------------------------ */ + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 + +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 + +#define GLEW_ARB_fragment_program GLEW_GET_VAR(__GLEW_ARB_fragment_program) + +#endif /* GL_ARB_fragment_program */ + +/* --------------------- GL_ARB_fragment_program_shadow -------------------- */ + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 + +#define GLEW_ARB_fragment_program_shadow GLEW_GET_VAR(__GLEW_ARB_fragment_program_shadow) + +#endif /* GL_ARB_fragment_program_shadow */ + +/* ------------------------- GL_ARB_fragment_shader ------------------------ */ + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 + +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B + +#define GLEW_ARB_fragment_shader GLEW_GET_VAR(__GLEW_ARB_fragment_shader) + +#endif /* GL_ARB_fragment_shader */ + +/* ----------------------- GL_ARB_framebuffer_object ----------------------- */ + +#ifndef GL_ARB_framebuffer_object +#define GL_ARB_framebuffer_object 1 + +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_INDEX 0x8222 +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_SRGB 0x8C40 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 + +typedef void (GLAPIENTRY * PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); +typedef void (GLAPIENTRY * PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef GLenum (GLAPIENTRY * PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint* framebuffers); +typedef void (GLAPIENTRY * PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint* renderbuffers); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURLAYERPROC) (GLenum target,GLenum attachment, GLuint texture,GLint level,GLint layer); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURE1DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURE3DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); +typedef void (GLAPIENTRY * PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint* framebuffers); +typedef void (GLAPIENTRY * PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint* renderbuffers); +typedef void (GLAPIENTRY * PFNGLGENERATEMIPMAPPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); +typedef GLboolean (GLAPIENTRY * PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); + +#define glBindFramebuffer GLEW_GET_FUN(__glewBindFramebuffer) +#define glBindRenderbuffer GLEW_GET_FUN(__glewBindRenderbuffer) +#define glBlitFramebuffer GLEW_GET_FUN(__glewBlitFramebuffer) +#define glCheckFramebufferStatus GLEW_GET_FUN(__glewCheckFramebufferStatus) +#define glDeleteFramebuffers GLEW_GET_FUN(__glewDeleteFramebuffers) +#define glDeleteRenderbuffers GLEW_GET_FUN(__glewDeleteRenderbuffers) +#define glFramebufferRenderbuffer GLEW_GET_FUN(__glewFramebufferRenderbuffer) +#define glFramebufferTexturLayer GLEW_GET_FUN(__glewFramebufferTexturLayer) +#define glFramebufferTexture1D GLEW_GET_FUN(__glewFramebufferTexture1D) +#define glFramebufferTexture2D GLEW_GET_FUN(__glewFramebufferTexture2D) +#define glFramebufferTexture3D GLEW_GET_FUN(__glewFramebufferTexture3D) +#define glGenFramebuffers GLEW_GET_FUN(__glewGenFramebuffers) +#define glGenRenderbuffers GLEW_GET_FUN(__glewGenRenderbuffers) +#define glGenerateMipmap GLEW_GET_FUN(__glewGenerateMipmap) +#define glGetFramebufferAttachmentParameteriv GLEW_GET_FUN(__glewGetFramebufferAttachmentParameteriv) +#define glGetRenderbufferParameteriv GLEW_GET_FUN(__glewGetRenderbufferParameteriv) +#define glIsFramebuffer GLEW_GET_FUN(__glewIsFramebuffer) +#define glIsRenderbuffer GLEW_GET_FUN(__glewIsRenderbuffer) +#define glRenderbufferStorage GLEW_GET_FUN(__glewRenderbufferStorage) +#define glRenderbufferStorageMultisample GLEW_GET_FUN(__glewRenderbufferStorageMultisample) + +#define GLEW_ARB_framebuffer_object GLEW_GET_VAR(__GLEW_ARB_framebuffer_object) + +#endif /* GL_ARB_framebuffer_object */ + +/* ------------------------ GL_ARB_framebuffer_sRGB ------------------------ */ + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_ARB_framebuffer_sRGB 1 + +#define GL_FRAMEBUFFER_SRGB 0x8DB9 + +#define GLEW_ARB_framebuffer_sRGB GLEW_GET_VAR(__GLEW_ARB_framebuffer_sRGB) + +#endif /* GL_ARB_framebuffer_sRGB */ + +/* ------------------------ GL_ARB_geometry_shader4 ------------------------ */ + +#ifndef GL_ARB_geometry_shader4 +#define GL_ARB_geometry_shader4 1 + +#define GL_LINES_ADJACENCY_ARB 0xA +#define GL_LINE_STRIP_ADJACENCY_ARB 0xB +#define GL_TRIANGLES_ADJACENCY_ARB 0xC +#define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0xD +#define GL_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 +#define GL_GEOMETRY_SHADER_ARB 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 + +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTUREARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURELAYERARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETERIARBPROC) (GLuint program, GLenum pname, GLint value); + +#define glFramebufferTextureARB GLEW_GET_FUN(__glewFramebufferTextureARB) +#define glFramebufferTextureFaceARB GLEW_GET_FUN(__glewFramebufferTextureFaceARB) +#define glFramebufferTextureLayerARB GLEW_GET_FUN(__glewFramebufferTextureLayerARB) +#define glProgramParameteriARB GLEW_GET_FUN(__glewProgramParameteriARB) + +#define GLEW_ARB_geometry_shader4 GLEW_GET_VAR(__GLEW_ARB_geometry_shader4) + +#endif /* GL_ARB_geometry_shader4 */ + +/* ------------------------ GL_ARB_half_float_pixel ------------------------ */ + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 + +#define GL_HALF_FLOAT_ARB 0x140B + +#define GLEW_ARB_half_float_pixel GLEW_GET_VAR(__GLEW_ARB_half_float_pixel) + +#endif /* GL_ARB_half_float_pixel */ + +/* ------------------------ GL_ARB_half_float_vertex ----------------------- */ + +#ifndef GL_ARB_half_float_vertex +#define GL_ARB_half_float_vertex 1 + +#define GL_HALF_FLOAT 0x140B + +#define GLEW_ARB_half_float_vertex GLEW_GET_VAR(__GLEW_ARB_half_float_vertex) + +#endif /* GL_ARB_half_float_vertex */ + +/* ----------------------------- GL_ARB_imaging ---------------------------- */ + +#ifndef GL_ARB_imaging +#define GL_ARB_imaging 1 + +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_IGNORE_BORDER 0x8150 +#define GL_CONSTANT_BORDER 0x8151 +#define GL_WRAP_BORDER 0x8152 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 + +typedef void (GLAPIENTRY * PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (GLAPIENTRY * PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (GLAPIENTRY * PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (GLAPIENTRY * PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (GLAPIENTRY * PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (GLAPIENTRY * PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (GLAPIENTRY * PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (GLAPIENTRY * PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum types, GLvoid *values); +typedef void (GLAPIENTRY * PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (GLAPIENTRY * PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (GLAPIENTRY * PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (GLAPIENTRY * PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (GLAPIENTRY * PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLRESETMINMAXPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); + +#define glColorSubTable GLEW_GET_FUN(__glewColorSubTable) +#define glColorTable GLEW_GET_FUN(__glewColorTable) +#define glColorTableParameterfv GLEW_GET_FUN(__glewColorTableParameterfv) +#define glColorTableParameteriv GLEW_GET_FUN(__glewColorTableParameteriv) +#define glConvolutionFilter1D GLEW_GET_FUN(__glewConvolutionFilter1D) +#define glConvolutionFilter2D GLEW_GET_FUN(__glewConvolutionFilter2D) +#define glConvolutionParameterf GLEW_GET_FUN(__glewConvolutionParameterf) +#define glConvolutionParameterfv GLEW_GET_FUN(__glewConvolutionParameterfv) +#define glConvolutionParameteri GLEW_GET_FUN(__glewConvolutionParameteri) +#define glConvolutionParameteriv GLEW_GET_FUN(__glewConvolutionParameteriv) +#define glCopyColorSubTable GLEW_GET_FUN(__glewCopyColorSubTable) +#define glCopyColorTable GLEW_GET_FUN(__glewCopyColorTable) +#define glCopyConvolutionFilter1D GLEW_GET_FUN(__glewCopyConvolutionFilter1D) +#define glCopyConvolutionFilter2D GLEW_GET_FUN(__glewCopyConvolutionFilter2D) +#define glGetColorTable GLEW_GET_FUN(__glewGetColorTable) +#define glGetColorTableParameterfv GLEW_GET_FUN(__glewGetColorTableParameterfv) +#define glGetColorTableParameteriv GLEW_GET_FUN(__glewGetColorTableParameteriv) +#define glGetConvolutionFilter GLEW_GET_FUN(__glewGetConvolutionFilter) +#define glGetConvolutionParameterfv GLEW_GET_FUN(__glewGetConvolutionParameterfv) +#define glGetConvolutionParameteriv GLEW_GET_FUN(__glewGetConvolutionParameteriv) +#define glGetHistogram GLEW_GET_FUN(__glewGetHistogram) +#define glGetHistogramParameterfv GLEW_GET_FUN(__glewGetHistogramParameterfv) +#define glGetHistogramParameteriv GLEW_GET_FUN(__glewGetHistogramParameteriv) +#define glGetMinmax GLEW_GET_FUN(__glewGetMinmax) +#define glGetMinmaxParameterfv GLEW_GET_FUN(__glewGetMinmaxParameterfv) +#define glGetMinmaxParameteriv GLEW_GET_FUN(__glewGetMinmaxParameteriv) +#define glGetSeparableFilter GLEW_GET_FUN(__glewGetSeparableFilter) +#define glHistogram GLEW_GET_FUN(__glewHistogram) +#define glMinmax GLEW_GET_FUN(__glewMinmax) +#define glResetHistogram GLEW_GET_FUN(__glewResetHistogram) +#define glResetMinmax GLEW_GET_FUN(__glewResetMinmax) +#define glSeparableFilter2D GLEW_GET_FUN(__glewSeparableFilter2D) + +#define GLEW_ARB_imaging GLEW_GET_VAR(__GLEW_ARB_imaging) + +#endif /* GL_ARB_imaging */ + +/* ------------------------ GL_ARB_instanced_arrays ------------------------ */ + +#ifndef GL_ARB_instanced_arrays +#define GL_ARB_instanced_arrays 1 + +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE + +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBDIVISORARBPROC) (GLuint index, GLuint divisor); + +#define glVertexAttribDivisorARB GLEW_GET_FUN(__glewVertexAttribDivisorARB) + +#define GLEW_ARB_instanced_arrays GLEW_GET_VAR(__GLEW_ARB_instanced_arrays) + +#endif /* GL_ARB_instanced_arrays */ + +/* ------------------------ GL_ARB_map_buffer_range ------------------------ */ + +#ifndef GL_ARB_map_buffer_range +#define GL_ARB_map_buffer_range 1 + +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 + +typedef void (GLAPIENTRY * PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); +typedef GLvoid * (GLAPIENTRY * PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); + +#define glFlushMappedBufferRange GLEW_GET_FUN(__glewFlushMappedBufferRange) +#define glMapBufferRange GLEW_GET_FUN(__glewMapBufferRange) + +#define GLEW_ARB_map_buffer_range GLEW_GET_VAR(__GLEW_ARB_map_buffer_range) + +#endif /* GL_ARB_map_buffer_range */ + +/* ------------------------- GL_ARB_matrix_palette ------------------------- */ + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 + +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 + +typedef void (GLAPIENTRY * PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (GLAPIENTRY * PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLMATRIXINDEXUBVARBPROC) (GLint size, GLubyte *indices); +typedef void (GLAPIENTRY * PFNGLMATRIXINDEXUIVARBPROC) (GLint size, GLuint *indices); +typedef void (GLAPIENTRY * PFNGLMATRIXINDEXUSVARBPROC) (GLint size, GLushort *indices); + +#define glCurrentPaletteMatrixARB GLEW_GET_FUN(__glewCurrentPaletteMatrixARB) +#define glMatrixIndexPointerARB GLEW_GET_FUN(__glewMatrixIndexPointerARB) +#define glMatrixIndexubvARB GLEW_GET_FUN(__glewMatrixIndexubvARB) +#define glMatrixIndexuivARB GLEW_GET_FUN(__glewMatrixIndexuivARB) +#define glMatrixIndexusvARB GLEW_GET_FUN(__glewMatrixIndexusvARB) + +#define GLEW_ARB_matrix_palette GLEW_GET_VAR(__GLEW_ARB_matrix_palette) + +#endif /* GL_ARB_matrix_palette */ + +/* --------------------------- GL_ARB_multisample -------------------------- */ + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 + +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 + +typedef void (GLAPIENTRY * PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); + +#define glSampleCoverageARB GLEW_GET_FUN(__glewSampleCoverageARB) + +#define GLEW_ARB_multisample GLEW_GET_VAR(__GLEW_ARB_multisample) + +#endif /* GL_ARB_multisample */ + +/* -------------------------- GL_ARB_multitexture -------------------------- */ + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 + +typedef void (GLAPIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (GLAPIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); + +#define glActiveTextureARB GLEW_GET_FUN(__glewActiveTextureARB) +#define glClientActiveTextureARB GLEW_GET_FUN(__glewClientActiveTextureARB) +#define glMultiTexCoord1dARB GLEW_GET_FUN(__glewMultiTexCoord1dARB) +#define glMultiTexCoord1dvARB GLEW_GET_FUN(__glewMultiTexCoord1dvARB) +#define glMultiTexCoord1fARB GLEW_GET_FUN(__glewMultiTexCoord1fARB) +#define glMultiTexCoord1fvARB GLEW_GET_FUN(__glewMultiTexCoord1fvARB) +#define glMultiTexCoord1iARB GLEW_GET_FUN(__glewMultiTexCoord1iARB) +#define glMultiTexCoord1ivARB GLEW_GET_FUN(__glewMultiTexCoord1ivARB) +#define glMultiTexCoord1sARB GLEW_GET_FUN(__glewMultiTexCoord1sARB) +#define glMultiTexCoord1svARB GLEW_GET_FUN(__glewMultiTexCoord1svARB) +#define glMultiTexCoord2dARB GLEW_GET_FUN(__glewMultiTexCoord2dARB) +#define glMultiTexCoord2dvARB GLEW_GET_FUN(__glewMultiTexCoord2dvARB) +#define glMultiTexCoord2fARB GLEW_GET_FUN(__glewMultiTexCoord2fARB) +#define glMultiTexCoord2fvARB GLEW_GET_FUN(__glewMultiTexCoord2fvARB) +#define glMultiTexCoord2iARB GLEW_GET_FUN(__glewMultiTexCoord2iARB) +#define glMultiTexCoord2ivARB GLEW_GET_FUN(__glewMultiTexCoord2ivARB) +#define glMultiTexCoord2sARB GLEW_GET_FUN(__glewMultiTexCoord2sARB) +#define glMultiTexCoord2svARB GLEW_GET_FUN(__glewMultiTexCoord2svARB) +#define glMultiTexCoord3dARB GLEW_GET_FUN(__glewMultiTexCoord3dARB) +#define glMultiTexCoord3dvARB GLEW_GET_FUN(__glewMultiTexCoord3dvARB) +#define glMultiTexCoord3fARB GLEW_GET_FUN(__glewMultiTexCoord3fARB) +#define glMultiTexCoord3fvARB GLEW_GET_FUN(__glewMultiTexCoord3fvARB) +#define glMultiTexCoord3iARB GLEW_GET_FUN(__glewMultiTexCoord3iARB) +#define glMultiTexCoord3ivARB GLEW_GET_FUN(__glewMultiTexCoord3ivARB) +#define glMultiTexCoord3sARB GLEW_GET_FUN(__glewMultiTexCoord3sARB) +#define glMultiTexCoord3svARB GLEW_GET_FUN(__glewMultiTexCoord3svARB) +#define glMultiTexCoord4dARB GLEW_GET_FUN(__glewMultiTexCoord4dARB) +#define glMultiTexCoord4dvARB GLEW_GET_FUN(__glewMultiTexCoord4dvARB) +#define glMultiTexCoord4fARB GLEW_GET_FUN(__glewMultiTexCoord4fARB) +#define glMultiTexCoord4fvARB GLEW_GET_FUN(__glewMultiTexCoord4fvARB) +#define glMultiTexCoord4iARB GLEW_GET_FUN(__glewMultiTexCoord4iARB) +#define glMultiTexCoord4ivARB GLEW_GET_FUN(__glewMultiTexCoord4ivARB) +#define glMultiTexCoord4sARB GLEW_GET_FUN(__glewMultiTexCoord4sARB) +#define glMultiTexCoord4svARB GLEW_GET_FUN(__glewMultiTexCoord4svARB) + +#define GLEW_ARB_multitexture GLEW_GET_VAR(__GLEW_ARB_multitexture) + +#endif /* GL_ARB_multitexture */ + +/* ------------------------- GL_ARB_occlusion_query ------------------------ */ + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 + +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 + +typedef void (GLAPIENTRY * PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (GLAPIENTRY * PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint* ids); +typedef void (GLAPIENTRY * PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint* ids); +typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint* params); +typedef void (GLAPIENTRY * PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISQUERYARBPROC) (GLuint id); + +#define glBeginQueryARB GLEW_GET_FUN(__glewBeginQueryARB) +#define glDeleteQueriesARB GLEW_GET_FUN(__glewDeleteQueriesARB) +#define glEndQueryARB GLEW_GET_FUN(__glewEndQueryARB) +#define glGenQueriesARB GLEW_GET_FUN(__glewGenQueriesARB) +#define glGetQueryObjectivARB GLEW_GET_FUN(__glewGetQueryObjectivARB) +#define glGetQueryObjectuivARB GLEW_GET_FUN(__glewGetQueryObjectuivARB) +#define glGetQueryivARB GLEW_GET_FUN(__glewGetQueryivARB) +#define glIsQueryARB GLEW_GET_FUN(__glewIsQueryARB) + +#define GLEW_ARB_occlusion_query GLEW_GET_VAR(__GLEW_ARB_occlusion_query) + +#endif /* GL_ARB_occlusion_query */ + +/* ----------------------- GL_ARB_pixel_buffer_object ---------------------- */ + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 + +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF + +#define GLEW_ARB_pixel_buffer_object GLEW_GET_VAR(__GLEW_ARB_pixel_buffer_object) + +#endif /* GL_ARB_pixel_buffer_object */ + +/* ------------------------ GL_ARB_point_parameters ------------------------ */ + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 + +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 + +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, GLfloat* params); + +#define glPointParameterfARB GLEW_GET_FUN(__glewPointParameterfARB) +#define glPointParameterfvARB GLEW_GET_FUN(__glewPointParameterfvARB) + +#define GLEW_ARB_point_parameters GLEW_GET_VAR(__GLEW_ARB_point_parameters) + +#endif /* GL_ARB_point_parameters */ + +/* -------------------------- GL_ARB_point_sprite -------------------------- */ + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 + +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 + +#define GLEW_ARB_point_sprite GLEW_GET_VAR(__GLEW_ARB_point_sprite) + +#endif /* GL_ARB_point_sprite */ + +/* ------------------------- GL_ARB_shader_objects ------------------------- */ + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 + +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 + +typedef char GLcharARB; +typedef unsigned int GLhandleARB; + +typedef void (GLAPIENTRY * PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (GLAPIENTRY * PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (GLAPIENTRY * PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef GLhandleARB (GLAPIENTRY * PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (GLAPIENTRY * PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef void (GLAPIENTRY * PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef void (GLAPIENTRY * PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei* length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (GLAPIENTRY * PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei* count, GLhandleARB *obj); +typedef GLhandleARB (GLAPIENTRY * PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (GLAPIENTRY * PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei* length, GLcharARB *infoLog); +typedef void (GLAPIENTRY * PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei* length, GLcharARB *source); +typedef GLint (GLAPIENTRY * PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB* name); +typedef void (GLAPIENTRY * PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint* params); +typedef void (GLAPIENTRY * PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (GLAPIENTRY * PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB ** string, const GLint *length); +typedef void (GLAPIENTRY * PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (GLAPIENTRY * PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (GLAPIENTRY * PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (GLAPIENTRY * PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (GLAPIENTRY * PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (GLAPIENTRY * PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (GLAPIENTRY * PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (GLAPIENTRY * PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (GLAPIENTRY * PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (GLAPIENTRY * PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); + +#define glAttachObjectARB GLEW_GET_FUN(__glewAttachObjectARB) +#define glCompileShaderARB GLEW_GET_FUN(__glewCompileShaderARB) +#define glCreateProgramObjectARB GLEW_GET_FUN(__glewCreateProgramObjectARB) +#define glCreateShaderObjectARB GLEW_GET_FUN(__glewCreateShaderObjectARB) +#define glDeleteObjectARB GLEW_GET_FUN(__glewDeleteObjectARB) +#define glDetachObjectARB GLEW_GET_FUN(__glewDetachObjectARB) +#define glGetActiveUniformARB GLEW_GET_FUN(__glewGetActiveUniformARB) +#define glGetAttachedObjectsARB GLEW_GET_FUN(__glewGetAttachedObjectsARB) +#define glGetHandleARB GLEW_GET_FUN(__glewGetHandleARB) +#define glGetInfoLogARB GLEW_GET_FUN(__glewGetInfoLogARB) +#define glGetObjectParameterfvARB GLEW_GET_FUN(__glewGetObjectParameterfvARB) +#define glGetObjectParameterivARB GLEW_GET_FUN(__glewGetObjectParameterivARB) +#define glGetShaderSourceARB GLEW_GET_FUN(__glewGetShaderSourceARB) +#define glGetUniformLocationARB GLEW_GET_FUN(__glewGetUniformLocationARB) +#define glGetUniformfvARB GLEW_GET_FUN(__glewGetUniformfvARB) +#define glGetUniformivARB GLEW_GET_FUN(__glewGetUniformivARB) +#define glLinkProgramARB GLEW_GET_FUN(__glewLinkProgramARB) +#define glShaderSourceARB GLEW_GET_FUN(__glewShaderSourceARB) +#define glUniform1fARB GLEW_GET_FUN(__glewUniform1fARB) +#define glUniform1fvARB GLEW_GET_FUN(__glewUniform1fvARB) +#define glUniform1iARB GLEW_GET_FUN(__glewUniform1iARB) +#define glUniform1ivARB GLEW_GET_FUN(__glewUniform1ivARB) +#define glUniform2fARB GLEW_GET_FUN(__glewUniform2fARB) +#define glUniform2fvARB GLEW_GET_FUN(__glewUniform2fvARB) +#define glUniform2iARB GLEW_GET_FUN(__glewUniform2iARB) +#define glUniform2ivARB GLEW_GET_FUN(__glewUniform2ivARB) +#define glUniform3fARB GLEW_GET_FUN(__glewUniform3fARB) +#define glUniform3fvARB GLEW_GET_FUN(__glewUniform3fvARB) +#define glUniform3iARB GLEW_GET_FUN(__glewUniform3iARB) +#define glUniform3ivARB GLEW_GET_FUN(__glewUniform3ivARB) +#define glUniform4fARB GLEW_GET_FUN(__glewUniform4fARB) +#define glUniform4fvARB GLEW_GET_FUN(__glewUniform4fvARB) +#define glUniform4iARB GLEW_GET_FUN(__glewUniform4iARB) +#define glUniform4ivARB GLEW_GET_FUN(__glewUniform4ivARB) +#define glUniformMatrix2fvARB GLEW_GET_FUN(__glewUniformMatrix2fvARB) +#define glUniformMatrix3fvARB GLEW_GET_FUN(__glewUniformMatrix3fvARB) +#define glUniformMatrix4fvARB GLEW_GET_FUN(__glewUniformMatrix4fvARB) +#define glUseProgramObjectARB GLEW_GET_FUN(__glewUseProgramObjectARB) +#define glValidateProgramARB GLEW_GET_FUN(__glewValidateProgramARB) + +#define GLEW_ARB_shader_objects GLEW_GET_VAR(__GLEW_ARB_shader_objects) + +#endif /* GL_ARB_shader_objects */ + +/* ---------------------- GL_ARB_shading_language_100 ---------------------- */ + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 + +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C + +#define GLEW_ARB_shading_language_100 GLEW_GET_VAR(__GLEW_ARB_shading_language_100) + +#endif /* GL_ARB_shading_language_100 */ + +/* ----------------------------- GL_ARB_shadow ----------------------------- */ + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 + +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E + +#define GLEW_ARB_shadow GLEW_GET_VAR(__GLEW_ARB_shadow) + +#endif /* GL_ARB_shadow */ + +/* ------------------------- GL_ARB_shadow_ambient ------------------------- */ + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 + +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF + +#define GLEW_ARB_shadow_ambient GLEW_GET_VAR(__GLEW_ARB_shadow_ambient) + +#endif /* GL_ARB_shadow_ambient */ + +/* ---------------------- GL_ARB_texture_border_clamp ---------------------- */ + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 + +#define GL_CLAMP_TO_BORDER_ARB 0x812D + +#define GLEW_ARB_texture_border_clamp GLEW_GET_VAR(__GLEW_ARB_texture_border_clamp) + +#endif /* GL_ARB_texture_border_clamp */ + +/* ---------------------- GL_ARB_texture_buffer_object --------------------- */ + +#ifndef GL_ARB_texture_buffer_object +#define GL_ARB_texture_buffer_object 1 + +#define GL_TEXTURE_BUFFER_ARB 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E + +typedef void (GLAPIENTRY * PFNGLTEXBUFFERARBPROC) (GLenum target, GLenum internalformat, GLuint buffer); + +#define glTexBufferARB GLEW_GET_FUN(__glewTexBufferARB) + +#define GLEW_ARB_texture_buffer_object GLEW_GET_VAR(__GLEW_ARB_texture_buffer_object) + +#endif /* GL_ARB_texture_buffer_object */ + +/* ----------------------- GL_ARB_texture_compression ---------------------- */ + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 + +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 + +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint lod, void* img); + +#define glCompressedTexImage1DARB GLEW_GET_FUN(__glewCompressedTexImage1DARB) +#define glCompressedTexImage2DARB GLEW_GET_FUN(__glewCompressedTexImage2DARB) +#define glCompressedTexImage3DARB GLEW_GET_FUN(__glewCompressedTexImage3DARB) +#define glCompressedTexSubImage1DARB GLEW_GET_FUN(__glewCompressedTexSubImage1DARB) +#define glCompressedTexSubImage2DARB GLEW_GET_FUN(__glewCompressedTexSubImage2DARB) +#define glCompressedTexSubImage3DARB GLEW_GET_FUN(__glewCompressedTexSubImage3DARB) +#define glGetCompressedTexImageARB GLEW_GET_FUN(__glewGetCompressedTexImageARB) + +#define GLEW_ARB_texture_compression GLEW_GET_VAR(__GLEW_ARB_texture_compression) + +#endif /* GL_ARB_texture_compression */ + +/* -------------------- GL_ARB_texture_compression_rgtc -------------------- */ + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_ARB_texture_compression_rgtc 1 + +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE + +#define GLEW_ARB_texture_compression_rgtc GLEW_GET_VAR(__GLEW_ARB_texture_compression_rgtc) + +#endif /* GL_ARB_texture_compression_rgtc */ + +/* ------------------------ GL_ARB_texture_cube_map ------------------------ */ + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 + +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C + +#define GLEW_ARB_texture_cube_map GLEW_GET_VAR(__GLEW_ARB_texture_cube_map) + +#endif /* GL_ARB_texture_cube_map */ + +/* ------------------------- GL_ARB_texture_env_add ------------------------ */ + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 + +#define GLEW_ARB_texture_env_add GLEW_GET_VAR(__GLEW_ARB_texture_env_add) + +#endif /* GL_ARB_texture_env_add */ + +/* ----------------------- GL_ARB_texture_env_combine ---------------------- */ + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 + +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A + +#define GLEW_ARB_texture_env_combine GLEW_GET_VAR(__GLEW_ARB_texture_env_combine) + +#endif /* GL_ARB_texture_env_combine */ + +/* ---------------------- GL_ARB_texture_env_crossbar ---------------------- */ + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 + +#define GLEW_ARB_texture_env_crossbar GLEW_GET_VAR(__GLEW_ARB_texture_env_crossbar) + +#endif /* GL_ARB_texture_env_crossbar */ + +/* ------------------------ GL_ARB_texture_env_dot3 ------------------------ */ + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 + +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF + +#define GLEW_ARB_texture_env_dot3 GLEW_GET_VAR(__GLEW_ARB_texture_env_dot3) + +#endif /* GL_ARB_texture_env_dot3 */ + +/* -------------------------- GL_ARB_texture_float ------------------------- */ + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 + +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 + +#define GLEW_ARB_texture_float GLEW_GET_VAR(__GLEW_ARB_texture_float) + +#endif /* GL_ARB_texture_float */ + +/* --------------------- GL_ARB_texture_mirrored_repeat -------------------- */ + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 + +#define GL_MIRRORED_REPEAT_ARB 0x8370 + +#define GLEW_ARB_texture_mirrored_repeat GLEW_GET_VAR(__GLEW_ARB_texture_mirrored_repeat) + +#endif /* GL_ARB_texture_mirrored_repeat */ + +/* -------------------- GL_ARB_texture_non_power_of_two -------------------- */ + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 + +#define GLEW_ARB_texture_non_power_of_two GLEW_GET_VAR(__GLEW_ARB_texture_non_power_of_two) + +#endif /* GL_ARB_texture_non_power_of_two */ + +/* ------------------------ GL_ARB_texture_rectangle ----------------------- */ + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 + +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 + +#define GLEW_ARB_texture_rectangle GLEW_GET_VAR(__GLEW_ARB_texture_rectangle) + +#endif /* GL_ARB_texture_rectangle */ + +/* --------------------------- GL_ARB_texture_rg --------------------------- */ + +#ifndef GL_ARB_texture_rg +#define GL_ARB_texture_rg 1 + +#define GL_RED 0x1903 +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C + +#define GLEW_ARB_texture_rg GLEW_GET_VAR(__GLEW_ARB_texture_rg) + +#endif /* GL_ARB_texture_rg */ + +/* ------------------------ GL_ARB_transpose_matrix ------------------------ */ + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 + +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 + +typedef void (GLAPIENTRY * PFNGLLOADTRANSPOSEMATRIXDARBPROC) (GLdouble m[16]); +typedef void (GLAPIENTRY * PFNGLLOADTRANSPOSEMATRIXFARBPROC) (GLfloat m[16]); +typedef void (GLAPIENTRY * PFNGLMULTTRANSPOSEMATRIXDARBPROC) (GLdouble m[16]); +typedef void (GLAPIENTRY * PFNGLMULTTRANSPOSEMATRIXFARBPROC) (GLfloat m[16]); + +#define glLoadTransposeMatrixdARB GLEW_GET_FUN(__glewLoadTransposeMatrixdARB) +#define glLoadTransposeMatrixfARB GLEW_GET_FUN(__glewLoadTransposeMatrixfARB) +#define glMultTransposeMatrixdARB GLEW_GET_FUN(__glewMultTransposeMatrixdARB) +#define glMultTransposeMatrixfARB GLEW_GET_FUN(__glewMultTransposeMatrixfARB) + +#define GLEW_ARB_transpose_matrix GLEW_GET_VAR(__GLEW_ARB_transpose_matrix) + +#endif /* GL_ARB_transpose_matrix */ + +/* ----------------------- GL_ARB_vertex_array_object ---------------------- */ + +#ifndef GL_ARB_vertex_array_object +#define GL_ARB_vertex_array_object 1 + +#define GL_VERTEX_ARRAY_BINDING 0x85B5 + +typedef void (GLAPIENTRY * PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (GLAPIENTRY * PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint* arrays); +typedef void (GLAPIENTRY * PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint* arrays); +typedef GLboolean (GLAPIENTRY * PFNGLISVERTEXARRAYPROC) (GLuint array); + +#define glBindVertexArray GLEW_GET_FUN(__glewBindVertexArray) +#define glDeleteVertexArrays GLEW_GET_FUN(__glewDeleteVertexArrays) +#define glGenVertexArrays GLEW_GET_FUN(__glewGenVertexArrays) +#define glIsVertexArray GLEW_GET_FUN(__glewIsVertexArray) + +#define GLEW_ARB_vertex_array_object GLEW_GET_VAR(__GLEW_ARB_vertex_array_object) + +#endif /* GL_ARB_vertex_array_object */ + +/* -------------------------- GL_ARB_vertex_blend -------------------------- */ + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 + +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F + +typedef void (GLAPIENTRY * PFNGLVERTEXBLENDARBPROC) (GLint count); +typedef void (GLAPIENTRY * PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLWEIGHTBVARBPROC) (GLint size, GLbyte *weights); +typedef void (GLAPIENTRY * PFNGLWEIGHTDVARBPROC) (GLint size, GLdouble *weights); +typedef void (GLAPIENTRY * PFNGLWEIGHTFVARBPROC) (GLint size, GLfloat *weights); +typedef void (GLAPIENTRY * PFNGLWEIGHTIVARBPROC) (GLint size, GLint *weights); +typedef void (GLAPIENTRY * PFNGLWEIGHTSVARBPROC) (GLint size, GLshort *weights); +typedef void (GLAPIENTRY * PFNGLWEIGHTUBVARBPROC) (GLint size, GLubyte *weights); +typedef void (GLAPIENTRY * PFNGLWEIGHTUIVARBPROC) (GLint size, GLuint *weights); +typedef void (GLAPIENTRY * PFNGLWEIGHTUSVARBPROC) (GLint size, GLushort *weights); + +#define glVertexBlendARB GLEW_GET_FUN(__glewVertexBlendARB) +#define glWeightPointerARB GLEW_GET_FUN(__glewWeightPointerARB) +#define glWeightbvARB GLEW_GET_FUN(__glewWeightbvARB) +#define glWeightdvARB GLEW_GET_FUN(__glewWeightdvARB) +#define glWeightfvARB GLEW_GET_FUN(__glewWeightfvARB) +#define glWeightivARB GLEW_GET_FUN(__glewWeightivARB) +#define glWeightsvARB GLEW_GET_FUN(__glewWeightsvARB) +#define glWeightubvARB GLEW_GET_FUN(__glewWeightubvARB) +#define glWeightuivARB GLEW_GET_FUN(__glewWeightuivARB) +#define glWeightusvARB GLEW_GET_FUN(__glewWeightusvARB) + +#define GLEW_ARB_vertex_blend GLEW_GET_VAR(__GLEW_ARB_vertex_blend) + +#endif /* GL_ARB_vertex_blend */ + +/* ---------------------- GL_ARB_vertex_buffer_object ---------------------- */ + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 + +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA + +typedef ptrdiff_t GLsizeiptrARB; +typedef ptrdiff_t GLintptrARB; + +typedef void (GLAPIENTRY * PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (GLAPIENTRY * PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid* data, GLenum usage); +typedef void (GLAPIENTRY * PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid* data); +typedef void (GLAPIENTRY * PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint* buffers); +typedef void (GLAPIENTRY * PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint* buffers); +typedef void (GLAPIENTRY * PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid** params); +typedef void (GLAPIENTRY * PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid* data); +typedef GLboolean (GLAPIENTRY * PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef GLvoid * (GLAPIENTRY * PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (GLAPIENTRY * PFNGLUNMAPBUFFERARBPROC) (GLenum target); + +#define glBindBufferARB GLEW_GET_FUN(__glewBindBufferARB) +#define glBufferDataARB GLEW_GET_FUN(__glewBufferDataARB) +#define glBufferSubDataARB GLEW_GET_FUN(__glewBufferSubDataARB) +#define glDeleteBuffersARB GLEW_GET_FUN(__glewDeleteBuffersARB) +#define glGenBuffersARB GLEW_GET_FUN(__glewGenBuffersARB) +#define glGetBufferParameterivARB GLEW_GET_FUN(__glewGetBufferParameterivARB) +#define glGetBufferPointervARB GLEW_GET_FUN(__glewGetBufferPointervARB) +#define glGetBufferSubDataARB GLEW_GET_FUN(__glewGetBufferSubDataARB) +#define glIsBufferARB GLEW_GET_FUN(__glewIsBufferARB) +#define glMapBufferARB GLEW_GET_FUN(__glewMapBufferARB) +#define glUnmapBufferARB GLEW_GET_FUN(__glewUnmapBufferARB) + +#define GLEW_ARB_vertex_buffer_object GLEW_GET_VAR(__GLEW_ARB_vertex_buffer_object) + +#endif /* GL_ARB_vertex_buffer_object */ + +/* ------------------------- GL_ARB_vertex_program ------------------------- */ + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 + +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF + +typedef void (GLAPIENTRY * PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (GLAPIENTRY * PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint* programs); +typedef void (GLAPIENTRY * PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (GLAPIENTRY * PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (GLAPIENTRY * PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint* programs); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, void* string); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, GLvoid** pointer); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISPROGRAMARBPROC) (GLuint program); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const void* string); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); + +#define glBindProgramARB GLEW_GET_FUN(__glewBindProgramARB) +#define glDeleteProgramsARB GLEW_GET_FUN(__glewDeleteProgramsARB) +#define glDisableVertexAttribArrayARB GLEW_GET_FUN(__glewDisableVertexAttribArrayARB) +#define glEnableVertexAttribArrayARB GLEW_GET_FUN(__glewEnableVertexAttribArrayARB) +#define glGenProgramsARB GLEW_GET_FUN(__glewGenProgramsARB) +#define glGetProgramEnvParameterdvARB GLEW_GET_FUN(__glewGetProgramEnvParameterdvARB) +#define glGetProgramEnvParameterfvARB GLEW_GET_FUN(__glewGetProgramEnvParameterfvARB) +#define glGetProgramLocalParameterdvARB GLEW_GET_FUN(__glewGetProgramLocalParameterdvARB) +#define glGetProgramLocalParameterfvARB GLEW_GET_FUN(__glewGetProgramLocalParameterfvARB) +#define glGetProgramStringARB GLEW_GET_FUN(__glewGetProgramStringARB) +#define glGetProgramivARB GLEW_GET_FUN(__glewGetProgramivARB) +#define glGetVertexAttribPointervARB GLEW_GET_FUN(__glewGetVertexAttribPointervARB) +#define glGetVertexAttribdvARB GLEW_GET_FUN(__glewGetVertexAttribdvARB) +#define glGetVertexAttribfvARB GLEW_GET_FUN(__glewGetVertexAttribfvARB) +#define glGetVertexAttribivARB GLEW_GET_FUN(__glewGetVertexAttribivARB) +#define glIsProgramARB GLEW_GET_FUN(__glewIsProgramARB) +#define glProgramEnvParameter4dARB GLEW_GET_FUN(__glewProgramEnvParameter4dARB) +#define glProgramEnvParameter4dvARB GLEW_GET_FUN(__glewProgramEnvParameter4dvARB) +#define glProgramEnvParameter4fARB GLEW_GET_FUN(__glewProgramEnvParameter4fARB) +#define glProgramEnvParameter4fvARB GLEW_GET_FUN(__glewProgramEnvParameter4fvARB) +#define glProgramLocalParameter4dARB GLEW_GET_FUN(__glewProgramLocalParameter4dARB) +#define glProgramLocalParameter4dvARB GLEW_GET_FUN(__glewProgramLocalParameter4dvARB) +#define glProgramLocalParameter4fARB GLEW_GET_FUN(__glewProgramLocalParameter4fARB) +#define glProgramLocalParameter4fvARB GLEW_GET_FUN(__glewProgramLocalParameter4fvARB) +#define glProgramStringARB GLEW_GET_FUN(__glewProgramStringARB) +#define glVertexAttrib1dARB GLEW_GET_FUN(__glewVertexAttrib1dARB) +#define glVertexAttrib1dvARB GLEW_GET_FUN(__glewVertexAttrib1dvARB) +#define glVertexAttrib1fARB GLEW_GET_FUN(__glewVertexAttrib1fARB) +#define glVertexAttrib1fvARB GLEW_GET_FUN(__glewVertexAttrib1fvARB) +#define glVertexAttrib1sARB GLEW_GET_FUN(__glewVertexAttrib1sARB) +#define glVertexAttrib1svARB GLEW_GET_FUN(__glewVertexAttrib1svARB) +#define glVertexAttrib2dARB GLEW_GET_FUN(__glewVertexAttrib2dARB) +#define glVertexAttrib2dvARB GLEW_GET_FUN(__glewVertexAttrib2dvARB) +#define glVertexAttrib2fARB GLEW_GET_FUN(__glewVertexAttrib2fARB) +#define glVertexAttrib2fvARB GLEW_GET_FUN(__glewVertexAttrib2fvARB) +#define glVertexAttrib2sARB GLEW_GET_FUN(__glewVertexAttrib2sARB) +#define glVertexAttrib2svARB GLEW_GET_FUN(__glewVertexAttrib2svARB) +#define glVertexAttrib3dARB GLEW_GET_FUN(__glewVertexAttrib3dARB) +#define glVertexAttrib3dvARB GLEW_GET_FUN(__glewVertexAttrib3dvARB) +#define glVertexAttrib3fARB GLEW_GET_FUN(__glewVertexAttrib3fARB) +#define glVertexAttrib3fvARB GLEW_GET_FUN(__glewVertexAttrib3fvARB) +#define glVertexAttrib3sARB GLEW_GET_FUN(__glewVertexAttrib3sARB) +#define glVertexAttrib3svARB GLEW_GET_FUN(__glewVertexAttrib3svARB) +#define glVertexAttrib4NbvARB GLEW_GET_FUN(__glewVertexAttrib4NbvARB) +#define glVertexAttrib4NivARB GLEW_GET_FUN(__glewVertexAttrib4NivARB) +#define glVertexAttrib4NsvARB GLEW_GET_FUN(__glewVertexAttrib4NsvARB) +#define glVertexAttrib4NubARB GLEW_GET_FUN(__glewVertexAttrib4NubARB) +#define glVertexAttrib4NubvARB GLEW_GET_FUN(__glewVertexAttrib4NubvARB) +#define glVertexAttrib4NuivARB GLEW_GET_FUN(__glewVertexAttrib4NuivARB) +#define glVertexAttrib4NusvARB GLEW_GET_FUN(__glewVertexAttrib4NusvARB) +#define glVertexAttrib4bvARB GLEW_GET_FUN(__glewVertexAttrib4bvARB) +#define glVertexAttrib4dARB GLEW_GET_FUN(__glewVertexAttrib4dARB) +#define glVertexAttrib4dvARB GLEW_GET_FUN(__glewVertexAttrib4dvARB) +#define glVertexAttrib4fARB GLEW_GET_FUN(__glewVertexAttrib4fARB) +#define glVertexAttrib4fvARB GLEW_GET_FUN(__glewVertexAttrib4fvARB) +#define glVertexAttrib4ivARB GLEW_GET_FUN(__glewVertexAttrib4ivARB) +#define glVertexAttrib4sARB GLEW_GET_FUN(__glewVertexAttrib4sARB) +#define glVertexAttrib4svARB GLEW_GET_FUN(__glewVertexAttrib4svARB) +#define glVertexAttrib4ubvARB GLEW_GET_FUN(__glewVertexAttrib4ubvARB) +#define glVertexAttrib4uivARB GLEW_GET_FUN(__glewVertexAttrib4uivARB) +#define glVertexAttrib4usvARB GLEW_GET_FUN(__glewVertexAttrib4usvARB) +#define glVertexAttribPointerARB GLEW_GET_FUN(__glewVertexAttribPointerARB) + +#define GLEW_ARB_vertex_program GLEW_GET_VAR(__GLEW_ARB_vertex_program) + +#endif /* GL_ARB_vertex_program */ + +/* -------------------------- GL_ARB_vertex_shader ------------------------- */ + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 + +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A + +typedef void (GLAPIENTRY * PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB* name); +typedef void (GLAPIENTRY * PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei* length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (GLAPIENTRY * PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB* name); + +#define glBindAttribLocationARB GLEW_GET_FUN(__glewBindAttribLocationARB) +#define glGetActiveAttribARB GLEW_GET_FUN(__glewGetActiveAttribARB) +#define glGetAttribLocationARB GLEW_GET_FUN(__glewGetAttribLocationARB) + +#define GLEW_ARB_vertex_shader GLEW_GET_VAR(__GLEW_ARB_vertex_shader) + +#endif /* GL_ARB_vertex_shader */ + +/* --------------------------- GL_ARB_window_pos --------------------------- */ + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 + +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2DVARBPROC) (const GLdouble* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2FVARBPROC) (const GLfloat* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2IVARBPROC) (const GLint* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2SVARBPROC) (const GLshort* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3DVARBPROC) (const GLdouble* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3FVARBPROC) (const GLfloat* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3IVARBPROC) (const GLint* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3SVARBPROC) (const GLshort* p); + +#define glWindowPos2dARB GLEW_GET_FUN(__glewWindowPos2dARB) +#define glWindowPos2dvARB GLEW_GET_FUN(__glewWindowPos2dvARB) +#define glWindowPos2fARB GLEW_GET_FUN(__glewWindowPos2fARB) +#define glWindowPos2fvARB GLEW_GET_FUN(__glewWindowPos2fvARB) +#define glWindowPos2iARB GLEW_GET_FUN(__glewWindowPos2iARB) +#define glWindowPos2ivARB GLEW_GET_FUN(__glewWindowPos2ivARB) +#define glWindowPos2sARB GLEW_GET_FUN(__glewWindowPos2sARB) +#define glWindowPos2svARB GLEW_GET_FUN(__glewWindowPos2svARB) +#define glWindowPos3dARB GLEW_GET_FUN(__glewWindowPos3dARB) +#define glWindowPos3dvARB GLEW_GET_FUN(__glewWindowPos3dvARB) +#define glWindowPos3fARB GLEW_GET_FUN(__glewWindowPos3fARB) +#define glWindowPos3fvARB GLEW_GET_FUN(__glewWindowPos3fvARB) +#define glWindowPos3iARB GLEW_GET_FUN(__glewWindowPos3iARB) +#define glWindowPos3ivARB GLEW_GET_FUN(__glewWindowPos3ivARB) +#define glWindowPos3sARB GLEW_GET_FUN(__glewWindowPos3sARB) +#define glWindowPos3svARB GLEW_GET_FUN(__glewWindowPos3svARB) + +#define GLEW_ARB_window_pos GLEW_GET_VAR(__GLEW_ARB_window_pos) + +#endif /* GL_ARB_window_pos */ + +/* ------------------------- GL_ATIX_point_sprites ------------------------- */ + +#ifndef GL_ATIX_point_sprites +#define GL_ATIX_point_sprites 1 + +#define GL_TEXTURE_POINT_MODE_ATIX 0x60B0 +#define GL_TEXTURE_POINT_ONE_COORD_ATIX 0x60B1 +#define GL_TEXTURE_POINT_SPRITE_ATIX 0x60B2 +#define GL_POINT_SPRITE_CULL_MODE_ATIX 0x60B3 +#define GL_POINT_SPRITE_CULL_CENTER_ATIX 0x60B4 +#define GL_POINT_SPRITE_CULL_CLIP_ATIX 0x60B5 + +#define GLEW_ATIX_point_sprites GLEW_GET_VAR(__GLEW_ATIX_point_sprites) + +#endif /* GL_ATIX_point_sprites */ + +/* ---------------------- GL_ATIX_texture_env_combine3 --------------------- */ + +#ifndef GL_ATIX_texture_env_combine3 +#define GL_ATIX_texture_env_combine3 1 + +#define GL_MODULATE_ADD_ATIX 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATIX 0x8745 +#define GL_MODULATE_SUBTRACT_ATIX 0x8746 + +#define GLEW_ATIX_texture_env_combine3 GLEW_GET_VAR(__GLEW_ATIX_texture_env_combine3) + +#endif /* GL_ATIX_texture_env_combine3 */ + +/* ----------------------- GL_ATIX_texture_env_route ----------------------- */ + +#ifndef GL_ATIX_texture_env_route +#define GL_ATIX_texture_env_route 1 + +#define GL_SECONDARY_COLOR_ATIX 0x8747 +#define GL_TEXTURE_OUTPUT_RGB_ATIX 0x8748 +#define GL_TEXTURE_OUTPUT_ALPHA_ATIX 0x8749 + +#define GLEW_ATIX_texture_env_route GLEW_GET_VAR(__GLEW_ATIX_texture_env_route) + +#endif /* GL_ATIX_texture_env_route */ + +/* ---------------- GL_ATIX_vertex_shader_output_point_size ---------------- */ + +#ifndef GL_ATIX_vertex_shader_output_point_size +#define GL_ATIX_vertex_shader_output_point_size 1 + +#define GL_OUTPUT_POINT_SIZE_ATIX 0x610E + +#define GLEW_ATIX_vertex_shader_output_point_size GLEW_GET_VAR(__GLEW_ATIX_vertex_shader_output_point_size) + +#endif /* GL_ATIX_vertex_shader_output_point_size */ + +/* -------------------------- GL_ATI_draw_buffers -------------------------- */ + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 + +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 + +typedef void (GLAPIENTRY * PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum* bufs); + +#define glDrawBuffersATI GLEW_GET_FUN(__glewDrawBuffersATI) + +#define GLEW_ATI_draw_buffers GLEW_GET_VAR(__GLEW_ATI_draw_buffers) + +#endif /* GL_ATI_draw_buffers */ + +/* -------------------------- GL_ATI_element_array ------------------------- */ + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 + +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A + +typedef void (GLAPIENTRY * PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (GLAPIENTRY * PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +typedef void (GLAPIENTRY * PFNGLELEMENTPOINTERATIPROC) (GLenum type, const void* pointer); + +#define glDrawElementArrayATI GLEW_GET_FUN(__glewDrawElementArrayATI) +#define glDrawRangeElementArrayATI GLEW_GET_FUN(__glewDrawRangeElementArrayATI) +#define glElementPointerATI GLEW_GET_FUN(__glewElementPointerATI) + +#define GLEW_ATI_element_array GLEW_GET_VAR(__GLEW_ATI_element_array) + +#endif /* GL_ATI_element_array */ + +/* ------------------------- GL_ATI_envmap_bumpmap ------------------------- */ + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 + +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C + +typedef void (GLAPIENTRY * PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +typedef void (GLAPIENTRY * PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (GLAPIENTRY * PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +typedef void (GLAPIENTRY * PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); + +#define glGetTexBumpParameterfvATI GLEW_GET_FUN(__glewGetTexBumpParameterfvATI) +#define glGetTexBumpParameterivATI GLEW_GET_FUN(__glewGetTexBumpParameterivATI) +#define glTexBumpParameterfvATI GLEW_GET_FUN(__glewTexBumpParameterfvATI) +#define glTexBumpParameterivATI GLEW_GET_FUN(__glewTexBumpParameterivATI) + +#define GLEW_ATI_envmap_bumpmap GLEW_GET_VAR(__GLEW_ATI_envmap_bumpmap) + +#endif /* GL_ATI_envmap_bumpmap */ + +/* ------------------------- GL_ATI_fragment_shader ------------------------ */ + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 + +#define GL_RED_BIT_ATI 0x00000001 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B + +typedef void (GLAPIENTRY * PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (GLAPIENTRY * PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (GLAPIENTRY * PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (GLAPIENTRY * PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (GLAPIENTRY * PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (GLAPIENTRY * PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (GLAPIENTRY * PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (GLAPIENTRY * PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef GLuint (GLAPIENTRY * PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (GLAPIENTRY * PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (GLAPIENTRY * PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (GLAPIENTRY * PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat* value); + +#define glAlphaFragmentOp1ATI GLEW_GET_FUN(__glewAlphaFragmentOp1ATI) +#define glAlphaFragmentOp2ATI GLEW_GET_FUN(__glewAlphaFragmentOp2ATI) +#define glAlphaFragmentOp3ATI GLEW_GET_FUN(__glewAlphaFragmentOp3ATI) +#define glBeginFragmentShaderATI GLEW_GET_FUN(__glewBeginFragmentShaderATI) +#define glBindFragmentShaderATI GLEW_GET_FUN(__glewBindFragmentShaderATI) +#define glColorFragmentOp1ATI GLEW_GET_FUN(__glewColorFragmentOp1ATI) +#define glColorFragmentOp2ATI GLEW_GET_FUN(__glewColorFragmentOp2ATI) +#define glColorFragmentOp3ATI GLEW_GET_FUN(__glewColorFragmentOp3ATI) +#define glDeleteFragmentShaderATI GLEW_GET_FUN(__glewDeleteFragmentShaderATI) +#define glEndFragmentShaderATI GLEW_GET_FUN(__glewEndFragmentShaderATI) +#define glGenFragmentShadersATI GLEW_GET_FUN(__glewGenFragmentShadersATI) +#define glPassTexCoordATI GLEW_GET_FUN(__glewPassTexCoordATI) +#define glSampleMapATI GLEW_GET_FUN(__glewSampleMapATI) +#define glSetFragmentShaderConstantATI GLEW_GET_FUN(__glewSetFragmentShaderConstantATI) + +#define GLEW_ATI_fragment_shader GLEW_GET_VAR(__GLEW_ATI_fragment_shader) + +#endif /* GL_ATI_fragment_shader */ + +/* ------------------------ GL_ATI_map_object_buffer ----------------------- */ + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 + +typedef void* (GLAPIENTRY * PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (GLAPIENTRY * PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); + +#define glMapObjectBufferATI GLEW_GET_FUN(__glewMapObjectBufferATI) +#define glUnmapObjectBufferATI GLEW_GET_FUN(__glewUnmapObjectBufferATI) + +#define GLEW_ATI_map_object_buffer GLEW_GET_VAR(__GLEW_ATI_map_object_buffer) + +#endif /* GL_ATI_map_object_buffer */ + +/* -------------------------- GL_ATI_pn_triangles -------------------------- */ + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 + +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 + +typedef void (GLAPIENTRY * PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); + +#define glPNTrianglesfATI GLEW_GET_FUN(__glPNTrianglewesfATI) +#define glPNTrianglesiATI GLEW_GET_FUN(__glPNTrianglewesiATI) + +#define GLEW_ATI_pn_triangles GLEW_GET_VAR(__GLEW_ATI_pn_triangles) + +#endif /* GL_ATI_pn_triangles */ + +/* ------------------------ GL_ATI_separate_stencil ------------------------ */ + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 + +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 + +typedef void (GLAPIENTRY * PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +typedef void (GLAPIENTRY * PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); + +#define glStencilFuncSeparateATI GLEW_GET_FUN(__glewStencilFuncSeparateATI) +#define glStencilOpSeparateATI GLEW_GET_FUN(__glewStencilOpSeparateATI) + +#define GLEW_ATI_separate_stencil GLEW_GET_VAR(__GLEW_ATI_separate_stencil) + +#endif /* GL_ATI_separate_stencil */ + +/* ----------------------- GL_ATI_shader_texture_lod ----------------------- */ + +#ifndef GL_ATI_shader_texture_lod +#define GL_ATI_shader_texture_lod 1 + +#define GLEW_ATI_shader_texture_lod GLEW_GET_VAR(__GLEW_ATI_shader_texture_lod) + +#endif /* GL_ATI_shader_texture_lod */ + +/* ---------------------- GL_ATI_text_fragment_shader ---------------------- */ + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 + +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 + +#define GLEW_ATI_text_fragment_shader GLEW_GET_VAR(__GLEW_ATI_text_fragment_shader) + +#endif /* GL_ATI_text_fragment_shader */ + +/* --------------------- GL_ATI_texture_compression_3dc -------------------- */ + +#ifndef GL_ATI_texture_compression_3dc +#define GL_ATI_texture_compression_3dc 1 + +#define GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI 0x8837 + +#define GLEW_ATI_texture_compression_3dc GLEW_GET_VAR(__GLEW_ATI_texture_compression_3dc) + +#endif /* GL_ATI_texture_compression_3dc */ + +/* ---------------------- GL_ATI_texture_env_combine3 ---------------------- */ + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 + +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 + +#define GLEW_ATI_texture_env_combine3 GLEW_GET_VAR(__GLEW_ATI_texture_env_combine3) + +#endif /* GL_ATI_texture_env_combine3 */ + +/* -------------------------- GL_ATI_texture_float ------------------------- */ + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 + +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F + +#define GLEW_ATI_texture_float GLEW_GET_VAR(__GLEW_ATI_texture_float) + +#endif /* GL_ATI_texture_float */ + +/* ----------------------- GL_ATI_texture_mirror_once ---------------------- */ + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 + +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 + +#define GLEW_ATI_texture_mirror_once GLEW_GET_VAR(__GLEW_ATI_texture_mirror_once) + +#endif /* GL_ATI_texture_mirror_once */ + +/* ----------------------- GL_ATI_vertex_array_object ---------------------- */ + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 + +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 + +typedef void (GLAPIENTRY * PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (GLAPIENTRY * PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (GLAPIENTRY * PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef GLuint (GLAPIENTRY * PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const void* pointer, GLenum usage); +typedef void (GLAPIENTRY * PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const void* pointer, GLenum preserve); +typedef void (GLAPIENTRY * PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); + +#define glArrayObjectATI GLEW_GET_FUN(__glewArrayObjectATI) +#define glFreeObjectBufferATI GLEW_GET_FUN(__glewFreeObjectBufferATI) +#define glGetArrayObjectfvATI GLEW_GET_FUN(__glewGetArrayObjectfvATI) +#define glGetArrayObjectivATI GLEW_GET_FUN(__glewGetArrayObjectivATI) +#define glGetObjectBufferfvATI GLEW_GET_FUN(__glewGetObjectBufferfvATI) +#define glGetObjectBufferivATI GLEW_GET_FUN(__glewGetObjectBufferivATI) +#define glGetVariantArrayObjectfvATI GLEW_GET_FUN(__glewGetVariantArrayObjectfvATI) +#define glGetVariantArrayObjectivATI GLEW_GET_FUN(__glewGetVariantArrayObjectivATI) +#define glIsObjectBufferATI GLEW_GET_FUN(__glewIsObjectBufferATI) +#define glNewObjectBufferATI GLEW_GET_FUN(__glewNewObjectBufferATI) +#define glUpdateObjectBufferATI GLEW_GET_FUN(__glewUpdateObjectBufferATI) +#define glVariantArrayObjectATI GLEW_GET_FUN(__glewVariantArrayObjectATI) + +#define GLEW_ATI_vertex_array_object GLEW_GET_VAR(__GLEW_ATI_vertex_array_object) + +#endif /* GL_ATI_vertex_array_object */ + +/* ------------------- GL_ATI_vertex_attrib_array_object ------------------- */ + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 + +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); + +#define glGetVertexAttribArrayObjectfvATI GLEW_GET_FUN(__glewGetVertexAttribArrayObjectfvATI) +#define glGetVertexAttribArrayObjectivATI GLEW_GET_FUN(__glewGetVertexAttribArrayObjectivATI) +#define glVertexAttribArrayObjectATI GLEW_GET_FUN(__glewVertexAttribArrayObjectATI) + +#define GLEW_ATI_vertex_attrib_array_object GLEW_GET_VAR(__GLEW_ATI_vertex_attrib_array_object) + +#endif /* GL_ATI_vertex_attrib_array_object */ + +/* ------------------------- GL_ATI_vertex_streams ------------------------- */ + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 + +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_SOURCE_ATI 0x876C +#define GL_VERTEX_STREAM0_ATI 0x876D +#define GL_VERTEX_STREAM1_ATI 0x876E +#define GL_VERTEX_STREAM2_ATI 0x876F +#define GL_VERTEX_STREAM3_ATI 0x8770 +#define GL_VERTEX_STREAM4_ATI 0x8771 +#define GL_VERTEX_STREAM5_ATI 0x8772 +#define GL_VERTEX_STREAM6_ATI 0x8773 +#define GL_VERTEX_STREAM7_ATI 0x8774 + +typedef void (GLAPIENTRY * PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte x, GLbyte y, GLbyte z); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *v); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *v); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (GLAPIENTRY * PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *v); + +#define glClientActiveVertexStreamATI GLEW_GET_FUN(__glewClientActiveVertexStreamATI) +#define glNormalStream3bATI GLEW_GET_FUN(__glewNormalStream3bATI) +#define glNormalStream3bvATI GLEW_GET_FUN(__glewNormalStream3bvATI) +#define glNormalStream3dATI GLEW_GET_FUN(__glewNormalStream3dATI) +#define glNormalStream3dvATI GLEW_GET_FUN(__glewNormalStream3dvATI) +#define glNormalStream3fATI GLEW_GET_FUN(__glewNormalStream3fATI) +#define glNormalStream3fvATI GLEW_GET_FUN(__glewNormalStream3fvATI) +#define glNormalStream3iATI GLEW_GET_FUN(__glewNormalStream3iATI) +#define glNormalStream3ivATI GLEW_GET_FUN(__glewNormalStream3ivATI) +#define glNormalStream3sATI GLEW_GET_FUN(__glewNormalStream3sATI) +#define glNormalStream3svATI GLEW_GET_FUN(__glewNormalStream3svATI) +#define glVertexBlendEnvfATI GLEW_GET_FUN(__glewVertexBlendEnvfATI) +#define glVertexBlendEnviATI GLEW_GET_FUN(__glewVertexBlendEnviATI) +#define glVertexStream2dATI GLEW_GET_FUN(__glewVertexStream2dATI) +#define glVertexStream2dvATI GLEW_GET_FUN(__glewVertexStream2dvATI) +#define glVertexStream2fATI GLEW_GET_FUN(__glewVertexStream2fATI) +#define glVertexStream2fvATI GLEW_GET_FUN(__glewVertexStream2fvATI) +#define glVertexStream2iATI GLEW_GET_FUN(__glewVertexStream2iATI) +#define glVertexStream2ivATI GLEW_GET_FUN(__glewVertexStream2ivATI) +#define glVertexStream2sATI GLEW_GET_FUN(__glewVertexStream2sATI) +#define glVertexStream2svATI GLEW_GET_FUN(__glewVertexStream2svATI) +#define glVertexStream3dATI GLEW_GET_FUN(__glewVertexStream3dATI) +#define glVertexStream3dvATI GLEW_GET_FUN(__glewVertexStream3dvATI) +#define glVertexStream3fATI GLEW_GET_FUN(__glewVertexStream3fATI) +#define glVertexStream3fvATI GLEW_GET_FUN(__glewVertexStream3fvATI) +#define glVertexStream3iATI GLEW_GET_FUN(__glewVertexStream3iATI) +#define glVertexStream3ivATI GLEW_GET_FUN(__glewVertexStream3ivATI) +#define glVertexStream3sATI GLEW_GET_FUN(__glewVertexStream3sATI) +#define glVertexStream3svATI GLEW_GET_FUN(__glewVertexStream3svATI) +#define glVertexStream4dATI GLEW_GET_FUN(__glewVertexStream4dATI) +#define glVertexStream4dvATI GLEW_GET_FUN(__glewVertexStream4dvATI) +#define glVertexStream4fATI GLEW_GET_FUN(__glewVertexStream4fATI) +#define glVertexStream4fvATI GLEW_GET_FUN(__glewVertexStream4fvATI) +#define glVertexStream4iATI GLEW_GET_FUN(__glewVertexStream4iATI) +#define glVertexStream4ivATI GLEW_GET_FUN(__glewVertexStream4ivATI) +#define glVertexStream4sATI GLEW_GET_FUN(__glewVertexStream4sATI) +#define glVertexStream4svATI GLEW_GET_FUN(__glewVertexStream4svATI) + +#define GLEW_ATI_vertex_streams GLEW_GET_VAR(__GLEW_ATI_vertex_streams) + +#endif /* GL_ATI_vertex_streams */ + +/* --------------------------- GL_EXT_422_pixels --------------------------- */ + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 + +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF + +#define GLEW_EXT_422_pixels GLEW_GET_VAR(__GLEW_EXT_422_pixels) + +#endif /* GL_EXT_422_pixels */ + +/* ---------------------------- GL_EXT_Cg_shader --------------------------- */ + +#ifndef GL_EXT_Cg_shader +#define GL_EXT_Cg_shader 1 + +#define GL_CG_VERTEX_SHADER_EXT 0x890E +#define GL_CG_FRAGMENT_SHADER_EXT 0x890F + +#define GLEW_EXT_Cg_shader GLEW_GET_VAR(__GLEW_EXT_Cg_shader) + +#endif /* GL_EXT_Cg_shader */ + +/* ------------------------------ GL_EXT_abgr ------------------------------ */ + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 + +#define GL_ABGR_EXT 0x8000 + +#define GLEW_EXT_abgr GLEW_GET_VAR(__GLEW_EXT_abgr) + +#endif /* GL_EXT_abgr */ + +/* ------------------------------ GL_EXT_bgra ------------------------------ */ + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 + +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 + +#define GLEW_EXT_bgra GLEW_GET_VAR(__GLEW_EXT_bgra) + +#endif /* GL_EXT_bgra */ + +/* ------------------------ GL_EXT_bindable_uniform ------------------------ */ + +#ifndef GL_EXT_bindable_uniform +#define GL_EXT_bindable_uniform 1 + +#define GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT 0x8DE2 +#define GL_MAX_FRAGMENT_BINDABLE_UNIFORMS_EXT 0x8DE3 +#define GL_MAX_GEOMETRY_BINDABLE_UNIFORMS_EXT 0x8DE4 +#define GL_MAX_BINDABLE_UNIFORM_SIZE_EXT 0x8DED +#define GL_UNIFORM_BUFFER_EXT 0x8DEE +#define GL_UNIFORM_BUFFER_BINDING_EXT 0x8DEF + +typedef GLint (GLAPIENTRY * PFNGLGETUNIFORMBUFFERSIZEEXTPROC) (GLuint program, GLint location); +typedef GLintptr (GLAPIENTRY * PFNGLGETUNIFORMOFFSETEXTPROC) (GLuint program, GLint location); +typedef void (GLAPIENTRY * PFNGLUNIFORMBUFFEREXTPROC) (GLuint program, GLint location, GLuint buffer); + +#define glGetUniformBufferSizeEXT GLEW_GET_FUN(__glewGetUniformBufferSizeEXT) +#define glGetUniformOffsetEXT GLEW_GET_FUN(__glewGetUniformOffsetEXT) +#define glUniformBufferEXT GLEW_GET_FUN(__glewUniformBufferEXT) + +#define GLEW_EXT_bindable_uniform GLEW_GET_VAR(__GLEW_EXT_bindable_uniform) + +#endif /* GL_EXT_bindable_uniform */ + +/* --------------------------- GL_EXT_blend_color -------------------------- */ + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 + +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 + +typedef void (GLAPIENTRY * PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + +#define glBlendColorEXT GLEW_GET_FUN(__glewBlendColorEXT) + +#define GLEW_EXT_blend_color GLEW_GET_VAR(__GLEW_EXT_blend_color) + +#endif /* GL_EXT_blend_color */ + +/* --------------------- GL_EXT_blend_equation_separate -------------------- */ + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 + +#define GL_BLEND_EQUATION_RGB_EXT 0x8009 +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D + +typedef void (GLAPIENTRY * PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); + +#define glBlendEquationSeparateEXT GLEW_GET_FUN(__glewBlendEquationSeparateEXT) + +#define GLEW_EXT_blend_equation_separate GLEW_GET_VAR(__GLEW_EXT_blend_equation_separate) + +#endif /* GL_EXT_blend_equation_separate */ + +/* ----------------------- GL_EXT_blend_func_separate ---------------------- */ + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 + +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB + +typedef void (GLAPIENTRY * PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); + +#define glBlendFuncSeparateEXT GLEW_GET_FUN(__glewBlendFuncSeparateEXT) + +#define GLEW_EXT_blend_func_separate GLEW_GET_VAR(__GLEW_EXT_blend_func_separate) + +#endif /* GL_EXT_blend_func_separate */ + +/* ------------------------- GL_EXT_blend_logic_op ------------------------- */ + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 + +#define GLEW_EXT_blend_logic_op GLEW_GET_VAR(__GLEW_EXT_blend_logic_op) + +#endif /* GL_EXT_blend_logic_op */ + +/* -------------------------- GL_EXT_blend_minmax -------------------------- */ + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 + +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 + +typedef void (GLAPIENTRY * PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); + +#define glBlendEquationEXT GLEW_GET_FUN(__glewBlendEquationEXT) + +#define GLEW_EXT_blend_minmax GLEW_GET_VAR(__GLEW_EXT_blend_minmax) + +#endif /* GL_EXT_blend_minmax */ + +/* ------------------------- GL_EXT_blend_subtract ------------------------- */ + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 + +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B + +#define GLEW_EXT_blend_subtract GLEW_GET_VAR(__GLEW_EXT_blend_subtract) + +#endif /* GL_EXT_blend_subtract */ + +/* ------------------------ GL_EXT_clip_volume_hint ------------------------ */ + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 + +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 + +#define GLEW_EXT_clip_volume_hint GLEW_GET_VAR(__GLEW_EXT_clip_volume_hint) + +#endif /* GL_EXT_clip_volume_hint */ + +/* ------------------------------ GL_EXT_cmyka ----------------------------- */ + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 + +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F + +#define GLEW_EXT_cmyka GLEW_GET_VAR(__GLEW_EXT_cmyka) + +#endif /* GL_EXT_cmyka */ + +/* ------------------------- GL_EXT_color_subtable ------------------------- */ + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 + +typedef void (GLAPIENTRY * PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void* data); +typedef void (GLAPIENTRY * PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); + +#define glColorSubTableEXT GLEW_GET_FUN(__glewColorSubTableEXT) +#define glCopyColorSubTableEXT GLEW_GET_FUN(__glewCopyColorSubTableEXT) + +#define GLEW_EXT_color_subtable GLEW_GET_VAR(__GLEW_EXT_color_subtable) + +#endif /* GL_EXT_color_subtable */ + +/* ---------------------- GL_EXT_compiled_vertex_array --------------------- */ + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 + +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 + +typedef void (GLAPIENTRY * PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (GLAPIENTRY * PFNGLUNLOCKARRAYSEXTPROC) (void); + +#define glLockArraysEXT GLEW_GET_FUN(__glewLockArraysEXT) +#define glUnlockArraysEXT GLEW_GET_FUN(__glewUnlockArraysEXT) + +#define GLEW_EXT_compiled_vertex_array GLEW_GET_VAR(__GLEW_EXT_compiled_vertex_array) + +#endif /* GL_EXT_compiled_vertex_array */ + +/* --------------------------- GL_EXT_convolution -------------------------- */ + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 + +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 + +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void* image); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* image); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void* image); +typedef void (GLAPIENTRY * PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void* row, void* column, void* span); +typedef void (GLAPIENTRY * PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* row, const void* column); + +#define glConvolutionFilter1DEXT GLEW_GET_FUN(__glewConvolutionFilter1DEXT) +#define glConvolutionFilter2DEXT GLEW_GET_FUN(__glewConvolutionFilter2DEXT) +#define glConvolutionParameterfEXT GLEW_GET_FUN(__glewConvolutionParameterfEXT) +#define glConvolutionParameterfvEXT GLEW_GET_FUN(__glewConvolutionParameterfvEXT) +#define glConvolutionParameteriEXT GLEW_GET_FUN(__glewConvolutionParameteriEXT) +#define glConvolutionParameterivEXT GLEW_GET_FUN(__glewConvolutionParameterivEXT) +#define glCopyConvolutionFilter1DEXT GLEW_GET_FUN(__glewCopyConvolutionFilter1DEXT) +#define glCopyConvolutionFilter2DEXT GLEW_GET_FUN(__glewCopyConvolutionFilter2DEXT) +#define glGetConvolutionFilterEXT GLEW_GET_FUN(__glewGetConvolutionFilterEXT) +#define glGetConvolutionParameterfvEXT GLEW_GET_FUN(__glewGetConvolutionParameterfvEXT) +#define glGetConvolutionParameterivEXT GLEW_GET_FUN(__glewGetConvolutionParameterivEXT) +#define glGetSeparableFilterEXT GLEW_GET_FUN(__glewGetSeparableFilterEXT) +#define glSeparableFilter2DEXT GLEW_GET_FUN(__glewSeparableFilter2DEXT) + +#define GLEW_EXT_convolution GLEW_GET_VAR(__GLEW_EXT_convolution) + +#endif /* GL_EXT_convolution */ + +/* ------------------------ GL_EXT_coordinate_frame ------------------------ */ + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 + +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 + +typedef void (GLAPIENTRY * PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, void* pointer); +typedef void (GLAPIENTRY * PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, void* pointer); + +#define glBinormalPointerEXT GLEW_GET_FUN(__glewBinormalPointerEXT) +#define glTangentPointerEXT GLEW_GET_FUN(__glewTangentPointerEXT) + +#define GLEW_EXT_coordinate_frame GLEW_GET_VAR(__GLEW_EXT_coordinate_frame) + +#endif /* GL_EXT_coordinate_frame */ + +/* -------------------------- GL_EXT_copy_texture -------------------------- */ + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 + +typedef void (GLAPIENTRY * PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (GLAPIENTRY * PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (GLAPIENTRY * PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); + +#define glCopyTexImage1DEXT GLEW_GET_FUN(__glewCopyTexImage1DEXT) +#define glCopyTexImage2DEXT GLEW_GET_FUN(__glewCopyTexImage2DEXT) +#define glCopyTexSubImage1DEXT GLEW_GET_FUN(__glewCopyTexSubImage1DEXT) +#define glCopyTexSubImage2DEXT GLEW_GET_FUN(__glewCopyTexSubImage2DEXT) +#define glCopyTexSubImage3DEXT GLEW_GET_FUN(__glewCopyTexSubImage3DEXT) + +#define GLEW_EXT_copy_texture GLEW_GET_VAR(__GLEW_EXT_copy_texture) + +#endif /* GL_EXT_copy_texture */ + +/* --------------------------- GL_EXT_cull_vertex -------------------------- */ + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 + +typedef void (GLAPIENTRY * PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat* params); + +#define glCullParameterdvEXT GLEW_GET_FUN(__glewCullParameterdvEXT) +#define glCullParameterfvEXT GLEW_GET_FUN(__glewCullParameterfvEXT) + +#define GLEW_EXT_cull_vertex GLEW_GET_VAR(__GLEW_EXT_cull_vertex) + +#endif /* GL_EXT_cull_vertex */ + +/* ------------------------ GL_EXT_depth_bounds_test ----------------------- */ + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 + +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 + +typedef void (GLAPIENTRY * PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); + +#define glDepthBoundsEXT GLEW_GET_FUN(__glewDepthBoundsEXT) + +#define GLEW_EXT_depth_bounds_test GLEW_GET_VAR(__GLEW_EXT_depth_bounds_test) + +#endif /* GL_EXT_depth_bounds_test */ + +/* ----------------------- GL_EXT_direct_state_access ---------------------- */ + +#ifndef GL_EXT_direct_state_access +#define GL_EXT_direct_state_access 1 + +#define GL_PROGRAM_MATRIX_EXT 0x8E2D +#define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E +#define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F + +typedef void (GLAPIENTRY * PFNGLBINDMULTITEXTUREEXTPROC) (GLenum texunit, GLenum target, GLuint texture); +typedef GLenum (GLAPIENTRY * PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC) (GLuint framebuffer, GLenum target); +typedef void (GLAPIENTRY * PFNGLCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data); +typedef void (GLAPIENTRY * PFNGLCOPYMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (GLAPIENTRY * PFNGLCOPYMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (GLAPIENTRY * PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLCOPYTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (GLAPIENTRY * PFNGLCOPYTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (GLAPIENTRY * PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (GLAPIENTRY * PFNGLENABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC) (GLuint framebuffer, GLsizei n, const GLenum* bufs); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERREADBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (GLAPIENTRY * PFNGLGENERATEMULTITEXMIPMAPEXTPROC) (GLenum texunit, GLenum target); +typedef void (GLAPIENTRY * PFNGLGENERATETEXTUREMIPMAPEXTPROC) (GLuint texture, GLenum target); +typedef void (GLAPIENTRY * PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, void* img); +typedef void (GLAPIENTRY * PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, void* img); +typedef void (GLAPIENTRY * PFNGLGETDOUBLEINDEXEDVEXTPROC) (GLenum pname, GLuint index, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETFLOATINDEXEDVEXTPROC) (GLenum pname, GLuint index, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint* param); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void* pixels); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLuint* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC) (GLuint buffer, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDBUFFERPOINTERVEXTPROC) (GLuint buffer, GLenum pname, void** params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void* data); +typedef void (GLAPIENTRY * PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum pname, void* string); +typedef void (GLAPIENTRY * PFNGLGETNAMEDPROGRAMIVEXTPROC) (GLuint program, GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC) (GLuint renderbuffer, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETPOINTERINDEXEDVEXTPROC) (GLenum pname, GLuint index, GLvoid** params); +typedef void (GLAPIENTRY * PFNGLGETTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void* pixels); +typedef void (GLAPIENTRY * PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLuint* params); +typedef void (GLAPIENTRY * PFNGLGETTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint* params); +typedef GLvoid * (GLAPIENTRY * PFNGLMAPNAMEDBUFFEREXTPROC) (GLuint buffer, GLenum access); +typedef void (GLAPIENTRY * PFNGLMATRIXFRUSTUMEXTPROC) (GLenum matrixMode, GLdouble l, GLdouble r, GLdouble b, GLdouble t, GLdouble n, GLdouble f); +typedef void (GLAPIENTRY * PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum matrixMode); +typedef void (GLAPIENTRY * PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum matrixMode, const GLdouble* m); +typedef void (GLAPIENTRY * PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum matrixMode, const GLfloat* m); +typedef void (GLAPIENTRY * PFNGLMATRIXLOADDEXTPROC) (GLenum matrixMode, const GLdouble* m); +typedef void (GLAPIENTRY * PFNGLMATRIXLOADFEXTPROC) (GLenum matrixMode, const GLfloat* m); +typedef void (GLAPIENTRY * PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum matrixMode, const GLdouble* m); +typedef void (GLAPIENTRY * PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum matrixMode, const GLfloat* m); +typedef void (GLAPIENTRY * PFNGLMATRIXMULTDEXTPROC) (GLenum matrixMode, const GLdouble* m); +typedef void (GLAPIENTRY * PFNGLMATRIXMULTFEXTPROC) (GLenum matrixMode, const GLfloat* m); +typedef void (GLAPIENTRY * PFNGLMATRIXORTHOEXTPROC) (GLenum matrixMode, GLdouble l, GLdouble r, GLdouble b, GLdouble t, GLdouble n, GLdouble f); +typedef void (GLAPIENTRY * PFNGLMATRIXPOPEXTPROC) (GLenum matrixMode); +typedef void (GLAPIENTRY * PFNGLMATRIXPUSHEXTPROC) (GLenum matrixMode); +typedef void (GLAPIENTRY * PFNGLMATRIXROTATEDEXTPROC) (GLenum matrixMode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLMATRIXROTATEFEXTPROC) (GLenum matrixMode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLMATRIXSCALEDEXTPROC) (GLenum matrixMode, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLMATRIXSCALEFEXTPROC) (GLenum matrixMode, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum matrixMode, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum matrixMode, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLMULTITEXBUFFEREXTPROC) (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORDPOINTEREXTPROC) (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void* pointer); +typedef void (GLAPIENTRY * PFNGLMULTITEXENVFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLMULTITEXENVIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLMULTITEXGENDEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +typedef void (GLAPIENTRY * PFNGLMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLdouble* params); +typedef void (GLAPIENTRY * PFNGLMULTITEXGENFEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLMULTITEXGENIEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLuint* params); +typedef void (GLAPIENTRY * PFNGLMULTITEXPARAMETERFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat* param); +typedef void (GLAPIENTRY * PFNGLMULTITEXPARAMETERIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint* param); +typedef void (GLAPIENTRY * PFNGLMULTITEXRENDERBUFFEREXTPROC) (GLenum texunit, GLenum target, GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLsizeiptr size, const void* data, GLenum usage); +typedef void (GLAPIENTRY * PFNGLNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data); +typedef void (GLAPIENTRY * PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (GLAPIENTRY * PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (GLAPIENTRY * PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLdouble* params); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC) (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLint* params); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLuint* params); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint* params); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint* params); +typedef void (GLAPIENTRY * PFNGLNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum format, GLsizei len, const void* string); +typedef void (GLAPIENTRY * PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC) (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); +typedef void (GLAPIENTRY * PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (GLAPIENTRY * PFNGLTEXTUREBUFFEREXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (GLAPIENTRY * PFNGLTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLuint* params); +typedef void (GLAPIENTRY * PFNGLTEXTUREPARAMETERFEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLfloat* param); +typedef void (GLAPIENTRY * PFNGLTEXTUREPARAMETERIEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint* param); +typedef void (GLAPIENTRY * PFNGLTEXTURERENDERBUFFEREXTPROC) (GLuint texture, GLenum target, GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels); +typedef GLboolean (GLAPIENTRY * PFNGLUNMAPNAMEDBUFFEREXTPROC) (GLuint buffer); + +#define glBindMultiTextureEXT GLEW_GET_FUN(__glewBindMultiTextureEXT) +#define glCheckNamedFramebufferStatusEXT GLEW_GET_FUN(__glewCheckNamedFramebufferStatusEXT) +#define glClientAttribDefaultEXT GLEW_GET_FUN(__glewClientAttribDefaultEXT) +#define glCompressedMultiTexImage1DEXT GLEW_GET_FUN(__glewCompressedMultiTexImage1DEXT) +#define glCompressedMultiTexImage2DEXT GLEW_GET_FUN(__glewCompressedMultiTexImage2DEXT) +#define glCompressedMultiTexImage3DEXT GLEW_GET_FUN(__glewCompressedMultiTexImage3DEXT) +#define glCompressedMultiTexSubImage1DEXT GLEW_GET_FUN(__glewCompressedMultiTexSubImage1DEXT) +#define glCompressedMultiTexSubImage2DEXT GLEW_GET_FUN(__glewCompressedMultiTexSubImage2DEXT) +#define glCompressedMultiTexSubImage3DEXT GLEW_GET_FUN(__glewCompressedMultiTexSubImage3DEXT) +#define glCompressedTextureImage1DEXT GLEW_GET_FUN(__glewCompressedTextureImage1DEXT) +#define glCompressedTextureImage2DEXT GLEW_GET_FUN(__glewCompressedTextureImage2DEXT) +#define glCompressedTextureImage3DEXT GLEW_GET_FUN(__glewCompressedTextureImage3DEXT) +#define glCompressedTextureSubImage1DEXT GLEW_GET_FUN(__glewCompressedTextureSubImage1DEXT) +#define glCompressedTextureSubImage2DEXT GLEW_GET_FUN(__glewCompressedTextureSubImage2DEXT) +#define glCompressedTextureSubImage3DEXT GLEW_GET_FUN(__glewCompressedTextureSubImage3DEXT) +#define glCopyMultiTexImage1DEXT GLEW_GET_FUN(__glewCopyMultiTexImage1DEXT) +#define glCopyMultiTexImage2DEXT GLEW_GET_FUN(__glewCopyMultiTexImage2DEXT) +#define glCopyMultiTexSubImage1DEXT GLEW_GET_FUN(__glewCopyMultiTexSubImage1DEXT) +#define glCopyMultiTexSubImage2DEXT GLEW_GET_FUN(__glewCopyMultiTexSubImage2DEXT) +#define glCopyMultiTexSubImage3DEXT GLEW_GET_FUN(__glewCopyMultiTexSubImage3DEXT) +#define glCopyTextureImage1DEXT GLEW_GET_FUN(__glewCopyTextureImage1DEXT) +#define glCopyTextureImage2DEXT GLEW_GET_FUN(__glewCopyTextureImage2DEXT) +#define glCopyTextureSubImage1DEXT GLEW_GET_FUN(__glewCopyTextureSubImage1DEXT) +#define glCopyTextureSubImage2DEXT GLEW_GET_FUN(__glewCopyTextureSubImage2DEXT) +#define glCopyTextureSubImage3DEXT GLEW_GET_FUN(__glewCopyTextureSubImage3DEXT) +#define glDisableClientStateIndexedEXT GLEW_GET_FUN(__glewDisableClientStateIndexedEXT) +#define glEnableClientStateIndexedEXT GLEW_GET_FUN(__glewEnableClientStateIndexedEXT) +#define glFramebufferDrawBufferEXT GLEW_GET_FUN(__glewFramebufferDrawBufferEXT) +#define glFramebufferDrawBuffersEXT GLEW_GET_FUN(__glewFramebufferDrawBuffersEXT) +#define glFramebufferReadBufferEXT GLEW_GET_FUN(__glewFramebufferReadBufferEXT) +#define glGenerateMultiTexMipmapEXT GLEW_GET_FUN(__glewGenerateMultiTexMipmapEXT) +#define glGenerateTextureMipmapEXT GLEW_GET_FUN(__glewGenerateTextureMipmapEXT) +#define glGetCompressedMultiTexImageEXT GLEW_GET_FUN(__glewGetCompressedMultiTexImageEXT) +#define glGetCompressedTextureImageEXT GLEW_GET_FUN(__glewGetCompressedTextureImageEXT) +#define glGetDoubleIndexedvEXT GLEW_GET_FUN(__glewGetDoubleIndexedvEXT) +#define glGetFloatIndexedvEXT GLEW_GET_FUN(__glewGetFloatIndexedvEXT) +#define glGetFramebufferParameterivEXT GLEW_GET_FUN(__glewGetFramebufferParameterivEXT) +#define glGetMultiTexEnvfvEXT GLEW_GET_FUN(__glewGetMultiTexEnvfvEXT) +#define glGetMultiTexEnvivEXT GLEW_GET_FUN(__glewGetMultiTexEnvivEXT) +#define glGetMultiTexGendvEXT GLEW_GET_FUN(__glewGetMultiTexGendvEXT) +#define glGetMultiTexGenfvEXT GLEW_GET_FUN(__glewGetMultiTexGenfvEXT) +#define glGetMultiTexGenivEXT GLEW_GET_FUN(__glewGetMultiTexGenivEXT) +#define glGetMultiTexImageEXT GLEW_GET_FUN(__glewGetMultiTexImageEXT) +#define glGetMultiTexLevelParameterfvEXT GLEW_GET_FUN(__glewGetMultiTexLevelParameterfvEXT) +#define glGetMultiTexLevelParameterivEXT GLEW_GET_FUN(__glewGetMultiTexLevelParameterivEXT) +#define glGetMultiTexParameterIivEXT GLEW_GET_FUN(__glewGetMultiTexParameterIivEXT) +#define glGetMultiTexParameterIuivEXT GLEW_GET_FUN(__glewGetMultiTexParameterIuivEXT) +#define glGetMultiTexParameterfvEXT GLEW_GET_FUN(__glewGetMultiTexParameterfvEXT) +#define glGetMultiTexParameterivEXT GLEW_GET_FUN(__glewGetMultiTexParameterivEXT) +#define glGetNamedBufferParameterivEXT GLEW_GET_FUN(__glewGetNamedBufferParameterivEXT) +#define glGetNamedBufferPointervEXT GLEW_GET_FUN(__glewGetNamedBufferPointervEXT) +#define glGetNamedBufferSubDataEXT GLEW_GET_FUN(__glewGetNamedBufferSubDataEXT) +#define glGetNamedFramebufferAttachmentParameterivEXT GLEW_GET_FUN(__glewGetNamedFramebufferAttachmentParameterivEXT) +#define glGetNamedProgramLocalParameterIivEXT GLEW_GET_FUN(__glewGetNamedProgramLocalParameterIivEXT) +#define glGetNamedProgramLocalParameterIuivEXT GLEW_GET_FUN(__glewGetNamedProgramLocalParameterIuivEXT) +#define glGetNamedProgramLocalParameterdvEXT GLEW_GET_FUN(__glewGetNamedProgramLocalParameterdvEXT) +#define glGetNamedProgramLocalParameterfvEXT GLEW_GET_FUN(__glewGetNamedProgramLocalParameterfvEXT) +#define glGetNamedProgramStringEXT GLEW_GET_FUN(__glewGetNamedProgramStringEXT) +#define glGetNamedProgramivEXT GLEW_GET_FUN(__glewGetNamedProgramivEXT) +#define glGetNamedRenderbufferParameterivEXT GLEW_GET_FUN(__glewGetNamedRenderbufferParameterivEXT) +#define glGetPointerIndexedvEXT GLEW_GET_FUN(__glewGetPointerIndexedvEXT) +#define glGetTextureImageEXT GLEW_GET_FUN(__glewGetTextureImageEXT) +#define glGetTextureLevelParameterfvEXT GLEW_GET_FUN(__glewGetTextureLevelParameterfvEXT) +#define glGetTextureLevelParameterivEXT GLEW_GET_FUN(__glewGetTextureLevelParameterivEXT) +#define glGetTextureParameterIivEXT GLEW_GET_FUN(__glewGetTextureParameterIivEXT) +#define glGetTextureParameterIuivEXT GLEW_GET_FUN(__glewGetTextureParameterIuivEXT) +#define glGetTextureParameterfvEXT GLEW_GET_FUN(__glewGetTextureParameterfvEXT) +#define glGetTextureParameterivEXT GLEW_GET_FUN(__glewGetTextureParameterivEXT) +#define glMapNamedBufferEXT GLEW_GET_FUN(__glewMapNamedBufferEXT) +#define glMatrixFrustumEXT GLEW_GET_FUN(__glewMatrixFrustumEXT) +#define glMatrixLoadIdentityEXT GLEW_GET_FUN(__glewMatrixLoadIdentityEXT) +#define glMatrixLoadTransposedEXT GLEW_GET_FUN(__glewMatrixLoadTransposedEXT) +#define glMatrixLoadTransposefEXT GLEW_GET_FUN(__glewMatrixLoadTransposefEXT) +#define glMatrixLoaddEXT GLEW_GET_FUN(__glewMatrixLoaddEXT) +#define glMatrixLoadfEXT GLEW_GET_FUN(__glewMatrixLoadfEXT) +#define glMatrixMultTransposedEXT GLEW_GET_FUN(__glewMatrixMultTransposedEXT) +#define glMatrixMultTransposefEXT GLEW_GET_FUN(__glewMatrixMultTransposefEXT) +#define glMatrixMultdEXT GLEW_GET_FUN(__glewMatrixMultdEXT) +#define glMatrixMultfEXT GLEW_GET_FUN(__glewMatrixMultfEXT) +#define glMatrixOrthoEXT GLEW_GET_FUN(__glewMatrixOrthoEXT) +#define glMatrixPopEXT GLEW_GET_FUN(__glewMatrixPopEXT) +#define glMatrixPushEXT GLEW_GET_FUN(__glewMatrixPushEXT) +#define glMatrixRotatedEXT GLEW_GET_FUN(__glewMatrixRotatedEXT) +#define glMatrixRotatefEXT GLEW_GET_FUN(__glewMatrixRotatefEXT) +#define glMatrixScaledEXT GLEW_GET_FUN(__glewMatrixScaledEXT) +#define glMatrixScalefEXT GLEW_GET_FUN(__glewMatrixScalefEXT) +#define glMatrixTranslatedEXT GLEW_GET_FUN(__glewMatrixTranslatedEXT) +#define glMatrixTranslatefEXT GLEW_GET_FUN(__glewMatrixTranslatefEXT) +#define glMultiTexBufferEXT GLEW_GET_FUN(__glewMultiTexBufferEXT) +#define glMultiTexCoordPointerEXT GLEW_GET_FUN(__glewMultiTexCoordPointerEXT) +#define glMultiTexEnvfEXT GLEW_GET_FUN(__glewMultiTexEnvfEXT) +#define glMultiTexEnvfvEXT GLEW_GET_FUN(__glewMultiTexEnvfvEXT) +#define glMultiTexEnviEXT GLEW_GET_FUN(__glewMultiTexEnviEXT) +#define glMultiTexEnvivEXT GLEW_GET_FUN(__glewMultiTexEnvivEXT) +#define glMultiTexGendEXT GLEW_GET_FUN(__glewMultiTexGendEXT) +#define glMultiTexGendvEXT GLEW_GET_FUN(__glewMultiTexGendvEXT) +#define glMultiTexGenfEXT GLEW_GET_FUN(__glewMultiTexGenfEXT) +#define glMultiTexGenfvEXT GLEW_GET_FUN(__glewMultiTexGenfvEXT) +#define glMultiTexGeniEXT GLEW_GET_FUN(__glewMultiTexGeniEXT) +#define glMultiTexGenivEXT GLEW_GET_FUN(__glewMultiTexGenivEXT) +#define glMultiTexImage1DEXT GLEW_GET_FUN(__glewMultiTexImage1DEXT) +#define glMultiTexImage2DEXT GLEW_GET_FUN(__glewMultiTexImage2DEXT) +#define glMultiTexImage3DEXT GLEW_GET_FUN(__glewMultiTexImage3DEXT) +#define glMultiTexParameterIivEXT GLEW_GET_FUN(__glewMultiTexParameterIivEXT) +#define glMultiTexParameterIuivEXT GLEW_GET_FUN(__glewMultiTexParameterIuivEXT) +#define glMultiTexParameterfEXT GLEW_GET_FUN(__glewMultiTexParameterfEXT) +#define glMultiTexParameterfvEXT GLEW_GET_FUN(__glewMultiTexParameterfvEXT) +#define glMultiTexParameteriEXT GLEW_GET_FUN(__glewMultiTexParameteriEXT) +#define glMultiTexParameterivEXT GLEW_GET_FUN(__glewMultiTexParameterivEXT) +#define glMultiTexRenderbufferEXT GLEW_GET_FUN(__glewMultiTexRenderbufferEXT) +#define glMultiTexSubImage1DEXT GLEW_GET_FUN(__glewMultiTexSubImage1DEXT) +#define glMultiTexSubImage2DEXT GLEW_GET_FUN(__glewMultiTexSubImage2DEXT) +#define glMultiTexSubImage3DEXT GLEW_GET_FUN(__glewMultiTexSubImage3DEXT) +#define glNamedBufferDataEXT GLEW_GET_FUN(__glewNamedBufferDataEXT) +#define glNamedBufferSubDataEXT GLEW_GET_FUN(__glewNamedBufferSubDataEXT) +#define glNamedFramebufferRenderbufferEXT GLEW_GET_FUN(__glewNamedFramebufferRenderbufferEXT) +#define glNamedFramebufferTexture1DEXT GLEW_GET_FUN(__glewNamedFramebufferTexture1DEXT) +#define glNamedFramebufferTexture2DEXT GLEW_GET_FUN(__glewNamedFramebufferTexture2DEXT) +#define glNamedFramebufferTexture3DEXT GLEW_GET_FUN(__glewNamedFramebufferTexture3DEXT) +#define glNamedFramebufferTextureEXT GLEW_GET_FUN(__glewNamedFramebufferTextureEXT) +#define glNamedFramebufferTextureFaceEXT GLEW_GET_FUN(__glewNamedFramebufferTextureFaceEXT) +#define glNamedFramebufferTextureLayerEXT GLEW_GET_FUN(__glewNamedFramebufferTextureLayerEXT) +#define glNamedProgramLocalParameter4dEXT GLEW_GET_FUN(__glewNamedProgramLocalParameter4dEXT) +#define glNamedProgramLocalParameter4dvEXT GLEW_GET_FUN(__glewNamedProgramLocalParameter4dvEXT) +#define glNamedProgramLocalParameter4fEXT GLEW_GET_FUN(__glewNamedProgramLocalParameter4fEXT) +#define glNamedProgramLocalParameter4fvEXT GLEW_GET_FUN(__glewNamedProgramLocalParameter4fvEXT) +#define glNamedProgramLocalParameterI4iEXT GLEW_GET_FUN(__glewNamedProgramLocalParameterI4iEXT) +#define glNamedProgramLocalParameterI4ivEXT GLEW_GET_FUN(__glewNamedProgramLocalParameterI4ivEXT) +#define glNamedProgramLocalParameterI4uiEXT GLEW_GET_FUN(__glewNamedProgramLocalParameterI4uiEXT) +#define glNamedProgramLocalParameterI4uivEXT GLEW_GET_FUN(__glewNamedProgramLocalParameterI4uivEXT) +#define glNamedProgramLocalParameters4fvEXT GLEW_GET_FUN(__glewNamedProgramLocalParameters4fvEXT) +#define glNamedProgramLocalParametersI4ivEXT GLEW_GET_FUN(__glewNamedProgramLocalParametersI4ivEXT) +#define glNamedProgramLocalParametersI4uivEXT GLEW_GET_FUN(__glewNamedProgramLocalParametersI4uivEXT) +#define glNamedProgramStringEXT GLEW_GET_FUN(__glewNamedProgramStringEXT) +#define glNamedRenderbufferStorageEXT GLEW_GET_FUN(__glewNamedRenderbufferStorageEXT) +#define glNamedRenderbufferStorageMultisampleCoverageEXT GLEW_GET_FUN(__glewNamedRenderbufferStorageMultisampleCoverageEXT) +#define glNamedRenderbufferStorageMultisampleEXT GLEW_GET_FUN(__glewNamedRenderbufferStorageMultisampleEXT) +#define glProgramUniform1fEXT GLEW_GET_FUN(__glewProgramUniform1fEXT) +#define glProgramUniform1fvEXT GLEW_GET_FUN(__glewProgramUniform1fvEXT) +#define glProgramUniform1iEXT GLEW_GET_FUN(__glewProgramUniform1iEXT) +#define glProgramUniform1ivEXT GLEW_GET_FUN(__glewProgramUniform1ivEXT) +#define glProgramUniform1uiEXT GLEW_GET_FUN(__glewProgramUniform1uiEXT) +#define glProgramUniform1uivEXT GLEW_GET_FUN(__glewProgramUniform1uivEXT) +#define glProgramUniform2fEXT GLEW_GET_FUN(__glewProgramUniform2fEXT) +#define glProgramUniform2fvEXT GLEW_GET_FUN(__glewProgramUniform2fvEXT) +#define glProgramUniform2iEXT GLEW_GET_FUN(__glewProgramUniform2iEXT) +#define glProgramUniform2ivEXT GLEW_GET_FUN(__glewProgramUniform2ivEXT) +#define glProgramUniform2uiEXT GLEW_GET_FUN(__glewProgramUniform2uiEXT) +#define glProgramUniform2uivEXT GLEW_GET_FUN(__glewProgramUniform2uivEXT) +#define glProgramUniform3fEXT GLEW_GET_FUN(__glewProgramUniform3fEXT) +#define glProgramUniform3fvEXT GLEW_GET_FUN(__glewProgramUniform3fvEXT) +#define glProgramUniform3iEXT GLEW_GET_FUN(__glewProgramUniform3iEXT) +#define glProgramUniform3ivEXT GLEW_GET_FUN(__glewProgramUniform3ivEXT) +#define glProgramUniform3uiEXT GLEW_GET_FUN(__glewProgramUniform3uiEXT) +#define glProgramUniform3uivEXT GLEW_GET_FUN(__glewProgramUniform3uivEXT) +#define glProgramUniform4fEXT GLEW_GET_FUN(__glewProgramUniform4fEXT) +#define glProgramUniform4fvEXT GLEW_GET_FUN(__glewProgramUniform4fvEXT) +#define glProgramUniform4iEXT GLEW_GET_FUN(__glewProgramUniform4iEXT) +#define glProgramUniform4ivEXT GLEW_GET_FUN(__glewProgramUniform4ivEXT) +#define glProgramUniform4uiEXT GLEW_GET_FUN(__glewProgramUniform4uiEXT) +#define glProgramUniform4uivEXT GLEW_GET_FUN(__glewProgramUniform4uivEXT) +#define glProgramUniformMatrix2fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix2fvEXT) +#define glProgramUniformMatrix2x3fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix2x3fvEXT) +#define glProgramUniformMatrix2x4fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix2x4fvEXT) +#define glProgramUniformMatrix3fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix3fvEXT) +#define glProgramUniformMatrix3x2fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix3x2fvEXT) +#define glProgramUniformMatrix3x4fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix3x4fvEXT) +#define glProgramUniformMatrix4fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix4fvEXT) +#define glProgramUniformMatrix4x2fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix4x2fvEXT) +#define glProgramUniformMatrix4x3fvEXT GLEW_GET_FUN(__glewProgramUniformMatrix4x3fvEXT) +#define glPushClientAttribDefaultEXT GLEW_GET_FUN(__glewPushClientAttribDefaultEXT) +#define glTextureBufferEXT GLEW_GET_FUN(__glewTextureBufferEXT) +#define glTextureImage1DEXT GLEW_GET_FUN(__glewTextureImage1DEXT) +#define glTextureImage2DEXT GLEW_GET_FUN(__glewTextureImage2DEXT) +#define glTextureImage3DEXT GLEW_GET_FUN(__glewTextureImage3DEXT) +#define glTextureParameterIivEXT GLEW_GET_FUN(__glewTextureParameterIivEXT) +#define glTextureParameterIuivEXT GLEW_GET_FUN(__glewTextureParameterIuivEXT) +#define glTextureParameterfEXT GLEW_GET_FUN(__glewTextureParameterfEXT) +#define glTextureParameterfvEXT GLEW_GET_FUN(__glewTextureParameterfvEXT) +#define glTextureParameteriEXT GLEW_GET_FUN(__glewTextureParameteriEXT) +#define glTextureParameterivEXT GLEW_GET_FUN(__glewTextureParameterivEXT) +#define glTextureRenderbufferEXT GLEW_GET_FUN(__glewTextureRenderbufferEXT) +#define glTextureSubImage1DEXT GLEW_GET_FUN(__glewTextureSubImage1DEXT) +#define glTextureSubImage2DEXT GLEW_GET_FUN(__glewTextureSubImage2DEXT) +#define glTextureSubImage3DEXT GLEW_GET_FUN(__glewTextureSubImage3DEXT) +#define glUnmapNamedBufferEXT GLEW_GET_FUN(__glewUnmapNamedBufferEXT) + +#define GLEW_EXT_direct_state_access GLEW_GET_VAR(__GLEW_EXT_direct_state_access) + +#endif /* GL_EXT_direct_state_access */ + +/* -------------------------- GL_EXT_draw_buffers2 ------------------------- */ + +#ifndef GL_EXT_draw_buffers2 +#define GL_EXT_draw_buffers2 1 + +typedef void (GLAPIENTRY * PFNGLCOLORMASKINDEXEDEXTPROC) (GLuint buf, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (GLAPIENTRY * PFNGLDISABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (GLAPIENTRY * PFNGLENABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (GLAPIENTRY * PFNGLGETBOOLEANINDEXEDVEXTPROC) (GLenum value, GLuint index, GLboolean* data); +typedef void (GLAPIENTRY * PFNGLGETINTEGERINDEXEDVEXTPROC) (GLenum value, GLuint index, GLint* data); +typedef GLboolean (GLAPIENTRY * PFNGLISENABLEDINDEXEDEXTPROC) (GLenum target, GLuint index); + +#define glColorMaskIndexedEXT GLEW_GET_FUN(__glewColorMaskIndexedEXT) +#define glDisableIndexedEXT GLEW_GET_FUN(__glewDisableIndexedEXT) +#define glEnableIndexedEXT GLEW_GET_FUN(__glewEnableIndexedEXT) +#define glGetBooleanIndexedvEXT GLEW_GET_FUN(__glewGetBooleanIndexedvEXT) +#define glGetIntegerIndexedvEXT GLEW_GET_FUN(__glewGetIntegerIndexedvEXT) +#define glIsEnabledIndexedEXT GLEW_GET_FUN(__glewIsEnabledIndexedEXT) + +#define GLEW_EXT_draw_buffers2 GLEW_GET_VAR(__GLEW_EXT_draw_buffers2) + +#endif /* GL_EXT_draw_buffers2 */ + +/* ------------------------- GL_EXT_draw_instanced ------------------------- */ + +#ifndef GL_EXT_draw_instanced +#define GL_EXT_draw_instanced 1 + +typedef void (GLAPIENTRY * PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +typedef void (GLAPIENTRY * PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); + +#define glDrawArraysInstancedEXT GLEW_GET_FUN(__glewDrawArraysInstancedEXT) +#define glDrawElementsInstancedEXT GLEW_GET_FUN(__glewDrawElementsInstancedEXT) + +#define GLEW_EXT_draw_instanced GLEW_GET_VAR(__GLEW_EXT_draw_instanced) + +#endif /* GL_EXT_draw_instanced */ + +/* ----------------------- GL_EXT_draw_range_elements ---------------------- */ + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 + +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 + +typedef void (GLAPIENTRY * PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); + +#define glDrawRangeElementsEXT GLEW_GET_FUN(__glewDrawRangeElementsEXT) + +#define GLEW_EXT_draw_range_elements GLEW_GET_VAR(__GLEW_EXT_draw_range_elements) + +#endif /* GL_EXT_draw_range_elements */ + +/* ---------------------------- GL_EXT_fog_coord --------------------------- */ + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 + +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 + +typedef void (GLAPIENTRY * PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (GLAPIENTRY * PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (GLAPIENTRY * PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (GLAPIENTRY * PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); + +#define glFogCoordPointerEXT GLEW_GET_FUN(__glewFogCoordPointerEXT) +#define glFogCoorddEXT GLEW_GET_FUN(__glewFogCoorddEXT) +#define glFogCoorddvEXT GLEW_GET_FUN(__glewFogCoorddvEXT) +#define glFogCoordfEXT GLEW_GET_FUN(__glewFogCoordfEXT) +#define glFogCoordfvEXT GLEW_GET_FUN(__glewFogCoordfvEXT) + +#define GLEW_EXT_fog_coord GLEW_GET_VAR(__GLEW_EXT_fog_coord) + +#endif /* GL_EXT_fog_coord */ + +/* ------------------------ GL_EXT_fragment_lighting ----------------------- */ + +#ifndef GL_EXT_fragment_lighting +#define GL_EXT_fragment_lighting 1 + +#define GL_FRAGMENT_LIGHTING_EXT 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_EXT 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_EXT 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_EXT 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_EXT 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_EXT 0x8405 +#define GL_CURRENT_RASTER_NORMAL_EXT 0x8406 +#define GL_LIGHT_ENV_MODE_EXT 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_EXT 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_EXT 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_EXT 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_EXT 0x840B +#define GL_FRAGMENT_LIGHT0_EXT 0x840C +#define GL_FRAGMENT_LIGHT7_EXT 0x8413 + +typedef void (GLAPIENTRY * PFNGLFRAGMENTCOLORMATERIALEXTPROC) (GLenum face, GLenum mode); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELFEXTPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELFVEXTPROC) (GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELIEXTPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELIVEXTPROC) (GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTFEXTPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTFVEXTPROC) (GLenum light, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTIEXTPROC) (GLenum light, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTIVEXTPROC) (GLenum light, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALFEXTPROC) (GLenum face, GLenum pname, const GLfloat param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALFVEXTPROC) (GLenum face, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALIEXTPROC) (GLenum face, GLenum pname, const GLint param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALIVEXTPROC) (GLenum face, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTLIGHTFVEXTPROC) (GLenum light, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTLIGHTIVEXTPROC) (GLenum light, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTMATERIALFVEXTPROC) (GLenum face, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTMATERIALIVEXTPROC) (GLenum face, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLLIGHTENVIEXTPROC) (GLenum pname, GLint param); + +#define glFragmentColorMaterialEXT GLEW_GET_FUN(__glewFragmentColorMaterialEXT) +#define glFragmentLightModelfEXT GLEW_GET_FUN(__glewFragmentLightModelfEXT) +#define glFragmentLightModelfvEXT GLEW_GET_FUN(__glewFragmentLightModelfvEXT) +#define glFragmentLightModeliEXT GLEW_GET_FUN(__glewFragmentLightModeliEXT) +#define glFragmentLightModelivEXT GLEW_GET_FUN(__glewFragmentLightModelivEXT) +#define glFragmentLightfEXT GLEW_GET_FUN(__glewFragmentLightfEXT) +#define glFragmentLightfvEXT GLEW_GET_FUN(__glewFragmentLightfvEXT) +#define glFragmentLightiEXT GLEW_GET_FUN(__glewFragmentLightiEXT) +#define glFragmentLightivEXT GLEW_GET_FUN(__glewFragmentLightivEXT) +#define glFragmentMaterialfEXT GLEW_GET_FUN(__glewFragmentMaterialfEXT) +#define glFragmentMaterialfvEXT GLEW_GET_FUN(__glewFragmentMaterialfvEXT) +#define glFragmentMaterialiEXT GLEW_GET_FUN(__glewFragmentMaterialiEXT) +#define glFragmentMaterialivEXT GLEW_GET_FUN(__glewFragmentMaterialivEXT) +#define glGetFragmentLightfvEXT GLEW_GET_FUN(__glewGetFragmentLightfvEXT) +#define glGetFragmentLightivEXT GLEW_GET_FUN(__glewGetFragmentLightivEXT) +#define glGetFragmentMaterialfvEXT GLEW_GET_FUN(__glewGetFragmentMaterialfvEXT) +#define glGetFragmentMaterialivEXT GLEW_GET_FUN(__glewGetFragmentMaterialivEXT) +#define glLightEnviEXT GLEW_GET_FUN(__glewLightEnviEXT) + +#define GLEW_EXT_fragment_lighting GLEW_GET_VAR(__GLEW_EXT_fragment_lighting) + +#endif /* GL_EXT_fragment_lighting */ + +/* ------------------------ GL_EXT_framebuffer_blit ------------------------ */ + +#ifndef GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_blit 1 + +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA + +typedef void (GLAPIENTRY * PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + +#define glBlitFramebufferEXT GLEW_GET_FUN(__glewBlitFramebufferEXT) + +#define GLEW_EXT_framebuffer_blit GLEW_GET_VAR(__GLEW_EXT_framebuffer_blit) + +#endif /* GL_EXT_framebuffer_blit */ + +/* --------------------- GL_EXT_framebuffer_multisample -------------------- */ + +#ifndef GL_EXT_framebuffer_multisample +#define GL_EXT_framebuffer_multisample 1 + +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 + +typedef void (GLAPIENTRY * PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); + +#define glRenderbufferStorageMultisampleEXT GLEW_GET_FUN(__glewRenderbufferStorageMultisampleEXT) + +#define GLEW_EXT_framebuffer_multisample GLEW_GET_VAR(__GLEW_EXT_framebuffer_multisample) + +#endif /* GL_EXT_framebuffer_multisample */ + +/* ----------------------- GL_EXT_framebuffer_object ----------------------- */ + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 + +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 + +typedef void (GLAPIENTRY * PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (GLAPIENTRY * PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef GLenum (GLAPIENTRY * PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint* framebuffers); +typedef void (GLAPIENTRY * PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint* renderbuffers); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (GLAPIENTRY * PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint* framebuffers); +typedef void (GLAPIENTRY * PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint* renderbuffers); +typedef void (GLAPIENTRY * PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef GLboolean (GLAPIENTRY * PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (GLAPIENTRY * PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); + +#define glBindFramebufferEXT GLEW_GET_FUN(__glewBindFramebufferEXT) +#define glBindRenderbufferEXT GLEW_GET_FUN(__glewBindRenderbufferEXT) +#define glCheckFramebufferStatusEXT GLEW_GET_FUN(__glewCheckFramebufferStatusEXT) +#define glDeleteFramebuffersEXT GLEW_GET_FUN(__glewDeleteFramebuffersEXT) +#define glDeleteRenderbuffersEXT GLEW_GET_FUN(__glewDeleteRenderbuffersEXT) +#define glFramebufferRenderbufferEXT GLEW_GET_FUN(__glewFramebufferRenderbufferEXT) +#define glFramebufferTexture1DEXT GLEW_GET_FUN(__glewFramebufferTexture1DEXT) +#define glFramebufferTexture2DEXT GLEW_GET_FUN(__glewFramebufferTexture2DEXT) +#define glFramebufferTexture3DEXT GLEW_GET_FUN(__glewFramebufferTexture3DEXT) +#define glGenFramebuffersEXT GLEW_GET_FUN(__glewGenFramebuffersEXT) +#define glGenRenderbuffersEXT GLEW_GET_FUN(__glewGenRenderbuffersEXT) +#define glGenerateMipmapEXT GLEW_GET_FUN(__glewGenerateMipmapEXT) +#define glGetFramebufferAttachmentParameterivEXT GLEW_GET_FUN(__glewGetFramebufferAttachmentParameterivEXT) +#define glGetRenderbufferParameterivEXT GLEW_GET_FUN(__glewGetRenderbufferParameterivEXT) +#define glIsFramebufferEXT GLEW_GET_FUN(__glewIsFramebufferEXT) +#define glIsRenderbufferEXT GLEW_GET_FUN(__glewIsRenderbufferEXT) +#define glRenderbufferStorageEXT GLEW_GET_FUN(__glewRenderbufferStorageEXT) + +#define GLEW_EXT_framebuffer_object GLEW_GET_VAR(__GLEW_EXT_framebuffer_object) + +#endif /* GL_EXT_framebuffer_object */ + +/* ------------------------ GL_EXT_framebuffer_sRGB ------------------------ */ + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_EXT_framebuffer_sRGB 1 + +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#define GL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x8DBA + +#define GLEW_EXT_framebuffer_sRGB GLEW_GET_VAR(__GLEW_EXT_framebuffer_sRGB) + +#endif /* GL_EXT_framebuffer_sRGB */ + +/* ------------------------ GL_EXT_geometry_shader4 ------------------------ */ + +#ifndef GL_EXT_geometry_shader4 +#define GL_EXT_geometry_shader4 1 + +#define GL_LINES_ADJACENCY_EXT 0xA +#define GL_LINE_STRIP_ADJACENCY_EXT 0xB +#define GL_TRIANGLES_ADJACENCY_EXT 0xC +#define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0xD +#define GL_PROGRAM_POINT_SIZE_EXT 0x8642 +#define GL_MAX_VARYING_COMPONENTS_EXT 0x8B4B +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT 0x8CD4 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT 0x8DA9 +#define GL_GEOMETRY_SHADER_EXT 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_EXT 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_EXT 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_EXT 0x8DDC +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_EXT 0x8DDE +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 + +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (GLAPIENTRY * PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); + +#define glFramebufferTextureEXT GLEW_GET_FUN(__glewFramebufferTextureEXT) +#define glFramebufferTextureFaceEXT GLEW_GET_FUN(__glewFramebufferTextureFaceEXT) +#define glFramebufferTextureLayerEXT GLEW_GET_FUN(__glewFramebufferTextureLayerEXT) +#define glProgramParameteriEXT GLEW_GET_FUN(__glewProgramParameteriEXT) + +#define GLEW_EXT_geometry_shader4 GLEW_GET_VAR(__GLEW_EXT_geometry_shader4) + +#endif /* GL_EXT_geometry_shader4 */ + +/* --------------------- GL_EXT_gpu_program_parameters --------------------- */ + +#ifndef GL_EXT_gpu_program_parameters +#define GL_EXT_gpu_program_parameters 1 + +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat* params); + +#define glProgramEnvParameters4fvEXT GLEW_GET_FUN(__glewProgramEnvParameters4fvEXT) +#define glProgramLocalParameters4fvEXT GLEW_GET_FUN(__glewProgramLocalParameters4fvEXT) + +#define GLEW_EXT_gpu_program_parameters GLEW_GET_VAR(__GLEW_EXT_gpu_program_parameters) + +#endif /* GL_EXT_gpu_program_parameters */ + +/* --------------------------- GL_EXT_gpu_shader4 -------------------------- */ + +#ifndef GL_EXT_gpu_shader4 +#define GL_EXT_gpu_shader4 1 + +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_EXT 0x88FD +#define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 +#define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 +#define GL_UNSIGNED_INT_VEC2_EXT 0x8DC6 +#define GL_UNSIGNED_INT_VEC3_EXT 0x8DC7 +#define GL_UNSIGNED_INT_VEC4_EXT 0x8DC8 +#define GL_INT_SAMPLER_1D_EXT 0x8DC9 +#define GL_INT_SAMPLER_2D_EXT 0x8DCA +#define GL_INT_SAMPLER_3D_EXT 0x8DCB +#define GL_INT_SAMPLER_CUBE_EXT 0x8DCC +#define GL_INT_SAMPLER_2D_RECT_EXT 0x8DCD +#define GL_INT_SAMPLER_1D_ARRAY_EXT 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY_EXT 0x8DCF +#define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_1D_EXT 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D_EXT 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D_EXT 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE_EXT 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT_EXT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY_EXT 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY_EXT 0x8DD7 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 + +typedef void (GLAPIENTRY * PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (GLAPIENTRY * PFNGLGETFRAGDATALOCATIONEXTPROC) (GLuint program, const GLchar *name); +typedef void (GLAPIENTRY * PFNGLGETUNIFORMUIVEXTPROC) (GLuint program, GLint location, GLuint *params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIIVEXTPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIUIVEXTPROC) (GLuint index, GLenum pname, GLuint *params); +typedef void (GLAPIENTRY * PFNGLUNIFORM1UIEXTPROC) (GLint location, GLuint v0); +typedef void (GLAPIENTRY * PFNGLUNIFORM1UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (GLAPIENTRY * PFNGLUNIFORM2UIEXTPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (GLAPIENTRY * PFNGLUNIFORM2UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (GLAPIENTRY * PFNGLUNIFORM3UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (GLAPIENTRY * PFNGLUNIFORM3UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (GLAPIENTRY * PFNGLUNIFORM4UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (GLAPIENTRY * PFNGLUNIFORM4UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1IEXTPROC) (GLuint index, GLint x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1IVEXTPROC) (GLuint index, const GLint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1UIEXTPROC) (GLuint index, GLuint x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI1UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2IEXTPROC) (GLuint index, GLint x, GLint y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2IVEXTPROC) (GLuint index, const GLint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2UIEXTPROC) (GLuint index, GLuint x, GLuint y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI2UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3IEXTPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3IVEXTPROC) (GLuint index, const GLint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI3UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4BVEXTPROC) (GLuint index, const GLbyte *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4IEXTPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4IVEXTPROC) (GLuint index, const GLint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4SVEXTPROC) (GLuint index, const GLshort *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4UBVEXTPROC) (GLuint index, const GLubyte *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBI4USVEXTPROC) (GLuint index, const GLushort *v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBIPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); + +#define glBindFragDataLocationEXT GLEW_GET_FUN(__glewBindFragDataLocationEXT) +#define glGetFragDataLocationEXT GLEW_GET_FUN(__glewGetFragDataLocationEXT) +#define glGetUniformuivEXT GLEW_GET_FUN(__glewGetUniformuivEXT) +#define glGetVertexAttribIivEXT GLEW_GET_FUN(__glewGetVertexAttribIivEXT) +#define glGetVertexAttribIuivEXT GLEW_GET_FUN(__glewGetVertexAttribIuivEXT) +#define glUniform1uiEXT GLEW_GET_FUN(__glewUniform1uiEXT) +#define glUniform1uivEXT GLEW_GET_FUN(__glewUniform1uivEXT) +#define glUniform2uiEXT GLEW_GET_FUN(__glewUniform2uiEXT) +#define glUniform2uivEXT GLEW_GET_FUN(__glewUniform2uivEXT) +#define glUniform3uiEXT GLEW_GET_FUN(__glewUniform3uiEXT) +#define glUniform3uivEXT GLEW_GET_FUN(__glewUniform3uivEXT) +#define glUniform4uiEXT GLEW_GET_FUN(__glewUniform4uiEXT) +#define glUniform4uivEXT GLEW_GET_FUN(__glewUniform4uivEXT) +#define glVertexAttribI1iEXT GLEW_GET_FUN(__glewVertexAttribI1iEXT) +#define glVertexAttribI1ivEXT GLEW_GET_FUN(__glewVertexAttribI1ivEXT) +#define glVertexAttribI1uiEXT GLEW_GET_FUN(__glewVertexAttribI1uiEXT) +#define glVertexAttribI1uivEXT GLEW_GET_FUN(__glewVertexAttribI1uivEXT) +#define glVertexAttribI2iEXT GLEW_GET_FUN(__glewVertexAttribI2iEXT) +#define glVertexAttribI2ivEXT GLEW_GET_FUN(__glewVertexAttribI2ivEXT) +#define glVertexAttribI2uiEXT GLEW_GET_FUN(__glewVertexAttribI2uiEXT) +#define glVertexAttribI2uivEXT GLEW_GET_FUN(__glewVertexAttribI2uivEXT) +#define glVertexAttribI3iEXT GLEW_GET_FUN(__glewVertexAttribI3iEXT) +#define glVertexAttribI3ivEXT GLEW_GET_FUN(__glewVertexAttribI3ivEXT) +#define glVertexAttribI3uiEXT GLEW_GET_FUN(__glewVertexAttribI3uiEXT) +#define glVertexAttribI3uivEXT GLEW_GET_FUN(__glewVertexAttribI3uivEXT) +#define glVertexAttribI4bvEXT GLEW_GET_FUN(__glewVertexAttribI4bvEXT) +#define glVertexAttribI4iEXT GLEW_GET_FUN(__glewVertexAttribI4iEXT) +#define glVertexAttribI4ivEXT GLEW_GET_FUN(__glewVertexAttribI4ivEXT) +#define glVertexAttribI4svEXT GLEW_GET_FUN(__glewVertexAttribI4svEXT) +#define glVertexAttribI4ubvEXT GLEW_GET_FUN(__glewVertexAttribI4ubvEXT) +#define glVertexAttribI4uiEXT GLEW_GET_FUN(__glewVertexAttribI4uiEXT) +#define glVertexAttribI4uivEXT GLEW_GET_FUN(__glewVertexAttribI4uivEXT) +#define glVertexAttribI4usvEXT GLEW_GET_FUN(__glewVertexAttribI4usvEXT) +#define glVertexAttribIPointerEXT GLEW_GET_FUN(__glewVertexAttribIPointerEXT) + +#define GLEW_EXT_gpu_shader4 GLEW_GET_VAR(__GLEW_EXT_gpu_shader4) + +#endif /* GL_EXT_gpu_shader4 */ + +/* ---------------------------- GL_EXT_histogram --------------------------- */ + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 + +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 + +typedef void (GLAPIENTRY * PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void* values); +typedef void (GLAPIENTRY * PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void* values); +typedef void (GLAPIENTRY * PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (GLAPIENTRY * PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (GLAPIENTRY * PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLRESETMINMAXEXTPROC) (GLenum target); + +#define glGetHistogramEXT GLEW_GET_FUN(__glewGetHistogramEXT) +#define glGetHistogramParameterfvEXT GLEW_GET_FUN(__glewGetHistogramParameterfvEXT) +#define glGetHistogramParameterivEXT GLEW_GET_FUN(__glewGetHistogramParameterivEXT) +#define glGetMinmaxEXT GLEW_GET_FUN(__glewGetMinmaxEXT) +#define glGetMinmaxParameterfvEXT GLEW_GET_FUN(__glewGetMinmaxParameterfvEXT) +#define glGetMinmaxParameterivEXT GLEW_GET_FUN(__glewGetMinmaxParameterivEXT) +#define glHistogramEXT GLEW_GET_FUN(__glewHistogramEXT) +#define glMinmaxEXT GLEW_GET_FUN(__glewMinmaxEXT) +#define glResetHistogramEXT GLEW_GET_FUN(__glewResetHistogramEXT) +#define glResetMinmaxEXT GLEW_GET_FUN(__glewResetMinmaxEXT) + +#define GLEW_EXT_histogram GLEW_GET_VAR(__GLEW_EXT_histogram) + +#endif /* GL_EXT_histogram */ + +/* ----------------------- GL_EXT_index_array_formats ---------------------- */ + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 + +#define GLEW_EXT_index_array_formats GLEW_GET_VAR(__GLEW_EXT_index_array_formats) + +#endif /* GL_EXT_index_array_formats */ + +/* --------------------------- GL_EXT_index_func --------------------------- */ + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 + +typedef void (GLAPIENTRY * PFNGLINDEXFUNCEXTPROC) (GLenum func, GLfloat ref); + +#define glIndexFuncEXT GLEW_GET_FUN(__glewIndexFuncEXT) + +#define GLEW_EXT_index_func GLEW_GET_VAR(__GLEW_EXT_index_func) + +#endif /* GL_EXT_index_func */ + +/* ------------------------- GL_EXT_index_material ------------------------- */ + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 + +typedef void (GLAPIENTRY * PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); + +#define glIndexMaterialEXT GLEW_GET_FUN(__glewIndexMaterialEXT) + +#define GLEW_EXT_index_material GLEW_GET_VAR(__GLEW_EXT_index_material) + +#endif /* GL_EXT_index_material */ + +/* -------------------------- GL_EXT_index_texture ------------------------- */ + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 + +#define GLEW_EXT_index_texture GLEW_GET_VAR(__GLEW_EXT_index_texture) + +#endif /* GL_EXT_index_texture */ + +/* -------------------------- GL_EXT_light_texture ------------------------- */ + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 + +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 + +typedef void (GLAPIENTRY * PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (GLAPIENTRY * PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (GLAPIENTRY * PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); + +#define glApplyTextureEXT GLEW_GET_FUN(__glewApplyTextureEXT) +#define glTextureLightEXT GLEW_GET_FUN(__glewTextureLightEXT) +#define glTextureMaterialEXT GLEW_GET_FUN(__glewTextureMaterialEXT) + +#define GLEW_EXT_light_texture GLEW_GET_VAR(__GLEW_EXT_light_texture) + +#endif /* GL_EXT_light_texture */ + +/* ------------------------- GL_EXT_misc_attribute ------------------------- */ + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 + +#define GLEW_EXT_misc_attribute GLEW_GET_VAR(__GLEW_EXT_misc_attribute) + +#endif /* GL_EXT_misc_attribute */ + +/* ------------------------ GL_EXT_multi_draw_arrays ----------------------- */ + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 + +typedef void (GLAPIENTRY * PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, GLint* first, GLsizei *count, GLsizei primcount); +typedef void (GLAPIENTRY * PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, GLsizei* count, GLenum type, const GLvoid **indices, GLsizei primcount); + +#define glMultiDrawArraysEXT GLEW_GET_FUN(__glewMultiDrawArraysEXT) +#define glMultiDrawElementsEXT GLEW_GET_FUN(__glewMultiDrawElementsEXT) + +#define GLEW_EXT_multi_draw_arrays GLEW_GET_VAR(__GLEW_EXT_multi_draw_arrays) + +#endif /* GL_EXT_multi_draw_arrays */ + +/* --------------------------- GL_EXT_multisample -------------------------- */ + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 + +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 + +typedef void (GLAPIENTRY * PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (GLAPIENTRY * PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); + +#define glSampleMaskEXT GLEW_GET_FUN(__glewSampleMaskEXT) +#define glSamplePatternEXT GLEW_GET_FUN(__glewSamplePatternEXT) + +#define GLEW_EXT_multisample GLEW_GET_VAR(__GLEW_EXT_multisample) + +#endif /* GL_EXT_multisample */ + +/* ---------------------- GL_EXT_packed_depth_stencil ---------------------- */ + +#ifndef GL_EXT_packed_depth_stencil +#define GL_EXT_packed_depth_stencil 1 + +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 + +#define GLEW_EXT_packed_depth_stencil GLEW_GET_VAR(__GLEW_EXT_packed_depth_stencil) + +#endif /* GL_EXT_packed_depth_stencil */ + +/* -------------------------- GL_EXT_packed_float -------------------------- */ + +#ifndef GL_EXT_packed_float +#define GL_EXT_packed_float 1 + +#define GL_R11F_G11F_B10F_EXT 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV_EXT 0x8C3B +#define GL_RGBA_SIGNED_COMPONENTS_EXT 0x8C3C + +#define GLEW_EXT_packed_float GLEW_GET_VAR(__GLEW_EXT_packed_float) + +#endif /* GL_EXT_packed_float */ + +/* -------------------------- GL_EXT_packed_pixels ------------------------- */ + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 + +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 + +#define GLEW_EXT_packed_pixels GLEW_GET_VAR(__GLEW_EXT_packed_pixels) + +#endif /* GL_EXT_packed_pixels */ + +/* ------------------------ GL_EXT_paletted_texture ------------------------ */ + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 + +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_COLOR_TABLE_FORMAT_EXT 0x80D8 +#define GL_COLOR_TABLE_WIDTH_EXT 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_EXT 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_EXT 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_EXT 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_EXT 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_EXT 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_EXT 0x80DF +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B + +typedef void (GLAPIENTRY * PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, void* data); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint* params); + +#define glColorTableEXT GLEW_GET_FUN(__glewColorTableEXT) +#define glGetColorTableEXT GLEW_GET_FUN(__glewGetColorTableEXT) +#define glGetColorTableParameterfvEXT GLEW_GET_FUN(__glewGetColorTableParameterfvEXT) +#define glGetColorTableParameterivEXT GLEW_GET_FUN(__glewGetColorTableParameterivEXT) + +#define GLEW_EXT_paletted_texture GLEW_GET_VAR(__GLEW_EXT_paletted_texture) + +#endif /* GL_EXT_paletted_texture */ + +/* ----------------------- GL_EXT_pixel_buffer_object ---------------------- */ + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 + +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF + +#define GLEW_EXT_pixel_buffer_object GLEW_GET_VAR(__GLEW_EXT_pixel_buffer_object) + +#endif /* GL_EXT_pixel_buffer_object */ + +/* ------------------------- GL_EXT_pixel_transform ------------------------ */ + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 + +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 + +typedef void (GLAPIENTRY * PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, const GLfloat param); +typedef void (GLAPIENTRY * PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, const GLint param); +typedef void (GLAPIENTRY * PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint* params); + +#define glGetPixelTransformParameterfvEXT GLEW_GET_FUN(__glewGetPixelTransformParameterfvEXT) +#define glGetPixelTransformParameterivEXT GLEW_GET_FUN(__glewGetPixelTransformParameterivEXT) +#define glPixelTransformParameterfEXT GLEW_GET_FUN(__glewPixelTransformParameterfEXT) +#define glPixelTransformParameterfvEXT GLEW_GET_FUN(__glewPixelTransformParameterfvEXT) +#define glPixelTransformParameteriEXT GLEW_GET_FUN(__glewPixelTransformParameteriEXT) +#define glPixelTransformParameterivEXT GLEW_GET_FUN(__glewPixelTransformParameterivEXT) + +#define GLEW_EXT_pixel_transform GLEW_GET_VAR(__GLEW_EXT_pixel_transform) + +#endif /* GL_EXT_pixel_transform */ + +/* ------------------- GL_EXT_pixel_transform_color_table ------------------ */ + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 + +#define GLEW_EXT_pixel_transform_color_table GLEW_GET_VAR(__GLEW_EXT_pixel_transform_color_table) + +#endif /* GL_EXT_pixel_transform_color_table */ + +/* ------------------------ GL_EXT_point_parameters ------------------------ */ + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 + +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 + +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, GLfloat* params); + +#define glPointParameterfEXT GLEW_GET_FUN(__glewPointParameterfEXT) +#define glPointParameterfvEXT GLEW_GET_FUN(__glewPointParameterfvEXT) + +#define GLEW_EXT_point_parameters GLEW_GET_VAR(__GLEW_EXT_point_parameters) + +#endif /* GL_EXT_point_parameters */ + +/* ------------------------- GL_EXT_polygon_offset ------------------------- */ + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 + +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 + +typedef void (GLAPIENTRY * PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); + +#define glPolygonOffsetEXT GLEW_GET_FUN(__glewPolygonOffsetEXT) + +#define GLEW_EXT_polygon_offset GLEW_GET_VAR(__GLEW_EXT_polygon_offset) + +#endif /* GL_EXT_polygon_offset */ + +/* ------------------------- GL_EXT_rescale_normal ------------------------- */ + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 + +#define GL_RESCALE_NORMAL_EXT 0x803A + +#define GLEW_EXT_rescale_normal GLEW_GET_VAR(__GLEW_EXT_rescale_normal) + +#endif /* GL_EXT_rescale_normal */ + +/* -------------------------- GL_EXT_scene_marker -------------------------- */ + +#ifndef GL_EXT_scene_marker +#define GL_EXT_scene_marker 1 + +typedef void (GLAPIENTRY * PFNGLBEGINSCENEEXTPROC) (void); +typedef void (GLAPIENTRY * PFNGLENDSCENEEXTPROC) (void); + +#define glBeginSceneEXT GLEW_GET_FUN(__glewBeginSceneEXT) +#define glEndSceneEXT GLEW_GET_FUN(__glewEndSceneEXT) + +#define GLEW_EXT_scene_marker GLEW_GET_VAR(__GLEW_EXT_scene_marker) + +#endif /* GL_EXT_scene_marker */ + +/* ------------------------- GL_EXT_secondary_color ------------------------ */ + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 + +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E + +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLvoid *pointer); + +#define glSecondaryColor3bEXT GLEW_GET_FUN(__glewSecondaryColor3bEXT) +#define glSecondaryColor3bvEXT GLEW_GET_FUN(__glewSecondaryColor3bvEXT) +#define glSecondaryColor3dEXT GLEW_GET_FUN(__glewSecondaryColor3dEXT) +#define glSecondaryColor3dvEXT GLEW_GET_FUN(__glewSecondaryColor3dvEXT) +#define glSecondaryColor3fEXT GLEW_GET_FUN(__glewSecondaryColor3fEXT) +#define glSecondaryColor3fvEXT GLEW_GET_FUN(__glewSecondaryColor3fvEXT) +#define glSecondaryColor3iEXT GLEW_GET_FUN(__glewSecondaryColor3iEXT) +#define glSecondaryColor3ivEXT GLEW_GET_FUN(__glewSecondaryColor3ivEXT) +#define glSecondaryColor3sEXT GLEW_GET_FUN(__glewSecondaryColor3sEXT) +#define glSecondaryColor3svEXT GLEW_GET_FUN(__glewSecondaryColor3svEXT) +#define glSecondaryColor3ubEXT GLEW_GET_FUN(__glewSecondaryColor3ubEXT) +#define glSecondaryColor3ubvEXT GLEW_GET_FUN(__glewSecondaryColor3ubvEXT) +#define glSecondaryColor3uiEXT GLEW_GET_FUN(__glewSecondaryColor3uiEXT) +#define glSecondaryColor3uivEXT GLEW_GET_FUN(__glewSecondaryColor3uivEXT) +#define glSecondaryColor3usEXT GLEW_GET_FUN(__glewSecondaryColor3usEXT) +#define glSecondaryColor3usvEXT GLEW_GET_FUN(__glewSecondaryColor3usvEXT) +#define glSecondaryColorPointerEXT GLEW_GET_FUN(__glewSecondaryColorPointerEXT) + +#define GLEW_EXT_secondary_color GLEW_GET_VAR(__GLEW_EXT_secondary_color) + +#endif /* GL_EXT_secondary_color */ + +/* --------------------- GL_EXT_separate_specular_color -------------------- */ + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 + +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA + +#define GLEW_EXT_separate_specular_color GLEW_GET_VAR(__GLEW_EXT_separate_specular_color) + +#endif /* GL_EXT_separate_specular_color */ + +/* -------------------------- GL_EXT_shadow_funcs -------------------------- */ + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 + +#define GLEW_EXT_shadow_funcs GLEW_GET_VAR(__GLEW_EXT_shadow_funcs) + +#endif /* GL_EXT_shadow_funcs */ + +/* --------------------- GL_EXT_shared_texture_palette --------------------- */ + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 + +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB + +#define GLEW_EXT_shared_texture_palette GLEW_GET_VAR(__GLEW_EXT_shared_texture_palette) + +#endif /* GL_EXT_shared_texture_palette */ + +/* ------------------------ GL_EXT_stencil_clear_tag ----------------------- */ + +#ifndef GL_EXT_stencil_clear_tag +#define GL_EXT_stencil_clear_tag 1 + +#define GL_STENCIL_TAG_BITS_EXT 0x88F2 +#define GL_STENCIL_CLEAR_TAG_VALUE_EXT 0x88F3 + +#define GLEW_EXT_stencil_clear_tag GLEW_GET_VAR(__GLEW_EXT_stencil_clear_tag) + +#endif /* GL_EXT_stencil_clear_tag */ + +/* ------------------------ GL_EXT_stencil_two_side ------------------------ */ + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 + +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 + +typedef void (GLAPIENTRY * PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); + +#define glActiveStencilFaceEXT GLEW_GET_FUN(__glewActiveStencilFaceEXT) + +#define GLEW_EXT_stencil_two_side GLEW_GET_VAR(__GLEW_EXT_stencil_two_side) + +#endif /* GL_EXT_stencil_two_side */ + +/* -------------------------- GL_EXT_stencil_wrap -------------------------- */ + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 + +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 + +#define GLEW_EXT_stencil_wrap GLEW_GET_VAR(__GLEW_EXT_stencil_wrap) + +#endif /* GL_EXT_stencil_wrap */ + +/* --------------------------- GL_EXT_subtexture --------------------------- */ + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 + +typedef void (GLAPIENTRY * PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels); + +#define glTexSubImage1DEXT GLEW_GET_FUN(__glewTexSubImage1DEXT) +#define glTexSubImage2DEXT GLEW_GET_FUN(__glewTexSubImage2DEXT) +#define glTexSubImage3DEXT GLEW_GET_FUN(__glewTexSubImage3DEXT) + +#define GLEW_EXT_subtexture GLEW_GET_VAR(__GLEW_EXT_subtexture) + +#endif /* GL_EXT_subtexture */ + +/* ----------------------------- GL_EXT_texture ---------------------------- */ + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 + +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 + +#define GLEW_EXT_texture GLEW_GET_VAR(__GLEW_EXT_texture) + +#endif /* GL_EXT_texture */ + +/* ---------------------------- GL_EXT_texture3D --------------------------- */ + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 + +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 + +typedef void (GLAPIENTRY * PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels); + +#define glTexImage3DEXT GLEW_GET_FUN(__glewTexImage3DEXT) + +#define GLEW_EXT_texture3D GLEW_GET_VAR(__GLEW_EXT_texture3D) + +#endif /* GL_EXT_texture3D */ + +/* -------------------------- GL_EXT_texture_array ------------------------- */ + +#ifndef GL_EXT_texture_array +#define GL_EXT_texture_array 1 + +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D + +#define GLEW_EXT_texture_array GLEW_GET_VAR(__GLEW_EXT_texture_array) + +#endif /* GL_EXT_texture_array */ + +/* ---------------------- GL_EXT_texture_buffer_object --------------------- */ + +#ifndef GL_EXT_texture_buffer_object +#define GL_EXT_texture_buffer_object 1 + +#define GL_TEXTURE_BUFFER_EXT 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_EXT 0x8C2E + +typedef void (GLAPIENTRY * PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); + +#define glTexBufferEXT GLEW_GET_FUN(__glewTexBufferEXT) + +#define GLEW_EXT_texture_buffer_object GLEW_GET_VAR(__GLEW_EXT_texture_buffer_object) + +#endif /* GL_EXT_texture_buffer_object */ + +/* -------------------- GL_EXT_texture_compression_dxt1 -------------------- */ + +#ifndef GL_EXT_texture_compression_dxt1 +#define GL_EXT_texture_compression_dxt1 1 + +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + +#define GLEW_EXT_texture_compression_dxt1 GLEW_GET_VAR(__GLEW_EXT_texture_compression_dxt1) + +#endif /* GL_EXT_texture_compression_dxt1 */ + +/* -------------------- GL_EXT_texture_compression_latc -------------------- */ + +#ifndef GL_EXT_texture_compression_latc +#define GL_EXT_texture_compression_latc 1 + +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 + +#define GLEW_EXT_texture_compression_latc GLEW_GET_VAR(__GLEW_EXT_texture_compression_latc) + +#endif /* GL_EXT_texture_compression_latc */ + +/* -------------------- GL_EXT_texture_compression_rgtc -------------------- */ + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_EXT_texture_compression_rgtc 1 + +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE + +#define GLEW_EXT_texture_compression_rgtc GLEW_GET_VAR(__GLEW_EXT_texture_compression_rgtc) + +#endif /* GL_EXT_texture_compression_rgtc */ + +/* -------------------- GL_EXT_texture_compression_s3tc -------------------- */ + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_compression_s3tc 1 + +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +#define GLEW_EXT_texture_compression_s3tc GLEW_GET_VAR(__GLEW_EXT_texture_compression_s3tc) + +#endif /* GL_EXT_texture_compression_s3tc */ + +/* ------------------------ GL_EXT_texture_cube_map ------------------------ */ + +#ifndef GL_EXT_texture_cube_map +#define GL_EXT_texture_cube_map 1 + +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C + +#define GLEW_EXT_texture_cube_map GLEW_GET_VAR(__GLEW_EXT_texture_cube_map) + +#endif /* GL_EXT_texture_cube_map */ + +/* ----------------------- GL_EXT_texture_edge_clamp ----------------------- */ + +#ifndef GL_EXT_texture_edge_clamp +#define GL_EXT_texture_edge_clamp 1 + +#define GL_CLAMP_TO_EDGE_EXT 0x812F + +#define GLEW_EXT_texture_edge_clamp GLEW_GET_VAR(__GLEW_EXT_texture_edge_clamp) + +#endif /* GL_EXT_texture_edge_clamp */ + +/* --------------------------- GL_EXT_texture_env -------------------------- */ + +#ifndef GL_EXT_texture_env +#define GL_EXT_texture_env 1 + +#define GL_TEXTURE_ENV0_EXT 0 +#define GL_ENV_BLEND_EXT 0 +#define GL_TEXTURE_ENV_SHIFT_EXT 0 +#define GL_ENV_REPLACE_EXT 0 +#define GL_ENV_ADD_EXT 0 +#define GL_ENV_SUBTRACT_EXT 0 +#define GL_TEXTURE_ENV_MODE_ALPHA_EXT 0 +#define GL_ENV_REVERSE_SUBTRACT_EXT 0 +#define GL_ENV_REVERSE_BLEND_EXT 0 +#define GL_ENV_COPY_EXT 0 +#define GL_ENV_MODULATE_EXT 0 + +#define GLEW_EXT_texture_env GLEW_GET_VAR(__GLEW_EXT_texture_env) + +#endif /* GL_EXT_texture_env */ + +/* ------------------------- GL_EXT_texture_env_add ------------------------ */ + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 + +#define GLEW_EXT_texture_env_add GLEW_GET_VAR(__GLEW_EXT_texture_env_add) + +#endif /* GL_EXT_texture_env_add */ + +/* ----------------------- GL_EXT_texture_env_combine ---------------------- */ + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 + +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A + +#define GLEW_EXT_texture_env_combine GLEW_GET_VAR(__GLEW_EXT_texture_env_combine) + +#endif /* GL_EXT_texture_env_combine */ + +/* ------------------------ GL_EXT_texture_env_dot3 ------------------------ */ + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 + +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 + +#define GLEW_EXT_texture_env_dot3 GLEW_GET_VAR(__GLEW_EXT_texture_env_dot3) + +#endif /* GL_EXT_texture_env_dot3 */ + +/* ------------------- GL_EXT_texture_filter_anisotropic ------------------- */ + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 + +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + +#define GLEW_EXT_texture_filter_anisotropic GLEW_GET_VAR(__GLEW_EXT_texture_filter_anisotropic) + +#endif /* GL_EXT_texture_filter_anisotropic */ + +/* ------------------------- GL_EXT_texture_integer ------------------------ */ + +#ifndef GL_EXT_texture_integer +#define GL_EXT_texture_integer 1 + +#define GL_RGBA32UI_EXT 0x8D70 +#define GL_RGB32UI_EXT 0x8D71 +#define GL_ALPHA32UI_EXT 0x8D72 +#define GL_INTENSITY32UI_EXT 0x8D73 +#define GL_LUMINANCE32UI_EXT 0x8D74 +#define GL_LUMINANCE_ALPHA32UI_EXT 0x8D75 +#define GL_RGBA16UI_EXT 0x8D76 +#define GL_RGB16UI_EXT 0x8D77 +#define GL_ALPHA16UI_EXT 0x8D78 +#define GL_INTENSITY16UI_EXT 0x8D79 +#define GL_LUMINANCE16UI_EXT 0x8D7A +#define GL_LUMINANCE_ALPHA16UI_EXT 0x8D7B +#define GL_RGBA8UI_EXT 0x8D7C +#define GL_RGB8UI_EXT 0x8D7D +#define GL_ALPHA8UI_EXT 0x8D7E +#define GL_INTENSITY8UI_EXT 0x8D7F +#define GL_LUMINANCE8UI_EXT 0x8D80 +#define GL_LUMINANCE_ALPHA8UI_EXT 0x8D81 +#define GL_RGBA32I_EXT 0x8D82 +#define GL_RGB32I_EXT 0x8D83 +#define GL_ALPHA32I_EXT 0x8D84 +#define GL_INTENSITY32I_EXT 0x8D85 +#define GL_LUMINANCE32I_EXT 0x8D86 +#define GL_LUMINANCE_ALPHA32I_EXT 0x8D87 +#define GL_RGBA16I_EXT 0x8D88 +#define GL_RGB16I_EXT 0x8D89 +#define GL_ALPHA16I_EXT 0x8D8A +#define GL_INTENSITY16I_EXT 0x8D8B +#define GL_LUMINANCE16I_EXT 0x8D8C +#define GL_LUMINANCE_ALPHA16I_EXT 0x8D8D +#define GL_RGBA8I_EXT 0x8D8E +#define GL_RGB8I_EXT 0x8D8F +#define GL_ALPHA8I_EXT 0x8D90 +#define GL_INTENSITY8I_EXT 0x8D91 +#define GL_LUMINANCE8I_EXT 0x8D92 +#define GL_LUMINANCE_ALPHA8I_EXT 0x8D93 +#define GL_RED_INTEGER_EXT 0x8D94 +#define GL_GREEN_INTEGER_EXT 0x8D95 +#define GL_BLUE_INTEGER_EXT 0x8D96 +#define GL_ALPHA_INTEGER_EXT 0x8D97 +#define GL_RGB_INTEGER_EXT 0x8D98 +#define GL_RGBA_INTEGER_EXT 0x8D99 +#define GL_BGR_INTEGER_EXT 0x8D9A +#define GL_BGRA_INTEGER_EXT 0x8D9B +#define GL_LUMINANCE_INTEGER_EXT 0x8D9C +#define GL_LUMINANCE_ALPHA_INTEGER_EXT 0x8D9D +#define GL_RGBA_INTEGER_MODE_EXT 0x8D9E + +typedef void (GLAPIENTRY * PFNGLCLEARCOLORIIEXTPROC) (GLint red, GLint green, GLint blue, GLint alpha); +typedef void (GLAPIENTRY * PFNGLCLEARCOLORIUIEXTPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); +typedef void (GLAPIENTRY * PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (GLAPIENTRY * PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (GLAPIENTRY * PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); + +#define glClearColorIiEXT GLEW_GET_FUN(__glewClearColorIiEXT) +#define glClearColorIuiEXT GLEW_GET_FUN(__glewClearColorIuiEXT) +#define glGetTexParameterIivEXT GLEW_GET_FUN(__glewGetTexParameterIivEXT) +#define glGetTexParameterIuivEXT GLEW_GET_FUN(__glewGetTexParameterIuivEXT) +#define glTexParameterIivEXT GLEW_GET_FUN(__glewTexParameterIivEXT) +#define glTexParameterIuivEXT GLEW_GET_FUN(__glewTexParameterIuivEXT) + +#define GLEW_EXT_texture_integer GLEW_GET_VAR(__GLEW_EXT_texture_integer) + +#endif /* GL_EXT_texture_integer */ + +/* ------------------------ GL_EXT_texture_lod_bias ------------------------ */ + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 + +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 + +#define GLEW_EXT_texture_lod_bias GLEW_GET_VAR(__GLEW_EXT_texture_lod_bias) + +#endif /* GL_EXT_texture_lod_bias */ + +/* ---------------------- GL_EXT_texture_mirror_clamp ---------------------- */ + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 + +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 + +#define GLEW_EXT_texture_mirror_clamp GLEW_GET_VAR(__GLEW_EXT_texture_mirror_clamp) + +#endif /* GL_EXT_texture_mirror_clamp */ + +/* ------------------------- GL_EXT_texture_object ------------------------- */ + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 + +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A + +typedef GLboolean (GLAPIENTRY * PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint* textures, GLboolean* residences); +typedef void (GLAPIENTRY * PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (GLAPIENTRY * PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint* textures); +typedef void (GLAPIENTRY * PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint* textures); +typedef GLboolean (GLAPIENTRY * PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (GLAPIENTRY * PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint* textures, const GLclampf* priorities); + +#define glAreTexturesResidentEXT GLEW_GET_FUN(__glewAreTexturesResidentEXT) +#define glBindTextureEXT GLEW_GET_FUN(__glewBindTextureEXT) +#define glDeleteTexturesEXT GLEW_GET_FUN(__glewDeleteTexturesEXT) +#define glGenTexturesEXT GLEW_GET_FUN(__glewGenTexturesEXT) +#define glIsTextureEXT GLEW_GET_FUN(__glewIsTextureEXT) +#define glPrioritizeTexturesEXT GLEW_GET_FUN(__glewPrioritizeTexturesEXT) + +#define GLEW_EXT_texture_object GLEW_GET_VAR(__GLEW_EXT_texture_object) + +#endif /* GL_EXT_texture_object */ + +/* --------------------- GL_EXT_texture_perturb_normal --------------------- */ + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 + +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF + +typedef void (GLAPIENTRY * PFNGLTEXTURENORMALEXTPROC) (GLenum mode); + +#define glTextureNormalEXT GLEW_GET_FUN(__glewTextureNormalEXT) + +#define GLEW_EXT_texture_perturb_normal GLEW_GET_VAR(__GLEW_EXT_texture_perturb_normal) + +#endif /* GL_EXT_texture_perturb_normal */ + +/* ------------------------ GL_EXT_texture_rectangle ----------------------- */ + +#ifndef GL_EXT_texture_rectangle +#define GL_EXT_texture_rectangle 1 + +#define GL_TEXTURE_RECTANGLE_EXT 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_EXT 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_EXT 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT 0x84F8 + +#define GLEW_EXT_texture_rectangle GLEW_GET_VAR(__GLEW_EXT_texture_rectangle) + +#endif /* GL_EXT_texture_rectangle */ + +/* -------------------------- GL_EXT_texture_sRGB -------------------------- */ + +#ifndef GL_EXT_texture_sRGB +#define GL_EXT_texture_sRGB 1 + +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F + +#define GLEW_EXT_texture_sRGB GLEW_GET_VAR(__GLEW_EXT_texture_sRGB) + +#endif /* GL_EXT_texture_sRGB */ + +/* --------------------- GL_EXT_texture_shared_exponent -------------------- */ + +#ifndef GL_EXT_texture_shared_exponent +#define GL_EXT_texture_shared_exponent 1 + +#define GL_RGB9_E5_EXT 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV_EXT 0x8C3E +#define GL_TEXTURE_SHARED_SIZE_EXT 0x8C3F + +#define GLEW_EXT_texture_shared_exponent GLEW_GET_VAR(__GLEW_EXT_texture_shared_exponent) + +#endif /* GL_EXT_texture_shared_exponent */ + +/* ------------------------- GL_EXT_texture_swizzle ------------------------ */ + +#ifndef GL_EXT_texture_swizzle +#define GL_EXT_texture_swizzle 1 + +#define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42 +#define GL_TEXTURE_SWIZZLE_G_EXT 0x8E43 +#define GL_TEXTURE_SWIZZLE_B_EXT 0x8E44 +#define GL_TEXTURE_SWIZZLE_A_EXT 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA_EXT 0x8E46 + +#define GLEW_EXT_texture_swizzle GLEW_GET_VAR(__GLEW_EXT_texture_swizzle) + +#endif /* GL_EXT_texture_swizzle */ + +/* --------------------------- GL_EXT_timer_query -------------------------- */ + +#ifndef GL_EXT_timer_query +#define GL_EXT_timer_query 1 + +#define GL_TIME_ELAPSED_EXT 0x88BF + +typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64EXT *params); +typedef void (GLAPIENTRY * PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64EXT *params); + +#define glGetQueryObjecti64vEXT GLEW_GET_FUN(__glewGetQueryObjecti64vEXT) +#define glGetQueryObjectui64vEXT GLEW_GET_FUN(__glewGetQueryObjectui64vEXT) + +#define GLEW_EXT_timer_query GLEW_GET_VAR(__GLEW_EXT_timer_query) + +#endif /* GL_EXT_timer_query */ + +/* ----------------------- GL_EXT_transform_feedback ----------------------- */ + +#ifndef GL_EXT_transform_feedback +#define GL_EXT_transform_feedback 1 + +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH_EXT 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_EXT 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_EXT 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_EXT 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_EXT 0x8C85 +#define GL_PRIMITIVES_GENERATED_EXT 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT 0x8C88 +#define GL_RASTERIZER_DISCARD_EXT 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT 0x8C8B +#define GL_INTERLEAVED_ATTRIBS_EXT 0x8C8C +#define GL_SEPARATE_ATTRIBS_EXT 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER_EXT 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT 0x8C8F + +typedef void (GLAPIENTRY * PFNGLBEGINTRANSFORMFEEDBACKEXTPROC) (GLenum primitiveMode); +typedef void (GLAPIENTRY * PFNGLBINDBUFFERBASEEXTPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (GLAPIENTRY * PFNGLBINDBUFFEROFFSETEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (GLAPIENTRY * PFNGLBINDBUFFERRANGEEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (GLAPIENTRY * PFNGLENDTRANSFORMFEEDBACKEXTPROC) (void); +typedef void (GLAPIENTRY * PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei *size, GLenum *type, char *name); +typedef void (GLAPIENTRY * PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC) (GLuint program, GLsizei count, const char ** varyings, GLenum bufferMode); + +#define glBeginTransformFeedbackEXT GLEW_GET_FUN(__glewBeginTransformFeedbackEXT) +#define glBindBufferBaseEXT GLEW_GET_FUN(__glewBindBufferBaseEXT) +#define glBindBufferOffsetEXT GLEW_GET_FUN(__glewBindBufferOffsetEXT) +#define glBindBufferRangeEXT GLEW_GET_FUN(__glewBindBufferRangeEXT) +#define glEndTransformFeedbackEXT GLEW_GET_FUN(__glewEndTransformFeedbackEXT) +#define glGetTransformFeedbackVaryingEXT GLEW_GET_FUN(__glewGetTransformFeedbackVaryingEXT) +#define glTransformFeedbackVaryingsEXT GLEW_GET_FUN(__glewTransformFeedbackVaryingsEXT) + +#define GLEW_EXT_transform_feedback GLEW_GET_VAR(__GLEW_EXT_transform_feedback) + +#endif /* GL_EXT_transform_feedback */ + +/* -------------------------- GL_EXT_vertex_array -------------------------- */ + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 + +#define GL_DOUBLE_EXT 0x140A +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 + +typedef void (GLAPIENTRY * PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (GLAPIENTRY * PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer); +typedef void (GLAPIENTRY * PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (GLAPIENTRY * PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean* pointer); +typedef void (GLAPIENTRY * PFNGLGETPOINTERVEXTPROC) (GLenum pname, void** params); +typedef void (GLAPIENTRY * PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void* pointer); +typedef void (GLAPIENTRY * PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void* pointer); +typedef void (GLAPIENTRY * PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer); +typedef void (GLAPIENTRY * PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer); + +#define glArrayElementEXT GLEW_GET_FUN(__glewArrayElementEXT) +#define glColorPointerEXT GLEW_GET_FUN(__glewColorPointerEXT) +#define glDrawArraysEXT GLEW_GET_FUN(__glewDrawArraysEXT) +#define glEdgeFlagPointerEXT GLEW_GET_FUN(__glewEdgeFlagPointerEXT) +#define glGetPointervEXT GLEW_GET_FUN(__glewGetPointervEXT) +#define glIndexPointerEXT GLEW_GET_FUN(__glewIndexPointerEXT) +#define glNormalPointerEXT GLEW_GET_FUN(__glewNormalPointerEXT) +#define glTexCoordPointerEXT GLEW_GET_FUN(__glewTexCoordPointerEXT) +#define glVertexPointerEXT GLEW_GET_FUN(__glewVertexPointerEXT) + +#define GLEW_EXT_vertex_array GLEW_GET_VAR(__GLEW_EXT_vertex_array) + +#endif /* GL_EXT_vertex_array */ + +/* ------------------------ GL_EXT_vertex_array_bgra ----------------------- */ + +#ifndef GL_EXT_vertex_array_bgra +#define GL_EXT_vertex_array_bgra 1 + +#define GL_BGRA 0x80E1 + +#define GLEW_EXT_vertex_array_bgra GLEW_GET_VAR(__GLEW_EXT_vertex_array_bgra) + +#endif /* GL_EXT_vertex_array_bgra */ + +/* -------------------------- GL_EXT_vertex_shader ------------------------- */ + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 + +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED + +typedef void (GLAPIENTRY * PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef GLuint (GLAPIENTRY * PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (GLAPIENTRY * PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (GLAPIENTRY * PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLuint (GLAPIENTRY * PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (GLAPIENTRY * PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef void (GLAPIENTRY * PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (GLAPIENTRY * PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (GLAPIENTRY * PFNGLGENSYMBOLSEXTPROC) (GLenum dataType, GLenum storageType, GLenum range, GLuint components); +typedef GLuint (GLAPIENTRY * PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (GLAPIENTRY * PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (GLAPIENTRY * PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (GLAPIENTRY * PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (GLAPIENTRY * PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (GLAPIENTRY * PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (GLAPIENTRY * PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (GLAPIENTRY * PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (GLAPIENTRY * PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (GLAPIENTRY * PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (GLAPIENTRY * PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, GLvoid **data); +typedef void (GLAPIENTRY * PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLboolean (GLAPIENTRY * PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (GLAPIENTRY * PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, GLvoid *addr); +typedef void (GLAPIENTRY * PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, GLvoid *addr); +typedef void (GLAPIENTRY * PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (GLAPIENTRY * PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (GLAPIENTRY * PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (GLAPIENTRY * PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (GLAPIENTRY * PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, GLvoid *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTBVEXTPROC) (GLuint id, GLbyte *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTDVEXTPROC) (GLuint id, GLdouble *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTFVEXTPROC) (GLuint id, GLfloat *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTIVEXTPROC) (GLuint id, GLint *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTSVEXTPROC) (GLuint id, GLshort *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTUBVEXTPROC) (GLuint id, GLubyte *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTUIVEXTPROC) (GLuint id, GLuint *addr); +typedef void (GLAPIENTRY * PFNGLVARIANTUSVEXTPROC) (GLuint id, GLushort *addr); +typedef void (GLAPIENTRY * PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); + +#define glBeginVertexShaderEXT GLEW_GET_FUN(__glewBeginVertexShaderEXT) +#define glBindLightParameterEXT GLEW_GET_FUN(__glewBindLightParameterEXT) +#define glBindMaterialParameterEXT GLEW_GET_FUN(__glewBindMaterialParameterEXT) +#define glBindParameterEXT GLEW_GET_FUN(__glewBindParameterEXT) +#define glBindTexGenParameterEXT GLEW_GET_FUN(__glewBindTexGenParameterEXT) +#define glBindTextureUnitParameterEXT GLEW_GET_FUN(__glewBindTextureUnitParameterEXT) +#define glBindVertexShaderEXT GLEW_GET_FUN(__glewBindVertexShaderEXT) +#define glDeleteVertexShaderEXT GLEW_GET_FUN(__glewDeleteVertexShaderEXT) +#define glDisableVariantClientStateEXT GLEW_GET_FUN(__glewDisableVariantClientStateEXT) +#define glEnableVariantClientStateEXT GLEW_GET_FUN(__glewEnableVariantClientStateEXT) +#define glEndVertexShaderEXT GLEW_GET_FUN(__glewEndVertexShaderEXT) +#define glExtractComponentEXT GLEW_GET_FUN(__glewExtractComponentEXT) +#define glGenSymbolsEXT GLEW_GET_FUN(__glewGenSymbolsEXT) +#define glGenVertexShadersEXT GLEW_GET_FUN(__glewGenVertexShadersEXT) +#define glGetInvariantBooleanvEXT GLEW_GET_FUN(__glewGetInvariantBooleanvEXT) +#define glGetInvariantFloatvEXT GLEW_GET_FUN(__glewGetInvariantFloatvEXT) +#define glGetInvariantIntegervEXT GLEW_GET_FUN(__glewGetInvariantIntegervEXT) +#define glGetLocalConstantBooleanvEXT GLEW_GET_FUN(__glewGetLocalConstantBooleanvEXT) +#define glGetLocalConstantFloatvEXT GLEW_GET_FUN(__glewGetLocalConstantFloatvEXT) +#define glGetLocalConstantIntegervEXT GLEW_GET_FUN(__glewGetLocalConstantIntegervEXT) +#define glGetVariantBooleanvEXT GLEW_GET_FUN(__glewGetVariantBooleanvEXT) +#define glGetVariantFloatvEXT GLEW_GET_FUN(__glewGetVariantFloatvEXT) +#define glGetVariantIntegervEXT GLEW_GET_FUN(__glewGetVariantIntegervEXT) +#define glGetVariantPointervEXT GLEW_GET_FUN(__glewGetVariantPointervEXT) +#define glInsertComponentEXT GLEW_GET_FUN(__glewInsertComponentEXT) +#define glIsVariantEnabledEXT GLEW_GET_FUN(__glewIsVariantEnabledEXT) +#define glSetInvariantEXT GLEW_GET_FUN(__glewSetInvariantEXT) +#define glSetLocalConstantEXT GLEW_GET_FUN(__glewSetLocalConstantEXT) +#define glShaderOp1EXT GLEW_GET_FUN(__glewShaderOp1EXT) +#define glShaderOp2EXT GLEW_GET_FUN(__glewShaderOp2EXT) +#define glShaderOp3EXT GLEW_GET_FUN(__glewShaderOp3EXT) +#define glSwizzleEXT GLEW_GET_FUN(__glewSwizzleEXT) +#define glVariantPointerEXT GLEW_GET_FUN(__glewVariantPointerEXT) +#define glVariantbvEXT GLEW_GET_FUN(__glewVariantbvEXT) +#define glVariantdvEXT GLEW_GET_FUN(__glewVariantdvEXT) +#define glVariantfvEXT GLEW_GET_FUN(__glewVariantfvEXT) +#define glVariantivEXT GLEW_GET_FUN(__glewVariantivEXT) +#define glVariantsvEXT GLEW_GET_FUN(__glewVariantsvEXT) +#define glVariantubvEXT GLEW_GET_FUN(__glewVariantubvEXT) +#define glVariantuivEXT GLEW_GET_FUN(__glewVariantuivEXT) +#define glVariantusvEXT GLEW_GET_FUN(__glewVariantusvEXT) +#define glWriteMaskEXT GLEW_GET_FUN(__glewWriteMaskEXT) + +#define GLEW_EXT_vertex_shader GLEW_GET_VAR(__GLEW_EXT_vertex_shader) + +#endif /* GL_EXT_vertex_shader */ + +/* ------------------------ GL_EXT_vertex_weighting ------------------------ */ + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 + +#define GL_MODELVIEW0_STACK_DEPTH_EXT 0x0BA3 +#define GL_MODELVIEW0_MATRIX_EXT 0x0BA6 +#define GL_MODELVIEW0_EXT 0x1700 +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 + +typedef void (GLAPIENTRY * PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, void* pointer); +typedef void (GLAPIENTRY * PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (GLAPIENTRY * PFNGLVERTEXWEIGHTFVEXTPROC) (GLfloat* weight); + +#define glVertexWeightPointerEXT GLEW_GET_FUN(__glewVertexWeightPointerEXT) +#define glVertexWeightfEXT GLEW_GET_FUN(__glewVertexWeightfEXT) +#define glVertexWeightfvEXT GLEW_GET_FUN(__glewVertexWeightfvEXT) + +#define GLEW_EXT_vertex_weighting GLEW_GET_VAR(__GLEW_EXT_vertex_weighting) + +#endif /* GL_EXT_vertex_weighting */ + +/* ---------------------- GL_GREMEDY_frame_terminator ---------------------- */ + +#ifndef GL_GREMEDY_frame_terminator +#define GL_GREMEDY_frame_terminator 1 + +typedef void (GLAPIENTRY * PFNGLFRAMETERMINATORGREMEDYPROC) (void); + +#define glFrameTerminatorGREMEDY GLEW_GET_FUN(__glewFrameTerminatorGREMEDY) + +#define GLEW_GREMEDY_frame_terminator GLEW_GET_VAR(__GLEW_GREMEDY_frame_terminator) + +#endif /* GL_GREMEDY_frame_terminator */ + +/* ------------------------ GL_GREMEDY_string_marker ----------------------- */ + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 + +typedef void (GLAPIENTRY * PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const void* string); + +#define glStringMarkerGREMEDY GLEW_GET_FUN(__glewStringMarkerGREMEDY) + +#define GLEW_GREMEDY_string_marker GLEW_GET_VAR(__GLEW_GREMEDY_string_marker) + +#endif /* GL_GREMEDY_string_marker */ + +/* --------------------- GL_HP_convolution_border_modes -------------------- */ + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 + +#define GLEW_HP_convolution_border_modes GLEW_GET_VAR(__GLEW_HP_convolution_border_modes) + +#endif /* GL_HP_convolution_border_modes */ + +/* ------------------------- GL_HP_image_transform ------------------------- */ + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 + +typedef void (GLAPIENTRY * PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, const GLfloat param); +typedef void (GLAPIENTRY * PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, const GLint param); +typedef void (GLAPIENTRY * PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint* params); + +#define glGetImageTransformParameterfvHP GLEW_GET_FUN(__glewGetImageTransformParameterfvHP) +#define glGetImageTransformParameterivHP GLEW_GET_FUN(__glewGetImageTransformParameterivHP) +#define glImageTransformParameterfHP GLEW_GET_FUN(__glewImageTransformParameterfHP) +#define glImageTransformParameterfvHP GLEW_GET_FUN(__glewImageTransformParameterfvHP) +#define glImageTransformParameteriHP GLEW_GET_FUN(__glewImageTransformParameteriHP) +#define glImageTransformParameterivHP GLEW_GET_FUN(__glewImageTransformParameterivHP) + +#define GLEW_HP_image_transform GLEW_GET_VAR(__GLEW_HP_image_transform) + +#endif /* GL_HP_image_transform */ + +/* -------------------------- GL_HP_occlusion_test ------------------------- */ + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 + +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 + +#define GLEW_HP_occlusion_test GLEW_GET_VAR(__GLEW_HP_occlusion_test) + +#endif /* GL_HP_occlusion_test */ + +/* ------------------------- GL_HP_texture_lighting ------------------------ */ + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 + +#define GLEW_HP_texture_lighting GLEW_GET_VAR(__GLEW_HP_texture_lighting) + +#endif /* GL_HP_texture_lighting */ + +/* --------------------------- GL_IBM_cull_vertex -------------------------- */ + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 + +#define GL_CULL_VERTEX_IBM 103050 + +#define GLEW_IBM_cull_vertex GLEW_GET_VAR(__GLEW_IBM_cull_vertex) + +#endif /* GL_IBM_cull_vertex */ + +/* ---------------------- GL_IBM_multimode_draw_arrays --------------------- */ + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 + +typedef void (GLAPIENTRY * PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum* mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (GLAPIENTRY * PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum* mode, const GLsizei *count, GLenum type, const GLvoid * const *indices, GLsizei primcount, GLint modestride); + +#define glMultiModeDrawArraysIBM GLEW_GET_FUN(__glewMultiModeDrawArraysIBM) +#define glMultiModeDrawElementsIBM GLEW_GET_FUN(__glewMultiModeDrawElementsIBM) + +#define GLEW_IBM_multimode_draw_arrays GLEW_GET_VAR(__GLEW_IBM_multimode_draw_arrays) + +#endif /* GL_IBM_multimode_draw_arrays */ + +/* ------------------------- GL_IBM_rasterpos_clip ------------------------- */ + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 + +#define GL_RASTER_POSITION_UNCLIPPED_IBM 103010 + +#define GLEW_IBM_rasterpos_clip GLEW_GET_VAR(__GLEW_IBM_rasterpos_clip) + +#endif /* GL_IBM_rasterpos_clip */ + +/* --------------------------- GL_IBM_static_data -------------------------- */ + +#ifndef GL_IBM_static_data +#define GL_IBM_static_data 1 + +#define GL_ALL_STATIC_DATA_IBM 103060 +#define GL_STATIC_VERTEX_ARRAY_IBM 103061 + +#define GLEW_IBM_static_data GLEW_GET_VAR(__GLEW_IBM_static_data) + +#endif /* GL_IBM_static_data */ + +/* --------------------- GL_IBM_texture_mirrored_repeat -------------------- */ + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_IBM_texture_mirrored_repeat 1 + +#define GL_MIRRORED_REPEAT_IBM 0x8370 + +#define GLEW_IBM_texture_mirrored_repeat GLEW_GET_VAR(__GLEW_IBM_texture_mirrored_repeat) + +#endif /* GL_IBM_texture_mirrored_repeat */ + +/* ----------------------- GL_IBM_vertex_array_lists ----------------------- */ + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 + +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 + +typedef void (GLAPIENTRY * PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride); +typedef void (GLAPIENTRY * PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean ** pointer, GLint ptrstride); +typedef void (GLAPIENTRY * PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride); +typedef void (GLAPIENTRY * PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride); +typedef void (GLAPIENTRY * PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride); +typedef void (GLAPIENTRY * PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride); +typedef void (GLAPIENTRY * PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride); + +#define glColorPointerListIBM GLEW_GET_FUN(__glewColorPointerListIBM) +#define glEdgeFlagPointerListIBM GLEW_GET_FUN(__glewEdgeFlagPointerListIBM) +#define glFogCoordPointerListIBM GLEW_GET_FUN(__glewFogCoordPointerListIBM) +#define glIndexPointerListIBM GLEW_GET_FUN(__glewIndexPointerListIBM) +#define glNormalPointerListIBM GLEW_GET_FUN(__glewNormalPointerListIBM) +#define glSecondaryColorPointerListIBM GLEW_GET_FUN(__glewSecondaryColorPointerListIBM) +#define glTexCoordPointerListIBM GLEW_GET_FUN(__glewTexCoordPointerListIBM) +#define glVertexPointerListIBM GLEW_GET_FUN(__glewVertexPointerListIBM) + +#define GLEW_IBM_vertex_array_lists GLEW_GET_VAR(__GLEW_IBM_vertex_array_lists) + +#endif /* GL_IBM_vertex_array_lists */ + +/* -------------------------- GL_INGR_color_clamp -------------------------- */ + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 + +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 + +#define GLEW_INGR_color_clamp GLEW_GET_VAR(__GLEW_INGR_color_clamp) + +#endif /* GL_INGR_color_clamp */ + +/* ------------------------- GL_INGR_interlace_read ------------------------ */ + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 + +#define GL_INTERLACE_READ_INGR 0x8568 + +#define GLEW_INGR_interlace_read GLEW_GET_VAR(__GLEW_INGR_interlace_read) + +#endif /* GL_INGR_interlace_read */ + +/* ------------------------ GL_INTEL_parallel_arrays ----------------------- */ + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 + +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 + +typedef void (GLAPIENTRY * PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const void** pointer); +typedef void (GLAPIENTRY * PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const void** pointer); +typedef void (GLAPIENTRY * PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const void** pointer); +typedef void (GLAPIENTRY * PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const void** pointer); + +#define glColorPointervINTEL GLEW_GET_FUN(__glewColorPointervINTEL) +#define glNormalPointervINTEL GLEW_GET_FUN(__glewNormalPointervINTEL) +#define glTexCoordPointervINTEL GLEW_GET_FUN(__glewTexCoordPointervINTEL) +#define glVertexPointervINTEL GLEW_GET_FUN(__glewVertexPointervINTEL) + +#define GLEW_INTEL_parallel_arrays GLEW_GET_VAR(__GLEW_INTEL_parallel_arrays) + +#endif /* GL_INTEL_parallel_arrays */ + +/* ------------------------ GL_INTEL_texture_scissor ----------------------- */ + +#ifndef GL_INTEL_texture_scissor +#define GL_INTEL_texture_scissor 1 + +typedef void (GLAPIENTRY * PFNGLTEXSCISSORFUNCINTELPROC) (GLenum target, GLenum lfunc, GLenum hfunc); +typedef void (GLAPIENTRY * PFNGLTEXSCISSORINTELPROC) (GLenum target, GLclampf tlow, GLclampf thigh); + +#define glTexScissorFuncINTEL GLEW_GET_FUN(__glewTexScissorFuncINTEL) +#define glTexScissorINTEL GLEW_GET_FUN(__glewTexScissorINTEL) + +#define GLEW_INTEL_texture_scissor GLEW_GET_VAR(__GLEW_INTEL_texture_scissor) + +#endif /* GL_INTEL_texture_scissor */ + +/* -------------------------- GL_KTX_buffer_region ------------------------- */ + +#ifndef GL_KTX_buffer_region +#define GL_KTX_buffer_region 1 + +#define GL_KTX_FRONT_REGION 0x0 +#define GL_KTX_BACK_REGION 0x1 +#define GL_KTX_Z_REGION 0x2 +#define GL_KTX_STENCIL_REGION 0x3 + +typedef GLuint (GLAPIENTRY * PFNGLBUFFERREGIONENABLEDEXTPROC) (void); +typedef void (GLAPIENTRY * PFNGLDELETEBUFFERREGIONEXTPROC) (GLenum region); +typedef void (GLAPIENTRY * PFNGLDRAWBUFFERREGIONEXTPROC) (GLuint region, GLint x, GLint y, GLsizei width, GLsizei height, GLint xDest, GLint yDest); +typedef GLuint (GLAPIENTRY * PFNGLNEWBUFFERREGIONEXTPROC) (GLenum region); +typedef void (GLAPIENTRY * PFNGLREADBUFFERREGIONEXTPROC) (GLuint region, GLint x, GLint y, GLsizei width, GLsizei height); + +#define glBufferRegionEnabledEXT GLEW_GET_FUN(__glewBufferRegionEnabledEXT) +#define glDeleteBufferRegionEXT GLEW_GET_FUN(__glewDeleteBufferRegionEXT) +#define glDrawBufferRegionEXT GLEW_GET_FUN(__glewDrawBufferRegionEXT) +#define glNewBufferRegionEXT GLEW_GET_FUN(__glewNewBufferRegionEXT) +#define glReadBufferRegionEXT GLEW_GET_FUN(__glewReadBufferRegionEXT) + +#define GLEW_KTX_buffer_region GLEW_GET_VAR(__GLEW_KTX_buffer_region) + +#endif /* GL_KTX_buffer_region */ + +/* ------------------------- GL_MESAX_texture_stack ------------------------ */ + +#ifndef GL_MESAX_texture_stack +#define GL_MESAX_texture_stack 1 + +#define GL_TEXTURE_1D_STACK_MESAX 0x8759 +#define GL_TEXTURE_2D_STACK_MESAX 0x875A +#define GL_PROXY_TEXTURE_1D_STACK_MESAX 0x875B +#define GL_PROXY_TEXTURE_2D_STACK_MESAX 0x875C +#define GL_TEXTURE_1D_STACK_BINDING_MESAX 0x875D +#define GL_TEXTURE_2D_STACK_BINDING_MESAX 0x875E + +#define GLEW_MESAX_texture_stack GLEW_GET_VAR(__GLEW_MESAX_texture_stack) + +#endif /* GL_MESAX_texture_stack */ + +/* -------------------------- GL_MESA_pack_invert -------------------------- */ + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 + +#define GL_PACK_INVERT_MESA 0x8758 + +#define GLEW_MESA_pack_invert GLEW_GET_VAR(__GLEW_MESA_pack_invert) + +#endif /* GL_MESA_pack_invert */ + +/* ------------------------- GL_MESA_resize_buffers ------------------------ */ + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 + +typedef void (GLAPIENTRY * PFNGLRESIZEBUFFERSMESAPROC) (void); + +#define glResizeBuffersMESA GLEW_GET_FUN(__glewResizeBuffersMESA) + +#define GLEW_MESA_resize_buffers GLEW_GET_VAR(__GLEW_MESA_resize_buffers) + +#endif /* GL_MESA_resize_buffers */ + +/* --------------------------- GL_MESA_window_pos -------------------------- */ + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 + +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2IVMESAPROC) (const GLint* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS2SVMESAPROC) (const GLshort* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3IVMESAPROC) (const GLint* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS3SVMESAPROC) (const GLshort* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4IVMESAPROC) (const GLint* p); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (GLAPIENTRY * PFNGLWINDOWPOS4SVMESAPROC) (const GLshort* p); + +#define glWindowPos2dMESA GLEW_GET_FUN(__glewWindowPos2dMESA) +#define glWindowPos2dvMESA GLEW_GET_FUN(__glewWindowPos2dvMESA) +#define glWindowPos2fMESA GLEW_GET_FUN(__glewWindowPos2fMESA) +#define glWindowPos2fvMESA GLEW_GET_FUN(__glewWindowPos2fvMESA) +#define glWindowPos2iMESA GLEW_GET_FUN(__glewWindowPos2iMESA) +#define glWindowPos2ivMESA GLEW_GET_FUN(__glewWindowPos2ivMESA) +#define glWindowPos2sMESA GLEW_GET_FUN(__glewWindowPos2sMESA) +#define glWindowPos2svMESA GLEW_GET_FUN(__glewWindowPos2svMESA) +#define glWindowPos3dMESA GLEW_GET_FUN(__glewWindowPos3dMESA) +#define glWindowPos3dvMESA GLEW_GET_FUN(__glewWindowPos3dvMESA) +#define glWindowPos3fMESA GLEW_GET_FUN(__glewWindowPos3fMESA) +#define glWindowPos3fvMESA GLEW_GET_FUN(__glewWindowPos3fvMESA) +#define glWindowPos3iMESA GLEW_GET_FUN(__glewWindowPos3iMESA) +#define glWindowPos3ivMESA GLEW_GET_FUN(__glewWindowPos3ivMESA) +#define glWindowPos3sMESA GLEW_GET_FUN(__glewWindowPos3sMESA) +#define glWindowPos3svMESA GLEW_GET_FUN(__glewWindowPos3svMESA) +#define glWindowPos4dMESA GLEW_GET_FUN(__glewWindowPos4dMESA) +#define glWindowPos4dvMESA GLEW_GET_FUN(__glewWindowPos4dvMESA) +#define glWindowPos4fMESA GLEW_GET_FUN(__glewWindowPos4fMESA) +#define glWindowPos4fvMESA GLEW_GET_FUN(__glewWindowPos4fvMESA) +#define glWindowPos4iMESA GLEW_GET_FUN(__glewWindowPos4iMESA) +#define glWindowPos4ivMESA GLEW_GET_FUN(__glewWindowPos4ivMESA) +#define glWindowPos4sMESA GLEW_GET_FUN(__glewWindowPos4sMESA) +#define glWindowPos4svMESA GLEW_GET_FUN(__glewWindowPos4svMESA) + +#define GLEW_MESA_window_pos GLEW_GET_VAR(__GLEW_MESA_window_pos) + +#endif /* GL_MESA_window_pos */ + +/* ------------------------- GL_MESA_ycbcr_texture ------------------------- */ + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 + +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 + +#define GLEW_MESA_ycbcr_texture GLEW_GET_VAR(__GLEW_MESA_ycbcr_texture) + +#endif /* GL_MESA_ycbcr_texture */ + +/* --------------------------- GL_NV_blend_square -------------------------- */ + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 + +#define GLEW_NV_blend_square GLEW_GET_VAR(__GLEW_NV_blend_square) + +#endif /* GL_NV_blend_square */ + +/* ------------------------ GL_NV_conditional_render ----------------------- */ + +#ifndef GL_NV_conditional_render +#define GL_NV_conditional_render 1 + +#define GL_QUERY_WAIT_NV 0x8E13 +#define GL_QUERY_NO_WAIT_NV 0x8E14 +#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 + +typedef void (GLAPIENTRY * PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); +typedef void (GLAPIENTRY * PFNGLENDCONDITIONALRENDERNVPROC) (void); + +#define glBeginConditionalRenderNV GLEW_GET_FUN(__glewBeginConditionalRenderNV) +#define glEndConditionalRenderNV GLEW_GET_FUN(__glewEndConditionalRenderNV) + +#define GLEW_NV_conditional_render GLEW_GET_VAR(__GLEW_NV_conditional_render) + +#endif /* GL_NV_conditional_render */ + +/* ----------------------- GL_NV_copy_depth_to_color ----------------------- */ + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 + +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F + +#define GLEW_NV_copy_depth_to_color GLEW_GET_VAR(__GLEW_NV_copy_depth_to_color) + +#endif /* GL_NV_copy_depth_to_color */ + +/* ------------------------ GL_NV_depth_buffer_float ----------------------- */ + +#ifndef GL_NV_depth_buffer_float +#define GL_NV_depth_buffer_float 1 + +#define GL_DEPTH_COMPONENT32F_NV 0x8DAB +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV 0x8DAD +#define GL_DEPTH_BUFFER_FLOAT_MODE_NV 0x8DAF + +typedef void (GLAPIENTRY * PFNGLCLEARDEPTHDNVPROC) (GLdouble depth); +typedef void (GLAPIENTRY * PFNGLDEPTHBOUNDSDNVPROC) (GLdouble zmin, GLdouble zmax); +typedef void (GLAPIENTRY * PFNGLDEPTHRANGEDNVPROC) (GLdouble zNear, GLdouble zFar); + +#define glClearDepthdNV GLEW_GET_FUN(__glewClearDepthdNV) +#define glDepthBoundsdNV GLEW_GET_FUN(__glewDepthBoundsdNV) +#define glDepthRangedNV GLEW_GET_FUN(__glewDepthRangedNV) + +#define GLEW_NV_depth_buffer_float GLEW_GET_VAR(__GLEW_NV_depth_buffer_float) + +#endif /* GL_NV_depth_buffer_float */ + +/* --------------------------- GL_NV_depth_clamp --------------------------- */ + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 + +#define GL_DEPTH_CLAMP_NV 0x864F + +#define GLEW_NV_depth_clamp GLEW_GET_VAR(__GLEW_NV_depth_clamp) + +#endif /* GL_NV_depth_clamp */ + +/* ---------------------- GL_NV_depth_range_unclamped ---------------------- */ + +#ifndef GL_NV_depth_range_unclamped +#define GL_NV_depth_range_unclamped 1 + +#define GL_SAMPLE_COUNT_BITS_NV 0x8864 +#define GL_CURRENT_SAMPLE_COUNT_QUERY_NV 0x8865 +#define GL_QUERY_RESULT_NV 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_NV 0x8867 +#define GL_SAMPLE_COUNT_NV 0x8914 + +#define GLEW_NV_depth_range_unclamped GLEW_GET_VAR(__GLEW_NV_depth_range_unclamped) + +#endif /* GL_NV_depth_range_unclamped */ + +/* ---------------------------- GL_NV_evaluators --------------------------- */ + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 + +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 + +typedef void (GLAPIENTRY * PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +typedef void (GLAPIENTRY * PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void* points); +typedef void (GLAPIENTRY * PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void* points); +typedef void (GLAPIENTRY * PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint* params); + +#define glEvalMapsNV GLEW_GET_FUN(__glewEvalMapsNV) +#define glGetMapAttribParameterfvNV GLEW_GET_FUN(__glewGetMapAttribParameterfvNV) +#define glGetMapAttribParameterivNV GLEW_GET_FUN(__glewGetMapAttribParameterivNV) +#define glGetMapControlPointsNV GLEW_GET_FUN(__glewGetMapControlPointsNV) +#define glGetMapParameterfvNV GLEW_GET_FUN(__glewGetMapParameterfvNV) +#define glGetMapParameterivNV GLEW_GET_FUN(__glewGetMapParameterivNV) +#define glMapControlPointsNV GLEW_GET_FUN(__glewMapControlPointsNV) +#define glMapParameterfvNV GLEW_GET_FUN(__glewMapParameterfvNV) +#define glMapParameterivNV GLEW_GET_FUN(__glewMapParameterivNV) + +#define GLEW_NV_evaluators GLEW_GET_VAR(__GLEW_NV_evaluators) + +#endif /* GL_NV_evaluators */ + +/* ----------------------- GL_NV_explicit_multisample ---------------------- */ + +#ifndef GL_NV_explicit_multisample +#define GL_NV_explicit_multisample 1 + +#define GL_SAMPLE_POSITION_NV 0x8E50 +#define GL_SAMPLE_MASK_NV 0x8E51 +#define GL_SAMPLE_MASK_VALUE_NV 0x8E52 +#define GL_TEXTURE_BINDING_RENDERBUFFER_NV 0x8E53 +#define GL_TEXTURE_RENDERBUFFER_DATA_STORE_BINDING_NV 0x8E54 +#define GL_TEXTURE_RENDERBUFFER_NV 0x8E55 +#define GL_SAMPLER_RENDERBUFFER_NV 0x8E56 +#define GL_INT_SAMPLER_RENDERBUFFER_NV 0x8E57 +#define GL_UNSIGNED_INT_SAMPLER_RENDERBUFFER_NV 0x8E58 +#define GL_MAX_SAMPLE_MASK_WORDS_NV 0x8E59 + +typedef void (GLAPIENTRY * PFNGLGETMULTISAMPLEFVNVPROC) (GLenum pname, GLuint index, GLfloat* val); +typedef void (GLAPIENTRY * PFNGLSAMPLEMASKINDEXEDNVPROC) (GLuint index, GLbitfield mask); +typedef void (GLAPIENTRY * PFNGLTEXRENDERBUFFERNVPROC) (GLenum target, GLuint renderbuffer); + +#define glGetMultisamplefvNV GLEW_GET_FUN(__glewGetMultisamplefvNV) +#define glSampleMaskIndexedNV GLEW_GET_FUN(__glewSampleMaskIndexedNV) +#define glTexRenderbufferNV GLEW_GET_FUN(__glewTexRenderbufferNV) + +#define GLEW_NV_explicit_multisample GLEW_GET_VAR(__GLEW_NV_explicit_multisample) + +#endif /* GL_NV_explicit_multisample */ + +/* ------------------------------ GL_NV_fence ------------------------------ */ + +#ifndef GL_NV_fence +#define GL_NV_fence 1 + +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 + +typedef void (GLAPIENTRY * PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint* fences); +typedef void (GLAPIENTRY * PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (GLAPIENTRY * PFNGLGENFENCESNVPROC) (GLsizei n, GLuint* fences); +typedef void (GLAPIENTRY * PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISFENCENVPROC) (GLuint fence); +typedef void (GLAPIENTRY * PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +typedef GLboolean (GLAPIENTRY * PFNGLTESTFENCENVPROC) (GLuint fence); + +#define glDeleteFencesNV GLEW_GET_FUN(__glewDeleteFencesNV) +#define glFinishFenceNV GLEW_GET_FUN(__glewFinishFenceNV) +#define glGenFencesNV GLEW_GET_FUN(__glewGenFencesNV) +#define glGetFenceivNV GLEW_GET_FUN(__glewGetFenceivNV) +#define glIsFenceNV GLEW_GET_FUN(__glewIsFenceNV) +#define glSetFenceNV GLEW_GET_FUN(__glewSetFenceNV) +#define glTestFenceNV GLEW_GET_FUN(__glewTestFenceNV) + +#define GLEW_NV_fence GLEW_GET_VAR(__GLEW_NV_fence) + +#endif /* GL_NV_fence */ + +/* --------------------------- GL_NV_float_buffer -------------------------- */ + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 + +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E + +#define GLEW_NV_float_buffer GLEW_GET_VAR(__GLEW_NV_float_buffer) + +#endif /* GL_NV_float_buffer */ + +/* --------------------------- GL_NV_fog_distance -------------------------- */ + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 + +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C + +#define GLEW_NV_fog_distance GLEW_GET_VAR(__GLEW_NV_fog_distance) + +#endif /* GL_NV_fog_distance */ + +/* ------------------------- GL_NV_fragment_program ------------------------ */ + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 + +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 + +typedef void (GLAPIENTRY * PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte* name, GLdouble *params); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte* name, GLfloat *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte* name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte* name, const GLdouble v[]); +typedef void (GLAPIENTRY * PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte* name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte* name, const GLfloat v[]); + +#define glGetProgramNamedParameterdvNV GLEW_GET_FUN(__glewGetProgramNamedParameterdvNV) +#define glGetProgramNamedParameterfvNV GLEW_GET_FUN(__glewGetProgramNamedParameterfvNV) +#define glProgramNamedParameter4dNV GLEW_GET_FUN(__glewProgramNamedParameter4dNV) +#define glProgramNamedParameter4dvNV GLEW_GET_FUN(__glewProgramNamedParameter4dvNV) +#define glProgramNamedParameter4fNV GLEW_GET_FUN(__glewProgramNamedParameter4fNV) +#define glProgramNamedParameter4fvNV GLEW_GET_FUN(__glewProgramNamedParameter4fvNV) + +#define GLEW_NV_fragment_program GLEW_GET_VAR(__GLEW_NV_fragment_program) + +#endif /* GL_NV_fragment_program */ + +/* ------------------------ GL_NV_fragment_program2 ------------------------ */ + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 + +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 + +#define GLEW_NV_fragment_program2 GLEW_GET_VAR(__GLEW_NV_fragment_program2) + +#endif /* GL_NV_fragment_program2 */ + +/* ------------------------ GL_NV_fragment_program4 ------------------------ */ + +#ifndef GL_NV_fragment_program4 +#define GL_NV_fragment_program4 1 + +#define GLEW_NV_fragment_program4 GLEW_GET_VAR(__GLEW_NV_fragment_program4) + +#endif /* GL_NV_fragment_program4 */ + +/* --------------------- GL_NV_fragment_program_option --------------------- */ + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 + +#define GLEW_NV_fragment_program_option GLEW_GET_VAR(__GLEW_NV_fragment_program_option) + +#endif /* GL_NV_fragment_program_option */ + +/* ----------------- GL_NV_framebuffer_multisample_coverage ---------------- */ + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_NV_framebuffer_multisample_coverage 1 + +#define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB +#define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 +#define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 +#define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 + +typedef void (GLAPIENTRY * PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); + +#define glRenderbufferStorageMultisampleCoverageNV GLEW_GET_FUN(__glewRenderbufferStorageMultisampleCoverageNV) + +#define GLEW_NV_framebuffer_multisample_coverage GLEW_GET_VAR(__GLEW_NV_framebuffer_multisample_coverage) + +#endif /* GL_NV_framebuffer_multisample_coverage */ + +/* ------------------------ GL_NV_geometry_program4 ------------------------ */ + +#ifndef GL_NV_geometry_program4 +#define GL_NV_geometry_program4 1 + +#define GL_GEOMETRY_PROGRAM_NV 0x8C26 +#define GL_MAX_PROGRAM_OUTPUT_VERTICES_NV 0x8C27 +#define GL_MAX_PROGRAM_TOTAL_OUTPUT_COMPONENTS_NV 0x8C28 + +typedef void (GLAPIENTRY * PFNGLPROGRAMVERTEXLIMITNVPROC) (GLenum target, GLint limit); + +#define glProgramVertexLimitNV GLEW_GET_FUN(__glewProgramVertexLimitNV) + +#define GLEW_NV_geometry_program4 GLEW_GET_VAR(__GLEW_NV_geometry_program4) + +#endif /* GL_NV_geometry_program4 */ + +/* ------------------------- GL_NV_geometry_shader4 ------------------------ */ + +#ifndef GL_NV_geometry_shader4 +#define GL_NV_geometry_shader4 1 + +#define GLEW_NV_geometry_shader4 GLEW_GET_VAR(__GLEW_NV_geometry_shader4) + +#endif /* GL_NV_geometry_shader4 */ + +/* --------------------------- GL_NV_gpu_program4 -------------------------- */ + +#ifndef GL_NV_gpu_program4 +#define GL_NV_gpu_program4 1 + +#define GL_MIN_PROGRAM_TEXEL_OFFSET_NV 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_NV 0x8905 +#define GL_PROGRAM_ATTRIB_COMPONENTS_NV 0x8906 +#define GL_PROGRAM_RESULT_COMPONENTS_NV 0x8907 +#define GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV 0x8908 +#define GL_MAX_PROGRAM_RESULT_COMPONENTS_NV 0x8909 +#define GL_MAX_PROGRAM_GENERIC_ATTRIBS_NV 0x8DA5 +#define GL_MAX_PROGRAM_GENERIC_RESULTS_NV 0x8DA6 + +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); + +#define glProgramEnvParameterI4iNV GLEW_GET_FUN(__glewProgramEnvParameterI4iNV) +#define glProgramEnvParameterI4ivNV GLEW_GET_FUN(__glewProgramEnvParameterI4ivNV) +#define glProgramEnvParameterI4uiNV GLEW_GET_FUN(__glewProgramEnvParameterI4uiNV) +#define glProgramEnvParameterI4uivNV GLEW_GET_FUN(__glewProgramEnvParameterI4uivNV) +#define glProgramEnvParametersI4ivNV GLEW_GET_FUN(__glewProgramEnvParametersI4ivNV) +#define glProgramEnvParametersI4uivNV GLEW_GET_FUN(__glewProgramEnvParametersI4uivNV) +#define glProgramLocalParameterI4iNV GLEW_GET_FUN(__glewProgramLocalParameterI4iNV) +#define glProgramLocalParameterI4ivNV GLEW_GET_FUN(__glewProgramLocalParameterI4ivNV) +#define glProgramLocalParameterI4uiNV GLEW_GET_FUN(__glewProgramLocalParameterI4uiNV) +#define glProgramLocalParameterI4uivNV GLEW_GET_FUN(__glewProgramLocalParameterI4uivNV) +#define glProgramLocalParametersI4ivNV GLEW_GET_FUN(__glewProgramLocalParametersI4ivNV) +#define glProgramLocalParametersI4uivNV GLEW_GET_FUN(__glewProgramLocalParametersI4uivNV) + +#define GLEW_NV_gpu_program4 GLEW_GET_VAR(__GLEW_NV_gpu_program4) + +#endif /* GL_NV_gpu_program4 */ + +/* ---------------------------- GL_NV_half_float --------------------------- */ + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 + +#define GL_HALF_FLOAT_NV 0x140B + +typedef unsigned short GLhalf; + +typedef void (GLAPIENTRY * PFNGLCOLOR3HNVPROC) (GLhalf red, GLhalf green, GLhalf blue); +typedef void (GLAPIENTRY * PFNGLCOLOR3HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLCOLOR4HNVPROC) (GLhalf red, GLhalf green, GLhalf blue, GLhalf alpha); +typedef void (GLAPIENTRY * PFNGLCOLOR4HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLFOGCOORDHNVPROC) (GLhalf fog); +typedef void (GLAPIENTRY * PFNGLFOGCOORDHVNVPROC) (const GLhalf* fog); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalf s); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalf s, GLhalf t); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalf s, GLhalf t, GLhalf r); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalf s, GLhalf t, GLhalf r, GLhalf q); +typedef void (GLAPIENTRY * PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLNORMAL3HNVPROC) (GLhalf nx, GLhalf ny, GLhalf nz); +typedef void (GLAPIENTRY * PFNGLNORMAL3HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3HNVPROC) (GLhalf red, GLhalf green, GLhalf blue); +typedef void (GLAPIENTRY * PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD1HNVPROC) (GLhalf s); +typedef void (GLAPIENTRY * PFNGLTEXCOORD1HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2HNVPROC) (GLhalf s, GLhalf t); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD3HNVPROC) (GLhalf s, GLhalf t, GLhalf r); +typedef void (GLAPIENTRY * PFNGLTEXCOORD3HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD4HNVPROC) (GLhalf s, GLhalf t, GLhalf r, GLhalf q); +typedef void (GLAPIENTRY * PFNGLTEXCOORD4HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEX2HNVPROC) (GLhalf x, GLhalf y); +typedef void (GLAPIENTRY * PFNGLVERTEX2HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEX3HNVPROC) (GLhalf x, GLhalf y, GLhalf z); +typedef void (GLAPIENTRY * PFNGLVERTEX3HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEX4HNVPROC) (GLhalf x, GLhalf y, GLhalf z, GLhalf w); +typedef void (GLAPIENTRY * PFNGLVERTEX4HVNVPROC) (const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalf x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalf x, GLhalf y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalf x, GLhalf y, GLhalf z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalf x, GLhalf y, GLhalf z, GLhalf w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalf* v); +typedef void (GLAPIENTRY * PFNGLVERTEXWEIGHTHNVPROC) (GLhalf weight); +typedef void (GLAPIENTRY * PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalf* weight); + +#define glColor3hNV GLEW_GET_FUN(__glewColor3hNV) +#define glColor3hvNV GLEW_GET_FUN(__glewColor3hvNV) +#define glColor4hNV GLEW_GET_FUN(__glewColor4hNV) +#define glColor4hvNV GLEW_GET_FUN(__glewColor4hvNV) +#define glFogCoordhNV GLEW_GET_FUN(__glewFogCoordhNV) +#define glFogCoordhvNV GLEW_GET_FUN(__glewFogCoordhvNV) +#define glMultiTexCoord1hNV GLEW_GET_FUN(__glewMultiTexCoord1hNV) +#define glMultiTexCoord1hvNV GLEW_GET_FUN(__glewMultiTexCoord1hvNV) +#define glMultiTexCoord2hNV GLEW_GET_FUN(__glewMultiTexCoord2hNV) +#define glMultiTexCoord2hvNV GLEW_GET_FUN(__glewMultiTexCoord2hvNV) +#define glMultiTexCoord3hNV GLEW_GET_FUN(__glewMultiTexCoord3hNV) +#define glMultiTexCoord3hvNV GLEW_GET_FUN(__glewMultiTexCoord3hvNV) +#define glMultiTexCoord4hNV GLEW_GET_FUN(__glewMultiTexCoord4hNV) +#define glMultiTexCoord4hvNV GLEW_GET_FUN(__glewMultiTexCoord4hvNV) +#define glNormal3hNV GLEW_GET_FUN(__glewNormal3hNV) +#define glNormal3hvNV GLEW_GET_FUN(__glewNormal3hvNV) +#define glSecondaryColor3hNV GLEW_GET_FUN(__glewSecondaryColor3hNV) +#define glSecondaryColor3hvNV GLEW_GET_FUN(__glewSecondaryColor3hvNV) +#define glTexCoord1hNV GLEW_GET_FUN(__glewTexCoord1hNV) +#define glTexCoord1hvNV GLEW_GET_FUN(__glewTexCoord1hvNV) +#define glTexCoord2hNV GLEW_GET_FUN(__glewTexCoord2hNV) +#define glTexCoord2hvNV GLEW_GET_FUN(__glewTexCoord2hvNV) +#define glTexCoord3hNV GLEW_GET_FUN(__glewTexCoord3hNV) +#define glTexCoord3hvNV GLEW_GET_FUN(__glewTexCoord3hvNV) +#define glTexCoord4hNV GLEW_GET_FUN(__glewTexCoord4hNV) +#define glTexCoord4hvNV GLEW_GET_FUN(__glewTexCoord4hvNV) +#define glVertex2hNV GLEW_GET_FUN(__glewVertex2hNV) +#define glVertex2hvNV GLEW_GET_FUN(__glewVertex2hvNV) +#define glVertex3hNV GLEW_GET_FUN(__glewVertex3hNV) +#define glVertex3hvNV GLEW_GET_FUN(__glewVertex3hvNV) +#define glVertex4hNV GLEW_GET_FUN(__glewVertex4hNV) +#define glVertex4hvNV GLEW_GET_FUN(__glewVertex4hvNV) +#define glVertexAttrib1hNV GLEW_GET_FUN(__glewVertexAttrib1hNV) +#define glVertexAttrib1hvNV GLEW_GET_FUN(__glewVertexAttrib1hvNV) +#define glVertexAttrib2hNV GLEW_GET_FUN(__glewVertexAttrib2hNV) +#define glVertexAttrib2hvNV GLEW_GET_FUN(__glewVertexAttrib2hvNV) +#define glVertexAttrib3hNV GLEW_GET_FUN(__glewVertexAttrib3hNV) +#define glVertexAttrib3hvNV GLEW_GET_FUN(__glewVertexAttrib3hvNV) +#define glVertexAttrib4hNV GLEW_GET_FUN(__glewVertexAttrib4hNV) +#define glVertexAttrib4hvNV GLEW_GET_FUN(__glewVertexAttrib4hvNV) +#define glVertexAttribs1hvNV GLEW_GET_FUN(__glewVertexAttribs1hvNV) +#define glVertexAttribs2hvNV GLEW_GET_FUN(__glewVertexAttribs2hvNV) +#define glVertexAttribs3hvNV GLEW_GET_FUN(__glewVertexAttribs3hvNV) +#define glVertexAttribs4hvNV GLEW_GET_FUN(__glewVertexAttribs4hvNV) +#define glVertexWeighthNV GLEW_GET_FUN(__glewVertexWeighthNV) +#define glVertexWeighthvNV GLEW_GET_FUN(__glewVertexWeighthvNV) + +#define GLEW_NV_half_float GLEW_GET_VAR(__GLEW_NV_half_float) + +#endif /* GL_NV_half_float */ + +/* ------------------------ GL_NV_light_max_exponent ----------------------- */ + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 + +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 + +#define GLEW_NV_light_max_exponent GLEW_GET_VAR(__GLEW_NV_light_max_exponent) + +#endif /* GL_NV_light_max_exponent */ + +/* --------------------- GL_NV_multisample_filter_hint --------------------- */ + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 + +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 + +#define GLEW_NV_multisample_filter_hint GLEW_GET_VAR(__GLEW_NV_multisample_filter_hint) + +#endif /* GL_NV_multisample_filter_hint */ + +/* ------------------------- GL_NV_occlusion_query ------------------------- */ + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 + +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 + +typedef void (GLAPIENTRY * PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint* ids); +typedef void (GLAPIENTRY * PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (GLAPIENTRY * PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint* ids); +typedef void (GLAPIENTRY * PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); + +#define glBeginOcclusionQueryNV GLEW_GET_FUN(__glewBeginOcclusionQueryNV) +#define glDeleteOcclusionQueriesNV GLEW_GET_FUN(__glewDeleteOcclusionQueriesNV) +#define glEndOcclusionQueryNV GLEW_GET_FUN(__glewEndOcclusionQueryNV) +#define glGenOcclusionQueriesNV GLEW_GET_FUN(__glewGenOcclusionQueriesNV) +#define glGetOcclusionQueryivNV GLEW_GET_FUN(__glewGetOcclusionQueryivNV) +#define glGetOcclusionQueryuivNV GLEW_GET_FUN(__glewGetOcclusionQueryuivNV) +#define glIsOcclusionQueryNV GLEW_GET_FUN(__glewIsOcclusionQueryNV) + +#define GLEW_NV_occlusion_query GLEW_GET_VAR(__GLEW_NV_occlusion_query) + +#endif /* GL_NV_occlusion_query */ + +/* ----------------------- GL_NV_packed_depth_stencil ---------------------- */ + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 + +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA + +#define GLEW_NV_packed_depth_stencil GLEW_GET_VAR(__GLEW_NV_packed_depth_stencil) + +#endif /* GL_NV_packed_depth_stencil */ + +/* --------------------- GL_NV_parameter_buffer_object --------------------- */ + +#ifndef GL_NV_parameter_buffer_object +#define GL_NV_parameter_buffer_object 1 + +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_BINDINGS_NV 0x8DA0 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_SIZE_NV 0x8DA1 +#define GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV 0x8DA2 +#define GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV 0x8DA3 +#define GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV 0x8DA4 + +typedef void (GLAPIENTRY * PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLuint *params); +typedef void (GLAPIENTRY * PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLfloat *params); + +#define glProgramBufferParametersIivNV GLEW_GET_FUN(__glewProgramBufferParametersIivNV) +#define glProgramBufferParametersIuivNV GLEW_GET_FUN(__glewProgramBufferParametersIuivNV) +#define glProgramBufferParametersfvNV GLEW_GET_FUN(__glewProgramBufferParametersfvNV) + +#define GLEW_NV_parameter_buffer_object GLEW_GET_VAR(__GLEW_NV_parameter_buffer_object) + +#endif /* GL_NV_parameter_buffer_object */ + +/* ------------------------- GL_NV_pixel_data_range ------------------------ */ + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 + +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D + +typedef void (GLAPIENTRY * PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +typedef void (GLAPIENTRY * PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, void* pointer); + +#define glFlushPixelDataRangeNV GLEW_GET_FUN(__glewFlushPixelDataRangeNV) +#define glPixelDataRangeNV GLEW_GET_FUN(__glewPixelDataRangeNV) + +#define GLEW_NV_pixel_data_range GLEW_GET_VAR(__GLEW_NV_pixel_data_range) + +#endif /* GL_NV_pixel_data_range */ + +/* --------------------------- GL_NV_point_sprite -------------------------- */ + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 + +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 + +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint* params); + +#define glPointParameteriNV GLEW_GET_FUN(__glewPointParameteriNV) +#define glPointParameterivNV GLEW_GET_FUN(__glewPointParameterivNV) + +#define GLEW_NV_point_sprite GLEW_GET_VAR(__GLEW_NV_point_sprite) + +#endif /* GL_NV_point_sprite */ + +/* -------------------------- GL_NV_present_video -------------------------- */ + +#ifndef GL_NV_present_video +#define GL_NV_present_video 1 + +#define GL_FRAME_NV 0x8E26 +#define GL_FIELDS_NV 0x8E27 +#define GL_CURRENT_TIME_NV 0x8E28 +#define GL_NUM_FILL_STREAMS_NV 0x8E29 +#define GL_PRESENT_TIME_NV 0x8E2A +#define GL_PRESENT_DURATION_NV 0x8E2B + +typedef void (GLAPIENTRY * PFNGLGETVIDEOI64VNVPROC) (GLuint video_slot, GLenum pname, GLint64EXT* params); +typedef void (GLAPIENTRY * PFNGLGETVIDEOIVNVPROC) (GLuint video_slot, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETVIDEOUI64VNVPROC) (GLuint video_slot, GLenum pname, GLuint64EXT* params); +typedef void (GLAPIENTRY * PFNGLGETVIDEOUIVNVPROC) (GLuint video_slot, GLenum pname, GLuint* params); +typedef void (GLAPIENTRY * PFNGLPRESENTFRAMEDUALFILLNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +typedef void (GLAPIENTRY * PFNGLPRESENTFRAMEKEYEDNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +typedef void (GLAPIENTRY * PFNGLVIDEOPARAMETERIVNVPROC) (GLuint video_slot, GLenum pname, const GLint* params); + +#define glGetVideoi64vNV GLEW_GET_FUN(__glewGetVideoi64vNV) +#define glGetVideoivNV GLEW_GET_FUN(__glewGetVideoivNV) +#define glGetVideoui64vNV GLEW_GET_FUN(__glewGetVideoui64vNV) +#define glGetVideouivNV GLEW_GET_FUN(__glewGetVideouivNV) +#define glPresentFrameDualFillNV GLEW_GET_FUN(__glewPresentFrameDualFillNV) +#define glPresentFrameKeyedNV GLEW_GET_FUN(__glewPresentFrameKeyedNV) +#define glVideoParameterivNV GLEW_GET_FUN(__glewVideoParameterivNV) + +#define GLEW_NV_present_video GLEW_GET_VAR(__GLEW_NV_present_video) + +#endif /* GL_NV_present_video */ + +/* ------------------------ GL_NV_primitive_restart ------------------------ */ + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 + +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 + +typedef void (GLAPIENTRY * PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +typedef void (GLAPIENTRY * PFNGLPRIMITIVERESTARTNVPROC) (void); + +#define glPrimitiveRestartIndexNV GLEW_GET_FUN(__glewPrimitiveRestartIndexNV) +#define glPrimitiveRestartNV GLEW_GET_FUN(__glewPrimitiveRestartNV) + +#define GLEW_NV_primitive_restart GLEW_GET_VAR(__GLEW_NV_primitive_restart) + +#endif /* GL_NV_primitive_restart */ + +/* ------------------------ GL_NV_register_combiners ----------------------- */ + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 + +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 + +typedef void (GLAPIENTRY * PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (GLAPIENTRY * PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (GLAPIENTRY * PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (GLAPIENTRY * PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint* params); + +#define glCombinerInputNV GLEW_GET_FUN(__glewCombinerInputNV) +#define glCombinerOutputNV GLEW_GET_FUN(__glewCombinerOutputNV) +#define glCombinerParameterfNV GLEW_GET_FUN(__glewCombinerParameterfNV) +#define glCombinerParameterfvNV GLEW_GET_FUN(__glewCombinerParameterfvNV) +#define glCombinerParameteriNV GLEW_GET_FUN(__glewCombinerParameteriNV) +#define glCombinerParameterivNV GLEW_GET_FUN(__glewCombinerParameterivNV) +#define glFinalCombinerInputNV GLEW_GET_FUN(__glewFinalCombinerInputNV) +#define glGetCombinerInputParameterfvNV GLEW_GET_FUN(__glewGetCombinerInputParameterfvNV) +#define glGetCombinerInputParameterivNV GLEW_GET_FUN(__glewGetCombinerInputParameterivNV) +#define glGetCombinerOutputParameterfvNV GLEW_GET_FUN(__glewGetCombinerOutputParameterfvNV) +#define glGetCombinerOutputParameterivNV GLEW_GET_FUN(__glewGetCombinerOutputParameterivNV) +#define glGetFinalCombinerInputParameterfvNV GLEW_GET_FUN(__glewGetFinalCombinerInputParameterfvNV) +#define glGetFinalCombinerInputParameterivNV GLEW_GET_FUN(__glewGetFinalCombinerInputParameterivNV) + +#define GLEW_NV_register_combiners GLEW_GET_VAR(__GLEW_NV_register_combiners) + +#endif /* GL_NV_register_combiners */ + +/* ----------------------- GL_NV_register_combiners2 ----------------------- */ + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 + +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 + +typedef void (GLAPIENTRY * PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat* params); + +#define glCombinerStageParameterfvNV GLEW_GET_FUN(__glewCombinerStageParameterfvNV) +#define glGetCombinerStageParameterfvNV GLEW_GET_FUN(__glewGetCombinerStageParameterfvNV) + +#define GLEW_NV_register_combiners2 GLEW_GET_VAR(__GLEW_NV_register_combiners2) + +#endif /* GL_NV_register_combiners2 */ + +/* -------------------------- GL_NV_texgen_emboss -------------------------- */ + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 + +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F + +#define GLEW_NV_texgen_emboss GLEW_GET_VAR(__GLEW_NV_texgen_emboss) + +#endif /* GL_NV_texgen_emboss */ + +/* ------------------------ GL_NV_texgen_reflection ------------------------ */ + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 + +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 + +#define GLEW_NV_texgen_reflection GLEW_GET_VAR(__GLEW_NV_texgen_reflection) + +#endif /* GL_NV_texgen_reflection */ + +/* --------------------- GL_NV_texture_compression_vtc --------------------- */ + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 + +#define GLEW_NV_texture_compression_vtc GLEW_GET_VAR(__GLEW_NV_texture_compression_vtc) + +#endif /* GL_NV_texture_compression_vtc */ + +/* ----------------------- GL_NV_texture_env_combine4 ---------------------- */ + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 + +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B + +#define GLEW_NV_texture_env_combine4 GLEW_GET_VAR(__GLEW_NV_texture_env_combine4) + +#endif /* GL_NV_texture_env_combine4 */ + +/* ---------------------- GL_NV_texture_expand_normal ---------------------- */ + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 + +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F + +#define GLEW_NV_texture_expand_normal GLEW_GET_VAR(__GLEW_NV_texture_expand_normal) + +#endif /* GL_NV_texture_expand_normal */ + +/* ------------------------ GL_NV_texture_rectangle ------------------------ */ + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 + +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 + +#define GLEW_NV_texture_rectangle GLEW_GET_VAR(__GLEW_NV_texture_rectangle) + +#endif /* GL_NV_texture_rectangle */ + +/* -------------------------- GL_NV_texture_shader ------------------------- */ + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 + +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_2D_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_BIAS_NV 0x86E3 +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F + +#define GLEW_NV_texture_shader GLEW_GET_VAR(__GLEW_NV_texture_shader) + +#endif /* GL_NV_texture_shader */ + +/* ------------------------- GL_NV_texture_shader2 ------------------------- */ + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 + +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D + +#define GLEW_NV_texture_shader2 GLEW_GET_VAR(__GLEW_NV_texture_shader2) + +#endif /* GL_NV_texture_shader2 */ + +/* ------------------------- GL_NV_texture_shader3 ------------------------- */ + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 + +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 + +#define GLEW_NV_texture_shader3 GLEW_GET_VAR(__GLEW_NV_texture_shader3) + +#endif /* GL_NV_texture_shader3 */ + +/* ------------------------ GL_NV_transform_feedback ----------------------- */ + +#ifndef GL_NV_transform_feedback +#define GL_NV_transform_feedback 1 + +#define GL_BACK_PRIMARY_COLOR_NV 0x8C77 +#define GL_BACK_SECONDARY_COLOR_NV 0x8C78 +#define GL_TEXTURE_COORD_NV 0x8C79 +#define GL_CLIP_DISTANCE_NV 0x8C7A +#define GL_VERTEX_ID_NV 0x8C7B +#define GL_PRIMITIVE_ID_NV 0x8C7C +#define GL_GENERIC_ATTRIB_NV 0x8C7D +#define GL_TRANSFORM_FEEDBACK_ATTRIBS_NV 0x8C7E +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_NV 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_NV 0x8C80 +#define GL_ACTIVE_VARYINGS_NV 0x8C81 +#define GL_ACTIVE_VARYING_MAX_LENGTH_NV 0x8C82 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_NV 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_NV 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_NV 0x8C85 +#define GL_TRANSFORM_FEEDBACK_RECORD_NV 0x8C86 +#define GL_PRIMITIVES_GENERATED_NV 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV 0x8C88 +#define GL_RASTERIZER_DISCARD_NV 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_NV 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_NV 0x8C8B +#define GL_INTERLEAVED_ATTRIBS_NV 0x8C8C +#define GL_SEPARATE_ATTRIBS_NV 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER_NV 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_NV 0x8C8F + +typedef void (GLAPIENTRY * PFNGLACTIVEVARYINGNVPROC) (GLuint program, const GLchar *name); +typedef void (GLAPIENTRY * PFNGLBEGINTRANSFORMFEEDBACKNVPROC) (GLenum primitiveMode); +typedef void (GLAPIENTRY * PFNGLBINDBUFFERBASENVPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (GLAPIENTRY * PFNGLBINDBUFFEROFFSETNVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (GLAPIENTRY * PFNGLBINDBUFFERRANGENVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (GLAPIENTRY * PFNGLENDTRANSFORMFEEDBACKNVPROC) (void); +typedef void (GLAPIENTRY * PFNGLGETACTIVEVARYINGNVPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (GLAPIENTRY * PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC) (GLuint program, GLuint index, GLint *location); +typedef GLint (GLAPIENTRY * PFNGLGETVARYINGLOCATIONNVPROC) (GLuint program, const GLchar *name); +typedef void (GLAPIENTRY * PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC) (GLuint count, const GLint *attribs, GLenum bufferMode); +typedef void (GLAPIENTRY * PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC) (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); + +#define glActiveVaryingNV GLEW_GET_FUN(__glewActiveVaryingNV) +#define glBeginTransformFeedbackNV GLEW_GET_FUN(__glewBeginTransformFeedbackNV) +#define glBindBufferBaseNV GLEW_GET_FUN(__glewBindBufferBaseNV) +#define glBindBufferOffsetNV GLEW_GET_FUN(__glewBindBufferOffsetNV) +#define glBindBufferRangeNV GLEW_GET_FUN(__glewBindBufferRangeNV) +#define glEndTransformFeedbackNV GLEW_GET_FUN(__glewEndTransformFeedbackNV) +#define glGetActiveVaryingNV GLEW_GET_FUN(__glewGetActiveVaryingNV) +#define glGetTransformFeedbackVaryingNV GLEW_GET_FUN(__glewGetTransformFeedbackVaryingNV) +#define glGetVaryingLocationNV GLEW_GET_FUN(__glewGetVaryingLocationNV) +#define glTransformFeedbackAttribsNV GLEW_GET_FUN(__glewTransformFeedbackAttribsNV) +#define glTransformFeedbackVaryingsNV GLEW_GET_FUN(__glewTransformFeedbackVaryingsNV) + +#define GLEW_NV_transform_feedback GLEW_GET_VAR(__GLEW_NV_transform_feedback) + +#endif /* GL_NV_transform_feedback */ + +/* ------------------------ GL_NV_vertex_array_range ----------------------- */ + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 + +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 + +typedef void (GLAPIENTRY * PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (GLAPIENTRY * PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, void* pointer); + +#define glFlushVertexArrayRangeNV GLEW_GET_FUN(__glewFlushVertexArrayRangeNV) +#define glVertexArrayRangeNV GLEW_GET_FUN(__glewVertexArrayRangeNV) + +#define GLEW_NV_vertex_array_range GLEW_GET_VAR(__GLEW_NV_vertex_array_range) + +#endif /* GL_NV_vertex_array_range */ + +/* ----------------------- GL_NV_vertex_array_range2 ----------------------- */ + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 + +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 + +#define GLEW_NV_vertex_array_range2 GLEW_GET_VAR(__GLEW_NV_vertex_array_range2) + +#endif /* GL_NV_vertex_array_range2 */ + +/* -------------------------- GL_NV_vertex_program ------------------------- */ + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 + +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F + +typedef GLboolean (GLAPIENTRY * PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint* ids, GLboolean *residences); +typedef void (GLAPIENTRY * PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (GLAPIENTRY * PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint* ids); +typedef void (GLAPIENTRY * PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint* ids); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte* program); +typedef void (GLAPIENTRY * PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, GLvoid** pointer); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint* params); +typedef GLboolean (GLAPIENTRY * PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (GLAPIENTRY * PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte* program); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLuint num, const GLdouble* params); +typedef void (GLAPIENTRY * PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLuint num, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, GLuint* ids); +typedef void (GLAPIENTRY * PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei n, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei n, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei n, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei n, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei n, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei n, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei n, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei n, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei n, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei n, const GLdouble* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei n, const GLfloat* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei n, const GLshort* v); +typedef void (GLAPIENTRY * PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei n, const GLubyte* v); + +#define glAreProgramsResidentNV GLEW_GET_FUN(__glewAreProgramsResidentNV) +#define glBindProgramNV GLEW_GET_FUN(__glewBindProgramNV) +#define glDeleteProgramsNV GLEW_GET_FUN(__glewDeleteProgramsNV) +#define glExecuteProgramNV GLEW_GET_FUN(__glewExecuteProgramNV) +#define glGenProgramsNV GLEW_GET_FUN(__glewGenProgramsNV) +#define glGetProgramParameterdvNV GLEW_GET_FUN(__glewGetProgramParameterdvNV) +#define glGetProgramParameterfvNV GLEW_GET_FUN(__glewGetProgramParameterfvNV) +#define glGetProgramStringNV GLEW_GET_FUN(__glewGetProgramStringNV) +#define glGetProgramivNV GLEW_GET_FUN(__glewGetProgramivNV) +#define glGetTrackMatrixivNV GLEW_GET_FUN(__glewGetTrackMatrixivNV) +#define glGetVertexAttribPointervNV GLEW_GET_FUN(__glewGetVertexAttribPointervNV) +#define glGetVertexAttribdvNV GLEW_GET_FUN(__glewGetVertexAttribdvNV) +#define glGetVertexAttribfvNV GLEW_GET_FUN(__glewGetVertexAttribfvNV) +#define glGetVertexAttribivNV GLEW_GET_FUN(__glewGetVertexAttribivNV) +#define glIsProgramNV GLEW_GET_FUN(__glewIsProgramNV) +#define glLoadProgramNV GLEW_GET_FUN(__glewLoadProgramNV) +#define glProgramParameter4dNV GLEW_GET_FUN(__glewProgramParameter4dNV) +#define glProgramParameter4dvNV GLEW_GET_FUN(__glewProgramParameter4dvNV) +#define glProgramParameter4fNV GLEW_GET_FUN(__glewProgramParameter4fNV) +#define glProgramParameter4fvNV GLEW_GET_FUN(__glewProgramParameter4fvNV) +#define glProgramParameters4dvNV GLEW_GET_FUN(__glewProgramParameters4dvNV) +#define glProgramParameters4fvNV GLEW_GET_FUN(__glewProgramParameters4fvNV) +#define glRequestResidentProgramsNV GLEW_GET_FUN(__glewRequestResidentProgramsNV) +#define glTrackMatrixNV GLEW_GET_FUN(__glewTrackMatrixNV) +#define glVertexAttrib1dNV GLEW_GET_FUN(__glewVertexAttrib1dNV) +#define glVertexAttrib1dvNV GLEW_GET_FUN(__glewVertexAttrib1dvNV) +#define glVertexAttrib1fNV GLEW_GET_FUN(__glewVertexAttrib1fNV) +#define glVertexAttrib1fvNV GLEW_GET_FUN(__glewVertexAttrib1fvNV) +#define glVertexAttrib1sNV GLEW_GET_FUN(__glewVertexAttrib1sNV) +#define glVertexAttrib1svNV GLEW_GET_FUN(__glewVertexAttrib1svNV) +#define glVertexAttrib2dNV GLEW_GET_FUN(__glewVertexAttrib2dNV) +#define glVertexAttrib2dvNV GLEW_GET_FUN(__glewVertexAttrib2dvNV) +#define glVertexAttrib2fNV GLEW_GET_FUN(__glewVertexAttrib2fNV) +#define glVertexAttrib2fvNV GLEW_GET_FUN(__glewVertexAttrib2fvNV) +#define glVertexAttrib2sNV GLEW_GET_FUN(__glewVertexAttrib2sNV) +#define glVertexAttrib2svNV GLEW_GET_FUN(__glewVertexAttrib2svNV) +#define glVertexAttrib3dNV GLEW_GET_FUN(__glewVertexAttrib3dNV) +#define glVertexAttrib3dvNV GLEW_GET_FUN(__glewVertexAttrib3dvNV) +#define glVertexAttrib3fNV GLEW_GET_FUN(__glewVertexAttrib3fNV) +#define glVertexAttrib3fvNV GLEW_GET_FUN(__glewVertexAttrib3fvNV) +#define glVertexAttrib3sNV GLEW_GET_FUN(__glewVertexAttrib3sNV) +#define glVertexAttrib3svNV GLEW_GET_FUN(__glewVertexAttrib3svNV) +#define glVertexAttrib4dNV GLEW_GET_FUN(__glewVertexAttrib4dNV) +#define glVertexAttrib4dvNV GLEW_GET_FUN(__glewVertexAttrib4dvNV) +#define glVertexAttrib4fNV GLEW_GET_FUN(__glewVertexAttrib4fNV) +#define glVertexAttrib4fvNV GLEW_GET_FUN(__glewVertexAttrib4fvNV) +#define glVertexAttrib4sNV GLEW_GET_FUN(__glewVertexAttrib4sNV) +#define glVertexAttrib4svNV GLEW_GET_FUN(__glewVertexAttrib4svNV) +#define glVertexAttrib4ubNV GLEW_GET_FUN(__glewVertexAttrib4ubNV) +#define glVertexAttrib4ubvNV GLEW_GET_FUN(__glewVertexAttrib4ubvNV) +#define glVertexAttribPointerNV GLEW_GET_FUN(__glewVertexAttribPointerNV) +#define glVertexAttribs1dvNV GLEW_GET_FUN(__glewVertexAttribs1dvNV) +#define glVertexAttribs1fvNV GLEW_GET_FUN(__glewVertexAttribs1fvNV) +#define glVertexAttribs1svNV GLEW_GET_FUN(__glewVertexAttribs1svNV) +#define glVertexAttribs2dvNV GLEW_GET_FUN(__glewVertexAttribs2dvNV) +#define glVertexAttribs2fvNV GLEW_GET_FUN(__glewVertexAttribs2fvNV) +#define glVertexAttribs2svNV GLEW_GET_FUN(__glewVertexAttribs2svNV) +#define glVertexAttribs3dvNV GLEW_GET_FUN(__glewVertexAttribs3dvNV) +#define glVertexAttribs3fvNV GLEW_GET_FUN(__glewVertexAttribs3fvNV) +#define glVertexAttribs3svNV GLEW_GET_FUN(__glewVertexAttribs3svNV) +#define glVertexAttribs4dvNV GLEW_GET_FUN(__glewVertexAttribs4dvNV) +#define glVertexAttribs4fvNV GLEW_GET_FUN(__glewVertexAttribs4fvNV) +#define glVertexAttribs4svNV GLEW_GET_FUN(__glewVertexAttribs4svNV) +#define glVertexAttribs4ubvNV GLEW_GET_FUN(__glewVertexAttribs4ubvNV) + +#define GLEW_NV_vertex_program GLEW_GET_VAR(__GLEW_NV_vertex_program) + +#endif /* GL_NV_vertex_program */ + +/* ------------------------ GL_NV_vertex_program1_1 ------------------------ */ + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 + +#define GLEW_NV_vertex_program1_1 GLEW_GET_VAR(__GLEW_NV_vertex_program1_1) + +#endif /* GL_NV_vertex_program1_1 */ + +/* ------------------------- GL_NV_vertex_program2 ------------------------- */ + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 + +#define GLEW_NV_vertex_program2 GLEW_GET_VAR(__GLEW_NV_vertex_program2) + +#endif /* GL_NV_vertex_program2 */ + +/* ---------------------- GL_NV_vertex_program2_option --------------------- */ + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 + +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 + +#define GLEW_NV_vertex_program2_option GLEW_GET_VAR(__GLEW_NV_vertex_program2_option) + +#endif /* GL_NV_vertex_program2_option */ + +/* ------------------------- GL_NV_vertex_program3 ------------------------- */ + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 + +#define MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C + +#define GLEW_NV_vertex_program3 GLEW_GET_VAR(__GLEW_NV_vertex_program3) + +#endif /* GL_NV_vertex_program3 */ + +/* ------------------------- GL_NV_vertex_program4 ------------------------- */ + +#ifndef GL_NV_vertex_program4 +#define GL_NV_vertex_program4 1 + +#define GLEW_NV_vertex_program4 GLEW_GET_VAR(__GLEW_NV_vertex_program4) + +#endif /* GL_NV_vertex_program4 */ + +/* ------------------------ GL_OES_byte_coordinates ------------------------ */ + +#ifndef GL_OES_byte_coordinates +#define GL_OES_byte_coordinates 1 + +#define GL_BYTE 0x1400 + +#define GLEW_OES_byte_coordinates GLEW_GET_VAR(__GLEW_OES_byte_coordinates) + +#endif /* GL_OES_byte_coordinates */ + +/* ------------------- GL_OES_compressed_paletted_texture ------------------ */ + +#ifndef GL_OES_compressed_paletted_texture +#define GL_OES_compressed_paletted_texture 1 + +#define GL_PALETTE4_RGB8_OES 0x8B90 +#define GL_PALETTE4_RGBA8_OES 0x8B91 +#define GL_PALETTE4_R5_G6_B5_OES 0x8B92 +#define GL_PALETTE4_RGBA4_OES 0x8B93 +#define GL_PALETTE4_RGB5_A1_OES 0x8B94 +#define GL_PALETTE8_RGB8_OES 0x8B95 +#define GL_PALETTE8_RGBA8_OES 0x8B96 +#define GL_PALETTE8_R5_G6_B5_OES 0x8B97 +#define GL_PALETTE8_RGBA4_OES 0x8B98 +#define GL_PALETTE8_RGB5_A1_OES 0x8B99 + +#define GLEW_OES_compressed_paletted_texture GLEW_GET_VAR(__GLEW_OES_compressed_paletted_texture) + +#endif /* GL_OES_compressed_paletted_texture */ + +/* --------------------------- GL_OES_read_format -------------------------- */ + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 + +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B + +#define GLEW_OES_read_format GLEW_GET_VAR(__GLEW_OES_read_format) + +#endif /* GL_OES_read_format */ + +/* ------------------------ GL_OES_single_precision ------------------------ */ + +#ifndef GL_OES_single_precision +#define GL_OES_single_precision 1 + +typedef void (GLAPIENTRY * PFNGLCLEARDEPTHFOESPROC) (GLclampd depth); +typedef void (GLAPIENTRY * PFNGLCLIPPLANEFOESPROC) (GLenum plane, const GLfloat* equation); +typedef void (GLAPIENTRY * PFNGLDEPTHRANGEFOESPROC) (GLclampf n, GLclampf f); +typedef void (GLAPIENTRY * PFNGLFRUSTUMFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +typedef void (GLAPIENTRY * PFNGLGETCLIPPLANEFOESPROC) (GLenum plane, GLfloat* equation); +typedef void (GLAPIENTRY * PFNGLORTHOFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); + +#define glClearDepthfOES GLEW_GET_FUN(__glewClearDepthfOES) +#define glClipPlanefOES GLEW_GET_FUN(__glewClipPlanefOES) +#define glDepthRangefOES GLEW_GET_FUN(__glewDepthRangefOES) +#define glFrustumfOES GLEW_GET_FUN(__glewFrustumfOES) +#define glGetClipPlanefOES GLEW_GET_FUN(__glewGetClipPlanefOES) +#define glOrthofOES GLEW_GET_FUN(__glewOrthofOES) + +#define GLEW_OES_single_precision GLEW_GET_VAR(__GLEW_OES_single_precision) + +#endif /* GL_OES_single_precision */ + +/* ---------------------------- GL_OML_interlace --------------------------- */ + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 + +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 + +#define GLEW_OML_interlace GLEW_GET_VAR(__GLEW_OML_interlace) + +#endif /* GL_OML_interlace */ + +/* ---------------------------- GL_OML_resample ---------------------------- */ + +#ifndef GL_OML_resample +#define GL_OML_resample 1 + +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 + +#define GLEW_OML_resample GLEW_GET_VAR(__GLEW_OML_resample) + +#endif /* GL_OML_resample */ + +/* ---------------------------- GL_OML_subsample --------------------------- */ + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 + +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 + +#define GLEW_OML_subsample GLEW_GET_VAR(__GLEW_OML_subsample) + +#endif /* GL_OML_subsample */ + +/* --------------------------- GL_PGI_misc_hints --------------------------- */ + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 + +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 107000 +#define GL_CONSERVE_MEMORY_HINT_PGI 107005 +#define GL_RECLAIM_MEMORY_HINT_PGI 107006 +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 107010 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 107011 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 107012 +#define GL_ALWAYS_FAST_HINT_PGI 107020 +#define GL_ALWAYS_SOFT_HINT_PGI 107021 +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 107022 +#define GL_ALLOW_DRAW_WIN_HINT_PGI 107023 +#define GL_ALLOW_DRAW_FRG_HINT_PGI 107024 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 107025 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 107030 +#define GL_STRICT_LIGHTING_HINT_PGI 107031 +#define GL_STRICT_SCISSOR_HINT_PGI 107032 +#define GL_FULL_STIPPLE_HINT_PGI 107033 +#define GL_CLIP_NEAR_HINT_PGI 107040 +#define GL_CLIP_FAR_HINT_PGI 107041 +#define GL_WIDE_LINE_HINT_PGI 107042 +#define GL_BACK_NORMALS_HINT_PGI 107043 + +#define GLEW_PGI_misc_hints GLEW_GET_VAR(__GLEW_PGI_misc_hints) + +#endif /* GL_PGI_misc_hints */ + +/* -------------------------- GL_PGI_vertex_hints -------------------------- */ + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 + +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_VERTEX_DATA_HINT_PGI 107050 +#define GL_VERTEX_CONSISTENT_HINT_PGI 107051 +#define GL_MATERIAL_SIDE_HINT_PGI 107052 +#define GL_MAX_VERTEX_HINT_PGI 107053 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 + +#define GLEW_PGI_vertex_hints GLEW_GET_VAR(__GLEW_PGI_vertex_hints) + +#endif /* GL_PGI_vertex_hints */ + +/* ----------------------- GL_REND_screen_coordinates ---------------------- */ + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 + +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 + +#define GLEW_REND_screen_coordinates GLEW_GET_VAR(__GLEW_REND_screen_coordinates) + +#endif /* GL_REND_screen_coordinates */ + +/* ------------------------------- GL_S3_s3tc ------------------------------ */ + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 + +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#define GL_RGBA_DXT5_S3TC 0x83A4 +#define GL_RGBA4_DXT5_S3TC 0x83A5 + +#define GLEW_S3_s3tc GLEW_GET_VAR(__GLEW_S3_s3tc) + +#endif /* GL_S3_s3tc */ + +/* -------------------------- GL_SGIS_color_range -------------------------- */ + +#ifndef GL_SGIS_color_range +#define GL_SGIS_color_range 1 + +#define GL_EXTENDED_RANGE_SGIS 0x85A5 +#define GL_MIN_RED_SGIS 0x85A6 +#define GL_MAX_RED_SGIS 0x85A7 +#define GL_MIN_GREEN_SGIS 0x85A8 +#define GL_MAX_GREEN_SGIS 0x85A9 +#define GL_MIN_BLUE_SGIS 0x85AA +#define GL_MAX_BLUE_SGIS 0x85AB +#define GL_MIN_ALPHA_SGIS 0x85AC +#define GL_MAX_ALPHA_SGIS 0x85AD + +#define GLEW_SGIS_color_range GLEW_GET_VAR(__GLEW_SGIS_color_range) + +#endif /* GL_SGIS_color_range */ + +/* ------------------------- GL_SGIS_detail_texture ------------------------ */ + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 + +typedef void (GLAPIENTRY * PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat* points); +typedef void (GLAPIENTRY * PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat* points); + +#define glDetailTexFuncSGIS GLEW_GET_FUN(__glewDetailTexFuncSGIS) +#define glGetDetailTexFuncSGIS GLEW_GET_FUN(__glewGetDetailTexFuncSGIS) + +#define GLEW_SGIS_detail_texture GLEW_GET_VAR(__GLEW_SGIS_detail_texture) + +#endif /* GL_SGIS_detail_texture */ + +/* -------------------------- GL_SGIS_fog_function ------------------------- */ + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 + +typedef void (GLAPIENTRY * PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat* points); +typedef void (GLAPIENTRY * PFNGLGETFOGFUNCSGISPROC) (GLfloat* points); + +#define glFogFuncSGIS GLEW_GET_FUN(__glewFogFuncSGIS) +#define glGetFogFuncSGIS GLEW_GET_FUN(__glewGetFogFuncSGIS) + +#define GLEW_SGIS_fog_function GLEW_GET_VAR(__GLEW_SGIS_fog_function) + +#endif /* GL_SGIS_fog_function */ + +/* ------------------------ GL_SGIS_generate_mipmap ------------------------ */ + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 + +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 + +#define GLEW_SGIS_generate_mipmap GLEW_GET_VAR(__GLEW_SGIS_generate_mipmap) + +#endif /* GL_SGIS_generate_mipmap */ + +/* -------------------------- GL_SGIS_multisample -------------------------- */ + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 + +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 + +typedef void (GLAPIENTRY * PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (GLAPIENTRY * PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); + +#define glSampleMaskSGIS GLEW_GET_FUN(__glewSampleMaskSGIS) +#define glSamplePatternSGIS GLEW_GET_FUN(__glewSamplePatternSGIS) + +#define GLEW_SGIS_multisample GLEW_GET_VAR(__GLEW_SGIS_multisample) + +#endif /* GL_SGIS_multisample */ + +/* ------------------------- GL_SGIS_pixel_texture ------------------------- */ + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 + +#define GLEW_SGIS_pixel_texture GLEW_GET_VAR(__GLEW_SGIS_pixel_texture) + +#endif /* GL_SGIS_pixel_texture */ + +/* ----------------------- GL_SGIS_point_line_texgen ----------------------- */ + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 + +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 + +#define GLEW_SGIS_point_line_texgen GLEW_GET_VAR(__GLEW_SGIS_point_line_texgen) + +#endif /* GL_SGIS_point_line_texgen */ + +/* ------------------------ GL_SGIS_sharpen_texture ------------------------ */ + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 + +typedef void (GLAPIENTRY * PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat* points); +typedef void (GLAPIENTRY * PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat* points); + +#define glGetSharpenTexFuncSGIS GLEW_GET_FUN(__glewGetSharpenTexFuncSGIS) +#define glSharpenTexFuncSGIS GLEW_GET_FUN(__glewSharpenTexFuncSGIS) + +#define GLEW_SGIS_sharpen_texture GLEW_GET_VAR(__GLEW_SGIS_sharpen_texture) + +#endif /* GL_SGIS_sharpen_texture */ + +/* --------------------------- GL_SGIS_texture4D --------------------------- */ + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 + +typedef void (GLAPIENTRY * PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei extent, GLint border, GLenum format, GLenum type, const void* pixels); +typedef void (GLAPIENTRY * PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei extent, GLenum format, GLenum type, const void* pixels); + +#define glTexImage4DSGIS GLEW_GET_FUN(__glewTexImage4DSGIS) +#define glTexSubImage4DSGIS GLEW_GET_FUN(__glewTexSubImage4DSGIS) + +#define GLEW_SGIS_texture4D GLEW_GET_VAR(__GLEW_SGIS_texture4D) + +#endif /* GL_SGIS_texture4D */ + +/* ---------------------- GL_SGIS_texture_border_clamp --------------------- */ + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 + +#define GL_CLAMP_TO_BORDER_SGIS 0x812D + +#define GLEW_SGIS_texture_border_clamp GLEW_GET_VAR(__GLEW_SGIS_texture_border_clamp) + +#endif /* GL_SGIS_texture_border_clamp */ + +/* ----------------------- GL_SGIS_texture_edge_clamp ---------------------- */ + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 + +#define GL_CLAMP_TO_EDGE_SGIS 0x812F + +#define GLEW_SGIS_texture_edge_clamp GLEW_GET_VAR(__GLEW_SGIS_texture_edge_clamp) + +#endif /* GL_SGIS_texture_edge_clamp */ + +/* ------------------------ GL_SGIS_texture_filter4 ------------------------ */ + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 + +typedef void (GLAPIENTRY * PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat* weights); +typedef void (GLAPIENTRY * PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat* weights); + +#define glGetTexFilterFuncSGIS GLEW_GET_FUN(__glewGetTexFilterFuncSGIS) +#define glTexFilterFuncSGIS GLEW_GET_FUN(__glewTexFilterFuncSGIS) + +#define GLEW_SGIS_texture_filter4 GLEW_GET_VAR(__GLEW_SGIS_texture_filter4) + +#endif /* GL_SGIS_texture_filter4 */ + +/* -------------------------- GL_SGIS_texture_lod -------------------------- */ + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 + +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D + +#define GLEW_SGIS_texture_lod GLEW_GET_VAR(__GLEW_SGIS_texture_lod) + +#endif /* GL_SGIS_texture_lod */ + +/* ------------------------- GL_SGIS_texture_select ------------------------ */ + +#ifndef GL_SGIS_texture_select +#define GL_SGIS_texture_select 1 + +#define GLEW_SGIS_texture_select GLEW_GET_VAR(__GLEW_SGIS_texture_select) + +#endif /* GL_SGIS_texture_select */ + +/* ----------------------------- GL_SGIX_async ----------------------------- */ + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 + +#define GL_ASYNC_MARKER_SGIX 0x8329 + +typedef void (GLAPIENTRY * PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef void (GLAPIENTRY * PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLint (GLAPIENTRY * PFNGLFINISHASYNCSGIXPROC) (GLuint* markerp); +typedef GLuint (GLAPIENTRY * PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef GLboolean (GLAPIENTRY * PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (GLAPIENTRY * PFNGLPOLLASYNCSGIXPROC) (GLuint* markerp); + +#define glAsyncMarkerSGIX GLEW_GET_FUN(__glewAsyncMarkerSGIX) +#define glDeleteAsyncMarkersSGIX GLEW_GET_FUN(__glewDeleteAsyncMarkersSGIX) +#define glFinishAsyncSGIX GLEW_GET_FUN(__glewFinishAsyncSGIX) +#define glGenAsyncMarkersSGIX GLEW_GET_FUN(__glewGenAsyncMarkersSGIX) +#define glIsAsyncMarkerSGIX GLEW_GET_FUN(__glewIsAsyncMarkerSGIX) +#define glPollAsyncSGIX GLEW_GET_FUN(__glewPollAsyncSGIX) + +#define GLEW_SGIX_async GLEW_GET_VAR(__GLEW_SGIX_async) + +#endif /* GL_SGIX_async */ + +/* ------------------------ GL_SGIX_async_histogram ------------------------ */ + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 + +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D + +#define GLEW_SGIX_async_histogram GLEW_GET_VAR(__GLEW_SGIX_async_histogram) + +#endif /* GL_SGIX_async_histogram */ + +/* -------------------------- GL_SGIX_async_pixel -------------------------- */ + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 + +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 + +#define GLEW_SGIX_async_pixel GLEW_GET_VAR(__GLEW_SGIX_async_pixel) + +#endif /* GL_SGIX_async_pixel */ + +/* ----------------------- GL_SGIX_blend_alpha_minmax ---------------------- */ + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 + +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 + +#define GLEW_SGIX_blend_alpha_minmax GLEW_GET_VAR(__GLEW_SGIX_blend_alpha_minmax) + +#endif /* GL_SGIX_blend_alpha_minmax */ + +/* ---------------------------- GL_SGIX_clipmap ---------------------------- */ + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 + +#define GLEW_SGIX_clipmap GLEW_GET_VAR(__GLEW_SGIX_clipmap) + +#endif /* GL_SGIX_clipmap */ + +/* ---------------------- GL_SGIX_convolution_accuracy --------------------- */ + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 + +#define GL_CONVOLUTION_HINT_SGIX 0x8316 + +#define GLEW_SGIX_convolution_accuracy GLEW_GET_VAR(__GLEW_SGIX_convolution_accuracy) + +#endif /* GL_SGIX_convolution_accuracy */ + +/* ------------------------- GL_SGIX_depth_texture ------------------------- */ + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 + +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 + +#define GLEW_SGIX_depth_texture GLEW_GET_VAR(__GLEW_SGIX_depth_texture) + +#endif /* GL_SGIX_depth_texture */ + +/* -------------------------- GL_SGIX_flush_raster ------------------------- */ + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 + +typedef void (GLAPIENTRY * PFNGLFLUSHRASTERSGIXPROC) (void); + +#define glFlushRasterSGIX GLEW_GET_FUN(__glewFlushRasterSGIX) + +#define GLEW_SGIX_flush_raster GLEW_GET_VAR(__GLEW_SGIX_flush_raster) + +#endif /* GL_SGIX_flush_raster */ + +/* --------------------------- GL_SGIX_fog_offset -------------------------- */ + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 + +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 + +#define GLEW_SGIX_fog_offset GLEW_GET_VAR(__GLEW_SGIX_fog_offset) + +#endif /* GL_SGIX_fog_offset */ + +/* -------------------------- GL_SGIX_fog_texture -------------------------- */ + +#ifndef GL_SGIX_fog_texture +#define GL_SGIX_fog_texture 1 + +#define GL_TEXTURE_FOG_SGIX 0 +#define GL_FOG_PATCHY_FACTOR_SGIX 0 +#define GL_FRAGMENT_FOG_SGIX 0 + +typedef void (GLAPIENTRY * PFNGLTEXTUREFOGSGIXPROC) (GLenum pname); + +#define glTextureFogSGIX GLEW_GET_FUN(__glewTextureFogSGIX) + +#define GLEW_SGIX_fog_texture GLEW_GET_VAR(__GLEW_SGIX_fog_texture) + +#endif /* GL_SGIX_fog_texture */ + +/* ------------------- GL_SGIX_fragment_specular_lighting ------------------ */ + +#ifndef GL_SGIX_fragment_specular_lighting +#define GL_SGIX_fragment_specular_lighting 1 + +typedef void (GLAPIENTRY * PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, const GLfloat param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, const GLint param); +typedef void (GLAPIENTRY * PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum value, GLfloat* data); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum value, GLint* data); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat* data); +typedef void (GLAPIENTRY * PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint* data); + +#define glFragmentColorMaterialSGIX GLEW_GET_FUN(__glewFragmentColorMaterialSGIX) +#define glFragmentLightModelfSGIX GLEW_GET_FUN(__glewFragmentLightModelfSGIX) +#define glFragmentLightModelfvSGIX GLEW_GET_FUN(__glewFragmentLightModelfvSGIX) +#define glFragmentLightModeliSGIX GLEW_GET_FUN(__glewFragmentLightModeliSGIX) +#define glFragmentLightModelivSGIX GLEW_GET_FUN(__glewFragmentLightModelivSGIX) +#define glFragmentLightfSGIX GLEW_GET_FUN(__glewFragmentLightfSGIX) +#define glFragmentLightfvSGIX GLEW_GET_FUN(__glewFragmentLightfvSGIX) +#define glFragmentLightiSGIX GLEW_GET_FUN(__glewFragmentLightiSGIX) +#define glFragmentLightivSGIX GLEW_GET_FUN(__glewFragmentLightivSGIX) +#define glFragmentMaterialfSGIX GLEW_GET_FUN(__glewFragmentMaterialfSGIX) +#define glFragmentMaterialfvSGIX GLEW_GET_FUN(__glewFragmentMaterialfvSGIX) +#define glFragmentMaterialiSGIX GLEW_GET_FUN(__glewFragmentMaterialiSGIX) +#define glFragmentMaterialivSGIX GLEW_GET_FUN(__glewFragmentMaterialivSGIX) +#define glGetFragmentLightfvSGIX GLEW_GET_FUN(__glewGetFragmentLightfvSGIX) +#define glGetFragmentLightivSGIX GLEW_GET_FUN(__glewGetFragmentLightivSGIX) +#define glGetFragmentMaterialfvSGIX GLEW_GET_FUN(__glewGetFragmentMaterialfvSGIX) +#define glGetFragmentMaterialivSGIX GLEW_GET_FUN(__glewGetFragmentMaterialivSGIX) + +#define GLEW_SGIX_fragment_specular_lighting GLEW_GET_VAR(__GLEW_SGIX_fragment_specular_lighting) + +#endif /* GL_SGIX_fragment_specular_lighting */ + +/* --------------------------- GL_SGIX_framezoom --------------------------- */ + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 + +typedef void (GLAPIENTRY * PFNGLFRAMEZOOMSGIXPROC) (GLint factor); + +#define glFrameZoomSGIX GLEW_GET_FUN(__glewFrameZoomSGIX) + +#define GLEW_SGIX_framezoom GLEW_GET_VAR(__GLEW_SGIX_framezoom) + +#endif /* GL_SGIX_framezoom */ + +/* --------------------------- GL_SGIX_interlace --------------------------- */ + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 + +#define GL_INTERLACE_SGIX 0x8094 + +#define GLEW_SGIX_interlace GLEW_GET_VAR(__GLEW_SGIX_interlace) + +#endif /* GL_SGIX_interlace */ + +/* ------------------------- GL_SGIX_ir_instrument1 ------------------------ */ + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 + +#define GLEW_SGIX_ir_instrument1 GLEW_GET_VAR(__GLEW_SGIX_ir_instrument1) + +#endif /* GL_SGIX_ir_instrument1 */ + +/* ------------------------- GL_SGIX_list_priority ------------------------- */ + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 + +#define GLEW_SGIX_list_priority GLEW_GET_VAR(__GLEW_SGIX_list_priority) + +#endif /* GL_SGIX_list_priority */ + +/* ------------------------- GL_SGIX_pixel_texture ------------------------- */ + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 + +typedef void (GLAPIENTRY * PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); + +#define glPixelTexGenSGIX GLEW_GET_FUN(__glewPixelTexGenSGIX) + +#define GLEW_SGIX_pixel_texture GLEW_GET_VAR(__GLEW_SGIX_pixel_texture) + +#endif /* GL_SGIX_pixel_texture */ + +/* ----------------------- GL_SGIX_pixel_texture_bits ---------------------- */ + +#ifndef GL_SGIX_pixel_texture_bits +#define GL_SGIX_pixel_texture_bits 1 + +#define GLEW_SGIX_pixel_texture_bits GLEW_GET_VAR(__GLEW_SGIX_pixel_texture_bits) + +#endif /* GL_SGIX_pixel_texture_bits */ + +/* ------------------------ GL_SGIX_reference_plane ------------------------ */ + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 + +typedef void (GLAPIENTRY * PFNGLREFERENCEPLANESGIXPROC) (const GLdouble* equation); + +#define glReferencePlaneSGIX GLEW_GET_FUN(__glewReferencePlaneSGIX) + +#define GLEW_SGIX_reference_plane GLEW_GET_VAR(__GLEW_SGIX_reference_plane) + +#endif /* GL_SGIX_reference_plane */ + +/* ---------------------------- GL_SGIX_resample --------------------------- */ + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 + +#define GL_PACK_RESAMPLE_SGIX 0x842E +#define GL_UNPACK_RESAMPLE_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#define GL_RESAMPLE_REPLICATE_SGIX 0x8433 +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x8434 + +#define GLEW_SGIX_resample GLEW_GET_VAR(__GLEW_SGIX_resample) + +#endif /* GL_SGIX_resample */ + +/* ----------------------------- GL_SGIX_shadow ---------------------------- */ + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 + +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D + +#define GLEW_SGIX_shadow GLEW_GET_VAR(__GLEW_SGIX_shadow) + +#endif /* GL_SGIX_shadow */ + +/* ------------------------- GL_SGIX_shadow_ambient ------------------------ */ + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 + +#define GL_SHADOW_AMBIENT_SGIX 0x80BF + +#define GLEW_SGIX_shadow_ambient GLEW_GET_VAR(__GLEW_SGIX_shadow_ambient) + +#endif /* GL_SGIX_shadow_ambient */ + +/* ----------------------------- GL_SGIX_sprite ---------------------------- */ + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 + +typedef void (GLAPIENTRY * PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (GLAPIENTRY * PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (GLAPIENTRY * PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, GLint* params); + +#define glSpriteParameterfSGIX GLEW_GET_FUN(__glewSpriteParameterfSGIX) +#define glSpriteParameterfvSGIX GLEW_GET_FUN(__glewSpriteParameterfvSGIX) +#define glSpriteParameteriSGIX GLEW_GET_FUN(__glewSpriteParameteriSGIX) +#define glSpriteParameterivSGIX GLEW_GET_FUN(__glewSpriteParameterivSGIX) + +#define GLEW_SGIX_sprite GLEW_GET_VAR(__GLEW_SGIX_sprite) + +#endif /* GL_SGIX_sprite */ + +/* ----------------------- GL_SGIX_tag_sample_buffer ----------------------- */ + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 + +typedef void (GLAPIENTRY * PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); + +#define glTagSampleBufferSGIX GLEW_GET_FUN(__glewTagSampleBufferSGIX) + +#define GLEW_SGIX_tag_sample_buffer GLEW_GET_VAR(__GLEW_SGIX_tag_sample_buffer) + +#endif /* GL_SGIX_tag_sample_buffer */ + +/* ------------------------ GL_SGIX_texture_add_env ------------------------ */ + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 + +#define GLEW_SGIX_texture_add_env GLEW_GET_VAR(__GLEW_SGIX_texture_add_env) + +#endif /* GL_SGIX_texture_add_env */ + +/* -------------------- GL_SGIX_texture_coordinate_clamp ------------------- */ + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 + +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B + +#define GLEW_SGIX_texture_coordinate_clamp GLEW_GET_VAR(__GLEW_SGIX_texture_coordinate_clamp) + +#endif /* GL_SGIX_texture_coordinate_clamp */ + +/* ------------------------ GL_SGIX_texture_lod_bias ----------------------- */ + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 + +#define GLEW_SGIX_texture_lod_bias GLEW_GET_VAR(__GLEW_SGIX_texture_lod_bias) + +#endif /* GL_SGIX_texture_lod_bias */ + +/* ---------------------- GL_SGIX_texture_multi_buffer --------------------- */ + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 + +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E + +#define GLEW_SGIX_texture_multi_buffer GLEW_GET_VAR(__GLEW_SGIX_texture_multi_buffer) + +#endif /* GL_SGIX_texture_multi_buffer */ + +/* ------------------------- GL_SGIX_texture_range ------------------------- */ + +#ifndef GL_SGIX_texture_range +#define GL_SGIX_texture_range 1 + +#define GL_RGB_SIGNED_SGIX 0x85E0 +#define GL_RGBA_SIGNED_SGIX 0x85E1 +#define GL_ALPHA_SIGNED_SGIX 0x85E2 +#define GL_LUMINANCE_SIGNED_SGIX 0x85E3 +#define GL_INTENSITY_SIGNED_SGIX 0x85E4 +#define GL_LUMINANCE_ALPHA_SIGNED_SGIX 0x85E5 +#define GL_RGB16_SIGNED_SGIX 0x85E6 +#define GL_RGBA16_SIGNED_SGIX 0x85E7 +#define GL_ALPHA16_SIGNED_SGIX 0x85E8 +#define GL_LUMINANCE16_SIGNED_SGIX 0x85E9 +#define GL_INTENSITY16_SIGNED_SGIX 0x85EA +#define GL_LUMINANCE16_ALPHA16_SIGNED_SGIX 0x85EB +#define GL_RGB_EXTENDED_RANGE_SGIX 0x85EC +#define GL_RGBA_EXTENDED_RANGE_SGIX 0x85ED +#define GL_ALPHA_EXTENDED_RANGE_SGIX 0x85EE +#define GL_LUMINANCE_EXTENDED_RANGE_SGIX 0x85EF +#define GL_INTENSITY_EXTENDED_RANGE_SGIX 0x85F0 +#define GL_LUMINANCE_ALPHA_EXTENDED_RANGE_SGIX 0x85F1 +#define GL_RGB16_EXTENDED_RANGE_SGIX 0x85F2 +#define GL_RGBA16_EXTENDED_RANGE_SGIX 0x85F3 +#define GL_ALPHA16_EXTENDED_RANGE_SGIX 0x85F4 +#define GL_LUMINANCE16_EXTENDED_RANGE_SGIX 0x85F5 +#define GL_INTENSITY16_EXTENDED_RANGE_SGIX 0x85F6 +#define GL_LUMINANCE16_ALPHA16_EXTENDED_RANGE_SGIX 0x85F7 +#define GL_MIN_LUMINANCE_SGIS 0x85F8 +#define GL_MAX_LUMINANCE_SGIS 0x85F9 +#define GL_MIN_INTENSITY_SGIS 0x85FA +#define GL_MAX_INTENSITY_SGIS 0x85FB + +#define GLEW_SGIX_texture_range GLEW_GET_VAR(__GLEW_SGIX_texture_range) + +#endif /* GL_SGIX_texture_range */ + +/* ----------------------- GL_SGIX_texture_scale_bias ---------------------- */ + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 + +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C + +#define GLEW_SGIX_texture_scale_bias GLEW_GET_VAR(__GLEW_SGIX_texture_scale_bias) + +#endif /* GL_SGIX_texture_scale_bias */ + +/* ------------------------- GL_SGIX_vertex_preclip ------------------------ */ + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 + +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF + +#define GLEW_SGIX_vertex_preclip GLEW_GET_VAR(__GLEW_SGIX_vertex_preclip) + +#endif /* GL_SGIX_vertex_preclip */ + +/* ---------------------- GL_SGIX_vertex_preclip_hint ---------------------- */ + +#ifndef GL_SGIX_vertex_preclip_hint +#define GL_SGIX_vertex_preclip_hint 1 + +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF + +#define GLEW_SGIX_vertex_preclip_hint GLEW_GET_VAR(__GLEW_SGIX_vertex_preclip_hint) + +#endif /* GL_SGIX_vertex_preclip_hint */ + +/* ----------------------------- GL_SGIX_ycrcb ----------------------------- */ + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 + +#define GLEW_SGIX_ycrcb GLEW_GET_VAR(__GLEW_SGIX_ycrcb) + +#endif /* GL_SGIX_ycrcb */ + +/* -------------------------- GL_SGI_color_matrix -------------------------- */ + +#ifndef GL_SGI_color_matrix +#define GL_SGI_color_matrix 1 + +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB + +#define GLEW_SGI_color_matrix GLEW_GET_VAR(__GLEW_SGI_color_matrix) + +#endif /* GL_SGI_color_matrix */ + +/* --------------------------- GL_SGI_color_table -------------------------- */ + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 + +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF + +typedef void (GLAPIENTRY * PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat* params); +typedef void (GLAPIENTRY * PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint* params); +typedef void (GLAPIENTRY * PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void* table); +typedef void (GLAPIENTRY * PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat* params); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint* params); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, void* table); + +#define glColorTableParameterfvSGI GLEW_GET_FUN(__glewColorTableParameterfvSGI) +#define glColorTableParameterivSGI GLEW_GET_FUN(__glewColorTableParameterivSGI) +#define glColorTableSGI GLEW_GET_FUN(__glewColorTableSGI) +#define glCopyColorTableSGI GLEW_GET_FUN(__glewCopyColorTableSGI) +#define glGetColorTableParameterfvSGI GLEW_GET_FUN(__glewGetColorTableParameterfvSGI) +#define glGetColorTableParameterivSGI GLEW_GET_FUN(__glewGetColorTableParameterivSGI) +#define glGetColorTableSGI GLEW_GET_FUN(__glewGetColorTableSGI) + +#define GLEW_SGI_color_table GLEW_GET_VAR(__GLEW_SGI_color_table) + +#endif /* GL_SGI_color_table */ + +/* ----------------------- GL_SGI_texture_color_table ---------------------- */ + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 + +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD + +#define GLEW_SGI_texture_color_table GLEW_GET_VAR(__GLEW_SGI_texture_color_table) + +#endif /* GL_SGI_texture_color_table */ + +/* ------------------------- GL_SUNX_constant_data ------------------------- */ + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 + +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 + +typedef void (GLAPIENTRY * PFNGLFINISHTEXTURESUNXPROC) (void); + +#define glFinishTextureSUNX GLEW_GET_FUN(__glewFinishTextureSUNX) + +#define GLEW_SUNX_constant_data GLEW_GET_VAR(__GLEW_SUNX_constant_data) + +#endif /* GL_SUNX_constant_data */ + +/* -------------------- GL_SUN_convolution_border_modes -------------------- */ + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 + +#define GL_WRAP_BORDER_SUN 0x81D4 + +#define GLEW_SUN_convolution_border_modes GLEW_GET_VAR(__GLEW_SUN_convolution_border_modes) + +#endif /* GL_SUN_convolution_border_modes */ + +/* -------------------------- GL_SUN_global_alpha -------------------------- */ + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 + +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA + +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +typedef void (GLAPIENTRY * PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); + +#define glGlobalAlphaFactorbSUN GLEW_GET_FUN(__glewGlobalAlphaFactorbSUN) +#define glGlobalAlphaFactordSUN GLEW_GET_FUN(__glewGlobalAlphaFactordSUN) +#define glGlobalAlphaFactorfSUN GLEW_GET_FUN(__glewGlobalAlphaFactorfSUN) +#define glGlobalAlphaFactoriSUN GLEW_GET_FUN(__glewGlobalAlphaFactoriSUN) +#define glGlobalAlphaFactorsSUN GLEW_GET_FUN(__glewGlobalAlphaFactorsSUN) +#define glGlobalAlphaFactorubSUN GLEW_GET_FUN(__glewGlobalAlphaFactorubSUN) +#define glGlobalAlphaFactoruiSUN GLEW_GET_FUN(__glewGlobalAlphaFactoruiSUN) +#define glGlobalAlphaFactorusSUN GLEW_GET_FUN(__glewGlobalAlphaFactorusSUN) + +#define GLEW_SUN_global_alpha GLEW_GET_VAR(__GLEW_SUN_global_alpha) + +#endif /* GL_SUN_global_alpha */ + +/* --------------------------- GL_SUN_mesh_array --------------------------- */ + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 + +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 + +#define GLEW_SUN_mesh_array GLEW_GET_VAR(__GLEW_SUN_mesh_array) + +#endif /* GL_SUN_mesh_array */ + +/* ------------------------ GL_SUN_read_video_pixels ----------------------- */ + +#ifndef GL_SUN_read_video_pixels +#define GL_SUN_read_video_pixels 1 + +typedef void (GLAPIENTRY * PFNGLREADVIDEOPIXELSSUNPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels); + +#define glReadVideoPixelsSUN GLEW_GET_FUN(__glewReadVideoPixelsSUN) + +#define GLEW_SUN_read_video_pixels GLEW_GET_VAR(__GLEW_SUN_read_video_pixels) + +#endif /* GL_SUN_read_video_pixels */ + +/* --------------------------- GL_SUN_slice_accum -------------------------- */ + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 + +#define GL_SLICE_ACCUM_SUN 0x85CC + +#define GLEW_SUN_slice_accum GLEW_GET_VAR(__GLEW_SUN_slice_accum) + +#endif /* GL_SUN_slice_accum */ + +/* -------------------------- GL_SUN_triangle_list ------------------------- */ + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 + +#define GL_RESTART_SUN 0x01 +#define GL_REPLACE_MIDDLE_SUN 0x02 +#define GL_REPLACE_OLDEST_SUN 0x03 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB + +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const void* pointer); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte* code); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint* code); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort* code); + +#define glReplacementCodePointerSUN GLEW_GET_FUN(__glewReplacementCodePointerSUN) +#define glReplacementCodeubSUN GLEW_GET_FUN(__glewReplacementCodeubSUN) +#define glReplacementCodeubvSUN GLEW_GET_FUN(__glewReplacementCodeubvSUN) +#define glReplacementCodeuiSUN GLEW_GET_FUN(__glewReplacementCodeuiSUN) +#define glReplacementCodeuivSUN GLEW_GET_FUN(__glewReplacementCodeuivSUN) +#define glReplacementCodeusSUN GLEW_GET_FUN(__glewReplacementCodeusSUN) +#define glReplacementCodeusvSUN GLEW_GET_FUN(__glewReplacementCodeusvSUN) + +#define GLEW_SUN_triangle_list GLEW_GET_VAR(__GLEW_SUN_triangle_list) + +#endif /* GL_SUN_triangle_list */ + +/* ----------------------------- GL_SUN_vertex ----------------------------- */ + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 + +typedef void (GLAPIENTRY * PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat* c, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat* c, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (GLAPIENTRY * PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte* c, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte* c, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat* n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint* rc, const GLfloat *c, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint* rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint* rc, const GLubyte *c, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint* rc, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint* rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint* rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint* rc, const GLfloat *tc, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint* rc, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat* tc, const GLfloat *c, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat* tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat* tc, const GLubyte *c, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat* tc, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (GLAPIENTRY * PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat* tc, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat* tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (GLAPIENTRY * PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (GLAPIENTRY * PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat* tc, const GLfloat *v); + +#define glColor3fVertex3fSUN GLEW_GET_FUN(__glewColor3fVertex3fSUN) +#define glColor3fVertex3fvSUN GLEW_GET_FUN(__glewColor3fVertex3fvSUN) +#define glColor4fNormal3fVertex3fSUN GLEW_GET_FUN(__glewColor4fNormal3fVertex3fSUN) +#define glColor4fNormal3fVertex3fvSUN GLEW_GET_FUN(__glewColor4fNormal3fVertex3fvSUN) +#define glColor4ubVertex2fSUN GLEW_GET_FUN(__glewColor4ubVertex2fSUN) +#define glColor4ubVertex2fvSUN GLEW_GET_FUN(__glewColor4ubVertex2fvSUN) +#define glColor4ubVertex3fSUN GLEW_GET_FUN(__glewColor4ubVertex3fSUN) +#define glColor4ubVertex3fvSUN GLEW_GET_FUN(__glewColor4ubVertex3fvSUN) +#define glNormal3fVertex3fSUN GLEW_GET_FUN(__glewNormal3fVertex3fSUN) +#define glNormal3fVertex3fvSUN GLEW_GET_FUN(__glewNormal3fVertex3fvSUN) +#define glReplacementCodeuiColor3fVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiColor3fVertex3fSUN) +#define glReplacementCodeuiColor3fVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiColor3fVertex3fvSUN) +#define glReplacementCodeuiColor4fNormal3fVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiColor4fNormal3fVertex3fSUN) +#define glReplacementCodeuiColor4fNormal3fVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiColor4fNormal3fVertex3fvSUN) +#define glReplacementCodeuiColor4ubVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiColor4ubVertex3fSUN) +#define glReplacementCodeuiColor4ubVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiColor4ubVertex3fvSUN) +#define glReplacementCodeuiNormal3fVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiNormal3fVertex3fSUN) +#define glReplacementCodeuiNormal3fVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiNormal3fVertex3fvSUN) +#define glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN) +#define glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN) +#define glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiTexCoord2fNormal3fVertex3fSUN) +#define glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN) +#define glReplacementCodeuiTexCoord2fVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiTexCoord2fVertex3fSUN) +#define glReplacementCodeuiTexCoord2fVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiTexCoord2fVertex3fvSUN) +#define glReplacementCodeuiVertex3fSUN GLEW_GET_FUN(__glewReplacementCodeuiVertex3fSUN) +#define glReplacementCodeuiVertex3fvSUN GLEW_GET_FUN(__glewReplacementCodeuiVertex3fvSUN) +#define glTexCoord2fColor3fVertex3fSUN GLEW_GET_FUN(__glewTexCoord2fColor3fVertex3fSUN) +#define glTexCoord2fColor3fVertex3fvSUN GLEW_GET_FUN(__glewTexCoord2fColor3fVertex3fvSUN) +#define glTexCoord2fColor4fNormal3fVertex3fSUN GLEW_GET_FUN(__glewTexCoord2fColor4fNormal3fVertex3fSUN) +#define glTexCoord2fColor4fNormal3fVertex3fvSUN GLEW_GET_FUN(__glewTexCoord2fColor4fNormal3fVertex3fvSUN) +#define glTexCoord2fColor4ubVertex3fSUN GLEW_GET_FUN(__glewTexCoord2fColor4ubVertex3fSUN) +#define glTexCoord2fColor4ubVertex3fvSUN GLEW_GET_FUN(__glewTexCoord2fColor4ubVertex3fvSUN) +#define glTexCoord2fNormal3fVertex3fSUN GLEW_GET_FUN(__glewTexCoord2fNormal3fVertex3fSUN) +#define glTexCoord2fNormal3fVertex3fvSUN GLEW_GET_FUN(__glewTexCoord2fNormal3fVertex3fvSUN) +#define glTexCoord2fVertex3fSUN GLEW_GET_FUN(__glewTexCoord2fVertex3fSUN) +#define glTexCoord2fVertex3fvSUN GLEW_GET_FUN(__glewTexCoord2fVertex3fvSUN) +#define glTexCoord4fColor4fNormal3fVertex4fSUN GLEW_GET_FUN(__glewTexCoord4fColor4fNormal3fVertex4fSUN) +#define glTexCoord4fColor4fNormal3fVertex4fvSUN GLEW_GET_FUN(__glewTexCoord4fColor4fNormal3fVertex4fvSUN) +#define glTexCoord4fVertex4fSUN GLEW_GET_FUN(__glewTexCoord4fVertex4fSUN) +#define glTexCoord4fVertex4fvSUN GLEW_GET_FUN(__glewTexCoord4fVertex4fvSUN) + +#define GLEW_SUN_vertex GLEW_GET_VAR(__GLEW_SUN_vertex) + +#endif /* GL_SUN_vertex */ + +/* -------------------------- GL_WIN_phong_shading ------------------------- */ + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 + +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB + +#define GLEW_WIN_phong_shading GLEW_GET_VAR(__GLEW_WIN_phong_shading) + +#endif /* GL_WIN_phong_shading */ + +/* -------------------------- GL_WIN_specular_fog -------------------------- */ + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 + +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC + +#define GLEW_WIN_specular_fog GLEW_GET_VAR(__GLEW_WIN_specular_fog) + +#endif /* GL_WIN_specular_fog */ + +/* ---------------------------- GL_WIN_swap_hint --------------------------- */ + +#ifndef GL_WIN_swap_hint +#define GL_WIN_swap_hint 1 + +typedef void (GLAPIENTRY * PFNGLADDSWAPHINTRECTWINPROC) (GLint x, GLint y, GLsizei width, GLsizei height); + +#define glAddSwapHintRectWIN GLEW_GET_FUN(__glewAddSwapHintRectWIN) + +#define GLEW_WIN_swap_hint GLEW_GET_VAR(__GLEW_WIN_swap_hint) + +#endif /* GL_WIN_swap_hint */ + +/* ------------------------------------------------------------------------- */ + +#if defined(GLEW_MX) && defined(_WIN32) +#define GLEW_FUN_EXPORT +#else +#define GLEW_FUN_EXPORT GLEWAPI +#endif /* GLEW_MX */ + +#if defined(GLEW_MX) +#define GLEW_VAR_EXPORT +#else +#define GLEW_VAR_EXPORT GLEWAPI +#endif /* GLEW_MX */ + +#if defined(GLEW_MX) && defined(_WIN32) +struct GLEWContextStruct +{ +#endif /* GLEW_MX */ + +GLEW_FUN_EXPORT PFNGLCOPYTEXSUBIMAGE3DPROC __glewCopyTexSubImage3D; +GLEW_FUN_EXPORT PFNGLDRAWRANGEELEMENTSPROC __glewDrawRangeElements; +GLEW_FUN_EXPORT PFNGLTEXIMAGE3DPROC __glewTexImage3D; +GLEW_FUN_EXPORT PFNGLTEXSUBIMAGE3DPROC __glewTexSubImage3D; + +GLEW_FUN_EXPORT PFNGLACTIVETEXTUREPROC __glewActiveTexture; +GLEW_FUN_EXPORT PFNGLCLIENTACTIVETEXTUREPROC __glewClientActiveTexture; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXIMAGE1DPROC __glewCompressedTexImage1D; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXIMAGE2DPROC __glewCompressedTexImage2D; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXIMAGE3DPROC __glewCompressedTexImage3D; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC __glewCompressedTexSubImage1D; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC __glewCompressedTexSubImage2D; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC __glewCompressedTexSubImage3D; +GLEW_FUN_EXPORT PFNGLGETCOMPRESSEDTEXIMAGEPROC __glewGetCompressedTexImage; +GLEW_FUN_EXPORT PFNGLLOADTRANSPOSEMATRIXDPROC __glewLoadTransposeMatrixd; +GLEW_FUN_EXPORT PFNGLLOADTRANSPOSEMATRIXFPROC __glewLoadTransposeMatrixf; +GLEW_FUN_EXPORT PFNGLMULTTRANSPOSEMATRIXDPROC __glewMultTransposeMatrixd; +GLEW_FUN_EXPORT PFNGLMULTTRANSPOSEMATRIXFPROC __glewMultTransposeMatrixf; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1DPROC __glewMultiTexCoord1d; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1DVPROC __glewMultiTexCoord1dv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1FPROC __glewMultiTexCoord1f; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1FVPROC __glewMultiTexCoord1fv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1IPROC __glewMultiTexCoord1i; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1IVPROC __glewMultiTexCoord1iv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1SPROC __glewMultiTexCoord1s; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1SVPROC __glewMultiTexCoord1sv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2DPROC __glewMultiTexCoord2d; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2DVPROC __glewMultiTexCoord2dv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2FPROC __glewMultiTexCoord2f; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2FVPROC __glewMultiTexCoord2fv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2IPROC __glewMultiTexCoord2i; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2IVPROC __glewMultiTexCoord2iv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2SPROC __glewMultiTexCoord2s; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2SVPROC __glewMultiTexCoord2sv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3DPROC __glewMultiTexCoord3d; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3DVPROC __glewMultiTexCoord3dv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3FPROC __glewMultiTexCoord3f; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3FVPROC __glewMultiTexCoord3fv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3IPROC __glewMultiTexCoord3i; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3IVPROC __glewMultiTexCoord3iv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3SPROC __glewMultiTexCoord3s; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3SVPROC __glewMultiTexCoord3sv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4DPROC __glewMultiTexCoord4d; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4DVPROC __glewMultiTexCoord4dv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4FPROC __glewMultiTexCoord4f; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4FVPROC __glewMultiTexCoord4fv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4IPROC __glewMultiTexCoord4i; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4IVPROC __glewMultiTexCoord4iv; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4SPROC __glewMultiTexCoord4s; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4SVPROC __glewMultiTexCoord4sv; +GLEW_FUN_EXPORT PFNGLSAMPLECOVERAGEPROC __glewSampleCoverage; + +GLEW_FUN_EXPORT PFNGLBLENDCOLORPROC __glewBlendColor; +GLEW_FUN_EXPORT PFNGLBLENDEQUATIONPROC __glewBlendEquation; +GLEW_FUN_EXPORT PFNGLBLENDFUNCSEPARATEPROC __glewBlendFuncSeparate; +GLEW_FUN_EXPORT PFNGLFOGCOORDPOINTERPROC __glewFogCoordPointer; +GLEW_FUN_EXPORT PFNGLFOGCOORDDPROC __glewFogCoordd; +GLEW_FUN_EXPORT PFNGLFOGCOORDDVPROC __glewFogCoorddv; +GLEW_FUN_EXPORT PFNGLFOGCOORDFPROC __glewFogCoordf; +GLEW_FUN_EXPORT PFNGLFOGCOORDFVPROC __glewFogCoordfv; +GLEW_FUN_EXPORT PFNGLMULTIDRAWARRAYSPROC __glewMultiDrawArrays; +GLEW_FUN_EXPORT PFNGLMULTIDRAWELEMENTSPROC __glewMultiDrawElements; +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERFPROC __glewPointParameterf; +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERFVPROC __glewPointParameterfv; +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERIPROC __glewPointParameteri; +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERIVPROC __glewPointParameteriv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3BPROC __glewSecondaryColor3b; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3BVPROC __glewSecondaryColor3bv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3DPROC __glewSecondaryColor3d; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3DVPROC __glewSecondaryColor3dv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3FPROC __glewSecondaryColor3f; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3FVPROC __glewSecondaryColor3fv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3IPROC __glewSecondaryColor3i; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3IVPROC __glewSecondaryColor3iv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3SPROC __glewSecondaryColor3s; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3SVPROC __glewSecondaryColor3sv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UBPROC __glewSecondaryColor3ub; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UBVPROC __glewSecondaryColor3ubv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UIPROC __glewSecondaryColor3ui; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UIVPROC __glewSecondaryColor3uiv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3USPROC __glewSecondaryColor3us; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3USVPROC __glewSecondaryColor3usv; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLORPOINTERPROC __glewSecondaryColorPointer; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2DPROC __glewWindowPos2d; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2DVPROC __glewWindowPos2dv; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2FPROC __glewWindowPos2f; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2FVPROC __glewWindowPos2fv; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2IPROC __glewWindowPos2i; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2IVPROC __glewWindowPos2iv; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2SPROC __glewWindowPos2s; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2SVPROC __glewWindowPos2sv; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3DPROC __glewWindowPos3d; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3DVPROC __glewWindowPos3dv; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3FPROC __glewWindowPos3f; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3FVPROC __glewWindowPos3fv; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3IPROC __glewWindowPos3i; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3IVPROC __glewWindowPos3iv; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3SPROC __glewWindowPos3s; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3SVPROC __glewWindowPos3sv; + +GLEW_FUN_EXPORT PFNGLBEGINQUERYPROC __glewBeginQuery; +GLEW_FUN_EXPORT PFNGLBINDBUFFERPROC __glewBindBuffer; +GLEW_FUN_EXPORT PFNGLBUFFERDATAPROC __glewBufferData; +GLEW_FUN_EXPORT PFNGLBUFFERSUBDATAPROC __glewBufferSubData; +GLEW_FUN_EXPORT PFNGLDELETEBUFFERSPROC __glewDeleteBuffers; +GLEW_FUN_EXPORT PFNGLDELETEQUERIESPROC __glewDeleteQueries; +GLEW_FUN_EXPORT PFNGLENDQUERYPROC __glewEndQuery; +GLEW_FUN_EXPORT PFNGLGENBUFFERSPROC __glewGenBuffers; +GLEW_FUN_EXPORT PFNGLGENQUERIESPROC __glewGenQueries; +GLEW_FUN_EXPORT PFNGLGETBUFFERPARAMETERIVPROC __glewGetBufferParameteriv; +GLEW_FUN_EXPORT PFNGLGETBUFFERPOINTERVPROC __glewGetBufferPointerv; +GLEW_FUN_EXPORT PFNGLGETBUFFERSUBDATAPROC __glewGetBufferSubData; +GLEW_FUN_EXPORT PFNGLGETQUERYOBJECTIVPROC __glewGetQueryObjectiv; +GLEW_FUN_EXPORT PFNGLGETQUERYOBJECTUIVPROC __glewGetQueryObjectuiv; +GLEW_FUN_EXPORT PFNGLGETQUERYIVPROC __glewGetQueryiv; +GLEW_FUN_EXPORT PFNGLISBUFFERPROC __glewIsBuffer; +GLEW_FUN_EXPORT PFNGLISQUERYPROC __glewIsQuery; +GLEW_FUN_EXPORT PFNGLMAPBUFFERPROC __glewMapBuffer; +GLEW_FUN_EXPORT PFNGLUNMAPBUFFERPROC __glewUnmapBuffer; + +GLEW_FUN_EXPORT PFNGLATTACHSHADERPROC __glewAttachShader; +GLEW_FUN_EXPORT PFNGLBINDATTRIBLOCATIONPROC __glewBindAttribLocation; +GLEW_FUN_EXPORT PFNGLBLENDEQUATIONSEPARATEPROC __glewBlendEquationSeparate; +GLEW_FUN_EXPORT PFNGLCOMPILESHADERPROC __glewCompileShader; +GLEW_FUN_EXPORT PFNGLCREATEPROGRAMPROC __glewCreateProgram; +GLEW_FUN_EXPORT PFNGLCREATESHADERPROC __glewCreateShader; +GLEW_FUN_EXPORT PFNGLDELETEPROGRAMPROC __glewDeleteProgram; +GLEW_FUN_EXPORT PFNGLDELETESHADERPROC __glewDeleteShader; +GLEW_FUN_EXPORT PFNGLDETACHSHADERPROC __glewDetachShader; +GLEW_FUN_EXPORT PFNGLDISABLEVERTEXATTRIBARRAYPROC __glewDisableVertexAttribArray; +GLEW_FUN_EXPORT PFNGLDRAWBUFFERSPROC __glewDrawBuffers; +GLEW_FUN_EXPORT PFNGLENABLEVERTEXATTRIBARRAYPROC __glewEnableVertexAttribArray; +GLEW_FUN_EXPORT PFNGLGETACTIVEATTRIBPROC __glewGetActiveAttrib; +GLEW_FUN_EXPORT PFNGLGETACTIVEUNIFORMPROC __glewGetActiveUniform; +GLEW_FUN_EXPORT PFNGLGETATTACHEDSHADERSPROC __glewGetAttachedShaders; +GLEW_FUN_EXPORT PFNGLGETATTRIBLOCATIONPROC __glewGetAttribLocation; +GLEW_FUN_EXPORT PFNGLGETPROGRAMINFOLOGPROC __glewGetProgramInfoLog; +GLEW_FUN_EXPORT PFNGLGETPROGRAMIVPROC __glewGetProgramiv; +GLEW_FUN_EXPORT PFNGLGETSHADERINFOLOGPROC __glewGetShaderInfoLog; +GLEW_FUN_EXPORT PFNGLGETSHADERSOURCEPROC __glewGetShaderSource; +GLEW_FUN_EXPORT PFNGLGETSHADERIVPROC __glewGetShaderiv; +GLEW_FUN_EXPORT PFNGLGETUNIFORMLOCATIONPROC __glewGetUniformLocation; +GLEW_FUN_EXPORT PFNGLGETUNIFORMFVPROC __glewGetUniformfv; +GLEW_FUN_EXPORT PFNGLGETUNIFORMIVPROC __glewGetUniformiv; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBPOINTERVPROC __glewGetVertexAttribPointerv; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBDVPROC __glewGetVertexAttribdv; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBFVPROC __glewGetVertexAttribfv; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBIVPROC __glewGetVertexAttribiv; +GLEW_FUN_EXPORT PFNGLISPROGRAMPROC __glewIsProgram; +GLEW_FUN_EXPORT PFNGLISSHADERPROC __glewIsShader; +GLEW_FUN_EXPORT PFNGLLINKPROGRAMPROC __glewLinkProgram; +GLEW_FUN_EXPORT PFNGLSHADERSOURCEPROC __glewShaderSource; +GLEW_FUN_EXPORT PFNGLSTENCILFUNCSEPARATEPROC __glewStencilFuncSeparate; +GLEW_FUN_EXPORT PFNGLSTENCILMASKSEPARATEPROC __glewStencilMaskSeparate; +GLEW_FUN_EXPORT PFNGLSTENCILOPSEPARATEPROC __glewStencilOpSeparate; +GLEW_FUN_EXPORT PFNGLUNIFORM1FPROC __glewUniform1f; +GLEW_FUN_EXPORT PFNGLUNIFORM1FVPROC __glewUniform1fv; +GLEW_FUN_EXPORT PFNGLUNIFORM1IPROC __glewUniform1i; +GLEW_FUN_EXPORT PFNGLUNIFORM1IVPROC __glewUniform1iv; +GLEW_FUN_EXPORT PFNGLUNIFORM2FPROC __glewUniform2f; +GLEW_FUN_EXPORT PFNGLUNIFORM2FVPROC __glewUniform2fv; +GLEW_FUN_EXPORT PFNGLUNIFORM2IPROC __glewUniform2i; +GLEW_FUN_EXPORT PFNGLUNIFORM2IVPROC __glewUniform2iv; +GLEW_FUN_EXPORT PFNGLUNIFORM3FPROC __glewUniform3f; +GLEW_FUN_EXPORT PFNGLUNIFORM3FVPROC __glewUniform3fv; +GLEW_FUN_EXPORT PFNGLUNIFORM3IPROC __glewUniform3i; +GLEW_FUN_EXPORT PFNGLUNIFORM3IVPROC __glewUniform3iv; +GLEW_FUN_EXPORT PFNGLUNIFORM4FPROC __glewUniform4f; +GLEW_FUN_EXPORT PFNGLUNIFORM4FVPROC __glewUniform4fv; +GLEW_FUN_EXPORT PFNGLUNIFORM4IPROC __glewUniform4i; +GLEW_FUN_EXPORT PFNGLUNIFORM4IVPROC __glewUniform4iv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX2FVPROC __glewUniformMatrix2fv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX3FVPROC __glewUniformMatrix3fv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX4FVPROC __glewUniformMatrix4fv; +GLEW_FUN_EXPORT PFNGLUSEPROGRAMPROC __glewUseProgram; +GLEW_FUN_EXPORT PFNGLVALIDATEPROGRAMPROC __glewValidateProgram; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1DPROC __glewVertexAttrib1d; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1DVPROC __glewVertexAttrib1dv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1FPROC __glewVertexAttrib1f; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1FVPROC __glewVertexAttrib1fv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1SPROC __glewVertexAttrib1s; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1SVPROC __glewVertexAttrib1sv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2DPROC __glewVertexAttrib2d; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2DVPROC __glewVertexAttrib2dv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2FPROC __glewVertexAttrib2f; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2FVPROC __glewVertexAttrib2fv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2SPROC __glewVertexAttrib2s; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2SVPROC __glewVertexAttrib2sv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3DPROC __glewVertexAttrib3d; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3DVPROC __glewVertexAttrib3dv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3FPROC __glewVertexAttrib3f; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3FVPROC __glewVertexAttrib3fv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3SPROC __glewVertexAttrib3s; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3SVPROC __glewVertexAttrib3sv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NBVPROC __glewVertexAttrib4Nbv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NIVPROC __glewVertexAttrib4Niv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NSVPROC __glewVertexAttrib4Nsv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUBPROC __glewVertexAttrib4Nub; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUBVPROC __glewVertexAttrib4Nubv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUIVPROC __glewVertexAttrib4Nuiv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUSVPROC __glewVertexAttrib4Nusv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4BVPROC __glewVertexAttrib4bv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4DPROC __glewVertexAttrib4d; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4DVPROC __glewVertexAttrib4dv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4FPROC __glewVertexAttrib4f; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4FVPROC __glewVertexAttrib4fv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4IVPROC __glewVertexAttrib4iv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4SPROC __glewVertexAttrib4s; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4SVPROC __glewVertexAttrib4sv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4UBVPROC __glewVertexAttrib4ubv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4UIVPROC __glewVertexAttrib4uiv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4USVPROC __glewVertexAttrib4usv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBPOINTERPROC __glewVertexAttribPointer; + +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX2X3FVPROC __glewUniformMatrix2x3fv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX2X4FVPROC __glewUniformMatrix2x4fv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX3X2FVPROC __glewUniformMatrix3x2fv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX3X4FVPROC __glewUniformMatrix3x4fv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX4X2FVPROC __glewUniformMatrix4x2fv; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX4X3FVPROC __glewUniformMatrix4x3fv; + +GLEW_FUN_EXPORT PFNGLBEGINCONDITIONALRENDERPROC __glewBeginConditionalRender; +GLEW_FUN_EXPORT PFNGLBEGINTRANSFORMFEEDBACKPROC __glewBeginTransformFeedback; +GLEW_FUN_EXPORT PFNGLBINDBUFFERBASEPROC __glewBindBufferBase; +GLEW_FUN_EXPORT PFNGLBINDBUFFERRANGEPROC __glewBindBufferRange; +GLEW_FUN_EXPORT PFNGLBINDFRAGDATALOCATIONPROC __glewBindFragDataLocation; +GLEW_FUN_EXPORT PFNGLCLAMPCOLORPROC __glewClampColor; +GLEW_FUN_EXPORT PFNGLCLEARBUFFERFIPROC __glewClearBufferfi; +GLEW_FUN_EXPORT PFNGLCLEARBUFFERFVPROC __glewClearBufferfv; +GLEW_FUN_EXPORT PFNGLCLEARBUFFERIVPROC __glewClearBufferiv; +GLEW_FUN_EXPORT PFNGLCLEARBUFFERUIVPROC __glewClearBufferuiv; +GLEW_FUN_EXPORT PFNGLCOLORMASKIPROC __glewColorMaski; +GLEW_FUN_EXPORT PFNGLDISABLEIPROC __glewDisablei; +GLEW_FUN_EXPORT PFNGLENABLEIPROC __glewEnablei; +GLEW_FUN_EXPORT PFNGLENDCONDITIONALRENDERPROC __glewEndConditionalRender; +GLEW_FUN_EXPORT PFNGLENDTRANSFORMFEEDBACKPROC __glewEndTransformFeedback; +GLEW_FUN_EXPORT PFNGLGETBOOLEANI_VPROC __glewGetBooleani_v; +GLEW_FUN_EXPORT PFNGLGETFRAGDATALOCATIONPROC __glewGetFragDataLocation; +GLEW_FUN_EXPORT PFNGLGETINTEGERI_VPROC __glewGetIntegeri_v; +GLEW_FUN_EXPORT PFNGLGETSTRINGIPROC __glewGetStringi; +GLEW_FUN_EXPORT PFNGLGETTEXPARAMETERIIVPROC __glewGetTexParameterIiv; +GLEW_FUN_EXPORT PFNGLGETTEXPARAMETERIUIVPROC __glewGetTexParameterIuiv; +GLEW_FUN_EXPORT PFNGLGETTRANSFORMFEEDBACKVARYINGPROC __glewGetTransformFeedbackVarying; +GLEW_FUN_EXPORT PFNGLGETUNIFORMUIVPROC __glewGetUniformuiv; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBIIVPROC __glewGetVertexAttribIiv; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBIUIVPROC __glewGetVertexAttribIuiv; +GLEW_FUN_EXPORT PFNGLISENABLEDIPROC __glewIsEnabledi; +GLEW_FUN_EXPORT PFNGLTEXPARAMETERIIVPROC __glewTexParameterIiv; +GLEW_FUN_EXPORT PFNGLTEXPARAMETERIUIVPROC __glewTexParameterIuiv; +GLEW_FUN_EXPORT PFNGLTRANSFORMFEEDBACKVARYINGSPROC __glewTransformFeedbackVaryings; +GLEW_FUN_EXPORT PFNGLUNIFORM1UIPROC __glewUniform1ui; +GLEW_FUN_EXPORT PFNGLUNIFORM1UIVPROC __glewUniform1uiv; +GLEW_FUN_EXPORT PFNGLUNIFORM2UIPROC __glewUniform2ui; +GLEW_FUN_EXPORT PFNGLUNIFORM2UIVPROC __glewUniform2uiv; +GLEW_FUN_EXPORT PFNGLUNIFORM3UIPROC __glewUniform3ui; +GLEW_FUN_EXPORT PFNGLUNIFORM3UIVPROC __glewUniform3uiv; +GLEW_FUN_EXPORT PFNGLUNIFORM4UIPROC __glewUniform4ui; +GLEW_FUN_EXPORT PFNGLUNIFORM4UIVPROC __glewUniform4uiv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1IPROC __glewVertexAttribI1i; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1IVPROC __glewVertexAttribI1iv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1UIPROC __glewVertexAttribI1ui; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1UIVPROC __glewVertexAttribI1uiv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2IPROC __glewVertexAttribI2i; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2IVPROC __glewVertexAttribI2iv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2UIPROC __glewVertexAttribI2ui; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2UIVPROC __glewVertexAttribI2uiv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3IPROC __glewVertexAttribI3i; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3IVPROC __glewVertexAttribI3iv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3UIPROC __glewVertexAttribI3ui; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3UIVPROC __glewVertexAttribI3uiv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4BVPROC __glewVertexAttribI4bv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4IPROC __glewVertexAttribI4i; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4IVPROC __glewVertexAttribI4iv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4SVPROC __glewVertexAttribI4sv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4UBVPROC __glewVertexAttribI4ubv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4UIPROC __glewVertexAttribI4ui; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4UIVPROC __glewVertexAttribI4uiv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4USVPROC __glewVertexAttribI4usv; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBIPOINTERPROC __glewVertexAttribIPointer; + +GLEW_FUN_EXPORT PFNGLTBUFFERMASK3DFXPROC __glewTbufferMask3DFX; + +GLEW_FUN_EXPORT PFNGLDRAWELEMENTARRAYAPPLEPROC __glewDrawElementArrayAPPLE; +GLEW_FUN_EXPORT PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC __glewDrawRangeElementArrayAPPLE; +GLEW_FUN_EXPORT PFNGLELEMENTPOINTERAPPLEPROC __glewElementPointerAPPLE; +GLEW_FUN_EXPORT PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC __glewMultiDrawElementArrayAPPLE; +GLEW_FUN_EXPORT PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC __glewMultiDrawRangeElementArrayAPPLE; + +GLEW_FUN_EXPORT PFNGLDELETEFENCESAPPLEPROC __glewDeleteFencesAPPLE; +GLEW_FUN_EXPORT PFNGLFINISHFENCEAPPLEPROC __glewFinishFenceAPPLE; +GLEW_FUN_EXPORT PFNGLFINISHOBJECTAPPLEPROC __glewFinishObjectAPPLE; +GLEW_FUN_EXPORT PFNGLGENFENCESAPPLEPROC __glewGenFencesAPPLE; +GLEW_FUN_EXPORT PFNGLISFENCEAPPLEPROC __glewIsFenceAPPLE; +GLEW_FUN_EXPORT PFNGLSETFENCEAPPLEPROC __glewSetFenceAPPLE; +GLEW_FUN_EXPORT PFNGLTESTFENCEAPPLEPROC __glewTestFenceAPPLE; +GLEW_FUN_EXPORT PFNGLTESTOBJECTAPPLEPROC __glewTestObjectAPPLE; + +GLEW_FUN_EXPORT PFNGLBUFFERPARAMETERIAPPLEPROC __glewBufferParameteriAPPLE; +GLEW_FUN_EXPORT PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC __glewFlushMappedBufferRangeAPPLE; + +GLEW_FUN_EXPORT PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC __glewGetTexParameterPointervAPPLE; +GLEW_FUN_EXPORT PFNGLTEXTURERANGEAPPLEPROC __glewTextureRangeAPPLE; + +GLEW_FUN_EXPORT PFNGLBINDVERTEXARRAYAPPLEPROC __glewBindVertexArrayAPPLE; +GLEW_FUN_EXPORT PFNGLDELETEVERTEXARRAYSAPPLEPROC __glewDeleteVertexArraysAPPLE; +GLEW_FUN_EXPORT PFNGLGENVERTEXARRAYSAPPLEPROC __glewGenVertexArraysAPPLE; +GLEW_FUN_EXPORT PFNGLISVERTEXARRAYAPPLEPROC __glewIsVertexArrayAPPLE; + +GLEW_FUN_EXPORT PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC __glewFlushVertexArrayRangeAPPLE; +GLEW_FUN_EXPORT PFNGLVERTEXARRAYPARAMETERIAPPLEPROC __glewVertexArrayParameteriAPPLE; +GLEW_FUN_EXPORT PFNGLVERTEXARRAYRANGEAPPLEPROC __glewVertexArrayRangeAPPLE; + +GLEW_FUN_EXPORT PFNGLCLAMPCOLORARBPROC __glewClampColorARB; + +GLEW_FUN_EXPORT PFNGLDRAWBUFFERSARBPROC __glewDrawBuffersARB; + +GLEW_FUN_EXPORT PFNGLDRAWARRAYSINSTANCEDARBPROC __glewDrawArraysInstancedARB; +GLEW_FUN_EXPORT PFNGLDRAWELEMENTSINSTANCEDARBPROC __glewDrawElementsInstancedARB; + +GLEW_FUN_EXPORT PFNGLBINDFRAMEBUFFERPROC __glewBindFramebuffer; +GLEW_FUN_EXPORT PFNGLBINDRENDERBUFFERPROC __glewBindRenderbuffer; +GLEW_FUN_EXPORT PFNGLBLITFRAMEBUFFERPROC __glewBlitFramebuffer; +GLEW_FUN_EXPORT PFNGLCHECKFRAMEBUFFERSTATUSPROC __glewCheckFramebufferStatus; +GLEW_FUN_EXPORT PFNGLDELETEFRAMEBUFFERSPROC __glewDeleteFramebuffers; +GLEW_FUN_EXPORT PFNGLDELETERENDERBUFFERSPROC __glewDeleteRenderbuffers; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERRENDERBUFFERPROC __glewFramebufferRenderbuffer; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURLAYERPROC __glewFramebufferTexturLayer; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURE1DPROC __glewFramebufferTexture1D; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURE2DPROC __glewFramebufferTexture2D; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURE3DPROC __glewFramebufferTexture3D; +GLEW_FUN_EXPORT PFNGLGENFRAMEBUFFERSPROC __glewGenFramebuffers; +GLEW_FUN_EXPORT PFNGLGENRENDERBUFFERSPROC __glewGenRenderbuffers; +GLEW_FUN_EXPORT PFNGLGENERATEMIPMAPPROC __glewGenerateMipmap; +GLEW_FUN_EXPORT PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC __glewGetFramebufferAttachmentParameteriv; +GLEW_FUN_EXPORT PFNGLGETRENDERBUFFERPARAMETERIVPROC __glewGetRenderbufferParameteriv; +GLEW_FUN_EXPORT PFNGLISFRAMEBUFFERPROC __glewIsFramebuffer; +GLEW_FUN_EXPORT PFNGLISRENDERBUFFERPROC __glewIsRenderbuffer; +GLEW_FUN_EXPORT PFNGLRENDERBUFFERSTORAGEPROC __glewRenderbufferStorage; +GLEW_FUN_EXPORT PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC __glewRenderbufferStorageMultisample; + +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTUREARBPROC __glewFramebufferTextureARB; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTUREFACEARBPROC __glewFramebufferTextureFaceARB; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURELAYERARBPROC __glewFramebufferTextureLayerARB; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETERIARBPROC __glewProgramParameteriARB; + +GLEW_FUN_EXPORT PFNGLCOLORSUBTABLEPROC __glewColorSubTable; +GLEW_FUN_EXPORT PFNGLCOLORTABLEPROC __glewColorTable; +GLEW_FUN_EXPORT PFNGLCOLORTABLEPARAMETERFVPROC __glewColorTableParameterfv; +GLEW_FUN_EXPORT PFNGLCOLORTABLEPARAMETERIVPROC __glewColorTableParameteriv; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONFILTER1DPROC __glewConvolutionFilter1D; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONFILTER2DPROC __glewConvolutionFilter2D; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERFPROC __glewConvolutionParameterf; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERFVPROC __glewConvolutionParameterfv; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERIPROC __glewConvolutionParameteri; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERIVPROC __glewConvolutionParameteriv; +GLEW_FUN_EXPORT PFNGLCOPYCOLORSUBTABLEPROC __glewCopyColorSubTable; +GLEW_FUN_EXPORT PFNGLCOPYCOLORTABLEPROC __glewCopyColorTable; +GLEW_FUN_EXPORT PFNGLCOPYCONVOLUTIONFILTER1DPROC __glewCopyConvolutionFilter1D; +GLEW_FUN_EXPORT PFNGLCOPYCONVOLUTIONFILTER2DPROC __glewCopyConvolutionFilter2D; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEPROC __glewGetColorTable; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEPARAMETERFVPROC __glewGetColorTableParameterfv; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEPARAMETERIVPROC __glewGetColorTableParameteriv; +GLEW_FUN_EXPORT PFNGLGETCONVOLUTIONFILTERPROC __glewGetConvolutionFilter; +GLEW_FUN_EXPORT PFNGLGETCONVOLUTIONPARAMETERFVPROC __glewGetConvolutionParameterfv; +GLEW_FUN_EXPORT PFNGLGETCONVOLUTIONPARAMETERIVPROC __glewGetConvolutionParameteriv; +GLEW_FUN_EXPORT PFNGLGETHISTOGRAMPROC __glewGetHistogram; +GLEW_FUN_EXPORT PFNGLGETHISTOGRAMPARAMETERFVPROC __glewGetHistogramParameterfv; +GLEW_FUN_EXPORT PFNGLGETHISTOGRAMPARAMETERIVPROC __glewGetHistogramParameteriv; +GLEW_FUN_EXPORT PFNGLGETMINMAXPROC __glewGetMinmax; +GLEW_FUN_EXPORT PFNGLGETMINMAXPARAMETERFVPROC __glewGetMinmaxParameterfv; +GLEW_FUN_EXPORT PFNGLGETMINMAXPARAMETERIVPROC __glewGetMinmaxParameteriv; +GLEW_FUN_EXPORT PFNGLGETSEPARABLEFILTERPROC __glewGetSeparableFilter; +GLEW_FUN_EXPORT PFNGLHISTOGRAMPROC __glewHistogram; +GLEW_FUN_EXPORT PFNGLMINMAXPROC __glewMinmax; +GLEW_FUN_EXPORT PFNGLRESETHISTOGRAMPROC __glewResetHistogram; +GLEW_FUN_EXPORT PFNGLRESETMINMAXPROC __glewResetMinmax; +GLEW_FUN_EXPORT PFNGLSEPARABLEFILTER2DPROC __glewSeparableFilter2D; + +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBDIVISORARBPROC __glewVertexAttribDivisorARB; + +GLEW_FUN_EXPORT PFNGLFLUSHMAPPEDBUFFERRANGEPROC __glewFlushMappedBufferRange; +GLEW_FUN_EXPORT PFNGLMAPBUFFERRANGEPROC __glewMapBufferRange; + +GLEW_FUN_EXPORT PFNGLCURRENTPALETTEMATRIXARBPROC __glewCurrentPaletteMatrixARB; +GLEW_FUN_EXPORT PFNGLMATRIXINDEXPOINTERARBPROC __glewMatrixIndexPointerARB; +GLEW_FUN_EXPORT PFNGLMATRIXINDEXUBVARBPROC __glewMatrixIndexubvARB; +GLEW_FUN_EXPORT PFNGLMATRIXINDEXUIVARBPROC __glewMatrixIndexuivARB; +GLEW_FUN_EXPORT PFNGLMATRIXINDEXUSVARBPROC __glewMatrixIndexusvARB; + +GLEW_FUN_EXPORT PFNGLSAMPLECOVERAGEARBPROC __glewSampleCoverageARB; + +GLEW_FUN_EXPORT PFNGLACTIVETEXTUREARBPROC __glewActiveTextureARB; +GLEW_FUN_EXPORT PFNGLCLIENTACTIVETEXTUREARBPROC __glewClientActiveTextureARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1DARBPROC __glewMultiTexCoord1dARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1DVARBPROC __glewMultiTexCoord1dvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1FARBPROC __glewMultiTexCoord1fARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1FVARBPROC __glewMultiTexCoord1fvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1IARBPROC __glewMultiTexCoord1iARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1IVARBPROC __glewMultiTexCoord1ivARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1SARBPROC __glewMultiTexCoord1sARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1SVARBPROC __glewMultiTexCoord1svARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2DARBPROC __glewMultiTexCoord2dARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2DVARBPROC __glewMultiTexCoord2dvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2FARBPROC __glewMultiTexCoord2fARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2FVARBPROC __glewMultiTexCoord2fvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2IARBPROC __glewMultiTexCoord2iARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2IVARBPROC __glewMultiTexCoord2ivARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2SARBPROC __glewMultiTexCoord2sARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2SVARBPROC __glewMultiTexCoord2svARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3DARBPROC __glewMultiTexCoord3dARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3DVARBPROC __glewMultiTexCoord3dvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3FARBPROC __glewMultiTexCoord3fARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3FVARBPROC __glewMultiTexCoord3fvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3IARBPROC __glewMultiTexCoord3iARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3IVARBPROC __glewMultiTexCoord3ivARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3SARBPROC __glewMultiTexCoord3sARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3SVARBPROC __glewMultiTexCoord3svARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4DARBPROC __glewMultiTexCoord4dARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4DVARBPROC __glewMultiTexCoord4dvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4FARBPROC __glewMultiTexCoord4fARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4FVARBPROC __glewMultiTexCoord4fvARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4IARBPROC __glewMultiTexCoord4iARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4IVARBPROC __glewMultiTexCoord4ivARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4SARBPROC __glewMultiTexCoord4sARB; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4SVARBPROC __glewMultiTexCoord4svARB; + +GLEW_FUN_EXPORT PFNGLBEGINQUERYARBPROC __glewBeginQueryARB; +GLEW_FUN_EXPORT PFNGLDELETEQUERIESARBPROC __glewDeleteQueriesARB; +GLEW_FUN_EXPORT PFNGLENDQUERYARBPROC __glewEndQueryARB; +GLEW_FUN_EXPORT PFNGLGENQUERIESARBPROC __glewGenQueriesARB; +GLEW_FUN_EXPORT PFNGLGETQUERYOBJECTIVARBPROC __glewGetQueryObjectivARB; +GLEW_FUN_EXPORT PFNGLGETQUERYOBJECTUIVARBPROC __glewGetQueryObjectuivARB; +GLEW_FUN_EXPORT PFNGLGETQUERYIVARBPROC __glewGetQueryivARB; +GLEW_FUN_EXPORT PFNGLISQUERYARBPROC __glewIsQueryARB; + +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERFARBPROC __glewPointParameterfARB; +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERFVARBPROC __glewPointParameterfvARB; + +GLEW_FUN_EXPORT PFNGLATTACHOBJECTARBPROC __glewAttachObjectARB; +GLEW_FUN_EXPORT PFNGLCOMPILESHADERARBPROC __glewCompileShaderARB; +GLEW_FUN_EXPORT PFNGLCREATEPROGRAMOBJECTARBPROC __glewCreateProgramObjectARB; +GLEW_FUN_EXPORT PFNGLCREATESHADEROBJECTARBPROC __glewCreateShaderObjectARB; +GLEW_FUN_EXPORT PFNGLDELETEOBJECTARBPROC __glewDeleteObjectARB; +GLEW_FUN_EXPORT PFNGLDETACHOBJECTARBPROC __glewDetachObjectARB; +GLEW_FUN_EXPORT PFNGLGETACTIVEUNIFORMARBPROC __glewGetActiveUniformARB; +GLEW_FUN_EXPORT PFNGLGETATTACHEDOBJECTSARBPROC __glewGetAttachedObjectsARB; +GLEW_FUN_EXPORT PFNGLGETHANDLEARBPROC __glewGetHandleARB; +GLEW_FUN_EXPORT PFNGLGETINFOLOGARBPROC __glewGetInfoLogARB; +GLEW_FUN_EXPORT PFNGLGETOBJECTPARAMETERFVARBPROC __glewGetObjectParameterfvARB; +GLEW_FUN_EXPORT PFNGLGETOBJECTPARAMETERIVARBPROC __glewGetObjectParameterivARB; +GLEW_FUN_EXPORT PFNGLGETSHADERSOURCEARBPROC __glewGetShaderSourceARB; +GLEW_FUN_EXPORT PFNGLGETUNIFORMLOCATIONARBPROC __glewGetUniformLocationARB; +GLEW_FUN_EXPORT PFNGLGETUNIFORMFVARBPROC __glewGetUniformfvARB; +GLEW_FUN_EXPORT PFNGLGETUNIFORMIVARBPROC __glewGetUniformivARB; +GLEW_FUN_EXPORT PFNGLLINKPROGRAMARBPROC __glewLinkProgramARB; +GLEW_FUN_EXPORT PFNGLSHADERSOURCEARBPROC __glewShaderSourceARB; +GLEW_FUN_EXPORT PFNGLUNIFORM1FARBPROC __glewUniform1fARB; +GLEW_FUN_EXPORT PFNGLUNIFORM1FVARBPROC __glewUniform1fvARB; +GLEW_FUN_EXPORT PFNGLUNIFORM1IARBPROC __glewUniform1iARB; +GLEW_FUN_EXPORT PFNGLUNIFORM1IVARBPROC __glewUniform1ivARB; +GLEW_FUN_EXPORT PFNGLUNIFORM2FARBPROC __glewUniform2fARB; +GLEW_FUN_EXPORT PFNGLUNIFORM2FVARBPROC __glewUniform2fvARB; +GLEW_FUN_EXPORT PFNGLUNIFORM2IARBPROC __glewUniform2iARB; +GLEW_FUN_EXPORT PFNGLUNIFORM2IVARBPROC __glewUniform2ivARB; +GLEW_FUN_EXPORT PFNGLUNIFORM3FARBPROC __glewUniform3fARB; +GLEW_FUN_EXPORT PFNGLUNIFORM3FVARBPROC __glewUniform3fvARB; +GLEW_FUN_EXPORT PFNGLUNIFORM3IARBPROC __glewUniform3iARB; +GLEW_FUN_EXPORT PFNGLUNIFORM3IVARBPROC __glewUniform3ivARB; +GLEW_FUN_EXPORT PFNGLUNIFORM4FARBPROC __glewUniform4fARB; +GLEW_FUN_EXPORT PFNGLUNIFORM4FVARBPROC __glewUniform4fvARB; +GLEW_FUN_EXPORT PFNGLUNIFORM4IARBPROC __glewUniform4iARB; +GLEW_FUN_EXPORT PFNGLUNIFORM4IVARBPROC __glewUniform4ivARB; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX2FVARBPROC __glewUniformMatrix2fvARB; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX3FVARBPROC __glewUniformMatrix3fvARB; +GLEW_FUN_EXPORT PFNGLUNIFORMMATRIX4FVARBPROC __glewUniformMatrix4fvARB; +GLEW_FUN_EXPORT PFNGLUSEPROGRAMOBJECTARBPROC __glewUseProgramObjectARB; +GLEW_FUN_EXPORT PFNGLVALIDATEPROGRAMARBPROC __glewValidateProgramARB; + +GLEW_FUN_EXPORT PFNGLTEXBUFFERARBPROC __glewTexBufferARB; + +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXIMAGE1DARBPROC __glewCompressedTexImage1DARB; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXIMAGE2DARBPROC __glewCompressedTexImage2DARB; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXIMAGE3DARBPROC __glewCompressedTexImage3DARB; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC __glewCompressedTexSubImage1DARB; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC __glewCompressedTexSubImage2DARB; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC __glewCompressedTexSubImage3DARB; +GLEW_FUN_EXPORT PFNGLGETCOMPRESSEDTEXIMAGEARBPROC __glewGetCompressedTexImageARB; + +GLEW_FUN_EXPORT PFNGLLOADTRANSPOSEMATRIXDARBPROC __glewLoadTransposeMatrixdARB; +GLEW_FUN_EXPORT PFNGLLOADTRANSPOSEMATRIXFARBPROC __glewLoadTransposeMatrixfARB; +GLEW_FUN_EXPORT PFNGLMULTTRANSPOSEMATRIXDARBPROC __glewMultTransposeMatrixdARB; +GLEW_FUN_EXPORT PFNGLMULTTRANSPOSEMATRIXFARBPROC __glewMultTransposeMatrixfARB; + +GLEW_FUN_EXPORT PFNGLBINDVERTEXARRAYPROC __glewBindVertexArray; +GLEW_FUN_EXPORT PFNGLDELETEVERTEXARRAYSPROC __glewDeleteVertexArrays; +GLEW_FUN_EXPORT PFNGLGENVERTEXARRAYSPROC __glewGenVertexArrays; +GLEW_FUN_EXPORT PFNGLISVERTEXARRAYPROC __glewIsVertexArray; + +GLEW_FUN_EXPORT PFNGLVERTEXBLENDARBPROC __glewVertexBlendARB; +GLEW_FUN_EXPORT PFNGLWEIGHTPOINTERARBPROC __glewWeightPointerARB; +GLEW_FUN_EXPORT PFNGLWEIGHTBVARBPROC __glewWeightbvARB; +GLEW_FUN_EXPORT PFNGLWEIGHTDVARBPROC __glewWeightdvARB; +GLEW_FUN_EXPORT PFNGLWEIGHTFVARBPROC __glewWeightfvARB; +GLEW_FUN_EXPORT PFNGLWEIGHTIVARBPROC __glewWeightivARB; +GLEW_FUN_EXPORT PFNGLWEIGHTSVARBPROC __glewWeightsvARB; +GLEW_FUN_EXPORT PFNGLWEIGHTUBVARBPROC __glewWeightubvARB; +GLEW_FUN_EXPORT PFNGLWEIGHTUIVARBPROC __glewWeightuivARB; +GLEW_FUN_EXPORT PFNGLWEIGHTUSVARBPROC __glewWeightusvARB; + +GLEW_FUN_EXPORT PFNGLBINDBUFFERARBPROC __glewBindBufferARB; +GLEW_FUN_EXPORT PFNGLBUFFERDATAARBPROC __glewBufferDataARB; +GLEW_FUN_EXPORT PFNGLBUFFERSUBDATAARBPROC __glewBufferSubDataARB; +GLEW_FUN_EXPORT PFNGLDELETEBUFFERSARBPROC __glewDeleteBuffersARB; +GLEW_FUN_EXPORT PFNGLGENBUFFERSARBPROC __glewGenBuffersARB; +GLEW_FUN_EXPORT PFNGLGETBUFFERPARAMETERIVARBPROC __glewGetBufferParameterivARB; +GLEW_FUN_EXPORT PFNGLGETBUFFERPOINTERVARBPROC __glewGetBufferPointervARB; +GLEW_FUN_EXPORT PFNGLGETBUFFERSUBDATAARBPROC __glewGetBufferSubDataARB; +GLEW_FUN_EXPORT PFNGLISBUFFERARBPROC __glewIsBufferARB; +GLEW_FUN_EXPORT PFNGLMAPBUFFERARBPROC __glewMapBufferARB; +GLEW_FUN_EXPORT PFNGLUNMAPBUFFERARBPROC __glewUnmapBufferARB; + +GLEW_FUN_EXPORT PFNGLBINDPROGRAMARBPROC __glewBindProgramARB; +GLEW_FUN_EXPORT PFNGLDELETEPROGRAMSARBPROC __glewDeleteProgramsARB; +GLEW_FUN_EXPORT PFNGLDISABLEVERTEXATTRIBARRAYARBPROC __glewDisableVertexAttribArrayARB; +GLEW_FUN_EXPORT PFNGLENABLEVERTEXATTRIBARRAYARBPROC __glewEnableVertexAttribArrayARB; +GLEW_FUN_EXPORT PFNGLGENPROGRAMSARBPROC __glewGenProgramsARB; +GLEW_FUN_EXPORT PFNGLGETPROGRAMENVPARAMETERDVARBPROC __glewGetProgramEnvParameterdvARB; +GLEW_FUN_EXPORT PFNGLGETPROGRAMENVPARAMETERFVARBPROC __glewGetProgramEnvParameterfvARB; +GLEW_FUN_EXPORT PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC __glewGetProgramLocalParameterdvARB; +GLEW_FUN_EXPORT PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC __glewGetProgramLocalParameterfvARB; +GLEW_FUN_EXPORT PFNGLGETPROGRAMSTRINGARBPROC __glewGetProgramStringARB; +GLEW_FUN_EXPORT PFNGLGETPROGRAMIVARBPROC __glewGetProgramivARB; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBPOINTERVARBPROC __glewGetVertexAttribPointervARB; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBDVARBPROC __glewGetVertexAttribdvARB; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBFVARBPROC __glewGetVertexAttribfvARB; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBIVARBPROC __glewGetVertexAttribivARB; +GLEW_FUN_EXPORT PFNGLISPROGRAMARBPROC __glewIsProgramARB; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETER4DARBPROC __glewProgramEnvParameter4dARB; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETER4DVARBPROC __glewProgramEnvParameter4dvARB; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETER4FARBPROC __glewProgramEnvParameter4fARB; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETER4FVARBPROC __glewProgramEnvParameter4fvARB; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETER4DARBPROC __glewProgramLocalParameter4dARB; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETER4DVARBPROC __glewProgramLocalParameter4dvARB; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETER4FARBPROC __glewProgramLocalParameter4fARB; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETER4FVARBPROC __glewProgramLocalParameter4fvARB; +GLEW_FUN_EXPORT PFNGLPROGRAMSTRINGARBPROC __glewProgramStringARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1DARBPROC __glewVertexAttrib1dARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1DVARBPROC __glewVertexAttrib1dvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1FARBPROC __glewVertexAttrib1fARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1FVARBPROC __glewVertexAttrib1fvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1SARBPROC __glewVertexAttrib1sARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1SVARBPROC __glewVertexAttrib1svARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2DARBPROC __glewVertexAttrib2dARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2DVARBPROC __glewVertexAttrib2dvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2FARBPROC __glewVertexAttrib2fARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2FVARBPROC __glewVertexAttrib2fvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2SARBPROC __glewVertexAttrib2sARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2SVARBPROC __glewVertexAttrib2svARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3DARBPROC __glewVertexAttrib3dARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3DVARBPROC __glewVertexAttrib3dvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3FARBPROC __glewVertexAttrib3fARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3FVARBPROC __glewVertexAttrib3fvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3SARBPROC __glewVertexAttrib3sARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3SVARBPROC __glewVertexAttrib3svARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NBVARBPROC __glewVertexAttrib4NbvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NIVARBPROC __glewVertexAttrib4NivARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NSVARBPROC __glewVertexAttrib4NsvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUBARBPROC __glewVertexAttrib4NubARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUBVARBPROC __glewVertexAttrib4NubvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUIVARBPROC __glewVertexAttrib4NuivARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4NUSVARBPROC __glewVertexAttrib4NusvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4BVARBPROC __glewVertexAttrib4bvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4DARBPROC __glewVertexAttrib4dARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4DVARBPROC __glewVertexAttrib4dvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4FARBPROC __glewVertexAttrib4fARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4FVARBPROC __glewVertexAttrib4fvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4IVARBPROC __glewVertexAttrib4ivARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4SARBPROC __glewVertexAttrib4sARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4SVARBPROC __glewVertexAttrib4svARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4UBVARBPROC __glewVertexAttrib4ubvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4UIVARBPROC __glewVertexAttrib4uivARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4USVARBPROC __glewVertexAttrib4usvARB; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBPOINTERARBPROC __glewVertexAttribPointerARB; + +GLEW_FUN_EXPORT PFNGLBINDATTRIBLOCATIONARBPROC __glewBindAttribLocationARB; +GLEW_FUN_EXPORT PFNGLGETACTIVEATTRIBARBPROC __glewGetActiveAttribARB; +GLEW_FUN_EXPORT PFNGLGETATTRIBLOCATIONARBPROC __glewGetAttribLocationARB; + +GLEW_FUN_EXPORT PFNGLWINDOWPOS2DARBPROC __glewWindowPos2dARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2DVARBPROC __glewWindowPos2dvARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2FARBPROC __glewWindowPos2fARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2FVARBPROC __glewWindowPos2fvARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2IARBPROC __glewWindowPos2iARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2IVARBPROC __glewWindowPos2ivARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2SARBPROC __glewWindowPos2sARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2SVARBPROC __glewWindowPos2svARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3DARBPROC __glewWindowPos3dARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3DVARBPROC __glewWindowPos3dvARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3FARBPROC __glewWindowPos3fARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3FVARBPROC __glewWindowPos3fvARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3IARBPROC __glewWindowPos3iARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3IVARBPROC __glewWindowPos3ivARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3SARBPROC __glewWindowPos3sARB; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3SVARBPROC __glewWindowPos3svARB; + +GLEW_FUN_EXPORT PFNGLDRAWBUFFERSATIPROC __glewDrawBuffersATI; + +GLEW_FUN_EXPORT PFNGLDRAWELEMENTARRAYATIPROC __glewDrawElementArrayATI; +GLEW_FUN_EXPORT PFNGLDRAWRANGEELEMENTARRAYATIPROC __glewDrawRangeElementArrayATI; +GLEW_FUN_EXPORT PFNGLELEMENTPOINTERATIPROC __glewElementPointerATI; + +GLEW_FUN_EXPORT PFNGLGETTEXBUMPPARAMETERFVATIPROC __glewGetTexBumpParameterfvATI; +GLEW_FUN_EXPORT PFNGLGETTEXBUMPPARAMETERIVATIPROC __glewGetTexBumpParameterivATI; +GLEW_FUN_EXPORT PFNGLTEXBUMPPARAMETERFVATIPROC __glewTexBumpParameterfvATI; +GLEW_FUN_EXPORT PFNGLTEXBUMPPARAMETERIVATIPROC __glewTexBumpParameterivATI; + +GLEW_FUN_EXPORT PFNGLALPHAFRAGMENTOP1ATIPROC __glewAlphaFragmentOp1ATI; +GLEW_FUN_EXPORT PFNGLALPHAFRAGMENTOP2ATIPROC __glewAlphaFragmentOp2ATI; +GLEW_FUN_EXPORT PFNGLALPHAFRAGMENTOP3ATIPROC __glewAlphaFragmentOp3ATI; +GLEW_FUN_EXPORT PFNGLBEGINFRAGMENTSHADERATIPROC __glewBeginFragmentShaderATI; +GLEW_FUN_EXPORT PFNGLBINDFRAGMENTSHADERATIPROC __glewBindFragmentShaderATI; +GLEW_FUN_EXPORT PFNGLCOLORFRAGMENTOP1ATIPROC __glewColorFragmentOp1ATI; +GLEW_FUN_EXPORT PFNGLCOLORFRAGMENTOP2ATIPROC __glewColorFragmentOp2ATI; +GLEW_FUN_EXPORT PFNGLCOLORFRAGMENTOP3ATIPROC __glewColorFragmentOp3ATI; +GLEW_FUN_EXPORT PFNGLDELETEFRAGMENTSHADERATIPROC __glewDeleteFragmentShaderATI; +GLEW_FUN_EXPORT PFNGLENDFRAGMENTSHADERATIPROC __glewEndFragmentShaderATI; +GLEW_FUN_EXPORT PFNGLGENFRAGMENTSHADERSATIPROC __glewGenFragmentShadersATI; +GLEW_FUN_EXPORT PFNGLPASSTEXCOORDATIPROC __glewPassTexCoordATI; +GLEW_FUN_EXPORT PFNGLSAMPLEMAPATIPROC __glewSampleMapATI; +GLEW_FUN_EXPORT PFNGLSETFRAGMENTSHADERCONSTANTATIPROC __glewSetFragmentShaderConstantATI; + +GLEW_FUN_EXPORT PFNGLMAPOBJECTBUFFERATIPROC __glewMapObjectBufferATI; +GLEW_FUN_EXPORT PFNGLUNMAPOBJECTBUFFERATIPROC __glewUnmapObjectBufferATI; + +GLEW_FUN_EXPORT PFNGLPNTRIANGLESFATIPROC __glPNTrianglewesfATI; +GLEW_FUN_EXPORT PFNGLPNTRIANGLESIATIPROC __glPNTrianglewesiATI; + +GLEW_FUN_EXPORT PFNGLSTENCILFUNCSEPARATEATIPROC __glewStencilFuncSeparateATI; +GLEW_FUN_EXPORT PFNGLSTENCILOPSEPARATEATIPROC __glewStencilOpSeparateATI; + +GLEW_FUN_EXPORT PFNGLARRAYOBJECTATIPROC __glewArrayObjectATI; +GLEW_FUN_EXPORT PFNGLFREEOBJECTBUFFERATIPROC __glewFreeObjectBufferATI; +GLEW_FUN_EXPORT PFNGLGETARRAYOBJECTFVATIPROC __glewGetArrayObjectfvATI; +GLEW_FUN_EXPORT PFNGLGETARRAYOBJECTIVATIPROC __glewGetArrayObjectivATI; +GLEW_FUN_EXPORT PFNGLGETOBJECTBUFFERFVATIPROC __glewGetObjectBufferfvATI; +GLEW_FUN_EXPORT PFNGLGETOBJECTBUFFERIVATIPROC __glewGetObjectBufferivATI; +GLEW_FUN_EXPORT PFNGLGETVARIANTARRAYOBJECTFVATIPROC __glewGetVariantArrayObjectfvATI; +GLEW_FUN_EXPORT PFNGLGETVARIANTARRAYOBJECTIVATIPROC __glewGetVariantArrayObjectivATI; +GLEW_FUN_EXPORT PFNGLISOBJECTBUFFERATIPROC __glewIsObjectBufferATI; +GLEW_FUN_EXPORT PFNGLNEWOBJECTBUFFERATIPROC __glewNewObjectBufferATI; +GLEW_FUN_EXPORT PFNGLUPDATEOBJECTBUFFERATIPROC __glewUpdateObjectBufferATI; +GLEW_FUN_EXPORT PFNGLVARIANTARRAYOBJECTATIPROC __glewVariantArrayObjectATI; + +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC __glewGetVertexAttribArrayObjectfvATI; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC __glewGetVertexAttribArrayObjectivATI; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBARRAYOBJECTATIPROC __glewVertexAttribArrayObjectATI; + +GLEW_FUN_EXPORT PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC __glewClientActiveVertexStreamATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3BATIPROC __glewNormalStream3bATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3BVATIPROC __glewNormalStream3bvATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3DATIPROC __glewNormalStream3dATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3DVATIPROC __glewNormalStream3dvATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3FATIPROC __glewNormalStream3fATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3FVATIPROC __glewNormalStream3fvATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3IATIPROC __glewNormalStream3iATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3IVATIPROC __glewNormalStream3ivATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3SATIPROC __glewNormalStream3sATI; +GLEW_FUN_EXPORT PFNGLNORMALSTREAM3SVATIPROC __glewNormalStream3svATI; +GLEW_FUN_EXPORT PFNGLVERTEXBLENDENVFATIPROC __glewVertexBlendEnvfATI; +GLEW_FUN_EXPORT PFNGLVERTEXBLENDENVIATIPROC __glewVertexBlendEnviATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2DATIPROC __glewVertexStream2dATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2DVATIPROC __glewVertexStream2dvATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2FATIPROC __glewVertexStream2fATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2FVATIPROC __glewVertexStream2fvATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2IATIPROC __glewVertexStream2iATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2IVATIPROC __glewVertexStream2ivATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2SATIPROC __glewVertexStream2sATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM2SVATIPROC __glewVertexStream2svATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3DATIPROC __glewVertexStream3dATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3DVATIPROC __glewVertexStream3dvATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3FATIPROC __glewVertexStream3fATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3FVATIPROC __glewVertexStream3fvATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3IATIPROC __glewVertexStream3iATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3IVATIPROC __glewVertexStream3ivATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3SATIPROC __glewVertexStream3sATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM3SVATIPROC __glewVertexStream3svATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4DATIPROC __glewVertexStream4dATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4DVATIPROC __glewVertexStream4dvATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4FATIPROC __glewVertexStream4fATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4FVATIPROC __glewVertexStream4fvATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4IATIPROC __glewVertexStream4iATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4IVATIPROC __glewVertexStream4ivATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4SATIPROC __glewVertexStream4sATI; +GLEW_FUN_EXPORT PFNGLVERTEXSTREAM4SVATIPROC __glewVertexStream4svATI; + +GLEW_FUN_EXPORT PFNGLGETUNIFORMBUFFERSIZEEXTPROC __glewGetUniformBufferSizeEXT; +GLEW_FUN_EXPORT PFNGLGETUNIFORMOFFSETEXTPROC __glewGetUniformOffsetEXT; +GLEW_FUN_EXPORT PFNGLUNIFORMBUFFEREXTPROC __glewUniformBufferEXT; + +GLEW_FUN_EXPORT PFNGLBLENDCOLOREXTPROC __glewBlendColorEXT; + +GLEW_FUN_EXPORT PFNGLBLENDEQUATIONSEPARATEEXTPROC __glewBlendEquationSeparateEXT; + +GLEW_FUN_EXPORT PFNGLBLENDFUNCSEPARATEEXTPROC __glewBlendFuncSeparateEXT; + +GLEW_FUN_EXPORT PFNGLBLENDEQUATIONEXTPROC __glewBlendEquationEXT; + +GLEW_FUN_EXPORT PFNGLCOLORSUBTABLEEXTPROC __glewColorSubTableEXT; +GLEW_FUN_EXPORT PFNGLCOPYCOLORSUBTABLEEXTPROC __glewCopyColorSubTableEXT; + +GLEW_FUN_EXPORT PFNGLLOCKARRAYSEXTPROC __glewLockArraysEXT; +GLEW_FUN_EXPORT PFNGLUNLOCKARRAYSEXTPROC __glewUnlockArraysEXT; + +GLEW_FUN_EXPORT PFNGLCONVOLUTIONFILTER1DEXTPROC __glewConvolutionFilter1DEXT; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONFILTER2DEXTPROC __glewConvolutionFilter2DEXT; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERFEXTPROC __glewConvolutionParameterfEXT; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERFVEXTPROC __glewConvolutionParameterfvEXT; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERIEXTPROC __glewConvolutionParameteriEXT; +GLEW_FUN_EXPORT PFNGLCONVOLUTIONPARAMETERIVEXTPROC __glewConvolutionParameterivEXT; +GLEW_FUN_EXPORT PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC __glewCopyConvolutionFilter1DEXT; +GLEW_FUN_EXPORT PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC __glewCopyConvolutionFilter2DEXT; +GLEW_FUN_EXPORT PFNGLGETCONVOLUTIONFILTEREXTPROC __glewGetConvolutionFilterEXT; +GLEW_FUN_EXPORT PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC __glewGetConvolutionParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC __glewGetConvolutionParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETSEPARABLEFILTEREXTPROC __glewGetSeparableFilterEXT; +GLEW_FUN_EXPORT PFNGLSEPARABLEFILTER2DEXTPROC __glewSeparableFilter2DEXT; + +GLEW_FUN_EXPORT PFNGLBINORMALPOINTEREXTPROC __glewBinormalPointerEXT; +GLEW_FUN_EXPORT PFNGLTANGENTPOINTEREXTPROC __glewTangentPointerEXT; + +GLEW_FUN_EXPORT PFNGLCOPYTEXIMAGE1DEXTPROC __glewCopyTexImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXIMAGE2DEXTPROC __glewCopyTexImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXSUBIMAGE1DEXTPROC __glewCopyTexSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXSUBIMAGE2DEXTPROC __glewCopyTexSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXSUBIMAGE3DEXTPROC __glewCopyTexSubImage3DEXT; + +GLEW_FUN_EXPORT PFNGLCULLPARAMETERDVEXTPROC __glewCullParameterdvEXT; +GLEW_FUN_EXPORT PFNGLCULLPARAMETERFVEXTPROC __glewCullParameterfvEXT; + +GLEW_FUN_EXPORT PFNGLDEPTHBOUNDSEXTPROC __glewDepthBoundsEXT; + +GLEW_FUN_EXPORT PFNGLBINDMULTITEXTUREEXTPROC __glewBindMultiTextureEXT; +GLEW_FUN_EXPORT PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC __glewCheckNamedFramebufferStatusEXT; +GLEW_FUN_EXPORT PFNGLCLIENTATTRIBDEFAULTEXTPROC __glewClientAttribDefaultEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC __glewCompressedMultiTexImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC __glewCompressedMultiTexImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC __glewCompressedMultiTexImage3DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC __glewCompressedMultiTexSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC __glewCompressedMultiTexSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC __glewCompressedMultiTexSubImage3DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC __glewCompressedTextureImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC __glewCompressedTextureImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC __glewCompressedTextureImage3DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC __glewCompressedTextureSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC __glewCompressedTextureSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC __glewCompressedTextureSubImage3DEXT; +GLEW_FUN_EXPORT PFNGLCOPYMULTITEXIMAGE1DEXTPROC __glewCopyMultiTexImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOPYMULTITEXIMAGE2DEXTPROC __glewCopyMultiTexImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC __glewCopyMultiTexSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC __glewCopyMultiTexSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC __glewCopyMultiTexSubImage3DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXTUREIMAGE1DEXTPROC __glewCopyTextureImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXTUREIMAGE2DEXTPROC __glewCopyTextureImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC __glewCopyTextureSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC __glewCopyTextureSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC __glewCopyTextureSubImage3DEXT; +GLEW_FUN_EXPORT PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC __glewDisableClientStateIndexedEXT; +GLEW_FUN_EXPORT PFNGLENABLECLIENTSTATEINDEXEDEXTPROC __glewEnableClientStateIndexedEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC __glewFramebufferDrawBufferEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC __glewFramebufferDrawBuffersEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERREADBUFFEREXTPROC __glewFramebufferReadBufferEXT; +GLEW_FUN_EXPORT PFNGLGENERATEMULTITEXMIPMAPEXTPROC __glewGenerateMultiTexMipmapEXT; +GLEW_FUN_EXPORT PFNGLGENERATETEXTUREMIPMAPEXTPROC __glewGenerateTextureMipmapEXT; +GLEW_FUN_EXPORT PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC __glewGetCompressedMultiTexImageEXT; +GLEW_FUN_EXPORT PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC __glewGetCompressedTextureImageEXT; +GLEW_FUN_EXPORT PFNGLGETDOUBLEINDEXEDVEXTPROC __glewGetDoubleIndexedvEXT; +GLEW_FUN_EXPORT PFNGLGETFLOATINDEXEDVEXTPROC __glewGetFloatIndexedvEXT; +GLEW_FUN_EXPORT PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC __glewGetFramebufferParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXENVFVEXTPROC __glewGetMultiTexEnvfvEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXENVIVEXTPROC __glewGetMultiTexEnvivEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXGENDVEXTPROC __glewGetMultiTexGendvEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXGENFVEXTPROC __glewGetMultiTexGenfvEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXGENIVEXTPROC __glewGetMultiTexGenivEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXIMAGEEXTPROC __glewGetMultiTexImageEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC __glewGetMultiTexLevelParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC __glewGetMultiTexLevelParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXPARAMETERIIVEXTPROC __glewGetMultiTexParameterIivEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXPARAMETERIUIVEXTPROC __glewGetMultiTexParameterIuivEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXPARAMETERFVEXTPROC __glewGetMultiTexParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETMULTITEXPARAMETERIVEXTPROC __glewGetMultiTexParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC __glewGetNamedBufferParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDBUFFERPOINTERVEXTPROC __glewGetNamedBufferPointervEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDBUFFERSUBDATAEXTPROC __glewGetNamedBufferSubDataEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC __glewGetNamedFramebufferAttachmentParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC __glewGetNamedProgramLocalParameterIivEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC __glewGetNamedProgramLocalParameterIuivEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC __glewGetNamedProgramLocalParameterdvEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC __glewGetNamedProgramLocalParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDPROGRAMSTRINGEXTPROC __glewGetNamedProgramStringEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDPROGRAMIVEXTPROC __glewGetNamedProgramivEXT; +GLEW_FUN_EXPORT PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC __glewGetNamedRenderbufferParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETPOINTERINDEXEDVEXTPROC __glewGetPointerIndexedvEXT; +GLEW_FUN_EXPORT PFNGLGETTEXTUREIMAGEEXTPROC __glewGetTextureImageEXT; +GLEW_FUN_EXPORT PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC __glewGetTextureLevelParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC __glewGetTextureLevelParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETTEXTUREPARAMETERIIVEXTPROC __glewGetTextureParameterIivEXT; +GLEW_FUN_EXPORT PFNGLGETTEXTUREPARAMETERIUIVEXTPROC __glewGetTextureParameterIuivEXT; +GLEW_FUN_EXPORT PFNGLGETTEXTUREPARAMETERFVEXTPROC __glewGetTextureParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETTEXTUREPARAMETERIVEXTPROC __glewGetTextureParameterivEXT; +GLEW_FUN_EXPORT PFNGLMAPNAMEDBUFFEREXTPROC __glewMapNamedBufferEXT; +GLEW_FUN_EXPORT PFNGLMATRIXFRUSTUMEXTPROC __glewMatrixFrustumEXT; +GLEW_FUN_EXPORT PFNGLMATRIXLOADIDENTITYEXTPROC __glewMatrixLoadIdentityEXT; +GLEW_FUN_EXPORT PFNGLMATRIXLOADTRANSPOSEDEXTPROC __glewMatrixLoadTransposedEXT; +GLEW_FUN_EXPORT PFNGLMATRIXLOADTRANSPOSEFEXTPROC __glewMatrixLoadTransposefEXT; +GLEW_FUN_EXPORT PFNGLMATRIXLOADDEXTPROC __glewMatrixLoaddEXT; +GLEW_FUN_EXPORT PFNGLMATRIXLOADFEXTPROC __glewMatrixLoadfEXT; +GLEW_FUN_EXPORT PFNGLMATRIXMULTTRANSPOSEDEXTPROC __glewMatrixMultTransposedEXT; +GLEW_FUN_EXPORT PFNGLMATRIXMULTTRANSPOSEFEXTPROC __glewMatrixMultTransposefEXT; +GLEW_FUN_EXPORT PFNGLMATRIXMULTDEXTPROC __glewMatrixMultdEXT; +GLEW_FUN_EXPORT PFNGLMATRIXMULTFEXTPROC __glewMatrixMultfEXT; +GLEW_FUN_EXPORT PFNGLMATRIXORTHOEXTPROC __glewMatrixOrthoEXT; +GLEW_FUN_EXPORT PFNGLMATRIXPOPEXTPROC __glewMatrixPopEXT; +GLEW_FUN_EXPORT PFNGLMATRIXPUSHEXTPROC __glewMatrixPushEXT; +GLEW_FUN_EXPORT PFNGLMATRIXROTATEDEXTPROC __glewMatrixRotatedEXT; +GLEW_FUN_EXPORT PFNGLMATRIXROTATEFEXTPROC __glewMatrixRotatefEXT; +GLEW_FUN_EXPORT PFNGLMATRIXSCALEDEXTPROC __glewMatrixScaledEXT; +GLEW_FUN_EXPORT PFNGLMATRIXSCALEFEXTPROC __glewMatrixScalefEXT; +GLEW_FUN_EXPORT PFNGLMATRIXTRANSLATEDEXTPROC __glewMatrixTranslatedEXT; +GLEW_FUN_EXPORT PFNGLMATRIXTRANSLATEFEXTPROC __glewMatrixTranslatefEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXBUFFEREXTPROC __glewMultiTexBufferEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORDPOINTEREXTPROC __glewMultiTexCoordPointerEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXENVFEXTPROC __glewMultiTexEnvfEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXENVFVEXTPROC __glewMultiTexEnvfvEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXENVIEXTPROC __glewMultiTexEnviEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXENVIVEXTPROC __glewMultiTexEnvivEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXGENDEXTPROC __glewMultiTexGendEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXGENDVEXTPROC __glewMultiTexGendvEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXGENFEXTPROC __glewMultiTexGenfEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXGENFVEXTPROC __glewMultiTexGenfvEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXGENIEXTPROC __glewMultiTexGeniEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXGENIVEXTPROC __glewMultiTexGenivEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXIMAGE1DEXTPROC __glewMultiTexImage1DEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXIMAGE2DEXTPROC __glewMultiTexImage2DEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXIMAGE3DEXTPROC __glewMultiTexImage3DEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXPARAMETERIIVEXTPROC __glewMultiTexParameterIivEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXPARAMETERIUIVEXTPROC __glewMultiTexParameterIuivEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXPARAMETERFEXTPROC __glewMultiTexParameterfEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXPARAMETERFVEXTPROC __glewMultiTexParameterfvEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXPARAMETERIEXTPROC __glewMultiTexParameteriEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXPARAMETERIVEXTPROC __glewMultiTexParameterivEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXRENDERBUFFEREXTPROC __glewMultiTexRenderbufferEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXSUBIMAGE1DEXTPROC __glewMultiTexSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXSUBIMAGE2DEXTPROC __glewMultiTexSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLMULTITEXSUBIMAGE3DEXTPROC __glewMultiTexSubImage3DEXT; +GLEW_FUN_EXPORT PFNGLNAMEDBUFFERDATAEXTPROC __glewNamedBufferDataEXT; +GLEW_FUN_EXPORT PFNGLNAMEDBUFFERSUBDATAEXTPROC __glewNamedBufferSubDataEXT; +GLEW_FUN_EXPORT PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC __glewNamedFramebufferRenderbufferEXT; +GLEW_FUN_EXPORT PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC __glewNamedFramebufferTexture1DEXT; +GLEW_FUN_EXPORT PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC __glewNamedFramebufferTexture2DEXT; +GLEW_FUN_EXPORT PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC __glewNamedFramebufferTexture3DEXT; +GLEW_FUN_EXPORT PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC __glewNamedFramebufferTextureEXT; +GLEW_FUN_EXPORT PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC __glewNamedFramebufferTextureFaceEXT; +GLEW_FUN_EXPORT PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC __glewNamedFramebufferTextureLayerEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC __glewNamedProgramLocalParameter4dEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC __glewNamedProgramLocalParameter4dvEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC __glewNamedProgramLocalParameter4fEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC __glewNamedProgramLocalParameter4fvEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC __glewNamedProgramLocalParameterI4iEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC __glewNamedProgramLocalParameterI4ivEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC __glewNamedProgramLocalParameterI4uiEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC __glewNamedProgramLocalParameterI4uivEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC __glewNamedProgramLocalParameters4fvEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC __glewNamedProgramLocalParametersI4ivEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC __glewNamedProgramLocalParametersI4uivEXT; +GLEW_FUN_EXPORT PFNGLNAMEDPROGRAMSTRINGEXTPROC __glewNamedProgramStringEXT; +GLEW_FUN_EXPORT PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC __glewNamedRenderbufferStorageEXT; +GLEW_FUN_EXPORT PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC __glewNamedRenderbufferStorageMultisampleCoverageEXT; +GLEW_FUN_EXPORT PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC __glewNamedRenderbufferStorageMultisampleEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM1FEXTPROC __glewProgramUniform1fEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM1FVEXTPROC __glewProgramUniform1fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM1IEXTPROC __glewProgramUniform1iEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM1IVEXTPROC __glewProgramUniform1ivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM1UIEXTPROC __glewProgramUniform1uiEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM1UIVEXTPROC __glewProgramUniform1uivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM2FEXTPROC __glewProgramUniform2fEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM2FVEXTPROC __glewProgramUniform2fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM2IEXTPROC __glewProgramUniform2iEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM2IVEXTPROC __glewProgramUniform2ivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM2UIEXTPROC __glewProgramUniform2uiEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM2UIVEXTPROC __glewProgramUniform2uivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM3FEXTPROC __glewProgramUniform3fEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM3FVEXTPROC __glewProgramUniform3fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM3IEXTPROC __glewProgramUniform3iEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM3IVEXTPROC __glewProgramUniform3ivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM3UIEXTPROC __glewProgramUniform3uiEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM3UIVEXTPROC __glewProgramUniform3uivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM4FEXTPROC __glewProgramUniform4fEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM4FVEXTPROC __glewProgramUniform4fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM4IEXTPROC __glewProgramUniform4iEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM4IVEXTPROC __glewProgramUniform4ivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM4UIEXTPROC __glewProgramUniform4uiEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORM4UIVEXTPROC __glewProgramUniform4uivEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC __glewProgramUniformMatrix2fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC __glewProgramUniformMatrix2x3fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC __glewProgramUniformMatrix2x4fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC __glewProgramUniformMatrix3fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC __glewProgramUniformMatrix3x2fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC __glewProgramUniformMatrix3x4fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC __glewProgramUniformMatrix4fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC __glewProgramUniformMatrix4x2fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC __glewProgramUniformMatrix4x3fvEXT; +GLEW_FUN_EXPORT PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC __glewPushClientAttribDefaultEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREBUFFEREXTPROC __glewTextureBufferEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREIMAGE1DEXTPROC __glewTextureImage1DEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREIMAGE2DEXTPROC __glewTextureImage2DEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREIMAGE3DEXTPROC __glewTextureImage3DEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREPARAMETERIIVEXTPROC __glewTextureParameterIivEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREPARAMETERIUIVEXTPROC __glewTextureParameterIuivEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREPARAMETERFEXTPROC __glewTextureParameterfEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREPARAMETERFVEXTPROC __glewTextureParameterfvEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREPARAMETERIEXTPROC __glewTextureParameteriEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREPARAMETERIVEXTPROC __glewTextureParameterivEXT; +GLEW_FUN_EXPORT PFNGLTEXTURERENDERBUFFEREXTPROC __glewTextureRenderbufferEXT; +GLEW_FUN_EXPORT PFNGLTEXTURESUBIMAGE1DEXTPROC __glewTextureSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLTEXTURESUBIMAGE2DEXTPROC __glewTextureSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLTEXTURESUBIMAGE3DEXTPROC __glewTextureSubImage3DEXT; +GLEW_FUN_EXPORT PFNGLUNMAPNAMEDBUFFEREXTPROC __glewUnmapNamedBufferEXT; + +GLEW_FUN_EXPORT PFNGLCOLORMASKINDEXEDEXTPROC __glewColorMaskIndexedEXT; +GLEW_FUN_EXPORT PFNGLDISABLEINDEXEDEXTPROC __glewDisableIndexedEXT; +GLEW_FUN_EXPORT PFNGLENABLEINDEXEDEXTPROC __glewEnableIndexedEXT; +GLEW_FUN_EXPORT PFNGLGETBOOLEANINDEXEDVEXTPROC __glewGetBooleanIndexedvEXT; +GLEW_FUN_EXPORT PFNGLGETINTEGERINDEXEDVEXTPROC __glewGetIntegerIndexedvEXT; +GLEW_FUN_EXPORT PFNGLISENABLEDINDEXEDEXTPROC __glewIsEnabledIndexedEXT; + +GLEW_FUN_EXPORT PFNGLDRAWARRAYSINSTANCEDEXTPROC __glewDrawArraysInstancedEXT; +GLEW_FUN_EXPORT PFNGLDRAWELEMENTSINSTANCEDEXTPROC __glewDrawElementsInstancedEXT; + +GLEW_FUN_EXPORT PFNGLDRAWRANGEELEMENTSEXTPROC __glewDrawRangeElementsEXT; + +GLEW_FUN_EXPORT PFNGLFOGCOORDPOINTEREXTPROC __glewFogCoordPointerEXT; +GLEW_FUN_EXPORT PFNGLFOGCOORDDEXTPROC __glewFogCoorddEXT; +GLEW_FUN_EXPORT PFNGLFOGCOORDDVEXTPROC __glewFogCoorddvEXT; +GLEW_FUN_EXPORT PFNGLFOGCOORDFEXTPROC __glewFogCoordfEXT; +GLEW_FUN_EXPORT PFNGLFOGCOORDFVEXTPROC __glewFogCoordfvEXT; + +GLEW_FUN_EXPORT PFNGLFRAGMENTCOLORMATERIALEXTPROC __glewFragmentColorMaterialEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELFEXTPROC __glewFragmentLightModelfEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELFVEXTPROC __glewFragmentLightModelfvEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELIEXTPROC __glewFragmentLightModeliEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELIVEXTPROC __glewFragmentLightModelivEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTFEXTPROC __glewFragmentLightfEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTFVEXTPROC __glewFragmentLightfvEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTIEXTPROC __glewFragmentLightiEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTIVEXTPROC __glewFragmentLightivEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALFEXTPROC __glewFragmentMaterialfEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALFVEXTPROC __glewFragmentMaterialfvEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALIEXTPROC __glewFragmentMaterialiEXT; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALIVEXTPROC __glewFragmentMaterialivEXT; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTLIGHTFVEXTPROC __glewGetFragmentLightfvEXT; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTLIGHTIVEXTPROC __glewGetFragmentLightivEXT; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTMATERIALFVEXTPROC __glewGetFragmentMaterialfvEXT; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTMATERIALIVEXTPROC __glewGetFragmentMaterialivEXT; +GLEW_FUN_EXPORT PFNGLLIGHTENVIEXTPROC __glewLightEnviEXT; + +GLEW_FUN_EXPORT PFNGLBLITFRAMEBUFFEREXTPROC __glewBlitFramebufferEXT; + +GLEW_FUN_EXPORT PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC __glewRenderbufferStorageMultisampleEXT; + +GLEW_FUN_EXPORT PFNGLBINDFRAMEBUFFEREXTPROC __glewBindFramebufferEXT; +GLEW_FUN_EXPORT PFNGLBINDRENDERBUFFEREXTPROC __glewBindRenderbufferEXT; +GLEW_FUN_EXPORT PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC __glewCheckFramebufferStatusEXT; +GLEW_FUN_EXPORT PFNGLDELETEFRAMEBUFFERSEXTPROC __glewDeleteFramebuffersEXT; +GLEW_FUN_EXPORT PFNGLDELETERENDERBUFFERSEXTPROC __glewDeleteRenderbuffersEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC __glewFramebufferRenderbufferEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURE1DEXTPROC __glewFramebufferTexture1DEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURE2DEXTPROC __glewFramebufferTexture2DEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURE3DEXTPROC __glewFramebufferTexture3DEXT; +GLEW_FUN_EXPORT PFNGLGENFRAMEBUFFERSEXTPROC __glewGenFramebuffersEXT; +GLEW_FUN_EXPORT PFNGLGENRENDERBUFFERSEXTPROC __glewGenRenderbuffersEXT; +GLEW_FUN_EXPORT PFNGLGENERATEMIPMAPEXTPROC __glewGenerateMipmapEXT; +GLEW_FUN_EXPORT PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC __glewGetFramebufferAttachmentParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC __glewGetRenderbufferParameterivEXT; +GLEW_FUN_EXPORT PFNGLISFRAMEBUFFEREXTPROC __glewIsFramebufferEXT; +GLEW_FUN_EXPORT PFNGLISRENDERBUFFEREXTPROC __glewIsRenderbufferEXT; +GLEW_FUN_EXPORT PFNGLRENDERBUFFERSTORAGEEXTPROC __glewRenderbufferStorageEXT; + +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTUREEXTPROC __glewFramebufferTextureEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC __glewFramebufferTextureFaceEXT; +GLEW_FUN_EXPORT PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC __glewFramebufferTextureLayerEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETERIEXTPROC __glewProgramParameteriEXT; + +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETERS4FVEXTPROC __glewProgramEnvParameters4fvEXT; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC __glewProgramLocalParameters4fvEXT; + +GLEW_FUN_EXPORT PFNGLBINDFRAGDATALOCATIONEXTPROC __glewBindFragDataLocationEXT; +GLEW_FUN_EXPORT PFNGLGETFRAGDATALOCATIONEXTPROC __glewGetFragDataLocationEXT; +GLEW_FUN_EXPORT PFNGLGETUNIFORMUIVEXTPROC __glewGetUniformuivEXT; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBIIVEXTPROC __glewGetVertexAttribIivEXT; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBIUIVEXTPROC __glewGetVertexAttribIuivEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM1UIEXTPROC __glewUniform1uiEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM1UIVEXTPROC __glewUniform1uivEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM2UIEXTPROC __glewUniform2uiEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM2UIVEXTPROC __glewUniform2uivEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM3UIEXTPROC __glewUniform3uiEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM3UIVEXTPROC __glewUniform3uivEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM4UIEXTPROC __glewUniform4uiEXT; +GLEW_FUN_EXPORT PFNGLUNIFORM4UIVEXTPROC __glewUniform4uivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1IEXTPROC __glewVertexAttribI1iEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1IVEXTPROC __glewVertexAttribI1ivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1UIEXTPROC __glewVertexAttribI1uiEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI1UIVEXTPROC __glewVertexAttribI1uivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2IEXTPROC __glewVertexAttribI2iEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2IVEXTPROC __glewVertexAttribI2ivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2UIEXTPROC __glewVertexAttribI2uiEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI2UIVEXTPROC __glewVertexAttribI2uivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3IEXTPROC __glewVertexAttribI3iEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3IVEXTPROC __glewVertexAttribI3ivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3UIEXTPROC __glewVertexAttribI3uiEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI3UIVEXTPROC __glewVertexAttribI3uivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4BVEXTPROC __glewVertexAttribI4bvEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4IEXTPROC __glewVertexAttribI4iEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4IVEXTPROC __glewVertexAttribI4ivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4SVEXTPROC __glewVertexAttribI4svEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4UBVEXTPROC __glewVertexAttribI4ubvEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4UIEXTPROC __glewVertexAttribI4uiEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4UIVEXTPROC __glewVertexAttribI4uivEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBI4USVEXTPROC __glewVertexAttribI4usvEXT; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBIPOINTEREXTPROC __glewVertexAttribIPointerEXT; + +GLEW_FUN_EXPORT PFNGLGETHISTOGRAMEXTPROC __glewGetHistogramEXT; +GLEW_FUN_EXPORT PFNGLGETHISTOGRAMPARAMETERFVEXTPROC __glewGetHistogramParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETHISTOGRAMPARAMETERIVEXTPROC __glewGetHistogramParameterivEXT; +GLEW_FUN_EXPORT PFNGLGETMINMAXEXTPROC __glewGetMinmaxEXT; +GLEW_FUN_EXPORT PFNGLGETMINMAXPARAMETERFVEXTPROC __glewGetMinmaxParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETMINMAXPARAMETERIVEXTPROC __glewGetMinmaxParameterivEXT; +GLEW_FUN_EXPORT PFNGLHISTOGRAMEXTPROC __glewHistogramEXT; +GLEW_FUN_EXPORT PFNGLMINMAXEXTPROC __glewMinmaxEXT; +GLEW_FUN_EXPORT PFNGLRESETHISTOGRAMEXTPROC __glewResetHistogramEXT; +GLEW_FUN_EXPORT PFNGLRESETMINMAXEXTPROC __glewResetMinmaxEXT; + +GLEW_FUN_EXPORT PFNGLINDEXFUNCEXTPROC __glewIndexFuncEXT; + +GLEW_FUN_EXPORT PFNGLINDEXMATERIALEXTPROC __glewIndexMaterialEXT; + +GLEW_FUN_EXPORT PFNGLAPPLYTEXTUREEXTPROC __glewApplyTextureEXT; +GLEW_FUN_EXPORT PFNGLTEXTURELIGHTEXTPROC __glewTextureLightEXT; +GLEW_FUN_EXPORT PFNGLTEXTUREMATERIALEXTPROC __glewTextureMaterialEXT; + +GLEW_FUN_EXPORT PFNGLMULTIDRAWARRAYSEXTPROC __glewMultiDrawArraysEXT; +GLEW_FUN_EXPORT PFNGLMULTIDRAWELEMENTSEXTPROC __glewMultiDrawElementsEXT; + +GLEW_FUN_EXPORT PFNGLSAMPLEMASKEXTPROC __glewSampleMaskEXT; +GLEW_FUN_EXPORT PFNGLSAMPLEPATTERNEXTPROC __glewSamplePatternEXT; + +GLEW_FUN_EXPORT PFNGLCOLORTABLEEXTPROC __glewColorTableEXT; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEEXTPROC __glewGetColorTableEXT; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEPARAMETERFVEXTPROC __glewGetColorTableParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEPARAMETERIVEXTPROC __glewGetColorTableParameterivEXT; + +GLEW_FUN_EXPORT PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC __glewGetPixelTransformParameterfvEXT; +GLEW_FUN_EXPORT PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC __glewGetPixelTransformParameterivEXT; +GLEW_FUN_EXPORT PFNGLPIXELTRANSFORMPARAMETERFEXTPROC __glewPixelTransformParameterfEXT; +GLEW_FUN_EXPORT PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC __glewPixelTransformParameterfvEXT; +GLEW_FUN_EXPORT PFNGLPIXELTRANSFORMPARAMETERIEXTPROC __glewPixelTransformParameteriEXT; +GLEW_FUN_EXPORT PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC __glewPixelTransformParameterivEXT; + +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERFEXTPROC __glewPointParameterfEXT; +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERFVEXTPROC __glewPointParameterfvEXT; + +GLEW_FUN_EXPORT PFNGLPOLYGONOFFSETEXTPROC __glewPolygonOffsetEXT; + +GLEW_FUN_EXPORT PFNGLBEGINSCENEEXTPROC __glewBeginSceneEXT; +GLEW_FUN_EXPORT PFNGLENDSCENEEXTPROC __glewEndSceneEXT; + +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3BEXTPROC __glewSecondaryColor3bEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3BVEXTPROC __glewSecondaryColor3bvEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3DEXTPROC __glewSecondaryColor3dEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3DVEXTPROC __glewSecondaryColor3dvEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3FEXTPROC __glewSecondaryColor3fEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3FVEXTPROC __glewSecondaryColor3fvEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3IEXTPROC __glewSecondaryColor3iEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3IVEXTPROC __glewSecondaryColor3ivEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3SEXTPROC __glewSecondaryColor3sEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3SVEXTPROC __glewSecondaryColor3svEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UBEXTPROC __glewSecondaryColor3ubEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UBVEXTPROC __glewSecondaryColor3ubvEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UIEXTPROC __glewSecondaryColor3uiEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3UIVEXTPROC __glewSecondaryColor3uivEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3USEXTPROC __glewSecondaryColor3usEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3USVEXTPROC __glewSecondaryColor3usvEXT; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLORPOINTEREXTPROC __glewSecondaryColorPointerEXT; + +GLEW_FUN_EXPORT PFNGLACTIVESTENCILFACEEXTPROC __glewActiveStencilFaceEXT; + +GLEW_FUN_EXPORT PFNGLTEXSUBIMAGE1DEXTPROC __glewTexSubImage1DEXT; +GLEW_FUN_EXPORT PFNGLTEXSUBIMAGE2DEXTPROC __glewTexSubImage2DEXT; +GLEW_FUN_EXPORT PFNGLTEXSUBIMAGE3DEXTPROC __glewTexSubImage3DEXT; + +GLEW_FUN_EXPORT PFNGLTEXIMAGE3DEXTPROC __glewTexImage3DEXT; + +GLEW_FUN_EXPORT PFNGLTEXBUFFEREXTPROC __glewTexBufferEXT; + +GLEW_FUN_EXPORT PFNGLCLEARCOLORIIEXTPROC __glewClearColorIiEXT; +GLEW_FUN_EXPORT PFNGLCLEARCOLORIUIEXTPROC __glewClearColorIuiEXT; +GLEW_FUN_EXPORT PFNGLGETTEXPARAMETERIIVEXTPROC __glewGetTexParameterIivEXT; +GLEW_FUN_EXPORT PFNGLGETTEXPARAMETERIUIVEXTPROC __glewGetTexParameterIuivEXT; +GLEW_FUN_EXPORT PFNGLTEXPARAMETERIIVEXTPROC __glewTexParameterIivEXT; +GLEW_FUN_EXPORT PFNGLTEXPARAMETERIUIVEXTPROC __glewTexParameterIuivEXT; + +GLEW_FUN_EXPORT PFNGLARETEXTURESRESIDENTEXTPROC __glewAreTexturesResidentEXT; +GLEW_FUN_EXPORT PFNGLBINDTEXTUREEXTPROC __glewBindTextureEXT; +GLEW_FUN_EXPORT PFNGLDELETETEXTURESEXTPROC __glewDeleteTexturesEXT; +GLEW_FUN_EXPORT PFNGLGENTEXTURESEXTPROC __glewGenTexturesEXT; +GLEW_FUN_EXPORT PFNGLISTEXTUREEXTPROC __glewIsTextureEXT; +GLEW_FUN_EXPORT PFNGLPRIORITIZETEXTURESEXTPROC __glewPrioritizeTexturesEXT; + +GLEW_FUN_EXPORT PFNGLTEXTURENORMALEXTPROC __glewTextureNormalEXT; + +GLEW_FUN_EXPORT PFNGLGETQUERYOBJECTI64VEXTPROC __glewGetQueryObjecti64vEXT; +GLEW_FUN_EXPORT PFNGLGETQUERYOBJECTUI64VEXTPROC __glewGetQueryObjectui64vEXT; + +GLEW_FUN_EXPORT PFNGLBEGINTRANSFORMFEEDBACKEXTPROC __glewBeginTransformFeedbackEXT; +GLEW_FUN_EXPORT PFNGLBINDBUFFERBASEEXTPROC __glewBindBufferBaseEXT; +GLEW_FUN_EXPORT PFNGLBINDBUFFEROFFSETEXTPROC __glewBindBufferOffsetEXT; +GLEW_FUN_EXPORT PFNGLBINDBUFFERRANGEEXTPROC __glewBindBufferRangeEXT; +GLEW_FUN_EXPORT PFNGLENDTRANSFORMFEEDBACKEXTPROC __glewEndTransformFeedbackEXT; +GLEW_FUN_EXPORT PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC __glewGetTransformFeedbackVaryingEXT; +GLEW_FUN_EXPORT PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC __glewTransformFeedbackVaryingsEXT; + +GLEW_FUN_EXPORT PFNGLARRAYELEMENTEXTPROC __glewArrayElementEXT; +GLEW_FUN_EXPORT PFNGLCOLORPOINTEREXTPROC __glewColorPointerEXT; +GLEW_FUN_EXPORT PFNGLDRAWARRAYSEXTPROC __glewDrawArraysEXT; +GLEW_FUN_EXPORT PFNGLEDGEFLAGPOINTEREXTPROC __glewEdgeFlagPointerEXT; +GLEW_FUN_EXPORT PFNGLGETPOINTERVEXTPROC __glewGetPointervEXT; +GLEW_FUN_EXPORT PFNGLINDEXPOINTEREXTPROC __glewIndexPointerEXT; +GLEW_FUN_EXPORT PFNGLNORMALPOINTEREXTPROC __glewNormalPointerEXT; +GLEW_FUN_EXPORT PFNGLTEXCOORDPOINTEREXTPROC __glewTexCoordPointerEXT; +GLEW_FUN_EXPORT PFNGLVERTEXPOINTEREXTPROC __glewVertexPointerEXT; + +GLEW_FUN_EXPORT PFNGLBEGINVERTEXSHADEREXTPROC __glewBeginVertexShaderEXT; +GLEW_FUN_EXPORT PFNGLBINDLIGHTPARAMETEREXTPROC __glewBindLightParameterEXT; +GLEW_FUN_EXPORT PFNGLBINDMATERIALPARAMETEREXTPROC __glewBindMaterialParameterEXT; +GLEW_FUN_EXPORT PFNGLBINDPARAMETEREXTPROC __glewBindParameterEXT; +GLEW_FUN_EXPORT PFNGLBINDTEXGENPARAMETEREXTPROC __glewBindTexGenParameterEXT; +GLEW_FUN_EXPORT PFNGLBINDTEXTUREUNITPARAMETEREXTPROC __glewBindTextureUnitParameterEXT; +GLEW_FUN_EXPORT PFNGLBINDVERTEXSHADEREXTPROC __glewBindVertexShaderEXT; +GLEW_FUN_EXPORT PFNGLDELETEVERTEXSHADEREXTPROC __glewDeleteVertexShaderEXT; +GLEW_FUN_EXPORT PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC __glewDisableVariantClientStateEXT; +GLEW_FUN_EXPORT PFNGLENABLEVARIANTCLIENTSTATEEXTPROC __glewEnableVariantClientStateEXT; +GLEW_FUN_EXPORT PFNGLENDVERTEXSHADEREXTPROC __glewEndVertexShaderEXT; +GLEW_FUN_EXPORT PFNGLEXTRACTCOMPONENTEXTPROC __glewExtractComponentEXT; +GLEW_FUN_EXPORT PFNGLGENSYMBOLSEXTPROC __glewGenSymbolsEXT; +GLEW_FUN_EXPORT PFNGLGENVERTEXSHADERSEXTPROC __glewGenVertexShadersEXT; +GLEW_FUN_EXPORT PFNGLGETINVARIANTBOOLEANVEXTPROC __glewGetInvariantBooleanvEXT; +GLEW_FUN_EXPORT PFNGLGETINVARIANTFLOATVEXTPROC __glewGetInvariantFloatvEXT; +GLEW_FUN_EXPORT PFNGLGETINVARIANTINTEGERVEXTPROC __glewGetInvariantIntegervEXT; +GLEW_FUN_EXPORT PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC __glewGetLocalConstantBooleanvEXT; +GLEW_FUN_EXPORT PFNGLGETLOCALCONSTANTFLOATVEXTPROC __glewGetLocalConstantFloatvEXT; +GLEW_FUN_EXPORT PFNGLGETLOCALCONSTANTINTEGERVEXTPROC __glewGetLocalConstantIntegervEXT; +GLEW_FUN_EXPORT PFNGLGETVARIANTBOOLEANVEXTPROC __glewGetVariantBooleanvEXT; +GLEW_FUN_EXPORT PFNGLGETVARIANTFLOATVEXTPROC __glewGetVariantFloatvEXT; +GLEW_FUN_EXPORT PFNGLGETVARIANTINTEGERVEXTPROC __glewGetVariantIntegervEXT; +GLEW_FUN_EXPORT PFNGLGETVARIANTPOINTERVEXTPROC __glewGetVariantPointervEXT; +GLEW_FUN_EXPORT PFNGLINSERTCOMPONENTEXTPROC __glewInsertComponentEXT; +GLEW_FUN_EXPORT PFNGLISVARIANTENABLEDEXTPROC __glewIsVariantEnabledEXT; +GLEW_FUN_EXPORT PFNGLSETINVARIANTEXTPROC __glewSetInvariantEXT; +GLEW_FUN_EXPORT PFNGLSETLOCALCONSTANTEXTPROC __glewSetLocalConstantEXT; +GLEW_FUN_EXPORT PFNGLSHADEROP1EXTPROC __glewShaderOp1EXT; +GLEW_FUN_EXPORT PFNGLSHADEROP2EXTPROC __glewShaderOp2EXT; +GLEW_FUN_EXPORT PFNGLSHADEROP3EXTPROC __glewShaderOp3EXT; +GLEW_FUN_EXPORT PFNGLSWIZZLEEXTPROC __glewSwizzleEXT; +GLEW_FUN_EXPORT PFNGLVARIANTPOINTEREXTPROC __glewVariantPointerEXT; +GLEW_FUN_EXPORT PFNGLVARIANTBVEXTPROC __glewVariantbvEXT; +GLEW_FUN_EXPORT PFNGLVARIANTDVEXTPROC __glewVariantdvEXT; +GLEW_FUN_EXPORT PFNGLVARIANTFVEXTPROC __glewVariantfvEXT; +GLEW_FUN_EXPORT PFNGLVARIANTIVEXTPROC __glewVariantivEXT; +GLEW_FUN_EXPORT PFNGLVARIANTSVEXTPROC __glewVariantsvEXT; +GLEW_FUN_EXPORT PFNGLVARIANTUBVEXTPROC __glewVariantubvEXT; +GLEW_FUN_EXPORT PFNGLVARIANTUIVEXTPROC __glewVariantuivEXT; +GLEW_FUN_EXPORT PFNGLVARIANTUSVEXTPROC __glewVariantusvEXT; +GLEW_FUN_EXPORT PFNGLWRITEMASKEXTPROC __glewWriteMaskEXT; + +GLEW_FUN_EXPORT PFNGLVERTEXWEIGHTPOINTEREXTPROC __glewVertexWeightPointerEXT; +GLEW_FUN_EXPORT PFNGLVERTEXWEIGHTFEXTPROC __glewVertexWeightfEXT; +GLEW_FUN_EXPORT PFNGLVERTEXWEIGHTFVEXTPROC __glewVertexWeightfvEXT; + +GLEW_FUN_EXPORT PFNGLFRAMETERMINATORGREMEDYPROC __glewFrameTerminatorGREMEDY; + +GLEW_FUN_EXPORT PFNGLSTRINGMARKERGREMEDYPROC __glewStringMarkerGREMEDY; + +GLEW_FUN_EXPORT PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC __glewGetImageTransformParameterfvHP; +GLEW_FUN_EXPORT PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC __glewGetImageTransformParameterivHP; +GLEW_FUN_EXPORT PFNGLIMAGETRANSFORMPARAMETERFHPPROC __glewImageTransformParameterfHP; +GLEW_FUN_EXPORT PFNGLIMAGETRANSFORMPARAMETERFVHPPROC __glewImageTransformParameterfvHP; +GLEW_FUN_EXPORT PFNGLIMAGETRANSFORMPARAMETERIHPPROC __glewImageTransformParameteriHP; +GLEW_FUN_EXPORT PFNGLIMAGETRANSFORMPARAMETERIVHPPROC __glewImageTransformParameterivHP; + +GLEW_FUN_EXPORT PFNGLMULTIMODEDRAWARRAYSIBMPROC __glewMultiModeDrawArraysIBM; +GLEW_FUN_EXPORT PFNGLMULTIMODEDRAWELEMENTSIBMPROC __glewMultiModeDrawElementsIBM; + +GLEW_FUN_EXPORT PFNGLCOLORPOINTERLISTIBMPROC __glewColorPointerListIBM; +GLEW_FUN_EXPORT PFNGLEDGEFLAGPOINTERLISTIBMPROC __glewEdgeFlagPointerListIBM; +GLEW_FUN_EXPORT PFNGLFOGCOORDPOINTERLISTIBMPROC __glewFogCoordPointerListIBM; +GLEW_FUN_EXPORT PFNGLINDEXPOINTERLISTIBMPROC __glewIndexPointerListIBM; +GLEW_FUN_EXPORT PFNGLNORMALPOINTERLISTIBMPROC __glewNormalPointerListIBM; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLORPOINTERLISTIBMPROC __glewSecondaryColorPointerListIBM; +GLEW_FUN_EXPORT PFNGLTEXCOORDPOINTERLISTIBMPROC __glewTexCoordPointerListIBM; +GLEW_FUN_EXPORT PFNGLVERTEXPOINTERLISTIBMPROC __glewVertexPointerListIBM; + +GLEW_FUN_EXPORT PFNGLCOLORPOINTERVINTELPROC __glewColorPointervINTEL; +GLEW_FUN_EXPORT PFNGLNORMALPOINTERVINTELPROC __glewNormalPointervINTEL; +GLEW_FUN_EXPORT PFNGLTEXCOORDPOINTERVINTELPROC __glewTexCoordPointervINTEL; +GLEW_FUN_EXPORT PFNGLVERTEXPOINTERVINTELPROC __glewVertexPointervINTEL; + +GLEW_FUN_EXPORT PFNGLTEXSCISSORFUNCINTELPROC __glewTexScissorFuncINTEL; +GLEW_FUN_EXPORT PFNGLTEXSCISSORINTELPROC __glewTexScissorINTEL; + +GLEW_FUN_EXPORT PFNGLBUFFERREGIONENABLEDEXTPROC __glewBufferRegionEnabledEXT; +GLEW_FUN_EXPORT PFNGLDELETEBUFFERREGIONEXTPROC __glewDeleteBufferRegionEXT; +GLEW_FUN_EXPORT PFNGLDRAWBUFFERREGIONEXTPROC __glewDrawBufferRegionEXT; +GLEW_FUN_EXPORT PFNGLNEWBUFFERREGIONEXTPROC __glewNewBufferRegionEXT; +GLEW_FUN_EXPORT PFNGLREADBUFFERREGIONEXTPROC __glewReadBufferRegionEXT; + +GLEW_FUN_EXPORT PFNGLRESIZEBUFFERSMESAPROC __glewResizeBuffersMESA; + +GLEW_FUN_EXPORT PFNGLWINDOWPOS2DMESAPROC __glewWindowPos2dMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2DVMESAPROC __glewWindowPos2dvMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2FMESAPROC __glewWindowPos2fMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2FVMESAPROC __glewWindowPos2fvMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2IMESAPROC __glewWindowPos2iMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2IVMESAPROC __glewWindowPos2ivMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2SMESAPROC __glewWindowPos2sMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS2SVMESAPROC __glewWindowPos2svMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3DMESAPROC __glewWindowPos3dMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3DVMESAPROC __glewWindowPos3dvMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3FMESAPROC __glewWindowPos3fMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3FVMESAPROC __glewWindowPos3fvMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3IMESAPROC __glewWindowPos3iMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3IVMESAPROC __glewWindowPos3ivMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3SMESAPROC __glewWindowPos3sMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS3SVMESAPROC __glewWindowPos3svMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4DMESAPROC __glewWindowPos4dMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4DVMESAPROC __glewWindowPos4dvMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4FMESAPROC __glewWindowPos4fMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4FVMESAPROC __glewWindowPos4fvMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4IMESAPROC __glewWindowPos4iMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4IVMESAPROC __glewWindowPos4ivMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4SMESAPROC __glewWindowPos4sMESA; +GLEW_FUN_EXPORT PFNGLWINDOWPOS4SVMESAPROC __glewWindowPos4svMESA; + +GLEW_FUN_EXPORT PFNGLBEGINCONDITIONALRENDERNVPROC __glewBeginConditionalRenderNV; +GLEW_FUN_EXPORT PFNGLENDCONDITIONALRENDERNVPROC __glewEndConditionalRenderNV; + +GLEW_FUN_EXPORT PFNGLCLEARDEPTHDNVPROC __glewClearDepthdNV; +GLEW_FUN_EXPORT PFNGLDEPTHBOUNDSDNVPROC __glewDepthBoundsdNV; +GLEW_FUN_EXPORT PFNGLDEPTHRANGEDNVPROC __glewDepthRangedNV; + +GLEW_FUN_EXPORT PFNGLEVALMAPSNVPROC __glewEvalMapsNV; +GLEW_FUN_EXPORT PFNGLGETMAPATTRIBPARAMETERFVNVPROC __glewGetMapAttribParameterfvNV; +GLEW_FUN_EXPORT PFNGLGETMAPATTRIBPARAMETERIVNVPROC __glewGetMapAttribParameterivNV; +GLEW_FUN_EXPORT PFNGLGETMAPCONTROLPOINTSNVPROC __glewGetMapControlPointsNV; +GLEW_FUN_EXPORT PFNGLGETMAPPARAMETERFVNVPROC __glewGetMapParameterfvNV; +GLEW_FUN_EXPORT PFNGLGETMAPPARAMETERIVNVPROC __glewGetMapParameterivNV; +GLEW_FUN_EXPORT PFNGLMAPCONTROLPOINTSNVPROC __glewMapControlPointsNV; +GLEW_FUN_EXPORT PFNGLMAPPARAMETERFVNVPROC __glewMapParameterfvNV; +GLEW_FUN_EXPORT PFNGLMAPPARAMETERIVNVPROC __glewMapParameterivNV; + +GLEW_FUN_EXPORT PFNGLGETMULTISAMPLEFVNVPROC __glewGetMultisamplefvNV; +GLEW_FUN_EXPORT PFNGLSAMPLEMASKINDEXEDNVPROC __glewSampleMaskIndexedNV; +GLEW_FUN_EXPORT PFNGLTEXRENDERBUFFERNVPROC __glewTexRenderbufferNV; + +GLEW_FUN_EXPORT PFNGLDELETEFENCESNVPROC __glewDeleteFencesNV; +GLEW_FUN_EXPORT PFNGLFINISHFENCENVPROC __glewFinishFenceNV; +GLEW_FUN_EXPORT PFNGLGENFENCESNVPROC __glewGenFencesNV; +GLEW_FUN_EXPORT PFNGLGETFENCEIVNVPROC __glewGetFenceivNV; +GLEW_FUN_EXPORT PFNGLISFENCENVPROC __glewIsFenceNV; +GLEW_FUN_EXPORT PFNGLSETFENCENVPROC __glewSetFenceNV; +GLEW_FUN_EXPORT PFNGLTESTFENCENVPROC __glewTestFenceNV; + +GLEW_FUN_EXPORT PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC __glewGetProgramNamedParameterdvNV; +GLEW_FUN_EXPORT PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC __glewGetProgramNamedParameterfvNV; +GLEW_FUN_EXPORT PFNGLPROGRAMNAMEDPARAMETER4DNVPROC __glewProgramNamedParameter4dNV; +GLEW_FUN_EXPORT PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC __glewProgramNamedParameter4dvNV; +GLEW_FUN_EXPORT PFNGLPROGRAMNAMEDPARAMETER4FNVPROC __glewProgramNamedParameter4fNV; +GLEW_FUN_EXPORT PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC __glewProgramNamedParameter4fvNV; + +GLEW_FUN_EXPORT PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC __glewRenderbufferStorageMultisampleCoverageNV; + +GLEW_FUN_EXPORT PFNGLPROGRAMVERTEXLIMITNVPROC __glewProgramVertexLimitNV; + +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETERI4INVPROC __glewProgramEnvParameterI4iNV; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETERI4IVNVPROC __glewProgramEnvParameterI4ivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETERI4UINVPROC __glewProgramEnvParameterI4uiNV; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETERI4UIVNVPROC __glewProgramEnvParameterI4uivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETERSI4IVNVPROC __glewProgramEnvParametersI4ivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC __glewProgramEnvParametersI4uivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETERI4INVPROC __glewProgramLocalParameterI4iNV; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC __glewProgramLocalParameterI4ivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETERI4UINVPROC __glewProgramLocalParameterI4uiNV; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC __glewProgramLocalParameterI4uivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC __glewProgramLocalParametersI4ivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC __glewProgramLocalParametersI4uivNV; + +GLEW_FUN_EXPORT PFNGLCOLOR3HNVPROC __glewColor3hNV; +GLEW_FUN_EXPORT PFNGLCOLOR3HVNVPROC __glewColor3hvNV; +GLEW_FUN_EXPORT PFNGLCOLOR4HNVPROC __glewColor4hNV; +GLEW_FUN_EXPORT PFNGLCOLOR4HVNVPROC __glewColor4hvNV; +GLEW_FUN_EXPORT PFNGLFOGCOORDHNVPROC __glewFogCoordhNV; +GLEW_FUN_EXPORT PFNGLFOGCOORDHVNVPROC __glewFogCoordhvNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1HNVPROC __glewMultiTexCoord1hNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD1HVNVPROC __glewMultiTexCoord1hvNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2HNVPROC __glewMultiTexCoord2hNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD2HVNVPROC __glewMultiTexCoord2hvNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3HNVPROC __glewMultiTexCoord3hNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD3HVNVPROC __glewMultiTexCoord3hvNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4HNVPROC __glewMultiTexCoord4hNV; +GLEW_FUN_EXPORT PFNGLMULTITEXCOORD4HVNVPROC __glewMultiTexCoord4hvNV; +GLEW_FUN_EXPORT PFNGLNORMAL3HNVPROC __glewNormal3hNV; +GLEW_FUN_EXPORT PFNGLNORMAL3HVNVPROC __glewNormal3hvNV; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3HNVPROC __glewSecondaryColor3hNV; +GLEW_FUN_EXPORT PFNGLSECONDARYCOLOR3HVNVPROC __glewSecondaryColor3hvNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD1HNVPROC __glewTexCoord1hNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD1HVNVPROC __glewTexCoord1hvNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD2HNVPROC __glewTexCoord2hNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD2HVNVPROC __glewTexCoord2hvNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD3HNVPROC __glewTexCoord3hNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD3HVNVPROC __glewTexCoord3hvNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD4HNVPROC __glewTexCoord4hNV; +GLEW_FUN_EXPORT PFNGLTEXCOORD4HVNVPROC __glewTexCoord4hvNV; +GLEW_FUN_EXPORT PFNGLVERTEX2HNVPROC __glewVertex2hNV; +GLEW_FUN_EXPORT PFNGLVERTEX2HVNVPROC __glewVertex2hvNV; +GLEW_FUN_EXPORT PFNGLVERTEX3HNVPROC __glewVertex3hNV; +GLEW_FUN_EXPORT PFNGLVERTEX3HVNVPROC __glewVertex3hvNV; +GLEW_FUN_EXPORT PFNGLVERTEX4HNVPROC __glewVertex4hNV; +GLEW_FUN_EXPORT PFNGLVERTEX4HVNVPROC __glewVertex4hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1HNVPROC __glewVertexAttrib1hNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1HVNVPROC __glewVertexAttrib1hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2HNVPROC __glewVertexAttrib2hNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2HVNVPROC __glewVertexAttrib2hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3HNVPROC __glewVertexAttrib3hNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3HVNVPROC __glewVertexAttrib3hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4HNVPROC __glewVertexAttrib4hNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4HVNVPROC __glewVertexAttrib4hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS1HVNVPROC __glewVertexAttribs1hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS2HVNVPROC __glewVertexAttribs2hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS3HVNVPROC __glewVertexAttribs3hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS4HVNVPROC __glewVertexAttribs4hvNV; +GLEW_FUN_EXPORT PFNGLVERTEXWEIGHTHNVPROC __glewVertexWeighthNV; +GLEW_FUN_EXPORT PFNGLVERTEXWEIGHTHVNVPROC __glewVertexWeighthvNV; + +GLEW_FUN_EXPORT PFNGLBEGINOCCLUSIONQUERYNVPROC __glewBeginOcclusionQueryNV; +GLEW_FUN_EXPORT PFNGLDELETEOCCLUSIONQUERIESNVPROC __glewDeleteOcclusionQueriesNV; +GLEW_FUN_EXPORT PFNGLENDOCCLUSIONQUERYNVPROC __glewEndOcclusionQueryNV; +GLEW_FUN_EXPORT PFNGLGENOCCLUSIONQUERIESNVPROC __glewGenOcclusionQueriesNV; +GLEW_FUN_EXPORT PFNGLGETOCCLUSIONQUERYIVNVPROC __glewGetOcclusionQueryivNV; +GLEW_FUN_EXPORT PFNGLGETOCCLUSIONQUERYUIVNVPROC __glewGetOcclusionQueryuivNV; +GLEW_FUN_EXPORT PFNGLISOCCLUSIONQUERYNVPROC __glewIsOcclusionQueryNV; + +GLEW_FUN_EXPORT PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC __glewProgramBufferParametersIivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC __glewProgramBufferParametersIuivNV; +GLEW_FUN_EXPORT PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC __glewProgramBufferParametersfvNV; + +GLEW_FUN_EXPORT PFNGLFLUSHPIXELDATARANGENVPROC __glewFlushPixelDataRangeNV; +GLEW_FUN_EXPORT PFNGLPIXELDATARANGENVPROC __glewPixelDataRangeNV; + +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERINVPROC __glewPointParameteriNV; +GLEW_FUN_EXPORT PFNGLPOINTPARAMETERIVNVPROC __glewPointParameterivNV; + +GLEW_FUN_EXPORT PFNGLGETVIDEOI64VNVPROC __glewGetVideoi64vNV; +GLEW_FUN_EXPORT PFNGLGETVIDEOIVNVPROC __glewGetVideoivNV; +GLEW_FUN_EXPORT PFNGLGETVIDEOUI64VNVPROC __glewGetVideoui64vNV; +GLEW_FUN_EXPORT PFNGLGETVIDEOUIVNVPROC __glewGetVideouivNV; +GLEW_FUN_EXPORT PFNGLPRESENTFRAMEDUALFILLNVPROC __glewPresentFrameDualFillNV; +GLEW_FUN_EXPORT PFNGLPRESENTFRAMEKEYEDNVPROC __glewPresentFrameKeyedNV; +GLEW_FUN_EXPORT PFNGLVIDEOPARAMETERIVNVPROC __glewVideoParameterivNV; + +GLEW_FUN_EXPORT PFNGLPRIMITIVERESTARTINDEXNVPROC __glewPrimitiveRestartIndexNV; +GLEW_FUN_EXPORT PFNGLPRIMITIVERESTARTNVPROC __glewPrimitiveRestartNV; + +GLEW_FUN_EXPORT PFNGLCOMBINERINPUTNVPROC __glewCombinerInputNV; +GLEW_FUN_EXPORT PFNGLCOMBINEROUTPUTNVPROC __glewCombinerOutputNV; +GLEW_FUN_EXPORT PFNGLCOMBINERPARAMETERFNVPROC __glewCombinerParameterfNV; +GLEW_FUN_EXPORT PFNGLCOMBINERPARAMETERFVNVPROC __glewCombinerParameterfvNV; +GLEW_FUN_EXPORT PFNGLCOMBINERPARAMETERINVPROC __glewCombinerParameteriNV; +GLEW_FUN_EXPORT PFNGLCOMBINERPARAMETERIVNVPROC __glewCombinerParameterivNV; +GLEW_FUN_EXPORT PFNGLFINALCOMBINERINPUTNVPROC __glewFinalCombinerInputNV; +GLEW_FUN_EXPORT PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC __glewGetCombinerInputParameterfvNV; +GLEW_FUN_EXPORT PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC __glewGetCombinerInputParameterivNV; +GLEW_FUN_EXPORT PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC __glewGetCombinerOutputParameterfvNV; +GLEW_FUN_EXPORT PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC __glewGetCombinerOutputParameterivNV; +GLEW_FUN_EXPORT PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC __glewGetFinalCombinerInputParameterfvNV; +GLEW_FUN_EXPORT PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC __glewGetFinalCombinerInputParameterivNV; + +GLEW_FUN_EXPORT PFNGLCOMBINERSTAGEPARAMETERFVNVPROC __glewCombinerStageParameterfvNV; +GLEW_FUN_EXPORT PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC __glewGetCombinerStageParameterfvNV; + +GLEW_FUN_EXPORT PFNGLACTIVEVARYINGNVPROC __glewActiveVaryingNV; +GLEW_FUN_EXPORT PFNGLBEGINTRANSFORMFEEDBACKNVPROC __glewBeginTransformFeedbackNV; +GLEW_FUN_EXPORT PFNGLBINDBUFFERBASENVPROC __glewBindBufferBaseNV; +GLEW_FUN_EXPORT PFNGLBINDBUFFEROFFSETNVPROC __glewBindBufferOffsetNV; +GLEW_FUN_EXPORT PFNGLBINDBUFFERRANGENVPROC __glewBindBufferRangeNV; +GLEW_FUN_EXPORT PFNGLENDTRANSFORMFEEDBACKNVPROC __glewEndTransformFeedbackNV; +GLEW_FUN_EXPORT PFNGLGETACTIVEVARYINGNVPROC __glewGetActiveVaryingNV; +GLEW_FUN_EXPORT PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC __glewGetTransformFeedbackVaryingNV; +GLEW_FUN_EXPORT PFNGLGETVARYINGLOCATIONNVPROC __glewGetVaryingLocationNV; +GLEW_FUN_EXPORT PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC __glewTransformFeedbackAttribsNV; +GLEW_FUN_EXPORT PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC __glewTransformFeedbackVaryingsNV; + +GLEW_FUN_EXPORT PFNGLFLUSHVERTEXARRAYRANGENVPROC __glewFlushVertexArrayRangeNV; +GLEW_FUN_EXPORT PFNGLVERTEXARRAYRANGENVPROC __glewVertexArrayRangeNV; + +GLEW_FUN_EXPORT PFNGLAREPROGRAMSRESIDENTNVPROC __glewAreProgramsResidentNV; +GLEW_FUN_EXPORT PFNGLBINDPROGRAMNVPROC __glewBindProgramNV; +GLEW_FUN_EXPORT PFNGLDELETEPROGRAMSNVPROC __glewDeleteProgramsNV; +GLEW_FUN_EXPORT PFNGLEXECUTEPROGRAMNVPROC __glewExecuteProgramNV; +GLEW_FUN_EXPORT PFNGLGENPROGRAMSNVPROC __glewGenProgramsNV; +GLEW_FUN_EXPORT PFNGLGETPROGRAMPARAMETERDVNVPROC __glewGetProgramParameterdvNV; +GLEW_FUN_EXPORT PFNGLGETPROGRAMPARAMETERFVNVPROC __glewGetProgramParameterfvNV; +GLEW_FUN_EXPORT PFNGLGETPROGRAMSTRINGNVPROC __glewGetProgramStringNV; +GLEW_FUN_EXPORT PFNGLGETPROGRAMIVNVPROC __glewGetProgramivNV; +GLEW_FUN_EXPORT PFNGLGETTRACKMATRIXIVNVPROC __glewGetTrackMatrixivNV; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBPOINTERVNVPROC __glewGetVertexAttribPointervNV; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBDVNVPROC __glewGetVertexAttribdvNV; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBFVNVPROC __glewGetVertexAttribfvNV; +GLEW_FUN_EXPORT PFNGLGETVERTEXATTRIBIVNVPROC __glewGetVertexAttribivNV; +GLEW_FUN_EXPORT PFNGLISPROGRAMNVPROC __glewIsProgramNV; +GLEW_FUN_EXPORT PFNGLLOADPROGRAMNVPROC __glewLoadProgramNV; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETER4DNVPROC __glewProgramParameter4dNV; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETER4DVNVPROC __glewProgramParameter4dvNV; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETER4FNVPROC __glewProgramParameter4fNV; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETER4FVNVPROC __glewProgramParameter4fvNV; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETERS4DVNVPROC __glewProgramParameters4dvNV; +GLEW_FUN_EXPORT PFNGLPROGRAMPARAMETERS4FVNVPROC __glewProgramParameters4fvNV; +GLEW_FUN_EXPORT PFNGLREQUESTRESIDENTPROGRAMSNVPROC __glewRequestResidentProgramsNV; +GLEW_FUN_EXPORT PFNGLTRACKMATRIXNVPROC __glewTrackMatrixNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1DNVPROC __glewVertexAttrib1dNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1DVNVPROC __glewVertexAttrib1dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1FNVPROC __glewVertexAttrib1fNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1FVNVPROC __glewVertexAttrib1fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1SNVPROC __glewVertexAttrib1sNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB1SVNVPROC __glewVertexAttrib1svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2DNVPROC __glewVertexAttrib2dNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2DVNVPROC __glewVertexAttrib2dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2FNVPROC __glewVertexAttrib2fNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2FVNVPROC __glewVertexAttrib2fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2SNVPROC __glewVertexAttrib2sNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB2SVNVPROC __glewVertexAttrib2svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3DNVPROC __glewVertexAttrib3dNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3DVNVPROC __glewVertexAttrib3dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3FNVPROC __glewVertexAttrib3fNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3FVNVPROC __glewVertexAttrib3fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3SNVPROC __glewVertexAttrib3sNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB3SVNVPROC __glewVertexAttrib3svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4DNVPROC __glewVertexAttrib4dNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4DVNVPROC __glewVertexAttrib4dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4FNVPROC __glewVertexAttrib4fNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4FVNVPROC __glewVertexAttrib4fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4SNVPROC __glewVertexAttrib4sNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4SVNVPROC __glewVertexAttrib4svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4UBNVPROC __glewVertexAttrib4ubNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIB4UBVNVPROC __glewVertexAttrib4ubvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBPOINTERNVPROC __glewVertexAttribPointerNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS1DVNVPROC __glewVertexAttribs1dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS1FVNVPROC __glewVertexAttribs1fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS1SVNVPROC __glewVertexAttribs1svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS2DVNVPROC __glewVertexAttribs2dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS2FVNVPROC __glewVertexAttribs2fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS2SVNVPROC __glewVertexAttribs2svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS3DVNVPROC __glewVertexAttribs3dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS3FVNVPROC __glewVertexAttribs3fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS3SVNVPROC __glewVertexAttribs3svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS4DVNVPROC __glewVertexAttribs4dvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS4FVNVPROC __glewVertexAttribs4fvNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS4SVNVPROC __glewVertexAttribs4svNV; +GLEW_FUN_EXPORT PFNGLVERTEXATTRIBS4UBVNVPROC __glewVertexAttribs4ubvNV; + +GLEW_FUN_EXPORT PFNGLCLEARDEPTHFOESPROC __glewClearDepthfOES; +GLEW_FUN_EXPORT PFNGLCLIPPLANEFOESPROC __glewClipPlanefOES; +GLEW_FUN_EXPORT PFNGLDEPTHRANGEFOESPROC __glewDepthRangefOES; +GLEW_FUN_EXPORT PFNGLFRUSTUMFOESPROC __glewFrustumfOES; +GLEW_FUN_EXPORT PFNGLGETCLIPPLANEFOESPROC __glewGetClipPlanefOES; +GLEW_FUN_EXPORT PFNGLORTHOFOESPROC __glewOrthofOES; + +GLEW_FUN_EXPORT PFNGLDETAILTEXFUNCSGISPROC __glewDetailTexFuncSGIS; +GLEW_FUN_EXPORT PFNGLGETDETAILTEXFUNCSGISPROC __glewGetDetailTexFuncSGIS; + +GLEW_FUN_EXPORT PFNGLFOGFUNCSGISPROC __glewFogFuncSGIS; +GLEW_FUN_EXPORT PFNGLGETFOGFUNCSGISPROC __glewGetFogFuncSGIS; + +GLEW_FUN_EXPORT PFNGLSAMPLEMASKSGISPROC __glewSampleMaskSGIS; +GLEW_FUN_EXPORT PFNGLSAMPLEPATTERNSGISPROC __glewSamplePatternSGIS; + +GLEW_FUN_EXPORT PFNGLGETSHARPENTEXFUNCSGISPROC __glewGetSharpenTexFuncSGIS; +GLEW_FUN_EXPORT PFNGLSHARPENTEXFUNCSGISPROC __glewSharpenTexFuncSGIS; + +GLEW_FUN_EXPORT PFNGLTEXIMAGE4DSGISPROC __glewTexImage4DSGIS; +GLEW_FUN_EXPORT PFNGLTEXSUBIMAGE4DSGISPROC __glewTexSubImage4DSGIS; + +GLEW_FUN_EXPORT PFNGLGETTEXFILTERFUNCSGISPROC __glewGetTexFilterFuncSGIS; +GLEW_FUN_EXPORT PFNGLTEXFILTERFUNCSGISPROC __glewTexFilterFuncSGIS; + +GLEW_FUN_EXPORT PFNGLASYNCMARKERSGIXPROC __glewAsyncMarkerSGIX; +GLEW_FUN_EXPORT PFNGLDELETEASYNCMARKERSSGIXPROC __glewDeleteAsyncMarkersSGIX; +GLEW_FUN_EXPORT PFNGLFINISHASYNCSGIXPROC __glewFinishAsyncSGIX; +GLEW_FUN_EXPORT PFNGLGENASYNCMARKERSSGIXPROC __glewGenAsyncMarkersSGIX; +GLEW_FUN_EXPORT PFNGLISASYNCMARKERSGIXPROC __glewIsAsyncMarkerSGIX; +GLEW_FUN_EXPORT PFNGLPOLLASYNCSGIXPROC __glewPollAsyncSGIX; + +GLEW_FUN_EXPORT PFNGLFLUSHRASTERSGIXPROC __glewFlushRasterSGIX; + +GLEW_FUN_EXPORT PFNGLTEXTUREFOGSGIXPROC __glewTextureFogSGIX; + +GLEW_FUN_EXPORT PFNGLFRAGMENTCOLORMATERIALSGIXPROC __glewFragmentColorMaterialSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELFSGIXPROC __glewFragmentLightModelfSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELFVSGIXPROC __glewFragmentLightModelfvSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELISGIXPROC __glewFragmentLightModeliSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTMODELIVSGIXPROC __glewFragmentLightModelivSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTFSGIXPROC __glewFragmentLightfSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTFVSGIXPROC __glewFragmentLightfvSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTISGIXPROC __glewFragmentLightiSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTLIGHTIVSGIXPROC __glewFragmentLightivSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALFSGIXPROC __glewFragmentMaterialfSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALFVSGIXPROC __glewFragmentMaterialfvSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALISGIXPROC __glewFragmentMaterialiSGIX; +GLEW_FUN_EXPORT PFNGLFRAGMENTMATERIALIVSGIXPROC __glewFragmentMaterialivSGIX; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTLIGHTFVSGIXPROC __glewGetFragmentLightfvSGIX; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTLIGHTIVSGIXPROC __glewGetFragmentLightivSGIX; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTMATERIALFVSGIXPROC __glewGetFragmentMaterialfvSGIX; +GLEW_FUN_EXPORT PFNGLGETFRAGMENTMATERIALIVSGIXPROC __glewGetFragmentMaterialivSGIX; + +GLEW_FUN_EXPORT PFNGLFRAMEZOOMSGIXPROC __glewFrameZoomSGIX; + +GLEW_FUN_EXPORT PFNGLPIXELTEXGENSGIXPROC __glewPixelTexGenSGIX; + +GLEW_FUN_EXPORT PFNGLREFERENCEPLANESGIXPROC __glewReferencePlaneSGIX; + +GLEW_FUN_EXPORT PFNGLSPRITEPARAMETERFSGIXPROC __glewSpriteParameterfSGIX; +GLEW_FUN_EXPORT PFNGLSPRITEPARAMETERFVSGIXPROC __glewSpriteParameterfvSGIX; +GLEW_FUN_EXPORT PFNGLSPRITEPARAMETERISGIXPROC __glewSpriteParameteriSGIX; +GLEW_FUN_EXPORT PFNGLSPRITEPARAMETERIVSGIXPROC __glewSpriteParameterivSGIX; + +GLEW_FUN_EXPORT PFNGLTAGSAMPLEBUFFERSGIXPROC __glewTagSampleBufferSGIX; + +GLEW_FUN_EXPORT PFNGLCOLORTABLEPARAMETERFVSGIPROC __glewColorTableParameterfvSGI; +GLEW_FUN_EXPORT PFNGLCOLORTABLEPARAMETERIVSGIPROC __glewColorTableParameterivSGI; +GLEW_FUN_EXPORT PFNGLCOLORTABLESGIPROC __glewColorTableSGI; +GLEW_FUN_EXPORT PFNGLCOPYCOLORTABLESGIPROC __glewCopyColorTableSGI; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEPARAMETERFVSGIPROC __glewGetColorTableParameterfvSGI; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLEPARAMETERIVSGIPROC __glewGetColorTableParameterivSGI; +GLEW_FUN_EXPORT PFNGLGETCOLORTABLESGIPROC __glewGetColorTableSGI; + +GLEW_FUN_EXPORT PFNGLFINISHTEXTURESUNXPROC __glewFinishTextureSUNX; + +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORBSUNPROC __glewGlobalAlphaFactorbSUN; +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORDSUNPROC __glewGlobalAlphaFactordSUN; +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORFSUNPROC __glewGlobalAlphaFactorfSUN; +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORISUNPROC __glewGlobalAlphaFactoriSUN; +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORSSUNPROC __glewGlobalAlphaFactorsSUN; +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORUBSUNPROC __glewGlobalAlphaFactorubSUN; +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORUISUNPROC __glewGlobalAlphaFactoruiSUN; +GLEW_FUN_EXPORT PFNGLGLOBALALPHAFACTORUSSUNPROC __glewGlobalAlphaFactorusSUN; + +GLEW_FUN_EXPORT PFNGLREADVIDEOPIXELSSUNPROC __glewReadVideoPixelsSUN; + +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEPOINTERSUNPROC __glewReplacementCodePointerSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUBSUNPROC __glewReplacementCodeubSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUBVSUNPROC __glewReplacementCodeubvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUISUNPROC __glewReplacementCodeuiSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUIVSUNPROC __glewReplacementCodeuivSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUSSUNPROC __glewReplacementCodeusSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUSVSUNPROC __glewReplacementCodeusvSUN; + +GLEW_FUN_EXPORT PFNGLCOLOR3FVERTEX3FSUNPROC __glewColor3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLCOLOR3FVERTEX3FVSUNPROC __glewColor3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC __glewColor4fNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewColor4fNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLCOLOR4UBVERTEX2FSUNPROC __glewColor4ubVertex2fSUN; +GLEW_FUN_EXPORT PFNGLCOLOR4UBVERTEX2FVSUNPROC __glewColor4ubVertex2fvSUN; +GLEW_FUN_EXPORT PFNGLCOLOR4UBVERTEX3FSUNPROC __glewColor4ubVertex3fSUN; +GLEW_FUN_EXPORT PFNGLCOLOR4UBVERTEX3FVSUNPROC __glewColor4ubVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLNORMAL3FVERTEX3FSUNPROC __glewNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLNORMAL3FVERTEX3FVSUNPROC __glewNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC __glewReplacementCodeuiColor3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC __glewReplacementCodeuiColor3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiColor4fNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiColor4fNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC __glewReplacementCodeuiColor4ubVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC __glewReplacementCodeuiColor4ubVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiTexCoord2fNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC __glewReplacementCodeuiTexCoord2fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC __glewReplacementCodeuiTexCoord2fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC __glewReplacementCodeuiVertex3fSUN; +GLEW_FUN_EXPORT PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC __glewReplacementCodeuiVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC __glewTexCoord2fColor3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC __glewTexCoord2fColor3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC __glewTexCoord2fColor4fNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewTexCoord2fColor4fNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC __glewTexCoord2fColor4ubVertex3fSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC __glewTexCoord2fColor4ubVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC __glewTexCoord2fNormal3fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC __glewTexCoord2fNormal3fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FVERTEX3FSUNPROC __glewTexCoord2fVertex3fSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD2FVERTEX3FVSUNPROC __glewTexCoord2fVertex3fvSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC __glewTexCoord4fColor4fNormal3fVertex4fSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC __glewTexCoord4fColor4fNormal3fVertex4fvSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD4FVERTEX4FSUNPROC __glewTexCoord4fVertex4fSUN; +GLEW_FUN_EXPORT PFNGLTEXCOORD4FVERTEX4FVSUNPROC __glewTexCoord4fVertex4fvSUN; + +GLEW_FUN_EXPORT PFNGLADDSWAPHINTRECTWINPROC __glewAddSwapHintRectWIN; + +#if defined(GLEW_MX) && !defined(_WIN32) +struct GLEWContextStruct +{ +#endif /* GLEW_MX */ + +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_1_1; +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_1_2; +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_1_3; +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_1_4; +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_1_5; +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_2_0; +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_2_1; +GLEW_VAR_EXPORT GLboolean __GLEW_VERSION_3_0; +GLEW_VAR_EXPORT GLboolean __GLEW_3DFX_multisample; +GLEW_VAR_EXPORT GLboolean __GLEW_3DFX_tbuffer; +GLEW_VAR_EXPORT GLboolean __GLEW_3DFX_texture_compression_FXT1; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_client_storage; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_element_array; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_fence; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_float_pixels; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_flush_buffer_range; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_pixel_buffer; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_specular_vector; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_texture_range; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_transform_hint; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_vertex_array_object; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_vertex_array_range; +GLEW_VAR_EXPORT GLboolean __GLEW_APPLE_ycbcr_422; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_color_buffer_float; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_depth_buffer_float; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_depth_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_draw_buffers; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_draw_instanced; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_fragment_program; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_fragment_program_shadow; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_fragment_shader; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_framebuffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_framebuffer_sRGB; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_geometry_shader4; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_half_float_pixel; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_half_float_vertex; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_imaging; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_instanced_arrays; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_map_buffer_range; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_matrix_palette; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_multisample; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_multitexture; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_occlusion_query; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_pixel_buffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_point_parameters; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_point_sprite; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_shader_objects; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_shading_language_100; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_shadow; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_shadow_ambient; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_border_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_buffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_compression; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_compression_rgtc; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_cube_map; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_env_add; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_env_combine; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_env_crossbar; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_env_dot3; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_float; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_mirrored_repeat; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_non_power_of_two; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_rectangle; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_texture_rg; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_transpose_matrix; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_vertex_array_object; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_vertex_blend; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_vertex_buffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_vertex_program; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_vertex_shader; +GLEW_VAR_EXPORT GLboolean __GLEW_ARB_window_pos; +GLEW_VAR_EXPORT GLboolean __GLEW_ATIX_point_sprites; +GLEW_VAR_EXPORT GLboolean __GLEW_ATIX_texture_env_combine3; +GLEW_VAR_EXPORT GLboolean __GLEW_ATIX_texture_env_route; +GLEW_VAR_EXPORT GLboolean __GLEW_ATIX_vertex_shader_output_point_size; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_draw_buffers; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_element_array; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_envmap_bumpmap; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_fragment_shader; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_map_object_buffer; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_pn_triangles; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_separate_stencil; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_shader_texture_lod; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_text_fragment_shader; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_texture_compression_3dc; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_texture_env_combine3; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_texture_float; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_texture_mirror_once; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_vertex_array_object; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_vertex_attrib_array_object; +GLEW_VAR_EXPORT GLboolean __GLEW_ATI_vertex_streams; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_422_pixels; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_Cg_shader; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_abgr; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_bgra; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_bindable_uniform; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_blend_color; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_blend_equation_separate; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_blend_func_separate; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_blend_logic_op; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_blend_minmax; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_blend_subtract; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_clip_volume_hint; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_cmyka; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_color_subtable; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_compiled_vertex_array; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_convolution; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_coordinate_frame; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_copy_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_cull_vertex; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_depth_bounds_test; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_direct_state_access; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_draw_buffers2; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_draw_instanced; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_draw_range_elements; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_fog_coord; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_fragment_lighting; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_framebuffer_blit; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_framebuffer_multisample; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_framebuffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_framebuffer_sRGB; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_geometry_shader4; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_gpu_program_parameters; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_gpu_shader4; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_histogram; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_index_array_formats; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_index_func; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_index_material; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_index_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_light_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_misc_attribute; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_multi_draw_arrays; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_multisample; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_packed_depth_stencil; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_packed_float; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_packed_pixels; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_paletted_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_pixel_buffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_pixel_transform; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_pixel_transform_color_table; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_point_parameters; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_polygon_offset; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_rescale_normal; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_scene_marker; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_secondary_color; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_separate_specular_color; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_shadow_funcs; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_shared_texture_palette; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_stencil_clear_tag; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_stencil_two_side; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_stencil_wrap; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_subtexture; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture3D; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_array; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_buffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_compression_dxt1; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_compression_latc; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_compression_rgtc; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_compression_s3tc; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_cube_map; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_edge_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_env; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_env_add; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_env_combine; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_env_dot3; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_filter_anisotropic; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_integer; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_lod_bias; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_mirror_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_object; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_perturb_normal; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_rectangle; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_sRGB; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_shared_exponent; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_texture_swizzle; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_timer_query; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_transform_feedback; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_vertex_array; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_vertex_array_bgra; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_vertex_shader; +GLEW_VAR_EXPORT GLboolean __GLEW_EXT_vertex_weighting; +GLEW_VAR_EXPORT GLboolean __GLEW_GREMEDY_frame_terminator; +GLEW_VAR_EXPORT GLboolean __GLEW_GREMEDY_string_marker; +GLEW_VAR_EXPORT GLboolean __GLEW_HP_convolution_border_modes; +GLEW_VAR_EXPORT GLboolean __GLEW_HP_image_transform; +GLEW_VAR_EXPORT GLboolean __GLEW_HP_occlusion_test; +GLEW_VAR_EXPORT GLboolean __GLEW_HP_texture_lighting; +GLEW_VAR_EXPORT GLboolean __GLEW_IBM_cull_vertex; +GLEW_VAR_EXPORT GLboolean __GLEW_IBM_multimode_draw_arrays; +GLEW_VAR_EXPORT GLboolean __GLEW_IBM_rasterpos_clip; +GLEW_VAR_EXPORT GLboolean __GLEW_IBM_static_data; +GLEW_VAR_EXPORT GLboolean __GLEW_IBM_texture_mirrored_repeat; +GLEW_VAR_EXPORT GLboolean __GLEW_IBM_vertex_array_lists; +GLEW_VAR_EXPORT GLboolean __GLEW_INGR_color_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_INGR_interlace_read; +GLEW_VAR_EXPORT GLboolean __GLEW_INTEL_parallel_arrays; +GLEW_VAR_EXPORT GLboolean __GLEW_INTEL_texture_scissor; +GLEW_VAR_EXPORT GLboolean __GLEW_KTX_buffer_region; +GLEW_VAR_EXPORT GLboolean __GLEW_MESAX_texture_stack; +GLEW_VAR_EXPORT GLboolean __GLEW_MESA_pack_invert; +GLEW_VAR_EXPORT GLboolean __GLEW_MESA_resize_buffers; +GLEW_VAR_EXPORT GLboolean __GLEW_MESA_window_pos; +GLEW_VAR_EXPORT GLboolean __GLEW_MESA_ycbcr_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_blend_square; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_conditional_render; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_copy_depth_to_color; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_depth_buffer_float; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_depth_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_depth_range_unclamped; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_evaluators; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_explicit_multisample; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_fence; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_float_buffer; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_fog_distance; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_fragment_program; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_fragment_program2; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_fragment_program4; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_fragment_program_option; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_framebuffer_multisample_coverage; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_geometry_program4; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_geometry_shader4; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_gpu_program4; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_half_float; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_light_max_exponent; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_multisample_filter_hint; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_occlusion_query; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_packed_depth_stencil; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_parameter_buffer_object; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_pixel_data_range; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_point_sprite; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_present_video; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_primitive_restart; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_register_combiners; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_register_combiners2; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texgen_emboss; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texgen_reflection; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texture_compression_vtc; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texture_env_combine4; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texture_expand_normal; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texture_rectangle; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texture_shader; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texture_shader2; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_texture_shader3; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_transform_feedback; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_array_range; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_array_range2; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_program; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_program1_1; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_program2; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_program2_option; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_program3; +GLEW_VAR_EXPORT GLboolean __GLEW_NV_vertex_program4; +GLEW_VAR_EXPORT GLboolean __GLEW_OES_byte_coordinates; +GLEW_VAR_EXPORT GLboolean __GLEW_OES_compressed_paletted_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_OES_read_format; +GLEW_VAR_EXPORT GLboolean __GLEW_OES_single_precision; +GLEW_VAR_EXPORT GLboolean __GLEW_OML_interlace; +GLEW_VAR_EXPORT GLboolean __GLEW_OML_resample; +GLEW_VAR_EXPORT GLboolean __GLEW_OML_subsample; +GLEW_VAR_EXPORT GLboolean __GLEW_PGI_misc_hints; +GLEW_VAR_EXPORT GLboolean __GLEW_PGI_vertex_hints; +GLEW_VAR_EXPORT GLboolean __GLEW_REND_screen_coordinates; +GLEW_VAR_EXPORT GLboolean __GLEW_S3_s3tc; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_color_range; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_detail_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_fog_function; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_generate_mipmap; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_multisample; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_pixel_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_point_line_texgen; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_sharpen_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_texture4D; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_texture_border_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_texture_edge_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_texture_filter4; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_texture_lod; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIS_texture_select; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_async; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_async_histogram; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_async_pixel; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_blend_alpha_minmax; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_clipmap; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_convolution_accuracy; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_depth_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_flush_raster; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_fog_offset; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_fog_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_fragment_specular_lighting; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_framezoom; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_interlace; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_ir_instrument1; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_list_priority; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_pixel_texture; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_pixel_texture_bits; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_reference_plane; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_resample; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_shadow; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_shadow_ambient; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_sprite; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_tag_sample_buffer; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_texture_add_env; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_texture_coordinate_clamp; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_texture_lod_bias; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_texture_multi_buffer; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_texture_range; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_texture_scale_bias; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_vertex_preclip; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_vertex_preclip_hint; +GLEW_VAR_EXPORT GLboolean __GLEW_SGIX_ycrcb; +GLEW_VAR_EXPORT GLboolean __GLEW_SGI_color_matrix; +GLEW_VAR_EXPORT GLboolean __GLEW_SGI_color_table; +GLEW_VAR_EXPORT GLboolean __GLEW_SGI_texture_color_table; +GLEW_VAR_EXPORT GLboolean __GLEW_SUNX_constant_data; +GLEW_VAR_EXPORT GLboolean __GLEW_SUN_convolution_border_modes; +GLEW_VAR_EXPORT GLboolean __GLEW_SUN_global_alpha; +GLEW_VAR_EXPORT GLboolean __GLEW_SUN_mesh_array; +GLEW_VAR_EXPORT GLboolean __GLEW_SUN_read_video_pixels; +GLEW_VAR_EXPORT GLboolean __GLEW_SUN_slice_accum; +GLEW_VAR_EXPORT GLboolean __GLEW_SUN_triangle_list; +GLEW_VAR_EXPORT GLboolean __GLEW_SUN_vertex; +GLEW_VAR_EXPORT GLboolean __GLEW_WIN_phong_shading; +GLEW_VAR_EXPORT GLboolean __GLEW_WIN_specular_fog; +GLEW_VAR_EXPORT GLboolean __GLEW_WIN_swap_hint; + +#ifdef GLEW_MX +}; /* GLEWContextStruct */ +#endif /* GLEW_MX */ + +/* ------------------------------------------------------------------------- */ + +/* error codes */ +#define GLEW_OK 0 +#define GLEW_NO_ERROR 0 +#define GLEW_ERROR_NO_GL_VERSION 1 /* missing GL version */ +#define GLEW_ERROR_GL_VERSION_10_ONLY 2 /* GL 1.1 and up are not supported */ +#define GLEW_ERROR_GLX_VERSION_11_ONLY 3 /* GLX 1.2 and up are not supported */ + +/* string codes */ +#define GLEW_VERSION 1 +#define GLEW_VERSION_MAJOR 2 +#define GLEW_VERSION_MINOR 3 +#define GLEW_VERSION_MICRO 4 + +/* API */ +#ifdef GLEW_MX + +typedef struct GLEWContextStruct GLEWContext; +GLEWAPI GLenum glewContextInit (GLEWContext* ctx); +GLEWAPI GLboolean glewContextIsSupported (GLEWContext* ctx, const char* name); + +#define glewInit() glewContextInit(glewGetContext()) +#define glewIsSupported(x) glewContextIsSupported(glewGetContext(), x) +#define glewIsExtensionSupported(x) glewIsSupported(x) + +#define GLEW_GET_VAR(x) (*(const GLboolean*)&(glewGetContext()->x)) +#ifdef _WIN32 +# define GLEW_GET_FUN(x) glewGetContext()->x +#else +# define GLEW_GET_FUN(x) x +#endif + +#else /* GLEW_MX */ + +GLEWAPI GLenum glewInit (); +GLEWAPI GLboolean glewIsSupported (const char* name); +#define glewIsExtensionSupported(x) glewIsSupported(x) + +#define GLEW_GET_VAR(x) (*(const GLboolean*)&x) +#define GLEW_GET_FUN(x) x + +#endif /* GLEW_MX */ + +GLEWAPI GLboolean glewExperimental; +GLEWAPI GLboolean glewGetExtension (const char* name); +GLEWAPI const GLubyte* glewGetErrorString (GLenum error); +GLEWAPI const GLubyte* glewGetString (GLenum name); + +#ifdef __cplusplus +} +#endif + +#ifdef GLEW_APIENTRY_DEFINED +#undef GLEW_APIENTRY_DEFINED +#undef APIENTRY +#undef GLAPIENTRY +#endif + +#ifdef GLEW_CALLBACK_DEFINED +#undef GLEW_CALLBACK_DEFINED +#undef CALLBACK +#endif + +#ifdef GLEW_WINGDIAPI_DEFINED +#undef GLEW_WINGDIAPI_DEFINED +#undef WINGDIAPI +#endif + +#undef GLAPI +/* #undef GLEWAPI */ + +#endif /* __glew_h__ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/glxew.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/glxew.h new file mode 100644 index 0000000..a29030d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/glxew.h @@ -0,0 +1,1397 @@ +/* +** The OpenGL Extension Wrangler Library +** Copyright (C) 2002-2008, Milan Ikits +** Copyright (C) 2002-2008, Marcelo E. Magallon +** Copyright (C) 2002, Lev Povalahev +** 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. +** * The name of the author may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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. +*/ + +/* + * Mesa 3-D graphics library + * Version: 7.0 + * + * Copyright (C) 1999-2007 Brian Paul All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +** Copyright (c) 2007 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +#ifndef __glxew_h__ +#define __glxew_h__ +#define __GLXEW_H__ + +#ifdef __glxext_h_ +#error glxext.h included before glxew.h +#endif +#ifdef GLX_H +#error glx.h included before glxew.h +#endif + +#define __glxext_h_ +#define __GLX_glx_h__ +#define GLX_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------- GLX_VERSION_1_0 --------------------------- */ + +#ifndef GLX_VERSION_1_0 +#define GLX_VERSION_1_0 1 + +#define GLX_USE_GL 1 +#define GLX_BUFFER_SIZE 2 +#define GLX_LEVEL 3 +#define GLX_RGBA 4 +#define GLX_DOUBLEBUFFER 5 +#define GLX_STEREO 6 +#define GLX_AUX_BUFFERS 7 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_ACCUM_RED_SIZE 14 +#define GLX_ACCUM_GREEN_SIZE 15 +#define GLX_ACCUM_BLUE_SIZE 16 +#define GLX_ACCUM_ALPHA_SIZE 17 +#define GLX_BAD_SCREEN 1 +#define GLX_BAD_ATTRIBUTE 2 +#define GLX_NO_EXTENSION 3 +#define GLX_BAD_VISUAL 4 +#define GLX_BAD_CONTEXT 5 +#define GLX_BAD_VALUE 6 +#define GLX_BAD_ENUM 7 + +typedef XID GLXDrawable; +typedef XID GLXPixmap; +#ifdef __sun +typedef struct __glXContextRec *GLXContext; +#else +typedef struct __GLXcontextRec *GLXContext; +#endif + +typedef unsigned int GLXVideoDeviceNV; + +extern Bool glXQueryExtension (Display *dpy, int *errorBase, int *eventBase); +extern Bool glXQueryVersion (Display *dpy, int *major, int *minor); +extern int glXGetConfig (Display *dpy, XVisualInfo *vis, int attrib, int *value); +extern XVisualInfo* glXChooseVisual (Display *dpy, int screen, int *attribList); +extern GLXPixmap glXCreateGLXPixmap (Display *dpy, XVisualInfo *vis, Pixmap pixmap); +extern void glXDestroyGLXPixmap (Display *dpy, GLXPixmap pix); +extern GLXContext glXCreateContext (Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct); +extern void glXDestroyContext (Display *dpy, GLXContext ctx); +extern Bool glXIsDirect (Display *dpy, GLXContext ctx); +extern void glXCopyContext (Display *dpy, GLXContext src, GLXContext dst, GLulong mask); +extern Bool glXMakeCurrent (Display *dpy, GLXDrawable drawable, GLXContext ctx); +extern GLXContext glXGetCurrentContext (void); +extern GLXDrawable glXGetCurrentDrawable (void); +extern void glXWaitGL (void); +extern void glXWaitX (void); +extern void glXSwapBuffers (Display *dpy, GLXDrawable drawable); +extern void glXUseXFont (Font font, int first, int count, int listBase); + +#define GLXEW_VERSION_1_0 GLXEW_GET_VAR(__GLXEW_VERSION_1_0) + +#endif /* GLX_VERSION_1_0 */ + +/* ---------------------------- GLX_VERSION_1_1 --------------------------- */ + +#ifndef GLX_VERSION_1_1 +#define GLX_VERSION_1_1 + +#define GLX_VENDOR 0x1 +#define GLX_VERSION 0x2 +#define GLX_EXTENSIONS 0x3 + +extern const char* glXQueryExtensionsString (Display *dpy, int screen); +extern const char* glXGetClientString (Display *dpy, int name); +extern const char* glXQueryServerString (Display *dpy, int screen, int name); + +#define GLXEW_VERSION_1_1 GLXEW_GET_VAR(__GLXEW_VERSION_1_1) + +#endif /* GLX_VERSION_1_1 */ + +/* ---------------------------- GLX_VERSION_1_2 ---------------------------- */ + +#ifndef GLX_VERSION_1_2 +#define GLX_VERSION_1_2 1 + +typedef Display* ( * PFNGLXGETCURRENTDISPLAYPROC) (void); + +#define glXGetCurrentDisplay GLXEW_GET_FUN(__glewXGetCurrentDisplay) + +#define GLXEW_VERSION_1_2 GLXEW_GET_VAR(__GLXEW_VERSION_1_2) + +#endif /* GLX_VERSION_1_2 */ + +/* ---------------------------- GLX_VERSION_1_3 ---------------------------- */ + +#ifndef GLX_VERSION_1_3 +#define GLX_VERSION_1_3 1 + +#define GLX_RGBA_BIT 0x00000001 +#define GLX_FRONT_LEFT_BUFFER_BIT 0x00000001 +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_COLOR_INDEX_BIT 0x00000002 +#define GLX_PIXMAP_BIT 0x00000002 +#define GLX_FRONT_RIGHT_BUFFER_BIT 0x00000002 +#define GLX_BACK_LEFT_BUFFER_BIT 0x00000004 +#define GLX_PBUFFER_BIT 0x00000004 +#define GLX_BACK_RIGHT_BUFFER_BIT 0x00000008 +#define GLX_AUX_BUFFERS_BIT 0x00000010 +#define GLX_CONFIG_CAVEAT 0x20 +#define GLX_DEPTH_BUFFER_BIT 0x00000020 +#define GLX_X_VISUAL_TYPE 0x22 +#define GLX_TRANSPARENT_TYPE 0x23 +#define GLX_TRANSPARENT_INDEX_VALUE 0x24 +#define GLX_TRANSPARENT_RED_VALUE 0x25 +#define GLX_TRANSPARENT_GREEN_VALUE 0x26 +#define GLX_TRANSPARENT_BLUE_VALUE 0x27 +#define GLX_TRANSPARENT_ALPHA_VALUE 0x28 +#define GLX_STENCIL_BUFFER_BIT 0x00000040 +#define GLX_ACCUM_BUFFER_BIT 0x00000080 +#define GLX_NONE 0x8000 +#define GLX_SLOW_CONFIG 0x8001 +#define GLX_TRUE_COLOR 0x8002 +#define GLX_DIRECT_COLOR 0x8003 +#define GLX_PSEUDO_COLOR 0x8004 +#define GLX_STATIC_COLOR 0x8005 +#define GLX_GRAY_SCALE 0x8006 +#define GLX_STATIC_GRAY 0x8007 +#define GLX_TRANSPARENT_RGB 0x8008 +#define GLX_TRANSPARENT_INDEX 0x8009 +#define GLX_VISUAL_ID 0x800B +#define GLX_SCREEN 0x800C +#define GLX_NON_CONFORMANT_CONFIG 0x800D +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_X_RENDERABLE 0x8012 +#define GLX_FBCONFIG_ID 0x8013 +#define GLX_RGBA_TYPE 0x8014 +#define GLX_COLOR_INDEX_TYPE 0x8015 +#define GLX_MAX_PBUFFER_WIDTH 0x8016 +#define GLX_MAX_PBUFFER_HEIGHT 0x8017 +#define GLX_MAX_PBUFFER_PIXELS 0x8018 +#define GLX_PRESERVED_CONTENTS 0x801B +#define GLX_LARGEST_PBUFFER 0x801C +#define GLX_WIDTH 0x801D +#define GLX_HEIGHT 0x801E +#define GLX_EVENT_MASK 0x801F +#define GLX_DAMAGED 0x8020 +#define GLX_SAVED 0x8021 +#define GLX_WINDOW 0x8022 +#define GLX_PBUFFER 0x8023 +#define GLX_PBUFFER_HEIGHT 0x8040 +#define GLX_PBUFFER_WIDTH 0x8041 +#define GLX_PBUFFER_CLOBBER_MASK 0x08000000 +#define GLX_DONT_CARE 0xFFFFFFFF + +typedef XID GLXFBConfigID; +typedef XID GLXWindow; +typedef XID GLXPbuffer; +typedef struct __GLXFBConfigRec *GLXFBConfig; + +typedef struct { + int event_type; + int draw_type; + unsigned long serial; + Bool send_event; + Display *display; + GLXDrawable drawable; + unsigned int buffer_mask; + unsigned int aux_buffer; + int x, y; + int width, height; + int count; +} GLXPbufferClobberEvent; +typedef union __GLXEvent { + GLXPbufferClobberEvent glxpbufferclobber; + long pad[24]; +} GLXEvent; + +typedef GLXFBConfig* ( * PFNGLXCHOOSEFBCONFIGPROC) (Display *dpy, int screen, const int *attrib_list, int *nelements); +typedef GLXContext ( * PFNGLXCREATENEWCONTEXTPROC) (Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); +typedef GLXPbuffer ( * PFNGLXCREATEPBUFFERPROC) (Display *dpy, GLXFBConfig config, const int *attrib_list); +typedef GLXPixmap ( * PFNGLXCREATEPIXMAPPROC) (Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list); +typedef GLXWindow ( * PFNGLXCREATEWINDOWPROC) (Display *dpy, GLXFBConfig config, Window win, const int *attrib_list); +typedef void ( * PFNGLXDESTROYPBUFFERPROC) (Display *dpy, GLXPbuffer pbuf); +typedef void ( * PFNGLXDESTROYPIXMAPPROC) (Display *dpy, GLXPixmap pixmap); +typedef void ( * PFNGLXDESTROYWINDOWPROC) (Display *dpy, GLXWindow win); +typedef GLXDrawable ( * PFNGLXGETCURRENTREADDRAWABLEPROC) (void); +typedef int ( * PFNGLXGETFBCONFIGATTRIBPROC) (Display *dpy, GLXFBConfig config, int attribute, int *value); +typedef GLXFBConfig* ( * PFNGLXGETFBCONFIGSPROC) (Display *dpy, int screen, int *nelements); +typedef void ( * PFNGLXGETSELECTEDEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long *event_mask); +typedef XVisualInfo* ( * PFNGLXGETVISUALFROMFBCONFIGPROC) (Display *dpy, GLXFBConfig config); +typedef Bool ( * PFNGLXMAKECONTEXTCURRENTPROC) (Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +typedef int ( * PFNGLXQUERYCONTEXTPROC) (Display *dpy, GLXContext ctx, int attribute, int *value); +typedef void ( * PFNGLXQUERYDRAWABLEPROC) (Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); +typedef void ( * PFNGLXSELECTEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long event_mask); + +#define glXChooseFBConfig GLXEW_GET_FUN(__glewXChooseFBConfig) +#define glXCreateNewContext GLXEW_GET_FUN(__glewXCreateNewContext) +#define glXCreatePbuffer GLXEW_GET_FUN(__glewXCreatePbuffer) +#define glXCreatePixmap GLXEW_GET_FUN(__glewXCreatePixmap) +#define glXCreateWindow GLXEW_GET_FUN(__glewXCreateWindow) +#define glXDestroyPbuffer GLXEW_GET_FUN(__glewXDestroyPbuffer) +#define glXDestroyPixmap GLXEW_GET_FUN(__glewXDestroyPixmap) +#define glXDestroyWindow GLXEW_GET_FUN(__glewXDestroyWindow) +#define glXGetCurrentReadDrawable GLXEW_GET_FUN(__glewXGetCurrentReadDrawable) +#define glXGetFBConfigAttrib GLXEW_GET_FUN(__glewXGetFBConfigAttrib) +#define glXGetFBConfigs GLXEW_GET_FUN(__glewXGetFBConfigs) +#define glXGetSelectedEvent GLXEW_GET_FUN(__glewXGetSelectedEvent) +#define glXGetVisualFromFBConfig GLXEW_GET_FUN(__glewXGetVisualFromFBConfig) +#define glXMakeContextCurrent GLXEW_GET_FUN(__glewXMakeContextCurrent) +#define glXQueryContext GLXEW_GET_FUN(__glewXQueryContext) +#define glXQueryDrawable GLXEW_GET_FUN(__glewXQueryDrawable) +#define glXSelectEvent GLXEW_GET_FUN(__glewXSelectEvent) + +#define GLXEW_VERSION_1_3 GLXEW_GET_VAR(__GLXEW_VERSION_1_3) + +#endif /* GLX_VERSION_1_3 */ + +/* ---------------------------- GLX_VERSION_1_4 ---------------------------- */ + +#ifndef GLX_VERSION_1_4 +#define GLX_VERSION_1_4 1 + +#define GLX_SAMPLE_BUFFERS 100000 +#define GLX_SAMPLES 100001 + +extern void ( * glXGetProcAddress (const GLubyte *procName)) (void); + +#define GLXEW_VERSION_1_4 GLXEW_GET_VAR(__GLXEW_VERSION_1_4) + +#endif /* GLX_VERSION_1_4 */ + +/* -------------------------- GLX_3DFX_multisample ------------------------- */ + +#ifndef GLX_3DFX_multisample +#define GLX_3DFX_multisample 1 + +#define GLX_SAMPLE_BUFFERS_3DFX 0x8050 +#define GLX_SAMPLES_3DFX 0x8051 + +#define GLXEW_3DFX_multisample GLXEW_GET_VAR(__GLXEW_3DFX_multisample) + +#endif /* GLX_3DFX_multisample */ + +/* ------------------------- GLX_ARB_create_context ------------------------ */ + +#ifndef GLX_ARB_create_context +#define GLX_ARB_create_context 1 + +#define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 + +typedef GLXContext ( * PFNGLXCREATECONTEXTATTRIBSARBPROC) (Display* dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); + +#define glXCreateContextAttribsARB GLXEW_GET_FUN(__glewXCreateContextAttribsARB) + +#define GLXEW_ARB_create_context GLXEW_GET_VAR(__GLXEW_ARB_create_context) + +#endif /* GLX_ARB_create_context */ + +/* ------------------------- GLX_ARB_fbconfig_float ------------------------ */ + +#ifndef GLX_ARB_fbconfig_float +#define GLX_ARB_fbconfig_float 1 + +#define GLX_RGBA_FLOAT_BIT 0x00000004 +#define GLX_RGBA_FLOAT_TYPE 0x20B9 + +#define GLXEW_ARB_fbconfig_float GLXEW_GET_VAR(__GLXEW_ARB_fbconfig_float) + +#endif /* GLX_ARB_fbconfig_float */ + +/* ------------------------ GLX_ARB_framebuffer_sRGB ----------------------- */ + +#ifndef GLX_ARB_framebuffer_sRGB +#define GLX_ARB_framebuffer_sRGB 1 + +#define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20B2 + +#define GLXEW_ARB_framebuffer_sRGB GLXEW_GET_VAR(__GLXEW_ARB_framebuffer_sRGB) + +#endif /* GLX_ARB_framebuffer_sRGB */ + +/* ------------------------ GLX_ARB_get_proc_address ----------------------- */ + +#ifndef GLX_ARB_get_proc_address +#define GLX_ARB_get_proc_address 1 + +extern void ( * glXGetProcAddressARB (const GLubyte *procName)) (void); + +#define GLXEW_ARB_get_proc_address GLXEW_GET_VAR(__GLXEW_ARB_get_proc_address) + +#endif /* GLX_ARB_get_proc_address */ + +/* -------------------------- GLX_ARB_multisample -------------------------- */ + +#ifndef GLX_ARB_multisample +#define GLX_ARB_multisample 1 + +#define GLX_SAMPLE_BUFFERS_ARB 100000 +#define GLX_SAMPLES_ARB 100001 + +#define GLXEW_ARB_multisample GLXEW_GET_VAR(__GLXEW_ARB_multisample) + +#endif /* GLX_ARB_multisample */ + +/* ----------------------- GLX_ATI_pixel_format_float ---------------------- */ + +#ifndef GLX_ATI_pixel_format_float +#define GLX_ATI_pixel_format_float 1 + +#define GLX_RGBA_FLOAT_ATI_BIT 0x00000100 + +#define GLXEW_ATI_pixel_format_float GLXEW_GET_VAR(__GLXEW_ATI_pixel_format_float) + +#endif /* GLX_ATI_pixel_format_float */ + +/* ------------------------- GLX_ATI_render_texture ------------------------ */ + +#ifndef GLX_ATI_render_texture +#define GLX_ATI_render_texture 1 + +#define GLX_BIND_TO_TEXTURE_RGB_ATI 0x9800 +#define GLX_BIND_TO_TEXTURE_RGBA_ATI 0x9801 +#define GLX_TEXTURE_FORMAT_ATI 0x9802 +#define GLX_TEXTURE_TARGET_ATI 0x9803 +#define GLX_MIPMAP_TEXTURE_ATI 0x9804 +#define GLX_TEXTURE_RGB_ATI 0x9805 +#define GLX_TEXTURE_RGBA_ATI 0x9806 +#define GLX_NO_TEXTURE_ATI 0x9807 +#define GLX_TEXTURE_CUBE_MAP_ATI 0x9808 +#define GLX_TEXTURE_1D_ATI 0x9809 +#define GLX_TEXTURE_2D_ATI 0x980A +#define GLX_MIPMAP_LEVEL_ATI 0x980B +#define GLX_CUBE_MAP_FACE_ATI 0x980C +#define GLX_TEXTURE_CUBE_MAP_POSITIVE_X_ATI 0x980D +#define GLX_TEXTURE_CUBE_MAP_NEGATIVE_X_ATI 0x980E +#define GLX_TEXTURE_CUBE_MAP_POSITIVE_Y_ATI 0x980F +#define GLX_TEXTURE_CUBE_MAP_NEGATIVE_Y_ATI 0x9810 +#define GLX_TEXTURE_CUBE_MAP_POSITIVE_Z_ATI 0x9811 +#define GLX_TEXTURE_CUBE_MAP_NEGATIVE_Z_ATI 0x9812 +#define GLX_FRONT_LEFT_ATI 0x9813 +#define GLX_FRONT_RIGHT_ATI 0x9814 +#define GLX_BACK_LEFT_ATI 0x9815 +#define GLX_BACK_RIGHT_ATI 0x9816 +#define GLX_AUX0_ATI 0x9817 +#define GLX_AUX1_ATI 0x9818 +#define GLX_AUX2_ATI 0x9819 +#define GLX_AUX3_ATI 0x981A +#define GLX_AUX4_ATI 0x981B +#define GLX_AUX5_ATI 0x981C +#define GLX_AUX6_ATI 0x981D +#define GLX_AUX7_ATI 0x981E +#define GLX_AUX8_ATI 0x981F +#define GLX_AUX9_ATI 0x9820 +#define GLX_BIND_TO_TEXTURE_LUMINANCE_ATI 0x9821 +#define GLX_BIND_TO_TEXTURE_INTENSITY_ATI 0x9822 + +typedef void ( * PFNGLXBINDTEXIMAGEATIPROC) (Display *dpy, GLXPbuffer pbuf, int buffer); +typedef void ( * PFNGLXDRAWABLEATTRIBATIPROC) (Display *dpy, GLXDrawable draw, const int *attrib_list); +typedef void ( * PFNGLXRELEASETEXIMAGEATIPROC) (Display *dpy, GLXPbuffer pbuf, int buffer); + +#define glXBindTexImageATI GLXEW_GET_FUN(__glewXBindTexImageATI) +#define glXDrawableAttribATI GLXEW_GET_FUN(__glewXDrawableAttribATI) +#define glXReleaseTexImageATI GLXEW_GET_FUN(__glewXReleaseTexImageATI) + +#define GLXEW_ATI_render_texture GLXEW_GET_VAR(__GLXEW_ATI_render_texture) + +#endif /* GLX_ATI_render_texture */ + +/* --------------------- GLX_EXT_fbconfig_packed_float --------------------- */ + +#ifndef GLX_EXT_fbconfig_packed_float +#define GLX_EXT_fbconfig_packed_float 1 + +#define GLX_RGBA_UNSIGNED_FLOAT_BIT_EXT 0x00000008 +#define GLX_RGBA_UNSIGNED_FLOAT_TYPE_EXT 0x20B1 + +#define GLXEW_EXT_fbconfig_packed_float GLXEW_GET_VAR(__GLXEW_EXT_fbconfig_packed_float) + +#endif /* GLX_EXT_fbconfig_packed_float */ + +/* ------------------------ GLX_EXT_framebuffer_sRGB ----------------------- */ + +#ifndef GLX_EXT_framebuffer_sRGB +#define GLX_EXT_framebuffer_sRGB 1 + +#define GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20B2 + +#define GLXEW_EXT_framebuffer_sRGB GLXEW_GET_VAR(__GLXEW_EXT_framebuffer_sRGB) + +#endif /* GLX_EXT_framebuffer_sRGB */ + +/* ------------------------- GLX_EXT_import_context ------------------------ */ + +#ifndef GLX_EXT_import_context +#define GLX_EXT_import_context 1 + +#define GLX_SHARE_CONTEXT_EXT 0x800A +#define GLX_VISUAL_ID_EXT 0x800B +#define GLX_SCREEN_EXT 0x800C + +typedef XID GLXContextID; + +typedef void ( * PFNGLXFREECONTEXTEXTPROC) (Display* dpy, GLXContext context); +typedef GLXContextID ( * PFNGLXGETCONTEXTIDEXTPROC) (const GLXContext context); +typedef GLXContext ( * PFNGLXIMPORTCONTEXTEXTPROC) (Display* dpy, GLXContextID contextID); +typedef int ( * PFNGLXQUERYCONTEXTINFOEXTPROC) (Display* dpy, GLXContext context, int attribute,int *value); + +#define glXFreeContextEXT GLXEW_GET_FUN(__glewXFreeContextEXT) +#define glXGetContextIDEXT GLXEW_GET_FUN(__glewXGetContextIDEXT) +#define glXImportContextEXT GLXEW_GET_FUN(__glewXImportContextEXT) +#define glXQueryContextInfoEXT GLXEW_GET_FUN(__glewXQueryContextInfoEXT) + +#define GLXEW_EXT_import_context GLXEW_GET_VAR(__GLXEW_EXT_import_context) + +#endif /* GLX_EXT_import_context */ + +/* -------------------------- GLX_EXT_scene_marker ------------------------- */ + +#ifndef GLX_EXT_scene_marker +#define GLX_EXT_scene_marker 1 + +#define GLXEW_EXT_scene_marker GLXEW_GET_VAR(__GLXEW_EXT_scene_marker) + +#endif /* GLX_EXT_scene_marker */ + +/* ---------------------- GLX_EXT_texture_from_pixmap ---------------------- */ + +#ifndef GLX_EXT_texture_from_pixmap +#define GLX_EXT_texture_from_pixmap 1 + +#define GLX_TEXTURE_1D_BIT_EXT 0x00000001 +#define GLX_TEXTURE_2D_BIT_EXT 0x00000002 +#define GLX_TEXTURE_RECTANGLE_BIT_EXT 0x00000004 +#define GLX_BIND_TO_TEXTURE_RGB_EXT 0x20D0 +#define GLX_BIND_TO_TEXTURE_RGBA_EXT 0x20D1 +#define GLX_BIND_TO_MIPMAP_TEXTURE_EXT 0x20D2 +#define GLX_BIND_TO_TEXTURE_TARGETS_EXT 0x20D3 +#define GLX_Y_INVERTED_EXT 0x20D4 +#define GLX_TEXTURE_FORMAT_EXT 0x20D5 +#define GLX_TEXTURE_TARGET_EXT 0x20D6 +#define GLX_MIPMAP_TEXTURE_EXT 0x20D7 +#define GLX_TEXTURE_FORMAT_NONE_EXT 0x20D8 +#define GLX_TEXTURE_FORMAT_RGB_EXT 0x20D9 +#define GLX_TEXTURE_FORMAT_RGBA_EXT 0x20DA +#define GLX_TEXTURE_1D_EXT 0x20DB +#define GLX_TEXTURE_2D_EXT 0x20DC +#define GLX_TEXTURE_RECTANGLE_EXT 0x20DD +#define GLX_FRONT_LEFT_EXT 0x20DE +#define GLX_FRONT_RIGHT_EXT 0x20DF +#define GLX_BACK_LEFT_EXT 0x20E0 +#define GLX_BACK_RIGHT_EXT 0x20E1 +#define GLX_AUX0_EXT 0x20E2 +#define GLX_AUX1_EXT 0x20E3 +#define GLX_AUX2_EXT 0x20E4 +#define GLX_AUX3_EXT 0x20E5 +#define GLX_AUX4_EXT 0x20E6 +#define GLX_AUX5_EXT 0x20E7 +#define GLX_AUX6_EXT 0x20E8 +#define GLX_AUX7_EXT 0x20E9 +#define GLX_AUX8_EXT 0x20EA +#define GLX_AUX9_EXT 0x20EB + +typedef void ( * PFNGLXBINDTEXIMAGEEXTPROC) (Display* display, GLXDrawable drawable, int buffer, const int *attrib_list); +typedef void ( * PFNGLXRELEASETEXIMAGEEXTPROC) (Display* display, GLXDrawable drawable, int buffer); + +#define glXBindTexImageEXT GLXEW_GET_FUN(__glewXBindTexImageEXT) +#define glXReleaseTexImageEXT GLXEW_GET_FUN(__glewXReleaseTexImageEXT) + +#define GLXEW_EXT_texture_from_pixmap GLXEW_GET_VAR(__GLXEW_EXT_texture_from_pixmap) + +#endif /* GLX_EXT_texture_from_pixmap */ + +/* -------------------------- GLX_EXT_visual_info -------------------------- */ + +#ifndef GLX_EXT_visual_info +#define GLX_EXT_visual_info 1 + +#define GLX_X_VISUAL_TYPE_EXT 0x22 +#define GLX_TRANSPARENT_TYPE_EXT 0x23 +#define GLX_TRANSPARENT_INDEX_VALUE_EXT 0x24 +#define GLX_TRANSPARENT_RED_VALUE_EXT 0x25 +#define GLX_TRANSPARENT_GREEN_VALUE_EXT 0x26 +#define GLX_TRANSPARENT_BLUE_VALUE_EXT 0x27 +#define GLX_TRANSPARENT_ALPHA_VALUE_EXT 0x28 +#define GLX_NONE_EXT 0x8000 +#define GLX_TRUE_COLOR_EXT 0x8002 +#define GLX_DIRECT_COLOR_EXT 0x8003 +#define GLX_PSEUDO_COLOR_EXT 0x8004 +#define GLX_STATIC_COLOR_EXT 0x8005 +#define GLX_GRAY_SCALE_EXT 0x8006 +#define GLX_STATIC_GRAY_EXT 0x8007 +#define GLX_TRANSPARENT_RGB_EXT 0x8008 +#define GLX_TRANSPARENT_INDEX_EXT 0x8009 + +#define GLXEW_EXT_visual_info GLXEW_GET_VAR(__GLXEW_EXT_visual_info) + +#endif /* GLX_EXT_visual_info */ + +/* ------------------------- GLX_EXT_visual_rating ------------------------- */ + +#ifndef GLX_EXT_visual_rating +#define GLX_EXT_visual_rating 1 + +#define GLX_VISUAL_CAVEAT_EXT 0x20 +#define GLX_SLOW_VISUAL_EXT 0x8001 +#define GLX_NON_CONFORMANT_VISUAL_EXT 0x800D + +#define GLXEW_EXT_visual_rating GLXEW_GET_VAR(__GLXEW_EXT_visual_rating) + +#endif /* GLX_EXT_visual_rating */ + +/* -------------------------- GLX_MESA_agp_offset -------------------------- */ + +#ifndef GLX_MESA_agp_offset +#define GLX_MESA_agp_offset 1 + +typedef unsigned int ( * PFNGLXGETAGPOFFSETMESAPROC) (const void* pointer); + +#define glXGetAGPOffsetMESA GLXEW_GET_FUN(__glewXGetAGPOffsetMESA) + +#define GLXEW_MESA_agp_offset GLXEW_GET_VAR(__GLXEW_MESA_agp_offset) + +#endif /* GLX_MESA_agp_offset */ + +/* ------------------------ GLX_MESA_copy_sub_buffer ----------------------- */ + +#ifndef GLX_MESA_copy_sub_buffer +#define GLX_MESA_copy_sub_buffer 1 + +typedef void ( * PFNGLXCOPYSUBBUFFERMESAPROC) (Display* dpy, GLXDrawable drawable, int x, int y, int width, int height); + +#define glXCopySubBufferMESA GLXEW_GET_FUN(__glewXCopySubBufferMESA) + +#define GLXEW_MESA_copy_sub_buffer GLXEW_GET_VAR(__GLXEW_MESA_copy_sub_buffer) + +#endif /* GLX_MESA_copy_sub_buffer */ + +/* ------------------------ GLX_MESA_pixmap_colormap ----------------------- */ + +#ifndef GLX_MESA_pixmap_colormap +#define GLX_MESA_pixmap_colormap 1 + +typedef GLXPixmap ( * PFNGLXCREATEGLXPIXMAPMESAPROC) (Display* dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap); + +#define glXCreateGLXPixmapMESA GLXEW_GET_FUN(__glewXCreateGLXPixmapMESA) + +#define GLXEW_MESA_pixmap_colormap GLXEW_GET_VAR(__GLXEW_MESA_pixmap_colormap) + +#endif /* GLX_MESA_pixmap_colormap */ + +/* ------------------------ GLX_MESA_release_buffers ----------------------- */ + +#ifndef GLX_MESA_release_buffers +#define GLX_MESA_release_buffers 1 + +typedef Bool ( * PFNGLXRELEASEBUFFERSMESAPROC) (Display* dpy, GLXDrawable d); + +#define glXReleaseBuffersMESA GLXEW_GET_FUN(__glewXReleaseBuffersMESA) + +#define GLXEW_MESA_release_buffers GLXEW_GET_VAR(__GLXEW_MESA_release_buffers) + +#endif /* GLX_MESA_release_buffers */ + +/* ------------------------- GLX_MESA_set_3dfx_mode ------------------------ */ + +#ifndef GLX_MESA_set_3dfx_mode +#define GLX_MESA_set_3dfx_mode 1 + +#define GLX_3DFX_WINDOW_MODE_MESA 0x1 +#define GLX_3DFX_FULLSCREEN_MODE_MESA 0x2 + +typedef GLboolean ( * PFNGLXSET3DFXMODEMESAPROC) (GLint mode); + +#define glXSet3DfxModeMESA GLXEW_GET_FUN(__glewXSet3DfxModeMESA) + +#define GLXEW_MESA_set_3dfx_mode GLXEW_GET_VAR(__GLXEW_MESA_set_3dfx_mode) + +#endif /* GLX_MESA_set_3dfx_mode */ + +/* -------------------------- GLX_NV_float_buffer -------------------------- */ + +#ifndef GLX_NV_float_buffer +#define GLX_NV_float_buffer 1 + +#define GLX_FLOAT_COMPONENTS_NV 0x20B0 + +#define GLXEW_NV_float_buffer GLXEW_GET_VAR(__GLXEW_NV_float_buffer) + +#endif /* GLX_NV_float_buffer */ + +/* -------------------------- GLX_NV_present_video ------------------------- */ + +#ifndef GLX_NV_present_video +#define GLX_NV_present_video 1 + +#define GLX_NUM_VIDEO_SLOTS_NV 0x20F0 + +typedef int ( * PFNGLXBINDVIDEODEVICENVPROC) (Display* dpy, unsigned int video_slot, unsigned int video_device, const int *attrib_list); +typedef unsigned int* ( * PFNGLXENUMERATEVIDEODEVICESNVPROC) (Display *dpy, int screen, int *nelements); + +#define glXBindVideoDeviceNV GLXEW_GET_FUN(__glewXBindVideoDeviceNV) +#define glXEnumerateVideoDevicesNV GLXEW_GET_FUN(__glewXEnumerateVideoDevicesNV) + +#define GLXEW_NV_present_video GLXEW_GET_VAR(__GLXEW_NV_present_video) + +#endif /* GLX_NV_present_video */ + +/* --------------------------- GLX_NV_swap_group --------------------------- */ + +#ifndef GLX_NV_swap_group +#define GLX_NV_swap_group 1 + +typedef Bool ( * PFNGLXBINDSWAPBARRIERNVPROC) (Display* dpy, GLuint group, GLuint barrier); +typedef Bool ( * PFNGLXJOINSWAPGROUPNVPROC) (Display* dpy, GLXDrawable drawable, GLuint group); +typedef Bool ( * PFNGLXQUERYFRAMECOUNTNVPROC) (Display* dpy, int screen, GLuint *count); +typedef Bool ( * PFNGLXQUERYMAXSWAPGROUPSNVPROC) (Display* dpy, int screen, GLuint *maxGroups, GLuint *maxBarriers); +typedef Bool ( * PFNGLXQUERYSWAPGROUPNVPROC) (Display* dpy, GLXDrawable drawable, GLuint *group, GLuint *barrier); +typedef Bool ( * PFNGLXRESETFRAMECOUNTNVPROC) (Display* dpy, int screen); + +#define glXBindSwapBarrierNV GLXEW_GET_FUN(__glewXBindSwapBarrierNV) +#define glXJoinSwapGroupNV GLXEW_GET_FUN(__glewXJoinSwapGroupNV) +#define glXQueryFrameCountNV GLXEW_GET_FUN(__glewXQueryFrameCountNV) +#define glXQueryMaxSwapGroupsNV GLXEW_GET_FUN(__glewXQueryMaxSwapGroupsNV) +#define glXQuerySwapGroupNV GLXEW_GET_FUN(__glewXQuerySwapGroupNV) +#define glXResetFrameCountNV GLXEW_GET_FUN(__glewXResetFrameCountNV) + +#define GLXEW_NV_swap_group GLXEW_GET_VAR(__GLXEW_NV_swap_group) + +#endif /* GLX_NV_swap_group */ + +/* ----------------------- GLX_NV_vertex_array_range ----------------------- */ + +#ifndef GLX_NV_vertex_array_range +#define GLX_NV_vertex_array_range 1 + +typedef void * ( * PFNGLXALLOCATEMEMORYNVPROC) (GLsizei size, GLfloat readFrequency, GLfloat writeFrequency, GLfloat priority); +typedef void ( * PFNGLXFREEMEMORYNVPROC) (void *pointer); + +#define glXAllocateMemoryNV GLXEW_GET_FUN(__glewXAllocateMemoryNV) +#define glXFreeMemoryNV GLXEW_GET_FUN(__glewXFreeMemoryNV) + +#define GLXEW_NV_vertex_array_range GLXEW_GET_VAR(__GLXEW_NV_vertex_array_range) + +#endif /* GLX_NV_vertex_array_range */ + +/* -------------------------- GLX_NV_video_output -------------------------- */ + +#ifndef GLX_NV_video_output +#define GLX_NV_video_output 1 + +#define GLX_VIDEO_OUT_COLOR_NV 0x20C3 +#define GLX_VIDEO_OUT_ALPHA_NV 0x20C4 +#define GLX_VIDEO_OUT_DEPTH_NV 0x20C5 +#define GLX_VIDEO_OUT_COLOR_AND_ALPHA_NV 0x20C6 +#define GLX_VIDEO_OUT_COLOR_AND_DEPTH_NV 0x20C7 +#define GLX_VIDEO_OUT_FRAME_NV 0x20C8 +#define GLX_VIDEO_OUT_FIELD_1_NV 0x20C9 +#define GLX_VIDEO_OUT_FIELD_2_NV 0x20CA +#define GLX_VIDEO_OUT_STACKED_FIELDS_1_2_NV 0x20CB +#define GLX_VIDEO_OUT_STACKED_FIELDS_2_1_NV 0x20CC + +typedef int ( * PFNGLXBINDVIDEOIMAGENVPROC) (Display* dpy, GLXVideoDeviceNV VideoDevice, GLXPbuffer pbuf, int iVideoBuffer); +typedef int ( * PFNGLXGETVIDEODEVICENVPROC) (Display* dpy, int screen, int numVideoDevices, GLXVideoDeviceNV *pVideoDevice); +typedef int ( * PFNGLXGETVIDEOINFONVPROC) (Display* dpy, int screen, GLXVideoDeviceNV VideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +typedef int ( * PFNGLXRELEASEVIDEODEVICENVPROC) (Display* dpy, int screen, GLXVideoDeviceNV VideoDevice); +typedef int ( * PFNGLXRELEASEVIDEOIMAGENVPROC) (Display* dpy, GLXPbuffer pbuf); +typedef int ( * PFNGLXSENDPBUFFERTOVIDEONVPROC) (Display* dpy, GLXPbuffer pbuf, int iBufferType, unsigned long *pulCounterPbuffer, GLboolean bBlock); + +#define glXBindVideoImageNV GLXEW_GET_FUN(__glewXBindVideoImageNV) +#define glXGetVideoDeviceNV GLXEW_GET_FUN(__glewXGetVideoDeviceNV) +#define glXGetVideoInfoNV GLXEW_GET_FUN(__glewXGetVideoInfoNV) +#define glXReleaseVideoDeviceNV GLXEW_GET_FUN(__glewXReleaseVideoDeviceNV) +#define glXReleaseVideoImageNV GLXEW_GET_FUN(__glewXReleaseVideoImageNV) +#define glXSendPbufferToVideoNV GLXEW_GET_FUN(__glewXSendPbufferToVideoNV) + +#define GLXEW_NV_video_output GLXEW_GET_VAR(__GLXEW_NV_video_output) + +#endif /* GLX_NV_video_output */ + +/* -------------------------- GLX_OML_swap_method -------------------------- */ + +#ifndef GLX_OML_swap_method +#define GLX_OML_swap_method 1 + +#define GLX_SWAP_METHOD_OML 0x8060 +#define GLX_SWAP_EXCHANGE_OML 0x8061 +#define GLX_SWAP_COPY_OML 0x8062 +#define GLX_SWAP_UNDEFINED_OML 0x8063 + +#define GLXEW_OML_swap_method GLXEW_GET_VAR(__GLXEW_OML_swap_method) + +#endif /* GLX_OML_swap_method */ + +/* -------------------------- GLX_OML_sync_control ------------------------- */ + +#if !defined(GLX_OML_sync_control) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#include +#define GLX_OML_sync_control 1 + +typedef Bool ( * PFNGLXGETMSCRATEOMLPROC) (Display* dpy, GLXDrawable drawable, int32_t* numerator, int32_t* denominator); +typedef Bool ( * PFNGLXGETSYNCVALUESOMLPROC) (Display* dpy, GLXDrawable drawable, int64_t* ust, int64_t* msc, int64_t* sbc); +typedef int64_t ( * PFNGLXSWAPBUFFERSMSCOMLPROC) (Display* dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder); +typedef Bool ( * PFNGLXWAITFORMSCOMLPROC) (Display* dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t* ust, int64_t* msc, int64_t* sbc); +typedef Bool ( * PFNGLXWAITFORSBCOMLPROC) (Display* dpy, GLXDrawable drawable, int64_t target_sbc, int64_t* ust, int64_t* msc, int64_t* sbc); + +#define glXGetMscRateOML GLXEW_GET_FUN(__glewXGetMscRateOML) +#define glXGetSyncValuesOML GLXEW_GET_FUN(__glewXGetSyncValuesOML) +#define glXSwapBuffersMscOML GLXEW_GET_FUN(__glewXSwapBuffersMscOML) +#define glXWaitForMscOML GLXEW_GET_FUN(__glewXWaitForMscOML) +#define glXWaitForSbcOML GLXEW_GET_FUN(__glewXWaitForSbcOML) + +#define GLXEW_OML_sync_control GLXEW_GET_VAR(__GLXEW_OML_sync_control) + +#endif /* GLX_OML_sync_control */ + +/* ------------------------ GLX_SGIS_blended_overlay ----------------------- */ + +#ifndef GLX_SGIS_blended_overlay +#define GLX_SGIS_blended_overlay 1 + +#define GLX_BLENDED_RGBA_SGIS 0x8025 + +#define GLXEW_SGIS_blended_overlay GLXEW_GET_VAR(__GLXEW_SGIS_blended_overlay) + +#endif /* GLX_SGIS_blended_overlay */ + +/* -------------------------- GLX_SGIS_color_range ------------------------- */ + +#ifndef GLX_SGIS_color_range +#define GLX_SGIS_color_range 1 + +#define GLX_MIN_RED_SGIS 0 +#define GLX_MAX_GREEN_SGIS 0 +#define GLX_MIN_BLUE_SGIS 0 +#define GLX_MAX_ALPHA_SGIS 0 +#define GLX_MIN_GREEN_SGIS 0 +#define GLX_MIN_ALPHA_SGIS 0 +#define GLX_MAX_RED_SGIS 0 +#define GLX_EXTENDED_RANGE_SGIS 0 +#define GLX_MAX_BLUE_SGIS 0 + +#define GLXEW_SGIS_color_range GLXEW_GET_VAR(__GLXEW_SGIS_color_range) + +#endif /* GLX_SGIS_color_range */ + +/* -------------------------- GLX_SGIS_multisample ------------------------- */ + +#ifndef GLX_SGIS_multisample +#define GLX_SGIS_multisample 1 + +#define GLX_SAMPLE_BUFFERS_SGIS 100000 +#define GLX_SAMPLES_SGIS 100001 + +#define GLXEW_SGIS_multisample GLXEW_GET_VAR(__GLXEW_SGIS_multisample) + +#endif /* GLX_SGIS_multisample */ + +/* ---------------------- GLX_SGIS_shared_multisample ---------------------- */ + +#ifndef GLX_SGIS_shared_multisample +#define GLX_SGIS_shared_multisample 1 + +#define GLX_MULTISAMPLE_SUB_RECT_WIDTH_SGIS 0x8026 +#define GLX_MULTISAMPLE_SUB_RECT_HEIGHT_SGIS 0x8027 + +#define GLXEW_SGIS_shared_multisample GLXEW_GET_VAR(__GLXEW_SGIS_shared_multisample) + +#endif /* GLX_SGIS_shared_multisample */ + +/* --------------------------- GLX_SGIX_fbconfig --------------------------- */ + +#ifndef GLX_SGIX_fbconfig +#define GLX_SGIX_fbconfig 1 + +#define GLX_WINDOW_BIT_SGIX 0x00000001 +#define GLX_RGBA_BIT_SGIX 0x00000001 +#define GLX_PIXMAP_BIT_SGIX 0x00000002 +#define GLX_COLOR_INDEX_BIT_SGIX 0x00000002 +#define GLX_SCREEN_EXT 0x800C +#define GLX_DRAWABLE_TYPE_SGIX 0x8010 +#define GLX_RENDER_TYPE_SGIX 0x8011 +#define GLX_X_RENDERABLE_SGIX 0x8012 +#define GLX_FBCONFIG_ID_SGIX 0x8013 +#define GLX_RGBA_TYPE_SGIX 0x8014 +#define GLX_COLOR_INDEX_TYPE_SGIX 0x8015 + +typedef XID GLXFBConfigIDSGIX; +typedef struct __GLXFBConfigRec *GLXFBConfigSGIX; + +typedef GLXFBConfigSGIX* ( * PFNGLXCHOOSEFBCONFIGSGIXPROC) (Display *dpy, int screen, const int *attrib_list, int *nelements); +typedef GLXContext ( * PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC) (Display* dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); +typedef GLXPixmap ( * PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC) (Display* dpy, GLXFBConfig config, Pixmap pixmap); +typedef int ( * PFNGLXGETFBCONFIGATTRIBSGIXPROC) (Display* dpy, GLXFBConfigSGIX config, int attribute, int *value); +typedef GLXFBConfigSGIX ( * PFNGLXGETFBCONFIGFROMVISUALSGIXPROC) (Display* dpy, XVisualInfo *vis); +typedef XVisualInfo* ( * PFNGLXGETVISUALFROMFBCONFIGSGIXPROC) (Display *dpy, GLXFBConfig config); + +#define glXChooseFBConfigSGIX GLXEW_GET_FUN(__glewXChooseFBConfigSGIX) +#define glXCreateContextWithConfigSGIX GLXEW_GET_FUN(__glewXCreateContextWithConfigSGIX) +#define glXCreateGLXPixmapWithConfigSGIX GLXEW_GET_FUN(__glewXCreateGLXPixmapWithConfigSGIX) +#define glXGetFBConfigAttribSGIX GLXEW_GET_FUN(__glewXGetFBConfigAttribSGIX) +#define glXGetFBConfigFromVisualSGIX GLXEW_GET_FUN(__glewXGetFBConfigFromVisualSGIX) +#define glXGetVisualFromFBConfigSGIX GLXEW_GET_FUN(__glewXGetVisualFromFBConfigSGIX) + +#define GLXEW_SGIX_fbconfig GLXEW_GET_VAR(__GLXEW_SGIX_fbconfig) + +#endif /* GLX_SGIX_fbconfig */ + +/* --------------------------- GLX_SGIX_hyperpipe -------------------------- */ + +#ifndef GLX_SGIX_hyperpipe +#define GLX_SGIX_hyperpipe 1 + +#define GLX_HYPERPIPE_DISPLAY_PIPE_SGIX 0x00000001 +#define GLX_PIPE_RECT_SGIX 0x00000001 +#define GLX_PIPE_RECT_LIMITS_SGIX 0x00000002 +#define GLX_HYPERPIPE_RENDER_PIPE_SGIX 0x00000002 +#define GLX_HYPERPIPE_STEREO_SGIX 0x00000003 +#define GLX_HYPERPIPE_PIXEL_AVERAGE_SGIX 0x00000004 +#define GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX 80 +#define GLX_BAD_HYPERPIPE_CONFIG_SGIX 91 +#define GLX_BAD_HYPERPIPE_SGIX 92 +#define GLX_HYPERPIPE_ID_SGIX 0x8030 + +typedef struct { + char pipeName[GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX]; + int networkId; +} GLXHyperpipeNetworkSGIX; +typedef struct { + char pipeName[GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX]; + int XOrigin; + int YOrigin; + int maxHeight; + int maxWidth; +} GLXPipeRectLimits; +typedef struct { + char pipeName[GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX]; + int channel; + unsigned int participationType; + int timeSlice; +} GLXHyperpipeConfigSGIX; +typedef struct { + char pipeName[GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX]; + int srcXOrigin; + int srcYOrigin; + int srcWidth; + int srcHeight; + int destXOrigin; + int destYOrigin; + int destWidth; + int destHeight; +} GLXPipeRect; + +typedef int ( * PFNGLXBINDHYPERPIPESGIXPROC) (Display *dpy, int hpId); +typedef int ( * PFNGLXDESTROYHYPERPIPECONFIGSGIXPROC) (Display *dpy, int hpId); +typedef int ( * PFNGLXHYPERPIPEATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *attribList); +typedef int ( * PFNGLXHYPERPIPECONFIGSGIXPROC) (Display *dpy, int networkId, int npipes, GLXHyperpipeConfigSGIX *cfg, int *hpId); +typedef int ( * PFNGLXQUERYHYPERPIPEATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *returnAttribList); +typedef int ( * PFNGLXQUERYHYPERPIPEBESTATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *attribList, void *returnAttribList); +typedef GLXHyperpipeConfigSGIX * ( * PFNGLXQUERYHYPERPIPECONFIGSGIXPROC) (Display *dpy, int hpId, int *npipes); +typedef GLXHyperpipeNetworkSGIX * ( * PFNGLXQUERYHYPERPIPENETWORKSGIXPROC) (Display *dpy, int *npipes); + +#define glXBindHyperpipeSGIX GLXEW_GET_FUN(__glewXBindHyperpipeSGIX) +#define glXDestroyHyperpipeConfigSGIX GLXEW_GET_FUN(__glewXDestroyHyperpipeConfigSGIX) +#define glXHyperpipeAttribSGIX GLXEW_GET_FUN(__glewXHyperpipeAttribSGIX) +#define glXHyperpipeConfigSGIX GLXEW_GET_FUN(__glewXHyperpipeConfigSGIX) +#define glXQueryHyperpipeAttribSGIX GLXEW_GET_FUN(__glewXQueryHyperpipeAttribSGIX) +#define glXQueryHyperpipeBestAttribSGIX GLXEW_GET_FUN(__glewXQueryHyperpipeBestAttribSGIX) +#define glXQueryHyperpipeConfigSGIX GLXEW_GET_FUN(__glewXQueryHyperpipeConfigSGIX) +#define glXQueryHyperpipeNetworkSGIX GLXEW_GET_FUN(__glewXQueryHyperpipeNetworkSGIX) + +#define GLXEW_SGIX_hyperpipe GLXEW_GET_VAR(__GLXEW_SGIX_hyperpipe) + +#endif /* GLX_SGIX_hyperpipe */ + +/* ---------------------------- GLX_SGIX_pbuffer --------------------------- */ + +#ifndef GLX_SGIX_pbuffer +#define GLX_SGIX_pbuffer 1 + +#define GLX_FRONT_LEFT_BUFFER_BIT_SGIX 0x00000001 +#define GLX_FRONT_RIGHT_BUFFER_BIT_SGIX 0x00000002 +#define GLX_PBUFFER_BIT_SGIX 0x00000004 +#define GLX_BACK_LEFT_BUFFER_BIT_SGIX 0x00000004 +#define GLX_BACK_RIGHT_BUFFER_BIT_SGIX 0x00000008 +#define GLX_AUX_BUFFERS_BIT_SGIX 0x00000010 +#define GLX_DEPTH_BUFFER_BIT_SGIX 0x00000020 +#define GLX_STENCIL_BUFFER_BIT_SGIX 0x00000040 +#define GLX_ACCUM_BUFFER_BIT_SGIX 0x00000080 +#define GLX_SAMPLE_BUFFERS_BIT_SGIX 0x00000100 +#define GLX_MAX_PBUFFER_WIDTH_SGIX 0x8016 +#define GLX_MAX_PBUFFER_HEIGHT_SGIX 0x8017 +#define GLX_MAX_PBUFFER_PIXELS_SGIX 0x8018 +#define GLX_OPTIMAL_PBUFFER_WIDTH_SGIX 0x8019 +#define GLX_OPTIMAL_PBUFFER_HEIGHT_SGIX 0x801A +#define GLX_PRESERVED_CONTENTS_SGIX 0x801B +#define GLX_LARGEST_PBUFFER_SGIX 0x801C +#define GLX_WIDTH_SGIX 0x801D +#define GLX_HEIGHT_SGIX 0x801E +#define GLX_EVENT_MASK_SGIX 0x801F +#define GLX_DAMAGED_SGIX 0x8020 +#define GLX_SAVED_SGIX 0x8021 +#define GLX_WINDOW_SGIX 0x8022 +#define GLX_PBUFFER_SGIX 0x8023 +#define GLX_BUFFER_CLOBBER_MASK_SGIX 0x08000000 + +typedef XID GLXPbufferSGIX; +typedef struct { int type; unsigned long serial; Bool send_event; Display *display; GLXDrawable drawable; int event_type; int draw_type; unsigned int mask; int x, y; int width, height; int count; } GLXBufferClobberEventSGIX; + +typedef GLXPbuffer ( * PFNGLXCREATEGLXPBUFFERSGIXPROC) (Display* dpy, GLXFBConfig config, unsigned int width, unsigned int height, int *attrib_list); +typedef void ( * PFNGLXDESTROYGLXPBUFFERSGIXPROC) (Display* dpy, GLXPbuffer pbuf); +typedef void ( * PFNGLXGETSELECTEDEVENTSGIXPROC) (Display* dpy, GLXDrawable drawable, unsigned long *mask); +typedef void ( * PFNGLXQUERYGLXPBUFFERSGIXPROC) (Display* dpy, GLXPbuffer pbuf, int attribute, unsigned int *value); +typedef void ( * PFNGLXSELECTEVENTSGIXPROC) (Display* dpy, GLXDrawable drawable, unsigned long mask); + +#define glXCreateGLXPbufferSGIX GLXEW_GET_FUN(__glewXCreateGLXPbufferSGIX) +#define glXDestroyGLXPbufferSGIX GLXEW_GET_FUN(__glewXDestroyGLXPbufferSGIX) +#define glXGetSelectedEventSGIX GLXEW_GET_FUN(__glewXGetSelectedEventSGIX) +#define glXQueryGLXPbufferSGIX GLXEW_GET_FUN(__glewXQueryGLXPbufferSGIX) +#define glXSelectEventSGIX GLXEW_GET_FUN(__glewXSelectEventSGIX) + +#define GLXEW_SGIX_pbuffer GLXEW_GET_VAR(__GLXEW_SGIX_pbuffer) + +#endif /* GLX_SGIX_pbuffer */ + +/* ------------------------- GLX_SGIX_swap_barrier ------------------------- */ + +#ifndef GLX_SGIX_swap_barrier +#define GLX_SGIX_swap_barrier 1 + +typedef void ( * PFNGLXBINDSWAPBARRIERSGIXPROC) (Display *dpy, GLXDrawable drawable, int barrier); +typedef Bool ( * PFNGLXQUERYMAXSWAPBARRIERSSGIXPROC) (Display *dpy, int screen, int *max); + +#define glXBindSwapBarrierSGIX GLXEW_GET_FUN(__glewXBindSwapBarrierSGIX) +#define glXQueryMaxSwapBarriersSGIX GLXEW_GET_FUN(__glewXQueryMaxSwapBarriersSGIX) + +#define GLXEW_SGIX_swap_barrier GLXEW_GET_VAR(__GLXEW_SGIX_swap_barrier) + +#endif /* GLX_SGIX_swap_barrier */ + +/* -------------------------- GLX_SGIX_swap_group -------------------------- */ + +#ifndef GLX_SGIX_swap_group +#define GLX_SGIX_swap_group 1 + +typedef void ( * PFNGLXJOINSWAPGROUPSGIXPROC) (Display *dpy, GLXDrawable drawable, GLXDrawable member); + +#define glXJoinSwapGroupSGIX GLXEW_GET_FUN(__glewXJoinSwapGroupSGIX) + +#define GLXEW_SGIX_swap_group GLXEW_GET_VAR(__GLXEW_SGIX_swap_group) + +#endif /* GLX_SGIX_swap_group */ + +/* ------------------------- GLX_SGIX_video_resize ------------------------- */ + +#ifndef GLX_SGIX_video_resize +#define GLX_SGIX_video_resize 1 + +#define GLX_SYNC_FRAME_SGIX 0x00000000 +#define GLX_SYNC_SWAP_SGIX 0x00000001 + +typedef int ( * PFNGLXBINDCHANNELTOWINDOWSGIXPROC) (Display* display, int screen, int channel, Window window); +typedef int ( * PFNGLXCHANNELRECTSGIXPROC) (Display* display, int screen, int channel, int x, int y, int w, int h); +typedef int ( * PFNGLXCHANNELRECTSYNCSGIXPROC) (Display* display, int screen, int channel, GLenum synctype); +typedef int ( * PFNGLXQUERYCHANNELDELTASSGIXPROC) (Display* display, int screen, int channel, int *x, int *y, int *w, int *h); +typedef int ( * PFNGLXQUERYCHANNELRECTSGIXPROC) (Display* display, int screen, int channel, int *dx, int *dy, int *dw, int *dh); + +#define glXBindChannelToWindowSGIX GLXEW_GET_FUN(__glewXBindChannelToWindowSGIX) +#define glXChannelRectSGIX GLXEW_GET_FUN(__glewXChannelRectSGIX) +#define glXChannelRectSyncSGIX GLXEW_GET_FUN(__glewXChannelRectSyncSGIX) +#define glXQueryChannelDeltasSGIX GLXEW_GET_FUN(__glewXQueryChannelDeltasSGIX) +#define glXQueryChannelRectSGIX GLXEW_GET_FUN(__glewXQueryChannelRectSGIX) + +#define GLXEW_SGIX_video_resize GLXEW_GET_VAR(__GLXEW_SGIX_video_resize) + +#endif /* GLX_SGIX_video_resize */ + +/* ---------------------- GLX_SGIX_visual_select_group --------------------- */ + +#ifndef GLX_SGIX_visual_select_group +#define GLX_SGIX_visual_select_group 1 + +#define GLX_VISUAL_SELECT_GROUP_SGIX 0x8028 + +#define GLXEW_SGIX_visual_select_group GLXEW_GET_VAR(__GLXEW_SGIX_visual_select_group) + +#endif /* GLX_SGIX_visual_select_group */ + +/* ---------------------------- GLX_SGI_cushion ---------------------------- */ + +#ifndef GLX_SGI_cushion +#define GLX_SGI_cushion 1 + +typedef void ( * PFNGLXCUSHIONSGIPROC) (Display* dpy, Window window, float cushion); + +#define glXCushionSGI GLXEW_GET_FUN(__glewXCushionSGI) + +#define GLXEW_SGI_cushion GLXEW_GET_VAR(__GLXEW_SGI_cushion) + +#endif /* GLX_SGI_cushion */ + +/* ----------------------- GLX_SGI_make_current_read ----------------------- */ + +#ifndef GLX_SGI_make_current_read +#define GLX_SGI_make_current_read 1 + +typedef GLXDrawable ( * PFNGLXGETCURRENTREADDRAWABLESGIPROC) (void); +typedef Bool ( * PFNGLXMAKECURRENTREADSGIPROC) (Display* dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); + +#define glXGetCurrentReadDrawableSGI GLXEW_GET_FUN(__glewXGetCurrentReadDrawableSGI) +#define glXMakeCurrentReadSGI GLXEW_GET_FUN(__glewXMakeCurrentReadSGI) + +#define GLXEW_SGI_make_current_read GLXEW_GET_VAR(__GLXEW_SGI_make_current_read) + +#endif /* GLX_SGI_make_current_read */ + +/* -------------------------- GLX_SGI_swap_control ------------------------- */ + +#ifndef GLX_SGI_swap_control +#define GLX_SGI_swap_control 1 + +typedef int ( * PFNGLXSWAPINTERVALSGIPROC) (int interval); + +#define glXSwapIntervalSGI GLXEW_GET_FUN(__glewXSwapIntervalSGI) + +#define GLXEW_SGI_swap_control GLXEW_GET_VAR(__GLXEW_SGI_swap_control) + +#endif /* GLX_SGI_swap_control */ + +/* --------------------------- GLX_SGI_video_sync -------------------------- */ + +#ifndef GLX_SGI_video_sync +#define GLX_SGI_video_sync 1 + +typedef int ( * PFNGLXGETVIDEOSYNCSGIPROC) (uint* count); +typedef int ( * PFNGLXWAITVIDEOSYNCSGIPROC) (int divisor, int remainder, unsigned int* count); + +#define glXGetVideoSyncSGI GLXEW_GET_FUN(__glewXGetVideoSyncSGI) +#define glXWaitVideoSyncSGI GLXEW_GET_FUN(__glewXWaitVideoSyncSGI) + +#define GLXEW_SGI_video_sync GLXEW_GET_VAR(__GLXEW_SGI_video_sync) + +#endif /* GLX_SGI_video_sync */ + +/* --------------------- GLX_SUN_get_transparent_index --------------------- */ + +#ifndef GLX_SUN_get_transparent_index +#define GLX_SUN_get_transparent_index 1 + +typedef Status ( * PFNGLXGETTRANSPARENTINDEXSUNPROC) (Display* dpy, Window overlay, Window underlay, unsigned long *pTransparentIndex); + +#define glXGetTransparentIndexSUN GLXEW_GET_FUN(__glewXGetTransparentIndexSUN) + +#define GLXEW_SUN_get_transparent_index GLXEW_GET_VAR(__GLXEW_SUN_get_transparent_index) + +#endif /* GLX_SUN_get_transparent_index */ + +/* -------------------------- GLX_SUN_video_resize ------------------------- */ + +#ifndef GLX_SUN_video_resize +#define GLX_SUN_video_resize 1 + +#define GLX_VIDEO_RESIZE_SUN 0x8171 +#define GL_VIDEO_RESIZE_COMPENSATION_SUN 0x85CD + +typedef int ( * PFNGLXGETVIDEORESIZESUNPROC) (Display* display, GLXDrawable window, float* factor); +typedef int ( * PFNGLXVIDEORESIZESUNPROC) (Display* display, GLXDrawable window, float factor); + +#define glXGetVideoResizeSUN GLXEW_GET_FUN(__glewXGetVideoResizeSUN) +#define glXVideoResizeSUN GLXEW_GET_FUN(__glewXVideoResizeSUN) + +#define GLXEW_SUN_video_resize GLXEW_GET_VAR(__GLXEW_SUN_video_resize) + +#endif /* GLX_SUN_video_resize */ + +/* ------------------------------------------------------------------------- */ + +#ifdef GLEW_MX +#define GLXEW_EXPORT +#else +#define GLXEW_EXPORT extern +#endif /* GLEW_MX */ + +extern PFNGLXGETCURRENTDISPLAYPROC __glewXGetCurrentDisplay; + +extern PFNGLXCHOOSEFBCONFIGPROC __glewXChooseFBConfig; +extern PFNGLXCREATENEWCONTEXTPROC __glewXCreateNewContext; +extern PFNGLXCREATEPBUFFERPROC __glewXCreatePbuffer; +extern PFNGLXCREATEPIXMAPPROC __glewXCreatePixmap; +extern PFNGLXCREATEWINDOWPROC __glewXCreateWindow; +extern PFNGLXDESTROYPBUFFERPROC __glewXDestroyPbuffer; +extern PFNGLXDESTROYPIXMAPPROC __glewXDestroyPixmap; +extern PFNGLXDESTROYWINDOWPROC __glewXDestroyWindow; +extern PFNGLXGETCURRENTREADDRAWABLEPROC __glewXGetCurrentReadDrawable; +extern PFNGLXGETFBCONFIGATTRIBPROC __glewXGetFBConfigAttrib; +extern PFNGLXGETFBCONFIGSPROC __glewXGetFBConfigs; +extern PFNGLXGETSELECTEDEVENTPROC __glewXGetSelectedEvent; +extern PFNGLXGETVISUALFROMFBCONFIGPROC __glewXGetVisualFromFBConfig; +extern PFNGLXMAKECONTEXTCURRENTPROC __glewXMakeContextCurrent; +extern PFNGLXQUERYCONTEXTPROC __glewXQueryContext; +extern PFNGLXQUERYDRAWABLEPROC __glewXQueryDrawable; +extern PFNGLXSELECTEVENTPROC __glewXSelectEvent; + +extern PFNGLXCREATECONTEXTATTRIBSARBPROC __glewXCreateContextAttribsARB; + +extern PFNGLXBINDTEXIMAGEATIPROC __glewXBindTexImageATI; +extern PFNGLXDRAWABLEATTRIBATIPROC __glewXDrawableAttribATI; +extern PFNGLXRELEASETEXIMAGEATIPROC __glewXReleaseTexImageATI; + +extern PFNGLXFREECONTEXTEXTPROC __glewXFreeContextEXT; +extern PFNGLXGETCONTEXTIDEXTPROC __glewXGetContextIDEXT; +extern PFNGLXIMPORTCONTEXTEXTPROC __glewXImportContextEXT; +extern PFNGLXQUERYCONTEXTINFOEXTPROC __glewXQueryContextInfoEXT; + +extern PFNGLXBINDTEXIMAGEEXTPROC __glewXBindTexImageEXT; +extern PFNGLXRELEASETEXIMAGEEXTPROC __glewXReleaseTexImageEXT; + +extern PFNGLXGETAGPOFFSETMESAPROC __glewXGetAGPOffsetMESA; + +extern PFNGLXCOPYSUBBUFFERMESAPROC __glewXCopySubBufferMESA; + +extern PFNGLXCREATEGLXPIXMAPMESAPROC __glewXCreateGLXPixmapMESA; + +extern PFNGLXRELEASEBUFFERSMESAPROC __glewXReleaseBuffersMESA; + +extern PFNGLXSET3DFXMODEMESAPROC __glewXSet3DfxModeMESA; + +extern PFNGLXBINDVIDEODEVICENVPROC __glewXBindVideoDeviceNV; +extern PFNGLXENUMERATEVIDEODEVICESNVPROC __glewXEnumerateVideoDevicesNV; + +extern PFNGLXBINDSWAPBARRIERNVPROC __glewXBindSwapBarrierNV; +extern PFNGLXJOINSWAPGROUPNVPROC __glewXJoinSwapGroupNV; +extern PFNGLXQUERYFRAMECOUNTNVPROC __glewXQueryFrameCountNV; +extern PFNGLXQUERYMAXSWAPGROUPSNVPROC __glewXQueryMaxSwapGroupsNV; +extern PFNGLXQUERYSWAPGROUPNVPROC __glewXQuerySwapGroupNV; +extern PFNGLXRESETFRAMECOUNTNVPROC __glewXResetFrameCountNV; + +extern PFNGLXALLOCATEMEMORYNVPROC __glewXAllocateMemoryNV; +extern PFNGLXFREEMEMORYNVPROC __glewXFreeMemoryNV; + +extern PFNGLXBINDVIDEOIMAGENVPROC __glewXBindVideoImageNV; +extern PFNGLXGETVIDEODEVICENVPROC __glewXGetVideoDeviceNV; +extern PFNGLXGETVIDEOINFONVPROC __glewXGetVideoInfoNV; +extern PFNGLXRELEASEVIDEODEVICENVPROC __glewXReleaseVideoDeviceNV; +extern PFNGLXRELEASEVIDEOIMAGENVPROC __glewXReleaseVideoImageNV; +extern PFNGLXSENDPBUFFERTOVIDEONVPROC __glewXSendPbufferToVideoNV; + +#ifdef GLX_OML_sync_control +extern PFNGLXGETMSCRATEOMLPROC __glewXGetMscRateOML; +extern PFNGLXGETSYNCVALUESOMLPROC __glewXGetSyncValuesOML; +extern PFNGLXSWAPBUFFERSMSCOMLPROC __glewXSwapBuffersMscOML; +extern PFNGLXWAITFORMSCOMLPROC __glewXWaitForMscOML; +extern PFNGLXWAITFORSBCOMLPROC __glewXWaitForSbcOML; +#endif + +extern PFNGLXCHOOSEFBCONFIGSGIXPROC __glewXChooseFBConfigSGIX; +extern PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC __glewXCreateContextWithConfigSGIX; +extern PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC __glewXCreateGLXPixmapWithConfigSGIX; +extern PFNGLXGETFBCONFIGATTRIBSGIXPROC __glewXGetFBConfigAttribSGIX; +extern PFNGLXGETFBCONFIGFROMVISUALSGIXPROC __glewXGetFBConfigFromVisualSGIX; +extern PFNGLXGETVISUALFROMFBCONFIGSGIXPROC __glewXGetVisualFromFBConfigSGIX; + +extern PFNGLXBINDHYPERPIPESGIXPROC __glewXBindHyperpipeSGIX; +extern PFNGLXDESTROYHYPERPIPECONFIGSGIXPROC __glewXDestroyHyperpipeConfigSGIX; +extern PFNGLXHYPERPIPEATTRIBSGIXPROC __glewXHyperpipeAttribSGIX; +extern PFNGLXHYPERPIPECONFIGSGIXPROC __glewXHyperpipeConfigSGIX; +extern PFNGLXQUERYHYPERPIPEATTRIBSGIXPROC __glewXQueryHyperpipeAttribSGIX; +extern PFNGLXQUERYHYPERPIPEBESTATTRIBSGIXPROC __glewXQueryHyperpipeBestAttribSGIX; +extern PFNGLXQUERYHYPERPIPECONFIGSGIXPROC __glewXQueryHyperpipeConfigSGIX; +extern PFNGLXQUERYHYPERPIPENETWORKSGIXPROC __glewXQueryHyperpipeNetworkSGIX; + +extern PFNGLXCREATEGLXPBUFFERSGIXPROC __glewXCreateGLXPbufferSGIX; +extern PFNGLXDESTROYGLXPBUFFERSGIXPROC __glewXDestroyGLXPbufferSGIX; +extern PFNGLXGETSELECTEDEVENTSGIXPROC __glewXGetSelectedEventSGIX; +extern PFNGLXQUERYGLXPBUFFERSGIXPROC __glewXQueryGLXPbufferSGIX; +extern PFNGLXSELECTEVENTSGIXPROC __glewXSelectEventSGIX; + +extern PFNGLXBINDSWAPBARRIERSGIXPROC __glewXBindSwapBarrierSGIX; +extern PFNGLXQUERYMAXSWAPBARRIERSSGIXPROC __glewXQueryMaxSwapBarriersSGIX; + +extern PFNGLXJOINSWAPGROUPSGIXPROC __glewXJoinSwapGroupSGIX; + +extern PFNGLXBINDCHANNELTOWINDOWSGIXPROC __glewXBindChannelToWindowSGIX; +extern PFNGLXCHANNELRECTSGIXPROC __glewXChannelRectSGIX; +extern PFNGLXCHANNELRECTSYNCSGIXPROC __glewXChannelRectSyncSGIX; +extern PFNGLXQUERYCHANNELDELTASSGIXPROC __glewXQueryChannelDeltasSGIX; +extern PFNGLXQUERYCHANNELRECTSGIXPROC __glewXQueryChannelRectSGIX; + +extern PFNGLXCUSHIONSGIPROC __glewXCushionSGI; + +extern PFNGLXGETCURRENTREADDRAWABLESGIPROC __glewXGetCurrentReadDrawableSGI; +extern PFNGLXMAKECURRENTREADSGIPROC __glewXMakeCurrentReadSGI; + +extern PFNGLXSWAPINTERVALSGIPROC __glewXSwapIntervalSGI; + +extern PFNGLXGETVIDEOSYNCSGIPROC __glewXGetVideoSyncSGI; +extern PFNGLXWAITVIDEOSYNCSGIPROC __glewXWaitVideoSyncSGI; + +extern PFNGLXGETTRANSPARENTINDEXSUNPROC __glewXGetTransparentIndexSUN; + +extern PFNGLXGETVIDEORESIZESUNPROC __glewXGetVideoResizeSUN; +extern PFNGLXVIDEORESIZESUNPROC __glewXVideoResizeSUN; + +#if defined(GLEW_MX) +struct GLXEWContextStruct +{ +#endif /* GLEW_MX */ + +GLXEW_EXPORT GLboolean __GLXEW_VERSION_1_0; +GLXEW_EXPORT GLboolean __GLXEW_VERSION_1_1; +GLXEW_EXPORT GLboolean __GLXEW_VERSION_1_2; +GLXEW_EXPORT GLboolean __GLXEW_VERSION_1_3; +GLXEW_EXPORT GLboolean __GLXEW_VERSION_1_4; +GLXEW_EXPORT GLboolean __GLXEW_3DFX_multisample; +GLXEW_EXPORT GLboolean __GLXEW_ARB_create_context; +GLXEW_EXPORT GLboolean __GLXEW_ARB_fbconfig_float; +GLXEW_EXPORT GLboolean __GLXEW_ARB_framebuffer_sRGB; +GLXEW_EXPORT GLboolean __GLXEW_ARB_get_proc_address; +GLXEW_EXPORT GLboolean __GLXEW_ARB_multisample; +GLXEW_EXPORT GLboolean __GLXEW_ATI_pixel_format_float; +GLXEW_EXPORT GLboolean __GLXEW_ATI_render_texture; +GLXEW_EXPORT GLboolean __GLXEW_EXT_fbconfig_packed_float; +GLXEW_EXPORT GLboolean __GLXEW_EXT_framebuffer_sRGB; +GLXEW_EXPORT GLboolean __GLXEW_EXT_import_context; +GLXEW_EXPORT GLboolean __GLXEW_EXT_scene_marker; +GLXEW_EXPORT GLboolean __GLXEW_EXT_texture_from_pixmap; +GLXEW_EXPORT GLboolean __GLXEW_EXT_visual_info; +GLXEW_EXPORT GLboolean __GLXEW_EXT_visual_rating; +GLXEW_EXPORT GLboolean __GLXEW_MESA_agp_offset; +GLXEW_EXPORT GLboolean __GLXEW_MESA_copy_sub_buffer; +GLXEW_EXPORT GLboolean __GLXEW_MESA_pixmap_colormap; +GLXEW_EXPORT GLboolean __GLXEW_MESA_release_buffers; +GLXEW_EXPORT GLboolean __GLXEW_MESA_set_3dfx_mode; +GLXEW_EXPORT GLboolean __GLXEW_NV_float_buffer; +GLXEW_EXPORT GLboolean __GLXEW_NV_present_video; +GLXEW_EXPORT GLboolean __GLXEW_NV_swap_group; +GLXEW_EXPORT GLboolean __GLXEW_NV_vertex_array_range; +GLXEW_EXPORT GLboolean __GLXEW_NV_video_output; +GLXEW_EXPORT GLboolean __GLXEW_OML_swap_method; +GLXEW_EXPORT GLboolean __GLXEW_OML_sync_control; +GLXEW_EXPORT GLboolean __GLXEW_SGIS_blended_overlay; +GLXEW_EXPORT GLboolean __GLXEW_SGIS_color_range; +GLXEW_EXPORT GLboolean __GLXEW_SGIS_multisample; +GLXEW_EXPORT GLboolean __GLXEW_SGIS_shared_multisample; +GLXEW_EXPORT GLboolean __GLXEW_SGIX_fbconfig; +GLXEW_EXPORT GLboolean __GLXEW_SGIX_hyperpipe; +GLXEW_EXPORT GLboolean __GLXEW_SGIX_pbuffer; +GLXEW_EXPORT GLboolean __GLXEW_SGIX_swap_barrier; +GLXEW_EXPORT GLboolean __GLXEW_SGIX_swap_group; +GLXEW_EXPORT GLboolean __GLXEW_SGIX_video_resize; +GLXEW_EXPORT GLboolean __GLXEW_SGIX_visual_select_group; +GLXEW_EXPORT GLboolean __GLXEW_SGI_cushion; +GLXEW_EXPORT GLboolean __GLXEW_SGI_make_current_read; +GLXEW_EXPORT GLboolean __GLXEW_SGI_swap_control; +GLXEW_EXPORT GLboolean __GLXEW_SGI_video_sync; +GLXEW_EXPORT GLboolean __GLXEW_SUN_get_transparent_index; +GLXEW_EXPORT GLboolean __GLXEW_SUN_video_resize; + +#ifdef GLEW_MX +}; /* GLXEWContextStruct */ +#endif /* GLEW_MX */ + +/* ------------------------------------------------------------------------ */ + +#ifdef GLEW_MX + +typedef struct GLXEWContextStruct GLXEWContext; +extern GLenum glxewContextInit (GLXEWContext* ctx); +extern GLboolean glxewContextIsSupported (GLXEWContext* ctx, const char* name); + +#define glxewInit() glxewContextInit(glxewGetContext()) +#define glxewIsSupported(x) glxewContextIsSupported(glxewGetContext(), x) + +#define GLXEW_GET_VAR(x) (*(const GLboolean*)&(glxewGetContext()->x)) +#define GLXEW_GET_FUN(x) x + +#else /* GLEW_MX */ + +#define GLXEW_GET_VAR(x) (*(const GLboolean*)&x) +#define GLXEW_GET_FUN(x) x + +extern GLboolean glxewIsSupported (const char* name); + +#endif /* GLEW_MX */ + +extern GLboolean glxewGetExtension (const char* name); + +#ifdef __cplusplus +} +#endif + +#endif /* __glxew_h__ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/wglew.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/wglew.h new file mode 100644 index 0000000..2eaad36 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/include/GL/wglew.h @@ -0,0 +1,1165 @@ +/* +** The OpenGL Extension Wrangler Library +** Copyright (C) 2002-2008, Milan Ikits +** Copyright (C) 2002-2008, Marcelo E. Magallon +** Copyright (C) 2002, Lev Povalahev +** 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. +** * The name of the author may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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. +*/ + +/* +** Copyright (c) 2007 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +#ifndef __wglew_h__ +#define __wglew_h__ +#define __WGLEW_H__ + +#ifdef __wglext_h_ +#error wglext.h included before wglew.h +#endif + +#define __wglext_h_ + +#if !defined(APIENTRY) && !defined(__CYGWIN__) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +# endif +#include +#endif + +/* + * GLEW_STATIC needs to be set when using the static version. + * GLEW_BUILD is set when building the DLL version. + */ +#ifdef GLEW_STATIC +# define GLEWAPI extern +#else +# ifdef GLEW_BUILD +# define GLEWAPI extern __declspec(dllexport) +# else +# define GLEWAPI extern __declspec(dllimport) +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* -------------------------- WGL_3DFX_multisample ------------------------- */ + +#ifndef WGL_3DFX_multisample +#define WGL_3DFX_multisample 1 + +#define WGL_SAMPLE_BUFFERS_3DFX 0x2060 +#define WGL_SAMPLES_3DFX 0x2061 + +#define WGLEW_3DFX_multisample WGLEW_GET_VAR(__WGLEW_3DFX_multisample) + +#endif /* WGL_3DFX_multisample */ + +/* ------------------------- WGL_3DL_stereo_control ------------------------ */ + +#ifndef WGL_3DL_stereo_control +#define WGL_3DL_stereo_control 1 + +#define WGL_STEREO_EMITTER_ENABLE_3DL 0x2055 +#define WGL_STEREO_EMITTER_DISABLE_3DL 0x2056 +#define WGL_STEREO_POLARITY_NORMAL_3DL 0x2057 +#define WGL_STEREO_POLARITY_INVERT_3DL 0x2058 + +typedef BOOL (WINAPI * PFNWGLSETSTEREOEMITTERSTATE3DLPROC) (HDC hDC, UINT uState); + +#define wglSetStereoEmitterState3DL WGLEW_GET_FUN(__wglewSetStereoEmitterState3DL) + +#define WGLEW_3DL_stereo_control WGLEW_GET_VAR(__WGLEW_3DL_stereo_control) + +#endif /* WGL_3DL_stereo_control */ + +/* ------------------------- WGL_ARB_buffer_region ------------------------- */ + +#ifndef WGL_ARB_buffer_region +#define WGL_ARB_buffer_region 1 + +#define WGL_FRONT_COLOR_BUFFER_BIT_ARB 0x00000001 +#define WGL_BACK_COLOR_BUFFER_BIT_ARB 0x00000002 +#define WGL_DEPTH_BUFFER_BIT_ARB 0x00000004 +#define WGL_STENCIL_BUFFER_BIT_ARB 0x00000008 + +typedef HANDLE (WINAPI * PFNWGLCREATEBUFFERREGIONARBPROC) (HDC hDC, int iLayerPlane, UINT uType); +typedef VOID (WINAPI * PFNWGLDELETEBUFFERREGIONARBPROC) (HANDLE hRegion); +typedef BOOL (WINAPI * PFNWGLRESTOREBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); +typedef BOOL (WINAPI * PFNWGLSAVEBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height); + +#define wglCreateBufferRegionARB WGLEW_GET_FUN(__wglewCreateBufferRegionARB) +#define wglDeleteBufferRegionARB WGLEW_GET_FUN(__wglewDeleteBufferRegionARB) +#define wglRestoreBufferRegionARB WGLEW_GET_FUN(__wglewRestoreBufferRegionARB) +#define wglSaveBufferRegionARB WGLEW_GET_FUN(__wglewSaveBufferRegionARB) + +#define WGLEW_ARB_buffer_region WGLEW_GET_VAR(__WGLEW_ARB_buffer_region) + +#endif /* WGL_ARB_buffer_region */ + +/* ------------------------- WGL_ARB_create_context ------------------------ */ + +#ifndef WGL_ARB_create_context +#define WGL_ARB_create_context 1 + +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 + +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int* attribList); + +#define wglCreateContextAttribsARB WGLEW_GET_FUN(__wglewCreateContextAttribsARB) + +#define WGLEW_ARB_create_context WGLEW_GET_VAR(__WGLEW_ARB_create_context) + +#endif /* WGL_ARB_create_context */ + +/* ----------------------- WGL_ARB_extensions_string ----------------------- */ + +#ifndef WGL_ARB_extensions_string +#define WGL_ARB_extensions_string 1 + +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc); + +#define wglGetExtensionsStringARB WGLEW_GET_FUN(__wglewGetExtensionsStringARB) + +#define WGLEW_ARB_extensions_string WGLEW_GET_VAR(__WGLEW_ARB_extensions_string) + +#endif /* WGL_ARB_extensions_string */ + +/* ------------------------ WGL_ARB_framebuffer_sRGB ----------------------- */ + +#ifndef WGL_ARB_framebuffer_sRGB +#define WGL_ARB_framebuffer_sRGB 1 + +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 + +#define WGLEW_ARB_framebuffer_sRGB WGLEW_GET_VAR(__WGLEW_ARB_framebuffer_sRGB) + +#endif /* WGL_ARB_framebuffer_sRGB */ + +/* ----------------------- WGL_ARB_make_current_read ----------------------- */ + +#ifndef WGL_ARB_make_current_read +#define WGL_ARB_make_current_read 1 + +#define ERROR_INVALID_PIXEL_TYPE_ARB 0x2043 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 + +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCARBPROC) (VOID); +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTARBPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); + +#define wglGetCurrentReadDCARB WGLEW_GET_FUN(__wglewGetCurrentReadDCARB) +#define wglMakeContextCurrentARB WGLEW_GET_FUN(__wglewMakeContextCurrentARB) + +#define WGLEW_ARB_make_current_read WGLEW_GET_VAR(__WGLEW_ARB_make_current_read) + +#endif /* WGL_ARB_make_current_read */ + +/* -------------------------- WGL_ARB_multisample -------------------------- */ + +#ifndef WGL_ARB_multisample +#define WGL_ARB_multisample 1 + +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 + +#define WGLEW_ARB_multisample WGLEW_GET_VAR(__WGLEW_ARB_multisample) + +#endif /* WGL_ARB_multisample */ + +/* ---------------------------- WGL_ARB_pbuffer ---------------------------- */ + +#ifndef WGL_ARB_pbuffer +#define WGL_ARB_pbuffer 1 + +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E +#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030 +#define WGL_PBUFFER_LARGEST_ARB 0x2033 +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_PBUFFER_LOST_ARB 0x2036 + +DECLARE_HANDLE(HPBUFFERARB); + +typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int* piAttribList); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int* piValue); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); + +#define wglCreatePbufferARB WGLEW_GET_FUN(__wglewCreatePbufferARB) +#define wglDestroyPbufferARB WGLEW_GET_FUN(__wglewDestroyPbufferARB) +#define wglGetPbufferDCARB WGLEW_GET_FUN(__wglewGetPbufferDCARB) +#define wglQueryPbufferARB WGLEW_GET_FUN(__wglewQueryPbufferARB) +#define wglReleasePbufferDCARB WGLEW_GET_FUN(__wglewReleasePbufferDCARB) + +#define WGLEW_ARB_pbuffer WGLEW_GET_VAR(__WGLEW_ARB_pbuffer) + +#endif /* WGL_ARB_pbuffer */ + +/* -------------------------- WGL_ARB_pixel_format ------------------------- */ + +#ifndef WGL_ARB_pixel_format +#define WGL_ARB_pixel_format 1 + +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B + +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int* piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int* piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int* piAttributes, int *piValues); + +#define wglChoosePixelFormatARB WGLEW_GET_FUN(__wglewChoosePixelFormatARB) +#define wglGetPixelFormatAttribfvARB WGLEW_GET_FUN(__wglewGetPixelFormatAttribfvARB) +#define wglGetPixelFormatAttribivARB WGLEW_GET_FUN(__wglewGetPixelFormatAttribivARB) + +#define WGLEW_ARB_pixel_format WGLEW_GET_VAR(__WGLEW_ARB_pixel_format) + +#endif /* WGL_ARB_pixel_format */ + +/* ----------------------- WGL_ARB_pixel_format_float ---------------------- */ + +#ifndef WGL_ARB_pixel_format_float +#define WGL_ARB_pixel_format_float 1 + +#define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0 + +#define WGLEW_ARB_pixel_format_float WGLEW_GET_VAR(__WGLEW_ARB_pixel_format_float) + +#endif /* WGL_ARB_pixel_format_float */ + +/* ------------------------- WGL_ARB_render_texture ------------------------ */ + +#ifndef WGL_ARB_render_texture +#define WGL_ARB_render_texture 1 + +#define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_MIPMAP_TEXTURE_ARB 0x2074 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 +#define WGL_TEXTURE_1D_ARB 0x2079 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_MIPMAP_LEVEL_ARB 0x207B +#define WGL_CUBE_MAP_FACE_ARB 0x207C +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x207E +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x207F +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x2080 +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x2081 +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x2082 +#define WGL_FRONT_LEFT_ARB 0x2083 +#define WGL_FRONT_RIGHT_ARB 0x2084 +#define WGL_BACK_LEFT_ARB 0x2085 +#define WGL_BACK_RIGHT_ARB 0x2086 +#define WGL_AUX0_ARB 0x2087 +#define WGL_AUX1_ARB 0x2088 +#define WGL_AUX2_ARB 0x2089 +#define WGL_AUX3_ARB 0x208A +#define WGL_AUX4_ARB 0x208B +#define WGL_AUX5_ARB 0x208C +#define WGL_AUX6_ARB 0x208D +#define WGL_AUX7_ARB 0x208E +#define WGL_AUX8_ARB 0x208F +#define WGL_AUX9_ARB 0x2090 + +typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int* piAttribList); + +#define wglBindTexImageARB WGLEW_GET_FUN(__wglewBindTexImageARB) +#define wglReleaseTexImageARB WGLEW_GET_FUN(__wglewReleaseTexImageARB) +#define wglSetPbufferAttribARB WGLEW_GET_FUN(__wglewSetPbufferAttribARB) + +#define WGLEW_ARB_render_texture WGLEW_GET_VAR(__WGLEW_ARB_render_texture) + +#endif /* WGL_ARB_render_texture */ + +/* ----------------------- WGL_ATI_pixel_format_float ---------------------- */ + +#ifndef WGL_ATI_pixel_format_float +#define WGL_ATI_pixel_format_float 1 + +#define WGL_TYPE_RGBA_FLOAT_ATI 0x21A0 +#define GL_RGBA_FLOAT_MODE_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 + +#define WGLEW_ATI_pixel_format_float WGLEW_GET_VAR(__WGLEW_ATI_pixel_format_float) + +#endif /* WGL_ATI_pixel_format_float */ + +/* -------------------- WGL_ATI_render_texture_rectangle ------------------- */ + +#ifndef WGL_ATI_render_texture_rectangle +#define WGL_ATI_render_texture_rectangle 1 + +#define WGL_TEXTURE_RECTANGLE_ATI 0x21A5 + +#define WGLEW_ATI_render_texture_rectangle WGLEW_GET_VAR(__WGLEW_ATI_render_texture_rectangle) + +#endif /* WGL_ATI_render_texture_rectangle */ + +/* -------------------------- WGL_EXT_depth_float -------------------------- */ + +#ifndef WGL_EXT_depth_float +#define WGL_EXT_depth_float 1 + +#define WGL_DEPTH_FLOAT_EXT 0x2040 + +#define WGLEW_EXT_depth_float WGLEW_GET_VAR(__WGLEW_EXT_depth_float) + +#endif /* WGL_EXT_depth_float */ + +/* ---------------------- WGL_EXT_display_color_table ---------------------- */ + +#ifndef WGL_EXT_display_color_table +#define WGL_EXT_display_color_table 1 + +typedef GLboolean (WINAPI * PFNWGLBINDDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef GLboolean (WINAPI * PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef void (WINAPI * PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef GLboolean (WINAPI * PFNWGLLOADDISPLAYCOLORTABLEEXTPROC) (GLushort* table, GLuint length); + +#define wglBindDisplayColorTableEXT WGLEW_GET_FUN(__wglewBindDisplayColorTableEXT) +#define wglCreateDisplayColorTableEXT WGLEW_GET_FUN(__wglewCreateDisplayColorTableEXT) +#define wglDestroyDisplayColorTableEXT WGLEW_GET_FUN(__wglewDestroyDisplayColorTableEXT) +#define wglLoadDisplayColorTableEXT WGLEW_GET_FUN(__wglewLoadDisplayColorTableEXT) + +#define WGLEW_EXT_display_color_table WGLEW_GET_VAR(__WGLEW_EXT_display_color_table) + +#endif /* WGL_EXT_display_color_table */ + +/* ----------------------- WGL_EXT_extensions_string ----------------------- */ + +#ifndef WGL_EXT_extensions_string +#define WGL_EXT_extensions_string 1 + +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC) (void); + +#define wglGetExtensionsStringEXT WGLEW_GET_FUN(__wglewGetExtensionsStringEXT) + +#define WGLEW_EXT_extensions_string WGLEW_GET_VAR(__WGLEW_EXT_extensions_string) + +#endif /* WGL_EXT_extensions_string */ + +/* ------------------------ WGL_EXT_framebuffer_sRGB ----------------------- */ + +#ifndef WGL_EXT_framebuffer_sRGB +#define WGL_EXT_framebuffer_sRGB 1 + +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20A9 + +#define WGLEW_EXT_framebuffer_sRGB WGLEW_GET_VAR(__WGLEW_EXT_framebuffer_sRGB) + +#endif /* WGL_EXT_framebuffer_sRGB */ + +/* ----------------------- WGL_EXT_make_current_read ----------------------- */ + +#ifndef WGL_EXT_make_current_read +#define WGL_EXT_make_current_read 1 + +#define ERROR_INVALID_PIXEL_TYPE_EXT 0x2043 + +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCEXTPROC) (VOID); +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTEXTPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); + +#define wglGetCurrentReadDCEXT WGLEW_GET_FUN(__wglewGetCurrentReadDCEXT) +#define wglMakeContextCurrentEXT WGLEW_GET_FUN(__wglewMakeContextCurrentEXT) + +#define WGLEW_EXT_make_current_read WGLEW_GET_VAR(__WGLEW_EXT_make_current_read) + +#endif /* WGL_EXT_make_current_read */ + +/* -------------------------- WGL_EXT_multisample -------------------------- */ + +#ifndef WGL_EXT_multisample +#define WGL_EXT_multisample 1 + +#define WGL_SAMPLE_BUFFERS_EXT 0x2041 +#define WGL_SAMPLES_EXT 0x2042 + +#define WGLEW_EXT_multisample WGLEW_GET_VAR(__WGLEW_EXT_multisample) + +#endif /* WGL_EXT_multisample */ + +/* ---------------------------- WGL_EXT_pbuffer ---------------------------- */ + +#ifndef WGL_EXT_pbuffer +#define WGL_EXT_pbuffer 1 + +#define WGL_DRAW_TO_PBUFFER_EXT 0x202D +#define WGL_MAX_PBUFFER_PIXELS_EXT 0x202E +#define WGL_MAX_PBUFFER_WIDTH_EXT 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_EXT 0x2030 +#define WGL_OPTIMAL_PBUFFER_WIDTH_EXT 0x2031 +#define WGL_OPTIMAL_PBUFFER_HEIGHT_EXT 0x2032 +#define WGL_PBUFFER_LARGEST_EXT 0x2033 +#define WGL_PBUFFER_WIDTH_EXT 0x2034 +#define WGL_PBUFFER_HEIGHT_EXT 0x2035 + +DECLARE_HANDLE(HPBUFFEREXT); + +typedef HPBUFFEREXT (WINAPI * PFNWGLCREATEPBUFFEREXTPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int* piAttribList); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer, int iAttribute, int* piValue); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer, HDC hDC); + +#define wglCreatePbufferEXT WGLEW_GET_FUN(__wglewCreatePbufferEXT) +#define wglDestroyPbufferEXT WGLEW_GET_FUN(__wglewDestroyPbufferEXT) +#define wglGetPbufferDCEXT WGLEW_GET_FUN(__wglewGetPbufferDCEXT) +#define wglQueryPbufferEXT WGLEW_GET_FUN(__wglewQueryPbufferEXT) +#define wglReleasePbufferDCEXT WGLEW_GET_FUN(__wglewReleasePbufferDCEXT) + +#define WGLEW_EXT_pbuffer WGLEW_GET_VAR(__WGLEW_EXT_pbuffer) + +#endif /* WGL_EXT_pbuffer */ + +/* -------------------------- WGL_EXT_pixel_format ------------------------- */ + +#ifndef WGL_EXT_pixel_format +#define WGL_EXT_pixel_format 1 + +#define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000 +#define WGL_DRAW_TO_WINDOW_EXT 0x2001 +#define WGL_DRAW_TO_BITMAP_EXT 0x2002 +#define WGL_ACCELERATION_EXT 0x2003 +#define WGL_NEED_PALETTE_EXT 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006 +#define WGL_SWAP_METHOD_EXT 0x2007 +#define WGL_NUMBER_OVERLAYS_EXT 0x2008 +#define WGL_NUMBER_UNDERLAYS_EXT 0x2009 +#define WGL_TRANSPARENT_EXT 0x200A +#define WGL_TRANSPARENT_VALUE_EXT 0x200B +#define WGL_SHARE_DEPTH_EXT 0x200C +#define WGL_SHARE_STENCIL_EXT 0x200D +#define WGL_SHARE_ACCUM_EXT 0x200E +#define WGL_SUPPORT_GDI_EXT 0x200F +#define WGL_SUPPORT_OPENGL_EXT 0x2010 +#define WGL_DOUBLE_BUFFER_EXT 0x2011 +#define WGL_STEREO_EXT 0x2012 +#define WGL_PIXEL_TYPE_EXT 0x2013 +#define WGL_COLOR_BITS_EXT 0x2014 +#define WGL_RED_BITS_EXT 0x2015 +#define WGL_RED_SHIFT_EXT 0x2016 +#define WGL_GREEN_BITS_EXT 0x2017 +#define WGL_GREEN_SHIFT_EXT 0x2018 +#define WGL_BLUE_BITS_EXT 0x2019 +#define WGL_BLUE_SHIFT_EXT 0x201A +#define WGL_ALPHA_BITS_EXT 0x201B +#define WGL_ALPHA_SHIFT_EXT 0x201C +#define WGL_ACCUM_BITS_EXT 0x201D +#define WGL_ACCUM_RED_BITS_EXT 0x201E +#define WGL_ACCUM_GREEN_BITS_EXT 0x201F +#define WGL_ACCUM_BLUE_BITS_EXT 0x2020 +#define WGL_ACCUM_ALPHA_BITS_EXT 0x2021 +#define WGL_DEPTH_BITS_EXT 0x2022 +#define WGL_STENCIL_BITS_EXT 0x2023 +#define WGL_AUX_BUFFERS_EXT 0x2024 +#define WGL_NO_ACCELERATION_EXT 0x2025 +#define WGL_GENERIC_ACCELERATION_EXT 0x2026 +#define WGL_FULL_ACCELERATION_EXT 0x2027 +#define WGL_SWAP_EXCHANGE_EXT 0x2028 +#define WGL_SWAP_COPY_EXT 0x2029 +#define WGL_SWAP_UNDEFINED_EXT 0x202A +#define WGL_TYPE_RGBA_EXT 0x202B +#define WGL_TYPE_COLORINDEX_EXT 0x202C + +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATEXTPROC) (HDC hdc, const int* piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int* piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int* piAttributes, int *piValues); + +#define wglChoosePixelFormatEXT WGLEW_GET_FUN(__wglewChoosePixelFormatEXT) +#define wglGetPixelFormatAttribfvEXT WGLEW_GET_FUN(__wglewGetPixelFormatAttribfvEXT) +#define wglGetPixelFormatAttribivEXT WGLEW_GET_FUN(__wglewGetPixelFormatAttribivEXT) + +#define WGLEW_EXT_pixel_format WGLEW_GET_VAR(__WGLEW_EXT_pixel_format) + +#endif /* WGL_EXT_pixel_format */ + +/* ------------------- WGL_EXT_pixel_format_packed_float ------------------- */ + +#ifndef WGL_EXT_pixel_format_packed_float +#define WGL_EXT_pixel_format_packed_float 1 + +#define WGL_TYPE_RGBA_UNSIGNED_FLOAT_EXT 0x20A8 + +#define WGLEW_EXT_pixel_format_packed_float WGLEW_GET_VAR(__WGLEW_EXT_pixel_format_packed_float) + +#endif /* WGL_EXT_pixel_format_packed_float */ + +/* -------------------------- WGL_EXT_swap_control ------------------------- */ + +#ifndef WGL_EXT_swap_control +#define WGL_EXT_swap_control 1 + +typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void); +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); + +#define wglGetSwapIntervalEXT WGLEW_GET_FUN(__wglewGetSwapIntervalEXT) +#define wglSwapIntervalEXT WGLEW_GET_FUN(__wglewSwapIntervalEXT) + +#define WGLEW_EXT_swap_control WGLEW_GET_VAR(__WGLEW_EXT_swap_control) + +#endif /* WGL_EXT_swap_control */ + +/* --------------------- WGL_I3D_digital_video_control --------------------- */ + +#ifndef WGL_I3D_digital_video_control +#define WGL_I3D_digital_video_control 1 + +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_FRAMEBUFFER_I3D 0x2050 +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_VALUE_I3D 0x2051 +#define WGL_DIGITAL_VIDEO_CURSOR_INCLUDED_I3D 0x2052 +#define WGL_DIGITAL_VIDEO_GAMMA_CORRECTED_I3D 0x2053 + +typedef BOOL (WINAPI * PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int* piValue); +typedef BOOL (WINAPI * PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int* piValue); + +#define wglGetDigitalVideoParametersI3D WGLEW_GET_FUN(__wglewGetDigitalVideoParametersI3D) +#define wglSetDigitalVideoParametersI3D WGLEW_GET_FUN(__wglewSetDigitalVideoParametersI3D) + +#define WGLEW_I3D_digital_video_control WGLEW_GET_VAR(__WGLEW_I3D_digital_video_control) + +#endif /* WGL_I3D_digital_video_control */ + +/* ----------------------------- WGL_I3D_gamma ----------------------------- */ + +#ifndef WGL_I3D_gamma +#define WGL_I3D_gamma 1 + +#define WGL_GAMMA_TABLE_SIZE_I3D 0x204E +#define WGL_GAMMA_EXCLUDE_DESKTOP_I3D 0x204F + +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, USHORT* puRed, USHORT *puGreen, USHORT *puBlue); +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int* piValue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, const USHORT* puRed, const USHORT *puGreen, const USHORT *puBlue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int* piValue); + +#define wglGetGammaTableI3D WGLEW_GET_FUN(__wglewGetGammaTableI3D) +#define wglGetGammaTableParametersI3D WGLEW_GET_FUN(__wglewGetGammaTableParametersI3D) +#define wglSetGammaTableI3D WGLEW_GET_FUN(__wglewSetGammaTableI3D) +#define wglSetGammaTableParametersI3D WGLEW_GET_FUN(__wglewSetGammaTableParametersI3D) + +#define WGLEW_I3D_gamma WGLEW_GET_VAR(__WGLEW_I3D_gamma) + +#endif /* WGL_I3D_gamma */ + +/* ---------------------------- WGL_I3D_genlock ---------------------------- */ + +#ifndef WGL_I3D_genlock +#define WGL_I3D_genlock 1 + +#define WGL_GENLOCK_SOURCE_MULTIVIEW_I3D 0x2044 +#define WGL_GENLOCK_SOURCE_EXTERNAL_SYNC_I3D 0x2045 +#define WGL_GENLOCK_SOURCE_EXTERNAL_FIELD_I3D 0x2046 +#define WGL_GENLOCK_SOURCE_EXTERNAL_TTL_I3D 0x2047 +#define WGL_GENLOCK_SOURCE_DIGITAL_SYNC_I3D 0x2048 +#define WGL_GENLOCK_SOURCE_DIGITAL_FIELD_I3D 0x2049 +#define WGL_GENLOCK_SOURCE_EDGE_FALLING_I3D 0x204A +#define WGL_GENLOCK_SOURCE_EDGE_RISING_I3D 0x204B +#define WGL_GENLOCK_SOURCE_EDGE_BOTH_I3D 0x204C + +typedef BOOL (WINAPI * PFNWGLDISABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLENABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT uRate); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT uDelay); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT uEdge); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEI3DPROC) (HDC hDC, UINT uSource); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT* uRate); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT* uDelay); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT* uEdge); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEI3DPROC) (HDC hDC, UINT* uSource); +typedef BOOL (WINAPI * PFNWGLISENABLEDGENLOCKI3DPROC) (HDC hDC, BOOL* pFlag); +typedef BOOL (WINAPI * PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC) (HDC hDC, UINT* uMaxLineDelay, UINT *uMaxPixelDelay); + +#define wglDisableGenlockI3D WGLEW_GET_FUN(__wglewDisableGenlockI3D) +#define wglEnableGenlockI3D WGLEW_GET_FUN(__wglewEnableGenlockI3D) +#define wglGenlockSampleRateI3D WGLEW_GET_FUN(__wglewGenlockSampleRateI3D) +#define wglGenlockSourceDelayI3D WGLEW_GET_FUN(__wglewGenlockSourceDelayI3D) +#define wglGenlockSourceEdgeI3D WGLEW_GET_FUN(__wglewGenlockSourceEdgeI3D) +#define wglGenlockSourceI3D WGLEW_GET_FUN(__wglewGenlockSourceI3D) +#define wglGetGenlockSampleRateI3D WGLEW_GET_FUN(__wglewGetGenlockSampleRateI3D) +#define wglGetGenlockSourceDelayI3D WGLEW_GET_FUN(__wglewGetGenlockSourceDelayI3D) +#define wglGetGenlockSourceEdgeI3D WGLEW_GET_FUN(__wglewGetGenlockSourceEdgeI3D) +#define wglGetGenlockSourceI3D WGLEW_GET_FUN(__wglewGetGenlockSourceI3D) +#define wglIsEnabledGenlockI3D WGLEW_GET_FUN(__wglewIsEnabledGenlockI3D) +#define wglQueryGenlockMaxSourceDelayI3D WGLEW_GET_FUN(__wglewQueryGenlockMaxSourceDelayI3D) + +#define WGLEW_I3D_genlock WGLEW_GET_VAR(__WGLEW_I3D_genlock) + +#endif /* WGL_I3D_genlock */ + +/* -------------------------- WGL_I3D_image_buffer ------------------------- */ + +#ifndef WGL_I3D_image_buffer +#define WGL_I3D_image_buffer 1 + +#define WGL_IMAGE_BUFFER_MIN_ACCESS_I3D 0x00000001 +#define WGL_IMAGE_BUFFER_LOCK_I3D 0x00000002 + +typedef BOOL (WINAPI * PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC) (HDC hdc, HANDLE* pEvent, LPVOID *pAddress, DWORD *pSize, UINT count); +typedef LPVOID (WINAPI * PFNWGLCREATEIMAGEBUFFERI3DPROC) (HDC hDC, DWORD dwSize, UINT uFlags); +typedef BOOL (WINAPI * PFNWGLDESTROYIMAGEBUFFERI3DPROC) (HDC hDC, LPVOID pAddress); +typedef BOOL (WINAPI * PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC) (HDC hdc, LPVOID* pAddress, UINT count); + +#define wglAssociateImageBufferEventsI3D WGLEW_GET_FUN(__wglewAssociateImageBufferEventsI3D) +#define wglCreateImageBufferI3D WGLEW_GET_FUN(__wglewCreateImageBufferI3D) +#define wglDestroyImageBufferI3D WGLEW_GET_FUN(__wglewDestroyImageBufferI3D) +#define wglReleaseImageBufferEventsI3D WGLEW_GET_FUN(__wglewReleaseImageBufferEventsI3D) + +#define WGLEW_I3D_image_buffer WGLEW_GET_VAR(__WGLEW_I3D_image_buffer) + +#endif /* WGL_I3D_image_buffer */ + +/* ------------------------ WGL_I3D_swap_frame_lock ------------------------ */ + +#ifndef WGL_I3D_swap_frame_lock +#define WGL_I3D_swap_frame_lock 1 + +typedef BOOL (WINAPI * PFNWGLDISABLEFRAMELOCKI3DPROC) (VOID); +typedef BOOL (WINAPI * PFNWGLENABLEFRAMELOCKI3DPROC) (VOID); +typedef BOOL (WINAPI * PFNWGLISENABLEDFRAMELOCKI3DPROC) (BOOL* pFlag); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMELOCKMASTERI3DPROC) (BOOL* pFlag); + +#define wglDisableFrameLockI3D WGLEW_GET_FUN(__wglewDisableFrameLockI3D) +#define wglEnableFrameLockI3D WGLEW_GET_FUN(__wglewEnableFrameLockI3D) +#define wglIsEnabledFrameLockI3D WGLEW_GET_FUN(__wglewIsEnabledFrameLockI3D) +#define wglQueryFrameLockMasterI3D WGLEW_GET_FUN(__wglewQueryFrameLockMasterI3D) + +#define WGLEW_I3D_swap_frame_lock WGLEW_GET_VAR(__WGLEW_I3D_swap_frame_lock) + +#endif /* WGL_I3D_swap_frame_lock */ + +/* ------------------------ WGL_I3D_swap_frame_usage ----------------------- */ + +#ifndef WGL_I3D_swap_frame_usage +#define WGL_I3D_swap_frame_usage 1 + +typedef BOOL (WINAPI * PFNWGLBEGINFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLENDFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLGETFRAMEUSAGEI3DPROC) (float* pUsage); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMETRACKINGI3DPROC) (DWORD* pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); + +#define wglBeginFrameTrackingI3D WGLEW_GET_FUN(__wglewBeginFrameTrackingI3D) +#define wglEndFrameTrackingI3D WGLEW_GET_FUN(__wglewEndFrameTrackingI3D) +#define wglGetFrameUsageI3D WGLEW_GET_FUN(__wglewGetFrameUsageI3D) +#define wglQueryFrameTrackingI3D WGLEW_GET_FUN(__wglewQueryFrameTrackingI3D) + +#define WGLEW_I3D_swap_frame_usage WGLEW_GET_VAR(__WGLEW_I3D_swap_frame_usage) + +#endif /* WGL_I3D_swap_frame_usage */ + +/* -------------------------- WGL_NV_float_buffer -------------------------- */ + +#ifndef WGL_NV_float_buffer +#define WGL_NV_float_buffer 1 + +#define WGL_FLOAT_COMPONENTS_NV 0x20B0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 +#define WGL_TEXTURE_FLOAT_R_NV 0x20B5 +#define WGL_TEXTURE_FLOAT_RG_NV 0x20B6 +#define WGL_TEXTURE_FLOAT_RGB_NV 0x20B7 +#define WGL_TEXTURE_FLOAT_RGBA_NV 0x20B8 + +#define WGLEW_NV_float_buffer WGLEW_GET_VAR(__WGLEW_NV_float_buffer) + +#endif /* WGL_NV_float_buffer */ + +/* -------------------------- WGL_NV_gpu_affinity -------------------------- */ + +#ifndef WGL_NV_gpu_affinity +#define WGL_NV_gpu_affinity 1 + +#define WGL_ERROR_INCOMPATIBLE_AFFINITY_MASKS_NV 0x20D0 +#define WGL_ERROR_MISSING_AFFINITY_MASK_NV 0x20D1 + +DECLARE_HANDLE(HGPUNV); +typedef struct _GPU_DEVICE { + DWORD cb; + CHAR DeviceName[32]; + CHAR DeviceString[128]; + DWORD Flags; + RECT rcVirtualScreen; +} GPU_DEVICE, *PGPU_DEVICE; + +typedef HDC (WINAPI * PFNWGLCREATEAFFINITYDCNVPROC) (const HGPUNV *phGpuList); +typedef BOOL (WINAPI * PFNWGLDELETEDCNVPROC) (HDC hdc); +typedef BOOL (WINAPI * PFNWGLENUMGPUDEVICESNVPROC) (HGPUNV hGpu, UINT iDeviceIndex, PGPU_DEVICE lpGpuDevice); +typedef BOOL (WINAPI * PFNWGLENUMGPUSFROMAFFINITYDCNVPROC) (HDC hAffinityDC, UINT iGpuIndex, HGPUNV *hGpu); +typedef BOOL (WINAPI * PFNWGLENUMGPUSNVPROC) (UINT iGpuIndex, HGPUNV *phGpu); + +#define wglCreateAffinityDCNV WGLEW_GET_FUN(__wglewCreateAffinityDCNV) +#define wglDeleteDCNV WGLEW_GET_FUN(__wglewDeleteDCNV) +#define wglEnumGpuDevicesNV WGLEW_GET_FUN(__wglewEnumGpuDevicesNV) +#define wglEnumGpusFromAffinityDCNV WGLEW_GET_FUN(__wglewEnumGpusFromAffinityDCNV) +#define wglEnumGpusNV WGLEW_GET_FUN(__wglewEnumGpusNV) + +#define WGLEW_NV_gpu_affinity WGLEW_GET_VAR(__WGLEW_NV_gpu_affinity) + +#endif /* WGL_NV_gpu_affinity */ + +/* -------------------------- WGL_NV_present_video ------------------------- */ + +#ifndef WGL_NV_present_video +#define WGL_NV_present_video 1 + +#define WGL_NUM_VIDEO_SLOTS_NV 0x20F0 + +DECLARE_HANDLE(HVIDEOOUTPUTDEVICENV); + +typedef BOOL (WINAPI * PFNWGLBINDVIDEODEVICENVPROC) (HDC hDc, unsigned int uVideoSlot, HVIDEOOUTPUTDEVICENV hVideoDevice, const int* piAttribList); +typedef int (WINAPI * PFNWGLENUMERATEVIDEODEVICESNVPROC) (HDC hDc, HVIDEOOUTPUTDEVICENV* phDeviceList); +typedef BOOL (WINAPI * PFNWGLQUERYCURRENTCONTEXTNVPROC) (int iAttribute, int* piValue); + +#define wglBindVideoDeviceNV WGLEW_GET_FUN(__wglewBindVideoDeviceNV) +#define wglEnumerateVideoDevicesNV WGLEW_GET_FUN(__wglewEnumerateVideoDevicesNV) +#define wglQueryCurrentContextNV WGLEW_GET_FUN(__wglewQueryCurrentContextNV) + +#define WGLEW_NV_present_video WGLEW_GET_VAR(__WGLEW_NV_present_video) + +#endif /* WGL_NV_present_video */ + +/* ---------------------- WGL_NV_render_depth_texture ---------------------- */ + +#ifndef WGL_NV_render_depth_texture +#define WGL_NV_render_depth_texture 1 + +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_BIND_TO_TEXTURE_DEPTH_NV 0x20A3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_DEPTH_NV 0x20A4 +#define WGL_DEPTH_TEXTURE_FORMAT_NV 0x20A5 +#define WGL_TEXTURE_DEPTH_COMPONENT_NV 0x20A6 +#define WGL_DEPTH_COMPONENT_NV 0x20A7 + +#define WGLEW_NV_render_depth_texture WGLEW_GET_VAR(__WGLEW_NV_render_depth_texture) + +#endif /* WGL_NV_render_depth_texture */ + +/* -------------------- WGL_NV_render_texture_rectangle -------------------- */ + +#ifndef WGL_NV_render_texture_rectangle +#define WGL_NV_render_texture_rectangle 1 + +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 + +#define WGLEW_NV_render_texture_rectangle WGLEW_GET_VAR(__WGLEW_NV_render_texture_rectangle) + +#endif /* WGL_NV_render_texture_rectangle */ + +/* --------------------------- WGL_NV_swap_group --------------------------- */ + +#ifndef WGL_NV_swap_group +#define WGL_NV_swap_group 1 + +typedef BOOL (WINAPI * PFNWGLBINDSWAPBARRIERNVPROC) (GLuint group, GLuint barrier); +typedef BOOL (WINAPI * PFNWGLJOINSWAPGROUPNVPROC) (HDC hDC, GLuint group); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMECOUNTNVPROC) (HDC hDC, GLuint* count); +typedef BOOL (WINAPI * PFNWGLQUERYMAXSWAPGROUPSNVPROC) (HDC hDC, GLuint* maxGroups, GLuint *maxBarriers); +typedef BOOL (WINAPI * PFNWGLQUERYSWAPGROUPNVPROC) (HDC hDC, GLuint* group); +typedef BOOL (WINAPI * PFNWGLRESETFRAMECOUNTNVPROC) (HDC hDC); + +#define wglBindSwapBarrierNV WGLEW_GET_FUN(__wglewBindSwapBarrierNV) +#define wglJoinSwapGroupNV WGLEW_GET_FUN(__wglewJoinSwapGroupNV) +#define wglQueryFrameCountNV WGLEW_GET_FUN(__wglewQueryFrameCountNV) +#define wglQueryMaxSwapGroupsNV WGLEW_GET_FUN(__wglewQueryMaxSwapGroupsNV) +#define wglQuerySwapGroupNV WGLEW_GET_FUN(__wglewQuerySwapGroupNV) +#define wglResetFrameCountNV WGLEW_GET_FUN(__wglewResetFrameCountNV) + +#define WGLEW_NV_swap_group WGLEW_GET_VAR(__WGLEW_NV_swap_group) + +#endif /* WGL_NV_swap_group */ + +/* ----------------------- WGL_NV_vertex_array_range ----------------------- */ + +#ifndef WGL_NV_vertex_array_range +#define WGL_NV_vertex_array_range 1 + +typedef void * (WINAPI * PFNWGLALLOCATEMEMORYNVPROC) (GLsizei size, GLfloat readFrequency, GLfloat writeFrequency, GLfloat priority); +typedef void (WINAPI * PFNWGLFREEMEMORYNVPROC) (void *pointer); + +#define wglAllocateMemoryNV WGLEW_GET_FUN(__wglewAllocateMemoryNV) +#define wglFreeMemoryNV WGLEW_GET_FUN(__wglewFreeMemoryNV) + +#define WGLEW_NV_vertex_array_range WGLEW_GET_VAR(__WGLEW_NV_vertex_array_range) + +#endif /* WGL_NV_vertex_array_range */ + +/* -------------------------- WGL_NV_video_output -------------------------- */ + +#ifndef WGL_NV_video_output +#define WGL_NV_video_output 1 + +#define WGL_BIND_TO_VIDEO_RGB_NV 0x20C0 +#define WGL_BIND_TO_VIDEO_RGBA_NV 0x20C1 +#define WGL_BIND_TO_VIDEO_RGB_AND_DEPTH_NV 0x20C2 +#define WGL_VIDEO_OUT_COLOR_NV 0x20C3 +#define WGL_VIDEO_OUT_ALPHA_NV 0x20C4 +#define WGL_VIDEO_OUT_DEPTH_NV 0x20C5 +#define WGL_VIDEO_OUT_COLOR_AND_ALPHA_NV 0x20C6 +#define WGL_VIDEO_OUT_COLOR_AND_DEPTH_NV 0x20C7 +#define WGL_VIDEO_OUT_FRAME 0x20C8 +#define WGL_VIDEO_OUT_FIELD_1 0x20C9 +#define WGL_VIDEO_OUT_FIELD_2 0x20CA +#define WGL_VIDEO_OUT_STACKED_FIELDS_1_2 0x20CB +#define WGL_VIDEO_OUT_STACKED_FIELDS_2_1 0x20CC + +DECLARE_HANDLE(HPVIDEODEV); + +typedef BOOL (WINAPI * PFNWGLBINDVIDEOIMAGENVPROC) (HPVIDEODEV hVideoDevice, HPBUFFERARB hPbuffer, int iVideoBuffer); +typedef BOOL (WINAPI * PFNWGLGETVIDEODEVICENVPROC) (HDC hDC, int numDevices, HPVIDEODEV* hVideoDevice); +typedef BOOL (WINAPI * PFNWGLGETVIDEOINFONVPROC) (HPVIDEODEV hpVideoDevice, unsigned long* pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEODEVICENVPROC) (HPVIDEODEV hVideoDevice); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEOIMAGENVPROC) (HPBUFFERARB hPbuffer, int iVideoBuffer); +typedef BOOL (WINAPI * PFNWGLSENDPBUFFERTOVIDEONVPROC) (HPBUFFERARB hPbuffer, int iBufferType, unsigned long* pulCounterPbuffer, BOOL bBlock); + +#define wglBindVideoImageNV WGLEW_GET_FUN(__wglewBindVideoImageNV) +#define wglGetVideoDeviceNV WGLEW_GET_FUN(__wglewGetVideoDeviceNV) +#define wglGetVideoInfoNV WGLEW_GET_FUN(__wglewGetVideoInfoNV) +#define wglReleaseVideoDeviceNV WGLEW_GET_FUN(__wglewReleaseVideoDeviceNV) +#define wglReleaseVideoImageNV WGLEW_GET_FUN(__wglewReleaseVideoImageNV) +#define wglSendPbufferToVideoNV WGLEW_GET_FUN(__wglewSendPbufferToVideoNV) + +#define WGLEW_NV_video_output WGLEW_GET_VAR(__WGLEW_NV_video_output) + +#endif /* WGL_NV_video_output */ + +/* -------------------------- WGL_OML_sync_control ------------------------- */ + +#ifndef WGL_OML_sync_control +#define WGL_OML_sync_control 1 + +typedef BOOL (WINAPI * PFNWGLGETMSCRATEOMLPROC) (HDC hdc, INT32* numerator, INT32 *denominator); +typedef BOOL (WINAPI * PFNWGLGETSYNCVALUESOMLPROC) (HDC hdc, INT64* ust, INT64 *msc, INT64 *sbc); +typedef INT64 (WINAPI * PFNWGLSWAPBUFFERSMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef INT64 (WINAPI * PFNWGLSWAPLAYERBUFFERSMSCOMLPROC) (HDC hdc, INT fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef BOOL (WINAPI * PFNWGLWAITFORMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64* ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLWAITFORSBCOMLPROC) (HDC hdc, INT64 target_sbc, INT64* ust, INT64 *msc, INT64 *sbc); + +#define wglGetMscRateOML WGLEW_GET_FUN(__wglewGetMscRateOML) +#define wglGetSyncValuesOML WGLEW_GET_FUN(__wglewGetSyncValuesOML) +#define wglSwapBuffersMscOML WGLEW_GET_FUN(__wglewSwapBuffersMscOML) +#define wglSwapLayerBuffersMscOML WGLEW_GET_FUN(__wglewSwapLayerBuffersMscOML) +#define wglWaitForMscOML WGLEW_GET_FUN(__wglewWaitForMscOML) +#define wglWaitForSbcOML WGLEW_GET_FUN(__wglewWaitForSbcOML) + +#define WGLEW_OML_sync_control WGLEW_GET_VAR(__WGLEW_OML_sync_control) + +#endif /* WGL_OML_sync_control */ + +/* ------------------------------------------------------------------------- */ + +#ifdef GLEW_MX +#define WGLEW_EXPORT +#else +#define WGLEW_EXPORT GLEWAPI +#endif /* GLEW_MX */ + +#ifdef GLEW_MX +struct WGLEWContextStruct +{ +#endif /* GLEW_MX */ + +WGLEW_EXPORT PFNWGLSETSTEREOEMITTERSTATE3DLPROC __wglewSetStereoEmitterState3DL; + +WGLEW_EXPORT PFNWGLCREATEBUFFERREGIONARBPROC __wglewCreateBufferRegionARB; +WGLEW_EXPORT PFNWGLDELETEBUFFERREGIONARBPROC __wglewDeleteBufferRegionARB; +WGLEW_EXPORT PFNWGLRESTOREBUFFERREGIONARBPROC __wglewRestoreBufferRegionARB; +WGLEW_EXPORT PFNWGLSAVEBUFFERREGIONARBPROC __wglewSaveBufferRegionARB; + +WGLEW_EXPORT PFNWGLCREATECONTEXTATTRIBSARBPROC __wglewCreateContextAttribsARB; + +WGLEW_EXPORT PFNWGLGETEXTENSIONSSTRINGARBPROC __wglewGetExtensionsStringARB; + +WGLEW_EXPORT PFNWGLGETCURRENTREADDCARBPROC __wglewGetCurrentReadDCARB; +WGLEW_EXPORT PFNWGLMAKECONTEXTCURRENTARBPROC __wglewMakeContextCurrentARB; + +WGLEW_EXPORT PFNWGLCREATEPBUFFERARBPROC __wglewCreatePbufferARB; +WGLEW_EXPORT PFNWGLDESTROYPBUFFERARBPROC __wglewDestroyPbufferARB; +WGLEW_EXPORT PFNWGLGETPBUFFERDCARBPROC __wglewGetPbufferDCARB; +WGLEW_EXPORT PFNWGLQUERYPBUFFERARBPROC __wglewQueryPbufferARB; +WGLEW_EXPORT PFNWGLRELEASEPBUFFERDCARBPROC __wglewReleasePbufferDCARB; + +WGLEW_EXPORT PFNWGLCHOOSEPIXELFORMATARBPROC __wglewChoosePixelFormatARB; +WGLEW_EXPORT PFNWGLGETPIXELFORMATATTRIBFVARBPROC __wglewGetPixelFormatAttribfvARB; +WGLEW_EXPORT PFNWGLGETPIXELFORMATATTRIBIVARBPROC __wglewGetPixelFormatAttribivARB; + +WGLEW_EXPORT PFNWGLBINDTEXIMAGEARBPROC __wglewBindTexImageARB; +WGLEW_EXPORT PFNWGLRELEASETEXIMAGEARBPROC __wglewReleaseTexImageARB; +WGLEW_EXPORT PFNWGLSETPBUFFERATTRIBARBPROC __wglewSetPbufferAttribARB; + +WGLEW_EXPORT PFNWGLBINDDISPLAYCOLORTABLEEXTPROC __wglewBindDisplayColorTableEXT; +WGLEW_EXPORT PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC __wglewCreateDisplayColorTableEXT; +WGLEW_EXPORT PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC __wglewDestroyDisplayColorTableEXT; +WGLEW_EXPORT PFNWGLLOADDISPLAYCOLORTABLEEXTPROC __wglewLoadDisplayColorTableEXT; + +WGLEW_EXPORT PFNWGLGETEXTENSIONSSTRINGEXTPROC __wglewGetExtensionsStringEXT; + +WGLEW_EXPORT PFNWGLGETCURRENTREADDCEXTPROC __wglewGetCurrentReadDCEXT; +WGLEW_EXPORT PFNWGLMAKECONTEXTCURRENTEXTPROC __wglewMakeContextCurrentEXT; + +WGLEW_EXPORT PFNWGLCREATEPBUFFEREXTPROC __wglewCreatePbufferEXT; +WGLEW_EXPORT PFNWGLDESTROYPBUFFEREXTPROC __wglewDestroyPbufferEXT; +WGLEW_EXPORT PFNWGLGETPBUFFERDCEXTPROC __wglewGetPbufferDCEXT; +WGLEW_EXPORT PFNWGLQUERYPBUFFEREXTPROC __wglewQueryPbufferEXT; +WGLEW_EXPORT PFNWGLRELEASEPBUFFERDCEXTPROC __wglewReleasePbufferDCEXT; + +WGLEW_EXPORT PFNWGLCHOOSEPIXELFORMATEXTPROC __wglewChoosePixelFormatEXT; +WGLEW_EXPORT PFNWGLGETPIXELFORMATATTRIBFVEXTPROC __wglewGetPixelFormatAttribfvEXT; +WGLEW_EXPORT PFNWGLGETPIXELFORMATATTRIBIVEXTPROC __wglewGetPixelFormatAttribivEXT; + +WGLEW_EXPORT PFNWGLGETSWAPINTERVALEXTPROC __wglewGetSwapIntervalEXT; +WGLEW_EXPORT PFNWGLSWAPINTERVALEXTPROC __wglewSwapIntervalEXT; + +WGLEW_EXPORT PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC __wglewGetDigitalVideoParametersI3D; +WGLEW_EXPORT PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC __wglewSetDigitalVideoParametersI3D; + +WGLEW_EXPORT PFNWGLGETGAMMATABLEI3DPROC __wglewGetGammaTableI3D; +WGLEW_EXPORT PFNWGLGETGAMMATABLEPARAMETERSI3DPROC __wglewGetGammaTableParametersI3D; +WGLEW_EXPORT PFNWGLSETGAMMATABLEI3DPROC __wglewSetGammaTableI3D; +WGLEW_EXPORT PFNWGLSETGAMMATABLEPARAMETERSI3DPROC __wglewSetGammaTableParametersI3D; + +WGLEW_EXPORT PFNWGLDISABLEGENLOCKI3DPROC __wglewDisableGenlockI3D; +WGLEW_EXPORT PFNWGLENABLEGENLOCKI3DPROC __wglewEnableGenlockI3D; +WGLEW_EXPORT PFNWGLGENLOCKSAMPLERATEI3DPROC __wglewGenlockSampleRateI3D; +WGLEW_EXPORT PFNWGLGENLOCKSOURCEDELAYI3DPROC __wglewGenlockSourceDelayI3D; +WGLEW_EXPORT PFNWGLGENLOCKSOURCEEDGEI3DPROC __wglewGenlockSourceEdgeI3D; +WGLEW_EXPORT PFNWGLGENLOCKSOURCEI3DPROC __wglewGenlockSourceI3D; +WGLEW_EXPORT PFNWGLGETGENLOCKSAMPLERATEI3DPROC __wglewGetGenlockSampleRateI3D; +WGLEW_EXPORT PFNWGLGETGENLOCKSOURCEDELAYI3DPROC __wglewGetGenlockSourceDelayI3D; +WGLEW_EXPORT PFNWGLGETGENLOCKSOURCEEDGEI3DPROC __wglewGetGenlockSourceEdgeI3D; +WGLEW_EXPORT PFNWGLGETGENLOCKSOURCEI3DPROC __wglewGetGenlockSourceI3D; +WGLEW_EXPORT PFNWGLISENABLEDGENLOCKI3DPROC __wglewIsEnabledGenlockI3D; +WGLEW_EXPORT PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC __wglewQueryGenlockMaxSourceDelayI3D; + +WGLEW_EXPORT PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC __wglewAssociateImageBufferEventsI3D; +WGLEW_EXPORT PFNWGLCREATEIMAGEBUFFERI3DPROC __wglewCreateImageBufferI3D; +WGLEW_EXPORT PFNWGLDESTROYIMAGEBUFFERI3DPROC __wglewDestroyImageBufferI3D; +WGLEW_EXPORT PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC __wglewReleaseImageBufferEventsI3D; + +WGLEW_EXPORT PFNWGLDISABLEFRAMELOCKI3DPROC __wglewDisableFrameLockI3D; +WGLEW_EXPORT PFNWGLENABLEFRAMELOCKI3DPROC __wglewEnableFrameLockI3D; +WGLEW_EXPORT PFNWGLISENABLEDFRAMELOCKI3DPROC __wglewIsEnabledFrameLockI3D; +WGLEW_EXPORT PFNWGLQUERYFRAMELOCKMASTERI3DPROC __wglewQueryFrameLockMasterI3D; + +WGLEW_EXPORT PFNWGLBEGINFRAMETRACKINGI3DPROC __wglewBeginFrameTrackingI3D; +WGLEW_EXPORT PFNWGLENDFRAMETRACKINGI3DPROC __wglewEndFrameTrackingI3D; +WGLEW_EXPORT PFNWGLGETFRAMEUSAGEI3DPROC __wglewGetFrameUsageI3D; +WGLEW_EXPORT PFNWGLQUERYFRAMETRACKINGI3DPROC __wglewQueryFrameTrackingI3D; + +WGLEW_EXPORT PFNWGLCREATEAFFINITYDCNVPROC __wglewCreateAffinityDCNV; +WGLEW_EXPORT PFNWGLDELETEDCNVPROC __wglewDeleteDCNV; +WGLEW_EXPORT PFNWGLENUMGPUDEVICESNVPROC __wglewEnumGpuDevicesNV; +WGLEW_EXPORT PFNWGLENUMGPUSFROMAFFINITYDCNVPROC __wglewEnumGpusFromAffinityDCNV; +WGLEW_EXPORT PFNWGLENUMGPUSNVPROC __wglewEnumGpusNV; + +WGLEW_EXPORT PFNWGLBINDVIDEODEVICENVPROC __wglewBindVideoDeviceNV; +WGLEW_EXPORT PFNWGLENUMERATEVIDEODEVICESNVPROC __wglewEnumerateVideoDevicesNV; +WGLEW_EXPORT PFNWGLQUERYCURRENTCONTEXTNVPROC __wglewQueryCurrentContextNV; + +WGLEW_EXPORT PFNWGLBINDSWAPBARRIERNVPROC __wglewBindSwapBarrierNV; +WGLEW_EXPORT PFNWGLJOINSWAPGROUPNVPROC __wglewJoinSwapGroupNV; +WGLEW_EXPORT PFNWGLQUERYFRAMECOUNTNVPROC __wglewQueryFrameCountNV; +WGLEW_EXPORT PFNWGLQUERYMAXSWAPGROUPSNVPROC __wglewQueryMaxSwapGroupsNV; +WGLEW_EXPORT PFNWGLQUERYSWAPGROUPNVPROC __wglewQuerySwapGroupNV; +WGLEW_EXPORT PFNWGLRESETFRAMECOUNTNVPROC __wglewResetFrameCountNV; + +WGLEW_EXPORT PFNWGLALLOCATEMEMORYNVPROC __wglewAllocateMemoryNV; +WGLEW_EXPORT PFNWGLFREEMEMORYNVPROC __wglewFreeMemoryNV; + +WGLEW_EXPORT PFNWGLBINDVIDEOIMAGENVPROC __wglewBindVideoImageNV; +WGLEW_EXPORT PFNWGLGETVIDEODEVICENVPROC __wglewGetVideoDeviceNV; +WGLEW_EXPORT PFNWGLGETVIDEOINFONVPROC __wglewGetVideoInfoNV; +WGLEW_EXPORT PFNWGLRELEASEVIDEODEVICENVPROC __wglewReleaseVideoDeviceNV; +WGLEW_EXPORT PFNWGLRELEASEVIDEOIMAGENVPROC __wglewReleaseVideoImageNV; +WGLEW_EXPORT PFNWGLSENDPBUFFERTOVIDEONVPROC __wglewSendPbufferToVideoNV; + +WGLEW_EXPORT PFNWGLGETMSCRATEOMLPROC __wglewGetMscRateOML; +WGLEW_EXPORT PFNWGLGETSYNCVALUESOMLPROC __wglewGetSyncValuesOML; +WGLEW_EXPORT PFNWGLSWAPBUFFERSMSCOMLPROC __wglewSwapBuffersMscOML; +WGLEW_EXPORT PFNWGLSWAPLAYERBUFFERSMSCOMLPROC __wglewSwapLayerBuffersMscOML; +WGLEW_EXPORT PFNWGLWAITFORMSCOMLPROC __wglewWaitForMscOML; +WGLEW_EXPORT PFNWGLWAITFORSBCOMLPROC __wglewWaitForSbcOML; +WGLEW_EXPORT GLboolean __WGLEW_3DFX_multisample; +WGLEW_EXPORT GLboolean __WGLEW_3DL_stereo_control; +WGLEW_EXPORT GLboolean __WGLEW_ARB_buffer_region; +WGLEW_EXPORT GLboolean __WGLEW_ARB_create_context; +WGLEW_EXPORT GLboolean __WGLEW_ARB_extensions_string; +WGLEW_EXPORT GLboolean __WGLEW_ARB_framebuffer_sRGB; +WGLEW_EXPORT GLboolean __WGLEW_ARB_make_current_read; +WGLEW_EXPORT GLboolean __WGLEW_ARB_multisample; +WGLEW_EXPORT GLboolean __WGLEW_ARB_pbuffer; +WGLEW_EXPORT GLboolean __WGLEW_ARB_pixel_format; +WGLEW_EXPORT GLboolean __WGLEW_ARB_pixel_format_float; +WGLEW_EXPORT GLboolean __WGLEW_ARB_render_texture; +WGLEW_EXPORT GLboolean __WGLEW_ATI_pixel_format_float; +WGLEW_EXPORT GLboolean __WGLEW_ATI_render_texture_rectangle; +WGLEW_EXPORT GLboolean __WGLEW_EXT_depth_float; +WGLEW_EXPORT GLboolean __WGLEW_EXT_display_color_table; +WGLEW_EXPORT GLboolean __WGLEW_EXT_extensions_string; +WGLEW_EXPORT GLboolean __WGLEW_EXT_framebuffer_sRGB; +WGLEW_EXPORT GLboolean __WGLEW_EXT_make_current_read; +WGLEW_EXPORT GLboolean __WGLEW_EXT_multisample; +WGLEW_EXPORT GLboolean __WGLEW_EXT_pbuffer; +WGLEW_EXPORT GLboolean __WGLEW_EXT_pixel_format; +WGLEW_EXPORT GLboolean __WGLEW_EXT_pixel_format_packed_float; +WGLEW_EXPORT GLboolean __WGLEW_EXT_swap_control; +WGLEW_EXPORT GLboolean __WGLEW_I3D_digital_video_control; +WGLEW_EXPORT GLboolean __WGLEW_I3D_gamma; +WGLEW_EXPORT GLboolean __WGLEW_I3D_genlock; +WGLEW_EXPORT GLboolean __WGLEW_I3D_image_buffer; +WGLEW_EXPORT GLboolean __WGLEW_I3D_swap_frame_lock; +WGLEW_EXPORT GLboolean __WGLEW_I3D_swap_frame_usage; +WGLEW_EXPORT GLboolean __WGLEW_NV_float_buffer; +WGLEW_EXPORT GLboolean __WGLEW_NV_gpu_affinity; +WGLEW_EXPORT GLboolean __WGLEW_NV_present_video; +WGLEW_EXPORT GLboolean __WGLEW_NV_render_depth_texture; +WGLEW_EXPORT GLboolean __WGLEW_NV_render_texture_rectangle; +WGLEW_EXPORT GLboolean __WGLEW_NV_swap_group; +WGLEW_EXPORT GLboolean __WGLEW_NV_vertex_array_range; +WGLEW_EXPORT GLboolean __WGLEW_NV_video_output; +WGLEW_EXPORT GLboolean __WGLEW_OML_sync_control; + +#ifdef GLEW_MX +}; /* WGLEWContextStruct */ +#endif /* GLEW_MX */ + +/* ------------------------------------------------------------------------- */ + +#ifdef GLEW_MX + +typedef struct WGLEWContextStruct WGLEWContext; +GLEWAPI GLenum wglewContextInit (WGLEWContext* ctx); +GLEWAPI GLboolean wglewContextIsSupported (WGLEWContext* ctx, const char* name); + +#define wglewInit() wglewContextInit(wglewGetContext()) +#define wglewIsSupported(x) wglewContextIsSupported(wglewGetContext(), x) + +#define WGLEW_GET_VAR(x) (*(const GLboolean*)&(wglewGetContext()->x)) +#define WGLEW_GET_FUN(x) wglewGetContext()->x + +#else /* GLEW_MX */ + +#define WGLEW_GET_VAR(x) (*(const GLboolean*)&x) +#define WGLEW_GET_FUN(x) x + +GLEWAPI GLboolean wglewIsSupported (const char* name); + +#endif /* GLEW_MX */ + +GLEWAPI GLboolean wglewGetExtension (const char* name); + +#ifdef __cplusplus +} +#endif + +#undef GLEWAPI + +#endif /* __wglew_h__ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/glew.c b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/glew.c new file mode 100644 index 0000000..7d4b8f8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/glew.c @@ -0,0 +1,12180 @@ +/* +** The OpenGL Extension Wrangler Library +** Copyright (C) 2002-2008, Milan Ikits +** Copyright (C) 2002-2008, Marcelo E. Magallon +** Copyright (C) 2002, Lev Povalahev +** 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. +** * The name of the author may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +** THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "../include/GL/glew.h" +#if defined(_WIN32) +# include "../include/GL/wglew.h" +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +# include "../include/GL/glxew.h" +#endif + +/* + * Define glewGetContext and related helper macros. + */ +#ifdef GLEW_MX +# define glewGetContext() ctx +# ifdef _WIN32 +# define GLEW_CONTEXT_ARG_DEF_INIT GLEWContext* ctx +# define GLEW_CONTEXT_ARG_VAR_INIT ctx +# define wglewGetContext() ctx +# define WGLEW_CONTEXT_ARG_DEF_INIT WGLEWContext* ctx +# define WGLEW_CONTEXT_ARG_DEF_LIST WGLEWContext* ctx +# else /* _WIN32 */ +# define GLEW_CONTEXT_ARG_DEF_INIT void +# define GLEW_CONTEXT_ARG_VAR_INIT +# define glxewGetContext() ctx +# define GLXEW_CONTEXT_ARG_DEF_INIT void +# define GLXEW_CONTEXT_ARG_DEF_LIST GLXEWContext* ctx +# endif /* _WIN32 */ +# define GLEW_CONTEXT_ARG_DEF_LIST GLEWContext* ctx +#else /* GLEW_MX */ +# define GLEW_CONTEXT_ARG_DEF_INIT void +# define GLEW_CONTEXT_ARG_VAR_INIT +# define GLEW_CONTEXT_ARG_DEF_LIST void +# define WGLEW_CONTEXT_ARG_DEF_INIT void +# define WGLEW_CONTEXT_ARG_DEF_LIST void +# define GLXEW_CONTEXT_ARG_DEF_INIT void +# define GLXEW_CONTEXT_ARG_DEF_LIST void +#endif /* GLEW_MX */ + +#if defined(__APPLE__) +#include +#include +#include + +void* NSGLGetProcAddress (const GLubyte *name) +{ + static const struct mach_header* image = NULL; + NSSymbol symbol; + char* symbolName; + if (NULL == image) + { + image = NSAddImage("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", NSADDIMAGE_OPTION_RETURN_ON_ERROR); + } + /* prepend a '_' for the Unix C symbol mangling convention */ + symbolName = malloc(strlen((const char*)name) + 2); + strcpy(symbolName+1, (const char*)name); + symbolName[0] = '_'; + symbol = NULL; + /* if (NSIsSymbolNameDefined(symbolName)) + symbol = NSLookupAndBindSymbol(symbolName); */ + symbol = image ? NSLookupSymbolInImage(image, symbolName, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND | NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR) : NULL; + free(symbolName); + return symbol ? NSAddressOfSymbol(symbol) : NULL; +} +#endif /* __APPLE__ */ + +#if defined(__sgi) || defined (__sun) +#include +#include +#include + +void* dlGetProcAddress (const GLubyte* name) +{ + static void* h = NULL; + static void* gpa; + + if (h == NULL) + { + if ((h = dlopen(NULL, RTLD_LAZY | RTLD_LOCAL)) == NULL) return NULL; + gpa = dlsym(h, "glXGetProcAddress"); + } + + if (gpa != NULL) + return ((void*(*)(const GLubyte*))gpa)(name); + else + return dlsym(h, (const char*)name); +} +#endif /* __sgi || __sun */ + +/* + * Define glewGetProcAddress. + */ +#if defined(_WIN32) +# define glewGetProcAddress(name) wglGetProcAddress((LPCSTR)name) +#else +# if defined(__APPLE__) +# define glewGetProcAddress(name) NSGLGetProcAddress(name) +# else +# if defined(__sgi) || defined(__sun) +# define glewGetProcAddress(name) dlGetProcAddress(name) +# else /* __linux */ +# define glewGetProcAddress(name) (*glXGetProcAddressARB)(name) +# endif +# endif +#endif + +/* + * Define GLboolean const cast. + */ +#define CONST_CAST(x) (*(GLboolean*)&x) + +/* + * GLEW, just like OpenGL or GLU, does not rely on the standard C library. + * These functions implement the functionality required in this file. + */ +static GLuint _glewStrLen (const GLubyte* s) +{ + GLuint i=0; + if (s == NULL) return 0; + while (s[i] != '\0') i++; + return i; +} + +static GLuint _glewStrCLen (const GLubyte* s, GLubyte c) +{ + GLuint i=0; + if (s == NULL) return 0; + while (s[i] != '\0' && s[i] != c) i++; + return (s[i] == '\0' || s[i] == c) ? i : 0; +} + +static GLboolean _glewStrSame (const GLubyte* a, const GLubyte* b, GLuint n) +{ + GLuint i=0; + if(a == NULL || b == NULL) + return (a == NULL && b == NULL && n == 0) ? GL_TRUE : GL_FALSE; + while (i < n && a[i] != '\0' && b[i] != '\0' && a[i] == b[i]) i++; + return i == n ? GL_TRUE : GL_FALSE; +} + +static GLboolean _glewStrSame1 (GLubyte** a, GLuint* na, const GLubyte* b, GLuint nb) +{ + while (*na > 0 && (**a == ' ' || **a == '\n' || **a == '\r' || **a == '\t')) + { + (*a)++; + (*na)--; + } + if(*na >= nb) + { + GLuint i=0; + while (i < nb && (*a)+i != NULL && b+i != NULL && (*a)[i] == b[i]) i++; + if(i == nb) + { + *a = *a + nb; + *na = *na - nb; + return GL_TRUE; + } + } + return GL_FALSE; +} + +static GLboolean _glewStrSame2 (GLubyte** a, GLuint* na, const GLubyte* b, GLuint nb) +{ + if(*na >= nb) + { + GLuint i=0; + while (i < nb && (*a)+i != NULL && b+i != NULL && (*a)[i] == b[i]) i++; + if(i == nb) + { + *a = *a + nb; + *na = *na - nb; + return GL_TRUE; + } + } + return GL_FALSE; +} + +static GLboolean _glewStrSame3 (GLubyte** a, GLuint* na, const GLubyte* b, GLuint nb) +{ + if(*na >= nb) + { + GLuint i=0; + while (i < nb && (*a)+i != NULL && b+i != NULL && (*a)[i] == b[i]) i++; + if (i == nb && (*na == nb || (*a)[i] == ' ' || (*a)[i] == '\n' || (*a)[i] == '\r' || (*a)[i] == '\t')) + { + *a = *a + nb; + *na = *na - nb; + return GL_TRUE; + } + } + return GL_FALSE; +} + +#if !defined(_WIN32) || !defined(GLEW_MX) + +PFNGLCOPYTEXSUBIMAGE3DPROC __glewCopyTexSubImage3D = NULL; +PFNGLDRAWRANGEELEMENTSPROC __glewDrawRangeElements = NULL; +PFNGLTEXIMAGE3DPROC __glewTexImage3D = NULL; +PFNGLTEXSUBIMAGE3DPROC __glewTexSubImage3D = NULL; + +PFNGLACTIVETEXTUREPROC __glewActiveTexture = NULL; +PFNGLCLIENTACTIVETEXTUREPROC __glewClientActiveTexture = NULL; +PFNGLCOMPRESSEDTEXIMAGE1DPROC __glewCompressedTexImage1D = NULL; +PFNGLCOMPRESSEDTEXIMAGE2DPROC __glewCompressedTexImage2D = NULL; +PFNGLCOMPRESSEDTEXIMAGE3DPROC __glewCompressedTexImage3D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC __glewCompressedTexSubImage1D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC __glewCompressedTexSubImage2D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC __glewCompressedTexSubImage3D = NULL; +PFNGLGETCOMPRESSEDTEXIMAGEPROC __glewGetCompressedTexImage = NULL; +PFNGLLOADTRANSPOSEMATRIXDPROC __glewLoadTransposeMatrixd = NULL; +PFNGLLOADTRANSPOSEMATRIXFPROC __glewLoadTransposeMatrixf = NULL; +PFNGLMULTTRANSPOSEMATRIXDPROC __glewMultTransposeMatrixd = NULL; +PFNGLMULTTRANSPOSEMATRIXFPROC __glewMultTransposeMatrixf = NULL; +PFNGLMULTITEXCOORD1DPROC __glewMultiTexCoord1d = NULL; +PFNGLMULTITEXCOORD1DVPROC __glewMultiTexCoord1dv = NULL; +PFNGLMULTITEXCOORD1FPROC __glewMultiTexCoord1f = NULL; +PFNGLMULTITEXCOORD1FVPROC __glewMultiTexCoord1fv = NULL; +PFNGLMULTITEXCOORD1IPROC __glewMultiTexCoord1i = NULL; +PFNGLMULTITEXCOORD1IVPROC __glewMultiTexCoord1iv = NULL; +PFNGLMULTITEXCOORD1SPROC __glewMultiTexCoord1s = NULL; +PFNGLMULTITEXCOORD1SVPROC __glewMultiTexCoord1sv = NULL; +PFNGLMULTITEXCOORD2DPROC __glewMultiTexCoord2d = NULL; +PFNGLMULTITEXCOORD2DVPROC __glewMultiTexCoord2dv = NULL; +PFNGLMULTITEXCOORD2FPROC __glewMultiTexCoord2f = NULL; +PFNGLMULTITEXCOORD2FVPROC __glewMultiTexCoord2fv = NULL; +PFNGLMULTITEXCOORD2IPROC __glewMultiTexCoord2i = NULL; +PFNGLMULTITEXCOORD2IVPROC __glewMultiTexCoord2iv = NULL; +PFNGLMULTITEXCOORD2SPROC __glewMultiTexCoord2s = NULL; +PFNGLMULTITEXCOORD2SVPROC __glewMultiTexCoord2sv = NULL; +PFNGLMULTITEXCOORD3DPROC __glewMultiTexCoord3d = NULL; +PFNGLMULTITEXCOORD3DVPROC __glewMultiTexCoord3dv = NULL; +PFNGLMULTITEXCOORD3FPROC __glewMultiTexCoord3f = NULL; +PFNGLMULTITEXCOORD3FVPROC __glewMultiTexCoord3fv = NULL; +PFNGLMULTITEXCOORD3IPROC __glewMultiTexCoord3i = NULL; +PFNGLMULTITEXCOORD3IVPROC __glewMultiTexCoord3iv = NULL; +PFNGLMULTITEXCOORD3SPROC __glewMultiTexCoord3s = NULL; +PFNGLMULTITEXCOORD3SVPROC __glewMultiTexCoord3sv = NULL; +PFNGLMULTITEXCOORD4DPROC __glewMultiTexCoord4d = NULL; +PFNGLMULTITEXCOORD4DVPROC __glewMultiTexCoord4dv = NULL; +PFNGLMULTITEXCOORD4FPROC __glewMultiTexCoord4f = NULL; +PFNGLMULTITEXCOORD4FVPROC __glewMultiTexCoord4fv = NULL; +PFNGLMULTITEXCOORD4IPROC __glewMultiTexCoord4i = NULL; +PFNGLMULTITEXCOORD4IVPROC __glewMultiTexCoord4iv = NULL; +PFNGLMULTITEXCOORD4SPROC __glewMultiTexCoord4s = NULL; +PFNGLMULTITEXCOORD4SVPROC __glewMultiTexCoord4sv = NULL; +PFNGLSAMPLECOVERAGEPROC __glewSampleCoverage = NULL; + +PFNGLBLENDCOLORPROC __glewBlendColor = NULL; +PFNGLBLENDEQUATIONPROC __glewBlendEquation = NULL; +PFNGLBLENDFUNCSEPARATEPROC __glewBlendFuncSeparate = NULL; +PFNGLFOGCOORDPOINTERPROC __glewFogCoordPointer = NULL; +PFNGLFOGCOORDDPROC __glewFogCoordd = NULL; +PFNGLFOGCOORDDVPROC __glewFogCoorddv = NULL; +PFNGLFOGCOORDFPROC __glewFogCoordf = NULL; +PFNGLFOGCOORDFVPROC __glewFogCoordfv = NULL; +PFNGLMULTIDRAWARRAYSPROC __glewMultiDrawArrays = NULL; +PFNGLMULTIDRAWELEMENTSPROC __glewMultiDrawElements = NULL; +PFNGLPOINTPARAMETERFPROC __glewPointParameterf = NULL; +PFNGLPOINTPARAMETERFVPROC __glewPointParameterfv = NULL; +PFNGLPOINTPARAMETERIPROC __glewPointParameteri = NULL; +PFNGLPOINTPARAMETERIVPROC __glewPointParameteriv = NULL; +PFNGLSECONDARYCOLOR3BPROC __glewSecondaryColor3b = NULL; +PFNGLSECONDARYCOLOR3BVPROC __glewSecondaryColor3bv = NULL; +PFNGLSECONDARYCOLOR3DPROC __glewSecondaryColor3d = NULL; +PFNGLSECONDARYCOLOR3DVPROC __glewSecondaryColor3dv = NULL; +PFNGLSECONDARYCOLOR3FPROC __glewSecondaryColor3f = NULL; +PFNGLSECONDARYCOLOR3FVPROC __glewSecondaryColor3fv = NULL; +PFNGLSECONDARYCOLOR3IPROC __glewSecondaryColor3i = NULL; +PFNGLSECONDARYCOLOR3IVPROC __glewSecondaryColor3iv = NULL; +PFNGLSECONDARYCOLOR3SPROC __glewSecondaryColor3s = NULL; +PFNGLSECONDARYCOLOR3SVPROC __glewSecondaryColor3sv = NULL; +PFNGLSECONDARYCOLOR3UBPROC __glewSecondaryColor3ub = NULL; +PFNGLSECONDARYCOLOR3UBVPROC __glewSecondaryColor3ubv = NULL; +PFNGLSECONDARYCOLOR3UIPROC __glewSecondaryColor3ui = NULL; +PFNGLSECONDARYCOLOR3UIVPROC __glewSecondaryColor3uiv = NULL; +PFNGLSECONDARYCOLOR3USPROC __glewSecondaryColor3us = NULL; +PFNGLSECONDARYCOLOR3USVPROC __glewSecondaryColor3usv = NULL; +PFNGLSECONDARYCOLORPOINTERPROC __glewSecondaryColorPointer = NULL; +PFNGLWINDOWPOS2DPROC __glewWindowPos2d = NULL; +PFNGLWINDOWPOS2DVPROC __glewWindowPos2dv = NULL; +PFNGLWINDOWPOS2FPROC __glewWindowPos2f = NULL; +PFNGLWINDOWPOS2FVPROC __glewWindowPos2fv = NULL; +PFNGLWINDOWPOS2IPROC __glewWindowPos2i = NULL; +PFNGLWINDOWPOS2IVPROC __glewWindowPos2iv = NULL; +PFNGLWINDOWPOS2SPROC __glewWindowPos2s = NULL; +PFNGLWINDOWPOS2SVPROC __glewWindowPos2sv = NULL; +PFNGLWINDOWPOS3DPROC __glewWindowPos3d = NULL; +PFNGLWINDOWPOS3DVPROC __glewWindowPos3dv = NULL; +PFNGLWINDOWPOS3FPROC __glewWindowPos3f = NULL; +PFNGLWINDOWPOS3FVPROC __glewWindowPos3fv = NULL; +PFNGLWINDOWPOS3IPROC __glewWindowPos3i = NULL; +PFNGLWINDOWPOS3IVPROC __glewWindowPos3iv = NULL; +PFNGLWINDOWPOS3SPROC __glewWindowPos3s = NULL; +PFNGLWINDOWPOS3SVPROC __glewWindowPos3sv = NULL; + +PFNGLBEGINQUERYPROC __glewBeginQuery = NULL; +PFNGLBINDBUFFERPROC __glewBindBuffer = NULL; +PFNGLBUFFERDATAPROC __glewBufferData = NULL; +PFNGLBUFFERSUBDATAPROC __glewBufferSubData = NULL; +PFNGLDELETEBUFFERSPROC __glewDeleteBuffers = NULL; +PFNGLDELETEQUERIESPROC __glewDeleteQueries = NULL; +PFNGLENDQUERYPROC __glewEndQuery = NULL; +PFNGLGENBUFFERSPROC __glewGenBuffers = NULL; +PFNGLGENQUERIESPROC __glewGenQueries = NULL; +PFNGLGETBUFFERPARAMETERIVPROC __glewGetBufferParameteriv = NULL; +PFNGLGETBUFFERPOINTERVPROC __glewGetBufferPointerv = NULL; +PFNGLGETBUFFERSUBDATAPROC __glewGetBufferSubData = NULL; +PFNGLGETQUERYOBJECTIVPROC __glewGetQueryObjectiv = NULL; +PFNGLGETQUERYOBJECTUIVPROC __glewGetQueryObjectuiv = NULL; +PFNGLGETQUERYIVPROC __glewGetQueryiv = NULL; +PFNGLISBUFFERPROC __glewIsBuffer = NULL; +PFNGLISQUERYPROC __glewIsQuery = NULL; +PFNGLMAPBUFFERPROC __glewMapBuffer = NULL; +PFNGLUNMAPBUFFERPROC __glewUnmapBuffer = NULL; + +PFNGLATTACHSHADERPROC __glewAttachShader = NULL; +PFNGLBINDATTRIBLOCATIONPROC __glewBindAttribLocation = NULL; +PFNGLBLENDEQUATIONSEPARATEPROC __glewBlendEquationSeparate = NULL; +PFNGLCOMPILESHADERPROC __glewCompileShader = NULL; +PFNGLCREATEPROGRAMPROC __glewCreateProgram = NULL; +PFNGLCREATESHADERPROC __glewCreateShader = NULL; +PFNGLDELETEPROGRAMPROC __glewDeleteProgram = NULL; +PFNGLDELETESHADERPROC __glewDeleteShader = NULL; +PFNGLDETACHSHADERPROC __glewDetachShader = NULL; +PFNGLDISABLEVERTEXATTRIBARRAYPROC __glewDisableVertexAttribArray = NULL; +PFNGLDRAWBUFFERSPROC __glewDrawBuffers = NULL; +PFNGLENABLEVERTEXATTRIBARRAYPROC __glewEnableVertexAttribArray = NULL; +PFNGLGETACTIVEATTRIBPROC __glewGetActiveAttrib = NULL; +PFNGLGETACTIVEUNIFORMPROC __glewGetActiveUniform = NULL; +PFNGLGETATTACHEDSHADERSPROC __glewGetAttachedShaders = NULL; +PFNGLGETATTRIBLOCATIONPROC __glewGetAttribLocation = NULL; +PFNGLGETPROGRAMINFOLOGPROC __glewGetProgramInfoLog = NULL; +PFNGLGETPROGRAMIVPROC __glewGetProgramiv = NULL; +PFNGLGETSHADERINFOLOGPROC __glewGetShaderInfoLog = NULL; +PFNGLGETSHADERSOURCEPROC __glewGetShaderSource = NULL; +PFNGLGETSHADERIVPROC __glewGetShaderiv = NULL; +PFNGLGETUNIFORMLOCATIONPROC __glewGetUniformLocation = NULL; +PFNGLGETUNIFORMFVPROC __glewGetUniformfv = NULL; +PFNGLGETUNIFORMIVPROC __glewGetUniformiv = NULL; +PFNGLGETVERTEXATTRIBPOINTERVPROC __glewGetVertexAttribPointerv = NULL; +PFNGLGETVERTEXATTRIBDVPROC __glewGetVertexAttribdv = NULL; +PFNGLGETVERTEXATTRIBFVPROC __glewGetVertexAttribfv = NULL; +PFNGLGETVERTEXATTRIBIVPROC __glewGetVertexAttribiv = NULL; +PFNGLISPROGRAMPROC __glewIsProgram = NULL; +PFNGLISSHADERPROC __glewIsShader = NULL; +PFNGLLINKPROGRAMPROC __glewLinkProgram = NULL; +PFNGLSHADERSOURCEPROC __glewShaderSource = NULL; +PFNGLSTENCILFUNCSEPARATEPROC __glewStencilFuncSeparate = NULL; +PFNGLSTENCILMASKSEPARATEPROC __glewStencilMaskSeparate = NULL; +PFNGLSTENCILOPSEPARATEPROC __glewStencilOpSeparate = NULL; +PFNGLUNIFORM1FPROC __glewUniform1f = NULL; +PFNGLUNIFORM1FVPROC __glewUniform1fv = NULL; +PFNGLUNIFORM1IPROC __glewUniform1i = NULL; +PFNGLUNIFORM1IVPROC __glewUniform1iv = NULL; +PFNGLUNIFORM2FPROC __glewUniform2f = NULL; +PFNGLUNIFORM2FVPROC __glewUniform2fv = NULL; +PFNGLUNIFORM2IPROC __glewUniform2i = NULL; +PFNGLUNIFORM2IVPROC __glewUniform2iv = NULL; +PFNGLUNIFORM3FPROC __glewUniform3f = NULL; +PFNGLUNIFORM3FVPROC __glewUniform3fv = NULL; +PFNGLUNIFORM3IPROC __glewUniform3i = NULL; +PFNGLUNIFORM3IVPROC __glewUniform3iv = NULL; +PFNGLUNIFORM4FPROC __glewUniform4f = NULL; +PFNGLUNIFORM4FVPROC __glewUniform4fv = NULL; +PFNGLUNIFORM4IPROC __glewUniform4i = NULL; +PFNGLUNIFORM4IVPROC __glewUniform4iv = NULL; +PFNGLUNIFORMMATRIX2FVPROC __glewUniformMatrix2fv = NULL; +PFNGLUNIFORMMATRIX3FVPROC __glewUniformMatrix3fv = NULL; +PFNGLUNIFORMMATRIX4FVPROC __glewUniformMatrix4fv = NULL; +PFNGLUSEPROGRAMPROC __glewUseProgram = NULL; +PFNGLVALIDATEPROGRAMPROC __glewValidateProgram = NULL; +PFNGLVERTEXATTRIB1DPROC __glewVertexAttrib1d = NULL; +PFNGLVERTEXATTRIB1DVPROC __glewVertexAttrib1dv = NULL; +PFNGLVERTEXATTRIB1FPROC __glewVertexAttrib1f = NULL; +PFNGLVERTEXATTRIB1FVPROC __glewVertexAttrib1fv = NULL; +PFNGLVERTEXATTRIB1SPROC __glewVertexAttrib1s = NULL; +PFNGLVERTEXATTRIB1SVPROC __glewVertexAttrib1sv = NULL; +PFNGLVERTEXATTRIB2DPROC __glewVertexAttrib2d = NULL; +PFNGLVERTEXATTRIB2DVPROC __glewVertexAttrib2dv = NULL; +PFNGLVERTEXATTRIB2FPROC __glewVertexAttrib2f = NULL; +PFNGLVERTEXATTRIB2FVPROC __glewVertexAttrib2fv = NULL; +PFNGLVERTEXATTRIB2SPROC __glewVertexAttrib2s = NULL; +PFNGLVERTEXATTRIB2SVPROC __glewVertexAttrib2sv = NULL; +PFNGLVERTEXATTRIB3DPROC __glewVertexAttrib3d = NULL; +PFNGLVERTEXATTRIB3DVPROC __glewVertexAttrib3dv = NULL; +PFNGLVERTEXATTRIB3FPROC __glewVertexAttrib3f = NULL; +PFNGLVERTEXATTRIB3FVPROC __glewVertexAttrib3fv = NULL; +PFNGLVERTEXATTRIB3SPROC __glewVertexAttrib3s = NULL; +PFNGLVERTEXATTRIB3SVPROC __glewVertexAttrib3sv = NULL; +PFNGLVERTEXATTRIB4NBVPROC __glewVertexAttrib4Nbv = NULL; +PFNGLVERTEXATTRIB4NIVPROC __glewVertexAttrib4Niv = NULL; +PFNGLVERTEXATTRIB4NSVPROC __glewVertexAttrib4Nsv = NULL; +PFNGLVERTEXATTRIB4NUBPROC __glewVertexAttrib4Nub = NULL; +PFNGLVERTEXATTRIB4NUBVPROC __glewVertexAttrib4Nubv = NULL; +PFNGLVERTEXATTRIB4NUIVPROC __glewVertexAttrib4Nuiv = NULL; +PFNGLVERTEXATTRIB4NUSVPROC __glewVertexAttrib4Nusv = NULL; +PFNGLVERTEXATTRIB4BVPROC __glewVertexAttrib4bv = NULL; +PFNGLVERTEXATTRIB4DPROC __glewVertexAttrib4d = NULL; +PFNGLVERTEXATTRIB4DVPROC __glewVertexAttrib4dv = NULL; +PFNGLVERTEXATTRIB4FPROC __glewVertexAttrib4f = NULL; +PFNGLVERTEXATTRIB4FVPROC __glewVertexAttrib4fv = NULL; +PFNGLVERTEXATTRIB4IVPROC __glewVertexAttrib4iv = NULL; +PFNGLVERTEXATTRIB4SPROC __glewVertexAttrib4s = NULL; +PFNGLVERTEXATTRIB4SVPROC __glewVertexAttrib4sv = NULL; +PFNGLVERTEXATTRIB4UBVPROC __glewVertexAttrib4ubv = NULL; +PFNGLVERTEXATTRIB4UIVPROC __glewVertexAttrib4uiv = NULL; +PFNGLVERTEXATTRIB4USVPROC __glewVertexAttrib4usv = NULL; +PFNGLVERTEXATTRIBPOINTERPROC __glewVertexAttribPointer = NULL; + +PFNGLUNIFORMMATRIX2X3FVPROC __glewUniformMatrix2x3fv = NULL; +PFNGLUNIFORMMATRIX2X4FVPROC __glewUniformMatrix2x4fv = NULL; +PFNGLUNIFORMMATRIX3X2FVPROC __glewUniformMatrix3x2fv = NULL; +PFNGLUNIFORMMATRIX3X4FVPROC __glewUniformMatrix3x4fv = NULL; +PFNGLUNIFORMMATRIX4X2FVPROC __glewUniformMatrix4x2fv = NULL; +PFNGLUNIFORMMATRIX4X3FVPROC __glewUniformMatrix4x3fv = NULL; + +PFNGLBEGINCONDITIONALRENDERPROC __glewBeginConditionalRender = NULL; +PFNGLBEGINTRANSFORMFEEDBACKPROC __glewBeginTransformFeedback = NULL; +PFNGLBINDBUFFERBASEPROC __glewBindBufferBase = NULL; +PFNGLBINDBUFFERRANGEPROC __glewBindBufferRange = NULL; +PFNGLBINDFRAGDATALOCATIONPROC __glewBindFragDataLocation = NULL; +PFNGLCLAMPCOLORPROC __glewClampColor = NULL; +PFNGLCLEARBUFFERFIPROC __glewClearBufferfi = NULL; +PFNGLCLEARBUFFERFVPROC __glewClearBufferfv = NULL; +PFNGLCLEARBUFFERIVPROC __glewClearBufferiv = NULL; +PFNGLCLEARBUFFERUIVPROC __glewClearBufferuiv = NULL; +PFNGLCOLORMASKIPROC __glewColorMaski = NULL; +PFNGLDISABLEIPROC __glewDisablei = NULL; +PFNGLENABLEIPROC __glewEnablei = NULL; +PFNGLENDCONDITIONALRENDERPROC __glewEndConditionalRender = NULL; +PFNGLENDTRANSFORMFEEDBACKPROC __glewEndTransformFeedback = NULL; +PFNGLGETBOOLEANI_VPROC __glewGetBooleani_v = NULL; +PFNGLGETFRAGDATALOCATIONPROC __glewGetFragDataLocation = NULL; +PFNGLGETINTEGERI_VPROC __glewGetIntegeri_v = NULL; +PFNGLGETSTRINGIPROC __glewGetStringi = NULL; +PFNGLGETTEXPARAMETERIIVPROC __glewGetTexParameterIiv = NULL; +PFNGLGETTEXPARAMETERIUIVPROC __glewGetTexParameterIuiv = NULL; +PFNGLGETTRANSFORMFEEDBACKVARYINGPROC __glewGetTransformFeedbackVarying = NULL; +PFNGLGETUNIFORMUIVPROC __glewGetUniformuiv = NULL; +PFNGLGETVERTEXATTRIBIIVPROC __glewGetVertexAttribIiv = NULL; +PFNGLGETVERTEXATTRIBIUIVPROC __glewGetVertexAttribIuiv = NULL; +PFNGLISENABLEDIPROC __glewIsEnabledi = NULL; +PFNGLTEXPARAMETERIIVPROC __glewTexParameterIiv = NULL; +PFNGLTEXPARAMETERIUIVPROC __glewTexParameterIuiv = NULL; +PFNGLTRANSFORMFEEDBACKVARYINGSPROC __glewTransformFeedbackVaryings = NULL; +PFNGLUNIFORM1UIPROC __glewUniform1ui = NULL; +PFNGLUNIFORM1UIVPROC __glewUniform1uiv = NULL; +PFNGLUNIFORM2UIPROC __glewUniform2ui = NULL; +PFNGLUNIFORM2UIVPROC __glewUniform2uiv = NULL; +PFNGLUNIFORM3UIPROC __glewUniform3ui = NULL; +PFNGLUNIFORM3UIVPROC __glewUniform3uiv = NULL; +PFNGLUNIFORM4UIPROC __glewUniform4ui = NULL; +PFNGLUNIFORM4UIVPROC __glewUniform4uiv = NULL; +PFNGLVERTEXATTRIBI1IPROC __glewVertexAttribI1i = NULL; +PFNGLVERTEXATTRIBI1IVPROC __glewVertexAttribI1iv = NULL; +PFNGLVERTEXATTRIBI1UIPROC __glewVertexAttribI1ui = NULL; +PFNGLVERTEXATTRIBI1UIVPROC __glewVertexAttribI1uiv = NULL; +PFNGLVERTEXATTRIBI2IPROC __glewVertexAttribI2i = NULL; +PFNGLVERTEXATTRIBI2IVPROC __glewVertexAttribI2iv = NULL; +PFNGLVERTEXATTRIBI2UIPROC __glewVertexAttribI2ui = NULL; +PFNGLVERTEXATTRIBI2UIVPROC __glewVertexAttribI2uiv = NULL; +PFNGLVERTEXATTRIBI3IPROC __glewVertexAttribI3i = NULL; +PFNGLVERTEXATTRIBI3IVPROC __glewVertexAttribI3iv = NULL; +PFNGLVERTEXATTRIBI3UIPROC __glewVertexAttribI3ui = NULL; +PFNGLVERTEXATTRIBI3UIVPROC __glewVertexAttribI3uiv = NULL; +PFNGLVERTEXATTRIBI4BVPROC __glewVertexAttribI4bv = NULL; +PFNGLVERTEXATTRIBI4IPROC __glewVertexAttribI4i = NULL; +PFNGLVERTEXATTRIBI4IVPROC __glewVertexAttribI4iv = NULL; +PFNGLVERTEXATTRIBI4SVPROC __glewVertexAttribI4sv = NULL; +PFNGLVERTEXATTRIBI4UBVPROC __glewVertexAttribI4ubv = NULL; +PFNGLVERTEXATTRIBI4UIPROC __glewVertexAttribI4ui = NULL; +PFNGLVERTEXATTRIBI4UIVPROC __glewVertexAttribI4uiv = NULL; +PFNGLVERTEXATTRIBI4USVPROC __glewVertexAttribI4usv = NULL; +PFNGLVERTEXATTRIBIPOINTERPROC __glewVertexAttribIPointer = NULL; + +PFNGLTBUFFERMASK3DFXPROC __glewTbufferMask3DFX = NULL; + +PFNGLDRAWELEMENTARRAYAPPLEPROC __glewDrawElementArrayAPPLE = NULL; +PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC __glewDrawRangeElementArrayAPPLE = NULL; +PFNGLELEMENTPOINTERAPPLEPROC __glewElementPointerAPPLE = NULL; +PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC __glewMultiDrawElementArrayAPPLE = NULL; +PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC __glewMultiDrawRangeElementArrayAPPLE = NULL; + +PFNGLDELETEFENCESAPPLEPROC __glewDeleteFencesAPPLE = NULL; +PFNGLFINISHFENCEAPPLEPROC __glewFinishFenceAPPLE = NULL; +PFNGLFINISHOBJECTAPPLEPROC __glewFinishObjectAPPLE = NULL; +PFNGLGENFENCESAPPLEPROC __glewGenFencesAPPLE = NULL; +PFNGLISFENCEAPPLEPROC __glewIsFenceAPPLE = NULL; +PFNGLSETFENCEAPPLEPROC __glewSetFenceAPPLE = NULL; +PFNGLTESTFENCEAPPLEPROC __glewTestFenceAPPLE = NULL; +PFNGLTESTOBJECTAPPLEPROC __glewTestObjectAPPLE = NULL; + +PFNGLBUFFERPARAMETERIAPPLEPROC __glewBufferParameteriAPPLE = NULL; +PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC __glewFlushMappedBufferRangeAPPLE = NULL; + +PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC __glewGetTexParameterPointervAPPLE = NULL; +PFNGLTEXTURERANGEAPPLEPROC __glewTextureRangeAPPLE = NULL; + +PFNGLBINDVERTEXARRAYAPPLEPROC __glewBindVertexArrayAPPLE = NULL; +PFNGLDELETEVERTEXARRAYSAPPLEPROC __glewDeleteVertexArraysAPPLE = NULL; +PFNGLGENVERTEXARRAYSAPPLEPROC __glewGenVertexArraysAPPLE = NULL; +PFNGLISVERTEXARRAYAPPLEPROC __glewIsVertexArrayAPPLE = NULL; + +PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC __glewFlushVertexArrayRangeAPPLE = NULL; +PFNGLVERTEXARRAYPARAMETERIAPPLEPROC __glewVertexArrayParameteriAPPLE = NULL; +PFNGLVERTEXARRAYRANGEAPPLEPROC __glewVertexArrayRangeAPPLE = NULL; + +PFNGLCLAMPCOLORARBPROC __glewClampColorARB = NULL; + +PFNGLDRAWBUFFERSARBPROC __glewDrawBuffersARB = NULL; + +PFNGLDRAWARRAYSINSTANCEDARBPROC __glewDrawArraysInstancedARB = NULL; +PFNGLDRAWELEMENTSINSTANCEDARBPROC __glewDrawElementsInstancedARB = NULL; + +PFNGLBINDFRAMEBUFFERPROC __glewBindFramebuffer = NULL; +PFNGLBINDRENDERBUFFERPROC __glewBindRenderbuffer = NULL; +PFNGLBLITFRAMEBUFFERPROC __glewBlitFramebuffer = NULL; +PFNGLCHECKFRAMEBUFFERSTATUSPROC __glewCheckFramebufferStatus = NULL; +PFNGLDELETEFRAMEBUFFERSPROC __glewDeleteFramebuffers = NULL; +PFNGLDELETERENDERBUFFERSPROC __glewDeleteRenderbuffers = NULL; +PFNGLFRAMEBUFFERRENDERBUFFERPROC __glewFramebufferRenderbuffer = NULL; +PFNGLFRAMEBUFFERTEXTURLAYERPROC __glewFramebufferTexturLayer = NULL; +PFNGLFRAMEBUFFERTEXTURE1DPROC __glewFramebufferTexture1D = NULL; +PFNGLFRAMEBUFFERTEXTURE2DPROC __glewFramebufferTexture2D = NULL; +PFNGLFRAMEBUFFERTEXTURE3DPROC __glewFramebufferTexture3D = NULL; +PFNGLGENFRAMEBUFFERSPROC __glewGenFramebuffers = NULL; +PFNGLGENRENDERBUFFERSPROC __glewGenRenderbuffers = NULL; +PFNGLGENERATEMIPMAPPROC __glewGenerateMipmap = NULL; +PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC __glewGetFramebufferAttachmentParameteriv = NULL; +PFNGLGETRENDERBUFFERPARAMETERIVPROC __glewGetRenderbufferParameteriv = NULL; +PFNGLISFRAMEBUFFERPROC __glewIsFramebuffer = NULL; +PFNGLISRENDERBUFFERPROC __glewIsRenderbuffer = NULL; +PFNGLRENDERBUFFERSTORAGEPROC __glewRenderbufferStorage = NULL; +PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC __glewRenderbufferStorageMultisample = NULL; + +PFNGLFRAMEBUFFERTEXTUREARBPROC __glewFramebufferTextureARB = NULL; +PFNGLFRAMEBUFFERTEXTUREFACEARBPROC __glewFramebufferTextureFaceARB = NULL; +PFNGLFRAMEBUFFERTEXTURELAYERARBPROC __glewFramebufferTextureLayerARB = NULL; +PFNGLPROGRAMPARAMETERIARBPROC __glewProgramParameteriARB = NULL; + +PFNGLCOLORSUBTABLEPROC __glewColorSubTable = NULL; +PFNGLCOLORTABLEPROC __glewColorTable = NULL; +PFNGLCOLORTABLEPARAMETERFVPROC __glewColorTableParameterfv = NULL; +PFNGLCOLORTABLEPARAMETERIVPROC __glewColorTableParameteriv = NULL; +PFNGLCONVOLUTIONFILTER1DPROC __glewConvolutionFilter1D = NULL; +PFNGLCONVOLUTIONFILTER2DPROC __glewConvolutionFilter2D = NULL; +PFNGLCONVOLUTIONPARAMETERFPROC __glewConvolutionParameterf = NULL; +PFNGLCONVOLUTIONPARAMETERFVPROC __glewConvolutionParameterfv = NULL; +PFNGLCONVOLUTIONPARAMETERIPROC __glewConvolutionParameteri = NULL; +PFNGLCONVOLUTIONPARAMETERIVPROC __glewConvolutionParameteriv = NULL; +PFNGLCOPYCOLORSUBTABLEPROC __glewCopyColorSubTable = NULL; +PFNGLCOPYCOLORTABLEPROC __glewCopyColorTable = NULL; +PFNGLCOPYCONVOLUTIONFILTER1DPROC __glewCopyConvolutionFilter1D = NULL; +PFNGLCOPYCONVOLUTIONFILTER2DPROC __glewCopyConvolutionFilter2D = NULL; +PFNGLGETCOLORTABLEPROC __glewGetColorTable = NULL; +PFNGLGETCOLORTABLEPARAMETERFVPROC __glewGetColorTableParameterfv = NULL; +PFNGLGETCOLORTABLEPARAMETERIVPROC __glewGetColorTableParameteriv = NULL; +PFNGLGETCONVOLUTIONFILTERPROC __glewGetConvolutionFilter = NULL; +PFNGLGETCONVOLUTIONPARAMETERFVPROC __glewGetConvolutionParameterfv = NULL; +PFNGLGETCONVOLUTIONPARAMETERIVPROC __glewGetConvolutionParameteriv = NULL; +PFNGLGETHISTOGRAMPROC __glewGetHistogram = NULL; +PFNGLGETHISTOGRAMPARAMETERFVPROC __glewGetHistogramParameterfv = NULL; +PFNGLGETHISTOGRAMPARAMETERIVPROC __glewGetHistogramParameteriv = NULL; +PFNGLGETMINMAXPROC __glewGetMinmax = NULL; +PFNGLGETMINMAXPARAMETERFVPROC __glewGetMinmaxParameterfv = NULL; +PFNGLGETMINMAXPARAMETERIVPROC __glewGetMinmaxParameteriv = NULL; +PFNGLGETSEPARABLEFILTERPROC __glewGetSeparableFilter = NULL; +PFNGLHISTOGRAMPROC __glewHistogram = NULL; +PFNGLMINMAXPROC __glewMinmax = NULL; +PFNGLRESETHISTOGRAMPROC __glewResetHistogram = NULL; +PFNGLRESETMINMAXPROC __glewResetMinmax = NULL; +PFNGLSEPARABLEFILTER2DPROC __glewSeparableFilter2D = NULL; + +PFNGLVERTEXATTRIBDIVISORARBPROC __glewVertexAttribDivisorARB = NULL; + +PFNGLFLUSHMAPPEDBUFFERRANGEPROC __glewFlushMappedBufferRange = NULL; +PFNGLMAPBUFFERRANGEPROC __glewMapBufferRange = NULL; + +PFNGLCURRENTPALETTEMATRIXARBPROC __glewCurrentPaletteMatrixARB = NULL; +PFNGLMATRIXINDEXPOINTERARBPROC __glewMatrixIndexPointerARB = NULL; +PFNGLMATRIXINDEXUBVARBPROC __glewMatrixIndexubvARB = NULL; +PFNGLMATRIXINDEXUIVARBPROC __glewMatrixIndexuivARB = NULL; +PFNGLMATRIXINDEXUSVARBPROC __glewMatrixIndexusvARB = NULL; + +PFNGLSAMPLECOVERAGEARBPROC __glewSampleCoverageARB = NULL; + +PFNGLACTIVETEXTUREARBPROC __glewActiveTextureARB = NULL; +PFNGLCLIENTACTIVETEXTUREARBPROC __glewClientActiveTextureARB = NULL; +PFNGLMULTITEXCOORD1DARBPROC __glewMultiTexCoord1dARB = NULL; +PFNGLMULTITEXCOORD1DVARBPROC __glewMultiTexCoord1dvARB = NULL; +PFNGLMULTITEXCOORD1FARBPROC __glewMultiTexCoord1fARB = NULL; +PFNGLMULTITEXCOORD1FVARBPROC __glewMultiTexCoord1fvARB = NULL; +PFNGLMULTITEXCOORD1IARBPROC __glewMultiTexCoord1iARB = NULL; +PFNGLMULTITEXCOORD1IVARBPROC __glewMultiTexCoord1ivARB = NULL; +PFNGLMULTITEXCOORD1SARBPROC __glewMultiTexCoord1sARB = NULL; +PFNGLMULTITEXCOORD1SVARBPROC __glewMultiTexCoord1svARB = NULL; +PFNGLMULTITEXCOORD2DARBPROC __glewMultiTexCoord2dARB = NULL; +PFNGLMULTITEXCOORD2DVARBPROC __glewMultiTexCoord2dvARB = NULL; +PFNGLMULTITEXCOORD2FARBPROC __glewMultiTexCoord2fARB = NULL; +PFNGLMULTITEXCOORD2FVARBPROC __glewMultiTexCoord2fvARB = NULL; +PFNGLMULTITEXCOORD2IARBPROC __glewMultiTexCoord2iARB = NULL; +PFNGLMULTITEXCOORD2IVARBPROC __glewMultiTexCoord2ivARB = NULL; +PFNGLMULTITEXCOORD2SARBPROC __glewMultiTexCoord2sARB = NULL; +PFNGLMULTITEXCOORD2SVARBPROC __glewMultiTexCoord2svARB = NULL; +PFNGLMULTITEXCOORD3DARBPROC __glewMultiTexCoord3dARB = NULL; +PFNGLMULTITEXCOORD3DVARBPROC __glewMultiTexCoord3dvARB = NULL; +PFNGLMULTITEXCOORD3FARBPROC __glewMultiTexCoord3fARB = NULL; +PFNGLMULTITEXCOORD3FVARBPROC __glewMultiTexCoord3fvARB = NULL; +PFNGLMULTITEXCOORD3IARBPROC __glewMultiTexCoord3iARB = NULL; +PFNGLMULTITEXCOORD3IVARBPROC __glewMultiTexCoord3ivARB = NULL; +PFNGLMULTITEXCOORD3SARBPROC __glewMultiTexCoord3sARB = NULL; +PFNGLMULTITEXCOORD3SVARBPROC __glewMultiTexCoord3svARB = NULL; +PFNGLMULTITEXCOORD4DARBPROC __glewMultiTexCoord4dARB = NULL; +PFNGLMULTITEXCOORD4DVARBPROC __glewMultiTexCoord4dvARB = NULL; +PFNGLMULTITEXCOORD4FARBPROC __glewMultiTexCoord4fARB = NULL; +PFNGLMULTITEXCOORD4FVARBPROC __glewMultiTexCoord4fvARB = NULL; +PFNGLMULTITEXCOORD4IARBPROC __glewMultiTexCoord4iARB = NULL; +PFNGLMULTITEXCOORD4IVARBPROC __glewMultiTexCoord4ivARB = NULL; +PFNGLMULTITEXCOORD4SARBPROC __glewMultiTexCoord4sARB = NULL; +PFNGLMULTITEXCOORD4SVARBPROC __glewMultiTexCoord4svARB = NULL; + +PFNGLBEGINQUERYARBPROC __glewBeginQueryARB = NULL; +PFNGLDELETEQUERIESARBPROC __glewDeleteQueriesARB = NULL; +PFNGLENDQUERYARBPROC __glewEndQueryARB = NULL; +PFNGLGENQUERIESARBPROC __glewGenQueriesARB = NULL; +PFNGLGETQUERYOBJECTIVARBPROC __glewGetQueryObjectivARB = NULL; +PFNGLGETQUERYOBJECTUIVARBPROC __glewGetQueryObjectuivARB = NULL; +PFNGLGETQUERYIVARBPROC __glewGetQueryivARB = NULL; +PFNGLISQUERYARBPROC __glewIsQueryARB = NULL; + +PFNGLPOINTPARAMETERFARBPROC __glewPointParameterfARB = NULL; +PFNGLPOINTPARAMETERFVARBPROC __glewPointParameterfvARB = NULL; + +PFNGLATTACHOBJECTARBPROC __glewAttachObjectARB = NULL; +PFNGLCOMPILESHADERARBPROC __glewCompileShaderARB = NULL; +PFNGLCREATEPROGRAMOBJECTARBPROC __glewCreateProgramObjectARB = NULL; +PFNGLCREATESHADEROBJECTARBPROC __glewCreateShaderObjectARB = NULL; +PFNGLDELETEOBJECTARBPROC __glewDeleteObjectARB = NULL; +PFNGLDETACHOBJECTARBPROC __glewDetachObjectARB = NULL; +PFNGLGETACTIVEUNIFORMARBPROC __glewGetActiveUniformARB = NULL; +PFNGLGETATTACHEDOBJECTSARBPROC __glewGetAttachedObjectsARB = NULL; +PFNGLGETHANDLEARBPROC __glewGetHandleARB = NULL; +PFNGLGETINFOLOGARBPROC __glewGetInfoLogARB = NULL; +PFNGLGETOBJECTPARAMETERFVARBPROC __glewGetObjectParameterfvARB = NULL; +PFNGLGETOBJECTPARAMETERIVARBPROC __glewGetObjectParameterivARB = NULL; +PFNGLGETSHADERSOURCEARBPROC __glewGetShaderSourceARB = NULL; +PFNGLGETUNIFORMLOCATIONARBPROC __glewGetUniformLocationARB = NULL; +PFNGLGETUNIFORMFVARBPROC __glewGetUniformfvARB = NULL; +PFNGLGETUNIFORMIVARBPROC __glewGetUniformivARB = NULL; +PFNGLLINKPROGRAMARBPROC __glewLinkProgramARB = NULL; +PFNGLSHADERSOURCEARBPROC __glewShaderSourceARB = NULL; +PFNGLUNIFORM1FARBPROC __glewUniform1fARB = NULL; +PFNGLUNIFORM1FVARBPROC __glewUniform1fvARB = NULL; +PFNGLUNIFORM1IARBPROC __glewUniform1iARB = NULL; +PFNGLUNIFORM1IVARBPROC __glewUniform1ivARB = NULL; +PFNGLUNIFORM2FARBPROC __glewUniform2fARB = NULL; +PFNGLUNIFORM2FVARBPROC __glewUniform2fvARB = NULL; +PFNGLUNIFORM2IARBPROC __glewUniform2iARB = NULL; +PFNGLUNIFORM2IVARBPROC __glewUniform2ivARB = NULL; +PFNGLUNIFORM3FARBPROC __glewUniform3fARB = NULL; +PFNGLUNIFORM3FVARBPROC __glewUniform3fvARB = NULL; +PFNGLUNIFORM3IARBPROC __glewUniform3iARB = NULL; +PFNGLUNIFORM3IVARBPROC __glewUniform3ivARB = NULL; +PFNGLUNIFORM4FARBPROC __glewUniform4fARB = NULL; +PFNGLUNIFORM4FVARBPROC __glewUniform4fvARB = NULL; +PFNGLUNIFORM4IARBPROC __glewUniform4iARB = NULL; +PFNGLUNIFORM4IVARBPROC __glewUniform4ivARB = NULL; +PFNGLUNIFORMMATRIX2FVARBPROC __glewUniformMatrix2fvARB = NULL; +PFNGLUNIFORMMATRIX3FVARBPROC __glewUniformMatrix3fvARB = NULL; +PFNGLUNIFORMMATRIX4FVARBPROC __glewUniformMatrix4fvARB = NULL; +PFNGLUSEPROGRAMOBJECTARBPROC __glewUseProgramObjectARB = NULL; +PFNGLVALIDATEPROGRAMARBPROC __glewValidateProgramARB = NULL; + +PFNGLTEXBUFFERARBPROC __glewTexBufferARB = NULL; + +PFNGLCOMPRESSEDTEXIMAGE1DARBPROC __glewCompressedTexImage1DARB = NULL; +PFNGLCOMPRESSEDTEXIMAGE2DARBPROC __glewCompressedTexImage2DARB = NULL; +PFNGLCOMPRESSEDTEXIMAGE3DARBPROC __glewCompressedTexImage3DARB = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC __glewCompressedTexSubImage1DARB = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC __glewCompressedTexSubImage2DARB = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC __glewCompressedTexSubImage3DARB = NULL; +PFNGLGETCOMPRESSEDTEXIMAGEARBPROC __glewGetCompressedTexImageARB = NULL; + +PFNGLLOADTRANSPOSEMATRIXDARBPROC __glewLoadTransposeMatrixdARB = NULL; +PFNGLLOADTRANSPOSEMATRIXFARBPROC __glewLoadTransposeMatrixfARB = NULL; +PFNGLMULTTRANSPOSEMATRIXDARBPROC __glewMultTransposeMatrixdARB = NULL; +PFNGLMULTTRANSPOSEMATRIXFARBPROC __glewMultTransposeMatrixfARB = NULL; + +PFNGLBINDVERTEXARRAYPROC __glewBindVertexArray = NULL; +PFNGLDELETEVERTEXARRAYSPROC __glewDeleteVertexArrays = NULL; +PFNGLGENVERTEXARRAYSPROC __glewGenVertexArrays = NULL; +PFNGLISVERTEXARRAYPROC __glewIsVertexArray = NULL; + +PFNGLVERTEXBLENDARBPROC __glewVertexBlendARB = NULL; +PFNGLWEIGHTPOINTERARBPROC __glewWeightPointerARB = NULL; +PFNGLWEIGHTBVARBPROC __glewWeightbvARB = NULL; +PFNGLWEIGHTDVARBPROC __glewWeightdvARB = NULL; +PFNGLWEIGHTFVARBPROC __glewWeightfvARB = NULL; +PFNGLWEIGHTIVARBPROC __glewWeightivARB = NULL; +PFNGLWEIGHTSVARBPROC __glewWeightsvARB = NULL; +PFNGLWEIGHTUBVARBPROC __glewWeightubvARB = NULL; +PFNGLWEIGHTUIVARBPROC __glewWeightuivARB = NULL; +PFNGLWEIGHTUSVARBPROC __glewWeightusvARB = NULL; + +PFNGLBINDBUFFERARBPROC __glewBindBufferARB = NULL; +PFNGLBUFFERDATAARBPROC __glewBufferDataARB = NULL; +PFNGLBUFFERSUBDATAARBPROC __glewBufferSubDataARB = NULL; +PFNGLDELETEBUFFERSARBPROC __glewDeleteBuffersARB = NULL; +PFNGLGENBUFFERSARBPROC __glewGenBuffersARB = NULL; +PFNGLGETBUFFERPARAMETERIVARBPROC __glewGetBufferParameterivARB = NULL; +PFNGLGETBUFFERPOINTERVARBPROC __glewGetBufferPointervARB = NULL; +PFNGLGETBUFFERSUBDATAARBPROC __glewGetBufferSubDataARB = NULL; +PFNGLISBUFFERARBPROC __glewIsBufferARB = NULL; +PFNGLMAPBUFFERARBPROC __glewMapBufferARB = NULL; +PFNGLUNMAPBUFFERARBPROC __glewUnmapBufferARB = NULL; + +PFNGLBINDPROGRAMARBPROC __glewBindProgramARB = NULL; +PFNGLDELETEPROGRAMSARBPROC __glewDeleteProgramsARB = NULL; +PFNGLDISABLEVERTEXATTRIBARRAYARBPROC __glewDisableVertexAttribArrayARB = NULL; +PFNGLENABLEVERTEXATTRIBARRAYARBPROC __glewEnableVertexAttribArrayARB = NULL; +PFNGLGENPROGRAMSARBPROC __glewGenProgramsARB = NULL; +PFNGLGETPROGRAMENVPARAMETERDVARBPROC __glewGetProgramEnvParameterdvARB = NULL; +PFNGLGETPROGRAMENVPARAMETERFVARBPROC __glewGetProgramEnvParameterfvARB = NULL; +PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC __glewGetProgramLocalParameterdvARB = NULL; +PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC __glewGetProgramLocalParameterfvARB = NULL; +PFNGLGETPROGRAMSTRINGARBPROC __glewGetProgramStringARB = NULL; +PFNGLGETPROGRAMIVARBPROC __glewGetProgramivARB = NULL; +PFNGLGETVERTEXATTRIBPOINTERVARBPROC __glewGetVertexAttribPointervARB = NULL; +PFNGLGETVERTEXATTRIBDVARBPROC __glewGetVertexAttribdvARB = NULL; +PFNGLGETVERTEXATTRIBFVARBPROC __glewGetVertexAttribfvARB = NULL; +PFNGLGETVERTEXATTRIBIVARBPROC __glewGetVertexAttribivARB = NULL; +PFNGLISPROGRAMARBPROC __glewIsProgramARB = NULL; +PFNGLPROGRAMENVPARAMETER4DARBPROC __glewProgramEnvParameter4dARB = NULL; +PFNGLPROGRAMENVPARAMETER4DVARBPROC __glewProgramEnvParameter4dvARB = NULL; +PFNGLPROGRAMENVPARAMETER4FARBPROC __glewProgramEnvParameter4fARB = NULL; +PFNGLPROGRAMENVPARAMETER4FVARBPROC __glewProgramEnvParameter4fvARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4DARBPROC __glewProgramLocalParameter4dARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4DVARBPROC __glewProgramLocalParameter4dvARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4FARBPROC __glewProgramLocalParameter4fARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4FVARBPROC __glewProgramLocalParameter4fvARB = NULL; +PFNGLPROGRAMSTRINGARBPROC __glewProgramStringARB = NULL; +PFNGLVERTEXATTRIB1DARBPROC __glewVertexAttrib1dARB = NULL; +PFNGLVERTEXATTRIB1DVARBPROC __glewVertexAttrib1dvARB = NULL; +PFNGLVERTEXATTRIB1FARBPROC __glewVertexAttrib1fARB = NULL; +PFNGLVERTEXATTRIB1FVARBPROC __glewVertexAttrib1fvARB = NULL; +PFNGLVERTEXATTRIB1SARBPROC __glewVertexAttrib1sARB = NULL; +PFNGLVERTEXATTRIB1SVARBPROC __glewVertexAttrib1svARB = NULL; +PFNGLVERTEXATTRIB2DARBPROC __glewVertexAttrib2dARB = NULL; +PFNGLVERTEXATTRIB2DVARBPROC __glewVertexAttrib2dvARB = NULL; +PFNGLVERTEXATTRIB2FARBPROC __glewVertexAttrib2fARB = NULL; +PFNGLVERTEXATTRIB2FVARBPROC __glewVertexAttrib2fvARB = NULL; +PFNGLVERTEXATTRIB2SARBPROC __glewVertexAttrib2sARB = NULL; +PFNGLVERTEXATTRIB2SVARBPROC __glewVertexAttrib2svARB = NULL; +PFNGLVERTEXATTRIB3DARBPROC __glewVertexAttrib3dARB = NULL; +PFNGLVERTEXATTRIB3DVARBPROC __glewVertexAttrib3dvARB = NULL; +PFNGLVERTEXATTRIB3FARBPROC __glewVertexAttrib3fARB = NULL; +PFNGLVERTEXATTRIB3FVARBPROC __glewVertexAttrib3fvARB = NULL; +PFNGLVERTEXATTRIB3SARBPROC __glewVertexAttrib3sARB = NULL; +PFNGLVERTEXATTRIB3SVARBPROC __glewVertexAttrib3svARB = NULL; +PFNGLVERTEXATTRIB4NBVARBPROC __glewVertexAttrib4NbvARB = NULL; +PFNGLVERTEXATTRIB4NIVARBPROC __glewVertexAttrib4NivARB = NULL; +PFNGLVERTEXATTRIB4NSVARBPROC __glewVertexAttrib4NsvARB = NULL; +PFNGLVERTEXATTRIB4NUBARBPROC __glewVertexAttrib4NubARB = NULL; +PFNGLVERTEXATTRIB4NUBVARBPROC __glewVertexAttrib4NubvARB = NULL; +PFNGLVERTEXATTRIB4NUIVARBPROC __glewVertexAttrib4NuivARB = NULL; +PFNGLVERTEXATTRIB4NUSVARBPROC __glewVertexAttrib4NusvARB = NULL; +PFNGLVERTEXATTRIB4BVARBPROC __glewVertexAttrib4bvARB = NULL; +PFNGLVERTEXATTRIB4DARBPROC __glewVertexAttrib4dARB = NULL; +PFNGLVERTEXATTRIB4DVARBPROC __glewVertexAttrib4dvARB = NULL; +PFNGLVERTEXATTRIB4FARBPROC __glewVertexAttrib4fARB = NULL; +PFNGLVERTEXATTRIB4FVARBPROC __glewVertexAttrib4fvARB = NULL; +PFNGLVERTEXATTRIB4IVARBPROC __glewVertexAttrib4ivARB = NULL; +PFNGLVERTEXATTRIB4SARBPROC __glewVertexAttrib4sARB = NULL; +PFNGLVERTEXATTRIB4SVARBPROC __glewVertexAttrib4svARB = NULL; +PFNGLVERTEXATTRIB4UBVARBPROC __glewVertexAttrib4ubvARB = NULL; +PFNGLVERTEXATTRIB4UIVARBPROC __glewVertexAttrib4uivARB = NULL; +PFNGLVERTEXATTRIB4USVARBPROC __glewVertexAttrib4usvARB = NULL; +PFNGLVERTEXATTRIBPOINTERARBPROC __glewVertexAttribPointerARB = NULL; + +PFNGLBINDATTRIBLOCATIONARBPROC __glewBindAttribLocationARB = NULL; +PFNGLGETACTIVEATTRIBARBPROC __glewGetActiveAttribARB = NULL; +PFNGLGETATTRIBLOCATIONARBPROC __glewGetAttribLocationARB = NULL; + +PFNGLWINDOWPOS2DARBPROC __glewWindowPos2dARB = NULL; +PFNGLWINDOWPOS2DVARBPROC __glewWindowPos2dvARB = NULL; +PFNGLWINDOWPOS2FARBPROC __glewWindowPos2fARB = NULL; +PFNGLWINDOWPOS2FVARBPROC __glewWindowPos2fvARB = NULL; +PFNGLWINDOWPOS2IARBPROC __glewWindowPos2iARB = NULL; +PFNGLWINDOWPOS2IVARBPROC __glewWindowPos2ivARB = NULL; +PFNGLWINDOWPOS2SARBPROC __glewWindowPos2sARB = NULL; +PFNGLWINDOWPOS2SVARBPROC __glewWindowPos2svARB = NULL; +PFNGLWINDOWPOS3DARBPROC __glewWindowPos3dARB = NULL; +PFNGLWINDOWPOS3DVARBPROC __glewWindowPos3dvARB = NULL; +PFNGLWINDOWPOS3FARBPROC __glewWindowPos3fARB = NULL; +PFNGLWINDOWPOS3FVARBPROC __glewWindowPos3fvARB = NULL; +PFNGLWINDOWPOS3IARBPROC __glewWindowPos3iARB = NULL; +PFNGLWINDOWPOS3IVARBPROC __glewWindowPos3ivARB = NULL; +PFNGLWINDOWPOS3SARBPROC __glewWindowPos3sARB = NULL; +PFNGLWINDOWPOS3SVARBPROC __glewWindowPos3svARB = NULL; + +PFNGLDRAWBUFFERSATIPROC __glewDrawBuffersATI = NULL; + +PFNGLDRAWELEMENTARRAYATIPROC __glewDrawElementArrayATI = NULL; +PFNGLDRAWRANGEELEMENTARRAYATIPROC __glewDrawRangeElementArrayATI = NULL; +PFNGLELEMENTPOINTERATIPROC __glewElementPointerATI = NULL; + +PFNGLGETTEXBUMPPARAMETERFVATIPROC __glewGetTexBumpParameterfvATI = NULL; +PFNGLGETTEXBUMPPARAMETERIVATIPROC __glewGetTexBumpParameterivATI = NULL; +PFNGLTEXBUMPPARAMETERFVATIPROC __glewTexBumpParameterfvATI = NULL; +PFNGLTEXBUMPPARAMETERIVATIPROC __glewTexBumpParameterivATI = NULL; + +PFNGLALPHAFRAGMENTOP1ATIPROC __glewAlphaFragmentOp1ATI = NULL; +PFNGLALPHAFRAGMENTOP2ATIPROC __glewAlphaFragmentOp2ATI = NULL; +PFNGLALPHAFRAGMENTOP3ATIPROC __glewAlphaFragmentOp3ATI = NULL; +PFNGLBEGINFRAGMENTSHADERATIPROC __glewBeginFragmentShaderATI = NULL; +PFNGLBINDFRAGMENTSHADERATIPROC __glewBindFragmentShaderATI = NULL; +PFNGLCOLORFRAGMENTOP1ATIPROC __glewColorFragmentOp1ATI = NULL; +PFNGLCOLORFRAGMENTOP2ATIPROC __glewColorFragmentOp2ATI = NULL; +PFNGLCOLORFRAGMENTOP3ATIPROC __glewColorFragmentOp3ATI = NULL; +PFNGLDELETEFRAGMENTSHADERATIPROC __glewDeleteFragmentShaderATI = NULL; +PFNGLENDFRAGMENTSHADERATIPROC __glewEndFragmentShaderATI = NULL; +PFNGLGENFRAGMENTSHADERSATIPROC __glewGenFragmentShadersATI = NULL; +PFNGLPASSTEXCOORDATIPROC __glewPassTexCoordATI = NULL; +PFNGLSAMPLEMAPATIPROC __glewSampleMapATI = NULL; +PFNGLSETFRAGMENTSHADERCONSTANTATIPROC __glewSetFragmentShaderConstantATI = NULL; + +PFNGLMAPOBJECTBUFFERATIPROC __glewMapObjectBufferATI = NULL; +PFNGLUNMAPOBJECTBUFFERATIPROC __glewUnmapObjectBufferATI = NULL; + +PFNGLPNTRIANGLESFATIPROC __glPNTrianglewesfATI = NULL; +PFNGLPNTRIANGLESIATIPROC __glPNTrianglewesiATI = NULL; + +PFNGLSTENCILFUNCSEPARATEATIPROC __glewStencilFuncSeparateATI = NULL; +PFNGLSTENCILOPSEPARATEATIPROC __glewStencilOpSeparateATI = NULL; + +PFNGLARRAYOBJECTATIPROC __glewArrayObjectATI = NULL; +PFNGLFREEOBJECTBUFFERATIPROC __glewFreeObjectBufferATI = NULL; +PFNGLGETARRAYOBJECTFVATIPROC __glewGetArrayObjectfvATI = NULL; +PFNGLGETARRAYOBJECTIVATIPROC __glewGetArrayObjectivATI = NULL; +PFNGLGETOBJECTBUFFERFVATIPROC __glewGetObjectBufferfvATI = NULL; +PFNGLGETOBJECTBUFFERIVATIPROC __glewGetObjectBufferivATI = NULL; +PFNGLGETVARIANTARRAYOBJECTFVATIPROC __glewGetVariantArrayObjectfvATI = NULL; +PFNGLGETVARIANTARRAYOBJECTIVATIPROC __glewGetVariantArrayObjectivATI = NULL; +PFNGLISOBJECTBUFFERATIPROC __glewIsObjectBufferATI = NULL; +PFNGLNEWOBJECTBUFFERATIPROC __glewNewObjectBufferATI = NULL; +PFNGLUPDATEOBJECTBUFFERATIPROC __glewUpdateObjectBufferATI = NULL; +PFNGLVARIANTARRAYOBJECTATIPROC __glewVariantArrayObjectATI = NULL; + +PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC __glewGetVertexAttribArrayObjectfvATI = NULL; +PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC __glewGetVertexAttribArrayObjectivATI = NULL; +PFNGLVERTEXATTRIBARRAYOBJECTATIPROC __glewVertexAttribArrayObjectATI = NULL; + +PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC __glewClientActiveVertexStreamATI = NULL; +PFNGLNORMALSTREAM3BATIPROC __glewNormalStream3bATI = NULL; +PFNGLNORMALSTREAM3BVATIPROC __glewNormalStream3bvATI = NULL; +PFNGLNORMALSTREAM3DATIPROC __glewNormalStream3dATI = NULL; +PFNGLNORMALSTREAM3DVATIPROC __glewNormalStream3dvATI = NULL; +PFNGLNORMALSTREAM3FATIPROC __glewNormalStream3fATI = NULL; +PFNGLNORMALSTREAM3FVATIPROC __glewNormalStream3fvATI = NULL; +PFNGLNORMALSTREAM3IATIPROC __glewNormalStream3iATI = NULL; +PFNGLNORMALSTREAM3IVATIPROC __glewNormalStream3ivATI = NULL; +PFNGLNORMALSTREAM3SATIPROC __glewNormalStream3sATI = NULL; +PFNGLNORMALSTREAM3SVATIPROC __glewNormalStream3svATI = NULL; +PFNGLVERTEXBLENDENVFATIPROC __glewVertexBlendEnvfATI = NULL; +PFNGLVERTEXBLENDENVIATIPROC __glewVertexBlendEnviATI = NULL; +PFNGLVERTEXSTREAM2DATIPROC __glewVertexStream2dATI = NULL; +PFNGLVERTEXSTREAM2DVATIPROC __glewVertexStream2dvATI = NULL; +PFNGLVERTEXSTREAM2FATIPROC __glewVertexStream2fATI = NULL; +PFNGLVERTEXSTREAM2FVATIPROC __glewVertexStream2fvATI = NULL; +PFNGLVERTEXSTREAM2IATIPROC __glewVertexStream2iATI = NULL; +PFNGLVERTEXSTREAM2IVATIPROC __glewVertexStream2ivATI = NULL; +PFNGLVERTEXSTREAM2SATIPROC __glewVertexStream2sATI = NULL; +PFNGLVERTEXSTREAM2SVATIPROC __glewVertexStream2svATI = NULL; +PFNGLVERTEXSTREAM3DATIPROC __glewVertexStream3dATI = NULL; +PFNGLVERTEXSTREAM3DVATIPROC __glewVertexStream3dvATI = NULL; +PFNGLVERTEXSTREAM3FATIPROC __glewVertexStream3fATI = NULL; +PFNGLVERTEXSTREAM3FVATIPROC __glewVertexStream3fvATI = NULL; +PFNGLVERTEXSTREAM3IATIPROC __glewVertexStream3iATI = NULL; +PFNGLVERTEXSTREAM3IVATIPROC __glewVertexStream3ivATI = NULL; +PFNGLVERTEXSTREAM3SATIPROC __glewVertexStream3sATI = NULL; +PFNGLVERTEXSTREAM3SVATIPROC __glewVertexStream3svATI = NULL; +PFNGLVERTEXSTREAM4DATIPROC __glewVertexStream4dATI = NULL; +PFNGLVERTEXSTREAM4DVATIPROC __glewVertexStream4dvATI = NULL; +PFNGLVERTEXSTREAM4FATIPROC __glewVertexStream4fATI = NULL; +PFNGLVERTEXSTREAM4FVATIPROC __glewVertexStream4fvATI = NULL; +PFNGLVERTEXSTREAM4IATIPROC __glewVertexStream4iATI = NULL; +PFNGLVERTEXSTREAM4IVATIPROC __glewVertexStream4ivATI = NULL; +PFNGLVERTEXSTREAM4SATIPROC __glewVertexStream4sATI = NULL; +PFNGLVERTEXSTREAM4SVATIPROC __glewVertexStream4svATI = NULL; + +PFNGLGETUNIFORMBUFFERSIZEEXTPROC __glewGetUniformBufferSizeEXT = NULL; +PFNGLGETUNIFORMOFFSETEXTPROC __glewGetUniformOffsetEXT = NULL; +PFNGLUNIFORMBUFFEREXTPROC __glewUniformBufferEXT = NULL; + +PFNGLBLENDCOLOREXTPROC __glewBlendColorEXT = NULL; + +PFNGLBLENDEQUATIONSEPARATEEXTPROC __glewBlendEquationSeparateEXT = NULL; + +PFNGLBLENDFUNCSEPARATEEXTPROC __glewBlendFuncSeparateEXT = NULL; + +PFNGLBLENDEQUATIONEXTPROC __glewBlendEquationEXT = NULL; + +PFNGLCOLORSUBTABLEEXTPROC __glewColorSubTableEXT = NULL; +PFNGLCOPYCOLORSUBTABLEEXTPROC __glewCopyColorSubTableEXT = NULL; + +PFNGLLOCKARRAYSEXTPROC __glewLockArraysEXT = NULL; +PFNGLUNLOCKARRAYSEXTPROC __glewUnlockArraysEXT = NULL; + +PFNGLCONVOLUTIONFILTER1DEXTPROC __glewConvolutionFilter1DEXT = NULL; +PFNGLCONVOLUTIONFILTER2DEXTPROC __glewConvolutionFilter2DEXT = NULL; +PFNGLCONVOLUTIONPARAMETERFEXTPROC __glewConvolutionParameterfEXT = NULL; +PFNGLCONVOLUTIONPARAMETERFVEXTPROC __glewConvolutionParameterfvEXT = NULL; +PFNGLCONVOLUTIONPARAMETERIEXTPROC __glewConvolutionParameteriEXT = NULL; +PFNGLCONVOLUTIONPARAMETERIVEXTPROC __glewConvolutionParameterivEXT = NULL; +PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC __glewCopyConvolutionFilter1DEXT = NULL; +PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC __glewCopyConvolutionFilter2DEXT = NULL; +PFNGLGETCONVOLUTIONFILTEREXTPROC __glewGetConvolutionFilterEXT = NULL; +PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC __glewGetConvolutionParameterfvEXT = NULL; +PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC __glewGetConvolutionParameterivEXT = NULL; +PFNGLGETSEPARABLEFILTEREXTPROC __glewGetSeparableFilterEXT = NULL; +PFNGLSEPARABLEFILTER2DEXTPROC __glewSeparableFilter2DEXT = NULL; + +PFNGLBINORMALPOINTEREXTPROC __glewBinormalPointerEXT = NULL; +PFNGLTANGENTPOINTEREXTPROC __glewTangentPointerEXT = NULL; + +PFNGLCOPYTEXIMAGE1DEXTPROC __glewCopyTexImage1DEXT = NULL; +PFNGLCOPYTEXIMAGE2DEXTPROC __glewCopyTexImage2DEXT = NULL; +PFNGLCOPYTEXSUBIMAGE1DEXTPROC __glewCopyTexSubImage1DEXT = NULL; +PFNGLCOPYTEXSUBIMAGE2DEXTPROC __glewCopyTexSubImage2DEXT = NULL; +PFNGLCOPYTEXSUBIMAGE3DEXTPROC __glewCopyTexSubImage3DEXT = NULL; + +PFNGLCULLPARAMETERDVEXTPROC __glewCullParameterdvEXT = NULL; +PFNGLCULLPARAMETERFVEXTPROC __glewCullParameterfvEXT = NULL; + +PFNGLDEPTHBOUNDSEXTPROC __glewDepthBoundsEXT = NULL; + +PFNGLBINDMULTITEXTUREEXTPROC __glewBindMultiTextureEXT = NULL; +PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC __glewCheckNamedFramebufferStatusEXT = NULL; +PFNGLCLIENTATTRIBDEFAULTEXTPROC __glewClientAttribDefaultEXT = NULL; +PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC __glewCompressedMultiTexImage1DEXT = NULL; +PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC __glewCompressedMultiTexImage2DEXT = NULL; +PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC __glewCompressedMultiTexImage3DEXT = NULL; +PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC __glewCompressedMultiTexSubImage1DEXT = NULL; +PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC __glewCompressedMultiTexSubImage2DEXT = NULL; +PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC __glewCompressedMultiTexSubImage3DEXT = NULL; +PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC __glewCompressedTextureImage1DEXT = NULL; +PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC __glewCompressedTextureImage2DEXT = NULL; +PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC __glewCompressedTextureImage3DEXT = NULL; +PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC __glewCompressedTextureSubImage1DEXT = NULL; +PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC __glewCompressedTextureSubImage2DEXT = NULL; +PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC __glewCompressedTextureSubImage3DEXT = NULL; +PFNGLCOPYMULTITEXIMAGE1DEXTPROC __glewCopyMultiTexImage1DEXT = NULL; +PFNGLCOPYMULTITEXIMAGE2DEXTPROC __glewCopyMultiTexImage2DEXT = NULL; +PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC __glewCopyMultiTexSubImage1DEXT = NULL; +PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC __glewCopyMultiTexSubImage2DEXT = NULL; +PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC __glewCopyMultiTexSubImage3DEXT = NULL; +PFNGLCOPYTEXTUREIMAGE1DEXTPROC __glewCopyTextureImage1DEXT = NULL; +PFNGLCOPYTEXTUREIMAGE2DEXTPROC __glewCopyTextureImage2DEXT = NULL; +PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC __glewCopyTextureSubImage1DEXT = NULL; +PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC __glewCopyTextureSubImage2DEXT = NULL; +PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC __glewCopyTextureSubImage3DEXT = NULL; +PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC __glewDisableClientStateIndexedEXT = NULL; +PFNGLENABLECLIENTSTATEINDEXEDEXTPROC __glewEnableClientStateIndexedEXT = NULL; +PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC __glewFramebufferDrawBufferEXT = NULL; +PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC __glewFramebufferDrawBuffersEXT = NULL; +PFNGLFRAMEBUFFERREADBUFFEREXTPROC __glewFramebufferReadBufferEXT = NULL; +PFNGLGENERATEMULTITEXMIPMAPEXTPROC __glewGenerateMultiTexMipmapEXT = NULL; +PFNGLGENERATETEXTUREMIPMAPEXTPROC __glewGenerateTextureMipmapEXT = NULL; +PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC __glewGetCompressedMultiTexImageEXT = NULL; +PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC __glewGetCompressedTextureImageEXT = NULL; +PFNGLGETDOUBLEINDEXEDVEXTPROC __glewGetDoubleIndexedvEXT = NULL; +PFNGLGETFLOATINDEXEDVEXTPROC __glewGetFloatIndexedvEXT = NULL; +PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC __glewGetFramebufferParameterivEXT = NULL; +PFNGLGETMULTITEXENVFVEXTPROC __glewGetMultiTexEnvfvEXT = NULL; +PFNGLGETMULTITEXENVIVEXTPROC __glewGetMultiTexEnvivEXT = NULL; +PFNGLGETMULTITEXGENDVEXTPROC __glewGetMultiTexGendvEXT = NULL; +PFNGLGETMULTITEXGENFVEXTPROC __glewGetMultiTexGenfvEXT = NULL; +PFNGLGETMULTITEXGENIVEXTPROC __glewGetMultiTexGenivEXT = NULL; +PFNGLGETMULTITEXIMAGEEXTPROC __glewGetMultiTexImageEXT = NULL; +PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC __glewGetMultiTexLevelParameterfvEXT = NULL; +PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC __glewGetMultiTexLevelParameterivEXT = NULL; +PFNGLGETMULTITEXPARAMETERIIVEXTPROC __glewGetMultiTexParameterIivEXT = NULL; +PFNGLGETMULTITEXPARAMETERIUIVEXTPROC __glewGetMultiTexParameterIuivEXT = NULL; +PFNGLGETMULTITEXPARAMETERFVEXTPROC __glewGetMultiTexParameterfvEXT = NULL; +PFNGLGETMULTITEXPARAMETERIVEXTPROC __glewGetMultiTexParameterivEXT = NULL; +PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC __glewGetNamedBufferParameterivEXT = NULL; +PFNGLGETNAMEDBUFFERPOINTERVEXTPROC __glewGetNamedBufferPointervEXT = NULL; +PFNGLGETNAMEDBUFFERSUBDATAEXTPROC __glewGetNamedBufferSubDataEXT = NULL; +PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC __glewGetNamedFramebufferAttachmentParameterivEXT = NULL; +PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC __glewGetNamedProgramLocalParameterIivEXT = NULL; +PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC __glewGetNamedProgramLocalParameterIuivEXT = NULL; +PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC __glewGetNamedProgramLocalParameterdvEXT = NULL; +PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC __glewGetNamedProgramLocalParameterfvEXT = NULL; +PFNGLGETNAMEDPROGRAMSTRINGEXTPROC __glewGetNamedProgramStringEXT = NULL; +PFNGLGETNAMEDPROGRAMIVEXTPROC __glewGetNamedProgramivEXT = NULL; +PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC __glewGetNamedRenderbufferParameterivEXT = NULL; +PFNGLGETPOINTERINDEXEDVEXTPROC __glewGetPointerIndexedvEXT = NULL; +PFNGLGETTEXTUREIMAGEEXTPROC __glewGetTextureImageEXT = NULL; +PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC __glewGetTextureLevelParameterfvEXT = NULL; +PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC __glewGetTextureLevelParameterivEXT = NULL; +PFNGLGETTEXTUREPARAMETERIIVEXTPROC __glewGetTextureParameterIivEXT = NULL; +PFNGLGETTEXTUREPARAMETERIUIVEXTPROC __glewGetTextureParameterIuivEXT = NULL; +PFNGLGETTEXTUREPARAMETERFVEXTPROC __glewGetTextureParameterfvEXT = NULL; +PFNGLGETTEXTUREPARAMETERIVEXTPROC __glewGetTextureParameterivEXT = NULL; +PFNGLMAPNAMEDBUFFEREXTPROC __glewMapNamedBufferEXT = NULL; +PFNGLMATRIXFRUSTUMEXTPROC __glewMatrixFrustumEXT = NULL; +PFNGLMATRIXLOADIDENTITYEXTPROC __glewMatrixLoadIdentityEXT = NULL; +PFNGLMATRIXLOADTRANSPOSEDEXTPROC __glewMatrixLoadTransposedEXT = NULL; +PFNGLMATRIXLOADTRANSPOSEFEXTPROC __glewMatrixLoadTransposefEXT = NULL; +PFNGLMATRIXLOADDEXTPROC __glewMatrixLoaddEXT = NULL; +PFNGLMATRIXLOADFEXTPROC __glewMatrixLoadfEXT = NULL; +PFNGLMATRIXMULTTRANSPOSEDEXTPROC __glewMatrixMultTransposedEXT = NULL; +PFNGLMATRIXMULTTRANSPOSEFEXTPROC __glewMatrixMultTransposefEXT = NULL; +PFNGLMATRIXMULTDEXTPROC __glewMatrixMultdEXT = NULL; +PFNGLMATRIXMULTFEXTPROC __glewMatrixMultfEXT = NULL; +PFNGLMATRIXORTHOEXTPROC __glewMatrixOrthoEXT = NULL; +PFNGLMATRIXPOPEXTPROC __glewMatrixPopEXT = NULL; +PFNGLMATRIXPUSHEXTPROC __glewMatrixPushEXT = NULL; +PFNGLMATRIXROTATEDEXTPROC __glewMatrixRotatedEXT = NULL; +PFNGLMATRIXROTATEFEXTPROC __glewMatrixRotatefEXT = NULL; +PFNGLMATRIXSCALEDEXTPROC __glewMatrixScaledEXT = NULL; +PFNGLMATRIXSCALEFEXTPROC __glewMatrixScalefEXT = NULL; +PFNGLMATRIXTRANSLATEDEXTPROC __glewMatrixTranslatedEXT = NULL; +PFNGLMATRIXTRANSLATEFEXTPROC __glewMatrixTranslatefEXT = NULL; +PFNGLMULTITEXBUFFEREXTPROC __glewMultiTexBufferEXT = NULL; +PFNGLMULTITEXCOORDPOINTEREXTPROC __glewMultiTexCoordPointerEXT = NULL; +PFNGLMULTITEXENVFEXTPROC __glewMultiTexEnvfEXT = NULL; +PFNGLMULTITEXENVFVEXTPROC __glewMultiTexEnvfvEXT = NULL; +PFNGLMULTITEXENVIEXTPROC __glewMultiTexEnviEXT = NULL; +PFNGLMULTITEXENVIVEXTPROC __glewMultiTexEnvivEXT = NULL; +PFNGLMULTITEXGENDEXTPROC __glewMultiTexGendEXT = NULL; +PFNGLMULTITEXGENDVEXTPROC __glewMultiTexGendvEXT = NULL; +PFNGLMULTITEXGENFEXTPROC __glewMultiTexGenfEXT = NULL; +PFNGLMULTITEXGENFVEXTPROC __glewMultiTexGenfvEXT = NULL; +PFNGLMULTITEXGENIEXTPROC __glewMultiTexGeniEXT = NULL; +PFNGLMULTITEXGENIVEXTPROC __glewMultiTexGenivEXT = NULL; +PFNGLMULTITEXIMAGE1DEXTPROC __glewMultiTexImage1DEXT = NULL; +PFNGLMULTITEXIMAGE2DEXTPROC __glewMultiTexImage2DEXT = NULL; +PFNGLMULTITEXIMAGE3DEXTPROC __glewMultiTexImage3DEXT = NULL; +PFNGLMULTITEXPARAMETERIIVEXTPROC __glewMultiTexParameterIivEXT = NULL; +PFNGLMULTITEXPARAMETERIUIVEXTPROC __glewMultiTexParameterIuivEXT = NULL; +PFNGLMULTITEXPARAMETERFEXTPROC __glewMultiTexParameterfEXT = NULL; +PFNGLMULTITEXPARAMETERFVEXTPROC __glewMultiTexParameterfvEXT = NULL; +PFNGLMULTITEXPARAMETERIEXTPROC __glewMultiTexParameteriEXT = NULL; +PFNGLMULTITEXPARAMETERIVEXTPROC __glewMultiTexParameterivEXT = NULL; +PFNGLMULTITEXRENDERBUFFEREXTPROC __glewMultiTexRenderbufferEXT = NULL; +PFNGLMULTITEXSUBIMAGE1DEXTPROC __glewMultiTexSubImage1DEXT = NULL; +PFNGLMULTITEXSUBIMAGE2DEXTPROC __glewMultiTexSubImage2DEXT = NULL; +PFNGLMULTITEXSUBIMAGE3DEXTPROC __glewMultiTexSubImage3DEXT = NULL; +PFNGLNAMEDBUFFERDATAEXTPROC __glewNamedBufferDataEXT = NULL; +PFNGLNAMEDBUFFERSUBDATAEXTPROC __glewNamedBufferSubDataEXT = NULL; +PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC __glewNamedFramebufferRenderbufferEXT = NULL; +PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC __glewNamedFramebufferTexture1DEXT = NULL; +PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC __glewNamedFramebufferTexture2DEXT = NULL; +PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC __glewNamedFramebufferTexture3DEXT = NULL; +PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC __glewNamedFramebufferTextureEXT = NULL; +PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC __glewNamedFramebufferTextureFaceEXT = NULL; +PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC __glewNamedFramebufferTextureLayerEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC __glewNamedProgramLocalParameter4dEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC __glewNamedProgramLocalParameter4dvEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC __glewNamedProgramLocalParameter4fEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC __glewNamedProgramLocalParameter4fvEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC __glewNamedProgramLocalParameterI4iEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC __glewNamedProgramLocalParameterI4ivEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC __glewNamedProgramLocalParameterI4uiEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC __glewNamedProgramLocalParameterI4uivEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC __glewNamedProgramLocalParameters4fvEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC __glewNamedProgramLocalParametersI4ivEXT = NULL; +PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC __glewNamedProgramLocalParametersI4uivEXT = NULL; +PFNGLNAMEDPROGRAMSTRINGEXTPROC __glewNamedProgramStringEXT = NULL; +PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC __glewNamedRenderbufferStorageEXT = NULL; +PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC __glewNamedRenderbufferStorageMultisampleCoverageEXT = NULL; +PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC __glewNamedRenderbufferStorageMultisampleEXT = NULL; +PFNGLPROGRAMUNIFORM1FEXTPROC __glewProgramUniform1fEXT = NULL; +PFNGLPROGRAMUNIFORM1FVEXTPROC __glewProgramUniform1fvEXT = NULL; +PFNGLPROGRAMUNIFORM1IEXTPROC __glewProgramUniform1iEXT = NULL; +PFNGLPROGRAMUNIFORM1IVEXTPROC __glewProgramUniform1ivEXT = NULL; +PFNGLPROGRAMUNIFORM1UIEXTPROC __glewProgramUniform1uiEXT = NULL; +PFNGLPROGRAMUNIFORM1UIVEXTPROC __glewProgramUniform1uivEXT = NULL; +PFNGLPROGRAMUNIFORM2FEXTPROC __glewProgramUniform2fEXT = NULL; +PFNGLPROGRAMUNIFORM2FVEXTPROC __glewProgramUniform2fvEXT = NULL; +PFNGLPROGRAMUNIFORM2IEXTPROC __glewProgramUniform2iEXT = NULL; +PFNGLPROGRAMUNIFORM2IVEXTPROC __glewProgramUniform2ivEXT = NULL; +PFNGLPROGRAMUNIFORM2UIEXTPROC __glewProgramUniform2uiEXT = NULL; +PFNGLPROGRAMUNIFORM2UIVEXTPROC __glewProgramUniform2uivEXT = NULL; +PFNGLPROGRAMUNIFORM3FEXTPROC __glewProgramUniform3fEXT = NULL; +PFNGLPROGRAMUNIFORM3FVEXTPROC __glewProgramUniform3fvEXT = NULL; +PFNGLPROGRAMUNIFORM3IEXTPROC __glewProgramUniform3iEXT = NULL; +PFNGLPROGRAMUNIFORM3IVEXTPROC __glewProgramUniform3ivEXT = NULL; +PFNGLPROGRAMUNIFORM3UIEXTPROC __glewProgramUniform3uiEXT = NULL; +PFNGLPROGRAMUNIFORM3UIVEXTPROC __glewProgramUniform3uivEXT = NULL; +PFNGLPROGRAMUNIFORM4FEXTPROC __glewProgramUniform4fEXT = NULL; +PFNGLPROGRAMUNIFORM4FVEXTPROC __glewProgramUniform4fvEXT = NULL; +PFNGLPROGRAMUNIFORM4IEXTPROC __glewProgramUniform4iEXT = NULL; +PFNGLPROGRAMUNIFORM4IVEXTPROC __glewProgramUniform4ivEXT = NULL; +PFNGLPROGRAMUNIFORM4UIEXTPROC __glewProgramUniform4uiEXT = NULL; +PFNGLPROGRAMUNIFORM4UIVEXTPROC __glewProgramUniform4uivEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC __glewProgramUniformMatrix2fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC __glewProgramUniformMatrix2x3fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC __glewProgramUniformMatrix2x4fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC __glewProgramUniformMatrix3fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC __glewProgramUniformMatrix3x2fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC __glewProgramUniformMatrix3x4fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC __glewProgramUniformMatrix4fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC __glewProgramUniformMatrix4x2fvEXT = NULL; +PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC __glewProgramUniformMatrix4x3fvEXT = NULL; +PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC __glewPushClientAttribDefaultEXT = NULL; +PFNGLTEXTUREBUFFEREXTPROC __glewTextureBufferEXT = NULL; +PFNGLTEXTUREIMAGE1DEXTPROC __glewTextureImage1DEXT = NULL; +PFNGLTEXTUREIMAGE2DEXTPROC __glewTextureImage2DEXT = NULL; +PFNGLTEXTUREIMAGE3DEXTPROC __glewTextureImage3DEXT = NULL; +PFNGLTEXTUREPARAMETERIIVEXTPROC __glewTextureParameterIivEXT = NULL; +PFNGLTEXTUREPARAMETERIUIVEXTPROC __glewTextureParameterIuivEXT = NULL; +PFNGLTEXTUREPARAMETERFEXTPROC __glewTextureParameterfEXT = NULL; +PFNGLTEXTUREPARAMETERFVEXTPROC __glewTextureParameterfvEXT = NULL; +PFNGLTEXTUREPARAMETERIEXTPROC __glewTextureParameteriEXT = NULL; +PFNGLTEXTUREPARAMETERIVEXTPROC __glewTextureParameterivEXT = NULL; +PFNGLTEXTURERENDERBUFFEREXTPROC __glewTextureRenderbufferEXT = NULL; +PFNGLTEXTURESUBIMAGE1DEXTPROC __glewTextureSubImage1DEXT = NULL; +PFNGLTEXTURESUBIMAGE2DEXTPROC __glewTextureSubImage2DEXT = NULL; +PFNGLTEXTURESUBIMAGE3DEXTPROC __glewTextureSubImage3DEXT = NULL; +PFNGLUNMAPNAMEDBUFFEREXTPROC __glewUnmapNamedBufferEXT = NULL; + +PFNGLCOLORMASKINDEXEDEXTPROC __glewColorMaskIndexedEXT = NULL; +PFNGLDISABLEINDEXEDEXTPROC __glewDisableIndexedEXT = NULL; +PFNGLENABLEINDEXEDEXTPROC __glewEnableIndexedEXT = NULL; +PFNGLGETBOOLEANINDEXEDVEXTPROC __glewGetBooleanIndexedvEXT = NULL; +PFNGLGETINTEGERINDEXEDVEXTPROC __glewGetIntegerIndexedvEXT = NULL; +PFNGLISENABLEDINDEXEDEXTPROC __glewIsEnabledIndexedEXT = NULL; + +PFNGLDRAWARRAYSINSTANCEDEXTPROC __glewDrawArraysInstancedEXT = NULL; +PFNGLDRAWELEMENTSINSTANCEDEXTPROC __glewDrawElementsInstancedEXT = NULL; + +PFNGLDRAWRANGEELEMENTSEXTPROC __glewDrawRangeElementsEXT = NULL; + +PFNGLFOGCOORDPOINTEREXTPROC __glewFogCoordPointerEXT = NULL; +PFNGLFOGCOORDDEXTPROC __glewFogCoorddEXT = NULL; +PFNGLFOGCOORDDVEXTPROC __glewFogCoorddvEXT = NULL; +PFNGLFOGCOORDFEXTPROC __glewFogCoordfEXT = NULL; +PFNGLFOGCOORDFVEXTPROC __glewFogCoordfvEXT = NULL; + +PFNGLFRAGMENTCOLORMATERIALEXTPROC __glewFragmentColorMaterialEXT = NULL; +PFNGLFRAGMENTLIGHTMODELFEXTPROC __glewFragmentLightModelfEXT = NULL; +PFNGLFRAGMENTLIGHTMODELFVEXTPROC __glewFragmentLightModelfvEXT = NULL; +PFNGLFRAGMENTLIGHTMODELIEXTPROC __glewFragmentLightModeliEXT = NULL; +PFNGLFRAGMENTLIGHTMODELIVEXTPROC __glewFragmentLightModelivEXT = NULL; +PFNGLFRAGMENTLIGHTFEXTPROC __glewFragmentLightfEXT = NULL; +PFNGLFRAGMENTLIGHTFVEXTPROC __glewFragmentLightfvEXT = NULL; +PFNGLFRAGMENTLIGHTIEXTPROC __glewFragmentLightiEXT = NULL; +PFNGLFRAGMENTLIGHTIVEXTPROC __glewFragmentLightivEXT = NULL; +PFNGLFRAGMENTMATERIALFEXTPROC __glewFragmentMaterialfEXT = NULL; +PFNGLFRAGMENTMATERIALFVEXTPROC __glewFragmentMaterialfvEXT = NULL; +PFNGLFRAGMENTMATERIALIEXTPROC __glewFragmentMaterialiEXT = NULL; +PFNGLFRAGMENTMATERIALIVEXTPROC __glewFragmentMaterialivEXT = NULL; +PFNGLGETFRAGMENTLIGHTFVEXTPROC __glewGetFragmentLightfvEXT = NULL; +PFNGLGETFRAGMENTLIGHTIVEXTPROC __glewGetFragmentLightivEXT = NULL; +PFNGLGETFRAGMENTMATERIALFVEXTPROC __glewGetFragmentMaterialfvEXT = NULL; +PFNGLGETFRAGMENTMATERIALIVEXTPROC __glewGetFragmentMaterialivEXT = NULL; +PFNGLLIGHTENVIEXTPROC __glewLightEnviEXT = NULL; + +PFNGLBLITFRAMEBUFFEREXTPROC __glewBlitFramebufferEXT = NULL; + +PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC __glewRenderbufferStorageMultisampleEXT = NULL; + +PFNGLBINDFRAMEBUFFEREXTPROC __glewBindFramebufferEXT = NULL; +PFNGLBINDRENDERBUFFEREXTPROC __glewBindRenderbufferEXT = NULL; +PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC __glewCheckFramebufferStatusEXT = NULL; +PFNGLDELETEFRAMEBUFFERSEXTPROC __glewDeleteFramebuffersEXT = NULL; +PFNGLDELETERENDERBUFFERSEXTPROC __glewDeleteRenderbuffersEXT = NULL; +PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC __glewFramebufferRenderbufferEXT = NULL; +PFNGLFRAMEBUFFERTEXTURE1DEXTPROC __glewFramebufferTexture1DEXT = NULL; +PFNGLFRAMEBUFFERTEXTURE2DEXTPROC __glewFramebufferTexture2DEXT = NULL; +PFNGLFRAMEBUFFERTEXTURE3DEXTPROC __glewFramebufferTexture3DEXT = NULL; +PFNGLGENFRAMEBUFFERSEXTPROC __glewGenFramebuffersEXT = NULL; +PFNGLGENRENDERBUFFERSEXTPROC __glewGenRenderbuffersEXT = NULL; +PFNGLGENERATEMIPMAPEXTPROC __glewGenerateMipmapEXT = NULL; +PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC __glewGetFramebufferAttachmentParameterivEXT = NULL; +PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC __glewGetRenderbufferParameterivEXT = NULL; +PFNGLISFRAMEBUFFEREXTPROC __glewIsFramebufferEXT = NULL; +PFNGLISRENDERBUFFEREXTPROC __glewIsRenderbufferEXT = NULL; +PFNGLRENDERBUFFERSTORAGEEXTPROC __glewRenderbufferStorageEXT = NULL; + +PFNGLFRAMEBUFFERTEXTUREEXTPROC __glewFramebufferTextureEXT = NULL; +PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC __glewFramebufferTextureFaceEXT = NULL; +PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC __glewFramebufferTextureLayerEXT = NULL; +PFNGLPROGRAMPARAMETERIEXTPROC __glewProgramParameteriEXT = NULL; + +PFNGLPROGRAMENVPARAMETERS4FVEXTPROC __glewProgramEnvParameters4fvEXT = NULL; +PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC __glewProgramLocalParameters4fvEXT = NULL; + +PFNGLBINDFRAGDATALOCATIONEXTPROC __glewBindFragDataLocationEXT = NULL; +PFNGLGETFRAGDATALOCATIONEXTPROC __glewGetFragDataLocationEXT = NULL; +PFNGLGETUNIFORMUIVEXTPROC __glewGetUniformuivEXT = NULL; +PFNGLGETVERTEXATTRIBIIVEXTPROC __glewGetVertexAttribIivEXT = NULL; +PFNGLGETVERTEXATTRIBIUIVEXTPROC __glewGetVertexAttribIuivEXT = NULL; +PFNGLUNIFORM1UIEXTPROC __glewUniform1uiEXT = NULL; +PFNGLUNIFORM1UIVEXTPROC __glewUniform1uivEXT = NULL; +PFNGLUNIFORM2UIEXTPROC __glewUniform2uiEXT = NULL; +PFNGLUNIFORM2UIVEXTPROC __glewUniform2uivEXT = NULL; +PFNGLUNIFORM3UIEXTPROC __glewUniform3uiEXT = NULL; +PFNGLUNIFORM3UIVEXTPROC __glewUniform3uivEXT = NULL; +PFNGLUNIFORM4UIEXTPROC __glewUniform4uiEXT = NULL; +PFNGLUNIFORM4UIVEXTPROC __glewUniform4uivEXT = NULL; +PFNGLVERTEXATTRIBI1IEXTPROC __glewVertexAttribI1iEXT = NULL; +PFNGLVERTEXATTRIBI1IVEXTPROC __glewVertexAttribI1ivEXT = NULL; +PFNGLVERTEXATTRIBI1UIEXTPROC __glewVertexAttribI1uiEXT = NULL; +PFNGLVERTEXATTRIBI1UIVEXTPROC __glewVertexAttribI1uivEXT = NULL; +PFNGLVERTEXATTRIBI2IEXTPROC __glewVertexAttribI2iEXT = NULL; +PFNGLVERTEXATTRIBI2IVEXTPROC __glewVertexAttribI2ivEXT = NULL; +PFNGLVERTEXATTRIBI2UIEXTPROC __glewVertexAttribI2uiEXT = NULL; +PFNGLVERTEXATTRIBI2UIVEXTPROC __glewVertexAttribI2uivEXT = NULL; +PFNGLVERTEXATTRIBI3IEXTPROC __glewVertexAttribI3iEXT = NULL; +PFNGLVERTEXATTRIBI3IVEXTPROC __glewVertexAttribI3ivEXT = NULL; +PFNGLVERTEXATTRIBI3UIEXTPROC __glewVertexAttribI3uiEXT = NULL; +PFNGLVERTEXATTRIBI3UIVEXTPROC __glewVertexAttribI3uivEXT = NULL; +PFNGLVERTEXATTRIBI4BVEXTPROC __glewVertexAttribI4bvEXT = NULL; +PFNGLVERTEXATTRIBI4IEXTPROC __glewVertexAttribI4iEXT = NULL; +PFNGLVERTEXATTRIBI4IVEXTPROC __glewVertexAttribI4ivEXT = NULL; +PFNGLVERTEXATTRIBI4SVEXTPROC __glewVertexAttribI4svEXT = NULL; +PFNGLVERTEXATTRIBI4UBVEXTPROC __glewVertexAttribI4ubvEXT = NULL; +PFNGLVERTEXATTRIBI4UIEXTPROC __glewVertexAttribI4uiEXT = NULL; +PFNGLVERTEXATTRIBI4UIVEXTPROC __glewVertexAttribI4uivEXT = NULL; +PFNGLVERTEXATTRIBI4USVEXTPROC __glewVertexAttribI4usvEXT = NULL; +PFNGLVERTEXATTRIBIPOINTEREXTPROC __glewVertexAttribIPointerEXT = NULL; + +PFNGLGETHISTOGRAMEXTPROC __glewGetHistogramEXT = NULL; +PFNGLGETHISTOGRAMPARAMETERFVEXTPROC __glewGetHistogramParameterfvEXT = NULL; +PFNGLGETHISTOGRAMPARAMETERIVEXTPROC __glewGetHistogramParameterivEXT = NULL; +PFNGLGETMINMAXEXTPROC __glewGetMinmaxEXT = NULL; +PFNGLGETMINMAXPARAMETERFVEXTPROC __glewGetMinmaxParameterfvEXT = NULL; +PFNGLGETMINMAXPARAMETERIVEXTPROC __glewGetMinmaxParameterivEXT = NULL; +PFNGLHISTOGRAMEXTPROC __glewHistogramEXT = NULL; +PFNGLMINMAXEXTPROC __glewMinmaxEXT = NULL; +PFNGLRESETHISTOGRAMEXTPROC __glewResetHistogramEXT = NULL; +PFNGLRESETMINMAXEXTPROC __glewResetMinmaxEXT = NULL; + +PFNGLINDEXFUNCEXTPROC __glewIndexFuncEXT = NULL; + +PFNGLINDEXMATERIALEXTPROC __glewIndexMaterialEXT = NULL; + +PFNGLAPPLYTEXTUREEXTPROC __glewApplyTextureEXT = NULL; +PFNGLTEXTURELIGHTEXTPROC __glewTextureLightEXT = NULL; +PFNGLTEXTUREMATERIALEXTPROC __glewTextureMaterialEXT = NULL; + +PFNGLMULTIDRAWARRAYSEXTPROC __glewMultiDrawArraysEXT = NULL; +PFNGLMULTIDRAWELEMENTSEXTPROC __glewMultiDrawElementsEXT = NULL; + +PFNGLSAMPLEMASKEXTPROC __glewSampleMaskEXT = NULL; +PFNGLSAMPLEPATTERNEXTPROC __glewSamplePatternEXT = NULL; + +PFNGLCOLORTABLEEXTPROC __glewColorTableEXT = NULL; +PFNGLGETCOLORTABLEEXTPROC __glewGetColorTableEXT = NULL; +PFNGLGETCOLORTABLEPARAMETERFVEXTPROC __glewGetColorTableParameterfvEXT = NULL; +PFNGLGETCOLORTABLEPARAMETERIVEXTPROC __glewGetColorTableParameterivEXT = NULL; + +PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC __glewGetPixelTransformParameterfvEXT = NULL; +PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC __glewGetPixelTransformParameterivEXT = NULL; +PFNGLPIXELTRANSFORMPARAMETERFEXTPROC __glewPixelTransformParameterfEXT = NULL; +PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC __glewPixelTransformParameterfvEXT = NULL; +PFNGLPIXELTRANSFORMPARAMETERIEXTPROC __glewPixelTransformParameteriEXT = NULL; +PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC __glewPixelTransformParameterivEXT = NULL; + +PFNGLPOINTPARAMETERFEXTPROC __glewPointParameterfEXT = NULL; +PFNGLPOINTPARAMETERFVEXTPROC __glewPointParameterfvEXT = NULL; + +PFNGLPOLYGONOFFSETEXTPROC __glewPolygonOffsetEXT = NULL; + +PFNGLBEGINSCENEEXTPROC __glewBeginSceneEXT = NULL; +PFNGLENDSCENEEXTPROC __glewEndSceneEXT = NULL; + +PFNGLSECONDARYCOLOR3BEXTPROC __glewSecondaryColor3bEXT = NULL; +PFNGLSECONDARYCOLOR3BVEXTPROC __glewSecondaryColor3bvEXT = NULL; +PFNGLSECONDARYCOLOR3DEXTPROC __glewSecondaryColor3dEXT = NULL; +PFNGLSECONDARYCOLOR3DVEXTPROC __glewSecondaryColor3dvEXT = NULL; +PFNGLSECONDARYCOLOR3FEXTPROC __glewSecondaryColor3fEXT = NULL; +PFNGLSECONDARYCOLOR3FVEXTPROC __glewSecondaryColor3fvEXT = NULL; +PFNGLSECONDARYCOLOR3IEXTPROC __glewSecondaryColor3iEXT = NULL; +PFNGLSECONDARYCOLOR3IVEXTPROC __glewSecondaryColor3ivEXT = NULL; +PFNGLSECONDARYCOLOR3SEXTPROC __glewSecondaryColor3sEXT = NULL; +PFNGLSECONDARYCOLOR3SVEXTPROC __glewSecondaryColor3svEXT = NULL; +PFNGLSECONDARYCOLOR3UBEXTPROC __glewSecondaryColor3ubEXT = NULL; +PFNGLSECONDARYCOLOR3UBVEXTPROC __glewSecondaryColor3ubvEXT = NULL; +PFNGLSECONDARYCOLOR3UIEXTPROC __glewSecondaryColor3uiEXT = NULL; +PFNGLSECONDARYCOLOR3UIVEXTPROC __glewSecondaryColor3uivEXT = NULL; +PFNGLSECONDARYCOLOR3USEXTPROC __glewSecondaryColor3usEXT = NULL; +PFNGLSECONDARYCOLOR3USVEXTPROC __glewSecondaryColor3usvEXT = NULL; +PFNGLSECONDARYCOLORPOINTEREXTPROC __glewSecondaryColorPointerEXT = NULL; + +PFNGLACTIVESTENCILFACEEXTPROC __glewActiveStencilFaceEXT = NULL; + +PFNGLTEXSUBIMAGE1DEXTPROC __glewTexSubImage1DEXT = NULL; +PFNGLTEXSUBIMAGE2DEXTPROC __glewTexSubImage2DEXT = NULL; +PFNGLTEXSUBIMAGE3DEXTPROC __glewTexSubImage3DEXT = NULL; + +PFNGLTEXIMAGE3DEXTPROC __glewTexImage3DEXT = NULL; + +PFNGLTEXBUFFEREXTPROC __glewTexBufferEXT = NULL; + +PFNGLCLEARCOLORIIEXTPROC __glewClearColorIiEXT = NULL; +PFNGLCLEARCOLORIUIEXTPROC __glewClearColorIuiEXT = NULL; +PFNGLGETTEXPARAMETERIIVEXTPROC __glewGetTexParameterIivEXT = NULL; +PFNGLGETTEXPARAMETERIUIVEXTPROC __glewGetTexParameterIuivEXT = NULL; +PFNGLTEXPARAMETERIIVEXTPROC __glewTexParameterIivEXT = NULL; +PFNGLTEXPARAMETERIUIVEXTPROC __glewTexParameterIuivEXT = NULL; + +PFNGLARETEXTURESRESIDENTEXTPROC __glewAreTexturesResidentEXT = NULL; +PFNGLBINDTEXTUREEXTPROC __glewBindTextureEXT = NULL; +PFNGLDELETETEXTURESEXTPROC __glewDeleteTexturesEXT = NULL; +PFNGLGENTEXTURESEXTPROC __glewGenTexturesEXT = NULL; +PFNGLISTEXTUREEXTPROC __glewIsTextureEXT = NULL; +PFNGLPRIORITIZETEXTURESEXTPROC __glewPrioritizeTexturesEXT = NULL; + +PFNGLTEXTURENORMALEXTPROC __glewTextureNormalEXT = NULL; + +PFNGLGETQUERYOBJECTI64VEXTPROC __glewGetQueryObjecti64vEXT = NULL; +PFNGLGETQUERYOBJECTUI64VEXTPROC __glewGetQueryObjectui64vEXT = NULL; + +PFNGLBEGINTRANSFORMFEEDBACKEXTPROC __glewBeginTransformFeedbackEXT = NULL; +PFNGLBINDBUFFERBASEEXTPROC __glewBindBufferBaseEXT = NULL; +PFNGLBINDBUFFEROFFSETEXTPROC __glewBindBufferOffsetEXT = NULL; +PFNGLBINDBUFFERRANGEEXTPROC __glewBindBufferRangeEXT = NULL; +PFNGLENDTRANSFORMFEEDBACKEXTPROC __glewEndTransformFeedbackEXT = NULL; +PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC __glewGetTransformFeedbackVaryingEXT = NULL; +PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC __glewTransformFeedbackVaryingsEXT = NULL; + +PFNGLARRAYELEMENTEXTPROC __glewArrayElementEXT = NULL; +PFNGLCOLORPOINTEREXTPROC __glewColorPointerEXT = NULL; +PFNGLDRAWARRAYSEXTPROC __glewDrawArraysEXT = NULL; +PFNGLEDGEFLAGPOINTEREXTPROC __glewEdgeFlagPointerEXT = NULL; +PFNGLGETPOINTERVEXTPROC __glewGetPointervEXT = NULL; +PFNGLINDEXPOINTEREXTPROC __glewIndexPointerEXT = NULL; +PFNGLNORMALPOINTEREXTPROC __glewNormalPointerEXT = NULL; +PFNGLTEXCOORDPOINTEREXTPROC __glewTexCoordPointerEXT = NULL; +PFNGLVERTEXPOINTEREXTPROC __glewVertexPointerEXT = NULL; + +PFNGLBEGINVERTEXSHADEREXTPROC __glewBeginVertexShaderEXT = NULL; +PFNGLBINDLIGHTPARAMETEREXTPROC __glewBindLightParameterEXT = NULL; +PFNGLBINDMATERIALPARAMETEREXTPROC __glewBindMaterialParameterEXT = NULL; +PFNGLBINDPARAMETEREXTPROC __glewBindParameterEXT = NULL; +PFNGLBINDTEXGENPARAMETEREXTPROC __glewBindTexGenParameterEXT = NULL; +PFNGLBINDTEXTUREUNITPARAMETEREXTPROC __glewBindTextureUnitParameterEXT = NULL; +PFNGLBINDVERTEXSHADEREXTPROC __glewBindVertexShaderEXT = NULL; +PFNGLDELETEVERTEXSHADEREXTPROC __glewDeleteVertexShaderEXT = NULL; +PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC __glewDisableVariantClientStateEXT = NULL; +PFNGLENABLEVARIANTCLIENTSTATEEXTPROC __glewEnableVariantClientStateEXT = NULL; +PFNGLENDVERTEXSHADEREXTPROC __glewEndVertexShaderEXT = NULL; +PFNGLEXTRACTCOMPONENTEXTPROC __glewExtractComponentEXT = NULL; +PFNGLGENSYMBOLSEXTPROC __glewGenSymbolsEXT = NULL; +PFNGLGENVERTEXSHADERSEXTPROC __glewGenVertexShadersEXT = NULL; +PFNGLGETINVARIANTBOOLEANVEXTPROC __glewGetInvariantBooleanvEXT = NULL; +PFNGLGETINVARIANTFLOATVEXTPROC __glewGetInvariantFloatvEXT = NULL; +PFNGLGETINVARIANTINTEGERVEXTPROC __glewGetInvariantIntegervEXT = NULL; +PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC __glewGetLocalConstantBooleanvEXT = NULL; +PFNGLGETLOCALCONSTANTFLOATVEXTPROC __glewGetLocalConstantFloatvEXT = NULL; +PFNGLGETLOCALCONSTANTINTEGERVEXTPROC __glewGetLocalConstantIntegervEXT = NULL; +PFNGLGETVARIANTBOOLEANVEXTPROC __glewGetVariantBooleanvEXT = NULL; +PFNGLGETVARIANTFLOATVEXTPROC __glewGetVariantFloatvEXT = NULL; +PFNGLGETVARIANTINTEGERVEXTPROC __glewGetVariantIntegervEXT = NULL; +PFNGLGETVARIANTPOINTERVEXTPROC __glewGetVariantPointervEXT = NULL; +PFNGLINSERTCOMPONENTEXTPROC __glewInsertComponentEXT = NULL; +PFNGLISVARIANTENABLEDEXTPROC __glewIsVariantEnabledEXT = NULL; +PFNGLSETINVARIANTEXTPROC __glewSetInvariantEXT = NULL; +PFNGLSETLOCALCONSTANTEXTPROC __glewSetLocalConstantEXT = NULL; +PFNGLSHADEROP1EXTPROC __glewShaderOp1EXT = NULL; +PFNGLSHADEROP2EXTPROC __glewShaderOp2EXT = NULL; +PFNGLSHADEROP3EXTPROC __glewShaderOp3EXT = NULL; +PFNGLSWIZZLEEXTPROC __glewSwizzleEXT = NULL; +PFNGLVARIANTPOINTEREXTPROC __glewVariantPointerEXT = NULL; +PFNGLVARIANTBVEXTPROC __glewVariantbvEXT = NULL; +PFNGLVARIANTDVEXTPROC __glewVariantdvEXT = NULL; +PFNGLVARIANTFVEXTPROC __glewVariantfvEXT = NULL; +PFNGLVARIANTIVEXTPROC __glewVariantivEXT = NULL; +PFNGLVARIANTSVEXTPROC __glewVariantsvEXT = NULL; +PFNGLVARIANTUBVEXTPROC __glewVariantubvEXT = NULL; +PFNGLVARIANTUIVEXTPROC __glewVariantuivEXT = NULL; +PFNGLVARIANTUSVEXTPROC __glewVariantusvEXT = NULL; +PFNGLWRITEMASKEXTPROC __glewWriteMaskEXT = NULL; + +PFNGLVERTEXWEIGHTPOINTEREXTPROC __glewVertexWeightPointerEXT = NULL; +PFNGLVERTEXWEIGHTFEXTPROC __glewVertexWeightfEXT = NULL; +PFNGLVERTEXWEIGHTFVEXTPROC __glewVertexWeightfvEXT = NULL; + +PFNGLFRAMETERMINATORGREMEDYPROC __glewFrameTerminatorGREMEDY = NULL; + +PFNGLSTRINGMARKERGREMEDYPROC __glewStringMarkerGREMEDY = NULL; + +PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC __glewGetImageTransformParameterfvHP = NULL; +PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC __glewGetImageTransformParameterivHP = NULL; +PFNGLIMAGETRANSFORMPARAMETERFHPPROC __glewImageTransformParameterfHP = NULL; +PFNGLIMAGETRANSFORMPARAMETERFVHPPROC __glewImageTransformParameterfvHP = NULL; +PFNGLIMAGETRANSFORMPARAMETERIHPPROC __glewImageTransformParameteriHP = NULL; +PFNGLIMAGETRANSFORMPARAMETERIVHPPROC __glewImageTransformParameterivHP = NULL; + +PFNGLMULTIMODEDRAWARRAYSIBMPROC __glewMultiModeDrawArraysIBM = NULL; +PFNGLMULTIMODEDRAWELEMENTSIBMPROC __glewMultiModeDrawElementsIBM = NULL; + +PFNGLCOLORPOINTERLISTIBMPROC __glewColorPointerListIBM = NULL; +PFNGLEDGEFLAGPOINTERLISTIBMPROC __glewEdgeFlagPointerListIBM = NULL; +PFNGLFOGCOORDPOINTERLISTIBMPROC __glewFogCoordPointerListIBM = NULL; +PFNGLINDEXPOINTERLISTIBMPROC __glewIndexPointerListIBM = NULL; +PFNGLNORMALPOINTERLISTIBMPROC __glewNormalPointerListIBM = NULL; +PFNGLSECONDARYCOLORPOINTERLISTIBMPROC __glewSecondaryColorPointerListIBM = NULL; +PFNGLTEXCOORDPOINTERLISTIBMPROC __glewTexCoordPointerListIBM = NULL; +PFNGLVERTEXPOINTERLISTIBMPROC __glewVertexPointerListIBM = NULL; + +PFNGLCOLORPOINTERVINTELPROC __glewColorPointervINTEL = NULL; +PFNGLNORMALPOINTERVINTELPROC __glewNormalPointervINTEL = NULL; +PFNGLTEXCOORDPOINTERVINTELPROC __glewTexCoordPointervINTEL = NULL; +PFNGLVERTEXPOINTERVINTELPROC __glewVertexPointervINTEL = NULL; + +PFNGLTEXSCISSORFUNCINTELPROC __glewTexScissorFuncINTEL = NULL; +PFNGLTEXSCISSORINTELPROC __glewTexScissorINTEL = NULL; + +PFNGLBUFFERREGIONENABLEDEXTPROC __glewBufferRegionEnabledEXT = NULL; +PFNGLDELETEBUFFERREGIONEXTPROC __glewDeleteBufferRegionEXT = NULL; +PFNGLDRAWBUFFERREGIONEXTPROC __glewDrawBufferRegionEXT = NULL; +PFNGLNEWBUFFERREGIONEXTPROC __glewNewBufferRegionEXT = NULL; +PFNGLREADBUFFERREGIONEXTPROC __glewReadBufferRegionEXT = NULL; + +PFNGLRESIZEBUFFERSMESAPROC __glewResizeBuffersMESA = NULL; + +PFNGLWINDOWPOS2DMESAPROC __glewWindowPos2dMESA = NULL; +PFNGLWINDOWPOS2DVMESAPROC __glewWindowPos2dvMESA = NULL; +PFNGLWINDOWPOS2FMESAPROC __glewWindowPos2fMESA = NULL; +PFNGLWINDOWPOS2FVMESAPROC __glewWindowPos2fvMESA = NULL; +PFNGLWINDOWPOS2IMESAPROC __glewWindowPos2iMESA = NULL; +PFNGLWINDOWPOS2IVMESAPROC __glewWindowPos2ivMESA = NULL; +PFNGLWINDOWPOS2SMESAPROC __glewWindowPos2sMESA = NULL; +PFNGLWINDOWPOS2SVMESAPROC __glewWindowPos2svMESA = NULL; +PFNGLWINDOWPOS3DMESAPROC __glewWindowPos3dMESA = NULL; +PFNGLWINDOWPOS3DVMESAPROC __glewWindowPos3dvMESA = NULL; +PFNGLWINDOWPOS3FMESAPROC __glewWindowPos3fMESA = NULL; +PFNGLWINDOWPOS3FVMESAPROC __glewWindowPos3fvMESA = NULL; +PFNGLWINDOWPOS3IMESAPROC __glewWindowPos3iMESA = NULL; +PFNGLWINDOWPOS3IVMESAPROC __glewWindowPos3ivMESA = NULL; +PFNGLWINDOWPOS3SMESAPROC __glewWindowPos3sMESA = NULL; +PFNGLWINDOWPOS3SVMESAPROC __glewWindowPos3svMESA = NULL; +PFNGLWINDOWPOS4DMESAPROC __glewWindowPos4dMESA = NULL; +PFNGLWINDOWPOS4DVMESAPROC __glewWindowPos4dvMESA = NULL; +PFNGLWINDOWPOS4FMESAPROC __glewWindowPos4fMESA = NULL; +PFNGLWINDOWPOS4FVMESAPROC __glewWindowPos4fvMESA = NULL; +PFNGLWINDOWPOS4IMESAPROC __glewWindowPos4iMESA = NULL; +PFNGLWINDOWPOS4IVMESAPROC __glewWindowPos4ivMESA = NULL; +PFNGLWINDOWPOS4SMESAPROC __glewWindowPos4sMESA = NULL; +PFNGLWINDOWPOS4SVMESAPROC __glewWindowPos4svMESA = NULL; + +PFNGLBEGINCONDITIONALRENDERNVPROC __glewBeginConditionalRenderNV = NULL; +PFNGLENDCONDITIONALRENDERNVPROC __glewEndConditionalRenderNV = NULL; + +PFNGLCLEARDEPTHDNVPROC __glewClearDepthdNV = NULL; +PFNGLDEPTHBOUNDSDNVPROC __glewDepthBoundsdNV = NULL; +PFNGLDEPTHRANGEDNVPROC __glewDepthRangedNV = NULL; + +PFNGLEVALMAPSNVPROC __glewEvalMapsNV = NULL; +PFNGLGETMAPATTRIBPARAMETERFVNVPROC __glewGetMapAttribParameterfvNV = NULL; +PFNGLGETMAPATTRIBPARAMETERIVNVPROC __glewGetMapAttribParameterivNV = NULL; +PFNGLGETMAPCONTROLPOINTSNVPROC __glewGetMapControlPointsNV = NULL; +PFNGLGETMAPPARAMETERFVNVPROC __glewGetMapParameterfvNV = NULL; +PFNGLGETMAPPARAMETERIVNVPROC __glewGetMapParameterivNV = NULL; +PFNGLMAPCONTROLPOINTSNVPROC __glewMapControlPointsNV = NULL; +PFNGLMAPPARAMETERFVNVPROC __glewMapParameterfvNV = NULL; +PFNGLMAPPARAMETERIVNVPROC __glewMapParameterivNV = NULL; + +PFNGLGETMULTISAMPLEFVNVPROC __glewGetMultisamplefvNV = NULL; +PFNGLSAMPLEMASKINDEXEDNVPROC __glewSampleMaskIndexedNV = NULL; +PFNGLTEXRENDERBUFFERNVPROC __glewTexRenderbufferNV = NULL; + +PFNGLDELETEFENCESNVPROC __glewDeleteFencesNV = NULL; +PFNGLFINISHFENCENVPROC __glewFinishFenceNV = NULL; +PFNGLGENFENCESNVPROC __glewGenFencesNV = NULL; +PFNGLGETFENCEIVNVPROC __glewGetFenceivNV = NULL; +PFNGLISFENCENVPROC __glewIsFenceNV = NULL; +PFNGLSETFENCENVPROC __glewSetFenceNV = NULL; +PFNGLTESTFENCENVPROC __glewTestFenceNV = NULL; + +PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC __glewGetProgramNamedParameterdvNV = NULL; +PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC __glewGetProgramNamedParameterfvNV = NULL; +PFNGLPROGRAMNAMEDPARAMETER4DNVPROC __glewProgramNamedParameter4dNV = NULL; +PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC __glewProgramNamedParameter4dvNV = NULL; +PFNGLPROGRAMNAMEDPARAMETER4FNVPROC __glewProgramNamedParameter4fNV = NULL; +PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC __glewProgramNamedParameter4fvNV = NULL; + +PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC __glewRenderbufferStorageMultisampleCoverageNV = NULL; + +PFNGLPROGRAMVERTEXLIMITNVPROC __glewProgramVertexLimitNV = NULL; + +PFNGLPROGRAMENVPARAMETERI4INVPROC __glewProgramEnvParameterI4iNV = NULL; +PFNGLPROGRAMENVPARAMETERI4IVNVPROC __glewProgramEnvParameterI4ivNV = NULL; +PFNGLPROGRAMENVPARAMETERI4UINVPROC __glewProgramEnvParameterI4uiNV = NULL; +PFNGLPROGRAMENVPARAMETERI4UIVNVPROC __glewProgramEnvParameterI4uivNV = NULL; +PFNGLPROGRAMENVPARAMETERSI4IVNVPROC __glewProgramEnvParametersI4ivNV = NULL; +PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC __glewProgramEnvParametersI4uivNV = NULL; +PFNGLPROGRAMLOCALPARAMETERI4INVPROC __glewProgramLocalParameterI4iNV = NULL; +PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC __glewProgramLocalParameterI4ivNV = NULL; +PFNGLPROGRAMLOCALPARAMETERI4UINVPROC __glewProgramLocalParameterI4uiNV = NULL; +PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC __glewProgramLocalParameterI4uivNV = NULL; +PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC __glewProgramLocalParametersI4ivNV = NULL; +PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC __glewProgramLocalParametersI4uivNV = NULL; + +PFNGLCOLOR3HNVPROC __glewColor3hNV = NULL; +PFNGLCOLOR3HVNVPROC __glewColor3hvNV = NULL; +PFNGLCOLOR4HNVPROC __glewColor4hNV = NULL; +PFNGLCOLOR4HVNVPROC __glewColor4hvNV = NULL; +PFNGLFOGCOORDHNVPROC __glewFogCoordhNV = NULL; +PFNGLFOGCOORDHVNVPROC __glewFogCoordhvNV = NULL; +PFNGLMULTITEXCOORD1HNVPROC __glewMultiTexCoord1hNV = NULL; +PFNGLMULTITEXCOORD1HVNVPROC __glewMultiTexCoord1hvNV = NULL; +PFNGLMULTITEXCOORD2HNVPROC __glewMultiTexCoord2hNV = NULL; +PFNGLMULTITEXCOORD2HVNVPROC __glewMultiTexCoord2hvNV = NULL; +PFNGLMULTITEXCOORD3HNVPROC __glewMultiTexCoord3hNV = NULL; +PFNGLMULTITEXCOORD3HVNVPROC __glewMultiTexCoord3hvNV = NULL; +PFNGLMULTITEXCOORD4HNVPROC __glewMultiTexCoord4hNV = NULL; +PFNGLMULTITEXCOORD4HVNVPROC __glewMultiTexCoord4hvNV = NULL; +PFNGLNORMAL3HNVPROC __glewNormal3hNV = NULL; +PFNGLNORMAL3HVNVPROC __glewNormal3hvNV = NULL; +PFNGLSECONDARYCOLOR3HNVPROC __glewSecondaryColor3hNV = NULL; +PFNGLSECONDARYCOLOR3HVNVPROC __glewSecondaryColor3hvNV = NULL; +PFNGLTEXCOORD1HNVPROC __glewTexCoord1hNV = NULL; +PFNGLTEXCOORD1HVNVPROC __glewTexCoord1hvNV = NULL; +PFNGLTEXCOORD2HNVPROC __glewTexCoord2hNV = NULL; +PFNGLTEXCOORD2HVNVPROC __glewTexCoord2hvNV = NULL; +PFNGLTEXCOORD3HNVPROC __glewTexCoord3hNV = NULL; +PFNGLTEXCOORD3HVNVPROC __glewTexCoord3hvNV = NULL; +PFNGLTEXCOORD4HNVPROC __glewTexCoord4hNV = NULL; +PFNGLTEXCOORD4HVNVPROC __glewTexCoord4hvNV = NULL; +PFNGLVERTEX2HNVPROC __glewVertex2hNV = NULL; +PFNGLVERTEX2HVNVPROC __glewVertex2hvNV = NULL; +PFNGLVERTEX3HNVPROC __glewVertex3hNV = NULL; +PFNGLVERTEX3HVNVPROC __glewVertex3hvNV = NULL; +PFNGLVERTEX4HNVPROC __glewVertex4hNV = NULL; +PFNGLVERTEX4HVNVPROC __glewVertex4hvNV = NULL; +PFNGLVERTEXATTRIB1HNVPROC __glewVertexAttrib1hNV = NULL; +PFNGLVERTEXATTRIB1HVNVPROC __glewVertexAttrib1hvNV = NULL; +PFNGLVERTEXATTRIB2HNVPROC __glewVertexAttrib2hNV = NULL; +PFNGLVERTEXATTRIB2HVNVPROC __glewVertexAttrib2hvNV = NULL; +PFNGLVERTEXATTRIB3HNVPROC __glewVertexAttrib3hNV = NULL; +PFNGLVERTEXATTRIB3HVNVPROC __glewVertexAttrib3hvNV = NULL; +PFNGLVERTEXATTRIB4HNVPROC __glewVertexAttrib4hNV = NULL; +PFNGLVERTEXATTRIB4HVNVPROC __glewVertexAttrib4hvNV = NULL; +PFNGLVERTEXATTRIBS1HVNVPROC __glewVertexAttribs1hvNV = NULL; +PFNGLVERTEXATTRIBS2HVNVPROC __glewVertexAttribs2hvNV = NULL; +PFNGLVERTEXATTRIBS3HVNVPROC __glewVertexAttribs3hvNV = NULL; +PFNGLVERTEXATTRIBS4HVNVPROC __glewVertexAttribs4hvNV = NULL; +PFNGLVERTEXWEIGHTHNVPROC __glewVertexWeighthNV = NULL; +PFNGLVERTEXWEIGHTHVNVPROC __glewVertexWeighthvNV = NULL; + +PFNGLBEGINOCCLUSIONQUERYNVPROC __glewBeginOcclusionQueryNV = NULL; +PFNGLDELETEOCCLUSIONQUERIESNVPROC __glewDeleteOcclusionQueriesNV = NULL; +PFNGLENDOCCLUSIONQUERYNVPROC __glewEndOcclusionQueryNV = NULL; +PFNGLGENOCCLUSIONQUERIESNVPROC __glewGenOcclusionQueriesNV = NULL; +PFNGLGETOCCLUSIONQUERYIVNVPROC __glewGetOcclusionQueryivNV = NULL; +PFNGLGETOCCLUSIONQUERYUIVNVPROC __glewGetOcclusionQueryuivNV = NULL; +PFNGLISOCCLUSIONQUERYNVPROC __glewIsOcclusionQueryNV = NULL; + +PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC __glewProgramBufferParametersIivNV = NULL; +PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC __glewProgramBufferParametersIuivNV = NULL; +PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC __glewProgramBufferParametersfvNV = NULL; + +PFNGLFLUSHPIXELDATARANGENVPROC __glewFlushPixelDataRangeNV = NULL; +PFNGLPIXELDATARANGENVPROC __glewPixelDataRangeNV = NULL; + +PFNGLPOINTPARAMETERINVPROC __glewPointParameteriNV = NULL; +PFNGLPOINTPARAMETERIVNVPROC __glewPointParameterivNV = NULL; + +PFNGLGETVIDEOI64VNVPROC __glewGetVideoi64vNV = NULL; +PFNGLGETVIDEOIVNVPROC __glewGetVideoivNV = NULL; +PFNGLGETVIDEOUI64VNVPROC __glewGetVideoui64vNV = NULL; +PFNGLGETVIDEOUIVNVPROC __glewGetVideouivNV = NULL; +PFNGLPRESENTFRAMEDUALFILLNVPROC __glewPresentFrameDualFillNV = NULL; +PFNGLPRESENTFRAMEKEYEDNVPROC __glewPresentFrameKeyedNV = NULL; +PFNGLVIDEOPARAMETERIVNVPROC __glewVideoParameterivNV = NULL; + +PFNGLPRIMITIVERESTARTINDEXNVPROC __glewPrimitiveRestartIndexNV = NULL; +PFNGLPRIMITIVERESTARTNVPROC __glewPrimitiveRestartNV = NULL; + +PFNGLCOMBINERINPUTNVPROC __glewCombinerInputNV = NULL; +PFNGLCOMBINEROUTPUTNVPROC __glewCombinerOutputNV = NULL; +PFNGLCOMBINERPARAMETERFNVPROC __glewCombinerParameterfNV = NULL; +PFNGLCOMBINERPARAMETERFVNVPROC __glewCombinerParameterfvNV = NULL; +PFNGLCOMBINERPARAMETERINVPROC __glewCombinerParameteriNV = NULL; +PFNGLCOMBINERPARAMETERIVNVPROC __glewCombinerParameterivNV = NULL; +PFNGLFINALCOMBINERINPUTNVPROC __glewFinalCombinerInputNV = NULL; +PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC __glewGetCombinerInputParameterfvNV = NULL; +PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC __glewGetCombinerInputParameterivNV = NULL; +PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC __glewGetCombinerOutputParameterfvNV = NULL; +PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC __glewGetCombinerOutputParameterivNV = NULL; +PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC __glewGetFinalCombinerInputParameterfvNV = NULL; +PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC __glewGetFinalCombinerInputParameterivNV = NULL; + +PFNGLCOMBINERSTAGEPARAMETERFVNVPROC __glewCombinerStageParameterfvNV = NULL; +PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC __glewGetCombinerStageParameterfvNV = NULL; + +PFNGLACTIVEVARYINGNVPROC __glewActiveVaryingNV = NULL; +PFNGLBEGINTRANSFORMFEEDBACKNVPROC __glewBeginTransformFeedbackNV = NULL; +PFNGLBINDBUFFERBASENVPROC __glewBindBufferBaseNV = NULL; +PFNGLBINDBUFFEROFFSETNVPROC __glewBindBufferOffsetNV = NULL; +PFNGLBINDBUFFERRANGENVPROC __glewBindBufferRangeNV = NULL; +PFNGLENDTRANSFORMFEEDBACKNVPROC __glewEndTransformFeedbackNV = NULL; +PFNGLGETACTIVEVARYINGNVPROC __glewGetActiveVaryingNV = NULL; +PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC __glewGetTransformFeedbackVaryingNV = NULL; +PFNGLGETVARYINGLOCATIONNVPROC __glewGetVaryingLocationNV = NULL; +PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC __glewTransformFeedbackAttribsNV = NULL; +PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC __glewTransformFeedbackVaryingsNV = NULL; + +PFNGLFLUSHVERTEXARRAYRANGENVPROC __glewFlushVertexArrayRangeNV = NULL; +PFNGLVERTEXARRAYRANGENVPROC __glewVertexArrayRangeNV = NULL; + +PFNGLAREPROGRAMSRESIDENTNVPROC __glewAreProgramsResidentNV = NULL; +PFNGLBINDPROGRAMNVPROC __glewBindProgramNV = NULL; +PFNGLDELETEPROGRAMSNVPROC __glewDeleteProgramsNV = NULL; +PFNGLEXECUTEPROGRAMNVPROC __glewExecuteProgramNV = NULL; +PFNGLGENPROGRAMSNVPROC __glewGenProgramsNV = NULL; +PFNGLGETPROGRAMPARAMETERDVNVPROC __glewGetProgramParameterdvNV = NULL; +PFNGLGETPROGRAMPARAMETERFVNVPROC __glewGetProgramParameterfvNV = NULL; +PFNGLGETPROGRAMSTRINGNVPROC __glewGetProgramStringNV = NULL; +PFNGLGETPROGRAMIVNVPROC __glewGetProgramivNV = NULL; +PFNGLGETTRACKMATRIXIVNVPROC __glewGetTrackMatrixivNV = NULL; +PFNGLGETVERTEXATTRIBPOINTERVNVPROC __glewGetVertexAttribPointervNV = NULL; +PFNGLGETVERTEXATTRIBDVNVPROC __glewGetVertexAttribdvNV = NULL; +PFNGLGETVERTEXATTRIBFVNVPROC __glewGetVertexAttribfvNV = NULL; +PFNGLGETVERTEXATTRIBIVNVPROC __glewGetVertexAttribivNV = NULL; +PFNGLISPROGRAMNVPROC __glewIsProgramNV = NULL; +PFNGLLOADPROGRAMNVPROC __glewLoadProgramNV = NULL; +PFNGLPROGRAMPARAMETER4DNVPROC __glewProgramParameter4dNV = NULL; +PFNGLPROGRAMPARAMETER4DVNVPROC __glewProgramParameter4dvNV = NULL; +PFNGLPROGRAMPARAMETER4FNVPROC __glewProgramParameter4fNV = NULL; +PFNGLPROGRAMPARAMETER4FVNVPROC __glewProgramParameter4fvNV = NULL; +PFNGLPROGRAMPARAMETERS4DVNVPROC __glewProgramParameters4dvNV = NULL; +PFNGLPROGRAMPARAMETERS4FVNVPROC __glewProgramParameters4fvNV = NULL; +PFNGLREQUESTRESIDENTPROGRAMSNVPROC __glewRequestResidentProgramsNV = NULL; +PFNGLTRACKMATRIXNVPROC __glewTrackMatrixNV = NULL; +PFNGLVERTEXATTRIB1DNVPROC __glewVertexAttrib1dNV = NULL; +PFNGLVERTEXATTRIB1DVNVPROC __glewVertexAttrib1dvNV = NULL; +PFNGLVERTEXATTRIB1FNVPROC __glewVertexAttrib1fNV = NULL; +PFNGLVERTEXATTRIB1FVNVPROC __glewVertexAttrib1fvNV = NULL; +PFNGLVERTEXATTRIB1SNVPROC __glewVertexAttrib1sNV = NULL; +PFNGLVERTEXATTRIB1SVNVPROC __glewVertexAttrib1svNV = NULL; +PFNGLVERTEXATTRIB2DNVPROC __glewVertexAttrib2dNV = NULL; +PFNGLVERTEXATTRIB2DVNVPROC __glewVertexAttrib2dvNV = NULL; +PFNGLVERTEXATTRIB2FNVPROC __glewVertexAttrib2fNV = NULL; +PFNGLVERTEXATTRIB2FVNVPROC __glewVertexAttrib2fvNV = NULL; +PFNGLVERTEXATTRIB2SNVPROC __glewVertexAttrib2sNV = NULL; +PFNGLVERTEXATTRIB2SVNVPROC __glewVertexAttrib2svNV = NULL; +PFNGLVERTEXATTRIB3DNVPROC __glewVertexAttrib3dNV = NULL; +PFNGLVERTEXATTRIB3DVNVPROC __glewVertexAttrib3dvNV = NULL; +PFNGLVERTEXATTRIB3FNVPROC __glewVertexAttrib3fNV = NULL; +PFNGLVERTEXATTRIB3FVNVPROC __glewVertexAttrib3fvNV = NULL; +PFNGLVERTEXATTRIB3SNVPROC __glewVertexAttrib3sNV = NULL; +PFNGLVERTEXATTRIB3SVNVPROC __glewVertexAttrib3svNV = NULL; +PFNGLVERTEXATTRIB4DNVPROC __glewVertexAttrib4dNV = NULL; +PFNGLVERTEXATTRIB4DVNVPROC __glewVertexAttrib4dvNV = NULL; +PFNGLVERTEXATTRIB4FNVPROC __glewVertexAttrib4fNV = NULL; +PFNGLVERTEXATTRIB4FVNVPROC __glewVertexAttrib4fvNV = NULL; +PFNGLVERTEXATTRIB4SNVPROC __glewVertexAttrib4sNV = NULL; +PFNGLVERTEXATTRIB4SVNVPROC __glewVertexAttrib4svNV = NULL; +PFNGLVERTEXATTRIB4UBNVPROC __glewVertexAttrib4ubNV = NULL; +PFNGLVERTEXATTRIB4UBVNVPROC __glewVertexAttrib4ubvNV = NULL; +PFNGLVERTEXATTRIBPOINTERNVPROC __glewVertexAttribPointerNV = NULL; +PFNGLVERTEXATTRIBS1DVNVPROC __glewVertexAttribs1dvNV = NULL; +PFNGLVERTEXATTRIBS1FVNVPROC __glewVertexAttribs1fvNV = NULL; +PFNGLVERTEXATTRIBS1SVNVPROC __glewVertexAttribs1svNV = NULL; +PFNGLVERTEXATTRIBS2DVNVPROC __glewVertexAttribs2dvNV = NULL; +PFNGLVERTEXATTRIBS2FVNVPROC __glewVertexAttribs2fvNV = NULL; +PFNGLVERTEXATTRIBS2SVNVPROC __glewVertexAttribs2svNV = NULL; +PFNGLVERTEXATTRIBS3DVNVPROC __glewVertexAttribs3dvNV = NULL; +PFNGLVERTEXATTRIBS3FVNVPROC __glewVertexAttribs3fvNV = NULL; +PFNGLVERTEXATTRIBS3SVNVPROC __glewVertexAttribs3svNV = NULL; +PFNGLVERTEXATTRIBS4DVNVPROC __glewVertexAttribs4dvNV = NULL; +PFNGLVERTEXATTRIBS4FVNVPROC __glewVertexAttribs4fvNV = NULL; +PFNGLVERTEXATTRIBS4SVNVPROC __glewVertexAttribs4svNV = NULL; +PFNGLVERTEXATTRIBS4UBVNVPROC __glewVertexAttribs4ubvNV = NULL; + +PFNGLCLEARDEPTHFOESPROC __glewClearDepthfOES = NULL; +PFNGLCLIPPLANEFOESPROC __glewClipPlanefOES = NULL; +PFNGLDEPTHRANGEFOESPROC __glewDepthRangefOES = NULL; +PFNGLFRUSTUMFOESPROC __glewFrustumfOES = NULL; +PFNGLGETCLIPPLANEFOESPROC __glewGetClipPlanefOES = NULL; +PFNGLORTHOFOESPROC __glewOrthofOES = NULL; + +PFNGLDETAILTEXFUNCSGISPROC __glewDetailTexFuncSGIS = NULL; +PFNGLGETDETAILTEXFUNCSGISPROC __glewGetDetailTexFuncSGIS = NULL; + +PFNGLFOGFUNCSGISPROC __glewFogFuncSGIS = NULL; +PFNGLGETFOGFUNCSGISPROC __glewGetFogFuncSGIS = NULL; + +PFNGLSAMPLEMASKSGISPROC __glewSampleMaskSGIS = NULL; +PFNGLSAMPLEPATTERNSGISPROC __glewSamplePatternSGIS = NULL; + +PFNGLGETSHARPENTEXFUNCSGISPROC __glewGetSharpenTexFuncSGIS = NULL; +PFNGLSHARPENTEXFUNCSGISPROC __glewSharpenTexFuncSGIS = NULL; + +PFNGLTEXIMAGE4DSGISPROC __glewTexImage4DSGIS = NULL; +PFNGLTEXSUBIMAGE4DSGISPROC __glewTexSubImage4DSGIS = NULL; + +PFNGLGETTEXFILTERFUNCSGISPROC __glewGetTexFilterFuncSGIS = NULL; +PFNGLTEXFILTERFUNCSGISPROC __glewTexFilterFuncSGIS = NULL; + +PFNGLASYNCMARKERSGIXPROC __glewAsyncMarkerSGIX = NULL; +PFNGLDELETEASYNCMARKERSSGIXPROC __glewDeleteAsyncMarkersSGIX = NULL; +PFNGLFINISHASYNCSGIXPROC __glewFinishAsyncSGIX = NULL; +PFNGLGENASYNCMARKERSSGIXPROC __glewGenAsyncMarkersSGIX = NULL; +PFNGLISASYNCMARKERSGIXPROC __glewIsAsyncMarkerSGIX = NULL; +PFNGLPOLLASYNCSGIXPROC __glewPollAsyncSGIX = NULL; + +PFNGLFLUSHRASTERSGIXPROC __glewFlushRasterSGIX = NULL; + +PFNGLTEXTUREFOGSGIXPROC __glewTextureFogSGIX = NULL; + +PFNGLFRAGMENTCOLORMATERIALSGIXPROC __glewFragmentColorMaterialSGIX = NULL; +PFNGLFRAGMENTLIGHTMODELFSGIXPROC __glewFragmentLightModelfSGIX = NULL; +PFNGLFRAGMENTLIGHTMODELFVSGIXPROC __glewFragmentLightModelfvSGIX = NULL; +PFNGLFRAGMENTLIGHTMODELISGIXPROC __glewFragmentLightModeliSGIX = NULL; +PFNGLFRAGMENTLIGHTMODELIVSGIXPROC __glewFragmentLightModelivSGIX = NULL; +PFNGLFRAGMENTLIGHTFSGIXPROC __glewFragmentLightfSGIX = NULL; +PFNGLFRAGMENTLIGHTFVSGIXPROC __glewFragmentLightfvSGIX = NULL; +PFNGLFRAGMENTLIGHTISGIXPROC __glewFragmentLightiSGIX = NULL; +PFNGLFRAGMENTLIGHTIVSGIXPROC __glewFragmentLightivSGIX = NULL; +PFNGLFRAGMENTMATERIALFSGIXPROC __glewFragmentMaterialfSGIX = NULL; +PFNGLFRAGMENTMATERIALFVSGIXPROC __glewFragmentMaterialfvSGIX = NULL; +PFNGLFRAGMENTMATERIALISGIXPROC __glewFragmentMaterialiSGIX = NULL; +PFNGLFRAGMENTMATERIALIVSGIXPROC __glewFragmentMaterialivSGIX = NULL; +PFNGLGETFRAGMENTLIGHTFVSGIXPROC __glewGetFragmentLightfvSGIX = NULL; +PFNGLGETFRAGMENTLIGHTIVSGIXPROC __glewGetFragmentLightivSGIX = NULL; +PFNGLGETFRAGMENTMATERIALFVSGIXPROC __glewGetFragmentMaterialfvSGIX = NULL; +PFNGLGETFRAGMENTMATERIALIVSGIXPROC __glewGetFragmentMaterialivSGIX = NULL; + +PFNGLFRAMEZOOMSGIXPROC __glewFrameZoomSGIX = NULL; + +PFNGLPIXELTEXGENSGIXPROC __glewPixelTexGenSGIX = NULL; + +PFNGLREFERENCEPLANESGIXPROC __glewReferencePlaneSGIX = NULL; + +PFNGLSPRITEPARAMETERFSGIXPROC __glewSpriteParameterfSGIX = NULL; +PFNGLSPRITEPARAMETERFVSGIXPROC __glewSpriteParameterfvSGIX = NULL; +PFNGLSPRITEPARAMETERISGIXPROC __glewSpriteParameteriSGIX = NULL; +PFNGLSPRITEPARAMETERIVSGIXPROC __glewSpriteParameterivSGIX = NULL; + +PFNGLTAGSAMPLEBUFFERSGIXPROC __glewTagSampleBufferSGIX = NULL; + +PFNGLCOLORTABLEPARAMETERFVSGIPROC __glewColorTableParameterfvSGI = NULL; +PFNGLCOLORTABLEPARAMETERIVSGIPROC __glewColorTableParameterivSGI = NULL; +PFNGLCOLORTABLESGIPROC __glewColorTableSGI = NULL; +PFNGLCOPYCOLORTABLESGIPROC __glewCopyColorTableSGI = NULL; +PFNGLGETCOLORTABLEPARAMETERFVSGIPROC __glewGetColorTableParameterfvSGI = NULL; +PFNGLGETCOLORTABLEPARAMETERIVSGIPROC __glewGetColorTableParameterivSGI = NULL; +PFNGLGETCOLORTABLESGIPROC __glewGetColorTableSGI = NULL; + +PFNGLFINISHTEXTURESUNXPROC __glewFinishTextureSUNX = NULL; + +PFNGLGLOBALALPHAFACTORBSUNPROC __glewGlobalAlphaFactorbSUN = NULL; +PFNGLGLOBALALPHAFACTORDSUNPROC __glewGlobalAlphaFactordSUN = NULL; +PFNGLGLOBALALPHAFACTORFSUNPROC __glewGlobalAlphaFactorfSUN = NULL; +PFNGLGLOBALALPHAFACTORISUNPROC __glewGlobalAlphaFactoriSUN = NULL; +PFNGLGLOBALALPHAFACTORSSUNPROC __glewGlobalAlphaFactorsSUN = NULL; +PFNGLGLOBALALPHAFACTORUBSUNPROC __glewGlobalAlphaFactorubSUN = NULL; +PFNGLGLOBALALPHAFACTORUISUNPROC __glewGlobalAlphaFactoruiSUN = NULL; +PFNGLGLOBALALPHAFACTORUSSUNPROC __glewGlobalAlphaFactorusSUN = NULL; + +PFNGLREADVIDEOPIXELSSUNPROC __glewReadVideoPixelsSUN = NULL; + +PFNGLREPLACEMENTCODEPOINTERSUNPROC __glewReplacementCodePointerSUN = NULL; +PFNGLREPLACEMENTCODEUBSUNPROC __glewReplacementCodeubSUN = NULL; +PFNGLREPLACEMENTCODEUBVSUNPROC __glewReplacementCodeubvSUN = NULL; +PFNGLREPLACEMENTCODEUISUNPROC __glewReplacementCodeuiSUN = NULL; +PFNGLREPLACEMENTCODEUIVSUNPROC __glewReplacementCodeuivSUN = NULL; +PFNGLREPLACEMENTCODEUSSUNPROC __glewReplacementCodeusSUN = NULL; +PFNGLREPLACEMENTCODEUSVSUNPROC __glewReplacementCodeusvSUN = NULL; + +PFNGLCOLOR3FVERTEX3FSUNPROC __glewColor3fVertex3fSUN = NULL; +PFNGLCOLOR3FVERTEX3FVSUNPROC __glewColor3fVertex3fvSUN = NULL; +PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC __glewColor4fNormal3fVertex3fSUN = NULL; +PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewColor4fNormal3fVertex3fvSUN = NULL; +PFNGLCOLOR4UBVERTEX2FSUNPROC __glewColor4ubVertex2fSUN = NULL; +PFNGLCOLOR4UBVERTEX2FVSUNPROC __glewColor4ubVertex2fvSUN = NULL; +PFNGLCOLOR4UBVERTEX3FSUNPROC __glewColor4ubVertex3fSUN = NULL; +PFNGLCOLOR4UBVERTEX3FVSUNPROC __glewColor4ubVertex3fvSUN = NULL; +PFNGLNORMAL3FVERTEX3FSUNPROC __glewNormal3fVertex3fSUN = NULL; +PFNGLNORMAL3FVERTEX3FVSUNPROC __glewNormal3fVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC __glewReplacementCodeuiColor3fVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC __glewReplacementCodeuiColor3fVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiColor4fNormal3fVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiColor4fNormal3fVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC __glewReplacementCodeuiColor4ubVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC __glewReplacementCodeuiColor4ubVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiNormal3fVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiNormal3fVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC __glewReplacementCodeuiTexCoord2fNormal3fVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC __glewReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC __glewReplacementCodeuiTexCoord2fVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC __glewReplacementCodeuiTexCoord2fVertex3fvSUN = NULL; +PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC __glewReplacementCodeuiVertex3fSUN = NULL; +PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC __glewReplacementCodeuiVertex3fvSUN = NULL; +PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC __glewTexCoord2fColor3fVertex3fSUN = NULL; +PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC __glewTexCoord2fColor3fVertex3fvSUN = NULL; +PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC __glewTexCoord2fColor4fNormal3fVertex3fSUN = NULL; +PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC __glewTexCoord2fColor4fNormal3fVertex3fvSUN = NULL; +PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC __glewTexCoord2fColor4ubVertex3fSUN = NULL; +PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC __glewTexCoord2fColor4ubVertex3fvSUN = NULL; +PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC __glewTexCoord2fNormal3fVertex3fSUN = NULL; +PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC __glewTexCoord2fNormal3fVertex3fvSUN = NULL; +PFNGLTEXCOORD2FVERTEX3FSUNPROC __glewTexCoord2fVertex3fSUN = NULL; +PFNGLTEXCOORD2FVERTEX3FVSUNPROC __glewTexCoord2fVertex3fvSUN = NULL; +PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC __glewTexCoord4fColor4fNormal3fVertex4fSUN = NULL; +PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC __glewTexCoord4fColor4fNormal3fVertex4fvSUN = NULL; +PFNGLTEXCOORD4FVERTEX4FSUNPROC __glewTexCoord4fVertex4fSUN = NULL; +PFNGLTEXCOORD4FVERTEX4FVSUNPROC __glewTexCoord4fVertex4fvSUN = NULL; + +PFNGLADDSWAPHINTRECTWINPROC __glewAddSwapHintRectWIN = NULL; + +#endif /* !WIN32 || !GLEW_MX */ + +#if !defined(GLEW_MX) + +GLboolean __GLEW_VERSION_1_1 = GL_FALSE; +GLboolean __GLEW_VERSION_1_2 = GL_FALSE; +GLboolean __GLEW_VERSION_1_3 = GL_FALSE; +GLboolean __GLEW_VERSION_1_4 = GL_FALSE; +GLboolean __GLEW_VERSION_1_5 = GL_FALSE; +GLboolean __GLEW_VERSION_2_0 = GL_FALSE; +GLboolean __GLEW_VERSION_2_1 = GL_FALSE; +GLboolean __GLEW_VERSION_3_0 = GL_FALSE; +GLboolean __GLEW_3DFX_multisample = GL_FALSE; +GLboolean __GLEW_3DFX_tbuffer = GL_FALSE; +GLboolean __GLEW_3DFX_texture_compression_FXT1 = GL_FALSE; +GLboolean __GLEW_APPLE_client_storage = GL_FALSE; +GLboolean __GLEW_APPLE_element_array = GL_FALSE; +GLboolean __GLEW_APPLE_fence = GL_FALSE; +GLboolean __GLEW_APPLE_float_pixels = GL_FALSE; +GLboolean __GLEW_APPLE_flush_buffer_range = GL_FALSE; +GLboolean __GLEW_APPLE_pixel_buffer = GL_FALSE; +GLboolean __GLEW_APPLE_specular_vector = GL_FALSE; +GLboolean __GLEW_APPLE_texture_range = GL_FALSE; +GLboolean __GLEW_APPLE_transform_hint = GL_FALSE; +GLboolean __GLEW_APPLE_vertex_array_object = GL_FALSE; +GLboolean __GLEW_APPLE_vertex_array_range = GL_FALSE; +GLboolean __GLEW_APPLE_ycbcr_422 = GL_FALSE; +GLboolean __GLEW_ARB_color_buffer_float = GL_FALSE; +GLboolean __GLEW_ARB_depth_buffer_float = GL_FALSE; +GLboolean __GLEW_ARB_depth_texture = GL_FALSE; +GLboolean __GLEW_ARB_draw_buffers = GL_FALSE; +GLboolean __GLEW_ARB_draw_instanced = GL_FALSE; +GLboolean __GLEW_ARB_fragment_program = GL_FALSE; +GLboolean __GLEW_ARB_fragment_program_shadow = GL_FALSE; +GLboolean __GLEW_ARB_fragment_shader = GL_FALSE; +GLboolean __GLEW_ARB_framebuffer_object = GL_FALSE; +GLboolean __GLEW_ARB_framebuffer_sRGB = GL_FALSE; +GLboolean __GLEW_ARB_geometry_shader4 = GL_FALSE; +GLboolean __GLEW_ARB_half_float_pixel = GL_FALSE; +GLboolean __GLEW_ARB_half_float_vertex = GL_FALSE; +GLboolean __GLEW_ARB_imaging = GL_FALSE; +GLboolean __GLEW_ARB_instanced_arrays = GL_FALSE; +GLboolean __GLEW_ARB_map_buffer_range = GL_FALSE; +GLboolean __GLEW_ARB_matrix_palette = GL_FALSE; +GLboolean __GLEW_ARB_multisample = GL_FALSE; +GLboolean __GLEW_ARB_multitexture = GL_FALSE; +GLboolean __GLEW_ARB_occlusion_query = GL_FALSE; +GLboolean __GLEW_ARB_pixel_buffer_object = GL_FALSE; +GLboolean __GLEW_ARB_point_parameters = GL_FALSE; +GLboolean __GLEW_ARB_point_sprite = GL_FALSE; +GLboolean __GLEW_ARB_shader_objects = GL_FALSE; +GLboolean __GLEW_ARB_shading_language_100 = GL_FALSE; +GLboolean __GLEW_ARB_shadow = GL_FALSE; +GLboolean __GLEW_ARB_shadow_ambient = GL_FALSE; +GLboolean __GLEW_ARB_texture_border_clamp = GL_FALSE; +GLboolean __GLEW_ARB_texture_buffer_object = GL_FALSE; +GLboolean __GLEW_ARB_texture_compression = GL_FALSE; +GLboolean __GLEW_ARB_texture_compression_rgtc = GL_FALSE; +GLboolean __GLEW_ARB_texture_cube_map = GL_FALSE; +GLboolean __GLEW_ARB_texture_env_add = GL_FALSE; +GLboolean __GLEW_ARB_texture_env_combine = GL_FALSE; +GLboolean __GLEW_ARB_texture_env_crossbar = GL_FALSE; +GLboolean __GLEW_ARB_texture_env_dot3 = GL_FALSE; +GLboolean __GLEW_ARB_texture_float = GL_FALSE; +GLboolean __GLEW_ARB_texture_mirrored_repeat = GL_FALSE; +GLboolean __GLEW_ARB_texture_non_power_of_two = GL_FALSE; +GLboolean __GLEW_ARB_texture_rectangle = GL_FALSE; +GLboolean __GLEW_ARB_texture_rg = GL_FALSE; +GLboolean __GLEW_ARB_transpose_matrix = GL_FALSE; +GLboolean __GLEW_ARB_vertex_array_object = GL_FALSE; +GLboolean __GLEW_ARB_vertex_blend = GL_FALSE; +GLboolean __GLEW_ARB_vertex_buffer_object = GL_FALSE; +GLboolean __GLEW_ARB_vertex_program = GL_FALSE; +GLboolean __GLEW_ARB_vertex_shader = GL_FALSE; +GLboolean __GLEW_ARB_window_pos = GL_FALSE; +GLboolean __GLEW_ATIX_point_sprites = GL_FALSE; +GLboolean __GLEW_ATIX_texture_env_combine3 = GL_FALSE; +GLboolean __GLEW_ATIX_texture_env_route = GL_FALSE; +GLboolean __GLEW_ATIX_vertex_shader_output_point_size = GL_FALSE; +GLboolean __GLEW_ATI_draw_buffers = GL_FALSE; +GLboolean __GLEW_ATI_element_array = GL_FALSE; +GLboolean __GLEW_ATI_envmap_bumpmap = GL_FALSE; +GLboolean __GLEW_ATI_fragment_shader = GL_FALSE; +GLboolean __GLEW_ATI_map_object_buffer = GL_FALSE; +GLboolean __GLEW_ATI_pn_triangles = GL_FALSE; +GLboolean __GLEW_ATI_separate_stencil = GL_FALSE; +GLboolean __GLEW_ATI_shader_texture_lod = GL_FALSE; +GLboolean __GLEW_ATI_text_fragment_shader = GL_FALSE; +GLboolean __GLEW_ATI_texture_compression_3dc = GL_FALSE; +GLboolean __GLEW_ATI_texture_env_combine3 = GL_FALSE; +GLboolean __GLEW_ATI_texture_float = GL_FALSE; +GLboolean __GLEW_ATI_texture_mirror_once = GL_FALSE; +GLboolean __GLEW_ATI_vertex_array_object = GL_FALSE; +GLboolean __GLEW_ATI_vertex_attrib_array_object = GL_FALSE; +GLboolean __GLEW_ATI_vertex_streams = GL_FALSE; +GLboolean __GLEW_EXT_422_pixels = GL_FALSE; +GLboolean __GLEW_EXT_Cg_shader = GL_FALSE; +GLboolean __GLEW_EXT_abgr = GL_FALSE; +GLboolean __GLEW_EXT_bgra = GL_FALSE; +GLboolean __GLEW_EXT_bindable_uniform = GL_FALSE; +GLboolean __GLEW_EXT_blend_color = GL_FALSE; +GLboolean __GLEW_EXT_blend_equation_separate = GL_FALSE; +GLboolean __GLEW_EXT_blend_func_separate = GL_FALSE; +GLboolean __GLEW_EXT_blend_logic_op = GL_FALSE; +GLboolean __GLEW_EXT_blend_minmax = GL_FALSE; +GLboolean __GLEW_EXT_blend_subtract = GL_FALSE; +GLboolean __GLEW_EXT_clip_volume_hint = GL_FALSE; +GLboolean __GLEW_EXT_cmyka = GL_FALSE; +GLboolean __GLEW_EXT_color_subtable = GL_FALSE; +GLboolean __GLEW_EXT_compiled_vertex_array = GL_FALSE; +GLboolean __GLEW_EXT_convolution = GL_FALSE; +GLboolean __GLEW_EXT_coordinate_frame = GL_FALSE; +GLboolean __GLEW_EXT_copy_texture = GL_FALSE; +GLboolean __GLEW_EXT_cull_vertex = GL_FALSE; +GLboolean __GLEW_EXT_depth_bounds_test = GL_FALSE; +GLboolean __GLEW_EXT_direct_state_access = GL_FALSE; +GLboolean __GLEW_EXT_draw_buffers2 = GL_FALSE; +GLboolean __GLEW_EXT_draw_instanced = GL_FALSE; +GLboolean __GLEW_EXT_draw_range_elements = GL_FALSE; +GLboolean __GLEW_EXT_fog_coord = GL_FALSE; +GLboolean __GLEW_EXT_fragment_lighting = GL_FALSE; +GLboolean __GLEW_EXT_framebuffer_blit = GL_FALSE; +GLboolean __GLEW_EXT_framebuffer_multisample = GL_FALSE; +GLboolean __GLEW_EXT_framebuffer_object = GL_FALSE; +GLboolean __GLEW_EXT_framebuffer_sRGB = GL_FALSE; +GLboolean __GLEW_EXT_geometry_shader4 = GL_FALSE; +GLboolean __GLEW_EXT_gpu_program_parameters = GL_FALSE; +GLboolean __GLEW_EXT_gpu_shader4 = GL_FALSE; +GLboolean __GLEW_EXT_histogram = GL_FALSE; +GLboolean __GLEW_EXT_index_array_formats = GL_FALSE; +GLboolean __GLEW_EXT_index_func = GL_FALSE; +GLboolean __GLEW_EXT_index_material = GL_FALSE; +GLboolean __GLEW_EXT_index_texture = GL_FALSE; +GLboolean __GLEW_EXT_light_texture = GL_FALSE; +GLboolean __GLEW_EXT_misc_attribute = GL_FALSE; +GLboolean __GLEW_EXT_multi_draw_arrays = GL_FALSE; +GLboolean __GLEW_EXT_multisample = GL_FALSE; +GLboolean __GLEW_EXT_packed_depth_stencil = GL_FALSE; +GLboolean __GLEW_EXT_packed_float = GL_FALSE; +GLboolean __GLEW_EXT_packed_pixels = GL_FALSE; +GLboolean __GLEW_EXT_paletted_texture = GL_FALSE; +GLboolean __GLEW_EXT_pixel_buffer_object = GL_FALSE; +GLboolean __GLEW_EXT_pixel_transform = GL_FALSE; +GLboolean __GLEW_EXT_pixel_transform_color_table = GL_FALSE; +GLboolean __GLEW_EXT_point_parameters = GL_FALSE; +GLboolean __GLEW_EXT_polygon_offset = GL_FALSE; +GLboolean __GLEW_EXT_rescale_normal = GL_FALSE; +GLboolean __GLEW_EXT_scene_marker = GL_FALSE; +GLboolean __GLEW_EXT_secondary_color = GL_FALSE; +GLboolean __GLEW_EXT_separate_specular_color = GL_FALSE; +GLboolean __GLEW_EXT_shadow_funcs = GL_FALSE; +GLboolean __GLEW_EXT_shared_texture_palette = GL_FALSE; +GLboolean __GLEW_EXT_stencil_clear_tag = GL_FALSE; +GLboolean __GLEW_EXT_stencil_two_side = GL_FALSE; +GLboolean __GLEW_EXT_stencil_wrap = GL_FALSE; +GLboolean __GLEW_EXT_subtexture = GL_FALSE; +GLboolean __GLEW_EXT_texture = GL_FALSE; +GLboolean __GLEW_EXT_texture3D = GL_FALSE; +GLboolean __GLEW_EXT_texture_array = GL_FALSE; +GLboolean __GLEW_EXT_texture_buffer_object = GL_FALSE; +GLboolean __GLEW_EXT_texture_compression_dxt1 = GL_FALSE; +GLboolean __GLEW_EXT_texture_compression_latc = GL_FALSE; +GLboolean __GLEW_EXT_texture_compression_rgtc = GL_FALSE; +GLboolean __GLEW_EXT_texture_compression_s3tc = GL_FALSE; +GLboolean __GLEW_EXT_texture_cube_map = GL_FALSE; +GLboolean __GLEW_EXT_texture_edge_clamp = GL_FALSE; +GLboolean __GLEW_EXT_texture_env = GL_FALSE; +GLboolean __GLEW_EXT_texture_env_add = GL_FALSE; +GLboolean __GLEW_EXT_texture_env_combine = GL_FALSE; +GLboolean __GLEW_EXT_texture_env_dot3 = GL_FALSE; +GLboolean __GLEW_EXT_texture_filter_anisotropic = GL_FALSE; +GLboolean __GLEW_EXT_texture_integer = GL_FALSE; +GLboolean __GLEW_EXT_texture_lod_bias = GL_FALSE; +GLboolean __GLEW_EXT_texture_mirror_clamp = GL_FALSE; +GLboolean __GLEW_EXT_texture_object = GL_FALSE; +GLboolean __GLEW_EXT_texture_perturb_normal = GL_FALSE; +GLboolean __GLEW_EXT_texture_rectangle = GL_FALSE; +GLboolean __GLEW_EXT_texture_sRGB = GL_FALSE; +GLboolean __GLEW_EXT_texture_shared_exponent = GL_FALSE; +GLboolean __GLEW_EXT_texture_swizzle = GL_FALSE; +GLboolean __GLEW_EXT_timer_query = GL_FALSE; +GLboolean __GLEW_EXT_transform_feedback = GL_FALSE; +GLboolean __GLEW_EXT_vertex_array = GL_FALSE; +GLboolean __GLEW_EXT_vertex_array_bgra = GL_FALSE; +GLboolean __GLEW_EXT_vertex_shader = GL_FALSE; +GLboolean __GLEW_EXT_vertex_weighting = GL_FALSE; +GLboolean __GLEW_GREMEDY_frame_terminator = GL_FALSE; +GLboolean __GLEW_GREMEDY_string_marker = GL_FALSE; +GLboolean __GLEW_HP_convolution_border_modes = GL_FALSE; +GLboolean __GLEW_HP_image_transform = GL_FALSE; +GLboolean __GLEW_HP_occlusion_test = GL_FALSE; +GLboolean __GLEW_HP_texture_lighting = GL_FALSE; +GLboolean __GLEW_IBM_cull_vertex = GL_FALSE; +GLboolean __GLEW_IBM_multimode_draw_arrays = GL_FALSE; +GLboolean __GLEW_IBM_rasterpos_clip = GL_FALSE; +GLboolean __GLEW_IBM_static_data = GL_FALSE; +GLboolean __GLEW_IBM_texture_mirrored_repeat = GL_FALSE; +GLboolean __GLEW_IBM_vertex_array_lists = GL_FALSE; +GLboolean __GLEW_INGR_color_clamp = GL_FALSE; +GLboolean __GLEW_INGR_interlace_read = GL_FALSE; +GLboolean __GLEW_INTEL_parallel_arrays = GL_FALSE; +GLboolean __GLEW_INTEL_texture_scissor = GL_FALSE; +GLboolean __GLEW_KTX_buffer_region = GL_FALSE; +GLboolean __GLEW_MESAX_texture_stack = GL_FALSE; +GLboolean __GLEW_MESA_pack_invert = GL_FALSE; +GLboolean __GLEW_MESA_resize_buffers = GL_FALSE; +GLboolean __GLEW_MESA_window_pos = GL_FALSE; +GLboolean __GLEW_MESA_ycbcr_texture = GL_FALSE; +GLboolean __GLEW_NV_blend_square = GL_FALSE; +GLboolean __GLEW_NV_conditional_render = GL_FALSE; +GLboolean __GLEW_NV_copy_depth_to_color = GL_FALSE; +GLboolean __GLEW_NV_depth_buffer_float = GL_FALSE; +GLboolean __GLEW_NV_depth_clamp = GL_FALSE; +GLboolean __GLEW_NV_depth_range_unclamped = GL_FALSE; +GLboolean __GLEW_NV_evaluators = GL_FALSE; +GLboolean __GLEW_NV_explicit_multisample = GL_FALSE; +GLboolean __GLEW_NV_fence = GL_FALSE; +GLboolean __GLEW_NV_float_buffer = GL_FALSE; +GLboolean __GLEW_NV_fog_distance = GL_FALSE; +GLboolean __GLEW_NV_fragment_program = GL_FALSE; +GLboolean __GLEW_NV_fragment_program2 = GL_FALSE; +GLboolean __GLEW_NV_fragment_program4 = GL_FALSE; +GLboolean __GLEW_NV_fragment_program_option = GL_FALSE; +GLboolean __GLEW_NV_framebuffer_multisample_coverage = GL_FALSE; +GLboolean __GLEW_NV_geometry_program4 = GL_FALSE; +GLboolean __GLEW_NV_geometry_shader4 = GL_FALSE; +GLboolean __GLEW_NV_gpu_program4 = GL_FALSE; +GLboolean __GLEW_NV_half_float = GL_FALSE; +GLboolean __GLEW_NV_light_max_exponent = GL_FALSE; +GLboolean __GLEW_NV_multisample_filter_hint = GL_FALSE; +GLboolean __GLEW_NV_occlusion_query = GL_FALSE; +GLboolean __GLEW_NV_packed_depth_stencil = GL_FALSE; +GLboolean __GLEW_NV_parameter_buffer_object = GL_FALSE; +GLboolean __GLEW_NV_pixel_data_range = GL_FALSE; +GLboolean __GLEW_NV_point_sprite = GL_FALSE; +GLboolean __GLEW_NV_present_video = GL_FALSE; +GLboolean __GLEW_NV_primitive_restart = GL_FALSE; +GLboolean __GLEW_NV_register_combiners = GL_FALSE; +GLboolean __GLEW_NV_register_combiners2 = GL_FALSE; +GLboolean __GLEW_NV_texgen_emboss = GL_FALSE; +GLboolean __GLEW_NV_texgen_reflection = GL_FALSE; +GLboolean __GLEW_NV_texture_compression_vtc = GL_FALSE; +GLboolean __GLEW_NV_texture_env_combine4 = GL_FALSE; +GLboolean __GLEW_NV_texture_expand_normal = GL_FALSE; +GLboolean __GLEW_NV_texture_rectangle = GL_FALSE; +GLboolean __GLEW_NV_texture_shader = GL_FALSE; +GLboolean __GLEW_NV_texture_shader2 = GL_FALSE; +GLboolean __GLEW_NV_texture_shader3 = GL_FALSE; +GLboolean __GLEW_NV_transform_feedback = GL_FALSE; +GLboolean __GLEW_NV_vertex_array_range = GL_FALSE; +GLboolean __GLEW_NV_vertex_array_range2 = GL_FALSE; +GLboolean __GLEW_NV_vertex_program = GL_FALSE; +GLboolean __GLEW_NV_vertex_program1_1 = GL_FALSE; +GLboolean __GLEW_NV_vertex_program2 = GL_FALSE; +GLboolean __GLEW_NV_vertex_program2_option = GL_FALSE; +GLboolean __GLEW_NV_vertex_program3 = GL_FALSE; +GLboolean __GLEW_NV_vertex_program4 = GL_FALSE; +GLboolean __GLEW_OES_byte_coordinates = GL_FALSE; +GLboolean __GLEW_OES_compressed_paletted_texture = GL_FALSE; +GLboolean __GLEW_OES_read_format = GL_FALSE; +GLboolean __GLEW_OES_single_precision = GL_FALSE; +GLboolean __GLEW_OML_interlace = GL_FALSE; +GLboolean __GLEW_OML_resample = GL_FALSE; +GLboolean __GLEW_OML_subsample = GL_FALSE; +GLboolean __GLEW_PGI_misc_hints = GL_FALSE; +GLboolean __GLEW_PGI_vertex_hints = GL_FALSE; +GLboolean __GLEW_REND_screen_coordinates = GL_FALSE; +GLboolean __GLEW_S3_s3tc = GL_FALSE; +GLboolean __GLEW_SGIS_color_range = GL_FALSE; +GLboolean __GLEW_SGIS_detail_texture = GL_FALSE; +GLboolean __GLEW_SGIS_fog_function = GL_FALSE; +GLboolean __GLEW_SGIS_generate_mipmap = GL_FALSE; +GLboolean __GLEW_SGIS_multisample = GL_FALSE; +GLboolean __GLEW_SGIS_pixel_texture = GL_FALSE; +GLboolean __GLEW_SGIS_point_line_texgen = GL_FALSE; +GLboolean __GLEW_SGIS_sharpen_texture = GL_FALSE; +GLboolean __GLEW_SGIS_texture4D = GL_FALSE; +GLboolean __GLEW_SGIS_texture_border_clamp = GL_FALSE; +GLboolean __GLEW_SGIS_texture_edge_clamp = GL_FALSE; +GLboolean __GLEW_SGIS_texture_filter4 = GL_FALSE; +GLboolean __GLEW_SGIS_texture_lod = GL_FALSE; +GLboolean __GLEW_SGIS_texture_select = GL_FALSE; +GLboolean __GLEW_SGIX_async = GL_FALSE; +GLboolean __GLEW_SGIX_async_histogram = GL_FALSE; +GLboolean __GLEW_SGIX_async_pixel = GL_FALSE; +GLboolean __GLEW_SGIX_blend_alpha_minmax = GL_FALSE; +GLboolean __GLEW_SGIX_clipmap = GL_FALSE; +GLboolean __GLEW_SGIX_convolution_accuracy = GL_FALSE; +GLboolean __GLEW_SGIX_depth_texture = GL_FALSE; +GLboolean __GLEW_SGIX_flush_raster = GL_FALSE; +GLboolean __GLEW_SGIX_fog_offset = GL_FALSE; +GLboolean __GLEW_SGIX_fog_texture = GL_FALSE; +GLboolean __GLEW_SGIX_fragment_specular_lighting = GL_FALSE; +GLboolean __GLEW_SGIX_framezoom = GL_FALSE; +GLboolean __GLEW_SGIX_interlace = GL_FALSE; +GLboolean __GLEW_SGIX_ir_instrument1 = GL_FALSE; +GLboolean __GLEW_SGIX_list_priority = GL_FALSE; +GLboolean __GLEW_SGIX_pixel_texture = GL_FALSE; +GLboolean __GLEW_SGIX_pixel_texture_bits = GL_FALSE; +GLboolean __GLEW_SGIX_reference_plane = GL_FALSE; +GLboolean __GLEW_SGIX_resample = GL_FALSE; +GLboolean __GLEW_SGIX_shadow = GL_FALSE; +GLboolean __GLEW_SGIX_shadow_ambient = GL_FALSE; +GLboolean __GLEW_SGIX_sprite = GL_FALSE; +GLboolean __GLEW_SGIX_tag_sample_buffer = GL_FALSE; +GLboolean __GLEW_SGIX_texture_add_env = GL_FALSE; +GLboolean __GLEW_SGIX_texture_coordinate_clamp = GL_FALSE; +GLboolean __GLEW_SGIX_texture_lod_bias = GL_FALSE; +GLboolean __GLEW_SGIX_texture_multi_buffer = GL_FALSE; +GLboolean __GLEW_SGIX_texture_range = GL_FALSE; +GLboolean __GLEW_SGIX_texture_scale_bias = GL_FALSE; +GLboolean __GLEW_SGIX_vertex_preclip = GL_FALSE; +GLboolean __GLEW_SGIX_vertex_preclip_hint = GL_FALSE; +GLboolean __GLEW_SGIX_ycrcb = GL_FALSE; +GLboolean __GLEW_SGI_color_matrix = GL_FALSE; +GLboolean __GLEW_SGI_color_table = GL_FALSE; +GLboolean __GLEW_SGI_texture_color_table = GL_FALSE; +GLboolean __GLEW_SUNX_constant_data = GL_FALSE; +GLboolean __GLEW_SUN_convolution_border_modes = GL_FALSE; +GLboolean __GLEW_SUN_global_alpha = GL_FALSE; +GLboolean __GLEW_SUN_mesh_array = GL_FALSE; +GLboolean __GLEW_SUN_read_video_pixels = GL_FALSE; +GLboolean __GLEW_SUN_slice_accum = GL_FALSE; +GLboolean __GLEW_SUN_triangle_list = GL_FALSE; +GLboolean __GLEW_SUN_vertex = GL_FALSE; +GLboolean __GLEW_WIN_phong_shading = GL_FALSE; +GLboolean __GLEW_WIN_specular_fog = GL_FALSE; +GLboolean __GLEW_WIN_swap_hint = GL_FALSE; + +#endif /* !GLEW_MX */ + +#ifdef GL_VERSION_1_2 + +static GLboolean _glewInit_GL_VERSION_1_2 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glCopyTexSubImage3D = (PFNGLCOPYTEXSUBIMAGE3DPROC)glewGetProcAddress((const GLubyte*)"glCopyTexSubImage3D")) == NULL) || r; + r = ((glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)glewGetProcAddress((const GLubyte*)"glDrawRangeElements")) == NULL) || r; + r = ((glTexImage3D = (PFNGLTEXIMAGE3DPROC)glewGetProcAddress((const GLubyte*)"glTexImage3D")) == NULL) || r; + r = ((glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)glewGetProcAddress((const GLubyte*)"glTexSubImage3D")) == NULL) || r; + + return r; +} + +#endif /* GL_VERSION_1_2 */ + +#ifdef GL_VERSION_1_3 + +static GLboolean _glewInit_GL_VERSION_1_3 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glActiveTexture = (PFNGLACTIVETEXTUREPROC)glewGetProcAddress((const GLubyte*)"glActiveTexture")) == NULL) || r; + r = ((glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)glewGetProcAddress((const GLubyte*)"glClientActiveTexture")) == NULL) || r; + r = ((glCompressedTexImage1D = (PFNGLCOMPRESSEDTEXIMAGE1DPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexImage1D")) == NULL) || r; + r = ((glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexImage2D")) == NULL) || r; + r = ((glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexImage3D")) == NULL) || r; + r = ((glCompressedTexSubImage1D = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexSubImage1D")) == NULL) || r; + r = ((glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexSubImage2D")) == NULL) || r; + r = ((glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexSubImage3D")) == NULL) || r; + r = ((glGetCompressedTexImage = (PFNGLGETCOMPRESSEDTEXIMAGEPROC)glewGetProcAddress((const GLubyte*)"glGetCompressedTexImage")) == NULL) || r; + r = ((glLoadTransposeMatrixd = (PFNGLLOADTRANSPOSEMATRIXDPROC)glewGetProcAddress((const GLubyte*)"glLoadTransposeMatrixd")) == NULL) || r; + r = ((glLoadTransposeMatrixf = (PFNGLLOADTRANSPOSEMATRIXFPROC)glewGetProcAddress((const GLubyte*)"glLoadTransposeMatrixf")) == NULL) || r; + r = ((glMultTransposeMatrixd = (PFNGLMULTTRANSPOSEMATRIXDPROC)glewGetProcAddress((const GLubyte*)"glMultTransposeMatrixd")) == NULL) || r; + r = ((glMultTransposeMatrixf = (PFNGLMULTTRANSPOSEMATRIXFPROC)glewGetProcAddress((const GLubyte*)"glMultTransposeMatrixf")) == NULL) || r; + r = ((glMultiTexCoord1d = (PFNGLMULTITEXCOORD1DPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1d")) == NULL) || r; + r = ((glMultiTexCoord1dv = (PFNGLMULTITEXCOORD1DVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1dv")) == NULL) || r; + r = ((glMultiTexCoord1f = (PFNGLMULTITEXCOORD1FPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1f")) == NULL) || r; + r = ((glMultiTexCoord1fv = (PFNGLMULTITEXCOORD1FVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1fv")) == NULL) || r; + r = ((glMultiTexCoord1i = (PFNGLMULTITEXCOORD1IPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1i")) == NULL) || r; + r = ((glMultiTexCoord1iv = (PFNGLMULTITEXCOORD1IVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1iv")) == NULL) || r; + r = ((glMultiTexCoord1s = (PFNGLMULTITEXCOORD1SPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1s")) == NULL) || r; + r = ((glMultiTexCoord1sv = (PFNGLMULTITEXCOORD1SVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1sv")) == NULL) || r; + r = ((glMultiTexCoord2d = (PFNGLMULTITEXCOORD2DPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2d")) == NULL) || r; + r = ((glMultiTexCoord2dv = (PFNGLMULTITEXCOORD2DVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2dv")) == NULL) || r; + r = ((glMultiTexCoord2f = (PFNGLMULTITEXCOORD2FPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2f")) == NULL) || r; + r = ((glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2fv")) == NULL) || r; + r = ((glMultiTexCoord2i = (PFNGLMULTITEXCOORD2IPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2i")) == NULL) || r; + r = ((glMultiTexCoord2iv = (PFNGLMULTITEXCOORD2IVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2iv")) == NULL) || r; + r = ((glMultiTexCoord2s = (PFNGLMULTITEXCOORD2SPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2s")) == NULL) || r; + r = ((glMultiTexCoord2sv = (PFNGLMULTITEXCOORD2SVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2sv")) == NULL) || r; + r = ((glMultiTexCoord3d = (PFNGLMULTITEXCOORD3DPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3d")) == NULL) || r; + r = ((glMultiTexCoord3dv = (PFNGLMULTITEXCOORD3DVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3dv")) == NULL) || r; + r = ((glMultiTexCoord3f = (PFNGLMULTITEXCOORD3FPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3f")) == NULL) || r; + r = ((glMultiTexCoord3fv = (PFNGLMULTITEXCOORD3FVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3fv")) == NULL) || r; + r = ((glMultiTexCoord3i = (PFNGLMULTITEXCOORD3IPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3i")) == NULL) || r; + r = ((glMultiTexCoord3iv = (PFNGLMULTITEXCOORD3IVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3iv")) == NULL) || r; + r = ((glMultiTexCoord3s = (PFNGLMULTITEXCOORD3SPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3s")) == NULL) || r; + r = ((glMultiTexCoord3sv = (PFNGLMULTITEXCOORD3SVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3sv")) == NULL) || r; + r = ((glMultiTexCoord4d = (PFNGLMULTITEXCOORD4DPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4d")) == NULL) || r; + r = ((glMultiTexCoord4dv = (PFNGLMULTITEXCOORD4DVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4dv")) == NULL) || r; + r = ((glMultiTexCoord4f = (PFNGLMULTITEXCOORD4FPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4f")) == NULL) || r; + r = ((glMultiTexCoord4fv = (PFNGLMULTITEXCOORD4FVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4fv")) == NULL) || r; + r = ((glMultiTexCoord4i = (PFNGLMULTITEXCOORD4IPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4i")) == NULL) || r; + r = ((glMultiTexCoord4iv = (PFNGLMULTITEXCOORD4IVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4iv")) == NULL) || r; + r = ((glMultiTexCoord4s = (PFNGLMULTITEXCOORD4SPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4s")) == NULL) || r; + r = ((glMultiTexCoord4sv = (PFNGLMULTITEXCOORD4SVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4sv")) == NULL) || r; + r = ((glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC)glewGetProcAddress((const GLubyte*)"glSampleCoverage")) == NULL) || r; + + return r; +} + +#endif /* GL_VERSION_1_3 */ + +#ifdef GL_VERSION_1_4 + +static GLboolean _glewInit_GL_VERSION_1_4 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBlendColor = (PFNGLBLENDCOLORPROC)glewGetProcAddress((const GLubyte*)"glBlendColor")) == NULL) || r; + r = ((glBlendEquation = (PFNGLBLENDEQUATIONPROC)glewGetProcAddress((const GLubyte*)"glBlendEquation")) == NULL) || r; + r = ((glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)glewGetProcAddress((const GLubyte*)"glBlendFuncSeparate")) == NULL) || r; + r = ((glFogCoordPointer = (PFNGLFOGCOORDPOINTERPROC)glewGetProcAddress((const GLubyte*)"glFogCoordPointer")) == NULL) || r; + r = ((glFogCoordd = (PFNGLFOGCOORDDPROC)glewGetProcAddress((const GLubyte*)"glFogCoordd")) == NULL) || r; + r = ((glFogCoorddv = (PFNGLFOGCOORDDVPROC)glewGetProcAddress((const GLubyte*)"glFogCoorddv")) == NULL) || r; + r = ((glFogCoordf = (PFNGLFOGCOORDFPROC)glewGetProcAddress((const GLubyte*)"glFogCoordf")) == NULL) || r; + r = ((glFogCoordfv = (PFNGLFOGCOORDFVPROC)glewGetProcAddress((const GLubyte*)"glFogCoordfv")) == NULL) || r; + r = ((glMultiDrawArrays = (PFNGLMULTIDRAWARRAYSPROC)glewGetProcAddress((const GLubyte*)"glMultiDrawArrays")) == NULL) || r; + r = ((glMultiDrawElements = (PFNGLMULTIDRAWELEMENTSPROC)glewGetProcAddress((const GLubyte*)"glMultiDrawElements")) == NULL) || r; + r = ((glPointParameterf = (PFNGLPOINTPARAMETERFPROC)glewGetProcAddress((const GLubyte*)"glPointParameterf")) == NULL) || r; + r = ((glPointParameterfv = (PFNGLPOINTPARAMETERFVPROC)glewGetProcAddress((const GLubyte*)"glPointParameterfv")) == NULL) || r; + r = ((glPointParameteri = (PFNGLPOINTPARAMETERIPROC)glewGetProcAddress((const GLubyte*)"glPointParameteri")) == NULL) || r; + r = ((glPointParameteriv = (PFNGLPOINTPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glPointParameteriv")) == NULL) || r; + r = ((glSecondaryColor3b = (PFNGLSECONDARYCOLOR3BPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3b")) == NULL) || r; + r = ((glSecondaryColor3bv = (PFNGLSECONDARYCOLOR3BVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3bv")) == NULL) || r; + r = ((glSecondaryColor3d = (PFNGLSECONDARYCOLOR3DPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3d")) == NULL) || r; + r = ((glSecondaryColor3dv = (PFNGLSECONDARYCOLOR3DVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3dv")) == NULL) || r; + r = ((glSecondaryColor3f = (PFNGLSECONDARYCOLOR3FPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3f")) == NULL) || r; + r = ((glSecondaryColor3fv = (PFNGLSECONDARYCOLOR3FVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3fv")) == NULL) || r; + r = ((glSecondaryColor3i = (PFNGLSECONDARYCOLOR3IPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3i")) == NULL) || r; + r = ((glSecondaryColor3iv = (PFNGLSECONDARYCOLOR3IVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3iv")) == NULL) || r; + r = ((glSecondaryColor3s = (PFNGLSECONDARYCOLOR3SPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3s")) == NULL) || r; + r = ((glSecondaryColor3sv = (PFNGLSECONDARYCOLOR3SVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3sv")) == NULL) || r; + r = ((glSecondaryColor3ub = (PFNGLSECONDARYCOLOR3UBPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3ub")) == NULL) || r; + r = ((glSecondaryColor3ubv = (PFNGLSECONDARYCOLOR3UBVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3ubv")) == NULL) || r; + r = ((glSecondaryColor3ui = (PFNGLSECONDARYCOLOR3UIPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3ui")) == NULL) || r; + r = ((glSecondaryColor3uiv = (PFNGLSECONDARYCOLOR3UIVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3uiv")) == NULL) || r; + r = ((glSecondaryColor3us = (PFNGLSECONDARYCOLOR3USPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3us")) == NULL) || r; + r = ((glSecondaryColor3usv = (PFNGLSECONDARYCOLOR3USVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3usv")) == NULL) || r; + r = ((glSecondaryColorPointer = (PFNGLSECONDARYCOLORPOINTERPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColorPointer")) == NULL) || r; + r = ((glWindowPos2d = (PFNGLWINDOWPOS2DPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2d")) == NULL) || r; + r = ((glWindowPos2dv = (PFNGLWINDOWPOS2DVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2dv")) == NULL) || r; + r = ((glWindowPos2f = (PFNGLWINDOWPOS2FPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2f")) == NULL) || r; + r = ((glWindowPos2fv = (PFNGLWINDOWPOS2FVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2fv")) == NULL) || r; + r = ((glWindowPos2i = (PFNGLWINDOWPOS2IPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2i")) == NULL) || r; + r = ((glWindowPos2iv = (PFNGLWINDOWPOS2IVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2iv")) == NULL) || r; + r = ((glWindowPos2s = (PFNGLWINDOWPOS2SPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2s")) == NULL) || r; + r = ((glWindowPos2sv = (PFNGLWINDOWPOS2SVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2sv")) == NULL) || r; + r = ((glWindowPos3d = (PFNGLWINDOWPOS3DPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3d")) == NULL) || r; + r = ((glWindowPos3dv = (PFNGLWINDOWPOS3DVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3dv")) == NULL) || r; + r = ((glWindowPos3f = (PFNGLWINDOWPOS3FPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3f")) == NULL) || r; + r = ((glWindowPos3fv = (PFNGLWINDOWPOS3FVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3fv")) == NULL) || r; + r = ((glWindowPos3i = (PFNGLWINDOWPOS3IPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3i")) == NULL) || r; + r = ((glWindowPos3iv = (PFNGLWINDOWPOS3IVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3iv")) == NULL) || r; + r = ((glWindowPos3s = (PFNGLWINDOWPOS3SPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3s")) == NULL) || r; + r = ((glWindowPos3sv = (PFNGLWINDOWPOS3SVPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3sv")) == NULL) || r; + + return r; +} + +#endif /* GL_VERSION_1_4 */ + +#ifdef GL_VERSION_1_5 + +static GLboolean _glewInit_GL_VERSION_1_5 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginQuery = (PFNGLBEGINQUERYPROC)glewGetProcAddress((const GLubyte*)"glBeginQuery")) == NULL) || r; + r = ((glBindBuffer = (PFNGLBINDBUFFERPROC)glewGetProcAddress((const GLubyte*)"glBindBuffer")) == NULL) || r; + r = ((glBufferData = (PFNGLBUFFERDATAPROC)glewGetProcAddress((const GLubyte*)"glBufferData")) == NULL) || r; + r = ((glBufferSubData = (PFNGLBUFFERSUBDATAPROC)glewGetProcAddress((const GLubyte*)"glBufferSubData")) == NULL) || r; + r = ((glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)glewGetProcAddress((const GLubyte*)"glDeleteBuffers")) == NULL) || r; + r = ((glDeleteQueries = (PFNGLDELETEQUERIESPROC)glewGetProcAddress((const GLubyte*)"glDeleteQueries")) == NULL) || r; + r = ((glEndQuery = (PFNGLENDQUERYPROC)glewGetProcAddress((const GLubyte*)"glEndQuery")) == NULL) || r; + r = ((glGenBuffers = (PFNGLGENBUFFERSPROC)glewGetProcAddress((const GLubyte*)"glGenBuffers")) == NULL) || r; + r = ((glGenQueries = (PFNGLGENQUERIESPROC)glewGetProcAddress((const GLubyte*)"glGenQueries")) == NULL) || r; + r = ((glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glGetBufferParameteriv")) == NULL) || r; + r = ((glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC)glewGetProcAddress((const GLubyte*)"glGetBufferPointerv")) == NULL) || r; + r = ((glGetBufferSubData = (PFNGLGETBUFFERSUBDATAPROC)glewGetProcAddress((const GLubyte*)"glGetBufferSubData")) == NULL) || r; + r = ((glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC)glewGetProcAddress((const GLubyte*)"glGetQueryObjectiv")) == NULL) || r; + r = ((glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)glewGetProcAddress((const GLubyte*)"glGetQueryObjectuiv")) == NULL) || r; + r = ((glGetQueryiv = (PFNGLGETQUERYIVPROC)glewGetProcAddress((const GLubyte*)"glGetQueryiv")) == NULL) || r; + r = ((glIsBuffer = (PFNGLISBUFFERPROC)glewGetProcAddress((const GLubyte*)"glIsBuffer")) == NULL) || r; + r = ((glIsQuery = (PFNGLISQUERYPROC)glewGetProcAddress((const GLubyte*)"glIsQuery")) == NULL) || r; + r = ((glMapBuffer = (PFNGLMAPBUFFERPROC)glewGetProcAddress((const GLubyte*)"glMapBuffer")) == NULL) || r; + r = ((glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)glewGetProcAddress((const GLubyte*)"glUnmapBuffer")) == NULL) || r; + + return r; +} + +#endif /* GL_VERSION_1_5 */ + +#ifdef GL_VERSION_2_0 + +static GLboolean _glewInit_GL_VERSION_2_0 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glAttachShader = (PFNGLATTACHSHADERPROC)glewGetProcAddress((const GLubyte*)"glAttachShader")) == NULL) || r; + r = ((glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)glewGetProcAddress((const GLubyte*)"glBindAttribLocation")) == NULL) || r; + r = ((glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)glewGetProcAddress((const GLubyte*)"glBlendEquationSeparate")) == NULL) || r; + r = ((glCompileShader = (PFNGLCOMPILESHADERPROC)glewGetProcAddress((const GLubyte*)"glCompileShader")) == NULL) || r; + r = ((glCreateProgram = (PFNGLCREATEPROGRAMPROC)glewGetProcAddress((const GLubyte*)"glCreateProgram")) == NULL) || r; + r = ((glCreateShader = (PFNGLCREATESHADERPROC)glewGetProcAddress((const GLubyte*)"glCreateShader")) == NULL) || r; + r = ((glDeleteProgram = (PFNGLDELETEPROGRAMPROC)glewGetProcAddress((const GLubyte*)"glDeleteProgram")) == NULL) || r; + r = ((glDeleteShader = (PFNGLDELETESHADERPROC)glewGetProcAddress((const GLubyte*)"glDeleteShader")) == NULL) || r; + r = ((glDetachShader = (PFNGLDETACHSHADERPROC)glewGetProcAddress((const GLubyte*)"glDetachShader")) == NULL) || r; + r = ((glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)glewGetProcAddress((const GLubyte*)"glDisableVertexAttribArray")) == NULL) || r; + r = ((glDrawBuffers = (PFNGLDRAWBUFFERSPROC)glewGetProcAddress((const GLubyte*)"glDrawBuffers")) == NULL) || r; + r = ((glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)glewGetProcAddress((const GLubyte*)"glEnableVertexAttribArray")) == NULL) || r; + r = ((glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC)glewGetProcAddress((const GLubyte*)"glGetActiveAttrib")) == NULL) || r; + r = ((glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)glewGetProcAddress((const GLubyte*)"glGetActiveUniform")) == NULL) || r; + r = ((glGetAttachedShaders = (PFNGLGETATTACHEDSHADERSPROC)glewGetProcAddress((const GLubyte*)"glGetAttachedShaders")) == NULL) || r; + r = ((glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)glewGetProcAddress((const GLubyte*)"glGetAttribLocation")) == NULL) || r; + r = ((glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)glewGetProcAddress((const GLubyte*)"glGetProgramInfoLog")) == NULL) || r; + r = ((glGetProgramiv = (PFNGLGETPROGRAMIVPROC)glewGetProcAddress((const GLubyte*)"glGetProgramiv")) == NULL) || r; + r = ((glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)glewGetProcAddress((const GLubyte*)"glGetShaderInfoLog")) == NULL) || r; + r = ((glGetShaderSource = (PFNGLGETSHADERSOURCEPROC)glewGetProcAddress((const GLubyte*)"glGetShaderSource")) == NULL) || r; + r = ((glGetShaderiv = (PFNGLGETSHADERIVPROC)glewGetProcAddress((const GLubyte*)"glGetShaderiv")) == NULL) || r; + r = ((glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)glewGetProcAddress((const GLubyte*)"glGetUniformLocation")) == NULL) || r; + r = ((glGetUniformfv = (PFNGLGETUNIFORMFVPROC)glewGetProcAddress((const GLubyte*)"glGetUniformfv")) == NULL) || r; + r = ((glGetUniformiv = (PFNGLGETUNIFORMIVPROC)glewGetProcAddress((const GLubyte*)"glGetUniformiv")) == NULL) || r; + r = ((glGetVertexAttribPointerv = (PFNGLGETVERTEXATTRIBPOINTERVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribPointerv")) == NULL) || r; + r = ((glGetVertexAttribdv = (PFNGLGETVERTEXATTRIBDVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribdv")) == NULL) || r; + r = ((glGetVertexAttribfv = (PFNGLGETVERTEXATTRIBFVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribfv")) == NULL) || r; + r = ((glGetVertexAttribiv = (PFNGLGETVERTEXATTRIBIVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribiv")) == NULL) || r; + r = ((glIsProgram = (PFNGLISPROGRAMPROC)glewGetProcAddress((const GLubyte*)"glIsProgram")) == NULL) || r; + r = ((glIsShader = (PFNGLISSHADERPROC)glewGetProcAddress((const GLubyte*)"glIsShader")) == NULL) || r; + r = ((glLinkProgram = (PFNGLLINKPROGRAMPROC)glewGetProcAddress((const GLubyte*)"glLinkProgram")) == NULL) || r; + r = ((glShaderSource = (PFNGLSHADERSOURCEPROC)glewGetProcAddress((const GLubyte*)"glShaderSource")) == NULL) || r; + r = ((glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)glewGetProcAddress((const GLubyte*)"glStencilFuncSeparate")) == NULL) || r; + r = ((glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)glewGetProcAddress((const GLubyte*)"glStencilMaskSeparate")) == NULL) || r; + r = ((glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)glewGetProcAddress((const GLubyte*)"glStencilOpSeparate")) == NULL) || r; + r = ((glUniform1f = (PFNGLUNIFORM1FPROC)glewGetProcAddress((const GLubyte*)"glUniform1f")) == NULL) || r; + r = ((glUniform1fv = (PFNGLUNIFORM1FVPROC)glewGetProcAddress((const GLubyte*)"glUniform1fv")) == NULL) || r; + r = ((glUniform1i = (PFNGLUNIFORM1IPROC)glewGetProcAddress((const GLubyte*)"glUniform1i")) == NULL) || r; + r = ((glUniform1iv = (PFNGLUNIFORM1IVPROC)glewGetProcAddress((const GLubyte*)"glUniform1iv")) == NULL) || r; + r = ((glUniform2f = (PFNGLUNIFORM2FPROC)glewGetProcAddress((const GLubyte*)"glUniform2f")) == NULL) || r; + r = ((glUniform2fv = (PFNGLUNIFORM2FVPROC)glewGetProcAddress((const GLubyte*)"glUniform2fv")) == NULL) || r; + r = ((glUniform2i = (PFNGLUNIFORM2IPROC)glewGetProcAddress((const GLubyte*)"glUniform2i")) == NULL) || r; + r = ((glUniform2iv = (PFNGLUNIFORM2IVPROC)glewGetProcAddress((const GLubyte*)"glUniform2iv")) == NULL) || r; + r = ((glUniform3f = (PFNGLUNIFORM3FPROC)glewGetProcAddress((const GLubyte*)"glUniform3f")) == NULL) || r; + r = ((glUniform3fv = (PFNGLUNIFORM3FVPROC)glewGetProcAddress((const GLubyte*)"glUniform3fv")) == NULL) || r; + r = ((glUniform3i = (PFNGLUNIFORM3IPROC)glewGetProcAddress((const GLubyte*)"glUniform3i")) == NULL) || r; + r = ((glUniform3iv = (PFNGLUNIFORM3IVPROC)glewGetProcAddress((const GLubyte*)"glUniform3iv")) == NULL) || r; + r = ((glUniform4f = (PFNGLUNIFORM4FPROC)glewGetProcAddress((const GLubyte*)"glUniform4f")) == NULL) || r; + r = ((glUniform4fv = (PFNGLUNIFORM4FVPROC)glewGetProcAddress((const GLubyte*)"glUniform4fv")) == NULL) || r; + r = ((glUniform4i = (PFNGLUNIFORM4IPROC)glewGetProcAddress((const GLubyte*)"glUniform4i")) == NULL) || r; + r = ((glUniform4iv = (PFNGLUNIFORM4IVPROC)glewGetProcAddress((const GLubyte*)"glUniform4iv")) == NULL) || r; + r = ((glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix2fv")) == NULL) || r; + r = ((glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix3fv")) == NULL) || r; + r = ((glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix4fv")) == NULL) || r; + r = ((glUseProgram = (PFNGLUSEPROGRAMPROC)glewGetProcAddress((const GLubyte*)"glUseProgram")) == NULL) || r; + r = ((glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)glewGetProcAddress((const GLubyte*)"glValidateProgram")) == NULL) || r; + r = ((glVertexAttrib1d = (PFNGLVERTEXATTRIB1DPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1d")) == NULL) || r; + r = ((glVertexAttrib1dv = (PFNGLVERTEXATTRIB1DVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1dv")) == NULL) || r; + r = ((glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1f")) == NULL) || r; + r = ((glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1fv")) == NULL) || r; + r = ((glVertexAttrib1s = (PFNGLVERTEXATTRIB1SPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1s")) == NULL) || r; + r = ((glVertexAttrib1sv = (PFNGLVERTEXATTRIB1SVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1sv")) == NULL) || r; + r = ((glVertexAttrib2d = (PFNGLVERTEXATTRIB2DPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2d")) == NULL) || r; + r = ((glVertexAttrib2dv = (PFNGLVERTEXATTRIB2DVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2dv")) == NULL) || r; + r = ((glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2f")) == NULL) || r; + r = ((glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2fv")) == NULL) || r; + r = ((glVertexAttrib2s = (PFNGLVERTEXATTRIB2SPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2s")) == NULL) || r; + r = ((glVertexAttrib2sv = (PFNGLVERTEXATTRIB2SVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2sv")) == NULL) || r; + r = ((glVertexAttrib3d = (PFNGLVERTEXATTRIB3DPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3d")) == NULL) || r; + r = ((glVertexAttrib3dv = (PFNGLVERTEXATTRIB3DVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3dv")) == NULL) || r; + r = ((glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3f")) == NULL) || r; + r = ((glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3fv")) == NULL) || r; + r = ((glVertexAttrib3s = (PFNGLVERTEXATTRIB3SPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3s")) == NULL) || r; + r = ((glVertexAttrib3sv = (PFNGLVERTEXATTRIB3SVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3sv")) == NULL) || r; + r = ((glVertexAttrib4Nbv = (PFNGLVERTEXATTRIB4NBVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4Nbv")) == NULL) || r; + r = ((glVertexAttrib4Niv = (PFNGLVERTEXATTRIB4NIVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4Niv")) == NULL) || r; + r = ((glVertexAttrib4Nsv = (PFNGLVERTEXATTRIB4NSVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4Nsv")) == NULL) || r; + r = ((glVertexAttrib4Nub = (PFNGLVERTEXATTRIB4NUBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4Nub")) == NULL) || r; + r = ((glVertexAttrib4Nubv = (PFNGLVERTEXATTRIB4NUBVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4Nubv")) == NULL) || r; + r = ((glVertexAttrib4Nuiv = (PFNGLVERTEXATTRIB4NUIVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4Nuiv")) == NULL) || r; + r = ((glVertexAttrib4Nusv = (PFNGLVERTEXATTRIB4NUSVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4Nusv")) == NULL) || r; + r = ((glVertexAttrib4bv = (PFNGLVERTEXATTRIB4BVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4bv")) == NULL) || r; + r = ((glVertexAttrib4d = (PFNGLVERTEXATTRIB4DPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4d")) == NULL) || r; + r = ((glVertexAttrib4dv = (PFNGLVERTEXATTRIB4DVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4dv")) == NULL) || r; + r = ((glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4f")) == NULL) || r; + r = ((glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4fv")) == NULL) || r; + r = ((glVertexAttrib4iv = (PFNGLVERTEXATTRIB4IVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4iv")) == NULL) || r; + r = ((glVertexAttrib4s = (PFNGLVERTEXATTRIB4SPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4s")) == NULL) || r; + r = ((glVertexAttrib4sv = (PFNGLVERTEXATTRIB4SVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4sv")) == NULL) || r; + r = ((glVertexAttrib4ubv = (PFNGLVERTEXATTRIB4UBVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4ubv")) == NULL) || r; + r = ((glVertexAttrib4uiv = (PFNGLVERTEXATTRIB4UIVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4uiv")) == NULL) || r; + r = ((glVertexAttrib4usv = (PFNGLVERTEXATTRIB4USVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4usv")) == NULL) || r; + r = ((glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribPointer")) == NULL) || r; + + return r; +} + +#endif /* GL_VERSION_2_0 */ + +#ifdef GL_VERSION_2_1 + +static GLboolean _glewInit_GL_VERSION_2_1 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix2x3fv")) == NULL) || r; + r = ((glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix2x4fv")) == NULL) || r; + r = ((glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix3x2fv")) == NULL) || r; + r = ((glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix3x4fv")) == NULL) || r; + r = ((glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix4x2fv")) == NULL) || r; + r = ((glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix4x3fv")) == NULL) || r; + + return r; +} + +#endif /* GL_VERSION_2_1 */ + +#ifdef GL_VERSION_3_0 + +static GLboolean _glewInit_GL_VERSION_3_0 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginConditionalRender = (PFNGLBEGINCONDITIONALRENDERPROC)glewGetProcAddress((const GLubyte*)"glBeginConditionalRender")) == NULL) || r; + r = ((glBeginTransformFeedback = (PFNGLBEGINTRANSFORMFEEDBACKPROC)glewGetProcAddress((const GLubyte*)"glBeginTransformFeedback")) == NULL) || r; + r = ((glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)glewGetProcAddress((const GLubyte*)"glBindBufferBase")) == NULL) || r; + r = ((glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)glewGetProcAddress((const GLubyte*)"glBindBufferRange")) == NULL) || r; + r = ((glBindFragDataLocation = (PFNGLBINDFRAGDATALOCATIONPROC)glewGetProcAddress((const GLubyte*)"glBindFragDataLocation")) == NULL) || r; + r = ((glClampColor = (PFNGLCLAMPCOLORPROC)glewGetProcAddress((const GLubyte*)"glClampColor")) == NULL) || r; + r = ((glClearBufferfi = (PFNGLCLEARBUFFERFIPROC)glewGetProcAddress((const GLubyte*)"glClearBufferfi")) == NULL) || r; + r = ((glClearBufferfv = (PFNGLCLEARBUFFERFVPROC)glewGetProcAddress((const GLubyte*)"glClearBufferfv")) == NULL) || r; + r = ((glClearBufferiv = (PFNGLCLEARBUFFERIVPROC)glewGetProcAddress((const GLubyte*)"glClearBufferiv")) == NULL) || r; + r = ((glClearBufferuiv = (PFNGLCLEARBUFFERUIVPROC)glewGetProcAddress((const GLubyte*)"glClearBufferuiv")) == NULL) || r; + r = ((glColorMaski = (PFNGLCOLORMASKIPROC)glewGetProcAddress((const GLubyte*)"glColorMaski")) == NULL) || r; + r = ((glDisablei = (PFNGLDISABLEIPROC)glewGetProcAddress((const GLubyte*)"glDisablei")) == NULL) || r; + r = ((glEnablei = (PFNGLENABLEIPROC)glewGetProcAddress((const GLubyte*)"glEnablei")) == NULL) || r; + r = ((glEndConditionalRender = (PFNGLENDCONDITIONALRENDERPROC)glewGetProcAddress((const GLubyte*)"glEndConditionalRender")) == NULL) || r; + r = ((glEndTransformFeedback = (PFNGLENDTRANSFORMFEEDBACKPROC)glewGetProcAddress((const GLubyte*)"glEndTransformFeedback")) == NULL) || r; + r = ((glGetBooleani_v = (PFNGLGETBOOLEANI_VPROC)glewGetProcAddress((const GLubyte*)"glGetBooleani_v")) == NULL) || r; + r = ((glGetFragDataLocation = (PFNGLGETFRAGDATALOCATIONPROC)glewGetProcAddress((const GLubyte*)"glGetFragDataLocation")) == NULL) || r; + r = ((glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)glewGetProcAddress((const GLubyte*)"glGetIntegeri_v")) == NULL) || r; + r = ((glGetStringi = (PFNGLGETSTRINGIPROC)glewGetProcAddress((const GLubyte*)"glGetStringi")) == NULL) || r; + r = ((glGetTexParameterIiv = (PFNGLGETTEXPARAMETERIIVPROC)glewGetProcAddress((const GLubyte*)"glGetTexParameterIiv")) == NULL) || r; + r = ((glGetTexParameterIuiv = (PFNGLGETTEXPARAMETERIUIVPROC)glewGetProcAddress((const GLubyte*)"glGetTexParameterIuiv")) == NULL) || r; + r = ((glGetTransformFeedbackVarying = (PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)glewGetProcAddress((const GLubyte*)"glGetTransformFeedbackVarying")) == NULL) || r; + r = ((glGetUniformuiv = (PFNGLGETUNIFORMUIVPROC)glewGetProcAddress((const GLubyte*)"glGetUniformuiv")) == NULL) || r; + r = ((glGetVertexAttribIiv = (PFNGLGETVERTEXATTRIBIIVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribIiv")) == NULL) || r; + r = ((glGetVertexAttribIuiv = (PFNGLGETVERTEXATTRIBIUIVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribIuiv")) == NULL) || r; + r = ((glIsEnabledi = (PFNGLISENABLEDIPROC)glewGetProcAddress((const GLubyte*)"glIsEnabledi")) == NULL) || r; + r = ((glTexParameterIiv = (PFNGLTEXPARAMETERIIVPROC)glewGetProcAddress((const GLubyte*)"glTexParameterIiv")) == NULL) || r; + r = ((glTexParameterIuiv = (PFNGLTEXPARAMETERIUIVPROC)glewGetProcAddress((const GLubyte*)"glTexParameterIuiv")) == NULL) || r; + r = ((glTransformFeedbackVaryings = (PFNGLTRANSFORMFEEDBACKVARYINGSPROC)glewGetProcAddress((const GLubyte*)"glTransformFeedbackVaryings")) == NULL) || r; + r = ((glUniform1ui = (PFNGLUNIFORM1UIPROC)glewGetProcAddress((const GLubyte*)"glUniform1ui")) == NULL) || r; + r = ((glUniform1uiv = (PFNGLUNIFORM1UIVPROC)glewGetProcAddress((const GLubyte*)"glUniform1uiv")) == NULL) || r; + r = ((glUniform2ui = (PFNGLUNIFORM2UIPROC)glewGetProcAddress((const GLubyte*)"glUniform2ui")) == NULL) || r; + r = ((glUniform2uiv = (PFNGLUNIFORM2UIVPROC)glewGetProcAddress((const GLubyte*)"glUniform2uiv")) == NULL) || r; + r = ((glUniform3ui = (PFNGLUNIFORM3UIPROC)glewGetProcAddress((const GLubyte*)"glUniform3ui")) == NULL) || r; + r = ((glUniform3uiv = (PFNGLUNIFORM3UIVPROC)glewGetProcAddress((const GLubyte*)"glUniform3uiv")) == NULL) || r; + r = ((glUniform4ui = (PFNGLUNIFORM4UIPROC)glewGetProcAddress((const GLubyte*)"glUniform4ui")) == NULL) || r; + r = ((glUniform4uiv = (PFNGLUNIFORM4UIVPROC)glewGetProcAddress((const GLubyte*)"glUniform4uiv")) == NULL) || r; + r = ((glVertexAttribI1i = (PFNGLVERTEXATTRIBI1IPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1i")) == NULL) || r; + r = ((glVertexAttribI1iv = (PFNGLVERTEXATTRIBI1IVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1iv")) == NULL) || r; + r = ((glVertexAttribI1ui = (PFNGLVERTEXATTRIBI1UIPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1ui")) == NULL) || r; + r = ((glVertexAttribI1uiv = (PFNGLVERTEXATTRIBI1UIVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1uiv")) == NULL) || r; + r = ((glVertexAttribI2i = (PFNGLVERTEXATTRIBI2IPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2i")) == NULL) || r; + r = ((glVertexAttribI2iv = (PFNGLVERTEXATTRIBI2IVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2iv")) == NULL) || r; + r = ((glVertexAttribI2ui = (PFNGLVERTEXATTRIBI2UIPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2ui")) == NULL) || r; + r = ((glVertexAttribI2uiv = (PFNGLVERTEXATTRIBI2UIVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2uiv")) == NULL) || r; + r = ((glVertexAttribI3i = (PFNGLVERTEXATTRIBI3IPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3i")) == NULL) || r; + r = ((glVertexAttribI3iv = (PFNGLVERTEXATTRIBI3IVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3iv")) == NULL) || r; + r = ((glVertexAttribI3ui = (PFNGLVERTEXATTRIBI3UIPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3ui")) == NULL) || r; + r = ((glVertexAttribI3uiv = (PFNGLVERTEXATTRIBI3UIVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3uiv")) == NULL) || r; + r = ((glVertexAttribI4bv = (PFNGLVERTEXATTRIBI4BVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4bv")) == NULL) || r; + r = ((glVertexAttribI4i = (PFNGLVERTEXATTRIBI4IPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4i")) == NULL) || r; + r = ((glVertexAttribI4iv = (PFNGLVERTEXATTRIBI4IVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4iv")) == NULL) || r; + r = ((glVertexAttribI4sv = (PFNGLVERTEXATTRIBI4SVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4sv")) == NULL) || r; + r = ((glVertexAttribI4ubv = (PFNGLVERTEXATTRIBI4UBVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4ubv")) == NULL) || r; + r = ((glVertexAttribI4ui = (PFNGLVERTEXATTRIBI4UIPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4ui")) == NULL) || r; + r = ((glVertexAttribI4uiv = (PFNGLVERTEXATTRIBI4UIVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4uiv")) == NULL) || r; + r = ((glVertexAttribI4usv = (PFNGLVERTEXATTRIBI4USVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4usv")) == NULL) || r; + r = ((glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribIPointer")) == NULL) || r; + + return r; +} + +#endif /* GL_VERSION_3_0 */ + +#ifdef GL_3DFX_multisample + +#endif /* GL_3DFX_multisample */ + +#ifdef GL_3DFX_tbuffer + +static GLboolean _glewInit_GL_3DFX_tbuffer (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTbufferMask3DFX = (PFNGLTBUFFERMASK3DFXPROC)glewGetProcAddress((const GLubyte*)"glTbufferMask3DFX")) == NULL) || r; + + return r; +} + +#endif /* GL_3DFX_tbuffer */ + +#ifdef GL_3DFX_texture_compression_FXT1 + +#endif /* GL_3DFX_texture_compression_FXT1 */ + +#ifdef GL_APPLE_client_storage + +#endif /* GL_APPLE_client_storage */ + +#ifdef GL_APPLE_element_array + +static GLboolean _glewInit_GL_APPLE_element_array (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDrawElementArrayAPPLE = (PFNGLDRAWELEMENTARRAYAPPLEPROC)glewGetProcAddress((const GLubyte*)"glDrawElementArrayAPPLE")) == NULL) || r; + r = ((glDrawRangeElementArrayAPPLE = (PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC)glewGetProcAddress((const GLubyte*)"glDrawRangeElementArrayAPPLE")) == NULL) || r; + r = ((glElementPointerAPPLE = (PFNGLELEMENTPOINTERAPPLEPROC)glewGetProcAddress((const GLubyte*)"glElementPointerAPPLE")) == NULL) || r; + r = ((glMultiDrawElementArrayAPPLE = (PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC)glewGetProcAddress((const GLubyte*)"glMultiDrawElementArrayAPPLE")) == NULL) || r; + r = ((glMultiDrawRangeElementArrayAPPLE = (PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC)glewGetProcAddress((const GLubyte*)"glMultiDrawRangeElementArrayAPPLE")) == NULL) || r; + + return r; +} + +#endif /* GL_APPLE_element_array */ + +#ifdef GL_APPLE_fence + +static GLboolean _glewInit_GL_APPLE_fence (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDeleteFencesAPPLE = (PFNGLDELETEFENCESAPPLEPROC)glewGetProcAddress((const GLubyte*)"glDeleteFencesAPPLE")) == NULL) || r; + r = ((glFinishFenceAPPLE = (PFNGLFINISHFENCEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glFinishFenceAPPLE")) == NULL) || r; + r = ((glFinishObjectAPPLE = (PFNGLFINISHOBJECTAPPLEPROC)glewGetProcAddress((const GLubyte*)"glFinishObjectAPPLE")) == NULL) || r; + r = ((glGenFencesAPPLE = (PFNGLGENFENCESAPPLEPROC)glewGetProcAddress((const GLubyte*)"glGenFencesAPPLE")) == NULL) || r; + r = ((glIsFenceAPPLE = (PFNGLISFENCEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glIsFenceAPPLE")) == NULL) || r; + r = ((glSetFenceAPPLE = (PFNGLSETFENCEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glSetFenceAPPLE")) == NULL) || r; + r = ((glTestFenceAPPLE = (PFNGLTESTFENCEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glTestFenceAPPLE")) == NULL) || r; + r = ((glTestObjectAPPLE = (PFNGLTESTOBJECTAPPLEPROC)glewGetProcAddress((const GLubyte*)"glTestObjectAPPLE")) == NULL) || r; + + return r; +} + +#endif /* GL_APPLE_fence */ + +#ifdef GL_APPLE_float_pixels + +#endif /* GL_APPLE_float_pixels */ + +#ifdef GL_APPLE_flush_buffer_range + +static GLboolean _glewInit_GL_APPLE_flush_buffer_range (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBufferParameteriAPPLE = (PFNGLBUFFERPARAMETERIAPPLEPROC)glewGetProcAddress((const GLubyte*)"glBufferParameteriAPPLE")) == NULL) || r; + r = ((glFlushMappedBufferRangeAPPLE = (PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glFlushMappedBufferRangeAPPLE")) == NULL) || r; + + return r; +} + +#endif /* GL_APPLE_flush_buffer_range */ + +#ifdef GL_APPLE_pixel_buffer + +#endif /* GL_APPLE_pixel_buffer */ + +#ifdef GL_APPLE_specular_vector + +#endif /* GL_APPLE_specular_vector */ + +#ifdef GL_APPLE_texture_range + +static GLboolean _glewInit_GL_APPLE_texture_range (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetTexParameterPointervAPPLE = (PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC)glewGetProcAddress((const GLubyte*)"glGetTexParameterPointervAPPLE")) == NULL) || r; + r = ((glTextureRangeAPPLE = (PFNGLTEXTURERANGEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glTextureRangeAPPLE")) == NULL) || r; + + return r; +} + +#endif /* GL_APPLE_texture_range */ + +#ifdef GL_APPLE_transform_hint + +#endif /* GL_APPLE_transform_hint */ + +#ifdef GL_APPLE_vertex_array_object + +static GLboolean _glewInit_GL_APPLE_vertex_array_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindVertexArrayAPPLE = (PFNGLBINDVERTEXARRAYAPPLEPROC)glewGetProcAddress((const GLubyte*)"glBindVertexArrayAPPLE")) == NULL) || r; + r = ((glDeleteVertexArraysAPPLE = (PFNGLDELETEVERTEXARRAYSAPPLEPROC)glewGetProcAddress((const GLubyte*)"glDeleteVertexArraysAPPLE")) == NULL) || r; + r = ((glGenVertexArraysAPPLE = (PFNGLGENVERTEXARRAYSAPPLEPROC)glewGetProcAddress((const GLubyte*)"glGenVertexArraysAPPLE")) == NULL) || r; + r = ((glIsVertexArrayAPPLE = (PFNGLISVERTEXARRAYAPPLEPROC)glewGetProcAddress((const GLubyte*)"glIsVertexArrayAPPLE")) == NULL) || r; + + return r; +} + +#endif /* GL_APPLE_vertex_array_object */ + +#ifdef GL_APPLE_vertex_array_range + +static GLboolean _glewInit_GL_APPLE_vertex_array_range (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFlushVertexArrayRangeAPPLE = (PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glFlushVertexArrayRangeAPPLE")) == NULL) || r; + r = ((glVertexArrayParameteriAPPLE = (PFNGLVERTEXARRAYPARAMETERIAPPLEPROC)glewGetProcAddress((const GLubyte*)"glVertexArrayParameteriAPPLE")) == NULL) || r; + r = ((glVertexArrayRangeAPPLE = (PFNGLVERTEXARRAYRANGEAPPLEPROC)glewGetProcAddress((const GLubyte*)"glVertexArrayRangeAPPLE")) == NULL) || r; + + return r; +} + +#endif /* GL_APPLE_vertex_array_range */ + +#ifdef GL_APPLE_ycbcr_422 + +#endif /* GL_APPLE_ycbcr_422 */ + +#ifdef GL_ARB_color_buffer_float + +static GLboolean _glewInit_GL_ARB_color_buffer_float (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glClampColorARB = (PFNGLCLAMPCOLORARBPROC)glewGetProcAddress((const GLubyte*)"glClampColorARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_color_buffer_float */ + +#ifdef GL_ARB_depth_buffer_float + +#endif /* GL_ARB_depth_buffer_float */ + +#ifdef GL_ARB_depth_texture + +#endif /* GL_ARB_depth_texture */ + +#ifdef GL_ARB_draw_buffers + +static GLboolean _glewInit_GL_ARB_draw_buffers (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDrawBuffersARB = (PFNGLDRAWBUFFERSARBPROC)glewGetProcAddress((const GLubyte*)"glDrawBuffersARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_draw_buffers */ + +#ifdef GL_ARB_draw_instanced + +static GLboolean _glewInit_GL_ARB_draw_instanced (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDrawArraysInstancedARB = (PFNGLDRAWARRAYSINSTANCEDARBPROC)glewGetProcAddress((const GLubyte*)"glDrawArraysInstancedARB")) == NULL) || r; + r = ((glDrawElementsInstancedARB = (PFNGLDRAWELEMENTSINSTANCEDARBPROC)glewGetProcAddress((const GLubyte*)"glDrawElementsInstancedARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_draw_instanced */ + +#ifdef GL_ARB_fragment_program + +#endif /* GL_ARB_fragment_program */ + +#ifdef GL_ARB_fragment_program_shadow + +#endif /* GL_ARB_fragment_program_shadow */ + +#ifdef GL_ARB_fragment_shader + +#endif /* GL_ARB_fragment_shader */ + +#ifdef GL_ARB_framebuffer_object + +static GLboolean _glewInit_GL_ARB_framebuffer_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)glewGetProcAddress((const GLubyte*)"glBindFramebuffer")) == NULL) || r; + r = ((glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)glewGetProcAddress((const GLubyte*)"glBindRenderbuffer")) == NULL) || r; + r = ((glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)glewGetProcAddress((const GLubyte*)"glBlitFramebuffer")) == NULL) || r; + r = ((glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)glewGetProcAddress((const GLubyte*)"glCheckFramebufferStatus")) == NULL) || r; + r = ((glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)glewGetProcAddress((const GLubyte*)"glDeleteFramebuffers")) == NULL) || r; + r = ((glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)glewGetProcAddress((const GLubyte*)"glDeleteRenderbuffers")) == NULL) || r; + r = ((glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)glewGetProcAddress((const GLubyte*)"glFramebufferRenderbuffer")) == NULL) || r; + r = ((glFramebufferTexturLayer = (PFNGLFRAMEBUFFERTEXTURLAYERPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTexturLayer")) == NULL) || r; + r = ((glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTexture1D")) == NULL) || r; + r = ((glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTexture2D")) == NULL) || r; + r = ((glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTexture3D")) == NULL) || r; + r = ((glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)glewGetProcAddress((const GLubyte*)"glGenFramebuffers")) == NULL) || r; + r = ((glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)glewGetProcAddress((const GLubyte*)"glGenRenderbuffers")) == NULL) || r; + r = ((glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)glewGetProcAddress((const GLubyte*)"glGenerateMipmap")) == NULL) || r; + r = ((glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glGetFramebufferAttachmentParameteriv")) == NULL) || r; + r = ((glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glGetRenderbufferParameteriv")) == NULL) || r; + r = ((glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)glewGetProcAddress((const GLubyte*)"glIsFramebuffer")) == NULL) || r; + r = ((glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)glewGetProcAddress((const GLubyte*)"glIsRenderbuffer")) == NULL) || r; + r = ((glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)glewGetProcAddress((const GLubyte*)"glRenderbufferStorage")) == NULL) || r; + r = ((glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)glewGetProcAddress((const GLubyte*)"glRenderbufferStorageMultisample")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_framebuffer_object */ + +#ifdef GL_ARB_framebuffer_sRGB + +#endif /* GL_ARB_framebuffer_sRGB */ + +#ifdef GL_ARB_geometry_shader4 + +static GLboolean _glewInit_GL_ARB_geometry_shader4 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFramebufferTextureARB = (PFNGLFRAMEBUFFERTEXTUREARBPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTextureARB")) == NULL) || r; + r = ((glFramebufferTextureFaceARB = (PFNGLFRAMEBUFFERTEXTUREFACEARBPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTextureFaceARB")) == NULL) || r; + r = ((glFramebufferTextureLayerARB = (PFNGLFRAMEBUFFERTEXTURELAYERARBPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTextureLayerARB")) == NULL) || r; + r = ((glProgramParameteriARB = (PFNGLPROGRAMPARAMETERIARBPROC)glewGetProcAddress((const GLubyte*)"glProgramParameteriARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_geometry_shader4 */ + +#ifdef GL_ARB_half_float_pixel + +#endif /* GL_ARB_half_float_pixel */ + +#ifdef GL_ARB_half_float_vertex + +#endif /* GL_ARB_half_float_vertex */ + +#ifdef GL_ARB_imaging + +static GLboolean _glewInit_GL_ARB_imaging (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBlendEquation = (PFNGLBLENDEQUATIONPROC)glewGetProcAddress((const GLubyte*)"glBlendEquation")) == NULL) || r; + r = ((glColorSubTable = (PFNGLCOLORSUBTABLEPROC)glewGetProcAddress((const GLubyte*)"glColorSubTable")) == NULL) || r; + r = ((glColorTable = (PFNGLCOLORTABLEPROC)glewGetProcAddress((const GLubyte*)"glColorTable")) == NULL) || r; + r = ((glColorTableParameterfv = (PFNGLCOLORTABLEPARAMETERFVPROC)glewGetProcAddress((const GLubyte*)"glColorTableParameterfv")) == NULL) || r; + r = ((glColorTableParameteriv = (PFNGLCOLORTABLEPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glColorTableParameteriv")) == NULL) || r; + r = ((glConvolutionFilter1D = (PFNGLCONVOLUTIONFILTER1DPROC)glewGetProcAddress((const GLubyte*)"glConvolutionFilter1D")) == NULL) || r; + r = ((glConvolutionFilter2D = (PFNGLCONVOLUTIONFILTER2DPROC)glewGetProcAddress((const GLubyte*)"glConvolutionFilter2D")) == NULL) || r; + r = ((glConvolutionParameterf = (PFNGLCONVOLUTIONPARAMETERFPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameterf")) == NULL) || r; + r = ((glConvolutionParameterfv = (PFNGLCONVOLUTIONPARAMETERFVPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameterfv")) == NULL) || r; + r = ((glConvolutionParameteri = (PFNGLCONVOLUTIONPARAMETERIPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameteri")) == NULL) || r; + r = ((glConvolutionParameteriv = (PFNGLCONVOLUTIONPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameteriv")) == NULL) || r; + r = ((glCopyColorSubTable = (PFNGLCOPYCOLORSUBTABLEPROC)glewGetProcAddress((const GLubyte*)"glCopyColorSubTable")) == NULL) || r; + r = ((glCopyColorTable = (PFNGLCOPYCOLORTABLEPROC)glewGetProcAddress((const GLubyte*)"glCopyColorTable")) == NULL) || r; + r = ((glCopyConvolutionFilter1D = (PFNGLCOPYCONVOLUTIONFILTER1DPROC)glewGetProcAddress((const GLubyte*)"glCopyConvolutionFilter1D")) == NULL) || r; + r = ((glCopyConvolutionFilter2D = (PFNGLCOPYCONVOLUTIONFILTER2DPROC)glewGetProcAddress((const GLubyte*)"glCopyConvolutionFilter2D")) == NULL) || r; + r = ((glGetColorTable = (PFNGLGETCOLORTABLEPROC)glewGetProcAddress((const GLubyte*)"glGetColorTable")) == NULL) || r; + r = ((glGetColorTableParameterfv = (PFNGLGETCOLORTABLEPARAMETERFVPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableParameterfv")) == NULL) || r; + r = ((glGetColorTableParameteriv = (PFNGLGETCOLORTABLEPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableParameteriv")) == NULL) || r; + r = ((glGetConvolutionFilter = (PFNGLGETCONVOLUTIONFILTERPROC)glewGetProcAddress((const GLubyte*)"glGetConvolutionFilter")) == NULL) || r; + r = ((glGetConvolutionParameterfv = (PFNGLGETCONVOLUTIONPARAMETERFVPROC)glewGetProcAddress((const GLubyte*)"glGetConvolutionParameterfv")) == NULL) || r; + r = ((glGetConvolutionParameteriv = (PFNGLGETCONVOLUTIONPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glGetConvolutionParameteriv")) == NULL) || r; + r = ((glGetHistogram = (PFNGLGETHISTOGRAMPROC)glewGetProcAddress((const GLubyte*)"glGetHistogram")) == NULL) || r; + r = ((glGetHistogramParameterfv = (PFNGLGETHISTOGRAMPARAMETERFVPROC)glewGetProcAddress((const GLubyte*)"glGetHistogramParameterfv")) == NULL) || r; + r = ((glGetHistogramParameteriv = (PFNGLGETHISTOGRAMPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glGetHistogramParameteriv")) == NULL) || r; + r = ((glGetMinmax = (PFNGLGETMINMAXPROC)glewGetProcAddress((const GLubyte*)"glGetMinmax")) == NULL) || r; + r = ((glGetMinmaxParameterfv = (PFNGLGETMINMAXPARAMETERFVPROC)glewGetProcAddress((const GLubyte*)"glGetMinmaxParameterfv")) == NULL) || r; + r = ((glGetMinmaxParameteriv = (PFNGLGETMINMAXPARAMETERIVPROC)glewGetProcAddress((const GLubyte*)"glGetMinmaxParameteriv")) == NULL) || r; + r = ((glGetSeparableFilter = (PFNGLGETSEPARABLEFILTERPROC)glewGetProcAddress((const GLubyte*)"glGetSeparableFilter")) == NULL) || r; + r = ((glHistogram = (PFNGLHISTOGRAMPROC)glewGetProcAddress((const GLubyte*)"glHistogram")) == NULL) || r; + r = ((glMinmax = (PFNGLMINMAXPROC)glewGetProcAddress((const GLubyte*)"glMinmax")) == NULL) || r; + r = ((glResetHistogram = (PFNGLRESETHISTOGRAMPROC)glewGetProcAddress((const GLubyte*)"glResetHistogram")) == NULL) || r; + r = ((glResetMinmax = (PFNGLRESETMINMAXPROC)glewGetProcAddress((const GLubyte*)"glResetMinmax")) == NULL) || r; + r = ((glSeparableFilter2D = (PFNGLSEPARABLEFILTER2DPROC)glewGetProcAddress((const GLubyte*)"glSeparableFilter2D")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_imaging */ + +#ifdef GL_ARB_instanced_arrays + +static GLboolean _glewInit_GL_ARB_instanced_arrays (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glVertexAttribDivisorARB = (PFNGLVERTEXATTRIBDIVISORARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribDivisorARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_instanced_arrays */ + +#ifdef GL_ARB_map_buffer_range + +static GLboolean _glewInit_GL_ARB_map_buffer_range (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)glewGetProcAddress((const GLubyte*)"glFlushMappedBufferRange")) == NULL) || r; + r = ((glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)glewGetProcAddress((const GLubyte*)"glMapBufferRange")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_map_buffer_range */ + +#ifdef GL_ARB_matrix_palette + +static GLboolean _glewInit_GL_ARB_matrix_palette (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glCurrentPaletteMatrixARB = (PFNGLCURRENTPALETTEMATRIXARBPROC)glewGetProcAddress((const GLubyte*)"glCurrentPaletteMatrixARB")) == NULL) || r; + r = ((glMatrixIndexPointerARB = (PFNGLMATRIXINDEXPOINTERARBPROC)glewGetProcAddress((const GLubyte*)"glMatrixIndexPointerARB")) == NULL) || r; + r = ((glMatrixIndexubvARB = (PFNGLMATRIXINDEXUBVARBPROC)glewGetProcAddress((const GLubyte*)"glMatrixIndexubvARB")) == NULL) || r; + r = ((glMatrixIndexuivARB = (PFNGLMATRIXINDEXUIVARBPROC)glewGetProcAddress((const GLubyte*)"glMatrixIndexuivARB")) == NULL) || r; + r = ((glMatrixIndexusvARB = (PFNGLMATRIXINDEXUSVARBPROC)glewGetProcAddress((const GLubyte*)"glMatrixIndexusvARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_matrix_palette */ + +#ifdef GL_ARB_multisample + +static GLboolean _glewInit_GL_ARB_multisample (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glSampleCoverageARB = (PFNGLSAMPLECOVERAGEARBPROC)glewGetProcAddress((const GLubyte*)"glSampleCoverageARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_multisample */ + +#ifdef GL_ARB_multitexture + +static GLboolean _glewInit_GL_ARB_multitexture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)glewGetProcAddress((const GLubyte*)"glActiveTextureARB")) == NULL) || r; + r = ((glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)glewGetProcAddress((const GLubyte*)"glClientActiveTextureARB")) == NULL) || r; + r = ((glMultiTexCoord1dARB = (PFNGLMULTITEXCOORD1DARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1dARB")) == NULL) || r; + r = ((glMultiTexCoord1dvARB = (PFNGLMULTITEXCOORD1DVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1dvARB")) == NULL) || r; + r = ((glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1fARB")) == NULL) || r; + r = ((glMultiTexCoord1fvARB = (PFNGLMULTITEXCOORD1FVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1fvARB")) == NULL) || r; + r = ((glMultiTexCoord1iARB = (PFNGLMULTITEXCOORD1IARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1iARB")) == NULL) || r; + r = ((glMultiTexCoord1ivARB = (PFNGLMULTITEXCOORD1IVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1ivARB")) == NULL) || r; + r = ((glMultiTexCoord1sARB = (PFNGLMULTITEXCOORD1SARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1sARB")) == NULL) || r; + r = ((glMultiTexCoord1svARB = (PFNGLMULTITEXCOORD1SVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1svARB")) == NULL) || r; + r = ((glMultiTexCoord2dARB = (PFNGLMULTITEXCOORD2DARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2dARB")) == NULL) || r; + r = ((glMultiTexCoord2dvARB = (PFNGLMULTITEXCOORD2DVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2dvARB")) == NULL) || r; + r = ((glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2fARB")) == NULL) || r; + r = ((glMultiTexCoord2fvARB = (PFNGLMULTITEXCOORD2FVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2fvARB")) == NULL) || r; + r = ((glMultiTexCoord2iARB = (PFNGLMULTITEXCOORD2IARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2iARB")) == NULL) || r; + r = ((glMultiTexCoord2ivARB = (PFNGLMULTITEXCOORD2IVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2ivARB")) == NULL) || r; + r = ((glMultiTexCoord2sARB = (PFNGLMULTITEXCOORD2SARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2sARB")) == NULL) || r; + r = ((glMultiTexCoord2svARB = (PFNGLMULTITEXCOORD2SVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2svARB")) == NULL) || r; + r = ((glMultiTexCoord3dARB = (PFNGLMULTITEXCOORD3DARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3dARB")) == NULL) || r; + r = ((glMultiTexCoord3dvARB = (PFNGLMULTITEXCOORD3DVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3dvARB")) == NULL) || r; + r = ((glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3fARB")) == NULL) || r; + r = ((glMultiTexCoord3fvARB = (PFNGLMULTITEXCOORD3FVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3fvARB")) == NULL) || r; + r = ((glMultiTexCoord3iARB = (PFNGLMULTITEXCOORD3IARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3iARB")) == NULL) || r; + r = ((glMultiTexCoord3ivARB = (PFNGLMULTITEXCOORD3IVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3ivARB")) == NULL) || r; + r = ((glMultiTexCoord3sARB = (PFNGLMULTITEXCOORD3SARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3sARB")) == NULL) || r; + r = ((glMultiTexCoord3svARB = (PFNGLMULTITEXCOORD3SVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3svARB")) == NULL) || r; + r = ((glMultiTexCoord4dARB = (PFNGLMULTITEXCOORD4DARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4dARB")) == NULL) || r; + r = ((glMultiTexCoord4dvARB = (PFNGLMULTITEXCOORD4DVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4dvARB")) == NULL) || r; + r = ((glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4fARB")) == NULL) || r; + r = ((glMultiTexCoord4fvARB = (PFNGLMULTITEXCOORD4FVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4fvARB")) == NULL) || r; + r = ((glMultiTexCoord4iARB = (PFNGLMULTITEXCOORD4IARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4iARB")) == NULL) || r; + r = ((glMultiTexCoord4ivARB = (PFNGLMULTITEXCOORD4IVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4ivARB")) == NULL) || r; + r = ((glMultiTexCoord4sARB = (PFNGLMULTITEXCOORD4SARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4sARB")) == NULL) || r; + r = ((glMultiTexCoord4svARB = (PFNGLMULTITEXCOORD4SVARBPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4svARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_multitexture */ + +#ifdef GL_ARB_occlusion_query + +static GLboolean _glewInit_GL_ARB_occlusion_query (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginQueryARB = (PFNGLBEGINQUERYARBPROC)glewGetProcAddress((const GLubyte*)"glBeginQueryARB")) == NULL) || r; + r = ((glDeleteQueriesARB = (PFNGLDELETEQUERIESARBPROC)glewGetProcAddress((const GLubyte*)"glDeleteQueriesARB")) == NULL) || r; + r = ((glEndQueryARB = (PFNGLENDQUERYARBPROC)glewGetProcAddress((const GLubyte*)"glEndQueryARB")) == NULL) || r; + r = ((glGenQueriesARB = (PFNGLGENQUERIESARBPROC)glewGetProcAddress((const GLubyte*)"glGenQueriesARB")) == NULL) || r; + r = ((glGetQueryObjectivARB = (PFNGLGETQUERYOBJECTIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetQueryObjectivARB")) == NULL) || r; + r = ((glGetQueryObjectuivARB = (PFNGLGETQUERYOBJECTUIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetQueryObjectuivARB")) == NULL) || r; + r = ((glGetQueryivARB = (PFNGLGETQUERYIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetQueryivARB")) == NULL) || r; + r = ((glIsQueryARB = (PFNGLISQUERYARBPROC)glewGetProcAddress((const GLubyte*)"glIsQueryARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_occlusion_query */ + +#ifdef GL_ARB_pixel_buffer_object + +#endif /* GL_ARB_pixel_buffer_object */ + +#ifdef GL_ARB_point_parameters + +static GLboolean _glewInit_GL_ARB_point_parameters (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glPointParameterfARB = (PFNGLPOINTPARAMETERFARBPROC)glewGetProcAddress((const GLubyte*)"glPointParameterfARB")) == NULL) || r; + r = ((glPointParameterfvARB = (PFNGLPOINTPARAMETERFVARBPROC)glewGetProcAddress((const GLubyte*)"glPointParameterfvARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_point_parameters */ + +#ifdef GL_ARB_point_sprite + +#endif /* GL_ARB_point_sprite */ + +#ifdef GL_ARB_shader_objects + +static GLboolean _glewInit_GL_ARB_shader_objects (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)glewGetProcAddress((const GLubyte*)"glAttachObjectARB")) == NULL) || r; + r = ((glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)glewGetProcAddress((const GLubyte*)"glCompileShaderARB")) == NULL) || r; + r = ((glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)glewGetProcAddress((const GLubyte*)"glCreateProgramObjectARB")) == NULL) || r; + r = ((glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)glewGetProcAddress((const GLubyte*)"glCreateShaderObjectARB")) == NULL) || r; + r = ((glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC)glewGetProcAddress((const GLubyte*)"glDeleteObjectARB")) == NULL) || r; + r = ((glDetachObjectARB = (PFNGLDETACHOBJECTARBPROC)glewGetProcAddress((const GLubyte*)"glDetachObjectARB")) == NULL) || r; + r = ((glGetActiveUniformARB = (PFNGLGETACTIVEUNIFORMARBPROC)glewGetProcAddress((const GLubyte*)"glGetActiveUniformARB")) == NULL) || r; + r = ((glGetAttachedObjectsARB = (PFNGLGETATTACHEDOBJECTSARBPROC)glewGetProcAddress((const GLubyte*)"glGetAttachedObjectsARB")) == NULL) || r; + r = ((glGetHandleARB = (PFNGLGETHANDLEARBPROC)glewGetProcAddress((const GLubyte*)"glGetHandleARB")) == NULL) || r; + r = ((glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)glewGetProcAddress((const GLubyte*)"glGetInfoLogARB")) == NULL) || r; + r = ((glGetObjectParameterfvARB = (PFNGLGETOBJECTPARAMETERFVARBPROC)glewGetProcAddress((const GLubyte*)"glGetObjectParameterfvARB")) == NULL) || r; + r = ((glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetObjectParameterivARB")) == NULL) || r; + r = ((glGetShaderSourceARB = (PFNGLGETSHADERSOURCEARBPROC)glewGetProcAddress((const GLubyte*)"glGetShaderSourceARB")) == NULL) || r; + r = ((glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)glewGetProcAddress((const GLubyte*)"glGetUniformLocationARB")) == NULL) || r; + r = ((glGetUniformfvARB = (PFNGLGETUNIFORMFVARBPROC)glewGetProcAddress((const GLubyte*)"glGetUniformfvARB")) == NULL) || r; + r = ((glGetUniformivARB = (PFNGLGETUNIFORMIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetUniformivARB")) == NULL) || r; + r = ((glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)glewGetProcAddress((const GLubyte*)"glLinkProgramARB")) == NULL) || r; + r = ((glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)glewGetProcAddress((const GLubyte*)"glShaderSourceARB")) == NULL) || r; + r = ((glUniform1fARB = (PFNGLUNIFORM1FARBPROC)glewGetProcAddress((const GLubyte*)"glUniform1fARB")) == NULL) || r; + r = ((glUniform1fvARB = (PFNGLUNIFORM1FVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform1fvARB")) == NULL) || r; + r = ((glUniform1iARB = (PFNGLUNIFORM1IARBPROC)glewGetProcAddress((const GLubyte*)"glUniform1iARB")) == NULL) || r; + r = ((glUniform1ivARB = (PFNGLUNIFORM1IVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform1ivARB")) == NULL) || r; + r = ((glUniform2fARB = (PFNGLUNIFORM2FARBPROC)glewGetProcAddress((const GLubyte*)"glUniform2fARB")) == NULL) || r; + r = ((glUniform2fvARB = (PFNGLUNIFORM2FVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform2fvARB")) == NULL) || r; + r = ((glUniform2iARB = (PFNGLUNIFORM2IARBPROC)glewGetProcAddress((const GLubyte*)"glUniform2iARB")) == NULL) || r; + r = ((glUniform2ivARB = (PFNGLUNIFORM2IVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform2ivARB")) == NULL) || r; + r = ((glUniform3fARB = (PFNGLUNIFORM3FARBPROC)glewGetProcAddress((const GLubyte*)"glUniform3fARB")) == NULL) || r; + r = ((glUniform3fvARB = (PFNGLUNIFORM3FVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform3fvARB")) == NULL) || r; + r = ((glUniform3iARB = (PFNGLUNIFORM3IARBPROC)glewGetProcAddress((const GLubyte*)"glUniform3iARB")) == NULL) || r; + r = ((glUniform3ivARB = (PFNGLUNIFORM3IVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform3ivARB")) == NULL) || r; + r = ((glUniform4fARB = (PFNGLUNIFORM4FARBPROC)glewGetProcAddress((const GLubyte*)"glUniform4fARB")) == NULL) || r; + r = ((glUniform4fvARB = (PFNGLUNIFORM4FVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform4fvARB")) == NULL) || r; + r = ((glUniform4iARB = (PFNGLUNIFORM4IARBPROC)glewGetProcAddress((const GLubyte*)"glUniform4iARB")) == NULL) || r; + r = ((glUniform4ivARB = (PFNGLUNIFORM4IVARBPROC)glewGetProcAddress((const GLubyte*)"glUniform4ivARB")) == NULL) || r; + r = ((glUniformMatrix2fvARB = (PFNGLUNIFORMMATRIX2FVARBPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix2fvARB")) == NULL) || r; + r = ((glUniformMatrix3fvARB = (PFNGLUNIFORMMATRIX3FVARBPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix3fvARB")) == NULL) || r; + r = ((glUniformMatrix4fvARB = (PFNGLUNIFORMMATRIX4FVARBPROC)glewGetProcAddress((const GLubyte*)"glUniformMatrix4fvARB")) == NULL) || r; + r = ((glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)glewGetProcAddress((const GLubyte*)"glUseProgramObjectARB")) == NULL) || r; + r = ((glValidateProgramARB = (PFNGLVALIDATEPROGRAMARBPROC)glewGetProcAddress((const GLubyte*)"glValidateProgramARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_shader_objects */ + +#ifdef GL_ARB_shading_language_100 + +#endif /* GL_ARB_shading_language_100 */ + +#ifdef GL_ARB_shadow + +#endif /* GL_ARB_shadow */ + +#ifdef GL_ARB_shadow_ambient + +#endif /* GL_ARB_shadow_ambient */ + +#ifdef GL_ARB_texture_border_clamp + +#endif /* GL_ARB_texture_border_clamp */ + +#ifdef GL_ARB_texture_buffer_object + +static GLboolean _glewInit_GL_ARB_texture_buffer_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTexBufferARB = (PFNGLTEXBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"glTexBufferARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_texture_buffer_object */ + +#ifdef GL_ARB_texture_compression + +static GLboolean _glewInit_GL_ARB_texture_compression (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glCompressedTexImage1DARB = (PFNGLCOMPRESSEDTEXIMAGE1DARBPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexImage1DARB")) == NULL) || r; + r = ((glCompressedTexImage2DARB = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexImage2DARB")) == NULL) || r; + r = ((glCompressedTexImage3DARB = (PFNGLCOMPRESSEDTEXIMAGE3DARBPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexImage3DARB")) == NULL) || r; + r = ((glCompressedTexSubImage1DARB = (PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexSubImage1DARB")) == NULL) || r; + r = ((glCompressedTexSubImage2DARB = (PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexSubImage2DARB")) == NULL) || r; + r = ((glCompressedTexSubImage3DARB = (PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC)glewGetProcAddress((const GLubyte*)"glCompressedTexSubImage3DARB")) == NULL) || r; + r = ((glGetCompressedTexImageARB = (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)glewGetProcAddress((const GLubyte*)"glGetCompressedTexImageARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_texture_compression */ + +#ifdef GL_ARB_texture_compression_rgtc + +#endif /* GL_ARB_texture_compression_rgtc */ + +#ifdef GL_ARB_texture_cube_map + +#endif /* GL_ARB_texture_cube_map */ + +#ifdef GL_ARB_texture_env_add + +#endif /* GL_ARB_texture_env_add */ + +#ifdef GL_ARB_texture_env_combine + +#endif /* GL_ARB_texture_env_combine */ + +#ifdef GL_ARB_texture_env_crossbar + +#endif /* GL_ARB_texture_env_crossbar */ + +#ifdef GL_ARB_texture_env_dot3 + +#endif /* GL_ARB_texture_env_dot3 */ + +#ifdef GL_ARB_texture_float + +#endif /* GL_ARB_texture_float */ + +#ifdef GL_ARB_texture_mirrored_repeat + +#endif /* GL_ARB_texture_mirrored_repeat */ + +#ifdef GL_ARB_texture_non_power_of_two + +#endif /* GL_ARB_texture_non_power_of_two */ + +#ifdef GL_ARB_texture_rectangle + +#endif /* GL_ARB_texture_rectangle */ + +#ifdef GL_ARB_texture_rg + +#endif /* GL_ARB_texture_rg */ + +#ifdef GL_ARB_transpose_matrix + +static GLboolean _glewInit_GL_ARB_transpose_matrix (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glLoadTransposeMatrixdARB = (PFNGLLOADTRANSPOSEMATRIXDARBPROC)glewGetProcAddress((const GLubyte*)"glLoadTransposeMatrixdARB")) == NULL) || r; + r = ((glLoadTransposeMatrixfARB = (PFNGLLOADTRANSPOSEMATRIXFARBPROC)glewGetProcAddress((const GLubyte*)"glLoadTransposeMatrixfARB")) == NULL) || r; + r = ((glMultTransposeMatrixdARB = (PFNGLMULTTRANSPOSEMATRIXDARBPROC)glewGetProcAddress((const GLubyte*)"glMultTransposeMatrixdARB")) == NULL) || r; + r = ((glMultTransposeMatrixfARB = (PFNGLMULTTRANSPOSEMATRIXFARBPROC)glewGetProcAddress((const GLubyte*)"glMultTransposeMatrixfARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_transpose_matrix */ + +#ifdef GL_ARB_vertex_array_object + +static GLboolean _glewInit_GL_ARB_vertex_array_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)glewGetProcAddress((const GLubyte*)"glBindVertexArray")) == NULL) || r; + r = ((glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)glewGetProcAddress((const GLubyte*)"glDeleteVertexArrays")) == NULL) || r; + r = ((glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)glewGetProcAddress((const GLubyte*)"glGenVertexArrays")) == NULL) || r; + r = ((glIsVertexArray = (PFNGLISVERTEXARRAYPROC)glewGetProcAddress((const GLubyte*)"glIsVertexArray")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_vertex_array_object */ + +#ifdef GL_ARB_vertex_blend + +static GLboolean _glewInit_GL_ARB_vertex_blend (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glVertexBlendARB = (PFNGLVERTEXBLENDARBPROC)glewGetProcAddress((const GLubyte*)"glVertexBlendARB")) == NULL) || r; + r = ((glWeightPointerARB = (PFNGLWEIGHTPOINTERARBPROC)glewGetProcAddress((const GLubyte*)"glWeightPointerARB")) == NULL) || r; + r = ((glWeightbvARB = (PFNGLWEIGHTBVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightbvARB")) == NULL) || r; + r = ((glWeightdvARB = (PFNGLWEIGHTDVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightdvARB")) == NULL) || r; + r = ((glWeightfvARB = (PFNGLWEIGHTFVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightfvARB")) == NULL) || r; + r = ((glWeightivARB = (PFNGLWEIGHTIVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightivARB")) == NULL) || r; + r = ((glWeightsvARB = (PFNGLWEIGHTSVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightsvARB")) == NULL) || r; + r = ((glWeightubvARB = (PFNGLWEIGHTUBVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightubvARB")) == NULL) || r; + r = ((glWeightuivARB = (PFNGLWEIGHTUIVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightuivARB")) == NULL) || r; + r = ((glWeightusvARB = (PFNGLWEIGHTUSVARBPROC)glewGetProcAddress((const GLubyte*)"glWeightusvARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_vertex_blend */ + +#ifdef GL_ARB_vertex_buffer_object + +static GLboolean _glewInit_GL_ARB_vertex_buffer_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindBufferARB = (PFNGLBINDBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"glBindBufferARB")) == NULL) || r; + r = ((glBufferDataARB = (PFNGLBUFFERDATAARBPROC)glewGetProcAddress((const GLubyte*)"glBufferDataARB")) == NULL) || r; + r = ((glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)glewGetProcAddress((const GLubyte*)"glBufferSubDataARB")) == NULL) || r; + r = ((glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)glewGetProcAddress((const GLubyte*)"glDeleteBuffersARB")) == NULL) || r; + r = ((glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)glewGetProcAddress((const GLubyte*)"glGenBuffersARB")) == NULL) || r; + r = ((glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetBufferParameterivARB")) == NULL) || r; + r = ((glGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC)glewGetProcAddress((const GLubyte*)"glGetBufferPointervARB")) == NULL) || r; + r = ((glGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC)glewGetProcAddress((const GLubyte*)"glGetBufferSubDataARB")) == NULL) || r; + r = ((glIsBufferARB = (PFNGLISBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"glIsBufferARB")) == NULL) || r; + r = ((glMapBufferARB = (PFNGLMAPBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"glMapBufferARB")) == NULL) || r; + r = ((glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"glUnmapBufferARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_vertex_buffer_object */ + +#ifdef GL_ARB_vertex_program + +static GLboolean _glewInit_GL_ARB_vertex_program (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindProgramARB = (PFNGLBINDPROGRAMARBPROC)glewGetProcAddress((const GLubyte*)"glBindProgramARB")) == NULL) || r; + r = ((glDeleteProgramsARB = (PFNGLDELETEPROGRAMSARBPROC)glewGetProcAddress((const GLubyte*)"glDeleteProgramsARB")) == NULL) || r; + r = ((glDisableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)glewGetProcAddress((const GLubyte*)"glDisableVertexAttribArrayARB")) == NULL) || r; + r = ((glEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)glewGetProcAddress((const GLubyte*)"glEnableVertexAttribArrayARB")) == NULL) || r; + r = ((glGenProgramsARB = (PFNGLGENPROGRAMSARBPROC)glewGetProcAddress((const GLubyte*)"glGenProgramsARB")) == NULL) || r; + r = ((glGetProgramEnvParameterdvARB = (PFNGLGETPROGRAMENVPARAMETERDVARBPROC)glewGetProcAddress((const GLubyte*)"glGetProgramEnvParameterdvARB")) == NULL) || r; + r = ((glGetProgramEnvParameterfvARB = (PFNGLGETPROGRAMENVPARAMETERFVARBPROC)glewGetProcAddress((const GLubyte*)"glGetProgramEnvParameterfvARB")) == NULL) || r; + r = ((glGetProgramLocalParameterdvARB = (PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC)glewGetProcAddress((const GLubyte*)"glGetProgramLocalParameterdvARB")) == NULL) || r; + r = ((glGetProgramLocalParameterfvARB = (PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC)glewGetProcAddress((const GLubyte*)"glGetProgramLocalParameterfvARB")) == NULL) || r; + r = ((glGetProgramStringARB = (PFNGLGETPROGRAMSTRINGARBPROC)glewGetProcAddress((const GLubyte*)"glGetProgramStringARB")) == NULL) || r; + r = ((glGetProgramivARB = (PFNGLGETPROGRAMIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetProgramivARB")) == NULL) || r; + r = ((glGetVertexAttribPointervARB = (PFNGLGETVERTEXATTRIBPOINTERVARBPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribPointervARB")) == NULL) || r; + r = ((glGetVertexAttribdvARB = (PFNGLGETVERTEXATTRIBDVARBPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribdvARB")) == NULL) || r; + r = ((glGetVertexAttribfvARB = (PFNGLGETVERTEXATTRIBFVARBPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribfvARB")) == NULL) || r; + r = ((glGetVertexAttribivARB = (PFNGLGETVERTEXATTRIBIVARBPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribivARB")) == NULL) || r; + r = ((glIsProgramARB = (PFNGLISPROGRAMARBPROC)glewGetProcAddress((const GLubyte*)"glIsProgramARB")) == NULL) || r; + r = ((glProgramEnvParameter4dARB = (PFNGLPROGRAMENVPARAMETER4DARBPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameter4dARB")) == NULL) || r; + r = ((glProgramEnvParameter4dvARB = (PFNGLPROGRAMENVPARAMETER4DVARBPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameter4dvARB")) == NULL) || r; + r = ((glProgramEnvParameter4fARB = (PFNGLPROGRAMENVPARAMETER4FARBPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameter4fARB")) == NULL) || r; + r = ((glProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameter4fvARB")) == NULL) || r; + r = ((glProgramLocalParameter4dARB = (PFNGLPROGRAMLOCALPARAMETER4DARBPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameter4dARB")) == NULL) || r; + r = ((glProgramLocalParameter4dvARB = (PFNGLPROGRAMLOCALPARAMETER4DVARBPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameter4dvARB")) == NULL) || r; + r = ((glProgramLocalParameter4fARB = (PFNGLPROGRAMLOCALPARAMETER4FARBPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameter4fARB")) == NULL) || r; + r = ((glProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameter4fvARB")) == NULL) || r; + r = ((glProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC)glewGetProcAddress((const GLubyte*)"glProgramStringARB")) == NULL) || r; + r = ((glVertexAttrib1dARB = (PFNGLVERTEXATTRIB1DARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1dARB")) == NULL) || r; + r = ((glVertexAttrib1dvARB = (PFNGLVERTEXATTRIB1DVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1dvARB")) == NULL) || r; + r = ((glVertexAttrib1fARB = (PFNGLVERTEXATTRIB1FARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1fARB")) == NULL) || r; + r = ((glVertexAttrib1fvARB = (PFNGLVERTEXATTRIB1FVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1fvARB")) == NULL) || r; + r = ((glVertexAttrib1sARB = (PFNGLVERTEXATTRIB1SARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1sARB")) == NULL) || r; + r = ((glVertexAttrib1svARB = (PFNGLVERTEXATTRIB1SVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1svARB")) == NULL) || r; + r = ((glVertexAttrib2dARB = (PFNGLVERTEXATTRIB2DARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2dARB")) == NULL) || r; + r = ((glVertexAttrib2dvARB = (PFNGLVERTEXATTRIB2DVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2dvARB")) == NULL) || r; + r = ((glVertexAttrib2fARB = (PFNGLVERTEXATTRIB2FARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2fARB")) == NULL) || r; + r = ((glVertexAttrib2fvARB = (PFNGLVERTEXATTRIB2FVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2fvARB")) == NULL) || r; + r = ((glVertexAttrib2sARB = (PFNGLVERTEXATTRIB2SARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2sARB")) == NULL) || r; + r = ((glVertexAttrib2svARB = (PFNGLVERTEXATTRIB2SVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2svARB")) == NULL) || r; + r = ((glVertexAttrib3dARB = (PFNGLVERTEXATTRIB3DARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3dARB")) == NULL) || r; + r = ((glVertexAttrib3dvARB = (PFNGLVERTEXATTRIB3DVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3dvARB")) == NULL) || r; + r = ((glVertexAttrib3fARB = (PFNGLVERTEXATTRIB3FARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3fARB")) == NULL) || r; + r = ((glVertexAttrib3fvARB = (PFNGLVERTEXATTRIB3FVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3fvARB")) == NULL) || r; + r = ((glVertexAttrib3sARB = (PFNGLVERTEXATTRIB3SARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3sARB")) == NULL) || r; + r = ((glVertexAttrib3svARB = (PFNGLVERTEXATTRIB3SVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3svARB")) == NULL) || r; + r = ((glVertexAttrib4NbvARB = (PFNGLVERTEXATTRIB4NBVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4NbvARB")) == NULL) || r; + r = ((glVertexAttrib4NivARB = (PFNGLVERTEXATTRIB4NIVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4NivARB")) == NULL) || r; + r = ((glVertexAttrib4NsvARB = (PFNGLVERTEXATTRIB4NSVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4NsvARB")) == NULL) || r; + r = ((glVertexAttrib4NubARB = (PFNGLVERTEXATTRIB4NUBARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4NubARB")) == NULL) || r; + r = ((glVertexAttrib4NubvARB = (PFNGLVERTEXATTRIB4NUBVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4NubvARB")) == NULL) || r; + r = ((glVertexAttrib4NuivARB = (PFNGLVERTEXATTRIB4NUIVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4NuivARB")) == NULL) || r; + r = ((glVertexAttrib4NusvARB = (PFNGLVERTEXATTRIB4NUSVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4NusvARB")) == NULL) || r; + r = ((glVertexAttrib4bvARB = (PFNGLVERTEXATTRIB4BVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4bvARB")) == NULL) || r; + r = ((glVertexAttrib4dARB = (PFNGLVERTEXATTRIB4DARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4dARB")) == NULL) || r; + r = ((glVertexAttrib4dvARB = (PFNGLVERTEXATTRIB4DVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4dvARB")) == NULL) || r; + r = ((glVertexAttrib4fARB = (PFNGLVERTEXATTRIB4FARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4fARB")) == NULL) || r; + r = ((glVertexAttrib4fvARB = (PFNGLVERTEXATTRIB4FVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4fvARB")) == NULL) || r; + r = ((glVertexAttrib4ivARB = (PFNGLVERTEXATTRIB4IVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4ivARB")) == NULL) || r; + r = ((glVertexAttrib4sARB = (PFNGLVERTEXATTRIB4SARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4sARB")) == NULL) || r; + r = ((glVertexAttrib4svARB = (PFNGLVERTEXATTRIB4SVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4svARB")) == NULL) || r; + r = ((glVertexAttrib4ubvARB = (PFNGLVERTEXATTRIB4UBVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4ubvARB")) == NULL) || r; + r = ((glVertexAttrib4uivARB = (PFNGLVERTEXATTRIB4UIVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4uivARB")) == NULL) || r; + r = ((glVertexAttrib4usvARB = (PFNGLVERTEXATTRIB4USVARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4usvARB")) == NULL) || r; + r = ((glVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribPointerARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_vertex_program */ + +#ifdef GL_ARB_vertex_shader + +static GLboolean _glewInit_GL_ARB_vertex_shader (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindAttribLocationARB = (PFNGLBINDATTRIBLOCATIONARBPROC)glewGetProcAddress((const GLubyte*)"glBindAttribLocationARB")) == NULL) || r; + r = ((glGetActiveAttribARB = (PFNGLGETACTIVEATTRIBARBPROC)glewGetProcAddress((const GLubyte*)"glGetActiveAttribARB")) == NULL) || r; + r = ((glGetAttribLocationARB = (PFNGLGETATTRIBLOCATIONARBPROC)glewGetProcAddress((const GLubyte*)"glGetAttribLocationARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_vertex_shader */ + +#ifdef GL_ARB_window_pos + +static GLboolean _glewInit_GL_ARB_window_pos (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glWindowPos2dARB = (PFNGLWINDOWPOS2DARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2dARB")) == NULL) || r; + r = ((glWindowPos2dvARB = (PFNGLWINDOWPOS2DVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2dvARB")) == NULL) || r; + r = ((glWindowPos2fARB = (PFNGLWINDOWPOS2FARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2fARB")) == NULL) || r; + r = ((glWindowPos2fvARB = (PFNGLWINDOWPOS2FVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2fvARB")) == NULL) || r; + r = ((glWindowPos2iARB = (PFNGLWINDOWPOS2IARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2iARB")) == NULL) || r; + r = ((glWindowPos2ivARB = (PFNGLWINDOWPOS2IVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2ivARB")) == NULL) || r; + r = ((glWindowPos2sARB = (PFNGLWINDOWPOS2SARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2sARB")) == NULL) || r; + r = ((glWindowPos2svARB = (PFNGLWINDOWPOS2SVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2svARB")) == NULL) || r; + r = ((glWindowPos3dARB = (PFNGLWINDOWPOS3DARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3dARB")) == NULL) || r; + r = ((glWindowPos3dvARB = (PFNGLWINDOWPOS3DVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3dvARB")) == NULL) || r; + r = ((glWindowPos3fARB = (PFNGLWINDOWPOS3FARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3fARB")) == NULL) || r; + r = ((glWindowPos3fvARB = (PFNGLWINDOWPOS3FVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3fvARB")) == NULL) || r; + r = ((glWindowPos3iARB = (PFNGLWINDOWPOS3IARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3iARB")) == NULL) || r; + r = ((glWindowPos3ivARB = (PFNGLWINDOWPOS3IVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3ivARB")) == NULL) || r; + r = ((glWindowPos3sARB = (PFNGLWINDOWPOS3SARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3sARB")) == NULL) || r; + r = ((glWindowPos3svARB = (PFNGLWINDOWPOS3SVARBPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3svARB")) == NULL) || r; + + return r; +} + +#endif /* GL_ARB_window_pos */ + +#ifdef GL_ATIX_point_sprites + +#endif /* GL_ATIX_point_sprites */ + +#ifdef GL_ATIX_texture_env_combine3 + +#endif /* GL_ATIX_texture_env_combine3 */ + +#ifdef GL_ATIX_texture_env_route + +#endif /* GL_ATIX_texture_env_route */ + +#ifdef GL_ATIX_vertex_shader_output_point_size + +#endif /* GL_ATIX_vertex_shader_output_point_size */ + +#ifdef GL_ATI_draw_buffers + +static GLboolean _glewInit_GL_ATI_draw_buffers (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDrawBuffersATI = (PFNGLDRAWBUFFERSATIPROC)glewGetProcAddress((const GLubyte*)"glDrawBuffersATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_draw_buffers */ + +#ifdef GL_ATI_element_array + +static GLboolean _glewInit_GL_ATI_element_array (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDrawElementArrayATI = (PFNGLDRAWELEMENTARRAYATIPROC)glewGetProcAddress((const GLubyte*)"glDrawElementArrayATI")) == NULL) || r; + r = ((glDrawRangeElementArrayATI = (PFNGLDRAWRANGEELEMENTARRAYATIPROC)glewGetProcAddress((const GLubyte*)"glDrawRangeElementArrayATI")) == NULL) || r; + r = ((glElementPointerATI = (PFNGLELEMENTPOINTERATIPROC)glewGetProcAddress((const GLubyte*)"glElementPointerATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_element_array */ + +#ifdef GL_ATI_envmap_bumpmap + +static GLboolean _glewInit_GL_ATI_envmap_bumpmap (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetTexBumpParameterfvATI = (PFNGLGETTEXBUMPPARAMETERFVATIPROC)glewGetProcAddress((const GLubyte*)"glGetTexBumpParameterfvATI")) == NULL) || r; + r = ((glGetTexBumpParameterivATI = (PFNGLGETTEXBUMPPARAMETERIVATIPROC)glewGetProcAddress((const GLubyte*)"glGetTexBumpParameterivATI")) == NULL) || r; + r = ((glTexBumpParameterfvATI = (PFNGLTEXBUMPPARAMETERFVATIPROC)glewGetProcAddress((const GLubyte*)"glTexBumpParameterfvATI")) == NULL) || r; + r = ((glTexBumpParameterivATI = (PFNGLTEXBUMPPARAMETERIVATIPROC)glewGetProcAddress((const GLubyte*)"glTexBumpParameterivATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_envmap_bumpmap */ + +#ifdef GL_ATI_fragment_shader + +static GLboolean _glewInit_GL_ATI_fragment_shader (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glAlphaFragmentOp1ATI = (PFNGLALPHAFRAGMENTOP1ATIPROC)glewGetProcAddress((const GLubyte*)"glAlphaFragmentOp1ATI")) == NULL) || r; + r = ((glAlphaFragmentOp2ATI = (PFNGLALPHAFRAGMENTOP2ATIPROC)glewGetProcAddress((const GLubyte*)"glAlphaFragmentOp2ATI")) == NULL) || r; + r = ((glAlphaFragmentOp3ATI = (PFNGLALPHAFRAGMENTOP3ATIPROC)glewGetProcAddress((const GLubyte*)"glAlphaFragmentOp3ATI")) == NULL) || r; + r = ((glBeginFragmentShaderATI = (PFNGLBEGINFRAGMENTSHADERATIPROC)glewGetProcAddress((const GLubyte*)"glBeginFragmentShaderATI")) == NULL) || r; + r = ((glBindFragmentShaderATI = (PFNGLBINDFRAGMENTSHADERATIPROC)glewGetProcAddress((const GLubyte*)"glBindFragmentShaderATI")) == NULL) || r; + r = ((glColorFragmentOp1ATI = (PFNGLCOLORFRAGMENTOP1ATIPROC)glewGetProcAddress((const GLubyte*)"glColorFragmentOp1ATI")) == NULL) || r; + r = ((glColorFragmentOp2ATI = (PFNGLCOLORFRAGMENTOP2ATIPROC)glewGetProcAddress((const GLubyte*)"glColorFragmentOp2ATI")) == NULL) || r; + r = ((glColorFragmentOp3ATI = (PFNGLCOLORFRAGMENTOP3ATIPROC)glewGetProcAddress((const GLubyte*)"glColorFragmentOp3ATI")) == NULL) || r; + r = ((glDeleteFragmentShaderATI = (PFNGLDELETEFRAGMENTSHADERATIPROC)glewGetProcAddress((const GLubyte*)"glDeleteFragmentShaderATI")) == NULL) || r; + r = ((glEndFragmentShaderATI = (PFNGLENDFRAGMENTSHADERATIPROC)glewGetProcAddress((const GLubyte*)"glEndFragmentShaderATI")) == NULL) || r; + r = ((glGenFragmentShadersATI = (PFNGLGENFRAGMENTSHADERSATIPROC)glewGetProcAddress((const GLubyte*)"glGenFragmentShadersATI")) == NULL) || r; + r = ((glPassTexCoordATI = (PFNGLPASSTEXCOORDATIPROC)glewGetProcAddress((const GLubyte*)"glPassTexCoordATI")) == NULL) || r; + r = ((glSampleMapATI = (PFNGLSAMPLEMAPATIPROC)glewGetProcAddress((const GLubyte*)"glSampleMapATI")) == NULL) || r; + r = ((glSetFragmentShaderConstantATI = (PFNGLSETFRAGMENTSHADERCONSTANTATIPROC)glewGetProcAddress((const GLubyte*)"glSetFragmentShaderConstantATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_fragment_shader */ + +#ifdef GL_ATI_map_object_buffer + +static GLboolean _glewInit_GL_ATI_map_object_buffer (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glMapObjectBufferATI = (PFNGLMAPOBJECTBUFFERATIPROC)glewGetProcAddress((const GLubyte*)"glMapObjectBufferATI")) == NULL) || r; + r = ((glUnmapObjectBufferATI = (PFNGLUNMAPOBJECTBUFFERATIPROC)glewGetProcAddress((const GLubyte*)"glUnmapObjectBufferATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_map_object_buffer */ + +#ifdef GL_ATI_pn_triangles + +static GLboolean _glewInit_GL_ATI_pn_triangles (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glPNTrianglesfATI = (PFNGLPNTRIANGLESFATIPROC)glewGetProcAddress((const GLubyte*)"glPNTrianglesfATI")) == NULL) || r; + r = ((glPNTrianglesiATI = (PFNGLPNTRIANGLESIATIPROC)glewGetProcAddress((const GLubyte*)"glPNTrianglesiATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_pn_triangles */ + +#ifdef GL_ATI_separate_stencil + +static GLboolean _glewInit_GL_ATI_separate_stencil (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glStencilFuncSeparateATI = (PFNGLSTENCILFUNCSEPARATEATIPROC)glewGetProcAddress((const GLubyte*)"glStencilFuncSeparateATI")) == NULL) || r; + r = ((glStencilOpSeparateATI = (PFNGLSTENCILOPSEPARATEATIPROC)glewGetProcAddress((const GLubyte*)"glStencilOpSeparateATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_separate_stencil */ + +#ifdef GL_ATI_shader_texture_lod + +#endif /* GL_ATI_shader_texture_lod */ + +#ifdef GL_ATI_text_fragment_shader + +#endif /* GL_ATI_text_fragment_shader */ + +#ifdef GL_ATI_texture_compression_3dc + +#endif /* GL_ATI_texture_compression_3dc */ + +#ifdef GL_ATI_texture_env_combine3 + +#endif /* GL_ATI_texture_env_combine3 */ + +#ifdef GL_ATI_texture_float + +#endif /* GL_ATI_texture_float */ + +#ifdef GL_ATI_texture_mirror_once + +#endif /* GL_ATI_texture_mirror_once */ + +#ifdef GL_ATI_vertex_array_object + +static GLboolean _glewInit_GL_ATI_vertex_array_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glArrayObjectATI = (PFNGLARRAYOBJECTATIPROC)glewGetProcAddress((const GLubyte*)"glArrayObjectATI")) == NULL) || r; + r = ((glFreeObjectBufferATI = (PFNGLFREEOBJECTBUFFERATIPROC)glewGetProcAddress((const GLubyte*)"glFreeObjectBufferATI")) == NULL) || r; + r = ((glGetArrayObjectfvATI = (PFNGLGETARRAYOBJECTFVATIPROC)glewGetProcAddress((const GLubyte*)"glGetArrayObjectfvATI")) == NULL) || r; + r = ((glGetArrayObjectivATI = (PFNGLGETARRAYOBJECTIVATIPROC)glewGetProcAddress((const GLubyte*)"glGetArrayObjectivATI")) == NULL) || r; + r = ((glGetObjectBufferfvATI = (PFNGLGETOBJECTBUFFERFVATIPROC)glewGetProcAddress((const GLubyte*)"glGetObjectBufferfvATI")) == NULL) || r; + r = ((glGetObjectBufferivATI = (PFNGLGETOBJECTBUFFERIVATIPROC)glewGetProcAddress((const GLubyte*)"glGetObjectBufferivATI")) == NULL) || r; + r = ((glGetVariantArrayObjectfvATI = (PFNGLGETVARIANTARRAYOBJECTFVATIPROC)glewGetProcAddress((const GLubyte*)"glGetVariantArrayObjectfvATI")) == NULL) || r; + r = ((glGetVariantArrayObjectivATI = (PFNGLGETVARIANTARRAYOBJECTIVATIPROC)glewGetProcAddress((const GLubyte*)"glGetVariantArrayObjectivATI")) == NULL) || r; + r = ((glIsObjectBufferATI = (PFNGLISOBJECTBUFFERATIPROC)glewGetProcAddress((const GLubyte*)"glIsObjectBufferATI")) == NULL) || r; + r = ((glNewObjectBufferATI = (PFNGLNEWOBJECTBUFFERATIPROC)glewGetProcAddress((const GLubyte*)"glNewObjectBufferATI")) == NULL) || r; + r = ((glUpdateObjectBufferATI = (PFNGLUPDATEOBJECTBUFFERATIPROC)glewGetProcAddress((const GLubyte*)"glUpdateObjectBufferATI")) == NULL) || r; + r = ((glVariantArrayObjectATI = (PFNGLVARIANTARRAYOBJECTATIPROC)glewGetProcAddress((const GLubyte*)"glVariantArrayObjectATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_vertex_array_object */ + +#ifdef GL_ATI_vertex_attrib_array_object + +static GLboolean _glewInit_GL_ATI_vertex_attrib_array_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetVertexAttribArrayObjectfvATI = (PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribArrayObjectfvATI")) == NULL) || r; + r = ((glGetVertexAttribArrayObjectivATI = (PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribArrayObjectivATI")) == NULL) || r; + r = ((glVertexAttribArrayObjectATI = (PFNGLVERTEXATTRIBARRAYOBJECTATIPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribArrayObjectATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_vertex_attrib_array_object */ + +#ifdef GL_ATI_vertex_streams + +static GLboolean _glewInit_GL_ATI_vertex_streams (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glClientActiveVertexStreamATI = (PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC)glewGetProcAddress((const GLubyte*)"glClientActiveVertexStreamATI")) == NULL) || r; + r = ((glNormalStream3bATI = (PFNGLNORMALSTREAM3BATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3bATI")) == NULL) || r; + r = ((glNormalStream3bvATI = (PFNGLNORMALSTREAM3BVATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3bvATI")) == NULL) || r; + r = ((glNormalStream3dATI = (PFNGLNORMALSTREAM3DATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3dATI")) == NULL) || r; + r = ((glNormalStream3dvATI = (PFNGLNORMALSTREAM3DVATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3dvATI")) == NULL) || r; + r = ((glNormalStream3fATI = (PFNGLNORMALSTREAM3FATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3fATI")) == NULL) || r; + r = ((glNormalStream3fvATI = (PFNGLNORMALSTREAM3FVATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3fvATI")) == NULL) || r; + r = ((glNormalStream3iATI = (PFNGLNORMALSTREAM3IATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3iATI")) == NULL) || r; + r = ((glNormalStream3ivATI = (PFNGLNORMALSTREAM3IVATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3ivATI")) == NULL) || r; + r = ((glNormalStream3sATI = (PFNGLNORMALSTREAM3SATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3sATI")) == NULL) || r; + r = ((glNormalStream3svATI = (PFNGLNORMALSTREAM3SVATIPROC)glewGetProcAddress((const GLubyte*)"glNormalStream3svATI")) == NULL) || r; + r = ((glVertexBlendEnvfATI = (PFNGLVERTEXBLENDENVFATIPROC)glewGetProcAddress((const GLubyte*)"glVertexBlendEnvfATI")) == NULL) || r; + r = ((glVertexBlendEnviATI = (PFNGLVERTEXBLENDENVIATIPROC)glewGetProcAddress((const GLubyte*)"glVertexBlendEnviATI")) == NULL) || r; + r = ((glVertexStream2dATI = (PFNGLVERTEXSTREAM2DATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2dATI")) == NULL) || r; + r = ((glVertexStream2dvATI = (PFNGLVERTEXSTREAM2DVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2dvATI")) == NULL) || r; + r = ((glVertexStream2fATI = (PFNGLVERTEXSTREAM2FATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2fATI")) == NULL) || r; + r = ((glVertexStream2fvATI = (PFNGLVERTEXSTREAM2FVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2fvATI")) == NULL) || r; + r = ((glVertexStream2iATI = (PFNGLVERTEXSTREAM2IATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2iATI")) == NULL) || r; + r = ((glVertexStream2ivATI = (PFNGLVERTEXSTREAM2IVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2ivATI")) == NULL) || r; + r = ((glVertexStream2sATI = (PFNGLVERTEXSTREAM2SATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2sATI")) == NULL) || r; + r = ((glVertexStream2svATI = (PFNGLVERTEXSTREAM2SVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream2svATI")) == NULL) || r; + r = ((glVertexStream3dATI = (PFNGLVERTEXSTREAM3DATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3dATI")) == NULL) || r; + r = ((glVertexStream3dvATI = (PFNGLVERTEXSTREAM3DVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3dvATI")) == NULL) || r; + r = ((glVertexStream3fATI = (PFNGLVERTEXSTREAM3FATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3fATI")) == NULL) || r; + r = ((glVertexStream3fvATI = (PFNGLVERTEXSTREAM3FVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3fvATI")) == NULL) || r; + r = ((glVertexStream3iATI = (PFNGLVERTEXSTREAM3IATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3iATI")) == NULL) || r; + r = ((glVertexStream3ivATI = (PFNGLVERTEXSTREAM3IVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3ivATI")) == NULL) || r; + r = ((glVertexStream3sATI = (PFNGLVERTEXSTREAM3SATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3sATI")) == NULL) || r; + r = ((glVertexStream3svATI = (PFNGLVERTEXSTREAM3SVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream3svATI")) == NULL) || r; + r = ((glVertexStream4dATI = (PFNGLVERTEXSTREAM4DATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4dATI")) == NULL) || r; + r = ((glVertexStream4dvATI = (PFNGLVERTEXSTREAM4DVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4dvATI")) == NULL) || r; + r = ((glVertexStream4fATI = (PFNGLVERTEXSTREAM4FATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4fATI")) == NULL) || r; + r = ((glVertexStream4fvATI = (PFNGLVERTEXSTREAM4FVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4fvATI")) == NULL) || r; + r = ((glVertexStream4iATI = (PFNGLVERTEXSTREAM4IATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4iATI")) == NULL) || r; + r = ((glVertexStream4ivATI = (PFNGLVERTEXSTREAM4IVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4ivATI")) == NULL) || r; + r = ((glVertexStream4sATI = (PFNGLVERTEXSTREAM4SATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4sATI")) == NULL) || r; + r = ((glVertexStream4svATI = (PFNGLVERTEXSTREAM4SVATIPROC)glewGetProcAddress((const GLubyte*)"glVertexStream4svATI")) == NULL) || r; + + return r; +} + +#endif /* GL_ATI_vertex_streams */ + +#ifdef GL_EXT_422_pixels + +#endif /* GL_EXT_422_pixels */ + +#ifdef GL_EXT_Cg_shader + +#endif /* GL_EXT_Cg_shader */ + +#ifdef GL_EXT_abgr + +#endif /* GL_EXT_abgr */ + +#ifdef GL_EXT_bgra + +#endif /* GL_EXT_bgra */ + +#ifdef GL_EXT_bindable_uniform + +static GLboolean _glewInit_GL_EXT_bindable_uniform (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetUniformBufferSizeEXT = (PFNGLGETUNIFORMBUFFERSIZEEXTPROC)glewGetProcAddress((const GLubyte*)"glGetUniformBufferSizeEXT")) == NULL) || r; + r = ((glGetUniformOffsetEXT = (PFNGLGETUNIFORMOFFSETEXTPROC)glewGetProcAddress((const GLubyte*)"glGetUniformOffsetEXT")) == NULL) || r; + r = ((glUniformBufferEXT = (PFNGLUNIFORMBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glUniformBufferEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_bindable_uniform */ + +#ifdef GL_EXT_blend_color + +static GLboolean _glewInit_GL_EXT_blend_color (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBlendColorEXT = (PFNGLBLENDCOLOREXTPROC)glewGetProcAddress((const GLubyte*)"glBlendColorEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_blend_color */ + +#ifdef GL_EXT_blend_equation_separate + +static GLboolean _glewInit_GL_EXT_blend_equation_separate (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBlendEquationSeparateEXT = (PFNGLBLENDEQUATIONSEPARATEEXTPROC)glewGetProcAddress((const GLubyte*)"glBlendEquationSeparateEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_blend_equation_separate */ + +#ifdef GL_EXT_blend_func_separate + +static GLboolean _glewInit_GL_EXT_blend_func_separate (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBlendFuncSeparateEXT = (PFNGLBLENDFUNCSEPARATEEXTPROC)glewGetProcAddress((const GLubyte*)"glBlendFuncSeparateEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_blend_func_separate */ + +#ifdef GL_EXT_blend_logic_op + +#endif /* GL_EXT_blend_logic_op */ + +#ifdef GL_EXT_blend_minmax + +static GLboolean _glewInit_GL_EXT_blend_minmax (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBlendEquationEXT = (PFNGLBLENDEQUATIONEXTPROC)glewGetProcAddress((const GLubyte*)"glBlendEquationEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_blend_minmax */ + +#ifdef GL_EXT_blend_subtract + +#endif /* GL_EXT_blend_subtract */ + +#ifdef GL_EXT_clip_volume_hint + +#endif /* GL_EXT_clip_volume_hint */ + +#ifdef GL_EXT_cmyka + +#endif /* GL_EXT_cmyka */ + +#ifdef GL_EXT_color_subtable + +static GLboolean _glewInit_GL_EXT_color_subtable (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColorSubTableEXT = (PFNGLCOLORSUBTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"glColorSubTableEXT")) == NULL) || r; + r = ((glCopyColorSubTableEXT = (PFNGLCOPYCOLORSUBTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyColorSubTableEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_color_subtable */ + +#ifdef GL_EXT_compiled_vertex_array + +static GLboolean _glewInit_GL_EXT_compiled_vertex_array (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glLockArraysEXT = (PFNGLLOCKARRAYSEXTPROC)glewGetProcAddress((const GLubyte*)"glLockArraysEXT")) == NULL) || r; + r = ((glUnlockArraysEXT = (PFNGLUNLOCKARRAYSEXTPROC)glewGetProcAddress((const GLubyte*)"glUnlockArraysEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_compiled_vertex_array */ + +#ifdef GL_EXT_convolution + +static GLboolean _glewInit_GL_EXT_convolution (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glConvolutionFilter1DEXT = (PFNGLCONVOLUTIONFILTER1DEXTPROC)glewGetProcAddress((const GLubyte*)"glConvolutionFilter1DEXT")) == NULL) || r; + r = ((glConvolutionFilter2DEXT = (PFNGLCONVOLUTIONFILTER2DEXTPROC)glewGetProcAddress((const GLubyte*)"glConvolutionFilter2DEXT")) == NULL) || r; + r = ((glConvolutionParameterfEXT = (PFNGLCONVOLUTIONPARAMETERFEXTPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameterfEXT")) == NULL) || r; + r = ((glConvolutionParameterfvEXT = (PFNGLCONVOLUTIONPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameterfvEXT")) == NULL) || r; + r = ((glConvolutionParameteriEXT = (PFNGLCONVOLUTIONPARAMETERIEXTPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameteriEXT")) == NULL) || r; + r = ((glConvolutionParameterivEXT = (PFNGLCONVOLUTIONPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glConvolutionParameterivEXT")) == NULL) || r; + r = ((glCopyConvolutionFilter1DEXT = (PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyConvolutionFilter1DEXT")) == NULL) || r; + r = ((glCopyConvolutionFilter2DEXT = (PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyConvolutionFilter2DEXT")) == NULL) || r; + r = ((glGetConvolutionFilterEXT = (PFNGLGETCONVOLUTIONFILTEREXTPROC)glewGetProcAddress((const GLubyte*)"glGetConvolutionFilterEXT")) == NULL) || r; + r = ((glGetConvolutionParameterfvEXT = (PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetConvolutionParameterfvEXT")) == NULL) || r; + r = ((glGetConvolutionParameterivEXT = (PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetConvolutionParameterivEXT")) == NULL) || r; + r = ((glGetSeparableFilterEXT = (PFNGLGETSEPARABLEFILTEREXTPROC)glewGetProcAddress((const GLubyte*)"glGetSeparableFilterEXT")) == NULL) || r; + r = ((glSeparableFilter2DEXT = (PFNGLSEPARABLEFILTER2DEXTPROC)glewGetProcAddress((const GLubyte*)"glSeparableFilter2DEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_convolution */ + +#ifdef GL_EXT_coordinate_frame + +static GLboolean _glewInit_GL_EXT_coordinate_frame (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBinormalPointerEXT = (PFNGLBINORMALPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glBinormalPointerEXT")) == NULL) || r; + r = ((glTangentPointerEXT = (PFNGLTANGENTPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glTangentPointerEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_coordinate_frame */ + +#ifdef GL_EXT_copy_texture + +static GLboolean _glewInit_GL_EXT_copy_texture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glCopyTexImage1DEXT = (PFNGLCOPYTEXIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTexImage1DEXT")) == NULL) || r; + r = ((glCopyTexImage2DEXT = (PFNGLCOPYTEXIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTexImage2DEXT")) == NULL) || r; + r = ((glCopyTexSubImage1DEXT = (PFNGLCOPYTEXSUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTexSubImage1DEXT")) == NULL) || r; + r = ((glCopyTexSubImage2DEXT = (PFNGLCOPYTEXSUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTexSubImage2DEXT")) == NULL) || r; + r = ((glCopyTexSubImage3DEXT = (PFNGLCOPYTEXSUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTexSubImage3DEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_copy_texture */ + +#ifdef GL_EXT_cull_vertex + +static GLboolean _glewInit_GL_EXT_cull_vertex (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glCullParameterdvEXT = (PFNGLCULLPARAMETERDVEXTPROC)glewGetProcAddress((const GLubyte*)"glCullParameterdvEXT")) == NULL) || r; + r = ((glCullParameterfvEXT = (PFNGLCULLPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glCullParameterfvEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_cull_vertex */ + +#ifdef GL_EXT_depth_bounds_test + +static GLboolean _glewInit_GL_EXT_depth_bounds_test (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDepthBoundsEXT = (PFNGLDEPTHBOUNDSEXTPROC)glewGetProcAddress((const GLubyte*)"glDepthBoundsEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_depth_bounds_test */ + +#ifdef GL_EXT_direct_state_access + +static GLboolean _glewInit_GL_EXT_direct_state_access (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindMultiTextureEXT = (PFNGLBINDMULTITEXTUREEXTPROC)glewGetProcAddress((const GLubyte*)"glBindMultiTextureEXT")) == NULL) || r; + r = ((glCheckNamedFramebufferStatusEXT = (PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC)glewGetProcAddress((const GLubyte*)"glCheckNamedFramebufferStatusEXT")) == NULL) || r; + r = ((glClientAttribDefaultEXT = (PFNGLCLIENTATTRIBDEFAULTEXTPROC)glewGetProcAddress((const GLubyte*)"glClientAttribDefaultEXT")) == NULL) || r; + r = ((glCompressedMultiTexImage1DEXT = (PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedMultiTexImage1DEXT")) == NULL) || r; + r = ((glCompressedMultiTexImage2DEXT = (PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedMultiTexImage2DEXT")) == NULL) || r; + r = ((glCompressedMultiTexImage3DEXT = (PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedMultiTexImage3DEXT")) == NULL) || r; + r = ((glCompressedMultiTexSubImage1DEXT = (PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedMultiTexSubImage1DEXT")) == NULL) || r; + r = ((glCompressedMultiTexSubImage2DEXT = (PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedMultiTexSubImage2DEXT")) == NULL) || r; + r = ((glCompressedMultiTexSubImage3DEXT = (PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedMultiTexSubImage3DEXT")) == NULL) || r; + r = ((glCompressedTextureImage1DEXT = (PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedTextureImage1DEXT")) == NULL) || r; + r = ((glCompressedTextureImage2DEXT = (PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedTextureImage2DEXT")) == NULL) || r; + r = ((glCompressedTextureImage3DEXT = (PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedTextureImage3DEXT")) == NULL) || r; + r = ((glCompressedTextureSubImage1DEXT = (PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedTextureSubImage1DEXT")) == NULL) || r; + r = ((glCompressedTextureSubImage2DEXT = (PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedTextureSubImage2DEXT")) == NULL) || r; + r = ((glCompressedTextureSubImage3DEXT = (PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glCompressedTextureSubImage3DEXT")) == NULL) || r; + r = ((glCopyMultiTexImage1DEXT = (PFNGLCOPYMULTITEXIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyMultiTexImage1DEXT")) == NULL) || r; + r = ((glCopyMultiTexImage2DEXT = (PFNGLCOPYMULTITEXIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyMultiTexImage2DEXT")) == NULL) || r; + r = ((glCopyMultiTexSubImage1DEXT = (PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyMultiTexSubImage1DEXT")) == NULL) || r; + r = ((glCopyMultiTexSubImage2DEXT = (PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyMultiTexSubImage2DEXT")) == NULL) || r; + r = ((glCopyMultiTexSubImage3DEXT = (PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyMultiTexSubImage3DEXT")) == NULL) || r; + r = ((glCopyTextureImage1DEXT = (PFNGLCOPYTEXTUREIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTextureImage1DEXT")) == NULL) || r; + r = ((glCopyTextureImage2DEXT = (PFNGLCOPYTEXTUREIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTextureImage2DEXT")) == NULL) || r; + r = ((glCopyTextureSubImage1DEXT = (PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTextureSubImage1DEXT")) == NULL) || r; + r = ((glCopyTextureSubImage2DEXT = (PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTextureSubImage2DEXT")) == NULL) || r; + r = ((glCopyTextureSubImage3DEXT = (PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glCopyTextureSubImage3DEXT")) == NULL) || r; + r = ((glDisableClientStateIndexedEXT = (PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC)glewGetProcAddress((const GLubyte*)"glDisableClientStateIndexedEXT")) == NULL) || r; + r = ((glEnableClientStateIndexedEXT = (PFNGLENABLECLIENTSTATEINDEXEDEXTPROC)glewGetProcAddress((const GLubyte*)"glEnableClientStateIndexedEXT")) == NULL) || r; + r = ((glFramebufferDrawBufferEXT = (PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferDrawBufferEXT")) == NULL) || r; + r = ((glFramebufferDrawBuffersEXT = (PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferDrawBuffersEXT")) == NULL) || r; + r = ((glFramebufferReadBufferEXT = (PFNGLFRAMEBUFFERREADBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferReadBufferEXT")) == NULL) || r; + r = ((glGenerateMultiTexMipmapEXT = (PFNGLGENERATEMULTITEXMIPMAPEXTPROC)glewGetProcAddress((const GLubyte*)"glGenerateMultiTexMipmapEXT")) == NULL) || r; + r = ((glGenerateTextureMipmapEXT = (PFNGLGENERATETEXTUREMIPMAPEXTPROC)glewGetProcAddress((const GLubyte*)"glGenerateTextureMipmapEXT")) == NULL) || r; + r = ((glGetCompressedMultiTexImageEXT = (PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glGetCompressedMultiTexImageEXT")) == NULL) || r; + r = ((glGetCompressedTextureImageEXT = (PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glGetCompressedTextureImageEXT")) == NULL) || r; + r = ((glGetDoubleIndexedvEXT = (PFNGLGETDOUBLEINDEXEDVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetDoubleIndexedvEXT")) == NULL) || r; + r = ((glGetFloatIndexedvEXT = (PFNGLGETFLOATINDEXEDVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFloatIndexedvEXT")) == NULL) || r; + r = ((glGetFramebufferParameterivEXT = (PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFramebufferParameterivEXT")) == NULL) || r; + r = ((glGetMultiTexEnvfvEXT = (PFNGLGETMULTITEXENVFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexEnvfvEXT")) == NULL) || r; + r = ((glGetMultiTexEnvivEXT = (PFNGLGETMULTITEXENVIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexEnvivEXT")) == NULL) || r; + r = ((glGetMultiTexGendvEXT = (PFNGLGETMULTITEXGENDVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexGendvEXT")) == NULL) || r; + r = ((glGetMultiTexGenfvEXT = (PFNGLGETMULTITEXGENFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexGenfvEXT")) == NULL) || r; + r = ((glGetMultiTexGenivEXT = (PFNGLGETMULTITEXGENIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexGenivEXT")) == NULL) || r; + r = ((glGetMultiTexImageEXT = (PFNGLGETMULTITEXIMAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexImageEXT")) == NULL) || r; + r = ((glGetMultiTexLevelParameterfvEXT = (PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexLevelParameterfvEXT")) == NULL) || r; + r = ((glGetMultiTexLevelParameterivEXT = (PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexLevelParameterivEXT")) == NULL) || r; + r = ((glGetMultiTexParameterIivEXT = (PFNGLGETMULTITEXPARAMETERIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexParameterIivEXT")) == NULL) || r; + r = ((glGetMultiTexParameterIuivEXT = (PFNGLGETMULTITEXPARAMETERIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexParameterIuivEXT")) == NULL) || r; + r = ((glGetMultiTexParameterfvEXT = (PFNGLGETMULTITEXPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexParameterfvEXT")) == NULL) || r; + r = ((glGetMultiTexParameterivEXT = (PFNGLGETMULTITEXPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMultiTexParameterivEXT")) == NULL) || r; + r = ((glGetNamedBufferParameterivEXT = (PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedBufferParameterivEXT")) == NULL) || r; + r = ((glGetNamedBufferPointervEXT = (PFNGLGETNAMEDBUFFERPOINTERVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedBufferPointervEXT")) == NULL) || r; + r = ((glGetNamedBufferSubDataEXT = (PFNGLGETNAMEDBUFFERSUBDATAEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedBufferSubDataEXT")) == NULL) || r; + r = ((glGetNamedFramebufferAttachmentParameterivEXT = (PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedFramebufferAttachmentParameterivEXT")) == NULL) || r; + r = ((glGetNamedProgramLocalParameterIivEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedProgramLocalParameterIivEXT")) == NULL) || r; + r = ((glGetNamedProgramLocalParameterIuivEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedProgramLocalParameterIuivEXT")) == NULL) || r; + r = ((glGetNamedProgramLocalParameterdvEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedProgramLocalParameterdvEXT")) == NULL) || r; + r = ((glGetNamedProgramLocalParameterfvEXT = (PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedProgramLocalParameterfvEXT")) == NULL) || r; + r = ((glGetNamedProgramStringEXT = (PFNGLGETNAMEDPROGRAMSTRINGEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedProgramStringEXT")) == NULL) || r; + r = ((glGetNamedProgramivEXT = (PFNGLGETNAMEDPROGRAMIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedProgramivEXT")) == NULL) || r; + r = ((glGetNamedRenderbufferParameterivEXT = (PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetNamedRenderbufferParameterivEXT")) == NULL) || r; + r = ((glGetPointerIndexedvEXT = (PFNGLGETPOINTERINDEXEDVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetPointerIndexedvEXT")) == NULL) || r; + r = ((glGetTextureImageEXT = (PFNGLGETTEXTUREIMAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTextureImageEXT")) == NULL) || r; + r = ((glGetTextureLevelParameterfvEXT = (PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTextureLevelParameterfvEXT")) == NULL) || r; + r = ((glGetTextureLevelParameterivEXT = (PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTextureLevelParameterivEXT")) == NULL) || r; + r = ((glGetTextureParameterIivEXT = (PFNGLGETTEXTUREPARAMETERIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTextureParameterIivEXT")) == NULL) || r; + r = ((glGetTextureParameterIuivEXT = (PFNGLGETTEXTUREPARAMETERIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTextureParameterIuivEXT")) == NULL) || r; + r = ((glGetTextureParameterfvEXT = (PFNGLGETTEXTUREPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTextureParameterfvEXT")) == NULL) || r; + r = ((glGetTextureParameterivEXT = (PFNGLGETTEXTUREPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTextureParameterivEXT")) == NULL) || r; + r = ((glMapNamedBufferEXT = (PFNGLMAPNAMEDBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glMapNamedBufferEXT")) == NULL) || r; + r = ((glMatrixFrustumEXT = (PFNGLMATRIXFRUSTUMEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixFrustumEXT")) == NULL) || r; + r = ((glMatrixLoadIdentityEXT = (PFNGLMATRIXLOADIDENTITYEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixLoadIdentityEXT")) == NULL) || r; + r = ((glMatrixLoadTransposedEXT = (PFNGLMATRIXLOADTRANSPOSEDEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixLoadTransposedEXT")) == NULL) || r; + r = ((glMatrixLoadTransposefEXT = (PFNGLMATRIXLOADTRANSPOSEFEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixLoadTransposefEXT")) == NULL) || r; + r = ((glMatrixLoaddEXT = (PFNGLMATRIXLOADDEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixLoaddEXT")) == NULL) || r; + r = ((glMatrixLoadfEXT = (PFNGLMATRIXLOADFEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixLoadfEXT")) == NULL) || r; + r = ((glMatrixMultTransposedEXT = (PFNGLMATRIXMULTTRANSPOSEDEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixMultTransposedEXT")) == NULL) || r; + r = ((glMatrixMultTransposefEXT = (PFNGLMATRIXMULTTRANSPOSEFEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixMultTransposefEXT")) == NULL) || r; + r = ((glMatrixMultdEXT = (PFNGLMATRIXMULTDEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixMultdEXT")) == NULL) || r; + r = ((glMatrixMultfEXT = (PFNGLMATRIXMULTFEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixMultfEXT")) == NULL) || r; + r = ((glMatrixOrthoEXT = (PFNGLMATRIXORTHOEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixOrthoEXT")) == NULL) || r; + r = ((glMatrixPopEXT = (PFNGLMATRIXPOPEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixPopEXT")) == NULL) || r; + r = ((glMatrixPushEXT = (PFNGLMATRIXPUSHEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixPushEXT")) == NULL) || r; + r = ((glMatrixRotatedEXT = (PFNGLMATRIXROTATEDEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixRotatedEXT")) == NULL) || r; + r = ((glMatrixRotatefEXT = (PFNGLMATRIXROTATEFEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixRotatefEXT")) == NULL) || r; + r = ((glMatrixScaledEXT = (PFNGLMATRIXSCALEDEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixScaledEXT")) == NULL) || r; + r = ((glMatrixScalefEXT = (PFNGLMATRIXSCALEFEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixScalefEXT")) == NULL) || r; + r = ((glMatrixTranslatedEXT = (PFNGLMATRIXTRANSLATEDEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixTranslatedEXT")) == NULL) || r; + r = ((glMatrixTranslatefEXT = (PFNGLMATRIXTRANSLATEFEXTPROC)glewGetProcAddress((const GLubyte*)"glMatrixTranslatefEXT")) == NULL) || r; + r = ((glMultiTexBufferEXT = (PFNGLMULTITEXBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexBufferEXT")) == NULL) || r; + r = ((glMultiTexCoordPointerEXT = (PFNGLMULTITEXCOORDPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoordPointerEXT")) == NULL) || r; + r = ((glMultiTexEnvfEXT = (PFNGLMULTITEXENVFEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexEnvfEXT")) == NULL) || r; + r = ((glMultiTexEnvfvEXT = (PFNGLMULTITEXENVFVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexEnvfvEXT")) == NULL) || r; + r = ((glMultiTexEnviEXT = (PFNGLMULTITEXENVIEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexEnviEXT")) == NULL) || r; + r = ((glMultiTexEnvivEXT = (PFNGLMULTITEXENVIVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexEnvivEXT")) == NULL) || r; + r = ((glMultiTexGendEXT = (PFNGLMULTITEXGENDEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexGendEXT")) == NULL) || r; + r = ((glMultiTexGendvEXT = (PFNGLMULTITEXGENDVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexGendvEXT")) == NULL) || r; + r = ((glMultiTexGenfEXT = (PFNGLMULTITEXGENFEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexGenfEXT")) == NULL) || r; + r = ((glMultiTexGenfvEXT = (PFNGLMULTITEXGENFVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexGenfvEXT")) == NULL) || r; + r = ((glMultiTexGeniEXT = (PFNGLMULTITEXGENIEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexGeniEXT")) == NULL) || r; + r = ((glMultiTexGenivEXT = (PFNGLMULTITEXGENIVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexGenivEXT")) == NULL) || r; + r = ((glMultiTexImage1DEXT = (PFNGLMULTITEXIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexImage1DEXT")) == NULL) || r; + r = ((glMultiTexImage2DEXT = (PFNGLMULTITEXIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexImage2DEXT")) == NULL) || r; + r = ((glMultiTexImage3DEXT = (PFNGLMULTITEXIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexImage3DEXT")) == NULL) || r; + r = ((glMultiTexParameterIivEXT = (PFNGLMULTITEXPARAMETERIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexParameterIivEXT")) == NULL) || r; + r = ((glMultiTexParameterIuivEXT = (PFNGLMULTITEXPARAMETERIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexParameterIuivEXT")) == NULL) || r; + r = ((glMultiTexParameterfEXT = (PFNGLMULTITEXPARAMETERFEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexParameterfEXT")) == NULL) || r; + r = ((glMultiTexParameterfvEXT = (PFNGLMULTITEXPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexParameterfvEXT")) == NULL) || r; + r = ((glMultiTexParameteriEXT = (PFNGLMULTITEXPARAMETERIEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexParameteriEXT")) == NULL) || r; + r = ((glMultiTexParameterivEXT = (PFNGLMULTITEXPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexParameterivEXT")) == NULL) || r; + r = ((glMultiTexRenderbufferEXT = (PFNGLMULTITEXRENDERBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexRenderbufferEXT")) == NULL) || r; + r = ((glMultiTexSubImage1DEXT = (PFNGLMULTITEXSUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexSubImage1DEXT")) == NULL) || r; + r = ((glMultiTexSubImage2DEXT = (PFNGLMULTITEXSUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexSubImage2DEXT")) == NULL) || r; + r = ((glMultiTexSubImage3DEXT = (PFNGLMULTITEXSUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiTexSubImage3DEXT")) == NULL) || r; + r = ((glNamedBufferDataEXT = (PFNGLNAMEDBUFFERDATAEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedBufferDataEXT")) == NULL) || r; + r = ((glNamedBufferSubDataEXT = (PFNGLNAMEDBUFFERSUBDATAEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedBufferSubDataEXT")) == NULL) || r; + r = ((glNamedFramebufferRenderbufferEXT = (PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glNamedFramebufferRenderbufferEXT")) == NULL) || r; + r = ((glNamedFramebufferTexture1DEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedFramebufferTexture1DEXT")) == NULL) || r; + r = ((glNamedFramebufferTexture2DEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedFramebufferTexture2DEXT")) == NULL) || r; + r = ((glNamedFramebufferTexture3DEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedFramebufferTexture3DEXT")) == NULL) || r; + r = ((glNamedFramebufferTextureEXT = (PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedFramebufferTextureEXT")) == NULL) || r; + r = ((glNamedFramebufferTextureFaceEXT = (PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedFramebufferTextureFaceEXT")) == NULL) || r; + r = ((glNamedFramebufferTextureLayerEXT = (PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC)glewGetProcAddress((const GLubyte*)"glNamedFramebufferTextureLayerEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameter4dEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameter4dEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameter4dvEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameter4dvEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameter4fEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameter4fEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameter4fvEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameter4fvEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameterI4iEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameterI4iEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameterI4ivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameterI4ivEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameterI4uiEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameterI4uiEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameterI4uivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameterI4uivEXT")) == NULL) || r; + r = ((glNamedProgramLocalParameters4fvEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParameters4fvEXT")) == NULL) || r; + r = ((glNamedProgramLocalParametersI4ivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParametersI4ivEXT")) == NULL) || r; + r = ((glNamedProgramLocalParametersI4uivEXT = (PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramLocalParametersI4uivEXT")) == NULL) || r; + r = ((glNamedProgramStringEXT = (PFNGLNAMEDPROGRAMSTRINGEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedProgramStringEXT")) == NULL) || r; + r = ((glNamedRenderbufferStorageEXT = (PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedRenderbufferStorageEXT")) == NULL) || r; + r = ((glNamedRenderbufferStorageMultisampleCoverageEXT = (PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedRenderbufferStorageMultisampleCoverageEXT")) == NULL) || r; + r = ((glNamedRenderbufferStorageMultisampleEXT = (PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)glewGetProcAddress((const GLubyte*)"glNamedRenderbufferStorageMultisampleEXT")) == NULL) || r; + r = ((glProgramUniform1fEXT = (PFNGLPROGRAMUNIFORM1FEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform1fEXT")) == NULL) || r; + r = ((glProgramUniform1fvEXT = (PFNGLPROGRAMUNIFORM1FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform1fvEXT")) == NULL) || r; + r = ((glProgramUniform1iEXT = (PFNGLPROGRAMUNIFORM1IEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform1iEXT")) == NULL) || r; + r = ((glProgramUniform1ivEXT = (PFNGLPROGRAMUNIFORM1IVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform1ivEXT")) == NULL) || r; + r = ((glProgramUniform1uiEXT = (PFNGLPROGRAMUNIFORM1UIEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform1uiEXT")) == NULL) || r; + r = ((glProgramUniform1uivEXT = (PFNGLPROGRAMUNIFORM1UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform1uivEXT")) == NULL) || r; + r = ((glProgramUniform2fEXT = (PFNGLPROGRAMUNIFORM2FEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform2fEXT")) == NULL) || r; + r = ((glProgramUniform2fvEXT = (PFNGLPROGRAMUNIFORM2FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform2fvEXT")) == NULL) || r; + r = ((glProgramUniform2iEXT = (PFNGLPROGRAMUNIFORM2IEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform2iEXT")) == NULL) || r; + r = ((glProgramUniform2ivEXT = (PFNGLPROGRAMUNIFORM2IVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform2ivEXT")) == NULL) || r; + r = ((glProgramUniform2uiEXT = (PFNGLPROGRAMUNIFORM2UIEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform2uiEXT")) == NULL) || r; + r = ((glProgramUniform2uivEXT = (PFNGLPROGRAMUNIFORM2UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform2uivEXT")) == NULL) || r; + r = ((glProgramUniform3fEXT = (PFNGLPROGRAMUNIFORM3FEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform3fEXT")) == NULL) || r; + r = ((glProgramUniform3fvEXT = (PFNGLPROGRAMUNIFORM3FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform3fvEXT")) == NULL) || r; + r = ((glProgramUniform3iEXT = (PFNGLPROGRAMUNIFORM3IEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform3iEXT")) == NULL) || r; + r = ((glProgramUniform3ivEXT = (PFNGLPROGRAMUNIFORM3IVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform3ivEXT")) == NULL) || r; + r = ((glProgramUniform3uiEXT = (PFNGLPROGRAMUNIFORM3UIEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform3uiEXT")) == NULL) || r; + r = ((glProgramUniform3uivEXT = (PFNGLPROGRAMUNIFORM3UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform3uivEXT")) == NULL) || r; + r = ((glProgramUniform4fEXT = (PFNGLPROGRAMUNIFORM4FEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform4fEXT")) == NULL) || r; + r = ((glProgramUniform4fvEXT = (PFNGLPROGRAMUNIFORM4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform4fvEXT")) == NULL) || r; + r = ((glProgramUniform4iEXT = (PFNGLPROGRAMUNIFORM4IEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform4iEXT")) == NULL) || r; + r = ((glProgramUniform4ivEXT = (PFNGLPROGRAMUNIFORM4IVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform4ivEXT")) == NULL) || r; + r = ((glProgramUniform4uiEXT = (PFNGLPROGRAMUNIFORM4UIEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform4uiEXT")) == NULL) || r; + r = ((glProgramUniform4uivEXT = (PFNGLPROGRAMUNIFORM4UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniform4uivEXT")) == NULL) || r; + r = ((glProgramUniformMatrix2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix2fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix2x3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix2x3fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix2x4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix2x4fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix3fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix3x2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix3x2fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix3x4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix3x4fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix4fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix4fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix4x2fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix4x2fvEXT")) == NULL) || r; + r = ((glProgramUniformMatrix4x3fvEXT = (PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramUniformMatrix4x3fvEXT")) == NULL) || r; + r = ((glPushClientAttribDefaultEXT = (PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC)glewGetProcAddress((const GLubyte*)"glPushClientAttribDefaultEXT")) == NULL) || r; + r = ((glTextureBufferEXT = (PFNGLTEXTUREBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glTextureBufferEXT")) == NULL) || r; + r = ((glTextureImage1DEXT = (PFNGLTEXTUREIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureImage1DEXT")) == NULL) || r; + r = ((glTextureImage2DEXT = (PFNGLTEXTUREIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureImage2DEXT")) == NULL) || r; + r = ((glTextureImage3DEXT = (PFNGLTEXTUREIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureImage3DEXT")) == NULL) || r; + r = ((glTextureParameterIivEXT = (PFNGLTEXTUREPARAMETERIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureParameterIivEXT")) == NULL) || r; + r = ((glTextureParameterIuivEXT = (PFNGLTEXTUREPARAMETERIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureParameterIuivEXT")) == NULL) || r; + r = ((glTextureParameterfEXT = (PFNGLTEXTUREPARAMETERFEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureParameterfEXT")) == NULL) || r; + r = ((glTextureParameterfvEXT = (PFNGLTEXTUREPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureParameterfvEXT")) == NULL) || r; + r = ((glTextureParameteriEXT = (PFNGLTEXTUREPARAMETERIEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureParameteriEXT")) == NULL) || r; + r = ((glTextureParameterivEXT = (PFNGLTEXTUREPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureParameterivEXT")) == NULL) || r; + r = ((glTextureRenderbufferEXT = (PFNGLTEXTURERENDERBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glTextureRenderbufferEXT")) == NULL) || r; + r = ((glTextureSubImage1DEXT = (PFNGLTEXTURESUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureSubImage1DEXT")) == NULL) || r; + r = ((glTextureSubImage2DEXT = (PFNGLTEXTURESUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureSubImage2DEXT")) == NULL) || r; + r = ((glTextureSubImage3DEXT = (PFNGLTEXTURESUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureSubImage3DEXT")) == NULL) || r; + r = ((glUnmapNamedBufferEXT = (PFNGLUNMAPNAMEDBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glUnmapNamedBufferEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_direct_state_access */ + +#ifdef GL_EXT_draw_buffers2 + +static GLboolean _glewInit_GL_EXT_draw_buffers2 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColorMaskIndexedEXT = (PFNGLCOLORMASKINDEXEDEXTPROC)glewGetProcAddress((const GLubyte*)"glColorMaskIndexedEXT")) == NULL) || r; + r = ((glDisableIndexedEXT = (PFNGLDISABLEINDEXEDEXTPROC)glewGetProcAddress((const GLubyte*)"glDisableIndexedEXT")) == NULL) || r; + r = ((glEnableIndexedEXT = (PFNGLENABLEINDEXEDEXTPROC)glewGetProcAddress((const GLubyte*)"glEnableIndexedEXT")) == NULL) || r; + r = ((glGetBooleanIndexedvEXT = (PFNGLGETBOOLEANINDEXEDVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetBooleanIndexedvEXT")) == NULL) || r; + r = ((glGetIntegerIndexedvEXT = (PFNGLGETINTEGERINDEXEDVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetIntegerIndexedvEXT")) == NULL) || r; + r = ((glIsEnabledIndexedEXT = (PFNGLISENABLEDINDEXEDEXTPROC)glewGetProcAddress((const GLubyte*)"glIsEnabledIndexedEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_draw_buffers2 */ + +#ifdef GL_EXT_draw_instanced + +static GLboolean _glewInit_GL_EXT_draw_instanced (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDrawArraysInstancedEXT = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)glewGetProcAddress((const GLubyte*)"glDrawArraysInstancedEXT")) == NULL) || r; + r = ((glDrawElementsInstancedEXT = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)glewGetProcAddress((const GLubyte*)"glDrawElementsInstancedEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_draw_instanced */ + +#ifdef GL_EXT_draw_range_elements + +static GLboolean _glewInit_GL_EXT_draw_range_elements (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDrawRangeElementsEXT = (PFNGLDRAWRANGEELEMENTSEXTPROC)glewGetProcAddress((const GLubyte*)"glDrawRangeElementsEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_draw_range_elements */ + +#ifdef GL_EXT_fog_coord + +static GLboolean _glewInit_GL_EXT_fog_coord (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFogCoordPointerEXT = (PFNGLFOGCOORDPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glFogCoordPointerEXT")) == NULL) || r; + r = ((glFogCoorddEXT = (PFNGLFOGCOORDDEXTPROC)glewGetProcAddress((const GLubyte*)"glFogCoorddEXT")) == NULL) || r; + r = ((glFogCoorddvEXT = (PFNGLFOGCOORDDVEXTPROC)glewGetProcAddress((const GLubyte*)"glFogCoorddvEXT")) == NULL) || r; + r = ((glFogCoordfEXT = (PFNGLFOGCOORDFEXTPROC)glewGetProcAddress((const GLubyte*)"glFogCoordfEXT")) == NULL) || r; + r = ((glFogCoordfvEXT = (PFNGLFOGCOORDFVEXTPROC)glewGetProcAddress((const GLubyte*)"glFogCoordfvEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_fog_coord */ + +#ifdef GL_EXT_fragment_lighting + +static GLboolean _glewInit_GL_EXT_fragment_lighting (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFragmentColorMaterialEXT = (PFNGLFRAGMENTCOLORMATERIALEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentColorMaterialEXT")) == NULL) || r; + r = ((glFragmentLightModelfEXT = (PFNGLFRAGMENTLIGHTMODELFEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModelfEXT")) == NULL) || r; + r = ((glFragmentLightModelfvEXT = (PFNGLFRAGMENTLIGHTMODELFVEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModelfvEXT")) == NULL) || r; + r = ((glFragmentLightModeliEXT = (PFNGLFRAGMENTLIGHTMODELIEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModeliEXT")) == NULL) || r; + r = ((glFragmentLightModelivEXT = (PFNGLFRAGMENTLIGHTMODELIVEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModelivEXT")) == NULL) || r; + r = ((glFragmentLightfEXT = (PFNGLFRAGMENTLIGHTFEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightfEXT")) == NULL) || r; + r = ((glFragmentLightfvEXT = (PFNGLFRAGMENTLIGHTFVEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightfvEXT")) == NULL) || r; + r = ((glFragmentLightiEXT = (PFNGLFRAGMENTLIGHTIEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightiEXT")) == NULL) || r; + r = ((glFragmentLightivEXT = (PFNGLFRAGMENTLIGHTIVEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightivEXT")) == NULL) || r; + r = ((glFragmentMaterialfEXT = (PFNGLFRAGMENTMATERIALFEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialfEXT")) == NULL) || r; + r = ((glFragmentMaterialfvEXT = (PFNGLFRAGMENTMATERIALFVEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialfvEXT")) == NULL) || r; + r = ((glFragmentMaterialiEXT = (PFNGLFRAGMENTMATERIALIEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialiEXT")) == NULL) || r; + r = ((glFragmentMaterialivEXT = (PFNGLFRAGMENTMATERIALIVEXTPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialivEXT")) == NULL) || r; + r = ((glGetFragmentLightfvEXT = (PFNGLGETFRAGMENTLIGHTFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentLightfvEXT")) == NULL) || r; + r = ((glGetFragmentLightivEXT = (PFNGLGETFRAGMENTLIGHTIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentLightivEXT")) == NULL) || r; + r = ((glGetFragmentMaterialfvEXT = (PFNGLGETFRAGMENTMATERIALFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentMaterialfvEXT")) == NULL) || r; + r = ((glGetFragmentMaterialivEXT = (PFNGLGETFRAGMENTMATERIALIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentMaterialivEXT")) == NULL) || r; + r = ((glLightEnviEXT = (PFNGLLIGHTENVIEXTPROC)glewGetProcAddress((const GLubyte*)"glLightEnviEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_fragment_lighting */ + +#ifdef GL_EXT_framebuffer_blit + +static GLboolean _glewInit_GL_EXT_framebuffer_blit (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBlitFramebufferEXT = (PFNGLBLITFRAMEBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glBlitFramebufferEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_framebuffer_blit */ + +#ifdef GL_EXT_framebuffer_multisample + +static GLboolean _glewInit_GL_EXT_framebuffer_multisample (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)glewGetProcAddress((const GLubyte*)"glRenderbufferStorageMultisampleEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_framebuffer_multisample */ + +#ifdef GL_EXT_framebuffer_object + +static GLboolean _glewInit_GL_EXT_framebuffer_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindFramebufferEXT")) == NULL) || r; + r = ((glBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindRenderbufferEXT")) == NULL) || r; + r = ((glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)glewGetProcAddress((const GLubyte*)"glCheckFramebufferStatusEXT")) == NULL) || r; + r = ((glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)glewGetProcAddress((const GLubyte*)"glDeleteFramebuffersEXT")) == NULL) || r; + r = ((glDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC)glewGetProcAddress((const GLubyte*)"glDeleteRenderbuffersEXT")) == NULL) || r; + r = ((glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferRenderbufferEXT")) == NULL) || r; + r = ((glFramebufferTexture1DEXT = (PFNGLFRAMEBUFFERTEXTURE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTexture1DEXT")) == NULL) || r; + r = ((glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTexture2DEXT")) == NULL) || r; + r = ((glFramebufferTexture3DEXT = (PFNGLFRAMEBUFFERTEXTURE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTexture3DEXT")) == NULL) || r; + r = ((glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)glewGetProcAddress((const GLubyte*)"glGenFramebuffersEXT")) == NULL) || r; + r = ((glGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC)glewGetProcAddress((const GLubyte*)"glGenRenderbuffersEXT")) == NULL) || r; + r = ((glGenerateMipmapEXT = (PFNGLGENERATEMIPMAPEXTPROC)glewGetProcAddress((const GLubyte*)"glGenerateMipmapEXT")) == NULL) || r; + r = ((glGetFramebufferAttachmentParameterivEXT = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFramebufferAttachmentParameterivEXT")) == NULL) || r; + r = ((glGetRenderbufferParameterivEXT = (PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetRenderbufferParameterivEXT")) == NULL) || r; + r = ((glIsFramebufferEXT = (PFNGLISFRAMEBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glIsFramebufferEXT")) == NULL) || r; + r = ((glIsRenderbufferEXT = (PFNGLISRENDERBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glIsRenderbufferEXT")) == NULL) || r; + r = ((glRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glRenderbufferStorageEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_framebuffer_object */ + +#ifdef GL_EXT_framebuffer_sRGB + +#endif /* GL_EXT_framebuffer_sRGB */ + +#ifdef GL_EXT_geometry_shader4 + +static GLboolean _glewInit_GL_EXT_geometry_shader4 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFramebufferTextureEXT = (PFNGLFRAMEBUFFERTEXTUREEXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTextureEXT")) == NULL) || r; + r = ((glFramebufferTextureFaceEXT = (PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTextureFaceEXT")) == NULL) || r; + r = ((glFramebufferTextureLayerEXT = (PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC)glewGetProcAddress((const GLubyte*)"glFramebufferTextureLayerEXT")) == NULL) || r; + r = ((glProgramParameteriEXT = (PFNGLPROGRAMPARAMETERIEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramParameteriEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_geometry_shader4 */ + +#ifdef GL_EXT_gpu_program_parameters + +static GLboolean _glewInit_GL_EXT_gpu_program_parameters (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glProgramEnvParameters4fvEXT = (PFNGLPROGRAMENVPARAMETERS4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameters4fvEXT")) == NULL) || r; + r = ((glProgramLocalParameters4fvEXT = (PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameters4fvEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_gpu_program_parameters */ + +#ifdef GL_EXT_gpu_shader4 + +static GLboolean _glewInit_GL_EXT_gpu_shader4 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBindFragDataLocationEXT = (PFNGLBINDFRAGDATALOCATIONEXTPROC)glewGetProcAddress((const GLubyte*)"glBindFragDataLocationEXT")) == NULL) || r; + r = ((glGetFragDataLocationEXT = (PFNGLGETFRAGDATALOCATIONEXTPROC)glewGetProcAddress((const GLubyte*)"glGetFragDataLocationEXT")) == NULL) || r; + r = ((glGetUniformuivEXT = (PFNGLGETUNIFORMUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetUniformuivEXT")) == NULL) || r; + r = ((glGetVertexAttribIivEXT = (PFNGLGETVERTEXATTRIBIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribIivEXT")) == NULL) || r; + r = ((glGetVertexAttribIuivEXT = (PFNGLGETVERTEXATTRIBIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribIuivEXT")) == NULL) || r; + r = ((glUniform1uiEXT = (PFNGLUNIFORM1UIEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform1uiEXT")) == NULL) || r; + r = ((glUniform1uivEXT = (PFNGLUNIFORM1UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform1uivEXT")) == NULL) || r; + r = ((glUniform2uiEXT = (PFNGLUNIFORM2UIEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform2uiEXT")) == NULL) || r; + r = ((glUniform2uivEXT = (PFNGLUNIFORM2UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform2uivEXT")) == NULL) || r; + r = ((glUniform3uiEXT = (PFNGLUNIFORM3UIEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform3uiEXT")) == NULL) || r; + r = ((glUniform3uivEXT = (PFNGLUNIFORM3UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform3uivEXT")) == NULL) || r; + r = ((glUniform4uiEXT = (PFNGLUNIFORM4UIEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform4uiEXT")) == NULL) || r; + r = ((glUniform4uivEXT = (PFNGLUNIFORM4UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glUniform4uivEXT")) == NULL) || r; + r = ((glVertexAttribI1iEXT = (PFNGLVERTEXATTRIBI1IEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1iEXT")) == NULL) || r; + r = ((glVertexAttribI1ivEXT = (PFNGLVERTEXATTRIBI1IVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1ivEXT")) == NULL) || r; + r = ((glVertexAttribI1uiEXT = (PFNGLVERTEXATTRIBI1UIEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1uiEXT")) == NULL) || r; + r = ((glVertexAttribI1uivEXT = (PFNGLVERTEXATTRIBI1UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI1uivEXT")) == NULL) || r; + r = ((glVertexAttribI2iEXT = (PFNGLVERTEXATTRIBI2IEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2iEXT")) == NULL) || r; + r = ((glVertexAttribI2ivEXT = (PFNGLVERTEXATTRIBI2IVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2ivEXT")) == NULL) || r; + r = ((glVertexAttribI2uiEXT = (PFNGLVERTEXATTRIBI2UIEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2uiEXT")) == NULL) || r; + r = ((glVertexAttribI2uivEXT = (PFNGLVERTEXATTRIBI2UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI2uivEXT")) == NULL) || r; + r = ((glVertexAttribI3iEXT = (PFNGLVERTEXATTRIBI3IEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3iEXT")) == NULL) || r; + r = ((glVertexAttribI3ivEXT = (PFNGLVERTEXATTRIBI3IVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3ivEXT")) == NULL) || r; + r = ((glVertexAttribI3uiEXT = (PFNGLVERTEXATTRIBI3UIEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3uiEXT")) == NULL) || r; + r = ((glVertexAttribI3uivEXT = (PFNGLVERTEXATTRIBI3UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI3uivEXT")) == NULL) || r; + r = ((glVertexAttribI4bvEXT = (PFNGLVERTEXATTRIBI4BVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4bvEXT")) == NULL) || r; + r = ((glVertexAttribI4iEXT = (PFNGLVERTEXATTRIBI4IEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4iEXT")) == NULL) || r; + r = ((glVertexAttribI4ivEXT = (PFNGLVERTEXATTRIBI4IVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4ivEXT")) == NULL) || r; + r = ((glVertexAttribI4svEXT = (PFNGLVERTEXATTRIBI4SVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4svEXT")) == NULL) || r; + r = ((glVertexAttribI4ubvEXT = (PFNGLVERTEXATTRIBI4UBVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4ubvEXT")) == NULL) || r; + r = ((glVertexAttribI4uiEXT = (PFNGLVERTEXATTRIBI4UIEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4uiEXT")) == NULL) || r; + r = ((glVertexAttribI4uivEXT = (PFNGLVERTEXATTRIBI4UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4uivEXT")) == NULL) || r; + r = ((glVertexAttribI4usvEXT = (PFNGLVERTEXATTRIBI4USVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribI4usvEXT")) == NULL) || r; + r = ((glVertexAttribIPointerEXT = (PFNGLVERTEXATTRIBIPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribIPointerEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_gpu_shader4 */ + +#ifdef GL_EXT_histogram + +static GLboolean _glewInit_GL_EXT_histogram (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetHistogramEXT = (PFNGLGETHISTOGRAMEXTPROC)glewGetProcAddress((const GLubyte*)"glGetHistogramEXT")) == NULL) || r; + r = ((glGetHistogramParameterfvEXT = (PFNGLGETHISTOGRAMPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetHistogramParameterfvEXT")) == NULL) || r; + r = ((glGetHistogramParameterivEXT = (PFNGLGETHISTOGRAMPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetHistogramParameterivEXT")) == NULL) || r; + r = ((glGetMinmaxEXT = (PFNGLGETMINMAXEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMinmaxEXT")) == NULL) || r; + r = ((glGetMinmaxParameterfvEXT = (PFNGLGETMINMAXPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMinmaxParameterfvEXT")) == NULL) || r; + r = ((glGetMinmaxParameterivEXT = (PFNGLGETMINMAXPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetMinmaxParameterivEXT")) == NULL) || r; + r = ((glHistogramEXT = (PFNGLHISTOGRAMEXTPROC)glewGetProcAddress((const GLubyte*)"glHistogramEXT")) == NULL) || r; + r = ((glMinmaxEXT = (PFNGLMINMAXEXTPROC)glewGetProcAddress((const GLubyte*)"glMinmaxEXT")) == NULL) || r; + r = ((glResetHistogramEXT = (PFNGLRESETHISTOGRAMEXTPROC)glewGetProcAddress((const GLubyte*)"glResetHistogramEXT")) == NULL) || r; + r = ((glResetMinmaxEXT = (PFNGLRESETMINMAXEXTPROC)glewGetProcAddress((const GLubyte*)"glResetMinmaxEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_histogram */ + +#ifdef GL_EXT_index_array_formats + +#endif /* GL_EXT_index_array_formats */ + +#ifdef GL_EXT_index_func + +static GLboolean _glewInit_GL_EXT_index_func (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glIndexFuncEXT = (PFNGLINDEXFUNCEXTPROC)glewGetProcAddress((const GLubyte*)"glIndexFuncEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_index_func */ + +#ifdef GL_EXT_index_material + +static GLboolean _glewInit_GL_EXT_index_material (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glIndexMaterialEXT = (PFNGLINDEXMATERIALEXTPROC)glewGetProcAddress((const GLubyte*)"glIndexMaterialEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_index_material */ + +#ifdef GL_EXT_index_texture + +#endif /* GL_EXT_index_texture */ + +#ifdef GL_EXT_light_texture + +static GLboolean _glewInit_GL_EXT_light_texture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glApplyTextureEXT = (PFNGLAPPLYTEXTUREEXTPROC)glewGetProcAddress((const GLubyte*)"glApplyTextureEXT")) == NULL) || r; + r = ((glTextureLightEXT = (PFNGLTEXTURELIGHTEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureLightEXT")) == NULL) || r; + r = ((glTextureMaterialEXT = (PFNGLTEXTUREMATERIALEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureMaterialEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_light_texture */ + +#ifdef GL_EXT_misc_attribute + +#endif /* GL_EXT_misc_attribute */ + +#ifdef GL_EXT_multi_draw_arrays + +static GLboolean _glewInit_GL_EXT_multi_draw_arrays (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glMultiDrawArraysEXT = (PFNGLMULTIDRAWARRAYSEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiDrawArraysEXT")) == NULL) || r; + r = ((glMultiDrawElementsEXT = (PFNGLMULTIDRAWELEMENTSEXTPROC)glewGetProcAddress((const GLubyte*)"glMultiDrawElementsEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_multi_draw_arrays */ + +#ifdef GL_EXT_multisample + +static GLboolean _glewInit_GL_EXT_multisample (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glSampleMaskEXT = (PFNGLSAMPLEMASKEXTPROC)glewGetProcAddress((const GLubyte*)"glSampleMaskEXT")) == NULL) || r; + r = ((glSamplePatternEXT = (PFNGLSAMPLEPATTERNEXTPROC)glewGetProcAddress((const GLubyte*)"glSamplePatternEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_multisample */ + +#ifdef GL_EXT_packed_depth_stencil + +#endif /* GL_EXT_packed_depth_stencil */ + +#ifdef GL_EXT_packed_float + +#endif /* GL_EXT_packed_float */ + +#ifdef GL_EXT_packed_pixels + +#endif /* GL_EXT_packed_pixels */ + +#ifdef GL_EXT_paletted_texture + +static GLboolean _glewInit_GL_EXT_paletted_texture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColorTableEXT = (PFNGLCOLORTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"glColorTableEXT")) == NULL) || r; + r = ((glGetColorTableEXT = (PFNGLGETCOLORTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableEXT")) == NULL) || r; + r = ((glGetColorTableParameterfvEXT = (PFNGLGETCOLORTABLEPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableParameterfvEXT")) == NULL) || r; + r = ((glGetColorTableParameterivEXT = (PFNGLGETCOLORTABLEPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableParameterivEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_paletted_texture */ + +#ifdef GL_EXT_pixel_buffer_object + +#endif /* GL_EXT_pixel_buffer_object */ + +#ifdef GL_EXT_pixel_transform + +static GLboolean _glewInit_GL_EXT_pixel_transform (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetPixelTransformParameterfvEXT = (PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetPixelTransformParameterfvEXT")) == NULL) || r; + r = ((glGetPixelTransformParameterivEXT = (PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetPixelTransformParameterivEXT")) == NULL) || r; + r = ((glPixelTransformParameterfEXT = (PFNGLPIXELTRANSFORMPARAMETERFEXTPROC)glewGetProcAddress((const GLubyte*)"glPixelTransformParameterfEXT")) == NULL) || r; + r = ((glPixelTransformParameterfvEXT = (PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glPixelTransformParameterfvEXT")) == NULL) || r; + r = ((glPixelTransformParameteriEXT = (PFNGLPIXELTRANSFORMPARAMETERIEXTPROC)glewGetProcAddress((const GLubyte*)"glPixelTransformParameteriEXT")) == NULL) || r; + r = ((glPixelTransformParameterivEXT = (PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC)glewGetProcAddress((const GLubyte*)"glPixelTransformParameterivEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_pixel_transform */ + +#ifdef GL_EXT_pixel_transform_color_table + +#endif /* GL_EXT_pixel_transform_color_table */ + +#ifdef GL_EXT_point_parameters + +static GLboolean _glewInit_GL_EXT_point_parameters (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glPointParameterfEXT = (PFNGLPOINTPARAMETERFEXTPROC)glewGetProcAddress((const GLubyte*)"glPointParameterfEXT")) == NULL) || r; + r = ((glPointParameterfvEXT = (PFNGLPOINTPARAMETERFVEXTPROC)glewGetProcAddress((const GLubyte*)"glPointParameterfvEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_point_parameters */ + +#ifdef GL_EXT_polygon_offset + +static GLboolean _glewInit_GL_EXT_polygon_offset (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glPolygonOffsetEXT = (PFNGLPOLYGONOFFSETEXTPROC)glewGetProcAddress((const GLubyte*)"glPolygonOffsetEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_polygon_offset */ + +#ifdef GL_EXT_rescale_normal + +#endif /* GL_EXT_rescale_normal */ + +#ifdef GL_EXT_scene_marker + +static GLboolean _glewInit_GL_EXT_scene_marker (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginSceneEXT = (PFNGLBEGINSCENEEXTPROC)glewGetProcAddress((const GLubyte*)"glBeginSceneEXT")) == NULL) || r; + r = ((glEndSceneEXT = (PFNGLENDSCENEEXTPROC)glewGetProcAddress((const GLubyte*)"glEndSceneEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_scene_marker */ + +#ifdef GL_EXT_secondary_color + +static GLboolean _glewInit_GL_EXT_secondary_color (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glSecondaryColor3bEXT = (PFNGLSECONDARYCOLOR3BEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3bEXT")) == NULL) || r; + r = ((glSecondaryColor3bvEXT = (PFNGLSECONDARYCOLOR3BVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3bvEXT")) == NULL) || r; + r = ((glSecondaryColor3dEXT = (PFNGLSECONDARYCOLOR3DEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3dEXT")) == NULL) || r; + r = ((glSecondaryColor3dvEXT = (PFNGLSECONDARYCOLOR3DVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3dvEXT")) == NULL) || r; + r = ((glSecondaryColor3fEXT = (PFNGLSECONDARYCOLOR3FEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3fEXT")) == NULL) || r; + r = ((glSecondaryColor3fvEXT = (PFNGLSECONDARYCOLOR3FVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3fvEXT")) == NULL) || r; + r = ((glSecondaryColor3iEXT = (PFNGLSECONDARYCOLOR3IEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3iEXT")) == NULL) || r; + r = ((glSecondaryColor3ivEXT = (PFNGLSECONDARYCOLOR3IVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3ivEXT")) == NULL) || r; + r = ((glSecondaryColor3sEXT = (PFNGLSECONDARYCOLOR3SEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3sEXT")) == NULL) || r; + r = ((glSecondaryColor3svEXT = (PFNGLSECONDARYCOLOR3SVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3svEXT")) == NULL) || r; + r = ((glSecondaryColor3ubEXT = (PFNGLSECONDARYCOLOR3UBEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3ubEXT")) == NULL) || r; + r = ((glSecondaryColor3ubvEXT = (PFNGLSECONDARYCOLOR3UBVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3ubvEXT")) == NULL) || r; + r = ((glSecondaryColor3uiEXT = (PFNGLSECONDARYCOLOR3UIEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3uiEXT")) == NULL) || r; + r = ((glSecondaryColor3uivEXT = (PFNGLSECONDARYCOLOR3UIVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3uivEXT")) == NULL) || r; + r = ((glSecondaryColor3usEXT = (PFNGLSECONDARYCOLOR3USEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3usEXT")) == NULL) || r; + r = ((glSecondaryColor3usvEXT = (PFNGLSECONDARYCOLOR3USVEXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3usvEXT")) == NULL) || r; + r = ((glSecondaryColorPointerEXT = (PFNGLSECONDARYCOLORPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColorPointerEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_secondary_color */ + +#ifdef GL_EXT_separate_specular_color + +#endif /* GL_EXT_separate_specular_color */ + +#ifdef GL_EXT_shadow_funcs + +#endif /* GL_EXT_shadow_funcs */ + +#ifdef GL_EXT_shared_texture_palette + +#endif /* GL_EXT_shared_texture_palette */ + +#ifdef GL_EXT_stencil_clear_tag + +#endif /* GL_EXT_stencil_clear_tag */ + +#ifdef GL_EXT_stencil_two_side + +static GLboolean _glewInit_GL_EXT_stencil_two_side (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glActiveStencilFaceEXT = (PFNGLACTIVESTENCILFACEEXTPROC)glewGetProcAddress((const GLubyte*)"glActiveStencilFaceEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_stencil_two_side */ + +#ifdef GL_EXT_stencil_wrap + +#endif /* GL_EXT_stencil_wrap */ + +#ifdef GL_EXT_subtexture + +static GLboolean _glewInit_GL_EXT_subtexture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTexSubImage1DEXT = (PFNGLTEXSUBIMAGE1DEXTPROC)glewGetProcAddress((const GLubyte*)"glTexSubImage1DEXT")) == NULL) || r; + r = ((glTexSubImage2DEXT = (PFNGLTEXSUBIMAGE2DEXTPROC)glewGetProcAddress((const GLubyte*)"glTexSubImage2DEXT")) == NULL) || r; + r = ((glTexSubImage3DEXT = (PFNGLTEXSUBIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glTexSubImage3DEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_subtexture */ + +#ifdef GL_EXT_texture + +#endif /* GL_EXT_texture */ + +#ifdef GL_EXT_texture3D + +static GLboolean _glewInit_GL_EXT_texture3D (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC)glewGetProcAddress((const GLubyte*)"glTexImage3DEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_texture3D */ + +#ifdef GL_EXT_texture_array + +#endif /* GL_EXT_texture_array */ + +#ifdef GL_EXT_texture_buffer_object + +static GLboolean _glewInit_GL_EXT_texture_buffer_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTexBufferEXT = (PFNGLTEXBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"glTexBufferEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_texture_buffer_object */ + +#ifdef GL_EXT_texture_compression_dxt1 + +#endif /* GL_EXT_texture_compression_dxt1 */ + +#ifdef GL_EXT_texture_compression_latc + +#endif /* GL_EXT_texture_compression_latc */ + +#ifdef GL_EXT_texture_compression_rgtc + +#endif /* GL_EXT_texture_compression_rgtc */ + +#ifdef GL_EXT_texture_compression_s3tc + +#endif /* GL_EXT_texture_compression_s3tc */ + +#ifdef GL_EXT_texture_cube_map + +#endif /* GL_EXT_texture_cube_map */ + +#ifdef GL_EXT_texture_edge_clamp + +#endif /* GL_EXT_texture_edge_clamp */ + +#ifdef GL_EXT_texture_env + +#endif /* GL_EXT_texture_env */ + +#ifdef GL_EXT_texture_env_add + +#endif /* GL_EXT_texture_env_add */ + +#ifdef GL_EXT_texture_env_combine + +#endif /* GL_EXT_texture_env_combine */ + +#ifdef GL_EXT_texture_env_dot3 + +#endif /* GL_EXT_texture_env_dot3 */ + +#ifdef GL_EXT_texture_filter_anisotropic + +#endif /* GL_EXT_texture_filter_anisotropic */ + +#ifdef GL_EXT_texture_integer + +static GLboolean _glewInit_GL_EXT_texture_integer (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glClearColorIiEXT = (PFNGLCLEARCOLORIIEXTPROC)glewGetProcAddress((const GLubyte*)"glClearColorIiEXT")) == NULL) || r; + r = ((glClearColorIuiEXT = (PFNGLCLEARCOLORIUIEXTPROC)glewGetProcAddress((const GLubyte*)"glClearColorIuiEXT")) == NULL) || r; + r = ((glGetTexParameterIivEXT = (PFNGLGETTEXPARAMETERIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTexParameterIivEXT")) == NULL) || r; + r = ((glGetTexParameterIuivEXT = (PFNGLGETTEXPARAMETERIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTexParameterIuivEXT")) == NULL) || r; + r = ((glTexParameterIivEXT = (PFNGLTEXPARAMETERIIVEXTPROC)glewGetProcAddress((const GLubyte*)"glTexParameterIivEXT")) == NULL) || r; + r = ((glTexParameterIuivEXT = (PFNGLTEXPARAMETERIUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glTexParameterIuivEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_texture_integer */ + +#ifdef GL_EXT_texture_lod_bias + +#endif /* GL_EXT_texture_lod_bias */ + +#ifdef GL_EXT_texture_mirror_clamp + +#endif /* GL_EXT_texture_mirror_clamp */ + +#ifdef GL_EXT_texture_object + +static GLboolean _glewInit_GL_EXT_texture_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glAreTexturesResidentEXT = (PFNGLARETEXTURESRESIDENTEXTPROC)glewGetProcAddress((const GLubyte*)"glAreTexturesResidentEXT")) == NULL) || r; + r = ((glBindTextureEXT = (PFNGLBINDTEXTUREEXTPROC)glewGetProcAddress((const GLubyte*)"glBindTextureEXT")) == NULL) || r; + r = ((glDeleteTexturesEXT = (PFNGLDELETETEXTURESEXTPROC)glewGetProcAddress((const GLubyte*)"glDeleteTexturesEXT")) == NULL) || r; + r = ((glGenTexturesEXT = (PFNGLGENTEXTURESEXTPROC)glewGetProcAddress((const GLubyte*)"glGenTexturesEXT")) == NULL) || r; + r = ((glIsTextureEXT = (PFNGLISTEXTUREEXTPROC)glewGetProcAddress((const GLubyte*)"glIsTextureEXT")) == NULL) || r; + r = ((glPrioritizeTexturesEXT = (PFNGLPRIORITIZETEXTURESEXTPROC)glewGetProcAddress((const GLubyte*)"glPrioritizeTexturesEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_texture_object */ + +#ifdef GL_EXT_texture_perturb_normal + +static GLboolean _glewInit_GL_EXT_texture_perturb_normal (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTextureNormalEXT = (PFNGLTEXTURENORMALEXTPROC)glewGetProcAddress((const GLubyte*)"glTextureNormalEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_texture_perturb_normal */ + +#ifdef GL_EXT_texture_rectangle + +#endif /* GL_EXT_texture_rectangle */ + +#ifdef GL_EXT_texture_sRGB + +#endif /* GL_EXT_texture_sRGB */ + +#ifdef GL_EXT_texture_shared_exponent + +#endif /* GL_EXT_texture_shared_exponent */ + +#ifdef GL_EXT_texture_swizzle + +#endif /* GL_EXT_texture_swizzle */ + +#ifdef GL_EXT_timer_query + +static GLboolean _glewInit_GL_EXT_timer_query (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetQueryObjecti64vEXT = (PFNGLGETQUERYOBJECTI64VEXTPROC)glewGetProcAddress((const GLubyte*)"glGetQueryObjecti64vEXT")) == NULL) || r; + r = ((glGetQueryObjectui64vEXT = (PFNGLGETQUERYOBJECTUI64VEXTPROC)glewGetProcAddress((const GLubyte*)"glGetQueryObjectui64vEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_timer_query */ + +#ifdef GL_EXT_transform_feedback + +static GLboolean _glewInit_GL_EXT_transform_feedback (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginTransformFeedbackEXT = (PFNGLBEGINTRANSFORMFEEDBACKEXTPROC)glewGetProcAddress((const GLubyte*)"glBeginTransformFeedbackEXT")) == NULL) || r; + r = ((glBindBufferBaseEXT = (PFNGLBINDBUFFERBASEEXTPROC)glewGetProcAddress((const GLubyte*)"glBindBufferBaseEXT")) == NULL) || r; + r = ((glBindBufferOffsetEXT = (PFNGLBINDBUFFEROFFSETEXTPROC)glewGetProcAddress((const GLubyte*)"glBindBufferOffsetEXT")) == NULL) || r; + r = ((glBindBufferRangeEXT = (PFNGLBINDBUFFERRANGEEXTPROC)glewGetProcAddress((const GLubyte*)"glBindBufferRangeEXT")) == NULL) || r; + r = ((glEndTransformFeedbackEXT = (PFNGLENDTRANSFORMFEEDBACKEXTPROC)glewGetProcAddress((const GLubyte*)"glEndTransformFeedbackEXT")) == NULL) || r; + r = ((glGetTransformFeedbackVaryingEXT = (PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC)glewGetProcAddress((const GLubyte*)"glGetTransformFeedbackVaryingEXT")) == NULL) || r; + r = ((glTransformFeedbackVaryingsEXT = (PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC)glewGetProcAddress((const GLubyte*)"glTransformFeedbackVaryingsEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_transform_feedback */ + +#ifdef GL_EXT_vertex_array + +static GLboolean _glewInit_GL_EXT_vertex_array (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glArrayElementEXT = (PFNGLARRAYELEMENTEXTPROC)glewGetProcAddress((const GLubyte*)"glArrayElementEXT")) == NULL) || r; + r = ((glColorPointerEXT = (PFNGLCOLORPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glColorPointerEXT")) == NULL) || r; + r = ((glDrawArraysEXT = (PFNGLDRAWARRAYSEXTPROC)glewGetProcAddress((const GLubyte*)"glDrawArraysEXT")) == NULL) || r; + r = ((glEdgeFlagPointerEXT = (PFNGLEDGEFLAGPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glEdgeFlagPointerEXT")) == NULL) || r; + r = ((glGetPointervEXT = (PFNGLGETPOINTERVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetPointervEXT")) == NULL) || r; + r = ((glIndexPointerEXT = (PFNGLINDEXPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glIndexPointerEXT")) == NULL) || r; + r = ((glNormalPointerEXT = (PFNGLNORMALPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glNormalPointerEXT")) == NULL) || r; + r = ((glTexCoordPointerEXT = (PFNGLTEXCOORDPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glTexCoordPointerEXT")) == NULL) || r; + r = ((glVertexPointerEXT = (PFNGLVERTEXPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glVertexPointerEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_vertex_array */ + +#ifdef GL_EXT_vertex_array_bgra + +#endif /* GL_EXT_vertex_array_bgra */ + +#ifdef GL_EXT_vertex_shader + +static GLboolean _glewInit_GL_EXT_vertex_shader (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginVertexShaderEXT = (PFNGLBEGINVERTEXSHADEREXTPROC)glewGetProcAddress((const GLubyte*)"glBeginVertexShaderEXT")) == NULL) || r; + r = ((glBindLightParameterEXT = (PFNGLBINDLIGHTPARAMETEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindLightParameterEXT")) == NULL) || r; + r = ((glBindMaterialParameterEXT = (PFNGLBINDMATERIALPARAMETEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindMaterialParameterEXT")) == NULL) || r; + r = ((glBindParameterEXT = (PFNGLBINDPARAMETEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindParameterEXT")) == NULL) || r; + r = ((glBindTexGenParameterEXT = (PFNGLBINDTEXGENPARAMETEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindTexGenParameterEXT")) == NULL) || r; + r = ((glBindTextureUnitParameterEXT = (PFNGLBINDTEXTUREUNITPARAMETEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindTextureUnitParameterEXT")) == NULL) || r; + r = ((glBindVertexShaderEXT = (PFNGLBINDVERTEXSHADEREXTPROC)glewGetProcAddress((const GLubyte*)"glBindVertexShaderEXT")) == NULL) || r; + r = ((glDeleteVertexShaderEXT = (PFNGLDELETEVERTEXSHADEREXTPROC)glewGetProcAddress((const GLubyte*)"glDeleteVertexShaderEXT")) == NULL) || r; + r = ((glDisableVariantClientStateEXT = (PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC)glewGetProcAddress((const GLubyte*)"glDisableVariantClientStateEXT")) == NULL) || r; + r = ((glEnableVariantClientStateEXT = (PFNGLENABLEVARIANTCLIENTSTATEEXTPROC)glewGetProcAddress((const GLubyte*)"glEnableVariantClientStateEXT")) == NULL) || r; + r = ((glEndVertexShaderEXT = (PFNGLENDVERTEXSHADEREXTPROC)glewGetProcAddress((const GLubyte*)"glEndVertexShaderEXT")) == NULL) || r; + r = ((glExtractComponentEXT = (PFNGLEXTRACTCOMPONENTEXTPROC)glewGetProcAddress((const GLubyte*)"glExtractComponentEXT")) == NULL) || r; + r = ((glGenSymbolsEXT = (PFNGLGENSYMBOLSEXTPROC)glewGetProcAddress((const GLubyte*)"glGenSymbolsEXT")) == NULL) || r; + r = ((glGenVertexShadersEXT = (PFNGLGENVERTEXSHADERSEXTPROC)glewGetProcAddress((const GLubyte*)"glGenVertexShadersEXT")) == NULL) || r; + r = ((glGetInvariantBooleanvEXT = (PFNGLGETINVARIANTBOOLEANVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetInvariantBooleanvEXT")) == NULL) || r; + r = ((glGetInvariantFloatvEXT = (PFNGLGETINVARIANTFLOATVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetInvariantFloatvEXT")) == NULL) || r; + r = ((glGetInvariantIntegervEXT = (PFNGLGETINVARIANTINTEGERVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetInvariantIntegervEXT")) == NULL) || r; + r = ((glGetLocalConstantBooleanvEXT = (PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetLocalConstantBooleanvEXT")) == NULL) || r; + r = ((glGetLocalConstantFloatvEXT = (PFNGLGETLOCALCONSTANTFLOATVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetLocalConstantFloatvEXT")) == NULL) || r; + r = ((glGetLocalConstantIntegervEXT = (PFNGLGETLOCALCONSTANTINTEGERVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetLocalConstantIntegervEXT")) == NULL) || r; + r = ((glGetVariantBooleanvEXT = (PFNGLGETVARIANTBOOLEANVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetVariantBooleanvEXT")) == NULL) || r; + r = ((glGetVariantFloatvEXT = (PFNGLGETVARIANTFLOATVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetVariantFloatvEXT")) == NULL) || r; + r = ((glGetVariantIntegervEXT = (PFNGLGETVARIANTINTEGERVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetVariantIntegervEXT")) == NULL) || r; + r = ((glGetVariantPointervEXT = (PFNGLGETVARIANTPOINTERVEXTPROC)glewGetProcAddress((const GLubyte*)"glGetVariantPointervEXT")) == NULL) || r; + r = ((glInsertComponentEXT = (PFNGLINSERTCOMPONENTEXTPROC)glewGetProcAddress((const GLubyte*)"glInsertComponentEXT")) == NULL) || r; + r = ((glIsVariantEnabledEXT = (PFNGLISVARIANTENABLEDEXTPROC)glewGetProcAddress((const GLubyte*)"glIsVariantEnabledEXT")) == NULL) || r; + r = ((glSetInvariantEXT = (PFNGLSETINVARIANTEXTPROC)glewGetProcAddress((const GLubyte*)"glSetInvariantEXT")) == NULL) || r; + r = ((glSetLocalConstantEXT = (PFNGLSETLOCALCONSTANTEXTPROC)glewGetProcAddress((const GLubyte*)"glSetLocalConstantEXT")) == NULL) || r; + r = ((glShaderOp1EXT = (PFNGLSHADEROP1EXTPROC)glewGetProcAddress((const GLubyte*)"glShaderOp1EXT")) == NULL) || r; + r = ((glShaderOp2EXT = (PFNGLSHADEROP2EXTPROC)glewGetProcAddress((const GLubyte*)"glShaderOp2EXT")) == NULL) || r; + r = ((glShaderOp3EXT = (PFNGLSHADEROP3EXTPROC)glewGetProcAddress((const GLubyte*)"glShaderOp3EXT")) == NULL) || r; + r = ((glSwizzleEXT = (PFNGLSWIZZLEEXTPROC)glewGetProcAddress((const GLubyte*)"glSwizzleEXT")) == NULL) || r; + r = ((glVariantPointerEXT = (PFNGLVARIANTPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glVariantPointerEXT")) == NULL) || r; + r = ((glVariantbvEXT = (PFNGLVARIANTBVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantbvEXT")) == NULL) || r; + r = ((glVariantdvEXT = (PFNGLVARIANTDVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantdvEXT")) == NULL) || r; + r = ((glVariantfvEXT = (PFNGLVARIANTFVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantfvEXT")) == NULL) || r; + r = ((glVariantivEXT = (PFNGLVARIANTIVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantivEXT")) == NULL) || r; + r = ((glVariantsvEXT = (PFNGLVARIANTSVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantsvEXT")) == NULL) || r; + r = ((glVariantubvEXT = (PFNGLVARIANTUBVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantubvEXT")) == NULL) || r; + r = ((glVariantuivEXT = (PFNGLVARIANTUIVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantuivEXT")) == NULL) || r; + r = ((glVariantusvEXT = (PFNGLVARIANTUSVEXTPROC)glewGetProcAddress((const GLubyte*)"glVariantusvEXT")) == NULL) || r; + r = ((glWriteMaskEXT = (PFNGLWRITEMASKEXTPROC)glewGetProcAddress((const GLubyte*)"glWriteMaskEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_vertex_shader */ + +#ifdef GL_EXT_vertex_weighting + +static GLboolean _glewInit_GL_EXT_vertex_weighting (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glVertexWeightPointerEXT = (PFNGLVERTEXWEIGHTPOINTEREXTPROC)glewGetProcAddress((const GLubyte*)"glVertexWeightPointerEXT")) == NULL) || r; + r = ((glVertexWeightfEXT = (PFNGLVERTEXWEIGHTFEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexWeightfEXT")) == NULL) || r; + r = ((glVertexWeightfvEXT = (PFNGLVERTEXWEIGHTFVEXTPROC)glewGetProcAddress((const GLubyte*)"glVertexWeightfvEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_EXT_vertex_weighting */ + +#ifdef GL_GREMEDY_frame_terminator + +static GLboolean _glewInit_GL_GREMEDY_frame_terminator (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFrameTerminatorGREMEDY = (PFNGLFRAMETERMINATORGREMEDYPROC)glewGetProcAddress((const GLubyte*)"glFrameTerminatorGREMEDY")) == NULL) || r; + + return r; +} + +#endif /* GL_GREMEDY_frame_terminator */ + +#ifdef GL_GREMEDY_string_marker + +static GLboolean _glewInit_GL_GREMEDY_string_marker (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glStringMarkerGREMEDY = (PFNGLSTRINGMARKERGREMEDYPROC)glewGetProcAddress((const GLubyte*)"glStringMarkerGREMEDY")) == NULL) || r; + + return r; +} + +#endif /* GL_GREMEDY_string_marker */ + +#ifdef GL_HP_convolution_border_modes + +#endif /* GL_HP_convolution_border_modes */ + +#ifdef GL_HP_image_transform + +static GLboolean _glewInit_GL_HP_image_transform (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetImageTransformParameterfvHP = (PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC)glewGetProcAddress((const GLubyte*)"glGetImageTransformParameterfvHP")) == NULL) || r; + r = ((glGetImageTransformParameterivHP = (PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC)glewGetProcAddress((const GLubyte*)"glGetImageTransformParameterivHP")) == NULL) || r; + r = ((glImageTransformParameterfHP = (PFNGLIMAGETRANSFORMPARAMETERFHPPROC)glewGetProcAddress((const GLubyte*)"glImageTransformParameterfHP")) == NULL) || r; + r = ((glImageTransformParameterfvHP = (PFNGLIMAGETRANSFORMPARAMETERFVHPPROC)glewGetProcAddress((const GLubyte*)"glImageTransformParameterfvHP")) == NULL) || r; + r = ((glImageTransformParameteriHP = (PFNGLIMAGETRANSFORMPARAMETERIHPPROC)glewGetProcAddress((const GLubyte*)"glImageTransformParameteriHP")) == NULL) || r; + r = ((glImageTransformParameterivHP = (PFNGLIMAGETRANSFORMPARAMETERIVHPPROC)glewGetProcAddress((const GLubyte*)"glImageTransformParameterivHP")) == NULL) || r; + + return r; +} + +#endif /* GL_HP_image_transform */ + +#ifdef GL_HP_occlusion_test + +#endif /* GL_HP_occlusion_test */ + +#ifdef GL_HP_texture_lighting + +#endif /* GL_HP_texture_lighting */ + +#ifdef GL_IBM_cull_vertex + +#endif /* GL_IBM_cull_vertex */ + +#ifdef GL_IBM_multimode_draw_arrays + +static GLboolean _glewInit_GL_IBM_multimode_draw_arrays (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glMultiModeDrawArraysIBM = (PFNGLMULTIMODEDRAWARRAYSIBMPROC)glewGetProcAddress((const GLubyte*)"glMultiModeDrawArraysIBM")) == NULL) || r; + r = ((glMultiModeDrawElementsIBM = (PFNGLMULTIMODEDRAWELEMENTSIBMPROC)glewGetProcAddress((const GLubyte*)"glMultiModeDrawElementsIBM")) == NULL) || r; + + return r; +} + +#endif /* GL_IBM_multimode_draw_arrays */ + +#ifdef GL_IBM_rasterpos_clip + +#endif /* GL_IBM_rasterpos_clip */ + +#ifdef GL_IBM_static_data + +#endif /* GL_IBM_static_data */ + +#ifdef GL_IBM_texture_mirrored_repeat + +#endif /* GL_IBM_texture_mirrored_repeat */ + +#ifdef GL_IBM_vertex_array_lists + +static GLboolean _glewInit_GL_IBM_vertex_array_lists (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColorPointerListIBM = (PFNGLCOLORPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glColorPointerListIBM")) == NULL) || r; + r = ((glEdgeFlagPointerListIBM = (PFNGLEDGEFLAGPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glEdgeFlagPointerListIBM")) == NULL) || r; + r = ((glFogCoordPointerListIBM = (PFNGLFOGCOORDPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glFogCoordPointerListIBM")) == NULL) || r; + r = ((glIndexPointerListIBM = (PFNGLINDEXPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glIndexPointerListIBM")) == NULL) || r; + r = ((glNormalPointerListIBM = (PFNGLNORMALPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glNormalPointerListIBM")) == NULL) || r; + r = ((glSecondaryColorPointerListIBM = (PFNGLSECONDARYCOLORPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColorPointerListIBM")) == NULL) || r; + r = ((glTexCoordPointerListIBM = (PFNGLTEXCOORDPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glTexCoordPointerListIBM")) == NULL) || r; + r = ((glVertexPointerListIBM = (PFNGLVERTEXPOINTERLISTIBMPROC)glewGetProcAddress((const GLubyte*)"glVertexPointerListIBM")) == NULL) || r; + + return r; +} + +#endif /* GL_IBM_vertex_array_lists */ + +#ifdef GL_INGR_color_clamp + +#endif /* GL_INGR_color_clamp */ + +#ifdef GL_INGR_interlace_read + +#endif /* GL_INGR_interlace_read */ + +#ifdef GL_INTEL_parallel_arrays + +static GLboolean _glewInit_GL_INTEL_parallel_arrays (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColorPointervINTEL = (PFNGLCOLORPOINTERVINTELPROC)glewGetProcAddress((const GLubyte*)"glColorPointervINTEL")) == NULL) || r; + r = ((glNormalPointervINTEL = (PFNGLNORMALPOINTERVINTELPROC)glewGetProcAddress((const GLubyte*)"glNormalPointervINTEL")) == NULL) || r; + r = ((glTexCoordPointervINTEL = (PFNGLTEXCOORDPOINTERVINTELPROC)glewGetProcAddress((const GLubyte*)"glTexCoordPointervINTEL")) == NULL) || r; + r = ((glVertexPointervINTEL = (PFNGLVERTEXPOINTERVINTELPROC)glewGetProcAddress((const GLubyte*)"glVertexPointervINTEL")) == NULL) || r; + + return r; +} + +#endif /* GL_INTEL_parallel_arrays */ + +#ifdef GL_INTEL_texture_scissor + +static GLboolean _glewInit_GL_INTEL_texture_scissor (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTexScissorFuncINTEL = (PFNGLTEXSCISSORFUNCINTELPROC)glewGetProcAddress((const GLubyte*)"glTexScissorFuncINTEL")) == NULL) || r; + r = ((glTexScissorINTEL = (PFNGLTEXSCISSORINTELPROC)glewGetProcAddress((const GLubyte*)"glTexScissorINTEL")) == NULL) || r; + + return r; +} + +#endif /* GL_INTEL_texture_scissor */ + +#ifdef GL_KTX_buffer_region + +static GLboolean _glewInit_GL_KTX_buffer_region (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBufferRegionEnabledEXT = (PFNGLBUFFERREGIONENABLEDEXTPROC)glewGetProcAddress((const GLubyte*)"glBufferRegionEnabledEXT")) == NULL) || r; + r = ((glDeleteBufferRegionEXT = (PFNGLDELETEBUFFERREGIONEXTPROC)glewGetProcAddress((const GLubyte*)"glDeleteBufferRegionEXT")) == NULL) || r; + r = ((glDrawBufferRegionEXT = (PFNGLDRAWBUFFERREGIONEXTPROC)glewGetProcAddress((const GLubyte*)"glDrawBufferRegionEXT")) == NULL) || r; + r = ((glNewBufferRegionEXT = (PFNGLNEWBUFFERREGIONEXTPROC)glewGetProcAddress((const GLubyte*)"glNewBufferRegionEXT")) == NULL) || r; + r = ((glReadBufferRegionEXT = (PFNGLREADBUFFERREGIONEXTPROC)glewGetProcAddress((const GLubyte*)"glReadBufferRegionEXT")) == NULL) || r; + + return r; +} + +#endif /* GL_KTX_buffer_region */ + +#ifdef GL_MESAX_texture_stack + +#endif /* GL_MESAX_texture_stack */ + +#ifdef GL_MESA_pack_invert + +#endif /* GL_MESA_pack_invert */ + +#ifdef GL_MESA_resize_buffers + +static GLboolean _glewInit_GL_MESA_resize_buffers (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glResizeBuffersMESA = (PFNGLRESIZEBUFFERSMESAPROC)glewGetProcAddress((const GLubyte*)"glResizeBuffersMESA")) == NULL) || r; + + return r; +} + +#endif /* GL_MESA_resize_buffers */ + +#ifdef GL_MESA_window_pos + +static GLboolean _glewInit_GL_MESA_window_pos (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glWindowPos2dMESA = (PFNGLWINDOWPOS2DMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2dMESA")) == NULL) || r; + r = ((glWindowPos2dvMESA = (PFNGLWINDOWPOS2DVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2dvMESA")) == NULL) || r; + r = ((glWindowPos2fMESA = (PFNGLWINDOWPOS2FMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2fMESA")) == NULL) || r; + r = ((glWindowPos2fvMESA = (PFNGLWINDOWPOS2FVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2fvMESA")) == NULL) || r; + r = ((glWindowPos2iMESA = (PFNGLWINDOWPOS2IMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2iMESA")) == NULL) || r; + r = ((glWindowPos2ivMESA = (PFNGLWINDOWPOS2IVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2ivMESA")) == NULL) || r; + r = ((glWindowPos2sMESA = (PFNGLWINDOWPOS2SMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2sMESA")) == NULL) || r; + r = ((glWindowPos2svMESA = (PFNGLWINDOWPOS2SVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos2svMESA")) == NULL) || r; + r = ((glWindowPos3dMESA = (PFNGLWINDOWPOS3DMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3dMESA")) == NULL) || r; + r = ((glWindowPos3dvMESA = (PFNGLWINDOWPOS3DVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3dvMESA")) == NULL) || r; + r = ((glWindowPos3fMESA = (PFNGLWINDOWPOS3FMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3fMESA")) == NULL) || r; + r = ((glWindowPos3fvMESA = (PFNGLWINDOWPOS3FVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3fvMESA")) == NULL) || r; + r = ((glWindowPos3iMESA = (PFNGLWINDOWPOS3IMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3iMESA")) == NULL) || r; + r = ((glWindowPos3ivMESA = (PFNGLWINDOWPOS3IVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3ivMESA")) == NULL) || r; + r = ((glWindowPos3sMESA = (PFNGLWINDOWPOS3SMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3sMESA")) == NULL) || r; + r = ((glWindowPos3svMESA = (PFNGLWINDOWPOS3SVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos3svMESA")) == NULL) || r; + r = ((glWindowPos4dMESA = (PFNGLWINDOWPOS4DMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4dMESA")) == NULL) || r; + r = ((glWindowPos4dvMESA = (PFNGLWINDOWPOS4DVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4dvMESA")) == NULL) || r; + r = ((glWindowPos4fMESA = (PFNGLWINDOWPOS4FMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4fMESA")) == NULL) || r; + r = ((glWindowPos4fvMESA = (PFNGLWINDOWPOS4FVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4fvMESA")) == NULL) || r; + r = ((glWindowPos4iMESA = (PFNGLWINDOWPOS4IMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4iMESA")) == NULL) || r; + r = ((glWindowPos4ivMESA = (PFNGLWINDOWPOS4IVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4ivMESA")) == NULL) || r; + r = ((glWindowPos4sMESA = (PFNGLWINDOWPOS4SMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4sMESA")) == NULL) || r; + r = ((glWindowPos4svMESA = (PFNGLWINDOWPOS4SVMESAPROC)glewGetProcAddress((const GLubyte*)"glWindowPos4svMESA")) == NULL) || r; + + return r; +} + +#endif /* GL_MESA_window_pos */ + +#ifdef GL_MESA_ycbcr_texture + +#endif /* GL_MESA_ycbcr_texture */ + +#ifdef GL_NV_blend_square + +#endif /* GL_NV_blend_square */ + +#ifdef GL_NV_conditional_render + +static GLboolean _glewInit_GL_NV_conditional_render (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginConditionalRenderNV = (PFNGLBEGINCONDITIONALRENDERNVPROC)glewGetProcAddress((const GLubyte*)"glBeginConditionalRenderNV")) == NULL) || r; + r = ((glEndConditionalRenderNV = (PFNGLENDCONDITIONALRENDERNVPROC)glewGetProcAddress((const GLubyte*)"glEndConditionalRenderNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_conditional_render */ + +#ifdef GL_NV_copy_depth_to_color + +#endif /* GL_NV_copy_depth_to_color */ + +#ifdef GL_NV_depth_buffer_float + +static GLboolean _glewInit_GL_NV_depth_buffer_float (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glClearDepthdNV = (PFNGLCLEARDEPTHDNVPROC)glewGetProcAddress((const GLubyte*)"glClearDepthdNV")) == NULL) || r; + r = ((glDepthBoundsdNV = (PFNGLDEPTHBOUNDSDNVPROC)glewGetProcAddress((const GLubyte*)"glDepthBoundsdNV")) == NULL) || r; + r = ((glDepthRangedNV = (PFNGLDEPTHRANGEDNVPROC)glewGetProcAddress((const GLubyte*)"glDepthRangedNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_depth_buffer_float */ + +#ifdef GL_NV_depth_clamp + +#endif /* GL_NV_depth_clamp */ + +#ifdef GL_NV_depth_range_unclamped + +#endif /* GL_NV_depth_range_unclamped */ + +#ifdef GL_NV_evaluators + +static GLboolean _glewInit_GL_NV_evaluators (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glEvalMapsNV = (PFNGLEVALMAPSNVPROC)glewGetProcAddress((const GLubyte*)"glEvalMapsNV")) == NULL) || r; + r = ((glGetMapAttribParameterfvNV = (PFNGLGETMAPATTRIBPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetMapAttribParameterfvNV")) == NULL) || r; + r = ((glGetMapAttribParameterivNV = (PFNGLGETMAPATTRIBPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetMapAttribParameterivNV")) == NULL) || r; + r = ((glGetMapControlPointsNV = (PFNGLGETMAPCONTROLPOINTSNVPROC)glewGetProcAddress((const GLubyte*)"glGetMapControlPointsNV")) == NULL) || r; + r = ((glGetMapParameterfvNV = (PFNGLGETMAPPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetMapParameterfvNV")) == NULL) || r; + r = ((glGetMapParameterivNV = (PFNGLGETMAPPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetMapParameterivNV")) == NULL) || r; + r = ((glMapControlPointsNV = (PFNGLMAPCONTROLPOINTSNVPROC)glewGetProcAddress((const GLubyte*)"glMapControlPointsNV")) == NULL) || r; + r = ((glMapParameterfvNV = (PFNGLMAPPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glMapParameterfvNV")) == NULL) || r; + r = ((glMapParameterivNV = (PFNGLMAPPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glMapParameterivNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_evaluators */ + +#ifdef GL_NV_explicit_multisample + +static GLboolean _glewInit_GL_NV_explicit_multisample (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetMultisamplefvNV = (PFNGLGETMULTISAMPLEFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetMultisamplefvNV")) == NULL) || r; + r = ((glSampleMaskIndexedNV = (PFNGLSAMPLEMASKINDEXEDNVPROC)glewGetProcAddress((const GLubyte*)"glSampleMaskIndexedNV")) == NULL) || r; + r = ((glTexRenderbufferNV = (PFNGLTEXRENDERBUFFERNVPROC)glewGetProcAddress((const GLubyte*)"glTexRenderbufferNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_explicit_multisample */ + +#ifdef GL_NV_fence + +static GLboolean _glewInit_GL_NV_fence (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDeleteFencesNV = (PFNGLDELETEFENCESNVPROC)glewGetProcAddress((const GLubyte*)"glDeleteFencesNV")) == NULL) || r; + r = ((glFinishFenceNV = (PFNGLFINISHFENCENVPROC)glewGetProcAddress((const GLubyte*)"glFinishFenceNV")) == NULL) || r; + r = ((glGenFencesNV = (PFNGLGENFENCESNVPROC)glewGetProcAddress((const GLubyte*)"glGenFencesNV")) == NULL) || r; + r = ((glGetFenceivNV = (PFNGLGETFENCEIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetFenceivNV")) == NULL) || r; + r = ((glIsFenceNV = (PFNGLISFENCENVPROC)glewGetProcAddress((const GLubyte*)"glIsFenceNV")) == NULL) || r; + r = ((glSetFenceNV = (PFNGLSETFENCENVPROC)glewGetProcAddress((const GLubyte*)"glSetFenceNV")) == NULL) || r; + r = ((glTestFenceNV = (PFNGLTESTFENCENVPROC)glewGetProcAddress((const GLubyte*)"glTestFenceNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_fence */ + +#ifdef GL_NV_float_buffer + +#endif /* GL_NV_float_buffer */ + +#ifdef GL_NV_fog_distance + +#endif /* GL_NV_fog_distance */ + +#ifdef GL_NV_fragment_program + +static GLboolean _glewInit_GL_NV_fragment_program (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetProgramNamedParameterdvNV = (PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC)glewGetProcAddress((const GLubyte*)"glGetProgramNamedParameterdvNV")) == NULL) || r; + r = ((glGetProgramNamedParameterfvNV = (PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetProgramNamedParameterfvNV")) == NULL) || r; + r = ((glProgramNamedParameter4dNV = (PFNGLPROGRAMNAMEDPARAMETER4DNVPROC)glewGetProcAddress((const GLubyte*)"glProgramNamedParameter4dNV")) == NULL) || r; + r = ((glProgramNamedParameter4dvNV = (PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramNamedParameter4dvNV")) == NULL) || r; + r = ((glProgramNamedParameter4fNV = (PFNGLPROGRAMNAMEDPARAMETER4FNVPROC)glewGetProcAddress((const GLubyte*)"glProgramNamedParameter4fNV")) == NULL) || r; + r = ((glProgramNamedParameter4fvNV = (PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramNamedParameter4fvNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_fragment_program */ + +#ifdef GL_NV_fragment_program2 + +#endif /* GL_NV_fragment_program2 */ + +#ifdef GL_NV_fragment_program4 + +#endif /* GL_NV_fragment_program4 */ + +#ifdef GL_NV_fragment_program_option + +#endif /* GL_NV_fragment_program_option */ + +#ifdef GL_NV_framebuffer_multisample_coverage + +static GLboolean _glewInit_GL_NV_framebuffer_multisample_coverage (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glRenderbufferStorageMultisampleCoverageNV = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC)glewGetProcAddress((const GLubyte*)"glRenderbufferStorageMultisampleCoverageNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_framebuffer_multisample_coverage */ + +#ifdef GL_NV_geometry_program4 + +static GLboolean _glewInit_GL_NV_geometry_program4 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glProgramVertexLimitNV = (PFNGLPROGRAMVERTEXLIMITNVPROC)glewGetProcAddress((const GLubyte*)"glProgramVertexLimitNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_geometry_program4 */ + +#ifdef GL_NV_geometry_shader4 + +#endif /* GL_NV_geometry_shader4 */ + +#ifdef GL_NV_gpu_program4 + +static GLboolean _glewInit_GL_NV_gpu_program4 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glProgramEnvParameterI4iNV = (PFNGLPROGRAMENVPARAMETERI4INVPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameterI4iNV")) == NULL) || r; + r = ((glProgramEnvParameterI4ivNV = (PFNGLPROGRAMENVPARAMETERI4IVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameterI4ivNV")) == NULL) || r; + r = ((glProgramEnvParameterI4uiNV = (PFNGLPROGRAMENVPARAMETERI4UINVPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameterI4uiNV")) == NULL) || r; + r = ((glProgramEnvParameterI4uivNV = (PFNGLPROGRAMENVPARAMETERI4UIVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParameterI4uivNV")) == NULL) || r; + r = ((glProgramEnvParametersI4ivNV = (PFNGLPROGRAMENVPARAMETERSI4IVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParametersI4ivNV")) == NULL) || r; + r = ((glProgramEnvParametersI4uivNV = (PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramEnvParametersI4uivNV")) == NULL) || r; + r = ((glProgramLocalParameterI4iNV = (PFNGLPROGRAMLOCALPARAMETERI4INVPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameterI4iNV")) == NULL) || r; + r = ((glProgramLocalParameterI4ivNV = (PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameterI4ivNV")) == NULL) || r; + r = ((glProgramLocalParameterI4uiNV = (PFNGLPROGRAMLOCALPARAMETERI4UINVPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameterI4uiNV")) == NULL) || r; + r = ((glProgramLocalParameterI4uivNV = (PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParameterI4uivNV")) == NULL) || r; + r = ((glProgramLocalParametersI4ivNV = (PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParametersI4ivNV")) == NULL) || r; + r = ((glProgramLocalParametersI4uivNV = (PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramLocalParametersI4uivNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_gpu_program4 */ + +#ifdef GL_NV_half_float + +static GLboolean _glewInit_GL_NV_half_float (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColor3hNV = (PFNGLCOLOR3HNVPROC)glewGetProcAddress((const GLubyte*)"glColor3hNV")) == NULL) || r; + r = ((glColor3hvNV = (PFNGLCOLOR3HVNVPROC)glewGetProcAddress((const GLubyte*)"glColor3hvNV")) == NULL) || r; + r = ((glColor4hNV = (PFNGLCOLOR4HNVPROC)glewGetProcAddress((const GLubyte*)"glColor4hNV")) == NULL) || r; + r = ((glColor4hvNV = (PFNGLCOLOR4HVNVPROC)glewGetProcAddress((const GLubyte*)"glColor4hvNV")) == NULL) || r; + r = ((glFogCoordhNV = (PFNGLFOGCOORDHNVPROC)glewGetProcAddress((const GLubyte*)"glFogCoordhNV")) == NULL) || r; + r = ((glFogCoordhvNV = (PFNGLFOGCOORDHVNVPROC)glewGetProcAddress((const GLubyte*)"glFogCoordhvNV")) == NULL) || r; + r = ((glMultiTexCoord1hNV = (PFNGLMULTITEXCOORD1HNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1hNV")) == NULL) || r; + r = ((glMultiTexCoord1hvNV = (PFNGLMULTITEXCOORD1HVNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord1hvNV")) == NULL) || r; + r = ((glMultiTexCoord2hNV = (PFNGLMULTITEXCOORD2HNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2hNV")) == NULL) || r; + r = ((glMultiTexCoord2hvNV = (PFNGLMULTITEXCOORD2HVNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord2hvNV")) == NULL) || r; + r = ((glMultiTexCoord3hNV = (PFNGLMULTITEXCOORD3HNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3hNV")) == NULL) || r; + r = ((glMultiTexCoord3hvNV = (PFNGLMULTITEXCOORD3HVNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord3hvNV")) == NULL) || r; + r = ((glMultiTexCoord4hNV = (PFNGLMULTITEXCOORD4HNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4hNV")) == NULL) || r; + r = ((glMultiTexCoord4hvNV = (PFNGLMULTITEXCOORD4HVNVPROC)glewGetProcAddress((const GLubyte*)"glMultiTexCoord4hvNV")) == NULL) || r; + r = ((glNormal3hNV = (PFNGLNORMAL3HNVPROC)glewGetProcAddress((const GLubyte*)"glNormal3hNV")) == NULL) || r; + r = ((glNormal3hvNV = (PFNGLNORMAL3HVNVPROC)glewGetProcAddress((const GLubyte*)"glNormal3hvNV")) == NULL) || r; + r = ((glSecondaryColor3hNV = (PFNGLSECONDARYCOLOR3HNVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3hNV")) == NULL) || r; + r = ((glSecondaryColor3hvNV = (PFNGLSECONDARYCOLOR3HVNVPROC)glewGetProcAddress((const GLubyte*)"glSecondaryColor3hvNV")) == NULL) || r; + r = ((glTexCoord1hNV = (PFNGLTEXCOORD1HNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord1hNV")) == NULL) || r; + r = ((glTexCoord1hvNV = (PFNGLTEXCOORD1HVNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord1hvNV")) == NULL) || r; + r = ((glTexCoord2hNV = (PFNGLTEXCOORD2HNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2hNV")) == NULL) || r; + r = ((glTexCoord2hvNV = (PFNGLTEXCOORD2HVNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2hvNV")) == NULL) || r; + r = ((glTexCoord3hNV = (PFNGLTEXCOORD3HNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord3hNV")) == NULL) || r; + r = ((glTexCoord3hvNV = (PFNGLTEXCOORD3HVNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord3hvNV")) == NULL) || r; + r = ((glTexCoord4hNV = (PFNGLTEXCOORD4HNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord4hNV")) == NULL) || r; + r = ((glTexCoord4hvNV = (PFNGLTEXCOORD4HVNVPROC)glewGetProcAddress((const GLubyte*)"glTexCoord4hvNV")) == NULL) || r; + r = ((glVertex2hNV = (PFNGLVERTEX2HNVPROC)glewGetProcAddress((const GLubyte*)"glVertex2hNV")) == NULL) || r; + r = ((glVertex2hvNV = (PFNGLVERTEX2HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertex2hvNV")) == NULL) || r; + r = ((glVertex3hNV = (PFNGLVERTEX3HNVPROC)glewGetProcAddress((const GLubyte*)"glVertex3hNV")) == NULL) || r; + r = ((glVertex3hvNV = (PFNGLVERTEX3HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertex3hvNV")) == NULL) || r; + r = ((glVertex4hNV = (PFNGLVERTEX4HNVPROC)glewGetProcAddress((const GLubyte*)"glVertex4hNV")) == NULL) || r; + r = ((glVertex4hvNV = (PFNGLVERTEX4HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertex4hvNV")) == NULL) || r; + r = ((glVertexAttrib1hNV = (PFNGLVERTEXATTRIB1HNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1hNV")) == NULL) || r; + r = ((glVertexAttrib1hvNV = (PFNGLVERTEXATTRIB1HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1hvNV")) == NULL) || r; + r = ((glVertexAttrib2hNV = (PFNGLVERTEXATTRIB2HNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2hNV")) == NULL) || r; + r = ((glVertexAttrib2hvNV = (PFNGLVERTEXATTRIB2HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2hvNV")) == NULL) || r; + r = ((glVertexAttrib3hNV = (PFNGLVERTEXATTRIB3HNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3hNV")) == NULL) || r; + r = ((glVertexAttrib3hvNV = (PFNGLVERTEXATTRIB3HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3hvNV")) == NULL) || r; + r = ((glVertexAttrib4hNV = (PFNGLVERTEXATTRIB4HNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4hNV")) == NULL) || r; + r = ((glVertexAttrib4hvNV = (PFNGLVERTEXATTRIB4HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4hvNV")) == NULL) || r; + r = ((glVertexAttribs1hvNV = (PFNGLVERTEXATTRIBS1HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs1hvNV")) == NULL) || r; + r = ((glVertexAttribs2hvNV = (PFNGLVERTEXATTRIBS2HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs2hvNV")) == NULL) || r; + r = ((glVertexAttribs3hvNV = (PFNGLVERTEXATTRIBS3HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs3hvNV")) == NULL) || r; + r = ((glVertexAttribs4hvNV = (PFNGLVERTEXATTRIBS4HVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs4hvNV")) == NULL) || r; + r = ((glVertexWeighthNV = (PFNGLVERTEXWEIGHTHNVPROC)glewGetProcAddress((const GLubyte*)"glVertexWeighthNV")) == NULL) || r; + r = ((glVertexWeighthvNV = (PFNGLVERTEXWEIGHTHVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexWeighthvNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_half_float */ + +#ifdef GL_NV_light_max_exponent + +#endif /* GL_NV_light_max_exponent */ + +#ifdef GL_NV_multisample_filter_hint + +#endif /* GL_NV_multisample_filter_hint */ + +#ifdef GL_NV_occlusion_query + +static GLboolean _glewInit_GL_NV_occlusion_query (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glBeginOcclusionQueryNV = (PFNGLBEGINOCCLUSIONQUERYNVPROC)glewGetProcAddress((const GLubyte*)"glBeginOcclusionQueryNV")) == NULL) || r; + r = ((glDeleteOcclusionQueriesNV = (PFNGLDELETEOCCLUSIONQUERIESNVPROC)glewGetProcAddress((const GLubyte*)"glDeleteOcclusionQueriesNV")) == NULL) || r; + r = ((glEndOcclusionQueryNV = (PFNGLENDOCCLUSIONQUERYNVPROC)glewGetProcAddress((const GLubyte*)"glEndOcclusionQueryNV")) == NULL) || r; + r = ((glGenOcclusionQueriesNV = (PFNGLGENOCCLUSIONQUERIESNVPROC)glewGetProcAddress((const GLubyte*)"glGenOcclusionQueriesNV")) == NULL) || r; + r = ((glGetOcclusionQueryivNV = (PFNGLGETOCCLUSIONQUERYIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetOcclusionQueryivNV")) == NULL) || r; + r = ((glGetOcclusionQueryuivNV = (PFNGLGETOCCLUSIONQUERYUIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetOcclusionQueryuivNV")) == NULL) || r; + r = ((glIsOcclusionQueryNV = (PFNGLISOCCLUSIONQUERYNVPROC)glewGetProcAddress((const GLubyte*)"glIsOcclusionQueryNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_occlusion_query */ + +#ifdef GL_NV_packed_depth_stencil + +#endif /* GL_NV_packed_depth_stencil */ + +#ifdef GL_NV_parameter_buffer_object + +static GLboolean _glewInit_GL_NV_parameter_buffer_object (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glProgramBufferParametersIivNV = (PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramBufferParametersIivNV")) == NULL) || r; + r = ((glProgramBufferParametersIuivNV = (PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramBufferParametersIuivNV")) == NULL) || r; + r = ((glProgramBufferParametersfvNV = (PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramBufferParametersfvNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_parameter_buffer_object */ + +#ifdef GL_NV_pixel_data_range + +static GLboolean _glewInit_GL_NV_pixel_data_range (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFlushPixelDataRangeNV = (PFNGLFLUSHPIXELDATARANGENVPROC)glewGetProcAddress((const GLubyte*)"glFlushPixelDataRangeNV")) == NULL) || r; + r = ((glPixelDataRangeNV = (PFNGLPIXELDATARANGENVPROC)glewGetProcAddress((const GLubyte*)"glPixelDataRangeNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_pixel_data_range */ + +#ifdef GL_NV_point_sprite + +static GLboolean _glewInit_GL_NV_point_sprite (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glPointParameteriNV = (PFNGLPOINTPARAMETERINVPROC)glewGetProcAddress((const GLubyte*)"glPointParameteriNV")) == NULL) || r; + r = ((glPointParameterivNV = (PFNGLPOINTPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glPointParameterivNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_point_sprite */ + +#ifdef GL_NV_present_video + +static GLboolean _glewInit_GL_NV_present_video (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetVideoi64vNV = (PFNGLGETVIDEOI64VNVPROC)glewGetProcAddress((const GLubyte*)"glGetVideoi64vNV")) == NULL) || r; + r = ((glGetVideoivNV = (PFNGLGETVIDEOIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetVideoivNV")) == NULL) || r; + r = ((glGetVideoui64vNV = (PFNGLGETVIDEOUI64VNVPROC)glewGetProcAddress((const GLubyte*)"glGetVideoui64vNV")) == NULL) || r; + r = ((glGetVideouivNV = (PFNGLGETVIDEOUIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetVideouivNV")) == NULL) || r; + r = ((glPresentFrameDualFillNV = (PFNGLPRESENTFRAMEDUALFILLNVPROC)glewGetProcAddress((const GLubyte*)"glPresentFrameDualFillNV")) == NULL) || r; + r = ((glPresentFrameKeyedNV = (PFNGLPRESENTFRAMEKEYEDNVPROC)glewGetProcAddress((const GLubyte*)"glPresentFrameKeyedNV")) == NULL) || r; + r = ((glVideoParameterivNV = (PFNGLVIDEOPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glVideoParameterivNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_present_video */ + +#ifdef GL_NV_primitive_restart + +static GLboolean _glewInit_GL_NV_primitive_restart (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glPrimitiveRestartIndexNV = (PFNGLPRIMITIVERESTARTINDEXNVPROC)glewGetProcAddress((const GLubyte*)"glPrimitiveRestartIndexNV")) == NULL) || r; + r = ((glPrimitiveRestartNV = (PFNGLPRIMITIVERESTARTNVPROC)glewGetProcAddress((const GLubyte*)"glPrimitiveRestartNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_primitive_restart */ + +#ifdef GL_NV_register_combiners + +static GLboolean _glewInit_GL_NV_register_combiners (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glCombinerInputNV = (PFNGLCOMBINERINPUTNVPROC)glewGetProcAddress((const GLubyte*)"glCombinerInputNV")) == NULL) || r; + r = ((glCombinerOutputNV = (PFNGLCOMBINEROUTPUTNVPROC)glewGetProcAddress((const GLubyte*)"glCombinerOutputNV")) == NULL) || r; + r = ((glCombinerParameterfNV = (PFNGLCOMBINERPARAMETERFNVPROC)glewGetProcAddress((const GLubyte*)"glCombinerParameterfNV")) == NULL) || r; + r = ((glCombinerParameterfvNV = (PFNGLCOMBINERPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glCombinerParameterfvNV")) == NULL) || r; + r = ((glCombinerParameteriNV = (PFNGLCOMBINERPARAMETERINVPROC)glewGetProcAddress((const GLubyte*)"glCombinerParameteriNV")) == NULL) || r; + r = ((glCombinerParameterivNV = (PFNGLCOMBINERPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glCombinerParameterivNV")) == NULL) || r; + r = ((glFinalCombinerInputNV = (PFNGLFINALCOMBINERINPUTNVPROC)glewGetProcAddress((const GLubyte*)"glFinalCombinerInputNV")) == NULL) || r; + r = ((glGetCombinerInputParameterfvNV = (PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetCombinerInputParameterfvNV")) == NULL) || r; + r = ((glGetCombinerInputParameterivNV = (PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetCombinerInputParameterivNV")) == NULL) || r; + r = ((glGetCombinerOutputParameterfvNV = (PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetCombinerOutputParameterfvNV")) == NULL) || r; + r = ((glGetCombinerOutputParameterivNV = (PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetCombinerOutputParameterivNV")) == NULL) || r; + r = ((glGetFinalCombinerInputParameterfvNV = (PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetFinalCombinerInputParameterfvNV")) == NULL) || r; + r = ((glGetFinalCombinerInputParameterivNV = (PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetFinalCombinerInputParameterivNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_register_combiners */ + +#ifdef GL_NV_register_combiners2 + +static GLboolean _glewInit_GL_NV_register_combiners2 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glCombinerStageParameterfvNV = (PFNGLCOMBINERSTAGEPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glCombinerStageParameterfvNV")) == NULL) || r; + r = ((glGetCombinerStageParameterfvNV = (PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetCombinerStageParameterfvNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_register_combiners2 */ + +#ifdef GL_NV_texgen_emboss + +#endif /* GL_NV_texgen_emboss */ + +#ifdef GL_NV_texgen_reflection + +#endif /* GL_NV_texgen_reflection */ + +#ifdef GL_NV_texture_compression_vtc + +#endif /* GL_NV_texture_compression_vtc */ + +#ifdef GL_NV_texture_env_combine4 + +#endif /* GL_NV_texture_env_combine4 */ + +#ifdef GL_NV_texture_expand_normal + +#endif /* GL_NV_texture_expand_normal */ + +#ifdef GL_NV_texture_rectangle + +#endif /* GL_NV_texture_rectangle */ + +#ifdef GL_NV_texture_shader + +#endif /* GL_NV_texture_shader */ + +#ifdef GL_NV_texture_shader2 + +#endif /* GL_NV_texture_shader2 */ + +#ifdef GL_NV_texture_shader3 + +#endif /* GL_NV_texture_shader3 */ + +#ifdef GL_NV_transform_feedback + +static GLboolean _glewInit_GL_NV_transform_feedback (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glActiveVaryingNV = (PFNGLACTIVEVARYINGNVPROC)glewGetProcAddress((const GLubyte*)"glActiveVaryingNV")) == NULL) || r; + r = ((glBeginTransformFeedbackNV = (PFNGLBEGINTRANSFORMFEEDBACKNVPROC)glewGetProcAddress((const GLubyte*)"glBeginTransformFeedbackNV")) == NULL) || r; + r = ((glBindBufferBaseNV = (PFNGLBINDBUFFERBASENVPROC)glewGetProcAddress((const GLubyte*)"glBindBufferBaseNV")) == NULL) || r; + r = ((glBindBufferOffsetNV = (PFNGLBINDBUFFEROFFSETNVPROC)glewGetProcAddress((const GLubyte*)"glBindBufferOffsetNV")) == NULL) || r; + r = ((glBindBufferRangeNV = (PFNGLBINDBUFFERRANGENVPROC)glewGetProcAddress((const GLubyte*)"glBindBufferRangeNV")) == NULL) || r; + r = ((glEndTransformFeedbackNV = (PFNGLENDTRANSFORMFEEDBACKNVPROC)glewGetProcAddress((const GLubyte*)"glEndTransformFeedbackNV")) == NULL) || r; + r = ((glGetActiveVaryingNV = (PFNGLGETACTIVEVARYINGNVPROC)glewGetProcAddress((const GLubyte*)"glGetActiveVaryingNV")) == NULL) || r; + r = ((glGetTransformFeedbackVaryingNV = (PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC)glewGetProcAddress((const GLubyte*)"glGetTransformFeedbackVaryingNV")) == NULL) || r; + r = ((glGetVaryingLocationNV = (PFNGLGETVARYINGLOCATIONNVPROC)glewGetProcAddress((const GLubyte*)"glGetVaryingLocationNV")) == NULL) || r; + r = ((glTransformFeedbackAttribsNV = (PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC)glewGetProcAddress((const GLubyte*)"glTransformFeedbackAttribsNV")) == NULL) || r; + r = ((glTransformFeedbackVaryingsNV = (PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC)glewGetProcAddress((const GLubyte*)"glTransformFeedbackVaryingsNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_transform_feedback */ + +#ifdef GL_NV_vertex_array_range + +static GLboolean _glewInit_GL_NV_vertex_array_range (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFlushVertexArrayRangeNV = (PFNGLFLUSHVERTEXARRAYRANGENVPROC)glewGetProcAddress((const GLubyte*)"glFlushVertexArrayRangeNV")) == NULL) || r; + r = ((glVertexArrayRangeNV = (PFNGLVERTEXARRAYRANGENVPROC)glewGetProcAddress((const GLubyte*)"glVertexArrayRangeNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_vertex_array_range */ + +#ifdef GL_NV_vertex_array_range2 + +#endif /* GL_NV_vertex_array_range2 */ + +#ifdef GL_NV_vertex_program + +static GLboolean _glewInit_GL_NV_vertex_program (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glAreProgramsResidentNV = (PFNGLAREPROGRAMSRESIDENTNVPROC)glewGetProcAddress((const GLubyte*)"glAreProgramsResidentNV")) == NULL) || r; + r = ((glBindProgramNV = (PFNGLBINDPROGRAMNVPROC)glewGetProcAddress((const GLubyte*)"glBindProgramNV")) == NULL) || r; + r = ((glDeleteProgramsNV = (PFNGLDELETEPROGRAMSNVPROC)glewGetProcAddress((const GLubyte*)"glDeleteProgramsNV")) == NULL) || r; + r = ((glExecuteProgramNV = (PFNGLEXECUTEPROGRAMNVPROC)glewGetProcAddress((const GLubyte*)"glExecuteProgramNV")) == NULL) || r; + r = ((glGenProgramsNV = (PFNGLGENPROGRAMSNVPROC)glewGetProcAddress((const GLubyte*)"glGenProgramsNV")) == NULL) || r; + r = ((glGetProgramParameterdvNV = (PFNGLGETPROGRAMPARAMETERDVNVPROC)glewGetProcAddress((const GLubyte*)"glGetProgramParameterdvNV")) == NULL) || r; + r = ((glGetProgramParameterfvNV = (PFNGLGETPROGRAMPARAMETERFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetProgramParameterfvNV")) == NULL) || r; + r = ((glGetProgramStringNV = (PFNGLGETPROGRAMSTRINGNVPROC)glewGetProcAddress((const GLubyte*)"glGetProgramStringNV")) == NULL) || r; + r = ((glGetProgramivNV = (PFNGLGETPROGRAMIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetProgramivNV")) == NULL) || r; + r = ((glGetTrackMatrixivNV = (PFNGLGETTRACKMATRIXIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetTrackMatrixivNV")) == NULL) || r; + r = ((glGetVertexAttribPointervNV = (PFNGLGETVERTEXATTRIBPOINTERVNVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribPointervNV")) == NULL) || r; + r = ((glGetVertexAttribdvNV = (PFNGLGETVERTEXATTRIBDVNVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribdvNV")) == NULL) || r; + r = ((glGetVertexAttribfvNV = (PFNGLGETVERTEXATTRIBFVNVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribfvNV")) == NULL) || r; + r = ((glGetVertexAttribivNV = (PFNGLGETVERTEXATTRIBIVNVPROC)glewGetProcAddress((const GLubyte*)"glGetVertexAttribivNV")) == NULL) || r; + r = ((glIsProgramNV = (PFNGLISPROGRAMNVPROC)glewGetProcAddress((const GLubyte*)"glIsProgramNV")) == NULL) || r; + r = ((glLoadProgramNV = (PFNGLLOADPROGRAMNVPROC)glewGetProcAddress((const GLubyte*)"glLoadProgramNV")) == NULL) || r; + r = ((glProgramParameter4dNV = (PFNGLPROGRAMPARAMETER4DNVPROC)glewGetProcAddress((const GLubyte*)"glProgramParameter4dNV")) == NULL) || r; + r = ((glProgramParameter4dvNV = (PFNGLPROGRAMPARAMETER4DVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramParameter4dvNV")) == NULL) || r; + r = ((glProgramParameter4fNV = (PFNGLPROGRAMPARAMETER4FNVPROC)glewGetProcAddress((const GLubyte*)"glProgramParameter4fNV")) == NULL) || r; + r = ((glProgramParameter4fvNV = (PFNGLPROGRAMPARAMETER4FVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramParameter4fvNV")) == NULL) || r; + r = ((glProgramParameters4dvNV = (PFNGLPROGRAMPARAMETERS4DVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramParameters4dvNV")) == NULL) || r; + r = ((glProgramParameters4fvNV = (PFNGLPROGRAMPARAMETERS4FVNVPROC)glewGetProcAddress((const GLubyte*)"glProgramParameters4fvNV")) == NULL) || r; + r = ((glRequestResidentProgramsNV = (PFNGLREQUESTRESIDENTPROGRAMSNVPROC)glewGetProcAddress((const GLubyte*)"glRequestResidentProgramsNV")) == NULL) || r; + r = ((glTrackMatrixNV = (PFNGLTRACKMATRIXNVPROC)glewGetProcAddress((const GLubyte*)"glTrackMatrixNV")) == NULL) || r; + r = ((glVertexAttrib1dNV = (PFNGLVERTEXATTRIB1DNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1dNV")) == NULL) || r; + r = ((glVertexAttrib1dvNV = (PFNGLVERTEXATTRIB1DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1dvNV")) == NULL) || r; + r = ((glVertexAttrib1fNV = (PFNGLVERTEXATTRIB1FNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1fNV")) == NULL) || r; + r = ((glVertexAttrib1fvNV = (PFNGLVERTEXATTRIB1FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1fvNV")) == NULL) || r; + r = ((glVertexAttrib1sNV = (PFNGLVERTEXATTRIB1SNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1sNV")) == NULL) || r; + r = ((glVertexAttrib1svNV = (PFNGLVERTEXATTRIB1SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib1svNV")) == NULL) || r; + r = ((glVertexAttrib2dNV = (PFNGLVERTEXATTRIB2DNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2dNV")) == NULL) || r; + r = ((glVertexAttrib2dvNV = (PFNGLVERTEXATTRIB2DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2dvNV")) == NULL) || r; + r = ((glVertexAttrib2fNV = (PFNGLVERTEXATTRIB2FNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2fNV")) == NULL) || r; + r = ((glVertexAttrib2fvNV = (PFNGLVERTEXATTRIB2FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2fvNV")) == NULL) || r; + r = ((glVertexAttrib2sNV = (PFNGLVERTEXATTRIB2SNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2sNV")) == NULL) || r; + r = ((glVertexAttrib2svNV = (PFNGLVERTEXATTRIB2SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib2svNV")) == NULL) || r; + r = ((glVertexAttrib3dNV = (PFNGLVERTEXATTRIB3DNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3dNV")) == NULL) || r; + r = ((glVertexAttrib3dvNV = (PFNGLVERTEXATTRIB3DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3dvNV")) == NULL) || r; + r = ((glVertexAttrib3fNV = (PFNGLVERTEXATTRIB3FNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3fNV")) == NULL) || r; + r = ((glVertexAttrib3fvNV = (PFNGLVERTEXATTRIB3FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3fvNV")) == NULL) || r; + r = ((glVertexAttrib3sNV = (PFNGLVERTEXATTRIB3SNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3sNV")) == NULL) || r; + r = ((glVertexAttrib3svNV = (PFNGLVERTEXATTRIB3SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib3svNV")) == NULL) || r; + r = ((glVertexAttrib4dNV = (PFNGLVERTEXATTRIB4DNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4dNV")) == NULL) || r; + r = ((glVertexAttrib4dvNV = (PFNGLVERTEXATTRIB4DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4dvNV")) == NULL) || r; + r = ((glVertexAttrib4fNV = (PFNGLVERTEXATTRIB4FNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4fNV")) == NULL) || r; + r = ((glVertexAttrib4fvNV = (PFNGLVERTEXATTRIB4FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4fvNV")) == NULL) || r; + r = ((glVertexAttrib4sNV = (PFNGLVERTEXATTRIB4SNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4sNV")) == NULL) || r; + r = ((glVertexAttrib4svNV = (PFNGLVERTEXATTRIB4SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4svNV")) == NULL) || r; + r = ((glVertexAttrib4ubNV = (PFNGLVERTEXATTRIB4UBNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4ubNV")) == NULL) || r; + r = ((glVertexAttrib4ubvNV = (PFNGLVERTEXATTRIB4UBVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttrib4ubvNV")) == NULL) || r; + r = ((glVertexAttribPointerNV = (PFNGLVERTEXATTRIBPOINTERNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribPointerNV")) == NULL) || r; + r = ((glVertexAttribs1dvNV = (PFNGLVERTEXATTRIBS1DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs1dvNV")) == NULL) || r; + r = ((glVertexAttribs1fvNV = (PFNGLVERTEXATTRIBS1FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs1fvNV")) == NULL) || r; + r = ((glVertexAttribs1svNV = (PFNGLVERTEXATTRIBS1SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs1svNV")) == NULL) || r; + r = ((glVertexAttribs2dvNV = (PFNGLVERTEXATTRIBS2DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs2dvNV")) == NULL) || r; + r = ((glVertexAttribs2fvNV = (PFNGLVERTEXATTRIBS2FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs2fvNV")) == NULL) || r; + r = ((glVertexAttribs2svNV = (PFNGLVERTEXATTRIBS2SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs2svNV")) == NULL) || r; + r = ((glVertexAttribs3dvNV = (PFNGLVERTEXATTRIBS3DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs3dvNV")) == NULL) || r; + r = ((glVertexAttribs3fvNV = (PFNGLVERTEXATTRIBS3FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs3fvNV")) == NULL) || r; + r = ((glVertexAttribs3svNV = (PFNGLVERTEXATTRIBS3SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs3svNV")) == NULL) || r; + r = ((glVertexAttribs4dvNV = (PFNGLVERTEXATTRIBS4DVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs4dvNV")) == NULL) || r; + r = ((glVertexAttribs4fvNV = (PFNGLVERTEXATTRIBS4FVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs4fvNV")) == NULL) || r; + r = ((glVertexAttribs4svNV = (PFNGLVERTEXATTRIBS4SVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs4svNV")) == NULL) || r; + r = ((glVertexAttribs4ubvNV = (PFNGLVERTEXATTRIBS4UBVNVPROC)glewGetProcAddress((const GLubyte*)"glVertexAttribs4ubvNV")) == NULL) || r; + + return r; +} + +#endif /* GL_NV_vertex_program */ + +#ifdef GL_NV_vertex_program1_1 + +#endif /* GL_NV_vertex_program1_1 */ + +#ifdef GL_NV_vertex_program2 + +#endif /* GL_NV_vertex_program2 */ + +#ifdef GL_NV_vertex_program2_option + +#endif /* GL_NV_vertex_program2_option */ + +#ifdef GL_NV_vertex_program3 + +#endif /* GL_NV_vertex_program3 */ + +#ifdef GL_NV_vertex_program4 + +#endif /* GL_NV_vertex_program4 */ + +#ifdef GL_OES_byte_coordinates + +#endif /* GL_OES_byte_coordinates */ + +#ifdef GL_OES_compressed_paletted_texture + +#endif /* GL_OES_compressed_paletted_texture */ + +#ifdef GL_OES_read_format + +#endif /* GL_OES_read_format */ + +#ifdef GL_OES_single_precision + +static GLboolean _glewInit_GL_OES_single_precision (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glClearDepthfOES = (PFNGLCLEARDEPTHFOESPROC)glewGetProcAddress((const GLubyte*)"glClearDepthfOES")) == NULL) || r; + r = ((glClipPlanefOES = (PFNGLCLIPPLANEFOESPROC)glewGetProcAddress((const GLubyte*)"glClipPlanefOES")) == NULL) || r; + r = ((glDepthRangefOES = (PFNGLDEPTHRANGEFOESPROC)glewGetProcAddress((const GLubyte*)"glDepthRangefOES")) == NULL) || r; + r = ((glFrustumfOES = (PFNGLFRUSTUMFOESPROC)glewGetProcAddress((const GLubyte*)"glFrustumfOES")) == NULL) || r; + r = ((glGetClipPlanefOES = (PFNGLGETCLIPPLANEFOESPROC)glewGetProcAddress((const GLubyte*)"glGetClipPlanefOES")) == NULL) || r; + r = ((glOrthofOES = (PFNGLORTHOFOESPROC)glewGetProcAddress((const GLubyte*)"glOrthofOES")) == NULL) || r; + + return r; +} + +#endif /* GL_OES_single_precision */ + +#ifdef GL_OML_interlace + +#endif /* GL_OML_interlace */ + +#ifdef GL_OML_resample + +#endif /* GL_OML_resample */ + +#ifdef GL_OML_subsample + +#endif /* GL_OML_subsample */ + +#ifdef GL_PGI_misc_hints + +#endif /* GL_PGI_misc_hints */ + +#ifdef GL_PGI_vertex_hints + +#endif /* GL_PGI_vertex_hints */ + +#ifdef GL_REND_screen_coordinates + +#endif /* GL_REND_screen_coordinates */ + +#ifdef GL_S3_s3tc + +#endif /* GL_S3_s3tc */ + +#ifdef GL_SGIS_color_range + +#endif /* GL_SGIS_color_range */ + +#ifdef GL_SGIS_detail_texture + +static GLboolean _glewInit_GL_SGIS_detail_texture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glDetailTexFuncSGIS = (PFNGLDETAILTEXFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glDetailTexFuncSGIS")) == NULL) || r; + r = ((glGetDetailTexFuncSGIS = (PFNGLGETDETAILTEXFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glGetDetailTexFuncSGIS")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIS_detail_texture */ + +#ifdef GL_SGIS_fog_function + +static GLboolean _glewInit_GL_SGIS_fog_function (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFogFuncSGIS = (PFNGLFOGFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glFogFuncSGIS")) == NULL) || r; + r = ((glGetFogFuncSGIS = (PFNGLGETFOGFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glGetFogFuncSGIS")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIS_fog_function */ + +#ifdef GL_SGIS_generate_mipmap + +#endif /* GL_SGIS_generate_mipmap */ + +#ifdef GL_SGIS_multisample + +static GLboolean _glewInit_GL_SGIS_multisample (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glSampleMaskSGIS = (PFNGLSAMPLEMASKSGISPROC)glewGetProcAddress((const GLubyte*)"glSampleMaskSGIS")) == NULL) || r; + r = ((glSamplePatternSGIS = (PFNGLSAMPLEPATTERNSGISPROC)glewGetProcAddress((const GLubyte*)"glSamplePatternSGIS")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIS_multisample */ + +#ifdef GL_SGIS_pixel_texture + +#endif /* GL_SGIS_pixel_texture */ + +#ifdef GL_SGIS_point_line_texgen + +#endif /* GL_SGIS_point_line_texgen */ + +#ifdef GL_SGIS_sharpen_texture + +static GLboolean _glewInit_GL_SGIS_sharpen_texture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetSharpenTexFuncSGIS = (PFNGLGETSHARPENTEXFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glGetSharpenTexFuncSGIS")) == NULL) || r; + r = ((glSharpenTexFuncSGIS = (PFNGLSHARPENTEXFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glSharpenTexFuncSGIS")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIS_sharpen_texture */ + +#ifdef GL_SGIS_texture4D + +static GLboolean _glewInit_GL_SGIS_texture4D (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTexImage4DSGIS = (PFNGLTEXIMAGE4DSGISPROC)glewGetProcAddress((const GLubyte*)"glTexImage4DSGIS")) == NULL) || r; + r = ((glTexSubImage4DSGIS = (PFNGLTEXSUBIMAGE4DSGISPROC)glewGetProcAddress((const GLubyte*)"glTexSubImage4DSGIS")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIS_texture4D */ + +#ifdef GL_SGIS_texture_border_clamp + +#endif /* GL_SGIS_texture_border_clamp */ + +#ifdef GL_SGIS_texture_edge_clamp + +#endif /* GL_SGIS_texture_edge_clamp */ + +#ifdef GL_SGIS_texture_filter4 + +static GLboolean _glewInit_GL_SGIS_texture_filter4 (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGetTexFilterFuncSGIS = (PFNGLGETTEXFILTERFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glGetTexFilterFuncSGIS")) == NULL) || r; + r = ((glTexFilterFuncSGIS = (PFNGLTEXFILTERFUNCSGISPROC)glewGetProcAddress((const GLubyte*)"glTexFilterFuncSGIS")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIS_texture_filter4 */ + +#ifdef GL_SGIS_texture_lod + +#endif /* GL_SGIS_texture_lod */ + +#ifdef GL_SGIS_texture_select + +#endif /* GL_SGIS_texture_select */ + +#ifdef GL_SGIX_async + +static GLboolean _glewInit_GL_SGIX_async (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glAsyncMarkerSGIX = (PFNGLASYNCMARKERSGIXPROC)glewGetProcAddress((const GLubyte*)"glAsyncMarkerSGIX")) == NULL) || r; + r = ((glDeleteAsyncMarkersSGIX = (PFNGLDELETEASYNCMARKERSSGIXPROC)glewGetProcAddress((const GLubyte*)"glDeleteAsyncMarkersSGIX")) == NULL) || r; + r = ((glFinishAsyncSGIX = (PFNGLFINISHASYNCSGIXPROC)glewGetProcAddress((const GLubyte*)"glFinishAsyncSGIX")) == NULL) || r; + r = ((glGenAsyncMarkersSGIX = (PFNGLGENASYNCMARKERSSGIXPROC)glewGetProcAddress((const GLubyte*)"glGenAsyncMarkersSGIX")) == NULL) || r; + r = ((glIsAsyncMarkerSGIX = (PFNGLISASYNCMARKERSGIXPROC)glewGetProcAddress((const GLubyte*)"glIsAsyncMarkerSGIX")) == NULL) || r; + r = ((glPollAsyncSGIX = (PFNGLPOLLASYNCSGIXPROC)glewGetProcAddress((const GLubyte*)"glPollAsyncSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_async */ + +#ifdef GL_SGIX_async_histogram + +#endif /* GL_SGIX_async_histogram */ + +#ifdef GL_SGIX_async_pixel + +#endif /* GL_SGIX_async_pixel */ + +#ifdef GL_SGIX_blend_alpha_minmax + +#endif /* GL_SGIX_blend_alpha_minmax */ + +#ifdef GL_SGIX_clipmap + +#endif /* GL_SGIX_clipmap */ + +#ifdef GL_SGIX_convolution_accuracy + +#endif /* GL_SGIX_convolution_accuracy */ + +#ifdef GL_SGIX_depth_texture + +#endif /* GL_SGIX_depth_texture */ + +#ifdef GL_SGIX_flush_raster + +static GLboolean _glewInit_GL_SGIX_flush_raster (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFlushRasterSGIX = (PFNGLFLUSHRASTERSGIXPROC)glewGetProcAddress((const GLubyte*)"glFlushRasterSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_flush_raster */ + +#ifdef GL_SGIX_fog_offset + +#endif /* GL_SGIX_fog_offset */ + +#ifdef GL_SGIX_fog_texture + +static GLboolean _glewInit_GL_SGIX_fog_texture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTextureFogSGIX = (PFNGLTEXTUREFOGSGIXPROC)glewGetProcAddress((const GLubyte*)"glTextureFogSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_fog_texture */ + +#ifdef GL_SGIX_fragment_specular_lighting + +static GLboolean _glewInit_GL_SGIX_fragment_specular_lighting (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFragmentColorMaterialSGIX = (PFNGLFRAGMENTCOLORMATERIALSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentColorMaterialSGIX")) == NULL) || r; + r = ((glFragmentLightModelfSGIX = (PFNGLFRAGMENTLIGHTMODELFSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModelfSGIX")) == NULL) || r; + r = ((glFragmentLightModelfvSGIX = (PFNGLFRAGMENTLIGHTMODELFVSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModelfvSGIX")) == NULL) || r; + r = ((glFragmentLightModeliSGIX = (PFNGLFRAGMENTLIGHTMODELISGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModeliSGIX")) == NULL) || r; + r = ((glFragmentLightModelivSGIX = (PFNGLFRAGMENTLIGHTMODELIVSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightModelivSGIX")) == NULL) || r; + r = ((glFragmentLightfSGIX = (PFNGLFRAGMENTLIGHTFSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightfSGIX")) == NULL) || r; + r = ((glFragmentLightfvSGIX = (PFNGLFRAGMENTLIGHTFVSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightfvSGIX")) == NULL) || r; + r = ((glFragmentLightiSGIX = (PFNGLFRAGMENTLIGHTISGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightiSGIX")) == NULL) || r; + r = ((glFragmentLightivSGIX = (PFNGLFRAGMENTLIGHTIVSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentLightivSGIX")) == NULL) || r; + r = ((glFragmentMaterialfSGIX = (PFNGLFRAGMENTMATERIALFSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialfSGIX")) == NULL) || r; + r = ((glFragmentMaterialfvSGIX = (PFNGLFRAGMENTMATERIALFVSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialfvSGIX")) == NULL) || r; + r = ((glFragmentMaterialiSGIX = (PFNGLFRAGMENTMATERIALISGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialiSGIX")) == NULL) || r; + r = ((glFragmentMaterialivSGIX = (PFNGLFRAGMENTMATERIALIVSGIXPROC)glewGetProcAddress((const GLubyte*)"glFragmentMaterialivSGIX")) == NULL) || r; + r = ((glGetFragmentLightfvSGIX = (PFNGLGETFRAGMENTLIGHTFVSGIXPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentLightfvSGIX")) == NULL) || r; + r = ((glGetFragmentLightivSGIX = (PFNGLGETFRAGMENTLIGHTIVSGIXPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentLightivSGIX")) == NULL) || r; + r = ((glGetFragmentMaterialfvSGIX = (PFNGLGETFRAGMENTMATERIALFVSGIXPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentMaterialfvSGIX")) == NULL) || r; + r = ((glGetFragmentMaterialivSGIX = (PFNGLGETFRAGMENTMATERIALIVSGIXPROC)glewGetProcAddress((const GLubyte*)"glGetFragmentMaterialivSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_fragment_specular_lighting */ + +#ifdef GL_SGIX_framezoom + +static GLboolean _glewInit_GL_SGIX_framezoom (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFrameZoomSGIX = (PFNGLFRAMEZOOMSGIXPROC)glewGetProcAddress((const GLubyte*)"glFrameZoomSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_framezoom */ + +#ifdef GL_SGIX_interlace + +#endif /* GL_SGIX_interlace */ + +#ifdef GL_SGIX_ir_instrument1 + +#endif /* GL_SGIX_ir_instrument1 */ + +#ifdef GL_SGIX_list_priority + +#endif /* GL_SGIX_list_priority */ + +#ifdef GL_SGIX_pixel_texture + +static GLboolean _glewInit_GL_SGIX_pixel_texture (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glPixelTexGenSGIX = (PFNGLPIXELTEXGENSGIXPROC)glewGetProcAddress((const GLubyte*)"glPixelTexGenSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_pixel_texture */ + +#ifdef GL_SGIX_pixel_texture_bits + +#endif /* GL_SGIX_pixel_texture_bits */ + +#ifdef GL_SGIX_reference_plane + +static GLboolean _glewInit_GL_SGIX_reference_plane (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glReferencePlaneSGIX = (PFNGLREFERENCEPLANESGIXPROC)glewGetProcAddress((const GLubyte*)"glReferencePlaneSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_reference_plane */ + +#ifdef GL_SGIX_resample + +#endif /* GL_SGIX_resample */ + +#ifdef GL_SGIX_shadow + +#endif /* GL_SGIX_shadow */ + +#ifdef GL_SGIX_shadow_ambient + +#endif /* GL_SGIX_shadow_ambient */ + +#ifdef GL_SGIX_sprite + +static GLboolean _glewInit_GL_SGIX_sprite (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glSpriteParameterfSGIX = (PFNGLSPRITEPARAMETERFSGIXPROC)glewGetProcAddress((const GLubyte*)"glSpriteParameterfSGIX")) == NULL) || r; + r = ((glSpriteParameterfvSGIX = (PFNGLSPRITEPARAMETERFVSGIXPROC)glewGetProcAddress((const GLubyte*)"glSpriteParameterfvSGIX")) == NULL) || r; + r = ((glSpriteParameteriSGIX = (PFNGLSPRITEPARAMETERISGIXPROC)glewGetProcAddress((const GLubyte*)"glSpriteParameteriSGIX")) == NULL) || r; + r = ((glSpriteParameterivSGIX = (PFNGLSPRITEPARAMETERIVSGIXPROC)glewGetProcAddress((const GLubyte*)"glSpriteParameterivSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_sprite */ + +#ifdef GL_SGIX_tag_sample_buffer + +static GLboolean _glewInit_GL_SGIX_tag_sample_buffer (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glTagSampleBufferSGIX = (PFNGLTAGSAMPLEBUFFERSGIXPROC)glewGetProcAddress((const GLubyte*)"glTagSampleBufferSGIX")) == NULL) || r; + + return r; +} + +#endif /* GL_SGIX_tag_sample_buffer */ + +#ifdef GL_SGIX_texture_add_env + +#endif /* GL_SGIX_texture_add_env */ + +#ifdef GL_SGIX_texture_coordinate_clamp + +#endif /* GL_SGIX_texture_coordinate_clamp */ + +#ifdef GL_SGIX_texture_lod_bias + +#endif /* GL_SGIX_texture_lod_bias */ + +#ifdef GL_SGIX_texture_multi_buffer + +#endif /* GL_SGIX_texture_multi_buffer */ + +#ifdef GL_SGIX_texture_range + +#endif /* GL_SGIX_texture_range */ + +#ifdef GL_SGIX_texture_scale_bias + +#endif /* GL_SGIX_texture_scale_bias */ + +#ifdef GL_SGIX_vertex_preclip + +#endif /* GL_SGIX_vertex_preclip */ + +#ifdef GL_SGIX_vertex_preclip_hint + +#endif /* GL_SGIX_vertex_preclip_hint */ + +#ifdef GL_SGIX_ycrcb + +#endif /* GL_SGIX_ycrcb */ + +#ifdef GL_SGI_color_matrix + +#endif /* GL_SGI_color_matrix */ + +#ifdef GL_SGI_color_table + +static GLboolean _glewInit_GL_SGI_color_table (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColorTableParameterfvSGI = (PFNGLCOLORTABLEPARAMETERFVSGIPROC)glewGetProcAddress((const GLubyte*)"glColorTableParameterfvSGI")) == NULL) || r; + r = ((glColorTableParameterivSGI = (PFNGLCOLORTABLEPARAMETERIVSGIPROC)glewGetProcAddress((const GLubyte*)"glColorTableParameterivSGI")) == NULL) || r; + r = ((glColorTableSGI = (PFNGLCOLORTABLESGIPROC)glewGetProcAddress((const GLubyte*)"glColorTableSGI")) == NULL) || r; + r = ((glCopyColorTableSGI = (PFNGLCOPYCOLORTABLESGIPROC)glewGetProcAddress((const GLubyte*)"glCopyColorTableSGI")) == NULL) || r; + r = ((glGetColorTableParameterfvSGI = (PFNGLGETCOLORTABLEPARAMETERFVSGIPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableParameterfvSGI")) == NULL) || r; + r = ((glGetColorTableParameterivSGI = (PFNGLGETCOLORTABLEPARAMETERIVSGIPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableParameterivSGI")) == NULL) || r; + r = ((glGetColorTableSGI = (PFNGLGETCOLORTABLESGIPROC)glewGetProcAddress((const GLubyte*)"glGetColorTableSGI")) == NULL) || r; + + return r; +} + +#endif /* GL_SGI_color_table */ + +#ifdef GL_SGI_texture_color_table + +#endif /* GL_SGI_texture_color_table */ + +#ifdef GL_SUNX_constant_data + +static GLboolean _glewInit_GL_SUNX_constant_data (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glFinishTextureSUNX = (PFNGLFINISHTEXTURESUNXPROC)glewGetProcAddress((const GLubyte*)"glFinishTextureSUNX")) == NULL) || r; + + return r; +} + +#endif /* GL_SUNX_constant_data */ + +#ifdef GL_SUN_convolution_border_modes + +#endif /* GL_SUN_convolution_border_modes */ + +#ifdef GL_SUN_global_alpha + +static GLboolean _glewInit_GL_SUN_global_alpha (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glGlobalAlphaFactorbSUN = (PFNGLGLOBALALPHAFACTORBSUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactorbSUN")) == NULL) || r; + r = ((glGlobalAlphaFactordSUN = (PFNGLGLOBALALPHAFACTORDSUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactordSUN")) == NULL) || r; + r = ((glGlobalAlphaFactorfSUN = (PFNGLGLOBALALPHAFACTORFSUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactorfSUN")) == NULL) || r; + r = ((glGlobalAlphaFactoriSUN = (PFNGLGLOBALALPHAFACTORISUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactoriSUN")) == NULL) || r; + r = ((glGlobalAlphaFactorsSUN = (PFNGLGLOBALALPHAFACTORSSUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactorsSUN")) == NULL) || r; + r = ((glGlobalAlphaFactorubSUN = (PFNGLGLOBALALPHAFACTORUBSUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactorubSUN")) == NULL) || r; + r = ((glGlobalAlphaFactoruiSUN = (PFNGLGLOBALALPHAFACTORUISUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactoruiSUN")) == NULL) || r; + r = ((glGlobalAlphaFactorusSUN = (PFNGLGLOBALALPHAFACTORUSSUNPROC)glewGetProcAddress((const GLubyte*)"glGlobalAlphaFactorusSUN")) == NULL) || r; + + return r; +} + +#endif /* GL_SUN_global_alpha */ + +#ifdef GL_SUN_mesh_array + +#endif /* GL_SUN_mesh_array */ + +#ifdef GL_SUN_read_video_pixels + +static GLboolean _glewInit_GL_SUN_read_video_pixels (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glReadVideoPixelsSUN = (PFNGLREADVIDEOPIXELSSUNPROC)glewGetProcAddress((const GLubyte*)"glReadVideoPixelsSUN")) == NULL) || r; + + return r; +} + +#endif /* GL_SUN_read_video_pixels */ + +#ifdef GL_SUN_slice_accum + +#endif /* GL_SUN_slice_accum */ + +#ifdef GL_SUN_triangle_list + +static GLboolean _glewInit_GL_SUN_triangle_list (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glReplacementCodePointerSUN = (PFNGLREPLACEMENTCODEPOINTERSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodePointerSUN")) == NULL) || r; + r = ((glReplacementCodeubSUN = (PFNGLREPLACEMENTCODEUBSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeubSUN")) == NULL) || r; + r = ((glReplacementCodeubvSUN = (PFNGLREPLACEMENTCODEUBVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeubvSUN")) == NULL) || r; + r = ((glReplacementCodeuiSUN = (PFNGLREPLACEMENTCODEUISUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiSUN")) == NULL) || r; + r = ((glReplacementCodeuivSUN = (PFNGLREPLACEMENTCODEUIVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuivSUN")) == NULL) || r; + r = ((glReplacementCodeusSUN = (PFNGLREPLACEMENTCODEUSSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeusSUN")) == NULL) || r; + r = ((glReplacementCodeusvSUN = (PFNGLREPLACEMENTCODEUSVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeusvSUN")) == NULL) || r; + + return r; +} + +#endif /* GL_SUN_triangle_list */ + +#ifdef GL_SUN_vertex + +static GLboolean _glewInit_GL_SUN_vertex (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glColor3fVertex3fSUN = (PFNGLCOLOR3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glColor3fVertex3fSUN")) == NULL) || r; + r = ((glColor3fVertex3fvSUN = (PFNGLCOLOR3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glColor3fVertex3fvSUN")) == NULL) || r; + r = ((glColor4fNormal3fVertex3fSUN = (PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glColor4fNormal3fVertex3fSUN")) == NULL) || r; + r = ((glColor4fNormal3fVertex3fvSUN = (PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glColor4fNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glColor4ubVertex2fSUN = (PFNGLCOLOR4UBVERTEX2FSUNPROC)glewGetProcAddress((const GLubyte*)"glColor4ubVertex2fSUN")) == NULL) || r; + r = ((glColor4ubVertex2fvSUN = (PFNGLCOLOR4UBVERTEX2FVSUNPROC)glewGetProcAddress((const GLubyte*)"glColor4ubVertex2fvSUN")) == NULL) || r; + r = ((glColor4ubVertex3fSUN = (PFNGLCOLOR4UBVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glColor4ubVertex3fSUN")) == NULL) || r; + r = ((glColor4ubVertex3fvSUN = (PFNGLCOLOR4UBVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glColor4ubVertex3fvSUN")) == NULL) || r; + r = ((glNormal3fVertex3fSUN = (PFNGLNORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glNormal3fVertex3fSUN")) == NULL) || r; + r = ((glNormal3fVertex3fvSUN = (PFNGLNORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiColor3fVertex3fSUN = (PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiColor3fVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiColor3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiColor3fVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiColor4fNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiColor4fNormal3fVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiColor4fNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiColor4fNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiColor4ubVertex3fSUN = (PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiColor4ubVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiColor4ubVertex3fvSUN = (PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiColor4ubVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiNormal3fVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiTexCoord2fVertex3fSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiTexCoord2fVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiTexCoord2fVertex3fvSUN = (PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiTexCoord2fVertex3fvSUN")) == NULL) || r; + r = ((glReplacementCodeuiVertex3fSUN = (PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiVertex3fSUN")) == NULL) || r; + r = ((glReplacementCodeuiVertex3fvSUN = (PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glReplacementCodeuiVertex3fvSUN")) == NULL) || r; + r = ((glTexCoord2fColor3fVertex3fSUN = (PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fColor3fVertex3fSUN")) == NULL) || r; + r = ((glTexCoord2fColor3fVertex3fvSUN = (PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fColor3fVertex3fvSUN")) == NULL) || r; + r = ((glTexCoord2fColor4fNormal3fVertex3fSUN = (PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fColor4fNormal3fVertex3fSUN")) == NULL) || r; + r = ((glTexCoord2fColor4fNormal3fVertex3fvSUN = (PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fColor4fNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glTexCoord2fColor4ubVertex3fSUN = (PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fColor4ubVertex3fSUN")) == NULL) || r; + r = ((glTexCoord2fColor4ubVertex3fvSUN = (PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fColor4ubVertex3fvSUN")) == NULL) || r; + r = ((glTexCoord2fNormal3fVertex3fSUN = (PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fNormal3fVertex3fSUN")) == NULL) || r; + r = ((glTexCoord2fNormal3fVertex3fvSUN = (PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fNormal3fVertex3fvSUN")) == NULL) || r; + r = ((glTexCoord2fVertex3fSUN = (PFNGLTEXCOORD2FVERTEX3FSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fVertex3fSUN")) == NULL) || r; + r = ((glTexCoord2fVertex3fvSUN = (PFNGLTEXCOORD2FVERTEX3FVSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord2fVertex3fvSUN")) == NULL) || r; + r = ((glTexCoord4fColor4fNormal3fVertex4fSUN = (PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord4fColor4fNormal3fVertex4fSUN")) == NULL) || r; + r = ((glTexCoord4fColor4fNormal3fVertex4fvSUN = (PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord4fColor4fNormal3fVertex4fvSUN")) == NULL) || r; + r = ((glTexCoord4fVertex4fSUN = (PFNGLTEXCOORD4FVERTEX4FSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord4fVertex4fSUN")) == NULL) || r; + r = ((glTexCoord4fVertex4fvSUN = (PFNGLTEXCOORD4FVERTEX4FVSUNPROC)glewGetProcAddress((const GLubyte*)"glTexCoord4fVertex4fvSUN")) == NULL) || r; + + return r; +} + +#endif /* GL_SUN_vertex */ + +#ifdef GL_WIN_phong_shading + +#endif /* GL_WIN_phong_shading */ + +#ifdef GL_WIN_specular_fog + +#endif /* GL_WIN_specular_fog */ + +#ifdef GL_WIN_swap_hint + +static GLboolean _glewInit_GL_WIN_swap_hint (GLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glAddSwapHintRectWIN = (PFNGLADDSWAPHINTRECTWINPROC)glewGetProcAddress((const GLubyte*)"glAddSwapHintRectWIN")) == NULL) || r; + + return r; +} + +#endif /* GL_WIN_swap_hint */ + +/* ------------------------------------------------------------------------- */ + +/* + * Search for name in the extensions string. Use of strstr() + * is not sufficient because extension names can be prefixes of + * other extension names. Could use strtok() but the constant + * string returned by glGetString might be in read-only memory. + */ +GLboolean glewGetExtension (const char* name) +{ + GLubyte* p; + GLubyte* end; + GLuint len = _glewStrLen((const GLubyte*)name); + p = (GLubyte*)glGetString(GL_EXTENSIONS); + if (0 == p) return GL_FALSE; + end = p + _glewStrLen(p); + while (p < end) + { + GLuint n = _glewStrCLen(p, ' '); + if (len == n && _glewStrSame((const GLubyte*)name, p, n)) return GL_TRUE; + p += n+1; + } + return GL_FALSE; +} + +/* ------------------------------------------------------------------------- */ + +#ifndef GLEW_MX +static +#endif +GLenum glewContextInit (GLEW_CONTEXT_ARG_DEF_LIST) +{ + const GLubyte* s; + GLuint dot, major, minor; + /* query opengl version */ + s = glGetString(GL_VERSION); + dot = _glewStrCLen(s, '.'); + major = dot-1; + minor = dot+1; + if (dot == 0 || s[minor] == '\0') + return GLEW_ERROR_NO_GL_VERSION; + if (s[major] == '1' && s[minor] == '0') + { + return GLEW_ERROR_GL_VERSION_10_ONLY; + } + else + { + CONST_CAST(GLEW_VERSION_1_1) = GL_TRUE; + if (s[major] >= '2') + { + CONST_CAST(GLEW_VERSION_1_2) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_3) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_4) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_5) = GL_TRUE; + CONST_CAST(GLEW_VERSION_2_0) = GL_TRUE; + if (s[minor] >= '1') + { + CONST_CAST(GLEW_VERSION_2_1) = GL_TRUE; + } + } + else + { + if (s[minor] >= '5') + { + CONST_CAST(GLEW_VERSION_1_2) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_3) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_4) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_5) = GL_TRUE; + CONST_CAST(GLEW_VERSION_2_0) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_1) = GL_FALSE; + } + if (s[minor] == '4') + { + CONST_CAST(GLEW_VERSION_1_2) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_3) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_4) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_5) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_0) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_1) = GL_FALSE; + } + if (s[minor] == '3') + { + CONST_CAST(GLEW_VERSION_1_2) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_3) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_4) = GL_FALSE; + CONST_CAST(GLEW_VERSION_1_5) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_0) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_1) = GL_FALSE; + } + if (s[minor] == '2') + { + CONST_CAST(GLEW_VERSION_1_2) = GL_TRUE; + CONST_CAST(GLEW_VERSION_1_3) = GL_FALSE; + CONST_CAST(GLEW_VERSION_1_4) = GL_FALSE; + CONST_CAST(GLEW_VERSION_1_5) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_0) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_1) = GL_FALSE; + } + if (s[minor] < '2') + { + CONST_CAST(GLEW_VERSION_1_2) = GL_FALSE; + CONST_CAST(GLEW_VERSION_1_3) = GL_FALSE; + CONST_CAST(GLEW_VERSION_1_4) = GL_FALSE; + CONST_CAST(GLEW_VERSION_1_5) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_0) = GL_FALSE; + CONST_CAST(GLEW_VERSION_2_1) = GL_FALSE; + } + } + } + /* initialize extensions */ +#ifdef GL_VERSION_1_2 + if (glewExperimental || GLEW_VERSION_1_2) CONST_CAST(GLEW_VERSION_1_2) = !_glewInit_GL_VERSION_1_2(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_VERSION_1_2 */ +#ifdef GL_VERSION_1_3 + if (glewExperimental || GLEW_VERSION_1_3) CONST_CAST(GLEW_VERSION_1_3) = !_glewInit_GL_VERSION_1_3(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_VERSION_1_3 */ +#ifdef GL_VERSION_1_4 + if (glewExperimental || GLEW_VERSION_1_4) CONST_CAST(GLEW_VERSION_1_4) = !_glewInit_GL_VERSION_1_4(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_VERSION_1_4 */ +#ifdef GL_VERSION_1_5 + if (glewExperimental || GLEW_VERSION_1_5) CONST_CAST(GLEW_VERSION_1_5) = !_glewInit_GL_VERSION_1_5(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_VERSION_1_5 */ +#ifdef GL_VERSION_2_0 + if (glewExperimental || GLEW_VERSION_2_0) CONST_CAST(GLEW_VERSION_2_0) = !_glewInit_GL_VERSION_2_0(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_VERSION_2_0 */ +#ifdef GL_VERSION_2_1 + if (glewExperimental || GLEW_VERSION_2_1) CONST_CAST(GLEW_VERSION_2_1) = !_glewInit_GL_VERSION_2_1(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_VERSION_2_1 */ +#ifdef GL_VERSION_3_0 + if (glewExperimental || GLEW_VERSION_3_0) CONST_CAST(GLEW_VERSION_3_0) = !_glewInit_GL_VERSION_3_0(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_VERSION_3_0 */ +#ifdef GL_3DFX_multisample + CONST_CAST(GLEW_3DFX_multisample) = glewGetExtension("GL_3DFX_multisample"); +#endif /* GL_3DFX_multisample */ +#ifdef GL_3DFX_tbuffer + CONST_CAST(GLEW_3DFX_tbuffer) = glewGetExtension("GL_3DFX_tbuffer"); + if (glewExperimental || GLEW_3DFX_tbuffer) CONST_CAST(GLEW_3DFX_tbuffer) = !_glewInit_GL_3DFX_tbuffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_3DFX_tbuffer */ +#ifdef GL_3DFX_texture_compression_FXT1 + CONST_CAST(GLEW_3DFX_texture_compression_FXT1) = glewGetExtension("GL_3DFX_texture_compression_FXT1"); +#endif /* GL_3DFX_texture_compression_FXT1 */ +#ifdef GL_APPLE_client_storage + CONST_CAST(GLEW_APPLE_client_storage) = glewGetExtension("GL_APPLE_client_storage"); +#endif /* GL_APPLE_client_storage */ +#ifdef GL_APPLE_element_array + CONST_CAST(GLEW_APPLE_element_array) = glewGetExtension("GL_APPLE_element_array"); + if (glewExperimental || GLEW_APPLE_element_array) CONST_CAST(GLEW_APPLE_element_array) = !_glewInit_GL_APPLE_element_array(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_APPLE_element_array */ +#ifdef GL_APPLE_fence + CONST_CAST(GLEW_APPLE_fence) = glewGetExtension("GL_APPLE_fence"); + if (glewExperimental || GLEW_APPLE_fence) CONST_CAST(GLEW_APPLE_fence) = !_glewInit_GL_APPLE_fence(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_APPLE_fence */ +#ifdef GL_APPLE_float_pixels + CONST_CAST(GLEW_APPLE_float_pixels) = glewGetExtension("GL_APPLE_float_pixels"); +#endif /* GL_APPLE_float_pixels */ +#ifdef GL_APPLE_flush_buffer_range + CONST_CAST(GLEW_APPLE_flush_buffer_range) = glewGetExtension("GL_APPLE_flush_buffer_range"); + if (glewExperimental || GLEW_APPLE_flush_buffer_range) CONST_CAST(GLEW_APPLE_flush_buffer_range) = !_glewInit_GL_APPLE_flush_buffer_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_APPLE_flush_buffer_range */ +#ifdef GL_APPLE_pixel_buffer + CONST_CAST(GLEW_APPLE_pixel_buffer) = glewGetExtension("GL_APPLE_pixel_buffer"); +#endif /* GL_APPLE_pixel_buffer */ +#ifdef GL_APPLE_specular_vector + CONST_CAST(GLEW_APPLE_specular_vector) = glewGetExtension("GL_APPLE_specular_vector"); +#endif /* GL_APPLE_specular_vector */ +#ifdef GL_APPLE_texture_range + CONST_CAST(GLEW_APPLE_texture_range) = glewGetExtension("GL_APPLE_texture_range"); + if (glewExperimental || GLEW_APPLE_texture_range) CONST_CAST(GLEW_APPLE_texture_range) = !_glewInit_GL_APPLE_texture_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_APPLE_texture_range */ +#ifdef GL_APPLE_transform_hint + CONST_CAST(GLEW_APPLE_transform_hint) = glewGetExtension("GL_APPLE_transform_hint"); +#endif /* GL_APPLE_transform_hint */ +#ifdef GL_APPLE_vertex_array_object + CONST_CAST(GLEW_APPLE_vertex_array_object) = glewGetExtension("GL_APPLE_vertex_array_object"); + if (glewExperimental || GLEW_APPLE_vertex_array_object) CONST_CAST(GLEW_APPLE_vertex_array_object) = !_glewInit_GL_APPLE_vertex_array_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_APPLE_vertex_array_object */ +#ifdef GL_APPLE_vertex_array_range + CONST_CAST(GLEW_APPLE_vertex_array_range) = glewGetExtension("GL_APPLE_vertex_array_range"); + if (glewExperimental || GLEW_APPLE_vertex_array_range) CONST_CAST(GLEW_APPLE_vertex_array_range) = !_glewInit_GL_APPLE_vertex_array_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_APPLE_vertex_array_range */ +#ifdef GL_APPLE_ycbcr_422 + CONST_CAST(GLEW_APPLE_ycbcr_422) = glewGetExtension("GL_APPLE_ycbcr_422"); +#endif /* GL_APPLE_ycbcr_422 */ +#ifdef GL_ARB_color_buffer_float + CONST_CAST(GLEW_ARB_color_buffer_float) = glewGetExtension("GL_ARB_color_buffer_float"); + if (glewExperimental || GLEW_ARB_color_buffer_float) CONST_CAST(GLEW_ARB_color_buffer_float) = !_glewInit_GL_ARB_color_buffer_float(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_color_buffer_float */ +#ifdef GL_ARB_depth_buffer_float + CONST_CAST(GLEW_ARB_depth_buffer_float) = glewGetExtension("GL_ARB_depth_buffer_float"); +#endif /* GL_ARB_depth_buffer_float */ +#ifdef GL_ARB_depth_texture + CONST_CAST(GLEW_ARB_depth_texture) = glewGetExtension("GL_ARB_depth_texture"); +#endif /* GL_ARB_depth_texture */ +#ifdef GL_ARB_draw_buffers + CONST_CAST(GLEW_ARB_draw_buffers) = glewGetExtension("GL_ARB_draw_buffers"); + if (glewExperimental || GLEW_ARB_draw_buffers) CONST_CAST(GLEW_ARB_draw_buffers) = !_glewInit_GL_ARB_draw_buffers(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_draw_buffers */ +#ifdef GL_ARB_draw_instanced + CONST_CAST(GLEW_ARB_draw_instanced) = glewGetExtension("GL_ARB_draw_instanced"); + if (glewExperimental || GLEW_ARB_draw_instanced) CONST_CAST(GLEW_ARB_draw_instanced) = !_glewInit_GL_ARB_draw_instanced(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_draw_instanced */ +#ifdef GL_ARB_fragment_program + CONST_CAST(GLEW_ARB_fragment_program) = glewGetExtension("GL_ARB_fragment_program"); +#endif /* GL_ARB_fragment_program */ +#ifdef GL_ARB_fragment_program_shadow + CONST_CAST(GLEW_ARB_fragment_program_shadow) = glewGetExtension("GL_ARB_fragment_program_shadow"); +#endif /* GL_ARB_fragment_program_shadow */ +#ifdef GL_ARB_fragment_shader + CONST_CAST(GLEW_ARB_fragment_shader) = glewGetExtension("GL_ARB_fragment_shader"); +#endif /* GL_ARB_fragment_shader */ +#ifdef GL_ARB_framebuffer_object + CONST_CAST(GLEW_ARB_framebuffer_object) = glewGetExtension("GL_ARB_framebuffer_object"); + if (glewExperimental || GLEW_ARB_framebuffer_object) CONST_CAST(GLEW_ARB_framebuffer_object) = !_glewInit_GL_ARB_framebuffer_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_framebuffer_object */ +#ifdef GL_ARB_framebuffer_sRGB + CONST_CAST(GLEW_ARB_framebuffer_sRGB) = glewGetExtension("GL_ARB_framebuffer_sRGB"); +#endif /* GL_ARB_framebuffer_sRGB */ +#ifdef GL_ARB_geometry_shader4 + CONST_CAST(GLEW_ARB_geometry_shader4) = glewGetExtension("GL_ARB_geometry_shader4"); + if (glewExperimental || GLEW_ARB_geometry_shader4) CONST_CAST(GLEW_ARB_geometry_shader4) = !_glewInit_GL_ARB_geometry_shader4(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_geometry_shader4 */ +#ifdef GL_ARB_half_float_pixel + CONST_CAST(GLEW_ARB_half_float_pixel) = glewGetExtension("GL_ARB_half_float_pixel"); +#endif /* GL_ARB_half_float_pixel */ +#ifdef GL_ARB_half_float_vertex + CONST_CAST(GLEW_ARB_half_float_vertex) = glewGetExtension("GL_ARB_half_float_vertex"); +#endif /* GL_ARB_half_float_vertex */ +#ifdef GL_ARB_imaging + CONST_CAST(GLEW_ARB_imaging) = glewGetExtension("GL_ARB_imaging"); + if (glewExperimental || GLEW_ARB_imaging) CONST_CAST(GLEW_ARB_imaging) = !_glewInit_GL_ARB_imaging(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_imaging */ +#ifdef GL_ARB_instanced_arrays + CONST_CAST(GLEW_ARB_instanced_arrays) = glewGetExtension("GL_ARB_instanced_arrays"); + if (glewExperimental || GLEW_ARB_instanced_arrays) CONST_CAST(GLEW_ARB_instanced_arrays) = !_glewInit_GL_ARB_instanced_arrays(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_instanced_arrays */ +#ifdef GL_ARB_map_buffer_range + CONST_CAST(GLEW_ARB_map_buffer_range) = glewGetExtension("GL_ARB_map_buffer_range"); + if (glewExperimental || GLEW_ARB_map_buffer_range) CONST_CAST(GLEW_ARB_map_buffer_range) = !_glewInit_GL_ARB_map_buffer_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_map_buffer_range */ +#ifdef GL_ARB_matrix_palette + CONST_CAST(GLEW_ARB_matrix_palette) = glewGetExtension("GL_ARB_matrix_palette"); + if (glewExperimental || GLEW_ARB_matrix_palette) CONST_CAST(GLEW_ARB_matrix_palette) = !_glewInit_GL_ARB_matrix_palette(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_matrix_palette */ +#ifdef GL_ARB_multisample + CONST_CAST(GLEW_ARB_multisample) = glewGetExtension("GL_ARB_multisample"); + if (glewExperimental || GLEW_ARB_multisample) CONST_CAST(GLEW_ARB_multisample) = !_glewInit_GL_ARB_multisample(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_multisample */ +#ifdef GL_ARB_multitexture + CONST_CAST(GLEW_ARB_multitexture) = glewGetExtension("GL_ARB_multitexture"); + if (glewExperimental || GLEW_ARB_multitexture) CONST_CAST(GLEW_ARB_multitexture) = !_glewInit_GL_ARB_multitexture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_multitexture */ +#ifdef GL_ARB_occlusion_query + CONST_CAST(GLEW_ARB_occlusion_query) = glewGetExtension("GL_ARB_occlusion_query"); + if (glewExperimental || GLEW_ARB_occlusion_query) CONST_CAST(GLEW_ARB_occlusion_query) = !_glewInit_GL_ARB_occlusion_query(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_occlusion_query */ +#ifdef GL_ARB_pixel_buffer_object + CONST_CAST(GLEW_ARB_pixel_buffer_object) = glewGetExtension("GL_ARB_pixel_buffer_object"); +#endif /* GL_ARB_pixel_buffer_object */ +#ifdef GL_ARB_point_parameters + CONST_CAST(GLEW_ARB_point_parameters) = glewGetExtension("GL_ARB_point_parameters"); + if (glewExperimental || GLEW_ARB_point_parameters) CONST_CAST(GLEW_ARB_point_parameters) = !_glewInit_GL_ARB_point_parameters(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_point_parameters */ +#ifdef GL_ARB_point_sprite + CONST_CAST(GLEW_ARB_point_sprite) = glewGetExtension("GL_ARB_point_sprite"); +#endif /* GL_ARB_point_sprite */ +#ifdef GL_ARB_shader_objects + CONST_CAST(GLEW_ARB_shader_objects) = glewGetExtension("GL_ARB_shader_objects"); + if (glewExperimental || GLEW_ARB_shader_objects) CONST_CAST(GLEW_ARB_shader_objects) = !_glewInit_GL_ARB_shader_objects(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_shader_objects */ +#ifdef GL_ARB_shading_language_100 + CONST_CAST(GLEW_ARB_shading_language_100) = glewGetExtension("GL_ARB_shading_language_100"); +#endif /* GL_ARB_shading_language_100 */ +#ifdef GL_ARB_shadow + CONST_CAST(GLEW_ARB_shadow) = glewGetExtension("GL_ARB_shadow"); +#endif /* GL_ARB_shadow */ +#ifdef GL_ARB_shadow_ambient + CONST_CAST(GLEW_ARB_shadow_ambient) = glewGetExtension("GL_ARB_shadow_ambient"); +#endif /* GL_ARB_shadow_ambient */ +#ifdef GL_ARB_texture_border_clamp + CONST_CAST(GLEW_ARB_texture_border_clamp) = glewGetExtension("GL_ARB_texture_border_clamp"); +#endif /* GL_ARB_texture_border_clamp */ +#ifdef GL_ARB_texture_buffer_object + CONST_CAST(GLEW_ARB_texture_buffer_object) = glewGetExtension("GL_ARB_texture_buffer_object"); + if (glewExperimental || GLEW_ARB_texture_buffer_object) CONST_CAST(GLEW_ARB_texture_buffer_object) = !_glewInit_GL_ARB_texture_buffer_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_texture_buffer_object */ +#ifdef GL_ARB_texture_compression + CONST_CAST(GLEW_ARB_texture_compression) = glewGetExtension("GL_ARB_texture_compression"); + if (glewExperimental || GLEW_ARB_texture_compression) CONST_CAST(GLEW_ARB_texture_compression) = !_glewInit_GL_ARB_texture_compression(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_texture_compression */ +#ifdef GL_ARB_texture_compression_rgtc + CONST_CAST(GLEW_ARB_texture_compression_rgtc) = glewGetExtension("GL_ARB_texture_compression_rgtc"); +#endif /* GL_ARB_texture_compression_rgtc */ +#ifdef GL_ARB_texture_cube_map + CONST_CAST(GLEW_ARB_texture_cube_map) = glewGetExtension("GL_ARB_texture_cube_map"); +#endif /* GL_ARB_texture_cube_map */ +#ifdef GL_ARB_texture_env_add + CONST_CAST(GLEW_ARB_texture_env_add) = glewGetExtension("GL_ARB_texture_env_add"); +#endif /* GL_ARB_texture_env_add */ +#ifdef GL_ARB_texture_env_combine + CONST_CAST(GLEW_ARB_texture_env_combine) = glewGetExtension("GL_ARB_texture_env_combine"); +#endif /* GL_ARB_texture_env_combine */ +#ifdef GL_ARB_texture_env_crossbar + CONST_CAST(GLEW_ARB_texture_env_crossbar) = glewGetExtension("GL_ARB_texture_env_crossbar"); +#endif /* GL_ARB_texture_env_crossbar */ +#ifdef GL_ARB_texture_env_dot3 + CONST_CAST(GLEW_ARB_texture_env_dot3) = glewGetExtension("GL_ARB_texture_env_dot3"); +#endif /* GL_ARB_texture_env_dot3 */ +#ifdef GL_ARB_texture_float + CONST_CAST(GLEW_ARB_texture_float) = glewGetExtension("GL_ARB_texture_float"); +#endif /* GL_ARB_texture_float */ +#ifdef GL_ARB_texture_mirrored_repeat + CONST_CAST(GLEW_ARB_texture_mirrored_repeat) = glewGetExtension("GL_ARB_texture_mirrored_repeat"); +#endif /* GL_ARB_texture_mirrored_repeat */ +#ifdef GL_ARB_texture_non_power_of_two + CONST_CAST(GLEW_ARB_texture_non_power_of_two) = glewGetExtension("GL_ARB_texture_non_power_of_two"); +#endif /* GL_ARB_texture_non_power_of_two */ +#ifdef GL_ARB_texture_rectangle + CONST_CAST(GLEW_ARB_texture_rectangle) = glewGetExtension("GL_ARB_texture_rectangle"); +#endif /* GL_ARB_texture_rectangle */ +#ifdef GL_ARB_texture_rg + CONST_CAST(GLEW_ARB_texture_rg) = glewGetExtension("GL_ARB_texture_rg"); +#endif /* GL_ARB_texture_rg */ +#ifdef GL_ARB_transpose_matrix + CONST_CAST(GLEW_ARB_transpose_matrix) = glewGetExtension("GL_ARB_transpose_matrix"); + if (glewExperimental || GLEW_ARB_transpose_matrix) CONST_CAST(GLEW_ARB_transpose_matrix) = !_glewInit_GL_ARB_transpose_matrix(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_transpose_matrix */ +#ifdef GL_ARB_vertex_array_object + CONST_CAST(GLEW_ARB_vertex_array_object) = glewGetExtension("GL_ARB_vertex_array_object"); + if (glewExperimental || GLEW_ARB_vertex_array_object) CONST_CAST(GLEW_ARB_vertex_array_object) = !_glewInit_GL_ARB_vertex_array_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_vertex_array_object */ +#ifdef GL_ARB_vertex_blend + CONST_CAST(GLEW_ARB_vertex_blend) = glewGetExtension("GL_ARB_vertex_blend"); + if (glewExperimental || GLEW_ARB_vertex_blend) CONST_CAST(GLEW_ARB_vertex_blend) = !_glewInit_GL_ARB_vertex_blend(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_vertex_blend */ +#ifdef GL_ARB_vertex_buffer_object + CONST_CAST(GLEW_ARB_vertex_buffer_object) = glewGetExtension("GL_ARB_vertex_buffer_object"); + if (glewExperimental || GLEW_ARB_vertex_buffer_object) CONST_CAST(GLEW_ARB_vertex_buffer_object) = !_glewInit_GL_ARB_vertex_buffer_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_vertex_buffer_object */ +#ifdef GL_ARB_vertex_program + CONST_CAST(GLEW_ARB_vertex_program) = glewGetExtension("GL_ARB_vertex_program"); + if (glewExperimental || GLEW_ARB_vertex_program) CONST_CAST(GLEW_ARB_vertex_program) = !_glewInit_GL_ARB_vertex_program(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_vertex_program */ +#ifdef GL_ARB_vertex_shader + CONST_CAST(GLEW_ARB_vertex_shader) = glewGetExtension("GL_ARB_vertex_shader"); + if (glewExperimental || GLEW_ARB_vertex_shader) CONST_CAST(GLEW_ARB_vertex_shader) = !_glewInit_GL_ARB_vertex_shader(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_vertex_shader */ +#ifdef GL_ARB_window_pos + CONST_CAST(GLEW_ARB_window_pos) = glewGetExtension("GL_ARB_window_pos"); + if (glewExperimental || GLEW_ARB_window_pos) CONST_CAST(GLEW_ARB_window_pos) = !_glewInit_GL_ARB_window_pos(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ARB_window_pos */ +#ifdef GL_ATIX_point_sprites + CONST_CAST(GLEW_ATIX_point_sprites) = glewGetExtension("GL_ATIX_point_sprites"); +#endif /* GL_ATIX_point_sprites */ +#ifdef GL_ATIX_texture_env_combine3 + CONST_CAST(GLEW_ATIX_texture_env_combine3) = glewGetExtension("GL_ATIX_texture_env_combine3"); +#endif /* GL_ATIX_texture_env_combine3 */ +#ifdef GL_ATIX_texture_env_route + CONST_CAST(GLEW_ATIX_texture_env_route) = glewGetExtension("GL_ATIX_texture_env_route"); +#endif /* GL_ATIX_texture_env_route */ +#ifdef GL_ATIX_vertex_shader_output_point_size + CONST_CAST(GLEW_ATIX_vertex_shader_output_point_size) = glewGetExtension("GL_ATIX_vertex_shader_output_point_size"); +#endif /* GL_ATIX_vertex_shader_output_point_size */ +#ifdef GL_ATI_draw_buffers + CONST_CAST(GLEW_ATI_draw_buffers) = glewGetExtension("GL_ATI_draw_buffers"); + if (glewExperimental || GLEW_ATI_draw_buffers) CONST_CAST(GLEW_ATI_draw_buffers) = !_glewInit_GL_ATI_draw_buffers(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_draw_buffers */ +#ifdef GL_ATI_element_array + CONST_CAST(GLEW_ATI_element_array) = glewGetExtension("GL_ATI_element_array"); + if (glewExperimental || GLEW_ATI_element_array) CONST_CAST(GLEW_ATI_element_array) = !_glewInit_GL_ATI_element_array(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_element_array */ +#ifdef GL_ATI_envmap_bumpmap + CONST_CAST(GLEW_ATI_envmap_bumpmap) = glewGetExtension("GL_ATI_envmap_bumpmap"); + if (glewExperimental || GLEW_ATI_envmap_bumpmap) CONST_CAST(GLEW_ATI_envmap_bumpmap) = !_glewInit_GL_ATI_envmap_bumpmap(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_envmap_bumpmap */ +#ifdef GL_ATI_fragment_shader + CONST_CAST(GLEW_ATI_fragment_shader) = glewGetExtension("GL_ATI_fragment_shader"); + if (glewExperimental || GLEW_ATI_fragment_shader) CONST_CAST(GLEW_ATI_fragment_shader) = !_glewInit_GL_ATI_fragment_shader(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_fragment_shader */ +#ifdef GL_ATI_map_object_buffer + CONST_CAST(GLEW_ATI_map_object_buffer) = glewGetExtension("GL_ATI_map_object_buffer"); + if (glewExperimental || GLEW_ATI_map_object_buffer) CONST_CAST(GLEW_ATI_map_object_buffer) = !_glewInit_GL_ATI_map_object_buffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_map_object_buffer */ +#ifdef GL_ATI_pn_triangles + CONST_CAST(GLEW_ATI_pn_triangles) = glewGetExtension("GL_ATI_pn_triangles"); + if (glewExperimental || GLEW_ATI_pn_triangles) CONST_CAST(GLEW_ATI_pn_triangles) = !_glewInit_GL_ATI_pn_triangles(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_pn_triangles */ +#ifdef GL_ATI_separate_stencil + CONST_CAST(GLEW_ATI_separate_stencil) = glewGetExtension("GL_ATI_separate_stencil"); + if (glewExperimental || GLEW_ATI_separate_stencil) CONST_CAST(GLEW_ATI_separate_stencil) = !_glewInit_GL_ATI_separate_stencil(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_separate_stencil */ +#ifdef GL_ATI_shader_texture_lod + CONST_CAST(GLEW_ATI_shader_texture_lod) = glewGetExtension("GL_ATI_shader_texture_lod"); +#endif /* GL_ATI_shader_texture_lod */ +#ifdef GL_ATI_text_fragment_shader + CONST_CAST(GLEW_ATI_text_fragment_shader) = glewGetExtension("GL_ATI_text_fragment_shader"); +#endif /* GL_ATI_text_fragment_shader */ +#ifdef GL_ATI_texture_compression_3dc + CONST_CAST(GLEW_ATI_texture_compression_3dc) = glewGetExtension("GL_ATI_texture_compression_3dc"); +#endif /* GL_ATI_texture_compression_3dc */ +#ifdef GL_ATI_texture_env_combine3 + CONST_CAST(GLEW_ATI_texture_env_combine3) = glewGetExtension("GL_ATI_texture_env_combine3"); +#endif /* GL_ATI_texture_env_combine3 */ +#ifdef GL_ATI_texture_float + CONST_CAST(GLEW_ATI_texture_float) = glewGetExtension("GL_ATI_texture_float"); +#endif /* GL_ATI_texture_float */ +#ifdef GL_ATI_texture_mirror_once + CONST_CAST(GLEW_ATI_texture_mirror_once) = glewGetExtension("GL_ATI_texture_mirror_once"); +#endif /* GL_ATI_texture_mirror_once */ +#ifdef GL_ATI_vertex_array_object + CONST_CAST(GLEW_ATI_vertex_array_object) = glewGetExtension("GL_ATI_vertex_array_object"); + if (glewExperimental || GLEW_ATI_vertex_array_object) CONST_CAST(GLEW_ATI_vertex_array_object) = !_glewInit_GL_ATI_vertex_array_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_vertex_array_object */ +#ifdef GL_ATI_vertex_attrib_array_object + CONST_CAST(GLEW_ATI_vertex_attrib_array_object) = glewGetExtension("GL_ATI_vertex_attrib_array_object"); + if (glewExperimental || GLEW_ATI_vertex_attrib_array_object) CONST_CAST(GLEW_ATI_vertex_attrib_array_object) = !_glewInit_GL_ATI_vertex_attrib_array_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_vertex_attrib_array_object */ +#ifdef GL_ATI_vertex_streams + CONST_CAST(GLEW_ATI_vertex_streams) = glewGetExtension("GL_ATI_vertex_streams"); + if (glewExperimental || GLEW_ATI_vertex_streams) CONST_CAST(GLEW_ATI_vertex_streams) = !_glewInit_GL_ATI_vertex_streams(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_ATI_vertex_streams */ +#ifdef GL_EXT_422_pixels + CONST_CAST(GLEW_EXT_422_pixels) = glewGetExtension("GL_EXT_422_pixels"); +#endif /* GL_EXT_422_pixels */ +#ifdef GL_EXT_Cg_shader + CONST_CAST(GLEW_EXT_Cg_shader) = glewGetExtension("GL_EXT_Cg_shader"); +#endif /* GL_EXT_Cg_shader */ +#ifdef GL_EXT_abgr + CONST_CAST(GLEW_EXT_abgr) = glewGetExtension("GL_EXT_abgr"); +#endif /* GL_EXT_abgr */ +#ifdef GL_EXT_bgra + CONST_CAST(GLEW_EXT_bgra) = glewGetExtension("GL_EXT_bgra"); +#endif /* GL_EXT_bgra */ +#ifdef GL_EXT_bindable_uniform + CONST_CAST(GLEW_EXT_bindable_uniform) = glewGetExtension("GL_EXT_bindable_uniform"); + if (glewExperimental || GLEW_EXT_bindable_uniform) CONST_CAST(GLEW_EXT_bindable_uniform) = !_glewInit_GL_EXT_bindable_uniform(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_bindable_uniform */ +#ifdef GL_EXT_blend_color + CONST_CAST(GLEW_EXT_blend_color) = glewGetExtension("GL_EXT_blend_color"); + if (glewExperimental || GLEW_EXT_blend_color) CONST_CAST(GLEW_EXT_blend_color) = !_glewInit_GL_EXT_blend_color(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_blend_color */ +#ifdef GL_EXT_blend_equation_separate + CONST_CAST(GLEW_EXT_blend_equation_separate) = glewGetExtension("GL_EXT_blend_equation_separate"); + if (glewExperimental || GLEW_EXT_blend_equation_separate) CONST_CAST(GLEW_EXT_blend_equation_separate) = !_glewInit_GL_EXT_blend_equation_separate(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_blend_equation_separate */ +#ifdef GL_EXT_blend_func_separate + CONST_CAST(GLEW_EXT_blend_func_separate) = glewGetExtension("GL_EXT_blend_func_separate"); + if (glewExperimental || GLEW_EXT_blend_func_separate) CONST_CAST(GLEW_EXT_blend_func_separate) = !_glewInit_GL_EXT_blend_func_separate(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_blend_func_separate */ +#ifdef GL_EXT_blend_logic_op + CONST_CAST(GLEW_EXT_blend_logic_op) = glewGetExtension("GL_EXT_blend_logic_op"); +#endif /* GL_EXT_blend_logic_op */ +#ifdef GL_EXT_blend_minmax + CONST_CAST(GLEW_EXT_blend_minmax) = glewGetExtension("GL_EXT_blend_minmax"); + if (glewExperimental || GLEW_EXT_blend_minmax) CONST_CAST(GLEW_EXT_blend_minmax) = !_glewInit_GL_EXT_blend_minmax(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_blend_minmax */ +#ifdef GL_EXT_blend_subtract + CONST_CAST(GLEW_EXT_blend_subtract) = glewGetExtension("GL_EXT_blend_subtract"); +#endif /* GL_EXT_blend_subtract */ +#ifdef GL_EXT_clip_volume_hint + CONST_CAST(GLEW_EXT_clip_volume_hint) = glewGetExtension("GL_EXT_clip_volume_hint"); +#endif /* GL_EXT_clip_volume_hint */ +#ifdef GL_EXT_cmyka + CONST_CAST(GLEW_EXT_cmyka) = glewGetExtension("GL_EXT_cmyka"); +#endif /* GL_EXT_cmyka */ +#ifdef GL_EXT_color_subtable + CONST_CAST(GLEW_EXT_color_subtable) = glewGetExtension("GL_EXT_color_subtable"); + if (glewExperimental || GLEW_EXT_color_subtable) CONST_CAST(GLEW_EXT_color_subtable) = !_glewInit_GL_EXT_color_subtable(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_color_subtable */ +#ifdef GL_EXT_compiled_vertex_array + CONST_CAST(GLEW_EXT_compiled_vertex_array) = glewGetExtension("GL_EXT_compiled_vertex_array"); + if (glewExperimental || GLEW_EXT_compiled_vertex_array) CONST_CAST(GLEW_EXT_compiled_vertex_array) = !_glewInit_GL_EXT_compiled_vertex_array(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_compiled_vertex_array */ +#ifdef GL_EXT_convolution + CONST_CAST(GLEW_EXT_convolution) = glewGetExtension("GL_EXT_convolution"); + if (glewExperimental || GLEW_EXT_convolution) CONST_CAST(GLEW_EXT_convolution) = !_glewInit_GL_EXT_convolution(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_convolution */ +#ifdef GL_EXT_coordinate_frame + CONST_CAST(GLEW_EXT_coordinate_frame) = glewGetExtension("GL_EXT_coordinate_frame"); + if (glewExperimental || GLEW_EXT_coordinate_frame) CONST_CAST(GLEW_EXT_coordinate_frame) = !_glewInit_GL_EXT_coordinate_frame(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_coordinate_frame */ +#ifdef GL_EXT_copy_texture + CONST_CAST(GLEW_EXT_copy_texture) = glewGetExtension("GL_EXT_copy_texture"); + if (glewExperimental || GLEW_EXT_copy_texture) CONST_CAST(GLEW_EXT_copy_texture) = !_glewInit_GL_EXT_copy_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_copy_texture */ +#ifdef GL_EXT_cull_vertex + CONST_CAST(GLEW_EXT_cull_vertex) = glewGetExtension("GL_EXT_cull_vertex"); + if (glewExperimental || GLEW_EXT_cull_vertex) CONST_CAST(GLEW_EXT_cull_vertex) = !_glewInit_GL_EXT_cull_vertex(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_cull_vertex */ +#ifdef GL_EXT_depth_bounds_test + CONST_CAST(GLEW_EXT_depth_bounds_test) = glewGetExtension("GL_EXT_depth_bounds_test"); + if (glewExperimental || GLEW_EXT_depth_bounds_test) CONST_CAST(GLEW_EXT_depth_bounds_test) = !_glewInit_GL_EXT_depth_bounds_test(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_depth_bounds_test */ +#ifdef GL_EXT_direct_state_access + CONST_CAST(GLEW_EXT_direct_state_access) = glewGetExtension("GL_EXT_direct_state_access"); + if (glewExperimental || GLEW_EXT_direct_state_access) CONST_CAST(GLEW_EXT_direct_state_access) = !_glewInit_GL_EXT_direct_state_access(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_direct_state_access */ +#ifdef GL_EXT_draw_buffers2 + CONST_CAST(GLEW_EXT_draw_buffers2) = glewGetExtension("GL_EXT_draw_buffers2"); + if (glewExperimental || GLEW_EXT_draw_buffers2) CONST_CAST(GLEW_EXT_draw_buffers2) = !_glewInit_GL_EXT_draw_buffers2(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_draw_buffers2 */ +#ifdef GL_EXT_draw_instanced + CONST_CAST(GLEW_EXT_draw_instanced) = glewGetExtension("GL_EXT_draw_instanced"); + if (glewExperimental || GLEW_EXT_draw_instanced) CONST_CAST(GLEW_EXT_draw_instanced) = !_glewInit_GL_EXT_draw_instanced(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_draw_instanced */ +#ifdef GL_EXT_draw_range_elements + CONST_CAST(GLEW_EXT_draw_range_elements) = glewGetExtension("GL_EXT_draw_range_elements"); + if (glewExperimental || GLEW_EXT_draw_range_elements) CONST_CAST(GLEW_EXT_draw_range_elements) = !_glewInit_GL_EXT_draw_range_elements(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_draw_range_elements */ +#ifdef GL_EXT_fog_coord + CONST_CAST(GLEW_EXT_fog_coord) = glewGetExtension("GL_EXT_fog_coord"); + if (glewExperimental || GLEW_EXT_fog_coord) CONST_CAST(GLEW_EXT_fog_coord) = !_glewInit_GL_EXT_fog_coord(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_fog_coord */ +#ifdef GL_EXT_fragment_lighting + CONST_CAST(GLEW_EXT_fragment_lighting) = glewGetExtension("GL_EXT_fragment_lighting"); + if (glewExperimental || GLEW_EXT_fragment_lighting) CONST_CAST(GLEW_EXT_fragment_lighting) = !_glewInit_GL_EXT_fragment_lighting(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_fragment_lighting */ +#ifdef GL_EXT_framebuffer_blit + CONST_CAST(GLEW_EXT_framebuffer_blit) = glewGetExtension("GL_EXT_framebuffer_blit"); + if (glewExperimental || GLEW_EXT_framebuffer_blit) CONST_CAST(GLEW_EXT_framebuffer_blit) = !_glewInit_GL_EXT_framebuffer_blit(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_framebuffer_blit */ +#ifdef GL_EXT_framebuffer_multisample + CONST_CAST(GLEW_EXT_framebuffer_multisample) = glewGetExtension("GL_EXT_framebuffer_multisample"); + if (glewExperimental || GLEW_EXT_framebuffer_multisample) CONST_CAST(GLEW_EXT_framebuffer_multisample) = !_glewInit_GL_EXT_framebuffer_multisample(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_framebuffer_multisample */ +#ifdef GL_EXT_framebuffer_object + CONST_CAST(GLEW_EXT_framebuffer_object) = glewGetExtension("GL_EXT_framebuffer_object"); + if (glewExperimental || GLEW_EXT_framebuffer_object) CONST_CAST(GLEW_EXT_framebuffer_object) = !_glewInit_GL_EXT_framebuffer_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_framebuffer_object */ +#ifdef GL_EXT_framebuffer_sRGB + CONST_CAST(GLEW_EXT_framebuffer_sRGB) = glewGetExtension("GL_EXT_framebuffer_sRGB"); +#endif /* GL_EXT_framebuffer_sRGB */ +#ifdef GL_EXT_geometry_shader4 + CONST_CAST(GLEW_EXT_geometry_shader4) = glewGetExtension("GL_EXT_geometry_shader4"); + if (glewExperimental || GLEW_EXT_geometry_shader4) CONST_CAST(GLEW_EXT_geometry_shader4) = !_glewInit_GL_EXT_geometry_shader4(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_geometry_shader4 */ +#ifdef GL_EXT_gpu_program_parameters + CONST_CAST(GLEW_EXT_gpu_program_parameters) = glewGetExtension("GL_EXT_gpu_program_parameters"); + if (glewExperimental || GLEW_EXT_gpu_program_parameters) CONST_CAST(GLEW_EXT_gpu_program_parameters) = !_glewInit_GL_EXT_gpu_program_parameters(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_gpu_program_parameters */ +#ifdef GL_EXT_gpu_shader4 + CONST_CAST(GLEW_EXT_gpu_shader4) = glewGetExtension("GL_EXT_gpu_shader4"); + if (glewExperimental || GLEW_EXT_gpu_shader4) CONST_CAST(GLEW_EXT_gpu_shader4) = !_glewInit_GL_EXT_gpu_shader4(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_gpu_shader4 */ +#ifdef GL_EXT_histogram + CONST_CAST(GLEW_EXT_histogram) = glewGetExtension("GL_EXT_histogram"); + if (glewExperimental || GLEW_EXT_histogram) CONST_CAST(GLEW_EXT_histogram) = !_glewInit_GL_EXT_histogram(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_histogram */ +#ifdef GL_EXT_index_array_formats + CONST_CAST(GLEW_EXT_index_array_formats) = glewGetExtension("GL_EXT_index_array_formats"); +#endif /* GL_EXT_index_array_formats */ +#ifdef GL_EXT_index_func + CONST_CAST(GLEW_EXT_index_func) = glewGetExtension("GL_EXT_index_func"); + if (glewExperimental || GLEW_EXT_index_func) CONST_CAST(GLEW_EXT_index_func) = !_glewInit_GL_EXT_index_func(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_index_func */ +#ifdef GL_EXT_index_material + CONST_CAST(GLEW_EXT_index_material) = glewGetExtension("GL_EXT_index_material"); + if (glewExperimental || GLEW_EXT_index_material) CONST_CAST(GLEW_EXT_index_material) = !_glewInit_GL_EXT_index_material(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_index_material */ +#ifdef GL_EXT_index_texture + CONST_CAST(GLEW_EXT_index_texture) = glewGetExtension("GL_EXT_index_texture"); +#endif /* GL_EXT_index_texture */ +#ifdef GL_EXT_light_texture + CONST_CAST(GLEW_EXT_light_texture) = glewGetExtension("GL_EXT_light_texture"); + if (glewExperimental || GLEW_EXT_light_texture) CONST_CAST(GLEW_EXT_light_texture) = !_glewInit_GL_EXT_light_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_light_texture */ +#ifdef GL_EXT_misc_attribute + CONST_CAST(GLEW_EXT_misc_attribute) = glewGetExtension("GL_EXT_misc_attribute"); +#endif /* GL_EXT_misc_attribute */ +#ifdef GL_EXT_multi_draw_arrays + CONST_CAST(GLEW_EXT_multi_draw_arrays) = glewGetExtension("GL_EXT_multi_draw_arrays"); + if (glewExperimental || GLEW_EXT_multi_draw_arrays) CONST_CAST(GLEW_EXT_multi_draw_arrays) = !_glewInit_GL_EXT_multi_draw_arrays(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_multi_draw_arrays */ +#ifdef GL_EXT_multisample + CONST_CAST(GLEW_EXT_multisample) = glewGetExtension("GL_EXT_multisample"); + if (glewExperimental || GLEW_EXT_multisample) CONST_CAST(GLEW_EXT_multisample) = !_glewInit_GL_EXT_multisample(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_multisample */ +#ifdef GL_EXT_packed_depth_stencil + CONST_CAST(GLEW_EXT_packed_depth_stencil) = glewGetExtension("GL_EXT_packed_depth_stencil"); +#endif /* GL_EXT_packed_depth_stencil */ +#ifdef GL_EXT_packed_float + CONST_CAST(GLEW_EXT_packed_float) = glewGetExtension("GL_EXT_packed_float"); +#endif /* GL_EXT_packed_float */ +#ifdef GL_EXT_packed_pixels + CONST_CAST(GLEW_EXT_packed_pixels) = glewGetExtension("GL_EXT_packed_pixels"); +#endif /* GL_EXT_packed_pixels */ +#ifdef GL_EXT_paletted_texture + CONST_CAST(GLEW_EXT_paletted_texture) = glewGetExtension("GL_EXT_paletted_texture"); + if (glewExperimental || GLEW_EXT_paletted_texture) CONST_CAST(GLEW_EXT_paletted_texture) = !_glewInit_GL_EXT_paletted_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_paletted_texture */ +#ifdef GL_EXT_pixel_buffer_object + CONST_CAST(GLEW_EXT_pixel_buffer_object) = glewGetExtension("GL_EXT_pixel_buffer_object"); +#endif /* GL_EXT_pixel_buffer_object */ +#ifdef GL_EXT_pixel_transform + CONST_CAST(GLEW_EXT_pixel_transform) = glewGetExtension("GL_EXT_pixel_transform"); + if (glewExperimental || GLEW_EXT_pixel_transform) CONST_CAST(GLEW_EXT_pixel_transform) = !_glewInit_GL_EXT_pixel_transform(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_pixel_transform */ +#ifdef GL_EXT_pixel_transform_color_table + CONST_CAST(GLEW_EXT_pixel_transform_color_table) = glewGetExtension("GL_EXT_pixel_transform_color_table"); +#endif /* GL_EXT_pixel_transform_color_table */ +#ifdef GL_EXT_point_parameters + CONST_CAST(GLEW_EXT_point_parameters) = glewGetExtension("GL_EXT_point_parameters"); + if (glewExperimental || GLEW_EXT_point_parameters) CONST_CAST(GLEW_EXT_point_parameters) = !_glewInit_GL_EXT_point_parameters(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_point_parameters */ +#ifdef GL_EXT_polygon_offset + CONST_CAST(GLEW_EXT_polygon_offset) = glewGetExtension("GL_EXT_polygon_offset"); + if (glewExperimental || GLEW_EXT_polygon_offset) CONST_CAST(GLEW_EXT_polygon_offset) = !_glewInit_GL_EXT_polygon_offset(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_polygon_offset */ +#ifdef GL_EXT_rescale_normal + CONST_CAST(GLEW_EXT_rescale_normal) = glewGetExtension("GL_EXT_rescale_normal"); +#endif /* GL_EXT_rescale_normal */ +#ifdef GL_EXT_scene_marker + CONST_CAST(GLEW_EXT_scene_marker) = glewGetExtension("GL_EXT_scene_marker"); + if (glewExperimental || GLEW_EXT_scene_marker) CONST_CAST(GLEW_EXT_scene_marker) = !_glewInit_GL_EXT_scene_marker(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_scene_marker */ +#ifdef GL_EXT_secondary_color + CONST_CAST(GLEW_EXT_secondary_color) = glewGetExtension("GL_EXT_secondary_color"); + if (glewExperimental || GLEW_EXT_secondary_color) CONST_CAST(GLEW_EXT_secondary_color) = !_glewInit_GL_EXT_secondary_color(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_secondary_color */ +#ifdef GL_EXT_separate_specular_color + CONST_CAST(GLEW_EXT_separate_specular_color) = glewGetExtension("GL_EXT_separate_specular_color"); +#endif /* GL_EXT_separate_specular_color */ +#ifdef GL_EXT_shadow_funcs + CONST_CAST(GLEW_EXT_shadow_funcs) = glewGetExtension("GL_EXT_shadow_funcs"); +#endif /* GL_EXT_shadow_funcs */ +#ifdef GL_EXT_shared_texture_palette + CONST_CAST(GLEW_EXT_shared_texture_palette) = glewGetExtension("GL_EXT_shared_texture_palette"); +#endif /* GL_EXT_shared_texture_palette */ +#ifdef GL_EXT_stencil_clear_tag + CONST_CAST(GLEW_EXT_stencil_clear_tag) = glewGetExtension("GL_EXT_stencil_clear_tag"); +#endif /* GL_EXT_stencil_clear_tag */ +#ifdef GL_EXT_stencil_two_side + CONST_CAST(GLEW_EXT_stencil_two_side) = glewGetExtension("GL_EXT_stencil_two_side"); + if (glewExperimental || GLEW_EXT_stencil_two_side) CONST_CAST(GLEW_EXT_stencil_two_side) = !_glewInit_GL_EXT_stencil_two_side(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_stencil_two_side */ +#ifdef GL_EXT_stencil_wrap + CONST_CAST(GLEW_EXT_stencil_wrap) = glewGetExtension("GL_EXT_stencil_wrap"); +#endif /* GL_EXT_stencil_wrap */ +#ifdef GL_EXT_subtexture + CONST_CAST(GLEW_EXT_subtexture) = glewGetExtension("GL_EXT_subtexture"); + if (glewExperimental || GLEW_EXT_subtexture) CONST_CAST(GLEW_EXT_subtexture) = !_glewInit_GL_EXT_subtexture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_subtexture */ +#ifdef GL_EXT_texture + CONST_CAST(GLEW_EXT_texture) = glewGetExtension("GL_EXT_texture"); +#endif /* GL_EXT_texture */ +#ifdef GL_EXT_texture3D + CONST_CAST(GLEW_EXT_texture3D) = glewGetExtension("GL_EXT_texture3D"); + if (glewExperimental || GLEW_EXT_texture3D) CONST_CAST(GLEW_EXT_texture3D) = !_glewInit_GL_EXT_texture3D(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_texture3D */ +#ifdef GL_EXT_texture_array + CONST_CAST(GLEW_EXT_texture_array) = glewGetExtension("GL_EXT_texture_array"); +#endif /* GL_EXT_texture_array */ +#ifdef GL_EXT_texture_buffer_object + CONST_CAST(GLEW_EXT_texture_buffer_object) = glewGetExtension("GL_EXT_texture_buffer_object"); + if (glewExperimental || GLEW_EXT_texture_buffer_object) CONST_CAST(GLEW_EXT_texture_buffer_object) = !_glewInit_GL_EXT_texture_buffer_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_texture_buffer_object */ +#ifdef GL_EXT_texture_compression_dxt1 + CONST_CAST(GLEW_EXT_texture_compression_dxt1) = glewGetExtension("GL_EXT_texture_compression_dxt1"); +#endif /* GL_EXT_texture_compression_dxt1 */ +#ifdef GL_EXT_texture_compression_latc + CONST_CAST(GLEW_EXT_texture_compression_latc) = glewGetExtension("GL_EXT_texture_compression_latc"); +#endif /* GL_EXT_texture_compression_latc */ +#ifdef GL_EXT_texture_compression_rgtc + CONST_CAST(GLEW_EXT_texture_compression_rgtc) = glewGetExtension("GL_EXT_texture_compression_rgtc"); +#endif /* GL_EXT_texture_compression_rgtc */ +#ifdef GL_EXT_texture_compression_s3tc + CONST_CAST(GLEW_EXT_texture_compression_s3tc) = glewGetExtension("GL_EXT_texture_compression_s3tc"); +#endif /* GL_EXT_texture_compression_s3tc */ +#ifdef GL_EXT_texture_cube_map + CONST_CAST(GLEW_EXT_texture_cube_map) = glewGetExtension("GL_EXT_texture_cube_map"); +#endif /* GL_EXT_texture_cube_map */ +#ifdef GL_EXT_texture_edge_clamp + CONST_CAST(GLEW_EXT_texture_edge_clamp) = glewGetExtension("GL_EXT_texture_edge_clamp"); +#endif /* GL_EXT_texture_edge_clamp */ +#ifdef GL_EXT_texture_env + CONST_CAST(GLEW_EXT_texture_env) = glewGetExtension("GL_EXT_texture_env"); +#endif /* GL_EXT_texture_env */ +#ifdef GL_EXT_texture_env_add + CONST_CAST(GLEW_EXT_texture_env_add) = glewGetExtension("GL_EXT_texture_env_add"); +#endif /* GL_EXT_texture_env_add */ +#ifdef GL_EXT_texture_env_combine + CONST_CAST(GLEW_EXT_texture_env_combine) = glewGetExtension("GL_EXT_texture_env_combine"); +#endif /* GL_EXT_texture_env_combine */ +#ifdef GL_EXT_texture_env_dot3 + CONST_CAST(GLEW_EXT_texture_env_dot3) = glewGetExtension("GL_EXT_texture_env_dot3"); +#endif /* GL_EXT_texture_env_dot3 */ +#ifdef GL_EXT_texture_filter_anisotropic + CONST_CAST(GLEW_EXT_texture_filter_anisotropic) = glewGetExtension("GL_EXT_texture_filter_anisotropic"); +#endif /* GL_EXT_texture_filter_anisotropic */ +#ifdef GL_EXT_texture_integer + CONST_CAST(GLEW_EXT_texture_integer) = glewGetExtension("GL_EXT_texture_integer"); + if (glewExperimental || GLEW_EXT_texture_integer) CONST_CAST(GLEW_EXT_texture_integer) = !_glewInit_GL_EXT_texture_integer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_texture_integer */ +#ifdef GL_EXT_texture_lod_bias + CONST_CAST(GLEW_EXT_texture_lod_bias) = glewGetExtension("GL_EXT_texture_lod_bias"); +#endif /* GL_EXT_texture_lod_bias */ +#ifdef GL_EXT_texture_mirror_clamp + CONST_CAST(GLEW_EXT_texture_mirror_clamp) = glewGetExtension("GL_EXT_texture_mirror_clamp"); +#endif /* GL_EXT_texture_mirror_clamp */ +#ifdef GL_EXT_texture_object + CONST_CAST(GLEW_EXT_texture_object) = glewGetExtension("GL_EXT_texture_object"); + if (glewExperimental || GLEW_EXT_texture_object) CONST_CAST(GLEW_EXT_texture_object) = !_glewInit_GL_EXT_texture_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_texture_object */ +#ifdef GL_EXT_texture_perturb_normal + CONST_CAST(GLEW_EXT_texture_perturb_normal) = glewGetExtension("GL_EXT_texture_perturb_normal"); + if (glewExperimental || GLEW_EXT_texture_perturb_normal) CONST_CAST(GLEW_EXT_texture_perturb_normal) = !_glewInit_GL_EXT_texture_perturb_normal(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_texture_perturb_normal */ +#ifdef GL_EXT_texture_rectangle + CONST_CAST(GLEW_EXT_texture_rectangle) = glewGetExtension("GL_EXT_texture_rectangle"); +#endif /* GL_EXT_texture_rectangle */ +#ifdef GL_EXT_texture_sRGB + CONST_CAST(GLEW_EXT_texture_sRGB) = glewGetExtension("GL_EXT_texture_sRGB"); +#endif /* GL_EXT_texture_sRGB */ +#ifdef GL_EXT_texture_shared_exponent + CONST_CAST(GLEW_EXT_texture_shared_exponent) = glewGetExtension("GL_EXT_texture_shared_exponent"); +#endif /* GL_EXT_texture_shared_exponent */ +#ifdef GL_EXT_texture_swizzle + CONST_CAST(GLEW_EXT_texture_swizzle) = glewGetExtension("GL_EXT_texture_swizzle"); +#endif /* GL_EXT_texture_swizzle */ +#ifdef GL_EXT_timer_query + CONST_CAST(GLEW_EXT_timer_query) = glewGetExtension("GL_EXT_timer_query"); + if (glewExperimental || GLEW_EXT_timer_query) CONST_CAST(GLEW_EXT_timer_query) = !_glewInit_GL_EXT_timer_query(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_timer_query */ +#ifdef GL_EXT_transform_feedback + CONST_CAST(GLEW_EXT_transform_feedback) = glewGetExtension("GL_EXT_transform_feedback"); + if (glewExperimental || GLEW_EXT_transform_feedback) CONST_CAST(GLEW_EXT_transform_feedback) = !_glewInit_GL_EXT_transform_feedback(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_transform_feedback */ +#ifdef GL_EXT_vertex_array + CONST_CAST(GLEW_EXT_vertex_array) = glewGetExtension("GL_EXT_vertex_array"); + if (glewExperimental || GLEW_EXT_vertex_array) CONST_CAST(GLEW_EXT_vertex_array) = !_glewInit_GL_EXT_vertex_array(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_vertex_array */ +#ifdef GL_EXT_vertex_array_bgra + CONST_CAST(GLEW_EXT_vertex_array_bgra) = glewGetExtension("GL_EXT_vertex_array_bgra"); +#endif /* GL_EXT_vertex_array_bgra */ +#ifdef GL_EXT_vertex_shader + CONST_CAST(GLEW_EXT_vertex_shader) = glewGetExtension("GL_EXT_vertex_shader"); + if (glewExperimental || GLEW_EXT_vertex_shader) CONST_CAST(GLEW_EXT_vertex_shader) = !_glewInit_GL_EXT_vertex_shader(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_vertex_shader */ +#ifdef GL_EXT_vertex_weighting + CONST_CAST(GLEW_EXT_vertex_weighting) = glewGetExtension("GL_EXT_vertex_weighting"); + if (glewExperimental || GLEW_EXT_vertex_weighting) CONST_CAST(GLEW_EXT_vertex_weighting) = !_glewInit_GL_EXT_vertex_weighting(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_EXT_vertex_weighting */ +#ifdef GL_GREMEDY_frame_terminator + CONST_CAST(GLEW_GREMEDY_frame_terminator) = glewGetExtension("GL_GREMEDY_frame_terminator"); + if (glewExperimental || GLEW_GREMEDY_frame_terminator) CONST_CAST(GLEW_GREMEDY_frame_terminator) = !_glewInit_GL_GREMEDY_frame_terminator(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_GREMEDY_frame_terminator */ +#ifdef GL_GREMEDY_string_marker + CONST_CAST(GLEW_GREMEDY_string_marker) = glewGetExtension("GL_GREMEDY_string_marker"); + if (glewExperimental || GLEW_GREMEDY_string_marker) CONST_CAST(GLEW_GREMEDY_string_marker) = !_glewInit_GL_GREMEDY_string_marker(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_GREMEDY_string_marker */ +#ifdef GL_HP_convolution_border_modes + CONST_CAST(GLEW_HP_convolution_border_modes) = glewGetExtension("GL_HP_convolution_border_modes"); +#endif /* GL_HP_convolution_border_modes */ +#ifdef GL_HP_image_transform + CONST_CAST(GLEW_HP_image_transform) = glewGetExtension("GL_HP_image_transform"); + if (glewExperimental || GLEW_HP_image_transform) CONST_CAST(GLEW_HP_image_transform) = !_glewInit_GL_HP_image_transform(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_HP_image_transform */ +#ifdef GL_HP_occlusion_test + CONST_CAST(GLEW_HP_occlusion_test) = glewGetExtension("GL_HP_occlusion_test"); +#endif /* GL_HP_occlusion_test */ +#ifdef GL_HP_texture_lighting + CONST_CAST(GLEW_HP_texture_lighting) = glewGetExtension("GL_HP_texture_lighting"); +#endif /* GL_HP_texture_lighting */ +#ifdef GL_IBM_cull_vertex + CONST_CAST(GLEW_IBM_cull_vertex) = glewGetExtension("GL_IBM_cull_vertex"); +#endif /* GL_IBM_cull_vertex */ +#ifdef GL_IBM_multimode_draw_arrays + CONST_CAST(GLEW_IBM_multimode_draw_arrays) = glewGetExtension("GL_IBM_multimode_draw_arrays"); + if (glewExperimental || GLEW_IBM_multimode_draw_arrays) CONST_CAST(GLEW_IBM_multimode_draw_arrays) = !_glewInit_GL_IBM_multimode_draw_arrays(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_IBM_multimode_draw_arrays */ +#ifdef GL_IBM_rasterpos_clip + CONST_CAST(GLEW_IBM_rasterpos_clip) = glewGetExtension("GL_IBM_rasterpos_clip"); +#endif /* GL_IBM_rasterpos_clip */ +#ifdef GL_IBM_static_data + CONST_CAST(GLEW_IBM_static_data) = glewGetExtension("GL_IBM_static_data"); +#endif /* GL_IBM_static_data */ +#ifdef GL_IBM_texture_mirrored_repeat + CONST_CAST(GLEW_IBM_texture_mirrored_repeat) = glewGetExtension("GL_IBM_texture_mirrored_repeat"); +#endif /* GL_IBM_texture_mirrored_repeat */ +#ifdef GL_IBM_vertex_array_lists + CONST_CAST(GLEW_IBM_vertex_array_lists) = glewGetExtension("GL_IBM_vertex_array_lists"); + if (glewExperimental || GLEW_IBM_vertex_array_lists) CONST_CAST(GLEW_IBM_vertex_array_lists) = !_glewInit_GL_IBM_vertex_array_lists(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_IBM_vertex_array_lists */ +#ifdef GL_INGR_color_clamp + CONST_CAST(GLEW_INGR_color_clamp) = glewGetExtension("GL_INGR_color_clamp"); +#endif /* GL_INGR_color_clamp */ +#ifdef GL_INGR_interlace_read + CONST_CAST(GLEW_INGR_interlace_read) = glewGetExtension("GL_INGR_interlace_read"); +#endif /* GL_INGR_interlace_read */ +#ifdef GL_INTEL_parallel_arrays + CONST_CAST(GLEW_INTEL_parallel_arrays) = glewGetExtension("GL_INTEL_parallel_arrays"); + if (glewExperimental || GLEW_INTEL_parallel_arrays) CONST_CAST(GLEW_INTEL_parallel_arrays) = !_glewInit_GL_INTEL_parallel_arrays(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_INTEL_parallel_arrays */ +#ifdef GL_INTEL_texture_scissor + CONST_CAST(GLEW_INTEL_texture_scissor) = glewGetExtension("GL_INTEL_texture_scissor"); + if (glewExperimental || GLEW_INTEL_texture_scissor) CONST_CAST(GLEW_INTEL_texture_scissor) = !_glewInit_GL_INTEL_texture_scissor(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_INTEL_texture_scissor */ +#ifdef GL_KTX_buffer_region + CONST_CAST(GLEW_KTX_buffer_region) = glewGetExtension("GL_KTX_buffer_region"); + if (glewExperimental || GLEW_KTX_buffer_region) CONST_CAST(GLEW_KTX_buffer_region) = !_glewInit_GL_KTX_buffer_region(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_KTX_buffer_region */ +#ifdef GL_MESAX_texture_stack + CONST_CAST(GLEW_MESAX_texture_stack) = glewGetExtension("GL_MESAX_texture_stack"); +#endif /* GL_MESAX_texture_stack */ +#ifdef GL_MESA_pack_invert + CONST_CAST(GLEW_MESA_pack_invert) = glewGetExtension("GL_MESA_pack_invert"); +#endif /* GL_MESA_pack_invert */ +#ifdef GL_MESA_resize_buffers + CONST_CAST(GLEW_MESA_resize_buffers) = glewGetExtension("GL_MESA_resize_buffers"); + if (glewExperimental || GLEW_MESA_resize_buffers) CONST_CAST(GLEW_MESA_resize_buffers) = !_glewInit_GL_MESA_resize_buffers(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_MESA_resize_buffers */ +#ifdef GL_MESA_window_pos + CONST_CAST(GLEW_MESA_window_pos) = glewGetExtension("GL_MESA_window_pos"); + if (glewExperimental || GLEW_MESA_window_pos) CONST_CAST(GLEW_MESA_window_pos) = !_glewInit_GL_MESA_window_pos(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_MESA_window_pos */ +#ifdef GL_MESA_ycbcr_texture + CONST_CAST(GLEW_MESA_ycbcr_texture) = glewGetExtension("GL_MESA_ycbcr_texture"); +#endif /* GL_MESA_ycbcr_texture */ +#ifdef GL_NV_blend_square + CONST_CAST(GLEW_NV_blend_square) = glewGetExtension("GL_NV_blend_square"); +#endif /* GL_NV_blend_square */ +#ifdef GL_NV_conditional_render + CONST_CAST(GLEW_NV_conditional_render) = glewGetExtension("GL_NV_conditional_render"); + if (glewExperimental || GLEW_NV_conditional_render) CONST_CAST(GLEW_NV_conditional_render) = !_glewInit_GL_NV_conditional_render(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_conditional_render */ +#ifdef GL_NV_copy_depth_to_color + CONST_CAST(GLEW_NV_copy_depth_to_color) = glewGetExtension("GL_NV_copy_depth_to_color"); +#endif /* GL_NV_copy_depth_to_color */ +#ifdef GL_NV_depth_buffer_float + CONST_CAST(GLEW_NV_depth_buffer_float) = glewGetExtension("GL_NV_depth_buffer_float"); + if (glewExperimental || GLEW_NV_depth_buffer_float) CONST_CAST(GLEW_NV_depth_buffer_float) = !_glewInit_GL_NV_depth_buffer_float(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_depth_buffer_float */ +#ifdef GL_NV_depth_clamp + CONST_CAST(GLEW_NV_depth_clamp) = glewGetExtension("GL_NV_depth_clamp"); +#endif /* GL_NV_depth_clamp */ +#ifdef GL_NV_depth_range_unclamped + CONST_CAST(GLEW_NV_depth_range_unclamped) = glewGetExtension("GL_NV_depth_range_unclamped"); +#endif /* GL_NV_depth_range_unclamped */ +#ifdef GL_NV_evaluators + CONST_CAST(GLEW_NV_evaluators) = glewGetExtension("GL_NV_evaluators"); + if (glewExperimental || GLEW_NV_evaluators) CONST_CAST(GLEW_NV_evaluators) = !_glewInit_GL_NV_evaluators(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_evaluators */ +#ifdef GL_NV_explicit_multisample + CONST_CAST(GLEW_NV_explicit_multisample) = glewGetExtension("GL_NV_explicit_multisample"); + if (glewExperimental || GLEW_NV_explicit_multisample) CONST_CAST(GLEW_NV_explicit_multisample) = !_glewInit_GL_NV_explicit_multisample(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_explicit_multisample */ +#ifdef GL_NV_fence + CONST_CAST(GLEW_NV_fence) = glewGetExtension("GL_NV_fence"); + if (glewExperimental || GLEW_NV_fence) CONST_CAST(GLEW_NV_fence) = !_glewInit_GL_NV_fence(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_fence */ +#ifdef GL_NV_float_buffer + CONST_CAST(GLEW_NV_float_buffer) = glewGetExtension("GL_NV_float_buffer"); +#endif /* GL_NV_float_buffer */ +#ifdef GL_NV_fog_distance + CONST_CAST(GLEW_NV_fog_distance) = glewGetExtension("GL_NV_fog_distance"); +#endif /* GL_NV_fog_distance */ +#ifdef GL_NV_fragment_program + CONST_CAST(GLEW_NV_fragment_program) = glewGetExtension("GL_NV_fragment_program"); + if (glewExperimental || GLEW_NV_fragment_program) CONST_CAST(GLEW_NV_fragment_program) = !_glewInit_GL_NV_fragment_program(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_fragment_program */ +#ifdef GL_NV_fragment_program2 + CONST_CAST(GLEW_NV_fragment_program2) = glewGetExtension("GL_NV_fragment_program2"); +#endif /* GL_NV_fragment_program2 */ +#ifdef GL_NV_fragment_program4 + CONST_CAST(GLEW_NV_fragment_program4) = glewGetExtension("GL_NV_fragment_program4"); +#endif /* GL_NV_fragment_program4 */ +#ifdef GL_NV_fragment_program_option + CONST_CAST(GLEW_NV_fragment_program_option) = glewGetExtension("GL_NV_fragment_program_option"); +#endif /* GL_NV_fragment_program_option */ +#ifdef GL_NV_framebuffer_multisample_coverage + CONST_CAST(GLEW_NV_framebuffer_multisample_coverage) = glewGetExtension("GL_NV_framebuffer_multisample_coverage"); + if (glewExperimental || GLEW_NV_framebuffer_multisample_coverage) CONST_CAST(GLEW_NV_framebuffer_multisample_coverage) = !_glewInit_GL_NV_framebuffer_multisample_coverage(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_framebuffer_multisample_coverage */ +#ifdef GL_NV_geometry_program4 + CONST_CAST(GLEW_NV_geometry_program4) = glewGetExtension("GL_NV_geometry_program4"); + if (glewExperimental || GLEW_NV_geometry_program4) CONST_CAST(GLEW_NV_geometry_program4) = !_glewInit_GL_NV_geometry_program4(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_geometry_program4 */ +#ifdef GL_NV_geometry_shader4 + CONST_CAST(GLEW_NV_geometry_shader4) = glewGetExtension("GL_NV_geometry_shader4"); +#endif /* GL_NV_geometry_shader4 */ +#ifdef GL_NV_gpu_program4 + CONST_CAST(GLEW_NV_gpu_program4) = glewGetExtension("GL_NV_gpu_program4"); + if (glewExperimental || GLEW_NV_gpu_program4) CONST_CAST(GLEW_NV_gpu_program4) = !_glewInit_GL_NV_gpu_program4(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_gpu_program4 */ +#ifdef GL_NV_half_float + CONST_CAST(GLEW_NV_half_float) = glewGetExtension("GL_NV_half_float"); + if (glewExperimental || GLEW_NV_half_float) CONST_CAST(GLEW_NV_half_float) = !_glewInit_GL_NV_half_float(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_half_float */ +#ifdef GL_NV_light_max_exponent + CONST_CAST(GLEW_NV_light_max_exponent) = glewGetExtension("GL_NV_light_max_exponent"); +#endif /* GL_NV_light_max_exponent */ +#ifdef GL_NV_multisample_filter_hint + CONST_CAST(GLEW_NV_multisample_filter_hint) = glewGetExtension("GL_NV_multisample_filter_hint"); +#endif /* GL_NV_multisample_filter_hint */ +#ifdef GL_NV_occlusion_query + CONST_CAST(GLEW_NV_occlusion_query) = glewGetExtension("GL_NV_occlusion_query"); + if (glewExperimental || GLEW_NV_occlusion_query) CONST_CAST(GLEW_NV_occlusion_query) = !_glewInit_GL_NV_occlusion_query(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_occlusion_query */ +#ifdef GL_NV_packed_depth_stencil + CONST_CAST(GLEW_NV_packed_depth_stencil) = glewGetExtension("GL_NV_packed_depth_stencil"); +#endif /* GL_NV_packed_depth_stencil */ +#ifdef GL_NV_parameter_buffer_object + CONST_CAST(GLEW_NV_parameter_buffer_object) = glewGetExtension("GL_NV_parameter_buffer_object"); + if (glewExperimental || GLEW_NV_parameter_buffer_object) CONST_CAST(GLEW_NV_parameter_buffer_object) = !_glewInit_GL_NV_parameter_buffer_object(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_parameter_buffer_object */ +#ifdef GL_NV_pixel_data_range + CONST_CAST(GLEW_NV_pixel_data_range) = glewGetExtension("GL_NV_pixel_data_range"); + if (glewExperimental || GLEW_NV_pixel_data_range) CONST_CAST(GLEW_NV_pixel_data_range) = !_glewInit_GL_NV_pixel_data_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_pixel_data_range */ +#ifdef GL_NV_point_sprite + CONST_CAST(GLEW_NV_point_sprite) = glewGetExtension("GL_NV_point_sprite"); + if (glewExperimental || GLEW_NV_point_sprite) CONST_CAST(GLEW_NV_point_sprite) = !_glewInit_GL_NV_point_sprite(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_point_sprite */ +#ifdef GL_NV_present_video + CONST_CAST(GLEW_NV_present_video) = glewGetExtension("GL_NV_present_video"); + if (glewExperimental || GLEW_NV_present_video) CONST_CAST(GLEW_NV_present_video) = !_glewInit_GL_NV_present_video(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_present_video */ +#ifdef GL_NV_primitive_restart + CONST_CAST(GLEW_NV_primitive_restart) = glewGetExtension("GL_NV_primitive_restart"); + if (glewExperimental || GLEW_NV_primitive_restart) CONST_CAST(GLEW_NV_primitive_restart) = !_glewInit_GL_NV_primitive_restart(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_primitive_restart */ +#ifdef GL_NV_register_combiners + CONST_CAST(GLEW_NV_register_combiners) = glewGetExtension("GL_NV_register_combiners"); + if (glewExperimental || GLEW_NV_register_combiners) CONST_CAST(GLEW_NV_register_combiners) = !_glewInit_GL_NV_register_combiners(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_register_combiners */ +#ifdef GL_NV_register_combiners2 + CONST_CAST(GLEW_NV_register_combiners2) = glewGetExtension("GL_NV_register_combiners2"); + if (glewExperimental || GLEW_NV_register_combiners2) CONST_CAST(GLEW_NV_register_combiners2) = !_glewInit_GL_NV_register_combiners2(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_register_combiners2 */ +#ifdef GL_NV_texgen_emboss + CONST_CAST(GLEW_NV_texgen_emboss) = glewGetExtension("GL_NV_texgen_emboss"); +#endif /* GL_NV_texgen_emboss */ +#ifdef GL_NV_texgen_reflection + CONST_CAST(GLEW_NV_texgen_reflection) = glewGetExtension("GL_NV_texgen_reflection"); +#endif /* GL_NV_texgen_reflection */ +#ifdef GL_NV_texture_compression_vtc + CONST_CAST(GLEW_NV_texture_compression_vtc) = glewGetExtension("GL_NV_texture_compression_vtc"); +#endif /* GL_NV_texture_compression_vtc */ +#ifdef GL_NV_texture_env_combine4 + CONST_CAST(GLEW_NV_texture_env_combine4) = glewGetExtension("GL_NV_texture_env_combine4"); +#endif /* GL_NV_texture_env_combine4 */ +#ifdef GL_NV_texture_expand_normal + CONST_CAST(GLEW_NV_texture_expand_normal) = glewGetExtension("GL_NV_texture_expand_normal"); +#endif /* GL_NV_texture_expand_normal */ +#ifdef GL_NV_texture_rectangle + CONST_CAST(GLEW_NV_texture_rectangle) = glewGetExtension("GL_NV_texture_rectangle"); +#endif /* GL_NV_texture_rectangle */ +#ifdef GL_NV_texture_shader + CONST_CAST(GLEW_NV_texture_shader) = glewGetExtension("GL_NV_texture_shader"); +#endif /* GL_NV_texture_shader */ +#ifdef GL_NV_texture_shader2 + CONST_CAST(GLEW_NV_texture_shader2) = glewGetExtension("GL_NV_texture_shader2"); +#endif /* GL_NV_texture_shader2 */ +#ifdef GL_NV_texture_shader3 + CONST_CAST(GLEW_NV_texture_shader3) = glewGetExtension("GL_NV_texture_shader3"); +#endif /* GL_NV_texture_shader3 */ +#ifdef GL_NV_transform_feedback + CONST_CAST(GLEW_NV_transform_feedback) = glewGetExtension("GL_NV_transform_feedback"); + if (glewExperimental || GLEW_NV_transform_feedback) CONST_CAST(GLEW_NV_transform_feedback) = !_glewInit_GL_NV_transform_feedback(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_transform_feedback */ +#ifdef GL_NV_vertex_array_range + CONST_CAST(GLEW_NV_vertex_array_range) = glewGetExtension("GL_NV_vertex_array_range"); + if (glewExperimental || GLEW_NV_vertex_array_range) CONST_CAST(GLEW_NV_vertex_array_range) = !_glewInit_GL_NV_vertex_array_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_vertex_array_range */ +#ifdef GL_NV_vertex_array_range2 + CONST_CAST(GLEW_NV_vertex_array_range2) = glewGetExtension("GL_NV_vertex_array_range2"); +#endif /* GL_NV_vertex_array_range2 */ +#ifdef GL_NV_vertex_program + CONST_CAST(GLEW_NV_vertex_program) = glewGetExtension("GL_NV_vertex_program"); + if (glewExperimental || GLEW_NV_vertex_program) CONST_CAST(GLEW_NV_vertex_program) = !_glewInit_GL_NV_vertex_program(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_NV_vertex_program */ +#ifdef GL_NV_vertex_program1_1 + CONST_CAST(GLEW_NV_vertex_program1_1) = glewGetExtension("GL_NV_vertex_program1_1"); +#endif /* GL_NV_vertex_program1_1 */ +#ifdef GL_NV_vertex_program2 + CONST_CAST(GLEW_NV_vertex_program2) = glewGetExtension("GL_NV_vertex_program2"); +#endif /* GL_NV_vertex_program2 */ +#ifdef GL_NV_vertex_program2_option + CONST_CAST(GLEW_NV_vertex_program2_option) = glewGetExtension("GL_NV_vertex_program2_option"); +#endif /* GL_NV_vertex_program2_option */ +#ifdef GL_NV_vertex_program3 + CONST_CAST(GLEW_NV_vertex_program3) = glewGetExtension("GL_NV_vertex_program3"); +#endif /* GL_NV_vertex_program3 */ +#ifdef GL_NV_vertex_program4 + CONST_CAST(GLEW_NV_vertex_program4) = glewGetExtension("GL_NV_vertex_program4"); +#endif /* GL_NV_vertex_program4 */ +#ifdef GL_OES_byte_coordinates + CONST_CAST(GLEW_OES_byte_coordinates) = glewGetExtension("GL_OES_byte_coordinates"); +#endif /* GL_OES_byte_coordinates */ +#ifdef GL_OES_compressed_paletted_texture + CONST_CAST(GLEW_OES_compressed_paletted_texture) = glewGetExtension("GL_OES_compressed_paletted_texture"); +#endif /* GL_OES_compressed_paletted_texture */ +#ifdef GL_OES_read_format + CONST_CAST(GLEW_OES_read_format) = glewGetExtension("GL_OES_read_format"); +#endif /* GL_OES_read_format */ +#ifdef GL_OES_single_precision + CONST_CAST(GLEW_OES_single_precision) = glewGetExtension("GL_OES_single_precision"); + if (glewExperimental || GLEW_OES_single_precision) CONST_CAST(GLEW_OES_single_precision) = !_glewInit_GL_OES_single_precision(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_OES_single_precision */ +#ifdef GL_OML_interlace + CONST_CAST(GLEW_OML_interlace) = glewGetExtension("GL_OML_interlace"); +#endif /* GL_OML_interlace */ +#ifdef GL_OML_resample + CONST_CAST(GLEW_OML_resample) = glewGetExtension("GL_OML_resample"); +#endif /* GL_OML_resample */ +#ifdef GL_OML_subsample + CONST_CAST(GLEW_OML_subsample) = glewGetExtension("GL_OML_subsample"); +#endif /* GL_OML_subsample */ +#ifdef GL_PGI_misc_hints + CONST_CAST(GLEW_PGI_misc_hints) = glewGetExtension("GL_PGI_misc_hints"); +#endif /* GL_PGI_misc_hints */ +#ifdef GL_PGI_vertex_hints + CONST_CAST(GLEW_PGI_vertex_hints) = glewGetExtension("GL_PGI_vertex_hints"); +#endif /* GL_PGI_vertex_hints */ +#ifdef GL_REND_screen_coordinates + CONST_CAST(GLEW_REND_screen_coordinates) = glewGetExtension("GL_REND_screen_coordinates"); +#endif /* GL_REND_screen_coordinates */ +#ifdef GL_S3_s3tc + CONST_CAST(GLEW_S3_s3tc) = glewGetExtension("GL_S3_s3tc"); +#endif /* GL_S3_s3tc */ +#ifdef GL_SGIS_color_range + CONST_CAST(GLEW_SGIS_color_range) = glewGetExtension("GL_SGIS_color_range"); +#endif /* GL_SGIS_color_range */ +#ifdef GL_SGIS_detail_texture + CONST_CAST(GLEW_SGIS_detail_texture) = glewGetExtension("GL_SGIS_detail_texture"); + if (glewExperimental || GLEW_SGIS_detail_texture) CONST_CAST(GLEW_SGIS_detail_texture) = !_glewInit_GL_SGIS_detail_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIS_detail_texture */ +#ifdef GL_SGIS_fog_function + CONST_CAST(GLEW_SGIS_fog_function) = glewGetExtension("GL_SGIS_fog_function"); + if (glewExperimental || GLEW_SGIS_fog_function) CONST_CAST(GLEW_SGIS_fog_function) = !_glewInit_GL_SGIS_fog_function(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIS_fog_function */ +#ifdef GL_SGIS_generate_mipmap + CONST_CAST(GLEW_SGIS_generate_mipmap) = glewGetExtension("GL_SGIS_generate_mipmap"); +#endif /* GL_SGIS_generate_mipmap */ +#ifdef GL_SGIS_multisample + CONST_CAST(GLEW_SGIS_multisample) = glewGetExtension("GL_SGIS_multisample"); + if (glewExperimental || GLEW_SGIS_multisample) CONST_CAST(GLEW_SGIS_multisample) = !_glewInit_GL_SGIS_multisample(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIS_multisample */ +#ifdef GL_SGIS_pixel_texture + CONST_CAST(GLEW_SGIS_pixel_texture) = glewGetExtension("GL_SGIS_pixel_texture"); +#endif /* GL_SGIS_pixel_texture */ +#ifdef GL_SGIS_point_line_texgen + CONST_CAST(GLEW_SGIS_point_line_texgen) = glewGetExtension("GL_SGIS_point_line_texgen"); +#endif /* GL_SGIS_point_line_texgen */ +#ifdef GL_SGIS_sharpen_texture + CONST_CAST(GLEW_SGIS_sharpen_texture) = glewGetExtension("GL_SGIS_sharpen_texture"); + if (glewExperimental || GLEW_SGIS_sharpen_texture) CONST_CAST(GLEW_SGIS_sharpen_texture) = !_glewInit_GL_SGIS_sharpen_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIS_sharpen_texture */ +#ifdef GL_SGIS_texture4D + CONST_CAST(GLEW_SGIS_texture4D) = glewGetExtension("GL_SGIS_texture4D"); + if (glewExperimental || GLEW_SGIS_texture4D) CONST_CAST(GLEW_SGIS_texture4D) = !_glewInit_GL_SGIS_texture4D(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIS_texture4D */ +#ifdef GL_SGIS_texture_border_clamp + CONST_CAST(GLEW_SGIS_texture_border_clamp) = glewGetExtension("GL_SGIS_texture_border_clamp"); +#endif /* GL_SGIS_texture_border_clamp */ +#ifdef GL_SGIS_texture_edge_clamp + CONST_CAST(GLEW_SGIS_texture_edge_clamp) = glewGetExtension("GL_SGIS_texture_edge_clamp"); +#endif /* GL_SGIS_texture_edge_clamp */ +#ifdef GL_SGIS_texture_filter4 + CONST_CAST(GLEW_SGIS_texture_filter4) = glewGetExtension("GL_SGIS_texture_filter4"); + if (glewExperimental || GLEW_SGIS_texture_filter4) CONST_CAST(GLEW_SGIS_texture_filter4) = !_glewInit_GL_SGIS_texture_filter4(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIS_texture_filter4 */ +#ifdef GL_SGIS_texture_lod + CONST_CAST(GLEW_SGIS_texture_lod) = glewGetExtension("GL_SGIS_texture_lod"); +#endif /* GL_SGIS_texture_lod */ +#ifdef GL_SGIS_texture_select + CONST_CAST(GLEW_SGIS_texture_select) = glewGetExtension("GL_SGIS_texture_select"); +#endif /* GL_SGIS_texture_select */ +#ifdef GL_SGIX_async + CONST_CAST(GLEW_SGIX_async) = glewGetExtension("GL_SGIX_async"); + if (glewExperimental || GLEW_SGIX_async) CONST_CAST(GLEW_SGIX_async) = !_glewInit_GL_SGIX_async(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_async */ +#ifdef GL_SGIX_async_histogram + CONST_CAST(GLEW_SGIX_async_histogram) = glewGetExtension("GL_SGIX_async_histogram"); +#endif /* GL_SGIX_async_histogram */ +#ifdef GL_SGIX_async_pixel + CONST_CAST(GLEW_SGIX_async_pixel) = glewGetExtension("GL_SGIX_async_pixel"); +#endif /* GL_SGIX_async_pixel */ +#ifdef GL_SGIX_blend_alpha_minmax + CONST_CAST(GLEW_SGIX_blend_alpha_minmax) = glewGetExtension("GL_SGIX_blend_alpha_minmax"); +#endif /* GL_SGIX_blend_alpha_minmax */ +#ifdef GL_SGIX_clipmap + CONST_CAST(GLEW_SGIX_clipmap) = glewGetExtension("GL_SGIX_clipmap"); +#endif /* GL_SGIX_clipmap */ +#ifdef GL_SGIX_convolution_accuracy + CONST_CAST(GLEW_SGIX_convolution_accuracy) = glewGetExtension("GL_SGIX_convolution_accuracy"); +#endif /* GL_SGIX_convolution_accuracy */ +#ifdef GL_SGIX_depth_texture + CONST_CAST(GLEW_SGIX_depth_texture) = glewGetExtension("GL_SGIX_depth_texture"); +#endif /* GL_SGIX_depth_texture */ +#ifdef GL_SGIX_flush_raster + CONST_CAST(GLEW_SGIX_flush_raster) = glewGetExtension("GL_SGIX_flush_raster"); + if (glewExperimental || GLEW_SGIX_flush_raster) CONST_CAST(GLEW_SGIX_flush_raster) = !_glewInit_GL_SGIX_flush_raster(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_flush_raster */ +#ifdef GL_SGIX_fog_offset + CONST_CAST(GLEW_SGIX_fog_offset) = glewGetExtension("GL_SGIX_fog_offset"); +#endif /* GL_SGIX_fog_offset */ +#ifdef GL_SGIX_fog_texture + CONST_CAST(GLEW_SGIX_fog_texture) = glewGetExtension("GL_SGIX_fog_texture"); + if (glewExperimental || GLEW_SGIX_fog_texture) CONST_CAST(GLEW_SGIX_fog_texture) = !_glewInit_GL_SGIX_fog_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_fog_texture */ +#ifdef GL_SGIX_fragment_specular_lighting + CONST_CAST(GLEW_SGIX_fragment_specular_lighting) = glewGetExtension("GL_SGIX_fragment_specular_lighting"); + if (glewExperimental || GLEW_SGIX_fragment_specular_lighting) CONST_CAST(GLEW_SGIX_fragment_specular_lighting) = !_glewInit_GL_SGIX_fragment_specular_lighting(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_fragment_specular_lighting */ +#ifdef GL_SGIX_framezoom + CONST_CAST(GLEW_SGIX_framezoom) = glewGetExtension("GL_SGIX_framezoom"); + if (glewExperimental || GLEW_SGIX_framezoom) CONST_CAST(GLEW_SGIX_framezoom) = !_glewInit_GL_SGIX_framezoom(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_framezoom */ +#ifdef GL_SGIX_interlace + CONST_CAST(GLEW_SGIX_interlace) = glewGetExtension("GL_SGIX_interlace"); +#endif /* GL_SGIX_interlace */ +#ifdef GL_SGIX_ir_instrument1 + CONST_CAST(GLEW_SGIX_ir_instrument1) = glewGetExtension("GL_SGIX_ir_instrument1"); +#endif /* GL_SGIX_ir_instrument1 */ +#ifdef GL_SGIX_list_priority + CONST_CAST(GLEW_SGIX_list_priority) = glewGetExtension("GL_SGIX_list_priority"); +#endif /* GL_SGIX_list_priority */ +#ifdef GL_SGIX_pixel_texture + CONST_CAST(GLEW_SGIX_pixel_texture) = glewGetExtension("GL_SGIX_pixel_texture"); + if (glewExperimental || GLEW_SGIX_pixel_texture) CONST_CAST(GLEW_SGIX_pixel_texture) = !_glewInit_GL_SGIX_pixel_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_pixel_texture */ +#ifdef GL_SGIX_pixel_texture_bits + CONST_CAST(GLEW_SGIX_pixel_texture_bits) = glewGetExtension("GL_SGIX_pixel_texture_bits"); +#endif /* GL_SGIX_pixel_texture_bits */ +#ifdef GL_SGIX_reference_plane + CONST_CAST(GLEW_SGIX_reference_plane) = glewGetExtension("GL_SGIX_reference_plane"); + if (glewExperimental || GLEW_SGIX_reference_plane) CONST_CAST(GLEW_SGIX_reference_plane) = !_glewInit_GL_SGIX_reference_plane(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_reference_plane */ +#ifdef GL_SGIX_resample + CONST_CAST(GLEW_SGIX_resample) = glewGetExtension("GL_SGIX_resample"); +#endif /* GL_SGIX_resample */ +#ifdef GL_SGIX_shadow + CONST_CAST(GLEW_SGIX_shadow) = glewGetExtension("GL_SGIX_shadow"); +#endif /* GL_SGIX_shadow */ +#ifdef GL_SGIX_shadow_ambient + CONST_CAST(GLEW_SGIX_shadow_ambient) = glewGetExtension("GL_SGIX_shadow_ambient"); +#endif /* GL_SGIX_shadow_ambient */ +#ifdef GL_SGIX_sprite + CONST_CAST(GLEW_SGIX_sprite) = glewGetExtension("GL_SGIX_sprite"); + if (glewExperimental || GLEW_SGIX_sprite) CONST_CAST(GLEW_SGIX_sprite) = !_glewInit_GL_SGIX_sprite(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_sprite */ +#ifdef GL_SGIX_tag_sample_buffer + CONST_CAST(GLEW_SGIX_tag_sample_buffer) = glewGetExtension("GL_SGIX_tag_sample_buffer"); + if (glewExperimental || GLEW_SGIX_tag_sample_buffer) CONST_CAST(GLEW_SGIX_tag_sample_buffer) = !_glewInit_GL_SGIX_tag_sample_buffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGIX_tag_sample_buffer */ +#ifdef GL_SGIX_texture_add_env + CONST_CAST(GLEW_SGIX_texture_add_env) = glewGetExtension("GL_SGIX_texture_add_env"); +#endif /* GL_SGIX_texture_add_env */ +#ifdef GL_SGIX_texture_coordinate_clamp + CONST_CAST(GLEW_SGIX_texture_coordinate_clamp) = glewGetExtension("GL_SGIX_texture_coordinate_clamp"); +#endif /* GL_SGIX_texture_coordinate_clamp */ +#ifdef GL_SGIX_texture_lod_bias + CONST_CAST(GLEW_SGIX_texture_lod_bias) = glewGetExtension("GL_SGIX_texture_lod_bias"); +#endif /* GL_SGIX_texture_lod_bias */ +#ifdef GL_SGIX_texture_multi_buffer + CONST_CAST(GLEW_SGIX_texture_multi_buffer) = glewGetExtension("GL_SGIX_texture_multi_buffer"); +#endif /* GL_SGIX_texture_multi_buffer */ +#ifdef GL_SGIX_texture_range + CONST_CAST(GLEW_SGIX_texture_range) = glewGetExtension("GL_SGIX_texture_range"); +#endif /* GL_SGIX_texture_range */ +#ifdef GL_SGIX_texture_scale_bias + CONST_CAST(GLEW_SGIX_texture_scale_bias) = glewGetExtension("GL_SGIX_texture_scale_bias"); +#endif /* GL_SGIX_texture_scale_bias */ +#ifdef GL_SGIX_vertex_preclip + CONST_CAST(GLEW_SGIX_vertex_preclip) = glewGetExtension("GL_SGIX_vertex_preclip"); +#endif /* GL_SGIX_vertex_preclip */ +#ifdef GL_SGIX_vertex_preclip_hint + CONST_CAST(GLEW_SGIX_vertex_preclip_hint) = glewGetExtension("GL_SGIX_vertex_preclip_hint"); +#endif /* GL_SGIX_vertex_preclip_hint */ +#ifdef GL_SGIX_ycrcb + CONST_CAST(GLEW_SGIX_ycrcb) = glewGetExtension("GL_SGIX_ycrcb"); +#endif /* GL_SGIX_ycrcb */ +#ifdef GL_SGI_color_matrix + CONST_CAST(GLEW_SGI_color_matrix) = glewGetExtension("GL_SGI_color_matrix"); +#endif /* GL_SGI_color_matrix */ +#ifdef GL_SGI_color_table + CONST_CAST(GLEW_SGI_color_table) = glewGetExtension("GL_SGI_color_table"); + if (glewExperimental || GLEW_SGI_color_table) CONST_CAST(GLEW_SGI_color_table) = !_glewInit_GL_SGI_color_table(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SGI_color_table */ +#ifdef GL_SGI_texture_color_table + CONST_CAST(GLEW_SGI_texture_color_table) = glewGetExtension("GL_SGI_texture_color_table"); +#endif /* GL_SGI_texture_color_table */ +#ifdef GL_SUNX_constant_data + CONST_CAST(GLEW_SUNX_constant_data) = glewGetExtension("GL_SUNX_constant_data"); + if (glewExperimental || GLEW_SUNX_constant_data) CONST_CAST(GLEW_SUNX_constant_data) = !_glewInit_GL_SUNX_constant_data(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SUNX_constant_data */ +#ifdef GL_SUN_convolution_border_modes + CONST_CAST(GLEW_SUN_convolution_border_modes) = glewGetExtension("GL_SUN_convolution_border_modes"); +#endif /* GL_SUN_convolution_border_modes */ +#ifdef GL_SUN_global_alpha + CONST_CAST(GLEW_SUN_global_alpha) = glewGetExtension("GL_SUN_global_alpha"); + if (glewExperimental || GLEW_SUN_global_alpha) CONST_CAST(GLEW_SUN_global_alpha) = !_glewInit_GL_SUN_global_alpha(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SUN_global_alpha */ +#ifdef GL_SUN_mesh_array + CONST_CAST(GLEW_SUN_mesh_array) = glewGetExtension("GL_SUN_mesh_array"); +#endif /* GL_SUN_mesh_array */ +#ifdef GL_SUN_read_video_pixels + CONST_CAST(GLEW_SUN_read_video_pixels) = glewGetExtension("GL_SUN_read_video_pixels"); + if (glewExperimental || GLEW_SUN_read_video_pixels) CONST_CAST(GLEW_SUN_read_video_pixels) = !_glewInit_GL_SUN_read_video_pixels(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SUN_read_video_pixels */ +#ifdef GL_SUN_slice_accum + CONST_CAST(GLEW_SUN_slice_accum) = glewGetExtension("GL_SUN_slice_accum"); +#endif /* GL_SUN_slice_accum */ +#ifdef GL_SUN_triangle_list + CONST_CAST(GLEW_SUN_triangle_list) = glewGetExtension("GL_SUN_triangle_list"); + if (glewExperimental || GLEW_SUN_triangle_list) CONST_CAST(GLEW_SUN_triangle_list) = !_glewInit_GL_SUN_triangle_list(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SUN_triangle_list */ +#ifdef GL_SUN_vertex + CONST_CAST(GLEW_SUN_vertex) = glewGetExtension("GL_SUN_vertex"); + if (glewExperimental || GLEW_SUN_vertex) CONST_CAST(GLEW_SUN_vertex) = !_glewInit_GL_SUN_vertex(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_SUN_vertex */ +#ifdef GL_WIN_phong_shading + CONST_CAST(GLEW_WIN_phong_shading) = glewGetExtension("GL_WIN_phong_shading"); +#endif /* GL_WIN_phong_shading */ +#ifdef GL_WIN_specular_fog + CONST_CAST(GLEW_WIN_specular_fog) = glewGetExtension("GL_WIN_specular_fog"); +#endif /* GL_WIN_specular_fog */ +#ifdef GL_WIN_swap_hint + CONST_CAST(GLEW_WIN_swap_hint) = glewGetExtension("GL_WIN_swap_hint"); + if (glewExperimental || GLEW_WIN_swap_hint) CONST_CAST(GLEW_WIN_swap_hint) = !_glewInit_GL_WIN_swap_hint(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GL_WIN_swap_hint */ + + return GLEW_OK; +} + + +#if defined(_WIN32) + +#if !defined(GLEW_MX) + +PFNWGLSETSTEREOEMITTERSTATE3DLPROC __wglewSetStereoEmitterState3DL = NULL; + +PFNWGLCREATEBUFFERREGIONARBPROC __wglewCreateBufferRegionARB = NULL; +PFNWGLDELETEBUFFERREGIONARBPROC __wglewDeleteBufferRegionARB = NULL; +PFNWGLRESTOREBUFFERREGIONARBPROC __wglewRestoreBufferRegionARB = NULL; +PFNWGLSAVEBUFFERREGIONARBPROC __wglewSaveBufferRegionARB = NULL; + +PFNWGLCREATECONTEXTATTRIBSARBPROC __wglewCreateContextAttribsARB = NULL; + +PFNWGLGETEXTENSIONSSTRINGARBPROC __wglewGetExtensionsStringARB = NULL; + +PFNWGLGETCURRENTREADDCARBPROC __wglewGetCurrentReadDCARB = NULL; +PFNWGLMAKECONTEXTCURRENTARBPROC __wglewMakeContextCurrentARB = NULL; + +PFNWGLCREATEPBUFFERARBPROC __wglewCreatePbufferARB = NULL; +PFNWGLDESTROYPBUFFERARBPROC __wglewDestroyPbufferARB = NULL; +PFNWGLGETPBUFFERDCARBPROC __wglewGetPbufferDCARB = NULL; +PFNWGLQUERYPBUFFERARBPROC __wglewQueryPbufferARB = NULL; +PFNWGLRELEASEPBUFFERDCARBPROC __wglewReleasePbufferDCARB = NULL; + +PFNWGLCHOOSEPIXELFORMATARBPROC __wglewChoosePixelFormatARB = NULL; +PFNWGLGETPIXELFORMATATTRIBFVARBPROC __wglewGetPixelFormatAttribfvARB = NULL; +PFNWGLGETPIXELFORMATATTRIBIVARBPROC __wglewGetPixelFormatAttribivARB = NULL; + +PFNWGLBINDTEXIMAGEARBPROC __wglewBindTexImageARB = NULL; +PFNWGLRELEASETEXIMAGEARBPROC __wglewReleaseTexImageARB = NULL; +PFNWGLSETPBUFFERATTRIBARBPROC __wglewSetPbufferAttribARB = NULL; + +PFNWGLBINDDISPLAYCOLORTABLEEXTPROC __wglewBindDisplayColorTableEXT = NULL; +PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC __wglewCreateDisplayColorTableEXT = NULL; +PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC __wglewDestroyDisplayColorTableEXT = NULL; +PFNWGLLOADDISPLAYCOLORTABLEEXTPROC __wglewLoadDisplayColorTableEXT = NULL; + +PFNWGLGETEXTENSIONSSTRINGEXTPROC __wglewGetExtensionsStringEXT = NULL; + +PFNWGLGETCURRENTREADDCEXTPROC __wglewGetCurrentReadDCEXT = NULL; +PFNWGLMAKECONTEXTCURRENTEXTPROC __wglewMakeContextCurrentEXT = NULL; + +PFNWGLCREATEPBUFFEREXTPROC __wglewCreatePbufferEXT = NULL; +PFNWGLDESTROYPBUFFEREXTPROC __wglewDestroyPbufferEXT = NULL; +PFNWGLGETPBUFFERDCEXTPROC __wglewGetPbufferDCEXT = NULL; +PFNWGLQUERYPBUFFEREXTPROC __wglewQueryPbufferEXT = NULL; +PFNWGLRELEASEPBUFFERDCEXTPROC __wglewReleasePbufferDCEXT = NULL; + +PFNWGLCHOOSEPIXELFORMATEXTPROC __wglewChoosePixelFormatEXT = NULL; +PFNWGLGETPIXELFORMATATTRIBFVEXTPROC __wglewGetPixelFormatAttribfvEXT = NULL; +PFNWGLGETPIXELFORMATATTRIBIVEXTPROC __wglewGetPixelFormatAttribivEXT = NULL; + +PFNWGLGETSWAPINTERVALEXTPROC __wglewGetSwapIntervalEXT = NULL; +PFNWGLSWAPINTERVALEXTPROC __wglewSwapIntervalEXT = NULL; + +PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC __wglewGetDigitalVideoParametersI3D = NULL; +PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC __wglewSetDigitalVideoParametersI3D = NULL; + +PFNWGLGETGAMMATABLEI3DPROC __wglewGetGammaTableI3D = NULL; +PFNWGLGETGAMMATABLEPARAMETERSI3DPROC __wglewGetGammaTableParametersI3D = NULL; +PFNWGLSETGAMMATABLEI3DPROC __wglewSetGammaTableI3D = NULL; +PFNWGLSETGAMMATABLEPARAMETERSI3DPROC __wglewSetGammaTableParametersI3D = NULL; + +PFNWGLDISABLEGENLOCKI3DPROC __wglewDisableGenlockI3D = NULL; +PFNWGLENABLEGENLOCKI3DPROC __wglewEnableGenlockI3D = NULL; +PFNWGLGENLOCKSAMPLERATEI3DPROC __wglewGenlockSampleRateI3D = NULL; +PFNWGLGENLOCKSOURCEDELAYI3DPROC __wglewGenlockSourceDelayI3D = NULL; +PFNWGLGENLOCKSOURCEEDGEI3DPROC __wglewGenlockSourceEdgeI3D = NULL; +PFNWGLGENLOCKSOURCEI3DPROC __wglewGenlockSourceI3D = NULL; +PFNWGLGETGENLOCKSAMPLERATEI3DPROC __wglewGetGenlockSampleRateI3D = NULL; +PFNWGLGETGENLOCKSOURCEDELAYI3DPROC __wglewGetGenlockSourceDelayI3D = NULL; +PFNWGLGETGENLOCKSOURCEEDGEI3DPROC __wglewGetGenlockSourceEdgeI3D = NULL; +PFNWGLGETGENLOCKSOURCEI3DPROC __wglewGetGenlockSourceI3D = NULL; +PFNWGLISENABLEDGENLOCKI3DPROC __wglewIsEnabledGenlockI3D = NULL; +PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC __wglewQueryGenlockMaxSourceDelayI3D = NULL; + +PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC __wglewAssociateImageBufferEventsI3D = NULL; +PFNWGLCREATEIMAGEBUFFERI3DPROC __wglewCreateImageBufferI3D = NULL; +PFNWGLDESTROYIMAGEBUFFERI3DPROC __wglewDestroyImageBufferI3D = NULL; +PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC __wglewReleaseImageBufferEventsI3D = NULL; + +PFNWGLDISABLEFRAMELOCKI3DPROC __wglewDisableFrameLockI3D = NULL; +PFNWGLENABLEFRAMELOCKI3DPROC __wglewEnableFrameLockI3D = NULL; +PFNWGLISENABLEDFRAMELOCKI3DPROC __wglewIsEnabledFrameLockI3D = NULL; +PFNWGLQUERYFRAMELOCKMASTERI3DPROC __wglewQueryFrameLockMasterI3D = NULL; + +PFNWGLBEGINFRAMETRACKINGI3DPROC __wglewBeginFrameTrackingI3D = NULL; +PFNWGLENDFRAMETRACKINGI3DPROC __wglewEndFrameTrackingI3D = NULL; +PFNWGLGETFRAMEUSAGEI3DPROC __wglewGetFrameUsageI3D = NULL; +PFNWGLQUERYFRAMETRACKINGI3DPROC __wglewQueryFrameTrackingI3D = NULL; + +PFNWGLCREATEAFFINITYDCNVPROC __wglewCreateAffinityDCNV = NULL; +PFNWGLDELETEDCNVPROC __wglewDeleteDCNV = NULL; +PFNWGLENUMGPUDEVICESNVPROC __wglewEnumGpuDevicesNV = NULL; +PFNWGLENUMGPUSFROMAFFINITYDCNVPROC __wglewEnumGpusFromAffinityDCNV = NULL; +PFNWGLENUMGPUSNVPROC __wglewEnumGpusNV = NULL; + +PFNWGLBINDVIDEODEVICENVPROC __wglewBindVideoDeviceNV = NULL; +PFNWGLENUMERATEVIDEODEVICESNVPROC __wglewEnumerateVideoDevicesNV = NULL; +PFNWGLQUERYCURRENTCONTEXTNVPROC __wglewQueryCurrentContextNV = NULL; + +PFNWGLBINDSWAPBARRIERNVPROC __wglewBindSwapBarrierNV = NULL; +PFNWGLJOINSWAPGROUPNVPROC __wglewJoinSwapGroupNV = NULL; +PFNWGLQUERYFRAMECOUNTNVPROC __wglewQueryFrameCountNV = NULL; +PFNWGLQUERYMAXSWAPGROUPSNVPROC __wglewQueryMaxSwapGroupsNV = NULL; +PFNWGLQUERYSWAPGROUPNVPROC __wglewQuerySwapGroupNV = NULL; +PFNWGLRESETFRAMECOUNTNVPROC __wglewResetFrameCountNV = NULL; + +PFNWGLALLOCATEMEMORYNVPROC __wglewAllocateMemoryNV = NULL; +PFNWGLFREEMEMORYNVPROC __wglewFreeMemoryNV = NULL; + +PFNWGLBINDVIDEOIMAGENVPROC __wglewBindVideoImageNV = NULL; +PFNWGLGETVIDEODEVICENVPROC __wglewGetVideoDeviceNV = NULL; +PFNWGLGETVIDEOINFONVPROC __wglewGetVideoInfoNV = NULL; +PFNWGLRELEASEVIDEODEVICENVPROC __wglewReleaseVideoDeviceNV = NULL; +PFNWGLRELEASEVIDEOIMAGENVPROC __wglewReleaseVideoImageNV = NULL; +PFNWGLSENDPBUFFERTOVIDEONVPROC __wglewSendPbufferToVideoNV = NULL; + +PFNWGLGETMSCRATEOMLPROC __wglewGetMscRateOML = NULL; +PFNWGLGETSYNCVALUESOMLPROC __wglewGetSyncValuesOML = NULL; +PFNWGLSWAPBUFFERSMSCOMLPROC __wglewSwapBuffersMscOML = NULL; +PFNWGLSWAPLAYERBUFFERSMSCOMLPROC __wglewSwapLayerBuffersMscOML = NULL; +PFNWGLWAITFORMSCOMLPROC __wglewWaitForMscOML = NULL; +PFNWGLWAITFORSBCOMLPROC __wglewWaitForSbcOML = NULL; +GLboolean __WGLEW_3DFX_multisample = GL_FALSE; +GLboolean __WGLEW_3DL_stereo_control = GL_FALSE; +GLboolean __WGLEW_ARB_buffer_region = GL_FALSE; +GLboolean __WGLEW_ARB_create_context = GL_FALSE; +GLboolean __WGLEW_ARB_extensions_string = GL_FALSE; +GLboolean __WGLEW_ARB_framebuffer_sRGB = GL_FALSE; +GLboolean __WGLEW_ARB_make_current_read = GL_FALSE; +GLboolean __WGLEW_ARB_multisample = GL_FALSE; +GLboolean __WGLEW_ARB_pbuffer = GL_FALSE; +GLboolean __WGLEW_ARB_pixel_format = GL_FALSE; +GLboolean __WGLEW_ARB_pixel_format_float = GL_FALSE; +GLboolean __WGLEW_ARB_render_texture = GL_FALSE; +GLboolean __WGLEW_ATI_pixel_format_float = GL_FALSE; +GLboolean __WGLEW_ATI_render_texture_rectangle = GL_FALSE; +GLboolean __WGLEW_EXT_depth_float = GL_FALSE; +GLboolean __WGLEW_EXT_display_color_table = GL_FALSE; +GLboolean __WGLEW_EXT_extensions_string = GL_FALSE; +GLboolean __WGLEW_EXT_framebuffer_sRGB = GL_FALSE; +GLboolean __WGLEW_EXT_make_current_read = GL_FALSE; +GLboolean __WGLEW_EXT_multisample = GL_FALSE; +GLboolean __WGLEW_EXT_pbuffer = GL_FALSE; +GLboolean __WGLEW_EXT_pixel_format = GL_FALSE; +GLboolean __WGLEW_EXT_pixel_format_packed_float = GL_FALSE; +GLboolean __WGLEW_EXT_swap_control = GL_FALSE; +GLboolean __WGLEW_I3D_digital_video_control = GL_FALSE; +GLboolean __WGLEW_I3D_gamma = GL_FALSE; +GLboolean __WGLEW_I3D_genlock = GL_FALSE; +GLboolean __WGLEW_I3D_image_buffer = GL_FALSE; +GLboolean __WGLEW_I3D_swap_frame_lock = GL_FALSE; +GLboolean __WGLEW_I3D_swap_frame_usage = GL_FALSE; +GLboolean __WGLEW_NV_float_buffer = GL_FALSE; +GLboolean __WGLEW_NV_gpu_affinity = GL_FALSE; +GLboolean __WGLEW_NV_present_video = GL_FALSE; +GLboolean __WGLEW_NV_render_depth_texture = GL_FALSE; +GLboolean __WGLEW_NV_render_texture_rectangle = GL_FALSE; +GLboolean __WGLEW_NV_swap_group = GL_FALSE; +GLboolean __WGLEW_NV_vertex_array_range = GL_FALSE; +GLboolean __WGLEW_NV_video_output = GL_FALSE; +GLboolean __WGLEW_OML_sync_control = GL_FALSE; + +#endif /* !GLEW_MX */ + +#ifdef WGL_3DFX_multisample + +#endif /* WGL_3DFX_multisample */ + +#ifdef WGL_3DL_stereo_control + +static GLboolean _glewInit_WGL_3DL_stereo_control (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglSetStereoEmitterState3DL = (PFNWGLSETSTEREOEMITTERSTATE3DLPROC)glewGetProcAddress((const GLubyte*)"wglSetStereoEmitterState3DL")) == NULL) || r; + + return r; +} + +#endif /* WGL_3DL_stereo_control */ + +#ifdef WGL_ARB_buffer_region + +static GLboolean _glewInit_WGL_ARB_buffer_region (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglCreateBufferRegionARB = (PFNWGLCREATEBUFFERREGIONARBPROC)glewGetProcAddress((const GLubyte*)"wglCreateBufferRegionARB")) == NULL) || r; + r = ((wglDeleteBufferRegionARB = (PFNWGLDELETEBUFFERREGIONARBPROC)glewGetProcAddress((const GLubyte*)"wglDeleteBufferRegionARB")) == NULL) || r; + r = ((wglRestoreBufferRegionARB = (PFNWGLRESTOREBUFFERREGIONARBPROC)glewGetProcAddress((const GLubyte*)"wglRestoreBufferRegionARB")) == NULL) || r; + r = ((wglSaveBufferRegionARB = (PFNWGLSAVEBUFFERREGIONARBPROC)glewGetProcAddress((const GLubyte*)"wglSaveBufferRegionARB")) == NULL) || r; + + return r; +} + +#endif /* WGL_ARB_buffer_region */ + +#ifdef WGL_ARB_create_context + +static GLboolean _glewInit_WGL_ARB_create_context (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)glewGetProcAddress((const GLubyte*)"wglCreateContextAttribsARB")) == NULL) || r; + + return r; +} + +#endif /* WGL_ARB_create_context */ + +#ifdef WGL_ARB_extensions_string + +static GLboolean _glewInit_WGL_ARB_extensions_string (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)glewGetProcAddress((const GLubyte*)"wglGetExtensionsStringARB")) == NULL) || r; + + return r; +} + +#endif /* WGL_ARB_extensions_string */ + +#ifdef WGL_ARB_framebuffer_sRGB + +#endif /* WGL_ARB_framebuffer_sRGB */ + +#ifdef WGL_ARB_make_current_read + +static GLboolean _glewInit_WGL_ARB_make_current_read (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetCurrentReadDCARB = (PFNWGLGETCURRENTREADDCARBPROC)glewGetProcAddress((const GLubyte*)"wglGetCurrentReadDCARB")) == NULL) || r; + r = ((wglMakeContextCurrentARB = (PFNWGLMAKECONTEXTCURRENTARBPROC)glewGetProcAddress((const GLubyte*)"wglMakeContextCurrentARB")) == NULL) || r; + + return r; +} + +#endif /* WGL_ARB_make_current_read */ + +#ifdef WGL_ARB_multisample + +#endif /* WGL_ARB_multisample */ + +#ifdef WGL_ARB_pbuffer + +static GLboolean _glewInit_WGL_ARB_pbuffer (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglCreatePbufferARB = (PFNWGLCREATEPBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"wglCreatePbufferARB")) == NULL) || r; + r = ((wglDestroyPbufferARB = (PFNWGLDESTROYPBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"wglDestroyPbufferARB")) == NULL) || r; + r = ((wglGetPbufferDCARB = (PFNWGLGETPBUFFERDCARBPROC)glewGetProcAddress((const GLubyte*)"wglGetPbufferDCARB")) == NULL) || r; + r = ((wglQueryPbufferARB = (PFNWGLQUERYPBUFFERARBPROC)glewGetProcAddress((const GLubyte*)"wglQueryPbufferARB")) == NULL) || r; + r = ((wglReleasePbufferDCARB = (PFNWGLRELEASEPBUFFERDCARBPROC)glewGetProcAddress((const GLubyte*)"wglReleasePbufferDCARB")) == NULL) || r; + + return r; +} + +#endif /* WGL_ARB_pbuffer */ + +#ifdef WGL_ARB_pixel_format + +static GLboolean _glewInit_WGL_ARB_pixel_format (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)glewGetProcAddress((const GLubyte*)"wglChoosePixelFormatARB")) == NULL) || r; + r = ((wglGetPixelFormatAttribfvARB = (PFNWGLGETPIXELFORMATATTRIBFVARBPROC)glewGetProcAddress((const GLubyte*)"wglGetPixelFormatAttribfvARB")) == NULL) || r; + r = ((wglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)glewGetProcAddress((const GLubyte*)"wglGetPixelFormatAttribivARB")) == NULL) || r; + + return r; +} + +#endif /* WGL_ARB_pixel_format */ + +#ifdef WGL_ARB_pixel_format_float + +#endif /* WGL_ARB_pixel_format_float */ + +#ifdef WGL_ARB_render_texture + +static GLboolean _glewInit_WGL_ARB_render_texture (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglBindTexImageARB = (PFNWGLBINDTEXIMAGEARBPROC)glewGetProcAddress((const GLubyte*)"wglBindTexImageARB")) == NULL) || r; + r = ((wglReleaseTexImageARB = (PFNWGLRELEASETEXIMAGEARBPROC)glewGetProcAddress((const GLubyte*)"wglReleaseTexImageARB")) == NULL) || r; + r = ((wglSetPbufferAttribARB = (PFNWGLSETPBUFFERATTRIBARBPROC)glewGetProcAddress((const GLubyte*)"wglSetPbufferAttribARB")) == NULL) || r; + + return r; +} + +#endif /* WGL_ARB_render_texture */ + +#ifdef WGL_ATI_pixel_format_float + +#endif /* WGL_ATI_pixel_format_float */ + +#ifdef WGL_ATI_render_texture_rectangle + +#endif /* WGL_ATI_render_texture_rectangle */ + +#ifdef WGL_EXT_depth_float + +#endif /* WGL_EXT_depth_float */ + +#ifdef WGL_EXT_display_color_table + +static GLboolean _glewInit_WGL_EXT_display_color_table (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglBindDisplayColorTableEXT = (PFNWGLBINDDISPLAYCOLORTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"wglBindDisplayColorTableEXT")) == NULL) || r; + r = ((wglCreateDisplayColorTableEXT = (PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"wglCreateDisplayColorTableEXT")) == NULL) || r; + r = ((wglDestroyDisplayColorTableEXT = (PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"wglDestroyDisplayColorTableEXT")) == NULL) || r; + r = ((wglLoadDisplayColorTableEXT = (PFNWGLLOADDISPLAYCOLORTABLEEXTPROC)glewGetProcAddress((const GLubyte*)"wglLoadDisplayColorTableEXT")) == NULL) || r; + + return r; +} + +#endif /* WGL_EXT_display_color_table */ + +#ifdef WGL_EXT_extensions_string + +static GLboolean _glewInit_WGL_EXT_extensions_string (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)glewGetProcAddress((const GLubyte*)"wglGetExtensionsStringEXT")) == NULL) || r; + + return r; +} + +#endif /* WGL_EXT_extensions_string */ + +#ifdef WGL_EXT_framebuffer_sRGB + +#endif /* WGL_EXT_framebuffer_sRGB */ + +#ifdef WGL_EXT_make_current_read + +static GLboolean _glewInit_WGL_EXT_make_current_read (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetCurrentReadDCEXT = (PFNWGLGETCURRENTREADDCEXTPROC)glewGetProcAddress((const GLubyte*)"wglGetCurrentReadDCEXT")) == NULL) || r; + r = ((wglMakeContextCurrentEXT = (PFNWGLMAKECONTEXTCURRENTEXTPROC)glewGetProcAddress((const GLubyte*)"wglMakeContextCurrentEXT")) == NULL) || r; + + return r; +} + +#endif /* WGL_EXT_make_current_read */ + +#ifdef WGL_EXT_multisample + +#endif /* WGL_EXT_multisample */ + +#ifdef WGL_EXT_pbuffer + +static GLboolean _glewInit_WGL_EXT_pbuffer (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglCreatePbufferEXT = (PFNWGLCREATEPBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"wglCreatePbufferEXT")) == NULL) || r; + r = ((wglDestroyPbufferEXT = (PFNWGLDESTROYPBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"wglDestroyPbufferEXT")) == NULL) || r; + r = ((wglGetPbufferDCEXT = (PFNWGLGETPBUFFERDCEXTPROC)glewGetProcAddress((const GLubyte*)"wglGetPbufferDCEXT")) == NULL) || r; + r = ((wglQueryPbufferEXT = (PFNWGLQUERYPBUFFEREXTPROC)glewGetProcAddress((const GLubyte*)"wglQueryPbufferEXT")) == NULL) || r; + r = ((wglReleasePbufferDCEXT = (PFNWGLRELEASEPBUFFERDCEXTPROC)glewGetProcAddress((const GLubyte*)"wglReleasePbufferDCEXT")) == NULL) || r; + + return r; +} + +#endif /* WGL_EXT_pbuffer */ + +#ifdef WGL_EXT_pixel_format + +static GLboolean _glewInit_WGL_EXT_pixel_format (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglChoosePixelFormatEXT = (PFNWGLCHOOSEPIXELFORMATEXTPROC)glewGetProcAddress((const GLubyte*)"wglChoosePixelFormatEXT")) == NULL) || r; + r = ((wglGetPixelFormatAttribfvEXT = (PFNWGLGETPIXELFORMATATTRIBFVEXTPROC)glewGetProcAddress((const GLubyte*)"wglGetPixelFormatAttribfvEXT")) == NULL) || r; + r = ((wglGetPixelFormatAttribivEXT = (PFNWGLGETPIXELFORMATATTRIBIVEXTPROC)glewGetProcAddress((const GLubyte*)"wglGetPixelFormatAttribivEXT")) == NULL) || r; + + return r; +} + +#endif /* WGL_EXT_pixel_format */ + +#ifdef WGL_EXT_pixel_format_packed_float + +#endif /* WGL_EXT_pixel_format_packed_float */ + +#ifdef WGL_EXT_swap_control + +static GLboolean _glewInit_WGL_EXT_swap_control (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC)glewGetProcAddress((const GLubyte*)"wglGetSwapIntervalEXT")) == NULL) || r; + r = ((wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)glewGetProcAddress((const GLubyte*)"wglSwapIntervalEXT")) == NULL) || r; + + return r; +} + +#endif /* WGL_EXT_swap_control */ + +#ifdef WGL_I3D_digital_video_control + +static GLboolean _glewInit_WGL_I3D_digital_video_control (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetDigitalVideoParametersI3D = (PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetDigitalVideoParametersI3D")) == NULL) || r; + r = ((wglSetDigitalVideoParametersI3D = (PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC)glewGetProcAddress((const GLubyte*)"wglSetDigitalVideoParametersI3D")) == NULL) || r; + + return r; +} + +#endif /* WGL_I3D_digital_video_control */ + +#ifdef WGL_I3D_gamma + +static GLboolean _glewInit_WGL_I3D_gamma (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetGammaTableI3D = (PFNWGLGETGAMMATABLEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetGammaTableI3D")) == NULL) || r; + r = ((wglGetGammaTableParametersI3D = (PFNWGLGETGAMMATABLEPARAMETERSI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetGammaTableParametersI3D")) == NULL) || r; + r = ((wglSetGammaTableI3D = (PFNWGLSETGAMMATABLEI3DPROC)glewGetProcAddress((const GLubyte*)"wglSetGammaTableI3D")) == NULL) || r; + r = ((wglSetGammaTableParametersI3D = (PFNWGLSETGAMMATABLEPARAMETERSI3DPROC)glewGetProcAddress((const GLubyte*)"wglSetGammaTableParametersI3D")) == NULL) || r; + + return r; +} + +#endif /* WGL_I3D_gamma */ + +#ifdef WGL_I3D_genlock + +static GLboolean _glewInit_WGL_I3D_genlock (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglDisableGenlockI3D = (PFNWGLDISABLEGENLOCKI3DPROC)glewGetProcAddress((const GLubyte*)"wglDisableGenlockI3D")) == NULL) || r; + r = ((wglEnableGenlockI3D = (PFNWGLENABLEGENLOCKI3DPROC)glewGetProcAddress((const GLubyte*)"wglEnableGenlockI3D")) == NULL) || r; + r = ((wglGenlockSampleRateI3D = (PFNWGLGENLOCKSAMPLERATEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGenlockSampleRateI3D")) == NULL) || r; + r = ((wglGenlockSourceDelayI3D = (PFNWGLGENLOCKSOURCEDELAYI3DPROC)glewGetProcAddress((const GLubyte*)"wglGenlockSourceDelayI3D")) == NULL) || r; + r = ((wglGenlockSourceEdgeI3D = (PFNWGLGENLOCKSOURCEEDGEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGenlockSourceEdgeI3D")) == NULL) || r; + r = ((wglGenlockSourceI3D = (PFNWGLGENLOCKSOURCEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGenlockSourceI3D")) == NULL) || r; + r = ((wglGetGenlockSampleRateI3D = (PFNWGLGETGENLOCKSAMPLERATEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetGenlockSampleRateI3D")) == NULL) || r; + r = ((wglGetGenlockSourceDelayI3D = (PFNWGLGETGENLOCKSOURCEDELAYI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetGenlockSourceDelayI3D")) == NULL) || r; + r = ((wglGetGenlockSourceEdgeI3D = (PFNWGLGETGENLOCKSOURCEEDGEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetGenlockSourceEdgeI3D")) == NULL) || r; + r = ((wglGetGenlockSourceI3D = (PFNWGLGETGENLOCKSOURCEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetGenlockSourceI3D")) == NULL) || r; + r = ((wglIsEnabledGenlockI3D = (PFNWGLISENABLEDGENLOCKI3DPROC)glewGetProcAddress((const GLubyte*)"wglIsEnabledGenlockI3D")) == NULL) || r; + r = ((wglQueryGenlockMaxSourceDelayI3D = (PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC)glewGetProcAddress((const GLubyte*)"wglQueryGenlockMaxSourceDelayI3D")) == NULL) || r; + + return r; +} + +#endif /* WGL_I3D_genlock */ + +#ifdef WGL_I3D_image_buffer + +static GLboolean _glewInit_WGL_I3D_image_buffer (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglAssociateImageBufferEventsI3D = (PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC)glewGetProcAddress((const GLubyte*)"wglAssociateImageBufferEventsI3D")) == NULL) || r; + r = ((wglCreateImageBufferI3D = (PFNWGLCREATEIMAGEBUFFERI3DPROC)glewGetProcAddress((const GLubyte*)"wglCreateImageBufferI3D")) == NULL) || r; + r = ((wglDestroyImageBufferI3D = (PFNWGLDESTROYIMAGEBUFFERI3DPROC)glewGetProcAddress((const GLubyte*)"wglDestroyImageBufferI3D")) == NULL) || r; + r = ((wglReleaseImageBufferEventsI3D = (PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC)glewGetProcAddress((const GLubyte*)"wglReleaseImageBufferEventsI3D")) == NULL) || r; + + return r; +} + +#endif /* WGL_I3D_image_buffer */ + +#ifdef WGL_I3D_swap_frame_lock + +static GLboolean _glewInit_WGL_I3D_swap_frame_lock (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglDisableFrameLockI3D = (PFNWGLDISABLEFRAMELOCKI3DPROC)glewGetProcAddress((const GLubyte*)"wglDisableFrameLockI3D")) == NULL) || r; + r = ((wglEnableFrameLockI3D = (PFNWGLENABLEFRAMELOCKI3DPROC)glewGetProcAddress((const GLubyte*)"wglEnableFrameLockI3D")) == NULL) || r; + r = ((wglIsEnabledFrameLockI3D = (PFNWGLISENABLEDFRAMELOCKI3DPROC)glewGetProcAddress((const GLubyte*)"wglIsEnabledFrameLockI3D")) == NULL) || r; + r = ((wglQueryFrameLockMasterI3D = (PFNWGLQUERYFRAMELOCKMASTERI3DPROC)glewGetProcAddress((const GLubyte*)"wglQueryFrameLockMasterI3D")) == NULL) || r; + + return r; +} + +#endif /* WGL_I3D_swap_frame_lock */ + +#ifdef WGL_I3D_swap_frame_usage + +static GLboolean _glewInit_WGL_I3D_swap_frame_usage (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglBeginFrameTrackingI3D = (PFNWGLBEGINFRAMETRACKINGI3DPROC)glewGetProcAddress((const GLubyte*)"wglBeginFrameTrackingI3D")) == NULL) || r; + r = ((wglEndFrameTrackingI3D = (PFNWGLENDFRAMETRACKINGI3DPROC)glewGetProcAddress((const GLubyte*)"wglEndFrameTrackingI3D")) == NULL) || r; + r = ((wglGetFrameUsageI3D = (PFNWGLGETFRAMEUSAGEI3DPROC)glewGetProcAddress((const GLubyte*)"wglGetFrameUsageI3D")) == NULL) || r; + r = ((wglQueryFrameTrackingI3D = (PFNWGLQUERYFRAMETRACKINGI3DPROC)glewGetProcAddress((const GLubyte*)"wglQueryFrameTrackingI3D")) == NULL) || r; + + return r; +} + +#endif /* WGL_I3D_swap_frame_usage */ + +#ifdef WGL_NV_float_buffer + +#endif /* WGL_NV_float_buffer */ + +#ifdef WGL_NV_gpu_affinity + +static GLboolean _glewInit_WGL_NV_gpu_affinity (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglCreateAffinityDCNV = (PFNWGLCREATEAFFINITYDCNVPROC)glewGetProcAddress((const GLubyte*)"wglCreateAffinityDCNV")) == NULL) || r; + r = ((wglDeleteDCNV = (PFNWGLDELETEDCNVPROC)glewGetProcAddress((const GLubyte*)"wglDeleteDCNV")) == NULL) || r; + r = ((wglEnumGpuDevicesNV = (PFNWGLENUMGPUDEVICESNVPROC)glewGetProcAddress((const GLubyte*)"wglEnumGpuDevicesNV")) == NULL) || r; + r = ((wglEnumGpusFromAffinityDCNV = (PFNWGLENUMGPUSFROMAFFINITYDCNVPROC)glewGetProcAddress((const GLubyte*)"wglEnumGpusFromAffinityDCNV")) == NULL) || r; + r = ((wglEnumGpusNV = (PFNWGLENUMGPUSNVPROC)glewGetProcAddress((const GLubyte*)"wglEnumGpusNV")) == NULL) || r; + + return r; +} + +#endif /* WGL_NV_gpu_affinity */ + +#ifdef WGL_NV_present_video + +static GLboolean _glewInit_WGL_NV_present_video (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglBindVideoDeviceNV = (PFNWGLBINDVIDEODEVICENVPROC)glewGetProcAddress((const GLubyte*)"wglBindVideoDeviceNV")) == NULL) || r; + r = ((wglEnumerateVideoDevicesNV = (PFNWGLENUMERATEVIDEODEVICESNVPROC)glewGetProcAddress((const GLubyte*)"wglEnumerateVideoDevicesNV")) == NULL) || r; + r = ((wglQueryCurrentContextNV = (PFNWGLQUERYCURRENTCONTEXTNVPROC)glewGetProcAddress((const GLubyte*)"wglQueryCurrentContextNV")) == NULL) || r; + + return r; +} + +#endif /* WGL_NV_present_video */ + +#ifdef WGL_NV_render_depth_texture + +#endif /* WGL_NV_render_depth_texture */ + +#ifdef WGL_NV_render_texture_rectangle + +#endif /* WGL_NV_render_texture_rectangle */ + +#ifdef WGL_NV_swap_group + +static GLboolean _glewInit_WGL_NV_swap_group (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglBindSwapBarrierNV = (PFNWGLBINDSWAPBARRIERNVPROC)glewGetProcAddress((const GLubyte*)"wglBindSwapBarrierNV")) == NULL) || r; + r = ((wglJoinSwapGroupNV = (PFNWGLJOINSWAPGROUPNVPROC)glewGetProcAddress((const GLubyte*)"wglJoinSwapGroupNV")) == NULL) || r; + r = ((wglQueryFrameCountNV = (PFNWGLQUERYFRAMECOUNTNVPROC)glewGetProcAddress((const GLubyte*)"wglQueryFrameCountNV")) == NULL) || r; + r = ((wglQueryMaxSwapGroupsNV = (PFNWGLQUERYMAXSWAPGROUPSNVPROC)glewGetProcAddress((const GLubyte*)"wglQueryMaxSwapGroupsNV")) == NULL) || r; + r = ((wglQuerySwapGroupNV = (PFNWGLQUERYSWAPGROUPNVPROC)glewGetProcAddress((const GLubyte*)"wglQuerySwapGroupNV")) == NULL) || r; + r = ((wglResetFrameCountNV = (PFNWGLRESETFRAMECOUNTNVPROC)glewGetProcAddress((const GLubyte*)"wglResetFrameCountNV")) == NULL) || r; + + return r; +} + +#endif /* WGL_NV_swap_group */ + +#ifdef WGL_NV_vertex_array_range + +static GLboolean _glewInit_WGL_NV_vertex_array_range (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglAllocateMemoryNV = (PFNWGLALLOCATEMEMORYNVPROC)glewGetProcAddress((const GLubyte*)"wglAllocateMemoryNV")) == NULL) || r; + r = ((wglFreeMemoryNV = (PFNWGLFREEMEMORYNVPROC)glewGetProcAddress((const GLubyte*)"wglFreeMemoryNV")) == NULL) || r; + + return r; +} + +#endif /* WGL_NV_vertex_array_range */ + +#ifdef WGL_NV_video_output + +static GLboolean _glewInit_WGL_NV_video_output (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglBindVideoImageNV = (PFNWGLBINDVIDEOIMAGENVPROC)glewGetProcAddress((const GLubyte*)"wglBindVideoImageNV")) == NULL) || r; + r = ((wglGetVideoDeviceNV = (PFNWGLGETVIDEODEVICENVPROC)glewGetProcAddress((const GLubyte*)"wglGetVideoDeviceNV")) == NULL) || r; + r = ((wglGetVideoInfoNV = (PFNWGLGETVIDEOINFONVPROC)glewGetProcAddress((const GLubyte*)"wglGetVideoInfoNV")) == NULL) || r; + r = ((wglReleaseVideoDeviceNV = (PFNWGLRELEASEVIDEODEVICENVPROC)glewGetProcAddress((const GLubyte*)"wglReleaseVideoDeviceNV")) == NULL) || r; + r = ((wglReleaseVideoImageNV = (PFNWGLRELEASEVIDEOIMAGENVPROC)glewGetProcAddress((const GLubyte*)"wglReleaseVideoImageNV")) == NULL) || r; + r = ((wglSendPbufferToVideoNV = (PFNWGLSENDPBUFFERTOVIDEONVPROC)glewGetProcAddress((const GLubyte*)"wglSendPbufferToVideoNV")) == NULL) || r; + + return r; +} + +#endif /* WGL_NV_video_output */ + +#ifdef WGL_OML_sync_control + +static GLboolean _glewInit_WGL_OML_sync_control (WGLEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((wglGetMscRateOML = (PFNWGLGETMSCRATEOMLPROC)glewGetProcAddress((const GLubyte*)"wglGetMscRateOML")) == NULL) || r; + r = ((wglGetSyncValuesOML = (PFNWGLGETSYNCVALUESOMLPROC)glewGetProcAddress((const GLubyte*)"wglGetSyncValuesOML")) == NULL) || r; + r = ((wglSwapBuffersMscOML = (PFNWGLSWAPBUFFERSMSCOMLPROC)glewGetProcAddress((const GLubyte*)"wglSwapBuffersMscOML")) == NULL) || r; + r = ((wglSwapLayerBuffersMscOML = (PFNWGLSWAPLAYERBUFFERSMSCOMLPROC)glewGetProcAddress((const GLubyte*)"wglSwapLayerBuffersMscOML")) == NULL) || r; + r = ((wglWaitForMscOML = (PFNWGLWAITFORMSCOMLPROC)glewGetProcAddress((const GLubyte*)"wglWaitForMscOML")) == NULL) || r; + r = ((wglWaitForSbcOML = (PFNWGLWAITFORSBCOMLPROC)glewGetProcAddress((const GLubyte*)"wglWaitForSbcOML")) == NULL) || r; + + return r; +} + +#endif /* WGL_OML_sync_control */ + +/* ------------------------------------------------------------------------- */ + +static PFNWGLGETEXTENSIONSSTRINGARBPROC _wglewGetExtensionsStringARB = NULL; +static PFNWGLGETEXTENSIONSSTRINGEXTPROC _wglewGetExtensionsStringEXT = NULL; + +GLboolean wglewGetExtension (const char* name) +{ + GLubyte* p; + GLubyte* end; + GLuint len = _glewStrLen((const GLubyte*)name); + if (_wglewGetExtensionsStringARB == NULL) + if (_wglewGetExtensionsStringEXT == NULL) + return GL_FALSE; + else + p = (GLubyte*)_wglewGetExtensionsStringEXT(); + else + p = (GLubyte*)_wglewGetExtensionsStringARB(wglGetCurrentDC()); + if (0 == p) return GL_FALSE; + end = p + _glewStrLen(p); + while (p < end) + { + GLuint n = _glewStrCLen(p, ' '); + if (len == n && _glewStrSame((const GLubyte*)name, p, n)) return GL_TRUE; + p += n+1; + } + return GL_FALSE; +} + +GLenum wglewContextInit (WGLEW_CONTEXT_ARG_DEF_LIST) +{ + GLboolean crippled; + /* find wgl extension string query functions */ + _wglewGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)glewGetProcAddress((const GLubyte*)"wglGetExtensionsStringARB"); + _wglewGetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)glewGetProcAddress((const GLubyte*)"wglGetExtensionsStringEXT"); + /* initialize extensions */ + crippled = _wglewGetExtensionsStringARB == NULL && _wglewGetExtensionsStringEXT == NULL; +#ifdef WGL_3DFX_multisample + CONST_CAST(WGLEW_3DFX_multisample) = wglewGetExtension("WGL_3DFX_multisample"); +#endif /* WGL_3DFX_multisample */ +#ifdef WGL_3DL_stereo_control + CONST_CAST(WGLEW_3DL_stereo_control) = wglewGetExtension("WGL_3DL_stereo_control"); + if (glewExperimental || WGLEW_3DL_stereo_control|| crippled) CONST_CAST(WGLEW_3DL_stereo_control)= !_glewInit_WGL_3DL_stereo_control(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_3DL_stereo_control */ +#ifdef WGL_ARB_buffer_region + CONST_CAST(WGLEW_ARB_buffer_region) = wglewGetExtension("WGL_ARB_buffer_region"); + if (glewExperimental || WGLEW_ARB_buffer_region|| crippled) CONST_CAST(WGLEW_ARB_buffer_region)= !_glewInit_WGL_ARB_buffer_region(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_ARB_buffer_region */ +#ifdef WGL_ARB_create_context + CONST_CAST(WGLEW_ARB_create_context) = wglewGetExtension("WGL_ARB_create_context"); + if (glewExperimental || WGLEW_ARB_create_context|| crippled) CONST_CAST(WGLEW_ARB_create_context)= !_glewInit_WGL_ARB_create_context(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_ARB_create_context */ +#ifdef WGL_ARB_extensions_string + CONST_CAST(WGLEW_ARB_extensions_string) = wglewGetExtension("WGL_ARB_extensions_string"); + if (glewExperimental || WGLEW_ARB_extensions_string|| crippled) CONST_CAST(WGLEW_ARB_extensions_string)= !_glewInit_WGL_ARB_extensions_string(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_ARB_extensions_string */ +#ifdef WGL_ARB_framebuffer_sRGB + CONST_CAST(WGLEW_ARB_framebuffer_sRGB) = wglewGetExtension("WGL_ARB_framebuffer_sRGB"); +#endif /* WGL_ARB_framebuffer_sRGB */ +#ifdef WGL_ARB_make_current_read + CONST_CAST(WGLEW_ARB_make_current_read) = wglewGetExtension("WGL_ARB_make_current_read"); + if (glewExperimental || WGLEW_ARB_make_current_read|| crippled) CONST_CAST(WGLEW_ARB_make_current_read)= !_glewInit_WGL_ARB_make_current_read(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_ARB_make_current_read */ +#ifdef WGL_ARB_multisample + CONST_CAST(WGLEW_ARB_multisample) = wglewGetExtension("WGL_ARB_multisample"); +#endif /* WGL_ARB_multisample */ +#ifdef WGL_ARB_pbuffer + CONST_CAST(WGLEW_ARB_pbuffer) = wglewGetExtension("WGL_ARB_pbuffer"); + if (glewExperimental || WGLEW_ARB_pbuffer|| crippled) CONST_CAST(WGLEW_ARB_pbuffer)= !_glewInit_WGL_ARB_pbuffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_ARB_pbuffer */ +#ifdef WGL_ARB_pixel_format + CONST_CAST(WGLEW_ARB_pixel_format) = wglewGetExtension("WGL_ARB_pixel_format"); + if (glewExperimental || WGLEW_ARB_pixel_format|| crippled) CONST_CAST(WGLEW_ARB_pixel_format)= !_glewInit_WGL_ARB_pixel_format(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_ARB_pixel_format */ +#ifdef WGL_ARB_pixel_format_float + CONST_CAST(WGLEW_ARB_pixel_format_float) = wglewGetExtension("WGL_ARB_pixel_format_float"); +#endif /* WGL_ARB_pixel_format_float */ +#ifdef WGL_ARB_render_texture + CONST_CAST(WGLEW_ARB_render_texture) = wglewGetExtension("WGL_ARB_render_texture"); + if (glewExperimental || WGLEW_ARB_render_texture|| crippled) CONST_CAST(WGLEW_ARB_render_texture)= !_glewInit_WGL_ARB_render_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_ARB_render_texture */ +#ifdef WGL_ATI_pixel_format_float + CONST_CAST(WGLEW_ATI_pixel_format_float) = wglewGetExtension("WGL_ATI_pixel_format_float"); +#endif /* WGL_ATI_pixel_format_float */ +#ifdef WGL_ATI_render_texture_rectangle + CONST_CAST(WGLEW_ATI_render_texture_rectangle) = wglewGetExtension("WGL_ATI_render_texture_rectangle"); +#endif /* WGL_ATI_render_texture_rectangle */ +#ifdef WGL_EXT_depth_float + CONST_CAST(WGLEW_EXT_depth_float) = wglewGetExtension("WGL_EXT_depth_float"); +#endif /* WGL_EXT_depth_float */ +#ifdef WGL_EXT_display_color_table + CONST_CAST(WGLEW_EXT_display_color_table) = wglewGetExtension("WGL_EXT_display_color_table"); + if (glewExperimental || WGLEW_EXT_display_color_table|| crippled) CONST_CAST(WGLEW_EXT_display_color_table)= !_glewInit_WGL_EXT_display_color_table(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_EXT_display_color_table */ +#ifdef WGL_EXT_extensions_string + CONST_CAST(WGLEW_EXT_extensions_string) = wglewGetExtension("WGL_EXT_extensions_string"); + if (glewExperimental || WGLEW_EXT_extensions_string|| crippled) CONST_CAST(WGLEW_EXT_extensions_string)= !_glewInit_WGL_EXT_extensions_string(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_EXT_extensions_string */ +#ifdef WGL_EXT_framebuffer_sRGB + CONST_CAST(WGLEW_EXT_framebuffer_sRGB) = wglewGetExtension("WGL_EXT_framebuffer_sRGB"); +#endif /* WGL_EXT_framebuffer_sRGB */ +#ifdef WGL_EXT_make_current_read + CONST_CAST(WGLEW_EXT_make_current_read) = wglewGetExtension("WGL_EXT_make_current_read"); + if (glewExperimental || WGLEW_EXT_make_current_read|| crippled) CONST_CAST(WGLEW_EXT_make_current_read)= !_glewInit_WGL_EXT_make_current_read(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_EXT_make_current_read */ +#ifdef WGL_EXT_multisample + CONST_CAST(WGLEW_EXT_multisample) = wglewGetExtension("WGL_EXT_multisample"); +#endif /* WGL_EXT_multisample */ +#ifdef WGL_EXT_pbuffer + CONST_CAST(WGLEW_EXT_pbuffer) = wglewGetExtension("WGL_EXT_pbuffer"); + if (glewExperimental || WGLEW_EXT_pbuffer|| crippled) CONST_CAST(WGLEW_EXT_pbuffer)= !_glewInit_WGL_EXT_pbuffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_EXT_pbuffer */ +#ifdef WGL_EXT_pixel_format + CONST_CAST(WGLEW_EXT_pixel_format) = wglewGetExtension("WGL_EXT_pixel_format"); + if (glewExperimental || WGLEW_EXT_pixel_format|| crippled) CONST_CAST(WGLEW_EXT_pixel_format)= !_glewInit_WGL_EXT_pixel_format(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_EXT_pixel_format */ +#ifdef WGL_EXT_pixel_format_packed_float + CONST_CAST(WGLEW_EXT_pixel_format_packed_float) = wglewGetExtension("WGL_EXT_pixel_format_packed_float"); +#endif /* WGL_EXT_pixel_format_packed_float */ +#ifdef WGL_EXT_swap_control + CONST_CAST(WGLEW_EXT_swap_control) = wglewGetExtension("WGL_EXT_swap_control"); + if (glewExperimental || WGLEW_EXT_swap_control|| crippled) CONST_CAST(WGLEW_EXT_swap_control)= !_glewInit_WGL_EXT_swap_control(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_EXT_swap_control */ +#ifdef WGL_I3D_digital_video_control + CONST_CAST(WGLEW_I3D_digital_video_control) = wglewGetExtension("WGL_I3D_digital_video_control"); + if (glewExperimental || WGLEW_I3D_digital_video_control|| crippled) CONST_CAST(WGLEW_I3D_digital_video_control)= !_glewInit_WGL_I3D_digital_video_control(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_I3D_digital_video_control */ +#ifdef WGL_I3D_gamma + CONST_CAST(WGLEW_I3D_gamma) = wglewGetExtension("WGL_I3D_gamma"); + if (glewExperimental || WGLEW_I3D_gamma|| crippled) CONST_CAST(WGLEW_I3D_gamma)= !_glewInit_WGL_I3D_gamma(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_I3D_gamma */ +#ifdef WGL_I3D_genlock + CONST_CAST(WGLEW_I3D_genlock) = wglewGetExtension("WGL_I3D_genlock"); + if (glewExperimental || WGLEW_I3D_genlock|| crippled) CONST_CAST(WGLEW_I3D_genlock)= !_glewInit_WGL_I3D_genlock(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_I3D_genlock */ +#ifdef WGL_I3D_image_buffer + CONST_CAST(WGLEW_I3D_image_buffer) = wglewGetExtension("WGL_I3D_image_buffer"); + if (glewExperimental || WGLEW_I3D_image_buffer|| crippled) CONST_CAST(WGLEW_I3D_image_buffer)= !_glewInit_WGL_I3D_image_buffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_I3D_image_buffer */ +#ifdef WGL_I3D_swap_frame_lock + CONST_CAST(WGLEW_I3D_swap_frame_lock) = wglewGetExtension("WGL_I3D_swap_frame_lock"); + if (glewExperimental || WGLEW_I3D_swap_frame_lock|| crippled) CONST_CAST(WGLEW_I3D_swap_frame_lock)= !_glewInit_WGL_I3D_swap_frame_lock(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_I3D_swap_frame_lock */ +#ifdef WGL_I3D_swap_frame_usage + CONST_CAST(WGLEW_I3D_swap_frame_usage) = wglewGetExtension("WGL_I3D_swap_frame_usage"); + if (glewExperimental || WGLEW_I3D_swap_frame_usage|| crippled) CONST_CAST(WGLEW_I3D_swap_frame_usage)= !_glewInit_WGL_I3D_swap_frame_usage(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_I3D_swap_frame_usage */ +#ifdef WGL_NV_float_buffer + CONST_CAST(WGLEW_NV_float_buffer) = wglewGetExtension("WGL_NV_float_buffer"); +#endif /* WGL_NV_float_buffer */ +#ifdef WGL_NV_gpu_affinity + CONST_CAST(WGLEW_NV_gpu_affinity) = wglewGetExtension("WGL_NV_gpu_affinity"); + if (glewExperimental || WGLEW_NV_gpu_affinity|| crippled) CONST_CAST(WGLEW_NV_gpu_affinity)= !_glewInit_WGL_NV_gpu_affinity(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_NV_gpu_affinity */ +#ifdef WGL_NV_present_video + CONST_CAST(WGLEW_NV_present_video) = wglewGetExtension("WGL_NV_present_video"); + if (glewExperimental || WGLEW_NV_present_video|| crippled) CONST_CAST(WGLEW_NV_present_video)= !_glewInit_WGL_NV_present_video(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_NV_present_video */ +#ifdef WGL_NV_render_depth_texture + CONST_CAST(WGLEW_NV_render_depth_texture) = wglewGetExtension("WGL_NV_render_depth_texture"); +#endif /* WGL_NV_render_depth_texture */ +#ifdef WGL_NV_render_texture_rectangle + CONST_CAST(WGLEW_NV_render_texture_rectangle) = wglewGetExtension("WGL_NV_render_texture_rectangle"); +#endif /* WGL_NV_render_texture_rectangle */ +#ifdef WGL_NV_swap_group + CONST_CAST(WGLEW_NV_swap_group) = wglewGetExtension("WGL_NV_swap_group"); + if (glewExperimental || WGLEW_NV_swap_group|| crippled) CONST_CAST(WGLEW_NV_swap_group)= !_glewInit_WGL_NV_swap_group(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_NV_swap_group */ +#ifdef WGL_NV_vertex_array_range + CONST_CAST(WGLEW_NV_vertex_array_range) = wglewGetExtension("WGL_NV_vertex_array_range"); + if (glewExperimental || WGLEW_NV_vertex_array_range|| crippled) CONST_CAST(WGLEW_NV_vertex_array_range)= !_glewInit_WGL_NV_vertex_array_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_NV_vertex_array_range */ +#ifdef WGL_NV_video_output + CONST_CAST(WGLEW_NV_video_output) = wglewGetExtension("WGL_NV_video_output"); + if (glewExperimental || WGLEW_NV_video_output|| crippled) CONST_CAST(WGLEW_NV_video_output)= !_glewInit_WGL_NV_video_output(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_NV_video_output */ +#ifdef WGL_OML_sync_control + CONST_CAST(WGLEW_OML_sync_control) = wglewGetExtension("WGL_OML_sync_control"); + if (glewExperimental || WGLEW_OML_sync_control|| crippled) CONST_CAST(WGLEW_OML_sync_control)= !_glewInit_WGL_OML_sync_control(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* WGL_OML_sync_control */ + + return GLEW_OK; +} + +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) + +PFNGLXGETCURRENTDISPLAYPROC __glewXGetCurrentDisplay = NULL; + +PFNGLXCHOOSEFBCONFIGPROC __glewXChooseFBConfig = NULL; +PFNGLXCREATENEWCONTEXTPROC __glewXCreateNewContext = NULL; +PFNGLXCREATEPBUFFERPROC __glewXCreatePbuffer = NULL; +PFNGLXCREATEPIXMAPPROC __glewXCreatePixmap = NULL; +PFNGLXCREATEWINDOWPROC __glewXCreateWindow = NULL; +PFNGLXDESTROYPBUFFERPROC __glewXDestroyPbuffer = NULL; +PFNGLXDESTROYPIXMAPPROC __glewXDestroyPixmap = NULL; +PFNGLXDESTROYWINDOWPROC __glewXDestroyWindow = NULL; +PFNGLXGETCURRENTREADDRAWABLEPROC __glewXGetCurrentReadDrawable = NULL; +PFNGLXGETFBCONFIGATTRIBPROC __glewXGetFBConfigAttrib = NULL; +PFNGLXGETFBCONFIGSPROC __glewXGetFBConfigs = NULL; +PFNGLXGETSELECTEDEVENTPROC __glewXGetSelectedEvent = NULL; +PFNGLXGETVISUALFROMFBCONFIGPROC __glewXGetVisualFromFBConfig = NULL; +PFNGLXMAKECONTEXTCURRENTPROC __glewXMakeContextCurrent = NULL; +PFNGLXQUERYCONTEXTPROC __glewXQueryContext = NULL; +PFNGLXQUERYDRAWABLEPROC __glewXQueryDrawable = NULL; +PFNGLXSELECTEVENTPROC __glewXSelectEvent = NULL; + +PFNGLXCREATECONTEXTATTRIBSARBPROC __glewXCreateContextAttribsARB = NULL; + +PFNGLXBINDTEXIMAGEATIPROC __glewXBindTexImageATI = NULL; +PFNGLXDRAWABLEATTRIBATIPROC __glewXDrawableAttribATI = NULL; +PFNGLXRELEASETEXIMAGEATIPROC __glewXReleaseTexImageATI = NULL; + +PFNGLXFREECONTEXTEXTPROC __glewXFreeContextEXT = NULL; +PFNGLXGETCONTEXTIDEXTPROC __glewXGetContextIDEXT = NULL; +PFNGLXIMPORTCONTEXTEXTPROC __glewXImportContextEXT = NULL; +PFNGLXQUERYCONTEXTINFOEXTPROC __glewXQueryContextInfoEXT = NULL; + +PFNGLXBINDTEXIMAGEEXTPROC __glewXBindTexImageEXT = NULL; +PFNGLXRELEASETEXIMAGEEXTPROC __glewXReleaseTexImageEXT = NULL; + +PFNGLXGETAGPOFFSETMESAPROC __glewXGetAGPOffsetMESA = NULL; + +PFNGLXCOPYSUBBUFFERMESAPROC __glewXCopySubBufferMESA = NULL; + +PFNGLXCREATEGLXPIXMAPMESAPROC __glewXCreateGLXPixmapMESA = NULL; + +PFNGLXRELEASEBUFFERSMESAPROC __glewXReleaseBuffersMESA = NULL; + +PFNGLXSET3DFXMODEMESAPROC __glewXSet3DfxModeMESA = NULL; + +PFNGLXBINDVIDEODEVICENVPROC __glewXBindVideoDeviceNV = NULL; +PFNGLXENUMERATEVIDEODEVICESNVPROC __glewXEnumerateVideoDevicesNV = NULL; + +PFNGLXBINDSWAPBARRIERNVPROC __glewXBindSwapBarrierNV = NULL; +PFNGLXJOINSWAPGROUPNVPROC __glewXJoinSwapGroupNV = NULL; +PFNGLXQUERYFRAMECOUNTNVPROC __glewXQueryFrameCountNV = NULL; +PFNGLXQUERYMAXSWAPGROUPSNVPROC __glewXQueryMaxSwapGroupsNV = NULL; +PFNGLXQUERYSWAPGROUPNVPROC __glewXQuerySwapGroupNV = NULL; +PFNGLXRESETFRAMECOUNTNVPROC __glewXResetFrameCountNV = NULL; + +PFNGLXALLOCATEMEMORYNVPROC __glewXAllocateMemoryNV = NULL; +PFNGLXFREEMEMORYNVPROC __glewXFreeMemoryNV = NULL; + +PFNGLXBINDVIDEOIMAGENVPROC __glewXBindVideoImageNV = NULL; +PFNGLXGETVIDEODEVICENVPROC __glewXGetVideoDeviceNV = NULL; +PFNGLXGETVIDEOINFONVPROC __glewXGetVideoInfoNV = NULL; +PFNGLXRELEASEVIDEODEVICENVPROC __glewXReleaseVideoDeviceNV = NULL; +PFNGLXRELEASEVIDEOIMAGENVPROC __glewXReleaseVideoImageNV = NULL; +PFNGLXSENDPBUFFERTOVIDEONVPROC __glewXSendPbufferToVideoNV = NULL; + +#ifdef GLX_OML_sync_control +PFNGLXGETMSCRATEOMLPROC __glewXGetMscRateOML = NULL; +PFNGLXGETSYNCVALUESOMLPROC __glewXGetSyncValuesOML = NULL; +PFNGLXSWAPBUFFERSMSCOMLPROC __glewXSwapBuffersMscOML = NULL; +PFNGLXWAITFORMSCOMLPROC __glewXWaitForMscOML = NULL; +PFNGLXWAITFORSBCOMLPROC __glewXWaitForSbcOML = NULL; +#endif + +PFNGLXCHOOSEFBCONFIGSGIXPROC __glewXChooseFBConfigSGIX = NULL; +PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC __glewXCreateContextWithConfigSGIX = NULL; +PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC __glewXCreateGLXPixmapWithConfigSGIX = NULL; +PFNGLXGETFBCONFIGATTRIBSGIXPROC __glewXGetFBConfigAttribSGIX = NULL; +PFNGLXGETFBCONFIGFROMVISUALSGIXPROC __glewXGetFBConfigFromVisualSGIX = NULL; +PFNGLXGETVISUALFROMFBCONFIGSGIXPROC __glewXGetVisualFromFBConfigSGIX = NULL; + +PFNGLXBINDHYPERPIPESGIXPROC __glewXBindHyperpipeSGIX = NULL; +PFNGLXDESTROYHYPERPIPECONFIGSGIXPROC __glewXDestroyHyperpipeConfigSGIX = NULL; +PFNGLXHYPERPIPEATTRIBSGIXPROC __glewXHyperpipeAttribSGIX = NULL; +PFNGLXHYPERPIPECONFIGSGIXPROC __glewXHyperpipeConfigSGIX = NULL; +PFNGLXQUERYHYPERPIPEATTRIBSGIXPROC __glewXQueryHyperpipeAttribSGIX = NULL; +PFNGLXQUERYHYPERPIPEBESTATTRIBSGIXPROC __glewXQueryHyperpipeBestAttribSGIX = NULL; +PFNGLXQUERYHYPERPIPECONFIGSGIXPROC __glewXQueryHyperpipeConfigSGIX = NULL; +PFNGLXQUERYHYPERPIPENETWORKSGIXPROC __glewXQueryHyperpipeNetworkSGIX = NULL; + +PFNGLXCREATEGLXPBUFFERSGIXPROC __glewXCreateGLXPbufferSGIX = NULL; +PFNGLXDESTROYGLXPBUFFERSGIXPROC __glewXDestroyGLXPbufferSGIX = NULL; +PFNGLXGETSELECTEDEVENTSGIXPROC __glewXGetSelectedEventSGIX = NULL; +PFNGLXQUERYGLXPBUFFERSGIXPROC __glewXQueryGLXPbufferSGIX = NULL; +PFNGLXSELECTEVENTSGIXPROC __glewXSelectEventSGIX = NULL; + +PFNGLXBINDSWAPBARRIERSGIXPROC __glewXBindSwapBarrierSGIX = NULL; +PFNGLXQUERYMAXSWAPBARRIERSSGIXPROC __glewXQueryMaxSwapBarriersSGIX = NULL; + +PFNGLXJOINSWAPGROUPSGIXPROC __glewXJoinSwapGroupSGIX = NULL; + +PFNGLXBINDCHANNELTOWINDOWSGIXPROC __glewXBindChannelToWindowSGIX = NULL; +PFNGLXCHANNELRECTSGIXPROC __glewXChannelRectSGIX = NULL; +PFNGLXCHANNELRECTSYNCSGIXPROC __glewXChannelRectSyncSGIX = NULL; +PFNGLXQUERYCHANNELDELTASSGIXPROC __glewXQueryChannelDeltasSGIX = NULL; +PFNGLXQUERYCHANNELRECTSGIXPROC __glewXQueryChannelRectSGIX = NULL; + +PFNGLXCUSHIONSGIPROC __glewXCushionSGI = NULL; + +PFNGLXGETCURRENTREADDRAWABLESGIPROC __glewXGetCurrentReadDrawableSGI = NULL; +PFNGLXMAKECURRENTREADSGIPROC __glewXMakeCurrentReadSGI = NULL; + +PFNGLXSWAPINTERVALSGIPROC __glewXSwapIntervalSGI = NULL; + +PFNGLXGETVIDEOSYNCSGIPROC __glewXGetVideoSyncSGI = NULL; +PFNGLXWAITVIDEOSYNCSGIPROC __glewXWaitVideoSyncSGI = NULL; + +PFNGLXGETTRANSPARENTINDEXSUNPROC __glewXGetTransparentIndexSUN = NULL; + +PFNGLXGETVIDEORESIZESUNPROC __glewXGetVideoResizeSUN = NULL; +PFNGLXVIDEORESIZESUNPROC __glewXVideoResizeSUN = NULL; + +#if !defined(GLEW_MX) + +GLboolean __GLXEW_VERSION_1_0 = GL_FALSE; +GLboolean __GLXEW_VERSION_1_1 = GL_FALSE; +GLboolean __GLXEW_VERSION_1_2 = GL_FALSE; +GLboolean __GLXEW_VERSION_1_3 = GL_FALSE; +GLboolean __GLXEW_VERSION_1_4 = GL_FALSE; +GLboolean __GLXEW_3DFX_multisample = GL_FALSE; +GLboolean __GLXEW_ARB_create_context = GL_FALSE; +GLboolean __GLXEW_ARB_fbconfig_float = GL_FALSE; +GLboolean __GLXEW_ARB_framebuffer_sRGB = GL_FALSE; +GLboolean __GLXEW_ARB_get_proc_address = GL_FALSE; +GLboolean __GLXEW_ARB_multisample = GL_FALSE; +GLboolean __GLXEW_ATI_pixel_format_float = GL_FALSE; +GLboolean __GLXEW_ATI_render_texture = GL_FALSE; +GLboolean __GLXEW_EXT_fbconfig_packed_float = GL_FALSE; +GLboolean __GLXEW_EXT_framebuffer_sRGB = GL_FALSE; +GLboolean __GLXEW_EXT_import_context = GL_FALSE; +GLboolean __GLXEW_EXT_scene_marker = GL_FALSE; +GLboolean __GLXEW_EXT_texture_from_pixmap = GL_FALSE; +GLboolean __GLXEW_EXT_visual_info = GL_FALSE; +GLboolean __GLXEW_EXT_visual_rating = GL_FALSE; +GLboolean __GLXEW_MESA_agp_offset = GL_FALSE; +GLboolean __GLXEW_MESA_copy_sub_buffer = GL_FALSE; +GLboolean __GLXEW_MESA_pixmap_colormap = GL_FALSE; +GLboolean __GLXEW_MESA_release_buffers = GL_FALSE; +GLboolean __GLXEW_MESA_set_3dfx_mode = GL_FALSE; +GLboolean __GLXEW_NV_float_buffer = GL_FALSE; +GLboolean __GLXEW_NV_present_video = GL_FALSE; +GLboolean __GLXEW_NV_swap_group = GL_FALSE; +GLboolean __GLXEW_NV_vertex_array_range = GL_FALSE; +GLboolean __GLXEW_NV_video_output = GL_FALSE; +GLboolean __GLXEW_OML_swap_method = GL_FALSE; +#ifdef GLX_OML_sync_control +GLboolean __GLXEW_OML_sync_control = GL_FALSE; +#endif +GLboolean __GLXEW_SGIS_blended_overlay = GL_FALSE; +GLboolean __GLXEW_SGIS_color_range = GL_FALSE; +GLboolean __GLXEW_SGIS_multisample = GL_FALSE; +GLboolean __GLXEW_SGIS_shared_multisample = GL_FALSE; +GLboolean __GLXEW_SGIX_fbconfig = GL_FALSE; +GLboolean __GLXEW_SGIX_hyperpipe = GL_FALSE; +GLboolean __GLXEW_SGIX_pbuffer = GL_FALSE; +GLboolean __GLXEW_SGIX_swap_barrier = GL_FALSE; +GLboolean __GLXEW_SGIX_swap_group = GL_FALSE; +GLboolean __GLXEW_SGIX_video_resize = GL_FALSE; +GLboolean __GLXEW_SGIX_visual_select_group = GL_FALSE; +GLboolean __GLXEW_SGI_cushion = GL_FALSE; +GLboolean __GLXEW_SGI_make_current_read = GL_FALSE; +GLboolean __GLXEW_SGI_swap_control = GL_FALSE; +GLboolean __GLXEW_SGI_video_sync = GL_FALSE; +GLboolean __GLXEW_SUN_get_transparent_index = GL_FALSE; +GLboolean __GLXEW_SUN_video_resize = GL_FALSE; + +#endif /* !GLEW_MX */ + +#ifdef GLX_VERSION_1_2 + +static GLboolean _glewInit_GLX_VERSION_1_2 (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXGetCurrentDisplay = (PFNGLXGETCURRENTDISPLAYPROC)glewGetProcAddress((const GLubyte*)"glXGetCurrentDisplay")) == NULL) || r; + + return r; +} + +#endif /* GLX_VERSION_1_2 */ + +#ifdef GLX_VERSION_1_3 + +static GLboolean _glewInit_GLX_VERSION_1_3 (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXChooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)glewGetProcAddress((const GLubyte*)"glXChooseFBConfig")) == NULL) || r; + r = ((glXCreateNewContext = (PFNGLXCREATENEWCONTEXTPROC)glewGetProcAddress((const GLubyte*)"glXCreateNewContext")) == NULL) || r; + r = ((glXCreatePbuffer = (PFNGLXCREATEPBUFFERPROC)glewGetProcAddress((const GLubyte*)"glXCreatePbuffer")) == NULL) || r; + r = ((glXCreatePixmap = (PFNGLXCREATEPIXMAPPROC)glewGetProcAddress((const GLubyte*)"glXCreatePixmap")) == NULL) || r; + r = ((glXCreateWindow = (PFNGLXCREATEWINDOWPROC)glewGetProcAddress((const GLubyte*)"glXCreateWindow")) == NULL) || r; + r = ((glXDestroyPbuffer = (PFNGLXDESTROYPBUFFERPROC)glewGetProcAddress((const GLubyte*)"glXDestroyPbuffer")) == NULL) || r; + r = ((glXDestroyPixmap = (PFNGLXDESTROYPIXMAPPROC)glewGetProcAddress((const GLubyte*)"glXDestroyPixmap")) == NULL) || r; + r = ((glXDestroyWindow = (PFNGLXDESTROYWINDOWPROC)glewGetProcAddress((const GLubyte*)"glXDestroyWindow")) == NULL) || r; + r = ((glXGetCurrentReadDrawable = (PFNGLXGETCURRENTREADDRAWABLEPROC)glewGetProcAddress((const GLubyte*)"glXGetCurrentReadDrawable")) == NULL) || r; + r = ((glXGetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC)glewGetProcAddress((const GLubyte*)"glXGetFBConfigAttrib")) == NULL) || r; + r = ((glXGetFBConfigs = (PFNGLXGETFBCONFIGSPROC)glewGetProcAddress((const GLubyte*)"glXGetFBConfigs")) == NULL) || r; + r = ((glXGetSelectedEvent = (PFNGLXGETSELECTEDEVENTPROC)glewGetProcAddress((const GLubyte*)"glXGetSelectedEvent")) == NULL) || r; + r = ((glXGetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC)glewGetProcAddress((const GLubyte*)"glXGetVisualFromFBConfig")) == NULL) || r; + r = ((glXMakeContextCurrent = (PFNGLXMAKECONTEXTCURRENTPROC)glewGetProcAddress((const GLubyte*)"glXMakeContextCurrent")) == NULL) || r; + r = ((glXQueryContext = (PFNGLXQUERYCONTEXTPROC)glewGetProcAddress((const GLubyte*)"glXQueryContext")) == NULL) || r; + r = ((glXQueryDrawable = (PFNGLXQUERYDRAWABLEPROC)glewGetProcAddress((const GLubyte*)"glXQueryDrawable")) == NULL) || r; + r = ((glXSelectEvent = (PFNGLXSELECTEVENTPROC)glewGetProcAddress((const GLubyte*)"glXSelectEvent")) == NULL) || r; + + return r; +} + +#endif /* GLX_VERSION_1_3 */ + +#ifdef GLX_VERSION_1_4 + +#endif /* GLX_VERSION_1_4 */ + +#ifdef GLX_3DFX_multisample + +#endif /* GLX_3DFX_multisample */ + +#ifdef GLX_ARB_create_context + +static GLboolean _glewInit_GLX_ARB_create_context (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glewGetProcAddress((const GLubyte*)"glXCreateContextAttribsARB")) == NULL) || r; + + return r; +} + +#endif /* GLX_ARB_create_context */ + +#ifdef GLX_ARB_fbconfig_float + +#endif /* GLX_ARB_fbconfig_float */ + +#ifdef GLX_ARB_framebuffer_sRGB + +#endif /* GLX_ARB_framebuffer_sRGB */ + +#ifdef GLX_ARB_get_proc_address + +#endif /* GLX_ARB_get_proc_address */ + +#ifdef GLX_ARB_multisample + +#endif /* GLX_ARB_multisample */ + +#ifdef GLX_ATI_pixel_format_float + +#endif /* GLX_ATI_pixel_format_float */ + +#ifdef GLX_ATI_render_texture + +static GLboolean _glewInit_GLX_ATI_render_texture (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindTexImageATI = (PFNGLXBINDTEXIMAGEATIPROC)glewGetProcAddress((const GLubyte*)"glXBindTexImageATI")) == NULL) || r; + r = ((glXDrawableAttribATI = (PFNGLXDRAWABLEATTRIBATIPROC)glewGetProcAddress((const GLubyte*)"glXDrawableAttribATI")) == NULL) || r; + r = ((glXReleaseTexImageATI = (PFNGLXRELEASETEXIMAGEATIPROC)glewGetProcAddress((const GLubyte*)"glXReleaseTexImageATI")) == NULL) || r; + + return r; +} + +#endif /* GLX_ATI_render_texture */ + +#ifdef GLX_EXT_fbconfig_packed_float + +#endif /* GLX_EXT_fbconfig_packed_float */ + +#ifdef GLX_EXT_framebuffer_sRGB + +#endif /* GLX_EXT_framebuffer_sRGB */ + +#ifdef GLX_EXT_import_context + +static GLboolean _glewInit_GLX_EXT_import_context (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXFreeContextEXT = (PFNGLXFREECONTEXTEXTPROC)glewGetProcAddress((const GLubyte*)"glXFreeContextEXT")) == NULL) || r; + r = ((glXGetContextIDEXT = (PFNGLXGETCONTEXTIDEXTPROC)glewGetProcAddress((const GLubyte*)"glXGetContextIDEXT")) == NULL) || r; + r = ((glXImportContextEXT = (PFNGLXIMPORTCONTEXTEXTPROC)glewGetProcAddress((const GLubyte*)"glXImportContextEXT")) == NULL) || r; + r = ((glXQueryContextInfoEXT = (PFNGLXQUERYCONTEXTINFOEXTPROC)glewGetProcAddress((const GLubyte*)"glXQueryContextInfoEXT")) == NULL) || r; + + return r; +} + +#endif /* GLX_EXT_import_context */ + +#ifdef GLX_EXT_scene_marker + +#endif /* GLX_EXT_scene_marker */ + +#ifdef GLX_EXT_texture_from_pixmap + +static GLboolean _glewInit_GLX_EXT_texture_from_pixmap (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindTexImageEXT = (PFNGLXBINDTEXIMAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glXBindTexImageEXT")) == NULL) || r; + r = ((glXReleaseTexImageEXT = (PFNGLXRELEASETEXIMAGEEXTPROC)glewGetProcAddress((const GLubyte*)"glXReleaseTexImageEXT")) == NULL) || r; + + return r; +} + +#endif /* GLX_EXT_texture_from_pixmap */ + +#ifdef GLX_EXT_visual_info + +#endif /* GLX_EXT_visual_info */ + +#ifdef GLX_EXT_visual_rating + +#endif /* GLX_EXT_visual_rating */ + +#ifdef GLX_MESA_agp_offset + +static GLboolean _glewInit_GLX_MESA_agp_offset (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXGetAGPOffsetMESA = (PFNGLXGETAGPOFFSETMESAPROC)glewGetProcAddress((const GLubyte*)"glXGetAGPOffsetMESA")) == NULL) || r; + + return r; +} + +#endif /* GLX_MESA_agp_offset */ + +#ifdef GLX_MESA_copy_sub_buffer + +static GLboolean _glewInit_GLX_MESA_copy_sub_buffer (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXCopySubBufferMESA = (PFNGLXCOPYSUBBUFFERMESAPROC)glewGetProcAddress((const GLubyte*)"glXCopySubBufferMESA")) == NULL) || r; + + return r; +} + +#endif /* GLX_MESA_copy_sub_buffer */ + +#ifdef GLX_MESA_pixmap_colormap + +static GLboolean _glewInit_GLX_MESA_pixmap_colormap (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXCreateGLXPixmapMESA = (PFNGLXCREATEGLXPIXMAPMESAPROC)glewGetProcAddress((const GLubyte*)"glXCreateGLXPixmapMESA")) == NULL) || r; + + return r; +} + +#endif /* GLX_MESA_pixmap_colormap */ + +#ifdef GLX_MESA_release_buffers + +static GLboolean _glewInit_GLX_MESA_release_buffers (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXReleaseBuffersMESA = (PFNGLXRELEASEBUFFERSMESAPROC)glewGetProcAddress((const GLubyte*)"glXReleaseBuffersMESA")) == NULL) || r; + + return r; +} + +#endif /* GLX_MESA_release_buffers */ + +#ifdef GLX_MESA_set_3dfx_mode + +static GLboolean _glewInit_GLX_MESA_set_3dfx_mode (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXSet3DfxModeMESA = (PFNGLXSET3DFXMODEMESAPROC)glewGetProcAddress((const GLubyte*)"glXSet3DfxModeMESA")) == NULL) || r; + + return r; +} + +#endif /* GLX_MESA_set_3dfx_mode */ + +#ifdef GLX_NV_float_buffer + +#endif /* GLX_NV_float_buffer */ + +#ifdef GLX_NV_present_video + +static GLboolean _glewInit_GLX_NV_present_video (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindVideoDeviceNV = (PFNGLXBINDVIDEODEVICENVPROC)glewGetProcAddress((const GLubyte*)"glXBindVideoDeviceNV")) == NULL) || r; + r = ((glXEnumerateVideoDevicesNV = (PFNGLXENUMERATEVIDEODEVICESNVPROC)glewGetProcAddress((const GLubyte*)"glXEnumerateVideoDevicesNV")) == NULL) || r; + + return r; +} + +#endif /* GLX_NV_present_video */ + +#ifdef GLX_NV_swap_group + +static GLboolean _glewInit_GLX_NV_swap_group (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindSwapBarrierNV = (PFNGLXBINDSWAPBARRIERNVPROC)glewGetProcAddress((const GLubyte*)"glXBindSwapBarrierNV")) == NULL) || r; + r = ((glXJoinSwapGroupNV = (PFNGLXJOINSWAPGROUPNVPROC)glewGetProcAddress((const GLubyte*)"glXJoinSwapGroupNV")) == NULL) || r; + r = ((glXQueryFrameCountNV = (PFNGLXQUERYFRAMECOUNTNVPROC)glewGetProcAddress((const GLubyte*)"glXQueryFrameCountNV")) == NULL) || r; + r = ((glXQueryMaxSwapGroupsNV = (PFNGLXQUERYMAXSWAPGROUPSNVPROC)glewGetProcAddress((const GLubyte*)"glXQueryMaxSwapGroupsNV")) == NULL) || r; + r = ((glXQuerySwapGroupNV = (PFNGLXQUERYSWAPGROUPNVPROC)glewGetProcAddress((const GLubyte*)"glXQuerySwapGroupNV")) == NULL) || r; + r = ((glXResetFrameCountNV = (PFNGLXRESETFRAMECOUNTNVPROC)glewGetProcAddress((const GLubyte*)"glXResetFrameCountNV")) == NULL) || r; + + return r; +} + +#endif /* GLX_NV_swap_group */ + +#ifdef GLX_NV_vertex_array_range + +static GLboolean _glewInit_GLX_NV_vertex_array_range (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXAllocateMemoryNV = (PFNGLXALLOCATEMEMORYNVPROC)glewGetProcAddress((const GLubyte*)"glXAllocateMemoryNV")) == NULL) || r; + r = ((glXFreeMemoryNV = (PFNGLXFREEMEMORYNVPROC)glewGetProcAddress((const GLubyte*)"glXFreeMemoryNV")) == NULL) || r; + + return r; +} + +#endif /* GLX_NV_vertex_array_range */ + +#ifdef GLX_NV_video_output + +static GLboolean _glewInit_GLX_NV_video_output (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindVideoImageNV = (PFNGLXBINDVIDEOIMAGENVPROC)glewGetProcAddress((const GLubyte*)"glXBindVideoImageNV")) == NULL) || r; + r = ((glXGetVideoDeviceNV = (PFNGLXGETVIDEODEVICENVPROC)glewGetProcAddress((const GLubyte*)"glXGetVideoDeviceNV")) == NULL) || r; + r = ((glXGetVideoInfoNV = (PFNGLXGETVIDEOINFONVPROC)glewGetProcAddress((const GLubyte*)"glXGetVideoInfoNV")) == NULL) || r; + r = ((glXReleaseVideoDeviceNV = (PFNGLXRELEASEVIDEODEVICENVPROC)glewGetProcAddress((const GLubyte*)"glXReleaseVideoDeviceNV")) == NULL) || r; + r = ((glXReleaseVideoImageNV = (PFNGLXRELEASEVIDEOIMAGENVPROC)glewGetProcAddress((const GLubyte*)"glXReleaseVideoImageNV")) == NULL) || r; + r = ((glXSendPbufferToVideoNV = (PFNGLXSENDPBUFFERTOVIDEONVPROC)glewGetProcAddress((const GLubyte*)"glXSendPbufferToVideoNV")) == NULL) || r; + + return r; +} + +#endif /* GLX_NV_video_output */ + +#ifdef GLX_OML_swap_method + +#endif /* GLX_OML_swap_method */ + +#if defined(GLX_OML_sync_control) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#include + +static GLboolean _glewInit_GLX_OML_sync_control (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXGetMscRateOML = (PFNGLXGETMSCRATEOMLPROC)glewGetProcAddress((const GLubyte*)"glXGetMscRateOML")) == NULL) || r; + r = ((glXGetSyncValuesOML = (PFNGLXGETSYNCVALUESOMLPROC)glewGetProcAddress((const GLubyte*)"glXGetSyncValuesOML")) == NULL) || r; + r = ((glXSwapBuffersMscOML = (PFNGLXSWAPBUFFERSMSCOMLPROC)glewGetProcAddress((const GLubyte*)"glXSwapBuffersMscOML")) == NULL) || r; + r = ((glXWaitForMscOML = (PFNGLXWAITFORMSCOMLPROC)glewGetProcAddress((const GLubyte*)"glXWaitForMscOML")) == NULL) || r; + r = ((glXWaitForSbcOML = (PFNGLXWAITFORSBCOMLPROC)glewGetProcAddress((const GLubyte*)"glXWaitForSbcOML")) == NULL) || r; + + return r; +} + +#endif /* GLX_OML_sync_control */ + +#ifdef GLX_SGIS_blended_overlay + +#endif /* GLX_SGIS_blended_overlay */ + +#ifdef GLX_SGIS_color_range + +#endif /* GLX_SGIS_color_range */ + +#ifdef GLX_SGIS_multisample + +#endif /* GLX_SGIS_multisample */ + +#ifdef GLX_SGIS_shared_multisample + +#endif /* GLX_SGIS_shared_multisample */ + +#ifdef GLX_SGIX_fbconfig + +static GLboolean _glewInit_GLX_SGIX_fbconfig (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXChooseFBConfigSGIX = (PFNGLXCHOOSEFBCONFIGSGIXPROC)glewGetProcAddress((const GLubyte*)"glXChooseFBConfigSGIX")) == NULL) || r; + r = ((glXCreateContextWithConfigSGIX = (PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC)glewGetProcAddress((const GLubyte*)"glXCreateContextWithConfigSGIX")) == NULL) || r; + r = ((glXCreateGLXPixmapWithConfigSGIX = (PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC)glewGetProcAddress((const GLubyte*)"glXCreateGLXPixmapWithConfigSGIX")) == NULL) || r; + r = ((glXGetFBConfigAttribSGIX = (PFNGLXGETFBCONFIGATTRIBSGIXPROC)glewGetProcAddress((const GLubyte*)"glXGetFBConfigAttribSGIX")) == NULL) || r; + r = ((glXGetFBConfigFromVisualSGIX = (PFNGLXGETFBCONFIGFROMVISUALSGIXPROC)glewGetProcAddress((const GLubyte*)"glXGetFBConfigFromVisualSGIX")) == NULL) || r; + r = ((glXGetVisualFromFBConfigSGIX = (PFNGLXGETVISUALFROMFBCONFIGSGIXPROC)glewGetProcAddress((const GLubyte*)"glXGetVisualFromFBConfigSGIX")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGIX_fbconfig */ + +#ifdef GLX_SGIX_hyperpipe + +static GLboolean _glewInit_GLX_SGIX_hyperpipe (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindHyperpipeSGIX = (PFNGLXBINDHYPERPIPESGIXPROC)glewGetProcAddress((const GLubyte*)"glXBindHyperpipeSGIX")) == NULL) || r; + r = ((glXDestroyHyperpipeConfigSGIX = (PFNGLXDESTROYHYPERPIPECONFIGSGIXPROC)glewGetProcAddress((const GLubyte*)"glXDestroyHyperpipeConfigSGIX")) == NULL) || r; + r = ((glXHyperpipeAttribSGIX = (PFNGLXHYPERPIPEATTRIBSGIXPROC)glewGetProcAddress((const GLubyte*)"glXHyperpipeAttribSGIX")) == NULL) || r; + r = ((glXHyperpipeConfigSGIX = (PFNGLXHYPERPIPECONFIGSGIXPROC)glewGetProcAddress((const GLubyte*)"glXHyperpipeConfigSGIX")) == NULL) || r; + r = ((glXQueryHyperpipeAttribSGIX = (PFNGLXQUERYHYPERPIPEATTRIBSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryHyperpipeAttribSGIX")) == NULL) || r; + r = ((glXQueryHyperpipeBestAttribSGIX = (PFNGLXQUERYHYPERPIPEBESTATTRIBSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryHyperpipeBestAttribSGIX")) == NULL) || r; + r = ((glXQueryHyperpipeConfigSGIX = (PFNGLXQUERYHYPERPIPECONFIGSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryHyperpipeConfigSGIX")) == NULL) || r; + r = ((glXQueryHyperpipeNetworkSGIX = (PFNGLXQUERYHYPERPIPENETWORKSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryHyperpipeNetworkSGIX")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGIX_hyperpipe */ + +#ifdef GLX_SGIX_pbuffer + +static GLboolean _glewInit_GLX_SGIX_pbuffer (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXCreateGLXPbufferSGIX = (PFNGLXCREATEGLXPBUFFERSGIXPROC)glewGetProcAddress((const GLubyte*)"glXCreateGLXPbufferSGIX")) == NULL) || r; + r = ((glXDestroyGLXPbufferSGIX = (PFNGLXDESTROYGLXPBUFFERSGIXPROC)glewGetProcAddress((const GLubyte*)"glXDestroyGLXPbufferSGIX")) == NULL) || r; + r = ((glXGetSelectedEventSGIX = (PFNGLXGETSELECTEDEVENTSGIXPROC)glewGetProcAddress((const GLubyte*)"glXGetSelectedEventSGIX")) == NULL) || r; + r = ((glXQueryGLXPbufferSGIX = (PFNGLXQUERYGLXPBUFFERSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryGLXPbufferSGIX")) == NULL) || r; + r = ((glXSelectEventSGIX = (PFNGLXSELECTEVENTSGIXPROC)glewGetProcAddress((const GLubyte*)"glXSelectEventSGIX")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGIX_pbuffer */ + +#ifdef GLX_SGIX_swap_barrier + +static GLboolean _glewInit_GLX_SGIX_swap_barrier (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindSwapBarrierSGIX = (PFNGLXBINDSWAPBARRIERSGIXPROC)glewGetProcAddress((const GLubyte*)"glXBindSwapBarrierSGIX")) == NULL) || r; + r = ((glXQueryMaxSwapBarriersSGIX = (PFNGLXQUERYMAXSWAPBARRIERSSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryMaxSwapBarriersSGIX")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGIX_swap_barrier */ + +#ifdef GLX_SGIX_swap_group + +static GLboolean _glewInit_GLX_SGIX_swap_group (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXJoinSwapGroupSGIX = (PFNGLXJOINSWAPGROUPSGIXPROC)glewGetProcAddress((const GLubyte*)"glXJoinSwapGroupSGIX")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGIX_swap_group */ + +#ifdef GLX_SGIX_video_resize + +static GLboolean _glewInit_GLX_SGIX_video_resize (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXBindChannelToWindowSGIX = (PFNGLXBINDCHANNELTOWINDOWSGIXPROC)glewGetProcAddress((const GLubyte*)"glXBindChannelToWindowSGIX")) == NULL) || r; + r = ((glXChannelRectSGIX = (PFNGLXCHANNELRECTSGIXPROC)glewGetProcAddress((const GLubyte*)"glXChannelRectSGIX")) == NULL) || r; + r = ((glXChannelRectSyncSGIX = (PFNGLXCHANNELRECTSYNCSGIXPROC)glewGetProcAddress((const GLubyte*)"glXChannelRectSyncSGIX")) == NULL) || r; + r = ((glXQueryChannelDeltasSGIX = (PFNGLXQUERYCHANNELDELTASSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryChannelDeltasSGIX")) == NULL) || r; + r = ((glXQueryChannelRectSGIX = (PFNGLXQUERYCHANNELRECTSGIXPROC)glewGetProcAddress((const GLubyte*)"glXQueryChannelRectSGIX")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGIX_video_resize */ + +#ifdef GLX_SGIX_visual_select_group + +#endif /* GLX_SGIX_visual_select_group */ + +#ifdef GLX_SGI_cushion + +static GLboolean _glewInit_GLX_SGI_cushion (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXCushionSGI = (PFNGLXCUSHIONSGIPROC)glewGetProcAddress((const GLubyte*)"glXCushionSGI")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGI_cushion */ + +#ifdef GLX_SGI_make_current_read + +static GLboolean _glewInit_GLX_SGI_make_current_read (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXGetCurrentReadDrawableSGI = (PFNGLXGETCURRENTREADDRAWABLESGIPROC)glewGetProcAddress((const GLubyte*)"glXGetCurrentReadDrawableSGI")) == NULL) || r; + r = ((glXMakeCurrentReadSGI = (PFNGLXMAKECURRENTREADSGIPROC)glewGetProcAddress((const GLubyte*)"glXMakeCurrentReadSGI")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGI_make_current_read */ + +#ifdef GLX_SGI_swap_control + +static GLboolean _glewInit_GLX_SGI_swap_control (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glewGetProcAddress((const GLubyte*)"glXSwapIntervalSGI")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGI_swap_control */ + +#ifdef GLX_SGI_video_sync + +static GLboolean _glewInit_GLX_SGI_video_sync (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC)glewGetProcAddress((const GLubyte*)"glXGetVideoSyncSGI")) == NULL) || r; + r = ((glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glewGetProcAddress((const GLubyte*)"glXWaitVideoSyncSGI")) == NULL) || r; + + return r; +} + +#endif /* GLX_SGI_video_sync */ + +#ifdef GLX_SUN_get_transparent_index + +static GLboolean _glewInit_GLX_SUN_get_transparent_index (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXGetTransparentIndexSUN = (PFNGLXGETTRANSPARENTINDEXSUNPROC)glewGetProcAddress((const GLubyte*)"glXGetTransparentIndexSUN")) == NULL) || r; + + return r; +} + +#endif /* GLX_SUN_get_transparent_index */ + +#ifdef GLX_SUN_video_resize + +static GLboolean _glewInit_GLX_SUN_video_resize (GLXEW_CONTEXT_ARG_DEF_INIT) +{ + GLboolean r = GL_FALSE; + + r = ((glXGetVideoResizeSUN = (PFNGLXGETVIDEORESIZESUNPROC)glewGetProcAddress((const GLubyte*)"glXGetVideoResizeSUN")) == NULL) || r; + r = ((glXVideoResizeSUN = (PFNGLXVIDEORESIZESUNPROC)glewGetProcAddress((const GLubyte*)"glXVideoResizeSUN")) == NULL) || r; + + return r; +} + +#endif /* GLX_SUN_video_resize */ + +/* ------------------------------------------------------------------------ */ + +GLboolean glxewGetExtension (const char* name) +{ + GLubyte* p; + GLubyte* end; + GLuint len = _glewStrLen((const GLubyte*)name); +/* if (glXQueryExtensionsString == NULL || glXGetCurrentDisplay == NULL) return GL_FALSE; */ +/* p = (GLubyte*)glXQueryExtensionsString(glXGetCurrentDisplay(), DefaultScreen(glXGetCurrentDisplay())); */ + if (glXGetClientString == NULL || glXGetCurrentDisplay == NULL) return GL_FALSE; + p = (GLubyte*)glXGetClientString(glXGetCurrentDisplay(), GLX_EXTENSIONS); + if (0 == p) return GL_FALSE; + end = p + _glewStrLen(p); + while (p < end) + { + GLuint n = _glewStrCLen(p, ' '); + if (len == n && _glewStrSame((const GLubyte*)name, p, n)) return GL_TRUE; + p += n+1; + } + return GL_FALSE; +} + +GLenum glxewContextInit (GLXEW_CONTEXT_ARG_DEF_LIST) +{ + int major, minor; + /* initialize core GLX 1.2 */ + if (_glewInit_GLX_VERSION_1_2(GLEW_CONTEXT_ARG_VAR_INIT)) return GLEW_ERROR_GLX_VERSION_11_ONLY; + /* initialize flags */ + CONST_CAST(GLXEW_VERSION_1_0) = GL_TRUE; + CONST_CAST(GLXEW_VERSION_1_1) = GL_TRUE; + CONST_CAST(GLXEW_VERSION_1_2) = GL_TRUE; + CONST_CAST(GLXEW_VERSION_1_3) = GL_TRUE; + CONST_CAST(GLXEW_VERSION_1_4) = GL_TRUE; + /* query GLX version */ + glXQueryVersion(glXGetCurrentDisplay(), &major, &minor); + if (major == 1 && minor <= 3) + { + switch (minor) + { + case 3: + CONST_CAST(GLXEW_VERSION_1_4) = GL_FALSE; + break; + case 2: + CONST_CAST(GLXEW_VERSION_1_4) = GL_FALSE; + CONST_CAST(GLXEW_VERSION_1_3) = GL_FALSE; + break; + default: + return GLEW_ERROR_GLX_VERSION_11_ONLY; + break; + } + } + /* initialize extensions */ +#ifdef GLX_VERSION_1_3 + if (glewExperimental || GLXEW_VERSION_1_3) CONST_CAST(GLXEW_VERSION_1_3) = !_glewInit_GLX_VERSION_1_3(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_VERSION_1_3 */ +#ifdef GLX_3DFX_multisample + CONST_CAST(GLXEW_3DFX_multisample) = glxewGetExtension("GLX_3DFX_multisample"); +#endif /* GLX_3DFX_multisample */ +#ifdef GLX_ARB_create_context + CONST_CAST(GLXEW_ARB_create_context) = glxewGetExtension("GLX_ARB_create_context"); + if (glewExperimental || GLXEW_ARB_create_context) CONST_CAST(GLXEW_ARB_create_context) = !_glewInit_GLX_ARB_create_context(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_ARB_create_context */ +#ifdef GLX_ARB_fbconfig_float + CONST_CAST(GLXEW_ARB_fbconfig_float) = glxewGetExtension("GLX_ARB_fbconfig_float"); +#endif /* GLX_ARB_fbconfig_float */ +#ifdef GLX_ARB_framebuffer_sRGB + CONST_CAST(GLXEW_ARB_framebuffer_sRGB) = glxewGetExtension("GLX_ARB_framebuffer_sRGB"); +#endif /* GLX_ARB_framebuffer_sRGB */ +#ifdef GLX_ARB_get_proc_address + CONST_CAST(GLXEW_ARB_get_proc_address) = glxewGetExtension("GLX_ARB_get_proc_address"); +#endif /* GLX_ARB_get_proc_address */ +#ifdef GLX_ARB_multisample + CONST_CAST(GLXEW_ARB_multisample) = glxewGetExtension("GLX_ARB_multisample"); +#endif /* GLX_ARB_multisample */ +#ifdef GLX_ATI_pixel_format_float + CONST_CAST(GLXEW_ATI_pixel_format_float) = glxewGetExtension("GLX_ATI_pixel_format_float"); +#endif /* GLX_ATI_pixel_format_float */ +#ifdef GLX_ATI_render_texture + CONST_CAST(GLXEW_ATI_render_texture) = glxewGetExtension("GLX_ATI_render_texture"); + if (glewExperimental || GLXEW_ATI_render_texture) CONST_CAST(GLXEW_ATI_render_texture) = !_glewInit_GLX_ATI_render_texture(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_ATI_render_texture */ +#ifdef GLX_EXT_fbconfig_packed_float + CONST_CAST(GLXEW_EXT_fbconfig_packed_float) = glxewGetExtension("GLX_EXT_fbconfig_packed_float"); +#endif /* GLX_EXT_fbconfig_packed_float */ +#ifdef GLX_EXT_framebuffer_sRGB + CONST_CAST(GLXEW_EXT_framebuffer_sRGB) = glxewGetExtension("GLX_EXT_framebuffer_sRGB"); +#endif /* GLX_EXT_framebuffer_sRGB */ +#ifdef GLX_EXT_import_context + CONST_CAST(GLXEW_EXT_import_context) = glxewGetExtension("GLX_EXT_import_context"); + if (glewExperimental || GLXEW_EXT_import_context) CONST_CAST(GLXEW_EXT_import_context) = !_glewInit_GLX_EXT_import_context(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_EXT_import_context */ +#ifdef GLX_EXT_scene_marker + CONST_CAST(GLXEW_EXT_scene_marker) = glxewGetExtension("GLX_EXT_scene_marker"); +#endif /* GLX_EXT_scene_marker */ +#ifdef GLX_EXT_texture_from_pixmap + CONST_CAST(GLXEW_EXT_texture_from_pixmap) = glxewGetExtension("GLX_EXT_texture_from_pixmap"); + if (glewExperimental || GLXEW_EXT_texture_from_pixmap) CONST_CAST(GLXEW_EXT_texture_from_pixmap) = !_glewInit_GLX_EXT_texture_from_pixmap(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_EXT_texture_from_pixmap */ +#ifdef GLX_EXT_visual_info + CONST_CAST(GLXEW_EXT_visual_info) = glxewGetExtension("GLX_EXT_visual_info"); +#endif /* GLX_EXT_visual_info */ +#ifdef GLX_EXT_visual_rating + CONST_CAST(GLXEW_EXT_visual_rating) = glxewGetExtension("GLX_EXT_visual_rating"); +#endif /* GLX_EXT_visual_rating */ +#ifdef GLX_MESA_agp_offset + CONST_CAST(GLXEW_MESA_agp_offset) = glxewGetExtension("GLX_MESA_agp_offset"); + if (glewExperimental || GLXEW_MESA_agp_offset) CONST_CAST(GLXEW_MESA_agp_offset) = !_glewInit_GLX_MESA_agp_offset(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_MESA_agp_offset */ +#ifdef GLX_MESA_copy_sub_buffer + CONST_CAST(GLXEW_MESA_copy_sub_buffer) = glxewGetExtension("GLX_MESA_copy_sub_buffer"); + if (glewExperimental || GLXEW_MESA_copy_sub_buffer) CONST_CAST(GLXEW_MESA_copy_sub_buffer) = !_glewInit_GLX_MESA_copy_sub_buffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_MESA_copy_sub_buffer */ +#ifdef GLX_MESA_pixmap_colormap + CONST_CAST(GLXEW_MESA_pixmap_colormap) = glxewGetExtension("GLX_MESA_pixmap_colormap"); + if (glewExperimental || GLXEW_MESA_pixmap_colormap) CONST_CAST(GLXEW_MESA_pixmap_colormap) = !_glewInit_GLX_MESA_pixmap_colormap(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_MESA_pixmap_colormap */ +#ifdef GLX_MESA_release_buffers + CONST_CAST(GLXEW_MESA_release_buffers) = glxewGetExtension("GLX_MESA_release_buffers"); + if (glewExperimental || GLXEW_MESA_release_buffers) CONST_CAST(GLXEW_MESA_release_buffers) = !_glewInit_GLX_MESA_release_buffers(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_MESA_release_buffers */ +#ifdef GLX_MESA_set_3dfx_mode + CONST_CAST(GLXEW_MESA_set_3dfx_mode) = glxewGetExtension("GLX_MESA_set_3dfx_mode"); + if (glewExperimental || GLXEW_MESA_set_3dfx_mode) CONST_CAST(GLXEW_MESA_set_3dfx_mode) = !_glewInit_GLX_MESA_set_3dfx_mode(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_MESA_set_3dfx_mode */ +#ifdef GLX_NV_float_buffer + CONST_CAST(GLXEW_NV_float_buffer) = glxewGetExtension("GLX_NV_float_buffer"); +#endif /* GLX_NV_float_buffer */ +#ifdef GLX_NV_present_video + CONST_CAST(GLXEW_NV_present_video) = glxewGetExtension("GLX_NV_present_video"); + if (glewExperimental || GLXEW_NV_present_video) CONST_CAST(GLXEW_NV_present_video) = !_glewInit_GLX_NV_present_video(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_NV_present_video */ +#ifdef GLX_NV_swap_group + CONST_CAST(GLXEW_NV_swap_group) = glxewGetExtension("GLX_NV_swap_group"); + if (glewExperimental || GLXEW_NV_swap_group) CONST_CAST(GLXEW_NV_swap_group) = !_glewInit_GLX_NV_swap_group(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_NV_swap_group */ +#ifdef GLX_NV_vertex_array_range + CONST_CAST(GLXEW_NV_vertex_array_range) = glxewGetExtension("GLX_NV_vertex_array_range"); + if (glewExperimental || GLXEW_NV_vertex_array_range) CONST_CAST(GLXEW_NV_vertex_array_range) = !_glewInit_GLX_NV_vertex_array_range(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_NV_vertex_array_range */ +#ifdef GLX_NV_video_output + CONST_CAST(GLXEW_NV_video_output) = glxewGetExtension("GLX_NV_video_output"); + if (glewExperimental || GLXEW_NV_video_output) CONST_CAST(GLXEW_NV_video_output) = !_glewInit_GLX_NV_video_output(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_NV_video_output */ +#ifdef GLX_OML_swap_method + CONST_CAST(GLXEW_OML_swap_method) = glxewGetExtension("GLX_OML_swap_method"); +#endif /* GLX_OML_swap_method */ +#if defined(GLX_OML_sync_control) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#include + CONST_CAST(GLXEW_OML_sync_control) = glxewGetExtension("GLX_OML_sync_control"); + if (glewExperimental || GLXEW_OML_sync_control) CONST_CAST(GLXEW_OML_sync_control) = !_glewInit_GLX_OML_sync_control(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_OML_sync_control */ +#ifdef GLX_SGIS_blended_overlay + CONST_CAST(GLXEW_SGIS_blended_overlay) = glxewGetExtension("GLX_SGIS_blended_overlay"); +#endif /* GLX_SGIS_blended_overlay */ +#ifdef GLX_SGIS_color_range + CONST_CAST(GLXEW_SGIS_color_range) = glxewGetExtension("GLX_SGIS_color_range"); +#endif /* GLX_SGIS_color_range */ +#ifdef GLX_SGIS_multisample + CONST_CAST(GLXEW_SGIS_multisample) = glxewGetExtension("GLX_SGIS_multisample"); +#endif /* GLX_SGIS_multisample */ +#ifdef GLX_SGIS_shared_multisample + CONST_CAST(GLXEW_SGIS_shared_multisample) = glxewGetExtension("GLX_SGIS_shared_multisample"); +#endif /* GLX_SGIS_shared_multisample */ +#ifdef GLX_SGIX_fbconfig + CONST_CAST(GLXEW_SGIX_fbconfig) = glxewGetExtension("GLX_SGIX_fbconfig"); + if (glewExperimental || GLXEW_SGIX_fbconfig) CONST_CAST(GLXEW_SGIX_fbconfig) = !_glewInit_GLX_SGIX_fbconfig(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGIX_fbconfig */ +#ifdef GLX_SGIX_hyperpipe + CONST_CAST(GLXEW_SGIX_hyperpipe) = glxewGetExtension("GLX_SGIX_hyperpipe"); + if (glewExperimental || GLXEW_SGIX_hyperpipe) CONST_CAST(GLXEW_SGIX_hyperpipe) = !_glewInit_GLX_SGIX_hyperpipe(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGIX_hyperpipe */ +#ifdef GLX_SGIX_pbuffer + CONST_CAST(GLXEW_SGIX_pbuffer) = glxewGetExtension("GLX_SGIX_pbuffer"); + if (glewExperimental || GLXEW_SGIX_pbuffer) CONST_CAST(GLXEW_SGIX_pbuffer) = !_glewInit_GLX_SGIX_pbuffer(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGIX_pbuffer */ +#ifdef GLX_SGIX_swap_barrier + CONST_CAST(GLXEW_SGIX_swap_barrier) = glxewGetExtension("GLX_SGIX_swap_barrier"); + if (glewExperimental || GLXEW_SGIX_swap_barrier) CONST_CAST(GLXEW_SGIX_swap_barrier) = !_glewInit_GLX_SGIX_swap_barrier(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGIX_swap_barrier */ +#ifdef GLX_SGIX_swap_group + CONST_CAST(GLXEW_SGIX_swap_group) = glxewGetExtension("GLX_SGIX_swap_group"); + if (glewExperimental || GLXEW_SGIX_swap_group) CONST_CAST(GLXEW_SGIX_swap_group) = !_glewInit_GLX_SGIX_swap_group(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGIX_swap_group */ +#ifdef GLX_SGIX_video_resize + CONST_CAST(GLXEW_SGIX_video_resize) = glxewGetExtension("GLX_SGIX_video_resize"); + if (glewExperimental || GLXEW_SGIX_video_resize) CONST_CAST(GLXEW_SGIX_video_resize) = !_glewInit_GLX_SGIX_video_resize(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGIX_video_resize */ +#ifdef GLX_SGIX_visual_select_group + CONST_CAST(GLXEW_SGIX_visual_select_group) = glxewGetExtension("GLX_SGIX_visual_select_group"); +#endif /* GLX_SGIX_visual_select_group */ +#ifdef GLX_SGI_cushion + CONST_CAST(GLXEW_SGI_cushion) = glxewGetExtension("GLX_SGI_cushion"); + if (glewExperimental || GLXEW_SGI_cushion) CONST_CAST(GLXEW_SGI_cushion) = !_glewInit_GLX_SGI_cushion(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGI_cushion */ +#ifdef GLX_SGI_make_current_read + CONST_CAST(GLXEW_SGI_make_current_read) = glxewGetExtension("GLX_SGI_make_current_read"); + if (glewExperimental || GLXEW_SGI_make_current_read) CONST_CAST(GLXEW_SGI_make_current_read) = !_glewInit_GLX_SGI_make_current_read(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGI_make_current_read */ +#ifdef GLX_SGI_swap_control + CONST_CAST(GLXEW_SGI_swap_control) = glxewGetExtension("GLX_SGI_swap_control"); + if (glewExperimental || GLXEW_SGI_swap_control) CONST_CAST(GLXEW_SGI_swap_control) = !_glewInit_GLX_SGI_swap_control(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGI_swap_control */ +#ifdef GLX_SGI_video_sync + CONST_CAST(GLXEW_SGI_video_sync) = glxewGetExtension("GLX_SGI_video_sync"); + if (glewExperimental || GLXEW_SGI_video_sync) CONST_CAST(GLXEW_SGI_video_sync) = !_glewInit_GLX_SGI_video_sync(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SGI_video_sync */ +#ifdef GLX_SUN_get_transparent_index + CONST_CAST(GLXEW_SUN_get_transparent_index) = glxewGetExtension("GLX_SUN_get_transparent_index"); + if (glewExperimental || GLXEW_SUN_get_transparent_index) CONST_CAST(GLXEW_SUN_get_transparent_index) = !_glewInit_GLX_SUN_get_transparent_index(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SUN_get_transparent_index */ +#ifdef GLX_SUN_video_resize + CONST_CAST(GLXEW_SUN_video_resize) = glxewGetExtension("GLX_SUN_video_resize"); + if (glewExperimental || GLXEW_SUN_video_resize) CONST_CAST(GLXEW_SUN_video_resize) = !_glewInit_GLX_SUN_video_resize(GLEW_CONTEXT_ARG_VAR_INIT); +#endif /* GLX_SUN_video_resize */ + + return GLEW_OK; +} + +#endif /* !__APPLE__ || GLEW_APPLE_GLX */ + +/* ------------------------------------------------------------------------ */ + +const GLubyte* glewGetErrorString (GLenum error) +{ + static const GLubyte* _glewErrorString[] = + { + (const GLubyte*)"No error", + (const GLubyte*)"Missing GL version", + (const GLubyte*)"GL 1.1 and up are not supported", + (const GLubyte*)"GLX 1.2 and up are not supported", + (const GLubyte*)"Unknown error" + }; + const int max_error = sizeof(_glewErrorString)/sizeof(*_glewErrorString) - 1; + return _glewErrorString[(int)error > max_error ? max_error : (int)error]; +} + +const GLubyte* glewGetString (GLenum name) +{ + static const GLubyte* _glewString[] = + { + (const GLubyte*)NULL, + (const GLubyte*)"1.5.1", + (const GLubyte*)"1", + (const GLubyte*)"5", + (const GLubyte*)"1" + }; + const int max_string = sizeof(_glewString)/sizeof(*_glewString) - 1; + return _glewString[(int)name > max_string ? 0 : (int)name]; +} + +/* ------------------------------------------------------------------------ */ + +GLboolean glewExperimental = GL_FALSE; + +#if !defined(GLEW_MX) + +#if defined(_WIN32) +extern GLenum wglewContextInit (void); +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) /* _UNIX */ +extern GLenum glxewContextInit (void); +#endif /* _WIN32 */ + +GLenum glewInit () +{ + GLenum r; + if ( (r = glewContextInit()) ) return r; +#if defined(_WIN32) + return wglewContextInit(); +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) /* _UNIX */ + return glxewContextInit(); +#else + return r; +#endif /* _WIN32 */ +} + +#endif /* !GLEW_MX */ +#ifdef GLEW_MX +GLboolean glewContextIsSupported (GLEWContext* ctx, const char* name) +#else +GLboolean glewIsSupported (const char* name) +#endif +{ + GLubyte* pos = (GLubyte*)name; + GLuint len = _glewStrLen(pos); + GLboolean ret = GL_TRUE; + while (ret && len > 0) + { + if (_glewStrSame1(&pos, &len, (const GLubyte*)"GL_", 3)) + { + if (_glewStrSame2(&pos, &len, (const GLubyte*)"VERSION_", 8)) + { +#ifdef GL_VERSION_1_2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"1_2", 3)) + { + ret = GLEW_VERSION_1_2; + continue; + } +#endif +#ifdef GL_VERSION_1_3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"1_3", 3)) + { + ret = GLEW_VERSION_1_3; + continue; + } +#endif +#ifdef GL_VERSION_1_4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"1_4", 3)) + { + ret = GLEW_VERSION_1_4; + continue; + } +#endif +#ifdef GL_VERSION_1_5 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"1_5", 3)) + { + ret = GLEW_VERSION_1_5; + continue; + } +#endif +#ifdef GL_VERSION_2_0 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"2_0", 3)) + { + ret = GLEW_VERSION_2_0; + continue; + } +#endif +#ifdef GL_VERSION_2_1 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"2_1", 3)) + { + ret = GLEW_VERSION_2_1; + continue; + } +#endif +#ifdef GL_VERSION_3_0 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"3_0", 3)) + { + ret = GLEW_VERSION_3_0; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"3DFX_", 5)) + { +#ifdef GL_3DFX_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = GLEW_3DFX_multisample; + continue; + } +#endif +#ifdef GL_3DFX_tbuffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"tbuffer", 7)) + { + ret = GLEW_3DFX_tbuffer; + continue; + } +#endif +#ifdef GL_3DFX_texture_compression_FXT1 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_FXT1", 24)) + { + ret = GLEW_3DFX_texture_compression_FXT1; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"APPLE_", 6)) + { +#ifdef GL_APPLE_client_storage + if (_glewStrSame3(&pos, &len, (const GLubyte*)"client_storage", 14)) + { + ret = GLEW_APPLE_client_storage; + continue; + } +#endif +#ifdef GL_APPLE_element_array + if (_glewStrSame3(&pos, &len, (const GLubyte*)"element_array", 13)) + { + ret = GLEW_APPLE_element_array; + continue; + } +#endif +#ifdef GL_APPLE_fence + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fence", 5)) + { + ret = GLEW_APPLE_fence; + continue; + } +#endif +#ifdef GL_APPLE_float_pixels + if (_glewStrSame3(&pos, &len, (const GLubyte*)"float_pixels", 12)) + { + ret = GLEW_APPLE_float_pixels; + continue; + } +#endif +#ifdef GL_APPLE_flush_buffer_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"flush_buffer_range", 18)) + { + ret = GLEW_APPLE_flush_buffer_range; + continue; + } +#endif +#ifdef GL_APPLE_pixel_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_buffer", 12)) + { + ret = GLEW_APPLE_pixel_buffer; + continue; + } +#endif +#ifdef GL_APPLE_specular_vector + if (_glewStrSame3(&pos, &len, (const GLubyte*)"specular_vector", 15)) + { + ret = GLEW_APPLE_specular_vector; + continue; + } +#endif +#ifdef GL_APPLE_texture_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_range", 13)) + { + ret = GLEW_APPLE_texture_range; + continue; + } +#endif +#ifdef GL_APPLE_transform_hint + if (_glewStrSame3(&pos, &len, (const GLubyte*)"transform_hint", 14)) + { + ret = GLEW_APPLE_transform_hint; + continue; + } +#endif +#ifdef GL_APPLE_vertex_array_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_object", 19)) + { + ret = GLEW_APPLE_vertex_array_object; + continue; + } +#endif +#ifdef GL_APPLE_vertex_array_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_range", 18)) + { + ret = GLEW_APPLE_vertex_array_range; + continue; + } +#endif +#ifdef GL_APPLE_ycbcr_422 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"ycbcr_422", 9)) + { + ret = GLEW_APPLE_ycbcr_422; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"ARB_", 4)) + { +#ifdef GL_ARB_color_buffer_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"color_buffer_float", 18)) + { + ret = GLEW_ARB_color_buffer_float; + continue; + } +#endif +#ifdef GL_ARB_depth_buffer_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_buffer_float", 18)) + { + ret = GLEW_ARB_depth_buffer_float; + continue; + } +#endif +#ifdef GL_ARB_depth_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_texture", 13)) + { + ret = GLEW_ARB_depth_texture; + continue; + } +#endif +#ifdef GL_ARB_draw_buffers + if (_glewStrSame3(&pos, &len, (const GLubyte*)"draw_buffers", 12)) + { + ret = GLEW_ARB_draw_buffers; + continue; + } +#endif +#ifdef GL_ARB_draw_instanced + if (_glewStrSame3(&pos, &len, (const GLubyte*)"draw_instanced", 14)) + { + ret = GLEW_ARB_draw_instanced; + continue; + } +#endif +#ifdef GL_ARB_fragment_program + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_program", 16)) + { + ret = GLEW_ARB_fragment_program; + continue; + } +#endif +#ifdef GL_ARB_fragment_program_shadow + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_program_shadow", 23)) + { + ret = GLEW_ARB_fragment_program_shadow; + continue; + } +#endif +#ifdef GL_ARB_fragment_shader + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_shader", 15)) + { + ret = GLEW_ARB_fragment_shader; + continue; + } +#endif +#ifdef GL_ARB_framebuffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_object", 18)) + { + ret = GLEW_ARB_framebuffer_object; + continue; + } +#endif +#ifdef GL_ARB_framebuffer_sRGB + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_sRGB", 16)) + { + ret = GLEW_ARB_framebuffer_sRGB; + continue; + } +#endif +#ifdef GL_ARB_geometry_shader4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"geometry_shader4", 16)) + { + ret = GLEW_ARB_geometry_shader4; + continue; + } +#endif +#ifdef GL_ARB_half_float_pixel + if (_glewStrSame3(&pos, &len, (const GLubyte*)"half_float_pixel", 16)) + { + ret = GLEW_ARB_half_float_pixel; + continue; + } +#endif +#ifdef GL_ARB_half_float_vertex + if (_glewStrSame3(&pos, &len, (const GLubyte*)"half_float_vertex", 17)) + { + ret = GLEW_ARB_half_float_vertex; + continue; + } +#endif +#ifdef GL_ARB_imaging + if (_glewStrSame3(&pos, &len, (const GLubyte*)"imaging", 7)) + { + ret = GLEW_ARB_imaging; + continue; + } +#endif +#ifdef GL_ARB_instanced_arrays + if (_glewStrSame3(&pos, &len, (const GLubyte*)"instanced_arrays", 16)) + { + ret = GLEW_ARB_instanced_arrays; + continue; + } +#endif +#ifdef GL_ARB_map_buffer_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"map_buffer_range", 16)) + { + ret = GLEW_ARB_map_buffer_range; + continue; + } +#endif +#ifdef GL_ARB_matrix_palette + if (_glewStrSame3(&pos, &len, (const GLubyte*)"matrix_palette", 14)) + { + ret = GLEW_ARB_matrix_palette; + continue; + } +#endif +#ifdef GL_ARB_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = GLEW_ARB_multisample; + continue; + } +#endif +#ifdef GL_ARB_multitexture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multitexture", 12)) + { + ret = GLEW_ARB_multitexture; + continue; + } +#endif +#ifdef GL_ARB_occlusion_query + if (_glewStrSame3(&pos, &len, (const GLubyte*)"occlusion_query", 15)) + { + ret = GLEW_ARB_occlusion_query; + continue; + } +#endif +#ifdef GL_ARB_pixel_buffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_buffer_object", 19)) + { + ret = GLEW_ARB_pixel_buffer_object; + continue; + } +#endif +#ifdef GL_ARB_point_parameters + if (_glewStrSame3(&pos, &len, (const GLubyte*)"point_parameters", 16)) + { + ret = GLEW_ARB_point_parameters; + continue; + } +#endif +#ifdef GL_ARB_point_sprite + if (_glewStrSame3(&pos, &len, (const GLubyte*)"point_sprite", 12)) + { + ret = GLEW_ARB_point_sprite; + continue; + } +#endif +#ifdef GL_ARB_shader_objects + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shader_objects", 14)) + { + ret = GLEW_ARB_shader_objects; + continue; + } +#endif +#ifdef GL_ARB_shading_language_100 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shading_language_100", 20)) + { + ret = GLEW_ARB_shading_language_100; + continue; + } +#endif +#ifdef GL_ARB_shadow + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shadow", 6)) + { + ret = GLEW_ARB_shadow; + continue; + } +#endif +#ifdef GL_ARB_shadow_ambient + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shadow_ambient", 14)) + { + ret = GLEW_ARB_shadow_ambient; + continue; + } +#endif +#ifdef GL_ARB_texture_border_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_border_clamp", 20)) + { + ret = GLEW_ARB_texture_border_clamp; + continue; + } +#endif +#ifdef GL_ARB_texture_buffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_buffer_object", 21)) + { + ret = GLEW_ARB_texture_buffer_object; + continue; + } +#endif +#ifdef GL_ARB_texture_compression + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression", 19)) + { + ret = GLEW_ARB_texture_compression; + continue; + } +#endif +#ifdef GL_ARB_texture_compression_rgtc + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_rgtc", 24)) + { + ret = GLEW_ARB_texture_compression_rgtc; + continue; + } +#endif +#ifdef GL_ARB_texture_cube_map + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_cube_map", 16)) + { + ret = GLEW_ARB_texture_cube_map; + continue; + } +#endif +#ifdef GL_ARB_texture_env_add + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_add", 15)) + { + ret = GLEW_ARB_texture_env_add; + continue; + } +#endif +#ifdef GL_ARB_texture_env_combine + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_combine", 19)) + { + ret = GLEW_ARB_texture_env_combine; + continue; + } +#endif +#ifdef GL_ARB_texture_env_crossbar + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_crossbar", 20)) + { + ret = GLEW_ARB_texture_env_crossbar; + continue; + } +#endif +#ifdef GL_ARB_texture_env_dot3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_dot3", 16)) + { + ret = GLEW_ARB_texture_env_dot3; + continue; + } +#endif +#ifdef GL_ARB_texture_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_float", 13)) + { + ret = GLEW_ARB_texture_float; + continue; + } +#endif +#ifdef GL_ARB_texture_mirrored_repeat + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_mirrored_repeat", 23)) + { + ret = GLEW_ARB_texture_mirrored_repeat; + continue; + } +#endif +#ifdef GL_ARB_texture_non_power_of_two + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_non_power_of_two", 24)) + { + ret = GLEW_ARB_texture_non_power_of_two; + continue; + } +#endif +#ifdef GL_ARB_texture_rectangle + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_rectangle", 17)) + { + ret = GLEW_ARB_texture_rectangle; + continue; + } +#endif +#ifdef GL_ARB_texture_rg + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_rg", 10)) + { + ret = GLEW_ARB_texture_rg; + continue; + } +#endif +#ifdef GL_ARB_transpose_matrix + if (_glewStrSame3(&pos, &len, (const GLubyte*)"transpose_matrix", 16)) + { + ret = GLEW_ARB_transpose_matrix; + continue; + } +#endif +#ifdef GL_ARB_vertex_array_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_object", 19)) + { + ret = GLEW_ARB_vertex_array_object; + continue; + } +#endif +#ifdef GL_ARB_vertex_blend + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_blend", 12)) + { + ret = GLEW_ARB_vertex_blend; + continue; + } +#endif +#ifdef GL_ARB_vertex_buffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_buffer_object", 20)) + { + ret = GLEW_ARB_vertex_buffer_object; + continue; + } +#endif +#ifdef GL_ARB_vertex_program + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_program", 14)) + { + ret = GLEW_ARB_vertex_program; + continue; + } +#endif +#ifdef GL_ARB_vertex_shader + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_shader", 13)) + { + ret = GLEW_ARB_vertex_shader; + continue; + } +#endif +#ifdef GL_ARB_window_pos + if (_glewStrSame3(&pos, &len, (const GLubyte*)"window_pos", 10)) + { + ret = GLEW_ARB_window_pos; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"ATIX_", 5)) + { +#ifdef GL_ATIX_point_sprites + if (_glewStrSame3(&pos, &len, (const GLubyte*)"point_sprites", 13)) + { + ret = GLEW_ATIX_point_sprites; + continue; + } +#endif +#ifdef GL_ATIX_texture_env_combine3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_combine3", 20)) + { + ret = GLEW_ATIX_texture_env_combine3; + continue; + } +#endif +#ifdef GL_ATIX_texture_env_route + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_route", 17)) + { + ret = GLEW_ATIX_texture_env_route; + continue; + } +#endif +#ifdef GL_ATIX_vertex_shader_output_point_size + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_shader_output_point_size", 31)) + { + ret = GLEW_ATIX_vertex_shader_output_point_size; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"ATI_", 4)) + { +#ifdef GL_ATI_draw_buffers + if (_glewStrSame3(&pos, &len, (const GLubyte*)"draw_buffers", 12)) + { + ret = GLEW_ATI_draw_buffers; + continue; + } +#endif +#ifdef GL_ATI_element_array + if (_glewStrSame3(&pos, &len, (const GLubyte*)"element_array", 13)) + { + ret = GLEW_ATI_element_array; + continue; + } +#endif +#ifdef GL_ATI_envmap_bumpmap + if (_glewStrSame3(&pos, &len, (const GLubyte*)"envmap_bumpmap", 14)) + { + ret = GLEW_ATI_envmap_bumpmap; + continue; + } +#endif +#ifdef GL_ATI_fragment_shader + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_shader", 15)) + { + ret = GLEW_ATI_fragment_shader; + continue; + } +#endif +#ifdef GL_ATI_map_object_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"map_object_buffer", 17)) + { + ret = GLEW_ATI_map_object_buffer; + continue; + } +#endif +#ifdef GL_ATI_pn_triangles + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pn_triangles", 12)) + { + ret = GLEW_ATI_pn_triangles; + continue; + } +#endif +#ifdef GL_ATI_separate_stencil + if (_glewStrSame3(&pos, &len, (const GLubyte*)"separate_stencil", 16)) + { + ret = GLEW_ATI_separate_stencil; + continue; + } +#endif +#ifdef GL_ATI_shader_texture_lod + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shader_texture_lod", 18)) + { + ret = GLEW_ATI_shader_texture_lod; + continue; + } +#endif +#ifdef GL_ATI_text_fragment_shader + if (_glewStrSame3(&pos, &len, (const GLubyte*)"text_fragment_shader", 20)) + { + ret = GLEW_ATI_text_fragment_shader; + continue; + } +#endif +#ifdef GL_ATI_texture_compression_3dc + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_3dc", 23)) + { + ret = GLEW_ATI_texture_compression_3dc; + continue; + } +#endif +#ifdef GL_ATI_texture_env_combine3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_combine3", 20)) + { + ret = GLEW_ATI_texture_env_combine3; + continue; + } +#endif +#ifdef GL_ATI_texture_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_float", 13)) + { + ret = GLEW_ATI_texture_float; + continue; + } +#endif +#ifdef GL_ATI_texture_mirror_once + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_mirror_once", 19)) + { + ret = GLEW_ATI_texture_mirror_once; + continue; + } +#endif +#ifdef GL_ATI_vertex_array_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_object", 19)) + { + ret = GLEW_ATI_vertex_array_object; + continue; + } +#endif +#ifdef GL_ATI_vertex_attrib_array_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_attrib_array_object", 26)) + { + ret = GLEW_ATI_vertex_attrib_array_object; + continue; + } +#endif +#ifdef GL_ATI_vertex_streams + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_streams", 14)) + { + ret = GLEW_ATI_vertex_streams; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"EXT_", 4)) + { +#ifdef GL_EXT_422_pixels + if (_glewStrSame3(&pos, &len, (const GLubyte*)"422_pixels", 10)) + { + ret = GLEW_EXT_422_pixels; + continue; + } +#endif +#ifdef GL_EXT_Cg_shader + if (_glewStrSame3(&pos, &len, (const GLubyte*)"Cg_shader", 9)) + { + ret = GLEW_EXT_Cg_shader; + continue; + } +#endif +#ifdef GL_EXT_abgr + if (_glewStrSame3(&pos, &len, (const GLubyte*)"abgr", 4)) + { + ret = GLEW_EXT_abgr; + continue; + } +#endif +#ifdef GL_EXT_bgra + if (_glewStrSame3(&pos, &len, (const GLubyte*)"bgra", 4)) + { + ret = GLEW_EXT_bgra; + continue; + } +#endif +#ifdef GL_EXT_bindable_uniform + if (_glewStrSame3(&pos, &len, (const GLubyte*)"bindable_uniform", 16)) + { + ret = GLEW_EXT_bindable_uniform; + continue; + } +#endif +#ifdef GL_EXT_blend_color + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_color", 11)) + { + ret = GLEW_EXT_blend_color; + continue; + } +#endif +#ifdef GL_EXT_blend_equation_separate + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_equation_separate", 23)) + { + ret = GLEW_EXT_blend_equation_separate; + continue; + } +#endif +#ifdef GL_EXT_blend_func_separate + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_func_separate", 19)) + { + ret = GLEW_EXT_blend_func_separate; + continue; + } +#endif +#ifdef GL_EXT_blend_logic_op + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_logic_op", 14)) + { + ret = GLEW_EXT_blend_logic_op; + continue; + } +#endif +#ifdef GL_EXT_blend_minmax + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_minmax", 12)) + { + ret = GLEW_EXT_blend_minmax; + continue; + } +#endif +#ifdef GL_EXT_blend_subtract + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_subtract", 14)) + { + ret = GLEW_EXT_blend_subtract; + continue; + } +#endif +#ifdef GL_EXT_clip_volume_hint + if (_glewStrSame3(&pos, &len, (const GLubyte*)"clip_volume_hint", 16)) + { + ret = GLEW_EXT_clip_volume_hint; + continue; + } +#endif +#ifdef GL_EXT_cmyka + if (_glewStrSame3(&pos, &len, (const GLubyte*)"cmyka", 5)) + { + ret = GLEW_EXT_cmyka; + continue; + } +#endif +#ifdef GL_EXT_color_subtable + if (_glewStrSame3(&pos, &len, (const GLubyte*)"color_subtable", 14)) + { + ret = GLEW_EXT_color_subtable; + continue; + } +#endif +#ifdef GL_EXT_compiled_vertex_array + if (_glewStrSame3(&pos, &len, (const GLubyte*)"compiled_vertex_array", 21)) + { + ret = GLEW_EXT_compiled_vertex_array; + continue; + } +#endif +#ifdef GL_EXT_convolution + if (_glewStrSame3(&pos, &len, (const GLubyte*)"convolution", 11)) + { + ret = GLEW_EXT_convolution; + continue; + } +#endif +#ifdef GL_EXT_coordinate_frame + if (_glewStrSame3(&pos, &len, (const GLubyte*)"coordinate_frame", 16)) + { + ret = GLEW_EXT_coordinate_frame; + continue; + } +#endif +#ifdef GL_EXT_copy_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"copy_texture", 12)) + { + ret = GLEW_EXT_copy_texture; + continue; + } +#endif +#ifdef GL_EXT_cull_vertex + if (_glewStrSame3(&pos, &len, (const GLubyte*)"cull_vertex", 11)) + { + ret = GLEW_EXT_cull_vertex; + continue; + } +#endif +#ifdef GL_EXT_depth_bounds_test + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_bounds_test", 17)) + { + ret = GLEW_EXT_depth_bounds_test; + continue; + } +#endif +#ifdef GL_EXT_direct_state_access + if (_glewStrSame3(&pos, &len, (const GLubyte*)"direct_state_access", 19)) + { + ret = GLEW_EXT_direct_state_access; + continue; + } +#endif +#ifdef GL_EXT_draw_buffers2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"draw_buffers2", 13)) + { + ret = GLEW_EXT_draw_buffers2; + continue; + } +#endif +#ifdef GL_EXT_draw_instanced + if (_glewStrSame3(&pos, &len, (const GLubyte*)"draw_instanced", 14)) + { + ret = GLEW_EXT_draw_instanced; + continue; + } +#endif +#ifdef GL_EXT_draw_range_elements + if (_glewStrSame3(&pos, &len, (const GLubyte*)"draw_range_elements", 19)) + { + ret = GLEW_EXT_draw_range_elements; + continue; + } +#endif +#ifdef GL_EXT_fog_coord + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fog_coord", 9)) + { + ret = GLEW_EXT_fog_coord; + continue; + } +#endif +#ifdef GL_EXT_fragment_lighting + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_lighting", 17)) + { + ret = GLEW_EXT_fragment_lighting; + continue; + } +#endif +#ifdef GL_EXT_framebuffer_blit + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_blit", 16)) + { + ret = GLEW_EXT_framebuffer_blit; + continue; + } +#endif +#ifdef GL_EXT_framebuffer_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_multisample", 23)) + { + ret = GLEW_EXT_framebuffer_multisample; + continue; + } +#endif +#ifdef GL_EXT_framebuffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_object", 18)) + { + ret = GLEW_EXT_framebuffer_object; + continue; + } +#endif +#ifdef GL_EXT_framebuffer_sRGB + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_sRGB", 16)) + { + ret = GLEW_EXT_framebuffer_sRGB; + continue; + } +#endif +#ifdef GL_EXT_geometry_shader4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"geometry_shader4", 16)) + { + ret = GLEW_EXT_geometry_shader4; + continue; + } +#endif +#ifdef GL_EXT_gpu_program_parameters + if (_glewStrSame3(&pos, &len, (const GLubyte*)"gpu_program_parameters", 22)) + { + ret = GLEW_EXT_gpu_program_parameters; + continue; + } +#endif +#ifdef GL_EXT_gpu_shader4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"gpu_shader4", 11)) + { + ret = GLEW_EXT_gpu_shader4; + continue; + } +#endif +#ifdef GL_EXT_histogram + if (_glewStrSame3(&pos, &len, (const GLubyte*)"histogram", 9)) + { + ret = GLEW_EXT_histogram; + continue; + } +#endif +#ifdef GL_EXT_index_array_formats + if (_glewStrSame3(&pos, &len, (const GLubyte*)"index_array_formats", 19)) + { + ret = GLEW_EXT_index_array_formats; + continue; + } +#endif +#ifdef GL_EXT_index_func + if (_glewStrSame3(&pos, &len, (const GLubyte*)"index_func", 10)) + { + ret = GLEW_EXT_index_func; + continue; + } +#endif +#ifdef GL_EXT_index_material + if (_glewStrSame3(&pos, &len, (const GLubyte*)"index_material", 14)) + { + ret = GLEW_EXT_index_material; + continue; + } +#endif +#ifdef GL_EXT_index_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"index_texture", 13)) + { + ret = GLEW_EXT_index_texture; + continue; + } +#endif +#ifdef GL_EXT_light_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"light_texture", 13)) + { + ret = GLEW_EXT_light_texture; + continue; + } +#endif +#ifdef GL_EXT_misc_attribute + if (_glewStrSame3(&pos, &len, (const GLubyte*)"misc_attribute", 14)) + { + ret = GLEW_EXT_misc_attribute; + continue; + } +#endif +#ifdef GL_EXT_multi_draw_arrays + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multi_draw_arrays", 17)) + { + ret = GLEW_EXT_multi_draw_arrays; + continue; + } +#endif +#ifdef GL_EXT_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = GLEW_EXT_multisample; + continue; + } +#endif +#ifdef GL_EXT_packed_depth_stencil + if (_glewStrSame3(&pos, &len, (const GLubyte*)"packed_depth_stencil", 20)) + { + ret = GLEW_EXT_packed_depth_stencil; + continue; + } +#endif +#ifdef GL_EXT_packed_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"packed_float", 12)) + { + ret = GLEW_EXT_packed_float; + continue; + } +#endif +#ifdef GL_EXT_packed_pixels + if (_glewStrSame3(&pos, &len, (const GLubyte*)"packed_pixels", 13)) + { + ret = GLEW_EXT_packed_pixels; + continue; + } +#endif +#ifdef GL_EXT_paletted_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"paletted_texture", 16)) + { + ret = GLEW_EXT_paletted_texture; + continue; + } +#endif +#ifdef GL_EXT_pixel_buffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_buffer_object", 19)) + { + ret = GLEW_EXT_pixel_buffer_object; + continue; + } +#endif +#ifdef GL_EXT_pixel_transform + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_transform", 15)) + { + ret = GLEW_EXT_pixel_transform; + continue; + } +#endif +#ifdef GL_EXT_pixel_transform_color_table + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_transform_color_table", 27)) + { + ret = GLEW_EXT_pixel_transform_color_table; + continue; + } +#endif +#ifdef GL_EXT_point_parameters + if (_glewStrSame3(&pos, &len, (const GLubyte*)"point_parameters", 16)) + { + ret = GLEW_EXT_point_parameters; + continue; + } +#endif +#ifdef GL_EXT_polygon_offset + if (_glewStrSame3(&pos, &len, (const GLubyte*)"polygon_offset", 14)) + { + ret = GLEW_EXT_polygon_offset; + continue; + } +#endif +#ifdef GL_EXT_rescale_normal + if (_glewStrSame3(&pos, &len, (const GLubyte*)"rescale_normal", 14)) + { + ret = GLEW_EXT_rescale_normal; + continue; + } +#endif +#ifdef GL_EXT_scene_marker + if (_glewStrSame3(&pos, &len, (const GLubyte*)"scene_marker", 12)) + { + ret = GLEW_EXT_scene_marker; + continue; + } +#endif +#ifdef GL_EXT_secondary_color + if (_glewStrSame3(&pos, &len, (const GLubyte*)"secondary_color", 15)) + { + ret = GLEW_EXT_secondary_color; + continue; + } +#endif +#ifdef GL_EXT_separate_specular_color + if (_glewStrSame3(&pos, &len, (const GLubyte*)"separate_specular_color", 23)) + { + ret = GLEW_EXT_separate_specular_color; + continue; + } +#endif +#ifdef GL_EXT_shadow_funcs + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shadow_funcs", 12)) + { + ret = GLEW_EXT_shadow_funcs; + continue; + } +#endif +#ifdef GL_EXT_shared_texture_palette + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shared_texture_palette", 22)) + { + ret = GLEW_EXT_shared_texture_palette; + continue; + } +#endif +#ifdef GL_EXT_stencil_clear_tag + if (_glewStrSame3(&pos, &len, (const GLubyte*)"stencil_clear_tag", 17)) + { + ret = GLEW_EXT_stencil_clear_tag; + continue; + } +#endif +#ifdef GL_EXT_stencil_two_side + if (_glewStrSame3(&pos, &len, (const GLubyte*)"stencil_two_side", 16)) + { + ret = GLEW_EXT_stencil_two_side; + continue; + } +#endif +#ifdef GL_EXT_stencil_wrap + if (_glewStrSame3(&pos, &len, (const GLubyte*)"stencil_wrap", 12)) + { + ret = GLEW_EXT_stencil_wrap; + continue; + } +#endif +#ifdef GL_EXT_subtexture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"subtexture", 10)) + { + ret = GLEW_EXT_subtexture; + continue; + } +#endif +#ifdef GL_EXT_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture", 7)) + { + ret = GLEW_EXT_texture; + continue; + } +#endif +#ifdef GL_EXT_texture3D + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture3D", 9)) + { + ret = GLEW_EXT_texture3D; + continue; + } +#endif +#ifdef GL_EXT_texture_array + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_array", 13)) + { + ret = GLEW_EXT_texture_array; + continue; + } +#endif +#ifdef GL_EXT_texture_buffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_buffer_object", 21)) + { + ret = GLEW_EXT_texture_buffer_object; + continue; + } +#endif +#ifdef GL_EXT_texture_compression_dxt1 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_dxt1", 24)) + { + ret = GLEW_EXT_texture_compression_dxt1; + continue; + } +#endif +#ifdef GL_EXT_texture_compression_latc + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_latc", 24)) + { + ret = GLEW_EXT_texture_compression_latc; + continue; + } +#endif +#ifdef GL_EXT_texture_compression_rgtc + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_rgtc", 24)) + { + ret = GLEW_EXT_texture_compression_rgtc; + continue; + } +#endif +#ifdef GL_EXT_texture_compression_s3tc + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_s3tc", 24)) + { + ret = GLEW_EXT_texture_compression_s3tc; + continue; + } +#endif +#ifdef GL_EXT_texture_cube_map + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_cube_map", 16)) + { + ret = GLEW_EXT_texture_cube_map; + continue; + } +#endif +#ifdef GL_EXT_texture_edge_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_edge_clamp", 18)) + { + ret = GLEW_EXT_texture_edge_clamp; + continue; + } +#endif +#ifdef GL_EXT_texture_env + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env", 11)) + { + ret = GLEW_EXT_texture_env; + continue; + } +#endif +#ifdef GL_EXT_texture_env_add + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_add", 15)) + { + ret = GLEW_EXT_texture_env_add; + continue; + } +#endif +#ifdef GL_EXT_texture_env_combine + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_combine", 19)) + { + ret = GLEW_EXT_texture_env_combine; + continue; + } +#endif +#ifdef GL_EXT_texture_env_dot3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_dot3", 16)) + { + ret = GLEW_EXT_texture_env_dot3; + continue; + } +#endif +#ifdef GL_EXT_texture_filter_anisotropic + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_filter_anisotropic", 26)) + { + ret = GLEW_EXT_texture_filter_anisotropic; + continue; + } +#endif +#ifdef GL_EXT_texture_integer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_integer", 15)) + { + ret = GLEW_EXT_texture_integer; + continue; + } +#endif +#ifdef GL_EXT_texture_lod_bias + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_lod_bias", 16)) + { + ret = GLEW_EXT_texture_lod_bias; + continue; + } +#endif +#ifdef GL_EXT_texture_mirror_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_mirror_clamp", 20)) + { + ret = GLEW_EXT_texture_mirror_clamp; + continue; + } +#endif +#ifdef GL_EXT_texture_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_object", 14)) + { + ret = GLEW_EXT_texture_object; + continue; + } +#endif +#ifdef GL_EXT_texture_perturb_normal + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_perturb_normal", 22)) + { + ret = GLEW_EXT_texture_perturb_normal; + continue; + } +#endif +#ifdef GL_EXT_texture_rectangle + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_rectangle", 17)) + { + ret = GLEW_EXT_texture_rectangle; + continue; + } +#endif +#ifdef GL_EXT_texture_sRGB + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_sRGB", 12)) + { + ret = GLEW_EXT_texture_sRGB; + continue; + } +#endif +#ifdef GL_EXT_texture_shared_exponent + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_shared_exponent", 23)) + { + ret = GLEW_EXT_texture_shared_exponent; + continue; + } +#endif +#ifdef GL_EXT_texture_swizzle + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_swizzle", 15)) + { + ret = GLEW_EXT_texture_swizzle; + continue; + } +#endif +#ifdef GL_EXT_timer_query + if (_glewStrSame3(&pos, &len, (const GLubyte*)"timer_query", 11)) + { + ret = GLEW_EXT_timer_query; + continue; + } +#endif +#ifdef GL_EXT_transform_feedback + if (_glewStrSame3(&pos, &len, (const GLubyte*)"transform_feedback", 18)) + { + ret = GLEW_EXT_transform_feedback; + continue; + } +#endif +#ifdef GL_EXT_vertex_array + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array", 12)) + { + ret = GLEW_EXT_vertex_array; + continue; + } +#endif +#ifdef GL_EXT_vertex_array_bgra + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_bgra", 17)) + { + ret = GLEW_EXT_vertex_array_bgra; + continue; + } +#endif +#ifdef GL_EXT_vertex_shader + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_shader", 13)) + { + ret = GLEW_EXT_vertex_shader; + continue; + } +#endif +#ifdef GL_EXT_vertex_weighting + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_weighting", 16)) + { + ret = GLEW_EXT_vertex_weighting; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"GREMEDY_", 8)) + { +#ifdef GL_GREMEDY_frame_terminator + if (_glewStrSame3(&pos, &len, (const GLubyte*)"frame_terminator", 16)) + { + ret = GLEW_GREMEDY_frame_terminator; + continue; + } +#endif +#ifdef GL_GREMEDY_string_marker + if (_glewStrSame3(&pos, &len, (const GLubyte*)"string_marker", 13)) + { + ret = GLEW_GREMEDY_string_marker; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"HP_", 3)) + { +#ifdef GL_HP_convolution_border_modes + if (_glewStrSame3(&pos, &len, (const GLubyte*)"convolution_border_modes", 24)) + { + ret = GLEW_HP_convolution_border_modes; + continue; + } +#endif +#ifdef GL_HP_image_transform + if (_glewStrSame3(&pos, &len, (const GLubyte*)"image_transform", 15)) + { + ret = GLEW_HP_image_transform; + continue; + } +#endif +#ifdef GL_HP_occlusion_test + if (_glewStrSame3(&pos, &len, (const GLubyte*)"occlusion_test", 14)) + { + ret = GLEW_HP_occlusion_test; + continue; + } +#endif +#ifdef GL_HP_texture_lighting + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_lighting", 16)) + { + ret = GLEW_HP_texture_lighting; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"IBM_", 4)) + { +#ifdef GL_IBM_cull_vertex + if (_glewStrSame3(&pos, &len, (const GLubyte*)"cull_vertex", 11)) + { + ret = GLEW_IBM_cull_vertex; + continue; + } +#endif +#ifdef GL_IBM_multimode_draw_arrays + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multimode_draw_arrays", 21)) + { + ret = GLEW_IBM_multimode_draw_arrays; + continue; + } +#endif +#ifdef GL_IBM_rasterpos_clip + if (_glewStrSame3(&pos, &len, (const GLubyte*)"rasterpos_clip", 14)) + { + ret = GLEW_IBM_rasterpos_clip; + continue; + } +#endif +#ifdef GL_IBM_static_data + if (_glewStrSame3(&pos, &len, (const GLubyte*)"static_data", 11)) + { + ret = GLEW_IBM_static_data; + continue; + } +#endif +#ifdef GL_IBM_texture_mirrored_repeat + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_mirrored_repeat", 23)) + { + ret = GLEW_IBM_texture_mirrored_repeat; + continue; + } +#endif +#ifdef GL_IBM_vertex_array_lists + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_lists", 18)) + { + ret = GLEW_IBM_vertex_array_lists; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"INGR_", 5)) + { +#ifdef GL_INGR_color_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"color_clamp", 11)) + { + ret = GLEW_INGR_color_clamp; + continue; + } +#endif +#ifdef GL_INGR_interlace_read + if (_glewStrSame3(&pos, &len, (const GLubyte*)"interlace_read", 14)) + { + ret = GLEW_INGR_interlace_read; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"INTEL_", 6)) + { +#ifdef GL_INTEL_parallel_arrays + if (_glewStrSame3(&pos, &len, (const GLubyte*)"parallel_arrays", 15)) + { + ret = GLEW_INTEL_parallel_arrays; + continue; + } +#endif +#ifdef GL_INTEL_texture_scissor + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_scissor", 15)) + { + ret = GLEW_INTEL_texture_scissor; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"KTX_", 4)) + { +#ifdef GL_KTX_buffer_region + if (_glewStrSame3(&pos, &len, (const GLubyte*)"buffer_region", 13)) + { + ret = GLEW_KTX_buffer_region; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"MESAX_", 6)) + { +#ifdef GL_MESAX_texture_stack + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_stack", 13)) + { + ret = GLEW_MESAX_texture_stack; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"MESA_", 5)) + { +#ifdef GL_MESA_pack_invert + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pack_invert", 11)) + { + ret = GLEW_MESA_pack_invert; + continue; + } +#endif +#ifdef GL_MESA_resize_buffers + if (_glewStrSame3(&pos, &len, (const GLubyte*)"resize_buffers", 14)) + { + ret = GLEW_MESA_resize_buffers; + continue; + } +#endif +#ifdef GL_MESA_window_pos + if (_glewStrSame3(&pos, &len, (const GLubyte*)"window_pos", 10)) + { + ret = GLEW_MESA_window_pos; + continue; + } +#endif +#ifdef GL_MESA_ycbcr_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"ycbcr_texture", 13)) + { + ret = GLEW_MESA_ycbcr_texture; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"NV_", 3)) + { +#ifdef GL_NV_blend_square + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_square", 12)) + { + ret = GLEW_NV_blend_square; + continue; + } +#endif +#ifdef GL_NV_conditional_render + if (_glewStrSame3(&pos, &len, (const GLubyte*)"conditional_render", 18)) + { + ret = GLEW_NV_conditional_render; + continue; + } +#endif +#ifdef GL_NV_copy_depth_to_color + if (_glewStrSame3(&pos, &len, (const GLubyte*)"copy_depth_to_color", 19)) + { + ret = GLEW_NV_copy_depth_to_color; + continue; + } +#endif +#ifdef GL_NV_depth_buffer_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_buffer_float", 18)) + { + ret = GLEW_NV_depth_buffer_float; + continue; + } +#endif +#ifdef GL_NV_depth_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_clamp", 11)) + { + ret = GLEW_NV_depth_clamp; + continue; + } +#endif +#ifdef GL_NV_depth_range_unclamped + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_range_unclamped", 21)) + { + ret = GLEW_NV_depth_range_unclamped; + continue; + } +#endif +#ifdef GL_NV_evaluators + if (_glewStrSame3(&pos, &len, (const GLubyte*)"evaluators", 10)) + { + ret = GLEW_NV_evaluators; + continue; + } +#endif +#ifdef GL_NV_explicit_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"explicit_multisample", 20)) + { + ret = GLEW_NV_explicit_multisample; + continue; + } +#endif +#ifdef GL_NV_fence + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fence", 5)) + { + ret = GLEW_NV_fence; + continue; + } +#endif +#ifdef GL_NV_float_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"float_buffer", 12)) + { + ret = GLEW_NV_float_buffer; + continue; + } +#endif +#ifdef GL_NV_fog_distance + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fog_distance", 12)) + { + ret = GLEW_NV_fog_distance; + continue; + } +#endif +#ifdef GL_NV_fragment_program + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_program", 16)) + { + ret = GLEW_NV_fragment_program; + continue; + } +#endif +#ifdef GL_NV_fragment_program2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_program2", 17)) + { + ret = GLEW_NV_fragment_program2; + continue; + } +#endif +#ifdef GL_NV_fragment_program4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_program4", 17)) + { + ret = GLEW_NV_fragment_program4; + continue; + } +#endif +#ifdef GL_NV_fragment_program_option + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_program_option", 23)) + { + ret = GLEW_NV_fragment_program_option; + continue; + } +#endif +#ifdef GL_NV_framebuffer_multisample_coverage + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_multisample_coverage", 32)) + { + ret = GLEW_NV_framebuffer_multisample_coverage; + continue; + } +#endif +#ifdef GL_NV_geometry_program4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"geometry_program4", 17)) + { + ret = GLEW_NV_geometry_program4; + continue; + } +#endif +#ifdef GL_NV_geometry_shader4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"geometry_shader4", 16)) + { + ret = GLEW_NV_geometry_shader4; + continue; + } +#endif +#ifdef GL_NV_gpu_program4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"gpu_program4", 12)) + { + ret = GLEW_NV_gpu_program4; + continue; + } +#endif +#ifdef GL_NV_half_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"half_float", 10)) + { + ret = GLEW_NV_half_float; + continue; + } +#endif +#ifdef GL_NV_light_max_exponent + if (_glewStrSame3(&pos, &len, (const GLubyte*)"light_max_exponent", 18)) + { + ret = GLEW_NV_light_max_exponent; + continue; + } +#endif +#ifdef GL_NV_multisample_filter_hint + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample_filter_hint", 23)) + { + ret = GLEW_NV_multisample_filter_hint; + continue; + } +#endif +#ifdef GL_NV_occlusion_query + if (_glewStrSame3(&pos, &len, (const GLubyte*)"occlusion_query", 15)) + { + ret = GLEW_NV_occlusion_query; + continue; + } +#endif +#ifdef GL_NV_packed_depth_stencil + if (_glewStrSame3(&pos, &len, (const GLubyte*)"packed_depth_stencil", 20)) + { + ret = GLEW_NV_packed_depth_stencil; + continue; + } +#endif +#ifdef GL_NV_parameter_buffer_object + if (_glewStrSame3(&pos, &len, (const GLubyte*)"parameter_buffer_object", 23)) + { + ret = GLEW_NV_parameter_buffer_object; + continue; + } +#endif +#ifdef GL_NV_pixel_data_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_data_range", 16)) + { + ret = GLEW_NV_pixel_data_range; + continue; + } +#endif +#ifdef GL_NV_point_sprite + if (_glewStrSame3(&pos, &len, (const GLubyte*)"point_sprite", 12)) + { + ret = GLEW_NV_point_sprite; + continue; + } +#endif +#ifdef GL_NV_present_video + if (_glewStrSame3(&pos, &len, (const GLubyte*)"present_video", 13)) + { + ret = GLEW_NV_present_video; + continue; + } +#endif +#ifdef GL_NV_primitive_restart + if (_glewStrSame3(&pos, &len, (const GLubyte*)"primitive_restart", 17)) + { + ret = GLEW_NV_primitive_restart; + continue; + } +#endif +#ifdef GL_NV_register_combiners + if (_glewStrSame3(&pos, &len, (const GLubyte*)"register_combiners", 18)) + { + ret = GLEW_NV_register_combiners; + continue; + } +#endif +#ifdef GL_NV_register_combiners2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"register_combiners2", 19)) + { + ret = GLEW_NV_register_combiners2; + continue; + } +#endif +#ifdef GL_NV_texgen_emboss + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texgen_emboss", 13)) + { + ret = GLEW_NV_texgen_emboss; + continue; + } +#endif +#ifdef GL_NV_texgen_reflection + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texgen_reflection", 17)) + { + ret = GLEW_NV_texgen_reflection; + continue; + } +#endif +#ifdef GL_NV_texture_compression_vtc + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_compression_vtc", 23)) + { + ret = GLEW_NV_texture_compression_vtc; + continue; + } +#endif +#ifdef GL_NV_texture_env_combine4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_env_combine4", 20)) + { + ret = GLEW_NV_texture_env_combine4; + continue; + } +#endif +#ifdef GL_NV_texture_expand_normal + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_expand_normal", 21)) + { + ret = GLEW_NV_texture_expand_normal; + continue; + } +#endif +#ifdef GL_NV_texture_rectangle + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_rectangle", 17)) + { + ret = GLEW_NV_texture_rectangle; + continue; + } +#endif +#ifdef GL_NV_texture_shader + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_shader", 14)) + { + ret = GLEW_NV_texture_shader; + continue; + } +#endif +#ifdef GL_NV_texture_shader2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_shader2", 15)) + { + ret = GLEW_NV_texture_shader2; + continue; + } +#endif +#ifdef GL_NV_texture_shader3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_shader3", 15)) + { + ret = GLEW_NV_texture_shader3; + continue; + } +#endif +#ifdef GL_NV_transform_feedback + if (_glewStrSame3(&pos, &len, (const GLubyte*)"transform_feedback", 18)) + { + ret = GLEW_NV_transform_feedback; + continue; + } +#endif +#ifdef GL_NV_vertex_array_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_range", 18)) + { + ret = GLEW_NV_vertex_array_range; + continue; + } +#endif +#ifdef GL_NV_vertex_array_range2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_range2", 19)) + { + ret = GLEW_NV_vertex_array_range2; + continue; + } +#endif +#ifdef GL_NV_vertex_program + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_program", 14)) + { + ret = GLEW_NV_vertex_program; + continue; + } +#endif +#ifdef GL_NV_vertex_program1_1 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_program1_1", 17)) + { + ret = GLEW_NV_vertex_program1_1; + continue; + } +#endif +#ifdef GL_NV_vertex_program2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_program2", 15)) + { + ret = GLEW_NV_vertex_program2; + continue; + } +#endif +#ifdef GL_NV_vertex_program2_option + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_program2_option", 22)) + { + ret = GLEW_NV_vertex_program2_option; + continue; + } +#endif +#ifdef GL_NV_vertex_program3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_program3", 15)) + { + ret = GLEW_NV_vertex_program3; + continue; + } +#endif +#ifdef GL_NV_vertex_program4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_program4", 15)) + { + ret = GLEW_NV_vertex_program4; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"OES_", 4)) + { +#ifdef GL_OES_byte_coordinates + if (_glewStrSame3(&pos, &len, (const GLubyte*)"byte_coordinates", 16)) + { + ret = GLEW_OES_byte_coordinates; + continue; + } +#endif +#ifdef GL_OES_compressed_paletted_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"compressed_paletted_texture", 27)) + { + ret = GLEW_OES_compressed_paletted_texture; + continue; + } +#endif +#ifdef GL_OES_read_format + if (_glewStrSame3(&pos, &len, (const GLubyte*)"read_format", 11)) + { + ret = GLEW_OES_read_format; + continue; + } +#endif +#ifdef GL_OES_single_precision + if (_glewStrSame3(&pos, &len, (const GLubyte*)"single_precision", 16)) + { + ret = GLEW_OES_single_precision; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"OML_", 4)) + { +#ifdef GL_OML_interlace + if (_glewStrSame3(&pos, &len, (const GLubyte*)"interlace", 9)) + { + ret = GLEW_OML_interlace; + continue; + } +#endif +#ifdef GL_OML_resample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"resample", 8)) + { + ret = GLEW_OML_resample; + continue; + } +#endif +#ifdef GL_OML_subsample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"subsample", 9)) + { + ret = GLEW_OML_subsample; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"PGI_", 4)) + { +#ifdef GL_PGI_misc_hints + if (_glewStrSame3(&pos, &len, (const GLubyte*)"misc_hints", 10)) + { + ret = GLEW_PGI_misc_hints; + continue; + } +#endif +#ifdef GL_PGI_vertex_hints + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_hints", 12)) + { + ret = GLEW_PGI_vertex_hints; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"REND_", 5)) + { +#ifdef GL_REND_screen_coordinates + if (_glewStrSame3(&pos, &len, (const GLubyte*)"screen_coordinates", 18)) + { + ret = GLEW_REND_screen_coordinates; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"S3_", 3)) + { +#ifdef GL_S3_s3tc + if (_glewStrSame3(&pos, &len, (const GLubyte*)"s3tc", 4)) + { + ret = GLEW_S3_s3tc; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SGIS_", 5)) + { +#ifdef GL_SGIS_color_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"color_range", 11)) + { + ret = GLEW_SGIS_color_range; + continue; + } +#endif +#ifdef GL_SGIS_detail_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"detail_texture", 14)) + { + ret = GLEW_SGIS_detail_texture; + continue; + } +#endif +#ifdef GL_SGIS_fog_function + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fog_function", 12)) + { + ret = GLEW_SGIS_fog_function; + continue; + } +#endif +#ifdef GL_SGIS_generate_mipmap + if (_glewStrSame3(&pos, &len, (const GLubyte*)"generate_mipmap", 15)) + { + ret = GLEW_SGIS_generate_mipmap; + continue; + } +#endif +#ifdef GL_SGIS_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = GLEW_SGIS_multisample; + continue; + } +#endif +#ifdef GL_SGIS_pixel_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_texture", 13)) + { + ret = GLEW_SGIS_pixel_texture; + continue; + } +#endif +#ifdef GL_SGIS_point_line_texgen + if (_glewStrSame3(&pos, &len, (const GLubyte*)"point_line_texgen", 17)) + { + ret = GLEW_SGIS_point_line_texgen; + continue; + } +#endif +#ifdef GL_SGIS_sharpen_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"sharpen_texture", 15)) + { + ret = GLEW_SGIS_sharpen_texture; + continue; + } +#endif +#ifdef GL_SGIS_texture4D + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture4D", 9)) + { + ret = GLEW_SGIS_texture4D; + continue; + } +#endif +#ifdef GL_SGIS_texture_border_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_border_clamp", 20)) + { + ret = GLEW_SGIS_texture_border_clamp; + continue; + } +#endif +#ifdef GL_SGIS_texture_edge_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_edge_clamp", 18)) + { + ret = GLEW_SGIS_texture_edge_clamp; + continue; + } +#endif +#ifdef GL_SGIS_texture_filter4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_filter4", 15)) + { + ret = GLEW_SGIS_texture_filter4; + continue; + } +#endif +#ifdef GL_SGIS_texture_lod + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_lod", 11)) + { + ret = GLEW_SGIS_texture_lod; + continue; + } +#endif +#ifdef GL_SGIS_texture_select + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_select", 14)) + { + ret = GLEW_SGIS_texture_select; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SGIX_", 5)) + { +#ifdef GL_SGIX_async + if (_glewStrSame3(&pos, &len, (const GLubyte*)"async", 5)) + { + ret = GLEW_SGIX_async; + continue; + } +#endif +#ifdef GL_SGIX_async_histogram + if (_glewStrSame3(&pos, &len, (const GLubyte*)"async_histogram", 15)) + { + ret = GLEW_SGIX_async_histogram; + continue; + } +#endif +#ifdef GL_SGIX_async_pixel + if (_glewStrSame3(&pos, &len, (const GLubyte*)"async_pixel", 11)) + { + ret = GLEW_SGIX_async_pixel; + continue; + } +#endif +#ifdef GL_SGIX_blend_alpha_minmax + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blend_alpha_minmax", 18)) + { + ret = GLEW_SGIX_blend_alpha_minmax; + continue; + } +#endif +#ifdef GL_SGIX_clipmap + if (_glewStrSame3(&pos, &len, (const GLubyte*)"clipmap", 7)) + { + ret = GLEW_SGIX_clipmap; + continue; + } +#endif +#ifdef GL_SGIX_convolution_accuracy + if (_glewStrSame3(&pos, &len, (const GLubyte*)"convolution_accuracy", 20)) + { + ret = GLEW_SGIX_convolution_accuracy; + continue; + } +#endif +#ifdef GL_SGIX_depth_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_texture", 13)) + { + ret = GLEW_SGIX_depth_texture; + continue; + } +#endif +#ifdef GL_SGIX_flush_raster + if (_glewStrSame3(&pos, &len, (const GLubyte*)"flush_raster", 12)) + { + ret = GLEW_SGIX_flush_raster; + continue; + } +#endif +#ifdef GL_SGIX_fog_offset + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fog_offset", 10)) + { + ret = GLEW_SGIX_fog_offset; + continue; + } +#endif +#ifdef GL_SGIX_fog_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fog_texture", 11)) + { + ret = GLEW_SGIX_fog_texture; + continue; + } +#endif +#ifdef GL_SGIX_fragment_specular_lighting + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fragment_specular_lighting", 26)) + { + ret = GLEW_SGIX_fragment_specular_lighting; + continue; + } +#endif +#ifdef GL_SGIX_framezoom + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framezoom", 9)) + { + ret = GLEW_SGIX_framezoom; + continue; + } +#endif +#ifdef GL_SGIX_interlace + if (_glewStrSame3(&pos, &len, (const GLubyte*)"interlace", 9)) + { + ret = GLEW_SGIX_interlace; + continue; + } +#endif +#ifdef GL_SGIX_ir_instrument1 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"ir_instrument1", 14)) + { + ret = GLEW_SGIX_ir_instrument1; + continue; + } +#endif +#ifdef GL_SGIX_list_priority + if (_glewStrSame3(&pos, &len, (const GLubyte*)"list_priority", 13)) + { + ret = GLEW_SGIX_list_priority; + continue; + } +#endif +#ifdef GL_SGIX_pixel_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_texture", 13)) + { + ret = GLEW_SGIX_pixel_texture; + continue; + } +#endif +#ifdef GL_SGIX_pixel_texture_bits + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_texture_bits", 18)) + { + ret = GLEW_SGIX_pixel_texture_bits; + continue; + } +#endif +#ifdef GL_SGIX_reference_plane + if (_glewStrSame3(&pos, &len, (const GLubyte*)"reference_plane", 15)) + { + ret = GLEW_SGIX_reference_plane; + continue; + } +#endif +#ifdef GL_SGIX_resample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"resample", 8)) + { + ret = GLEW_SGIX_resample; + continue; + } +#endif +#ifdef GL_SGIX_shadow + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shadow", 6)) + { + ret = GLEW_SGIX_shadow; + continue; + } +#endif +#ifdef GL_SGIX_shadow_ambient + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shadow_ambient", 14)) + { + ret = GLEW_SGIX_shadow_ambient; + continue; + } +#endif +#ifdef GL_SGIX_sprite + if (_glewStrSame3(&pos, &len, (const GLubyte*)"sprite", 6)) + { + ret = GLEW_SGIX_sprite; + continue; + } +#endif +#ifdef GL_SGIX_tag_sample_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"tag_sample_buffer", 17)) + { + ret = GLEW_SGIX_tag_sample_buffer; + continue; + } +#endif +#ifdef GL_SGIX_texture_add_env + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_add_env", 15)) + { + ret = GLEW_SGIX_texture_add_env; + continue; + } +#endif +#ifdef GL_SGIX_texture_coordinate_clamp + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_coordinate_clamp", 24)) + { + ret = GLEW_SGIX_texture_coordinate_clamp; + continue; + } +#endif +#ifdef GL_SGIX_texture_lod_bias + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_lod_bias", 16)) + { + ret = GLEW_SGIX_texture_lod_bias; + continue; + } +#endif +#ifdef GL_SGIX_texture_multi_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_multi_buffer", 20)) + { + ret = GLEW_SGIX_texture_multi_buffer; + continue; + } +#endif +#ifdef GL_SGIX_texture_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_range", 13)) + { + ret = GLEW_SGIX_texture_range; + continue; + } +#endif +#ifdef GL_SGIX_texture_scale_bias + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_scale_bias", 18)) + { + ret = GLEW_SGIX_texture_scale_bias; + continue; + } +#endif +#ifdef GL_SGIX_vertex_preclip + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_preclip", 14)) + { + ret = GLEW_SGIX_vertex_preclip; + continue; + } +#endif +#ifdef GL_SGIX_vertex_preclip_hint + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_preclip_hint", 19)) + { + ret = GLEW_SGIX_vertex_preclip_hint; + continue; + } +#endif +#ifdef GL_SGIX_ycrcb + if (_glewStrSame3(&pos, &len, (const GLubyte*)"ycrcb", 5)) + { + ret = GLEW_SGIX_ycrcb; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SGI_", 4)) + { +#ifdef GL_SGI_color_matrix + if (_glewStrSame3(&pos, &len, (const GLubyte*)"color_matrix", 12)) + { + ret = GLEW_SGI_color_matrix; + continue; + } +#endif +#ifdef GL_SGI_color_table + if (_glewStrSame3(&pos, &len, (const GLubyte*)"color_table", 11)) + { + ret = GLEW_SGI_color_table; + continue; + } +#endif +#ifdef GL_SGI_texture_color_table + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_color_table", 19)) + { + ret = GLEW_SGI_texture_color_table; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SUNX_", 5)) + { +#ifdef GL_SUNX_constant_data + if (_glewStrSame3(&pos, &len, (const GLubyte*)"constant_data", 13)) + { + ret = GLEW_SUNX_constant_data; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SUN_", 4)) + { +#ifdef GL_SUN_convolution_border_modes + if (_glewStrSame3(&pos, &len, (const GLubyte*)"convolution_border_modes", 24)) + { + ret = GLEW_SUN_convolution_border_modes; + continue; + } +#endif +#ifdef GL_SUN_global_alpha + if (_glewStrSame3(&pos, &len, (const GLubyte*)"global_alpha", 12)) + { + ret = GLEW_SUN_global_alpha; + continue; + } +#endif +#ifdef GL_SUN_mesh_array + if (_glewStrSame3(&pos, &len, (const GLubyte*)"mesh_array", 10)) + { + ret = GLEW_SUN_mesh_array; + continue; + } +#endif +#ifdef GL_SUN_read_video_pixels + if (_glewStrSame3(&pos, &len, (const GLubyte*)"read_video_pixels", 17)) + { + ret = GLEW_SUN_read_video_pixels; + continue; + } +#endif +#ifdef GL_SUN_slice_accum + if (_glewStrSame3(&pos, &len, (const GLubyte*)"slice_accum", 11)) + { + ret = GLEW_SUN_slice_accum; + continue; + } +#endif +#ifdef GL_SUN_triangle_list + if (_glewStrSame3(&pos, &len, (const GLubyte*)"triangle_list", 13)) + { + ret = GLEW_SUN_triangle_list; + continue; + } +#endif +#ifdef GL_SUN_vertex + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex", 6)) + { + ret = GLEW_SUN_vertex; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"WIN_", 4)) + { +#ifdef GL_WIN_phong_shading + if (_glewStrSame3(&pos, &len, (const GLubyte*)"phong_shading", 13)) + { + ret = GLEW_WIN_phong_shading; + continue; + } +#endif +#ifdef GL_WIN_specular_fog + if (_glewStrSame3(&pos, &len, (const GLubyte*)"specular_fog", 12)) + { + ret = GLEW_WIN_specular_fog; + continue; + } +#endif +#ifdef GL_WIN_swap_hint + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_hint", 9)) + { + ret = GLEW_WIN_swap_hint; + continue; + } +#endif + } + } + ret = (len == 0); + } + return ret; +} + +#if defined(_WIN32) + +#if defined(GLEW_MX) +GLboolean wglewContextIsSupported (WGLEWContext* ctx, const char* name) +#else +GLboolean wglewIsSupported (const char* name) +#endif +{ + GLubyte* pos = (GLubyte*)name; + GLuint len = _glewStrLen(pos); + GLboolean ret = GL_TRUE; + while (ret && len > 0) + { + if (_glewStrSame1(&pos, &len, (const GLubyte*)"WGL_", 4)) + { + if (_glewStrSame2(&pos, &len, (const GLubyte*)"3DFX_", 5)) + { +#ifdef WGL_3DFX_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = WGLEW_3DFX_multisample; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"3DL_", 4)) + { +#ifdef WGL_3DL_stereo_control + if (_glewStrSame3(&pos, &len, (const GLubyte*)"stereo_control", 14)) + { + ret = WGLEW_3DL_stereo_control; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"ARB_", 4)) + { +#ifdef WGL_ARB_buffer_region + if (_glewStrSame3(&pos, &len, (const GLubyte*)"buffer_region", 13)) + { + ret = WGLEW_ARB_buffer_region; + continue; + } +#endif +#ifdef WGL_ARB_create_context + if (_glewStrSame3(&pos, &len, (const GLubyte*)"create_context", 14)) + { + ret = WGLEW_ARB_create_context; + continue; + } +#endif +#ifdef WGL_ARB_extensions_string + if (_glewStrSame3(&pos, &len, (const GLubyte*)"extensions_string", 17)) + { + ret = WGLEW_ARB_extensions_string; + continue; + } +#endif +#ifdef WGL_ARB_framebuffer_sRGB + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_sRGB", 16)) + { + ret = WGLEW_ARB_framebuffer_sRGB; + continue; + } +#endif +#ifdef WGL_ARB_make_current_read + if (_glewStrSame3(&pos, &len, (const GLubyte*)"make_current_read", 17)) + { + ret = WGLEW_ARB_make_current_read; + continue; + } +#endif +#ifdef WGL_ARB_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = WGLEW_ARB_multisample; + continue; + } +#endif +#ifdef WGL_ARB_pbuffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pbuffer", 7)) + { + ret = WGLEW_ARB_pbuffer; + continue; + } +#endif +#ifdef WGL_ARB_pixel_format + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_format", 12)) + { + ret = WGLEW_ARB_pixel_format; + continue; + } +#endif +#ifdef WGL_ARB_pixel_format_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_format_float", 18)) + { + ret = WGLEW_ARB_pixel_format_float; + continue; + } +#endif +#ifdef WGL_ARB_render_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"render_texture", 14)) + { + ret = WGLEW_ARB_render_texture; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"ATI_", 4)) + { +#ifdef WGL_ATI_pixel_format_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_format_float", 18)) + { + ret = WGLEW_ATI_pixel_format_float; + continue; + } +#endif +#ifdef WGL_ATI_render_texture_rectangle + if (_glewStrSame3(&pos, &len, (const GLubyte*)"render_texture_rectangle", 24)) + { + ret = WGLEW_ATI_render_texture_rectangle; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"EXT_", 4)) + { +#ifdef WGL_EXT_depth_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"depth_float", 11)) + { + ret = WGLEW_EXT_depth_float; + continue; + } +#endif +#ifdef WGL_EXT_display_color_table + if (_glewStrSame3(&pos, &len, (const GLubyte*)"display_color_table", 19)) + { + ret = WGLEW_EXT_display_color_table; + continue; + } +#endif +#ifdef WGL_EXT_extensions_string + if (_glewStrSame3(&pos, &len, (const GLubyte*)"extensions_string", 17)) + { + ret = WGLEW_EXT_extensions_string; + continue; + } +#endif +#ifdef WGL_EXT_framebuffer_sRGB + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_sRGB", 16)) + { + ret = WGLEW_EXT_framebuffer_sRGB; + continue; + } +#endif +#ifdef WGL_EXT_make_current_read + if (_glewStrSame3(&pos, &len, (const GLubyte*)"make_current_read", 17)) + { + ret = WGLEW_EXT_make_current_read; + continue; + } +#endif +#ifdef WGL_EXT_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = WGLEW_EXT_multisample; + continue; + } +#endif +#ifdef WGL_EXT_pbuffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pbuffer", 7)) + { + ret = WGLEW_EXT_pbuffer; + continue; + } +#endif +#ifdef WGL_EXT_pixel_format + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_format", 12)) + { + ret = WGLEW_EXT_pixel_format; + continue; + } +#endif +#ifdef WGL_EXT_pixel_format_packed_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_format_packed_float", 25)) + { + ret = WGLEW_EXT_pixel_format_packed_float; + continue; + } +#endif +#ifdef WGL_EXT_swap_control + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_control", 12)) + { + ret = WGLEW_EXT_swap_control; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"I3D_", 4)) + { +#ifdef WGL_I3D_digital_video_control + if (_glewStrSame3(&pos, &len, (const GLubyte*)"digital_video_control", 21)) + { + ret = WGLEW_I3D_digital_video_control; + continue; + } +#endif +#ifdef WGL_I3D_gamma + if (_glewStrSame3(&pos, &len, (const GLubyte*)"gamma", 5)) + { + ret = WGLEW_I3D_gamma; + continue; + } +#endif +#ifdef WGL_I3D_genlock + if (_glewStrSame3(&pos, &len, (const GLubyte*)"genlock", 7)) + { + ret = WGLEW_I3D_genlock; + continue; + } +#endif +#ifdef WGL_I3D_image_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"image_buffer", 12)) + { + ret = WGLEW_I3D_image_buffer; + continue; + } +#endif +#ifdef WGL_I3D_swap_frame_lock + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_frame_lock", 15)) + { + ret = WGLEW_I3D_swap_frame_lock; + continue; + } +#endif +#ifdef WGL_I3D_swap_frame_usage + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_frame_usage", 16)) + { + ret = WGLEW_I3D_swap_frame_usage; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"NV_", 3)) + { +#ifdef WGL_NV_float_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"float_buffer", 12)) + { + ret = WGLEW_NV_float_buffer; + continue; + } +#endif +#ifdef WGL_NV_gpu_affinity + if (_glewStrSame3(&pos, &len, (const GLubyte*)"gpu_affinity", 12)) + { + ret = WGLEW_NV_gpu_affinity; + continue; + } +#endif +#ifdef WGL_NV_present_video + if (_glewStrSame3(&pos, &len, (const GLubyte*)"present_video", 13)) + { + ret = WGLEW_NV_present_video; + continue; + } +#endif +#ifdef WGL_NV_render_depth_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"render_depth_texture", 20)) + { + ret = WGLEW_NV_render_depth_texture; + continue; + } +#endif +#ifdef WGL_NV_render_texture_rectangle + if (_glewStrSame3(&pos, &len, (const GLubyte*)"render_texture_rectangle", 24)) + { + ret = WGLEW_NV_render_texture_rectangle; + continue; + } +#endif +#ifdef WGL_NV_swap_group + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_group", 10)) + { + ret = WGLEW_NV_swap_group; + continue; + } +#endif +#ifdef WGL_NV_vertex_array_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_range", 18)) + { + ret = WGLEW_NV_vertex_array_range; + continue; + } +#endif +#ifdef WGL_NV_video_output + if (_glewStrSame3(&pos, &len, (const GLubyte*)"video_output", 12)) + { + ret = WGLEW_NV_video_output; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"OML_", 4)) + { +#ifdef WGL_OML_sync_control + if (_glewStrSame3(&pos, &len, (const GLubyte*)"sync_control", 12)) + { + ret = WGLEW_OML_sync_control; + continue; + } +#endif + } + } + ret = (len == 0); + } + return ret; +} + +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) + +#if defined(GLEW_MX) +GLboolean glxewContextIsSupported (GLXEWContext* ctx, const char* name) +#else +GLboolean glxewIsSupported (const char* name) +#endif +{ + GLubyte* pos = (GLubyte*)name; + GLuint len = _glewStrLen(pos); + GLboolean ret = GL_TRUE; + while (ret && len > 0) + { + if(_glewStrSame1(&pos, &len, (const GLubyte*)"GLX_", 4)) + { + if (_glewStrSame2(&pos, &len, (const GLubyte*)"VERSION_", 8)) + { +#ifdef GLX_VERSION_1_2 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"1_2", 3)) + { + ret = GLXEW_VERSION_1_2; + continue; + } +#endif +#ifdef GLX_VERSION_1_3 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"1_3", 3)) + { + ret = GLXEW_VERSION_1_3; + continue; + } +#endif +#ifdef GLX_VERSION_1_4 + if (_glewStrSame3(&pos, &len, (const GLubyte*)"1_4", 3)) + { + ret = GLXEW_VERSION_1_4; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"3DFX_", 5)) + { +#ifdef GLX_3DFX_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = GLXEW_3DFX_multisample; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"ARB_", 4)) + { +#ifdef GLX_ARB_create_context + if (_glewStrSame3(&pos, &len, (const GLubyte*)"create_context", 14)) + { + ret = GLXEW_ARB_create_context; + continue; + } +#endif +#ifdef GLX_ARB_fbconfig_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fbconfig_float", 14)) + { + ret = GLXEW_ARB_fbconfig_float; + continue; + } +#endif +#ifdef GLX_ARB_framebuffer_sRGB + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_sRGB", 16)) + { + ret = GLXEW_ARB_framebuffer_sRGB; + continue; + } +#endif +#ifdef GLX_ARB_get_proc_address + if (_glewStrSame3(&pos, &len, (const GLubyte*)"get_proc_address", 16)) + { + ret = GLXEW_ARB_get_proc_address; + continue; + } +#endif +#ifdef GLX_ARB_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = GLXEW_ARB_multisample; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"ATI_", 4)) + { +#ifdef GLX_ATI_pixel_format_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixel_format_float", 18)) + { + ret = GLXEW_ATI_pixel_format_float; + continue; + } +#endif +#ifdef GLX_ATI_render_texture + if (_glewStrSame3(&pos, &len, (const GLubyte*)"render_texture", 14)) + { + ret = GLXEW_ATI_render_texture; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"EXT_", 4)) + { +#ifdef GLX_EXT_fbconfig_packed_float + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fbconfig_packed_float", 21)) + { + ret = GLXEW_EXT_fbconfig_packed_float; + continue; + } +#endif +#ifdef GLX_EXT_framebuffer_sRGB + if (_glewStrSame3(&pos, &len, (const GLubyte*)"framebuffer_sRGB", 16)) + { + ret = GLXEW_EXT_framebuffer_sRGB; + continue; + } +#endif +#ifdef GLX_EXT_import_context + if (_glewStrSame3(&pos, &len, (const GLubyte*)"import_context", 14)) + { + ret = GLXEW_EXT_import_context; + continue; + } +#endif +#ifdef GLX_EXT_scene_marker + if (_glewStrSame3(&pos, &len, (const GLubyte*)"scene_marker", 12)) + { + ret = GLXEW_EXT_scene_marker; + continue; + } +#endif +#ifdef GLX_EXT_texture_from_pixmap + if (_glewStrSame3(&pos, &len, (const GLubyte*)"texture_from_pixmap", 19)) + { + ret = GLXEW_EXT_texture_from_pixmap; + continue; + } +#endif +#ifdef GLX_EXT_visual_info + if (_glewStrSame3(&pos, &len, (const GLubyte*)"visual_info", 11)) + { + ret = GLXEW_EXT_visual_info; + continue; + } +#endif +#ifdef GLX_EXT_visual_rating + if (_glewStrSame3(&pos, &len, (const GLubyte*)"visual_rating", 13)) + { + ret = GLXEW_EXT_visual_rating; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"MESA_", 5)) + { +#ifdef GLX_MESA_agp_offset + if (_glewStrSame3(&pos, &len, (const GLubyte*)"agp_offset", 10)) + { + ret = GLXEW_MESA_agp_offset; + continue; + } +#endif +#ifdef GLX_MESA_copy_sub_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"copy_sub_buffer", 15)) + { + ret = GLXEW_MESA_copy_sub_buffer; + continue; + } +#endif +#ifdef GLX_MESA_pixmap_colormap + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pixmap_colormap", 15)) + { + ret = GLXEW_MESA_pixmap_colormap; + continue; + } +#endif +#ifdef GLX_MESA_release_buffers + if (_glewStrSame3(&pos, &len, (const GLubyte*)"release_buffers", 15)) + { + ret = GLXEW_MESA_release_buffers; + continue; + } +#endif +#ifdef GLX_MESA_set_3dfx_mode + if (_glewStrSame3(&pos, &len, (const GLubyte*)"set_3dfx_mode", 13)) + { + ret = GLXEW_MESA_set_3dfx_mode; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"NV_", 3)) + { +#ifdef GLX_NV_float_buffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"float_buffer", 12)) + { + ret = GLXEW_NV_float_buffer; + continue; + } +#endif +#ifdef GLX_NV_present_video + if (_glewStrSame3(&pos, &len, (const GLubyte*)"present_video", 13)) + { + ret = GLXEW_NV_present_video; + continue; + } +#endif +#ifdef GLX_NV_swap_group + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_group", 10)) + { + ret = GLXEW_NV_swap_group; + continue; + } +#endif +#ifdef GLX_NV_vertex_array_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"vertex_array_range", 18)) + { + ret = GLXEW_NV_vertex_array_range; + continue; + } +#endif +#ifdef GLX_NV_video_output + if (_glewStrSame3(&pos, &len, (const GLubyte*)"video_output", 12)) + { + ret = GLXEW_NV_video_output; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"OML_", 4)) + { +#ifdef GLX_OML_swap_method + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_method", 11)) + { + ret = GLXEW_OML_swap_method; + continue; + } +#endif +#if defined(GLX_OML_sync_control) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#include + if (_glewStrSame3(&pos, &len, (const GLubyte*)"sync_control", 12)) + { + ret = GLXEW_OML_sync_control; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SGIS_", 5)) + { +#ifdef GLX_SGIS_blended_overlay + if (_glewStrSame3(&pos, &len, (const GLubyte*)"blended_overlay", 15)) + { + ret = GLXEW_SGIS_blended_overlay; + continue; + } +#endif +#ifdef GLX_SGIS_color_range + if (_glewStrSame3(&pos, &len, (const GLubyte*)"color_range", 11)) + { + ret = GLXEW_SGIS_color_range; + continue; + } +#endif +#ifdef GLX_SGIS_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"multisample", 11)) + { + ret = GLXEW_SGIS_multisample; + continue; + } +#endif +#ifdef GLX_SGIS_shared_multisample + if (_glewStrSame3(&pos, &len, (const GLubyte*)"shared_multisample", 18)) + { + ret = GLXEW_SGIS_shared_multisample; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SGIX_", 5)) + { +#ifdef GLX_SGIX_fbconfig + if (_glewStrSame3(&pos, &len, (const GLubyte*)"fbconfig", 8)) + { + ret = GLXEW_SGIX_fbconfig; + continue; + } +#endif +#ifdef GLX_SGIX_hyperpipe + if (_glewStrSame3(&pos, &len, (const GLubyte*)"hyperpipe", 9)) + { + ret = GLXEW_SGIX_hyperpipe; + continue; + } +#endif +#ifdef GLX_SGIX_pbuffer + if (_glewStrSame3(&pos, &len, (const GLubyte*)"pbuffer", 7)) + { + ret = GLXEW_SGIX_pbuffer; + continue; + } +#endif +#ifdef GLX_SGIX_swap_barrier + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_barrier", 12)) + { + ret = GLXEW_SGIX_swap_barrier; + continue; + } +#endif +#ifdef GLX_SGIX_swap_group + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_group", 10)) + { + ret = GLXEW_SGIX_swap_group; + continue; + } +#endif +#ifdef GLX_SGIX_video_resize + if (_glewStrSame3(&pos, &len, (const GLubyte*)"video_resize", 12)) + { + ret = GLXEW_SGIX_video_resize; + continue; + } +#endif +#ifdef GLX_SGIX_visual_select_group + if (_glewStrSame3(&pos, &len, (const GLubyte*)"visual_select_group", 19)) + { + ret = GLXEW_SGIX_visual_select_group; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SGI_", 4)) + { +#ifdef GLX_SGI_cushion + if (_glewStrSame3(&pos, &len, (const GLubyte*)"cushion", 7)) + { + ret = GLXEW_SGI_cushion; + continue; + } +#endif +#ifdef GLX_SGI_make_current_read + if (_glewStrSame3(&pos, &len, (const GLubyte*)"make_current_read", 17)) + { + ret = GLXEW_SGI_make_current_read; + continue; + } +#endif +#ifdef GLX_SGI_swap_control + if (_glewStrSame3(&pos, &len, (const GLubyte*)"swap_control", 12)) + { + ret = GLXEW_SGI_swap_control; + continue; + } +#endif +#ifdef GLX_SGI_video_sync + if (_glewStrSame3(&pos, &len, (const GLubyte*)"video_sync", 10)) + { + ret = GLXEW_SGI_video_sync; + continue; + } +#endif + } + if (_glewStrSame2(&pos, &len, (const GLubyte*)"SUN_", 4)) + { +#ifdef GLX_SUN_get_transparent_index + if (_glewStrSame3(&pos, &len, (const GLubyte*)"get_transparent_index", 21)) + { + ret = GLXEW_SUN_get_transparent_index; + continue; + } +#endif +#ifdef GLX_SUN_video_resize + if (_glewStrSame3(&pos, &len, (const GLubyte*)"video_resize", 12)) + { + ret = GLXEW_SUN_video_resize; + continue; + } +#endif + } + } + ret = (len == 0); + } + return ret; +} + +#endif /* _WIN32 */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/glewinfo.c b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/glewinfo.c new file mode 100644 index 0000000..13c6d05 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/glewinfo.c @@ -0,0 +1,7180 @@ +/* +** The OpenGL Extension Wrangler Library +** Copyright (C) 2002-2008, Milan Ikits +** Copyright (C) 2002-2008, Marcelo E. Magallon +** Copyright (C) 2002, Lev Povalahev +** 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. +** * The name of the author may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +** THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#if defined(_WIN32) +#include +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +#include +#endif + +static FILE* f; + +#ifdef GLEW_MX +GLEWContext _glewctx; +#define glewGetContext() (&_glewctx) +#ifdef _WIN32 +WGLEWContext _wglewctx; +#define wglewGetContext() (&_wglewctx) +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +GLXEWContext _glxewctx; +#define glxewGetContext() (&_glxewctx) +#endif +#endif + +#if defined(_WIN32) +GLboolean glewCreateContext (int* pixelformat); +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +GLboolean glewCreateContext (const char* display, int* visual); +#else +GLboolean glewCreateContext (); +#endif + +#if defined(_WIN32) || !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +GLboolean glewParseArgs (int argc, char** argv, char** display, int* visual); +#endif + +void glewDestroyContext (); + +/* ------------------------------------------------------------------------- */ + +static void glewPrintExt (const char* name, GLboolean def1, GLboolean def2, GLboolean def3) +{ + unsigned int i; + fprintf(f, "\n%s:", name); + for (i=0; i<62-strlen(name); i++) fprintf(f, " "); + fprintf(f, "%s ", def1 ? "OK" : "MISSING"); + if (def1 != def2) + fprintf(f, "[%s] ", def2 ? "OK" : "MISSING"); + if (def1 != def3) + fprintf(f, "[%s]\n", def3 ? "OK" : "MISSING"); + else + fprintf(f, "\n"); + for (i=0; i= 199901L) +#include + +static void _glewInfo_GLX_OML_sync_control (void) +{ + glewPrintExt("GLX_OML_sync_control", GLXEW_OML_sync_control, glxewIsSupported("GLX_OML_sync_control"), glxewGetExtension("GLX_OML_sync_control")); + + glewInfoFunc("glXGetMscRateOML", glXGetMscRateOML == NULL); + glewInfoFunc("glXGetSyncValuesOML", glXGetSyncValuesOML == NULL); + glewInfoFunc("glXSwapBuffersMscOML", glXSwapBuffersMscOML == NULL); + glewInfoFunc("glXWaitForMscOML", glXWaitForMscOML == NULL); + glewInfoFunc("glXWaitForSbcOML", glXWaitForSbcOML == NULL); +} + +#endif /* GLX_OML_sync_control */ + +#ifdef GLX_SGIS_blended_overlay + +static void _glewInfo_GLX_SGIS_blended_overlay (void) +{ + glewPrintExt("GLX_SGIS_blended_overlay", GLXEW_SGIS_blended_overlay, glxewIsSupported("GLX_SGIS_blended_overlay"), glxewGetExtension("GLX_SGIS_blended_overlay")); +} + +#endif /* GLX_SGIS_blended_overlay */ + +#ifdef GLX_SGIS_color_range + +static void _glewInfo_GLX_SGIS_color_range (void) +{ + glewPrintExt("GLX_SGIS_color_range", GLXEW_SGIS_color_range, glxewIsSupported("GLX_SGIS_color_range"), glxewGetExtension("GLX_SGIS_color_range")); +} + +#endif /* GLX_SGIS_color_range */ + +#ifdef GLX_SGIS_multisample + +static void _glewInfo_GLX_SGIS_multisample (void) +{ + glewPrintExt("GLX_SGIS_multisample", GLXEW_SGIS_multisample, glxewIsSupported("GLX_SGIS_multisample"), glxewGetExtension("GLX_SGIS_multisample")); +} + +#endif /* GLX_SGIS_multisample */ + +#ifdef GLX_SGIS_shared_multisample + +static void _glewInfo_GLX_SGIS_shared_multisample (void) +{ + glewPrintExt("GLX_SGIS_shared_multisample", GLXEW_SGIS_shared_multisample, glxewIsSupported("GLX_SGIS_shared_multisample"), glxewGetExtension("GLX_SGIS_shared_multisample")); +} + +#endif /* GLX_SGIS_shared_multisample */ + +#ifdef GLX_SGIX_fbconfig + +static void _glewInfo_GLX_SGIX_fbconfig (void) +{ + glewPrintExt("GLX_SGIX_fbconfig", GLXEW_SGIX_fbconfig, glxewIsSupported("GLX_SGIX_fbconfig"), glxewGetExtension("GLX_SGIX_fbconfig")); + + glewInfoFunc("glXChooseFBConfigSGIX", glXChooseFBConfigSGIX == NULL); + glewInfoFunc("glXCreateContextWithConfigSGIX", glXCreateContextWithConfigSGIX == NULL); + glewInfoFunc("glXCreateGLXPixmapWithConfigSGIX", glXCreateGLXPixmapWithConfigSGIX == NULL); + glewInfoFunc("glXGetFBConfigAttribSGIX", glXGetFBConfigAttribSGIX == NULL); + glewInfoFunc("glXGetFBConfigFromVisualSGIX", glXGetFBConfigFromVisualSGIX == NULL); + glewInfoFunc("glXGetVisualFromFBConfigSGIX", glXGetVisualFromFBConfigSGIX == NULL); +} + +#endif /* GLX_SGIX_fbconfig */ + +#ifdef GLX_SGIX_hyperpipe + +static void _glewInfo_GLX_SGIX_hyperpipe (void) +{ + glewPrintExt("GLX_SGIX_hyperpipe", GLXEW_SGIX_hyperpipe, glxewIsSupported("GLX_SGIX_hyperpipe"), glxewGetExtension("GLX_SGIX_hyperpipe")); + + glewInfoFunc("glXBindHyperpipeSGIX", glXBindHyperpipeSGIX == NULL); + glewInfoFunc("glXDestroyHyperpipeConfigSGIX", glXDestroyHyperpipeConfigSGIX == NULL); + glewInfoFunc("glXHyperpipeAttribSGIX", glXHyperpipeAttribSGIX == NULL); + glewInfoFunc("glXHyperpipeConfigSGIX", glXHyperpipeConfigSGIX == NULL); + glewInfoFunc("glXQueryHyperpipeAttribSGIX", glXQueryHyperpipeAttribSGIX == NULL); + glewInfoFunc("glXQueryHyperpipeBestAttribSGIX", glXQueryHyperpipeBestAttribSGIX == NULL); + glewInfoFunc("glXQueryHyperpipeConfigSGIX", glXQueryHyperpipeConfigSGIX == NULL); + glewInfoFunc("glXQueryHyperpipeNetworkSGIX", glXQueryHyperpipeNetworkSGIX == NULL); +} + +#endif /* GLX_SGIX_hyperpipe */ + +#ifdef GLX_SGIX_pbuffer + +static void _glewInfo_GLX_SGIX_pbuffer (void) +{ + glewPrintExt("GLX_SGIX_pbuffer", GLXEW_SGIX_pbuffer, glxewIsSupported("GLX_SGIX_pbuffer"), glxewGetExtension("GLX_SGIX_pbuffer")); + + glewInfoFunc("glXCreateGLXPbufferSGIX", glXCreateGLXPbufferSGIX == NULL); + glewInfoFunc("glXDestroyGLXPbufferSGIX", glXDestroyGLXPbufferSGIX == NULL); + glewInfoFunc("glXGetSelectedEventSGIX", glXGetSelectedEventSGIX == NULL); + glewInfoFunc("glXQueryGLXPbufferSGIX", glXQueryGLXPbufferSGIX == NULL); + glewInfoFunc("glXSelectEventSGIX", glXSelectEventSGIX == NULL); +} + +#endif /* GLX_SGIX_pbuffer */ + +#ifdef GLX_SGIX_swap_barrier + +static void _glewInfo_GLX_SGIX_swap_barrier (void) +{ + glewPrintExt("GLX_SGIX_swap_barrier", GLXEW_SGIX_swap_barrier, glxewIsSupported("GLX_SGIX_swap_barrier"), glxewGetExtension("GLX_SGIX_swap_barrier")); + + glewInfoFunc("glXBindSwapBarrierSGIX", glXBindSwapBarrierSGIX == NULL); + glewInfoFunc("glXQueryMaxSwapBarriersSGIX", glXQueryMaxSwapBarriersSGIX == NULL); +} + +#endif /* GLX_SGIX_swap_barrier */ + +#ifdef GLX_SGIX_swap_group + +static void _glewInfo_GLX_SGIX_swap_group (void) +{ + glewPrintExt("GLX_SGIX_swap_group", GLXEW_SGIX_swap_group, glxewIsSupported("GLX_SGIX_swap_group"), glxewGetExtension("GLX_SGIX_swap_group")); + + glewInfoFunc("glXJoinSwapGroupSGIX", glXJoinSwapGroupSGIX == NULL); +} + +#endif /* GLX_SGIX_swap_group */ + +#ifdef GLX_SGIX_video_resize + +static void _glewInfo_GLX_SGIX_video_resize (void) +{ + glewPrintExt("GLX_SGIX_video_resize", GLXEW_SGIX_video_resize, glxewIsSupported("GLX_SGIX_video_resize"), glxewGetExtension("GLX_SGIX_video_resize")); + + glewInfoFunc("glXBindChannelToWindowSGIX", glXBindChannelToWindowSGIX == NULL); + glewInfoFunc("glXChannelRectSGIX", glXChannelRectSGIX == NULL); + glewInfoFunc("glXChannelRectSyncSGIX", glXChannelRectSyncSGIX == NULL); + glewInfoFunc("glXQueryChannelDeltasSGIX", glXQueryChannelDeltasSGIX == NULL); + glewInfoFunc("glXQueryChannelRectSGIX", glXQueryChannelRectSGIX == NULL); +} + +#endif /* GLX_SGIX_video_resize */ + +#ifdef GLX_SGIX_visual_select_group + +static void _glewInfo_GLX_SGIX_visual_select_group (void) +{ + glewPrintExt("GLX_SGIX_visual_select_group", GLXEW_SGIX_visual_select_group, glxewIsSupported("GLX_SGIX_visual_select_group"), glxewGetExtension("GLX_SGIX_visual_select_group")); +} + +#endif /* GLX_SGIX_visual_select_group */ + +#ifdef GLX_SGI_cushion + +static void _glewInfo_GLX_SGI_cushion (void) +{ + glewPrintExt("GLX_SGI_cushion", GLXEW_SGI_cushion, glxewIsSupported("GLX_SGI_cushion"), glxewGetExtension("GLX_SGI_cushion")); + + glewInfoFunc("glXCushionSGI", glXCushionSGI == NULL); +} + +#endif /* GLX_SGI_cushion */ + +#ifdef GLX_SGI_make_current_read + +static void _glewInfo_GLX_SGI_make_current_read (void) +{ + glewPrintExt("GLX_SGI_make_current_read", GLXEW_SGI_make_current_read, glxewIsSupported("GLX_SGI_make_current_read"), glxewGetExtension("GLX_SGI_make_current_read")); + + glewInfoFunc("glXGetCurrentReadDrawableSGI", glXGetCurrentReadDrawableSGI == NULL); + glewInfoFunc("glXMakeCurrentReadSGI", glXMakeCurrentReadSGI == NULL); +} + +#endif /* GLX_SGI_make_current_read */ + +#ifdef GLX_SGI_swap_control + +static void _glewInfo_GLX_SGI_swap_control (void) +{ + glewPrintExt("GLX_SGI_swap_control", GLXEW_SGI_swap_control, glxewIsSupported("GLX_SGI_swap_control"), glxewGetExtension("GLX_SGI_swap_control")); + + glewInfoFunc("glXSwapIntervalSGI", glXSwapIntervalSGI == NULL); +} + +#endif /* GLX_SGI_swap_control */ + +#ifdef GLX_SGI_video_sync + +static void _glewInfo_GLX_SGI_video_sync (void) +{ + glewPrintExt("GLX_SGI_video_sync", GLXEW_SGI_video_sync, glxewIsSupported("GLX_SGI_video_sync"), glxewGetExtension("GLX_SGI_video_sync")); + + glewInfoFunc("glXGetVideoSyncSGI", glXGetVideoSyncSGI == NULL); + glewInfoFunc("glXWaitVideoSyncSGI", glXWaitVideoSyncSGI == NULL); +} + +#endif /* GLX_SGI_video_sync */ + +#ifdef GLX_SUN_get_transparent_index + +static void _glewInfo_GLX_SUN_get_transparent_index (void) +{ + glewPrintExt("GLX_SUN_get_transparent_index", GLXEW_SUN_get_transparent_index, glxewIsSupported("GLX_SUN_get_transparent_index"), glxewGetExtension("GLX_SUN_get_transparent_index")); + + glewInfoFunc("glXGetTransparentIndexSUN", glXGetTransparentIndexSUN == NULL); +} + +#endif /* GLX_SUN_get_transparent_index */ + +#ifdef GLX_SUN_video_resize + +static void _glewInfo_GLX_SUN_video_resize (void) +{ + glewPrintExt("GLX_SUN_video_resize", GLXEW_SUN_video_resize, glxewIsSupported("GLX_SUN_video_resize"), glxewGetExtension("GLX_SUN_video_resize")); + + glewInfoFunc("glXGetVideoResizeSUN", glXGetVideoResizeSUN == NULL); + glewInfoFunc("glXVideoResizeSUN", glXVideoResizeSUN == NULL); +} + +#endif /* GLX_SUN_video_resize */ + +#endif /* _WIN32 */ + +/* ------------------------------------------------------------------------ */ + +static void glewInfo (void) +{ +#ifdef GL_VERSION_1_1 + _glewInfo_GL_VERSION_1_1(); +#endif /* GL_VERSION_1_1 */ +#ifdef GL_VERSION_1_2 + _glewInfo_GL_VERSION_1_2(); +#endif /* GL_VERSION_1_2 */ +#ifdef GL_VERSION_1_3 + _glewInfo_GL_VERSION_1_3(); +#endif /* GL_VERSION_1_3 */ +#ifdef GL_VERSION_1_4 + _glewInfo_GL_VERSION_1_4(); +#endif /* GL_VERSION_1_4 */ +#ifdef GL_VERSION_1_5 + _glewInfo_GL_VERSION_1_5(); +#endif /* GL_VERSION_1_5 */ +#ifdef GL_VERSION_2_0 + _glewInfo_GL_VERSION_2_0(); +#endif /* GL_VERSION_2_0 */ +#ifdef GL_VERSION_2_1 + _glewInfo_GL_VERSION_2_1(); +#endif /* GL_VERSION_2_1 */ +#ifdef GL_VERSION_3_0 + _glewInfo_GL_VERSION_3_0(); +#endif /* GL_VERSION_3_0 */ +#ifdef GL_3DFX_multisample + _glewInfo_GL_3DFX_multisample(); +#endif /* GL_3DFX_multisample */ +#ifdef GL_3DFX_tbuffer + _glewInfo_GL_3DFX_tbuffer(); +#endif /* GL_3DFX_tbuffer */ +#ifdef GL_3DFX_texture_compression_FXT1 + _glewInfo_GL_3DFX_texture_compression_FXT1(); +#endif /* GL_3DFX_texture_compression_FXT1 */ +#ifdef GL_APPLE_client_storage + _glewInfo_GL_APPLE_client_storage(); +#endif /* GL_APPLE_client_storage */ +#ifdef GL_APPLE_element_array + _glewInfo_GL_APPLE_element_array(); +#endif /* GL_APPLE_element_array */ +#ifdef GL_APPLE_fence + _glewInfo_GL_APPLE_fence(); +#endif /* GL_APPLE_fence */ +#ifdef GL_APPLE_float_pixels + _glewInfo_GL_APPLE_float_pixels(); +#endif /* GL_APPLE_float_pixels */ +#ifdef GL_APPLE_flush_buffer_range + _glewInfo_GL_APPLE_flush_buffer_range(); +#endif /* GL_APPLE_flush_buffer_range */ +#ifdef GL_APPLE_pixel_buffer + _glewInfo_GL_APPLE_pixel_buffer(); +#endif /* GL_APPLE_pixel_buffer */ +#ifdef GL_APPLE_specular_vector + _glewInfo_GL_APPLE_specular_vector(); +#endif /* GL_APPLE_specular_vector */ +#ifdef GL_APPLE_texture_range + _glewInfo_GL_APPLE_texture_range(); +#endif /* GL_APPLE_texture_range */ +#ifdef GL_APPLE_transform_hint + _glewInfo_GL_APPLE_transform_hint(); +#endif /* GL_APPLE_transform_hint */ +#ifdef GL_APPLE_vertex_array_object + _glewInfo_GL_APPLE_vertex_array_object(); +#endif /* GL_APPLE_vertex_array_object */ +#ifdef GL_APPLE_vertex_array_range + _glewInfo_GL_APPLE_vertex_array_range(); +#endif /* GL_APPLE_vertex_array_range */ +#ifdef GL_APPLE_ycbcr_422 + _glewInfo_GL_APPLE_ycbcr_422(); +#endif /* GL_APPLE_ycbcr_422 */ +#ifdef GL_ARB_color_buffer_float + _glewInfo_GL_ARB_color_buffer_float(); +#endif /* GL_ARB_color_buffer_float */ +#ifdef GL_ARB_depth_buffer_float + _glewInfo_GL_ARB_depth_buffer_float(); +#endif /* GL_ARB_depth_buffer_float */ +#ifdef GL_ARB_depth_texture + _glewInfo_GL_ARB_depth_texture(); +#endif /* GL_ARB_depth_texture */ +#ifdef GL_ARB_draw_buffers + _glewInfo_GL_ARB_draw_buffers(); +#endif /* GL_ARB_draw_buffers */ +#ifdef GL_ARB_draw_instanced + _glewInfo_GL_ARB_draw_instanced(); +#endif /* GL_ARB_draw_instanced */ +#ifdef GL_ARB_fragment_program + _glewInfo_GL_ARB_fragment_program(); +#endif /* GL_ARB_fragment_program */ +#ifdef GL_ARB_fragment_program_shadow + _glewInfo_GL_ARB_fragment_program_shadow(); +#endif /* GL_ARB_fragment_program_shadow */ +#ifdef GL_ARB_fragment_shader + _glewInfo_GL_ARB_fragment_shader(); +#endif /* GL_ARB_fragment_shader */ +#ifdef GL_ARB_framebuffer_object + _glewInfo_GL_ARB_framebuffer_object(); +#endif /* GL_ARB_framebuffer_object */ +#ifdef GL_ARB_framebuffer_sRGB + _glewInfo_GL_ARB_framebuffer_sRGB(); +#endif /* GL_ARB_framebuffer_sRGB */ +#ifdef GL_ARB_geometry_shader4 + _glewInfo_GL_ARB_geometry_shader4(); +#endif /* GL_ARB_geometry_shader4 */ +#ifdef GL_ARB_half_float_pixel + _glewInfo_GL_ARB_half_float_pixel(); +#endif /* GL_ARB_half_float_pixel */ +#ifdef GL_ARB_half_float_vertex + _glewInfo_GL_ARB_half_float_vertex(); +#endif /* GL_ARB_half_float_vertex */ +#ifdef GL_ARB_imaging + _glewInfo_GL_ARB_imaging(); +#endif /* GL_ARB_imaging */ +#ifdef GL_ARB_instanced_arrays + _glewInfo_GL_ARB_instanced_arrays(); +#endif /* GL_ARB_instanced_arrays */ +#ifdef GL_ARB_map_buffer_range + _glewInfo_GL_ARB_map_buffer_range(); +#endif /* GL_ARB_map_buffer_range */ +#ifdef GL_ARB_matrix_palette + _glewInfo_GL_ARB_matrix_palette(); +#endif /* GL_ARB_matrix_palette */ +#ifdef GL_ARB_multisample + _glewInfo_GL_ARB_multisample(); +#endif /* GL_ARB_multisample */ +#ifdef GL_ARB_multitexture + _glewInfo_GL_ARB_multitexture(); +#endif /* GL_ARB_multitexture */ +#ifdef GL_ARB_occlusion_query + _glewInfo_GL_ARB_occlusion_query(); +#endif /* GL_ARB_occlusion_query */ +#ifdef GL_ARB_pixel_buffer_object + _glewInfo_GL_ARB_pixel_buffer_object(); +#endif /* GL_ARB_pixel_buffer_object */ +#ifdef GL_ARB_point_parameters + _glewInfo_GL_ARB_point_parameters(); +#endif /* GL_ARB_point_parameters */ +#ifdef GL_ARB_point_sprite + _glewInfo_GL_ARB_point_sprite(); +#endif /* GL_ARB_point_sprite */ +#ifdef GL_ARB_shader_objects + _glewInfo_GL_ARB_shader_objects(); +#endif /* GL_ARB_shader_objects */ +#ifdef GL_ARB_shading_language_100 + _glewInfo_GL_ARB_shading_language_100(); +#endif /* GL_ARB_shading_language_100 */ +#ifdef GL_ARB_shadow + _glewInfo_GL_ARB_shadow(); +#endif /* GL_ARB_shadow */ +#ifdef GL_ARB_shadow_ambient + _glewInfo_GL_ARB_shadow_ambient(); +#endif /* GL_ARB_shadow_ambient */ +#ifdef GL_ARB_texture_border_clamp + _glewInfo_GL_ARB_texture_border_clamp(); +#endif /* GL_ARB_texture_border_clamp */ +#ifdef GL_ARB_texture_buffer_object + _glewInfo_GL_ARB_texture_buffer_object(); +#endif /* GL_ARB_texture_buffer_object */ +#ifdef GL_ARB_texture_compression + _glewInfo_GL_ARB_texture_compression(); +#endif /* GL_ARB_texture_compression */ +#ifdef GL_ARB_texture_compression_rgtc + _glewInfo_GL_ARB_texture_compression_rgtc(); +#endif /* GL_ARB_texture_compression_rgtc */ +#ifdef GL_ARB_texture_cube_map + _glewInfo_GL_ARB_texture_cube_map(); +#endif /* GL_ARB_texture_cube_map */ +#ifdef GL_ARB_texture_env_add + _glewInfo_GL_ARB_texture_env_add(); +#endif /* GL_ARB_texture_env_add */ +#ifdef GL_ARB_texture_env_combine + _glewInfo_GL_ARB_texture_env_combine(); +#endif /* GL_ARB_texture_env_combine */ +#ifdef GL_ARB_texture_env_crossbar + _glewInfo_GL_ARB_texture_env_crossbar(); +#endif /* GL_ARB_texture_env_crossbar */ +#ifdef GL_ARB_texture_env_dot3 + _glewInfo_GL_ARB_texture_env_dot3(); +#endif /* GL_ARB_texture_env_dot3 */ +#ifdef GL_ARB_texture_float + _glewInfo_GL_ARB_texture_float(); +#endif /* GL_ARB_texture_float */ +#ifdef GL_ARB_texture_mirrored_repeat + _glewInfo_GL_ARB_texture_mirrored_repeat(); +#endif /* GL_ARB_texture_mirrored_repeat */ +#ifdef GL_ARB_texture_non_power_of_two + _glewInfo_GL_ARB_texture_non_power_of_two(); +#endif /* GL_ARB_texture_non_power_of_two */ +#ifdef GL_ARB_texture_rectangle + _glewInfo_GL_ARB_texture_rectangle(); +#endif /* GL_ARB_texture_rectangle */ +#ifdef GL_ARB_texture_rg + _glewInfo_GL_ARB_texture_rg(); +#endif /* GL_ARB_texture_rg */ +#ifdef GL_ARB_transpose_matrix + _glewInfo_GL_ARB_transpose_matrix(); +#endif /* GL_ARB_transpose_matrix */ +#ifdef GL_ARB_vertex_array_object + _glewInfo_GL_ARB_vertex_array_object(); +#endif /* GL_ARB_vertex_array_object */ +#ifdef GL_ARB_vertex_blend + _glewInfo_GL_ARB_vertex_blend(); +#endif /* GL_ARB_vertex_blend */ +#ifdef GL_ARB_vertex_buffer_object + _glewInfo_GL_ARB_vertex_buffer_object(); +#endif /* GL_ARB_vertex_buffer_object */ +#ifdef GL_ARB_vertex_program + _glewInfo_GL_ARB_vertex_program(); +#endif /* GL_ARB_vertex_program */ +#ifdef GL_ARB_vertex_shader + _glewInfo_GL_ARB_vertex_shader(); +#endif /* GL_ARB_vertex_shader */ +#ifdef GL_ARB_window_pos + _glewInfo_GL_ARB_window_pos(); +#endif /* GL_ARB_window_pos */ +#ifdef GL_ATIX_point_sprites + _glewInfo_GL_ATIX_point_sprites(); +#endif /* GL_ATIX_point_sprites */ +#ifdef GL_ATIX_texture_env_combine3 + _glewInfo_GL_ATIX_texture_env_combine3(); +#endif /* GL_ATIX_texture_env_combine3 */ +#ifdef GL_ATIX_texture_env_route + _glewInfo_GL_ATIX_texture_env_route(); +#endif /* GL_ATIX_texture_env_route */ +#ifdef GL_ATIX_vertex_shader_output_point_size + _glewInfo_GL_ATIX_vertex_shader_output_point_size(); +#endif /* GL_ATIX_vertex_shader_output_point_size */ +#ifdef GL_ATI_draw_buffers + _glewInfo_GL_ATI_draw_buffers(); +#endif /* GL_ATI_draw_buffers */ +#ifdef GL_ATI_element_array + _glewInfo_GL_ATI_element_array(); +#endif /* GL_ATI_element_array */ +#ifdef GL_ATI_envmap_bumpmap + _glewInfo_GL_ATI_envmap_bumpmap(); +#endif /* GL_ATI_envmap_bumpmap */ +#ifdef GL_ATI_fragment_shader + _glewInfo_GL_ATI_fragment_shader(); +#endif /* GL_ATI_fragment_shader */ +#ifdef GL_ATI_map_object_buffer + _glewInfo_GL_ATI_map_object_buffer(); +#endif /* GL_ATI_map_object_buffer */ +#ifdef GL_ATI_pn_triangles + _glewInfo_GL_ATI_pn_triangles(); +#endif /* GL_ATI_pn_triangles */ +#ifdef GL_ATI_separate_stencil + _glewInfo_GL_ATI_separate_stencil(); +#endif /* GL_ATI_separate_stencil */ +#ifdef GL_ATI_shader_texture_lod + _glewInfo_GL_ATI_shader_texture_lod(); +#endif /* GL_ATI_shader_texture_lod */ +#ifdef GL_ATI_text_fragment_shader + _glewInfo_GL_ATI_text_fragment_shader(); +#endif /* GL_ATI_text_fragment_shader */ +#ifdef GL_ATI_texture_compression_3dc + _glewInfo_GL_ATI_texture_compression_3dc(); +#endif /* GL_ATI_texture_compression_3dc */ +#ifdef GL_ATI_texture_env_combine3 + _glewInfo_GL_ATI_texture_env_combine3(); +#endif /* GL_ATI_texture_env_combine3 */ +#ifdef GL_ATI_texture_float + _glewInfo_GL_ATI_texture_float(); +#endif /* GL_ATI_texture_float */ +#ifdef GL_ATI_texture_mirror_once + _glewInfo_GL_ATI_texture_mirror_once(); +#endif /* GL_ATI_texture_mirror_once */ +#ifdef GL_ATI_vertex_array_object + _glewInfo_GL_ATI_vertex_array_object(); +#endif /* GL_ATI_vertex_array_object */ +#ifdef GL_ATI_vertex_attrib_array_object + _glewInfo_GL_ATI_vertex_attrib_array_object(); +#endif /* GL_ATI_vertex_attrib_array_object */ +#ifdef GL_ATI_vertex_streams + _glewInfo_GL_ATI_vertex_streams(); +#endif /* GL_ATI_vertex_streams */ +#ifdef GL_EXT_422_pixels + _glewInfo_GL_EXT_422_pixels(); +#endif /* GL_EXT_422_pixels */ +#ifdef GL_EXT_Cg_shader + _glewInfo_GL_EXT_Cg_shader(); +#endif /* GL_EXT_Cg_shader */ +#ifdef GL_EXT_abgr + _glewInfo_GL_EXT_abgr(); +#endif /* GL_EXT_abgr */ +#ifdef GL_EXT_bgra + _glewInfo_GL_EXT_bgra(); +#endif /* GL_EXT_bgra */ +#ifdef GL_EXT_bindable_uniform + _glewInfo_GL_EXT_bindable_uniform(); +#endif /* GL_EXT_bindable_uniform */ +#ifdef GL_EXT_blend_color + _glewInfo_GL_EXT_blend_color(); +#endif /* GL_EXT_blend_color */ +#ifdef GL_EXT_blend_equation_separate + _glewInfo_GL_EXT_blend_equation_separate(); +#endif /* GL_EXT_blend_equation_separate */ +#ifdef GL_EXT_blend_func_separate + _glewInfo_GL_EXT_blend_func_separate(); +#endif /* GL_EXT_blend_func_separate */ +#ifdef GL_EXT_blend_logic_op + _glewInfo_GL_EXT_blend_logic_op(); +#endif /* GL_EXT_blend_logic_op */ +#ifdef GL_EXT_blend_minmax + _glewInfo_GL_EXT_blend_minmax(); +#endif /* GL_EXT_blend_minmax */ +#ifdef GL_EXT_blend_subtract + _glewInfo_GL_EXT_blend_subtract(); +#endif /* GL_EXT_blend_subtract */ +#ifdef GL_EXT_clip_volume_hint + _glewInfo_GL_EXT_clip_volume_hint(); +#endif /* GL_EXT_clip_volume_hint */ +#ifdef GL_EXT_cmyka + _glewInfo_GL_EXT_cmyka(); +#endif /* GL_EXT_cmyka */ +#ifdef GL_EXT_color_subtable + _glewInfo_GL_EXT_color_subtable(); +#endif /* GL_EXT_color_subtable */ +#ifdef GL_EXT_compiled_vertex_array + _glewInfo_GL_EXT_compiled_vertex_array(); +#endif /* GL_EXT_compiled_vertex_array */ +#ifdef GL_EXT_convolution + _glewInfo_GL_EXT_convolution(); +#endif /* GL_EXT_convolution */ +#ifdef GL_EXT_coordinate_frame + _glewInfo_GL_EXT_coordinate_frame(); +#endif /* GL_EXT_coordinate_frame */ +#ifdef GL_EXT_copy_texture + _glewInfo_GL_EXT_copy_texture(); +#endif /* GL_EXT_copy_texture */ +#ifdef GL_EXT_cull_vertex + _glewInfo_GL_EXT_cull_vertex(); +#endif /* GL_EXT_cull_vertex */ +#ifdef GL_EXT_depth_bounds_test + _glewInfo_GL_EXT_depth_bounds_test(); +#endif /* GL_EXT_depth_bounds_test */ +#ifdef GL_EXT_direct_state_access + _glewInfo_GL_EXT_direct_state_access(); +#endif /* GL_EXT_direct_state_access */ +#ifdef GL_EXT_draw_buffers2 + _glewInfo_GL_EXT_draw_buffers2(); +#endif /* GL_EXT_draw_buffers2 */ +#ifdef GL_EXT_draw_instanced + _glewInfo_GL_EXT_draw_instanced(); +#endif /* GL_EXT_draw_instanced */ +#ifdef GL_EXT_draw_range_elements + _glewInfo_GL_EXT_draw_range_elements(); +#endif /* GL_EXT_draw_range_elements */ +#ifdef GL_EXT_fog_coord + _glewInfo_GL_EXT_fog_coord(); +#endif /* GL_EXT_fog_coord */ +#ifdef GL_EXT_fragment_lighting + _glewInfo_GL_EXT_fragment_lighting(); +#endif /* GL_EXT_fragment_lighting */ +#ifdef GL_EXT_framebuffer_blit + _glewInfo_GL_EXT_framebuffer_blit(); +#endif /* GL_EXT_framebuffer_blit */ +#ifdef GL_EXT_framebuffer_multisample + _glewInfo_GL_EXT_framebuffer_multisample(); +#endif /* GL_EXT_framebuffer_multisample */ +#ifdef GL_EXT_framebuffer_object + _glewInfo_GL_EXT_framebuffer_object(); +#endif /* GL_EXT_framebuffer_object */ +#ifdef GL_EXT_framebuffer_sRGB + _glewInfo_GL_EXT_framebuffer_sRGB(); +#endif /* GL_EXT_framebuffer_sRGB */ +#ifdef GL_EXT_geometry_shader4 + _glewInfo_GL_EXT_geometry_shader4(); +#endif /* GL_EXT_geometry_shader4 */ +#ifdef GL_EXT_gpu_program_parameters + _glewInfo_GL_EXT_gpu_program_parameters(); +#endif /* GL_EXT_gpu_program_parameters */ +#ifdef GL_EXT_gpu_shader4 + _glewInfo_GL_EXT_gpu_shader4(); +#endif /* GL_EXT_gpu_shader4 */ +#ifdef GL_EXT_histogram + _glewInfo_GL_EXT_histogram(); +#endif /* GL_EXT_histogram */ +#ifdef GL_EXT_index_array_formats + _glewInfo_GL_EXT_index_array_formats(); +#endif /* GL_EXT_index_array_formats */ +#ifdef GL_EXT_index_func + _glewInfo_GL_EXT_index_func(); +#endif /* GL_EXT_index_func */ +#ifdef GL_EXT_index_material + _glewInfo_GL_EXT_index_material(); +#endif /* GL_EXT_index_material */ +#ifdef GL_EXT_index_texture + _glewInfo_GL_EXT_index_texture(); +#endif /* GL_EXT_index_texture */ +#ifdef GL_EXT_light_texture + _glewInfo_GL_EXT_light_texture(); +#endif /* GL_EXT_light_texture */ +#ifdef GL_EXT_misc_attribute + _glewInfo_GL_EXT_misc_attribute(); +#endif /* GL_EXT_misc_attribute */ +#ifdef GL_EXT_multi_draw_arrays + _glewInfo_GL_EXT_multi_draw_arrays(); +#endif /* GL_EXT_multi_draw_arrays */ +#ifdef GL_EXT_multisample + _glewInfo_GL_EXT_multisample(); +#endif /* GL_EXT_multisample */ +#ifdef GL_EXT_packed_depth_stencil + _glewInfo_GL_EXT_packed_depth_stencil(); +#endif /* GL_EXT_packed_depth_stencil */ +#ifdef GL_EXT_packed_float + _glewInfo_GL_EXT_packed_float(); +#endif /* GL_EXT_packed_float */ +#ifdef GL_EXT_packed_pixels + _glewInfo_GL_EXT_packed_pixels(); +#endif /* GL_EXT_packed_pixels */ +#ifdef GL_EXT_paletted_texture + _glewInfo_GL_EXT_paletted_texture(); +#endif /* GL_EXT_paletted_texture */ +#ifdef GL_EXT_pixel_buffer_object + _glewInfo_GL_EXT_pixel_buffer_object(); +#endif /* GL_EXT_pixel_buffer_object */ +#ifdef GL_EXT_pixel_transform + _glewInfo_GL_EXT_pixel_transform(); +#endif /* GL_EXT_pixel_transform */ +#ifdef GL_EXT_pixel_transform_color_table + _glewInfo_GL_EXT_pixel_transform_color_table(); +#endif /* GL_EXT_pixel_transform_color_table */ +#ifdef GL_EXT_point_parameters + _glewInfo_GL_EXT_point_parameters(); +#endif /* GL_EXT_point_parameters */ +#ifdef GL_EXT_polygon_offset + _glewInfo_GL_EXT_polygon_offset(); +#endif /* GL_EXT_polygon_offset */ +#ifdef GL_EXT_rescale_normal + _glewInfo_GL_EXT_rescale_normal(); +#endif /* GL_EXT_rescale_normal */ +#ifdef GL_EXT_scene_marker + _glewInfo_GL_EXT_scene_marker(); +#endif /* GL_EXT_scene_marker */ +#ifdef GL_EXT_secondary_color + _glewInfo_GL_EXT_secondary_color(); +#endif /* GL_EXT_secondary_color */ +#ifdef GL_EXT_separate_specular_color + _glewInfo_GL_EXT_separate_specular_color(); +#endif /* GL_EXT_separate_specular_color */ +#ifdef GL_EXT_shadow_funcs + _glewInfo_GL_EXT_shadow_funcs(); +#endif /* GL_EXT_shadow_funcs */ +#ifdef GL_EXT_shared_texture_palette + _glewInfo_GL_EXT_shared_texture_palette(); +#endif /* GL_EXT_shared_texture_palette */ +#ifdef GL_EXT_stencil_clear_tag + _glewInfo_GL_EXT_stencil_clear_tag(); +#endif /* GL_EXT_stencil_clear_tag */ +#ifdef GL_EXT_stencil_two_side + _glewInfo_GL_EXT_stencil_two_side(); +#endif /* GL_EXT_stencil_two_side */ +#ifdef GL_EXT_stencil_wrap + _glewInfo_GL_EXT_stencil_wrap(); +#endif /* GL_EXT_stencil_wrap */ +#ifdef GL_EXT_subtexture + _glewInfo_GL_EXT_subtexture(); +#endif /* GL_EXT_subtexture */ +#ifdef GL_EXT_texture + _glewInfo_GL_EXT_texture(); +#endif /* GL_EXT_texture */ +#ifdef GL_EXT_texture3D + _glewInfo_GL_EXT_texture3D(); +#endif /* GL_EXT_texture3D */ +#ifdef GL_EXT_texture_array + _glewInfo_GL_EXT_texture_array(); +#endif /* GL_EXT_texture_array */ +#ifdef GL_EXT_texture_buffer_object + _glewInfo_GL_EXT_texture_buffer_object(); +#endif /* GL_EXT_texture_buffer_object */ +#ifdef GL_EXT_texture_compression_dxt1 + _glewInfo_GL_EXT_texture_compression_dxt1(); +#endif /* GL_EXT_texture_compression_dxt1 */ +#ifdef GL_EXT_texture_compression_latc + _glewInfo_GL_EXT_texture_compression_latc(); +#endif /* GL_EXT_texture_compression_latc */ +#ifdef GL_EXT_texture_compression_rgtc + _glewInfo_GL_EXT_texture_compression_rgtc(); +#endif /* GL_EXT_texture_compression_rgtc */ +#ifdef GL_EXT_texture_compression_s3tc + _glewInfo_GL_EXT_texture_compression_s3tc(); +#endif /* GL_EXT_texture_compression_s3tc */ +#ifdef GL_EXT_texture_cube_map + _glewInfo_GL_EXT_texture_cube_map(); +#endif /* GL_EXT_texture_cube_map */ +#ifdef GL_EXT_texture_edge_clamp + _glewInfo_GL_EXT_texture_edge_clamp(); +#endif /* GL_EXT_texture_edge_clamp */ +#ifdef GL_EXT_texture_env + _glewInfo_GL_EXT_texture_env(); +#endif /* GL_EXT_texture_env */ +#ifdef GL_EXT_texture_env_add + _glewInfo_GL_EXT_texture_env_add(); +#endif /* GL_EXT_texture_env_add */ +#ifdef GL_EXT_texture_env_combine + _glewInfo_GL_EXT_texture_env_combine(); +#endif /* GL_EXT_texture_env_combine */ +#ifdef GL_EXT_texture_env_dot3 + _glewInfo_GL_EXT_texture_env_dot3(); +#endif /* GL_EXT_texture_env_dot3 */ +#ifdef GL_EXT_texture_filter_anisotropic + _glewInfo_GL_EXT_texture_filter_anisotropic(); +#endif /* GL_EXT_texture_filter_anisotropic */ +#ifdef GL_EXT_texture_integer + _glewInfo_GL_EXT_texture_integer(); +#endif /* GL_EXT_texture_integer */ +#ifdef GL_EXT_texture_lod_bias + _glewInfo_GL_EXT_texture_lod_bias(); +#endif /* GL_EXT_texture_lod_bias */ +#ifdef GL_EXT_texture_mirror_clamp + _glewInfo_GL_EXT_texture_mirror_clamp(); +#endif /* GL_EXT_texture_mirror_clamp */ +#ifdef GL_EXT_texture_object + _glewInfo_GL_EXT_texture_object(); +#endif /* GL_EXT_texture_object */ +#ifdef GL_EXT_texture_perturb_normal + _glewInfo_GL_EXT_texture_perturb_normal(); +#endif /* GL_EXT_texture_perturb_normal */ +#ifdef GL_EXT_texture_rectangle + _glewInfo_GL_EXT_texture_rectangle(); +#endif /* GL_EXT_texture_rectangle */ +#ifdef GL_EXT_texture_sRGB + _glewInfo_GL_EXT_texture_sRGB(); +#endif /* GL_EXT_texture_sRGB */ +#ifdef GL_EXT_texture_shared_exponent + _glewInfo_GL_EXT_texture_shared_exponent(); +#endif /* GL_EXT_texture_shared_exponent */ +#ifdef GL_EXT_texture_swizzle + _glewInfo_GL_EXT_texture_swizzle(); +#endif /* GL_EXT_texture_swizzle */ +#ifdef GL_EXT_timer_query + _glewInfo_GL_EXT_timer_query(); +#endif /* GL_EXT_timer_query */ +#ifdef GL_EXT_transform_feedback + _glewInfo_GL_EXT_transform_feedback(); +#endif /* GL_EXT_transform_feedback */ +#ifdef GL_EXT_vertex_array + _glewInfo_GL_EXT_vertex_array(); +#endif /* GL_EXT_vertex_array */ +#ifdef GL_EXT_vertex_array_bgra + _glewInfo_GL_EXT_vertex_array_bgra(); +#endif /* GL_EXT_vertex_array_bgra */ +#ifdef GL_EXT_vertex_shader + _glewInfo_GL_EXT_vertex_shader(); +#endif /* GL_EXT_vertex_shader */ +#ifdef GL_EXT_vertex_weighting + _glewInfo_GL_EXT_vertex_weighting(); +#endif /* GL_EXT_vertex_weighting */ +#ifdef GL_GREMEDY_frame_terminator + _glewInfo_GL_GREMEDY_frame_terminator(); +#endif /* GL_GREMEDY_frame_terminator */ +#ifdef GL_GREMEDY_string_marker + _glewInfo_GL_GREMEDY_string_marker(); +#endif /* GL_GREMEDY_string_marker */ +#ifdef GL_HP_convolution_border_modes + _glewInfo_GL_HP_convolution_border_modes(); +#endif /* GL_HP_convolution_border_modes */ +#ifdef GL_HP_image_transform + _glewInfo_GL_HP_image_transform(); +#endif /* GL_HP_image_transform */ +#ifdef GL_HP_occlusion_test + _glewInfo_GL_HP_occlusion_test(); +#endif /* GL_HP_occlusion_test */ +#ifdef GL_HP_texture_lighting + _glewInfo_GL_HP_texture_lighting(); +#endif /* GL_HP_texture_lighting */ +#ifdef GL_IBM_cull_vertex + _glewInfo_GL_IBM_cull_vertex(); +#endif /* GL_IBM_cull_vertex */ +#ifdef GL_IBM_multimode_draw_arrays + _glewInfo_GL_IBM_multimode_draw_arrays(); +#endif /* GL_IBM_multimode_draw_arrays */ +#ifdef GL_IBM_rasterpos_clip + _glewInfo_GL_IBM_rasterpos_clip(); +#endif /* GL_IBM_rasterpos_clip */ +#ifdef GL_IBM_static_data + _glewInfo_GL_IBM_static_data(); +#endif /* GL_IBM_static_data */ +#ifdef GL_IBM_texture_mirrored_repeat + _glewInfo_GL_IBM_texture_mirrored_repeat(); +#endif /* GL_IBM_texture_mirrored_repeat */ +#ifdef GL_IBM_vertex_array_lists + _glewInfo_GL_IBM_vertex_array_lists(); +#endif /* GL_IBM_vertex_array_lists */ +#ifdef GL_INGR_color_clamp + _glewInfo_GL_INGR_color_clamp(); +#endif /* GL_INGR_color_clamp */ +#ifdef GL_INGR_interlace_read + _glewInfo_GL_INGR_interlace_read(); +#endif /* GL_INGR_interlace_read */ +#ifdef GL_INTEL_parallel_arrays + _glewInfo_GL_INTEL_parallel_arrays(); +#endif /* GL_INTEL_parallel_arrays */ +#ifdef GL_INTEL_texture_scissor + _glewInfo_GL_INTEL_texture_scissor(); +#endif /* GL_INTEL_texture_scissor */ +#ifdef GL_KTX_buffer_region + _glewInfo_GL_KTX_buffer_region(); +#endif /* GL_KTX_buffer_region */ +#ifdef GL_MESAX_texture_stack + _glewInfo_GL_MESAX_texture_stack(); +#endif /* GL_MESAX_texture_stack */ +#ifdef GL_MESA_pack_invert + _glewInfo_GL_MESA_pack_invert(); +#endif /* GL_MESA_pack_invert */ +#ifdef GL_MESA_resize_buffers + _glewInfo_GL_MESA_resize_buffers(); +#endif /* GL_MESA_resize_buffers */ +#ifdef GL_MESA_window_pos + _glewInfo_GL_MESA_window_pos(); +#endif /* GL_MESA_window_pos */ +#ifdef GL_MESA_ycbcr_texture + _glewInfo_GL_MESA_ycbcr_texture(); +#endif /* GL_MESA_ycbcr_texture */ +#ifdef GL_NV_blend_square + _glewInfo_GL_NV_blend_square(); +#endif /* GL_NV_blend_square */ +#ifdef GL_NV_conditional_render + _glewInfo_GL_NV_conditional_render(); +#endif /* GL_NV_conditional_render */ +#ifdef GL_NV_copy_depth_to_color + _glewInfo_GL_NV_copy_depth_to_color(); +#endif /* GL_NV_copy_depth_to_color */ +#ifdef GL_NV_depth_buffer_float + _glewInfo_GL_NV_depth_buffer_float(); +#endif /* GL_NV_depth_buffer_float */ +#ifdef GL_NV_depth_clamp + _glewInfo_GL_NV_depth_clamp(); +#endif /* GL_NV_depth_clamp */ +#ifdef GL_NV_depth_range_unclamped + _glewInfo_GL_NV_depth_range_unclamped(); +#endif /* GL_NV_depth_range_unclamped */ +#ifdef GL_NV_evaluators + _glewInfo_GL_NV_evaluators(); +#endif /* GL_NV_evaluators */ +#ifdef GL_NV_explicit_multisample + _glewInfo_GL_NV_explicit_multisample(); +#endif /* GL_NV_explicit_multisample */ +#ifdef GL_NV_fence + _glewInfo_GL_NV_fence(); +#endif /* GL_NV_fence */ +#ifdef GL_NV_float_buffer + _glewInfo_GL_NV_float_buffer(); +#endif /* GL_NV_float_buffer */ +#ifdef GL_NV_fog_distance + _glewInfo_GL_NV_fog_distance(); +#endif /* GL_NV_fog_distance */ +#ifdef GL_NV_fragment_program + _glewInfo_GL_NV_fragment_program(); +#endif /* GL_NV_fragment_program */ +#ifdef GL_NV_fragment_program2 + _glewInfo_GL_NV_fragment_program2(); +#endif /* GL_NV_fragment_program2 */ +#ifdef GL_NV_fragment_program4 + _glewInfo_GL_NV_fragment_program4(); +#endif /* GL_NV_fragment_program4 */ +#ifdef GL_NV_fragment_program_option + _glewInfo_GL_NV_fragment_program_option(); +#endif /* GL_NV_fragment_program_option */ +#ifdef GL_NV_framebuffer_multisample_coverage + _glewInfo_GL_NV_framebuffer_multisample_coverage(); +#endif /* GL_NV_framebuffer_multisample_coverage */ +#ifdef GL_NV_geometry_program4 + _glewInfo_GL_NV_geometry_program4(); +#endif /* GL_NV_geometry_program4 */ +#ifdef GL_NV_geometry_shader4 + _glewInfo_GL_NV_geometry_shader4(); +#endif /* GL_NV_geometry_shader4 */ +#ifdef GL_NV_gpu_program4 + _glewInfo_GL_NV_gpu_program4(); +#endif /* GL_NV_gpu_program4 */ +#ifdef GL_NV_half_float + _glewInfo_GL_NV_half_float(); +#endif /* GL_NV_half_float */ +#ifdef GL_NV_light_max_exponent + _glewInfo_GL_NV_light_max_exponent(); +#endif /* GL_NV_light_max_exponent */ +#ifdef GL_NV_multisample_filter_hint + _glewInfo_GL_NV_multisample_filter_hint(); +#endif /* GL_NV_multisample_filter_hint */ +#ifdef GL_NV_occlusion_query + _glewInfo_GL_NV_occlusion_query(); +#endif /* GL_NV_occlusion_query */ +#ifdef GL_NV_packed_depth_stencil + _glewInfo_GL_NV_packed_depth_stencil(); +#endif /* GL_NV_packed_depth_stencil */ +#ifdef GL_NV_parameter_buffer_object + _glewInfo_GL_NV_parameter_buffer_object(); +#endif /* GL_NV_parameter_buffer_object */ +#ifdef GL_NV_pixel_data_range + _glewInfo_GL_NV_pixel_data_range(); +#endif /* GL_NV_pixel_data_range */ +#ifdef GL_NV_point_sprite + _glewInfo_GL_NV_point_sprite(); +#endif /* GL_NV_point_sprite */ +#ifdef GL_NV_present_video + _glewInfo_GL_NV_present_video(); +#endif /* GL_NV_present_video */ +#ifdef GL_NV_primitive_restart + _glewInfo_GL_NV_primitive_restart(); +#endif /* GL_NV_primitive_restart */ +#ifdef GL_NV_register_combiners + _glewInfo_GL_NV_register_combiners(); +#endif /* GL_NV_register_combiners */ +#ifdef GL_NV_register_combiners2 + _glewInfo_GL_NV_register_combiners2(); +#endif /* GL_NV_register_combiners2 */ +#ifdef GL_NV_texgen_emboss + _glewInfo_GL_NV_texgen_emboss(); +#endif /* GL_NV_texgen_emboss */ +#ifdef GL_NV_texgen_reflection + _glewInfo_GL_NV_texgen_reflection(); +#endif /* GL_NV_texgen_reflection */ +#ifdef GL_NV_texture_compression_vtc + _glewInfo_GL_NV_texture_compression_vtc(); +#endif /* GL_NV_texture_compression_vtc */ +#ifdef GL_NV_texture_env_combine4 + _glewInfo_GL_NV_texture_env_combine4(); +#endif /* GL_NV_texture_env_combine4 */ +#ifdef GL_NV_texture_expand_normal + _glewInfo_GL_NV_texture_expand_normal(); +#endif /* GL_NV_texture_expand_normal */ +#ifdef GL_NV_texture_rectangle + _glewInfo_GL_NV_texture_rectangle(); +#endif /* GL_NV_texture_rectangle */ +#ifdef GL_NV_texture_shader + _glewInfo_GL_NV_texture_shader(); +#endif /* GL_NV_texture_shader */ +#ifdef GL_NV_texture_shader2 + _glewInfo_GL_NV_texture_shader2(); +#endif /* GL_NV_texture_shader2 */ +#ifdef GL_NV_texture_shader3 + _glewInfo_GL_NV_texture_shader3(); +#endif /* GL_NV_texture_shader3 */ +#ifdef GL_NV_transform_feedback + _glewInfo_GL_NV_transform_feedback(); +#endif /* GL_NV_transform_feedback */ +#ifdef GL_NV_vertex_array_range + _glewInfo_GL_NV_vertex_array_range(); +#endif /* GL_NV_vertex_array_range */ +#ifdef GL_NV_vertex_array_range2 + _glewInfo_GL_NV_vertex_array_range2(); +#endif /* GL_NV_vertex_array_range2 */ +#ifdef GL_NV_vertex_program + _glewInfo_GL_NV_vertex_program(); +#endif /* GL_NV_vertex_program */ +#ifdef GL_NV_vertex_program1_1 + _glewInfo_GL_NV_vertex_program1_1(); +#endif /* GL_NV_vertex_program1_1 */ +#ifdef GL_NV_vertex_program2 + _glewInfo_GL_NV_vertex_program2(); +#endif /* GL_NV_vertex_program2 */ +#ifdef GL_NV_vertex_program2_option + _glewInfo_GL_NV_vertex_program2_option(); +#endif /* GL_NV_vertex_program2_option */ +#ifdef GL_NV_vertex_program3 + _glewInfo_GL_NV_vertex_program3(); +#endif /* GL_NV_vertex_program3 */ +#ifdef GL_NV_vertex_program4 + _glewInfo_GL_NV_vertex_program4(); +#endif /* GL_NV_vertex_program4 */ +#ifdef GL_OES_byte_coordinates + _glewInfo_GL_OES_byte_coordinates(); +#endif /* GL_OES_byte_coordinates */ +#ifdef GL_OES_compressed_paletted_texture + _glewInfo_GL_OES_compressed_paletted_texture(); +#endif /* GL_OES_compressed_paletted_texture */ +#ifdef GL_OES_read_format + _glewInfo_GL_OES_read_format(); +#endif /* GL_OES_read_format */ +#ifdef GL_OES_single_precision + _glewInfo_GL_OES_single_precision(); +#endif /* GL_OES_single_precision */ +#ifdef GL_OML_interlace + _glewInfo_GL_OML_interlace(); +#endif /* GL_OML_interlace */ +#ifdef GL_OML_resample + _glewInfo_GL_OML_resample(); +#endif /* GL_OML_resample */ +#ifdef GL_OML_subsample + _glewInfo_GL_OML_subsample(); +#endif /* GL_OML_subsample */ +#ifdef GL_PGI_misc_hints + _glewInfo_GL_PGI_misc_hints(); +#endif /* GL_PGI_misc_hints */ +#ifdef GL_PGI_vertex_hints + _glewInfo_GL_PGI_vertex_hints(); +#endif /* GL_PGI_vertex_hints */ +#ifdef GL_REND_screen_coordinates + _glewInfo_GL_REND_screen_coordinates(); +#endif /* GL_REND_screen_coordinates */ +#ifdef GL_S3_s3tc + _glewInfo_GL_S3_s3tc(); +#endif /* GL_S3_s3tc */ +#ifdef GL_SGIS_color_range + _glewInfo_GL_SGIS_color_range(); +#endif /* GL_SGIS_color_range */ +#ifdef GL_SGIS_detail_texture + _glewInfo_GL_SGIS_detail_texture(); +#endif /* GL_SGIS_detail_texture */ +#ifdef GL_SGIS_fog_function + _glewInfo_GL_SGIS_fog_function(); +#endif /* GL_SGIS_fog_function */ +#ifdef GL_SGIS_generate_mipmap + _glewInfo_GL_SGIS_generate_mipmap(); +#endif /* GL_SGIS_generate_mipmap */ +#ifdef GL_SGIS_multisample + _glewInfo_GL_SGIS_multisample(); +#endif /* GL_SGIS_multisample */ +#ifdef GL_SGIS_pixel_texture + _glewInfo_GL_SGIS_pixel_texture(); +#endif /* GL_SGIS_pixel_texture */ +#ifdef GL_SGIS_point_line_texgen + _glewInfo_GL_SGIS_point_line_texgen(); +#endif /* GL_SGIS_point_line_texgen */ +#ifdef GL_SGIS_sharpen_texture + _glewInfo_GL_SGIS_sharpen_texture(); +#endif /* GL_SGIS_sharpen_texture */ +#ifdef GL_SGIS_texture4D + _glewInfo_GL_SGIS_texture4D(); +#endif /* GL_SGIS_texture4D */ +#ifdef GL_SGIS_texture_border_clamp + _glewInfo_GL_SGIS_texture_border_clamp(); +#endif /* GL_SGIS_texture_border_clamp */ +#ifdef GL_SGIS_texture_edge_clamp + _glewInfo_GL_SGIS_texture_edge_clamp(); +#endif /* GL_SGIS_texture_edge_clamp */ +#ifdef GL_SGIS_texture_filter4 + _glewInfo_GL_SGIS_texture_filter4(); +#endif /* GL_SGIS_texture_filter4 */ +#ifdef GL_SGIS_texture_lod + _glewInfo_GL_SGIS_texture_lod(); +#endif /* GL_SGIS_texture_lod */ +#ifdef GL_SGIS_texture_select + _glewInfo_GL_SGIS_texture_select(); +#endif /* GL_SGIS_texture_select */ +#ifdef GL_SGIX_async + _glewInfo_GL_SGIX_async(); +#endif /* GL_SGIX_async */ +#ifdef GL_SGIX_async_histogram + _glewInfo_GL_SGIX_async_histogram(); +#endif /* GL_SGIX_async_histogram */ +#ifdef GL_SGIX_async_pixel + _glewInfo_GL_SGIX_async_pixel(); +#endif /* GL_SGIX_async_pixel */ +#ifdef GL_SGIX_blend_alpha_minmax + _glewInfo_GL_SGIX_blend_alpha_minmax(); +#endif /* GL_SGIX_blend_alpha_minmax */ +#ifdef GL_SGIX_clipmap + _glewInfo_GL_SGIX_clipmap(); +#endif /* GL_SGIX_clipmap */ +#ifdef GL_SGIX_convolution_accuracy + _glewInfo_GL_SGIX_convolution_accuracy(); +#endif /* GL_SGIX_convolution_accuracy */ +#ifdef GL_SGIX_depth_texture + _glewInfo_GL_SGIX_depth_texture(); +#endif /* GL_SGIX_depth_texture */ +#ifdef GL_SGIX_flush_raster + _glewInfo_GL_SGIX_flush_raster(); +#endif /* GL_SGIX_flush_raster */ +#ifdef GL_SGIX_fog_offset + _glewInfo_GL_SGIX_fog_offset(); +#endif /* GL_SGIX_fog_offset */ +#ifdef GL_SGIX_fog_texture + _glewInfo_GL_SGIX_fog_texture(); +#endif /* GL_SGIX_fog_texture */ +#ifdef GL_SGIX_fragment_specular_lighting + _glewInfo_GL_SGIX_fragment_specular_lighting(); +#endif /* GL_SGIX_fragment_specular_lighting */ +#ifdef GL_SGIX_framezoom + _glewInfo_GL_SGIX_framezoom(); +#endif /* GL_SGIX_framezoom */ +#ifdef GL_SGIX_interlace + _glewInfo_GL_SGIX_interlace(); +#endif /* GL_SGIX_interlace */ +#ifdef GL_SGIX_ir_instrument1 + _glewInfo_GL_SGIX_ir_instrument1(); +#endif /* GL_SGIX_ir_instrument1 */ +#ifdef GL_SGIX_list_priority + _glewInfo_GL_SGIX_list_priority(); +#endif /* GL_SGIX_list_priority */ +#ifdef GL_SGIX_pixel_texture + _glewInfo_GL_SGIX_pixel_texture(); +#endif /* GL_SGIX_pixel_texture */ +#ifdef GL_SGIX_pixel_texture_bits + _glewInfo_GL_SGIX_pixel_texture_bits(); +#endif /* GL_SGIX_pixel_texture_bits */ +#ifdef GL_SGIX_reference_plane + _glewInfo_GL_SGIX_reference_plane(); +#endif /* GL_SGIX_reference_plane */ +#ifdef GL_SGIX_resample + _glewInfo_GL_SGIX_resample(); +#endif /* GL_SGIX_resample */ +#ifdef GL_SGIX_shadow + _glewInfo_GL_SGIX_shadow(); +#endif /* GL_SGIX_shadow */ +#ifdef GL_SGIX_shadow_ambient + _glewInfo_GL_SGIX_shadow_ambient(); +#endif /* GL_SGIX_shadow_ambient */ +#ifdef GL_SGIX_sprite + _glewInfo_GL_SGIX_sprite(); +#endif /* GL_SGIX_sprite */ +#ifdef GL_SGIX_tag_sample_buffer + _glewInfo_GL_SGIX_tag_sample_buffer(); +#endif /* GL_SGIX_tag_sample_buffer */ +#ifdef GL_SGIX_texture_add_env + _glewInfo_GL_SGIX_texture_add_env(); +#endif /* GL_SGIX_texture_add_env */ +#ifdef GL_SGIX_texture_coordinate_clamp + _glewInfo_GL_SGIX_texture_coordinate_clamp(); +#endif /* GL_SGIX_texture_coordinate_clamp */ +#ifdef GL_SGIX_texture_lod_bias + _glewInfo_GL_SGIX_texture_lod_bias(); +#endif /* GL_SGIX_texture_lod_bias */ +#ifdef GL_SGIX_texture_multi_buffer + _glewInfo_GL_SGIX_texture_multi_buffer(); +#endif /* GL_SGIX_texture_multi_buffer */ +#ifdef GL_SGIX_texture_range + _glewInfo_GL_SGIX_texture_range(); +#endif /* GL_SGIX_texture_range */ +#ifdef GL_SGIX_texture_scale_bias + _glewInfo_GL_SGIX_texture_scale_bias(); +#endif /* GL_SGIX_texture_scale_bias */ +#ifdef GL_SGIX_vertex_preclip + _glewInfo_GL_SGIX_vertex_preclip(); +#endif /* GL_SGIX_vertex_preclip */ +#ifdef GL_SGIX_vertex_preclip_hint + _glewInfo_GL_SGIX_vertex_preclip_hint(); +#endif /* GL_SGIX_vertex_preclip_hint */ +#ifdef GL_SGIX_ycrcb + _glewInfo_GL_SGIX_ycrcb(); +#endif /* GL_SGIX_ycrcb */ +#ifdef GL_SGI_color_matrix + _glewInfo_GL_SGI_color_matrix(); +#endif /* GL_SGI_color_matrix */ +#ifdef GL_SGI_color_table + _glewInfo_GL_SGI_color_table(); +#endif /* GL_SGI_color_table */ +#ifdef GL_SGI_texture_color_table + _glewInfo_GL_SGI_texture_color_table(); +#endif /* GL_SGI_texture_color_table */ +#ifdef GL_SUNX_constant_data + _glewInfo_GL_SUNX_constant_data(); +#endif /* GL_SUNX_constant_data */ +#ifdef GL_SUN_convolution_border_modes + _glewInfo_GL_SUN_convolution_border_modes(); +#endif /* GL_SUN_convolution_border_modes */ +#ifdef GL_SUN_global_alpha + _glewInfo_GL_SUN_global_alpha(); +#endif /* GL_SUN_global_alpha */ +#ifdef GL_SUN_mesh_array + _glewInfo_GL_SUN_mesh_array(); +#endif /* GL_SUN_mesh_array */ +#ifdef GL_SUN_read_video_pixels + _glewInfo_GL_SUN_read_video_pixels(); +#endif /* GL_SUN_read_video_pixels */ +#ifdef GL_SUN_slice_accum + _glewInfo_GL_SUN_slice_accum(); +#endif /* GL_SUN_slice_accum */ +#ifdef GL_SUN_triangle_list + _glewInfo_GL_SUN_triangle_list(); +#endif /* GL_SUN_triangle_list */ +#ifdef GL_SUN_vertex + _glewInfo_GL_SUN_vertex(); +#endif /* GL_SUN_vertex */ +#ifdef GL_WIN_phong_shading + _glewInfo_GL_WIN_phong_shading(); +#endif /* GL_WIN_phong_shading */ +#ifdef GL_WIN_specular_fog + _glewInfo_GL_WIN_specular_fog(); +#endif /* GL_WIN_specular_fog */ +#ifdef GL_WIN_swap_hint + _glewInfo_GL_WIN_swap_hint(); +#endif /* GL_WIN_swap_hint */ +} + +/* ------------------------------------------------------------------------ */ + +#ifdef _WIN32 + +static void wglewInfo () +{ +#ifdef WGL_3DFX_multisample + _glewInfo_WGL_3DFX_multisample(); +#endif /* WGL_3DFX_multisample */ +#ifdef WGL_3DL_stereo_control + _glewInfo_WGL_3DL_stereo_control(); +#endif /* WGL_3DL_stereo_control */ +#ifdef WGL_ARB_buffer_region + _glewInfo_WGL_ARB_buffer_region(); +#endif /* WGL_ARB_buffer_region */ +#ifdef WGL_ARB_create_context + _glewInfo_WGL_ARB_create_context(); +#endif /* WGL_ARB_create_context */ +#ifdef WGL_ARB_extensions_string + _glewInfo_WGL_ARB_extensions_string(); +#endif /* WGL_ARB_extensions_string */ +#ifdef WGL_ARB_framebuffer_sRGB + _glewInfo_WGL_ARB_framebuffer_sRGB(); +#endif /* WGL_ARB_framebuffer_sRGB */ +#ifdef WGL_ARB_make_current_read + _glewInfo_WGL_ARB_make_current_read(); +#endif /* WGL_ARB_make_current_read */ +#ifdef WGL_ARB_multisample + _glewInfo_WGL_ARB_multisample(); +#endif /* WGL_ARB_multisample */ +#ifdef WGL_ARB_pbuffer + _glewInfo_WGL_ARB_pbuffer(); +#endif /* WGL_ARB_pbuffer */ +#ifdef WGL_ARB_pixel_format + _glewInfo_WGL_ARB_pixel_format(); +#endif /* WGL_ARB_pixel_format */ +#ifdef WGL_ARB_pixel_format_float + _glewInfo_WGL_ARB_pixel_format_float(); +#endif /* WGL_ARB_pixel_format_float */ +#ifdef WGL_ARB_render_texture + _glewInfo_WGL_ARB_render_texture(); +#endif /* WGL_ARB_render_texture */ +#ifdef WGL_ATI_pixel_format_float + _glewInfo_WGL_ATI_pixel_format_float(); +#endif /* WGL_ATI_pixel_format_float */ +#ifdef WGL_ATI_render_texture_rectangle + _glewInfo_WGL_ATI_render_texture_rectangle(); +#endif /* WGL_ATI_render_texture_rectangle */ +#ifdef WGL_EXT_depth_float + _glewInfo_WGL_EXT_depth_float(); +#endif /* WGL_EXT_depth_float */ +#ifdef WGL_EXT_display_color_table + _glewInfo_WGL_EXT_display_color_table(); +#endif /* WGL_EXT_display_color_table */ +#ifdef WGL_EXT_extensions_string + _glewInfo_WGL_EXT_extensions_string(); +#endif /* WGL_EXT_extensions_string */ +#ifdef WGL_EXT_framebuffer_sRGB + _glewInfo_WGL_EXT_framebuffer_sRGB(); +#endif /* WGL_EXT_framebuffer_sRGB */ +#ifdef WGL_EXT_make_current_read + _glewInfo_WGL_EXT_make_current_read(); +#endif /* WGL_EXT_make_current_read */ +#ifdef WGL_EXT_multisample + _glewInfo_WGL_EXT_multisample(); +#endif /* WGL_EXT_multisample */ +#ifdef WGL_EXT_pbuffer + _glewInfo_WGL_EXT_pbuffer(); +#endif /* WGL_EXT_pbuffer */ +#ifdef WGL_EXT_pixel_format + _glewInfo_WGL_EXT_pixel_format(); +#endif /* WGL_EXT_pixel_format */ +#ifdef WGL_EXT_pixel_format_packed_float + _glewInfo_WGL_EXT_pixel_format_packed_float(); +#endif /* WGL_EXT_pixel_format_packed_float */ +#ifdef WGL_EXT_swap_control + _glewInfo_WGL_EXT_swap_control(); +#endif /* WGL_EXT_swap_control */ +#ifdef WGL_I3D_digital_video_control + _glewInfo_WGL_I3D_digital_video_control(); +#endif /* WGL_I3D_digital_video_control */ +#ifdef WGL_I3D_gamma + _glewInfo_WGL_I3D_gamma(); +#endif /* WGL_I3D_gamma */ +#ifdef WGL_I3D_genlock + _glewInfo_WGL_I3D_genlock(); +#endif /* WGL_I3D_genlock */ +#ifdef WGL_I3D_image_buffer + _glewInfo_WGL_I3D_image_buffer(); +#endif /* WGL_I3D_image_buffer */ +#ifdef WGL_I3D_swap_frame_lock + _glewInfo_WGL_I3D_swap_frame_lock(); +#endif /* WGL_I3D_swap_frame_lock */ +#ifdef WGL_I3D_swap_frame_usage + _glewInfo_WGL_I3D_swap_frame_usage(); +#endif /* WGL_I3D_swap_frame_usage */ +#ifdef WGL_NV_float_buffer + _glewInfo_WGL_NV_float_buffer(); +#endif /* WGL_NV_float_buffer */ +#ifdef WGL_NV_gpu_affinity + _glewInfo_WGL_NV_gpu_affinity(); +#endif /* WGL_NV_gpu_affinity */ +#ifdef WGL_NV_present_video + _glewInfo_WGL_NV_present_video(); +#endif /* WGL_NV_present_video */ +#ifdef WGL_NV_render_depth_texture + _glewInfo_WGL_NV_render_depth_texture(); +#endif /* WGL_NV_render_depth_texture */ +#ifdef WGL_NV_render_texture_rectangle + _glewInfo_WGL_NV_render_texture_rectangle(); +#endif /* WGL_NV_render_texture_rectangle */ +#ifdef WGL_NV_swap_group + _glewInfo_WGL_NV_swap_group(); +#endif /* WGL_NV_swap_group */ +#ifdef WGL_NV_vertex_array_range + _glewInfo_WGL_NV_vertex_array_range(); +#endif /* WGL_NV_vertex_array_range */ +#ifdef WGL_NV_video_output + _glewInfo_WGL_NV_video_output(); +#endif /* WGL_NV_video_output */ +#ifdef WGL_OML_sync_control + _glewInfo_WGL_OML_sync_control(); +#endif /* WGL_OML_sync_control */ +} + +#else /* _UNIX */ + +static void glxewInfo () +{ +#ifdef GLX_VERSION_1_2 + _glewInfo_GLX_VERSION_1_2(); +#endif /* GLX_VERSION_1_2 */ +#ifdef GLX_VERSION_1_3 + _glewInfo_GLX_VERSION_1_3(); +#endif /* GLX_VERSION_1_3 */ +#ifdef GLX_VERSION_1_4 + _glewInfo_GLX_VERSION_1_4(); +#endif /* GLX_VERSION_1_4 */ +#ifdef GLX_3DFX_multisample + _glewInfo_GLX_3DFX_multisample(); +#endif /* GLX_3DFX_multisample */ +#ifdef GLX_ARB_create_context + _glewInfo_GLX_ARB_create_context(); +#endif /* GLX_ARB_create_context */ +#ifdef GLX_ARB_fbconfig_float + _glewInfo_GLX_ARB_fbconfig_float(); +#endif /* GLX_ARB_fbconfig_float */ +#ifdef GLX_ARB_framebuffer_sRGB + _glewInfo_GLX_ARB_framebuffer_sRGB(); +#endif /* GLX_ARB_framebuffer_sRGB */ +#ifdef GLX_ARB_get_proc_address + _glewInfo_GLX_ARB_get_proc_address(); +#endif /* GLX_ARB_get_proc_address */ +#ifdef GLX_ARB_multisample + _glewInfo_GLX_ARB_multisample(); +#endif /* GLX_ARB_multisample */ +#ifdef GLX_ATI_pixel_format_float + _glewInfo_GLX_ATI_pixel_format_float(); +#endif /* GLX_ATI_pixel_format_float */ +#ifdef GLX_ATI_render_texture + _glewInfo_GLX_ATI_render_texture(); +#endif /* GLX_ATI_render_texture */ +#ifdef GLX_EXT_fbconfig_packed_float + _glewInfo_GLX_EXT_fbconfig_packed_float(); +#endif /* GLX_EXT_fbconfig_packed_float */ +#ifdef GLX_EXT_framebuffer_sRGB + _glewInfo_GLX_EXT_framebuffer_sRGB(); +#endif /* GLX_EXT_framebuffer_sRGB */ +#ifdef GLX_EXT_import_context + _glewInfo_GLX_EXT_import_context(); +#endif /* GLX_EXT_import_context */ +#ifdef GLX_EXT_scene_marker + _glewInfo_GLX_EXT_scene_marker(); +#endif /* GLX_EXT_scene_marker */ +#ifdef GLX_EXT_texture_from_pixmap + _glewInfo_GLX_EXT_texture_from_pixmap(); +#endif /* GLX_EXT_texture_from_pixmap */ +#ifdef GLX_EXT_visual_info + _glewInfo_GLX_EXT_visual_info(); +#endif /* GLX_EXT_visual_info */ +#ifdef GLX_EXT_visual_rating + _glewInfo_GLX_EXT_visual_rating(); +#endif /* GLX_EXT_visual_rating */ +#ifdef GLX_MESA_agp_offset + _glewInfo_GLX_MESA_agp_offset(); +#endif /* GLX_MESA_agp_offset */ +#ifdef GLX_MESA_copy_sub_buffer + _glewInfo_GLX_MESA_copy_sub_buffer(); +#endif /* GLX_MESA_copy_sub_buffer */ +#ifdef GLX_MESA_pixmap_colormap + _glewInfo_GLX_MESA_pixmap_colormap(); +#endif /* GLX_MESA_pixmap_colormap */ +#ifdef GLX_MESA_release_buffers + _glewInfo_GLX_MESA_release_buffers(); +#endif /* GLX_MESA_release_buffers */ +#ifdef GLX_MESA_set_3dfx_mode + _glewInfo_GLX_MESA_set_3dfx_mode(); +#endif /* GLX_MESA_set_3dfx_mode */ +#ifdef GLX_NV_float_buffer + _glewInfo_GLX_NV_float_buffer(); +#endif /* GLX_NV_float_buffer */ +#ifdef GLX_NV_present_video + _glewInfo_GLX_NV_present_video(); +#endif /* GLX_NV_present_video */ +#ifdef GLX_NV_swap_group + _glewInfo_GLX_NV_swap_group(); +#endif /* GLX_NV_swap_group */ +#ifdef GLX_NV_vertex_array_range + _glewInfo_GLX_NV_vertex_array_range(); +#endif /* GLX_NV_vertex_array_range */ +#ifdef GLX_NV_video_output + _glewInfo_GLX_NV_video_output(); +#endif /* GLX_NV_video_output */ +#ifdef GLX_OML_swap_method + _glewInfo_GLX_OML_swap_method(); +#endif /* GLX_OML_swap_method */ +#if defined(GLX_OML_sync_control) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#include + _glewInfo_GLX_OML_sync_control(); +#endif /* GLX_OML_sync_control */ +#ifdef GLX_SGIS_blended_overlay + _glewInfo_GLX_SGIS_blended_overlay(); +#endif /* GLX_SGIS_blended_overlay */ +#ifdef GLX_SGIS_color_range + _glewInfo_GLX_SGIS_color_range(); +#endif /* GLX_SGIS_color_range */ +#ifdef GLX_SGIS_multisample + _glewInfo_GLX_SGIS_multisample(); +#endif /* GLX_SGIS_multisample */ +#ifdef GLX_SGIS_shared_multisample + _glewInfo_GLX_SGIS_shared_multisample(); +#endif /* GLX_SGIS_shared_multisample */ +#ifdef GLX_SGIX_fbconfig + _glewInfo_GLX_SGIX_fbconfig(); +#endif /* GLX_SGIX_fbconfig */ +#ifdef GLX_SGIX_hyperpipe + _glewInfo_GLX_SGIX_hyperpipe(); +#endif /* GLX_SGIX_hyperpipe */ +#ifdef GLX_SGIX_pbuffer + _glewInfo_GLX_SGIX_pbuffer(); +#endif /* GLX_SGIX_pbuffer */ +#ifdef GLX_SGIX_swap_barrier + _glewInfo_GLX_SGIX_swap_barrier(); +#endif /* GLX_SGIX_swap_barrier */ +#ifdef GLX_SGIX_swap_group + _glewInfo_GLX_SGIX_swap_group(); +#endif /* GLX_SGIX_swap_group */ +#ifdef GLX_SGIX_video_resize + _glewInfo_GLX_SGIX_video_resize(); +#endif /* GLX_SGIX_video_resize */ +#ifdef GLX_SGIX_visual_select_group + _glewInfo_GLX_SGIX_visual_select_group(); +#endif /* GLX_SGIX_visual_select_group */ +#ifdef GLX_SGI_cushion + _glewInfo_GLX_SGI_cushion(); +#endif /* GLX_SGI_cushion */ +#ifdef GLX_SGI_make_current_read + _glewInfo_GLX_SGI_make_current_read(); +#endif /* GLX_SGI_make_current_read */ +#ifdef GLX_SGI_swap_control + _glewInfo_GLX_SGI_swap_control(); +#endif /* GLX_SGI_swap_control */ +#ifdef GLX_SGI_video_sync + _glewInfo_GLX_SGI_video_sync(); +#endif /* GLX_SGI_video_sync */ +#ifdef GLX_SUN_get_transparent_index + _glewInfo_GLX_SUN_get_transparent_index(); +#endif /* GLX_SUN_get_transparent_index */ +#ifdef GLX_SUN_video_resize + _glewInfo_GLX_SUN_video_resize(); +#endif /* GLX_SUN_video_resize */ +} + +#endif /* _WIN32 */ + +/* ------------------------------------------------------------------------ */ + +#if defined(_WIN32) || !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +int main (int argc, char** argv) +#else +int main (void) +#endif +{ + GLuint err; + +#if defined(_WIN32) || !defined(__APPLE__) || defined(GLEW_APPLE_GLX) + char* display = NULL; + int visual = -1; + + if (glewParseArgs(argc-1, argv+1, &display, &visual)) + { +#if defined(_WIN32) + fprintf(stderr, "Usage: glewinfo [-pf ]\n"); +#else + fprintf(stderr, "Usage: glewinfo [-display ] [-visual ]\n"); +#endif + return 1; + } +#endif + +#if defined(_WIN32) + if (GL_TRUE == glewCreateContext(&visual)) +#elif defined(__APPLE__) && !defined(GLEW_APPLE_GLX) + if (GL_TRUE == glewCreateContext()) +#else + if (GL_TRUE == glewCreateContext(display, &visual)) +#endif + { + fprintf(stderr, "Error: glewCreateContext failed\n"); + glewDestroyContext(); + return 1; + } + glewExperimental = GL_TRUE; +#ifdef GLEW_MX + err = glewContextInit(glewGetContext()); +#ifdef _WIN32 + err = err || wglewContextInit(wglewGetContext()); +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) + err = err || glxewContextInit(glxewGetContext()); +#endif + +#else + err = glewInit(); +#endif + if (GLEW_OK != err) + { + fprintf(stderr, "Error [main]: glewInit failed: %s\n", glewGetErrorString(err)); + glewDestroyContext(); + return 1; + } +#if defined(_WIN32) + f = fopen("glewinfo.txt", "w"); + if (f == NULL) f = stdout; +#else + f = stdout; +#endif + fprintf(f, "---------------------------\n"); + fprintf(f, " GLEW Extension Info\n"); + fprintf(f, "---------------------------\n\n"); + fprintf(f, "GLEW version %s\n", glewGetString(GLEW_VERSION)); +#if defined(_WIN32) + fprintf(f, "Reporting capabilities of pixelformat %d\n", visual); +#elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) + fprintf(f, "Reporting capabilities of display %s, visual 0x%x\n", + display == NULL ? getenv("DISPLAY") : display, visual); +#endif + fprintf(f, "Running on a %s from %s\n", + glGetString(GL_RENDERER), glGetString(GL_VENDOR)); + fprintf(f, "OpenGL version %s is supported\n", glGetString(GL_VERSION)); + glewInfo(); +#if defined(_WIN32) + wglewInfo(); +#else + glxewInfo(); +#endif + if (f != stdout) fclose(f); + glewDestroyContext(); + return 0; +} + +/* ------------------------------------------------------------------------ */ + +#if defined(_WIN32) || !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +GLboolean glewParseArgs (int argc, char** argv, char** display, int* visual) +{ + int p = 0; + while (p < argc) + { +#if defined(_WIN32) + if (!strcmp(argv[p], "-pf") || !strcmp(argv[p], "-pixelformat")) + { + if (++p >= argc) return GL_TRUE; + *display = 0; + *visual = strtol(argv[p++], NULL, 0); + } + else + return GL_TRUE; +#else + if (!strcmp(argv[p], "-display")) + { + if (++p >= argc) return GL_TRUE; + *display = argv[p++]; + } + else if (!strcmp(argv[p], "-visual")) + { + if (++p >= argc) return GL_TRUE; + *visual = (int)strtol(argv[p++], NULL, 0); + } + else + return GL_TRUE; +#endif + } + return GL_FALSE; +} +#endif + +/* ------------------------------------------------------------------------ */ + +#if defined(_WIN32) + +HWND wnd = NULL; +HDC dc = NULL; +HGLRC rc = NULL; + +GLboolean glewCreateContext (int* pixelformat) +{ + WNDCLASS wc; + PIXELFORMATDESCRIPTOR pfd; + /* register window class */ + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.hInstance = GetModuleHandle(NULL); + wc.lpfnWndProc = DefWindowProc; + wc.lpszClassName = "GLEW"; + if (0 == RegisterClass(&wc)) return GL_TRUE; + /* create window */ + wnd = CreateWindow("GLEW", "GLEW", 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), NULL); + if (NULL == wnd) return GL_TRUE; + /* get the device context */ + dc = GetDC(wnd); + if (NULL == dc) return GL_TRUE; + /* find pixel format */ + ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR)); + if (*pixelformat == -1) /* find default */ + { + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + *pixelformat = ChoosePixelFormat(dc, &pfd); + if (*pixelformat == 0) return GL_TRUE; + } + /* set the pixel format for the dc */ + if (FALSE == SetPixelFormat(dc, *pixelformat, &pfd)) return GL_TRUE; + /* create rendering context */ + rc = wglCreateContext(dc); + if (NULL == rc) return GL_TRUE; + if (FALSE == wglMakeCurrent(dc, rc)) return GL_TRUE; + return GL_FALSE; +} + +void glewDestroyContext () +{ + if (NULL != rc) wglMakeCurrent(NULL, NULL); + if (NULL != rc) wglDeleteContext(wglGetCurrentContext()); + if (NULL != wnd && NULL != dc) ReleaseDC(wnd, dc); + if (NULL != wnd) DestroyWindow(wnd); + UnregisterClass("GLEW", GetModuleHandle(NULL)); +} + +/* ------------------------------------------------------------------------ */ + +#elif defined(__APPLE__) && !defined(GLEW_APPLE_GLX) + +#include + +AGLContext ctx, octx; + +GLboolean glewCreateContext () +{ + int attrib[] = { AGL_RGBA, AGL_NONE }; + AGLPixelFormat pf; + /*int major, minor; + SetPortWindowPort(wnd); + aglGetVersion(&major, &minor); + fprintf(stderr, "GL %d.%d\n", major, minor);*/ + pf = aglChoosePixelFormat(NULL, 0, attrib); + if (NULL == pf) return GL_TRUE; + ctx = aglCreateContext(pf, NULL); + if (NULL == ctx || AGL_NO_ERROR != aglGetError()) return GL_TRUE; + aglDestroyPixelFormat(pf); + /*aglSetDrawable(ctx, GetWindowPort(wnd));*/ + octx = aglGetCurrentContext(); + if (NULL == aglSetCurrentContext(ctx)) return GL_TRUE; + return GL_FALSE; +} + +void glewDestroyContext () +{ + aglSetCurrentContext(octx); + if (NULL != ctx) aglDestroyContext(ctx); +} + +/* ------------------------------------------------------------------------ */ + +#else /* __UNIX || (__APPLE__ && GLEW_APPLE_GLX) */ + +Display* dpy = NULL; +XVisualInfo* vi = NULL; +XVisualInfo* vis = NULL; +GLXContext ctx = NULL; +Window wnd = 0; +Colormap cmap = 0; + +GLboolean glewCreateContext (const char* display, int* visual) +{ + int attrib[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None }; + int erb, evb; + XSetWindowAttributes swa; + /* open display */ + dpy = XOpenDisplay(display); + if (NULL == dpy) return GL_TRUE; + /* query for glx */ + if (!glXQueryExtension(dpy, &erb, &evb)) return GL_TRUE; + /* choose visual */ + if (*visual == -1) + { + vi = glXChooseVisual(dpy, DefaultScreen(dpy), attrib); + if (NULL == vi) return GL_TRUE; + *visual = (int)XVisualIDFromVisual(vi->visual); + } + else + { + int n_vis, i; + vis = XGetVisualInfo(dpy, 0, NULL, &n_vis); + for (i=0; iscreen), 0, 0, 1, 1, 1, 0, 0);*/ + cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone); + swa.border_pixel = 0; + swa.colormap = cmap; + wnd = XCreateWindow(dpy, RootWindow(dpy, vi->screen), + 0, 0, 1, 1, 0, vi->depth, InputOutput, vi->visual, + CWBorderPixel | CWColormap, &swa); + /* make context current */ + if (!glXMakeCurrent(dpy, wnd, ctx)) return GL_TRUE; + return GL_FALSE; +} + +void glewDestroyContext () +{ + if (NULL != dpy && NULL != ctx) glXDestroyContext(dpy, ctx); + if (NULL != dpy && 0 != wnd) XDestroyWindow(dpy, wnd); + if (NULL != dpy && 0 != cmap) XFreeColormap(dpy, cmap); + if (NULL != vis) + XFree(vis); + else if (NULL != vi) + XFree(vi); + if (NULL != dpy) XCloseDisplay(dpy); +} + +#endif /* __UNIX || (__APPLE__ && GLEW_APPLE_GLX) */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/visualinfo.c b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/visualinfo.c new file mode 100644 index 0000000..384313d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/glew/src/visualinfo.c @@ -0,0 +1,1173 @@ +/* +** visualinfo.c +** +** Copyright (C) Nate Robins, 1997 +** Michael Wimmer, 1999 +** Milan Ikits, 2002-2008 +** +** visualinfo is a small utility that displays all available visuals, +** aka. pixelformats, in an OpenGL system along with renderer version +** information. It shows a table of all the visuals that support OpenGL +** along with their capabilities. The format of the table is similar to +** that of glxinfo on Unix systems: +** +** visual ~= pixel format descriptor +** id = visual id (integer from 1 - max visuals) +** tp = type (wn: window, pb: pbuffer, wp: window & pbuffer, bm: bitmap) +** ac = acceleration (ge: generic, fu: full, no: none) +** fm = format (i: integer, f: float, c: color index) +** db = double buffer (y = yes) +** sw = swap method (x: exchange, c: copy, u: undefined) +** st = stereo (y = yes) +** sz = total # bits +** r = # bits of red +** g = # bits of green +** b = # bits of blue +** a = # bits of alpha +** axbf = # aux buffers +** dpth = # bits of depth +** stcl = # bits of stencil +*/ + +#include +#include +#include +#include +#if defined(_WIN32) +#include +#elif defined(__APPLE__) && !defined(GLEW_APPLE_GLX) +#include +#else +#include +#endif + +#ifdef GLEW_MX +GLEWContext _glewctx; +# define glewGetContext() (&_glewctx) +# ifdef _WIN32 +WGLEWContext _wglewctx; +# define wglewGetContext() (&_wglewctx) +# elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) +GLXEWContext _glxewctx; +# define glxewGetContext() (&_glxewctx) +# endif +#endif /* GLEW_MX */ + +typedef struct GLContextStruct +{ +#ifdef _WIN32 + HWND wnd; + HDC dc; + HGLRC rc; +#elif defined(__APPLE__) && !defined(GLEW_APPLE_GLX) + AGLContext ctx, octx; +#else + Display* dpy; + XVisualInfo* vi; + GLXContext ctx; + Window wnd; + Colormap cmap; +#endif +} GLContext; + +void InitContext (GLContext* ctx); +GLboolean CreateContext (GLContext* ctx); +void DestroyContext (GLContext* ctx); +void VisualInfo (GLContext* ctx); +void PrintExtensions (const char* s); +GLboolean ParseArgs (int argc, char** argv); + +int showall = 0; +int displaystdout = 0; +int verbose = 0; +int drawableonly = 0; + +char* display = NULL; +int visual = -1; + +FILE* file = 0; +GLContext ctx; + +int +main (int argc, char** argv) +{ + GLenum err; + + /* ---------------------------------------------------------------------- */ + /* parse arguments */ + if (GL_TRUE == ParseArgs(argc-1, argv+1)) + { +#if defined(_WIN32) + fprintf(stderr, "Usage: visualinfo [-a] [-s] [-h] [-pf ]\n"); + fprintf(stderr, " -a: show all visuals\n"); + fprintf(stderr, " -s: display to stdout instead of visualinfo.txt\n"); + fprintf(stderr, " -pf : use given pixelformat\n"); + fprintf(stderr, " -h: this screen\n"); +#else + fprintf(stderr, "Usage: visualinfo [-h] [-display ] [-visual ]\n"); + fprintf(stderr, " -h: this screen\n"); + fprintf(stderr, " -display : use given display\n"); + fprintf(stderr, " -visual : use given visual\n"); +#endif + return 1; + } + + /* ---------------------------------------------------------------------- */ + /* create OpenGL rendering context */ + InitContext(&ctx); + if (GL_TRUE == CreateContext(&ctx)) + { + fprintf(stderr, "Error: CreateContext failed\n"); + DestroyContext(&ctx); + return 1; + } + + /* ---------------------------------------------------------------------- */ + /* initialize GLEW */ + glewExperimental = GL_TRUE; +#ifdef GLEW_MX + err = glewContextInit(glewGetContext()); +# ifdef _WIN32 + err = err || wglewContextInit(wglewGetContext()); +# elif !defined(__APPLE__) || defined(GLEW_APPLE_GLX) + err = err || glxewContextInit(glxewGetContext()); +# endif +#else + err = glewInit(); +#endif + if (GLEW_OK != err) + { + fprintf(stderr, "Error [main]: glewInit failed: %s\n", glewGetErrorString(err)); + DestroyContext(&ctx); + return 1; + } + + /* ---------------------------------------------------------------------- */ + /* open file */ +#if defined(_WIN32) + if (!displaystdout) + file = fopen("visualinfo.txt", "w"); + if (file == NULL) + file = stdout; +#else + file = stdout; +#endif + + /* ---------------------------------------------------------------------- */ + /* output header information */ + /* OpenGL extensions */ + fprintf(file, "OpenGL vendor string: %s\n", glGetString(GL_VENDOR)); + fprintf(file, "OpenGL renderer string: %s\n", glGetString(GL_RENDERER)); + fprintf(file, "OpenGL version string: %s\n", glGetString(GL_VERSION)); + fprintf(file, "OpenGL extensions (GL_): \n"); + PrintExtensions((char*)glGetString(GL_EXTENSIONS)); + /* GLU extensions */ + fprintf(file, "GLU version string: %s\n", gluGetString(GLU_VERSION)); + fprintf(file, "GLU extensions (GLU_): \n"); + PrintExtensions((char*)gluGetString(GLU_EXTENSIONS)); + + /* ---------------------------------------------------------------------- */ + /* extensions string */ +#if defined(_WIN32) + /* WGL extensions */ + if (WGLEW_ARB_extensions_string || WGLEW_EXT_extensions_string) + { + fprintf(file, "WGL extensions (WGL_): \n"); + PrintExtensions(wglGetExtensionsStringARB ? + (char*)wglGetExtensionsStringARB(ctx.dc) : + (char*)wglGetExtensionsStringEXT()); + } +#elif defined(__APPLE__) && !defined(GLEW_APPLE_GLX) + +#else + /* GLX extensions */ + fprintf(file, "GLX extensions (GLX_): \n"); + PrintExtensions(glXQueryExtensionsString(glXGetCurrentDisplay(), + DefaultScreen(glXGetCurrentDisplay()))); +#endif + + /* ---------------------------------------------------------------------- */ + /* enumerate all the formats */ + VisualInfo(&ctx); + + /* ---------------------------------------------------------------------- */ + /* release resources */ + DestroyContext(&ctx); + if (file != stdout) + fclose(file); + return 0; +} + +/* do the magic to separate all extensions with comma's, except + for the last one that _may_ terminate in a space. */ +void PrintExtensions (const char* s) +{ + char t[80]; + int i=0; + char* p=0; + + t[79] = '\0'; + while (*s) + { + t[i++] = *s; + if(*s == ' ') + { + if (*(s+1) != '\0') { + t[i-1] = ','; + t[i] = ' '; + p = &t[i++]; + } + else /* zoinks! last one terminated in a space! */ + { + t[i-1] = '\0'; + } + } + if(i > 80 - 5) + { + *p = t[i] = '\0'; + fprintf(file, " %s\n", t); + p++; + i = (int)strlen(p); + strcpy(t, p); + } + s++; + } + t[i] = '\0'; + fprintf(file, " %s.\n", t); +} + +/* ---------------------------------------------------------------------- */ + +#if defined(_WIN32) + +void +VisualInfoARB (GLContext* ctx) +{ + int attrib[32], value[32], n_attrib, n_pbuffer=0, n_float=0; + int i, pf, maxpf; + unsigned int c; + + /* to get pbuffer capable pixel formats */ + attrib[0] = WGL_DRAW_TO_PBUFFER_ARB; + attrib[1] = GL_TRUE; + attrib[2] = 0; + wglChoosePixelFormatARB(ctx->dc, attrib, 0, 1, &pf, &c); + /* query number of pixel formats */ + attrib[0] = WGL_NUMBER_PIXEL_FORMATS_ARB; + wglGetPixelFormatAttribivARB(ctx->dc, 0, 0, 1, attrib, value); + maxpf = value[0]; + for (i=0; i<32; i++) + value[i] = 0; + + attrib[0] = WGL_SUPPORT_OPENGL_ARB; + attrib[1] = WGL_DRAW_TO_WINDOW_ARB; + attrib[2] = WGL_DRAW_TO_BITMAP_ARB; + attrib[3] = WGL_ACCELERATION_ARB; + /* WGL_NO_ACCELERATION_ARB, WGL_GENERIC_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB */ + attrib[4] = WGL_SWAP_METHOD_ARB; + /* WGL_SWAP_EXCHANGE_ARB, WGL_SWAP_COPY_ARB, WGL_SWAP_UNDEFINED_ARB */ + attrib[5] = WGL_DOUBLE_BUFFER_ARB; + attrib[6] = WGL_STEREO_ARB; + attrib[7] = WGL_PIXEL_TYPE_ARB; + /* WGL_TYPE_RGBA_ARB, WGL_TYPE_COLORINDEX_ARB, + WGL_TYPE_RGBA_FLOAT_ATI (WGL_ATI_pixel_format_float) */ + /* Color buffer information */ + attrib[8] = WGL_COLOR_BITS_ARB; + attrib[9] = WGL_RED_BITS_ARB; + attrib[10] = WGL_GREEN_BITS_ARB; + attrib[11] = WGL_BLUE_BITS_ARB; + attrib[12] = WGL_ALPHA_BITS_ARB; + /* Accumulation buffer information */ + attrib[13] = WGL_ACCUM_BITS_ARB; + attrib[14] = WGL_ACCUM_RED_BITS_ARB; + attrib[15] = WGL_ACCUM_GREEN_BITS_ARB; + attrib[16] = WGL_ACCUM_BLUE_BITS_ARB; + attrib[17] = WGL_ACCUM_ALPHA_BITS_ARB; + /* Depth, stencil, and aux buffer information */ + attrib[18] = WGL_DEPTH_BITS_ARB; + attrib[19] = WGL_STENCIL_BITS_ARB; + attrib[20] = WGL_AUX_BUFFERS_ARB; + /* Layer information */ + attrib[21] = WGL_NUMBER_OVERLAYS_ARB; + attrib[22] = WGL_NUMBER_UNDERLAYS_ARB; + attrib[23] = WGL_SWAP_LAYER_BUFFERS_ARB; + attrib[24] = WGL_SAMPLES_ARB; + attrib[25] = WGL_SUPPORT_GDI_ARB; + n_attrib = 26; + if (WGLEW_ARB_pbuffer) + { + attrib[n_attrib] = WGL_DRAW_TO_PBUFFER_ARB; + n_pbuffer = n_attrib; + n_attrib++; + } + if (WGLEW_NV_float_buffer) + { + attrib[n_attrib] = WGL_FLOAT_COMPONENTS_NV; + n_float = n_attrib; + n_attrib++; + } + + if (!verbose) + { + /* print table header */ + fprintf(file, " +-----+-------------------------+-----------------+----------+-----------------+----------+\n"); + fprintf(file, " | | visual | color | ax dp st | accum | layer |\n"); + fprintf(file, " | id | tp ac gd fm db sw st ms | sz r g b a | bf th cl | sz r g b a | ov un sw |\n"); + fprintf(file, " +-----+-------------------------+-----------------+----------+-----------------+----------+\n"); + /* loop through all the pixel formats */ + for(i = 1; i <= maxpf; i++) + { + wglGetPixelFormatAttribivARB(ctx->dc, i, 0, n_attrib, attrib, value); + /* only describe this format if it supports OpenGL */ + if (!value[0]) continue; + /* by default show only fully accelerated window or pbuffer capable visuals */ + if (!showall + && ((value[2] && !value[1]) + || (!WGLEW_ARB_pbuffer || !value[n_pbuffer]) + || (value[3] != WGL_FULL_ACCELERATION_ARB))) continue; + /* print out the information for this visual */ + /* visual id */ + fprintf(file, " |% 4d | ", i); + /* visual type */ + if (value[1]) + { + if (WGLEW_ARB_pbuffer && value[n_pbuffer]) fprintf(file, "wp "); + else fprintf(file, "wn "); + } + else + { + if (value[2]) fprintf(file, "bm "); + else if (WGLEW_ARB_pbuffer && value[n_pbuffer]) fprintf(file, "pb "); + } + /* acceleration */ + fprintf(file, "%s ", value[3] == WGL_FULL_ACCELERATION_ARB ? "fu" : + value[3] == WGL_GENERIC_ACCELERATION_ARB ? "ge" : + value[3] == WGL_NO_ACCELERATION_ARB ? "no" : ". "); + /* gdi support */ + fprintf(file, " %c ", value[25] ? 'y' : '.'); + /* format */ + if (WGLEW_NV_float_buffer && value[n_float]) fprintf(file, " f "); + else if (WGLEW_ATI_pixel_format_float && value[7] == WGL_TYPE_RGBA_FLOAT_ATI) fprintf(file, " f "); + else if (value[7] == WGL_TYPE_RGBA_ARB) fprintf(file, " i "); + else if (value[7] == WGL_TYPE_COLORINDEX_ARB) fprintf(file, " c "); + /* double buffer */ + fprintf(file, " %c ", value[5] ? 'y' : '.'); + /* swap method */ + if (value[4] == WGL_SWAP_EXCHANGE_ARB) fprintf(file, " x "); + else if (value[4] == WGL_SWAP_COPY_ARB) fprintf(file, " c "); + else if (value[4] == WGL_SWAP_UNDEFINED_ARB) fprintf(file, " . "); + else fprintf(file, " . "); + /* stereo */ + fprintf(file, " %c ", value[6] ? 'y' : '.'); + /* multisample */ + if (value[24] > 0) + fprintf(file, "%2d | ", value[24]); + else + fprintf(file, " . | "); + /* color size */ + if (value[8]) fprintf(file, "%3d ", value[8]); + else fprintf(file, " . "); + /* red */ + if (value[9]) fprintf(file, "%2d ", value[9]); + else fprintf(file, " . "); + /* green */ + if (value[10]) fprintf(file, "%2d ", value[10]); + else fprintf(file, " . "); + /* blue */ + if (value[11]) fprintf(file, "%2d ", value[11]); + else fprintf(file, " . "); + /* alpha */ + if (value[12]) fprintf(file, "%2d | ", value[12]); + else fprintf(file, " . | "); + /* aux buffers */ + if (value[20]) fprintf(file, "%2d ", value[20]); + else fprintf(file, " . "); + /* depth */ + if (value[18]) fprintf(file, "%2d ", value[18]); + else fprintf(file, " . "); + /* stencil */ + if (value[19]) fprintf(file, "%2d | ", value[19]); + else fprintf(file, " . | "); + /* accum size */ + if (value[13]) fprintf(file, "%3d ", value[13]); + else fprintf(file, " . "); + /* accum red */ + if (value[14]) fprintf(file, "%2d ", value[14]); + else fprintf(file, " . "); + /* accum green */ + if (value[15]) fprintf(file, "%2d ", value[15]); + else fprintf(file, " . "); + /* accum blue */ + if (value[16]) fprintf(file, "%2d ", value[16]); + else fprintf(file, " . "); + /* accum alpha */ + if (value[17]) fprintf(file, "%2d | ", value[17]); + else fprintf(file, " . | "); + /* overlay */ + if (value[21]) fprintf(file, "%2d ", value[21]); + else fprintf(file, " . "); + /* underlay */ + if (value[22]) fprintf(file, "%2d ", value[22]); + else fprintf(file, " . "); + /* layer swap */ + if (value[23]) fprintf(file, "y "); + else fprintf(file, " . "); + fprintf(file, "|\n"); + } + /* print table footer */ + fprintf(file, " +-----+-------------------------+-----------------+----------+-----------------+----------+\n"); + fprintf(file, " | | visual | color | ax dp st | accum | layer |\n"); + fprintf(file, " | id | tp ac gd fm db sw st ms | sz r g b a | bf th cl | sz r g b a | ov un sw |\n"); + fprintf(file, " +-----+-------------------------+-----------------+----------+-----------------+----------+\n"); + } + else /* verbose */ + { +#if 0 + fprintf(file, "\n"); + /* loop through all the pixel formats */ + for(i = 1; i <= maxpf; i++) + { + DescribePixelFormat(ctx->dc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd); + /* only describe this format if it supports OpenGL */ + if(!(pfd.dwFlags & PFD_SUPPORT_OPENGL) + || (drawableonly && !(pfd.dwFlags & PFD_DRAW_TO_WINDOW))) continue; + fprintf(file, "Visual ID: %2d depth=%d class=%s\n", i, pfd.cDepthBits, + pfd.cColorBits <= 8 ? "PseudoColor" : "TrueColor"); + fprintf(file, " bufferSize=%d level=%d renderType=%s doubleBuffer=%d stereo=%d\n", pfd.cColorBits, pfd.bReserved, pfd.iPixelType == PFD_TYPE_RGBA ? "rgba" : "ci", pfd.dwFlags & PFD_DOUBLEBUFFER, pfd.dwFlags & PFD_STEREO); + fprintf(file, " generic=%d generic accelerated=%d\n", (pfd.dwFlags & PFD_GENERIC_FORMAT) == PFD_GENERIC_FORMAT, (pfd.dwFlags & PFD_GENERIC_ACCELERATED) == PFD_GENERIC_ACCELERATED); + fprintf(file, " rgba: redSize=%d greenSize=%d blueSize=%d alphaSize=%d\n", pfd.cRedBits, pfd.cGreenBits, pfd.cBlueBits, pfd.cAlphaBits); + fprintf(file, " auxBuffers=%d depthSize=%d stencilSize=%d\n", pfd.cAuxBuffers, pfd.cDepthBits, pfd.cStencilBits); + fprintf(file, " accum: redSize=%d greenSize=%d blueSize=%d alphaSize=%d\n", pfd.cAccumRedBits, pfd.cAccumGreenBits, pfd.cAccumBlueBits, pfd.cAccumAlphaBits); + fprintf(file, " multiSample=%d multisampleBuffers=%d\n", 0, 0); + fprintf(file, " Opaque.\n"); + } +#endif + } +} + +void +VisualInfoGDI (GLContext* ctx) +{ + int i, maxpf; + PIXELFORMATDESCRIPTOR pfd; + + /* calling DescribePixelFormat() with NULL pfd (!!!) return maximum + number of pixel formats */ + maxpf = DescribePixelFormat(ctx->dc, 1, 0, NULL); + + if (!verbose) + { + fprintf(file, "-----------------------------------------------------------------------------\n"); + fprintf(file, " visual x bf lv rg d st ge ge r g b a ax dp st accum buffs ms \n"); + fprintf(file, " id dep tp sp sz l ci b ro ne ac sz sz sz sz bf th cl sz r g b a ns b\n"); + fprintf(file, "-----------------------------------------------------------------------------\n"); + + /* loop through all the pixel formats */ + for(i = 1; i <= maxpf; i++) + { + DescribePixelFormat(ctx->dc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd); + /* only describe this format if it supports OpenGL */ + if(!(pfd.dwFlags & PFD_SUPPORT_OPENGL) + || (drawableonly && (pfd.dwFlags & PFD_DRAW_TO_BITMAP))) continue; + /* other criteria could be tested here for actual pixel format + choosing in an application: + + for (...each pixel format...) { + if (pfd.dwFlags & PFD_SUPPORT_OPENGL && + pfd.dwFlags & PFD_DOUBLEBUFFER && + pfd.cDepthBits >= 24 && + pfd.cColorBits >= 24) + { + goto found; + } + } + ... not found so exit ... + found: + ... found so use it ... + */ + /* print out the information for this pixel format */ + fprintf(file, "0x%02x ", i); + fprintf(file, "%3d ", pfd.cColorBits); + if(pfd.dwFlags & PFD_DRAW_TO_WINDOW) fprintf(file, "wn "); + else if(pfd.dwFlags & PFD_DRAW_TO_BITMAP) fprintf(file, "bm "); + else fprintf(file, "pb "); + /* should find transparent pixel from LAYERPLANEDESCRIPTOR */ + fprintf(file, " . "); + fprintf(file, "%3d ", pfd.cColorBits); + /* bReserved field indicates number of over/underlays */ + if(pfd.bReserved) fprintf(file, " %d ", pfd.bReserved); + else fprintf(file, " . "); + fprintf(file, " %c ", pfd.iPixelType == PFD_TYPE_RGBA ? 'r' : 'c'); + fprintf(file, "%c ", pfd.dwFlags & PFD_DOUBLEBUFFER ? 'y' : '.'); + fprintf(file, " %c ", pfd.dwFlags & PFD_STEREO ? 'y' : '.'); + /* added: */ + fprintf(file, " %c ", pfd.dwFlags & PFD_GENERIC_FORMAT ? 'y' : '.'); + fprintf(file, " %c ", pfd.dwFlags & PFD_GENERIC_ACCELERATED ? 'y' : '.'); + if(pfd.cRedBits && pfd.iPixelType == PFD_TYPE_RGBA) + fprintf(file, "%2d ", pfd.cRedBits); + else fprintf(file, " . "); + if(pfd.cGreenBits && pfd.iPixelType == PFD_TYPE_RGBA) + fprintf(file, "%2d ", pfd.cGreenBits); + else fprintf(file, " . "); + if(pfd.cBlueBits && pfd.iPixelType == PFD_TYPE_RGBA) + fprintf(file, "%2d ", pfd.cBlueBits); + else fprintf(file, " . "); + if(pfd.cAlphaBits && pfd.iPixelType == PFD_TYPE_RGBA) + fprintf(file, "%2d ", pfd.cAlphaBits); + else fprintf(file, " . "); + if(pfd.cAuxBuffers) fprintf(file, "%2d ", pfd.cAuxBuffers); + else fprintf(file, " . "); + if(pfd.cDepthBits) fprintf(file, "%2d ", pfd.cDepthBits); + else fprintf(file, " . "); + if(pfd.cStencilBits) fprintf(file, "%2d ", pfd.cStencilBits); + else fprintf(file, " . "); + if(pfd.cAccumBits) fprintf(file, "%3d ", pfd.cAccumBits); + else fprintf(file, " . "); + if(pfd.cAccumRedBits) fprintf(file, "%2d ", pfd.cAccumRedBits); + else fprintf(file, " . "); + if(pfd.cAccumGreenBits) fprintf(file, "%2d ", pfd.cAccumGreenBits); + else fprintf(file, " . "); + if(pfd.cAccumBlueBits) fprintf(file, "%2d ", pfd.cAccumBlueBits); + else fprintf(file, " . "); + if(pfd.cAccumAlphaBits) fprintf(file, "%2d ", pfd.cAccumAlphaBits); + else fprintf(file, " . "); + /* no multisample in win32 */ + fprintf(file, " . .\n"); + } + /* print table footer */ + fprintf(file, "-----------------------------------------------------------------------------\n"); + fprintf(file, " visual x bf lv rg d st ge ge r g b a ax dp st accum buffs ms \n"); + fprintf(file, " id dep tp sp sz l ci b ro ne ac sz sz sz sz bf th cl sz r g b a ns b\n"); + fprintf(file, "-----------------------------------------------------------------------------\n"); + } + else /* verbose */ + { + fprintf(file, "\n"); + /* loop through all the pixel formats */ + for(i = 1; i <= maxpf; i++) + { + DescribePixelFormat(ctx->dc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd); + /* only describe this format if it supports OpenGL */ + if(!(pfd.dwFlags & PFD_SUPPORT_OPENGL) + || (drawableonly && !(pfd.dwFlags & PFD_DRAW_TO_WINDOW))) continue; + fprintf(file, "Visual ID: %2d depth=%d class=%s\n", i, pfd.cDepthBits, + pfd.cColorBits <= 8 ? "PseudoColor" : "TrueColor"); + fprintf(file, " bufferSize=%d level=%d renderType=%s doubleBuffer=%ld stereo=%ld\n", pfd.cColorBits, pfd.bReserved, pfd.iPixelType == PFD_TYPE_RGBA ? "rgba" : "ci", pfd.dwFlags & PFD_DOUBLEBUFFER, pfd.dwFlags & PFD_STEREO); + fprintf(file, " generic=%d generic accelerated=%d\n", (pfd.dwFlags & PFD_GENERIC_FORMAT) == PFD_GENERIC_FORMAT, (pfd.dwFlags & PFD_GENERIC_ACCELERATED) == PFD_GENERIC_ACCELERATED); + fprintf(file, " rgba: redSize=%d greenSize=%d blueSize=%d alphaSize=%d\n", pfd.cRedBits, pfd.cGreenBits, pfd.cBlueBits, pfd.cAlphaBits); + fprintf(file, " auxBuffers=%d depthSize=%d stencilSize=%d\n", pfd.cAuxBuffers, pfd.cDepthBits, pfd.cStencilBits); + fprintf(file, " accum: redSize=%d greenSize=%d blueSize=%d alphaSize=%d\n", pfd.cAccumRedBits, pfd.cAccumGreenBits, pfd.cAccumBlueBits, pfd.cAccumAlphaBits); + fprintf(file, " multiSample=%d multisampleBuffers=%d\n", 0, 0); + fprintf(file, " Opaque.\n"); + } + } +} + +void +VisualInfo (GLContext* ctx) +{ + if (WGLEW_ARB_pixel_format) + VisualInfoARB(ctx); + else + VisualInfoGDI(ctx); +} + +/* ---------------------------------------------------------------------- */ + +#elif defined(__APPLE__) && !defined(GLEW_APPLE_GLX) + +void +VisualInfo (GLContext* ctx) +{ +/* + int attrib[] = { AGL_RGBA, AGL_NONE }; + AGLPixelFormat pf; + GLint value; + pf = aglChoosePixelFormat(NULL, 0, attrib); + while (pf != NULL) + { + aglDescribePixelFormat(pf, GL_RGBA, &value); + fprintf(stderr, "%d\n", value); + pf = aglNextPixelFormat(pf); + } +*/ +} + +#else /* GLX */ + +void +VisualInfo (GLContext* ctx) +{ + int n_fbc; + GLXFBConfig* fbc; + int value, ret, i; + + fbc = glXGetFBConfigs(ctx->dpy, DefaultScreen(ctx->dpy), &n_fbc); + + if (fbc) + { + if (!verbose) + { + /* print table header */ + fprintf(file, " +-----+-------------------------+-----------------+----------+-------------+-------+------+\n"); + fprintf(file, " | | visual | color | ax dp st | accum | ms | cav |\n"); + fprintf(file, " | id | tp xr cl fm db st lv xp | sz r g b a | bf th cl | r g b a | ns b | eat |\n"); + fprintf(file, " +-----+-------------------------+-----------------+----------+-------------+-------+------+\n"); + /* loop through all the fbcs */ + for (i=0; idpy, fbc[i], GLX_FBCONFIG_ID, &value); + if (ret != Success) + { + fprintf(file, "| ? |"); + } + else + { + fprintf(file, " |% 4d | ", value); + } + /* visual type */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_DRAWABLE_TYPE, &value); + if (ret != Success) + { + fprintf(file, " ? "); + } + else + { + if (value & GLX_WINDOW_BIT) + { + if (value & GLX_PBUFFER_BIT) + { + fprintf(file, "wp "); + } + else + { + fprintf(file, "wn "); + } + } + else + { + if (value & GLX_PBUFFER_BIT) + { + fprintf(file, "pb "); + } + else if (value & GLX_PIXMAP_BIT) + { + fprintf(file, "pm "); + } + else + { + fprintf(file, " ? "); + } + } + } + /* x renderable */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_X_RENDERABLE, &value); + if (ret != Success) + { + fprintf(file, " ? "); + } + else + { + fprintf(file, value ? " y " : " n "); + } + /* class */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_X_VISUAL_TYPE, &value); + if (ret != Success) + { + fprintf(file, " ? "); + } + else + { + if (GLX_TRUE_COLOR == value) + fprintf(file, "tc "); + else if (GLX_DIRECT_COLOR == value) + fprintf(file, "dc "); + else if (GLX_PSEUDO_COLOR == value) + fprintf(file, "pc "); + else if (GLX_STATIC_COLOR == value) + fprintf(file, "sc "); + else if (GLX_GRAY_SCALE == value) + fprintf(file, "gs "); + else if (GLX_STATIC_GRAY == value) + fprintf(file, "sg "); + else if (GLX_X_VISUAL_TYPE == value) + fprintf(file, " . "); + else + fprintf(file, " ? "); + } + /* format */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_RENDER_TYPE, &value); + if (ret != Success) + { + fprintf(file, " ? "); + } + else + { + if (GLXEW_NV_float_buffer) + { + int ret2, value2; + ret2 = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_FLOAT_COMPONENTS_NV, &value2); + if (Success == ret2 && GL_TRUE == value2) + { + fprintf(file, " f "); + } + else if (value & GLX_RGBA_BIT) + fprintf(file, " i "); + else if (value & GLX_COLOR_INDEX_BIT) + fprintf(file, " c "); + else + fprintf(file, " ? "); + } + else + { + if (value & GLX_RGBA_FLOAT_ATI_BIT) + fprintf(file, " f "); + else if (value & GLX_RGBA_BIT) + fprintf(file, " i "); + else if (value & GLX_COLOR_INDEX_BIT) + fprintf(file, " c "); + else + fprintf(file, " ? "); + } + } + /* double buffer */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_DOUBLEBUFFER, &value); + fprintf(file, " %c ", Success != ret ? '?' : (value ? 'y' : '.')); + /* stereo */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_STEREO, &value); + fprintf(file, " %c ", Success != ret ? '?' : (value ? 'y' : '.')); + /* level */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_LEVEL, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + fprintf(file, "%2d ", value); + } + /* transparency */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_TRANSPARENT_TYPE, &value); + if (Success != ret) + { + fprintf(file, " ? | "); + } + else + { + if (GLX_TRANSPARENT_RGB == value) + fprintf(file, " r | "); + else if (GLX_TRANSPARENT_INDEX == value) + fprintf(file, " i | "); + else if (GLX_NONE == value) + fprintf(file, " . | "); + else + fprintf(file, " ? | "); + } + /* color size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_BUFFER_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%3d ", value); + else + fprintf(file, " . "); + } + /* red size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_RED_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* green size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_GREEN_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* blue size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_BLUE_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* alpha size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_ALPHA_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? | "); + } + else + { + if (value) + fprintf(file, "%2d | ", value); + else + fprintf(file, " . | "); + } + /* aux buffers */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_AUX_BUFFERS, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* depth size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_DEPTH_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* stencil size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_STENCIL_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? | "); + } + else + { + if (value) + fprintf(file, "%2d | ", value); + else + fprintf(file, " . | "); + } + /* accum red size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_ACCUM_RED_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* accum green size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_ACCUM_GREEN_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* accum blue size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_ACCUM_BLUE_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + if (value) + fprintf(file, "%2d ", value); + else + fprintf(file, " . "); + } + /* accum alpha size */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_ACCUM_ALPHA_SIZE, &value); + if (Success != ret) + { + fprintf(file, " ? | "); + } + else + { + if (value) + fprintf(file, "%2d | ", value); + else + fprintf(file, " . | "); + } + /* multisample */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_SAMPLES, &value); + if (Success != ret) + { + fprintf(file, " ? "); + } + else + { + fprintf(file, "%2d ", value); + } + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_SAMPLE_BUFFERS, &value); + if (Success != ret) + { + fprintf(file, " ? | "); + } + else + { + fprintf(file, "%2d | ", value); + } + /* caveat */ + ret = glXGetFBConfigAttrib(ctx->dpy, fbc[i], GLX_CONFIG_CAVEAT, &value); + if (Success != ret) + { + fprintf(file, "???? |"); + } + else + { + if (GLX_NONE == value) + fprintf(file, "none |\n"); + else if (GLX_SLOW_CONFIG == value) + fprintf(file, "slow |\n"); + else if (GLX_NON_CONFORMANT_CONFIG == value) + fprintf(file, "ncft |\n"); + else + fprintf(file, "???? |\n"); + } + } + /* print table footer */ + fprintf(file, " +-----+-------------------------+-----------------+----------+-------------+-------+------+\n"); + fprintf(file, " | id | tp xr cl fm db st lv xp | sz r g b a | bf th cl | r g b a | ns b | eat |\n"); + fprintf(file, " | | visual | color | ax dp st | accum | ms | cav |\n"); + fprintf(file, " +-----+-------------------------+-----------------+----------+-------------+-------+------+\n"); + } + } +} + +#endif + +/* ------------------------------------------------------------------------ */ + +#if defined(_WIN32) + +void InitContext (GLContext* ctx) +{ + ctx->wnd = NULL; + ctx->dc = NULL; + ctx->rc = NULL; +} + +GLboolean CreateContext (GLContext* ctx) +{ + WNDCLASS wc; + PIXELFORMATDESCRIPTOR pfd; + /* check for input */ + if (NULL == ctx) return GL_TRUE; + /* register window class */ + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.hInstance = GetModuleHandle(NULL); + wc.lpfnWndProc = DefWindowProc; + wc.lpszClassName = "GLEW"; + if (0 == RegisterClass(&wc)) return GL_TRUE; + /* create window */ + ctx->wnd = CreateWindow("GLEW", "GLEW", 0, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, + GetModuleHandle(NULL), NULL); + if (NULL == ctx->wnd) return GL_TRUE; + /* get the device context */ + ctx->dc = GetDC(ctx->wnd); + if (NULL == ctx->dc) return GL_TRUE; + /* find pixel format */ + ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR)); + if (visual == -1) /* find default */ + { + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + visual = ChoosePixelFormat(ctx->dc, &pfd); + if (0 == visual) return GL_TRUE; + } + /* set the pixel format for the dc */ + if (FALSE == SetPixelFormat(ctx->dc, visual, &pfd)) return GL_TRUE; + /* create rendering context */ + ctx->rc = wglCreateContext(ctx->dc); + if (NULL == ctx->rc) return GL_TRUE; + if (FALSE == wglMakeCurrent(ctx->dc, ctx->rc)) return GL_TRUE; + return GL_FALSE; +} + +void DestroyContext (GLContext* ctx) +{ + if (NULL == ctx) return; + if (NULL != ctx->rc) wglMakeCurrent(NULL, NULL); + if (NULL != ctx->rc) wglDeleteContext(wglGetCurrentContext()); + if (NULL != ctx->wnd && NULL != ctx->dc) ReleaseDC(ctx->wnd, ctx->dc); + if (NULL != ctx->wnd) DestroyWindow(ctx->wnd); + UnregisterClass("GLEW", GetModuleHandle(NULL)); +} + +/* ------------------------------------------------------------------------ */ + +#elif defined(__APPLE__) && !defined(GLEW_APPLE_GLX) + +void InitContext (GLContext* ctx) +{ + ctx->ctx = NULL; + ctx->octx = NULL; +} + +GLboolean CreateContext (GLContext* ctx) +{ + int attrib[] = { AGL_RGBA, AGL_NONE }; + AGLPixelFormat pf; + /* check input */ + if (NULL == ctx) return GL_TRUE; + /*int major, minor; + SetPortWindowPort(wnd); + aglGetVersion(&major, &minor); + fprintf(stderr, "GL %d.%d\n", major, minor);*/ + pf = aglChoosePixelFormat(NULL, 0, attrib); + if (NULL == pf) return GL_TRUE; + ctx->ctx = aglCreateContext(pf, NULL); + if (NULL == ctx->ctx || AGL_NO_ERROR != aglGetError()) return GL_TRUE; + aglDestroyPixelFormat(pf); + /*aglSetDrawable(ctx, GetWindowPort(wnd));*/ + ctx->octx = aglGetCurrentContext(); + if (NULL == aglSetCurrentContext(ctx->ctx)) return GL_TRUE; + return GL_FALSE; +} + +void DestroyContext (GLContext* ctx) +{ + if (NULL == ctx) return; + aglSetCurrentContext(ctx->octx); + if (NULL != ctx->ctx) aglDestroyContext(ctx->ctx); +} + +/* ------------------------------------------------------------------------ */ + +#else /* __UNIX || (__APPLE__ && GLEW_APPLE_GLX) */ + +void InitContext (GLContext* ctx) +{ + ctx->dpy = NULL; + ctx->vi = NULL; + ctx->ctx = NULL; + ctx->wnd = 0; + ctx->cmap = 0; +} + +GLboolean CreateContext (GLContext* ctx) +{ + int attrib[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None }; + int erb, evb; + XSetWindowAttributes swa; + /* check input */ + if (NULL == ctx) return GL_TRUE; + /* open display */ + ctx->dpy = XOpenDisplay(display); + if (NULL == ctx->dpy) return GL_TRUE; + /* query for glx */ + if (!glXQueryExtension(ctx->dpy, &erb, &evb)) return GL_TRUE; + /* choose visual */ + ctx->vi = glXChooseVisual(ctx->dpy, DefaultScreen(ctx->dpy), attrib); + if (NULL == ctx->vi) return GL_TRUE; + /* create context */ + ctx->ctx = glXCreateContext(ctx->dpy, ctx->vi, None, True); + if (NULL == ctx->ctx) return GL_TRUE; + /* create window */ + /*wnd = XCreateSimpleWindow(dpy, RootWindow(dpy, vi->screen), 0, 0, 1, 1, 1, 0, 0);*/ + ctx->cmap = XCreateColormap(ctx->dpy, RootWindow(ctx->dpy, ctx->vi->screen), + ctx->vi->visual, AllocNone); + swa.border_pixel = 0; + swa.colormap = ctx->cmap; + ctx->wnd = XCreateWindow(ctx->dpy, RootWindow(ctx->dpy, ctx->vi->screen), + 0, 0, 1, 1, 0, ctx->vi->depth, InputOutput, ctx->vi->visual, + CWBorderPixel | CWColormap, &swa); + /* make context current */ + if (!glXMakeCurrent(ctx->dpy, ctx->wnd, ctx->ctx)) return GL_TRUE; + return GL_FALSE; +} + +void DestroyContext (GLContext* ctx) +{ + if (NULL != ctx->dpy && NULL != ctx->ctx) glXDestroyContext(ctx->dpy, ctx->ctx); + if (NULL != ctx->dpy && 0 != ctx->wnd) XDestroyWindow(ctx->dpy, ctx->wnd); + if (NULL != ctx->dpy && 0 != ctx->cmap) XFreeColormap(ctx->dpy, ctx->cmap); + if (NULL != ctx->vi) XFree(ctx->vi); + if (NULL != ctx->dpy) XCloseDisplay(ctx->dpy); +} + +#endif /* __UNIX || (__APPLE__ && GLEW_APPLE_GLX) */ + +GLboolean ParseArgs (int argc, char** argv) +{ + int p = 0; + while (p < argc) + { +#if defined(_WIN32) + if (!strcmp(argv[p], "-pf") || !strcmp(argv[p], "-pixelformat")) + { + if (++p >= argc) return GL_TRUE; + display = NULL; + visual = strtol(argv[p], NULL, 0); + } + else if (!strcmp(argv[p], "-a")) + { + showall = 1; + } + else if (!strcmp(argv[p], "-s")) + { + displaystdout = 1; + } + else if (!strcmp(argv[p], "-h")) + { + return GL_TRUE; + } + else + return GL_TRUE; +#else + if (!strcmp(argv[p], "-display")) + { + if (++p >= argc) return GL_TRUE; + display = argv[p]; + } + else if (!strcmp(argv[p], "-visual")) + { + if (++p >= argc) return GL_TRUE; + visual = (int)strtol(argv[p], NULL, 0); + } + else if (!strcmp(argv[p], "-h")) + { + return GL_TRUE; + } + else + return GL_TRUE; +#endif + p++; + } + return GL_FALSE; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice.cpp new file mode 100644 index 0000000..40db3c8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice.cpp @@ -0,0 +1,3058 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice.cpp (LICE core processing) + See lice.h for license and other information +*/ + + +#ifndef __LICE_CPP_IMPLEMENTED__ +#define __LICE_CPP_IMPLEMENTED__ + +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "lice.h" +#include +#include // only included in case we need to debug with sprintf etc + +#include "lice_combine.h" +#include "lice_extended.h" + +#ifndef _WIN32 +#include "../swell/swell.h" +#endif + +#define IGNORE_SCALING(mode) ((mode)&LICE_BLIT_IGNORE_SCALING) + +#define DO_RECT_SC(mode) \ + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); \ + if (__sc>0) { \ + if (!IGNORE_SCALING(mode)) { \ + __LICE_SC(x); \ + __LICE_SC(y); \ + __LICE_SCU(w); \ + __LICE_SCU(h); \ + } \ + __LICE_SCU(destbm_w); \ + __LICE_SCU(destbm_h); \ + } + + + +_LICE_ImageLoader_rec *LICE_ImageLoader_list; + +LICE_pixel LICE_CombinePixels(LICE_pixel dest, LICE_pixel src, float alpha, int mode) +{ + int r = LICE_GETR(src); + int g = LICE_GETG(src); + int b = LICE_GETB(src); + int a = LICE_GETA(src); + int al = (int)(alpha*256.0f); + +#define __LICE__ACTION(COMBFUNC) COMBFUNC::doPix((LICE_pixel_chan*)&dest,r, g, b, a, al) + __LICE_ACTION_SRCALPHA(mode, al,false); +#undef __LICE__ACTION + + return dest; +} + + +void LICE_CombinePixels2(LICE_pixel *destptr, int r, int g, int b, int a, int ia, int mode) +{ +#define __LICE__ACTION(COMBFUNC) COMBFUNC::doPix((LICE_pixel_chan*)destptr,r, g, b, a, ia) + __LICE_ACTION_SRCALPHA(mode, ia, false); +#undef __LICE__ACTION +} +void LICE_CombinePixels2Clamp(LICE_pixel *destptr, int r, int g, int b, int a, int ia, int mode) +{ +#define __LICE__ACTION(COMBFUNC) COMBFUNC::doPix((LICE_pixel_chan*)destptr,r, g, b, a, ia) + __LICE_ACTION_SRCALPHA(mode, ia, true); +#undef __LICE__ACTION +} + + +LICE_MemBitmap::LICE_MemBitmap(int w, int h, unsigned int linealign) +{ + m_allocsize=0; + m_fb=0; + m_width=0; + m_height=0; + m_linealign = linealign > 1 ? ((linealign & ~(linealign-1))-1) : 0; // force to be contiguous bits + if (m_linealign>16) m_linealign=16; + if (w>0&&h>0) __resize(w,h); +} + +LICE_MemBitmap::~LICE_MemBitmap() { free(m_fb); } + +bool LICE_MemBitmap::__resize(int w, int h) +{ + if (w!=m_width||h!=m_height) + { +#ifdef DEBUG_TIGHT_ALLOC // dont enable for anything you want to be even remotely fast + free(m_fb); + m_fb = (LICE_pixel *)malloc((m_allocsize = ((w+m_linealign)&~m_linealign)*h*sizeof(LICE_pixel)) + LICE_MEMBITMAP_ALIGNAMT); + m_width=m_fb?w:0; + m_height=m_fb?h:0; + return true; +#endif + int sz=(((m_width=w)+m_linealign)&~m_linealign)*(m_height=h)*sizeof(LICE_pixel); + + if (sz<=0||w<1||h<1) { free(m_fb); m_fb=0; m_allocsize=0; } + else if (!m_fb) m_fb=(LICE_pixel*)malloc((m_allocsize=sz) + LICE_MEMBITMAP_ALIGNAMT); + else + { + if (sz>m_allocsize) + { + void *op=m_fb; + if (!(m_fb=(LICE_pixel*)realloc(m_fb,(m_allocsize=sz+sz/4)+LICE_MEMBITMAP_ALIGNAMT))) + { + free(op); + m_fb=(LICE_pixel*)malloc((m_allocsize=sz)+LICE_MEMBITMAP_ALIGNAMT); + } + } + } + if (!m_fb) {m_width=m_height=0; } + + return true; + } + return false; +} + + + +#ifndef _LICE_NO_SYSBITMAPS_ + +LICE_SysBitmap::LICE_SysBitmap(int w, int h) +{ + m_allocw=m_alloch=0; +#ifdef _WIN32 + m_dc = CreateCompatibleDC(NULL); + m_bitmap = 0; + m_oldbitmap = 0; +#else + m_dc=0; +#endif + m_bits=0; + m_width=m_height=0; + m_adv_scaling=0; + m_draw_scaling=0; + + __resize(w,h); +} + + +LICE_SysBitmap::~LICE_SysBitmap() +{ +#ifdef _WIN32 + if (m_oldbitmap && m_dc) SelectObject(m_dc,m_oldbitmap); + if (m_bitmap) DeleteObject(m_bitmap); + if (m_dc) DeleteDC(m_dc); +#else + if (m_dc) + SWELL_DeleteGfxContext(m_dc); +#endif +} + +bool LICE_SysBitmap::__resize(int w, int h) +{ +#ifdef _WIN32 + if (!m_dc) { m_width=m_height=0; m_bits=0; return false; } +#endif + + if (m_width==w && m_height == h) return false; + + m_width=w; + m_height=h; + + if (m_draw_scaling > 0) + { + w = (w * m_draw_scaling) >> 8; + h = (h * m_draw_scaling) >> 8; + } + w = (w+3)&~3; // always keep backing store a multiple of 4px wide + +#ifndef DEBUG_TIGHT_ALLOC + // dont resize down bitmaps + if (w && h && w <= m_allocw && h <= m_alloch && m_bits) + { +#ifndef _WIN32 + if (isFlipped()) + { + m_bits=(LICE_pixel*)SWELL_GetCtxFrameBuffer(m_dc); + m_bits += (m_alloch-h)*m_allocw; + } +#endif + return true; + } +#endif//!DEBUG_TIGHT_ALLOC + + m_allocw=w; + m_alloch=h; + +#ifdef _WIN32 + if (m_oldbitmap) + { + SelectObject(m_dc,m_oldbitmap); + m_oldbitmap=0; + } + if (m_bitmap) DeleteObject(m_bitmap); + m_bitmap=0; + m_bits=0; + + + if (w<1 || h<1) return false; + + BITMAPINFO pbmInfo = {0,}; + pbmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmInfo.bmiHeader.biWidth = w; + pbmInfo.bmiHeader.biHeight = isFlipped()?h:-h; + pbmInfo.bmiHeader.biPlanes = 1; + pbmInfo.bmiHeader.biBitCount = sizeof(LICE_pixel)*8; + pbmInfo.bmiHeader.biCompression = BI_RGB; + m_bitmap = CreateDIBSection( NULL, &pbmInfo, DIB_RGB_COLORS, (void **)&m_bits, NULL, 0); + + if (m_bitmap) m_oldbitmap=SelectObject(m_dc, m_bitmap); + else { m_width=m_height=0; m_bits=0; } +#else + if (m_dc) SWELL_DeleteGfxContext(m_dc); + m_dc=0; + m_bits=0; + + if (w<1 || h<1) return false; + + m_dc=SWELL_CreateMemContext(0,w,h); + if (!m_dc) { m_width=m_height=0; m_bits=0; } + else m_bits=(LICE_pixel*)SWELL_GetCtxFrameBuffer(m_dc); +#endif + + return true; +} + +#endif // _LICE_NO_SYSBITMAPS_ + + + +#ifndef LICE_NO_BLIT_SUPPORT +void LICE_Copy(LICE_IBitmap *dest, LICE_IBitmap *src) // resizes dest +{ + if (src&&dest) + { + dest->resize(src->getWidth(),src->getHeight()); + LICE_Blit(dest,src,0,0,NULL,1.0,LICE_BLIT_MODE_COPY); + } +} +#endif + +template class _LICE_Template_Blit0 // these always templated +{ + public: + static void solidBlitFAST(LICE_pixel *dest, int w, int h, + LICE_pixel color, + int dest_span) + { + while (h--) + { + LICE_pixel *pout=dest; + int n=w; + while (n--) + { + COMBFUNC::doPixFAST(pout,color); + ++pout; + } + dest+=dest_span; + } + } + + + + static void scaleBlitFAST(LICE_pixel_chan *dest, const LICE_pixel_chan *src, int w, int h, + int icurx, int icury, int idx, int idy, unsigned int clipright, unsigned int clipbottom, + int src_span, int dest_span) + { + LICE_pixel* destpx = (LICE_pixel*) dest; + int destpxspan = dest_span*(int)sizeof(LICE_pixel_chan)/(int)sizeof(LICE_pixel); + + while (h--) + { + const unsigned int cury = icury >> 16; + if (cury < clipbottom) + { + int curx=icurx; + const LICE_pixel_chan *inptr=src + cury * src_span; + LICE_pixel* pout = destpx; + int n=w; + while (n--) + { + const unsigned int offs=curx >> 16; + if (offs +#endif +class _LICE_Template_Blit1 // these controlled by LICE_FAVOR_SIZE_EXTREME +{ +#ifdef LICE_FAVOR_SIZE_EXTREME + #define DOPIX(pout,r,g,b,a,ia) combFunc(pout,r,g,b,a,ia); +#else + #define DOPIX(pout,r,g,b,a,ia) COMBFUNC::doPix(pout,r,g,b,a,ia); +#endif + public: + static void solidBlit(LICE_pixel_chan *dest, int w, int h, + int ir, int ig, int ib, int pxa, int ia, + int dest_span +#ifdef LICE_FAVOR_SIZE_EXTREME + , LICE_COMBINEFUNC combFunc +#endif + ) + { + while (h--) + { + LICE_pixel_chan *pout=dest; + int n=w; + while (n--) + { + DOPIX(pout,ir,ig,ib,pxa,ia); + pout += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + } + dest+=dest_span; + } + } + static void gradBlit(LICE_pixel_chan *dest, int w, int h, + int ir, int ig, int ib, int ia, + int drdx, int dgdx, int dbdx, int dadx, + int drdy, int dgdy, int dbdy, int dady, + int dest_span +#ifdef LICE_FAVOR_SIZE_EXTREME + , LICE_COMBINEFUNC combFunc +#endif + ) + { + while (h--) + { + int r=ir,g=ig,b=ib,a=ia; + ir+=drdy; ig+=dgdy; ib+=dbdy; ia+=dady; + LICE_pixel_chan *pout=dest; + int n=w; + while (n--) + { + const int aa=a/65536; + DOPIX(pout,r/65536,g/65536,b/65536,aa,aa); + pout += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + r+=drdx; g+=dgdx; b+=dbdx; a+=dadx; + } + dest+=dest_span; + } + } + +#undef DOPIX +}; + + +#ifndef LICE_FAVOR_SIZE +template +#endif +class _LICE_Template_Blit2 // these controlled by LICE_FAVOR_SIZE +{ +#ifdef LICE_FAVOR_SIZE + #define DOPIX(pout,r,g,b,a,ia) combFunc(pout,r,g,b,a,ia); +#else + #define DOPIX(pout,r,g,b,a,ia) COMBFUNC::doPix(pout,r,g,b,a,ia); +#endif + + public: + + static void blit(LICE_pixel_chan *dest, const LICE_pixel_chan *src, int w, int h, int src_span, int dest_span, int ia +#ifdef LICE_FAVOR_SIZE + , LICE_COMBINEFUNC combFunc +#endif + ) + { + while (h-->0) + { + int n=w; + const LICE_pixel_chan *pin=src; + LICE_pixel_chan *pout=dest; + while (n--) + { + + DOPIX(pout,pin[LICE_PIXEL_R],pin[LICE_PIXEL_G],pin[LICE_PIXEL_B],pin[LICE_PIXEL_A],ia); + + pin += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + pout += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + } + dest+=dest_span; + src += src_span; + } + } + + // this is only designed for filtering down an image approx 2:1 to 4:1 or so.. it'll work (poortly) for higher, and for less it's crap too. + // probably need to redo it using linear interpolation of the filter coefficients, but bleh I'm gonna go play some call of duty + static void scaleBlitFilterDown(LICE_pixel_chan *dest, const LICE_pixel_chan *src, int w, int h, + int icurx, int icury, int idx, int idy, int clipright, int clipbottom, + int src_span, int dest_span, int ia, const int *filter, int filt_start, int filtsz +#ifdef LICE_FAVOR_SIZE + , LICE_COMBINEFUNC combFunc +#endif + + ) + { + + while (h--) + { + int cury = icury >> 16; + int curx=icurx; + int n=w; + if (cury >= 0 && cury < clipbottom) + { + const LICE_pixel_chan *inptr=src + (cury+filt_start) * src_span; + LICE_pixel_chan *pout=dest; + while (n--) + { + int offs=curx >> 16; + if (offs>=0 && offs= clipbottom) break; + + if (ypos >= 0) + { + int xpos=offs + filt_start; + const LICE_pixel_chan *pin = rdptr; + int fx=filtsz; + while (fx--) + { + int tsc = *scaletab++; + if (xpos >= 0 && xpos < clipright) + { + r+=pin[LICE_PIXEL_R]*tsc; + g+=pin[LICE_PIXEL_G]*tsc; + b+=pin[LICE_PIXEL_B]*tsc; + a+=pin[LICE_PIXEL_A]*tsc; + sc+=tsc; + } + xpos++; + pin+=sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + } + } + else scaletab += filtsz; + + ypos++; + rdptr+=src_span; + } + if (sc>0) + { + DOPIX(pout,r/sc,g/sc,b/sc,a/sc,ia); + } + } + + pout += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + curx+=idx; + } + } + dest+=dest_span; + icury+=idy; + } + } + static void scaleBlit(LICE_pixel_chan *dest, const LICE_pixel_chan *src, int w, int h, + int icurx, int icury, int idx, int idy, unsigned int clipright, unsigned int clipbottom, + int src_span, int dest_span, int ia, int filtermode +#ifdef LICE_FAVOR_SIZE + , LICE_COMBINEFUNC combFunc +#endif + + ) + { + + if (filtermode == LICE_BLIT_FILTER_BILINEAR) + { + while (h--) + { + const unsigned int cury = icury >> 16; + const int yfrac=icury&65535; + int curx=icurx; + const LICE_pixel_chan *inptr=src + cury * src_span; + LICE_pixel_chan *pout=dest; + int n=w; + if (cury < clipbottom-1) + { + while (n--) + { + const unsigned int offs=curx >> 16; + const LICE_pixel_chan *pin = inptr + offs*sizeof(LICE_pixel); + if (offs> 16; + const LICE_pixel_chan *pin = inptr + offs*sizeof(LICE_pixel); + if (offs> 16; + if (cury < clipbottom) + { + int curx=icurx; + const LICE_pixel_chan *inptr=src + cury * src_span; + LICE_pixel_chan *pout=dest; + int n=w; + while (n--) + { + const unsigned int offs=curx >> 16; + if (offs +#endif +class _LICE_Template_Blit3 // stuff controlled by LICE_FAVOR_SPEED +{ +#ifndef LICE_FAVOR_SPEED + #define DOPIX(pout,r,g,b,a,ia) combFunc(pout,r,g,b,a,ia); +#else + #define DOPIX(pout,r,g,b,a,ia) COMBFUNC::doPix(pout,r,g,b,a,ia); +#endif + + public: + + static void deltaBlit(LICE_pixel_chan *dest, const LICE_pixel_chan *src, int w, int h, + int isrcx, int isrcy, int idsdx, int idtdx, int idsdy, int idtdy, + int idsdxdy, int idtdxdy, + unsigned int src_right, unsigned int src_bottom, + int src_span, int dest_span, int ia, int filtermode +#ifndef LICE_FAVOR_SPEED + , LICE_COMBINEFUNC combFunc +#endif + ) + { + if (filtermode == LICE_BLIT_FILTER_BILINEAR) + { + while (h--) + { + int thisx=isrcx; + int thisy=isrcy; + LICE_pixel_chan *pout=dest; + int n=w; + while (n--) + { + const unsigned int cury = thisy >> 16; + const unsigned int curx = thisx >> 16; + if (cury < src_bottom-1) + { + if (curx < src_right-1) + { + const LICE_pixel_chan *pin = src + cury * src_span + curx*sizeof(LICE_pixel); + int r,g,b,a; + + __LICE_BilinearFilterI(&r,&g,&b,&a,pin,pin+src_span,thisx&65535,thisy&65535); + + DOPIX(pout,r,g,b,a,ia); + } + else if (curx==src_right-1) + { + + const LICE_pixel_chan *pin = src + cury * src_span + curx*sizeof(LICE_pixel); + int r,g,b,a; + + __LICE_LinearFilterI(&r,&g,&b,&a,pin,pin+src_span,thisy&65535); + DOPIX(pout,r,g,b,a,ia); + } + } + else if (cury==src_bottom-1) + { + if (curx> 16; + const unsigned int curx = thisx >> 16; + if (cury < src_bottom && curx < src_right) + { + const LICE_pixel_chan *pin = src + cury * src_span + curx*sizeof(LICE_pixel); + + DOPIX(pout,pin[LICE_PIXEL_R],pin[LICE_PIXEL_G],pin[LICE_PIXEL_B],pin[LICE_PIXEL_A],ia); + } + + pout += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + thisx+=idsdx; + thisy+=idtdx; + } + idsdx+=idsdxdy; + idtdx+=idtdxdy; + isrcx+=idsdy; + isrcy+=idtdy; + dest+=dest_span; + } + } + } + +#undef DOPIX +}; + +#ifdef LICE_FAVOR_SPEED +template +#endif +class _LICE_Template_Blit4 // stuff controlled by LICE_FAVOR_SPEED +{ +#ifndef LICE_FAVOR_SPEED + #define DOPIX(pout,r,g,b,a,ia) combFunc(pout,r,g,b,a,ia); +#else + #define DOPIX(pout,r,g,b,a,ia) COMBFUNC::doPix(pout,r,g,b,a,ia); +#endif + + public: + + static void deltaBlitAlpha(LICE_pixel_chan *dest, const LICE_pixel_chan *src, int w, int h, + int isrcx, int isrcy, int idsdx, int idtdx, int idsdy, int idtdy, + int idsdxdy, int idtdxdy, + unsigned int src_right, unsigned int src_bottom, + int src_span, int dest_span, int ia, int idadx, int idady, int idadxdy, int filtermode +#ifndef LICE_FAVOR_SPEED + , LICE_COMBINEFUNC combFunc +#endif + ) + { + if (filtermode == LICE_BLIT_FILTER_BILINEAR) + { + while (h--) + { + int thisx=isrcx; + int thisy=isrcy; + int thisa=ia; + LICE_pixel_chan *pout=dest; + int n=w; + while (n--) + { + const unsigned int cury = thisy >> 16; + const unsigned int curx = thisx >> 16; + if (cury < src_bottom-1) + { + if (curx < src_right-1) + { + const LICE_pixel_chan *pin = src + cury * src_span + curx*sizeof(LICE_pixel); + int r,g,b,a; + + __LICE_BilinearFilterI(&r,&g,&b,&a,pin,pin+src_span,thisx&65535,thisy&65535); + + DOPIX(pout,r,g,b,a,thisa>>8); + } + else if (curx==src_right-1) + { + + const LICE_pixel_chan *pin = src + cury * src_span + curx*sizeof(LICE_pixel); + int r,g,b,a; + + __LICE_LinearFilterI(&r,&g,&b,&a,pin,pin+src_span,thisy&65535); + DOPIX(pout,r,g,b,a,thisa>>8); + } + } + else if (cury==src_bottom-1) + { + if (curx>8); + } + else if (curx==src_right-1) + { + const LICE_pixel_chan *pin = src + cury * src_span + curx*sizeof(LICE_pixel); + DOPIX(pout,pin[LICE_PIXEL_R],pin[LICE_PIXEL_G],pin[LICE_PIXEL_B],pin[LICE_PIXEL_A],thisa>>8); + } + } + + pout += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + thisx+=idsdx; + thisy+=idtdx; + thisa+=idadx; + } + idsdx+=idsdxdy; + idtdx+=idtdxdy; + idadx+=idadxdy; + isrcx+=idsdy; + isrcy+=idtdy; + ia += idady; + dest+=dest_span; + } + } + else + { + while (h--) + { + int thisx=isrcx; + int thisy=isrcy; + int thisa=ia; + LICE_pixel_chan *pout=dest; + int n=w; + while (n--) + { + const unsigned int cury = thisy >> 16; + const unsigned int curx = thisx >> 16; + if (cury < src_bottom && curx < src_right) + { + const LICE_pixel_chan *pin = src + cury * src_span + curx*sizeof(LICE_pixel); + + DOPIX(pout,pin[LICE_PIXEL_R],pin[LICE_PIXEL_G],pin[LICE_PIXEL_B],pin[LICE_PIXEL_A],thisa>>8); + } + + pout += sizeof(LICE_pixel)/sizeof(LICE_pixel_chan); + thisx+=idsdx; + thisy+=idtdx; + thisa+=idadx; + } + idsdx+=idsdxdy; + idtdx+=idtdxdy; + idadx+=idadxdy; + isrcx+=idsdy; + isrcy+=idtdy; + ia+=idady; + dest+=dest_span; + } + } + } + +#undef DOPIX +}; + + + +#ifndef LICE_NO_GRADIENT_SUPPORT + +void LICE_GradRect(LICE_IBitmap *dest, int dstx, int dsty, int dstw, int dsth, + float ir, float ig, float ib, float ia, + float drdx, float dgdx, float dbdx, float dadx, + float drdy, float dgdy, float dbdy, float dady, + int mode) +{ + if (!dest) return; + + ir*=255.0; ig*=255.0; ib*=255.0; ia*=256.0; + drdx*=255.0; dgdx*=255.0; dbdx*=255.0; dadx*=256.0; + drdy*=255.0; dgdy*=255.0; dbdy*=255.0; dady*=256.0; + // dont scale alpha + + // clip to output + if (dstx < 0) + { + ir-=dstx*drdx; ig-=dstx*dgdx; ib-=dstx*dbdx; ia-=dstx*dadx; + dstw+=dstx; + dstx=0; + } + if (dsty < 0) + { + ir -= dsty*drdy; ig-=dsty*dgdy; ib -= dsty*dbdy; ia -= dsty*dady; + dsth += dsty; + dsty=0; + } + + int dest_span=dest->getRowSpan()*sizeof(LICE_pixel); + LICE_pixel_chan *pdest = (LICE_pixel_chan *)dest->getBits(); + + const int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + + if (!pdest || !dest_span || dstw < 1 || dsth < 1 || dstx >= destbm_w || dsty >= destbm_h) return; + + if (dstw > destbm_w-dstx) dstw = destbm_w-dstx; + if (dsth > destbm_h-dsty) dsth = destbm_h-dsty; + + if (dest->isFlipped()) + { + pdest += (destbm_h-dsty - 1)*dest_span; + dest_span=-dest_span; + } + else + { + pdest += dsty*dest_span; + } + pdest+=dstx*sizeof(LICE_pixel); +#define TOFIX(a) ((int)((a)*65536.0)) + + int iir=TOFIX(ir), iig=TOFIX(ig), iib=TOFIX(ib), iia=TOFIX(ia), idrdx=TOFIX(drdx),idgdx=TOFIX(dgdx),idbdx=TOFIX(dbdx),idadx=TOFIX(dadx), + idrdy=TOFIX(drdy), idgdy=TOFIX(dgdy), idbdy=TOFIX(dbdy), idady=TOFIX(dady); + + +#ifdef LICE_FAVOR_SIZE_EXTREME + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; +#else + + #define __LICE__ACTION(comb) _LICE_Template_Blit1::gradBlit(pdest,dstw,dsth,iir,iig,iib,iia,idrdx,idgdx,idbdx,idadx,idrdy,idgdy,idbdy,idady,dest_span) +#endif + + // todo: could predict whether or not the colors will ever go out of 0.255 range and optimize + + if ((mode & LICE_BLIT_MODE_MASK)==LICE_BLIT_MODE_COPY && iia==65536 && idady==0 && idadx == 0) + { + __LICE__ACTION(_LICE_CombinePixelsClobberClamp); + } + else + { + __LICE_ACTION_NOSRCALPHA(mode,256,true); + } + #undef __LICE__ACTION + +#ifdef LICE_FAVOR_SIZE_EXTREME + if (blitfunc) _LICE_Template_Blit1::gradBlit(pdest,dstw,dsth,iir,iig,iib,iia,idrdx,idgdx,idbdx,idadx,idrdy,idgdy,idbdy,idady,dest_span,blitfunc); +#endif + +#undef TOFIX +} + +#endif + + +#ifndef LICE_NO_BLIT_SUPPORT + +static void LICE_BlitInt(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, const RECT *srcrect, float alpha, int mode, bool allowSc) +{ + if (!dest || !src || !alpha) return; + + int srcbm_w = src->getWidth(), srcbm_h = src->getHeight(); + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + int __sc2 = (int)src->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc2 > 0) + { + const int __sc = __sc2; + __LICE_SCU(srcbm_w); + __LICE_SCU(srcbm_h); + } + + RECT sr={0,0,srcbm_w, srcbm_h}; + if (srcrect) + { + sr=*srcrect; + if (sr.left < 0) { dstx-=sr.left; sr.left=0; } + if (sr.top < 0) { dsty-=sr.top; sr.top=0; } + if (sr.right > srcbm_w) sr.right=srcbm_w; + if (sr.bottom > srcbm_h) sr.bottom=srcbm_h; + } + + int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (allowSc && __sc != __sc2 && !IGNORE_SCALING(mode)) + { + const int w = sr.right-sr.left, h=sr.bottom-sr.top; + LICE_ScaledBlit(dest,src,dstx,dsty,w,h, sr.left,sr.top,w,h,alpha,mode); + return; + } + if (__sc>0) + { + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(dstx); + __LICE_SC(dsty); + } + } + if (__sc2>0 && !IGNORE_SCALING(mode)) + { + __sc=__sc2; + __LICE_SC(sr.left); + __LICE_SC(sr.right); + __LICE_SC(sr.top); + __LICE_SC(sr.bottom); + } + + + // clip to output + if (dstx < 0) { sr.left -= dstx; dstx=0; } + if (dsty < 0) { sr.top -= dsty; dsty=0; } + + if (sr.left < 0 || sr.top < 0) return; // overflow check + if (sr.right <= sr.left || sr.bottom <= sr.top || dstx >= destbm_w || dsty >= destbm_h) return; + + if (sr.right > sr.left + (destbm_w-dstx)) sr.right = sr.left + (destbm_w-dstx); + if (sr.bottom > sr.top + (destbm_h-dsty)) sr.bottom = sr.top + (destbm_h-dsty); + + // ignore blits that are 0 + if (sr.right <= sr.left || sr.bottom <= sr.top) return; + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_BLIT_ACCEL)) + { + LICE_Ext_Blit_acceldata data(src, dstx, dsty, + sr.left, sr.top, + sr.right-sr.left, sr.bottom-sr.top, + alpha, mode); + if (dest->Extended(LICE_EXT_BLIT_ACCEL, &data)) return; + } +#endif + + + int dest_span=dest->getRowSpan()*sizeof(LICE_pixel); + int src_span=src->getRowSpan()*sizeof(LICE_pixel); + const LICE_pixel_chan *psrc = (LICE_pixel_chan *)src->getBits(); + LICE_pixel_chan *pdest = (LICE_pixel_chan *)dest->getBits(); + if (!psrc || !pdest) return; + + if (src->isFlipped()) + { + psrc += (src->getHeight()-sr.top - 1)*src_span; + src_span=-src_span; + } + else psrc += sr.top*src_span; + psrc += sr.left*sizeof(LICE_pixel); + + if (dest->isFlipped()) + { + pdest += (destbm_h-dsty - 1)*dest_span; + dest_span=-dest_span; + } + else pdest += dsty*dest_span; + pdest+=dstx*sizeof(LICE_pixel); + + int i=sr.bottom-sr.top; + int cpsize=sr.right-sr.left; + + if ((mode&LICE_BLIT_MODE_MASK) >= LICE_BLIT_MODE_CHANCOPY && (mode&LICE_BLIT_MODE_MASK) < LICE_BLIT_MODE_CHANCOPY+0x10) + { + while (i-->0) + { + LICE_pixel_chan *o=pdest+((mode>>2)&3); + const LICE_pixel_chan *in=psrc+(mode&3); + int a=cpsize; + while (a--) + { + *o=*in; + o+=sizeof(LICE_pixel); + in+=sizeof(LICE_pixel); + } + pdest+=dest_span; + psrc += src_span; + } + } + // special fast case for copy with no source alpha and alpha=1.0 or 0.5 + else if ((mode&(LICE_BLIT_MODE_MASK|LICE_BLIT_USE_ALPHA))==LICE_BLIT_MODE_COPY && (alpha==1.0||alpha==0.5)) + { + if (alpha==0.5) + { + while (i-->0) + { + int a=cpsize; + const LICE_pixel *rd = (LICE_pixel *)psrc; + LICE_pixel *wr = (LICE_pixel *)pdest; + while (a-->0) + { + *wr = ((*wr>>1)&0x7f7f7f7f)+((*rd++>>1)&0x7f7f7f7f); + wr++; + } + + pdest+=dest_span; + psrc += src_span; + } + } + else + { + while (i-->0) + { + memmove(pdest,psrc,cpsize*sizeof(LICE_pixel)); + pdest+=dest_span; + psrc += src_span; + } + } + } + else + { + int ia=(int)(alpha*256.0); + #ifdef LICE_FAVOR_SIZE + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; + #else + #define __LICE__ACTION(comb) _LICE_Template_Blit2::blit(pdest,psrc,cpsize,i,src_span,dest_span,ia) + #endif + + __LICE_ACTION_SRCALPHA(mode,ia,false); + + #undef __LICE__ACTION + + #ifdef LICE_FAVOR_SIZE + if (blitfunc) _LICE_Template_Blit2::blit(pdest,psrc,cpsize,i,src_span,dest_span,ia,blitfunc); + #endif + } +} + +void LICE_Blit(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, const RECT *srcrect, float alpha, int mode) +{ + LICE_BlitInt(dest,src,dstx,dsty,srcrect,alpha,mode,true); +} + +void LICE_Blit(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, int srcx, int srcy, int srcw, int srch, float alpha, int mode) +{ + RECT r={srcx,srcy,srcx+srcw,srcy+srch}; + LICE_BlitInt(dest,src,dstx,dsty,&r,alpha,mode,true); +} + +#endif + +#ifndef LICE_NO_BLUR_SUPPORT + +void LICE_Blur(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, int srcx, int srcy, int srcw, int srch) // src and dest can overlap, however it may look fudgy if they do +{ + if (!dest || !src) return; + + RECT sr={srcx,srcy,srcx+srcw,srcy+srch}; + if (sr.left < 0) sr.left=0; + if (sr.top < 0) sr.top=0; + if (sr.right > src->getWidth()) sr.right=src->getWidth(); + if (sr.bottom > src->getHeight()) sr.bottom = src->getHeight(); + + // clip to output + if (dstx < 0) { sr.left -= dstx; dstx=0; } + if (dsty < 0) { sr.top -= dsty; dsty=0; } + + if (sr.left < 0 || sr.top < 0) return; // overflow check + + const int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + if (sr.right <= sr.left || sr.bottom <= sr.top || dstx >= destbm_w || dsty >= destbm_h) return; + + if (sr.right > sr.left + (destbm_w-dstx)) sr.right = sr.left + (destbm_w-dstx); + if (sr.bottom > sr.top + (destbm_h-dsty)) sr.bottom = sr.top + (destbm_h-dsty); + + // ignore blits that are smaller than 2x2 + if (sr.right <= sr.left+1 || sr.bottom <= sr.top+1) return; + + int dest_span=dest->getRowSpan(); + int src_span=src->getRowSpan(); + const LICE_pixel *psrc = (LICE_pixel *)src->getBits(); + LICE_pixel *pdest = (LICE_pixel *)dest->getBits(); + if (!psrc || !pdest) return; + + if (src->isFlipped()) + { + psrc += (src->getHeight()-sr.top - 1)*src_span; + src_span=-src_span; + } + else psrc += sr.top*src_span; + psrc += sr.left; + + if (dest->isFlipped()) + { + pdest += (destbm_h-dsty - 1)*dest_span; + dest_span=-dest_span; + } + else pdest += dsty*dest_span; + pdest+=dstx; + + LICE_pixel *tmpbuf=NULL; + int w=sr.right-sr.left; + + // buffer to save the last unprocessed lines for the cases where blurring from a bitmap to itself + LICE_pixel turdbuf[2048]; + if (src==dest) + { + if (w <= (int) (sizeof(turdbuf)/sizeof(turdbuf[0])/2)) tmpbuf=turdbuf; + else tmpbuf=(LICE_pixel*)malloc(w*2*sizeof(LICE_pixel)); + } + + int i; + for (i = sr.top; i < sr.bottom; i ++) + { + if (tmpbuf) + memcpy(tmpbuf+((i&1)?w:0),psrc,w*sizeof(LICE_pixel)); + + if (i==sr.top || i==sr.bottom-1) + { + const LICE_pixel *psrc2=psrc+(i==sr.top ? src_span : -src_span); + + LICE_pixel lp; + + pdest[0] = LICE_PIXEL_HALF(lp=psrc[0]) + + LICE_PIXEL_QUARTER(psrc[1]) + + LICE_PIXEL_QUARTER(psrc2[0]); + int x; + for (x = 1; x < w-1; x ++) + { + LICE_pixel tp; + pdest[x] = LICE_PIXEL_HALF(tp=psrc[x]) + + LICE_PIXEL_QUARTER(psrc2[x]) + + LICE_PIXEL_EIGHTH(psrc[x+1]) + + LICE_PIXEL_EIGHTH(lp); + lp=tp; + } + pdest[x] = LICE_PIXEL_HALF(psrc[x]) + + LICE_PIXEL_QUARTER(lp) + + LICE_PIXEL_QUARTER(psrc2[x]); + } + else + { + const LICE_pixel *psrc2=psrc-src_span; + const LICE_pixel *psrc3=psrc+src_span; + if (tmpbuf) + psrc2=tmpbuf + ((i&1) ? 0 : w); + + LICE_pixel lp; + pdest[0] = LICE_PIXEL_HALF(lp=psrc[0]) + + LICE_PIXEL_QUARTER(psrc[1]) + + LICE_PIXEL_EIGHTH(psrc2[0]) + + LICE_PIXEL_EIGHTH(psrc3[0]); + int x; + for (x = 1; x < w-1; x ++) + { + LICE_pixel tp; + pdest[x] = LICE_PIXEL_HALF(tp=psrc[x]) + + LICE_PIXEL_EIGHTH(psrc[x+1]) + + LICE_PIXEL_EIGHTH(lp) + + LICE_PIXEL_EIGHTH(psrc2[x]) + + LICE_PIXEL_EIGHTH(psrc3[x]); + lp=tp; + } + pdest[x] = LICE_PIXEL_HALF(psrc[x]) + + LICE_PIXEL_QUARTER(lp) + + LICE_PIXEL_EIGHTH(psrc2[x]) + + LICE_PIXEL_EIGHTH(psrc3[x]); + } + pdest+=dest_span; + psrc += src_span; + } + if (tmpbuf && tmpbuf != turdbuf) + free(tmpbuf); +} + +#endif + +#ifndef LICE_NO_BLIT_SUPPORT +void LICE_ScaledBlit(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + float srcx, float srcy, float srcw, float srch, + float alpha, int mode) +{ + if (!dest || !src || !dstw || !dsth || !alpha) return; + + int srcbm_w = src->getWidth(), srcbm_h = src->getHeight(); + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + const float srcx_orig = srcx, srcy_orig = srcy, srcw_orig = srcw, srch_orig = srch; + const int dstx_orig = dstx, dsty_orig = dsty; + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(dstx); + __LICE_SC(dsty); + __LICE_SC(dstw); + __LICE_SC(dsth); + } + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + } + __sc = (int)src->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(srcx); + __LICE_SC(srcy); + __LICE_SC(srcw); + __LICE_SC(srch); + } + __LICE_SCU(srcbm_w); + __LICE_SCU(srcbm_h); + } + + // non-scaling optimized omde + if (fabs(srcw-dstw)<0.001 && fabs(srch-dsth)<0.001) + { + // and if not bilinear filtering, or + // the source coordinates are near their integer counterparts + if ((mode&LICE_BLIT_FILTER_MASK)!=LICE_BLIT_FILTER_BILINEAR || + (fabs(srcx-floor(srcx+0.5f))<0.03 && fabs(srcy-floor(srcy+0.5f))<0.03)) + { + RECT sr={(int)(srcx_orig+0.5f),(int)(srcy_orig+0.5f),}; + sr.right=sr.left+(int) (srcw_orig+0.5); + sr.bottom=sr.top+(int) (srch_orig+0.5); + LICE_BlitInt(dest,src,dstx_orig,dsty_orig,&sr,alpha,mode,false); + return; + } + } + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_SCALEDBLIT_ACCEL)) + { + LICE_Ext_ScaledBlit_acceldata data(src, dstx, dsty, dstw, dsth, srcx, srcy, srcw, srch, alpha, mode); + if (dest->Extended(LICE_EXT_SCALEDBLIT_ACCEL, &data)) return; + } +#endif + + if (dstw<0) + { + dstw=-dstw; + dstx-=dstw; + srcx+=srcw; + srcw=-srcw; + } + if (dsth<0) + { + dsth=-dsth; + dsty-=dsth; + srcy+=srch; + srch=-srch; + } + + double xadvance = srcw / dstw; + double yadvance = srch / dsth; + + int clip_r=(int)(srcx+lice_max(srcw,0)+0.999999); // this rounding logic is shit, but we're stuck with it + int clip_b=(int)(srcy+lice_max(srch,0)+0.999999); + if (clip_r>srcbm_w) clip_r=srcbm_w; + if (clip_b>srcbm_h) clip_b=srcbm_h; + + + if (dstx < 0) { srcx -= (float) (dstx*xadvance); dstw+=dstx; dstx=0; } + if (dsty < 0) { srcy -= (float) (dsty*yadvance); dsth+=dsty; dsty=0; } + + if (dstw < 1 || dsth < 1 || dstx >= destbm_w || dsty >= destbm_h) return; + + if (dstw > destbm_w-dstx) dstw=destbm_w-dstx; + if (dsth > destbm_h-dsty) dsth=destbm_h-dsty; + + const double fidx=floor(xadvance*65536.0), fidy=floor(yadvance*65536.0); + + double ficurx=floor(srcx*65536.0), ficury=floor(srcy*65536.0); + + // the clip area calculations need to be done fixed point so the results match runtime + + if (fidx>0) + { + if (ficurx < 0) // increase dstx, decrease dstw + { + int n = (int)((fidx-1-ficurx)/fidx); + dstw-=n; + dstx+=n; + ficurx+=fidx*n; + } + if ((ficurx + fidx*(dstw-1)) >= srcbm_w*65536.0) + { + int neww = (int)(((srcbm_w-1)*65536.0 - ficurx)/fidx); + if (neww < dstw) dstw=neww; + } + } + else if (fidx<0) + { + // todo: optimize source-clipping with reversed X axis + } + + if (fidy > 0) + { + if (ficury < 0) // increase dsty, decrease dsth + { + int n = (int) ((fidy-1-ficury)/fidy); + dsth-=n; + dsty+=n; + ficury+=fidy*n; + } + if ((ficury + fidy*(dsth-1)) >= srcbm_h*65536.0) + { + int newh = (int)(((srcbm_h-1)*65536.0 - ficury)/fidy); + if (newh < dsth) dsth=newh; + } + } + else if (fidy<0) + { + // todo: optimize source-clipping with reversed Y axis (check icury against src->getHeight(), etc) + } + if (dstw<1 || dsth<1) return; + + int dest_span=dest->getRowSpan()*sizeof(LICE_pixel); + int src_span=src->getRowSpan()*sizeof(LICE_pixel); + + const LICE_pixel_chan *psrc = (const LICE_pixel_chan *)src->getBits(); + LICE_pixel_chan *pdest = (LICE_pixel_chan *)dest->getBits(); + if (!psrc || !pdest) return; + + const int srcoffs_x = (int)((ficurx + (fidx<0 ? fidx*dstw : 0))*(1.0/65536.0)); + const int srcoffs_y = (int)((ficury + (fidy<0 ? fidy*dsth : 0))*(1.0/65536.0)); + + if (src->isFlipped()) + { + psrc += (srcbm_h-1-srcoffs_y)*src_span; + src_span=-src_span; + } + else psrc += srcoffs_y * src_span; + psrc += srcoffs_x * sizeof(LICE_pixel); + + if (dest->isFlipped()) + { + pdest += (destbm_h-dsty - 1)*dest_span; + dest_span=-dest_span; + } + else pdest += dsty*dest_span; + pdest+=dstx*sizeof(LICE_pixel); + + clip_r -= srcoffs_x; + clip_b -= srcoffs_y; + + const int icurx = (int) (ficurx - srcoffs_x*65536.0); + const int icury = (int) (ficury - srcoffs_y*65536.0); + const int idx = (int) fidx, idy = (int) fidy; + + if (clip_r<1||clip_b<1) return; + + int ia=(int)(alpha*256.0); + + if ((mode&(LICE_BLIT_FILTER_MASK|LICE_BLIT_MODE_MASK|LICE_BLIT_USE_ALPHA))==LICE_BLIT_MODE_COPY && (ia==128 || ia==256)) + { + if (ia==128) + { + _LICE_Template_Blit0<_LICE_CombinePixelsHalfMixFAST>::scaleBlitFAST(pdest,psrc,dstw,dsth,icurx,icury,idx,idy,clip_r,clip_b,src_span,dest_span); + } + else + { + _LICE_Template_Blit0<_LICE_CombinePixelsClobberFAST>::scaleBlitFAST(pdest,psrc,dstw,dsth,icurx,icury,idx,idy,clip_r,clip_b,src_span,dest_span); + } + } + else + { + if (xadvance>=1.7 && yadvance >=1.7 && (mode&LICE_BLIT_FILTER_MASK)==LICE_BLIT_FILTER_BILINEAR) + { + int msc = lice_max(idx,idy); + const int filtsz=msc>(3<<16) ? 5 : 3; + const int filt_start = - (filtsz/2); + + int filter[25]; // 5x5 max + { + int y; + // char buf[4096]; + // sprintf(buf,"filter, msc=%f: ",msc); + int *p=filter; + for(y=0;y1.0) *p++=65536; + else *p++=(int)(v*65536.0); + } + } + } +// OutputDebugString(buf); + } + + #ifdef LICE_FAVOR_SIZE + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; + #else + #define __LICE__ACTION(comb) _LICE_Template_Blit2::scaleBlitFilterDown(pdest,psrc,dstw,dsth,icurx,icury,idx,idy,clip_r,clip_b,src_span,dest_span,ia,filter,filt_start,filtsz) + #endif + __LICE_ACTION_SRCALPHA(mode,ia,false); + #undef __LICE__ACTION + + #ifdef LICE_FAVOR_SIZE + if (blitfunc) _LICE_Template_Blit2::scaleBlitFilterDown(pdest,psrc,dstw,dsth,icurx,icury,idx,idy,clip_r,clip_b,src_span,dest_span,ia,filter,filt_start,filtsz,blitfunc); + #endif + + } + else + { + #ifdef LICE_FAVOR_SIZE + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; + #else + #define __LICE__ACTION(comb) _LICE_Template_Blit2::scaleBlit(pdest,psrc,dstw,dsth,icurx,icury,idx,idy,clip_r,clip_b,src_span,dest_span,ia,mode&LICE_BLIT_FILTER_MASK) + #endif + __LICE_ACTION_SRCALPHA(mode,ia,false); + #undef __LICE__ACTION + #ifdef LICE_FAVOR_SIZE + if (blitfunc) _LICE_Template_Blit2::scaleBlit(pdest,psrc,dstw,dsth,icurx,icury,idx,idy,clip_r,clip_b,src_span,dest_span,ia,mode&LICE_BLIT_FILTER_MASK,blitfunc); + #endif + } + } +} + +void LICE_DeltaBlit(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + float srcx, float srcy, float srcw, float srch, + double dsdx, double dtdx, double dsdy, double dtdy, + double dsdxdy, double dtdxdy, + bool cliptosourcerect, float alpha, int mode) +{ + if (!dest || !src || !dstw || !dsth) return; + + int srcbm_w = src->getWidth(), srcbm_h = src->getHeight(); + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + + int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(dstx); + __LICE_SC(dsty); + __LICE_SC(dstw); + __LICE_SC(dsth); + } + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + + } + const int __scdest = __sc; + __sc = (int)src->Extended(LICE_EXT_GET_SCALING,NULL); + + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(srcx); + __LICE_SC(srcy); + __LICE_SC(srcw); + __LICE_SC(srch); + } + __LICE_SCU(srcbm_w); + __LICE_SCU(srcbm_h); + } + + if (__scdest != __sc && !IGNORE_SCALING(mode)) + { + const double adj = (__sc>0 ? __sc : 256.0) / (double) (__scdest>0 ? __scdest : 256.0); + dsdx *= adj; + dtdx *= adj; + dsdy *= adj; + dtdy *= adj; + dsdxdy *= adj; + dtdxdy *= adj; + } + + double src_top=0.0,src_left=0.0,src_right=srcbm_w,src_bottom=srcbm_h; + + if (cliptosourcerect) + { + if (srcx > src_left) src_left=srcx; + if (srcy > src_top) src_top=srcy; + if (srcx+srcw < src_right) src_right=srcx+srcw; + if (srcy+srch < src_bottom) src_bottom=srcy+srch; + } + + if (dstw<0) + { + dstw=-dstw; + dstx-=dstw; + srcx+=srcw; + } + if (dsth<0) + { + dsth=-dsth; + dsty-=dsth; + srcy+=srch; + } + + + if (dstx < 0) + { + srcx -= (float) (dstx*dsdx); + srcy -= (float) (dstx*dtdx); + dstw+=dstx; + dstx=0; + } + if (dsty < 0) + { + srcy -= (float) (dsty*dtdy); + srcx -= (float) (dsty*dsdy); + dsth+=dsty; + dsty=0; + } + + if (dstw < 1 || dsth < 1 || dstx >= destbm_w || dsty >= destbm_h) return; + + if (dstw > destbm_w-dstx) dstw=destbm_w-dstx; + if (dsth > destbm_h-dsty) dsth=destbm_h-dsty; + + + int dest_span=dest->getRowSpan()*sizeof(LICE_pixel); + int src_span=src->getRowSpan()*sizeof(LICE_pixel); + + const LICE_pixel_chan *psrc = (LICE_pixel_chan *)src->getBits(); + LICE_pixel_chan *pdest = (LICE_pixel_chan *)dest->getBits(); + if (!psrc || !pdest) return; + + if (src->isFlipped()) + { + psrc += (srcbm_h-1)*src_span; + src_span=-src_span; + } + + if (dest->isFlipped()) + { + pdest += (destbm_h-dsty - 1)*dest_span; + dest_span=-dest_span; + } + else pdest += dsty*dest_span; + pdest+=dstx*sizeof(LICE_pixel); + + int sl=(int)(src_left); + int sr=(int)(src_right); + int st=(int)(src_top); + int sb=(int)(src_bottom); + + sr -= sl; + sb -= st; + if (sr < 1 || sb < 1) return; + + psrc += src_span * st + sl * sizeof(LICE_pixel); + + int ia=(int)(alpha*256.0); + int isrcx=(int)(srcx*65536.0); + int isrcy=(int)(srcy*65536.0); + int idsdx=(int)(dsdx*65536.0); + int idtdx=(int)(dtdx*65536.0); + int idsdy=(int)(dsdy*65536.0); + int idtdy=(int)(dtdy*65536.0); + int idsdxdy=(int)(dsdxdy*65536.0); + int idtdxdy=(int)(dtdxdy*65536.0); + +#ifndef LICE_FAVOR_SPEED + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc = comb::doPix; +#else + #define __LICE__ACTION(comb) _LICE_Template_Blit3::deltaBlit(pdest,psrc,dstw,dsth,isrcx,isrcy,idsdx,idtdx,idsdy,idtdy,idsdxdy,idtdxdy,sr,sb,src_span,dest_span,ia,mode&LICE_BLIT_FILTER_MASK) +#endif + __LICE_ACTION_SRCALPHA(mode,ia,false); + #undef __LICE__ACTION + +#ifndef LICE_FAVOR_SPEED + if (blitfunc) _LICE_Template_Blit3::deltaBlit(pdest,psrc,dstw,dsth,isrcx,isrcy,idsdx,idtdx,idsdy,idtdy,idsdxdy,idtdxdy,sr,sb,src_span,dest_span,ia,mode&LICE_BLIT_FILTER_MASK,blitfunc); +#endif + +} + +void LICE_DeltaBlitAlpha(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + float srcx, float srcy, float srcw, float srch, + double dsdx, double dtdx, double dsdy, double dtdy, + double dsdxdy, double dtdxdy, + bool cliptosourcerect, float alpha, int mode, double dadx, double dady, double dadxdy) +{ + if (!dest || !src || !dstw || !dsth) return; + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + int srcbm_w = src->getWidth(), srcbm_h = src->getHeight(); + int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(dstx); + __LICE_SC(dsty); + __LICE_SC(dstw); + __LICE_SC(dsth); + } + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + + } + const int __scdest = __sc; + __sc = (int)src->Extended(LICE_EXT_GET_SCALING,NULL); + + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(srcx); + __LICE_SC(srcy); + __LICE_SC(srcw); + __LICE_SC(srch); + } + __LICE_SCU(srcbm_w); + __LICE_SCU(srcbm_h); + } + + if (lice_max(__scdest,0) != lice_max(__sc,0)) + { + const double adj = (__sc>0 ? __sc : 256.0) / (double) (__scdest>0 ? __scdest : 256.0); + dsdx *= adj; + dtdx *= adj; + dadx *= adj; + dsdy *= adj; + dtdy *= adj; + dady *= adj; + dsdxdy *= adj; + dtdxdy *= adj; + dadxdy *= adj; + } + + const double eps = 0.0001; + if (fabs(dadx*dstw) < eps && fabs(dady*dsth) < eps && fabs(dadxdy*dsth) < eps) + { + LICE_DeltaBlit(dest, src, dstx, dsty, dstw, dsth, srcx, srcy, srcw, srch, dsdx, dtdx, dsdy, dtdy, dsdxdy, dtdxdy, cliptosourcerect, alpha, mode); + return; + } + + double src_top=0.0,src_left=0.0,src_right=srcbm_w,src_bottom=srcbm_h; + + if (cliptosourcerect) + { + if (srcx > src_left) src_left=srcx; + if (srcy > src_top) src_top=srcy; + if (srcx+srcw < src_right) src_right=srcx+srcw; + if (srcy+srch < src_bottom) src_bottom=srcy+srch; + } + + if (dstw<0) + { + dstw=-dstw; + dstx-=dstw; + srcx+=srcw; + } + if (dsth<0) + { + dsth=-dsth; + dsty-=dsth; + srcy+=srch; + } + + + if (dstx < 0) + { + alpha -= (float) (dstx*dadx); + srcx -= (float) (dstx*dsdx); + srcy -= (float) (dstx*dtdx); + dstw+=dstx; + dstx=0; + } + if (dsty < 0) + { + alpha -= (float) (dsty*dady); + srcy -= (float) (dsty*dtdy); + srcx -= (float) (dsty*dsdy); + dsth+=dsty; + dsty=0; + } + + if (dstw < 1 || dsth < 1 || dstx >= destbm_w || dsty >= destbm_h) return; + + if (dstw > destbm_w-dstx) dstw=destbm_w-dstx; + if (dsth > destbm_h-dsty) dsth=destbm_h-dsty; + + + int dest_span=dest->getRowSpan()*sizeof(LICE_pixel); + int src_span=src->getRowSpan()*sizeof(LICE_pixel); + + const LICE_pixel_chan *psrc = (LICE_pixel_chan *)src->getBits(); + LICE_pixel_chan *pdest = (LICE_pixel_chan *)dest->getBits(); + if (!psrc || !pdest) return; + + if (src->isFlipped()) + { + psrc += (srcbm_h-1)*src_span; + src_span=-src_span; + } + + if (dest->isFlipped()) + { + pdest += (destbm_h-dsty - 1)*dest_span; + dest_span=-dest_span; + } + else pdest += dsty*dest_span; + pdest+=dstx*sizeof(LICE_pixel); + + int sl=(int)(src_left); + int sr=(int)(src_right); + int st=(int)(src_top); + int sb=(int)(src_bottom); + + sr -= sl; + sb -= st; + if (sr < 1 || sb < 1) return; + + psrc += src_span * st + sl * sizeof(LICE_pixel); + + int ia=(int)(alpha*65536.0); + int isrcx=(int)(srcx*65536.0); + int isrcy=(int)(srcy*65536.0); + int idsdx=(int)(dsdx*65536.0); + int idtdx=(int)(dtdx*65536.0); + int idsdy=(int)(dsdy*65536.0); + int idtdy=(int)(dtdy*65536.0); + int idsdxdy=(int)(dsdxdy*65536.0); + int idtdxdy=(int)(dtdxdy*65536.0); + + int idadx=(int)(dadx*65536.0); + int idady=(int)(dady*65536.0); + int idadxdy=(int)(dadxdy*65536.0); + +#ifndef LICE_FAVOR_SPEED + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc = comb::doPix; +#else + #define __LICE__ACTION(comb) _LICE_Template_Blit4::deltaBlitAlpha(pdest,psrc,dstw,dsth,isrcx,isrcy,idsdx,idtdx,idsdy,idtdy,idsdxdy,idtdxdy,sr,sb,src_span,dest_span,ia,idadx,idady,idadxdy,mode&LICE_BLIT_FILTER_MASK) +#endif + __LICE_ACTION_NOSRCALPHA(mode,256,true); + #undef __LICE__ACTION + +#ifndef LICE_FAVOR_SPEED + if (blitfunc) _LICE_Template_Blit4::deltaBlitAlpha(pdest,psrc,dstw,dsth,isrcx,isrcy,idsdx,idtdx,idsdy,idtdy,idsdxdy,idtdxdy,sr,sb,src_span,dest_span,ia,idadx,idady,idadxdy,mode&LICE_BLIT_FILTER_MASK,blitfunc); +#endif + +} + + + + +void LICE_RotatedBlit(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + float srcx, float srcy, float srcw, float srch, + float angle, + bool cliptosourcerect, float alpha, int mode, float rotxcent, float rotycent) +{ + if (!dest || !src || !dstw || !dsth) return; + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + int srcbm_w = src->getWidth(), srcbm_h = src->getHeight(); + int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(dstx); + __LICE_SC(dsty); + __LICE_SC(dstw); + __LICE_SC(dsth); + } + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + + } + __sc = (int)src->Extended(LICE_EXT_GET_SCALING,NULL); + + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(srcx); + __LICE_SC(srcy); + __LICE_SC(srcw); + __LICE_SC(srch); + } + __LICE_SCU(srcbm_w); + __LICE_SCU(srcbm_h); + } + double src_top=0.0,src_left=0.0,src_right=srcbm_w,src_bottom=srcbm_h; + + if (cliptosourcerect) + { + if (srcx > src_left) src_left=srcx; + if (srcy > src_top) src_top=srcy; + if (srcx+srcw < src_right) src_right=srcx+srcw; + if (srcy+srch < src_bottom) src_bottom=srcy+srch; + } + + if (dstw<0) + { + dstw=-dstw; + dstx-=dstw; + srcx+=srcw; + srcw=-srcw; + } + if (dsth<0) + { + dsth=-dsth; + dsty-=dsth; + srcy+=srch; + srch=-srch; + } + + double cosa=cos(angle); + double sina=sin(angle); + + double xsc=srcw / dstw; + double ysc=srch / dsth; + + double dsdx = xsc * cosa; + double dtdy = ysc * cosa; + double dsdy = xsc * sina; + double dtdx = ysc * -sina; + + srcx -= (float) (0.5 * (dstw*dsdx + dsth*dsdy - srcw) - rotxcent); + srcy -= (float) (0.5 * (dsth*dtdy + dstw*dtdx - srch) - rotycent); + + if (dstx < 0) + { + srcx -= (float) (dstx*dsdx); + srcy -= (float) (dstx*dtdx); + dstw+=dstx; + dstx=0; + } + if (dsty < 0) + { + srcy -= (float) (dsty*dtdy); + srcx -= (float) (dsty*dsdy); + dsth+=dsty; + dsty=0; + } + + if (dstw < 1 || dsth < 1 || dstx >= destbm_w || dsty >= destbm_h) return; + + if (dstw > destbm_w-dstx) dstw=destbm_w-dstx; + if (dsth > destbm_h-dsty) dsth=destbm_h-dsty; + + + int dest_span=dest->getRowSpan()*sizeof(LICE_pixel); + int src_span=src->getRowSpan()*sizeof(LICE_pixel); + + const LICE_pixel_chan *psrc = (LICE_pixel_chan *)src->getBits(); + LICE_pixel_chan *pdest = (LICE_pixel_chan *)dest->getBits(); + if (!psrc || !pdest) return; + + if (src->isFlipped()) + { + psrc += (srcbm_h-1)*src_span; + src_span=-src_span; + } + + if (dest->isFlipped()) + { + pdest += (destbm_h-dsty - 1)*dest_span; + dest_span=-dest_span; + } + else pdest += dsty*dest_span; + pdest+=dstx*sizeof(LICE_pixel); + + int sl=(int)(src_left); + int sr=(int)(src_right); + int st=(int)(src_top); + int sb=(int)(src_bottom); + + sr -= sl; + sb -= st; + srcx -= sl; + srcy -= st; + if (sr < 1 || sb < 1) return; + + psrc += src_span * st + sl * sizeof(LICE_pixel); + + int ia=(int)(alpha*256.0); + int isrcx=(int)(srcx*65536.0); + int isrcy=(int)(srcy*65536.0); + int idsdx=(int)(dsdx*65536.0); + int idtdx=(int)(dtdx*65536.0); + int idsdy=(int)(dsdy*65536.0); + int idtdy=(int)(dtdy*65536.0); + +#ifndef LICE_FAVOR_SPEED + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc = comb::doPix; +#else + #define __LICE__ACTION(comb) _LICE_Template_Blit3::deltaBlit(pdest,psrc,dstw,dsth,isrcx,isrcy,idsdx,idtdx,idsdy,idtdy,0,0,sr,sb,src_span,dest_span,ia,mode&LICE_BLIT_FILTER_MASK) +#endif + __LICE_ACTION_SRCALPHA(mode,ia,false); + #undef __LICE__ACTION + +#ifndef LICE_FAVOR_SPEED + if (blitfunc) _LICE_Template_Blit3::deltaBlit(pdest,psrc,dstw,dsth,isrcx,isrcy,idsdx,idtdx,idsdy,idtdy,0,0,sr,sb,src_span,dest_span,ia,mode&LICE_BLIT_FILTER_MASK,blitfunc); +#endif +} + +#endif + + +void LICE_Clear(LICE_IBitmap *dest, LICE_pixel color) +{ + if (!dest) return; + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_CLEAR_ACCEL)) + { + if (dest->Extended(LICE_EXT_CLEAR_ACCEL, &color)) return; + } +#endif + + LICE_pixel *p=dest->getBits(); + int h=dest->getHeight(); + int w=dest->getWidth(); + const int sp=dest->getRowSpan(); + + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + } + + if (!p || w<1 || h<1 || !sp) return; + + while (h-->0) + { + int n=w; + while (n--) *p++ = color; + p+=sp-w; + } +} + + + + +void LICE_MultiplyAddRect(LICE_IBitmap *dest, int x, int y, int w, int h, + float rsc, float gsc, float bsc, float asc, + float radd, float gadd, float badd, float aadd) +{ + if (!dest) return; + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + DO_RECT_SC(0) + + if (x<0) { w+=x; x=0; } + if (y<0) { h+=y; y=0; } + + LICE_pixel *p=dest->getBits(); + const int sp=dest->getRowSpan(); + if (!p || !sp || w<1 || h < 1 || x >= destbm_w || y >= destbm_h) return; + + if (w>destbm_w-x) w=destbm_w-x; + if (h>destbm_h-y) h=destbm_h-y; + + if (dest->isFlipped()) + { + p+=(destbm_h - y - h)*sp; + } + else + { + p+=sp*y; + } + + p += x; + + int ir=(int)(rsc*256.0); + int ig=(int)(gsc*256.0); + int ib=(int)(bsc*256.0); + int ia=(int)(asc*256.0); + int ir2=(int)(radd*256.0); + int ig2=(int)(gadd*256.0); + int ib2=(int)(badd*256.0); + int ia2=(int)(aadd*256.0); + + while (h-->0) + { + int n=w; + while (n--) + { + LICE_pixel_chan *ptr=(LICE_pixel_chan *)p++; + _LICE_MakePixelClamp(ptr,(ptr[LICE_PIXEL_R]*ir+ir2)>>8, + (ptr[LICE_PIXEL_G]*ig+ig2)>>8, + (ptr[LICE_PIXEL_B]*ib+ib2)>>8, + (ptr[LICE_PIXEL_A]*ia+ia2)>>8); + } + p+=sp-w; + } +} + +void LICE_ProcessRect(LICE_IBitmap *dest, int x, int y, int w, int h, void (*procFunc)(LICE_pixel *p, void *parm), void *parm) +{ + if (!dest||!procFunc) return; + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + DO_RECT_SC(0) + + if (x<0) { w+=x; x=0; } + if (y<0) { h+=y; y=0; } + + LICE_pixel *p=dest->getBits(); + const int sp=dest->getRowSpan(); + + if (!p || !sp || w<1 || h < 1 || x >= destbm_w || y >= destbm_h) return; + + if (w>destbm_w-x) w=destbm_w-x; + if (h>destbm_h-y) h=destbm_h-y; + + if (dest->isFlipped()) + { + p+=(destbm_h - y - h)*sp; + } + else + { + p+=sp*y; + } + + p += x; + + while (h--) + { + LICE_pixel *pout=p; + int n=w; + while (n-->0) procFunc(pout++,parm); + p+=sp; + } + + +} + +void LICE_FillRect(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel color, float alpha, int mode) +{ + if (!dest) return; + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + DO_RECT_SC(mode) + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_FILLRECT_ACCEL)) + { + LICE_Ext_FillRect_acceldata data(x, y, w, h, color, alpha, mode); + if (dest->Extended(LICE_EXT_FILLRECT_ACCEL, &data)) return; + } +#endif + + if (mode & LICE_BLIT_USE_ALPHA) alpha *= LICE_GETA(color)/255.0f; + + LICE_pixel *p=dest->getBits(); + const int sp=dest->getRowSpan(); + + if (x<0) { w+=x; x=0; } + if (y<0) { h+=y; y=0; } + if (!alpha || !p || !sp || w<1 || h < 1 || x >= destbm_w || y >= destbm_h) return; + + if (w>destbm_w-x) w=destbm_w-x; + if (h>destbm_h-y) h=destbm_h-y; + + if (dest->isFlipped()) + { + p+=(destbm_h - y - h)*sp; + } + else + { + p+=sp*y; + } + + p += x; + + const int ia=(int)(alpha*256.0); + // copy, alpha=1, alpha=0.5, 0.25, 0.75 optimizations + if ((mode&LICE_BLIT_MODE_MASK)==LICE_BLIT_MODE_COPY) + { + if (ia==256) + { + _LICE_Template_Blit0<_LICE_CombinePixelsClobberFAST>::solidBlitFAST(p,w,h,color,sp); + return; + } + if (ia==128) + { + // we use _LICE_CombinePixelsHalfMix2 because we pre-halve color here + _LICE_Template_Blit0<_LICE_CombinePixelsHalfMix2FAST>::solidBlitFAST(p,w,h,(color>>1)&0x7f7f7f7f,sp); + return; + } + if (ia==64) + { + _LICE_Template_Blit0<_LICE_CombinePixelsQuarterMix2FAST>::solidBlitFAST(p,w,h,(color>>2)&0x3f3f3f3f,sp); + return; + } + if (ia==192) + { + _LICE_Template_Blit0<_LICE_CombinePixelsThreeQuarterMix2FAST>::solidBlitFAST(p,w,h, + ((color>>1)&0x7f7f7f7f)+((color>>2)&0x3f3f3f3f),sp); + return; + } + } + +#ifdef LICE_FAVOR_SIZE_EXTREME + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; +#else + #define __LICE__ACTION(comb) _LICE_Template_Blit1::solidBlit((LICE_pixel_chan*)p,w,h,LICE_GETR(color),LICE_GETG(color),LICE_GETB(color),LICE_GETA(color),ia,sp*sizeof(LICE_pixel)) +#endif + + // we use __LICE_ACTION_NOSRCALPHA even though __LICE_ACTION_CONSTANTALPHA + // is valid, sinc we optimized the 1.0f/0.5f cases above + __LICE_ACTION_NOSRCALPHA(mode,ia,false); + #undef __LICE__ACTION + +#ifdef LICE_FAVOR_SIZE_EXTREME + if (blitfunc) _LICE_Template_Blit1::solidBlit((LICE_pixel_chan*)p,w,h,LICE_GETR(color),LICE_GETG(color),LICE_GETB(color),LICE_GETA(color),ia,sp*sizeof(LICE_pixel),blitfunc); +#endif +} + +void LICE_ClearRect(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel mask, LICE_pixel orbits) +{ + if (!dest) return; + LICE_pixel *p=dest->getBits(); + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + DO_RECT_SC(0) + + if (x<0) { w+=x; x=0; } + if (y<0) { h+=y; y=0; } + + const int sp=dest->getRowSpan(); + if (!p || !sp || w<1 || h < 1 || x >= destbm_w || y >= destbm_h) return; + + if (w>destbm_w-x) w=destbm_w-x; + if (h>destbm_h-y) h=destbm_h-y; + + if (dest->isFlipped()) + { + p+=(destbm_h - y - h)*sp; + } + else p+=sp*y; + + p += x; + while (h-->0) + { + int n=w; + while (n--) + { + *p = (*p&mask)|orbits; + p++; + } + p+=sp-w; + } +} + + +LICE_pixel LICE_GetPixel(LICE_IBitmap *bm, int x, int y) +{ + if (!bm) return 0; + + int w = bm->getWidth(), h = bm->getHeight(); + const int __sc = (int)bm->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + __LICE_SC(x); + __LICE_SC(y); + } +#ifndef DISABLE_LICE_EXTENSIONS + if (bm->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_GETPIXEL_ACCEL)) + { + LICE_Ext_GetPixel_acceldata data(x, y); + if (bm->Extended(LICE_EXT_GETPIXEL_ACCEL, &data)) return data.px; + } +#endif + + const LICE_pixel *px; + + if (!(px=bm->getBits()) || x < 0 || y < 0 || x >= w || y>= h) return 0; + if (bm->isFlipped()) return px[(h-1-y) * bm->getRowSpan() + x]; + return px[y * bm->getRowSpan() + x]; +} + +void LICE_PutPixel(LICE_IBitmap *bm, int x, int y, LICE_pixel color, float alpha, int mode) +{ + if (!bm) return; + + const int __sc = (int)bm->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + if (!IGNORE_SCALING(mode)) + { + LICE_FillRect(bm,x,y,1,1,color,alpha,mode); + return; + } + + } + + int w = bm->getWidth(), h = bm->getHeight(); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + } +#ifndef DISABLE_LICE_EXTENSIONS + if (bm->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_PUTPIXEL_ACCEL)) + { + LICE_Ext_PutPixel_acceldata data(x, y, color, alpha, mode); + if (bm->Extended(LICE_EXT_PUTPIXEL_ACCEL, &data)) return; + } +#endif + + LICE_pixel *px; + if (!(px=bm->getBits()) || x < 0 || y < 0 || x >= w || y >= h) return; + + if (bm->isFlipped()) px+=x+(h-1-y)*bm->getRowSpan(); + else px+=x+y*bm->getRowSpan(); + + int ia = (int)(alpha * 256.0f); + if ((mode&LICE_BLIT_MODE_MASK)==LICE_BLIT_MODE_COPY) + { + if (ia==256) + { + *px = color; + return; + } + if (ia==128) + { + *px = ((*px>>1)&0x7f7f7f7f) + ((color>>1)&0x7f7f7f7f); + return; + } + if (ia==64) + { + *px = ((*px>>1)&0x7f7f7f7f) + ((*px>>2)&0x3f3f3f3f) + ((color>>2)&0x3f3f3f3f); + return; + } + if (ia==192) + { + *px = ((*px>>2)&0x3f3f3f3f) + ((color>>1)&0x7f7f7f7f) + ((color>>2)&0x3f3f3f3f); + return; + } + } +#define __LICE__ACTION(comb) comb::doPix((LICE_pixel_chan *)px, LICE_GETR(color),LICE_GETG(color),LICE_GETB(color),LICE_GETA(color), ia) + __LICE_ACTION_NOSRCALPHA(mode,ia,false); +#undef __LICE__ACTION +} + + +#ifndef LICE_NO_BLIT_SUPPORT + +template class LICE_TransformBlit_class +{ + public: + static void blit(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + const T *srcpoints, int div_w, int div_h, // srcpoints coords should be div_w*div_h*2 long, and be in source image coordinates + float alpha, int mode) +{ + if (!dest || !src || dstw<1 || dsth<1 || div_w<2 || div_h<2) return; + + int cypos=dsty; + double ypos=dsty; + double dxpos=dstw/(float)(div_w-1); + double dypos=dsth/(float)(div_h-1); + int y; + const T *curpoints=srcpoints; + for (y = 0; y < div_h-1; y ++) + { + int nypos=(int)((ypos+=dypos) + 0.5); + int x; + double xpos=dstx; + int cxpos=dstx; + + if (nypos != cypos) + { + double iy=1.0/(double)(nypos-cypos); + for (x = 0; x < div_w-1; x ++) + { + int nxpos=(int) ((xpos+=dxpos) + 0.5); + if (nxpos != cxpos) + { + int offs=x*2; + double sx=curpoints[offs]; + double sy=curpoints[offs+1]; + double sw=curpoints[offs+2]-sx; + double sh=curpoints[offs+3]-sy; + + offs+=div_w*2; + double sxdiff=curpoints[offs]-sx; + double sydiff=curpoints[offs+1]-sy; + double sw3=curpoints[offs+2]-curpoints[offs]; + double sh3=curpoints[offs+3]-curpoints[offs+1]; + + double ix=1.0/(double)(nxpos-cxpos); + double dsdx=sw*ix; + double dtdx=sh*ix; + double dsdx2=sw3*ix; + double dtdx2=sh3*ix; + double dsdy=sxdiff*iy; + double dtdy=sydiff*iy; + double dsdxdy = (dsdx2-dsdx)*iy; + double dtdxdy = (dtdx2-dtdx)*iy; + + LICE_DeltaBlit(dest,src,cxpos,cypos,nxpos-cxpos,nypos-cypos, + (float)sx,(float)sy,(float)sw,(float)sh, + dsdx,dtdx,dsdy,dtdy,dsdxdy,dtdxdy,false,alpha,mode); + } + + cxpos=nxpos; + } + } + curpoints+=div_w*2; + cypos=nypos; + } +} +}; + +template class LICE_TransformBlitAlpha_class +{ + public: + static void blit(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + const T *srcpoints, int div_w, int div_h, // srcpoints coords should be div_w*div_h*2 long, and be in source image coordinates + int mode) +{ + if (!dest || !src || dstw<1 || dsth<1 || div_w<2 || div_h<2) return; + + int cypos=dsty; + double ypos=dsty; + double dxpos=dstw/(float)(div_w-1); + double dypos=dsth/(float)(div_h-1); + int y; + const T *curpoints=srcpoints; + for (y = 0; y < div_h-1; y ++) + { + int nypos=(int)((ypos+=dypos)+0.5); + int x; + double xpos=dstx; + int cxpos=dstx; + + if (nypos != cypos) + { + double iy=1.0/(double)(nypos-cypos); + for (x = 0; x < div_w-1; x ++) + { + int nxpos=(int) ((xpos+=dxpos)+0.5); + if (nxpos != cxpos) + { + int offs=x*3; + double sx=curpoints[offs]; + double sy=curpoints[offs+1]; + double sa=curpoints[offs+2]; + double sw=curpoints[offs+3]-sx; + double sh=curpoints[offs+4]-sy; + double sa2 = curpoints[offs+5]-sa; + + offs+=div_w*3; + double sxdiff=curpoints[offs]-sx; + double sydiff=curpoints[offs+1]-sy; + double sadiff=curpoints[offs+2]-sa; + double sw3=curpoints[offs+3]-curpoints[offs]; + double sh3=curpoints[offs+4]-curpoints[offs+1]; + double sa3=curpoints[offs+5]-curpoints[offs+2]; + + double ix=1.0/(double)(nxpos-cxpos); + double dsdx=sw*ix; + double dtdx=sh*ix; + double dadx=sa2*ix; + double dsdx2=sw3*ix; + double dtdx2=sh3*ix; + double dadx2=sa3*ix; + double dsdy=sxdiff*iy; + double dtdy=sydiff*iy; + double dady=sadiff*iy; + double dsdxdy = (dsdx2-dsdx)*iy; + double dtdxdy = (dtdx2-dtdx)*iy; + double dadxdy = (dadx2-dadx)*iy; + + LICE_DeltaBlitAlpha(dest,src,cxpos,cypos,nxpos-cxpos,nypos-cypos, + (float)sx,(float)sy,(float)sw,(float)sh, + dsdx,dtdx,dsdy,dtdy,dsdxdy,dtdxdy,false,sa,mode,dadx,dady,dadxdy); + } + + cxpos=nxpos; + } + } + curpoints+=div_w*3; + cypos=nypos; + } +} +}; + +void LICE_TransformBlit(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + const float *srcpoints, int div_w, int div_h, // srcpoints coords should be div_w*div_h*2 long, and be in source image coordinates + float alpha, int mode) +{ + LICE_TransformBlit_class::blit(dest,src,dstx,dsty,dstw,dsth,srcpoints,div_w,div_h,alpha,mode); +} +void LICE_TransformBlit2(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + const double *srcpoints, int div_w, int div_h, // srcpoints coords should be div_w*div_h*2 long, and be in source image coordinates + float alpha, int mode) +{ + LICE_TransformBlit_class::blit(dest,src,dstx,dsty,dstw,dsth,srcpoints,div_w,div_h,alpha,mode); +} + + +void LICE_TransformBlit2Alpha(LICE_IBitmap *dest, LICE_IBitmap *src, + int dstx, int dsty, int dstw, int dsth, + const double *srcpoints, int div_w, int div_h, // srcpoints coords should be div_w*div_h*3 long, and be in source image coordinates+alpha + int mode) +{ + LICE_TransformBlitAlpha_class::blit(dest,src,dstx,dsty,dstw,dsth,srcpoints,div_w,div_h,mode); +} +#endif + +#ifndef LICE_NO_MISC_SUPPORT + +void LICE_SetAlphaFromColorMask(LICE_IBitmap *dest, LICE_pixel color) +{ + if (!dest) return; + LICE_pixel *p=dest->getBits(); + int h=dest->getHeight(); + int w=dest->getWidth(); + int sp=dest->getRowSpan(); + if (!p || w<1 || h<1 || sp<1) return; + + while (h-->0) + { + int n=w; + while (n--) + { + if ((*p&LICE_RGBA(255,255,255,0)) == color) *p&=LICE_RGBA(255,255,255,0); + else *p|=LICE_RGBA(0,0,0,255); + p++; + } + p+=sp-w; + } +} + + +void LICE_SimpleFill(LICE_IBitmap *dest, int x, int y, LICE_pixel newcolor, + LICE_pixel comparemask, + LICE_pixel keepmask) +{ + if (!dest) return; + int dw=dest->getWidth(); + int dh=dest->getHeight(); + int rowsize=dest->getRowSpan(); + if (x<0||x>=dw||y<0||y>=dh) return; + LICE_pixel *ptr=dest->getBits(); + if (!ptr) return; + ptr += rowsize*y; + + LICE_pixel compval=ptr[x]&comparemask; + int ay; + for (ay=y;ay=0) + { + if ((ptr[ax]&comparemask)!=compval) break; + ptr[ax]=(ptr[ax]&keepmask)|newcolor; + } + + ptr+=rowsize; + } + ptr =dest->getBits()+rowsize*y; + + ay=y; + while (--ay>=0) + { + ptr-=rowsize; + if ((ptr[x]&comparemask)!=compval) break; + ptr[x]=(ptr[x]&keepmask)|newcolor; + + int ax; + for(ax=x+1;ax=0) + { + if ((ptr[ax]&comparemask)!=compval) break; + ptr[ax]=(ptr[ax]&keepmask)|newcolor; + } + } +} + +// VS6 instantiates this wrong as a template function, needs to be a template class +template class GlyphDrawImpl +{ +public: + static void DrawGlyph(const LICE_pixel_chan* srcalpha, LICE_pixel* destpx, int src_w, int src_h, LICE_pixel color, int span, int src_span, int aa) + { + const int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + int xi, yi; + for (yi = 0; yi < src_h; ++yi, srcalpha += src_span, destpx += span) { + const LICE_pixel_chan* tsrc = srcalpha; + LICE_pixel* tdest = destpx; + for (xi = 0; xi < src_w; ++xi, ++tsrc, ++tdest) { + const LICE_pixel_chan v = *tsrc; + if (v) { // glyphs should be expected to have a lot of "holes" + COMBFUNC::doPix((LICE_pixel_chan*) tdest, r, g, b, a, v*aa/256); + } + } + } + } + static void DrawGlyphScale(const LICE_pixel_chan* srcalpha, LICE_pixel* destpx, int src_w, int src_h, LICE_pixel color, int span, int src_span, int aa, int scale) + { + const int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + int xi, yi; + int ysum = 0; + for (yi = 0; yi < src_h; ++yi, srcalpha += src_span) { + ysum += scale; + while (ysum > 255) + { + const LICE_pixel_chan* tsrc = srcalpha; + LICE_pixel* tdest = destpx; + ysum -= 256; + destpx += span; + int sum = 0; + for (xi = 0; xi < src_w; ++xi, ++tsrc) { + const LICE_pixel_chan v = *tsrc; + sum += scale; + if (v) { // glyphs should be expected to have a lot of "holes" + while (sum>255) + { + COMBFUNC::doPix((LICE_pixel_chan*) tdest, r, g, b, a, v*aa/256); + tdest++; + sum -= 256; + } + } + else + { + tdest += sum/256; + sum &= 255; + } + } + } + } + } + static void DrawGlyphMono(const unsigned char * srcalpha, LICE_pixel* destpx, int src_w, int src_h, LICE_pixel color, int span, int src_span, int aa) + { + const int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + int xi, yi; + for (yi = 0; yi < src_h; ++yi, srcalpha += src_span, destpx += span) { + const unsigned char *tsrc = srcalpha; + LICE_pixel* tdest = destpx; + unsigned char cv=0; + for (xi = 0; xi < src_w; ++xi, ++tdest) { + if (!(xi&7)) cv = *tsrc++; + const LICE_pixel_chan v = (cv&128)?255:0; + cv<<=1; + if (v) { // glyphs should be expected to have a lot of "holes" + COMBFUNC::doPix((LICE_pixel_chan*) tdest, r, g, b, a, v*aa/256); + } + } + } + } + static void DrawGlyphMonoScale(const unsigned char * srcalpha, LICE_pixel* destpx, int src_w, int src_h, LICE_pixel color, int span, int src_span, int aa, int scale) + { + const int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + int xi, yi; + int ysum=0; + for (yi = 0; yi < src_h; ++yi, srcalpha += src_span) { + ysum += scale; + while (ysum > 255) + { + const unsigned char *tsrc = srcalpha; + LICE_pixel* tdest = destpx; + unsigned char cv=0; + int sum=0; + ysum -= 256; + destpx++; + for (xi = 0; xi < src_w; ++xi) { + if (!(xi&7)) cv = *tsrc++; + const LICE_pixel_chan v = (cv&128)?255:0; + cv<<=1; + sum += scale; + if (v) { // glyphs should be expected to have a lot of "holes" + while (sum > 255) + { + COMBFUNC::doPix((LICE_pixel_chan*) tdest, r, g, b, a, v*aa/256); + tdest++; + sum -= 256; + } + } + else + { + tdest += sum/256; + sum &= 255; + } + } + } + } + } +}; + +void LICE_DrawGlyphEx(LICE_IBitmap* dest, int x, int y, LICE_pixel color, const LICE_pixel_chan* alphas, int glyph_w, int glyph_span, int glyph_h, float alpha, int mode) +{ + if (!dest) return; + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_DRAWGLYPH_ACCEL) && glyph_w == glyph_span) + { + LICE_Ext_DrawGlyph_acceldata data(x, y, color, alphas, glyph_w, glyph_h, alpha, mode); + if (dest->Extended(LICE_EXT_DRAWGLYPH_ACCEL, &data)) return; + } +#endif + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0 && IGNORE_SCALING(mode)) + { + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + } + + if (glyph_span < 0) alphas += -glyph_span * (glyph_h-1); + + const int ia= (int)(alpha*256.0f); + + int src_x = 0, src_y = 0, src_w = glyph_w, src_h = glyph_h; + if (x <= -src_w || y <= -src_h) return; + + if (x < 0) { + src_x -= x; + if (src_x < 0) return; // overflow + src_w += x; + x = 0; + } + if (y < 0) { + src_y -= y; + if (src_y < 0) return; // overflow + src_h += y; + y = 0; + } + + if (src_w < 0 || src_h < 0 || x >= destbm_w || y >= destbm_h) return; + + if (src_h > destbm_h-y) src_h = destbm_h-y; + if (src_w > destbm_w-x) src_w = destbm_w-x; + + if (src_w < 1 || src_h < 1) return; + + if (__sc>0 && !IGNORE_SCALING(mode)) + { + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + __LICE_SC(x); + __LICE_SC(y); + } + + LICE_pixel* destpx = dest->getBits(); + int span = dest->getRowSpan(); + if (dest->isFlipped()) { + destpx += (destbm_h-y-1)*span+x; + span = -span; + } + else { + destpx += y*dest->getRowSpan()+x; + } + + const LICE_pixel_chan* srcalpha = alphas+src_y*glyph_span+src_x; + + if (__sc>0 && !IGNORE_SCALING(mode)) + { +#define __LICE__ACTION(COMBFUNC) GlyphDrawImpl::DrawGlyphScale(srcalpha,destpx, src_w, src_h, color,span,glyph_span,ia,__sc) + __LICE_ACTION_NOSRCALPHA(mode, ia, false); +#undef __LICE__ACTION + } + else + { +#define __LICE__ACTION(COMBFUNC) GlyphDrawImpl::DrawGlyph(srcalpha,destpx, src_w, src_h, color,span,glyph_span,ia) + __LICE_ACTION_NOSRCALPHA(mode, ia, false); +#undef __LICE__ACTION + } +} + +void LICE_DrawGlyph(LICE_IBitmap* dest, int x, int y, LICE_pixel color, const LICE_pixel_chan* alphas, int glyph_w, int glyph_h, float alpha, int mode) +{ + LICE_DrawGlyphEx(dest,x,y,color,alphas,glyph_w,glyph_w,glyph_h,alpha,mode); +} + + +void LICE_DrawMonoGlyph(LICE_IBitmap* dest, int x, int y, LICE_pixel color, const unsigned char *alphabits, int glyph_w, int glyph_span, int glyph_h, float alpha, int mode) +{ + if (!dest) return; + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0 && IGNORE_SCALING(mode)) + { + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + } + + if (glyph_span < 0) alphabits += -glyph_span * (glyph_h-1); + + const int ia= (int)(alpha*256.0f); + + int src_x = 0, src_y = 0, src_w = glyph_w, src_h = glyph_h; + if (x <= -src_w || y <= -src_h) return; + + if (x < 0) { + src_x -= x; + src_w += x; + x = 0; + } + if (y < 0) { + src_y -= y; + src_h += y; + y = 0; + } + + if (src_w < 0 || src_h < 0 || x >= destbm_w || y >= destbm_h) return; + + if (src_h > destbm_h-y) src_h = destbm_h-y; + if (src_w > destbm_w-x) src_w = destbm_w-x; + + if (src_w < 1 || src_h < 1) return; + + if (__sc>0 && !IGNORE_SCALING(mode)) + { + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + __LICE_SC(x); + __LICE_SC(y); + } + + LICE_pixel* destpx = dest->getBits(); + int span = dest->getRowSpan(); + if (dest->isFlipped()) { + destpx += (destbm_h-y-1)*span+x; + span = -span; + } + else { + destpx += y*dest->getRowSpan()+x; + } + + const unsigned char * srcalpha = alphabits+src_y*glyph_span+src_x; + if (__sc>0 && !IGNORE_SCALING(mode)) + { +#define __LICE__ACTION(COMBFUNC) GlyphDrawImpl::DrawGlyphMonoScale(srcalpha,destpx, src_w, src_h, color,span,glyph_span,ia,__sc) + __LICE_ACTION_NOSRCALPHA(mode, ia, false); +#undef __LICE__ACTION + } + else + { +#define __LICE__ACTION(COMBFUNC) GlyphDrawImpl::DrawGlyphMono(srcalpha,destpx, src_w, src_h, color,span,glyph_span,ia) + __LICE_ACTION_NOSRCALPHA(mode, ia, false); +#undef __LICE__ACTION + } +} + +void LICE_HalveBlitAA(LICE_IBitmap *dest, LICE_IBitmap *src) +{ + if (!dest||!src) return; + int w = dest->getWidth(); + if (w > src->getWidth()/2) w=src->getWidth()/2; + int h = dest->getHeight(); + if (h > src->getHeight()/2) h=src->getHeight()/2; + int src_span = src->getRowSpan(); + int dest_span = dest->getRowSpan(); + const LICE_pixel *srcptr = src->getBits(); + LICE_pixel *destptr = dest->getBits(); + + while (h--) + { + const LICE_pixel *sp = srcptr; + const LICE_pixel *sp2 = srcptr+src_span; + LICE_pixel *dp = destptr; + int x=w/2; + + // this is begging for SSE intrinsics, but we never use this function so leave it as-is :) + + // perhaps we should use more precision rather than chopping each src pixel to 6 bits, but oh well + while (x--) // unroll 2px at a time, about 5% faster on core2 and ICC + { + int r1=((sp[0]>>2)&0x3f3f3f3f); + int r2=((sp[2]>>2)&0x3f3f3f3f); + r1 += ((sp[1]>>2)&0x3f3f3f3f); + r2 += ((sp[3]>>2)&0x3f3f3f3f); + dp[0] = r1 + + ((sp2[0]>>2)&0x3f3f3f3f) + + ((sp2[1]>>2)&0x3f3f3f3f); + dp[1] = r2 + + ((sp2[2]>>2)&0x3f3f3f3f) + + ((sp2[3]>>2)&0x3f3f3f3f); + sp+=4; + sp2+=4; + dp+=2; + } + if (w&1) + { + *dp = + ((sp[0]>>2)&0x3f3f3f3f) + + ((sp[1]>>2)&0x3f3f3f3f) + + ((sp2[0]>>2)&0x3f3f3f3f) + + ((sp2[1]>>2)&0x3f3f3f3f); + } + srcptr+=src_span*2; + destptr+=dest_span; + } +} + +#endif // LICE_NO_MISC_SUPPORT + +int LICE_BitmapCmp(LICE_IBitmap* a, LICE_IBitmap* b, int *coordsOut) +{ + return LICE_BitmapCmpEx(a,b,LICE_RGBA(255,255,255,255),coordsOut); +} + +int LICE_BitmapCmpEx(LICE_IBitmap* a, LICE_IBitmap* b, LICE_pixel mask, int *coordsOut) +{ + if (!a || !b) { + if (!a && b) return -1; + if (a && !b) return 1; + return 0; + } + + int aw = a->getWidth(), bw = b->getWidth(); + if (aw != bw) return bw-aw; + int ah = a->getHeight(), bh = b->getHeight(); + if (ah != bh) return bh-ah; + + //coordsOut + const LICE_pixel *px1 = a->getBits(); + const LICE_pixel *px2 = b->getBits(); + int span1 = a->getRowSpan(); + int span2 = b->getRowSpan(); + if (a->isFlipped()) + { + px1+=span1*(ah-1); + span1=-span1; + } + if (b->isFlipped()) + { + px2+=span2*(ah-1); + span2=-span2; + } + + int y; + if (!coordsOut) + { + if (mask == (LICE_pixel)LICE_RGBA(255,255,255,255)) + for (y=0; y < ah; y ++) + { + const int dv = memcmp(px1,px2,aw*sizeof(LICE_pixel)); + if (dv) return dv; + px1+=span1; + px2+=span2; + } + else + for (y=0; y < ah; y ++) + { + const LICE_pixel *ptr1 = px1, *ptr2 = px2; + int x=aw; + while (x--) + if ((*ptr1++ ^ *ptr2++) & mask) return true; + px1+=span1; + px2+=span2; + } + } + else + { + int x; + + // find first row that differs + for (y=0; y < ah; y ++) + { + // check left side + for (x=0;x=ah) + { + memset(coordsOut,0,4*sizeof(int)); + return 0; // no differences + } + + int miny=y; + int minx=x; + // scan right edge of top differing row + for (x=aw-1;x>minx && !((px1[x]^px2[x])&mask);x--); + int maxx=x; + + // find last row that differs + px1+=span1 * (ah-1-y); + px2+=span2 * (ah-1-y); + for (y = ah-1; y > miny; y --) + { + // check left side + for (x=0;x miny) + { + // scan right edge of bottom row that differs + for (x=aw-1;x>maxx && !((px1[x]^px2[x])&mask);x--); + maxx=x; + } + + + // find min/max x that differ + px1+=span1 * (miny+1-y); + px2+=span2 * (miny+1-y); + for (y=miny+1;y0 || maxxmaxx && !((px1[x]^px2[x])&mask);x--); + maxx=x; + + px1+=span1; + px2+=span2; + } + + coordsOut[0]=minx; + coordsOut[1]=miny; + coordsOut[2]=maxx-minx+1; + coordsOut[3]=maxy-miny+1; + + return 1; + } + + return 0; +} + +unsigned short _LICE_RGB2HSV_invtab[256]={ // 65536/idx - 1 + 0, 0xffff, 0x7fff, 0x5554, 0x3fff, 0x3332, 0x2aa9, 0x2491, + 0x1fff, 0x1c70, 0x1998, 0x1744, 0x1554, 0x13b0, 0x1248, 0x1110, + 0x0fff, 0x0f0e, 0x0e37, 0x0d78, 0x0ccb, 0x0c2f, 0x0ba1, 0x0b20, + 0x0aa9, 0x0a3c, 0x09d7, 0x097a, 0x0923, 0x08d2, 0x0887, 0x0841, + 0x07ff, 0x07c0, 0x0786, 0x074f, 0x071b, 0x06ea, 0x06bb, 0x068f, + 0x0665, 0x063d, 0x0617, 0x05f3, 0x05d0, 0x05af, 0x058f, 0x0571, + 0x0554, 0x0538, 0x051d, 0x0504, 0x04eb, 0x04d3, 0x04bc, 0x04a6, + 0x0491, 0x047c, 0x0468, 0x0455, 0x0443, 0x0431, 0x0420, 0x040f, + 0x03ff, 0x03ef, 0x03df, 0x03d1, 0x03c2, 0x03b4, 0x03a7, 0x039a, + 0x038d, 0x0380, 0x0374, 0x0368, 0x035d, 0x0352, 0x0347, 0x033c, + 0x0332, 0x0328, 0x031e, 0x0314, 0x030b, 0x0302, 0x02f9, 0x02f0, + 0x02e7, 0x02df, 0x02d7, 0x02cf, 0x02c7, 0x02bf, 0x02b8, 0x02b0, + 0x02a9, 0x02a2, 0x029b, 0x0294, 0x028e, 0x0287, 0x0281, 0x027b, + 0x0275, 0x026f, 0x0269, 0x0263, 0x025d, 0x0258, 0x0252, 0x024d, + 0x0248, 0x0242, 0x023d, 0x0238, 0x0233, 0x022f, 0x022a, 0x0225, + 0x0221, 0x021c, 0x0218, 0x0213, 0x020f, 0x020b, 0x0207, 0x0203, + 0x01ff, 0x01fb, 0x01f7, 0x01f3, 0x01ef, 0x01eb, 0x01e8, 0x01e4, + 0x01e0, 0x01dd, 0x01d9, 0x01d6, 0x01d3, 0x01cf, 0x01cc, 0x01c9, + 0x01c6, 0x01c2, 0x01bf, 0x01bc, 0x01b9, 0x01b6, 0x01b3, 0x01b1, + 0x01ae, 0x01ab, 0x01a8, 0x01a5, 0x01a3, 0x01a0, 0x019d, 0x019b, + 0x0198, 0x0196, 0x0193, 0x0191, 0x018e, 0x018c, 0x0189, 0x0187, + 0x0185, 0x0182, 0x0180, 0x017e, 0x017c, 0x0179, 0x0177, 0x0175, + 0x0173, 0x0171, 0x016f, 0x016d, 0x016b, 0x0169, 0x0167, 0x0165, + 0x0163, 0x0161, 0x015f, 0x015d, 0x015b, 0x0159, 0x0157, 0x0156, + 0x0154, 0x0152, 0x0150, 0x014f, 0x014d, 0x014b, 0x0149, 0x0148, + 0x0146, 0x0145, 0x0143, 0x0141, 0x0140, 0x013e, 0x013d, 0x013b, + 0x013a, 0x0138, 0x0137, 0x0135, 0x0134, 0x0132, 0x0131, 0x012f, + 0x012e, 0x012d, 0x012b, 0x012a, 0x0128, 0x0127, 0x0126, 0x0124, + 0x0123, 0x0122, 0x0120, 0x011f, 0x011e, 0x011d, 0x011b, 0x011a, + 0x0119, 0x0118, 0x0117, 0x0115, 0x0114, 0x0113, 0x0112, 0x0111, + 0x0110, 0x010e, 0x010d, 0x010c, 0x010b, 0x010a, 0x0109, 0x0108, + 0x0107, 0x0106, 0x0105, 0x0104, 0x0103, 0x0102, 0x0101, 0x0100, +}; + + +#endif//__LICE_CPP_IMPLEMENTED__ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice.h new file mode 100644 index 0000000..8e4c927 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice.h @@ -0,0 +1,639 @@ +#ifndef _LICE_H +#define _LICE_H + +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + + Copyright (C) 2007 and later, Cockos Incorporated + Portions Copyright (C) 2007 "schwa" + + 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. + +*/ + +#ifdef _WIN32 +#include +#else +#include "../swell/swell-types.h" // use SWELL on other systems +#endif + + +// one of these can be defined in your project if you choose: +//#define LICE_FAVOR_SPEED // optimizes some stuff that doesnt seem to benefit much (like LICE_DeltaBlit/LICE_RotatedBlit/LICE_TransformBlit) +// (nothing) default probably good overall +//#define LICE_FAVOR_SIZE // reduces code size of normal/scaled blit functions +//#define LICE_FAVOR_SIZE_EXTREME // same as LICE_FAVOR_SIZE w/ smaller gains with bigger perf penalties (solid fills etc) + +#ifdef LICE_FAVOR_SPEED + #ifdef LICE_FAVOR_SIZE_EXTREME + #undef LICE_FAVOR_SIZE_EXTREME + #endif + #ifdef LICE_FAVOR_SIZE + #undef LICE_FAVOR_SIZE + #endif +#endif +#if defined(LICE_FAVOR_SIZE_EXTREME) && !defined(LICE_FAVOR_SIZE) +#define LICE_FAVOR_SIZE +#endif + + +typedef unsigned int LICE_pixel; +typedef unsigned char LICE_pixel_chan; + +#define LICE_RGBA(r,g,b,a) (((b)&0xff)|(((g)&0xff)<<8)|(((r)&0xff)<<16)|(((a)&0xff)<<24)) +#define LICE_GETB(v) ((v)&0xff) +#define LICE_GETG(v) (((v)>>8)&0xff) +#define LICE_GETR(v) (((v)>>16)&0xff) +#define LICE_GETA(v) (((v)>>24)&0xff) + +#if defined(__APPLE__) && defined(__ppc__) +#define LICE_PIXEL_A 0 +#define LICE_PIXEL_R 1 +#define LICE_PIXEL_G 2 +#define LICE_PIXEL_B 3 +#else +#define LICE_PIXEL_B 0 +#define LICE_PIXEL_G 1 +#define LICE_PIXEL_R 2 +#define LICE_PIXEL_A 3 +#endif + + +static inline LICE_pixel LICE_RGBA_FROMNATIVE(LICE_pixel col, int alpha=0) +{ + return LICE_RGBA(GetRValue(col),GetGValue(col),GetBValue(col),alpha); +} + +// bitmap interface, and some built-in types (memory bitmap and system bitmap) + +class LICE_IBitmap +{ +public: + virtual ~LICE_IBitmap() { } + + virtual LICE_pixel *getBits()=0; + virtual int getWidth()=0; + virtual int getHeight()=0; + virtual int getRowSpan()=0; // includes any off-bitmap data + virtual bool isFlipped() { return false; } + virtual bool resize(int w, int h)=0; + + virtual HDC getDC() { return 0; } // only sysbitmaps have to implement this + + + virtual INT_PTR Extended(int id, void* data) { return 0; } +}; + +#define LICE_EXT_SET_SCALING 0x2000 // data = int *, scaling is .8 fixed point. returns true if supported. affects LICE_*() draw operations +#define LICE_EXT_GET_SCALING 0x2001 // data ignored, returns .8 fixed point, returns 0 if unscaled +#define LICE_EXT_SET_ADVISORY_SCALING 0x2002 // data = int *, scaling is .8 fixed point. returns true if supported. does not affect draw operations +#define LICE_EXT_GET_ADVISORY_SCALING 0x2003 // data ignored, returns .8 fixed point. returns 0 if unscaled +#define LICE_EXT_GET_ANY_SCALING 0x2004 // data ignored, returns .8 fixed point, 0 if unscaled + +#define LICE_MEMBITMAP_ALIGNAMT 63 + +class LICE_MemBitmap : public LICE_IBitmap +{ +public: + LICE_MemBitmap(int w=0, int h=0, unsigned int linealign=4); + virtual ~LICE_MemBitmap(); + + + // LICE_IBitmap interface + virtual LICE_pixel *getBits() + { + const UINT_PTR extra=LICE_MEMBITMAP_ALIGNAMT; + return (LICE_pixel *) (((UINT_PTR)m_fb + extra)&~extra); + } + virtual int getWidth() { return m_width; } + virtual int getHeight() { return m_height; } + virtual int getRowSpan() { return (m_width+m_linealign)&~m_linealign; } + virtual bool resize(int w, int h) { return __resize(w,h); } // returns TRUE if a resize occurred + + // todo: LICE_EXT_SET_SCALING ? + +private: + bool __resize(int w, int h); + LICE_pixel *m_fb; + int m_width, m_height; + int m_allocsize; + unsigned int m_linealign; +}; + +class LICE_SysBitmap : public LICE_IBitmap +{ +public: + LICE_SysBitmap(int w=0, int h=0); + virtual ~LICE_SysBitmap(); + + // LICE_IBitmap interface + virtual LICE_pixel *getBits() { return m_bits; } + virtual int getWidth() { return m_width; } + virtual int getHeight() { return m_height; } + virtual int getRowSpan() { return m_allocw; }; + virtual bool resize(int w, int h) { return __resize(w,h); } // returns TRUE if a resize occurred + + virtual INT_PTR Extended(int id, void* data) + { + switch (id) + { + case LICE_EXT_SET_ADVISORY_SCALING: + { + int sc = data && *(int*)data != 256 ? *(int *)data : 0; + if (sc < 0) sc = 0; + m_adv_scaling = sc; + } + return 1; + case LICE_EXT_SET_SCALING: + { + int sc = data && *(int*)data != 256 ? *(int *)data : 0; + if (sc < 0) sc = 0; + if (m_draw_scaling != sc) + { + const int tmp=m_width; + m_draw_scaling = sc; + m_width=0; + resize(tmp,m_height); + } + } + return 1; + case LICE_EXT_GET_SCALING: + return m_draw_scaling; + case LICE_EXT_GET_ADVISORY_SCALING: + return m_adv_scaling; + case LICE_EXT_GET_ANY_SCALING: + if (m_draw_scaling > 0) + { + if (m_adv_scaling > 0) + return (m_adv_scaling * m_draw_scaling) >> 8; + return m_draw_scaling; + } + return m_adv_scaling; + } + return 0; + } + + // sysbitmap specific calls + virtual HDC getDC() { return m_dc; } + + +private: + bool __resize(int w, int h); + int m_width, m_height; + + HDC m_dc; + LICE_pixel *m_bits; + int m_allocw, m_alloch; +#ifdef _WIN32 + HBITMAP m_bitmap; + HGDIOBJ m_oldbitmap; +#endif + int m_draw_scaling, m_adv_scaling; +}; + +class LICE_WrapperBitmap : public LICE_IBitmap +{ + public: + LICE_WrapperBitmap(LICE_pixel *buf, int w, int h, int span, bool flipped) + { + m_buf=buf; + m_w=w; + m_h=h; + m_span=span; + m_flipped=flipped; + } + virtual ~LICE_WrapperBitmap() {} + + virtual bool resize(int w, int h) { return false; } + virtual LICE_pixel *getBits() { return m_buf; } + virtual int getWidth() { return m_w; } + virtual int getHeight() { return m_h; } + virtual int getRowSpan() { return m_span; } + + virtual HDC getDC() { return NULL; } + virtual bool isFlipped() { return m_flipped; } + + + LICE_pixel *m_buf; + int m_w,m_h,m_span; + bool m_flipped; +}; + + +class LICE_SubBitmap : public LICE_IBitmap // note: you should only keep these around as long as they are needed, and don't resize the parent while this is allocated +{ + public: + LICE_SubBitmap(LICE_IBitmap *parent, int x, int y, int w, int h) + { + m_parent=parent; + if(x<0)x=0; + if(y<0)y=0; + m_x=x;m_y=y; + __resize(w,h); + } + virtual ~LICE_SubBitmap() { } + + virtual bool resize(int w, int h) { return __resize(w,h); } + + bool __resize(int w, int h) + { + m_w=0;m_h=0; + if (m_parent && m_x >= 0 && m_y >= 0 && m_x < m_parent->getWidth() && m_y < m_parent->getHeight()) + { + if (w > m_parent->getWidth()-m_x) w=m_parent->getWidth()-m_x; + if (h > m_parent->getHeight()-m_y) h=m_parent->getHeight()-m_y; + + m_w=w; + m_h=h; + } + + return true; + } + + virtual bool isFlipped() { return m_parent && m_parent->isFlipped(); } + + virtual LICE_pixel *getBits() + { + if (!m_parent) return 0; + + int xc = m_x, yc = m_y, h = m_h; + const int scale = (int)m_parent->Extended(LICE_EXT_GET_SCALING,NULL); + if (scale > 0) + { + xc = (xc*scale)>>8; + yc = (yc*scale)>>8; + h = (h*scale)>>8; + } + + LICE_pixel* parentptr=m_parent->getBits(); + if (m_parent->isFlipped()) parentptr += (m_parent->getHeight() - (yc+h))*m_parent->getRowSpan()+xc; + else parentptr += yc*m_parent->getRowSpan()+xc; + + return parentptr; + } + + enum { + LICE_GET_SUBBITMAP_VERSION = 0x51b7000, + LICE_SUBBITMAP_VERSION = 0x1000 // if we change any of this struct, then we *must* increment this version. + }; + + virtual INT_PTR Extended(int id, void* data) + { + if (id == LICE_GET_SUBBITMAP_VERSION) return LICE_SUBBITMAP_VERSION; + + if (!m_parent) return 0; + return m_parent->Extended(id, data); + } + + virtual int getWidth() { return m_w; } + virtual int getHeight() { return m_h; } + virtual int getRowSpan() { return m_parent ? m_parent->getRowSpan() : 0; } + + virtual HDC getDC() { return NULL; } + + int m_w,m_h,m_x,m_y; + LICE_IBitmap *m_parent; +}; + + +// flags that most blit functions can take + +#define LICE_BLIT_MODE_MASK 0xff +#define LICE_BLIT_MODE_COPY 0 +#define LICE_BLIT_MODE_ADD 1 +#define LICE_BLIT_MODE_DODGE 2 +#define LICE_BLIT_MODE_MUL 3 +#define LICE_BLIT_MODE_OVERLAY 4 +#define LICE_BLIT_MODE_HSVADJ 5 + +#define LICE_BLIT_MODE_CHANCOPY 0xf0 // in this mode, only available for LICE_Blit(), the low nibble is 2 bits of source channel (low 2), 2 bits of dest channel (high 2) + +#define LICE_BLIT_FILTER_MASK 0xff00 +#define LICE_BLIT_FILTER_NONE 0 +#define LICE_BLIT_FILTER_BILINEAR 0x100 // currently pretty slow! ack +#define LICE_BLIT_IGNORE_SCALING 0x20000 + + +#define LICE_BLIT_USE_ALPHA 0x10000 // use source's alpha channel + +#ifndef lice_max +#define lice_max(x,y) ((x)<(y)?(y):(x)) +#define lice_min(x,y) ((x)<(y)?(x):(y)) +#endif + +#ifdef _MSC_VER + #include + #define lice_isfinite(x) _finite(x) +#else + #define lice_isfinite(x) isfinite(x) +#endif + +// Reaper exports most LICE functions, so the function declarations below +// will collide with reaper_plugin.h +#ifndef LICE_PROVIDED_BY_APP + + +// bitmap loaders + +// dispatch to a linked loader implementation based on file extension +LICE_IBitmap* LICE_LoadImage(const char* filename, LICE_IBitmap* bmp=NULL, bool tryIgnoreExtension=false); +char *LICE_GetImageExtensionList(bool wantAllSup=true, bool wantAllFiles=true); // returns doublenull terminated GetOpenFileName() style list -- free() when done. +bool LICE_ImageIsSupported(const char *filename); // must be a filename that ends in .jpg, etc. if you want to check the extension, pass .ext + + +// pass a bmp if you wish to load it into that bitmap. note that if it fails bmp will not be deleted. +LICE_IBitmap *LICE_LoadPNG(const char *filename, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success +LICE_IBitmap *LICE_LoadPNGFromMemory(const void *data_in, int buflen, LICE_IBitmap *bmp=NULL); +LICE_IBitmap *LICE_LoadPNGFromResource(HINSTANCE hInst, const char *resid, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success +#ifndef _WIN32 +LICE_IBitmap *LICE_LoadPNGFromNamedResource(const char *name, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success +#endif + +LICE_IBitmap *LICE_LoadBMP(const char *filename, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success +LICE_IBitmap *LICE_LoadBMPFromResource(HINSTANCE hInst, const char *resid, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success + +LICE_IBitmap *LICE_LoadIcon(const char *filename, int reqiconsz=16, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success +LICE_IBitmap *LICE_LoadIconFromResource(HINSTANCE hInst, const char *resid, int reqiconsz=16, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success + +LICE_IBitmap *LICE_LoadJPG(const char *filename, LICE_IBitmap *bmp=NULL); +LICE_IBitmap *LICE_LoadJPGFromMemory(const void *data_in, int buflen, LICE_IBitmap *bmp = NULL); +LICE_IBitmap* LICE_LoadJPGFromResource(HINSTANCE hInst, const char *resid, LICE_IBitmap* bmp = 0); + +LICE_IBitmap *LICE_LoadGIF(const char *filename, LICE_IBitmap *bmp=NULL, int *nframes=NULL); // if nframes set, will be set to number of images (stacked vertically), otherwise first frame used + +LICE_IBitmap *LICE_LoadPCX(const char *filename, LICE_IBitmap *bmp=NULL); // returns a bitmap (bmp if nonzero) on success + +// bitmap saving +bool LICE_WritePNG(const char *filename, LICE_IBitmap *bmp, bool wantalpha=true); +bool LICE_WriteJPG(const char *filename, LICE_IBitmap *bmp, int quality=95, bool force_baseline=true); +bool LICE_WriteGIF(const char *filename, LICE_IBitmap *bmp, int transparent_alpha=0, bool dither=true); // if alphaExtended(LICE_EXT_GET_SCALING,NULL); \ + if (rsc>0) \ + StretchBlt(hdc,_x,_y,_w,_h,(src)->getDC(),(_sx*rsc)/256,(_sy*rsc)/256,(_w*rsc)>>8,(_h*rsc)>>8,_mode); \ + else BitBlt(hdc,_x,_y,_w,_h,(src)->getDC(),_sx,_sy,_mode); \ +} while (0) +#else +#define LICE_Scale_BitBlt(hdc, x,y,w,h, src, sx,sy, mode) BitBlt(hdc,x,y,w,h,(src)->getDC(),sx,sy,mode) +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_arc.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_arc.cpp new file mode 100644 index 0000000..6f0e467 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_arc.cpp @@ -0,0 +1,731 @@ +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "lice.h" +#include "lice_combine.h" +#include + +#define _PI 3.141592653589793238f + +#define IGNORE_SCALING(mode) ((mode)&LICE_BLIT_IGNORE_SCALING) +template inline void _SWAP(T& a, T& b) { T tmp = a; a = b; b = tmp; } + +#define A(x) ((LICE_pixel_chan)((x)*255.0+0.5)) +#define AF(x) (255) + +#define DEF_ALPHAS(dim) \ + static const LICE_pixel_chan alphas_unfill[] = { __ALPHAS__(A) }; \ + static const LICE_pixel_chan alphas_fill[] = { __ALPHAS__(AF) }; \ + const LICE_pixel_chan * const alphas = fill ? alphas_fill : alphas_unfill; \ + ((void)sizeof(char[1 - 2*(sizeof(alphas_unfill) != dim*dim)])); \ + ((void)sizeof(char[1 - 2*(sizeof(alphas_fill) != dim*dim)])); + +static bool CachedCircle(LICE_IBitmap* dest, float cx, float cy, float r, LICE_pixel color, float alpha, int mode, bool aa, bool fill) +{ + // fast draw for some small circles + if (r == 1.5f) + { + if (aa) + { +#define __ALPHAS__(B) \ + A(0.31), A(1.00), A(1.00), A(0.31), \ + A(1.00), B(0.06), B(0.06), A(1.00), \ + A(1.00), B(0.06), B(0.06), A(1.00), \ + A(0.31), A(1.00), A(1.00), A(0.31), + + DEF_ALPHAS(4) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 4, 4, alpha, mode); + } + else + { +#define __ALPHAS__(B) \ + A(0.00), A(1.00), A(1.00), A(0.00), \ + A(1.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), A(1.00), \ + A(0.00), A(1.00), A(1.00), A(0.00), + + DEF_ALPHAS(4) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 4, 4, alpha, mode); + } + return true; + } + else if (r == 2.0f) + { + if (aa) + { +#define __ALPHAS__(B) \ + A(0.06), A(0.75), A(1.00), A(0.75), A(0.06), \ + A(0.75), A(0.82), B(0.31), A(0.82), A(0.75), \ + A(1.00), B(0.31), B(0.00), B(0.31), A(1.00), \ + A(0.75), A(0.82), B(0.31), A(0.82), A(0.75), \ + A(0.06), A(0.75), A(1.00), A(0.75), A(0.06) + + DEF_ALPHAS(5) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 5, 5, alpha, mode); + } + else + { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(1.00), A(0.00), A(0.00), \ + A(0.00), A(1.00), B(0.00), A(1.00), A(0.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(0.00), A(1.00), B(0.00), A(1.00), A(0.00), \ + A(0.00), A(0.00), A(1.00), A(0.00), A(0.00) + + DEF_ALPHAS(5) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 5, 5, alpha, mode); + } + return true; + } + else if (r == 2.5f) { + if (aa) { +#define __ALPHAS__(B) \ + A(0.06), A(0.75), A(1.00), A(1.00), A(0.75), A(0.06), \ + A(0.75), A(0.82), B(0.31), B(0.31), A(0.82), A(0.75), \ + A(1.00), B(0.31), B(0.00), B(0.00), B(0.31), A(1.00), \ + A(1.00), B(0.31), B(0.00), B(0.00), B(0.31), A(1.00), \ + A(0.75), A(0.82), B(0.31), B(0.31), A(0.82), A(0.75), \ + A(0.06), A(0.75), A(1.00), A(1.00), A(0.75), A(0.06) + + DEF_ALPHAS(6) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 6, 6, alpha, mode); + } + else { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(1.00), A(1.00), A(0.00), A(0.00), \ + A(0.00), A(1.00), B(0.00), B(0.00), A(1.00), A(0.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(0.00), A(1.00), B(0.00), B(0.00), A(1.00), A(0.00), \ + A(0.00), A(0.00), A(1.00), A(1.00), A(0.00), A(0.00) + + DEF_ALPHAS(6) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 6, 6, alpha, mode); + } + return true; + } + else if (r == 3.0f) { + if (aa) { +#define __ALPHAS__(B) \ + A(0.00), A(0.56), A(1.00), A(1.00), A(1.00), A(0.56), A(0.00), \ + A(0.56), A(1.00), B(0.38), B(0.25), B(0.38), A(1.00), A(0.56), \ + A(1.00), B(0.44), B(0.00), B(0.00), B(0.00), B(0.44), A(1.00), \ + A(1.00), B(0.19), B(0.00), B(0.00), B(0.00), B(0.19), A(1.00), \ + A(1.00), B(0.44), B(0.00), B(0.00), B(0.00), B(0.44), A(1.00), \ + A(0.56), A(1.00), B(0.38), B(0.25), B(0.38), A(1.00), A(0.56), \ + A(0.00), A(0.56), A(1.00), A(1.00), A(1.00), A(0.56), A(0.00) + + DEF_ALPHAS(7) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 7, 7, alpha, mode); + } + else { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(1.00), A(1.00), A(1.00), A(0.00), A(0.00), \ + A(0.00), A(1.00), B(0.00), B(0.00), B(0.00), A(1.00), A(0.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(0.00), A(1.00), B(0.00), B(0.00), B(0.00), A(1.00), A(0.00), \ + A(0.00), A(0.00), A(1.00), A(1.00), A(1.00), A(0.00), A(0.00) + + DEF_ALPHAS(7) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 7, 7, alpha, mode); + } + return true; + } + else if (r == 3.5f) { + if (aa) { +#define __ALPHAS__(B) \ + A(0.00), A(0.31), A(0.87), A(1.00), A(1.00), A(0.87), A(0.31), A(0.00), \ + A(0.31), A(1.00), A(0.69), B(0.25), B(0.25), A(0.69), A(1.00), A(0.31), \ + A(0.87), A(0.69), B(0.00), B(0.00), B(0.00), B(0.00), A(0.69), A(0.87), \ + A(1.00), B(0.25), B(0.00), B(0.00), B(0.00), B(0.00), B(0.25), A(1.00), \ + A(1.00), B(0.25), B(0.00), B(0.00), B(0.00), B(0.00), B(0.25), A(1.00), \ + A(0.87), A(0.69), B(0.00), B(0.00), B(0.00), B(0.00), A(0.69), A(0.87), \ + A(0.31), A(1.00), A(0.69), B(0.25), B(0.25), A(0.69), A(1.00), A(0.31), \ + A(0.00), A(0.31), A(0.87), A(1.00), A(1.00), A(0.87), A(0.31), A(0.00) + + DEF_ALPHAS(8) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 8, 8, alpha, mode); + } + else { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(1.00), A(1.00), A(1.00), A(1.00), A(0.00), A(0.00), \ + A(0.00), A(1.00), A(1.00), B(0.00), B(0.00), A(1.00), A(1.00), A(0.00), \ + A(1.00), A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), A(1.00), \ + A(0.00), A(1.00), A(1.00), B(0.00), B(0.00), A(1.00), A(1.00), A(0.00), \ + A(0.00), A(0.00), A(1.00), A(1.00), A(1.00), A(1.00), A(0.00), A(0.00) + + DEF_ALPHAS(8) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 8, 8, alpha, mode); + } + return true; + } + else if (r == 4.0f) { + if (aa) { +#define __ALPHAS__(B) \ + A(0.00), A(0.12), A(0.69), A(1.00), A(1.00), A(1.00), A(0.69), A(0.12), A(0.00), \ + A(0.12), A(0.94), A(0.82), B(0.31), B(0.25), B(0.31), A(0.82), A(0.94), A(0.12), \ + A(0.69), A(0.82), B(0.06), B(0.00), B(0.00), B(0.00), B(0.06), A(0.82), A(0.69), \ + A(1.00), B(0.31), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.31), A(1.00), \ + A(1.00), B(0.19), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.19), A(1.00), \ + A(1.00), B(0.31), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.31), A(1.00), \ + A(0.69), A(0.82), B(0.06), B(0.00), B(0.00), B(0.00), B(0.06), A(0.82), A(0.69), \ + A(0.12), A(0.94), A(0.82), B(0.31), B(0.25), B(0.31), A(0.82), A(0.94), A(0.12), \ + A(0.00), A(0.12), A(0.69), A(1.00), A(1.00), A(1.00), A(0.69), A(0.12), A(0.00) + + DEF_ALPHAS(9) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 9, 9, alpha, mode); + } + else { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(1.00), A(1.00), A(1.00), A(1.00), A(1.00), A(0.00), A(0.00), \ + A(0.00), A(1.00), A(1.00), B(0.00), B(0.00), B(0.00), A(1.00), A(1.00), A(0.00), \ + A(1.00), A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(1.00), A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), A(1.00), \ + A(0.00), A(1.00), A(1.00), B(0.00), B(0.00), B(0.00), A(1.00), A(1.00), A(0.00), \ + A(0.00), A(0.00), A(1.00), A(1.00), A(1.00), A(1.00), A(1.00), A(0.00), A(0.00) + + DEF_ALPHAS(9) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 9, 9, alpha, mode); + } + return true; + } + else if (r == 5.0f) + { + if (aa) { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(0.00), A(0.58), A(0.90), A(1.00), A(0.90), A(0.58), A(0.00), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(1.00), B(0.42), B(0.10), B(0.00), B(0.10), B(0.42), A(1.00), A(0.00), A(0.00), \ + A(0.00), A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), A(0.00), \ + A(0.58), B(0.42), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.42), A(0.58), \ + A(0.90), B(0.10), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.10), A(0.90), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(0.90), B(0.10), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.10), A(0.90), \ + A(0.58), B(0.42), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.42), A(0.58), \ + A(0.00), A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), A(0.00), \ + A(0.00), A(0.00), A(1.00), B(0.42), B(0.10), B(0.00), B(0.10), B(0.42), A(1.00), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(0.00), A(0.58), A(0.90), A(1.00), A(0.90), A(0.58), A(0.00), A(0.00), A(0.00) + + DEF_ALPHAS(11) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 11, 11, alpha, mode); + return true; + } + } + else if (r == 6.0f) + { + if (aa) { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(0.00), A(0.20), A(0.66), A(0.92), A(1.00), A(0.92), A(0.66), A(0.20), A(0.00), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(0.47), A(0.81), B(0.35), B(0.09), B(0.00), B(0.09), B(0.35), A(0.81), A(0.47), A(0.00), A(0.00), \ + A(0.00), A(0.47), B(0.53), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.53), A(0.47), A(0.00), \ + A(0.20), A(0.81), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(0.81), A(0.20), \ + A(0.66), B(0.35), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.35), A(0.66), \ + A(0.92), B(0.09), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.09), A(0.92), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(0.92), B(0.09), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.09), A(0.92), \ + A(0.66), B(0.35), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.35), A(0.66), \ + A(0.20), A(0.81), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(0.81), A(0.20), \ + A(0.00), A(0.47), B(0.53), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.53), A(0.47), A(0.00), \ + A(0.00), A(0.00), A(0.47), A(0.81), B(0.35), B(0.09), B(0.00), B(0.09), B(0.35), A(0.81), A(0.47), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(0.00), A(0.20), A(0.66), A(0.92), A(1.00), A(0.92), A(0.66), A(0.20), A(0.00), A(0.00), A(0.00), + + DEF_ALPHAS(13) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 13, 13, alpha, mode); + return true; + } + } + else if (r == 7.0f) + { + if (aa) { +#define __ALPHAS__(B) \ + A(0.00), A(0.00), A(0.00), A(0.00), A(0.33), A(0.71), A(0.93), A(1.00), A(0.93), A(0.71), A(0.33), A(0.00), A(0.00), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(0.00), A(0.75), A(0.68), B(0.29), B(0.07), B(0.00), B(0.07), B(0.29), A(0.68), A(0.75), A(0.00), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(0.90), B(0.26), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.34), A(0.90), A(0.00), A(0.00), \ + A(0.00), A(0.75), B(0.34), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.26), A(0.75), A(0.00), \ + A(0.33), A(0.68), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(0.68), A(0.33), \ + A(0.71), B(0.29), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.29), A(0.71), \ + A(0.93), B(0.07), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.07), A(0.93), \ + A(1.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(1.00), \ + A(0.93), B(0.07), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.07), A(0.93), \ + A(0.71), B(0.29), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.29), A(0.71), \ + A(0.33), A(0.68), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), A(0.68), A(0.33), \ + A(0.00), A(0.75), B(0.34), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.26), A(0.75), A(0.00), \ + A(0.00), A(0.00), A(0.90), B(0.26), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.00), B(0.34), A(0.90), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(0.00), A(0.75), A(0.68), B(0.29), B(0.07), B(0.00), B(0.07), B(0.29), A(0.68), A(0.75), A(0.00), A(0.00), A(0.00), \ + A(0.00), A(0.00), A(0.00), A(0.00), A(0.33), A(0.71), A(0.93), A(1.00), A(0.93), A(0.71), A(0.33), A(0.00), A(0.00), A(0.00), A(0.00), + + DEF_ALPHAS(15) +#undef __ALPHAS__ + LICE_DrawGlyph(dest, cx-r, cy-r, color, alphas, 15, 15, alpha, mode); + return true; + } + } + + return false; +} + + +template class _LICE_CircleDrawer +{ +public: + + static void DrawClippedPt(LICE_IBitmap* dest, int x, int y, const int *clip, + int r, int g, int b, int a, int alpha, bool doclip) + { + if (doclip && (x < clip[0] || x >= clip[2] || y < clip[1] || y >= clip[3])) return; + LICE_pixel* px = dest->getBits()+y*dest->getRowSpan()+x; + COMBFUNC::doPix((LICE_pixel_chan*)px, r, g, b, a, alpha); + } + + static void DrawClippedHorzLine(LICE_IBitmap* dest, int y, int xlo, int xhi, const int *clip, + int r, int g, int b, int a, int alpha, bool doclip) + { + if (doclip) + { + if (y < clip[1] || y >= clip[3]) return; + xlo = lice_max(xlo, clip[0]); + xhi = lice_min(xhi, clip[2]-1); + } + LICE_pixel* px = dest->getBits()+y*dest->getRowSpan()+xlo; + while (xlo <= xhi) + { + COMBFUNC::doPix((LICE_pixel_chan*)px, r, g, b, a, alpha); + ++px; + ++xlo; + } + } + + static void DrawClippedVertLine(LICE_IBitmap* dest, int x, int ylo, int yhi, const int *clip, + int r, int g, int b, int a, int alpha, bool doclip) + { + if (doclip) + { + if (x < clip[0] || x >= clip[2]) return; + ylo = lice_max(ylo, clip[1]); + yhi = lice_min(yhi, clip[3]-1); + } + int span=dest->getRowSpan(); + LICE_pixel* px = dest->getBits()+ylo*span+x; + while (ylo <= yhi) + { + COMBFUNC::doPix((LICE_pixel_chan*)px, r, g, b, a, alpha); + px += span; + ++ylo; + } + } + + static void DrawClippedCircleAA(LICE_IBitmap* dest, float cx, float cy, float rad, + const int *clip, LICE_pixel color, int ai, bool filled, bool doclip) + { + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + const int cx0=(int)floor(cx+0.5f); + const int cy0=(int)floor(cy+0.5f); + + int y=(int)rad; + double w=rad-floor(rad); + int wa=(int)((double)ai*w); + + DrawClippedPt(dest, cx0, cy0-y-1, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0, cy0+y+1, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0-y-1, cy0, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0+y+1, cy0, clip, r, g, b, a, wa, doclip); + + if (filled) + { + DrawClippedVertLine(dest, cx0, cy0-y, cy0-1, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0, cy0+1, cy0+y, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0, cx0-y, cx0+y, clip, r, g, b, a, ai, doclip); + } + else + { + int iwa=ai-wa; + DrawClippedPt(dest, cx0, cy0-y, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0+y, cy0, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0, cy0+y, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0-y, cy0, clip, r, g, b, a, iwa, doclip); + } + + double r2=rad*rad; + double yf=sqrt(r2-1.0); + int yl=(int)(yf+0.5); + + int x=1; + while (x <= yl) + { + y=(int)yf; + w=yf-floor(yf); + wa=(int)((double)ai*w); + + DrawClippedPt(dest, cx0-x, cy0-y-1, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0-x, cy0+y+1, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0+x, cy0-y-1, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0+x, cy0+y+1, clip, r, g, b, a, wa, doclip); + if (x != yl) + { + DrawClippedPt(dest, cx0-y-1, cy0-x, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0+y+1, cy0-x, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0-y-1, cy0+x, clip, r, g, b, a, wa, doclip); + DrawClippedPt(dest, cx0+y+1, cy0+x, clip, r, g, b, a, wa, doclip); + } + + if (filled) + { + DrawClippedVertLine(dest, cx0-x, cy0-y, cy0-x-1, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0-x, cy0+x+1, cy0+y, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0-x, cx0-y, cx0-x, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0-x, cx0+x, cx0+y, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0+x, cx0-y, cx0-x, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0+x, cx0+x, cx0+y, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0+x, cy0-y, cy0-x-1, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0+x, cy0+x+1, cy0+y, clip, r, g, b, a, ai, doclip); + } + else + { + int iwa=ai-wa; + DrawClippedPt(dest, cx0-y, cy0-x, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0+y, cy0-x, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0-x, cy0+y, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0+x, cy0+y, clip, r, g, b, a, iwa, doclip); + if (x != yl) + { + DrawClippedPt(dest, cx0-x, cy0-y, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0+x, cy0-y, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0-y, cy0+x, clip, r, g, b, a, iwa, doclip); + DrawClippedPt(dest, cx0+y, cy0+x, clip, r, g, b, a, iwa, doclip); + } + } + + ++x; + yf=sqrt(r2-(double)(x*x)); + yl=(int)(yf+0.5); + } + } + + static void DrawClippedCircle(LICE_IBitmap* dest, float cx, float cy, float rad, + const int *clip, LICE_pixel color, int ai, bool filled, bool doclip) + { + const int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + const int cx0=(int)floor(cx+0.5f); + const int cy0=(int)floor(cy+0.5f); + const int r0=(int)(rad+0.5f); + + if (filled) + { + DrawClippedVertLine(dest, cx0, cy0-r0, cy0-1, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0, cy0+1, cy0+r0, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0, cx0-r0, cx0+r0, clip, r, g, b, a, ai, doclip); + } + else + { + DrawClippedPt(dest, cx0, cy0-r0, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0+r0, cy0, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0, cy0+r0, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0-r0, cy0, clip, r, g, b, a, ai, doclip); + } + + int x=0; + int y=r0; + int e=-r0; + while (++x < y) + { + if (e < 0) + { + e += 2*x+1; + } + else + { + --y; + e += 2*(x-y)+1; + } + + if (filled) + { + DrawClippedVertLine(dest, cx0-x, cy0-y, cy0-x-1, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0-x, cy0+x+1, cy0+y, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0-x, cx0-y, cx0-x, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0-x, cx0+x, cx0+y, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0+x, cx0-y, cx0-x, clip, r, g, b, a, ai, doclip); + DrawClippedHorzLine(dest, cy0+x, cx0+x, cx0+y, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0+x, cy0-y, cy0-x-1, clip, r, g, b, a, ai, doclip); + DrawClippedVertLine(dest, cx0+ x, cy0+x+1, cy0+y, clip, r, g, b, a, ai, doclip); + } + else + { + DrawClippedPt(dest, cx0-x, cy0-y, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0-x, cy0+y, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0+x, cy0-y, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0+x, cy0+y, clip, r, g, b, a, ai, doclip); + if (x != y) + { + DrawClippedPt(dest, cx0-y, cy0-x, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0-y, cy0+x, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0+y, cy0-x, clip, r, g, b, a, ai, doclip); + DrawClippedPt(dest, cx0+y, cy0+x, clip, r, g, b, a, ai, doclip); + } + } + } + } + +}; + + +static void __DrawCircleClipped(LICE_IBitmap* dest, float cx, float cy, float rad, + LICE_pixel color, int ia, bool aa, bool filled, int mode, const int *clip, bool doclip) +{ + // todo: more clipped/filled versions (to optimize constants out?) + if (aa) + { + #define __LICE__ACTION(COMBFUNC) _LICE_CircleDrawer::DrawClippedCircleAA(dest, cx, cy, rad, clip, color, ia, filled, doclip) + __LICE_ACTION_NOSRCALPHA(mode, ia,false) + #undef __LICE__ACTION + } + else + { + #define __LICE__ACTION(COMBFUNC) _LICE_CircleDrawer::DrawClippedCircle(dest, cx, cy, rad, clip, color, ia, filled, doclip) + __LICE_ACTION_CONSTANTALPHA(mode,ia,false) + #undef __LICE__ACTION + } +} + + +static void __DrawArc(int w, int h, LICE_IBitmap* dest, float cx, float cy, float rad, double anglo, double anghi, + LICE_pixel color, int ialpha, bool aa, int mode) +{ + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(cx); + __LICE_SC(cy); + __LICE_SC(rad); + } + } + // -2PI <= anglo <= anghi <= 2PI + anglo += 2.0*_PI; + anghi += 2.0*_PI; + + // 0 <= anglo <= anghi <= 4PI + + double next_ang = anglo - fmod(anglo,0.5*_PI); + + int ly = (int)floor(cy - rad*cos(anglo) + 0.5); + int lx = (int)floor(cx + rad*sin(anglo) + 0.5); + + while (anglo < anghi) + { + next_ang += 0.5*_PI; + if (next_ang > anghi) next_ang = anghi; + + int yhi = (int) floor(cy-rad*cos(next_ang)+0.5); + int xhi = (int) floor(cx+rad*sin(next_ang)+0.5); + int ylo = ly; + int xlo = lx; + + ly = yhi; + lx = xhi; + + if (yhi < ylo) { int tmp = ylo; ylo = yhi; yhi=tmp; } + if (xhi < xlo) { int tmp = xlo; xlo = xhi; xhi=tmp; } + + anglo = next_ang; + + if (xhi != cx) xhi++; + if (yhi != cy) yhi++; + + const int clip[4]={lice_max(xlo,0),lice_max(0, ylo),lice_min(w,xhi+1),lice_min(h, yhi+1)}; + + __DrawCircleClipped(dest,cx,cy,rad,color,ialpha,aa,false,mode,clip,true); + } +} + +void LICE_Arc(LICE_IBitmap* dest, float cx, float cy, float r, float minAngle, float maxAngle, + LICE_pixel color, float alpha, int mode, bool aa) +{ + if (!dest) return; + + if (dest->isFlipped()) { cy=dest->getHeight()-1-cy; minAngle=_PI-minAngle; maxAngle=_PI-maxAngle; } + + if (maxAngle < minAngle) + { + float tmp=maxAngle; + maxAngle=minAngle; + minAngle=tmp; + } + + if (maxAngle - minAngle >= 2.0f*_PI) + { + LICE_Circle(dest,cx,cy,r,color,alpha,mode,aa); + return; + } + + if (maxAngle >= 2.0f*_PI) + { + float tmp = fmod(maxAngle,2.0f*_PI); + minAngle -= maxAngle - tmp; // reduce by factors of 2PI + maxAngle = tmp; + } + else if (minAngle <= -2.0f*_PI) + { + float tmp = fmod(minAngle,2.0f*_PI); + maxAngle -= minAngle - tmp; // toward zero by factors of 2pi + minAngle = tmp; + } + + // -2PI <= minAngle <= maxAngle <= 2PI + + int ia = (int) (alpha*256.0f); + if (!ia) return; + + __DrawArc(dest->getWidth(),dest->getHeight(),dest,cx,cy,r,minAngle,maxAngle,color,ia,aa,mode); +} + + + + +void LICE_Circle(LICE_IBitmap* dest, float cx, float cy, float r, LICE_pixel color, float alpha, int mode, bool aa) +{ + if (!dest) return; + + int w = dest->getWidth(), h = dest->getHeight(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(cx); + __LICE_SC(cy); + __LICE_SC(r); + } + } + + const int clip[4] = { 0, 0, w, h }; + if (w < 1 || h <1 || r<0 || + (int)cx+(int)r < -2 || (int)cy + (int)r < - 2 || + (int)cx-(int)r > w + 2 || (int)cy - (int)r > h + 2 + ) return; + + int ia = (int) (alpha*256.0f); + if (!ia) return; + + if (CachedCircle(dest, cx, cy, r, color, alpha, mode|LICE_BLIT_IGNORE_SCALING, aa, false)) return; + + if (dest->isFlipped()) cy=h-1-cy; + + const bool doclip = !(cx-r-2 >= 0 && cy-r-2 >= 0 && cx+r+2 < w && cy+r+2 < h); + + __DrawCircleClipped(dest,cx,cy,r,color,ia,aa,false,mode,clip,doclip); +} + +void LICE_FillCircle(LICE_IBitmap* dest, float cx, float cy, float r, LICE_pixel color, float alpha, int mode, bool aa) +{ + if (!dest) return; + + int w = dest->getWidth(), h = dest->getHeight(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(cx); + __LICE_SC(cy); + __LICE_SC(r); + } + } + + if (w < 1 || h < 1 || r < 0.0 || + (int)cx+(int)r < -2 || (int)cy + (int)r < - 2 || + (int)cx-(int)r > w + 2 || (int)cy - (int)r > h + 2 + ) return; + + const int ia = (int) (alpha*256.0f); + if (!ia) return; + + if (CachedCircle(dest, cx, cy, r, color, alpha, mode|LICE_BLIT_IGNORE_SCALING, aa, true)) return; + + if (dest->isFlipped()) cy=h-1-cy; + + const int clip[4] = { 0, 0, w, h }; + + const bool doclip = !(cx-r-2 >= 0 && cy-r-2 >= 0 && cx+r+2 < w && cy+r+2 < h); + __DrawCircleClipped(dest,cx,cy,r,color,ia,aa,true,mode,clip,doclip); +} + + +void LICE_RoundRect(LICE_IBitmap *drawbm, float xpos, float ypos, float w, float h, int cornerradius, + LICE_pixel col, float alpha, int mode, bool aa) +{ + xpos = floor(xpos+0.5); + ypos = floor(ypos+0.5); + w = floor(w+0.5); + h = floor(h+0.5); + if (cornerradius>0) + { + float cr=cornerradius; + if (cr > w*0.5) cr=w*0.5; + if (cr > h*0.5) cr=h*0.5; + cr=floor(cr); + + if (cr>=2) + { + double adj = 0.0; + const int __sc = IGNORE_SCALING(mode) ? 0 : drawbm ? (int)drawbm->Extended(LICE_EXT_GET_SCALING,NULL) : 0; + if (__sc>0) + { + adj = 1.0 - 256.0/__sc; + + LICE_FLine(drawbm,xpos+cr+adj,ypos+adj,xpos+w-cr,ypos+adj,col,alpha,mode,true); + LICE_FLine(drawbm,xpos+cr-1+adj,ypos+h-adj,xpos+w-cr-adj,ypos+h-adj,col,alpha,mode,true); + LICE_FLine(drawbm,xpos+w-adj,ypos+cr+adj,xpos+w-adj,ypos+h-cr-adj,col,alpha,mode,true); + LICE_FLine(drawbm,xpos+adj,ypos+cr-1+adj,xpos+adj,ypos+h-cr-adj,col,alpha,mode,true); +// aa=true; + } + else + { + LICE_Line(drawbm,xpos+cr,ypos,xpos+w-cr,ypos,col,alpha,mode,aa); + LICE_Line(drawbm,xpos+cr-1,ypos+h,xpos+w-cr,ypos+h,col,alpha,mode,aa); + LICE_Line(drawbm,xpos+w,ypos+cr,xpos+w,ypos+h-cr,col,alpha,mode,aa); + LICE_Line(drawbm,xpos,ypos+cr-1,xpos,ypos+h-cr,col,alpha,mode,aa); + } + + LICE_Arc(drawbm,xpos+cr+adj,ypos+cr+adj,cr,-_PI*0.5f,0,col,alpha,mode,aa); + LICE_Arc(drawbm,xpos+w-cr-adj,ypos+cr+adj,cr,0,_PI*0.5f,col,alpha,mode,aa); + LICE_Arc(drawbm,xpos+w-cr-adj,ypos+h-cr-adj,cr,_PI*0.5f,_PI,col,alpha,mode,aa); + LICE_Arc(drawbm,xpos+cr+adj,ypos+h-cr-adj,cr,_PI,_PI*1.5f,col,alpha,mode,aa); + + return; + } + } + + LICE_DrawRect(drawbm, (int)xpos, (int)ypos, (int)w, (int)h, col, alpha, mode); +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_bezier.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_bezier.h new file mode 100644 index 0000000..f446642 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_bezier.h @@ -0,0 +1,306 @@ +#ifndef _LICE_BEZIER_ +#define _LICE_BEZIER_ + +#include "lice.h" +#include + +// Returns quadratic bezier x, y for a given t in [0,1]. +template +void LICE_Bezier(T ctrl_x1, T ctrl_x2, T ctrl_x3, + T ctrl_y1, T ctrl_y2, T ctrl_y3, double t, T* pX, T* pY) +{ + double it = 1.0 - t; + double a = it * it; + double b = 2.0 * it * t; + double c = t * t; + *pX = (T) (a * (double) ctrl_x1 + b * (double) ctrl_x2 + c * (double) ctrl_x3); + *pY = (T) (a * (double) ctrl_y1 + b * (double) ctrl_y2 + c * (double) ctrl_y3); +} + +template +void LICE_CBezier_GetCoeffs(T ctrl_x1, T ctrl_x2, T ctrl_x3, T ctrl_x4, + T ctrl_y1, T ctrl_y2, T ctrl_y3, T ctrl_y4, + double* pAX, double* pBX, double* pCX, + double* pAY, double* pBY, double* pCY) +{ + double cx = *pCX = 3.0 * (double) (ctrl_x2 - ctrl_x1); + double bx = *pBX = 3.0 * (double) (ctrl_x3 - ctrl_x2) - cx; + *pAX = (double) (ctrl_x4 - ctrl_x1) - cx - bx; + double cy = *pCY = 3.0 * (double) (ctrl_y2 - ctrl_y1); + double by = *pBY = 3.0 * (double) (ctrl_y3 - ctrl_y2) - cy; + *pAY = (double) (ctrl_y4 - ctrl_y1) - cy - by; +} + +// Returns cubic bezier x, y for a given t in [0,1]. +template +void LICE_CBezier(T ctrl_x1, T ctrl_x2, T ctrl_x3, T ctrl_x4, + T ctrl_y1, T ctrl_y2, T ctrl_y3, T ctrl_y4, double t, T* pX, T* pY) +{ + double ax, bx, cx, ay, by, cy; + LICE_CBezier_GetCoeffs(ctrl_x1, ctrl_x2, ctrl_x3, ctrl_x4, + ctrl_y1, ctrl_y2, ctrl_y3, ctrl_y4, + &ax, &bx, &cx, &ay, &by, &cy); + + double t2 = t * t; + double t3 = t * t2; + *pX = (T) (ax * t3 + bx * t2 + cx * t) + ctrl_x1; + *pY = (T) (ay * t3 + by * t2 + cy * t) + ctrl_y1; +} + +// Returns quadratic bezier y for a given x in [x1, x3] (for rasterizing). +// ctrl_x1 < ctrl_x3 required. +template +T LICE_Bezier_GetY(T ctrl_x1, T ctrl_x2, T ctrl_x3, T ctrl_y1, T ctrl_y2, T ctrl_y3, T x, double* pt=0) +{ + if (x <= ctrl_x1) + { + if (pt) *pt = 0.0; + return ctrl_y1; + } + if (x >= ctrl_x3) + { + if (pt) *pt = 1.0; + return ctrl_y3; + } + + double t, a = (double) ctrl_x1 - (double) (2 * ctrl_x2) + (double) ctrl_x3; + if (a == 0.0) + { + t=(ctrl_x1 == ctrl_x3) ? 0.0 : (x-ctrl_x1)/(ctrl_x3-ctrl_x1); + } + else + { + t = (double) (ctrl_x2 - ctrl_x1); + t = (-t + sqrt(t * t - a * (ctrl_x1 - x))) / a; + } + const double it = 1.0 - t; + + if (pt) *pt = t; + return (T) (it * it * (double) ctrl_y1 + t * (2.0*it*(double)ctrl_y2 + t * (double) ctrl_y3)); +} + +// Special case for x = y = [0,1] +template +void LICE_Bezier_Norm(T ctrl_x2, T ctrl_y2, double t, T* pX, T* pY) +{ + double b = 2.0 * (1.0 - t) * t; + double c = t * t; + *pX = (T) (b * (double) ctrl_x2 + c); + *pY = (T) (b * (double) ctrl_y2 + c); +} + +// special case for x = y = [0,1]. +template +T LICE_Bezier_GetY_Norm(T ctrl_x2, T ctrl_y2, T x) +{ + if (x < (T) 0.0) { + return (T) 0.0; + } + if (x >= (T) 1.0) { + return (T) 1.0; + } + if (ctrl_x2 == (T) 0.5) { // linear + return x; + } + + +/* + // this causes ICC 11.0 to produce bad results on OSX/386 + double b = (double) (2 * ctrl_x2); + double a = 1.0 - b; + double c = (double) -x; + double t = (-b + sqrt(b * b - 4.0 * a * c)) / (2.0 * a); + + b = 2.0 * (1.0 - t) * t; + c = t * t; + return (T) (b * (double) ctrl_y2 + c); + + // the simplified math below works properly +*/ + + + const double t = (-ctrl_x2 + sqrt(ctrl_x2 * (ctrl_x2 - 2.0*x) + x)) / (1.0-2.0*ctrl_x2); + + return (T) (((2.0 * (1.0-t)) * ctrl_y2 + t)*t); +} + +// Finds the cardinal bezier control points surrounding x2. +// Cubic bezier over (x1,x1b,x2a,x2), (y1,y1b,y2a,y2) or +// quadratic bezier over (x1,x1b,mid(x1b,x2a)), (y1,y1b,mid(y1b,y2a)) +// will smoothly interpolate between (x1,y1) and (x2,y2) while preserving all existing values. +// The lower alpha is, the more tame the bezier curve will be (0.25 = subtle). +template +void LICE_Bezier_FindCardinalCtlPts(double alpha, T x1, T x2, T x3, T y1, T y2, T y3, + T* ctrl_x2a, T* ctrl_x2b, T* ctrl_y2a, T* ctrl_y2b) +{ + double dxa = alpha * (double) (x2 - x1); + double dxb = alpha * (double) (x3 - x2); + if (ctrl_x2a) *ctrl_x2a = x2 - (T) dxa; + if (ctrl_x2b) *ctrl_x2b = x2 + (T) dxb; + + if (x1 == x3) + { + if (ctrl_y2a) *ctrl_y2a = y2; + if (ctrl_y2b) *ctrl_y2b = y2; + } + else + { + double m = (double) (y3 - y1) / (double) (x3 - x1); + if (ctrl_y2a) *ctrl_y2a = y2 - (T) (m * dxa); + if (ctrl_y2b) *ctrl_y2b = y2 + (T) (m * dxb); + } +} + +// Basic quadratic nurbs. Given a set of n (x,y) pairs, +// populate pDest with the unit-spaced nurbs curve. +// pDest must be passed in with size (int) (*(pX+n-1) - *pX). +// pX must be monotonically increasing and no duplicates. +template +inline void LICE_QNurbs(T* pDest, int pDest_sz, int *pX, T* pY, int n, bool hit_each_point=false) +{ + int x1 = *pX++, x2 = *pX++; + T y1 = *pY++, y2 = *pY++; + double xm1, xm2 = 0.5 * (x1 + x2); + double ym1, ym2 = 0.5 * (y1 + y2); + + double yi = y1, m = (y2 - y1) / (double) (x2 - x1); + int xi = x1, iend = (int)floor(xm2+0.5); // this (and below) was previously ceil(), but can't see any reason why it should matter (this should be more correct, I'd imagine) + for (; xi < iend; xi++, yi += m) + { + if (--pDest_sz<0) return; + *pDest++ = (T) yi; + } + + for (int i = 2; i < n; ++i) + { + x1 = x2; + x2 = *pX++; + y1 = y2; + y2 = *pY++; + + xm1 = xm2; + xm2 = 0.5 * (x1 + x2); + ym1 = ym2; + ym2 = 0.5 * (y1 + y2); + + iend = (int)floor(xm2+0.5); + if (ym1 == ym2 && y1 == ym1) + { + for (; xi < iend; xi++) + { + if (--pDest_sz<0) return; + *pDest++ = (T) y1; + } + } + else + { + double y1u = y1; + if (hit_each_point) + { + // y1 = LICE_Bezier_GetY(0,0.5,1.0, ym1,y1u,ym2,0.5), this is the inverse + y1u = 2.0 * y1 - 0.5 * (ym1 + ym2); + } + for (; xi < iend; xi++) + { + if (--pDest_sz<0) return; + *pDest++ = (T) LICE_Bezier_GetY(xm1, (double)x1, xm2, ym1, (double)y1u, ym2, (double)xi); + } + } + } + + m = (y2 - y1) / (double) (x2 - x1); + yi = ym2; + for (; xi < x2; xi++, yi += m) + { + if (--pDest_sz<0) return; + *pDest++ = (T) yi; + } +} + +#define CBEZ_ITERS 8 + +#define EVAL_CBEZ(tx,a,b,c,d,t) \ +{ \ + double _t2=t*t; \ + tx=(a*t*_t2+b*_t2+c*t+d); \ +} + +#define EVAL_CBEZXY(tx, ty, ax, bx, cx, dx, ay, by, cy, dy, t) \ +{ \ + double _t2=t*t; \ + double _t3=t*_t2; \ + tx=ax*_t3+bx*_t2+cx*t+dx; \ + ty=ay*_t3+by*_t2+cy*t+dy; \ +} + +template +T LICE_CBezier_GetY(T ctrl_x1, T ctrl_x2, T ctrl_x3, T ctrl_x4, + T ctrl_y1, T ctrl_y2, T ctrl_y3, T ctrl_y4, T x, + T* pNextX = 0, T* pdYdX = 0, double* ptLo = 0, double* ptHi = 0) +{ + if (x < ctrl_x1) + { + if (pNextX) *pNextX = ctrl_x1; + if (pdYdX) *pdYdX = (T) 0.0; + return ctrl_y1; + } + if (x >= ctrl_x4) + { + if (pNextX) *pNextX = ctrl_x4; + if (pdYdX) *pdYdX = (T) 0.0; + return ctrl_y4; + } + + double ax, bx, cx, ay, by, cy; + LICE_CBezier_GetCoeffs(ctrl_x1, ctrl_x2, ctrl_x3, ctrl_x4, + ctrl_y1, ctrl_y2, ctrl_y3, ctrl_y4, + &ax, &bx, &cx, &ay, &by, &cy); + + double tx, t, tLo = 0.0, tHi = 1.0; + double xLo=0.0, xHi=0.0, yLo, yHi; + int i; + for (i = 0; i < CBEZ_ITERS; ++i) + { + t = 0.5 * (tLo + tHi); + EVAL_CBEZ(tx, ax, bx, cx, (double) ctrl_x1, t); + if (tx < (double) x) + { + tLo = t; + xLo = tx; + } + else if (tx > (double) x) + { + tHi = t; + xHi = tx; + } + else + { + tLo = t; + xLo = tx; + tHi = t + 1.0/pow(2.0,CBEZ_ITERS); + if (tHi > 1.0) tHi = 1.0; // floating point error + EVAL_CBEZ(xHi, ax, bx, cx, (double) ctrl_x1, tHi); + break; + } + } + + if (tLo == 0.0) EVAL_CBEZ(xLo, ax, bx, cx, (double) ctrl_x1, 0.0); + if (tHi == 1.0) EVAL_CBEZ(xHi, ax, bx, cx, (double) ctrl_x1, 1.0); + + EVAL_CBEZ(yLo, ay, by, cy, (double) ctrl_y1, tLo); + EVAL_CBEZ(yHi, ay, by, cy, (double) ctrl_y1, tHi); + + double dYdX = (xLo == xHi ? 0.0 : (yHi - yLo) / (xHi - xLo)); + double y = yLo + ((double) x - xLo) * dYdX; + + if (pNextX) *pNextX = (T) xHi; + if (pdYdX) *pdYdX = (T) dYdX; + + if (ptLo) *ptLo = tLo; + if (ptHi) *ptHi = tHi; + + return (T) y; +} + +#endif + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_bmp.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_bmp.cpp new file mode 100644 index 0000000..3f2842d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_bmp.cpp @@ -0,0 +1,122 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_bmp.cpp (BMP loading for LICE) + See lice.h for license and other information +*/ + +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "lice.h" +#include "../wdltypes.h" +#ifndef _WIN32 +#include "../swell/swell.h" +#endif + +static LICE_IBitmap *hbmToBit(HBITMAP hbm, LICE_IBitmap *bmp) +{ + BITMAP bm; + GetObject(hbm, sizeof(BITMAP), (LPSTR)&bm); + + LICE_SysBitmap sysbitmap(bm.bmWidth,bm.bmHeight); + +#ifdef _WIN32 + HDC hdc=CreateCompatibleDC(NULL); + HGDIOBJ oldBM=SelectObject(hdc,hbm); + + BitBlt(sysbitmap.getDC(),0,0,bm.bmWidth,bm.bmHeight,hdc,0,0,SRCCOPY); + GdiFlush(); + + if (!bmp) bmp=new WDL_NEW LICE_MemBitmap(bm.bmWidth,bm.bmHeight); + LICE_Copy(bmp,&sysbitmap); + + SelectObject(hdc,oldBM); + DeleteDC(hdc); + #else + LICE_Clear(&sysbitmap,0); + RECT r={0,0,bm.bmWidth,bm.bmHeight}; + DrawImageInRect(sysbitmap.getDC(),hbm,&r); + if (!bmp) bmp=new WDL_NEW LICE_MemBitmap(bm.bmWidth,bm.bmHeight); + LICE_Copy(bmp,&sysbitmap); + #endif + + if (bmp) LICE_FillRect(bmp,0,0,bmp->getWidth(),bmp->getHeight(),LICE_RGBA(0,0,0,255),1.0f,LICE_BLIT_MODE_ADD); + + return bmp; +} + + +LICE_IBitmap *LICE_LoadBMP(const char *filename, LICE_IBitmap *bmp) // returns a bitmap (bmp if nonzero) on success +{ + HBITMAP bm=NULL; +#ifdef _WIN32 +#ifndef WDL_NO_SUPPORT_UTF8 + #ifdef WDL_SUPPORT_WIN9X + if (GetVersion()<0x80000000) + #endif + { + WCHAR wf[2048]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wf,2048)) + bm = (HBITMAP) LoadImageW(NULL,wf,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_LOADFROMFILE); + } +#endif + + if (!bm) bm=(HBITMAP) LoadImage(NULL,filename,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_LOADFROMFILE); +#else + bm=(HBITMAP) LoadNamedImage(filename,false); +#endif + if (!bm) return 0; + + LICE_IBitmap *ret=hbmToBit(bm,bmp); + + DeleteObject(bm); + return ret; +} + +#ifdef _WIN32 +LICE_IBitmap *LICE_LoadBMPFromResource(HINSTANCE hInst, const char *resid, LICE_IBitmap *bmp) // returns a bitmap (bmp if nonzero) on success +{ + HBITMAP bm=(HBITMAP) LoadImage(hInst,resid,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION); + if (!bm) return 0; + + LICE_IBitmap *ret=hbmToBit(bm,bmp); + + DeleteObject(bm); + return ret; +} +#endif + + + +class LICE_BMPLoader +{ +public: + _LICE_ImageLoader_rec rec; + LICE_BMPLoader() + { + rec.loadfunc = loadfunc; + rec.get_extlist = get_extlist; + rec._next = LICE_ImageLoader_list; + LICE_ImageLoader_list = &rec; + } + + static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) + { + if (checkFileName) + { + const char *p=filename; + while (*p)p++; + while (p>filename && *p != '\\' && *p != '/' && *p != '.') p--; + if (stricmp(p,".bmp")) return 0; + } + return LICE_LoadBMP(filename,bmpbase); + } + static const char *get_extlist() + { + return "BMP files (*.BMP)\0*.BMP\0"; + } + +}; + +LICE_BMPLoader LICE_bmpldr; diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_colorspace.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_colorspace.cpp new file mode 100644 index 0000000..f8acb0d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_colorspace.cpp @@ -0,0 +1,151 @@ +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "lice.h" +#include + +#define LICE_COMBINE_IMPLEMENT_HSV +#include "lice_combine.h" + + +LICE_pixel LICE_AlterColorHSV_int(LICE_pixel color, int dH, int dS, int dV) // H is rolled over [0,384), S and V are clamped [0,255) +{ + int h, s, v; + LICE_RGB2HSV(LICE_GETR(color), LICE_GETG(color), LICE_GETB(color), &h, &s, &v); + + h += dH; + s += dS; + v += dV; + + if (h < 0) h += 384; + else if (h >= 384) h -= 384; + + if (s & ~255) + { + if (s<0) s = 0; + else s = 255; + } + + if (v&~255) + { + if (v < 0) v = 0.; + else v = 255; + } + + return LICE_HSV2Pix(h, s, v, LICE_GETA(color)); +} + +LICE_pixel LICE_AlterColorHSV(LICE_pixel color, float dH, float dS, float dV) // H is rolled over, S and V are clamped, all [0,1) +{ + int dHi = (int)(dH*384.0f); + int dSi = (int)(dS*255.0f); + int dVi = (int)(dV*255.0f); + return LICE_AlterColorHSV_int(color, dHi, dSi, dVi); +} + +void LICE_AlterBitmapHSV(LICE_IBitmap* src, float dH, float dS, float dV) // H is rolled over, S and V are clamped +{ + if (src) LICE_AlterRectHSV(src,0,0,src->getWidth(),src->getHeight(),dH,dS,dV); +} + +void LICE_AlterRectHSV(LICE_IBitmap* src, int xpos, int ypos, int w, int h, float dH, float dS, float dV, int mode) // H is rolled over, S and V are clamped +{ + if (!src) return; + + int destbm_w = src->getWidth(), destbm_h = src->getHeight(); + const int __sc = (int)src->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + if (!(mode & LICE_BLIT_IGNORE_SCALING)) + { + __LICE_SC(w); + __LICE_SC(h); + __LICE_SC(xpos); + __LICE_SC(ypos); + } + } + + if (xpos < 0) { + w += xpos; + xpos = 0; + } + if (ypos < 0) { + h += ypos; + ypos = 0; + } + + const int span = src->getRowSpan(); + if (span < 1 || w < 1 || h < 1 || xpos >= destbm_w || ypos >= destbm_h) return; + + if (w > destbm_w - xpos) w = destbm_w - xpos; + if (h > destbm_h - ypos) h = destbm_h - ypos; + + LICE_pixel* px = src->getBits()+ypos*span+xpos; + + int dHi = (int)(dH*384.0f); + int dSi = (int)(dS*255.0f); + int dVi = (int)(dV*255.0f); + if (dHi > 383) dHi=383; + else if (dHi < -383) dHi=-383; + + + if (!dHi && !dSi && !dVi) return; // no mod + + if (w*h > 8192) + { + // generate a table of HSV translations with clip/clamp + unsigned char stab[256], vtab[256]; + short htab[384]; + int x; + for(x=0;x<256;x++) + { + int a=x+dSi; + if(a<0)a=0; else if (a>255)a=255; + stab[x]=a; + + a=x+dVi; + if(a<0)a=0; else if (a>255)a=255; + vtab[x]=a; + + a=x+dHi; + if(a<0)a+=384; else if (a>=384)a-=384; + htab[x]=a; + } + for(;x<384;x++) + { + int a=x+dHi; + if(a<0)a+=384; else if (a>=384)a-=384; + htab[x]=a; + } + + while (h-->0) + { + LICE_pixel* tpx = px; + px+=span; + int xi=w; + while (xi-->0) + { + LICE_pixel color = *tpx; + int hh,s,v; + LICE_RGB2HSV(LICE_GETR(color), LICE_GETG(color), LICE_GETB(color), &hh, &s, &v); + *tpx++ = LICE_HSV2Pix(htab[hh],stab[s],vtab[v],LICE_GETA(color)); + } + } + } + else + { + while (h-->0) + { + LICE_pixel* tpx = px; + px+=span; + int xi=w; + while (xi-->0) + { + *tpx = LICE_AlterColorHSV_int(*tpx, dHi, dSi, dVi); + tpx++; + } + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_combine.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_combine.h new file mode 100644 index 0000000..6b355e8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_combine.h @@ -0,0 +1,846 @@ +#ifndef _LICE_COMBINE_H_ +#define _LICE_COMBINE_H_ + +#include "../wdltypes.h" + +#if defined(_MSC_VER) +#pragma warning(disable:4244) // float-to-int +#endif + +#define __LICE_BOUND(x,lo,hi) ((x)<(lo)?(lo):((x)>(hi)?(hi):(x))) + + +#define LICE_PIXEL_HALF(x) (((x)>>1)&0x7F7F7F7F) +#define LICE_PIXEL_QUARTER(x) (((x)>>2)&0x3F3F3F3F) +#define LICE_PIXEL_EIGHTH(x) (((x)>>3)&0x1F1F1F1F) + + +static inline void __LICE_BilinearFilterI(int *r, int *g, int *b, int *a, const LICE_pixel_chan *pin, const LICE_pixel_chan *pinnext, unsigned int xfrac, unsigned int yfrac) +{ + const unsigned int f4=(xfrac*yfrac)>>16; + const unsigned int f3=yfrac-f4; // (1.0-xfrac)*yfrac; + const unsigned int f2=xfrac-f4; // xfrac*(1.0-yfrac); + const unsigned int f1=65536-yfrac-xfrac+f4; // (1.0-xfrac)*(1.0-yfrac); + #define DOCHAN(output, inchan) \ + (output)=(pin[(inchan)]*f1 + pin[4+(inchan)]*f2 + pinnext[(inchan)]*f3 + pinnext[4+(inchan)]*f4)>>16; + DOCHAN(*r,LICE_PIXEL_R) + DOCHAN(*g,LICE_PIXEL_G) + DOCHAN(*b,LICE_PIXEL_B) + DOCHAN(*a,LICE_PIXEL_A) + #undef DOCHAN +} + +static inline void __LICE_BilinearFilterIPixOut(LICE_pixel_chan *out, const LICE_pixel_chan *pin, const LICE_pixel_chan *pinnext, unsigned int xfrac, unsigned int yfrac) +{ + const unsigned int f4=(xfrac*yfrac)>>16; + const unsigned int f3=yfrac-f4; // (1.0-xfrac)*yfrac; + const unsigned int f2=xfrac-f4; // xfrac*(1.0-yfrac); + const unsigned int f1=65536-yfrac-xfrac+f4; // (1.0-xfrac)*(1.0-yfrac); + #define DOCHAN(inchan) \ + (out[inchan])=(pin[(inchan)]*f1 + pin[4+(inchan)]*f2 + pinnext[(inchan)]*f3 + pinnext[4+(inchan)]*f4)>>16; + DOCHAN(LICE_PIXEL_R) + DOCHAN(LICE_PIXEL_G) + DOCHAN(LICE_PIXEL_B) + DOCHAN(LICE_PIXEL_A) + #undef DOCHAN +} + + +static inline void __LICE_BilinearFilterI_2(int *r, int *g, int *b, int *a, const LICE_pixel_chan *pin, const LICE_pixel_chan *pinnext, int npoffs, unsigned int xfrac, unsigned int yfrac) +{ + const unsigned int f4=(xfrac*yfrac)>>16; + const unsigned int f3=yfrac-f4; // (1.0-xfrac)*yfrac; + const unsigned int f2=xfrac-f4; // xfrac*(1.0-yfrac); + const unsigned int f1=65536-yfrac-xfrac+f4; // (1.0-xfrac)*(1.0-yfrac); + *r=(pin[LICE_PIXEL_R]*f1 + pin[npoffs+LICE_PIXEL_R]*f2 + pinnext[LICE_PIXEL_R]*f3 + pinnext[npoffs+LICE_PIXEL_R]*f4)>>16; + *g=(pin[LICE_PIXEL_G]*f1 + pin[npoffs+LICE_PIXEL_G]*f2 + pinnext[LICE_PIXEL_G]*f3 + pinnext[npoffs+LICE_PIXEL_G]*f4)>>16; + *b=(pin[LICE_PIXEL_B]*f1 + pin[npoffs+LICE_PIXEL_B]*f2 + pinnext[LICE_PIXEL_B]*f3 + pinnext[npoffs+LICE_PIXEL_B]*f4)>>16; + *a=(pin[LICE_PIXEL_A]*f1 + pin[npoffs+LICE_PIXEL_A]*f2 + pinnext[LICE_PIXEL_A]*f3 + pinnext[npoffs+LICE_PIXEL_A]*f4)>>16; +} + + +static inline void __LICE_LinearFilterI(int *r, int *g, int *b, int *a, const LICE_pixel_chan *pin, const LICE_pixel_chan *pinnext, unsigned int frac) +{ + const unsigned int f=65536-frac; + *r=(pin[LICE_PIXEL_R]*f + pinnext[LICE_PIXEL_R]*frac)>>16; + *g=(pin[LICE_PIXEL_G]*f + pinnext[LICE_PIXEL_G]*frac)>>16; + *b=(pin[LICE_PIXEL_B]*f + pinnext[LICE_PIXEL_B]*frac)>>16; + *a=(pin[LICE_PIXEL_A]*f + pinnext[LICE_PIXEL_A]*frac)>>16; +} +static inline void __LICE_LinearFilterIPixOut(LICE_pixel_chan *out, const LICE_pixel_chan *pin, const LICE_pixel_chan *pinnext, unsigned int frac) +{ + const unsigned int f=65536-frac; + out[LICE_PIXEL_R]=(pin[LICE_PIXEL_R]*f + pinnext[LICE_PIXEL_R]*frac)>>16; + out[LICE_PIXEL_G]=(pin[LICE_PIXEL_G]*f + pinnext[LICE_PIXEL_G]*frac)>>16; + out[LICE_PIXEL_B]=(pin[LICE_PIXEL_B]*f + pinnext[LICE_PIXEL_B]*frac)>>16; + out[LICE_PIXEL_A]=(pin[LICE_PIXEL_A]*f + pinnext[LICE_PIXEL_A]*frac)>>16; +} + +static void inline _LICE_MakePixelClamp(LICE_pixel_chan *out, int r, int g, int b, int a) +{ +#define LICE_PIX_MAKECHAN(a,b) out[a] = (b&~0xff) ? (b<0?0:255) : b; + LICE_PIX_MAKECHAN(LICE_PIXEL_B,b) + LICE_PIX_MAKECHAN(LICE_PIXEL_G,g) + LICE_PIX_MAKECHAN(LICE_PIXEL_R,r) + LICE_PIX_MAKECHAN(LICE_PIXEL_A,a) +#undef LICE_PIX_MAKECHAN +} + +static void inline _LICE_MakePixelNoClamp(LICE_pixel_chan *out, LICE_pixel_chan r, LICE_pixel_chan g, LICE_pixel_chan b, LICE_pixel_chan a) +{ +#define LICE_PIX_MAKECHAN(a,b) out[a] = b; + LICE_PIX_MAKECHAN(LICE_PIXEL_B,b) + LICE_PIX_MAKECHAN(LICE_PIXEL_G,g) + LICE_PIX_MAKECHAN(LICE_PIXEL_R,r) + LICE_PIX_MAKECHAN(LICE_PIXEL_A,a) +#undef LICE_PIX_MAKECHAN +} + + + +#define HSV_P v*(256-s)/256 +#define HSV_Q(hval) v*(16384-(hval)*s)/16384 +#define HSV_T(hval) v*(16384-(64-(hval))*s)/16384 +#define HSV_X v +extern unsigned short _LICE_RGB2HSV_invtab[256]; // 65536/idx - 1 + +#ifdef LICE_COMBINE_IMPLEMENT_HSV + LICE_pixel LICE_HSV2Pix(int h, int s, int v, int alpha) + #define __LICE_HSV2Pix LICE_HSV2Pix +#else + static inline LICE_pixel __LICE_HSV2Pix(int h, int s, int v, int alpha) +#endif +{ + if (h<192) + { + if (h<64) return LICE_RGBA(HSV_X,HSV_T(h),HSV_P,alpha); + if (h<128) return LICE_RGBA(HSV_Q(h-64),HSV_X,HSV_P,alpha); + return LICE_RGBA(HSV_P,HSV_X,HSV_T(h-128),alpha); + } + if (h < 256) return LICE_RGBA(HSV_P,HSV_Q(h-192),HSV_X,alpha); + if (h < 320) return LICE_RGBA(HSV_T(h-256),HSV_P,HSV_X,alpha); + return LICE_RGBA(HSV_X,HSV_P,HSV_Q(h-320),alpha); +} + +#ifdef LICE_COMBINE_IMPLEMENT_HSV +void LICE_HSV2RGB(int h, int s, int v, int* r, int* g, int* b) +#define __LICE_HSV2RGB LICE_HSV2RGB +#else +static inline void __LICE_HSV2RGB(int h, int s, int v, int* r, int* g, int* b) +#endif +{ + if (h<192) + { + if (h<64) + { + *r = HSV_X; *g = HSV_T(h); *b = HSV_P; + } + else if (h<128) + { + *r = HSV_Q(h-64); *g = HSV_X; *b = HSV_P; + } + else + { + *r = HSV_P; *g = HSV_X; *b = HSV_T(h-128); + } + } + else + { + if (h < 256) + { + *r = HSV_P; *g = HSV_Q(h-192); *b = HSV_X; + } + else if (h < 320) + { + *r = HSV_T(h-256); *g = HSV_P; *b = HSV_X; + } + else + { + *r = HSV_X; *g = HSV_P; *b = HSV_Q(h-320); + } + } +} + + +#define LICE_RGB2HSV_USE_TABLE +// h = [0,384), s and v = [0,256) + +#ifdef LICE_COMBINE_IMPLEMENT_HSV + void LICE_RGB2HSV(int r, int g, int b, int* h, int* s, int* v) + #define __LICE_RGB2HSV LICE_RGB2HSV +#else + static inline void __LICE_RGB2HSV(int r, int g, int b, int* h, int* s, int* v) +#endif +{ + + // this makes it just 3 conditional branches per call + int df,d,maxrgb; + int degoffs; + if (g > r) + { + if (g>b) // green max + { + maxrgb=g; + degoffs=128; + df = maxrgb - lice_min(b,r); + d=b-r; + } + else // blue max + { + maxrgb=b; + degoffs=256; + df = maxrgb - lice_min(g,r); + d=r-g; + } + } + else // r >= g + { + if (r > b) // red max + { + maxrgb=r; + + if (g>1, + (dest[LICE_PIXEL_G]+g)>>1, + (dest[LICE_PIXEL_B]+b)>>1, + (dest[LICE_PIXEL_A]+a)>>1); + } +}; + +class _LICE_CombinePixelsHalfMixFAST +{ +public: + static inline void doPixFAST(LICE_pixel *dest, LICE_pixel src) // src is full range + { + *dest = ((*dest>>1) &0x7f7f7f7f) + ((src>>1)&0x7f7f7f7f); + } +}; + +class _LICE_CombinePixelsHalfMixClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + _LICE_MakePixelClamp(dest, + (dest[LICE_PIXEL_R]+r)>>1, + (dest[LICE_PIXEL_G]+g)>>1, + (dest[LICE_PIXEL_B]+b)>>1, + (dest[LICE_PIXEL_A]+a)>>1); + } + +}; + + +class _LICE_CombinePixelsHalfMix2FAST +{ +public: + static inline void doPixFAST(LICE_pixel *dest, LICE_pixel src) // src is pre-halfed and masked + { + *dest = ((*dest>>1) &0x7f7f7f7f) + src; + } +}; + +class _LICE_CombinePixelsQuarterMix2FAST +{ +public: + static inline void doPixFAST(LICE_pixel *dest, LICE_pixel src) // src is pre-quartered and masked + { + LICE_pixel tmp = *dest; + *dest = ((tmp>>1) &0x7f7f7f7f) + ((tmp>>2) &0x3f3f3f3f) + src; + } +}; + +class _LICE_CombinePixelsThreeEighthMix2FAST +{ +public: + static inline void doPixFAST(LICE_pixel *dest, LICE_pixel src) // src is pre-three-eighthed and masked + { + LICE_pixel tmp = *dest; + *dest = ((tmp>>1) &0x7f7f7f7f) + ((tmp>>3) &0x1f1f1f1f) + src; + } +}; + +class _LICE_CombinePixelsThreeQuarterMix2FAST +{ +public: + static inline void doPixFAST(LICE_pixel *dest, LICE_pixel src) // src is pre-three-quartered and masked + { + *dest = ((*dest>>2) &0x3f3f3f3f) + src; + } +}; + +class _LICE_CombinePixelsCopyNoClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + const int sc=(256-alpha); + + // don't check alpha=0 here, since the caller should (since alpha is usually used for static alphas) + _LICE_MakePixelNoClamp(dest, + r + ((dest[LICE_PIXEL_R]-r)*sc)/256, + g + ((dest[LICE_PIXEL_G]-g)*sc)/256, + b + ((dest[LICE_PIXEL_B]-b)*sc)/256, + a + ((dest[LICE_PIXEL_A]-a)*sc)/256); + } +}; + +class _LICE_CombinePixelsCopyClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + const int sc=(256-alpha); + + // don't check alpha=0 here, since the caller should (since alpha is usually used for static alphas) + _LICE_MakePixelClamp(dest, + r + ((dest[LICE_PIXEL_R]-r)*sc)/256, + g + ((dest[LICE_PIXEL_G]-g)*sc)/256, + b + ((dest[LICE_PIXEL_B]-b)*sc)/256, + a + ((dest[LICE_PIXEL_A]-a)*sc)/256); + } +}; + +class _LICE_CombinePixelsCopySourceAlphaNoClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + if (a) + { + const int sc2=(alpha*(a+1))/256; + const int sc = 256 - sc2; + + _LICE_MakePixelNoClamp(dest, + r + ((dest[LICE_PIXEL_R]-r)*sc)/256, + g + ((dest[LICE_PIXEL_G]-g)*sc)/256, + b + ((dest[LICE_PIXEL_B]-b)*sc)/256, + lice_min(255,sc2 + dest[LICE_PIXEL_A])); + } + } +}; + +class _LICE_CombinePixelsCopySourceAlphaClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + if (a) + { + const int sc2=(alpha*(a+1))/256; + const int sc = 256 - sc2; + + _LICE_MakePixelClamp(dest, + r + ((dest[LICE_PIXEL_R]-r)*sc)/256, + g + ((dest[LICE_PIXEL_G]-g)*sc)/256, + b + ((dest[LICE_PIXEL_B]-b)*sc)/256, + sc2 + dest[LICE_PIXEL_A]); + } + } +}; +class _LICE_CombinePixelsCopySourceAlphaIgnoreAlphaParmNoClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + if (a) + { + if (a==255) + { + _LICE_MakePixelNoClamp(dest,r,g,b,a); + } + else + { + const int sc=(255-a); + + _LICE_MakePixelNoClamp(dest, + r + ((dest[LICE_PIXEL_R]-r)*sc)/256, + g + ((dest[LICE_PIXEL_G]-g)*sc)/256, + b + ((dest[LICE_PIXEL_B]-b)*sc)/256, + lice_min(255,a + dest[LICE_PIXEL_A])); + } + } + } +}; +class _LICE_CombinePixelsCopySourceAlphaIgnoreAlphaParmClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + if (a) + { + if (a==255) + { + _LICE_MakePixelClamp(dest,r,g,b,a); + } + else + { + const int sc=(255-a); + + _LICE_MakePixelClamp(dest, + r + ((dest[LICE_PIXEL_R]-r)*sc)/256, + g + ((dest[LICE_PIXEL_G]-g)*sc)/256, + b + ((dest[LICE_PIXEL_B]-b)*sc)/256, + a + dest[LICE_PIXEL_A]); + } + } + } +}; + +#ifndef LICE_DISABLE_BLEND_ADD + +class _LICE_CombinePixelsAdd +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + // don't check alpha=0 here, since the caller should (since alpha is usually used for static alphas) + + _LICE_MakePixelClamp(dest, + dest[LICE_PIXEL_R]+(r*alpha)/256, + dest[LICE_PIXEL_G]+(g*alpha)/256, + dest[LICE_PIXEL_B]+(b*alpha)/256, + dest[LICE_PIXEL_A]+(a*alpha)/256); + + } +}; +class _LICE_CombinePixelsAddSourceAlpha +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + if (a) + { + alpha=(alpha*(a+1))/256; + _LICE_MakePixelClamp(dest, + dest[LICE_PIXEL_R]+(r*alpha)/256, + dest[LICE_PIXEL_G]+(g*alpha)/256, + dest[LICE_PIXEL_B]+(b*alpha)/256, + dest[LICE_PIXEL_A]+(a*alpha)/256); + } + } +}; + +#else // !LICE_DISABLE_BLEND_ADD +#define _LICE_CombinePixelsAddSourceAlpha _LICE_CombinePixelsCopySourceAlphaClamp +#define _LICE_CombinePixelsAdd _LICE_CombinePixelsCopyClamp +#endif + +#ifndef LICE_DISABLE_BLEND_DODGE + +class _LICE_CombinePixelsColorDodge +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + const int src_r = 256-r*alpha/256; + const int src_g = 256-g*alpha/256; + const int src_b = 256-b*alpha/256; + const int src_a = 256-a*alpha/256; + + _LICE_MakePixelClamp(dest, + src_r > 1 ? 256*dest[LICE_PIXEL_R] / src_r : 256*dest[LICE_PIXEL_R], + src_g > 1 ? 256*dest[LICE_PIXEL_G] / src_g : 256*dest[LICE_PIXEL_G], + src_b > 1 ? 256*dest[LICE_PIXEL_B] / src_b : 256*dest[LICE_PIXEL_B], + src_a > 1 ? 256*dest[LICE_PIXEL_A] / src_a : 256*dest[LICE_PIXEL_A]); + } +}; + +class _LICE_CombinePixelsColorDodgeSourceAlpha +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + const int ualpha=(alpha*(a+1))/256; + + const int src_r = 256-r*ualpha/256; + const int src_g = 256-g*ualpha/256; + const int src_b = 256-b*ualpha/256; + const int src_a = 256-a*ualpha/256; + + _LICE_MakePixelClamp(dest, + src_r > 1 ? 256*dest[LICE_PIXEL_R] / src_r : 256*dest[LICE_PIXEL_R], + src_g > 1 ? 256*dest[LICE_PIXEL_G] / src_g : 256*dest[LICE_PIXEL_G], + src_b > 1 ? 256*dest[LICE_PIXEL_B] / src_b : 256*dest[LICE_PIXEL_B], + src_a > 1 ? 256*dest[LICE_PIXEL_A] / src_a : 256*dest[LICE_PIXEL_A]); + } +}; + +#else // !LICE_DISABLE_BLEND_DODGE +#define _LICE_CombinePixelsColorDodgeSourceAlpha _LICE_CombinePixelsCopySourceAlphaClamp +#define _LICE_CombinePixelsColorDodge _LICE_CombinePixelsCopyClamp +#endif + + +#ifndef LICE_DISABLE_BLEND_MUL + +class _LICE_CombinePixelsMulNoClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + // we could check alpha=0 here, but the caller should (since alpha is usually used for static alphas) + + const int da=(256-alpha)*256; + _LICE_MakePixelNoClamp(dest, + (dest[LICE_PIXEL_R]*(da + (r*alpha)))>>16, + (dest[LICE_PIXEL_G]*(da + (g*alpha)))>>16, + (dest[LICE_PIXEL_B]*(da + (b*alpha)))>>16, + (dest[LICE_PIXEL_A]*(da + (a*alpha)))>>16); + + } +}; +class _LICE_CombinePixelsMulClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + // we could check alpha=0 here, but the caller should (since alpha is usually used for static alphas) + + const int da=(256-alpha)*256; + _LICE_MakePixelClamp(dest, + (dest[LICE_PIXEL_R]*(da + (r*alpha)))>>16, + (dest[LICE_PIXEL_G]*(da + (g*alpha)))>>16, + (dest[LICE_PIXEL_B]*(da + (b*alpha)))>>16, + (dest[LICE_PIXEL_A]*(da + (a*alpha)))>>16); + + } +}; +class _LICE_CombinePixelsMulSourceAlphaNoClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + if (a) + { + const int ualpha=(alpha*(a+1))/256; + const int da=(256-ualpha)*256; + _LICE_MakePixelNoClamp(dest, + (dest[LICE_PIXEL_R]*(da + (r*ualpha)))>>16, + (dest[LICE_PIXEL_G]*(da + (g*ualpha)))>>16, + (dest[LICE_PIXEL_B]*(da + (b*ualpha)))>>16, + (dest[LICE_PIXEL_A]*(da + (a*ualpha)))>>16); + + } + } +}; +class _LICE_CombinePixelsMulSourceAlphaClamp +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + if (a) + { + const int ualpha=(alpha*(a+1))/256; + const int da=(256-ualpha)*256; + _LICE_MakePixelClamp(dest, + (dest[LICE_PIXEL_R]*(da + (r*ualpha)))>>16, + (dest[LICE_PIXEL_G]*(da + (g*ualpha)))>>16, + (dest[LICE_PIXEL_B]*(da + (b*ualpha)))>>16, + (dest[LICE_PIXEL_A]*(da + (a*ualpha)))>>16); + + } + } +}; + +#else // !LICE_DISABLE_BLEND_MUL +#define _LICE_CombinePixelsMulSourceAlphaNoClamp _LICE_CombinePixelsCopySourceAlphaNoClamp +#define _LICE_CombinePixelsMulSourceAlphaClamp _LICE_CombinePixelsCopySourceAlphaClamp +#define _LICE_CombinePixelsMulNoClamp _LICE_CombinePixelsCopyNoClamp +#define _LICE_CombinePixelsMulClamp _LICE_CombinePixelsCopyClamp +#endif + +//#define LICE_DISABLE_BLEND_OVERLAY +#ifndef LICE_DISABLE_BLEND_OVERLAY + +class _LICE_CombinePixelsOverlay +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + // we could check alpha=0 here, but the caller should (since alpha is usually used for static alphas) + + int destr = dest[LICE_PIXEL_R], destg = dest[LICE_PIXEL_G], destb = dest[LICE_PIXEL_B], desta = dest[LICE_PIXEL_A]; + +#if 0 + int srcr = r*alpha, srcg = g*alpha, srcb = b*alpha, srca = a*alpha; + int da=(256-alpha)*256; + int mr = (destr*(da+srcr))/65536; + int mg = (destg*(da+srcg))/65536; + int mb = (destb*(da+srcb))/65536; + int ma = (desta*(da+srca))/65536; + int sr = 256-(65536-srcr)*(256-destr)/65536; + int sg = 256-(65536-srcg)*(256-destg)/65536; + int sb = 256-(65536-srcb)*(256-destb)/65536; + int sa = 256-(65536-srca)*(256-desta)/65536; + + destr = (destr*sr+(256-destr)*mr)/256; + destg = (destg*sg+(256-destg)*mg)/256; + destb = (destb*sb+(256-destb)*mb)/256; + desta = (desta*sa+(256-desta)*ma)/256; +#else + // can produce slightly diff (+-1) results from above due to rounding + const int da=(256-alpha)*128; + const int srcr = r*alpha+da, srcg = g*alpha+da, srcb = b*alpha+da, srca = a*alpha + da; + destr = ( destr*( (destr*(32768-srcr))/256 + srcr ) ) >> 15; + destg = ( destg*( (destg*(32768-srcg))/256 + srcg ) ) >> 15; + destb = ( destb*( (destb*(32768-srcb))/256 + srcb ) ) >> 15; + desta = ( desta*( (desta*(32768-srca))/256 + srca ) ) >> 15; + +#endif + + _LICE_MakePixelClamp(dest, destr, destg, destb, desta); + } +}; + +class _LICE_CombinePixelsOverlaySourceAlpha +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + _LICE_CombinePixelsOverlay::doPix(dest, r, g, b, a, (alpha*(a+1))/256); + } +}; + +#else // !LICE_DISABLE_BLEND_OVERLAY +#define _LICE_CombinePixelsOverlaySourceAlpha _LICE_CombinePixelsCopySourceAlphaClamp +#define _LICE_CombinePixelsOverlay _LICE_CombinePixelsCopyClamp +#endif + + +//#define LICE_DISABLE_BLEND_HSVADJ +#ifndef LICE_DISABLE_BLEND_HSVADJ + +class _LICE_CombinePixelsHSVAdjust +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + int h,s,v; + __LICE_RGB2HSV(dest[LICE_PIXEL_R],dest[LICE_PIXEL_G],dest[LICE_PIXEL_B],&h,&s,&v); + h+=(((r+r/2) - 192) * alpha)/256; + if (h<0)h+=384; + else if (h>=384) h-=384; + s+=((g-128)*alpha)/128; + if (s&~0xff) + { + if (s<0)s=0; + else s=255; + } + v+=((b-128)*alpha)/128; + if (v&~0xff) + { + if (v<0)v=0; + else v=255; + } + + *(LICE_pixel *)dest = __LICE_HSV2Pix(h,s,v,a); + } +}; + +class _LICE_CombinePixelsHSVAdjustSourceAlpha +{ +public: + static inline void doPix(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha) + { + _LICE_CombinePixelsHSVAdjust::doPix(dest, r, g, b, a, (alpha*(a+1))/256); + } +}; + +#else // !LICE_DISABLE_BLEND_HSVADJ +#define _LICE_CombinePixelsHSVAdjustSourceAlpha _LICE_CombinePixelsCopySourceAlphaClamp +#define _LICE_CombinePixelsHSVAdjust _LICE_CombinePixelsCopyClamp +#endif + +// note: the "clamp" parameter would generally be false, unless you're working with +// input colors that need to be clamped (i.e. if you have a r value of >255 or <0, etc. +// if your input is LICE_pixel only then use false, and it will clamp as needed depending +// on the blend mode.. + +//#define __LICE__ACTION(comb) templateclass::function(parameters) +//__LICE_ACTION_SRCALPHA(mode,alpha,clamp); +//#undef __LICE__ACTION + + +// use this for paths that support LICE_BLIT_USE_ALPHA (source-alpha combining), but +// otherwise have constant alpha +#define __LICE_ACTION_SRCALPHA(mode,ia,clamp) \ + if ((ia)!=0) switch ((mode)&(LICE_BLIT_MODE_MASK|LICE_BLIT_USE_ALPHA)) { \ + case LICE_BLIT_MODE_COPY: if ((ia)>0) { \ + if (clamp) { \ + if ((ia)==256) { __LICE__ACTION(_LICE_CombinePixelsClobberClamp); } \ + else { __LICE__ACTION(_LICE_CombinePixelsCopyClamp); } \ + } else { \ + if ((ia)==256) { __LICE__ACTION(_LICE_CombinePixelsClobberNoClamp); } \ + else { __LICE__ACTION(_LICE_CombinePixelsCopyNoClamp); } \ + } \ + } \ + break; \ + case LICE_BLIT_MODE_ADD: __LICE__ACTION(_LICE_CombinePixelsAdd); break; \ + case LICE_BLIT_MODE_DODGE: __LICE__ACTION(_LICE_CombinePixelsColorDodge); break; \ + case LICE_BLIT_MODE_MUL: \ + if (clamp) { __LICE__ACTION(_LICE_CombinePixelsMulClamp); } \ + else { __LICE__ACTION(_LICE_CombinePixelsMulNoClamp); } \ + break; \ + case LICE_BLIT_MODE_OVERLAY: __LICE__ACTION(_LICE_CombinePixelsOverlay); break; \ + case LICE_BLIT_MODE_HSVADJ: __LICE__ACTION(_LICE_CombinePixelsHSVAdjust); break; \ + case LICE_BLIT_MODE_COPY|LICE_BLIT_USE_ALPHA: \ + if (clamp) { \ + if ((ia)==256) { __LICE__ACTION(_LICE_CombinePixelsCopySourceAlphaIgnoreAlphaParmClamp);} \ + else { __LICE__ACTION(_LICE_CombinePixelsCopySourceAlphaClamp); } \ + } else { \ + if ((ia)==256) { __LICE__ACTION(_LICE_CombinePixelsCopySourceAlphaIgnoreAlphaParmNoClamp); } \ + else { __LICE__ACTION(_LICE_CombinePixelsCopySourceAlphaNoClamp); } \ + } \ + break; \ + case LICE_BLIT_MODE_ADD|LICE_BLIT_USE_ALPHA: \ + __LICE__ACTION(_LICE_CombinePixelsAddSourceAlpha); \ + break; \ + case LICE_BLIT_MODE_DODGE|LICE_BLIT_USE_ALPHA: \ + __LICE__ACTION(_LICE_CombinePixelsColorDodgeSourceAlpha); \ + break; \ + case LICE_BLIT_MODE_MUL|LICE_BLIT_USE_ALPHA: \ + if (clamp) { __LICE__ACTION(_LICE_CombinePixelsMulSourceAlphaClamp); } \ + else { __LICE__ACTION(_LICE_CombinePixelsMulSourceAlphaNoClamp); } \ + break; \ + case LICE_BLIT_MODE_OVERLAY|LICE_BLIT_USE_ALPHA: \ + __LICE__ACTION(_LICE_CombinePixelsOverlaySourceAlpha); \ + break; \ + case LICE_BLIT_MODE_HSVADJ|LICE_BLIT_USE_ALPHA: \ + __LICE__ACTION(_LICE_CombinePixelsHSVAdjustSourceAlpha); \ + break; \ + } + + +// use this for paths that can have per pixel alpha, but calculate it themselves +#define __LICE_ACTION_NOSRCALPHA(mode, ia,clamp) \ + if ((ia)!=0) switch ((mode)&LICE_BLIT_MODE_MASK) { \ + case LICE_BLIT_MODE_COPY: if ((ia)>0) { if (clamp) { __LICE__ACTION(_LICE_CombinePixelsCopyClamp); } else { __LICE__ACTION(_LICE_CombinePixelsCopyNoClamp); } } break; \ + case LICE_BLIT_MODE_ADD: __LICE__ACTION(_LICE_CombinePixelsAdd); break; \ + case LICE_BLIT_MODE_DODGE: __LICE__ACTION(_LICE_CombinePixelsColorDodge); break; \ + case LICE_BLIT_MODE_MUL: if (clamp) { __LICE__ACTION(_LICE_CombinePixelsMulClamp); } else { __LICE__ACTION(_LICE_CombinePixelsMulNoClamp); } break; \ + case LICE_BLIT_MODE_OVERLAY: __LICE__ACTION(_LICE_CombinePixelsOverlay); break; \ + case LICE_BLIT_MODE_HSVADJ: __LICE__ACTION(_LICE_CombinePixelsHSVAdjust); break; \ + } + +// For drawing where there is constant alpha and no per-pixel alpha. +#define __LICE_ACTION_CONSTANTALPHA(mode,ia,clamp) \ + if ((ia)!=0) switch ((mode)&LICE_BLIT_MODE_MASK) { \ + case LICE_BLIT_MODE_COPY: \ + if ((ia)==256) { if (clamp) { __LICE__ACTION(_LICE_CombinePixelsClobberClamp); } else { __LICE__ACTION(_LICE_CombinePixelsClobberNoClamp); } } \ + else if ((ia)==128) { if (clamp) { __LICE__ACTION(_LICE_CombinePixelsHalfMixClamp); } else { __LICE__ACTION(_LICE_CombinePixelsHalfMixNoClamp); } } \ + else if ((ia)>0) { if (clamp) { __LICE__ACTION(_LICE_CombinePixelsCopyClamp); } else { __LICE__ACTION(_LICE_CombinePixelsCopyNoClamp); } } \ + break; \ + case LICE_BLIT_MODE_ADD: __LICE__ACTION(_LICE_CombinePixelsAdd); break; \ + case LICE_BLIT_MODE_DODGE: __LICE__ACTION(_LICE_CombinePixelsColorDodge); break; \ + case LICE_BLIT_MODE_MUL: if (clamp) { __LICE__ACTION(_LICE_CombinePixelsMulClamp); } else { __LICE__ACTION(_LICE_CombinePixelsMulNoClamp); } break; \ + case LICE_BLIT_MODE_OVERLAY: __LICE__ACTION(_LICE_CombinePixelsOverlay); break; \ + case LICE_BLIT_MODE_HSVADJ: __LICE__ACTION(_LICE_CombinePixelsHSVAdjust); break; \ + } + +typedef void (*LICE_COMBINEFUNC)(LICE_pixel_chan *dest, int r, int g, int b, int a, int alpha); + +static void WDL_STATICFUNC_UNUSED __LICE_SC_INTERNAL(int &x, int __sc) { + const WDL_INT64 t = (((WDL_INT64) x*(__sc))/256); + x = (int) wdl_clamp(t, -WDL_INT64_CONST(0x80000000), WDL_INT64_CONST(0x7fffffff)); +} +static void __LICE_SCU_INTERNAL(int &x, int __sc) { + const WDL_UINT64 t = (((WDL_UINT64) x*(__sc))>>8); + x = (unsigned int) wdl_min(t, WDL_UINT64_CONST(0xffffffff)); +} + +#if defined(_WIN32) || (defined(__APPLE__) && !defined(__LP64__)) +static void WDL_STATICFUNC_UNUSED __LICE_SC_INTERNAL(LONG &x, int __sc) { + const WDL_INT64 t = (((WDL_INT64) x*(__sc))/256); + x = (LONG) wdl_clamp(t, -WDL_INT64_CONST(0x80000000), WDL_INT64_CONST(0x7fffffff)); +} +#endif + +static void WDL_STATICFUNC_UNUSED __LICE_SC_INTERNAL(float &x, int __sc) { x = (float) (((double)x * __sc) / 256.0); } +static void WDL_STATICFUNC_UNUSED __LICE_SC_INTERNAL(double &x, int __sc) { x = (x * __sc) / 256.0; } + +#define __LICE_SC(x) __LICE_SC_INTERNAL(x,__sc) +#define __LICE_SCU(x) __LICE_SCU_INTERNAL(x,__sc) + +#endif // _LICE_COMBINE_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_extended.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_extended.h new file mode 100644 index 0000000..a453a77 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_extended.h @@ -0,0 +1,164 @@ +#ifndef _LICE_EXTENDED_ +#define _LICE_EXTENDED_ + +#include "lice.h" + +#define DISABLE_LICE_EXTENSIONS + +// stuff to pass to LICE_IBitmap::Extended + +enum // IDs +{ + LICE_EXT_SUPPORTS_ID, // data = ID, returns 1 if that extension ID is supported + LICE_EXT_CLEAR_ACCEL, + LICE_EXT_LINE_ACCEL, + LICE_EXT_FILLRECT_ACCEL, + LICE_EXT_DRAWCBEZIER_ACCEL, + LICE_EXT_DRAWGLYPH_ACCEL, + LICE_EXT_BLIT_ACCEL, + LICE_EXT_SCALEDBLIT_ACCEL, + LICE_EXT_GETFBOTEX_ACCEL, // if the bitmap is implemented as an openGL framebuffer object, get its texture backing store + LICE_EXT_DASHEDLINE_ACCEL, + LICE_EXT_GETPIXEL_ACCEL, + LICE_EXT_PUTPIXEL_ACCEL, + LICE_EXT_SETCLIP, // data == 0 to clear clip + LICE_EXT_WINDOW_BLIT, + LICE_EXT_FORGET, // optimizations can sometimes happen if a bitmap can be told it doesn't need to retain data after it's accessed + LICE_EXT_DRAWTRIANGLE_ACCEL, +}; + +struct LICE_Ext_Line_acceldata +{ + float x1, y1, x2, y2; + LICE_pixel color; + float alpha; + int mode; + bool aa; + + LICE_Ext_Line_acceldata(float _x1, float _y1, float _x2, float _y2, LICE_pixel _color, float _alpha, int _mode, bool _aa) + : x1(_x1), y1(_y1), x2(_x2), y2(_y2), color(_color), alpha(_alpha), mode(_mode), aa(_aa) {} +}; + +struct LICE_Ext_FillRect_acceldata +{ + int x, y, w, h; + LICE_pixel color; + float alpha; + int mode; + + LICE_Ext_FillRect_acceldata(int _x, int _y, int _w, int _h, LICE_pixel _color, float _alpha, int _mode) + : x(_x), y(_y), w(_w), h(_h), color(_color), alpha(_alpha), mode(_mode) {} +}; + +struct LICE_Ext_DrawCBezier_acceldata +{ + float xstart, ystart, xctl1, yctl1, xctl2, yctl2, xend, yend; + LICE_pixel color; + float alpha; + int mode; + bool aa; + + LICE_Ext_DrawCBezier_acceldata(float _xstart, float _ystart, float _xctl1, float _yctl1, float _xctl2, float _yctl2, float _xend, float _yend, + LICE_pixel _color, float _alpha, int _mode, bool _aa) + : xstart(_xstart), ystart(_ystart), xctl1(_xctl1), yctl1(_yctl1), xctl2(_xctl2), yctl2(_yctl2), xend(_xend), yend(_yend), + color(_color), alpha(_alpha), mode(_mode), aa(_aa) {} +}; + +struct LICE_Ext_DrawGlyph_acceldata +{ + int x; + int y; + LICE_pixel color; + const LICE_pixel_chan* alphas; + int glyph_w, glyph_h; + float alpha; + int mode; + + LICE_Ext_DrawGlyph_acceldata(int _x, int _y, LICE_pixel _color, LICE_pixel_chan* _alphas, int _glyph_w, int _glyph_h, float _alpha, int _mode) + : x(_x), y(_y), color(_color), alphas(_alphas), glyph_w(_glyph_w), glyph_h(_glyph_h), alpha(_alpha), mode(_mode) {} +}; + +struct LICE_Ext_Blit_acceldata +{ + LICE_IBitmap* src; + int dstx, dsty, srcx, srcy, srcw, srch; + float alpha; + int mode; + + LICE_Ext_Blit_acceldata(LICE_IBitmap* _src, int _dstx, int _dsty, int _srcx, int _srcy, int _srcw, int _srch, float _alpha, int _mode) + : src(_src), dstx(_dstx), dsty(_dsty), srcx(_srcx), srcy(_srcy), srcw(_srcw), srch(_srch), alpha(_alpha), mode(_mode) {} +}; + +struct LICE_Ext_ScaledBlit_acceldata +{ + LICE_IBitmap* src; + int dstx, dsty, dstw, dsth; + float srcx, srcy, srcw, srch; + float alpha; + int mode; + + LICE_Ext_ScaledBlit_acceldata(LICE_IBitmap* _src, int _dstx, int _dsty, int _dstw, int _dsth, float _srcx, float _srcy, float _srcw, float _srch, float _alpha, int _mode) + : src(_src), dstx(_dstx), dsty(_dsty), dstw(_dstw), dsth(_dsth), srcx(_srcx), srcy(_srcy), srcw(_srcw), srch(_srch), alpha(_alpha), mode(_mode) {} +}; + +struct LICE_Ext_DashedLine_acceldata +{ + float x1, y1, x2, y2; + int pxon, pxoff; + LICE_pixel color; + float alpha; + int mode; + bool aa; + + LICE_Ext_DashedLine_acceldata(float _x1, float _y1, float _x2, float _y2, int _pxon, int _pxoff, LICE_pixel _color, float _alpha, int _mode, bool _aa) + : x1(_x1), y1(_y1), x2(_x2), y2(_y2), pxon(_pxon), pxoff(_pxoff), color(_color), alpha(_alpha), mode(_mode), aa(_aa) {} +}; + +struct LICE_Ext_GetPixel_acceldata +{ + int x, y; + LICE_pixel px; // return + + LICE_Ext_GetPixel_acceldata(int _x, int _y) + : x(_x), y(_y), px(0) {} +}; + +struct LICE_Ext_PutPixel_acceldata +{ + int x, y; + LICE_pixel color; + float alpha; + int mode; + + LICE_Ext_PutPixel_acceldata(int _x, int _y, LICE_pixel _color, float _alpha, int _mode) + : x(_x), y(_y), color(_color), alpha(_alpha), mode(_mode) {} +}; + +struct LICE_Ext_SetClip_data +{ + int x, y, w, h; + + LICE_Ext_SetClip_data(int _x, int _y, int _w, int _h) + : x(_x), y(_y), w(_w), h(_h) {} +}; + +class pl_Mat; + +struct LICE_Ext_DrawTriangle_acceldata +{ + pl_Mat *mat; // will need to include plush.h to access this + double VertexShades[3][3]; // for solid element + float scrx[3], scry[3], scrz[3]; // scrz = 1/Zdist + double mapping_coords[2][3][2]; // [texture or texture2][vertex][uv] +}; + +struct LICE_Ext_WindowBlit_data +{ + HWND hwnd; + int destx, desty, srcx, srcy, w, h; + + LICE_Ext_WindowBlit_data(HWND _hwnd, int _destx, int _desty, int _srcx, int _srcy, int _w, int _h) + : hwnd(_hwnd), destx(_destx), desty(_desty), srcx(_srcx), srcy(_srcy), w(_w), h(_h) {} +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gif.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gif.cpp new file mode 100644 index 0000000..b6989e7 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gif.cpp @@ -0,0 +1,462 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_gif.cpp (GIF loading for LICE) + See lice.h for license and other information +*/ + +#include "lice.h" +#include "../heapbuf.h" +#include "../fileread.h" +#include + +extern "C" { + +#include "../giflib/gif_lib.h" +int _GifError; +}; + +static void applyGifFrameToBitmap(LICE_IBitmap *bmp, GifFileType *fp, int transparent_pix, bool clear) +{ + const int width=fp->Image.Width,height=fp->Image.Height; + + LICE_pixel cmap[256]; + GifPixelType _linebuf[2048]; + GifPixelType *linebuf=width > 2048 ? (GifPixelType*)malloc(width*sizeof(GifPixelType)) : _linebuf; + int y; + + if (bmp) + { + y=0; + if (fp->Image.ColorMap && fp->Image.ColorMap->Colors) for (;y<256 && y < fp->Image.ColorMap->ColorCount;y++) + { + GifColorType *ct=fp->Image.ColorMap->Colors+y; + cmap[y]=LICE_RGBA(ct->Red,ct->Green,ct->Blue,255); + } + + if (fp->SColorMap && fp->SColorMap->Colors) for (;y<256 && y < fp->SColorMap->ColorCount;y++) + { + GifColorType *ct=fp->SColorMap->Colors+y; + cmap[y]=LICE_RGBA(ct->Red,ct->Green,ct->Blue,255); + } + for (;y<256;y++) cmap[y]=0; + + if (clear) + { + LICE_pixel col = 0; + + if (fp->SColorMap && fp->SColorMap->Colors && fp->SBackGroundColor >= 0 && fp->SBackGroundColor < fp->SColorMap->ColorCount) + { + GifColorType *ct=fp->SColorMap->Colors+fp->SBackGroundColor; + col = LICE_RGBA(ct->Red,ct->Green,ct->Blue,0); + } + else if (fp->SBackGroundColor>=0 && fp->SBackGroundColor<256) + { + col = cmap[fp->SBackGroundColor] & LICE_RGBA(255,255,255,0); + } + LICE_Clear(bmp,col); + } + } + + const int bmp_h = bmp ? bmp->getHeight() : 0; + const int bmp_w = bmp ? bmp->getWidth() : 0; + LICE_pixel *bmp_ptr = bmp ? bmp->getBits() : NULL; + int bmp_span = bmp ? bmp->getRowSpan() : 0; + if (bmp && bmp->isFlipped() ) + { + bmp_ptr += (bmp_h-1)*bmp_span; + bmp_span = -bmp_span; + } + const int xpos = fp->Image.Left; + + int skip=0; + int use_width = width; + if (xpos < 0) skip = -xpos; + + if (use_width > bmp_w - xpos) use_width = bmp_w - xpos; + + int ystate = 0, ypass=0; + for (y=0; y < height; y ++) + { + if (DGifGetLine(fp,linebuf,width)==GIF_ERROR) break; + + int ypos; + if (fp->Image.Interlace) + { + if (ypass == 0) + { + ypos = ystate++ * 8; + if (ypos >= height) { ypass++; ystate=0; } + } + if (ypass == 1) + { + ypos = ystate++ * 8 + 4; + if (ypos >= height) { ypass++; ystate=0; } + } + if (ypass == 2) + { + ypos = ystate++ * 4 + 2; + if (ypos >= height) { ypass++; ystate=0; } + } + if (ypass==3) ypos = ystate++ * 2 + 1; + } + else + { + ypos = ystate++; + } + + ypos += fp->Image.Top; + if (ypos < 0 || ypos >= bmp_h) continue; + + LICE_pixel *p = bmp_ptr + ypos*bmp_span + xpos; + int x; + for (x = skip; x < use_width; x ++) + { + const int a = (int) linebuf[x]; + if (a != transparent_pix) p[x]=cmap[a]; + } + } + if (linebuf != _linebuf) free(linebuf); +} + +static int readfunc_fh(GifFileType *fh, GifByteType *buf, int sz) +{ + return ((WDL_FileRead*)fh->UserData)->Read(buf,sz); +} + + +LICE_IBitmap *LICE_LoadGIF(const char *filename, LICE_IBitmap *bmp, int *nframes) +{ + WDL_FileRead fpp(filename,0,65536); + + if (nframes) *nframes=0; + + if (!fpp.IsOpen()) return 0; + + GifFileType *fp=DGifOpen(&fpp, readfunc_fh); + if (!fp) return 0; + + int transparent_pix = -1; + + bool had_image = false; + bool had_delay = false; + bool need_new_frame = false; + + const bool own_bmp = !bmp; + GifRecordType RecordType; + GifByteType *Extension; + int ExtCode; + LICE_WrapperBitmap workbm(NULL,0,0,0,false); + WDL_HeapBuf workbuf(128*1024); + + + do + { + if (DGifGetRecordType(fp, &RecordType) == GIF_ERROR) break; + + switch (RecordType) + { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(fp) == GIF_ERROR) + { + RecordType = TERMINATE_RECORD_TYPE; + break; + } + + if (!bmp) bmp=new WDL_NEW LICE_MemBitmap; + + if (!had_image || (nframes && need_new_frame)) + { + if (had_image) + { + // implies nframes + const int ht = (*nframes+1) * (int)fp->SHeight; + workbm.m_h = ht; + workbm.m_w = (int)fp->SWidth; + workbm.m_span = (workbm.m_w+3)&~3; + workbm.m_buf = (LICE_pixel *) workbuf.ResizeOK(ht * sizeof(LICE_pixel) * workbm.m_span); + if (!workbm.m_buf) + { + RecordType = TERMINATE_RECORD_TYPE; + break; + } + } + else + { + if (bmp) bmp->resize(fp->SWidth,fp->SHeight); + + if (!bmp || bmp->getWidth() != (int)fp->SWidth || bmp->getHeight() != (int)fp->SHeight) + { + RecordType = TERMINATE_RECORD_TYPE; + break; + } + } + + if (nframes) + { + if (*nframes > 0) + { + if (*nframes == 1) + LICE_Blit(&workbm,bmp,0,0,0,0,bmp->getWidth(),bmp->getHeight(),1.0,LICE_BLIT_MODE_COPY); + + LICE_Blit(&workbm,&workbm,0,workbm.getHeight()-(int)fp->SHeight,0, + workbm.getHeight()-(int)fp->SHeight*2, workbm.getWidth(), (int)fp->SHeight,1.0f,LICE_BLIT_MODE_COPY); + } + + *nframes += 1; + } + } + + if (nframes) + { + LICE_SubBitmap tmp(&workbm, 0,workbm.getHeight() - (int) fp->SHeight, workbm.getWidth(), (int)fp->SHeight); + applyGifFrameToBitmap(&tmp,fp,transparent_pix,!had_image); + } + else + applyGifFrameToBitmap(bmp,fp,transparent_pix,!had_image); + + had_image = true; + + need_new_frame = false; + transparent_pix = -1; + + if (had_delay) + { + if (!nframes) + { + RecordType = TERMINATE_RECORD_TYPE; // finish up if first frame is finished + } + else + { + need_new_frame = true; + } + had_delay = false; + } + break; + case EXTENSION_RECORD_TYPE: + if (DGifGetExtension(fp, &ExtCode, &Extension) == GIF_ERROR) + { + RecordType = TERMINATE_RECORD_TYPE; + } + else + { + while (Extension != NULL) + { + if (ExtCode == 0xF9 && *Extension >= 4) + { + transparent_pix = -1; + if (Extension[1]&1) + { + transparent_pix = Extension[4]; + } + if (Extension[2] || Extension[3]) had_delay=true; + } + + if (DGifGetExtensionNext(fp, &Extension) == GIF_ERROR) + { + RecordType = TERMINATE_RECORD_TYPE; + break; + } + } + } + break; + case TERMINATE_RECORD_TYPE: + break; + default: /* Should be traps by DGifGetRecordType. */ + break; + } + } + while (RecordType != TERMINATE_RECORD_TYPE); + + DGifCloseFile(fp); + + if (had_image) + { + if (workbm.getWidth()) LICE_Copy(bmp,&workbm); + return bmp; + } + + if (own_bmp) delete bmp; + return NULL; +} + + + +class LICE_GIFLoader +{ +public: + _LICE_ImageLoader_rec rec; + LICE_GIFLoader() + { + rec.loadfunc = loadfunc; + rec.get_extlist = get_extlist; + rec._next = LICE_ImageLoader_list; + LICE_ImageLoader_list = &rec; + } + + static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) + { + if (checkFileName) + { + const char *p=filename; + while (*p)p++; + while (p>filename && *p != '\\' && *p != '/' && *p != '.') p--; + if (stricmp(p,".gif")) return 0; + } + return LICE_LoadGIF(filename,bmpbase,NULL); + } + static const char *get_extlist() + { + return "GIF files (*.GIF)\0*.GIF\0"; + } + +}; + +LICE_GIFLoader LICE_gifldr; + + + + + +struct lice_gif_read_ctx +{ + WDL_FileRead *fh; + GifFileType *gif; + int msecpos; + int state; + WDL_FILEREAD_POSTYPE ipos; +}; + + +void *LICE_GIF_LoadEx(const char *filename) +{ + WDL_FileRead *fpp = new WDL_FileRead(filename,0,32768); + + if (!fpp->IsOpen()) + { + delete fpp; + return 0; + } + + GifFileType *gif=DGifOpen(fpp, readfunc_fh); + if (!gif) + { + delete fpp; + return 0; + } + + lice_gif_read_ctx *ret = (lice_gif_read_ctx*)calloc(sizeof(lice_gif_read_ctx), 1); + if (!ret) + { + DGifCloseFile(gif); + delete fpp; + return NULL; + } + ret->fh = fpp; + ret->gif = gif; + ret->msecpos = 0; + ret->state = 0; + ret->ipos = fpp->GetPosition(); + + return ret; +} + + +void LICE_GIF_Close(void *handle) +{ + lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; + if (h) + { + DGifCloseFile(h->gif); + delete h->fh; + free(h); + } +} +void LICE_GIF_Rewind(void *handle) +{ + lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; + if (h) + { + h->state = 0; + h->msecpos = 0; + h->fh->SetPosition(h->ipos); + // todo: flush giflib too + } +} +unsigned int LICE_GIF_GetFilePos(void *handle) +{ + // only used for accounting at the moment, so meh + lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; + if (h && h->fh) + { + return (unsigned int) h->fh->GetPosition(); + } + + return 0; +} + +int LICE_GIF_UpdateFrame(void *handle, LICE_IBitmap *bm) +{ + lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; + if (!h||h->state<0) return -2; + GifFileType *fp = h->gif; + + if (bm) + { + bm->resize(fp->SWidth, fp->SHeight); + if (bm->getWidth() != (int)fp->SWidth || bm->getHeight() != (int)fp->SHeight) return -3; + } + + int has_delay = 0; + int transparent_pix = -1; + GifRecordType RecordType; + GifByteType *Extension; + int ExtCode; + do + { + if (DGifGetRecordType(fp, &RecordType) == GIF_ERROR) return h->state = -5; + switch (RecordType) + { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(fp) == GIF_ERROR) return h->state = -4; + + applyGifFrameToBitmap(bm,fp,transparent_pix,!h->state); + h->state += 1; + h->msecpos += has_delay*10; + return has_delay*10; + + case EXTENSION_RECORD_TYPE: + if (DGifGetExtension(fp, &ExtCode, &Extension) == GIF_ERROR) + { + return h->state = -9; + } + else + { + while (Extension != NULL) + { + if (ExtCode == 0xF9 && *Extension >= 4) + { + transparent_pix = -1; + if (Extension[1]&1) + { + transparent_pix = Extension[4]; + } + has_delay = ((int)Extension[3] << 8) + Extension[2]; + if (has_delay == 1) has_delay = 10; // https://www.biphelps.com/blog/The-Fastest-GIF-Does-Not-Exist + } + + if (DGifGetExtensionNext(fp, &Extension) == GIF_ERROR) return h->state = -8; + } + } + break; + case TERMINATE_RECORD_TYPE: + break; + default: /* Should be traps by DGifGetRecordType. */ + break; + } + } + while (RecordType != TERMINATE_RECORD_TYPE); + + return h->state = -10; +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gif_write.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gif_write.cpp new file mode 100644 index 0000000..be01beb --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gif_write.cpp @@ -0,0 +1,491 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_gif.cpp (GIF loading for LICE) + See lice.h for license and other information +*/ + +#include "lice.h" + +#include + +#include "../wdltypes.h" +#include "../filewrite.h" + +extern "C" { + +#include "../giflib/gif_lib.h" +//int _GifError; +}; + + +struct liceGifWriteRec +{ + GifFileType *f; + WDL_FileWrite *fh; + ColorMapObject *cmap; + GifPixelType *linebuf; + LICE_IBitmap *prevframe; // used when multiframe, transalpha<0 + void *last_octree; + LICE_pixel last_palette[256]; + unsigned char from15to8bit[32][32][32];//r,g,b + + int transalpha; + int w,h; + bool append; + bool dither; + bool has_had_frame; + bool has_global_cmap; + + bool has_from15to8bit; // set when last_octree has been generated into from15to8bit +}; + +static inline GifPixelType QuantPixel(LICE_pixel p, liceGifWriteRec *wr) +{ + return wr->from15to8bit[LICE_GETR(p)>>3][LICE_GETG(p)>>3][LICE_GETB(p)>>3]; +} + +static int generate_palette_from_octree(void *ww, void *octree, int numcolors) +{ + liceGifWriteRec *wr = (liceGifWriteRec *)ww; + if (!octree||!ww||numcolors>256) return 0; + + ColorMapObject *cmap = wr->cmap; + + int palette_sz=0; + + // store palette + { + LICE_pixel* palette=wr->last_palette; + + palette_sz = LICE_ExtractOctreePalette(octree, palette); + + int i; + for (i = 0; i < palette_sz; ++i) + { + cmap->Colors[i].Red = LICE_GETR(palette[i]); + cmap->Colors[i].Green = LICE_GETG(palette[i]); + cmap->Colors[i].Blue = LICE_GETB(palette[i]); + } + for (i = palette_sz; i < numcolors; ++i) + { + cmap->Colors[i].Red = cmap->Colors[i].Green = cmap->Colors[i].Blue = 0; + } + } + + wr->has_from15to8bit = false; + wr->has_global_cmap=true; + + return palette_sz; +} + +static void generate15to8(void *ww, void *octree) +{ + liceGifWriteRec *wr = (liceGifWriteRec *)ww; + if (!octree||!ww) return; + + // map palette to 16 bit + unsigned char r,g,b; + for(r=0;r<32;r++) + { + unsigned char cr = r<<3; + for (g=0;g<32;g++) + { + unsigned char cg = g<<3; + for (b=0;b<32;b++) + { + unsigned char cb = b<<3; + LICE_pixel col = LICE_RGBA(cr,cg,cb,0); + wr->from15to8bit[r][g][b] = LICE_FindInOctree(octree, col); + } + } + } + wr->has_from15to8bit=true; +} + +int LICE_SetGIFColorMapFromOctree(void *ww, void *octree, int numcolors) +{ + const int rv = generate_palette_from_octree(ww,octree,numcolors); + generate15to8(ww,octree); + return rv; +} + +unsigned int LICE_WriteGIFGetSize(void *handle) +{ + if (handle) + { + liceGifWriteRec *wr = (liceGifWriteRec*)handle; + if (wr->fh) return (unsigned int) ((WDL_FileWrite *)wr->fh)->GetPosition(); + } + return 0; +} + +bool LICE_WriteGIFFrame(void *handle, LICE_IBitmap *frame, int xpos, int ypos, bool perImageColorMap, int frame_delay, int nreps) +{ + liceGifWriteRec *wr = (liceGifWriteRec*)handle; + if (!wr) return false; + + bool isFirst=false; + if (!wr->has_had_frame) + { + wr->has_had_frame=true; + isFirst=true; + + if (!perImageColorMap && !wr->has_global_cmap) + { + const int ccnt = 256 - (wr->transalpha?1:0); + void* octree = wr->last_octree; + if (!octree) wr->last_octree = octree = LICE_CreateOctree(ccnt); + else LICE_ResetOctree(octree,ccnt); + + if (octree) + { + LICE_BuildOctree(octree, frame); + // sets has_global_cmap + int pcnt = generate_palette_from_octree(wr, octree, ccnt); + + if (pcnt < 256 && wr->transalpha) pcnt++; + int nb = 1; + while (nb < 8 && (1<cmap->ColorCount = 1<cmap->BitsPerPixel=nb; + } + } + + if (!wr->append) EGifPutScreenDesc(wr->f,wr->w,wr->h,8,0,wr->has_global_cmap ? wr->cmap : 0); + + } + + int usew=frame->getWidth(), useh=frame->getHeight(); + if (xpos+usew > wr->w) usew = wr->w-xpos; + if (ypos+useh > wr->h) useh = wr->h-ypos; + if (usew<1||useh<1) return false; + + int pixcnt=usew*useh; + + const int trans_chan_mask = wr->transalpha&0xff; // -1 means 0xff by default, user can change this accordingly + const LICE_pixel trans_mask = LICE_RGBA(trans_chan_mask,trans_chan_mask,trans_chan_mask,0); + const bool advanced_trans_stats = !!(wr->transalpha&0x100); + + if (perImageColorMap && !wr->has_global_cmap) + { + const int ccnt = 256 - (wr->transalpha?1:0); + void* octree = wr->last_octree; + if (!octree) wr->last_octree = octree = LICE_CreateOctree(ccnt); + else LICE_ResetOctree(octree,ccnt); + if (octree) + { + if ((!isFirst || frame_delay) && wr->transalpha<0 && wr->prevframe) + { + LICE_SubBitmap tmpprev(wr->prevframe, xpos, ypos, usew, useh); + int pc=LICE_BuildOctreeForDiff(octree,frame,&tmpprev,trans_mask); + if (!advanced_trans_stats) pixcnt = pc; + } + else if (wr->transalpha>0) + pixcnt=LICE_BuildOctreeForAlpha(octree, frame,wr->transalpha&0xff); + else + LICE_BuildOctree(octree, frame); + + // sets has_global_cmap (clear below) + int pcnt = generate_palette_from_octree(wr, octree, ccnt); + + wr->has_global_cmap=false; + if (pcnt < 256 && wr->transalpha) pcnt++; + int nb = 1; + while (nb < 8 && (1<cmap->ColorCount = 1<cmap->BitsPerPixel=nb; + } + } + + if (!wr->has_from15to8bit && pixcnt > 40000 && wr->last_octree) + { + generate15to8(wr,wr->last_octree); + } + + const unsigned char transparent_pix = wr->cmap->ColorCount-1; + unsigned char gce[4] = { 0, }; + if (wr->transalpha) + { + gce[0] |= 1; + gce[3] = transparent_pix; + } + + int a = frame_delay/10; + if(a<2&&frame_delay)a=2; // https://www.biphelps.com/blog/The-Fastest-GIF-Does-Not-Exist + else if (a>60000) a=60000; + gce[1]=(a)&255; + gce[2]=(a)>>8; + + if (isFirst && frame_delay && nreps!=1 && !wr->append) + { + int nr = nreps > 1 && nreps <= 65536 ? nreps-1 : 0; + unsigned char ext[]={0xB, 'N','E','T','S','C','A','P','E','2','.','0',3,1,(unsigned char) (nr&0xff), (unsigned char) ((nr>>8)&0xff)}; + EGifPutExtension(wr->f,0xFF, sizeof(ext),ext); + } + + if (gce[0]||gce[1]||gce[2]) + EGifPutExtension(wr->f, 0xF9, sizeof(gce), gce); + + + EGifPutImageDesc(wr->f, xpos, ypos, usew,useh, 0, wr->has_global_cmap ? NULL : wr->cmap); + + GifPixelType *linebuf = wr->linebuf; + int y; + + void *use_octree = wr->has_from15to8bit ? NULL : wr->last_octree; + + if ((!isFirst || frame_delay) && wr->transalpha<0) + { + bool ignFr=false; + if (!wr->prevframe) + { + ignFr=true; + wr->prevframe = new WDL_NEW LICE_MemBitmap(wr->w,wr->h); + LICE_Clear(wr->prevframe,0); + } + + LICE_SubBitmap tmp(wr->prevframe,xpos,ypos,usew,useh); + + LICE_pixel last_pixel_rgb=0; + GifPixelType last_pixel_idx=transparent_pix; + + int pix_stats[256]; + if (advanced_trans_stats) memset(pix_stats,0,sizeof(pix_stats)); + pix_stats[transparent_pix] = -8; + + for(y=0;yisFlipped()) rdy = frame->getHeight()-1-y; + if (tmp.isFlipped()) rdy2 = tmp.getHeight()-1-y; + const LICE_pixel *in = frame->getBits() + rdy*frame->getRowSpan(); + const LICE_pixel *in2 = tmp.getBits() + rdy2*tmp.getRowSpan(); + int x; + + if (advanced_trans_stats) + { + if (use_octree) for(x=0;xlast_palette[np]&trans_mask) || pix_stats[transparent_pix] > pix_stats[np]) + last_pixel_idx = transparent_pix; + else + last_pixel_idx = np; + } + } + linebuf[x] = last_pixel_idx; + pix_stats[last_pixel_idx]++; + last_pixel_rgb = p; + } + else for(x=0;xlast_palette[np]&trans_mask) || pix_stats[transparent_pix] > pix_stats[np]) + last_pixel_idx = transparent_pix; + else + last_pixel_idx = np; + } + } + linebuf[x] = last_pixel_idx; + pix_stats[last_pixel_idx]++; + last_pixel_rgb = p; + } + } + else + { + // optimize solids by reusing the same value if previous rgb was the same, also avoid switching between + // from color to transparent if the color hasn't changed + if (use_octree) for(x=0;xf, linebuf, usew); + } + + LICE_Blit(&tmp,frame,0,0,0,0,usew,useh,1.0f,LICE_BLIT_MODE_COPY); + + } + else if (wr->transalpha>0) + { + const unsigned int al = wr->transalpha&0xff; + for(y=0;yisFlipped()) rdy = frame->getHeight()-1-y; + const LICE_pixel *in = frame->getBits() + rdy*frame->getRowSpan(); + int x; + if (use_octree) for(x=0;xf, linebuf, usew); + } + } + else for(y=0;yisFlipped()) rdy = frame->getHeight()-1-y; + const LICE_pixel *in = frame->getBits() + rdy*frame->getRowSpan(); + int x; + if (use_octree) for(x=0;xf, linebuf, usew); + } + + return true; +} + +static int writefunc_fh(GifFileType *fh, const GifByteType *buf, int sz) +{ + return ((WDL_FileWrite *)fh->UserData)->Write(buf,sz); +} + +void *LICE_WriteGIFBeginNoFrame(const char *filename, int w, int h, int transparent_alpha, bool dither, bool is_append) +{ + WDL_FileWrite *fp = new WDL_FileWrite(filename,1,65536,16,16,is_append); + if (!fp->IsOpen()) + { + delete fp; + return NULL; + } + + + EGifSetGifVersion("89a"); + + + GifFileType *f = EGifOpen(fp,writefunc_fh); + if (!f) + { + delete fp; + return NULL; + } + + liceGifWriteRec *wr = (liceGifWriteRec*)calloc(sizeof(liceGifWriteRec),1); + wr->f = f; + wr->fh = fp; + wr->append = is_append; + wr->dither = dither; + wr->w=w; + wr->h=h; + wr->cmap = (ColorMapObject*)calloc(sizeof(ColorMapObject)+256*sizeof(GifColorType),1); + wr->cmap->Colors = (GifColorType*)(wr->cmap+1); + wr->cmap->ColorCount=256; + wr->cmap->BitsPerPixel=8; + wr->has_had_frame=false; + wr->has_global_cmap=false; + wr->has_from15to8bit=false; + wr->last_octree=NULL; + + wr->linebuf = (GifPixelType*)malloc(wr->w*sizeof(GifPixelType)); + wr->transalpha = transparent_alpha; + + return wr; +} +void *LICE_WriteGIFBegin(const char *filename, LICE_IBitmap *firstframe, int transparent_alpha, int frame_delay, bool dither, int nreps) +{ + if (!firstframe) return NULL; + + void *wr=LICE_WriteGIFBeginNoFrame(filename,firstframe->getWidth(),firstframe->getHeight(),transparent_alpha,dither); + if (wr) LICE_WriteGIFFrame(wr,firstframe,0,0,true,frame_delay,nreps); + + return wr; +} + + + +bool LICE_WriteGIFEnd(void *handle) +{ + liceGifWriteRec *wr = (liceGifWriteRec*)handle; + if (!wr) return false; + + int ret = EGifCloseFile(wr->f); + + free(wr->linebuf); + free(wr->cmap); + if (wr->last_octree) LICE_DestroyOctree(wr->last_octree); + + delete wr->prevframe; + delete wr->fh; + + free(wr); + + return ret!=GIF_ERROR; +} + + +bool LICE_WriteGIF(const char *filename, LICE_IBitmap *bmp, int transparent_alpha, bool dither) +{ + // todo: alpha? + if (!bmp) return false; + + int has_transparent = 0; + if (transparent_alpha>0) + { + int y=bmp->getHeight(); + LICE_pixel *p = bmp->getBits(); + int w = bmp->getWidth(); + while (y--&&!has_transparent) + { + int x=w; + while(x--) + { + if (LICE_GETA(*p) < (unsigned int)transparent_alpha) + { + has_transparent=1; + break; + } + p++; + } + p+=bmp->getRowSpan()-w; + } + } + + + void *wr=LICE_WriteGIFBeginNoFrame(filename,bmp->getWidth(),bmp->getHeight(),has_transparent?transparent_alpha:0,dither); + if (!wr) return false; + + LICE_WriteGIFFrame(wr,bmp,0,0,false,0); + + return LICE_WriteGIFEnd(wr); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gl_ctx.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gl_ctx.cpp new file mode 100644 index 0000000..314ff03 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gl_ctx.cpp @@ -0,0 +1,264 @@ +#include "lice_gl_ctx.h" + +#define MAX_CACHED_GLYPHS 4096 + +// create one hidden window per process to hold the openGL state, +// its GL render context stays current for the life of the process, +// we serve all framebuffers from the same render context + +class LICE_GL_ctx +{ +public: + + LICE_GL_ctx(); + ~LICE_GL_ctx(); + + bool IsValid(); + HWND GetWindow() { return m_hwnd; } + void Close(); + + GLUnurbsObj* GetNurbsObj(int linetol=8); // linetol = maximum number of straight-line pixels + + int GetTexFromGlyph(const unsigned char* glyph, int glyph_w, int glyph_h); + void ClearTex(); + + struct GlyphCache + { + unsigned int tex; + unsigned char* glyph; // lives on the heap + int glyph_w, glyph_h; + }; + +private: + + bool Init(); + + bool m_init_tried; + HINSTANCE m_gldll; + HWND m_hwnd; + HGLRC m_glrc; + + GLUnurbsObj* m_nurbs; // keep this here for easy reuse + + GlyphCache m_glyphCache[MAX_CACHED_GLYPHS]; + int m_nCachedGlyphs; +}; + +LICE_GL_ctx::LICE_GL_ctx() +{ + m_init_tried = false; + m_gldll = 0; + m_hwnd = 0; + m_glrc = 0; + m_nurbs = 0; + memset(m_glyphCache, 0, MAX_CACHED_GLYPHS*sizeof(GlyphCache)); + m_nCachedGlyphs = 0; +} + +LICE_GL_ctx::~LICE_GL_ctx() +{ + Close(); +} + +bool LICE_GL_ctx::Init() +{ + m_init_tried = true; + + m_gldll = LoadLibrary("opengl32.dll"); + if (!m_gldll) + { + Close(); + return false; + } + + // create a minimal GL render context to serve FBOs out of + WNDCLASS wc; + memset(&wc, 0, sizeof(WNDCLASS)); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = DefWindowProc; + wc.lpszClassName = "LICE_GL_ctx"; + RegisterClass(&wc); + m_hwnd = CreateWindow("LICE_GL_ctx", "LICE_GL_ctx", 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, GetModuleHandle(0), 0); + HDC dc = GetDC(m_hwnd); + if (!dc) + { + Close(); + return false; + } + + PIXELFORMATDESCRIPTOR pfd; + memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + pfd.cAlphaBits = 8; + int pxfmt = ChoosePixelFormat(dc, &pfd); + if (!SetPixelFormat(dc, pxfmt, &pfd)) + { + Close(); + return false; + } + + m_glrc = wglCreateContext(dc); + if (!wglMakeCurrent(dc, m_glrc)) // render context should stay current throughout + { + Close(); + return false; + } + + char *rendstr = (char*) glGetString(GL_RENDERER); + if (!rendstr || strstr(rendstr, "GDI")) + { + Close(); + return false; + } + + // check now for all the extension functions we will ever need + if (glewInit() != GLEW_OK || + !glewIsSupported("GL_EXT_framebuffer_object") || + !glewIsSupported("GL_ARB_texture_rectangle")) + { + Close(); + return false; + } + + // any one-time initialization goes here + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + ReleaseDC(m_hwnd, dc); + + return true; +} + +bool LICE_GL_ctx::IsValid() +{ + if (m_gldll && m_glrc) return true; + if (!m_init_tried) return Init(); + return false; +} + +void LICE_GL_ctx::Close() +{ + ClearTex(); + if (m_nurbs) + { + gluDeleteNurbsRenderer(m_nurbs); + m_nurbs = 0; + } + if (m_glrc) + { + wglMakeCurrent(0, 0); + wglDeleteContext(m_glrc); + m_glrc = 0; + } + if (m_hwnd) + { + DestroyWindow(m_hwnd); + m_hwnd = 0; + } + if (m_gldll) + { + FreeLibrary(m_gldll); + m_gldll = 0; + } +} + +GLUnurbsObj* LICE_GL_ctx::GetNurbsObj(int linetol) +{ + if (!IsValid()) return 0; + if (!m_nurbs) m_nurbs = gluNewNurbsRenderer(); + if (m_nurbs) gluNurbsProperty(m_nurbs, GLU_SAMPLING_TOLERANCE, (float)linetol); + return m_nurbs; +} + +void LICE_GL_ctx::ClearTex() +{ + int i; + for (i = 0; i < m_nCachedGlyphs; ++i) + { + glDeleteTextures(1, &m_glyphCache[i].tex); + free(m_glyphCache[i].glyph); + memset(&m_glyphCache[i], 0, sizeof(GlyphCache)); + } + m_nCachedGlyphs = 0; +} + +static int _glyphcmp(const void* p1, const void* p2) +{ + LICE_GL_ctx::GlyphCache* gc1 = (LICE_GL_ctx::GlyphCache*) p1; + LICE_GL_ctx::GlyphCache* gc2 = (LICE_GL_ctx::GlyphCache*) p2; + + if (gc1->glyph_w < gc2->glyph_w) return -1; + if (gc1->glyph_w > gc2->glyph_w) return 1; + if (gc1->glyph_h < gc2->glyph_h) return -1; + if (gc1->glyph_h > gc2->glyph_h) return 1; + return memcmp(gc1->glyph, gc2->glyph, gc1->glyph_w*gc1->glyph_h); +} + +int LICE_GL_ctx::GetTexFromGlyph(const unsigned char* glyph, int glyph_w, int glyph_h) +{ + if (!IsValid()) return false; + + GlyphCache gc; + gc.tex = 0; + gc.glyph = (unsigned char *)glyph; + gc.glyph_w = glyph_w; + gc.glyph_h = glyph_h; + + GlyphCache* p = (GlyphCache*) bsearch(&gc, m_glyphCache, m_nCachedGlyphs, sizeof(GlyphCache), _glyphcmp); + if (p) return p->tex; + + glGenTextures(1, &gc.tex); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, gc.tex); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_ALPHA8, glyph_w, glyph_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, glyph); + + if (m_nCachedGlyphs >= MAX_CACHED_GLYPHS) ClearTex(); // quick & dirty + + gc.glyph = (unsigned char*) malloc(glyph_w*glyph_h); + memcpy(gc.glyph, glyph, glyph_w*glyph_h); + m_glyphCache[m_nCachedGlyphs++] = gc; // copy + qsort(m_glyphCache, m_nCachedGlyphs, sizeof(GlyphCache), _glyphcmp); + + return gc.tex; +} + +//////// + +static LICE_GL_ctx s_glctx; // one static opengl context object per process + + +bool LICE_GL_IsValid() +{ + return s_glctx.IsValid(); +} + +HWND LICE_GL_GetWindow() +{ + if (s_glctx.IsValid()) return s_glctx.GetWindow(); + return 0; +} + +void LICE_GL_CloseCtx() +{ + s_glctx.Close(); +} + +GLUnurbsObj* LICE_GL_GetNurbsObj(int linetol) // linetol = maximum number of straight-line pixels +{ + return s_glctx.GetNurbsObj(linetol); +} + +GLuint LICE_GL_GetTexFromGlyph(const unsigned char* glyph, int glyph_w, int glyph_h) +{ + return s_glctx.GetTexFromGlyph(glyph, glyph_w, glyph_h); +} + +void LICE_GL_ClearTex() +{ + s_glctx.ClearTex(); +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gl_ctx.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gl_ctx.h new file mode 100644 index 0000000..2915a06 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_gl_ctx.h @@ -0,0 +1,27 @@ +#ifndef _GL_CTX_ +#define _GL_CTX_ + +#include "lice.h" + +#define GLEW_STATIC +#include "glew/include/gl/glew.h" +#include "glew/include/gl/wglew.h" +#include "glew/include/gl/wglext.h" + +// GL context functions +// opening and managing GL context is handled behind the scenes + + +bool LICE_GL_IsValid(); // GL context is initialized (will be lazy initialized on first call) and valid + +HWND LICE_GL_GetWindow(); // Get the window that owns the GL context (one per process) + +void LICE_GL_CloseCtx(); // Something failed, turn off GL context forever so we don't keep failing + +GLUnurbsObj* LICE_GL_GetNurbsObj(int linetol=8); // linetol = maximum number of straight-line pixels + +// facility for associating a glyph with a texture +GLuint LICE_GL_GetTexFromGlyph(const unsigned char* glyph, int glyph_w, int glyph_h); +void LICE_GL_ClearTex(); + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_glbitmap.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_glbitmap.cpp new file mode 100644 index 0000000..140ab96 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_glbitmap.cpp @@ -0,0 +1,708 @@ +#include "lice_glbitmap.h" +#include "lice_gl_ctx.h" + +#include "../plush2/plush.h" + +LICE_GLBitmap::LICE_GLBitmap() +{ + m_bmp = 0; + m_fbo = 0; + m_tex = 0; + m_bufloc = EMPTY; +} + +// This is separate from the constructor for initialization order reasons +void LICE_GLBitmap::Init(LICE_IBitmap* bmp, int w, int h) +{ + m_bmp = bmp; + if (w > 0 && h > 0) CreateFBO(w, h); +} + +bool LICE_GLBitmap::CreateFBO(int w, int h) +{ + if (LICE_GL_IsValid()) + { + if (m_fbo) ReleaseFBO(); + glGenFramebuffersEXT(1, &m_fbo); // create a new empty FBO + if (m_fbo) + { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo); // bind this FBO so it is the rendering target + glEnable(GL_TEXTURE_RECTANGLE_ARB); // enable texturing + glGenTextures(1, &m_tex); // create a new texture to be the backing store + if (m_tex) + { + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_tex); // bind this texture so it is the texturing target + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // we won't be scaling it for this purpose + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); // size the texture + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, m_tex, 0); // attach the texture as the backing store for the FBO + } + glDisable(GL_TEXTURE_RECTANGLE_ARB); // done texturing + } + + return BindFBO(); // this tests the FBO for validity + } + return false; +} + +LICE_GL_SysBitmap::LICE_GL_SysBitmap(int w, int h) +: m_sysbmp(w, h) +{ + Init(&m_sysbmp, w, h); +} + +LICE_GL_MemBitmap::LICE_GL_MemBitmap(int w, int h) +: m_membmp(w, h) +{ + Init(&m_membmp, w, h); +} + +HDC LICE_GL_SysBitmap::getDC() +{ + // no known way to get a DC directly from an offscreen openGL render context, sadly + FramebufferFromGPU(); + OutputDebugString("GL to screen"); + return m_sysbmp.getDC(); +} + + +LICE_GL_SubBitmap::LICE_GL_SubBitmap(LICE_IBitmap *parent, int x, int y, int w, int h) +{ + m_parent = parent; + m_x = x; + m_y = y; + m_w = w; + m_h = h; +} + +LICE_pixel* LICE_GL_SubBitmap::getBits() +{ + if (!m_parent) return 0; + return m_parent->getBits()+m_y*m_parent->getRowSpan()+m_x; +} + +bool LICE_GL_SubBitmap::resize(int w, int h) +{ + if (w == m_w && h == m_h) return false; + m_w = w; + m_h = h; + return true; +} + +INT_PTR LICE_GL_SubBitmap::Extended(int id, void* data) +{ + if (!m_parent) return 0; + + if (id == LICE_EXT_SUPPORTS_ID) return m_parent->Extended(id, data); + + LICE_Ext_SetClip_data clipdata(m_x, m_y, m_w, m_h); + if (!m_parent->Extended(LICE_EXT_SETCLIP, &clipdata)) return 0; + INT_PTR ret = m_parent->Extended(id, data); + m_parent->Extended(LICE_EXT_SETCLIP, 0); + + return ret; +} + +bool LICE_GLBitmap::SetClip_ext(LICE_Ext_SetClip_data* p) +{ + if (!FramebufferToGPU()) return false; + + if (p) + { + GLdouble c0[4] = { 1.0, 0.0, 0.0, -p->x }; + GLdouble c1[4] = { -1.0, 0.0, 0.0, p->x+p->w }; + GLdouble c2[4] = { 0.0, 1.0, 0.0, -p->y}; + GLdouble c3[4] = { 0.0, -1.0, 0.0, p->y+p->h }; + glClipPlane(GL_CLIP_PLANE0, c0); + glClipPlane(GL_CLIP_PLANE1, c1); + glClipPlane(GL_CLIP_PLANE2, c2); + glClipPlane(GL_CLIP_PLANE3, c3); + glEnable(GL_CLIP_PLANE0); + glEnable(GL_CLIP_PLANE1); + glEnable(GL_CLIP_PLANE2); + glEnable(GL_CLIP_PLANE3); + } + else + { + glDisable(GL_CLIP_PLANE0); + glDisable(GL_CLIP_PLANE1); + glDisable(GL_CLIP_PLANE2); + glDisable(GL_CLIP_PLANE3); + } + + return true; +} + +LICE_GLBitmap::~LICE_GLBitmap() +{ + ReleaseFBO(); +} + +int LICE_GLBitmap::getWidth() +{ + return m_bmp->getWidth(); +} + +int LICE_GLBitmap::getHeight() +{ + return m_bmp->getHeight(); +} + +int LICE_GLBitmap::getRowSpan() +{ + return m_bmp->getRowSpan(); +} + +LICE_pixel* LICE_GLBitmap::getBits() +{ + FramebufferFromGPU(); + return m_bmp->getBits(); +} + +bool LICE_GLBitmap::resize(int w, int h) +{ + int oldw = getWidth(); + int oldh = getHeight(); + if (w == oldw && h == oldh) return false; + + if (oldw == 0 && oldh == 0 && w > 0 && h > 0) + { + m_bmp->resize(w, h); + CreateFBO(w, h); + return true; + } + + if (!m_fbo || !m_tex) return m_bmp->resize(w, h); + + OutputDebugString("GL resizing"); + + // the framebuffer/GPU transfer overhead is per-call, so it's important to be able + // to move the entire bitmap back and forth in one call, which means keeping m_sysbmp width == rowspan + + int oldloc = m_bufloc; + + FramebufferFromGPU(); // copy out the GPU data (this binds the FBO so it is the rendering target) + + glEnable(GL_TEXTURE_RECTANGLE_ARB); // enable texturing + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_tex); // bind this texture so it is the texturing target + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // we won't be scaling it for this purpose + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); // size the texture + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, m_tex, 0); // attach the texture as the backing store for the FBO + glDisable(GL_TEXTURE_RECTANGLE_ARB); // done texturing + + if (!BindFBO()) return m_bmp->resize(w, h); // this tests the FBO for validity + + static LICE_MemBitmap tmpbmp; + tmpbmp.resize(w, h); + LICE_Blit(&tmpbmp, m_bmp, 0, 0, 0, 0, oldw, oldh, 1.0f, LICE_BLIT_MODE_COPY); + m_bmp->resize(0, 0); // force it to resize down + LICE_Copy(m_bmp, &tmpbmp); + + if (oldloc == EMPTY) m_bufloc = EMPTY; // else bufloc remains INMEM + + return true; +} + +bool LICE_GLBitmap::BindFBO() +{ + bool valid = false; + if (m_fbo) + { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo); // bind this FBO so it is the rendering target + valid = (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT); + if (valid) + { + int w = getWidth(); + int h = getHeight(); + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0.0, w, 0.0, h); + } + else + { + ReleaseFBO(); + LICE_GL_CloseCtx(); // if we fail once we're done with GL + } + } + return valid; +} + +void LICE_GLBitmap::ReleaseFBO() +{ + if (m_fbo) + { + glDeleteFramebuffersEXT(1, &m_fbo); + m_fbo = 0; + } + if (m_tex) + { + glDeleteTextures(1, &m_tex); + m_tex = 0; + } +} + +bool LICE_GLBitmap::FramebufferToGPU() +{ + if (BindFBO()) + { + if (m_bufloc == INMEM) + { + OutputDebugString("GL to GPU"); + glDisable(GL_BLEND); + glRasterPos2i(0, 0); + glDrawPixels(getWidth(), getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, m_bmp->getBits()); + } + m_bufloc = INGPU; + } + return (m_bufloc == INGPU); +} + +void LICE_GLBitmap::FramebufferFromGPU() +{ + if (m_bufloc == INGPU && BindFBO()) + { + OutputDebugString("GL to mem"); + glFinish(); + glReadPixels(0, 0, getWidth(), getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, m_bmp->getBits()); + } + m_bufloc = INMEM; +} + +INT_PTR LICE_GLBitmap::Extended(int id, void* data) +{ + if (id == LICE_EXT_SUPPORTS_ID) + { + int extid = (int) data; + if (extid == LICE_EXT_LINE_ACCEL) return 1; + if (extid == LICE_EXT_FILLRECT_ACCEL) return 1; + if (extid == LICE_EXT_DRAWCBEZIER_ACCEL) return 1; + if (extid == LICE_EXT_DRAWGLYPH_ACCEL) return 1; + if (extid == LICE_EXT_BLIT_ACCEL) return 1; + if (extid == LICE_EXT_SCALEDBLIT_ACCEL) return 1; + if (extid == LICE_EXT_GETFBOTEX_ACCEL) return 1; + if (extid == LICE_EXT_CLEAR_ACCEL) return 1; + if (extid == LICE_EXT_DASHEDLINE_ACCEL) return 1; + if (extid == LICE_EXT_GETPIXEL_ACCEL) return 1; + if (extid == LICE_EXT_PUTPIXEL_ACCEL) return 1; + if (extid == LICE_EXT_SETCLIP) return 1; + if (extid == LICE_EXT_WINDOW_BLIT) return 1; + if (extid == LICE_EXT_FORGET) return 1; + if (extid == LICE_EXT_DRAWTRIANGLE_ACCEL) return 1; + return 0; + } + + if (id == LICE_EXT_CLEAR_ACCEL) return Clear_accel((LICE_pixel*)data); + if (id == LICE_EXT_LINE_ACCEL) return Line_accel((LICE_Ext_Line_acceldata*)data); + if (id == LICE_EXT_FILLRECT_ACCEL) return FillRect_accel((LICE_Ext_FillRect_acceldata*) data); + if (id == LICE_EXT_DRAWCBEZIER_ACCEL) return DrawCBezier_accel((LICE_Ext_DrawCBezier_acceldata*)data); + if (id == LICE_EXT_DRAWGLYPH_ACCEL) return DrawGlyph_accel((LICE_Ext_DrawGlyph_acceldata*)data); + if (id == LICE_EXT_BLIT_ACCEL) return Blit_accel((LICE_Ext_Blit_acceldata*)data); + if (id == LICE_EXT_SCALEDBLIT_ACCEL) return ScaledBlit_accel((LICE_Ext_ScaledBlit_acceldata*)data); + if (id == LICE_EXT_DASHEDLINE_ACCEL) return DashedLine_accel((LICE_Ext_DashedLine_acceldata*)data); + if (id == LICE_EXT_GETPIXEL_ACCEL) return GetPixel_accel((LICE_Ext_GetPixel_acceldata*)data); + if (id == LICE_EXT_PUTPIXEL_ACCEL) return PutPixel_accel((LICE_Ext_PutPixel_acceldata*)data); + if (id == LICE_EXT_SETCLIP) return SetClip_ext((LICE_Ext_SetClip_data*)data); + if (id == LICE_EXT_DRAWTRIANGLE_ACCEL) return DrawTriangle_accel((LICE_Ext_DrawTriangle_acceldata*)data); + if (id == LICE_EXT_WINDOW_BLIT) return WindowBlit((LICE_Ext_WindowBlit_data*)data); + + if (id == LICE_EXT_FORGET) + { + m_bufloc = EMPTY; + return 1; + } + + if (id == LICE_EXT_GETFBOTEX_ACCEL) + { + if (FramebufferToGPU()) return m_tex; + return 0; + } + + return 0; +} + +static void SetGLAliasing(bool aa) +{ + if (aa) + { + glEnable(GL_LINE_SMOOTH); + } + else + { + glDisable(GL_LINE_SMOOTH); + } +} + +static void SetGLColor(LICE_pixel color, float alpha, int licemode) +{ + int a = 255; + if (licemode&LICE_BLIT_USE_ALPHA) a = LICE_GETA(color); + a = (float)a*alpha; + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + glColor4ub(LICE_GETB(color), LICE_GETG(color), LICE_GETR(color), a); + //glColor4ub(LICE_GETR(color), LICE_GETG(color), LICE_GETB(color), a); +} + +bool LICE_GLBitmap::Clear_accel(LICE_pixel* color) +{ + if (!color) return false; + LICE_pixel col = *color; + + if (!FramebufferToGPU()) return false; + + float r = (float)LICE_GETR(col)/255.0f; + float g = (float)LICE_GETG(col)/255.0f; + float b = (float)LICE_GETB(col)/255.0f; + float a = (float)LICE_GETA(col)/255.0f; + glClearColor(b, g, r, a); + //glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); + + return true; +} + +bool LICE_GLBitmap::Line_accel(LICE_Ext_Line_acceldata* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + SetGLColor(p->color, p->alpha, p->mode); + SetGLAliasing(p->aa); + + glBegin(GL_LINES); + + glVertex2f(p->x1, p->y1); + glVertex2f(p->x2, p->y2); + + glEnd(); + + return true; +} + +bool LICE_GLBitmap::DashedLine_accel(LICE_Ext_DashedLine_acceldata* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + SetGLColor(p->color, p->alpha, p->mode); + SetGLAliasing(p->aa); + + glEnable(GL_LINE_STIPPLE); + int n = (p->pxon+p->pxoff)/2; // todo properly when pxon != pxoff + glLineStipple(n, 0xAAAA); + + glBegin(GL_LINES); + + glVertex2f(p->x1, p->y1); + glVertex2f(p->x2, p->y2); + + glEnd(); + + glLineStipple(1, 65535); + glDisable(GL_LINE_STIPPLE); + + return true; +} + + +bool LICE_GLBitmap::FillRect_accel(LICE_Ext_FillRect_acceldata* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + SetGLColor(p->color, p->alpha, p->mode); + + glBegin(GL_POLYGON); + + glVertex2i(p->x, p->y); + glVertex2i(p->x+p->w, p->y); + glVertex2i(p->x+p->w, p->y+p->h); + glVertex2i(p->x, p->y+p->h); + + glEnd(); + + return true; +} + +bool LICE_GLBitmap::DrawCBezier_accel(LICE_Ext_DrawCBezier_acceldata* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + GLUnurbsObj* nurbs = LICE_GL_GetNurbsObj(); + if (!nurbs) return false; + +p->color = LICE_RGBA(255,255,255,255); // temp for easy ID of GL rendering + + SetGLColor(p->color, p->alpha, p->mode); + SetGLAliasing(p->aa); + + float ctlpts[] = + { + p->xstart, p->ystart, 0.0f, + p->xctl1, p->yctl1, 0.0f, + p->xctl2, p->yctl2, 0.0f, + p->xend, p->yend, 0.0f + }; + float knots[] = { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + + gluBeginCurve(nurbs); + gluNurbsCurve(nurbs, 8, knots, 3, ctlpts, 4, GL_MAP1_VERTEX_3); + gluEndCurve(nurbs); + + return true; +} + +bool LICE_GLBitmap::DrawGlyph_accel(LICE_Ext_DrawGlyph_acceldata* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + glEnable(GL_TEXTURE_RECTANGLE_ARB); + + int texID = LICE_GL_GetTexFromGlyph(p->alphas, p->glyph_w, p->glyph_h); + if (!texID) return false; + + SetGLColor(p->color, p->alpha, p->mode); + + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID); + glBegin(GL_POLYGON); + + glTexCoord2i(0, 0); + glVertex2i(p->x, p->y); + glTexCoord2i(p->glyph_w, 0); + glVertex2i(p->x+p->glyph_w, p->y); + glTexCoord2i(p->glyph_w, p->glyph_h); + glVertex2i(p->x+p->glyph_w, p->y+p->glyph_h); + glTexCoord2i(0, p->glyph_h); + glVertex2i(p->x, p->y+p->glyph_h); + + glEnd(); + + glDisable(GL_TEXTURE_RECTANGLE_ARB); + + return true; +} + +bool LICE_GLBitmap::Blit_accel(LICE_Ext_Blit_acceldata* p) +{ + if (!p || !p->src) return false; + if (!FramebufferToGPU()) return false; + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + GLuint src_tex = p->src->Extended(LICE_EXT_GETFBOTEX_ACCEL, 0); // this binds the src's FBO + if (src_tex) + { + if (!BindFBO()) return false; // re-bind dest FBO + + glEnable(GL_TEXTURE_RECTANGLE_ARB); + + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, src_tex); + glBegin(GL_POLYGON); + + glTexCoord2i(p->srcx, p->srcy); + glVertex2i(p->dstx, p->dsty); + glTexCoord2i(p->srcx+p->srcw, p->srcy); + glVertex2i(p->dstx+p->srcw, p->dsty); + glTexCoord2i(p->srcx+p->srcw, p->srcy+p->srch); + glVertex2i(p->dstx+p->srcw, p->dsty+p->srch); + glTexCoord2i(p->srcx, p->srcy+p->srch); + glVertex2i(p->dstx, p->dsty+p->srch); + + glEnd(); + + glDisable(GL_TEXTURE_RECTANGLE_ARB); + + return true; + } + + int srcspan = p->src->getRowSpan(); + if (p->srcx == 0 && p->srcy == 0 && p->srcw == srcspan) + { + glRasterPos2i(p->dstx, p->dsty); + glDrawPixels(p->srcw, p->srch, GL_RGBA, GL_UNSIGNED_BYTE, p->src->getBits()); + return true; + } + + LICE_pixel* srcrow = p->src->getBits()+p->srcy*srcspan+p->srcx; + int y; + for (y = 0; y < p->srch; ++y, srcrow += srcspan) + { + glRasterPos2i(p->dstx, p->dsty+y); + glDrawPixels(p->srcw, 1, GL_RGBA, GL_UNSIGNED_BYTE, srcrow); + } + return true; +} + +bool LICE_GLBitmap::ScaledBlit_accel(LICE_Ext_ScaledBlit_acceldata* p) +{ + if (!p || !p->src) return false; + if (!FramebufferToGPU()) return false; + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + glEnable(GL_TEXTURE_RECTANGLE_ARB); + + GLuint src_tex = p->src->Extended(LICE_EXT_GETFBOTEX_ACCEL, 0); // this binds the src's FBO + bool src_has_tex = (src_tex > 0); + if (src_has_tex) + { + if (!BindFBO()) return false; // re-bind dest FBO + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, src_tex); + } + else + { + // create texture from src bits + glGenTextures(1, &src_tex); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, src_tex); + + int srcspan = p->src->getRowSpan(); + if (p->srcx == 0 && p->srcy == 0 && srcspan == p->srcw) + { + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, p->srcw, p->srch, 0, GL_RGBA, GL_UNSIGNED_BYTE, p->src->getBits()); + } + else + { + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, p->srcw, p->srch, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); // size the texture + LICE_pixel* srcrow = p->src->getBits()+(int)p->srcy*srcspan+(int)p->srcx; + int y; + for (y = 0; y < p->srch; ++y, srcrow += srcspan) + { + glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, y, p->srcw, 1, GL_RGBA, GL_UNSIGNED_BYTE, srcrow); + } + } + } + + if (!src_tex) return false; + + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // filter + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glBegin(GL_POLYGON); + + glTexCoord2i(p->srcx, p->srcy); + glVertex2i(p->dstx, p->dsty); + glTexCoord2i(p->srcx+p->srcw, p->srcy); + glVertex2i(p->dstx+p->dstw, p->dsty); + glTexCoord2i(p->srcx+p->srcw, p->srcy+p->srch); + glVertex2i(p->dstx+p->dstw, p->dsty+p->dsth); + glTexCoord2i(p->srcx, p->srcy+p->srch); + glVertex2i(p->dstx, p->dsty+p->dsth); + + glEnd(); + + if (!src_has_tex) glDeleteTextures(1, &src_tex); + + glDisable(GL_TEXTURE_RECTANGLE_ARB); + + return true; +} + +bool LICE_GLBitmap::GetPixel_accel(LICE_Ext_GetPixel_acceldata* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + p->px = 0; + glReadPixels(p->x, p->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &p->px); + + return true; +} + +bool LICE_GLBitmap::PutPixel_accel(LICE_Ext_PutPixel_acceldata* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + SetGLColor(p->color, p->alpha, p->mode); + + glBegin(GL_POINTS); + glVertex2i(p->x, p->y); + glEnd(); + + return true; +} + +bool LICE_GLBitmap::DrawTriangle_accel(LICE_Ext_DrawTriangle_acceldata *p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + // todo: finish implementing zbuffering, (multi)texture mapping, alpha, etc + glColor4f(p->VertexShades[0][0], + p->VertexShades[0][1], + p->VertexShades[0][2], + p->mat->SolidOpacity); + + glBegin(GL_TRIANGLES); + + int x; + for(x=0;x<3;x++) + { + glVertex2i(p->scrx[x], p->scry[x]); + } + + glEnd(); + + return true; +} + +bool LICE_GLBitmap::WindowBlit(LICE_Ext_WindowBlit_data* p) +{ + if (!p) return false; + if (!FramebufferToGPU()) return false; + + HWND hwnd = p->hwnd; + if (hwnd != LICE_GL_GetWindow()) return 0; + + glFinish(); + + //HDC dc = GetDC(hwnd); + //HGLRC rc = wglCreateContext(dc); + //wglMakeCurrent(dc, rc); + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + +/* + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0.0, w, 0.0, h); +*/ + glEnable(GL_TEXTURE_RECTANGLE_ARB); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_tex); + + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + // the src image is upside down + // todo positioning + glBegin(GL_POLYGON); + glTexCoord2i(0, p->h); + glVertex2i(0, 0); + glTexCoord2i(p->w, p->h); + glVertex2i(p->w, 0); + glTexCoord2i(p->w, 0); + glVertex2i(p->w-1, p->h); + glTexCoord2i(0, 0); + glVertex2i(0, p->h); + glEnd(); + glDisable(GL_TEXTURE_RECTANGLE_ARB); + +//wglMakeCurrent(0, 0); +//wglDeleteContext(rc); + +// ReleaseDC(hwnd, dc); + + return true; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_glbitmap.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_glbitmap.h new file mode 100644 index 0000000..46ec7b8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_glbitmap.h @@ -0,0 +1,110 @@ +#include "lice.h" +#include "lice_extended.h" + +// interface class for LICE_GL_SysBitmap and LICE_GL_MemBitmap +class LICE_GLBitmap : public LICE_IBitmap +{ +public: + + LICE_GLBitmap(); + ~LICE_GLBitmap(); + + LICE_pixel* getBits(); + + int getWidth(); + int getHeight(); + int getRowSpan(); + + bool resize(int w, int h); + + virtual HDC getDC() = 0; // re-virtualize this to prevent instantiating LICE_GLBitmap directly + + INT_PTR Extended(int id, void* data); + +private: + + bool Clear_accel(LICE_pixel* color); + bool Line_accel(LICE_Ext_Line_acceldata* p); + bool FillRect_accel(LICE_Ext_FillRect_acceldata* p); + bool DrawCBezier_accel(LICE_Ext_DrawCBezier_acceldata* p); + bool DrawGlyph_accel(LICE_Ext_DrawGlyph_acceldata* p); + bool Blit_accel(LICE_Ext_Blit_acceldata* p); + bool ScaledBlit_accel(LICE_Ext_ScaledBlit_acceldata* p); + bool DashedLine_accel(LICE_Ext_DashedLine_acceldata* p); + bool GetPixel_accel(LICE_Ext_GetPixel_acceldata* p); + bool PutPixel_accel(LICE_Ext_PutPixel_acceldata* p); + bool SetClip_ext(LICE_Ext_SetClip_data* p); + bool DrawTriangle_accel(LICE_Ext_DrawTriangle_acceldata *p); + // etc + + bool WindowBlit(LICE_Ext_WindowBlit_data* p); + + bool CreateFBO(int w, int h); + bool BindFBO(); // bind this FBO so it is the current rendering target, and test for validity + void ReleaseFBO(); + + unsigned int m_fbo; // framebuffer object: rendering target for drawing on this glbitmap + unsigned int m_tex; // texture object: backing store for this framebuffer object + + enum { EMPTY, INGPU, INMEM }; + int m_bufloc; // where is the current framebuffer? + LICE_IBitmap* m_bmp; + +protected: + + void Init(LICE_IBitmap* bmp, int w, int h); + bool FramebufferToGPU(); + void FramebufferFromGPU(); +}; + +class LICE_GL_SysBitmap : public LICE_GLBitmap +{ +public: + + LICE_GL_SysBitmap(int w=0, int h=0); + + HDC getDC(); + +private: + + LICE_SysBitmap m_sysbmp; +}; + + +class LICE_GL_MemBitmap : public LICE_GLBitmap +{ +public: + + LICE_GL_MemBitmap(int w=0, int h=0); + + HDC getDC() { return 0; } + +private: + + LICE_MemBitmap m_membmp; +}; + + +class LICE_GL_SubBitmap : public LICE_IBitmap +{ +public: + + LICE_GL_SubBitmap(LICE_IBitmap *parent, int x, int y, int w, int h); + + LICE_pixel* getBits(); + + int getWidth() { return m_w; } + int getHeight() { return m_h; } + int getRowSpan() { return (m_parent ? m_parent->getRowSpan() : 0); } + + bool resize(int w, int h); + + HDC getDC() { return (m_parent ? m_parent->getDC() : 0); } + + INT_PTR Extended(int id, void* data); + +private: + + LICE_IBitmap* m_parent; + int m_x, m_y, m_w, m_h; +}; diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_ico.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_ico.cpp new file mode 100644 index 0000000..c3be7af --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_ico.cpp @@ -0,0 +1,191 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_ico.cpp (ICO icon file loading for LICE) + See lice.h for license and other information + + This file contains some code from Microsoft's MSDN ICO loading sample + see: http://msdn2.microsoft.com/en-us/library/ms997538.aspx +*/ + +#include "lice.h" +#include "../wdltypes.h" +#ifndef _WIN32 +#include "../swell/swell.h" +#endif + +static LICE_IBitmap *icoToBitmap(HICON icon, LICE_IBitmap *bmpOut) +{ + int icon_w = 16, icon_h=16; + +#ifdef _WIN32 + ICONINFO ii={0,}; + if (GetIconInfo(icon,&ii)) + { + bool blah=false; + if (ii.hbmColor) + { + BITMAP bm={0,}; + if (GetObject(ii.hbmColor,sizeof(bm),&bm) && bm.bmWidth && bm.bmHeight) + { + icon_w=bm.bmWidth; + icon_h=bm.bmHeight; + blah=true; + } + DeleteObject(ii.hbmColor); + } + if (ii.hbmMask) + { + BITMAP bm={0,}; + if (!blah && GetObject(ii.hbmMask,sizeof(bm),&bm) && bm.bmWidth && bm.bmHeight) + { + icon_w=bm.bmWidth; + icon_h=bm.bmHeight; + } + DeleteObject(ii.hbmMask); + } + } +#else + BITMAP bm={0,}; + if (GetObject(icon,sizeof(bm),&bm) && bm.bmWidth && bm.bmHeight) // SWELL's GetObject() works on icons + { + icon_w=bm.bmWidth; + icon_h=bm.bmHeight; + } + +#endif + + LICE_SysBitmap tempbm(icon_w*2,icon_h); + LICE_FillRect(&tempbm,0,0,icon_w,icon_h,LICE_RGBA(0,0,0,255),1.0f,LICE_BLIT_MODE_COPY); +#ifdef _WIN32 + DrawIconEx(tempbm.getDC(),0,0,icon,icon_w,icon_h,0,NULL,DI_NORMAL); +#else + { + RECT r={0,0,icon_w,icon_h}; + DrawImageInRect(tempbm.getDC(),icon,&r); + } +#endif + + LICE_FillRect(&tempbm,icon_w,0,icon_w,icon_h,LICE_RGBA(255,255,255,255),1.0f,LICE_BLIT_MODE_COPY); +#ifdef _WIN32 + DrawIconEx(tempbm.getDC(),icon_w,0,icon,icon_w,icon_h,0,NULL,DI_NORMAL); +#else + { + RECT r={icon_w,0,icon_w+icon_w,icon_h}; + DrawImageInRect(tempbm.getDC(),icon,&r); + } +#endif + + if (!bmpOut) bmpOut = new WDL_NEW LICE_MemBitmap(icon_w,icon_h); + else bmpOut->resize(icon_w,icon_h); + + int y; // since we have the image drawn on white and on black, we can calculate the alpha channel... + if (bmpOut) for(y=0;y=255) alpha=255; + else if (alpha>0) + { + r1 = (r1*255)/alpha; // LICE stores its alpha channel non-premultiplied, so we need to scale these up. + g1 = (g1*255)/alpha; + b1 = (b1*255)/alpha; + if (r1>255)r1=255; + if (g1>255)g1=255; + if (b1>255)b1=255; + } + else alpha=0; + LICE_PutPixel(bmpOut,x,y,LICE_RGBA(r1,g1,b1,alpha),1.0f,LICE_BLIT_MODE_COPY); + } + } + + return bmpOut; +} + +LICE_IBitmap *LICE_LoadIcon(const char *filename, int reqiconsz, LICE_IBitmap *bmp) // returns a bitmap (bmp if nonzero) on success +{ + if (reqiconsz<1) reqiconsz=16; + HICON icon = NULL; +#ifdef _WIN32 + +#ifndef WDL_NO_SUPPORT_UTF8 + #ifdef WDL_SUPPORT_WIN9X + if (GetVersion()<0x80000000) + #endif + { + WCHAR wf[2048]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wf,2048)) + icon = (HICON)LoadImageW(NULL,wf,IMAGE_ICON,reqiconsz,reqiconsz,LR_LOADFROMFILE); + } +#endif + + if (!icon) icon = (HICON)LoadImage(NULL,filename,IMAGE_ICON,reqiconsz,reqiconsz,LR_LOADFROMFILE); + +#else + icon = (HICON)LoadNamedImage(filename,false); +#endif + if (!icon) return 0; + + LICE_IBitmap *ret=icoToBitmap(icon,bmp); + DestroyIcon(icon); + return ret; +} + +LICE_IBitmap *LICE_LoadIconFromResource(HINSTANCE hInst, const char *resid, int reqiconsz, LICE_IBitmap *bmp) // returns a bitmap (bmp if nonzero) on success +{ +#ifdef _WIN32 + if (reqiconsz<1) reqiconsz=16; + HICON icon = (HICON)LoadImage(hInst,resid,IMAGE_ICON,reqiconsz,reqiconsz,0); + if (!icon) return 0; + + LICE_IBitmap *ret=icoToBitmap(icon,bmp); + DestroyIcon(icon); + return ret; +#else + return 0; +#endif +} + + + + +class LICE_ICOLoader +{ +public: + _LICE_ImageLoader_rec rec; + LICE_ICOLoader() + { + rec.loadfunc = loadfunc; + rec.get_extlist = get_extlist; + rec._next = LICE_ImageLoader_list; + LICE_ImageLoader_list = &rec; + } + + static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) + { + if (checkFileName) + { + const char *p=filename; + while (*p)p++; + while (p>filename && *p != '\\' && *p != '/' && *p != '.') p--; + if (stricmp(p,".ico")) return 0; + } + return LICE_LoadIcon(filename,16,bmpbase); + } + static const char *get_extlist() + { + return "ICO files (*.ICO)\0*.ICO\0"; + } + +}; + +LICE_ICOLoader LICE_icoldr; diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_image.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_image.cpp new file mode 100644 index 0000000..93bd063 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_image.cpp @@ -0,0 +1,147 @@ +#include "lice.h" + + + +LICE_IBitmap* LICE_LoadImage(const char* filename, LICE_IBitmap* bmp, bool tryIgnoreExtension) +{ + _LICE_ImageLoader_rec *hdr = LICE_ImageLoader_list; + while (hdr) + { + LICE_IBitmap *ret = hdr->loadfunc(filename,true,bmp); + if (ret) return ret; + hdr=hdr->_next; + } + if (tryIgnoreExtension) + { + hdr = LICE_ImageLoader_list; + while (hdr) + { + LICE_IBitmap *ret = hdr->loadfunc(filename,false,bmp); + if (ret) return ret; + hdr=hdr->_next; + } + } + + return 0; +} + + +static bool grow_buf(char **buf, int *bufsz, int *wrpos, const char *rd, int len) +{ + if (*wrpos + len > *bufsz) + { + char *nbuf=(char*)realloc(*buf,*bufsz = *wrpos+len+4096); + if (!nbuf) return true; // ugh + *buf = nbuf; + } + memcpy(*buf+*wrpos,rd,len); + *wrpos+=len; + return false; +} + +char *LICE_GetImageExtensionList(bool wantAllSup, bool wantAllFiles) +{ + _LICE_ImageLoader_rec *hdr = LICE_ImageLoader_list; + int bufsz=4096; + int wrpos=0; + char *buf=(char *)malloc(bufsz); + buf[0]=buf[1]=buf[2]=0; + + if (wantAllSup) + { + static const char af[]="All supported images"; + if (grow_buf(&buf,&bufsz,&wrpos,af,sizeof(af))) { free(buf); return NULL; } // fail + + int cnt=0; + while (hdr) + { + const char *rd = hdr->get_extlist(); + if (rd && *rd) + { + bool st=false; + const char *p=rd; + while (*p) + { + if (st) + { + if (cnt++) + if (grow_buf(&buf,&bufsz,&wrpos,";",1)) { free(buf); return NULL; } // fail + if (grow_buf(&buf,&bufsz,&wrpos,p,(int)strlen(p)+1)) { free(buf); return NULL; } // fail + wrpos--; + } + while (*p) p++; + p++; + st=!st; + } + } + hdr=hdr->_next; + } + + if (!cnt) + { + wrpos=0; // reset if nothing meaningful added + buf[0]=buf[1]=buf[2]=0; + } + else wrpos++; + + hdr = LICE_ImageLoader_list; + } + + while (hdr) + { + const char *rd = hdr->get_extlist(); + if (rd && *rd) + { + int len = 0; + while (rd[len] || rd[len+1]) len++; // doublenull terminated list + + if (len) + { + if (grow_buf(&buf, &bufsz, &wrpos, rd, len+2)) return buf; // fail + wrpos--; // remove doublenull on next round + } + } + hdr=hdr->_next; + } + if (wantAllFiles) + { + static const char af[]="All files (*.*)\0*.*\0"; + if (grow_buf(&buf,&bufsz,&wrpos,af,sizeof(af))) return buf; // fail + wrpos--; + } + + return buf; +} + +bool LICE_ImageIsSupported(const char *filename) +{ + const char *extension = filename; + while (*extension)extension++; + while (extension>=filename && *extension != '/' && *extension != '.' && *extension != '\\') extension--; + if (extensionget_extlist(); + if (el) + { + while (*el) el++; // skip desc + ++el; + + // scan rest of buffer + while (*el) + { + if (!strnicmp(el,extension,extlen)) + { + if (el[extlen] == 0|| el[extlen] == ';') return true; + } + el++; + } + } + hdr=hdr->_next; + } + return false; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_import.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_import.h new file mode 100644 index 0000000..17876a1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_import.h @@ -0,0 +1,84 @@ +#define LICE_TEXT_NO_DECLARE_CACHEDFONT + +#ifdef LICE_IMPORT_INTERFACE_ONLY +#define LICE_FUNC_DEF_DECL extern +#else +#define LICE_FUNC_DEF_DECL +#endif + +#include "lice_text.h" +LICE_FUNC_DEF_DECL LICE_IBitmap *(*__LICE_CreateBitmap)(int, int, int); +LICE_FUNC_DEF_DECL void (*__LICE_PutPixel)(LICE_IBitmap* dest, int x, int y, LICE_pixel color, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_Line)(LICE_IBitmap *dest, int x1, int y1, int x2, int y2, LICE_pixel color, float alpha, int mode, bool aa); +LICE_FUNC_DEF_DECL void (*__LICE_FLine)(LICE_IBitmap *dest, float x1, float y1, float x2, float y2, LICE_pixel color, float alpha, int mode, bool aa); +LICE_FUNC_DEF_DECL void (*__LICE_DashedLine)(LICE_IBitmap *dest, int x1, int y1, int x2, int y2, int on, int off, LICE_pixel color, float alpha, int mode, bool aa); +LICE_FUNC_DEF_DECL void (*__LICE_FillRect)(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel color, float alpha , int mode); +LICE_FUNC_DEF_DECL void (*__LICE_DrawRect)(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel color, float alpha , int mode); +LICE_FUNC_DEF_DECL void (*__LICE_BorderedRect)(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel bgcolor, LICE_pixel fgcolor, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_Circle)(LICE_IBitmap* dest, float cx, float cy, float r, LICE_pixel color, float alpha, int mode, bool aa); +LICE_FUNC_DEF_DECL void (*__LICE_FillCircle)(LICE_IBitmap* dest, float cx, float cy, float r, LICE_pixel color, float alpha, int mode, bool aa); +LICE_FUNC_DEF_DECL void (*__LICE_Clear)(LICE_IBitmap *dest, LICE_pixel color); +LICE_FUNC_DEF_DECL void (*__LICE_Blit)(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, int srcx, int srcy, int srcw, int srch, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_RotatedBlit)(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, int dstw, int dsth, float srcx, float srcy, float srcw, float srch, float angle, bool cliptosourcerect, float alpha, int mode, float rotxcent, float rotycent); +LICE_FUNC_DEF_DECL void (*__LICE_DrawGlyph)(LICE_IBitmap* dest, int x, int y, LICE_pixel color, LICE_pixel_chan* glyph, int glyph_w, int glyph_h, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_FillTriangle)(LICE_IBitmap *dest, int x1, int y1, int x2, int y2, int x3, int y3, LICE_pixel color, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_Arc)(LICE_IBitmap* dest, float cx, float cy, float r, float alo, float ahi, LICE_pixel color, float alpha, int mode, bool aa); +LICE_FUNC_DEF_DECL void (*__LICE_FillTrapezoid)(LICE_IBitmap* dest, int x1a, int x1b, int y1, int x2a, int x2b, int y2, LICE_pixel color, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_FillConvexPolygon)(LICE_IBitmap* dest, int* x, int* y, int npoints, LICE_pixel color, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_Copy)(LICE_IBitmap* dest, LICE_IBitmap* src); +LICE_FUNC_DEF_DECL void (*__LICE_DrawText)(LICE_IBitmap *bm, int x, int y, const char *string, LICE_pixel color, float alpha, int mode); +LICE_FUNC_DEF_DECL void (*__LICE_MeasureText)(const char *string, int *w, int *h); +LICE_FUNC_DEF_DECL void (*__LICE_ScaledBlit)(LICE_IBitmap *dest, LICE_IBitmap *src, int dstx, int dsty, int dstw, int dsth, float srcx, float srcy, float srcw, float srch, float alpha, int mode); + +LICE_FUNC_DEF_DECL void * (*LICE_CreateFont)(); +#define LICE_PutPixel __LICE_PutPixel +#define LICE_Line __LICE_Line +#define LICE_FLine __LICE_FLine +#define LICE_DashedLine __LICE_DashedLine +#define LICE_FillRect __LICE_FillRect +#define LICE_DrawRect __LICE_DrawRect +#define LICE_Circle __LICE_Circle +#define LICE_Clear __LICE_Clear +#define LICE_Blit __LICE_Blit +#define LICE_RotatedBlit __LICE_RotatedBlit +#define LICE_DrawGlyph __LICE_DrawGlyph +#define LICE_FillCircle __LICE_FillCircle +#define LICE_BorderedRect __LICE_BorderedRect +#define LICE_FillTriangle __LICE_FillTriangle +#define LICE_Arc __LICE_Arc +#define LICE_FillTrapezoid __LICE_FillTrapezoid +#define LICE_FillConvexPolygon __LICE_FillConvexPolygon +#define LICE_Copy __LICE_Copy +#define LICE_DrawText __LICE_DrawText +#define LICE_MeasureText __LICE_MeasureText +#define LICE_ScaledBlit __LICE_ScaledBlit +#define LICE_CreateMemBitmap(w,h) (__LICE_CreateBitmap ? __LICE_CreateBitmap(0,w,h) : 0) +#define LICE_CreateSysBitmap(w,h) (__LICE_CreateBitmap ? __LICE_CreateBitmap(1,w,h) : 0) +#define LICE_CreateTextCache() ((LICE_IFont*)(LICE_CreateFont?LICE_CreateFont():0)) +#undef LICE_FUNC_DEF_DECL + +#define IMPORT_LICE_FUNCS(IMPORT_FUNC) \ + IMPORT_FUNC(__LICE_CreateBitmap,"LICE_CreateBitmap") \ + IMPORT_FUNC(__LICE_PutPixel,"LICE_PutPixel") \ + IMPORT_FUNC(__LICE_Line,"LICE_LineInt") \ + IMPORT_FUNC(__LICE_FLine,"LICE_Line") \ + IMPORT_FUNC(__LICE_DashedLine, "LICE_DashedLine") \ + IMPORT_FUNC(__LICE_Circle,"LICE_Circle") \ + IMPORT_FUNC(__LICE_FillCircle,"LICE_FillCircle") \ + IMPORT_FUNC(__LICE_FillRect,"LICE_FillRect") \ + IMPORT_FUNC(__LICE_DrawRect,"LICE_DrawRect") \ + IMPORT_FUNC(__LICE_BorderedRect,"LICE_BorderedRect") \ + IMPORT_FUNC(__LICE_Clear,"LICE_Clear") \ + IMPORT_FUNC(__LICE_Blit,"LICE_Blit") \ + IMPORT_FUNC(__LICE_RotatedBlit,"LICE_RotatedBlit") \ + IMPORT_FUNC(__LICE_DrawGlyph,"LICE_DrawGlyph") \ + IMPORT_FUNC(LICE_CreateFont,"LICE_CreateFont") \ + IMPORT_FUNC(LICE_FillTriangle,"LICE_FillTriangle") \ + IMPORT_FUNC(LICE_Arc,"LICE_Arc") \ + IMPORT_FUNC(LICE_FillTrapezoid,"LICE_FillTrapezoid") \ + IMPORT_FUNC(LICE_FillConvexPolygon,"LICE_FillConvexPolygon") \ + IMPORT_FUNC(LICE_Copy,"LICE_Copy") \ + IMPORT_FUNC(__LICE_ScaledBlit,"LICE_ScaledBlit") \ + IMPORT_FUNC(__LICE_MeasureText,"LICE_MeasureText") \ + IMPORT_FUNC(__LICE_DrawText,"LICE_DrawText") + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_jpg.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_jpg.cpp new file mode 100644 index 0000000..eebff49 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_jpg.cpp @@ -0,0 +1,404 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_jpg.cpp (JPG loading for LICE) + See lice.h for license and other information +*/ + +#include +#include "lice.h" +#include +#include "../wdltypes.h" + +extern "C" { +#include "../jpeglib/jpeglib.h" +}; + +struct my_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; +static void LICEJPEG_Error(j_common_ptr cinfo) +{ + longjmp(((my_error_mgr*)cinfo->err)->setjmp_buffer,1); +} +static void LICEJPEG_EmitMsg(j_common_ptr cinfo, int msg_level) { } +static void LICEJPEG_FmtMsg(j_common_ptr cinfo, char *) { } +static void LICEJPEG_OutMsg(j_common_ptr cinfo) { } +static void LICEJPEG_reset_error_mgr(j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + cinfo->err->msg_code = 0; +} + +#ifdef _WIN32 +static void LICEJPEG_init_source(j_decompress_ptr cinfo) {} +static unsigned char EOI_data[2] = { 0xFF, 0xD9 }; +static boolean LICEJPEG_fill_input_buffer(j_decompress_ptr cinfo) +{ + cinfo->src->next_input_byte = EOI_data; + cinfo->src->bytes_in_buffer = 2; + return TRUE; +} +static void LICEJPEG_skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + if (num_bytes > 0) + { + if (num_bytes > (long) cinfo->src->bytes_in_buffer) + { + num_bytes = (long) cinfo->src->bytes_in_buffer; + } + cinfo->src->next_input_byte += (size_t) num_bytes; + cinfo->src->bytes_in_buffer -= (size_t) num_bytes; + } +} +static void LICEJPEG_term_source(j_decompress_ptr cinfo) {} +#endif + + +LICE_IBitmap *LICE_LoadJPGFromResource(HINSTANCE hInst, const char *resid, LICE_IBitmap *bmp) +{ +#ifdef _WIN32 + HRSRC hResource = FindResource(hInst, resid, "JPG"); + if(!hResource) return NULL; + + DWORD imageSize = SizeofResource(hInst, hResource); + if(imageSize < 8) return NULL; + + HGLOBAL res = LoadResource(hInst, hResource); + const void* pResourceData = LockResource(res); + if(!pResourceData) return NULL; + + unsigned char *data = (unsigned char *)pResourceData; + + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr={0,}; + JSAMPARRAY buffer; + int row_stride; + + jerr.pub.error_exit = LICEJPEG_Error; + jerr.pub.emit_message = LICEJPEG_EmitMsg; + jerr.pub.output_message = LICEJPEG_OutMsg; + jerr.pub.format_message = LICEJPEG_FmtMsg; + jerr.pub.reset_error_mgr = LICEJPEG_reset_error_mgr; + + cinfo.err = &jerr.pub; + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + return 0; + } + jpeg_create_decompress(&cinfo); + + cinfo.src = (struct jpeg_source_mgr *) (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, sizeof (struct jpeg_source_mgr)); + + cinfo.src->init_source = LICEJPEG_init_source; + cinfo.src->fill_input_buffer = LICEJPEG_fill_input_buffer; + cinfo.src->skip_input_data = LICEJPEG_skip_input_data; + cinfo.src->resync_to_restart = jpeg_resync_to_restart; + cinfo.src->term_source = LICEJPEG_term_source; + + cinfo.src->next_input_byte = data; + cinfo.src->bytes_in_buffer = imageSize; + + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + row_stride = cinfo.output_width * cinfo.output_components; + + buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + LICE_IBitmap *delbmp = NULL; + if (bmp) bmp->resize(cinfo.output_width,cinfo.output_height); + else delbmp = bmp = new WDL_NEW LICE_MemBitmap(cinfo.output_width,cinfo.output_height); + + if (!bmp || bmp->getWidth() != (int)cinfo.output_width || bmp->getHeight() != (int)cinfo.output_height) + { + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + delete delbmp; + return 0; + } + + LICE_pixel *bmpptr = bmp->getBits(); + int dbmpptr=bmp->getRowSpan(); + if (bmp->isFlipped()) + { + bmpptr += dbmpptr*(bmp->getHeight()-1); + dbmpptr=-dbmpptr; + } + + while (cinfo.output_scanline < cinfo.output_height) + { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + jpeg_read_scanlines(&cinfo, buffer, 1); + /* Assume put_scanline_someplace wants a pointer and sample count. */ +// put_scanline_someplace(buffer[0], row_stride); + if (cinfo.output_components==3) + { + int x; + for (x = 0; x < (int)cinfo.output_width; x++) + { + bmpptr[x]=LICE_RGBA(buffer[0][x*3],buffer[0][x*3+1],buffer[0][x*3+2],255); + } + } + else if (cinfo.output_components==1) + { + int x; + for (x = 0; x < (int)cinfo.output_width; x++) + { + int v=buffer[0][x]; + bmpptr[x]=LICE_RGBA(v,v,v,255); + } + } + else + { + memset(bmpptr,0,4*cinfo.output_width); + } + bmpptr+=dbmpptr; + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); // we created cinfo.src with some special alloc so I think it gets collected + + return bmp; + +#else + return 0; +#endif +} + + +LICE_IBitmap *LICE_LoadJPG(const char *filename, LICE_IBitmap *bmp) +{ + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr={{0},}; + JSAMPARRAY buffer; + int row_stride; + + FILE *fp=NULL; +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + #ifdef WDL_SUPPORT_WIN9X + if (GetVersion()<0x80000000) + #endif + { + WCHAR wf[2048]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wf,2048)) + fp = _wfopen(wf,L"rb"); + } +#endif + if (!fp) fp = WDL_fopenA(filename,"rb"); + + if (!fp) return 0; + + jerr.pub.error_exit = LICEJPEG_Error; + jerr.pub.emit_message = LICEJPEG_EmitMsg; + jerr.pub.output_message = LICEJPEG_OutMsg; + jerr.pub.format_message = LICEJPEG_FmtMsg; + jerr.pub.reset_error_mgr = LICEJPEG_reset_error_mgr; + + cinfo.err = &jerr.pub; + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + fclose(fp); + return 0; + } + jpeg_create_decompress(&cinfo); + + jpeg_stdio_src(&cinfo, fp); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + row_stride = cinfo.output_width * cinfo.output_components; + + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + + LICE_IBitmap *delbmp = NULL; + if (bmp) bmp->resize(cinfo.output_width,cinfo.output_height); + else delbmp = bmp = new WDL_NEW LICE_MemBitmap(cinfo.output_width,cinfo.output_height); + + if (!bmp || bmp->getWidth() != (int)cinfo.output_width || bmp->getHeight() != (int)cinfo.output_height) + { + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(fp); + delete delbmp; + return 0; + } + + LICE_pixel *bmpptr = bmp->getBits(); + int dbmpptr=bmp->getRowSpan(); + if (bmp->isFlipped()) + { + bmpptr += dbmpptr*(bmp->getHeight()-1); + dbmpptr=-dbmpptr; + } + + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + jpeg_read_scanlines(&cinfo, buffer, 1); + /* Assume put_scanline_someplace wants a pointer and sample count. */ +// put_scanline_someplace(buffer[0], row_stride); + if (cinfo.output_components==3) + { + int x; + for (x = 0; x < (int)cinfo.output_width; x++) + { + bmpptr[x]=LICE_RGBA(buffer[0][x*3],buffer[0][x*3+1],buffer[0][x*3+2],255); + } + } + else if (cinfo.output_components==1) + { + int x; + for (x = 0; x < (int)cinfo.output_width; x++) + { + int v=buffer[0][x]; + bmpptr[x]=LICE_RGBA(v,v,v,255); + } + } + else + memset(bmpptr,0,4*cinfo.output_width); + bmpptr+=dbmpptr; + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(fp); + + return bmp; +} + +LICE_IBitmap *LICE_LoadJPGFromMemory(const void *data_in, int buflen, LICE_IBitmap *bmp) +{ + if (buflen < 8) return NULL; + + unsigned char *data = (unsigned char *)(void *)data_in; + + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr={{0},}; + JSAMPARRAY buffer; + int row_stride; + + jerr.pub.error_exit = LICEJPEG_Error; + jerr.pub.emit_message = LICEJPEG_EmitMsg; + jerr.pub.output_message = LICEJPEG_OutMsg; + jerr.pub.format_message = LICEJPEG_FmtMsg; + jerr.pub.reset_error_mgr = LICEJPEG_reset_error_mgr; + + cinfo.err = &jerr.pub; + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + return 0; + } + jpeg_create_decompress(&cinfo); + + jpeg_mem_src(&cinfo, data, buflen); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + row_stride = cinfo.output_width * cinfo.output_components; + + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + + LICE_IBitmap *delbmp = NULL; + if (bmp) bmp->resize(cinfo.output_width,cinfo.output_height); + else delbmp = bmp = new WDL_NEW LICE_MemBitmap(cinfo.output_width,cinfo.output_height); + + if (!bmp || bmp->getWidth() != (int)cinfo.output_width || bmp->getHeight() != (int)cinfo.output_height) + { + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + delete delbmp; + return 0; + } + + LICE_pixel *bmpptr = bmp->getBits(); + int dbmpptr=bmp->getRowSpan(); + if (bmp->isFlipped()) + { + bmpptr += dbmpptr*(bmp->getHeight()-1); + dbmpptr=-dbmpptr; + } + + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + jpeg_read_scanlines(&cinfo, buffer, 1); + /* Assume put_scanline_someplace wants a pointer and sample count. */ +// put_scanline_someplace(buffer[0], row_stride); + if (cinfo.output_components==3) + { + int x; + for (x = 0; x < (int)cinfo.output_width; x++) + { + bmpptr[x]=LICE_RGBA(buffer[0][x*3],buffer[0][x*3+1],buffer[0][x*3+2],255); + } + } + else if (cinfo.output_components==1) + { + int x; + for (x = 0; x < (int)cinfo.output_width; x++) + { + int v=buffer[0][x]; + bmpptr[x]=LICE_RGBA(v,v,v,255); + } + } + else + memset(bmpptr,0,4*cinfo.output_width); + bmpptr+=dbmpptr; + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return bmp; +} + +class LICE_JPGLoader +{ +public: + _LICE_ImageLoader_rec rec; + LICE_JPGLoader() + { + rec.loadfunc = loadfunc; + rec.get_extlist = get_extlist; + rec._next = LICE_ImageLoader_list; + LICE_ImageLoader_list = &rec; + } + + static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) + { + if (checkFileName) + { + const char *p=filename; + while (*p)p++; + while (p>filename && *p != '\\' && *p != '/' && *p != '.') p--; + if (stricmp(p,".jpg")&&stricmp(p,".jpeg")&&stricmp(p,".jfif")) return 0; + } + return LICE_LoadJPG(filename,bmpbase); + } + static const char *get_extlist() + { + return "JPEG files (*.JPG;*.JPEG;*.JFIF)\0*.JPG;*.JPEG;*.JFIF\0"; + } + +}; + +LICE_JPGLoader LICE_jgpldr; diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_jpg_write.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_jpg_write.cpp new file mode 100644 index 0000000..0f8621e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_jpg_write.cpp @@ -0,0 +1,119 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_jpg_write.cpp (JPG writing for LICE) + See lice.h for license and other information +*/ + +#include +#include "lice.h" +#include + +extern "C" { +#include "../jpeglib/jpeglib.h" +}; + +struct my_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; +static void LICEJPEG_Error(j_common_ptr cinfo) +{ + longjmp(((my_error_mgr*)cinfo->err)->setjmp_buffer,1); +} +static void LICEJPEG_EmitMsg(j_common_ptr cinfo, int msg_level) { } +static void LICEJPEG_FmtMsg(j_common_ptr cinfo, char *) { } +static void LICEJPEG_OutMsg(j_common_ptr cinfo) { } +static void LICEJPEG_reset_error_mgr(j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + cinfo->err->msg_code = 0; +} + +bool LICE_WriteJPG(const char *filename, LICE_IBitmap *bmp, int quality, bool force_baseline) +{ + if (!bmp || !filename) return false; + + FILE *fp=NULL; +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + #ifdef WDL_SUPPORT_WIN9X + if (GetVersion()<0x80000000) + #endif + { + WCHAR wf[2048]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wf,2048)) + fp = _wfopen(wf,L"wb"); + } +#endif + if (!fp) fp = fopen(filename,"wb"); + + if (!fp) return false; + + struct jpeg_compress_struct cinfo; + struct my_error_mgr jerr={0,}; + jerr.pub.error_exit = LICEJPEG_Error; + jerr.pub.emit_message = LICEJPEG_EmitMsg; + jerr.pub.output_message = LICEJPEG_OutMsg; + jerr.pub.format_message = LICEJPEG_FmtMsg; + jerr.pub.reset_error_mgr = LICEJPEG_reset_error_mgr; + + cinfo.err = &jerr.pub; + unsigned char *buf = NULL; + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_compress(&cinfo); + if (fp) fclose(fp); + free(buf); + return false; + } + jpeg_create_compress(&cinfo); + + jpeg_stdio_dest(&cinfo, fp); + + cinfo.image_width = bmp->getWidth(); /* image width and height, in pixels */ + cinfo.image_height = bmp->getHeight(); + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, !!force_baseline); + jpeg_start_compress(&cinfo, TRUE); + + buf = (unsigned char *)malloc(cinfo.image_width * 3); + LICE_pixel_chan *rd = (LICE_pixel_chan *)bmp->getBits(); + int rowspan = bmp->getRowSpan()*4; + if (bmp->isFlipped()) + { + rd += rowspan*(bmp->getHeight()-1); + rowspan=-rowspan; + } + while (cinfo.next_scanline < cinfo.image_height) + { + unsigned char *outp=buf; + LICE_pixel_chan *rdp = rd; + int x=cinfo.image_width; + while(x--) + { + outp[0] = rdp[LICE_PIXEL_R]; + outp[1] = rdp[LICE_PIXEL_G]; + outp[2] = rdp[LICE_PIXEL_B]; + outp+=3; + rdp+=4; + } + jpeg_write_scanlines(&cinfo, &buf, 1); + + rd+=rowspan; + } + free(buf); + buf=0; + + jpeg_finish_compress(&cinfo); + + if (fp) fclose(fp); + fp=0; + + jpeg_destroy_compress(&cinfo); + + return true; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lcf.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lcf.cpp new file mode 100644 index 0000000..d35f8ab --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lcf.cpp @@ -0,0 +1,673 @@ +#include +#include + +#include "lice_lcf.h" + +#include "../filewrite.h" +#include "../fileread.h" + + +#define LCF_VERSION 0x11CEb001 + +LICECaptureCompressor::LICECaptureCompressor(const char *outfn, int w, int h, int interval, int bsize_w, int bsize_h) +{ + m_inframes = m_outframes=0; + m_file = new WDL_FileWrite(outfn,1,512*1024); + if (!m_file->IsOpen()) { delete m_file; m_file=0; } + + memset(&m_compstream,0,sizeof(m_compstream)); + if (m_file) + { + if (deflateInit(&m_compstream,9)!=Z_OK) + { + delete m_file; + m_file=0; + } + } + + m_inbytes=0; + m_outsize=0; + m_w=w; + m_h=h; + m_interval=interval; + m_bsize_w=bsize_w; + m_bsize_h=bsize_h; + m_state=0; + m_which=0; + m_outchunkpos=0; + m_numcols = (m_w+bsize_w-1) / (bsize_w>0?bsize_w:1); + if (m_numcols<1) m_numcols=1; + m_numrows = (m_h+bsize_h-1)/ (bsize_h>0?bsize_h:1); + + m_current_block_srcsize=0; +} + +void LICECaptureCompressor::OnFrame(LICE_IBitmap *fr, int delta_t_ms) +{ + if (fr) + { + if (fr->getWidth()!=m_w || fr->getHeight()!=m_h) return; + + frameRec *rec = m_framelists[m_which].Get(m_state); + if (!rec) + { + rec = new frameRec(m_w*m_h); + m_framelists[m_which].Add(rec); + } + rec->delta_t_ms=delta_t_ms; + BitmapToFrameRec(fr,rec); + m_state++; + m_inframes++; + } + + + bool isLastBlock = m_state >= m_interval || !fr; + + if (m_framelists[!m_which].GetSize()) + { + int compressTo; + if (isLastBlock) compressTo = m_numcols*m_numrows; + else compressTo = (m_state * m_numcols*m_numrows) / m_interval; + + frameRec **list = m_framelists[!m_which].GetList(); + int list_size = m_framelists[!m_which].GetSize(); + + // compress some data + int chunkpos = m_outchunkpos; + while (chunkpos < compressTo) + { + int xpos = (chunkpos%m_numcols) * m_bsize_w; + int ypos = (chunkpos/m_numcols) * m_bsize_h; + + int wid = m_w-xpos; + int hei = m_h-ypos; + if (wid > m_bsize_w) wid=m_bsize_w; + if (hei > m_bsize_h) hei=m_bsize_h; + + int i; + int rdoffs = xpos + ypos*m_w; + int rdspan = m_w; + + int repeat_cnt=0; + + for(i=0;idata + rdoffs; + if (i&&repeat_cnt<255) + { + unsigned short *rd1=rd; + unsigned short *rd2=list[i-1]->data+rdoffs; + int a=hei; + while(a--) + { + if (memcmp(rd1,rd2,wid*sizeof(short))) break; + rd1+=rdspan; + rd2+=rdspan; + } + if (a<0) + { + repeat_cnt++; + continue; + } + } + + if (i || repeat_cnt) + { + unsigned char c = (unsigned char)repeat_cnt; + DeflateBlock(&c,1,false); + repeat_cnt=0; + } + int a=hei; + while (a--) + { + DeflateBlock(rd,wid*sizeof(short),false); + rd+=rdspan; + } + } + if (repeat_cnt) + { + unsigned char c = (unsigned char)repeat_cnt; + DeflateBlock(&c,1,false); + } + + chunkpos++; + } + m_outchunkpos=chunkpos; + } + + if (isLastBlock) + { + if (m_framelists[!m_which].GetSize()) + { + m_outframes += m_framelists[!m_which].GetSize(); + + DeflateBlock(NULL,0,true); + + deflateReset(&m_compstream); + + m_hdrqueue.Clear(); + AddHdrInt(LCF_VERSION); + AddHdrInt(16); + AddHdrInt(m_w); + AddHdrInt(m_h); + AddHdrInt(m_bsize_w); + AddHdrInt(m_bsize_h); + int nf = m_framelists[!m_which].GetSize(); + AddHdrInt(nf); + int sz = m_current_block.Available(); + AddHdrInt(sz); + + int uncomp_sz = m_current_block_srcsize; + AddHdrInt(uncomp_sz); + + { + int x; + for(x=0;xdelta_t_ms); + } + } + + + m_file->Write(m_hdrqueue.Get(),m_hdrqueue.Available()); + m_outsize += m_hdrqueue.Available(); + m_file->Write(m_current_block.Get(),sz); + m_outsize += sz; + + m_current_block.Clear(); + m_current_block_srcsize=0; + } + + + int old_state=m_state; + m_state=0; + m_outchunkpos=0; + m_which=!m_which; + + + if (old_state>0 && !fr) + { + while (m_framelists[!m_which].GetSize() > old_state) + m_framelists[!m_which].Delete(m_framelists[!m_which].GetSize()-1,true); + + OnFrame(NULL,0); + } + + if (!fr) + { + m_framelists[0].Empty(true); + m_framelists[1].Empty(true); + } + } +} + +void LICECaptureCompressor::BitmapToFrameRec(LICE_IBitmap *fr, frameRec *dest) +{ + unsigned short *outptr = dest->data; + const LICE_pixel *p = fr->getBits(); + int span = fr->getRowSpan(); + if (fr->isFlipped()) + { + p+=(fr->getHeight()-1)*span; + span=-span; + } + int h = fr->getHeight(),w=fr->getWidth(); + while (h--) + { + int x=w; + const LICE_pixel *sp = p; + while (x--) + { + LICE_pixel pix = *sp++; + *outptr++ = (((int)LICE_GETR(pix)&0xF8)>>3) | (((int)LICE_GETG(pix)&0xFC)<<3) | (((int)LICE_GETB(pix)&0xF8)<<8); + } + p += span; + } +} + +void LICECaptureCompressor::DeflateBlock(void *data, int data_size, bool flush) +{ + m_current_block_srcsize += data_size; + m_inbytes += data_size; + int bytesout=0; + + m_compstream.next_in = (unsigned char *)data; + m_compstream.avail_in = data_size; + + for (;;) + { + int add_sz = data_size+32768; + m_compstream.next_out = (unsigned char *)m_current_block.Add(NULL,add_sz); + m_compstream.avail_out = add_sz; + + int e = deflate(&m_compstream,flush?Z_FULL_FLUSH:Z_NO_FLUSH); + + m_current_block.Add(NULL,-(int)m_compstream.avail_out); + + bytesout+=add_sz-m_compstream.avail_out; + + + if (e != Z_OK) + { + break; + } + + if (!m_compstream.avail_in && (!flush || add_sz==(int)m_compstream.avail_out)) break; + } + m_outsize += bytesout; + +} + + + +LICECaptureCompressor::~LICECaptureCompressor() +{ + // process any pending frames + if (m_file) + { + OnFrame(NULL,0); + deflateEnd(&m_compstream); + } + + delete m_file; + m_framelists[0].Empty(true); + m_framelists[1].Empty(true); +} + + + + + + + + + + + + + + +////////////////////////////////////////// +///////DECOMPRESS +////////////////////////////////////////// + + +LICECaptureDecompressor::LICECaptureDecompressor(const char *fn, bool want_seekable) : m_workbm(0,0,1) +{ + m_bytes_read=0; + m_file_length_ms=0; + m_rd_which=0; + m_frameidx=0; + memset(&m_compstream,0,sizeof(m_compstream)); + memset(&m_curhdr,0,sizeof(m_curhdr)); + m_file = new WDL_FileRead(fn,2,1024*1024); + if (m_file->IsOpen()) + { + if (inflateInit(&m_compstream)!=Z_OK) + { + delete m_file; + m_file=0; + } + if (m_file) + { + m_file_frame_info.Clear(); + m_file_length_ms=0; + if (want_seekable) + { + unsigned int lastpos = 0; + int first_frame_delay = 0; + while (ReadHdr(0)) + { + m_file_frame_info.Add(&lastpos,1); + unsigned int mst = m_file_length_ms; + if (m_frame_deltas[0].GetSize()) + { + if (lastpos > 0) + mst += m_frame_deltas[0].Get()[0]-first_frame_delay; // TOC is by time of first frames, ignore first delay when seeking + else + first_frame_delay = m_frame_deltas[0].Get()[0]; + } + m_file_frame_info.Add(&mst,1); + + int x; + for(x=0;xSetPosition(lastpos = (unsigned int)(m_file->GetPosition() + m_curhdr[0].cdata_left)); + } + } + + Seek(0); + } + } + + if (m_curhdr[m_rd_which].bpp!=16) + { + delete m_file; + m_file=0; + } + +} + +LICECaptureDecompressor::~LICECaptureDecompressor() +{ + inflateEnd(&m_compstream); + delete m_file; +} + +bool LICECaptureDecompressor::NextFrame() // TRUE if out of frames +{ + if (++m_frameidx >= m_frame_deltas[m_rd_which].GetSize()) + { + m_rd_which=!m_rd_which; + + DecompressBlock(m_rd_which,1.0); + if (!ReadHdr(!m_rd_which)) + memset(&m_curhdr[!m_rd_which],0,sizeof(m_curhdr[!m_rd_which])); + m_frameidx=0; + if (!m_curhdr[m_rd_which].bpp) return false; + DecodeSlices(); + } + else + DecompressBlock(!m_rd_which,m_frameidx/(double)m_frame_deltas[m_rd_which].GetSize()); + return false; +} + +int LICECaptureDecompressor::Seek(unsigned int offset_ms) +{ + + memset(m_curhdr,0,sizeof(m_curhdr)); + if (!m_file) return -1; + + int rval=0; + + unsigned int seekpos=0; + m_frameidx=0; + if (offset_ms>0&&m_file_frame_info.GetSize()) + { + int x; + for(x=0;x0) rval=-1; + offset_ms=0; + } + + m_rd_which=0; + m_file->SetPosition(seekpos); + if (!ReadHdr(m_rd_which)||!DecompressBlock(m_rd_which,1.0)) + { + rval=-1; + memset(&m_curhdr,0,sizeof(m_curhdr)); + } + else + { + if (offset_ms>0 && rval==0) + { + int x; + for (x = 1; x < m_frame_deltas[m_rd_which].GetSize(); x++) + { + if (offset_ms < m_frame_deltas[m_rd_which].Get()[x]) + { + rval = offset_ms; + break; + } + offset_ms -= m_frame_deltas[m_rd_which].Get()[x]; + } + m_frameidx=x-1; + } + if (!ReadHdr(!m_rd_which)) + memset(&m_curhdr[!m_rd_which],0,sizeof(m_curhdr[!m_rd_which])); + + DecodeSlices(); + } + + return rval; +} + + +bool LICECaptureDecompressor::ReadHdr(int whdr) // todo: eventually make this read/decompress the next header as it goes +{ + m_tmp.Clear(); + int hdr_sz = (4*9); + if (m_file->Read(m_tmp.Add(NULL,hdr_sz),hdr_sz)!=hdr_sz) return false; + m_bytes_read+=hdr_sz; + int ver=0; + m_tmp.GetTFromLE(&ver); + if (ver !=LCF_VERSION) return false; + m_tmp.GetTFromLE(&m_curhdr[whdr].bpp); + m_tmp.GetTFromLE(&m_curhdr[whdr].w); + m_tmp.GetTFromLE(&m_curhdr[whdr].h); + m_tmp.GetTFromLE(&m_curhdr[whdr].bsize_w); + m_tmp.GetTFromLE(&m_curhdr[whdr].bsize_h); + int nf=0; + m_tmp.GetTFromLE(&nf); + int csize=0; + m_tmp.GetTFromLE(&csize); + + int dsize=0; + m_tmp.GetTFromLE(&dsize); + + if (nf<1 || nf > 1024) return false; + + m_frame_deltas[whdr].Resize(nf); + + if (m_frame_deltas[whdr].GetSize()!=nf) return false; + + if (m_file->Read(m_frame_deltas[whdr].Get(),nf*4)!=nf*4) return false; + m_bytes_read+=nf*4; + int x; + for(x=0;xpercent) break; + } + m_compstream.next_in = buf; + m_compstream.avail_in = m_curhdr[whdr].cdata_left; + if (m_compstream.avail_in > (int)sizeof(buf)) m_compstream.avail_in=(int)sizeof(buf); + + m_compstream.avail_in = m_file->Read(buf,m_compstream.avail_in); + m_bytes_read+=m_compstream.avail_in; + m_curhdr[whdr].cdata_left -= m_compstream.avail_in; + + int e = inflate(&m_compstream,0); + if (e != Z_OK&&e!=Z_STREAM_END) + { +// printf("inflate error: %d (%d/%d)\n",e,m_compstream.avail_in, m_curhdr[whdr].cdata_left); + return !m_compstream.avail_out; + } + if (!m_compstream.avail_out && !m_compstream.avail_in) break; + } + m_compstream.next_in = NULL; + } + + return true; +} + +int LICECaptureDecompressor::GetTimeToNextFrame() +{ + int nf = m_frame_deltas[m_rd_which].GetSize(); + int fidx = m_frameidx; + + if (fidx<0&& nf) return m_frame_deltas[m_rd_which].Get()[0]; + + if (fidx+1 < nf) return m_frame_deltas[m_rd_which].Get()[fidx+1]; + + if (m_curhdr[!m_rd_which].bpp && m_frame_deltas[!m_rd_which].GetSize()) + return m_frame_deltas[!m_rd_which].Get()[0]; + + return 100; +} + +void LICECaptureDecompressor::DecodeSlices() +{ + int nf = m_frame_deltas[m_rd_which].GetSize(); + unsigned char *sp = (unsigned char *)m_decompdata[m_rd_which].Get(); + int sp_left = m_decompdata[m_rd_which].GetSize(); + hdrType *hdr = m_curhdr+m_rd_which; + int ns_x = (hdr->w + hdr->bsize_w-1)/hdr->bsize_w; + int ns_y = (hdr->h + hdr->bsize_h-1)/hdr->bsize_h; + + int ns_frame = ns_x*ns_y; + void **slicelist = m_slices.Resize(nf * ns_frame); + + // format of sp is: + // nf slices + // each slice is : + // initial value + // repeat cnt + // frame + // repeat cnt + // .. + + int ypos, + toth=hdr->h, + totw=hdr->w; + + int bytespersample = (hdr->bpp+7)/8; + + for (ypos = 0; ypos < toth; ypos+=hdr->bsize_h) + { + int hei = toth-ypos; + if (hei>hdr->bsize_h) hei=hdr->bsize_h; + int xpos; + for (xpos=0; xposbsize_w) + { + int wid = totw-xpos; + if (wid>hdr->bsize_w) wid=hdr->bsize_w; + + int sz1=wid*hei*bytespersample; + + int slicewritepos = 0,i = 0; + void *lvalid = NULL; + while (i0) + { + if (lvalid) + { + unsigned char c = *sp++; + sp_left--; + while (c-->0 && i++ < nf) + { + // repeat last slice + slicelist[slicewritepos] = lvalid; + slicewritepos += ns_frame; + } + } + if (i=0 && fidx < nf && m_slices.GetSize() && hdr->bsize_w && hdr->bsize_h) + { + int ns_x = (hdr->w + hdr->bsize_w-1)/hdr->bsize_w; + int ns_y = (hdr->h + hdr->bsize_h-1)/hdr->bsize_h; + + int ns_frame = ns_x*ns_y; + + if (m_slices.GetSize() != ns_frame*nf) + return NULL; // invalid slices + + if (hdr->bpp == 16) + { + m_workbm.resize(hdr->w,hdr->h); + //unsigned short * + // format of m_decompdata is: + // nf frames of slice1, nf frames of slice2, etc + + LICE_pixel *pout = m_workbm.getBits(); + int span = m_workbm.getRowSpan(); + + int ypos, + toth=hdr->h, + totw=hdr->w; + void **sliceptr = m_slices.Get() + ns_frame * fidx; + + for (ypos = 0; ypos < toth; ypos+=hdr->bsize_h) + { + int hei = toth-ypos; + if (hei>hdr->bsize_h) hei=hdr->bsize_h; + int xpos; + for (xpos=0; xposbsize_w) + { + int wid = totw-xpos; + if (wid>hdr->bsize_w) wid=hdr->bsize_w; + + unsigned short *rdptr = (unsigned short *)*sliceptr; + + sliceptr++; + + LICE_pixel *dest = pout + xpos + ypos*span; + int y; + for (y=0;y>3)&0xFC,(px>>8)&0xF8,255); + } + dest+=span; + } + } + } + + + return &m_workbm; + } + } + return NULL; +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lcf.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lcf.h new file mode 100644 index 0000000..6d16570 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lcf.h @@ -0,0 +1,111 @@ +#ifndef _LICE_LCF_H_ +#define _LICE_LCF_H_ + +#include "../zlib/zlib.h" +#include "lice.h" + +#include "../ptrlist.h" +#include "../queue.h" +class WDL_FileWrite; +class WDL_FileRead; + +class LICECaptureCompressor +{ +public: + LICECaptureCompressor(const char *outfn, int w, int h, int interval=20, int bsize_w=128, int bsize_h=16); + + ~LICECaptureCompressor(); + + bool IsOpen() { return !!m_file; } + void OnFrame(LICE_IBitmap *fr, int delta_t_ms); + + WDL_INT64 GetOutSize() { return m_outsize; } + WDL_INT64 GetInSize() { return m_inbytes; } + +private: + WDL_FileWrite *m_file; + WDL_INT64 m_outsize,m_inbytes; + int m_inframes, m_outframes; + + int m_w,m_h,m_interval,m_bsize_w,m_bsize_h; + + + struct frameRec + { + frameRec(int sz) { data=(unsigned short *)malloc(sz*sizeof(short)); delta_t_ms=0; } + ~frameRec() { free(data); } + unsigned short *data; // shorts + int delta_t_ms; // time (ms) since last frame + }; + WDL_PtrList m_framelists[2]; + WDL_Queue m_current_block; + WDL_Queue m_hdrqueue; + + int m_state, m_which,m_outchunkpos,m_numrows,m_numcols; + int m_current_block_srcsize; + + z_stream m_compstream; + + void BitmapToFrameRec(LICE_IBitmap *fr, frameRec *dest); + void DeflateBlock(void *data, int data_size, bool flush); + void AddHdrInt(int a) { m_hdrqueue.AddToLE(&a); } + + +}; + + +class LICECaptureDecompressor +{ +public: + LICECaptureDecompressor(const char *fn, bool want_seekable=false); + ~LICECaptureDecompressor(); + + + bool IsOpen() { return !!m_file; } + + // only supported if want_seekable=true + int GetLength() { return m_file_length_ms; } // length in ms + int Seek(unsigned int offset_ms); // return -1 on fail (out of range), or >0 to tell you how far into the frame you seeked (0=exact hit) + + bool NextFrame(); // TRUE if out of frames + LICE_IBitmap *GetCurrentFrame(); // can return NULL if error + int GetTimeToNextFrame(); // delta in ms + + int GetWidth(){ return m_curhdr[m_rd_which].w; } + int GetHeight(){ return m_curhdr[m_rd_which].h; } + + int m_bytes_read; // increases for statistics, caller can clear + +private: + LICE_MemBitmap m_workbm; + + struct hdrType + { + int bpp; + int w, h; + int bsize_w, bsize_h; + int cdata_left; + } m_curhdr[2]; + + int m_rd_which; + int m_frameidx; + + bool ReadHdr(int whdr); + bool DecompressBlock(int whdr, double percent=1.0); + + z_stream m_compstream; + WDL_Queue m_tmp; + + WDL_FileRead *m_file; + + unsigned int m_file_length_ms; + WDL_TypedQueue m_file_frame_info; //pairs of offset_bytes, offset_ms + + WDL_TypedBuf m_frame_deltas[2]; + WDL_HeapBuf m_decompdata[2]; + WDL_TypedBuf m_slices; // indexed by [frame][slice] + + void DecodeSlices(); +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_line.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_line.cpp new file mode 100644 index 0000000..9677426 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_line.cpp @@ -0,0 +1,2035 @@ +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "lice.h" +#include "lice_combine.h" +#include "lice_extended.h" +#include +#include +//#include + +#define IGNORE_SCALING(mode) ((mode)&LICE_BLIT_IGNORE_SCALING) + +template inline void SWAP(T& a, T& b) { T tmp = a; a = b; b = tmp; } + +enum { eOK = 0, eXLo = 1, eXHi = 2, eYLo = 4, eYHi = 8 }; + +static int OffscreenTest(int x, int y, int nX, int nY) +{ + int e = eOK; + if (x < 0) e |= eXLo; + else if (x >= nX) e |= eXHi; + if (y < 0) e |= eYLo; + else if (y >= nY) e |= eYHi; + return e; +} + +// Cohen-Sutherland. Returns false if the line is entirely offscreen. +static bool ClipLine(int* pX1, int* pY1, int* pX2, int* pY2, int nX, int nY) +{ + int x1 = *pX1, y1 = *pY1, x2 = *pX2, y2 = *pY2; + int e1 = OffscreenTest(x1, y1, nX, nY); + int e2 = OffscreenTest(x2, y2, nX, nY); + int timeout = 32; + bool accept = false, done = false; + do + { + if (!(e1 | e2)) { + accept = done = true; + } + else + if (e1 & e2) { + done = true; // Line is entirely offscreen. + } + else { + int x, y; + int eOut = e1 ? e1 : e2; + if (eOut & eYHi) { + x = x1 + (int) ((double) (x2 - x1) * (double) (nY - 1 - y1) / (double) (y2 - y1)); + y = nY - 1; + } + else + if (eOut & eYLo) { + x = x1 + (int) ((double) (x2 - x1) * (double) -y1 / (double) (y2 - y1)); + y = 0; + } + else + if (eOut & eXHi) { + y = y1 + (int) ((double) (y2 - y1) * (double) (nX - 1 - x1) / (double) (x2 - x1)); + x = nX - 1; + } + else { + y = y1 + (int) ((double) (y2 - y1) * (double) -x1 / (double) (x2 - x1)); + x = 0; + } + + if (eOut == e1) { + x1 = x; + y1 = y; + e1 = OffscreenTest(x1, y1, nX, nY); + } + else { + x2 = x; + y2 = y; + e2 = OffscreenTest(x2, y2, nX, nY); + } + } + } + while (!done && timeout--); + + *pX1 = x1; + *pY1 = y1; + *pX2 = x2; + *pY2 = y2; + return accept; +} + +template static int OffscreenFTest(T x, T y, T w, T h) +{ + int e = eOK; + if (x < 0.0f) e |= eXLo; + else if (x >= w) e |= eXHi; + if (y < 0.0f) e |= eYLo; + else if (y >= h) e |= eYHi; + return e; +} + +template static bool ClipFLine(T * x1, T * y1, T * x2, T *y2, int w, int h) +{ + T tx1 = *x1, ty1 = *y1, tx2 = *x2, ty2 = *y2; + T tw = (T)(w-1), th = (T)(h-1); + if (!lice_isfinite(tx1) || !lice_isfinite(tx2) || + !lice_isfinite(ty1) || !lice_isfinite(ty2)) return false; + + int e1 = OffscreenFTest(tx1, ty1, tw, th); + int e2 = OffscreenFTest(tx2, ty2, tw, th); + + int timeout = 32; + bool accept = false, done = false; + do + { + if (!(e1|e2)) + { + accept = done = true; + } + else + if (e1&e2) + { + done = true; // Line is entirely offscreen. + } + else + { + T x, y; + int eOut = (e1 ? e1 : e2); + if (eOut&eYHi) + { + x = tx1+(tx2-tx1)*(th-ty1)/(ty2-ty1); + y = th-1.0f; + } + else if (eOut&eYLo) + { + x = tx1+(tx2-tx1)*ty1/(ty1-ty2); + y = 0.0f; + } + else if (eOut&eXHi) + { + y = ty1+(ty2-ty1)*(tw-tx1)/(tx2-tx1); + x = tw-1.0f; + } + else + { + y = ty1+(ty2-ty1)*tx1/(tx1-tx2); + x = 0.0f; + } + + if (eOut == e1) + { + tx1 = x; + ty1 = y; + e1 = OffscreenFTest(tx1, ty1, tw, th); + } + else + { + tx2 = x; + ty2 = y; + e2 = OffscreenFTest(tx2, ty2, tw, th); + } + } + } + while (!done && timeout--); + + *x1 = tx1; + *y1 = ty1; + *x2 = tx2; + *y2 = ty2; + return accept; +} + + +inline static void LICE_DiagLineFAST(LICE_pixel *px, int span, int n, int xstep, int ystep, LICE_pixel color, bool aa) +{ + int step = xstep+ystep; + if (aa) + { + LICE_pixel color75 = ((color>>1)&0x7f7f7f7f)+((color>>2)&0x3f3f3f3f); + LICE_pixel color25 = (color>>2)&0x3f3f3f3f; + while (n--) + { + _LICE_CombinePixelsThreeQuarterMix2FAST::doPixFAST(px, color75); + _LICE_CombinePixelsQuarterMix2FAST::doPixFAST(px+xstep, color25); + _LICE_CombinePixelsQuarterMix2FAST::doPixFAST(px+ystep, color25); + px += step; + } + _LICE_CombinePixelsThreeQuarterMix2FAST::doPixFAST(px, color75); + } + else + { + ++n; + while (n--) + { + *px = color; + px += step; + } + } +} + +inline static void LICE_DottedVertLineFAST(LICE_IBitmap* dest, int x, int y1, int y2, LICE_pixel color) +{ + int span = dest->getRowSpan(); + LICE_pixel* px = dest->getBits()+y1*span+x; + + int n = (y2-y1+1)/2; + while (n--) + { + *px = color; + px += 2*span; + } +} + +// this is the white-color table, doing this properly requires correcting the destination color specifically +#define DO_AA_GAMMA_CORRECT 0 +#if DO_AA_GAMMA_CORRECT +static unsigned char AA_GAMMA_CORRECT[256] = +{ + // 1.8 gamma + 0,11,17,21,25,28,31,34,37,39,42,44,46,48,50,52,54,56,58,60,61,63,65,67,68,70,71,73,74,76,77,79,80,81,83,84,85,87,88,89,91,92,93,94,96,97,98,99,100,101,103,104,105,106,107,108,109,110,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,132,133,134,135,136,137,138,139,140,141,142,142,143,144,145,146,147,148,149,149,150,151,152,153,154,154,155,156,157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169,170,170,171,172,173,173,174,175,176,176,177,178,179,179,180,181,182,182,183,184,184,185,186,187,187,188,189,189,190,191,191,192,193,194,194,195,196,196,197,198,198,199,200,200,201,202,202,203,204,204,205,206,206,207,208,208,209,210,210,211,212,212,213,214,214,215,215,216,217,217,218,219,219,220,220,221,222,222,223,224,224,225,225,226,227,227,228,228,229,230,230,231,231,232,233,233,234,234,235,236,236,237,237,238,239,239,240,240,241,241,242,243,243,244,244,245,245,246,247,247,248,248,249,249,250,251,251,252,252,253,253,254,255 + + // 2.0 gamma + //0,15,22,27,31,35,39,42,45,47,50,52,55,57,59,61,63,65,67,69,71,73,74,76,78,79,81,82,84,85,87,88,90,91,93,94,95,97,98,99,100,102,103,104,105,107,108,109,110,111,112,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,141,142,143,144,145,146,147,148,148,149,150,151,152,153,153,154,155,156,157,158,158,159,160,161,162,162,163,164,165,165,166,167,168,168,169,170,171,171,172,173,174,174,175,176,177,177,178,179,179,180,181,182,182,183,184,184,185,186,186,187,188,188,189,190,190,191,192,192,193,194,194,195,196,196,197,198,198,199,200,200,201,201,202,203,203,204,205,205,206,206,207,208,208,209,210,210,211,211,212,213,213,214,214,215,216,216,217,217,218,218,219,220,220,221,221,222,222,223,224,224,225,225,226,226,227,228,228,229,229,230,230,231,231,232,233,233,234,234,235,235,236,236,237,237,238,238,239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,251,252,252,253,253,254,255 + + // 2.2 gamma + //0,20,28,33,38,42,46,49,52,55,58,61,63,65,68,70,72,74,76,78,80,81,83,85,87,88,90,91,93,94,96,97,99,100,102,103,104,106,107,108,109,111,112,113,114,115,117,118,119,120,121,122,123,124,125,126,128,129,130,131,132,133,134,135,136,136,137,138,139,140,141,142,143,144,145,146,147,147,148,149,150,151,152,153,153,154,155,156,157,158,158,159,160,161,162,162,163,164,165,165,166,167,168,168,169,170,171,171,172,173,174,174,175,176,176,177,178,178,179,180,181,181,182,183,183,184,185,185,186,187,187,188,189,189,190,190,191,192,192,193,194,194,195,196,196,197,197,198,199,199,200,200,201,202,202,203,203,204,205,205,206,206,207,208,208,209,209,210,210,211,212,212,213,213,214,214,215,216,216,217,217,218,218,219,219,220,220,221,222,222,223,223,224,224,225,225,226,226,227,227,228,228,229,229,230,230,231,231,232,232,233,233,234,234,235,235,236,236,237,237,238,238,239,239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247,247,248,248,249,249,249,250,250,251,251,252,252,253,253,254,254,255 + + // 2.6 gamma + //0,30,39,46,51,56,60,63,67,70,73,76,78,81,83,85,87,89,91,93,95,97,99,101,102,104,105,107,109,110,111,113,114,116,117,118,120,121,122,123,125,126,127,128,129,130,131,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,148,149,150,151,152,153,154,155,155,156,157,158,159,160,160,161,162,163,164,164,165,166,167,167,168,169,170,170,171,172,173,173,174,175,175,176,177,177,178,179,179,180,181,181,182,183,183,184,185,185,186,187,187,188,188,189,190,190,191,192,192,193,193,194,195,195,196,196,197,197,198,199,199,200,200,201,201,202,203,203,204,204,205,205,206,206,207,207,208,208,209,210,210,211,211,212,212,213,213,214,214,215,215,216,216,217,217,218,218,219,219,220,220,221,221,222,222,223,223,223,224,224,225,225,226,226,227,227,228,228,229,229,230,230,230,231,231,232,232,233,233,234,234,234,235,235,236,236,237,237,237,238,238,239,239,240,240,240,241,241,242,242,243,243,243,244,244,245,245,245,246,246,247,247,247,248,248,249,249,249,250,250,251,251,251,252,252,253,253,253,254,254,255 +}; +#endif + +static void GetAAPxWeight(int err, int alpha, int* wt, int* iwt) +{ + int i = err>>8; + int w = 255-i; + +#if DO_AA_GAMMA_CORRECT + w = AA_GAMMA_CORRECT[w]; + i = AA_GAMMA_CORRECT[i]; +#endif + + w = (alpha*w) >> 8; + i = (alpha*i) >> 8; + *wt = w; + *iwt = i; +} + +static void GetAAPxWeightFAST(int err, int* wt, int* iwt) +{ + int i = err>>8; + int w = 255-i; + +#if DO_AA_GAMMA_CORRECT + w = AA_GAMMA_CORRECT[w]; + i = AA_GAMMA_CORRECT[i]; +#endif + + *wt = w; + *iwt = i; +} +template class __LICE_LineClassSimple +{ +public: + static void LICE_VertLineFAST(LICE_pixel *px, int span, int len, LICE_pixel color) + { + while (len--) + { + COMBFUNC::doPixFAST(px, color); + px+=span; + } + } + + static void LICE_HorizLineFAST(LICE_pixel *px, int n, LICE_pixel color) + { + while (n--) + { + COMBFUNC::doPixFAST(px, color); + px++; + } + } + static void LICE_VertLine(LICE_pixel *px, int span, int len, int color, int aw) + { + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + while (len--) + { + COMBFUNC::doPix((LICE_pixel_chan*) px, r, g, b, a, aw); + px+=span; + } + } + + static void LICE_HorizLine(LICE_pixel *px, int n, LICE_pixel color, int aw) + { + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + while (n--) + { + COMBFUNC::doPix((LICE_pixel_chan*) px, r, g, b, a, aw); + px++; + } + } + static void LICE_DiagLine(LICE_pixel *px, int span, int n, int xstep, int ystep, LICE_pixel color, int aw) + { + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + int step = xstep+ystep; + + for (int i = 0; i <= n; ++i, px += step) + { + COMBFUNC::doPix((LICE_pixel_chan*) px, r, g, b, a, aw); + } + } + static void LICE_DiagLineAA(LICE_pixel *px, int span, int n, int xstep, int ystep, LICE_pixel color, int aw) + { + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + int step = xstep+ystep; + +#if DO_AA_GAMMA_CORRECT + int iw = aw*AA_GAMMA_CORRECT[256*3/4]/256; + int dw = aw*AA_GAMMA_CORRECT[256/4]/256; +#else + int iw = aw*3/4; + int dw = aw/4; +#endif + for (int i = 0; i < n; ++i, px += step) + { + COMBFUNC::doPix((LICE_pixel_chan*) px, r, g, b, a, iw); + COMBFUNC::doPix((LICE_pixel_chan*) (px+xstep), r, g, b, a, dw); + COMBFUNC::doPix((LICE_pixel_chan*) (px+ystep), r, g, b, a, dw); + } + COMBFUNC::doPix((LICE_pixel_chan*) px, r, g, b, a, iw); + } +}; + + +#ifndef LICE_FAVOR_SIZE +template +#endif +class __LICE_LineClass +{ +public: + +#ifdef LICE_FAVOR_SIZE + #define DOPIX(pout,r,g,b,a,ia) combFunc(pout,r,g,b,a,ia); +#else + #define DOPIX(pout,r,g,b,a,ia) COMBFUNC::doPix(pout,r,g,b,a,ia); +#endif + + static void DashedLine(LICE_IBitmap* dest, int x1, int y1, int x2, int y2, int pxon, int pxoff, LICE_pixel color, int aw +#ifdef LICE_FAVOR_SIZE + , LICE_COMBINEFUNC combFunc +#endif + ) + { + int span = dest->getRowSpan(); + LICE_pixel* px = dest->getBits()+y1*span+x1; + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + if (x1 == x2) + { + int i, y; + for (y = y1; y < y2-pxon; y += pxon+pxoff) + { + for (i = 0; i < pxon; ++i, px += span) DOPIX((LICE_pixel_chan*) px, r, g, b, a, aw) + px += pxoff*span; + } + for (i = 0; i < lice_min(pxon, y2-y); ++i, px += span) DOPIX((LICE_pixel_chan*) px, r, g, b, a, aw) + } + else if (y1 == y2) + { + int i, x; + for (x = x1; x < x2-pxon; x += pxon+pxoff) + { + for (i = 0; i < pxon; ++i, ++px) DOPIX((LICE_pixel_chan*) px, r, g, b, a, aw) + px += pxoff; + } + for (i = 0; i < lice_min(pxon, x2-x); ++i, ++px) DOPIX((LICE_pixel_chan*) px, r, g, b, a, aw) + } + } + + + + + static void LICE_LineImpl(LICE_pixel *px, LICE_pixel *px2, int derr, int astep, int da, int bstep, LICE_pixel color, int aw, bool aa +#ifdef LICE_FAVOR_SIZE + , LICE_COMBINEFUNC combFunc +#endif + ) + { + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + int err = 0; + int i; + int n = (da+1)/2; + + if (aa) + { + DOPIX((LICE_pixel_chan*) px, r, g, b, a, aw) + DOPIX((LICE_pixel_chan*) px2, r, g, b, a, aw) + px += astep; + px2 -= astep; + err = derr; + + if (aw == 256) + { + for (i = 1; i < n; ++i) + { + int wt, iwt; + GetAAPxWeightFAST(err, &wt, &iwt); + DOPIX((LICE_pixel_chan*)px, r, g, b, a, wt) + DOPIX((LICE_pixel_chan*)(px+bstep), r, g, b, a, iwt) + DOPIX((LICE_pixel_chan*)px2, r, g, b, a, wt) + DOPIX((LICE_pixel_chan*)(px2-bstep), r, g, b, a, iwt) + + err += derr; + if (err >= 65536) + { + px += bstep; + px2 -= bstep; + err -= 65536; + } + px += astep; + px2 -= astep; + } + } + else // alpha<256 + { + for (i = 1; i < n; ++i) + { + int wt, iwt; + GetAAPxWeight(err, aw, &wt, &iwt); + DOPIX((LICE_pixel_chan*)px, r, g, b, a, wt) + DOPIX((LICE_pixel_chan*)(px+bstep), r, g, b, a, iwt) + DOPIX((LICE_pixel_chan*)px2, r, g, b, a, wt) + DOPIX((LICE_pixel_chan*)(px2-bstep), r, g, b, a, iwt) + + err += derr; + if (err >= 65536) + { + px += bstep; + px2 -= bstep; + err -= 65536; + } + px += astep; + px2 -= astep; + } + } + if (!(da%2)) + { + int wt, iwt; + if (aw == 256) GetAAPxWeightFAST(err, &wt, &iwt); + else GetAAPxWeight(err, aw, &wt, &iwt); + DOPIX((LICE_pixel_chan*)px, r, g, b, a, wt) + DOPIX((LICE_pixel_chan*)(px+bstep), r, g, b, a, iwt) + } + } + else // not aa + { + for (i = 0; i < n; ++i) + { + DOPIX((LICE_pixel_chan*)px, r, g, b, a, aw) + DOPIX((LICE_pixel_chan*)px2, r, g, b, a, aw) + err += derr; + if (err >= 65536/2) + { + px += bstep; + px2 -= bstep; + err -= 65536; + } + + px += astep; + px2 -= astep; + } + if (!(da%2)) + { + DOPIX((LICE_pixel_chan*)px, r, g, b, a, aw) + } + } + } + + static void LICE_FLineImpl(LICE_pixel *px, int n , int err, int derr, int astep, int bstep, LICE_pixel color, int aw +#ifdef LICE_FAVOR_SIZE + , LICE_COMBINEFUNC combFunc +#endif + + ) // only does AA + { + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + + int wt, iwt; + int i; + + if (aw == 256) + { + for (i = 0; i <= n; ++i) + { + GetAAPxWeightFAST(err, &wt, &iwt); + DOPIX((LICE_pixel_chan*)px, r, g, b, a, wt) + DOPIX((LICE_pixel_chan*)(px+bstep), r, g, b, a, iwt) + + err += derr; + if (err >= 65536) + { + px += bstep; + err -= 65536; + } + px += astep; + } + } + else // alpha != 256 + { + for (i = 0; i <= n; ++i) + { + GetAAPxWeight(err, aw, &wt, &iwt); + DOPIX((LICE_pixel_chan*)px, r, g, b, a, wt) + DOPIX((LICE_pixel_chan*)(px+bstep), r, g, b, a, iwt) + + err += derr; + if (err >= 65536) + { + px += bstep; + err -= 65536; + } + px += astep; + } + } + } + + static void LICE_FLineImplFill(LICE_pixel *px, int n , int err, int derr, int astep, int bstep, LICE_pixel color, int aw, + int fill_sz, int b_pos, unsigned int b_max +#ifdef LICE_FAVOR_SIZE + , LICE_COMBINEFUNC combFunc +#endif + ) + { + // fill_sz always >= 2 + int r = LICE_GETR(color), g = LICE_GETG(color), b = LICE_GETB(color), a = LICE_GETA(color); + + int wt, iwt; + int i; + + const int dbpos = bstep < 0 ? -1 : 1; + + const int b_adj = -(fill_sz/2); + b_pos += b_adj*dbpos; + px += b_adj*bstep; + + fill_sz--; // fill size of 2 has one extra pixel in the middle, 2 AA pixels + + if (aw == 256) + { + for (i = 0; i <= n; ++i) + { + GetAAPxWeightFAST(err, &wt, &iwt); + LICE_pixel *wr = px; + unsigned int bp = b_pos; + if (bp= 65536) + { + px += bstep; + b_pos += dbpos; + err -= 65536; + } + px += astep; + } + } + else // alpha != 256 + { + for (i = 0; i <= n; ++i) + { + GetAAPxWeight(err, aw, &wt, &iwt); + LICE_pixel *wr = px; + unsigned int bp = b_pos; + if (bp= 65536) + { + px += bstep; + b_pos += dbpos; + err -= 65536; + } + px += astep; + } + } + } + +#undef DOPIX +}; + + +void LICE_Line(LICE_IBitmap *dest, int x1, int y1, int x2, int y2, LICE_pixel color, float alpha, int mode, bool aa) +{ + if (!dest) return; + + int w = dest->getWidth(); + int h = dest->getHeight(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(x1); + __LICE_SC(y1); + __LICE_SC(x2); + __LICE_SC(y2); + } + } + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_LINE_ACCEL)) + { + LICE_Ext_Line_acceldata data(x1, y1, x2, y2, color, alpha, mode, aa); + if (dest->Extended(LICE_EXT_LINE_ACCEL, &data)) return; + } +#endif + + if (dest->isFlipped()) + { + y1 = h-1-y1; + y2 = h-1-y2; + } + + if (ClipLine(&x1, &y1, &x2, &y2, w, h)) + { + int xdiff = x2-x1; + if (y1 == y2) // horizontal line optimizations + { + if (x1 > x2) SWAP(x1, x2); + int span = dest->getRowSpan(); + LICE_pixel* px = dest->getBits()+y1*span+x1; + int n=x2-x1+1; + + if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 1.0f) + { + __LICE_LineClassSimple<_LICE_CombinePixelsClobberFAST>::LICE_HorizLineFAST(px, n, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 0.5f) + { + color = (color>>1)&0x7f7f7f7f; + __LICE_LineClassSimple<_LICE_CombinePixelsHalfMix2FAST>::LICE_HorizLineFAST(px, n, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 0.25f) + { + color = (color>>2)&0x3f3f3f3f; + __LICE_LineClassSimple<_LICE_CombinePixelsQuarterMix2FAST>::LICE_HorizLineFAST(px, n, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 0.75f) + { + color = ((color>>1)&0x7f7f7f7f)+((color>>2)&0x3f3f3f3f); + __LICE_LineClassSimple<_LICE_CombinePixelsThreeQuarterMix2FAST>::LICE_HorizLineFAST(px, n, color); + } + else + { + int aw = (int)(256.0f*alpha); +#define __LICE__ACTION(COMBFUNC) __LICE_LineClassSimple::LICE_HorizLine(px, n, color, aw) + __LICE_ACTION_CONSTANTALPHA(mode, aw, false); +#undef __LICE__ACTION + } + } + else if (!xdiff) // vertical line optimizations + { + if (y1 > y2) SWAP(y1, y2); + int len=y2+1-y1; + int span = dest->getRowSpan(); + LICE_pixel* px = dest->getBits()+y1*span+x1; + int aw = (int)(256.0f*alpha); + if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 1.0f) + { + __LICE_LineClassSimple<_LICE_CombinePixelsClobberFAST>::LICE_VertLineFAST(px, span, len, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 0.5f) + { + color = (color>>1)&0x7f7f7f7f; + __LICE_LineClassSimple<_LICE_CombinePixelsHalfMix2FAST>::LICE_VertLineFAST(px, span, len, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 0.25f) + { + color = (color>>2)&0x3f3f3f3f; + __LICE_LineClassSimple<_LICE_CombinePixelsQuarterMix2FAST>::LICE_VertLineFAST(px, span, len, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 0.75f) + { + color = ((color>>1)&0x7f7f7f7f)+((color>>2)&0x3f3f3f3f); + __LICE_LineClassSimple<_LICE_CombinePixelsThreeQuarterMix2FAST>::LICE_VertLineFAST(px, span, len, color); + } + else + { +#define __LICE__ACTION(COMBFUNC) __LICE_LineClassSimple::LICE_VertLine(px, span, len, color,aw) + __LICE_ACTION_CONSTANTALPHA(mode, aw, false); +#undef __LICE__ACTION + } + } + else if ((xdiff=abs(xdiff)) == abs(y2-y1)) // diagonal line optimizations + { + int span = dest->getRowSpan(); + LICE_pixel* px = dest->getBits()+y1*span+x1; + int aw = (int)(256.0f*alpha); + int xstep = (x2 > x1 ? 1 : -1); + int ystep = (y2 > y1 ? span : -span); + if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 1.0f) + { + LICE_DiagLineFAST(px,span, xdiff, xstep, ystep, color, aa); + } + else + { + if (aa) + { +#define __LICE__ACTION(COMBFUNC) __LICE_LineClassSimple::LICE_DiagLineAA(px,span, xdiff, xstep, ystep, color, aw) + __LICE_ACTION_NOSRCALPHA(mode, aw, false); +#undef __LICE__ACTION + } + else + { +#define __LICE__ACTION(COMBFUNC) __LICE_LineClassSimple::LICE_DiagLine(px,span, xdiff, xstep, ystep, color, aw) + __LICE_ACTION_CONSTANTALPHA(mode, aw, false); +#undef __LICE__ACTION + } + } + } + else + { + + // common set-up for normal line draws + + int span = dest->getRowSpan(); + int aw = (int)(256.0f*alpha); + LICE_pixel* px = dest->getBits()+y1*span+x1; + LICE_pixel* px2 = dest->getBits()+y2*span+x2; + + int da, db; + int astep, bstep; + int dx = x2-x1; + int dy = y2-y1; + + if (abs(dx) > abs(dy)) + { + da = dx; + db = dy; + astep = 1; + bstep = span; + } + else + { + da = dy; + db = dx; + astep = span; + bstep = 1; + } + + if (da < 0) + { + da = -da; + db = -db; + SWAP(px, px2); + } + if (db < 0) + { + db = -db; + bstep = -bstep; + } + + double dbda = (double)db/(double)da; + + int derr = (int)(dbda*65536.0); + +#ifdef LICE_FAVOR_SIZE + + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; + +#else + #define __LICE__ACTION(COMBFUNC) __LICE_LineClass::LICE_LineImpl(px,px2, derr, astep, da, bstep, color, aw, aa) +#endif + if (aa) + { + __LICE_ACTION_NOSRCALPHA(mode, aw, false); + } + else + { + __LICE_ACTION_CONSTANTALPHA(mode, aw, false); + } + + #undef __LICE__ACTION + +#ifdef LICE_FAVOR_SIZE + if (blitfunc) __LICE_LineClass::LICE_LineImpl(px,px2, derr, astep, da, bstep, color, aw, aa, blitfunc); +#endif + } + } +} + +void LICE_FLine(LICE_IBitmap* dest, float x1, float y1, float x2, float y2, LICE_pixel color, float alpha, int mode, bool aa) +{ + if (!dest) return; + if (!aa) + { + LICE_Line(dest,(int)x1,(int)y1,(int)x2,(int)y2,color,alpha,mode,false); + return; + } + + int w = dest->getWidth(); + int h = dest->getHeight(); + if (dest->isFlipped()) + { + y1 = (float)(h-1)-y1; + y2 = (float)(h-1)-y2; + } + + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(x1); + __LICE_SC(x2); + __LICE_SC(y1); + __LICE_SC(y2); + } + } + + if (ClipFLine(&x1, &y1, &x2, &y2, w, h)) + { + if (x1 != x2 || y1 != y2) + { + int span = dest->getRowSpan(); + int aw = (int)(256.0f*alpha); + + float a1, a2, b1, b2, da, db; + int astep, bstep; + float dx = x2-x1; + float dy = y2-y1; + + if (fabs(dx) > fabs(dy)) + { + a1 = x1; + a2 = x2; + b1 = y1; + b2 = y2; + da = dx; + db = dy; + astep = 1; + bstep = span; + } + else + { + a1 = y1; + a2 = y2; + b1 = x1; + b2 = x2; + da = dy; + db = dx; + astep = span; + bstep = 1; + } + + if (da < 0.0f) + { + da = -da; + db = -db; + SWAP(a1, a2); + SWAP(b1, b2); + } + if (db < 0.0f) + { + bstep = -bstep; + } + + int n = (int)(floor(a2)-ceil(a1)); + float dbda = db/da; + + float ta = ceil(a1); + float tb = b1+(ta-a1)*dbda; + float bf = tb-floor(tb); + int err = (int)(bf*65536.0f); + if (bstep < 0) err = 65535-err; + int derr = (int)(fabs(dbda)*65536.0f); + + LICE_pixel* px = dest->getBits()+(int)ta*astep+(int)tb*abs(bstep); + + if (bstep < 0) px -= bstep; + +#ifdef LICE_FAVOR_SIZE + + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; + +#else + + #define __LICE__ACTION(COMBFUNC) __LICE_LineClass::LICE_FLineImpl(px,n,err,derr,astep,bstep, color, aw) +#endif + + __LICE_ACTION_NOSRCALPHA(mode, aw, false); + +#ifdef LICE_FAVOR_SIZE + if (blitfunc) __LICE_LineClass::LICE_FLineImpl(px,n,err,derr,astep,bstep, color, aw, blitfunc); +#endif + + #undef __LICE__ACTION + } + } +} + +void LICE_DashedLine(LICE_IBitmap* dest, int x1, int y1, int x2, int y2, int pxon, int pxoff, LICE_pixel color, float alpha, int mode, bool aa) +{ + if (!dest) return; + + int w = dest->getWidth(); + int h = dest->getHeight(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(x1); + __LICE_SC(y1); + __LICE_SC(x2); + __LICE_SC(y2); + __LICE_SCU(pxon); + __LICE_SCU(pxoff); + } + } + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_DASHEDLINE_ACCEL)) + { + LICE_Ext_DashedLine_acceldata data(x1, y1, x2, y2, pxon, pxoff, color, alpha, mode, aa); + if (dest->Extended(LICE_EXT_DASHEDLINE_ACCEL, &data)) return; + } +#endif + + if (ClipLine(&x1, &y1, &x2, &y2, w, h)) + { + if (y1 > y2) SWAP(y1, y2); + if (pxon == 1 && pxoff == 1 && x1 == x2 && (mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && alpha == 1.0f) + { + LICE_DottedVertLineFAST(dest, x1, y1, y2, color); + } + else + { + int aw = (int)(256.0f*alpha); + if (x1 > x2) SWAP(x1, x2); + +#ifdef LICE_FAVOR_SIZE + + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; + +#else + + #define __LICE__ACTION(COMBFUNC) __LICE_LineClass::DashedLine(dest, x1, y1, x2, y2, pxon, pxoff, color, aw); +#endif + + __LICE_ACTION_CONSTANTALPHA(mode, aw, false); + +#ifdef LICE_FAVOR_SIZE + if (blitfunc) __LICE_LineClass::DashedLine(dest, x1, y1, x2, y2, pxon, pxoff, color, aw, blitfunc); +#endif + +#undef __LICE__ACTION + } + } +} + +bool LICE_ClipLine(int* pX1, int* pY1, int* pX2, int* pY2, int xLo, int yLo, int xHi, int yHi) +{ + int x1 = *pX1-xLo; + int y1 = *pY1-yLo; + int x2 = *pX2-xLo; + int y2 = *pY2-yLo; + bool onscreen = ClipLine(&x1, &y1, &x2, &y2, xHi-xLo, yHi-yLo); + *pX1 = x1+xLo; + *pY1 = y1+yLo; + *pX2 = x2+xLo; + *pY2 = y2+yLo; + return onscreen; +} + +bool LICE_ClipFLine(float* px1, float* py1, float* px2, float* py2, float xlo, float ylo, float xhi, float yhi) +{ + float x1 = *px1-xlo; + float y1 = *py1-ylo; + float x2 = *px2-xlo; + float y2 = *py2-ylo; + bool onscreen = ClipFLine(&x1, &y1, &x2, &y2, xhi-xlo, yhi-ylo); + *px1 = x1+xlo; + *py1 = y1+ylo; + *px2 = x2+xlo; + *py2 = y2+ylo; + return onscreen; +} + + +#include "lice_bezier.h" + +static void DoBezierFillSegment(LICE_IBitmap* dest, int x1, int y1, int x2, int y2, int yfill, LICE_pixel color, float alpha, int mode) +{ + if (x2 < x1) return; + if (x2 == x1) + { + if (y1 > y2) SWAP(y1,y2); + int ylo = lice_min(y1,yfill); + int yhi = lice_max(y2,yfill+1); + LICE_FillRect(dest, x1, ylo, 1, yhi-ylo+1, color, alpha, mode); + return; + } + + if ((y1 < yfill) == (y2 < yfill)) + { + if (y1 < yfill) ++yfill; + int x[4] = { x1, x1, x2, x2 }; + int y[4] = { y1, yfill, y2, yfill }; + LICE_FillConvexPolygon(dest, x, y, 4, color, alpha, mode); + } + else + { + int x = x1+(int)((double)(yfill-y1)*(double)(x2-x1)/(double)(y2-y1)); + int yf = yfill; + if (y1 < yfill) ++yf; + LICE_FillTriangle(dest, x1, y1, x1, yf, x, yf, color, alpha, mode); + yf = yfill; + if (y2 < yfill) ++yf; + LICE_FillTriangle(dest, x, yf, x2, yf, x2, y2, color, alpha, mode); + } +} + +static void DoBezierFillSegmentX(LICE_IBitmap* dest, int x1, int y1, int x2, int y2, int xfill, LICE_pixel color, float alpha, int mode) +{ + if (y2 < y1) return; + if (y2 == y1) + { + if (x1 > x2) SWAP(x1,x2); + int xlo = lice_min(x1,xfill); + int xhi = lice_max(x2,xfill+1); + LICE_FillRect(dest, xlo, y1, xhi-xlo+1, 1, color, alpha, mode); + return; + } + + if ((x1 < xfill) == (x2 < xfill)) + { + if (x1 < xfill) ++xfill; + int x[4] = { x1, xfill, x2, xfill }; + int y[4] = { y1, y1, y2+1, y2+1 }; + LICE_FillConvexPolygon(dest, x, y, 4, color, alpha, mode); + } + else + { + int y = y1+(int)((double)(xfill-x1)*(double)(y2-y1)/(double)(x2-x1)); + int xf = xfill; + if (x1 < xfill) ++xf; + LICE_FillTriangle(dest, x1, y1, xf, y1, xf, y, color, alpha, mode); + xf = xfill; + if (x2 < xfill) ++xf; + LICE_FillTriangle(dest, xf, y, xf, y2, x2, y2, color, alpha, mode); + } +} + + +// quadratic bezier ... NOT TESTED YET +// attempt to draw segments no longer than tol px +void LICE_DrawQBezier(LICE_IBitmap* dest, double xstart, double ystart, double xctl, double yctl, double xend, double yend, + LICE_pixel color, float alpha, int mode, bool aa, double tol) +{ + if (!dest) return; + + int w = dest->getWidth(); + + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc) + { + __LICE_SCU(w); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(xstart); + __LICE_SC(ystart); + __LICE_SC(xctl); + __LICE_SC(yctl); + __LICE_SC(xend); + __LICE_SC(yend); + } + mode|=LICE_BLIT_IGNORE_SCALING; + } + + + + + if (xstart > xend) + { + SWAP(xstart, xend); + SWAP(ystart, yend); + } + + double len = sqrt((xctl-xstart)*(xctl-xstart)+(yctl-ystart)*(yctl-ystart)); + len += sqrt((xend-xctl)*(xend-xctl)+(yend-yctl)*(yend-yctl)); + + double xlo = xstart; + double xhi = xend; + double ylo = ystart; + double yhi = yend; + double tlo = 0.0; + double thi = 1.0; + + if (xlo < 0.0f) + { + xlo = 0.0f; + ylo = LICE_Bezier_GetY(xstart, xctl, xend, ystart, yctl, yend, xlo, &tlo); + } + if (xhi >= (float)w) + { + xhi = (float)(w-1); + yhi = LICE_Bezier_GetY(xstart, xctl, xend, ystart, yctl, yend, xhi, &thi); + } + if (xlo > xhi) return; + + len *= (thi-tlo); + if (tol <= 0.0f) tol = 1.0f; + int nsteps = (int)(len/tol); + if (nsteps <= 0) nsteps = 1; + + double dt = (thi-tlo)/(double)nsteps; + double t = tlo+dt; + + double lastx = xlo; + double lasty = ylo; + double x, y; + int i; + for (i = 1; i < nsteps; ++i) + { + LICE_Bezier(xstart, xctl, xend, ystart, yctl, yend, t, &x, &y); + LICE_FLine(dest, lastx, lasty, x, y, color, alpha, mode, aa); + lastx = x; + lasty = y; + t += dt; + } + LICE_FLine(dest, lastx, lasty, xhi, yhi, color, alpha, mode, aa); + +} + +int LICE_CBezPrep(int dest_w, double xstart, double ystart, double xctl1, double yctl1, + double xctl2, double yctl2, double xend, double yend, double tol, bool xbasis, + double* ax, double* bx, double* cx, double* dx, double* ay, double* by, double* cy, double* dy, + double* xlo, double* xhi, double* ylo, double* yhi, double* tlo, double* thi) +{ + const int w = dest_w; + + if ((xbasis && xstart > xend) || (!xbasis && ystart > yend)) + { + SWAP(xstart, xend); + SWAP(xctl1, xctl2); + SWAP(ystart, yend); + SWAP(yctl1, yctl2); + } + + double len = sqrt((xctl1-xstart)*(xctl1-xstart)+(yctl1-ystart)*(yctl1-ystart)); + len += sqrt((xctl2-xctl1)*(xctl2-xctl1)+(yctl2-yctl1)*(yctl2-yctl1)); + len += sqrt((xend-xctl2)*(xend-xctl2)+(yend-yctl2)*(yend-yctl2)); + + LICE_CBezier_GetCoeffs(xstart, xctl1, xctl2, xend, ystart, yctl1, yctl2, yend, ax, bx, cx, ay, by, cy); + *dx = xstart; + *dy = ystart; + + *xlo = xstart; + *xhi = xend; + *ylo = ystart; + *yhi = yend; + *tlo = 0.0; + *thi = 1.0; + + if (*xlo < 0.0f) + { + *xlo = 0.0f; + *ylo = LICE_CBezier_GetY(xstart, xctl1, xctl2, xend, ystart, yctl1, yctl2, yend, *xlo, (double*)NULL, (double*)NULL, (double*)NULL, tlo); + } + if (*xhi > w) + { + *xhi = w; + *yhi = LICE_CBezier_GetY(xstart, xctl1, xctl2, xend, ystart, yctl1, yctl2, yend, *xhi, (double*)NULL, (double*)(double*)NULL, thi, (double*)NULL); + } + if ((xbasis && *xlo > *xhi) || (!xbasis && *ylo > *yhi)) + { + return 0; + } + + len *= (*thi-*tlo); + if (tol <= 0.0f) tol = 1.0f; + int nsteps = (int)(len/tol); + if (nsteps <= 0) nsteps = 1; + return nsteps; +} + +#define __LICE_SC_BEZ \ + __LICE_SC(destbm_w); \ + if (!IGNORE_SCALING(mode)) { \ + __LICE_SC(xstart); \ + __LICE_SC(ystart); \ + __LICE_SC(xctl1); \ + __LICE_SC(yctl1); \ + __LICE_SC(xctl2); \ + __LICE_SC(yctl2); \ + __LICE_SC(xend); \ + __LICE_SC(yend); \ + } + + +void LICE_DrawCBezier(LICE_IBitmap* dest, double xstart, double ystart, double xctl1, double yctl1, + double xctl2, double yctl2, double xend, double yend, LICE_pixel color, float alpha, int mode, bool aa, double tol) +{ + if (!dest) return; + int destbm_w = dest->getWidth(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc) + { + __LICE_SC_BEZ + mode|=LICE_BLIT_IGNORE_SCALING; + } + +#ifndef DISABLE_LICE_EXTENSIONS + if (dest->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_DRAWCBEZIER_ACCEL)) + { + LICE_Ext_DrawCBezier_acceldata data(xstart, ystart, xctl1, yctl1, xctl2, yctl2, xend, yend, color, alpha, mode, aa); + if (dest->Extended(LICE_EXT_DRAWCBEZIER_ACCEL, &data)) return; + } +#endif + + double ax, bx, cx, dx, ay, by, cy, dy; + double xlo, xhi, ylo, yhi; + double tlo, thi; + int nsteps = LICE_CBezPrep(destbm_w, xstart, ystart, xctl1, yctl1, xctl2, yctl2, xend, yend, tol, true, + &ax, &bx, &cx, &dx, &ay, &by, &cy, &dy, &xlo, &xhi, &ylo, &yhi, &tlo, &thi); + if (!nsteps) return; + + double dt = (thi-tlo)/(double)nsteps; + double t = tlo+dt; + + double lastx = xlo; + double lasty = ylo; + double x, y; + int i; + for (i = 1; i < nsteps-1; ++i) + { + EVAL_CBEZXY(x, y, ax, bx, cx, dx, ay, by, cy, dy, t); + LICE_FLine(dest, lastx, lasty, x, y, color, alpha, mode, aa); + lastx = x; + lasty = y; + t += dt; + } + LICE_FLine(dest, lastx, lasty, xhi, yhi, color, alpha, mode, aa); +} + +void LICE_DrawThickCBezier(LICE_IBitmap* dest, double xstart, double ystart, double xctl1, double yctl1, + double xctl2, double yctl2, double xend, double yend, LICE_pixel color, float alpha, int mode, int wid, double tol) +{ + if (!dest) return; + int destbm_w = dest->getWidth(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc) + { + __LICE_SC_BEZ + mode|=LICE_BLIT_IGNORE_SCALING; + } + + double ax, bx, cx, dx, ay, by, cy, dy; + double xlo, xhi, ylo, yhi; + double tlo, thi; + int nsteps = LICE_CBezPrep(destbm_w, xstart, ystart, xctl1, yctl1, xctl2, yctl2, xend, yend, tol, true, + &ax, &bx, &cx, &dx, &ay, &by, &cy, &dy, &xlo, &xhi, &ylo, &yhi, &tlo, &thi); + if (!nsteps) return; + + double dt = (thi-tlo)/(double)nsteps; + double t = tlo+dt; + + double lastx = xlo; + double lasty = ylo; + double x, y; + bool last_xmaj=false; + int i; + for (i = 1; i < nsteps; ++i) + { + if (i == nsteps-1) + { + x = xhi; + y = yhi; + } + else + { + EVAL_CBEZXY(x, y, ax, bx, cx, dx, ay, by, cy, dy, t); + } + LICE_ThickFLine(dest, lastx, lasty, x, y, color, alpha, mode, wid); + + bool xmaj = fabs(x-lastx) > fabs(y-lasty); + if (i>1 && xmaj != last_xmaj) + { + //int color = LICE_RGBA(255,0,0,0); + if (wid>2) + { + // tested this with w=3, w=4, w=8 and all looked pretty decent + double r = wid*.5 - 1; + if (r<0) r=0; + LICE_FillCircle(dest,floor(lastx+0.5),floor(lasty+0.5),.5+r*.707,color,alpha,mode,true); + } + else + { + const int ix = (int)floor(lastx+0.5), iy = (int)floor(lasty); + const double da = lasty - iy; + LICE_PutPixel(dest,ix,iy,color,alpha * (1.0-da),mode); + LICE_PutPixel(dest,ix,iy+1,color,alpha*da,mode); + } + } + + last_xmaj = xmaj; + lastx = x; + lasty = y; + t += dt; + } +} + +void LICE_FillCBezier(LICE_IBitmap* dest, double xstart, double ystart, double xctl1, double yctl1, + double xctl2, double yctl2, double xend, double yend, int yfill, LICE_pixel color, float alpha, int mode, double tol) +{ + if (!dest) return; + int destbm_w = dest->getWidth(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc) + { + __LICE_SC_BEZ + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(yfill); + mode|=LICE_BLIT_IGNORE_SCALING; + } + } + + + double ax, bx, cx, dx, ay, by, cy, dy; + double xlo, xhi, ylo, yhi; + double tlo, thi; + int nsteps = LICE_CBezPrep(destbm_w, xstart, ystart, xctl1, yctl1, xctl2, yctl2, xend, yend, tol, true, + &ax, &bx, &cx, &dx, &ay, &by, &cy, &dy, &xlo, &xhi, &ylo, &yhi, &tlo, &thi); + if (!nsteps) return; + + double dt = (thi-tlo)/(double)nsteps; + double t = tlo+dt; + + int lastfillx = (int)xlo; + int lastfilly = (int)(ylo+0.5f); + double x, y; + int i; + for (i = 1; i < nsteps-1; ++i) + { + EVAL_CBEZXY(x, y, ax, bx, cx, dx, ay, by, cy, dy, t); + if ((int)x >= lastfillx) + { + int xi = (int)x; + int yi = (int)(y+0.5f); + DoBezierFillSegment(dest, lastfillx, lastfilly, xi, yi, yfill, color, alpha, mode); + lastfillx = xi+1; + lastfilly = yi; + } + t += dt; + } + if ((int)(xhi-1.0f) >= lastfillx) + { + DoBezierFillSegment(dest, lastfillx, lastfilly, (int)(xhi-1.0f),(int)(yhi+0.5f), yfill, color, alpha, mode); + } +} + +void LICE_FillCBezierX(LICE_IBitmap* dest, double xstart, double ystart, double xctl1, double yctl1, + double xctl2, double yctl2, double xend, double yend, int xfill, LICE_pixel color, float alpha, int mode, double tol) +{ + if (!dest) return; + + int destbm_w = dest->getWidth(); + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc) + { + __LICE_SC_BEZ + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(xfill); + mode|=LICE_BLIT_IGNORE_SCALING; + } + } + + double ax, bx, cx, dx, ay, by, cy, dy; + double xlo, xhi, ylo, yhi; + double tlo, thi; + int nsteps = LICE_CBezPrep(destbm_w, xstart, ystart, xctl1, yctl1, xctl2, yctl2, xend, yend, tol, false, + &ax, &bx, &cx, &dx, &ay, &by, &cy, &dy, &xlo, &xhi, &ylo, &yhi, &tlo, &thi); + if (!nsteps) return; + + double dt = (thi-tlo)/(double)nsteps; + double t = tlo+dt; + + int lastfillx = (int)(xlo+0.5f); + int lastfilly = (int)ylo; + double x, y; + int i; + for (i = 1; i < nsteps-1; ++i) + { + EVAL_CBEZXY(x, y, ax, bx, cx, dx, ay, by, cy, dy, t); + if ((int)y >= lastfilly) + { + int xi = (int)(x+0.5f); + int yi = (int)y; + DoBezierFillSegmentX(dest, lastfillx, lastfilly, xi, yi, xfill, color, alpha, mode); + lastfillx = xi; + lastfilly = yi+1; + } + t += dt; + } + if ((int)(yhi-1.0f) >= lastfilly) + { + DoBezierFillSegmentX(dest, lastfillx, lastfilly, (int)(xhi+0.5),(int)(yhi-1.0f), xfill, color, alpha, mode); + } +} + + +void LICE_DrawRect(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel color, float alpha, int mode) +{ + const int __sc = IGNORE_SCALING(mode) ? 0 : (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + double x1 = x, y1 = y, x2 = x+w, y2 = y+h; + const double amt = 1.0 - 256.0/__sc; + x1 += amt; + y1 += amt; + x2 -= amt; + y2 -= amt; + LICE_FLine(dest, x1, y1, x2, y1, color, alpha, mode, true); + LICE_FLine(dest, x2, y1, x2, y2, color, alpha, mode, true); + LICE_FLine(dest, x2, y2, x1, y2, color, alpha, mode, true); + LICE_FLine(dest, x1, y2, x1, y1, color, alpha, mode, true); + } + else + { + LICE_Line(dest, x, y, x+w, y, color, alpha, mode, false); + LICE_Line(dest, x+w, y, x+w, y+h, color, alpha, mode, false); + LICE_Line(dest, x+w, y+h, x, y+h, color, alpha, mode, false); + LICE_Line(dest, x, y+h, x, y, color, alpha, mode, false); + } +} + +void LICE_BorderedRect(LICE_IBitmap *dest, int x, int y, int w, int h, LICE_pixel bgcolor, LICE_pixel fgcolor, float alpha, int mode) +{ + LICE_FillRect(dest, x+1, y+1, w-1, h-1, bgcolor, alpha, mode); + LICE_DrawRect(dest, x, y, w, h, fgcolor, alpha, mode); +} + + +#ifndef LICE_FAVOR_SIZE_EXTREME +template +#endif +class _LICE_Fill +{ + +#ifdef LICE_FAVOR_SIZE_EXTREME + #define DOPIX(pout,r,g,b,a,ia) combFunc(pout,r,g,b,a,ia); +#else + #define DOPIX(pout,r,g,b,a,ia) COMBFUNC::doPix(pout,r,g,b,a,ia); +#endif + +public: + + // da, db are [0..65536] + static void FillClippedTrapezoid(int wid, int span, LICE_pixel *px, int y, int xa, int xb, int da, int db, int a, int b, int astep, int bstep, int cr, int cg, int cb, int ca, int aw +#ifdef LICE_FAVOR_SIZE_EXTREME + , LICE_COMBINEFUNC combFunc +#endif + ) + { + if (!da && !db) + { + while (y-->0) + { + LICE_pixel* xpx = px; + int x=xb; + while (x--) + { + DOPIX((LICE_pixel_chan*)xpx, cr, cg, cb, ca, aw) + ++xpx; + } + px += span; + } + return; + } + + + while (y-->0) + { + int x1=lice_max(xa,0); + int x2=lice_min(xb,wid); + LICE_pixel* xpx = px + x1; + int cnt=x2-x1; + while (cnt-->0) + { + DOPIX((LICE_pixel_chan*)xpx, cr, cg, cb, ca, aw) + ++xpx; + } + + a += da; + b += db; + if (a >= 65536) + { + int na = a>>16; + a &= 65535; + if (astep<0)na=-na; + xa += na; + } + if (b >= 65536) + { + int nb = b>>16; + b &= 65535; + if (bstep<0)nb=-nb; + xb += nb; + } + px += span; + } + } +}; + + +template class _LICE_FillFast +{ +public: + + // da, db are [0..65536] + static void FillClippedTrapezoidFAST(int wid, int span, LICE_pixel *px, int y, int xa, int xb, int da, int db, int a, int b, int astep, int bstep, LICE_pixel color) + { + if (!da && !db) + { + while (y-->0) + { + LICE_pixel* xpx = px; + int x=xb; + while (x--) + { + COMBFUNC::doPixFAST(xpx, color); + ++xpx; + } + px += span; + } + return; + } + + + while (y-->0) + { + int x1=lice_max(xa,0); + int x2=lice_min(xb,wid); + LICE_pixel* xpx = px + x1; + int cnt=x2-x1; + while (cnt-->0) + { + COMBFUNC::doPixFAST(xpx, color); + ++xpx; + } + + a += da; + b += db; + if (a >= 65536) + { + int na = a>>16; + a &= 65535; + if (astep<0)na=-na; + xa += na; + } + if (b >= 65536) + { + int nb = b>>16; + b &= 65535; + if (bstep<0)nb=-nb; + xb += nb; + } + px += span; + } + } +}; + +static double FindXOnSegment(int x1, int y1, int x2, int y2, int ty) +{ + if (y1 > y2) + { + SWAP(x1, x2); + SWAP(y1, y2); + } + if (ty <= y1) return x1; + if (ty >= y2) return x2; + const double dxdy = (x2-x1)/(double)(y2-y1); + return x1+(ty-y1)*dxdy; +} + +void LICE_FillTrapezoidF(LICE_IBitmap* dest, double fx1a, double fx1b, int y1, double fx2a, double fx2b, int y2, LICE_pixel color, float alpha, int mode) +{ + if (!dest) return; + if (y1 > y2) + { + SWAP(y1, y2); + SWAP(fx1a, fx2a); + SWAP(fx1b, fx2b); + } + if (fx1a > fx1b) SWAP(fx1a, fx1b); + if (fx2a > fx2b) SWAP(fx2a, fx2b); + + int w = dest->getWidth(); + int h = dest->getHeight(); + + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(fx1a); + __LICE_SC(fx1b); + __LICE_SC(fx2a); + __LICE_SC(fx2b); + __LICE_SC(y1); + __LICE_SC(y2); + } + } + + if (fx1b < 0 && fx2b < 0) return; + if (fx1a >= w && fx2a >= w) return; + + if (fx1a <= 0 && fx2a <= 0) fx1a = fx2a = 0; + if (fx1b >= w-1 && fx2b >= w-1) fx1b = fx2b = w-1; + + if (y2 < 0 || y1 >= h) return; + + int aw = (int)(alpha*256.0f); + + double idy = y2==y1 ? 0.0 : (65536.0/(y2-y1)); + + const double maxv=(double)(1<<29); + double tmp = (fx2a-fx1a)*idy; + if (tmp > maxv) tmp=maxv; + else if (tmp < -maxv) tmp=-maxv; + int dxady = (int)floor(tmp+0.5); + + tmp = ((fx2b-fx1b)*idy); + if (tmp > maxv) tmp=maxv; + else if (tmp < -maxv) tmp=-maxv; + int dxbdy = (int)floor(tmp+0.5); + + int astep = 1; + int bstep = 1; + if (dxady < 0) + { + dxady = -dxady; + astep = -1; + } + if (dxbdy < 0) + { + dxbdy = -dxbdy; + bstep = -1; + } + + int x1a = (int)floor(fx1a); + int x1b = (int)floor(fx1b); + int a = (int) floor((fx1a-x1a)*65536.0*astep+0.5); + int b = (int) floor((fx1b-x1b)*65536.0*bstep+0.5); + + if (y1<0) + { + a -= dxady*y1; + b -= dxbdy*y1; + y1=0; + } + if (a< 0 || a >= 65536) + { + int na = a>>16; + a &= 65535; + if (astep<0)na=-na; + x1a += na; + } + if (b < 0 || b >= 65536) + { + int nb = b>>16; + b &= 65535; + if (bstep<0)nb=-nb; + x1b += nb; + } + const int extra = __sc> 0 && !IGNORE_SCALING(mode) ? (__sc/256 - 1) : 0; + if (y2 > h-1-extra) y2 = h-1-extra; + + int wid = w; + int span = dest->getRowSpan(); + LICE_pixel* px = dest->getBits()+y1*span; + int y = y2-y1 + 1 + extra; + + x1b++; // from now on draw [x1a,x1b) + + if (!dxady && !dxbdy) + { + if (x1a<0)x1a=0; + x1b = lice_min(x1b,wid)-x1a; + px+=x1a; + if (x1b<1) return; + } + + if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && aw==256) + { + _LICE_FillFast<_LICE_CombinePixelsClobberFAST>::FillClippedTrapezoidFAST(wid,span,px,y, x1a, x1b, dxady, dxbdy, a,b, astep,bstep, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && aw==128) + { + color = (color>>1)&0x7f7f7f7f; + _LICE_FillFast<_LICE_CombinePixelsHalfMix2FAST>::FillClippedTrapezoidFAST(wid,span,px,y, x1a, x1b, dxady, dxbdy, a,b, astep,bstep, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && aw==64) + { + color = (color>>2)&0x3f3f3f3f; + _LICE_FillFast<_LICE_CombinePixelsQuarterMix2FAST>::FillClippedTrapezoidFAST(wid,span,px,y, x1a, x1b, dxady, dxbdy, a,b, astep,bstep, color); + } + else if ((mode&LICE_BLIT_MODE_MASK) == LICE_BLIT_MODE_COPY && aw==192) + { + color = ((color>>1)&0x7f7f7f7f)+((color>>2)&0x3f3f3f3f); + _LICE_FillFast<_LICE_CombinePixelsThreeQuarterMix2FAST>::FillClippedTrapezoidFAST(wid,span,px,y, x1a, x1b, dxady, dxbdy,a,b, astep,bstep, color); + } + else + { + int cr = LICE_GETR(color), cg = LICE_GETG(color), cb = LICE_GETB(color), ca = LICE_GETA(color); +#ifdef LICE_FAVOR_SIZE_EXTREME + + LICE_COMBINEFUNC blitfunc=NULL; +#define __LICE__ACTION(comb) blitfunc=comb::doPix; +#else +#define __LICE__ACTION(COMBFUNC) _LICE_Fill::FillClippedTrapezoid(wid,span,px,y, x1a, x1b, dxady, dxbdy, a,b, astep,bstep, cr,cg,cb,ca, aw); +#endif + + __LICE_ACTION_CONSTANTALPHA(mode, aw, false); + +#ifdef LICE_FAVOR_SIZE_EXTREME + if (blitfunc) _LICE_Fill::FillClippedTrapezoid(wid,span,px,y, x1a, x1b, dxady, dxbdy, a,b, astep,bstep, cr,cg,cb,ca, aw, blitfunc); +#endif + +#undef __LICE__ACTION + } +} + +void LICE_FillTrapezoid(LICE_IBitmap* dest, int x1a, int x1b, int y1, int x2a, int x2b, int y2, LICE_pixel color, float alpha, int mode) +{ + LICE_FillTrapezoidF(dest,x1a,x1b,y1,x2a,x2b,y2,color,alpha,mode); +} + +static int _ysort(const void* a, const void* b) +{ + int* xya = (int*)a; + int* xyb = (int*)b; + if (xya[1] < xyb[1]) return -1; + if (xya[1] > xyb[1]) return 1; + if (xya[0] < xyb[0]) return -1; + if (xya[0] > xyb[0]) return 1; + return 0; +} + +#define _X(i) xy[2*(i)] +#define _Y(i) xy[2*(i)+1] + +static int FindNextEdgeVertex(int* xy, int a, int n, int dir) +{ + bool init = false; + double dxdy_best = 0.0f; + int i, ilo = a; + + for (i = a+1; i < n; ++i) + { + if (_Y(i) == _Y(a)) continue; + const double dxdy = (_X(i)-_X(a))/(double)(_Y(i)-_Y(a)); + if (!init || dxdy == dxdy_best || (dir < 0 && dxdy < dxdy_best) || (dir > 0 && dxdy > dxdy_best)) + { + init = true; + ilo = i; + dxdy_best = dxdy; + } + } + return ilo; +} + +void LICE_FillConvexPolygon(LICE_IBitmap* dest, const int* x, const int* y, int npoints, LICE_pixel color, float alpha, int mode) +{ + if (!dest) return; + if (npoints < 3) return; + + int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + + int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc) + { + __LICE_SCU(destbm_w); + __LICE_SCU(destbm_h); + if (IGNORE_SCALING(mode)) __sc = 0; // input is already scaled, ignore + mode |= LICE_BLIT_IGNORE_SCALING; + } + + int* xy = 0; + int xyt[1024]; // use stack space if small + bool usestack = npoints <= (int) (sizeof(xyt)/sizeof(int)/2); + if (usestack) xy = xyt; + else xy = (int*)malloc(npoints*sizeof(int)*2); + + int i; + { + int min_x=destbm_w,max_x=0; + for (i = 0; i < npoints; ++i) + { + int tx = x[i], ty=y[i]; + if (__sc) + { + __LICE_SC(tx); + __LICE_SC(ty); + } + if (tx < min_x) min_x=tx; + if (tx > max_x) max_x=tx; + _X(i) = tx; + if (dest->isFlipped()) ty = destbm_h-ty-1; + _Y(i) = ty; + } + qsort(xy, npoints, 2*sizeof(int), _ysort); // sorts by y, at same y sorts by x + + + int ty=_Y(0); + if (ty == _Y(npoints-1)) + { + // special case 1px high polygon + if (ty >= 0 && ty < dest->getHeight() && min_x <= max_x) + { + LICE_FillTrapezoid(dest,min_x,max_x,ty,min_x,max_x,ty,color,alpha,mode); + } + if (!usestack) free(xy); + + return; + } + } + + int a1, b1; // index of previous vertex L and R + int a2, b2; // index of next vertex L and R + int y1; // top and bottom of current trapezoid + + a1 = b1 = 0; + y1 = _Y(0); + + for (i = 1; i < npoints && _Y(i) == y1; ++i) + { + if (_X(i) == _X(0)) a1 = i; + b1 = i; + } + + a2 = FindNextEdgeVertex(xy, a1, npoints, -1); + b2 = FindNextEdgeVertex(xy, b1, npoints, 1); + + while (a1 != a2 || b1 != b2) + { + int y_a2 = _Y(a2); + int y_b2 = _Y(b2); + + int y2 = lice_min(y_a2, y_b2); + double x1a = FindXOnSegment(_X(a1), _Y(a1), _X(a2), y_a2, y1); + double x1b = FindXOnSegment(_X(b1), _Y(b1), _X(b2), y_b2, y1); + double x2a = FindXOnSegment(_X(a1), _Y(a1), _X(a2), y_a2, y2); + double x2b = FindXOnSegment(_X(b1), _Y(b1), _X(b2), y_b2, y2); + + LICE_FillTrapezoidF(dest, x1a, x1b, y1, x2a, x2b, y2, color, alpha, mode); + + bool dir = y1<=y2; // should always be true + + y1 = y2; + if (y_a2 == y1) + { + a1 = a2; + a2 = FindNextEdgeVertex(xy, a2, npoints, -1); + } + if (y_b2 == y1) + { + b1 = b2; + b2 = FindNextEdgeVertex(xy, b2, npoints, 1); + } + + if (dir) y1++; + else y1--; + } + + if (!usestack) free(xy); +} + +#undef _X +#undef _Y + + +void LICE_FillTriangle(LICE_IBitmap *dest, int x1, int y1, int x2, int y2, int x3, int y3, LICE_pixel color, float alpha, int mode) +{ + if (!dest) return; + + int x[3] = { x1, x2, x3 }; + int y[3] = { y1, y2, y3 }; + LICE_FillConvexPolygon(dest, x, y, 3, color, alpha, mode); +} + +void LICE_ThickFLine(LICE_IBitmap* dest, double x1, double y1, double x2, double y2, LICE_pixel color, float alpha, int mode, int wid) // always AA. wid is not affected by scaling (1 is always normal line, 2 is always 2 physical pixels, etc) +{ + if (!dest || wid<1) return; + if (wid==1) + { + LICE_Line(dest,(float)x1,(float)y1,(float)x2,float(y2),color,alpha,mode,true); + return; + } + + int w = dest->getWidth(); + int h = dest->getHeight(); + if (dest->isFlipped()) + { + y1 = (h-1)-y1; + y2 = (h-1)-y2; + } + + const int __sc = (int)dest->Extended(LICE_EXT_GET_SCALING,NULL); + if (__sc>0) + { + __LICE_SCU(w); + __LICE_SCU(h); + if (!IGNORE_SCALING(mode)) + { + __LICE_SC(x1); + __LICE_SC(x2); + __LICE_SC(y1); + __LICE_SC(y2); + } + } + + bool steep = fabs(y2-y1) >= fabs(x2-x1); + + if (ClipFLine(&x1, &y1, &x2, &y2, w, h)) + { + if (x1 != x2 || y1 != y2) + { + int span = dest->getRowSpan(); + int aw = (int)(256.0f*alpha); + + double a1, a2, b1, b2, da, db; + int astep, bstep; + double dx = x2-x1; + double dy = y2-y1; + + int b_max; + if (!steep) + { + a1 = x1; + a2 = x2; + b1 = y1; + b2 = y2; + da = dx; + db = dy; + astep = 1; + bstep = span; + b_max = h; + } + else + { + a1 = y1; + a2 = y2; + b1 = x1; + b2 = x2; + da = dy; + db = dx; + astep = span; + bstep = 1; + b_max = w; + } + + if (da < 0.0) + { + da = -da; + db = -db; + SWAP(a1, a2); + SWAP(b1, b2); + } + if (db < 0.0) + { + bstep = -bstep; + } + + int n = (int)(floor(a2)-ceil(a1)); + double dbda = db/da; + + double ta = ceil(a1); + double tb = b1+(ta-a1)*dbda; + double bf = tb-floor(tb); + int err = (int)(bf*65536.0); + if (bstep < 0) err = 65535-err; + int derr = (int)(fabs(dbda)*65536.0); + + int b_pos = (int) tb; + LICE_pixel *px = dest->getBits() + (int)ta*astep+b_pos*abs(bstep); + + if (bstep < 0) { px -= bstep; b_pos++; } + +#ifdef LICE_FAVOR_SIZE + + LICE_COMBINEFUNC blitfunc=NULL; + #define __LICE__ACTION(comb) blitfunc=comb::doPix; + +#else + #define __LICE__ACTION(COMBFUNC) \ + __LICE_LineClass::LICE_FLineImplFill(px,n,err,derr,astep,bstep, color, aw, wid, b_pos, b_max) +#endif + + __LICE_ACTION_NOSRCALPHA(mode, aw, false); + +#ifdef LICE_FAVOR_SIZE + if (blitfunc) + { + __LICE_LineClass::LICE_FLineImplFill(px,n,err,derr,astep,bstep, color, aw, wid, b_pos, b_max, blitfunc); + } +#endif + + #undef __LICE__ACTION + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lvg.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lvg.cpp new file mode 100644 index 0000000..d68bb06 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_lvg.cpp @@ -0,0 +1,625 @@ +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "lice.h" +#include +#include +#include "../projectcontext.h" +#include "../lineparse.h" +#include "../ptrlist.h" +#include "../assocarray.h" + +#define PI 3.1415926535897932384626433832795 + +static inline int chartohex(char c) +{ + if (c >= '0' && c<='9') return c-'0'; + else if (c>='A' && c<='F') return 10 + c - 'A'; + else if (c>='a' && c<='f') return 10 + c - 'a'; + return -1; +} +static int __boolval(const char *p, int defval) +{ + if (!stricmp(p,"yes") || + !stricmp(p,"true") || + !stricmp(p,"on") || + atoi(p)>0) return 1; + if (!stricmp(p,"no") || + !stricmp(p,"false") || + !stricmp(p,"off") || + !stricmp(p,"0")) return 0; + return defval; +} + +static LICE_pixel __colorval(const char *p, LICE_pixel def) +{ + const size_t lp = strlen(p); + if (lp == 3) + { + int r = chartohex(p[0]); + int g = chartohex(p[1]); + int b = chartohex(p[2]); + if (r>=0&&g>=0&&b>=0) + def = LICE_RGBA(r+(r<<4),g+(g<<4),b+(b<<4),255); + } + else if (lp == 6||lp==8) + { + int r = chartohex(p[0]), r2 = chartohex(p[1]); + int g = chartohex(p[2]), g2 = chartohex(p[3]); + int b = chartohex(p[4]), b2 = chartohex(p[5]); + int a = 0xf, a2=0xf; + if (lp==8) { a=chartohex(p[6]); a2=chartohex(p[7]); } + if (r>=0&&g>=0&&b>=0&&r2>=0&&g2>=0&&b2>=0&&a>=0&&a2>=0) + def = LICE_RGBA((r<<4)+r2,(g<<4)+g2,(b<<4)+b2,(a<<4)+a2); + } + return def; +} + +class lvgRenderState +{ +public: + lvgRenderState() + { + m_color=LICE_RGBA(255,255,255,255); + m_alpha=1.0f; + m_blend = LICE_BLIT_MODE_COPY; + m_aa = false; + } + ~lvgRenderState() { } + + LICE_pixel m_color; + float m_alpha; + int m_blend; + bool m_aa; + + WDL_TypedBuf m_aa_stack; + WDL_TypedBuf m_alpha_stack; + WDL_TypedBuf m_color_stack; + WDL_TypedBuf m_blend_stack; + +///////// + + void parsealpha(const char *p) + { + int idx=0; + if (*p == '-') idx++; + if (p[idx] == '.' || (p[idx] >= '0' && p[idx] <= '9')) + m_alpha = (float)atof(p); + } + + void parseaa(const char *p) + { + int a = __boolval(p,-1); + if (a>=0) m_aa = !!a; + } + + void parseblend(const char *p) + { + if (!stricmp(p,"copy")) m_blend = LICE_BLIT_MODE_COPY; + else if (!stricmp(p,"add")) m_blend = LICE_BLIT_MODE_ADD; + else if (!stricmp(p,"dodge")) m_blend = LICE_BLIT_MODE_DODGE; + else if (!stricmp(p,"mul")||!stricmp(p,"multiply")) m_blend = LICE_BLIT_MODE_MUL; + else if (!stricmp(p,"overlay")) m_blend = LICE_BLIT_MODE_MUL; + else if (!stricmp(p,"hsvadj")) m_blend = LICE_BLIT_MODE_HSVADJ; + } + + void parsecolor(const char *p) + { + m_color = __colorval(p,m_color); + } + +#define DEF_PUSHPOP(name,defpopval) \ + void push##name() { int sz=m_##name##_stack.GetSize(); m_##name##_stack.Resize(sz+1,false)[sz] = m_##name; } \ + void pop##name() { int sz = m_##name##_stack.GetSize()-1; m_##name = sz>=0 ? m_##name##_stack.Get()[sz] : defpopval; m_##name##_stack.Resize(sz,false); } + DEF_PUSHPOP(color,LICE_RGBA(0,0,0,255)) + DEF_PUSHPOP(alpha,1.0f) + DEF_PUSHPOP(aa,false) + DEF_PUSHPOP(blend,LICE_BLIT_MODE_COPY) +#undef DEF_PUSHPOP + + bool processAttributeLine(LineParser *lp) + { + int i,numtok=lp->getnumtokens(); + switch (lp->gettoken_enum(0,"color\0" + "alpha\0" + "aa\0" + "blend\0" + "\0")) + { +#define PROCTYPE(v,name) \ + case v: for (i=1;igettoken_str(i); \ + if (!stricmp(p,"push")) push##name(); \ + else if (!stricmp(p,"pop")) pop##name(); \ + else parse##name(p); \ + } \ + return true; + + PROCTYPE(0,color) + PROCTYPE(1,alpha) + PROCTYPE(2,aa) + PROCTYPE(3,blend) +#undef PROCTYPE + + } + return false; + } + + +}; + +class lvgImageCtx +{ +public: + lvgImageCtx(lvgImageCtx *par) : m_images(true,deleteThis) + { + m_in_render=false; + m_par=par; + m_cachedImage=0; + m_base_w=0; + m_base_h=0; + } + ~lvgImageCtx() + { + delete m_cachedImage; + m_lines.Empty(true,free); + } + + WDL_PtrList m_lines; + LICE_IBitmap *m_cachedImage; + + lvgImageCtx *m_par; + WDL_StringKeyedArray m_images; + + int m_base_w,m_base_h; + bool m_in_render; + + void render(lvgRenderState *rstate, int wantw, int wanth); + +private: + static void deleteThis(lvgImageCtx *t) { delete t; } + + double parsecoord(const char *p, double scale, bool round) + { + if (!*p) return 0; + if (p[0] == 'a' && p[1]) return atoi(p+1); + if (p[0] == 'w') + { + scale = m_cachedImage ? m_cachedImage->getWidth() : 0.0; + p++; + } + else if (p[0] == 'h') + { + scale = m_cachedImage ? m_cachedImage->getHeight() : 0.0; + p++; + } + return atof(p) * scale + (round ? 0.5 : 0.0); + } + void processLvgLine(LineParser *lp, lvgRenderState *state, LICE_IBitmap *bm, double xscale, double yscale); + +}; + +#define DECL_OPT(type, cfunc) \ + static type getoption_##type(LineParser *lp, int startidx, const char *name, type def) { \ + const size_t namelen = strlen(name); \ + for(;startidxgetnumtokens();startidx++) { \ + const char *p=lp->gettoken_str(startidx); \ + if (!strnicmp(name,p,namelen) && p[namelen]=='=') return cfunc(p+namelen+1,def); \ + } \ + return def; \ + } + +static int __intval(const char *p, int def) { return atoi(p); } +static double __dblval(const char *p, double def) { return atof(p); } + +DECL_OPT(bool,!!__boolval) +DECL_OPT(int,__intval) +DECL_OPT(double,__dblval) +DECL_OPT(LICE_pixel,__colorval) + +#undef DECL_OPT + +void lvgImageCtx::processLvgLine(LineParser *lp, lvgRenderState *state, LICE_IBitmap *bm, double xscale, double yscale) +{ + if (state->processAttributeLine(lp)) return; + + int numtok = lp->getnumtokens(); + const char *tok = lp->gettoken_str(0); + if (!stricmp(tok,"line")) + { + int i; + float lx,ly; + for (i = 1; i < numtok-1; i+= 2) + { + const char *p=lp->gettoken_str(i); + if (strstr(p,"=")) break; + float x=(float)parsecoord(p,xscale,false); + p=lp->gettoken_str(i+1); + if (strstr(p,"=")) break; + float y=(float)parsecoord(p,yscale,false); + + if (i!=1) LICE_FLine(bm,lx,ly,x,y,state->m_color,state->m_alpha,state->m_blend,state->m_aa); + + lx=x; + ly=y; + } + } + else if (!stricmp(tok,"circle")) + { + if (numtok>=4) + { + float x=(float)parsecoord(lp->gettoken_str(1),xscale,false); + float y=(float)parsecoord(lp->gettoken_str(2),yscale,false); + float r=(float)(atof(lp->gettoken_str(3))*lice_min(xscale,yscale)); + if (getoption_bool(lp,1,"fill",false)) + { + LICE_FillCircle(bm,x,y,r,state->m_color,state->m_alpha,state->m_blend,state->m_aa); + } + else + LICE_Circle(bm,x,y,r,state->m_color,state->m_alpha,state->m_blend,state->m_aa); + } + } + else if (!stricmp(tok,"arc")) + { + if (numtok>=6) + { + float x=(float)parsecoord(lp->gettoken_str(1),xscale,false); + float y=(float)parsecoord(lp->gettoken_str(2),yscale,false); + float r=(float)(atof(lp->gettoken_str(3))*lice_min(xscale,yscale)); + float a1=(float)(atof(lp->gettoken_str(4))*PI/180.0); + float a2=(float)(atof(lp->gettoken_str(5))*PI/180.0); + LICE_Arc(bm,x,y,r,a1,a2,state->m_color,state->m_alpha,state->m_blend,state->m_aa); + } + } + else if (!stricmp(tok,"fill")) + { + if (numtok>=3) // fill x y [cmask=xxxxxx kmask=xxxxxxx] + { + LICE_pixel cmask = getoption_LICE_pixel(lp,1,"cmask",LICE_RGBA(255,255,255,0)); + LICE_pixel kmask = getoption_LICE_pixel(lp,1,"kmask",LICE_RGBA(0,0,0,0)); + int x = (int)parsecoord(lp->gettoken_str(1),xscale,true); + int y = (int)parsecoord(lp->gettoken_str(2),yscale,true); + + LICE_SimpleFill(bm,x,y,state->m_color,cmask,kmask); + } + } + else if (!stricmp(tok,"rect")) + { + if (numtok>=5) // rect x y w h [dcdx=xxxxxxxx dcdy=xxxxxxxxx dcdxscale=1.0 dcdyscale=1.0] + { + LICE_pixel dcdx = getoption_LICE_pixel(lp,1,"dcdx",LICE_RGBA(0x80,0x80,0x80,0x80)); + LICE_pixel dcdy = getoption_LICE_pixel(lp,1,"dcdy",LICE_RGBA(0x80,0x80,0x80,0x80)); + double dcdxsc = getoption_double(lp,1,"dcdxscale",1.0); + double dcdysc = getoption_double(lp,1,"dcdyscale",1.0); + + // todo: any options? + int x = (int)parsecoord(lp->gettoken_str(1),xscale,true); + int y = (int)parsecoord(lp->gettoken_str(2),yscale,true); + int w = (int)parsecoord(lp->gettoken_str(3),xscale,true); + int h = (int)parsecoord(lp->gettoken_str(4),yscale,true); + if (w>0 && h>0) + { + if (dcdx!=LICE_RGBA(0x80,0x80,0x80,0x80) || + dcdy!=LICE_RGBA(0x80,0x80,0x80,0x80)) + { + LICE_pixel sc = state->m_color; + dcdxsc /= w*128.0; + dcdysc /= h*128.0; + LICE_GradRect(bm,x,y,w,h, + (float)(LICE_GETR(sc)/255.0), + (float)(LICE_GETG(sc)/255.0), + (float)(LICE_GETB(sc)/255.0), + (float)(LICE_GETA(sc)/255.0*state->m_alpha), + (float)(((int)LICE_GETR(dcdx)-0x80)*dcdxsc), + (float)(((int)LICE_GETG(dcdx)-0x80)*dcdxsc), + (float)(((int)LICE_GETB(dcdx)-0x80)*dcdxsc), + (float)(((int)LICE_GETA(dcdx)-0x80)*dcdxsc), + (float)(((int)LICE_GETR(dcdy)-0x80)*dcdysc), + (float)(((int)LICE_GETG(dcdy)-0x80)*dcdysc), + (float)(((int)LICE_GETB(dcdy)-0x80)*dcdysc), + (float)(((int)LICE_GETA(dcdy)-0x80)*dcdysc), + state->m_blend); + } + else + LICE_FillRect(bm,x,y,w,h,state->m_color,state->m_alpha,state->m_blend); + } + } + } + else if (!stricmp(tok,"rerender")) + { + if (numtok>=2) + { + int forcew=getoption_int(lp,1,"w",0),forceh=getoption_int(lp,1,"h",0); + bool useState=getoption_bool(lp,1,"usestate",false); + + lvgImageCtx *scan = this; + while (scan) + { + lvgImageCtx *p = scan->m_images.Get(lp->gettoken_str(1)); + if (p) + { + if (!p->m_in_render) + { + p->m_in_render=true; + p->render(useState ? state : NULL,forcew,forceh); + p->m_in_render=false; + } + break; + } + scan=scan->m_par; + } + } + } + else if (!stricmp(tok,"blit")) + { + if (numtok>=3) // blit image x y [options] + { + LICE_IBitmap *src=NULL; + lvgImageCtx *scan = this; + const char *rd = lp->gettoken_str(1); + + while (!strnicmp(rd,"parent:",7)) { scan = scan ? scan->m_par : NULL; rd += 7; } + + if (!stricmp(rd,"parent")) + { + if (scan) scan=scan->m_par; + if (scan) src=scan->m_cachedImage; + } + else if (!stricmp(rd,"self")) + { + if (scan) src=scan->m_cachedImage; + } + else while (scan&&!src) + { + lvgImageCtx *p = scan->m_images.Get(rd); + if (p) + { + if (!p->m_cachedImage && !p->m_in_render) + { + p->m_in_render=true; + p->render(NULL,0,0); + p->m_in_render=false; + } + src = p->m_cachedImage; + break; + } + scan=scan->m_par; + } + if (src) + { + int x = (int)parsecoord(lp->gettoken_str(2),xscale,true); + int y = (int)parsecoord(lp->gettoken_str(3),yscale,true); + + // these will be options filter= srcalpha= w= h= scale= + bool filter=getoption_bool(lp,1,"filter",true); + bool usesrcalpha = getoption_bool(lp,1,"srcalpha",true); + int w = getoption_int(lp,1,"w",src->getWidth()); + int h = getoption_int(lp,1,"h",src->getHeight()); + double sc = getoption_double(lp,1,"scale",1.0f); + if (fabs(sc-1.0)>0.0000000001) + { + w = (int)(w*sc+0.5); + h = (int)(h*sc+0.5); + } +// double ang = getoption_double(lp,1,"rotate",0.0) * PI / 180.0; + float sx=(float)getoption_double(lp,1,"srcx",0.0); + float sy=(float)getoption_double(lp,1,"srcy",0.0); + float sw=(float)getoption_double(lp,1,"srcw",src->getWidth()); + float sh=(float)getoption_double(lp,1,"srch",src->getHeight()); +// if (fabs(ang)>0.0001) LICE_RotatedBlit(bm,src,x,y,w,h,sx,sy,sw,sh,ang,true,state->m_alpha,state->m_blend,0,0); + //else + LICE_ScaledBlit(bm,src,x,y,w,h,sx,sy,sw,sh, + state->m_alpha,state->m_blend|(filter ? LICE_BLIT_FILTER_BILINEAR : 0)|(usesrcalpha ? LICE_BLIT_USE_ALPHA : 0)); + } + } + } +} + +void lvgImageCtx::render(lvgRenderState *rstate, int wantw, int wanth) +{ + if (wantw<1) wantw = m_base_w; + if (wanth<1) wanth = m_base_h; + + if (wantw<1||wanth<1) + { + if (m_cachedImage) m_cachedImage->resize(0,0); + return; + } + + if (!m_cachedImage) m_cachedImage = new LICE_MemBitmap(wantw,wanth); + else m_cachedImage->resize(wantw,wanth); + + LICE_Clear(m_cachedImage,LICE_RGBA(0,0,0,0)); + + lvgRenderState rs; + if (rstate) rs = *rstate; + + double xscale = wantw / lice_max(m_base_w,1); + double yscale = wanth / lice_max(m_base_h,1); + int x; + bool comment_state=false; + LineParser lp(comment_state); + for (x=0;x0) + processLvgLine(&lp,&rs,m_cachedImage,xscale,yscale); + } +} + +void *LICE_GetSubLVG(void *lvg, const char *subname) +{ + if (!lvg||!subname||!*subname) return NULL; + lvgImageCtx *t = (lvgImageCtx *)lvg; + return t->m_images.Get(subname); +} + +LICE_IBitmap *LICE_RenderLVG(void *lvg, int reqw, int reqh, LICE_IBitmap *bmOut) +{ + lvgImageCtx *t = (lvgImageCtx *)lvg; + if (!t || !t->m_lines.GetSize() || t->m_in_render) return NULL; + + if (bmOut) + { + delete t->m_cachedImage; + t->m_cachedImage = bmOut; + } + else if (!t->m_cachedImage) t->m_cachedImage = new LICE_MemBitmap; + + t->m_in_render=true; + t->render(NULL,reqw,reqh); + t->m_in_render=false; + + LICE_IBitmap *ret = t->m_cachedImage; + + t->m_cachedImage=NULL; + + return ret; +} + +void LICE_DestroyLVG(void *lvg) +{ + lvgImageCtx *t = (lvgImageCtx *)lvg; + if (t && !t->m_par) delete t; +} + +class lvgRdContext : public ProjectStateContext +{ +public: + lvgRdContext(FILE *fp) { m_fp=fp; } + virtual ~lvgRdContext() { } + + virtual void AddLine(const char *fmt, ...) {}; + virtual int GetLine(char *buf, int buflen) // returns -1 on eof + { + if (!m_fp) return -1; + for (;;) + { + buf[0]=0; + fgets(buf,buflen,m_fp); + if (!buf[0]) return -1; + + char *p=buf; + while (*p) p++; + while (p>buf && (p[-1] == '\r' || p[-1]=='\n')) p--; + *p=0; + if (*buf) return 0; + } + } + + virtual WDL_INT64 GetOutputSize() { return 0; } + virtual int GetTempFlag() { return 0; } + virtual void SetTempFlag(int flag) {} + + FILE *m_fp; +}; + +void *LICE_LoadLVGFromContext(ProjectStateContext *ctx, const char *nameInfo, int defw, int defh) +{ + if (!ctx) return NULL; + + bool err=false; + int ignoreBlockCnt=0; + + lvgImageCtx *retimg = new lvgImageCtx(NULL); + lvgImageCtx *curimg = NULL; + + if (nameInfo) + { + curimg = retimg; + curimg->m_base_w = defw; + curimg->m_base_h = defh; + } + + while (!err) + { + char line[4096]; + line[0]=0; + if (ctx->GetLine(line,sizeof(line))) break; + + char *p=line; + while (*p == ' ' || *p == '\t') p++; + if (!*p) continue; + + if (ignoreBlockCnt>0) + { + if (*p == '<') ignoreBlockCnt++; + else if (*p == '>') ignoreBlockCnt--; + } + else + { + if (*p == '<') + { + bool comment_state=false; + LineParser lp(comment_state); + if (!lp.parse(p)&&lp.getnumtokens()>=2 && !strcmp(lp.gettoken_str(0),"m_images.Insert(lp.gettoken_str(1),img); + curimg = img; + } + curimg->m_base_w = lp.gettoken_int(2); + curimg->m_base_h = lp.gettoken_int(3); + } + else ignoreBlockCnt++; + } + else if (curimg) + { + if (*p == '>') + { + curimg = curimg->m_par; + if (!curimg) break; // success! + } + else + { + curimg->m_lines.Add(strdup(p)); + } + } + + if (!curimg) err=true; // 24-bit RGB) + +struct ONode +{ + WDL_INT64 colorcount; // number of color instances at or below this node + WDL_INT64 sumrgb[3]; + int childflag; // 0=leaf, >0=index of single child, <0=branch + int leafidx; // populated at the end + ONode* next; // for OTree::branches + ONode* children[8]; +}; + +struct OTree +{ + int maxcolors; + int leafcount; + ONode* trunk; + ONode* branches[OCTREE_DEPTH]; // linked lists of branches for each level of the tree + ONode* spares; + LICE_pixel* palette; // populated at the end + bool palette_valid; +}; + + + +int LICE_BuildPalette(LICE_IBitmap* bmp, LICE_pixel* palette, int maxcolors) +{ + void* tree = LICE_CreateOctree(maxcolors); + LICE_BuildOctree(tree, bmp); + int sz = LICE_ExtractOctreePalette(tree, palette); + LICE_DestroyOctree(tree); + return sz; +} + + +static void AddColorToTree(OTree*, const LICE_pixel_chan *rgb); +static int FindColorInTree(OTree*, const LICE_pixel_chan *rgb); +static int PruneTree(OTree*); +static void DeleteNode(OTree*, ONode*, ONode **delete_to); +static int CollectLeaves(OTree*); +static int CollectNodeLeaves(ONode* node, LICE_pixel* palette, int colorcount); + + +void* LICE_CreateOctree(int maxcolors) +{ + OTree* tree = new OTree; + memset(tree, 0, sizeof(OTree)); + tree->maxcolors = maxcolors; + tree->trunk = new ONode; + memset(tree->trunk, 0, sizeof(ONode)); + tree->spares = NULL; + return tree; +} + + +void LICE_ResetOctree(void *octree, int maxc) +{ + OTree* tree = (OTree*)octree; + if (!tree) return; + + if (maxc > tree->maxcolors) + { + free(tree->palette); + tree->palette=0; + } + + DeleteNode(tree, tree->trunk, &tree->spares); + tree->leafcount = 0; + tree->maxcolors = maxc; + tree->palette_valid=false; + memset(tree->branches,0,sizeof(tree->branches)); + + tree->trunk=tree->spares; + if (tree->trunk) tree->spares = tree->trunk->next; + else tree->trunk = new ONode; + + memset(tree->trunk, 0, sizeof(ONode)); +} + +void LICE_DestroyOctree(void* octree) +{ + OTree* tree = (OTree*)octree; + if (!tree) return; + + DeleteNode(tree, tree->trunk, NULL); + + ONode *p = tree->spares; + while (p) + { + ONode *del = p; + p = p->next; + + delete del; + } + free(tree->palette); + delete tree; +} + + +int LICE_BuildOctree(void* octree, LICE_IBitmap* bmp) +{ + OTree* tree = (OTree*)octree; + if (!tree || !bmp) return 0; + + tree->palette_valid = false; + + int y; + const int h=bmp->getHeight(); + const int w=bmp->getWidth(); + const int rowspan = bmp->getRowSpan(); + const LICE_pixel *bits = bmp->getBits(); + for (y = 0; y < h; ++y) + { + const LICE_pixel *px = bits+y*rowspan; + int x=w; + while (x--) + { + AddColorToTree(tree, (const LICE_pixel_chan*)px); + if (tree->leafcount > tree->maxcolors) PruneTree(tree); + px++; + } + } + + return tree->leafcount; +} + +int LICE_BuildOctreeForAlpha(void* octree, LICE_IBitmap* bmp, unsigned int minalpha) +{ + OTree* tree = (OTree*)octree; + if (!tree || !bmp) return 0; + + tree->palette_valid = false; + + int y; + const int h=bmp->getHeight(); + const int w=bmp->getWidth(); + const int rowspan = bmp->getRowSpan(); + const LICE_pixel *bits = bmp->getBits(); + int pxcnt=0; + for (y = 0; y < h; ++y) + { + const LICE_pixel *px = bits+y*rowspan; + int x=w; + while (x--) + { + if (px[LICE_PIXEL_A] >= minalpha) + { + AddColorToTree(tree, (const LICE_pixel_chan*)px); + if (tree->leafcount > tree->maxcolors) PruneTree(tree); + pxcnt++; + } + px++; + } + } + + return pxcnt; +} + + +int LICE_BuildOctreeForDiff(void* octree, LICE_IBitmap* bmp, LICE_IBitmap* refbmp, LICE_pixel mask) +{ + OTree* tree = (OTree*)octree; + if (!tree || !bmp || !refbmp) return 0; + + tree->palette_valid=false; + + int y; + const int h=lice_min(bmp->getHeight(),refbmp->getHeight()); + const int w=lice_min(bmp->getWidth(),refbmp->getWidth()); + + int rowspan = bmp->getRowSpan(); + int rowspan2 = refbmp->getRowSpan(); + const LICE_pixel *bits = bmp->getBits(); + const LICE_pixel *bits2 = refbmp->getBits(); + + if (bmp->isFlipped()) + { + bits += rowspan * (bmp->getHeight()-1); + rowspan = -rowspan; + } + + if (refbmp->isFlipped()) + { + bits2 += rowspan2 * (refbmp->getHeight()-1); + rowspan2 = -rowspan2; + } + + int pxcnt=0; + for (y = 0; y < h; ++y) + { + const LICE_pixel * px = bits+y*rowspan; + const LICE_pixel * px2 = bits2+y*rowspan2; + int x=w; + while (x--) + { + if ((*px ^ *px2) & mask) + { + AddColorToTree(tree, (const LICE_pixel_chan *)px); + if (tree->leafcount > tree->maxcolors) PruneTree(tree); + pxcnt++; + } + px++; + px2++; + } + } + + return pxcnt; +} + + +int LICE_FindInOctree(void* octree, LICE_pixel color) +{ + OTree* tree = (OTree*)octree; + if (!tree) return 0; + + if (!tree->palette_valid) CollectLeaves(tree); + + return FindColorInTree(tree, (const LICE_pixel_chan *)&color); +} + + +int LICE_ExtractOctreePalette(void* octree, LICE_pixel* palette) +{ + OTree* tree = (OTree*)octree; + if (!tree || !palette) return 0; + + if (!tree->palette_valid) CollectLeaves(tree); + + if (tree->palette) memcpy(palette, tree->palette, tree->maxcolors*sizeof(LICE_pixel)); + + return tree->leafcount; +} + + + +void LICE_TestPalette(LICE_IBitmap* bmp, LICE_pixel* palette, int numcolors) +{ + int x, y; + for (y = 0; y < bmp->getHeight(); ++y) + { + LICE_pixel* px = bmp->getBits()+y*bmp->getRowSpan(); + for (x = 0; x < bmp->getWidth(); ++x) + { + const LICE_pixel col = px[x]; + const int rgb[3] = { (int)LICE_GETR(col), (int)LICE_GETG(col), (int)LICE_GETB(col) }; + + int minerr = 0x7FFFFFFF; + int bestcol=-1; + int i; + for (i = 0; i < numcolors; ++i) + { + const LICE_pixel palcol = palette[i]; + const int rerr[3] = { rgb[0]-(int)LICE_GETR(palcol), rgb[1]-(int)LICE_GETG(palcol), rgb[2]-(int)LICE_GETB(palcol) }; + const int err = rerr[0]*rerr[0]+rerr[1]*rerr[1]+rerr[2]*rerr[2]; + if (err < minerr) + { + bestcol=i; + minerr=err; + } + } + px[x] = palette[bestcol]; + } + } +} + + +void AddColorToTree(OTree* tree, const LICE_pixel_chan *rgb) +{ + ONode* p = tree->trunk; + p->colorcount++; + + int i; + const unsigned char r = rgb[LICE_PIXEL_R]; + const unsigned char g = rgb[LICE_PIXEL_G]; + const unsigned char b = rgb[LICE_PIXEL_B]; + for (i = OCTREE_DEPTH-1; i >= 0; --i) + { + const int j = i+8-OCTREE_DEPTH; + const unsigned char idx = (((r>>(j-2))&4))|(((g>>(j-1))&2))|((b>>j)&1); + + ONode* np = p->children[idx]; + bool isleaf = false; + + if (np) + { + isleaf = !np->childflag; + } + else // add node + { + if (!p->childflag) // first time down this path + { + p->childflag=idx+1; + } + else if (p->childflag > 0) // creating a new branch + { + p->childflag = -1; + p->next = tree->branches[i]; + tree->branches[i] = p; + } + // else multiple branch, which we don't care about + + np=tree->spares; + if (np) tree->spares = np->next; + else np = new ONode; + + p->children[idx] = np; + memset(np, 0, sizeof(ONode)); + } + + np->sumrgb[0] += r; + np->sumrgb[1] += g; + np->sumrgb[2] += b; + np->colorcount++; + + if (isleaf) return; + + p=np; // continue downward + } + + // p is a new leaf at the bottom + tree->leafcount++; +} + +int FindColorInTree(OTree* tree, const LICE_pixel_chan *rgb) +{ + ONode* p = tree->trunk; + + int i; + const unsigned char r=rgb[LICE_PIXEL_R]; + const unsigned char g=rgb[LICE_PIXEL_G]; + const unsigned char b=rgb[LICE_PIXEL_B]; + for (i = OCTREE_DEPTH-1; i >= 0; --i) + { + if (!p->childflag) break; + + const int j = i+8-OCTREE_DEPTH; + const unsigned char idx = (((r>>(j-2))&4))|(((g>>(j-1))&2))|((b>>j)&1); + + ONode* np = p->children[idx]; + if (!np) break; + + p = np; + } + + return p->leafidx; +} + + +int PruneTree(OTree* tree) +{ + ONode* branch=0; + int i; + for (i = 0; i < OCTREE_DEPTH; ++i) // prune at the furthest level from the trunk + { + branch = tree->branches[i]; + if (branch) + { + tree->branches[i] = branch->next; + branch->next=0; + break; + } + } + + if (branch) + { + for (i = 0; i < 8; ++i) + { + if (branch->children[i]) + { + DeleteNode(tree, branch->children[i],&tree->spares); + branch->children[i]=0; + } + } + branch->childflag=0; // now it's a leaf + tree->leafcount++; + } + + return tree->leafcount; +} + +int CollectLeaves(OTree* tree) +{ + if (!tree->palette) tree->palette = (LICE_pixel*)malloc(tree->maxcolors*sizeof(LICE_pixel)); + + if (!tree->palette) return 0; + + int sz = CollectNodeLeaves(tree->trunk, tree->palette, 0); + memset(tree->palette+sz, 0, (tree->maxcolors-sz)*sizeof(LICE_pixel)); + tree->palette_valid = true; + + return sz; +} + +int CollectNodeLeaves(ONode* p, LICE_pixel* palette, int colorcount) +{ + if (!p->childflag) + { + p->leafidx = colorcount; + int r = (int)((double)p->sumrgb[0]/(double)p->colorcount); + int g = (int)((double)p->sumrgb[1]/(double)p->colorcount); + int b = (int)((double)p->sumrgb[2]/(double)p->colorcount); + palette[colorcount++] = LICE_RGBA(r, g, b, 255); + } + else + { + if (p->childflag > 0) + { + colorcount = CollectNodeLeaves(p->children[p->childflag-1], palette, colorcount); + } + else + { + int i; + for (i = 0; i < 8; ++i) + { + if (p->children[i]) + { + colorcount = CollectNodeLeaves(p->children[i], palette, colorcount); + } + } + } + // this is a branch or passthrough node, record the index + // of any downtree leaf here so that we can return it for + // color lookups that want to diverge off this node + p->leafidx = colorcount-1; + } + + // colorcount should == leafcount + return colorcount; +} + + +void DeleteNode(OTree* tree, ONode* p, ONode **delete_to) +{ + if (!p->childflag) + { + tree->leafcount--; + } + else if (p->childflag > 0) + { + DeleteNode(tree, p->children[p->childflag-1],delete_to); + } + else + { + int i; + for (i = 0; i < 8; ++i) + { + if (p->children[i]) + { + DeleteNode(tree, p->children[i],delete_to); + } + } + } + + if (delete_to) + { + p->next = *delete_to; + *delete_to = p; + } + else + { + delete p; + } +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_pcx.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_pcx.cpp new file mode 100644 index 0000000..6b9b1fc --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_pcx.cpp @@ -0,0 +1,118 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_pcx.cpp (PCX loading for LICE) + See lice.h for license and other information +*/ + +#include "lice.h" +#include "../wdltypes.h" + +#include + +// note: you'd never really want to use PCX files, but in case you do... + +LICE_IBitmap *LICE_LoadPCX(const char *filename, LICE_IBitmap *_bmp) +{ + FILE *fp = WDL_fopenA(filename,"rb"); + if(!fp) return 0; + + fgetc(fp); + if (fgetc(fp) != 5) { fclose(fp); return NULL; } + if (fgetc(fp) != 1) { fclose(fp); return NULL; } + if (fgetc(fp) != 8) { fclose(fp); return NULL; } + + int sx = fgetc(fp); sx += fgetc(fp)<<8; + int sy = fgetc(fp); sy += fgetc(fp)<<8; + int ex = fgetc(fp); ex += fgetc(fp)<<8; + int ey = fgetc(fp); ey += fgetc(fp)<<8; + + + unsigned char pal[768]; + fseek(fp,-769,SEEK_END); + if (fgetc(fp) != 12) { fclose(fp); return NULL; } + if (fread(pal,1,768,fp) != 768 || feof(fp)) + { + fclose(fp); + return NULL; + } + + + LICE_IBitmap *usebmp = NULL; + if (_bmp) (usebmp=_bmp)->resize(ex-sx+1,ey-sy+1); + else usebmp = new WDL_NEW LICE_MemBitmap(ex-sx+1,ey-sy+1); + if (!usebmp || usebmp->getWidth() != (ex-sx+1) || usebmp->getHeight() != (ey-sy+1)) + { + if (usebmp != _bmp) delete usebmp; + fclose(fp); + return NULL; + } + + fseek(fp,128,SEEK_SET); + + LICE_Clear(usebmp,0); + int y = usebmp->getHeight(); + int w = usebmp->getWidth(); + int rowspan = usebmp->getRowSpan(); + LICE_pixel *pout = usebmp->getBits(); + if (usebmp->isFlipped()) + { + pout += rowspan*(y-1); + rowspan=-rowspan; + } + while (y--) + { + int xpos = 0; + while (xpos < w) + { + int c = fgetc(fp); + if (c&~255) break; + if ((c & 192) == 192) + { + int oc = (fgetc(fp))&255; + LICE_pixel t=LICE_RGBA(pal[oc*3],pal[oc*3+1],pal[oc*3+2],255); + + c&=63; + while (c-- && xposfilename && *p != '\\' && *p != '/' && *p != '.') p--; + if (stricmp(p,".pcx")) return 0; + } + return LICE_LoadPCX(filename,bmpbase); + } + static const char *get_extlist() + { + return "PCX files (*.PCX)\0*.PCX\0"; + } + +}; + +LICE_PCXLoader LICE_pcxldr; diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_png.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_png.cpp new file mode 100644 index 0000000..d2f5656 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_png.cpp @@ -0,0 +1,375 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_png.cpp (PNG loading for LICE) + See lice.h for license and other information +*/ + +#include "lice.h" + +#include "../wdltypes.h" + +#include +#include "../libpng/png.h" + +#ifdef __APPLE__ +#include // for loading images from embedded resource +#endif + + +LICE_IBitmap *LICE_LoadPNG(const char *filename, LICE_IBitmap *bmp) +{ + FILE *fp = NULL; +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + #ifdef WDL_SUPPORT_WIN9X + if (GetVersion()<0x80000000) + #endif + { + WCHAR wf[2048]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wf,2048)) + fp = _wfopen(wf,L"rb"); + } +#endif + + if (!fp) fp = WDL_fopenA(filename,"rb"); + if (!fp) return 0; + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png_ptr) + { + fclose(fp); + return 0; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) + { + png_destroy_read_struct(&png_ptr, NULL, NULL); + fclose(fp); + return 0; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 0; + } + + png_init_io(png_ptr, fp); + + png_read_info(png_ptr, info_ptr); + + unsigned int width, height; + int bit_depth, color_type, interlace_type, compression_type, filter_method; + png_get_IHDR(png_ptr, info_ptr, &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_method); + + //convert whatever it is to RGBA + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png_ptr); + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + { + png_set_tRNS_to_alpha(png_ptr); + color_type |= PNG_COLOR_MASK_ALPHA; + } + + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + if (bit_depth < 8) + png_set_packing(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + if (color_type & PNG_COLOR_MASK_ALPHA) + png_set_swap_alpha(png_ptr); + else + png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); + + LICE_IBitmap *delbmp = NULL; + + if (bmp) bmp->resize(width,height); + else delbmp = bmp = new WDL_NEW LICE_MemBitmap(width,height); + + if (!bmp || bmp->getWidth() != (int)width || bmp->getHeight() != (int)height) + { + delete delbmp; + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + fclose(fp); + return 0; + } + + unsigned char **row_pointers=(unsigned char **)malloc(height*sizeof(unsigned char *));; + LICE_pixel *srcptr = bmp->getBits(); + int dsrcptr=bmp->getRowSpan(); + if (bmp->isFlipped()) + { + srcptr += dsrcptr*(bmp->getHeight()-1); + dsrcptr=-dsrcptr; + } + unsigned int i; + for(i=0;i0) + { + unsigned char a = bp[0]; + unsigned char r = bp[1]; + unsigned char g = bp[2]; + unsigned char b = bp[3]; + ((LICE_pixel*)bp)[0] = LICE_RGBA(r,g,b,a); + bp+=4; + } + } + #endif + free(row_pointers); + + return bmp; +} + +typedef struct +{ + unsigned char *data; + int len; +} pngReadStruct; + +static void staticPngReadFunc(png_structp png_ptr, png_bytep data, png_size_t length) +{ + pngReadStruct *readStruct = (pngReadStruct *)png_get_io_ptr(png_ptr); + memset(data, 0, length); + + int l = (int)length; + if (l > readStruct->len) l = readStruct->len; + memcpy(data, readStruct->data, l); + readStruct->data += l; + readStruct->len -= l; +} + +#ifndef _WIN32 +LICE_IBitmap *LICE_LoadPNGFromNamedResource(const char *name, LICE_IBitmap *bmp) // returns a bitmap (bmp if nonzero) on success +{ + char buf[2048]; + buf[0]=0; + if (strlen(name)>400) return NULL; // max name for this is 400 chars + +#ifdef __APPLE__ + CFBundleRef bund = CFBundleGetMainBundle(); + if (bund) + { + CFURLRef url=CFBundleCopyBundleURL(bund); + if (url) + { + CFURLGetFileSystemRepresentation(url,true,(UInt8*)buf,sizeof(buf)-512); + CFRelease(url); + } + } + if (!buf[0]) return 0; + strcat(buf,"/Contents/Resources/"); +#else + int sz = readlink("/proc/self/exe", buf, sizeof(buf)-512); + if (sz < 1) + { + static char tmp; + // this will likely not work if the program was launched with a relative path + // and the cwd has changed, but give it a try anyway + Dl_info inf={0,}; + if (dladdr(&tmp,&inf) && inf.dli_fname) + sz = (int) strlen(inf.dli_fname); + else + sz = 0; + } + + if ((unsigned int)sz >= sizeof(buf)-512) sz = sizeof(buf)-512-1; + buf[sz]=0; + char *p = buf; + while (*p) p++; + while (p > buf && *p != '/') p--; + *p=0; + strcat(buf,"/Resources/"); +#endif // !__APPLE__ + + strcat(buf,name); + return LICE_LoadPNG(buf,bmp); +} +#endif + +LICE_IBitmap *LICE_LoadPNGFromMemory(const void *data_in, int buflen, LICE_IBitmap *bmp) +{ + if (buflen<8) return NULL; + unsigned char *data = (unsigned char *)(void*)data_in; + if(png_sig_cmp(data, 0, 8)) return NULL; + + pngReadStruct readStruct = {data, buflen}; + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png_ptr) + { + return 0; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) + { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return 0; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 0; + } + + png_set_read_fn(png_ptr, &readStruct, staticPngReadFunc); + + png_read_info(png_ptr, info_ptr); + + unsigned int width, height; + int bit_depth, color_type, interlace_type, compression_type, filter_method; + png_get_IHDR(png_ptr, info_ptr, &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_method); + + //convert whatever it is to RGBA + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png_ptr); + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + { + png_set_tRNS_to_alpha(png_ptr); + color_type |= PNG_COLOR_MASK_ALPHA; + } + + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + if (bit_depth < 8) + png_set_packing(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + if (color_type & PNG_COLOR_MASK_ALPHA) + png_set_swap_alpha(png_ptr); + else + png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); + + LICE_IBitmap *delbmp = NULL; + + if (bmp) bmp->resize(width,height); + else delbmp = bmp = new WDL_NEW LICE_MemBitmap(width,height); + if (!bmp || bmp->getWidth() != (int)width || bmp->getHeight() != (int)height) + { + delete delbmp; + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + return 0; + } + + unsigned char **row_pointers=(unsigned char **)malloc(height*sizeof(unsigned char *));; + LICE_pixel *srcptr = bmp->getBits(); + int dsrcptr=bmp->getRowSpan(); + unsigned int i; + for(i=0;i0) + { + unsigned char a = bp[0]; + unsigned char r = bp[1]; + unsigned char g = bp[2]; + unsigned char b = bp[3]; + ((LICE_pixel*)bp)[0] = LICE_RGBA(r,g,b,a); + bp+=4; + } + } + #endif + free(row_pointers); + return bmp; +} +LICE_IBitmap *LICE_LoadPNGFromResource(HINSTANCE hInst, const char *resid, LICE_IBitmap *bmp) +{ +#ifdef _WIN32 + HRSRC hResource = FindResource(hInst, resid, "PNG"); + if(!hResource) return NULL; + + DWORD imageSize = SizeofResource(hInst, hResource); + if(imageSize < 8) return NULL; + + HGLOBAL res = LoadResource(hInst, hResource); + const void* pResourceData = LockResource(res); + if(!pResourceData) return NULL; + + LICE_IBitmap * ret = LICE_LoadPNGFromMemory(pResourceData,imageSize,bmp); + + // todo : cleanup res?? + + return ret; +#else + return 0; +#endif +} + + +class LICE_PNGLoader +{ +public: + _LICE_ImageLoader_rec rec; + LICE_PNGLoader() + { + rec.loadfunc = loadfunc; + rec.get_extlist = get_extlist; + rec._next = LICE_ImageLoader_list; + LICE_ImageLoader_list = &rec; + } + + static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) + { + if (checkFileName) + { + const char *p=filename; + while (*p)p++; + while (p>filename && *p != '\\' && *p != '/' && *p != '.') p--; + if (stricmp(p,".png")) return 0; + } + return LICE_LoadPNG(filename,bmpbase); + } + static const char *get_extlist() + { + return "PNG files (*.PNG)\0*.PNG\0"; + } + +}; + +LICE_PNGLoader LICE_pngldr; diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_png_write.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_png_write.cpp new file mode 100644 index 0000000..29f6719 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_png_write.cpp @@ -0,0 +1,133 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: lice_png_write.cpp (PNG saving for LICE) + See lice.h for license and other information +*/ + +#include "lice.h" + + +#include +#include "../libpng/png.h" + + +bool LICE_WritePNG(const char *filename, LICE_IBitmap *bmp, bool wantalpha /*=true*/) +{ + if (!bmp || !filename) return false; + /* + ** Joshua Teitelbaum 1/1/2008 + ** Gifted to cockos for toe nail clippings. + ** + ** JF> tweaked some + */ + png_structp png_ptr=NULL; + png_infop info_ptr=NULL; + unsigned char *rowbuf=NULL; + + FILE *fp=NULL; +#if defined(_WIN32) && !defined(WDL_NO_SUPPORT_UTF8) + #ifdef WDL_SUPPORT_WIN9X + if (GetVersion()<0x80000000) + #endif + { + WCHAR wf[2048]; + if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,filename,-1,wf,2048)) + fp = _wfopen(wf,L"wb"); + } +#endif + if (!fp) fp = fopen(filename,"wb"); + + if (fp == NULL) return false; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL, NULL, NULL); + + if (png_ptr == NULL) { + fclose(fp); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + fclose(fp); + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + /* If we get here, we had a problem reading the file */ + if (fp) fclose(fp); + fp=0; + free(rowbuf); + rowbuf=0; + png_destroy_write_struct(&png_ptr, &info_ptr); + return false; + } + + + png_init_io(png_ptr, fp); + int width=bmp->getWidth(); + int height = bmp->getHeight(); + +#define BITDEPTH 8 + png_set_IHDR(png_ptr, info_ptr, width, height, BITDEPTH, wantalpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(png_ptr, info_ptr); + + png_set_bgr(png_ptr); + + // kill alpha channel bytes if not wanted + if (!wantalpha) png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + + LICE_pixel *ptr=(LICE_pixel *)bmp->getBits(); + int rowspan=bmp->getRowSpan(); + if (bmp->isFlipped()) + { + ptr+=rowspan*(bmp->getHeight()-1); + rowspan=-rowspan; + } + + + if (LICE_PIXEL_B != 0 || LICE_PIXEL_G != 1 || LICE_PIXEL_R != 2 || LICE_PIXEL_A != 3) + { + rowbuf=(unsigned char *)malloc(width*4); + int k; + for (k = 0; k < height; k++) + { + int x; + unsigned char *bout = rowbuf; + LICE_pixel_chan *bin = (LICE_pixel_chan *) ptr; + for(x=0;x + +void LICE_TexGen_Marble(LICE_IBitmap *dest, const RECT *rect, float rv, float gv, float bv, float intensity) +{ + int span=dest->getRowSpan(); + int w = dest->getWidth(); + int h = dest->getHeight(); + int x = 0; + int y = 0; + if(rect) + { + x = rect->left; + y = rect->top; + w = rect->right - rect->left; + h = rect->bottom - rect->top; + } + + if (x<0) { w+=x; x=0; } + if (y<0) { h+=y; y=0; } + + const int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + if (w<1 || h < 1 || x >= destbm_w || y >= destbm_h) return; + + if (w>destbm_w-x) w=destbm_w-x; + if (h>destbm_h-y) h=destbm_h-y; + + + LICE_pixel *startp = dest->getBits(); + if (dest->isFlipped()) + { + startp += x + (dest->getHeight()-1-y)*span; + span=-span; + } + else startp += x + y*span; + + //simple 16bit marble noise generator + +#define ROL(x,y) ((x<<(y))|(((unsigned short)x)>>(16-(y)))) +#define ROR(x,y) ((((unsigned short)x)>>(y))|(x<<(16-(y)))) + + intensity/=1024.0f; + int maxc = 0; + { + LICE_pixel *p = startp; + short n1 = 0, n2 = 0; + for(int i=0;i0) + { + c = p[j-span]; + if(j==0) + c2 = p[(w-1)-span]; + else + c2 = p[(j-1)-span]; + } + + int pix = (((c + c2)/2) + val); + if(pix>maxc) maxc = pix; + p[j] = pix; + } + p+=span; + } + } + + //normalize values and apply gamma + { + LICE_pixel *p = startp; + float sc=255.0f/maxc; + + for(int i=0;igetRowSpan(); + int w = dest->getWidth(); + int h = dest->getHeight(); + int dx = 0; + int dy = 0; + if(rect) + { + dx = rect->left; + dy = rect->top; + w = rect->right - rect->left; + h = rect->bottom - rect->top; + } + + if (dx<0) { w+=dx; dx=0; } + if (dy<0) { h+=dy; dy=0; } + const int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + if (w<1 || h < 1 || dx >= destbm_w || dy >= destbm_h) return; + + if (w>destbm_w-dx) w=destbm_w-dx; + if (h>destbm_h-dy) h=destbm_h-dy; + + LICE_pixel *startp = dest->getBits(); + if (dest->isFlipped()) + { + startp += dx + (dest->getHeight()-1-dy)*span; + span=-span; + } + else startp += dx + dy*span; + + { + LICE_pixel *p = startp; + for(int i=0;i=1) + { + switch(mode) + { + case NOISE_MODE_NORMAL: val += noise(x/size, y/size)*size; break; + case NOISE_MODE_WOOD: val += (float)cos( x/size + noise(x/size,y/size) )*size/2; break; + } + size /= 2; + } + float col = (float)fabs(val/smooth)*255; + if(col>255) col=255; + + p[j] = LICE_RGBA((int)(col*rv),(int)(col*gv),(int)(col*bv),255); + } + p+=span; + } + } +} + +static float turbulence(int x, int y, float size, float isize) +{ + float value = 0.0; + const float initialSize = isize; + while(size >= 1) + { + value += noise(x * isize, y * isize) * size; + size *= 0.5f; + isize *= 2.0f; + } + return(128.0f * value * initialSize); +} + +void LICE_TexGen_CircNoise(LICE_IBitmap *dest, const RECT *rect, float rv, float gv, float bv, float nrings, float power, int size) +{ + initNoise(); + + int span=dest->getRowSpan(); + int w = dest->getWidth(); + int h = dest->getHeight(); + int x = 0; + int y = 0; + if(rect) + { + x = rect->left; + y = rect->top; + w = rect->right - rect->left; + h = rect->bottom - rect->top; + } + + if (x<0) { w+=x; x=0; } + if (y<0) { h+=y; y=0; } + const int destbm_w = dest->getWidth(), destbm_h = dest->getHeight(); + if (w<1 || h < 1 || x >= destbm_w || y >= destbm_h) return; + + if (w>destbm_w-x) w=destbm_w-x; + if (h>destbm_h-y) h=destbm_h-y; + + LICE_pixel *startp = dest->getBits(); + if (dest->isFlipped()) + { + startp += x + (dest->getHeight()-1-y)*span; + span=-span; + } + else startp += x + y*span; + + float xyPeriod = nrings; + float turbPower = power; + const float iturbSize = 1.0f/(float)size; + const float turbSize = (float)size; + + { + LICE_pixel *p = startp; + for(int i=0;i' */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x00, /* 00000000 */ + + /* 63 0x3f '?' */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + + /* 64 0x40 '@' */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xde, /* 11011110 */ + 0xde, /* 11011110 */ + 0xde, /* 11011110 */ + 0xc0, /* 11000000 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + + /* 65 0x41 'A' */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + + /* 66 0x42 'B' */ + 0xfc, /* 11111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0xfc, /* 11111100 */ + 0x00, /* 00000000 */ + + /* 67 0x43 'C' */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 68 0x44 'D' */ + 0xf8, /* 11111000 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0xf8, /* 11111000 */ + 0x00, /* 00000000 */ + + /* 69 0x45 'E' */ + 0xfe, /* 11111110 */ + 0x62, /* 01100010 */ + 0x68, /* 01101000 */ + 0x78, /* 01111000 */ + 0x68, /* 01101000 */ + 0x62, /* 01100010 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + + /* 70 0x46 'F' */ + 0xfe, /* 11111110 */ + 0x62, /* 01100010 */ + 0x68, /* 01101000 */ + 0x78, /* 01111000 */ + 0x68, /* 01101000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + + /* 71 0x47 'G' */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xce, /* 11001110 */ + 0x66, /* 01100110 */ + 0x3a, /* 00111010 */ + 0x00, /* 00000000 */ + + /* 72 0x48 'H' */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + + /* 73 0x49 'I' */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 74 0x4a 'J' */ + 0x1e, /* 00011110 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + + /* 75 0x4b 'K' */ + 0xe6, /* 11100110 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0x78, /* 01111000 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + + /* 76 0x4c 'L' */ + 0xf0, /* 11110000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x62, /* 01100010 */ + 0x66, /* 01100110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + + /* 77 0x4d 'M' */ + 0xc6, /* 11000110 */ + 0xee, /* 11101110 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0xd6, /* 11010110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + + /* 78 0x4e 'N' */ + 0xc6, /* 11000110 */ + 0xe6, /* 11100110 */ + 0xf6, /* 11110110 */ + 0xde, /* 11011110 */ + 0xce, /* 11001110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + + /* 79 0x4f 'O' */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + + /* 80 0x50 'P' */ + 0xfc, /* 11111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + + /* 81 0x51 'Q' */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xce, /* 11001110 */ + 0x7c, /* 01111100 */ + 0x0e, /* 00001110 */ + + /* 82 0x52 'R' */ + 0xfc, /* 11111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + + /* 83 0x53 'S' */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 84 0x54 'T' */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x5a, /* 01011010 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 85 0x55 'U' */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + + /* 86 0x56 'V' */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + + /* 87 0x57 'W' */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xfe, /* 11111110 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + + /* 88 0x58 'X' */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + + /* 89 0x59 'Y' */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 90 0x5a 'Z' */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0x8c, /* 10001100 */ + 0x18, /* 00011000 */ + 0x32, /* 00110010 */ + 0x66, /* 01100110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + + /* 91 0x5b '[' */ + 0x3c, /* 00111100 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 92 0x5c '\' */ + 0xc0, /* 11000000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x06, /* 00000110 */ + 0x02, /* 00000010 */ + 0x00, /* 00000000 */ + + /* 93 0x5d ']' */ + 0x3c, /* 00111100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 94 0x5e '^' */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 95 0x5f '_' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + + /* 96 0x60 '`' */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 97 0x61 'a' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + + /* 98 0x62 'b' */ + 0xe0, /* 11100000 */ + 0x60, /* 01100000 */ + 0x7c, /* 01111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0xdc, /* 11011100 */ + 0x00, /* 00000000 */ + + /* 99 0x63 'c' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + + /* 100 0x64 'd' */ + 0x1c, /* 00011100 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + + /* 101 0x65 'e' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + + /* 102 0x66 'f' */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x60, /* 01100000 */ + 0xf8, /* 11111000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + + /* 103 0x67 'g' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x7c, /* 01111100 */ + 0x0c, /* 00001100 */ + 0xf8, /* 11111000 */ + + /* 104 0x68 'h' */ + 0xe0, /* 11100000 */ + 0x60, /* 01100000 */ + 0x6c, /* 01101100 */ + 0x76, /* 01110110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + + /* 105 0x69 'i' */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 106 0x6a 'j' */ + 0x06, /* 00000110 */ + 0x00, /* 00000000 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + + /* 107 0x6b 'k' */ + 0xe0, /* 11100000 */ + 0x60, /* 01100000 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0x78, /* 01111000 */ + 0x6c, /* 01101100 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + + /* 108 0x6c 'l' */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 109 0x6d 'm' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xec, /* 11101100 */ + 0xfe, /* 11111110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0x00, /* 00000000 */ + + /* 110 0x6e 'n' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xdc, /* 11011100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + + /* 111 0x6f 'o' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + + /* 112 0x70 'p' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xdc, /* 11011100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + + /* 113 0x71 'q' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x7c, /* 01111100 */ + 0x0c, /* 00001100 */ + 0x1e, /* 00011110 */ + + /* 114 0x72 'r' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xdc, /* 11011100 */ + 0x76, /* 01110110 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + + /* 115 0x73 's' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0xc0, /* 11000000 */ + 0x7c, /* 01111100 */ + 0x06, /* 00000110 */ + 0xfc, /* 11111100 */ + 0x00, /* 00000000 */ + + /* 116 0x74 't' */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0xfc, /* 11111100 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x36, /* 00110110 */ + 0x1c, /* 00011100 */ + 0x00, /* 00000000 */ + + /* 117 0x75 'u' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + + /* 118 0x76 'v' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + + /* 119 0x77 'w' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xfe, /* 11111110 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + + /* 120 0x78 'x' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + + /* 121 0x79 'y' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7e, /* 01111110 */ + 0x06, /* 00000110 */ + 0xfc, /* 11111100 */ + + /* 122 0x7a 'z' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x4c, /* 01001100 */ + 0x18, /* 00011000 */ + 0x32, /* 00110010 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + + /* 123 0x7b '{' */ + 0x0e, /* 00001110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x70, /* 01110000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x0e, /* 00001110 */ + 0x00, /* 00000000 */ + + /* 124 0x7c '|' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + + /* 125 0x7d '}' */ + 0x70, /* 01110000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x0e, /* 00001110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x70, /* 01110000 */ + 0x00, /* 00000000 */ + + /* 126 0x7e '~' */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + + /* 127 0x7f '' */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ +}; + + +void LICE_DrawChar(LICE_IBitmap *bm, int x, int y, char c, + LICE_pixel color, float alpha, int mode) +{ + LICE_pixel *fb; + if (c<1 || !bm || !(fb=bm->getBits()))return; + unsigned char *font = LICE_deffont + ((c-1)*LICE_FONT_HEIGHT); + int len = LICE_FONT_HEIGHT; + int smask=128; + int xlen=8; + + if (y < 0) + { + font -= y; + len += y; + y = 0; + } + if (x<0) + { + smask >>= -x; + xlen+=x; + x=0; + } + + const int bm_w = bm->getWidth(), bm_h = bm->getHeight(); + if (xlen < 1 || len < 1 || x >= bm_w || y >= bm_h) return; + + if (xlen > bm_w - x) xlen = bm_w - x; + if (len > bm_h - y) len = bm_h - y; + + int rs=bm->getRowSpan(); + if (bm->isFlipped()) + { + fb += x+((bm->getHeight()-1-y)*rs); + rs=-rs; + } + else + fb += x+(y*rs); + + int red=LICE_GETR(color), green=LICE_GETG(color), blue=LICE_GETB(color), alp=LICE_GETA(color), ialpha=(int) (alpha * 256.0f); + + while (len-->0) + { + LICE_pixel *outmem = fb; + fb+=rs; + unsigned char ch = *font++; + int a=smask; + int xleft = xlen; + while (a && xleft--) + { + if (ch & a) + { + #define __LICE__ACTION(comb) comb::doPix((LICE_pixel_chan *)outmem, red,green,blue,alp,ialpha) + __LICE_ACTION_NOSRCALPHA(mode,ialpha, false); + #undef __LICE__ACTION + } + outmem++; + a >>= 1; + } + } +} + +void LICE_DrawText(LICE_IBitmap *bm, int x, int y, const char *string, + LICE_pixel color, float alpha, int mode) +{ + if (!bm) return; + int w=bm->getWidth(); + int h=bm->getHeight(); + int xx = x; + while (*string) + { + switch (*string) + { + case '\n': y += LICE_FONT_HEIGHT; xx = x; break; + case ' ': xx += 8; break; + case '\r': break; + case '\t': xx += 8*5; break; + default: + if (xx>=-8 && xx= -LICE_FONT_HEIGHT && y < h) + LICE_DrawChar(bm,xx,y,*string, color,alpha,mode); + xx += 8; + break; + } + string++; + } +} + +void LICE_MeasureText(const char *string, int *w, int *h) +{ + if (w) *w=0; + if (h) *h=0; + int x=0,y=LICE_FONT_HEIGHT; + while (*string) + { + switch (*string) + { + case '\n': y += LICE_FONT_HEIGHT; x = 0; break; + case '\r': break; + case '\t': + x += 8*4; + WDL_FALLTHROUGH; + default: + x += 8; + if (w && x > *w) *w=x; + if (h && y > *h) *h=y; + break; + } + string++; + } +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_text.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_text.h new file mode 100644 index 0000000..bda2dca --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_text.h @@ -0,0 +1,274 @@ +#ifndef _LICE_TEXT_H_ +#define _LICE_TEXT_H_ + +#include "lice.h" + +#ifndef _WIN32 +#include "../swell/swell.h" +#endif +#include "../heapbuf.h" + +#define LICE_FONT_FLAG_VERTICAL 1 // rotate text to vertical (do not set the windows font to vertical though) +#define LICE_FONT_FLAG_VERTICAL_BOTTOMUP 2 + +#define LICE_FONT_FLAG_PRECALCALL 4 +//#define LICE_FONT_FLAG_ALLOW_NATIVE 8 +#define LICE_FONT_FLAG_FORCE_NATIVE 1024 + +#define LICE_FONT_FLAG_FX_BLUR 16 +#define LICE_FONT_FLAG_FX_INVERT 32 +#define LICE_FONT_FLAG_FX_MONO 64 // faster but no AA/etc + +#define LICE_FONT_FLAG_FX_SHADOW 128 // these imply MONO +#define LICE_FONT_FLAG_FX_OUTLINE 256 + +#define LICE_FONT_FLAG_OWNS_HFONT 512 + +// could do a mask for these flags +#define LICE_FONT_FLAGS_HAS_FX(flag) \ + (flag&(LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP| \ + LICE_FONT_FLAG_FX_BLUR|LICE_FONT_FLAG_FX_INVERT|LICE_FONT_FLAG_FX_MONO| \ + LICE_FONT_FLAG_FX_SHADOW|LICE_FONT_FLAG_FX_OUTLINE)) + +#define LICE_DT_NEEDALPHA 0x80000000 // include in DrawText() if the output alpha channel is important +#define LICE_DT_USEFGALPHA 0x40000000 // uses alpha channel in fg color + +class LICE_IFont +{ + public: + virtual ~LICE_IFont() {} + + virtual void SetFromHFont(HFONT font, int flags=0)=0; // hfont must REMAIN valid, unless LICE_FONT_FLAG_PRECALCALL or LICE_FONT_FLAG_OWNS_HFONT set (OWNS means LICE_IFont will clean up hfont on font change or exit) + + virtual LICE_pixel SetTextColor(LICE_pixel color)=0; + virtual LICE_pixel SetBkColor(LICE_pixel color)=0; + virtual LICE_pixel SetEffectColor(LICE_pixel color)=0; + virtual int SetBkMode(int bkmode)=0; + virtual void SetCombineMode(int combine, float alpha=1.0f)=0; + + virtual int DrawText(LICE_IBitmap *bm, const char *str, int strcnt, RECT *rect, UINT dtFlags)=0; + + virtual LICE_pixel GetTextColor()=0; + virtual HFONT GetHFont()=0; + virtual int GetLineHeight()=0; + virtual void SetLineSpacingAdjust(int amt)=0; +}; + + +#ifndef LICE_TEXT_NO_DECLARE_CACHEDFONT + +class LICE_CachedFont : public LICE_IFont +{ + public: + LICE_CachedFont(); + virtual ~LICE_CachedFont(); + + virtual void SetFromHFont(HFONT font, int flags=0); + + virtual LICE_pixel SetTextColor(LICE_pixel color) { LICE_pixel ret=m_fg; m_fg=color; return ret; } + virtual LICE_pixel SetBkColor(LICE_pixel color) { LICE_pixel ret=m_bg; m_bg=color; return ret; } + virtual LICE_pixel SetEffectColor(LICE_pixel color) { LICE_pixel ret=m_effectcol; m_effectcol=color; return ret; } + virtual int SetBkMode(int bkmode) { int bk = m_bgmode; m_bgmode=bkmode; return bk; } + virtual void SetCombineMode(int combine, float alpha=1.0f) { m_comb=combine; m_alpha=alpha; } + + virtual int DrawText(LICE_IBitmap *bm, const char *str, int strcnt, RECT *rect, UINT dtFlags) + { + return DrawTextImpl(bm,str,strcnt,rect,dtFlags); + } + + virtual LICE_pixel GetTextColor() { return m_fg; } + virtual HFONT GetHFont() { return m_font; } + virtual int GetLineHeight() { return m_line_height; } + + virtual void SetLineSpacingAdjust(int amt) { m_lsadj=amt; } + + protected: + + virtual bool DrawGlyph(LICE_IBitmap *bm, unsigned short c, int xpos, int ypos, const RECT *clipR); + int DrawTextImpl(LICE_IBitmap *bm, const char *str, int strcnt, RECT *rect, UINT dtFlags); // cause swell defines DrawText to SWELL_DrawText etc + + bool RenderGlyph(unsigned short idx); + + const char *NextWordBreak(const char *str, int strcnt, int w); + + LICE_pixel m_fg,m_bg,m_effectcol; + int m_bgmode; + int m_comb; + float m_alpha; + int m_flags; + + int m_line_height,m_lsadj; + struct charEnt + { + int base_offset; // offset in m_cachestore+1, so 1=offset0, 0=unset, -1=failed to render + int width, height; + int advance; + int charid; // used by m_extracharlist + int left_extra; + }; + charEnt *findChar(unsigned short c); + + charEnt m_lowchars[128]; // first 128 chars cached here + WDL_TypedBuf m_extracharlist; + WDL_TypedBuf m_cachestore; + + static int _charSortFunc(const void *a, const void *b); + + HFONT m_font; + +}; + +#endif // !LICE_TEXT_NO_DECLARE_CACHEDFONT + +#ifndef LICE_TEXT_NO_MULTIDPI +class __LICE_dpiAwareFont : public LICE_IFont +{ + struct rec { + LICE_IFont *cache; + int sz; + }; + WDL_TypedBuf m_list; // used entries are at end of list, most recently used last. sz=0 for unused + + int (*m_getflags)(int); + int m_flags; + LICE_pixel m_fg, m_bg, m_effectcol; + int m_bgmode, m_comb; + float m_alpha; + int m_lsadj; + +public: + LOGFONT m_lf; + + + // LICE_IFont interface + virtual void SetFromHFont(HFONT font, int flags=0) { } + + virtual LICE_pixel SetTextColor(LICE_pixel color) { LICE_pixel ret=m_fg; m_fg=color; return ret; } + virtual LICE_pixel SetBkColor(LICE_pixel color) { LICE_pixel ret=m_bg; m_bg=color; return ret; } + virtual LICE_pixel SetEffectColor(LICE_pixel color) { LICE_pixel ret=m_effectcol; m_effectcol=color; return ret; } + virtual int SetBkMode(int bkmode) { int bk = m_bgmode; m_bgmode=bkmode; return bk; } + virtual void SetCombineMode(int combine, float alpha=1.0f) { m_comb=combine; m_alpha=alpha; } + + virtual int DrawText(LICE_IBitmap *bm, const char *str, int strcnt, RECT *rect, UINT dtFlags) + { + LICE_IFont *f = get(bm); + if (!f) return 0; + if (!(dtFlags & DT_CALCRECT)) + { + f->SetTextColor(m_fg); + f->SetBkColor(m_bg); + f->SetEffectColor(m_effectcol); + f->SetBkMode(m_bgmode); + f->SetCombineMode(m_comb,m_alpha); + f->SetLineSpacingAdjust(m_lsadj); + } + return f->DrawText(bm,str,strcnt,rect,dtFlags); + } + + virtual LICE_pixel GetTextColor() { return m_fg; } + virtual HFONT GetHFont() { return NULL; } + virtual int GetLineHeight() { return GetLineHeightDPI(NULL); } + + virtual void SetLineSpacingAdjust(int amt) { m_lsadj=amt; } + + __LICE_dpiAwareFont(int maxsz) + { + memset(&m_lf,0,sizeof(m_lf)); + m_getflags = NULL; + m_flags=0; + m_fg=m_bg=m_effectcol=0; + m_bgmode=TRANSPARENT; + m_comb=0; + m_alpha=1.0; + m_lsadj=0; + + rec *l = m_list.ResizeOK(maxsz); + if (l) memset(l,0,sizeof(*l)*maxsz); + } + ~__LICE_dpiAwareFont() + { + for (int x = 0; x < m_list.GetSize(); x ++) delete m_list.Get()[x].cache; + } + void SetFromLogFont(LOGFONT *lf, int (*get_flags)(int)) + { + m_lf = *lf; + m_getflags = get_flags; + m_flags = get_flags ? get_flags(0) : 0; + clear(); + } + + void clear() + { + int x = m_list.GetSize()-1; + rec *t = m_list.Get(); + while (x>=0 && t[x].sz) t[x--].sz=0; + } + + LICE_IFont *get_for_sc(int fsc) + { + int use_flag = m_getflags ? m_getflags(0) & ~LICE_FONT_FLAG_PRECALCALL : 0; + if (m_flags != use_flag) + { + m_flags = use_flag; + clear(); + } + + int ht = m_lf.lfHeight, ht2 = m_lf.lfWidth; + if (fsc && fsc != 256) + { + ht = (ht * fsc) / 256; + ht2 = (ht2 * fsc) / 256; + use_flag |= LICE_FONT_FLAG_FORCE_NATIVE; + } + + int x = m_list.GetSize()-1; + rec *t = m_list.Get(); + while (x>=0 && t[x].sz != ht && t[x].sz) x--; + if (x<0) t[x=0].sz = 0; // if list full, use oldest item + + // move to end of list + if (x != m_list.GetSize()-1) + { + rec tmp = t[x]; + m_list.Delete(x); + m_list.Add(tmp); + } + + t = m_list.Get() + m_list.GetSize() - 1; + if (!t->cache) t->cache = __CreateFont(); + if (!t->sz && t->cache) + { + t->sz = ht; + LOGFONT lf = m_lf; + lf.lfHeight = ht; + lf.lfWidth = ht2; + #ifdef _WIN32 + if (!(m_flags & LICE_FONT_FLAG_FORCE_NATIVE) && abs(lf.lfHeight) <= 14) lf.lfQuality = NONANTIALIASED_QUALITY; + #endif + t->cache->SetFromHFont(CreateFontIndirect(&lf), LICE_FONT_FLAG_OWNS_HFONT | use_flag); + } + + return t->cache; + } + LICE_IFont *get(LICE_IBitmap *bm) + { + return get_for_sc(bm ? (int)bm->Extended(LICE_EXT_GET_ANY_SCALING,NULL) : 0); + } + + + int GetLineHeightDPI(LICE_IBitmap *bm) + { + LICE_IFont *f = get(bm); + return f ? f->GetLineHeight() : 10; + } + virtual LICE_IFont *__CreateFont()=0; +}; + +template class LICE_dpiAwareFont : public __LICE_dpiAwareFont { + public: + LICE_dpiAwareFont(int max) : __LICE_dpiAwareFont(max) { } + virtual LICE_IFont *__CreateFont() { return new BASEFONT; } +}; +#endif//LICE_TEXT_NO_MULTIDPI + +#endif//_LICE_TEXT_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_textnew.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_textnew.cpp new file mode 100644 index 0000000..2547cb9 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/lice_textnew.cpp @@ -0,0 +1,1356 @@ +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "lice_text.h" +#include + +#include "lice_combine.h" +#include "lice_extended.h" + +#define IGNORE_SCALING(mode) ((mode)&LICE_BLIT_IGNORE_SCALING) + +#if defined(_WIN32) && defined(WDL_SUPPORT_WIN9X) +static char __1ifNT2if98=0; // 2 for iswin98 +#endif + +// for very large fonts, fallback to native and avoid making a huge glyph cache, which would be terrible for performance +#define USE_NATIVE_RENDERING_FOR_FONTS_HIGHER_THAN 256 + + +// if other methods fail, at this point just flat out refuse to render glyphs (since they would use a ridiculous amount of memory) +#ifndef ABSOLUTELY_NO_GLYPHS_HIGHER_THAN +#define ABSOLUTELY_NO_GLYPHS_HIGHER_THAN 1024 +#endif + + +static int utf8makechar(char *ptrout, unsigned short charIn) +{ + unsigned char *pout = (unsigned char *)ptrout; + if (charIn < 128) { *pout = (unsigned char)charIn; return 1; } + if (charIn < 2048) { pout[0] = 0xC0 + (charIn>>6); pout[1] = 0x80 + (charIn&0x3f); return 2; } + pout[0] = 0xE0 + (charIn>>12); + pout[1] = 0x80 + ((charIn>>6)&0x3f); + pout[2] = 0x80 + (charIn&0x3f); + return 3; +} + +static int utf8char(const char *ptr, unsigned short *charOut) // returns char length +{ + const unsigned char *p = (const unsigned char *)ptr; + unsigned char tc = *p; + + if (tc < 128) + { + if (charOut) *charOut = (unsigned short) tc; + return 1; + } + else if (tc < 0xC2) // invalid chars (subsequent in sequence, or overlong which we disable for) + { + } + else if (tc < 0xE0) // 2 char seq + { + if (p[1] >= 0x80 && p[1] <= 0xC0) + { + if (charOut) *charOut = ((tc&0x1f)<<6) | (p[1]&0x3f); + return 2; + } + } + else if (tc < 0xF0) // 3 char seq + { + if (p[1] >= 0x80 && p[1] <= 0xC0 && p[2] >= 0x80 && p[2] <= 0xC0) + { + if (charOut) *charOut = ((tc&0xf)<<12) | ((p[1]&0x3f)<<6) | ((p[2]&0x3f)); + return 3; + } + } + else if (tc < 0xF5) // 4 char seq + { + if (p[1] >= 0x80 && p[1] <= 0xC0 && p[2] >= 0x80 && p[2] <= 0xC0 && p[3] >= 0x80 && p[3] <= 0xC0) + { + if (charOut) *charOut = (unsigned short)' '; // dont support 4 byte sequences yet(ever?) + return 4; + } + } + if (charOut) *charOut = (unsigned short) tc; + return 1; +} + + + +//not threadsafe ---- +static LICE_SysBitmap *s_tempbitmap; // keep a sysbitmap around for rendering fonts +static LICE_SysBitmap *s_nativerender_tempbitmap; +static int s_tempbitmap_refcnt; + + +int LICE_CachedFont::_charSortFunc(const void *a, const void *b) +{ + charEnt *aa = (charEnt *)a; + charEnt *bb = (charEnt *)b; + return aa->charid - bb->charid; +} + +LICE_CachedFont::LICE_CachedFont() : m_cachestore(65536) +{ + s_tempbitmap_refcnt++; + m_fg=0; + m_effectcol=m_bg=LICE_RGBA(255,255,255,255); + m_comb=0; + m_alpha=1.0f; + m_bgmode = TRANSPARENT; + m_flags=0; + m_line_height=0; + m_lsadj=0; + m_font=0; + memset(m_lowchars,0,sizeof(m_lowchars)); +} + +LICE_CachedFont::~LICE_CachedFont() +{ + if ((m_flags&LICE_FONT_FLAG_OWNS_HFONT) && m_font) { + DeleteObject(m_font); + } + if (!--s_tempbitmap_refcnt) + { + delete s_tempbitmap; + s_tempbitmap=0; + delete s_nativerender_tempbitmap; + s_nativerender_tempbitmap=0; + } +} + +void LICE_CachedFont::SetFromHFont(HFONT font, int flags) +{ + if ((m_flags&LICE_FONT_FLAG_OWNS_HFONT) && m_font && m_font != font) + { + DeleteObject(m_font); + } + + m_flags=flags; + m_font=font; + if (font) + { + if (!s_tempbitmap) s_tempbitmap=new LICE_SysBitmap; + + if (s_tempbitmap->getWidth() < 256 || s_tempbitmap->getHeight() < 256) + { + s_tempbitmap->resize(256,256); + ::SetTextColor(s_tempbitmap->getDC(),RGB(255,255,255)); + ::SetBkMode(s_tempbitmap->getDC(),OPAQUE); + ::SetBkColor(s_tempbitmap->getDC(),RGB(0,0,0)); + } + + TEXTMETRIC tm; + HGDIOBJ oldFont = 0; + if (font) oldFont = SelectObject(s_tempbitmap->getDC(),font); + GetTextMetrics(s_tempbitmap->getDC(),&tm); + if (oldFont) SelectObject(s_tempbitmap->getDC(),oldFont); + + m_line_height = tm.tmHeight; + } + + memset(m_lowchars,0,sizeof(m_lowchars)); + m_extracharlist.Resize(0,false); + m_cachestore.Resize(0); + if (flags&LICE_FONT_FLAG_PRECALCALL) + { + int x; + for(x=0;x<128;x++) + RenderGlyph(x); + } +} + +bool LICE_CachedFont::RenderGlyph(unsigned short idx) // return TRUE if ok +{ + if (m_line_height >= ABSOLUTELY_NO_GLYPHS_HIGHER_THAN) return false; + + bool needSort=false; + charEnt *ent; + if (idx>=128) + { +#if defined(_WIN32) && defined(WDL_SUPPORT_WIN9X) + if (!__1ifNT2if98) __1ifNT2if98 = GetVersion()<0x80000000 ? 1 : 2; + + if (__1ifNT2if98==2) return false; +#endif + ent=findChar(idx); + if (!ent) + { + if (m_flags & LICE_FONT_FLAG_PRECALCALL) return false; + + int oldsz=m_extracharlist.GetSize(); + ent = m_extracharlist.Resize(oldsz+1) + oldsz; + memset(ent,0,sizeof(*ent)); + ent->charid = idx; + + needSort=true; + } + } + else ent = m_lowchars+idx; + + const int bmsz=lice_max(m_line_height,1) * 2 + 8; + + if (!s_tempbitmap) s_tempbitmap=new LICE_SysBitmap; + + if (s_tempbitmap->getWidth() < bmsz || s_tempbitmap->getHeight() < bmsz) + { + s_tempbitmap->resize(bmsz,bmsz); + ::SetTextColor(s_tempbitmap->getDC(),RGB(255,255,255)); + ::SetBkMode(s_tempbitmap->getDC(),OPAQUE); + ::SetBkColor(s_tempbitmap->getDC(),RGB(0,0,0)); + } + + HGDIOBJ oldFont=0; + if (m_font) oldFont = SelectObject(s_tempbitmap->getDC(),m_font); + RECT r={0,0,0,0,}; + int advance; + // overrender sides and check to see if it was updated + const int right_extra_pad = 2+wdl_max(m_line_height/8,0); + const int left_extra_pad = 2+wdl_max(m_line_height/16,0); + +#ifdef _WIN32 +#if defined(WDL_SUPPORT_WIN9X) + if (__1ifNT2if98==1) +#endif + { + WCHAR tmpstr[2]={(WCHAR)idx,0}; + ::DrawTextW(s_tempbitmap->getDC(),tmpstr,1,&r,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX); + advance=r.right; + r.right += right_extra_pad+left_extra_pad; + LICE_FillRect(s_tempbitmap,0,0,r.right,r.bottom,0,1.0f,LICE_BLIT_MODE_COPY); + r.left+=left_extra_pad; + ::DrawTextW(s_tempbitmap->getDC(),tmpstr,1,&r,DT_SINGLELINE|DT_LEFT|DT_TOP|DT_NOPREFIX|DT_NOCLIP); + } + #if defined(WDL_SUPPORT_WIN9X) + else + #endif +#endif + +#if !defined(_WIN32) || defined(WDL_SUPPORT_WIN9X) + { + + char tmpstr[6]={(char)idx,0}; +#ifndef _WIN32 + if (idx>=128) utf8makechar(tmpstr,idx); +#endif + ::DrawText(s_tempbitmap->getDC(),tmpstr,-1,&r,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX); + advance=r.right; + r.right += right_extra_pad+left_extra_pad; + LICE_FillRect(s_tempbitmap,0,0,r.right,r.bottom,0,1.0f,LICE_BLIT_MODE_COPY); + r.left += left_extra_pad; + ::DrawText(s_tempbitmap->getDC(),tmpstr,-1,&r,DT_SINGLELINE|DT_LEFT|DT_TOP|DT_NOPREFIX|DT_NOCLIP); + } +#endif + + if (advance > s_tempbitmap->getWidth()) advance=s_tempbitmap->getWidth(); + if (r.right > s_tempbitmap->getWidth()) r.right=s_tempbitmap->getWidth(); + if (r.bottom > s_tempbitmap->getHeight()) r.bottom=s_tempbitmap->getHeight(); + + if (oldFont) SelectObject(s_tempbitmap->getDC(),oldFont); + + if (advance < 1 || r.bottom < 1) + { + ent->base_offset=-1; + ent->left_extra=ent->advance=ent->width=ent->height=0; + } + else + { + ent->advance=advance; + int flags=m_flags; + if (flags&LICE_FONT_FLAG_FX_BLUR) + { + LICE_Blur(s_tempbitmap,s_tempbitmap,0,0,0,0,r.right,r.bottom); + } + LICE_pixel *srcbuf = s_tempbitmap->getBits(); + int span=s_tempbitmap->getRowSpan(); + + ent->base_offset=m_cachestore.GetSize()+1; + int newsz = ent->base_offset-1+r.right*r.bottom; + unsigned char *destbuf = m_cachestore.Resize(newsz) + ent->base_offset-1; + if (m_cachestore.GetSize() != newsz) + { + ent->base_offset=-1; + ent->advance=ent->width=ent->height=0; + } + else + { + if (s_tempbitmap->isFlipped()) + { + srcbuf += (s_tempbitmap->getHeight()-1)*span; + span=-span; + } + int x,y; + int min_x=left_extra_pad; + int max_x=advance + min_x; + + for(y=0;y 0) + { + const int neww = max_x-min_x; + const unsigned char *rdptr=destbuf + min_x; + // trim down destbuf + for (y=0;ybase_offset-1+r.right*r.bottom; + destbuf = m_cachestore.Resize(newsz,false) + ent->base_offset-1; + } + + if (flags&LICE_FONT_FLAG_VERTICAL) + { + int a=r.bottom; r.bottom=r.right; r.right=a; + + unsigned char *tmpbuf = (unsigned char *)s_tempbitmap->getBits(); + memcpy(tmpbuf,destbuf,r.right*r.bottom); + + int dptr=r.bottom; + int dtmpbuf=1; + if (!(flags&LICE_FONT_FLAG_VERTICAL_BOTTOMUP)) + { + tmpbuf += (r.right-1)*dptr; + dptr=-dptr; + } + else + { + tmpbuf+=r.bottom-1; + dtmpbuf=-1; + } + + for(y=0;y130 ? 255:0; + destbuf++; + } + + destbuf -= r.right*r.bottom; + } + if (flags&(LICE_FONT_FLAG_FX_SHADOW|LICE_FONT_FLAG_FX_OUTLINE)) + { + for(y=0;y130 ? 255:0; + destbuf++; + } + + destbuf -= r.right*r.bottom; + if (flags&LICE_FONT_FLAG_FX_SHADOW) + { + for(y=0;y0) + { + if (destbuf[-1]==255) *destbuf=128; + else if (y>0 && destbuf[-r.right-1]==255) *destbuf=128; + } + if (y>0 && destbuf[-r.right]==255) *destbuf=128; + } + destbuf++; + } + } + else + { + for(y=0;y0 && destbuf[-r.right]==255) *destbuf=128; + else if (y0) + { + if (destbuf[-1]==255) *destbuf=128; + // else if (y>0 && destbuf[-r.right-1]==255) *destbuf=128; + // else if (y0 && destbuf[-r.right+1]==255) *destbuf=128; + // else if (yleft_extra=left_extra_pad-min_x; + ent->width = r.right; + ent->height = r.bottom; + } + } + if (needSort&&m_extracharlist.GetSize()>1) qsort(m_extracharlist.Get(),m_extracharlist.GetSize(),sizeof(charEnt),_charSortFunc); + + return true; +} + +template class GlyphRenderer +{ +public: + static void Normal(unsigned char *gsrc, LICE_pixel *pout, + int src_span, int dest_span, int width, int height, + int red, int green, int blue, int pxa, int a256) + { + int y; + if (a256==256) + { + for(y=0;y256)a=256; + T::doPix((unsigned char *)(pout+x),red,green,blue,pxa,a); + } + } + gsrc += src_span; + pout += dest_span; + } + } + } + static void Mono(unsigned char *gsrc, LICE_pixel *pout, + int src_span, int dest_span, int width, int height, + int red, int green, int blue, int pxa, int alpha) + { + int y; + for(y=0;yleft_extra; + else + ypos += ch->left_extra; + } + else + { + xpos -= ch->left_extra; + } + + if (xpos >= clipR->right || + ypos >= clipR->bottom || + xpos+ch->width <= clipR->left || + ypos+ch->height <= clipR->top) return true; // would have drawn but out of bounds + + unsigned char *gsrc = m_cachestore.Get() + ch->base_offset-1; + int src_span = ch->width; + int width = ch->width; + int height = ch->height; + +#ifndef DISABLE_LICE_EXTENSIONS + if (bm->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_DRAWGLYPH_ACCEL)) + { + LICE_Ext_DrawGlyph_acceldata data(xpos, ypos, m_fg, gsrc, width, height, m_alpha, m_comb); + if (bm->Extended(LICE_EXT_DRAWGLYPH_ACCEL, &data)) return true; + } +#endif + + if (xpos < clipR->left) + { + width += (xpos-clipR->left); + gsrc += clipR->left-xpos; + xpos=clipR->left; + } + if (ypos < clipR->top) + { + gsrc += src_span*(clipR->top-ypos); + height += (ypos-clipR->top); + ypos=clipR->top; + } + int dest_span = bm->getRowSpan(); + LICE_pixel *pout = bm->getBits(); + + if (bm->isFlipped()) + { + int bm_h = bm->getHeight(); + const int __sc = bm ? (int)bm->Extended(LICE_EXT_GET_SCALING,NULL) : 0; + if (__sc>0) + { + __LICE_SCU(bm_h); + } + + pout += (bm_h-1)*dest_span; + dest_span=-dest_span; + } + + pout += xpos + ypos * dest_span; + + if (width >= clipR->right-xpos) width = clipR->right-xpos; + if (height >= clipR->bottom-ypos) height = clipR->bottom-ypos; + + if (width < 1 || height < 1) return false; // this could be an assert, it is guaranteed by the xpos/ypos checks at the top of this function + + int mode=m_comb&~LICE_BLIT_USE_ALPHA; + float alpha=m_alpha; + + if (m_bgmode==OPAQUE) + LICE_FillRect(bm,xpos,ypos,width,height,m_bg,alpha,mode|LICE_BLIT_IGNORE_SCALING); + + int red=LICE_GETR(m_fg); + int green=LICE_GETG(m_fg); + int blue=LICE_GETB(m_fg); + int pxa=LICE_GETA(m_fg); + + if (m_flags&LICE_FONT_FLAG_FX_MONO) + { + if (alpha==1.0 && (mode&LICE_BLIT_MODE_MASK)==LICE_BLIT_MODE_COPY) // fast simple + { + LICE_pixel col=m_fg; + int y; + for(y=0;y256)avalint=256; + + #define __LICE__ACTION(comb) GlyphRenderer::Mono(gsrc,pout,src_span,dest_span,width,height,red,green,blue,pxa,avalint) + __LICE_ACTION_NOSRCALPHA(mode,avalint, false); + #undef __LICE__ACTION + } + } + else if (m_flags&(LICE_FONT_FLAG_FX_SHADOW|LICE_FONT_FLAG_FX_OUTLINE)) + { + LICE_pixel col=m_fg; + LICE_pixel bkcol=m_effectcol; + + if (alpha==1.0 && (mode&LICE_BLIT_MODE_MASK)==LICE_BLIT_MODE_COPY) + { + int y; + for(y=0;y256)avalint=256; + int r2=LICE_GETR(bkcol); + int g2=LICE_GETG(bkcol); + int b2=LICE_GETB(bkcol); + int pxa2=LICE_GETA(bkcol); + #define __LICE__ACTION(comb) GlyphRenderer::Effect(gsrc,pout,src_span,dest_span,width,height,red,green,blue,pxa,avalint,r2,g2,b2,pxa2) + __LICE_ACTION_NOSRCALPHA(mode,avalint, false); + #undef __LICE__ACTION + } + } + else + { + int avalint = (int) (alpha*256.0); + #define __LICE__ACTION(comb) GlyphRenderer::Normal(gsrc,pout,src_span,dest_span,width,height,red,green,blue,pxa,avalint) + __LICE_ACTION_NOSRCALPHA(mode,avalint, false); + #undef __LICE__ACTION + } + + return true; // drew glyph at all (for updating max extents) +} + + +#ifndef LICE_TEXT_NONATIVE +static int LICE_Text_IsWine() +{ + static int isWine=-1; +#ifdef _WIN32 + if (isWine<0) + { + HKEY hk; + if (RegOpenKey(HKEY_LOCAL_MACHINE,"Software\\Wine",&hk)==ERROR_SUCCESS) + { + isWine=1; + RegCloseKey(hk); + } + else isWine=0; + } +#endif + return isWine>0; +} +#endif + +#ifdef _WIN32 +static BOOL LICE_Text_HasUTF8(const char *_str) +{ + const unsigned char *str = (const unsigned char *)_str; + if (!str) return FALSE; + while (*str) + { + unsigned char c = *str; + if (c >= 0xC2) // fuck overlongs + { + if (c <= 0xDF && str[1] >=0x80 && str[1] <= 0xBF) return TRUE; + else if (c <= 0xEF && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + else if (c <= 0xF4 && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE; + } + str++; + } + return FALSE; +} +#endif + + +#define __LICE_SC_DRAWTEXT_RESTORE_RECT \ + if (__sc > 0 && rect) { \ + rect->left = (rect->left * 256) / __sc; \ + rect->top = (rect->top * 256) / __sc; \ + rect->right = (rect->right * 256) / __sc; \ + rect->bottom = (rect->bottom * 256) / __sc; \ + } + + +static const char *adv_str(const char *str, int *strcnt, unsigned short *c) +{ + int charlen=utf8char(str, c); + if (strcnt && *strcnt > 0) *strcnt=wdl_max(*strcnt-charlen, 0); + return str+charlen; +} + +const char *LICE_CachedFont::NextWordBreak(const char *str, int strcnt, int w) +{ + // returns the first character of the next line + const char *next_break=NULL; + while (*str && strcnt) + { + unsigned short c; + str=adv_str(str, &strcnt, &c); + if (c == '\n') return str; + if (c != '\r') + { + charEnt *ent=findChar(c); + if (ent && ent->base_offset > 0 && ent->base_offset < m_cachestore.GetSize()) + { + w -= ent->advance; + if (w < 0) return next_break ? next_break : str; + } + } + if (c == ' ' || c == '\t' || c == '\r') next_break=str; + } + return str; +} + + +int LICE_CachedFont::DrawTextImpl(LICE_IBitmap *bm, const char *str, int strcnt, + RECT *rect, UINT dtFlags) +{ + WDL_ASSERT((dtFlags & DT_SINGLELINE) || !(dtFlags & (DT_BOTTOM|DT_VCENTER))); // if DT_BOTTOM or DT_VCENTER used, must have DT_SINGLELINE + if (!bm && !(dtFlags&DT_CALCRECT)) return 0; + + const int __sc = bm ? (int)bm->Extended(LICE_EXT_GET_SCALING,NULL) : 0; + int bm_w = bm ? bm->getWidth() : 0; + int bm_h = bm ? bm->getHeight() : 0; + + if (__sc>0 && rect) + { + if (!IGNORE_SCALING(m_comb)) + { + __LICE_SC(rect->left); + __LICE_SC(rect->top); + __LICE_SC(rect->right); + __LICE_SC(rect->bottom); + } + __LICE_SC(bm_w); + __LICE_SC(bm_h); + } + + bool forceWantAlpha=false; + + if (dtFlags & LICE_DT_NEEDALPHA) + { + forceWantAlpha=true; + dtFlags &= ~LICE_DT_NEEDALPHA; + } + + if (dtFlags&DT_SINGLELINE) dtFlags &= ~DT_WORDBREAK; + +#ifndef _WIN32 + const int lsadj = m_lsadj+3; +#else + const int lsadj = m_lsadj; +#endif + + // if using line-spacing adjustments (m_lsadj), don't allow native rendering + // todo: split rendering up into invidual lines and DrawText calls +#ifndef LICE_TEXT_NONATIVE + + int ret=0; + if (!bm || !bm->Extended('YUVx',NULL)) if (((m_flags&LICE_FONT_FLAG_FORCE_NATIVE) && m_font && !forceWantAlpha &&!LICE_Text_IsWine() && +#ifndef _WIN32 + // swell does not support DT_WORDBREAK at the moment + !(dtFlags & DT_WORDBREAK) && +#endif + !(dtFlags & LICE_DT_USEFGALPHA) && + !(m_flags&LICE_FONT_FLAG_PRECALCALL) && !LICE_FONT_FLAGS_HAS_FX(m_flags) && + (!lsadj || (dtFlags&DT_SINGLELINE))) || + (m_line_height >= USE_NATIVE_RENDERING_FOR_FONTS_HIGHER_THAN) ) + { + +#ifdef _WIN32 + WCHAR wtmpbuf[1024]; + WCHAR *wtmp=NULL; + #ifdef WDL_SUPPORT_WIN9X + static int win9x; + if (!win9x) win9x = GetVersion() < 0x80000000 ? -1 : 1; //>0 if win9x + if (win9x<0 && LICE_Text_HasUTF8(str)) + #else + if (LICE_Text_HasUTF8(str)) + #endif + { + int req = MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,str,strcnt,NULL,0); + if (req < 1000) + { + int cnt=0; + if ((cnt=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,str,strcnt,wtmpbuf,1024))) + { + wtmp=wtmpbuf; + wtmp[cnt]=0; + } + } + else + { + wtmp = (WCHAR *)malloc((req + 32)*sizeof(WCHAR)); + int cnt=-1; + if (wtmp && !(cnt=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,str,strcnt,wtmp,req+1))) + free(wtmp); + else if (cnt>0) wtmp[cnt]=0; + } + } +#endif + + HDC hdc = (bm ? bm->getDC() : 0); + HGDIOBJ oldfont = 0; + RECT dt_rect={0,}; + + bool isTmp=false; + RECT tmp_rect={0,}; + +#ifdef _WIN32 + HRGN rgn=NULL; +#endif + RECT nr_subbitmap_clip = {0,}; + bool nr_subbitmap_clip_use=false; + if (!hdc && bm) + { + LICE_IBitmap *bmt = bm; + while (bmt->Extended(LICE_SubBitmap::LICE_GET_SUBBITMAP_VERSION,NULL) == (INT_PTR)LICE_SubBitmap::LICE_SUBBITMAP_VERSION) + { + LICE_SubBitmap *sb = (LICE_SubBitmap *)bmt; + int sub_x = sb->m_x, sub_y = sb->m_y; + if (__sc>0) + { + __LICE_SC(sub_x); + __LICE_SC(sub_y); + } + nr_subbitmap_clip.left += sub_x; + nr_subbitmap_clip.top += sub_y; + bmt = sb->m_parent; + if (!bmt) break; // ran out of parents + + hdc=bmt->getDC(); + if (hdc) + { + int sub_w = ((LICE_SubBitmap*)bm)->m_w, sub_h = ((LICE_SubBitmap*)bm)->m_h; + if (__sc>0) + { + __LICE_SC(sub_w); + __LICE_SC(sub_h); + } + nr_subbitmap_clip.right = nr_subbitmap_clip.left + sub_w; + nr_subbitmap_clip.bottom = nr_subbitmap_clip.top + sub_h; + nr_subbitmap_clip_use=!(dtFlags & DT_CALCRECT); + bm = bmt; + break; + } + } + } + + if (!hdc) + { + // use temp buffer -- we could optionally enable this if non-1 alpha was desired, though this only works for BLIT_MODE_COPY anyway (since it will end up compositing the whole rectangle, ugh) + isTmp=true; + + if (!s_nativerender_tempbitmap) + s_nativerender_tempbitmap = new LICE_SysBitmap; + + if (s_nativerender_tempbitmap->getWidth() < 4 || s_nativerender_tempbitmap->getHeight() < 4) s_nativerender_tempbitmap->resize(4,4); + + hdc = s_nativerender_tempbitmap->getDC(); + if (!hdc) goto finish_up_native_render; + + oldfont = SelectObject(hdc, m_font); + + RECT text_size = {0,0}; + ret = +#ifdef _WIN32 + wtmp ? ::DrawTextW(hdc,wtmp,-1,&text_size,(dtFlags&~(DT_CENTER|DT_VCENTER|DT_TOP|DT_BOTTOM|DT_LEFT|DT_RIGHT))|DT_CALCRECT|DT_NOPREFIX) : +#endif + ::DrawText(hdc,str,strcnt,&text_size,(dtFlags&~(DT_CENTER|DT_VCENTER|DT_TOP|DT_BOTTOM|DT_LEFT|DT_RIGHT))|DT_CALCRECT|DT_NOPREFIX); + if (dtFlags & DT_CALCRECT) + { + rect->right = rect->left + text_size.right - text_size.left; + rect->bottom = rect->top + text_size.bottom - text_size.top; + goto finish_up_native_render; + } + if (!bm) goto finish_up_native_render; + + if (dtFlags & DT_RIGHT) tmp_rect.left = rect->right - text_size.right; + else if (dtFlags & DT_CENTER) tmp_rect.left = (rect->right+rect->left- text_size.right)/2; + else tmp_rect.left = rect->left; + tmp_rect.right = tmp_rect.left + text_size.right; + + if (dtFlags & DT_BOTTOM) tmp_rect.top = rect->bottom - text_size.bottom; + else if (dtFlags & DT_VCENTER) tmp_rect.top = (rect->bottom+rect->top- text_size.bottom)/2; + else tmp_rect.top = rect->top; + tmp_rect.bottom = tmp_rect.top + text_size.bottom; + + // tmp_rect is the desired rect of drawing, now clip to bitmap (adjusting dt_rect.top/left if starting offscreen) + if (tmp_rect.right > bm_w) tmp_rect.right=bm_w; + if (tmp_rect.bottom > bm_h) tmp_rect.bottom=bm_h; + + int lclip = 0, tclip = 0; + // clip tmp_rect to rect if not DT_NOCLIP + if (!(dtFlags&DT_NOCLIP)) + { + if (tmp_rect.right > rect->right) tmp_rect.right=rect->right; + if (tmp_rect.bottom > rect->bottom) tmp_rect.bottom=rect->bottom; + if (rect->left > 0) lclip = rect->left; + if (rect->top > 0) tclip = rect->top; + } + + if (tmp_rect.left < lclip) + { + dt_rect.left = tmp_rect.left - lclip; + tmp_rect.left = lclip; + } + if (tmp_rect.top < tclip) + { + dt_rect.top = tmp_rect.top - tclip; + tmp_rect.top = tclip; + } + + if (tmp_rect.bottom <= tmp_rect.top || tmp_rect.right <= tmp_rect.left) goto finish_up_native_render; + + dtFlags &= ~(DT_BOTTOM|DT_RIGHT|DT_CENTER|DT_VCENTER); + + int left_overrender = 2+(m_line_height>=16 ? m_line_height/16 : 0); + if (left_overrender>tmp_rect.left) left_overrender=tmp_rect.left; + tmp_rect.left -= left_overrender; + + if (tmp_rect.right-tmp_rect.left > s_nativerender_tempbitmap->getWidth() || + tmp_rect.bottom-tmp_rect.top > s_nativerender_tempbitmap->getHeight()) + { + SelectObject(hdc,oldfont); + s_nativerender_tempbitmap->resize(tmp_rect.right-tmp_rect.left, tmp_rect.bottom-tmp_rect.top); + hdc = s_nativerender_tempbitmap->getDC(); + oldfont = SelectObject(hdc, m_font); + } + + LICE_Blit(s_nativerender_tempbitmap, bm, 0, 0, &tmp_rect, 1.0f, LICE_BLIT_MODE_COPY); + + dt_rect.left += left_overrender; + dt_rect.right=tmp_rect.right; + dt_rect.bottom=tmp_rect.bottom; + } + else + { + oldfont = SelectObject(hdc, m_font); + dt_rect = *rect; + } + + ::SetTextColor(hdc,RGB(LICE_GETR(m_fg),LICE_GETG(m_fg),LICE_GETB(m_fg))); + ::SetBkMode(hdc,m_bgmode); + if (m_bgmode==OPAQUE) ::SetBkColor(hdc,RGB(LICE_GETR(m_bg),LICE_GETG(m_bg),LICE_GETB(m_bg))); + + if (nr_subbitmap_clip_use) + { +#ifdef _WIN32 + rgn=CreateRectRgn(0,0,0,0); + if (!GetClipRgn(hdc,rgn)) + { + DeleteObject(rgn); + rgn=NULL; + } + IntersectClipRect(hdc,nr_subbitmap_clip.left,nr_subbitmap_clip.top,nr_subbitmap_clip.right,nr_subbitmap_clip.bottom); +#else + SWELL_PushClipRegion(hdc); + SWELL_SetClipRegion(hdc,&nr_subbitmap_clip); +#endif + dt_rect.left += nr_subbitmap_clip.left; + dt_rect.top += nr_subbitmap_clip.top; + dt_rect.right += nr_subbitmap_clip.left; + dt_rect.bottom += nr_subbitmap_clip.top; + } + + ret = +#ifdef _WIN32 + wtmp ? ::DrawTextW(hdc,wtmp,-1,&dt_rect,dtFlags|DT_NOPREFIX) : +#endif + ::DrawText(hdc,str,strcnt,&dt_rect,dtFlags|DT_NOPREFIX); + + if (nr_subbitmap_clip_use) + { +#ifdef _WIN32 + SelectClipRgn(hdc,rgn); // if rgn is NULL, clears region + if (rgn) DeleteObject(rgn); +#else + SWELL_PopClipRegion(hdc); +#endif + } + + if (isTmp) + LICE_Blit(bm, s_nativerender_tempbitmap, tmp_rect.left, tmp_rect.top, + 0,0, tmp_rect.right-tmp_rect.left, tmp_rect.bottom-tmp_rect.top, + m_alpha, // this is a slight improvement over the non-tempbuffer version, but might not be necessary... + LICE_BLIT_MODE_COPY); + else if (dtFlags & DT_CALCRECT) *rect = dt_rect; + +finish_up_native_render: + if (hdc) SelectObject(hdc, oldfont); +#ifdef _WIN32 + if (wtmp!=wtmpbuf) free(wtmp); +#endif + + __LICE_SC_DRAWTEXT_RESTORE_RECT + if (__sc>0) ret = (ret * 256) / __sc; + + return ret; + } +#endif + + // ensure all glyphs rendered + const char *tstr=str; + int tcnt=strcnt; + while (*tstr && tcnt) + { + unsigned short c; + tstr=adv_str(tstr, &tcnt, &c); + + if (c == '\r') continue; + if (c == '\n') + { + if (dtFlags & DT_SINGLELINE) c=' '; + else continue; + } + + charEnt *ent=findChar(c); + if (!ent) + { + const int os=m_extracharlist.GetSize(); + RenderGlyph(c); + if (m_extracharlist.GetSize() != os) ent=findChar(c); + } + if (ent && ent->base_offset == 0) RenderGlyph(c); + } + + if (dtFlags & DT_CALCRECT) + { + int xpos=0; + int ypos=0; + int max_xpos=0; + int max_ypos=0; + const char *next_break=NULL; + while (*str && strcnt) + { + unsigned short c; + str=adv_str(str, &strcnt, &c); + + if (c == '\r') continue; + if (c == '\n') + { + if (dtFlags & DT_SINGLELINE) + { + c=' '; // different from win32 native behavior, which skips the character + } + else + { + if (m_flags&LICE_FONT_FLAG_VERTICAL) + { + xpos+=m_line_height+lsadj; + ypos=0; + } + else + { + ypos+=m_line_height+lsadj; + xpos=0; + } + if (dtFlags&DT_WORDBREAK) next_break=NULL; + continue; + } + } + + charEnt *ent = findChar(c); + if (ent && ent->base_offset > 0 && ent->base_offset < m_cachestore.GetSize()) + { + if (m_flags&LICE_FONT_FLAG_VERTICAL) + { + const int yext = ypos + ent->height - ent->left_extra; + ypos += ent->advance; + if (xpos+ent->width>max_xpos) max_xpos=xpos+ent->width; + if (ypos>max_ypos) max_ypos=ypos; + if (yext>max_ypos) max_ypos=yext; + } + else + { + const int xext = xpos + ent->width - ent->left_extra; + xpos += ent->advance; + if (ypos+ent->height>max_ypos) max_ypos=ypos+ent->height; + if (xpos>max_xpos) max_xpos=xpos; + if (xext>max_xpos) max_xpos=xext; + } + + if (dtFlags&DT_WORDBREAK) + { + if (m_flags&LICE_FONT_FLAG_VERTICAL) + { + if (str == next_break) + { + xpos += m_line_height+lsadj; + ypos=0; + next_break=NULL; + } + if (!next_break) + { + next_break=NextWordBreak(str, strcnt, rect->bottom-rect->top-ypos); + } + } + else + { + if (str == next_break) + { + ypos += m_line_height+lsadj; + xpos=0; + next_break=NULL; + } + if (!next_break) + { + next_break=NextWordBreak(str, strcnt, rect->right-rect->left-xpos); + } + } + } + } + } + + rect->right = rect->left+max_xpos; + rect->bottom = rect->top+max_ypos; + + + int retval = (m_flags&LICE_FONT_FLAG_VERTICAL) ? max_xpos : max_ypos; + __LICE_SC_DRAWTEXT_RESTORE_RECT + if (__sc>0) return (retval * 256) / __sc; + return retval; + } + float alphaSave = m_alpha; + + if (dtFlags & LICE_DT_USEFGALPHA) + { + m_alpha *= LICE_GETA(m_fg)/255.0; + } + + if (m_alpha==0.0) + { + m_alpha=alphaSave; + __LICE_SC_DRAWTEXT_RESTORE_RECT + return 0; + } + + + RECT use_rect=*rect; + int xpos=use_rect.left; + int ypos=use_rect.top; + + bool isVertRev = false; + if ((m_flags&(LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP)) == (LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP)) + isVertRev=true; + + if (dtFlags & (DT_CENTER|DT_VCENTER|DT_RIGHT|DT_BOTTOM)) + { + RECT tr={0,}; + DrawTextImpl(bm,str,strcnt,&tr,DT_CALCRECT|(dtFlags & DT_SINGLELINE)|(forceWantAlpha?LICE_DT_NEEDALPHA:0)); + if (__sc > 0) + { + __LICE_SC(tr.right); + __LICE_SC(tr.bottom); + } + if (dtFlags & DT_CENTER) + { + xpos += (use_rect.right-use_rect.left-tr.right)/2; + } + else if (dtFlags & DT_RIGHT) + { + xpos = use_rect.right - tr.right; + } + + if (dtFlags & DT_VCENTER) + { + ypos += (use_rect.bottom-use_rect.top-tr.bottom)/2; + } + else if (dtFlags & DT_BOTTOM) + { + ypos = use_rect.bottom - tr.bottom; + } + if (isVertRev) + ypos += tr.bottom; + } + else if (isVertRev) + { + RECT tr={0,}; + DrawTextImpl(bm,str,strcnt,&tr,DT_CALCRECT|(dtFlags & DT_SINGLELINE)|(forceWantAlpha?LICE_DT_NEEDALPHA:0)); + if (__sc > 0) __LICE_SC(tr.bottom); + ypos += tr.bottom; + } + + + int start_y=ypos; + int start_x=xpos; + int max_ypos=ypos; + int max_xpos=xpos; + + if (!(dtFlags & DT_NOCLIP)) + { + if (use_rect.left<0)use_rect.left=0; + if (use_rect.top<0) use_rect.top=0; + if (use_rect.right > bm_w) use_rect.right = bm_w; + if (use_rect.bottom > bm_h) use_rect.bottom = bm_h; + if (use_rect.right <= use_rect.left || use_rect.bottom <= use_rect.top) + { + m_alpha=alphaSave; + __LICE_SC_DRAWTEXT_RESTORE_RECT + return 0; + } + } + else + { + use_rect.left=use_rect.top=0; + use_rect.right = bm_w; + use_rect.bottom = bm_h; + } + + + // todo: handle DT_END_ELLIPSIS etc + // thought: calculate length of "...", then when pos+length+widthofnextchar >= right, switch + // might need to precalc size to make sure it's needed, though + + const char *next_break=NULL; + while (*str && strcnt) + { + unsigned short c; + str=adv_str(str, &strcnt, &c); + + if (c == '\r') continue; + if (c == '\n') + { + if (dtFlags & DT_SINGLELINE) + { + c=' '; // different from win32 native behavior, which skips the character + } + else + { + if (m_flags&LICE_FONT_FLAG_VERTICAL) + { + xpos+=m_line_height+lsadj; + ypos=start_y; + } + else + { + ypos+=m_line_height+lsadj; + xpos=start_x; + } + if (dtFlags&DT_WORDBREAK) next_break=NULL; + continue; + } + } + + charEnt *ent = findChar(c); + if (ent && ent->base_offset > 0 && ent->base_offset < m_cachestore.GetSize()) + { + if (isVertRev) ypos -= ent->height; + + bool drawn = DrawGlyph(bm,c,xpos,ypos,&use_rect); + + if (m_flags&LICE_FONT_FLAG_VERTICAL) + { + if (!isVertRev) + { + ypos += ent->advance; + } + else ypos += ent->height - ent->advance; + if (drawn && xpos+ent->width > max_xpos) max_xpos=xpos; + } + else + { + xpos += ent->advance; + if (drawn && ypos+ent->height>max_ypos) max_ypos=ypos+ent->height; + } + + if (dtFlags&DT_WORDBREAK) + { + if (m_flags&LICE_FONT_FLAG_VERTICAL) + { + if (str == next_break) + { + xpos += m_line_height+lsadj; + ypos=start_y; + next_break=NULL; + } + if (!next_break) + { + next_break=NextWordBreak(str, strcnt, use_rect.bottom-ypos); + } + } + else + { + if (str == next_break) + { + ypos += m_line_height+lsadj; + xpos=start_x; + next_break=NULL; + } + if (!next_break) + { + next_break=NextWordBreak(str, strcnt, use_rect.right-xpos); + } + } + } + } + } + + m_alpha=alphaSave; + int retv = (m_flags&LICE_FONT_FLAG_VERTICAL) ? max_xpos - start_x : max_ypos - start_y; + __LICE_SC_DRAWTEXT_RESTORE_RECT + if (__sc>0) return (retv*256)/__sc; + return retv; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/.gitignore b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/.gitignore new file mode 100644 index 0000000..4787a18 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/.gitignore @@ -0,0 +1,5 @@ +Win32/Debug/ +Win32/Release/ +x64/Debug/ +x64/Release/ + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Controller.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Controller.h new file mode 100644 index 0000000..89aed0a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Controller.h @@ -0,0 +1,9 @@ +/* Controller */ + +#import + +@interface Controller : NSObject +{ +} +-(void)awakeFromNib; +@end diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Controller.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Controller.mm new file mode 100644 index 0000000..e539797 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Controller.mm @@ -0,0 +1,13 @@ +#import "Controller.h" + +#include "main.cpp" // this would otherwise conflict in object name with main.m, which xcode made us. + + +@implementation Controller +-(void)awakeFromNib +{ + SWELL_RegisterCustomControlCreator(ccontrolCreator); + HWND h=CreateDialog(NULL,MAKEINTRESOURCE(IDD_DIALOG1),NULL,dlgProc); + ShowWindow(h,SW_SHOW); +} +@end diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/InfoPlist.strings b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..997688e Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/InfoPlist.strings differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/classes.nib b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 0000000..cfd6e17 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,7 @@ +{ + IBClasses = ( + {CLASS = Controller; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/info.nib b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 0000000..6dc6ca1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,21 @@ + + + + + IBDocumentLocation + 110 86 356 240 0 0 1440 878 + IBEditorPositions + + 29 + 109 299 338 44 0 0 1440 878 + + IBFramework Version + 446.1 + IBOpenObjects + + 29 + + IBSystem Version + 8R2232 + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/keyedobjects.nib b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/keyedobjects.nib new file mode 100644 index 0000000..21c91cb Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Info.plist b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Info.plist new file mode 100644 index 0000000..abca571 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.yourcompany.test + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Makefile b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Makefile new file mode 100644 index 0000000..2f8c313 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/Makefile @@ -0,0 +1,68 @@ +CFLAGS=-O2 -g -Wall +LFLAGS= +CC=gcc +CXX=g++ +WDL_PATH=../.. + +CFLAGS += -D_FILE_OFFSET_BITS=64 + +ifdef NOSWELL +CFLAGS += -D_LICE_NO_SYSBITMAPS_ +SWELL_OBJS = +else + CFLAGS += -DSWELL_LICE_GDI + + ifdef GDK2 + CFLAGS += -DSWELL_TARGET_GDK=2 $(shell pkg-config --cflags gdk-2.0) + LFLAGS += $(shell pkg-config --libs gdk-2.0) -lX11 -lXi + else + CFLAGS += -DSWELL_TARGET_GDK=3 $(shell pkg-config --cflags gdk-3.0) + LFLAGS += $(shell pkg-config --libs gdk-3.0) -lX11 -lXi + endif + ifndef NOFREETYPE + CFLAGS += -DSWELL_FREETYPE $(shell pkg-config --cflags freetype2) + LFLAGS += $(shell pkg-config --libs freetype2) + endif + + SWELL_OBJS = swell-wnd-generic.o swell-gdi-lice.o swell.o swell-misc-generic.o \ + swell-dlg-generic.o swell-menu-generic.o swell-kb-generic.o \ + swell-gdi-generic.o swell-ini.o swell-generic-gdk.o \ + swell-appstub-generic.o swell-miscdlg-generic.o + + LFLAGS += -ldl + + vpath %.cpp $(WDL_PATH)/swell +endif + +CXXFLAGS=$(CFLAGS) + +vpath %.c $(WDL_PATH)/zlib $(WDL_PATH)/libpng $(WDL_PATH)/jpeglib $(WDL_PATH)/giflib +vpath %.cpp $(WDL_PATH)/lice $(WDL_PATH)/plush2 + +ZLIB_OBJS = compress.o adler32.o crc32.o deflate.o infback.o inffast.o inflate.o inftrees.o trees.o uncompr.o zutil.o ioapi.o zip.o unzip.o + +PNGLIB_OBJS = png.o pngerror.o pngget.o pngmem.o pngpread.o pngread.o pngrio.o pngrtran.o pngrutil.o pngset.o pngtrans.o + +JPEGLIB_OBJS = jcomapi.o jdapimin.o jdapistd.o jdatadst.o jdatasrc.o jdcoefct.o jdcolor.o jddctmgr.o jdhuff.o jdinput.o jdmainct.o jdmarker.o \ + jdmaster.o jdmerge.o jdphuff.o jdpostct.o jdsample.o jerror.o jfdctflt.o jfdctfst.o jfdctint.o jidctflt.o jidctfst.o jidctint.o \ + jidctred.o jmemmgr.o jmemnobs.o jquant1.o jquant2.o jutils.o + +GIFLIB_OBJS = dgif_lib.o egif_lib.o gifalloc.o gif_hash.o + +LICEOBJS = lice.o lice_gif.o lice_gif_write.o lice_image.o lice_jpg.o lice_png.o lice_pcx.o lice_palette.o lice_line.o lice_arc.o lice_text.o lice_textnew.o lice_texgen.o lice_colorspace.o + +PLUSH_OBJS = pl_cam.o pl_make.o pl_math.o pl_obj.o pl_putface.o pl_read_3ds.o pl_read_cob.o pl_read_jaw.o pl_spline.o + +.phony: clean default + +default: test + +test: $(LICEOBJS) $(JPEGLIB_OBJS) $(PNGLIB_OBJS) $(ZLIB_OBJS) $(GIFLIB_OBJS) $(SWELL_OBJS) $(PLUSH_OBJS) main.o fly.o + $(CXX) $(CFLAGS) -o $@ $^ $(LFLAGS) + + +imgs2gif: $(LICEOBJS) $(JPEGLIB_OBJS) $(PNGLIB_OBJS) $(ZLIB_OBJS) $(GIFLIB_OBJS) $(SWELL_OBJS) imgs2gif.o + $(CXX) $(CFLAGS) -o $@ $^ $(LFLAGS) + +clean: + -rm $(LICEOBJS) $(JPEGLIB_OBJS) $(PNGLIB_OBJS) $(ZLIB_OBJS) $(GIFLIB_OBJS) imgs2gif.o imgs2gif $(SWELL_OBJS) $(PLUSH_OBJS) test main.o fly.o diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/fly.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/fly.cpp new file mode 100644 index 0000000..3826fdd --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/fly.cpp @@ -0,0 +1,286 @@ +#include "../lice.h" +#include "../../plush2/plush.h" + +#ifndef _WIN32 +#include "../../swell/swell.h" +#endif + + +/* Physical size of land */ +#define LAND_SIZE 65000 +/* Number of divisions of the land. Higher number == more polygons */ +#define LAND_DIV 32 + + // These are the amount the mouse has moved since the last frame + // mouse_b = button status, mouse_avail is whether or not mouse is + // available, mouse_buttons is number of buttons +static float mouse_sens = 8192.0/32768.0; + +static int draw_sky = 1; // do we draw the sky? +static int wait_vsync = 0; // do we wait for vsync? +static pl_Mat *mat[3+1]; // our materials, we have 1 extra for null + // termination for plMatMakeOptPal2() +static pl_Cam *cam; // our camera +static pl_Obj *land; // the land object +static pl_Obj *sky, *sky2; // the two skies +static pl_Light lights[16]; + +static void setup_materials(pl_Mat **mat); + // Sets up the landscape and skies +static pl_Obj *setup_landscape(pl_Mat *m, pl_Mat *sm, pl_Mat *sm2); + +void doFlyEffect(LICE_IBitmap *fb, HWND hwnd) +{ + static int initted; + if (!initted) + { + initted=1; + cam = new pl_Cam; + cam->Fov=90.0; + cam->WantZBuffer=true; + if (cam->WantZBuffer) cam->Sort = -1; + cam->Y = 800; // move the camera up from the ground + cam->Pitch = 180.0; + + setup_materials(mat); // intialize materials and palette + + land = setup_landscape(mat[0],mat[1],mat[2]); // create landscape + sky = land->Children.Get(0); // unhierarchicalize the sky from the land + land->Children.Delete(0); + sky2 = land->Children.Get(0); + land->Children.Delete(0); + + int x; + for(x=0;xBegin(fb); + + int x; + for(x=0;xRenderLight(&lights[x]); + + // lots of rendering special casing + if (draw_sky) { // if we're drawing the sky + if (cam->Y > 2000) { // if above the sky, only render the skies, with + // no far clip plane + + cam->RenderObject(sky); + cam->RenderObject(sky2); + } else { // otherwise, render the sky (but not the second sky), + // and the land, with a far clip plane + + + cam->RenderObject(sky); + cam->RenderObject(land); + } + } else { // not drawing sky, just render the land + + cam->RenderObject(land); + } + cam->End(); // finish rendering + + static POINT lpos; + POINT p; + GetCursorPos(&p); + int mouse_x = 0; + int mouse_y = 0; + int mouse_b=0; + + if (hwnd) + { + mouse_x = p.x-lpos.x; + mouse_y = p.y-lpos.y; + if (GetAsyncKeyState(VK_LBUTTON)&0x8000) mouse_b|=2; + + RECT r; + GetWindowRect(hwnd,&r); + p.x=(r.right+r.left)/2; + p.y=(r.bottom+r.top)/2; + SetCursorPos(p.x,p.y); + } + lpos=p; + // We calculate the amount of time in thousanths of seconds this frame took + double prevtime = 10; //((uclock() / (float) UCLOCKS_PER_SEC) - prevtime)*1000.0; + + if (mouse_b & 2) { // if right button hit, we go forward quickly + cam->X -= + prevtime*4*sin(cam->Pan*PL_PI/180.0)*cos(cam->Pitch*PL_PI/180.0); + cam->Z += + prevtime*4*cos(cam->Pan*PL_PI/180.0)*cos(cam->Pitch*PL_PI/180.0); + cam->Y += + prevtime*4*sin(cam->Pitch*PL_PI/180.0); + } else if (mouse_b & 1) { // if left button hit, we go forward slowly + cam->X -= + prevtime*2*sin(cam->Pan*PL_PI/180.0)*cos(cam->Pitch*PL_PI/180.0); + cam->Z += + prevtime*2*cos(cam->Pan*PL_PI/180.0)*cos(cam->Pitch*PL_PI/180.0); + cam->Y += + prevtime*2*sin(cam->Pitch*PL_PI/180.0); + } + cam->Pitch += (mouse_y*mouse_sens); // update pitch and pan of ship + cam->Pan += (mouse_x*mouse_sens)*(-cos(cam->Pitch*PL_PI/180.0)); + + if (cam->X > LAND_SIZE/2) cam->X = LAND_SIZE/2; // make sure we don't go + if (cam->X < -LAND_SIZE/2) cam->X = -LAND_SIZE/2; // too far away + if (cam->Z > LAND_SIZE/2) cam->Z = LAND_SIZE/2; + if (cam->Z < -LAND_SIZE/2) cam->Z = -LAND_SIZE/2; + if (cam->Y < 0) cam->Y = 0; + if (cam->Y > 8999) cam->Y = 8999; +#if 0 + + while (kbhit()) switch(getch()) { // handle keystrokes + case 27: done++; break; // ESC == quit + // + is for zooming in. + case '=': case '+': cam->Fov -= 1.0; if (cam->Fov < 1.0) cam->Fov = 1.0; + sprintf(lastmessage,"FOV: %2.f",cam->Fov); + break; + // - is for zooming out + case '-': cam->Fov += 1.0; if (cam->Fov > 179.0) cam->Fov = 179.0; + sprintf(lastmessage,"FOV: %2.f",cam->Fov); + break; + // [ decreases mouse sensitivity + case '[': mouse_sens /= 1.1; + sprintf(lastmessage,"MouseSens: %.3f",mouse_sens); + break; + // ] increases mouse sensitivity + case ']': mouse_sens *= 1.1; + sprintf(lastmessage,"MouseSens: %.3f",mouse_sens); + break; + // v toggles vsync + case 'v': wait_vsync ^= 1; + sprintf(lastmessage,"VSync %s",wait_vsync ? "on" : "off"); + break; + // s toggles sky + case 's': draw_sky ^= 1; + sprintf(lastmessage,"Sky %s",draw_sky ? "on" : "off"); + break; + } +#endif + + //LICE_ScaledBlit(fb,mat[2]->Texture,0,0,fb->getWidth(),fb->getHeight(),0,0,mat[2]->Texture->getWidth(),mat[2]->Texture->getHeight(),1.0f,0); +} + + + + +static void setup_materials(pl_Mat **mat) { + // create our 3 materials, make the fourth null so that plMatMakeOptPal2() + // knows where to stop + mat[0] = new pl_Mat; + mat[1] = new pl_Mat; + mat[2] = new pl_Mat; + mat[3] = 0; + + extern LICE_IBitmap *bmp; + // set up material 0 (the ground) + mat[0]->Smoothing = true; + mat[0]->Lightable=true; + mat[0]->Ambient[0] = 0.0; // these calculations are to get the + mat[0]->Ambient[1] = 0.0; // distance shading to work right + mat[0]->Ambient[2] = 0.0; + mat[0]->Diffuse[0] = 1.0; + mat[0]->Diffuse[1] = 1.0; + mat[0]->Diffuse[2] = 1.0; + mat[0]->FadeDist = 10000.0; + mat[0]->Texture2 = LICE_LoadPCX("c:\\ground.pcx"); + if (!mat[0]->Texture2) + { + mat[0]->Texture2 = new LICE_MemBitmap(400,400); +// LICE_TexGen_Marble(mat[0]->Texture2,NULL,1.0,0.8,0.8,1.0f); + LICE_TexGen_Noise(mat[0]->Texture2,NULL,1.0,1.0,1.0,1.0f,NOISE_MODE_WOOD,8); + } + + mat[0]->Texture=mat[0]->Texture2; + mat[0]->Texture2=bmp; + mat[0]->TexCombineMode=LICE_BLIT_MODE_MUL|LICE_BLIT_FILTER_BILINEAR; + mat[0]->Tex2CombineMode=LICE_BLIT_MODE_DODGE|LICE_BLIT_USE_ALPHA|LICE_BLIT_FILTER_BILINEAR; + mat[0]->Tex2Scaling[1]= + mat[0]->Tex2Scaling[0] = 40.0*LAND_SIZE/50000; + mat[0]->TexScaling[1]= + mat[0]->TexScaling[0] = 40.0*LAND_SIZE/50000; + mat[0]->Tex2MapIdx=0; + mat[0]->PerspectiveCorrect = 16; + mat[0]->BackfaceIllumination=1.0; + + // set up material 1 (the sky) + mat[1]->Lightable=true; + mat[1]->Ambient[0] = 0.4; // these calculations are to get the + mat[1]->Ambient[1] = 0.4; // distance shading to work right + mat[1]->Ambient[2] = 0.4; + mat[1]->Diffuse[0] = 1.0; + mat[1]->Diffuse[1] = 1.0; + mat[1]->Diffuse[2] = 1.0; + mat[1]->FadeDist = 10000.0; + mat[1]->Texture = LICE_LoadPCX("c:\\sky.pcx"); + mat[1]->TexCombineMode=LICE_BLIT_MODE_MUL; + mat[1]->TexScaling[1] = mat[1]->TexScaling[0] = 45.0*LAND_SIZE/50000; + mat[1]->PerspectiveCorrect = 32; + mat[1]->BackfaceCull=false; + mat[1]->BackfaceIllumination=1.0; + + if (!mat[1]->Texture) + { + mat[1]->Texture = new LICE_MemBitmap(400,400); + LICE_TexGen_Noise(mat[1]->Texture,NULL,0.0,0.0,1.0,1.0f,NOISE_MODE_WOOD,8); + } + mat[1]->Texture2=bmp; + mat[1]->Tex2CombineMode= LICE_BLIT_MODE_HSVADJ|LICE_BLIT_FILTER_BILINEAR|LICE_BLIT_USE_ALPHA; + mat[1]->Tex2Scaling[1] = mat[1]->Tex2Scaling[0] = 45.0*LAND_SIZE/50000*0.37; + mat[1]->Tex2MapIdx=0; + + // set up material 2 (the second sky) + mat[2]->Lightable=true; + mat[2]->Smoothing=false; + mat[2]->Ambient[0] = 0.2; // these calculations are to get the + mat[2]->Ambient[1] = 0.2; // distance shading to work right + mat[2]->Ambient[2] = 0.2; + mat[2]->Diffuse[0] = 0.0; + mat[2]->Diffuse[1] = 0.0; + mat[2]->Diffuse[2] = 0.0; + mat[2]->Texture = LICE_LoadPCX("c:\\sky2.pcx"); + if (!mat[2]->Texture) + { + mat[2]->Texture = new LICE_MemBitmap(400,400); + LICE_TexGen_Marble(mat[2]->Texture,NULL,0.3,0,0.4,0.4); + } + mat[2]->TexScaling[0] = mat[2]->TexScaling[1] = 10.0; //200.0*LAND_SIZE/50000; + mat[2]->TexCombineMode=LICE_BLIT_MODE_COPY; + mat[2]->PerspectiveCorrect = 16; + + mat[2]->Texture2=bmp; + mat[2]->Tex2CombineMode= LICE_BLIT_MODE_HSVADJ|LICE_BLIT_FILTER_BILINEAR|LICE_BLIT_USE_ALPHA; + mat[2]->Tex2Scaling[1] = mat[1]->Tex2Scaling[0] = 45.0*LAND_SIZE/50000*3.37; + mat[2]->Tex2MapIdx=0; + +} + +pl_Obj *setup_landscape(pl_Mat *m, pl_Mat *sm, pl_Mat *sm2) { + int i; + // make our root object the land + pl_Obj *o = plMakePlane(LAND_SIZE,LAND_SIZE,LAND_DIV-1,m); + // give it a nice random bumpy effect + for (i = 0; i < o->Vertices.GetSize(); i ++) + o->Vertices.Get()[i].y += (float) (rand()%1400)-700; + // gotta recalculate normals for backface culling to work right + o->CalculateNormals(); + + // Make our first child the first sky + o->Children.Add(plMakePlane(LAND_SIZE,LAND_SIZE,30,sm)); + o->Children.Get(0)->Yp = 2000; + + // and the second the second sky + o->Children.Add(plMakeSphere(LAND_SIZE,10,10,sm2)); + o->Children.Get(1)->Yp = 2000; + o->Children.Get(1)->FlipNormals(); + + return (o); +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/image.png b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/image.png new file mode 100644 index 0000000..ae85591 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/image.png differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/imgs2gif.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/imgs2gif.cpp new file mode 100644 index 0000000..46c41e3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/imgs2gif.cpp @@ -0,0 +1,108 @@ +#include +#include + +#include "../lice.h" + +void usage(const char *a, const char *b) +{ + if (a||b) printf("%s: %s\n",a,b); + printf("Usage: imgs2gif [-w width] [-h height] [-d ms] [-skipempty] [-leaddelay ms] output.gif file1.jpg [file2.png ...]\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + int delay=10; + int size_w=0,size_h=0; + int i; + int rv=0; + int leaddelay=0; + bool skipempty=false; + void *gifOut=NULL; + const char *fn=NULL; + + LICE_MemBitmap lastfr, fr,fr2; + int accum_lat=0; + + for (i=1;i= argc) usage("Missing parameter",argv[i-1]); + size_w = atoi(argv[i]); + } + else if (!strcmp(argv[i],"-h")) + { + if (++i >= argc) usage("Missing parameter",argv[i-1]); + size_h = atoi(argv[i]); + } + else if (!strcmp(argv[i],"-d")) + { + if (++i >= argc) usage("Missing parameter",argv[i-1]); + delay = atoi(argv[i]); + } + else if (!strcmp(argv[i],"-skipempty")) skipempty=true; + else if (!strcmp(argv[i],"-leaddelay")) + { + if (++i >= argc) usage("Missing parameter",argv[i-1]); + leaddelay=atoi(argv[i]); + } + else usage("Unknown option",argv[i]); + } + else if (!fn) fn=argv[i]; + else + { + printf("Loading %s\n",argv[i]); + if (!LICE_LoadImage(argv[i],&fr) || !fr.getWidth() || !fr.getHeight()) + { + printf("Error loading image: %s\n",argv[i]); + continue; + } + + if (!size_w) size_w=fr.getWidth(); + if (!size_h) size_h=fr.getHeight(); + LICE_MemBitmap *usefr = &fr; + if (fr.getWidth() != size_w || fr.getHeight() != size_h) + { + fr2.resize(size_w,size_h); + LICE_ScaledBlit(usefr=&fr2,&fr,0,0,size_w,size_h,0,0,fr.getWidth(),fr.getHeight(),1.0f,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + } + + if (!gifOut) + { + gifOut=LICE_WriteGIFBegin(fn,usefr,0,leaddelay?leaddelay:delay,false); + if (!gifOut) usage("Error writing to file",fn); + } + else + { + accum_lat += delay; + int diffcoords[4]={0,0,size_w,size_h}; + if (LICE_BitmapCmp(usefr,&lastfr,diffcoords)) + { + LICE_SubBitmap bm(usefr,diffcoords[0],diffcoords[1], diffcoords[2],diffcoords[3]); + LICE_WriteGIFFrame(gifOut,&bm,diffcoords[0],diffcoords[1], true, accum_lat); + accum_lat=0; + } + if (skipempty) accum_lat=0; + } + + LICE_Copy(&lastfr,usefr); + } + } + printf("finishing up\n"); + if (gifOut) LICE_WriteGIFEnd(gifOut); + + return rv; +} + +#ifndef _WIN32 +INT_PTR SWELLAppMain(int msg, INT_PTR parm1, INT_PTR parm2) +{ + return 0; +} +#endif + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.cpp new file mode 100644 index 0000000..781abbe --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.cpp @@ -0,0 +1,1137 @@ +/* + Cockos WDL - LICE - Lightweight Image Compositing Engine + Copyright (C) 2007 and later, Cockos Incorporated + File: main.cpp (example use of LICE) + See lice.h for license and other information +*/ + +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "../lice.h" +#include "../../plush2/plush.h" +#include "../../MersenneTwister.h" +#include +#include + +#ifndef _WIN32 +#include +#include "../../swell/swell.h" +#endif + +static double gettm() +{ +#ifndef _WIN32 + struct timeval tm={0,}; + gettimeofday(&tm,NULL); + return (double)tm.tv_sec + (double)tm.tv_usec/1000000; +#else + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return (double)now.QuadPart / (double)freq.QuadPart; +#endif +} + +char g_status[1024]; + + +#include "../lice_text.h" + +#include "../lice_glbitmap.h" +//#define GLEW_STATIC +//#include "../glew/include/gl/glew.h" +//#include "../glew/include/gl/wglew.h" + +//uncomment this to enable the ffmpeg video output in test 1 +//#define FFMPEG_TEST + +//#define TIMING +#include "../../timing.c" + +#ifdef FFMPEG_TEST +//ffmpeg encoding test +#include "../../ffmpeg.h" +#pragma comment(lib, "../../../sdks/ffmpeg/lib/avcodec.lib") +#pragma comment(lib, "../../../sdks/ffmpeg/lib/avformat.lib") +#pragma comment(lib, "../../../sdks/ffmpeg/lib/avutil.lib") +#pragma comment(lib, "../../../sdks/ffmpeg/lib/swscale.lib") +static WDL_VideoEncode *m_encoder; +static WDL_VideoDecode *m_decoder; +#endif + +#include "resource.h" + +#define NUM_EFFECTS 25 + +const char *effect_names[NUM_EFFECTS] = +{ + "Rotated + Scaled blit", + "Simple alpha blit", + "Rotated blit", + "Scaled blit", + "GradRect", + "Marble generator", + "Wood generator", + "Noise generator", + "Circular noise generator", + "GradRect + text + rotated blit", + "PutPixel", + "Line", + "DrawText", + "Icon loading", + "Circles, Arc", + "Rotated + Multiply add blit", + "Transform blit", + "Plush 3D", + "3D Fly (use mouse)", + "SVG loading (C:\\test.svg)", + "GL acceleration (disabled)", + "Bezier curves", + "Convex polygon fill", + "Palette generator (C:\\test.png)", + "Triangle test", +}; + +HINSTANCE g_hInstance; +LICE_IBitmap *jpg; +LICE_IBitmap *bmp; +LICE_IBitmap *icon; +LICE_IBitmap *framebuffer; +static int m_effect = 11; +static int m_doeff = 0; + +static LICE_IBitmap* tmpbmp = 0; + +static DWORD m_start_time, m_frame_cnt; +bool m_cap; + + +static void DoPaint(HWND hwndDlg, HDC dc) +{ + RECT r; + GetClientRect(hwndDlg, &r); + +#ifdef _WIN32 + r.top+=40; + if (r.top >= r.bottom) r.top=r.bottom-1; +#endif + + if (framebuffer->resize(r.right-r.left,r.bottom-r.top)) + { + m_doeff=1; + // memset(framebuffer->getBits(),0,framebuffer->getWidth()*framebuffer->getHeight()*4); + } + + int x=rand()%(r.right+300)-150; + int y=rand()%(r.bottom+300)-150; + + static int frame_cnt; + static int s_preveff = -1; + if (m_effect != s_preveff) + { + frame_cnt=0; + s_preveff = m_effect; + LICE_Clear(framebuffer, 0); + } + + static MTRand s_rng; + double t2=gettm(); + + switch(m_effect) + { + case 23: + { + const int palnumcols=256; + + static int init=0; + static LICE_IBitmap* srcbmp=0; + static LICE_IBitmap* palbmp=0; + static LICE_pixel palette[palnumcols] = { 0 }; + + if (!init) + { + init=-1; + srcbmp = LICE_LoadPNG("C:\\test.png"); + if (srcbmp) + { + int n = LICE_BuildPalette(srcbmp, palette, palnumcols); + palbmp = new LICE_MemBitmap; + LICE_Copy(palbmp, srcbmp); + void LICE_TestPalette(LICE_IBitmap*, LICE_pixel*, int); + LICE_TestPalette(palbmp, palette, n); + init=1; + } + } + + if (init > 0) + { + static int lastw=0; + static int lasth=0; + if (lastw != framebuffer->getWidth() || lasth != framebuffer->getHeight()) + { + lastw = framebuffer->getWidth(); + lasth = framebuffer->getHeight(); + int y = framebuffer->getHeight()*3/4; + LICE_ScaledBlit(framebuffer, srcbmp, 0, 0, lastw/2, y, 0, 0, srcbmp->getWidth(), srcbmp->getHeight(), 1.0f, LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + LICE_ScaledBlit(framebuffer, palbmp, lastw/2, 0, lastw/2, y, 0, 0, palbmp->getWidth(), palbmp->getHeight(), 1.0f, LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + + int dy = (lasth-y)/4; + int dx = lastw/(palnumcols/4); + int i, j, k=0; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < palnumcols/4; ++j) + { + LICE_FillRect(framebuffer, j*dx, y+i*dy, dx, dy, palette[k++], 1.0f, LICE_BLIT_MODE_COPY); + } + } + } + } + } + break; + case 24: + { + LICE_MemBitmap bm(128,128); + LICE_Clear(framebuffer,0); + LICE_Clear(&bm,0); + + static POINT sp; + POINT p; + if (!sp.x&&!sp.y) GetCursorPos(&sp); + GetCursorPos(&p); + p.x-=sp.x; + p.y-=sp.y; + int th=p.y+16; + bool flip = th < 0; + if (flip) th=-th; + int tw=p.x+16; + int cx=(tw+1)/2; + int x=1,y=1; + LICE_pixel lc=LICE_RGBA(255,0,255,255); + LICE_Line(&bm,x,y,x+tw,y,lc,1.0f,LICE_BLIT_MODE_COPY); + LICE_Line(&bm,x,y+th,x+tw,y+th,lc,1.0f,LICE_BLIT_MODE_COPY); + LICE_Line(&bm,x+tw,y,x+tw,y+th,lc,1.0f,LICE_BLIT_MODE_COPY); + LICE_Line(&bm,x,y,x,y+th,lc,1.0f,LICE_BLIT_MODE_COPY); + + LICE_FillTriangle(&bm,x+tw,y+(flip?th:0)/*+th/4*/,x+cx,y+(flip?0:th),x,y+(flip?th:0),LICE_RGBA(255,255,255,255),0.75f,LICE_BLIT_MODE_COPY); + + int sc=16; + LICE_ScaledBlit(framebuffer,&bm,0,0,bm.getWidth()*sc,bm.getHeight()*sc,0,0,bm.getWidth(),bm.getHeight(),1.0,LICE_BLIT_MODE_COPY); + for (y=0;ygetWidth(); + int h = framebuffer->getHeight(); + + static bool init = false; + if (!init) + { + init = true; + for (i = 0; i < 16; ++i) + { + x[i] = s_rng.randInt(w-1); + y[i] = s_rng.randInt(h-1); + } + } + + for (i = 0; i < 16; ++i) + { + x[i] += s_rng.randNorm(0.0, 1.0)+0.5; + y[i] += s_rng.randNorm(0.0, 1.0)+0.5; + if (x[i] < 0) x[i] = 0; + else if (x[i] >= w) x[i] = w-1; + if (y[i] < 0) y[i] = 0; + else if (y[i] >= h) y[i] = h-1; + } + + LICE_Clear(framebuffer, 0); + LICE_FillConvexPolygon(framebuffer, x, y, 16, LICE_RGBA(96,96,96,255), 0.5f, LICE_BLIT_MODE_ADD); + + for (i = 0; i < 16; ++i) + { + LICE_Line(framebuffer, x[i]-1, y[i], x[i]+1, y[i], LICE_RGBA(255,0,0,255), 1.0f, LICE_BLIT_MODE_COPY); + LICE_Line(framebuffer, x[i], y[i]-1, x[i], y[i]+1, LICE_RGBA(255,0,0,255), 1.0f, LICE_BLIT_MODE_COPY); + } + } + break; + + case 21: + { + int w = framebuffer->getWidth(); + int h = framebuffer->getHeight(); + + int x0, y0, x1, y1, x2, y2, x3, y3; + + bool aa = true; + float maxsegmentpx = 0.0f; + + x0 = w*(double)rand()/RAND_MAX; + y0 = h*(double)rand()/RAND_MAX; + x1 = w*(double)rand()/RAND_MAX; + y1 = h*(double)rand()/RAND_MAX; + x2 = w*(double)rand()/RAND_MAX; + y2 = h*(double)rand()/RAND_MAX; + LICE_DrawQBezier(framebuffer, x0, y0, x1, y1, x2, y2, LICE_RGBA(255,0,0,255), 1.0f, LICE_BLIT_MODE_COPY, aa, maxsegmentpx); + + x0 = w*(double)rand()/RAND_MAX; + y0 = h*(double)rand()/RAND_MAX; + x1 = w*(double)rand()/RAND_MAX; + y1 = h*(double)rand()/RAND_MAX; + x2 = w*(double)rand()/RAND_MAX; + y2 = h*(double)rand()/RAND_MAX; + x3 = w*(double)rand()/RAND_MAX; + y3 = h*(double)rand()/RAND_MAX; + LICE_DrawCBezier(framebuffer, x0, y0, x1, y1, x2, y2, x3, y3, LICE_RGBA(0,255,0,255), 1.0f, LICE_BLIT_MODE_COPY, aa, maxsegmentpx); + } + break; + +#ifndef DISABLE_LICE_EXTENSIONS + case 20: // GL acceleration + { + int w = framebuffer->getWidth(); + int h = framebuffer->getHeight(); + + int x, y, tw, th; + + static LICE_IBitmap* glbmp = 0; + if (!glbmp) + { + glbmp = new LICE_GL_SysBitmap(0, 0); + glbmp->resize(w, h); + + glbmp = new LICE_GL_SubBitmap(glbmp, 20, 80, 50, 20); + + framebuffer = glbmp; + } + + if (!tmpbmp) + { + tmpbmp = new LICE_GL_MemBitmap(0, 0); + tmpbmp->resize(20, 20); + //tmpbmp = new LICE_MemBitmap(20, 20); + } + + LICE_Clear(tmpbmp, LICE_RGBA(255,0,0,255)); + LICE_Line(tmpbmp, 0, 0, 20, 20, LICE_RGBA(255,255,255,255), 1.0f, LICE_BLIT_MODE_COPY, true); + + static int _n = 0; + + //if (_n < 3) + { + //LICE_Clear(glbmp, LICE_RGBA(0,0,0,0)); + + x = w*rand()/RAND_MAX; + y = h*rand()/RAND_MAX; + LICE_Blit(glbmp, tmpbmp, x, y, 0, 0, 20, 20, 1.0f, LICE_BLIT_MODE_COPY); // blit one GL bitmap to another + + x = w*rand()/RAND_MAX; + y = h*rand()/RAND_MAX; + LICE_ScaledBlit(glbmp, tmpbmp, x, y, 40, 40, 0, 0, 20, 20, 1.0f, LICE_BLIT_MODE_COPY); // blit one GL bitmap to another + + x = w*rand()/RAND_MAX; + y = h*rand()/RAND_MAX; + tw = (w-x)*rand()/RAND_MAX; + th = (h-y)*rand()/RAND_MAX; + int color = (_n%2 ? LICE_RGBA(63,63,63,255) : LICE_RGBA(0,0,0,255)); + LICE_FillRect(glbmp, x, y, tw, th, color, 1.0f, LICE_BLIT_MODE_COPY); + + x = w*rand()/RAND_MAX; + y = h*rand()/RAND_MAX; + tw = (w-x)*rand()/RAND_MAX; + th = (h-y)*rand()/RAND_MAX; + LICE_Line(glbmp, x, y, x+tw, y+th, LICE_RGBA(255,0,0,255), 1.0f, LICE_BLIT_MODE_COPY, true); + + int x0 = w*rand()/RAND_MAX; + int y0 = h*rand()/RAND_MAX; + int x1 = w*rand()/RAND_MAX; + int y1 = h*rand()/RAND_MAX; + int x2 = w*rand()/RAND_MAX; + int y2 = h*rand()/RAND_MAX; + int x3 = w*rand()/RAND_MAX; + int y3 = h*rand()/RAND_MAX; + LICE_DrawCBezier(glbmp, x0, y0, x1, y1, x2, y2, x3, y3, LICE_RGBA(0,255,0,255), 1.0f, LICE_BLIT_MODE_COPY, true); + + #define A(x) ((LICE_pixel_chan)((x)*255.0+0.5)) + + LICE_pixel_chan alphas[81] = + { + A(0.00), A(0.12), A(0.69), A(1.00), A(1.00), A(1.00), A(0.69), A(0.12), A(0.00), + A(0.12), A(0.94), A(0.82), A(0.31), A(0.25), A(0.31), A(0.82), A(0.94), A(0.12), + A(0.69), A(0.82), A(0.06), A(0.00), A(0.00), A(0.00), A(0.06), A(0.82), A(0.69), + A(1.00), A(0.31), A(0.00), A(0.00), A(0.00), A(0.00), A(0.00), A(0.31), A(1.00), + A(1.00), A(0.19), A(0.00), A(0.00), A(0.00), A(0.00), A(0.00), A(0.19), A(1.00), + A(1.00), A(0.31), A(0.00), A(0.00), A(0.00), A(0.00), A(0.00), A(0.31), A(1.00), + A(0.69), A(0.82), A(0.06), A(0.00), A(0.00), A(0.00), A(0.06), A(0.82), A(0.69), + A(0.12), A(0.94), A(0.82), A(0.31), A(0.25), A(0.31), A(0.82), A(0.94), A(0.12), + A(0.00), A(0.12), A(0.69), A(1.00), A(1.00), A(1.00), A(0.69), A(0.12), A(0.00) + }; + int gw = 9; + int gh = 9; + + x = w*rand()/RAND_MAX; + y = h*rand()/RAND_MAX; + LICE_DrawGlyph(glbmp, x, y, LICE_RGBA(255,255,0,255), alphas, gw, gh, 1.0f, LICE_BLIT_MODE_COPY); + + static LICE_CachedFont* font = 0; + if (!font) + { + font = new LICE_CachedFont; + LOGFONT lf={ 12, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "Arial"}; + HFONT hf = CreateFontIndirect(&lf); + font->SetFromHFont(hf, 0); + font->SetTextColor(LICE_RGBA(255,255,0,255)); + } + + x = w*rand()/RAND_MAX; + y = h*rand()/RAND_MAX; + RECT r = { x, y, x+40, y+10 }; + font->DrawText(glbmp, "foo bar", -1, &r, 0); + + } + ++_n; + } + break; +#endif + + case 18: + { + void doFlyEffect(LICE_IBitmap *fb,HWND); + doFlyEffect(framebuffer,m_cap ? hwndDlg : NULL); + } + break; + + case 17: + { + static pl_Obj *obj=NULL,*obj2=NULL; + static LICE_IBitmap *framebuffer2; + if (!framebuffer2) framebuffer2=new LICE_MemBitmap; + LICE_Copy(framebuffer2,framebuffer); + if (!obj) + { + pl_Mat *mat = new pl_Mat; + pl_Mat *mat2 = new pl_Mat; + + mat2->Smoothing=false; + mat2->Ambient[0]=mat2->Ambient[1]=mat2->Ambient[2]=0.0; + mat2->Diffuse[0]=mat2->Diffuse[1]=0.6; + mat2->Diffuse[2]=1.0; + + mat->Ambient[0]=mat->Ambient[1]=mat->Ambient[2]=0.0; + mat->Diffuse[0]=mat->Diffuse[1]=1.9; + mat->Diffuse[2]=0.4; + + mat->PerspectiveCorrect=16; + mat->SolidCombineMode=LICE_BLIT_MODE_COPY; + mat->SolidOpacity=1.0; + mat->Smoothing=true; + mat->Lightable=true; + //mat->FadeDist = 300.0; + + mat2->Texture=bmp; + mat2->TexOpacity=0.5; + mat2->TexCombineMode=LICE_BLIT_MODE_MUL|LICE_BLIT_FILTER_BILINEAR; + mat2->SolidOpacity=0.4; + mat2->BackfaceCull=false; + mat2->BackfaceIllumination=1.0; + mat2->Texture2=framebuffer2; + mat2->Tex2MapIdx=-1; + mat2->Tex2Opacity=0.75; + + mat->Texture=bmp; + LICE_TexGen_Marble(mat->Texture = new LICE_MemBitmap(r.right,r.bottom),NULL,0.3,0.4,0.0,1.0f); + + mat->TexOpacity=0.5; + mat->TexScaling[0]=mat->TexScaling[1]=3.0; + mat->TexCombineMode=LICE_BLIT_MODE_MUL|LICE_BLIT_FILTER_BILINEAR; + + LICE_TexGen_Noise(mat->Texture2 = new LICE_MemBitmap(r.right,r.bottom),NULL,0.3,0.4,0.0,1.0f); + // mat->Texture2=icon; + mat->Tex2MapIdx=-1; + mat->Tex2CombineMode=LICE_BLIT_MODE_ADD|LICE_BLIT_FILTER_BILINEAR; + mat->Tex2Opacity=0.8; + mat->Tex2Scaling[0]=2.0; + mat->Tex2Scaling[1]=-2.0; + + mat->BackfaceCull=true; + mat->BackfaceIllumination=0.0; + + obj=plMakeTorus(100.0,80.0,40,40,mat); + + int x; + if (0)for(x=1;x<3;x++) + { + pl_Obj *no = obj->Clone(); + no->Translate(0,40.0,-x*35.0); + obj->Children.Add(no); + no->Xa += 50.35*x; + no->Ya -= 30.13*x; + } + obj2=plMakeSphere(58,20,20,mat2); + obj2->Zp -= 30.0; + obj->Zp += 30.0; + + /*pl_Obj *o = plRead3DSObj("c:\\temp\\suzanne.3ds",mat); + if (o) + { + o->Scale(30.0); + o->Translate(150.0,0,0); + obj->Children.Add(o); + } + */ + } + obj2->Xa+=0.3; + obj2->Ya+=-0.1; + obj->Ya+=0.1; + obj->Xa+=0.1; + obj->Za+=0.1; + obj->GenMatrix=true; + + if (1) LICE_Clear(framebuffer,0); + else { + double a=GetTickCount()/1000.0; + + double scale=(1.1+sin(a)*0.3); + + LICE_RotatedBlit(framebuffer,framebuffer,0,0,r.right,r.bottom,0+sin(a*0.3)*16.0,0+sin(a*0.21)*16.0,r.right,r.bottom,cos(a*0.5)*0.13,false,254/255.0,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + } + static pl_Cam cam; + LICE_SubBitmap tmpbm(framebuffer,10,10,framebuffer->getWidth()-20,framebuffer->getHeight()-20); + //cam.CenterX = (tmpbm.getWidth()/2+80); + //cam.CenterY = (tmpbm.getHeight()/2+80); + cam.AspectRatio = 1.0;//cam.frameBuffer->getWidth()* 3.0/4.0 / (double)cam.frameBuffer->getHeight(); + cam.X = cam.Y = 0.0; + cam.Z = -200.0; + cam.WantZBuffer=true; + cam.SetTarget(0,0,0); + + + + static pl_Light light; + light.Set(PL_LIGHT_POINT,500.0,0,-900.0,1.3f,0.5f,0.5f,1000.0); + static pl_Light light2; + light2.Set(PL_LIGHT_POINT,-500.0,0,-700.0,0.0f,1.0f,0.5f,1000.0); + cam.ClipBack=220.0; + + cam.Begin(&tmpbm); + cam.RenderLight(&light); + cam.RenderLight(&light2); + cam.RenderObject(obj); + cam.SortToCurrent(); + cam.RenderObject(obj2); + cam.End(); + + char buf[512]; + sprintf(buf,"tri: %d->%d->%d, pix=%.0f", + cam.RenderTrisIn, + cam.RenderTrisCulled, + cam.RenderTrisOut,cam.RenderPixelsOut); + LICE_DrawText(framebuffer,0,10,buf,LICE_RGBA(255,255,255,255),1.0f,0); + + } + break; + case 15: + case 0: + { + double a=.51;//GetTickCount()/1000.0; + + double scale=(1.1+sin(a)*0.3); + + if (0) // weirdness + { + LICE_RotatedBlit(framebuffer,framebuffer,0,0,r.right,r.bottom,0+sin(a*0.3)*16.0,0+sin(a*0.21)*16.0,r.right,r.bottom,cos(a*0.5)*0.13,false,254/255.0,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + } + else // artifact-free mode + { + LICE_MemBitmap framebuffer_back; + + LICE_Copy(&framebuffer_back,framebuffer); + LICE_RotatedBlit(framebuffer,&framebuffer_back,0,0,r.right,r.bottom,0+sin(a*0.3)*16.0,0+sin(a*0.21)*16.0,r.right,r.bottom,cos(a*0.5)*0.13,false,1.0,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + timingEnter(0); + LICE_ScaledBlit(framebuffer,&framebuffer_back,0,0,r.right,r.bottom,-200,-200,3000,3000,0.1,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + timingLeave(0); + timingEnter(1); + LICE_ScaledBlit(framebuffer,&framebuffer_back,0,0,r.right,r.bottom,0,0,r.right/2,r.bottom/2,0.1,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + timingLeave(1); + } + //LICE_Clear(framebuffer,0); + if (bmp) LICE_RotatedBlit(framebuffer,bmp,r.right*scale,r.bottom*scale,r.right*(1.0-scale*2.0),r.bottom*(1.0-scale*2.0),0,0,bmp->getWidth(),bmp->getHeight(),cos(a*0.3)*13.0,false,0.3,LICE_BLIT_MODE_ADD|LICE_BLIT_USE_ALPHA|LICE_BLIT_FILTER_BILINEAR); + + if (m_effect==15) + { + LICE_MultiplyAddRect(framebuffer,0,0,framebuffer->getWidth(),framebuffer->getHeight(),0.9,0.9,-0.3,1, + 3,2,200,0); + } + + +#ifdef FFMPEG_TEST + +#if 0 + //ffmpeg encoding test + if(!m_encoder) m_encoder = new WDL_VideoEncode("flv", framebuffer->getWidth(),framebuffer->getHeight(), 25, 1256, NULL, 44100, 1, 128); + if(m_encoder->isInited()) + { + //set the alpha bit to 0xff + //LICE_FillRect(framebuffer, 0, 0, framebuffer->getWidth(), framebuffer->getHeight(), LICE_RGBA(0,0,0,255), 1.0f, LICE_BLIT_MODE_ADD); + m_encoder->encodeVideo(framebuffer->getBits()); + + static short audiodata[2000]={0,}; + static int initaudio=0; + if(!initaudio) + { + float t = 0; + for(int j=0;j<2000;j++) + { + audiodata[j] = (int)(sin(t) * 10000); + t += 2 * M_PI * 440.0 / 44100; + } + initaudio = 1; + } + m_encoder->encodeAudio(audiodata, 44100/25); + + static WDL_HeapBuf h; + h.Resize(256*1024); + unsigned char *p = (unsigned char *)h.Get(); + int s = m_encoder->getBytes(p, 256*1024); + if(s) + { + FILE *fh = fopen("c:\\temp\\out.flv", "ab"); + fwrite(p, s, 1, fh); + fclose(fh); + } + } +#else + //ffmpeg decoding test + if(!m_decoder) m_decoder = new WDL_VideoDecode("c:\\test.avi"); + if(m_decoder->isInited()) + { + static LICE_IBitmap *m_tmpframe; + static double t = 0; + if(!m_tmpframe) + { + m_tmpframe = new LICE_MemBitmap(framebuffer->getWidth(), framebuffer->getHeight()); + } + m_decoder->GetVideoFrameAtTime(m_tmpframe, t, NULL, &t, true); + t+=0.0001; + LICE_Blit(framebuffer, m_tmpframe, 0, 0, 0, 0, m_tmpframe->getWidth(), m_tmpframe->getHeight(), 1.0f, 0); + } +#endif + +#endif + + + } + break; + case 1: + if (rand()%6==0) + LICE_Blit(framebuffer,bmp,x,y,NULL,-1.4,LICE_BLIT_MODE_ADD|LICE_BLIT_USE_ALPHA); + else + LICE_Blit(framebuffer,bmp,x,y,NULL,0.6,LICE_BLIT_MODE_COPY|LICE_BLIT_USE_ALPHA); + break; + case 2: + { + LICE_Clear(framebuffer,0); + double a=GetTickCount()/1000.0; + + double scale=(1.1+sin(a)*0.3); + if (bmp) LICE_RotatedBlit(framebuffer,bmp,r.right*scale,r.bottom*scale,r.right*(1.0-scale*2.0),r.bottom*(1.0-scale*2.0),0,0,bmp->getWidth(),bmp->getHeight(),cos(a*0.3)*13.0,false,1.0,LICE_BLIT_MODE_ADD|LICE_BLIT_USE_ALPHA|LICE_BLIT_FILTER_BILINEAR,0.0,-bmp->getHeight()/2); + } + break; + case 3: + { + LICE_Clear(framebuffer,LICE_RGBA(128,128,128,128)); + static double a; + a+=0.003; + int xsize=sin(a*1.1)*r.right*10.5; + int ysize=sin(a*1.7)*r.bottom*10.5; + int xp = sin(a*0.3+1515851)*r.right*0.5; + int yp = sin(a*0.3+15853)*r.bottom*0.5; + + if (bmp) + { +// if (rand()%3==0) + // LICE_ScaledBlit(framebuffer,bmp,r.right/2-xsize/2,r.bottom/2-ysize/2,xsize,ysize,0.0,0.0,bmp->getWidth(),bmp->getHeight(),-0.7,LICE_BLIT_USE_ALPHA|LICE_BLIT_MODE_ADD|LICE_BLIT_FILTER_BILINEAR); + /// else + LICE_ScaledBlit(framebuffer,bmp,xp + r.right/2-xsize/2,yp + r.bottom/2-ysize/2,xsize,ysize,-300,-300,600+bmp->getWidth(),600+bmp->getHeight(),1,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + } + } + break; + case 4: + case 9: + + { + static double a; + a+=0.003; + + LICE_GradRect(framebuffer,0,0,framebuffer->getWidth(),framebuffer->getHeight(), + 0.5*sin(a*14.0),0.5*cos(a*2.0+1.3),0.5*sin(a*4.0),1.0, + (cos(a*37.0))/framebuffer->getWidth()*0.5,(sin(a*17.0))/framebuffer->getWidth()*0.5,(cos(a*7.0))/framebuffer->getWidth()*0.5,0, + (sin(a*12.0))/framebuffer->getHeight()*0.5,(cos(a*4.0))/framebuffer->getHeight()*0.5,(cos(a*3.0))/framebuffer->getHeight()*0.5,0, + LICE_BLIT_MODE_COPY); + + + if (m_effect==9) + { + /* LOGFONT lf={ + 140,0,0,0,FW_NORMAL,FALSE,FALSE,FALSE,DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH, + "Times New Roman" + }; + HFONT font=CreateFontIndirect(&lf); + + */ + + + LICE_SysBitmap bm(60,60); + LICE_Clear(&bm,LICE_RGBA(0,0,0,0)); + SetTextColor(bm.getDC(),RGB(255,255,255)); + SetBkMode(bm.getDC(),TRANSPARENT); + // HGDIOBJ of=SelectObject(bm.getDC(),font); + RECT r={0,0,bm.getWidth(),bm.getHeight()}; + DrawText(bm.getDC(),"LICE",-1,&r,DT_LEFT|DT_TOP|DT_SINGLELINE); + // SelectObject(bm.getDC(),of); + // DeleteObject(font); + + LICE_Blit(&bm,&bm,0,0,NULL,1.0,LICE_BLIT_MODE_CHANCOPY|LICE_PIXEL_R|(LICE_PIXEL_A<<2)); + + int bmw=bm.getWidth(); + int bmh=bm.getHeight(); + LICE_FillRect(framebuffer,framebuffer->getWidth()/2,framebuffer->getHeight()/2,bmh,bmw,0,0.5,LICE_BLIT_MODE_COPY); + LICE_RotatedBlit(framebuffer,&bm, + framebuffer->getWidth()/2,framebuffer->getHeight()/2, + bmh,bmw,0,0,bmw,bmh, + 3.14159*0.5,false,.4,LICE_BLIT_MODE_COPY|LICE_BLIT_USE_ALPHA|LICE_BLIT_FILTER_BILINEAR,-bm.getWidth()/4,-bm.getHeight()/4); + } + + break; + } + case 5: + if(m_doeff) + { + LICE_TexGen_Marble(framebuffer, NULL, 1, 1, 1, 1); + } + break; + case 6: + if(m_doeff) + { + LICE_TexGen_Noise(framebuffer, NULL, 0.9, 0.3, 0.6, 6.0f, NOISE_MODE_WOOD, 2); + } + break; + case 7: + if(m_doeff) + { + LICE_TexGen_Noise(framebuffer, NULL, 1,1,1, 8.0f, NOISE_MODE_NORMAL, 8); + } + break; + case 8: + if(m_doeff) + { + LICE_TexGen_CircNoise(framebuffer, NULL, 0.5f,0.5f,0.5f, 12.0f, 0.1f, 32); + } + break; + case 10: + { + int x; + static double a; + double sc=sin(a)*0.24; + a+=0.03; + for (x = 0; x < 10000; x ++) + LICE_PutPixel(framebuffer,rand()%framebuffer->getWidth(),rand()%framebuffer->getHeight(),LICE_RGBA(255,255,255,255),sc,LICE_BLIT_MODE_ADD); + } + break; + case 11: + //line test + { + + LICE_pixel goodCol=LICE_RGBA(192,0,192,64); + LICE_Clear(framebuffer,goodCol); + int subx=30,suby=30,subw=framebuffer->getWidth()-60,subh=framebuffer->getHeight()-60; + LICE_SubBitmap bm(framebuffer,subx,suby,subw,subh); + LICE_Clear(&bm,LICE_RGBA(80,80,80,255)); + + int n; + int w = framebuffer->getWidth(), h = framebuffer->getHeight(); + for(n=0;n<1000;n++) + { + LICE_FLine(&bm, rand()%(w*3/2)-w/4, rand()%(h*3/2)-h/4, rand()%(w*3/2)-w/4, rand()%(h*3/2)-h/4, LICE_RGBA(rand()%255,rand()%255,rand()%255,255)); + } + int y; + if (0) for(y=0;y=subx+subw||y>=suby+subh) + { + if (LICE_GetPixel(framebuffer,x,y)!=goodCol) + { + LICE_Clear(framebuffer,LICE_RGBA(255,255,255,255)); + y=h; + break; + } + } + } + } + + // LICE_Line(framebuffer, rand()%(w*3/2)-w/4, rand()%(h*3/2)-h/4, rand()%(w*3/2)-w/4, rand()%(h*3/2)-h/4, LICE_RGBA(rand()%255,rand()%255,rand()%255,255)); + } + break; + case 12: + //lice draw text test + { + static double a; + a+=0.001; + LICE_DrawText(framebuffer,0.5*(1+sin(a))*(framebuffer->getWidth()-30),0.5*(1+sin(a*7.0+1.3))*(framebuffer->getHeight()-16),"LICE RULEZ",LICE_RGBA(255,0,0,0),sin(a*0.7),LICE_BLIT_MODE_ADD); + } + break; + case 13: + //icon loading test + { + LICE_Clear(framebuffer, LICE_RGBA(255,255,255,255)); + LICE_Blit(framebuffer,icon,0,0,NULL,1.0f,LICE_BLIT_MODE_COPY|LICE_BLIT_USE_ALPHA); + } + break; + case 14: + // circles/arcs + { + int w = framebuffer->getWidth(), h = framebuffer->getHeight(); + const double _PI = acos(-1.0); + static int m_init, m_x, m_y; + if (!m_init) { + m_init = true; + m_x = w/2; m_y = h/2; + } + int r = rand()%w; + float alpha = 1.0f; //(float) r / (float) w; + float aLo = 2*_PI*rand()/RAND_MAX; + float aHi = 2*_PI*rand()/RAND_MAX; + + //LICE_Clear(framebuffer, LICE_RGBA(0,0,0,0)); + LICE_Arc(framebuffer, m_x, m_y, r, aLo, aHi, LICE_RGBA(rand()%255,rand()%255,rand()%255,255),alpha); + //LICE_Circle(framebuffer, m_x, m_y, r, LICE_RGBA(rand()%255,rand()%255,rand()%255,255)); + } + break; + case 16: + { + int sw=framebuffer->getWidth(); + int sh=framebuffer->getHeight(); + + LICE_MemBitmap framebuffer_back; + { + static double a; + a+=0.003; + + static int turd; + if ((turd++&511) < 12) + LICE_GradRect(framebuffer,sw/4,sh/4,sw/2,sh/2, + 0.5*sin(a*14.0),0.5*cos(a*2.0+1.3),0.5*sin(a*4.0),0.1, + (cos(a*37.0))/framebuffer->getWidth()*0.5,(sin(a*17.0))/framebuffer->getWidth()*0.5,(cos(a*7.0))/framebuffer->getWidth()*0.5,0, + (sin(a*12.0))/framebuffer->getHeight()*0.5,(cos(a*4.0))/framebuffer->getHeight()*0.5,(cos(a*3.0))/framebuffer->getHeight()*0.5,0, + LICE_BLIT_MODE_ADD); + } + //LICE_TexGen_Marble(framebuffer, NULL, 1, 1, 1, 1); + LICE_Copy(&framebuffer_back,framebuffer); + + + const int divw=10; + const int divh=5; + float pts[2*divw*divh]; + static float angs[2*divw*divh]; + static float dangs[2*divw*divh]; + static int turd; + if (!turd) + { + turd++; + int a; + for (a = 0; a < 2*divw*divh; a ++) + { + dangs[a]=((rand()%1000)-500)*0.0001; + angs[a]=((rand()%1000)-500)*0.1; + } + } + int x,y; + for (y=0;ygetWidth(), + framebuffer->getHeight(),pts,divw,divh,0.8,LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR); + } + + break; + case 19: + //SVG loading + break; + } + + if(jpg) + { + LICE_ScaledBlit(framebuffer,jpg,0,0,framebuffer->getWidth(),framebuffer->getHeight(),0,0,jpg->getWidth(),jpg->getHeight(),0.5,LICE_BLIT_MODE_COPY); + } + t2 = gettm()-t2; + + m_frame_cnt++; + + double sec=(GetTickCount()-m_start_time)*0.001; + //if (sec>0.0001) + if (g_status[0]) + { + LICE_DrawText(framebuffer,1,1,g_status,LICE_RGBA(0,0,0,0),1,LICE_BLIT_MODE_COPY); + LICE_DrawText(framebuffer,0,0,g_status,LICE_RGBA(255,255,255,0),1,LICE_BLIT_MODE_COPY); + } + + m_doeff = 0; + + double t1=gettm(); + + BitBlt(dc,r.left,r.top,framebuffer->getWidth(),framebuffer->getHeight(),framebuffer->getDC(),0,0,SRCCOPY); + // bmp->blitToDC(dc, NULL, 0, 0); + t1 = gettm()-t1; + + static double ac,stt,ac2; + + if (!frame_cnt++) + { + ac=ac2=0; + stt=gettm(); + } + ac+=t1; + ac2+=t2; + sprintf(g_status,"blit = %f/%f, %f/%f %dx%d @ %ffps\n",t1,t2,ac/frame_cnt,ac2/frame_cnt,framebuffer->getWidth(),framebuffer->getHeight(),frame_cnt/(gettm()-stt)); + +#if 0 + if (GetAsyncKeyState(VK_SHIFT)&0x8000) + if (GetAsyncKeyState(VK_MENU)&0x8000) + if (GetAsyncKeyState(VK_CONTROL)&0x8000) + { + LICE_WritePNG("/tmp/blah.png",framebuffer,false); + LICE_WriteJPG("/tmp/blah.jpg",framebuffer); + } +#endif +} + + +// this is only used on OS X since it's way faster there +LRESULT WINAPI testRenderDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg==WM_PAINT) + { + PAINTSTRUCT ps; + + HDC dc = BeginPaint(hwndDlg, &ps); + DoPaint(hwndDlg,dc); + EndPaint(hwndDlg, &ps); + return 0; + } + if (uMsg == WM_LBUTTONDOWN) + { + m_cap=true; + SetCapture(hwndDlg); + ShowCursor(FALSE); + } + else if (uMsg == WM_LBUTTONUP||uMsg==WM_CAPTURECHANGED) + { + m_cap=false; + ShowCursor(TRUE); + if (uMsg==WM_LBUTTONUP)ReleaseCapture(); + } + + return DefWindowProc(hwndDlg,uMsg,wParam,lParam); +} + +WDL_DLGRET WINAPI dlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == WM_LBUTTONDOWN) + { + m_cap=true; + SetCapture(hwndDlg); + ShowCursor(FALSE); + } + else if (uMsg == WM_LBUTTONUP||uMsg==WM_CAPTURECHANGED) + { + m_cap=false; + ShowCursor(TRUE); + if (uMsg==WM_LBUTTONUP)ReleaseCapture(); + } + + switch(uMsg) + { + case WM_INITDIALOG: + + framebuffer = new LICE_SysBitmap(0,0); + + //jpg=LICE_LoadJPG("C:/turds.jpg"); + +#ifdef _WIN32 + bmp = LICE_LoadPNGFromResource(g_hInstance, MAKEINTRESOURCE(IDC_PNG1)); + icon = LICE_LoadIconFromResource(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), 0); +#else + bmp = LICE_LoadPNGFromNamedResource("image.png"); + + + // uncomment if you want to try GL blits: + // SWELL_SetViewGL(GetDlgItem(hwndDlg,IDC_RECT),true); + SendMessage(hwndDlg,WM_SIZE,0,0); +#endif + + SetTimer(hwndDlg,1,1,NULL); + { + int x; + for (x = 0; x < NUM_EFFECTS; x ++) + { + char buf[512]; + wsprintf(buf,"Effect %d - %s",x+1,effect_names[x]); + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)buf); + } + SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_SETCURSEL,m_effect,0); + + m_start_time=GetTickCount(); + m_frame_cnt=0; + } + return 0; + case WM_DESTROY: + + + delete icon; + delete bmp; + delete framebuffer; + return 0; + +#ifdef _WIN32 + case WM_TIMER: + InvalidateRect(hwndDlg,NULL,FALSE); + return 0; + case WM_PAINT: + { + PAINTSTRUCT ps; + + HDC dc = BeginPaint(hwndDlg, &ps); + + DoPaint(hwndDlg,dc); + EndPaint(hwndDlg,&ps); + } + break; +#else + case WM_SIZE: + { + RECT r; + GetClientRect(hwndDlg,&r); + r.top+=40; + SetWindowPos(GetDlgItem(hwndDlg,IDC_RECT),NULL,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOACTIVATE); + } + return 0; + case WM_TIMER: +#if 1 + InvalidateRect(GetDlgItem(hwndDlg,IDC_RECT),NULL,FALSE); +#else + { + HWND h = GetDlgItem(hwndDlg,IDC_RECT); + HDC dc = GetWindowDC(h); + DoPaint(hwndDlg,dc); + ReleaseDC(h,dc); + SWELL_FlushWindow(h); + } +#endif + return 0; +#endif + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDC_COMBO1: + m_effect = SendDlgItemMessage(hwndDlg,IDC_COMBO1,CB_GETCURSEL,0,0); + m_doeff=1; + m_start_time=GetTickCount(); + m_frame_cnt=0; + break; + case IDCANCEL: +#ifndef __APPLE__ + EndDialog(hwndDlg, 0); +#else + DestroyWindow(hwndDlg); // on mac we run modeless +#endif + break; + } + break; + } + return 0; +} + +#ifdef _WIN32 +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nShowCmd) +{ + + timingInit(); + g_hInstance=hInstance; + DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, dlgProc); + + timingPrint(); + + return 0; +} +#else + +static HWND ccontrolCreator(HWND parent, const char *cname, int idx, const char *classname, int style, int x, int y, int w, int h) +{ + if (!stricmp(classname,"TestRenderingClass")) + { + HWND hw=CreateDialog(NULL,0,parent,(DLGPROC)testRenderDialogProc); + SetWindowLong(hw,GWL_ID,idx); + SetWindowPos(hw,HWND_TOP,x,y,w,h,SWP_NOZORDER|SWP_NOACTIVATE); + ShowWindow(hw,SW_SHOWNA); + return hw; + } + return 0; +} + +#include "../../swell/swell-dlggen.h" + +// define our dialog box resource! + +SWELL_DEFINE_DIALOG_RESOURCE_BEGIN(IDD_DIALOG1,SWELL_DLG_WS_RESIZABLE|SWELL_DLG_WS_FLIPPED,"LICE Test",400,300,1.8) +BEGIN +CONTROL "",IDC_RECT,"TestRenderingClass",0,7,23,384,239 // we arae creating a custom control here because it will be opaque and therefor a LOT faster drawing +COMBOBOX IDC_COMBO1,7,7,181,170,CBS_DROPDOWNLIST | WS_VSCROLL | +WS_TABSTOP + +END +SWELL_DEFINE_DIALOG_RESOURCE_END(IDD_DIALOG1) + +#if !defined(__APPLE__) +int main(int argc, char **argv) +{ + SWELL_initargs(&argc,&argv); + SWELL_Internal_PostMessage_Init(); + SWELL_ExtendedAPI("APPNAME",(void*)"LICE test"); + SWELL_RegisterCustomControlCreator(ccontrolCreator); + //SWELL_ExtendedAPI("INIFILE",(void*)"path/to/ini/file.ini"); + //SWELL_ExtendedAPI("FONTPANGRAM",(void*)"LICE test thingy lbah akbzfshauoh01384u1023"); + DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, dlgProc); + + return 0; +} + +INT_PTR SWELLAppMain(int msg, INT_PTR parm1, INT_PTR parm2) +{ + return 0; +} +#endif +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.ico b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.ico new file mode 100644 index 0000000..78c2e61 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.ico differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.m b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.m new file mode 100644 index 0000000..90b83c9 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/main.m @@ -0,0 +1,14 @@ +// +// main.m +// test +// +// Created by Justin Frankel on 11/9/07. +// Copyright __MyCompanyName__ 2007. All rights reserved. +// + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **) argv); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/resource.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/resource.h new file mode 100644 index 0000000..ffc3f78 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/resource.h @@ -0,0 +1,27 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by test.rc +// +#define IDD_DIALOG1 101 +#define IDC_PNG1 102 +#define IDI_MAIN 103 +#define IDC_BUTTON1 1000 +#define IDC_RECT 1001 +#define IDC_BUTTON2 1002 +#define IDC_COMBO1 1002 +#define IDC_BUTTON3 1003 +#define IDC_BUTTON4 1004 +#define IDC_BUTTON5 1005 +#define IDC_BUTTON6 1006 +#define IDC_BUTTON7 1007 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.rc b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.rc new file mode 100644 index 0000000..7c75d52 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.rc @@ -0,0 +1,107 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 398, 270 +STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "LICE Test App" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "",IDC_RECT,"Static",SS_BLACKRECT | NOT WS_VISIBLE | + WS_DISABLED,7,23,384,239 + COMBOBOX IDC_COMBO1,7,7,181,170,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// PNG +// + +IDC_PNG1 PNG DISCARDABLE "image.png" +IDI_MAIN ICON DISCARDABLE "main.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 391 + TOPMARGIN, 7 + BOTTOMMARGIN, 263 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.sln b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.sln new file mode 100644 index 0000000..73e1b06 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.30110.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test.vcxproj", "{58161B74-2237-4F69-B7AA-34106CD68E71}" +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 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Debug|Win32.ActiveCfg = Debug|Win32 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Debug|Win32.Build.0 = Debug|Win32 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Debug|x64.ActiveCfg = Debug|x64 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Debug|x64.Build.0 = Debug|x64 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Release|Win32.ActiveCfg = Release|Win32 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Release|Win32.Build.0 = Release|Win32 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Release|x64.ActiveCfg = Release|x64 + {58161B74-2237-4F69-B7AA-34106CD68E71}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.vcxproj b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.vcxproj new file mode 100644 index 0000000..7379b17 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.vcxproj @@ -0,0 +1,294 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {58161B74-2237-4F69-B7AA-34106CD68E71} + Win32Proj + test + + + + Application + true + v120 + MultiByte + + + Application + true + v120 + MultiByte + + + Application + false + v120 + true + MultiByte + + + Application + false + v120 + true + MultiByte + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\ + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;_DEBUG;_WINDOWS;PNG_WRITE_SUPPORTED;%(PreprocessorDefinitions) + true + false + 4244;4018;4800 + + + Windows + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;_DEBUG;_WINDOWS;PNG_WRITE_SUPPORTED;%(PreprocessorDefinitions) + true + false + 4244;4267;4018;4800 + + + Windows + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;NDEBUG;_WINDOWS;PNG_WRITE_SUPPORTED;%(PreprocessorDefinitions) + true + MultiThreaded + 4244;4018;4800 + + + Windows + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;NDEBUG;_WINDOWS;PNG_WRITE_SUPPORTED;%(PreprocessorDefinitions) + true + MultiThreaded + 4244;4267;4018;4800 + + + Windows + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies) + + + + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.vcxproj.filters b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.vcxproj.filters new file mode 100644 index 0000000..ab48655 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.vcxproj.filters @@ -0,0 +1,428 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;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 + + + {A2D5C8FE-9DD1-445B-A76A-437EC9B38311} + + + {81B35D42-B8AF-459D-B003-7183AC5D2D90} + + + {14E27EE8-BE4B-4F0E-B3F3-C45510FBBA5F} + + + {34D86512-4698-421C-B3F4-F26086C11248} + + + {CE0DEE75-2CBE-4B35-8BEA-34F0008F915B} + + + {E207BB18-2AED-4AC5-8939-DA29532D9147} + + + {C18123E5-73BD-439C-A7E8-62FF9C421D0F} + + + {ff533619-b842-4840-badb-6df14c222c5a} + + + {aa47edf6-fe12-44bc-8f1b-8c65172bb9c2} + + + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\libpng + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\jpeglib + + + Source Files\giflib + + + Source Files\giflib + + + Source Files\giflib + + + Source Files\giflib + + + Source Files + + + Source Files + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\plush2 + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + Source Files\lice + + + + + Header Files\libpng + + + Header Files\libpng + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\giflib + + + Header Files\giflib + + + Header Files\giflib + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.xcodeproj/project.pbxproj b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.xcodeproj/project.pbxproj new file mode 100644 index 0000000..668e125 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lice/test/test.xcodeproj/project.pbxproj @@ -0,0 +1,874 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 3331EC5A1963208E0092583E /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3331EC591963208E0092583E /* OpenGL.framework */; }; + 33343C070CE4DB5D00FF8CE6 /* swell-dlg.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33343C060CE4DB5D00FF8CE6 /* swell-dlg.mm */; }; + 33343C130CE4DB7000FF8CE6 /* swell-gdi.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33343C0A0CE4DB7000FF8CE6 /* swell-gdi.mm */; }; + 33343C140CE4DB7000FF8CE6 /* swell-ini.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C0B0CE4DB7000FF8CE6 /* swell-ini.cpp */; }; + 33343C150CE4DB7000FF8CE6 /* swell-kb.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33343C0C0CE4DB7000FF8CE6 /* swell-kb.mm */; }; + 33343C160CE4DB7000FF8CE6 /* swell-menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33343C0D0CE4DB7000FF8CE6 /* swell-menu.mm */; }; + 33343C170CE4DB7000FF8CE6 /* swell-miscdlg.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33343C0F0CE4DB7000FF8CE6 /* swell-miscdlg.mm */; }; + 33343C180CE4DB7000FF8CE6 /* swell-wnd.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33343C100CE4DB7000FF8CE6 /* swell-wnd.mm */; }; + 33343C190CE4DB7000FF8CE6 /* swell.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C110CE4DB7000FF8CE6 /* swell.cpp */; }; + 33343C210CE4DB9E00FF8CE6 /* lice_arc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C200CE4DB9E00FF8CE6 /* lice_arc.cpp */; }; + 33343C2D0CE4DBBB00FF8CE6 /* lice_gif.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C240CE4DBBB00FF8CE6 /* lice_gif.cpp */; }; + 33343C2E0CE4DBBB00FF8CE6 /* lice_jpg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C250CE4DBBB00FF8CE6 /* lice_jpg.cpp */; }; + 33343C2F0CE4DBBB00FF8CE6 /* lice_line.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C260CE4DBBB00FF8CE6 /* lice_line.cpp */; }; + 33343C300CE4DBBB00FF8CE6 /* lice_png.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C270CE4DBBB00FF8CE6 /* lice_png.cpp */; }; + 33343C310CE4DBBB00FF8CE6 /* lice_texgen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C280CE4DBBB00FF8CE6 /* lice_texgen.cpp */; }; + 33343C320CE4DBBB00FF8CE6 /* lice_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C290CE4DBBB00FF8CE6 /* lice_text.cpp */; }; + 33343C330CE4DBBB00FF8CE6 /* lice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33343C2A0CE4DBBB00FF8CE6 /* lice.cpp */; }; + 33343C3C0CE4DBD200FF8CE6 /* dgif_lib.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C350CE4DBD200FF8CE6 /* dgif_lib.c */; settings = {COMPILER_FLAGS = "-D HAVE_CONFIG_H"; }; }; + 33343C3D0CE4DBD200FF8CE6 /* egif_lib.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C360CE4DBD200FF8CE6 /* egif_lib.c */; settings = {COMPILER_FLAGS = "-D HAVE_CONFIG_H"; }; }; + 33343C3E0CE4DBD200FF8CE6 /* gif_hash.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C370CE4DBD200FF8CE6 /* gif_hash.c */; settings = {COMPILER_FLAGS = "-D HAVE_CONFIG_H"; }; }; + 33343C3F0CE4DBD200FF8CE6 /* gifalloc.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C3B0CE4DBD200FF8CE6 /* gifalloc.c */; settings = {COMPILER_FLAGS = "-D HAVE_CONFIG_H"; }; }; + 33343C790CE4DBE200FF8CE6 /* jcapimin.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C400CE4DBE200FF8CE6 /* jcapimin.c */; }; + 33343C7A0CE4DBE200FF8CE6 /* jcapistd.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C410CE4DBE200FF8CE6 /* jcapistd.c */; }; + 33343C7B0CE4DBE200FF8CE6 /* jccoefct.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C420CE4DBE200FF8CE6 /* jccoefct.c */; }; + 33343C7C0CE4DBE200FF8CE6 /* jccolor.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C430CE4DBE200FF8CE6 /* jccolor.c */; }; + 33343C7D0CE4DBE200FF8CE6 /* jcdctmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C440CE4DBE200FF8CE6 /* jcdctmgr.c */; }; + 33343C7E0CE4DBE200FF8CE6 /* jchuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C450CE4DBE200FF8CE6 /* jchuff.c */; }; + 33343C7F0CE4DBE200FF8CE6 /* jcinit.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C470CE4DBE200FF8CE6 /* jcinit.c */; }; + 33343C800CE4DBE200FF8CE6 /* jcmainct.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C480CE4DBE200FF8CE6 /* jcmainct.c */; }; + 33343C810CE4DBE200FF8CE6 /* jcmarker.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C490CE4DBE200FF8CE6 /* jcmarker.c */; }; + 33343C820CE4DBE200FF8CE6 /* jcmaster.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C4A0CE4DBE200FF8CE6 /* jcmaster.c */; }; + 33343C830CE4DBE200FF8CE6 /* jcomapi.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C4B0CE4DBE200FF8CE6 /* jcomapi.c */; }; + 33343C840CE4DBE200FF8CE6 /* jcparam.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C4D0CE4DBE200FF8CE6 /* jcparam.c */; }; + 33343C850CE4DBE200FF8CE6 /* jcphuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C4E0CE4DBE200FF8CE6 /* jcphuff.c */; }; + 33343C860CE4DBE200FF8CE6 /* jcprepct.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C4F0CE4DBE200FF8CE6 /* jcprepct.c */; }; + 33343C870CE4DBE200FF8CE6 /* jcsample.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C500CE4DBE200FF8CE6 /* jcsample.c */; }; + 33343C880CE4DBE200FF8CE6 /* jctrans.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C510CE4DBE200FF8CE6 /* jctrans.c */; }; + 33343C890CE4DBE200FF8CE6 /* jdapimin.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C520CE4DBE200FF8CE6 /* jdapimin.c */; }; + 33343C8A0CE4DBE200FF8CE6 /* jdapistd.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C530CE4DBE200FF8CE6 /* jdapistd.c */; }; + 33343C8B0CE4DBE200FF8CE6 /* jdatadst.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C540CE4DBE200FF8CE6 /* jdatadst.c */; }; + 33343C8C0CE4DBE200FF8CE6 /* jdatasrc.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C550CE4DBE200FF8CE6 /* jdatasrc.c */; }; + 33343C8D0CE4DBE200FF8CE6 /* jdcoefct.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C560CE4DBE200FF8CE6 /* jdcoefct.c */; }; + 33343C8E0CE4DBE200FF8CE6 /* jdcolor.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C570CE4DBE200FF8CE6 /* jdcolor.c */; }; + 33343C8F0CE4DBE200FF8CE6 /* jddctmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C590CE4DBE200FF8CE6 /* jddctmgr.c */; }; + 33343C900CE4DBE200FF8CE6 /* jdhuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C5A0CE4DBE200FF8CE6 /* jdhuff.c */; }; + 33343C910CE4DBE200FF8CE6 /* jdinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C5C0CE4DBE200FF8CE6 /* jdinput.c */; }; + 33343C920CE4DBE200FF8CE6 /* jdmainct.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C5D0CE4DBE200FF8CE6 /* jdmainct.c */; }; + 33343C930CE4DBE200FF8CE6 /* jdmarker.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C5E0CE4DBE200FF8CE6 /* jdmarker.c */; }; + 33343C940CE4DBE200FF8CE6 /* jdmaster.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C5F0CE4DBE200FF8CE6 /* jdmaster.c */; }; + 33343C950CE4DBE200FF8CE6 /* jdmerge.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C600CE4DBE200FF8CE6 /* jdmerge.c */; }; + 33343C960CE4DBE200FF8CE6 /* jdphuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C610CE4DBE200FF8CE6 /* jdphuff.c */; }; + 33343C970CE4DBE200FF8CE6 /* jdpostct.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C620CE4DBE200FF8CE6 /* jdpostct.c */; }; + 33343C980CE4DBE200FF8CE6 /* jdsample.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C630CE4DBE200FF8CE6 /* jdsample.c */; }; + 33343C990CE4DBE200FF8CE6 /* jdtrans.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C640CE4DBE200FF8CE6 /* jdtrans.c */; }; + 33343C9A0CE4DBE200FF8CE6 /* jerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C650CE4DBE200FF8CE6 /* jerror.c */; }; + 33343C9B0CE4DBE200FF8CE6 /* jfdctflt.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C670CE4DBE200FF8CE6 /* jfdctflt.c */; }; + 33343C9C0CE4DBE200FF8CE6 /* jfdctfst.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C680CE4DBE200FF8CE6 /* jfdctfst.c */; }; + 33343C9D0CE4DBE200FF8CE6 /* jfdctint.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C690CE4DBE200FF8CE6 /* jfdctint.c */; }; + 33343C9E0CE4DBE200FF8CE6 /* jidctflt.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C6A0CE4DBE200FF8CE6 /* jidctflt.c */; }; + 33343C9F0CE4DBE200FF8CE6 /* jidctfst.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C6B0CE4DBE200FF8CE6 /* jidctfst.c */; }; + 33343CA00CE4DBE200FF8CE6 /* jidctint.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C6C0CE4DBE200FF8CE6 /* jidctint.c */; }; + 33343CA10CE4DBE200FF8CE6 /* jidctred.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C6D0CE4DBE200FF8CE6 /* jidctred.c */; }; + 33343CA20CE4DBE200FF8CE6 /* jmemmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C6F0CE4DBE200FF8CE6 /* jmemmgr.c */; }; + 33343CA30CE4DBE200FF8CE6 /* jmemnobs.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C700CE4DBE200FF8CE6 /* jmemnobs.c */; }; + 33343CA40CE4DBE200FF8CE6 /* jquant1.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C750CE4DBE200FF8CE6 /* jquant1.c */; }; + 33343CA50CE4DBE200FF8CE6 /* jquant2.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C760CE4DBE200FF8CE6 /* jquant2.c */; }; + 33343CA60CE4DBE200FF8CE6 /* jutils.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343C770CE4DBE200FF8CE6 /* jutils.c */; }; + 33343CFE0CE4DCFB00FF8CE6 /* adler32.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CE50CE4DCFB00FF8CE6 /* adler32.c */; }; + 33343CFF0CE4DCFB00FF8CE6 /* compress.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CE60CE4DCFB00FF8CE6 /* compress.c */; }; + 33343D000CE4DCFB00FF8CE6 /* crc32.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CE70CE4DCFB00FF8CE6 /* crc32.c */; }; + 33343D010CE4DCFB00FF8CE6 /* deflate.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CE90CE4DCFB00FF8CE6 /* deflate.c */; }; + 33343D040CE4DCFB00FF8CE6 /* infback.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CED0CE4DCFB00FF8CE6 /* infback.c */; }; + 33343D050CE4DCFB00FF8CE6 /* inffast.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CEE0CE4DCFB00FF8CE6 /* inffast.c */; }; + 33343D060CE4DCFB00FF8CE6 /* inflate.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CF10CE4DCFB00FF8CE6 /* inflate.c */; }; + 33343D070CE4DCFB00FF8CE6 /* inftrees.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CF30CE4DCFB00FF8CE6 /* inftrees.c */; }; + 33343D090CE4DCFB00FF8CE6 /* trees.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CF60CE4DCFB00FF8CE6 /* trees.c */; }; + 33343D0A0CE4DCFB00FF8CE6 /* uncompr.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CF80CE4DCFB00FF8CE6 /* uncompr.c */; }; + 33343D0B0CE4DCFB00FF8CE6 /* zutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343CFC0CE4DCFB00FF8CE6 /* zutil.c */; }; + 33343D210CE4DD1F00FF8CE6 /* png.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D0D0CE4DD1F00FF8CE6 /* png.c */; }; + 33343D220CE4DD1F00FF8CE6 /* pngerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D100CE4DD1F00FF8CE6 /* pngerror.c */; }; + 33343D240CE4DD1F00FF8CE6 /* pngget.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D120CE4DD1F00FF8CE6 /* pngget.c */; }; + 33343D250CE4DD1F00FF8CE6 /* pngmem.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D130CE4DD1F00FF8CE6 /* pngmem.c */; }; + 33343D260CE4DD1F00FF8CE6 /* pngpread.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D140CE4DD1F00FF8CE6 /* pngpread.c */; }; + 33343D270CE4DD1F00FF8CE6 /* pngread.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D150CE4DD1F00FF8CE6 /* pngread.c */; }; + 33343D280CE4DD1F00FF8CE6 /* pngrio.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D160CE4DD1F00FF8CE6 /* pngrio.c */; }; + 33343D290CE4DD1F00FF8CE6 /* pngrtran.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D170CE4DD1F00FF8CE6 /* pngrtran.c */; }; + 33343D2A0CE4DD1F00FF8CE6 /* pngrutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D180CE4DD1F00FF8CE6 /* pngrutil.c */; }; + 33343D2B0CE4DD1F00FF8CE6 /* pngset.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D190CE4DD1F00FF8CE6 /* pngset.c */; }; + 33343D2D0CE4DD1F00FF8CE6 /* pngtrans.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D1B0CE4DD1F00FF8CE6 /* pngtrans.c */; }; + 33343D2F0CE4DD1F00FF8CE6 /* pngwio.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D1D0CE4DD1F00FF8CE6 /* pngwio.c */; }; + 33343D300CE4DD1F00FF8CE6 /* pngwrite.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D1E0CE4DD1F00FF8CE6 /* pngwrite.c */; }; + 33343D310CE4DD1F00FF8CE6 /* pngwtran.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D1F0CE4DD1F00FF8CE6 /* pngwtran.c */; }; + 33343D320CE4DD1F00FF8CE6 /* pngwutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 33343D200CE4DD1F00FF8CE6 /* pngwutil.c */; }; + 334560580CE4E727004D76AE /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 334560570CE4E727004D76AE /* Carbon.framework */; }; + 3350A9D2146B63B500702E1F /* lice_lvg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3350A9D0146B63B500702E1F /* lice_lvg.cpp */; }; + 3350A9D3146B63B500702E1F /* lice_palette.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3350A9D1146B63B500702E1F /* lice_palette.cpp */; }; + 33651CDE10B766A6005FF751 /* swell-misc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33651CDD10B766A6005FF751 /* swell-misc.mm */; }; + 33BD26B70ECF7811000A367A /* pl_cam.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26AC0ECF7811000A367A /* pl_cam.cpp */; }; + 33BD26B80ECF7811000A367A /* pl_make.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26AD0ECF7811000A367A /* pl_make.cpp */; }; + 33BD26B90ECF7811000A367A /* pl_math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26AE0ECF7811000A367A /* pl_math.cpp */; }; + 33BD26BA0ECF7811000A367A /* pl_obj.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26AF0ECF7811000A367A /* pl_obj.cpp */; }; + 33BD26BB0ECF7811000A367A /* pl_putface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26B10ECF7811000A367A /* pl_putface.cpp */; }; + 33BD26BC0ECF7811000A367A /* pl_read_3ds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26B20ECF7811000A367A /* pl_read_3ds.cpp */; }; + 33BD26BD0ECF7811000A367A /* pl_read_cob.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26B30ECF7811000A367A /* pl_read_cob.cpp */; }; + 33BD26BE0ECF7811000A367A /* pl_read_jaw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26B40ECF7811000A367A /* pl_read_jaw.cpp */; }; + 33BD26BF0ECF7811000A367A /* pl_spline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26B50ECF7811000A367A /* pl_spline.cpp */; }; + 33BD26C90ECF7830000A367A /* lice_bmp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26C10ECF7830000A367A /* lice_bmp.cpp */; }; + 33BD26CA0ECF7830000A367A /* lice_ico.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26C20ECF7830000A367A /* lice_ico.cpp */; }; + 33BD26CB0ECF7830000A367A /* lice_image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26C30ECF7830000A367A /* lice_image.cpp */; }; + 33BD26CC0ECF7830000A367A /* lice_jpg_write.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26C40ECF7830000A367A /* lice_jpg_write.cpp */; }; + 33BD26CD0ECF7830000A367A /* lice_pcx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26C50ECF7830000A367A /* lice_pcx.cpp */; }; + 33BD26CF0ECF7830000A367A /* lice_textnew.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26C80ECF7830000A367A /* lice_textnew.cpp */; }; + 33BD26DF0ECF78C3000A367A /* fly.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BD26DE0ECF78C3000A367A /* fly.cpp */; }; + 33DF417F1969C743006C7E88 /* image.png in Resources */ = {isa = PBXBuildFile; fileRef = 33DF417E1969C743006C7E88 /* image.png */; }; + 33E075650CE4DDD3005DDE14 /* Controller.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33E075640CE4DDD3005DDE14 /* Controller.mm */; }; + 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; }; + 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; + 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = ""; }; + 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; + 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 3331EC591963208E0092583E /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; + 33343C060CE4DB5D00FF8CE6 /* swell-dlg.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-dlg.mm"; path = "../../swell/swell-dlg.mm"; sourceTree = SOURCE_ROOT; }; + 33343C080CE4DB7000FF8CE6 /* swell-dlggen.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = "swell-dlggen.h"; path = "../../swell/swell-dlggen.h"; sourceTree = SOURCE_ROOT; }; + 33343C0A0CE4DB7000FF8CE6 /* swell-gdi.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-gdi.mm"; path = "../../swell/swell-gdi.mm"; sourceTree = SOURCE_ROOT; }; + 33343C0B0CE4DB7000FF8CE6 /* swell-ini.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = "swell-ini.cpp"; path = "../../swell/swell-ini.cpp"; sourceTree = SOURCE_ROOT; }; + 33343C0C0CE4DB7000FF8CE6 /* swell-kb.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-kb.mm"; path = "../../swell/swell-kb.mm"; sourceTree = SOURCE_ROOT; }; + 33343C0D0CE4DB7000FF8CE6 /* swell-menu.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-menu.mm"; path = "../../swell/swell-menu.mm"; sourceTree = SOURCE_ROOT; }; + 33343C0E0CE4DB7000FF8CE6 /* swell-menugen.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = "swell-menugen.h"; path = "../../swell/swell-menugen.h"; sourceTree = SOURCE_ROOT; }; + 33343C0F0CE4DB7000FF8CE6 /* swell-miscdlg.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-miscdlg.mm"; path = "../../swell/swell-miscdlg.mm"; sourceTree = SOURCE_ROOT; }; + 33343C100CE4DB7000FF8CE6 /* swell-wnd.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-wnd.mm"; path = "../../swell/swell-wnd.mm"; sourceTree = SOURCE_ROOT; }; + 33343C110CE4DB7000FF8CE6 /* swell.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = swell.cpp; path = ../../swell/swell.cpp; sourceTree = SOURCE_ROOT; }; + 33343C120CE4DB7000FF8CE6 /* swell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = swell.h; path = ../../swell/swell.h; sourceTree = SOURCE_ROOT; }; + 33343C200CE4DB9E00FF8CE6 /* lice_arc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice_arc.cpp; path = ../lice_arc.cpp; sourceTree = SOURCE_ROOT; }; + 33343C230CE4DBBB00FF8CE6 /* lice_combine.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = lice_combine.h; path = ../lice_combine.h; sourceTree = SOURCE_ROOT; }; + 33343C240CE4DBBB00FF8CE6 /* lice_gif.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice_gif.cpp; path = ../lice_gif.cpp; sourceTree = SOURCE_ROOT; }; + 33343C250CE4DBBB00FF8CE6 /* lice_jpg.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice_jpg.cpp; path = ../lice_jpg.cpp; sourceTree = SOURCE_ROOT; }; + 33343C260CE4DBBB00FF8CE6 /* lice_line.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice_line.cpp; path = ../lice_line.cpp; sourceTree = SOURCE_ROOT; }; + 33343C270CE4DBBB00FF8CE6 /* lice_png.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice_png.cpp; path = ../lice_png.cpp; sourceTree = SOURCE_ROOT; }; + 33343C280CE4DBBB00FF8CE6 /* lice_texgen.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice_texgen.cpp; path = ../lice_texgen.cpp; sourceTree = SOURCE_ROOT; }; + 33343C290CE4DBBB00FF8CE6 /* lice_text.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice_text.cpp; path = ../lice_text.cpp; sourceTree = SOURCE_ROOT; }; + 33343C2A0CE4DBBB00FF8CE6 /* lice.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = lice.cpp; path = ../lice.cpp; sourceTree = SOURCE_ROOT; }; + 33343C2B0CE4DBBB00FF8CE6 /* lice.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = lice.h; path = ../lice.h; sourceTree = SOURCE_ROOT; }; + 33343C340CE4DBD200FF8CE6 /* config.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = config.h; path = ../../giflib/config.h; sourceTree = SOURCE_ROOT; }; + 33343C350CE4DBD200FF8CE6 /* dgif_lib.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = dgif_lib.c; path = ../../giflib/dgif_lib.c; sourceTree = SOURCE_ROOT; }; + 33343C360CE4DBD200FF8CE6 /* egif_lib.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = egif_lib.c; path = ../../giflib/egif_lib.c; sourceTree = SOURCE_ROOT; }; + 33343C370CE4DBD200FF8CE6 /* gif_hash.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = gif_hash.c; path = ../../giflib/gif_hash.c; sourceTree = SOURCE_ROOT; }; + 33343C380CE4DBD200FF8CE6 /* gif_hash.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = gif_hash.h; path = ../../giflib/gif_hash.h; sourceTree = SOURCE_ROOT; }; + 33343C390CE4DBD200FF8CE6 /* gif_lib_private.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = gif_lib_private.h; path = ../../giflib/gif_lib_private.h; sourceTree = SOURCE_ROOT; }; + 33343C3A0CE4DBD200FF8CE6 /* gif_lib.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = gif_lib.h; path = ../../giflib/gif_lib.h; sourceTree = SOURCE_ROOT; }; + 33343C3B0CE4DBD200FF8CE6 /* gifalloc.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = gifalloc.c; path = ../../giflib/gifalloc.c; sourceTree = SOURCE_ROOT; }; + 33343C400CE4DBE200FF8CE6 /* jcapimin.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcapimin.c; path = ../../jpeglib/jcapimin.c; sourceTree = SOURCE_ROOT; }; + 33343C410CE4DBE200FF8CE6 /* jcapistd.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcapistd.c; path = ../../jpeglib/jcapistd.c; sourceTree = SOURCE_ROOT; }; + 33343C420CE4DBE200FF8CE6 /* jccoefct.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jccoefct.c; path = ../../jpeglib/jccoefct.c; sourceTree = SOURCE_ROOT; }; + 33343C430CE4DBE200FF8CE6 /* jccolor.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jccolor.c; path = ../../jpeglib/jccolor.c; sourceTree = SOURCE_ROOT; }; + 33343C440CE4DBE200FF8CE6 /* jcdctmgr.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcdctmgr.c; path = ../../jpeglib/jcdctmgr.c; sourceTree = SOURCE_ROOT; }; + 33343C450CE4DBE200FF8CE6 /* jchuff.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jchuff.c; path = ../../jpeglib/jchuff.c; sourceTree = SOURCE_ROOT; }; + 33343C460CE4DBE200FF8CE6 /* jchuff.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jchuff.h; path = ../../jpeglib/jchuff.h; sourceTree = SOURCE_ROOT; }; + 33343C470CE4DBE200FF8CE6 /* jcinit.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcinit.c; path = ../../jpeglib/jcinit.c; sourceTree = SOURCE_ROOT; }; + 33343C480CE4DBE200FF8CE6 /* jcmainct.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcmainct.c; path = ../../jpeglib/jcmainct.c; sourceTree = SOURCE_ROOT; }; + 33343C490CE4DBE200FF8CE6 /* jcmarker.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcmarker.c; path = ../../jpeglib/jcmarker.c; sourceTree = SOURCE_ROOT; }; + 33343C4A0CE4DBE200FF8CE6 /* jcmaster.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcmaster.c; path = ../../jpeglib/jcmaster.c; sourceTree = SOURCE_ROOT; }; + 33343C4B0CE4DBE200FF8CE6 /* jcomapi.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcomapi.c; path = ../../jpeglib/jcomapi.c; sourceTree = SOURCE_ROOT; }; + 33343C4C0CE4DBE200FF8CE6 /* jconfig.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jconfig.h; path = ../../jpeglib/jconfig.h; sourceTree = SOURCE_ROOT; }; + 33343C4D0CE4DBE200FF8CE6 /* jcparam.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcparam.c; path = ../../jpeglib/jcparam.c; sourceTree = SOURCE_ROOT; }; + 33343C4E0CE4DBE200FF8CE6 /* jcphuff.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcphuff.c; path = ../../jpeglib/jcphuff.c; sourceTree = SOURCE_ROOT; }; + 33343C4F0CE4DBE200FF8CE6 /* jcprepct.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcprepct.c; path = ../../jpeglib/jcprepct.c; sourceTree = SOURCE_ROOT; }; + 33343C500CE4DBE200FF8CE6 /* jcsample.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jcsample.c; path = ../../jpeglib/jcsample.c; sourceTree = SOURCE_ROOT; }; + 33343C510CE4DBE200FF8CE6 /* jctrans.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jctrans.c; path = ../../jpeglib/jctrans.c; sourceTree = SOURCE_ROOT; }; + 33343C520CE4DBE200FF8CE6 /* jdapimin.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdapimin.c; path = ../../jpeglib/jdapimin.c; sourceTree = SOURCE_ROOT; }; + 33343C530CE4DBE200FF8CE6 /* jdapistd.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdapistd.c; path = ../../jpeglib/jdapistd.c; sourceTree = SOURCE_ROOT; }; + 33343C540CE4DBE200FF8CE6 /* jdatadst.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdatadst.c; path = ../../jpeglib/jdatadst.c; sourceTree = SOURCE_ROOT; }; + 33343C550CE4DBE200FF8CE6 /* jdatasrc.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdatasrc.c; path = ../../jpeglib/jdatasrc.c; sourceTree = SOURCE_ROOT; }; + 33343C560CE4DBE200FF8CE6 /* jdcoefct.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdcoefct.c; path = ../../jpeglib/jdcoefct.c; sourceTree = SOURCE_ROOT; }; + 33343C570CE4DBE200FF8CE6 /* jdcolor.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdcolor.c; path = ../../jpeglib/jdcolor.c; sourceTree = SOURCE_ROOT; }; + 33343C580CE4DBE200FF8CE6 /* jdct.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jdct.h; path = ../../jpeglib/jdct.h; sourceTree = SOURCE_ROOT; }; + 33343C590CE4DBE200FF8CE6 /* jddctmgr.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jddctmgr.c; path = ../../jpeglib/jddctmgr.c; sourceTree = SOURCE_ROOT; }; + 33343C5A0CE4DBE200FF8CE6 /* jdhuff.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdhuff.c; path = ../../jpeglib/jdhuff.c; sourceTree = SOURCE_ROOT; }; + 33343C5B0CE4DBE200FF8CE6 /* jdhuff.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jdhuff.h; path = ../../jpeglib/jdhuff.h; sourceTree = SOURCE_ROOT; }; + 33343C5C0CE4DBE200FF8CE6 /* jdinput.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdinput.c; path = ../../jpeglib/jdinput.c; sourceTree = SOURCE_ROOT; }; + 33343C5D0CE4DBE200FF8CE6 /* jdmainct.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdmainct.c; path = ../../jpeglib/jdmainct.c; sourceTree = SOURCE_ROOT; }; + 33343C5E0CE4DBE200FF8CE6 /* jdmarker.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdmarker.c; path = ../../jpeglib/jdmarker.c; sourceTree = SOURCE_ROOT; }; + 33343C5F0CE4DBE200FF8CE6 /* jdmaster.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdmaster.c; path = ../../jpeglib/jdmaster.c; sourceTree = SOURCE_ROOT; }; + 33343C600CE4DBE200FF8CE6 /* jdmerge.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdmerge.c; path = ../../jpeglib/jdmerge.c; sourceTree = SOURCE_ROOT; }; + 33343C610CE4DBE200FF8CE6 /* jdphuff.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdphuff.c; path = ../../jpeglib/jdphuff.c; sourceTree = SOURCE_ROOT; }; + 33343C620CE4DBE200FF8CE6 /* jdpostct.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdpostct.c; path = ../../jpeglib/jdpostct.c; sourceTree = SOURCE_ROOT; }; + 33343C630CE4DBE200FF8CE6 /* jdsample.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdsample.c; path = ../../jpeglib/jdsample.c; sourceTree = SOURCE_ROOT; }; + 33343C640CE4DBE200FF8CE6 /* jdtrans.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jdtrans.c; path = ../../jpeglib/jdtrans.c; sourceTree = SOURCE_ROOT; }; + 33343C650CE4DBE200FF8CE6 /* jerror.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jerror.c; path = ../../jpeglib/jerror.c; sourceTree = SOURCE_ROOT; }; + 33343C660CE4DBE200FF8CE6 /* jerror.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jerror.h; path = ../../jpeglib/jerror.h; sourceTree = SOURCE_ROOT; }; + 33343C670CE4DBE200FF8CE6 /* jfdctflt.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jfdctflt.c; path = ../../jpeglib/jfdctflt.c; sourceTree = SOURCE_ROOT; }; + 33343C680CE4DBE200FF8CE6 /* jfdctfst.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jfdctfst.c; path = ../../jpeglib/jfdctfst.c; sourceTree = SOURCE_ROOT; }; + 33343C690CE4DBE200FF8CE6 /* jfdctint.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jfdctint.c; path = ../../jpeglib/jfdctint.c; sourceTree = SOURCE_ROOT; }; + 33343C6A0CE4DBE200FF8CE6 /* jidctflt.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jidctflt.c; path = ../../jpeglib/jidctflt.c; sourceTree = SOURCE_ROOT; }; + 33343C6B0CE4DBE200FF8CE6 /* jidctfst.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jidctfst.c; path = ../../jpeglib/jidctfst.c; sourceTree = SOURCE_ROOT; }; + 33343C6C0CE4DBE200FF8CE6 /* jidctint.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jidctint.c; path = ../../jpeglib/jidctint.c; sourceTree = SOURCE_ROOT; }; + 33343C6D0CE4DBE200FF8CE6 /* jidctred.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jidctred.c; path = ../../jpeglib/jidctred.c; sourceTree = SOURCE_ROOT; }; + 33343C6E0CE4DBE200FF8CE6 /* jinclude.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jinclude.h; path = ../../jpeglib/jinclude.h; sourceTree = SOURCE_ROOT; }; + 33343C6F0CE4DBE200FF8CE6 /* jmemmgr.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jmemmgr.c; path = ../../jpeglib/jmemmgr.c; sourceTree = SOURCE_ROOT; }; + 33343C700CE4DBE200FF8CE6 /* jmemnobs.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jmemnobs.c; path = ../../jpeglib/jmemnobs.c; sourceTree = SOURCE_ROOT; }; + 33343C710CE4DBE200FF8CE6 /* jmemsys.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jmemsys.h; path = ../../jpeglib/jmemsys.h; sourceTree = SOURCE_ROOT; }; + 33343C720CE4DBE200FF8CE6 /* jmorecfg.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jmorecfg.h; path = ../../jpeglib/jmorecfg.h; sourceTree = SOURCE_ROOT; }; + 33343C730CE4DBE200FF8CE6 /* jpegint.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jpegint.h; path = ../../jpeglib/jpegint.h; sourceTree = SOURCE_ROOT; }; + 33343C740CE4DBE200FF8CE6 /* jpeglib.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jpeglib.h; path = ../../jpeglib/jpeglib.h; sourceTree = SOURCE_ROOT; }; + 33343C750CE4DBE200FF8CE6 /* jquant1.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jquant1.c; path = ../../jpeglib/jquant1.c; sourceTree = SOURCE_ROOT; }; + 33343C760CE4DBE200FF8CE6 /* jquant2.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jquant2.c; path = ../../jpeglib/jquant2.c; sourceTree = SOURCE_ROOT; }; + 33343C770CE4DBE200FF8CE6 /* jutils.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = jutils.c; path = ../../jpeglib/jutils.c; sourceTree = SOURCE_ROOT; }; + 33343C780CE4DBE200FF8CE6 /* jversion.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jversion.h; path = ../../jpeglib/jversion.h; sourceTree = SOURCE_ROOT; }; + 33343CE50CE4DCFB00FF8CE6 /* adler32.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = adler32.c; path = ../../zlib/adler32.c; sourceTree = SOURCE_ROOT; }; + 33343CE60CE4DCFB00FF8CE6 /* compress.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = compress.c; path = ../../zlib/compress.c; sourceTree = SOURCE_ROOT; }; + 33343CE70CE4DCFB00FF8CE6 /* crc32.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = crc32.c; path = ../../zlib/crc32.c; sourceTree = SOURCE_ROOT; }; + 33343CE80CE4DCFB00FF8CE6 /* crc32.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = crc32.h; path = ../../zlib/crc32.h; sourceTree = SOURCE_ROOT; }; + 33343CE90CE4DCFB00FF8CE6 /* deflate.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = deflate.c; path = ../../zlib/deflate.c; sourceTree = SOURCE_ROOT; }; + 33343CEA0CE4DCFB00FF8CE6 /* deflate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = deflate.h; path = ../../zlib/deflate.h; sourceTree = SOURCE_ROOT; }; + 33343CED0CE4DCFB00FF8CE6 /* infback.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = infback.c; path = ../../zlib/infback.c; sourceTree = SOURCE_ROOT; }; + 33343CEE0CE4DCFB00FF8CE6 /* inffast.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = inffast.c; path = ../../zlib/inffast.c; sourceTree = SOURCE_ROOT; }; + 33343CEF0CE4DCFB00FF8CE6 /* inffast.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = inffast.h; path = ../../zlib/inffast.h; sourceTree = SOURCE_ROOT; }; + 33343CF00CE4DCFB00FF8CE6 /* inffixed.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = inffixed.h; path = ../../zlib/inffixed.h; sourceTree = SOURCE_ROOT; }; + 33343CF10CE4DCFB00FF8CE6 /* inflate.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = inflate.c; path = ../../zlib/inflate.c; sourceTree = SOURCE_ROOT; }; + 33343CF20CE4DCFB00FF8CE6 /* inflate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = inflate.h; path = ../../zlib/inflate.h; sourceTree = SOURCE_ROOT; }; + 33343CF30CE4DCFB00FF8CE6 /* inftrees.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = inftrees.c; path = ../../zlib/inftrees.c; sourceTree = SOURCE_ROOT; }; + 33343CF40CE4DCFB00FF8CE6 /* inftrees.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = inftrees.h; path = ../../zlib/inftrees.h; sourceTree = SOURCE_ROOT; }; + 33343CF60CE4DCFB00FF8CE6 /* trees.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = trees.c; path = ../../zlib/trees.c; sourceTree = SOURCE_ROOT; }; + 33343CF70CE4DCFB00FF8CE6 /* trees.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = trees.h; path = ../../zlib/trees.h; sourceTree = SOURCE_ROOT; }; + 33343CF80CE4DCFB00FF8CE6 /* uncompr.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = uncompr.c; path = ../../zlib/uncompr.c; sourceTree = SOURCE_ROOT; }; + 33343CF90CE4DCFB00FF8CE6 /* zconf.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = zconf.h; path = ../../zlib/zconf.h; sourceTree = SOURCE_ROOT; }; + 33343CFA0CE4DCFB00FF8CE6 /* zconf.in.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = zconf.in.h; path = ../../zlib/zconf.in.h; sourceTree = SOURCE_ROOT; }; + 33343CFB0CE4DCFB00FF8CE6 /* zlib.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = zlib.h; path = ../../zlib/zlib.h; sourceTree = SOURCE_ROOT; }; + 33343CFC0CE4DCFB00FF8CE6 /* zutil.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = zutil.c; path = ../../zlib/zutil.c; sourceTree = SOURCE_ROOT; }; + 33343CFD0CE4DCFB00FF8CE6 /* zutil.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = zutil.h; path = ../../zlib/zutil.h; sourceTree = SOURCE_ROOT; }; + 33343D0D0CE4DD1F00FF8CE6 /* png.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = png.c; path = ../../libpng/png.c; sourceTree = SOURCE_ROOT; }; + 33343D0E0CE4DD1F00FF8CE6 /* png.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = png.h; path = ../../libpng/png.h; sourceTree = SOURCE_ROOT; }; + 33343D0F0CE4DD1F00FF8CE6 /* pngconf.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = pngconf.h; path = ../../libpng/pngconf.h; sourceTree = SOURCE_ROOT; }; + 33343D100CE4DD1F00FF8CE6 /* pngerror.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngerror.c; path = ../../libpng/pngerror.c; sourceTree = SOURCE_ROOT; }; + 33343D120CE4DD1F00FF8CE6 /* pngget.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngget.c; path = ../../libpng/pngget.c; sourceTree = SOURCE_ROOT; }; + 33343D130CE4DD1F00FF8CE6 /* pngmem.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngmem.c; path = ../../libpng/pngmem.c; sourceTree = SOURCE_ROOT; }; + 33343D140CE4DD1F00FF8CE6 /* pngpread.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngpread.c; path = ../../libpng/pngpread.c; sourceTree = SOURCE_ROOT; }; + 33343D150CE4DD1F00FF8CE6 /* pngread.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngread.c; path = ../../libpng/pngread.c; sourceTree = SOURCE_ROOT; }; + 33343D160CE4DD1F00FF8CE6 /* pngrio.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngrio.c; path = ../../libpng/pngrio.c; sourceTree = SOURCE_ROOT; }; + 33343D170CE4DD1F00FF8CE6 /* pngrtran.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngrtran.c; path = ../../libpng/pngrtran.c; sourceTree = SOURCE_ROOT; }; + 33343D180CE4DD1F00FF8CE6 /* pngrutil.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngrutil.c; path = ../../libpng/pngrutil.c; sourceTree = SOURCE_ROOT; }; + 33343D190CE4DD1F00FF8CE6 /* pngset.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngset.c; path = ../../libpng/pngset.c; sourceTree = SOURCE_ROOT; }; + 33343D1B0CE4DD1F00FF8CE6 /* pngtrans.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngtrans.c; path = ../../libpng/pngtrans.c; sourceTree = SOURCE_ROOT; }; + 33343D1D0CE4DD1F00FF8CE6 /* pngwio.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngwio.c; path = ../../libpng/pngwio.c; sourceTree = SOURCE_ROOT; }; + 33343D1E0CE4DD1F00FF8CE6 /* pngwrite.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngwrite.c; path = ../../libpng/pngwrite.c; sourceTree = SOURCE_ROOT; }; + 33343D1F0CE4DD1F00FF8CE6 /* pngwtran.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngwtran.c; path = ../../libpng/pngwtran.c; sourceTree = SOURCE_ROOT; }; + 33343D200CE4DD1F00FF8CE6 /* pngwutil.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pngwutil.c; path = ../../libpng/pngwutil.c; sourceTree = SOURCE_ROOT; }; + 334560570CE4E727004D76AE /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; + 3350A9D0146B63B500702E1F /* lice_lvg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_lvg.cpp; path = ../lice_lvg.cpp; sourceTree = SOURCE_ROOT; }; + 3350A9D1146B63B500702E1F /* lice_palette.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_palette.cpp; path = ../lice_palette.cpp; sourceTree = SOURCE_ROOT; }; + 33651CDD10B766A6005FF751 /* swell-misc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-misc.mm"; path = "../../swell/swell-misc.mm"; sourceTree = SOURCE_ROOT; }; + 33BD26AC0ECF7811000A367A /* pl_cam.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_cam.cpp; path = ../../plush2/pl_cam.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26AD0ECF7811000A367A /* pl_make.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_make.cpp; path = ../../plush2/pl_make.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26AE0ECF7811000A367A /* pl_math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_math.cpp; path = ../../plush2/pl_math.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26AF0ECF7811000A367A /* pl_obj.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_obj.cpp; path = ../../plush2/pl_obj.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26B00ECF7811000A367A /* pl_pf_tex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pl_pf_tex.h; path = ../../plush2/pl_pf_tex.h; sourceTree = SOURCE_ROOT; }; + 33BD26B10ECF7811000A367A /* pl_putface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_putface.cpp; path = ../../plush2/pl_putface.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26B20ECF7811000A367A /* pl_read_3ds.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_read_3ds.cpp; path = ../../plush2/pl_read_3ds.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26B30ECF7811000A367A /* pl_read_cob.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_read_cob.cpp; path = ../../plush2/pl_read_cob.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26B40ECF7811000A367A /* pl_read_jaw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_read_jaw.cpp; path = ../../plush2/pl_read_jaw.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26B50ECF7811000A367A /* pl_spline.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pl_spline.cpp; path = ../../plush2/pl_spline.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26B60ECF7811000A367A /* plush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = plush.h; path = ../../plush2/plush.h; sourceTree = SOURCE_ROOT; }; + 33BD26C00ECF7830000A367A /* lice_bezier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lice_bezier.h; path = ../lice_bezier.h; sourceTree = SOURCE_ROOT; }; + 33BD26C10ECF7830000A367A /* lice_bmp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_bmp.cpp; path = ../lice_bmp.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26C20ECF7830000A367A /* lice_ico.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_ico.cpp; path = ../lice_ico.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26C30ECF7830000A367A /* lice_image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_image.cpp; path = ../lice_image.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26C40ECF7830000A367A /* lice_jpg_write.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_jpg_write.cpp; path = ../lice_jpg_write.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26C50ECF7830000A367A /* lice_pcx.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_pcx.cpp; path = ../lice_pcx.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26C70ECF7830000A367A /* lice_text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lice_text.h; path = ../lice_text.h; sourceTree = SOURCE_ROOT; }; + 33BD26C80ECF7830000A367A /* lice_textnew.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lice_textnew.cpp; path = ../lice_textnew.cpp; sourceTree = SOURCE_ROOT; }; + 33BD26DE0ECF78C3000A367A /* fly.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fly.cpp; sourceTree = ""; }; + 33DF417E1969C743006C7E88 /* image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = image.png; sourceTree = ""; }; + 33E075630CE4DDD3005DDE14 /* Controller.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Controller.h; sourceTree = ""; }; + 33E075640CE4DDD3005DDE14 /* Controller.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = Controller.mm; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 8D1107320486CEB800E47090 /* test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = test.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8D11072E0486CEB800E47090 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3331EC5A1963208E0092583E /* OpenGL.framework in Frameworks */, + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, + 334560580CE4E727004D76AE /* Carbon.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 33E075630CE4DDD3005DDE14 /* Controller.h */, + 33E075640CE4DDD3005DDE14 /* Controller.mm */, + ); + name = Classes; + sourceTree = ""; + }; + 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { + isa = PBXGroup; + children = ( + 334560570CE4E727004D76AE /* Carbon.framework */, + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, + ); + name = "Linked Frameworks"; + sourceTree = ""; + }; + 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 29B97324FDCFA39411CA2CEA /* AppKit.framework */, + 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, + 29B97325FDCFA39411CA2CEA /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8D1107320486CEB800E47090 /* test.app */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* test */ = { + isa = PBXGroup; + children = ( + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = test; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 33BD26DE0ECF78C3000A367A /* fly.cpp */, + 33BD26AA0ECF77F9000A367A /* plush2 */, + 33343C1B0CE4DB7B00FF8CE6 /* LICE */, + 33343C050CE4DB4A00FF8CE6 /* SWELL */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 33DF417E1969C743006C7E88 /* image.png */, + 8D1107310486CEB800E47090 /* Info.plist */, + 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, + 29B97318FDCFA39411CA2CEA /* MainMenu.nib */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3331EC591963208E0092583E /* OpenGL.framework */, + 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, + 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + 33343C050CE4DB4A00FF8CE6 /* SWELL */ = { + isa = PBXGroup; + children = ( + 33651CDD10B766A6005FF751 /* swell-misc.mm */, + 33343C080CE4DB7000FF8CE6 /* swell-dlggen.h */, + 33343C0A0CE4DB7000FF8CE6 /* swell-gdi.mm */, + 33343C0B0CE4DB7000FF8CE6 /* swell-ini.cpp */, + 33343C0C0CE4DB7000FF8CE6 /* swell-kb.mm */, + 33343C0D0CE4DB7000FF8CE6 /* swell-menu.mm */, + 33343C0E0CE4DB7000FF8CE6 /* swell-menugen.h */, + 33343C0F0CE4DB7000FF8CE6 /* swell-miscdlg.mm */, + 33343C100CE4DB7000FF8CE6 /* swell-wnd.mm */, + 33343C110CE4DB7000FF8CE6 /* swell.cpp */, + 33343C120CE4DB7000FF8CE6 /* swell.h */, + 33343C060CE4DB5D00FF8CE6 /* swell-dlg.mm */, + ); + name = SWELL; + sourceTree = ""; + }; + 33343C1B0CE4DB7B00FF8CE6 /* LICE */ = { + isa = PBXGroup; + children = ( + 3350A9D0146B63B500702E1F /* lice_lvg.cpp */, + 3350A9D1146B63B500702E1F /* lice_palette.cpp */, + 33BD26C00ECF7830000A367A /* lice_bezier.h */, + 33BD26C10ECF7830000A367A /* lice_bmp.cpp */, + 33BD26C20ECF7830000A367A /* lice_ico.cpp */, + 33BD26C30ECF7830000A367A /* lice_image.cpp */, + 33BD26C40ECF7830000A367A /* lice_jpg_write.cpp */, + 33BD26C50ECF7830000A367A /* lice_pcx.cpp */, + 33BD26C70ECF7830000A367A /* lice_text.h */, + 33BD26C80ECF7830000A367A /* lice_textnew.cpp */, + 33343C230CE4DBBB00FF8CE6 /* lice_combine.h */, + 33343C240CE4DBBB00FF8CE6 /* lice_gif.cpp */, + 33343C250CE4DBBB00FF8CE6 /* lice_jpg.cpp */, + 33343C260CE4DBBB00FF8CE6 /* lice_line.cpp */, + 33343C270CE4DBBB00FF8CE6 /* lice_png.cpp */, + 33343C280CE4DBBB00FF8CE6 /* lice_texgen.cpp */, + 33343C290CE4DBBB00FF8CE6 /* lice_text.cpp */, + 33343C2A0CE4DBBB00FF8CE6 /* lice.cpp */, + 33343C2B0CE4DBBB00FF8CE6 /* lice.h */, + 33343C200CE4DB9E00FF8CE6 /* lice_arc.cpp */, + 33343C1F0CE4DB9200FF8CE6 /* giflib */, + 33343C1E0CE4DB8E00FF8CE6 /* JPeglib */, + 33343C1D0CE4DB8A00FF8CE6 /* Zlib */, + 33343C1C0CE4DB8100FF8CE6 /* PNGlib */, + ); + name = LICE; + sourceTree = ""; + }; + 33343C1C0CE4DB8100FF8CE6 /* PNGlib */ = { + isa = PBXGroup; + children = ( + 33343D0D0CE4DD1F00FF8CE6 /* png.c */, + 33343D0E0CE4DD1F00FF8CE6 /* png.h */, + 33343D0F0CE4DD1F00FF8CE6 /* pngconf.h */, + 33343D100CE4DD1F00FF8CE6 /* pngerror.c */, + 33343D120CE4DD1F00FF8CE6 /* pngget.c */, + 33343D130CE4DD1F00FF8CE6 /* pngmem.c */, + 33343D140CE4DD1F00FF8CE6 /* pngpread.c */, + 33343D150CE4DD1F00FF8CE6 /* pngread.c */, + 33343D160CE4DD1F00FF8CE6 /* pngrio.c */, + 33343D170CE4DD1F00FF8CE6 /* pngrtran.c */, + 33343D180CE4DD1F00FF8CE6 /* pngrutil.c */, + 33343D190CE4DD1F00FF8CE6 /* pngset.c */, + 33343D1B0CE4DD1F00FF8CE6 /* pngtrans.c */, + 33343D1D0CE4DD1F00FF8CE6 /* pngwio.c */, + 33343D1E0CE4DD1F00FF8CE6 /* pngwrite.c */, + 33343D1F0CE4DD1F00FF8CE6 /* pngwtran.c */, + 33343D200CE4DD1F00FF8CE6 /* pngwutil.c */, + ); + name = PNGlib; + sourceTree = ""; + }; + 33343C1D0CE4DB8A00FF8CE6 /* Zlib */ = { + isa = PBXGroup; + children = ( + 33343CE50CE4DCFB00FF8CE6 /* adler32.c */, + 33343CE60CE4DCFB00FF8CE6 /* compress.c */, + 33343CE70CE4DCFB00FF8CE6 /* crc32.c */, + 33343CE80CE4DCFB00FF8CE6 /* crc32.h */, + 33343CE90CE4DCFB00FF8CE6 /* deflate.c */, + 33343CEA0CE4DCFB00FF8CE6 /* deflate.h */, + 33343CED0CE4DCFB00FF8CE6 /* infback.c */, + 33343CEE0CE4DCFB00FF8CE6 /* inffast.c */, + 33343CEF0CE4DCFB00FF8CE6 /* inffast.h */, + 33343CF00CE4DCFB00FF8CE6 /* inffixed.h */, + 33343CF10CE4DCFB00FF8CE6 /* inflate.c */, + 33343CF20CE4DCFB00FF8CE6 /* inflate.h */, + 33343CF30CE4DCFB00FF8CE6 /* inftrees.c */, + 33343CF40CE4DCFB00FF8CE6 /* inftrees.h */, + 33343CF60CE4DCFB00FF8CE6 /* trees.c */, + 33343CF70CE4DCFB00FF8CE6 /* trees.h */, + 33343CF80CE4DCFB00FF8CE6 /* uncompr.c */, + 33343CF90CE4DCFB00FF8CE6 /* zconf.h */, + 33343CFA0CE4DCFB00FF8CE6 /* zconf.in.h */, + 33343CFB0CE4DCFB00FF8CE6 /* zlib.h */, + 33343CFC0CE4DCFB00FF8CE6 /* zutil.c */, + 33343CFD0CE4DCFB00FF8CE6 /* zutil.h */, + ); + name = Zlib; + sourceTree = ""; + }; + 33343C1E0CE4DB8E00FF8CE6 /* JPeglib */ = { + isa = PBXGroup; + children = ( + 33343C400CE4DBE200FF8CE6 /* jcapimin.c */, + 33343C410CE4DBE200FF8CE6 /* jcapistd.c */, + 33343C420CE4DBE200FF8CE6 /* jccoefct.c */, + 33343C430CE4DBE200FF8CE6 /* jccolor.c */, + 33343C440CE4DBE200FF8CE6 /* jcdctmgr.c */, + 33343C450CE4DBE200FF8CE6 /* jchuff.c */, + 33343C460CE4DBE200FF8CE6 /* jchuff.h */, + 33343C470CE4DBE200FF8CE6 /* jcinit.c */, + 33343C480CE4DBE200FF8CE6 /* jcmainct.c */, + 33343C490CE4DBE200FF8CE6 /* jcmarker.c */, + 33343C4A0CE4DBE200FF8CE6 /* jcmaster.c */, + 33343C4B0CE4DBE200FF8CE6 /* jcomapi.c */, + 33343C4C0CE4DBE200FF8CE6 /* jconfig.h */, + 33343C4D0CE4DBE200FF8CE6 /* jcparam.c */, + 33343C4E0CE4DBE200FF8CE6 /* jcphuff.c */, + 33343C4F0CE4DBE200FF8CE6 /* jcprepct.c */, + 33343C500CE4DBE200FF8CE6 /* jcsample.c */, + 33343C510CE4DBE200FF8CE6 /* jctrans.c */, + 33343C520CE4DBE200FF8CE6 /* jdapimin.c */, + 33343C530CE4DBE200FF8CE6 /* jdapistd.c */, + 33343C540CE4DBE200FF8CE6 /* jdatadst.c */, + 33343C550CE4DBE200FF8CE6 /* jdatasrc.c */, + 33343C560CE4DBE200FF8CE6 /* jdcoefct.c */, + 33343C570CE4DBE200FF8CE6 /* jdcolor.c */, + 33343C580CE4DBE200FF8CE6 /* jdct.h */, + 33343C590CE4DBE200FF8CE6 /* jddctmgr.c */, + 33343C5A0CE4DBE200FF8CE6 /* jdhuff.c */, + 33343C5B0CE4DBE200FF8CE6 /* jdhuff.h */, + 33343C5C0CE4DBE200FF8CE6 /* jdinput.c */, + 33343C5D0CE4DBE200FF8CE6 /* jdmainct.c */, + 33343C5E0CE4DBE200FF8CE6 /* jdmarker.c */, + 33343C5F0CE4DBE200FF8CE6 /* jdmaster.c */, + 33343C600CE4DBE200FF8CE6 /* jdmerge.c */, + 33343C610CE4DBE200FF8CE6 /* jdphuff.c */, + 33343C620CE4DBE200FF8CE6 /* jdpostct.c */, + 33343C630CE4DBE200FF8CE6 /* jdsample.c */, + 33343C640CE4DBE200FF8CE6 /* jdtrans.c */, + 33343C650CE4DBE200FF8CE6 /* jerror.c */, + 33343C660CE4DBE200FF8CE6 /* jerror.h */, + 33343C670CE4DBE200FF8CE6 /* jfdctflt.c */, + 33343C680CE4DBE200FF8CE6 /* jfdctfst.c */, + 33343C690CE4DBE200FF8CE6 /* jfdctint.c */, + 33343C6A0CE4DBE200FF8CE6 /* jidctflt.c */, + 33343C6B0CE4DBE200FF8CE6 /* jidctfst.c */, + 33343C6C0CE4DBE200FF8CE6 /* jidctint.c */, + 33343C6D0CE4DBE200FF8CE6 /* jidctred.c */, + 33343C6E0CE4DBE200FF8CE6 /* jinclude.h */, + 33343C6F0CE4DBE200FF8CE6 /* jmemmgr.c */, + 33343C700CE4DBE200FF8CE6 /* jmemnobs.c */, + 33343C710CE4DBE200FF8CE6 /* jmemsys.h */, + 33343C720CE4DBE200FF8CE6 /* jmorecfg.h */, + 33343C730CE4DBE200FF8CE6 /* jpegint.h */, + 33343C740CE4DBE200FF8CE6 /* jpeglib.h */, + 33343C750CE4DBE200FF8CE6 /* jquant1.c */, + 33343C760CE4DBE200FF8CE6 /* jquant2.c */, + 33343C770CE4DBE200FF8CE6 /* jutils.c */, + 33343C780CE4DBE200FF8CE6 /* jversion.h */, + ); + name = JPeglib; + sourceTree = ""; + }; + 33343C1F0CE4DB9200FF8CE6 /* giflib */ = { + isa = PBXGroup; + children = ( + 33343C340CE4DBD200FF8CE6 /* config.h */, + 33343C350CE4DBD200FF8CE6 /* dgif_lib.c */, + 33343C360CE4DBD200FF8CE6 /* egif_lib.c */, + 33343C370CE4DBD200FF8CE6 /* gif_hash.c */, + 33343C380CE4DBD200FF8CE6 /* gif_hash.h */, + 33343C390CE4DBD200FF8CE6 /* gif_lib_private.h */, + 33343C3A0CE4DBD200FF8CE6 /* gif_lib.h */, + 33343C3B0CE4DBD200FF8CE6 /* gifalloc.c */, + ); + name = giflib; + sourceTree = ""; + }; + 33BD26AA0ECF77F9000A367A /* plush2 */ = { + isa = PBXGroup; + children = ( + 33BD26AC0ECF7811000A367A /* pl_cam.cpp */, + 33BD26AD0ECF7811000A367A /* pl_make.cpp */, + 33BD26AE0ECF7811000A367A /* pl_math.cpp */, + 33BD26AF0ECF7811000A367A /* pl_obj.cpp */, + 33BD26B00ECF7811000A367A /* pl_pf_tex.h */, + 33BD26B10ECF7811000A367A /* pl_putface.cpp */, + 33BD26B20ECF7811000A367A /* pl_read_3ds.cpp */, + 33BD26B30ECF7811000A367A /* pl_read_cob.cpp */, + 33BD26B40ECF7811000A367A /* pl_read_jaw.cpp */, + 33BD26B50ECF7811000A367A /* pl_spline.cpp */, + 33BD26B60ECF7811000A367A /* plush.h */, + ); + name = plush2; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8D1107260486CEB800E47090 /* test */ = { + isa = PBXNativeTarget; + buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "test" */; + buildPhases = ( + 8D1107290486CEB800E47090 /* Resources */, + 8D11072C0486CEB800E47090 /* Sources */, + 8D11072E0486CEB800E47090 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = test; + productInstallPath = "$(HOME)/Applications"; + productName = test; + productReference = 8D1107320486CEB800E47090 /* test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "test" */; + compatibilityVersion = "Xcode 2.4"; + developmentRegion = en; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* test */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8D1107260486CEB800E47090 /* test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8D1107290486CEB800E47090 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */, + 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, + 33DF417F1969C743006C7E88 /* image.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8D11072C0486CEB800E47090 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072D0486CEB800E47090 /* main.m in Sources */, + 33343C070CE4DB5D00FF8CE6 /* swell-dlg.mm in Sources */, + 33343C130CE4DB7000FF8CE6 /* swell-gdi.mm in Sources */, + 33343C140CE4DB7000FF8CE6 /* swell-ini.cpp in Sources */, + 33343C150CE4DB7000FF8CE6 /* swell-kb.mm in Sources */, + 33343C160CE4DB7000FF8CE6 /* swell-menu.mm in Sources */, + 33343C170CE4DB7000FF8CE6 /* swell-miscdlg.mm in Sources */, + 33343C180CE4DB7000FF8CE6 /* swell-wnd.mm in Sources */, + 33343C190CE4DB7000FF8CE6 /* swell.cpp in Sources */, + 33343C210CE4DB9E00FF8CE6 /* lice_arc.cpp in Sources */, + 33343C2D0CE4DBBB00FF8CE6 /* lice_gif.cpp in Sources */, + 33343C2E0CE4DBBB00FF8CE6 /* lice_jpg.cpp in Sources */, + 33343C2F0CE4DBBB00FF8CE6 /* lice_line.cpp in Sources */, + 33343C300CE4DBBB00FF8CE6 /* lice_png.cpp in Sources */, + 33343C310CE4DBBB00FF8CE6 /* lice_texgen.cpp in Sources */, + 33343C320CE4DBBB00FF8CE6 /* lice_text.cpp in Sources */, + 33343C330CE4DBBB00FF8CE6 /* lice.cpp in Sources */, + 33343C3C0CE4DBD200FF8CE6 /* dgif_lib.c in Sources */, + 33343C3D0CE4DBD200FF8CE6 /* egif_lib.c in Sources */, + 33343C3E0CE4DBD200FF8CE6 /* gif_hash.c in Sources */, + 33343C3F0CE4DBD200FF8CE6 /* gifalloc.c in Sources */, + 33343C790CE4DBE200FF8CE6 /* jcapimin.c in Sources */, + 33343C7A0CE4DBE200FF8CE6 /* jcapistd.c in Sources */, + 33343C7B0CE4DBE200FF8CE6 /* jccoefct.c in Sources */, + 33343C7C0CE4DBE200FF8CE6 /* jccolor.c in Sources */, + 33343C7D0CE4DBE200FF8CE6 /* jcdctmgr.c in Sources */, + 33343C7E0CE4DBE200FF8CE6 /* jchuff.c in Sources */, + 33343C7F0CE4DBE200FF8CE6 /* jcinit.c in Sources */, + 33343C800CE4DBE200FF8CE6 /* jcmainct.c in Sources */, + 33343C810CE4DBE200FF8CE6 /* jcmarker.c in Sources */, + 33343C820CE4DBE200FF8CE6 /* jcmaster.c in Sources */, + 33343C830CE4DBE200FF8CE6 /* jcomapi.c in Sources */, + 33343C840CE4DBE200FF8CE6 /* jcparam.c in Sources */, + 33343C850CE4DBE200FF8CE6 /* jcphuff.c in Sources */, + 33343C860CE4DBE200FF8CE6 /* jcprepct.c in Sources */, + 33343C870CE4DBE200FF8CE6 /* jcsample.c in Sources */, + 33343C880CE4DBE200FF8CE6 /* jctrans.c in Sources */, + 33343C890CE4DBE200FF8CE6 /* jdapimin.c in Sources */, + 33343C8A0CE4DBE200FF8CE6 /* jdapistd.c in Sources */, + 33343C8B0CE4DBE200FF8CE6 /* jdatadst.c in Sources */, + 33343C8C0CE4DBE200FF8CE6 /* jdatasrc.c in Sources */, + 33343C8D0CE4DBE200FF8CE6 /* jdcoefct.c in Sources */, + 33343C8E0CE4DBE200FF8CE6 /* jdcolor.c in Sources */, + 33343C8F0CE4DBE200FF8CE6 /* jddctmgr.c in Sources */, + 33343C900CE4DBE200FF8CE6 /* jdhuff.c in Sources */, + 33343C910CE4DBE200FF8CE6 /* jdinput.c in Sources */, + 33343C920CE4DBE200FF8CE6 /* jdmainct.c in Sources */, + 33343C930CE4DBE200FF8CE6 /* jdmarker.c in Sources */, + 33343C940CE4DBE200FF8CE6 /* jdmaster.c in Sources */, + 33343C950CE4DBE200FF8CE6 /* jdmerge.c in Sources */, + 33343C960CE4DBE200FF8CE6 /* jdphuff.c in Sources */, + 33343C970CE4DBE200FF8CE6 /* jdpostct.c in Sources */, + 33343C980CE4DBE200FF8CE6 /* jdsample.c in Sources */, + 33343C990CE4DBE200FF8CE6 /* jdtrans.c in Sources */, + 33343C9A0CE4DBE200FF8CE6 /* jerror.c in Sources */, + 33343C9B0CE4DBE200FF8CE6 /* jfdctflt.c in Sources */, + 33343C9C0CE4DBE200FF8CE6 /* jfdctfst.c in Sources */, + 33343C9D0CE4DBE200FF8CE6 /* jfdctint.c in Sources */, + 33343C9E0CE4DBE200FF8CE6 /* jidctflt.c in Sources */, + 33343C9F0CE4DBE200FF8CE6 /* jidctfst.c in Sources */, + 33343CA00CE4DBE200FF8CE6 /* jidctint.c in Sources */, + 33343CA10CE4DBE200FF8CE6 /* jidctred.c in Sources */, + 33343CA20CE4DBE200FF8CE6 /* jmemmgr.c in Sources */, + 33343CA30CE4DBE200FF8CE6 /* jmemnobs.c in Sources */, + 33343CA40CE4DBE200FF8CE6 /* jquant1.c in Sources */, + 33343CA50CE4DBE200FF8CE6 /* jquant2.c in Sources */, + 33343CA60CE4DBE200FF8CE6 /* jutils.c in Sources */, + 33343CFE0CE4DCFB00FF8CE6 /* adler32.c in Sources */, + 33343CFF0CE4DCFB00FF8CE6 /* compress.c in Sources */, + 33343D000CE4DCFB00FF8CE6 /* crc32.c in Sources */, + 33343D010CE4DCFB00FF8CE6 /* deflate.c in Sources */, + 33343D040CE4DCFB00FF8CE6 /* infback.c in Sources */, + 33343D050CE4DCFB00FF8CE6 /* inffast.c in Sources */, + 33343D060CE4DCFB00FF8CE6 /* inflate.c in Sources */, + 33343D070CE4DCFB00FF8CE6 /* inftrees.c in Sources */, + 33343D090CE4DCFB00FF8CE6 /* trees.c in Sources */, + 33343D0A0CE4DCFB00FF8CE6 /* uncompr.c in Sources */, + 33343D0B0CE4DCFB00FF8CE6 /* zutil.c in Sources */, + 33343D210CE4DD1F00FF8CE6 /* png.c in Sources */, + 33343D220CE4DD1F00FF8CE6 /* pngerror.c in Sources */, + 33343D240CE4DD1F00FF8CE6 /* pngget.c in Sources */, + 33343D250CE4DD1F00FF8CE6 /* pngmem.c in Sources */, + 33343D260CE4DD1F00FF8CE6 /* pngpread.c in Sources */, + 33343D270CE4DD1F00FF8CE6 /* pngread.c in Sources */, + 33343D280CE4DD1F00FF8CE6 /* pngrio.c in Sources */, + 33343D290CE4DD1F00FF8CE6 /* pngrtran.c in Sources */, + 33343D2A0CE4DD1F00FF8CE6 /* pngrutil.c in Sources */, + 33343D2B0CE4DD1F00FF8CE6 /* pngset.c in Sources */, + 33343D2D0CE4DD1F00FF8CE6 /* pngtrans.c in Sources */, + 33343D2F0CE4DD1F00FF8CE6 /* pngwio.c in Sources */, + 33343D300CE4DD1F00FF8CE6 /* pngwrite.c in Sources */, + 33343D310CE4DD1F00FF8CE6 /* pngwtran.c in Sources */, + 33343D320CE4DD1F00FF8CE6 /* pngwutil.c in Sources */, + 33E075650CE4DDD3005DDE14 /* Controller.mm in Sources */, + 33BD26B70ECF7811000A367A /* pl_cam.cpp in Sources */, + 33BD26B80ECF7811000A367A /* pl_make.cpp in Sources */, + 33BD26B90ECF7811000A367A /* pl_math.cpp in Sources */, + 33BD26BA0ECF7811000A367A /* pl_obj.cpp in Sources */, + 33BD26BB0ECF7811000A367A /* pl_putface.cpp in Sources */, + 33BD26BC0ECF7811000A367A /* pl_read_3ds.cpp in Sources */, + 33BD26BD0ECF7811000A367A /* pl_read_cob.cpp in Sources */, + 33BD26BE0ECF7811000A367A /* pl_read_jaw.cpp in Sources */, + 33BD26BF0ECF7811000A367A /* pl_spline.cpp in Sources */, + 33BD26C90ECF7830000A367A /* lice_bmp.cpp in Sources */, + 33BD26CA0ECF7830000A367A /* lice_ico.cpp in Sources */, + 33BD26CB0ECF7830000A367A /* lice_image.cpp in Sources */, + 33BD26CC0ECF7830000A367A /* lice_jpg_write.cpp in Sources */, + 33BD26CD0ECF7830000A367A /* lice_pcx.cpp in Sources */, + 33BD26CF0ECF7830000A367A /* lice_textnew.cpp in Sources */, + 33BD26DF0ECF78C3000A367A /* fly.cpp in Sources */, + 33651CDE10B766A6005FF751 /* swell-misc.mm in Sources */, + 3350A9D2146B63B500702E1F /* lice_lvg.cpp in Sources */, + 3350A9D3146B63B500702E1F /* lice_palette.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 089C165DFE840E0CC02AAC07 /* English */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = { + isa = PBXVariantGroup; + children = ( + 29B97319FDCFA39411CA2CEA /* English */, + ); + name = MainMenu.nib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + C01FCF4B08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_LINK_OBJC_RUNTIME = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = SWELL_SUPPORT_OPENGL_BLIT; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + PRODUCT_NAME = test; + WRAPPER_EXTENSION = app; + ZERO_LINK = YES; + }; + name = Debug; + }; + C01FCF4C08A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_LINK_OBJC_RUNTIME = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PREPROCESSOR_DEFINITIONS = SWELL_SUPPORT_OPENGL_BLIT; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + PRODUCT_NAME = test; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4B08A954540054247B /* Debug */, + C01FCF4C08A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/lineparse.h b/plugin-reaper-realearn/main/lib/WDL/WDL/lineparse.h new file mode 100644 index 0000000..07f4a4e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/lineparse.h @@ -0,0 +1,350 @@ +/* + WDL - lineparse.h + Copyright (C) 2005-2014 Cockos Incorporated + Copyright (C) 1999-2004 Nullsoft, 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. + +*/ + +/* + + This file provides a simple line parsing class. This class was derived from that of NSIS, + http://nsis.sf.net, but it is no longer compatible (escaped-encodings and multiline C-style comments + are ignored). + + In particular, it allows for multiple space delimited tokens + on a line, with a choice of three quotes (`bla`, 'bla', or "bla") to contain any + items that may have spaces. + +*/ + +#ifndef WDL_LINEPARSE_H_ +#define WDL_LINEPARSE_H_ + +#include "heapbuf.h" + +#ifndef WDL_LINEPARSER_HAS_LINEPARSERINT +#define WDL_LINEPARSER_HAS_LINEPARSERINT +#endif + +#ifndef WDL_LINEPARSE_IMPL_ONLY +class LineParserInt // version which does not have any temporary space for buffers (requires use of parseDestroyBuffer) +{ + public: + int getnumtokens() const { return m_nt-m_eat; } + + #ifdef WDL_LINEPARSE_INTF_ONLY + // parse functions return <0 on error (-1=mem, -2=unterminated quotes), ignore_commentchars = true means don't treat #; as comments + int parseDestroyBuffer(char *line, bool ignore_commentchars = true, bool backtickquote = true, bool allowunterminatedquotes = false); + + double gettoken_float(int token) const; + int gettoken_int(int token) const; + unsigned int gettoken_uint(int token) const; // deprecated + const char *gettoken_str(int token) const; + char gettoken_quotingchar(int token) const; + int gettoken_enum(int token, const char *strlist) const; // null separated list + void insert_token_raw(int token, const char *p); // first character of p is quoting character! + #endif + + void eattoken() { if (m_eat= m_nt) return ""; + return m_tokens[token]; + } + + char WDL_LINEPARSE_PREFIX gettoken_quotingchar(int token) const + { + token+=m_eat; + if ((unsigned int)token >= m_nt) return 0; + + const char *tok = m_tokens[token]; + if (tok != m_tokenbasebuffer) switch (tok[-1]) + { + case '"': return '"'; + case '`': return '`'; + case '\'': return '\''; + } + return 0; + } + + int WDL_LINEPARSE_PREFIX gettoken_enum(int token, const char *strlist) const // null separated list + { + int x=0; + const char *tt=gettoken_str(token); + if (*tt) while (*strlist) + { + if (!stricmp(tt,strlist)) return x; + while (*strlist) strlist++; + strlist++; + x++; + } + return -1; + } + + void WDL_LINEPARSE_PREFIX insert_token_raw(int token, const char *p) // first character of p is quoting character! + { + if (WDL_NOT_NORMALLY((unsigned int)token > m_nt)) return; + if (WDL_NOT_NORMALLY(!adding_token_alloc())) return; + if ((unsigned int)token < m_nt) + memmove(m_tokens + token + 1, m_tokens + token, (m_nt-token) * sizeof(const char *)); + m_tokens[token] = p+1; + m_nt++; + } + +#ifndef WDL_LINEPARSE_IMPL_ONLY + private: +#endif + + + #undef WDL_LINEPARSE_PREFIX + #undef WDL_LINEPARSE_DEFPARM +#endif // ! WDL_LINEPARSE_INTF_ONLY + +#ifndef WDL_LINEPARSE_IMPL_ONLY + protected: + + bool adding_token_alloc() + { + if (m_nt < (int) (sizeof(m_toklist_small)/sizeof(m_toklist_small[0]))) + return true; + + m_tokens = m_toklist_big.ResizeOK(m_nt+1,false); + if (!m_tokens) + { + m_nt=0; + return false; + } + if (m_nt == (int) (sizeof(m_toklist_small)/sizeof(m_toklist_small[0]))) + memcpy(m_tokens,m_toklist_small,m_nt*sizeof(const char *)); + + return true; + } + + WDL_TypedBuf m_toklist_big; + + unsigned int m_nt, m_eat; + + const char *m_tokenbasebuffer; // points to (mangled) caller's buffer + const char **m_tokens; // points to m_toklist_small or m_toklist_big + + const char *m_toklist_small[64]; +}; +#endif//!WDL_LINEPARSE_IMPL_ONLY + + + + + + +// derived + +#ifndef WDL_LINEPARSE_IMPL_ONLY +class LineParser : public LineParserInt +{ + public: + int parse(const char *line) { return parse_ex(line,false); } // <0 on error, old style (;# starting tokens means comment to EOL) + + #ifdef WDL_LINEPARSE_INTF_ONLY + // parse functions return <0 on error (-1=mem, -2=unterminated quotes), ignore_commentchars = true means don't treat #; as comments + int parse_ex(const char *line, bool ignore_commentchars = true, bool backtickquote = true, bool allowunterminatedquotes = false); + void set_one_token(const char *ptr); + char *__get_tmpbuf(const char *line); + #endif + + + LineParser(bool ignoredLegacyValue=false) { } + +#endif // !WDL_LINEPARSE_IMPL_ONLY + + + +#ifndef WDL_LINEPARSE_INTF_ONLY + #ifdef WDL_LINEPARSE_IMPL_ONLY + #define WDL_LINEPARSE_PREFIX LineParser:: + #define WDL_LINEPARSE_DEFPARM(x) + #else + #define WDL_LINEPARSE_PREFIX + #define WDL_LINEPARSE_DEFPARM(x) =(x) + #endif + + int WDL_LINEPARSE_PREFIX parse_ex(const char *line, bool ignore_commentchars WDL_LINEPARSE_DEFPARM(true), bool backtickquote WDL_LINEPARSE_DEFPARM(true), bool allowunterminatedquotes WDL_LINEPARSE_DEFPARM(false)) + { + return parseDestroyBuffer(__get_tmpbuf(line), ignore_commentchars, backtickquote, allowunterminatedquotes); + } + + void WDL_LINEPARSE_PREFIX set_one_token(const char *line) + { + m_tokens=m_toklist_small; + m_tokens[0] = m_tokenbasebuffer = __get_tmpbuf(line); + m_eat=0; + m_nt=m_tokenbasebuffer?1:0; + } + + char * WDL_LINEPARSE_PREFIX __get_tmpbuf(const char *line) + { + int linelen = (int)strlen(line); + + char *usebuf=m_tmpbuf; + if (linelen >= (int)sizeof(m_tmpbuf)) + { + usebuf = (char *)m_tmpbuf_big.ResizeOK(linelen+1,false); + if (!usebuf) + { + m_nt=0; + return NULL; + } + } + memcpy(usebuf,line,linelen+1); + return usebuf; + } + + #undef WDL_LINEPARSE_PREFIX + #undef WDL_LINEPARSE_DEFPARM +#endif // ! WDL_LINEPARSE_INTF_ONLY + +#ifndef WDL_LINEPARSE_IMPL_ONLY + private: + + WDL_HeapBuf m_tmpbuf_big; + char m_tmpbuf[2048]; +}; +#endif//!WDL_LINEPARSE_IMPL_ONLY + + + + + + + +#endif//WDL_LINEPARSE_H_ + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/build_sample_langpack.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/build_sample_langpack.cpp new file mode 100644 index 0000000..34ea259 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/build_sample_langpack.cpp @@ -0,0 +1,691 @@ +/* + * to generate a template language pack, use: + * build_sample_langpack --template *.rc *.cpp etc > file.langpack + * + */ +#include +#include +#include +#include +#ifndef _WIN32 +#define stricmp strcasecmp +#endif + +#include "../wdltypes.h" +#include "../assocarray.h" +#include "../ptrlist.h" +#include "../wdlstring.h" +#include "../fnv64.h" + +static bool isblank(char c) +{ + return c==' ' || c== '\t' || c=='\r' || c=='\n'; +} +static bool isblankorz(char c) +{ + return !c || isblank(c); +} + +WDL_StringKeyedArray section_descs; + +WDL_StringKeyedArray< WDL_PtrList * > translations; +WDL_StringKeyedArray< WDL_StringKeyedArray * > translations_indexed; + +void gotString(const char *str, int len, const char *secname, bool isRC, const char *fn, int line) +{ + WDL_PtrList *sec=translations.Get(secname); + if (!sec) + { + sec=new WDL_PtrList; + translations.Insert(secname,sec); + } + WDL_StringKeyedArray *sec2 = translations_indexed.Get(secname); + if (!sec2) + { + sec2 = new WDL_StringKeyedArray(true); + translations_indexed.Insert(secname,sec2); + } + if (len > (int)strlen(str)) + { + fprintf(stderr,"gotString got len>strlen(str) on %s:%d\n",fn,line); + exit(1); + } + + WDL_FastString buf; + if (!isRC) + { + int st = 0; + for (int x = 0; x < len; x ++) + { + if (!st) + { + if (str[x] == '\\' && str[x+1]) + { + buf.Append(str+x,2); + x++; + } + else if (str[x] == '\"') st=1; + else buf.Append(str+x,1); + } + else if (str[x] == '\"') st=0; + else if (!isblank(str[x])) + { + fprintf(stderr,"gotString has junk between concatenated strings on %s:%d\n",fn,line); + exit(1); + } + } + } + else + { + buf.Set(str,len); + } + if (sec2->Get(buf.Get())) return; //already in list + + sec2->Insert(buf.Get(),true); + sec->Add(strdup(buf.Get())); +} +const char *g_last_file; +int g_last_linecnt; +int length_of_quoted_string(char *p, bool convertRCquotesToSlash) +{ + int l=0; + while (p[l]) + { + if (convertRCquotesToSlash && p[l] == '\"' && p[l+1] == '\"') p[l]='\\'; + + if (p[l] == '\"') + { + if (convertRCquotesToSlash) return l; + + // scan over whitespace to see if another string begins, concat them + int l2 = l+1; + while (isblank(p[l2])) l2++; + if (p[l2] != '\"') return l; + l = l2; + } + if (p[l] == '\\') + { + l++; + } + if (!p[l] || p[l] == '\r' || p[l] == '\n') break; + l++; + } + fprintf(stderr,"ERROR: mismatched quotes in file %s:%d, check input!\n",g_last_file,g_last_linecnt); + exit(1); + return -1; +} + +static int uint64cmpfunc(WDL_UINT64 *a, WDL_UINT64 *b) +{ + if (*a < *b) return -1; + if (*a > *b) return 1; + return 0; +} +#define HACK_WILDCARD_ENTRY 2 +static int isLocalizeCall(const char *p) +{ + int rv = 0; + if (!strncmp(p,"__LOCALIZE",10)) { p+=10; rv = 1; } + else if (!strncmp(p,"ADD_WILDCARD_ENTRY",18)) { p+=18; rv = HACK_WILDCARD_ENTRY; } + else return 0; + + if (*p == '_') while (*p == '_' || (*p >= 'A' && *p <= 'Z')||(*p>='0' && *p <='9')) p++; + while (isblank(*p)) p++; + return *p == '(' ? rv : 0; +} + +WDL_UINT64 outputLine(const char *strv, int casemode) +{ + WDL_UINT64 h = WDL_FNV64_IV; + const char *p=strv; + while (*p) + { + char c = *p++; + if (c == '\\') + { + if (*p == '\\'||*p == '"' || *p == '\'') h=WDL_FNV64(h,(unsigned char *)p,1); + else if (*p == 'n') h=WDL_FNV64(h,(unsigned char *)"\n",1); + else if (*p == 'r') h=WDL_FNV64(h,(unsigned char *)"\r",1); + else if (*p == 't') h=WDL_FNV64(h,(unsigned char *)"\t",1); + else if (*p == '0') h=WDL_FNV64(h,(unsigned char *)"",1); + else if (*p == 'x' && p[1] == 'e' && p[2] == '9') + { + h=WDL_FNV64(h,(unsigned char *)"\xe9",1); + p+=2; + } + else + { + fprintf(stderr,"ERROR: unknown escape seq in '%s' at '%s'\n",strv,p); + exit(1); + } + p++; + } + else h=WDL_FNV64(h,(unsigned char *)&c,1); + } + h=WDL_FNV64(h,(unsigned char *)"",1); + + printf("%08X%08X=",(int)(h>>32),(int)(h&0xffffffff)); + int lc = 0; + while (*strv) + { + int c = *strv++; + if (lc == '%' || lc == '\\') { /* hacky*/ } + else if (c == '\\' && strv[0] == 'x' && strv[1] == 'e' && strv[2] == '9') + { + strv+=3; + c = 0xe9; + } + else if (casemode == 2) + { + switch (tolower(c)) + { + case 'o': c='0'; break; + case 'i': c='1'; break; + case 'e': c='3'; break; + case 'a': c='4'; break; + case 's': c='5'; break; + } + } + else if (casemode==-1) c=tolower(c); + else if (casemode==1) c=toupper(c); + else if (casemode==4) + { + switch (c) + { + case 'E': c=0xc494; break; + case 'A': c=0xc381; break; + case 'I': c=0xc38f; break; + case 'N': c=0xc391; break; + case 'O': c=0xc395; break; + case 'U': c=0xc39a; break; + case 'B': c=0xc39f; break; + case 'C': c=0xc486; break; + case 'G': c=0xc4a0; break; + case 'e': c=0xc497; break; + case 'a': c=0xc3a4; break; + case 'i': c=0xc4ad; break; + case 'n': c=0xc584; break; + case 'o': c=0xc58d; break; + case 'u': c=0xc5af; break; + case 'b': c=0xc39e; break; + case 'c': c=0xc48d; break; + case 'g': c=0xc49f; break; + } + if (c >= 256) + { + printf("%c",(c>>8)); + c&=0xff; + } + } + printf("%c",c); + if (lc == '%' && (c == '.' || c=='l' || (c>='0' && c<='9'))) + { + // ignore .xyz and l between format spec (hacky) + } + else if (lc == '%' && c == '%') lc = 0; + else if (lc == '\\' && c == '\\') lc = 0; + else lc = c; + } + printf("\n"); + return h; +} + + +WDL_StringKeyedArray g_resdefs; +const char *getResourceDefinesFromHeader(const char *fn) +{ + g_resdefs.DeleteAll(); + + FILE *fp=fopen(fn,"rb"); + if (!fp) return "error opening header"; + for (;;) + { + char buf[8192]; + g_last_linecnt++; + if (!fgets(buf,sizeof(buf),fp)) break; + char *p = buf; + while (*p) p++; + while (p>buf && (p[-1] == '\r'|| p[-1] == '\n' || p[-1] == ' ')) p--; + *p=0; + + if (!strncmp(buf,"#define",7)) + { + p=buf; + while (*p && *p != ' '&& *p != '\t') p++; + while (*p == ' ' || *p == '\t') p++; + char *n1 = p; + while (*p && *p != ' '&& *p != '\t') p++; + if (*p) *p++=0; + while (*p == ' ' || *p == '\t') p++; + int a = atoi(p); + if (a && *n1) + { + g_resdefs.Insert(n1,a); + } + } + } + + fclose(fp); + return NULL; +} + +void processRCfile(FILE *fp, const char *dirprefix, const char *filename) +{ + char sname[512]; + sname[0]=0; + int depth=0; + for (;;) + { + char buf[8192]; + g_last_linecnt++; + if (!fgets(buf,sizeof(buf),fp)) break; + char *p = buf; + while (*p) p++; + while (p>buf && (p[-1] == '\r'|| p[-1] == '\n' || p[-1] == ' ')) p--; + *p=0; + + p=buf; + if (sname[0]) while (*p == ' ' || *p == '\t') p++; + char *first_tok = p; + if (!strncmp(first_tok,"CLASS",5) && isblank(first_tok[5]) && first_tok[6]=='\"') continue; + + while (*p && *p != '\t' && *p != ' ') p++; + if (*p) *p++=0; + while (*p == '\t' || *p == ' ') p++; + char *second_tok = p; + + if ((!strncmp(second_tok,"DIALOG",6) && isblankorz(second_tok[6])) || + (!strncmp(second_tok,"DIALOGEX",8) && isblankorz(second_tok[8]))|| + (!strncmp(second_tok,"MENU",4) && isblankorz(second_tok[4]))) + { + if (sname[0]) + { + fprintf(stderr,"got %s inside a block\n",second_tok); + exit(1); + } + int sec = g_resdefs.Get(first_tok); + if (!sec) + { + fprintf(stderr, "unknown dialog %s\n",first_tok); + exit(1); + } + sprintf(sname,"%s%s%s_%d",dirprefix?dirprefix:"",dirprefix?"_":"",second_tok[0] == 'M' ? "MENU" : "DLG",sec); + section_descs.Insert(sname,strdup(first_tok)); + } + else if (sname[0] && *second_tok && *first_tok) + { + if (*second_tok == '"') + { + int l = length_of_quoted_string(second_tok+1,true); + if (l>0) + { + gotString(second_tok+1,l,sname, true, filename, g_last_linecnt); + + // OSX menu support: store a 2nd string w/o \tshortcuts, strip '&' too + // note: relies on length_of_quoted_string() pre-conversion above + if (depth && strstr(sname, "MENU_")) + { + int j=0; + char* m=second_tok+1; + for(;;) + { + if (!*m || (*m == '\\' && *(m+1)=='t') || (*m=='\"' && *(m-1)!='\\')) { buf[j]=0; break; } + if (*m != '&') buf[j++] = *m; + m++; + } + if (j!=l) gotString(buf,j,sname,true, filename, g_last_linecnt); + } + } + } + } + else if (!strcmp(first_tok,"BEGIN")) + { + depth++; + } + else if (!strcmp(first_tok,"END")) + { + depth--; + if (depth<0) + { + fprintf(stderr,"extra END\n"); + exit(1); + } + if (!depth) sname[0]=0; + } + + } + if (depth!=0) + { + fprintf(stderr,"missing some ENDs at end of rc file\n"); + exit(1); + } +} + +void processCPPfile(FILE *fp, const char *filename) +{ + char clocsec[512]; + clocsec[0]=0; + WDL_FastString fs; + for (;;) + { + char buf[8192]; + if (!fgets(buf,sizeof(buf),fp)) break; + fs.Append(buf); + } + + char *p = (char*)fs.Get(); + char *comment_state = NULL; + g_last_linecnt++; + while (*p) + { + if (!strncmp(p,"//",2)) + { + comment_state = p; + p+=2; + } + else if (*p == '\n') + { + g_last_linecnt++; + comment_state = NULL; + p++; + } + else if (!comment_state) + { + int hm; + if (*p == '\\') { p++; if (*p) p++; } + else if (*p == '\'') { p++; if (*p == '"') p++; } + else if (*p == '"') + { + int l = length_of_quoted_string(p+1,false); + if (clocsec[0]) + { + gotString(p+1,l,clocsec,false, filename, g_last_linecnt); + } + p += l+2; + } + else if ((p==(char*)fs.Get() || (!isalnum(p[-1]) && p[-1] != '_')) && (hm=isLocalizeCall(p))) + { + while (*p != '(') p++; + p++; + while (isblank(*p)) p++; + if (*p++ != '"') + { + fprintf(stderr,"Error: missing \" on %s:%d\n",filename,g_last_linecnt); + exit(1); + } + int l = length_of_quoted_string(p,false); + char *sp = p; + p+=l+1; + while (isblank(*p)) p++; + if (*p++ != ',') + { + fprintf(stderr,"Error: missing , on %s:%d\n",filename,g_last_linecnt); + exit(1); + } + while (isblank(*p)) p++; + if (*p++ != '"') + { + fprintf(stderr,"Error: missing second \" on %s:%d\n",filename,g_last_linecnt); + exit(1); + } + int l2 = length_of_quoted_string(p,false); + + if (hm == HACK_WILDCARD_ENTRY) + { + gotString(p,l2,"render_wildcard",false, filename, g_last_linecnt); + p += l2; + } + else + { + char sec[512]; + memcpy(sec,p,l2); + sec[l2]=0; + p+=l2; + gotString(sp,l,sec,false, filename, g_last_linecnt); + } + p++; + } + else + p++; + } + else if (p > comment_state && p < comment_state + 4) + { + if (!strncmp(p,"!WANT_LOCALIZE_STRINGS_BEGIN:",29)) + { + p += 29; + if (clocsec[0]) + { + fprintf(stderr,"Error: !WANT_LOCALIZE_STRINGS_BEGIN: before WANT_LOCALIZE_STRINGS_END on %s:%d\n",filename,g_last_linecnt); + exit(1); + } + int a = 0; + while (*p && !isblank(*p) && a < sizeof(clocsec)) clocsec[a++] = *p++; + if (a >= sizeof(clocsec)) + { + fprintf(stderr,"Error: !WANT_LOCALIZE_STRINGS_BEGIN: too long on %s:%d\n",filename,g_last_linecnt); + exit(1); + } + clocsec[a]=0; + } + else + { + if (!strncmp(p,"!WANT_LOCALIZE_STRINGS_END",26)) + { + if (!clocsec[0]) + { + fprintf(stderr,"Error: mismatched !WANT_LOCALIZE_STRINGS_END on %s:%d\n",filename,g_last_linecnt); + exit(1); + } + clocsec[0]=0; + } + p++; + } + } + else p++; + } + if (clocsec[0]) + { + fprintf(stderr,"Error: missing !WANT_LOCALIZE_STRINGS_END at eof %s:%d\n",filename,g_last_linecnt); + exit(1); + } +} + + + +int main(int argc, char **argv) +{ + int x; + int casemode=0; + for (x=1;x3 && !stricmp(argv[x]+alen-3,".rc")) + { + WDL_String s(argv[x]); + WDL_String dpre; + char *p=s.Get(); + while (*p) p++; + while (p>=s.Get() && *p != '\\' && *p != '/') p--; + *++p=0; + if (p>s.Get()) + { + p-=2; + while (p>=s.Get() && *p != '\\' && *p != '/') p--; + dpre.Set(++p); // get dir name portion + if (dpre.GetLength()) dpre.Get()[dpre.GetLength()-1]=0; + } + if (!strcmp(dpre.Get(),"jesusonic")) dpre.Set("jsfx"); + + s.Append("resource.h"); + const char *err=getResourceDefinesFromHeader(s.Get()); + if (err) + { + fprintf(stderr,"Error reading %s: %s\n",s.Get(),err); + exit(1); + } + processRCfile(fp,dpre.Get()[0]?dpre.Get():NULL, argv[x]); + } + else + { + processCPPfile(fp,argv[x]); + } + + fclose(fp); + } + if (casemode==4) printf("\xef\xbb\xbf"); + printf("#NAME:%s\n", + casemode==-1 ? "English (lower case, demo)" : + casemode==1 ? "English (upper case, demo)" : + casemode==2 ? "English (leet-speak, demo)" : + casemode==4 ? "UTF-8 English test (demo)" : + casemode==3 ? "Template (edit-me)" : + "English (sample language pack)"); + if (casemode==3) + { + printf("; NOTE: this is the best starting point for making a new langpack.\n" + "; As you translate a string, remove the ; from the beginning of the\n" + "; line. If the line begins with ;^, then it is an optional string,\n" + "; and you should only modify that line if the definition in [common]\n" + "; is not accurate for that context.\n" + "; You can enlarge windows using 5CA1E00000000000=scale, for example:\n" + "; [DLG_218] ; IDD_LOCKSETTINGS\n" + "; 5CA1E00000000000=1.2\n" + "; This makes the above dialog 1.2x wider than default.\n\n"); + } + + WDL_StringKeyedArray common_found; + { + if (!translations_indexed.GetSize()) + { + fprintf(stderr,"no sections!\n"); + exit(1); + } + else if (translations_indexed.GetSize() > 4096) + { + fprintf(stderr,"too many translation sections, check input or adjust code here\n"); + exit(1); + } + fprintf(stderr,"%d sections\n",translations_indexed.GetSize()); + + int pos[4096]={0,}; + printf("[common]\n"); + WDL_FastString matchlist; + WDL_AssocArray ids(uint64cmpfunc); + int minpos = 0; + for (;;) + { + int matchcnt=0; + matchlist.Set(""); + const char *str=NULL; + for(x=minpos;x *l = translations_indexed.Enumerate(x,&secname); + int sz=l->GetSize(); + if (!str) + { + if (x>minpos) + { + memset(pos,0,sizeof(pos)); // start over + minpos=x; + } + while (!str && pos[x]Enumerate(pos[x]++,&str); + if (!*str || common_found.Get(str)) str=NULL; // skip if we've already analyzed this string + } + if (str) matchlist.Set(secname); + } + else + { + while (pos[x] < sz) + { + const char *tv=NULL; + l->Enumerate(pos[x],&tv); + int c = strcmp(tv,str); + if (c>0) break; + pos[x]++; + if (!c) + { + matchlist.Append(", "); + matchlist.Append(secname); + matchcnt++; + break; + } + } + } + } + if (matchcnt>0) + { + common_found.Insert(str,true); + //printf("; used by: %s\n",matchlist.Get()); + if (casemode==3) printf(";"); + WDL_UINT64 a = outputLine(str,casemode); + if (ids.Get(a)) + { + fprintf(stderr,"duplicate hash for strings in common section, hope this is OK.. 64 bit hash fail!\n"); + exit(1); + } +// printf("\n"); + ids.Insert(a,true); + } + if (minpos == x-1) break; + } + printf("\n"); + } + + for(x=0;x *p = translations.Enumerate(x,&nm); + if (x) printf("\n"); + char *secinfo = section_descs.Get(nm); + printf("[%s]%s%s\n",nm,secinfo?" ; ":"", secinfo?secinfo:""); + int a; + int y; + WDL_AssocArray ids(uint64cmpfunc); + for (a=0;a<2;a++) + { + for (y=0;yGetSize();y++) + { + char *strv=p->Get(y); + if (!*strv) continue; + if ((common_found.Get(strv)?1:0) != a) continue; + + if (a) printf(";^"); + else if (casemode==3) printf(";"); + + WDL_UINT64 a = outputLine(strv,casemode); + if (ids.Get(a)) + { + fprintf(stderr,"duplicate hash for strings in section, hope this is OK.. 64 bit hash fail!\n"); + exit(1); + } + ids.Insert(a,true); + } + } + } + return 0; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/.gitignore b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/.gitignore new file mode 100644 index 0000000..a44db36 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/.gitignore @@ -0,0 +1,7 @@ +Win32/Debug/ +Win32/Release/ +x64/Debug/ +x64/Release/ +*.sdf +*.opensdf +*.v12.suo diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Base.lproj/MainMenu.xib b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..5e4de97 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Base.lproj/MainMenu.xib @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit-Info.plist b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit-Info.plist new file mode 100644 index 0000000..0fb4d8c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit-Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + LangPackEdit + CFBundleIdentifier + nobody.wdl.langpackedit + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.009 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright © 2022 Cockos Inc + NSMainNibFile + MainMenu + NSPrincipalClass + SWELLApplication + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit-Prefix.pch b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit-Prefix.pch new file mode 100644 index 0000000..4187f19 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.icns b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.icns new file mode 100644 index 0000000..6eeef52 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.icns differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.sln b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.sln new file mode 100644 index 0000000..ca3a7c1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LangPackEdit", "LangPackEdit.vcxproj", "{DC8CCBA9-5214-48ED-96AE-C18B212FBE10}" +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 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|Win32.ActiveCfg = Debug|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|Win32.Build.0 = Debug|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|x64.ActiveCfg = Debug|x64 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|x64.Build.0 = Debug|x64 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|Win32.ActiveCfg = Release|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|Win32.Build.0 = Release|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|x64.ActiveCfg = Release|x64 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.vcxproj b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.vcxproj new file mode 100644 index 0000000..d5061fc --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10} + Win32Proj + LangPackEdit + + + + Application + true + v120 + MultiByte + + + Application + true + v120 + MultiByte + + + Application + false + v120 + true + MultiByte + + + Application + false + v120 + true + MultiByte + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\ + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + false + + + Windows + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + false + + + Windows + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.vcxproj.filters b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.vcxproj.filters new file mode 100644 index 0000000..95a0a99 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;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 + + + {26d63633-dc60-4f96-afe7-14e5aa4ad154} + + + + + + + + Header Files + + + + + Source Files\WDL + + + Source Files\WDL + + + Source Files + + + Source Files\WDL + + + Source Files\WDL + + + + + Resource Files + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.xcodeproj/project.pbxproj b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7ab354a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/LangPackEdit.xcodeproj/project.pbxproj @@ -0,0 +1,417 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 338536692957A46300048720 /* langpack_edit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 338536682957A46300048720 /* langpack_edit.cpp */; }; + 3385366B2957A4D300048720 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3385366A2957A4D300048720 /* main.m */; }; + 338536702957A62F00048720 /* LangPackEdit.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3385366E2957A62F00048720 /* LangPackEdit.icns */; }; + 338536732957A6EB00048720 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 338536712957A6EB00048720 /* MainMenu.xib */; }; + 3385932D185B555500FDFFEA /* wndsize.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3385932C185B555500FDFFEA /* wndsize.cpp */; }; + 33AA3116185A7E8D001D767E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33AA3115185A7E8D001D767E /* Cocoa.framework */; }; + 33AA3126185A7E8D001D767E /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 33AA3124185A7E8D001D767E /* Credits.rtf */; }; + 33B8832B2958A0C300A9EBFF /* filebrowse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33B8832A2958A0C300A9EBFF /* filebrowse.cpp */; }; + 33B8832E2958A3BD00A9EBFF /* localize.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33B8832D2958A3BD00A9EBFF /* localize.cpp */; }; + 33F96581185A7F79004C7070 /* swell-appstub.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96576185A7F79004C7070 /* swell-appstub.mm */; }; + 33F96582185A7F79004C7070 /* swell-dlg.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96577185A7F79004C7070 /* swell-dlg.mm */; }; + 33F96583185A7F79004C7070 /* swell-gdi.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96578185A7F79004C7070 /* swell-gdi.mm */; }; + 33F96584185A7F79004C7070 /* swell-ini.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33F96579185A7F79004C7070 /* swell-ini.cpp */; }; + 33F96585185A7F79004C7070 /* swell-kb.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657A185A7F79004C7070 /* swell-kb.mm */; }; + 33F96586185A7F79004C7070 /* swell-menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657B185A7F79004C7070 /* swell-menu.mm */; }; + 33F96587185A7F79004C7070 /* swell-misc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657C185A7F79004C7070 /* swell-misc.mm */; }; + 33F96588185A7F79004C7070 /* swell-miscdlg.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657D185A7F79004C7070 /* swell-miscdlg.mm */; }; + 33F96589185A7F79004C7070 /* swell-wnd.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657E185A7F79004C7070 /* swell-wnd.mm */; }; + 33F9658A185A7F79004C7070 /* swell.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657F185A7F79004C7070 /* swell.cpp */; }; + 33F9658B185A7F79004C7070 /* swellappmain.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96580185A7F79004C7070 /* swellappmain.mm */; }; + 33F9658D185A8228004C7070 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33F9658C185A8228004C7070 /* Carbon.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 338536682957A46300048720 /* langpack_edit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = langpack_edit.cpp; sourceTree = SOURCE_ROOT; }; + 3385366A2957A4D300048720 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; }; + 3385366C2957A62F00048720 /* LangPackEdit-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "LangPackEdit-Info.plist"; sourceTree = SOURCE_ROOT; }; + 3385366D2957A62F00048720 /* LangPackEdit-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LangPackEdit-Prefix.pch"; sourceTree = SOURCE_ROOT; }; + 3385366E2957A62F00048720 /* LangPackEdit.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = LangPackEdit.icns; sourceTree = SOURCE_ROOT; }; + 338536722957A6EB00048720 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = SOURCE_ROOT; }; + 3385932C185B555500FDFFEA /* wndsize.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wndsize.cpp; path = ../../wingui/wndsize.cpp; sourceTree = ""; }; + 33AA3112185A7E8D001D767E /* LangPackEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LangPackEdit.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33AA3115185A7E8D001D767E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 33AA3118185A7E8D001D767E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 33AA3119185A7E8D001D767E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + 33AA311A185A7E8D001D767E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 33AA311F185A7E8D001D767E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 33AA3125185A7E8D001D767E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; + 33B8832A2958A0C300A9EBFF /* filebrowse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ../../filebrowse.cpp; sourceTree = ""; }; + 33B8832C2958A3BD00A9EBFF /* localize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = localize.h; path = ../localize.h; sourceTree = ""; }; + 33B8832D2958A3BD00A9EBFF /* localize.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = localize.cpp; path = ../localize.cpp; sourceTree = ""; }; + 33F96576185A7F79004C7070 /* swell-appstub.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-appstub.mm"; path = "../../swell/swell-appstub.mm"; sourceTree = ""; }; + 33F96577185A7F79004C7070 /* swell-dlg.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-dlg.mm"; path = "../../swell/swell-dlg.mm"; sourceTree = ""; }; + 33F96578185A7F79004C7070 /* swell-gdi.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-gdi.mm"; path = "../../swell/swell-gdi.mm"; sourceTree = ""; }; + 33F96579185A7F79004C7070 /* swell-ini.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "swell-ini.cpp"; path = "../../swell/swell-ini.cpp"; sourceTree = ""; }; + 33F9657A185A7F79004C7070 /* swell-kb.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-kb.mm"; path = "../../swell/swell-kb.mm"; sourceTree = ""; }; + 33F9657B185A7F79004C7070 /* swell-menu.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-menu.mm"; path = "../../swell/swell-menu.mm"; sourceTree = ""; }; + 33F9657C185A7F79004C7070 /* swell-misc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-misc.mm"; path = "../../swell/swell-misc.mm"; sourceTree = ""; }; + 33F9657D185A7F79004C7070 /* swell-miscdlg.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-miscdlg.mm"; path = "../../swell/swell-miscdlg.mm"; sourceTree = ""; }; + 33F9657E185A7F79004C7070 /* swell-wnd.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-wnd.mm"; path = "../../swell/swell-wnd.mm"; sourceTree = ""; }; + 33F9657F185A7F79004C7070 /* swell.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = swell.cpp; path = ../../swell/swell.cpp; sourceTree = ""; }; + 33F96580185A7F79004C7070 /* swellappmain.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = swellappmain.mm; path = ../../swell/swellappmain.mm; sourceTree = ""; }; + 33F9658C185A8228004C7070 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33AA310F185A7E8D001D767E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 33F9658D185A8228004C7070 /* Carbon.framework in Frameworks */, + 33AA3116185A7E8D001D767E /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33AA3109185A7E8D001D767E = { + isa = PBXGroup; + children = ( + 338536712957A6EB00048720 /* MainMenu.xib */, + 338536682957A46300048720 /* langpack_edit.cpp */, + 3385366C2957A62F00048720 /* LangPackEdit-Info.plist */, + 3385366D2957A62F00048720 /* LangPackEdit-Prefix.pch */, + 3385366E2957A62F00048720 /* LangPackEdit.icns */, + 3385366A2957A4D300048720 /* main.m */, + 33AA3149185A7EEC001D767E /* WDL */, + 33AA311E185A7E8D001D767E /* InfoPlist.strings */, + 33AA3124185A7E8D001D767E /* Credits.rtf */, + 33AA3114185A7E8D001D767E /* Frameworks */, + 33AA3113185A7E8D001D767E /* Products */, + ); + sourceTree = ""; + }; + 33AA3113185A7E8D001D767E /* Products */ = { + isa = PBXGroup; + children = ( + 33AA3112185A7E8D001D767E /* LangPackEdit.app */, + ); + name = Products; + sourceTree = ""; + }; + 33AA3114185A7E8D001D767E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 33F9658C185A8228004C7070 /* Carbon.framework */, + 33AA3115185A7E8D001D767E /* Cocoa.framework */, + 33AA3117185A7E8D001D767E /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + 33AA3117185A7E8D001D767E /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 33AA3118185A7E8D001D767E /* AppKit.framework */, + 33AA3119185A7E8D001D767E /* CoreData.framework */, + 33AA311A185A7E8D001D767E /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 33AA3149185A7EEC001D767E /* WDL */ = { + isa = PBXGroup; + children = ( + 33B8832A2958A0C300A9EBFF /* filebrowse.cpp */, + 33B8832D2958A3BD00A9EBFF /* localize.cpp */, + 33B8832C2958A3BD00A9EBFF /* localize.h */, + 3385932C185B555500FDFFEA /* wndsize.cpp */, + 33AA314A185A7EF4001D767E /* swell */, + ); + name = WDL; + path = .; + sourceTree = ""; + }; + 33AA314A185A7EF4001D767E /* swell */ = { + isa = PBXGroup; + children = ( + 33F96576185A7F79004C7070 /* swell-appstub.mm */, + 33F96577185A7F79004C7070 /* swell-dlg.mm */, + 33F96578185A7F79004C7070 /* swell-gdi.mm */, + 33F96579185A7F79004C7070 /* swell-ini.cpp */, + 33F9657A185A7F79004C7070 /* swell-kb.mm */, + 33F9657B185A7F79004C7070 /* swell-menu.mm */, + 33F9657C185A7F79004C7070 /* swell-misc.mm */, + 33F9657D185A7F79004C7070 /* swell-miscdlg.mm */, + 33F9657E185A7F79004C7070 /* swell-wnd.mm */, + 33F9657F185A7F79004C7070 /* swell.cpp */, + 33F96580185A7F79004C7070 /* swellappmain.mm */, + ); + name = swell; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33AA3111185A7E8D001D767E /* LangPackEdit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33AA3143185A7E8D001D767E /* Build configuration list for PBXNativeTarget "LangPackEdit" */; + buildPhases = ( + 3304E16F1FC2253B00290319 /* ShellScript */, + 33AA310E185A7E8D001D767E /* Sources */, + 33AA310F185A7E8D001D767E /* Frameworks */, + 33AA3110185A7E8D001D767E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LangPackEdit; + productName = langpack_edit; + productReference = 33AA3112185A7E8D001D767E /* LangPackEdit.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33AA310A185A7E8D001D767E /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = cockos; + }; + buildConfigurationList = 33AA310D185A7E8D001D767E /* Build configuration list for PBXProject "LangPackEdit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + Base, + ); + mainGroup = 33AA3109185A7E8D001D767E; + productRefGroup = 33AA3113185A7E8D001D767E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33AA3111185A7E8D001D767E /* LangPackEdit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33AA3110185A7E8D001D767E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 338536732957A6EB00048720 /* MainMenu.xib in Resources */, + 33AA3126185A7E8D001D767E /* Credits.rtf in Resources */, + 338536702957A62F00048720 /* LangPackEdit.icns in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3304E16F1FC2253B00290319 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/bin/perl ../../swell/swell_resgen.pl res.rc\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33AA310E185A7E8D001D767E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33B8832E2958A3BD00A9EBFF /* localize.cpp in Sources */, + 33F96581185A7F79004C7070 /* swell-appstub.mm in Sources */, + 33F9658B185A7F79004C7070 /* swellappmain.mm in Sources */, + 33F9658A185A7F79004C7070 /* swell.cpp in Sources */, + 33F96584185A7F79004C7070 /* swell-ini.cpp in Sources */, + 3385932D185B555500FDFFEA /* wndsize.cpp in Sources */, + 33F96589185A7F79004C7070 /* swell-wnd.mm in Sources */, + 33F96582185A7F79004C7070 /* swell-dlg.mm in Sources */, + 33F96585185A7F79004C7070 /* swell-kb.mm in Sources */, + 33F96586185A7F79004C7070 /* swell-menu.mm in Sources */, + 33F96583185A7F79004C7070 /* swell-gdi.mm in Sources */, + 338536692957A46300048720 /* langpack_edit.cpp in Sources */, + 33B8832B2958A0C300A9EBFF /* filebrowse.cpp in Sources */, + 33F96587185A7F79004C7070 /* swell-misc.mm in Sources */, + 3385366B2957A4D300048720 /* main.m in Sources */, + 33F96588185A7F79004C7070 /* swell-miscdlg.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 338536712957A6EB00048720 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 338536722957A6EB00048720 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; + 33AA311E185A7E8D001D767E /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 33AA311F185A7E8D001D767E /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 33AA3124185A7E8D001D767E /* Credits.rtf */ = { + isa = PBXVariantGroup; + children = ( + 33AA3125185A7E8D001D767E /* en */, + ); + name = Credits.rtf; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 33AA3141185A7E8D001D767E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++98"; + CLANG_CXX_LIBRARY = "libstdc++"; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_STRICT_ALIASING = NO; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 33AA3142185A7E8D001D767E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++98"; + CLANG_CXX_LIBRARY = "libstdc++"; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_STRICT_ALIASING = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + SDKROOT = macosx; + }; + name = Release; + }; + 33AA3144185A7E8D001D767E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LIBRARY = "libc++"; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "LangPackEdit-Prefix.pch"; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + INFOPLIST_FILE = "LangPackEdit-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_BUNDLE_IDENTIFIER = nobody.wdl.langpackedit; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 33AA3145185A7E8D001D767E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LIBRARY = "libc++"; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "LangPackEdit-Prefix.pch"; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + INFOPLIST_FILE = "LangPackEdit-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_BUNDLE_IDENTIFIER = nobody.wdl.langpackedit; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33AA310D185A7E8D001D767E /* Build configuration list for PBXProject "LangPackEdit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33AA3141185A7E8D001D767E /* Debug */, + 33AA3142185A7E8D001D767E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33AA3143185A7E8D001D767E /* Build configuration list for PBXNativeTarget "LangPackEdit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33AA3144185A7E8D001D767E /* Debug */, + 33AA3145185A7E8D001D767E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33AA310A185A7E8D001D767E /* Project object */; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Makefile b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Makefile new file mode 100644 index 0000000..a2558f4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Makefile @@ -0,0 +1,110 @@ +APPNAME=langpack_edit + +WDL_PATH = ../.. +vpath swell%.cpp $(WDL_PATH)/swell +vpath lice%.cpp $(WDL_PATH)/lice +vpath %.c $(WDL_PATH) +vpath %.cpp $(WDL_PATH) $(WDL_PATH)/wingui $(WDL_PATH)/localize + +###### Objects and resources (probably too many) +SWELL_OBJS = swell.o swell-ini.o swell-miscdlg-generic.o swell-wnd-generic.o \ + swell-menu-generic.o swell-kb-generic.o swell-dlg-generic.o \ + swell-gdi-generic.o swell-misc-generic.o swell-gdi-lice.o \ + swell-generic-gdk.o swell-appstub-generic.o swell-modstub-generic.o + +LICE_OBJS = lice_image.o lice_arc.o lice_line.o lice_text.o \ + lice_textnew.o lice.o lice_colorspace.o + +OTHER_OBJS = wndsize.o localize.o filebrowse.o + +RESFILES = res.rc_mac_dlg res.rc_mac_menu + +OBJS += langpack_edit.o $(SWELL_OBJS) $(OTHER_OBJS) + + +###### Compiler/Linker flags +CFLAGS += -pipe -fvisibility=hidden -fno-math-errno -fPIC -DPIC -Wall -Wshadow -Wtype-limits \ + -Wno-unused-function -Wno-multichar -Wno-unused-result + +CFLAGS += -D_FILE_OFFSET_BITS=64 + +ARCH := $(shell uname -m) +PKG_CONFIG = pkg-config + +ifndef ALLOW_WARNINGS + CFLAGS += -Werror +endif +ifndef DEPRECATED_WARNINGS + CFLAGS += -Wno-deprecated-declarations +endif + +ifeq ($(ARCH),arm64) + CFLAGS += -fsigned-char +else + ifneq ($(filter arm%,$(ARCH)),) + CFLAGS += -fsigned-char -mfpu=vfp -march=armv6t2 -marm + endif + ifeq ($(ARCH),aarch64) + CFLAGS += -fsigned-char + endif +endif + + +ifndef RELEASE + CFLAGS += -O0 -g -D_DEBUG -DWDL_CHECK_FOR_NON_UTF8_FOPEN +else + CFLAGS += -O2 -DNDEBUG +endif + +LINKEXTRA = -lpthread -ldl + +ifndef NOGDK + ifdef GDK2 + CFLAGS += -DSWELL_TARGET_GDK=2 $(shell $(PKG_CONFIG) --cflags gdk-2.0) + LINKEXTRA += $(shell $(PKG_CONFIG) --libs gdk-2.0) + LINKEXTRA += -lX11 -lXi + else + ifdef SWELL_SUPPORT_GTK + CFLAGS += -DSWELL_TARGET_GDK=3 $(shell $(PKG_CONFIG) --cflags gtk+-3.0) -DSWELL_SUPPORT_GTK + else + CFLAGS += -DSWELL_TARGET_GDK=3 $(shell $(PKG_CONFIG) --cflags gdk-3.0) + endif + LINKEXTRA += -lX11 -lXi -lGL + ifdef SWELL_SUPPORT_GTK + LINKEXTRA += $(shell $(PKG_CONFIG) --libs gtk+-3.0) + else + LINKEXTRA += $(shell $(PKG_CONFIG) --libs gdk-3.0) + endif + endif + CFLAGS += -DSWELL_LICE_GDI + OBJS += $(LICE_OBJS) + + ifndef NOFREETYPE + CFLAGS += -DSWELL_FREETYPE $(shell $(PKG_CONFIG) --cflags freetype2) + LINKEXTRA += $(shell $(PKG_CONFIG) --libs freetype2) + ifndef NOFONTCONFIG + CFLAGS += -DSWELL_FONTCONFIG + LINKEXTRA += -lfontconfig + endif + endif +endif + + +CXXFLAGS = $(CFLAGS) + + +default: $(APPNAME) + +.PHONY: clean run + +$(RESFILES): res.rc + $(WDL_PATH)/swell/swell_resgen.pl $^ + +$(APPNAME): $(RESFILES) $(OBJS) + $(CXX) -o $@ $(CFLAGS) $(OBJS) $(LINKEXTRA) + +run: $(APPNAME) + ./$^ + +clean: + -rm $(OBJS) $(APPNAME) $(RESFILES) diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Resources/main.png b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Resources/main.png new file mode 100644 index 0000000..a0d791c Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/Resources/main.png differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/en.lproj/Credits.rtf b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/en.lproj/Credits.rtf new file mode 100644 index 0000000..33bd334 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/en.lproj/Credits.rtf @@ -0,0 +1,7 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1265 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\vieww9600\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 + +\f0\b\fs24 \cf0 This is it, word.} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/en.lproj/InfoPlist.strings b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/icon1.ico b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/icon1.ico new file mode 100644 index 0000000..09660b3 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/icon1.ico differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/langpack_edit.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/langpack_edit.cpp new file mode 100644 index 0000000..6be0c33 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/langpack_edit.cpp @@ -0,0 +1,1285 @@ +/* + LangPackEdit + Copyright (C) 2022 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. +*/ + +#ifdef _WIN32 +#include +#include +#endif + +#include "../../swell/swell.h" + +#include "../../win32_utf8.h" +#include "../../wingui/wndsize.h" +#include "../../filebrowse.h" +#include "../../wdlstring.h" + +#include "../../assocarray.h" + +#include "../../localize/localize.h" +#include "../../lineparse.h" + +#define WDL_HASSTRINGS_REWUTF8_HOOK(str, base) \ + if ((str) > (base)) switch ((str)[0]) { \ + case 'r': case 'n': case 't': case '0': if ((str)[-1] == '\\') (str)--; break; \ + case 'd': case 's': case 'f': case 'g': case 'c': case 'u': if ((str)[-1] == '%') (str)--; break; \ + } +#include "../../has_strings.h" + +#include "resource.h" + + +#ifndef HDF_SORTUP +#define HDF_SORTUP 0x0400 +#endif +#ifndef HDF_SORTDOWN +#define HDF_SORTDOWN 0x0200 +#endif + +void ListView_SetHeaderSortArrow(HWND hlist, int col, int dir) +{ + HWND hhdr=ListView_GetHeader(hlist); + if (!hhdr) return; + for (int i=0; i < Header_GetItemCount(hhdr); ++i) + { + HDITEM hi = { HDI_FORMAT, 0, }; + Header_GetItem(hhdr, i, &hi); + hi.fmt &= ~(HDF_SORTUP|HDF_SORTDOWN); + if (i == col) hi.fmt |= (dir > 0 ? HDF_SORTUP : HDF_SORTDOWN); + Header_SetItem(hhdr, i, &hi); + } +} + + +template static void Restore_ListSelState(HWND hlist, const WDL_TypedBuf *order, const B *st) +{ +#ifdef __APPLE__ + SendMessage(hlist,WM_SETREDRAW,FALSE,0); +#endif + int x; + const int n = order->GetSize(); + int frow = -1; + for (x = 0; x < n; x ++) + { + bool sel = st->Get(order->Get()[x]); + if (frow < 0 && sel) frow = x; + ListView_SetItemState(hlist, x, sel ? LVIS_SELECTED : 0, LVIS_SELECTED); + } + if (frow >= 0) + ListView_EnsureVisible(hlist, frow, FALSE); +#ifdef __APPLE__ + SendMessage(hlist,WM_SETREDRAW,TRUE,0); +#endif +} + +template static void Save_ListSelState(HWND hlist, const WDL_TypedBuf *order, B *st) +{ + int x; + const int n = order->GetSize(); + for (x = 0; x < n; x ++) + { + if (ListView_GetItemState(hlist,x,LVIS_SELECTED)) st->AddUnsorted(order->Get()[x], true); + } + st->Resort(); +} + + + +#if !defined(_WIN32) && !defined(__APPLE__) +bool g_quit; +#endif + +HINSTANCE g_hInstance; +WDL_FastString g_ini_file; + +enum { + COL_STATE=0, // if we edit these need to edit the IDs of ID_COL_* in resource.h + COL_ID, + COL_ROW_IDX, + COL_TEMPLATE, + COL_LOCALIZED, + COL_COMMON_LOCALIZED, + COL_MAX, +}; + + +struct pack_rec { + char *template_str, *pack_str; + + char *key_desc; // key name + comments + + int common_idx; + + static void freeptrs(pack_rec r) + { + free(r.key_desc); + free(r.template_str); + free(r.pack_str); + } +}; + +struct editor_instance { + editor_instance() : m_recs(false, pack_rec::freeptrs), + m_column_no_searchflags(0), m_sort_col(COL_ID), m_sort_rev(false), + m_hwnd(NULL), m_dirty(false) { } + ~editor_instance() { } + + WDL_FastString m_pack_fn; + + WDL_StringKeyedArray2 m_recs; + WDL_TypedBuf m_display_order; + int m_column_no_searchflags; // bits for exclude from search per column + int m_sort_col; + bool m_sort_rev; + + HWND m_hwnd; + bool m_dirty; + + WDL_WndSizer m_resize; + + void load_file(const char *filename, bool is_template); + void save_file(const char *filename); + + void cull_recs(); + void refresh_list(); + void sort_display_order(); + void on_sort_change(); + + const char *get_rec_value(const pack_rec *r, const char *k, int w) const + { + __LOCALIZE_LCACHE("(empty)","langpackedit",empt); + switch (w) + { + case COL_STATE: + { + if (!r->template_str) + { + if (strstr(k,":5CA1E00000000000")) return ""; + __LOCALIZE_LCACHE("not-in-template","langpackedit",nit); + return nit; + } + if (r->pack_str) return ""; + if (r->common_idx>=0) + { + const char *k2; + pack_rec *r2 = m_recs.EnumeratePtr(r->common_idx,&k2); + if (WDL_NORMALLY(r2 && k2)) + { + WDL_ASSERT(!strcmp(strstr(k2,":"),strstr(k,":"))); + __LOCALIZE_LCACHE("localized-in-common","langpackedit",lic); + __LOCALIZE_LCACHE("common-not-localized","langpackedit",cnl); + if (r2->pack_str) return lic; + return cnl; + } + } + return "not-localized"; + } + case COL_ID: return r->key_desc ? r->key_desc : k; + case COL_TEMPLATE: return r->template_str; + case COL_LOCALIZED: return r->pack_str == NULL ? "" : r->pack_str[0] ? r->pack_str : empt; + case COL_COMMON_LOCALIZED: + if (r->common_idx>=0) + { + const char *k2; + pack_rec *r2 = m_recs.EnumeratePtr(r->common_idx,&k2); + if (WDL_NORMALLY(r2 && k2)) + { + WDL_ASSERT(!strcmp(strstr(k2,":"),strstr(k,":"))); + return r2->pack_str == NULL ? "" : r2->pack_str[0] ? r2->pack_str : empt; + } + } + break; + + } + return NULL; + } + + const char *get_row_value(int row, int w) const + { + if (row < 0 || row >= m_display_order.GetSize()) return ""; + const char *k; + pack_rec *r = m_recs.EnumeratePtr(m_display_order.Get()[row],&k); + if (!r || !k) return ""; + const char *rv = get_rec_value(r,k,w); + return rv ? rv : ""; + } + + bool edit_row(int rec_idx, int other_action=IDC_LOCALIZED_STRING); + bool on_key(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + void item_context_menu(); + + void set_dirty() + { + if (!m_dirty) + { + m_dirty=true; + set_caption(); + } + } + void set_caption() + { + if (m_hwnd) + { + char tmp[512]; + snprintf(tmp,sizeof(tmp),"%s%sLangPackEdit%s", + m_pack_fn.get_filepart(),m_pack_fn.GetLength() ? " - ": "",m_dirty ? __LOCALIZE(" (unsaved)","langpackedit") :""); + SetWindowText(m_hwnd,tmp); + } + } + bool prompt_exit() + { + if (m_dirty) + { + int a = MessageBox(m_hwnd,__LOCALIZE("LangPack is not saved, save before exiting?","langpackedit"), + __LOCALIZE("Unsaved","langpackedit"),MB_YESNOCANCEL); + if (a == IDCANCEL) return false; + if (a == IDNO) m_dirty = false; + else SendMessage(m_hwnd,WM_COMMAND,IDC_PACK_SAVE,0); + } + return !m_dirty; + } +}; + +static void del_array(WDL_AssocArray *d) { delete d; } + +static void format_section_id(char *buf, size_t bufsz, const char *section, WDL_UINT64 id) +{ + snprintf(buf,bufsz,"%s:%08X%08X",section, (int)(id>>32),(int)(id&0xffffffff)); +} + +static const char *parse_section_id(const char *k, char *buf, int bufsz) // returns ID or NULL +{ + if (buf) lstrcpyn_safe(buf, k, bufsz); + + char *base = buf ? buf : (char*)k, *p = base; + while (*p) p++; + while (p > base && *p != ':') p--; + if (WDL_NOT_NORMALLY(p==base)) return NULL; + if (buf) *p=0; + return p+1; +} + +void editor_instance::save_file(const char *filename) +{ + FILE *fp = fopen(filename,"wb"); + if (!fp) + { + MessageBox(m_hwnd,__LOCALIZE("Error opening file for writing","langpackedit"), + __LOCALIZE("Error","langpackedit"),MB_OK); + return; + } + char buf[32768]; + buf[0]=0; + if (WDL_NORMALLY(m_hwnd)) + GetDlgItemText(m_hwnd,IDC_COMMENTS,buf,sizeof(buf)); + WDL_remove_trailing_whitespace(buf); + fprintf(fp,"%s\r\n",buf); + + char last_sec[1024]; + last_sec[0]=0; + + for (int x = 0; x < m_recs.GetSize(); x ++) + { + const char *k; + const pack_rec *rec = m_recs.EnumeratePtr(x,&k); + if (!rec->pack_str) continue; + + char sec[256]; + const char *id = parse_section_id(k,sec,sizeof(sec)); + if (WDL_NORMALLY(id)) + { + if (stricmp(last_sec,sec)) + { + lstrcpyn_safe(last_sec,sec,sizeof(last_sec)); + const char *trail = ""; + if (rec->key_desc) + { + trail = strstr(rec->key_desc," "); + trail = trail ? (trail+1) : ""; + } + fprintf(fp,"\r\n[%s]%s\r\n",sec,trail); + } + fprintf(fp,"%s=%s\r\n",id,rec->pack_str); + } + } + fclose(fp); +} + +void editor_instance::load_file(const char *filename, bool is_template) +{ + for (int x = 0; x < m_recs.GetSize(); x ++) + { + pack_rec *r = m_recs.EnumeratePtr(x); + if (is_template) + { + free(r->template_str); + r->template_str = NULL; + } + else + { + free(r->pack_str); + r->pack_str = NULL; + } + } + + WDL_StringKeyedArray extra(false, WDL_StringKeyedArray::freecharptr); + if (*filename) + { + WDL_StringKeyedArray< WDL_AssocArray * > r(false,del_array); + WDL_LoadLanguagePackInternal(filename,&r,NULL, is_template, true, &extra); + + for (int si = 0; si < r.GetSize(); si ++) + { + const char *sec_name; + WDL_AssocArray *sec = r.Enumerate(si,&sec_name); + for (int i = 0; i < sec->GetSize(); i ++) + { + WDL_UINT64 id; + const char *value = sec->Enumerate(i,&id); + if (WDL_NOT_NORMALLY(!value)) break; + + char rec_buf[256], key_desc[256]; + format_section_id(rec_buf,sizeof(rec_buf),sec_name, id); + + { + const char *p = extra.Get(sec_name); + if (p) + snprintf(key_desc,sizeof(key_desc),"%.100s %s",rec_buf,p); + else + key_desc[0]=0; + } + + pack_rec *rec = m_recs.GetPtr(rec_buf); + if (!rec) + { + pack_rec newr = { 0 }; + if (is_template) newr.template_str = strdup(value); + else newr.pack_str = strdup(value); + + if (key_desc[0]) newr.key_desc = strdup(key_desc); + m_recs.Insert(rec_buf,newr); + } + else + { + if (is_template || key_desc[0]) + { + free(rec->key_desc); + rec->key_desc = key_desc[0] ? strdup(key_desc) : NULL; + } + if (is_template) + { + free(rec->template_str); + rec->template_str = strdup(value); + } + else + { + free(rec->pack_str); + rec->pack_str = strdup(value); + } + } + } + } + } + cull_recs(); + refresh_list(); + + if (m_hwnd) + { + SetDlgItemText(m_hwnd,is_template ? IDC_TEMPLATE : IDC_PACK, *filename ? filename : __LOCALIZE("(none)","langpackedit")); + + if (!is_template) + { + WDL_FastString fs; + for (int x=0; ; x++) + { + char tmp[512]; + snprintf(tmp,sizeof(tmp),"_initial_comment_%d",x); + const char *p = extra.Get(tmp); + if (!p) break; + if (fs.GetLength()) fs.Append("\r\n"); + fs.Append(p); + } + if (fs.GetLength()) + WDL_remove_trailing_whitespace((char *)fs.Get()); + SetDlgItemText(m_hwnd,IDC_COMMENTS,fs.Get()); + } + } +} + +void editor_instance::cull_recs() +{ + for (int x = 0; x < m_recs.GetSize(); x ++) + { + pack_rec *r = m_recs.EnumeratePtr(x); + if (!r->template_str && !r->pack_str) + m_recs.DeleteByIndex(x--); + } +} + +void editor_instance::refresh_list() +{ + HWND list = WDL_NORMALLY(m_hwnd) ? GetDlgItem(m_hwnd,IDC_LIST) : NULL; + WDL_IntKeyedArray selState; + if (list) + Save_ListSelState(list, &m_display_order, &selState); + + m_display_order.Resize(0,false); + + LineParser lp; + if (m_hwnd) + { + char filter[512]; + GetDlgItemText(m_hwnd,IDC_FILTER,filter,sizeof(filter)); + WDL_makeSearchFilter(filter, &lp); + } + const bool do_filt = lp.getnumtokens()>0; + for (int x = 0; x < m_recs.GetSize(); x ++) + { + const char *k = NULL; + pack_rec *r = m_recs.EnumeratePtr(x,&k); + if (WDL_NOT_NORMALLY(!r)) break; + + if (strnicmp(k,"common:",7)) + { + const char *sid = parse_section_id(k,NULL,0); + if (WDL_NORMALLY(sid)) + { + char tmp[256]; + snprintf(tmp,sizeof(tmp),"common:%s",sid); + r->common_idx = m_recs.GetIdx(tmp); + } + } + else + r->common_idx = -1; + + const char *strs[COL_MAX]; + int nc = 0; + if (do_filt) + { + for (int c =0; c < COL_MAX; c ++) + { + if (m_column_no_searchflags & (1< idx_b ? 1 : 0; + + const int col = sort_inst->m_sort_col; + switch (col) + { + case COL_STATE: + case COL_TEMPLATE: + case COL_LOCALIZED: + case COL_COMMON_LOCALIZED: + { + const char *ak, *bk; + const pack_rec *ar = sort_inst->m_recs.EnumeratePtr(idx_a,&ak); + const pack_rec *br = sort_inst->m_recs.EnumeratePtr(idx_b,&bk); + const char *av = ar ? sort_inst->get_rec_value(ar,ak,col) : NULL; + const char *bv = br ? sort_inst->get_rec_value(br,bk,col) : NULL; + if (av || bv) + { + if (av) while (*av == ' ') av++; + if (bv) while (*bv == ' ') bv++; + int diff = WDL_strcmp_logical_ex(av?av:"",bv?bv:"",0,WDL_STRCMP_LOGICAL_EX_FLAG_UTF8CONVERT); + if (diff) ret = diff; + } + } + break; + } + return sort_inst->m_sort_rev ? -ret : ret; +} + + +void editor_instance::on_sort_change() +{ + char tmp[64]; + snprintf(tmp, sizeof(tmp), "%d", m_sort_col); + WritePrivateProfileString("LangPackEdit", "sortcol", tmp, g_ini_file.Get()); + WritePrivateProfileString("LangPackEdit", "sortrev", m_sort_rev ? "1" : "0", g_ini_file.Get()); + + if (!m_hwnd) return; + HWND list = GetDlgItem(m_hwnd,IDC_LIST); + + if (!list) return; + WDL_IntKeyedArray selState; + Save_ListSelState(list, &m_display_order, &selState); + + sort_display_order(); + + ListView_SetHeaderSortArrow(list, m_sort_col, (m_sort_rev ? -1 : 1)); + ListView_SetItemCount(list, m_display_order.GetSize()); + Restore_ListSelState(list, &m_display_order, &selState); + + ListView_RedrawItems(list, 0, m_display_order.GetSize()); +} + +void editor_instance::sort_display_order() +{ + if (m_display_order.GetSize() > 1) + { + sort_inst = this; + qsort(m_display_order.Get(), m_display_order.GetSize(), sizeof(m_display_order.Get()[0]), sort_func); + sort_inst = NULL; + } +} + +WDL_DLGRET editorProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + { + void **p = (void **)lParam; + editor_instance *edit = (editor_instance *)p[0]; + int item = (int) (INT_PTR) p[1]; + const char *k; + pack_rec *rec = edit->m_recs.EnumeratePtr(item,&k); + if (WDL_NORMALLY(rec)) + { + char tmp[512]; + snprintf(tmp,sizeof(tmp),__LOCALIZE_VERFMT("Localize: %s","langpackedit"),k); + SetWindowText(hwndDlg,tmp); + + if (rec->template_str) + SetDlgItemText(hwndDlg,IDC_TEMPLATE_STRING,rec->template_str); + + const char *common_str = NULL; + if (rec->common_idx>=0) + { + const char *k2; + pack_rec *r2 = edit->m_recs.EnumeratePtr(rec->common_idx,&k2); + if (WDL_NORMALLY(r2 && k2)) + { + WDL_ASSERT(!strcmp(strstr(k2,":"),strstr(k,":"))); + SetDlgItemText(hwndDlg,IDC_COMMON_STRING,r2->pack_str ? r2->pack_str : __LOCALIZE("(not yet localized)","langpackedit")); + common_str = r2->pack_str; + } + } + else if (!strnicmp(k,"common:",7)) + { + ShowWindow(GetDlgItem(hwndDlg,IDC_COMMON_LABEL),SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg,IDC_COMMON_STRING),SW_HIDE); + } + else + SetDlgItemText(hwndDlg,IDC_COMMON_STRING,__LOCALIZE("(not in [common])","langpackedit")); + + if (rec->pack_str || rec->template_str) + SetDlgItemText(hwndDlg,IDC_LOCALIZED_STRING,rec->pack_str ? rec->pack_str : + common_str ? common_str : rec->template_str); + } + } + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_REMOVE_LOCALIZATION: + case IDC_COPY_TEMPLATE: + case IDOK: + { + void **p = (void **)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + editor_instance *edit = (editor_instance *)p[0]; + int item = (int) (INT_PTR) p[1]; + const char *k; + pack_rec *rec = edit->m_recs.EnumeratePtr(item,&k); + if (WDL_NORMALLY(rec)) + { + if (LOWORD(wParam) == IDOK) + { + char buf[32768]; + GetDlgItemText(hwndDlg,IDC_LOCALIZED_STRING,buf,sizeof(buf)); + free(rec->pack_str); + rec->pack_str = strdup(buf); + } + else + { + free(rec->pack_str); + rec->pack_str = LOWORD(wParam) == IDC_COPY_TEMPLATE && rec->template_str ? + strdup(rec->template_str) : NULL; + } + } + } + EndDialog(hwndDlg,1); + break; + case IDCANCEL: + EndDialog(hwndDlg,0); + break; + } + break; + } + return 0; +} + +bool editor_instance::edit_row(int row, int other_action) +{ + if (row < 0 || row >= m_display_order.GetSize()) return false; + int rec_idx = m_display_order.Get()[row]; + + WDL_ASSERT(rec_idx>=0 && rec_idx < m_recs.GetSize()); + + if (other_action && other_action != IDC_LOCALIZED_STRING) + { + const char *k; + pack_rec *r = m_recs.EnumeratePtr(rec_idx,&k); + if (WDL_NORMALLY(r)) + { + switch (other_action) + { + case IDC_COMMON_STRING: + if (r->common_idx>=0 && r->common_idx < m_recs.GetSize()) + rec_idx = r->common_idx; + break; + case IDC_REMOVE_NONLOCALIZATION: + if (r->pack_str) + { + if (!r->template_str) return false; + if (strcmp(r->template_str,r->pack_str)) return false; + } + // fall through + case IDC_REMOVE_LOCALIZATION: + case IDC_COPY_TEMPLATE: + if (other_action == IDC_COPY_TEMPLATE && r->pack_str) return false; // do not modify already-localized strings + + free(r->pack_str); + r->pack_str = other_action == IDC_COPY_TEMPLATE && r->template_str ? strdup(r->template_str) : NULL; + return true; + case ID_SCALING_ADD: + { + char sec[256]; + if (WDL_NORMALLY(parse_section_id(k,sec,sizeof(sec)))) + { + lstrcatn(sec,":5CA1E00000000000",sizeof(sec)); + if (!m_recs.GetPtr(sec)) + { + pack_rec newr = { 0 }; + m_recs.Insert(sec,newr); + int idx = m_recs.GetIdx(sec); + if (WDL_NORMALLY(idx>=0)) + { + // added a rec, adjust indices and stick us at the end + for (int x = 0; x < m_display_order.GetSize(); x ++) + { + if (m_display_order.Get()[x] >= idx) + m_display_order.Get()[x]++; + } + m_display_order.Add(idx); + return true; + } + + } + } + } + return false; + } + } + } + + void *p[2] = { this, (void *)(INT_PTR) rec_idx }; + if (DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_RENAME), m_hwnd, editorProc, (LPARAM)p)) + { + refresh_list(); + set_dirty(); + return true; + } + return false; +} + + +bool editor_instance::on_key(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (!m_hwnd || (hwnd != m_hwnd && !IsChild(m_hwnd,hwnd))) + return false; + +#ifndef __APPLE__ + if (msg == WM_KEYDOWN && (wParam=='S' || wParam == 'O' || wParam == 'T')) + { + bool shift = !!(GetAsyncKeyState(VK_SHIFT)&0x8000); + bool ctrl = !!(GetAsyncKeyState(VK_CONTROL)&0x8000); + if (!shift && ctrl) + { + bool alt = !!(GetAsyncKeyState(VK_MENU)&0x8000); + int cmd = 0; + if (wParam == 'S' && ctrl) cmd = alt ? IDC_PACK_SAVE_AS : IDC_PACK_SAVE; + else if (wParam == 'O') cmd = IDC_PACK_LOAD; + else if (wParam == 'T') cmd = IDC_TEMPLATE_LOAD; + + if (cmd) + { + SendMessage(m_hwnd,WM_COMMAND,cmd,0); + return 1; + } + } + } +#endif + + HWND hlist = GetDlgItem(m_hwnd,IDC_LIST); + if (hwnd == hlist || IsChild(hlist,hwnd)) + { + if (msg == WM_KEYDOWN) switch (wParam) + { + case VK_RETURN: + SendMessage(m_hwnd,WM_COMMAND,IDC_LOCALIZED_STRING,0); + return true; + break; +#ifdef _WIN32 + case VK_APPS: + item_context_menu(); + return true; +#endif + case VK_BACK: + case VK_DELETE: + SendMessage(m_hwnd,WM_COMMAND,IDC_REMOVE_LOCALIZATION,0); + return true; + } + } + return false; +} + +void editor_instance::item_context_menu() +{ + HMENU menu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_CONTEXTMENU)); + POINT p; + GetCursorPos(&p); + TrackPopupMenu(GetSubMenu(menu,0),0,p.x,p.y,0,m_hwnd,NULL); + DestroyMenu(menu); +} + +const char *COL_DESCS[COL_MAX] = { + // !WANT_LOCALIZE_STRINGS_BEGIN:langpackedit + "State", + "ID", + "Row", + "Template", + "Localized", + "Common Localized", + // !WANT_LOCALIZE_STRINGS_END +}; + +int COL_SIZES[COL_MAX] = { + 120, + 120, + 30, + 240, + 240, + 240, +}; + +editor_instance g_editor; +WDL_DLGRET mainProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + enum { + TIMER_FILTER=1 + }; + switch (uMsg) + { + case WM_INITDIALOG: + g_editor.m_hwnd = hwndDlg; +#ifdef _WIN32 + { + HICON icon=LoadIcon(g_hInstance,MAKEINTRESOURCE(IDI_ICON1)); + SetClassLongPtr(hwndDlg,GCLP_HICON,(LPARAM)icon); + } +#endif + + g_editor.m_resize.init(hwndDlg); + g_editor.m_resize.init_item(IDC_FILTER,0,0,1,0); + g_editor.m_resize.init_item(IDC_TEMPLATE,0,0,1,0); + g_editor.m_resize.init_item(IDC_PACK,0,0,1,0); + g_editor.m_resize.init_item(IDC_COMMENTS,0,0,1,0); + g_editor.m_resize.init_item(IDC_LIST,0,0,1,1); + + { + HWND hlist = GetDlgItem(hwndDlg, IDC_LIST); + + int s=LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES; +#ifdef _WIN32 + s|=LVS_EX_DOUBLEBUFFER; +#endif + ListView_SetExtendedListViewStyleEx(hlist, s,s ); + +#ifdef _WIN32 + WDL_UTF8_HookListView(hlist); + SendMessage(hlist,LVM_SETUNICODEFORMAT,1,0); +#endif + for (int x = 0; x < COL_MAX; x ++) + { + LVCOLUMN lvc = { LVCF_TEXT|LVCF_WIDTH, 0, COL_SIZES[x], (char*)__localizeFunc(COL_DESCS[x],"langpackedit",LOCALIZE_FLAG_NOCACHE) }; + ListView_InsertColumn(hlist, x, &lvc); + } + } + + { + char buf[2048]; + GetPrivateProfileString("LangPackEdit","template","",buf,sizeof(buf),g_ini_file.Get()); + g_editor.load_file(buf,true); + + GetPrivateProfileString("LangPackEdit","lastpack","",buf,sizeof(buf),g_ini_file.Get()); + g_editor.load_file(buf,false); + + g_editor.m_column_no_searchflags = GetPrivateProfileInt("LangPackEdit","nosearchcols",0,g_ini_file.Get()); + g_editor.m_sort_col = GetPrivateProfileInt("LangPackEdit","sortcol",COL_ID,g_ini_file.Get()); + g_editor.m_sort_rev = GetPrivateProfileInt("LangPackEdit","sortrev",0,g_ini_file.Get()) > 0; + ListView_SetHeaderSortArrow(GetDlgItem(hwndDlg,IDC_LIST), g_editor.m_sort_col, (g_editor.m_sort_rev ? -1 : 1)); + g_editor.m_pack_fn.Set(buf); + g_editor.set_caption(); + } + return 1; + case WM_DESTROY: + g_editor.m_hwnd = NULL; +#ifdef __APPLE__ + SWELL_PostQuitMessage(0); +#elif defined(_WIN32) + PostQuitMessage(0); +#else + g_quit = true; +#endif + break; + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) + g_editor.m_resize.onResize(); + break; + case WM_TIMER: + switch (wParam) + { + case TIMER_FILTER: + KillTimer(hwndDlg,TIMER_FILTER); + g_editor.refresh_list(); + break; + } + break; + case WM_CLOSE: + if (g_editor.prompt_exit()) + DestroyWindow(hwndDlg); + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case ID_QUIT: + if (g_editor.prompt_exit()) + DestroyWindow(hwndDlg); + break; + case IDC_FILTER: + if (HIWORD(wParam) == EN_CHANGE) + { + SetTimer(hwndDlg,TIMER_FILTER,100,NULL); + } + break; + case IDC_PACK_SAVE_AS: + case IDC_PACK_SAVE: + + if (!g_editor.m_pack_fn.GetLength() || LOWORD(wParam) == IDC_PACK_SAVE_AS) + { + char newfn[2048]; + if (!WDL_ChooseFileForSave(hwndDlg, __LOCALIZE("Save LangPack as...","langpackedit"), + NULL, + g_editor.m_pack_fn.Get(), + "All files (*.*)\0*.*\0", + "", + false, + newfn,sizeof(newfn)) || !newfn[0]) return 0; + + g_editor.m_pack_fn.Set(newfn); + WritePrivateProfileString("LangPackEdit","lastpack",newfn,g_ini_file.Get()); + SetDlgItemText(hwndDlg,IDC_PACK,newfn); + } + + g_editor.save_file(g_editor.m_pack_fn.Get()); + g_editor.m_dirty=false; + g_editor.set_caption(); + break; + + case IDC_PACK_LOAD: + case IDC_TEMPLATE_LOAD: + { + char buf[2048]; + const bool is_template = LOWORD(wParam) == IDC_TEMPLATE_LOAD; + const char *inikey = is_template ? "template" : "lastpack"; + GetPrivateProfileString("LangPackEdit",inikey,"",buf,sizeof(buf),g_ini_file.Get()); + char *f = WDL_ChooseFileForOpen(hwndDlg, + is_template ? __LOCALIZE("Choose LangPack Template","langpackedit") : + __LOCALIZE("Load LangPack","langpackedit"), + NULL, + buf, + "All files (*.*)\0*.*\0" + , + "", + false, false); + if (f) + { + WritePrivateProfileString("LangPackEdit",inikey,f,g_ini_file.Get()); + g_editor.load_file(f,is_template); + if (!is_template) g_editor.m_pack_fn.Set(f); + free(f); + if (!is_template) + { + g_editor.m_dirty=false; + g_editor.set_caption(); + } + } + } + break; + case IDC_LOCALIZED_STRING: + case IDC_COMMON_STRING: + case ID_SCALING_ADD: + case IDC_COPY_TEMPLATE: + case IDC_REMOVE_LOCALIZATION: + case IDC_REMOVE_NONLOCALIZATION: + { + HWND list = GetDlgItem(hwndDlg,IDC_LIST); + int cnt = 0; + const int n = ListView_GetItemCount(list); + for (int x = 0; x < n; x ++) + { + if (ListView_GetItemState(list,x,LVIS_SELECTED) & LVIS_SELECTED) + { + if (g_editor.edit_row(x,(int)wParam)) cnt++; + if (wParam == ID_SCALING_ADD) + ListView_SetItemState(list,x,0,LVIS_SELECTED); + } + } + if (cnt && wParam != IDC_LOCALIZED_STRING) + { + g_editor.refresh_list(); + g_editor.set_dirty(); + } + if (wParam == ID_SCALING_ADD) + { + const int nn = ListView_GetItemCount(list); + if (n < nn) + { + ListView_EnsureVisible(list,n,false); + for (int i = n; i < nn; i ++) + ListView_SetItemState(list,i,LVIS_SELECTED,LVIS_SELECTED); + } + } + } + break; + case ID_COL_STATE: + case ID_COL_ID: + case ID_COL_TEMPLATE: + case ID_COL_LOCALIZED: + case ID_COL_COMMONLOCALIZED: + g_editor.m_column_no_searchflags ^= (1<<(LOWORD(wParam) - ID_COL_STATE)); + { + char tmp[64]; + snprintf(tmp,sizeof(tmp),"%d",g_editor.m_column_no_searchflags); + WritePrivateProfileString("LangPackEdit","nosearchcols",tmp,g_ini_file.Get()); + } + SetTimer(hwndDlg,TIMER_FILTER,100,NULL); + break; + case ID_SORTCOL_STATE: + case ID_SORTCOL_ID: + case ID_SORTCOL_TEMPLATE: + case ID_SORTCOL_LOCALIZED: + case ID_SORTCOL_COMMONLOCALIZED: + g_editor.m_sort_rev = false; + g_editor.m_sort_col = LOWORD(wParam) - ID_SORTCOL_STATE; + g_editor.on_sort_change(); + break; + case ID_SORTCOL_REVERSE: + g_editor.m_sort_rev = !g_editor.m_sort_rev; + g_editor.on_sort_change(); + break; + } + break; + case WM_INITMENUPOPUP: + if (wParam) + { + HMENU menu = (HMENU) wParam; + static const unsigned short tab[]={ + IDC_LOCALIZED_STRING, + IDC_COMMON_STRING, + ID_SCALING_ADD, + IDC_COPY_TEMPLATE, + IDC_REMOVE_LOCALIZATION, + IDC_REMOVE_NONLOCALIZATION, + }; + bool en = ListView_GetSelectedCount(GetDlgItem(hwndDlg,IDC_LIST))>0; + for (size_t x = 0; x < sizeof(tab)/sizeof(tab[0]); x ++) + EnableMenuItem(menu,tab[x],MF_BYCOMMAND|(en ? 0 : MF_GRAYED)); + for (int x = 0; x < COL_MAX; x ++) + CheckMenuItem(menu,ID_COL_STATE+x, MF_BYCOMMAND | ((g_editor.m_column_no_searchflags&(1<hdr.idFrom == IDC_LIST) switch (lv->hdr.code) + { + case NM_DBLCLK: + g_editor.edit_row(lv->iItem); + return 0; + case NM_RCLICK: + if (ListView_GetSelectedCount(lv->hdr.hwndFrom)>0) + { + g_editor.item_context_menu(); + } + return 0; + case LVN_COLUMNCLICK: + { + int col = lv->iSubItem; + if (col == COL_ROW_IDX) col = COL_ID; + if (g_editor.m_sort_col == col) + { + g_editor.m_sort_rev = !g_editor.m_sort_rev; + } + else + { + g_editor.m_sort_rev = false; + g_editor.m_sort_col = col; + } + g_editor.on_sort_change(); + } + return 0; + case LVN_GETDISPINFO: +#ifdef _WIN32 + case LVN_GETDISPINFOW: +#endif + { + NMLVDISPINFO *lpdi = (NMLVDISPINFO*) lParam; + if (lpdi->item.mask & LVIF_TEXT) + { + if (lpdi->item.iSubItem == COL_ROW_IDX) + snprintf(lpdi->item.pszText,lpdi->item.cchTextMax,"%d",lpdi->item.iItem+1); + else + lpdi->item.pszText = (char*) g_editor.get_row_value(lpdi->item.iItem, lpdi->item.iSubItem); +#ifdef _WIN32 + if (lv->hdr.code == LVN_GETDISPINFOW) + WDL_UTF8_ListViewConvertDispInfoToW(lpdi); +#endif + } + } + } + } + break; + } + return 0; +} + +INT_PTR SWELLAppMain(int msg, INT_PTR parm1, INT_PTR parm2) +{ + switch (msg) + { + case SWELLAPP_ONLOAD: + { + } + break; + case SWELLAPP_LOADED: + { + char buf[2048]; + GetModuleFileName(NULL,buf,sizeof(buf)); + WDL_remove_filepart(buf); + lstrcatn(buf,WDL_DIRCHAR_STR "LangPackEdit.ini",sizeof(buf)); + g_ini_file.Set(buf); + + WDL_remove_filepart(buf); + lstrcatn(buf,WDL_DIRCHAR_STR "LangPackEdit.LangPack",sizeof(buf)); + +#ifdef _DEBUG + extern bool g_debug_langpack_has_loaded; + g_debug_langpack_has_loaded=true; +#endif + WDL_LoadLanguagePack(buf,NULL); + + HWND h=CreateDialog(NULL,MAKEINTRESOURCE(IDD_DIALOG1),NULL,mainProc); + ShowWindow(h,SW_SHOW); + +#ifndef _WIN32 + { + HMENU menu = LoadMenu(NULL,MAKEINTRESOURCE(IDR_MENU1)); +#ifdef __APPLE__ + { + HMENU sm=GetSubMenu(menu,0); + DeleteMenu(sm,ID_QUIT,MF_BYCOMMAND); // remove QUIT from our file menu, since it is in the system menu on OSX + + // remove any trailing separators + int a= GetMenuItemCount(sm); + while (a > 0 && GetMenuItemID(sm,a-1)==0) DeleteMenu(sm,--a,MF_BYPOSITION); + } + + extern HMENU SWELL_app_stocksysmenu; + if (SWELL_app_stocksysmenu) // insert the stock system menu + { + HMENU nm=SWELL_DuplicateMenu(SWELL_app_stocksysmenu); + if (nm) + { + MENUITEMINFO mi={sizeof(mi),MIIM_STATE|MIIM_SUBMENU|MIIM_TYPE,MFT_STRING,0,0,nm,NULL,NULL,0,(char*)"LangPackEdit"}; + InsertMenuItem(menu,0,TRUE,&mi); + } + } + SetMenuItemModifier(menu,IDC_TEMPLATE_LOAD,MF_BYCOMMAND,'T',FCONTROL); + SetMenuItemModifier(menu,IDC_PACK_LOAD,MF_BYCOMMAND,'O',FCONTROL); + SetMenuItemModifier(menu,IDC_PACK_SAVE,MF_BYCOMMAND,'S',FCONTROL); + SetMenuItemModifier(menu,IDC_PACK_SAVE_AS,MF_BYCOMMAND,'S',FCONTROL|FALT); +#endif + + SetMenu(h,menu); + } +#endif + + } + break; + case SWELLAPP_DESTROY: + if (g_editor.m_hwnd) DestroyWindow(g_editor.m_hwnd); + break; + case SWELLAPP_SHOULDDESTROY: + return g_editor.prompt_exit() ? 0 : 1; + case SWELLAPP_ONCOMMAND: + if (g_editor.m_hwnd) + SendMessage(g_editor.m_hwnd,WM_COMMAND,parm1,0); + return 0; + case SWELLAPP_PROCESSMESSAGE: + if (parm1) + { + const MSG *m = (MSG *)parm1; + if (m->message == WM_KEYDOWN && m->hwnd) + { +#ifndef __APPLE__ + if (m->wParam == 'Q' && + (GetAsyncKeyState(VK_CONTROL)&0x8000) && + !(GetAsyncKeyState(VK_SHIFT)&0x8000) && + !(GetAsyncKeyState(VK_MENU)&0x8000)) + { + if (g_editor.m_hwnd) + SendMessage(g_editor.m_hwnd,WM_COMMAND,ID_QUIT,0); + return 1; + } +#endif + if (g_editor.on_key(m->hwnd, m->message, m->wParam, m->lParam)) + return 1; + } + } + return 0; + } + return 0; +} + + + +#ifdef _WIN32 + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + g_hInstance = hInstance; + + SWELLAppMain(SWELLAPP_ONLOAD,0,0); + SWELLAppMain(SWELLAPP_LOADED,0,0); + + for(;;) + { + MSG msg={0,}; + int vvv = GetMessage(&msg,NULL,0,0); + if (!vvv) break; + + if (vvv<0) + { + Sleep(10); + continue; + } + if (!msg.hwnd) + { + DispatchMessage(&msg); + continue; + } + if (SWELLAppMain(SWELLAPP_PROCESSMESSAGE, (INT_PTR) &msg, 0)) continue; + + if (g_editor.m_hwnd && IsDialogMessage(g_editor.m_hwnd,&msg)) continue; + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + SWELLAppMain(SWELLAPP_DESTROY,0,0); + + ExitProcess(0); + + return 0; +} + +#else + +/************** SWELL stuff ********** */ + +#ifdef __APPLE__ +extern "C" { +#endif + +const char **g_argv; +int g_argc; + +#ifdef __APPLE__ +}; +#endif + + +#ifndef __APPLE__ + +int main(int argc, const char **argv) +{ + g_argc=argc; + g_argv=argv; + SWELL_initargs(&argc,(char***)&argv); + SWELL_Internal_PostMessage_Init(); + SWELL_ExtendedAPI("APPNAME",(void*)"LangPackEdit"); + SWELLAppMain(SWELLAPP_ONLOAD,0,0); + SWELLAppMain(SWELLAPP_LOADED,0,0); + while (!g_quit) { + SWELL_RunMessageLoop(); + Sleep(10); + } + SWELLAppMain(SWELLAPP_DESTROY,0,0); + return 0; +} + +#endif + + +#include "../../swell/swell-dlggen.h" +#include "res.rc_mac_dlg" +#undef BEGIN +#undef END +#include "../../swell/swell-menugen.h" +#include "res.rc_mac_menu" + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/main.m b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/main.m new file mode 100644 index 0000000..8737033 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/main.m @@ -0,0 +1,10 @@ +#import + +int main(int argc, const char * argv[]) +{ + extern const char **g_argv; + extern int g_argc; + g_argc=argc; + g_argv=argv; + return NSApplicationMain(argc, argv); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/res.rc b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/res.rc new file mode 100644 index 0000000..7e0b336 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/res.rc @@ -0,0 +1,249 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON DISCARDABLE "icon1.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 600, 400 +STYLE DS_CENTER | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | + WS_SYSMENU | WS_THICKFRAME +CAPTION "LangPackEdit" +MENU IDR_MENU1 +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Template:",IDC_TEMPLATE_LABEL,3,10,35,8 + EDITTEXT IDC_TEMPLATE,41,8,556,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Pack:",IDC_PACK_LABEL,3,26,35,8 + EDITTEXT IDC_PACK,41,24,556,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Comments:",IDC_COMMENTS_LABEL,3,40,35,8 + EDITTEXT IDC_COMMENTS,41,40,556,37,ES_MULTILINE | ES_AUTOHSCROLL + LTEXT "Filter:",IDC_FILTERLBL,3,83,35,8,SS_NOTIFY + EDITTEXT IDC_FILTER,41,80,556,14,ES_AUTOHSCROLL + CONTROL "List1",IDC_LIST,"SysListView32",LVS_REPORT | + LVS_SHOWSELALWAYS | LVS_OWNERDATA | + WS_BORDER | WS_TABSTOP,3,97,594,300 +END + +IDD_RENAME DIALOG DISCARDABLE 0, 0, 400, 81 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Localize: " +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "Localized string:",IDC_STATIC,7,41,61,8 + EDITTEXT IDC_LOCALIZED_STRING,65,39,328,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,118,61,44,14 + PUSHBUTTON "Cancel",IDCANCEL,167,61,44,14 + PUSHBUTTON "Remove Localization",IDC_REMOVE_LOCALIZATION,220,61,84, + 14 + PUSHBUTTON "Set to Template String",IDC_COPY_TEMPLATE,309,61,84,14 + LTEXT "Template string:",IDC_STATIC,7,9,61,8 + EDITTEXT IDC_TEMPLATE_STRING,65,7,328,14,ES_AUTOHSCROLL | + ES_READONLY + LTEXT "[common] string:",IDC_COMMON_LABEL,7,25,61,8 + EDITTEXT IDC_COMMON_STRING,65,23,328,14,ES_AUTOHSCROLL | + ES_READONLY +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_CONTEXTMENU MENU DISCARDABLE +BEGIN + POPUP "Context" + BEGIN + MENUITEM "Edit localization\tEnter", IDC_LOCALIZED_STRING + MENUITEM "Edit [common] localization", IDC_COMMON_STRING + MENUITEM "Add dialog scaling item for context", ID_SCALING_ADD + MENUITEM "Set localization to template value if not set (mark as localized)", + IDC_COPY_TEMPLATE + MENUITEM "Remove localization\tDelete", IDC_REMOVE_LOCALIZATION + MENUITEM "Remove localization from items that match template", + IDC_REMOVE_NONLOCALIZATION + END +END + +IDR_MENU1 MENU DISCARDABLE +BEGIN + POPUP "&File" + BEGIN + MENUITEM "Load &Template...\tCtrl+T", IDC_TEMPLATE_LOAD + MENUITEM "&Load Pack...\tCtrl+O", IDC_PACK_LOAD + MENUITEM "&Save Pack\tCtrl+S", IDC_PACK_SAVE + MENUITEM "Save Pack &As...\tCtrl+Alt+S", IDC_PACK_SAVE_AS + MENUITEM SEPARATOR + MENUITEM "&Quit\tCtrl+Q", ID_QUIT + END + POPUP "&View" + BEGIN + POPUP "Search columns" + BEGIN + MENUITEM "State", ID_COL_STATE + MENUITEM "ID", ID_COL_ID + MENUITEM "Template", ID_COL_TEMPLATE + MENUITEM "Localized", ID_COL_LOCALIZED + MENUITEM "Common Localized", ID_COL_COMMONLOCALIZED + END + POPUP "Sorting column" + BEGIN + MENUITEM "State", ID_SORTCOL_STATE + MENUITEM "ID", ID_SORTCOL_ID + MENUITEM "Template", ID_SORTCOL_TEMPLATE + MENUITEM "Localized", ID_SORTCOL_LOCALIZED + MENUITEM "Common Localized", ID_SORTCOL_COMMONLOCALIZED + MENUITEM SEPARATOR + MENUITEM "Reverse", ID_SORTCOL_REVERSE + END + END + POPUP "&Edit" + BEGIN + MENUITEM "Edit localization\tEnter", IDC_LOCALIZED_STRING + MENUITEM "Edit [common] localization", IDC_COMMON_STRING + MENUITEM "Add dialog scaling item for context", ID_SCALING_ADD + MENUITEM "Set localization to template value if not set (mark as localized)", + IDC_COPY_TEMPLATE + MENUITEM "Remove localization\tDelete", IDC_REMOVE_LOCALIZATION + MENUITEM "Remove localization from items that match template", + IDC_REMOVE_NONLOCALIZATION + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 597 + TOPMARGIN, 3 + BOTTOMMARGIN, 258 + END +END +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,0,0,9 + PRODUCTVERSION 0,0,0,9 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "Cockos Inc\0" + VALUE "FileDescription", "LangPackEdit\0" + VALUE "FileVersion", "0, 0, 0, 9\0" + VALUE "InternalName", "LangPackEdit\0" + VALUE "LegalCopyright", "Copyright � 2022 and onwards Cockos Inc\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "LangPackEdit.exe\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "LangPackEdit\0" + VALUE "ProductVersion", "0, 0, 0, 9\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/resource.h b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/resource.h new file mode 100644 index 0000000..69f457d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/langpack_edit/resource.h @@ -0,0 +1,54 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by res.rc +// +#define IDI_ICON1 101 +#define IDD_DIALOG1 102 +#define IDD_RENAME 103 +#define IDR_MENU1 103 +#define IDR_CONTEXTMENU 104 +#define IDC_LIST 1000 +#define IDC_FILTER 1001 +#define IDC_FILTERLBL 1002 +#define IDC_TEMPLATE 1003 +#define IDC_TEMPLATE_LOAD 1004 +#define IDC_TEMPLATE_LABEL 1005 +#define IDC_PACK 1006 +#define IDC_PACK_LOAD 1007 +#define IDC_PACK_LABEL 1008 +#define IDC_COMMENTS 1009 +#define IDC_COMMENTS_LABEL 1010 +#define IDC_PACK_SAVE 1011 +#define IDC_PACK_SAVE_AS 1012 +#define IDC_LOCALIZED_STRING 1013 +#define IDC_TEMPLATE_STRING 1014 +#define IDC_COMMON_STRING 1015 +#define IDC_COMMON_LABEL 1016 +#define IDC_REMOVE_LOCALIZATION 1017 +#define IDC_COPY_TEMPLATE 1018 +#define ID_SCALING_ADD 1019 +#define IDC_REMOVE_NONLOCALIZATION 1020 +#define ID_COL_STATE 2000 +#define ID_COL_ID 2001 +#define ID_COL_TEMPLATE 2003 +#define ID_COL_LOCALIZED 2004 +#define ID_COL_COMMONLOCALIZED 2005 +#define ID_SORTCOL_REVERSE 2019 +#define ID_SORTCOL_STATE 2020 +#define ID_SORTCOL_ID 2021 +#define ID_SORTCOL_TEMPLATE 2023 +#define ID_SORTCOL_LOCALIZED 2024 +#define ID_SORTCOL_COMMONLOCALIZED 2025 + +#define ID_QUIT 40000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1021 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize-import.h b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize-import.h new file mode 100644 index 0000000..2a4f287 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize-import.h @@ -0,0 +1,83 @@ +// used by plug-ins to access imported localization +// usage, in one file: +// #define LOCALIZE_IMPORT_PREFIX "midi_" (should be the directory name with _) +// #include "localize-import.h" +// #include "localize.h" +// import function pointers in initialization +// +// other files can simply import localize.h as normal +// note that if the module might be unloaded, LOCALIZE_FLAG_NOCACHE is essential! +// + +#ifdef _WDL_LOCALIZE_H_ +#error you must include localize-import.h before localize.h, sorry +#endif + +#include +#include "../wdltypes.h" + +// caller must import these 4 function pointers from the running app +static const char *(*importedLocalizeFunc)(const char *str, const char *subctx, int flags); +static void (*importedLocalizeMenu)(const char *rescat, HMENU hMenu, LPCSTR lpMenuName); +static DLGPROC (*importedLocalizePrepareDialog)(const char *rescat, HINSTANCE hInstance, const char *lpTemplate, DLGPROC dlgProc, LPARAM lPAram, void **ptrs, int nptrs); +static void (*importedLocalizeInitializeDialog)(HWND hwnd, const char *d); + +const char *__localizeFunc(const char *str, const char *subctx, int flags) +{ + if (WDL_NORMALLY(importedLocalizeFunc)) return importedLocalizeFunc(str,subctx,flags); + return str; +} + +HMENU __localizeLoadMenu(HINSTANCE hInstance, const char *lpMenuName) +{ + HMENU menu = LoadMenu(hInstance,lpMenuName); + if (menu && WDL_NORMALLY(importedLocalizeMenu)) importedLocalizeMenu(LOCALIZE_IMPORT_PREFIX,menu,lpMenuName); + return menu; +} + +HWND __localizeDialog(HINSTANCE hInstance, const char *lpTemplate, HWND hwndParent, DLGPROC dlgProc, LPARAM lParam, int mode) +{ + void *p[5]; + char tmp[256]; + if (mode == 1) + { + sprintf(tmp,"%.100s%d",LOCALIZE_IMPORT_PREFIX,(int)(INT_PTR)lpTemplate); + p[4] = tmp; + } + else + p[4] = NULL; + + if (WDL_NORMALLY(importedLocalizePrepareDialog)) + { + DLGPROC newDlg = importedLocalizePrepareDialog(LOCALIZE_IMPORT_PREFIX,hInstance,lpTemplate,dlgProc,lParam,p,sizeof(p)/sizeof(p[0])); + if (newDlg) + { + dlgProc = newDlg; + lParam = (LPARAM)(INT_PTR)p; + } + } + switch (mode) + { + case 0: return CreateDialogParam(hInstance,lpTemplate,hwndParent,dlgProc,lParam); + case 1: return (HWND) (INT_PTR)DialogBoxParam(hInstance,lpTemplate,hwndParent,dlgProc,lParam); + } + return 0; +} + +void __localizeInitializeDialog(HWND hwnd, const char *d) +{ + if (WDL_NORMALLY(importedLocalizeInitializeDialog)) importedLocalizeInitializeDialog(hwnd,d); +} + +static WDL_STATICFUNC_UNUSED void localizeKbdSection(KbdSectionInfo *sec, const char *nm) +{ + if (WDL_NORMALLY(importedLocalizeFunc)) + { + int x; + if (sec->action_list) for (x=0;xaction_list_cnt; x++) + { + KbdCmd *p = &sec->action_list[x]; + if (p->text) p->text = __localizeFunc(p->text,nm,2/*LOCALIZE_FLAG_NOCACHE*/); + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize-info.txt b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize-info.txt new file mode 100644 index 0000000..bb272ff --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize-info.txt @@ -0,0 +1,83 @@ +WDL Localization System +=============================================================================== + +This is a tool for localizing C++ applications. It supports Windows and SWELL-based +GUI applications, or command line only. + + +1. In your code +========================== +The main thing to do is include localize.h from most of your code, and add +localize.cpp to the project (you may want to include it from a wrapper file +in order to hook certain functions). + +At startup your application should call: + +WDL_LoadLanguagePack("/path/to/filename.langpack",NULL); + +When your code has a string that is to be localizable, you do it via this +macro: + + __LOCALIZE("my string","section"); + +Note that both parameters to the macro MUST be literal strings, and be one block +of string (i.e. not using concatenation, i.e. "part1" "part2" etc). + +If you are calling from code that could possibly be in a thread other than the +main thread, or from a module that could be unloaded, use: + + __LOCALIZE_NOCACHE("my string","section"); + +If you would like to have a format specifier in the string, you can use: + + snprintf(buf,sizeof(buf),__LOCALIZE_VERFMT("This has %d items","section"),6); + +The value returned by __LOCALIZE/etc is effectively a const char *, and will +persist, so it is safe to pass whereever and use again. If you are really +performance sensitive, you might want to do: + + static const char *msg; + if (!msg) msg = __LOCALIZE_NOCACHE("whatever","section"); + +This will do the lookup once, and cache the result. + +If you have strings which are present in a table such as: + struct foo bar[]={ + {x,y,z,"string 1"}, + {x,y,z,"string 2"}, + {x,y,z,"string 3"}, + }; + +The best way to handle this is to put comments around the string table, such as: + // !WANT_LOCALIZE_STRINGS_BEGIN:section_name + struct foo bar[]={ + {x,y,z,"string 1"}, + {x,y,z,"string 2"}, + {x,y,z,"string 3"}, + }; + // !WANT_LOCALIZE_STRINGS_END + +Or if other strings exist in that table that are not localized, you can use __LOCALIZE_REG_ONLY(). + +Then, supposing you reference these strings via bar[x].stringptr, you would use: + __localizeFunc(bar[x].stringptr,flags) + (where flags can be 0, or LOCALIZE_FLAG_VERIFY_FMTS or + LOCALIZE_FLAG_NOCACHE or some combination of those, see localize.h) + +There currently a limit of around 8k for localized strings -- if you are +localizing a huge block of text, it might be good to divide it up into +separate strings. + +Finally, for resources, the menus and dialogs are localized automatically via a +wrapper function and some #defines, which are in localize.h + +Dynamic libraries loaded can access the system by using localize-import.h (see comments in that file) + +2. Generating the language pack template +========================== + +To generate a template language pack, compile build_sample_langpack.cpp: + g++ -O -o build_sample_langpack build_sample_langpack.cpp + +Then use: + build_sample_langpack --template *.rc *.cpp > template.langpack diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize.cpp new file mode 100644 index 0000000..599f8fb --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize.cpp @@ -0,0 +1,1205 @@ +// this file may be easier to customize if included directly from other code. Some things that can be defined to hook: +// +// #define WDL_LOCALIZE_HOOK_ALLOW_CACHE (ismainthread()) +// #define WDL_LOCALIZE_HOOK_DLGPROC if (is_modal) return __localDlgProcModalPos; // can return a temporary dlgproc override +// #define WDL_LOCALIZE_HOOK_XLATE(str,subctx,flags,newptr) // can be used to override/tweak translations (newptr str is the original string, newptr is the translated string (or NULL) ) +#ifdef _WIN32 +#include +#else +#include "../swell/swell.h" +#endif + +#ifndef LOCALIZE_NO_DIALOG_MENU_REDEF +#define LOCALIZE_NO_DIALOG_MENU_REDEF +#endif +#include "localize.h" +#include +#include +#include +#include "../assocarray.h" +#include "../ptrlist.h" +#include "../chunkalloc.h" +#include "../fnv64.h" +#include "../win32_utf8.h" +#include "../wdlcstring.h" + + +#define LANGPACK_SCALE_CONSTANT WDL_UINT64_CONST(0x5CA1E00000000000) +static WDL_StringKeyedArray< WDL_AssocArray * > g_translations; +static WDL_AssocArray *g_translations_commonsec; + +static bool isPrintfModifier(char c) +{ + switch (c) + { + case '.': + case '-': + case '+': + case '#': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case ' ': + return true; + } + return false; +} + +static const char *next_format(const char *p) +{ + for (;;) + { + switch (p[0]) + { + case 0: return p; + case '%': + if (p[1] != '%') return p; + p++; + WDL_FALLTHROUGH; + default: + p++; + break; + } + } +} + +static bool validateStrs(const char *stock, const char *newstr, int flags) +{ + if (!(flags&LOCALIZE_FLAG_VERIFY_FMTS)) return true; + + for (;;) + { + newstr = next_format(newstr); + if (!newstr[0]) return true; // not enough formats in new string, safe + stock = next_format(stock); + if (!stock[0]) return false; // not enough formats in original string, unsafe + + // compare formats + newstr++; + stock++; + + do + { + while (isPrintfModifier(*newstr)) newstr++; + while (isPrintfModifier(*stock)) stock++; + if (*newstr != *stock) return false; + newstr++; + stock++; + } while (newstr[-1] == '*' || newstr[-1] == 'h' || newstr[-1] == 'l' || newstr[-1] == 'L'); + } + return true; +} + +static char *ChunkAlloc(int len) +{ +#ifdef _DEBUG + static WDL_ChunkAlloc backing(15000); // will free on exit (for debug mode) + return (char*)backing.Alloc(len); +#else + static WDL_ChunkAlloc *backing; + if (!backing) backing = new WDL_ChunkAlloc(15000); + return (char*)backing->Alloc(len); +#endif +} + +#ifdef _DEBUG + bool g_debug_langpack_has_loaded; + + class localizeValidatePtrCacheRec { + public: + localizeValidatePtrCacheRec(const char *subctx, const char *str, const char *ctx) + { + //printf("saving pointers for validation for %s (%s)\n",str,ctx); + m_str_copy=strdup(m_str=str); + m_sub_copy=strdup(m_sub=subctx); + m_ctx=ctx; + } + ~localizeValidatePtrCacheRec() + { + //printf("validating string %s\n",m_str_copy); + WDL_ASSERT(!strcmp(m_sub,m_sub_copy) /* if this triggers, then a caller called __LOCALIZE without NOCACHE on a non-static or unloaded string */ ); + WDL_ASSERT(!strcmp(m_str,m_str_copy) /* if this triggers, then a caller called __LOCALIZE without NOCACHE on a non-static or unloaded string */ ); + } + char *m_sub_copy, *m_str_copy; + const char *m_sub, *m_str, *m_ctx; + }; + static WDL_PtrList_DeleteOnDestroy s_debug_validateptrcache; + #define LANGPACK_DEBUG_SAVE_POINTERS_FOR_VALIDATION(subctx,str,ctx) \ + s_debug_validateptrcache.Add(new localizeValidatePtrCacheRec(subctx,str,ctx)); +#else + #define LANGPACK_DEBUG_SAVE_POINTERS_FOR_VALIDATION(subctx,str,ctx) +#endif + +const char *__localizeFunc(const char *str, const char *subctx, int flags) +{ +#ifdef _DEBUG + WDL_ASSERT(g_debug_langpack_has_loaded != false); +#endif + if (WDL_NOT_NORMALLY(!str || !subctx || !subctx[0])) return str; + if (!g_translations.GetSize()) + { +#ifdef WDL_LOCALIZE_HOOK_XLATE + char *newptr=NULL; + WDL_LOCALIZE_HOOK_XLATE(str,subctx,flags,newptr) + if (newptr) return newptr; +#endif + return str; + } + +#ifdef WDL_LOCALIZE_HOOK_ALLOW_CACHE + const bool cache_lookups = !(flags&LOCALIZE_FLAG_NOCACHE) && WDL_LOCALIZE_HOOK_ALLOW_CACHE; +#else + const bool cache_lookups = !(flags&LOCALIZE_FLAG_NOCACHE); +#endif + +#ifdef BENCHMARK_I8N + static int stats[2]; + static double sumtimes[2]; // [cached] + double startTime=time_precise(); +#endif + + static WDL_PtrKeyedArray< WDL_PtrKeyedArray * > ptrcache; + WDL_PtrKeyedArray *sc = NULL; + + if (cache_lookups) + { + sc = ptrcache.Get((INT_PTR)subctx); + if (sc) + { + const char *ret = sc->Get((INT_PTR)str); + if (ret) + { +#ifdef BENCHMARK_I8N + stats[1]++; + startTime = time_precise()-startTime; + sumtimes[1]+=startTime; + if (!(stats[1]%100)) + { + wdl_log("cached, avg was %fuS\n",sumtimes[1]*10000.0); + sumtimes[1]=0; + } +#endif + + if (ret == (const char *)(INT_PTR)1) return str; + return ret; + } + } + } + + char *newptr = NULL; + + int trycnt; + size_t len = strlen(str)+1; + + if (flags & LOCALIZE_FLAG_DOUBLENULL) + { + // need to test this + for (;;) + { + size_t a = strlen(str+len); + if (!a) break; + len += a+1; + } + len++; + } + else if (flags & LOCALIZE_FLAG_PAIR) + { + len += strlen(str + len) + 1; + } + + WDL_UINT64 hash; + if ((flags & LOCALIZE_FLAG_PAIR) && len == 18 && !memcmp(str,"__LOCALIZE_SCALE\0",18)) + hash = WDL_UINT64_CONST(0x5CA1E00000000000); + else + hash = WDL_FNV64(WDL_FNV64_IV,(const unsigned char *)str,(int)len); + + for (trycnt=0;trycnt<2 && !newptr;trycnt++) + { + WDL_AssocArray *section = trycnt == 1 ? g_translations_commonsec : g_translations.Get(subctx); + + if (section) + { + newptr = section->Get(hash,0); + if (newptr && !validateStrs(str,newptr,flags)) newptr=NULL; + } + } + + #ifdef WDL_LOCALIZE_HOOK_XLATE + WDL_LOCALIZE_HOOK_XLATE(str,subctx,flags,newptr) + #endif + + if (cache_lookups) + { + if (!sc) + { + sc = new WDL_PtrKeyedArray; + ptrcache.Insert((INT_PTR)subctx,sc); + } + sc->Insert((INT_PTR)str,newptr ? newptr : (char*)(INT_PTR)1); // update pointer cache for fast lookup + LANGPACK_DEBUG_SAVE_POINTERS_FOR_VALIDATION(subctx,str,"") + } +#ifdef BENCHMARK_I8N + stats[0]++; + startTime = time_precise()-startTime; + sumtimes[0]+=startTime; + if (!(stats[0]%100)) + { + wdl_log("uncached , avg was %f\n",sumtimes[0]*10000.0); + sumtimes[0]=0; + } +#endif + + return newptr?newptr:str; +} + +static void __localProcMenu(HMENU menu, WDL_AssocArray *s) +{ + int n = GetMenuItemCount(menu); + int x; + char buf[4096]; + for(x=0;xGet(hash,0) : NULL; + if (!newptr && g_translations_commonsec) newptr = g_translations_commonsec->Get(hash,0); + + if (!newptr) + { + #ifdef _WIN32 + char *p =buf; + while (*p && *p != '\t') p++; + lstrcpyn(mod,p,sizeof(mod)); + if (*p) + { + *p++=0; + goto tryagain; + } + #endif + + } + if (newptr) + { + #ifdef _WIN32 + if (mod[0]) + { + lstrcpyn(buf,newptr,3500); + strcat(buf,mod); + newptr=buf; + } + #endif + mii.fMask = MIIM_TYPE; +#ifdef __APPLE__ + mii.fMask |= MIIM_SWELL_DO_NOT_CALC_MODIFIERS; +#endif + mii.dwTypeData = (char*)newptr; + SetMenuItemInfo(menu,x,TRUE,&mii); + } + } + + if (mii.hSubMenu) __localProcMenu(mii.hSubMenu,s); + } + } +} + +void __localizeMenu(const char *rescat, HMENU hMenu, LPCSTR lpMenuName) +{ +#ifdef _DEBUG + WDL_ASSERT(g_debug_langpack_has_loaded != false); +#endif + INT_PTR a = (INT_PTR) lpMenuName; + if (hMenu && a>0&&a<65536) + { + char buf[128]; + sprintf(buf,"%sMENU_%d",rescat?rescat:"",(int)a); + WDL_AssocArray *s = g_translations.Get(buf); + if (s) + { + __localProcMenu(hMenu,s); + } + } +} + +HMENU __localizeLoadMenu(HINSTANCE hInstance, LPCSTR lpMenuName) +{ + HMENU ret = LoadMenu(hInstance,lpMenuName); + if (ret) __localizeMenu(NULL,ret,lpMenuName); + return ret; +} + +struct windowReorgEnt +{ + enum windowReorgEntType + { + WRET_GROUP=0, + WRET_SIZEADJ, + WRET_MISC, // dont analyze for size changes, but move around + + }; + windowReorgEnt(HWND _hwnd, RECT _r, int wc) + { + hwnd=_hwnd; + orig_r=r=_r; + mode=WRET_MISC; + move_amt=0; + wantsizeincrease=0; + scaled_width_change = wc; + } + ~windowReorgEnt() { } + + HWND hwnd; + RECT r,orig_r; + windowReorgEntType mode; + int move_amt; + int wantsizeincrease; + int scaled_width_change; + + static int Sort(const void *_a, const void *_b) + { + const windowReorgEnt *a = (const windowReorgEnt*)_a; + const windowReorgEnt *b = (const windowReorgEnt*)_b; + + if (a->r.left < b->r.left) return -1; + if (a->r.left > b->r.left) return 1; + + if (a->r.top < b->r.top) return -1; + if (a->r.top > b->r.top) return 1; + + return 0; + } + +}; +class windowReorgState +{ +public: + + windowReorgState(HWND _par, float _scx, float _scy) : par(_par), scx(_scx), scy(_scy) + { + RECT cr; + GetClientRect(_par,&cr); + + par_cr = cr; + if (scx != 1.0f) par_cr.right = (int) (par_cr.right * scx + 0.5); + if (scy != 1.0f) par_cr.bottom = (int) (par_cr.bottom * scy + 0.5); + + has_sc = (cr.right != par_cr.right || cr.bottom != par_cr.bottom); + + if (has_sc) + { + RECT wr; + if (GetWindowLong(_par,GWL_STYLE)&WS_CHILD) wr=cr; + else GetWindowRect(_par,&wr); + + SetWindowPos(_par,NULL,0,0, + (par_cr.right - cr.right) + (wr.right-wr.left), + (par_cr.bottom - cr.bottom) + (wr.bottom-wr.top), + SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER); + } + } + ~windowReorgState() { } + + WDL_TypedBuf cws; + WDL_IntKeyedArray columns; // map l|(r<<16) -> maps to size increase + RECT par_cr; + + HWND par; + float scx,scy; + bool has_sc; +}; + +static const char *xlateWindow(HWND hwnd, WDL_AssocArray *s, char *buf, int bufsz, bool prefix_handling) +{ + buf[0]=0; + GetWindowText(hwnd,buf,bufsz); + if (buf[0]) + { + buf[bufsz-1]=0; + WDL_UINT64 hash = WDL_FNV64(WDL_FNV64_IV,(const unsigned char *)buf,(int)strlen(buf)+1); + const char *newptr = s ? s->Get(hash,0) : NULL; + if (!newptr && g_translations_commonsec) newptr = g_translations_commonsec->Get(hash,0); + +#ifdef __APPLE__ + bool filter_prefix = false; + if (!newptr && prefix_handling) + { + extern const char *SWELL_GetRecentPrefixRemoval(const char *p); + const char *p = SWELL_GetRecentPrefixRemoval(buf); + if (p) + { + hash = WDL_FNV64(WDL_FNV64_IV,(const unsigned char *)p,(int)strlen(p)+1); + newptr = s ? s->Get(hash,0) : NULL; + if (!newptr && g_translations_commonsec) newptr = g_translations_commonsec->Get(hash,0); + filter_prefix = true; + } + } +#endif + + if (newptr && strcmp(newptr,buf)) + { +#ifdef __APPLE__ + if (filter_prefix) + { + const char *rd=newptr; + int widx=0; + while (widx < bufsz-1) + { + if (*rd == '&') rd++; + if (!*rd) break; + buf[widx++]=*rd++; + } + buf[widx]=0; + SetWindowText(hwnd,buf); + return newptr; + } +#endif + SetWindowText(hwnd,newptr); + return newptr; + } + } + return NULL; +} + +static BOOL CALLBACK xlateGetRects(HWND hwnd, LPARAM lParam) +{ + windowReorgState *s=(windowReorgState*)lParam; + + if (GetParent(hwnd) != s->par) return TRUE; + + RECT r; + GetWindowRect(hwnd,&r); + ScreenToClient(s->par,(LPPOINT)&r); + ScreenToClient(s->par,((LPPOINT)&r)+1); + int width_change = 0; + + if (s->has_sc) // scaling happens before all of the ripple-code + { + if (r.top > r.bottom) { const int t = r.top; r.top = r.bottom; r.bottom = t; } + + width_change = r.right-r.left; + r.left = (int) (r.left * s->scx + 0.5); + r.top = (int) (r.top * s->scy + 0.5); + r.right = (int) (r.right * s->scx + 0.5); + r.bottom = (int) (r.bottom * s->scy + 0.5); + SetWindowPos(hwnd,NULL, r.left,r.top, r.right-r.left, r.bottom-r.top, SWP_NOACTIVATE|SWP_NOZORDER); + width_change = (r.right-r.left) - width_change; + } + + windowReorgEnt t(hwnd,r,width_change); + +#ifdef _WIN32 + char buf[128]; + buf[0]=0; + GetClassName(hwnd,buf,sizeof(buf)); + if (!strcmp(buf,"Button")) + { + LONG style=GetWindowLong(hwnd,GWL_STYLE); + if (LOWORD(style)==BS_GROUPBOX) t.mode = windowReorgEnt::WRET_GROUP; + else t.mode = windowReorgEnt::WRET_SIZEADJ; + } + else if (!strcmp(buf,"Static")) + { + t.mode = windowReorgEnt::WRET_SIZEADJ; + } +#else + if (SWELL_IsGroupBox(hwnd)) t.mode = windowReorgEnt::WRET_GROUP; + else if (SWELL_IsButton(hwnd)||SWELL_IsStaticText(hwnd)) t.mode = windowReorgEnt::WRET_SIZEADJ; +#endif + + if (t.mode == windowReorgEnt::WRET_GROUP) + { + // first copy is the left side of the group, with hwnd + int a = t.r.right; + t.r.right = t.r.left+4; + t.orig_r=t.r; + s->cws.Add(t); + + // add a second copy, which is the right side but no hwnd + t.r.right = a; + t.r.left = a-4; + t.hwnd=0; + } + t.orig_r=t.r; + s->cws.Add(t); + + return TRUE; +} + +static int rippleControlsRight(HWND hwnd, const RECT *srcR, windowReorgEnt *ent, int ent_cnt, int dSize, int clientw) +{ + // limit first to client rectangle + int space = clientw - 6 - srcR->right; + if (dSize>space) dSize=space; + + while (ent_cnt>0 && dSize>0) + { + // if the two items overlap by more than 1px initially, dont make them ripple (it is assumed they are allowed, maybe one is hidden) +#define LOCALIZE_ISECT_PAIRS(x1,x2,y1,y2) \ + ((x1 >= y1 && x1 < y2) || (x2 >= y1 && x2 < y2) || \ + (y1 >= x1 && y1 < x2) || (y2 >= x1 && y2 < x2)) + if (ent->r.left >= srcR->right-1 && LOCALIZE_ISECT_PAIRS(srcR->top,srcR->bottom,ent->r.top,ent->r.bottom)) + { + space = ent->r.left - srcR->right; + + if (ent->mode == windowReorgEnt::WRET_GROUP) + { + if (dSize>space) dSize=space; + } + else + { + int needAmt = dSize - (space-2 /* border */); + if (needAmt>0) + { + int got = rippleControlsRight(ent->hwnd,&ent->r,ent+1,ent_cnt-1,needAmt,clientw); + dSize -= needAmt - got; + if (ent->move_amt < got) ent->move_amt=got; + } + } + } + + ent_cnt--; + ent++; + } + return dSize; +} + +static void localize_dialog(HWND hwnd, WDL_AssocArray *sec) +{ +#ifdef _DEBUG + WDL_ASSERT(g_debug_langpack_has_loaded != false); +#endif + const char *sc_str = sec->Get(LANGPACK_SCALE_CONSTANT); +/* commented [common] scaling fallback: the langpack author has to set it for each dialog that needs it + if (!sc_str && g_translations_commonsec) + { + sc_str = g_translations_commonsec->Get(LANGPACK_SCALE_CONSTANT); + } +*/ + float scx = 1.0, scy = 1.0; + if (sc_str) + { + while (*sc_str && (*sc_str == ' ' || *sc_str == '\t')) sc_str++; + +#ifdef WDL_HOOK_LOCALIZE_ATOF + float v = (float) WDL_HOOK_LOCALIZE_ATOF(sc_str); +#else + float v = (float) atof(sc_str); +#endif + if (v > 0.1 && v < 8.0) + { + scx=v; + while (*sc_str && *sc_str != ' ' && *sc_str != '\t') sc_str++; + if (*sc_str) + { +#ifdef WDL_HOOK_LOCALIZE_ATOF + v = (float)WDL_HOOK_LOCALIZE_ATOF(sc_str); +#else + v = (float)atof(sc_str); +#endif + if (v > 0.1 && v < 8.0) + scy = v; + } + } + } + + windowReorgState s(hwnd,scx,scy); + + char buf[8192]; + xlateWindow(hwnd,sec,buf,sizeof(buf),false); // translate window title + EnumChildWindows(hwnd,xlateGetRects,(LPARAM)&s); + +#ifdef _WIN32 + HDC hdc=GetDC(hwnd); + HGDIOBJ oldFont=0; + HFONT font = (HFONT)SendMessage(hwnd,WM_GETFONT,0,0); + if (font) oldFont=SelectObject(hdc,font); +#endif + + int x; + const bool do_columns = true; + for(x=0;xhwnd) + { + const char *newText=xlateWindow(rec->hwnd,sec,buf,sizeof(buf), rec->mode != windowReorgEnt::WRET_MISC); + if (newText && rec->mode == windowReorgEnt::WRET_SIZEADJ) + { + RECT r1={0},r2={0}; +#ifdef _WIN32 + DrawText(hdc,buf,-1,&r1,DT_CALCRECT); + DrawText(hdc,newText,-1,&r2,DT_CALCRECT); + r1.right += rec->scaled_width_change; +#else + GetClientRect(rec->hwnd,&r1); + SWELL_GetDesiredControlSize(rec->hwnd,&r2); +#endif + int dSize=r2.right-r1.right; + if (dSize>0) + { + rec->wantsizeincrease = ++dSize; + + if (do_columns) + { + const int v = (rec->r.right<<16) | (rec->r.left&0xffff); + int *diff = s.columns.GetPtr(v); + if (!diff) s.columns.Insert(v,dSize); + else if (dSize > *diff) *diff = dSize; + } + } + } + } + } + + if (do_columns) for(x=0;xhwnd && rec->mode == windowReorgEnt::WRET_SIZEADJ) + { + const int v = (rec->r.right<<16) | (rec->r.left&0xffff); + const int *diff = s.columns.GetPtr(v); + if (diff) rec->wantsizeincrease = *diff; + } + } + + int swSizeInc=0; + + do + { + qsort(s.cws.Get(),s.cws.GetSize(),sizeof(*s.cws.Get()),windowReorgEnt::Sort); + for(x=0;xwantsizeincrease>0) + { + int amt = rippleControlsRight(trec->hwnd,&trec->r,trec+1,s.cws.GetSize() - (x+1),trec->wantsizeincrease,s.par_cr.right); + if (amt>0) + { + trec->wantsizeincrease -= amt; + trec->r.right += amt; + } + int y; + for(y=x+1;ymove_amt); + if (a>0) + { + rec->r.left += a; + rec->r.right += a; + } + rec->move_amt=0; + } + if (!swSizeInc && trec->wantsizeincrease>0) swSizeInc=1; + } + } + + if (swSizeInc++) + { + // flip everything + int w=s.par_cr.right; + for(x=0;xr.left; + int b = w-1-rec->r.right; + rec->r.left = b; + rec->r.right = a; + } + } + } while (swSizeInc == 2); + + + for(x=0;xhwnd) + { + bool wantMove = rec->r.left != rec->orig_r.left; + bool wantSize = (rec->r.right-rec->r.left) != (rec->orig_r.right-rec->orig_r.left); + if (wantMove||wantSize) + { + SetWindowPos(rec->hwnd,NULL,rec->r.left,rec->r.top,rec->r.right-rec->r.left,rec->r.bottom-rec->r.top, + (wantSize?0:SWP_NOSIZE)|(wantMove?0:SWP_NOMOVE)|SWP_NOZORDER|SWP_NOACTIVATE); + } + } + } +#ifdef _WIN32 + if (oldFont) SelectObject(hdc,oldFont); + ReleaseDC(hwnd,hdc); +#endif +} + +void __localizeInitializeDialog(HWND hwnd, const char *desc) +{ + if (!desc || !hwnd || !*desc) return; + WDL_AssocArray *s = g_translations.Get(desc); + if (s) localize_dialog(hwnd,s); +} + +void (*localizePreInitDialogHook)(HWND hwndDlg); + +static WDL_DLGRET __localDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + void **l = (void **)lParam; + + if (localizePreInitDialogHook) + localizePreInitDialogHook(hwnd); + + if (l[2]) + localize_dialog(hwnd,(WDL_AssocArray *)l[2]); + +#ifdef _WIN32 + { + // if not a child window and has a menu set, localize our menu + WDL_AssocArray *s = (WDL_AssocArray *)l[3]; + if (s && !(GetWindowLong(hwnd,GWL_STYLE)&WS_CHILD)) + { + HMENU hmenu = GetMenu(hwnd); + if (hmenu) __localProcMenu(hmenu,s); + } + } +#endif + + DLGPROC newproc = (DLGPROC) l[0]; + SetWindowLongPtr(hwnd,DWLP_DLGPROC,(LRESULT) newproc); + + return newproc(hwnd,uMsg,wParam,(LPARAM)l[1]); + } + } + return 0; +} + +#ifdef _WIN32 +static WORD __getMenuIdFromDlgResource(HINSTANCE hInstance, const char * lpTemplate) +{ + HRSRC h=FindResource(hInstance,lpTemplate,RT_DIALOG); + if (!h) return 0; + DWORD sz = SizeofResource(hInstance, h); + if (sz<30) return 0; + + HGLOBAL hg=LoadResource(hInstance,h); + if (!hg) return 0; + WORD *p = (WORD *)LockResource(hg); + if (!p) return 0; + + if (p[0]!=1 || p[1] != 0xffff) return 0; // not extended dialog template + if (p[13]==0) return 0; + if (p[13]==0xffff) return p[14]; + + //otherwise it is a string, but we dont support(or use) that + return 0; + +} +#endif + +DLGPROC __localizePrepareDialog(const char *rescat, HINSTANCE hInstance, const char *lpTemplate, DLGPROC dlgProc, LPARAM lParam, void **ptrs, int nptrs) +{ + INT_PTR a = (INT_PTR) lpTemplate; + if (WDL_NOT_NORMALLY(nptrs<4)) return NULL; + + WDL_AssocArray *s = NULL, *s2 = NULL; + if (a>0&&a<65536) + { + char buf[128]; + snprintf(buf,sizeof(buf),"%sDLG_%d",rescat?rescat:"",(int)a); + s = g_translations.Get(buf); +#ifdef _WIN32 + int menuid = __getMenuIdFromDlgResource(hInstance,lpTemplate); + if (menuid) + { + snprintf(buf,sizeof(buf),"%sMENU_%d",rescat?rescat:"",menuid); + s2=g_translations.Get(buf); + } +#endif + } + + ptrs[0] = (void*)dlgProc; + ptrs[1] = (void*)(INT_PTR)lParam; + ptrs[2] = s; + ptrs[3] = s2; +#ifdef WDL_LOCALIZE_HOOK_DLGPROC + WDL_LOCALIZE_HOOK_DLGPROC +#endif + return (s||s2||(a>0 && localizePreInitDialogHook)) ? __localDlgProc : NULL; +} + +HWND __localizeDialog(HINSTANCE hInstance, const char *lpTemplate, HWND hwndParent, DLGPROC dlgProc, LPARAM lParam, int mode) +{ + void *p[5]; + char tmp[256]; + if (mode == 1) + { + p[4] = tmp; + snprintf(tmp,sizeof(tmp),"DLG%d",(int)(INT_PTR)lpTemplate); + } + else + p[4] = NULL; + DLGPROC newDlg = __localizePrepareDialog(NULL,hInstance,lpTemplate,dlgProc,lParam,p,sizeof(p)/sizeof(p[0])); + if (newDlg) + { + dlgProc = newDlg; + lParam = (LPARAM)(INT_PTR)p; + } + switch (mode) + { + case 0: return CreateDialogParam(hInstance,lpTemplate,hwndParent,dlgProc,lParam); + case 1: return (HWND) (INT_PTR)DialogBoxParam(hInstance,lpTemplate,hwndParent,dlgProc,lParam); + } + return 0; +} + +static int uint64cmpfunc(WDL_UINT64 *a, WDL_UINT64 *b) +{ + if (*a < *b) return -1; + if (*a > *b) return 1; + return 0; +} + +// call at start of file with format_flag=-1 +// expect possible blank (\r or \n) lines, too, ignore them +// format flag will get set to: 0=utf8, 1=utf16le, 2=utf16be, 3=ansi +void WDL_fgets_as_utf8(char *linebuf, int linebuf_size, FILE *fp, int *format_flag) +{ + linebuf[0]=0; + if (*format_flag>0) + { + int sz=0; + while (sz < linebuf_size-8) + { + int a = fgetc(fp); + int b = *format_flag==3 ? 0 : fgetc(fp); + if (a<0 || b<0) break; + if (*format_flag==2) a = (a<<8)+b; + else a += b<<8; + + + again: + if (a >= 0xD800 && a < 0xDC00) // UTF-16 supplementary planes + { + int aa = fgetc(fp); + int bb = fgetc(fp); + if (aa < 0 || bb < 0) break; + + if (*format_flag==2) aa = (aa<<8)+bb; + else aa += bb<<8; + + if (aa >= 0xDC00 && aa < 0xE000) + { + a = 0x10000 + ((a&0x3FF)<<10) + (aa&0x3FF); + } + else + { + a=aa; + goto again; + } + } + + + if (a < 0x80) linebuf[sz++] = a; + else + { + if (a<0x800) // 11 bits (5+6 bits) + { + linebuf[sz++] = 0xC0 + ((a>>6)&31); + } + else + { + if (a < 0x10000) // 16 bits (4+6+6 bits) + { + linebuf[sz++] = 0xE0 + ((a>>12)&15); // 4 bits + } + else // 21 bits yow + { + linebuf[sz++] = 0xF0 + ((a>>18)&7); // 3 bits + linebuf[sz++] = 0x80 + ((a>>12)&63); // 6 bits + } + linebuf[sz++] = 0x80 + ((a>>6)&63); // 6 bits + } + linebuf[sz++] = 0x80 + (a&63); // 6 bits + } + + if (a == '\n') break; + } + linebuf[sz]=0; + } + else + { + fgets(linebuf,linebuf_size,fp); + } + + if (linebuf[0] && *format_flag<0) + { + unsigned char *p=(unsigned char *)linebuf; + if (p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBf) + { + *format_flag=0; + memmove(linebuf,linebuf+3,strlen(linebuf+3)+1); // skip UTF-8 BOM + } + else if ((p[0] == 0xFF && p[1] == 0xFE) || (p[0] == 0xFE && p[1] == 0xFF)) + { + fseek(fp,2,SEEK_SET); + *format_flag=p[0] == 0xFE ? 2 : 1; + WDL_fgets_as_utf8(linebuf,linebuf_size,fp,format_flag); + return; + } + else + { + for (;;) + { + const unsigned char *str=(unsigned char *)linebuf; + while (*str) + { + unsigned char c = *str++; + if (c >= 0xC0) + { + if (c <= 0xDF && str[0] >=0x80 && str[0] <= 0xBF) str++; + else if (c <= 0xEF && str[0] >=0x80 && str[0] <= 0xBF && str[1] >=0x80 && str[1] <= 0xBF) str+=2; + else if (c <= 0xF7 && + str[0] >=0x80 && str[0] <= 0xBF && + str[1] >=0x80 && str[1] <= 0xBF && + str[2] >=0x80 && str[2] <= 0xBF) str+=3; + else break; + } + else if (c >= 128) break; + } + if (*str) break; + + linebuf[0]=0; + fgets(linebuf,linebuf_size,fp); + if (!linebuf[0]) break; + } + *format_flag = linebuf[0] ? 3 : 0; // if scanned the whole file without an invalid UTF8 pair, then UTF-8 (0), otherwise ansi (3) + fseek(fp,0,SEEK_SET); + WDL_fgets_as_utf8(linebuf,linebuf_size,fp,format_flag); + return; + } + } +} + +WDL_AssocArray *WDL_GetLangpackSection(const char *sec) +{ + return g_translations.Get(sec); +} + +WDL_AssocArray *WDL_LoadLanguagePackInternal(const char *fn, + WDL_StringKeyedArray< WDL_AssocArray * > *dest, + const char *onlySec_name, + bool include_commented_lines, + bool no_escape_strings, + WDL_StringKeyedArray *extra_metadata + ) +{ +#ifdef _DEBUG + g_debug_langpack_has_loaded = true; +#endif + WDL_AssocArray *rv=NULL; + FILE *fp = fopenUTF8(fn,"r"); + if (!fp) return rv; + + WDL_AssocArray *cursec=NULL; + + int format_flag=-1; + + WDL_TypedBuf procbuf; + char linebuf[16384]; + int ic_lines = 0; + for (;;) + { + WDL_fgets_as_utf8(linebuf,sizeof(linebuf),fp,&format_flag); + if (!linebuf[0]) break; + + char *p=linebuf; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + char *lbstart = p; + while (*p) p++; + p--; + while (p >= lbstart && (*p == '\t' || *p == '\n' || *p == '\r')) p--; + p++; + *p=0; + + if (include_commented_lines) + { + if (*lbstart == ';') + { + int x, offs = (lbstart[1] == '^') ? 2 : 1; + for (x = 0; x < 16; x ++) + { + char c = lbstart[offs+x]; + if (c >= 'A' && c <= 'F') { } + else if (c >= '0' && c <= '9') { } + else break; + } + if (x == 16 && lbstart[offs+16] == '=') + lbstart += offs; + } + } + if (!*lbstart || *lbstart == ';' || *lbstart == '#') + { + if (ic_lines >= 0 && extra_metadata) + { + char tmp[128]; + snprintf(tmp,sizeof(tmp),"_initial_comment_%d",ic_lines++); + extra_metadata->Insert(tmp,strdup(linebuf)); + } + continue; + } + + if (*lbstart == '[') + { + ic_lines = -1; + if (cursec) cursec->Resort(); + + lbstart++; + { + char *tmp = lbstart; + while (*tmp && *tmp != ']') tmp++; + *tmp++=0; + if (extra_metadata) + { + while (*tmp == ' ') tmp++; + if (*tmp) + extra_metadata->Insert(lbstart,strdup(tmp)); + } + } + + if (onlySec_name) + { + cursec=NULL; + if (!strcmp(lbstart,onlySec_name)) + { + if (!rv) rv=new WDL_AssocArray(uint64cmpfunc); + cursec=rv; + } + } + else + { + cursec = dest->Get(lbstart); + if (!cursec) + { + cursec = new WDL_AssocArray(uint64cmpfunc); + dest->Insert(lbstart,cursec); + } + } + } + else if (cursec) + { + char *eq = strstr(lbstart,"="); + if (eq) + { + *eq++ = 0; + if (strlen(lbstart) == 16) + { + WDL_UINT64 v=0; + int x; + for (x=0;x<16;x++) + { + int a = lbstart[x]; + if (a>='0' && a<='9') a-='0'; + else if (a>='A' && a<='F') a-='A' - 10; + else break; + + v<<=4; + v+=a; + } + if (x==16) + { + if (strstr(eq,"\\") && !no_escape_strings) + { + procbuf.Resize(0,false); + while (*eq) + { + if (*eq == '\\') + { + eq++; + if (*eq == '\\' || *eq == '\'' || *eq == '"') procbuf.Add(*eq); + else if (*eq == 't'||*eq=='T') procbuf.Add('\t'); + else if (*eq == 'r'||*eq=='R') procbuf.Add('\r'); + else if (*eq == 'n'||*eq=='N') procbuf.Add('\n'); + else if (*eq == '0') procbuf.Add(0); + else procbuf.Add(*eq); + } + else procbuf.Add(*eq); + + eq++; + } + procbuf.Add(0); + procbuf.Add(0); + char *pc = (char *)ChunkAlloc(procbuf.GetSize()); + if (pc) + { + memcpy(pc,procbuf.Get(),procbuf.GetSize()); + cursec->AddUnsorted(v,pc); + } + } + else + { + int eqlen = (int)strlen(eq); + char *pc = (char *)ChunkAlloc(eqlen+2); + if (pc) + { + memcpy(pc,eq,eqlen+1); + pc[eqlen+1]=0; // doublenull terminate to be safe, in case the caller requested LOCALIZE_FLAG_NULLPAIR + cursec->AddUnsorted(v,pc); + } + } + } + } + } + } + } + if (cursec) + cursec->Resort(); + + fclose(fp); + + return rv; +} + +WDL_AssocArray *WDL_LoadLanguagePack(const char *fn, const char *onlySec_name) +{ + WDL_AssocArray *rv = WDL_LoadLanguagePackInternal(fn,&g_translations, onlySec_name,false,false, NULL); + if (!onlySec_name) + g_translations_commonsec = g_translations.Get("common"); + return rv; +} + +void WDL_SetLangpackFallbackEntry(const char *src_sec, WDL_UINT64 src_v, const char *dest_sec, WDL_UINT64 dest_v) +{ + WDL_AssocArray *sec = g_translations.Get(src_sec); + char *v = sec ? sec->Get(src_v) : NULL; + if (!v) return; + sec = g_translations.Get(dest_sec); + if (!sec) + { + sec = new WDL_AssocArray(uint64cmpfunc); + g_translations.Insert(dest_sec,sec); + } + else if (sec->Get(dest_v)) return; + + sec->Insert(dest_v,v); +} + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize.h b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize.h new file mode 100644 index 0000000..75f1335 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/localize.h @@ -0,0 +1,95 @@ +#ifndef _WDL_LOCALIZE_H_ +#define _WDL_LOCALIZE_H_ + +#include "../assocarray.h" + +// normally onlySec_name is NULL and returns NULL (and updates global state) +// if onlySec_name is set, only loads/returns section in question and does not update global state. +WDL_AssocArray *WDL_LoadLanguagePack(const char *buf, const char *onlySec_name); + +WDL_AssocArray *WDL_LoadLanguagePackInternal(const char *buf, + WDL_StringKeyedArray< WDL_AssocArray * > *dest, + const char *onlySec_name, // if set, will not touch dest + bool include_commented_lines, + bool no_escape_strings, + WDL_StringKeyedArray *extra_metadata + ); + +WDL_AssocArray *WDL_GetLangpackSection(const char *sec); + +void WDL_SetLangpackFallbackEntry(const char *src_sec, WDL_UINT64 src_v, const char *dest_sec, WDL_UINT64 dest_v); + +#define LOCALIZE_FLAG_VERIFY_FMTS 1 // verifies translated format-string (%d should match %d, etc) +#define LOCALIZE_FLAG_NOCACHE 2 // must use this if the string passed is not a persistent static string, or if in another thread +#define LOCALIZE_FLAG_PAIR 4 // one \0 in string needed -- this is not doublenull terminated but just a special case +#define LOCALIZE_FLAG_DOUBLENULL 8 // doublenull terminated string + + +#ifdef LOCALIZE_DISABLE +#define __LOCALIZE(str, ctx) str +#define __LOCALIZE_2N(str,ctx) str +#define __LOCALIZE_VERFMT(str, ctx) str +#define __LOCALIZE_NOCACHE(str, ctx) str +#define __LOCALIZE_VERFMT_NOCACHE(str, ctx) str +#define __LOCALIZE_LCACHE(str, ctx, pp) const char *pp = str +#define __LOCALIZE_VERFMT_LCACHE(str, ctx, pp) const char *pp = str +#define LOCALIZE_GET_SCALE_STRING(ctx) ("") +#else +#define __LOCALIZE(str, ctx) __localizeFunc("" str "" , "" ctx "",0) +#define __LOCALIZE_2N(str,ctx) __localizeFunc("" str "" , "" ctx "",LOCALIZE_FLAG_PAIR) +#define __LOCALIZE_VERFMT(str, ctx) __localizeFunc("" str "", "" ctx "",LOCALIZE_FLAG_VERIFY_FMTS) +#define __LOCALIZE_NOCACHE(str, ctx) __localizeFunc("" str "", "" ctx "",LOCALIZE_FLAG_NOCACHE) +#define __LOCALIZE_VERFMT_NOCACHE(str, ctx) __localizeFunc("" str "", "" ctx "",LOCALIZE_FLAG_VERIFY_FMTS|LOCALIZE_FLAG_NOCACHE) +#define __LOCALIZE_LCACHE(str, ctx, pp) static const char *pp; if (!pp) pp = __localizeFunc("" str "", "" ctx "",LOCALIZE_FLAG_NOCACHE) +#define __LOCALIZE_VERFMT_LCACHE(str, ctx, pp) static const char *pp; if (!pp) pp = __localizeFunc("" str "", "" ctx "",LOCALIZE_FLAG_VERIFY_FMTS|LOCALIZE_FLAG_NOCACHE) +#define LOCALIZE_GET_SCALE_STRING(ctx) __localizeFunc("__LOCALIZE_SCALE\0",ctx,LOCALIZE_FLAG_PAIR|LOCALIZE_FLAG_NOCACHE) +#endif + +#define __LOCALIZE_REG_ONLY(str, ctx) str + + +// localize a string +const char *__localizeFunc(const char *str, const char *subctx, int flags); + +// localize a menu; rescat can be NULL, or a prefix +void __localizeMenu(const char *rescat, HMENU hMenu, LPCSTR lpMenuName); + +void __localizeInitializeDialog(HWND hwnd, const char *desc); // only use for certain child windows that we can't control creation of, pass desc of DLG_xyz etc + +// localize a dialog; rescat can be NULL, or a prefix +// if returns non-NULL, use retval as dlgproc and pass ptrs as LPARAM to dialogboxparam +// nptrs should be at least 4 (might increase someday, but this will only ever be used by utilfunc.cpp or localize-import.h) +DLGPROC __localizePrepareDialog(const char *rescat, HINSTANCE hInstance, const char *lpTemplate, DLGPROC dlgProc, LPARAM lParam, void **ptrs, int nptrs); + + +#ifndef LOCALIZE_NO_DIALOG_MENU_REDEF +#undef DialogBox +#undef CreateDialog +#undef DialogBoxParam +#undef CreateDialogParam + +#ifdef _WIN32 + #define DialogBoxParam(a,b,c,d,e) ((INT_PTR)__localizeDialog(a,b,c,d,e,1)) + #define CreateDialogParam(a,b,c,d,e) __localizeDialog(a,b,c,d,e,0) +#else + #define DialogBoxParam(a,b,c,d,e) ((INT_PTR)__localizeDialog(NULL,b,c,d,e,1)) + #define CreateDialogParam(a,b,c,d,e) __localizeDialog(NULL,b,c,d,e,0) +#endif + +#define DialogBox(hinst,lpt,hwndp,dlgproc) DialogBoxParam(hinst,lpt,hwndp,dlgproc,0) +#define CreateDialog(hinst,lpt,hwndp,dlgproc) CreateDialogParam(hinst,lpt,hwndp,dlgproc,0) + + +#undef LoadMenu +#define LoadMenu __localizeLoadMenu +#endif + +HMENU __localizeLoadMenu(HINSTANCE hInstance, const char *lpMenuName); +HWND __localizeDialog(HINSTANCE hInstance, const char * lpTemplate, HWND hwndParent, DLGPROC dlgProc, LPARAM lParam, int mode); + +#define __localizeDialogBoxParam(a,b,c,d,e) ((INT_PTR)__localizeDialog(a,b,c,d,e,1)) +#define __localizeCreateDialogParam(a,b,c,d,e) __localizeDialog(a,b,c,d,e,0) + +extern void (*localizePreInitDialogHook)(HWND hwndDlg); + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/localize/merge_langpacks.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/merge_langpacks.cpp new file mode 100644 index 0000000..a870c97 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/localize/merge_langpacks.cpp @@ -0,0 +1,432 @@ +#include +#include +#include +#ifndef _WIN32 +#define stricmp strcasecmp +#include +#endif + +#include "../assocarray.h" +#include "../wdlstring.h" +#include "../ptrlist.h" +#include "../wdlcstring.h" + + + + +// call at start of file with format_flag=-1 +// expect possible blank (\r or \n) lines, too, ignore them +static void fgets_as_utf8(char *linebuf, int linebuf_size, FILE *fp, int *format_flag) +{ + // format flag: 0=utf8, 1=utf16le, 2=utf16be, 3=ansi + linebuf[0]=0; + if (*format_flag>0) + { + int sz=0; + while (sz < linebuf_size-8) + { + int a = fgetc(fp); + int b = *format_flag==3 ? 0 : fgetc(fp); + if (a<0 || b<0) break; + if (*format_flag==2) a = (a<<8)+b; + else a += b<<8; + + + again: + if (a >= 0xD800 && a < 0xDC00) // UTF-16 supplementary planes + { + int aa = fgetc(fp); + int bb = fgetc(fp); + if (aa < 0 || bb < 0) break; + + if (*format_flag==2) aa = (aa<<8)+bb; + else aa += bb<<8; + + if (aa >= 0xDC00 && aa < 0xE000) + { + a = 0x10000 + ((a&0x3FF)<<10) + (aa&0x3FF); + } + else + { + a=aa; + goto again; + } + } + + + if (a < 0x80) linebuf[sz++] = a; + else + { + if (a<0x800) // 11 bits (5+6 bits) + { + linebuf[sz++] = 0xC0 + ((a>>6)&31); + } + else + { + if (a < 0x10000) // 16 bits (4+6+6 bits) + { + linebuf[sz++] = 0xE0 + ((a>>12)&15); // 4 bits + } + else // 21 bits yow + { + linebuf[sz++] = 0xF0 + ((a>>18)&7); // 3 bits + linebuf[sz++] = 0x80 + ((a>>12)&63); // 6 bits + } + linebuf[sz++] = 0x80 + ((a>>6)&63); // 6 bits + } + linebuf[sz++] = 0x80 + (a&63); // 6 bits + } + + if (a == '\n') break; + } + linebuf[sz]=0; + } + else + { + fgets(linebuf,linebuf_size,fp); + } + + if (linebuf[0] && *format_flag<0) + { + unsigned char *p=(unsigned char *)linebuf; + if (p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBf) + { + *format_flag=0; + memmove(linebuf,linebuf+3,strlen(linebuf+3)+1); // skip UTF-8 BOM + } + else if ((p[0] == 0xFF && p[1] == 0xFE) || (p[0] == 0xFE && p[1] == 0xFF)) + { + fseek(fp,2,SEEK_SET); + *format_flag=p[0] == 0xFE ? 2 : 1; + fgets_as_utf8(linebuf,linebuf_size,fp,format_flag); + return; + } + else + { + for (;;) + { + const unsigned char *str=(unsigned char *)linebuf; + while (*str) + { + unsigned char c = *str++; + if (c >= 0xC0) + { + if (c <= 0xDF && str[0] >=0x80 && str[0] <= 0xBF) str++; + else if (c <= 0xEF && str[0] >=0x80 && str[0] <= 0xBF && str[1] >=0x80 && str[1] <= 0xBF) str+=2; + else if (c <= 0xF7 && + str[0] >=0x80 && str[0] <= 0xBF && + str[1] >=0x80 && str[1] <= 0xBF && + str[2] >=0x80 && str[2] <= 0xBF) str+=3; + else break; + } + else if (c >= 128) break; + } + if (*str) break; + + linebuf[0]=0; + fgets(linebuf,linebuf_size,fp); + if (!linebuf[0]) break; + } + *format_flag = linebuf[0] ? 3 : 0; // if scanned the whole file without an invalid UTF8 pair, then UTF-8 (0), otherwise ansi (3) + fseek(fp,0,SEEK_SET); + fgets_as_utf8(linebuf,linebuf_size,fp,format_flag); + return; + } + } +} + + +bool read_ini(const char *fn, WDL_StringKeyedArray< WDL_StringKeyedArray * > *sections, WDL_String *mod_name, int options) +{ + FILE *fp = fopen(fn,"r"); + if (!fp) return false; + + WDL_StringKeyedArray *cursec=NULL; + + bool warn=false; + int char_fmt=-1; + char linebuf[16384]; + int linecnt=0; + for (;;) + { + linebuf[0]=0; + fgets_as_utf8(linebuf,sizeof(linebuf),fp,&char_fmt); + if (!linebuf[0]) break; + if (char_fmt==3 && !warn) + { + fprintf(stderr,"Warning: '%s' was not saved with UTF-8 or UTF-16 encoding, the merged Langpack might contain garbled characters.\n", fn); + warn=true; + } + + char *p=linebuf; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + char *lbstart = p; + while (*p) p++; + p--; + while (p >= lbstart) + { + if (options&1) { if (*p == '\n' || *p == '\r') p--; else break; } + else { if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p--; else break; } + } + p++; + *p=0; + + if (!linecnt && !strncmp(lbstart,"#NAME:",6)) + { + mod_name->Set(lbstart+6); + } + + linecnt++; + + if (!*lbstart || *lbstart == '#') continue; + + if (*lbstart == '[') + { + if (cursec) cursec->Resort(); + + lbstart++; + { + char *tmp = lbstart; + while (*tmp && *tmp != ']') tmp++; + *tmp=0; + } + + cursec = sections->Get(lbstart); + if (!cursec) + { + cursec = new WDL_StringKeyedArray(false,WDL_StringKeyedArray::freecharptr); + sections->Insert(lbstart,cursec); + } + } + else if (cursec) + { + char *eq = strstr(lbstart,"="); + if (eq) + { + *eq++ = 0; + cursec->AddUnsorted(lbstart,strdup(eq)); + } + } + } + if (cursec) + cursec->Resort(); + + fclose(fp); + return true; +} + + +char *GotKey(WDL_StringKeyedArray *cursec_modptr, WDL_StringKeyedArray *cursec_hadvals, char *key) // key[19], can be mangled +{ + char *match=NULL; + if (cursec_modptr) + { + int start_idx=2; + match = cursec_modptr->Get(key+start_idx); + if (!match) + { + start_idx=1; + key[1]=';'; + match = cursec_modptr->Get(key+start_idx); + } + if (!match) + { + start_idx=0; + key[0]=';'; + key[1]='^'; + match = cursec_modptr->Get(key+start_idx); + } + if (match) + { + printf("%s=%s\n",key+start_idx,match); + cursec_hadvals->Insert(key+2,true); + } + } + return match; +} + + + + + +int main(int argc, char **argv) +{ + int i; + int options = 0; + for (i=3; i3 && !options)) + { + fprintf(stderr,"Usage: merge_langpacks new_template.txt my_language.txt [options] > new_language.txt\n"); + fprintf(stderr,"Options:\n"); + fprintf(stderr," -p: preserve trailing spaces/tabs\n"); + fprintf(stderr," -c: comment obsolete sections/strings\n"); + return 1; + } + FILE *reffp = fopen(argv[1],"r"); + if (!reffp) + { + fprintf(stderr,"Error reading '%s', invalid template langpack.\n",argv[1]); + return 1; + } + WDL_StringKeyedArray * > mods; + WDL_String mod_name; + if (!read_ini(argv[2],&mods,&mod_name,options)||!mods.GetSize()) + { + fprintf(stderr,"Error reading '%s', invalid langpack.\n",argv[2]); + return 1; + } + + int char_fmt=-1; + + //printf("%c%c%c",0xEF,0xBB,0xBF); // UTF-8 BOM + WDL_String cursec; + WDL_StringKeyedArray *cursec_modptr=NULL; + WDL_StringKeyedArray cursec_hadvals(false); + WDL_PtrList cursec_added; + + bool warn=false; + char key[2+16+1]; + int line_count=0; + for (;;) + { + char buf[16384]; + buf[0]=0; + fgets_as_utf8(buf,sizeof(buf),reffp,&char_fmt); + if (char_fmt==3 && !warn) + { + fprintf(stderr,"Warning: '%s' was not saved with UTF-8 or UTF-16 encoding, the merged Langpack might contain garbled characters.\n", argv[1]); + warn=true; + } + + bool wasZ = !buf[0]; + + // remove any trailing newlines + char *p=buf; + while (*p) p++; + while (p>buf && (p[-1] == '\r'||p[-1] == '\n')) p--; + *p=0; + + // skip any leading space + p=buf; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + + line_count++; + if (line_count==1 && mod_name.Get()[0]) + { + printf("#NAME:%s\n",mod_name.Get()); + if (!strncmp(p,"#NAME:",6)) continue; // ignore template #NAME: line, use modded + } + + char *np; + if (wasZ || (p[0] == '[' && (np = strstr(p,"]")) && np > p+1)) + { + if (cursec.Get()[0]) + { + // finish current section + if (cursec_added.GetSize()) + { + printf("######## added in template (section: %s):\n",cursec.Get()); + int x; + for(x=0;xGetSize();x++) + { + const char *nm=NULL; + const char *val= cursec_modptr->Enumerate(x,&nm); + if (!nm || !val) break; + + if (nm[0] == ';') continue; + + if (!cursec_hadvals.Get(nm)) + { + if (!oc++) printf("######## not in template, possibly obsolete (section: %s):\n",cursec.Get()); + printf("%s%s=%s\n", (options&2) ? ";" : "", nm, val); + } + } + if (oc) printf("\n"); + + } + + mods.Delete(cursec.Get()); + cursec_hadvals.DeleteAll(); + cursec_added.Empty(true,free); + cursec_modptr=NULL; + } + + if (wasZ) break; + + printf("%s\n",buf); + cursec.Set(p+1, (int)(np-p-1)); + cursec_modptr = mods.Get(cursec.Get()); + + // if any, preserve the scaling at the top of the section + strcpy(key+2,"5CA1E00000000000"); + GotKey(cursec_modptr, &cursec_hadvals, key); + // done, scaling lines are added by hand (not part of templates) + + continue; + } + + + if (!cursec.Get()[0]) printf("%s\n",buf); + else + { + char *eq = strstr(p,"="); + if (!eq) printf("%s\n",buf); + else + { + if (p[0] == ';' && *++p == '^') p++; + if (eq-p==16) + { + lstrcpyn_safe(key+2, p, 17); + if (!GotKey(cursec_modptr, &cursec_hadvals, key)) + { + cursec_added.Add(strdup(buf)); + } + } + else + { + printf("%s\n",buf); + } + } + } + } + + fclose(reffp); + + int x; + for (x=0;xGetSize();y++) + { + const char *nm=NULL; + const char *v=cursec_modptr->Enumerate(y,&nm); + if (!nm || !v) break; + printf("%s%s=%s\n", (options&2) && nm[0]!=';' ? ";" : "", nm, v); + } + } + + return 0; +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/mergesort.h b/plugin-reaper-realearn/main/lib/WDL/WDL/mergesort.h new file mode 100644 index 0000000..7a8c862 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/mergesort.h @@ -0,0 +1,66 @@ +#ifndef _WDL_MERGESORT_H_ +#define _WDL_MERGESORT_H_ + +#include "wdltypes.h" + +static void WDL_STATICFUNC_UNUSED WDL_mergesort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *), char *tmpspace) +{ + char *b1,*b2; + size_t n1, n2; + + if (nmemb < 2) return; + + n1 = nmemb / 2; + b1 = (char *) base; + n2 = nmemb - n1; + b2 = b1 + (n1 * size); + + if (nmemb>2) + { + WDL_mergesort(b1, n1, size, compar, tmpspace); + WDL_mergesort(b2, n2, size, compar, tmpspace); + } + + + do + { + if (compar(b1, b2) > 0) // out of order, go to full merge + { + size_t sofar = b1-(char*)base; + memcpy(tmpspace,base,sofar); + memcpy(tmpspace+sofar, b2, size); + b2 += size; + n2--; + + char *writeptr=tmpspace+sofar+size; + while (n1 > 0 && n2 > 0) + { + if (compar(b1, b2) > 0) + { + memcpy(writeptr, b2, size); + b2 += size; + n2--; + } + else + { + memcpy(writeptr, b1, size); + b1 += size; + n1--; + } + writeptr += size; + } + + if (n1 > 0) memcpy(writeptr, b1, n1 * size); + memcpy(base, tmpspace, (nmemb - n2) * size); + + break; + } + + // in order, just advance + b1 += size; + n1--; + } + while (n1 > 0 && n2 > 0); +} + +#endif//_WDL_MERGESORT_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/metadata.h b/plugin-reaper-realearn/main/lib/WDL/WDL/metadata.h new file mode 100644 index 0000000..03f7a2b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/metadata.h @@ -0,0 +1,2271 @@ +#ifndef _WDL_METADATA_H_ +#define _WDL_METADATA_H_ + +#include +#include "wdlstring.h" +#include "xmlparse.h" +#include "fileread.h" +#include "filewrite.h" +#include "queue.h" +#include "win32_utf8.h" +#include "wdl_base64.h" +#include "coreaudio_channel_formats.h" + + +const char *EnumMexKeys(int i, const char **desc=NULL) +{ + // TO_DO_IF_METADATA_UPDATE + static const char *s_mexkeys[]= + {"TITLE", "ARTIST", "ALBUM", "TRACKNUMBER", "YEAR", "GENRE", "COMMENT", "DESC", "BPM", "KEY", "DB_CUSTOM"}; + static const char *s_mexdesc[]= + {"Title", "Artist", "Album", "Track", "Date", "Genre", "Comment", "Description", "BPM", "Key", "Media Explorer Tags"}; + + bool ok = i >= 0 && i < sizeof(s_mexkeys)/sizeof(s_mexkeys[0]); + if (desc) *desc = ok ? s_mexdesc[i] : NULL; + return ok ? s_mexkeys[i] : NULL; +} + + +int MetadataToArray(WDL_StringKeyedArray *metadata, WDL_TypedBuf *metadata_arr) +{ + if (!metadata || !metadata_arr) return 0; + + int cnt=0; + for (int i=0; i < metadata->GetSize(); ++i) + { + const char *k, *v=metadata->Enumerate(i, &k); + if (k && v) + { + metadata_arr->Add(k); + metadata_arr->Add(v); + ++cnt; + } + } + metadata_arr->Add(NULL); + return cnt; +} + +int ArrayToMetadata(const char **metadata_arr, WDL_StringKeyedArray *metadata) +{ + if (!metadata_arr || !metadata) return 0; + + int cnt=0; + for (; metadata_arr[0] && metadata_arr[1]; metadata_arr += 2) + { + metadata->AddUnsorted(metadata_arr[0], strdup(metadata_arr[1])); + ++cnt; + } + if (cnt) metadata->Resort(); + return cnt; +} + + +char *tag_strndup(const char *src, int len) +{ + if (!src || !len) return NULL; + int n=0; + while (n < len && src[n]) ++n; + char *dest=(char*)malloc(n+1); + if (!dest) return NULL; + memcpy(dest, src, n); + dest[n]=0; + return dest; +} + +WDL_UINT64 ParseUInt64(const char *val) +{ + WDL_UINT64 i=0; + if (val) + { + const char *p=val; + while (*p) + { + int d=*p-'0'; + if (d < 0 || d > 9) break; + i=(10*i)+d; + ++p; + } + if (*p) i=0; + } + return i; +} + +void InsertMetadataIncrKeyIfNeeded(WDL_StringKeyedArray *metadata, + const char *key, const char *val) +{ + if (!metadata->Exists(key)) + { + metadata->Insert(key, strdup(val)); + } + else + { + for (int i=2; i < 100; ++i) + { + char str[2048]; + snprintf(str,sizeof(str), "%s:%d", key, i); + if (!metadata->Exists(str)) + { + metadata->Insert(str, strdup(val)); + break; + } + } + } +} + +void XMLCompliantAppend(WDL_FastString *str, const char *txt, bool is_value) +{ + if (str && txt) for (;;) + { + char c = *txt++; + switch (c) + { + case 0: return; + case '<': str->Append("<"); break; + case '>': str->Append(">"); break; + case '&': str->Append("&"); break; + case ' ': str->Append(is_value ? " " : "_"); break; + default: str->Append(&c,1); break; + } + } +} + +const char *XMLHasOpenTag(WDL_FastString *str, const char *tag) // tag like "") +{ + // stupid + int taglen=strlen(tag); + const char *open=strstr(str->Get(), tag); + while (open) + { + const char *close=strstr(open+taglen, tag+1); + if (!close || WDL_NOT_NORMALLY(close[-1] != '/')) break; + open=strstr(close+taglen-1, tag); + } + return open; +} + +void UnpackXMLElement(const char *pre, wdl_xml_element *elem, + WDL_StringKeyedArray *metadata) +{ + WDL_FastString key; + if (stricmp(elem->name, "BWFXML")) + { + key.SetFormatted(512, "%s:%s", pre, elem->name); + pre=key.Get(); + } + if (elem->value.Get()[0]) + { + const char *k=key.Get(); + if (!strncmp(k, "IXML:ASWG:", 10)) + { + k += 5; + } + else if (!strncmp(k, "IXML:BEXT:", 10)) + { + // we could rewrite this as follows, but this might overwrite an actual bext chunk so maybe not? + /* + if (!strcmp(k+10, "BWF_DESCRIPTION")) k="BWF:Description"; + else if (!strcmp(k+10, "BWF_ORIGINATOR")) k="BWF:Originator"; + else if (!strcmp(k+10, "BWF_ORIGINATOR_REFERENCE")) k="BWF:OriginatorReference"; + else if (!strcmp(k+10, "BWF_ORIGINATION_DATE")) k="BWF:OriginationDate"; + else if (!strcmp(k+10, "BWF_ORIGINATION_TIME")) k="BWF:OriginationTime"; + else if (!strcmp(k+10, "BWF_TIME_REFERENCE")) k="BWF:TimeReference"; // todo parse lo/hi + else if (!strcmp(k+10, "BWF_VERSION")) k="BWF:Version"; + else if (!strcmp(k+10, "BWF_LOUDNESS_VALUE")) k="BWF:LoudnessValue"; + else if (!strcmp(k+10, "BWF_LOUDNESS_RANGE")) k="BWF:LoudnessRange"; + else if (!strcmp(k+10, "BWF_MAX_TRUE_PEAK_LEVEL")) k="BWF:MaxTruePeakLevel"; + else if (!strcmp(k+10, "BWF_MAX_MOMENTARY_LOUDNESS")) k="BWF:MaxMomentaryLoudness"; + else if (!strcmp(k+10, "BWF_MAX_SHORT_TERM_LOUDNESS")) k="BWF:MaxShortTermLoudness"; + */ + } + InsertMetadataIncrKeyIfNeeded(metadata, k, elem->value.Get()); + } + for (int i=0; i < elem->elements.GetSize(); ++i) + { + wdl_xml_element *elem2=elem->elements.Get(i); + UnpackXMLElement(pre, elem2, metadata); + } +} + +bool UnpackIXMLChunk(const char *buf, int buflen, + WDL_StringKeyedArray *metadata) +{ + if (!buf || !buflen || !metadata) return false; + + while (buflen > 20 && strnicmp(buf, "", 8)) + { + ++buf; + --buflen; + } + if (buflen >= 20) + { + wdl_xml_parser xml(buf, buflen); + if (!xml.parse() && xml.element_root) + { + UnpackXMLElement("IXML", xml.element_root, metadata); + return true; + } + } + + return false; +} + +bool IsXMPMetadata(const char *name, WDL_FastString *key) +{ + if (!name || !name[0] || !key) return false; + + // returns true if this XMP schema is one we know/care about + if (!strnicmp(name, "xmpDM:", 6) && name[6]) + { + key->SetFormatted(512, "XMP:dm/%s", name+6); + return true; + } + if (!strnicmp(name, "dc:", 3) && name[3]) + { + key->SetFormatted(512, "XMP:dc/%s", name+3); + return true; + } + return false; +} + +double UnpackXMPTimestamp(wdl_xml_element *elem) +{ + double tval=-1.0; + int num=0, denom=0; + for (int i=0; i < elem->attributes.GetSize(); ++i) + { + char *attr; + const char *val=elem->attributes.Enumerate(i, &attr); + if (!strcmp(attr, "xmpDM:scale") && val && val[0]) + { + if (sscanf(val, "%d/%d", &num, &denom) != 2) num=denom=0; + } + else if (!strcmp(attr, "xmpDM:value") && val && val[0]) + { + tval=atof(val); + } + } + if (tval >= 0.0 && num > 0 && denom > 0) + { + return tval*(double)num/(double)denom; + } + return -1.0; +} + +const char *GetXMPSubElement(wdl_xml_element *elem, const char *name) +{ + for (int i=0; i < elem->elements.GetSize(); ++i) + { + wdl_xml_element *elem2=elem->elements.Get(i); + if (!strcmp(elem2->name, name)) return elem2->value.Get(); // may be "" + } + return NULL; // element does not exist +} + +bool IsXMPResourceList(wdl_xml_element *elem) +{ + if (!strcmp(elem->name, "rdf:li")) + { + const char *val=elem->get_attribute("rdf:parseType"); + if (val && !strcmp(val, "Resource")) return true; + } + return false; +} + +// keep in sync with metadata.cpp:XMP_MARKER_RESOLUTION +#define XMP_MARKER_RESOLUTION 1000000 + +// todo generic PopulateCuesFromMetadata function metadata => list of REAPERCues + +void UnpackXMPTrack(wdl_xml_element *elem, WDL_StringKeyedArray *metadata) +{ + int num_markers=0; + WDL_FastString key, val; + for (int i=0; i < elem->elements.GetSize(); ++i) + { + wdl_xml_element *elem2=elem->elements.Get(i); + if (!strcmp(elem2->name, "rdf:Bag")) + { + for (int i2=0; i2 < elem2->elements.GetSize(); ++i2) + { + wdl_xml_element *elem3=elem2->elements.Get(i2); + if (IsXMPResourceList(elem3)) + { + const char *track_type=GetXMPSubElement(elem3, "xmpDM:trackType"); + const char *frame_rate=GetXMPSubElement(elem3, "xmpDM:frameRate"); + if (track_type && frame_rate && !strcmp(track_type, "Cue")) + { + double marker_resolution=0; + if (frame_rate[0] == 'f') marker_resolution=atof(frame_rate+1); + // todo parse other resolution types + if (marker_resolution > 0.0) + { + double marker_convert=XMP_MARKER_RESOLUTION/marker_resolution; + for (int i3=0; i3 < elem3->elements.GetSize(); ++i3) + { + wdl_xml_element *elem4=elem3->elements.Get(i3); + if (!strcmp(elem4->name, "xmpDM:markers")) + { + for (int i4=0; i4 < elem4->elements.GetSize(); ++i4) + { + wdl_xml_element *elem5=elem4->elements.Get(i4); + if (!strcmp(elem5->name, "rdf:Seq")) + { + for (int i5=0; i5 < elem5->elements.GetSize(); ++i5) + { + wdl_xml_element *elem6=elem5->elements.Get(i4); + const char *start_time=GetXMPSubElement(elem6, "xmpDM:startTime"); + const char *duration=GetXMPSubElement(elem6, "xmpDM:duration"); + const char *marker_name=GetXMPSubElement(elem6, "xmpDM:name"); + if (start_time) + { + double st=atof(start_time)*marker_convert; + double et = st + (duration ? atof(duration)*marker_convert : 0.0); + key.SetFormatted(512, "XMP:MARK%03d", num_markers++); + val.SetFormatted(512, "%.0f:%.0f:", st, et); + if (marker_name && marker_name[0]) val.Append(marker_name); + else val.AppendFormatted(512, "Marker %d", num_markers); + metadata->Insert(key.Get(), strdup(val.Get())); + } + } + } + } + } + } + } + } + } + } + } + } +} + +void UnpackXMPDescription(const char *curkey, wdl_xml_element *elem, + WDL_StringKeyedArray *metadata) +{ + if (!strcmp(elem->name, "xmpDM:Tracks")) + { + // xmp "tracks" are collections of markers and other related data, + // we can parse the markers (at least ones we wronte) but otherwise + // we can ignore this entire block + UnpackXMPTrack(elem, metadata); + return; + } + + if (!strcmp(elem->name, "xmpDM:relativeTimestamp")) + { + double tval=UnpackXMPTimestamp(elem); + if (tval >= 0.0) + { + char buf[512]; + snprintf(buf, sizeof(buf), "%.0f", floor(tval*1000.0)); + metadata->Insert("XMP:dm/relativeTimestamp", strdup(buf)); + } + return; + } + + WDL_FastString key; + int i; + for (i=0; i < elem->attributes.GetSize(); ++i) + { + char *attr; + const char *val=elem->attributes.Enumerate(i, &attr); + if (IsXMPMetadata(attr, &key) && val && val[0]) + { + metadata->Insert(key.Get(), strdup(val)); + } + } + + if (IsXMPMetadata(elem->name, &key)) curkey=key.Get(); + if (curkey && elem->value.Get()[0]) + { + InsertMetadataIncrKeyIfNeeded(metadata, curkey, elem->value.Get()); + } + + for (i=0; i < elem->elements.GetSize(); ++i) + { + wdl_xml_element *elem2=elem->elements.Get(i); + UnpackXMPDescription(curkey, elem2, metadata); + } +} + +void UnpackXMPElement(wdl_xml_element *elem, WDL_StringKeyedArray *metadata) +{ + if (!strcmp(elem->name, "rdf:Description")) + { + // everything we care about is in this block + UnpackXMPDescription(NULL, elem, metadata); + return; + } + + for (int i=0; i < elem->elements.GetSize(); ++i) + { + wdl_xml_element *elem2=elem->elements.Get(i); + UnpackXMPElement(elem2, metadata); + } +} + +bool UnpackXMPChunk(const char *buf, int buflen, + WDL_StringKeyedArray *metadata) +{ + if (!buf || !buflen || !metadata) return false; + + wdl_xml_parser xmp(buf, buflen); + if (!xmp.parse() && xmp.element_root) + { + UnpackXMPElement(xmp.element_root, metadata); + return true; + } + + return false; +} + + +// metadata is passed as an assocarray of id=>value strings, +// where id has the form "scheme:identifier" +// example "ID3:TIT2", "INFO:IPRD", "VORBIS:ALBUM", etc +// for user-defined metadata, the form is extended to "scheme:identifier:key", +// example "ID3:TXXX:mykey", "VORBIS:USER:mykey", etc +// id passed to this function is just "identifier:key" part +// NOTE: WDL/lameencdec and WDL/vorbisencdec have copies of this, so edit them if this changes +bool ParseUserDefMetadata(const char *id, const char *val, + const char **k, const char **v, int *klen, int *vlen) +{ + const char *sep=strchr(id, ':'); + if (sep) // key encoded in id, version >= 6.12 + { + *k=sep+1; + *klen=strlen(*k); + *v=val; + *vlen=strlen(*v); + return true; + } + + sep=strchr(val, '='); + if (sep) // key encoded in value, version <= 6.11 + { + *k=val; + *klen=sep-val; + *v=sep+1; + *vlen=strlen(*v); + return true; + } + + // no key, version <= 6.11 + *k="User"; + *klen=strlen(*k); + *v=val; + *vlen=strlen(*v); + return false; +} + + +bool HasScheme(const char *scheme, WDL_StringKeyedArray *metadata) +{ + if (!scheme || !scheme[0] || !metadata) return false; + + bool ismatch=false; + int idx=metadata->LowerBound(scheme, &ismatch); + const char *key=NULL; + metadata->Enumerate(idx, &key); + if (key && !strnicmp(key, scheme, strlen(scheme))) return true; + return false; +} + + +WDL_INT64 _ATOI64(const char *str) +{ + bool neg=false; + if (*str == '-') { neg=true; str++; } + WDL_INT64 v=0; + while (*str >= '0' && *str <= '9') + { + v = (v*10) + (WDL_INT64) (neg ? -(*str - '0') : (*str - '0')); + str++; + } + return v; +} + + +int PackIXMLChunk(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata, int padtolen) +{ + if (!hb || !metadata) return 0; + + if (!HasScheme("IXML", metadata) && + !HasScheme("ASWG", metadata) && + !HasScheme("BWF", metadata)) + { + return 0; + } + + int olen=hb->GetSize(); + + WDL_FastString ixml; + const char *ixml_open=""; + const char *need_close=NULL; + int junklen=0; + + for (int i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (!key || !key[0] || !val || !val[0]) continue; + + const char *sec = + !strncmp(key, "ASWG:", 5) ? "ASWG" : + !strncmp(key, "BWF:", 4) ? "BWF" : + !strncmp(key, "IXML:USER:", 10) ? "USER" : + !strncmp(key, "IXML:", 5) ? "IXML" : + NULL; + if (!sec) continue; + + key += strlen(sec)+1; + if (!strcmp(sec, "BWF")) + { + if (!strcmp(key, "Description")) key="BWF_DESCRIPTION"; + else if (!strcmp(key, "Originator")) key="BWF_ORIGINATOR"; + else if (!strcmp(key, "OriginatorReference")) key="BWF_ORIGINATOR_REFERENCE"; + else if (!strcmp(key, "OriginationDate")) key="BWF_ORIGINATION_DATE"; + else if (!strcmp(key, "OriginationTime")) key="BWF_ORIGINATION_TIME"; + else if (!strcmp(key, "TimeReference")) key="BWF_TIME_REFERENCE"; + else if (!strcmp(key, "Version")) key="BWF_VERSION"; + else if (!strcmp(key, "LoudnessValue")) key="BWF_LOUDNESS_VALUE"; + else if (!strcmp(key, "LoudnessRange")) key="BWF_LOUDNESS_RANGE"; + else if (!strcmp(key, "MaxTruePeakLevel")) key="BWF_MAX_TRUE_PEAK_LEVEL"; + else if (!strcmp(key, "MaxMomentaryLoudness")) key="BWF_MAX_MOMENTARY_LOUDNESS"; + else if (!strcmp(key, "MaxShortTermLoudness")) key="BWF_MAX_SHORT_TERM_LOUDNESS"; + else continue; + } + + if (!ixml.GetLength()) ixml.Append(ixml_open); + + if (need_close && strcmp(need_close, sec)) + { + ixml.AppendFormatted(512, "", need_close); + need_close=NULL; + } + if (!need_close && strcmp(sec, "IXML")) + { + ixml.AppendFormatted(512, "<%s>", sec); + need_close=sec; + } + + if (!strcmp(key, "BWF_TIME_REFERENCE")) + { + WDL_UINT64 pos=_ATOI64(val); + int hi=pos>>32, lo=(pos&0xFFFFFFFF); + ixml.AppendFormatted(4096, "<%s_HIGH>%d", key, hi, key); + ixml.AppendFormatted(4096, "<%s_LOW>%d", key, lo, key); + continue; + } + + if (!strcmp(sec, "USER")) + { + const char *k, *v; + int klen, vlen; + ParseUserDefMetadata(key, val, &k, &v, &klen, &vlen); + key=k; + val=v; + } + + if (!strncmp(val, "#junk#", 6)) + { + junklen += 11+2*strlen(key)+strlen(val); + continue; + } + + ixml.Append("<"); + XMLCompliantAppend(&ixml, key, false); + ixml.Append(">"); + XMLCompliantAppend(&ixml, val, true); + ixml.Append(""); + } + if (need_close) + { + ixml.AppendFormatted(512, "", need_close); + } + + if (ixml.GetLength()) + { + ixml.Append(""); + int ixmllen=ixml.GetLength(); + int len=ixmllen+1+junklen; + if (len < padtolen) len=padtolen; + if (len&1) ++len; + unsigned char *p=(unsigned char*)hb->ResizeOK(olen+len); + if (WDL_NORMALLY(p != NULL)) + { + memcpy(p+olen, ixml.Get(), ixmllen); + memset(p+olen+ixmllen, 0, len-ixmllen); + } + } + + return hb->GetSize()-olen; +} + + +int PackXMPChunk(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata) +{ + if (!hb || !metadata) return 0; + + if (!HasScheme("XMP", metadata)) return 0; + + int olen=hb->GetSize(); + + static const char *xmp_hdr= + "" + "" + "" + "" + "" + "" + ""; + + WDL_FastString xmp(xmp_hdr); + + for (int pass=0; pass < 2; ++pass) // attributes, then elements + { + if (pass) xmp.Append(">"); + for (int i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (!key || !key[0] || !val || !val[0]) continue; + + if (!strncmp(key, "XMP:", 4) && key[4]) + { + key += 4; + const char *prefix = // xmp schema + !strncmp(key, "dc/", 3) ? "dc" : + !strncmp(key, "dm/", 3) ? "xmpDM" : NULL; + if (prefix && key[3]) + { + if (!strcmp(key, "dm/markers")) continue; + + if (!strcmp(key, "dc/description") || !strcmp(key, "dc/title")) + { + // elements + if (!pass) continue; + + key += 3; + const char *lang=metadata->Get("XMP:dc/language"); + if (!lang) lang="x-default"; + xmp.AppendFormatted(1024, "<%s:%s>", prefix, key); + xmp.AppendFormatted(1024, "", lang); + xmp.Append(val); + xmp.Append(""); + xmp.AppendFormatted(1024, "", prefix, key); + } + else if (!strcmp(key, "dm/relativeTimestamp")) + { + // element + if (!pass) continue; + + key += 3; + xmp.AppendFormatted(1024, "<%s:%s xmpDM:value=\"%s\" xmpDM:scale=\"1/1000\"/>", + prefix, key, val); + } + else + { + // attributes + if (pass) continue; + + key += 3; + xmp.AppendFormatted(1024, " %s:%s=\"%s\"", prefix, key, val); + } + } + } + } + } + + static const char *track_hdr1= + "" + "" + "" + "Cue" + "f"; + static const char *track_hdr2= + "" + "" + ""; + static const char *track_ftr= + "" + "" + "" + "" + ""; + + char buf[128]; + int cnt=0; + for (int i=0; i < 1000; ++i) + { + snprintf(buf, sizeof(buf), "XMP:MARK%03d", i); + const char *val=metadata->Get(buf); + if (!val || !val[0]) break; + + const char *sep1=strchr(val, ':'); + if (WDL_NOT_NORMALLY(!sep1)) break; + double st=atof(val); + double et=atof(sep1+1); + if (WDL_NOT_NORMALLY(st < 0.0 || et < st)) break; + const char *sep2=strchr(sep1+1, ':'); + const char *name = sep2 ? sep2+1 : ""; // might need to make this xml compliant? + + if (!cnt++) xmp.AppendFormatted(1024, "%s%d%s", track_hdr1, XMP_MARKER_RESOLUTION, track_hdr2); + + xmp.Append(""); + xmp.AppendFormatted(1024, "%.0f", st); + if (et > st) xmp.AppendFormatted(1024, "%.0f", et-st); + if (name[0]) xmp.AppendFormatted(1024, "%s", name); + xmp.Append(""); + } + if (cnt) xmp.Append(track_ftr); + + xmp.Append(xmp_ftr); + + int xmplen=xmp.GetLength(); + int len=xmplen+1; + if (len&1) ++len; + unsigned char *p=(unsigned char*)hb->ResizeOK(olen+len); + if (WDL_NORMALLY(p != NULL)) + { + memcpy(p+olen, xmp.Get(), xmplen); + memset(p+olen+xmplen, 0, len-xmplen); + } + + return hb->GetSize()-olen; +} + +int PackVorbisFrame(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata, bool for_vorbis) +{ + if (!hb || !metadata) return 0; + + // for vorbis, we need an empty frame even if there's no metadata + if (!for_vorbis && !HasScheme("VORBIS", metadata)) return 0; + + int olen=hb->GetSize(); + + const char *vendor="REAPER"; + const int vendorlen=strlen(vendor); + int framelen=4+vendorlen+4+for_vorbis; + + int i, tagcnt=0; + for (i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (!key || !key[0] || !val || !val[0]) continue; + if (!strncmp(key, "VORBIS:", 7) && key[7]) + { + key += 7; + const char *k=key, *v=val; + int klen=strlen(k), vlen=strlen(v); + if (!strncmp(key, "USER", 4)) + { + ParseUserDefMetadata(key, val, &k, &v, &klen, &vlen); + } + + int taglen=4+klen+1+vlen; + if (framelen+taglen >= 0xFFFFFF) break; + framelen += taglen; + ++tagcnt; + } + } + + unsigned char *buf=(unsigned char*)hb->ResizeOK(olen+framelen); + if (WDL_NORMALLY(buf != NULL)) + { + buf += olen; + unsigned char *p=buf; + memcpy(p, &vendorlen, 4); + p += 4; + memcpy(p, vendor, vendorlen); + p += vendorlen; + memcpy(p, &tagcnt, 4); + p += 4; + + for (i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (!key || !key[0] || !val || !val[0]) continue; + if (!strncmp(key, "VORBIS:", 7) && key[7]) + { + key += 7; + const char *k=key, *v=val; + int klen=strlen(k), vlen=strlen(v); + if (!strncmp(key, "USER", 4)) + { + ParseUserDefMetadata(key, val, &k, &v, &klen, &vlen); + } + + int taglen=klen+1+vlen; + memcpy(p, &taglen, 4); + p += 4; + while (*k) + { + *p++ = (*k >= ' ' && *k <= '}' && *k != '=') ? *k : ' '; + k++; + } + *p++='='; + memcpy(p, v, vlen); + p += vlen; + + if (!--tagcnt) break; + } + } + + if (for_vorbis) *p++=1; // framing bit + + if (WDL_NOT_NORMALLY(p-buf != framelen) || framelen > 0xFFFFFF) + { + hb->Resize(olen); + } + } + + return hb->GetSize()-olen; +} + +bool UnpackVorbisFrame(unsigned char *frame, int framelen, + WDL_StringKeyedArray *metadata) +{ + if (!frame || !framelen || !metadata) return 0; + + char *p=(char*)frame; + + int vendor_len=*(int*)p; + if (4+vendor_len+4 > framelen) return false; + + p += 4+vendor_len; + int tagcnt=*(int*)p; + p += 4; + + WDL_String str; + int pos=4+vendor_len+4; + while (pos < framelen && tagcnt--) + { + int taglen=*(int*)p; + p += 4; + if (pos+taglen > framelen) return false; + str.Set("VORBIS:"); + str.Append(p, taglen); + p += taglen; + const char *sep=strchr(str.Get(), '='); + if (!sep) return false; + *(char*)sep=0; + metadata->Insert(str.Get(), strdup(sep+1)); + } + + return (pos == framelen && tagcnt == 0); +} + + +#define _AddInt32LE(i) \ + *p++=((i)&0xFF); \ + *p++=(((i)>>8)&0xFF); \ + *p++=(((i)>>16)&0xFF); \ + *p++=(((i)>>24)&0xFF); + +#define _GetInt32LE(p) \ + (((p)[0])|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24)) + + +int PackApeChunk(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata) +{ + if (!hb || !metadata) return false; + + if (!HasScheme("APE", metadata)) return false; + + int olen=hb->GetSize(); + + int i, apelen=0, cnt=0; + for (i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (strlen(key) < 5 || strncmp(key, "APE:", 4) || !val || !val[0]) continue; + key += 4; + if (!apelen) apelen=64; // includes header and footer + if (!strncmp(key, "User Defined", 12)) + { + const char *k, *v; + int klen, vlen; + ParseUserDefMetadata(key, val, &k, &v, &klen, &vlen); + apelen += 8+klen+1+vlen; + } + else + { + apelen += 8+strlen(key)+1+strlen(val); + } + ++cnt; + } + if (!apelen) return false; + + unsigned char *buf=(unsigned char*)hb->ResizeOK(olen+apelen); + if (WDL_NORMALLY(buf != NULL)) + { + buf += olen; + unsigned char *p=buf; + memcpy(p, "APETAGEX", 8); + p += 8; + _AddInt32LE(2000); // version + _AddInt32LE(apelen-32); // includes footer but not header + _AddInt32LE(cnt); + _AddInt32LE((1<<31)|(1<<30)|(1<<29)); // tag contains header and footer, this is the header + _AddInt32LE(0); + _AddInt32LE(0); + + for (i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (strlen(key) < 5 || strncmp(key, "APE:", 4) || !val || !val[0]) continue; + key += 4; + const char *k=key, *v=val; + int klen, vlen; + if (!strncmp(key, "User Defined", 12)) + { + ParseUserDefMetadata(key, val, &k, &v, &klen, &vlen); + } + else + { + klen=strlen(k); + vlen=strlen(v); + } + _AddInt32LE(vlen); + _AddInt32LE(0); + while (klen--) + { + *p = (*k >= 0x20 && *k <= 0x7E ? *k : ' '); + ++p; + ++k; + } + *p++=0; + memcpy(p, v, vlen); + p += vlen; + } + + memcpy(p, "APETAGEX", 8); + p += 8; + _AddInt32LE(2000); // version + _AddInt32LE(apelen-32); // includes footer but not header + _AddInt32LE(cnt); + _AddInt32LE((1<<31)|(1<<30)|(1<<28)); // tag contains header and footer, this is the footer + _AddInt32LE(0); + _AddInt32LE(0); + + if (WDL_NOT_NORMALLY(p-buf != apelen)) hb->Resize(olen); + } + + return hb->GetSize()-olen; +} + + +const char *EnumMetadataSchemeFromFileType(const char *filetype, int idx) +{ + if (!filetype || !filetype[0]) return NULL; + + if (filetype[0] == '.') ++filetype; + if (!stricmp(filetype, "bwf")) filetype="wav"; + else if (!stricmp(filetype, "opus")) filetype="ogg"; + else if (!stricmp(filetype, "aiff")) filetype="aif"; + else if (!stricmp(filetype, "caff")) filetype="caf"; + + static const char *WAV_SCHEMES[]= + { + "BWF", "INFO", "IXML", "ASWG", "XMP", "AXML", "CART", "ID3", + }; + static const char *MP3_SCHEMES[]= + { + "ID3", "APE", "IXML", "ASWG", "XMP", + }; + static const char *FLAC_SCHEMES[]= + { + "VORBIS", "BWF", "IXML", "ASWG", "XMP", + }; + static const char *OGG_SCHEMES[]= + { + "VORBIS", + }; + static const char *WV_SCHEMES[]= + { + "BWF", "APE", + }; + static const char *AIF_SCHEMES[]= + { + "IFF", "XMP", "ID3" + }; + static const char *CAF_SCHEMES[]= + { + "CAFINFO", + }; + static const char *RX2_SCHEMES[]= + { + "REX", + }; + +#define DO_SCHEME_MAP(X) if (!stricmp(filetype, #X)) \ + return idx < sizeof(X##_SCHEMES)/sizeof(X##_SCHEMES[0]) ? X##_SCHEMES[idx] : NULL; + + DO_SCHEME_MAP(WAV); + DO_SCHEME_MAP(MP3); + DO_SCHEME_MAP(FLAC); + DO_SCHEME_MAP(OGG); + DO_SCHEME_MAP(WV); + DO_SCHEME_MAP(AIF); + DO_SCHEME_MAP(CAF); + DO_SCHEME_MAP(RX2); + +#undef DO_SCHEME_MAP + + return NULL; +} + + +bool EnumMetadataKeyFromMexKey(const char *mexkey, int idx, char *key, int keylen) +{ + if (!mexkey || !mexkey[0] || idx < 0 || !key || !keylen) return false; + + // TO_DO_IF_METADATA_UPDATE + // "TITLE", "ARTIST", "ALBUM", "YEAR", "GENRE", "COMMENT", "DESC", "BPM", "KEY", "DB_CUSTOM", "TRACKNUMBER" + + if (!strcmp(mexkey, "DATE")) mexkey="YEAR"; + // callers handle PREFPOS + + // general priority order here: + // BWF + // INFO + // ID3 + // APE + // VORBIS + // CART + // IXML + // ASWG + // XMP + // CAFINFO + // IFF + // REX + + static const char *TITLE_KEYS[]= + { + "INFO:INAM", + "ID3:TIT2", + "APE:Title", + "VORBIS:TITLE", + "CART:Title", + "IXML:PROJECT", + "ASWG:project", + "XMP:dc/title", + "CAFINFO:title", + "IFF:NAME", + "REX:Name", + }; + static const char *ARTIST_KEYS[]= + { + "INFO:IART", + "ID3:TPE1", + "APE:Artist", + "VORBIS:ARTIST", + "CART:Artist", + "XMP:dm/artist", + "CAFINFO:artist", + "IFF:AUTH", + }; + static const char *ALBUM_KEYS[]= + { + "INFO:IALB", + "INFO:IPRD", + "ID3:TALB", + "APE:Album", + "VORBIS:ALBUM", + "XMP:dm/album", + "CAFINFO:album", + }; + static const char *YEAR_KEYS[]= // really DATE + { + "BWF:OriginationDate", + "INFO:ICRD", + "ID3:TYER", + "ID3:TDRC", + "APE:Year", + "APE:Record Date", + "VORBIS:DATE", + "CART:StartDate", + "XMP:dc/date", + "CAFINFO:year", + }; + static const char *GENRE_KEYS[]= + { + "INFO:IGNR", + "ID3:TCON", + "APE:Genre", + "VORBIS:GENRE", + "CART:Category", + "XMP:dm/genre", + "CAFINFO:genre", + }; + static const char *COMMENT_KEYS[]= + { + "INFO:ICMT", + "ID3:COMM", + "APE:Comment", + "VORBIS:COMMENT", + "CART:TagText", + "IXML:NOTE", + "ASWG:notes", + "XMP:dm/logComment", + "CAFINFO:comments", + "CAFINFO:comment", // spec is "comments" but ffmpeg may write "comment" + "REX:FreeText", + }; + static const char *DESC_KEYS[]= + { + "BWF:Description", + "INFO:ISBJ", + "INFO:IKEY", + "ID3:TIT3", + "APE:Subtitle", + "VORBIS:DESCRIPTION", + "XMP:dc/description", + "IFF:ANNO", + }; + static const char *BPM_KEYS[]= + { + "ACID:BPM", + "ID3:TBPM", + "APE:BPM", + "VORBIS:BPM", + "XMP:dm/tempo", + "CAFINFO:tempo", + }; + static const char *KEY_KEYS[]= + { + "ACID:KEY", + "ID3:TKEY", + "APE:Key", + "VORBIS:KEY", + "XMP:dm/key", + "CAFINFO:key signature", + }; + static const char *TRACKNUMBER_KEYS[]= + { + "INFO:TRCK", + "ID3:TRCK", + "APE:Track", + "VORBIS:TRACKNUMBER", + "CART:CutID", + // "IXML:TRACK", + "XMP:dm/trackNumber", + "CAFINFO:track number", + }; + +#define DO_MEXKEY_MAP(K) \ +if (!strcmp(mexkey, #K)) \ +{ \ + if (idx >= sizeof(K##_KEYS)/sizeof(K##_KEYS[0])) return false; \ + lstrcpyn(key, K##_KEYS[idx], keylen); return true; \ +} + + key[0]=0; + DO_MEXKEY_MAP(TITLE); + DO_MEXKEY_MAP(ARTIST); + DO_MEXKEY_MAP(ALBUM); + DO_MEXKEY_MAP(TRACKNUMBER); + DO_MEXKEY_MAP(YEAR); + DO_MEXKEY_MAP(GENRE); + DO_MEXKEY_MAP(COMMENT); + DO_MEXKEY_MAP(DESC); + DO_MEXKEY_MAP(BPM); + DO_MEXKEY_MAP(KEY); + +#undef DO_MEXKEY_MAP + + static const char *DB_CUSTOM_KEYS[]= + { + "ID3:TXXX", + "APE", + "VORBIS", + "IXML:USER", + }; + if (idx >= sizeof(DB_CUSTOM_KEYS)/sizeof(DB_CUSTOM_KEYS[0])) return false; + if (!strcmp(mexkey, "DB_CUSTOM")) mexkey="REAPER"; + snprintf(key, keylen, "%s:%s", DB_CUSTOM_KEYS[idx], mexkey); + return true; +} + +const char *GetMexKeyFromMetadataKey(const char *key) +{ + int i=0; + const char *mexkey; + while ((mexkey=EnumMexKeys(i++))) + { + int j=0; + char tkey[256]; + while (EnumMetadataKeyFromMexKey(mexkey, j++, tkey, sizeof(tkey)) && tkey[0]) + { + if (!strcmp(key, tkey)) return mexkey; + } + } + return NULL; +} + +bool HandleMexMetadataRequest(const char *mexkey, char *buf, int buflen, + WDL_StringKeyedArray *metadata) +{ + if (!mexkey || !mexkey[0] || !buf || !buflen || !metadata) return false; + buf[0]=0; + + buf[0]=0; + int i=0; + char key[256]; + while (EnumMetadataKeyFromMexKey(mexkey, i++, key, sizeof(key)) && key[0]) + { + const char *val=metadata->Get(key); + if (val && val[0]) + { + lstrcpyn(buf, val, buflen); + return true; + } + } + + if (strchr(mexkey, ':')) + { + const char *val=metadata->Get(mexkey); + if (val && val[0]) + { + lstrcpyn(buf, val, buflen); + return true; + } + } + + return false; +} + + +void WriteMetadataPrefPos(double prefpos, int srate, // prefpos <= 0.0 to clear + WDL_StringKeyedArray *metadata) +{ + if (!metadata) return; + + metadata->Delete("BWF:TimeReference"); + metadata->Delete("ID3:TXXX:TIME_REFERENCE"); + metadata->Delete("IXML:BEXT:BWF_TIME_REFERENCE_HIGH"); + metadata->Delete("IXML:BEXT:BWF_TIME_REFERENCE_LOW"); + metadata->Delete("XMP:dm/relativeTimestamp"); + metadata->Delete("VORBIS:TIME_REFERENCE"); + + if (prefpos > 0.0 && srate > 1) + { + char buf[128]; + if (srate > 0.0) + { + snprintf(buf, sizeof(buf), "%.0f", floor(prefpos*(double)srate)); + metadata->Insert("BWF:TimeReference", strdup(buf)); + // BWF:TimeReference causes IXML:BEXT element to be written as well + metadata->Insert("ID3:TXXX:TIME_REFERENCE", strdup(buf)); + metadata->Insert("VORBIS:TIME_REFERENCE", strdup(buf)); + } + snprintf(buf, sizeof(buf), "%.0f", floor(prefpos*1000.0)); + metadata->Insert("XMP:dm/relativeTimestamp", strdup(buf)); + } +} + +bool IsImageMetadata(const char *key) +{ + return !strncmp(key, "ID3:APIC", 8) || !strncmp(key, "FLACPIC:APIC", 12); +} + +void DeleteAllImageMetadata(WDL_StringKeyedArray *metadata) +{ + for (int i=0; i < metadata->GetSize(); ++i) + { + const char *key; + metadata->Enumerate(i, &key); + if (IsImageMetadata(key)) metadata->DeleteByIndex(i--); + } +} + +void AddMexMetadata(WDL_StringKeyedArray *mex_metadata, + WDL_StringKeyedArray *metadata, int srate) +{ + if (!mex_metadata || !metadata) return; + + bool added_img_metadata=false; + + for (int idx=0; idx < mex_metadata->GetSize(); ++idx) + { + const char *mexkey; + const char *val=mex_metadata->Enumerate(idx, &mexkey); + + if (!strcmp(mexkey, "PREFPOS")) + { + WDL_UINT64 ms = val && val[0] ? ParseUInt64(val) : 0; + WriteMetadataPrefPos((double)ms/1000.0, srate, metadata); + // caller may still have to do stuff if prefpos is represented + // in some other way outside the metadata we handle, like wavpack + continue; + } + + if (IsImageMetadata(mexkey)) + { + if (!added_img_metadata) + { + added_img_metadata=true; + DeleteAllImageMetadata(metadata); + } + metadata->Insert(mexkey, strdup(val)); + continue; + } + + int i=0; + char key[256]; + while (EnumMetadataKeyFromMexKey(mexkey, i++, key, sizeof(key)) && key[0]) + { + if (val && val[0]) metadata->Insert(key, strdup(val)); + else metadata->Delete(key); + } + } +} + + +void DumpMetadata(WDL_FastString *str, WDL_StringKeyedArray *metadata) +{ + if (!str || !metadata || !metadata->GetSize()) return; + + char scheme[256]; + scheme[0]=0; + + char buf[2048]; + int j=0; + const char *mexkey, *mexdesc; + while ((mexkey=EnumMexKeys(j++, &mexdesc))) + { + if (HandleMexMetadataRequest(mexkey, buf, sizeof(buf), metadata)) + { + if (!scheme[0]) + { + lstrcpyn(scheme, "mex", sizeof(scheme)); + str->Append("Metadata:\r\n"); + } + str->AppendFormatted(4096, " %s:%s%s\r\n", + mexdesc, strchr(buf, '\n') ? "\r\n" : " ", buf); + } + } + + for (int i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (!key || !key[0] || !val || !val[0] || !strncmp(val, "[Binary data]", 13)) continue; + + const char *sep=strchr(key, ':'); + if (sep) + { + int slen=wdl_min(sep-key, sizeof(scheme)-1); + if (strncmp(scheme, key, slen)) + { + lstrcpyn(scheme, key, slen+1); + str->AppendFormatted(256, "%s tags:\r\n", scheme); + } + key += slen+1; + } + str->AppendFormatted(4096, " %s:%s%s\r\n", + key, strchr(val, '\n') ? "\r\n" : " ", val); + } + + int unk_cnt=0; + for (int i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (key && key[0] && val && (!val[0] || !strncmp(val, "[Binary data]", 13))) + { + if (!unk_cnt++) str->Append("Other file sections:\r\n"); + str->AppendFormatted(4096, " %s%s\r\n", key, + !strnicmp(key, "smed", 4) ? + " (proprietary Soundminer metadata)" : ""); + } + } +} + +void CopyMetadata(WDL_StringKeyedArray *src, WDL_StringKeyedArray *dest) +{ + if (!dest || !src) return; + + dest->DeleteAll(false); + for (int i=0; i < src->GetSize(); ++i) + { + const char *key; + const char *val=src->Enumerate(i, &key); + dest->AddUnsorted(key, strdup(val)); + } + dest->Resort(); // safe in case src/dest have diff sort attributes +} + + +bool CopyFileData(WDL_FileRead *fr, WDL_FileWrite *fw, WDL_INT64 len) +{ + while (len) + { + char tmp[32768]; + const int amt = (int) (wdl_min(len,sizeof(tmp))); + const int rd = fr->Read(tmp, amt); + if (rd != amt) return false; + if (fw->Write(tmp, rd) != rd) return false; + len -= rd; + } + return true; +} + + +bool EnumVorbisChapters(WDL_StringKeyedArray *metadata, int idx, + double *pos, const char **name) +{ + if (!metadata) return false; + + int cnt=0; + bool ismatch=false; + const char *prev=NULL; + int i=metadata->LowerBound("VORBIS:CHAPTER", &ismatch); + for (; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (strncmp(key, "VORBIS:CHAPTER", 14)) return false; + if (!prev || strncmp(key, prev, 17)) + { + prev=key; + if (idx == cnt) + { + if (!key[17] && val && val[0]) + { + // VORBIS:CHAPTER001 => 00:00:00.000 + if (pos) + { + int hh=0, mm=0, ss=0, ms=0; + if (sscanf(val, "%d:%d:%d.%d", &hh, &mm, &ss, &ms) == 4) + { + *pos=(double)hh*3600.0+(double)mm*60.0+(double)ss+(double)ms*0.001; + } + } + if (name) + { + val=metadata->Enumerate(i+1, &key); + if (!strncmp(key, prev, 17) && !strcmp(key+17, "NAME")) + { + // VORBIS:CHAPTER001NAME => chapter name + *name=val; + } + } + return true; + } + return false; + } + ++cnt; + } + } + return false; +} + + +#define _AddSyncSafeInt32(i) \ +*p++=(((i)>>21)&0x7F); \ +*p++=(((i)>>14)&0x7F); \ +*p++=(((i)>>7)&0x7F); \ +*p++=((i)&0x7F); + +#define _AddInt32(i) \ +*p++=(((i)>>24)&0xFF); \ +*p++=(((i)>>16)&0xFF); \ +*p++=(((i)>>8)&0xFF); \ +*p++=((i)&0xFF); + +#define _GetSyncSafeInt32(p) \ +(((p)[0]<<21)|((p)[1]<<14)|((p)[2]<<7)|((p)[3])) + +void WriteSyncSafeInt32(WDL_FileWrite *fw, int i) +{ + unsigned char buf[4]; + unsigned char *p=buf; + _AddSyncSafeInt32(i); + fw->Write(buf, 4); +} + + +#define CTOC_NAME "TOC" // arbitrary name of table of contents element + + +static bool _isnum(const char *v, int pos, int len) +{ + for (int i=pos; i < pos+len; ++i) + { + if (v[i] < '0' || v[i] > '9') return false; + } + return true; +} + +int IsID3TimeVal(const char *v) +{ + if (strlen(v) == 4 && _isnum(v, 0, 4)) return 1; + if (strlen(v) == 5 && _isnum(v, 0, 2) && _isnum(v, 3, 2)) return 2; + return 0; +} + +struct ID3RawTag +{ + char key[8]; // we only use 4 chars + nul + WDL_HeapBuf val; // includes everything after taglen (flags, etc) +}; + +int ReadID3Raw(WDL_FileRead *fr, WDL_PtrList *rawtags) +{ + if (!fr || !fr->IsOpen() || !rawtags) return 0; + + unsigned char buf[16]; + if (fr->Read(buf, 10) != 10) return 0; + if (memcmp(buf, "ID3\x04", 4) && memcmp(buf, "ID3\x03", 4)) return 0; + int id3len=_GetSyncSafeInt32(buf+6); + if (!id3len) return 0; + + int rdlen=0; + WDL_HeapBuf hb; + while (rdlen < id3len) + { + if (fr->Read(buf, 8) != 8) return 0; + if (!buf[0]) return 10+id3len; // padding + if ((buf[0] < 'A' || buf[0] > 'Z') && (buf[0] < '0' || buf[0] > '9')) return 0; // unexpected + + int taglen=_GetSyncSafeInt32(buf+4)+2; // include flags in taglen + + unsigned char *p=(unsigned char*)hb.ResizeOK(taglen); + if (!p || fr->Read(p, taglen) != taglen) return 0; + + ID3RawTag *rawtag=rawtags->Add(new ID3RawTag); + memcpy(rawtag->key, buf, 4); + rawtag->key[4]=0; + rawtag->val=hb; + + rdlen += 8+taglen; + if (rdlen == id3len) return 10+id3len; + } + return 0; +} + +void DeleteID3Raw(WDL_PtrList *rawtags, const char *key) +{ + if (!rawtags || !rawtags->GetSize()) return; + if (strncmp(key, "ID3:", 4)) return; + if (WDL_NOT_NORMALLY(strlen(key) < 8)) return; + + key += 4; + const char *subkey=NULL; + int suboffs=0, sublen=0; + if (key[4] && strncmp(key, "APIC", 4)) + { + if (!strncmp(key, "TXXX:", 5)) suboffs=3; + else if (!strncmp(key, "PRIV:", 5)) suboffs=2; + if (!suboffs || !key[5]) return; + subkey=key+5; + sublen=strlen(subkey); + } + + for (int i=0; i < rawtags->GetSize(); ++i) + { + ID3RawTag *rawtag=rawtags->Get(i); + if (!strncmp(key, rawtag->key, 4)) + { + if (subkey && + (rawtag->val.GetSize() < sublen+suboffs || + memcmp((unsigned char*)rawtag->val.Get()+suboffs, subkey, sublen))) + { + continue; // key is like ID3:AAAA:BBBB but rawtag->val does not match *BBBB + } + rawtags->Delete(i--, true); + } + } +} + +int PackID3Chunk(WDL_HeapBuf *hb, WDL_StringKeyedArray *metadata, + bool want_embed_otherschemes, int *ixml_lenwritten, int ixml_padtolen, + WDL_PtrList *rawtags) +{ + if (!hb || !metadata) return false; + + bool want_ixml = want_embed_otherschemes && + (HasScheme("IXML", metadata) || HasScheme("ASWG", metadata) || HasScheme("BWF", metadata)); + bool want_xmp = want_embed_otherschemes && HasScheme("XMP", metadata); + if (!HasScheme("ID3", metadata) && !want_ixml && !want_xmp) return false; + + int olen=hb->GetSize(); + + int id3len=0, chapcnt=0; + WDL_TypedQueue toc; + int i; + for (i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (strlen(key) < 8 || strncmp(key, "ID3:", 4) || !val) continue; + key += 4; + if (!strncmp(key, "TXXX", 4)) + { + const char *k, *v; + int klen, vlen; + ParseUserDefMetadata(key, val, &k, &v, &klen, &vlen); + id3len += 10+1+klen+1+vlen; + } + else if (!strncmp(key, "TIME", 4)) + { + if (IsID3TimeVal(val)) id3len += 10+1+4; + } + else if (key[0] == 'T' && strlen(key) == 4) + { + id3len += 10+1+strlen(val); + } + else if (!strcmp(key, "COMM") || !strcmp(key, "USLT")) + { + id3len += 10+5+strlen(val); + } + else if (!strncmp(key, "CHAP", 4) && chapcnt < 255) + { + const char *c1=strchr(val, ':'); + const char *c2 = c1 ? strchr(c1+1, ':') : NULL; + if (c1) + { + ++chapcnt; + const char *toc_entry=key; // use "CHAP001", etc as the internal toc entry + const char *chap_name = c2 ? c2+1 : NULL; + toc.Add(toc_entry, strlen(toc_entry)+1); + id3len += 10+strlen(toc_entry)+1+16; + if (chap_name) id3len += 10+1+strlen(chap_name)+1; + } + } + } + if (chapcnt) + { + id3len += 10+strlen(CTOC_NAME)+1+2+toc.GetSize(); + } + + WDL_HeapBuf apic_hdr; + int apic_datalen=0; + const char *apic_fn=metadata->Get("ID3:APIC_FILE"); + if (apic_fn && apic_fn[0]) + { + const char *mime=NULL; + const char *ext=WDL_get_fileext(apic_fn); + if (ext && (!stricmp(ext, ".jpg") || !stricmp(ext, ".jpeg"))) mime="image/jpeg"; + else if (ext && !stricmp(ext, ".png")) mime="image/png"; + if (mime) + { + FILE *fp=fopenUTF8(apic_fn, "rb"); // could stat but let's make sure we can open the file + if (fp) + { + fseek(fp, 0, SEEK_END); + apic_datalen=ftell(fp); + fclose(fp); + } + } + if (apic_datalen) + { + const char *t=metadata->Get("ID3:APIC_TYPE"); + int type=-1; + if (t && t[0] >= '0' && t[0] <= '9') type=atoi(t); + if (type < 0 || type >= 16) type=3; // default "Cover (front)" + + const char *desc=metadata->Get("ID3:APIC_DESC"); + if (!desc) desc=""; + int desclen=wdl_min(strlen(desc), 63); + + int apic_hdrlen=1+strlen(mime)+1+1+desclen+1; + char *p=(char*)apic_hdr.ResizeOK(apic_hdrlen); + if (p) + { + *p++=3; // UTF-8 + memcpy(p, mime, strlen(mime)+1); + p += strlen(mime)+1; + *p++=type; + memcpy(p, desc, desclen); + p += desclen; + *p++=0; + id3len += 10+apic_hdrlen+apic_datalen; + } + } + } + + WDL_HeapBuf ixml; + if (want_ixml) + { + PackIXMLChunk(&ixml, metadata, ixml_padtolen); + if (ixml.GetSize()) + { + if (ixml_lenwritten) *ixml_lenwritten=ixml.GetSize(); + id3len += 10+5+ixml.GetSize(); + } + } + + WDL_HeapBuf xmp; + if (want_xmp) + { + PackXMPChunk(&xmp, metadata); + if (xmp.GetSize()) id3len += 10+4+xmp.GetSize(); + } + + if (rawtags) + { + for (int i=0; i < rawtags->GetSize(); ++i) + { + ID3RawTag *rawtag=rawtags->Get(i); + if (WDL_NORMALLY(rawtag && rawtag->key[0] && rawtag->val.GetSize())) + { + id3len += 8+rawtag->val.GetSize(); + } + } + } + + if (id3len) + { + id3len += 10; + unsigned char *buf=(unsigned char*)hb->ResizeOK(olen+id3len); + if (WDL_NORMALLY(buf != NULL)) + { + buf += olen; + chapcnt=0; + unsigned char *p=buf; + memcpy(p,"ID3\x04\x00\x00", 6); + p += 6; + _AddSyncSafeInt32(id3len-10); + for (i=0; i < metadata->GetSize(); ++i) + { + const char *key; + const char *val=metadata->Enumerate(i, &key); + if (strlen(key) < 8 || strncmp(key, "ID3:", 4) || !val) continue; + key += 4; + if (!strncmp(key, "TXXX", 4)) + { + memcpy(p, key, 4); + p += 4; + const char *k, *v; + int klen, vlen; + ParseUserDefMetadata(key, val, &k, &v, &klen, &vlen); + _AddSyncSafeInt32(1+klen+1+vlen); + memcpy(p, "\x00\x00\x03", 3); // UTF-8 + p += 3; + memcpy(p, k, klen); + p += klen; + *p++=0; + memcpy(p, v, vlen); + p += vlen; + } + else if (!strncmp(key, "TIME", 4)) + { + int tv=IsID3TimeVal(val); + if (tv) + { + memcpy(p, key, 4); + p += 4; + _AddSyncSafeInt32(1+4); + memcpy(p, "\x00\x00\x03", 3); // UTF-8 + p += 3; + memcpy(p, val, 2); + if (tv == 1) memcpy(p+2, val+2, 2); + else memcpy(p+2, val+3, 2); + p += 4; + } + } + else if (key[0] == 'T' && strlen(key) == 4) + { + memcpy(p, key, 4); + p += 4; + int len=strlen(val); + _AddSyncSafeInt32(1+len); + memcpy(p, "\x00\x00\x03", 3); // UTF-8 + p += 3; + memcpy(p, val, len); + p += len; + } + else if (!strcmp(key, "COMM") || !strcmp(key, "USLT")) + { + // http://www.loc.gov/standards/iso639-2/php/code_list.php + // most apps ignore this, itunes wants "eng" or something locale-specific + const char *lang=NULL; + if (!strcmp(key, "USLT")) lang=metadata->Get("ID3:LYRIC_LANG"); + if (!lang) lang=metadata->Get("ID3:COMM_LANG"); + if (!lang) lang=metadata->Get("ID3:COMMENT_LANG"); + + memcpy(p, key, 4); + p += 4; + int len=strlen(val); + _AddSyncSafeInt32(5+len); + memcpy(p, "\x00\x00\x03", 3); // UTF-8 + p += 3; + if (lang && strlen(lang) >= 3 && + tolower(*lang) >= 'a' && tolower(*lang) <= 'z') + { + *p++=tolower(*lang++); + *p++=tolower(*lang++); + *p++=tolower(*lang++); + *p++=0; + } + else + { + // some apps write "XXX" for "no particular language" + memcpy(p, "XXX\x00", 4); + p += 4; + } + memcpy(p, val, len); + p += len; + } + else if (!strncmp(key, "CHAP", 4) && chapcnt < 255) + { + const char *c1=strchr(val, ':'); + const char *c2 = c1 ? strchr(c1+1, ':') : NULL; + if (c1) + { + // note, the encoding ignores the chapter number (CHAP001, etc) + + ++chapcnt; + const char *toc_entry=key; // use "CHAP001", etc as the internal toc entry + const char *chap_name = c2 ? c2+1 : NULL; + int st=atoi(val); + int et=atoi(c1+1); + + int framelen=strlen(toc_entry)+1+16; + if (chap_name) framelen += 10+1+strlen(chap_name)+1; + + memcpy(p, "CHAP", 4); + p += 4; + _AddSyncSafeInt32(framelen); + memset(p, 0, 2); + p += 2; + memcpy(p, toc_entry, strlen(toc_entry)+1); + p += strlen(toc_entry)+1; + _AddInt32(st); + _AddInt32(et); + memset(p, 0, 8); + p += 8; + + if (chap_name) + { + int name_framelen=1+strlen(chap_name)+1; + memcpy(p, "TIT2", 4); + p += 4; + _AddSyncSafeInt32(name_framelen); + memcpy(p, "\x00\x00\x03", 3); // UTF-8 + p += 3; + memcpy(p, chap_name, strlen(chap_name)+1); + p += strlen(chap_name)+1; + } + } + } + } + + if (chapcnt) + { + int toc_framelen=strlen(CTOC_NAME)+1+2+toc.GetSize(); + memcpy(p, "CTOC", 4); + p += 4; + _AddSyncSafeInt32(toc_framelen); + memset(p, 0, 2); + p += 2; + memcpy(p, CTOC_NAME, strlen(CTOC_NAME)+1); + p += strlen(CTOC_NAME)+1; + *p++=3; // CTOC flags: &1=top level, &2=ordered + *p++=(chapcnt&0xFF); + memcpy(p, toc.Get(), toc.GetSize()); + p += toc.GetSize(); + } + + if (apic_hdr.GetSize() && apic_datalen) + { + memcpy(p, "APIC", 4); + p += 4; + int len=apic_hdr.GetSize()+apic_datalen; + _AddSyncSafeInt32(len); + memcpy(p, "\x00\x00", 2); + p += 2; + memcpy(p, apic_hdr.Get(), apic_hdr.GetSize()); + p += apic_hdr.GetSize(); + FILE *fp=fopenUTF8(apic_fn, "rb"); + if (WDL_NORMALLY(fp)) + { + fread(p, 1, apic_datalen, fp); + fclose(fp); + } + else // uh oh + { + memset(p, 0, apic_datalen); + } + p += apic_datalen; + } + + if (ixml.GetSize()) + { + memcpy(p, "PRIV", 4); + p += 4; + int len=ixml.GetSize()+5; + _AddSyncSafeInt32(len); + memcpy(p, "\x00\x00", 2); + p += 2; + memcpy(p, "iXML\x00", 5); + p += 5; + memcpy(p, ixml.Get(), ixml.GetSize()); + p += ixml.GetSize(); + } + + if (xmp.GetSize()) + { + memcpy(p, "PRIV", 4); + p += 4; + int len=xmp.GetSize()+4; + _AddSyncSafeInt32(len); + memcpy(p, "\x00\x00", 2); + p += 2; + memcpy(p, "XMP\x00", 4); + p += 4; + memcpy(p, xmp.Get(), xmp.GetSize()); + p += xmp.GetSize(); + } + + if (rawtags) + { + for (int i=0; i < rawtags->GetSize(); ++i) + { + ID3RawTag *rawtag=rawtags->Get(i); + if (WDL_NORMALLY(rawtag && rawtag->key[0] && rawtag->val.GetSize())) + { + memcpy(p, rawtag->key, strlen(rawtag->key)); + p += strlen(rawtag->key); + int vallen=rawtag->val.GetSize(); // includes flags + _AddSyncSafeInt32(vallen-2); + memcpy(p, rawtag->val.Get(), vallen); + p += vallen; + } + } + } + + if (WDL_NOT_NORMALLY(p-buf != id3len)) hb->Resize(olen); + } + } + + return hb->GetSize()-olen; +} + +double ReadMetadataPrefPos(WDL_StringKeyedArray *metadata, double srate) +{ + if (!metadata) return -1.0; + + const char *v=metadata->Get("BWF:TimeReference"); + if (!v || !v[0]) v=metadata->Get("ID3:TXXX:TIME_REFERENCE"); + if (!v || !v[0]) v=metadata->Get("VORBIS:TIME_REFERENCE"); + if (v && v[0] && srate > 0.0) + { + WDL_UINT64 i=ParseUInt64(v); + return (double)i/srate; + } + + v=metadata->Get("IXML:BEXT:BWF_TIME_REFERENCE_LOW"); + if (v && v[0] && srate > 0.0) + { + WDL_UINT64 ipos=atoi(v); + v=metadata->Get("IXML:BEXT:BWF_TIME_REFERENCE_HIGH"); + if (v && v[0]) ipos |= ((WDL_UINT64)atoi(v))<<32; + return (double)ipos/srate; + } + + v=metadata->Get("XMP:dm/relativeTimestamp"); + if (v && v[0]) + { + WDL_UINT64 i=ParseUInt64(v); + return (double)i/1000.0; + } + + return -1.0; +} + + +// nch 0 means channel count agnostic +// nch -1 means high order ambisonic, nch must? be an integer squared, layout tag must be or'd with the number of channels +struct ChanLayout { const char *fmts; int nch; const char *desc; int chan_layout, chan_mask; }; +static const ChanLayout CHAN_LAYOUTS[]= +{ + // using Ls/Rs for left side/right side + // using Lb/Rb for left back/right back + + { "cw", 2, "L R", + kAudioChannelLayoutTag_UseChannelBitmap, + kAudioChannelBit_Left | kAudioChannelBit_Right }, + + { "cw", 3, "L R C", + kAudioChannelLayoutTag_UseChannelBitmap, + kAudioChannelBit_Left | kAudioChannelBit_Right | kAudioChannelBit_Center }, + + { "cw", 4, "L R Lb Rb", + kAudioChannelLayoutTag_UseChannelBitmap, + kAudioChannelBit_Left | kAudioChannelBit_Right | + kAudioChannelBit_LeftSurround | kAudioChannelBit_RightSurround }, + + { "cw", 5, "L R C Lb Rb", + kAudioChannelLayoutTag_UseChannelBitmap, + kAudioChannelBit_Left | kAudioChannelBit_Right | kAudioChannelBit_Center | + kAudioChannelBit_LeftSurround | kAudioChannelBit_RightSurround }, + + { "cw", 6, "L R C LFE Lb Rb", + kAudioChannelLayoutTag_UseChannelBitmap, + kAudioChannelBit_Left | kAudioChannelBit_Right | kAudioChannelBit_Center | + kAudioChannelBit_LFEScreen | + kAudioChannelBit_LeftSurround | kAudioChannelBit_RightSurround }, + + { "cw", 6, "L R Lb Rb Ls Rs", + kAudioChannelLayoutTag_UseChannelBitmap, + kAudioChannelBit_Left | kAudioChannelBit_Right | + kAudioChannelBit_LeftSurround | kAudioChannelBit_RightSurround | + kAudioChannelBit_LeftSurroundDirect | kAudioChannelBit_RightSurroundDirect }, + + { "cw", 8, "L R C LFE Lb Rb Ls Rs", + kAudioChannelLayoutTag_UseChannelBitmap, + kAudioChannelBit_Left | kAudioChannelBit_Right | kAudioChannelBit_Center | + kAudioChannelBit_LFEScreen | + kAudioChannelBit_LeftSurround | kAudioChannelBit_RightSurround | + kAudioChannelBit_LeftSurroundDirect | kAudioChannelBit_RightSurroundDirect }, + + { "c", 2, "Mid-Side", kAudioChannelLayoutTag_MidSide, 0 }, + { "c", 2, "Binaural", kAudioChannelLayoutTag_Binaural, 0 }, + { "c", 4, "Ambisonic B-Format - W X Y Z", kAudioChannelLayoutTag_Ambisonic_B_Format, 0 }, + + { "c", 6, "MPEG 5.1A - L R C LFE Lb Rb", kAudioChannelLayoutTag_MPEG_5_1_A, 0 }, + { "c", 6, "MPEG 5.1B - L R Lb Rb C LFE", kAudioChannelLayoutTag_MPEG_5_1_B, 0 }, + { "c", 6, "MPEG 5.1C - L C R Lb Rb LFE", kAudioChannelLayoutTag_MPEG_5_1_C, 0 }, + { "c", 6, "MPEG 5.1D - C L R Lb Rb LFE", kAudioChannelLayoutTag_MPEG_5_1_D, 0 }, + + { "c", 8, "MPEG 7.1A - L R C LFE Lb Rb Lc Rc", kAudioChannelLayoutTag_MPEG_7_1_A, 0 }, + { "c", 8, "MPEG 7.1B - C Lc Rc L R Lb Rb LFE", kAudioChannelLayoutTag_MPEG_7_1_B, 0 }, + { "c", 8, "MPEG 7.1C (SMPTE 7.1) - L R C LFE Ls Rs Lb Rb", kAudioChannelLayoutTag_MPEG_7_1_C, 0 }, + + { "c", 6, "ITU 3.2.1 - L R C LFE Lb Rb", kAudioChannelLayoutTag_ITU_3_2_1, 0 }, + { "c", 8, "ITU 3.4.1 - L R C LFE Lb Rb Rls Rrs", kAudioChannelLayoutTag_ITU_3_4_1, 0 }, + + { "c", -1, "HO Ambisonic SN3D", kAudioChannelLayoutTag_HOA_ACN_SN3D, 0 }, + { "c", -1, "HO Ambisonic N3D", kAudioChannelLayoutTag_HOA_ACN_N3D, 0 }, + + { "c", 8, "Atmos 5.1.2 - L R C LFE Lb Rb Ltm Rtm", kAudioChannelLayoutTag_Atmos_5_1_2, 0 }, + { "c", 12, "Atmos 7.1.4 - L R C LFE Lb Rb Rls Rrs Ltf Rtf Ltr Rtr", kAudioChannelLayoutTag_Atmos_7_1_4, 0 }, + { "c", 16, "Atmos 9.1.6 - L R C LFE Lb Rb Rls Rrs Lw Rw Ltf Rtf Ltm Rtm Ltr Rtr", kAudioChannelLayoutTag_Atmos_9_1_6, 0 }, +}; + +#define LAYOUT_MASK_VALID(layout, mask) \ + (((layout) == kAudioChannelLayoutTag_UseChannelBitmap) == ((mask) != 0)) + + +const char *EnumSupportedChannelLayouts(int idx, char fmt) +{ + int n=0; + for (int i=0; i < sizeof(CHAN_LAYOUTS)/sizeof(CHAN_LAYOUTS[0]); ++i) + { + WDL_ASSERT(LAYOUT_MASK_VALID(CHAN_LAYOUTS[i].chan_layout, CHAN_LAYOUTS[i].chan_mask)); + + if ((!fmt || strchr(CHAN_LAYOUTS[i].fmts, fmt)) && idx == n++) + { + return CHAN_LAYOUTS[i].desc; + } + } + return NULL; +} + + +const char *GetChannelLayoutDesc(int chan_layout, int chan_mask) +{ + if (!LAYOUT_MASK_VALID(chan_layout, chan_mask)) return NULL; + + bool is_match=false; + for (int i=0; i < sizeof(CHAN_LAYOUTS)/sizeof(CHAN_LAYOUTS[0]); ++i) + { + if (CHAN_LAYOUTS[i].chan_mask != 0) + { + if (CHAN_LAYOUTS[i].chan_mask == chan_mask) is_match=true; + } + else if (CHAN_LAYOUTS[i].nch == -1) + { + // high order ambisonic layout tags are OR'd with the actual number of channels + if ((CHAN_LAYOUTS[i].chan_layout&0xFFFF0000) == (chan_layout&0xFFFF0000)) is_match=true; + } + else + { + if (CHAN_LAYOUTS[i].chan_layout == chan_layout) is_match=true; + } + if (is_match) + { + return CHAN_LAYOUTS[i].desc; + } + } + return NULL; +} + +bool GetChannelLayoutFromDesc(const char *desc, int *chan_layout, int *chan_mask, int *nch) +{ + if (!desc || !desc[0]) return false; + for (int i=0; i < sizeof(CHAN_LAYOUTS)/sizeof(CHAN_LAYOUTS[0]); ++i) + { + if (!strcmp(desc, CHAN_LAYOUTS[i].desc)) + { + if (chan_layout) *chan_layout=CHAN_LAYOUTS[i].chan_layout; + if (chan_mask) *chan_mask=CHAN_LAYOUTS[i].chan_mask; + if (nch) *nch=CHAN_LAYOUTS[i].nch; + return true; + } + } + return false; +} + + +bool PackFlacPicBase64(WDL_StringKeyedArray *metadata, + int img_w, int img_h, int bpp, WDL_HeapBuf *hb) +{ + if (!metadata || !hb || img_w <= 0 || img_h <= 0) return false; + + const char *picfn=metadata->Get("FLACPIC:APIC_FILE"); + const char *pictype=metadata->Get("FLACPIC:APIC_TYPE"); + const char *picdesc=metadata->Get("FLACPIC:APIC_DESC"); + + if (!picfn || !picfn[0]) return false; + if (!pictype) pictype="3"; + if (!picdesc) picdesc=""; + + const char *mime=NULL; + const char *ext=WDL_get_fileext(picfn); + if (ext && (!stricmp(ext, ".jpg") || !stricmp(ext, ".jpeg"))) mime="image/jpeg"; + else if (ext && !stricmp(ext, ".png")) mime="image/png"; + if (!mime) return false; + + WDL_FileRead fr(picfn); + if (!fr.IsOpen()) return false; + + WDL_INT64 datalen64=fr.GetSize(); + if (datalen64 < 1 || datalen64 >= (1<<30)) + { + return false; + } + int datalen = (int)datalen64; + int r8 = (datalen&7) ? 8-(datalen&7) : 0; + + // see opusfile src/info.c opus_picture_tag_parse_impl + // for what we are apparently encoding + + int mimelen=strlen(mime); + int desclen=strlen(picdesc); + + int binlen = + 4+ // pictype + 4+mimelen+ + 4+desclen+ + 4+4+4+4+ // w, h, depth, colors + 4+datalen+r8; + + WDL_HeapBuf hb_bin; + unsigned char *p=(unsigned char*)hb_bin.ResizeOK(binlen); + if (!p) + { + return false; + } + unsigned char *op=p; + + int t=atoi(pictype); + _AddInt32(t); + _AddInt32(mimelen); + memcpy(p, mime, mimelen); + p += mimelen; + _AddInt32(desclen); + memcpy(p, picdesc, desclen); + p += desclen; + _AddInt32(img_w); + _AddInt32(img_h); + _AddInt32(bpp); + _AddInt32(0); + _AddInt32(datalen+r8); + + fr.Read(p, datalen); + p += datalen; + + memset(p, 0, r8); + p += r8; + + if (WDL_NORMALLY(p-op == binlen)) + { + int base64len=binlen*4/3; + if (base64len&3) base64len += 4-(base64len&3); + ++base64len; // nul terminated return + + int osz=hb->GetSize(); + char *pout=(char*)hb->ResizeOK(osz+base64len); + if (pout) + { + wdl_base64encode(op, pout, binlen); + return true; + } + } + + return false; +} + +bool ExportMetadataImageToTmpFile(const char *srcfn, const char *infostr, + WDL_FastString *imgdesc, WDL_FastString *imgtype, WDL_FastString *imgfn) +{ + if (!srcfn || !srcfn[0] || !infostr || !infostr[0] || !imgfn) return false; + + WDL_HeapBuf tmp; + int tmplen=strlen(infostr); + if (!tmp.ResizeOK(tmplen+1)) return false; + memcpy(tmp.Get(), infostr, tmplen+1); + + const char *ext=NULL, *mime=NULL, *desc=NULL, *type=NULL, *offs=NULL, *len=NULL; + char *p=(char*)tmp.Get(); + for (int i=0; i < tmplen; ++i) + { + if (!strncmp(p+i, "ext:", 4)) { if (i) p[i-1]=0; i += 4; ext=p+i; } + else if (!strncmp(p+i, "mime:", 5)) { if (i) p[i-1]=0; i += 5; mime=p+i; } + else if (!strncmp(p+i, "desc:", 5)) { if (i) p[i-1]=0; i += 5; desc=p+i; } + else if (!strncmp(p+i, "type:", 5)) { if (i) p[i-1]=0; i += 5; type=p+i; } + else if (!strncmp(p+i, "offset:", 7)) { if (i) p[i-1]=0; i += 7; offs=p+i; } + else if (!strncmp(p+i, "length:", 7)) { if (i) p[i-1]=0; i += 7; len=p+i; } + } + + WDL_INT64 ioffs = offs ? (WDL_INT64)atof(offs) : 0; + int ilen = len ? atoi(len) : 0; + + bool ok=false; + if ((ext || mime) && ioffs > 0 && ilen > 0) + { + WDL_FileRead fr(srcfn); + if (fr.IsOpen() && fr.GetSize() >= ioffs+ilen) + { + fr.SetPosition(ioffs); + + char tmppath[2048]; + tmppath[0]=0; + GetTempPath(sizeof(tmppath), tmppath); + imgfn->Set(tmppath); + imgfn->Append(WDL_get_filepart(srcfn)); + imgfn->Append("."); + if (ext) imgfn->Append(ext); + else if (mime && !strncmp(mime, "image/", 6)) imgfn->Append(mime+6); + + WDL_FileWrite fw(imgfn->Get()); + if (fw.IsOpen() && CopyFileData(&fr, &fw, ilen)) + { + if (desc && imgdesc) imgdesc->Set(desc); + if (type && imgtype) imgtype->Set(type); + ok=true; + } + } + } + + return ok; +} + + + +#endif // _METADATA_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/mp3write.h b/plugin-reaper-realearn/main/lib/WDL/WDL/mp3write.h new file mode 100644 index 0000000..8add096 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/mp3write.h @@ -0,0 +1,138 @@ +/* + WDL - mp3write.h + Copyright (C) 2005 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. + +*/ + +/* + + This file provides a simple class for writing MP3 files (using lameencdec.h) + +*/ + + +#ifndef _MP3WRITE_H_ +#define _MP3WRITE_H_ + + +#include +#include "lameencdec.h" + +class mp3Writer +{ + public: + // appending doesnt check sample types + mp3Writer() + { + m_enc=0; + m_fp=0; + m_srate=0; + m_nch=0; + } + + mp3Writer(char *filename, int nch, int srate, int bitrate, int allow_append=1) + { + m_enc=0; + m_fp=0; + m_srate=0; + m_nch=0; + Open(filename,nch,srate,bitrate,allow_append); + + } + + int Open(char *filename, int nch, int srate, int bitrate, int allow_append=1) + { + m_fp=0; + if (allow_append) + { + m_fp=fopen(filename,"r+b"); + if (m_fp) + { + fseek(m_fp,0,SEEK_END); + } + } + if (!m_fp) + { + m_fp=fopen(filename,"wb"); + } + m_nch=nch>1?2:1; + m_srate=srate; + m_enc = new LameEncoder(srate,nch,bitrate); + if (m_enc->Status()) + { + delete m_enc; + m_enc=0; + } + return m_fp && m_enc; + } + + ~mp3Writer() + { + if (m_fp) + { + if (m_enc) + { + m_enc->Encode(NULL,0); + if (m_enc->outqueue.Available()) + { + fwrite(m_enc->outqueue.Get(),1,m_enc->outqueue.GetSize(),m_fp); + fflush(m_fp); + m_enc->outqueue.Advance(m_enc->outqueue.GetSize()); + m_enc->outqueue.Compact(); + } + } + + fclose(m_fp); + m_fp=0; + } + if (m_enc) + { + delete m_enc; + m_enc=0; + } + } + + int Status() { return m_enc && m_fp; } + + void WriteFloats(float *samples, int nsamples) + { + if (!m_fp || !m_enc) return; + + m_enc->Encode(samples,nsamples/m_nch); + if (m_enc->outqueue.Available()) + { + fwrite(m_enc->outqueue.Get(),1,m_enc->outqueue.GetSize(),m_fp); + fflush(m_fp); + m_enc->outqueue.Advance(m_enc->outqueue.GetSize()); + m_enc->outqueue.Compact(); + } + + } + + int get_nch() { return m_nch; } + int get_srate() { return m_srate; } + + private: + FILE *m_fp; + int m_nch,m_srate; + LameEncoder *m_enc; +}; + + +#endif//_MP3WRITE_H_ \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/mutex.h b/plugin-reaper-realearn/main/lib/WDL/WDL/mutex.h new file mode 100644 index 0000000..a83c24f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/mutex.h @@ -0,0 +1,237 @@ +/* + WDL - mutex.h + Copyright (C) 2005 and later, 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. + +*/ + +/* + + + This file provides a simple class that abstracts a mutex or critical section object. + On Windows it uses CRITICAL_SECTION, on everything else it uses pthread's mutex library. + It simulates the Critical Section behavior on non-Windows, as well (meaning a thread can + safely Enter the mutex multiple times, provided it Leaves the same number of times) + +*/ + +#ifndef _WDL_MUTEX_H_ +#define _WDL_MUTEX_H_ + +#ifdef _WIN32 +#include +#else + +#include +// define this if you wish to use carbon critical sections on OS X +// #define WDL_MAC_USE_CARBON_CRITSEC + +#ifdef WDL_MAC_USE_CARBON_CRITSEC +#include +#else +#include +#endif + +#endif + +#include "wdltypes.h" +#include "wdlatomic.h" + +#ifdef _DEBUG +#include +#endif + +class WDL_Mutex { + public: + WDL_Mutex() + { +#ifdef _WIN32 + InitializeCriticalSection(&m_cs); +#elif defined( WDL_MAC_USE_CARBON_CRITSEC) + MPCreateCriticalRegion(&m_cr); +#elif defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) && !defined(__linux__) + const pthread_mutex_t tmp = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; + m_mutex = tmp; +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE); +#ifdef __linux__ + // todo: macos too? + pthread_mutexattr_setprotocol(&attr,PTHREAD_PRIO_INHERIT); +#endif + pthread_mutex_init(&m_mutex,&attr); + pthread_mutexattr_destroy(&attr); +#endif + } + ~WDL_Mutex() + { +#ifdef _WIN32 + DeleteCriticalSection(&m_cs); +#elif defined(WDL_MAC_USE_CARBON_CRITSEC) + MPDeleteCriticalRegion(m_cr); +#else + pthread_mutex_destroy(&m_mutex); +#endif + } + + void Enter() + { +#ifdef _WIN32 + EnterCriticalSection(&m_cs); +#elif defined(WDL_MAC_USE_CARBON_CRITSEC) + MPEnterCriticalRegion(m_cr,kDurationForever); +#else + pthread_mutex_lock(&m_mutex); +#endif + } + + void Leave() + { +#ifdef _WIN32 + LeaveCriticalSection(&m_cs); +#elif defined(WDL_MAC_USE_CARBON_CRITSEC) + MPExitCriticalRegion(m_cr); +#else + pthread_mutex_unlock(&m_mutex); +#endif + } + + private: +#ifdef _WIN32 + CRITICAL_SECTION m_cs; +#elif defined(WDL_MAC_USE_CARBON_CRITSEC) + MPCriticalRegionID m_cr; +#else + pthread_mutex_t m_mutex; +#endif + + // prevent callers from copying mutexes accidentally + WDL_Mutex(const WDL_Mutex &cp) + { +#ifdef _DEBUG + assert(sizeof(WDL_Mutex) == 0); +#endif + } + WDL_Mutex &operator=(const WDL_Mutex &cp) + { +#ifdef _DEBUG + assert(sizeof(WDL_Mutex) == 0); +#endif + return *this; + } + +} WDL_FIXALIGN; + +class WDL_MutexLock { +public: + WDL_MutexLock(WDL_Mutex *m) : m_m(m) { if (m) m->Enter(); } + ~WDL_MutexLock() { if (m_m) m_m->Leave(); } + + // the caller modifies this, make sure it unlocks the mutex first and locks the new mutex! + WDL_Mutex *m_m; +} WDL_FIXALIGN; + +class WDL_SharedMutex +{ + public: + WDL_SharedMutex() { m_sharedcnt=0; } + ~WDL_SharedMutex() { } + + void LockExclusive() // note: the calling thread must NOT have any shared locks, or deadlock WILL occur + { + m_mutex.Enter(); +#ifdef _WIN32 + while (m_sharedcnt>0) Sleep(1); +#else + while (m_sharedcnt>0) usleep(100); +#endif + } + void UnlockExclusive() { m_mutex.Leave(); } + + void LockShared() + { + m_mutex.Enter(); + wdl_atomic_incr(&m_sharedcnt); + m_mutex.Leave(); + } + void UnlockShared() + { + wdl_atomic_decr(&m_sharedcnt); + } + + void SharedToExclusive() // assumes a SINGLE shared lock by this thread! + { + m_mutex.Enter(); +#ifdef _WIN32 + while (m_sharedcnt>1) Sleep(1); +#else + while (m_sharedcnt>1) usleep(100); +#endif + UnlockShared(); + } + + void ExclusiveToShared() // assumes exclusive locked returns with shared locked + { + // already have exclusive lock + wdl_atomic_incr(&m_sharedcnt); + m_mutex.Leave(); + } + + private: + WDL_Mutex m_mutex; + volatile int m_sharedcnt; + + // prevent callers from copying accidentally + WDL_SharedMutex(const WDL_SharedMutex &cp) + { + #ifdef _DEBUG + assert(sizeof(WDL_SharedMutex) == 0); + #endif + } + WDL_SharedMutex &operator=(const WDL_SharedMutex &cp) + { + #ifdef _DEBUG + assert(sizeof(WDL_SharedMutex) == 0); + #endif + return *this; + } + + +} WDL_FIXALIGN; + + + +class WDL_MutexLockShared { + public: + WDL_MutexLockShared(WDL_SharedMutex *m) : m_m(m) { if (m) m->LockShared(); } + ~WDL_MutexLockShared() { if (m_m) m_m->UnlockShared(); } + private: + WDL_SharedMutex *m_m; +} WDL_FIXALIGN; + +class WDL_MutexLockExclusive { + public: + WDL_MutexLockExclusive(WDL_SharedMutex *m) : m_m(m) { if (m) m->LockExclusive(); } + ~WDL_MutexLockExclusive() { if (m_m) m_m->UnlockExclusive(); } + private: + WDL_SharedMutex *m_m; +} WDL_FIXALIGN; + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/pcmfmtcvt.h b/plugin-reaper-realearn/main/lib/WDL/WDL/pcmfmtcvt.h new file mode 100644 index 0000000..b621830 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/pcmfmtcvt.h @@ -0,0 +1,173 @@ +/* + WDL - pcmfmtcvt.h + Copyright (C) 2005 and later, 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. + +*/ + +#ifndef _PCMFMTCVT_H_ +#define _PCMFMTCVT_H_ + + +#include "wdltypes.h" +#include + +#ifndef PCMFMTCVT_DBL_TYPE +#define PCMFMTCVT_DBL_TYPE double +#endif + +#define INT16_TO_float INT16_TO_double +#define float_TO_INT16 double_TO_INT16 +#define float_to_i32 double_to_i32 +#define float_to_i24 double_to_i24 +#define i24_to_float i24_to_double +#define i32_to_float i32_to_double + +#define pcmToFloats pcmToDoubles +#define floatsToPcm doublesToPcm + + +template static void INT16_TO_double(T &out, int in) +{ + out = (T) (in/32768.0); +} +template static void double_TO_INT16(T &out, double in) +{ + in *= 32768.0; + if (in <= -32768.0) out = -32768; + else if (in >= 32767.0) out = 32767; + else out = (T) floor(in + 0.5); +} + +template static void i32_to_double(int i32, T *p) +{ + *p = i32 * (1.0 / 2147483648.0); +} + +template static void i24_to_double(const unsigned char *i24, T *p) +{ + // little endian + int val=(i24[0]) | (i24[1]<<8) | (i24[2]<<16); + if (val&0x800000) val|=0xFF000000; + *p = (T) (((double) val) * (1.0 / 8388608.0)); +} + +template static void double_to_i32(const T *vv, int *i32) +{ + const double v = *vv * 2147483648.0; + if (v <= -2147483648.0) *i32 = 0x80000000; + else if (v >= 2147483647.0) *i32 = 0x7FFFFFFF; + else *i32 = (int) floor(v + 0.5); +} + +static WDL_STATICFUNC_UNUSED int double_to_int_24(const double vv) +{ + const double v = vv * 8388608.0; + if (v <= -8388608.0) return -0x800000; + if (v >= 8388607.0) return 0x7fffff; + return (int) floor(v + 0.5); +} + +static WDL_STATICFUNC_UNUSED int double_to_int_x(const double vv, int bits) +{ + const double sc = (double) (1<<(bits-1)); + const double v = vv * sc; + if (v <= -sc) return -(1<<(bits-1)); + if (v >= sc-1.0) return (1<<(bits-1))-1; + return (int) floor(v + 0.5); +} + +template static void double_to_i24(const T *vv, unsigned char *i24) +{ + const int i = double_to_int_24(*vv); + i24[0]=i&0xff; + i24[1]=(i>>8)&0xff; + i24[2]=(i>>16)&0xff; +} + +template static void pcmToDoubles(void *src, int items, int bps, int src_spacing, T *dest, int dest_spacing, int byteadvancefor24=0) +{ + if (bps == 32) + { + int *i1=(int *)src; + while (items--) + { + i32_to_double(*i1,dest); + i1+=src_spacing; + dest+=dest_spacing; + } + } + else if (bps == 24) + { + unsigned char *i1=(unsigned char *)src; + int adv=3*src_spacing+byteadvancefor24; + while (items--) + { + i24_to_double(i1,dest); + dest+=dest_spacing; + i1+=adv; + } + } + else if (bps == 16) + { + short *i1=(short *)src; + while (items--) + { + INT16_TO_double(*dest,*i1); + i1+=src_spacing; + dest+=dest_spacing; + } + } +} + +template static void doublesToPcm(const T *src, int src_spacing, int items, void *dest, int bps, int dest_spacing, int byteadvancefor24=0) +{ + if (bps==32) + { + int *o1=(int*)dest; + while (items--) + { + double_to_i32(src,o1); + src+=src_spacing; + o1+=dest_spacing; + } + } + else if (bps == 24) + { + unsigned char *o1=(unsigned char*)dest; + int adv=dest_spacing*3+byteadvancefor24; + while (items--) + { + double_to_i24(src,o1); + src+=src_spacing; + o1+=adv; + } + } + else if (bps==16) + { + short *o1=(short*)dest; + while (items--) + { + double_TO_INT16(*o1,*src); + src+=src_spacing; + o1+=dest_spacing; + } + } +} + +#endif //_PCMFMTCVT_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_cam.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_cam.cpp new file mode 100644 index 0000000..22e5f38 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_cam.cpp @@ -0,0 +1,762 @@ +/****************************************************************************** +Plush Version 1.2 +cam.c +Camera and Rendering +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************/ + +#include "plush.h" + + +#include "../lice/lice_extended.h" + +#include "../mergesort.h" + +#define MACRO_plMatrixApply(m,x,y,z,outx,outy,outz) \ + ( outx ) = ( x )*( m )[0] + ( y )*( m )[1] + ( z )*( m )[2] + ( m )[3];\ + ( outy ) = ( x )*( m )[4] + ( y )*( m )[5] + ( z )*( m )[6] + ( m )[7];\ + ( outz ) = ( x )*( m )[8] + ( y )*( m )[9] + ( z )*( m )[10] + ( m )[11] + +#define MACRO_plDotProduct(x1,y1,z1,x2,y2,z2) \ + ((( x1 )*( x2 ))+(( y1 )*( y2 ))+(( z1 )*( z2 ))) + +#define MACRO_plNormalizeVector(x,y,z) { \ + double length; \ + length = ( x )*( x )+( y )*( y )+( z )*( z ); \ + if (length > 0.0000000001) { \ + double __l = 1.0/sqrt(length); \ + ( x ) *= __l; \ + ( y ) *= __l; \ + ( z ) *= __l; \ + } \ +} + + +static void _FindNormal(double x2, double x3,double y2, double y3, + double zv, double *res) { + res[0] = zv*(y2-y3); + res[1] = zv*(x3-x2); + res[2] = x2*y3 - y2*x3; +} + + +void pl_Cam::SetTarget(pl_Float x, pl_Float y, pl_Float z) { + double dx, dy, dz; + dx = x - X; + dy = y - Y; + dz = z - Z; + Roll = 0; + if (dz > 0.0001f) { + Pan = (pl_Float) (-atan(dx/dz)*(180.0/PL_PI)); + dz /= cos(Pan*(PL_PI/180.0)); + Pitch = (pl_Float) (atan(dy/dz)*(180.0/PL_PI)); + } else if (dz < -0.0001f) { + Pan = (pl_Float) (180.0-atan(dx/dz)*(180.0/PL_PI)); + dz /= cos((Pan-180.0f)*(PL_PI/180.0)); + Pitch = (pl_Float) (-atan(dy/dz)*(180.0/PL_PI)); + } else { + Pan = 90.0f; + Pitch = (pl_Float) (atan2(dy,-dx) * (180.0 / PL_PI)); + Roll = -90.0f; + } + GenMatrix = true; +} + +void pl_Cam::RecalcFrustum(int fbw, int fbh) +{ + const int cx = (CenterX*m_lastFBScaling)/256 + fbw/2; + const int cy = (CenterY*m_lastFBScaling)/256 + fbh/2; + m_lastCX = cx; + m_lastCY = cy; + + m_adj_asp = 1.0 / AspectRatio; + m_fovfactor = CalcFOVFactor(fbw); + memset(m_clipPlanes,0,sizeof(m_clipPlanes)); + + /* Back */ + m_clipPlanes[0][2] = -1.0; + m_clipPlanes[0][3] = -ClipBack; + + /* Left */ + m_clipPlanes[1][3] = 0.00000001; + if (cx == 0) m_clipPlanes[1][0] = 1.0; + else + { + _FindNormal(-100,-100, + 100, -100, + m_fovfactor*100.0/cx, + m_clipPlanes[1]); + if (cx < 0) + { + m_clipPlanes[1][0] = -m_clipPlanes[1][0]; + m_clipPlanes[1][1] = -m_clipPlanes[1][1]; + m_clipPlanes[1][2] = -m_clipPlanes[1][2]; + } + } + + /* Right */ + m_clipPlanes[2][3] = 0.00000001; + if (fbw == cx) m_clipPlanes[2][0] = -1.0; + else + { + _FindNormal(100,100, + -100, 100, + m_fovfactor*100.0/(fbw-cx), + m_clipPlanes[2]); + if (cx > fbw) + { + m_clipPlanes[2][0] = -m_clipPlanes[2][0]; + m_clipPlanes[2][1] = -m_clipPlanes[2][1]; + m_clipPlanes[2][2] = -m_clipPlanes[2][2]; + } + } + + /* Top */ + m_clipPlanes[3][3] = 0.00000001; + if (cy == 0) m_clipPlanes[3][1] = 1.0; + else + { + _FindNormal(100, -100, + 100, 100, + m_fovfactor*m_adj_asp*-100.0/(cy), + m_clipPlanes[3]); + if (cy < 0) + { + m_clipPlanes[3][0] = -m_clipPlanes[3][0]; + m_clipPlanes[3][1] = -m_clipPlanes[3][1]; + m_clipPlanes[3][2] = -m_clipPlanes[3][2]; + } + } + + /* Bottom */ + m_clipPlanes[4][3] = 0.00000001; + if (cy == fbh) m_clipPlanes[4][1] = -1.0; + else + { + _FindNormal(-100, 100, + -100, -100, + m_fovfactor*m_adj_asp*100.0/(cy-fbh), + m_clipPlanes[4]); + if (cy > fbh) + { + m_clipPlanes[4][0] = -m_clipPlanes[4][0]; + m_clipPlanes[4][1] = -m_clipPlanes[4][1]; + m_clipPlanes[4][2] = -m_clipPlanes[4][2]; + } + } + +} + + + /* Returns: 0 if nothing gets in, 1 or 2 if pout1 & pout2 get in */ +pl_uInt pl_Cam::_ClipToPlane(pl_uInt numVerts, pl_Float *plane) +{ + pl_uInt i, nextvert, curin, nextin; + double curdot, nextdot, scale; + pl_uInt invert, outvert; + invert = 0; + outvert = 0; + curdot = m_cl[0].newVertices[0].xformedx*plane[0] + + m_cl[0].newVertices[0].xformedy*plane[1] + + m_cl[0].newVertices[0].xformedz*plane[2]; + curin = (curdot >= plane[3]); + + for (i=0 ; i < numVerts; i++) { + nextvert = (i + 1) % numVerts; + if (curin) { + memcpy(&m_cl[1].ShadeInfos[outvert][0],&m_cl[0].ShadeInfos[invert][0],3*sizeof(pl_Float)); + int a; + for(a=0;a= plane[3]); + if (curin != nextin) { + scale = (plane[3] - curdot) / (nextdot - curdot); + m_cl[1].newVertices[outvert].xformedx = (pl_Float) (m_cl[0].newVertices[invert].xformedx + + (m_cl[0].newVertices[nextvert].xformedx - m_cl[0].newVertices[invert].xformedx) + * scale); + m_cl[1].newVertices[outvert].xformedy = (pl_Float) (m_cl[0].newVertices[invert].xformedy + + (m_cl[0].newVertices[nextvert].xformedy - m_cl[0].newVertices[invert].xformedy) + * scale); + m_cl[1].newVertices[outvert].xformedz = (pl_Float) (m_cl[0].newVertices[invert].xformedz + + (m_cl[0].newVertices[nextvert].xformedz - m_cl[0].newVertices[invert].xformedz) + * scale); + + m_cl[1].ShadeInfos[outvert][0] = m_cl[0].ShadeInfos[invert][0] + (m_cl[0].ShadeInfos[nextvert][0] - m_cl[0].ShadeInfos[invert][0]) * scale; + m_cl[1].ShadeInfos[outvert][1] = m_cl[0].ShadeInfos[invert][1] + (m_cl[0].ShadeInfos[nextvert][1] - m_cl[0].ShadeInfos[invert][1]) * scale; + m_cl[1].ShadeInfos[outvert][2] = m_cl[0].ShadeInfos[invert][2] + (m_cl[0].ShadeInfos[nextvert][2] - m_cl[0].ShadeInfos[invert][2]) * scale; + + int a; + for(a=0;a= 0 && xtmp < m_lastFBWidth && ytmp >= 0 && ytmp < m_lastFBHeight; +} + + +void pl_Cam::ClipRenderFace(pl_Face *face, pl_Obj *obj) { + const int cx = m_lastCX, cy = m_lastCY; + + { + pl_Vertex *vlist=obj->Vertices.Get(); + int a; + for (a = 0; a < 3; a ++) { + m_cl[0].newVertices[a] = vlist[face->VertexIndices[a]]; + + memcpy(&m_cl[0].ShadeInfos[a][0],&face->Shades[a][0],3*sizeof(pl_Float)); + int b; + for(b=0;bMappingU[b][a]; + m_cl[0].MappingV[b][a] = face->MappingV[b][a]; + } + } + } + + pl_uInt numVerts = 3; + { + int a = (m_clipPlanes[0][3] < 0.0 ? 0 : 1); + while (a < PL_NUM_CLIP_PLANES && numVerts > 2) + { + numVerts = _ClipToPlane(numVerts, m_clipPlanes[a]); + memcpy(&m_cl[0],&m_cl[1],sizeof(m_cl[0])); + a++; + } + } + if (numVerts > 2) { + pl_Face newface; + memcpy(&newface,face,sizeof(pl_Face)); + int k; + for (k = 2; k < (int)numVerts; k ++) { + int a; + for (a = 0; a < 3; a ++) { + int w; + if (a == 0) w = 0; + else w = a+(k-2); ; + pl_Vertex *thisv=m_cl[0].newVertices+w; + newface.Shades[a][0] = m_cl[0].ShadeInfos[w][0]; + newface.Shades[a][1] = m_cl[0].ShadeInfos[w][1]; + newface.Shades[a][2] = m_cl[0].ShadeInfos[w][2]; + int b; + for(b=0;bxformedz; + double ytmp = m_fovfactor * newface.Scrz[a]; + double xtmp = ytmp*thisv->xformedx; + ytmp *= thisv->xformedy*m_adj_asp; + newface.Scrx[a] = xtmp+cx; + newface.Scry[a] = ytmp+cy; + } + RenderTrisOut++; + + // quick approx of triangle area + RenderPixelsOut += 0.5*fabs( + (newface.Scrx[1] - newface.Scrx[0]) * + (newface.Scry[2] - newface.Scry[0]) - + (newface.Scrx[2] - newface.Scrx[0]) * + (newface.Scry[1] - newface.Scry[0]) ); + + PutFace(&newface); + } + } +} + +pl_sInt pl_Cam::ClipNeeded(pl_Face *face, pl_Obj *obj) { + const int fbw=m_fBuffer.m_w, fbh=m_fBuffer.m_h; + const int cx = m_lastCX, cy = m_lastCY; + double dr,dl,db,dt; + double f; + dr = (fbw-cx); + dl = (-cx); + db = (fbh-cy); + dt = (-cy); + f = m_fovfactor*m_adj_asp; + pl_Vertex *vlist=obj->Vertices.Get(); + pl_Vertex *v0=vlist+face->VertexIndices[0]; + pl_Vertex *v1=vlist+face->VertexIndices[1]; + pl_Vertex *v2=vlist+face->VertexIndices[2]; + + return ((ClipBack <= 0.0 || + v0->xformedz <= ClipBack || + v1->xformedz <= ClipBack || + v2->xformedz <= ClipBack) && + (v0->xformedz >= 0 || + v1->xformedz >= 0 || + v2->xformedz >= 0) && + (v0->xformedx*m_fovfactor<=dr*v0->xformedz || + v1->xformedx*m_fovfactor<=dr*v1->xformedz || + v2->xformedx*m_fovfactor<=dr*v2->xformedz) && + (v0->xformedx*m_fovfactor>=dl*v0->xformedz || + v1->xformedx*m_fovfactor>=dl*v1->xformedz || + v2->xformedx*m_fovfactor>=dl*v2->xformedz) && + (v0->xformedy*f<=db*v0->xformedz || + v1->xformedy*f<=db*v1->xformedz || + v2->xformedy*f<=db*v2->xformedz) && + (v0->xformedy*f>=dt*v0->xformedz || + v1->xformedy*f>=dt*v1->xformedz || + v2->xformedy*f>=dt*v2->xformedz)); +} + + + + + + +void pl_Cam::Begin(LICE_IBitmap *fb, bool want_zbclear, pl_ZBuffer zbclear) { + if (WDL_NOT_NORMALLY(m_fBuffer.m_buf) || WDL_NOT_NORMALLY(!fb)) return; + + m_lastFBWidth=fb->getWidth(); + m_lastFBHeight=fb->getHeight(); + m_lastFBScaling = (int)fb->Extended(LICE_EXT_GET_SCALING,NULL); + if (m_lastFBScaling==0 || WDL_NOT_NORMALLY(m_lastFBScaling > 1024)) m_lastFBScaling=256; + + m_fBuffer.m_buf = fb->getBits(); + m_fBuffer.m_span = fb->getRowSpan(); + m_fBuffer.m_w = fb->getWidth() * m_lastFBScaling / 256; + m_fBuffer.m_h = fb->getHeight() * m_lastFBScaling / 256; + m_fBuffer.m_flipped = fb->isFlipped(); + + if (WantZBuffer) + { + int zbsz=m_fBuffer.m_w*m_fBuffer.m_h; + pl_ZBuffer *zb=zBuffer.ResizeOK(zbsz); + if (!zb) zBuffer.Resize(0); + else if (want_zbclear) + { + if (!zbclear) memset(zb,0,zbsz*sizeof(pl_ZBuffer)); + else + { + int i=zbsz; + while(i--) *zb++=zbclear; + } + } + } + else zBuffer.Resize(0); + pl_Float tempMatrix[16]; + _numlights = 0; + _numfaces = _numfaces_sorted = 0; + if (GenMatrix) + { + plMatrixRotate(CamMatrix,2,-Pan); + plMatrixRotate(tempMatrix,1,-Pitch); + plMatrixMultiply(CamMatrix,tempMatrix); + plMatrixRotate(tempMatrix,3,-Roll); + plMatrixMultiply(CamMatrix,tempMatrix); + } + + RecalcFrustum(m_fBuffer.m_w, m_fBuffer.m_h); + + RenderTrisIn=RenderTrisCulled=RenderTrisOut=0; + RenderPixelsOut=0.0; + +} + +void pl_Cam::RenderLight(pl_Light *light) { + if (!light||WDL_NOT_NORMALLY(!m_fBuffer.m_buf)) return; + + pl_Float *pl, xp, yp, zp; + if (light->Type == PL_LIGHT_NONE) return; + if (_lights.GetSize()<=_numlights) _lights.Resize(_numlights+1); + pl = _lights.Get()[_numlights].l; + if (light->Type == PL_LIGHT_VECTOR) { + xp = light->Xp; + yp = light->Yp; + zp = light->Zp; + MACRO_plMatrixApply(CamMatrix,xp,yp,zp,pl[0],pl[1],pl[2]); + } else if (light->Type & PL_LIGHT_POINT) { + xp = light->Xp-X; + yp = light->Yp-Y; + zp = light->Zp-Z; + MACRO_plMatrixApply(CamMatrix,xp,yp,zp,pl[0],pl[1],pl[2]); + } + _lights.Get()[_numlights++].light = light; +} + +void pl_Cam::RenderObject(pl_Obj *obj, const pl_Float *bmatrix, const pl_Float *bnmatrix) { + if (!obj||WDL_NOT_NORMALLY(!m_fBuffer.m_buf)) return; + + pl_Float oMatrix[16], nMatrix[16], tempMatrix[16]; + + if (obj->GenMatrix) { + plMatrixRotate(nMatrix,1,obj->Xa); + plMatrixRotate(tempMatrix,2,obj->Ya); + plMatrixMultiply(nMatrix,tempMatrix); + plMatrixRotate(tempMatrix,3,obj->Za); + plMatrixMultiply(nMatrix,tempMatrix); + memcpy(obj->RotMatrix,nMatrix,sizeof(pl_Float)*16); + + memcpy(oMatrix,nMatrix,sizeof(pl_Float)*16); + plMatrixTranslate(tempMatrix, obj->Xp, obj->Yp, obj->Zp); + plMatrixMultiply(oMatrix,tempMatrix); + memcpy(obj->Matrix,oMatrix,sizeof(pl_Float)*16); + } else { + memcpy(oMatrix,obj->Matrix,sizeof(pl_Float)*16); + memcpy(nMatrix,obj->RotMatrix,sizeof(pl_Float)*16); + } + + if (bnmatrix) plMatrixMultiply(nMatrix,bnmatrix); + if (bmatrix) plMatrixMultiply(oMatrix,bmatrix); + + { + int i; + for (i = 0; i < obj->Children.GetSize(); i ++) + if (obj->Children.Get(i)) RenderObject(obj->Children.Get(i),oMatrix,nMatrix); + } + if (!obj->Faces.GetSize() || !obj->Vertices.GetSize()) return; + + plMatrixTranslate(tempMatrix, -X, -Y, -Z); + plMatrixMultiply(oMatrix,tempMatrix); + plMatrixMultiply(oMatrix,CamMatrix); + plMatrixMultiply(nMatrix,CamMatrix); + + { + pl_Vertex *vertex = obj->Vertices.Get(); + int i = obj->Vertices.GetSize(); + + while (i--) + { + MACRO_plMatrixApply(oMatrix,vertex->x,vertex->y,vertex->z, + vertex->xformedx, vertex->xformedy, vertex->xformedz); + MACRO_plMatrixApply(nMatrix,vertex->nx,vertex->ny,vertex->nz, + vertex->xformednx,vertex->xformedny,vertex->xformednz); + vertex++; + } + } + + if (_faces.GetSize() < _numfaces + obj->Faces.GetSize()) _faces.Resize(_numfaces + obj->Faces.GetSize()); + + + _faceInfo *facelistout = _faces.Get() + _numfaces; + + pl_Face *face = obj->Faces.Get(); + int facecnt = obj->Faces.GetSize(); + + RenderTrisIn += facecnt; + _numfaces += facecnt; + pl_Vertex *vlist = obj->Vertices.Get(); + + while (facecnt--) + { + double nx,ny,nz; + pl_Mat *mat=face->Material; + if (mat->BackfaceCull || (mat->Lightable && !mat->Smoothing)) + { + MACRO_plMatrixApply(nMatrix,face->nx,face->ny,face->nz,nx,ny,nz); + } + pl_Vertex *v0=vlist+face->VertexIndices[0]; + pl_Vertex *v1=vlist+face->VertexIndices[1]; + pl_Vertex *v2=vlist+face->VertexIndices[2]; + + if (!mat->BackfaceCull || (MACRO_plDotProduct(nx,ny,nz, v0->xformedx, v0->xformedy, v0->xformedz) < 0.0000001)) { + if (ClipNeeded(face,obj)) { + if (!mat->Smoothing && (mat->Lightable||mat->FadeDist)) { + pl_Float val[3]; + memcpy(val,face->sLighting,3*sizeof(pl_Float)); + if (mat->Lightable) { + _lightInfo *inf = _lights.Get(); + int i=_numlights; + while (i--) + { + pl_Light *light = inf->light; + double lightsc=0.0; + if (light->Type & PL_LIGHT_POINT_ANGLE) { + double nx2 = inf->l[0] - v0->xformedx; + double ny2 = inf->l[1] - v0->xformedy; + double nz2 = inf->l[2] - v0->xformedz; + MACRO_plNormalizeVector(nx2,ny2,nz2); + lightsc = MACRO_plDotProduct(nx,ny,nz,nx2,ny2,nz2); + } + if (light->Type & PL_LIGHT_POINT_DISTANCE) { + double nx2 = inf->l[0] - v0->xformedx; + double ny2 = inf->l[1] - v0->xformedy; + double nz2 = inf->l[2] - v0->xformedz; + if (light->Type & PL_LIGHT_POINT_ANGLE) { + nx2 = (1.0 - 0.5*((nx2*nx2+ny2*ny2+nz2*nz2)/ + light->HalfDistSquared)); + lightsc *= plMax(0,plMin(1.0,nx2)); + } else { + lightsc = (1.0 - 0.5*((nx2*nx2+ny2*ny2+nz2*nz2)/ + light->HalfDistSquared)); + lightsc = plMax(0,plMin(1.0,lightsc)); + } + } + if (light->Type == PL_LIGHT_VECTOR) + lightsc = MACRO_plDotProduct(nx,ny,nz,inf->l[0],inf->l[1],inf->l[2]); + + if (lightsc>0.0) + { + val[0] += light->Intensity[0]*lightsc; + val[1] += light->Intensity[1]*lightsc; + val[2] += light->Intensity[2]*lightsc; + } + else if (mat->BackfaceIllumination) + { + val[0] -= light->Intensity[0]*lightsc*mat->BackfaceIllumination; + val[1] -= light->Intensity[1]*lightsc*mat->BackfaceIllumination; + val[2] -= light->Intensity[2]*lightsc*mat->BackfaceIllumination; + } + inf++; + } /* End of light loop */ + } /* End of flat shading if */ + + if (mat->FadeDist) + { + double lightsc = 1.0 - (v0->xformedz+v1->xformedz+v2->xformedz) / (mat->FadeDist*3.0); + if (lightsc<0.0) lightsc=0.0; + else if (lightsc>1.0)lightsc=1.0; + if (mat->Lightable) + { + val[0] *= lightsc; + val[1] *= lightsc; + val[2] *= lightsc; + } + else + { + val[0]+=lightsc; + val[1]+=lightsc; + val[2]+=lightsc; + } + } + face->Shades[0][0]=mat->Ambient[0] + mat->Diffuse[0]*val[0]; + face->Shades[0][1]=mat->Ambient[1] + mat->Diffuse[1]*val[1]; + face->Shades[0][2]=mat->Ambient[2] + mat->Diffuse[2]*val[2]; + } + else memcpy(face->Shades,mat->Ambient,sizeof(mat->Ambient)); // flat shading + + if ((mat->Texture && mat->TexMapIdx<0)||(mat->Texture2 && mat->Tex2MapIdx<0)) { + face->MappingU[PLUSH_MAX_MAPCOORDS-1][0] = 0.5 + (v0->xformednx); + face->MappingV[PLUSH_MAX_MAPCOORDS-1][0] = 0.5 - (v0->xformedny); + face->MappingU[PLUSH_MAX_MAPCOORDS-1][1] = 0.5 + (v1->xformednx); + face->MappingV[PLUSH_MAX_MAPCOORDS-1][1] = 0.5 - (v1->xformedny); + face->MappingU[PLUSH_MAX_MAPCOORDS-1][2] = 0.5 + (v2->xformednx); + face->MappingV[PLUSH_MAX_MAPCOORDS-1][2] = 0.5 - (v2->xformedny); + } + + if (mat->Smoothing && (mat->Lightable || mat->FadeDist)) + { + int a; + for (a = 0; a < 3; a ++) { + pl_Float val[3]; + memcpy(val,face->vsLighting[a],sizeof(val)); + pl_Vertex *thisvert = obj->Vertices.Get()+face->VertexIndices[a]; + + if (mat->Lightable) + { + int i=_numlights; + _lightInfo *inf = _lights.Get(); + while (i--) + { + double lightsc = 0.0; + pl_Light *light = inf->light; + if (light->Type & PL_LIGHT_POINT_ANGLE) { + double nx2 = inf->l[0] - thisvert->xformedx; + double ny2 = inf->l[1] - thisvert->xformedy; + double nz2 = inf->l[2] - thisvert->xformedz; + MACRO_plNormalizeVector(nx2,ny2,nz2); + lightsc = MACRO_plDotProduct(thisvert->xformednx, + thisvert->xformedny, + thisvert->xformednz, + nx2,ny2,nz2); + } + if (light->Type & PL_LIGHT_POINT_DISTANCE) { + double nx2 = inf->l[0] - thisvert->xformedx; + double ny2 = inf->l[1] - thisvert->xformedy; + double nz2 = inf->l[2] - thisvert->xformedz; + if (light->Type & PL_LIGHT_POINT_ANGLE) { + double t= (1.0 - 0.5*((nx2*nx2+ny2*ny2+nz2*nz2)/light->HalfDistSquared)); + lightsc *= plMax(0,plMin(1.0,t)); + } else { + lightsc = (1.0 - 0.5*((nx2*nx2+ny2*ny2+nz2*nz2)/light->HalfDistSquared)); + lightsc = plMax(0,plMin(1.0,lightsc)); + } + } + + if (light->Type == PL_LIGHT_VECTOR) + lightsc = MACRO_plDotProduct(thisvert->xformednx, + thisvert->xformedny, + thisvert->xformednz, + inf->l[0],inf->l[1],inf->l[2]); + if (lightsc > 0.0) + { + val[0] += lightsc * light->Intensity[0]; + val[1] += lightsc * light->Intensity[1]; + val[2] += lightsc * light->Intensity[2]; + } + else if (mat->BackfaceIllumination) + { + val[0] -= lightsc * light->Intensity[0]*mat->BackfaceIllumination; + val[1] -= lightsc * light->Intensity[1]*mat->BackfaceIllumination; + val[2] -= lightsc * light->Intensity[2]*mat->BackfaceIllumination; + } + inf++; + } /* End of light loop */ + } /* End of gouraud shading if */ + if (mat->FadeDist) + { + double lightsc = 1.0-thisvert->xformedz/mat->FadeDist; + if (lightsc<0.0) lightsc=0.0; + else if (lightsc>1.0)lightsc=1.0; + if (mat->Lightable) + { + val[0] *= lightsc; + val[1] *= lightsc; + val[2] *= lightsc; + } + else + { + val[0] += lightsc; + val[1] += lightsc; + val[2] += lightsc; + } + } + face->Shades[a][0] = mat->Ambient[0] + mat->Diffuse[0]*val[0]; + face->Shades[a][1] = mat->Ambient[1] + mat->Diffuse[1]*val[1]; + face->Shades[a][2] = mat->Ambient[2] + mat->Diffuse[2]*val[2]; + } /* End of vertex loop for */ + } /* End of gouraud shading mask if */ + else // flat modes, shade all vertices + { + memcpy(&face->Shades[1][0],&face->Shades[0][0],sizeof(pl_Float)*3); + memcpy(&face->Shades[2][0],&face->Shades[0][0],sizeof(pl_Float)*3); + } + + facelistout->zd = v0->xformedz+v1->xformedz+v2->xformedz; + facelistout->obj=obj; + facelistout->face = face; + facelistout++; + + + RenderTrisCulled++; + + } /* Is it in our area Check */ + } /* Backface Check */ + face++; + } + _numfaces = facelistout-_faces.Get(); +} +void pl_Cam::SortToCurrent() +{ + if (Sort && _numfaces > _numfaces_sorted+1) + { + WDL_mergesort(_faces.Get()+_numfaces_sorted, + _numfaces-_numfaces_sorted,sizeof(_faceInfo), + Sort > 0 ? sortFwdFunc : sortRevFunc, + (char*)_sort_tmpspace.Resize((_numfaces-_numfaces_sorted)*sizeof(_faceInfo),false)); + } + _numfaces_sorted=_numfaces; +} + +int pl_Cam::sortRevFunc(const void *a, const void *b) +{ + _faceInfo *aa = (_faceInfo*)a; + _faceInfo *bb = (_faceInfo*)b; + + if (aa->zd < bb->zd) return -1; + if (aa->zd > bb->zd) return 1; + return 0; +} + +int pl_Cam::sortFwdFunc(const void *a, const void *b) +{ + _faceInfo *aa = (_faceInfo*)a; + _faceInfo *bb = (_faceInfo*)b; + + if (aa->zd < bb->zd) return 1; + if (aa->zd > bb->zd) return -1; + return 0; +} + +void pl_Cam::End() { + if (WDL_NOT_NORMALLY(!m_fBuffer.m_buf)) return; + + SortToCurrent(); + + _faceInfo *f = _faces.Get(); + int n=_numfaces; + while (n-->0) + { + if (f->face->Material) + { + ClipRenderFace(f->face,f->obj); + } + f++; + } + + m_fBuffer.m_buf = NULL; + _numfaces=0; + _numlights = 0; +} + + + + +void pl_Light::Set(pl_uChar mode, pl_Float x, pl_Float y, pl_Float z, pl_Float intensity_r, pl_Float intensity_g, pl_Float intensity_b, pl_Float halfDist) { + pl_Float m[16], m2[16]; + Type = mode; + Intensity[0] = intensity_r; + Intensity[1] = intensity_g; + Intensity[2] = intensity_b; + HalfDistSquared = halfDist*halfDist; + switch (mode) { + case PL_LIGHT_VECTOR: + plMatrixRotate(m,1,x); + plMatrixRotate(m2,2,y); + plMatrixMultiply(m,m2); + plMatrixRotate(m2,3,z); + plMatrixMultiply(m,m2); + plMatrixApply(m,0.0,0.0,-1.0,&Xp, &Yp, &Zp); + break; + case PL_LIGHT_POINT_ANGLE: + case PL_LIGHT_POINT_DISTANCE: + case PL_LIGHT_POINT: + Xp = x; + Yp = y; + Zp = z; + break; + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_make.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_make.cpp new file mode 100644 index 0000000..0f59b28 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_make.cpp @@ -0,0 +1,537 @@ +/****************************************************************************** +Plush Version 1.2 +make.c +Object Primitives +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************* + Notes: + Most of these routines are highly unoptimized. + They could all use some work, such as more capable divisions (Box is + most notable), etc... The mapping coordinates are all set up nicely, + though. +******************************************************************************/ + +#include "plush.h" + +pl_Obj *plMakeTorus(pl_Float r1, pl_Float r2, pl_uInt divrot, pl_uInt divrad, + pl_Mat *m) { + pl_Obj *o; + pl_Vertex *v; + pl_Face *f; + pl_uInt x, y; + double ravg, rt, a, da, al, dal; + pl_Float U,V,dU,dV; + if (divrot < 3) divrot = 3; + if (divrad < 3) divrad = 3; + ravg = (r1+r2)*0.5; + rt = (r2-r1)*0.5; + o = new pl_Obj(divrad*divrot,divrad*divrot*2); + if (!o) return 0; + v = o->Vertices.Get(); + a = 0.0; + da = 2*PL_PI/divrot; + for (y = 0; y < divrot; y ++) { + al = 0.0; + dal = 2*PL_PI/divrad; + for (x = 0; x < divrad; x ++) { + v->x = (pl_Float) (cos((double) a)*(ravg + cos((double) al)*rt)); + v->z = (pl_Float) (sin((double) a)*(ravg + cos((double) al)*rt)); + v->y = (pl_Float) (sin((double) al)*rt); + v++; + al += dal; + } + a += da; + } + v = o->Vertices.Get(); + f = o->Faces.Get(); + dV = 1.0/divrad; + dU = 1.0/divrot; + U = 0; + for (y = 0; y < divrot; y ++) { + V = -0.5; + for (x = 0; x < divrad; x ++) { + f->VertexIndices[0] = v+x+y*divrad - o->Vertices.Get(); + f->MappingU[0][0] = U; + f->MappingV[0][0] = V; + f->VertexIndices[1] = v+(x+1==divrad?0:x+1)+y*divrad - o->Vertices.Get(); + f->MappingU[0][1] = U; + f->MappingV[0][1] = V+dV; + f->VertexIndices[2] = v+x+(y+1==divrot?0:(y+1)*divrad) - o->Vertices.Get(); + f->MappingU[0][2] = U+dU; + f->MappingV[0][2] = V; + f->Material = m; + f++; + f->VertexIndices[0] = v+x+(y+1==divrot?0:(y+1)*divrad) - o->Vertices.Get(); + f->MappingU[0][0] = U+dU; + f->MappingV[0][0] = V; + f->VertexIndices[1] = v+(x+1==divrad?0:x+1)+y*divrad - o->Vertices.Get(); + f->MappingU[0][1] = U; + f->MappingV[0][1] = V+dV; + f->VertexIndices[2] = v+(x+1==divrad?0:x+1)+(y+1==divrot?0:(y+1)*divrad) - o->Vertices.Get(); + f->MappingU[0][2] = U+dU; + f->MappingV[0][2] = V+dV; + f->Material = m; + f++; + V += dV; + } + U += dU; + } + o->CalculateNormals(); + return (o); +} + +pl_Obj *plMakeSphere(pl_Float r, pl_uInt divr, pl_uInt divh, pl_Mat *m) { + pl_Obj *o; + pl_Vertex *v; + pl_Face *f; + pl_uInt x, y; + double a, da, yp, ya, yda, yf; + pl_Float U,V,dU,dV; + if (divh < 3) divh = 3; + if (divr < 3) divr = 3; + o = new pl_Obj(2+(divh-2)*(divr),2*divr+(divh-3)*divr*2); + if (!o) return 0; + v = o->Vertices.Get(); + v->x = v->z = 0.0; v->y = r; v++; + v->x = v->z = 0.0; v->y = -r; v++; + ya = 0.0; + yda = PL_PI/(divh-1); + da = (PL_PI*2.0)/divr; + for (y = 0; y < divh - 2; y ++) { + ya += yda; + yp = cos((double) ya)*r; + yf = sin((double) ya)*r; + a = 0.0; + for (x = 0; x < divr; x ++) { + v->y = (pl_Float) yp; + v->x = (pl_Float) (cos((double) a)*yf); + v->z = (pl_Float) (sin((double) a)*yf); + v++; + a += da; + } + } + f = o->Faces.Get(); + v = o->Vertices.Get() + 2; + a = 0.0; + U = 0; + dU = 1.0/divr; + dV = V = 1.0/divh; + for (x = 0; x < divr; x ++) { + f->VertexIndices[0] = 0; + f->VertexIndices[1] = v + (x+1==divr ? 0 : x+1) - o->Vertices.Get(); + f->VertexIndices[2] = v + x - o->Vertices.Get(); + f->MappingU[0][0] = U; + f->MappingV[0][0] = 0; + f->MappingU[0][1] = U+dU; + f->MappingV[0][1] = V; + f->MappingU[0][2] = U; + f->MappingV[0][2] = V; + f->Material = m; + f++; + U += dU; + } + da = 1.0/(divr+1); + v = o->Vertices.Get() + 2; + for (x = 0; x < (divh-3); x ++) { + U = 0; + for (y = 0; y < divr; y ++) { + f->VertexIndices[0] = v+y - o->Vertices.Get(); + f->VertexIndices[1] = v+divr+(y+1==divr?0:y+1) - o->Vertices.Get(); + f->VertexIndices[2] = v+y+divr - o->Vertices.Get(); + f->MappingU[0][0] = U; + f->MappingV[0][0] = V; + f->MappingU[0][1] = U+dU; + f->MappingV[0][1] = V+dV; + f->MappingU[0][2] = U; + f->MappingV[0][2] = V+dV; + f->Material = m; f++; + f->VertexIndices[0] = v+y - o->Vertices.Get(); + f->VertexIndices[1] = v+(y+1==divr?0:y+1) - o->Vertices.Get(); + f->VertexIndices[2] = v+(y+1==divr?0:y+1)+divr - o->Vertices.Get(); + f->MappingU[0][0] = U; + f->MappingV[0][0] = V; + f->MappingU[0][1] = U+dU; + f->MappingV[0][1] = V; + f->MappingU[0][2] = U+dU; + f->MappingV[0][2] = V+dV; + f->Material = m; f++; + U += dU; + } + V += dV; + v += divr; + } + v = o->Vertices.Get() + o->Vertices.GetSize() - divr; + U = 0; + for (x = 0; x < divr; x ++) { + f->VertexIndices[0] = 1; + f->VertexIndices[1] = v + x - o->Vertices.Get(); + f->VertexIndices[2] = v + (x+1==divr ? 0 : x+1) - o->Vertices.Get(); + f->MappingU[0][0] = U; + f->MappingV[0][0] = 1.0; + f->MappingU[0][1] = U; + f->MappingV[0][1] = V; + f->MappingU[0][2] = U+dU; + f->MappingV[0][2] = V; + f->Material = m; + f++; + U += dU; + } + o->CalculateNormals(); + return (o); +} + +pl_Obj *plMakeDisc(pl_Float r, pl_uInt divr, pl_Mat *m) +{ + pl_Obj *o; + pl_Vertex *v; + pl_Face *f; + pl_uInt32 i; + double a, da; + + o=new pl_Obj(divr, divr); + if (!o) return NULL; + + a = 0.0; + da = (2.0*PL_PI)/divr; + v = o->Vertices.Get(); + for (i = 0; i < divr; i ++) + { + v->y = 0.0; + v->x = (pl_Float) (r*cos((double) a)); + v->z = (pl_Float)(r*sin(a)); + v->xformedx = (0.5 + (0.5*cos((double) a))); // temp + v->xformedy = (0.5 + (0.5*sin((double) a))); // use xf + v++; + a += da; + } + + v = o->Vertices.Get(); + f = o->Faces.Get(); + for (i = 0; i < divr; i ++) + { + f->VertexIndices[0] = i == divr-1 ? 0 : i + 1; + f->VertexIndices[1] = i; + f->VertexIndices[2] = 0; + f->MappingU[0][0] = v[(i==divr-1?0:i+1)].xformedx; + f->MappingV[0][0] = v[(i==divr-1?0:i+1)].xformedy; + f->MappingU[0][1] = v[i].xformedx; + f->MappingV[0][1] = v[i].xformedy; + f->MappingU[0][2] = f->MappingV[0][2] = 0.5; + f->Material = m; + f++; + } + + f->VertexIndices[0] = 0; + f->VertexIndices[1] = 2; + f->VertexIndices[2] = 1; + f->MappingU[0][0] = v[0].xformedx; + f->MappingV[0][0] = v[0].xformedy; + f->MappingU[0][1] = v[1].xformedx; + f->MappingV[0][1] = v[1].xformedy; + f->MappingU[0][2] = v[2].xformedx; + f->MappingV[0][2] = v[2].xformedy; + + f->Material = m; + + o->CalculateNormals(); + return o; +} + +pl_Obj *plMakeCylinder(pl_Float r, pl_Float h, pl_uInt divr, pl_Bool captop, + pl_Bool capbottom, pl_Mat *m) { + pl_Obj *o; + pl_Vertex *v, *topverts, *bottomverts, *topcapvert=0, *bottomcapvert=0; + pl_Face *f; + pl_uInt32 i; + double a, da; + if (divr < 3) divr = 3; + o = new pl_Obj(divr*2+((divr==3)?0:(captop?1:0)+(capbottom?1:0)), + divr*2+(divr==3 ? (captop ? 1 : 0) + (capbottom ? 1 : 0) : + (captop ? divr : 0) + (capbottom ? divr : 0))); + if (!o) return 0; + a = 0.0; + da = (2.0*PL_PI)/divr; + v = o->Vertices.Get(); + topverts = v; + for (i = 0; i < divr; i ++) { + v->y = h/2.0f; + v->x = (pl_Float) (r*cos((double) a)); + v->z = (pl_Float)(r*sin(a)); + v->xformedx = (0.5 + (0.5*cos((double) a))); // temp + v->xformedy = (0.5 + (0.5*sin((double) a))); // use xf + v++; + a += da; + } + bottomverts = v; + a = 0.0; + for (i = 0; i < divr; i ++) { + v->y = -h/2.0f; + v->x = (pl_Float) (r*cos((double) a)); + v->z = (pl_Float) (r*sin(a)); + v->xformedx = (0.5 + (0.5*cos((double) a))); + v->xformedy = (0.5 + (0.5*sin((double) a))); + v++; a += da; + } + if (captop && divr != 3) { + topcapvert = v; + v->y = h / 2.0f; + v->x = v->z = 0.0f; + v++; + } + if (capbottom && divr != 3) { + bottomcapvert = v; + v->y = -h / 2.0f; + v->x = v->z = 0.0f; + v++; + } + f = o->Faces.Get(); + for (i = 0; i < divr; i ++) { + f->VertexIndices[0] = bottomverts + i - o->Vertices.Get(); + f->VertexIndices[1] = topverts + i - o->Vertices.Get(); + f->VertexIndices[2] = bottomverts + (i == divr-1 ? 0 : i+1) - o->Vertices.Get(); + f->MappingV[0][0] = f->MappingV[0][2] = 1.0; f->MappingV[0][1] = 0; + f->MappingU[0][0] = f->MappingU[0][1] = i/(double)divr; + f->MappingU[0][2] = ((i+1))/(double)divr; + f->Material = m; f++; + f->VertexIndices[0] = bottomverts + (i == divr-1 ? 0 : i+1) - o->Vertices.Get(); + f->VertexIndices[1] = topverts + i - o->Vertices.Get(); + f->VertexIndices[2] = topverts + (i == divr-1 ? 0 : i+1) - o->Vertices.Get(); + f->MappingV[0][1] = f->MappingV[0][2] = 0; f->MappingV[0][0] = 1.0; + f->MappingU[0][0] = f->MappingU[0][2] = ((i+1))/(double)divr; + f->MappingU[0][1] = (i)/(double)divr; + f->Material = m; f++; + } + if (captop) { + if (divr == 3) { + f->VertexIndices[0] = topverts + 0 - o->Vertices.Get(); + f->VertexIndices[1] = topverts + 2 - o->Vertices.Get(); + f->VertexIndices[2] = topverts + 1 - o->Vertices.Get(); + f->MappingU[0][0] = topverts[0].xformedx; + f->MappingV[0][0] = topverts[0].xformedy; + f->MappingU[0][1] = topverts[1].xformedx; + f->MappingV[0][1] = topverts[1].xformedy; + f->MappingU[0][2] = topverts[2].xformedx; + f->MappingV[0][2] = topverts[2].xformedy; + f->Material = m; f++; + } else { + for (i = 0; i < divr; i ++) { + f->VertexIndices[0] = topverts + (i == divr-1 ? 0 : i + 1) - o->Vertices.Get(); + f->VertexIndices[1] = topverts + i - o->Vertices.Get(); + f->VertexIndices[2] = topcapvert - o->Vertices.Get(); + f->MappingU[0][0] = topverts[(i==divr-1?0:i+1)].xformedx; + f->MappingV[0][0] = topverts[(i==divr-1?0:i+1)].xformedy; + f->MappingU[0][1] = topverts[i].xformedx; + f->MappingV[0][1] = topverts[i].xformedy; + f->MappingU[0][2] = f->MappingV[0][2] = 0.5; + f->Material = m; f++; + } + } + } + if (capbottom) { + if (divr == 3) { + f->VertexIndices[0] = bottomverts + 0 - o->Vertices.Get(); + f->VertexIndices[1] = bottomverts + 1 - o->Vertices.Get(); + f->VertexIndices[2] = bottomverts + 2 - o->Vertices.Get(); + f->MappingU[0][0] = bottomverts[0].xformedx; + f->MappingV[0][0] = bottomverts[0].xformedy; + f->MappingU[0][1] = bottomverts[1].xformedx; + f->MappingV[0][1] = bottomverts[1].xformedy; + f->MappingU[0][2] = bottomverts[2].xformedx; + f->MappingV[0][2] = bottomverts[2].xformedy; + f->Material = m; f++; + } else { + for (i = 0; i < divr; i ++) { + f->VertexIndices[0] = bottomverts + i - o->Vertices.Get(); + f->VertexIndices[1] = bottomverts + (i == divr-1 ? 0 : i + 1) - o->Vertices.Get(); + f->VertexIndices[2] = bottomcapvert - o->Vertices.Get(); + f->MappingU[0][0] = bottomverts[i].xformedx; + f->MappingV[0][0] = bottomverts[i].xformedy; + f->MappingU[0][1] = bottomverts[(i==divr-1?0:i+1)].xformedx; + f->MappingV[0][1] = bottomverts[(i==divr-1?0:i+1)].xformedy; + f->MappingU[0][2] = f->MappingV[0][2] = 0.5; + f->Material = m; f++; + } + } + } + o->CalculateNormals(); + return (o); +} + +pl_Obj *plMakeCone(pl_Float r, pl_Float h, pl_uInt div, + pl_Bool cap, pl_Mat *m) { + pl_Obj *o; + pl_Vertex *v; + pl_Face *f; + pl_uInt32 i; + double a, da; + if (div < 3) div = 3; + o = new pl_Obj(div + (div == 3 ? 1 : (cap ? 2 : 1)), + div + (div == 3 ? 1 : (cap ? div : 0))); + if (!o) return 0; + v = o->Vertices.Get(); + v->x = v->z = 0; v->y = h/2; + v->xformedx = 0.5; + v->xformedy = 0.5; + v++; + a = 0.0; + da = (2.0*PL_PI)/div; + for (i = 1; i <= div; i ++) { + v->y = h/-2.0f; + v->x = (pl_Float) (r*cos((double) a)); + v->z = (pl_Float) (r*sin((double) a)); + v->xformedx = (0.5 + (cos((double) a)*0.5)); + v->xformedy = (0.5 + (sin((double) a)*0.5)); + a += da; + v++; + } + if (cap && div != 3) { + v->y = h / -2.0f; + v->x = v->z = 0.0f; + v->xformedx = 0.5; + v->xformedy = 0.5; + v++; + } + f = o->Faces.Get(); + for (i = 1; i <= div; i ++) { + f->VertexIndices[0] = 0; + f->VertexIndices[1] = o->Vertices.Get() + (i == div ? 1 : i + 1) - o->Vertices.Get(); + f->VertexIndices[2] = o->Vertices.Get() + i - o->Vertices.Get(); + f->MappingU[0][0] = o->Vertices.Get()[0].xformedx; + f->MappingV[0][0] = o->Vertices.Get()[0].xformedy; + f->MappingU[0][1] = o->Vertices.Get()[(i==div?1:i+1)].xformedx; + f->MappingV[0][1] = o->Vertices.Get()[(i==div?1:i+1)].xformedy; + f->MappingU[0][2] = o->Vertices.Get()[i].xformedx; + f->MappingV[0][2] = o->Vertices.Get()[i].xformedy; + f->Material = m; + f++; + } + if (cap) { + if (div == 3) { + f->VertexIndices[0] = 1; + f->VertexIndices[1] = 2; + f->VertexIndices[2] = 3; + f->MappingU[0][0] = o->Vertices.Get()[1].xformedx; + f->MappingV[0][0] = o->Vertices.Get()[1].xformedy; + f->MappingU[0][1] = o->Vertices.Get()[2].xformedx; + f->MappingV[0][1] = o->Vertices.Get()[2].xformedy; + f->MappingU[0][2] = o->Vertices.Get()[3].xformedx; + f->MappingV[0][2] = o->Vertices.Get()[3].xformedy; + f->Material = m; + f++; + } else { + for (i = 1; i <= div; i ++) { + f->VertexIndices[0] = div + 1; + f->VertexIndices[1] = i; + f->VertexIndices[2] = (i==div ? 1 : i+1); + f->MappingU[0][0] = o->Vertices.Get()[div+1].xformedx; + f->MappingV[0][0] = o->Vertices.Get()[div+1].xformedy; + f->MappingU[0][1] = o->Vertices.Get()[i].xformedx; + f->MappingV[0][1] = o->Vertices.Get()[i].xformedy; + f->MappingU[0][2] = o->Vertices.Get()[i==div?1:i+1].xformedx; + f->MappingV[0][2] = o->Vertices.Get()[i==div?1:i+1].xformedy; + f->Material = m; + f++; + } + } + } + o->CalculateNormals(); + return (o); +} + +static pl_uChar verts[6*6] = { + 0,4,1, 1,4,5, 0,1,2, 3,2,1, 2,3,6, 3,7,6, + 6,7,4, 4,7,5, 1,7,3, 7,1,5, 2,6,0, 4,0,6 +}; +static pl_uChar map[24*2*3] = { + 1,0, 1,1, 0,0, 0,0, 1,1, 0,1, + 0,0, 1,0, 0,1, 1,1, 0,1, 1,0, + 0,0, 1,0, 0,1, 1,0, 1,1, 0,1, + 0,0, 1,0, 0,1, 0,1, 1,0, 1,1, + 1,0, 0,1, 0,0, 0,1, 1,0, 1,1, + 1,0, 1,1, 0,0, 0,1, 0,0, 1,1 +}; + + +pl_Obj *plMakeBox(pl_Float w, pl_Float d, pl_Float h, pl_Mat *m) { + pl_uChar *mm = map; + pl_uChar *vv = verts; + pl_Obj *o; + pl_Vertex *v; + pl_Face *f; + pl_uInt x; + o = new pl_Obj(8,12); + if (!o) return 0; + v = o->Vertices.Get(); + v->x = -w/2; v->y = h/2; v->z = d/2; v++; + v->x = w/2; v->y = h/2; v->z = d/2; v++; + v->x = -w/2; v->y = h/2; v->z = -d/2; v++; + v->x = w/2; v->y = h/2; v->z = -d/2; v++; + v->x = -w/2; v->y = -h/2; v->z = d/2; v++; + v->x = w/2; v->y = -h/2; v->z = d/2; v++; + v->x = -w/2; v->y = -h/2; v->z = -d/2; v++; + v->x = w/2; v->y = -h/2; v->z = -d/2; v++; + f = o->Faces.Get(); + for (x = 0; x < 12; x ++) { + f->VertexIndices[0] = *vv++; + f->VertexIndices[1] = *vv++; + f->VertexIndices[2] = *vv++; + f->MappingU[0][0] = (pl_Float) *mm++; + f->MappingV[0][0] = (pl_Float) *mm++; + f->MappingU[0][1] = (pl_Float) *mm++; + f->MappingV[0][1] = (pl_Float) *mm++; + f->MappingU[0][2] = (pl_Float) *mm++; + f->MappingV[0][2] = (pl_Float) *mm++; + f->Material = m; + f++; + } + + o->CalculateNormals(); + return (o); +} + +pl_Obj *plMakePlane(pl_Float w, pl_Float d, pl_uInt res, pl_Mat *m) { + pl_Obj *o; + pl_Vertex *v; + pl_Face *f; + pl_uInt x, y; + o = new pl_Obj((res+1)*(res+1),res*res*2); + if (!o) return 0; + v = o->Vertices.Get(); + for (y = 0; y <= res; y ++) { + for (x = 0; x <= res; x ++) { + v->y = 0; + v->x = ((x*w)/res) - w/2; + v->z = ((y*d)/res) - d/2; + v++; + } + } + f = o->Faces.Get(); + for (y = 0; y < res; y ++) { + for (x = 0; x < res; x ++) { + f->VertexIndices[0] = x+(y*(res+1)); + f->MappingU[0][0] = (x)/(double)res; + f->MappingV[0][0] = (y)/(double)res; + f->VertexIndices[2] = x+1+(y*(res+1)); + f->MappingU[0][2] = ((x+1))/(double)res; + f->MappingV[0][2] = (y)/(double)res; + f->VertexIndices[1] = x+((y+1)*(res+1)); + f->MappingU[0][1] = (x)/(double)res; + f->MappingV[0][1] = ((y+1))/(double)res; + f->Material = m; + f++; + f->VertexIndices[0] = x+((y+1)*(res+1)); + f->MappingU[0][0] = (x)/(double)res; + f->MappingV[0][0] = ((y+1))/(double)res; + f->VertexIndices[2] = x+1+(y*(res+1)); + f->MappingU[0][2] = ((x+1))/(double)res; + f->MappingV[0][2] = (y)/(double)res; + f->VertexIndices[1] = x+1+((y+1)*(res+1)); + f->MappingU[0][1] = ((x+1))/(double)res; + f->MappingV[0][1] = ((y+1))/(double)res; + f->Material = m; + f++; + } + } + o->CalculateNormals(); + return (o); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_math.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_math.cpp new file mode 100644 index 0000000..f338a90 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_math.cpp @@ -0,0 +1,67 @@ +/****************************************************************************** +Plush Version 1.2 +math.c +Math and Matrix Control +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************/ + +#include "plush.h" + +void plMatrixRotate(pl_Float matrix[], pl_uChar m, pl_Float Deg) { + pl_uChar m1, m2; + double c,s; + double d= Deg * PL_PI / 180.0; + memset(matrix,0,sizeof(pl_Float)*16); + matrix[((m-1)<<2)+m-1] = matrix[15] = 1.0; + m1 = (m % 3); + m2 = ((m1+1) % 3); + c = cos(d); s = sin(d); + matrix[(m1<<2)+m1]=(pl_Float)c; matrix[(m1<<2)+m2]=(pl_Float)s; + matrix[(m2<<2)+m2]=(pl_Float)c; matrix[(m2<<2)+m1]=(pl_Float)-s; +} + +void plMatrixTranslate(pl_Float m[], pl_Float x, pl_Float y, pl_Float z) { + memset(m,0,sizeof(pl_Float)*16); + m[0] = m[4+1] = m[8+2] = m[12+3] = 1.0; + m[0+3] = x; m[4+3] = y; m[8+3] = z; +} + +void plMatrixMultiply(pl_Float *dest, const pl_Float src[]) { + pl_Float temp[16]; + pl_uInt i; + memcpy(temp,dest,sizeof(pl_Float)*16); + for (i = 0; i < 16; i += 4) { + *dest++ = src[i+0]*temp[(0<<2)+0]+src[i+1]*temp[(1<<2)+0]+ + src[i+2]*temp[(2<<2)+0]+src[i+3]*temp[(3<<2)+0]; + *dest++ = src[i+0]*temp[(0<<2)+1]+src[i+1]*temp[(1<<2)+1]+ + src[i+2]*temp[(2<<2)+1]+src[i+3]*temp[(3<<2)+1]; + *dest++ = src[i+0]*temp[(0<<2)+2]+src[i+1]*temp[(1<<2)+2]+ + src[i+2]*temp[(2<<2)+2]+src[i+3]*temp[(3<<2)+2]; + *dest++ = src[i+0]*temp[(0<<2)+3]+src[i+1]*temp[(1<<2)+3]+ + src[i+2]*temp[(2<<2)+3]+src[i+3]*temp[(3<<2)+3]; + } +} + +void plMatrixApply(pl_Float *m, pl_Float x, pl_Float y, pl_Float z, + pl_Float *outx, pl_Float *outy, pl_Float *outz) { + *outx = x*m[0] + y*m[1] + z*m[2] + m[3]; + *outy = x*m[4] + y*m[5] + z*m[6] + m[7]; + *outz = x*m[8] + y*m[9] + z*m[10] + m[11]; +} + +pl_Float plDotProduct(pl_Float x1, pl_Float y1, pl_Float z1, + pl_Float x2, pl_Float y2, pl_Float z2) { + return ((x1*x2)+(y1*y2)+(z1*z2)); +} + +void plNormalizeVector(pl_Float *x, pl_Float *y, pl_Float *z) { + double length; + length = (*x)*(*x)+(*y)*(*y)+(*z)*(*z); + if (length > 0.0000000001) { + pl_Float t = (pl_Float)sqrt(length); + *x /= t; + *y /= t; + *z /= t; + } else *x = *y = *z = 0.0; +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_obj.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_obj.cpp new file mode 100644 index 0000000..1bfa98c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_obj.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** +Plush Version 1.2 +obj.c +Object control +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************/ + +#include "plush.h" + +void pl_Obj::Scale(pl_Float s) { + int i = Vertices.GetSize(); + pl_Vertex *v = Vertices.Get(); + while (i--) { + v->x *= s; v->y *= s; v->z *= s; v++; + } + for (i = 0; i < Children.GetSize(); i ++) + if (Children.Get(i)) Children.Get(i)->Scale(s); +} + +void pl_Obj::Stretch(pl_Float x, pl_Float y, pl_Float z) { + int i = Vertices.GetSize(); + pl_Vertex *v = Vertices.Get(); + while (i--) { + v->x *= x; v->y *= y; v->z *= z; v++; + } + for (i = 0; i < Children.GetSize(); i ++) + if (Children.Get(i)) Children.Get(i)->Stretch(x,y,z); +} + +void pl_Obj::Translate(pl_Float x, pl_Float y, pl_Float z) { + int i = Vertices.GetSize(); + pl_Vertex *v = Vertices.Get(); + while (i--) { + v->x += x; v->y += y; v->z += z; v++; + } + for (i = 0; i < Children.GetSize(); i ++) + if (Children.Get(i)) Children.Get(i)->Translate(x,y,z); +} + +void pl_Obj::FlipNormals() { + int i = Vertices.GetSize(); + pl_Vertex *v = Vertices.Get(); + pl_Face *f = Faces.Get(); + while (i--) { + v->nx = - v->nx; v->ny = - v->ny; v->nz = - v->nz; v++; + } + i = Faces.GetSize(); + while (i--) { + f->nx = - f->nx; f->ny = - f->ny; f->nz = - f->nz; + f++; + } + for (i = 0; i < Children.GetSize(); i ++) + if (Children.Get(i)) Children.Get(i)->FlipNormals(); +} + + +pl_Obj *pl_Obj::Clone() { + int i; + pl_Obj *out; + if (!(out = new pl_Obj(Vertices.GetSize(),Faces.GetSize()))) return 0; + for (i = 0; i < Children.GetSize(); i ++) + out->Children.Add(Children.Get(i) ? Children.Get(i)->Clone() : NULL); + + out->Xa = Xa; out->Ya = Ya; out->Za = Za; + out->Xp = Xp; out->Yp = Yp; out->Zp = Zp; + out->GenMatrix = GenMatrix; + memcpy(out->Matrix,Matrix,sizeof(Matrix)); + memcpy(out->Vertices.Get(), Vertices.Get(), sizeof(pl_Vertex) * Vertices.GetSize()); + memcpy(out->Faces.Get(),Faces.Get(),sizeof(pl_Face) * Faces.GetSize()); + return out; +} + +void pl_Obj::SetMaterial(pl_Mat *m, pl_Bool th) { + pl_sInt32 i = Faces.GetSize(); + pl_Face *f = Faces.Get(); + while (i--) (f++)->Material = m; + if (th) for (i = 0; i < Children.GetSize(); i++) + if (Children.Get(i)) Children.Get(i)->SetMaterial(m,true); +} + +void pl_Obj::CalculateNormals() { + int i; + pl_Vertex *v = Vertices.Get(); + pl_Face *f = Faces.Get(); + double x1, x2, y1, y2, z1, z2; + i = Vertices.GetSize(); + while (i--) { + v->nx = 0.0; v->ny = 0.0; v->nz = 0.0; + v++; + } + i = Faces.GetSize(); + while (i--) { + pl_Vertex *vp=Vertices.Get(); + pl_Vertex *fVertices[3] = { + vp+f->VertexIndices[0], + vp+f->VertexIndices[1], + vp+f->VertexIndices[2], + }; + x1 = fVertices[0]->x-fVertices[1]->x; + x2 = fVertices[0]->x-fVertices[2]->x; + y1 = fVertices[0]->y-fVertices[1]->y; + y2 = fVertices[0]->y-fVertices[2]->y; + z1 = fVertices[0]->z-fVertices[1]->z; + z2 = fVertices[0]->z-fVertices[2]->z; + f->nx = (pl_Float) (y1*z2 - z1*y2); + f->ny = (pl_Float) (z1*x2 - x1*z2); + f->nz = (pl_Float) (x1*y2 - y1*x2); + plNormalizeVector(&f->nx, &f->ny, &f->nz); + fVertices[0]->nx += f->nx; + fVertices[0]->ny += f->ny; + fVertices[0]->nz += f->nz; + fVertices[1]->nx += f->nx; + fVertices[1]->ny += f->ny; + fVertices[1]->nz += f->nz; + fVertices[2]->nx += f->nx; + fVertices[2]->ny += f->ny; + fVertices[2]->nz += f->nz; + f++; + } + v = Vertices.Get(); + i = Vertices.GetSize(); + do { + plNormalizeVector(&v->nx, &v->ny, &v->nz); + v++; + } while (--i); + + for (i = 0; i < Children.GetSize(); i ++) + if (Children.Get(i)) Children.Get(i)->CalculateNormals(); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_pf_tex.h b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_pf_tex.h new file mode 100644 index 0000000..bf08d25 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_pf_tex.h @@ -0,0 +1,446 @@ + +static void +#ifdef PL_PF_MULTITEX +PLMTexTri +#else +PLTexTri +#endif +(LICE_pixel *gmem, int swidth, pl_Face *TriFace, pl_ZBuffer *zbuf, int zfb_width, bool zb_update, + int solidalpha, int solidcomb, LICE_IBitmap *tex, pl_Float *texscales, int texalpha, int texcomb, int texmap +#ifdef PL_PF_MULTITEX + , LICE_IBitmap *tex2, int tex2alpha, int tex2comb, int texmap2 +#endif + + ) +{ + pl_sInt32 C1[3], C3[3], C2[3], dC2[3]={0}, dCL[3]={0},dC1[3]={0}, dX2=0, dX1=0; + pl_Float dZ2=0, dZL=0,dZ1=0; + + PUTFACE_SORT(); + + + solidcomb&=LICE_BLIT_MODE_MASK; + if (solidcomb == 0 && solidalpha == 256) solidcomb=-1; + else if (solidalpha==0) solidcomb=-2; // ignore + int solidalpha2=(256-solidalpha)*256; + + + pl_Float dU2=0,dV2=0,dUL=0,dVL=0; + pl_Float dU1=0, dV1=0; + bool bilinear = (texcomb&LICE_BLIT_FILTER_MASK)==LICE_BLIT_FILTER_BILINEAR; + texcomb&=LICE_BLIT_MODE_MASK|LICE_BLIT_USE_ALPHA; + if (texcomb==LICE_BLIT_MODE_COPY && texalpha==256) texcomb=-1; + else if (texalpha==0) texcomb=-2; + int texalpha2=(256-texalpha); + + int tex_rowspan=0; + LICE_pixel *texture=NULL; + int tex_w=16,tex_h=16; + +#if defined(PLUSH_NO_SOLIDGOURAUD) || defined(PLUSH_NO_TEXTURE) + if (tex) +#endif + { + texture=tex->getBits(); + tex_rowspan = tex->getRowSpan(); + if (tex->isFlipped()) + { + texture += tex_rowspan*(tex->getHeight()-1); + tex_rowspan=-tex_rowspan; + } + tex_w=tex->getWidth(); + tex_h=tex->getHeight(); + tex_rowspan *= 4; + } + pl_sInt32 MappingU_Max=tex_w<<16, MappingV_Max=tex_h<<16; + texscales[0]*=MappingU_Max; + texscales[1]*=MappingV_Max; + tex_w *= 4; + +#ifdef PL_PF_MULTITEX + + pl_Float dU2_2=0,dV2_2=0,dUL_2=0,dVL_2=0; + pl_Float dU1_2=0, dV1_2=0; + int tex_w_2=16,tex_h_2=16; + bool bilinear2 = (tex2comb&LICE_BLIT_FILTER_MASK)==LICE_BLIT_FILTER_BILINEAR; + tex2comb&=LICE_BLIT_MODE_MASK|LICE_BLIT_USE_ALPHA; + if (tex2comb==LICE_BLIT_MODE_COPY && tex2alpha==256) tex2comb=-1; + else if (tex2alpha==0) tex2comb=-2; + int tex2alpha2=(256-tex2alpha); + LICE_pixel *texture_2=NULL; + int tex_rowspan_2 = 0; + pl_sInt32 MappingU_Max_2, MappingV_Max_2; + +#ifdef PLUSH_NO_TEXTURE + if (tex2) +#endif + { + tex_w_2=tex2->getWidth(); + tex_h_2=tex2->getHeight(); + texture_2=tex2->getBits(); + tex_rowspan_2 = tex2->getRowSpan(); + if (tex2->isFlipped()) + { + texture_2 += tex_rowspan_2*(tex2->getHeight()-1); + tex_rowspan_2=-tex_rowspan_2; + } + tex_rowspan_2 *= 4; + } + MappingU_Max_2=tex_w_2<<16; + MappingV_Max_2=tex_h_2<<16; + texscales[2]*=MappingU_Max_2; + texscales[3]*=MappingV_Max_2; + tex_w_2 *= 4; +#endif + + + pl_uChar nm = TriFace->Material->PerspectiveCorrect; + pl_uChar nmb = 0; while (nm) { nmb++; nm >>= 1; } + nmb = plMin(6,nmb); + nm = 1<Scrz[i0]; + pl_Float Z2 = TriFace->Scrz[i1]; + pl_Float Z3 = TriFace->Scrz[i2]; + + pl_Float MappingU1=TriFace->MappingU[texmap][i0]*texscales[0]*Z1; + pl_Float MappingV1=TriFace->MappingV[texmap][i0]*texscales[1]*Z1; + pl_Float MappingU2=TriFace->MappingU[texmap][i1]*texscales[0]*Z2; + pl_Float MappingV2=TriFace->MappingV[texmap][i1]*texscales[1]*Z2; + pl_Float MappingU3=TriFace->MappingU[texmap][i2]*texscales[0]*Z3; + pl_Float MappingV3=TriFace->MappingV[texmap][i2]*texscales[1]*Z3; + +#ifdef PL_PF_MULTITEX + pl_Float MappingU1_2=TriFace->MappingU[texmap2][i0]*texscales[2]*Z1; + pl_Float MappingV1_2=TriFace->MappingV[texmap2][i0]*texscales[3]*Z1; + pl_Float MappingU2_2=TriFace->MappingU[texmap2][i1]*texscales[2]*Z2; + pl_Float MappingV2_2=TriFace->MappingV[texmap2][i1]*texscales[3]*Z2; + pl_Float MappingU3_2=TriFace->MappingU[texmap2][i2]*texscales[2]*Z3; + pl_Float MappingV3_2=TriFace->MappingV[texmap2][i2]*texscales[3]*Z3; +#endif + + int a; + for(a=0;a<3;a++) + { + C1[a] = (pl_sInt32) (TriFace->Shades[i0][a]*(1<<24)); + C2[a] = (pl_sInt32) (TriFace->Shades[i1][a]*(1<<24)); + C3[a] = (pl_sInt32) (TriFace->Shades[i2][a]*(1<<24)); + } + + pl_Float U1, U2; + U1 = U2 = MappingU1; + pl_Float V1,V2; + V1 = V2 = MappingV1; +#ifdef PL_PF_MULTITEX + pl_Float U1_2, U2_2; + U1_2 = U2_2 = MappingU1_2; + pl_Float V1_2, V2_2; + V1_2 = V2_2 = MappingV1_2; +#endif + + pl_sInt32 X2,X1; + X2 = X1 = Scrx[i0]; + pl_sInt32 Y0 = Scry[i0]; + pl_sInt32 Y1 = Scry[i1]; + pl_sInt32 Y2 = Scry[i2]; + + + { + pl_sInt32 dY = Y2-Y0; + if (dY) { + dX2 = (Scrx[i2] - X1) / dY; + + pl_Float v = 1.0/dY; + for(a=0;a<3;a++) dC2[a] = (pl_sInt32) ((C3[a] - C1[a]) * v); + dZ2 = (Z3 - Z1) * v; + dU2 = (MappingU3 - U1) * v; + dV2 = (MappingV3 - V1) * v; + #ifdef PL_PF_MULTITEX + dU2_2 = (MappingU3_2 - U1_2) * v; + dV2_2 = (MappingV3_2 - V1_2) * v; + #endif + } + dY = Y1-Y0; + if (dY) { + dX1 = (Scrx[i1] - X1) / dY; + pl_Float v=1.0/dY; + dZ1 = (Z2 - Z1) * v; + for(a=0;a<3;a++) dC1[a] = (pl_sInt32) ((C2[a] - C1[a]) * v); + dU1 = (MappingU2 - U1) * v; + dV1 = (MappingV2 - V1) * v; + #ifdef PL_PF_MULTITEX + dU1_2 = (MappingU2_2 - U1_2) * v; + dV1_2 = (MappingV2_2 - V1_2) * v; + #endif + if (dX2 < dX1) { + SWAP(dX1,dX2,pl_sInt32); + SWAP(dU1,dU2,pl_Float); + SWAP(dV1,dV2,pl_Float); + #ifdef PL_PF_MULTITEX + SWAP(dU1_2,dU2_2,pl_Float); + SWAP(dV1_2,dV2_2,pl_Float); + #endif + SWAP(dZ1,dZ2,pl_Float); + for(a=0;a<3;a++) + SWAP(dC1[a],dC2[a],pl_sInt32); + stat = 2; + } else stat = 1; + Z2 = Z1; + C2[0] = C1[0]; + C2[1] = C1[1]; + C2[2] = C1[2]; + + } else { + if (Scrx[i1] > X1) { + X2 = Scrx[i1]; + U2 = MappingU2; + V2 = MappingV2; + #ifdef PL_PF_MULTITEX + U2_2 = MappingU2_2; + V2_2 = MappingV2_2; + #endif + stat = 2|4; + } else { + X1 = Scrx[i1]; + SWAP(Z1,Z2,pl_Float) + for(a=0;a<3;a++) SWAP(C1[a],C2[a],pl_sInt32); + U1 = MappingU2; + V1 = MappingV2; + #ifdef PL_PF_MULTITEX + U1_2 = MappingU2_2; + V1_2 = MappingV2_2; + #endif + stat = 1|8; + } + } + pl_sInt32 tmp = (dX1-dX2)*dY; + if (tmp) { + pl_Float v=(1<>XPOS_BITS; + pl_sInt32 Xlen = ((X2+(1<<(XPOS_BITS-1)))>>XPOS_BITS) - XL1; + if (Xlen > 0) { + pl_sInt32 iUL, iVL, idUL, idVL, iULnext, iVLnext; + pl_Float UL = U1; + pl_Float VL = V1; +#ifdef PL_PF_MULTITEX + pl_sInt32 iUL_2, iVL_2, idUL_2, idVL_2, iULnext_2, iVLnext_2; + pl_Float UL_2 = U1_2; + pl_Float VL_2 = V1_2; +#endif + pl_sInt32 CL[3] = {C1[0],C1[1], C1[2]}; + pl_Float pZL,ZL; + pl_Float t = 1.0f / (pZL = ZL = Z1); + gmem += XL1; + zbuf += XL1; + + XL1 += Xlen; // update to new line end pos so we can adjust gmem/zbuf later + iULnext = ((pl_sInt32) (UL*t)); + iVLnext = ((pl_sInt32) (VL*t)); +#ifdef PL_PF_MULTITEX + iULnext_2 = ((pl_sInt32) (UL_2*t)); + iVLnext_2 = ((pl_sInt32) (VL_2*t)); +#endif + do { + UL += dUL; + VL += dVL; + iUL = iULnext; + iVL = iVLnext; + pZL += pdZL; + t = 1.0f/pZL; + iULnext = ((pl_sInt32) (UL*t)); + iVLnext = ((pl_sInt32) (VL*t)); + idUL = (iULnext - iUL)>>nmb; + idVL = (iVLnext - iVL)>>nmb; + if (idUL>MappingU_Max) idUL=MappingU_Max; + else if (idUL<-MappingU_Max) idUL=-MappingU_Max; + if (idVL>MappingV_Max) idVL=MappingV_Max; + else if (idVL<-MappingV_Max) idVL=-MappingV_Max; + + // todo: this is slow as shit, should we force textures to be powers of two? hehe + if (iUL<0) do iUL+=MappingU_Max; while (iUL<0); + else while (iUL >= MappingU_Max) iUL-=MappingU_Max; + if (iVL<0) do iVL+=MappingV_Max; while (iVL<0); + else while (iVL >= MappingV_Max) iVL-=MappingV_Max; + +#ifdef PL_PF_MULTITEX + UL_2 += dUL_2; + VL_2 += dVL_2; + iUL_2 = iULnext_2; + iVL_2 = iVLnext_2; + iULnext_2 = ((pl_sInt32) (UL_2*t)); + iVLnext_2 = ((pl_sInt32) (VL_2*t)); + idUL_2 = (iULnext_2 - iUL_2)>>nmb; + idVL_2 = (iVLnext_2 - iVL_2)>>nmb; + if (idUL_2>MappingU_Max_2) idUL_2=MappingU_Max_2; + else if (idUL_2<-MappingU_Max_2) idUL_2=-MappingU_Max_2; + if (idVL_2>MappingV_Max_2) idVL_2=MappingV_Max_2; + else if (idVL_2<-MappingV_Max_2) idVL_2=-MappingV_Max_2; + + // todo: this is slow as shit, should we force textures to be powers of two? hehe + if (iUL_2<0) do iUL_2+=MappingU_Max_2; while (iUL_2<0); + else while (iUL_2 >= MappingU_Max_2) iUL_2-=MappingU_Max_2; + if (iVL_2<0) do iVL_2+=MappingV_Max_2; while (iVL_2<0); + else while (iVL_2 >= MappingV_Max_2) iVL_2-=MappingV_Max_2; + +#endif + + pl_uInt n = nm; + Xlen -= n; + if (Xlen < 0) n += Xlen; + if (zfb_width) do { + if (*zbuf < ZL) { + if (zb_update) *zbuf = (pl_ZBuffer) ZL; + + #ifdef PL_PF_MULTITEX + TextureMakePixel2((LICE_pixel_chan *)gmem,solidcomb,solidalpha,solidalpha2,CL, bilinear, + iUL,iVL,tex_w,tex_h,texture,tex_rowspan,texcomb,texalpha,texalpha2, + bilinear2, iUL_2,iVL_2, + tex_w_2,tex_h_2, + texture_2,tex_rowspan_2,tex2comb,tex2alpha,tex2alpha2); + #else + TextureMakePixel((LICE_pixel_chan *)gmem,solidcomb,solidalpha,solidalpha2,CL, bilinear, iUL,iVL, + tex_w,tex_h, + texture,tex_rowspan,texcomb,texalpha,texalpha2); + #endif + + } + zbuf++; + gmem++; + ZL += dZL; + CL[0] += dCL[0]; + CL[1] += dCL[1]; + CL[2] += dCL[2]; + iUL += idUL; + iVL += idVL; + + if (iUL<0) iUL+=MappingU_Max; + else if (iUL >= MappingU_Max) iUL -= MappingU_Max; + if (iVL<0) iVL+=MappingV_Max; + else if (iVL >= MappingV_Max) iVL -= MappingV_Max; +#ifdef PL_PF_MULTITEX + iUL_2 += idUL_2; + iVL_2 += idVL_2; + + if (iUL_2<0) iUL_2+=MappingU_Max_2; + else if (iUL_2 >= MappingU_Max_2) iUL_2 -= MappingU_Max_2; + if (iVL_2<0) iVL_2+=MappingV_Max_2; + else if (iVL_2 >= MappingV_Max_2) iVL_2 -= MappingV_Max_2; +#endif + } while (--n); + else do { + + #ifdef PL_PF_MULTITEX + TextureMakePixel2((LICE_pixel_chan *)gmem,solidcomb,solidalpha,solidalpha2,CL, bilinear, + iUL,iVL,tex_w,tex_h,texture,tex_rowspan,texcomb,texalpha,texalpha2, + bilinear2, iUL_2,iVL_2, + tex_w_2,tex_h_2, + texture_2,tex_rowspan_2,tex2comb,tex2alpha,tex2alpha2); + #else + TextureMakePixel((LICE_pixel_chan *)gmem,solidcomb,solidalpha,solidalpha2,CL, bilinear, iUL,iVL, + tex_w,tex_h, + texture,tex_rowspan,texcomb,texalpha,texalpha2); + #endif + + + gmem++; + CL[0] += dCL[0]; + CL[1] += dCL[1]; + CL[2] += dCL[2]; + iUL += idUL; + iVL += idVL; + + if (iUL<0) iUL+=MappingU_Max; + else if (iUL >= MappingU_Max) iUL -= MappingU_Max; + if (iVL<0) iVL+=MappingV_Max; + else if (iVL >= MappingV_Max) iVL -= MappingV_Max; + +#ifdef PL_PF_MULTITEX + iUL_2 += idUL_2; + iVL_2 += idVL_2; + + if (iUL_2<0) iUL_2+=MappingU_Max_2; + else if (iUL_2 >= MappingU_Max_2) iUL_2 -= MappingU_Max_2; + if (iVL_2<0) iVL_2+=MappingV_Max_2; + else if (iVL_2 >= MappingV_Max_2) iVL_2 -= MappingV_Max_2; +#endif + + } while (--n); + } while (Xlen > 0); + gmem += swidth-XL1; + zbuf += zfb_width-XL1; + } else { // xlen <=0 ,no drawing + gmem += swidth; + zbuf += zfb_width; + } + Z1 += dZ1; + U1 += dU1; + V1 += dV1; +#ifdef PL_PF_MULTITEX + U1_2 += dU1_2; + V1_2 += dV1_2; +#endif + X1 += dX1; + X2 += dX2; + C1[0] += dC1[0]; + C1[1] += dC1[1]; + C1[2] += dC1[2]; + } +} + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_putface.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_putface.cpp new file mode 100644 index 0000000..8a83de5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_putface.cpp @@ -0,0 +1,713 @@ +#include "plush.h" +#include "../lice/lice_combine.h" + +//#define PLUSH_NO_SOLIDFLAT // make non-texturemapped flat shading optimized engine +//#define PLUSH_NO_SOLIDGOURAUD // disable non-texturemapped gouraud optimized engine +//#define PLUSH_NO_TEXTURE // disable single-texture optimized engine +//#define PLUSH_NO_MULTITEXTURE // disable multitexture (this can do any of em) +#define XPOS_BITS 19 // allows 2^13 max screen width, or 8192px + +#define SWAP(a,b,t) { t ____tmp=(a); (a)=(b); (b)=____tmp; } + +#define PUTFACE_SORT() \ + int i0 = 0; int i1 = 1; int i2 = 2; int stat; \ + if (TriFace->Scry[0] > TriFace->Scry[1]) { i0 = 1; i1 = 0; } \ + if (TriFace->Scry[i0] > TriFace->Scry[2]) { SWAP(i0,i2,int); } \ + if (TriFace->Scry[i1] > TriFace->Scry[i2]) { SWAP(i1,i2,int); } \ + int Scrx[3] = {(int)(TriFace->Scrx[0]*(1<Scrx[1]*(1<Scrx[2]*(1<Scry[0]+0.5), (int) (TriFace->Scry[1]+0.5), (int) (TriFace->Scry[2]+0.5)}; \ + +#define DO_STAT_XDELTAS \ + if (stat & 1) { \ + dX1 = (Scrx[i2]-(X1 = Scrx[i1]))/dY; \ + if (stat & 8) dX2 = (Scrx[i2]-(X2 = Scrx[i0]))/dY; \ + } \ + else if (stat & 2) { \ + dX2 = (Scrx[i2]-(X2 = Scrx[i1]))/dY; \ + if (stat & 4) dX1 = (Scrx[i2]-(X1 = Scrx[i0]))/dY; \ + } + + +static inline void OverlayBlend(int &red, int &green, int &blue, int &alpha, int r, int g, int b, int a, int usea) +{ + int da=(256-usea)*128; + int srcr = r*usea+da, srcg = g*usea+da, srcb = b*usea+da, srca = usea*a + da; + red = ( red*( (red*(32768-srcr))/256 + srcr ) )/32768; + green = ( green*( (green*(32768-srcg))/256 + srcg ) )/32768; + blue = ( blue*( (blue*(32768-srcb))/256 + srcb ) )/32768; + alpha = ( alpha*( (alpha*(32768-srca))/256 + srca ) )/32768; +} + +static inline void MulBlend(int &red, int &green, int &blue, int &alpha, int r, int g, int b, int a, int ta) +{ + int ta2=(256-ta)*256; + red = (r*ta*red + red*ta2)/65536; + green = (g*ta*green + green*ta2)/65536; + blue = (b*ta*blue + blue*ta2)/65536; + alpha = (a*ta*alpha + alpha*ta2)/65536; +} + + +static inline void AdjustHSV(int &red, int &green, int &blue, int r, int g, int b, int texalpha) +{ + int h,s,v; + __LICE_RGB2HSV(red,green,blue,&h,&s,&v); + h+=(((r+r/2) - 192) * texalpha)/256; + if (h<0)h+=384; + else if (h>=384) h-=384; + s+=((g-128)*texalpha)/256; + if (s&~0xff) + { + if (s<0)s=0; + else s=255; + } + v+=((b-128)*texalpha)/256; + if (v&~0xff) + { + if (v<0)v=0; + else v=255; + } + __LICE_HSV2RGB(h,s,v,&red,&green,&blue); +} + +static inline void DodgeBlend(int &red, int &green, int &blue, int &alpha, int r, int g, int b, int a, int ta) +{ + int src_r = 256-r*ta/256; + int src_g = 256-g*ta/256; + int src_b = 256-b*ta/256; + int src_a = 256-(a*ta)/256; + + red = src_r > 1 ? 256*red / src_r : 256*red; + green = src_g > 1 ? 256*green / src_g : 256*green; + blue = src_b > 1 ? 256*blue / src_b : 256*blue; + alpha = src_a > 1 ? 256*alpha / src_a : 256*alpha; +} + + +static void inline DoTextureCombine(int texcomb, int r, int g, int b, int a, int &red, int &green, int &blue, int &alpha, int texalpha, int texalpha2) +{ + switch (texcomb) + { + case LICE_BLIT_MODE_COPY: + red = (r*texalpha + red*texalpha2) >> 8; + green = (g*texalpha + green*texalpha2) >> 8; + blue = (b*texalpha + blue*texalpha2) >> 8; + alpha = (a*texalpha + alpha*texalpha2) >> 8; + break; + case LICE_BLIT_MODE_ADD: + red += (r*texalpha) >> 8; + green += (g*texalpha) >> 8; + blue += (b*texalpha) >> 8; + alpha += (a*texalpha) >> 8; + break; + case LICE_BLIT_MODE_MUL: + MulBlend(red,green,blue,alpha,r,g,b,a,texalpha); + break; + case LICE_BLIT_MODE_DODGE: + DodgeBlend(red,green,blue,alpha,r,g,b,a,texalpha); + + break; + case LICE_BLIT_MODE_OVERLAY: + OverlayBlend(red,green,blue,alpha,r,g,b,a, texalpha); + break; + case LICE_BLIT_MODE_HSVADJ: + AdjustHSV(red,green,blue,r,g,b,texalpha); + break; + case LICE_BLIT_MODE_COPY|LICE_BLIT_USE_ALPHA: + { + int ta=(texalpha*(a+1)); + int ta2=(65536-ta); + red = (r*ta + red*ta2) >> 16; + green = (g*ta + green*ta2) >> 16; + blue = (b*ta + blue*ta2) >> 16; + alpha = (a*ta + alpha*ta2) >> 16; + } + break; + case LICE_BLIT_MODE_ADD|LICE_BLIT_USE_ALPHA: + { + int ta=(texalpha*(a+1)); + red += (r*ta) >> 16; + green += (g*ta) >> 16; + blue += (b*ta) >> 16; + alpha += (a*ta) >> 16; + } + break; + case LICE_BLIT_MODE_DODGE|LICE_BLIT_USE_ALPHA: + { + int ta=(texalpha*(a+1))/256; + DodgeBlend(red,green,blue,alpha,r,g,b,a,ta); + } + break; + case LICE_BLIT_MODE_MUL|LICE_BLIT_USE_ALPHA: + MulBlend(red,green,blue,alpha,r,g,b,a,(texalpha*(a+1))/256); + break; + case LICE_BLIT_MODE_OVERLAY|LICE_BLIT_USE_ALPHA: + OverlayBlend(red,green,blue,alpha,r,g,b,a, (texalpha*(a+1))/256); + break; + case LICE_BLIT_MODE_HSVADJ|LICE_BLIT_USE_ALPHA: + AdjustHSV(red,green,blue,r,g,b,(texalpha*(a+1))/256); + break; + case -2: + break; + default: + red=r; green=g; blue=b; alpha=a; + break; + } +} + + +static void inline TextureMakePixelSolidCombine(int &red, int &green, int &blue, int &alpha, + pl_sInt32 *CL, int solidcomb, int solidalpha, + int solidalpha2, + LICE_pixel_chan *gmemptr) +{ + + switch (solidcomb) + { + case LICE_BLIT_MODE_COPY: + red = ((CL[0]>>8)*solidalpha + gmemptr[LICE_PIXEL_R]*solidalpha2)>>16; + green = ((CL[1]>>8)*solidalpha + gmemptr[LICE_PIXEL_G]*solidalpha2)>>16; + blue = ((CL[2]>>8)*solidalpha + gmemptr[LICE_PIXEL_B]*solidalpha2)>>16; + alpha = solidalpha; + break; + case LICE_BLIT_MODE_ADD: + red = gmemptr[LICE_PIXEL_R] + (((CL[0]>>8)*solidalpha)>>16); + green = gmemptr[LICE_PIXEL_G] + (((CL[1]>>8)*solidalpha)>>16); + blue = gmemptr[LICE_PIXEL_B] + (((CL[2]>>8)*solidalpha)>>16); + alpha = gmemptr[LICE_PIXEL_A] + solidalpha; + break; + case LICE_BLIT_MODE_DODGE: + red=gmemptr[LICE_PIXEL_R]; + green=gmemptr[LICE_PIXEL_G]; + blue=gmemptr[LICE_PIXEL_B]; + alpha=gmemptr[LICE_PIXEL_A]; + DodgeBlend(red,green,blue,alpha,CL[0]>>16,CL[1]>>16,CL[2]>>16,solidalpha,solidalpha); + break; + case LICE_BLIT_MODE_MUL: + red=gmemptr[LICE_PIXEL_R]; + green=gmemptr[LICE_PIXEL_G]; + blue=gmemptr[LICE_PIXEL_B]; + alpha=gmemptr[LICE_PIXEL_A]; + MulBlend(red,green,blue,alpha,CL[0]>>16,CL[1]>>16,CL[2]>>16,solidalpha,solidalpha); + break; + case LICE_BLIT_MODE_OVERLAY: + red=gmemptr[LICE_PIXEL_R]; + green=gmemptr[LICE_PIXEL_G]; + blue=gmemptr[LICE_PIXEL_B]; + alpha=gmemptr[LICE_PIXEL_A]; + OverlayBlend(red,green,blue,alpha,CL[0]>>16,CL[1]>>16,CL[2]>>16,solidalpha, solidalpha); + break; + case LICE_BLIT_MODE_HSVADJ: + red=gmemptr[LICE_PIXEL_R]; + green=gmemptr[LICE_PIXEL_G]; + blue=gmemptr[LICE_PIXEL_B]; + alpha=gmemptr[LICE_PIXEL_A]; + AdjustHSV(red,green,blue,CL[0]>>16,CL[1]>>16,CL[2]>>16,solidalpha); + break; + case -2: + red=gmemptr[LICE_PIXEL_R]; + green=gmemptr[LICE_PIXEL_G]; + blue=gmemptr[LICE_PIXEL_B]; + alpha=gmemptr[LICE_PIXEL_A]; + break; + default: + red=CL[0]>>16; + green=CL[1]>>16; + blue=CL[2]>>16; + alpha=solidalpha; + break; + } +} + + +static void inline TextureMakePixel2(LICE_pixel_chan *gmemptr, + int solidcomb, int solidalpha, int solidalpha2, + pl_sInt32 *CL, + bool bilinear, + pl_sInt32 iUL, pl_sInt32 iVL, + pl_sInt32 texwidth, pl_sInt32 texheight, + LICE_pixel *texture, int tex_rowspan, + int texcomb, + int texalpha, + int texalpha2, + bool bilinear2, + pl_sInt32 iUL_2, pl_sInt32 iVL_2, + pl_sInt32 texwidth_2, pl_sInt32 texheight_2, + LICE_pixel *texture2, int tex_rowspan_2, + int tex2comb, + int tex2alpha, + int tex2alpha2) +{ + + int red,green,blue,alpha; + + TextureMakePixelSolidCombine(red,green,blue,alpha,CL,solidcomb,solidalpha,solidalpha2,gmemptr); + + int r,g,b,a; + +#if defined(PLUSH_NO_TEXTURE) + if (texture) +#endif + { + const int xpos=(iUL>>14)&~3; + const int ypos=iVL>>16; + const LICE_pixel_chan *rd = ((LICE_pixel_chan*)texture) + xpos+ypos*tex_rowspan; + + if (bilinear) + { + __LICE_BilinearFilterI_2(&r,&g,&b,&a,rd, + ypos < texheight - 1 ? rd+tex_rowspan : ((LICE_pixel_chan *)texture)+xpos, + xpos < texwidth - 4 ? 4 : 4-texwidth, + iUL&65535,iVL&65535); + } + else + { + r=rd[LICE_PIXEL_R]; g=rd[LICE_PIXEL_G]; b=rd[LICE_PIXEL_B]; a=rd[LICE_PIXEL_A]; + } + + DoTextureCombine(texcomb, r,g,b,a, red,green,blue,alpha,texalpha,texalpha2); + } + +#ifdef PLUSH_NO_TEXTURE + if (texture2) +#endif + { + const int xpos=(iUL_2>>14)&~3; + const int ypos=iVL_2>>16; + const LICE_pixel_chan *rd = ((LICE_pixel_chan*)texture2) + xpos+ypos*tex_rowspan_2; + + if (bilinear2) + { + __LICE_BilinearFilterI_2(&r,&g,&b,&a,rd, + ypos < texheight_2 - 1 ? rd+tex_rowspan_2 : ((LICE_pixel_chan *)texture2)+xpos, + xpos < texwidth_2 - 4 ? 4 : 4-texwidth_2, + iUL_2&65535,iVL_2&65535); + } + else + { + r=rd[LICE_PIXEL_R]; g=rd[LICE_PIXEL_G]; b=rd[LICE_PIXEL_B]; a=rd[LICE_PIXEL_A]; + } + + DoTextureCombine(tex2comb, r,g,b,a, red,green,blue,alpha,tex2alpha,tex2alpha2); + } + + _LICE_MakePixelClamp(gmemptr, red,green,blue,alpha); +} + +static void inline TextureMakePixel(LICE_pixel_chan *gmemptr, + int solidcomb, int solidalpha, int solidalpha2, + pl_sInt32 *CL, + bool bilinear, + pl_sInt32 iUL, pl_sInt32 iVL, + pl_sInt32 texwidth, pl_sInt32 texheight, + LICE_pixel *texture, int tex_rowspan, + int texcomb, + int texalpha, + int texalpha2) +{ + + int red,green,blue,alpha; + + if ( +#ifdef PLUSH_NO_SOLIDGOURAUD + !texture|| +#endif + texcomb!=-1) + TextureMakePixelSolidCombine(red,green,blue,alpha,CL,solidcomb,solidalpha,solidalpha2,gmemptr); + + + +#ifdef PLUSH_NO_SOLIDGOURAUD + if (texture) +#endif + { + int r,g,b,a; + const int xpos=(iUL>>14)&~3; + const int ypos=iVL>>16; + const LICE_pixel_chan *rd = ((LICE_pixel_chan*)texture) + xpos+ypos*tex_rowspan; + + if (bilinear) + { + + __LICE_BilinearFilterI_2(&r,&g,&b,&a,rd, + ypos < texheight - 1 ? rd+tex_rowspan : ((LICE_pixel_chan *)texture)+xpos, + xpos < texwidth - 4 ? 4 : 4-texwidth, + iUL&65535,iVL&65535); + } + else + { + r=rd[LICE_PIXEL_R]; g=rd[LICE_PIXEL_G]; b=rd[LICE_PIXEL_B]; a=rd[LICE_PIXEL_A]; + } + + DoTextureCombine(texcomb, r,g,b,a, red,green,blue,alpha,texalpha,texalpha2); + } + + _LICE_MakePixelClamp(gmemptr, red,green,blue,alpha); +} + + + +#ifndef PLUSH_NO_TEXTURE +#include "pl_pf_tex.h" +#endif + +#ifndef PLUSH_NO_MULTITEXTURE +#define PL_PF_MULTITEX +#include "pl_pf_tex.h" +#endif + + + + +template class PLSolidPutFace +{ + public: +#ifndef PLUSH_NO_SOLIDGOURAUD + static void SolidGouraud(LICE_pixel *gmem, int swidth, pl_Face *TriFace, int alpha, pl_ZBuffer *zbuf, int zfb_width, bool zb_update) + { + pl_Float dZL=0, dZ1=0, dZ2=0; + pl_sInt32 dX1=0, dX2=0, C1[3], C2[3], dC1[3]={0}, dC2[3]={0}, dCL[3]={0}, C3[3]; + + PUTFACE_SORT(); + + + int a; + for(a=0;a<3;a++) + { + C1[a] = (pl_sInt32) (TriFace->Shades[i0][a]*(1<<24)); + C2[a] = (pl_sInt32) (TriFace->Shades[i1][a]*(1<<24)); + C3[a] = (pl_sInt32) (TriFace->Shades[i2][a]*(1<<24)); + } + pl_sInt32 X2,X1; + X2 = X1 = Scrx[i0]; + pl_Float Z1 = TriFace->Scrz[i0]; + pl_Float Z2 = TriFace->Scrz[i1]; + pl_Float Z3 = TriFace->Scrz[i2]; + + pl_sInt32 Y0 = Scry[i0]; + pl_sInt32 Y1 = Scry[i1]; + pl_sInt32 Y2 = Scry[i2]; + + { + pl_sInt32 dY = Y2 - Y0; + if (dY) { + dX2 = (Scrx[i2] - X1) / dY; + for(a=0;a<3;a++) dC2[a] = (C3[a] - C1[a]) / dY; + dZ2 = (Z3 - Z1) / dY; + } + dY = Y1 - Y0; + if (dY) { + dX1 = (Scrx[i1] - X1) / dY; + for(a=0;a<3;a++) + dC1[a] = (C2[a] - C1[a]) / dY; + dZ1 = (Z2 - Z1) / dY; + if (dX2 < dX1) { + SWAP(dX2,dX1,pl_sInt32); + for(a=0;a<3;a++) SWAP(dC1[a],dC2[a],pl_sInt32); + SWAP(dZ1,dZ2,pl_Float); + stat = 2; + } else stat = 1; + Z2 = Z1; + C2[0] = C1[0]; + C2[1] = C1[1]; + C2[2] = C1[2]; + } else { + if (Scrx[i1] > X1) { + X2 = Scrx[i1]; + stat = 2|4; + } else { + for(a=0;a<3;a++) SWAP(C1[a],C2[a],pl_sInt32); + SWAP(Z1,Z2,pl_Float); + X1 = Scrx[i1]; + stat = 1|8; + } + } + + pl_sInt32 tmp = (dX1-dX2)*dY; + if (tmp) { + double v=(1<>XPOS_BITS; + pl_sInt32 XL2 = ((X2+(1<<(XPOS_BITS-1)))>>XPOS_BITS) - XL1; + if (XL2 > 0) { + gmem += XL1; + XL1 += XL2; + pl_sInt32 CL[3] = {C1[0],C1[1],C1[2]}; + if (zbuf) + { + pl_Float ZL = Z1; + zbuf += XL1-XL2; + do { + if (*zbuf < ZL) { + if (zb_update) *zbuf = (pl_ZBuffer) ZL; + + Comb::doPix((LICE_pixel_chan *)gmem,CL[0]>>16,CL[1]>>16,CL[2]>>16,255,alpha); + } + gmem++; + zbuf++; + ZL += dZL; + CL[0] += dCL[0]; + CL[1] += dCL[1]; + CL[2] += dCL[2]; + } while (--XL2); + zbuf -= XL1; + } + else do { + Comb::doPix((LICE_pixel_chan *)gmem,CL[0]>>16,CL[1]>>16,CL[2]>>16,255,alpha); + gmem++; + CL[0] += dCL[0]; + CL[1] += dCL[1]; + CL[2] += dCL[2]; + } while (--XL2); + gmem -= XL1; + } + gmem += swidth; + zbuf += zfb_width; + X1 += dX1; + X2 += dX2; + C1[0] += dC1[0]; + C1[1] += dC1[1]; + C1[2] += dC1[2]; + Z1 += dZ1; + Y0++; + } + } +#endif + +#ifndef PLUSH_NO_SOLIDFLAT + static void Solid(LICE_pixel *gmem, int swidth, pl_Face *TriFace, int alpha, pl_ZBuffer *zbuf, int zfb_width, bool zb_update) + { + pl_sInt32 dX1=0, dX2=0; + pl_Float dZL=0, dZ1=0, dZ2=0; + + PUTFACE_SORT(); + + int col0 = (int) (TriFace->Shades[0][0]*255.0); + int col1 = (int) (TriFace->Shades[0][1]*255.0); + int col2 = (int) (TriFace->Shades[0][2]*255.0); + + pl_sInt32 X1,X2; + X2 = X1 = Scrx[i0]; + pl_sInt32 Y0 = Scry[i0]; + pl_sInt32 Y1 = Scry[i1]; + pl_sInt32 Y2 = Scry[i2]; + + pl_Float Z1 = TriFace->Scrz[i0]; + pl_Float Z2 = TriFace->Scrz[i1]; + pl_Float Z3 = TriFace->Scrz[i2]; + + { + pl_sInt32 dY = Y2-Y0; + if (dY) { + dX2 = (Scrx[i2] - X1) / dY; + dZ2 = (Z3 - Z1) / dY; + } + dY = Y1-Y0; + if (dY) { + dX1 = (Scrx[i1] - X1) / dY; + dZ1 = (Z2 - Z1) / dY; + if (dX2 < dX1) { + SWAP(dX1,dX2,pl_sInt32); + SWAP(dZ1,dZ2,pl_Float); + stat = 2; + } else stat = 1; + Z2 = Z1; + } else { + if (Scrx[i1] > X1) { + X2 = Scrx[i1]; + stat = 2|4; + } else { + X1 = Scrx[i1]; + SWAP(Z1,Z2,pl_Float); + stat = 1|8; + } + } + + if (zbuf) + { + pl_sInt32 tmp=(dX1-dX2)*dY; + if (tmp) dZL = ((dZ1-dZ2)*dY)*(double)(1<>XPOS_BITS; + pl_sInt32 XL2 = ((X2+(1<<(XPOS_BITS-1)))>>XPOS_BITS) - XL1; + if (XL2 > 0) { + gmem += XL1; + XL1 += XL2; + if (zbuf) + { + pl_Float ZL = Z1; + zbuf += XL1-XL2; + do { + if (*zbuf < ZL) { + if (zb_update) *zbuf = (pl_ZBuffer) ZL; + Comb::doPix((LICE_pixel_chan *)gmem,col0,col1,col2,255,alpha); + } + gmem++; + zbuf++; + ZL += dZL; + } while (--XL2); + zbuf -= XL1; + } + else + { + do { + Comb::doPix((LICE_pixel_chan *)gmem,col0,col1,col2,255,alpha); + gmem++; + } while (--XL2); + } + gmem -= XL1; + + } + gmem += swidth; + zbuf += zfb_width; + Z1 += dZ1; + X1 += dX1; + X2 += dX2; + Y0++; + } + } +#endif +}; + +void pl_Cam::PutFace(pl_Face *TriFace) +{ + LICE_pixel *gmem = m_fBuffer.m_buf; + if (WDL_NOT_NORMALLY(!gmem)) return; + + int zfb_width = 0; + int zBufferable = TriFace->Material->zBufferable; + pl_ZBuffer *zb = NULL; + if (zBufferable && + zBuffer.GetSize() && + WDL_NORMALLY(zBuffer.GetSize() >= m_fBuffer.m_w*m_fBuffer.m_h) + ) + { + zfb_width = m_fBuffer.m_w; + zb = zBuffer.Get(); + } + + int swidth = m_fBuffer.m_span; + if (m_fBuffer.m_flipped) + { + gmem += swidth*(m_fBuffer.m_h-1); + swidth=-swidth; + } + + pl_Mat *mat=TriFace->Material; + +#ifndef PLUSH_NO_MULTITEXTURE + + #ifndef PLUSH_NO_TEXTURE + if (mat->Texture&&mat->Texture2) + #else + #ifndef PLUSH_NO_SOLIDGOURAUD + if (mat->Texture||mat->Texture2) + #endif + #endif + { + pl_Float texsc[4]; + memcpy(texsc,mat->TexScaling,sizeof(mat->TexScaling)); + memcpy(texsc+2,mat->Tex2Scaling,sizeof(mat->Tex2Scaling)); + int tidx = mat->TexMapIdx; + if (tidx<0 || tidx>=PLUSH_MAX_MAPCOORDS)tidx=PLUSH_MAX_MAPCOORDS-1; + int tidx2 = mat->Tex2MapIdx; + if (tidx2<0 || tidx2>=PLUSH_MAX_MAPCOORDS)tidx2=PLUSH_MAX_MAPCOORDS-1; + + PLMTexTri(gmem,swidth,TriFace,zb,zfb_width,zBufferable!=2,(int) (mat->SolidOpacity*256.0),mat->SolidCombineMode, + mat->Texture,texsc,(int) (mat->TexOpacity*256.0),mat->TexCombineMode,tidx, + mat->Texture2,(int) (mat->Tex2Opacity*256.0),mat->Tex2CombineMode,tidx2 + ); + return; + } +#endif + + +#ifndef PLUSH_NO_TEXTURE + #ifndef PLUSH_NO_SOLIDGOURAUD + if (mat->Texture||mat->Texture2) + #endif + { + LICE_IBitmap *tex=mat->Texture ? mat->Texture : mat->Texture2; + int talpha = (int) (mat->Texture ? mat->TexOpacity*256.0 : mat->Tex2Opacity*256.0); + int tcomb = (int) (mat->Texture ? mat->TexCombineMode : mat->Tex2CombineMode); + int tidx = (mat->Texture ? mat->TexMapIdx: mat->Tex2MapIdx); + if (tidx<0 || tidx>=PLUSH_MAX_MAPCOORDS)tidx=PLUSH_MAX_MAPCOORDS-1; + pl_Float texsc[2]; + memcpy(texsc,mat->Texture ? mat->TexScaling : mat->Tex2Scaling,sizeof(texsc)); + PLTexTri(gmem,swidth,TriFace,zb,zfb_width,zBufferable!=2,(int) (mat->SolidOpacity*256.0),mat->SolidCombineMode,tex,texsc,talpha,tcomb,tidx); + return; + } +#endif + + int alpha=(int) (mat->SolidOpacity*256.0); + if (!alpha) return; + +#ifndef PLUSH_NO_SOLIDGOURAUD +#ifndef PLUSH_NO_SOLIDFLAT + if (mat->Smoothing) +#endif + { + #define __LICE__ACTION(comb) PLSolidPutFace::SolidGouraud(gmem,swidth,TriFace,alpha,zb,zfb_width, zBufferable!=2); + __LICE_ACTION_CONSTANTALPHA(mat->SolidCombineMode,alpha,true); + #undef __LICE__ACTION + return; + } +#endif + +#ifndef PLUSH_NO_SOLIDFLAT + + #define __LICE__ACTION(comb) PLSolidPutFace::Solid(gmem,swidth,TriFace,alpha,zb,zfb_width, zBufferable!=2); + __LICE_ACTION_CONSTANTALPHA(mat->SolidCombineMode,alpha,true); + #undef __LICE__ACTION + +#endif // PLUSH_NO_SOLIDFLAT +} + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_3ds.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_3ds.cpp new file mode 100644 index 0000000..9e29ea5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_3ds.cpp @@ -0,0 +1,347 @@ +/****************************************************************************** +Plush Version 1.2 +read_3ds.c +3DS Object Reader +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************/ + +#include "plush.h" + +typedef struct +{ + pl_uInt16 id; + void (*func)(pl_uChar *ptr, pl_uInt32 p); +} _pl_3DSChunk; + +static pl_Obj *obj; +static pl_Obj *bobj; +static pl_Obj *lobj; +static pl_sInt16 currentobj; +static pl_Mat *_m; + +static pl_Float _pl3DSReadFloat(pl_uChar **ptr); +static pl_uInt32 _pl3DSReadDWord(pl_uChar **ptr); +static pl_uInt16 _pl3DSReadWord(pl_uChar **ptr); +static void _pl3DSChunkReader(pl_uChar *ptr, int len); +static void _pl3DSRGBFReader(pl_uChar *f, pl_uInt32 p); +static void _pl3DSRGBBReader(pl_uChar *f, pl_uInt32 p); +static int _pl3DSASCIIZReader(pl_uChar *ptr, pl_uInt32 p, char *as); +static void _pl3DSObjBlockReader(pl_uChar *ptr, pl_uInt32 p); +static void _pl3DSTriMeshReader(pl_uChar *f, pl_uInt32 p); +static void _pl3DSVertListReader(pl_uChar *f, pl_uInt32 p); +static void _pl3DSFaceListReader(pl_uChar *f, pl_uInt32 p); +static void _pl3DSFaceMatReader(pl_uChar *f, pl_uInt32 p); +static void MapListReader(pl_uChar *f, pl_uInt32 p); +static pl_sInt16 _pl3DSFindChunk(pl_uInt16 id); + +static _pl_3DSChunk _pl3DSChunkNames[] = { + {0x4D4D,NULL}, /* Main */ + {0x3D3D,NULL}, /* Object Mesh */ + {0x4000,_pl3DSObjBlockReader}, + {0x4100,_pl3DSTriMeshReader}, + {0x4110,_pl3DSVertListReader}, + {0x4120,_pl3DSFaceListReader}, + {0x4130,_pl3DSFaceMatReader}, + {0x4140,MapListReader}, + {0xAFFF,NULL}, /* Material */ + {0xA010,NULL}, /* Ambient */ + {0xA020,NULL}, /* Diff */ + {0xA030,NULL}, /* Specular */ + {0xA200,NULL}, /* Texture */ + {0x0010,_pl3DSRGBFReader}, + {0x0011,_pl3DSRGBBReader}, +}; + +pl_Obj *plRead3DSObjFromFile(char *fn, pl_Mat *m) +{ + FILE *f = fopen(fn, "rb"); + if (!f) return 0; + fseek(f, 0, 2); + pl_uInt32 p = ftell(f); + rewind(f); + + WDL_HeapBuf buf; + buf.Resize(p); + int s = fread(buf.Get(), 1, p, f); + fclose(f); + + if(!s) return 0; + + return plRead3DSObj(buf.Get(), s, m); +} + +pl_Obj *plRead3DSObjFromResource(HINSTANCE hInst, int resid, pl_Mat *m) +{ +#ifdef _WIN32 + HRSRC hResource = FindResource(hInst, MAKEINTRESOURCE(resid), "3DS"); + if(!hResource) return NULL; + + DWORD imageSize = SizeofResource(hInst, hResource); + if(imageSize < 6) return NULL; + + HGLOBAL res = LoadResource(hInst, hResource); + const void* pResourceData = LockResource(res); + if(!pResourceData) return NULL; + + unsigned char *data = (unsigned char *)pResourceData; + + pl_Obj *o = plRead3DSObj(data, imageSize, m); + + DeleteObject(res); + + return o; +#else + return 0; +#endif +} + +pl_Obj *plRead3DSObj(void *ptr, int size, pl_Mat *m) +{ + _m = m; + obj = bobj = lobj = 0; + currentobj = 0; + + _pl3DSChunkReader((pl_uChar *)ptr, size); + + return bobj; +} + +static pl_Float _pl3DSReadFloat(pl_uChar **ptr) { + pl_uInt32 *i; + pl_IEEEFloat32 c; + i = (pl_uInt32 *) &c; + *i = _pl3DSReadDWord(ptr); + return ((pl_Float) c); +} + +static pl_uInt32 _pl3DSReadDWord(pl_uChar **ptr) +{ + pl_uInt32 r; + pl_uChar *p = *ptr; + r = *p++; + r |= (*p++)<<8; + r |= (*p++)<<16; + r |= (*p++)<<24; + *ptr += 4; + return r; +} + +static pl_uInt16 _pl3DSReadWord(pl_uChar **ptr) +{ + pl_uInt16 r; + pl_uChar *p = *ptr; + r = *p++; + r |= (*p++)<<8; + *ptr += 2; + return r; +} + +static void _pl3DSRGBFReader(pl_uChar *f, pl_uInt32 p) +{ + pl_Float c[3]; + if(p < 3*4) return; + c[0] = _pl3DSReadFloat(&f); + c[1] = _pl3DSReadFloat(&f); + c[2] = _pl3DSReadFloat(&f); +} + +static void _pl3DSRGBBReader(pl_uChar *f, pl_uInt32 p) +{ + unsigned char c[3]; + if(p < 3) return; + memcpy(c, f, sizeof(c)); +} + +static int _pl3DSASCIIZReader(pl_uChar *ptr, pl_uInt32 p, char *as) +{ + int l = 0; + while (*ptr && p>0) + { + if(as) *as++ = *ptr; + ptr++; + l++; + p--; + } + if(as) *as = 0; + return l+1; +} + +static void _pl3DSObjBlockReader(pl_uChar *ptr, pl_uInt32 p) +{ + int l = _pl3DSASCIIZReader(ptr, p, 0); + ptr += l; + p -= l; + _pl3DSChunkReader(ptr, p); +} + +static void _pl3DSTriMeshReader(pl_uChar *ptr, pl_uInt32 p) +{ + pl_uInt32 i; + pl_Face *face; + obj = new pl_Obj; + _pl3DSChunkReader(ptr, p); + i = obj->Faces.GetSize(); + face = obj->Faces.Get(); + while (i--) + { + pl_Vertex *vp=obj->Vertices.Get(); + pl_Vertex *fVertices[3] = { + vp+face->VertexIndices[0], + vp+face->VertexIndices[1], + vp+face->VertexIndices[2], + }; + face->MappingU[0][0] = fVertices[0]->xformedx; + face->MappingV[0][0] = fVertices[0]->xformedy; + face->MappingU[0][1] = fVertices[1]->xformedx; + face->MappingV[0][1] = fVertices[1]->xformedy; + face->MappingU[0][2] = fVertices[2]->xformedx; + face->MappingV[0][2] = fVertices[2]->xformedy; + face++; + } + obj->CalculateNormals(); + if (currentobj == 0) + { + currentobj = 1; + lobj = bobj = obj; + } + else + { + lobj->Children.Add(obj); + lobj = obj; + } +} + +static void _pl3DSVertListReader(pl_uChar *f, pl_uInt32 p) +{ + pl_uInt16 nv; + pl_Vertex *v; + int len = (int)p; + nv = _pl3DSReadWord(&f); + len -= 2; + if(len <= 0) return; + obj->Vertices.Resize(nv); + v = obj->Vertices.Get(); + while (nv--) + { + memset(v,0,sizeof(pl_Vertex)); + v->x = _pl3DSReadFloat(&f); + v->y = _pl3DSReadFloat(&f); + v->z = _pl3DSReadFloat(&f); + len -= 3*4; + if(len < 0) return; + v++; + } +} + +static void _pl3DSFaceListReader(pl_uChar *f, pl_uInt32 p) +{ + pl_uInt16 nv; + pl_uInt16 c[3]; + pl_uInt16 flags; + pl_Face *face; + int len = (int)p; + + nv = _pl3DSReadWord(&f); + len -= 2; + if(len <= 0) return; + obj->Faces.Resize(nv); + face = obj->Faces.Get(); + while (nv--) + { + memset(face,0,sizeof(pl_Face)); + c[0] = _pl3DSReadWord(&f); + c[1] = _pl3DSReadWord(&f); + c[2] = _pl3DSReadWord(&f); + flags = _pl3DSReadWord(&f); + len -= 4*2; + if(len < 0) return; + + face->VertexIndices[0] = (c[0]&0x0000FFFF); + face->VertexIndices[1] = (c[1]&0x0000FFFF); + face->VertexIndices[2] = (c[2]&0x0000FFFF); + face->Material = _m; + face++; + } + if(len) _pl3DSChunkReader(f, len); +} + +static void _pl3DSFaceMatReader(pl_uChar *ptr, pl_uInt32 p) +{ + pl_uInt16 n, nf; + + int l = _pl3DSASCIIZReader(ptr, p, 0); + ptr += l; + p -= l; + + n = _pl3DSReadWord(&ptr); + while (n--) + { + nf = _pl3DSReadWord(&ptr); + } +} + +static void MapListReader(pl_uChar *f, pl_uInt32 p) +{ + pl_uInt16 nv; + pl_Float c[2]; + pl_Vertex *v; + int len = (int) p; + nv = _pl3DSReadWord(&f); + len -= 2; + v = obj->Vertices.Get(); + if (nv == obj->Vertices.GetSize()) while (nv--) + { + c[0] = _pl3DSReadFloat(&f); + c[1] = _pl3DSReadFloat(&f); + len -= 2*4; + if (len < 0) return; + v->xformedx = c[0]; + v->xformedy = c[1]; + v++; + } +} + +static pl_sInt16 _pl3DSFindChunk(pl_uInt16 id) +{ + pl_sInt16 i; + for (i = 0; i < sizeof(_pl3DSChunkNames)/sizeof(_pl3DSChunkNames[0]); i++) + if (id == _pl3DSChunkNames[i].id) return i; + return -1; +} + +static void _pl3DSChunkReader(pl_uChar *ptr, int len) +{ + pl_uInt32 hlen; + pl_uInt16 hid; + pl_sInt16 n; + + while (len > 0) + { + hid = _pl3DSReadWord(&ptr); + len -= 2; + if(len <= 0) return; + hlen = _pl3DSReadDWord(&ptr); + len -= 4; + if(len <= 0) return; + if (hlen == 0) return; + hlen -= 6; + n = _pl3DSFindChunk(hid); + if (n < 0) + { + ptr += hlen; + len -= hlen; + } + else + { + pl_uChar *p = ptr; + if (_pl3DSChunkNames[n].func != NULL) + _pl3DSChunkNames[n].func(p, hlen); + else + _pl3DSChunkReader(p, hlen); + + ptr += hlen; + len -= hlen; + } + } +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_cob.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_cob.cpp new file mode 100644 index 0000000..20d7e93 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_cob.cpp @@ -0,0 +1,181 @@ +/****************************************************************************** +Plush Version 1.2 +read_cob.c +ASCII COB Object Reader +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************/ + +#include "plush.h" + +#define PL_COB_MAX_LINELENGTH 1024 + +pl_Obj *plReadCOBObj(char *fn, pl_Mat *mat) { + FILE *fp = fopen(fn,"rt"); + int p1,m1,p2,m2,p3,m3; + char temp_string[PL_COB_MAX_LINELENGTH]; + float TransMatrix[4][4]; + pl_Obj *obj; + pl_sInt32 x,i2; + int numVertices, numMappingVertices, numFaces, i; + pl_Float *MappingVertices = 0; + if (!fp) return 0; + + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + if (memcmp("Caligari",temp_string,8)) { fclose(fp); return 0; } + + do { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } while (!feof(fp) && memcmp("Transform",temp_string,9)); + if (feof(fp)) { fclose(fp); return 0; } + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + sscanf(temp_string,"%f %f %f %f", + &TransMatrix[0][0],&TransMatrix[0][1],&TransMatrix[0][2],&TransMatrix[0][3]); + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + sscanf(temp_string,"%f %f %f %f", + &TransMatrix[1][0],&TransMatrix[1][1],&TransMatrix[1][2],&TransMatrix[1][3]); + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + sscanf(temp_string,"%f %f %f %f", + &TransMatrix[2][0],&TransMatrix[2][1],&TransMatrix[2][2],&TransMatrix[2][3]); + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + sscanf(temp_string,"%f %f %f %f", + &TransMatrix[3][0],&TransMatrix[3][1],&TransMatrix[3][2],&TransMatrix[3][3]); + + do { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } while (!feof(fp) && memcmp("World Vertices",temp_string,12)); + if (feof(fp) || sscanf(temp_string,"World Vertices %d",&numVertices) != 1) + { fclose(fp); return 0; } + + rewind(fp); + do { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } while (!feof(fp) && memcmp("Texture Vertices",temp_string,16)); + if (feof(fp) || + sscanf(temp_string,"Texture Vertices %d",&numMappingVertices) != 1) { + fclose(fp); return 0; + } + + rewind(fp); + do { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } while (!feof(fp) && memcmp("Faces",temp_string,5)); + if (feof(fp) || sscanf(temp_string,"Faces %d",&numFaces) != 1) { + fclose(fp); return 0; + } + for (x = numFaces; x; x--) { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + if (feof(fp) || sscanf(temp_string+4," verts %d",&i) != 1 || i < 3) { + fclose(fp); + return 0; + } + numFaces += i-3; + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } + obj = new pl_Obj(numVertices,numFaces); + if (!obj) { fclose(fp); return 0; } + rewind(fp); + do { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } while (!feof(fp) && memcmp("World Vertices",temp_string,12)); + if (feof(fp)) { delete obj; fclose(fp); return 0; } + for (x = 0; x < numVertices; x ++) { + float xp, yp, zp; + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + if (feof(fp) || + sscanf(temp_string,"%f %f %f", &xp, &yp, &zp) != 3) { + delete obj; fclose(fp); return 0; + } + obj->Vertices.Get()[x].x = (TransMatrix[0][0]*xp+TransMatrix[0][1]*yp+ + TransMatrix[0][2]*zp+TransMatrix[0][3]); + obj->Vertices.Get()[x].y = (TransMatrix[1][0]*xp+TransMatrix[1][1]*yp+ + TransMatrix[1][2]*zp+TransMatrix[1][3]); + obj->Vertices.Get()[x].z = (TransMatrix[2][0]*xp+TransMatrix[2][1]*yp+ + TransMatrix[2][2]*zp+TransMatrix[2][3]); + } + rewind(fp); + do { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } while (!feof(fp) && memcmp("Texture Vertices",temp_string,16)); + if (!feof(fp)) { + MappingVertices = (pl_Float *) + malloc(sizeof(pl_Float ) * numMappingVertices * 2); + if (MappingVertices) { + for (x = 0; x < numMappingVertices; x ++) { + float p1, p2; + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + if (feof(fp) || sscanf(temp_string,"%f %f", &p1, &p2) != 2) { + free(MappingVertices); delete obj; fclose(fp); return 0; + } + MappingVertices[x*2] = p1; + MappingVertices[x*2+1] = p2; + } + } + } + rewind(fp); + do { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + } while (!feof(fp) && memcmp("Faces",temp_string,5)); + if (feof(fp)) { + if (MappingVertices) free(MappingVertices); + delete obj; fclose(fp); return 0; + } + for (x = 0; x < numFaces; x ++) { + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + sscanf(temp_string+4," verts %d",&i); + fgets(temp_string,PL_COB_MAX_LINELENGTH,fp); + if (i == 3) { + if (feof(fp) || sscanf(temp_string,"<%d,%d> <%d,%d> <%d,%d>", + &p3,&m3,&p2,&m2,&p1,&m1) != 6) { + if (MappingVertices) free(MappingVertices); + delete obj; fclose(fp); return 0; + } + obj->Faces.Get()[x].VertexIndices[0] = p1; + obj->Faces.Get()[x].VertexIndices[1] = p2; + obj->Faces.Get()[x].VertexIndices[2] = p3; + if (MappingVertices) { + obj->Faces.Get()[x].MappingU[0][0] = MappingVertices[m1*2]; + obj->Faces.Get()[x].MappingV[0][0] = MappingVertices[m1*2+1]; + obj->Faces.Get()[x].MappingU[0][1] = MappingVertices[m2*2]; + obj->Faces.Get()[x].MappingV[0][1] = MappingVertices[m2*2+1]; + obj->Faces.Get()[x].MappingU[0][2] = MappingVertices[m3*2]; + obj->Faces.Get()[x].MappingV[0][2] = MappingVertices[m3*2+1]; + } + obj->Faces.Get()[x].Material = mat; + } else { + int p[16],m[16]; + if (feof(fp)) { + if (MappingVertices) free(MappingVertices); + delete obj; fclose(fp); return 0; + } + sscanf(temp_string, + "<%d,%d> <%d,%d> <%d,%d> <%d,%d> " + "<%d,%d> <%d,%d> <%d,%d> <%d,%d> " + "<%d,%d> <%d,%d> <%d,%d> <%d,%d> " + "<%d,%d> <%d,%d> <%d,%d> <%d,%d> ", + p+0,m+0,p+1,m+1,p+2,m+2,p+3,m+3, + p+4,m+4,p+5,m+5,p+6,m+6,p+7,m+7, + p+8,m+8,p+9,m+9,p+10,m+10,p+11,m+11, + p+12,m+12,p+13,m+13,p+14,m+14,p+15,m+15); + for (i2 = 1; i2 < (i-1); i2 ++) { + obj->Faces.Get()[x].VertexIndices[0] = p[0]; + obj->Faces.Get()[x].VertexIndices[1] = p[i2+1]; + obj->Faces.Get()[x].VertexIndices[2] = p[i2]; + if (MappingVertices) { + obj->Faces.Get()[x].MappingU[0][0] = MappingVertices[m[0]*2]; + obj->Faces.Get()[x].MappingV[0][0] = MappingVertices[m[0]*2+1]; + obj->Faces.Get()[x].MappingU[0][1] = MappingVertices[m[i2+1]*2]; + obj->Faces.Get()[x].MappingV[0][1] = MappingVertices[m[i2+1]*2+1]; + obj->Faces.Get()[x].MappingU[0][2] = MappingVertices[m[i2]*2]; + obj->Faces.Get()[x].MappingV[0][2] = MappingVertices[m[i2]*2+1]; + } + obj->Faces.Get()[x].Material = mat; + x++; + } + x--; + } + } + if (MappingVertices) free(MappingVertices); + obj->CalculateNormals(); + fclose(fp); + return obj; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_jaw.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_jaw.cpp new file mode 100644 index 0000000..a894721 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_read_jaw.cpp @@ -0,0 +1,69 @@ +/****************************************************************************** +Plush Version 1.2 +read_jaw.c +Jaw3D Object Reader +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************* + Notes on .JAW files: + This is a file format created by Jawed Karim for Jaw3D + (http://jaw3d.home.ml.org). + -- updated 11/6/00 - www.jawed.com + It is very simple, and lets one easily create ones own models using only + a text editor. The format is pretty simple: + The first line must be "Light: (x,y,z)" where x,y, and z are the x y and + z components of the lightsource vector (I think ;) + A series of lines, numbered 0 to n, in the format of + "i: x y z", where i is the vertex number (which should be listed in + order, and x y and z are the coordinates of that vertex. + A series of lines, having the format "tri a, b, c" where a b and c are + the vertices that the face uses. It is unclear at this time which + way the vertices are listed (ccw or cw), so just make em consistent + and you can always use plFlipObjectNormals() on the loaded object. + That is it! (I told ya it was simple). +******************************************************************************/ + +#include "plush.h" + +pl_Obj *plReadJAWObj(char *filename, pl_Mat *m) { + FILE *jawfile; + pl_Obj *obj; + pl_uInt32 i; + pl_sInt crap; + char line[256]; + pl_uInt32 total_points = 0, total_polys = 0; + if ((jawfile = fopen(filename, "r")) == NULL) return 0; + fgets(line, 256, jawfile); /* Ignores lightsource info */ + while (fgets(line, 256, jawfile) != NULL) + if (strstr(line, ":") != NULL) total_points++; + + rewind(jawfile); fgets(line, 256, jawfile); + while (fgets(line, 256, jawfile) != NULL) + if (strstr(line, "tri") != NULL) total_polys++; + + rewind(jawfile); fgets(line, 256, jawfile); + obj = new pl_Obj(total_points,total_polys); + + i = 0; + while (fgets(line, 256, jawfile) != NULL) if (strstr(line, ":") != NULL) { + float x, y, z; + sscanf(line, "%d: %f %f %f",&crap,&x,&y,&z); + obj->Vertices.Get()[i].x = (pl_Float) x; + obj->Vertices.Get()[i].y = (pl_Float) y; + obj->Vertices.Get()[i].z = (pl_Float) z; + i++; + } + rewind(jawfile); fgets(line, 256, jawfile); + i = 0; + while (fgets(line, 256, jawfile) != NULL) if (strstr(line, "tri") != NULL) { + pl_uInt32 a,b,c; + sscanf(line, "tri %ld, %ld, %ld", &a, &b, &c); + obj->Faces.Get()[i].VertexIndices[0] = a; + obj->Faces.Get()[i].VertexIndices[1] = c; + obj->Faces.Get()[i].VertexIndices[2] = b; + obj->Faces.Get()[i].Material = m; + i++; + } + fclose(jawfile); + obj->CalculateNormals(); + return obj; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_spline.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_spline.cpp new file mode 100644 index 0000000..78791e9 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/pl_spline.cpp @@ -0,0 +1,50 @@ +/****************************************************************************** +Plush Version 1.2 +spline.c +n-th Dimensional Spline Interpolator +Copyright (c) 1996-2000, Justin Frankel +******************************************************************************/ + +#include "plush.h" + +void pl_Spline::GetPoint(pl_Float frame, pl_Float *out) { + pl_sInt32 i, i_1, i0, i1, i2; + pl_Float time1,time2,time3; + pl_Float t1,t2,t3,t4,u1,u2,u3,u4,v1,v2,v3; + pl_Float a,b,c,d; + + int numKeys=keys.GetSize(); + pl_Float *keyptrs = keys.Get(); + + a = (1-tens)*(1+cont)*(1+bias); + b = (1-tens)*(1-cont)*(1-bias); + c = (1-tens)*(1-cont)*(1+bias); + d = (1-tens)*(1+cont)*(1-bias); + v1 = t1 = -a / 2.0; u1 = a; + u2 = (-6-2*a+2*b+c)/2.0; v2 = (a-b)/2.0; t2 = (4+a-b-c) / 2.0; + t3 = (-4+b+c-d) / 2.0; + u3 = (6-2*b-c+d)/2.0; + v3 = b/2.0; + t4 = d/2.0; u4 = -t4; + + i0 = (pl_uInt) frame; + i_1 = i0 - 1; + while (i_1 < 0) i_1 += numKeys; + i1 = i0 + 1; + while (i1 >= numKeys) i1 -= numKeys; + i2 = i0 + 2; + while (i2 >= numKeys) i2 -= numKeys; + time1 = frame - (pl_Float) ((pl_uInt) frame); + time2 = time1*time1; + time3 = time2*time1; + i0 *= keyWidth; + i1 *= keyWidth; + i2 *= keyWidth; + i_1 *= keyWidth; + for (i = 0; i < keyWidth; i ++) { + a = t1*keyptrs[i+i_1]+t2*keyptrs[i+i0]+t3*keyptrs[i+i1]+t4*keyptrs[i+i2]; + b = u1*keyptrs[i+i_1]+u2*keyptrs[i+i0]+u3*keyptrs[i+i1]+u4*keyptrs[i+i2]; + c = v1*keyptrs[i+i_1]+v2*keyptrs[i+i0]+v3*keyptrs[i+i1]; + *out++ = a*time3 + b*time2 + c*time1 + keyptrs[i+i0]; + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/plush.h b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/plush.h new file mode 100644 index 0000000..0fea6f8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/plush2/plush.h @@ -0,0 +1,607 @@ +/****************************************************************************** + plush.h + PLUSH 3D VERSION 2.0 MAIN HEADER + Copyright (c) 1996-2000 Justin Frankel + Copyright (c) 1998-2000 Nullsoft, Inc. + Copyright (c) 2008 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. + + +******************************************************************************/ + +#ifndef _PLUSH_H_ +#define _PLUSH_H_ + +#include +#include +#include +#include + +#include "../lice/lice.h" // using LICE for images +#include "../ptrlist.h" +#include "../wdltypes.h" + +typedef float pl_ZBuffer; /* z-buffer type (must be float) */ +typedef double pl_Float; /* General floating point */ +typedef float pl_IEEEFloat32; /* IEEE 32 bit floating point */ +typedef signed int pl_sInt32; /* signed 32 bit integer */ +typedef unsigned int pl_uInt32; /* unsigned 32 bit integer */ +typedef signed short int pl_sInt16; /* signed 16 bit integer */ +typedef unsigned short int pl_uInt16; /* unsigned 16 bit integer */ +typedef signed int pl_sInt; /* signed optimal integer */ +typedef unsigned int pl_uInt; /* unsigned optimal integer */ +typedef bool pl_Bool; /* boolean */ +typedef unsigned char pl_uChar; /* unsigned 8 bit integer */ +typedef signed char pl_sChar; /* signed 8 bit integer */ + + + +/* pi! */ +#define PL_PI 3.141592653589793238 + +/* Utility min() and max() functions */ +#define plMin(x,y) (( ( x ) > ( y ) ? ( y ) : ( x ))) +#define plMax(x,y) (( ( x ) < ( y ) ? ( y ) : ( x ))) + + + +/* +** Light modes. Used with plLight.Type or plLightSet(). +** Note that PL_LIGHT_POINT_ANGLE assumes no falloff and uses the angle between +** the light and the point, PL_LIGHT_POINT_DISTANCE has falloff with proportion +** to distance**2 (see plLightSet() for setting it), PL_LIGHT_POINT does both. +*/ +#define PL_LIGHT_NONE (0x0) +#define PL_LIGHT_VECTOR (0x1) +#define PL_LIGHT_POINT (0x2|0x4) +#define PL_LIGHT_POINT_DISTANCE (0x2) +#define PL_LIGHT_POINT_ANGLE (0x4) + + +#define PLUSH_MAX_MAPCOORDS 3 // 2 + envmap slot + + +class pl_Mat +{ +public: + pl_Mat() + { + memset(Ambient,0,sizeof(Ambient)); + Diffuse[0]=Diffuse[1]=Diffuse[2]=1.0; + SolidOpacity=1.0; + SolidCombineMode= LICE_BLIT_MODE_COPY; + + Texture=NULL; + TexCombineMode = LICE_BLIT_MODE_ADD; //LICE_BLIT_USE_SOURCE_ALPHA? + TexOpacity=1.0; + TexScaling[0]=TexScaling[1]=1.0; + TexMapIdx=0; + + Texture2=NULL; + Tex2CombineMode = LICE_BLIT_MODE_ADD; + Tex2Opacity=1.0; + Tex2Scaling[0]=Tex2Scaling[1]=1.0; + Tex2MapIdx=-1; + + FadeDist=0.0; + Smoothing=Lightable=true; + zBufferable=1; + PerspectiveCorrect=16; + BackfaceCull=true; + BackfaceIllumination=0.0; + + cachedTexture=cachedTexture2=0; + cachesInvalid=true; + } + + ~pl_Mat() + { + delete cachedTexture; + delete cachedTexture2; + } + + + pl_Bool Smoothing; // smoothing of lighting + pl_Bool Lightable; // affected by lights + pl_uChar zBufferable; /* Can this material be Z-buffered? 2=yes, but does not update Z buffer */ + pl_uChar PerspectiveCorrect; /* Correct texture perspective every n pixels */ + + pl_Float FadeDist WDL_FIXALIGN; /* For distance fading, distance at which intensity is 0. set to 0.0 for no distance shading */ + + pl_Bool BackfaceCull; /* Are backfacing polys drawn? */ + pl_Float BackfaceIllumination WDL_FIXALIGN; /* Illuminated by lights behind them, and by how much of a factor? */ + + // colors + pl_Float Ambient[3]; /* RGB of surface (0-1 is a good range) */ + pl_Float Diffuse[3]; /* RGB of diffuse (0-1 is a good range) */ + pl_Float SolidOpacity; + int SolidCombineMode; /* LICE combine mode for first pass (color), default should be replace (or add-black for transparent) */ + + // textures + LICE_IBitmap *Texture; /* Texture map (not owned by Material but a reference)*/ + pl_Float TexScaling[2] WDL_FIXALIGN; /* Texture map scaling */ + pl_Float TexOpacity; + int TexCombineMode; /* Texture combine mode (generally should be additive) */ + int TexMapIdx; // -1 for env + + LICE_IBitmap *Texture2; + pl_Float Tex2Scaling[2] WDL_FIXALIGN; + pl_Float Tex2Opacity; + int Tex2CombineMode; + int Tex2MapIdx; // -1 for env + + + void InvalidateTextureCaches() { cachesInvalid=true; } // call this if you change Texture or Texture2 after rendering + +private: + bool cachesInvalid; + LICE_IBitmap *cachedTexture,*cachedTexture2; // these may need to be LICE_GL_MemBitmaps etc + +} WDL_FIXALIGN; + + +struct pl_Vertex { + pl_Float x, y, z; /* Vertex coordinate (objectspace) */ + pl_Float nx, ny, nz; /* Unit vertex normal (objectspace) */ + + pl_Float xformedx, xformedy, xformedz; /* Transformed vertex coordinate (cameraspace) */ + pl_Float xformednx, xformedny, xformednz; /* Transformed unit vertex normal (cameraspace) */ +}; + +struct pl_Face { + pl_Mat *Material; /* Material of triangle */ + int VertexIndices[3]; /* Vertices of triangle */ + + pl_Float nx WDL_FIXALIGN; + pl_Float ny; + pl_Float nz; /* Normal of triangle (object space) */ + + pl_Float MappingU[PLUSH_MAX_MAPCOORDS][3], MappingV[PLUSH_MAX_MAPCOORDS][3]; /* Texture mapping coordinates */ + + pl_Float sLighting[3]; /* Face static lighting. Should usually be 0.0 */ + pl_Float vsLighting[3][3]; /* Vertex static lighting. Should usually be 0.0 */ + + + // calculated: + pl_Float Shades[3][3]; /* colors (first 3 used for flat, all for Gouraud) */ + pl_Float Scrx[3], Scry[3]; /* Projected screen coordinates */ + pl_Float Scrz[3]; /* Projected 1/Z coordinates */ + +}; + + +class pl_Obj { +public: + pl_Obj(int nv=0, int nf=0) + { + if (nv) memset(Vertices.Resize(nv),0,nv*sizeof(pl_Vertex)); + if (nf) memset(Faces.Resize(nf),0,nf*sizeof(pl_Face)); + + GenMatrix=true; + Xp=Yp=Zp=Xa=Ya=Za=0.0; + } + ~pl_Obj() { Children.Empty(true); } + + pl_Obj *Clone(); + void Scale(pl_Float sc); + void Stretch(pl_Float x, pl_Float y, pl_Float z); // scales but preserves normals + void Translate(pl_Float x, pl_Float y, pl_Float z); + void FlipNormals(); + + void SetMaterial(pl_Mat *m, pl_Bool recurse=true); + void CalculateNormals(); + + + pl_Float Xp, Yp, Zp, Xa, Ya, Za; /* Position and rotation of object: + Note: rotations are around X then Y then Z. Measured in degrees */ + pl_Float Matrix[16]; /* Transformation matrix */ + pl_Float RotMatrix[16]; /* Rotation-only, for normals */ + + WDL_TypedBuf Vertices; + WDL_TypedBuf Faces; + WDL_PtrList Children; + + pl_Bool GenMatrix; /* Generate Matrix from X-Z* if set */ +}; + + +class pl_Spline { +public: + pl_Spline() { cont=1.0; bias=0.3; tens=0.3; keyWidth=1; } + ~pl_Spline () { } + void GetPoint(pl_Float frame, pl_Float *out); + WDL_TypedBuf keys; /* Key data, keyWidth*numKeys */ + pl_sInt keyWidth; /* Number of floats per key */ + + pl_Float cont WDL_FIXALIGN; /* Continuity. Should be -1.0 -> 1.0 */ + pl_Float bias; /* Bias. -1.0 -> 1.0 */ + pl_Float tens; /* Tension. -1.0 -> 1.0 */ +}; + + +class pl_Light { +public: + pl_Light() { Type = PL_LIGHT_VECTOR; Xp=Yp=0.0; Zp=1.0; Intensity[0]=Intensity[1]=Intensity[2]=1.0; } + ~pl_Light() { } + +/* + Set() sets up a light: + mode: the mode of the light (PL_LIGHT_*) + x,y,z: either the position of the light (PL_LIGHT_POINT*) or the angle + in degrees of the light (PL_LIGHT_VECTOR) + intensity: the intensity of the light (0.0-1.0) + halfDist: the distance at which PL_LIGHT_POINT_DISTANCE is 1/2 intensity +*/ + void Set(pl_uChar mode, pl_Float x, pl_Float y, pl_Float z, pl_Float intensity_r, pl_Float intensity_g, pl_Float intensity_b, pl_Float halfDist); + + + // privatestuff + pl_uChar Type; /* Type of light: PL_LIGHT_* */ + pl_Float Xp WDL_FIXALIGN; + pl_Float Yp, Zp; /* If Type=PL_LIGHT_POINT*, + this is Position (PL_LIGHT_POINT_*), + otherwise if PL_LIGHT_VECTOR, + Unit vector */ + pl_Float Intensity[3]; /* Intensity. 0.0 is off, 1.0 is full */ + pl_Float HalfDistSquared; /* Distance squared at which + PL_LIGHT_POINT_DISTANCE is 50% */ +}; + + +class pl_Cam { +public: + pl_Cam() : m_fBuffer(NULL,0,0,0,false) + { + m_lastFBScaling=256; + m_lastFBWidth=m_lastFBHeight=0; + Fov=90.0; + AspectRatio=1.0; + Sort=1; + ClipBack=-1.0; + CenterX=CenterY=0; + X=Y=Z=0.0; + WantZBuffer=false; + Pitch=Pan=Roll=0.0; + GenMatrix = true; + } + ~pl_Cam() + { + } + + + void SetTarget(pl_Float x, pl_Float y, pl_Float z); + + pl_Float Pitch, Pan, Roll; /* Camera angle in degrees in worldspace */ + pl_Float Fov; /* FOV in degrees valid range is 1-179 */ + pl_Float AspectRatio; /* Aspect ratio (usually 1.0) */ + + pl_Float ClipBack WDL_FIXALIGN; /* Far clipping ( < 0.0 is none) */ + pl_Float X, Y, Z; /* Camera position in worldspace */ + + pl_Float CamMatrix[16]; /* should be rotation-only. translation will mess with the normals */ + + pl_sChar Sort; /* Sort polygons, -1 f-t-b, 1 b-t-f, 0 no */ + pl_sInt CenterX, CenterY; /* Offset center of screen from actual center by this much... */ + + pl_Bool WantZBuffer; + pl_Bool GenMatrix; /* if set, generates CamMatrix from Pan/Pitch/Roll in Begin() */ + + void Begin(LICE_IBitmap *fb, bool want_zbclear=true, pl_ZBuffer zbclear=0.0); + void RenderLight(pl_Light *light); + void RenderObject(pl_Obj *obj, const pl_Float *bmatrix=NULL, const pl_Float *bnmatrix=NULL); + void SortToCurrent(); // sorts all faces added since Begin() or last SortToCurrent() call. useful for if you use zbuffering with transparent objects (draw them last) + + // returns true if onscreen x/y/z are world coordinates + bool ProjectCoordinate(pl_Float x, pl_Float y, pl_Float z, pl_Float *screen_x, pl_Float *screen_y, pl_Float *dist); // outputs can be NULL if not needed + + LICE_IBitmap *GetFrameBuffer() { return m_fBuffer.m_buf ? &m_fBuffer : NULL; } + WDL_TypedBuf zBuffer; /* Z Buffer (validate size before using)*/ + + void End(); + + int RenderTrisIn; + int RenderTrisCulled; + int RenderTrisOut; + + double RenderPixelsOut WDL_FIXALIGN; + + void PutFace(pl_Face *TriFace); + +protected: + LICE_WrapperBitmap m_fBuffer; + pl_uInt m_lastFBWidth, m_lastFBHeight; // unscaled sizes when compared to m_fBuffer + pl_uInt m_lastFBScaling; + pl_sInt m_lastCX, m_lastCY; // calculated as w/2+CenterX/2 etc + + // internal use + void ClipRenderFace(pl_Face *face, pl_Obj *obj); + int ClipNeeded(pl_Face *face, pl_Obj *obj); // 0=no draw, 1=drawing (possibly splitting) necessary + void RecalcFrustum(int fbw, int fbh); + double CalcFOVFactor(double fbw) const + { + return fbw/tan(plMin(plMax(Fov,1.0),179.0)*(PL_PI/360.0)); + } + + + #define PL_NUM_CLIP_PLANES 5 + + struct _clipInfo + { + pl_Vertex newVertices[8]; + pl_Float ShadeInfos[8][3]; + pl_Float MappingU[PLUSH_MAX_MAPCOORDS][8]; + pl_Float MappingV[PLUSH_MAX_MAPCOORDS][8]; + }; + + _clipInfo m_cl[2] WDL_FIXALIGN; + pl_Float m_clipPlanes[PL_NUM_CLIP_PLANES][4]; + pl_Float m_fovfactor, m_adj_asp; // recalculated + + /* Returns: 0 if nothing gets in, 1 or 2 if pout1 & pout2 get in */ + pl_uInt _ClipToPlane(pl_uInt numVerts, pl_Float *plane); + + + struct _faceInfo { + pl_Float zd; + pl_Face *face; + pl_Obj *obj; + } WDL_FIXALIGN; + + struct _lightInfo { + pl_Float l[3]; + pl_Light *light; + } WDL_FIXALIGN; + + static int sortRevFunc(const void *a, const void *b); + static int sortFwdFunc(const void *a, const void *b); + + int _numfaces,_numfaces_sorted; + WDL_TypedBuf<_faceInfo> _faces; + + int _numlights; + WDL_TypedBuf<_lightInfo> _lights; + + void _RenderObj(pl_Obj *, pl_Float *, pl_Float *); + + WDL_HeapBuf _sort_tmpspace; + +}; + + + + +/****************************************************************************** +** Object Primitives Code (pl_make.cpp) +******************************************************************************/ + +/* + plMakePlane() makes a plane centered at the origin facing up the y axis. + Parameters: + w: width of the plane (along the x axis) + d: depth of the plane (along the z axis) + res: resolution of plane, i.e. subdivisions + m: material to use + Returns: + pointer to object created. +*/ +pl_Obj *plMakePlane(pl_Float w, pl_Float d, pl_uInt res, pl_Mat *m); + +/* + plMakeBox() makes a box centered at the origin + Parameters: + w: width of the box (x axis) + d: depth of the box (z axis) + h: height of the box (y axis) + Returns: + pointer to object created. +*/ +pl_Obj *plMakeBox(pl_Float w, pl_Float d, pl_Float h, pl_Mat *m); + +/* + plMakeDisc() makes a disc centered at the origin + Parameters: + r: radius of the disc (x-z axis) + divr: division of of disc (around the circle) (>=3) + m: material to use + Returns: + pointer to object created. + */ +pl_Obj *plMakeDisc(pl_Float r, pl_uInt divr, pl_Mat *m); + +/* + plMakeCone() makes a cone centered at the origin + Parameters: + r: radius of the cone (x-z axis) + h: height of the cone (y axis) + div: division of cone (>=3) + cap: close the big end? + m: material to use + Returns: + pointer to object created. +*/ +pl_Obj *plMakeCone(pl_Float r, pl_Float h, pl_uInt div, pl_Bool cap, pl_Mat *m); + +/* + plMakeCylinder() makes a cylinder centered at the origin + Parameters: + r: radius of the cylinder (x-z axis) + h: height of the cylinder (y axis) + divr: division of of cylinder (around the circle) (>=3) + captop: close the top + capbottom: close the bottom + m: material to use + Returns: + pointer to object created. +*/ +pl_Obj *plMakeCylinder(pl_Float r, pl_Float h, pl_uInt divr, pl_Bool captop, + pl_Bool capbottom, pl_Mat *m); + +/* + plMakeSphere() makes a sphere centered at the origin. + Parameters: + r: radius of the sphere + divr: division of the sphere (around the y axis) (>=3) + divh: division of the sphere (around the x,z axis) (>=3) + m: material to use + Returns: + pointer to object created. +*/ +pl_Obj *plMakeSphere(pl_Float r, pl_uInt divr, pl_uInt divh, pl_Mat *m); + +/* + plMakeTorus() makes a torus centered at the origin + Parameters: + r1: inner radius of the torus + r2: outer radius of the torus + divrot: division of the torus (around the y axis) (>=3) + divrad: division of the radius of the torus (x>=3) + m: material to use + Returns: + pointer to object created. +*/ +pl_Obj *plMakeTorus(pl_Float r1, pl_Float r2, pl_uInt divrot, + pl_uInt divrad, pl_Mat *m); + + +/****************************************************************************** +** File Readers (pl_read_*.cpp) +******************************************************************************/ + +/* + plRead3DSObj() reads a 3DS object + Parameters: + fn: filename of object to read + m: material to assign it + Returns: + pointer to object + Notes: + This reader organizes multiple objects like so: + 1) the first object is returned + 2) the second object is the first's first child + 3) the third object is the second's first child + 4) etc +*/ +pl_Obj *plRead3DSObj(void *ptr, int size, pl_Mat *m); +pl_Obj *plRead3DSObjFromFile(char *fn, pl_Mat *m); +pl_Obj *plRead3DSObjFromResource(HINSTANCE hInst, int resid, pl_Mat *m); + +/* + plReadCOBObj() reads an ascii .COB object + Parameters: + fn: filename of object to read + mat: material to assign it + Returns: + pointer to object + Notes: + This is Caligari's ASCII object format. + This reader doesn't handle multiple objects. It just reads the first one. + Polygons with lots of sides are not always tesselated correctly. Just + use the "Tesselate" button from within truespace to improve the results. +*/ +pl_Obj *plReadCOBObj(char *fn, pl_Mat *mat); + +/* + plReadJAWObj() reads a .JAW object. + Parameters: + fn: filename of object to read + m: material to assign it + Returns: + pointer to object + Notes: + For information on the .JAW format, please see the jaw3D homepage, + http://www.tc.umn.edu/nlhome/g346/kari0022/jaw3d/ +*/ +pl_Obj *plReadJAWObj(char *fn, pl_Mat *m); + + + +/****************************************************************************** +** Math Code (pl_math.cpp) +******************************************************************************/ + +/* + plMatrixRotate() generates a rotation matrix + Parameters: + matrix: an array of 16 pl_Floats that is a 4x4 matrix + m: the axis to rotate around, 1=X, 2=Y, 3=Z. + Deg: the angle in degrees to rotate + Returns: + nothing +*/ +void plMatrixRotate(pl_Float matrix[], pl_uChar m, pl_Float Deg); + +/* + plMatrixTranslate() generates a translation matrix + Parameters: + m: the matrix (see plMatrixRotate for more info) + x,y,z: the translation coordinates + Returns: + nothing +*/ +void plMatrixTranslate(pl_Float m[], pl_Float x, pl_Float y, pl_Float z); + +/* + plMatrixMultiply() multiplies two matrices + Parameters: + dest: destination matrix will be multipled by src + src: source matrix + Returns: + nothing + Notes: + this is the same as dest = dest*src (since the order *does* matter); +*/ +void plMatrixMultiply(pl_Float *dest, const pl_Float src[]); + +/* + plMatrixApply() applies a matrix. + Parameters: + m: matrix to apply + x,y,z: input coordinate + outx,outy,outz: pointers to output coords. + Returns: + nothing + Notes: + applies the matrix to the 3d point to produce the transformed 3d point +*/ +void plMatrixApply(pl_Float *m, pl_Float x, pl_Float y, pl_Float z, + pl_Float *outx, pl_Float *outy, pl_Float *outz); + +/* + plNormalizeVector() makes a vector a unit vector + Parameters: + x,y,z: pointers to the vector + Returns: + nothing +*/ +void plNormalizeVector(pl_Float *x, pl_Float *y, pl_Float *z); + +/* + plDotProduct() returns the dot product of two vectors + Parameters: + x1,y1,z1: the first vector + x2,y2,z2: the second vector + Returns: + the dot product of the two vectors +*/ +pl_Float plDotProduct(pl_Float x1, pl_Float y1, pl_Float z1, + pl_Float x2, pl_Float y2, pl_Float z2); + + + + +#endif /* !_PLUSH_H_ */ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/poollist.h b/plugin-reaper-realearn/main/lib/WDL/WDL/poollist.h new file mode 100644 index 0000000..a53a640 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/poollist.h @@ -0,0 +1,169 @@ +/* + WDL - poollist.h + Copyright (C) 2006 and later, 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. + + + + This file defines a template class for hosting lists of referenced count, string-identified objects. + + We mostly use it with WDL_ResourcePool, but any class like this can use it: + + + class SomeClass + { + public: + SomeClass(char *identstr) { WDL_POOLLIST_identstr=identstr; WDL_POOLLIST_refcnt=0; } + ~SomeClass() {} // do NOT free or delete WDL_POOLLIST_identstr + + + void Clear() {} // will be called if ReleasePool(x,false) is called and refcnt gets to 0 + int WDL_POOLLIST_refcnt; + char *WDL_POOLLIST_identstr; + }; + + + +*/ + + + +#ifndef _WDL_POOLLIST_H_ +#define _WDL_POOLLIST_H_ + +#include + +#include "mutex.h" + +template class WDL_PoolList_NoFreeOnDestroy +{ +public: + + WDL_PoolList_NoFreeOnDestroy() + { + } + ~WDL_PoolList_NoFreeOnDestroy() + { + } + + DATATYPE *Get(const char *filename, bool createIfExists=true) + { + WDL_MutexLock lock(&mutex); + + DATATYPE *t = Find(filename,false); + if (t) + { + t->WDL_POOLLIST_refcnt++; + return t; + } + if (!createIfExists) return NULL; + + t = new DATATYPE(strdup(filename)); + t->WDL_POOLLIST_refcnt=1; + + int x; + for(x=0;xWDL_POOLLIST_identstr,filename)>0) break; + + pool.Insert(x,t); + + return t; + } + + DATATYPE *Find(const char *filename, bool lockMutex=true) // not threadsafe + { + if (lockMutex) mutex.Enter(); + DATATYPE **_tmp=NULL; + if (pool.GetSize()) + { + DATATYPE tmp((char *)filename),*t=&tmp; + _tmp = (DATATYPE**)bsearch(&t,pool.GetList(),pool.GetSize(),sizeof(void *),_sortfunc); + } + if (lockMutex) mutex.Leave(); + return _tmp ? *_tmp : NULL; + } + + int ReleaseByName(const char *filename, bool isFull=true) + { + WDL_MutexLock lock(&mutex); + return Release(Find(filename,false),isFull); + } + + int Release(DATATYPE *tp, bool isFull=true) + { + if (!tp) return -1; + WDL_MutexLock lock(&mutex); + + int refcnt; + if (!(refcnt=--tp->WDL_POOLLIST_refcnt)) + { + if (!isFull) + { + tp->Clear(); + } + else + { + int x; + for (x = 0; x < pool.GetSize() && pool.Get(x) != tp; x ++); + if (xWDL_POOLLIST_identstr); + delete tp; + } + // remove from list + } + return refcnt; + } + + void RemoveAll() + { + int x; + for (x = 0; x < pool.GetSize(); x ++) + { + DATATYPE *p = pool.Get(x); + free(p->WDL_POOLLIST_identstr); + delete p; + } + pool.Empty(); + } + + WDL_Mutex mutex; + WDL_PtrList< DATATYPE > pool; + +private: + + static int _sortfunc(const void *a, const void *b) + { + DATATYPE *ta = *(DATATYPE **)a; + DATATYPE *tb = *(DATATYPE **)b; + + return stricmp(ta->WDL_POOLLIST_identstr,tb->WDL_POOLLIST_identstr); + } +}; + +template class WDL_PoolList : public WDL_PoolList_NoFreeOnDestroy +{ +public: + WDL_PoolList() { } + ~WDL_PoolList() { WDL_PoolList_NoFreeOnDestroy::RemoveAll(); } + + +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/projectcontext.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/projectcontext.cpp new file mode 100644 index 0000000..9fde493 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/projectcontext.cpp @@ -0,0 +1,1320 @@ +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif +#include +#include +#include + +#include "projectcontext.h" + +#include "fileread.h" +#include "filewrite.h" +#include "heapbuf.h" +#include "wdlstring.h" +#include "wdlcstring.h" +#include "fastqueue.h" +#include "lineparse.h" + + +//#define WDL_MEMPROJECTCONTEXT_USE_ZLIB 1 + +#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB + +#define WDL_MEMPROJECTCONTEXT_ZLIB_CHUNKSIZE 65536 +#include "zlib/zlib.h" + +#endif + +#include "denormal.h" + + +char *projectcontext_fastDoubleToString(double value, char *bufOut, int prec_digits) +{ + value = denormal_filter_double2(value); + + if (value<0.0) + { + value=-value; + *bufOut++ = '-'; + } + + if (value < 1e-20) + { + *bufOut++ = '0'; + *bufOut = 0; + return bufOut; + } + + if (value > 2147483647.0) + { + if (value >= 1.0e40) sprintf(bufOut, "%e", value); + else sprintf(bufOut, "%.*f", wdl_min(prec_digits,8), value); + while (*bufOut) bufOut++; + return bufOut; + } + + unsigned int value_i, frac, frac2; + int prec_digits2 = 0; + + if (prec_digits>0) + { + static const unsigned int scales[9] = + { + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 + }; + + value_i = (unsigned int)value; + const int value_digits = + value_i >= 10000 ? ( + value_i >= 1000000 ? + (value_i >= 100000000 ? + (value_i >= 1000000000 ? 10 : 9) : + (value_i >= 10000000 ? 8 : 7)) : + (value_i >= 100000 ? 6 : 5) + ) + : + ( + value_i >= 100 ? + (value_i >= 1000 ? 4 : 3) : + (value_i >= 10 ? 2 : 1) + ); + + // double precision is limited to about 17 decimal digits of meaningful values + if (prec_digits + value_digits > 17) prec_digits = 17-value_digits; + + if (prec_digits > 9) + { + prec_digits2 = prec_digits - 9; + prec_digits = 9; + if (prec_digits2 > 9) prec_digits2 = 9; + } + + const unsigned int prec_scale = scales[prec_digits-1]; + const double dfrac = (value - value_i) * prec_scale; + if (prec_digits2 > 0) + { + const unsigned int prec_scale2 = scales[prec_digits2-1]; + frac = (unsigned int) dfrac; + + double dfrac2 = (dfrac - frac) * prec_scale2; + frac2 = (unsigned int) (dfrac2 + 0.5); + + const int prec_scale2_small = wdl_min(prec_scale2/1024,10); + + if (frac2 <= prec_scale2_small) frac2=0; + else if (frac2 >= prec_scale2 - prec_scale2_small - 1) frac2=prec_scale2; + + if (frac2 >= prec_scale2) + { + frac2 -= prec_scale2; + frac++; + } + } + else + { + frac = (unsigned int) (dfrac + 0.5); + frac2 = 0; + const int prec_scale_small = wdl_min(prec_scale/1024,10); + if (frac <= prec_scale_small) frac=0; + else if (frac>=prec_scale-prec_scale_small - 1) frac=prec_scale; + } + + if (frac >= prec_scale) // round up to next integer + { + frac -= prec_scale; + value_i++; + } + } + else // round to int + { + value_i = (unsigned int)(value+0.5); + frac2 = frac = 0; + } + + char digs[32]; + + if (value_i) + { + int tmp=value_i; + int x = 0; + do { + const int a = (tmp%10); + tmp/=10; + digs[x++]='0' + a; + } while (tmp); + + while (x>0) *bufOut++ = digs[--x]; + } + else + { + *bufOut++ = '0'; + } + + + if (frac || frac2) + { + int x = 0; + int tmp=frac; + int dleft = prec_digits; + *bufOut++='.'; + + if (frac) do + { + const int a = (tmp%10); + tmp /= 10; + if (x || a || frac2) digs[x++] = '0'+a; + } while (dleft-- > 0 && tmp); + + while (dleft-->0) *bufOut++ = '0'; + while (x>0) *bufOut++ = digs[--x]; + // x is 0 + + if (frac2) + { + tmp=frac2; + dleft = prec_digits2; + do + { + const int a = (tmp%10); + tmp /= 10; + if (x || a) digs[x++] = '0'+a; + } while (dleft-- > 0 && tmp); + + while (dleft-->0) *bufOut++ = '0'; + while (x>0) *bufOut++ = digs[--x]; + } + } + + *bufOut = 0; + return bufOut; +} + +int ProjectContextFormatString(char *outbuf, size_t outbuf_size, const char *fmt, va_list va) +{ + int wroffs=0; + + while (*fmt && outbuf_size > 1) + { + char c = *fmt++; + if (c != '%') + { + outbuf[wroffs++] = c != '\n' ? c : ' '; + outbuf_size--; + continue; + } + + if (*fmt == '%') + { + outbuf[wroffs++] = '%'; + outbuf_size--; + fmt++; + continue; + } + + + const char *ofmt = fmt-1; + bool want_abort=false; + + int has_prec=0; + int prec=0; + if (*fmt == '.') + { + has_prec=1; + fmt++; + while (*fmt >= '0' && *fmt <= '9') prec = prec*10 + (*fmt++-'0'); + if (*fmt != 'f' || prec < 0 || prec>20) + { + want_abort=true; + } + } + else if (*fmt == '0') + { + has_prec=2; + fmt++; + while (*fmt >= '0' && *fmt <= '9') prec = prec*10 + (*fmt++-'0'); + if (*fmt != 'x' && *fmt != 'X' && *fmt != 'd' && *fmt != 'u') + { + want_abort=true; + } + } + + c = *fmt++; + if (!want_abort) switch (c) + { + case '@': + case 'p': + case 's': + { + const char *str=va_arg(va,const char *); + bool prefer_quoteless = true; + if (c == 'p' && *fmt == '~') // %p~ to bias towards "string" (legacy) + { + prefer_quoteless = false; + fmt++; + } + const char qc = outbuf_size >= 3 && c != 's' ? getConfigStringQuoteChar(str, prefer_quoteless) : ' '; + + if (qc != ' ') + { + outbuf[wroffs++] = qc ? qc : '`'; + outbuf_size-=2; // will add trailing quote below + } + + if (str) while (outbuf_size > 1 && *str) + { + char v = *str++; + if (!qc && v == '`') v = '\''; + outbuf[wroffs++] = v != '\n' && v != '\r' ? v : ' '; + outbuf_size--; + } + + if (qc != ' ') + { + outbuf[wroffs++] = qc ? qc : '`'; + // outbuf_size already decreased above + } + } + break; + case 'c': + { + int v = (va_arg(va,int)) & 0xff; + outbuf[wroffs++] = v != '\n' ? v : ' '; + outbuf_size--; + } + break; + case 'd': + { + int v = va_arg(va,int); + if (v<0) + { + outbuf[wroffs++] = '-'; + outbuf_size--; + v=-v; // this won't handle -2147483648 right, todo special case? + } + + char tab[32]; + int x=0; + do + { + tab[x++] = v%10; + v/=10; + } + while (v); + if (has_prec == 2) while (x= 0 && outbuf_size>1) + { + outbuf[wroffs++] = '0' + tab[x]; + outbuf_size--; + } + } + break; + case 'u': + { + unsigned int v = va_arg(va,unsigned int); + + char tab[32]; + int x=0; + do + { + tab[x++] = v%10; + v/=10; + } + while (v); + if (has_prec == 2) while (x= 0 && outbuf_size>1) + { + outbuf[wroffs++] = '0' + tab[x]; + outbuf_size--; + } + } + break; + case 'x': + case 'X': + { + const char base = (c - 'x') + 'a'; + unsigned int v = va_arg(va,unsigned int); + + char tab[32]; + int x=0; + do + { + tab[x++] = v&0xf; + v>>=4; + } + while (v); + + if (has_prec == 2) while (x= 0 && outbuf_size>1) + { + if (tab[x] < 10) + outbuf[wroffs++] = '0' + tab[x]; + else + outbuf[wroffs++] = base + tab[x] - 10; + + outbuf_size--; + } + } + break; + case 'f': + { + double v = va_arg(va,double); + if (outbuf_size<64) + { + char tmp[64]; + projectcontext_fastDoubleToString(v,tmp,has_prec?prec:6); + const char *str = tmp; + while (outbuf_size > 1 && *str) + { + outbuf[wroffs++] = *str++; + outbuf_size--; + } + } + else + { + const char *p=projectcontext_fastDoubleToString(v,outbuf+wroffs,has_prec?prec:6); + int amt = (int) (p-(outbuf+wroffs)); + wroffs += amt; + outbuf_size-=amt; + } + } + break; + default: + want_abort=true; + break; + } + if (want_abort) + { + fmt=ofmt; + break; + } + } + + outbuf += wroffs; + outbuf[0] = 0; + if (outbuf_size<2||!*fmt) + return wroffs; + +#if defined(_WIN32) && defined(_MSC_VER) + // _vsnprintf() does not always null terminate (see below) + _vsnprintf(outbuf,outbuf_size,fmt,va); +#else + // vsnprintf() on non-win32, always null terminates + vsnprintf(outbuf,outbuf_size,fmt,va); +#endif + + int l; + outbuf_size--; + for (l = 0; l < outbuf_size && outbuf[l]; l ++) if (outbuf[l] == '\n') outbuf[l] = ' '; + +#if defined(_WIN32) && defined(_MSC_VER) + // nul terminate for _vsnprintf() + outbuf[l]=0; +#endif + + return wroffs+l; +} + + + +class ProjectStateContext_Mem : public ProjectStateContext +{ +public: + + ProjectStateContext_Mem(WDL_HeapBuf *hb, int rwflags) + { + m_rwflags=rwflags; + m_heapbuf=hb; + m_pos=0; + m_tmpflag=0; +#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB + memset(&m_compstream,0,sizeof(m_compstream)); + m_usecomp=0; +#endif + } + + virtual ~ProjectStateContext_Mem() + { + #ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB + if (m_usecomp==1) + { + FlushComp(true); + deflateEnd(&m_compstream); + } + else if (m_usecomp==2) + { + inflateEnd(&m_compstream); + } + #endif + }; + + virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...); + virtual int GetLine(char *buf, int buflen); // returns -1 on eof + + virtual WDL_INT64 GetOutputSize() { return m_heapbuf ? m_heapbuf->GetSize() : 0; } + + virtual int GetTempFlag() { return m_tmpflag; } + virtual void SetTempFlag(int flag) { m_tmpflag=flag; } + + int m_pos; + WDL_HeapBuf *m_heapbuf; + int m_tmpflag; + int m_rwflags; + +#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB + int DecompressData() + { + if (m_pos >= m_heapbuf->GetSize()) return 1; + + m_compstream.next_in = (unsigned char *)m_heapbuf->Get() + m_pos; + m_compstream.avail_in = m_heapbuf->GetSize()-m_pos; + m_compstream.total_in = 0; + + int outchunk = 65536; + m_compstream.next_out = (unsigned char *)m_compdatabuf.Add(NULL,outchunk); + m_compstream.avail_out = outchunk; + m_compstream.total_out = 0; + + int e = inflate(&m_compstream,Z_NO_FLUSH); + + m_pos += m_compstream.total_in; + m_compdatabuf.Add(NULL,m_compstream.total_out - outchunk); // rewind + + return e != Z_OK; + } + + void FlushComp(bool eof) + { + while (m_compdatabuf.Available()>=WDL_MEMPROJECTCONTEXT_ZLIB_CHUNKSIZE || eof) + { + if (!m_heapbuf->GetSize()) m_heapbuf->SetGranul(256*1024); + m_compstream.next_in = (unsigned char *)m_compdatabuf.Get(); + m_compstream.avail_in = m_compdatabuf.Available(); + m_compstream.total_in = 0; + + int osz = m_heapbuf->GetSize(); + + int newsz=osz + wdl_max(m_compstream.avail_in,8192) + 256; + m_compstream.next_out = (unsigned char *)m_heapbuf->Resize(newsz, false) + osz; + if (m_heapbuf->GetSize()!=newsz) return; // ERROR + m_compstream.avail_out = newsz-osz; + m_compstream.total_out=0; + + deflate(&m_compstream,eof?Z_SYNC_FLUSH:Z_NO_FLUSH); + + m_heapbuf->Resize(osz+m_compstream.total_out,false); + m_compdatabuf.Advance(m_compstream.total_in); + if (m_compstream.avail_out) break; // no need to process anymore + + } + m_compdatabuf.Compact(); + } + + // these will be used for either decompression or compression, fear + int m_usecomp; // 0=?, -1 = fail, 1=yes + WDL_Queue m_compdatabuf; + z_stream m_compstream; +#endif + +}; + +// returns length, modifies ptr to point to tmp if newline needed to be filtered +static int filter_newline_buf(const char **ptr, char *tmp, int tmpsz) +{ + const char *use_buf = *ptr; + if (!use_buf) return -1; + + int l; + for (l=0; use_buf[l] && use_buf[l] != '\n'; l++); + + if (!use_buf[l]) return l; + + lstrcpyn_safe(tmp,use_buf,tmpsz); + *ptr=tmp; + + if (l >= tmpsz) return tmpsz-1; + + for (;tmp[l]; l++) if (tmp[l] == '\n') tmp[l] = ' '; // replace any newlines with spaces + return l; +} + + +void ProjectStateContext_Mem::AddLine(const char *fmt, ...) +{ + if (!m_heapbuf || !(m_rwflags&2)) return; + + char tmp[8192]; + + const char *use_buf; + int l; + + va_list va; + va_start(va,fmt); + + if (fmt && fmt[0] == '%' && (fmt[1] == 's' || fmt[1] == 'S') && !fmt[2]) + { + use_buf = va_arg(va,const char *); + l = filter_newline_buf(&use_buf,tmp,(int)sizeof(tmp)) + 1; + } + else + { + l = ProjectContextFormatString(tmp,sizeof(tmp),fmt, va) + 1; + use_buf = tmp; + } + va_end(va); + + if (l < 1) return; + + +#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB + if (!m_usecomp) + { + if (deflateInit(&m_compstream,WDL_MEMPROJECTCONTEXT_USE_ZLIB)==Z_OK) m_usecomp=1; + else m_usecomp=-1; + } + + if (m_usecomp==1) + { + m_compdatabuf.Add(use_buf,(int)l); + FlushComp(false); + return; + } +#endif + + + const int sz=m_heapbuf->GetSize(); + if (!sz && m_heapbuf->GetGranul() < 256*1024) + { + m_heapbuf->SetGranul(256*1024); + } + + char *p = (char *)m_heapbuf->ResizeOK(sz+l); + if (!p) + { + // ERROR, resize to 0 and return + m_heapbuf->Resize(0); + m_heapbuf=0; + return; + } + memcpy(p+sz,use_buf,l); +} + +int ProjectStateContext_Mem::GetLine(char *buf, int buflen) // returns -1 on eof +{ + if (!m_heapbuf || !(m_rwflags&1)) return -1; + + buf[0]=0; + + +#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB + if (!m_usecomp) + { + unsigned char hdr[]={0x78,0x01}; + if (m_heapbuf->GetSize()>2 && !memcmp(hdr,m_heapbuf->Get(),4) && inflateInit(&m_compstream)==Z_OK) m_usecomp=2; + else m_usecomp=-1; + } + if (m_usecomp==2) + { + int x=0; + for (;;) + { + const char *p = (const char*) m_compdatabuf.Get(); + for (x = 0; x < m_compdatabuf.Available() && p[x] && p[x] != '\r' && p[x] != '\n'; x ++); + while (x >= m_compdatabuf.Available()) + { + int err = DecompressData(); + p = (const char *)m_compdatabuf.Get(); + for (; x < m_compdatabuf.Available() && p[x] && p[x] != '\r' && p[x] != '\n'; x ++); + + if (err) break; + } + + if (x||!m_compdatabuf.Available()) break; + + m_compdatabuf.Advance(1); // skip over nul or newline char + } + + if (!x) return -1; + + if (buflen > 0 && buf) + { + int l = wdl_min(buflen-1,x); + memcpy(buf,m_compdatabuf.Get(),l); + buf[l]=0; + } + + m_compdatabuf.Advance(x+1); + m_compdatabuf.Compact(); + return 0; + } +#endif + + + int avail = m_heapbuf->GetSize() - m_pos; + const char *p=(const char *)m_heapbuf->Get() + m_pos; + while (avail > 0 && (p[0] =='\r'||p[0]=='\n'||!p[0]||p[0] == ' ' || p[0] == '\t')) + { + p++; + m_pos++; + avail--; + } + if (avail <= 0) return -1; + + int x; + for (x = 0; x < avail && p[x] && p[x] != '\n'; x ++); + m_pos += x+1; + + if (buflen > 0&&buf) + { + int l = buflen-1; + if (l>x) l=x; + memcpy(buf,p,l); + if (l>0 && buf[l-1]=='\r') l--; + buf[l]=0; + } + return 0; +} + +class ProjectStateContext_File : public ProjectStateContext +{ +public: + + ProjectStateContext_File(WDL_FileRead *rd, WDL_FileWrite *wr) + { + m_rd=rd; + m_wr=wr; + m_indent=0; + m_bytesOut=0; + m_errcnt=false; + m_tmpflag=0; + _rdbuf_pos = _rdbuf_valid = 0; + } + virtual ~ProjectStateContext_File(){ delete m_rd; delete m_wr; }; + + virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...); + virtual int GetLine(char *buf, int buflen); // returns -1 on eof + + virtual WDL_INT64 GetOutputSize() { return m_bytesOut; } + + virtual int GetTempFlag() { return m_tmpflag; } + virtual void SetTempFlag(int flag) { m_tmpflag=flag; } + + bool HasError() { return m_errcnt; } + + WDL_INT64 m_bytesOut WDL_FIXALIGN; + + WDL_FileRead *m_rd; + WDL_FileWrite *m_wr; + + char rdbuf[4096]; + int _rdbuf_pos, _rdbuf_valid; + + int m_indent; + int m_tmpflag; + bool m_errcnt; + +}; + +int ProjectStateContext_File::GetLine(char *buf, int buflen) +{ + if (!m_rd||buflen<3) return -1; + + char * const buf_orig=buf; + int rdpos = _rdbuf_pos; + int rdvalid = _rdbuf_valid; + buflen -= 2; + + for (;;) + { + while (rdpos < rdvalid) + { + char c=rdbuf[rdpos++]; + switch (c) + { + case ' ': case '\r': case '\n': case '\t': break; + default: + *buf++=c; + + do + { + int mxl = rdvalid - rdpos; + if (mxl > buflen) mxl=buflen; + while (mxl-->0) + { + char c2 = rdbuf[rdpos++]; + if (c2=='\n') goto finished; + + *buf++ = c2; + buflen--; + } + if (rdpos>=rdvalid) + { + rdpos = 0; + rdvalid = m_rd->Read(rdbuf, sizeof(rdbuf)); + if (rdvalid<1) break; + } + } + while (buflen > 0); + + finished: + _rdbuf_pos=rdpos; + _rdbuf_valid=rdvalid; + + if (buf > buf_orig && buf[-1] == '\r') buf--; + *buf=0; + return 0; + } + } + + rdpos = 0; + rdvalid = m_rd->Read(rdbuf, sizeof(rdbuf)); + if (rdvalid<1) + { + buf[0]=0; + return -1; + } + } +} + +void ProjectStateContext_File::AddLine(const char *fmt, ...) +{ + if (m_wr && !m_errcnt) + { + int err=0; + + char tmp[8192]; + const char *use_buf; + va_list va; + va_start(va,fmt); + int l; + + if (fmt && fmt[0] == '%' && (fmt[1] == 's' || fmt[1] == 'S') && !fmt[2]) + { + // special case "%s" passed, directly use it + use_buf = va_arg(va,const char *); + l = filter_newline_buf(&use_buf,tmp,(int)sizeof(tmp)); + } + else + { + l = ProjectContextFormatString(tmp,sizeof(tmp),fmt, va); + use_buf = tmp; + } + + va_end(va); + if (l < 0) return; + + + int a=m_indent; + if (use_buf[0] == '<') m_indent+=2; + else if (use_buf[0] == '>') a=(m_indent-=2); + + if (a>0) + { + m_bytesOut+=a; + char tb[128]; + memset(tb,' ',a < (int)sizeof(tb) ? a : (int)sizeof(tb)); + while (a>0) + { + const int tl = a < (int)sizeof(tb) ? a : (int)sizeof(tb); + a-=tl; + m_wr->Write(tb,tl); + } + } + + err |= m_wr->Write(use_buf,l) != l; + err |= m_wr->Write("\r\n",2) != 2; + m_bytesOut += 2 + l; + + if (err) m_errcnt=true; + } +} + + + +ProjectStateContext *ProjectCreateFileRead(const char *fn) +{ + WDL_FileRead *rd = new WDL_FileRead(fn,0,65536,1); + if (!rd || !rd->IsOpen()) + { + delete rd; + return NULL; + } + return new ProjectStateContext_File(rd,NULL); +} +ProjectStateContext *ProjectCreateFileWrite(const char *fn) +{ + WDL_FileWrite *wr = new WDL_FileWrite(fn); + if (!wr || !wr->IsOpen()) + { + delete wr; + return NULL; + } + return new ProjectStateContext_File(NULL,wr); +} + + +ProjectStateContext *ProjectCreateMemCtx(WDL_HeapBuf *hb) +{ + return new ProjectStateContext_Mem(hb,3); +} + +ProjectStateContext *ProjectCreateMemCtx_Read(const WDL_HeapBuf *hb) +{ + return new ProjectStateContext_Mem((WDL_HeapBuf *)hb,1); +} +ProjectStateContext *ProjectCreateMemCtx_Write(WDL_HeapBuf *hb) +{ + return new ProjectStateContext_Mem(hb,2); +} + + + + +class ProjectStateContext_FastQueue : public ProjectStateContext +{ +public: + + ProjectStateContext_FastQueue(WDL_FastQueue *fq) + { + m_fq = fq; + m_tmpflag=0; + } + + virtual ~ProjectStateContext_FastQueue() + { + }; + + virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...); + virtual int GetLine(char *buf, int buflen) { return -1; }//unsup + + virtual WDL_INT64 GetOutputSize() { return m_fq ? m_fq->Available() : 0; } + + virtual int GetTempFlag() { return m_tmpflag; } + virtual void SetTempFlag(int flag) { m_tmpflag=flag; } + + WDL_FastQueue *m_fq; + int m_tmpflag; + + +}; + +void ProjectStateContext_FastQueue::AddLine(const char *fmt, ...) +{ + if (!m_fq) return; + + va_list va; + va_start(va,fmt); + + char tmp[8192]; + if (fmt && fmt[0] == '%' && (fmt[1] == 's' || fmt[1] == 'S') && !fmt[2]) + { + const char *use_buf = va_arg(va,const char *); + const int l = filter_newline_buf(&use_buf,tmp,(int)sizeof(tmp)); + if (use_buf) m_fq->Add(use_buf, l + 1); + } + else + { + const int l = ProjectContextFormatString(tmp,sizeof(tmp),fmt, va); + if (l>0) m_fq->Add(tmp, l+1); + } + va_end(va); +} + + + + + + + +ProjectStateContext *ProjectCreateMemWriteFastQueue(WDL_FastQueue *fq) // only write! +{ + return new ProjectStateContext_FastQueue(fq); +} + +bool ProjectContext_GetNextLine(ProjectStateContext *ctx, LineParser *lpOut) +{ + for (;;) + { + char linebuf[4096]; + if (ctx->GetLine(linebuf,sizeof(linebuf))) + { + lpOut->parse(""); + return false; + } + + if (lpOut->parse(linebuf)||lpOut->getnumtokens()<=0) continue; + + return true; // success! + + } +} + + +bool ProjectContext_EatCurrentBlock(ProjectStateContext *ctx, ProjectStateContext *ctxOut) +{ + int child_count=1; + if (ctx) for (;;) + { + char linebuf[4096]; + if (ctx->GetLine(linebuf,sizeof(linebuf))) break; + const char *sp = linebuf; + while (*sp == ' ' || *sp == '\t') sp++; + + const char *p = sp; + if (*p == '\'' || *p == '"' || *p == '`') p++; // skip a quote if any + if (p[0] == '>') if (--child_count < 1) return true; + + if (ctxOut) ctxOut->AddLine("%s",sp); + + if (p[0] == '<') child_count++; + } + + return false; +} + + +#include "wdl_base64.h" + +int cfg_decode_binary(ProjectStateContext *ctx, WDL_HeapBuf *hb) // 0 on success, doesnt clear hb +{ + int child_count=1; + for (;;) + { + char linebuf[4096]; + if (ctx->GetLine(linebuf,sizeof(linebuf))) break; + + const char *p = linebuf; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\'' || *p == '"' || *p == '`') p++; // skip a quote if any + + if (p[0] == '<') child_count++; + else if (p[0] == '>') { if (child_count-- == 1) return 0; } + else if (child_count == 1 && p[0]) + { + unsigned char buf[3200]; + const int buf_l=wdl_base64decode(p,buf,sizeof(buf)); + if (buf_l) + { + const int os=hb->GetSize(); + char *dest = (char*)hb->ResizeOK(os+buf_l); + if (dest) memcpy(dest+os,buf,buf_l); + } + } + } + return -1; +} + +void cfg_encode_binary(ProjectStateContext *ctx, const void *ptr, int len) +{ + if (!ctx || len < 1) return; + + const unsigned char *p=(const unsigned char *)ptr; + if (len > 128 && len < (1<<30)) + { + // we could (probably should) use dynamic_cast<> here, but as we span modules this + // raises all kinds of questions (especially with VC having the option to disable RTTI). + // for now, we assume that the first void * in an object is the vtable pointer. with + // testing, of course. + WDL_FastQueue *fq = NULL; + WDL_HeapBuf *hb = NULL; +#ifndef WDL_MEMPROJECTCONTEXT_USE_ZLIB + static const ProjectStateContext_Mem hb_def(NULL,0); +#endif + static const ProjectStateContext_FastQueue fq_def(NULL); + if (*(void **)ctx == *(void **)&fq_def) + { + fq=((ProjectStateContext_FastQueue*)ctx)->m_fq; + } +#ifndef WDL_MEMPROJECTCONTEXT_USE_ZLIB + else if (*(void **)ctx == *(void **)&hb_def) + { + hb=((ProjectStateContext_Mem*)ctx)->m_heapbuf; + } +#endif + + if (fq||hb) + { + const int linelen8 = 280/8; + + const int enc_len = ((len+2)/3)*4; // every 3 characters end up as 4 + const int lines = (enc_len + linelen8*8 - 1) / (linelen8*8); + + char *wr = NULL; + if (fq) + { + wr = (char*)fq->Add(WDL_FASTQUEUE_ADD_NOZEROBUF,enc_len + lines); + } + else if (hb) + { + const int oldsz=hb->GetSize(); + wr=(char*)hb->ResizeOK(oldsz + enc_len + lines,false); + if (wr) wr+=oldsz; + } + + if (wr) + { + #ifdef _DEBUG + char * const wr_end=wr + enc_len + lines; + #endif + + int lpos = 0; + + while (len >= 6) + { + const int accum = (p[0] << 16) + (p[1] << 8) + p[2]; + const int accum2 = (p[3] << 16) + (p[4] << 8) + p[5]; + wr[0] = wdl_base64_alphabet[(accum >> 18) & 0x3F]; + wr[1] = wdl_base64_alphabet[(accum >> 12) & 0x3F]; + wr[2] = wdl_base64_alphabet[(accum >> 6) & 0x3F]; + wr[3] = wdl_base64_alphabet[accum & 0x3F]; + wr[4] = wdl_base64_alphabet[(accum2 >> 18) & 0x3F]; + wr[5] = wdl_base64_alphabet[(accum2 >> 12) & 0x3F]; + wr[6] = wdl_base64_alphabet[(accum2 >> 6) & 0x3F]; + wr[7] = wdl_base64_alphabet[accum2 & 0x3F]; + wr+=8; + p+=6; + len-=6; + + if (++lpos >= linelen8) { *wr++= 0; lpos=0; } + } + + if (len >= 3) + { + const int accum = (p[0] << 16) + (p[1] << 8) + p[2]; + wr[0] = wdl_base64_alphabet[(accum >> 18) & 0x3F]; + wr[1] = wdl_base64_alphabet[(accum >> 12) & 0x3F]; + wr[2] = wdl_base64_alphabet[(accum >> 6) & 0x3F]; + wr[3] = wdl_base64_alphabet[accum & 0x3F]; + wr+=4; + p+=3; + len-=3; + lpos+=3; + } + + if (len>0) + { + lpos += len; + if (len == 2) + { + const int accum = (p[0] << 8) | p[1]; + wr[0] = wdl_base64_alphabet[(accum >> 10) & 0x3F]; + wr[1] = wdl_base64_alphabet[(accum >> 4) & 0x3F]; + wr[2] = wdl_base64_alphabet[(accum & 0xF)<<2]; + } + else + { + const int accum = p[0]; + wr[0] = wdl_base64_alphabet[(accum >> 2) & 0x3F]; + wr[1] = wdl_base64_alphabet[(accum & 0x3)<<4]; + wr[2] = '='; + } + wr[3] = '='; + wr+=4; + } + if (lpos>0) *wr++=0; + + #ifdef _DEBUG + if (wr != wr_end) wdl_log("cfg_encode_binary: block mode size mismatch %d!\n", (int)(wr-wr_end)); + #endif + return; + } + } + } + + do + { + char buf[256]; + int thiss=len; + if (thiss > 96) thiss=96; + wdl_base64encode(p,buf,thiss); + + ctx->AddLine("%s",buf); + p+=thiss; + len-=thiss; + } + while (len>0); + +} + + +int cfg_decode_textblock(ProjectStateContext *ctx, WDL_FastString *str) // 0 on success, appends to str +{ + int child_count=1; + bool did_firstline=!!str->Get()[0]; + for (;;) + { + char linebuf[4096]; + if (ctx->GetLine(linebuf,sizeof(linebuf))) break; + + const char *p = linebuf; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\'' || *p == '"' || *p == '`') p++; // skip a quote if any + + if (!p[0]) continue; + else if (p[0] == '<') child_count++; + else if (p[0] == '>') { if (child_count-- == 1) return 0; } + else if (child_count == 1) + { + const char *prefix = did_firstline ? "\r\n" : ""; + // lines can have a prefix immediately before | to specify the line ending of the previous line + switch (p[0]) + { + case 'c': prefix=""; p++; break; + case 'n': prefix="\n"; p++; break; + case 'r': prefix="\r"; p++; break; + case 'R': prefix="\n\r"; p++; break; + } + + if (p[0] == '|') + { + if (*prefix) str->Append(prefix); + str->Append(++p); + did_firstline=true; + } + } + } + return -1; + +} + +void cfg_encode_textblock(ProjectStateContext *ctx, const char *txt) // splits long lines +{ + while (*txt) + { + int l = 0; + while (txt[l] && l < 4000 && txt[l] != '\r' && txt[l] != '\n') l++; + ctx->AddLine("|%.*s",l,txt); + txt += l; + if (*txt == '\r') + { + if (*++txt== '\n') txt++; + } + else if (*txt == '\n') + { + if (*++txt == '\r') txt++; + } + } +} + +void cfg_encode_textblock2(ProjectStateContext *ctx, const char *txt) // preserves long lines, line endings +{ + char prefix = ' '; + while (*txt) + { + int l = 0; + while (txt[l] && l < 2000 && txt[l] != '\r' && txt[l] != '\n') l++; + ctx->AddLine("%c|%.*s",prefix,l,txt); + txt += l; + if (*txt == '\r') + { + prefix = 'r'; + if (*++txt== '\n') + { + txt++; + prefix = ' '; + } + } + else if (*txt == '\n') + { + prefix = 'n'; + if (*++txt == '\r') + { + txt++; + prefix = 'R'; + } + } + else if (*txt) prefix = 'c'; + else break; + + if (!*txt) + ctx->AddLine("%c|",prefix); + } +} + +char getConfigStringQuoteChar(const char *p, bool prefer_quoteless) +{ + if (!p || !*p) return '"'; + + char fc = *p; + int flags=0; + while (*p && flags!=15) + { + char c=*p++; + if (c=='"') flags|=1; + else if (c=='\'') flags|=2; + else if (c=='`') flags|=4; + else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') flags |= 8; + } + if (prefer_quoteless || flags == 7) + { + if (!(flags & 8) && fc != '"' && fc != '\'' && fc != '`' && fc != '#' && fc != ';') return ' '; + } + + if (!(flags & 1)) return '"'; + if (!(flags & 2)) return '\''; + if (!(flags & 4)) return '`'; + return 0; +} + +bool configStringWantsBlockEncoding(const char *in) // returns true if over 1k long, has newlines, or contains all quote chars +{ + int maxl = 1024, flags = 0; + while (--maxl) + { + switch (*in++) + { + case 0: return false; + case '\n': return true; + case '"': if ((flags|=1)==7) return true; break; + case '`': if ((flags|=2)==7) return true; break; + case '\'': if ((flags|=4)==7) return true; break; + } + } + return true; +} + +void makeEscapedConfigString(const char *in, WDL_FastString *out) +{ + char c; + if (!in || !*in) out->Set("\"\""); + else if ((c = getConfigStringQuoteChar(in))) + { + if (c == ' ') + { + out->Set(in); + } + else + { + out->Set(&c,1); + out->Append(in); + out->Append(&c,1); + } + } + else // ick, change ` into ' + { + out->Set("`"); + out->Append(in); + out->Append("`"); + char *p=(char *)out->Get()+1; + while (*p && p[1]) + { + if (*p == '`') *p='\''; + else if (*p == '\r' || *p == '\n') *p=' '; + p++; + } + } +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/projectcontext.h b/plugin-reaper-realearn/main/lib/WDL/WDL/projectcontext.h new file mode 100644 index 0000000..ee1400e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/projectcontext.h @@ -0,0 +1,105 @@ +#ifndef _PROJECTCONTEXT_H_ +#define _PROJECTCONTEXT_H_ + +#include "wdltypes.h" + +class WDL_FastString; +class WDL_HeapBuf; +class WDL_FastQueue; + +#ifndef _REAPER_PLUGIN_PROJECTSTATECONTEXT_DEFINED_ +#define _WDL_PROJECTSTATECONTEXT_DEFINED_ + +class ProjectStateContext // this is also defined in reaper_plugin.h (keep them identical, thx) +{ +public: + virtual ~ProjectStateContext(){}; + + virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...) = 0; + virtual int GetLine(char *buf, int buflen)=0; // returns -1 on eof + + virtual WDL_INT64 GetOutputSize()=0; + + virtual int GetTempFlag()=0; + virtual void SetTempFlag(int flag)=0; +}; + +#endif + +ProjectStateContext *ProjectCreateFileRead(const char *fn); +ProjectStateContext *ProjectCreateFileWrite(const char *fn); +ProjectStateContext *ProjectCreateMemCtx(WDL_HeapBuf *hb); // read or write (ugh, deprecated), be sure to delete it before accessing hb +ProjectStateContext *ProjectCreateMemCtx_Read(const WDL_HeapBuf *hb); // read only +ProjectStateContext *ProjectCreateMemCtx_Write(WDL_HeapBuf *hb); // write only, be sure to delete it before accessing hb +ProjectStateContext *ProjectCreateMemWriteFastQueue(WDL_FastQueue *fq); // only write! no need to do anything at all before accessing (can clear/reuse as necessary) + + +// helper functions +class LineParser; +bool ProjectContext_EatCurrentBlock(ProjectStateContext *ctx, + ProjectStateContext *ctxOut=NULL); // returns TRUE if got valid >, otherwise it means eof... + // writes to ctxOut if specified, will not write final > + +bool ProjectContext_GetNextLine(ProjectStateContext *ctx, LineParser *lpOut); // true if lpOut is valid + +char *projectcontext_fastDoubleToString(double value, char *bufOut, int prec_digits); // returns pointer to end of encoded string. prec_digits 0..18. +int ProjectContextFormatString(char *outbuf, size_t outbuf_size, const char *fmt, va_list va); // returns bytes used + +int cfg_decode_binary(ProjectStateContext *ctx, WDL_HeapBuf *hb); // 0 on success, doesnt clear hb +void cfg_encode_binary(ProjectStateContext *ctx, const void *ptr, int len); + +int cfg_decode_textblock(ProjectStateContext *ctx, WDL_FastString *str); // 0 on success, appends to str +void cfg_encode_textblock(ProjectStateContext *ctx, const char *text); // long lines get split by newlines +void cfg_encode_textblock2(ProjectStateContext *ctx, const char *text); // preserves newlines/long lines/etc (requires recent cfg_decode_textblock()) + +char getConfigStringQuoteChar(const char *in, bool prefer_quoteless=true); // returns 0 if no quote char available! +bool configStringWantsBlockEncoding(const char *in); // returns true if over 1k long, has newlines, or contains all quote chars +void makeEscapedConfigString(const char *in, WDL_FastString *out); + + +class ProjectStateContext_GenericRead : public ProjectStateContext +{ + protected: + const char *m_ptr; + const char *m_endptr; + int m_tmpflag; + + public: + ProjectStateContext_GenericRead(const void *buf, int sz) : m_tmpflag(0) + { + m_ptr = (const char *)buf; + m_endptr = m_ptr ? m_ptr+sz : NULL; + } + virtual ~ProjectStateContext_GenericRead() {} + + virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...) { } + virtual int GetLine(char *buf, int buflen) // returns -1 on eof + { + const char *p = m_ptr; + const char *ep = m_endptr; + + while (p < ep && (!*p || *p == '\t' || *p == '\r' || *p == '\n' || *p == ' ')) p++; + if (p >= ep) + { + m_ptr=p; + return -1; + } + + if (buflen > 0) + { + while (--buflen > 0 && *p) *buf++ = *p++; + *buf=0; + } + + while (*p) p++; + m_ptr=p+1; // skip NUL + + return 0; + } + virtual int GetTempFlag() { return m_tmpflag; } + virtual void SetTempFlag(int flag) { m_tmpflag=flag; } + virtual WDL_INT64 GetOutputSize() { return 0; } +}; + + +#endif//_PROJECTCONTEXT_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/prvhash/prvhash_core.h b/plugin-reaper-realearn/main/lib/WDL/WDL/prvhash/prvhash_core.h new file mode 100644 index 0000000..873335f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/prvhash/prvhash_core.h @@ -0,0 +1,168 @@ +/** + * prvhash_core.h version 3.3 + * + * The inclusion file for the "prvhash_core64", "prvhash_core32", + * "prvhash_core16", "prvhash_core8", "prvhash_core4", "prvhash_core2" PRVHASH + * core functions for various state variable sizes. + * + * Description is available at https://github.com/avaneev/prvhash + * + * License + * + * Copyright (c) 2020-2021 Aleksey Vaneev + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef PRVHASH_CORE_INCLUDED +#define PRVHASH_CORE_INCLUDED + +#include + +/** + * This function runs a single PRVHASH random number generation round. This + * function can be used both as a hash generator and as a general-purpose + * random number generator. In the latter case, it is advisable to initially + * run this function 5 times before using its random output, to neutralize any + * possible oddities of "Seed"'s and "lcg"'s initial values. + * + * To generate hashes, the "lcg" variable should be XORed with entropy input + * prior to calling this function. + * + * @param[in,out] Seed0 The current "Seed" value. Can be initialized to any + * value. + * @param[in,out] lcg0 The current "lcg" value. Can be initialized to any + * value. + * @param[in,out] Hash0 Current hash word in a hash word array. + * @return Current random value. + */ + +inline uint64_t prvhash_core64( uint64_t* const Seed0, uint64_t* const lcg0, + uint64_t* const Hash0 ) +{ + uint64_t Seed = *Seed0; uint64_t lcg = *lcg0; uint64_t Hash = *Hash0; + + const uint64_t plcg = lcg; + const uint64_t mx = Seed * ( lcg - ~lcg ); + const uint64_t rs = mx >> 32 | mx << 32; + lcg += ~mx; + Hash += rs; + Seed = Hash ^ plcg; + const uint64_t out = lcg ^ rs; + + *Seed0 = Seed; *lcg0 = lcg; *Hash0 = Hash; + + return( out ); +} + +inline uint32_t prvhash_core32( uint32_t* const Seed0, uint32_t* const lcg0, + uint32_t* const Hash0 ) +{ + uint32_t Seed = *Seed0; uint32_t lcg = *lcg0; uint32_t Hash = *Hash0; + + const uint32_t plcg = lcg; + const uint32_t mx = Seed * ( lcg - ~lcg ); + const uint32_t rs = mx >> 16 | mx << 16; + lcg += ~mx; + Hash += rs; + Seed = Hash ^ plcg; + const uint32_t out = lcg ^ rs; + + *Seed0 = Seed; *lcg0 = lcg; *Hash0 = Hash; + + return( out ); +} + +inline uint16_t prvhash_core16( uint16_t* const Seed0, uint16_t* const lcg0, + uint16_t* const Hash0 ) +{ + uint16_t Seed = *Seed0; uint16_t lcg = *lcg0; uint16_t Hash = *Hash0; + + const uint16_t plcg = lcg; + const uint16_t mx = (uint16_t) ( Seed * ( lcg - ~lcg )); + const uint16_t rs = (uint16_t) ( mx >> 8 | mx << 8 ); + lcg += (uint16_t) ~mx; + Hash += rs; + Seed = (uint16_t) ( Hash ^ plcg ); + const uint16_t out = (uint16_t) ( lcg ^ rs ); + + *Seed0 = Seed; *lcg0 = lcg; *Hash0 = Hash; + + return( out ); +} + +inline uint8_t prvhash_core8( uint8_t* const Seed0, uint8_t* const lcg0, + uint8_t* const Hash0 ) +{ + uint8_t Seed = *Seed0; uint8_t lcg = *lcg0; uint8_t Hash = *Hash0; + + const uint8_t plcg = lcg; + const uint8_t mx = (uint8_t) ( Seed * ( lcg - ~lcg )); + const uint8_t rs = (uint8_t) ( mx >> 4 | mx << 4 ); + lcg += (uint8_t) ~mx; + Hash += rs; + Seed = (uint8_t) ( Hash ^ plcg ); + const uint8_t out = (uint8_t) ( lcg ^ rs ); + + *Seed0 = Seed; *lcg0 = lcg; *Hash0 = Hash; + + return( out ); +} + +inline uint8_t prvhash_core4( uint8_t* const Seed0, uint8_t* const lcg0, + uint8_t* const Hash0 ) +{ + uint8_t Seed = *Seed0; uint8_t lcg = *lcg0; uint8_t Hash = *Hash0; + + const uint8_t plcg = lcg; + const uint8_t mx = (uint8_t) (( Seed * ( lcg - ~lcg )) & 15 ); + const uint8_t rs = (uint8_t) (( mx >> 2 | mx << 2 ) & 15 ); + lcg += (uint8_t) ~mx; + lcg &= 15; + Hash += rs; + Hash &= 15; + Seed = (uint8_t) ( Hash ^ plcg ); + const uint8_t out = (uint8_t) ( lcg ^ rs ); + + *Seed0 = Seed; *lcg0 = lcg; *Hash0 = Hash; + + return( out ); +} + +inline uint8_t prvhash_core2( uint8_t* const Seed0, uint8_t* const lcg0, + uint8_t* const Hash0 ) +{ + uint8_t Seed = *Seed0; uint8_t lcg = *lcg0; uint8_t Hash = *Hash0; + + const uint8_t plcg = lcg; + const uint8_t mx = (uint8_t) (( Seed * ( lcg - ~lcg )) & 3 ); + const uint8_t rs = (uint8_t) (( mx >> 1 | mx << 1 ) & 3 ); + lcg += (uint8_t) ~mx; + lcg &= 3; + Hash += rs; + Hash &= 3; + Seed = (uint8_t) ( Hash ^ plcg ); + const uint8_t out = (uint8_t) ( lcg ^ rs ); + + *Seed0 = Seed; *lcg0 = lcg; *Hash0 = Hash; + + return( out ); +} + +#endif // PRVHASH_CORE_INCLUDED diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/ptrlist.h b/plugin-reaper-realearn/main/lib/WDL/WDL/ptrlist.h new file mode 100644 index 0000000..0f44d8f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/ptrlist.h @@ -0,0 +1,275 @@ +/* + WDL - ptrlist.h + Copyright (C) 2005 and later, 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. + +*/ + +/* + + This file provides a simple templated class for a list of pointers. By default this list + doesn't free any of the pointers, but you can call Empty(true) or Delete(x,true) to delete the pointer, + or you can use Empty(true,free) etc to call free (or any other function). + + Note: on certain compilers, instantiating with WDL_PtrList bla; will give a warning, since + the template will create code for "delete (void *)x;" which isn't technically valid. Oh well. + +*/ + +#ifndef _WDL_PTRLIST_H_ +#define _WDL_PTRLIST_H_ + +#include "heapbuf.h" + +template class WDL_PtrList +{ + public: + explicit WDL_PtrList(int defgran=4096) : m_hb(defgran) + { + } + + ~WDL_PtrList() + { + } + + void Prealloc(int sz) { m_hb.Prealloc(sz*sizeof(PTRTYPE *)); } + + PTRTYPE **GetList() const { return (PTRTYPE**)m_hb.Get(); } + PTRTYPE *Get(INT_PTR index) const + { + PTRTYPE **list = (PTRTYPE**)m_hb.Get(); + if (list && (UINT_PTR)index < (UINT_PTR)(m_hb.GetSize()/sizeof(PTRTYPE *))) return list[index]; + return NULL; + } + + int GetSize(void) const { return m_hb.GetSize()/(unsigned int)sizeof(PTRTYPE *); } + + int Find(const PTRTYPE *p) const + { + if (p) + { + PTRTYPE **list=(PTRTYPE **)m_hb.Get(); + int x; + const int n = GetSize(); + for (x = 0; x < n; x ++) if (list[x] == p) return x; + } + return -1; + } + int FindR(const PTRTYPE *p) const + { + if (p) + { + PTRTYPE **list=(PTRTYPE **)m_hb.Get(); + int x = GetSize(); + while (--x >= 0) if (list[x] == p) return x; + } + return -1; + } + + PTRTYPE *Add(PTRTYPE *item) + { + const int s=GetSize(); + PTRTYPE **list=(PTRTYPE **)m_hb.ResizeOK((s+1)*(unsigned int)sizeof(PTRTYPE*),false); + if (list) + { + list[s]=item; + return item; + } + return NULL; + } + + PTRTYPE *Set(int index, PTRTYPE *item) + { + PTRTYPE **list=(PTRTYPE **)m_hb.Get(); + if (list && index >= 0 && index < GetSize()) return list[index]=item; + return NULL; + } + + PTRTYPE *Insert(int index, PTRTYPE *item) + { + int s=GetSize(); + PTRTYPE **list = (PTRTYPE **)m_hb.ResizeOK((s+1)*(unsigned int)sizeof(PTRTYPE*),false); + + if (!list) return item; + + if (index<0) index=0; + + int x; + for (x = s; x > index; x --) list[x]=list[x-1]; + return (list[x] = item); + } + int FindSorted(const PTRTYPE *p, int (*compar)(const PTRTYPE **a, const PTRTYPE **b)) const + { + bool m; + int i = LowerBound(p,&m,compar); + return m ? i : -1; + } + PTRTYPE *InsertSorted(PTRTYPE *item, int (*compar)(const PTRTYPE **a, const PTRTYPE **b)) + { + bool m; + return Insert(LowerBound(item,&m,compar),item); + } + + void Delete(int index) + { + PTRTYPE **list=GetList(); + int size=GetSize(); + if (list && index >= 0 && index < size) + { + if (index < --size) memmove(list+index,list+index+1,(unsigned int)sizeof(PTRTYPE *)*(size-index)); + m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*),false); + } + } + void Delete(int index, bool wantDelete, void (*delfunc)(void *)=NULL) + { + PTRTYPE **list=GetList(); + int size=GetSize(); + if (list && index >= 0 && index < size) + { + if (wantDelete) + { + if (delfunc) delfunc(Get(index)); + else delete Get(index); + } + if (index < --size) memmove(list+index,list+index+1,(unsigned int)sizeof(PTRTYPE *)*(size-index)); + m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*),false); + } + } + void Delete(int index, void (*delfunc)(PTRTYPE *)) + { + PTRTYPE **list=GetList(); + int size=GetSize(); + if (list && index >= 0 && index < size) + { + if (delfunc) delfunc(Get(index)); + if (index < --size) memmove(list+index,list+index+1,(unsigned int)sizeof(PTRTYPE *)*(size-index)); + m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*),false); + } + } + void DeletePtr(const PTRTYPE *p) { Delete(Find(p)); } + void DeletePtr(const PTRTYPE *p, bool wantDelete, void (*delfunc)(void *)=NULL) { Delete(Find(p),wantDelete,delfunc); } + void DeletePtr(const PTRTYPE *p, void (*delfunc)(PTRTYPE *)) { Delete(Find(p),delfunc); } + + void Empty() + { + m_hb.Resize(0,false); + } + void Empty(bool wantDelete, void (*delfunc)(void *)=NULL) + { + if (wantDelete) + { + int x; + for (x = GetSize()-1; x >= 0; x --) + { + PTRTYPE* p = Get(x); + if (p) + { + if (delfunc) delfunc(p); + else delete p; + } + m_hb.Resize(x*(unsigned int)sizeof(PTRTYPE *),false); + } + } + m_hb.Resize(0,false); + } + void Empty(void (*delfunc)(PTRTYPE *)) + { + int x; + for (x = GetSize()-1; x >= 0; x --) + { + PTRTYPE* p = Get(x); + if (delfunc && p) delfunc(p); + m_hb.Resize(x*(unsigned int)sizeof(PTRTYPE *),false); + } + } + void EmptySafe(bool wantDelete=false,void (*delfunc)(void *)=NULL) + { + if (!wantDelete) Empty(); + else + { + WDL_PtrList tmp; + int x; + for(x=0;x 0) a = b+1; + else if (cmp < 0) c = b; + else + { + *ismatch = true; + return b; + } + } + *ismatch = false; + return a; + } + + void Compact() { m_hb.Resize(m_hb.GetSize(),true); } + + + int DeleteBatch(bool (*proc)(PTRTYPE *p, void *ctx), void *ctx=NULL) // proc returns true to remove item. returns number removed + { + const int sz = GetSize(); + int cnt=0; + PTRTYPE **rd = GetList(), **wr = rd; + for (int x = 0; x < sz; x ++) + { + if (!proc(*rd,ctx)) + { + if (rd != wr) *wr=*rd; + wr++; + cnt++; + } + rd++; + } + if (cnt < sz) m_hb.Resize(cnt * sizeof(PTRTYPE*),false); + return sz - cnt; + } + + private: + WDL_HeapBuf m_hb; + +}; + + +template class WDL_PtrList_DeleteOnDestroy : public WDL_PtrList +{ +public: + explicit WDL_PtrList_DeleteOnDestroy(void (*delfunc)(void *)=NULL, int defgran=4096) : WDL_PtrList(defgran), m_delfunc(delfunc) { } + ~WDL_PtrList_DeleteOnDestroy() + { + WDL_PtrList::EmptySafe(true,m_delfunc); + } +private: + void (*m_delfunc)(void *); +}; + +#endif + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/ptrlist_indexed.h b/plugin-reaper-realearn/main/lib/WDL/WDL/ptrlist_indexed.h new file mode 100644 index 0000000..1ef8727 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/ptrlist_indexed.h @@ -0,0 +1,142 @@ +#ifndef _WDL_PTRLIST_INDEXED_H_ +#define _WDL_PTRLIST_INDEXED_H_ + +#include "assocarray.h" +#include "ptrlist.h" + +template class WDL_IndexedPtrList { + public: + WDL_IndexedPtrList() { } + ~WDL_IndexedPtrList() { } + + int GetSize() const { + // do not _checkState(), GetSize() may be called while unlocked for estimation purposes + return m_list.GetSize(); + } + T * const *GetList() const { _checkState(); return m_list.GetList(); } + T *Get(INT_PTR idx) const { _checkState(); return m_list.Get(idx); } + int Find(const T *p) const + { + _checkState(); + if (!p) return -1; + const int *ret = m_index.GetPtr((INT_PTR)p); + return ret ? *ret : -1; + } + void Empty() { _checkState(); m_list.Empty(); m_index.DeleteAll(); } + void Empty(bool wantDelete, void (*delfunc)(void*)=NULL) { _checkState(); m_list.Empty(wantDelete,delfunc); m_index.DeleteAll(); } + void EmptySafe(bool wantDelete=false,void (*delfunc)(void *)=NULL) + { + _checkState(); + m_index.DeleteAll(); + m_list.EmptySafe(wantDelete,delfunc); + m_index.DeleteAll(); + } + void Delete(int idx, bool wantDelete=false, void (*delfunc)(void *)=NULL) + { + _checkState(); + T *item = m_list.Get(idx); + m_list.Delete(idx,wantDelete,delfunc); + if (item) + { + m_index.Delete((INT_PTR)item); + const int indexsz = m_index.GetSize(); + WDL_ASSERT(m_list.GetSize() == indexsz); + if (idx < indexsz) + { + for (int x=0;x idx) (*val)--; + } + } + } + } + } + void DeletePtr(const T *p) { Delete(Find(p)); } + void DeletePtr(const T *p, bool wantDelete, void (*delfunc)(void *)=NULL) { Delete(Find(p),wantDelete,delfunc); } + void Add(T *p) + { + _checkState(); + WDL_ASSERT(Find(p) < 0); + if (WDL_NORMALLY(p)) + { + const int sz = m_list.GetSize(); + m_list.Add(p); + m_index.Insert((INT_PTR)p,sz); + } + } + void RebuildIndex() + { + m_index.DeleteAll(); + for (int x = 0; x < m_list.GetSize(); x ++) + { + m_index.AddUnsorted((INT_PTR)m_list.Get(x),x); + } + m_index.Resort(); + _checkState(); + } + void Swap(int index1, int index2) + { + _checkState(); + if (index1 != index2 && + WDL_NORMALLY(index1>=0) && + WDL_NORMALLY(index1=0) && + WDL_NORMALLY(index2= listsz) { Add(p); return; } + + const int indexsz = m_index.GetSize(); + WDL_ASSERT(listsz==indexsz); + + for (int x=0;x= index) (*val)++; + WDL_ASSERT(*val != index); + } + } + m_list.Insert(index,p); + m_index.Insert((INT_PTR)p,index); + } + + WDL_PtrList m_list; + WDL_PtrKeyedArray m_index; + + WDL_IndexedPtrList &operator=(const WDL_IndexedPtrList &cp) + { + m_list = cp.m_list; + RebuildIndex(); + return *this; + } + + void _checkState() const + { +#ifdef _DEBUG + const int idxsz = m_index.GetSize(), listsz = m_list.GetSize(); + WDL_ASSERT(idxsz == listsz); +#endif + } +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/queue.h b/plugin-reaper-realearn/main/lib/WDL/WDL/queue.h new file mode 100644 index 0000000..11b6566 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/queue.h @@ -0,0 +1,269 @@ +/* + WDL - queue.h + Copyright (C) 2005 and later, 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. + +*/ + +/* + + This file provides a simple class for a FIFO queue of bytes. It uses a simple buffer, + so should not generally be used for large quantities of data (it can advance the queue + pointer, but Compact() needs to be called regularly to keep memory usage down, and when + it is called, there's a memcpy() penalty for the remaining data. oh well, is what it is). + + You may also wish to look at fastqueue.h or circbuf.h if these limitations aren't acceptable. + +*/ + +#ifndef _WDL_QUEUE_H_ +#define _WDL_QUEUE_H_ + +#include "heapbuf.h" + + +class WDL_Queue +{ +public: + WDL_Queue(int hbgran=4096) : m_hb(hbgran), m_pos(0) { } + ~WDL_Queue() { } + + template void* AddT(T* buf) + { + return Add(buf, sizeof(T)); + } + + void *Add(const void *buf, int len) + { + int olen=m_hb.GetSize(); + if (m_pos >= olen) m_pos=olen=0; // if queue is empty then autoreset it + + char *newbuf=(char *)m_hb.ResizeOK(olen+len,false); + if (newbuf) + { + newbuf += olen; + if (buf) memcpy(newbuf,buf,len); + } + return newbuf; + } + + template T* GetT(T* val=0) + { + T* p = (T*) Get(sizeof(T)); + if (val && p) *val = *p; + return p; + } + + void* Get(int size) + { + void* p = Get(); + if (p) Advance(size); + return p; + } + + void *Get() const + { + if (m_pos >= 0 && m_pos < m_hb.GetSize()) return (char *)m_hb.Get()+m_pos; + return NULL; + } + + void* Rewind() + { + m_pos = 0; + return m_hb.Get(); + } + + int GetSize() const + { + return m_hb.GetSize()-m_pos; + } + int Available() const { return GetSize(); } + + void Clear() + { + m_pos=0; + m_hb.Resize(0,false); + } + + void Advance(int bytecnt) + { + m_pos+=bytecnt; + if (m_pos<0)m_pos=0; + else if (m_pos > m_hb.GetSize()) m_pos=m_hb.GetSize(); + } + + void Compact(bool allocdown=false, bool force=false) + { + int olen=m_hb.GetSize(); + if (m_pos > (force ? 0 : olen/2)) + { + olen -= m_pos; + if (olen > 0) + { + char *a=(char*)m_hb.Get(); + memmove(a,a+m_pos,olen); + } + else + { + olen = 0; + } + m_hb.Resize(olen,allocdown); + m_pos=0; + } + } + + void SetGranul(int granul) { m_hb.SetGranul(granul); } + + + + + // endian-management stuff + + static void WDL_Queue__bswap_buffer(void *buf, int len) // poorly named! only bswaps on BE, deprecated, use wdl_memcpy_le() + { + wdl_memcpy_le(buf,buf,1,len); + } + + // older API of static functions (that ended up warning a bit anyway) +#define WDL_Queue__AddToLE(q, v) (q)->AddToLE(v) +#define WDL_Queue__AddDataToLE(q,d,ds,us) (q)->AddDataToLE(d,ds,us) +#define WDL_Queue__GetTFromLE(q,v) (q)->GetTFromLE(v) +#define WDL_Queue__GetDataFromLE(q,ds,us) (q)->GetDataFromLE(ds,us) + + template void AddToLE(T *val) + { + void *w = Add(NULL, sizeof(T)); + if (WDL_NORMALLY(w != NULL) && val != NULL) + wdl_memcpy_le(w, val, 1, sizeof(T)); + } + void AddDataToLE(const void *data, int datasize, int unitsize) + { + char *w = (char *)Add(NULL,datasize); + if (WDL_NOT_NORMALLY(w == NULL)) return; + + if (WDL_NOT_NORMALLY(unitsize<1)) unitsize=1; + WDL_ASSERT((datasize % unitsize) == 0); + if (data) wdl_memcpy_le(w,data,datasize/unitsize,unitsize); + } + + + // NOTE: these thrash the contents of the queue if on BE systems, do not use if you + // will rewind etc. better to use Get() and wdl_memcpy_le(). + template T *GetTFromLE(T* val=0) + { + T *p = GetT(val); + if (p) + { + wdl_memcpy_le(p,p,1,sizeof(T)); + if (val) *val = *p; + } + return p; + } + + void *GetDataFromLE(int datasize, int unitsize) + { + void *data=Get(datasize); + WDL_ASSERT((datasize % unitsize) == 0); + if (data && WDL_NORMALLY(unitsize>0)) wdl_memcpy_le(data,data,datasize/unitsize,unitsize); + return data; + } + + +private: + WDL_HeapBuf m_hb; + int m_pos; +public: + int __pad; // keep 8 byte aligned +}; + +template class WDL_TypedQueue +{ +public: + WDL_TypedQueue() : m_hb(4096), m_pos(0) { } + ~WDL_TypedQueue() { } + + T *Add(const T *buf, int len) + { + int olen=m_hb.GetSize(); + if (m_pos >= olen) olen=m_pos=0; + len *= (int)sizeof(T); + + char *newbuf=(char*)m_hb.ResizeOK(olen+len,false); + if (newbuf) + { + newbuf += olen; + if (buf) memcpy(newbuf,buf,len); + } + return (T*) newbuf; + } + + T *Get() const + { + if (m_pos >= 0 && m_pos < m_hb.GetSize()) return (T*)((char *)m_hb.Get()+m_pos); + return NULL; + } + + int GetSize() const + { + return m_pos < m_hb.GetSize() ? (m_hb.GetSize()-m_pos)/sizeof(T) : 0; + } + int Available() const { return GetSize(); } + + void Clear() + { + m_pos=0; + m_hb.Resize(0,false); + } + + void Advance(int cnt) + { + m_pos+=cnt*(int)sizeof(T); + if (m_pos<0)m_pos=0; + else if (m_pos > m_hb.GetSize()) m_pos=m_hb.GetSize(); + } + + void Compact(bool allocdown=false, bool force=false) + { + int olen=m_hb.GetSize(); + if (m_pos >= (force ? 0 : olen/2)) + { + olen -= m_pos; + if (olen > 0) + { + char *a=(char*)m_hb.Get(); + memmove(a,a+m_pos,olen); + } + else + { + olen = 0; + } + m_hb.Resize(olen,allocdown); + m_pos=0; + } + } + + void SetGranul(int granul) { m_hb.SetGranul(granul); } + +private: + WDL_HeapBuf m_hb; + int m_pos; +public: + int __pad; // keep 8 byte aligned +}; + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/resample.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/resample.cpp new file mode 100644 index 0000000..96b981f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/resample.cpp @@ -0,0 +1,1670 @@ +/* + WDL - resample.cpp + Copyright (C) 2010 and later 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. + + You may also distribute this software under the LGPL v2 or later. + +*/ + +#include "resample.h" +#include + +#include "denormal.h" + +#if !defined(WDL_RESAMPLE_NO_SSE) && !defined(WDL_RESAMPLE_USE_SSE) + #if defined(__SSE2__) || _M_IX86_FP >= 2 || (defined(_M_X64) && (_MSC_VER > 1400 || __INTEL_COMPILER > 0)) + #define WDL_RESAMPLE_USE_SSE + #endif +#endif + +#ifdef WDL_RESAMPLE_USE_SSE + #include +#endif + +#ifndef PI +#define PI 3.1415926535897932384626433832795 +#endif + +class WDL_Resampler::WDL_Resampler_Filter // pre/post filter +{ +public: + WDL_Resampler_Filter() : m_fpos(-1.0), m_filtsz(0), m_state(-1) { } + + void Reset() + { + memset(m_hist.Get(),0,m_hist.GetSize() * sizeof(double)); + m_state = -1; + } + + void setParms(double fpos, double fpos2, double Q, int hist_sz, int nch) + { + if (fpos < 1.0) + { + // if initial filter state is close to nyquist, fade-in + // (in case this resampler was newly activated on varispeed media) + if (m_state == -1 && fpos >= 0.97) m_state=0; + fpos *= fpos2; + } + else + { + fpos = 1.0; + } + + m_filtsz = hist_sz; + hist_sz *= 4 * nch; + if (m_hist.GetSize() != hist_sz) + { + const int oldsz = m_hist.GetSize(); + double *p = m_hist.Resize(hist_sz,false); + if (m_hist.GetSize() > oldsz) + memset(p+oldsz,0,(m_hist.GetSize() - oldsz)*sizeof(double)); + } + + if (fabs(fpos-m_fpos)<0.000001) return; + m_fpos=fpos; + + const double pos = (fpos == 1.0 ? fpos2 : fpos) * PI; + const double cpos=cos(pos); + const double spos=sin(pos); + const double alpha=spos/(2.0*Q); + const double sc=1.0/( 1 + alpha); + m_b1 = (1-cpos) * sc; + m_b2 = m_b0 = m_b1*0.5; + m_a1 = -2 * cpos * sc; + m_a2 = (1-alpha)*sc; + } + + void ApplyIIR(WDL_ResampleSample *out1, int ns, int span, double *hsave) + { + double b0=m_b0,b1=m_b1,b2=m_b2,a1=m_a1,a2=m_a2; + double hist[4]; + memcpy(hist,hsave, sizeof(hist)); + while (ns--) + { + double in=*out1; + double out = (double) ( in*b0 + hist[0]*b1 + hist[1]*b2 - hist[2]*a1 - hist[3]*a2); + hist[1]=hist[0]; hist[0]=in; + hist[3]=hist[2]; *out1 = hist[2]=denormal_filter_double(out); + + out1+=span; + } + memcpy(hsave, hist, sizeof(hist)); + } + void ApplyIIRFade(WDL_ResampleSample *out1, int ns, int span, double *hsave, double v0, double dv) + { + double b0=m_b0,b1=m_b1,b2=m_b2,a1=m_a1,a2=m_a2; + double hist[4]; + memcpy(hist,hsave, sizeof(hist)); + while (ns--) + { + double in=*out1; + double out = (double) ( in*b0 + hist[0]*b1 + hist[1]*b2 - hist[2]*a1 - hist[3]*a2); + hist[1]=hist[0]; hist[0]=in; + hist[3]=hist[2]; + *out1 = (hist[2] = denormal_filter_double(out)) * v0 + in * (1.0-v0); + v0 += dv; + out1+=span; + } + memcpy(hsave, hist, sizeof(hist)); + } + + void ApplyBuffer(WDL_ResampleSample *buf, int ns, int nch) + { + double *hist = m_hist.Get(); + const int nf = m_filtsz; + if (m_state == 0) + { + if (m_fpos >= 1.0) return; + m_state = 1; + for (int x=0; x < nch; x ++) for (int a = 0; a < nf; a ++, hist+=4) ApplyIIRFade(buf+x,ns,nch,hist,0.0,1.0/ns); + } + else if (m_fpos >= 1.0) + { + if (m_state > 0) + for (int x=0; x < nch; x ++) for (int a = 0; a < nf; a ++, hist+=4) ApplyIIRFade(buf+x,ns,nch,hist,1.0,-1.0/ns); + m_state = 0; + } + else + { + m_state = 1; + for (int x=0; x < nch; x ++) for (int a = 0; a < nf; a ++, hist+=4) ApplyIIR(buf+x,ns,nch,hist); + } + } + +private: + double m_fpos; + double m_a1,m_a2; + double m_b0,m_b1,m_b2; + + WDL_TypedBuf m_hist; + + int m_filtsz; + int m_state; +}; + + +template static void inline SincSample(T1 *outptr, const T1 *inptr, double fracpos, int nch, const T2 *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + filter += (oversize-ifpos) * filtsz; + fracpos -= ifpos; + + int x; + for (x = 0; x < nch; x ++) + { + double sum=0.0,sum2=0.0; + const T2 *fptr2=filter; + const T2 *fptr=fptr2 - filtsz; + const T1 *iptr=inptr+x; + int i=filtsz/2; + while (i--) + { + sum += fptr[0]*iptr[0]; + sum2 += fptr2[0]*iptr[0]; + sum += fptr[1]*iptr[nch]; + sum2 += fptr2[1]*iptr[nch]; + iptr+=nch*2; + fptr+=2; + fptr2+=2; + } + outptr[x]=sum*fracpos + sum2*(1.0-fracpos); + } + +} + +template static void inline SincSampleN(T1 *outptr, const T1 *inptr, double fracpos, int nch, const T2 *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + filter += (oversize-ifpos) * filtsz; + + int x; + for (x = 0; x < nch; x ++) + { + double sum2=0.0; + const T2 *fptr2=filter; + const T1 *iptr=inptr+x; + int i=filtsz/2; + while (i--) + { + sum2 += fptr2[0]*iptr[0]; + sum2 += fptr2[1]*iptr[nch]; + iptr+=nch*2; + fptr2+=2; + } + outptr[x]=sum2; + } + +} + +template static void inline SincSample1(T1 *outptr, const T1 *inptr, double fracpos, const T2 *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + fracpos -= ifpos; + + double sum=0.0,sum2=0.0; + const T2 *fptr2=filter + (oversize-ifpos) * filtsz; + const T2 *fptr=fptr2 - filtsz; + const T1 *iptr=inptr; + int i=filtsz/2; + while (i--) + { + sum += fptr[0]*iptr[0]; + sum2 += fptr2[0]*iptr[0]; + sum += fptr[1]*iptr[1]; + sum2 += fptr2[1]*iptr[1]; + iptr+=2; + fptr+=2; + fptr2+=2; + } + outptr[0]=sum*fracpos+sum2*(1.0-fracpos); +} + +template static void inline SincSample1N(T1 *outptr, const T1 *inptr, double fracpos, const T2 *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + + double sum2=0.0; + const T2 *fptr2=filter + (oversize-ifpos) * filtsz; + const T1 *iptr=inptr; + int i=filtsz/2; + while (i--) + { + sum2 += fptr2[0]*iptr[0]; + sum2 += fptr2[1]*iptr[1]; + iptr+=2; + fptr2+=2; + } + outptr[0]=sum2; +} + +template static void inline SincSample2(T1 *outptr, const T1 *inptr, double fracpos, const T2 *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + fracpos -= ifpos; + + const T2 *fptr2=filter + (oversize-ifpos) * filtsz; + const T2 *fptr=fptr2 - filtsz; + + double sum=0.0; + double sum2=0.0; + double sumb=0.0; + double sum2b=0.0; + const T1 *iptr=inptr; + int i=filtsz/2; + while (i--) + { + sum += fptr[0]*iptr[0]; + sum2 += fptr[0]*iptr[1]; + sumb += fptr2[0]*iptr[0]; + sum2b += fptr2[0]*iptr[1]; + sum += fptr[1]*iptr[2]; + sum2 += fptr[1]*iptr[3]; + sumb += fptr2[1]*iptr[2]; + sum2b += fptr2[1]*iptr[3]; + iptr+=4; + fptr+=2; + fptr2+=2; + } + outptr[0]=sum*fracpos + sumb*(1.0-fracpos); + outptr[1]=sum2*fracpos + sum2b*(1.0-fracpos); + +} + +template static void inline SincSample2N(T1 *outptr, const T1 *inptr, double fracpos, const T2 *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + + const T2 *fptr2=filter + (oversize-ifpos) * filtsz; + + double sumb=0.0; + double sum2b=0.0; + const T1 *iptr=inptr; + int i=filtsz/2; + while (i--) + { + sumb += fptr2[0]*iptr[0]; + sum2b += fptr2[0]*iptr[1]; + sumb += fptr2[1]*iptr[2]; + sum2b += fptr2[1]*iptr[3]; + iptr+=4; + fptr2+=2; + } + outptr[0]=sumb; + outptr[1]=sum2b; +} + + +#ifdef WDL_RESAMPLE_USE_SSE + +static void inline SincSample(double *outptr, const double *inptr, double fracpos, int nch, const float *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + filter += (oversize-ifpos) * filtsz; + fracpos -= ifpos; + + int x; + for (x = 0; x < nch; x ++) + { + double sum, sum2; + const float *fptr2=filter; + const float *fptr=fptr2 - filtsz; + const double *iptr=inptr+x; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2, xmm3; + + while (i--) + { + xmm2 = _mm_set_pd(iptr[nch], iptr[0]); + + xmm3 = _mm_load_sd((double *)fptr); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm3 = _mm_load_sd((double *)fptr2); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + iptr+=nch*2; + fptr+=2; + fptr2+=2; + } + + xmm2 = xmm0; + xmm0 = _mm_unpackhi_pd(xmm0, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm2); + _mm_store_sd(&sum, xmm0); + + xmm3 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm3); + xmm1 = _mm_add_pd(xmm1, xmm3); + _mm_store_sd(&sum2, xmm1); + + outptr[x]=sum*fracpos + sum2*(1.0-fracpos); + } + +} + +static void inline SincSampleN(double *outptr, const double *inptr, double fracpos, int nch, const float *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + filter += (oversize-ifpos) * filtsz; + + int x; + for (x = 0; x < nch; x ++) + { + double sum2; + const float *fptr2=filter; + const double *iptr=inptr+x; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2, xmm3; + + while (i >= 2) + { + xmm2 = _mm_set_pd(iptr[nch], iptr[0]); + + xmm3 = _mm_load_sd((double *)fptr2); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm2 = _mm_set_pd(iptr[nch*3], iptr[nch*2]); + + xmm3 = _mm_load_sd((double *)fptr2 + 1); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + iptr+=nch*4; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm2 = _mm_set_pd(iptr[nch], iptr[0]); + + xmm3 = _mm_load_sd((double *)fptr2); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + } + + xmm1 = _mm_add_pd(xmm1, xmm0); + + xmm3 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm3); + xmm1 = _mm_add_pd(xmm1, xmm3); + _mm_store_sd(&sum2, xmm1); + + outptr[x]=sum2; + } + +} + +static void inline SincSample1(double *outptr, const double *inptr, double fracpos, const float *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + fracpos -= ifpos; + + double sum, sum2; + const float *fptr2=filter + (oversize-ifpos) * filtsz; + const float *fptr=fptr2 - filtsz; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2, xmm3; + + while (i >= 2) + { + xmm2 = _mm_loadu_pd(iptr); + + xmm3 = _mm_cvtps_pd(_mm_load_ps(fptr)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm3 = _mm_cvtps_pd(_mm_load_ps(fptr2)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + xmm2 = _mm_loadu_pd(iptr+2); + + xmm3 = _mm_load_sd((double *)fptr + 1); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm3 = _mm_load_sd((double *)fptr2 + 1); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + iptr+=4; + fptr+=4; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm2 = _mm_loadu_pd(iptr); + + xmm3 = _mm_load_sd((double *)fptr); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm3 = _mm_load_sd((double *)fptr2); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + } + + xmm2 = xmm0; + xmm0 = _mm_unpackhi_pd(xmm0, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm2); + _mm_store_sd(&sum, xmm0); + + xmm3 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm3); + xmm1 = _mm_add_pd(xmm1, xmm3); + _mm_store_sd(&sum2, xmm1); + + outptr[0]=sum*fracpos+sum2*(1.0-fracpos); +} + +static void inline SincSample1N(double *outptr, const double *inptr, double fracpos, const float *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + + double sum2; + const float *fptr2=filter + (oversize-ifpos) * filtsz; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2, xmm3; + + while (i >= 2) + { + xmm2 = _mm_loadu_pd(iptr); + + xmm3 = _mm_cvtps_pd(_mm_load_ps(fptr2)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm2 = _mm_loadu_pd(iptr+2); + + xmm3 = _mm_load_sd((double *)fptr2 + 1); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + iptr+=4; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm2 = _mm_loadu_pd(iptr); + + xmm3 = _mm_load_sd((double *)fptr2); + xmm3 = _mm_cvtps_pd(_mm_castpd_ps(xmm3)); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + } + + xmm1 = _mm_add_pd(xmm1, xmm0); + + xmm3 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm3); + xmm1 = _mm_add_pd(xmm1, xmm3); + _mm_store_sd(&sum2, xmm1); + + outptr[0]=sum2; +} + +static void inline SincSample2(double *outptr, const double *inptr, double fracpos, const float *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + fracpos -= ifpos; + + const float *fptr2=filter + (oversize-ifpos) * filtsz; + const float *fptr=fptr2 - filtsz; + + double sum, sum2, sumb, sum2b; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2, xmm3; + __m128 xmm4; + + while (i--) + { + xmm2 = _mm_loadu_pd(iptr); + + xmm4 = _mm_set1_ps(fptr[0]); + xmm3 = _mm_cvtps_pd(xmm4); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm4 = _mm_set1_ps(fptr2[0]); + xmm3 = _mm_cvtps_pd(xmm4); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + xmm2 = _mm_loadu_pd(iptr+2); + + xmm4 = _mm_set1_ps(fptr[1]); + xmm3 = _mm_cvtps_pd(xmm4); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm4 = _mm_set1_ps(fptr2[1]); + xmm3 = _mm_cvtps_pd(xmm4); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + iptr+=4; + fptr+=2; + fptr2+=2; + } + + xmm2 = xmm0; + _mm_store_sd(&sum, xmm0); + xmm2 = _mm_unpackhi_pd(xmm2, xmm0); + _mm_store_sd(&sum2, xmm2); + + xmm3 = xmm1; + _mm_store_sd(&sumb, xmm1); + xmm3 = _mm_unpackhi_pd(xmm3, xmm1); + _mm_store_sd(&sum2b, xmm3); + + outptr[0]=sum*fracpos + sumb*(1.0-fracpos); + outptr[1]=sum2*fracpos + sum2b*(1.0-fracpos); +} + +static void inline SincSample2N(double *outptr, const double *inptr, double fracpos, const float *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + + const float *fptr2=filter + (oversize-ifpos) * filtsz; + + double sumb, sum2b; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2, xmm3; + __m128 xmm4; + + while (i--) + { + xmm2 = _mm_loadu_pd(iptr); + + xmm4 = _mm_set1_ps(fptr2[0]); + xmm3 = _mm_cvtps_pd(xmm4); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm2 = _mm_loadu_pd(iptr+2); + + xmm4 = _mm_set1_ps(fptr2[1]); + xmm3 = _mm_cvtps_pd(xmm4); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + iptr+=4; + fptr2+=2; + } + + xmm1 = _mm_add_pd(xmm1, xmm0); + + xmm3 = xmm1; + _mm_store_sd(&sumb, xmm1); + xmm3 = _mm_unpackhi_pd(xmm3, xmm1); + _mm_store_sd(&sum2b, xmm3); + + outptr[0]=sumb; + outptr[1]=sum2b; +} + + +static void inline SincSample(double *outptr, const double *inptr, double fracpos, int nch, const double *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + filter += (oversize-ifpos) * filtsz; + fracpos -= ifpos; + + int x; + for (x = 0; x < nch; x ++) + { + double sum, sum2; + const double *fptr2=filter; + const double *fptr=fptr2 - filtsz; + const double *iptr=inptr+x; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2 = _mm_setzero_pd(); + __m128d xmm3 = _mm_setzero_pd(); + __m128d xmm4, xmm5; + + while (i >= 2) + { + xmm4 = _mm_set_pd(iptr[nch], iptr[0]); + + xmm5 = _mm_load_pd(fptr); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm0 = _mm_add_pd(xmm0, xmm5); + + xmm5 = _mm_load_pd(fptr2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm1 = _mm_add_pd(xmm1, xmm5); + + xmm4 = _mm_set_pd(iptr[nch*3], iptr[nch*2]); + + xmm5 = _mm_load_pd(fptr+2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm2 = _mm_add_pd(xmm2, xmm5); + + xmm5 = _mm_load_pd(fptr2+2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm3 = _mm_add_pd(xmm3, xmm5); + + iptr+=nch*4; + fptr+=4; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm4 = _mm_set_pd(iptr[nch], iptr[0]); + + xmm5 = _mm_load_pd(fptr); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm0 = _mm_add_pd(xmm0, xmm5); + + xmm5 = _mm_load_pd(fptr2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm1 = _mm_add_pd(xmm1, xmm5); + } + + xmm0 = _mm_add_pd(xmm0, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + xmm2 = xmm0; + xmm0 = _mm_unpackhi_pd(xmm0, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm2); + _mm_store_sd(&sum, xmm0); + + xmm3 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm3); + xmm1 = _mm_add_pd(xmm1, xmm3); + _mm_store_sd(&sum2, xmm1); + + outptr[x]=sum*fracpos + sum2*(1.0-fracpos); + } + +} + +static void inline SincSampleN(double *outptr, const double *inptr, double fracpos, int nch, const double *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + filter += (oversize-ifpos) * filtsz; + + int x; + for (x = 0; x < nch; x ++) + { + double sum2; + const double *fptr2=filter; + const double *iptr=inptr+x; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2, xmm3; + + while (i >= 2) + { + xmm2 = _mm_set_pd(iptr[nch], iptr[0]); + + xmm3 = _mm_load_pd(fptr2); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + + xmm2 = _mm_set_pd(iptr[nch*3], iptr[nch*2]); + + xmm3 = _mm_load_pd(fptr2+2); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + iptr+=nch*4; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm2 = _mm_set_pd(iptr[nch], iptr[0]); + + xmm3 = _mm_load_pd(fptr2); + xmm3 = _mm_mul_pd(xmm3, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm3); + } + + xmm1 = _mm_add_pd(xmm1, xmm0); + + xmm3 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm3); + xmm1 = _mm_add_pd(xmm1, xmm3); + _mm_store_sd(&sum2, xmm1); + + outptr[x]=sum2; + } + +} + +static void inline SincSample1(double *outptr, const double *inptr, double fracpos, const double *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + fracpos -= ifpos; + + double sum, sum2; + const double *fptr2=filter + (oversize-ifpos) * filtsz; + const double *fptr=fptr2 - filtsz; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2 = _mm_setzero_pd(); + __m128d xmm3 = _mm_setzero_pd(); + __m128d xmm4, xmm5; + + while (i >= 2) + { + xmm4 = _mm_loadu_pd(iptr); + + xmm5 = _mm_load_pd(fptr); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm0 = _mm_add_pd(xmm0, xmm5); + + xmm5 = _mm_load_pd(fptr2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm1 = _mm_add_pd(xmm1, xmm5); + + xmm4 = _mm_loadu_pd(iptr+2); + + xmm5 = _mm_load_pd(fptr+2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm2 = _mm_add_pd(xmm2, xmm5); + + xmm5 = _mm_load_pd(fptr2+2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm3 = _mm_add_pd(xmm3, xmm5); + + iptr+=4; + fptr+=4; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm4 = _mm_loadu_pd(iptr); + + xmm5 = _mm_load_pd(fptr); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm0 = _mm_add_pd(xmm0, xmm5); + + xmm5 = _mm_load_pd(fptr2); + xmm5 = _mm_mul_pd(xmm5, xmm4); + xmm1 = _mm_add_pd(xmm1, xmm5); + } + + xmm0 = _mm_add_pd(xmm0, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + xmm2 = xmm0; + xmm0 = _mm_unpackhi_pd(xmm0, xmm2); + xmm0 = _mm_add_pd(xmm0, xmm2); + _mm_store_sd(&sum, xmm0); + + xmm3 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm3); + xmm1 = _mm_add_pd(xmm1, xmm3); + _mm_store_sd(&sum2, xmm1); + + outptr[0]=sum*fracpos+sum2*(1.0-fracpos); +} + +static void inline SincSample1N(double *outptr, const double *inptr, double fracpos, const double *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + + double sum2; + const double *fptr2=filter + (oversize-ifpos) * filtsz; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2; + + while (i >= 2) + { + xmm2 = _mm_loadu_pd(iptr); + xmm2 = _mm_mul_pd(xmm2, _mm_load_pd(fptr2)); + xmm0 = _mm_add_pd(xmm0, xmm2); + + xmm2 = _mm_loadu_pd(iptr+2); + xmm2 = _mm_mul_pd(xmm2, _mm_load_pd(fptr2+2)); + xmm1 = _mm_add_pd(xmm1, xmm2); + + iptr+=4; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm2 = _mm_loadu_pd(iptr); + xmm2 = _mm_mul_pd(xmm2, _mm_load_pd(fptr2)); + xmm0 = _mm_add_pd(xmm0, xmm2); + } + + xmm1 = _mm_add_pd(xmm1, xmm0); + + xmm2 = xmm1; + xmm1 = _mm_unpackhi_pd(xmm1, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm2); + _mm_store_sd(&sum2, xmm1); + + outptr[0]=sum2; +} + +static void inline SincSample2(double *outptr, const double *inptr, double fracpos, const double *filter, int filtsz, int oversize) +{ + fracpos *= oversize; + const int ifpos=(int)fracpos; + fracpos -= ifpos; + + const double *fptr2=filter + (oversize-ifpos) * filtsz; + const double *fptr=fptr2 - filtsz; + + double sum, sum2, sumb, sum2b; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2 = _mm_setzero_pd(); + __m128d xmm3 = _mm_setzero_pd(); + __m128d xmm4, xmm5, xmm6, xmm7; + + while (i--) + { + xmm4 = _mm_load_pd(fptr); + xmm5 = _mm_load_pd(fptr2); + + xmm6 = _mm_loadu_pd(iptr); + + xmm7 = xmm4; + xmm7 = _mm_unpacklo_pd(xmm7, xmm4); + xmm7 = _mm_mul_pd(xmm7, xmm6); + xmm0 = _mm_add_pd(xmm0, xmm7); + + xmm7 = xmm5; + xmm7 = _mm_unpacklo_pd(xmm7, xmm5); + xmm7 = _mm_mul_pd(xmm7, xmm6); + xmm1 = _mm_add_pd(xmm1, xmm7); + + xmm6 = _mm_loadu_pd(iptr+2); + + xmm4 = _mm_unpackhi_pd(xmm4, xmm4); + xmm4 = _mm_mul_pd(xmm4, xmm6); + xmm2 = _mm_add_pd(xmm2, xmm4); + + xmm5 = _mm_unpackhi_pd(xmm5, xmm5); + xmm5 = _mm_mul_pd(xmm5, xmm6); + xmm3 = _mm_add_pd(xmm3, xmm5); + + iptr+=4; + fptr+=2; + fptr2+=2; + } + + xmm0 = _mm_add_pd(xmm0, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + xmm2 = xmm0; + _mm_store_sd(&sum, xmm0); + xmm2 = _mm_unpackhi_pd(xmm2, xmm0); + _mm_store_sd(&sum2, xmm2); + + xmm3 = xmm1; + _mm_store_sd(&sumb, xmm1); + xmm3 = _mm_unpackhi_pd(xmm3, xmm1); + _mm_store_sd(&sum2b, xmm3); + + outptr[0]=sum*fracpos + sumb*(1.0-fracpos); + outptr[1]=sum2*fracpos + sum2b*(1.0-fracpos); +} + +static void inline SincSample2N(double *outptr, const double *inptr, double fracpos, const double *filter, int filtsz, int oversize) +{ + const int ifpos=(int)(fracpos*oversize+0.5); + + const double *fptr2=filter + (oversize-ifpos) * filtsz; + + double sumb, sum2b; + const double *iptr=inptr; + int i=filtsz/2; + + __m128d xmm0 = _mm_setzero_pd(); + __m128d xmm1 = _mm_setzero_pd(); + __m128d xmm2 = _mm_setzero_pd(); + __m128d xmm3 = _mm_setzero_pd(); + __m128d xmm4, xmm5, xmm6; + + while (i >= 2) + { + xmm4 = _mm_load_pd(fptr2); + xmm5 = xmm4; + + xmm6 = _mm_loadu_pd(iptr); + xmm4 = _mm_unpacklo_pd(xmm4, xmm5); + xmm6 = _mm_mul_pd(xmm6, xmm4); + xmm0 = _mm_add_pd(xmm0, xmm6); + + xmm6 = _mm_loadu_pd(iptr+2); + xmm5 = _mm_unpackhi_pd(xmm5, xmm5); + xmm6 = _mm_mul_pd(xmm6, xmm5); + xmm1 = _mm_add_pd(xmm1, xmm6); + + xmm4 = _mm_load_pd(fptr2+2); + xmm5 = xmm4; + + xmm6 = _mm_loadu_pd(iptr+4); + xmm4 = _mm_unpacklo_pd(xmm4, xmm5); + xmm6 = _mm_mul_pd(xmm6, xmm4); + xmm2 = _mm_add_pd(xmm2, xmm6); + + xmm6 = _mm_loadu_pd(iptr+6); + xmm5 = _mm_unpackhi_pd(xmm5, xmm5); + xmm6 = _mm_mul_pd(xmm6, xmm5); + xmm3 = _mm_add_pd(xmm3, xmm6); + + iptr+=8; + fptr2+=4; + i-=2; + } + + if (i) + { + xmm4 = _mm_load_pd(fptr2); + xmm5 = xmm4; + + xmm6 = _mm_loadu_pd(iptr); + xmm4 = _mm_unpacklo_pd(xmm4, xmm5); + xmm6 = _mm_mul_pd(xmm6, xmm4); + xmm0 = _mm_add_pd(xmm0, xmm6); + + xmm6 = _mm_loadu_pd(iptr+2); + xmm5 = _mm_unpackhi_pd(xmm5, xmm5); + xmm6 = _mm_mul_pd(xmm6, xmm5); + xmm1 = _mm_add_pd(xmm1, xmm6); + } + + xmm0 = _mm_add_pd(xmm0, xmm2); + xmm1 = _mm_add_pd(xmm1, xmm3); + + xmm1 = _mm_add_pd(xmm1, xmm0); + + xmm3 = xmm1; + _mm_store_sd(&sumb, xmm1); + xmm3 = _mm_unpackhi_pd(xmm3, xmm1); + _mm_store_sd(&sum2b, xmm3); + + outptr[0]=sumb; + outptr[1]=sum2b; +} + +#endif // WDL_RESAMPLE_USE_SSE + + +WDL_Resampler::WDL_Resampler() +{ + m_sinc_ideal_calced = -1; + m_filterq=0.707f; + m_filterpos=0.693f; // .792 ? + + m_sincoversize=0; + m_lp_oversize=1; + m_sincsize=0; + m_prepost_filtercnt = 1; + m_interp=true; + m_feedmode=false; + + m_filter_coeffs_size=0; + m_sratein=44100.0; + m_srateout=44100.0; + m_ratio=1.0; + m_filter_ratio=-1.0; + m_pre_filter = NULL; + m_post_filter = NULL; + + Reset(); +} + +WDL_Resampler::~WDL_Resampler() +{ + delete m_pre_filter; + delete m_post_filter; +} + +void WDL_Resampler::Reset(double fracpos) +{ + m_last_requested=0; + m_filtlatency=0; + m_fracpos=fracpos; + m_samples_in_rsinbuf=0; + m_rsinbuf_nch=0; + if (m_sinc_ideal_calced == -2) m_sinc_ideal_calced = -1; + if (m_pre_filter) m_pre_filter->Reset(); + if (m_post_filter) m_post_filter->Reset(); +} + +void WDL_Resampler::SetMode(bool interp, int filtercnt, bool sinc, int sinc_size, int sinc_interpsize) +{ + m_sincsize = sinc && sinc_size>= 4 ? sinc_size > 8192 ? 8192 : (sinc_size&~1) : 0; + m_sincoversize = m_sincsize ? (sinc_interpsize<= 1 ? 1 : sinc_interpsize>=8192 ? 8192 : sinc_interpsize) : 1; + + m_prepost_filtercnt = m_sincsize ? 0 : wdl_max(filtercnt,0); + m_interp=interp && !m_sincsize; + + if (!m_sincsize) + { + m_filter_coeffs.Resize(0); + m_filter_coeffs_size=0; + } + if (!m_prepost_filtercnt) + { + delete m_pre_filter; + m_pre_filter=NULL; + delete m_post_filter; + m_post_filter=NULL; + } +} + +void WDL_Resampler::SetRates(double rate_in, double rate_out) +{ + if (rate_in<1.0) rate_in=1.0; + if (rate_out<1.0) rate_out=1.0; + if (rate_in != m_sratein || rate_out != m_srateout) + { + m_sratein=rate_in; + m_srateout=rate_out; + m_ratio=m_sratein / m_srateout; + m_sinc_ideal_calced = -1; + } +} + +const WDL_SincFilterSample *WDL_Resampler::BuildLowPass(double filtpos, bool *isIdeal) // only called in sinc modes +{ + const int wantsize=m_sincsize; + int wantinterp=m_sincoversize; + + int ideal_interp = 0; + if (wantinterp) + { + if (m_sinc_ideal_calced == -1) + { + if (m_ratio < 1.0) + { + const double drat = m_srateout/m_sratein; + const int irat = (int) (drat + 0.5); + if (irat > 1 && irat==drat) ideal_interp=irat; + } + else + { + const int irat = (int) (m_ratio + 0.5); + if (m_ratio == irat) ideal_interp=1; // eg 96k to 48k, only need one table + } + + if (!ideal_interp) + { + // if whole integer rates, calculate GCD + const int in1 = (int)m_sratein, out1 = (int)m_srateout; + if (out1 > 0 && in1 > 0 && m_sratein == (double)in1 && m_srateout == (double)out1) + { + // don't bother finding the GCD if it's lower than is useful + int min_cd = out1 / (2*wantinterp); + if (min_cd < 1) min_cd = 1; + + int n1 = out1, n2=in1; + while (n2 >= min_cd) + { + const int tmp = n1; + n1 = n2; + n2 = tmp % n2; + } + if (!n2) + ideal_interp = out1 / n1; + } + } + if (ideal_interp > 0 && m_fracpos != 0.0) + { + // should maybe see if m_fracpos maps losslessly to ideal_interp + ideal_interp = -2; + } + m_sinc_ideal_calced = ideal_interp; + } + else + ideal_interp = m_sinc_ideal_calced; + + if (ideal_interp > 0 && ideal_interp <= wantinterp*2) // use ideal filter for reduced cpu use even if it means more memory + { + wantinterp = ideal_interp; + } + } + + *isIdeal = ideal_interp == wantinterp; + if (m_filter_ratio!=filtpos || + m_filter_coeffs_size != wantsize || + m_lp_oversize != wantinterp) + { + m_lp_oversize = wantinterp; + m_filter_ratio=filtpos; + + // build lowpass filter + const int allocsize = wantsize*(m_lp_oversize+1); + const int alignedsize = allocsize + 16/sizeof(WDL_SincFilterSample) - 1; + if (m_filter_coeffs.ResizeOK(alignedsize)) + { + WDL_SincFilterSample *cfout=m_filter_coeffs.GetAligned(16); + m_filter_coeffs_size=wantsize; + + const double dwindowpos = 2.0 * PI/(double)wantsize; + const double dsincpos = PI * filtpos; // filtpos is outrate/inrate, i.e. 0.5 is going to half rate + const int hwantsize=wantsize/2, hwantinterp=wantinterp/2; + + double filtpower=0.0; + WDL_SincFilterSample *ptrout = cfout; + int slice; + for (slice=0;slice<=hwantinterp;slice++) + { + const double frac = slice / (double)wantinterp; + const int center_x = slice == 0 ? hwantsize : -1; + + const int n = ((slice < hwantinterp) | (wantinterp & 1)) ? wantsize : hwantsize; + int x; + for (x=0;x= 0; ++x, --y) cfout[x] = cfout[y]; + } + else m_filter_coeffs_size=0; + + } + return m_filter_coeffs_size > 0 ? m_filter_coeffs.GetAligned(16) : NULL; +} + +double WDL_Resampler::GetCurrentLatency() +{ + double v=((double)m_samples_in_rsinbuf-m_filtlatency)/m_sratein; + + if (v<0.0)v=0.0; + return v; +} + +static void wdl_rs_reinterleave_buffer(WDL_ResampleSample *rdptr, int in_nch, int out_nch, int len) +{ + if (len < 1 || !rdptr) return; + + int x=len-1; + WDL_ResampleSample *wrptr = rdptr; + if (out_nch < in_nch) + { + const int sz1=out_nch*sizeof(WDL_ResampleSample); + while (x--) + { + rdptr += in_nch; + wrptr += out_nch; + memmove(wrptr,rdptr,sz1); + } + } + else if (out_nch > in_nch) + { + const int sz1=in_nch*sizeof(WDL_ResampleSample); + const int sz2=(out_nch-in_nch)*sizeof(WDL_ResampleSample); + rdptr += in_nch*x; + wrptr += out_nch*x; + while(x--) + { + memmove(wrptr,rdptr,sz1); + memset(wrptr+in_nch,0,sz2); + + rdptr-=in_nch; + wrptr-=out_nch; + } + memset(wrptr+in_nch,0,sz2); // last iteration doesnt need memcpy (but does need clear) + } +} + +int WDL_Resampler::ResamplePrepare(int out_samples, int nch, WDL_ResampleSample **inbuffer) +{ + if (nch < 1) return 0; + + if (m_prepost_filtercnt > 0) + { + if (!m_pre_filter) m_pre_filter = new WDL_Resampler_Filter; + m_pre_filter->setParms(1.0/m_ratio,m_filterpos,m_filterq, m_prepost_filtercnt,nch); + if (!m_post_filter) m_post_filter = new WDL_Resampler_Filter; + m_post_filter->setParms(m_ratio,m_filterpos,m_filterq, m_prepost_filtercnt,nch); + } + + int fsize=0; + if (m_sincsize>1) fsize = m_sincsize; + + int hfs=fsize/2; + if (hfs>1 && m_samples_in_rsinbuf0) + { + WDL_ResampleSample *p = m_rsinbuf.Resize(m_samples_in_rsinbuf*nch,false); + memset(p,0,sizeof(WDL_ResampleSample)*m_rsinbuf.GetSize()); + } + } + + int sreq = 0; + + if (!m_feedmode) sreq = (int)(m_ratio * out_samples) + 4 + fsize - m_samples_in_rsinbuf; + else sreq = out_samples; + + if (sreq<0)sreq=0; + + // if decreasing channel count, reinterleave before realloc + if (nch < m_rsinbuf_nch && m_rsinbuf_nch > 0 && m_samples_in_rsinbuf) + wdl_rs_reinterleave_buffer(m_rsinbuf.Get(), m_rsinbuf_nch, nch, m_samples_in_rsinbuf); + +again: + m_rsinbuf.Resize((m_samples_in_rsinbuf+sreq)*nch,false); + + int sz = m_rsinbuf.GetSize()/(nch?nch:1) - m_samples_in_rsinbuf; + if (sz!=sreq) + { + if (sreq>4 && !sz) + { + sreq/=2; + goto again; // try again with half the size + } + // todo: notify of error? + sreq=sz; + } + + // if increasing channel count, reinterleave after realloc + if (m_rsinbuf_nch < nch && m_rsinbuf_nch > 0 && m_samples_in_rsinbuf) + wdl_rs_reinterleave_buffer(m_rsinbuf.Get(), m_rsinbuf_nch, nch, m_samples_in_rsinbuf); + + m_rsinbuf_nch = nch; + + *inbuffer = m_rsinbuf.Get() + m_samples_in_rsinbuf*nch; + + m_last_requested=sreq; + return sreq; +} + + + +int WDL_Resampler::ResampleOut(WDL_ResampleSample *out, int nsamples_in, int nsamples_out, int nch) +{ + if (nch < 1) return 0; +#ifdef WDL_DENORMAL_WANTS_SCOPED_FTZ + WDL_denormal_ftz_scope ftz_force; +#endif + + if (m_pre_filter && nsamples_in > 0) // filter input + { + m_pre_filter->ApplyBuffer(m_rsinbuf.Get() + m_samples_in_rsinbuf*nch,nsamples_in,nch); + } + + // prevent the caller from corrupting the internal state + m_samples_in_rsinbuf += nsamples_in < m_last_requested ? nsamples_in : m_last_requested; + + int rsinbuf_availtemp = m_samples_in_rsinbuf; + + if (nsamples_in < m_last_requested) // flush out to ensure we can deliver + { + int fsize=(m_last_requested-nsamples_in)*2 + m_sincsize*2; + + int alloc_size=(m_samples_in_rsinbuf+fsize)*nch; + WDL_ResampleSample *zb=m_rsinbuf.Resize(alloc_size,false); + if (m_rsinbuf.GetSize()==alloc_size) + { + memset(zb+m_samples_in_rsinbuf*nch,0,fsize*nch*sizeof(WDL_ResampleSample)); + rsinbuf_availtemp = m_samples_in_rsinbuf+fsize; + } + } + + int ret=0; + double srcpos=m_fracpos; + double drspos = m_ratio; + WDL_ResampleSample *localin = m_rsinbuf.Get(); + + WDL_ResampleSample *outptr=out; + + int ns=nsamples_out; + + int outlatadj=0; + + bool isideal = false; + if (m_sincsize) // sinc interpolating + { + const WDL_SincFilterSample *filter; + if (m_ratio > 1.0) filter=BuildLowPass(1.0 / (m_ratio*1.03), &isideal); + else filter=BuildLowPass(1.0, &isideal); + + const int oversize = m_lp_oversize; + int filtsz=m_filter_coeffs_size; + int filtlen = rsinbuf_availtemp - filtsz; + outlatadj=filtsz/2-1; + + if (WDL_NOT_NORMALLY(!filter)) {} + else if (nch == 1) + { + if (isideal) + while (ns--) + { + int ipos = (int)srcpos; + + if (ipos >= filtlen-1) break; // quit decoding, not enough input samples + + SincSample1N(outptr,localin + ipos,srcpos-ipos,filter,filtsz,oversize); + outptr ++; + srcpos+=drspos; + ret++; + } + else + while (ns--) + { + int ipos = (int)srcpos; + + if (ipos >= filtlen-1) break; // quit decoding, not enough input samples + + SincSample1(outptr,localin + ipos,srcpos-ipos,filter,filtsz,oversize); + outptr ++; + srcpos+=drspos; + ret++; + } + } + else if (nch==2) + { + if (isideal) + while (ns--) + { + int ipos = (int)srcpos; + + if (ipos >= filtlen-1) break; // quit decoding, not enough input samples + + SincSample2N(outptr,localin + ipos*2,srcpos-ipos,filter,filtsz,oversize); + outptr+=2; + srcpos+=drspos; + ret++; + } + else + while (ns--) + { + int ipos = (int)srcpos; + + if (ipos >= filtlen-1) break; // quit decoding, not enough input samples + + SincSample2(outptr,localin + ipos*2,srcpos-ipos,filter,filtsz,oversize); + outptr+=2; + srcpos+=drspos; + ret++; + } + } + else + { + if (isideal) + while (ns--) + { + int ipos = (int)srcpos; + + if (ipos >= filtlen-1) break; // quit decoding, not enough input samples + + SincSampleN(outptr,localin + ipos*nch,srcpos-ipos,nch,filter,filtsz,oversize); + outptr += nch; + srcpos+=drspos; + ret++; + } + else + while (ns--) + { + int ipos = (int)srcpos; + + if (ipos >= filtlen-1) break; // quit decoding, not enough input samples + + SincSample(outptr,localin + ipos*nch,srcpos-ipos,nch,filter,filtsz,oversize); + outptr += nch; + srcpos+=drspos; + ret++; + } + } + } + else if (!m_interp) // point sampling + { + if (nch == 1) + { + while (ns--) + { + int ipos = (int)srcpos; + if (ipos >= rsinbuf_availtemp) break; // quit decoding, not enough input samples + + *outptr++ = localin[ipos]; + srcpos+=drspos; + ret++; + } + } + else if (nch == 2) + { + while (ns--) + { + int ipos = (int)srcpos; + if (ipos >= rsinbuf_availtemp) break; // quit decoding, not enough input samples + + ipos+=ipos; + + outptr[0] = localin[ipos]; + outptr[1] = localin[ipos+1]; + outptr+=2; + srcpos+=drspos; + ret++; + } + } + else + while (ns--) + { + int ipos = (int)srcpos; + if (ipos >= rsinbuf_availtemp) break; // quit decoding, not enough input samples + + memcpy(outptr,localin + ipos*nch,nch*sizeof(WDL_ResampleSample)); + outptr += nch; + srcpos+=drspos; + ret++; + } + } + else // linear interpolation + { + if (nch == 1) + { + while (ns--) + { + int ipos = (int)srcpos; + double fracpos=srcpos-ipos; + + if (ipos >= rsinbuf_availtemp-1) + { + break; // quit decoding, not enough input samples + } + + double ifracpos=1.0-fracpos; + WDL_ResampleSample *inptr = localin + ipos; + *outptr++ = inptr[0]*(ifracpos) + inptr[1]*(fracpos); + srcpos+=drspos; + ret++; + } + } + else if (nch == 2) + { + while (ns--) + { + int ipos = (int)srcpos; + double fracpos=srcpos-ipos; + + if (ipos >= rsinbuf_availtemp-1) + { + break; // quit decoding, not enough input samples + } + + double ifracpos=1.0-fracpos; + WDL_ResampleSample *inptr = localin + ipos*2; + outptr[0] = inptr[0]*(ifracpos) + inptr[2]*(fracpos); + outptr[1] = inptr[1]*(ifracpos) + inptr[3]*(fracpos); + outptr += 2; + srcpos+=drspos; + ret++; + } + } + else + { + while (ns--) + { + int ipos = (int)srcpos; + double fracpos=srcpos-ipos; + + if (ipos >= rsinbuf_availtemp-1) + { + break; // quit decoding, not enough input samples + } + + double ifracpos=1.0-fracpos; + int ch=nch; + WDL_ResampleSample *inptr = localin + ipos*nch; + while (ch--) + { + *outptr++ = inptr[0]*(ifracpos) + inptr[nch]*(fracpos); + inptr++; + } + srcpos+=drspos; + ret++; + } + } + } + + + if (ret > 0 && m_post_filter) // filter output + { + m_post_filter->ApplyBuffer(out,ret,nch); + } + + if (ret>0 && rsinbuf_availtemp>m_samples_in_rsinbuf) // we had to pad!! + { + // check for the case where rsinbuf_availtemp>m_samples_in_rsinbuf, decrease ret down to actual valid samples + double adj=(srcpos-m_samples_in_rsinbuf + outlatadj) / drspos; + if (adj>0) + { + ret -= (int) (adj + 0.5); + if (ret<0)ret=0; + } + } + + int isrcpos=(int)srcpos; + if (isrcpos > m_samples_in_rsinbuf) isrcpos=m_samples_in_rsinbuf; + m_fracpos = srcpos - isrcpos; + + if (m_sincsize && isideal) + m_fracpos = floor(m_lp_oversize*m_fracpos + 0.5)/m_lp_oversize; + + m_samples_in_rsinbuf -= isrcpos; + if (m_samples_in_rsinbuf <= 0) m_samples_in_rsinbuf=0; + else + memmove(localin, localin + isrcpos*nch,m_samples_in_rsinbuf*sizeof(WDL_ResampleSample)*nch); + + + return ret; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/resample.h b/plugin-reaper-realearn/main/lib/WDL/WDL/resample.h new file mode 100644 index 0000000..09e22ba --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/resample.h @@ -0,0 +1,110 @@ +/* + WDL - resample.h + Copyright (C) 2010 and later 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. + + You may also distribute this software under the LGPL v2 or later. + +*/ + +#ifndef _WDL_RESAMPLE_H_ +#define _WDL_RESAMPLE_H_ + +#include +#include +#include "wdltypes.h" +#include "heapbuf.h" + +// default to floats for sinc filter ceofficients +#ifdef WDL_RESAMPLE_FULL_SINC_PRECISION +typedef double WDL_SincFilterSample; +#else +typedef float WDL_SincFilterSample; +#endif + +// default to doubles for audio samples +#ifdef WDL_RESAMPLE_TYPE +typedef WDL_RESAMPLE_TYPE WDL_ResampleSample; +#else +typedef double WDL_ResampleSample; +#endif + + +class WDL_Resampler +{ +public: + WDL_Resampler(); + ~WDL_Resampler(); + // if sinc set, it overrides interp or filtercnt + void SetMode(bool interp, int filtercnt, bool sinc, int sinc_size=64, int sinc_interpsize=32); + + void SetFilterParms(float filterpos=0.693, float filterq=0.707) { m_filterpos=filterpos; m_filterq=filterq; } // used for filtercnt>0 but not sinc + void SetFeedMode(bool wantInputDriven) { m_feedmode=wantInputDriven; } // if true, that means the first parameter to ResamplePrepare will specify however much input you have, not how much you want + + void Reset(double fracpos=0.0); + void SetRates(double rate_in, double rate_out); + + double GetCurrentLatency(); // amount of input that has been received but not yet converted to output, in seconds + + // req_samples is output samples desired if !wantInputDriven, or if wantInputDriven is input samples that we have + // returns number of samples desired (put these into *inbuffer) + // note that it is safe to call ResamplePrepare without calling ResampleOut (the next call of ResamplePrepare will function as normal) + int ResamplePrepare(int req_samples, int nch, WDL_ResampleSample **inbuffer); + + + // if numsamples_in < the value return by ResamplePrepare(), then it will be flushed to produce all remaining valid samples + // do NOT call with nsamples_in greater than the value returned from resamplerprpare()! the extra samples will be ignored. + // returns number of samples successfully outputted to out + int ResampleOut(WDL_ResampleSample *out, int nsamples_in, int nsamples_out, int nch); + + + +private: + const WDL_SincFilterSample *BuildLowPass(double filtpos, bool *isIdeal); + + double m_sratein WDL_FIXALIGN; + double m_srateout; + double m_fracpos; + double m_ratio; + double m_filter_ratio; + float m_filterq, m_filterpos; + WDL_TypedBuf m_rsinbuf; + WDL_TypedBuf m_filter_coeffs; + + class WDL_Resampler_Filter; + WDL_Resampler_Filter *m_pre_filter, *m_post_filter; + int m_prepost_filtercnt; + + int m_filter_coeffs_size; + int m_last_requested; + int m_filtlatency; + int m_samples_in_rsinbuf; + int m_rsinbuf_nch; + int m_lp_oversize; + int m_sinc_ideal_calced; // -1=not yet calced + + int m_sincsize; + int m_sincoversize; + bool m_interp; + bool m_feedmode; + +}; + + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/rng.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/rng.cpp new file mode 100644 index 0000000..e02dc2e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/rng.cpp @@ -0,0 +1,92 @@ +/* + WDL - rng.cpp + Copyright (C) 2005 and later, 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. + +*/ + +/* + + This file provides the implementation of a decent random number generator, + that internally uses a 256-bit state, and SHA-1 to iterate. + +*/ + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + + +#include "rng.h" +#include "sha.h" + +static unsigned char state[32]; + +void WDL_RNG_addentropy(void *buf, int buflen) +{ + WDL_SHA1 tmp; + tmp.add(state,sizeof(state)); + tmp.result(state); + tmp.reset(); + tmp.add((unsigned char *)buf,buflen); + tmp.result(state+sizeof(state) - WDL_SHA1SIZE); +} + +static void rngcycle() +{ + int i; + for (i = 0; i < (int)sizeof(state) && state[i]++; i++); +} + +int WDL_RNG_int32() +{ + WDL_SHA1 tmp; + tmp.add(state,sizeof(state)); + rngcycle(); + union { + char buf[WDL_SHA1SIZE]; + int a; + } b; + tmp.result(b.buf); + return b.a; + +} + + +void WDL_RNG_bytes(void *buf, int buflen) +{ + char *b=(char *)buf; + while (buflen > 0) + { + char tb[WDL_SHA1SIZE]; + WDL_SHA1 tmp; + tmp.add(state,sizeof(state)); + rngcycle(); + + tmp.result(tb); + const int l=buflen < WDL_SHA1SIZE ? buflen : WDL_SHA1SIZE; + memcpy(b,tb,l); + buflen-=l; + b+=l; + } +} + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/rng.h b/plugin-reaper-realearn/main/lib/WDL/WDL/rng.h new file mode 100644 index 0000000..85fb21c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/rng.h @@ -0,0 +1,42 @@ +/* + WDL - rng.h + Copyright (C) 2005 and later, 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. + +*/ + +/* + + This header provides the interface to a decent random number generator, + that internally uses a 256-bit state, and SHA-1 to iterate. We wouldn't consider + this RNG to be cryptographically secure, but it may be decent. + +*/ + +#ifndef _WDL_RNG_H_ +#define _WDL_RNG_H_ + + +void WDL_RNG_addentropy(void *buf, int buflen); // add entropy to the RNG + +int WDL_RNG_int32(); // get a random integer +void WDL_RNG_bytes(void *buf, int buflen); // get a random string of bytes + + +#endif + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/rpool.h b/plugin-reaper-realearn/main/lib/WDL/WDL/rpool.h new file mode 100644 index 0000000..52f7380 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/rpool.h @@ -0,0 +1,191 @@ +/* + WDL - rpool.h + Copyright (C) 2006 and later, 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. + + + This file defines a template for a class that stores a list of objects, and allows the caller + to periodically get an object, do something with it, and add it back into the pool. + + When the caller does this, it can set ownership of the object, and an expiration for that ownership. + + + The PTYPE1 and PTYPE2entries for the template are there to store additional information (for use with poollist.h) + + + This is pretty esoteric. But we use it for some things. + +*/ + + +#ifndef _WDL_RPOOL_H_ +#define _WDL_RPOOL_H_ + +// resource pool (time based) + +#include "ptrlist.h" +#include "mutex.h" + +class WDL_ResourcePool_ResInfo // include in class RTYPE as WDL_ResourcePool_ResInfo m_rpoolinfo; +{ +public: + WDL_ResourcePool_ResInfo(){ m_owneduntil=0; m_ownerptr=0; next=0; } + ~WDL_ResourcePool_ResInfo() {} + + unsigned int m_owneduntil; + void *m_ownerptr; + + void *next; +} WDL_FIXALIGN; + + +template class WDL_ResourcePool +{ + public: + WDL_ResourcePool(char *identstr) + { + WDL_POOLLIST_refcnt=0; + WDL_POOLLIST_identstr=identstr; + m_rlist=NULL; + extraInfo=0; + m_hadres=false; + } + ~WDL_ResourcePool() + { + while (m_rlist) + { + RTYPE *tp=m_rlist; + m_rlist=(RTYPE *)m_rlist->m_rpoolinfo.next; + delete tp; + } + delete extraInfo; + } + void Clear() + { + m_mutex.Enter(); + while (m_rlist) + { + RTYPE *tp=m_rlist; + m_rlist=(RTYPE *)m_rlist->m_rpoolinfo.next; + delete tp; + } + m_hadres=false; + m_mutex.Leave(); + } + bool HasResources() + { + return m_hadres; + } + + void AddResource(RTYPE *item, void *own, unsigned int until) + { + item->m_rpoolinfo.m_ownerptr = own; + item->m_rpoolinfo.m_owneduntil = until; + + m_mutex.Enter(); + item->m_rpoolinfo.next = m_rlist; + m_rlist = item; + m_hadres=true; + m_mutex.Leave(); + } + + void ReleaseResources(void *own) + { + m_mutex.Enter(); + RTYPE *ent=m_rlist; + while (ent) + { + if (ent->m_rpoolinfo.m_ownerptr == own) + { + ent->m_rpoolinfo.m_ownerptr = 0; + ent->m_rpoolinfo.m_owneduntil=0; + } + ent=(RTYPE *)ent->m_rpoolinfo.next; + } + m_mutex.Leave(); + } + + RTYPE *GetResource(void *own, unsigned int now, int flags=0) // flags&1 to only match owner (not look at owneduntil). flags&2 to ignore owneduntil and always take from others (greedy) + { + m_mutex.Enter(); + RTYPE *ent=m_rlist, *lastent=NULL, *bestent=NULL, *bestlastent=NULL; + bool bestnoown=false; + while (ent) + { + if (ent->m_rpoolinfo.m_ownerptr == own) + { + if (lastent) lastent->m_rpoolinfo.next = ent->m_rpoolinfo.next; + else m_rlist = (RTYPE *)ent->m_rpoolinfo.next; + m_mutex.Leave(); + return ent; + } + + if (!bestnoown) + { + if (!ent->m_rpoolinfo.m_ownerptr || + (flags & 2) || + (!(flags & 1) && (now - (ent->m_rpoolinfo.m_owneduntil+1)) <= 0x7FFFFFFF)) + { + bestent=ent; + bestlastent=lastent; + if (!ent->m_rpoolinfo.m_ownerptr || !ent->m_rpoolinfo.m_owneduntil) bestnoown=true; + } + } + lastent=ent; + ent=(RTYPE *)ent->m_rpoolinfo.next; + } + + if (bestent) + { + if (bestlastent) bestlastent->m_rpoolinfo.next = bestent->m_rpoolinfo.next; + else m_rlist = (RTYPE *)bestent->m_rpoolinfo.next; + } + + m_mutex.Leave(); + return bestent; + } + + int WDL_POOLLIST_refcnt; + char *WDL_POOLLIST_identstr; + bool m_hadres; + + EXTRAINFOTYPE *extraInfo; + + RTYPE *PeekList() + { + return m_rlist; + } + void LockList() + { + m_mutex.Enter(); + } + void UnlockList() + { + m_mutex.Leave(); + } + +private: + + WDL_Mutex m_mutex; + RTYPE *m_rlist; + +} WDL_FIXALIGN; + + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/sc_bounce/index.html b/plugin-reaper-realearn/main/lib/WDL/WDL/sc_bounce/index.html new file mode 100644 index 0000000..3fd2c20 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/sc_bounce/index.html @@ -0,0 +1,50 @@ + + +stream + + + +
...loading...
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/Makefile b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/Makefile new file mode 100644 index 0000000..16bb5c8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/Makefile @@ -0,0 +1,102 @@ +APPNAME=myapp + +WDL_PATH = ../WDL +vpath swell%.cpp $(WDL_PATH)/swell +vpath lice%.cpp $(WDL_PATH)/lice +vpath %.c $(WDL_PATH) +vpath %.cpp $(WDL_PATH) $(WDL_PATH)/wingui + +###### Objects and resources (probably too many) +SWELL_OBJS = swell.o swell-ini.o swell-miscdlg-generic.o swell-wnd-generic.o \ + swell-menu-generic.o swell-kb-generic.o swell-dlg-generic.o \ + swell-gdi-generic.o swell-misc-generic.o swell-gdi-lice.o \ + swell-generic-gdk.o swell-appstub-generic.o swell-modstub-generic.o + +LICE_OBJS = lice_image.o lice_arc.o lice_line.o lice_text.o \ + lice_textnew.o lice.o lice_colorspace.o + +OTHER_OBJS = wndsize.o + +RESFILES = res.rc_mac_dlg res.rc_mac_menu + +OBJS += myapp.o $(SWELL_OBJS) $(OTHER_OBJS) + + +###### Compiler/Linker flags +CFLAGS += -pipe -fvisibility=hidden -fno-math-errno -fPIC -DPIC -Wall -Wshadow -Wtype-limits \ + -Wno-unused-function -Wno-multichar -Wno-unused-result + +CFLAGS += -D_FILE_OFFSET_BITS=64 + +ARCH := $(shell uname -m) +PKG_CONFIG = pkg-config + +ifndef ALLOW_WARNINGS + CFLAGS += -Werror +endif +ifndef DEPRECATED_WARNINGS + CFLAGS += -Wno-deprecated-declarations +endif + +ifneq ($(filter arm%,$(ARCH)),) + CFLAGS += -fsigned-char -marm +endif + +ifdef DEBUG + CFLAGS += -O0 -g -D_DEBUG -DWDL_CHECK_FOR_NON_UTF8_FOPEN +else + CFLAGS += -O2 -DNDEBUG +endif + +LINKEXTRA = -lpthread -ldl + +ifndef NOGDK + ifdef GDK2 + CFLAGS += -DSWELL_TARGET_GDK=2 $(shell $(PKG_CONFIG) --cflags gdk-2.0) + LINKEXTRA += $(shell $(PKG_CONFIG) --libs gdk-2.0) + LINKEXTRA += -lX11 -lXi + else + ifdef SWELL_SUPPORT_GTK + CFLAGS += -DSWELL_TARGET_GDK=3 $(shell $(PKG_CONFIG) --cflags gtk+-3.0) -DSWELL_SUPPORT_GTK + else + CFLAGS += -DSWELL_TARGET_GDK=3 $(shell $(PKG_CONFIG) --cflags gdk-3.0) + endif + LINKEXTRA += -lX11 -lXi -lGL + ifdef SWELL_SUPPORT_GTK + LINKEXTRA += $(shell $(PKG_CONFIG) --libs gtk+-3.0) + else + LINKEXTRA += $(shell $(PKG_CONFIG) --libs gdk-3.0) + endif + endif + CFLAGS += -DSWELL_LICE_GDI + OBJS += $(LICE_OBJS) + + ifndef NOFREETYPE + CFLAGS += -DSWELL_FREETYPE $(shell $(PKG_CONFIG) --cflags freetype2) + LINKEXTRA += $(shell $(PKG_CONFIG) --libs freetype2) + ifndef NOFONTCONFIG + CFLAGS += -DSWELL_FONTCONFIG + LINKEXTRA += -lfontconfig + endif + endif +endif + + +CXXFLAGS = $(CFLAGS) + + +default: $(APPNAME) + +.PHONY: clean run + +$(RESFILES): res.rc + $(WDL_PATH)/swell/swell_resgen.pl $^ + +$(APPNAME): $(RESFILES) $(OBJS) + $(CXX) -o $@ $(CFLAGS) $(OBJS) $(LINKEXTRA) + +run: $(APPNAME) + ./$^ + +clean: + -rm $(OBJS) $(APPNAME) $(RESFILES) diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp-Info.plist b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp-Info.plist new file mode 100644 index 0000000..1666f48 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp-Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + MyApp + CFBundleIdentifier + nobody.swell.myapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.3 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright © 2022 + NSMainNibFile + MainMenu + NSPrincipalClass + SWELLApplication + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp-Prefix.pch b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp-Prefix.pch new file mode 100644 index 0000000..4187f19 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.icns b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.icns new file mode 100644 index 0000000..6eeef52 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.icns differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.sln b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.sln new file mode 100644 index 0000000..21d0a47 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MyApp", "MyApp.vcxproj", "{DC8CCBA9-5214-48ED-96AE-C18B212FBE10}" +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 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|Win32.ActiveCfg = Debug|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|Win32.Build.0 = Debug|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|x64.ActiveCfg = Debug|x64 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Debug|x64.Build.0 = Debug|x64 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|Win32.ActiveCfg = Release|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|Win32.Build.0 = Release|Win32 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|x64.ActiveCfg = Release|x64 + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.vcxproj b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.vcxproj new file mode 100644 index 0000000..d7a9032 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.vcxproj @@ -0,0 +1,170 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {DC8CCBA9-5214-48ED-96AE-C18B212FBE10} + Win32Proj + MyApp + + + + Application + true + v120 + MultiByte + + + Application + true + v120 + MultiByte + + + Application + false + v120 + true + MultiByte + + + Application + false + v120 + true + MultiByte + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\ + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + false + + + Windows + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + false + + + Windows + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;BUILD_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;winmm.lib;wsock32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.vcxproj.filters b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.vcxproj.filters new file mode 100644 index 0000000..21caa74 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.vcxproj.filters @@ -0,0 +1,44 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;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 + + + {26d63633-dc60-4f96-afe7-14e5aa4ad154} + + + + + + + + Header Files + + + + + Source Files\WDL + + + Source Files\WDL + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.xcodeproj/project.pbxproj b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3fc809a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/MyApp.xcodeproj/project.pbxproj @@ -0,0 +1,406 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 338536692957A46300048720 /* myapp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 338536682957A46300048720 /* myapp.cpp */; }; + 3385366B2957A4D300048720 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3385366A2957A4D300048720 /* main.m */; }; + 338536702957A62F00048720 /* MyApp.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3385366E2957A62F00048720 /* MyApp.icns */; }; + 338536732957A6EB00048720 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 338536712957A6EB00048720 /* MainMenu.xib */; }; + 3385932D185B555500FDFFEA /* wndsize.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3385932C185B555500FDFFEA /* wndsize.cpp */; }; + 33AA3116185A7E8D001D767E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33AA3115185A7E8D001D767E /* Cocoa.framework */; }; + 33AA3126185A7E8D001D767E /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 33AA3124185A7E8D001D767E /* Credits.rtf */; }; + 33F96581185A7F79004C7070 /* swell-appstub.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96576185A7F79004C7070 /* swell-appstub.mm */; }; + 33F96582185A7F79004C7070 /* swell-dlg.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96577185A7F79004C7070 /* swell-dlg.mm */; }; + 33F96583185A7F79004C7070 /* swell-gdi.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96578185A7F79004C7070 /* swell-gdi.mm */; }; + 33F96584185A7F79004C7070 /* swell-ini.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33F96579185A7F79004C7070 /* swell-ini.cpp */; }; + 33F96585185A7F79004C7070 /* swell-kb.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657A185A7F79004C7070 /* swell-kb.mm */; }; + 33F96586185A7F79004C7070 /* swell-menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657B185A7F79004C7070 /* swell-menu.mm */; }; + 33F96587185A7F79004C7070 /* swell-misc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657C185A7F79004C7070 /* swell-misc.mm */; }; + 33F96588185A7F79004C7070 /* swell-miscdlg.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657D185A7F79004C7070 /* swell-miscdlg.mm */; }; + 33F96589185A7F79004C7070 /* swell-wnd.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657E185A7F79004C7070 /* swell-wnd.mm */; }; + 33F9658A185A7F79004C7070 /* swell.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33F9657F185A7F79004C7070 /* swell.cpp */; }; + 33F9658B185A7F79004C7070 /* swellappmain.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33F96580185A7F79004C7070 /* swellappmain.mm */; }; + 33F9658D185A8228004C7070 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33F9658C185A8228004C7070 /* Carbon.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 338536682957A46300048720 /* myapp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = myapp.cpp; sourceTree = SOURCE_ROOT; }; + 3385366A2957A4D300048720 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; }; + 3385366C2957A62F00048720 /* MyApp-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "MyApp-Info.plist"; sourceTree = SOURCE_ROOT; }; + 3385366D2957A62F00048720 /* MyApp-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MyApp-Prefix.pch"; sourceTree = SOURCE_ROOT; }; + 3385366E2957A62F00048720 /* MyApp.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = MyApp.icns; sourceTree = SOURCE_ROOT; }; + 338536722957A6EB00048720 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = SOURCE_ROOT; }; + 3385932C185B555500FDFFEA /* wndsize.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wndsize.cpp; path = ../WDL/wingui/wndsize.cpp; sourceTree = ""; }; + 33AA3112185A7E8D001D767E /* MyApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33AA3115185A7E8D001D767E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 33AA3118185A7E8D001D767E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 33AA3119185A7E8D001D767E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + 33AA311A185A7E8D001D767E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 33AA311F185A7E8D001D767E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 33AA3125185A7E8D001D767E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; + 33F96576185A7F79004C7070 /* swell-appstub.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-appstub.mm"; path = "../WDL/swell/swell-appstub.mm"; sourceTree = ""; }; + 33F96577185A7F79004C7070 /* swell-dlg.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-dlg.mm"; path = "../WDL/swell/swell-dlg.mm"; sourceTree = ""; }; + 33F96578185A7F79004C7070 /* swell-gdi.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-gdi.mm"; path = "../WDL/swell/swell-gdi.mm"; sourceTree = ""; }; + 33F96579185A7F79004C7070 /* swell-ini.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "swell-ini.cpp"; path = "../WDL/swell/swell-ini.cpp"; sourceTree = ""; }; + 33F9657A185A7F79004C7070 /* swell-kb.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-kb.mm"; path = "../WDL/swell/swell-kb.mm"; sourceTree = ""; }; + 33F9657B185A7F79004C7070 /* swell-menu.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-menu.mm"; path = "../WDL/swell/swell-menu.mm"; sourceTree = ""; }; + 33F9657C185A7F79004C7070 /* swell-misc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-misc.mm"; path = "../WDL/swell/swell-misc.mm"; sourceTree = ""; }; + 33F9657D185A7F79004C7070 /* swell-miscdlg.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-miscdlg.mm"; path = "../WDL/swell/swell-miscdlg.mm"; sourceTree = ""; }; + 33F9657E185A7F79004C7070 /* swell-wnd.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "swell-wnd.mm"; path = "../WDL/swell/swell-wnd.mm"; sourceTree = ""; }; + 33F9657F185A7F79004C7070 /* swell.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = swell.cpp; path = ../WDL/swell/swell.cpp; sourceTree = ""; }; + 33F96580185A7F79004C7070 /* swellappmain.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = swellappmain.mm; path = ../WDL/swell/swellappmain.mm; sourceTree = ""; }; + 33F9658C185A8228004C7070 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33AA310F185A7E8D001D767E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 33F9658D185A8228004C7070 /* Carbon.framework in Frameworks */, + 33AA3116185A7E8D001D767E /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33AA3109185A7E8D001D767E = { + isa = PBXGroup; + children = ( + 338536712957A6EB00048720 /* MainMenu.xib */, + 338536682957A46300048720 /* myapp.cpp */, + 3385366C2957A62F00048720 /* MyApp-Info.plist */, + 3385366D2957A62F00048720 /* MyApp-Prefix.pch */, + 3385366E2957A62F00048720 /* MyApp.icns */, + 3385366A2957A4D300048720 /* main.m */, + 33AA3149185A7EEC001D767E /* WDL */, + 33AA311E185A7E8D001D767E /* InfoPlist.strings */, + 33AA3124185A7E8D001D767E /* Credits.rtf */, + 33AA3114185A7E8D001D767E /* Frameworks */, + 33AA3113185A7E8D001D767E /* Products */, + ); + sourceTree = ""; + }; + 33AA3113185A7E8D001D767E /* Products */ = { + isa = PBXGroup; + children = ( + 33AA3112185A7E8D001D767E /* MyApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 33AA3114185A7E8D001D767E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 33F9658C185A8228004C7070 /* Carbon.framework */, + 33AA3115185A7E8D001D767E /* Cocoa.framework */, + 33AA3117185A7E8D001D767E /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + 33AA3117185A7E8D001D767E /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 33AA3118185A7E8D001D767E /* AppKit.framework */, + 33AA3119185A7E8D001D767E /* CoreData.framework */, + 33AA311A185A7E8D001D767E /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 33AA3149185A7EEC001D767E /* WDL */ = { + isa = PBXGroup; + children = ( + 3385932C185B555500FDFFEA /* wndsize.cpp */, + 33AA314A185A7EF4001D767E /* swell */, + ); + name = WDL; + path = ../WDL; + sourceTree = ""; + }; + 33AA314A185A7EF4001D767E /* swell */ = { + isa = PBXGroup; + children = ( + 33F96576185A7F79004C7070 /* swell-appstub.mm */, + 33F96577185A7F79004C7070 /* swell-dlg.mm */, + 33F96578185A7F79004C7070 /* swell-gdi.mm */, + 33F96579185A7F79004C7070 /* swell-ini.cpp */, + 33F9657A185A7F79004C7070 /* swell-kb.mm */, + 33F9657B185A7F79004C7070 /* swell-menu.mm */, + 33F9657C185A7F79004C7070 /* swell-misc.mm */, + 33F9657D185A7F79004C7070 /* swell-miscdlg.mm */, + 33F9657E185A7F79004C7070 /* swell-wnd.mm */, + 33F9657F185A7F79004C7070 /* swell.cpp */, + 33F96580185A7F79004C7070 /* swellappmain.mm */, + ); + name = swell; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33AA3111185A7E8D001D767E /* MyApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33AA3143185A7E8D001D767E /* Build configuration list for PBXNativeTarget "MyApp" */; + buildPhases = ( + 3304E16F1FC2253B00290319 /* ShellScript */, + 33AA310E185A7E8D001D767E /* Sources */, + 33AA310F185A7E8D001D767E /* Frameworks */, + 33AA3110185A7E8D001D767E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MyApp; + productName = myapp; + productReference = 33AA3112185A7E8D001D767E /* MyApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33AA310A185A7E8D001D767E /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = cockos; + }; + buildConfigurationList = 33AA310D185A7E8D001D767E /* Build configuration list for PBXProject "MyApp" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33AA3109185A7E8D001D767E; + productRefGroup = 33AA3113185A7E8D001D767E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33AA3111185A7E8D001D767E /* MyApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33AA3110185A7E8D001D767E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 338536732957A6EB00048720 /* MainMenu.xib in Resources */, + 33AA3126185A7E8D001D767E /* Credits.rtf in Resources */, + 338536702957A62F00048720 /* MyApp.icns in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3304E16F1FC2253B00290319 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/bin/perl ../WDL/swell/swell_resgen.sh res.rc"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33AA310E185A7E8D001D767E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33F96581185A7F79004C7070 /* swell-appstub.mm in Sources */, + 33F9658B185A7F79004C7070 /* swellappmain.mm in Sources */, + 33F9658A185A7F79004C7070 /* swell.cpp in Sources */, + 33F96584185A7F79004C7070 /* swell-ini.cpp in Sources */, + 3385932D185B555500FDFFEA /* wndsize.cpp in Sources */, + 33F96589185A7F79004C7070 /* swell-wnd.mm in Sources */, + 33F96582185A7F79004C7070 /* swell-dlg.mm in Sources */, + 33F96585185A7F79004C7070 /* swell-kb.mm in Sources */, + 33F96586185A7F79004C7070 /* swell-menu.mm in Sources */, + 33F96583185A7F79004C7070 /* swell-gdi.mm in Sources */, + 338536692957A46300048720 /* myapp.cpp in Sources */, + 33F96587185A7F79004C7070 /* swell-misc.mm in Sources */, + 3385366B2957A4D300048720 /* main.m in Sources */, + 33F96588185A7F79004C7070 /* swell-miscdlg.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 338536712957A6EB00048720 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 338536722957A6EB00048720 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; + 33AA311E185A7E8D001D767E /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 33AA311F185A7E8D001D767E /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 33AA3124185A7E8D001D767E /* Credits.rtf */ = { + isa = PBXVariantGroup; + children = ( + 33AA3125185A7E8D001D767E /* en */, + ); + name = Credits.rtf; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 33AA3141185A7E8D001D767E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++98"; + CLANG_CXX_LIBRARY = "libstdc++"; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_STRICT_ALIASING = NO; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 33AA3142185A7E8D001D767E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++98"; + CLANG_CXX_LIBRARY = "libstdc++"; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_STRICT_ALIASING = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + SDKROOT = macosx; + }; + name = Release; + }; + 33AA3144185A7E8D001D767E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LIBRARY = "libc++"; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "MyApp-Prefix.pch"; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + INFOPLIST_FILE = "MyApp-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_BUNDLE_IDENTIFIER = nobody.swell.myapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 33AA3145185A7E8D001D767E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LIBRARY = "libc++"; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "MyApp-Prefix.pch"; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + INFOPLIST_FILE = "MyApp-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_BUNDLE_IDENTIFIER = nobody.swell.myapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33AA310D185A7E8D001D767E /* Build configuration list for PBXProject "MyApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33AA3141185A7E8D001D767E /* Debug */, + 33AA3142185A7E8D001D767E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33AA3143185A7E8D001D767E /* Build configuration list for PBXNativeTarget "MyApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33AA3144185A7E8D001D767E /* Debug */, + 33AA3145185A7E8D001D767E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33AA310A185A7E8D001D767E /* Project object */; +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/Resources/main.png b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/Resources/main.png new file mode 100644 index 0000000..a0d791c Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/Resources/main.png differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/en.lproj/Credits.rtf b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/en.lproj/Credits.rtf new file mode 100644 index 0000000..33bd334 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/en.lproj/Credits.rtf @@ -0,0 +1,7 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1265 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\vieww9600\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 + +\f0\b\fs24 \cf0 This is it, word.} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/en.lproj/InfoPlist.strings b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/icon1.ico b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/icon1.ico new file mode 100644 index 0000000..09660b3 Binary files /dev/null and b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/icon1.ico differ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/main.m b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/main.m new file mode 100644 index 0000000..f0184cc --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/main.m @@ -0,0 +1,15 @@ +// +// main.m +// swell_myapp +// + +#import + +int main(int argc, const char * argv[]) +{ + extern const char **g_argv; + extern int g_argc; + g_argc=argc; + g_argv=argv; + return NSApplicationMain(argc, argv); +} diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/myapp.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/myapp.cpp new file mode 100644 index 0000000..cc183dd --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/myapp.cpp @@ -0,0 +1,199 @@ +/* + swell_myapp + + 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. +*/ + +#ifdef _WIN32 +#include +#include "../WDL/win32_utf8.h" +#endif + +#include "../WDL/swell/swell.h" + +#include "../WDL/wingui/wndsize.h" + +#include "resource.h" + +#if !defined(_WIN32) && !defined(__APPLE__) +bool g_quit; +#endif + +HINSTANCE g_hInstance; +HWND g_hwnd; + +WDL_DLGRET mainProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static WDL_WndSizer resize; + switch (uMsg) + { + case WM_INITDIALOG: + g_hwnd=hwndDlg; +#ifdef _WIN32 + { + HICON icon=LoadIcon(g_hInstance,MAKEINTRESOURCE(IDI_ICON1)); + SetClassLongPtr(hwndDlg,GCLP_HICON,(LPARAM)icon); + } +#endif + + resize.init(hwndDlg); + resize.init_item(IDCANCEL,0,1,0,1); + return 1; + case WM_CLOSE: + DestroyWindow(hwndDlg); + return 1; + case WM_DESTROY: + g_hwnd=NULL; +#ifdef __APPLE__ + SWELL_PostQuitMessage(0); +#elif defined(_WIN32) + PostQuitMessage(0); +#else + g_quit = true; +#endif + break; + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) + resize.onResize(); + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case ID_QUIT: + case IDCANCEL: + DestroyWindow(hwndDlg); + break; + } + break; + } + return 0; +} + +INT_PTR SWELLAppMain(int msg, INT_PTR parm1, INT_PTR parm2) +{ + switch (msg) + { + case SWELLAPP_ONLOAD: + { + } + break; + case SWELLAPP_LOADED: + { + HWND h=CreateDialog(NULL,MAKEINTRESOURCE(IDD_DIALOG1),NULL,mainProc); + ShowWindow(h,SW_SHOW); + } + break; + case SWELLAPP_DESTROY: + if (g_hwnd) DestroyWindow(g_hwnd); + break; + case SWELLAPP_ONCOMMAND: + // this is to catch commands coming from the system menu etc + if (g_hwnd && parm1) SendMessage(g_hwnd,WM_COMMAND,parm1,0); + break; + + } + return 0; +} + + + +#ifdef _WIN32 + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + g_hInstance = hInstance; + + SWELLAppMain(SWELLAPP_ONLOAD,0,0); + SWELLAppMain(SWELLAPP_LOADED,0,0); + + for(;;) + { + MSG msg={0,}; + int vvv = GetMessage(&msg,NULL,0,0); + if (!vvv) break; + + if (vvv<0) + { + Sleep(10); + continue; + } + if (!msg.hwnd) + { + DispatchMessage(&msg); + continue; + } + if (SWELLAppMain(SWELLAPP_PROCESSMESSAGE, (INT_PTR) &msg, 0)) continue; + + if (g_hwnd && IsDialogMessage(g_hwnd,&msg)) continue; + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + SWELLAppMain(SWELLAPP_DESTROY,0,0); + + ExitProcess(0); + + return 0; +} + +#else + +/************** SWELL stuff ********** */ + +#ifdef __APPLE__ +extern "C" { +#endif + +const char **g_argv; +int g_argc; + +#ifdef __APPLE__ +}; +#endif + + +#ifndef __APPLE__ + +int main(int argc, const char **argv) +{ + g_argc=argc; + g_argv=argv; + SWELL_initargs(&argc,(char***)&argv); + SWELL_Internal_PostMessage_Init(); + SWELL_ExtendedAPI("APPNAME",(void*)"MyApp"); + SWELLAppMain(SWELLAPP_ONLOAD,0,0); + SWELLAppMain(SWELLAPP_LOADED,0,0); + while (!g_quit) { + SWELL_RunMessageLoop(); + Sleep(10); + } + SWELLAppMain(SWELLAPP_DESTROY,0,0); + return 0; +} + +#endif + + +#include "../WDL/swell/swell-dlggen.h" +#include "res.rc_mac_dlg" +#undef BEGIN +#undef END +#include "../WDL/swell/swell-menugen.h" +#include "res.rc_mac_menu" + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/readme.txt b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/readme.txt new file mode 100644 index 0000000..042843d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/readme.txt @@ -0,0 +1,66 @@ +SWELL_MyApp +Copyright (C) 2022 and onwards, Cockos Incorporated +LICENSE: + + 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. + +Minimal SWELL win32/linux/macOS GUI application starting point, feel free to base your applications on this. + +NOTE: this project is not configured to be compiled in-place, you should move it to be alongside WDL, e.g. + +cp -a ./WDL/swell/swell_myapp ./my_new_app + + +------ + +Previous sample project xcode instructions: + + How we created the xcode project that compiles this, too: + + New Project -> Mac OS X -> Application -> Cocoa Application + + Save as... + + Add to "Other sources/SWELL": (from swell path) swell-dlg.mm swell-gdi.mm swell-ini.cpp swell-kb.mm swell-menu.mm swell-misc.mm swell-miscdlg.mm swell-wnd.mm swell.cpp swell-appstub.mm swellappmain.mm + + Add app_main.cpp main_dialog.cpp to "Other sources" + + Go to Frameworks -> Linked Frameworks, add existing framework, Carbon.Framework + + go to terminal, to project dir, and run /swell_resgen.pl sample_project.rc + + Open mainmenu.xib in Interface Builder (doubleclick it in XCode) + + + Delete the default "Window" + + File->Read class files, find and select "swellappmain.h" + + Go to Library, Objects, scroll to "Object" , drag to "MainMenu.xib", rename to "Controller", then open its + properties (Cmd+Shift+I, go to class identity tab), choose for Class "SWELLAppController". + + Select "Application" in MainMenu.xib, go to (Cmd+Shift+I) application identity tab, select "SWELLApplication" for the class. + + + Customize the "NewApplication" menu. + + Properties on "About NewApplication": + + set the tag to 40002 (matching resource.h for about) + + on the connection tab, "Sent Actions", remove the default connection, then drag a new connection to controller (onSysMenuCommand). + + Properties on "Quit NewApplication": + + set the tag to 40001 (matching resource.h for quit) + + on the connection tab, "Sent Actions", remove the default connection, then drag a new connection to controller (onSysMenuCommand). + + Delete the file/edit/format/view/window/help menus, if you like (or keep some of them if you want) + + + Save and quit IB + + Go to Targets->sample_project, hit cmd+I, go to "Properties", and change Principal class to "SWELLApplication" + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/res.rc b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/res.rc new file mode 100644 index 0000000..9a8125b --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/res.rc @@ -0,0 +1,151 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON DISCARDABLE "icon1.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 377, 261 +STYLE DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "SWELL MyApp" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Quit",IDCANCEL,4,244,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 374 + TOPMARGIN, 3 + BOTTOMMARGIN, 258 + END +END +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,0,0,1 + PRODUCTVERSION 0,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "unknown\0" + VALUE "FileDescription", "swell_myapp\0" + VALUE "FileVersion", "0, 0, 0, 1\0" + VALUE "InternalName", "swell_myapp\0" + VALUE "LegalCopyright", "Copyright 2022 and onwards\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "MyApp.exe\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "swell_myapp\0" + VALUE "ProductVersion", "0, 0, l, 1\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/resource.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/resource.h new file mode 100644 index 0000000..694388c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/sample_project/resource.h @@ -0,0 +1,22 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by res.rc +// +#define IDI_ICON1 101 +#define IDD_DIALOG1 102 +#define IDC_EDIT1 1000 +#define IDC_BUTTON1 1001 +#define IDC_LASTMSG 1002 +#define IDC_CHECK1 1003 +#define ID_QUIT 40000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1004 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-appstub-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-appstub-generic.cpp new file mode 100644 index 0000000..d6de07c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-appstub-generic.cpp @@ -0,0 +1,83 @@ +/* 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. +*/ + +#include "swell.h" + + +#ifndef SWELL_PROVIDED_BY_APP + +// only add this file to your project if you are an application that wishes to publish the SWELL API to its modules/plugins +// the modules should be compiled using SWELL_PROVIDED_BY_APP and include swell-modstub-generic.cpp + +#undef _WDL_SWELL_H_API_DEFINED_ +#undef SWELL_API_DEFINE +#define SWELL_API_DEFINE(ret, func, parms) {#func, (void *)func }, +static struct api_ent +{ + const char *name; + void *func; +} +api_table[]= +{ +#include "swell.h" +}; + +static int compfunc(const void *a, const void *b) +{ + return strcmp(((struct api_ent*)a)->name,((struct api_ent*)b)->name); +} + +extern "C" { + +__attribute__ ((visibility ("default"))) void *SWELLAPI_GetFunc(const char *name) +{ + if (!name) return (void *)0x100; // version + static int a; + if (!a) + { + a=1; + qsort(api_table,sizeof(api_table)/sizeof(api_table[0]),sizeof(api_table[0]),compfunc); + } + struct api_ent find={name,NULL}; + struct api_ent *res=(struct api_ent *)bsearch(&find,api_table,sizeof(api_table)/sizeof(api_table[0]),sizeof(api_table[0]),compfunc); + if (res) return res->func; + return NULL; +} + +}; + +#ifdef SWELL_MAKING_DYLIB +static INT_PTR (*s_AppMain)(int msg, INT_PTR parm1, INT_PTR parm2); +INT_PTR SWELLAppMain(int msg, INT_PTR parm1, INT_PTR parm2) +{ + if (s_AppMain) return s_AppMain(msg,parm1,parm2); + return 0; +} + +extern "C" { +__attribute__ ((visibility ("default"))) void SWELL_set_app_main(INT_PTR (*AppMain)(int msg, INT_PTR parm1, INT_PTR parm2)) +{ + s_AppMain = AppMain; +} +}; + +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-appstub.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-appstub.mm new file mode 100644 index 0000000..b08c111 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-appstub.mm @@ -0,0 +1,61 @@ +/* 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. +*/ +#include "swell.h" + + +#ifndef SWELL_PROVIDED_BY_APP + +// only add this file to your project if you are an application that wishes to publish the SWELL API to its modules/plugins +// the modules should be compiled using SWELL_PROVIDED_BY_APP and include swell-modstub.mm + +#undef _WDL_SWELL_H_API_DEFINED_ +#undef SWELL_API_DEFINE +#define SWELL_API_DEFINE(ret, func, parms) {#func, (void *)func }, +static struct api_ent +{ + const char *name; + void *func; +} +api_table[]= +{ +#include "swell-functions.h" +}; + +static int compfunc(const void *a, const void *b) +{ + return strcmp(((struct api_ent*)a)->name,((struct api_ent*)b)->name); +} + +void *SWELLAPI_GetFunc(const char *name) +{ + if (!name) return (void *)0x100; // version + static int a; + if (!a) + { + a=1; + qsort(api_table,sizeof(api_table)/sizeof(api_table[0]),sizeof(api_table[0]),compfunc); + } + struct api_ent find={name,NULL}; + struct api_ent *res=(struct api_ent *)bsearch(&find,api_table,sizeof(api_table)/sizeof(api_table[0]),sizeof(api_table[0]),compfunc); + if (res) return res->func; + return NULL; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlg-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlg-generic.cpp new file mode 100644 index 0000000..8b4d6e2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlg-generic.cpp @@ -0,0 +1,387 @@ +/* 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. +*/ + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-dlggen.h" + +#include "../ptrlist.h" + +static HMENU g_swell_defaultmenu,g_swell_defaultmenumodal; + +bool SWELL_owned_windows_levelincrease=false; + +#include "swell-internal.h" + +static SWELL_DialogResourceIndex *resById(SWELL_DialogResourceIndex *reshead, const char *resid) +{ + SWELL_DialogResourceIndex *p=reshead; + while (p) + { + if (p->resid == resid) return p; + p=p->_next; + } + return 0; +} + +// keep list of modal dialogs +struct modalDlgRet { + HWND hwnd; + bool has_ret; + int ret; +}; + + +static WDL_PtrList s_modalDialogs; + +bool IsModalDialogBox(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return false; + int a = s_modalDialogs.GetSize(); + while (a-- > 0) + { + modalDlgRet *r = s_modalDialogs.Get(a); + if (r && r->hwnd == hwnd) return true; + } + return false; +} + +HWND DialogBoxIsActive() +{ + int a = s_modalDialogs.GetSize(); + while (a-- > 0) + { + modalDlgRet *r = s_modalDialogs.Get(a); + if (r && !r->has_ret && r->hwnd) return r->hwnd; + } + return NULL; +} + +static SWELL_OSWINDOW s_spare; +static RECT s_spare_rect; +static UINT_PTR s_spare_timer; +static int s_spare_style; + +void swell_dlg_destroyspare() +{ + if (s_spare_timer) + { + KillTimer(NULL,s_spare_timer); + s_spare_timer=0; + } + if (s_spare) + { +#ifdef SWELL_TARGET_GDK + gdk_window_destroy(s_spare); +#endif + s_spare=NULL; + } +} + +static void spareTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwtime) +{ + swell_dlg_destroyspare(); +} + +static int s_last_dlgret; + +void EndDialog(HWND wnd, int ret) +{ + if (WDL_NOT_NORMALLY(!wnd)) return; + + int a = s_modalDialogs.GetSize(); + while (a-->0) + { + modalDlgRet *r = s_modalDialogs.Get(a); + if (r && r->hwnd == wnd) + { + r->ret = ret; + if (r->has_ret) return; + + r->has_ret=true; + } + } + + if (!wnd->m_hashaddestroy) + { + void RecurseDestroyWindow(HWND); + SendMessage(wnd,WM_DESTROY,0,0); + #ifndef SWELL_NO_SPARE_MODALDLG + if (wnd->m_oswindow && wnd->m_visible) + { + swell_dlg_destroyspare(); + s_spare_rect = wnd->m_position; + s_spare_style = wnd->m_style; + s_spare = wnd->m_oswindow; + wnd->m_oswindow = NULL; + s_spare_timer = SetTimer(NULL,0, + swell_is_app_inactive()>0 ? 500 : 100, + spareTimer); + } + #endif + RecurseDestroyWindow(wnd); + } + s_last_dlgret = ret; +} + +int SWELL_DialogBox(SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent, DLGPROC dlgproc, LPARAM param) +{ + SWELL_DialogResourceIndex *p=resById(reshead,resid); + if (resid) // allow modal dialogs to be created without template + { + if (!p||(p->windowTypeFlags&SWELL_DLG_WS_CHILD)) return -1; + } + else if (parent) + { + resid = (const char *)(INT_PTR)(0x400002); // force non-child, force no minimize box + } + + + int ret=-1; + s_last_dlgret = -1; + HWND hwnd = SWELL_CreateDialog(reshead,resid,parent,dlgproc,param); + // create dialog + if (hwnd) + { + hwnd->Retain(); + + void SWELL_OnNavigationFocus(HWND ch); + HWND SWELL_GetFocusedChild(HWND h); + SWELL_OnNavigationFocus(SWELL_GetFocusedChild(hwnd)); + + ReleaseCapture(); // force end of any captures + + WDL_PtrKeyedArray restwnds; + extern HWND__ *SWELL_topwindows; + HWND a = SWELL_topwindows; + while (a) + { + if (a!=hwnd) + { + int f=0; + if (a->m_enabled) { EnableWindow(a,FALSE); f|=1; } + if (a->m_israised) { SWELL_SetWindowLevel(a,0); f|=2; } + if (f) restwnds.AddUnsorted((INT_PTR)a,f); + } + a = a->m_next; + } + restwnds.Resort(); + SWELL_SetWindowLevel(hwnd,1); + + modalDlgRet r = { hwnd,false, -1 }; + s_modalDialogs.Add(&r); + + if (s_spare && s_spare_style == hwnd->m_style) + { + if (s_spare_timer) + { + KillTimer(NULL,s_spare_timer); + s_spare_timer = 0; + } + SWELL_OSWINDOW w = s_spare; + s_spare = NULL; + + int flags = 0; + const int dw = (hwnd->m_position.right-hwnd->m_position.left) - + (s_spare_rect.right - s_spare_rect.left); + const int dh = (hwnd->m_position.bottom-hwnd->m_position.top) - + (s_spare_rect.bottom - s_spare_rect.top); + + if (hwnd->m_has_had_position) flags |= 1; + if (dw || dh) flags |= 2; + + if (flags == 2) + { + // center on the old window + hwnd->m_position.right -= hwnd->m_position.left; + hwnd->m_position.bottom -= hwnd->m_position.top; + hwnd->m_position.left = s_spare_rect.left - dw/2; + hwnd->m_position.top = s_spare_rect.top - dh/2; + hwnd->m_position.right += hwnd->m_position.left; + hwnd->m_position.bottom += hwnd->m_position.top; + flags = 3; + } + + if (flags) + { + if (flags&2) swell_oswindow_begin_resize(w); + swell_oswindow_resize(w, flags, hwnd->m_position); + } + hwnd->m_oswindow = w; + + if (!flags) + { + hwnd->m_has_had_position = true; + hwnd->m_position = s_spare_rect; + if (!hwnd->m_hashaddestroy) + { + void swell_recalcMinMaxInfo(HWND hwnd); + swell_recalcMinMaxInfo(hwnd); + } + } + swell_oswindow_focus(hwnd); + + ShowWindow(hwnd,SW_SHOWNA); + InvalidateRect(hwnd,NULL,FALSE); + } + else + { + swell_dlg_destroyspare(); + ShowWindow(hwnd,SW_SHOW); + } + + while (!r.has_ret && !hwnd->m_hashaddestroy) + { + void SWELL_RunMessageLoop(); + SWELL_RunMessageLoop(); + Sleep(10); + } + ret=r.ret; + s_modalDialogs.DeletePtr(&r); + + a = SWELL_topwindows; + while (a) + { + if (a != hwnd) + { + int f = restwnds.Get((INT_PTR)a); + if (!a->m_enabled && (f&1)) EnableWindow(a,TRUE); + if (!a->m_israised && (f&2)) SWELL_SetWindowLevel(a,1); + } + a = a->m_next; + } + hwnd->Release(); + } + else + { + ret = s_last_dlgret; // SWELL_CreateDialog() failed, implies WM_INITDIALOG could have called EndDialog() + } + // while in list, do something + return ret; +} + +HWND SWELL_CreateDialog(SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent, DLGPROC dlgproc, LPARAM param) +{ + int forceStyles=0; // 1=resizable, 2=no minimize, 4=no close + bool forceNonChild=false; + if ((((INT_PTR)resid)&~0xf)==0x400000) + { + forceStyles = (int) (((INT_PTR)resid)&0xf); + if (forceStyles) forceNonChild=true; + resid=0; + } + SWELL_DialogResourceIndex *p=resById(reshead,resid); + if (!p&&resid) return 0; + + RECT r={0,0,SWELL_UI_SCALE(p ? p->width : 300), SWELL_UI_SCALE(p ? p->height : 200) }; + HWND owner=NULL; + + if (!forceNonChild && parent && (!p || (p->windowTypeFlags&SWELL_DLG_WS_CHILD))) + { + } + else + { + owner = parent; + parent = NULL; // top level window + } + + HWND__ *h = new HWND__(parent,0,&r,NULL,false,NULL,NULL, owner); + if (forceNonChild || (p && !(p->windowTypeFlags&SWELL_DLG_WS_CHILD))) + { + if ((forceStyles&1) || (p && (p->windowTypeFlags&SWELL_DLG_WS_RESIZABLE))) + h->m_style |= WS_THICKFRAME|WS_CAPTION; + else h->m_style |= WS_CAPTION; + } + else if (!p && !parent) h->m_style |= WS_CAPTION; + else if (parent && (!p || (p->windowTypeFlags&SWELL_DLG_WS_CHILD))) h->m_style |= WS_CHILD; + + if (p) + { + h->m_style |= p->windowTypeFlags & (WS_CLIPSIBLINGS); + if (p->windowTypeFlags&SWELL_DLG_WS_DROPTARGET) + h->m_exstyle|=WS_EX_ACCEPTFILES; + } + + h->Retain(); + + if (p) + { + p->createFunc(h,p->windowTypeFlags); + if (p->title) SetWindowText(h,p->title); + + h->m_dlgproc = dlgproc; + h->m_wndproc = SwellDialogDefaultWindowProc; + + HWND hFoc=h->m_children; + while (hFoc) + { + if (hFoc->m_wantfocus && hFoc->m_visible && hFoc->m_enabled) + { + h->m_focused_child = hFoc; // default focus to hFoc, but set focus more aggressively after WM_INITDIALOG if the dlgproc returns 1 + break; + } + hFoc=hFoc->m_next; + } + + if (hFoc) hFoc->Retain(); + + if (h->m_dlgproc(h,WM_INITDIALOG,(WPARAM)hFoc,param)) + { + if (hFoc && hFoc->m_wantfocus && hFoc->m_visible && hFoc->m_enabled) + { + if (!h->m_hashaddestroy && !hFoc->m_hashaddestroy) + SetFocus(hFoc); + } + } + + if (hFoc) hFoc->Release(); + } + else + { + h->m_wndproc = (WNDPROC)dlgproc; + h->m_wndproc(h,WM_CREATE,0,param); + } + + HWND rv = h->m_hashaddestroy ? NULL : h; + h->Release(); + return rv; +} + + +HMENU SWELL_GetDefaultWindowMenu() { return g_swell_defaultmenu; } +void SWELL_SetDefaultWindowMenu(HMENU menu) +{ + g_swell_defaultmenu=menu; +} +HMENU SWELL_GetDefaultModalWindowMenu() +{ + return g_swell_defaultmenumodal; +} +void SWELL_SetDefaultModalWindowMenu(HMENU menu) +{ + g_swell_defaultmenumodal=menu; +} + + + +SWELL_DialogResourceIndex *SWELL_curmodule_dialogresource_head; // this eventually will go into a per-module stub file + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlg.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlg.mm new file mode 100644 index 0000000..bb99dd5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlg.mm @@ -0,0 +1,4505 @@ +/* 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. +*/ + +#ifndef SWELL_PROVIDED_BY_APP + + +#include "swell.h" +#include "swell-dlggen.h" + +#import +#include +#include + +#define NSRECTSET_RECT NSRect + +#import + +#include "swell-internal.h" + +#ifndef SWELL_NO_METAL +#undef min +#undef max +#include +#import +#import +#include "../assocarray.h" + +#ifdef __SSE__ +// for SWELL_fastDoubleUpImage +#include +#endif + +#define LIMIT_METAL_BOUNDS_SIZE(sz) \ + if ((sz).width > 16384.0) (sz).width = 16384.0; \ + if ((sz).height > 16384.0) (sz).height = 16384.0; + +static id __class_CAMetalLayer, __class_MTLTextureDescriptor; +static id (*__MTLCreateSystemDefaultDevice)(void); +static id (*__CGDirectDisplayCopyCurrentMetalDevice)(CGDirectDisplayID); + +#endif + +@interface NSRectSet : NSObject +{ + struct CGRect _bounds; + struct CGRect *_rects; + unsigned long long _count; +} + ++ (id)emptyRectSet; ++ (void)initialize; +- (void)strokeExactInterior; +- (void)fillExactInterior; +- (void)stroke; +- (void)fill; +- (void)setClip; +- (void)addClip; +- (void)convertFromAncestor:(id)arg1 toView:(id)arg2 clipTo:(NSRECTSET_RECT)arg3; +- (void)intersectWithRect:(NSRECTSET_RECT)arg1; +- (void)subtractRect:(NSRECTSET_RECT)arg1; +- (void)setEmpty; +- (unsigned long long)count; +- (const NSRECTSET_RECT *)rects; +- (NSRECTSET_RECT)bounds; +- (BOOL)isEmpty; +- (id)description; +- (id)copyWithZone:(struct _NSZone *)arg1; +- (void)dealloc; +- (id)initWithCopyOfRects:(const NSRECTSET_RECT *)arg1 count:(unsigned long long)arg2 bounds:(NSRECTSET_RECT)arg3; +- (id)initWithRegion:(id)arg1; +- (id)initWithRect:(NSRECTSET_RECT)arg1; +- (id)init; + +@end + +@interface _NSDisplayOperationStack : NSObject +{ +} ++ (_NSDisplayOperationStack *) currentThreadDisplayOperationStack; +- (void) setRectSetBeingDrawn:(NSRectSet *)rs forView:(NSView *)v; +@end + +@interface NSView (swell_internals) +-(void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)vr rectIsVisibleRectForView:(NSView*)view topView:(NSView *)v2; +@end + +#undef NSRECTSET_RECT + + +#ifndef SWELL_CUT_OUT_COMPOSITING_MIDDLEMAN +#define SWELL_CUT_OUT_COMPOSITING_MIDDLEMAN 1 // 2 gives more performance, not correctly drawn window frames (try NSThemeFrame stuff? bleh) +#endif + +static HMENU g_swell_defaultmenu,g_swell_defaultmenumodal; + +bool SWELL_owned_windows_levelincrease=false; + +#include "../wdlstring.h" +#include "../wdlcstring.h" + +#define NSColorFromCol(a) [NSColor colorWithCalibratedRed:GetRValue(a)/255.0f green:GetGValue(a)/255.0f blue:GetBValue(a)/255.0f alpha:1.0f] +extern int g_swell_terminating; + +static LRESULT sendSwellMessage(id obj, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (obj && [obj respondsToSelector:@selector(onSwellMessage:p1:p2:)]) + return [(SWELL_hwndChild *)obj onSwellMessage:uMsg p1:wParam p2:lParam]; + return 0; +} + + +char g_swell_nomiddleman_cocoa_override=0; // -1 to disable, 1 to force + +static BOOL useNoMiddleManCocoa() +{ +#ifdef __ppc__ + return false; +#else + const int v = SWELL_GetOSXVersion(); + return v >= 0x1050 && (g_swell_nomiddleman_cocoa_override ? (g_swell_nomiddleman_cocoa_override>0) : v < 0x10a0); +#endif +} + +void updateWindowCollection(NSWindow *w) +{ + if (SWELL_GetOSXVersion()>=0x1060) + { + const int NSWindowCollectionBehaviorParticipatesInCycle = 1 << 5; + const int NSWindowCollectionBehaviorManaged = 1 << 2; + [(SWELL_WindowExtensions*)w setCollectionBehavior:NSWindowCollectionBehaviorManaged|NSWindowCollectionBehaviorParticipatesInCycle]; + } +} + +static void DrawSwellViewRectImpl(SWELL_hwndChild *view, NSRect rect, HDC hdc, bool isMetal=false); +static void swellRenderOptimizely(int passflags, SWELL_hwndChild *view, HDC hdc, BOOL doforce, WDL_PtrList *needdraws, const NSRect *rlist, NSInteger rlistcnt, int draw_xlate_x, int draw_xlate_y, bool iscv, NSView *rlist_coordview); + +static LRESULT SWELL_SendMouseMessage(SWELL_hwndChild *slf, int msg, NSEvent *event); +static LRESULT SWELL_SendMouseMessageImpl(SWELL_hwndChild *slf, int msg, NSEvent *theEvent) +{ + + NSView *capv=(NSView *)GetCapture(); + if (capv && capv != slf && [capv window] == [slf window] && [capv isKindOfClass:[SWELL_hwndChild class]]) + return SWELL_SendMouseMessage((SWELL_hwndChild*)capv,msg,theEvent); + + if (slf->m_hashaddestroy||!slf->m_wndproc) return -1; + + NSPoint swellProcessMouseEvent(int msg, NSView *view, NSEvent *event); + + NSPoint pt = swellProcessMouseEvent(msg,slf,theEvent); + unsigned short xpos=(int)floor(pt.x + 0.5); + unsigned short ypos=(int)floor(pt.y + 0.5); + + LRESULT htc=HTCLIENT; + if (msg != WM_MOUSEWHEEL && msg != WM_MOUSEHWHEEL && !capv) + { + DWORD p=GetMessagePos(); + htc=slf->m_wndproc((HWND)slf,WM_NCHITTEST,0,p); + if (slf->m_hashaddestroy||!slf->m_wndproc) return -1; // if somehow WM_NCHITTEST destroyed us, bail + + if (htc!=HTCLIENT) + { + if (msg==WM_MOUSEMOVE) return slf->m_wndproc((HWND)slf,WM_NCMOUSEMOVE,htc,p); + if (msg==WM_LBUTTONUP) return slf->m_wndproc((HWND)slf,WM_NCLBUTTONUP,htc,p); + if (msg==WM_LBUTTONDOWN) return slf->m_wndproc((HWND)slf,WM_NCLBUTTONDOWN,htc,p); + if (msg==WM_LBUTTONDBLCLK) return slf->m_wndproc((HWND)slf,WM_NCLBUTTONDBLCLK,htc,p); + if (msg==WM_RBUTTONUP) return slf->m_wndproc((HWND)slf,WM_NCRBUTTONUP,htc,p); + if (msg==WM_RBUTTONDOWN) return slf->m_wndproc((HWND)slf,WM_NCRBUTTONDOWN,htc,p); + if (msg==WM_RBUTTONDBLCLK) return slf->m_wndproc((HWND)slf,WM_NCRBUTTONDBLCLK,htc,p); + if (msg==WM_MBUTTONUP) return slf->m_wndproc((HWND)slf,WM_NCMBUTTONUP,htc,p); + if (msg==WM_MBUTTONDOWN) return slf->m_wndproc((HWND)slf,WM_NCMBUTTONDOWN,htc,p); + if (msg==WM_MBUTTONDBLCLK) return slf->m_wndproc((HWND)slf,WM_NCMBUTTONDBLCLK,htc,p); + } + } + + int l=0; + if (msg == WM_MOUSEWHEEL || msg == WM_MOUSEHWHEEL) + { + float dw = (msg == WM_MOUSEWHEEL ? [theEvent deltaY] : [theEvent deltaX]); + l = MAKELONG(0, (int)(dw*60.0)); + // put todo: modifiers into low word of l? + + POINT p; + GetCursorPos(&p); + return slf->m_wndproc((HWND)slf,msg,l,MAKELONG(p.x&0xffff,p.y)); + } + + LRESULT ret=slf->m_wndproc((HWND)slf,msg,l,MAKELONG(xpos&0xffff,ypos)); + + if (msg==WM_LBUTTONUP || msg==WM_RBUTTONUP || msg==WM_MOUSEMOVE || msg==WM_MBUTTONUP) { + if (!GetCapture() && (slf->m_hashaddestroy || !slf->m_wndproc || !slf->m_wndproc((HWND)slf,WM_SETCURSOR,(WPARAM)slf,MAKELONG(htc,msg)))) { + NSCursor *arr= [NSCursor arrowCursor]; + if (GetCursor() != (HCURSOR)arr) SetCursor((HCURSOR)arr); + } + } + return ret; +} +static LRESULT SWELL_SendMouseMessage(SWELL_hwndChild *slf, int msg, NSEvent *event) +{ + if (!slf) return 0; + [slf retain]; + LRESULT res=SWELL_SendMouseMessageImpl(slf,msg,event); + [slf release]; + return res; +} + +void SWELL_DoDialogColorUpdates(HWND hwnd, DLGPROC d, bool isUpdate) +{ + extern HDC__ *SWELL_GDP_CTX_NEW(); + NSArray *children = [(NSView *)hwnd subviews]; + + if (!d || !children || ![children count]) return; + + int had_flags=0; + + NSColor *staticFg=NULL; // had_flags&1, WM_CTLCOLORSTATIC + NSColor *editFg=NULL, *editBg=NULL; // had_flags&2, WM_CTLCOLOREDIT + NSColor *buttonFg=NULL; // had_flags&4, WM_CTLCOLORBTN + + int x; + for (x = 0; x < [children count]; x ++) + { + NSView *ch = [children objectAtIndex:x]; + if (ch) + { + if ([ch isKindOfClass:[NSButton class]] && [(NSButton *)ch image]) + { + if (!buttonFg && !(had_flags&4)) + { + had_flags|=4; + HDC__ *c = SWELL_GDP_CTX_NEW(); + if (c) + { + d(hwnd,WM_CTLCOLORBTN,(WPARAM)c,(LPARAM)ch); + if (c->curtextcol) buttonFg=NSColorFromCol(c->cur_text_color_int); + else if (isUpdate) buttonFg = [NSColor textColor]; // todo some other col? + if (buttonFg) [buttonFg retain]; + + SWELL_DeleteGfxContext((HDC)c); + } + } + if (buttonFg) + { + NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithAttributedString:[(NSButton *)ch attributedTitle]]; + NSRange range = NSMakeRange(0, [attrTitle length]); + [attrTitle addAttribute:NSForegroundColorAttributeName value:buttonFg range:range]; + [attrTitle fixAttributesInRange:range]; + [(NSButton *)ch setAttributedTitle:attrTitle]; + [attrTitle release]; + } + } + else if ([ch isKindOfClass:[NSTextField class]] || [ch isKindOfClass:[NSBox class]]) + { + bool isbox = ([ch isKindOfClass:[NSBox class]]); + if (!isbox && [(NSTextField *)ch isEditable]) + { +#if 0 // no color overrides for editable text fields + if (!editFg && !editBg && !(had_flags&2)) + { + had_flags|=2; + HDC__ *c = SWELL_GDP_CTX_NEW(); + if (c) + { + d(hwnd,WM_CTLCOLOREDIT,(WPARAM)c,(LPARAM)ch); + if (c->curtextcol) + { + editFg=NSColorFromCol(c->cur_text_color_int); + editBg=[NSColor colorWithCalibratedRed:GetRValue(c->curbkcol)/255.0f green:GetGValue(c->curbkcol)/255.0f blue:GetBValue(c->curbkcol)/255.0f alpha:1.0f]; + } + else if (isUpdate) + { + editFg = [NSColor textColor]; + editBg = [NSColor textBackgroundColor]; + } + if (editFg) [editFg retain]; + if (editBg) [editBg retain]; + SWELL_DeleteGfxContext((HDC)c); + } + } + if (editFg) [(NSTextField*)ch setTextColor:editFg]; + if (editBg) [(NSTextField*)ch setBackgroundColor:editBg]; +#endif + } + else // isbox or noneditable + { + if (!staticFg && !(had_flags&1)) + { + had_flags|=1; + HDC__ *c = SWELL_GDP_CTX_NEW(); + if (c) + { + d(hwnd,WM_CTLCOLORSTATIC,(WPARAM)c,(LPARAM)ch); + if (c->curtextcol) staticFg=NSColorFromCol(c->cur_text_color_int); + else if (isUpdate) + { + staticFg = [NSColor textColor]; + } + if (staticFg) [staticFg retain]; + SWELL_DeleteGfxContext((HDC)c); + } + } + if (staticFg) + { + if (isbox) + { + [[(NSBox*)ch titleCell] setTextColor:staticFg]; + //[(NSBox*)ch setBorderColor:staticFg]; // see comment at SWELL_MakeGroupBox + [ch setNeedsDisplay:YES]; + } + else + { + if ([ch isKindOfClass:[SWELL_TextField class]]) + ((SWELL_TextField *)ch)->m_ctlcolor_set = true; + [(NSTextField*)ch setTextColor:staticFg]; + } + } + } // noneditable + } //nstextfield + } // child + } // children + if (buttonFg) [buttonFg release]; + if (staticFg) [staticFg release]; + if (editFg) [editFg release]; + if (editBg) [editBg release]; +} + +static LRESULT SwellDialogDefaultWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + DLGPROC d=(DLGPROC)GetWindowLong(hwnd,DWL_DLGPROC); + if (d) + { + if (uMsg == WM_PAINT) + { + if (!d(hwnd,WM_ERASEBKGND,0,0)) + { + const bool nommc=useNoMiddleManCocoa(); + NSView *cv = [[(NSView *)hwnd window] contentView]; + const bool hwndIsOpaque = [(NSView *)hwnd isOpaque]; + const bool isop = hwndIsOpaque || (nommc && [cv isOpaque]); + const bool hwndIsCV = cv == (NSView *)hwnd; + if (isop || hwndIsCV) + { + PAINTSTRUCT ps; + if (!nommc && !hwndIsOpaque && !hwndIsCV && !(((SWELL_hwndChild*)hwnd)->m_isdirty&1)) + { + // if not no-middleman, not opaque, not content view, and not directly invalidated + // then don't bother background drawing + } + else if (BeginPaint(hwnd,&ps)) + { + RECT r=ps.rcPaint; + if (r.right > r.left && r.bottom > r.top) + { + HBRUSH hbrush = (HBRUSH) d(hwnd,WM_CTLCOLORDLG,(WPARAM)ps.hdc,(LPARAM)hwnd); + if (hbrush && hbrush != (HBRUSH)1) + { + // char bf[512]; + // GetWindowText(hwnd,bf,sizeof(bf)); +// static int a; + // printf("%d filled custom bg, (%p %s) %d %d %d %d\n",a++,hwnd,bf,r.left,r.top,r.right-r.left,r.bottom-r.top); + FillRect(ps.hdc,&r,hbrush); + } + else if (isop) // no need to do this fill if it is a content view and is not opaque + { + // char bf[512]; + // GetWindowText(hwnd,bf,sizeof(bf)); + // static int a; + // printf("%d: filled stock bg, (%p %s) %d %d %d %d\n",a++,hwnd,bf,r.left,r.top,r.right-r.left,r.bottom-r.top); + SWELL_FillDialogBackground(ps.hdc,&r,3); + } + } + EndPaint(hwnd,&ps); + } + } + } + } + + LRESULT r=(LRESULT) d(hwnd,uMsg,wParam,lParam); + + if (r) return r; + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + +static SWELL_DialogResourceIndex *resById(SWELL_DialogResourceIndex *reshead, const char *resid) +{ + SWELL_DialogResourceIndex *p=reshead; + while (p) + { + if (p->resid == resid) return p; + p=p->_next; + } + return 0; +} + +static void DoPaintStuff(WNDPROC wndproc, HWND hwnd, HDC hdc, NSRect *modrect, bool isMetal) +{ +#ifdef _DEBUG + extern int g_swell_in_paint; + g_swell_in_paint++; +#endif + RECT r; + GetWindowRect(hwnd,&r); + if (r.top>r.bottom) { int tmp=r.top; r.top=r.bottom; r.bottom=tmp; } + NCCALCSIZE_PARAMS p={{r,},}; + wndproc(hwnd,WM_NCCALCSIZE,FALSE,(LPARAM)&p); + RECT r2=r; + r=p.rgrc[0]; + + if (!isMetal) wndproc(hwnd,WM_NCPAINT,(WPARAM)1,0); + modrect->origin.x += r.left-r2.left; + modrect->origin.y += r.top-r2.top; + + if (modrect->size.width >= 1 && modrect->size.height >= 1) + { + int a=0; + if (!isMetal && memcmp(&r,&r2,sizeof(r))) + { + RECT tr; + SWELL_PushClipRegion(hdc); + GetClientRect(hwnd,&tr); + SWELL_SetClipRegion(hdc,&tr); + a++; + } + wndproc(hwnd,WM_PAINT,(WPARAM)hdc,0); + if (a) SWELL_PopClipRegion(hdc); + } + if (isMetal) wndproc(hwnd,WM_NCPAINT,(WPARAM)1,0); +#ifdef _DEBUG + g_swell_in_paint--; +#endif +} + + +static int DelegateMouseMove(NSView *view, NSEvent *theEvent) +{ + static int __nofwd; + if (__nofwd) return 0; + + NSWindow *w=[theEvent window]; + if (!w) return 0; + + NSPoint p=[theEvent locationInWindow]; + NSPoint screen_p=[w convertBaseToScreen:p]; + + NSWindow *bestwnd = w; + HWND cap = GetCapture(); + if (!cap) + { + // if not captured, find the window that should receive this event + + NSArray *windows=[NSApp orderedWindows]; + const NSInteger cnt=windows ? [windows count] : 0; + NSWindow *kw = [NSApp keyWindow]; + if (kw && windows && [windows containsObject:kw]) kw=NULL; + // make sure the keywindow, if any, is checked, but not twice + + for (NSInteger x = kw ? -1 : 0; x < cnt; x ++) + { + NSWindow *wnd = x < 0 ? kw : [windows objectAtIndex:x]; + if (wnd && [wnd isVisible]) + { + NSRect fr=[wnd frame]; + if (screen_p.x >= fr.origin.x && screen_p.x < fr.origin.x + fr.size.width && + screen_p.y >= fr.origin.y && screen_p.y < fr.origin.y + fr.size.height) + { + bestwnd=wnd; + break; + } + } + } + } + + if (bestwnd == w || [NSApp modalWindow]) + { + NSView *v=[[w contentView] hitTest:p]; + if (!v || v == view) return 0; // default processing if in view, or if in nonclient area + + __nofwd=1; + [v mouseMoved:theEvent]; + __nofwd=0; + return 1; + } + + // bestwnd != w + NSView *cv = [bestwnd contentView]; + if (cv && [cv isKindOfClass:[SWELL_hwndChild class]]) + { + p = [bestwnd convertScreenToBase:screen_p]; + NSView *v=[cv hitTest:p]; + if (v) + { + theEvent = [NSEvent mouseEventWithType:[theEvent type] + location:p + modifierFlags:[theEvent modifierFlags] + timestamp:[theEvent timestamp] + windowNumber:[bestwnd windowNumber] + context:[bestwnd graphicsContext] + eventNumber:[theEvent eventNumber] + clickCount:[theEvent clickCount] + pressure:[theEvent pressure]]; + __nofwd=1; + [v mouseMoved:theEvent]; + __nofwd=0; + return 1; + } + } + if (!cap) + { + // set default cursor, and eat message + NSCursor *arr= [NSCursor arrowCursor]; + if (GetCursor() != (HCURSOR)arr) SetCursor((HCURSOR)arr); + return 1; + } + return 0; +} + + + +static void SendTreeViewExpandNotification(SWELL_hwndChild *par, NSNotification *notification, int action) +{ + NSOutlineView *sender=[notification object]; + NMTREEVIEW nmhdr={{(HWND)sender,(UINT_PTR)[sender tag],TVN_ITEMEXPANDING},0,}; + SWELL_DataHold *t=[[notification userInfo] valueForKey:@"NSObject"]; + HTREEITEM hi = t ? (HTREEITEM)[t getValue] : NULL; + if (hi) + { + nmhdr.action=action; + nmhdr.itemNew.hItem=hi; + nmhdr.itemNew.lParam=hi->m_param; + } + if (par->m_wndproc && !par->m_hashaddestroy) + { + par->m_wndproc((HWND)par, WM_NOTIFY, (int)[sender tag], (LPARAM)&nmhdr); + } +} + +#ifndef SWELL_NO_METAL +struct swell_metal_device_ctx { + swell_metal_device_ctx() + { + m_commandQueue = NULL; + m_commandBuffer = NULL; + } + + id m_commandQueue; + id m_commandBuffer; // only used for presentation + + void present() + { + if (m_commandBuffer) + { + [m_commandBuffer commit]; + [m_commandBuffer waitUntilCompleted]; + [m_commandBuffer release]; + m_commandBuffer = NULL; + } + } +}; +static WDL_PtrKeyedArray s_metal_devices; // indexed by id +static int s_metal_devicelist_updcnt; +static bool s_mtl_in_update; + +@interface SWELL_MetalNotificationHandler : NSObject +- (void)handleDisplayChanges:(NSNotification *)notification; +@end + +@implementation SWELL_MetalNotificationHandler : NSObject +- (void)handleDisplayChanges:(NSNotification *)notification +{ + s_metal_devicelist_updcnt++; +} +@end + +static id mtl_def_device() +{ + static id def; + if (!def && __MTLCreateSystemDefaultDevice) def = __MTLCreateSystemDefaultDevice(); + return def; +} + +#endif + +@implementation SWELL_hwndChild : NSView + +- (NSMenu *)textView:(NSTextView *)view + menu:(NSMenu *)menu + forEvent:(NSEvent *)event + atIndex:(NSUInteger)charIndex +{ + return [view respondsToSelector:@selector(swellWantsContextMenu)] && ![(SWELL_TextView *)view swellWantsContextMenu] ? nil : menu; +} + +-(void)viewDidHide +{ + SendMessage((HWND)self, WM_SHOWWINDOW, FALSE, 0); +#ifndef SWELL_NO_METAL + if (m_use_metal>0) swell_removeMetalDirty(self); +#endif +} +-(void) viewDidUnhide +{ + SendMessage((HWND)self, WM_SHOWWINDOW, TRUE, 0); +#ifndef SWELL_NO_METAL + if (m_use_metal>0) + { + SWELL_AutoReleaseHelper arparp; + [self swellDrawMetal:NULL]; + swell_removeMetalDirty(self); + } +#endif +} + +- (void)SWELL_Timer:(id)sender +{ + extern HWND g_swell_only_timerhwnd; + if (g_swell_only_timerhwnd && (HWND)self != g_swell_only_timerhwnd) return; + + id uinfo=[sender userInfo]; + if ([uinfo respondsToSelector:@selector(getValue)]) + { + WPARAM idx=(WPARAM)[(SWELL_DataHold*)uinfo getValue]; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_TIMER,idx,0); + } +} + +- (int)swellCapChangeNotify { return YES; } + +- (LRESULT)onSwellMessage:(UINT)msg p1:(WPARAM)wParam p2:(LPARAM)lParam +{ + if (m_hashaddestroy) + { + if (m_hashaddestroy==2 || msg == WM_DESTROY || msg == WM_CAPTURECHANGED) return 0; + } + + if (msg==WM_DESTROY) // only ever called once per window + { + m_hashaddestroy=1; + if (GetCapture()==(HWND)self) ReleaseCapture(); + SWELL_MessageQueue_Clear((HWND)self); + + LRESULT ret=m_wndproc ? m_wndproc((HWND)self,msg,wParam,lParam) : 0; + + if ([[self window] contentView] == self && [[self window] respondsToSelector:@selector(swellDestroyAllOwnedWindows)]) + [(SWELL_ModelessWindow*)[self window] swellDestroyAllOwnedWindows]; + + if (m_wndproc) m_wndproc((HWND)self,WM_NCDESTROY,0,0); + if (GetCapture()==(HWND)self) ReleaseCapture(); + SWELL_MessageQueue_Clear((HWND)self); + +#ifndef SWELL_NO_METAL + if (m_use_metal>0) swell_removeMetalDirty(self); +#endif + + if (m_menu) + { + if ((HMENU)[NSApp mainMenu] == m_menu && !g_swell_terminating) [NSApp setMainMenu:nil]; + SWELL_SetMenuDestination(m_menu,NULL); + [(NSMenu *)m_menu release]; + m_menu=0; + } + NSView *v=self; + NSArray *ar; + if (v && [v isKindOfClass:[NSView class]] && (ar=[v subviews]) && [ar count]>0) + { + int x; + for (x = 0; x < [ar count]; x ++) + { + NSView *sv=[ar objectAtIndex:x]; + sendSwellMessage(sv,WM_DESTROY,0,0); + } + } + KillTimer((HWND)self,~(UINT_PTR)0); + m_hashaddestroy=2; + + return ret; + } + + return m_wndproc ? m_wndproc((HWND)self,msg,wParam,lParam) : 0; +} + +- (BOOL) isEnabled +{ + return m_enabled; +} +- (void) setEnabled:(BOOL)en +{ + m_enabled=en?1:0; +} + +- (void) setEnabledSwellNoFocus +{ + m_enabled = -1; +} + +- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + if ([aTableView isKindOfClass:[SWELL_ListView class]]) + { + SWELL_ListView *f = (SWELL_ListView *)aTableView; + + const unsigned short tag = (unsigned short)[aTableView tag]; + NMLVCUSTOMDRAW nmlv={ + { + {(HWND)aTableView,(UINT_PTR)tag, NM_CUSTOMDRAW}, + CDDS_ITEMPREPAINT,NULL, {0,0,0,0}, (DWORD)rowIndex, 0,0 + }, + (COLORREF)-1, + (COLORREF)-1, + f->m_cols ? f->m_cols->Find(aTableColumn) : 0 + }; + if (m_wndproc && !m_hashaddestroy) m_wndproc((HWND)self,WM_NOTIFY,tag,(LPARAM)&nmlv); + // todo clrTextBk too + if (nmlv.clrText != (COLORREF)-1) + { + if ([aCell respondsToSelector:@selector(setTextColor:)]) + { + [aCell setTextColor:NSColorFromCol(nmlv.clrText)]; + } + return; + } + + if (f->m_selColors&&[aTableView isRowSelected:rowIndex]) + { + const NSInteger cnt = [f->m_selColors count]; + const NSInteger offs = GetFocus() == (HWND)aTableView ? 0 : 2; + if (cnt>=offs+2) + { + if ([aCell respondsToSelector:@selector(setTextColor:)]) [aCell setTextColor:[f->m_selColors objectAtIndex:(offs+1)]]; + return; + } + } + + if (f->m_fgColor && [aCell respondsToSelector:@selector(setTextColor:)]) [aCell setTextColor:f->m_fgColor]; + } +} +- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + if ([outlineView isKindOfClass:[SWELL_TreeView class]]) + { + SWELL_TreeView *f = (SWELL_TreeView *)outlineView; + if (f->m_selColors) + { + HTREEITEM sel = TreeView_GetSelection((HWND)outlineView); + if (sel && sel->m_dh == item) + { + const NSInteger cnt = [f->m_selColors count]; + const NSInteger offs = GetFocus() == (HWND)outlineView ? 0 : 2; + if (cnt>=offs+2) + { + if ([cell respondsToSelector:@selector(setTextColor:)]) [cell setTextColor:[f->m_selColors objectAtIndex:(offs+1)]]; + return; + } + } + } + if (f->m_fgColor && [cell respondsToSelector:@selector(setTextColor:)]) [cell setTextColor:f->m_fgColor]; + } +} + + +//- (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item + +- (void)comboBoxWillPopUp:(NSNotification*)notification +{ + id sender=[notification object]; + int code=CBN_DROPDOWN; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],code),(LPARAM)sender); +} + +- (void)comboBoxSelectionDidChange:(NSNotification *)notification +{ + id sender=[notification object]; + int code=CBN_SELCHANGE; + if (m_wndproc&&!m_hashaddestroy) + { + if ([sender isKindOfClass:[SWELL_ComboBox class]]) + { + SWELL_ComboBox *p = (SWELL_ComboBox *)sender; + const int sel = (int)[p indexOfSelectedItem]; + if (sel == p->m_ignore_selchg) return; + p->m_ignore_selchg = sel; + } + m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],code),(LPARAM)sender); + } +} + +- (void)comboBoxWillDismiss:(NSNotification *)notification +{ + id sender=[notification object]; + int code=CBN_CLOSEUP; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],code),(LPARAM)sender); +} + +- (void)textDidEndEditing:(NSNotification *)aNotification +{ + id sender=[aNotification object]; + if ([sender isKindOfClass:[NSComboBox class]]) return; + if (m_wndproc&&!m_hashaddestroy) + { + int code=EN_KILLFOCUS; + m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],code),(LPARAM)sender); + } +} + +- (void)textDidChange:(NSNotification *)aNotification +{ + id sender=[aNotification object]; + int code=EN_CHANGE; + if ([sender isKindOfClass:[NSComboBox class]]) return; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],code),(LPARAM)sender); +} + +- (void)controlTextDidChange:(NSNotification *)aNotification +{ + id sender=[aNotification object]; + int code=EN_CHANGE; + if ([sender isKindOfClass:[NSComboBox class]]) code=CBN_EDITCHANGE; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],code),(LPARAM)sender); +} + +- (void)controlTextDidEndEditing:(NSNotification *)aNotification +{ + id sender=[aNotification object]; + int code=EN_KILLFOCUS; + if (m_wndproc && !m_hashaddestroy) m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],code),(LPARAM)sender); +} + +- (void)menuNeedsUpdate:(NSMenu *)menu +{ + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_INITMENUPOPUP,(WPARAM)menu,0); +} + +-(void) swellOnControlDoubleClick:(id)sender +{ + if (!m_wndproc||m_hashaddestroy) return; + + if ([sender isKindOfClass:[NSTableView class]] && + [sender respondsToSelector:@selector(getSwellNotificationMode)]) + { + if ([(SWELL_ListView*)sender getSwellNotificationMode]) + m_wndproc((HWND)self,WM_COMMAND,MAKELONG([(NSControl*)sender tag],LBN_DBLCLK),(LPARAM)sender); + else + { + SWELL_ListView *lv = (SWELL_ListView*)sender; + NMLISTVIEW nmlv={{(HWND)sender,(UINT_PTR)[(NSControl*)sender tag], NM_DBLCLK}, (int) [lv clickedRow], (int) [sender clickedColumn], }; + + if (nmlv.iItem == -1) + { + // ignore doubleclicks in column headers + NSTableHeaderView *v = [sender headerView]; + if (v) + { + NSPoint pt=[NSEvent mouseLocation]; + NSWindow *w = [self window]; + pt = [w convertScreenToBase:pt]; + pt = [v convertPoint:pt fromView:nil]; + if (NSPointInRect(pt,[v bounds])) return; + } + } + + SWELL_ListView_Row *row=lv->m_items->Get(nmlv.iItem); + if (row) + nmlv.lParam = row->m_param; + m_wndproc((HWND)self,WM_NOTIFY,[(NSControl*)sender tag],(LPARAM)&nmlv); + } + } + else + { + NMCLICK nm={{(HWND)sender,(UINT_PTR)[(NSControl*)sender tag],NM_DBLCLK}, }; + m_wndproc((HWND)self,WM_NOTIFY,[(NSControl*)sender tag],(LPARAM)&nm); + } +} + +- (void)outlineViewItemWillExpand:(NSNotification*)notification +{ + SendTreeViewExpandNotification(self, notification, TVE_EXPAND); +} +- (void)outlineViewItemWillCollapse:(NSNotification*)notification +{ + SendTreeViewExpandNotification(self, notification, TVE_COLLAPSE); +} + +- (void)outlineViewSelectionDidChange:(NSNotification *)notification +{ + NSOutlineView *sender=[notification object]; + NMTREEVIEW nmhdr={{(HWND)sender,(UINT_PTR)[sender tag],TVN_SELCHANGED},0,}; // todo: better treeview notifications + HTREEITEM item = TreeView_GetSelection((HWND)sender); + nmhdr.itemNew.hItem = item; + nmhdr.itemNew.lParam = item ? item->m_param : 0; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_NOTIFY,(int)[sender tag],(LPARAM)&nmhdr); +} +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +{ + extern int swell_ignore_listview_changes; + if (!swell_ignore_listview_changes) + { + swell_ignore_listview_changes++; + NSTableView *sender=[aNotification object]; + if ([sender respondsToSelector:@selector(getSwellNotificationMode)] && [(SWELL_ListView*)sender getSwellNotificationMode]) + { + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_COMMAND,MAKELONG((int)[sender tag],LBN_SELCHANGE),(LPARAM)sender); + } + else + { + NMLISTVIEW nmhdr={{(HWND)sender,(UINT_PTR)[sender tag],LVN_ITEMCHANGED},(int)[sender selectedRow],0}; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_NOTIFY,(int)[sender tag],(LPARAM)&nmhdr); + } + swell_ignore_listview_changes--; + } +} + +- (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn +{ + if ([tableView isKindOfClass:[SWELL_ListView class]] && + ((SWELL_ListView *)tableView)->m_cols && + !((SWELL_ListView *)tableView)->m_lbMode && + !(((SWELL_ListView *)tableView)->style & LVS_NOSORTHEADER) + ) + { + int col=((SWELL_ListView *)tableView)->m_cols->Find(tableColumn); + + NMLISTVIEW hdr={{(HWND)tableView,(UINT_PTR)[tableView tag],LVN_COLUMNCLICK},-1,col}; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_NOTIFY,[tableView tag], (LPARAM) &hdr); + } +} + +#ifdef MAC_OS_X_VERSION_10_8 +// for radio button with the OSX 10.8+ SDK, see comment in SWELL_MakeControl +-(void) onSwellCommand0:(id)sender { [self onSwellCommand:sender]; } +-(void) onSwellCommand2:(id)sender { [self onSwellCommand:sender]; } +-(void) onSwellCommand3:(id)sender { [self onSwellCommand:sender]; } +-(void) onSwellCommand4:(id)sender { [self onSwellCommand:sender]; } +-(void) onSwellCommand5:(id)sender { [self onSwellCommand:sender]; } +-(void) onSwellCommand6:(id)sender { [self onSwellCommand:sender]; } +-(void) onSwellCommand7:(id)sender { [self onSwellCommand:sender]; } +#endif +-(void) onSwellCommand:(id)sender +{ + if (!m_wndproc || m_hashaddestroy) return; + + if ([sender isKindOfClass:[NSSlider class]]) + { + m_wndproc((HWND)self,WM_HSCROLL,0,(LPARAM)sender); + // WM_HSCROLL, WM_VSCROLL + } + else if ([sender isKindOfClass:[NSTableView class]]) + { + #if 0 + if ([sender isKindOfClass:[NSOutlineView class]]) + { +// NMTREEVIEW nmhdr={{(HWND)sender,(int)[sender tag],TVN_SELCHANGED},0,}; // todo: better treeview notifications + // m_wndproc((HWND)self,WM_NOTIFY,(int)[sender tag],(LPARAM)&nmhdr); + } + else + { + + if ([sender respondsToSelector:@selector(getSwellNotificationMode)] && [(SWELL_ListView*)sender getSwellNotificationMode]) + { + m_wndproc((HWND)self,WM_COMMAND,(int)[sender tag] | (LBN_SELCHANGE<<16),(LPARAM)sender); + } + else + { + NMLISTVIEW nmhdr={{(HWND)sender,(int)[sender tag],LVN_ITEMCHANGED},(int)[sender clickedRow],0}; + m_wndproc((HWND)self,WM_NOTIFY,(int)[sender tag],(LPARAM)&nmhdr); + } + } + #endif + } + else + { + int cw=0; + if ([sender isKindOfClass:[NSComboBox class]]) return; // combo boxes will use delegate messages + else if ([sender isKindOfClass:[NSPopUpButton class]]) + { + cw=CBN_SELCHANGE; + } + else if ([sender isKindOfClass:[SWELL_Button class]]) + { + int rf; + if ((rf=(int)[(SWELL_Button*)sender swellGetRadioFlags]) & ~4096) + { + NSView *par=(NSView *)GetParent((HWND)sender); + if (par && [par isKindOfClass:[NSWindow class]]) par=[(NSWindow *)par contentView]; + if (par && [par isKindOfClass:[NSView class]]) + { + NSArray *ar=[par subviews]; + if (ar) + { + NSInteger x=[ar indexOfObject:sender]; + if (x != NSNotFound) + { + const NSInteger n = [ar count]; + NSInteger a=x; + if (!(rf&2)) while (--a >= 0) + { + NSView *item=[ar objectAtIndex:a]; + if (!item || ![item isKindOfClass:[SWELL_Button class]]) break; // we may want to allow other controls in there, but for now if it's non-button we're done + int bla=(int)[(SWELL_Button*)item swellGetRadioFlags]; + if (bla&1) if ([(NSButton *)item state]!=NSOffState) [(NSButton *)item setState:NSOffState]; + if (bla&2) break; + } + a=x; + while (++a < n) + { + NSView *item=[ar objectAtIndex:a]; + if (!item || ![item isKindOfClass:[SWELL_Button class]]) break; // we may want to allow other controls in there, but for now if it's non-button we're done + int bla=(int)[(SWELL_Button*)item swellGetRadioFlags]; + if (bla&2) break; + if (bla&1) if ([(NSButton *)item state]!=NSOffState) [(NSButton *)item setState:NSOffState]; + } + } + } + } + } + } + else if ([sender isKindOfClass:[NSControl class]]) + { + NSEvent *evt=[NSApp currentEvent]; + NSUInteger ty=evt?[evt type]:0; + if (evt && (ty==NSLeftMouseDown || ty==NSLeftMouseUp) && [evt clickCount] > 1) cw=STN_DBLCLK; + } + else if ([sender isKindOfClass:[NSMenuItem class]]) + { + m_wndproc((HWND)self,WM_COMMAND,[sender tag],0); + return; + } + m_wndproc((HWND)self,WM_COMMAND,MAKELONG([sender tag],cw),(LPARAM)sender); + } + +} +-(void) dealloc +{ + int x; + for (x=0;x0) swell_removeMetalDirty(self); +#endif + SWELL_MessageQueue_Clear((HWND)self); + + [super dealloc]; +} + +-(NSInteger)tag { return m_tag; } +-(void)setTag:(NSInteger)t { m_tag=t; } +-(LONG_PTR)getSwellUserData { return m_userdata; } +-(void)setSwellUserData:(LONG_PTR)val { m_userdata=val; } +-(LPARAM)getSwellExtraData:(int)idx { + idx/=sizeof(INT_PTR); + if (WDL_NORMALLY(idx>=0&&idx=0&&idxhdc = m_paintctx_hdc; + ps->fErase=false; + NSRECT_TO_RECT(&ps->rcPaint,m_paintctx_rect); + + // should NC_CALCSIZE to convert, but this will be good enough to fix this small scrollbar overdraw bug + RECT r; + GetClientRect((HWND)self,&r); + if (ps->rcPaint.right > r.right) ps->rcPaint.right = r.right; + if (ps->rcPaint.bottom > r.bottom) ps->rcPaint.bottom = r.bottom; + + } +} + +-(bool)swellCanPostMessage { return !m_hashaddestroy; } +-(int)swellEnumProps:(PROPENUMPROCEX)proc lp:(LPARAM)lParam +{ + WindowPropRec *p=m_props; + if (!p) return -1; + while (p) + { + WindowPropRec *ps=p; + p=p->_next; + if (!proc((HWND)self, ps->name, ps->data, lParam)) return 0; + } + return 1; +} + +-(void *)swellGetProp:(const char *)name wantRemove:(BOOL)rem +{ + WindowPropRec *p=m_props, *lp=NULL; + while (p) + { + if (p->name < (void *)65536) + { + if (name==p->name) break; + } + else if (name >= (void *)65536) + { + if (!strcmp(name,p->name)) break; + } + lp=p; p=p->_next; + } + if (!p) return NULL; + void *ret=p->data; + if (rem) + { + if (lp) lp->_next=p->_next; else m_props=p->_next; + free(p); + } + return ret; +} + +-(int)swellSetProp:(const char *)name value:(void *)val +{ + WindowPropRec *p=m_props; + while (p) + { + if (p->name < (void *)65536) + { + if (name==p->name) { p->data=val; return TRUE; }; + } + else if (name >= (void *)65536) + { + if (!strcmp(name,p->name)) { p->data=val; return TRUE; }; + } + p=p->_next; + } + p=(WindowPropRec*)malloc(sizeof(WindowPropRec)); + p->name = (name<(void*)65536) ? (char *)name : strdup(name); + p->data = val; p->_next=m_props; m_props=p; + return TRUE; +} + +-(NSOpenGLContext *)swellGetGLContext +{ + return m_glctx; +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +{ + if (m_enabled) + { + SendMessage((HWND)self, WM_MOUSEACTIVATE, 0, 0); + NSView* par=[self superview]; + if (par) SendMessage((HWND)par, WM_MOUSEACTIVATE, 0, 0); + return YES; + } + return NO; +} + +-(HMENU)swellGetMenu { return m_menu; } +-(BOOL)swellHasBeenDestroyed { return !!m_hashaddestroy; } +-(void)swellSetMenu:(HMENU)menu { + if (m_menu) SWELL_SetMenuDestination(m_menu,NULL); // don't free m_menu, but at least make it not point to us anymore + m_menu=menu; + if (m_menu) SWELL_SetMenuDestination(m_menu,(HWND)self); +} + +#ifndef SWELL_NO_METAL + +-(CALayer *)makeBackingLayer +{ + if (m_use_metal>0 && __class_CAMetalLayer) + { + CALayer *layer = [__class_CAMetalLayer layer]; + if (layer) return layer; + } + return [super makeBackingLayer]; +} +#endif + +- (id)initChild:(SWELL_DialogResourceIndex *)resstate Parent:(NSView *)parent dlgProc:(DLGPROC)dlgproc Param:(LPARAM)par +{ + NSRect contentRect=NSMakeRect(0,0,resstate ? resstate->width : 300,resstate ? resstate->height : 200); + if (!(self = [super initWithFrame:contentRect])) return self; + + m_classname=NULL; + memset(m_access_cacheptrs,0,sizeof(m_access_cacheptrs)); + m_allow_nomiddleman=1; + m_isdirty=3; + m_glctx=NULL; + m_enabled=TRUE; + m_lastTopLevelOwner=NULL; + m_dlgproc=NULL; + m_wndproc=NULL; + m_userdata=0; + memset(&m_extradata,0,sizeof(m_extradata)); + m_tag=0; + m_isfakerightmouse=0; + m_hashaddestroy=false; + m_menu=0; + m_flip=0; + m_supports_ddrop=0; + m_paintctx_used=0; + m_paintctx_hdc=0; + m_props=0; + + m_titlestr[0]=0; + +#ifndef SWELL_NO_METAL + m_use_metal=0; + m_metal_dc_dirty=0; + m_metal_retina=false; + m_metal_device=NULL; + m_metal_devicelist_updcnt=0; + m_metal_texture=NULL; + m_metal_drawable=NULL; + m_metal_in_needref_list=false; + m_metal_gravity=0; + memset(&m_metal_lastframe,0,sizeof(m_metal_lastframe)); + memset(&m_metal_in_needref_rect,0,sizeof(m_metal_in_needref_rect)); +#endif + + m_wndproc=SwellDialogDefaultWindowProc; + + m_isopaque = !resstate || (resstate->windowTypeFlags&SWELL_DLG_WS_OPAQUE); + m_flip = !resstate || (resstate->windowTypeFlags&SWELL_DLG_WS_FLIPPED); + m_supports_ddrop = resstate && (resstate->windowTypeFlags&SWELL_DLG_WS_DROPTARGET); + + [self setHidden:YES]; + + if ([parent isKindOfClass:[NSWindow class]]) + { + if ([parent isKindOfClass:[NSPanel class]] && + [parent respondsToSelector:@selector(setAccessoryView:)]) + { + [(NSOpenPanel *)parent setAccessoryView:self]; + if ([parent isKindOfClass:[NSOpenPanel class]] || + [[parent className] isEqualToString:@"NSLocalOpenPanel"]) + { + if ([parent respondsToSelector:@selector(setAccessoryViewDisclosed:)]) + [(NSOpenPanel *)parent setAccessoryViewDisclosed:YES]; + } + [self setHidden:NO]; + } + else + { + [(NSWindow *)parent setContentView:self]; + } + } + else + { + [parent addSubview:self]; + } + if (resstate) resstate->createFunc((HWND)self,resstate->windowTypeFlags); + + if (resstate) m_dlgproc=dlgproc; + else if (dlgproc) m_wndproc=(WNDPROC)dlgproc; + + if (resstate && (resstate->windowTypeFlags&SWELL_DLG_WS_DROPTARGET)) + { + [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]]; + } + + if (!resstate) + m_wndproc((HWND)self,WM_CREATE,0,par); + + if (m_dlgproc) + { + HWND hFoc=0; + NSArray *ar=[self subviews]; + if (ar && [ar count]>0) + { + int x; + for (x = 0; x < [ar count] && !hFoc; x ++) + { + NSView *v=[ar objectAtIndex:x]; + if (v && [v isKindOfClass:[NSScrollView class]]) v=[(NSScrollView *)v documentView]; + if (v && [v acceptsFirstResponder]) hFoc=(HWND)v; + } + } + + INT_PTR a; + if ((a=m_dlgproc((HWND)self,WM_INITDIALOG,(WPARAM)hFoc,par))) + { + // set first responder to first item in window + if (a == 0xbeef) hFoc = (HWND)self; // ret 0xbeef overrides to make the window itself focused (argh, need a cleaner way) + if (hFoc) + { + id wnd = [self window]; + if (wnd && [wnd firstResponder] != (id)hFoc) [wnd makeFirstResponder:(id)hFoc]; + } + + + if (parent && [self window] == (NSWindow *)parent && [(id)parent isKindOfClass:[SWELL_ModelessWindow class]] && ![(NSWindow *)parent isVisible]) + { + // on win32, if you do CreateDialog(), WM_INITDIALOG(ret=1), then ShowWindow(SW_SHOWNA), you get the + // window brought to front. this simulates that, hackishly. + ((SWELL_ModelessWindow *)parent)->m_wantInitialKeyWindowOnShow = true; + } + } + else + { + // if top level dialog,always set default focus if it wasn't set + // if this causes problems, change NSWindow to be SWELL_ModalDialog, as that would + // only affect DialogBox() and not CreateDialog(), which might be preferable. + if (hFoc && parent && [self window] == (NSWindow *)parent && [(id)parent isKindOfClass:[NSWindow class]]) + { + id fr = [(NSWindow *)parent firstResponder]; + if (!fr || fr == self || fr == (id)parent) [(NSWindow *)parent makeFirstResponder:(id)hFoc]; + + } + } + + SWELL_DoDialogColorUpdates((HWND)self,m_dlgproc,false); + } + +// if (!wasHid) + // [self setHidden:NO]; + + return self; +} + +-(void)setOpaque:(bool)isOpaque +{ + m_isopaque = isOpaque; +} + +-(BOOL)isOpaque +{ + return m_isopaque; +} + +- (void)setFrame:(NSRect)frameRect +{ + [super setFrame:frameRect]; + if (m_wndproc&&!m_hashaddestroy) m_wndproc((HWND)self,WM_SIZE,0,0); + HWND par = GetParent((HWND)self); + if (par) InvalidateRect(par,NULL,FALSE); +} + +- (void)keyDown:(NSEvent *)theEvent +{ + int flag,code=SWELL_MacKeyToWindowsKey(theEvent,&flag); + if (!m_wndproc || m_hashaddestroy || m_wndproc((HWND)self,WM_KEYDOWN,code,flag)==69) + { + [super keyDown:theEvent]; + } +} + +- (void)keyUp:(NSEvent *)theEvent +{ + int flag,code=SWELL_MacKeyToWindowsKey(theEvent,&flag); + if (!m_wndproc || m_hashaddestroy || m_wndproc((HWND)self,WM_KEYUP,code,flag)==69) + { + [super keyUp:theEvent]; + } +} + +#if SWELL_CUT_OUT_COMPOSITING_MIDDLEMAN > 0 // not done yet + +- (void)didAddSubview:(NSView *)subview +{ + m_isdirty|=2; + NSView *view = [self superview]; + while (view) + { + if ([view isKindOfClass:[SWELL_hwndChild class]]) + { + if (((SWELL_hwndChild *)view)->m_isdirty&2) break; + ((SWELL_hwndChild *)view)->m_isdirty|=2; + } + view = [view superview]; + } +} +- (void)willRemoveSubview:(NSView *)subview +{ + m_isdirty|=3; + [self setNeedsDisplay:YES]; + NSView *view = [self superview]; + while (view) + { + if ([view isKindOfClass:[SWELL_hwndChild class]]) + { + if ((((SWELL_hwndChild *)view)->m_isdirty&3)==3) break; + ((SWELL_hwndChild *)view)->m_isdirty|=3; + } + [view setNeedsDisplay:YES]; + view = [view superview]; + } +} + +-(void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)vr rectIsVisibleRectForView:(NSView*)view topView:(NSView *)v2 +{ + + + + // once we figure out how to get other controls to notify their parents that the view is dirty, we can enable this for 10.4 + // 10.5+ has some nice property where it goes up the hierarchy + +// NSLog(@"r:%@ vr:%d v=%p tv=%p self=%p %p\n",NSStringFromRect(rect),vr,v,v2,self, [[self window] contentView]); + if (!useNoMiddleManCocoa() || ![self isOpaque] || [[self window] contentView] != self || [self isHiddenOrHasHiddenAncestor] || !m_allow_nomiddleman) + { + [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:vr rectIsVisibleRectForView:view topView:v2]; + return; + } + + if (!m_isdirty && ![self needsDisplay]) return; + + const NSRect *rlist=NULL; + NSInteger rlistcnt=0; + [self getRectsBeingDrawn:&rlist count:&rlistcnt]; + + + [self lockFocus]; + HDC hdc=SWELL_CreateGfxContext([NSGraphicsContext currentContext]); + + + const bool twoPassMode = false; // true makes it draw non-opaque items over all window backgrounds, but opaque children going last (so native controls over groups, etc) + // this is probably slower + + static WDL_PtrList ndlist; + int ndlist_oldsz=ndlist.GetSize(); + swellRenderOptimizely(twoPassMode?1:3,self,hdc,false,&ndlist,rlist,rlistcnt,0,0,true,self); + + while (ndlist.GetSize()>ndlist_oldsz+1) + { + NSView *v = (NSView *)ndlist.Get(ndlist.GetSize()-1); + ndlist.Delete(ndlist.GetSize()-1); + + int flag = (int)(INT_PTR) ndlist.Get(ndlist.GetSize()-1); + ndlist.Delete(ndlist.GetSize()-1); + + NSRect b = [v bounds]; + + NSRectSet *rs = nil; + + if (rlistcnt && !(flag&1)) + { + rs = [[NSRectSet alloc] initWithCopyOfRects:rlist count:rlistcnt bounds:[self bounds]]; + [rs convertFromAncestor:self toView:v clipTo:b]; + } + else + { + rs = [[NSRectSet alloc] initWithRect:b]; + } + + if (![rs isEmpty]) + { + [[_NSDisplayOperationStack currentThreadDisplayOperationStack] setRectSetBeingDrawn:rs forView:v]; + NSRect a=[rs bounds]; +// [v displayRectIgnoringOpacity:a]; + [v _recursiveDisplayRectIfNeededIgnoringOpacity:a isVisibleRect:TRUE rectIsVisibleRectForView:v topView:v2]; + } + + [rs release]; + [v setNeedsDisplay:NO]; + [v release]; + } + + + if (twoPassMode) swellRenderOptimizely(2,self,hdc,false,&ndlist,rlist,rlistcnt,0,0,true,self); + SWELL_DeleteGfxContext(hdc); + [self unlockFocus]; + [self setNeedsDisplay:NO]; + +} +#endif + +-(BOOL) swellWantsMetal +{ +#ifndef SWELL_NO_METAL + return m_use_metal > 0; +#else + return NO; +#endif +} +-(void) swellDrawMetal:(const RECT *)forRect +{ +#ifndef SWELL_NO_METAL + +#define swell_metal_set_layer_gravity(layer, g) do { \ + const int grav = (g); \ + (layer).contentsGravity = (grav&1) ? (grav&2) ? @"bottomRight" : @"topRight" : \ + (grav&2) ? @"bottomLeft" : @"topLeft"; } while(0) + + if (m_use_metal != 1 && m_use_metal != 2) return; + const bool direct_mode = m_use_metal == 1; + + id device = m_metal_device; + + static bool reg; + if (!reg) + { + reg = true; + id s = [[SWELL_MetalNotificationHandler alloc] init]; + [[NSNotificationCenter defaultCenter] addObserver:s + selector:@selector(handleDisplayChanges:) + name:NSWindowDidChangeScreenNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:s + selector:@selector(handleDisplayChanges:) + name:NSApplicationDidChangeScreenParametersNotification + object:nil]; + } + + if (__CGDirectDisplayCopyCurrentMetalDevice && (!device || m_metal_devicelist_updcnt != s_metal_devicelist_updcnt)) + { + m_metal_devicelist_updcnt = s_metal_devicelist_updcnt; + CGDirectDisplayID viewDisplayID = (CGDirectDisplayID) [self.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue]; + device = __CGDirectDisplayCopyCurrentMetalDevice(viewDisplayID); + if (device == m_metal_device) + [device release]; + } + + if (!device) device=mtl_def_device(); + + CAMetalLayer *layer = (CAMetalLayer *)[self layer]; + + // this might happen if a caller calls SWELL_SetMetal too late after drawing has already occurred (and the backing layer was already created) + if (WDL_NOT_NORMALLY(![layer respondsToSelector:@selector(setFramebufferOnly:)])) return; + + if (device != m_metal_device) + { + id olddev = (id)m_metal_device; + if (olddev) NSLog(@"swell-cocoa: switching metal devices from %p %@ to %p %@\n",olddev,olddev.name,device,device.name); + m_metal_device = device; + [layer setDevice:device]; + swell_metal_set_layer_gravity(layer,m_metal_gravity ^ ([self isFlipped] ? 2 : 0)); + layer.framebufferOnly = NO; + [layer setPixelFormat:MTLPixelFormatBGRA8Unorm]; + + if (!direct_mode) [m_metal_texture release]; + + m_metal_texture = NULL; + m_metal_drawable = NULL; + } + + if (!device) + { + NSLog(@"swell-cocoa: no metal device\n"); + return; + } + + swell_metal_device_ctx *ctx = s_metal_devices.Get((INT_PTR)device); + if (!ctx) + { + NSLog(@"swell-cocoa: creating metal device context for %p %@\n",device,device.name); + ctx = new swell_metal_device_ctx; + s_metal_devices.Insert((INT_PTR)device, ctx); + } + + RECT cr; + GetClientRect((HWND)self,&cr); + if (m_use_metal == 2 && forRect) + { + WinIntersectRect(&cr,&cr,forRect); + } + + if (direct_mode) + { + m_metal_drawable = NULL; + m_metal_texture = NULL; + } + if (cr.right > cr.left && cr.bottom > cr.top) + { + // this might be good to enable for all metal windows? shrug + if (m_use_metal == 2) + { + NSRect frame = [self frame]; + NSView *cv = [[self window] contentView]; + if (cv != self) frame = [self convertRect:frame toView:cv]; + + const int last_grav = m_metal_gravity; + if (frame.size.width != m_metal_lastframe.size.width) + { + if (frame.origin.x != m_metal_lastframe.origin.x) m_metal_gravity|=1; + else m_metal_gravity&=~1; + } + if (frame.size.height != m_metal_lastframe.size.height) + { + if (frame.origin.y != m_metal_lastframe.origin.y) m_metal_gravity|=2; + else m_metal_gravity&=~2; + } + m_metal_lastframe = frame; + if (last_grav != m_metal_gravity) + swell_metal_set_layer_gravity(layer,m_metal_gravity ^ ([self isFlipped] ? 2 : 0)); + } + + HDC hdc = SWELL_CreateMetalDC(self); + + NSRect rect; + rect.origin.x = cr.left; + rect.origin.y = cr.top; + rect.size.width = cr.right-cr.left; + rect.size.height = cr.bottom-cr.top; + + m_metal_dc_dirty=0; + DrawSwellViewRectImpl(self,rect,hdc,true); + + SWELL_DeleteGfxContext(hdc); + } + m_metal_dc_dirty=0; + + if (direct_mode) + { + if (m_metal_drawable) + [m_metal_drawable present]; + m_metal_drawable = NULL; + m_metal_texture = NULL; + return; + } + +#ifdef _DEBUG + if (forRect && !IsWindowVisible((HWND)self)) + NSLog(@"swell-metal: drawing to hidden window %@, fix caller\n",self); +#endif + + id tex = (id) m_metal_texture; + if (!tex) return; // this can happen if GetDC()/ReleaseDC() are called before the first WM_PAINT + + NSRect bounds = [self bounds]; + if (bounds.size.width < 1 || bounds.size.height < 1) + { + NSLog(@"swell-cocoa: metal with empty bounds\n"); + return; + } + + if (m_metal_retina) + { + bounds.size.width *= 2; + bounds.size.height *= 2; + } + + LIMIT_METAL_BOUNDS_SIZE(bounds.size) + + CGSize oldsc = layer.drawableSize; + if (oldsc.width != bounds.size.width || oldsc.height != bounds.size.height) + { + CGSize ns; + ns.width = bounds.size.width; + ns.height = bounds.size.height; + layer.drawableSize = ns; + layer.contentsScale = m_metal_retina ? 2.0 : 1.0; + } + else if (layer.contentsScale != (m_metal_retina ? 2.0 : 1.0)) + layer.contentsScale = m_metal_retina ? 2.0 : 1.0; + id drawable = [layer nextDrawable]; + if (!drawable) + { + NSLog(@"swell-cocoa: metal surface got nul drawable\n"); + return; + } + + + if (!ctx->m_commandQueue) + ctx->m_commandQueue = [device newCommandQueue]; + + if (!ctx->m_commandBuffer) + { + ctx->m_commandBuffer = [ctx->m_commandQueue commandBuffer]; + [ctx->m_commandBuffer retain]; + } + + id cb = [ctx->m_commandQueue commandBuffer]; + if (WDL_NOT_NORMALLY(cb == NULL)) + cb = ctx->m_commandBuffer; // backup, run commands in the presentation buffer + + id encoder = [cb blitCommandEncoder]; + if (WDL_NOT_NORMALLY(encoder == NULL)) + { + NSLog(@"swell-cocoa: metal blitCommandEncoder failure\n"); + } + + const int texw = [(id) m_metal_texture width]; + const int texh = [(id) m_metal_texture height]; + if (texw < bounds.size.width) + { +#ifdef _DEBUG + NSLog(@"swell-cocoa: texture width mismatch %d vs %.0f\n",texw,bounds.size.width); +#endif + bounds.size.width = texw; + } + if (texh < bounds.size.height) + { +#ifdef _DEBUG + NSLog(@"swell-cocoa: texture height mismatch %d vs %.0f\n",texh,bounds.size.height); +#endif + bounds.size.height = texh; + } + [encoder copyFromTexture:m_metal_texture + sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(0,0,0) + sourceSize:MTLSizeMake(bounds.size.width,bounds.size.height,1.0) + toTexture:drawable.texture + destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(0,0,0)]; + + [encoder endEncoding]; + + if (cb != ctx->m_commandBuffer) + [cb commit]; + + [ctx->m_commandBuffer presentDrawable:drawable]; + + if (!s_mtl_in_update) + ctx->present(); +#endif +} + +-(void) drawRect:(NSRect)rect +{ + HDC hdc=SWELL_CreateGfxContext([NSGraphicsContext currentContext]); + DrawSwellViewRectImpl(self,rect,hdc); + SWELL_DeleteGfxContext(hdc); + m_isdirty=0; + +} + +- (void)rightMouseDragged:(NSEvent *)theEvent +{ + if (!m_enabled) return; + [self mouseDragged:theEvent]; +} +- (void)otherMouseDragged:(NSEvent *)theEvent +{ + if (!m_enabled) return; + [self mouseDragged:theEvent]; +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + if (!m_enabled) return; + + SWELL_SendMouseMessage(self,WM_MOUSEMOVE,theEvent); + if (SWELL_GetLastSetCursor()!=GetCursor()) SetCursor(SWELL_GetLastSetCursor()); +} +- (void)mouseMoved:(NSEvent *)theEvent +{ + if (DelegateMouseMove(self,theEvent)) return; + + if (m_enabled) if (!GetCapture() || GetCapture()==(HWND)self) { + SWELL_SendMouseMessage(self,WM_MOUSEMOVE, theEvent); + } +// [super mouseMoved:theEvent]; +} +- (void)mouseUp:(NSEvent *)theEvent +{ + if (!m_enabled) return; + if (m_isfakerightmouse) [self rightMouseUp:theEvent]; + else SWELL_SendMouseMessage(self,WM_LBUTTONUP,theEvent); +} +- (void)scrollWheel:(NSEvent *)theEvent +{ + if (!m_enabled) return; + // todo: use scrollingDeltaX/scrollingDeltaY etc on 10.7+ ? + if ([theEvent deltaY] != 0.0f) + { + SWELL_SendMouseMessage(self,WM_MOUSEWHEEL,theEvent); + } + if ([theEvent deltaX] != 0.0f) + { + SWELL_SendMouseMessage(self,WM_MOUSEHWHEEL,theEvent); + } +} +- (void)mouseDown:(NSEvent *)theEvent +{ + SWELL_FinishDragDrop(); + if (!m_enabled) return; + + m_isfakerightmouse=0; + if (([theEvent modifierFlags] & NSControlKeyMask) && IsRightClickEmulateEnabled()) + { + [self rightMouseDown:theEvent]; + if ([theEvent clickCount]<2) m_isfakerightmouse=1; + return; + } + + SWELL_SendMouseMessage(self,([theEvent clickCount]>1 ? WM_LBUTTONDBLCLK : WM_LBUTTONDOWN) ,theEvent); +} +- (void)rightMouseUp:(NSEvent *)theEvent +{ + if (!m_enabled) return; + m_isfakerightmouse=0; + SWELL_SendMouseMessage(self,WM_RBUTTONUP,theEvent); +} +- (void)rightMouseDown:(NSEvent *)theEvent +{ + m_isfakerightmouse=0; + if ([NSApp keyWindow] != [self window]) + { + SetFocus((HWND)[self window]); + } + SWELL_SendMouseMessage(self,([theEvent clickCount]>1 ? WM_RBUTTONDBLCLK : WM_RBUTTONDOWN),theEvent); +} +- (void)otherMouseUp:(NSEvent *)theEvent +{ + if (!m_enabled) return; + SWELL_SendMouseMessage(self,WM_MBUTTONUP,theEvent); +} +- (void)otherMouseDown:(NSEvent *)theEvent +{ + if ([NSApp keyWindow] != [self window]) + { + SetFocus((HWND)[self window]); + } + SWELL_SendMouseMessage(self,([theEvent clickCount]>1 ? WM_MBUTTONDBLCLK : WM_MBUTTONDOWN),theEvent); +} + +// multitouch support + +static void MakeGestureInfo(NSEvent* evt, GESTUREINFO* gi, HWND hwnd, int type) +{ + memset(gi, 0, sizeof(GESTUREINFO)); + gi->cbSize = sizeof(GESTUREINFO); + + gi->hwndTarget = hwnd; + gi->dwID = type; + + NSWindow* wnd = [evt window]; + NSPoint pt = [evt locationInWindow]; + pt = [wnd convertBaseToScreen:pt]; + gi->ptsLocation.x = pt.x; + gi->ptsLocation.y = pt.y; +} + +- (void)magnifyWithEvent:(NSEvent*)evt +{ + GESTUREINFO gi; + MakeGestureInfo(evt, &gi, (HWND) self, GID_ZOOM); + + gi.dwFlags = GF_BEGIN; + gi.ullArguments = 1024; // arbitrary + SendMessage((HWND)self, WM_GESTURE, 0, (LPARAM)&gi); + + gi.dwFlags = GF_END; + float z = [evt deltaZ]; // should be the same as 10.6 [evt magnification] + int a = (int)(1024.0f*z+0.5); + if (!a) a = (z >= 0.0f ? 1 : -1); + a += 1024; + if (a < 512) a=512; + else if (a > 2048) a=2048; + gi.ullArguments = a; + SendMessage((HWND)self, WM_GESTURE, gi.ullArguments, (LPARAM)&gi); +} + +- (void)swipeWithEvent:(NSEvent*)evt +{ + GESTUREINFO gi; + MakeGestureInfo(evt, &gi, (HWND) self, GID_PAN); + + gi.dwFlags = GF_BEGIN; + gi.ullArguments = 0; // for this gesture we only care about ptsLocation + SendMessage((HWND)self, WM_GESTURE, 0, (LPARAM)&gi); + + gi.dwFlags = GF_END; + NSRect r = [self bounds]; + int dx=0; + int dy=0; + + // for swipe events, deltaX/Y is either -1 or +1, convert to "one page" + if ([evt deltaX] < 0.0f) dx = -r.size.width; + else if ([evt deltaX] > 0.0f) dx = r.size.width; + else if ([evt deltaY] < 0.0f) dy = r.size.height; + else if ([evt deltaY] > 0.0f) dy = -r.size.height; + + gi.ptsLocation.x += dx; + gi.ptsLocation.y += dy; + + SendMessage((HWND)self, WM_GESTURE, gi.ullArguments, (LPARAM)&gi); +} + +-(void) rotateWithEvent:(NSEvent*)evt +{ + GESTUREINFO gi; + MakeGestureInfo(evt, &gi, (HWND) self, GID_ROTATE); + + gi.dwFlags = GF_BEGIN; + gi.ullArguments = 0; // Windows sends the absolute starting rotation as the first message, Mac doesn't + SendMessage((HWND)self, WM_GESTURE, 0, (LPARAM)&gi); + + gi.dwFlags = GF_END; + float z = [evt rotation]; + int i = (int)32767.0f*z/60.0f; + if (!i) i = (z >= 0.0f ? 1 : -1); + i += 32767; + if (i < 0) i=0; + else if (i > 65535) i=65535; + gi.ullArguments = i; + SendMessage((HWND)self, WM_GESTURE, i, (LPARAM)&gi); +} + + +- (const char *)onSwellGetText { return m_titlestr; } +-(void)onSwellSetText:(const char *)buf { lstrcpyn_safe(m_titlestr,buf,sizeof(m_titlestr)); } + + +// source-side drag/drop, only does something if source called SWELL_InitiateDragDrop while handling mouseDown +- (NSArray*) namesOfPromisedFilesDroppedAtDestination:(NSURL*) dropdestination +{ + NSArray* SWELL_DoDragDrop(NSURL*); + return SWELL_DoDragDrop(dropdestination); +} +- (BOOL)ignoreModifierKeysWhileDragging +{ + return GetProp((HWND)self,"SWELL_IgnoreModifierKeysWhileDragging") != NULL ? YES : NO; +} + +- (BOOL)becomeFirstResponder +{ + int en = m_enabled; + if (en < 0) + { + if ([[self window] contentView]==self) en = 1; // accept focus if we're enabled-without-focus and the contentview + } + if (en <= 0 || ![super becomeFirstResponder]) return NO; + SendMessage((HWND)self, WM_SETFOCUS, 0, 0); + SendMessage((HWND)self, WM_MOUSEACTIVATE, 0, 0); + return YES; +} + +- (BOOL)resignFirstResponder +{ + SendMessage((HWND)self, WM_KILLFOCUS, 0, 0); + return [super resignFirstResponder]; +} + +- (BOOL)acceptsFirstResponder +{ + if (m_enabled < 0) + { + // accept focus if we're enabled-without-focus and the contentview + if ([[self window] contentView]==self) return YES; + } + return m_enabled > 0?YES:NO; +} + +-(void)swellSetExtendedStyle:(LONG)st +{ + if (st&WS_EX_ACCEPTFILES) + { + if (!m_supports_ddrop) + { + [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]]; + m_supports_ddrop=true; + } + } + else + { + if (m_supports_ddrop) + { + [self unregisterDraggedTypes]; + m_supports_ddrop=false; + } + } +} +-(LONG)swellGetExtendedStyle +{ + LONG ret=0; + if (m_supports_ddrop) ret|=WS_EX_ACCEPTFILES; + return ret; +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + if (!m_supports_ddrop) return NSDragOperationNone; + + if (SWELL_DDrop_onDragEnter) + { + HANDLE h = (HANDLE)[self swellExtendedDragOp:sender retGlob:YES]; + if (h) + { + POINT pt; + NSPOINT_TO_POINT(&pt,[[self window] convertBaseToScreen:[sender draggingLocation]]); + SWELL_DDrop_onDragEnter(h,pt); + GlobalFree(h); + } + } + + return NSDragOperationGeneric; +} +- (BOOL) wantsPeriodicDraggingUpdates +{ + return NO; +} +- (NSDragOperation)draggingUpdated:(id )sender +{ + if (!m_supports_ddrop) return NSDragOperationNone; + + if (SWELL_DDrop_onDragOver) + { + POINT pt; + NSPOINT_TO_POINT(&pt,[[self window] convertBaseToScreen:[sender draggingLocation]]); + SWELL_DDrop_onDragOver(pt); + } + + return NSDragOperationGeneric; + +} +- (void)draggingExited:(id )sender +{ + if (m_supports_ddrop && SWELL_DDrop_onDragLeave) SWELL_DDrop_onDragLeave(); +} + +-(HANDLE)swellExtendedDragOp:(id )sender retGlob:(BOOL)retG +{ + if (!m_supports_ddrop) return 0; + + NSPasteboard *pboard; + pboard = [sender draggingPasteboard]; + + enum { PB_FILEREF=1, PB_FILEPROMISE }; + int pbtype = 0; + if ([[pboard types] containsObject:NSFilenamesPboardType]) pbtype |= PB_FILEREF; + if ([[pboard types] containsObject:NSFilesPromisePboardType]) pbtype |= PB_FILEPROMISE; + if (!pbtype) return 0; + + int sz=sizeof(DROPFILES); + + bool maketmpfn = false; + NSArray *files = 0; + if (pbtype&PB_FILEREF) + { + files = [pboard propertyListForType:NSFilenamesPboardType]; + } + else if (pbtype&PB_FILEPROMISE) + { + NSArray* exts = [pboard propertyListForType:NSFilesPromisePboardType]; // just the file extensions + if (retG) + { + files = exts; + maketmpfn = true; + } + else if (SWELL_DDrop_getDroppedFileTargetPath) + { + char ext[256]; + ext[0] = 0; + if ([exts objectAtIndex:0]) SWELL_CFStringToCString([exts objectAtIndex:0], ext, sizeof(ext)); + + const char* droppath = SWELL_DDrop_getDroppedFileTargetPath(ext); + if (!droppath || !droppath[0]) droppath = "/tmp/"; + NSString* pathstr = (NSString*)SWELL_CStringToCFString(droppath); + NSURL* dest = [NSURL fileURLWithPath:pathstr]; + + files = [sender namesOfPromisedFilesDroppedAtDestination:dest]; // tells the drag source to create the files + + if ([files count]) + { + NSMutableArray* paths=[NSMutableArray arrayWithCapacity:[files count]]; + int i; + for (i=0; i < [files count]; ++i) + { + NSString* fn=[files objectAtIndex:i]; + if (fn) + { + [paths addObject:[pathstr stringByAppendingPathComponent:fn]]; + } + } + files=paths; + } + + [pathstr release]; + } + } + if (!files) return 0; + + int x; + for (x = 0; x < [files count]; x ++) + { + NSString *sv=[files objectAtIndex:x]; + if (sv) + { + char text[4096]; + text[0]=0; + SWELL_CFStringToCString(sv,text,sizeof(text)); + sz+=strlen(text)+1; + if (maketmpfn) sz += strlen("tmp."); + } + } + + NSPoint tpt = [self convertPoint:[sender draggingLocation] fromView:nil]; + + HANDLE gobj=GlobalAlloc(0,sz+1); + DROPFILES *df=(DROPFILES*)gobj; + df->pFiles=sizeof(DROPFILES); + NSPOINT_TO_POINT(&df->pt, tpt); + df->fNC = FALSE; + df->fWide = FALSE; + char *pout = (char *)(df+1); + for (x = 0; x < [files count]; x ++) + { + NSString *sv=[files objectAtIndex:x]; + if (sv) + { + char text[4096]; + text[0]=0; + SWELL_CFStringToCString(sv,text,sizeof(text)); + if (maketmpfn) + { + strcpy(pout, "tmp."); + pout += strlen("tmp."); + } + strcpy(pout,text); + pout+=strlen(pout)+1; + } + } + *pout=0; + + if (!retG) + { + [self onSwellMessage:WM_DROPFILES p1:(WPARAM)gobj p2:0]; + GlobalFree(gobj); + } + + return gobj; +} + +- (BOOL)performDragOperation:(id )sender +{ + if (m_supports_ddrop && SWELL_DDrop_onDragLeave) SWELL_DDrop_onDragLeave(); + + HWND cv = NULL; // view to disable "setwindowrepre()" for + + id dragsrc = [sender draggingSource]; + if ([dragsrc isKindOfClass:[NSView class]]) + { + if ([(NSView *)dragsrc window] == [self window]) // this means we're likely dragging from the titlebar, so we gotta disable setwindowrepre cause cocoa sucks + { + cv = (HWND) [[self window] contentView]; + } + } + + if (cv) SetProp(cv,"SWELL_DisableWindowRepre",(HANDLE)TRUE); + + NSView *v=[self hitTest:[[self superview] convertPoint:[sender draggingLocation] fromView:nil]]; + if (v && [v isDescendantOf:self]) + { + while (v && v!=self) + { + if ([v respondsToSelector:@selector(swellExtendedDragOp:retGlob:)]) + if ([(SWELL_hwndChild *)v swellExtendedDragOp:sender retGlob:NO]) + { + if (cv) RemoveProp(cv,"SWELL_DisableWindowRepre"); + return YES; + } + v=[v superview]; + } + } + + BOOL ret=!![self swellExtendedDragOp:sender retGlob:NO]; + if (cv) RemoveProp(cv,"SWELL_DisableWindowRepre"); + return ret; +} + +-(unsigned int)swellCreateWindowFlags +{ + return m_create_windowflags; +} + + + + +// NSAccessibility + + +- (id)accessibilityHitTest:(NSPoint)point +{ + id ret = NULL; + id use_obj = NULL; + SendMessage((HWND)self,WM_GETOBJECT,0x1001,(LPARAM)&use_obj); + if (use_obj) + { + ret = [use_obj accessibilityHitTest:point]; + if (ret == use_obj && [ret accessibilityIsIgnored]) ret = NULL; + } + + if (!ret) ret = [super accessibilityHitTest:point]; + return ret; +} +- (id)accessibilityFocusedUIElement +{ + id use_obj = NULL, ret = NULL; + SendMessage((HWND)self,WM_GETOBJECT,0x1001,(LPARAM)&use_obj); + if (use_obj) + { + ret = [use_obj accessibilityFocusedUIElement]; + if (ret == use_obj) ret= NULL; + } + if (!ret) ret = [super accessibilityFocusedUIElement]; + return ret; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute +{ + id ret = [super accessibilityAttributeValue:attribute]; + int wo=0; + if ([attribute isEqual:NSAccessibilityChildrenAttribute] || (wo = !![attribute isEqual:NSAccessibilityVisibleChildrenAttribute])) + { + id *cp = wo ? m_access_cacheptrs+3 : m_access_cacheptrs; + id use_obj = NULL; + SendMessage((HWND)self,WM_GETOBJECT,0x1001,(LPARAM)&use_obj); + if (use_obj) + { + if (cp[0] && cp[1] && use_obj == cp[2] && (ret==cp[1] || [ret isEqualToArray:cp[1]])) return cp[0]; + + NSArray *ar=NULL; + if (ret && [ret count]) + { + ar = [NSMutableArray arrayWithArray:ret]; + [(NSMutableArray *)ar addObject:use_obj]; + } + else ar = [NSArray arrayWithObject:use_obj]; + + int x; + for (x=0;x<3;x++) if (cp[x]) { [cp[x] release]; cp[x]=0; } + + //cp[1]=ret; + //cp[2]=use_obj; + + ret = NSAccessibilityUnignoredChildren(ar); + //cp[0]=ret; + + for (x=0;x<3;x++) if (cp[x]) [cp[x] retain]; + + return ret; + } + int x; + for (x=0;x<3;x++) if (cp[x]) { [cp[x] release]; cp[x]=0; } + } + + return ret; +} +// Return YES if the UIElement doesn't show up to the outside world - i.e. its parent should return the UIElement's children as its own - cutting the UIElement out. E.g. NSControls are ignored when they are single-celled. +- (BOOL)accessibilityIsIgnored +{ + if (![[self subviews] count]) + { + id use_obj = NULL; + SendMessage((HWND)self,WM_GETOBJECT,0x1001,(LPARAM)&use_obj); + + if (use_obj) + { + return YES; + } + } + return [super accessibilityIsIgnored]; +} + +- (const char *)getSwellClass +{ + return m_classname; +} + +@end + + + + + +static HWND last_key_window; + +static HMENU swell_getEffectiveMenuForWindow(NSView *cv, NSWindow *window, bool isModal) // cv and window must be non-NULL +{ + for (;;) + { + if ([cv respondsToSelector:@selector(swellGetMenu)]) + { + HMENU menu = [(SWELL_hwndChild*)cv swellGetMenu]; + if (menu) return menu; + } + + if (isModal) return NULL; + + if (![window respondsToSelector:@selector(swellGetOwner)]) return NULL; + + id own = [(SWELL_ModelessWindow*)window swellGetOwner]; + if (!own || own == cv || own == window) return NULL; + + if ([own isKindOfClass:[NSWindow class]]) window = (NSWindow *)own; + else if ([own isKindOfClass:[NSView class]]) window = [(NSView *)own window]; + else return NULL; + + if (!window) return NULL; + + cv = [window contentView]; + if (!cv) return NULL; + } +} + +#define SWELLDIALOGCOMMONIMPLEMENTS_WND(ISMODAL) \ +-(BOOL)acceptsFirstResponder { return m_enabled?YES:NO; } \ +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { return m_enabled?YES:NO; } \ +- (void)setFrame:(NSRect)frameRect display:(BOOL)displayFlag \ +{ \ + [super setFrame:frameRect display:displayFlag]; \ + bool z = !![self isZoomed]; \ + if((int)frameRect.size.width != (int)lastFrameSize.width || \ + (int)frameRect.size.height != (int)lastFrameSize.height || \ + z != m_lastZoom) { \ + SWELL_hwndChild *hc = (SWELL_hwndChild*)[self contentView]; \ + sendSwellMessage(hc,WM_SIZE,z!=m_lastZoom ? z ? SIZE_MAXIMIZED : SIZE_RESTORED : 0,0); \ + m_lastZoom=z; \ + if ([hc isOpaque]) InvalidateRect((HWND)hc,NULL,FALSE); \ + lastFrameSize=frameRect.size; \ + } \ +} \ +- (void)windowDidMove:(NSNotification *)aNotification { \ + NSRect f=[self frame]; \ + sendSwellMessage([self contentView], WM_MOVE,0, MAKELPARAM((int)floor(f.origin.x+0.5),(int)floor(f.origin.y+0.5))); \ +} \ +- (BOOL)accessibilityIsIgnored \ +{ \ + if (!([self styleMask] & NSTitledWindowMask) && ![[[self contentView] subviews] count]) return YES; \ + return [super accessibilityIsIgnored]; \ +} \ +-(void)swellDoDestroyStuff \ +{ \ + if (last_key_window==(HWND)self) last_key_window=0; \ + OwnedWindowListRec *p=m_ownedwnds; m_ownedwnds=0; \ + while (p) \ + { \ + OwnedWindowListRec *next=p->_next; \ + DestroyWindow((HWND)p->hwnd); \ + free(p); p=next; \ + } \ + if (last_key_window==(HWND)self) last_key_window=0; \ + if (m_owner) { \ + [(SWELL_ModelessWindow*)m_owner swellRemoveOwnedWindow:self]; \ + if ([NSApp keyWindow] == self) [(SWELL_ModelessWindow*)m_owner makeKeyWindow]; \ + m_owner=0; \ + } \ +} \ +-(void)dealloc \ +{ \ + [self swellDoDestroyStuff]; \ + [super dealloc]; \ +} \ +- (void)swellDestroyAllOwnedWindows \ +{ \ + OwnedWindowListRec *p=m_ownedwnds; m_ownedwnds=0; \ + while (p) \ + { \ + OwnedWindowListRec *next=p->_next; \ + DestroyWindow((HWND)p->hwnd); \ + free(p); p=next; \ + } \ +} \ +- (void)resignKeyWindow { \ + [super resignKeyWindow]; \ + if (g_swell_terminating) return; \ + sendSwellMessage([self contentView],WM_ACTIVATE,WA_INACTIVE,0); \ + last_key_window=(HWND)self; \ +} \ +-(void)becomeKeyWindow \ +{ \ + [super becomeKeyWindow]; \ + if (g_swell_terminating) return; \ + NSView *foc=last_key_window && IsWindow(last_key_window) ? [(NSWindow *)last_key_window contentView] : 0; \ + HMENU menu=0; \ + if (foc && [foc respondsToSelector:@selector(swellHasBeenDestroyed)] && [(SWELL_hwndChild*)foc swellHasBeenDestroyed]) foc=NULL; \ + NSView *cv = [self contentView]; \ + if (!cv || ![cv respondsToSelector:@selector(swellHasBeenDestroyed)] || ![(SWELL_hwndChild*)cv swellHasBeenDestroyed]) { \ + menu = swell_getEffectiveMenuForWindow(cv,self,ISMODAL); \ + if (!menu) menu=ISMODAL && g_swell_defaultmenumodal ? g_swell_defaultmenumodal : g_swell_defaultmenu; \ + if (menu && menu != (HMENU)[NSApp mainMenu] && !g_swell_terminating) [NSApp setMainMenu:(NSMenu *)menu]; \ + sendSwellMessage(cv,WM_ACTIVATE,WA_ACTIVE,(LPARAM)foc); \ + sendSwellMessage(cv,WM_MOUSEACTIVATE,0,0); \ + } \ +} \ +-(BOOL)windowShouldClose:(id)sender \ +{ \ + NSView *v=[self contentView]; \ + if ([v respondsToSelector:@selector(onSwellMessage:p1:p2:)]) \ + if (![(SWELL_hwndChild*)v onSwellMessage:WM_CLOSE p1:0 p2:0]) \ + [(SWELL_hwndChild*)v onSwellMessage:WM_COMMAND p1:IDCANCEL p2:0]; \ + return NO; \ +} \ +- (BOOL)canBecomeKeyWindow { return !!m_enabled && !g_swell_terminating; } \ +- (void **)swellGetOwnerWindowHead { return (void **)&m_ownedwnds; } \ +- (void)swellAddOwnedWindow:(NSWindow*)wnd \ +{ \ + OwnedWindowListRec *p=m_ownedwnds; \ + while (p) { \ + if (p->hwnd == wnd) return; \ + p=p->_next; \ + } \ + p=(OwnedWindowListRec*)malloc(sizeof(OwnedWindowListRec)); \ + p->hwnd=wnd; p->_next=m_ownedwnds; m_ownedwnds=p; \ + if ([wnd respondsToSelector:@selector(swellSetOwner:)]) [(SWELL_ModelessWindow*)wnd swellSetOwner:self]; \ + if (SWELL_owned_windows_levelincrease) if ([wnd isKindOfClass:[NSWindow class]]) \ + { \ + int extra = [wnd isKindOfClass:[SWELL_ModelessWindow class]] ? ((SWELL_ModelessWindow *)wnd)->m_wantraiseamt : 0; \ + if ([NSApp isActive]) [wnd setLevel:[self level]+1+extra]; \ + } \ +} \ +- (void)swellRemoveOwnedWindow:(NSWindow *)wnd \ +{ \ + OwnedWindowListRec *p=m_ownedwnds, *lp=NULL; \ + while (p) { \ + if (p->hwnd == wnd) { \ + if (lp) lp->_next=p->_next; \ + else m_ownedwnds=p->_next; \ + free(p); \ + return; \ + } \ + lp=p; \ + p=p->_next; \ + } \ +} \ +- (void)swellResetOwnedWindowLevels { \ + if (SWELL_owned_windows_levelincrease) { OwnedWindowListRec *p=m_ownedwnds; \ + bool active = [NSApp isActive]; \ + int l=(int)[self level]+!!active; \ + while (p) { \ + if (p->hwnd) { \ + int extra = active && [(id)p->hwnd isKindOfClass:[SWELL_ModelessWindow class]] ? ((SWELL_ModelessWindow *)p->hwnd)->m_wantraiseamt : 0; \ + [(NSWindow *)p->hwnd setLevel:l+extra]; \ + if ([(id)p->hwnd respondsToSelector:@selector(swellResetOwnedWindowLevels)]) \ + [(id)p->hwnd swellResetOwnedWindowLevels]; \ + } \ + p=p->_next; \ + } \ + } \ +} \ +- (void)swellSetOwner:(id)owner { m_owner=owner; } \ +- (id)swellGetOwner { return m_owner; } \ +- (NSSize)minSize \ +{ \ + MINMAXINFO mmi; \ + memset(&mmi,0,sizeof(mmi)); \ + NSSize minsz=(NSSize)[super minSize]; \ + mmi.ptMinTrackSize.x=(int)minsz.width; mmi.ptMinTrackSize.y=(int)minsz.height; \ + sendSwellMessage([self contentView],WM_GETMINMAXINFO,0,(LPARAM)&mmi); \ + minsz.width=mmi.ptMinTrackSize.x; minsz.height=mmi.ptMinTrackSize.y; \ + return minsz; \ +} \ +- (NSSize)maxSize \ +{ \ + MINMAXINFO mmi; \ + memset(&mmi,0,sizeof(mmi)); \ + NSSize maxsz=(NSSize)[super maxSize]; NSSize tmp=maxsz;\ + if (tmp.width<1)tmp.width=1; else if (tmp.width > 1000000.0) tmp.width=1000000.0; \ + if (tmp.height<1)tmp.height=1; else if (tmp.height > 1000000.0) tmp.height=1000000.0; \ + mmi.ptMaxTrackSize.x=(int)tmp.width; mmi.ptMaxTrackSize.y=(int)tmp.height; \ + sendSwellMessage([self contentView], WM_GETMINMAXINFO, 0, (LPARAM)&mmi); \ + if (mmi.ptMaxTrackSize.x < 1000000) maxsz.width=mmi.ptMaxTrackSize.x; \ + if (mmi.ptMaxTrackSize.y < 1000000) maxsz.height=mmi.ptMaxTrackSize.y; \ + return maxsz; \ +} \ + + + +#define INIT_COMMON_VARS \ + m_enabled=TRUE; \ + m_owner=0; \ + m_ownedwnds=0; \ + m_lastZoom=false; \ + + +#if 0 +#define DOWINDOWMINMAXSIZES(ch) \ +{ \ + MINMAXINFO mmi={0}; \ + NSSize minsz=(NSSize)[super contentMinSize]; \ + mmi.ptMinTrackSize.x=(int)minsz.width; mmi.ptMinTrackSize.y=(int)minsz.height; \ + sendSwellMessage(ch,WM_GETMINMAXINFO,0,(LPARAM)&mmi); \ + minsz.width=mmi.ptMinTrackSize.x; minsz.height=mmi.ptMinTrackSize.y; \ + [super setContentMinSize:minsz]; \ +} + +#endif + +static void GetInitialWndPos(HWND owner, int h, int* x, int* y) +{ + RECT r; + if (owner) GetWindowRect(owner, &r); + else SWELL_GetViewPort(&r, 0, false); + *x = r.left+50; + *y = r.bottom-h-100; +} + +NSView **g_swell_mac_foreign_key_event_sink; + + +@implementation SWELL_ModelessWindow : NSWindow + +SWELLDIALOGCOMMONIMPLEMENTS_WND(0) + +-(id)_setFrame:(NSRect)r fromAdjustmentToScreen:(NSScreen *)scr anchorIfNeeded:(void *)anch animate:(int)anim +{ + if (m_disableMonitorAutosize) + return nil; + + SEL sel = @selector(_setFrame:fromAdjustmentToScreen:anchorIfNeeded:animate:); + if (WDL_NOT_NORMALLY(![super respondsToSelector:sel])) return nil; + + id (*send_msg)(struct objc_super *, SEL, NSRect, NSScreen*, void*, int) = (id (*)(struct objc_super *, SEL, NSRect, NSScreen*, void*, int)) &objc_msgSendSuper; + struct objc_super sup = { + self, + [self superclass] + }; + return send_msg(&sup, sel, r, scr, anch, anim); +} + + +- (id)initModelessForChild:(HWND)child owner:(HWND)owner styleMask:(unsigned int)smask +{ + INIT_COMMON_VARS + m_disableMonitorAutosize = false; + m_wantInitialKeyWindowOnShow=0; + m_wantraiseamt=0; + lastFrameSize.width=lastFrameSize.height=0.0f; + + NSRect cr=[(NSView *)child bounds]; + + int wx, wy; + GetInitialWndPos(owner, cr.size.height, &wx, &wy); + NSRect contentRect=NSMakeRect(wx,wy,cr.size.width,cr.size.height); + if (!(self = [super initWithContentRect:contentRect styleMask:smask backing:NSBackingStoreBuffered defer:NO])) return self; + + [self setDelegate:(id)self]; + [self disableCursorRects]; + [self setAcceptsMouseMovedEvents:YES]; + [self setContentView:(NSView *)child]; + [self useOptimizedDrawing:YES]; + if (SWELL_GetOSXVersion()>=0x10c0) [self setValue:[NSNumber numberWithInt:2] forKey:@"tabbingMode"]; + + updateWindowCollection(self); + + if (owner && [(id)owner respondsToSelector:@selector(swellAddOwnedWindow:)]) + { + [(id)owner swellAddOwnedWindow:self]; + } + else if (owner && [(id)owner isKindOfClass:[NSView class]]) + { + NSWindow *w=[(id)owner window]; + if (w && [w respondsToSelector:@selector(swellAddOwnedWindow:)]) + { + [(SWELL_ModelessWindow*)w swellAddOwnedWindow:self]; + } + } + + [self setAutorecalculatesKeyViewLoop:YES]; + [self display]; + return self; +} + +- (id)initModeless:(SWELL_DialogResourceIndex *)resstate Parent:(HWND)parent dlgProc:(DLGPROC)dlgproc Param:(LPARAM)par outputHwnd:(HWND *)hwndOut forceStyles:(unsigned int)smask +{ + INIT_COMMON_VARS + m_disableMonitorAutosize = false; + m_wantInitialKeyWindowOnShow=0; + m_wantraiseamt=0; + + lastFrameSize.width=lastFrameSize.height=0.0f; + + int w = (resstate ? resstate->width : 10); + int h = (resstate ? resstate->height : 10); + + int wx, wy; + GetInitialWndPos(parent, h, &wx, &wy); + NSRect contentRect=NSMakeRect(wx,wy,w,h); + int sf=smask; + + if (resstate) + { + sf |= NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask; + if (resstate->windowTypeFlags&SWELL_DLG_WS_RESIZABLE) sf |= NSResizableWindowMask; + } + + if (!(self = [super initWithContentRect:contentRect styleMask:sf backing:NSBackingStoreBuffered defer:NO])) return self; + + [self disableCursorRects]; + [self setAcceptsMouseMovedEvents:YES]; + [self useOptimizedDrawing:YES]; + [self setDelegate:(id)self]; + if (SWELL_GetOSXVersion()>=0x10c0) [self setValue:[NSNumber numberWithInt:2] forKey:@"tabbingMode"]; + + updateWindowCollection(self); + + if (resstate&&resstate->title) SetWindowText((HWND)self, resstate->title); + + + if (parent && [(id)parent respondsToSelector:@selector(swellAddOwnedWindow:)]) + { + [(id)parent swellAddOwnedWindow:self]; + } + else if (parent && [(id)parent isKindOfClass:[NSView class]]) + { + NSWindow *ww=[(id)parent window]; + if (ww && [ww respondsToSelector:@selector(swellAddOwnedWindow:)]) + { + [(SWELL_ModelessWindow*)ww swellAddOwnedWindow:self]; + } + } + + [self retain]; // in case WM_INITDIALOG goes and releases us + + SWELL_hwndChild *ch=[[SWELL_hwndChild alloc] initChild:resstate Parent:(NSView *)self dlgProc:dlgproc Param:par]; // create a new child view class + ch->m_create_windowflags=sf; + *hwndOut = (HWND)ch; + + [ch release]; + + [self setAutorecalculatesKeyViewLoop:YES]; + [self display]; + [self release]; // matching retain above + + return self; +} +-(NSInteger)level +{ + //if (SWELL_owned_windows_levelincrease) return NSNormalWindowLevel; + return [super level]; +} + +#if SWELL_CUT_OUT_COMPOSITING_MIDDLEMAN > 1 +-(void) displayIfNeeded +{ + if (![[self contentView] isOpaque]) + { + [super displayIfNeeded]; + } + else + { + // NSThemeFrame + if ([self viewsNeedDisplay]) + { + [[self contentView] _recursiveDisplayRectIfNeededIgnoringOpacity:NSMakeRect(0,0,0,0) isVisibleRect:YES rectIsVisibleRectForView:[self contentView] topView:[self contentView]]; + [self setViewsNeedDisplay:NO]; + [self flushWindow]; + } + + } +} +#endif + +-(void)keyDown:(NSEvent *)event +{ + if (g_swell_mac_foreign_key_event_sink && [event window] != self) + { + *g_swell_mac_foreign_key_event_sink = [self contentView]; + } + else + { + [super keyDown:event]; + } +} + +-(void)toggleFullScreen:(id)sender +{ + if (!SendMessage((HWND)[self contentView],WM_SWELL_EXTENDED,(WPARAM)"toggleFullScreen",(LPARAM)sender)) + [super toggleFullScreen:sender]; +} + +@end + + + + +@implementation SWELL_ModalDialog : NSPanel + +SWELLDIALOGCOMMONIMPLEMENTS_WND(1) + + + +- (id)initDialogBox:(SWELL_DialogResourceIndex *)resstate Parent:(HWND)parent dlgProc:(DLGPROC)dlgproc Param:(LPARAM)par +{ + m_rv=0; + m_hasrv=false; + INIT_COMMON_VARS + + NSRect contentRect=NSMakeRect(0,0,resstate->width,resstate->height); + unsigned int sf=(NSTitledWindowMask|NSClosableWindowMask|((resstate->windowTypeFlags&SWELL_DLG_WS_RESIZABLE)? NSResizableWindowMask : 0)); + if (!(self = [super initWithContentRect:contentRect styleMask:sf backing:NSBackingStoreBuffered defer:NO])) return self; + + [self setAcceptsMouseMovedEvents:YES]; + [self disableCursorRects]; + [self useOptimizedDrawing:YES]; + [self setDelegate:(id)self]; + updateWindowCollection(self); + + if (parent && [(id)parent respondsToSelector:@selector(swellAddOwnedWindow:)]) + { + [(id)parent swellAddOwnedWindow:self]; + } + else if (parent && [(id)parent isKindOfClass:[NSView class]]) + { + NSWindow *w=[(id)parent window]; + if (w && [w respondsToSelector:@selector(swellAddOwnedWindow:)]) + { + [(SWELL_ModelessWindow*)w swellAddOwnedWindow:self]; + } + } + if (resstate&&resstate->title) SetWindowText((HWND)self, resstate->title); + + SWELL_hwndChild *ch=[[SWELL_hwndChild alloc] initChild:resstate Parent:(NSView *)self dlgProc:dlgproc Param:par]; // create a new child view class + ch->m_create_windowflags=sf; + [ch setHidden:NO]; +// DOWINDOWMINMAXSIZES(ch) + [ch release]; + + [self setAutorecalculatesKeyViewLoop:YES]; + [self setHidesOnDeactivate:NO]; + [self display]; + + return self; +} + + +-(void)swellSetModalRetVal:(int)r +{ + m_hasrv=true; + m_rv=r; +} +-(int)swellGetModalRetVal +{ + return m_rv; +} +-(bool)swellHasModalRetVal +{ + return m_hasrv; +} + +@end + +void EndDialog(HWND wnd, int ret) +{ + if (WDL_NOT_NORMALLY(!wnd)) return; + + NSWindow *nswnd=NULL; + NSView *nsview = NULL; + if ([(id)wnd isKindOfClass:[NSView class]]) + { + nsview = (NSView *)wnd; + nswnd = [nsview window]; + } + else if ([(id)wnd isKindOfClass:[NSWindow class]]) + { + nswnd = (NSWindow *)wnd; + nsview = [nswnd contentView]; + } + if (!nswnd) return; + + if ([nswnd respondsToSelector:@selector(swellSetModalRetVal:)]) + [(SWELL_ModalDialog*)nswnd swellSetModalRetVal:ret]; + + if ([NSApp modalWindow] == nswnd) + { + sendSwellMessage(nsview,WM_DESTROY,0,0); + + NSEvent *evt=[NSApp currentEvent]; + if (evt && [evt window] == nswnd) + { + [NSApp stopModal]; + } + + [NSApp abortModal]; // always call this, otherwise if running in runModalForWindow: it can often require another even tto come through before things continue + + [nswnd close]; + } +} + + +int SWELL_DialogBox(SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent, DLGPROC dlgproc, LPARAM param) +{ + SWELL_DialogResourceIndex *p=resById(reshead,resid); + if (!p||(p->windowTypeFlags&SWELL_DLG_WS_CHILD)) return -1; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + SWELL_ModalDialog *box = [[SWELL_ModalDialog alloc] initDialogBox:p Parent:parent dlgProc:dlgproc Param:param]; + + if (!box) + { + [pool release]; + return -1; + } + + if ([box swellHasModalRetVal]) // detect EndDialog() in WM_INITDIALOG + { + int ret=[box swellGetModalRetVal]; + sendSwellMessage([box contentView],WM_DESTROY,0,0); + [box close]; + [box release]; + [pool release]; + return ret; + } + + if (0 && ![NSApp isActive]) // using this enables better background processing (i.e. if the app isnt active it still runs) + { + [NSApp activateIgnoringOtherApps:YES]; + NSModalSession session = [NSApp beginModalSessionForWindow:box]; + for (;;) + { + if ([NSApp runModalSession:session] != NSRunContinuesResponse) break; + Sleep(1); + } + [NSApp endModalSession:session]; + } + else + { + [NSApp runModalForWindow:box]; + } + int ret=[box swellGetModalRetVal]; + [box release]; + [pool release]; + return ret; +} + +HWND SWELL_CreateModelessFrameForWindow(HWND childW, HWND ownerW, unsigned int windowFlags) +{ + SWELL_ModelessWindow *ch=[[SWELL_ModelessWindow alloc] initModelessForChild:childW owner:ownerW styleMask:windowFlags]; + return (HWND)ch; +} + + +HWND SWELL_CreateDialog(SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent, DLGPROC dlgproc, LPARAM param) +{ + unsigned int forceStyles=0; + bool forceNonChild=false; + if ((((INT_PTR)resid)&~0xf)==0x400000) + { + const int a = ((int)(INT_PTR)resid)&0xf; + forceStyles = NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask; + if (a&1) forceStyles|=NSResizableWindowMask; + if (a&2) forceStyles&=~NSMiniaturizableWindowMask; + if (a&4) forceStyles&=~NSClosableWindowMask; + if (a) forceNonChild=true; + resid=NULL; + } + SWELL_DialogResourceIndex *p=resById(reshead,resid); + if (!p&&resid) return 0; + + NSView *parview=NULL; + if (parent) + { + if ([(id)parent isKindOfClass:[NSView class]]) + { + parview = (NSView *)parent; + } + else if ([(id)parent isKindOfClass:[NSWindow class]]) + { + if ([(id)parent isKindOfClass:[NSPanel class]] && + [(id)parent respondsToSelector:@selector(setAccessoryView:)]) + { + parview=(NSView *)parent; + } + else + { + parview=(NSView *)[(NSWindow *)parent contentView]; + } + } + } + + if ((!p || (p->windowTypeFlags&SWELL_DLG_WS_CHILD)) && parview && (p || !forceNonChild)) + { + SWELL_hwndChild *ch=[[SWELL_hwndChild alloc] initChild:p Parent:parview dlgProc:dlgproc Param:param]; // create a new child view class + ch->m_create_windowflags=(NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask|NSResizableWindowMask); + [ch release]; + return (HWND)ch; + } + else + { + HWND h=NULL; + [[SWELL_ModelessWindow alloc] initModeless:p Parent:parent dlgProc:dlgproc Param:param outputHwnd:&h forceStyles:forceStyles]; + return h; + } + + return 0; +} + + +HMENU SWELL_GetDefaultWindowMenu() { return g_swell_defaultmenu; } +void SWELL_SetDefaultWindowMenu(HMENU menu) +{ + g_swell_defaultmenu=menu; +} +HMENU SWELL_GetDefaultModalWindowMenu() +{ + return g_swell_defaultmenumodal; +} +void SWELL_SetDefaultModalWindowMenu(HMENU menu) +{ + g_swell_defaultmenumodal=menu; +} + + + +SWELL_DialogResourceIndex *SWELL_curmodule_dialogresource_head; // this eventually will go into a per-module stub file + + +#import + + +#if 0 +static void PrintAllHIViews(HIViewRef f, const char *bla) +{ + char tmp[4096]; + sprintf(tmp,"%s:%08x",bla,f); + + HIRect r; + HIViewGetFrame(f,&r); + printf("%s beg %f %f %f %f\n",tmp,r.origin.x,r.origin.y,r.size.width, r.size.height); + HIViewRef a=HIViewGetFirstSubview(f); + while (a) + { + PrintAllHIViews(a,tmp); + a=HIViewGetNextView(a); + } + printf("%s end\n",tmp); +} +#endif + +#ifndef __LP64__ +// carbon event handler for carbon-in-cocoa +OSStatus CarbonEvtHandler(EventHandlerCallRef nextHandlerRef, EventRef event, void* userdata) +{ + SWELL_hwndCarbonHost* _this = (SWELL_hwndCarbonHost*)userdata; + UInt32 evtkind = GetEventKind(event); + + switch (evtkind) + { + case kEventWindowActivated: + if (!g_swell_terminating) [NSApp setMainMenu:nil]; + break; + + case kEventWindowGetClickActivation: + { + ClickActivationResult car = kActivateAndHandleClick; + SetEventParameter(event, kEventParamClickActivation, typeClickActivationResult, sizeof(ClickActivationResult), &car); + } + break; + + case kEventWindowHandleDeactivate: + { + if (_this) + { + WindowRef wndref = (WindowRef)[_this->m_cwnd windowRef]; + if (wndref) ActivateWindow(wndref, true); + } + } + break; + + case kEventControlBoundsChanged: + { + if (_this && !_this->m_whileresizing) + { + Rect prevr, curr; + GetEventParameter(event, kEventParamPreviousBounds, typeQDRectangle, 0, sizeof(Rect), 0, &prevr); + GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, 0, sizeof(Rect), 0, &curr); + + RECT parr; + GetWindowRect((HWND)_this, &parr); + parr.left += curr.left-prevr.left; + parr.top += curr.top-prevr.top; + parr.right += curr.right-prevr.right; + parr.bottom += curr.bottom-prevr.bottom; + _this->m_whileresizing = true; + SetWindowPos((HWND)_this, 0, parr.left, parr.right, parr.right-parr.left, parr.bottom-parr.top, SWP_NOZORDER|SWP_NOACTIVATE); + _this->m_whileresizing = false; + } + } + break; + + case kEventRawKeyDown: + case kEventRawKeyUp: + case kEventRawKeyModifiersChanged: + { + if (_this->m_wantallkeys) return eventNotHandledErr; + + WindowRef wndref = (WindowRef)[_this->m_cwnd windowRef]; + if (wndref) + { + ControlRef ctlref=0; + GetKeyboardFocus(wndref, &ctlref); + if (ctlref) + { + ControlKind ctlkind = { 0, 0 }; + GetControlKind(ctlref, &ctlkind); + if (ctlkind.kind == kControlKindEditText || + ctlkind.kind == kControlKindEditUnicodeText || + ctlkind.kind == kControlKindHITextView) + { + // ControlDefinitions.h, HITextViews.h, etc list control types, + // we may want to pass on some other types too + return eventNotHandledErr; + } + } + } + + UInt32 keycode; + UInt32 modifiers; + char c[2] = { 0, 0 }; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, 0, sizeof(UInt32), 0, &keycode); + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, 0, sizeof(UInt32), 0, &modifiers); + GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar, 0, sizeof(char), 0, &c[0]); + + NSEventType type; + if (evtkind == kEventRawKeyDown) type = NSKeyDown; + else if (evtkind == kEventRawKeyUp) type = NSKeyUp; + else /*if (evtkind == kEventRawKeyModifiersChanged) */ + type = NSFlagsChanged; + + NSString* str = (NSString*)SWELL_CStringToCFString(c); + NSTimeInterval ts = 0; // [[NSApp currentevent] timestamp]; + NSEvent* evt = [NSEvent keyEventWithType:type location:NSMakePoint(0,0) + modifierFlags:modifiers + timestamp:ts windowNumber:0 + context:[NSGraphicsContext currentContext] + characters:str charactersIgnoringModifiers:str + isARepeat:NO keyCode:keycode]; + [str release]; + if (evt) [NSApp sendEvent:evt]; + return noErr; + } + } + return noErr; +} + +void SWELL_CarbonWndHost_SetWantAllKeys(void* carbonhost, bool want) +{ + SWELL_hwndCarbonHost* h = (SWELL_hwndCarbonHost*)carbonhost; + if (h && [h isKindOfClass:[SWELL_hwndCarbonHost class]]) h->m_wantallkeys = want; +} + +#endif // __LP + +@implementation SWELL_hwndCarbonHost + +- (id)initCarbonChild:(NSView *)parent rect:(Rect*)r composit:(bool)wantComp +{ + if (!(self = [super initChild:nil Parent:parent dlgProc:nil Param:0])) return self; + + m_wantallkeys=false; + +#ifndef __LP64__ + WindowRef wndref=0; + CreateNewWindow (kPlainWindowClass, (wantComp ? kWindowCompositingAttribute : 0) | kWindowStandardHandlerAttribute|kWindowNoShadowAttribute, r, &wndref); + if (wndref) + { + // eventually we should set this and have the real NSWindow parent call ActivateWindow when activated/deactivated + // SetWindowActivationScope( m_wndref, kWindowActivationScopeNone); + + // adding a Carbon event handler to catch special stuff that NSWindow::initWithWindowRef + // doesn't automatically redirect to a standard Cocoa window method + + ControlRef ctl=0; + if (!wantComp) CreateRootControl(wndref, &ctl); // creating root control here so callers must use GetRootControl + + EventTypeSpec winevts[] = + { + { kEventClassWindow, kEventWindowActivated }, + { kEventClassWindow, kEventWindowGetClickActivation }, + { kEventClassWindow, kEventWindowHandleDeactivate }, + { kEventClassKeyboard, kEventRawKeyDown }, + { kEventClassKeyboard, kEventRawKeyUp }, + { kEventClassKeyboard, kEventRawKeyModifiersChanged }, + }; + int nwinevts = sizeof(winevts)/sizeof(EventTypeSpec); + + EventTypeSpec ctlevts[] = + { + //{ kEventClassControl, kEventControlInitialize }, + //{ kEventClassControl, kEventControlDraw }, + { kEventClassControl, kEventControlBoundsChanged }, + }; + int nctlevts = sizeof(ctlevts)/sizeof(EventTypeSpec); + + EventHandlerRef wndhandler=0, ctlhandler=0; + InstallWindowEventHandler(wndref, CarbonEvtHandler, nwinevts, winevts, self, &wndhandler); + if (!wantComp) InstallControlEventHandler(ctl, CarbonEvtHandler, nctlevts, ctlevts, self, &ctlhandler); + m_wndhandler = wndhandler; + m_ctlhandler = ctlhandler; + + // initWithWindowRef does not retain // MAKE SURE THIS IS NOT BAD TO DO + //CFRetain(wndref); + + m_cwnd = [[NSWindow alloc] initWithWindowRef:wndref]; + [m_cwnd setDelegate:(id)self]; + + ShowWindow(wndref); + + //[[parent window] addChildWindow:m_cwnd ordered:NSWindowAbove]; + //[self swellDoRepos]; + SetTimer((HWND)self,1,10,NULL); + } +#endif + return self; +} + +-(BOOL)swellIsCarbonHostingView { return YES; } + + +-(void)close +{ + KillTimer((HWND)self,1); + +#ifndef __LP64__ + if (m_wndhandler) + { + EventHandlerRef wndhandler = (EventHandlerRef)m_wndhandler; + RemoveEventHandler(wndhandler); + m_wndhandler = 0; + } + if (m_ctlhandler) + { + EventHandlerRef ctlhandler = (EventHandlerRef)m_ctlhandler; + RemoveEventHandler(ctlhandler); + m_ctlhandler = 0; + } + + if (m_cwnd) + { + if ([m_cwnd parentWindow]) [[m_cwnd parentWindow] removeChildWindow:m_cwnd]; + [m_cwnd orderOut:self]; + [m_cwnd close]; // this disposes the owned wndref + m_cwnd=0; + } +#endif +} + +-(void)dealloc +{ + [self close]; + [super dealloc]; // ?! +} + +- (void)SWELL_Timer:(id)sender +{ +#ifndef __LP64__ + id uinfo=[sender userInfo]; + if ([uinfo respondsToSelector:@selector(getValue)]) + { + int idx=(int)(INT_PTR)[(SWELL_DataHold*)uinfo getValue]; + if (idx==1) + { + if (![self superview] || [[self superview] isHiddenOrHasHiddenAncestor]) + { + NSWindow *oldw=[m_cwnd parentWindow]; + if (oldw) + { + [oldw removeChildWindow:(NSWindow *)m_cwnd]; + [m_cwnd orderOut:self]; + } + } + else + { + if (![m_cwnd parentWindow]) + { + NSWindow *par = [self window]; + if (par) + { + [par addChildWindow:m_cwnd ordered:NSWindowAbove]; + [self swellDoRepos]; + } + } + else + { + if (GetCurrentEventButtonState()&7) + { + if ([NSApp keyWindow] == [self window]) + { + POINT p; + GetCursorPos(&p); + RECT r; + GetWindowRect((HWND)self,&r); + if (r.top>r.bottom) + { + int a=r.top; + r.top=r.bottom; + r.bottom=a; + } + if (m_cwnd && p.x >=r.left &&p.x < r.right && p.y >= r.top && p.y < r.bottom) + { + [(NSWindow *)m_cwnd makeKeyWindow]; + } + } + } + } + } + return; + } + KillTimer((HWND)self,idx); + return; + } +#endif +} +- (LRESULT)onSwellMessage:(UINT)msg p1:(WPARAM)wParam p2:(LPARAM)lParam +{ + if (msg == WM_DESTROY) + { + if (m_cwnd) + { + if ([NSApp keyWindow] == m_cwnd) // restore focus to highest window that is not us! + { + NSArray *ar = [NSApp orderedWindows]; + int x; + for (x = 0; x < (ar ? [ar count] : 0); x ++) + { + NSWindow *w=[ar objectAtIndex:x]; + if (w && w != m_cwnd && [w isVisible]) { [w makeKeyWindow]; break; } + } + } + + [self close]; + } + } + return [super onSwellMessage:msg p1:wParam p2:lParam]; +} +- (void)windowDidResignKey:(NSNotification *)aNotification +{ +} +- (void)windowDidBecomeKey:(NSNotification *)aNotification +{ +} + + +- (void)viewDidMoveToWindow +{ + [super viewDidMoveToWindow]; + if (m_cwnd) + { + // reparent m_cwnd to new owner + NSWindow *neww=[self window]; + NSWindow *oldw=[m_cwnd parentWindow]; + if (neww != oldw) + { + if (oldw) [oldw removeChildWindow:m_cwnd]; + } + } +} +-(void)swellDoRepos +{ +#ifndef __LP64__ + if (m_cwnd) + { + RECT r; + GetWindowRect((HWND)self,&r); + if (r.top>r.bottom) + { + int a=r.top; + r.top=r.bottom; + r.bottom=a; + } + + // [m_cwnd setFrameOrigin:NSMakePoint(r.left,r.top)]; + + { + Rect bounds; + bounds.left = r.left; + bounds.top = CGRectGetHeight(CGDisplayBounds(kCGDirectMainDisplay))-r.bottom; + // GetWindowBounds (m_wndref, kWindowContentRgn, &bounds); + bounds.right = bounds.left + (r.right-r.left); + bounds.bottom = bounds.top + (r.bottom-r.top); + + WindowRef wndref = (WindowRef)[m_cwnd windowRef]; + SetWindowBounds (wndref, kWindowContentRgn, &bounds); + + // might make sense to only do this on initial show, but doesnt seem to hurt to do it often + WindowAttributes wa=0; + GetWindowAttributes(wndref,&wa); + + if (wa&kWindowCompositingAttribute) + { +// [[m_cwnd contentView] setNeedsDisplay:YES]; + HIViewRef ref = HIViewGetRoot(wndref); + if (ref) + { + // PrintAllHIViews(ref,""); + + HIViewRef ref2=HIViewGetFirstSubview(ref); + while (ref2) + { + /* + HIRect r3=CGRectMake(0,0,bounds.right-bounds.left,bounds.bottom-bounds.top); + HIViewRef ref3=HIViewGetFirstSubview(ref2); + while (ref3) + { + HIViewSetVisible(ref3,true); + HIViewSetNeedsDisplay(ref3,true); + HIViewSetFrame(ref3,&r3); + ref3=HIViewGetNextView(ref3); + } + */ + + // HIViewSetVisible(ref2,true); + HIViewSetNeedsDisplay(ref2,true); + ref2=HIViewGetNextView(ref2); + } + //HIViewSetVisible(ref,true); + HIViewSetNeedsDisplay(ref,true); + HIViewRender(ref); + } + } + else + { + +#if 0 + ControlRef rc=NULL; + GetRootControl(m_wndref,&rc); + if (rc) + { + RgnHandle rgn=NewRgn(); + GetControlRegion(rc,kControlEntireControl,rgn); + UpdateControls(m_wndref,rgn); + CloseRgn(rgn); + } +#endif + // Rect r={0,0,bounds.bottom-bounds.top,bounds.right-bounds.left}; + // InvalWindowRect(m_wndref,&r); + + // or we could just do: + DrawControls(wndref); + } + } + } +#endif +} + +- (void)viewDidMoveToSuperview +{ + [super viewDidMoveToSuperview]; + [self swellDoRepos]; +} +- (void)setFrameSize:(NSSize)newSize +{ + [super setFrameSize:newSize]; + [self swellDoRepos]; +} +- (void)setFrame:(NSRect)frameRect +{ + [super setFrame:frameRect]; + [self swellDoRepos]; +} +- (void)setFrameOrigin:(NSPoint)newOrigin +{ + [super setFrameOrigin:newOrigin]; + [self swellDoRepos]; +} + + +-(BOOL)isOpaque +{ + return NO; +} + +@end + +HWND SWELL_GetAudioUnitCocoaView(HWND parent, AudioUnit aunit, AudioUnitCocoaViewInfo* viewinfo, RECT* r) +{ + NSString* classname = (NSString*)(viewinfo->mCocoaAUViewClass[0]); + if (!classname) return 0; + + NSBundle* bundle=0; + if ([NSBundle respondsToSelector:@selector(bundleWithURL:)]) + { + bundle=[NSBundle bundleWithURL:(NSURL*)viewinfo->mCocoaAUViewBundleLocation]; + } + + if (!bundle) + { + NSString* path = (NSString*)(CFURLCopyFileSystemPath(viewinfo->mCocoaAUViewBundleLocation,kCFURLPOSIXPathStyle)); + if (path) + { + bundle = [NSBundle bundleWithPath:path]; + [path release]; + } + } + + if (!bundle) return 0; + + Class factoryclass = [bundle classNamed:classname]; + if (![factoryclass conformsToProtocol: @protocol(AUCocoaUIBase)]) return 0; + if (![factoryclass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) return 0; + id viewfactory = [[factoryclass alloc] init]; + if (!viewfactory) return 0; + NSView* view = [viewfactory uiViewForAudioUnit:aunit withSize:NSMakeSize(r->right-r->left, r->bottom-r->top)]; + if (!view) + { + [viewfactory release]; + return 0; + } + + [view retain]; + + NSRect bounds = [view bounds]; + r->left = r->top = 0; + r->right = bounds.size.width; + r->bottom = bounds.size.height; + + [((NSView*)parent) setAutoresizesSubviews:NO]; + SetWindowPos((HWND)parent,NULL, 0,0, r->right,r->bottom, SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE); + + [(NSView*)parent addSubview:view]; + + [view release]; + [viewfactory release]; + + return (HWND)view; +} + + +HWND SWELL_CreateCarbonWindowView(HWND viewpar, void **wref, const RECT* r, bool wantcomp) // window is created with a root control +{ + RECT wndr = *r; + ClientToScreen(viewpar, (POINT*)&wndr); + ClientToScreen(viewpar, (POINT*)&wndr+1); + //Rect r2 = { wndr.top, wndr.left, wndr.bottom, wndr.right }; + Rect r2 = { (short)wndr.bottom, (short)wndr.left, (short)wndr.top, (short)wndr.right }; + SWELL_hwndCarbonHost *w = [[SWELL_hwndCarbonHost alloc] initCarbonChild:(NSView*)viewpar rect:&r2 composit:wantcomp]; + if (w) *wref = [w->m_cwnd windowRef]; + return (HWND)w; +} + +void* SWELL_GetWindowFromCarbonWindowView(HWND cwv) +{ + SWELL_hwndCarbonHost* w = (SWELL_hwndCarbonHost*)cwv; + if (WDL_NORMALLY(w)) return [w->m_cwnd windowRef]; + return 0; +} + +void SWELL_AddCarbonPaneToView(HWND cwv, void* pane) // not currently used +{ +#ifndef __LP64__ + SWELL_hwndCarbonHost* w = (SWELL_hwndCarbonHost*)cwv; + if (WDL_NORMALLY(w)) + { + WindowRef wndref = (WindowRef)[w->m_cwnd windowRef]; + if (wndref) + { + EventTypeSpec ctlevts[] = + { + //{ kEventClassControl, kEventControlInitialize }, + //{ kEventClassControl, kEventControlDraw }, + { kEventClassControl, kEventControlBoundsChanged }, + }; + int nctlevts = sizeof(ctlevts)/sizeof(EventTypeSpec); + + EventHandlerRef ctlhandler = (EventHandlerRef)w->m_ctlhandler; + InstallControlEventHandler((ControlRef)pane, CarbonEvtHandler, nctlevts, ctlevts, w, &ctlhandler); + } + } +#endif +} + +void SWELL_SetWindowFlip(HWND hwnd, bool flip) +{ + SWELL_hwndChild * hc = (SWELL_hwndChild*)hwnd; + if (WDL_NORMALLY(hc && [hc isKindOfClass:[SWELL_hwndChild class]])) + { + hc->m_flip = flip; + } +} + + +static char* s_dragdropsrcfn = 0; +static void (*s_dragdropsrccallback)(const char*) = 0; + +void SWELL_InitiateDragDrop(HWND hwnd, RECT* srcrect, const char* srcfn, void (*callback)(const char* dropfn)) +{ + SWELL_FinishDragDrop(); + + if (![(id)hwnd isKindOfClass:[SWELL_hwndChild class]]) return; + + s_dragdropsrcfn = strdup(srcfn); + s_dragdropsrccallback = callback; + + char* p = s_dragdropsrcfn+strlen(s_dragdropsrcfn)-1; + while (p >= s_dragdropsrcfn && *p != '.') --p; + ++p; + + NSString* str = (NSString*)SWELL_CStringToCFString(p); + NSRect r = NSMakeRect(srcrect->left, srcrect->top, srcrect->right-srcrect->left, srcrect->bottom-srcrect->top); + NSEvent* evt = [NSApp currentEvent]; + [(NSView*)hwnd dragPromisedFilesOfTypes:[NSArray arrayWithObject:str] fromRect:r source:(NSView*)hwnd slideBack:YES event:evt]; + [str release]; +} + +// owner owns srclist, make copies here etc +void SWELL_InitiateDragDropOfFileList(HWND hwnd, RECT *srcrect, const char **srclist, int srccount, HICON icon) +{ + SWELL_FinishDragDrop(); + + if (![(id)hwnd isKindOfClass:[SWELL_hwndChild class]]) return; + + NSMutableArray *ar = [[NSMutableArray alloc] initWithCapacity:srccount]; + int x; + + for(x=0;xleft,srcrect->top) offset:NSMakeSize(0,0) event:evt pasteboard:pb source:(id)hwnd slideBack:YES]; + + [ar release]; +} + + +static bool _file_exists(const char* fn) +{ + struct stat sb= { 0 }; + return !stat(fn, &sb); +} + +NSArray* SWELL_DoDragDrop(NSURL* droplocation) +{ + NSArray* fnarr=0; + if (s_dragdropsrcfn && s_dragdropsrccallback && droplocation) + { + const char* srcpath=s_dragdropsrcfn; + + const char* fn = srcpath+strlen(srcpath)-1; + while (fn >= srcpath && *fn != '/') --fn; + ++fn; + + WDL_String destpath; + destpath.SetFormatted(4096, "%s/%s", [[droplocation path] UTF8String], fn); + + bool ok=!_file_exists(destpath.Get()); + if (!ok) + { + NSInteger ret=NSRunAlertPanel(@"Copy", + @"An item named \"%s\" already exists in this location. Do you want to replace it with the one you're moving?", + @"Keep Both Files", @"Stop", @"Replace", fn); + + if (ret == -1) // replace + { + ok=true; + } + else if (ret == 1) // keep both + { + WDL_String base(destpath.Get()); + char* p=base.Get(); + size_t len=strlen(p); + const char* ext=""; + int incr=0; + + const char* q=fn+strlen(fn)-1; + while (q > fn && *q != '.') --q; + if (*q == '.') + { + ext=q; + len -= strlen(ext); + p[len]=0; + } + + int digits=0; + int i; + for (i=0; i < 3 && len > i+1 && isdigit(p[len-i-1]); ++i) ++digits; + if (len > digits+1 && (p[len-digits-1] == ' ' || p[len-digits-1] == '-' || p[len-digits-1] == '_')) + { + incr=atoi(p+len-digits); + p[len-digits]=0; + } + else + { + base.Append(" "); + } + + WDL_String trypath; + while (!ok && ++incr < 1000) + { + trypath.SetFormatted(4096, "%s%03d%s", base.Get(), incr, ext); + ok=!_file_exists(trypath.Get()); + } + + if (ok) destpath.Set(trypath.Get()); + } + } + + if (ok) + { + s_dragdropsrccallback(destpath.Get()); + ok=_file_exists(destpath.Get()); + } + + if (ok) + { + fn=destpath.Get(); + fn += strlen(fn)-1; + while (fn >= destpath.Get() && *fn != '/') --fn; + ++fn; + + NSString* nfn=(NSString*)SWELL_CStringToCFString(fn); + fnarr=[NSArray arrayWithObject:nfn]; + [nfn release]; + } + } + + SWELL_FinishDragDrop(); + return fnarr; +} + +void SWELL_FinishDragDrop() +{ + free(s_dragdropsrcfn); + s_dragdropsrcfn = 0; + s_dragdropsrccallback = 0; +} + +bool SWELL_SetGLContextToView(HWND h) +{ + if (!h) [NSOpenGLContext clearCurrentContext]; + else if (WDL_NORMALLY([(id)h isKindOfClass:[SWELL_hwndChild class]])) + { + SWELL_hwndChild *hc = (SWELL_hwndChild*)h; + if (hc->m_glctx) + { + [hc->m_glctx makeCurrentContext]; + return true; + } + } + return false; +} + +void SWELL_SetViewGL(HWND h, char wantGL) +{ + if (WDL_NORMALLY(h && [(id)h isKindOfClass:[SWELL_hwndChild class]])) + { + SWELL_hwndChild *hc = (SWELL_hwndChild*)h; + if (!!wantGL != !!hc->m_glctx) + { + if (wantGL) + { + if (wantGL == 2 && SWELL_GetOSXVersion()>=0x1070) [(NSView *)h setWantsBestResolutionOpenGLSurface:YES]; + + NSOpenGLPixelFormatAttribute atr[] = { + (NSOpenGLPixelFormatAttribute)96/*NSOpenGLPFAAllowOfflineRenderers*/, // allows use of NSSupportsAutomaticGraphicsSwitching and no gpu-forcing + (NSOpenGLPixelFormatAttribute)0 + }; // todo: optionally add any attributes before the 0 + if (SWELL_GetOSXVersion() < 0x1050) atr[0]=(NSOpenGLPixelFormatAttribute)0; // 10.4 can't use offline renderers and will fail trying + + NSOpenGLPixelFormat *fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:atr]; + + hc->m_glctx = [[NSOpenGLContext alloc] initWithFormat:fmt shareContext:nil]; + [fmt release]; + } + else + { + if ([NSOpenGLContext currentContext] == hc->m_glctx) [NSOpenGLContext clearCurrentContext]; + [hc->m_glctx release]; + hc->m_glctx=0; + } + } + + } +} + +bool SWELL_GetViewGL(HWND h) +{ + return WDL_NORMALLY(h && [(id)h isKindOfClass:[SWELL_hwndChild class]]) && ((SWELL_hwndChild*)h)->m_glctx; +} +void DrawSwellViewRectImpl(SWELL_hwndChild *view, NSRect rect, HDC hdc, bool isMetal) +{ + if (view->m_hashaddestroy) + { + return; + } + view->m_paintctx_hdc=hdc; + if (view->m_paintctx_hdc) + { + view->m_paintctx_hdc->GLgfxctx = view->m_glctx; + if (view->m_glctx) + { + [view->m_glctx setView:view]; + [view->m_glctx makeCurrentContext]; + [view->m_glctx update]; + } + } + view->m_paintctx_rect=rect; + view->m_paintctx_used=false; + DoPaintStuff(view->m_wndproc,(HWND)view,view->m_paintctx_hdc,&view->m_paintctx_rect,isMetal); + + if (view->m_paintctx_hdc) + { + if (view->m_glctx && [NSOpenGLContext currentContext] == view->m_glctx) + { + [NSOpenGLContext clearCurrentContext]; + } + view->m_paintctx_hdc->GLgfxctx = NULL; + } + view->m_paintctx_hdc=0; + if (!view->m_paintctx_used) { + /*[super drawRect:rect];*/ + } + +#if 0 + // debug: show everything + static CGColorSpaceRef cspace; + if (!cspace) cspace=CGColorSpaceCreateDeviceRGB(); + float cols[4]={0.0f,1.0f,0.0f,0.8f}; + CGColorRef color=CGColorCreate(cspace,cols); + + CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSetStrokeColorWithColor(ctx,color); + CGContextStrokeRectWithWidth(ctx, CGRectMake(rect.origin.x,rect.origin.y,rect.size.width,rect.size.height), 1); + + CGColorRelease(color); + + cols[0]=1.0f; + cols[1]=0.0f; + cols[2]=0.0f; + cols[3]=1.0f; + color=CGColorCreate(cspace,cols); + + NSRect rect2=[view bounds]; + CGContextSetStrokeColorWithColor(ctx,color); + CGContextStrokeRectWithWidth(ctx, CGRectMake(rect2.origin.x,rect2.origin.y,rect2.size.width,rect2.size.height), 1); + + + CGColorRelease(color); + + cols[0]=0.0f; + cols[1]=0.0f; + cols[2]=1.0f; + cols[3]=0.7f; + color=CGColorCreate(cspace,cols); + cols[3]=0.25; + cols[2]=0.5; + CGColorRef color2=CGColorCreate(cspace,cols); + + NSArray *ar = [view subviews]; + if (ar) + { + int x; + for(x=0;x<[ar count];x++) + { + NSView *v = [ar objectAtIndex:x]; + if (v && ![v isHidden]) + { + NSRect rect = [v frame]; + CGContextSetStrokeColorWithColor(ctx,color); + CGContextStrokeRectWithWidth(ctx, CGRectMake(rect.origin.x,rect.origin.y,rect.size.width,rect.size.height), 1); + CGContextSetFillColorWithColor(ctx,color2); + CGContextFillRect(ctx, CGRectMake(rect.origin.x,rect.origin.y,rect.size.width,rect.size.height)); + } + } + + // draw children + } + CGColorRelease(color); + CGColorRelease(color2); + +#endif + + + +} + +void swellRenderOptimizely(int passflags, SWELL_hwndChild *view, HDC hdc, BOOL doforce, WDL_PtrList *needdraws, const NSRect *rlist, NSInteger rlistcnt, int draw_xlate_x, int draw_xlate_y, bool iscv, NSView *rlist_coordview) +{ + if (view->m_isdirty&1) doforce=true; + NSArray *sv = [view subviews]; + if (doforce&&(passflags & ([sv count]?1:2))) + { + NSRect drawr = [view bounds]; + if (rlistcnt > 0) + { + if (view != rlist_coordview) drawr = [rlist_coordview convertRect:drawr fromView:view]; + + int x; + NSRect update_rect = rlist[0]; + for(x=1;x 0.0 && + drawr.size.height > 0.0 && + view != rlist_coordview) drawr = [rlist_coordview convertRect:drawr toView:view]; + + // if drawr is empty, might be good to update it back to bounds? if something stops painting right that would be a good thing to check + } + if (drawr.size.width > 0.0 && drawr.size.height > 0.0) + DrawSwellViewRectImpl(view,drawr, hdc); + } + + if (sv) + { + [sv retain]; + const NSInteger n=[sv count]; + HBRUSH bgbr=0; + bool bgbr_valid=false; + for (NSInteger x=0;xm_isdirty)|| [v needsDisplay]) + { + if (isSwellChild && ((SWELL_hwndChild *)v)->m_allow_nomiddleman) + { + NSRect fr = [v frame]; + CGContextSaveGState(hdc->ctx); + CGContextClipToRect(hdc->ctx,CGRectMake(fr.origin.x,fr.origin.y,fr.size.width,fr.size.height)); + CGContextTranslateCTM(hdc->ctx, fr.origin.x,fr.origin.y); + swellRenderOptimizely(passflags,(SWELL_hwndChild*)v,hdc,doforce,needdraws,rlist,rlistcnt,draw_xlate_x-(int)fr.origin.x,draw_xlate_y-(int)fr.origin.y,false,rlist_coordview); + CGContextRestoreGState(hdc->ctx); + if (passflags&2) [v setNeedsDisplay:NO]; + bgbr_valid=false; // code in swellRenderOptimizely() may trigger WM_CTLCOLORDLG which may invalidate our brush, so clear the cached value here + } + else if (passflags&1) + { + if ([v isKindOfClass:[NSScrollView class]]) + { + NSView *ccv = [(NSScrollView *)v contentView]; + if (ccv) + { + [v retain]; + needdraws->Add((void*)(INT_PTR)(doforce?1:0)); + needdraws->Add(v); + v=ccv; + } + } + [v retain]; + if (!doforce && ![v isOpaque]) + { + + NSRect fr= [v frame]; + + // we could recursively go up looking for WM_CTLCOLORDLG, but actually we just need to use the current window + if (!bgbr_valid) // note that any code in this loop that does anything that could trigger messages might invalidate bgbr, so it should clear bgbr_checked here + { + bgbr=(HGDIOBJ)SendMessage((HWND)view,WM_CTLCOLORDLG,(WPARAM)hdc,(LPARAM)view); + bgbr_valid=true; + } + + if (!iscv) fr = [view convertRect:fr toView:[[view window] contentView]]; + + int ri; + for(ri=0;ri0 && ff.size.height>0) + { + RECT r; + NSRECT_TO_RECT(&r,ff); + r.left+=draw_xlate_x; + r.right+=draw_xlate_x; + r.top+=draw_xlate_y; + r.bottom+=draw_xlate_y; + if (bgbr_valid && bgbr && bgbr != (HBRUSH)1) FillRect(hdc,&r,bgbr); + else SWELL_FillDialogBackground(hdc,&r,3); + } + } + } + needdraws->Add((void*)(INT_PTR)(doforce?1:0)); + needdraws->Add(v); + } + } + } + } + [sv release]; + } + if (passflags&2) + view->m_isdirty=0; +} + +#ifndef SWELL_NO_METAL + + +static void metalUpdateProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +{ + swell_updateAllMetalDirty(); +} + +static bool mtl_init() +{ + static int state; + if (!state) + { + state=-1; + + if (SWELL_GetOSXVersion() < 0x10b0) return false; + + dlopen("/System/Library/Frameworks/QuartzCore.framework/Versions/Current/QuartzCore",RTLD_LAZY); + void *lib = dlopen("/System/Library/Frameworks/Metal.framework/Versions/Current/Metal",RTLD_LAZY); + void *cgf = dlopen("/System/Library/Frameworks/CoreGraphics.framework/Versions/Current/CoreGraphics",RTLD_LAZY); + if (!lib || !cgf) return false; + NSArray *(*__MTLCopyAllDevices)(void); + *(void **)&__MTLCopyAllDevices = dlsym(lib,"MTLCopyAllDevices"); + *(void **)&__MTLCreateSystemDefaultDevice = dlsym(lib,"MTLCreateSystemDefaultDevice"); + *(void **)&__CGDirectDisplayCopyCurrentMetalDevice = dlsym(cgf,"CGDirectDisplayCopyCurrentMetalDevice"); + + if (__MTLCreateSystemDefaultDevice && + __MTLCopyAllDevices && + (__class_CAMetalLayer = objc_getClass("CAMetalLayer")) && + (__class_MTLTextureDescriptor = objc_getClass("MTLTextureDescriptor")) + ) + { + NSArray *ar = __MTLCopyAllDevices(); + NSUInteger cnt = [ar count]; + [ar release]; + if (cnt>0) + { + state=1; + SetTimer(NULL,0,1,metalUpdateProc); + } + } + } + + return state>0; +} + +bool swell_get_default_metal_devicename(char *buf, int bufsz) +{ + if (!mtl_init()) return false; + id dev = mtl_def_device(); + if (!dev) return false; + buf[0]=0; + SWELL_CFStringToCString(dev.name,buf,bufsz); + return buf[0]!=0; +} + + +#ifndef __ppc__ +static void SWELL_fastDoubleUpImage(unsigned int *op, const unsigned int *ip, int w, int h, int sw, int newspan) +{ + int y = h; + while (y-->0) + { + const unsigned int *rd = ip; + unsigned int *wr = op; + int remaining = w; + +#if 0 // def __AVX__ + // this isn't really any faster than SSE anyway + if (remaining >= 8) + { + if (!((INT_PTR)rd & 31)) + { + int x = remaining/8; + while (x-->0) + { + const __m256 m = _mm256_load_ps((const float *)rd); + rd+=8; + + const __m256 p1 = _mm256_permutevar_ps(_mm256_permute2f128_ps(m,m,0),_mm256_set_epi32(3,3,2,2,1,1,0,0)); + const __m256 p2 = _mm256_permutevar_ps(_mm256_permute2f128_ps(m,m,1|(1<<4)),_mm256_set_epi32(3,3,2,2,1,1,0,0)); + + unsigned int *wr2 = wr+newspan; + _mm256_store_ps((float*)wr,p1); + _mm256_store_ps((float*)wr2,p1); + + _mm256_store_ps((float*)wr + 8,p2); + _mm256_store_ps((float*)wr2 + 8,p2); + + wr += 16; + } + remaining &= 7; + + } + } +#endif + +#ifdef __SSE__ + if (remaining >= 4) + { + // with SSE is about 2x faster than without + if (((INT_PTR)rd & 7)) + { + // input isn't 8 byte aligned, must use unaligned reads + int x = remaining/4; + while (x-->0) + { + __m128 m = _mm_loadu_ps((const float *)rd); + __m128 p1 = _mm_shuffle_ps(m,m,_MM_SHUFFLE(1,1,0,0)); + __m128 p2 = _mm_shuffle_ps(m,m,_MM_SHUFFLE(3,3,2,2)); + + unsigned int *wr2 = wr+newspan; + rd+=4; + + _mm_store_ps((float*)wr,p1); + _mm_store_ps((float*)wr2,p1); + + _mm_store_ps((float*)wr + 4,p2); + _mm_store_ps((float*)wr2 + 4,p2); + + wr += 8; + } + } + else + { + // if rd is 8 byte aligned, we can do SSE without unaligned reads + + // but if it is not 16 byte aligned, we need to preprocess a pair of pixels + // (advancing rd by 8 bytes, and wr by 16) + + if ((INT_PTR)rd & 15) + { + unsigned int *nwr = wr+newspan; + wr[0] = wr[1] = nwr[0] = nwr[1] = rd[0]; + wr[2] = wr[3] = nwr[2] = nwr[3] = rd[1]; + wr+=4; + rd+=2; + remaining-=2; + } + + int x = remaining/4; + while (x-->0) + { + __m128 m = _mm_load_ps((const float *)rd); + __m128 p1 = _mm_shuffle_ps(m,m,_MM_SHUFFLE(1,1,0,0)); + __m128 p2 = _mm_shuffle_ps(m,m,_MM_SHUFFLE(3,3,2,2)); + + unsigned int *wr2 = wr+newspan; + rd+=4; + + _mm_store_ps((float*)wr,p1); + _mm_store_ps((float*)wr2,p1); + + _mm_store_ps((float*)wr + 4,p2); + _mm_store_ps((float*)wr2 + 4,p2); + + wr += 8; + } + } + remaining &= 3; + } +#endif //__SSE__ + + int x = remaining/2; + while (x-->0) + { + unsigned int *nwr = wr+newspan; + wr[0] = wr[1] = nwr[0] = nwr[1] = rd[0]; + wr[2] = wr[3] = nwr[2] = nwr[3] = rd[1]; + rd+=2; + wr+=4; + } + if (remaining&1) + { + wr[0] = wr[1] = wr[newspan] = wr[newspan+1] = *rd; + } + ip += sw; + op += newspan*2; + } +} +#endif + +static bool SWELL_Metal_ReadTex(SWELL_hwndChild *wnd, unsigned int *destbuf, int x, int y, int w, int h, int span) +{ + id tex = (id) wnd->m_metal_texture; + const int texw = (int)tex.width, texh = (int)tex.height; + if (WDL_NOT_NORMALLY(x<0) || + WDL_NOT_NORMALLY(y<0) || + WDL_NOT_NORMALLY(x+w > texw) || + WDL_NOT_NORMALLY(y+h > texh) || + WDL_NOT_NORMALLY(span>24); + if (alpha) + { + if (alpha<255) + { + const unsigned int dp = *dest; + const unsigned int ra=256-alpha; + *dest++ = (((dp&0xff) * ra + (v&0xff)*alpha) >> 8) + + ((((dp&0xff00) * ra + (v&0xff00)*alpha) >> 8) & 0xff00) + + ((((dp&0xff0000) * ra + (v&0xff0000)*alpha) >> 8) & 0xff0000); + } + else + *dest++ = v; + } + else + dest++; + } +} + +static void SWELL_Metal_WriteTex(SWELL_hwndChild *wnd, const unsigned int *srcbuf, int x, int y, int w, int h, int span, bool retina_hint) +{ + id tex = (id) wnd->m_metal_texture; + int texw = 0, texh = 0; + if (tex) + { + texw = (int)tex.width; + texh = (int)tex.height; + } + if (!wnd->m_metal_dc_dirty) + { + NSRect bounds = [wnd bounds]; + if (bounds.size.width < 1 || bounds.size.height < 1) return; + + wnd->m_metal_retina = retina_hint; + if (retina_hint) + { + bounds.size.width *= 2.0; + bounds.size.height *= 2.0; + } + + LIMIT_METAL_BOUNDS_SIZE(bounds.size) + + if (wnd->m_use_metal == 1) // direct + { + CAMetalLayer *layer = (CAMetalLayer *)[wnd layer]; + if (layer) + { + CGSize oldsc = layer.drawableSize; + if (oldsc.width != bounds.size.width || oldsc.height != bounds.size.height) + { + CGSize ns; + ns.width = bounds.size.width; + ns.height = bounds.size.height; + layer.drawableSize = ns; + layer.contentsScale = retina_hint ? 2.0 : 1.0; + } + else if (layer.contentsScale != (retina_hint ? 2.0 : 1.0)) + layer.contentsScale = retina_hint ? 2.0 : 1.0; + tex = [(wnd->m_metal_drawable = [layer nextDrawable]) texture]; + if (WDL_NOT_NORMALLY(!tex)) + { + NSLog(@"swell-cocoa: error creating metal texture for direct mode\n"); + } + wnd->m_metal_texture = tex; + } + } + else + { + const int want_w = ((int)bounds.size.width + 3) & ~3, want_h = ((int)bounds.size.height + 3)&~3; + // + if (!tex || texw < want_w || texh < want_h || texw > want_w * 2 || texh > want_h * 2) + { + [wnd->m_metal_texture release]; + MTLTextureDescriptor *textureDescriptor = [[__class_MTLTextureDescriptor alloc] init]; + textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; + textureDescriptor.width = want_w; + textureDescriptor.height = want_h; + tex = [wnd->m_metal_device newTextureWithDescriptor:textureDescriptor]; + wnd->m_metal_texture = tex; + if (WDL_NOT_NORMALLY(!tex)) + { + NSLog(@"swell-cocoa: error creating metal texture for full mode\n"); + } + + [textureDescriptor release]; + } + } + if (tex) + { + texw = (int)tex.width; + texh = (int)tex.height; + } + } + wnd->m_metal_dc_dirty=1; + + if (!tex) return; + + if (x<0) { w += x; srcbuf -= x; x=0; } + if (y<0) { h += y; srcbuf -= y*span; y=0; } + if (x+w > texw) w = texw-x; + if (y+h > texh) h = texh-y; + + if (w<1 || h<1) return; + + MTLRegion region = { { (NSUInteger)x, (NSUInteger)y, 0 }, {(NSUInteger)w,(NSUInteger)h, 1} }; + [tex replaceRegion:region mipmapLevel:0 withBytes:srcbuf bytesPerRow:span*4]; +} + +static WDL_TypedBuf s_metal_tmp; +void SWELL_Metal_Blit(void *_tex, const unsigned int *srcbuf, int x, int y, int w, int h, int span, bool retina_hint, bool use_alpha) +{ + if (!_tex) return; + SWELL_hwndChild *wnd = (SWELL_hwndChild *)_tex; + if (wnd->m_metal_dc_dirty) + { + if (retina_hint) + { + // source is retina + if (!wnd->m_metal_retina) + { + if (wnd->m_use_metal > 1) + { + NSLog(@"swell-cocoa: PERFORMANCE WARNING: metal non-retina drawing followed by retina drawing, calling code should be fixed.\n"); + if (WDL_NORMALLY(wnd->m_metal_texture)) + { + id tex = (id) wnd->m_metal_texture; + const int texw = (int)tex.width, texh = (int)tex.height; + const int oldspan = (texw+3)&~3, newspan = (texw*2+3)&~3; + unsigned int *dblframe = s_metal_tmp.ResizeOK(oldspan*texh + newspan*texh*2,false); + if (WDL_NOT_NORMALLY(!dblframe)) return; + unsigned int *frame = dblframe + newspan*texh*2; + if (SWELL_Metal_ReadTex(wnd,frame,0,0,texw,texh,oldspan)) + SWELL_fastDoubleUpImage(dblframe,frame,texw,texh,oldspan,newspan); + else + memset(dblframe,0,newspan*sizeof(int)*texh*2); + + if (x<0) { w += x; srcbuf -= x; x=0; } + if (y<0) { h += y; srcbuf -= y*span; y=0; } + if (x+w > texw*2) w=texw*2-x; + if (y+h > texh*2) h=texh*2-y; + + // blit our image into the full image + if (w>0 && h>0) + { + unsigned int *wr = dblframe + x + y*newspan; + const unsigned int *rd = srcbuf; + for (int i = 0; i < h; i ++) + { + if (use_alpha) + srcalphablend_line(wr, rd, w); + else + memcpy(wr, rd, w*sizeof(int)); + wr += newspan; + rd += span; + } + } + + // draw full image back + x=y=0; + w = texw*2; + h = texh*2; + span = newspan; + srcbuf = dblframe; + wnd->m_metal_dc_dirty = false; + use_alpha = false; + } + } + else + { + NSLog(@"swell-cocoa: DRAWING UNSUPPORTED: metal direct-mode non-retina drawing followed by retina drawing, will cause incorrect display.\n"); + } + } + } + else + { + // source is not retina + if (wnd->m_metal_retina) + { + // upsample to retina + const int newspan = (w*2+3)&~3; + unsigned int *p = s_metal_tmp.ResizeOK(newspan*h*2 + 32/4,false); + if (WDL_NOT_NORMALLY(!p)) return; + const UINT_PTR align = (UINT_PTR)p & 31; + if (align) p += 32-align; + SWELL_fastDoubleUpImage(p,srcbuf,w,h,span,newspan); + srcbuf = p; + w*=2; + h*=2; + x*=2; + y*=2; + span = newspan; + retina_hint = true; + } + } + + if (use_alpha && wnd->m_metal_texture) + { + id tex = (id) wnd->m_metal_texture; + const int texw = (int)tex.width, texh = (int)tex.height; + if (x<0) { w += x; srcbuf -= x; x=0; } + if (y<0) { h += y; srcbuf -= y*span; y=0; } + if (x+w > texw) w=texw-x; + if (y+h > texh) h=texh-y; + if (w<0 || h<0) return; + + const int lspan = (w+3)&~3; + static WDL_TypedBuf s_metal_tmp2; // if we are in one of the corner case modes (e.g. upsampled to retina), use this + unsigned int *p; + if (srcbuf >= s_metal_tmp.Get() && srcbuf < s_metal_tmp.Get()+s_metal_tmp.GetSize()) p=s_metal_tmp2.ResizeOK(lspan*h,false); + else p=s_metal_tmp.ResizeOK(lspan*h,false); + + if (WDL_NORMALLY(p) && SWELL_Metal_ReadTex(wnd,p,x,y,w,h,lspan)) + { + unsigned int *wr = p; + const unsigned int *rd = srcbuf; + for (int i = 0; i < h; i ++) + { + srcalphablend_line(wr, rd, w); + wr += lspan; + rd += span; + } + srcbuf = p; + span = lspan; + } + } + } + SWELL_Metal_WriteTex(wnd, srcbuf, x, y, w, h, span, retina_hint); +} + +void SWELL_Metal_FillRect(void *_tex, int x, int y, int w, int h, int colori) +{ + if (!_tex || w<1 || h<1) return; + + SWELL_hwndChild *wnd = (SWELL_hwndChild *)_tex; + const bool retina_hint = wnd->m_metal_dc_dirty && wnd->m_metal_retina; + if (retina_hint) + { + x*=2; y*=2; w*=2; h*=2; + } + + const int npix = w*h; + if (WDL_NOT_NORMALLY(npix < 0)) return; // overflow + + const unsigned int color = (unsigned int)colori; + unsigned int tmp[4096], *buf=tmp; + if (((unsigned int)npix*sizeof(int)) > sizeof(tmp)) + { + buf = s_metal_tmp.ResizeOK(npix); + if (WDL_NOT_NORMALLY(!buf)) return; + } + for (int i = 0; i < npix; i++) buf[i] = color; + SWELL_Metal_WriteTex(wnd,buf,x,y,w,h,w, retina_hint); +} + +WDL_PtrList s_mtl_dirty_list; + +#endif + +int SWELL_EnableMetal(HWND hwnd, int mode) +{ +#ifndef SWELL_NO_METAL + if (WDL_NOT_NORMALLY(!hwnd || ![(id)hwnd isKindOfClass:[SWELL_hwndChild class]])) return 0; + + SWELL_hwndChild *ch = (SWELL_hwndChild *)hwnd; + if (g_swell_nomiddleman_cocoa_override==0 && !ch->m_use_metal) + { + if (mode < 0) + { + ch->m_use_metal = mode; + [ch setWantsLayer:YES]; + if (mode == -1 && SWELL_GetOSXVersion() >= 0x1080) + [[ch layer] setDrawsAsynchronously:YES]; + } + else if (mode>0 && mtl_init()) + { + ch->m_use_metal = mode>1 ? 2 : mode; + [ch setWantsLayer:YES]; + InvalidateRect(hwnd,NULL,FALSE); + } + } + return ch->m_use_metal; +#else + return 0; +#endif +} + + +void swell_updateAllMetalDirty() // run from a timer, or called by UpdateWindow() +{ +#ifndef SWELL_NO_METAL + if (s_mtl_in_update || !s_mtl_dirty_list.GetSize()) return; + SWELL_AutoReleaseHelper arparp; + + s_mtl_in_update = true; + + NSWindow *lw = NULL; + bool lw_occluded = false; + + int x = s_mtl_dirty_list.GetSize(); + while (--x>=0) + { + SWELL_hwndChild *slf = s_mtl_dirty_list.Get(x); + if (!slf) break; // deleted out from under us?! + + NSWindow *w = [slf window]; + if (lw != w) + { + // this might only be needed on macOS12+ and/or M1 (could not duplicate on high sierra) + lw = w; + NSUInteger (*send_msg)(id, SEL) = (NSUInteger (*)(id, SEL))objc_msgSend; + lw_occluded = w && !(send_msg(w, @selector(occlusionState))&2); + } + if (lw_occluded) continue; + + const RECT tr = slf->m_metal_in_needref_rect; + s_mtl_dirty_list.Delete(x); + slf->m_metal_in_needref_list=false; + memset(&slf->m_metal_in_needref_rect,0,sizeof(slf->m_metal_in_needref_rect)); + + [slf swellDrawMetal:&tr]; + } + + for (int i = 0; ; i ++) + { + swell_metal_device_ctx *c = s_metal_devices.Enumerate(i); + if (!c) break; + c->present(); + } + + s_mtl_in_update = false; +#endif +} + + +#ifndef SWELL_NO_METAL + +void swell_addMetalDirty(SWELL_hwndChild *slf, const RECT *r, bool isReleaseDC) +{ + if (!slf) return; + #ifdef _DEBUG + if (!IsWindowVisible((HWND)slf)) + NSLog(@"swell-metal: addMetalDirty %@, fix caller\n",slf); + #endif + + if (isReleaseDC) + { + // just tag it dirty + } + else if (!r) + { + slf->m_metal_in_needref_rect.left = slf->m_metal_in_needref_rect.top = 0; + slf->m_metal_in_needref_rect.right = slf->m_metal_in_needref_rect.bottom = (1<<28); + } + else + { + if (r->right <= r->left || r->bottom <= r->top) return; // no rect + + WinUnionRect(&slf->m_metal_in_needref_rect,&slf->m_metal_in_needref_rect,r); + } + if (!slf->m_metal_in_needref_list) + { + slf->m_metal_in_needref_list=true; + + WDL_ASSERT(s_mtl_dirty_list.Find(slf)<0); + s_mtl_dirty_list.Add(slf); + } +} + +void swell_removeMetalDirty(SWELL_hwndChild *slf) +{ + WDL_ASSERT(!slf || (slf->m_metal_in_needref_list == (s_mtl_dirty_list.Find(slf)>=0))); + if (slf && slf->m_metal_in_needref_list) + { + slf->m_metal_in_needref_list=false; + memset(&slf->m_metal_in_needref_rect,0,sizeof(slf->m_metal_in_needref_rect)); + s_mtl_dirty_list.DeletePtr(slf); + } +} + +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlggen.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlggen.h new file mode 100644 index 0000000..2e4b0e6 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-dlggen.h @@ -0,0 +1,280 @@ +/* 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. + + DialogBox emulation is here. To declare the resource at a global level, use (in any source file that includes this file and resource.h): + + + #ifdef MAC + + + SWELL_DEFINE_DIALOG_RESOURCE_BEGIN(IDD_SOMEDIALOG,0,"Dialog Box Title",222,55,1.8) // width, height, scale (1.8 is usually good) + + BEGIN + DEFPUSHBUTTON "OK",IDOK,117,33,47,14 + CONTROL "Expand MIDI tracks to new REAPER tracks ",IDC_CHECK1, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,7,214,10 + CONTROL "Merge MIDI tempo map to project tempo map at ", + IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,19, + 214,10 + PUSHBUTTON "Cancel",IDCANCEL,168,33,50,14 + END + + SWELL_DEFINE_DIALOG_RESOURCE_END(IDD_SOMEDIALOG) + + + #endif + + + This file also provides functions to dynamically create controls in a view from a win32 resource script. + + + +*/ + + + + +#ifndef _SWELL_DLGGEN_H_ +#define _SWELL_DLGGEN_H_ + +#ifdef BEGIN +#undef BEGIN +#endif + +#ifdef END +#undef END +#endif + + +struct SWELL_DlgResourceEntry +{ + const char *str1; + int flag1; + const char *str2; + + int p1; // often used for ID + + // todo: see at runtime if some of these can be short instead of int (p2-p6 probably can, but not completely sure) -- i.e. do we use any + // big values in flags. note: it can't be unsigned short, because we want negative values. + int p2, p3, p4, p5, p6; // meaning is dependent on class +}; + + +#define BEGIN {NULL, +#define END }, + +#define PUSHBUTTON }, { "__SWELL_BUTTON", 0, +#define DEFPUSHBUTTON }, { "__SWELL_BUTTON", 1, +#define EDITTEXT }, { "__SWELL_EDIT", 0, "", +#define CTEXT }, { "__SWELL_LABEL", 0, +#define LTEXT }, { "__SWELL_LABEL", -1, +#define RTEXT }, { "__SWELL_LABEL", 1, +#define CONTROL }, { +#define COMBOBOX }, { "__SWELL_COMBO", 0, "", +#define GROUPBOX }, { "__SWELL_GROUP", 0, +#define CHECKBOX }, { "__SWELL_CHECKBOX", 0, +#define LISTBOX }, { "__SWELL_LISTBOX", 0, "", +#define ICON }, { "__SWELL_ICON", 0, (const char*)(INT_PTR) + +#define NOT + +// flags we may use +#define CBS_DROPDOWNLIST 0x0003L +#define CBS_DROPDOWN 0x0002L +#define CBS_SORT 0x0100L +#define ES_PASSWORD 0x0020L +#define ES_READONLY 0x0800L +#define ES_WANTRETURN 0x1000L +#define ES_NUMBER 0x2000L + +#define SS_LEFT 0 +#define SS_CENTER 0x1L +#define SS_RIGHT 0x2L +#define SS_BLACKRECT 0x4L +#define SS_BLACKFRAME (SS_BLACKRECT) +#define SS_LEFTNOWORDWRAP 0xCL +#define SS_ETCHEDHORZ 0x10L +#define SS_ETCHEDVERT 0x11L +#define SS_ETCHEDFRAME 0x12L +#define SS_TYPEMASK 0x1FL +#define SS_NOPREFIX 0x80L +#define SS_NOTIFY 0x0100L + +#define BS_LEFTTEXT 0x0020L + +#define BS_LEFT 0x100L +#define BS_CENTER 0x300L +#define BS_XPOSITION_MASK BS_CENTER + +#define BS_GROUPBOX 0x20000000 +#define BS_DEFPUSHBUTTON 0x10000000 +#define BS_PUSHBUTTON 0x8000000 + +#define LVS_LIST 0 /* 0x0003 */ +#define LVS_NOCOLUMNHEADER 0x4000 +#define LVS_NOSORTHEADER 0x8000 +#define LVS_REPORT 0x0001 +#define LVS_TYPEMASK 0x0003 +#define LVS_SINGLESEL 0x0004 +#define LVS_OWNERDATA 0x1000 +#define LVS_SORTASCENDING 0x0010 +#define LVS_SORTDESCENDING 0x0020 + +#define LBS_SORT 0x0002L +#define LBS_OWNERDRAWFIXED 0x0010L +#define LBS_EXTENDEDSEL 0x0800L + +#define ES_LEFT 0 +#define ES_CENTER 1 +#define ES_RIGHT 2 +#define ES_MULTILINE 4 +#define ES_AUTOHSCROLL 0x80 + +// flags we ignore +#define LVS_SHOWSELALWAYS 0 +#define LVS_SHAREIMAGELISTS 0 +#define ES_AUTOVSCROLL 0 +#define GROUP 0 +#define PBS_SMOOTH 0 +#define CBS_AUTOHSCROLL 0 +#define TBS_NOTICKS 0 +#define TBS_TOP 0 +#define TBS_BOTH 0 +#define LBS_NOINTEGRALHEIGHT 0 +#define TVS_HASLINES 0 +#define TVS_LINESATROOT 0 +#define TVS_SHOWSELALWAYS 0 +#define TVS_HASBUTTONS 0 +#define TVS_TRACKSELECT 0 +#define TVS_NONEVENHEIGHT 0 +#define TVS_NOTOOLTIPS 0 +#define BS_FLAT 0 +#define SS_SUNKEN 0 +#define BS_RIGHT 0 +#define WS_EX_STATICEDGE 0 +#define WS_EX_RIGHT 0 +#define SS_CENTERIMAGE 0 +#define WS_CLIPCHILDREN 0 + +// more ignore flags for vc11+ +#define LVS_ALIGNLEFT 0 /* 0x0800 */ + +#ifndef IDC_STATIC +#define IDC_STATIC 0 +#endif + + + + +#define SWELL_DLG_WS_CHILD 1 +#define SWELL_DLG_WS_RESIZABLE 2 +#define SWELL_DLG_WS_FLIPPED 4 +#define SWELL_DLG_WS_NOAUTOSIZE 8 +#define SWELL_DLG_WS_OPAQUE 16 +#define SWELL_DLG_WS_DROPTARGET 32 + +typedef struct SWELL_DialogResourceIndex +{ + const char *resid; + const char *title; + int windowTypeFlags; + void (*createFunc)(HWND, int); + int width,height; + struct SWELL_DialogResourceIndex *_next; +} SWELL_DialogResourceIndex; + +typedef struct SWELL_CursorResourceIndex +{ + const char *resid; + const char *resname; + POINT hotspot; + HCURSOR cachedCursor; + struct SWELL_CursorResourceIndex *_next; +} SWELL_CursorResourceIndex; + + +class SWELL_DialogRegHelper { + public: + SWELL_DialogResourceIndex m_rec; + SWELL_DialogRegHelper(SWELL_DialogResourceIndex **h, void (*cf)(HWND,int), int recid, int flags, + const char *titlestr, int wid, int hei, double xscale, double yscale) + { + if (recid) + { + m_rec.resid=MAKEINTRESOURCE(recid); + m_rec.title=titlestr; + m_rec.windowTypeFlags=flags; + m_rec.createFunc=cf; + m_rec.width=(int)(wid*xscale); + m_rec.height=(int)(hei*yscale); + m_rec._next=*h; + *h = &m_rec; + } + } +}; + +#ifdef _DEBUG + #include "../assocarray.h" + class SWELL_DialogRegValidator + { + public: + SWELL_DialogRegValidator(const SWELL_DlgResourceEntry *recs, size_t recs_sz) + { + if (recs_sz>1) + { + // check for duplicate IDs + WDL_IntKeyedArray tmp; + for (size_t x = 0; x < recs_sz; x ++) + { + const SWELL_DlgResourceEntry *list = recs + x; + const int idx = strncmp(list->str1,"__SWELL_",8) ? list->flag1 : list->p1; + if (idx != 0 && idx != -1) + { + WDL_ASSERT(!tmp.Get(idx)); + tmp.Insert(idx,true); + } + } + } + } + }; + #define SWELL_VALIDATE_DIALOG_RESOURCE(v,r) static SWELL_DialogRegValidator v(r+1, sizeof(r)/sizeof(r[0])-1); +#else + #define SWELL_VALIDATE_DIALOG_RESOURCE(v,r) +#endif + + +#define SWELL_DEFINE_DIALOG_RESOURCE_BEGIN(recid, flags, titlestr, wid, hei, scale) \ + static void SWELL__dlg_cf__##recid(HWND view, int wflags); \ + const float __swell_dlg_scale__##recid = (float) (scale); \ + static SWELL_DialogRegHelper __swell_dlg_helper_##recid(&SWELL_curmodule_dialogresource_head, SWELL__dlg_cf__##recid, recid,flags,titlestr,wid,hei,scale,(scale)*(SWELL_DLG_SCALE_AUTOGEN_YADJ)); \ + static const SWELL_DlgResourceEntry __swell_dlg_list__##recid[]={ + + +#define SWELL_DEFINE_DIALOG_RESOURCE_END(recid ) }; \ + SWELL_VALIDATE_DIALOG_RESOURCE( __swell_dlg_validator__##recid, __swell_dlg_list__##recid) \ + static void SWELL__dlg_cf__##recid(HWND view, int wflags) { \ + SWELL_MakeSetCurParms(__swell_dlg_scale__##recid,__swell_dlg_scale__##recid * (SWELL_DLG_SCALE_AUTOGEN_YADJ),0,0,view,false,!(wflags&SWELL_DLG_WS_NOAUTOSIZE)); \ + SWELL_GenerateDialogFromList(__swell_dlg_list__##recid+1,sizeof(__swell_dlg_list__##recid)/sizeof(__swell_dlg_list__##recid[0])-1); \ + } + +#ifndef SWELL_DLG_SCALE_AUTOGEN_YADJ +#define SWELL_DLG_SCALE_AUTOGEN_YADJ 1.0 +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-functions.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-functions.h new file mode 100644 index 0000000..c246f56 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-functions.h @@ -0,0 +1,1100 @@ +/* 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_API_DEFINED_ +#define _WDL_SWELL_H_API_DEFINED_ + +//////////////////////////////////////////// +/////////// FUNCTIONS +//////////////////////////////////////////// + +#ifndef SWELL_API_DEFINE + + +#if defined(SWELL_PROVIDED_BY_APP) || defined(SWELL_LOAD_SWELL_DYLIB) + #ifdef __cplusplus + #define SWELL_API_DEFINE(ret,func,parms) extern "C" ret (*func)parms; + #else + #define SWELL_API_DEFINE(ret,func,parms) extern ret (*func)parms; + #endif +#else +#define SWELL_API_DEFINE(ret,func,parms) ret func parms ; +#endif +#endif + +// when adding APIs, add it using: +// SWELL_API_DEFINE(void, function_name, (int parm, int parm2)) +// rather than: +// void function_name(int parm, int parm2); + +/* +** lstrcpyn: this is provided because strncpy is braindead (filling with zeroes, and not +** NULL terminating if the destination buffer is too short? ASKING for trouble..) +** lstrpcyn always null terminates the string and doesnt fill anything extra. +** +** note: wdlcstring.h defines lstrcpyn_safe(), which is preferred on win32 because of +** exception handling behavior. +*/ +SWELL_API_DEFINE(char *, lstrcpyn, (char *dest, const char *src, int l)) + + +/* +** MulDiv(): (parm1*parm2)/parm3 +** Implemented using long longs. +*/ +SWELL_API_DEFINE(int, MulDiv, (int, int, int)) + + +/* +** Sleep() sleeps for specified milliseconds. This maps to usleep, with a ms value of 0 +** usleeping for 100 microseconds. +*/ +SWELL_API_DEFINE(void, Sleep,(int ms)) + +/* +** GetTickCount() and timeGetTime() give you ms level timings via gettimeofday() or mach_getabsolutetime() or clock_gettime() +** +** NOTE: This doesn't map to time since system start (like in win32), so a wrap around +** is slightly more likely (i.e. even if you booted your system an hour ago it could happen). +*/ +SWELL_API_DEFINE(DWORD, GetTickCount,()) +#ifndef timeGetTime +#define timeGetTime() GetTickCount() +#endif + +/* +** GetFileTime() gets the file time of a file (FILE *), and converts it to the Windows time. +** +** NOTE: while it returns a 64 bit time value, it is only accurate to the second since thats +** what fstat() returns. Takes an int filedes rather than a HANDLE. +*/ +SWELL_API_DEFINE(BOOL, GetFileTime,(int filedes, FILETIME *lpCreationTime, FILETIME *lpLastAccessTime, FILETIME *lpLastWriteTime)) + +/* +** *PrivateProfileString/Int(): +** These are mostly thread-safe, mostly inter-process safe, and mostly module safe +** (i.e. writes from other modules) should be synchronized). +** +** NOTES: +** the filename used MUST be the full filename, unlike on Windows where files without paths go to +** C:/Windows, here they will be opened in the current directory. +** +** You can pass an empty string for filename to use ~/.libSwell.ini (that can be overridden by the app) +** +** It's probably not a good idea to push your luck with simultaneous writes from multiple +** modules in different threads/processes, but in theory it should work. +*/ +SWELL_API_DEFINE(BOOL, WritePrivateProfileString, (const char *appname, const char *keyname, const char *val, const char *fn)) +SWELL_API_DEFINE(DWORD, GetPrivateProfileString, (const char *appname, const char *keyname, const char *def, char *ret, int retsize, const char *fn)) +SWELL_API_DEFINE(int, GetPrivateProfileInt,(const char *appname, const char *keyname, int def, const char *fn)) +SWELL_API_DEFINE(BOOL, GetPrivateProfileStruct,(const char *appname, const char *keyname, void *buf, int bufsz, const char *fn)) +SWELL_API_DEFINE(BOOL, WritePrivateProfileStruct,(const char *appname, const char *keyname, const void *buf, int bufsz, const char *fn)) +SWELL_API_DEFINE(BOOL, WritePrivateProfileSection, (const char *appname, const char *strings, const char *fn)) +SWELL_API_DEFINE(DWORD, GetPrivateProfileSection, (const char *appname, char *strout, DWORD strout_len, const char *fn)) + +/* +** GetModuleFileName() +** Can pass NULL (exe filename) or a hInstance from DllMain or LoadLibrary +*/ +SWELL_API_DEFINE(DWORD, GetModuleFileName,(HINSTANCE hInst, char *fn, DWORD nSize)) + +#ifdef SWELL_TARGET_OSX +/* +** SWELL_CStringToCFString(): Creates a CFString/NSString * from a C string. This is mostly +** used internally but you may wish to use it as well (though none of the SWELL APIs take +** CFString/NSString. +*/ +SWELL_API_DEFINE(void *,SWELL_CStringToCFString,(const char *str)) +SWELL_API_DEFINE(void, SWELL_CFStringToCString, (const void *str, char *buf, int buflen)) +#endif + + +#ifdef PtInRect +#undef PtInRect +// #define funkiness because some Mac system headers define PtInRect as well. +#endif +#define PtInRect(r,p) SWELL_PtInRect(r,p) +SWELL_API_DEFINE(BOOL, SWELL_PtInRect,(const RECT *r, POINT p)) + +/* +** ShellExecute(): +** notes: +** action is ignored +** content1 can be a http:// or https:// URL +** content1 can be notepad/notepad.exe (maps to xdg-open or TextEdit.app) w/ content2 as a document +** content1 can be explorer.exe (optionally with /select) (maps to open finder or xdg-open) +** otherwise content1 can be an app w/ parameters in content2 +*/ +SWELL_API_DEFINE(BOOL, ShellExecute,(HWND hwndDlg, const char *action, const char *content1, const char *content2, const char *content3, int blah)) + +SWELL_API_DEFINE(int, MessageBox,(HWND hwndParent, const char *text, const char *caption, int type)) + + +/* +** GetOpenFileName() / GetSaveFileName() +** These are a different API because we didnt feel like reeimplimenting the full API. +** Extlist is something similar you'd pass getopenfilename, +** initialdir and initialfile are optional (and NULL means not set). +*/ + +// free() the result of this, if non-NULL. +// if allowmul is set, the multiple files are specified the same way GetOpenFileName() returns. +SWELL_API_DEFINE(char *,BrowseForFiles,(const char *text, const char *initialdir, + const char *initialfile, bool allowmul, const char *extlist)) + +// returns TRUE if file was chosen. +SWELL_API_DEFINE(bool, BrowseForSaveFile,(const char *text, const char *initialdir, const char *initialfile, const char *extlist, + char *fn, int fnsize)) + +// returns TRUE if path was chosen. +SWELL_API_DEFINE(bool, BrowseForDirectory,(const char *text, const char *initialdir, char *fn, int fnsize)) + +// can use this before calling BrowseForFiles or BrowseForSaveFile to use a template dialog +SWELL_API_DEFINE(void,BrowseFile_SetTemplate,(const char *dlgid, DLGPROC dlgProc, struct SWELL_DialogResourceIndex *reshead)) + + +// Note that window functions are generally NOT threadsafe. +// on macOS: all of these treat HWND as NSView and/or NSWindow (usually smartish about it) + +/* + * GetDlgItem() notes: +** macOS: GetDlgItem(hwnd,0) returns hwnd if hwnd is a NSView, or the contentview if hwnd is a NSWindow. +** macOS: note that this GetDlgItem will actually search a view hierarchy for the tagged view (win32 or -generic will only search immediate children) +*/ +SWELL_API_DEFINE(HWND, GetDlgItem,(HWND, int)) + + +SWELL_API_DEFINE(void, ShowWindow,(HWND, int)) + +SWELL_API_DEFINE(void, DestroyWindow,(HWND hwnd)) + +SWELL_API_DEFINE(BOOL, SWELL_GetGestureInfo, (LPARAM lParam, GESTUREINFO* gi)) + +SWELL_API_DEFINE(void, SWELL_HideApp,()) + +/* +** These should all work like their Win32 versions, though if idx=0 it gets/sets the +** value for the window. +** +** macOS: SetDlgItemText() for an edit control does NOT send a WM_COMMAND notification (win32 and -generic do) +*/ +SWELL_API_DEFINE(BOOL, SetDlgItemText,(HWND, int idx, const char *text)) +SWELL_API_DEFINE(BOOL, SetDlgItemInt,(HWND, int idx, int val, int issigned)) +SWELL_API_DEFINE(int, GetDlgItemInt,(HWND, int idx, BOOL *translated, int issigned)) +SWELL_API_DEFINE(BOOL, GetDlgItemText,(HWND, int idx, char *text, int textlen)) +SWELL_API_DEFINE(int, GetWindowTextLength,(HWND)) + +#ifndef GetWindowText +#define GetWindowText(hwnd,text,textlen) GetDlgItemText(hwnd,0,text,textlen) +#define SetWindowText(hwnd,text) SetDlgItemText(hwnd,0,text) +#endif + + +SWELL_API_DEFINE(void, CheckDlgButton,(HWND hwnd, int idx, int check)) +SWELL_API_DEFINE(int, IsDlgButtonChecked,(HWND hwnd, int idx)) +SWELL_API_DEFINE(void, EnableWindow,(HWND hwnd, int enable)) +SWELL_API_DEFINE(void, SetFocus,(HWND hwnd)) +SWELL_API_DEFINE(HWND, GetFocus,()) +SWELL_API_DEFINE(void, SetForegroundWindow,(HWND hwnd)) +SWELL_API_DEFINE(HWND, GetForegroundWindow,()) +#ifndef GetActiveWindow +#define GetActiveWindow() GetForegroundWindow() +#endif +#ifndef SetActiveWindow +#define SetActiveWindow(x) SetForegroundWindow(x) +#endif + +/* +** macOS: note that any HWND that returns YES to swellCapChangeNotify should do the following on +** destroy or dealloc: if (GetCapture()==(HWND)self) ReleaseCapture(); Failure to do so +** can cause a dealloc'd window to get messages sent to it. +*/ +SWELL_API_DEFINE(HWND, SetCapture,(HWND hwnd)) +SWELL_API_DEFINE(HWND, GetCapture,()) +SWELL_API_DEFINE(void, ReleaseCapture,()) + +/* +** IsChild() +** macOS: hwndChild must be a NSView, hwndParent can be a NSWindow or NSView. NSWindow level ownership/children are not detected. +*/ +SWELL_API_DEFINE(int, IsChild,(HWND hwndParent, HWND hwndChild)) + + +SWELL_API_DEFINE(HWND, GetParent,(HWND hwnd)) + +/* +** SetParent() +** macOS: hwnd must be a NSView, newPar can be either NSView or NSWindow. +*/ +SWELL_API_DEFINE(HWND, SetParent,(HWND hwnd, HWND newPar)) + +SWELL_API_DEFINE(HWND, GetWindow,(HWND hwnd, int what)) + +SWELL_API_DEFINE(BOOL, EnumWindows, (BOOL (*proc)(HWND, LPARAM), LPARAM lp)) + +SWELL_API_DEFINE(HWND,FindWindowEx,(HWND par, HWND lastw, const char *classname, const char *title)) + + +/* +** macOS note: common win32 code like this: +** RECT r; +** GetWindowRect(hwnd,&r); +** ScreenToClient(otherhwnd,(LPPOINT)&r); +** ScreenToClient(otherhwnd,((LPPOINT)&r)+1); +** does work, however be aware that in certain instances r.bottom may be less +** than r.top, due to flipped coordinates. SetWindowPos and other functions +** handle negative heights gracefully, and you should too. +** +** GetWindowContentViewRect gets the rectangle of the content view (pre-NCCALCSIZE etc) +*/ +SWELL_API_DEFINE(void, ClientToScreen,(HWND hwnd, POINT *p)) +SWELL_API_DEFINE(void, ScreenToClient,(HWND hwnd, POINT *p)) +SWELL_API_DEFINE(bool, GetWindowRect,(HWND hwnd, RECT *r)) +SWELL_API_DEFINE(void, GetWindowContentViewRect, (HWND hwnd, RECT *r)) +SWELL_API_DEFINE(void, GetClientRect,(HWND hwnd, RECT *r)) +SWELL_API_DEFINE(HWND, WindowFromPoint,(POINT p)) +SWELL_API_DEFINE(BOOL, WinOffsetRect, (LPRECT lprc, int dx, int dy)) +SWELL_API_DEFINE(BOOL, WinSetRect, (LPRECT lprc, int xLeft, int yTop, int xRight, int yBottom)) +SWELL_API_DEFINE(void,WinUnionRect,(RECT *out, const RECT *in1, const RECT *in2)) +SWELL_API_DEFINE(int,WinIntersectRect,(RECT *out, const RECT *in1, const RECT *in2)) + +SWELL_API_DEFINE(void, SetWindowPos,(HWND hwnd, HWND unused, int x, int y, int cx, int cy, int flags)) + +SWELL_API_DEFINE(int, SWELL_SetWindowLevel, (HWND hwnd, int newlevel)) + +SWELL_API_DEFINE(BOOL,InvalidateRect,(HWND hwnd, const RECT *r, int eraseBk)) + +SWELL_API_DEFINE(void,UpdateWindow,(HWND hwnd)) + + +/* +** GetWindowLong()/SetWindowLong() +** +** macOS: +** GWL_ID is supported for all objects that support the 'tag'/'setTag' methods, +** which would be controls and SWELL created windows/dialogs/controls. +** +** GWL_USERDATA is supported by SWELL created windows/dialogs/controls, using +** (int)getSwellUserData and setSwellUserData:(int). +** +** GWL_WNDPROC is supported by SWELL created windows/dialogs/controls, using +** (int)getSwellWindowProc and setSwellWindowProc:(int). +** +** DWL_DLGPROC is supported by SWELL-created dialogs now (it might work in windows/controls but isnt recommended) +** +** GWL_STYLE is only supported for NSButton. Currently the only flags supported are +** BS_AUTO3STATE (BS_AUTOCHECKBOX is returned but also ignored). +** +** indices of >= 0 and < 128 (32 integers) are supported for SWELL created +** windows/dialogs/controls, via (int)getSwellExtraData:(int)idx and +** setSwellExtraData:(int)idx value:(int)val . +** +** generic: indices of >= 0 && < 64*sizeof(INT_PTR) are supported +*/ +SWELL_API_DEFINE(LONG_PTR, GetWindowLong,(HWND hwnd, int idx)) +SWELL_API_DEFINE(LONG_PTR, SetWindowLong,(HWND hwnd, int idx, LONG_PTR val)) + + +SWELL_API_DEFINE(BOOL, ScrollWindow, (HWND hwnd, int xamt, int yamt, const RECT *lpRect, const RECT *lpClipRect)) + +/* +** GetProp() SetProp() RemoveProp() EnumPropsEx() +** Free your props otherwise they will leak. +** Restriction on what you can do in the PROPENUMPROCEX is similar to win32 +** (you can remove only the called prop, and can't add props within it). +** if the prop name is < (void *)65536 then it is treated as a short identifier. +*/ +SWELL_API_DEFINE(int, EnumPropsEx,(HWND, PROPENUMPROCEX, LPARAM)) +SWELL_API_DEFINE(HANDLE, GetProp, (HWND, const char *)) +SWELL_API_DEFINE(BOOL, SetProp, (HWND, const char *, HANDLE)) +SWELL_API_DEFINE(HANDLE, RemoveProp, (HWND, const char *)) + + +/* +** IsWindowVisible() +** macOS: +** if hwnd is a NSView, returns !isHiddenOrHasHiddenAncestor +** if hwnd is a NSWindow returns isVisible +** otherwise returns TRUE if non-null hwnd +*/ +SWELL_API_DEFINE(bool, IsWindowVisible,(HWND hwnd)) + +// IsWindow() +// probably best avoided. +// macOS: very costly (compared to win32) -- enumerates all windows, searches for hwnd +// generic: may not be implemented +SWELL_API_DEFINE(bool, IsWindow, (HWND hwnd)) + + +/* +** SetTimer/KillTimer(): +** Notes: +** The timer API may be threadsafe though it is highly untested. It is safest to only set +** timers from the main thread. +** +** Kill all timers for a window using KillTimer(hwnd,-1); +** +** macOS: Note also that the mechanism for sending timers is SWELL_Timer:(id). +** You MUST kill all timers for a window before destroying it. Note that SWELL created +** windows/dialogs/controls automatically do this, but if you use SetTimer() on a NSView * +** or NSWindow * directly, then you should kill all timers in -dealloc. +*/ +SWELL_API_DEFINE(UINT_PTR, SetTimer,(HWND hwnd, UINT_PTR timerid, UINT rate, TIMERPROC tProc)) +SWELL_API_DEFINE(BOOL, KillTimer,(HWND hwnd, UINT_PTR timerid)) + +#ifdef SWELL_TARGET_OSX +/* +** SendMessage can/should now be used with CB_* etc. +** macOS: Combo boxes may be implemented using a NSComboBox or NSPopUpButton depending on the style. +*/ +SWELL_API_DEFINE(int, SWELL_CB_AddString,(HWND hwnd, int idx, const char *str)) +SWELL_API_DEFINE(void, SWELL_CB_SetCurSel,(HWND hwnd, int idx, int sel)) +SWELL_API_DEFINE(int, SWELL_CB_GetCurSel,(HWND hwnd, int idx)) +SWELL_API_DEFINE(int, SWELL_CB_GetNumItems,(HWND hwnd, int idx)) +SWELL_API_DEFINE(void, SWELL_CB_SetItemData,(HWND hwnd, int idx, int item, LONG_PTR data)) +SWELL_API_DEFINE(LONG_PTR, SWELL_CB_GetItemData,(HWND hwnd, int idx, int item)) +SWELL_API_DEFINE(void, SWELL_CB_Empty,(HWND hwnd, int idx)) +SWELL_API_DEFINE(int, SWELL_CB_InsertString,(HWND hwnd, int idx, int pos, const char *str)) +SWELL_API_DEFINE(int, SWELL_CB_GetItemText,(HWND hwnd, int idx, int item, char *buf, int bufsz)) +SWELL_API_DEFINE(void, SWELL_CB_DeleteString,(HWND hwnd, int idx, int wh)) +SWELL_API_DEFINE(int, SWELL_CB_FindString,(HWND hwnd, int idx, int startAfter, const char *str, bool exact)) + + +/* +** Trackbar API +** You can/should now use SendMessage with TBM_* instead. +*/ +SWELL_API_DEFINE(void, SWELL_TB_SetPos,(HWND hwnd, int idx, int pos)) +SWELL_API_DEFINE(void, SWELL_TB_SetRange,(HWND hwnd, int idx, int low, int hi)) +SWELL_API_DEFINE(int, SWELL_TB_GetPos,(HWND hwnd, int idx)) +SWELL_API_DEFINE(void, SWELL_TB_SetTic,(HWND hwnd, int idx, int pos)) + + +#endif + +/* +** ListViews -- in owner data mode only LVN_GETDISPINFO is required (LVN_ODFINDITEM is never sent) +*/ +SWELL_API_DEFINE(void, ListView_SetExtendedListViewStyleEx,(HWND h, int mask, int style)) +SWELL_API_DEFINE(void, ListView_InsertColumn,(HWND h, int pos, const LVCOLUMN *lvc)) +SWELL_API_DEFINE(bool, ListView_DeleteColumn,(HWND h, int pos)) +SWELL_API_DEFINE(void, ListView_SetColumn,(HWND h, int pos, const LVCOLUMN *lvc)) +SWELL_API_DEFINE(void, ListView_GetColumn,(HWND h, int pos, LVCOLUMN *lvc)) +SWELL_API_DEFINE(int, ListView_GetColumnWidth,(HWND h, int pos)) +SWELL_API_DEFINE(int, ListView_InsertItem,(HWND h, const LVITEM *item)) +SWELL_API_DEFINE(void, ListView_SetItemText,(HWND h, int ipos, int cpos, const char *txt)) +SWELL_API_DEFINE(bool, ListView_SetItem,(HWND h, LVITEM *item)) +SWELL_API_DEFINE(int, ListView_GetNextItem,(HWND h, int istart, int flags)) +SWELL_API_DEFINE(bool, ListView_GetItem,(HWND h, LVITEM *item)) +SWELL_API_DEFINE(int, ListView_GetItemState,(HWND h, int ipos, UINT mask)) +SWELL_API_DEFINE(void, ListView_DeleteItem,(HWND h, int ipos)) +SWELL_API_DEFINE(void, ListView_DeleteAllItems,(HWND h)) +SWELL_API_DEFINE(int, ListView_GetSelectedCount,(HWND h)) +SWELL_API_DEFINE(int, ListView_GetItemCount,(HWND h)) +SWELL_API_DEFINE(int, ListView_GetSelectionMark,(HWND h)) +SWELL_API_DEFINE(void, ListView_SetColumnWidth,(HWND h, int colpos, int wid)) +SWELL_API_DEFINE(bool, ListView_SetItemState,(HWND h, int item, UINT state, UINT statemask)) +SWELL_API_DEFINE(void, ListView_RedrawItems,(HWND h, int startitem, int enditem)) +SWELL_API_DEFINE(void, ListView_SetItemCount,(HWND h, int cnt)) +#ifdef ListView_SetItemCountEx +#undef ListView_SetItemCountEx +#endif +#define ListView_SetItemCountEx(list,cnt,flags) ListView_SetItemCount(list,cnt) + +SWELL_API_DEFINE(void, ListView_EnsureVisible,(HWND h, int i, BOOL pok)) +SWELL_API_DEFINE(void, ListView_SetImageList,(HWND h, HIMAGELIST imagelist, int which)) +SWELL_API_DEFINE(int, ListView_SubItemHitTest,(HWND h, LVHITTESTINFO *pinf)) +SWELL_API_DEFINE(void, ListView_GetItemText,(HWND hwnd, int item, int subitem, char *text, int textmax)) +SWELL_API_DEFINE(void, ListView_SortItems,(HWND hwnd, PFNLVCOMPARE compf, LPARAM parm)) +SWELL_API_DEFINE(bool, ListView_Scroll,(HWND h, int xscroll, int yscroll)) +SWELL_API_DEFINE(int, ListView_GetTopIndex,(HWND h)) +SWELL_API_DEFINE(int, ListView_GetCountPerPage,(HWND h)) +SWELL_API_DEFINE(BOOL, ListView_SetColumnOrderArray,(HWND h, int cnt, int* arr)) +SWELL_API_DEFINE(BOOL, ListView_GetColumnOrderArray,(HWND h, int cnt, int* arr)) +SWELL_API_DEFINE(HWND, ListView_GetHeader,(HWND h)) +SWELL_API_DEFINE(int, Header_GetItemCount,(HWND h)) +SWELL_API_DEFINE(BOOL, Header_GetItem,(HWND h, int col, HDITEM* hi)) +SWELL_API_DEFINE(BOOL, Header_SetItem,(HWND h, int col, HDITEM* hi)) + + // NOTE: the Cocoa versions of these functions behave differently than swell-generic and Windows: + // they return the absolute (unscrolled) coordinates. In order to behave properly, the caller should + // use ClientToScreen in order to get to screen coordinates, and then convert those as desired. +SWELL_API_DEFINE(bool, ListView_GetItemRect,(HWND h, int item, RECT *r, int code)) +SWELL_API_DEFINE(bool, ListView_GetSubItemRect,(HWND h, int item, int subitem, int code, RECT *r)) + // NOTE: Cocoa version takes absolute (unscrolled) coordinates. ScreenToClient(listview) will convert screen + // coordinates to the correct coorindates +SWELL_API_DEFINE(int, ListView_HitTest,(HWND h, LVHITTESTINFO *pinf)) + +SWELL_API_DEFINE(int, SWELL_GetListViewHeaderHeight, (HWND h)) + +#ifndef ImageList_Create +#define ImageList_Create(x,y,a,b,c) ImageList_CreateEx(); +#endif +SWELL_API_DEFINE(HIMAGELIST, ImageList_CreateEx,()) +SWELL_API_DEFINE(BOOL, ImageList_Remove, (HIMAGELIST list, int idx)) +SWELL_API_DEFINE(int, ImageList_ReplaceIcon,(HIMAGELIST list, int offset, HICON image)) +SWELL_API_DEFINE(int, ImageList_Add,(HIMAGELIST list, HBITMAP image, HBITMAP mask)) +SWELL_API_DEFINE(void, ImageList_Destroy, (HIMAGELIST)) +/* +** TabCtrl api. +*/ +SWELL_API_DEFINE(int, TabCtrl_GetItemCount,(HWND hwnd)) +SWELL_API_DEFINE(BOOL, TabCtrl_DeleteItem,(HWND hwnd, int idx)) +SWELL_API_DEFINE(int, TabCtrl_InsertItem,(HWND hwnd, int idx, TCITEM *item)) +SWELL_API_DEFINE(int, TabCtrl_SetCurSel,(HWND hwnd, int idx)) +SWELL_API_DEFINE(int, TabCtrl_GetCurSel,(HWND hwnd)) +SWELL_API_DEFINE(BOOL, TabCtrl_AdjustRect, (HWND hwnd, BOOL fLarger, RECT *r)) + +/* +** TreeView +*/ + +SWELL_API_DEFINE(HTREEITEM, TreeView_InsertItem, (HWND hwnd, TV_INSERTSTRUCT *ins)) +SWELL_API_DEFINE(BOOL, TreeView_Expand,(HWND hwnd, HTREEITEM item, UINT flag)) +SWELL_API_DEFINE(HTREEITEM, TreeView_GetSelection,(HWND hwnd)) +SWELL_API_DEFINE(void, TreeView_DeleteItem,(HWND hwnd, HTREEITEM item)) +SWELL_API_DEFINE(void, TreeView_DeleteAllItems,(HWND hwnd)) +SWELL_API_DEFINE(void, TreeView_SelectItem,(HWND hwnd, HTREEITEM item)) +SWELL_API_DEFINE(void, TreeView_EnsureVisible,(HWND hwnd, HTREEITEM item)) +SWELL_API_DEFINE(BOOL, TreeView_GetItem,(HWND hwnd, LPTVITEM pitem)) +SWELL_API_DEFINE(BOOL, TreeView_SetItem,(HWND hwnd, LPTVITEM pitem)) +SWELL_API_DEFINE(HTREEITEM, TreeView_HitTest, (HWND hwnd, TVHITTESTINFO *hti)) +SWELL_API_DEFINE(BOOL, TreeView_SetIndent,(HWND hwnd, int indent)) + +SWELL_API_DEFINE(HTREEITEM, TreeView_GetParent, (HWND hwnd, HTREEITEM item)) +SWELL_API_DEFINE(HTREEITEM, TreeView_GetChild, (HWND hwnd, HTREEITEM item)) +SWELL_API_DEFINE(HTREEITEM, TreeView_GetNextSibling, (HWND hwnd, HTREEITEM item)) +SWELL_API_DEFINE(HTREEITEM, TreeView_GetRoot, (HWND hwnd)) + +SWELL_API_DEFINE(void,TreeView_SetBkColor,(HWND hwnd, int color)) +SWELL_API_DEFINE(void,TreeView_SetTextColor,(HWND hwnd, int color)) +SWELL_API_DEFINE(void,ListView_SetBkColor,(HWND hwnd, int color)) +SWELL_API_DEFINE(void,ListView_SetTextBkColor,(HWND hwnd, int color)) +SWELL_API_DEFINE(void,ListView_SetTextColor,(HWND hwnd, int color)) +SWELL_API_DEFINE(void,ListView_SetGridColor,(HWND hwnd, int color)) +SWELL_API_DEFINE(void,ListView_SetSelColors,(HWND hwnd, int *colors, int ncolors)) + +/* +** These are deprecated macOS-only functions for launching a modal window but still running +** your own code. In general use DialogBox with a timer if needed instead. +*/ +SWELL_API_DEFINE(void *, SWELL_ModalWindowStart,(HWND hwnd)) +SWELL_API_DEFINE(bool, SWELL_ModalWindowRun,(void *ctx, int *ret)) // returns false and puts retval in *ret when done +SWELL_API_DEFINE(void, SWELL_ModalWindowEnd,(void *ctx)) +SWELL_API_DEFINE(void, SWELL_CloseWindow,(HWND hwnd)) + +/* +** Menu functions +** macOS: HMENU is a NSMenu *. +*/ +SWELL_API_DEFINE(HMENU, CreatePopupMenu,()) +SWELL_API_DEFINE(HMENU, CreatePopupMenuEx,(const char *title)) +SWELL_API_DEFINE(void, DestroyMenu,(HMENU hMenu)) +SWELL_API_DEFINE(int, AddMenuItem,(HMENU hMenu, int pos, const char *name, int tagid)) +SWELL_API_DEFINE(HMENU, GetSubMenu,(HMENU hMenu, int pos)) +SWELL_API_DEFINE(int, GetMenuItemCount,(HMENU hMenu)) +SWELL_API_DEFINE(int, GetMenuItemID,(HMENU hMenu, int pos)) +SWELL_API_DEFINE(bool, SetMenuItemModifier,(HMENU hMenu, int idx, int flag, int code, unsigned int mask)) +SWELL_API_DEFINE(bool, SetMenuItemText,(HMENU hMenu, int idx, int flag, const char *text)) +SWELL_API_DEFINE(bool, EnableMenuItem,(HMENU hMenu, int idx, int en)) +SWELL_API_DEFINE(bool, DeleteMenu,(HMENU hMenu, int idx, int flag)) +SWELL_API_DEFINE(bool, CheckMenuItem,(HMENU hMenu, int idx, int chk)) +SWELL_API_DEFINE(void, InsertMenuItem,(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi)) +SWELL_API_DEFINE(void,SWELL_InsertMenu,(HMENU menu, int pos, unsigned int flag, UINT_PTR idx, const char *str)) +#ifdef InsertMenu +#undef InsertMenu +#endif +#define InsertMenu SWELL_InsertMenu + +SWELL_API_DEFINE(BOOL, GetMenuItemInfo,(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi)) +SWELL_API_DEFINE(BOOL, SetMenuItemInfo,(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi)) +SWELL_API_DEFINE(void, DrawMenuBar,(HWND)) + + + +/* +** LoadMenu() +** Loads a menu created with SWELL_DEFINE_MENU_RESOURCE_BEGIN(), see swell-menugen.h +** Notes: the hinst parameter is ignored, the menu must have been defined in the same +** module (executable or shared library) as the LoadMenu call. If you wish to load a +** menu from another module, get its SWELL_curmodule_menuresource_head and pass it to +** SWELL_LoadMenu directly. +*/ +#ifndef LoadMenu +#define LoadMenu(hinst,resid) SWELL_LoadMenu(SWELL_curmodule_menuresource_head,(resid)) +#endif +SWELL_API_DEFINE(HMENU, SWELL_LoadMenu,(struct SWELL_MenuResourceIndex *head, const char *resid)) + +/* +** TrackPopupMenu +** Notes: the rectangle is ignored, and resvd should always be 0. +*/ +SWELL_API_DEFINE(int, TrackPopupMenu,(HMENU hMenu, int flags, int xpos, int ypos, int resvd, HWND hwnd, const RECT *r)) + +/* +** SWELL_SetMenuDestination: set the action destination for all items and subitems in a menu +** macOS only, TrackPopupMenu and SetMenu use this internally, but it may be useful. +*/ +SWELL_API_DEFINE(void, SWELL_SetMenuDestination,(HMENU menu, HWND hwnd)) + +/* +** SWELL_DuplicateMenu: +** Copies an entire menu. +*/ +SWELL_API_DEFINE(HMENU, SWELL_DuplicateMenu,(HMENU menu)) + +/* +** SetMenu()/GetMenu() +** macOS: These work on SWELL created NSWindows, or objective C objects that respond to +** swellSetMenu:(HMENU) and swellGetMenu. SWELL windows will automatically set the +** application menu via NSApp setMainMenu: when activated. +*/ +SWELL_API_DEFINE(BOOL, SetMenu,(HWND hwnd, HMENU menu)) +SWELL_API_DEFINE(HMENU, GetMenu,(HWND hwnd)) + +/* +** SWELL_SetDefaultWindowMenu()/SWELL_GetDefaultWindowMenu() +** macOS: these set an internal flag for the default window menu, which will be set +** when switching to a window that has no menu set. Set this to your application's +** main menu. +** +** generic: these set the internal state, which is currently unused +*/ +SWELL_API_DEFINE(HMENU, SWELL_GetDefaultWindowMenu,()) +SWELL_API_DEFINE(void, SWELL_SetDefaultWindowMenu,(HMENU)) +SWELL_API_DEFINE(HMENU, SWELL_GetDefaultModalWindowMenu,()) +SWELL_API_DEFINE(void, SWELL_SetDefaultModalWindowMenu,(HMENU)) +SWELL_API_DEFINE(HMENU, SWELL_GetCurrentMenu,()) +SWELL_API_DEFINE(void, SWELL_SetCurrentMenu,(HMENU)) + + + +/* +** SWELL dialog box/control/window/child dialog/etc creation +** DialogBox(), DialogBoxParam(), CreateDialog(), and CreateDialogParam() +** +** Notes: +** hInstance parameters are ignored. If you wish to load a dialog resource from another +** module (shared library/executable), you should get its SWELL_curmodule_dialogresource_head +** via your own mechanism and pass it as the first parameter of SWELL_DialogBox or whichever API. +** +** If you are using CreateDialog() and creating a child window, you can use a resource ID of +** 0, which creates an opaque child window. Instead of passing a DLGPROC, you should pass a +** (WNDPROC) routine that retuns LRESULT (and cast it to DLGPROC). +** +*/ + +#ifndef DialogBox +#define DialogBox(hinst, resid, par, dlgproc) SWELL_DialogBox(SWELL_curmodule_dialogresource_head,(resid),par,dlgproc,0) +#define DialogBoxParam(hinst, resid, par, dlgproc, param) SWELL_DialogBox(SWELL_curmodule_dialogresource_head,(resid),par,dlgproc,param) +#define CreateDialog(hinst,resid,par,dlgproc) SWELL_CreateDialog(SWELL_curmodule_dialogresource_head,(resid),par,dlgproc,0) +#define CreateDialogParam(hinst,resid,par,dlgproc,param) SWELL_CreateDialog(SWELL_curmodule_dialogresource_head,(resid),par,dlgproc,param) +#endif +SWELL_API_DEFINE(int, SWELL_DialogBox,(struct SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent, DLGPROC dlgproc, LPARAM param)) +SWELL_API_DEFINE(HWND, SWELL_CreateDialog,(struct SWELL_DialogResourceIndex *reshead, const char *resid, HWND parent, DLGPROC dlgproc, LPARAM param)) + + +/* +** SWELL_RegisterCustomControlCreator(), SWELL_UnregisterCustomControlCreator() +** Notes: +** Pass these a callback function that can create custom controls based on classname. +*/ + +SWELL_API_DEFINE(void, SWELL_RegisterCustomControlCreator,(SWELL_ControlCreatorProc proc)) +SWELL_API_DEFINE(void, SWELL_UnregisterCustomControlCreator,(SWELL_ControlCreatorProc proc)) + +SWELL_API_DEFINE(LRESULT, DefWindowProc,(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)) + +SWELL_API_DEFINE(void, EndDialog,(HWND, int)) + + +SWELL_API_DEFINE(int,SWELL_GetDefaultButtonID,(HWND hwndDlg, bool onlyIfEnabled)) + + +/* +** SendMessage() +** Notes: +** LIMITATION - SendMessage should only be used from the same thread that the window/view +** was created in. Cross-thread use SHOULD BE AVOIDED. It may work, but it may blow up. +** PostMessage (below) can be used in certain instances for asynchronous notifications. +*/ +SWELL_API_DEFINE(LRESULT, SendMessage,(HWND, UINT, WPARAM, LPARAM)) + +#ifndef SendDlgItemMessage +#define SendDlgItemMessage(hwnd,idx,msg,wparam,lparam) SendMessage(GetDlgItem(hwnd,idx),msg,wparam,lparam) +#endif + +/* +** SWELL_BroadcastMessage() +** sends a message to all top-level windows +*/ +SWELL_API_DEFINE(void,SWELL_BroadcastMessage,(UINT, WPARAM, LPARAM)) + +/* +** PostMessage() +** Notes: +** Queues a message into the application message queue. Note that you should only ever +** send messages to destinations that were created from the main thread. They will be +** processed later from a timer (in the main thread). +** When a window is destroyed any outstanding messages will be discarded for it. +*/ +SWELL_API_DEFINE(BOOL, PostMessage,(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)) + +/* +** SWELL_MessageQueue_Flush(): +** Notes: +** Processes all messages in the message queue. ONLY call from the main thread. +*/ +SWELL_API_DEFINE(void, SWELL_MessageQueue_Flush,()) + +/* +** SWELL_MessageQueue_Clear(): +** Notes: +** Discards all messages from the message queue if h is NULL, otherwise discards all messages +** to h. +*/ +SWELL_API_DEFINE(void, SWELL_MessageQueue_Clear,(HWND h)) + + + +/* +** keyboard/mouse support +*/ + +/* +** SWELL_MacKeyToWindowsKey() +** Pass a keyboard NSEvent *, and it will return a windows VK_ keycode (or ascii), and set flags, +** including (possibly) FSHIFT, FCONTROL (apple key), FALT, and FVIRTKEY. The ctrl key is not checked, +** as SWELL generally encourages this to be used soley for a right mouse button (as modifier). +** flags may also include 0x1000000 for arrow keys and home/end (matching the lParam behavior of win32), +** or for numeric keypad enter +*/ +#ifdef SWELL_TARGET_OSX +SWELL_API_DEFINE(int, SWELL_MacKeyToWindowsKey,(void *nsevent, int *flags)) + + // ex is the same as normal, except if mode=1 it does more processing of raw keys w/ modifiers + // and also if nsevent==NULL current event is used +SWELL_API_DEFINE(int, SWELL_MacKeyToWindowsKeyEx,(void *nsevent, int *flags, int mode)) +#endif +SWELL_API_DEFINE(int,SWELL_KeyToASCII,(int wParam, int lParam, int *newflags)) + + +/* +** GetAsyncKeyState() +** macOS: only supports VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, VK_SHIFT, VK_MENU, VK_CONTROL (apple/command key), VK_LWIN (control key) +** GDK: only supports VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, VK_SHIFT, VK_MENU, VK_CONTROL, VK_LWIN +*/ +SWELL_API_DEFINE(WORD, GetAsyncKeyState,(int key)) + +/* +** GetCursorPos(), GetMessagePos() +** GetMessagePos() currently returns the same coordinates as GetCursorPos() +*/ +SWELL_API_DEFINE(void, GetCursorPos,(POINT *pt)) +SWELL_API_DEFINE(DWORD, GetMessagePos,()) + +/* +** LoadCursor(). +** Notes: hinstance parameter ignored, supports loading some of the predefined values (e.g. IDC_SIZEALL) +** and cursors registered into the main module +** macOS: HCURSOR = NSCursor * +** see also: SWELL_LoadCursorFromFile +*/ +SWELL_API_DEFINE(HCURSOR, SWELL_LoadCursor,(const char *idx)) +#ifndef LoadCursor +#define LoadCursor(a,x) SWELL_LoadCursor(x) +#endif + +/* +** SetCursor() +** macOS: can cast a NSCursor* to HCURSOR if desired +*/ +#ifdef SetCursor +#undef SetCursor +#endif +#define SetCursor(x) SWELL_SetCursor(x) +SWELL_API_DEFINE(void, SWELL_SetCursor,(HCURSOR curs)) + + +#ifdef GetCursor +#undef GetCursor +#endif +#define GetCursor SWELL_GetCursor + +#ifdef ShowCursor +#undef ShowCursor +#endif +#define ShowCursor SWELL_ShowCursor + +#ifdef SetCursorPos +#undef SetCursorPos +#endif +#define SetCursorPos SWELL_SetCursorPos + +#ifdef ScrollWindowEx +#undef ScrollWindowEx +#endif +#define ScrollWindowEx(a,b,c,d,e,f,g,h) ScrollWindow(a,b,c,d,e) + + +/* +** SWELL_EnableRightClickEmulate() +** macOS only +** Globally enable or disable emulating mouse right-click using control+left-click +*/ +SWELL_API_DEFINE(void, SWELL_EnableRightClickEmulate, (BOOL enable)) + + +/* +** GetCursor() gets the actual system cursor, +** SWELL_GetLastSetCursor() gets the last cursor set via SWELL (if they differ than some other window must have changed the cursor) +*/ +SWELL_API_DEFINE(HCURSOR, SWELL_GetCursor,()) +SWELL_API_DEFINE(HCURSOR, SWELL_GetLastSetCursor,()) + +SWELL_API_DEFINE(bool, SWELL_IsCursorVisible, ()) +SWELL_API_DEFINE(int, SWELL_ShowCursor, (BOOL bShow)) +SWELL_API_DEFINE(BOOL, SWELL_SetCursorPos, (int X, int Y)) + +/* +** SWELL_GetViewPort +** Gets screen information, for the screen that contains sourcerect. if wantWork is set +** it excluses the menu bar etc. +*/ +SWELL_API_DEFINE(void, SWELL_GetViewPort,(RECT *r, const RECT *sourcerect, bool wantWork)) + +/* +** Clipboard API +** macOS: setting multiple types may not be supported +** GDK: only CF_TEXT is shared with system, other types are stored internally +*/ +SWELL_API_DEFINE(bool, OpenClipboard,(HWND hwndDlg)) +SWELL_API_DEFINE(void, CloseClipboard,()) +SWELL_API_DEFINE(HANDLE, GetClipboardData,(UINT type)) + +SWELL_API_DEFINE(void, EmptyClipboard,()) +SWELL_API_DEFINE(void, SetClipboardData,(UINT type, HANDLE h)) +SWELL_API_DEFINE(UINT, RegisterClipboardFormat,(const char *desc)) +SWELL_API_DEFINE(UINT, EnumClipboardFormats,(UINT lastfmt)) + +SWELL_API_DEFINE(HANDLE, GlobalAlloc,(int flags, int sz)) +SWELL_API_DEFINE(void *, GlobalLock,(HANDLE h)) +SWELL_API_DEFINE(int, GlobalSize,(HANDLE h)) +SWELL_API_DEFINE(void, GlobalUnlock,(HANDLE h)) +SWELL_API_DEFINE(void, GlobalFree,(HANDLE h)) + + +SWELL_API_DEFINE(HANDLE,CreateThread,(void *TA, DWORD stackSize, DWORD (*ThreadProc)(LPVOID), LPVOID parm, DWORD cf, DWORD *tidOut)) +SWELL_API_DEFINE(HANDLE,CreateEvent,(void *SA, BOOL manualReset, BOOL initialSig, const char *ignored)) +SWELL_API_DEFINE(HANDLE,CreateEventAsSocket,(void *SA, BOOL manualReset, BOOL initialSig, const char *ignored)) + + +#ifdef _beginthreadex +#undef _beginthreadex +#endif +#define _beginthreadex(a,b,c,d,e,f) ((UINT_PTR)CreateThread(a,b,(unsigned (*)(LPVOID))(c),d,e,(DWORD*)(f))) + +SWELL_API_DEFINE(DWORD,GetCurrentThreadId,()) +SWELL_API_DEFINE(DWORD,WaitForSingleObject,(HANDLE hand, DWORD msTO)) +SWELL_API_DEFINE(DWORD,WaitForAnySocketObject,(int numObjs, HANDLE *objs, DWORD msTO)) +SWELL_API_DEFINE(BOOL,CloseHandle,(HANDLE hand)) +SWELL_API_DEFINE(BOOL,SetThreadPriority,(HANDLE evt, int prio)) +SWELL_API_DEFINE(BOOL,SetEvent,(HANDLE evt)) +SWELL_API_DEFINE(BOOL,ResetEvent,(HANDLE evt)) + +#ifdef SWELL_TARGET_OSX +SWELL_API_DEFINE(void,SWELL_EnsureMultithreadedCocoa,()) +SWELL_API_DEFINE(void *, SWELL_InitAutoRelease,()) +SWELL_API_DEFINE(void, SWELL_QuitAutoRelease,(void *p)) +SWELL_API_DEFINE(int,SWELL_TerminateProcess,(HANDLE hand)) +SWELL_API_DEFINE(HANDLE,SWELL_CreateProcessIO,(const char *exe, int nparams, const char **params, bool redirectIO)) +SWELL_API_DEFINE(int,SWELL_ReadWriteProcessIO,(HANDLE, int w/*stdin,stdout,stderr*/, char *buf, int bufsz)) +#else +SWELL_API_DEFINE(HANDLE,SWELL_CreateProcessFromPID,(int pid)) +#endif + +SWELL_API_DEFINE(HANDLE,SWELL_CreateProcess,(const char *exe, int nparams, const char **params)) +SWELL_API_DEFINE(int,SWELL_GetProcessExitCode,(HANDLE hand)) + + +SWELL_API_DEFINE(HINSTANCE,LoadLibraryGlobals,(const char *fileName, bool symbolsAsGlobals)) +SWELL_API_DEFINE(HINSTANCE,LoadLibrary,(const char *fileName)) +SWELL_API_DEFINE(void *,GetProcAddress,(HINSTANCE hInst, const char *procName)) +SWELL_API_DEFINE(BOOL,FreeLibrary,(HINSTANCE hInst)) + +SWELL_API_DEFINE(void*,SWELL_GetBundle,(HINSTANCE hInst)) + +/* +** SWELL_CreateMemContext() +** Creates a memory context (that you can get the bits for, below) +** hdc is ignored +*/ +SWELL_API_DEFINE(HDC, SWELL_CreateMemContext,(HDC hdc, int w, int h)) + +/* +** SWELL_DeleteGfxContext() +** Deletes a context created with SWELL_CreateMemContext() (or the internal SWELL_CreateGfxContext) +*/ +SWELL_API_DEFINE(void, SWELL_DeleteGfxContext,(HDC)) + +/* +** SWELL_GetCtxGC() +** macOS: Returns the CGContextRef of a HDC +** GDK: returns NULL +*/ +SWELL_API_DEFINE(void *, SWELL_GetCtxGC,(HDC ctx)) + + +/* +** SWELL_GetCtxFrameBuffer() +** Gets the framebuffer of a memory context. NULL if none available. +*/ +SWELL_API_DEFINE(void *, SWELL_GetCtxFrameBuffer,(HDC ctx)) + + + +/* +** Some utility functions for pushing, setting, and popping the clip region. +** macOS-only +*/ +SWELL_API_DEFINE(void, SWELL_PushClipRegion,(HDC ctx)) +SWELL_API_DEFINE(void, SWELL_SetClipRegion,(HDC ctx, const RECT *r)) +SWELL_API_DEFINE(void, SWELL_PopClipRegion,(HDC ctx)) + + + +SWELL_API_DEFINE(HFONT, CreateFontIndirect,(LOGFONT *)) +SWELL_API_DEFINE(HFONT, CreateFont,(int lfHeight, int lfWidth, int lfEscapement, int lfOrientation, int lfWeight, char lfItalic, + char lfUnderline, char lfStrikeOut, char lfCharSet, char lfOutPrecision, char lfClipPrecision, + char lfQuality, char lfPitchAndFamily, const char *lfFaceName)) + +SWELL_API_DEFINE(HPEN, CreatePen,(int attr, int wid, int col)) +SWELL_API_DEFINE(HBRUSH, CreateSolidBrush,(int col)) +SWELL_API_DEFINE(HPEN, CreatePenAlpha,(int attr, int wid, int col, float alpha)) +SWELL_API_DEFINE(HBRUSH, CreateSolidBrushAlpha,(int col, float alpha)) +SWELL_API_DEFINE(HGDIOBJ, SelectObject,(HDC ctx, HGDIOBJ pen)) +SWELL_API_DEFINE(HGDIOBJ, GetStockObject,(int wh)) +SWELL_API_DEFINE(void, DeleteObject,(HGDIOBJ)) +#ifndef DestroyIcon +#define DestroyIcon(x) DeleteObject(x) +#endif + +#ifdef LineTo +#undef LineTo +#endif +#ifdef SetPixel +#undef SetPixel +#endif +#ifdef FillRect +#undef FillRect +#endif +#ifdef DrawText +#undef DrawText +#endif +#ifdef Polygon +#undef Polygon +#endif + +#define DrawText SWELL_DrawText +#define FillRect SWELL_FillRect +#define LineTo SWELL_LineTo +#define SetPixel SWELL_SetPixel +#define Polygon(a,b,c) SWELL_Polygon(a,b,c) + +SWELL_API_DEFINE(void, SWELL_FillRect,(HDC ctx, const RECT *r, HBRUSH br)) +SWELL_API_DEFINE(void, Rectangle,(HDC ctx, int l, int t, int r, int b)) +SWELL_API_DEFINE(void, Ellipse,(HDC ctx, int l, int t, int r, int b)) +SWELL_API_DEFINE(void, SWELL_Polygon,(HDC ctx, POINT *pts, int npts)) +SWELL_API_DEFINE(void, MoveToEx,(HDC ctx, int x, int y, POINT *op)) +SWELL_API_DEFINE(void, LineTo,(HDC ctx, int x, int y)) +SWELL_API_DEFINE(void, SetPixel,(HDC ctx, int x, int y, int c)) +SWELL_API_DEFINE(void, PolyBezierTo,(HDC ctx, POINT *pts, int np)) +SWELL_API_DEFINE(int, SWELL_DrawText,(HDC ctx, const char *buf, int len, RECT *r, int align)) +SWELL_API_DEFINE(void, SetTextColor,(HDC ctx, int col)) +SWELL_API_DEFINE(int, GetTextColor,(HDC ctx)) +SWELL_API_DEFINE(void, SetBkColor,(HDC ctx, int col)) +SWELL_API_DEFINE(void, SetBkMode,(HDC ctx, int col)) +SWELL_API_DEFINE(int, GetGlyphIndicesW, (HDC ctx, wchar_t *buf, int len, unsigned short *indices, int flags)) + +SWELL_API_DEFINE(void, RoundRect,(HDC ctx, int x, int y, int x2, int y2, int xrnd, int yrnd)) +SWELL_API_DEFINE(void, PolyPolyline,(HDC ctx, POINT *pts, DWORD *cnts, int nseg)) +SWELL_API_DEFINE(BOOL, GetTextMetrics,(HDC ctx, TEXTMETRIC *tm)) +SWELL_API_DEFINE(int, GetTextFace,(HDC ctx, int nCount, LPTSTR lpFaceName)) +#ifdef SWELL_TARGET_OSX +SWELL_API_DEFINE(void *, GetNSImageFromHICON,(HICON)) +#endif +SWELL_API_DEFINE(BOOL, GetObject, (HICON icon, int bmsz, void *_bm)) +SWELL_API_DEFINE(HICON, CreateIconIndirect, (ICONINFO* iconinfo)) +SWELL_API_DEFINE(HICON, LoadNamedImage,(const char *name, bool alphaFromMask)) +SWELL_API_DEFINE(void, DrawImageInRect,(HDC ctx, HICON img, const RECT *r)) +SWELL_API_DEFINE(void, BitBlt,(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode)) +SWELL_API_DEFINE(void, StretchBlt,(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int srcw, int srch, int mode)) +#ifndef SWELL_TARGET_OSX +SWELL_API_DEFINE(void, StretchBltFromMem,(HDC hdcOut, int x, int y, int w, int h, const void *bits, int srcw, int srch, int srcspan)) +SWELL_API_DEFINE(int, SWELL_GetScaling256, (void)) +#endif + +SWELL_API_DEFINE(void*, SWELL_ExtendedAPI, (const char *key, void *v)) + +SWELL_API_DEFINE(int, GetSysColor,(int idx)) +SWELL_API_DEFINE(HBITMAP, CreateBitmap,(int width, int height, int numplanes, int bitsperpixel, unsigned char* bits)) + +SWELL_API_DEFINE(void, SetOpaque, (HWND h, bool isopaque)) +SWELL_API_DEFINE(void, SetAllowNoMiddleManRendering, (HWND h, bool allow)) // defaults to allow, use this to disable +#ifdef SWELL_TARGET_OSX +SWELL_API_DEFINE(int, SWELL_IsRetinaDC, (HDC hdc)) // returns 1 if DC is a retina DC (2x res possible) +SWELL_API_DEFINE(int, SWELL_IsRetinaHWND, (HWND h)) // returns 1 if HWND is a retina HWND +SWELL_API_DEFINE(void, SWELL_SetNoMultiMonitorAutoSize, (HWND h, bool noauto)) +#endif + +SWELL_API_DEFINE(void, SWELL_SetViewGL, (HWND h, char wantGL)) // wantGL=2 to enable wantsBestResolutionOpenGLSurface +SWELL_API_DEFINE(bool, SWELL_GetViewGL, (HWND h)) +SWELL_API_DEFINE(bool, SWELL_SetGLContextToView, (HWND h)) // sets GL context to that view, returns TRUE if successs (use NULL to clear GL context) + +#if defined(SWELL_TARGET_OSX) +SWELL_API_DEFINE(int, SWELL_EnableMetal,(HWND h, int mode)) // can only call once per window. calling with 0 does nothing. 1=metal enabled, 2=metal enabled and support GetDC()/ReleaseDC() for drawing (more overhead). returns metal setting. mode=-1 for non-metal async layered mode. mode=-2 for non-metal non-async layered mode + // NOTE: if using SWELL_EnableMetal(-1), any BitBlt()/StretchBlt() __MUST__ have the source bitmap persist. If it is resized after Blit it could cause crashes, too. So really this method is unsafe for practical use. +#else + #ifndef SWELL_EnableMetal + #define SWELL_EnableMetal(hwnd,x) (void)(x) + #endif +#endif + +SWELL_API_DEFINE(HDC, BeginPaint,(HWND, PAINTSTRUCT *)) +SWELL_API_DEFINE(BOOL, EndPaint,(HWND, PAINTSTRUCT *)) + +SWELL_API_DEFINE(HDC, GetDC,(HWND)) // use these sparingly! they kinda work but shouldnt be overused!! +SWELL_API_DEFINE(HDC, GetWindowDC,(HWND)) +SWELL_API_DEFINE(void, ReleaseDC,(HWND, HDC)) + +#ifdef SWELL_TARGET_OSX +SWELL_API_DEFINE(void, SWELL_FlushWindow,(HWND)) +#endif + +SWELL_API_DEFINE(void, SWELL_FillDialogBackground,(HDC hdc, const RECT *r, int level)) + +SWELL_API_DEFINE(HGDIOBJ,SWELL_CloneGDIObject,(HGDIOBJ a)) + +SWELL_API_DEFINE(int, GetSystemMetrics, (int)) + +SWELL_API_DEFINE(BOOL, DragQueryPoint,(HDROP,LPPOINT)) +SWELL_API_DEFINE(void, DragFinish,(HDROP)) +SWELL_API_DEFINE(UINT, DragQueryFile,(HDROP,UINT,char *,UINT)) + +// source drag/drop - callback is source implementing "create dropped files at droppath" +SWELL_API_DEFINE(void, SWELL_InitiateDragDrop, (HWND, RECT* srcrect, const char* srcfn, void (*callback)(const char* droppath))) +SWELL_API_DEFINE(void,SWELL_InitiateDragDropOfFileList,(HWND, RECT *srcrect, const char **srclist, int srccount, HICON icon)) +SWELL_API_DEFINE(void, SWELL_FinishDragDrop, ()) // cancels any outstanding InitiateDragDrop + + + +// focus rects aren't implemented as XOR as on win32, might be a straight blit or a separate window +// rct=NULL to "free" handle +// otherwise rct is in hwndPar coordinates +SWELL_API_DEFINE(void,SWELL_DrawFocusRect,(HWND hwndPar, RECT *rct, void **handle)) + + +#ifdef SWELL_TARGET_OSX +SWELL_API_DEFINE(void,SWELL_SetWindowRepre,(HWND hwnd, const char *fn, bool isDirty)) // sets the represented file and edited state +SWELL_API_DEFINE(void,SWELL_PostQuitMessage,(void *sender)) +SWELL_API_DEFINE(bool,SWELL_osx_is_dark_mode,(int mode)) // mode=0 for dark mode enabled enabled, 1=dark mode allowed (Breaks various things) +#endif + +/* +** Functions used by swell-dlggen.h and swell-menugen.h +** No need to really dig into these unless you're working on swell or debugging.. +*/ + +SWELL_API_DEFINE(void, SWELL_MakeSetCurParms,(float xscale, float yscale, float xtrans, float ytrans, HWND parent, bool doauto, bool dosizetofit)) + +SWELL_API_DEFINE(HWND, SWELL_MakeButton,(int def, const char *label, int idx, int x, int y, int w, int h, int flags)) +SWELL_API_DEFINE(HWND, SWELL_MakeEditField,(int idx, int x, int y, int w, int h, int flags)) +SWELL_API_DEFINE(HWND, SWELL_MakeLabel,(int align, const char *label, int idx, int x, int y, int w, int h, int flags)) +SWELL_API_DEFINE(HWND, SWELL_MakeControl,(const char *cname, int idx, const char *classname, int style, int x, int y, int w, int h, int exstyle)) +SWELL_API_DEFINE(HWND, SWELL_MakeCombo,(int idx, int x, int y, int w, int h, int flags)) +SWELL_API_DEFINE(HWND, SWELL_MakeGroupBox,(const char *name, int idx, int x, int y, int w, int h, int style)) +SWELL_API_DEFINE(HWND, SWELL_MakeCheckBox,(const char *name, int idx, int x, int y, int w, int h, int flags)) +SWELL_API_DEFINE(HWND, SWELL_MakeListBox,(int idx, int x, int y, int w, int h, int styles)) + +SWELL_API_DEFINE(void, SWELL_Menu_AddMenuItem,(HMENU hMenu, const char *name, int idx, unsigned int flags)) +SWELL_API_DEFINE(int, SWELL_GenerateMenuFromList,(HMENU hMenu, const void *list, int listsz)) // list is SWELL_MenuGen_Entry + +SWELL_API_DEFINE(void, SWELL_GenerateDialogFromList, (const void *list, int listsz)) + + +SWELL_API_DEFINE(unsigned int, _controlfp,(unsigned int flag, unsigned int mask)) + +SWELL_API_DEFINE(void,SWELL_Internal_PostMessage_Init,()) + + +SWELL_API_DEFINE(HCURSOR,SWELL_LoadCursorFromFile,(const char *fn)) +SWELL_API_DEFINE(void,SWELL_SetWindowWantRaiseAmt,(HWND h, int amt)) +SWELL_API_DEFINE(int,SWELL_GetWindowWantRaiseAmt,(HWND)) + +SWELL_API_DEFINE(void,SWELL_SetListViewFastClickMask,(HWND hList, int mask)) + + +SWELL_API_DEFINE(void,GetTempPath,(int sz, char *buf)) + +#ifndef SWELL_TARGET_OSX +SWELL_API_DEFINE(void,SWELL_initargs,(int *argc, char ***argv)) +SWELL_API_DEFINE(void,SWELL_RunMessageLoop,()) +SWELL_API_DEFINE(HWND,SWELL_CreateXBridgeWindow,(HWND viewpar, void **wref, const RECT*)) +SWELL_API_DEFINE(void*,SWELL_GetOSWindow,(HWND hwnd, const char *type)) // type should be "GdkWindow" +SWELL_API_DEFINE(void*,SWELL_GetOSEvent,(const char *type)) // type should be "GdkEvent" +#endif + +SWELL_API_DEFINE(bool,SWELL_GenerateGUID,(void *g)) + +SWELL_API_DEFINE(BOOL,EnumChildWindows,(HWND hwnd, BOOL (*cwEnumFunc)(HWND,LPARAM),LPARAM lParam)) + + +SWELL_API_DEFINE(BOOL,SWELL_IsGroupBox,(HWND)) +SWELL_API_DEFINE(BOOL,SWELL_IsButton,(HWND)) +SWELL_API_DEFINE(BOOL,SWELL_IsStaticText,(HWND)) +SWELL_API_DEFINE(void,SWELL_GetDesiredControlSize,(HWND hwnd, RECT *r)) + +SWELL_API_DEFINE(int,AddFontResourceEx,(LPCTSTR str, DWORD fl, void *pdv)) + +#ifdef SWELL_TARGET_OSX +SWELL_API_DEFINE(void,SWELL_DisableAppNap,(int disable)) +SWELL_API_DEFINE(int,SWELL_GetOSXVersion,()) +#endif + +SWELL_API_DEFINE(void,SWELL_Register_Cursor_Resource,(const char *idx, const char *name, int hotspot_x, int hotspot_y)) + +SWELL_API_DEFINE(bool, SWELL_ChooseColor, (HWND, COLORREF *, int ncustom, COLORREF *custom)) +SWELL_API_DEFINE(bool, SWELL_ChooseFont, (HWND, LOGFONT*)) + +SWELL_API_DEFINE(bool, IsWindowEnabled, (HWND)) + +SWELL_API_DEFINE(int, GetClassName, (HWND, char *, int)) // only partially implemented, if using custom control creators they should call SWELL_SetClassName() to set the class name (reading class name is desired) +SWELL_API_DEFINE(void, SWELL_SetClassName, (HWND, const char*)) // must pass a static string! + +SWELL_API_DEFINE(void, SWELL_DisableContextMenu, (HWND, bool)) + +SWELL_API_DEFINE(BOOL, EnumDisplayMonitors, (HDC,const LPRECT,MONITORENUMPROC,LPARAM)) +SWELL_API_DEFINE(BOOL, GetMonitorInfo, (HMONITOR, void *)) + +#endif // _WDL_SWELL_H_API_DEFINED_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-generic.cpp new file mode 100644 index 0000000..5b18f48 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-generic.cpp @@ -0,0 +1,797 @@ +/* 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. + + + This file provides basic win32 GDI--> null translation. + +*/ + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-internal.h" +#include "../wdlcstring.h" + +const char *g_swell_deffont_face = "Arial"; +const char *swell_last_font_filename; + +swell_colortheme g_swell_ctheme = { +#define __def_theme_ent(x,c) (c), +#define __def_theme_ent_fb(x,c,fb) (c), +SWELL_GENERIC_THEMEDEFS(__def_theme_ent,__def_theme_ent_fb) +#undef __def_theme_ent +#undef __def_theme_ent_fb +}; + +int GetSysColor(int idx) +{ + switch (idx) + { + case COLOR_WINDOW: + case COLOR_3DFACE: + case COLOR_BTNFACE: return g_swell_ctheme._3dface; + case COLOR_3DSHADOW: return g_swell_ctheme._3dshadow; + case COLOR_3DHILIGHT: return g_swell_ctheme._3dhilight; + case COLOR_3DDKSHADOW: return g_swell_ctheme._3ddkshadow; + case COLOR_BTNTEXT: return g_swell_ctheme.button_text; + case COLOR_INFOBK: return g_swell_ctheme.info_bk; + case COLOR_INFOTEXT: return g_swell_ctheme.info_text; + case COLOR_SCROLLBAR: return g_swell_ctheme.scrollbar; + } + return 0; +} + +int g_swell_ui_scale = 256; + +int SWELL_GetScaling256(void) +{ + return g_swell_ui_scale; +} + +#ifndef SWELL_LICE_GDI + +#include "../mutex.h" +#include "../ptrlist.h" + +#include "swell-gdi-internalpool.h" + +HDC SWELL_CreateGfxContext(void *c) +{ + HDC__ *ctx=SWELL_GDP_CTX_NEW(); + + + return ctx; +} + +HDC SWELL_CreateMemContext(HDC hdc, int w, int h) +{ + // we could use CGLayer here, but it's 10.4+ and seems to be slower than this +// if (w&1) w++; + void *buf=calloc(w*4,h); + if (!buf) return 0; + + HDC__ *ctx=SWELL_GDP_CTX_NEW(); + ctx->ownedData=buf; + + SetTextColor(ctx,0); + return ctx; +} + + +void SWELL_DeleteGfxContext(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct)) + { + if (ct->ownedData) + { + free(ct->ownedData); + } + SWELL_GDP_CTX_DELETE(ct); + } +} +HPEN CreatePen(int attr, int wid, int col) +{ + return CreatePenAlpha(attr,wid,col,1.0f); +} + +HBRUSH CreateSolidBrush(int col) +{ + return CreateSolidBrushAlpha(col,1.0f); +} + +HPEN CreatePenAlpha(int attr, int wid, int col, float alpha) +{ + HGDIOBJ__ *pen=GDP_OBJECT_NEW(); + pen->type=TYPE_PEN; + pen->wid=wid<0?0:wid; +// pen->color=CreateColor(col,alpha); + return pen; +} +HBRUSH CreateSolidBrushAlpha(int col, float alpha) +{ + HGDIOBJ__ *brush=GDP_OBJECT_NEW(); + brush->type=TYPE_BRUSH; +// brush->color=CreateColor(col,alpha); + brush->wid=0; + return brush; +} + +#define FONTSCALE 0.9 +HFONT CreateFont(int lfHeight, int lfWidth, int lfEscapement, int lfOrientation, int lfWeight, char lfItalic, + char lfUnderline, char lfStrikeOut, char lfCharSet, char lfOutPrecision, char lfClipPrecision, + char lfQuality, char lfPitchAndFamily, const char *lfFaceName) +{ + HGDIOBJ__ *font=GDP_OBJECT_NEW(); + font->type=TYPE_FONT; + float fontwid=lfHeight; + + + if (!fontwid) fontwid=lfWidth; + if (fontwid<0)fontwid=-fontwid; + + if (fontwid < 2 || fontwid > 8192) fontwid=10; + + return font; +} + +HFONT SWELL_GetDefaultFont() +{ + return NULL; +} + + +HFONT CreateFontIndirect(LOGFONT *lf) +{ + return CreateFont(lf->lfHeight, lf->lfWidth,lf->lfEscapement, lf->lfOrientation, lf->lfWeight, lf->lfItalic, + lf->lfUnderline, lf->lfStrikeOut, lf->lfCharSet, lf->lfOutPrecision,lf->lfClipPrecision, + lf->lfQuality, lf->lfPitchAndFamily, lf->lfFaceName); +} + +int GetTextFace(HDC ctx, int nCount, LPTSTR lpFaceName) +{ + if (lpFaceName) lpFaceName[0]=0; + return 0; +} + +void DeleteObject(HGDIOBJ pen) +{ + if (HGDIOBJ_VALID(pen)) + { + HGDIOBJ__ *p=(HGDIOBJ__ *)pen; + if (--p->additional_refcnt < 0) + { + if (p->type == TYPE_PEN || p->type == TYPE_BRUSH || p->type == TYPE_FONT || p->type == TYPE_BITMAP) + { + if (p->type == TYPE_PEN || p->type == TYPE_BRUSH) + if (p->wid<0) return; + + GDP_OBJECT_DELETE(p); + } + // JF> don't free unknown objects, this should never happen anyway: else free(p); + } + } +} + + +HGDIOBJ SelectObject(HDC ctx, HGDIOBJ pen) +{ + HDC__ *c=(HDC__ *)ctx; + HGDIOBJ__ *p=(HGDIOBJ__ *) pen; + HGDIOBJ__ **mod=0; + if (!HDC_VALID(c)||!p) return 0; + + if (p == (HGDIOBJ__ *)TYPE_PEN) mod=&c->curpen; + else if (p == (HGDIOBJ__ *)TYPE_BRUSH) mod=&c->curbrush; + else if (p == (HGDIOBJ__ *)TYPE_FONT) mod=&c->curfont; + + if (mod) + { + HGDIOBJ__ *np=*mod; + *mod=0; + return np?np:p; + } + + if (!HGDIOBJ_VALID(p)) return 0; + + if (p->type == TYPE_PEN) mod=&c->curpen; + else if (p->type == TYPE_BRUSH) mod=&c->curbrush; + else if (p->type == TYPE_FONT) mod=&c->curfont; + else return 0; + + HGDIOBJ__ *op=*mod; + if (!op) op=(HGDIOBJ__ *)(INT_PTR)p->type; + if (op != p) + { + *mod=p; + + if (p->type == TYPE_FONT) + { + } + } + return op; +} + + + +void SWELL_FillRect(HDC ctx, const RECT *r, HBRUSH br) +{ + HDC__ *c=(HDC__ *)ctx; + HGDIOBJ__ *b=(HGDIOBJ__ *) br; + if (!HDC_VALID(c) || !HGDIOBJ_VALID(b,TYPE_BRUSH)) return; + + if (b->wid<0) return; + + +} + +void RoundRect(HDC ctx, int x, int y, int x2, int y2, int xrnd, int yrnd) +{ + xrnd/=3; + yrnd/=3; + POINT pts[10]={ // todo: curves between edges + {x,y+yrnd}, + {x+xrnd,y}, + {x2-xrnd,y}, + {x2,y+yrnd}, + {x2,y2-yrnd}, + {x2-xrnd,y2}, + {x+xrnd,y2}, + {x,y2-yrnd}, + {x,y+yrnd}, + {x+xrnd,y}, +}; + + WDL_GDP_Polygon(ctx,pts,sizeof(pts)/sizeof(pts[0])); +} + +void Ellipse(HDC ctx, int l, int t, int r, int b) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + + //CGRect rect=CGRectMake(l,t,r-l,b-t); + + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >=0) + { + } + if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0) + { + } +} + +void Rectangle(HDC ctx, int l, int t, int r, int b) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0) + { + } + if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0) + { + } +} + +HGDIOBJ GetStockObject(int wh) +{ + switch (wh) + { + case NULL_BRUSH: + { + static HGDIOBJ__ br={0,}; + br.type=TYPE_BRUSH; + br.wid=-1; + return &br; + } + case NULL_PEN: + { + static HGDIOBJ__ pen={0,}; + pen.type=TYPE_PEN; + pen.wid=-1; + return &pen; + } + } + return 0; +} + +void Polygon(HDC ctx, POINT *pts, int npts) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + if (((!HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH)||c->curbrush->wid<0) && + (!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0)) || npts<2) return; + +} + +void MoveToEx(HDC ctx, int x, int y, POINT *op) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + if (op) + { + op->x = (int) (c->lastpos_x); + op->y = (int) (c->lastpos_y); + } + c->lastpos_x=(float)x; + c->lastpos_y=(float)y; +} + +void PolyBezierTo(HDC ctx, POINT *pts, int np) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0||np<3) return; + + int x; + float xp,yp; + for (x = 0; x < np-2; x += 3) + { + xp=(float)pts[x+2].x; + yp=(float)pts[x+2].y; + } + c->lastpos_x=(float)xp; + c->lastpos_y=(float)yp; +} + + +void SWELL_LineTo(HDC ctx, int x, int y) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0) return; + + float fx=(float)x,fy=(float)y; + + c->lastpos_x=fx; + c->lastpos_y=fy; +} + +void PolyPolyline(HDC ctx, POINT *pts, DWORD *cnts, int nseg) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0||nseg<1) return; + + while (nseg-->0) + { + DWORD cnt=*cnts++; + if (!cnt) continue; + if (!--cnt) { pts++; continue; } + + pts++; + + while (cnt--) + { + pts++; + } + } +} +void *SWELL_GetCtxGC(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return 0; + return NULL; +} + + +void SWELL_SetPixel(HDC ctx, int x, int y, int c) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; +} + + +BOOL GetTextMetrics(HDC ctx, TEXTMETRIC *tm) +{ + HDC__ *ct=(HDC__ *)ctx; + if (tm) // give some sane defaults + { + tm->tmInternalLeading=3; + tm->tmAscent=12; + tm->tmDescent=4; + tm->tmHeight=16; + tm->tmAveCharWidth = 10; + } + if (!HDC_VALID(ct)||!tm) return 0; + + return 1; +} + + +int DrawText(HDC ctx, const char *buf, int buflen, RECT *r, int align) +{ + WDL_ASSERT((align & DT_SINGLELINE) || !(align & (DT_VCENTER | DT_BOTTOM))); + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return 0; + if (r && (align&DT_CALCRECT)) + { + r->top=r->left=0; + r->bottom=10; + r->right = ( buflen < 0 ? strlen(buf) : buflen ) *8; + } + else printf("DrawText: %s\n",buf); + return 10; +} + +void SetBkColor(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->curbkcol=col; +} + +void SetBkMode(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->curbkmode=col; +} +int GetTextColor(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return -1; + return ct->cur_text_color_int; +} + +void SetTextColor(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->cur_text_color_int = col; + +} + +HICON LoadNamedImage(const char *name, bool alphaFromMask) +{ + return 0; // todo +} + +void DrawImageInRect(HDC ctx, HICON img, const RECT *r) +{ + // todo +} + +void SWELL_SetViewGL(HWND h, char wantGL) +{ +} +bool SWELL_GetViewGL(HWND h) +{ + return false; +} +bool SWELL_SetGLContextToView(HWND h) +{ + return false; +} + +BOOL GetObject(HICON icon, int bmsz, void *_bm) +{ + memset(_bm,0,bmsz); + if (bmsz != sizeof(BITMAP)) return false; + HGDIOBJ__ *i = (HGDIOBJ__ *)icon; + if (!HGDIOBJ_VALID(i,TYPE_BITMAP)) return false; + //BITMAP *bm=(BITMAP *)_bm; + + return false; +} + +void BitBltAlphaFromMem(HDC hdcOut, int x, int y, int w, int h, void *inbufptr, int inbuf_span, int inbuf_h, int xin, int yin, int mode, bool useAlphaChannel, float opacity) +{ +} + +void BitBltAlpha(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode, bool useAlphaChannel, float opacity) +{ +} + +void BitBlt(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode) +{ +} + +void StretchBlt(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int srcw, int srch, int mode) +{ +} + +void StretchBltFromMem(HDC hdcOut, int x, int y, int w, int h, const void *bits, int srcw, int srch, int srcspan) +{ +} + +void SWELL_PushClipRegion(HDC ctx) +{ +// HDC__ *ct=(HDC__ *)ctx; +} + +void SWELL_SetClipRegion(HDC ctx, const RECT *r) +{ +// HDC__ *ct=(HDC__ *)ctx; + +} + +void SWELL_PopClipRegion(HDC ctx) +{ +// HDC__ *ct=(HDC__ *)ctx; +} + +void *SWELL_GetCtxFrameBuffer(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct)) return ct->ownedData; + return 0; +} + + +HDC GetDC(HWND h) +{ + return NULL; +} + +HDC GetWindowDC(HWND h) +{ + return NULL; +} + +void ReleaseDC(HWND h, HDC hdc) +{ +} + +void SWELL_FillDialogBackground(HDC hdc, const RECT *r, int level) +{ +} + +HGDIOBJ SWELL_CloneGDIObject(HGDIOBJ a) +{ + if (HGDIOBJ_VALID(a)) + { + a->additional_refcnt++; + return a; + } + return NULL; +} + +HDC BeginPaint(HWND hwnd, PAINTSTRUCT *ps) +{ + if (!ps) return 0; + memset(ps,0,sizeof(PAINTSTRUCT)); + if (!hwnd) return 0; + + return NULL; +} + + +HBITMAP CreateBitmap(int width, int height, int numplanes, int bitsperpixel, unsigned char* bits) +{ + return NULL; +} + +HICON CreateIconIndirect(ICONINFO* iconinfo) +{ + return NULL; +} +HIMAGELIST ImageList_CreateEx() +{ + return (HIMAGELIST)new WDL_PtrList; +} +BOOL ImageList_Remove(HIMAGELIST list, int idx) +{ + WDL_PtrList* imglist=(WDL_PtrList*)list; + if (imglist && idx < imglist->GetSize()) + { + if (idx < 0) + { + int x,n=imglist->GetSize(); + for (x=0;xGet(x); + if (a) DeleteObject(a); + } + imglist->Empty(); + } + else + { + HGDIOBJ__ *a = imglist->Get(idx); + imglist->Set(idx, NULL); + if (a) DeleteObject(a); + } + return TRUE; + } + + return FALSE; +} + +void ImageList_Destroy(HIMAGELIST list) +{ + if (!list) return; + WDL_PtrList *p=(WDL_PtrList*)list; + ImageList_Remove(list,-1); + delete p; +} + +int ImageList_ReplaceIcon(HIMAGELIST list, int offset, HICON image) +{ + if (!image || !list) return -1; + WDL_PtrList *l=(WDL_PtrList *)list; + + HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image; + if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1; + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + icon->type=TYPE_BITMAP; + icon->wid=1; + // todo: copy underlying image + + image = (HICON) icon; + + if (offset<0||offset>=l->GetSize()) + { + l->Add(image); + offset=l->GetSize()-1; + } + else + { + HICON old=l->Get(offset); + l->Set(offset,image); + if (old) DeleteObject(old); + } + return offset; +} + +int ImageList_Add(HIMAGELIST list, HBITMAP image, HBITMAP mask) +{ + if (!image || !list) return -1; + WDL_PtrList *l=(WDL_PtrList *)list; + + HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image; + if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1; + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + icon->type=TYPE_BITMAP; + icon->wid=1; + // todo: copy underlying image + + image = (HICON) icon; + + l->Add(image); + return l->GetSize(); +} + + +int AddFontResourceEx(LPCTSTR str, DWORD fl, void *pdv) +{ + return 0; +} + +int GetGlyphIndicesW(HDC ctx, wchar_t *buf, int len, unsigned short *indices, int flags) +{ + int i; + for (i=0; i < len; ++i) indices[i]=(flags == GGI_MARK_NONEXISTING_GLYPHS ? 0xFFFF : 0); + return 0; +} + +#endif // !SWELL_LICE_GDI + +#ifdef SWELL__MAKE_THEME +void print_ent(const char *x, int c, const char *def) +{ + if (def) + printf("; %s #%02x%02x%02x ; defaults to %s\n",x,GetRValue(c),GetGValue(c),GetBValue(c),def); + else + { + if (strstr(x,"_size") || + strstr(x,"_height") || + strstr(x,"_width")) + printf("%s %d\n",x,c); + else printf("%s #%02x%02x%02x\n",x,GetRValue(c),GetGValue(c),GetBValue(c)); + } +} + +int main() +{ +#define __def_theme_ent(x,c) print_ent(#x,c,NULL); +#define __def_theme_ent_fb(x,c,fb) print_ent(#x,c,#fb); + +printf("default_font_face %s\n",g_swell_deffont_face); +SWELL_GENERIC_THEMEDEFS(__def_theme_ent,__def_theme_ent_fb) +return 0; +} +#else + +void swell_load_color_theme(const char *fn) +{ + FILE *fp = WDL_fopenA(fn,"r"); + if (fp) + { + swell_colortheme load; + memset(&load,-1,sizeof(load)); + char buf[1024]; + + for (;;) + { + if (!fgets(buf,sizeof(buf),fp)) break; + char *p = buf; + while (*p == ' ' || *p == '\t') p++; + char *np = p; + while (*np > 0 && (*np == '_' || isalnum(*np))) np++; + if (!*np || np == p) continue; + *np++ = 0; + while (*np == ' ' || *np == '\t') np++; + + if(!stricmp(p,"default_font_face")) + { + if (*np > 0 && !isspace(*np)) + { + char *b = strdup(np); + g_swell_deffont_face = b; + while (*b && *b != ';' && *b != '#') b++; + while (b>g_swell_deffont_face && b[-1] > 0 && isspace(b[-1])) b--; + *b=0; + } + continue; + } + + int col; + if (*np == '#') + { + np++; + char *next; + col = strtol(np,&next,16); + if (next != np+6) + { + if (next != np+3) continue; + col = ((col&0xf)<<4) | ((col&0xf0)<<8) | ((col&0xf00)<<12); + } + } + else if (*np >= '0' && *np <= '9') + { + col = atoi(np); + } + else continue; + + if(0){} +#define __def_theme_ent(x,c) else if (!stricmp(p,#x)) load.x = col; +#define __def_theme_ent_fb(x,c,fb) else if (!stricmp(p,#x)) load.x = col; +SWELL_GENERIC_THEMEDEFS(__def_theme_ent,__def_theme_ent_fb) +#undef __def_theme_ent +#undef __def_theme_ent_fb + } +#define __def_theme_ent(x,c) g_swell_ctheme.x = load.x == -1 ? c : load.x; +#define __def_theme_ent_fb(x,c,fb) g_swell_ctheme.x = load.x == -1 ? g_swell_ctheme.fb : load.x; +SWELL_GENERIC_THEMEDEFS(__def_theme_ent,__def_theme_ent_fb) +#undef __def_theme_ent +#undef __def_theme_ent_fb + + fclose(fp); + } +} + +// load color theme +class swellColorThemeLoader +{ +public: + swellColorThemeLoader() + { + char buf[1024]; + GetModuleFileName(NULL,buf,sizeof(buf)); + WDL_remove_filepart(buf); + lstrcatn(buf,"/libSwell.colortheme",sizeof(buf)); + swell_load_color_theme(buf); + } +}; +swellColorThemeLoader g_swell_themeloader; + + + +#endif + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-internalpool.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-internalpool.h new file mode 100644 index 0000000..41ac609 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-internalpool.h @@ -0,0 +1,222 @@ +/* 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. + + // used for HDC/HGDIOBJ pooling (to avoid excess heap use), used by swell-gdi.mm and swell-gdi-generic.cpp +*/ + +#if defined(_DEBUG) + #define SWELL_GDI_DEBUG +#endif + +static WDL_Mutex *m_ctxpool_mutex; +#ifdef SWELL_GDI_DEBUG + #include "../ptrlist.h" + static WDL_PtrList *m_ctxpool_debug; + static WDL_PtrList *m_objpool_debug; +#else + static HDC__ *m_ctxpool; + static int m_ctxpool_size; + static HGDIOBJ__ *m_objpool; + static int m_objpool_size; +#endif + + + +HDC__ *SWELL_GDP_CTX_NEW() +{ + if (!m_ctxpool_mutex) m_ctxpool_mutex=new WDL_Mutex; + + HDC__ *p=NULL; +#ifdef SWELL_GDI_DEBUG + m_ctxpool_mutex->Enter(); + if (!m_ctxpool_debug) m_ctxpool_debug = new WDL_PtrList; + if (m_ctxpool_debug->GetSize() > 8192) + { + p = m_ctxpool_debug->Get(0); + m_ctxpool_debug->Delete(0); + memset(p,0,sizeof(*p)); + } + m_ctxpool_mutex->Leave(); +#else + if (m_ctxpool) + { + m_ctxpool_mutex->Enter(); + if ((p=m_ctxpool)) + { + m_ctxpool=p->_next; + m_ctxpool_size--; + memset(p,0,sizeof(*p)); + } + m_ctxpool_mutex->Leave(); + } +#endif + if (!p) + { +// printf("alloc ctx\n"); + p=(HDC__ *)calloc(sizeof(HDC__)+128,1); // extra space in case things want to use it (i.e. swell-gdi-lice does) + } + return p; +} +static void SWELL_GDP_CTX_DELETE(HDC__ *p) +{ + if (!m_ctxpool_mutex) m_ctxpool_mutex=new WDL_Mutex; + + if (WDL_NOT_NORMALLY(!p || p->_infreelist)) return; + + memset(p,0,sizeof(*p)); + +#ifdef SWELL_GDI_DEBUG + m_ctxpool_mutex->Enter(); + p->_infreelist=true; + if (!m_ctxpool_debug) m_ctxpool_debug = new WDL_PtrList; + m_ctxpool_debug->Add(p); + m_ctxpool_mutex->Leave(); +#else + if (m_ctxpool_size<100) + { + m_ctxpool_mutex->Enter(); + p->_infreelist=true; + p->_next = m_ctxpool; + m_ctxpool = p; + m_ctxpool_size++; + m_ctxpool_mutex->Leave(); + } + else + { + // printf("free ctx\n"); + free(p); + } +#endif +} +static HGDIOBJ__ *GDP_OBJECT_NEW() +{ + if (!m_ctxpool_mutex) m_ctxpool_mutex=new WDL_Mutex; + HGDIOBJ__ *p=NULL; +#ifdef SWELL_GDI_DEBUG + m_ctxpool_mutex->Enter(); + if (!m_objpool_debug) m_objpool_debug = new WDL_PtrList; + if (m_objpool_debug->GetSize()>8192) + { + p = m_objpool_debug->Get(0); + m_objpool_debug->Delete(0); + memset(p,0,sizeof(*p)); + } + m_ctxpool_mutex->Leave(); +#else + if (m_objpool) + { + m_ctxpool_mutex->Enter(); + if ((p=m_objpool)) + { + m_objpool = p->_next; + m_objpool_size--; + memset(p,0,sizeof(*p)); + } + m_ctxpool_mutex->Leave(); + } +#endif + if (!p) + { + // printf("alloc obj\n"); + p=(HGDIOBJ__ *)calloc(sizeof(HGDIOBJ__),1); + } + return p; +} + +static bool HGDIOBJ_VALID(HGDIOBJ__ *p, int reqType=0) +{ + return p && + WDL_NORMALLY( p != (HGDIOBJ__*)TYPE_PEN && p != (HGDIOBJ__*)TYPE_BRUSH && + p != (HGDIOBJ__*)TYPE_FONT && p != (HGDIOBJ__*)TYPE_BITMAP) && + WDL_NORMALLY(!p->_infreelist) && + WDL_NORMALLY(!reqType || reqType == p->type); +} + +static void GDP_OBJECT_DELETE(HGDIOBJ__ *p) +{ + if (!m_ctxpool_mutex) m_ctxpool_mutex=new WDL_Mutex; + if (WDL_NOT_NORMALLY(!p) || !HGDIOBJ_VALID(p)) return; + + memset(p,0,sizeof(*p)); +#ifdef SWELL_GDI_DEBUG + m_ctxpool_mutex->Enter(); + p->_infreelist = true; + if (!m_objpool_debug) m_objpool_debug = new WDL_PtrList; + m_objpool_debug->Add(p); + m_ctxpool_mutex->Leave(); +#else + if (m_objpool_size<200) + { + m_ctxpool_mutex->Enter(); + p->_infreelist = true; + p->_next = m_objpool; + m_objpool = p; + m_objpool_size++; + m_ctxpool_mutex->Leave(); + } + else + { + // printf("free obj\n"); + free(p); + } +#endif +} + +static bool HDC_VALID(HDC__ *ct) +{ + return ct && WDL_NORMALLY(!ct->_infreelist); +} + + +#if !defined(SWELL_GDI_DEBUG) && defined(SWELL_CLEANUP_ON_UNLOAD) + +class _swellGdiUnloader +{ + public: + _swellGdiUnloader() { } + ~_swellGdiUnloader() + { + { + HDC__ *p = m_ctxpool; + m_ctxpool = NULL; + while (p) + { + HDC__ *t = p; + p = p->_next; + free(t); + } + } + { + HGDIOBJ__ *p = m_objpool; + m_objpool = NULL; + while (p) + { + HGDIOBJ__ *t = p; + p = p->_next; + free(t); + } + } + + delete m_ctxpool_mutex; + m_ctxpool_mutex=NULL; + } +}; + +_swellGdiUnloader __swell__swellGdiUnloader; +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-lice.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-lice.cpp new file mode 100644 index 0000000..e162e20 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi-lice.cpp @@ -0,0 +1,1879 @@ +/* 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. + + + This file provides basic win32 GDI-->lice translation. + +*/ + +#ifdef SWELL_LICE_GDI +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-internal.h" + +#include "../mutex.h" +#include "../ptrlist.h" +#include "../wdlcstring.h" +#include "../wdlutf8.h" + +#include "swell-gdi-internalpool.h" + + +#ifdef SWELL_FREETYPE +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + +#ifdef SWELL_FONTCONFIG +#include +#else +#include "../dirscan.h" +#endif + +static bool s_freetype_failed; +static FT_Library s_freetype; // todo: TLS for multithread support? -- none of the text drawing is thread safe! + +extern const char *swell_last_font_filename; + +#ifndef SWELL_FREETYPE_CACHE_SIZE +#define SWELL_FREETYPE_CACHE_SIZE 80 +#endif + +class fontConfigCacheEnt +{ + public: + fontConfigCacheEnt(const char *name, int flags, int w, int h, FT_Face face, const char *fndesc) + { + m_name = strdup(name); + m_flags = flags; + m_face = face; + m_w=w; + m_h=h; + m_fndesc = strdup(fndesc); + FT_Reference_Face(m_face); + } + ~fontConfigCacheEnt() + { + free(m_name); + free(m_fndesc); + FT_Done_Face(m_face); + } + + char *m_name; + char *m_fndesc; + int m_flags, m_w, m_h; + FT_Face m_face; +}; + + +#ifdef SWELL_FONTCONFIG + +static FcConfig *s_fontconfig; +const char *swell_enumFontFiles(int x) +{ + if (!s_fontconfig) return NULL; + + static FcPattern *pat; + static FcObjectSet *prop; + static FcFontSet *fonts; + if (x<0) + { + if (fonts) FcFontSetDestroy(fonts); + if (prop) FcObjectSetDestroy(prop); + if (pat) FcPatternDestroy(pat); + pat=NULL; + prop=NULL; + fonts=NULL; + return NULL; + } + if (!pat) + { + pat = FcPatternCreate(); + prop = FcObjectSetBuild(FC_FAMILY, NULL); + fonts = FcFontList(s_fontconfig,pat,prop); + } + if (fonts && x < fonts->nfont) + { + FcPattern *f = fonts->fonts[x]; + FcChar8 *fn = NULL; + if (FcPatternGetString(f,FC_FAMILY,0,&fn) == FcResultMatch && fn && *fn) + return (const char *)fn; + } + + return NULL; +} + +#else + +static WDL_PtrList s_freetype_fontlist; +static WDL_PtrList s_freetype_regfonts; + +const char *swell_enumFontFiles(int x) +{ + const int n1 = s_freetype_regfonts.GetSize(); + if (x < n1) return s_freetype_regfonts.Get(x); + return s_freetype_fontlist.Get(x-n1); +} + + +static void ScanFontDirectory(const char *path, int maxrec=3) +{ + WDL_DirScan ds; + WDL_FastString fs; + if (!ds.First(path)) do + { + if (ds.GetCurrentFN()[0] != '.') + { + if (ds.GetCurrentIsDirectory()) + { + if (maxrec>0 && + stricmp(ds.GetCurrentFN(),"type1") && + stricmp(ds.GetCurrentFN(),"cmap") && + stricmp(ds.GetCurrentFN(),"ghostscript") && + strcmp(ds.GetCurrentFN(),"X11") + ) + { + ds.GetCurrentFullFN(&fs); + ScanFontDirectory(fs.Get(),maxrec-1); + } + } + else + { + const char *ext = WDL_get_fileext(ds.GetCurrentFN()); + if (!stricmp(ext,".ttf") || !stricmp(ext,".otf")) + { + ds.GetCurrentFullFN(&fs); + s_freetype_fontlist.Add(strdup(fs.Get())); + } + } + } + } while (!ds.Next()); +} + +static int sortByFilePart(const char **a, const char **b) +{ + return stricmp(WDL_get_filepart(*a),WDL_get_filepart(*b)); +} + +struct fontScoreMatched { + int score1,score2; + const char *fn; + + static int sortfunc(const void *a, const void *b) + { + const int v = ((const fontScoreMatched *)a)->score1 - ((const fontScoreMatched *)b)->score1; + return v?v:((const fontScoreMatched *)a)->score2 - ((const fontScoreMatched *)b)->score2; + } + +}; + +static FT_Face MatchFont(const char *lfFaceName, int weight, int italic, int exact, char *matchFnOut, size_t matchFnOutSize) +{ + const int fn_len = strlen(lfFaceName), ntab=2; + WDL_PtrList *tab[ntab]= { &s_freetype_regfonts, &s_freetype_fontlist }; + int x; + bool match; + static WDL_TypedBuf matchlist; + matchlist.Resize(0,false); + + for (x=0;x *t = tab[x]; + int px = t->LowerBound(lfFaceName,&match,sortByFilePart); + for (;;) + { + const char *fn = t->Get(px++); + if (!fn) break; + const char *fnp = WDL_get_filepart(fn); + if (strnicmp(fnp,lfFaceName,fn_len)) break; + + fontScoreMatched s; + const char *residual = fnp + fn_len; + const char *dash = strstr(residual,"-"); + const char *ext = WDL_get_fileext(residual); + + if (!dash) + { + if (!strnicmp(residual,"Bold",4) || + !strnicmp(residual,"Italic",6) || + !strnicmp(residual,"Light",5) || + !strnicmp(residual,"Oblique",7)) + dash = residual; + else if (ext > residual && ext <= residual+2) + { + char c1 = residual[0],c2=residual[1]; + if (c1>0) c1=toupper(c1); + if (c2>0) c2=toupper(c2); + if ((c1 == 'B' || c1 == 'I' || c1 == 'L') && + (c2 == 'B' || c2 == 'I' || c2 == 'L' || c2 == '.')) + dash=residual; + } + } + + s.fn = fn; + s.score1 = (int)((dash?dash:ext)-residual); // characters between font and either "-" or "." + + if (exact > 0) + { + if (s.score1) continue; + } + else if (exact < 0 && !s.score1) + { + continue; + } + + s.score2 = 0; + + if (dash) { if (*dash == '-') dash++; } + else dash=residual; + + while (*dash && *dash != '.') + { + if (*dash > 0 && isalnum(*dash)) s.score2++; + dash++; + } + + if (WDL_stristr(residual,"Regular")) s.score2 -= 7; // ignore "Regular" + if (italic && WDL_stristr(residual,"Italic")) s.score2 -= 6+7; + else if (italic && WDL_stristr(residual,"Oblique")) s.score2 -= 7+3; // if Italic isnt available, use Oblique + if (weight >= FW_BOLD && WDL_stristr(residual,"Bold")) s.score2 -= 4+7; + else if (weight <= FW_LIGHT && WDL_stristr(residual,"Light")) s.score2 -= 5+7; + + if (ext > residual && ext <= residual+2) + { + char c1 = residual[0],c2=residual[1]; + if (c1>0) c1=toupper(c1); + if (c2>0) c2=toupper(c2); + + if (weight >= FW_BOLD && (c1 == 'B' || c2 == 'B')) s.score2 -= 2; + else if (weight <= FW_LIGHT && (c1 == 'L' || c2 == 'L')) s.score2 -= 2; + if (italic && (c1 == 'I' || c2 == 'I')) s.score2 -= 2; + } + + s.score2 = s.score2*ntab + x; + + matchlist.Add(s); + } + } + if (matchlist.GetSize()>1) + qsort(matchlist.Get(),matchlist.GetSize(),sizeof(fontScoreMatched),fontScoreMatched::sortfunc); + + for (x=0; x < matchlist.GetSize(); x ++) + { + const fontScoreMatched *s = matchlist.Get()+x; + + FT_Face face=NULL; + //printf("trying '%s' for '%s' score %d,%d w %d i %d\n",s->fn,lfFaceName,s->score1,s->score2,weight,italic); + FT_New_Face(s_freetype,s->fn,0,&face); + if (face) + { + lstrcpyn_safe(matchFnOut,s->fn,matchFnOutSize); + return face; + } + } + return NULL; +} +#endif // !SWELL_FONTCONFIG + +#endif // SWELL_FREETYPE + +HDC SWELL_CreateMemContext(HDC hdc, int w, int h) +{ + LICE_MemBitmap * bm = new LICE_MemBitmap(w,h); + if (WDL_NOT_NORMALLY(!bm)) return 0; + LICE_Clear(bm,LICE_RGBA(0,0,0,0)); + + HDC__ *ctx=SWELL_GDP_CTX_NEW(); + ctx->surface = bm; + ctx->surface_offs.x=0; + ctx->surface_offs.y=0; + ctx->dirty_rect_valid=false; + + SetTextColor(ctx,0); + return ctx; +} + + +void SWELL_DeleteGfxContext(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct)) + { + delete ct->surface; + ct->surface=0; + SWELL_GDP_CTX_DELETE(ct); + } +} + +HPEN CreatePen(int attr, int wid, int col) +{ + return CreatePenAlpha(attr,wid,col,1.0f); +} + +HBRUSH CreateSolidBrush(int col) +{ + return CreateSolidBrushAlpha(col,1.0f); +} + +HPEN CreatePenAlpha(int attr, int wid, int col, float alpha) +{ + HGDIOBJ__ *pen=GDP_OBJECT_NEW(); + pen->type=TYPE_PEN; + pen->wid=wid<0?0:wid; + pen->alpha = alpha; + pen->color=LICE_RGBA_FROMNATIVE(col); + return pen; +} +HBRUSH CreateSolidBrushAlpha(int col, float alpha) +{ + HGDIOBJ__ *brush=GDP_OBJECT_NEW(); + brush->type=TYPE_BRUSH; + brush->color=LICE_RGBA_FROMNATIVE(col); + brush->alpha = alpha; + brush->wid=0; + return brush; +} + +void SetBkColor(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->curbkcol=LICE_RGBA_FROMNATIVE(col,255); +} + +void SetBkMode(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->curbkmode=col; +} + +HGDIOBJ GetStockObject(int wh) +{ + switch (wh) + { + case NULL_BRUSH: + { + static HGDIOBJ__ br={0,}; + br.type=TYPE_BRUSH; + br.wid=-1; + return &br; + } + case NULL_PEN: + { + static HGDIOBJ__ pen={0,}; + pen.type=TYPE_PEN; + pen.wid=-1; + return &pen; + } + } + WDL_ASSERT(false); + return 0; +} + +HFONT CreateFont(int lfHeight, int lfWidth, int lfEscapement, int lfOrientation, int lfWeight, char lfItalic, + char lfUnderline, char lfStrikeOut, char lfCharSet, char lfOutPrecision, char lfClipPrecision, + char lfQuality, char lfPitchAndFamily, const char *lfFaceName) +{ + HGDIOBJ__ *font = GDP_OBJECT_NEW(); + font->typedata = NULL; + font->type=TYPE_FONT; + font->alpha = 1.0f; +#ifdef SWELL_FREETYPE + if (!s_freetype_failed && !s_freetype) + { + s_freetype_failed = !!FT_Init_FreeType(&s_freetype); + if (s_freetype) + { +#ifdef SWELL_FONTCONFIG + if (!s_fontconfig) s_fontconfig = FcInitLoadConfigAndFonts(); +#else + ScanFontDirectory("/usr/share/fonts"); + + qsort(s_freetype_fontlist.GetList(),s_freetype_fontlist.GetSize(),sizeof(const char *),(int (*)(const void *,const void*))sortByFilePart); +#endif + } + } + if (lfWidth<0) lfWidth=-lfWidth; + if (lfHeight<0) lfHeight=-lfHeight; + + static WDL_PtrList cache; + const int cache_flag = wdl_max(lfWeight,0) | (lfItalic ? (1<<30) : 0); + FT_Face face=NULL; + for (int x=0;xm_flags==cache_flag && + ent->m_w == lfWidth && + ent->m_h == lfHeight && + !strcmp(ent->m_name,lfFaceName?lfFaceName:"")) + { + face = ent->m_face; + swell_last_font_filename = ent->m_fndesc; + FT_Reference_Face(face); + if (x < cache.GetSize()-1) + { + cache.Delete(x); + cache.Add(ent); // make this cache entry most recent + } + break; + } + } + + if (!face && s_freetype) + { + int face_idx=0; + char face_fn[1024]; + face_fn[0]=0; +#ifdef SWELL_FONTCONFIG + if (!face && s_fontconfig) + { + FcPattern *pat = FcPatternCreate(); + if (pat) + { + if (lfFaceName && *lfFaceName) FcPatternAddString(pat,FC_FAMILY,(FcChar8*)lfFaceName); + if (lfWeight > 0) + { + int wt; + if (lfWeight >= FW_HEAVY) wt=FC_WEIGHT_HEAVY; + else if (lfWeight >= FW_EXTRABOLD) wt=FC_WEIGHT_EXTRABOLD; + else if (lfWeight >= FW_BOLD) wt=FC_WEIGHT_BOLD; + else if (lfWeight >= FW_SEMIBOLD) wt=FC_WEIGHT_SEMIBOLD; + else if (lfWeight >= FW_MEDIUM) wt=FC_WEIGHT_MEDIUM; + else if (lfWeight >= FW_NORMAL) wt=FC_WEIGHT_NORMAL; + else if (lfWeight >= FW_LIGHT) wt=FC_WEIGHT_LIGHT; + else if (lfWeight >= FW_EXTRALIGHT) wt=FC_WEIGHT_EXTRALIGHT; + else wt=FC_WEIGHT_THIN; + FcPatternAddInteger(pat,FC_WEIGHT,wt); + } + if (lfItalic) + FcPatternAddInteger(pat,FC_SLANT,FC_SLANT_ITALIC); + FcConfigSubstitute(s_fontconfig,pat,FcMatchPattern); + FcDefaultSubstitute(pat); + FcResult result; + FcPattern *hit = FcFontMatch(s_fontconfig,pat,&result); + if (hit) + { + FcChar8 *fn = NULL; + if (FcPatternGetString(hit,FC_FILE,0,&fn) == FcResultMatch && fn && *fn) + { + if (FcPatternGetInteger(hit,FC_INDEX,0,&face_idx) != FcResultMatch) face_idx=0; + FT_New_Face(s_freetype,(const char *)fn,face_idx,&face); + if (face) lstrcpyn_safe(face_fn,(const char *)fn,sizeof(face_fn)); + } + FcPatternDestroy(hit); + } + FcPatternDestroy(pat); + } + } +#else + + if (!face && lfFaceName && *lfFaceName) face = MatchFont(lfFaceName,lfWeight,lfItalic,0, face_fn, sizeof(face_fn)); + + if (!face) + { + static const char *fallbacklist[2]; + const int wl = (lfFaceName && ( + !strnicmp(lfFaceName,"Courier",7) || + !strnicmp(lfFaceName,"Fixed",5) + )) ? 1 : 0; + if (!fallbacklist[wl]) + { + static const char *ent[2] = { "ft_font_fallback", "ft_font_fallback_fixedwidth" }; + static const char *def[2] = { + "// LiberationSans Cantarell FreeSans DejaVuSans NotoSans Oxygen Arial Verdana", + "// LiberationMono FreeMono DejaVuSansMono NotoMono OxygenMono Courier" + }; + char tmp[1024]; + GetPrivateProfileString(".swell",ent[wl],"",tmp,sizeof(tmp),""); + if (!tmp[0]) + WritePrivateProfileString(".swell",ent[wl],def[wl],""); + const char *rp = tmp; + while (*rp == ' ' || *rp == '\t') rp++; + if (!*rp || *rp == '/') rp = def[wl] + 3; + + char *b = (char*) malloc(strlen(rp) + 2); + fallbacklist[wl] = b ? b : "FreeSans\0"; + if (b) + { + for(;;) + { + while (*rp == ' ' || *rp == '\t') rp++; + while (*rp && *rp != ' ' && *rp != '\t') *b++ = *rp++; + *b++=0; + if (!*rp) break; + } + *b++=0; + } + } + for (int exact=0;exact<2 && !face;exact++) + { + const char *l = fallbacklist[wl]; + while (*l && !face) + { + face = MatchFont(l,lfWeight,lfItalic,exact?-1:1, face_fn, sizeof(face_fn)); + l += strlen(l)+1; + } + } + } +#endif + + if (face) + { + if (face_idx) snprintf_append(face_fn,sizeof(face_fn)," <%d>",face_idx); + fontConfigCacheEnt *ce = new fontConfigCacheEnt(lfFaceName?lfFaceName:"",cache_flag,lfWidth,lfHeight,face, face_fn); + cache.Add(ce); + if (cache.GetSize()>SWELL_FREETYPE_CACHE_SIZE) cache.Delete(0,true); + swell_last_font_filename = ce->m_fndesc; + + FT_Set_Char_Size(face,lfWidth*64, lfHeight*64,0,0); // 72dpi + } + } + font->typedata = face; +#endif + + return font; +} + + +HFONT CreateFontIndirect(LOGFONT *lf) +{ + return CreateFont(lf->lfHeight, lf->lfWidth,lf->lfEscapement, lf->lfOrientation, lf->lfWeight, lf->lfItalic, + lf->lfUnderline, lf->lfStrikeOut, lf->lfCharSet, lf->lfOutPrecision,lf->lfClipPrecision, + lf->lfQuality, lf->lfPitchAndFamily, lf->lfFaceName); +} + +int GetTextFace(HDC ctx, int nCount, LPTSTR lpFaceName) +{ + if (lpFaceName && nCount>0) lpFaceName[0]=0; +#ifdef SWELL_FREETYPE + HDC__ *ct=(HDC__*)ctx; + if (!HDC_VALID(ct) || WDL_NOT_NORMALLY(nCount<1 || !lpFaceName) || !ct->curfont) return 0; + + const FT_FaceRec *p = (const FT_FaceRec *)ct->curfont->typedata; + if (p) + { + lstrcpyn_safe(lpFaceName, p->family_name, nCount); + return 1; + } +#endif + return 0; +} + +void DeleteObject(HGDIOBJ pen) +{ + if (HGDIOBJ_VALID(pen)) + { + HGDIOBJ__ *p=(HGDIOBJ__ *)pen; + if (--p->additional_refcnt < 0) + { + if (WDL_NORMALLY(p->type == TYPE_PEN || p->type == TYPE_BRUSH || p->type == TYPE_FONT || p->type == TYPE_BITMAP)) + { + if (p->type == TYPE_FONT) + { +#ifdef SWELL_FREETYPE + if (p->typedata) + { + FT_Done_Face((FT_Face)p->typedata); + p->typedata = 0; + } +#endif + } + else if (p->type == TYPE_PEN || p->type == TYPE_BRUSH) + { + if (p->wid<0) return; + } + else if (p->type == TYPE_BITMAP) + { + if (p->wid>0) delete (LICE_IBitmap *)p->typedata; + p->typedata = NULL; + } + + GDP_OBJECT_DELETE(p); + } + } + } +} + + +HGDIOBJ SelectObject(HDC ctx, HGDIOBJ pen) +{ + HDC__ *c=(HDC__ *)ctx; + HGDIOBJ__ *p= pen; + HGDIOBJ__ **mod=0; + if (!HDC_VALID(c)||WDL_NOT_NORMALLY(!p)) return 0; + + if (p == (HGDIOBJ__ *)TYPE_PEN) mod=&c->curpen; + else if (p == (HGDIOBJ__ *)TYPE_BRUSH) mod=&c->curbrush; + else if (p == (HGDIOBJ__ *)TYPE_FONT) mod=&c->curfont; + + if (mod) + { + HGDIOBJ__ *np=*mod; + *mod=0; + return np?np:p; + } + + if (!HGDIOBJ_VALID(p)) return 0; + + if (p->type == TYPE_PEN) mod=&c->curpen; + else if (p->type == TYPE_BRUSH) mod=&c->curbrush; + else if (p->type == TYPE_FONT) mod=&c->curfont; + else return 0; + + HGDIOBJ__ *op=*mod; + if (!op) op=(HGDIOBJ__ *)(INT_PTR)p->type; + if (op != p) + { + *mod=p; + } + return op; +} + + +static void swell_DirtyContext(HDC__ *out, int x1, int y1, int x2, int y2) +{ + if (x2surface_offs.x; + x2+=out->surface_offs.x; + y1+=out->surface_offs.y; + y2+=out->surface_offs.y; + + if (!out->dirty_rect_valid) + { + out->dirty_rect_valid=true; + out->dirty_rect.left = x1; + out->dirty_rect.top = y1; + out->dirty_rect.right = x2; + out->dirty_rect.bottom = y2; + } + else + { + if (out->dirty_rect.left > x1) out->dirty_rect.left=x1; + if (out->dirty_rect.top > y1) out->dirty_rect.top = y1; + if (out->dirty_rect.right < x2) out->dirty_rect.right = x2; + if (out->dirty_rect.bottom < y2) out->dirty_rect.bottom = y2; + } +} + + +void SWELL_FillRect(HDC ctx, const RECT *r, HBRUSH br) +{ + HDC__ *c=(HDC__ *)ctx; + HGDIOBJ__ *b=(HGDIOBJ__ *) br; + if (!HDC_VALID(c) || !HGDIOBJ_VALID(b,TYPE_BRUSH)) return; + if (!c->surface) return; + + if (b->wid<0) return; + LICE_FillRect(c->surface, + r->left+c->surface_offs.x, + r->top+c->surface_offs.y, + r->right-r->left,r->bottom-r->top,b->color,b->alpha,LICE_BLIT_MODE_COPY); + swell_DirtyContext(ctx,r->left,r->top,r->right,r->bottom); +} + +void RoundRect(HDC ctx, int x, int y, int x2, int y2, int xrnd, int yrnd) +{ + xrnd/=3; + yrnd/=3; + POINT pts[10]={ // todo: curves between edges + {x,y+yrnd}, + {x+xrnd,y}, + {x2-xrnd,y}, + {x2,y+yrnd}, + {x2,y2-yrnd}, + {x2-xrnd,y2}, + {x+xrnd,y2}, + {x,y2-yrnd}, + {x,y+yrnd}, + {x+xrnd,y}, +}; + + WDL_GDP_Polygon(ctx,pts,sizeof(pts)/sizeof(pts[0])); +} + +void Ellipse(HDC ctx, int l, int t, int r, int b) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c) || !c->surface) return; + + swell_DirtyContext(ctx,l,t,r,b); + + l += c->surface_offs.x; + t += c->surface_offs.y; + r += c->surface_offs.x; + b += c->surface_offs.y; + + int rad = wdl_min(r-l, b-t)/2; // todo: actual ellipse, for now just circles + + bool wantPen = HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0; + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0) + { + int use_rad = rad; + if (use_rad > 0) LICE_FillCircle(c->surface,l+use_rad,t+use_rad,use_rad,c->curbrush->color,c->curbrush->alpha,LICE_BLIT_MODE_COPY,!wantPen); + } + if (wantPen) + { + LICE_Circle(c->surface,l+rad,t+rad,rad,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,true); + } +} + +void Rectangle(HDC ctx, int l, int t, int r, int b) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c) || !c->surface) return; + + //CGRect rect=CGRectMake(l,t,r-l,b-t); + + swell_DirtyContext(ctx,l,t,r,b); + + l += c->surface_offs.x; + t += c->surface_offs.y; + r += c->surface_offs.x; + b += c->surface_offs.y; + + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0) + { + LICE_FillRect(c->surface,l,t,r-l,b-t,c->curbrush->color,c->curbrush->alpha,LICE_BLIT_MODE_COPY); + } + if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0) + { + if (r>l+1 && b>t+1) + LICE_DrawRect(c->surface,l,t,r-l-1,b-t-1,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY); + } +} + +void Polygon(HDC ctx, POINT *pts, int npts) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c) || !c->surface) return; + const bool fill=HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH)&&c->curbrush->wid>=0; + const bool outline = HGDIOBJ_VALID(c->curpen,TYPE_PEN)&&c->curpen->wid>=0; + if ((!fill && !outline) || npts<2 || !pts) return; + + const int dx=c->surface_offs.x; + const int dy=c->surface_offs.y; + + int minx=c->surface->getWidth()+1, maxx=0; + int miny=c->surface->getHeight()+1, maxy=0; + + if (fill) + { + int _tmp[256], *tmp=_tmp; + if (npts>128) + { + tmp = (int *)malloc(npts*2*sizeof(int)); + } + if (tmp) + { + for (int x = 0; x < npts; x ++) + { + const int xp = pts[x].x, yp = pts[x].y; + if (xp < minx) minx=xp; + if (xp > maxx) maxx=xp; + if (yp < miny) miny=yp; + if (yp > maxy) maxy=yp; + tmp[x] = xp + dx; + tmp[x+npts] = yp + dy; + } + LICE_FillConvexPolygon(c->surface,tmp, tmp+npts,npts, + c->curbrush->color,c->curbrush->alpha,LICE_BLIT_MODE_COPY); + } + if (tmp != _tmp) free(tmp); + } + if (outline) + { + int lx=0,ly=0,sx=0,sy=0; + for (int x = 0; x < npts; x ++) + { + const int xp = pts[x].x, yp = pts[x].y; + if (xp < minx) minx=xp; + if (xp > maxx) maxx=xp; + if (yp < miny) miny=yp; + if (yp > maxy) maxy=yp; + if (x) + LICE_Line(c->surface,xp+dx,yp+dy,lx+dx,ly+dy,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,true); + else { sx=xp; sy=yp; } + lx=xp; + ly=yp; + } + LICE_Line(c->surface,sx+dx,sy+dy,lx+dx,ly+dy,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,true); + } + + if (maxx>minx && maxy>miny) + swell_DirtyContext(ctx, minx,miny,maxx,maxy); +} + +void MoveToEx(HDC ctx, int x, int y, POINT *op) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + if (op) + { + op->x = (int) (c->lastpos_x); + op->y = (int) (c->lastpos_y); + } + c->lastpos_x=(float)x; + c->lastpos_y=(float)y; +} + +void PolyBezierTo(HDC ctx, POINT *pts, int np) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0||np<3) return; + + int x; + float xp=c->lastpos_x,yp=c->lastpos_y; + for (x = 0; x < np-2; x += 3) + { + // todo + xp=(float)pts[x+2].x; + yp=(float)pts[x+2].y; + } + c->lastpos_x=(float)xp; + c->lastpos_y=(float)yp; +} + + +void SWELL_LineTo(HDC ctx, int x, int y) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0) return; + + float fx=(float)x,fy=(float)y; + + int dx=c->surface_offs.x; + int dy=c->surface_offs.y; + int lx = (int)c->lastpos_x, ly = (int) c->lastpos_y; + if (c->surface) + LICE_Line(c->surface,x+dx,y+dy,lx+dx,ly+dy,c->curpen->color,c->curpen->alpha,LICE_BLIT_MODE_COPY,false); + + c->lastpos_x=fx; + c->lastpos_y=fy; + + if (lxcurpen,TYPE_PEN)||c->curpen->wid<0||nseg<1) return; + + while (nseg-->0) + { + DWORD cnt=*cnts++; + if (!cnt) continue; + if (!--cnt) { pts++; continue; } + + // CGContextMoveToPoint(c->ctx,(float)pts->x,(float)pts->y); + pts++; + + while (cnt--) + { +// CGContextAddLineToPoint(c->ctx,(float)pts->x,(float)pts->y); + pts++; + } + } +// CGContextStrokePath(c->ctx); +} +void *SWELL_GetCtxGC(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return 0; + return NULL; +} + + +void SWELL_SetPixel(HDC ctx, int x, int y, int c) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct) || !ct->surface) return; + LICE_PutPixel(ct->surface,x+ct->surface_offs.x, y+ct->surface_offs.y, LICE_RGBA_FROMNATIVE(c,255),1.0f,LICE_BLIT_MODE_COPY); + swell_DirtyContext(ct,x,y,x+1,y+1); +} + +HFONT SWELL_GetDefaultFont() +{ + static HFONT def; + if (!def) + { + def = CreateFont(g_swell_ctheme.default_font_size,0,0,0,FW_NORMAL,0,0,0,0,0,0,0,0,g_swell_deffont_face); + } + return def; +} + +BOOL GetTextMetrics(HDC ctx, TEXTMETRIC *tm) +{ + HDC__ *ct=(HDC__ *)ctx; + if (tm) // give some sane defaults + { + tm->tmInternalLeading=0; + tm->tmAscent=8; + tm->tmDescent=0; + tm->tmHeight=8; + tm->tmAveCharWidth = 8; + } + if (!HDC_VALID(ct)||WDL_NOT_NORMALLY(!tm)) return 0; + +#ifdef SWELL_FREETYPE + HGDIOBJ__ *font = HGDIOBJ_VALID(ct->curfont,TYPE_FONT) ? ct->curfont : SWELL_GetDefaultFont(); + if (font && font->typedata) + { + FT_Face face=(FT_Face) font->typedata; + tm->tmAscent = face->size->metrics.ascender/64; + tm->tmDescent = -face->size->metrics.descender/64; + tm->tmHeight = (face->size->metrics.ascender - face->size->metrics.descender)/64; + tm->tmAveCharWidth = face->size->metrics.height / 112; + + tm->tmInternalLeading = (face->size->metrics.ascender + face->size->metrics.descender - face->size->metrics.height)/64; + if (tm->tmInternalLeading<0) tm->tmInternalLeading=0; + } +#endif + + return 1; +} + + +int DrawText(HDC ctx, const char *buf, int buflen, RECT *r, int align) +{ + WDL_ASSERT((align & DT_SINGLELINE) || !(align & (DT_VCENTER | DT_BOTTOM))); + HDC__ *ct=(HDC__ *)ctx; + if (WDL_NOT_NORMALLY(!r)) return 0; + + int lineh = 8; + int charw = 8; + + HGDIOBJ__ *font = NULL; + int ascent=8, descent=0; +#ifdef SWELL_FREETYPE + font = HDC_VALID(ct) && HGDIOBJ_VALID(ct->curfont,TYPE_FONT) ? ct->curfont : SWELL_GetDefaultFont(); + FT_Face face = NULL; + if (font && font->typedata) + { + face=(FT_Face)font->typedata; + lineh = face->size->metrics.height/64; + ascent = face->size->metrics.ascender/64; + descent = face->size->metrics.descender/64; + charw = face->size->metrics.height / 112; + } +#endif + + if (align&DT_CALCRECT) + { + int xpos=0; + int ypos=0; + + r->bottom=r->top; + bool in_prefix=false; + while (buflen && *buf) // if buflen<0, go forever + { + int c=0, charlen = wdl_utf8_parsechar(buf,&c); + buf+=charlen; + if (buflen > 0) + { + buflen -= charlen; + if (buflen < 0) buflen=0; + } + if (!c) break; + + if (c=='&' && !in_prefix && !(align&DT_NOPREFIX)) + { + in_prefix = true; + continue; + } + in_prefix=false; + + if (c == '\n' && (align & DT_SINGLELINE)) c=' '; + + if (c == '\n') { ypos += lineh; xpos=0; } + else if (c != '\r') + { + if (font) + { +#ifdef SWELL_FREETYPE + if (c != '\t' && !FT_Load_Char(face, c, FT_LOAD_DEFAULT) && face->glyph) + { + // measure character + FT_GlyphSlot g = face->glyph; + int rext = xpos; + if ((align&(DT_BOTTOM|DT_VCENTER|DT_CENTER|DT_RIGHT))!=DT_RIGHT) + rext += (g->metrics.width + g->metrics.horiBearingX)/64; + + xpos += g->metrics.horiAdvance/64; + if (rextleft+rext > r->right) r->right = r->left+rext; + + int bext = r->top + ypos + ascent - descent; + if (bext > r->bottom) r->bottom = bext; + continue; + } +#endif + } + xpos += c=='\t' ? charw*5 : charw; + int bext = r->top + ypos + ascent - descent; + if (bext > r->bottom) r->bottom = bext; + if (r->left+xpos>r->right) r->right=r->left+xpos; + } + } + return r->bottom-r->top; + } + if (!HDC_VALID(ct)) return 0; + + RECT use_r = *r; + if ((align & DT_VCENTER) && use_r.top > use_r.bottom) + { + use_r.top = r->bottom; + use_r.bottom = r->top; + } + + use_r.left += ct->surface_offs.x; + use_r.right += ct->surface_offs.x; + use_r.top += ct->surface_offs.y; + use_r.bottom += ct->surface_offs.y; + + int xpos = use_r.left; + int ypos = use_r.top; + if (align&(DT_CENTER|DT_VCENTER|DT_RIGHT|DT_BOTTOM)) + { + RECT tr={0,}; + DrawText(ctx,buf,buflen,&tr,align|DT_CALCRECT); + + if (align&DT_CENTER) + xpos -= ((tr.right-tr.left) - (use_r.right-use_r.left))/2; + else if (align&DT_RIGHT) + xpos = use_r.right - (tr.right-tr.left); + + if (align&DT_VCENTER) + ypos -= ((tr.bottom-tr.top) - (use_r.bottom-use_r.top))/2; + else if (align&DT_BOTTOM) + ypos = use_r.bottom - (tr.bottom-tr.top); + } + LICE_IBitmap *surface = ct->surface; + int fgcol = ct->cur_text_color_int; + int bgcol = ct->curbkcol; + int bgmode = ct->curbkmode; + + + int clip_x1=wdl_max(use_r.left,0), clip_y1 = wdl_max(use_r.top,0); + int clip_w=0, clip_h=0; + if (surface) + { + clip_w = wdl_min(use_r.right,surface->getWidth())-clip_x1; + clip_h = wdl_min(use_r.bottom,surface->getHeight())-clip_y1; + if (clip_w<0)clip_w=0; + if (clip_h<0)clip_h=0; + } + + LICE_SubBitmap clipbm(surface,clip_x1,clip_y1,clip_w,clip_h); + if (surface && !(align&DT_NOCLIP)) { surface = &clipbm; xpos-=clip_x1; ypos-=clip_y1; } + + int left_xpos = xpos,start_ypos = ypos, max_ypos=ypos,max_xpos=0; + bool in_prefix=false; + + while (buflen && *buf) + { + int c=0, charlen = wdl_utf8_parsechar(buf,&c); + if (buflen>0) + { + buflen -= charlen; + if (buflen<0) buflen=0; + } + buf+=charlen; + + bool doUl=in_prefix; + + if (c=='&' && !in_prefix && !(align&DT_NOPREFIX)) + { + in_prefix = true; + continue; + } + in_prefix=false; + + if (c == '\n' && (align & DT_SINGLELINE)) c=' '; + + if (c=='\n' && !(align&DT_SINGLELINE)) { xpos=left_xpos; ypos+=lineh; } + else if (c=='\r') {} + else + { + bool needr=true; + if (font) + { +#ifdef SWELL_FREETYPE + if (c != '\t' && !FT_Load_Char(face, c, FT_LOAD_RENDER) && face->glyph) + { + FT_GlyphSlot g = face->glyph; + const int ha = g->metrics.horiAdvance/64; + if (bgmode==OPAQUE) LICE_FillRect(surface,xpos,ypos,ha,(align & DT_SINGLELINE) ? (ascent-descent) : lineh,bgcol,1.0f,LICE_BLIT_MODE_COPY); + + if (g->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + LICE_DrawMonoGlyph(surface,xpos+g->bitmap_left,ypos+ascent-g->bitmap_top,fgcol,(const unsigned char*)g->bitmap.buffer,g->bitmap.width,g->bitmap.pitch,g->bitmap.rows,1.0f,LICE_BLIT_MODE_COPY); + } + else // FT_PIXEL_MODE_GRAY (hopefully!) + { + LICE_DrawGlyphEx(surface,xpos+g->bitmap_left,ypos+ascent-g->bitmap_top,fgcol,(LICE_pixel_chan *)g->bitmap.buffer,g->bitmap.width,g->bitmap.pitch,g->bitmap.rows,1.0f,LICE_BLIT_MODE_COPY); + } + if (doUl) + { + int xw = g->metrics.width/64; + if (xw > 1) xw--; + LICE_Line(surface,xpos + g->metrics.horiBearingX/64,ypos+ascent+1, + xpos + xw,ypos+ascent+1,fgcol,1.0f,LICE_BLIT_MODE_COPY,false); + } + + int rext = xpos + (g->metrics.width + g->metrics.horiBearingX)/64; + if (rext<=xpos) rext=xpos + ha; + if (rext > max_xpos) max_xpos=rext; + xpos += ha; + const int bext = ypos + ascent-descent; + if (max_ypos < bext) max_ypos=bext; + needr=false; + } +#endif + } + + if (needr) + { + if (c=='\t') + { + if (bgmode==OPAQUE) LICE_FillRect(surface,xpos,ypos,charw*5,(align & DT_SINGLELINE) ? (ascent-descent) : lineh,bgcol,1.0f,LICE_BLIT_MODE_COPY); + xpos+=charw*5; + + const int bext = ypos+ascent-descent; + if (max_ypos < bext) max_ypos=bext; + } + else + { + if (bgmode==OPAQUE) LICE_FillRect(surface,xpos,ypos,charw,(align & DT_SINGLELINE) ? (ascent-descent) : lineh,bgcol,1.0f,LICE_BLIT_MODE_COPY); + LICE_DrawChar(surface,xpos,ypos,c,fgcol,1.0f,LICE_BLIT_MODE_COPY); + if (doUl) LICE_Line(surface,xpos,ypos+(ascent-descent)+1,xpos+charw,ypos+(ascent-descent)+1,fgcol,1.0f,LICE_BLIT_MODE_COPY,false); + + const int bext=ypos+ascent-descent+(doUl ? 2:1); + if (max_ypos < bext) max_ypos=bext; + xpos+=charw; + } + } + } + if(xpos>max_xpos)max_xpos=xpos; + } + if (surface==&clipbm) + swell_DirtyContext(ct,clip_x1+left_xpos,clip_y1+start_ypos,clip_x1+max_xpos,clip_y1+max_ypos); + else + swell_DirtyContext(ct,left_xpos,start_ypos,max_xpos,max_ypos); + return max_ypos - start_ypos; +} + + +int GetTextColor(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return -1; + return ct->cur_text_color_int; +} +void SetTextColor(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->cur_text_color_int = LICE_RGBA_FROMNATIVE(col,255); + +} + + +////////// todo: some sort of HICON emul + +HICON LoadNamedImage(const char *name, bool alphaFromMask) +{ +#ifdef SWELL_TARGET_GDK + char buf[1024]; + GdkPixbuf *pb = NULL; + if (strstr(name,"/")) + { + lstrcpyn_safe(buf,name,sizeof(buf)); + pb = gdk_pixbuf_new_from_file(buf,NULL); + } + else + { + GetModuleFileName(NULL,buf,sizeof(buf)); + WDL_remove_filepart(buf); + snprintf_append(buf,sizeof(buf),"/Resources/%s.png",name); + pb = gdk_pixbuf_new_from_file(buf,NULL); + if (!pb) + { + WDL_remove_fileext(buf); + lstrcatn(buf,".ico",sizeof(buf)); + pb = gdk_pixbuf_new_from_file(buf,NULL); + } + if (!pb) + { + WDL_remove_fileext(buf); + lstrcatn(buf,".bmp",sizeof(buf)); + pb = gdk_pixbuf_new_from_file(buf,NULL); + } + } + if (pb) + { + HGDIOBJ__ *ret=NULL; + const int w = gdk_pixbuf_get_width(pb), h = gdk_pixbuf_get_height(pb); + const int bpc = gdk_pixbuf_get_bits_per_sample(pb), chan = gdk_pixbuf_get_n_channels(pb); + const int alpha = gdk_pixbuf_get_has_alpha(pb); + const guchar *rd = gdk_pixbuf_get_pixels(pb); + if (bpc == 8 && (chan == 4 || chan == 3) && w > 0 && h>0 && rd) + { + LICE_MemBitmap *bm = new LICE_MemBitmap(w,h); + LICE_pixel_chan *wr = (LICE_pixel_chan*)bm->getBits(); + if (wr) + { + const int rdadv = gdk_pixbuf_get_rowstride(pb); + const int wradv = bm->getRowSpan()*4; + const unsigned char alphamod = !alpha ? 255 : 0; + int y; + for (y=0;y type=TYPE_BITMAP; + ret->alpha = 1.0f; + ret->wid=1; + ret->typedata = bm; + } + else delete wr; + } + g_object_unref(pb); + return ret; + } + + +#endif + return 0; // todo +} + +void DrawImageInRect(HDC hdcOut, HICON in, const RECT *r) +{ + HDC__ *out = (HDC__ *)hdcOut; + if (!HDC_VALID(out) || !HGDIOBJ_VALID(in,TYPE_BITMAP) || !out->surface || !in->typedata) return; + + const int x = r->left, y=r->top, w=r->right-x, h=r->bottom-y; + LICE_IBitmap *src=(LICE_IBitmap *)in->typedata; + LICE_ScaledBlit(out->surface,src, + x+out->surface_offs.x,y+out->surface_offs.y,w,h, + 0,0, src->getWidth(),src->getHeight(), + 1.0f,LICE_BLIT_MODE_COPY|LICE_BLIT_USE_ALPHA|LICE_BLIT_FILTER_BILINEAR); + swell_DirtyContext(out,x,y,x+w,y+h); +} + + +BOOL GetObject(HICON icon, int bmsz, void *_bm) +{ + memset(_bm,0,bmsz); + if (WDL_NOT_NORMALLY(bmsz < 2*(int)sizeof(LONG))) return false; + BITMAP *bm=(BITMAP *)_bm; + HGDIOBJ__ *i = (HGDIOBJ__ *)icon; + if (!HGDIOBJ_VALID(i,TYPE_BITMAP) || !i->typedata) return false; + + bm->bmWidth = ((LICE_IBitmap *)i->typedata)->getWidth(); + bm->bmHeight = ((LICE_IBitmap *)i->typedata)->getHeight(); + + if (bmsz >= (int)sizeof(BITMAP)) + { + bm->bmWidthBytes = ((LICE_IBitmap *)i->typedata)->getRowSpan()*4; + bm->bmPlanes = 1; + bm->bmBitsPixel = 32; + bm->bmBits = ((LICE_IBitmap *)i->typedata)->getBits(); + } + + return true; +} + + +//////////////////////////////////// + + +void BitBltAlphaFromMem(HDC hdcOut, int x, int y, int w, int h, void *inbufptr, int inbuf_span, int inbuf_h, int xin, int yin, int mode, bool useAlphaChannel, float opacity) +{ + // todo: use LICE_WrapperBitmap? +} + +void BitBltAlpha(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode, bool useAlphaChannel, float opacity) +{ + HDC__ *in = (HDC__ *)hdcIn; + HDC__ *out = (HDC__ *)hdcOut; + if (!HDC_VALID(out) || !HDC_VALID(in)) return; + if (!in->surface || !out->surface) return; + LICE_Blit(out->surface,in->surface, + x+out->surface_offs.x,y+out->surface_offs.y, + xin+in->surface_offs.x,yin+in->surface_offs.y,w,h, + opacity,LICE_BLIT_MODE_COPY|(useAlphaChannel?LICE_BLIT_USE_ALPHA:0)); + swell_DirtyContext(out,x,y,x+w,y+h); +} + +void BitBlt(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode) +{ + HDC__ *in = (HDC__ *)hdcIn; + HDC__ *out = (HDC__ *)hdcOut; + if (!HDC_VALID(out) || !HDC_VALID(in)) return; + if (!in->surface || !out->surface) return; + LICE_Blit(out->surface,in->surface, + x+out->surface_offs.x,y+out->surface_offs.y, + xin+in->surface_offs.x,yin+in->surface_offs.y,w,h, + 1.0f,LICE_BLIT_MODE_COPY | (mode == (int)SRCCOPY_USEALPHACHAN ? LICE_BLIT_USE_ALPHA : 0)); + swell_DirtyContext(out,x,y,x+w,y+h); +} + +void StretchBltFromMem(HDC hdcOut, int x, int y, int w, int h, const void *bits, int srcw, int srch, int srcspan) +{ + HDC__ *out = (HDC__ *)hdcOut; + if (!HDC_VALID(out) || !bits) return; + if (!out->surface) return; + + LICE_WrapperBitmap srcbm((LICE_pixel*)bits,srcw,srch,srcspan,false); + LICE_ScaledBlit(out->surface,&srcbm, + x+out->surface_offs.x,y+out->surface_offs.y,w,h, + 0,0,srcw,srch, + 1.0f,LICE_BLIT_MODE_COPY); + swell_DirtyContext(out,x,y,x+w,y+h); +} + +void StretchBlt(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int srcw, int srch, int mode) +{ + HDC__ *in = (HDC__ *)hdcIn; + HDC__ *out = (HDC__ *)hdcOut; + if (!HDC_VALID(out) || !HDC_VALID(in)) return; + if (!in->surface || !out->surface) return; + LICE_ScaledBlit(out->surface,in->surface, + x+out->surface_offs.x,y+out->surface_offs.y,w,h, + xin+in->surface_offs.x,yin+in->surface_offs.y,srcw,srch, + 1.0f,LICE_BLIT_MODE_COPY | (mode == (int)SRCCOPY_USEALPHACHAN ? LICE_BLIT_USE_ALPHA : 0)); + swell_DirtyContext(out,x,y,x+w,y+h); +} + +void SWELL_FillDialogBackground(HDC hdc, const RECT *r, int level) +{ + HBRUSH br = CreateSolidBrush(g_swell_ctheme._3dface); + FillRect(hdc,r,br); + DeleteObject(br); +} + +HGDIOBJ SWELL_CloneGDIObject(HGDIOBJ a) +{ + if (HGDIOBJ_VALID(a)) + { + a->additional_refcnt++; + return a; + } + return NULL; +} + +void SWELL_PushClipRegion(HDC ctx) +{ +// HDC__ *ct=(HDC__ *)ctx; +// if (ct && ct->ctx) CGContextSaveGState(ct->ctx); +} + +void SWELL_SetClipRegion(HDC ctx, const RECT *r) +{ +// HDC__ *ct=(HDC__ *)ctx; +// if (ct && ct->ctx) CGContextClipToRect(ct->ctx,CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top)); + +} + +void SWELL_PopClipRegion(HDC ctx) +{ +// HDC__ *ct=(HDC__ *)ctx; +// if (ct && ct->ctx) CGContextRestoreGState(ct->ctx); +} + +void *SWELL_GetCtxFrameBuffer(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct)&&ct->surface) return ct->surface->getBits(); + return 0; +} + + + +struct swell_gdpLocalContext +{ + HDC__ ctx; + RECT clipr; +}; + + + +HDC SWELL_internalGetWindowDC(HWND h, bool calcsize_on_first) +{ + if (WDL_NOT_NORMALLY(!h)) return NULL; + + int xoffs=0,yoffs=0; + int wndw = h->m_position.right-h->m_position.left; + int wndh = h->m_position.bottom-h->m_position.top; + + bool vis=true; + HWND starth = h; + int ltrim=0, ttrim=0, rtrim=0, btrim=0; + for (;;) + { + if ((calcsize_on_first || h!=starth) && h->m_wndproc) + { + RECT r; + GetWindowContentViewRect(h,&r); + NCCALCSIZE_PARAMS p={{r,},}; + h->m_wndproc(h,WM_NCCALCSIZE,FALSE,(LPARAM)&p); + RECT r2=r; + r=p.rgrc[0]; + + // todo: handle left edges and adjust postions/etc accordingly + if (h==starth) // if initial window, adjust rects to client area (this implies calcsize_on_first being false) + { + wndw=r.right-r.left; + wndh=r.bottom-r.top; + } + yoffs += r.top-r2.top; + xoffs += r.left-r2.left; + } + if (!h->m_visible) vis = false; + + if (h->m_backingstore || !h->m_parent) break; // found our target window + + xoffs += h->m_position.left; + yoffs += h->m_position.top; + + ltrim = wdl_max(ltrim, -xoffs); + ttrim = wdl_max(ttrim, -yoffs); + rtrim = wdl_max(rtrim, xoffs+wndw - h->m_position.right); + btrim = wdl_max(btrim, yoffs+wndh - h->m_position.bottom); + + h = h->m_parent; + } + + swell_gdpLocalContext *p = (swell_gdpLocalContext*)SWELL_GDP_CTX_NEW(); + + p->clipr.left=p->clipr.right=xoffs + ltrim; + p->clipr.top=p->clipr.bottom=yoffs + ttrim; + + if (h->m_backingstore && vis) + { + p->ctx.surface=new LICE_SubBitmap(h->m_backingstore, + xoffs+ltrim,yoffs+ttrim, + wndw-ltrim-rtrim,wndh-ttrim-btrim); + p->clipr.right += p->ctx.surface->getWidth(); + p->clipr.bottom += p->ctx.surface->getHeight(); + } + if (xoffs<0) p->ctx.surface_offs.x = xoffs; + if (yoffs<0) p->ctx.surface_offs.y = yoffs; + p->ctx.surface_offs.x -= ltrim; + p->ctx.surface_offs.y -= ttrim; + + p->ctx.curfont = starth->m_font; + // todo: other GDI defaults? + + return (HDC)p; +} +HDC GetWindowDC(HWND h) +{ + return SWELL_internalGetWindowDC(h,0); +} + +HDC GetDC(HWND h) +{ + return SWELL_internalGetWindowDC(h,1); +} + +void ReleaseDC(HWND h, HDC hdc) +{ + if (WDL_NOT_NORMALLY(!h) || !HDC_VALID(hdc)) return; + swell_gdpLocalContext *p = (swell_gdpLocalContext*)hdc; + + + if (!h->m_paintctx) + { + // handle blitting? + HWND par = h; + while (par && !par->m_backingstore) par=par->m_parent; + if (par) + { + if (p->ctx.dirty_rect_valid) + { + RECT r = p->clipr, dr=p->ctx.dirty_rect; + dr.left += r.left; + dr.top += r.top; + dr.right += r.left; + dr.bottom += r.top; +#if 1 + if (dr.left > r.left) r.left=dr.left; + if (dr.top > r.top) r.top=dr.top; + if (dr.right < r.right) r.right=dr.right; + if (dr.bottom < r.bottom) r.bottom=dr.bottom; +#endif + + if (r.topctx.surface; + SWELL_GDP_CTX_DELETE(hdc); +} + + + +HDC BeginPaint(HWND hwnd, PAINTSTRUCT *ps) +{ + if (WDL_NOT_NORMALLY(!ps)) return 0; + memset(ps,0,sizeof(PAINTSTRUCT)); + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + if (WDL_NOT_NORMALLY(!hwnd->m_paintctx)) return NULL; + + swell_gdpLocalContext *ctx = (swell_gdpLocalContext *)hwnd->m_paintctx; + ps->rcPaint = ctx->clipr; + ps->hdc = &ctx->ctx; + return &ctx->ctx; +} + + +// paint hwnd into bmout, where bmout points at bmout_xpos,bmout_ypos in window coordinates +void SWELL_internalLICEpaint(HWND hwnd, LICE_IBitmap *bmout, int bmout_xpos, int bmout_ypos, bool forceref) +{ +#ifdef _DEBUG + extern int g_swell_in_paint; + g_swell_in_paint++; +#endif + // todo: check to see if any children completely intersect clip region, if so then we don't need to render ourselves + // todo: opaque flag for windows? (to disable above behavior) + // todo: implement/use per-window backing store. + // we would want to only really use them for top level windows, and on those, only update windows (and children of windows) who had backingstore invalidated. + if (hwnd->m_invalidated) forceref=true; + + if (forceref || hwnd->m_child_invalidated) + { + swell_gdpLocalContext ctx; + memset(&ctx,0,sizeof(ctx)); + ctx.ctx.surface = bmout; + ctx.ctx.surface_offs.x = -bmout_xpos; + ctx.ctx.surface_offs.y = -bmout_ypos; + ctx.clipr.left = bmout_xpos; + ctx.clipr.top = bmout_ypos; + ctx.clipr.right = ctx.clipr.left + bmout->getWidth(); + ctx.clipr.bottom = ctx.clipr.top + bmout->getHeight(); + + void *oldpaintctx = hwnd->m_paintctx; + if (forceref) hwnd->m_paintctx = &ctx; + + LICE_SubBitmap tmpsub(NULL,0,0,0,0); + if (hwnd->m_wndproc) // this happens after m_paintctx is set -- that way GetWindowDC()/GetDC()/ReleaseDC() know to not actually update the screen + { + RECT r2; + GetWindowContentViewRect(hwnd,&r2); + WinOffsetRect(&r2,-r2.left,-r2.top); + + NCCALCSIZE_PARAMS p; + memset(&p,0,sizeof(p)); + p.rgrc[0]=r2; + hwnd->m_wndproc(hwnd,WM_NCCALCSIZE,FALSE,(LPARAM)&p); + RECT r = p.rgrc[0]; + if (forceref) hwnd->m_wndproc(hwnd,WM_NCPAINT,(WPARAM)1,0); + + // dx,dy is offset from the window's nonclient root + const int dx = r.left-r2.left,dy=r.top-r2.top; + + WinOffsetRect(&ctx.clipr,-dx,-dy); + ctx.ctx.surface_offs.x += dx; + ctx.ctx.surface_offs.y += dy; + bmout_xpos -= dx; + bmout_ypos -= dy; + + // make sure we can't overwrite the nonclient area + const int xo = wdl_max(-bmout_xpos,0), yo = wdl_max(-bmout_ypos,0); + if (xo || yo) + { + tmpsub.m_parent = ctx.ctx.surface; + tmpsub.m_x = xo; + tmpsub.m_y = yo; + tmpsub.m_w = ctx.ctx.surface->getWidth()-xo; + tmpsub.m_h = ctx.ctx.surface->getHeight()-yo; + if (tmpsub.m_w<0) tmpsub.m_w=0; + if (tmpsub.m_h<0) tmpsub.m_h=0; + ctx.ctx.surface = &tmpsub; + ctx.ctx.surface_offs.x -= xo; + ctx.ctx.surface_offs.y -= yo; + } + + // constrain clip rect to right/bottom extents + if (ctx.clipr.left < 0) ctx.clipr.left=0; + if (ctx.clipr.top < 0) ctx.clipr.top=0; + int newr = r.right-r.left; + if (ctx.clipr.right > newr) ctx.clipr.right=newr; + int newb = r.bottom-r.top; + if (ctx.clipr.bottom > newb) ctx.clipr.bottom=newb; + } + + // paint + if (forceref) + { + if (hwnd->m_wndproc && ctx.clipr.right > ctx.clipr.left && ctx.clipr.bottom > ctx.clipr.top) + { + ctx.ctx.curfont = hwnd->m_font; + hwnd->m_wndproc(hwnd,WM_PAINT,(WPARAM)&ctx,0); + } + + hwnd->m_paintctx = oldpaintctx; + + // it might be good to blit here on some OSes, rather than from the top level caller... + hwnd->m_invalidated=false; + } + } + + bool okToClearChild=true; + if (forceref || hwnd->m_child_invalidated) + { + HWND h = hwnd->m_children; + while (h) + { + if (h->m_visible && (forceref || h->m_invalidated||h->m_child_invalidated)) + { + int width = h->m_position.right - h->m_position.left, + height = h->m_position.bottom - h->m_position.top; // max width possible for this window + int xp = h->m_position.left - bmout_xpos, yp = h->m_position.top - bmout_ypos; + + if (okToClearChild && !forceref) + { + if (xp < 0 || xp+width > bmout->getWidth() || + yp < 0 || yp+height > bmout->getHeight()) okToClearChild = false; // extends outside of our region + } + // if xp/yp < 0, that means that the clip region starts inside the window, so we need to pass a positive render offset, and decrease the potential draw width + // if xp/yp > 0, then the clip region starts before the window, so we use the subbitmap and pass 0 for the offset parm + int subx = 0, suby=0; + if (xp<0) width+=xp; + else { subx=xp; xp=0; } + + if (yp<0) height+=yp; + else { suby=yp; yp=0; } + + LICE_SubBitmap subbm(bmout,subx,suby,width,height); // the right/bottom will automatically be clipped to the clip rect etc + if (subbm.getWidth()>0 && subbm.getHeight()>0) + SWELL_internalLICEpaint(h,&subbm,-xp,-yp,forceref); + } + h = h->m_next; + } + } + if (okToClearChild) hwnd->m_child_invalidated=false; +#ifdef _DEBUG + g_swell_in_paint--; +#endif +} + +HBITMAP CreateBitmap(int width, int height, int numplanes, int bitsperpixel, unsigned char* bits) +{ + if (WDL_NOT_NORMALLY(width < 1 || height < 1 || numplanes != 1 || bitsperpixel != 32 || !bits)) return NULL; + LICE_MemBitmap *bm = new LICE_MemBitmap(width,height); + if (WDL_NOT_NORMALLY(!bm->getBits())) { delete bm; return NULL; } + int y; + LICE_pixel *wr = bm->getBits(); + for (y=0;ygetRowSpan(); + } + + + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + icon->type=TYPE_BITMAP; + icon->wid=1; + icon->typedata = (void*)bm; + return icon; +} + +HICON CreateIconIndirect(ICONINFO* iconinfo) +{ + if (WDL_NOT_NORMALLY(!iconinfo || !iconinfo->fIcon)) return 0; + HGDIOBJ__* i=iconinfo->hbmColor; + if (!HGDIOBJ_VALID(i,TYPE_BITMAP) ) return 0; + + if (!i->typedata) return 0; + + LICE_MemBitmap *bm = new LICE_MemBitmap; + LICE_Copy(bm,(LICE_IBitmap*)i->typedata); + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + icon->type=TYPE_BITMAP; + icon->wid=1; + icon->typedata = (void*)bm; + + return icon; +} + +HIMAGELIST ImageList_CreateEx() +{ + return (HIMAGELIST)new WDL_PtrList; +} + +BOOL ImageList_Remove(HIMAGELIST list, int idx) +{ + WDL_PtrList* imglist=(WDL_PtrList*)list; + if (WDL_NORMALLY(imglist) && idx < imglist->GetSize()) + { + if (idx < 0) + { + int x,n=imglist->GetSize(); + for (x=0;xGet(x); + if (a) DeleteObject(a); + } + imglist->Empty(); + } + else + { + HGDIOBJ__ *a = imglist->Get(idx); + imglist->Set(idx, NULL); + if (a) DeleteObject(a); + } + return TRUE; + } + + return FALSE; +} + +void ImageList_Destroy(HIMAGELIST list) +{ + if (WDL_NOT_NORMALLY(!list)) return; + WDL_PtrList *p=(WDL_PtrList*)list; + ImageList_Remove(list,-1); + delete p; +} + +int ImageList_ReplaceIcon(HIMAGELIST list, int offset, HICON image) +{ + if (WDL_NOT_NORMALLY(!image || !list)) return -1; + WDL_PtrList *l=(WDL_PtrList *)list; + + HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image; + if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1; + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + LICE_MemBitmap *bm = new LICE_MemBitmap; + LICE_Copy(bm,(LICE_IBitmap*)imgsrc->typedata); + + icon->type=TYPE_BITMAP; + icon->alpha = 1.0f; + icon->wid=1; + icon->typedata = (void*)bm; + + image = (HICON) icon; + + if (offset<0||offset>=l->GetSize()) + { + l->Add(image); + offset=l->GetSize()-1; + } + else + { + HICON old=l->Get(offset); + l->Set(offset,image); + if (old) DeleteObject(old); + } + return offset; +} + +int ImageList_Add(HIMAGELIST list, HBITMAP image, HBITMAP mask) +{ + if (WDL_NOT_NORMALLY(!image || !list)) return -1; + WDL_PtrList *l=(WDL_PtrList *)list; + + HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image; + if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1; + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + LICE_MemBitmap *bm = new LICE_MemBitmap; + LICE_Copy(bm,(LICE_IBitmap*)imgsrc->typedata); + + icon->type=TYPE_BITMAP; + icon->wid=1; + icon->typedata = (void*)bm; + + image = (HICON) icon; + + l->Add(image); + return l->GetSize(); +} + +int AddFontResourceEx(LPCTSTR str, DWORD fl, void *pdv) +{ +#ifdef SWELL_FREETYPE + if (str && *str) + { +#ifdef SWELL_FONTCONFIG + if (!s_fontconfig) s_fontconfig = FcInitLoadConfigAndFonts(); + return s_fontconfig && FcConfigAppFontAddFile(s_fontconfig,(const FcChar8 *)str) != FcFalse; +#else + if (s_freetype_regfonts.FindSorted(str,sortByFilePart)>=0) return 0; + s_freetype_regfonts.InsertSorted(strdup(str), sortByFilePart); +#endif + return 1; + } +#endif + return 0; +} + +int GetGlyphIndicesW(HDC ctx, wchar_t *buf, int len, unsigned short *indices, int flags) +{ +#ifdef SWELL_FREETYPE + if (ctx) + { + HGDIOBJ font = HDC_VALID(ctx) && HGDIOBJ_VALID(ctx->curfont,TYPE_FONT) ? ctx->curfont : SWELL_GetDefaultFont(); + if (font) + { + FT_Face face=(FT_Face)font->typedata; + if (face) + { + for (int i=0; i < len; ++i) + { + int c = FT_Get_Char_Index(face,buf[i]); + indices[i] = c ? c : 0xFFFF; + } + return len; + } + } + } +#endif + + for (int i=0; i < len; ++i) indices[i]=0xFFFF; + return len; +} + +#endif + +#endif // SWELL_LICE_GDI diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi.mm new file mode 100644 index 0000000..5354825 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-gdi.mm @@ -0,0 +1,2024 @@ +/* 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. + + + This file provides basic win32 GDI-->Quartz translation. It uses features that require OS X 10.4+ + +*/ + +#ifndef SWELL_PROVIDED_BY_APP + +#import +#import +#import +#import +#include "swell.h" +#define SWELL_GetOSXVersion SWELL_GDI_GetOSXVersion +#define SWELL_IMPLEMENT_GETOSXVERSION static +#include "swell-internal.h" + +#include "../mutex.h" +#include "../assocarray.h" +#include "../wdlcstring.h" + +#ifdef __SSE__ +#include +#endif + +#ifdef SWELL_SUPPORT_OPENGL_BLIT +#include +#endif + +#ifndef SWELL_NO_METAL +void SWELL_Metal_FillRect(void *_tex, int x, int y, int w, int h, int color); +#endif + +#ifdef __AVX__ +#include +#endif + +#ifndef MAC_OS_X_VERSION_10_6 +// 10.5 SDK doesn't include CGContextSetAllowsFontSmoothing() in header (but apparently does in libs) +CG_EXTERN void CGContextSetAllowsFontSmoothing(CGContextRef c, bool) AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER; +#endif + +#ifndef SWELL_NO_CORETEXT +static bool IsCoreTextSupported() +{ +#ifdef SWELL_ATSUI_TEXT_SUPPORT + return SWELL_GDI_GetOSXVersion() >= 0x1050 && CTFontCreateWithName && CTLineDraw && CTFramesetterCreateWithAttributedString && CTFramesetterCreateFrame && + CTFrameGetLines && CTLineGetTypographicBounds && CTLineCreateWithAttributedString && CTFontCopyPostScriptName + ; +#else + // no ATSUI, targetting 10.5+, CT is always valid + return true; +#endif +} + +static CTFontRef GetCoreTextDefaultFont() +{ + static CTFontRef deffr; + static bool ok; + if (!ok) + { + ok=true; + if (IsCoreTextSupported()) + { + deffr=(CTFontRef) [[NSFont labelFontOfSize:10.0] retain]; + } + } + return deffr; +} +#endif // !SWELL_NO_CORETEXT + + +static NSString *CStringToNSString(const char *str) +{ + if (!str) str=""; + NSString *ret; + + ret=(NSString *)CFStringCreateWithCString(NULL,str,kCFStringEncodingUTF8); + if (ret) return ret; + ret=(NSString *)CFStringCreateWithCString(NULL,str,kCFStringEncodingASCII); + return ret; +} +CGColorSpaceRef __GetBitmapColorSpace() +{ + static CGColorSpaceRef cs; + if (!cs) cs = CGColorSpaceCreateDeviceRGB(); + return cs; +} + +CGColorSpaceRef __GetDisplayColorSpace() +{ + static CGColorSpaceRef cs; + if (!cs) + { + // use monitor profile for 10.7+ + if (SWELL_GDI_GetOSXVersion() >= 0x1070) + { + +#ifdef MAC_OS_X_VERSION_10_11 + // OSX 10.11 SDK removes CMGetSystemProfile + // this may be preferable on older SDKs as well, need to test (though CGDisplayCopyColorSpace is only available on 10.5+) + cs = CGDisplayCopyColorSpace(CGMainDisplayID()); +#else + CMProfileRef systemMonitorProfile = NULL; + CMError getProfileErr = CMGetSystemProfile(&systemMonitorProfile); + if(noErr == getProfileErr) + { + cs = CGColorSpaceCreateWithPlatformColorSpace(systemMonitorProfile); + CMCloseProfile(systemMonitorProfile); + } +#endif + } + } + if (!cs) + cs = CGColorSpaceCreateDeviceRGB(); + return cs; +} + +static CGColorRef CreateColor(int col, float alpha=1.0f) +{ + CGFloat cols[4]={GetRValue(col)/255.0f,GetGValue(col)/255.0f,GetBValue(col)/255.0f,alpha}; + CGColorRef color=CGColorCreate(__GetBitmapColorSpace(),cols); + return color; +} + + +#include "swell-gdi-internalpool.h" + +char g_swell_disable_retina; + +int SWELL_IsRetinaDC(HDC hdc) +{ + if (g_swell_disable_retina) return 0; + HDC__ *src=(HDC__*)hdc; + if (!src || !HDC_VALID(src)) return 0; + + if (!src->ctx) + { +#ifndef SWELL_NO_METAL + if (src->metal_ctx) + { + SWELL_hwndChild *ctx = (SWELL_hwndChild*)src->metal_ctx; + if (ctx->m_metal_dc_dirty) return ctx->m_metal_retina ? 1 : 0; + + return SWELL_IsRetinaHWND((HWND)src->metal_ctx); + } +#endif + return 0; + } + return CGContextConvertSizeToDeviceSpace((CGContextRef)src->ctx, CGSizeMake(1,1)).width > 1.9 ? 1 : 0; +} + + +HDC SWELL_CreateGfxContext(void *c) +{ + HDC__ *ctx=SWELL_GDP_CTX_NEW(); + NSGraphicsContext *nsc = (NSGraphicsContext *)c; +// if (![nsc isFlipped]) +// nsc = [NSGraphicsContext graphicsContextWithGraphicsPort:[nsc graphicsPort] flipped:YES]; + + ctx->ctx=(CGContextRef)[nsc graphicsPort]; +// CGAffineTransform f={1,0,0,-1,0,0}; + //CGContextSetTextMatrix(ctx->ctx,f); + //SetTextColor(ctx,0); + + // CGContextSelectFont(ctx->ctx,"Arial",12.0,kCGEncodingMacRoman); + return ctx; +} + +#ifndef SWELL_NO_METAL +HDC SWELL_CreateMetalDC(SWELL_hwndChild *tex) +{ + HDC__ *ctx=SWELL_GDP_CTX_NEW(); + ctx->metal_ctx = tex; + return ctx; +} +#endif + +#define ALIGN_EXTRA 63 +static void *ALIGN_FBUF(void *inbuf) +{ + const UINT_PTR extra = ALIGN_EXTRA; + return (void *) (((UINT_PTR)inbuf+extra)&~extra); +} + +HDC SWELL_CreateMemContext(HDC hdc, int w, int h) +{ + // we always allocate an extra row, because StretchBlt() will sometimes pass + // rowspan*h to CGDataProviderCreateWithData() with an X offset (and smaller width), + // which confuses guard malloc (and is generally possibly unsafe) + void *buf=calloc(w*4*(h+1)+ALIGN_EXTRA,1); + if (WDL_NOT_NORMALLY(!buf)) return 0; + CGContextRef c=CGBitmapContextCreate(ALIGN_FBUF(buf),w,h,8,w*4, __GetBitmapColorSpace(), kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host); + if (WDL_NOT_NORMALLY(!c)) + { + free(buf); + return 0; + } + + + CGContextTranslateCTM(c,0.0,h); + CGContextScaleCTM(c,1.0,-1.0); + CGContextSetAllowsFontSmoothing(c,0); // we may wish to enable this for some contexts eventually, but this is to match previous behavior + + HDC__ *ctx=SWELL_GDP_CTX_NEW(); + ctx->ctx=(CGContextRef)c; + ctx->ownedData=buf; + // CGContextSelectFont(ctx->ctx,"Arial",12.0,kCGEncodingMacRoman); + + SetTextColor(ctx,0); + return ctx; +} + +void SWELL_DeleteGfxContext(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct)) + { + if (ct->ownedData) + { + CGContextRelease(ct->ctx); + free(ct->ownedData); + } + if (ct->curtextcol) CFRelease(ct->curtextcol); + SWELL_GDP_CTX_DELETE(ct); + } +} +HPEN CreatePen(int attr, int wid, int col) +{ + return CreatePenAlpha(attr,wid,col,1.0f); +} + +HBRUSH CreateSolidBrush(int col) +{ + return CreateSolidBrushAlpha(col,1.0f); +} + + + +HPEN CreatePenAlpha(int attr, int wid, int col, float alpha) +{ + HGDIOBJ__ *pen=GDP_OBJECT_NEW(); + pen->type=TYPE_PEN; + pen->wid=wid<0?0:wid; + pen->color=CreateColor(col,alpha); + pen->color_int = col; + return pen; +} +HBRUSH CreateSolidBrushAlpha(int col, float alpha) +{ + HGDIOBJ__ *brush=GDP_OBJECT_NEW(); + brush->type=TYPE_BRUSH; + brush->color=CreateColor(col,alpha); + brush->color_int = col; + brush->wid=0; + return brush; +} + + +HFONT CreateFontIndirect(LOGFONT *lf) +{ + return CreateFont(lf->lfHeight, lf->lfWidth,lf->lfEscapement, lf->lfOrientation, lf->lfWeight, lf->lfItalic, + lf->lfUnderline, lf->lfStrikeOut, lf->lfCharSet, lf->lfOutPrecision,lf->lfClipPrecision, + lf->lfQuality, lf->lfPitchAndFamily, lf->lfFaceName); +} + +static HGDIOBJ__ global_objs[2]; + +void DeleteObject(HGDIOBJ pen) +{ + HGDIOBJ__ *p=(HGDIOBJ__ *)pen; + if (p >= global_objs && p < global_objs + sizeof(global_objs)/sizeof(global_objs[0])) return; + + if (HGDIOBJ_VALID(p)) + { + if (--p->additional_refcnt < 0) + { + if (p->type == TYPE_PEN || p->type == TYPE_BRUSH || p->type == TYPE_FONT || p->type == TYPE_BITMAP) + { + if (p->type == TYPE_PEN || p->type == TYPE_BRUSH) + if (p->wid<0) return; + if (p->color) CGColorRelease(p->color); + + if (p->ct_FontRef) CFRelease(p->ct_FontRef); + +#ifdef SWELL_ATSUI_TEXT_SUPPORT + if (p->atsui_font_style) ATSUDisposeStyle(p->atsui_font_style); +#endif + + if (p->wid && p->bitmapptr) [p->bitmapptr release]; + GDP_OBJECT_DELETE(p); + } + // JF> don't free unknown objects, this shouldn't ever happen anyway: else free(p); + } + } +} + + +HGDIOBJ SelectObject(HDC ctx, HGDIOBJ pen) +{ + HDC__ *c=(HDC__ *)ctx; + HGDIOBJ__ *p=(HGDIOBJ__*) pen; + HGDIOBJ__ **mod=0; + if (!HDC_VALID(c)) return 0; + + if (p == (HGDIOBJ__*)TYPE_PEN) mod=&c->curpen; + else if (p == (HGDIOBJ__*)TYPE_BRUSH) mod=&c->curbrush; + else if (p == (HGDIOBJ__*)TYPE_FONT) mod=&c->curfont; + + if (mod) // clearing a particular thing + { + HGDIOBJ__ *np=*mod; + *mod=0; + return HGDIOBJ_VALID(np,(int)(INT_PTR)p)?np:p; + } + + if (!HGDIOBJ_VALID(p)) return 0; + + if (p->type == TYPE_PEN) mod=&c->curpen; + else if (p->type == TYPE_BRUSH) mod=&c->curbrush; + else if (p->type == TYPE_FONT) mod=&c->curfont; + + if (!mod) return 0; + + HGDIOBJ__ *op=*mod; + if (!HGDIOBJ_VALID(op,p->type)) op=(HGDIOBJ__*)(INT_PTR)p->type; + if (op != p) *mod=p; + return op; +} + + + +void SWELL_FillRect(HDC ctx, const RECT *r, HBRUSH br) +{ + HDC__ *c=(HDC__ *)ctx; + HGDIOBJ__ *b=(HGDIOBJ__*) br; + if (!HDC_VALID(c) || !HGDIOBJ_VALID(b,TYPE_BRUSH) || b == (HGDIOBJ__*)TYPE_BRUSH || b->type != TYPE_BRUSH) return; + +#ifndef SWELL_NO_METAL + if (c->metal_ctx) + { + if (b->wid>=0) + SWELL_Metal_FillRect(c->metal_ctx, r->left, r->top, r->right-r->left,r->bottom-r->top, b->color_int); + return; + } +#endif + + if (!c->ctx) return; + + if (b->wid<0) return; + + CGRect rect=CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top); + CGContextSetFillColorWithColor(c->ctx,b->color); + CGContextFillRect(c->ctx,rect); + +} + +void RoundRect(HDC ctx, int x, int y, int x2, int y2, int xrnd, int yrnd) +{ + xrnd/=3; + yrnd/=3; + POINT pts[10]={ // todo: curves between edges + {x,y+yrnd}, + {x+xrnd,y}, + {x2-xrnd,y}, + {x2,y+yrnd}, + {x2,y2-yrnd}, + {x2-xrnd,y2}, + {x+xrnd,y2}, + {x,y2-yrnd}, + {x,y+yrnd}, + {x+xrnd,y}, +}; + + WDL_GDP_Polygon(ctx,pts,sizeof(pts)/sizeof(pts[0])); +} + +void Ellipse(HDC ctx, int l, int t, int r, int b) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + if (!c->ctx) return; + + CGRect rect=CGRectMake(l,t,r-l,b-t); + + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >=0) + { + CGContextSetFillColorWithColor(c->ctx,c->curbrush->color); + CGContextFillEllipseInRect(c->ctx,rect); + } + if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0) + { + CGContextSetStrokeColorWithColor(c->ctx,c->curpen->color); + CGContextStrokeEllipseInRect(c->ctx, rect); //, (float)wdl_max(1,c->curpen->wid)); + } +} + +void Rectangle(HDC ctx, int l, int t, int r, int b) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; +#ifndef SWELL_NO_METAL + if (c->metal_ctx) + { + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0) + { + SWELL_Metal_FillRect(c->metal_ctx, l,t,r-l,b-t, c->curbrush->color_int); + } + if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0) + { + const int wid = wdl_max(1,c->curpen->wid); + SWELL_Metal_FillRect(c->metal_ctx, l,t,r-l,wid, c->curpen->color_int); + SWELL_Metal_FillRect(c->metal_ctx, l,b-wid,r-l,wid, c->curpen->color_int); + SWELL_Metal_FillRect(c->metal_ctx, l,t+wid,wid,b-t-wid*2, c->curpen->color_int); + SWELL_Metal_FillRect(c->metal_ctx, r-wid,t+wid,wid,b-t-wid*2, c->curpen->color_int); + } + return; + } +#endif + if (!c->ctx) return; + + CGRect rect=CGRectMake(l,t,r-l,b-t); + + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0) + { + CGContextSetFillColorWithColor(c->ctx,c->curbrush->color); + CGContextFillRect(c->ctx,rect); + } + if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid >= 0) + { + CGContextSetStrokeColorWithColor(c->ctx,c->curpen->color); + CGContextStrokeRectWithWidth(c->ctx, rect, (float)wdl_max(1,c->curpen->wid)); + } +} + + +HGDIOBJ GetStockObject(int wh) +{ + switch (wh) + { + case NULL_BRUSH: + { + HGDIOBJ__ *p = &global_objs[0]; + p->type=TYPE_BRUSH; + p->wid=-1; + return p; + } + case NULL_PEN: + { + HGDIOBJ__ *p = &global_objs[1]; + p->type=TYPE_PEN; + p->wid=-1; + return p; + } + } + WDL_ASSERT(false); + return 0; +} + +void Polygon(HDC ctx, POINT *pts, int npts) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + if (!c->ctx) return; + if (((!HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH)||c->curbrush->wid<0) && (!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0)) || npts<2) return; + + CGContextBeginPath(c->ctx); + CGContextMoveToPoint(c->ctx,(float)pts[0].x,(float)pts[0].y); + int x; + for (x = 1; x < npts; x ++) + { + CGContextAddLineToPoint(c->ctx,(float)pts[x].x,(float)pts[x].y); + } + if (HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid >= 0) + { + CGContextSetFillColorWithColor(c->ctx,c->curbrush->color); + } + if (HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid>=0) + { + CGContextSetLineWidth(c->ctx,(float)wdl_max(c->curpen->wid,1)); + CGContextSetStrokeColorWithColor(c->ctx,c->curpen->color); + } + CGContextDrawPath(c->ctx,HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid>=0 && HGDIOBJ_VALID(c->curbrush,TYPE_BRUSH) && c->curbrush->wid>=0 ? kCGPathFillStroke : HGDIOBJ_VALID(c->curpen,TYPE_PEN) && c->curpen->wid>=0 ? kCGPathStroke : kCGPathFill); +} + +void MoveToEx(HDC ctx, int x, int y, POINT *op) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)) return; + if (op) + { + op->x = (int) (c->lastpos_x); + op->y = (int) (c->lastpos_y); + } + c->lastpos_x=(float)x; + c->lastpos_y=(float)y; +} + +void PolyBezierTo(HDC ctx, POINT *pts, int np) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0||np<3) return; + if (!c->ctx) return; + + CGContextSetLineWidth(c->ctx,(float)wdl_max(c->curpen->wid,1)); + CGContextSetStrokeColorWithColor(c->ctx,c->curpen->color); + + CGContextBeginPath(c->ctx); + CGContextMoveToPoint(c->ctx,c->lastpos_x,c->lastpos_y); + int x; + float xp,yp; + for (x = 0; x < np-2; x += 3) + { + CGContextAddCurveToPoint(c->ctx, + (float)pts[x].x,(float)pts[x].y, + (float)pts[x+1].x,(float)pts[x+1].y, + xp=(float)pts[x+2].x,yp=(float)pts[x+2].y); + } + c->lastpos_x=(float)xp; + c->lastpos_y=(float)yp; + CGContextStrokePath(c->ctx); +} + + +void SWELL_LineTo(HDC ctx, int x, int y) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0) return; +#ifndef SWELL_NO_METAL + if (c->metal_ctx) + { + if (x == c->lastpos_x) + { + const int my=wdl_min(y,c->lastpos_y); + SWELL_Metal_FillRect(c->metal_ctx, x, my, 1, wdl_max(y,c->lastpos_y)-my+1, c->curpen->color_int); + } + else if (y == c->lastpos_y) + { + const int mx = wdl_min(x,c->lastpos_x); + SWELL_Metal_FillRect(c->metal_ctx, mx, y, wdl_max(x,c->lastpos_x)-mx+1,1, c->curpen->color_int); + } + c->lastpos_x = x; + c->lastpos_y = y; + return; + } +#endif + if (!c->ctx) return; + + float w = (float)wdl_max(c->curpen->wid,1); + CGContextSetLineWidth(c->ctx,w); + CGContextSetStrokeColorWithColor(c->ctx,c->curpen->color); + + CGContextBeginPath(c->ctx); + CGContextMoveToPoint(c->ctx,c->lastpos_x + w * 0.5,c->lastpos_y + w*0.5); + float fx=(float)x,fy=(float)y; + + CGContextAddLineToPoint(c->ctx,fx+w*0.5,fy+w*0.5); + c->lastpos_x=fx; + c->lastpos_y=fy; + CGContextStrokePath(c->ctx); +} + +void PolyPolyline(HDC ctx, POINT *pts, DWORD *cnts, int nseg) +{ + HDC__ *c=(HDC__ *)ctx; + if (!HDC_VALID(c)||!HGDIOBJ_VALID(c->curpen,TYPE_PEN)||c->curpen->wid<0||nseg<1) return; + if (!c->ctx) return; + + float w = (float)wdl_max(c->curpen->wid,1); + CGContextSetLineWidth(c->ctx,w); + CGContextSetStrokeColorWithColor(c->ctx,c->curpen->color); + + CGContextBeginPath(c->ctx); + + while (nseg-->0) + { + DWORD cnt=*cnts++; + if (!cnt) continue; + if (!--cnt) { pts++; continue; } + + CGContextMoveToPoint(c->ctx,(float)pts->x+w*0.5,(float)pts->y+w*0.5); + pts++; + + while (cnt--) + { + CGContextAddLineToPoint(c->ctx,(float)pts->x+w*0.5,(float)pts->y+w*0.5); + pts++; + } + } + CGContextStrokePath(c->ctx); +} +void *SWELL_GetCtxGC(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return 0; + return ct->ctx; +} + + +void SWELL_SetPixel(HDC ctx, int x, int y, int c) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; +#ifndef SWELL_NO_METAL + if (ct->metal_ctx) + { + SWELL_Metal_FillRect(ct->metal_ctx, x, y, 1, 1,c); + return; + } +#endif + + if (!ct->ctx) return; + CGContextBeginPath(ct->ctx); + CGContextMoveToPoint(ct->ctx,(float)x-0.5,(float)y-0.5); + CGContextAddLineToPoint(ct->ctx,(float)x+0.5,(float)y+0.5); + CGContextSetLineWidth(ct->ctx,(float)1.0); + CGContextSetRGBStrokeColor(ct->ctx,GetRValue(c)/255.0,GetGValue(c)/255.0,GetBValue(c)/255.0,1.0); + CGContextStrokePath(ct->ctx); +} + + +static WDL_Mutex s_fontnamecache_mutex; + +#ifdef SWELL_CLEANUP_ON_UNLOAD +static void releaseString(NSString *s) { [s release]; } +#endif +static WDL_StringKeyedArray s_fontnamecache(true, +#ifdef SWELL_CLEANUP_ON_UNLOAD + releaseString +#else + NULL +#endif + ); + +static NSString *SWELL_GetCachedFontName(const char *nm) +{ + NSString *ret = NULL; + if (nm && *nm) + { + s_fontnamecache_mutex.Enter(); + ret = s_fontnamecache.Get(nm); + s_fontnamecache_mutex.Leave(); + if (!ret) + { + ret = CStringToNSString(nm); + if (ret) + { +#ifndef SWELL_NO_CORETEXT + // only do postscript name lookups on 10.9+ + if (floor(NSFoundationVersionNumber) > 945.00) // NSFoundationVersionNumber10_8 + { + NSFont *font = [NSFont fontWithName:ret size:10]; + NSString *nr = font ? (NSString *)CTFontCopyPostScriptName((CTFontRef)font) : NULL; + if (nr) + { + [ret release]; + ret = nr; + } + } +#endif + + s_fontnamecache_mutex.Enter(); + s_fontnamecache.Insert(nm,ret); + s_fontnamecache_mutex.Leave(); + } + } + } + return ret ? ret : @""; +} + +HFONT CreateFont(int lfHeight, int lfWidth, int lfEscapement, int lfOrientation, int lfWeight, char lfItalic, + char lfUnderline, char lfStrikeOut, char lfCharSet, char lfOutPrecision, char lfClipPrecision, + char lfQuality, char lfPitchAndFamily, const char *lfFaceName) +{ + HGDIOBJ__ *font=GDP_OBJECT_NEW(); + font->type=TYPE_FONT; + float fontwid=lfHeight; + + if (!fontwid) fontwid=lfWidth; + if (fontwid<0)fontwid=-fontwid; + + if (fontwid < 2 || fontwid > 8192) fontwid=10; + + font->font_rotation = lfOrientation/10.0; + +#ifndef SWELL_NO_CORETEXT + if (IsCoreTextSupported()) + { + char buf[1024]; + lstrcpyn_safe(buf,lfFaceName,900); + if (lfWeight >= FW_BOLD) strcat(buf," Bold"); + if (lfItalic) strcat(buf," Italic"); + + font->ct_FontRef = (void*)CTFontCreateWithName((CFStringRef)SWELL_GetCachedFontName(buf),fontwid,NULL); + if (!font->ct_FontRef) font->ct_FontRef = (void*)[[NSFont labelFontOfSize:fontwid] retain]; + + // might want to make this conditional (i.e. only return font if created successfully), but I think we'd rather fallback to a system font than use ATSUI + return font; + } +#endif + +#ifdef SWELL_ATSUI_TEXT_SUPPORT + ATSUFontID fontid=kATSUInvalidFontID; + if (lfFaceName && lfFaceName[0]) + { + ATSUFindFontFromName(lfFaceName,strlen(lfFaceName),kFontFullName /* kFontFamilyName? */ ,(FontPlatformCode)kFontNoPlatform,kFontNoScriptCode,kFontNoLanguageCode,&fontid); + // if (fontid==kATSUInvalidFontID) printf("looked up %s and got %d\n",lfFaceName,fontid); + } + + if (ATSUCreateStyle(&font->atsui_font_style) == noErr && font->atsui_font_style) + { + Fixed fsize=Long2Fix(fontwid); + + Boolean isBold=lfWeight >= FW_BOLD; + Boolean isItal=!!lfItalic; + Boolean isUnder=!!lfUnderline; + + ATSUAttributeTag theTags[] = { kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUQDUnderlineTag,kATSUSizeTag,kATSUFontTag }; + ByteCount theSizes[] = { sizeof(Boolean),sizeof(Boolean),sizeof(Boolean), sizeof(Fixed),sizeof(ATSUFontID) }; + ATSUAttributeValuePtr theValues[] = {&isBold, &isItal, &isUnder, &fsize, &fontid } ; + + int attrcnt=sizeof(theTags)/sizeof(theTags[0]); + if (fontid == kATSUInvalidFontID) attrcnt--; + + if (ATSUSetAttributes (font->atsui_font_style, + attrcnt, + theTags, + theSizes, + theValues)!=noErr) + { + ATSUDisposeStyle(font->atsui_font_style); + font->atsui_font_style=0; + } + } + else + font->atsui_font_style=0; + +#endif + + + return font; +} + +int GetTextFace(HDC ctx, int nCount, LPTSTR lpFaceName) +{ + HDC__ *ct=(HDC__*)ctx; + if (!HDC_VALID(ct) || WDL_NOT_NORMALLY(!nCount || !lpFaceName)) return 0; + +#ifndef SWELL_NO_CORETEXT + CTFontRef fr=NULL; + if (HGDIOBJ_VALID(ct->curfont,TYPE_FONT)) fr=(CTFontRef)ct->curfont->ct_FontRef; + if (!fr) fr=GetCoreTextDefaultFont(); + + if (fr) + { + CFStringRef name=CTFontCopyDisplayName(fr); + const char* p=[(NSString*)name UTF8String]; + if (p) + { + lstrcpyn_safe(lpFaceName, p, nCount); + return (int)strlen(lpFaceName); + } + } +#endif + + return 0; +} + +BOOL GetTextMetrics(HDC ctx, TEXTMETRIC *tm) +{ + HDC__ *ct=(HDC__ *)ctx; + if (tm) // give some sane defaults + { + tm->tmInternalLeading=3; + tm->tmAscent=12; + tm->tmDescent=4; + tm->tmHeight=16; + tm->tmAveCharWidth = 10; + } + if (!HDC_VALID(ct)||WDL_NOT_NORMALLY(!tm)) return 0; + + bool curfont_valid=HGDIOBJ_VALID(ct->curfont,TYPE_FONT); + +#ifdef SWELL_ATSUI_TEXT_SUPPORT + if (curfont_valid && ct->curfont->atsui_font_style) + { + ATSUTextMeasurement ascent=Long2Fix(10); + ATSUTextMeasurement descent=Long2Fix(3); + ATSUTextMeasurement sz=Long2Fix(0); + ATSUTextMeasurement width =Long2Fix(12); + ATSUGetAttribute(ct->curfont->atsui_font_style, kATSUAscentTag, sizeof(ATSUTextMeasurement), &ascent,NULL); + ATSUGetAttribute(ct->curfont->atsui_font_style, kATSUDescentTag, sizeof(ATSUTextMeasurement), &descent,NULL); + ATSUGetAttribute(ct->curfont->atsui_font_style, kATSUSizeTag, sizeof(ATSUTextMeasurement), &sz,NULL); + ATSUGetAttribute(ct->curfont->atsui_font_style, kATSULineWidthTag, sizeof(ATSUTextMeasurement),&width,NULL); + + float asc=Fix2X(ascent); + float desc=Fix2X(descent); + float size = Fix2X(sz); + + if (size < (asc+desc)*0.2) size=asc+desc; + + tm->tmAscent = (int)ceil(asc); + tm->tmDescent = (int)ceil(desc); + tm->tmInternalLeading=(int)ceil(asc+desc-size); + if (tm->tmInternalLeading<0)tm->tmInternalLeading=0; + tm->tmHeight=(int) ceil(asc+desc); + tm->tmAveCharWidth = (int) (ceil(asc+desc)*0.65); // (int)ceil(Fix2X(width)); + + return 1; + } +#endif + +#ifndef SWELL_NO_CORETEXT + CTFontRef fr = curfont_valid ? (CTFontRef)ct->curfont->ct_FontRef : NULL; + if (!fr) fr=GetCoreTextDefaultFont(); + + if (fr) + { + tm->tmInternalLeading = CTFontGetLeading(fr); + tm->tmAscent = CTFontGetAscent(fr); + tm->tmDescent = CTFontGetDescent(fr); + tm->tmHeight = (tm->tmInternalLeading + tm->tmAscent + tm->tmDescent); + tm->tmAveCharWidth = tm->tmHeight*2/3; // todo + + if (tm->tmHeight) tm->tmHeight++; + + return 1; + } +#endif + + + return 1; +} + + + +#ifdef SWELL_ATSUI_TEXT_SUPPORT + +static int DrawTextATSUI(HDC ctx, CFStringRef strin, RECT *r, int align, bool *err) +{ + HDC__ *ct=(HDC__ *)ctx; + HGDIOBJ__ *font=ct->curfont; // caller must specify a valid font + + UniChar strbuf[4096]; + int strbuf_len; + + { + strbuf[0]=0; + CFRange r = {0,CFStringGetLength(strin)}; + if (r.length > 4095) r.length=4095; + strbuf_len=CFStringGetBytes(strin,r,kCFStringEncodingUTF16,' ',false,(UInt8*)strbuf,sizeof(strbuf)-2,NULL); + if (strbuf_len<0)strbuf_len=0; + else if (strbuf_len>4095) strbuf_len=4095; + strbuf[strbuf_len]=0; + } + + { + ATSUAttributeTag theTags[] = { kATSUColorTag, }; + ByteCount theSizes[] = { sizeof(RGBColor), }; + + RGBColor tcolor; + ATSUAttributeValuePtr theValues[] = {&tcolor, } ; + + tcolor.red = GetRValue(ct->cur_text_color_int)*256; + tcolor.green = GetGValue(ct->cur_text_color_int)*256; + tcolor.blue = GetBValue(ct->cur_text_color_int)*256; + + // error check this? we can live with the wrong color maybe? + ATSUSetAttributes(font->atsui_font_style, sizeof(theTags)/sizeof(theTags[0]), theTags, theSizes, theValues); + } + + UniCharCount runLengths[1]={kATSUToTextEnd}; + ATSUTextLayout layout; + if (ATSUCreateTextLayoutWithTextPtr(strbuf, kATSUFromTextBeginning, kATSUToTextEnd, strbuf_len, 1, runLengths, &font->atsui_font_style, &layout)!=noErr) + { + *err=true; + return 0; + } + + { + Fixed frot = X2Fix(font->font_rotation); + + ATSULineTruncation tv = (align & DT_END_ELLIPSIS) ? kATSUTruncateEnd : kATSUTruncateNone; + ATSUAttributeTag theTags[] = { kATSUCGContextTag, kATSULineTruncationTag, kATSULineRotationTag }; + ByteCount theSizes[] = { sizeof (CGContextRef), sizeof(ATSULineTruncation), sizeof(Fixed)}; + ATSUAttributeValuePtr theValues[] = { &ct->ctx, &tv, &frot } ; + + + if (ATSUSetLayoutControls (layout, + + sizeof(theTags)/sizeof(theTags[0]), + + theTags, + + theSizes, + + theValues)!=noErr) + { + *err=true; + ATSUDisposeTextLayout(layout); + return 0; + } + } + + + ATSUTextMeasurement leftFixed, rightFixed, ascentFixed, descentFixed; + + if (ATSUGetUnjustifiedBounds(layout, kATSUFromTextBeginning, kATSUToTextEnd, &leftFixed, &rightFixed, &ascentFixed, &descentFixed)!=noErr) + { + *err=true; + ATSUDisposeTextLayout(layout); + return 0; + } + + int w=Fix2Long(rightFixed); + int descent=Fix2Long(descentFixed); + int h=descent + Fix2Long(ascentFixed); + if (align&DT_CALCRECT) + { + ATSUDisposeTextLayout(layout); + r->right=r->left+w; + r->bottom=r->top+h; + return h; + } + CGContextSaveGState(ct->ctx); + + if (!(align & DT_NOCLIP)) + CGContextClipToRect(ct->ctx,CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top)); + + int l=r->left, t=r->top; + + if (fabs(font->font_rotation)<45.0) + { + if (align & DT_RIGHT) l = r->right-w; + else if (align & DT_CENTER) l = (r->right+r->left)/2 - w/2; + } + else l+=Fix2Long(ascentFixed); // 90 degree special case (we should generalize this to be correct throughout the rotation range, but oh well) + + if (align & DT_BOTTOM) t = r->bottom-h; + else if (align & DT_VCENTER) t = (r->bottom+r->top)/2 - h/2; + + CGContextTranslateCTM(ct->ctx,0,t); + CGContextScaleCTM(ct->ctx,1,-1); + CGContextTranslateCTM(ct->ctx,0,-t-h); + + if (ct->curbkmode == OPAQUE) + { + CGRect bgr = CGRectMake(l, t, w, h); + CGColorRef bgc = CreateColor(ct->curbkcol); + CGContextSetFillColorWithColor(ct->ctx, bgc); + CGContextFillRect(ct->ctx, bgr); + CGColorRelease(bgc); + } + + if (ATSUDrawText(layout,kATSUFromTextBeginning,kATSUToTextEnd,Long2Fix(l),Long2Fix(t+descent))!=noErr) + *err=true; + + CGContextRestoreGState(ct->ctx); + + ATSUDisposeTextLayout(layout); + + return h; +} + +#endif + +int DrawText(HDC ctx, const char *buf, int buflen, RECT *r, int align) +{ + WDL_ASSERT((align & DT_SINGLELINE) || !(align & (DT_VCENTER | DT_BOTTOM))); + + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return 0; + if (!(align & DT_CALCRECT) && !ct->ctx) return 0; + + bool has_ml=false; + char tmp[4096]; + const char *p=buf; + char *op=tmp; + while (*p && (op-tmp)curfont,TYPE_FONT); +#ifdef SWELL_ATSUI_TEXT_SUPPORT + if (curfont_valid && ct->curfont->atsui_font_style) + { + bool err=false; + int ret = DrawTextATSUI(ctx,(CFStringRef)str,r,align,&err); + [str release]; + + if (!err) return ret; + return 0; + } +#endif + +#ifndef SWELL_NO_CORETEXT + CTFontRef fr = curfont_valid ? (CTFontRef)ct->curfont->ct_FontRef : NULL; + if (!fr) fr=GetCoreTextDefaultFont(); + if (fr) + { + // Initialize string, font, and context + CFStringRef keys[] = { kCTFontAttributeName,kCTForegroundColorAttributeName }; + CFTypeRef values[] = { fr,ct->curtextcol }; + + int nk= sizeof(keys) / sizeof(keys[0]); + if (!values[1]) nk--; + + CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, (const void**)&values, nk, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + CFAttributedStringRef attrString = + CFAttributedStringCreate(kCFAllocatorDefault, (CFStringRef)str, attributes); + CFRelease(attributes); + [str release]; + + + CTFrameRef frame = NULL; + CFArrayRef lines = NULL; + CTLineRef line = NULL; + CGFloat asc=0; + int line_w=0,line_h=0; + if (has_ml) + { + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); + if (framesetter) + { + CGMutablePathRef path=CGPathCreateMutable(); + CGPathAddRect(path,NULL,CGRectMake(0,0,100000,100000)); + frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0,0),path,NULL); + CFRelease(framesetter); + CFRelease(path); + } + if (frame) + { + lines = CTFrameGetLines(frame); + const int n = (int)CFArrayGetCount(lines); + for (int x=0;xright = r->left+line_w; + r->bottom = r->top+line_h; + if (line) CFRelease(line); + if (frame) CFRelease(frame); + return line_h; + } + + float xo=r->left,yo=r->top; + if (align & DT_RIGHT) xo += (r->right-r->left) - line_w; + else if (align & DT_CENTER) xo += (r->right-r->left)/2 - line_w/2; + + if (align & DT_BOTTOM) yo += (r->bottom-r->top) - line_h; + else if (align & DT_VCENTER) yo += (r->bottom-r->top)/2 - line_h/2; + + + CGContextSaveGState(ct->ctx); + + CGAffineTransform f={1,0,0,-1,0,0}; + CGContextSetTextMatrix(ct->ctx, f); + + if (!(align & DT_NOCLIP)) + { + CGContextClipToRect(ct->ctx,CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top)); + } + + CGColorRef bgc = NULL; + if (ct->curbkmode == OPAQUE) + { + bgc = CreateColor(ct->curbkcol); + } + + if (line) + { + if (bgc) + { + CGContextSetFillColorWithColor(ct->ctx, bgc); + CGContextFillRect(ct->ctx, CGRectMake(xo,yo,line_w,line_h)); + } + CGContextSetTextPosition(ct->ctx, xo, yo + asc); + CTLineDraw(line,ct->ctx); + + } + if (lines) + { + const int n = (int)CFArrayGetCount(lines); + for (int x=0;xctx, bgc); + CGContextFillRect(ct->ctx, CGRectMake(xo,yo,lw,asc+desc+lead)); + } + CGContextSetTextPosition(ct->ctx, xo, yo + asc); + CTLineDraw(l,ct->ctx); + + yo += floor(asc+desc+lead+0.5); + } + } + } + + CGContextRestoreGState(ct->ctx); + if (bgc) CGColorRelease(bgc); + if (line) CFRelease(line); + if (frame) CFRelease(frame); + + return line_h; + } +#endif + + + [str release]; + return 0; +} + + +int GetGlyphIndicesW(HDC ctx, wchar_t *buf, int len, unsigned short *indices, int flags) +{ + HDC__ *ct=(HDC__*)ctx; + if (HDC_VALID(ct) && HGDIOBJ_VALID(ct->curfont, TYPE_FONT)) + { +#ifndef SWELL_NO_CORETEXT + CTFontRef f=(CTFontRef)ct->curfont->ct_FontRef; + if (f && CTFontGetGlyphsForCharacters(f, (const UniChar*)buf, (CGGlyph*)indices, (CFIndex)len)) return len; +#endif + } + + int i; + for (i=0; i < len; ++i) indices[i]=(flags == GGI_MARK_NONEXISTING_GLYPHS ? 0xFFFF : 0); + return 0; +} + + +NSFont *SWELL_GetNSFont(HGDIOBJ__ *obj) +{ + if (HGDIOBJ_VALID(obj,TYPE_FONT)) + { + if (obj->ct_FontRef) return (NSFont *)obj->ct_FontRef; +#ifdef SWELL_ATSUI_TEXT_SUPPORT + else if (obj->atsui_font_style) + { + ATSUFontID fontid = kATSUInvalidFontID; + Fixed fsize = 0; + Boolean isbold = NO; + Boolean isital = NO; + Boolean isunder = NO; + if (ATSUGetAttribute(obj->atsui_font_style, kATSUFontTag, sizeof(ATSUFontID), &fontid, 0) == noErr && + ATSUGetAttribute(obj->atsui_font_style, kATSUSizeTag, sizeof(Fixed), &fsize, 0) == noErr && fsize && + ATSUGetAttribute(obj->atsui_font_style, kATSUQDBoldfaceTag, sizeof(Boolean), &isbold, 0) == noErr && + ATSUGetAttribute(obj->atsui_font_style, kATSUQDItalicTag, sizeof(Boolean), &isital, 0) == noErr && + ATSUGetAttribute(obj->atsui_font_style, kATSUQDUnderlineTag, sizeof(Boolean), &isunder, 0) == noErr) + { + char name[255]; + name[0]=0; + ByteCount namelen=0; + if (ATSUFindFontName(fontid, kFontFullName, (FontPlatformCode)kFontNoPlatform, kFontNoScriptCode, kFontNoLanguageCode, sizeof(name), name, &namelen, 0) == noErr && name[0] && namelen) + { + namelen /= 2; + int i; + for (i = 0; i < namelen; ++i) name[i] = name[2*i]; + name[namelen]=0; + + // todo bold/ital/underline + NSString* str = (NSString*)SWELL_CStringToCFString(name); + CGFloat sz = Fix2Long(fsize); + NSFont* font = [NSFont fontWithName:str size:sz]; + [str release]; + return font; + } + } + } +#endif + } + return NULL; +} + + +void SetBkColor(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->curbkcol=col; +} + +void SetBkMode(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->curbkmode=col; +} + +int GetTextColor(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return -1; + return ct->cur_text_color_int; +} + +void SetTextColor(HDC ctx, int col) +{ + HDC__ *ct=(HDC__ *)ctx; + if (!HDC_VALID(ct)) return; + ct->cur_text_color_int = col; + + if (ct->curtextcol) CFRelease(ct->curtextcol); + + ct->curtextcol = CreateColor(col); +} + + +HICON CreateIconIndirect(ICONINFO* iconinfo) +{ + if (WDL_NOT_NORMALLY(!iconinfo || !iconinfo->fIcon)) return 0; + HGDIOBJ__* i=iconinfo->hbmColor; + if (!HGDIOBJ_VALID(i,TYPE_BITMAP) || !i->bitmapptr) return 0; + NSImage* img=i->bitmapptr; + if (!img) return 0; + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + icon->type=TYPE_BITMAP; + icon->wid=1; + [img retain]; + icon->bitmapptr=img; + return icon; +} + +HICON LoadNamedImage(const char *name, bool alphaFromMask) +{ + NSImage *img=0; + NSString *str=CStringToNSString(name); + if (strstr(name,"/")) + { + img=[[NSImage alloc] initWithContentsOfFile:str]; + } + if (!img) + { + img=[NSImage imageNamed:str]; + if (img) [img retain]; + } + [str release]; + if (!img) + { + return 0; + } + + [img setFlipped:YES]; + if (alphaFromMask) + { + const NSSize sz=[img size]; + const int w = (int)sz.width, h=(int)sz.height; + HDC hdc; + if (w>0 && h>0 && NULL != (hdc=SWELL_CreateMemContext(NULL,w,h))) + { + [NSGraphicsContext saveGraphicsState]; + NSGraphicsContext *gc=[NSGraphicsContext graphicsContextWithGraphicsPort:((struct HDC__*)hdc)->ctx flipped:NO]; + [NSGraphicsContext setCurrentContext:gc]; + [img drawInRect:NSMakeRect(0,0,w,h) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0]; + [NSGraphicsContext restoreGraphicsState]; + + // on yosemite, calling [img TIFFRepresentation] seems to change img somehow for some images, ouch. + // in this case, we should always replace img with newImage (set rcnt=1), but in general + // maybe we shoulnt use alphaFromMask anyhow + NSData *data = [img TIFFRepresentation]; + if (!data) + { + SWELL_DeleteGfxContext(hdc); + goto return_img; + } + + NSImage *newImage=[[NSImage alloc] initWithData:data]; + [newImage setFlipped:YES]; + + const int *fb = (const int *)SWELL_GetCtxFrameBuffer(hdc); + int y,rcnt=0; + [newImage lockFocus]; + CGContextRef myContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; + for (y=0; y < h; y ++) + { + int x; + for (x = 0; x < w; x++) + { + if ((*fb++ & 0xffffff) == 0xff00ff) + { + CGContextClearRect(myContext,CGRectMake(x,y,1,1)); + rcnt++; + } + } + } + [newImage unlockFocus]; + + SWELL_DeleteGfxContext(hdc); + + if (rcnt) + { + [img release]; + img=newImage; + } + else + [newImage release]; + } + } + +return_img: + HGDIOBJ__ *i=GDP_OBJECT_NEW(); + i->type=TYPE_BITMAP; + i->wid=1; + i->bitmapptr = img; + return i; +} + +void DrawImageInRect(HDC ctx, HICON img, const RECT *r) +{ + HGDIOBJ__ *i = (HGDIOBJ__ *)img; + HDC__ *ct=(HDC__*)ctx; + if (!HDC_VALID(ct) || !HGDIOBJ_VALID(i,TYPE_BITMAP) || !i->bitmapptr) return; + if (WDL_NOT_NORMALLY(!ct->ctx)) return; + //CGContextDrawImage(ct->ctx,CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top),(CGImage*)i->bitmapptr); + // probably a better way since this ignores the ctx + [NSGraphicsContext saveGraphicsState]; + NSGraphicsContext *gc=[NSGraphicsContext graphicsContextWithGraphicsPort:ct->ctx flipped:NO]; + [NSGraphicsContext setCurrentContext:gc]; + NSImage *nsi=i->bitmapptr; + NSRect rr=NSMakeRect(r->left,r->top,r->right-r->left,r->bottom-r->top); + [nsi setFlipped:YES]; + [nsi drawInRect:rr fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; + [nsi setFlipped:NO]; // todo: restore old flippedness? + [NSGraphicsContext restoreGraphicsState]; +// [gc release]; +} + + +BOOL GetObject(HICON icon, int bmsz, void *_bm) +{ + memset(_bm,0,bmsz); + if (WDL_NOT_NORMALLY(bmsz < 2*(int)sizeof(LONG))) return false; + BITMAP *bm=(BITMAP *)_bm; + HGDIOBJ__ *i = (HGDIOBJ__ *)icon; + if (!HGDIOBJ_VALID(i,TYPE_BITMAP)) return false; + NSImage *img = i->bitmapptr; + if (WDL_NOT_NORMALLY(!img)) return false; + bm->bmWidth = (int) ([img size].width+0.5); + bm->bmHeight = (int) ([img size].height+0.5); + if (bmsz >= (int)sizeof(BITMAP)) + { + bm->bmWidthBytes = bm->bmWidth * 4; + bm->bmPlanes = 1; + bm->bmBitsPixel = 32; + bm->bmBits = NULL; + } + + return true; +} + + +void *GetNSImageFromHICON(HICON ico) +{ + HGDIOBJ__ *i = (HGDIOBJ__ *)ico; + if (!HGDIOBJ_VALID(i,TYPE_BITMAP)) return 0; + return i->bitmapptr; +} + +int GetSysColor(int idx) +{ + switch (idx) + { + case COLOR_WINDOW: + case COLOR_BTNFACE: + case COLOR_3DFACE: return SWELL_osx_is_dark_mode(0) ? RGB(37,37,37) : RGB(232,232,232); + case COLOR_BTNTEXT: return SWELL_osx_is_dark_mode(0) ? RGB(255,255,255) : RGB(0,0,0); + case COLOR_SCROLLBAR: return RGB(32,32,32); + case COLOR_3DSHADOW: return RGB(96,96,96); + case COLOR_3DHILIGHT: return RGB(224,224,224); + case COLOR_3DDKSHADOW: return RGB(48,48,48); + case COLOR_INFOBK: return RGB(255,240,200); + case COLOR_INFOTEXT: return RGB(0,0,0); + } + return 0; +} + + +void BitBlt(HDC hdcOut, int x, int y, int w, int h, HDC hdcIn, int xin, int yin, int mode) +{ + StretchBlt(hdcOut,x,y,w,h,hdcIn,xin,yin,w,h,mode); +} + +void StretchBlt(HDC hdcOut, int x, int y, int destw, int desth, HDC hdcIn, int xin, int yin, int w, int h, int mode) +{ + HDC__ *src=(HDC__*)hdcIn; + HDC__ *dest=(HDC__*)hdcOut; + if (w<1 || h<1 || !HDC_VALID(src) || !HDC_VALID(dest) || !src->ownedData || !src->ctx) return; + + const int sw = (int)CGBitmapContextGetWidth(src->ctx); + const int sh = (int)CGBitmapContextGetHeight(src->ctx); + + const int preclip_w=w; + const int preclip_h=h; + + if (xin<0) + { + x-=(xin*destw)/w; + w+=xin; + xin=0; + } + if (yin<0) + { + y-=(yin*desth)/h; + h+=yin; + yin=0; + } + if (xin+w > sw) w=sw-xin; + if (yin+h > sh) h=sh-yin; + + if (w<1||h<1) return; + + if (destw==preclip_w) destw=w; // no scaling, keep width the same + else if (w != preclip_w) destw = (w*destw)/preclip_w; + + if (desth == preclip_h) desth=h; + else if (h != preclip_h) desth = (h*desth)/preclip_h; + + if (destw < 1 || desth < 1) return; + + const bool use_alphachannel = mode == (int)SRCCOPY_USEALPHACHAN; + + unsigned char *p = (unsigned char *)ALIGN_FBUF(src->ownedData); + p += (xin + sw*yin)*4; + +#ifndef SWELL_NO_METAL + + if (dest->metal_ctx) + { + void SWELL_Metal_Blit(void *tex, const unsigned int *buf, int x, int y, int w, int h, int span, bool retina_hint, bool use_alpha); + + const unsigned int *ptr = (const unsigned int *)p; + if (w == destw && h == desth) + SWELL_Metal_Blit(hdcOut->metal_ctx,ptr,x,y,w,h,sw,false, use_alphachannel); + else if (w == destw*2 && h == desth*2) + SWELL_Metal_Blit(hdcOut->metal_ctx,ptr,x*2,y*2,w,h,sw,true, use_alphachannel); + else + { + // Using StretchBlt() to size contents isn't ideal (in Metal mode or in win32), but if the caller insists + const bool retina = w >= destw*2 && h >= desth*2 && SWELL_IsRetinaDC(hdcOut); + if (retina) + { + destw *= 2; + desth *= 2; + x*=2; + y*=2; + } + + // resize a copy of image to destw/desth/destsw/destptr + static WDL_TypedBuf tmp; + const int destspan = (destw+3)&~3; + const unsigned int dx = (w * 65536) / destw, dy = (h * 65536) / desth; + unsigned int *destptr = tmp.ResizeOK(destspan*desth, false); + if (WDL_NOT_NORMALLY(!destptr)) return; + + unsigned int *wr = destptr; + for (int i=0;i>16; + if (WDL_NOT_NORMALLY(yp >= (unsigned int)h)) break; + const unsigned int *rd = ptr + yp*sw; + int xpos = 0; + for (int j=0;j>16); + if (WDL_NOT_NORMALLY(xp >= (unsigned int)w)) break; + wr[j] = rd[xp]; + xpos += dx; + } + wr += destspan; + } + + SWELL_Metal_Blit(hdcOut->metal_ctx,destptr,x,y, destw, desth, destspan, retina, use_alphachannel); + } + + return; + } + +#endif + + if (!dest->ctx) return; + + CGContextRef output = (CGContextRef)dest->ctx; + CGRect outputr = CGRectMake(x,-desth-y,destw,desth); + + +#ifdef SWELL_SUPPORT_OPENGL_BLIT + if (dest->GLgfxctx) + { + NSOpenGLContext *glCtx = (NSOpenGLContext*) dest->GLgfxctx; + NSOpenGLContext *cCtx = [NSOpenGLContext currentContext]; + if (glCtx != cCtx) + { + [glCtx makeCurrentContext]; + } + + glDisable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_RECTANGLE_EXT); + + GLuint texid=0; + glGenTextures(1, &texid); + glBindTexture(GL_TEXTURE_RECTANGLE_EXT, texid); + glPixelStorei(GL_UNPACK_ROW_LENGTH, sw); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_RGBA8,w,h,0,GL_BGRA,GL_UNSIGNED_INT_8_8_8_8_REV, p); + + glViewport(x,[[glCtx view] bounds].size.height-desth-y,destw,desth); + glBegin(GL_QUADS); + + glTexCoord2f(0.0f, 0.0f); + glVertex2f(-1,1); + + glTexCoord2f(0.0f, h); + glVertex2f(-1,-1); + + glTexCoord2f(w,h); + glVertex2f(1,-1); + + glTexCoord2f(w, 0.0f); + glVertex2f(1,1); + glEnd(); + + glDeleteTextures(1,&texid); + glFlush(); + + if (glCtx != cCtx) [cCtx makeCurrentContext]; + return; + } +#endif + + + + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL,p,4*sw*h,NULL); + CGImageRef img = CGImageCreate(w,h,8,32,4*sw,__GetDisplayColorSpace(), + (use_alphachannel?kCGImageAlphaFirst:kCGImageAlphaNoneSkipFirst)|kCGBitmapByteOrder32Host, + provider,NULL,NO,kCGRenderingIntentDefault); + CGDataProviderRelease(provider); + + if (img) + { + CGContextSaveGState(output); + CGContextScaleCTM(output,1.0,-1.0); + + CGContextSetInterpolationQuality(output,kCGInterpolationNone); + CGContextDrawImage(output,outputr,img); + CGContextRestoreGState(output); + + CGImageRelease(img); + } +} + +void SWELL_PushClipRegion(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct) && ct->ctx) CGContextSaveGState(ct->ctx); +} + +void SWELL_SetClipRegion(HDC ctx, const RECT *r) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct) && ct->ctx) CGContextClipToRect(ct->ctx,CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top)); + +} + +void SWELL_PopClipRegion(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct) && ct->ctx) CGContextRestoreGState(ct->ctx); +} + +void *SWELL_GetCtxFrameBuffer(HDC ctx) +{ + HDC__ *ct=(HDC__ *)ctx; + if (HDC_VALID(ct)) return ALIGN_FBUF(ct->ownedData); + return 0; +} + + +HDC GetDC(HWND h) +{ + WDL_ASSERT(h); + if (h && [(id)h isKindOfClass:[NSWindow class]]) + { + if ([(id)h respondsToSelector:@selector(getSwellPaintInfo:)]) + { + PAINTSTRUCT ps={0,}; + [(id)h getSwellPaintInfo:(PAINTSTRUCT *)&ps]; + if (ps.hdc) + { + if ((ps.hdc)->ctx) CGContextSaveGState((ps.hdc)->ctx); + return ps.hdc; + } + } + h=(HWND)[(id)h contentView]; + } + + if (h && [(id)h isKindOfClass:[NSView class]]) + { + if ([(id)h respondsToSelector:@selector(getSwellPaintInfo:)]) + { + PAINTSTRUCT ps={0,}; + [(id)h getSwellPaintInfo:(PAINTSTRUCT *)&ps]; + if (HDC_VALID((HDC__*)ps.hdc)) + { + if (((HDC__*)ps.hdc)->ctx) CGContextSaveGState((ps.hdc)->ctx); + return ps.hdc; + } + } + +#ifndef SWELL_NO_METAL + if ([(id)h isKindOfClass:[SWELL_hwndChild class]] && [(SWELL_hwndChild *)h swellWantsMetal]) + { + SWELL_hwndChild *wnd = (SWELL_hwndChild*)h; + + wnd->m_metal_dc_dirty = 2; + return SWELL_CreateMetalDC(wnd); + } +#endif + + if ([(NSView*)h lockFocusIfCanDraw]) + { + HDC ret= SWELL_CreateGfxContext([NSGraphicsContext currentContext]); + if (ret) + { + if (ret->ctx) CGContextSaveGState(ret->ctx); + if (!ret->GLgfxctx && [(id)h respondsToSelector:@selector(swellGetGLContext)]) + { + NSOpenGLContext *glctx = (NSOpenGLContext*)[(id)h swellGetGLContext]; + ret->GLgfxctx = glctx; + if (glctx) [glctx setView:(NSView *)h]; + } + } + return ret; + } + } + return 0; +} + +HDC GetWindowDC(HWND h) +{ + WDL_ASSERT(h); + HDC ret=GetDC(h); + if (ret) + { + NSView *v=NULL; + if ([(id)h isKindOfClass:[NSWindow class]]) v=[(id)h contentView]; + else if ([(id)h isKindOfClass:[NSView class]]) v=(NSView *)h; + + if (v) + { + NSRect b=[v bounds]; + float xsc=b.origin.x; + float ysc=b.origin.y; + if ((xsc || ysc) && (ret)->ctx) CGContextTranslateCTM((ret)->ctx,xsc,ysc); + } + } + return ret; +} + +void ReleaseDC(HWND h, HDC hdc) +{ + WDL_ASSERT(h); + if (hdc) + { + if ((hdc)->ctx) CGContextRestoreGState((hdc)->ctx); + } + if (h && [(id)h isKindOfClass:[NSWindow class]]) + { + if ([(id)h respondsToSelector:@selector(getSwellPaintInfo:)]) + { + PAINTSTRUCT ps={0,}; + [(id)h getSwellPaintInfo:(PAINTSTRUCT *)&ps]; + if (ps.hdc && ps.hdc==hdc) return; + } + h=(HWND)[(id)h contentView]; + } + bool isView=h && [(id)h isKindOfClass:[NSView class]]; + if (isView) + { + if ([(id)h respondsToSelector:@selector(getSwellPaintInfo:)]) + { + PAINTSTRUCT ps={0,}; + [(id)h getSwellPaintInfo:(PAINTSTRUCT *)&ps]; + if (ps.hdc && ps.hdc==hdc) return; + } + } + if (hdc && hdc->GLgfxctx) + { + if ([NSOpenGLContext currentContext] == hdc->GLgfxctx) [NSOpenGLContext clearCurrentContext]; + hdc->GLgfxctx = NULL; + } + + if (hdc) SWELL_DeleteGfxContext(hdc); + if (isView && hdc) + { +#ifndef SWELL_NO_METAL + if ([(id)h isKindOfClass:[SWELL_hwndChild class]] && [(SWELL_hwndChild *)h swellWantsMetal]) + { + SWELL_hwndChild *wnd = (SWELL_hwndChild*)h; + if (wnd->m_metal_dc_dirty == 1) + { + if (WDL_NOT_NORMALLY(wnd->m_use_metal == 1)) + { + NSLog(@"swell-cocoa: metal(1) surface %p had write in GetDC()/ReleaseDC(), this is unsupported, use a metal(2) surface\n",wnd); + } + swell_addMetalDirty(wnd,NULL,true); + } + } + else +#endif + [(NSView *)h unlockFocus]; + } +} + +void SWELL_FillDialogBackground(HDC hdc, const RECT *r, int level) +{ + CGContextRef ctx=(CGContextRef)SWELL_GetCtxGC(hdc); + if (WDL_NORMALLY(ctx)) + { + bool ok = false; + if (SWELL_osx_is_dark_mode(1)) + { + CGColorRef col = CreateColor(GetSysColor(COLOR_3DFACE)); + if (col) + { + CGContextSetFillColorWithColor(ctx, col); + CGColorRelease(col); + ok = true; + } + } + else if (SWELL_GDI_GetOSXVersion()>=0x10d0) + { + NSColor *c = [NSColor windowBackgroundColor]; + if ([c respondsToSelector:@selector(CGColor)]) + { + void *(*send_msg)(id, SEL) = (void *(*)(id, SEL))objc_msgSend; + CGContextSetFillColorWithColor(ctx, (CGColorRef)send_msg(c, @selector(CGColor))); + ok = true; + } + } + + if (!ok) + HIThemeSetFill(kThemeBrushDialogBackgroundActive,NULL,ctx,kHIThemeOrientationNormal); + + CGRect rect=CGRectMake(r->left,r->top,r->right-r->left,r->bottom-r->top); + CGContextFillRect(ctx,rect); + } +} + +HGDIOBJ SWELL_CloneGDIObject(HGDIOBJ a) +{ + if (HGDIOBJ_VALID(a)) + { + a->additional_refcnt++; + return a; + } + return NULL; +} + + +HBITMAP CreateBitmap(int width, int height, int numplanes, int bitsperpixel, unsigned char* bits) +{ + int spp = bitsperpixel/8; + Boolean hasa = (bitsperpixel == 32); + Boolean hasp = (numplanes > 1); // won't actually work yet for planar data + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:0 pixelsWide:width pixelsHigh:height + bitsPerSample:8 samplesPerPixel:spp + hasAlpha:hasa isPlanar:hasp + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:NSAlphaFirstBitmapFormat + bytesPerRow:0 bitsPerPixel:0]; + if (WDL_NOT_NORMALLY(!rep)) return 0; + unsigned char* p = [rep bitmapData]; + const int pspan = (int)[rep bytesPerRow]; // might not be the same as width + + for (int y=0;ytype = TYPE_BITMAP; + obj->wid = 1; // need free + obj->bitmapptr = img; + return obj; +} + + +HIMAGELIST ImageList_CreateEx() +{ + return (HIMAGELIST)new WDL_PtrList; +} + +BOOL ImageList_Remove(HIMAGELIST list, int idx) +{ + WDL_PtrList* imglist=(WDL_PtrList*)list; + if (WDL_NORMALLY(imglist) && idx < imglist->GetSize()) + { + if (idx < 0) + { + int x,n=imglist->GetSize(); + for (x=0;xGet(x); + if (a) DeleteObject(a); + } + imglist->Empty(); + } + else + { + HGDIOBJ__ *a = imglist->Get(idx); + imglist->Set(idx, NULL); + if (a) DeleteObject(a); + } + return TRUE; + } + + return FALSE; +} + +void ImageList_Destroy(HIMAGELIST list) +{ + if (WDL_NOT_NORMALLY(!list)) return; + ImageList_Remove(list, -1); + delete (WDL_PtrList*)list; +} + +int ImageList_ReplaceIcon(HIMAGELIST list, int offset, HICON image) +{ + if (WDL_NOT_NORMALLY(!image || !list)) return -1; + WDL_PtrList *l=(WDL_PtrList *)list; + + HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image; + if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1; + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + icon->type=TYPE_BITMAP; + icon->wid=1; + icon->bitmapptr = imgsrc->bitmapptr; // no need to duplicate it, can just retain a copy + [icon->bitmapptr retain]; + image = (HICON) icon; + + if (offset<0||offset>=l->GetSize()) + { + l->Add(image); + offset=l->GetSize()-1; + } + else + { + HICON old=l->Get(offset); + l->Set(offset,image); + if (old) DeleteObject(old); + } + return offset; +} + +int ImageList_Add(HIMAGELIST list, HBITMAP image, HBITMAP mask) +{ + if (WDL_NOT_NORMALLY(!image || !list)) return -1; + WDL_PtrList *l=(WDL_PtrList *)list; + + HGDIOBJ__ *imgsrc = (HGDIOBJ__*)image; + if (!HGDIOBJ_VALID(imgsrc,TYPE_BITMAP)) return -1; + + HGDIOBJ__* icon=GDP_OBJECT_NEW(); + icon->type=TYPE_BITMAP; + icon->wid=1; + NSImage *nsimg = [imgsrc->bitmapptr copy]; // caller still owns the image + [nsimg setFlipped:YES]; + icon->bitmapptr = nsimg; + image = (HICON) icon; + + l->Add(image); + return l->GetSize(); +} + +int AddFontResourceEx(LPCTSTR str, DWORD fl, void *pdv) +{ + if (SWELL_GDI_GetOSXVersion() < 0x1060) return 0; + static bool l; + static bool (*_CTFontManagerRegisterFontsForURL)( CFURLRef fontURL, uint32_t scope, CFErrorRef *error ); + if (!l) + { + CFBundleRef b = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.CoreText")); + if (b) + { + *(void **)&_CTFontManagerRegisterFontsForURL = CFBundleGetFunctionPointerForName(b,CFSTR("CTFontManagerRegisterFontsForURL")); + } + + l=true; + } + + if (!_CTFontManagerRegisterFontsForURL) return 0; + + CFStringRef s=(CFStringRef)CStringToNSString(str); + + CFURLRef r=CFURLCreateWithFileSystemPath(NULL,s,kCFURLPOSIXPathStyle,true); + CFErrorRef err=NULL; + const int v = _CTFontManagerRegisterFontsForURL(r, + (fl & FR_PRIVATE) ? 1/*kCTFontManagerScopeProcess*/ : 2/*kCTFontManagerScopeUser*/, + &err)?1:0; + + // release err? don't think so + + CFRelease(s); + CFRelease(r); + return v; +} + +bool SWELL_osx_is_dark_mode(int mode) // mode=0 for enabled, 1=allowed +{ + static DWORD lastchk; + static char last_res,c; + if (!c) + { + NSUserDefaults *def = SWELL_GDI_GetOSXVersion() >= 0x10d0 ? [NSUserDefaults standardUserDefaults] : NULL; + c = (def && [def objectForKey:@"NSRequiresAquaSystemAppearance"] && [def boolForKey:@"NSRequiresAquaSystemAppearance"] == NO) ? 1 : -1; + } + if (c<0) return false; + if (mode == 1) return true; + + const DWORD now = GetTickCount(); + if (!last_res || (now-lastchk) > 1000) + { + lastchk = now; + last_res = [[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"] isEqualToString:@"Dark"] ? 1 : -1; + } + return last_res>0; +} + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-generic-gdk.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-generic-gdk.cpp new file mode 100644 index 0000000..0a30667 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-generic-gdk.cpp @@ -0,0 +1,3402 @@ +/* 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. + + + */ + + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" + +//#define SWELL_GDK_IMPROVE_WINDOWRECT // does not work yet (gdk_window_get_frame_extents() does not seem to be sufficiently reliable) + +#ifdef SWELL_PRELOAD +#define STR(x) #x +#define STR2(x) STR(x) +extern "C" { + char __attribute__ ((visibility ("default"))) SWELL_WANT_LOAD_LIBRARY[] = STR2(SWELL_PRELOAD); +}; +#undef STR +#undef STR2 +#endif + +#ifdef SWELL_TARGET_GDK + +#include "swell-internal.h" +#include "swell-dlggen.h" +#include "../wdlcstring.h" +#include "../wdlutf8.h" + + +#if !defined(SWELL_TARGET_GDK_NO_CURSOR_HACK) + #define SWELL_TARGET_GDK_CURSORHACK +#endif + +#include + +#include + +#include +#include + +static void (*_gdk_drag_drop_done)(GdkDragContext *, gboolean); // may not always be available + +static guint32 _gdk_x11_window_get_desktop(GdkWindow *window) +{ + Atom type; + gint format; + gulong nitems=0, bytes_after; + guchar *data; + + if (!window || !gdk_x11_screen_supports_net_wm_hint(gdk_window_get_screen(window), + gdk_atom_intern_static_string("_NET_WM_DESKTOP"))) + return 0; + + XGetWindowProperty(GDK_WINDOW_XDISPLAY(window), GDK_WINDOW_XID(window), + gdk_x11_get_xatom_by_name_for_display(gdk_window_get_display(window), "_NET_WM_DESKTOP"), + 0, G_MAXLONG, false, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &data); + if (type != XA_CARDINAL || nitems<1) return 0; + nitems = *(gulong *)data; + XFree(data); + return (guint32) nitems; +} + +static void _gdk_x11_window_move_to_desktop(GdkWindow *window, guint32 desktop) +{ + XClientMessageEvent xclient; + + if (!window || !gdk_x11_screen_supports_net_wm_hint(gdk_window_get_screen(window), + gdk_atom_intern_static_string("_NET_WM_DESKTOP"))) + return; + + memset (&xclient, 0, sizeof (xclient)); + xclient.type = ClientMessage; + xclient.send_event = true; + xclient.window = GDK_WINDOW_XID(window); + xclient.message_type = gdk_x11_get_xatom_by_name_for_display(gdk_window_get_display(window), "_NET_WM_DESKTOP"); + xclient.format = 32; + xclient.data.l[0] = desktop; + xclient.data.l[1] = 1; + + XSendEvent(GDK_WINDOW_XDISPLAY(window), gdk_x11_get_default_root_xwindow(), false, + SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xclient); +} + +// for m_oswindow_private +#define PRIVATE_NEEDSHOW 1 + +#ifndef SWELL_WINDOWSKEY_GDK_MASK +#define SWELL_WINDOWSKEY_GDK_MASK GDK_MOD4_MASK +#endif + +static int SWELL_gdk_active; +static GdkEvent *s_cur_evt; +static GList *s_program_icon_list; + +static SWELL_OSWINDOW swell_dragsrc_osw; +static DWORD swell_dragsrc_timeout_start; +static HWND swell_dragsrc_hwnd; +static DWORD swell_lastMessagePos; +static const char *swell_dragsrc_fn; + +static int gdk_options; +#define OPTION_KEEP_OWNED_ABOVE 1 +#define OPTION_OWNED_TASKLIST 2 +#define OPTION_BORDERLESS_OVERRIDEREDIRECT 4 +#define OPTION_BORDERLESS_DIALOG 8 +#define OPTION_ALLOW_MAYBE_INACTIVE 16 +#define OPTION_FULLSCREEN_FOR_OWNER_WINDOWS 32 +#define OPTION_FULLSCREEN_DYNAMIC 64 + +static HWND s_ddrop_hwnd; +static POINT s_ddrop_pt; + +static SWELL_CursorResourceIndex *SWELL_curmodule_cursorresource_head; + +static int s_cursor_vis_cnt; + +static HCURSOR s_last_cursor; +static HCURSOR s_last_setcursor; +static SWELL_OSWINDOW s_last_setcursor_oswnd; + +static void *g_swell_touchptr; // last GDK touch sequence +static void *g_swell_touchptr_wnd; // last window of touch sequence, for forcing end of sequence on destroy + +static bool g_swell_mouse_relmode; +static int g_swell_mouse_relmode_curpos_x; +static int g_swell_mouse_relmode_curpos_y; + +static HANDLE s_clipboard_getstate, s_clipboard_setstate; +static GdkAtom s_clipboard_getstate_fmt, s_clipboard_setstate_fmt; + +static WDL_IntKeyedArray m_clip_recs(GlobalFree); +static WDL_PtrList m_clip_curfmts; +static HWND s_clip_hwnd; + +static void swell_gdkEventHandler(GdkEvent *event, gpointer data); + +static int s_last_desktop; +static UINT_PTR s_deactivate_timer; +static guint32 s_force_window_time; +static bool swell_app_is_inactive; + +int swell_is_app_inactive() +{ + return swell_app_is_inactive ? 1 : (gdk_options&OPTION_ALLOW_MAYBE_INACTIVE) && s_deactivate_timer!=0 ? -1 : 0; +} + +static void update_menubar_activations() +{ + if (g_swell_ctheme.menubar_bg == g_swell_ctheme.menubar_bg_inactive && + g_swell_ctheme.menubar_text == g_swell_ctheme.menubar_text_inactive) return; + + HWND h = SWELL_topwindows; + while (h) + { + if (h->m_oswindow && h->m_menu) + { + DrawMenuBar(h); + } + h=h->m_next; + } +} + +static void on_activate(guint32 ftime) +{ + s_force_window_time = ftime; + swell_app_is_inactive=false; + HWND h = SWELL_topwindows; + while (h) + { + if (h->m_oswindow) + { + if (h->m_israised) + gdk_window_set_keep_above(h->m_oswindow,TRUE); + + if (!h->m_enabled) + gdk_window_set_accept_focus(h->m_oswindow,FALSE); + } + + PostMessage(h,WM_ACTIVATEAPP,1,0); + h=h->m_next; + } + s_last_desktop=0; + s_force_window_time = 0; + + update_menubar_activations(); +} + +void swell_gdk_reactivate_app(void) +{ + if (swell_app_is_inactive) + { + SWELL_focused_oswindow=NULL; + on_activate(GDK_CURRENT_TIME); + } +} + +static void on_deactivate() +{ + swell_app_is_inactive=true; + HWND lf = swell_oswindow_to_hwnd(SWELL_focused_oswindow); + s_last_desktop = lf && lf->m_oswindow ? _gdk_x11_window_get_desktop(lf->m_oswindow)+1 : 0; + + HWND h = SWELL_topwindows; + while (h) + { + if (h->m_oswindow) + { + if (h->m_israised) + gdk_window_set_keep_above(h->m_oswindow,FALSE); + if (!h->m_enabled) + gdk_window_set_accept_focus(h->m_oswindow,TRUE); // allow the user to activate app by clicking + } + PostMessage(h,WM_ACTIVATEAPP,0,0); + h=h->m_next; + } + swell_on_toplevel_raise(NULL); + DestroyPopupMenus(); +} + +void swell_oswindow_destroy(HWND hwnd) +{ + if (hwnd && hwnd->m_oswindow) + { + if (SWELL_focused_oswindow == hwnd->m_oswindow) SWELL_focused_oswindow = NULL; + if (g_swell_touchptr && g_swell_touchptr_wnd == hwnd->m_oswindow) + g_swell_touchptr = NULL; + gdk_window_destroy(hwnd->m_oswindow); + hwnd->m_oswindow=NULL; +#ifdef SWELL_LICE_GDI + delete hwnd->m_backingstore; + hwnd->m_backingstore=0; +#endif + + if (swell_app_is_inactive) + { + HWND h = SWELL_topwindows; + while (h) + { + if (h->m_oswindow) break; + h = h->m_next; + } + if (!h) on_activate(10); // arbitrary old timestamp that is nonzero + } + } +} +void swell_oswindow_update_text(HWND hwnd) +{ + if (hwnd && hwnd->m_oswindow) + { + gdk_window_set_title(hwnd->m_oswindow, (char*)hwnd->m_title.Get()); + } +} + +void swell_oswindow_focus(HWND hwnd) +{ + if (!hwnd) + { + SWELL_focused_oswindow = NULL; + update_menubar_activations(); + return; + } + + while (hwnd && !hwnd->m_oswindow) hwnd=hwnd->m_parent; + if (hwnd && !swell_app_is_inactive) + { + gdk_window_raise(hwnd->m_oswindow); + if (hwnd->m_oswindow != SWELL_focused_oswindow) + { + SWELL_focused_oswindow = hwnd->m_oswindow; + gdk_window_focus(hwnd->m_oswindow,GDK_CURRENT_TIME); + update_menubar_activations(); + } + } +} + +void swell_recalcMinMaxInfo(HWND hwnd) +{ + if (!hwnd || !hwnd->m_oswindow || !(hwnd->m_style & WS_CAPTION)) return; + + MINMAXINFO mmi; + memset(&mmi,0,sizeof(mmi)); + if (hwnd->m_style & WS_THICKFRAME) + { + mmi.ptMinTrackSize.x = 20; + mmi.ptMaxSize.x = mmi.ptMaxTrackSize.x = 16384; + mmi.ptMinTrackSize.y = 20; + mmi.ptMaxSize.y = mmi.ptMaxTrackSize.y = 16384; + SendMessage(hwnd,WM_GETMINMAXINFO,0,(LPARAM)&mmi); + } + else + { + RECT r=hwnd->m_position; + mmi.ptMinTrackSize.x = mmi.ptMaxSize.x = mmi.ptMaxTrackSize.x = r.right-r.left; + mmi.ptMinTrackSize.y = mmi.ptMaxSize.y = mmi.ptMaxTrackSize.y = r.bottom-r.top; + } + + GdkGeometry h; + memset(&h,0,sizeof(h)); + h.max_width= mmi.ptMaxSize.x; + h.max_height= mmi.ptMaxSize.y; + h.min_width= mmi.ptMinTrackSize.x; + h.min_height= mmi.ptMinTrackSize.y; + gdk_window_set_geometry_hints(hwnd->m_oswindow,&h,(GdkWindowHints) ((hwnd->m_has_had_position ? GDK_HINT_POS : 0) | GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE)); +} + +void SWELL_initargs(int *argc, char ***argv) +{ + if (!SWELL_gdk_active) + { + XInitThreads(); +#if SWELL_TARGET_GDK == 3 + void (*_gdk_set_allowed_backends)(const char *); + + *(void **)&_gdk_drag_drop_done = dlsym(RTLD_DEFAULT,"gdk_drag_drop_done"); + *(void **)&_gdk_set_allowed_backends = dlsym(RTLD_DEFAULT,"gdk_set_allowed_backends"); + + if (_gdk_set_allowed_backends) + _gdk_set_allowed_backends("x11"); +#endif + +#ifdef SWELL_SUPPORT_GTK + SWELL_gdk_active = gtk_init_check(argc,argv) ? 1 : -1; +#else + SWELL_gdk_active = gdk_init_check(argc,argv) ? 1 : -1; +#endif + if (SWELL_gdk_active > 0) + { + char buf[1024]; + GetModuleFileName(NULL,buf,sizeof(buf)); + WDL_remove_filepart(buf); + lstrcatn(buf,"/Resources/main.png",sizeof(buf)); + GdkPixbuf *pb = gdk_pixbuf_new_from_file(buf,NULL); + if (!pb) + { + strcpy(buf+strlen(buf)-3,"ico"); + pb = gdk_pixbuf_new_from_file(buf,NULL); + } + if (pb) s_program_icon_list = g_list_append(s_program_icon_list,pb); + + gdk_event_handler_set(swell_gdkEventHandler,NULL,NULL); + } + } +} + +static bool swell_initwindowsys() +{ + if (!SWELL_gdk_active) + { + // maybe make the main app call this with real parms + int argc=1; + char buf[32]; + strcpy(buf,"blah"); + char *argv[2]; + argv[0] = buf; + argv[1] = buf; + char **pargv = argv; + SWELL_initargs(&argc,&pargv); + } + + return SWELL_gdk_active>0; +} + +#ifdef SWELL_LICE_GDI +class LICE_CairoBitmap : public LICE_IBitmap +{ + public: + LICE_CairoBitmap() + { + m_fb = NULL; + m_allocsize = m_width = m_height = m_span = 0; + m_surf = NULL; + } + virtual ~LICE_CairoBitmap() + { + if (m_surf) cairo_surface_destroy(m_surf); + free(m_fb); + } + + // LICE_IBitmap interface + virtual LICE_pixel *getBits() + { + const UINT_PTR extra=LICE_MEMBITMAP_ALIGNAMT; + return (LICE_pixel *) (((UINT_PTR)m_fb + extra)&~extra); + } + + virtual int getWidth() { return m_width; } + virtual int getHeight() { return m_height; } + virtual int getRowSpan() { return m_span; } + virtual bool resize(int w, int h) + { + if (w<0) w=0; + if (h<0) h=0; + if (w == m_width && h == m_height) return false; + + if (m_surf) cairo_surface_destroy(m_surf); + m_surf = NULL; + + m_span = w ? cairo_format_stride_for_width(CAIRO_FORMAT_RGB24,w)/4 : 0; + const int sz = h * m_span * 4 + LICE_MEMBITMAP_ALIGNAMT; + if (!m_fb || m_allocsize < sz || sz < m_allocsize/4) + { + const int newalloc = m_allocsize= '0' && buf[0] <= '9') return atoi(buf); + return defv; +} + +static void init_options() +{ + if (!gdk_options) + { + gdk_options = 0x40000000; + + if (swell_gdk_option("gdk_owned_windows_keep_above", "auto (default is 1)",1)) + gdk_options|=OPTION_KEEP_OWNED_ABOVE; + + if (swell_gdk_option("gdk_owned_windows_in_tasklist", "auto (default is 0)",0)) + gdk_options|=OPTION_OWNED_TASKLIST; + + switch (swell_gdk_option("gdk_instant_menubar_inactivation", "auto (default is 1 if on Wayland, otherwise 0)",-1)) + { + case -1: + if (getenv("WAYLAND_DISPLAY") == NULL) break; + // fall through + case 1: + gdk_options|=OPTION_ALLOW_MAYBE_INACTIVE; + break; + } + + switch (swell_gdk_option("gdk_borderless_window_mode", "auto (default is 1=dialog hint. 2=override redirect. 0=normal hint)", 1)) + { + case 1: gdk_options|=OPTION_BORDERLESS_DIALOG; break; + case 2: gdk_options|=OPTION_BORDERLESS_OVERRIDEREDIRECT; break; + default: break; + } + + const char *wmname = gdk_x11_screen_get_window_manager_name(gdk_screen_get_default()); + switch (swell_gdk_option("gdk_fullscreen_for_owner_windows", "auto (default is 1 on kwin, otherwise 0)",-1)) + { + case -1: + if (!wmname || strnicmp(wmname,"KWin",4)) break; + // fall through + case 1: + gdk_options |= OPTION_FULLSCREEN_FOR_OWNER_WINDOWS; + break; + } + switch (swell_gdk_option("gdk_fullscreen_dynamic","auto (default is 1 on kwin, otherwise 0)",-1)) + { + case -1: + if (!wmname || strnicmp(wmname,"KWin",4)) break; + // fall through + case 1: + gdk_options |= OPTION_FULLSCREEN_DYNAMIC; + break; + + } + } + +} + +static void swell_hide_owned_windows_transient(HWND hwnd) +{ + if ((gdk_options&OPTION_KEEP_OWNED_ABOVE) && hwnd->m_owned_list) + { + HWND l = SWELL_topwindows; + while (l) + { + if (l->m_oswindow && l->m_owner == hwnd && l->m_visible) + gdk_window_hide(l->m_oswindow); + l = l->m_next; + } + } +} + +static void swell_set_owned_windows_transient(HWND hwnd, bool do_create) +{ + if ((gdk_options&OPTION_KEEP_OWNED_ABOVE) && hwnd->m_owned_list) + { + WDL_PtrList raiselist; + HWND l = SWELL_topwindows; + while (l) + { + if (l->m_owner == hwnd && l->m_visible) + { + if (l->m_oswindow) raiselist.Add(l->m_oswindow); + else if (do_create) swell_oswindow_manage(l,false); + } + l = l->m_next; + } + for (int x = raiselist.GetSize()-1; x>=0; x--) + { + SWELL_OSWINDOW r = (SWELL_OSWINDOW)raiselist.Get(x); + gdk_window_set_transient_for(r,hwnd->m_oswindow); + gdk_window_show_unraised(r); + } + } +} + + + +bool IsModalDialogBox(HWND); + +void swell_oswindow_manage(HWND hwnd, bool wantfocus) +{ + if (!hwnd) return; + + bool isVis = hwnd->m_oswindow != NULL; + bool wantVis = !hwnd->m_parent && hwnd->m_visible; + + if (isVis != wantVis) + { + if (!wantVis) + { + RECT r; + GetWindowRect(hwnd,&r); + swell_hide_owned_windows_transient(hwnd); + swell_oswindow_destroy(hwnd); + hwnd->m_position = r; + } + else + { + if (swell_initwindowsys()) + { + init_options(); + + SWELL_OSWINDOW transient_for=NULL; + if (hwnd->m_owner && (gdk_options&OPTION_KEEP_OWNED_ABOVE)) + { + HWND own = hwnd->m_owner; + while (own->m_parent && !own->m_oswindow) own=own->m_parent; + + if (!own->m_oswindow) + { + if (!IsModalDialogBox(hwnd)) return; // defer + + // if a modal window, parent to any owner up the chain + while (own->m_owner && !own->m_oswindow) + { + own = own->m_owner; + while (own->m_parent && !own->m_oswindow) own=own->m_parent; + } + } + transient_for = own->m_oswindow; + } + + RECT r = hwnd->m_position; + GdkWindowAttr attr={0,}; + attr.title = (char *)hwnd->m_title.Get(); + attr.event_mask = GDK_ALL_EVENTS_MASK|GDK_EXPOSURE_MASK; + attr.x = r.left; + attr.y = r.top; + attr.width = r.right-r.left; + attr.height = r.bottom-r.top; + attr.wclass = GDK_INPUT_OUTPUT; + const char *appname = g_swell_appname; + attr.wmclass_name = (gchar*)appname; + attr.wmclass_class = (gchar*)appname; + attr.window_type = GDK_WINDOW_TOPLEVEL; + if (GetProp(hwnd,"SWELLGdkAlphaChannel")) + attr.visual = gdk_screen_get_rgba_visual(gdk_screen_get_default()); + hwnd->m_oswindow = gdk_window_new(NULL,&attr,GDK_WA_X|GDK_WA_Y|(appname?GDK_WA_WMCLASS:0)|(attr.visual ? GDK_WA_VISUAL : 0)); + + if (hwnd->m_oswindow) + { + bool override_redirect=false; + const bool modal = DialogBoxIsActive() == hwnd; + + if (!(hwnd->m_style & WS_CAPTION)) + { + if (hwnd->m_style != WS_CHILD && !(gdk_options&OPTION_BORDERLESS_OVERRIDEREDIRECT)) + { + if (transient_for) + gdk_window_set_transient_for(hwnd->m_oswindow,transient_for); + gdk_window_set_type_hint(hwnd->m_oswindow, (gdk_options&OPTION_BORDERLESS_DIALOG) ? GDK_WINDOW_TYPE_HINT_DIALOG : GDK_WINDOW_TYPE_HINT_NORMAL); + gdk_window_set_decorations(hwnd->m_oswindow,(GdkWMDecoration) 0); + } + else + { + gdk_window_set_override_redirect(hwnd->m_oswindow,true); + override_redirect=true; + } + if (!SWELL_topwindows || + (SWELL_topwindows==hwnd && !hwnd->m_next)) wantfocus=true; + } + else + { + GdkWindowTypeHint type_hint = GDK_WINDOW_TYPE_HINT_NORMAL; + GdkWMDecoration decor = (GdkWMDecoration) (GDK_DECOR_ALL | GDK_DECOR_MENU); + + if (!(hwnd->m_style&WS_THICKFRAME)) + decor = (GdkWMDecoration) (GDK_DECOR_BORDER|GDK_DECOR_TITLE|GDK_DECOR_MINIMIZE); + + if (transient_for) + { + gdk_window_set_transient_for(hwnd->m_oswindow,transient_for); + if (modal) + gdk_window_set_modal_hint(hwnd->m_oswindow,true); + } + + if (modal) type_hint = GDK_WINDOW_TYPE_HINT_DIALOG; + + gdk_window_set_type_hint(hwnd->m_oswindow,type_hint); + gdk_window_set_decorations(hwnd->m_oswindow,decor); + } + + if (s_force_window_time) + gdk_x11_window_set_user_time(hwnd->m_oswindow,s_force_window_time); + + if (!wantfocus || swell_app_is_inactive) + gdk_window_set_focus_on_map(hwnd->m_oswindow,false); + +#ifdef SWELL_LICE_GDI + if (!hwnd->m_backingstore) hwnd->m_backingstore = new LICE_CairoBitmap; +#endif + if (!override_redirect) + { + if (s_program_icon_list) + gdk_window_set_icon_list(hwnd->m_oswindow,s_program_icon_list); + } + if (hwnd->m_owner && !(gdk_options&OPTION_OWNED_TASKLIST) && !override_redirect) + { + gdk_window_set_skip_taskbar_hint(hwnd->m_oswindow,true); + } + else if (hwnd->m_style == WS_CHILD) + { + // hack: parentless visible window with WS_CHILD set will + // not appear in taskbar + gdk_window_set_skip_taskbar_hint(hwnd->m_oswindow,true); + } + + if (hwnd->m_israised && !swell_app_is_inactive) + gdk_window_set_keep_above(hwnd->m_oswindow,TRUE); + + gdk_window_register_dnd(hwnd->m_oswindow); + + if (hwnd->m_oswindow_fullscreen) + gdk_window_fullscreen(hwnd->m_oswindow); + + if (!swell_app_is_inactive && !s_force_window_time) + gdk_window_show(hwnd->m_oswindow); + else + gdk_window_show_unraised(hwnd->m_oswindow); + + if (s_last_desktop>0) + _gdk_x11_window_move_to_desktop(hwnd->m_oswindow,s_last_desktop-1); + + if (!hwnd->m_oswindow_fullscreen) + { + swell_oswindow_resize(hwnd->m_oswindow,hwnd->m_has_had_position?3:2,r); + swell_oswindow_postresize(hwnd,r); + } + + swell_set_owned_windows_transient(hwnd, true); + } + } + } + } + if (wantVis) swell_oswindow_update_text(hwnd); +} + +void swell_oswindow_maximize(HWND hwnd, bool wantmax) // false=restore +{ + if (WDL_NORMALLY(hwnd && hwnd->m_oswindow)) + { + if (wantmax) + gdk_window_maximize(hwnd->m_oswindow); + else + gdk_window_unmaximize(hwnd->m_oswindow); + } +} + +void swell_oswindow_updatetoscreen(HWND hwnd, RECT *rect) +{ +#ifdef SWELL_LICE_GDI + if (hwnd && hwnd->m_backingstore && hwnd->m_oswindow) + { + LICE_IBitmap *bm = hwnd->m_backingstore; + LICE_SubBitmap tmpbm(bm,rect->left,rect->top,rect->right-rect->left,rect->bottom-rect->top); + + GdkRectangle rrr={rect->left,rect->top,rect->right-rect->left,rect->bottom-rect->top}; + gdk_window_begin_paint_rect(hwnd->m_oswindow, &rrr); + + cairo_t * crc = gdk_cairo_create (hwnd->m_oswindow); + cairo_surface_t *temp_surface = (cairo_surface_t*)bm->Extended(0xca140,NULL); + if (temp_surface) cairo_set_source_surface(crc, temp_surface, 0,0); + cairo_paint(crc); + cairo_destroy(crc); + + gdk_window_end_paint(hwnd->m_oswindow); + + if (temp_surface) bm->Extended(0xca140,temp_surface); // release + + } +#endif +} + +#if SWELL_TARGET_GDK == 2 + #define DEF_GKY(x) GDK_##x +#else + #define DEF_GKY(x) GDK_KEY_##x +#endif + +static guint swell_gdkConvertKey(guint key, bool *extended) +{ + switch(key) + { +#define DEF_GK2(h, v) \ + case DEF_GKY(h): *extended = true; return (v); \ + case DEF_GKY(KP_##h): return (v); + + DEF_GK2(Home,VK_HOME) + DEF_GK2(End,VK_END) + DEF_GK2(Up, VK_UP) + DEF_GK2(Down, VK_DOWN) + DEF_GK2(Left, VK_LEFT) + DEF_GK2(Right, VK_RIGHT) + DEF_GK2(Page_Up, VK_PRIOR) + DEF_GK2(Page_Down, VK_NEXT) + DEF_GK2(Insert,VK_INSERT) + DEF_GK2(Delete, VK_DELETE) + +#undef DEF_GK2 + case DEF_GKY(KP_Enter): *extended = true; return VK_RETURN; + case DEF_GKY(Return): return VK_RETURN; + + case DEF_GKY(Escape): return VK_ESCAPE; + case DEF_GKY(BackSpace): return VK_BACK; + case DEF_GKY(ISO_Left_Tab): + case DEF_GKY(Tab): return VK_TAB; + case DEF_GKY(F1): return VK_F1; + case DEF_GKY(F2): return VK_F2; + case DEF_GKY(F3): return VK_F3; + case DEF_GKY(F4): return VK_F4; + case DEF_GKY(F5): return VK_F5; + case DEF_GKY(F6): return VK_F6; + case DEF_GKY(F7): return VK_F7; + case DEF_GKY(F8): return VK_F8; + case DEF_GKY(F9): return VK_F9; + case DEF_GKY(F10): return VK_F10; + case DEF_GKY(F11): return VK_F11; + case DEF_GKY(F12): return VK_F12; + case DEF_GKY(KP_0): return VK_NUMPAD0; + case DEF_GKY(KP_1): return VK_NUMPAD1; + case DEF_GKY(KP_2): return VK_NUMPAD2; + case DEF_GKY(KP_3): return VK_NUMPAD3; + case DEF_GKY(KP_4): return VK_NUMPAD4; + case DEF_GKY(KP_5): return VK_NUMPAD5; + case DEF_GKY(KP_6): return VK_NUMPAD6; + case DEF_GKY(KP_7): return VK_NUMPAD7; + case DEF_GKY(KP_8): return VK_NUMPAD8; + case DEF_GKY(KP_9): return VK_NUMPAD9; + case DEF_GKY(KP_Multiply): return VK_MULTIPLY; + case DEF_GKY(KP_Add): return VK_ADD; + case DEF_GKY(KP_Separator): return VK_SEPARATOR; + case DEF_GKY(KP_Subtract): return VK_SUBTRACT; + case DEF_GKY(KP_Decimal): return VK_DECIMAL; + case DEF_GKY(KP_Divide): return VK_DIVIDE; + case DEF_GKY(Num_Lock): return VK_NUMLOCK; + case DEF_GKY(KP_Begin): return VK_SELECT; + } + return 0; +} + +LRESULT SWELL_SendMouseMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +static int hex_parse(char c) +{ + if (c >= '0' && c <= '9') return c-'0'; + if (c >= 'A' && c <= 'F') return 10+c-'A'; + if (c >= 'a' && c <= 'f') return 10+c-'a'; + return -1; +} + +static GdkAtom utf8atom() +{ + static GdkAtom tmp; + if (!tmp) tmp = gdk_atom_intern_static_string("UTF8_STRING"); + return tmp; +} +static GdkAtom tgtatom() +{ + static GdkAtom tmp; + if (!tmp) tmp = gdk_atom_intern_static_string("TARGETS"); + return tmp; +} +static GdkAtom urilistatom() +{ + static GdkAtom tmp; + if (!tmp) tmp = gdk_atom_intern_static_string("text/uri-list"); + return tmp; +} + + +static void OnSelectionRequestEvent(GdkEventSelection *b) +{ + //printf("got sel req %s\n",gdk_atom_name(b->target)); + GdkAtom prop=GDK_NONE; + + if (swell_dragsrc_osw && b->window == swell_dragsrc_osw) + { + if (swell_dragsrc_hwnd) + { + if (b->target == tgtatom()) + { + prop = b->property; + GdkAtom list[] = { urilistatom() }; +#if SWELL_TARGET_GDK == 2 + GdkWindow *pw = gdk_window_lookup(b->requestor); + if (!pw) pw = gdk_window_foreign_new(b->requestor); +#else + GdkWindow *pw = b->requestor; +#endif + if (pw) + gdk_property_change(pw,prop,GDK_SELECTION_TYPE_ATOM,32, GDK_PROP_MODE_REPLACE,(guchar*)list,(int) (sizeof(list)/sizeof(list[0]))); + } + SendMessage(swell_dragsrc_hwnd,WM_USER+100,(WPARAM)b,(LPARAM)&prop); + } + } + else if (s_clipboard_setstate) + { + if (b->target == tgtatom()) + { + if (s_clipboard_setstate_fmt) + { + prop = b->property; + GdkAtom list[] = { s_clipboard_setstate_fmt }; +#if SWELL_TARGET_GDK == 2 + GdkWindow *pw = gdk_window_lookup(b->requestor); + if (!pw) pw = gdk_window_foreign_new(b->requestor); +#else + GdkWindow *pw = b->requestor; +#endif + if (pw) + gdk_property_change(pw,prop,GDK_SELECTION_TYPE_ATOM,32, GDK_PROP_MODE_REPLACE,(guchar*)list,(int) (sizeof(list)/sizeof(list[0]))); + } + } + else + { + if (b->target == s_clipboard_setstate_fmt || + (b->target == GDK_TARGET_STRING && s_clipboard_setstate_fmt == utf8atom()) + ) + { + prop = b->property; + int len = GlobalSize(s_clipboard_setstate); + guchar *ptr = (guchar*)s_clipboard_setstate; + + WDL_FastString str; + if (s_clipboard_setstate_fmt == utf8atom()) + { + const char *rd = (const char *)s_clipboard_setstate; + while (*rd) + { + if (!strncmp(rd,"\r\n",2)) + { + str.Append("\n"); + rd+=2; + } + else + str.Append(rd++,1); + } + ptr = (guchar *)str.Get(); + len = str.GetLength(); + } + else if (s_clipboard_setstate_fmt == urilistatom()) + { + if (len > (int)sizeof(DROPFILES)) + { + DROPFILES *hdr = (DROPFILES *)ptr; + if (WDL_NORMALLY(hdr->pFiles < (DWORD)len) && + WDL_NORMALLY(!hdr->fWide) // todo deal with UTF-16 + ) + { + const char *rd = (const char *)ptr; + DWORD rdo = hdr->pFiles; + while (rdo < (DWORD)len && rd[rdo]) + { + const char *fn = rd + rdo; + rdo += strlen(rd+rdo)+1; + str.Append("file://"); + while (*fn) + { + if (isalnum(*fn) || *fn == '.' || *fn == '_' || *fn == '-' || *fn == '/' || *fn == '#') + str.Append(fn,1); + else + str.AppendFormatted(8,"%%%02x",*(unsigned char *)fn); + fn++; + } + str.Append("\r\n"); + } + } + } + + ptr = (guchar *)str.Get(); + len = str.GetLength(); + } + +#if SWELL_TARGET_GDK == 2 + GdkWindow *pw = gdk_window_lookup(b->requestor); + if (!pw) pw = gdk_window_foreign_new(b->requestor); +#else + GdkWindow *pw = b->requestor; +#endif + if (pw) + gdk_property_change(pw,prop,b->target,8, GDK_PROP_MODE_REPLACE,ptr,len); + } + } + } + gdk_selection_send_notify(b->requestor,b->selection,b->target,prop,GDK_CURRENT_TIME); +} + +static void OnExposeEvent(GdkEventExpose *exp) +{ + HWND hwnd = swell_oswindow_to_hwnd(exp->window); + if (!hwnd) return; + +#ifdef SWELL_LICE_GDI + RECT r,cr; + + // don't use GetClientRect(),since we're getting it pre-NCCALCSIZE etc + + cr.left=cr.top=0; + cr.right = hwnd->m_position.right - hwnd->m_position.left; + cr.bottom = hwnd->m_position.bottom - hwnd->m_position.top; + + r.left = exp->area.x; + r.top=exp->area.y; + r.bottom=r.top+exp->area.height; + r.right=r.left+exp->area.width; + + if (!hwnd->m_backingstore) hwnd->m_backingstore = new LICE_CairoBitmap; + + bool forceref = hwnd->m_backingstore->resize(cr.right-cr.left,cr.bottom-cr.top); + if (forceref) r = cr; + + LICE_SubBitmap tmpbm(hwnd->m_backingstore,r.left,r.top,r.right-r.left,r.bottom-r.top); + + if (tmpbm.getWidth()>0 && tmpbm.getHeight()>0) + { + void SWELL_internalLICEpaint(HWND hwnd, LICE_IBitmap *bmout, int bmout_xpos, int bmout_ypos, bool forceref); + SWELL_internalLICEpaint(hwnd, &tmpbm, r.left, r.top, forceref); + + GdkRectangle rrr={r.left,r.top,r.right-r.left,r.bottom-r.top}; + gdk_window_begin_paint_rect(exp->window, &rrr); + + cairo_t *crc = gdk_cairo_create (exp->window); + LICE_IBitmap *bm = hwnd->m_backingstore; + cairo_surface_t *temp_surface = (cairo_surface_t*)bm->Extended(0xca140,NULL); + if (temp_surface) cairo_set_source_surface(crc, temp_surface, 0,0); + cairo_paint(crc); + cairo_destroy(crc); + if (temp_surface) bm->Extended(0xca140,temp_surface); // release + + gdk_window_end_paint(exp->window); + } +#endif +} + +static void OnConfigureEvent(GdkEventConfigure *cfg) +{ + HWND hwnd = swell_oswindow_to_hwnd(cfg->window); + if (!hwnd) return; + int flag=0; + if (cfg->x != hwnd->m_position.left || + cfg->y != hwnd->m_position.top || + !hwnd->m_has_had_position) + { + flag|=1; + hwnd->m_has_had_position = true; + } + if (cfg->width != hwnd->m_position.right-hwnd->m_position.left || + cfg->height != hwnd->m_position.bottom - hwnd->m_position.top) flag|=2; + hwnd->m_position.left = cfg->x; + hwnd->m_position.top = cfg->y; + hwnd->m_position.right = cfg->x + cfg->width; + hwnd->m_position.bottom = cfg->y + cfg->height; + if (flag&1) SendMessage(hwnd,WM_MOVE,0,0); + if (flag&2) SendMessage(hwnd,WM_SIZE,hwnd->m_is_maximized ? SIZE_MAXIMIZED : SIZE_RESTORED,0); + if (!hwnd->m_hashaddestroy && hwnd->m_oswindow) swell_recalcMinMaxInfo(hwnd); +} + +static void OnWindowStateEvent(GdkEventWindowState *evt) +{ + HWND hwnd = swell_oswindow_to_hwnd(evt->window); + if (!hwnd) return; + + if (evt->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) + { + hwnd->m_is_maximized = (evt->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)!=0; + SendMessage(hwnd,WM_SIZE, + (evt->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) ? SIZE_MAXIMIZED : SIZE_RESTORED, 0); + } +} + +#define IS_DEAD_KEY(x) ((x) >= DEF_GKY(dead_grave) && (x) <= DEF_GKY(dead_greek)) + +#include "gtkimcontextsimpleseqs.h" + +static guint find_compose_sequence_compact(const guint16 *state, int state_size) // returns 0 if sequence not found, 1 if partial match, otherwise unicode character +{ + WDL_ASSERT(state_size > 0); + + const int max_seq_len = 5; + if (WDL_NOT_NORMALLY(state_size > max_seq_len)) return 0; + + const int index_size = 30, index_stride = 6; + const guint16 fc = state[0]; + const guint16 *index = gtk_compose_seqs_compact; + + int x; + for (x = 0; x < index_size && index[0] != fc; x ++, index += index_stride); + if (x == index_size) return 0; + if (!--state_size) return 1; + state++; + + x = index[1]; + for (int l = 2; l < index_stride; l ++) + { + const int mv = index[l]; + const int sl = l-1; + while (x < mv) + { + WDL_ASSERT(x >= index_stride * index_size); + WDL_ASSERT(x < (int) (sizeof(gtk_compose_seqs_compact) / sizeof(gtk_compose_seqs_compact[0]))); + // gtk_compose_seqs_compact+x is the remaining sequence + if (state_size <= sl && !memcmp(state,gtk_compose_seqs_compact+x,state_size*sizeof(state[0]))) + { + return state_size == sl ? gtk_compose_seqs_compact[x + sl] : 1; + } + x += l; + } + } + + return 0; +} + +static guint find_compose_sequence_alg(const guint16 *state, int state_size) // returns 0 if sequence not found, 1 if partial match, otherwise unicode character +{ + if (state_size < 2) return state_size==1 && IS_DEAD_KEY(state[0]) ? 1 : 0; + int i; + for (i = 0; i < state_size && IS_DEAD_KEY(state[i]); i++); + if (i != state_size-1) return 0; // must be all dead keys + one non-dead + + gunichar uc = gdk_keyval_to_unicode(state[state_size-1]); + if (!g_unichar_isalpha(uc)) return 0; + + // this is borrowed from GTK+'s impl + GString *input = g_string_sized_new(4 * state_size); + g_string_append_unichar(input, uc); + while (--i >= 0) + { + switch (state[i]) + { +#define CASE(keysym, unicode) case DEF_GKY(dead_##keysym): g_string_append_unichar(input, unicode); break + CASE (grave, 0x0300); + CASE (acute, 0x0301); + CASE (circumflex, 0x0302); + case GDK_KEY_dead_tilde: + if (g_unichar_get_script (uc) == G_UNICODE_SCRIPT_GREEK) + g_string_append_unichar (input, 0x342); /* combining perispomeni */ + else + g_string_append_unichar (input, 0x303); /* combining tilde */ + break; + CASE (macron, 0x0304); + CASE (breve, 0x0306); + CASE (abovedot, 0x0307); + CASE (diaeresis, 0x0308); + CASE (abovering, 0x30A); + CASE (hook, 0x0309); + CASE (doubleacute, 0x030B); + CASE (caron, 0x030C); + CASE (cedilla, 0x0327); + CASE (ogonek, 0x0328); /* Legacy use for dasia, 0x314.*/ + CASE (iota, 0x0345); + CASE (voiced_sound, 0x3099); /* Per Markus Kuhn keysyms.txt file. */ + CASE (semivoiced_sound, 0x309A); /* Per Markus Kuhn keysyms.txt file. */ + CASE (belowdot, 0x0323); + CASE (horn, 0x031B); /* Legacy use for psili, 0x313 (or 0x343). */ + CASE (stroke, 0x335); + CASE (abovecomma, 0x0313); /* Equivalent to psili */ + CASE (abovereversedcomma, 0x0314); /* Equivalent to dasia */ + CASE (doublegrave, 0x30F); + CASE (belowring, 0x325); + CASE (belowmacron, 0x331); + CASE (belowcircumflex, 0x32D); + CASE (belowtilde, 0x330); + CASE (belowbreve, 0x32e); + CASE (belowdiaeresis, 0x324); + CASE (invertedbreve, 0x32f); + CASE (belowcomma, 0x326); +#ifdef GDK_KEY_dead_longsolidusoverlay // requires a particularly recent GTK+3, I guess + CASE (lowline, 0x332); + CASE (aboveverticalline, 0x30D); + CASE (belowverticalline, 0x329); + CASE (longsolidusoverlay, 0x338); +#endif + CASE (a, 0x363); + CASE (A, 0x363); + CASE (e, 0x364); + CASE (E, 0x364); + CASE (i, 0x365); + CASE (I, 0x365); + CASE (o, 0x366); + CASE (O, 0x366); + CASE (u, 0x367); + CASE (U, 0x367); + CASE (small_schwa, 0x1DEA); + CASE (capital_schwa, 0x1DEA); +#undef CASE + default: + g_string_append_unichar (input, gdk_keyval_to_unicode(state[i])); + break; + } + } + char *nfc = g_utf8_normalize(input->str, input->len, G_NORMALIZE_NFC); + + int rv = 0; + if (nfc) wdl_utf8_parsechar(nfc,&rv); + + g_string_free(input, TRUE); + g_free(nfc); + + return rv; +} + +static guint swell_gdkComposeKeys(GdkEventKey *k) // return 0 if not in composition, 1 if mid-composition, >1 if composed +{ + const int state_max = 16; + static guint16 state[state_max], state_pos; + static GdkWindow *lw; + if (k->window != lw) + { + state_pos=0; + lw = k->window; + } + + if (!IS_DEAD_KEY(k->keyval) && !state_pos) return 0; + + static const guint16 mods[] = { + DEF_GKY(Overlay1_Enable), + DEF_GKY(Overlay2_Enable), + DEF_GKY(Shift_L), + DEF_GKY(Shift_R), + DEF_GKY(Control_L), + DEF_GKY(Control_R), + DEF_GKY(Caps_Lock), + DEF_GKY(Shift_Lock), + DEF_GKY(Meta_L), + DEF_GKY(Meta_R), + DEF_GKY(Alt_L), + DEF_GKY(Alt_R), + DEF_GKY(Super_L), + DEF_GKY(Super_R), + DEF_GKY(Hyper_L), + DEF_GKY(Hyper_R), + DEF_GKY(Mode_switch), + DEF_GKY(ISO_Level3_Shift), + DEF_GKY(ISO_Level3_Latch), + DEF_GKY(ISO_Level5_Shift), + DEF_GKY(ISO_Level5_Latch), + }; + + if (!k->keyval) return 1; + for (size_t x = 0; x < sizeof(mods)/sizeof(mods[0]);x++) + if (mods[x] == k->keyval) return 1; + + if (k->type == GDK_KEY_PRESS) + { + if (k->keyval == DEF_GKY(BackSpace) || k->keyval == DEF_GKY(Escape)) + return 1; + + state[state_pos++] = k->keyval; + } + else + { + if (k->keyval == DEF_GKY(Escape)) + state_pos = 0; + else if (k->keyval == DEF_GKY(BackSpace)) + state_pos--; + } + + if (!state_pos) return 1; + + guint rv = find_compose_sequence_compact(state,state_pos); + if (rv == 0) rv = find_compose_sequence_alg(state,state_pos); + if (rv!=1 || WDL_NOT_NORMALLY(state_pos >= state_max)) state_pos = 0; + return rv ? rv : 1; +} + + +static void OnKeyEvent(GdkEventKey *k) +{ + HWND hwnd = swell_oswindow_to_hwnd(k->window); + if (!hwnd) return; + + int modifiers = 0; + if (k->state&GDK_CONTROL_MASK) modifiers|=FCONTROL; + if (k->state&GDK_MOD1_MASK) modifiers|=FALT; + if (k->state&SWELL_WINDOWSKEY_GDK_MASK) modifiers|=FLWIN; + if (k->state&GDK_SHIFT_MASK) modifiers|=FSHIFT; + + UINT msgtype = k->type == GDK_KEY_PRESS ? WM_KEYDOWN : WM_KEYUP; + + bool is_extended = false; + guint kv; + + if ((kv = swell_gdkComposeKeys(k))) + { + if (kv==1) return; + modifiers = 0; + } + else if ((kv = swell_gdkConvertKey(k->keyval, &is_extended))) + { + modifiers |= FVIRTKEY; + + if (modifiers & FSHIFT) + { + if (k->state&GDK_MOD2_MASK) // numlock on + { + // if shift+numpad home/end is sent while numlock is on, that means it should be treated + // as unmodified home/end + if (!is_extended && kv >= VK_PRIOR && kv <= VK_SELECT) + modifiers &= ~FSHIFT; + } + else + { + // if numpadX is sent while numlock is off, then it should be treated as unmodified + if (kv >= VK_NUMPAD0 && kv <= VK_NUMPAD9) + modifiers &= ~FSHIFT; + } + } + } + else + { + kv = k->keyval; + if (kv >= 'a' && kv <= 'z') + { + kv += 'A'-'a'; + swell_is_likely_capslock = (modifiers&FSHIFT)!=0; + modifiers |= FVIRTKEY; + } + else if (kv >= 'A' && kv <= 'Z') + { + swell_is_likely_capslock = (modifiers&FSHIFT)==0; + modifiers |= FVIRTKEY; + } + else if (kv == ' ') + { + kv = VK_SPACE; + } + else if (kv >= '0' && kv <= '9') + { + modifiers |= FVIRTKEY; + } + else + { + if (kv >= DEF_GKY(Shift_L) || + (kv >= DEF_GKY(ISO_Lock) && + kv <= DEF_GKY(ISO_Last_Group_Lock)) + ) + { + if (kv == DEF_GKY(Shift_L) || kv == DEF_GKY(Shift_R)) kv = VK_SHIFT; + else if (kv == DEF_GKY(Control_L) || kv == DEF_GKY(Control_R)) kv = VK_CONTROL; + else if (kv == DEF_GKY(Alt_L) || kv == DEF_GKY(Alt_R)) kv = VK_MENU; + else if (kv == DEF_GKY(Super_L) || kv == DEF_GKY(Super_R)) kv = VK_LWIN; + else return; // unknown modifie key + + msgtype = k->type == GDK_KEY_PRESS ? WM_SYSKEYDOWN : WM_SYSKEYUP; + modifiers|=FVIRTKEY; + } + else if (kv > 255) + { + guint v = gdk_keyval_to_unicode(kv); + if (v) kv=v; + } + else + { + // treat as ASCII, clear shift flag (?) + modifiers &= ~FSHIFT; + } + } + } + + HWND foc = GetFocusIncludeMenus(); + if (foc && IsChild(hwnd,foc)) hwnd=foc; + else if (foc && foc->m_oswindow && !(foc->m_style&WS_CAPTION)) hwnd=foc; // for menus, event sent to other window due to gdk_window_set_override_redirect() + + if (is_extended) modifiers |= 1<<24; + + MSG msg = { hwnd, msgtype, kv, modifiers, }; + INT_PTR extra_flags = 0; + if (DialogBoxIsActive()) extra_flags |= 1; + if (SWELLAppMain(SWELLAPP_PROCESSMESSAGE,(INT_PTR)&msg,extra_flags)<=0) + SendMessage(hwnd, msg.message, kv, modifiers); +} + +static HWND getMouseTarget(SWELL_OSWINDOW osw, POINT p, const HWND *hwnd_has_osw) +{ + HWND hwnd = GetCapture(); + if (hwnd) return hwnd; + hwnd = hwnd_has_osw ? *hwnd_has_osw : swell_oswindow_to_hwnd(osw); + if (!hwnd || swell_window_wants_all_input() == hwnd) return hwnd; + return ChildWindowFromPoint(hwnd,p); +} + +static void OnMotionEvent(GdkEventMotion *m) +{ + swell_lastMessagePos = MAKELONG(((int)m->x_root&0xffff),((int)m->y_root&0xffff)); + POINT p={(int)m->x, (int)m->y}; + HWND hwnd = getMouseTarget(m->window,p,NULL); + + if (hwnd) + { + POINT p2={(int)m->x_root, (int)m->y_root}; + ScreenToClient(hwnd, &p2); + if (hwnd) hwnd->Retain(); + SWELL_SendMouseMessage(hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(p2.x, p2.y)); + if (hwnd) hwnd->Release(); + } +} + +static void OnScrollEvent(GdkEventScroll *b) +{ + swell_lastMessagePos = MAKELONG(((int)b->x_root&0xffff),((int)b->y_root&0xffff)); + POINT p={(int)b->x, (int)b->y}; + + HWND hwnd = getMouseTarget(b->window,p,NULL); + if (hwnd) + { + POINT p2={(int)b->x_root, (int)b->y_root}; + // p2 is screen coordinates for WM_MOUSEWHEEL + + int msg=(b->direction == GDK_SCROLL_UP || b->direction == GDK_SCROLL_DOWN) ? WM_MOUSEWHEEL : + (b->direction == GDK_SCROLL_LEFT || b->direction == GDK_SCROLL_RIGHT) ? WM_MOUSEHWHEEL : 0; + + if (msg) + { + int v = (b->direction == GDK_SCROLL_UP || b->direction == GDK_SCROLL_LEFT) ? 120 : -120; + + if (hwnd) hwnd->Retain(); + SWELL_SendMouseMessage(hwnd, msg, (v<<16), MAKELPARAM(p2.x, p2.y)); + if (hwnd) hwnd->Release(); + } + } +} + +static DWORD s_last_focus_change_time; + +static void OnButtonEvent(GdkEventButton *b) +{ + HWND hwnd = swell_oswindow_to_hwnd(b->window); + if (!hwnd) return; + swell_lastMessagePos = MAKELONG(((int)b->x_root&0xffff),((int)b->y_root&0xffff)); + POINT p={(int)b->x, (int)b->y}; + HWND hwnd2 = getMouseTarget(b->window,p,&hwnd); + + POINT p2={(int)b->x_root, (int)b->y_root}; + ScreenToClient(hwnd2, &p2); + + int msg=WM_LBUTTONDOWN; + if (b->button==2) msg=WM_MBUTTONDOWN; + else if (b->button==3) msg=WM_RBUTTONDOWN; + + if (hwnd2) hwnd2->Retain(); + + if (b->type == GDK_BUTTON_PRESS) + { + HWND oldFocus=GetFocus(); + if (!oldFocus || + oldFocus != hwnd2 || + (GetTickCount()-s_last_focus_change_time) < 500) + { + if (IsWindowEnabled(hwnd2)) + SendMessage(hwnd2,WM_MOUSEACTIVATE,0,0); + } + } + + if (hwnd && hwnd->m_oswindow && SWELL_focused_oswindow != hwnd->m_oswindow && + (b->type != GDK_BUTTON_RELEASE || PopupMenuIsActive())) + { + // 'b->type != GDK_BUTTON_RELEASE ||' might not be necessary, the only time this + // appears to matter is when popup menus are active (focus events aren't sent + // probably due to the override redirect menu windows) + SWELL_focused_oswindow = hwnd->m_oswindow; + update_menubar_activations(); + } + + + // for doubleclicks, GDK actually seems to send: + // GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE, + // GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE + // win32 expects: + // WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, WM_LBUTTONUP + // what we send: + // WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDOWN, WM_LBUTTONUP, + // WM_LBUTTONDBLCLK, WM_LBUTTONUP + // there is an extra down/up pair, but it should behave fine with most code + // (one hopes) + + if(b->type == GDK_BUTTON_RELEASE) + { + msg++; // convert WM_xBUTTONDOWN to WM_xBUTTONUP + } + else if(b->type == GDK_2BUTTON_PRESS) + { + msg++; // convert WM_xBUTTONDOWN to WM_xBUTTONUP + SWELL_SendMouseMessage(hwnd2, msg, 0, MAKELPARAM(p2.x, p2.y)); + + // if capture was released by WM_LBUTTONUP, allow the DBLCLICK to go to the correct window + HWND hwnd3 = getMouseTarget(b->window,p,&hwnd); + if (hwnd3 != hwnd2) + { + if (hwnd2) hwnd2->Release(); + hwnd2 = hwnd3; + if (hwnd2) hwnd2->Retain(); + p2.x = (int)b->x_root; + p2.y = (int)b->y_root; + ScreenToClient(hwnd2, &p2); + } + + msg++; // convert WM_xBUTTONUP to WM_xBUTTONDBLCLK + } + + SWELL_SendMouseMessage(hwnd2, msg, 0, MAKELPARAM(p2.x, p2.y)); + if (hwnd2) hwnd2->Release(); +} + + +static HANDLE urilistToDropFiles(const POINT *pt, const guchar *gptr, gint sz) +{ + HANDLE gobj=GlobalAlloc(0,sz+sizeof(DROPFILES)); + if (!gobj) return NULL; + + DROPFILES *df=(DROPFILES*)gobj; + df->pFiles = sizeof(DROPFILES); + if (pt) df->pt = *pt; + else df->pt.x = df->pt.y = 0; + + df->fNC=FALSE; + df->fWide=FALSE; + guchar *pout = (guchar *)(df+1); + const guchar *rd = gptr; + const guchar *rd_end = rd + sz; + for (;;) + { + while (rd < rd_end && *rd && isspace(*rd)) rd++; + if (rd >= rd_end) break; + + if (rd+7 < rd_end && !strnicmp((const char *)rd,"file://",7)) + { + rd += 7; + int c=0; + while (rd < rd_end && *rd && !isspace(*rd)) + { + int v1,v2; + if (*rd == '%' && rd+2 < rd_end && (v1=hex_parse(rd[1]))>=0 && (v2=hex_parse(rd[2]))>=0) + { + *pout++ = (v1<<4) | v2; + rd+=3; + } + else + { + *pout++ = *rd++; + } + c++; + } + if (c) *pout++=0; + } + else + { + while (rd < rd_end && *rd && !isspace(*rd)) rd++; + } + } + *pout++=0; + *pout++=0; + + return gobj; +} + +static void OnSelectionNotifyEvent(GdkEventSelection *b) +{ + HWND hwnd = swell_oswindow_to_hwnd(b->window); + if (!hwnd) return; + + if (hwnd == s_ddrop_hwnd && b->target == urilistatom()) + { + POINT p = s_ddrop_pt; + HWND cw=hwnd; + RECT r; + GetWindowContentViewRect(hwnd,&r); + if (PtInRect(&r,p)) + { + p.x -= r.left; + p.y -= r.top; + cw = ChildWindowFromPoint(hwnd,p); + } + if (!cw) cw=hwnd; + + guchar *gptr=NULL; + GdkAtom fmt; + gint unitsz=0; + gint sz=gdk_selection_property_get(b->window,&gptr,&fmt,&unitsz); + + if (sz>0 && gptr) + { + POINT pt2 = s_ddrop_pt; + ScreenToClient(cw,&pt2); + HANDLE gobj = urilistToDropFiles(&pt2,gptr,sz); + if (gobj) + { + SendMessage(cw,WM_DROPFILES,(WPARAM)gobj,0); + GlobalFree(gobj); + } + } + + if (gptr) g_free(gptr); + s_ddrop_hwnd=NULL; + return; + } + + s_ddrop_hwnd=NULL; + + if (s_clipboard_getstate) { GlobalFree(s_clipboard_getstate); s_clipboard_getstate=NULL; } + guchar *gptr=NULL; + GdkAtom fmt; + gint unitsz=0; + gint sz=gdk_selection_property_get(b->window,&gptr,&fmt,&unitsz); + if (sz>0 && gptr && (unitsz == 8 || unitsz == 16 || unitsz == 32)) + { + WDL_FastString str; + guchar *ptr = gptr; + if (fmt == urilistatom()) + { + s_clipboard_getstate = urilistToDropFiles(NULL,gptr,sz); + if (s_clipboard_getstate) + s_clipboard_getstate_fmt = fmt; + } + else + { + if (fmt == GDK_TARGET_STRING || fmt == utf8atom()) + { + int lastc=0; + while (sz-->0) + { + int c; + if (unitsz==32) { c = *(unsigned int *)ptr; ptr+=4; } + else if (unitsz==16) { c = *(unsigned short *)ptr; ptr+=2; } + else c = *ptr++; + + if (!c) break; + + if (c == '\n' && lastc != '\r') str.Append("\r",1); + + char bv[8]; + if (fmt != GDK_TARGET_STRING) + { + bv[0] = (char) ((unsigned char)c); + str.Append(bv,1); + } + else + { + WDL_MakeUTFChar(bv,c,sizeof(bv)); + str.Append(bv); + } + + lastc=c; + } + ptr = (guchar*)str.Get(); + sz=str.GetLength()+1; + } + else if (unitsz>8) sz *= (unitsz/8); + + s_clipboard_getstate = GlobalAlloc(0,sz); + if (s_clipboard_getstate) + { + memcpy(s_clipboard_getstate,ptr,sz); + s_clipboard_getstate_fmt = fmt; + } + } + } + if (gptr) g_free(gptr); +} + +static void OnDropStartEvent(GdkEventDND *e) +{ + HWND hwnd = swell_oswindow_to_hwnd(e->window); + if (!hwnd) return; + + GdkDragContext *ctx = e->context; + if (ctx) + { + POINT p = { (int)e->x_root, (int)e->y_root }; + s_ddrop_hwnd = hwnd; + s_ddrop_pt = p; + + GdkAtom srca = gdk_drag_get_selection(ctx); + gdk_selection_convert(e->window,srca,urilistatom(),e->time); + gdk_drop_finish(ctx,TRUE,e->time); + } +} + +static bool is_our_oswindow(GdkWindow *w) +{ + while (w) + { + HWND hwnd = swell_oswindow_to_hwnd(w); + if (hwnd) return true; + w = gdk_window_get_effective_parent(w); + } + return false; + +} + +static void deactivateTimer(HWND hwnd, UINT uMsg, UINT_PTR tm, DWORD dwt) +{ + KillTimer(NULL,s_deactivate_timer); + s_deactivate_timer=0; + if (swell_app_is_inactive) return; + GdkWindow *window = gdk_screen_get_active_window(gdk_screen_get_default()); + if (!is_our_oswindow(window)) + on_deactivate(); + + update_menubar_activations(); +} + +extern SWELL_OSWINDOW swell_ignore_focus_oswindow; +extern DWORD swell_ignore_focus_oswindow_until; + +static void swell_gdkEventHandler(GdkEvent *evt, gpointer data) +{ + GdkEvent *oldEvt = s_cur_evt; + s_cur_evt = evt; + + switch (evt->type) + { + case GDK_FOCUS_CHANGE: + { + bool do_menus = false; + GdkEventFocus *fc = (GdkEventFocus *)evt; + if (s_deactivate_timer) + { + KillTimer(NULL,s_deactivate_timer); + s_deactivate_timer=0; + do_menus = true; + } + if (fc->in && is_our_oswindow(fc->window)) + { + s_last_focus_change_time = GetTickCount(); + swell_on_toplevel_raise(fc->window); + if (swell_ignore_focus_oswindow != fc->window || + (GetTickCount()-swell_ignore_focus_oswindow_until) < 0x10000000) + { + SWELL_focused_oswindow = fc->window; + update_menubar_activations(); + } + if (swell_app_is_inactive) + { + on_activate(0); + } + } + else if (!swell_app_is_inactive) + { + s_deactivate_timer = SetTimer(NULL,0,200,deactivateTimer); + if (gdk_options & OPTION_ALLOW_MAYBE_INACTIVE) + do_menus = true; + } + if (do_menus) + update_menubar_activations(); + } + break; + case GDK_SELECTION_REQUEST: + OnSelectionRequestEvent((GdkEventSelection *)evt); + break; + + case GDK_DELETE: + { + HWND hwnd = swell_oswindow_to_hwnd(((GdkEventAny*)evt)->window); + if (hwnd && IsWindowEnabled(hwnd) && + !DestroyPopupMenus() && // ignore if a menu is open, instead just close the menu + !SendMessage(hwnd,WM_CLOSE,0,0)) + SendMessage(hwnd,WM_COMMAND,IDCANCEL,0); + } + break; + case GDK_EXPOSE: // paint! GdkEventExpose... + OnExposeEvent((GdkEventExpose *)evt); + break; + case GDK_CONFIGURE: // size/move, GdkEventConfigure + OnConfigureEvent((GdkEventConfigure*)evt); + break; + case GDK_WINDOW_STATE: /// GdkEventWindowState for min/max + OnWindowStateEvent((GdkEventWindowState*)evt); + break; + case GDK_GRAB_BROKEN: + if (swell_oswindow_to_hwnd(((GdkEventAny*)evt)->window)) + { + if (swell_captured_window) + { + SendMessage(swell_captured_window,WM_CAPTURECHANGED,0,0); + swell_captured_window=0; + } + } + break; + case GDK_KEY_PRESS: + case GDK_KEY_RELEASE: + swell_dlg_destroyspare(); + OnKeyEvent((GdkEventKey *)evt); + break; +#ifdef GDK_AVAILABLE_IN_3_4 + case GDK_TOUCH_BEGIN: + case GDK_TOUCH_UPDATE: + case GDK_TOUCH_END: + case GDK_TOUCH_CANCEL: + { + GdkEventTouch *e = (GdkEventTouch *)evt; + static guint32 touchptr_lasttime; + bool doubletap = false; + if (evt->type == GDK_TOUCH_BEGIN && !g_swell_touchptr) + { + DWORD now = e->time; + doubletap = touchptr_lasttime && + now >= touchptr_lasttime && + now < touchptr_lasttime+350; + touchptr_lasttime = now; + g_swell_touchptr = e->sequence; + g_swell_touchptr_wnd = e->window; + } + + if (!e->sequence || e->sequence != g_swell_touchptr) + { + touchptr_lasttime=0; + break; + } + + if (e->type == GDK_TOUCH_UPDATE) + { + GdkEventMotion m; + memset(&m,0,sizeof(m)); + m.type = GDK_MOTION_NOTIFY; + m.window = e->window; + m.time = e->time; + m.x = e->x; + m.y = e->y; + m.axes = e->axes; + m.state = e->state; + m.device = e->device; + m.x_root = e->x_root; + m.y_root = e->y_root; + OnMotionEvent(&m); + } + else + { + GdkEventButton but; + memset(&but,0,sizeof(but)); + if (e->type == GDK_TOUCH_BEGIN) + { + but.type = doubletap ? GDK_2BUTTON_PRESS:GDK_BUTTON_PRESS; + } + else + { + but.type = GDK_BUTTON_RELEASE; + g_swell_touchptr = NULL; + } + but.window = e->window; + but.time = e->time; + but.x = e->x; + but.y = e->y; + but.axes = e->axes; + but.state = e->state; + but.device = e->device; + but.button = 1; + but.x_root = e->x_root; + but.y_root = e->y_root; + swell_dlg_destroyspare(); + OnButtonEvent(&but); + } + } + break; +#endif + case GDK_MOTION_NOTIFY: + gdk_event_request_motions((GdkEventMotion *)evt); + OnMotionEvent((GdkEventMotion *)evt); + break; + case GDK_SCROLL: + OnScrollEvent((GdkEventScroll*)evt); + break; + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + swell_dlg_destroyspare(); + OnButtonEvent((GdkEventButton*)evt); + break; + case GDK_SELECTION_NOTIFY: + OnSelectionNotifyEvent((GdkEventSelection *)evt); + break; + case GDK_DRAG_ENTER: + case GDK_DRAG_MOTION: + if (swell_oswindow_to_hwnd(((GdkEventAny*)evt)->window)) + { + GdkEventDND *e = (GdkEventDND *)evt; + if (e->context) + { + POINT pt = { (int)e->x_root, (int)e->y_root }; + gdk_drag_status(e->context,GDK_ACTION_COPY,e->time); + + if (e->type == GDK_DRAG_ENTER) + { + if (SWELL_DDrop_onDragEnter) + { + const char *fn = swell_dragsrc_fn ? swell_dragsrc_fn : "/tmp/<<<<>>>>>>"; + HGLOBAL h=GlobalAlloc(0,sizeof(DROPFILES) + strlen(fn) + 2); + if (WDL_NORMALLY(h)) + { + DROPFILES *hdr = (DROPFILES *)GlobalLock(h); + if (WDL_NORMALLY(hdr)) + { + memset(hdr,0,sizeof(*hdr)); + hdr->pFiles = sizeof(DROPFILES); + hdr->pt = pt; + char *ep = ((char *)hdr) + sizeof(*hdr); + memcpy(ep, fn, strlen(fn)+1); + ep[strlen(fn)+1] = 0; + GlobalUnlock(h); + } + SWELL_DDrop_onDragEnter(h,pt); + GlobalFree(h); + } + } + } + else if (SWELL_DDrop_onDragOver) + { + SWELL_DDrop_onDragOver(pt); + } + } + } + break; + case GDK_DRAG_LEAVE: + if (SWELL_DDrop_onDragLeave) SWELL_DDrop_onDragLeave(); + break; + case GDK_DRAG_STATUS: + break; + case GDK_DROP_FINISHED: + if (SWELL_DDrop_onDragLeave) SWELL_DDrop_onDragLeave(); + break; + case GDK_DROP_START: + OnDropStartEvent((GdkEventDND *)evt); + if (SWELL_DDrop_onDragLeave) SWELL_DDrop_onDragLeave(); + break; + + default: + //printf("msg: %d\n",evt->type); + break; + } +#ifdef SWELL_SUPPORT_GTK + gtk_main_do_event(evt); +#endif + s_cur_evt = oldEvt; +} + +void SWELL_RunEvents() +{ + if (SWELL_gdk_active>0) + { +#if 0 && defined(SWELL_SUPPORT_GTK) + // does not seem to be necessary + while (gtk_events_pending()) + gtk_main_iteration(); +#else + +#if SWELL_TARGET_GDK == 2 + gdk_window_process_all_updates(); +#endif + + GMainContext *ctx=g_main_context_default(); + while (g_main_context_iteration(ctx,FALSE)) + { + GdkEvent *evt; + while (gdk_events_pending() && (evt = gdk_event_get())) + { + swell_gdkEventHandler(evt,(gpointer)1); + gdk_event_free(evt); + } + } +#endif + } +} + +bool swell_gdk_set_fullscreen(HWND hwnd, int fs) // fs=2 if window might have owned children +{ + if (!hwnd) return false; + if (fs==2 && (gdk_options & OPTION_FULLSCREEN_FOR_OWNER_WINDOWS)!=OPTION_FULLSCREEN_FOR_OWNER_WINDOWS) return false; + + const int stylemask = WS_BORDER|WS_CAPTION|WS_THICKFRAME; + if (!hwnd->m_oswindow || !(gdk_options & OPTION_FULLSCREEN_DYNAMIC)) + { + hwnd->m_oswindow_fullscreen = fs ? WS_VISIBLE | (hwnd->m_style & stylemask) : 0; + return true; // request caller hide/show + } + + // OPTION_FULLSCREEN_DYNAMIC and window exists, modify state + if (fs) + { + hwnd->m_oswindow_fullscreen = WS_VISIBLE | (hwnd->m_style & stylemask); + hwnd->m_style &= ~stylemask; + gdk_window_fullscreen(hwnd->m_oswindow); + } + else + { + hwnd->m_style |= (hwnd->m_oswindow_fullscreen & stylemask); + hwnd->m_oswindow_fullscreen = 0; + gdk_window_unfullscreen(hwnd->m_oswindow); + } + return false; +} + +void swell_oswindow_update_style(HWND hwnd, LONG oldstyle) +{ + const LONG val = hwnd->m_style, ret = oldstyle; + if (hwnd->m_oswindow && ((ret^val)& WS_CAPTION)) + { + swell_hide_owned_windows_transient(hwnd); + + gdk_window_hide(hwnd->m_oswindow); + if (val & WS_CAPTION) + { + if (val & WS_THICKFRAME) + gdk_window_set_decorations(hwnd->m_oswindow,(GdkWMDecoration) (GDK_DECOR_ALL | GDK_DECOR_MENU)); + else + gdk_window_set_decorations(hwnd->m_oswindow,(GdkWMDecoration) (GDK_DECOR_BORDER|GDK_DECOR_TITLE|GDK_DECOR_MINIMIZE)); + } + else + { + gdk_window_set_decorations(hwnd->m_oswindow,(GdkWMDecoration) 0); + } + hwnd->m_oswindow_private |= PRIVATE_NEEDSHOW; + } +} + +void swell_oswindow_update_enable(HWND hwnd) +{ + if (hwnd->m_oswindow && !swell_app_is_inactive) + gdk_window_set_accept_focus(hwnd->m_oswindow,hwnd->m_enabled); +} + +int SWELL_SetWindowLevel(HWND hwnd, int newlevel) +{ + int rv=0; + if (hwnd) + { + rv = hwnd->m_israised ? 1 : 0; + hwnd->m_israised = newlevel>0; + if (hwnd->m_oswindow) gdk_window_set_keep_above(hwnd->m_oswindow,newlevel>0 && !swell_app_is_inactive); + } + return rv; +} + +void SWELL_GetViewPort(RECT *r, const RECT *sourcerect, bool wantWork) +{ + r->left=r->top=0; + r->right=1024; + r->bottom=768; + if (!swell_initwindowsys()) return; + GdkScreen *defscr = gdk_screen_get_default(); + if (!defscr) return; + const gint n = gdk_screen_get_n_monitors(defscr); + if (n < 1) return; + + const gint prim = gdk_screen_get_primary_monitor(defscr); + double best_score = -1e20; + RECT sr; + if (sourcerect) sr = *sourcerect; + + for (gint idx = 0; idx < n; idx ++) + { + GdkRectangle rc={0,0,1024,1024}; + if (!sourcerect && prim>0) idx = prim; + +#if SWELL_TARGET_GDK != 2 + if (wantWork) + gdk_screen_get_monitor_workarea(defscr,idx,&rc); + else +#endif + gdk_screen_get_monitor_geometry(defscr,idx,&rc); + + RECT tmp; + tmp.left=rc.x; + tmp.top = rc.y; + tmp.right=rc.x+rc.width; + tmp.bottom=rc.y+rc.height; + if (!sourcerect || n < 2) + { + *r = tmp; + break; + } + + double score; + RECT res; + if (IntersectRect(&res, &tmp, &sr)) + { + score = wdl_abs((res.right-res.left) * (res.bottom-res.top)); + } + else + { + int dx = 0, dy = 0; + if (tmp.left > sr.right) dx = tmp.left - sr.right; + else if (tmp.right < sr.left) dx = sr.left - tmp.right; + if (tmp.bottom < sr.top) dy = tmp.bottom - sr.top; + else if (tmp.top > sr.bottom) dy = tmp.top - sr.bottom; + score = - (dx*dx + dy*dy); + } + + if (!idx || score > best_score) + { + best_score = score; + *r = tmp; + } + } +} + + +bool GetWindowRect(HWND hwnd, RECT *r) +{ + if (!hwnd) return false; + if (hwnd->m_oswindow) + { +#ifdef SWELL_GDK_IMPROVE_WINDOWRECT + GdkRectangle gr; + gdk_window_get_frame_extents(hwnd->m_oswindow,&gr); + + r->left=gr.x; + r->top=gr.y; + r->right=gr.x + gr.width; + r->bottom = gr.y + gr.height; +#else + // this is wrong (returns client rect in screen coordinates), but gdk_window_get_frame_extents() doesn't seem to work + gint x=hwnd->m_position.left,y=hwnd->m_position.top; + gdk_window_get_root_origin(hwnd->m_oswindow,&x,&y); + r->left=x; + r->top=y; + r->right=x + hwnd->m_position.right - hwnd->m_position.left; + r->bottom = y + hwnd->m_position.bottom - hwnd->m_position.top; +#endif + + return true; + } + + r->left=r->top=0; + ClientToScreen(hwnd,(LPPOINT)r); + r->right = r->left + hwnd->m_position.right - hwnd->m_position.left; + r->bottom = r->top + hwnd->m_position.bottom - hwnd->m_position.top; + return true; +} + +void swell_oswindow_begin_resize(SWELL_OSWINDOW wnd) +{ + // make sure window is resizable (hints will be re-set on upcoming CONFIGURE event) + gdk_window_set_geometry_hints(wnd,NULL,(GdkWindowHints) 0); +} + +void swell_oswindow_resize(SWELL_OSWINDOW wnd, int reposflag, RECT f) +{ +#ifdef SWELL_GDK_IMPROVE_WINDOWRECT + if (reposflag & 2) + { + // increase size to include titlebars etc + GdkRectangle gr; + gdk_window_get_frame_extents(wnd,&gr); + gint cw=gr.width, ch=gr.height; + gdk_window_get_geometry(wnd,NULL,NULL,&cw,&ch); + // when it matters, this seems to always make gr.height=ch, which is pointless + f.right -= gr.width - cw; + f.bottom -= gr.height - ch; + } +#endif + if ((reposflag&3)==3) gdk_window_move_resize(wnd,f.left,f.top,f.right-f.left,f.bottom-f.top); + else if (reposflag&2) gdk_window_resize(wnd,f.right-f.left,f.bottom-f.top); + else if (reposflag&1) gdk_window_move(wnd,f.left,f.top); +} + +void swell_oswindow_postresize(HWND hwnd, RECT f) +{ + if (hwnd->m_oswindow && (hwnd->m_oswindow_private&PRIVATE_NEEDSHOW) && !hwnd->m_oswindow_fullscreen) + { + gdk_window_show(hwnd->m_oswindow); + if (hwnd->m_style & WS_CAPTION) gdk_window_unmaximize(hwnd->m_oswindow); // fixes Kwin + swell_oswindow_resize(hwnd->m_oswindow,3,f); // fixes xfce + hwnd->m_oswindow_private &= ~PRIVATE_NEEDSHOW; + + swell_set_owned_windows_transient(hwnd,false); + } + if (hwnd->m_oswindow && !(hwnd->m_style&WS_THICKFRAME)) + { + // kwin requires this for non-sizeable windows (doing it in the configure event is too late, apparently) + GdkGeometry h; + memset(&h,0,sizeof(h)); + h.max_width = h.min_width = f.right - f.left; + h.max_height = h.min_height = f.bottom - f.top; + gdk_window_set_geometry_hints(hwnd->m_oswindow,&h,(GdkWindowHints) ((hwnd->m_has_had_position ? GDK_HINT_POS : 0) | GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE)); + } +} + +void UpdateWindow(HWND hwnd) +{ +#if SWELL_TARGET_GDK == 2 + if (hwnd) + { + while (hwnd && !hwnd->m_oswindow) hwnd=hwnd->m_parent; + if (hwnd && hwnd->m_oswindow) gdk_window_process_updates(hwnd->m_oswindow,true); + } +#endif +} + +void swell_oswindow_invalidate(HWND hwnd, const RECT *r) +{ + GdkRectangle gdkr; + if (r) + { + gdkr.x = r->left; + gdkr.y = r->top; + gdkr.width = r->right-r->left; + gdkr.height = r->bottom-r->top; + } + + gdk_window_invalidate_rect(hwnd->m_oswindow,r ? &gdkr : NULL,true); +} + + + +bool OpenClipboard(HWND hwndDlg) +{ + RegisterClipboardFormat(NULL); + s_clip_hwnd=hwndDlg ? hwndDlg : SWELL_topwindows; + if (s_clipboard_getstate) + { + GlobalFree(s_clipboard_getstate); + s_clipboard_getstate = NULL; + } + s_clipboard_getstate_fmt = NULL; + + return true; +} + +static HANDLE req_clipboard(GdkAtom type) +{ + if (s_clipboard_getstate_fmt == type) return s_clipboard_getstate; + + HWND h = s_clip_hwnd; + while (h && !h->m_oswindow) h = h->m_parent; + + if (h && SWELL_gdk_active > 0) + { + if (s_clipboard_getstate) + { + GlobalFree(s_clipboard_getstate); + s_clipboard_getstate=NULL; + } + gdk_selection_convert(h->m_oswindow,GDK_SELECTION_CLIPBOARD,type,GDK_CURRENT_TIME); + + GMainContext *ctx=g_main_context_default(); + DWORD startt = GetTickCount(); + for (;;) + { + while (!s_clipboard_getstate && g_main_context_iteration(ctx,FALSE)) + { + GdkEvent *evt; + while (!s_clipboard_getstate && gdk_events_pending() && (evt = gdk_event_get())) + { + if (evt->type == GDK_SELECTION_NOTIFY || evt->type == GDK_SELECTION_REQUEST) + swell_gdkEventHandler(evt,(gpointer)1); + gdk_event_free(evt); + } + } + + if (s_clipboard_getstate) + { + if (s_clipboard_getstate_fmt == type) return s_clipboard_getstate; + return NULL; + } + + if ((GetTickCount()-startt) > 500) break; + Sleep(10); + } + } + return NULL; +} + +void CloseClipboard() +{ + s_clip_hwnd=NULL; +} + +UINT EnumClipboardFormats(UINT lastfmt) +{ + if (lastfmt == CF_TEXT) return CF_HDROP; + if (!lastfmt) + { + // checking this causes issues (reentrancy, I suppose?) + //if (req_clipboard(utf8atom())) + return CF_TEXT; + } + if (lastfmt == CF_HDROP) lastfmt = 0; + + int x=0; + for (;;) + { + int fmt=0; + if (!m_clip_recs.Enumerate(x++,&fmt)) return 0; + if (lastfmt == 0) return fmt; + + if ((UINT)fmt == lastfmt) return m_clip_recs.Enumerate(x++,&fmt) ? fmt : 0; + } +} + +HANDLE GetClipboardData(UINT type) +{ + RegisterClipboardFormat(NULL); + if (type == CF_TEXT) + return req_clipboard(utf8atom()); + + if (type == CF_HDROP) + return req_clipboard(urilistatom()); + + return m_clip_recs.Get(type); +} + + +void EmptyClipboard() +{ + m_clip_recs.DeleteAll(); +} + +void SetClipboardData(UINT type, HANDLE h) +{ + RegisterClipboardFormat(NULL); + if (type == CF_TEXT || type == CF_HDROP) + { + if (s_clipboard_setstate) { GlobalFree(s_clipboard_setstate); s_clipboard_setstate=NULL; } + s_clipboard_setstate_fmt=NULL; + static GdkWindow *w; + if (!w) + { + GdkWindowAttr attr={0,}; + attr.title = (char *)"swell clipboard"; + attr.event_mask = GDK_ALL_EVENTS_MASK; + attr.wclass = GDK_INPUT_ONLY; + attr.window_type = GDK_WINDOW_TOPLEVEL; + w = gdk_window_new(NULL,&attr,0); + } + if (w) + { + s_clipboard_setstate_fmt = type == CF_HDROP ? urilistatom() : utf8atom(); + s_clipboard_setstate = h; + gdk_selection_owner_set(w,GDK_SELECTION_CLIPBOARD,GDK_CURRENT_TIME,TRUE); + } + return; + } + if (h) m_clip_recs.Insert(type,h); + else m_clip_recs.Delete(type); +} + +UINT RegisterClipboardFormat(const char *desc) +{ + if (!m_clip_curfmts.GetSize()) + { + m_clip_curfmts.Add(strdup("SWELL__CF_TEXT")); + m_clip_curfmts.Add(strdup("SWELL__CF_HDROP")); + } + + if (!desc || !*desc) return 0; + int x; + const int n = m_clip_curfmts.GetSize(); + for(x=0;xx=0; + pt->y=0; + if (SWELL_gdk_active>0) + { +//#if SWELL_TARGET_GDK == 3 +// GdkDevice *dev=NULL; +// if (s_cur_evt) dev = gdk_event_get_device(s_cur_evt); +// if (!dev) dev = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_display_get_default())); +// if (dev) gdk_device_get_position(dev,NULL,&pt->x,&pt->y); +//#else + gdk_display_get_pointer(gdk_display_get_default(),NULL,&pt->x,&pt->y,NULL); +//#endif + } +} + + +WORD GetAsyncKeyState(int key) +{ + if (SWELL_gdk_active>0) + { + GdkModifierType mod=(GdkModifierType)0; + HWND h = GetFocus(); + while (h && !h->m_oswindow) h = h->m_parent; +//#if SWELL_TARGET_GDK == 3 +// GdkDevice *dev=NULL; +// if (s_cur_evt) dev = gdk_event_get_device(s_cur_evt); +// if (!dev) dev = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_display_get_default())); +// if (dev) gdk_window_get_device_position(h? h->m_oswindow : gdk_get_default_root_window(),dev, NULL, NULL,&mod); +//#else + gdk_window_get_pointer(h? h->m_oswindow : gdk_get_default_root_window(),NULL,NULL,&mod); +//#endif + + if (key == VK_LBUTTON) return ((mod&GDK_BUTTON1_MASK)||g_swell_touchptr)?0x8000:0; + if (key == VK_MBUTTON) return (mod&GDK_BUTTON2_MASK)?0x8000:0; + if (key == VK_RBUTTON) return (mod&GDK_BUTTON3_MASK)?0x8000:0; + + if (key == VK_CONTROL) return (mod&GDK_CONTROL_MASK)?0x8000:0; + if (key == VK_MENU) return (mod&GDK_MOD1_MASK)?0x8000:0; + if (key == VK_SHIFT) return (mod&GDK_SHIFT_MASK)?0x8000:0; + if (key == VK_LWIN) return (mod&SWELL_WINDOWSKEY_GDK_MASK)?0x8000:0; + } + return 0; +} + +DWORD GetMessagePos() +{ + return swell_lastMessagePos; +} + +struct bridgeState { + bridgeState(bool needrep, GdkWindow *_w, Window _nw, Display *_disp, GdkWindow *_curpar, HWND _hwnd_child); + ~bridgeState(); + + GdkWindow *w; + Window native_w; + Display *native_disp; + GdkWindow *cur_parent; + HWND hwnd_child; + + bool lastvis; + bool need_reparent; + RECT lastrect; + + GLXContext gl_ctx; +}; + +static bridgeState *s_last_gl_ctx; + +static WDL_PtrList filter_windows; +bridgeState::~bridgeState() +{ + if (gl_ctx) + { + if (s_last_gl_ctx == this) + { + glXMakeCurrent(native_disp,None, NULL); + s_last_gl_ctx = NULL; + } + + glXDestroyContext(native_disp,gl_ctx); + gl_ctx = NULL; + } + filter_windows.DeletePtr(this); + if (w) + { + if (!need_reparent) + { + // if this window is a child of another window, it will get destroyed by the hierarchy unless it is fully + // released (we could do g_object_unref() again here, but reparenting feels safer in this context) + gdk_window_reparent(w,NULL,0,0); + } + g_object_unref(G_OBJECT(w)); + XDestroyWindow(native_disp,native_w); + } +} +bridgeState::bridgeState(bool needrep, GdkWindow *_w, Window _nw, Display *_disp, GdkWindow *_curpar, HWND _hwnd_child) +{ + hwnd_child = _hwnd_child; + gl_ctx = NULL; + w=_w; + native_w=_nw; + native_disp=_disp; + lastvis=false; + need_reparent=needrep; + cur_parent = _curpar; + memset(&lastrect,0,sizeof(lastrect)); + filter_windows.Add(this); +} + +static LRESULT xbridgeProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_DESTROY: + if (hwnd && hwnd->m_private_data) + { + bridgeState *bs = (bridgeState*)hwnd->m_private_data; + hwnd->m_private_data = 0; + delete bs; + } + break; + case WM_TIMER: + + if (wParam == 1010) + { + // callers can SetTimer(hwnd_container,1010,X,NULL) and have the child X window resized to fit the parent + // they are only resized once, but it is deferred by timer until the window is actually there. + // (if the window was created by another connection to the X server than ours, the window might not yet + // be valid on the X server). + bridgeState *bs = (bridgeState*)hwnd->m_private_data; + + RECT r; + GetClientRect(hwnd,&r); + if (r.right>0 && r.bottom>0 && bs) + { + Window root, par, *list=NULL; + unsigned int nlist=0; + // if a plug-in created a window on a separate X11 connection, it might not be valid yet. + if (XQueryTree(bs->native_disp,bs->native_w,&root,&par,&list, &nlist)) + { + if (!list || !nlist) + { + if (list) XFree(list); + return 0; + } + XSizeHints *hints = XAllocSizeHints(); + if (hints) + { + long hints_ret=0; + XGetWMNormalHints(bs->native_disp,list[0],hints,&hints_ret); + + if (hints->flags&PMinSize) + { + if (r.right < hints->min_width) r.right = hints->min_width; + if (r.bottom < hints->min_height) r.bottom = hints->min_height; + } + if (hints->flags&PMaxSize) + { + if (hints->max_width > 0 && r.right > hints->max_width) r.right = hints->max_width; + if (hints->max_height > 0 && r.bottom > hints->max_height) r.bottom = hints->max_height; + } + XFree(hints); + } + + XResizeWindow(bs->native_disp,list[0],r.right,r.bottom); + XFree(list); + } + } + KillTimer(hwnd,wParam); + } + + if (wParam != 1) break; + case WM_MOVE: + case WM_SIZE: + if (hwnd && hwnd->m_private_data) + { + bridgeState *bs = (bridgeState*)hwnd->m_private_data; + if (bs->w) + { + HWND h = hwnd->m_parent; + RECT tr = hwnd->m_position; + while (h) + { + RECT cr = h->m_position; + if (h->m_oswindow) + { + cr.right -= cr.left; + cr.bottom -= cr.top; + cr.left=cr.top=0; + } + + if (h->m_wndproc) + { + NCCALCSIZE_PARAMS p = {{ cr }}; + h->m_wndproc(h,WM_NCCALCSIZE,0,(LPARAM)&p); + cr = p.rgrc[0]; + } + tr.left += cr.left; + tr.top += cr.top; + tr.right += cr.left; + tr.bottom += cr.top; + + if (tr.left < cr.left) tr.left=cr.left; + if (tr.top < cr.top) tr.top = cr.top; + if (tr.right > cr.right) tr.right = cr.right; + if (tr.bottom > cr.bottom) tr.bottom = cr.bottom; + + if (h->m_oswindow) break; + h=h->m_parent; + } + + // todo: need to periodically check to see if the plug-in has resized its window + bool vis = IsWindowVisible(hwnd); + if (vis) + { +#if SWELL_TARGET_GDK == 2 + gint w=0,hh=0,d=0; + gdk_window_get_geometry(bs->w,NULL,NULL,&w,&hh,&d); +#else + gint w=0,hh=0; + gdk_window_get_geometry(bs->w,NULL,NULL,&w,&hh); +#endif + if (w > bs->lastrect.right-bs->lastrect.left) + { + bs->lastrect.right = bs->lastrect.left + w; + tr.right++; // workaround "bug" in GDK -- if bs->w was resized via Xlib, GDK won't resize it unless it thinks the size changed + } + if (hh > bs->lastrect.bottom-bs->lastrect.top) + { + bs->lastrect.bottom = bs->lastrect.top + hh; + tr.bottom++; // workaround "bug" in GDK -- if bs->w was resized via Xlib, GDK won't resize it unless it thinks the size changed + } + } + + if (h && h->m_oswindow != bs->cur_parent) + bs->need_reparent = true; + + if (h && (bs->need_reparent || (vis != bs->lastvis) || (vis&&memcmp(&tr,&bs->lastrect,sizeof(RECT))))) + { + if (bs->lastvis && !vis) + { + gdk_window_hide(bs->w); + bs->lastvis = false; + } + + if (bs->need_reparent) + { + gdk_window_reparent(bs->w,h->m_oswindow,tr.left,tr.top); + gdk_window_resize(bs->w, tr.right-tr.left,tr.bottom-tr.top); + bs->lastrect=tr; + + bs->cur_parent = h->m_oswindow; + bs->need_reparent=false; + if (vis && bs->lastvis) gdk_window_show(bs->w); + } + else if (memcmp(&tr,&bs->lastrect,sizeof(RECT))) + { + bs->lastrect = tr; + gdk_window_move_resize(bs->w,tr.left,tr.top, tr.right-tr.left, tr.bottom-tr.top); + } + if (vis && !bs->lastvis) + { + gdk_window_show(bs->w); + gdk_window_raise(bs->w); + bs->lastvis = true; + } + } + } + } + break; + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + +static const char * const bridge_class_name = "__swell_xbridgewndclass"; + +static bool want_key_embed_redirect(Display *disp, Window scan_id, Window *new_dest, int keycode, int modstate) +{ + for (int x=0;xcur_parent && + GDK_WINDOW_XID(bs->cur_parent) == scan_id && + bs->native_disp == disp) + { + HWND foc = GetFocus(); + if (foc && + foc->m_classname && + !strcmp(foc->m_classname,bridge_class_name) && + foc->m_private_data == (INT_PTR)bs + ) + { + void *p = GetProp(foc,"SWELL_XBRIDGE_KBHOOK_CHECK"); + if (p && SendMessage(GetParent(foc),(int)(INT_PTR)p,keycode,modstate)) + return false; + + Window root, par, *list=NULL; + unsigned int nlist=0; + if (XQueryTree(bs->native_disp,bs->native_w,&root,&par,&list, &nlist) && list) + { + if (nlist) *new_dest = list[0]; + XFree(list); + if (nlist) return true; + } + } + } + } + return false; +} + +static GdkFilterReturn filterCreateShowProc(GdkXEvent *xev, GdkEvent *event, gpointer data) +{ + const XEvent *xevent = (XEvent *)xev; + if (WDL_NOT_NORMALLY(!xev)) return GDK_FILTER_CONTINUE; + + switch (xevent->type) + { + case KeyPress: + case KeyRelease: + { + // only used if gdk_disable_multidevice() was called prior to gdk_init_ (maybe some env var too?) + Window dest; + Display *disp = xevent->xany.display; + if (!xevent->xany.send_event && + want_key_embed_redirect(disp,xevent->xkey.window - 1, &dest, xevent->xkey.keycode, xevent->xkey.state)) + { + XEvent k; + memset(&k,0,sizeof(k)); + k.xkey = xevent->xkey; + k.xkey.window = dest; + XSendEvent(disp, dest, False, NoEventMask, &k); + return GDK_FILTER_REMOVE; + } + } + break; + case FocusIn: + { + // only used if gdk_disable_multidevice() was called prior to gdk_init_ (maybe some env var too?) + Display *disp = xevent->xany.display; + Window scan_id = xevent->xfocus.window - 1; + for (int x=0;xcur_parent && + GDK_WINDOW_XID(bs->cur_parent) == scan_id && + bs->native_disp == disp) + { + POINT pt; + GetCursorPos(&pt); + RECT r; + GetWindowRect(bs->hwnd_child,&r); + if (PtInRect(&r,pt)) + { + SetFocus(bs->hwnd_child); + return GDK_FILTER_REMOVE; + } + } + } + } + break; + case GenericEvent: + // XInput2 + { + XIDeviceEvent *xievent = (XIDeviceEvent*)xevent->xcookie.data; + if (!xevent->xany.send_event && + xievent && + (xievent->evtype == XI_KeyPress || xievent->evtype == XI_KeyRelease)) + { + Window dest; + Display *disp = xevent->xany.display; + if (want_key_embed_redirect(disp,xievent->event-1, &dest, xievent->detail, xievent->mods.effective)) + { + XEvent k; + if (xievent->evtype == XI_KeyPress) k.xkey.type = KeyPress; + else k.xkey.type = KeyRelease; + k.xkey.serial = xievent->serial; + k.xkey.send_event = xievent->send_event; + k.xkey.display = disp; + k.xkey.window = dest; + k.xkey.root = xievent->root; + k.xkey.subwindow = xievent->child; + k.xkey.time = xievent->time; + k.xkey.x = xievent->event_x; + k.xkey.y = xievent->event_y; + k.xkey.x_root = xievent->root_x; + k.xkey.y_root = xievent->root_y; + k.xkey.state = xievent->mods.effective; + k.xkey.keycode = xievent->detail; + k.xkey.same_screen = 1; + XSendEvent(disp, dest, False, NoEventMask, &k); + return GDK_FILTER_REMOVE; + } + } + else if (xievent && xievent->evtype == XI_FocusIn) + { + Display *disp = xevent->xany.display; + XIFocusInEvent *foc = (XIFocusInEvent *)xievent; + Window scan_id = foc->event - 1; + for (int x=0;xcur_parent && + GDK_WINDOW_XID(bs->cur_parent) == scan_id && + bs->native_disp == disp) + { + POINT pt = { (int) foc->root_x, (int) foc->root_y }; + RECT r; + GetWindowRect(bs->hwnd_child,&r); + if (PtInRect(&r,pt)) + { + SetFocus(bs->hwnd_child); + return GDK_FILTER_REMOVE; + } + } + } + } + } + break; + case CreateNotify: + { + for (int x=0;xnative_w == xevent->xany.window && bs->native_disp == xevent->xany.display) + { + //gint w=0,hh=0; + //gdk_window_get_geometry(bs->w,NULL,NULL,&w,&hh); + XMapWindow(bs->native_disp, xevent->xcreatewindow.window); + //XResizeWindow(bs->native_disp, xevent->xcreatewindow.window,w,hh); + return GDK_FILTER_REMOVE; + } + } + } + break; + } + return GDK_FILTER_CONTINUE; +} + +HWND SWELL_CreateXBridgeWindow(HWND viewpar, void **wref, const RECT *r) +{ + HWND hwnd = NULL; + *wref = NULL; + + GdkWindow *ospar = NULL; + HWND hpar = viewpar; + while (hpar) + { + ospar = hpar->m_oswindow; + if (ospar) break; + hpar = hpar->m_parent; + } + + bool need_reparent=false; + + if (!ospar) + { + need_reparent = true; + ospar = gdk_screen_get_root_window(gdk_screen_get_default()); + } + + Display *disp = gdk_x11_display_get_xdisplay(gdk_window_get_display(ospar)); + Window w = XCreateWindow(disp,GDK_WINDOW_XID(ospar),0,0, + wdl_max(r->right-r->left,1), + wdl_max(r->bottom-r->top,1), + 0,CopyFromParent, InputOutput, CopyFromParent, 0, NULL); + GdkWindow *gdkw = w ? gdk_x11_window_foreign_new_for_display(gdk_display_get_default(),w) : NULL; + + hwnd = new HWND__(viewpar,0,r,NULL, true, xbridgeProc); + bridgeState *bs = gdkw ? new bridgeState(need_reparent,gdkw,w,disp, ospar, hwnd) : NULL; + hwnd->m_classname = bridge_class_name; + hwnd->m_private_data = (INT_PTR) bs; + if (gdkw) + { + *wref = (void *) w; + + XSelectInput(disp, w, StructureNotifyMask | SubstructureNotifyMask); + + static bool filt_add; + if (!filt_add) + { + filt_add=true; + gdk_window_add_filter(NULL, filterCreateShowProc, NULL); + } + SetTimer(hwnd,1,100,NULL); + if (!need_reparent) SendMessage(hwnd,WM_SIZE,SIZE_RESTORED,0); + } + return hwnd; +} + +struct dropSourceInfo { + dropSourceInfo() + { + srclist=NULL; srccount=0; srcfn=NULL; callback=NULL; + state=0; + dragctx=NULL; + } + ~dropSourceInfo() + { + free(srcfn); + if (dragctx) + { + if (_gdk_drag_drop_done) _gdk_drag_drop_done(dragctx,state!=0); + g_object_unref(dragctx); + } + } + + const char **srclist; + int srccount; + // or + void (*callback)(const char *); + char *srcfn; + + int state; + + GdkDragContext *dragctx; +}; + +static void encode_uri(WDL_FastString *s, const char *rd) +{ + while (*rd) + { + // unsure if UTF-8 chars should be urlencoded or allowed? + if (*rd < 0 || (!isalnum(*rd) && *rd != '-' && *rd != '_' && *rd != '.' && *rd != '/')) + { + char buf[8]; + snprintf(buf,sizeof(buf),"%%%02x",(int)(unsigned char)*rd); + s->Append(buf); + } + else s->Append(rd,1); + + rd++; + } +} + + +static LRESULT WINAPI dropSourceWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + dropSourceInfo *inf = (dropSourceInfo*)hwnd->m_private_data; + switch (msg) + { + case WM_CREATE: + if (!swell_dragsrc_osw) + { + GdkWindowAttr attr={0,}; + attr.title = (char *)"swell drag source"; + attr.event_mask = GDK_ALL_EVENTS_MASK; + attr.wclass = GDK_INPUT_ONLY; + attr.window_type = GDK_WINDOW_TOPLEVEL; + swell_dragsrc_osw = gdk_window_new(NULL,&attr,0); + } + if (swell_dragsrc_osw) + { + inf->dragctx = gdk_drag_begin(swell_dragsrc_osw, g_list_append(NULL,urilistatom())); + } + SetCapture(hwnd); + break; + case WM_MOUSEMOVE: + if (inf->dragctx) + { + POINT p; + GetCursorPos(&p); + GdkWindow *w = NULL; + GdkDragProtocol proto; + gdk_drag_find_window_for_screen(inf->dragctx,NULL,gdk_screen_get_default(),p.x,p.y,&w,&proto); + // todo: need to update gdk_drag_context_get_drag_window() + // (or just SetCursor() a drag and drop cursor) + if (w) + { + gdk_drag_motion(inf->dragctx,w,proto,p.x,p.y,GDK_ACTION_COPY,GDK_ACTION_COPY,GDK_CURRENT_TIME); + } + } + break; + case WM_LBUTTONUP: + if (inf->dragctx && !inf->state) + { + inf->state=1; + GdkAtom sel = gdk_drag_get_selection(inf->dragctx); + if (sel) gdk_selection_owner_set(swell_dragsrc_osw,sel,GDK_CURRENT_TIME,TRUE); + gdk_drag_drop(inf->dragctx,GDK_CURRENT_TIME); + if (!sel) + { + sel = gdk_drag_get_selection(inf->dragctx); + if (sel) gdk_selection_owner_set(swell_dragsrc_osw,sel,GDK_CURRENT_TIME,TRUE); + } + swell_dragsrc_timeout_start = GetTickCount(); + return 0; + } + ReleaseCapture(); + break; + case WM_USER+100: + if (wParam && lParam) + { + GdkAtom *aOut = (GdkAtom *)lParam; + GdkEventSelection *evt = (GdkEventSelection*)wParam; + + if (evt->target == urilistatom()) + { + WDL_FastString s; + if (inf->srclist && inf->srccount) + { + for (int x=0;xsrccount;x++) + { + if (x) s.Append("\n"); + s.Append("file://"); + encode_uri(&s,inf->srclist[x]); + } + } + else if (inf->callback && inf->srcfn && inf->state) + { + inf->callback(inf->srcfn); + s.Append("file://"); + encode_uri(&s,inf->srcfn); + } + + if (s.GetLength()) + { + *aOut = evt->property; +#if SWELL_TARGET_GDK == 2 + GdkWindow *pw = gdk_window_lookup(evt->requestor); + if (!pw) pw = gdk_window_foreign_new(evt->requestor); +#else + GdkWindow *pw = evt->requestor; +#endif + if (pw) + gdk_property_change(pw,*aOut,evt->target,8, GDK_PROP_MODE_REPLACE,(guchar*)s.Get(),s.GetLength()); + } + } + + if (inf->state) ReleaseCapture(); + } + break; + + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + + +void SWELL_InitiateDragDrop(HWND hwnd, RECT* srcrect, const char* srcfn, void (*callback)(const char* dropfn)) +{ + const bool is_osw_cursor = s_last_setcursor_oswnd && swell_oswindow_from_hwnd(hwnd) == s_last_setcursor_oswnd; + swell_dragsrc_fn = srcfn; + dropSourceInfo info; + info.srcfn = strdup(srcfn); + info.callback = callback; + RECT r={0,}; + HWND__ *h = new HWND__(NULL,0,&r,NULL,false,NULL,dropSourceWndProc, NULL); + swell_dragsrc_timeout_start = 0; + swell_dragsrc_hwnd=h; + h->m_private_data = (INT_PTR) &info; + dropSourceWndProc(h,WM_CREATE,0,0); + while (GetCapture()==h) + { + SWELL_RunEvents(); + Sleep(10); + if (swell_dragsrc_timeout_start && (GetTickCount()-swell_dragsrc_timeout_start) > 500) ReleaseCapture(); + } + + swell_dragsrc_hwnd=NULL; + DestroyWindow(h); + + if (is_osw_cursor && hwnd && GetFocus() != hwnd) + { + SWELL_OSWINDOW osw = swell_oswindow_from_hwnd(hwnd); + gdk_window_set_cursor(osw,NULL); + } + swell_dragsrc_fn = NULL; +} + +// owner owns srclist, make copies here etc +void SWELL_InitiateDragDropOfFileList(HWND hwnd, RECT *srcrect, const char **srclist, int srccount, HICON icon) +{ + swell_dragsrc_fn = srccount>0 ? srclist[0] : NULL; + const bool is_osw_cursor = s_last_setcursor_oswnd && swell_oswindow_from_hwnd(hwnd) == s_last_setcursor_oswnd; + dropSourceInfo info; + info.srclist = srclist; + info.srccount = srccount; + RECT r={0,}; + HWND__ *h = new HWND__(NULL,0,&r,NULL,false,NULL,dropSourceWndProc, NULL); + swell_dragsrc_timeout_start = 0; + swell_dragsrc_hwnd=h; + h->m_private_data = (INT_PTR) &info; + dropSourceWndProc(h,WM_CREATE,0,0); + while (GetCapture()==h) + { + SWELL_RunEvents(); + Sleep(10); + if (swell_dragsrc_timeout_start && (GetTickCount()-swell_dragsrc_timeout_start) > 500) ReleaseCapture(); + } + + swell_dragsrc_hwnd=NULL; + DestroyWindow(h); + + if (is_osw_cursor && hwnd && GetFocus() != hwnd) + { + SWELL_OSWINDOW osw = swell_oswindow_from_hwnd(hwnd); + gdk_window_set_cursor(osw,NULL); + } + swell_dragsrc_fn = NULL; +} + +void SWELL_FinishDragDrop() { } + + +bool SWELL_IsCursorVisible() +{ + return s_cursor_vis_cnt>=0; +} + + +void SWELL_SetCursor(HCURSOR curs) +{ + if (s_last_setcursor == curs && SWELL_focused_oswindow == s_last_setcursor_oswnd) return; + + s_last_setcursor=curs; + s_last_setcursor_oswnd = SWELL_focused_oswindow; + if (SWELL_focused_oswindow) + { + gdk_window_set_cursor(SWELL_focused_oswindow,(GdkCursor *)curs); +#ifdef SWELL_TARGET_GDK_CURSORHACK + if (GetCapture()) + { + // workaround for a GDK behavior: + // gdkwindow.c, gdk_window_set_cursor_internal() has a line: + // >>> if (_gdk_window_event_parent_of (window, pointer_info->window_under_pointer)) + // this should also allow setting the cursor if window is in a "grabbing" state + GdkDisplay *gdkdisp = gdk_display_get_default(); +#if SWELL_TARGET_GDK == 2 + if (gdkdisp && gdk_display_get_window_at_pointer(gdkdisp,NULL,NULL) != SWELL_focused_oswindow) +#else + GdkDevice *dev = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdkdisp)); + if (dev && gdk_device_get_window_at_position(dev,NULL,NULL) != SWELL_focused_oswindow) +#endif + { + Display *disp = gdk_x11_display_get_xdisplay(gdkdisp); + Window wn = GDK_WINDOW_XID(SWELL_focused_oswindow); +#if SWELL_TARGET_GDK == 2 + gint devid=2; // hardcoded default pointing device +#else + gint devid = gdk_x11_device_get_id(dev); +#endif + if (disp && wn) + { + if (curs) + XIDefineCursor(disp,devid,wn, gdk_x11_cursor_get_xcursor((GdkCursor*)curs)); + else + XIUndefineCursor(disp,devid,wn); + } + } + } +#endif // SWELL_TARGET_GDK_CURSORHACK + } +} + +HCURSOR SWELL_GetCursor() +{ + return s_last_setcursor; +} +HCURSOR SWELL_GetLastSetCursor() +{ + return s_last_setcursor; +} + +int SWELL_ShowCursor(BOOL bShow) +{ + s_cursor_vis_cnt += (bShow?1:-1); + if (s_cursor_vis_cnt==-1 && !bShow) + { + gint x1, y1; + #if SWELL_TARGET_GDK == 3 + GdkDevice *dev = gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_display_get_default ())); + gdk_device_get_position (dev, NULL, &x1, &y1); + #else + gdk_display_get_pointer(gdk_display_get_default(), NULL, &x1, &y1, NULL); + #endif + g_swell_mouse_relmode_curpos_x = x1; + g_swell_mouse_relmode_curpos_y = y1; + s_last_cursor = GetCursor(); + SetCursor((HCURSOR)gdk_cursor_new_for_display(gdk_display_get_default(),GDK_BLANK_CURSOR)); + //g_swell_mouse_relmode=true; + } + if (s_cursor_vis_cnt==0 && bShow) + { + SetCursor(s_last_cursor); + g_swell_mouse_relmode=false; + if (!g_swell_touchptr) + { + #if SWELL_TARGET_GDK == 3 + gdk_device_warp(gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_display_get_default())), + gdk_screen_get_default(), + g_swell_mouse_relmode_curpos_x, g_swell_mouse_relmode_curpos_y); + #else + gdk_display_warp_pointer(gdk_display_get_default(),gdk_screen_get_default(), g_swell_mouse_relmode_curpos_x, g_swell_mouse_relmode_curpos_y); + #endif + } + } + return s_cursor_vis_cnt; +} + +BOOL SWELL_SetCursorPos(int X, int Y) +{ + if (g_swell_mouse_relmode || g_swell_touchptr) return false; + + #if SWELL_TARGET_GDK == 3 + gdk_device_warp(gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_display_get_default())), + gdk_screen_get_default(), + X, Y); + #else + gdk_display_warp_pointer(gdk_display_get_default(),gdk_screen_get_default(), X, Y); + #endif + return true; +} + +static void getHotSpotForFile(const char *fn, POINT *pt) +{ + FILE *fp = WDL_fopenA(fn,"rb"); + if (!fp) return; + unsigned char buf[32]; + if (fread(buf,1,6,fp)==6 && !buf[0] && !buf[1] && buf[2] == 2 && buf[3] == 0 && buf[4] == 1 && buf[5] == 0) + { + if (fread(buf,1,16,fp)==16) + { + pt->x = buf[4]|(buf[5]<<8); + pt->y = buf[6]|(buf[7]<<8); + } + } + fclose(fp); +} + +HCURSOR SWELL_LoadCursorFromFile(const char *fn) +{ + GdkPixbuf *pb = gdk_pixbuf_new_from_file(fn,NULL); + if (pb) + { + POINT hs = {0,}; + getHotSpotForFile(fn,&hs); + GdkCursor *curs = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),pb,hs.x,hs.y); + g_object_unref(pb); + return (HCURSOR) curs; + } + return NULL; +} + +HCURSOR SWELL_LoadCursor(const char *_idx) +{ + GdkCursorType def = GDK_LEFT_PTR; + if (_idx == IDC_NO) def = GDK_PIRATE; + else if (_idx == IDC_SIZENWSE) def = GDK_BOTTOM_LEFT_CORNER; + else if (_idx == IDC_SIZENESW) def = GDK_BOTTOM_RIGHT_CORNER; + else if (_idx == IDC_SIZEALL) def = GDK_FLEUR; + else if (_idx == IDC_SIZEWE) def = GDK_RIGHT_SIDE; + else if (_idx == IDC_SIZENS) def = GDK_TOP_SIDE; + else if (_idx == IDC_ARROW) def = GDK_LEFT_PTR; + else if (_idx == IDC_HAND) def = GDK_HAND1; + else if (_idx == IDC_UPARROW) def = GDK_CENTER_PTR; + else if (_idx == IDC_IBEAM) def = GDK_XTERM; + else + { + SWELL_CursorResourceIndex *p = SWELL_curmodule_cursorresource_head; + while (p) + { + if (p->resid == _idx) + { + if (p->cachedCursor) return p->cachedCursor; + // todo: load from p->resname, into p->cachedCursor, p->hotspot + char buf[1024]; + GetModuleFileName(NULL,buf,sizeof(buf)); + WDL_remove_filepart(buf); + snprintf_append(buf,sizeof(buf),"/Resources/%s.cur",p->resname); + GdkPixbuf *pb = gdk_pixbuf_new_from_file(buf,NULL); + if (pb) + { + getHotSpotForFile(buf,&p->hotspot); + GdkCursor *curs = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),pb,p->hotspot.x,p->hotspot.y); + g_object_unref(pb); + return (p->cachedCursor = (HCURSOR) curs); + } + } + p=p->_next; + } + } + + HCURSOR hc= (HCURSOR)gdk_cursor_new_for_display(gdk_display_get_default(),def); + + return hc; +} + +void SWELL_Register_Cursor_Resource(const char *idx, const char *name, int hotspot_x, int hotspot_y) +{ + SWELL_CursorResourceIndex *ri = (SWELL_CursorResourceIndex*)malloc(sizeof(SWELL_CursorResourceIndex)); + ri->hotspot.x = hotspot_x; + ri->hotspot.y = hotspot_y; + ri->resname=name; + ri->cachedCursor=0; + ri->resid = idx; + ri->_next = SWELL_curmodule_cursorresource_head; + SWELL_curmodule_cursorresource_head = ri; +} + +int SWELL_KeyToASCII(int wParam, int lParam, int *newflags) +{ + return 0; +} + +void swell_scaling_init(bool no_auto_hidpi) +{ + #if SWELL_TARGET_GDK == 3 + + if (!no_auto_hidpi && g_swell_ui_scale == 256) + { + int (*gsf)(void*); + void * (*gpm)(GdkDisplay *); + *(void **)&gsf = dlsym(RTLD_DEFAULT,"gdk_monitor_get_scale_factor"); + *(void **)&gpm = dlsym(RTLD_DEFAULT,"gdk_display_get_primary_monitor"); + + if (gpm && gsf) + { + GdkDisplay *gdkdisp = gdk_display_get_default(); + if (gdkdisp) + { + void *m = gpm(gdkdisp); + if (m) + { + int sf = gsf(m); + if (sf > 1 && sf < 8) + g_swell_ui_scale = sf*256; + } + } + } + } + + if (g_swell_ui_scale != 256) + { + GdkDisplay *gdkdisp = gdk_display_get_default(); + if (gdkdisp) + { + void (*p)(GdkDisplay*, gint); + *(void **)&p = dlsym(RTLD_DEFAULT,"gdk_x11_display_set_window_scale"); + if (p) p(gdkdisp,1); + } + } + #endif +} + +BOOL EnumDisplayMonitors(HDC hdc,const LPRECT r,MONITORENUMPROC proc,LPARAM lParam) +{ + GdkScreen *defscr = gdk_screen_get_default(); + const int nmon = gdk_screen_get_n_monitors(defscr); + for (int x = 0; x < nmon; x ++) + { + GdkRectangle rc={0,0,1024,1024}; + gdk_screen_get_monitor_geometry(defscr,x,&rc); + RECT screen_rect, tmp = { rc.x, rc.y,rc.x+rc.width , rc.y+rc.height }; + if (r) + { + if (!IntersectRect(&screen_rect,r,&tmp)) + continue; + } + else + { + screen_rect = tmp; + } + + if (!proc((HMONITOR)(INT_PTR) (x+1),hdc,&screen_rect,lParam)) break; + } + return TRUE; +} +BOOL GetMonitorInfo(HMONITOR hmon, void *inf) +{ + GdkScreen *defscr = gdk_screen_get_default(); + const int nmon = gdk_screen_get_n_monitors(defscr); + const int monidx = ((int) (INT_PTR) hmon)-1; + if (monidx<0 || monidx >= nmon) return FALSE; + + MONITORINFOEX *a = (MONITORINFOEX*)inf; + if (a->cbSize < sizeof(MONITORINFO)) return FALSE; + a->dwFlags = 0; + GdkRectangle rc={0,0,1024,1024}; + gdk_screen_get_monitor_geometry(defscr,monidx,&rc); + RECT tmp = { rc.x, rc.y,rc.x+rc.width , rc.y+rc.height }; + a->rcMonitor = a->rcWork = tmp; + + if (a->cbSize > sizeof(MONITORINFO)) + { + const int maxlen = (int) (a->cbSize - sizeof(MONITORINFO)); + const char *s = gdk_screen_get_monitor_plug_name(defscr,monidx); + if (!s) return FALSE; + lstrcpyn_safe(a->szDevice,s,maxlen); + } + + return TRUE; +} + + + +void SWELL_SetViewGL(HWND h, char wantGL) +{ + // only works for X bridge windows + if (h && h->m_classname == bridge_class_name && h->m_private_data) + { + bridgeState *bs = (bridgeState*)h->m_private_data; + if (wantGL && !bs->gl_ctx) + { + static GLint att[] = { GLX_RGBA, None }; + XVisualInfo* vi = glXChooseVisual(bs->native_disp, 0, att); + bs->gl_ctx = glXCreateContext(bs->native_disp,vi,NULL,1); + } + else if (!wantGL && bs->gl_ctx) + { + if (s_last_gl_ctx == bs) + { + glXMakeCurrent(bs->native_disp,None, NULL); + s_last_gl_ctx = NULL; + } + + glXDestroyContext(bs->native_disp,bs->gl_ctx); + bs->gl_ctx = NULL; + } + } +} + +bool SWELL_GetViewGL(HWND h) +{ + return h && h->m_classname == bridge_class_name && h->m_private_data && + ((bridgeState*)h->m_private_data)->gl_ctx != NULL; +} + +bool SWELL_SetGLContextToView(HWND h) +{ + if (h) + { + if (h->m_classname == bridge_class_name && h->m_private_data) + { + bridgeState *bs = (bridgeState*)h->m_private_data; + if (bs->gl_ctx) + { + glXMakeCurrent(bs->native_disp, bs->native_w, bs->gl_ctx); + s_last_gl_ctx = bs; + return true; + } + } + return false; + } + + if (s_last_gl_ctx) + { + glXMakeCurrent(s_last_gl_ctx->native_disp,None, NULL); + s_last_gl_ctx = NULL; + } + return true; +} + +void *SWELL_GetOSWindow(HWND hwnd, const char *type) +{ + if (hwnd && !strcmp(type,"GdkWindow")) return hwnd->m_oswindow; + return NULL; +} + +void *SWELL_GetOSEvent(const char *type) +{ + return !strcmp(type,"GdkEvent") ? s_cur_evt : NULL; +} + + +#endif +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-generic-headless.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-generic-headless.cpp new file mode 100644 index 0000000..a794007 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-generic-headless.cpp @@ -0,0 +1,319 @@ +/* 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. + + + */ + + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" + +#ifndef SWELL_TARGET_GDK + +#include "swell-internal.h" +#include "swell-dlggen.h" +#include "../wdlcstring.h" + +void swell_oswindow_destroy(HWND hwnd) +{ + if (hwnd && hwnd->m_oswindow) + { + if (SWELL_focused_oswindow == hwnd->m_oswindow) SWELL_focused_oswindow = NULL; + hwnd->m_oswindow=NULL; +#ifdef SWELL_LICE_GDI + delete hwnd->m_backingstore; + hwnd->m_backingstore=0; +#endif + } +} +void swell_oswindow_update_text(HWND hwnd) +{ + if (hwnd) printf("SWELL: swt '%s'\n",hwnd->m_title.Get()); + +} + +void swell_oswindow_focus(HWND hwnd) +{ + if (!hwnd) + { + SWELL_focused_oswindow = NULL; + } + while (hwnd && !hwnd->m_oswindow) hwnd=hwnd->m_parent; + if (hwnd && hwnd->m_oswindow != SWELL_focused_oswindow) + { + SWELL_focused_oswindow = hwnd->m_oswindow; + } +} + +void swell_recalcMinMaxInfo(HWND hwnd) +{ +} + + +void SWELL_initargs(int *argc, char ***argv) +{ +} + +void swell_oswindow_updatetoscreen(HWND hwnd, RECT *rect) +{ +} + +void swell_oswindow_manage(HWND hwnd, bool wantfocus) +{ + if (!hwnd) return; + + bool isVis = !!hwnd->m_oswindow; + bool wantVis = !hwnd->m_parent && hwnd->m_visible; + + if (isVis != wantVis) + { + if (!wantVis) swell_oswindow_destroy(hwnd); + else + { + // generic implementation + hwnd->m_oswindow = hwnd; + if (wantfocus) swell_oswindow_focus(hwnd); + } + } + if (wantVis) swell_oswindow_update_text(hwnd); +} + +void SWELL_RunEvents() +{ +} + + +void swell_oswindow_update_style(HWND hwnd, LONG oldstyle) +{ +} + +void swell_oswindow_update_enable(HWND hwnd) +{ +} + +int SWELL_SetWindowLevel(HWND hwnd, int newlevel) +{ + int rv=0; + if (hwnd) + { + rv = hwnd->m_israised ? 1 : 0; + hwnd->m_israised = newlevel>0; + } + return rv; +} + +void SWELL_GetViewPort(RECT *r, const RECT *sourcerect, bool wantWork) +{ + r->left=r->top=0; + r->right=1024; + r->bottom=768; +} + + +bool GetWindowRect(HWND hwnd, RECT *r) +{ + if (!hwnd) return false; + if (hwnd->m_oswindow) + { + *r = hwnd->m_position; + return true; + } + + r->left=r->top=0; + ClientToScreen(hwnd,(LPPOINT)r); + r->right = r->left + hwnd->m_position.right - hwnd->m_position.left; + r->bottom = r->top + hwnd->m_position.bottom - hwnd->m_position.top; + return true; +} + +void swell_oswindow_begin_resize(SWELL_OSWINDOW wnd) { } +void swell_oswindow_resize(SWELL_OSWINDOW wnd, int reposflag, RECT f) { } +void swell_oswindow_postresize(HWND hwnd, RECT f) { } +void swell_oswindow_invalidate(HWND hwnd, const RECT *r) { } + +void UpdateWindow(HWND hwnd) { } + +static WDL_IntKeyedArray m_clip_recs(GlobalFree); +static WDL_PtrList m_clip_curfmts; + +bool OpenClipboard(HWND hwndDlg) +{ + RegisterClipboardFormat(NULL); + return true; +} + +void CloseClipboard() { } + +UINT EnumClipboardFormats(UINT lastfmt) +{ + int x=0; + for (;;) + { + int fmt=0; + if (!m_clip_recs.Enumerate(x++,&fmt)) return 0; + if (lastfmt == 0) return fmt; + + if ((UINT)fmt == lastfmt) return m_clip_recs.Enumerate(x++,&fmt) ? fmt : 0; + } +} + +HANDLE GetClipboardData(UINT type) +{ + return m_clip_recs.Get(type); +} + + +void EmptyClipboard() +{ + m_clip_recs.DeleteAll(); +} + +void SetClipboardData(UINT type, HANDLE h) +{ + if (h) m_clip_recs.Insert(type,h); + else m_clip_recs.Delete(type); +} + +UINT RegisterClipboardFormat(const char *desc) +{ + if (!m_clip_curfmts.GetSize()) + { + m_clip_curfmts.Add(strdup("SWELL__CF_TEXT")); + m_clip_curfmts.Add(strdup("SWELL__CF_HDROP")); + } + if (!desc || !*desc) return 0; + int x; + const int n = m_clip_curfmts.GetSize(); + for(x=0;xx=0; + pt->y=0; +} + + +WORD GetAsyncKeyState(int key) +{ + return 0; +} + + +DWORD GetMessagePos() +{ + return 0; +} + +HWND SWELL_CreateXBridgeWindow(HWND viewpar, void **wref, const RECT *r) +{ + *wref = NULL; + return NULL; +} + + + +void SWELL_InitiateDragDrop(HWND hwnd, RECT* srcrect, const char* srcfn, void (*callback)(const char* dropfn)) +{ +} + +// owner owns srclist, make copies here etc +void SWELL_InitiateDragDropOfFileList(HWND hwnd, RECT *srcrect, const char **srclist, int srccount, HICON icon) +{ +} + +void SWELL_FinishDragDrop() { } + +static HCURSOR m_last_setcursor; + +void SWELL_SetCursor(HCURSOR curs) +{ + m_last_setcursor=curs; +} + +HCURSOR SWELL_GetCursor() +{ + return m_last_setcursor; +} +HCURSOR SWELL_GetLastSetCursor() +{ + return m_last_setcursor; +} + +static int m_curvis_cnt; +bool SWELL_IsCursorVisible() +{ + return m_curvis_cnt>=0; +} + +int SWELL_ShowCursor(BOOL bShow) +{ + m_curvis_cnt += (bShow?1:-1); + return m_curvis_cnt; +} + +BOOL SWELL_SetCursorPos(int X, int Y) +{ + return false; +} + +HCURSOR SWELL_LoadCursorFromFile(const char *fn) +{ + return NULL; +} + + +static SWELL_CursorResourceIndex *SWELL_curmodule_cursorresource_head; + +HCURSOR SWELL_LoadCursor(const char *_idx) +{ + return NULL; +} + + + +void SWELL_Register_Cursor_Resource(const char *idx, const char *name, int hotspot_x, int hotspot_y) +{ + SWELL_CursorResourceIndex *ri = (SWELL_CursorResourceIndex*)malloc(sizeof(SWELL_CursorResourceIndex)); + ri->hotspot.x = hotspot_x; + ri->hotspot.y = hotspot_y; + ri->resname=name; + ri->cachedCursor=0; + ri->resid = idx; + ri->_next = SWELL_curmodule_cursorresource_head; + SWELL_curmodule_cursorresource_head = ri; +} + +int SWELL_KeyToASCII(int wParam, int lParam, int *newflags) +{ + return 0; +} + +int swell_is_app_inactive() { return 0; } + +void *SWELL_GetOSWindow(HWND hwnd, const char *type) { return NULL; } +void *SWELL_GetOSEvent(const char *type) { return NULL; } + +#endif +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-ini.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-ini.cpp new file mode 100644 index 0000000..fd073c3 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-ini.cpp @@ -0,0 +1,634 @@ +/* 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. + + This file implements basic win32 GetPrivateProfileString / etc support. + It works by caching reads, but writing through on every write that is required (to ensure + that updates take, especially when being updated from multiple modules who have their own + cache of the .ini file). + + It is threadsafe, but in theory if two processes are trying to access the same ini, + results may go a bit unpredictable (but in general the file should NOT get corrupted, + we hope). + +*/ + +#ifndef SWELL_PROVIDED_BY_APP + + +#include "swell.h" +#include "../assocarray.h" +#include "../wdlcstring.h" +#include "../mutex.h" +#include "../queue.h" +#include +#include +#include + +static void deleteStringKeyedArray(WDL_StringKeyedArray *p) { delete p; } + +struct iniFileContext +{ + iniFileContext() : m_sections(false,deleteStringKeyedArray) + { + m_curfn=NULL; + m_lastaccesscnt=0; + m_curfn_time=0; + m_curfn_sz=0; + } + ~iniFileContext() { } + + WDL_UINT64 m_lastaccesscnt; + time_t m_curfn_time; + int m_curfn_sz; + char *m_curfn; + + WDL_StringKeyedArray< WDL_StringKeyedArray * > m_sections; + +}; + +#define NUM_OPEN_CONTEXTS 32 +static iniFileContext s_ctxs[NUM_OPEN_CONTEXTS]; +static WDL_Mutex m_mutex; + +static time_t getfileupdtimesize(const char *fn, int *szOut) +{ + struct stat st; + *szOut = 0; + if (!fn || !fn[0] || stat(fn,&st)) return 0; + + if (S_ISLNK(st.st_mode)) + { + char *linkpath = realpath(fn,NULL); + if (linkpath) + { + const bool ok = !stat(linkpath,&st); + free(linkpath); + + if (!ok) return 0; + } + } + + *szOut = (int)st.st_size; + return st.st_mtime; +} + +static bool fgets_to_typedbuf(WDL_TypedBuf *buf, FILE *fp) +{ + int rdpos=0; + while (rdpos < 1024*1024*32) + { + if (buf->GetSize()Resize(rdpos+8192); + if (buf->GetSize()Get()+rdpos; + *p=0; + if (!fgets(p,buf->GetSize()-rdpos,fp) || !*p) break; + while (*p) p++; + if (p[-1] == '\r' || p[-1] == '\n') break; + + rdpos = (int) (p - buf->Get()); + } + return buf->GetSize()>0 && buf->Get()[0]; +} + +// return true on success +static iniFileContext *GetFileContext(const char *name) +{ + static WDL_UINT64 acc_cnt; + int best_z = 0; + char fntemp[512]; + if (!name || !strstr(name,"/")) + { + extern char *g_swell_defini; + if (g_swell_defini) + { + lstrcpyn_safe(fntemp,g_swell_defini,sizeof(fntemp)); + } + else + { + const char *p = getenv("HOME"); + snprintf(fntemp,sizeof(fntemp),"%s/.libSwell.ini", + p && *p ? p : "/tmp"); + } + if (name && *name) + { + WDL_remove_fileext(fntemp); + snprintf_append(fntemp,sizeof(fntemp),"_%s%s",name, + stricmp(WDL_get_fileext(name),".ini")?".ini":""); + } + name = fntemp; + } + + { + int w; + WDL_UINT64 bestcnt = 0; + bestcnt--; + + for (w=0;wm_lastaccesscnt=++acc_cnt; + + int sz=0; + if (!ctx->m_curfn || stricmp(ctx->m_curfn,name) || ctx->m_curfn_time != getfileupdtimesize(ctx->m_curfn,&sz) || sz != ctx->m_curfn_sz) + { + ctx->m_sections.DeleteAll(); +// printf("reinitting to %s\n",name); + if (!ctx->m_curfn || stricmp(ctx->m_curfn,name)) + { + free(ctx->m_curfn); + ctx->m_curfn=strdup(name); + } + FILE *fp = WDL_fopenA(name,"r"); + + if (!fp) + { + ctx->m_curfn_time=0; + ctx->m_curfn_sz=0; + return ctx; // allow to proceed (empty file) + } + + flock(fileno(fp),LOCK_SH); + + // parse .ini file + WDL_StringKeyedArray *cursec=NULL; + + int lcnt=0; + for (;;) + { + static WDL_TypedBuf _buf; + if (!fgets_to_typedbuf(&_buf,fp)) break; + + char *buf = _buf.Get(); + if (!ctx->m_sections.GetSize()) + { + lcnt += strlen(buf); + if (lcnt > 256*1024) break; // dont bother reading more than 256kb if no section encountered + } + char *p=buf; + + while (*p) p++; + + if (p>buf) + { + p--; + while (p >= buf && (*p==' ' || *p == '\r' || *p == '\n' || *p == '\t')) p--; + p[1]=0; + } + p=buf; + while (*p == ' ' || *p == '\t') p++; + if (p[0] == '[') + { + char *p2=p; + while (*p2 && *p2 != ']') p2++; + if (*p2) + { + *p2=0; + if (cursec) cursec->Resort(); + + if (p[1]) + { + cursec = ctx->m_sections.Get(p+1); + if (!cursec) + { + cursec = new WDL_StringKeyedArray(false,WDL_StringKeyedArray::freecharptr); + ctx->m_sections.Insert(p+1,cursec); + } + else cursec->DeleteAll(); + } + else cursec=0; + } + } + else if (cursec) + { + char *t=strstr(p,"="); + if (t) + { + *t++=0; + // for maximum win32 compat, we should skip leading whitespace on t, and also trim quotes if any + WDL_remove_trailing_whitespace(p); + if (*p) + cursec->AddUnsorted(p,strdup(t)); + } + } + } + ctx->m_curfn_time = getfileupdtimesize(name,&ctx->m_curfn_sz); + flock(fileno(fp),LOCK_UN); + fclose(fp); + + if (cursec) cursec->Resort(); + } + return ctx; +} + +static void WriteBackFile(iniFileContext *ctx) +{ + if (!ctx||!ctx->m_curfn) return; + char newfn[1024]; + + const char *curfn = ctx->m_curfn; + + struct stat st; + char *needfree = NULL; + if (!stat(curfn,&st) && S_ISLNK(st.st_mode)) + { + needfree = realpath(curfn,NULL); + if (needfree) curfn = needfree; + } + + lstrcpyn_safe(newfn,curfn,sizeof(newfn)-8); + { + char *p=newfn; + while (*p) p++; + while (p>newfn && p[-1] != '/') p--; + char lc = '.'; + while (*p) + { + char c = *p; + *p++ = lc; + lc = c; + } + *p++ = lc; + strcpy(p,".new"); + } + + FILE *fp = WDL_fopenA(newfn,"w"); + if (!fp) + { + free(needfree); + return; + } + + flock(fileno(fp),LOCK_EX); + + int x; + for (x = 0; ; x ++) + { + const char *secname=NULL; + WDL_StringKeyedArray * cursec = ctx->m_sections.Enumerate(x,&secname); + if (!cursec || !secname) break; + + fprintf(fp,"[%s]\n",secname); + int y; + for (y=0;;y++) + { + const char *keyname = NULL; + const char *keyvalue = cursec->Enumerate(y,&keyname); + if (!keyvalue || !keyname) break; + if (*keyname) fprintf(fp,"%s=%s\n",keyname,keyvalue); + } + fprintf(fp,"\n"); + } + + fflush(fp); + flock(fileno(fp),LOCK_UN); + fclose(fp); + + if (!rename(newfn,curfn)) + { + ctx->m_curfn_time = getfileupdtimesize(curfn,&ctx->m_curfn_sz); + } + else + { + // error updating, hmm how to handle this? + } + free(needfree); +} + +BOOL WritePrivateProfileSection(const char *appname, const char *strings, const char *fn) +{ + if (!appname) return FALSE; + WDL_MutexLock lock(&m_mutex); + iniFileContext *ctx = GetFileContext(fn); + if (!ctx) return FALSE; + + WDL_StringKeyedArray * cursec = ctx->m_sections.Get(appname); + if (!cursec) + { + if (!*strings) return TRUE; + + cursec = new WDL_StringKeyedArray(false,WDL_StringKeyedArray::freecharptr); + ctx->m_sections.Insert(appname,cursec); + } + else cursec->DeleteAll(); + + if (*strings) + { + while (*strings) + { + char buf[8192]; + lstrcpyn_safe(buf,strings,sizeof(buf)); + char *p = buf; + while (*p && *p != '=') p++; + if (*p) + { + *p++=0; + cursec->Insert(buf,strdup(strings + (p-buf))); + } + + strings += strlen(strings)+1; + } + } + WriteBackFile(ctx); + + return TRUE; +} + + +BOOL WritePrivateProfileString(const char *appname, const char *keyname, const char *val, const char *fn) +{ + if (!appname || (keyname && !*keyname)) return FALSE; +// printf("writing %s %s %s %s\n",appname,keyname,val,fn); + WDL_MutexLock lock(&m_mutex); + + iniFileContext *ctx = GetFileContext(fn); + if (!ctx) return FALSE; + + if (!keyname) + { + if (ctx->m_sections.Get(appname)) + { + ctx->m_sections.Delete(appname); + WriteBackFile(ctx); + } + } + else + { + WDL_StringKeyedArray * cursec = ctx->m_sections.Get(appname); + if (!val) + { + if (cursec && cursec->Get(keyname)) + { + cursec->Delete(keyname); + WriteBackFile(ctx); + } + } + else + { + const char *p; + if (!cursec || !(p=cursec->Get(keyname)) || strcmp(p,val)) + { + if (!cursec) + { + cursec = new WDL_StringKeyedArray(false,WDL_StringKeyedArray::freecharptr); + ctx->m_sections.Insert(appname,cursec); + } + cursec->Insert(keyname,strdup(val)); + WriteBackFile(ctx); + } + } + + } + + return TRUE; +} + +static void lstrcpyn_trimmed(char* dest, const char* src, int len) +{ + if (len<1) return; + // Mimic Win32 behavior of stripping quotes and whitespace + while (*src==' ' || *src=='\t') ++src; // Strip beginning whitespace + + const char *end = src; + if (*end) while (end[1]) end++; + + while (end >= src && (*end==' ' || *end=='\t')) --end; // Strip end whitespace + + if (end > src && ((*src=='\"' && *end=='\"') || (*src=='\'' && *end=='\''))) + { + // Strip initial set of "" or '' + ++src; + --end; + } + + int newlen = (int) (end-src+2); + if (newlen < 1) newlen = 1; + else if (newlen > len) newlen = len; + + lstrcpyn_safe(dest, src, newlen); +} + +DWORD GetPrivateProfileSection(const char *appname, char *strout, DWORD strout_len, const char *fn) +{ + WDL_MutexLock lock(&m_mutex); + + if (!strout || strout_len<2) + { + if (strout && strout_len==1) *strout=0; + return 0; + } + iniFileContext *ctx= GetFileContext(fn); + int szOut=0; + WDL_StringKeyedArray *cursec = ctx ? ctx->m_sections.Get(appname) : NULL; + + if (ctx && cursec) + { + int x; + for(x=0;xGetSize();x++) + { + const char *kv = NULL; + const char *val = cursec->Enumerate(x,&kv); + if (val && kv) + { + int l; + +#define WRSTR(v) \ + l = (int)strlen(v); \ + if (l > (int)strout_len - szOut - 2) l = (int)strout_len - 2 - szOut; \ + if (l>0) { memcpy(strout+szOut,v,l); szOut+=l; } + + WRSTR(kv) + WRSTR("=") +#undef WRSTR + + lstrcpyn_trimmed(strout+szOut, val, (int)strout_len - szOut - 2); + szOut += strlen(strout+szOut); + + l=1; + if (l > (int)strout_len - szOut - 1) l = (int)strout_len - 1 - szOut; + if (l>0) { memset(strout+szOut,0,l); szOut+=l; } + if (szOut >= (int)strout_len-1) + { + strout[strout_len-1]=0; + return strout_len-2; + } + } + } + } + strout[szOut]=0; + if (!szOut) strout[1]=0; + return szOut; +} + +DWORD GetPrivateProfileString(const char *appname, const char *keyname, const char *def, char *ret, int retsize, const char *fn) +{ + WDL_MutexLock lock(&m_mutex); + +// printf("getprivateprofilestring: %s\n",fn); + iniFileContext *ctx= GetFileContext(fn); + + if (ctx) + { + if (!appname||!keyname) + { + WDL_Queue tmpbuf; + if (!appname) + { + int x; + for (x = 0;; x ++) + { + const char *secname=NULL; + if (!ctx->m_sections.Enumerate(x,&secname) || !secname) break; + if (*secname) tmpbuf.Add(secname,(int)strlen(secname)+1); + } + } + else + { + WDL_StringKeyedArray *cursec = ctx->m_sections.Get(appname); + if (cursec) + { + int y; + for (y = 0; ; y ++) + { + const char *k=NULL; + if (!cursec->Enumerate(y,&k)||!k) break; + if (*k) tmpbuf.Add(k,(int)strlen(k)+1); + } + } + } + + int sz=tmpbuf.GetSize()-1; + if (sz<0) + { + ret[0]=ret[1]=0; + return 0; + } + if (sz > retsize-2) sz=retsize-2; + memcpy(ret,tmpbuf.Get(),sz); + ret[sz]=ret[sz+1]=0; + + return (DWORD)sz; + } + + WDL_StringKeyedArray *cursec = ctx->m_sections.Get(appname); + if (cursec) + { + const char *val = cursec->Get(keyname); + if (val) + { + lstrcpyn_trimmed(ret,val,retsize); + return (DWORD)strlen(ret); + } + } + } +// printf("def %s %s %s %s\n",appname,keyname,def,fn); + lstrcpyn_safe(ret,def?def:"",retsize); + return (DWORD)strlen(ret); +} + +int GetPrivateProfileInt(const char *appname, const char *keyname, int def, const char *fn) +{ + char buf[512]; + GetPrivateProfileString(appname,keyname,"",buf,sizeof(buf),fn); + if (buf[0]) + { + int a=atoi(buf); + if (a||buf[0]=='0') return a; + } + return def; +} + +static bool __readbyte(char *src, unsigned char *out) +{ + unsigned char cv=0; + int s=4; + while(s>=0) + { + if (*src >= '0' && *src <= '9') cv += (*src-'0')<= 'a' && *src <= 'f') cv += (*src-'a' + 10)<= 'A' && *src <= 'F') cv += (*src-'A' + 10)<0) + { + if (!__readbyte(src,&cv)) break; + *bufout++ = cv; + sum += cv; + src+=2; + } + ret = bufsz<0 && __readbyte(src,&cv) && cv==sum; + } + free(tmp); + //printf("getprivateprofilestruct returning %d\n",ret); + return ret; +} + +BOOL WritePrivateProfileStruct(const char *appname, const char *keyname, const void *buf, int bufsz, const char *fn) +{ + if (!keyname || !buf) return WritePrivateProfileString(appname,keyname,(const char *)buf,fn); + char *tmp=(char *)malloc((bufsz+1)*2+1); + if (!tmp) return 0; + char *p = tmp; + unsigned char sum=0; + unsigned char *src=(unsigned char *)buf; + while (bufsz-- > 0) + { + sprintf(p,"%02X",*src); + sum+=*src++; + p+=2; + } + sprintf(p,"%02X",sum); + + BOOL ret=WritePrivateProfileString(appname,keyname,tmp,fn); + free(tmp); + return ret; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-internal.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-internal.h new file mode 100644 index 0000000..0cc812f --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-internal.h @@ -0,0 +1,1393 @@ +/* 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. +*/ + +#ifndef _SWELL_INTERNAL_H_ +#define _SWELL_INTERNAL_H_ + +#include "../ptrlist.h" + +struct SWELL_ListView_Rec +{ + char *txt; + int image_idx; +}; + +class SWELL_ListView_Row +{ +public: + SWELL_ListView_Row() : m_param(0), m_tmp(0) { } + ~SWELL_ListView_Row() + { + for (int x = 0; x < m_cols.GetSize(); x ++) + { + free(m_cols.Get()[x].txt); + } + m_cols.Resize(0); + } + int get_num_cols() const { return m_cols.GetSize(); } + char *get_col_txt(int x) const { return x >= 0 && x < m_cols.GetSize() ? m_cols.Get()[x].txt : NULL; } + void add_col(const char *p) { SWELL_ListView_Rec r = { p ? strdup(p) : NULL }; m_cols.Add(r); } + void set_col_txt(int x, const char *p) + { + if (WDL_NORMALLY(x >= 0 && x < m_cols.GetSize())) + { + free(m_cols.Get()[x].txt); + m_cols.Get()[x].txt = p ? strdup(p) : NULL; + } + } + int get_img_idx(int x) const { return x >= 0 && x < m_cols.GetSize() ? m_cols.Get()[x].image_idx : 0; } + void set_img_idx(int x, int index) + { + if (WDL_NORMALLY(x >= 0 && x < m_cols.GetSize())) + m_cols.Get()[x].image_idx = index; + } + WDL_TypedBuf m_cols; + + LPARAM m_param; + int m_tmp; // Cocoa uses this temporarily, generic uses it as a mask (1= selected) +}; + +struct HTREEITEM__; + +#ifdef SWELL_TARGET_OSX + +#if 0 + // at some point we should enable this and use it in most SWELL APIs that call Cocoa code... + #define SWELL_BEGIN_TRY @try { + #define SWELL_END_TRY(x) } @catch (NSException *ex) { NSLog(@"SWELL exception in %s:%d :: %@:%@\n",__FILE__,__LINE__,[ex name], [ex reason]); x } +#else + #define SWELL_BEGIN_TRY + #define SWELL_END_TRY(x) +#endif + +#define __SWELL_PREFIX_CLASSNAME3(a,b) a##b +#define __SWELL_PREFIX_CLASSNAME2(a,b) __SWELL_PREFIX_CLASSNAME3(a,b) +#define __SWELL_PREFIX_CLASSNAME(cname) __SWELL_PREFIX_CLASSNAME2(SWELL_APP_PREFIX,cname) + +// this defines interfaces to internal swell classes +#define SWELL_hwndChild __SWELL_PREFIX_CLASSNAME(_hwnd) +#define SWELL_hwndCarbonHost __SWELL_PREFIX_CLASSNAME(_hwndcarbonhost) + +#define SWELL_ModelessWindow __SWELL_PREFIX_CLASSNAME(_modelesswindow) +#define SWELL_ModalDialog __SWELL_PREFIX_CLASSNAME(_dialogbox) + +#define SWELL_TextField __SWELL_PREFIX_CLASSNAME(_textfield) +#define SWELL_ListView __SWELL_PREFIX_CLASSNAME(_listview) +#define SWELL_TreeView __SWELL_PREFIX_CLASSNAME(_treeview) +#define SWELL_TabView __SWELL_PREFIX_CLASSNAME(_tabview) +#define SWELL_ProgressView __SWELL_PREFIX_CLASSNAME(_progind) +#define SWELL_TextView __SWELL_PREFIX_CLASSNAME(_textview) +#define SWELL_BoxView __SWELL_PREFIX_CLASSNAME(_box) +#define SWELL_Button __SWELL_PREFIX_CLASSNAME(_button) +#define SWELL_PopUpButton __SWELL_PREFIX_CLASSNAME(_pub) +#define SWELL_ComboBox __SWELL_PREFIX_CLASSNAME(_cbox) + +#define SWELL_StatusCell __SWELL_PREFIX_CLASSNAME(_statuscell) +#define SWELL_ListViewCell __SWELL_PREFIX_CLASSNAME(_listviewcell) +#define SWELL_ODListViewCell __SWELL_PREFIX_CLASSNAME(_ODlistviewcell) +#define SWELL_ODButtonCell __SWELL_PREFIX_CLASSNAME(_ODbuttoncell) +#define SWELL_ImageButtonCell __SWELL_PREFIX_CLASSNAME(_imgbuttoncell) + +#define SWELL_FocusRectWnd __SWELL_PREFIX_CLASSNAME(_drawfocusrectwnd) + +#define SWELL_DataHold __SWELL_PREFIX_CLASSNAME(_sdh) +#define SWELL_ThreadTmp __SWELL_PREFIX_CLASSNAME(_thread) +#define SWELL_PopupMenuRecv __SWELL_PREFIX_CLASSNAME(_trackpopupmenurecv) + +#define SWELL_TimerFuncTarget __SWELL_PREFIX_CLASSNAME(_tft) +#define SWELL_MetalNotificationHandler __SWELL_PREFIX_CLASSNAME(_mnfh) + +#define SWELL_Menu __SWELL_PREFIX_CLASSNAME(_menu) + +#ifdef __OBJC__ + +#ifndef MAC_OS_X_VERSION_10_7 +typedef struct _NSDraggingSession NSDraggingSession; +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 +typedef int NSInteger; +typedef unsigned int NSUInteger; +#endif + +@interface SWELL_Menu : NSMenu +{ +} +-(void)dealloc; +- (id)copyWithZone:(NSZone *)zone; +@end + +@interface SWELL_DataHold : NSObject +{ + @public + void *m_data; +} +-(id) initWithVal:(void *)val; +-(void *) getValue; +@end + +@interface SWELL_TimerFuncTarget : NSObject +{ + TIMERPROC m_cb; + HWND m_hwnd; + UINT_PTR m_timerid; +} +-(id) initWithId:(UINT_PTR)tid hwnd:(HWND)h callback:(TIMERPROC)cb; +-(void)SWELL_Timer:(id)sender; +@end + +typedef struct OwnedWindowListRec +{ + NSWindow *hwnd; + struct OwnedWindowListRec *_next; +} OwnedWindowListRec; + +typedef struct WindowPropRec +{ + char *name; // either <64k or a strdup'd name + void *data; + struct WindowPropRec *_next; +} WindowPropRec; + + + +@interface SWELL_TextField : NSTextField +{ + @public + bool m_last_dark_mode; + bool m_ctlcolor_set; + bool m_disable_menu; + LONG_PTR m_userdata; +} +- (id) init; +- (void)setNeedsDisplay:(BOOL)flag; +- (void)setNeedsDisplayInRect:(NSRect)rect; +- (void)drawRect:(NSRect)rect; +- (void)initColors:(int)darkmode; // -1 to not update darkmode but trigger update of colors +- (void)swellDisableContextMenu:(bool)dis; +- (NSMenu *)textView:(NSTextView *)view menu:(NSMenu *)menu forEvent:(NSEvent *)event atIndex:(NSUInteger)charIndex; +-(LONG_PTR)getSwellUserData; +-(void)setSwellUserData:(LONG_PTR)val; +@end + +@interface SWELL_TabView : NSTabView +{ + NSInteger m_tag; + id m_dest; +} +@end + +@interface SWELL_ProgressView : NSProgressIndicator +{ + NSInteger m_tag; +} +@end + +@interface SWELL_ListViewCell : NSTextFieldCell +{ +} +-(NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView; +- (NSRect)drawingRectForBounds:(NSRect)rect; +@end + +@interface SWELL_StatusCell : SWELL_ListViewCell +{ + NSImage *status; + bool m_always_indent; +} +-(id)initNewCell:(bool)always_indent; +-(void)setStatusImage:(NSImage *)img; +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView; +@end + +@interface SWELL_TreeView : NSOutlineView +{ + @public + bool m_fakerightmouse; + LONG style; + WDL_PtrList *m_items; + NSColor *m_fgColor; + NSMutableArray *m_selColors; +} +-(id) init; +-(void) dealloc; +-(bool) findItem:(HTREEITEM)item parOut:(HTREEITEM__ **)par idxOut:(int *)idx; +-(void)mouseDown:(NSEvent *)theEvent; +-(void)mouseDragged:(NSEvent *)theEvent; +-(void)mouseUp:(NSEvent *)theEvent; +- (void)rightMouseUp:(NSEvent *)theEvent; +- (void)highlightSelectionInClipRect:(NSRect)theClipRect; + +// data source +-(NSInteger) outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item; +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item; +- (id)outlineView:(NSOutlineView *)outlineView + child:(NSInteger)index + ofItem:(id)item; +- (id)outlineView:(NSOutlineView *)outlineView + objectValueForTableColumn:(NSTableColumn *)tableColumn + byItem:(id)item; + + + +- (BOOL)outlineView:(NSOutlineView *)outlineView + writeItems:(NSArray *)items + toPasteboard:(NSPasteboard *)pasteboard; +- (BOOL)outlineView:(NSOutlineView *)outlineView + acceptDrop:(id)info + item:(id)item + childIndex:(NSInteger)index; +- (void)outlineView:(NSOutlineView *)outlineView + draggingSession:(NSDraggingSession *)session + endedAtPoint:(NSPoint)screenPoint + operation:(NSDragOperation)operation; +- (void)outlineView:(NSOutlineView *)outlineView + draggingSession:(NSDraggingSession *)session + willBeginAtPoint:(NSPoint)screenPoint + forItems:(NSArray *)draggedItems; +- (NSDragOperation)outlineView:(NSOutlineView *)outlineView + validateDrop:(id)info + proposedItem:(id)item + proposedChildIndex:(NSInteger)index; + +@end + +@interface SWELL_ListView : NSTableView +{ + int m_leftmousemovecnt; + bool m_fakerightmouse; + @public + LONG style; + int ownermode_cnt; + int m_start_item; + int m_start_subitem; + int m_start_item_clickmode; + + int m_lbMode; + WDL_PtrList *m_items; + WDL_PtrList *m_cols; + WDL_PtrList *m_status_imagelist; + int m_status_imagelist_type; + int m_fastClickMask; + NSColor *m_fgColor; + NSMutableArray *m_selColors; + + // these are for the new yosemite mouse handling code + int m_last_plainly_clicked_item, m_last_shift_clicked_item; + + bool m_subitem_images; +} +-(LONG)getSwellStyle; +-(void)setSwellStyle:(LONG)st; +-(int)getSwellNotificationMode; +-(void)setSwellNotificationMode:(int)lbMode; +-(NSInteger)columnAtPoint:(NSPoint)pt; +-(int)getColumnPos:(int)idx; // get current position of column that was originally at idx +-(int)getColumnIdx:(int)pos; // get original index of column that is currently at position + +-(BOOL)accessibilityPerformShowMenu; +@end + +@interface SWELL_ImageButtonCell : NSButtonCell +{ +} +- (NSRect)drawTitle:(NSAttributedString *)title withFrame:(NSRect)frame inView:(NSView *)controlView; +@end + +@interface SWELL_ODButtonCell : NSButtonCell +{ +} +@end + +@interface SWELL_ODListViewCell : NSCell +{ + SWELL_ListView *m_ownctl; + int m_lastidx; +} +-(void)setOwnerControl:(SWELL_ListView *)t; +-(void)setItemIdx:(int)idx; +@end + +@interface SWELL_Button : NSButton +{ + void *m_swellGDIimage; + LONG_PTR m_userdata; + int m_radioflags; // =4096 if not a checkbox/radiobox. &2=new group, &1=radio +} +-(int)swellGetRadioFlags; +-(void)swellSetRadioFlags:(int)f; +-(LONG_PTR)getSwellUserData; +-(void)setSwellUserData:(LONG_PTR)val; +-(void)setSwellGDIImage:(void *)par; +-(void *)getSwellGDIImage; +@end + +@interface SWELL_TextView : NSTextView +{ + NSInteger m_tag; + bool m_disable_menu; +} +-(id)init; +-(NSInteger) tag; +-(void) setTag:(NSInteger)tag; +- (void)swellDisableContextMenu:(bool)dis; +- (bool)swellWantsContextMenu; +@end + +@interface SWELL_BoxView : NSBox +{ + NSInteger m_tag; +@public + int m_etch_mode; // if nonzero, SS_ETCHEDHORZ etc +} +-(NSInteger) tag; +-(void) setTag:(NSInteger)tag; +-(void) drawRect:(NSRect)r; +-(int) swellIsEtchBox; +@end + +@interface SWELL_FocusRectWnd : NSView +{ +} +@end + +@interface SWELL_ThreadTmp : NSObject +{ +@public + void *a, *b; +} +-(void)bla:(id)obj; +@end + + + +@interface SWELL_hwndChild : NSView // +{ +@public + int m_enabled; // -1 if preventing focus + DLGPROC m_dlgproc; + WNDPROC m_wndproc; + LONG_PTR m_userdata; + LONG_PTR m_extradata[32]; + NSInteger m_tag; + int m_isfakerightmouse; + char m_hashaddestroy; // 2 = WM_DESTROY has finished completely + HMENU m_menu; + BOOL m_flip; + bool m_supports_ddrop; + bool m_paintctx_used; + HDC m_paintctx_hdc; + WindowPropRec *m_props; + NSRect m_paintctx_rect; + BOOL m_isopaque; + char m_titlestr[1024]; + unsigned int m_create_windowflags; + NSOpenGLContext *m_glctx; + char m_isdirty; // &1=self needs redraw, &2=children may need redraw + char m_allow_nomiddleman; + id m_lastTopLevelOwner; // save a copy of the owner, if any + id m_access_cacheptrs[6]; + const char *m_classname; + +// only used if not SWELL_NO_METAL + char m_use_metal; // 1=normal mode, 2=full pipeline (GetDC() etc support). -1 is for non-metal async layered mode. -2 for non-metal non-async layered + + // metal state (if used) + char m_metal_dc_dirty; // used to track state during paint or getdc/releasedc. set to 1 if dirty, 2 if GetDC() but no write yet + char m_metal_gravity; // &1=resizing left, &2=resizing top + bool m_metal_retina; // last-retina-state, triggered to true by StretchBlt() with a 2:1 ratio + + bool m_metal_in_needref_list; + RECT m_metal_in_needref_rect; + NSRect m_metal_lastframe; + + id m_metal_texture; // id -- owned if in full pipeline mode, otherwise reference to m_metal_drawable + id m_metal_drawable; // id -- only used in normal mode + id m_metal_device; // id -- set to last-used-device + int m_metal_devicelist_updcnt; + +} +- (id)initChild:(SWELL_DialogResourceIndex *)resstate Parent:(NSView *)parent dlgProc:(DLGPROC)dlgproc Param:(LPARAM)par; +- (LRESULT)onSwellMessage:(UINT)msg p1:(WPARAM)wParam p2:(LPARAM)lParam; +-(HANDLE)swellExtendedDragOp:(id )sender retGlob:(BOOL)retG; +- (const char *)onSwellGetText; +-(void)onSwellSetText:(const char *)buf; +-(LONG)swellGetExtendedStyle; +-(void)swellSetExtendedStyle:(LONG)st; +-(HMENU)swellGetMenu; +-(BOOL)swellHasBeenDestroyed; +-(void)swellSetMenu:(HMENU)menu; +-(LONG_PTR)getSwellUserData; +-(void)setSwellUserData:(LONG_PTR)val; +-(void)setOpaque:(bool)isOpaque; +-(LPARAM)getSwellExtraData:(int)idx; +-(void)setSwellExtraData:(int)idx value:(LPARAM)val; +-(void)setSwellWindowProc:(WNDPROC)val; +-(WNDPROC)getSwellWindowProc; +-(void)setSwellDialogProc:(DLGPROC)val; +-(DLGPROC)getSwellDialogProc; + +- (NSArray*) namesOfPromisedFilesDroppedAtDestination:(NSURL*)droplocation; + +-(void) getSwellPaintInfo:(PAINTSTRUCT *)ps; +- (int)swellCapChangeNotify; +-(unsigned int)swellCreateWindowFlags; + +-(bool)swellCanPostMessage; +-(int)swellEnumProps:(PROPENUMPROCEX)proc lp:(LPARAM)lParam; +-(void *)swellGetProp:(const char *)name wantRemove:(BOOL)rem; +-(int)swellSetProp:(const char *)name value:(void *)val ; +-(NSOpenGLContext *)swellGetGLContext; +- (void) setEnabledSwellNoFocus; +- (void) setEnabled:(BOOL)en; +- (BOOL) isEnabled; +-(const char *)getSwellClass; + +// NSAccessibility + +// attribute methods +//- (NSArray *)accessibilityAttributeNames; +- (id)accessibilityAttributeValue:(NSString *)attribute; +//- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute; +//- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute; + +// parameterized attribute methods +//- (NSArray *)accessibilityParameterizedAttributeNames; +//- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter; + +// action methods +//- (NSArray *)accessibilityActionNames; +//- (NSString *)accessibilityActionDescription:(NSString *)action; +//- (void)accessibilityPerformAction:(NSString *)action; + +// Return YES if the UIElement doesn't show up to the outside world - i.e. its parent should return the UIElement's children as its own - cutting the UIElement out. E.g. NSControls are ignored when they are single-celled. +- (BOOL)accessibilityIsIgnored; + +// Returns the deepest descendant of the UIElement hierarchy that contains the point. You can assume the point has already been determined to lie within the receiver. Override this method to do deeper hit testing within a UIElement - e.g. a NSMatrix would test its cells. The point is bottom-left relative screen coordinates. +- (id)accessibilityHitTest:(NSPoint)point; + +// Returns the UI Element that has the focus. You can assume that the search for the focus has already been narrowed down to the reciever. Override this method to do a deeper search with a UIElement - e.g. a NSMatrix would determine if one of its cells has the focus. +- (id)accessibilityFocusedUIElement; + +-(void) swellOnControlDoubleClick:(id)sender; + +#ifdef MAC_OS_X_VERSION_10_8 +// for radio button with the OSX 10.8+ SDK, see comment in SWELL_MakeControl +-(void) onSwellCommand0:(id)sender; +-(void) onSwellCommand2:(id)sender; +-(void) onSwellCommand3:(id)sender; +-(void) onSwellCommand4:(id)sender; +-(void) onSwellCommand5:(id)sender; +-(void) onSwellCommand6:(id)sender; +-(void) onSwellCommand7:(id)sender; +#endif + +-(BOOL) swellWantsMetal; +-(void) swellDrawMetal:(const RECT *)forRect; +@end + +@interface SWELL_ModelessWindow : NSWindow +{ +@public + NSSize lastFrameSize; + id m_owner; + OwnedWindowListRec *m_ownedwnds; + BOOL m_enabled; + int m_wantraiseamt; + bool m_wantInitialKeyWindowOnShow; + bool m_lastZoom; + bool m_disableMonitorAutosize; +} +- (id)initModeless:(SWELL_DialogResourceIndex *)resstate Parent:(HWND)parent dlgProc:(DLGPROC)dlgproc Param:(LPARAM)par outputHwnd:(HWND *)hwndOut forceStyles:(unsigned int)smask; +- (id)initModelessForChild:(HWND)child owner:(HWND)owner styleMask:(unsigned int)smask; +- (void)keyDown:(NSEvent *)event; +- (void)swellDestroyAllOwnedWindows; +- (void)swellRemoveOwnedWindow:(NSWindow *)wnd; +- (void)swellAddOwnedWindow:(NSWindow*)wnd; +- (void)swellSetOwner:(id)owner; +- (id)swellGetOwner; +- (void **)swellGetOwnerWindowHead; +-(void)swellDoDestroyStuff; +-(void)swellResetOwnedWindowLevels; + +-(void)toggleFullScreen:(id)sender; +@end + +@interface SWELL_ModalDialog : NSPanel +{ + NSSize lastFrameSize; + id m_owner; + OwnedWindowListRec *m_ownedwnds; + + int m_rv; + bool m_hasrv; + BOOL m_enabled; + bool m_lastZoom; +} +- (id)initDialogBox:(SWELL_DialogResourceIndex *)resstate Parent:(HWND)parent dlgProc:(DLGPROC)dlgproc Param:(LPARAM)par; +- (void)swellDestroyAllOwnedWindows; +- (void)swellRemoveOwnedWindow:(NSWindow *)wnd; +- (void)swellAddOwnedWindow:(NSWindow*)wnd; +- (void)swellSetOwner:(id)owner; +- (id)swellGetOwner; +- (void **)swellGetOwnerWindowHead; +-(void)swellDoDestroyStuff; + +-(void)swellSetModalRetVal:(int)r; +-(int)swellGetModalRetVal; +-(bool)swellHasModalRetVal; +@end + +#ifndef SWELL_NO_METAL +void swell_removeMetalDirty(SWELL_hwndChild *slf); +void swell_updateAllMetalDirty(void); +void swell_addMetalDirty(SWELL_hwndChild *slf, const RECT *r, bool isReleaseDC=false); +HDC SWELL_CreateMetalDC(SWELL_hwndChild *); +#endif + + +@interface SWELL_hwndCarbonHost : SWELL_hwndChild +#ifdef MAC_OS_X_VERSION_10_7 + +#endif +{ +@public + NSWindow *m_cwnd; + + bool m_whileresizing; + void* m_wndhandler; // won't compile if declared EventHandlerRef, wtf + void* m_ctlhandler; // not sure if these need to be separate but cant hurt + bool m_wantallkeys; +} +-(BOOL)swellIsCarbonHostingView; +-(void)swellDoRepos; +@end + + +@interface SWELL_PopupMenuRecv : NSObject +{ + int m_act; + HWND cbwnd; +} +-(id) initWithWnd:(HWND)wnd; +-(void) onSwellCommand:(id)sender; +-(int) isCommand; +- (void)menuNeedsUpdate:(NSMenu *)menu; + +@end + +@interface SWELL_PopUpButton : NSPopUpButton +{ + LONG m_style; + LONG_PTR m_userdata; +} +-(id)init; +-(void)setSwellStyle:(LONG)style; +-(LONG)getSwellStyle; +-(LONG_PTR)getSwellUserData; +-(void)setSwellUserData:(LONG_PTR)val; +@end + +@interface SWELL_ComboBox : NSComboBox +{ +@public + LONG m_style; + LONG_PTR m_userdata; + WDL_PtrList *m_ids; + int m_ignore_selchg; // used to track the last set selection state, to avoid getting feedback notifications + bool m_disable_menu; +} +-(id)init; +-(void)dealloc; +-(void)setSwellStyle:(LONG)style; +-(LONG)getSwellStyle; +- (void)swellDisableContextMenu:(bool)dis; +- (NSMenu *)textView:(NSTextView *)view menu:(NSMenu *)menu forEvent:(NSEvent *)event atIndex:(NSUInteger)charIndex; +-(LONG_PTR)getSwellUserData; +-(void)setSwellUserData:(LONG_PTR)val; +@end + + + +// GDI internals + +#ifndef __AVAILABILITYMACROS__ +#error __AVAILABILITYMACROS__ not defined, include AvailabilityMacros.h! +#endif + +// 10.4 doesn't support CoreText, so allow ATSUI if targetting 10.4 SDK +#ifndef MAC_OS_X_VERSION_10_5 + // 10.4 SDK + #define SWELL_NO_CORETEXT + #define SWELL_ATSUI_TEXT_SUPPORT +#elif !defined(__LP64__) + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + #ifndef MAC_OS_X_VERSION_10_9 // not sure when ATSUI was dropped completely, definitely gone in 10.13! + #define SWELL_ATSUI_TEXT_SUPPORT + #endif + #endif +#endif + +#ifndef MAC_OS_X_VERSION_10_11 + // metal requires a recent SDK + #define SWELL_NO_METAL +#endif + +struct HGDIOBJ__ +{ + int type; + + int additional_refcnt; // refcnt of 0 means one owner (if >0, additional owners) + + // used by pen/brush + CGColorRef color; + int wid; + int color_int; + NSImage *bitmapptr; + + NSMutableDictionary *__old_fontdict; // unused, for ABI compat + // + // if ATSUI used, meaning IsCoreTextSupported() returned false + ATSUStyle atsui_font_style; + + float font_rotation; + + bool _infreelist; + struct HGDIOBJ__ *_next; + + // if using CoreText to draw text + void *ct_FontRef; +}; + +struct HDC__ { + CGContextRef ctx; + void *ownedData; // always use via SWELL_GetContextFrameBuffer() (which performs necessary alignment) + void *metal_ctx; // SWELL_hwndChild, only used if not SWELL_NO_METAL + HGDIOBJ__ *curpen; + HGDIOBJ__ *curbrush; + HGDIOBJ__ *curfont; + + NSColor *__old_nstextcol; // provided for ABI compat, but unused + int cur_text_color_int; // text color as int + + int curbkcol; + int curbkmode; + float lastpos_x,lastpos_y; + + void *GLgfxctx; // optionally set + bool _infreelist; + struct HDC__ *_next; + + CGColorRef curtextcol; // text color as CGColor +}; + + + + + +// some extras so we can call functions available only on some OSX versions without warnings, and with the correct types +#define SWELL_DelegateExtensions __SWELL_PREFIX_CLASSNAME(_delext) +#define SWELL_ViewExtensions __SWELL_PREFIX_CLASSNAME(_viewext) +#define SWELL_AppExtensions __SWELL_PREFIX_CLASSNAME(_appext) +#define SWELL_WindowExtensions __SWELL_PREFIX_CLASSNAME(_wndext) +#define SWELL_TableColumnExtensions __SWELL_PREFIX_CLASSNAME(_tcolext) + +@interface SWELL_WindowExtensions : NSWindow +-(void)setCollectionBehavior:(NSUInteger)a; +@end +@interface SWELL_ViewExtensions : NSView +-(void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)vr rectIsVisibleRectForView:(NSView*)v topView:(NSView *)v2; +@end + +@interface SWELL_DelegateExtensions : NSObject +-(bool)swellPostMessage:(HWND)dest msg:(int)message wp:(WPARAM)wParam lp:(LPARAM)lParam; +-(void)swellPostMessageClearQ:(HWND)dest; +-(void)swellPostMessageTick:(id)sender; +@end + +@interface SWELL_AppExtensions : NSApplication +-(NSUInteger)presentationOptions; +-(void)setPresentationOptions:(NSUInteger)o; +@end +@interface SWELL_TableColumnExtensions : NSTableColumn +-(BOOL)isHidden; +-(void)setHidden:(BOOL)h; +@end + + + + +#else + // compat when compiling targetting OSX but not in objectiveC mode + struct SWELL_DataHold; +#endif // !__OBJC__ + +// 10.4 sdk just uses "float" +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 + #ifdef __LP64__ + typedef double CGFloat; + #else + typedef float CGFloat; +#endif + +#endif + + +#define NSPOINT_TO_INTS(pt) (int)floor((pt).x+0.5), (int)floor((pt).y+0.5) + +#ifdef __OBJC__ +static WDL_STATICFUNC_UNUSED void NSPOINT_TO_POINT(POINT *p, const NSPoint &pt) +{ + p->x = (int)floor(pt.x+0.5); + p->y = (int)floor((pt).y+0.5); +} +static WDL_STATICFUNC_UNUSED void NSRECT_TO_RECT(RECT *r, const NSRect &tr) +{ + r->left=(int)floor(tr.origin.x+0.5); + r->right=(int)floor(tr.origin.x+tr.size.width+0.5); + r->top=(int)floor(tr.origin.y+0.5); + r->bottom=(int)floor(tr.origin.y+tr.size.height+0.5); +} +#endif + + +#ifdef SWELL_IMPLEMENT_GETOSXVERSION + +SWELL_IMPLEMENT_GETOSXVERSION int SWELL_GetOSXVersion() +{ + static SInt32 v; + if (!v) + { + if (NSAppKitVersionNumber >= 1266.0) + { + if (NSAppKitVersionNumber >= 2487.0) + v = 0x1400; + else if (NSAppKitVersionNumber >= 2299.0) + v = 0x1300; + else if (NSAppKitVersionNumber >= 2100.0) + v = 0x1200; + else if (NSAppKitVersionNumber >= 2022.0) + v = 0x1100; + else if (NSAppKitVersionNumber >= 1894.0) + v = 0x10e0; + else if (NSAppKitVersionNumber >= 1639.10) + v = 0x10d0; + else if (NSAppKitVersionNumber >= 1560) + v = 0x10c0; + else if (NSAppKitVersionNumber >= 1404.0) + v = 0x10b0; + else + v = 0x10a0; // 10.10+ Gestalt(gsv) return 0x109x, so we bump this to 0x10a0 + } + else + { + SInt32 a = 0x1040; + Gestalt(gestaltSystemVersion,&a); + v=a; + } + } + return v; +} + +#endif + + +#elif defined(SWELL_TARGET_GDK) + + +#ifdef SWELL_SUPPORT_GTK +#include +#endif + +#include +#include +#include + + +#else +// generic + +#endif // end generic + +struct HTREEITEM__ +{ + HTREEITEM__(); + ~HTREEITEM__(); + bool FindItem(HTREEITEM it, HTREEITEM__ **parOut, int *idxOut); + +#ifdef SWELL_TARGET_OSX + SWELL_DataHold *m_dh; +#else + int m_state; // TVIS_EXPANDED, for ex +#endif + + bool m_haschildren; + char *m_value; + WDL_PtrList m_children; // only used in tree mode + LPARAM m_param; +}; + + +#ifndef SWELL_TARGET_OSX + +#include "../wdlstring.h" + +#ifdef SWELL_LICE_GDI +#include "../lice/lice.h" +#endif +#include "../assocarray.h" + +LRESULT SwellDialogDefaultWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + +#ifdef SWELL_TARGET_GDK +typedef GdkWindow *SWELL_OSWINDOW; +#else +typedef void *SWELL_OSWINDOW; // maps to the HWND__ itself on visible, non-GDK, top level windows +#endif + +struct HWND__ +{ + HWND__(HWND par, int wID=0, const RECT *wndr=NULL, const char *label=NULL, bool visible=false, WNDPROC wndproc=NULL, DLGPROC dlgproc=NULL, HWND ownerWindow=NULL); + ~HWND__(); // DO NOT USE!!! We would make this private but it breaks PtrList using it on gcc. + + // using this API prevents the HWND from being valid -- it'll still get its resources destroyed via DestroyWindow() though. + // DestroyWindow() does cleanup, then the final Release(). + void Retain() { m_refcnt++; } + void Release() { if (!--m_refcnt) delete this; } + + const char *m_classname; + + SWELL_OSWINDOW m_oswindow; + + WDL_FastString m_title; + + HWND__ *m_children, *m_parent, *m_next, *m_prev; + HWND__ *m_owner, *m_owned_list, *m_owned_next, *m_owned_prev; + HWND__ *m_focused_child; // only valid if hwnd itself is in focus chain, and must be validated before accessed + RECT m_position; + UINT m_id; + int m_style, m_exstyle; + INT_PTR m_userdata; + WNDPROC m_wndproc; + DLGPROC m_dlgproc; + INT_PTR m_extra[64]; + INT_PTR m_private_data; // used by internal controls + + bool m_visible; + char m_hashaddestroy; // 1 in destroy, 2 full destroy + bool m_enabled; + bool m_wantfocus; + + bool m_israised; + bool m_has_had_position; + bool m_is_maximized; // only valid if m_oswindow is set + int m_oswindow_fullscreen; // may contain preserved style flags + + int m_refcnt; + int m_oswindow_private; // private state for generic-gtk or whatever + + HMENU m_menu; + HFONT m_font; + + WDL_StringKeyedArray m_props; + +#ifdef SWELL_LICE_GDI + void *m_paintctx; // temporarily set for calls to WM_PAINT + + bool m_child_invalidated; // if a child is invalidated + bool m_invalidated; // set to true on direct invalidate. todo RECT instead? + + LICE_IBitmap *m_backingstore; // if NULL, unused (probably only should use on top level windows, but support caching?) +#endif +}; + +struct HMENU__ +{ + HMENU__() { m_refcnt=1; sel_vis = -1; } + + void Retain() { m_refcnt++; } + void Release() { if (!--m_refcnt) delete this; } + + WDL_PtrList items; + int sel_vis; // for mouse/keyboard nav + int m_refcnt; + + HMENU__ *Duplicate(); + static void freeMenuItem(void *p); + +private: + ~HMENU__() { items.Empty(true,freeMenuItem); } +}; + + +struct HGDIOBJ__ +{ + int type; + int additional_refcnt; // refcnt of 0 means one owner (if >0, additional owners) + + int color; + int wid; + + float alpha; + + struct HGDIOBJ__ *_next; + bool _infreelist; + void *typedata; // font: FT_Face, bitmap: LICE_IBitmap +}; + + +struct HDC__ { +#ifdef SWELL_LICE_GDI + LICE_IBitmap *surface; // owned by context. can be (and usually is, if clipping is desired) LICE_SubBitmap + POINT surface_offs; // offset drawing into surface by this amount + + RECT dirty_rect; // in surface coordinates, used for GetWindowDC()/GetDC()/etc + bool dirty_rect_valid; +#else + void *ownedData; // for mem contexts, support a null rendering +#endif + + HGDIOBJ__ *curpen; + HGDIOBJ__ *curbrush; + HGDIOBJ__ *curfont; + + int cur_text_color_int; // text color as int + + int curbkcol; + int curbkmode; + float lastpos_x,lastpos_y; + + struct HDC__ *_next; + bool _infreelist; +}; + +HWND DialogBoxIsActive(void); +bool DestroyPopupMenus(void); +bool PopupMenuIsActive(void); +HWND ChildWindowFromPoint(HWND h, POINT p); +HWND GetFocusIncludeMenus(); + +void SWELL_RunEvents(); + +bool swell_isOSwindowmenu(SWELL_OSWINDOW osw); + +void swell_on_toplevel_raise(SWELL_OSWINDOW wnd); // called by swell-generic-gdk when a window is focused + +HWND swell_oswindow_to_hwnd(SWELL_OSWINDOW w); +SWELL_OSWINDOW swell_oswindow_from_hwnd(HWND hwnd); +void swell_oswindow_focus(HWND hwnd); +void swell_oswindow_update_style(HWND hwnd, LONG oldstyle); +void swell_oswindow_update_enable(HWND hwnd); +void swell_oswindow_update_text(HWND hwnd); +void swell_oswindow_begin_resize(SWELL_OSWINDOW wnd); +void swell_oswindow_resize(SWELL_OSWINDOW wnd, int reposflag, RECT f); +void swell_oswindow_postresize(HWND hwnd, RECT f); +void swell_oswindow_invalidate(HWND hwnd, const RECT *r); +void swell_oswindow_destroy(HWND hwnd); +void swell_oswindow_manage(HWND hwnd, bool wantfocus); +void swell_oswindow_maximize(HWND, bool wantmax); // false=restore +void swell_oswindow_updatetoscreen(HWND hwnd, RECT *rect); +HWND swell_window_wants_all_input(); // window with an active drag of menubar will have this set, to route all mouse events to nonclient area of window +int swell_delegate_menu_message(HWND src, LPARAM lParam, int msg, bool screencoords); // menubar/menus delegate to submenus during drag. + +void swell_dlg_destroyspare(); + +extern bool swell_is_likely_capslock; // only used when processing dit events for a-zA-Z +extern const char *g_swell_appname; +extern SWELL_OSWINDOW SWELL_focused_oswindow; // top level window which has focus (might not map to a HWND__!) +extern HWND swell_captured_window; +extern HWND SWELL_topwindows; // front of list = most recently active + +int swell_is_app_inactive(); // returns >0 if definitely inactive, -1 if maybe + +#ifdef _DEBUG +void VALIDATE_HWND_LIST(HWND list, HWND par); +#else +#define VALIDATE_HWND_LIST(list, par) do { } while (0) +#endif + + +#endif // !OSX + +HDC SWELL_CreateGfxContext(void *); + +// GDP internals +#define TYPE_PEN 1 +#define TYPE_BRUSH 2 +#define TYPE_FONT 3 +#define TYPE_BITMAP 4 + +typedef struct +{ + void *instptr; +#ifdef __APPLE__ + void *bundleinstptr; +#endif + int refcnt; + +#ifndef SWELL_EXTRA_MINIMAL + int (*SWELL_dllMain)(HINSTANCE, DWORD,LPVOID); //last parm=SWELLAPI_GetFunc + BOOL (*dllMain)(HINSTANCE, DWORD, LPVOID); +#endif + void *lastSymbolRequested; +} SWELL_HINSTANCE; + + +enum +{ + INTERNAL_OBJECT_START= 0x1000001, + INTERNAL_OBJECT_THREAD, + INTERNAL_OBJECT_EVENT, + INTERNAL_OBJECT_FILE, + INTERNAL_OBJECT_EXTERNALSOCKET, // socket not owned by us + INTERNAL_OBJECT_SOCKETEVENT, + INTERNAL_OBJECT_NSTASK, + INTERNAL_OBJECT_PID, + INTERNAL_OBJECT_END +}; + +typedef struct +{ + int type; // INTERNAL_OBJECT_* + int count; // reference count +} SWELL_InternalObjectHeader; + +typedef struct +{ + SWELL_InternalObjectHeader hdr; + DWORD (*threadProc)(LPVOID); + void *threadParm; + pthread_t pt; + DWORD retv; + bool done; +} SWELL_InternalObjectHeader_Thread; + +typedef struct +{ + SWELL_InternalObjectHeader hdr; + + pthread_mutex_t mutex; + pthread_cond_t cond; + + bool isSignal; + bool isManualReset; + +} SWELL_InternalObjectHeader_Event; + + +// used for both INTERNAL_OBJECT_EXTERNALSOCKET and INTERNAL_OBJECT_SOCKETEVENT. +// if EXTERNALSOCKET, socket[1] ignored and autoReset ignored. +typedef struct +{ + SWELL_InternalObjectHeader hdr; + int socket[2]; + bool autoReset; +} SWELL_InternalObjectHeader_SocketEvent; + +typedef struct +{ + SWELL_InternalObjectHeader hdr; + + FILE *fp; +} SWELL_InternalObjectHeader_File; + +typedef struct +{ + SWELL_InternalObjectHeader hdr; + void *task; +} SWELL_InternalObjectHeader_NSTask; + +typedef struct +{ + SWELL_InternalObjectHeader hdr; + int pid; + int done, result; +} SWELL_InternalObjectHeader_PID; + +bool IsRightClickEmulateEnabled(); + +#ifdef SWELL_INTERNAL_HTREEITEM_IMPL + +HTREEITEM__::HTREEITEM__() +{ + m_param=0; + m_value=0; + m_haschildren=false; +#ifdef SWELL_TARGET_OSX + m_dh = [[SWELL_DataHold alloc] initWithVal:this]; +#else + m_state=0; +#endif +} +HTREEITEM__::~HTREEITEM__() +{ + free(m_value); + m_children.Empty(true); +#ifdef SWELL_TARGET_OSX + if (m_dh) + { + m_dh->m_data = NULL; + [m_dh release]; + } +#endif +} + + +bool HTREEITEM__::FindItem(HTREEITEM it, HTREEITEM__ **parOut, int *idxOut) +{ + int a=m_children.Find((HTREEITEM__*)it); + if (a>=0) + { + if (parOut) *parOut=this; + if (idxOut) *idxOut=a; + return true; + } + int x; + const int n=m_children.GetSize(); + for (x = 0; x < n; x ++) + { + if (m_children.Get(x)->FindItem(it,parOut,idxOut)) return true; + } + return false; +} + +#endif + +#ifdef SWELL_INTERNAL_MERGESORT_IMPL + +static int __listview_sortfunc(void *p1, void *p2, int (*compar)(LPARAM val1, LPARAM val2, LPARAM userval), LPARAM userval) +{ + SWELL_ListView_Row *a = *(SWELL_ListView_Row **)p1; + SWELL_ListView_Row *b = *(SWELL_ListView_Row **)p2; + return compar(a->m_param,b->m_param,userval); +} + + +static void __listview_mergesort_internal(void *base, size_t nmemb, size_t size, + int (*compar)(LPARAM val1, LPARAM val2, LPARAM userval), + LPARAM parm, + char *tmpspace) +{ + char *b1,*b2; + size_t n1, n2; + + if (nmemb < 2) return; + + n1 = nmemb / 2; + b1 = (char *) base; + n2 = nmemb - n1; + b2 = b1 + (n1 * size); + + if (nmemb>2) + { + __listview_mergesort_internal(b1, n1, size, compar, parm, tmpspace); + __listview_mergesort_internal(b2, n2, size, compar, parm, tmpspace); + } + + char *p = tmpspace; + + do + { + if (__listview_sortfunc(b1, b2, compar,parm) > 0) + { + memcpy(p, b2, size); + b2 += size; + n2--; + } + else + { + memcpy(p, b1, size); + b1 += size; + n1--; + } + p += size; + } + while (n1 > 0 && n2 > 0); + + if (n1 > 0) memcpy(p, b1, n1 * size); + memcpy(base, tmpspace, (nmemb - n2) * size); +} + + +#endif + +#ifndef SWELL_TARGET_OSX + +#define SWELL_GENERIC_THEMESIZEDEFS(f,fd) \ + f(default_font_size, 12) \ + f(menubar_height, 17) \ + f(menubar_font_size, 13) \ + f(menubar_spacing_width, 8) \ + f(menubar_margin_width, 6) \ + f(scrollbar_width, 14) \ + f(smscrollbar_width, 16) \ + f(scrollbar_min_thumb_height, 4) \ + f(combo_height, 20) \ + + +#define SWELL_GENERIC_THEMEDEFS(f,fd) \ + SWELL_GENERIC_THEMESIZEDEFS(f,fd) \ + f(_3dface,RGB(192,192,192)) \ + f(_3dshadow,RGB(96,96,96)) \ + f(_3dhilight,RGB(224,224,224)) \ + f(_3ddkshadow,RGB(48,48,48)) \ + fd(button_bg,RGB(192,192,192),_3dface) \ + f(button_text,RGB(0,0,0)) \ + f(button_text_disabled, RGB(128,128,128)) \ + fd(button_shadow, RGB(96,96,96), _3dshadow) \ + fd(button_hilight, RGB(224,224,224), _3dhilight) \ + f(checkbox_text,RGB(0,0,0)) \ + f(checkbox_text_disabled, RGB(128,128,128)) \ + f(checkbox_fg, RGB(0,0,0)) \ + f(checkbox_inter, RGB(192,192,192)) \ + f(checkbox_bg, RGB(255,255,255)) \ + f(scrollbar,RGB(32,32,32)) \ + f(scrollbar_fg, RGB(160,160,160)) \ + f(scrollbar_bg, RGB(224,224,224)) \ + f(edit_cursor,RGB(0,128,255)) \ + f(edit_bg,RGB(255,255,255)) \ + f(edit_bg_disabled,RGB(224,224,224)) \ + f(edit_text,RGB(0,0,0)) \ + f(edit_text_disabled, RGB(128,128,128)) \ + f(edit_bg_sel,RGB(128,192,255)) \ + f(edit_text_sel,RGB(255,255,255)) \ + fd(edit_hilight, RGB(224,224,224), _3dhilight) \ + fd(edit_shadow, RGB(96,96,96), _3dshadow) \ + f(info_bk,RGB(255,240,200)) \ + f(info_text,RGB(0,0,0)) \ + fd(menu_bg, RGB(192,192,192), _3dface) \ + fd(menu_shadow, RGB(96,96,96), _3dshadow) \ + fd(menu_hilight, RGB(224,224,224), _3dhilight) \ + fd(menu_text, RGB(0,0,0), button_text) \ + fd(menu_text_disabled, RGB(224,224,224), _3dhilight) \ + fd(menu_bg_sel, RGB(0,0,0), menu_text) \ + fd(menu_text_sel, RGB(224,224,224), menu_bg) \ + f(menu_scroll, RGB(64,64,64)) \ + fd(menu_scroll_arrow, RGB(96,96,96), _3dshadow) \ + fd(menu_submenu_arrow, RGB(96,96,96), menu_text) \ + fd(menu_submenu_arrow_sel, RGB(96,96,96), menu_bg) \ + fd(menubar_bg, RGB(192,192,192), menu_bg) \ + fd(menubar_bg_inactive, RGB(192,192,192), menubar_bg) \ + fd(menubar_text, RGB(0,0,0), menu_text) \ + fd(menubar_text_inactive, RGB(0,0,0), menubar_text) \ + fd(menubar_text_disabled, RGB(224,224,224), menu_text_disabled) \ + fd(menubar_bg_sel, RGB(0,0,0), menu_bg_sel) \ + fd(menubar_text_sel, RGB(224,224,224), menu_text_sel) \ + f(trackbar_track, RGB(224,224,224)) \ + f(trackbar_mark, RGB(96,96,96)) \ + f(trackbar_knob, RGB(48,48,48)) \ + f(progress,RGB(0,128,255)) \ + fd(label_text, RGB(0,0,0), button_text) \ + fd(label_text_disabled, RGB(128,128,128), button_text_disabled) \ + fd(combo_text, RGB(0,0,0), button_text) \ + fd(combo_text_disabled, RGB(128,128,128), button_text_disabled) \ + fd(combo_bg, RGB(192,192,192), _3dface) \ + f(combo_bg2, RGB(255,255,255)) \ + fd(combo_shadow, RGB(96,96,96), _3dshadow) \ + fd(combo_hilight, RGB(224,224,224), _3dhilight) \ + fd(combo_arrow, RGB(96,96,96), _3dshadow) \ + fd(combo_arrow_press, RGB(224,224,224), _3dhilight) \ + f(listview_bg, RGB(255,255,255)) \ + f(listview_bg_sel, RGB(128,128, 255)) \ + f(listview_text, RGB(0,0,0)) \ + fd(listview_text_sel, RGB(255,255,255), listview_text) \ + fd(listview_bg_sel_inactive, RGB(200,200,200), listview_bg_sel) \ + fd(listview_text_sel_inactive, RGB(0,0,0), listview_text_sel) \ + fd(listview_grid, RGB(224,224,224), _3dhilight) \ + f(listview_hdr_arrow,RGB(96,96,96)) \ + fd(listview_shadow, RGB(96,96,96), _3dshadow) \ + fd(listview_hilight, RGB(224,224,224), _3dhilight) \ + fd(listview_hdr_shadow, RGB(96,96,96), _3dshadow) \ + fd(listview_hdr_hilight, RGB(224,224,224), _3dhilight) \ + fd(listview_hdr_bg, RGB(192,192,192), _3dface) \ + fd(listview_hdr_text, RGB(0,0,0), button_text) \ + f(treeview_text,RGB( 0,0,0)) \ + f(treeview_bg, RGB(255,255,255)) \ + f(treeview_bg_sel, RGB(128,128,255)) \ + f(treeview_text_sel, RGB(255,255,255)) \ + fd(treeview_bg_sel_inactive, RGB(200,200,200), treeview_bg_sel) \ + fd(treeview_text_sel_inactive, RGB(0,0,0), treeview_text_sel) \ + f(treeview_arrow, RGB(96,96,96)) \ + fd(treeview_shadow, RGB(96,96,96), _3dshadow) \ + fd(treeview_hilight, RGB(224,224,224), _3dhilight) \ + fd(tab_shadow, RGB(96,96,96), _3dshadow) \ + fd(tab_hilight, RGB(224,224,224), _3dhilight) \ + fd(tab_text, RGB(0,0,0), button_text) \ + f(focusrect,RGB(255,0,0)) \ + f(group_text,RGB(0,0,0)) \ + fd(group_shadow, RGB(96,96,96), _3dshadow) \ + fd(group_hilight, RGB(224,224,224), _3dhilight) \ + f(focus_hilight, RGB(140,190,233)) \ + + + +struct swell_colortheme { +#define __def_theme_ent(x,c) int x; +#define __def_theme_ent_fb(x,c,fb) int x; +SWELL_GENERIC_THEMEDEFS(__def_theme_ent,__def_theme_ent_fb) +#undef __def_theme_ent +#undef __def_theme_ent_fb +}; + +#define SWELL_UI_SCALE(x) (((x)*g_swell_ui_scale)/256) +void swell_scaling_init(bool no_auto_hidpi); +extern int g_swell_ui_scale; +extern swell_colortheme g_swell_ctheme; +extern const char *g_swell_deffont_face; +HFONT SWELL_GetDefaultFont(void); + +#endif + +extern void (*SWELL_DDrop_onDragLeave)(); +extern void (*SWELL_DDrop_onDragOver)(POINT pt); +extern void (*SWELL_DDrop_onDragEnter)(void *hGlobal, POINT pt); +extern const char* (*SWELL_DDrop_getDroppedFileTargetPath)(const char* extension); + +static WDL_STATICFUNC_UNUSED int ext_valid_for_extlist(const char *thisext, const char *extlist) +{ + if (!thisext || *thisext != '.' || !extlist) return -1; + int txlen = (int)strlen(thisext), witem = 0; + while (*extlist) + { + while (*extlist) extlist++; // description + extlist++; + + while (*extlist) + { + while (*extlist == ' ' || *extlist == ';') extlist++; + if (*extlist == '*') + { + if (!strnicmp(extlist+1,thisext,txlen) && + (extlist[1+txlen] == ';' ||extlist[1+txlen] == 0)) + return witem; + } + while (*extlist && *extlist != ';') extlist++; + if (*extlist) extlist++; + } + + while (*extlist) extlist++; + extlist++; + witem++; + } + return -1; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-kb-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-kb-generic.cpp new file mode 100644 index 0000000..822b4d2 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-kb-generic.cpp @@ -0,0 +1,26 @@ +/* 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. + + + This file provides basic key and mouse cursor querying, as well as a key to windows key translation function. + + */ + + +// everything has moved to swell-generic-*.cpp diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-kb.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-kb.mm new file mode 100644 index 0000000..759d4a4 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-kb.mm @@ -0,0 +1,693 @@ +/* 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. + + + This file provides basic key and mouse cursor querying, as well as a mac key to windows key translation function. + + */ + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-dlggen.h" +#include "../wdltypes.h" +#import +#import + + + +static int MacKeyCodeToVK(int code, int *flag) +{ + switch (code) + { + case 51: return VK_BACK; + case 65: return VK_DECIMAL; + case 67: return VK_MULTIPLY; + case 69: return VK_ADD; + case 71: return VK_NUMLOCK; + case 75: return VK_DIVIDE; + case 76: *flag |= 1<<24; return VK_RETURN; + case 78: return VK_SUBTRACT; + case 81: return VK_SEPARATOR; + case 82: return VK_NUMPAD0; + case 83: return VK_NUMPAD1; + case 84: return VK_NUMPAD2; + case 85: return VK_NUMPAD3; + case 86: return VK_NUMPAD4; + case 87: return VK_NUMPAD5; + case 88: return VK_NUMPAD6; + case 89: return VK_NUMPAD7; + case 91: return VK_NUMPAD8; + case 92: return VK_NUMPAD9; + case 96: return VK_F5; + case 97: return VK_F6; + case 98: return VK_F7; + case 99: return VK_F3; + case 100: return VK_F8; + case 101: return VK_F9; + case 109: return VK_F10; + case 103: return VK_F11; + case 111: return VK_F12; + case 114: *flag |= 1<<24; return VK_INSERT; + case 115: *flag |= 1<<24; return VK_HOME; + case 117: *flag |= 1<<24; return VK_DELETE; + case 116: *flag |= 1<<24; return VK_PRIOR; + case 118: return VK_F4; + case 119: *flag |= 1<<24; return VK_END; + case 120: return VK_F2; + case 121: *flag |= 1<<24; return VK_NEXT; + case 122: return VK_F1; + case 123: *flag |= 1<<24; return VK_LEFT; + case 124: *flag |= 1<<24; return VK_RIGHT; + case 125: *flag |= 1<<24; return VK_DOWN; + case 126: *flag |= 1<<24; return VK_UP; + case 0x69: return VK_F13; + case 0x6B: return VK_F14; + case 0x71: return VK_F15; + case 0x6A: return VK_F16; + } + return 0; +} + +bool IsRightClickEmulateEnabled(); + +#ifdef MAC_OS_X_VERSION_10_5 + +static int charFromVcode(int keyCode) // used for getting the root char (^, `) from dead keys on other keyboards, + // only used when using MacKeyToWindowsKeyEx() with mode=1, for now +{ + static char loaded; + static TISInputSourceRef (*_TISCopyCurrentKeyboardInputSource)( void); + static void* (*_TISGetInputSourceProperty) ( TISInputSourceRef inputSource, CFStringRef propertyKey); + + if (!loaded) + { + loaded++; + CFBundleRef b = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Carbon")); + if (b) + { + *(void **)&_TISGetInputSourceProperty = CFBundleGetFunctionPointerForName(b,CFSTR("TISGetInputSourceProperty")); + *(void **)&_TISCopyCurrentKeyboardInputSource = CFBundleGetFunctionPointerForName(b,CFSTR("TISCopyCurrentKeyboardInputSource")); + } + } + if (!_TISCopyCurrentKeyboardInputSource || !_TISGetInputSourceProperty) return 0; + + TISInputSourceRef currentKeyboard = _TISCopyCurrentKeyboardInputSource(); + CFDataRef uchr = (CFDataRef)_TISGetInputSourceProperty(currentKeyboard, CFSTR("TISPropertyUnicodeKeyLayoutData")); + const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); + + if(keyboardLayout) + { + UInt32 deadKeyState = 0; + UniCharCount maxStringLength = 255; + UniCharCount actualStringLength = 0; + UniChar unicodeString[maxStringLength]; + + OSStatus status = UCKeyTranslate(keyboardLayout, + keyCode, kUCKeyActionDown, 0, + LMGetKbdType(), 0, + &deadKeyState, + maxStringLength, + &actualStringLength, unicodeString); + + if (actualStringLength == 0 && deadKeyState) + { + status = UCKeyTranslate(keyboardLayout, + kVK_Space, kUCKeyActionDown, 0, + LMGetKbdType(), 0, + &deadKeyState, + maxStringLength, + &actualStringLength, unicodeString); + } + if(actualStringLength > 0 && status == noErr) return unicodeString[0]; + } + return 0; +} +#endif + +int SWELL_MacKeyToWindowsKeyEx(void *nsevent, int *flags, int mode) +{ + NSEvent *theEvent = (NSEvent *)nsevent; + if (!theEvent) theEvent = [NSApp currentEvent]; + + const NSInteger mod=[theEvent modifierFlags]; + + int flag=0; + if (mod & NSShiftKeyMask) flag|=FSHIFT; + if (mod & NSCommandKeyMask) flag|=FCONTROL; // todo: this should be command once we figure it out + if (mod & NSAlternateKeyMask) flag|=FALT; + if ((mod&NSControlKeyMask) && !IsRightClickEmulateEnabled()) flag|=FLWIN; + + int rawcode=[theEvent keyCode]; + + int code=MacKeyCodeToVK(rawcode,&flag); + if (!code) + { + NSString *str=NULL; + if (mode == 1) str=[theEvent characters]; + + if (!str || ![str length]) str=[theEvent charactersIgnoringModifiers]; + + if (!str || ![str length]) + { + #ifdef MAC_OS_X_VERSION_10_5 + if (mode==1) code=charFromVcode(rawcode); + #endif + if (!code) + { + code = 1024+rawcode; // raw code + flag|=FVIRTKEY; + } + } + else + { + code=[str characterAtIndex:0]; + if (code >= NSF1FunctionKey && code <= NSF24FunctionKey) + { + flag|=FVIRTKEY; + code += VK_F1 - NSF1FunctionKey; + } + else + { + if (code >= 'a' && code <= 'z') code+='A'-'a'; + if (code == 25 && (flag&FSHIFT)) code=VK_TAB; + if (isalnum(code)||code==' ' || code == '\r' || code == '\n' || code ==27 || code == VK_TAB) flag|=FVIRTKEY; + } + } + } + else + { + flag|=FVIRTKEY; + if (code==8) code='\b'; + } + + if (!(flag&FVIRTKEY)) flag&=~FSHIFT; + + if (flags) *flags=flag; + return code; +} + +int SWELL_MacKeyToWindowsKey(void *nsevent, int *flags) +{ + if (!nsevent) return 0; + return SWELL_MacKeyToWindowsKeyEx(nsevent,flags,0); +} + +int SWELL_KeyToASCII(int wParam, int lParam, int *newflags) +{ + if (wParam >= '0' && wParam <= '9' && lParam == (FSHIFT|FVIRTKEY)) + { + *newflags = lParam&~(FSHIFT|FVIRTKEY); + if (!(lParam & (FCONTROL|FLWIN))) switch (wParam) + { + case '1': return '!'; + case '2': return '@'; + case '3': return '#'; + case '4': return '$'; + case '5': return '%'; + case '6': return '^'; + case '7': return '&'; + case '8': return '*'; + case '9': return '('; + case '0': return ')'; + } + } + return 0; +} + + +WORD GetAsyncKeyState(int key) +{ + CGEventFlags state=0; + if (key == VK_LBUTTON || key == VK_RBUTTON || key == VK_MBUTTON) + { + state=GetCurrentEventButtonState(); + } + else + { + state=CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState); + } + + if ((key == VK_LBUTTON && (state&1)) || + (key == VK_RBUTTON && (state&2)) || + (key == VK_MBUTTON && (state&4)) || + (key == VK_SHIFT && (state&kCGEventFlagMaskShift)) || + (key == VK_CONTROL && (state&kCGEventFlagMaskCommand)) || + (key == VK_MENU && (state&kCGEventFlagMaskAlternate)) || + (key == VK_LWIN && !IsRightClickEmulateEnabled() && (state&kCGEventFlagMaskControl))) + { + return 0x8000; + } + + return 0; +} + + +static SWELL_CursorResourceIndex *SWELL_curmodule_cursorresource_head; + +static NSCursor* MakeCursorFromData(unsigned char* data, int hotspot_x, int hotspot_y) +{ + NSCursor *c=NULL; + NSBitmapImageRep* bmp = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:0 + pixelsWide:16 + pixelsHigh:16 + bitsPerSample:8 + samplesPerPixel:2 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedWhiteColorSpace + bytesPerRow:(16*2) + bitsPerPixel:16]; + + if (bmp) + { + unsigned char* p = [bmp bitmapData]; + if (p) + { + int i; + for (i = 0; i < 16*16; ++i) + { + // tried 4 bits per sample and memcpy, didn't work + p[2*i] = (data[i]&0xF0) | data[i]>>4; + p[2*i+1] = (data[i]<<4) | (data[i]&0xf); + } + + NSImage *img = [[NSImage alloc] init]; + if (img) + { + [img addRepresentation:bmp]; + NSPoint hs = NSMakePoint(hotspot_x, hotspot_y); + c = [[NSCursor alloc] initWithImage:img hotSpot:hs]; + [img release]; + } + } + [bmp release]; + } + return c; +} + +static NSCursor* MakeSWELLSystemCursor(const char *id) +{ + // bytemaps are (white<<4)|(alpha) + const unsigned char B = 0xF; + const unsigned char W = 0xFF; + const unsigned char G = 0xF8; + + static NSCursor* carr[4] = { 0, 0, 0, 0 }; + + NSCursor** pc=0; + if (id == IDC_SIZEALL) pc = &carr[0]; + else if (id == IDC_SIZENWSE) pc = &carr[1]; + else if (id == IDC_SIZENESW) pc = &carr[2]; + else if (id == IDC_NO) pc = &carr[3]; + else return 0; + + if (!(*pc)) + { + if (id == IDC_SIZEALL) + { + static unsigned char p[16*16] = + { + 0, 0, 0, 0, 0, 0, G, W, W, G, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, G, W, B, B, W, G, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, W, B, B, B, B, W, 0, 0, 0, 0, 0, + 0, 0, 0, 0, G, B, B, B, B, B, B, G, 0, 0, 0, 0, + 0, 0, 0, G, 0, 0, W, B, B, W, 0, 0, G, 0, 0, 0, + 0, G, W, B, 0, 0, W, B, B, W, 0, 0, B, W, G, 0, + G, W, B, B, W, W, W, B, B, W, W, W, B, B, W, G, + W, B, B, B, B, B, B, B, B, B, B, B, B, B, B, W, + W, B, B, B, B, B, B, B, B, B, B, B, B, B, B, W, + G, W, B, B, W, W, W, B, B, W, W, W, B, B, W, G, + 0, G, W, B, 0, 0, W, B, B, W, 0, 0, B, W, G, 0, + 0, 0, 0, G, 0, 0, W, B, B, W, 0, 0, G, 0, 0, 0, + 0, 0, 0, 0, G, B, B, B, B, B, B, G, 0, 0, 0, 0, + 0, 0, 0, 0, 0, W, B, B, B, B, W, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, G, W, B, B, W, G, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, G, W, W, G, 0, 0, 0, 0, 0, 0, + }; + *pc = MakeCursorFromData(p, 8, 8); + } + else if (id == IDC_SIZENWSE || id == IDC_SIZENESW) + { + static unsigned char p[16*16] = + { + W, W, W, W, W, W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + W, G, G, G, W, G, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + W, G, B, W, G, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + W, G, W, B, W, G, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + W, W, G, W, B, W, G, 0, 0, 0, 0, 0, 0, 0, 0, 0, + W, G, 0, G, W, B, W, G, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, G, W, B, W, G, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, G, W, B, W, G, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, G, W, B, W, G, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, G, W, B, W, G, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, G, W, B, W, G, 0, G, W, + 0, 0, 0, 0, 0, 0, 0, 0, 0, G, W, B, W, G, W, W, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, G, W, B, W, G, W, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, G, W, B, G, W, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, G, W, G, G, G, W, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W, W, W, W, W, W, + }; + if (id == IDC_SIZENESW) + { + int x, y; + for (y = 0; y < 16; ++y) + { + for (x = 0; x < 8; ++x) + { + unsigned char tmp = p[16*y+x]; + p[16*y+x] = p[16*y+16-x-1]; + p[16*y+16-x-1] = tmp; + } + } + } + *pc = MakeCursorFromData(p, 8, 8); + if (id == IDC_SIZENESW) // swap back! + { + int x, y; + for (y = 0; y < 16; ++y) + { + for (x = 0; x < 8; ++x) + { + unsigned char tmp = p[16*y+x]; + p[16*y+x] = p[16*y+16-x-1]; + p[16*y+16-x-1] = tmp; + } + } + } + } + else if (id == IDC_NO) + { + static unsigned char p[16*16] = + { + 0, 0, 0, 0, G, W, W, W, W, W, W, G, 0, 0, 0, 0, + 0, 0, G, W, W, B, B, B, B, B, B, W, W, G, 0, 0, + 0, G, W, B, B, B, W, W, W, W, B, B, B, W, G, 0, + 0, W, B, B, W, W, G, 0, 0, G, W, G, B, B, W, 0, + G, W, B, W, G, 0, 0, 0, 0, G, W, B, G, B, W, G, + W, B, B, W, 0, 0, 0, 0, G, W, B, W, W, B, B, W, + W, B, W, G, 0, 0, 0, G, W, B, W, G, G, W, B, W, + W, B, W, 0, 0, 0, G, W, B, W, G, 0, 0, W, B, W, + W, B, W, 0, 0, G, W, B, W, G, 0, 0, 0, W, B, W, + W, B, W, G, G, W, B, W, G, 0, 0, 0, G, W, B, W, + W, B, B, W, W, B, W, G, 0, 0, 0, 0, W, B, B, W, + G, W, B, G, B, W, G, 0, 0, 0, 0, G, W, B, W, G, + 0, W, B, B, G, W, G, 0, 0, G, W, W, B, B, W, 0, + 0, G, W, B, B, B, W, W, W, W, B, B, B, W, G, 0, + 0, 0, G, W, W, B, B, B, B, B, B, W, W, G, 0, 0, + 0, 0, 0, 0, G, W, W, W, W, W, W, G, 0, 0, 0, 0, + }; + *pc = MakeCursorFromData(p, 8, 8); + } + } + + return *pc; +} + +static NSImage *swell_imageFromCursorString(const char *name, POINT *hotSpot) +{ + NSImage *img=NULL; + FILE *fp = NULL; + bool isFullFn=false; + + if (!strstr(name,"/") && strlen(name)<1024) + { + char tmpn[4096]; + GetModuleFileName(NULL,tmpn,(DWORD)(sizeof(tmpn)-128-strlen(name))); + strcat(tmpn,"/Contents/Resources/"); + strcat(tmpn,name); + strcat(tmpn,".cur"); + fp = WDL_fopenA(tmpn,"rb"); + } + else + { + isFullFn=true; + if (strlen(name)>4 && !stricmp(name+strlen(name)-4,".cur")) fp = WDL_fopenA(name,"rb"); + } + + if (fp) + { + unsigned char buf[4096]; + if (fread(buf,1,6,fp)==6 && !buf[0] && !buf[1] && buf[2] == 2 && buf[3] == 0 && buf[4] == 1 && buf[5] == 0) + { + static char tempfn[512]; + if (!tempfn[0]) + { + GetTempPath(256,tempfn); + snprintf(tempfn+strlen(tempfn),256,"swellcur%x%x.ico", timeGetTime(),(int)getpid()); + } + + FILE *outfp = WDL_fopenA(tempfn,"wb"); + if (outfp) + { + bool wantLoad=false; + buf[2]=1; // change to .ico + fwrite(buf,1,6,outfp); + + fread(buf,1,16,fp); + int xHot = buf[4]|(buf[5]<<8); + int yHot = buf[6]|(buf[7]<<8); + + buf[4]=1; buf[5]=0; // 1 color plane + buf[6]=0; buf[7]=0; // 0 for pixel depth means "auto" + + if (!buf[3]) + { + fwrite(buf,1,16,outfp); + for (;;) + { + size_t a = fread(buf,1,sizeof(buf),fp); + if (a<1) break; + fwrite(buf,1,a,outfp); + } + wantLoad=true; + } + + fclose(outfp); + if (wantLoad) + { + NSString *str = (NSString *)SWELL_CStringToCFString(tempfn); + img = [[NSImage alloc] initWithContentsOfFile:str]; + [str release]; + if (img && hotSpot) + { + hotSpot->x = xHot; + hotSpot->y = yHot; + } + // printf("loaded converted ico for %s %s %d\n",tempfn,name,!!img); + } + unlink(tempfn); + } + + } + + fclose(fp); + } + + if (!img) // fall back + { + NSString *str = (NSString *)SWELL_CStringToCFString(name); + + if (isFullFn) img = [[NSImage alloc] initWithContentsOfFile:str]; + else + { + img = [NSImage imageNamed:str]; + if (img) [img retain]; + } + [str release]; + } + + return img; +} + + +HCURSOR SWELL_LoadCursorFromFile(const char *fn) +{ + POINT hotspot={0,}; + NSImage *img = swell_imageFromCursorString(fn,&hotspot); + if (img) + { + HCURSOR ret=(HCURSOR)[[NSCursor alloc] initWithImage:img hotSpot:NSMakePoint(hotspot.x,hotspot.y)]; + [img release]; + return ret; + } + return NULL; +} + +// todo: support for loading from file +HCURSOR SWELL_LoadCursor(const char *_idx) +{ + if (_idx == IDC_NO||_idx==IDC_SIZENWSE || _idx == IDC_SIZENESW || _idx == IDC_SIZEALL) return (HCURSOR) MakeSWELLSystemCursor(_idx); + if (_idx == IDC_SIZEWE) return (HCURSOR)[NSCursor resizeLeftRightCursor]; + if (_idx == IDC_SIZENS) return (HCURSOR)[NSCursor resizeUpDownCursor]; + if (_idx == IDC_ARROW) return (HCURSOR)[NSCursor arrowCursor]; + if (_idx == IDC_HAND) return (HCURSOR)[NSCursor openHandCursor]; + if (_idx == IDC_UPARROW) return (HCURSOR)[NSCursor resizeUpCursor]; + if (_idx == IDC_IBEAM) return (HCURSOR)[NSCursor IBeamCursor]; + + // search registered cursor list + SWELL_CursorResourceIndex *p = SWELL_curmodule_cursorresource_head; + while (p) + { + if (p->resid == _idx) + { + if (p->cachedCursor) return p->cachedCursor; + + NSImage *img = swell_imageFromCursorString(p->resname,&p->hotspot); + if (img) + { + p->cachedCursor=(HCURSOR)[[NSCursor alloc] initWithImage:img hotSpot:NSMakePoint(p->hotspot.x,p->hotspot.y)]; + [img release]; + return p->cachedCursor; + } + } + p=p->_next; + } + return 0; +} + +static HCURSOR m_last_setcursor; + +void SWELL_SetCursor(HCURSOR curs) +{ + if (curs && [(id) curs isKindOfClass:[NSCursor class]]) + { + m_last_setcursor=curs; + [(NSCursor *)curs set]; + } + else + { + m_last_setcursor=NULL; + [[NSCursor arrowCursor] set]; + } +} + +HCURSOR SWELL_GetCursor() +{ + return (HCURSOR)[NSCursor currentCursor]; +} +HCURSOR SWELL_GetLastSetCursor() +{ + return m_last_setcursor; +} + +static POINT g_swell_mouse_relmode_curpos; // stored in osx-native coordinates (y=0=top of screen) +static bool g_swell_mouse_relmode; + + + +void GetCursorPos(POINT *pt) +{ + if (g_swell_mouse_relmode) + { + *pt=g_swell_mouse_relmode_curpos; + return; + } + NSPoint localpt=[NSEvent mouseLocation]; + pt->x=(int)floor(localpt.x + 0.5); + pt->y=(int)floor(localpt.y + 0.5); +} + +DWORD GetMessagePos() +{ + if (g_swell_mouse_relmode) + { + return MAKELONG((int)g_swell_mouse_relmode_curpos.x,(int)g_swell_mouse_relmode_curpos.y); + } + NSPoint localpt=[NSEvent mouseLocation]; + return MAKELONG((int)floor(localpt.x + 0.5), (int)floor(localpt.y + 0.5)); +} + + +NSPoint swellProcessMouseEvent(int msg, NSView *view, NSEvent *event) +{ + if (g_swell_mouse_relmode && msg==WM_MOUSEMOVE) // event will have relative coordinates + { + int idx=(int)[event deltaX]; + int idy=(int)[event deltaY]; + g_swell_mouse_relmode_curpos.x += idx; + g_swell_mouse_relmode_curpos.y -= idy; + } + if (g_swell_mouse_relmode) + { + POINT p=g_swell_mouse_relmode_curpos; + ScreenToClient((HWND)view,&p); + return NSMakePoint(p.x,p.y); + } + NSPoint localpt=[event locationInWindow]; + return [view convertPoint:localpt fromView:nil]; +} + +static int m_curvis_cnt; +bool SWELL_IsCursorVisible() +{ + return m_curvis_cnt>=0; +} +int SWELL_ShowCursor(BOOL bShow) +{ + m_curvis_cnt += (bShow?1:-1); + if (m_curvis_cnt==-1 && !bShow) + { + GetCursorPos(&g_swell_mouse_relmode_curpos); + CGDisplayHideCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(false); + g_swell_mouse_relmode=true; + } + if (m_curvis_cnt==0 && bShow) + { + CGDisplayShowCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(true); + g_swell_mouse_relmode=false; + SetCursorPos(g_swell_mouse_relmode_curpos.x,g_swell_mouse_relmode_curpos.y); + } + return m_curvis_cnt; +} + + +BOOL SWELL_SetCursorPos(int X, int Y) +{ + if (g_swell_mouse_relmode) + { + g_swell_mouse_relmode_curpos.x=X; + g_swell_mouse_relmode_curpos.y=Y; + return TRUE; + } + + const int h = (int)CGDisplayPixelsHigh(CGMainDisplayID()); + CGPoint pos=CGPointMake(X,h-Y); + return CGWarpMouseCursorPosition(pos)==kCGErrorSuccess; +} + +void SWELL_Register_Cursor_Resource(const char *idx, const char *name, int hotspot_x, int hotspot_y) +{ + SWELL_CursorResourceIndex *ri = (SWELL_CursorResourceIndex*)malloc(sizeof(SWELL_CursorResourceIndex)); + ri->hotspot.x = hotspot_x; + ri->hotspot.y = hotspot_y; + ri->resname=name; + ri->cachedCursor=0; + ri->resid = idx; + ri->_next = SWELL_curmodule_cursorresource_head; + SWELL_curmodule_cursorresource_head = ri; +} + + + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menu-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menu-generic.cpp new file mode 100644 index 0000000..a8a33db --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menu-generic.cpp @@ -0,0 +1,1455 @@ +/* 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. + + + This file provides basic windows menu API + + */ + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-menugen.h" + +#include "swell-internal.h" + +#include "../ptrlist.h" +#include "../wdlcstring.h" + +static bool MenuIsStringType(const MENUITEMINFO *inf) +{ + return inf->fType == MFT_STRING || inf->fType == MFT_RADIOCHECK; +} + +HMENU__ *HMENU__::Duplicate() +{ + HMENU__ *p = new HMENU__; + int x; + for (x = 0; x < items.GetSize(); x ++) + { + MENUITEMINFO *s = items.Get(x); + MENUITEMINFO *inf = (MENUITEMINFO*)calloc(sizeof(MENUITEMINFO),1); + + *inf = *s; + if (inf->dwTypeData && MenuIsStringType(inf)) inf->dwTypeData=strdup(inf->dwTypeData); + if (inf->hSubMenu) inf->hSubMenu = inf->hSubMenu->Duplicate(); + + p->items.Add(inf); + } + return p; +} + +void HMENU__::freeMenuItem(void *p) +{ + MENUITEMINFO *inf = (MENUITEMINFO *)p; + if (!inf) return; + if (inf->hSubMenu) inf->hSubMenu->Release(); + if (MenuIsStringType(inf)) free(inf->dwTypeData); + free(inf); +} + +static MENUITEMINFO *GetMenuItemByID(HMENU menu, int id, bool searchChildren=true) +{ + if (WDL_NOT_NORMALLY(!menu)) return 0; + int x; + for (x = 0; x < menu->items.GetSize(); x ++) + if (menu->items.Get(x)->wID == (UINT)id) return menu->items.Get(x); + + if (searchChildren) for (x = 0; x < menu->items.GetSize(); x ++) + { + if (menu->items.Get(x)->hSubMenu) + { + MENUITEMINFO *ret = GetMenuItemByID(menu->items.Get(x)->hSubMenu,id,true); + if (ret) return ret; + } + } + + return 0; +} + +bool SetMenuItemText(HMENU hMenu, int idx, int flag, const char *text) +{ + MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? ((flag & MF_BYPOSITION) ? hMenu->items.Get(idx) : GetMenuItemByID(hMenu,idx)) : NULL; + if (!item) return false; + + if (MenuIsStringType(item)) free(item->dwTypeData); + else item->fType = MFT_STRING; + item->dwTypeData=strdup(text?text:""); + + return true; +} + +bool EnableMenuItem(HMENU hMenu, int idx, int en) +{ + MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? ((en & MF_BYPOSITION) ? hMenu->items.Get(idx) : GetMenuItemByID(hMenu,idx)) : NULL; + if (!item) return false; + + int mask = MF_ENABLED|MF_DISABLED|MF_GRAYED; + item->fState &= ~mask; + item->fState |= en&mask; + + return true; +} + +bool CheckMenuItem(HMENU hMenu, int idx, int chk) +{ + MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? ((chk & MF_BYPOSITION) ? hMenu->items.Get(idx) : GetMenuItemByID(hMenu,idx)) : NULL; + if (!item) return false; + + int mask = MF_CHECKED; + item->fState &= ~mask; + item->fState |= chk&mask; + + return true; +} +HMENU SWELL_GetCurrentMenu() +{ + return NULL; // not osx +} +void SWELL_SetCurrentMenu(HMENU hmenu) +{ +} + +HMENU GetSubMenu(HMENU hMenu, int pos) +{ + MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? hMenu->items.Get(pos) : NULL; + if (item) return item->hSubMenu; + return 0; +} + +int GetMenuItemCount(HMENU hMenu) +{ + if (WDL_NORMALLY(hMenu)) return hMenu->items.GetSize(); + return 0; +} + +int GetMenuItemID(HMENU hMenu, int pos) +{ + MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? hMenu->items.Get(pos) : NULL; + if (!item) + { + WDL_ASSERT(pos==0); // do not assert if GetMenuItemID(0) is called on an empty menu + return -1; + } + if (item->hSubMenu) return -1; + return item->wID; +} + +bool SetMenuItemModifier(HMENU hMenu, int idx, int flag, int code, unsigned int mask) +{ + return false; +} + +HMENU CreatePopupMenu() +{ + return new HMENU__; +} +HMENU CreatePopupMenuEx(const char *title) +{ + return CreatePopupMenu(); +} + +void DestroyMenu(HMENU hMenu) +{ + if (WDL_NORMALLY(hMenu)) hMenu->Release(); +} + +int AddMenuItem(HMENU hMenu, int pos, const char *name, int tagid) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return -1; + MENUITEMINFO *inf = (MENUITEMINFO*)calloc(1,sizeof(MENUITEMINFO)); + inf->wID = tagid; + inf->fType = MFT_STRING; + inf->dwTypeData = strdup(name?name:""); + hMenu->items.Insert(pos,inf); + return 0; +} + +bool DeleteMenu(HMENU hMenu, int idx, int flag) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return false; + if (flag&MF_BYPOSITION) + { + if (hMenu->items.Get(idx)) + { + hMenu->items.Delete(idx,true,HMENU__::freeMenuItem); + return true; + } + return false; + } + else + { + int x; + int cnt=0; + for (x=0;xitems.GetSize(); x ++) + { + if (!hMenu->items.Get(x)->hSubMenu && hMenu->items.Get(x)->wID == (UINT)idx) + { + hMenu->items.Delete(x--,true,HMENU__::freeMenuItem); + cnt++; + } + } + if (!cnt) + { + for (x=0;xitems.GetSize(); x ++) + { + if (hMenu->items.Get(x)->hSubMenu) cnt += DeleteMenu(hMenu->items.Get(x)->hSubMenu,idx,flag)?1:0; + } + } + return !!cnt; + } +} + + +BOOL SetMenuItemInfo(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return 0; + MENUITEMINFO *item = byPos ? hMenu->items.Get(pos) : GetMenuItemByID(hMenu, pos, true); + if (!item) return 0; + + if ((mi->fMask & MIIM_SUBMENU) && mi->hSubMenu != item->hSubMenu) + { + if (item->hSubMenu) item->hSubMenu->Release(); + item->hSubMenu = mi->hSubMenu; + } + if (mi->fMask & MIIM_TYPE) + { + const bool wasString = MenuIsStringType(item), isString = MenuIsStringType(mi); + if (wasString != isString) + { + if (wasString) free(item->dwTypeData); + item->dwTypeData=NULL; + } + + if (mi->fType == MFT_BITMAP) item->dwTypeData = mi->dwTypeData; + else if (isString && mi->dwTypeData) + { + free(item->dwTypeData); + item->dwTypeData = strdup(mi->dwTypeData); + } + item->fType = mi->fType; + } + + if (mi->fMask & MIIM_STATE) item->fState = mi->fState; + if (mi->fMask & MIIM_ID) item->wID = mi->wID; + if (mi->fMask & MIIM_DATA) item->dwItemData = mi->dwItemData; + if ((mi->fMask & MIIM_BITMAP) && mi->cbSize >= sizeof(*mi)) item->hbmpItem = mi->hbmpItem; + + return true; +} + +BOOL GetMenuItemInfo(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return 0; + MENUITEMINFO *item = byPos ? hMenu->items.Get(pos) : GetMenuItemByID(hMenu, pos, true); + if (!item) return 0; + + if (mi->fMask & MIIM_TYPE) + { + mi->fType = item->fType; + if (MenuIsStringType(mi) && mi->dwTypeData && mi->cch) + { + lstrcpyn_safe(mi->dwTypeData,item->dwTypeData?item->dwTypeData:"",mi->cch); + } + else if (item->fType == MFT_BITMAP) mi->dwTypeData = item->dwTypeData; + } + + if (mi->fMask & MIIM_DATA) mi->dwItemData = item->dwItemData; + if (mi->fMask & MIIM_STATE) mi->fState = item->fState; + if (mi->fMask & MIIM_ID) mi->wID = item->wID; + if (mi->fMask & MIIM_SUBMENU) mi->hSubMenu = item->hSubMenu; + if ((mi->fMask & MIIM_BITMAP) && mi->cbSize >= sizeof(*mi)) mi->hbmpItem = item->hbmpItem; + + return 1; + +} + +void SWELL_InsertMenu(HMENU menu, int pos, unsigned int flag, UINT_PTR idx, const char *str) +{ + MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING, + (flag & ~MF_BYPOSITION),(flag&MF_POPUP) ? 0 : (UINT)idx,NULL,NULL,NULL,0,(char *)str}; + + if (flag&MF_POPUP) + { + mi.hSubMenu = (HMENU)idx; + mi.fMask |= MIIM_SUBMENU; + mi.fState &= ~MF_POPUP; + } + + if (flag&MF_SEPARATOR) + { + mi.fMask=MIIM_TYPE; + mi.fType=MFT_SEPARATOR; + mi.fState &= ~MF_SEPARATOR; + } + + if (flag&MF_BITMAP) + { + mi.fType=MFT_BITMAP; + mi.fState &= ~MF_BITMAP; + } + + InsertMenuItem(menu,pos,(flag&MF_BYPOSITION) ? TRUE : FALSE, &mi); +} + +void InsertMenuItem(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return; + int ni=hMenu->items.GetSize(); + + if (!byPos) + { + int x; + for (x=0;xitems.Get(x)->wID != (UINT)pos; x++); + pos = x; + } + if (pos < 0 || pos > ni) pos=ni; + + MENUITEMINFO *inf = (MENUITEMINFO*)calloc(sizeof(MENUITEMINFO),1); + inf->fType = mi->fType; + if (MenuIsStringType(inf)) + { + inf->dwTypeData = strdup(mi->dwTypeData?mi->dwTypeData:""); + } + else if (mi->fType == MFT_BITMAP) + { + inf->dwTypeData = mi->dwTypeData; + } + else if (mi->fType == MFT_SEPARATOR) + { + } + if (mi->fMask&MIIM_SUBMENU) inf->hSubMenu = mi->hSubMenu; + if (mi->fMask & MIIM_STATE) inf->fState = mi->fState; + if (mi->fMask & MIIM_DATA) inf->dwItemData = mi->dwItemData; + if (mi->fMask & MIIM_ID) inf->wID = mi->wID; + if ((mi->fMask & MIIM_BITMAP) && mi->cbSize >= sizeof(*mi)) inf->hbmpItem = mi->hbmpItem; + + hMenu->items.Insert(pos,inf); +} + + +void SWELL_SetMenuDestination(HMENU menu, HWND hwnd) +{ + // only needed for Cocoa +} + +extern RECT g_trackpopup_yroot; +static POINT m_trackingPt, m_trackingPt2; +static int m_trackingMouseFlag; +static int m_trackingFlags,m_trackingRet; +static HWND m_trackingPar; +static WDL_PtrList m_trackingMenus; // each HWND as userdata = HMENU + +int swell_delegate_menu_message(HWND src, LPARAM lParam, int msg, bool screencoords) +{ + static bool _reent; + if (_reent) return 0; + + _reent = true; + + POINT sp = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + if (!screencoords) ClientToScreen(src,&sp); + + for (int x = m_trackingMenus.GetSize()-1; x>=0; x--) + { + HWND sw = m_trackingMenus.Get(x); + if (!sw) continue; + + if (sw == src) break; // stop searching (don't delegate to parent) + + RECT r; + GetWindowRect(sw,&r); + if (PtInRect(&r,sp)) + { + POINT p = sp; + ScreenToClient(sw,&p); + SendMessage(sw,msg,0,MAKELPARAM(p.x,p.y)); + _reent = false; + return 1; + } + } + + _reent = false; + return 0; +} + +bool swell_isOSwindowmenu(SWELL_OSWINDOW osw) +{ + int x = m_trackingMenus.GetSize(); + if (osw) while (--x>=0) + { + HWND__ *p = m_trackingMenus.Get(x); + if (p->m_oswindow == osw) return true; + } + return false; +} + +int menuBarNavigate(int dir); // -1 if no menu bar active, 0 if did nothing, 1 if navigated +HWND GetFocusIncludeMenus(void); +static DWORD swell_menu_ignore_mousemove_from; + +static LRESULT WINAPI submenuWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static int lcol, rcol, mcol, top_margin, separator_ht, text_ht_pad, bitmap_ht_pad, scroll_margin, item_bm_pad; + if (!lcol) + { + lcol=SWELL_UI_SCALE(30); rcol=SWELL_UI_SCALE(18); mcol=SWELL_UI_SCALE(10); + top_margin=SWELL_UI_SCALE(4); separator_ht=SWELL_UI_SCALE(8); + text_ht_pad=SWELL_UI_SCALE(4); bitmap_ht_pad=SWELL_UI_SCALE(4); + scroll_margin=SWELL_UI_SCALE(10); + item_bm_pad = SWELL_UI_SCALE(4); + } + switch (uMsg) + { + case WM_CREATE: + hwnd->m_classname = "__SWELL_MENU"; + hwnd->m_style = WS_CHILD; + m_trackingMenus.Add(hwnd); + SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); + + if (m_trackingPar && !(m_trackingFlags&TPM_NONOTIFY)) + SendMessage(m_trackingPar,WM_INITMENUPOPUP,(WPARAM)lParam,0); + + { + HDC hdc = GetDC(hwnd); + HMENU__ *menu = (HMENU__*)lParam; + int ht = 0, wid=SWELL_UI_SCALE(100),wid2=0; + int x; + for (x=0; x < menu->items.GetSize(); x++) + { + MENUITEMINFO *inf = menu->items.Get(x); + BITMAP bm2={0,}; + if (inf->hbmpItem) + GetObject(inf->hbmpItem,sizeof(bm2),&bm2); + + if (MenuIsStringType(inf)) + { + RECT r={0,}; + const char *str = inf->dwTypeData; + if (!str || !*str) str="XXXXX"; + const char *pt2 = strstr(str,"\t"); + DrawText(hdc,str,pt2 ? (int)(pt2-str) : -1,&r,DT_CALCRECT|DT_SINGLELINE); + if (r.bottom < bm2.bmHeight) r.bottom = bm2.bmHeight; + if (bm2.bmWidth) r.right += bm2.bmWidth + item_bm_pad; + + if (r.right > wid) wid=r.right; + ht += r.bottom + text_ht_pad; + + if (pt2) + { + r.right=r.left; + DrawText(hdc,pt2+1,-1,&r,DT_CALCRECT|DT_SINGLELINE); + if (r.right > wid2) wid2=r.right; + } + } + else if (inf->fType == MFT_BITMAP) + { + BITMAP bm={16,16}; + if (inf->dwTypeData) GetObject((HBITMAP)inf->dwTypeData,sizeof(bm),&bm); + if (bm.bmHeight < bm2.bmHeight) bm.bmHeight = bm2.bmHeight; + if (bm2.bmWidth) bm.bmWidth += bm2.bmWidth + item_bm_pad; + if (bm.bmWidth > wid) wid = bm.bmWidth; + + ht += bm.bmHeight + bitmap_ht_pad; + } + else + { + // treat as separator, ignore bm2 + ht += separator_ht; + } + } + wid+=lcol+rcol + (wid2?wid2+mcol:0); + ReleaseDC(hwnd,hdc); + + const RECT ref={m_trackingPt.x, m_trackingPt.y, m_trackingPt.x, m_trackingPt.y }; + RECT vp, tr={m_trackingPt.x,m_trackingPt.y, m_trackingPt.x+wid+SWELL_UI_SCALE(4),m_trackingPt.y+ht+top_margin * 2}; + SWELL_GetViewPort(&vp,&ref,true); + vp.bottom -= 8; + + if (g_trackpopup_yroot.bottom > g_trackpopup_yroot.top && + g_trackpopup_yroot.bottom > vp.top && + g_trackpopup_yroot.top < vp.bottom) + { + const int req_h = ht*9/8+32; + if (vp.bottom - g_trackpopup_yroot.bottom < req_h && g_trackpopup_yroot.top - vp.top >= req_h) + { + vp.bottom = g_trackpopup_yroot.top; + } + } + + if (tr.bottom > vp.bottom) { tr.top += vp.bottom-tr.bottom; tr.bottom=vp.bottom; } + if (tr.right > vp.right) + { + if ((vp.right - m_trackingPt2.x) < (m_trackingPt2.x - vp.left)) + { + tr.left = m_trackingPt2.x - (tr.right-tr.left); + tr.right = m_trackingPt2.x; + } + else + { + tr.left += vp.right-tr.right; tr.right=vp.right; + } + } + + if (tr.left < vp.left) { tr.right += vp.left-tr.left; tr.left=vp.left; } + if (tr.top < vp.top) { tr.bottom += vp.top-tr.top; tr.top=vp.top; } + if (tr.bottom > vp.bottom) tr.bottom=vp.bottom; + if (tr.right > vp.right) tr.right=vp.right; + + SetWindowPos(hwnd,NULL,tr.left,tr.top,tr.right-tr.left,tr.bottom-tr.top,SWP_NOZORDER); + + hwnd->m_extra[0] = 0; // Y scroll offset + hwnd->m_extra[1] = 0; // is currently autoscrolling to sel_vis + hwnd->m_extra[2] = 0; // remaining items out of view, if any + } + + ShowWindow(hwnd,SW_SHOW); + SetFocus(hwnd); + SetTimer(hwnd,1,100,NULL); + SetTimer(hwnd,2,15,NULL); + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT cr; + GetClientRect(hwnd,&cr); + HBRUSH br = CreateSolidBrush(g_swell_ctheme.menu_bg); + HBRUSH br2 = CreateSolidBrushAlpha(g_swell_ctheme.menu_scroll,0.5f); + HBRUSH br3 = CreateSolidBrush(g_swell_ctheme.menu_scroll_arrow); + HBRUSH br_submenu_arrow = CreateSolidBrush(g_swell_ctheme.menu_submenu_arrow); + HBRUSH br_submenu_arrow_sel = CreateSolidBrush(g_swell_ctheme.menu_submenu_arrow_sel); + HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.menu_shadow); + HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.menu_hilight); + HGDIOBJ oldbr = SelectObject(ps.hdc,br); + HGDIOBJ oldpen = SelectObject(ps.hdc,pen2); + Rectangle(ps.hdc,cr.left,cr.top,cr.right,cr.bottom); + SetBkMode(ps.hdc,TRANSPARENT); + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + int ypos = top_margin; + + int extra_left = 0; + bool want_autoscroll_down = false; + const int ni = menu->items.GetSize(); + int x; + for (x=wdl_max((int)hwnd->m_extra[0],0); x < ni; x++) + { + if (ypos >= cr.bottom) + { + extra_left = ni-x; + break; + } + MENUITEMINFO *inf = menu->items.Get(x); + if (WDL_NOT_NORMALLY(!inf)) break; + + RECT r={lcol,ypos,cr.right, }; + bool dis = !!(inf->fState & MF_GRAYED); + BITMAP bm={16,16}, bm2={0,}; + if (inf->hbmpItem) + GetObject(inf->hbmpItem,sizeof(bm2),&bm2); + + if (MenuIsStringType(inf)) + { + const char *str = inf->dwTypeData; + if (!str || !*str) str="XXXXX"; + RECT mr={0,}; + DrawText(ps.hdc,str,-1,&mr,DT_CALCRECT|DT_SINGLELINE); + + ypos += wdl_max(mr.bottom,bm2.bmHeight) + text_ht_pad; + r.bottom = ypos; + } + else if (inf->fType == MFT_BITMAP) + { + if (inf->dwTypeData) GetObject((HBITMAP)inf->dwTypeData,sizeof(bm),&bm); + + ypos += wdl_max(bm.bmHeight,bm2.bmHeight) + bitmap_ht_pad; + r.bottom = ypos; + + } + else + { + dis=true; + ypos += separator_ht; + r.bottom = ypos; + } + + if (x == menu->sel_vis && !dis) + { + HBRUSH brs=CreateSolidBrush(g_swell_ctheme.menu_bg_sel); + RECT r2=r; + r2.left = cr.left + 1; + r2.right = r2.right - 1; + FillRect(ps.hdc,&r2,brs); + DeleteObject(brs); + SetTextColor(ps.hdc,g_swell_ctheme.menu_text_sel); + } + else + { + SetTextColor(ps.hdc, + dis ? g_swell_ctheme.menu_text_disabled : + g_swell_ctheme.menu_text); + } + + if (bm2.bmWidth) + { + RECT tr = r; + tr.right = tr.left + bm2.bmWidth; + DrawImageInRect(ps.hdc,inf->hbmpItem,&tr); + + r.left += bm2.bmWidth + item_bm_pad; + } + + if (MenuIsStringType(inf)) + { + const char *str = inf->dwTypeData; + if (!str) str=""; + const char *pt2 = strstr(str,"\t"); + + if (*str) + { + DrawText(ps.hdc,str,pt2 ? (int)(pt2-str) : -1,&r,DT_VCENTER|DT_SINGLELINE); + if (pt2) + { + RECT tr=r; tr.right-=rcol; + DrawText(ps.hdc,pt2+1,-1,&tr,DT_VCENTER|DT_SINGLELINE|DT_RIGHT); + } + } + } + else if (inf->fType == MFT_BITMAP) + { + if (inf->dwTypeData) + { + RECT tr = r; + tr.top += bitmap_ht_pad/2; + tr.right = tr.left + bm.bmWidth; + tr.bottom = tr.top + bm.bmHeight; + DrawImageInRect(ps.hdc,(HBITMAP)inf->dwTypeData,&tr); + } + } + else + { + SelectObject(ps.hdc,pen2); + int margin = rcol / 2; + int y = r.top/2+r.bottom/2, left = cr.left+margin, right = r.right-margin; + MoveToEx(ps.hdc,left,y,NULL); + LineTo(ps.hdc,right,y); + SelectObject(ps.hdc,pen); + + y++; + MoveToEx(ps.hdc,left,y,NULL); + LineTo(ps.hdc,right,y); + } + if (inf->hSubMenu) + { + const int sz = (r.bottom-r.top)/5, xp = r.right - rcol/2 - sz, yp = (r.top + r.bottom)/2; + + POINT pts[3] = { + {xp, yp-sz}, + {xp, yp+sz}, + {xp + sz,yp} + }; + HGDIOBJ oldPen = SelectObject(ps.hdc,GetStockObject(NULL_PEN)); + + if (x == menu->sel_vis && !dis) + SelectObject(ps.hdc,br_submenu_arrow_sel); + else + SelectObject(ps.hdc,br_submenu_arrow); + + Polygon(ps.hdc,pts,3); + + SelectObject(ps.hdc,oldPen); + + + } + if (inf->fState&MF_CHECKED) + { + int col; + if (x == menu->sel_vis && !dis) + col = g_swell_ctheme.menu_text_sel; + else + col = dis ? g_swell_ctheme.menu_text_disabled : g_swell_ctheme.menu_text; + + HPEN tpen = CreatePen(PS_SOLID,0, col); + HBRUSH tbr = CreateSolidBrush(col); + HGDIOBJ oldBrush = SelectObject(ps.hdc,tbr); + HGDIOBJ oldPen = SelectObject(ps.hdc,tpen); + const int sz = (wdl_min(lcol, r.bottom-r.top) - SWELL_UI_SCALE(10)); + const int xo = SWELL_UI_SCALE(4), yo = (r.bottom+r.top)/2 - sz/2; + if (inf->fType&MFT_RADIOCHECK) + { + Ellipse(ps.hdc, xo, yo, xo+sz, yo+sz); + } + else + { + static const unsigned char coords[12] = { 0, 76, 12, 64, 40, 92, 40, 118, 116, 16, 128, 28, }; + for (int pass=0;pass<2;pass++) + { + POINT pts[4]; + for (int i=0;i<4; i ++) + { + pts[i].x = lcol/2 + sz * ((int)coords[i*2+pass*4] - 64) / 128; + pts[i].y = (r.bottom+r.top)/2 + sz * ((int)coords[i*2+pass*4+1] - 64) / 128; + } + Polygon(ps.hdc,pts,4); + } + } + + SelectObject(ps.hdc,oldPen); + SelectObject(ps.hdc,oldBrush); + DeleteObject(tpen); + DeleteObject(tbr); + } + if ((r.top+ypos)/2 > cr.bottom) + { + extra_left = ni-x; + } + if (ypos > cr.bottom && x == menu->sel_vis) + { + want_autoscroll_down = true; + extra_left = ni-x; + } + } + hwnd->m_extra[1] = want_autoscroll_down || x <= menu->sel_vis; + hwnd->m_extra[2] = extra_left; + + + // lower scroll indicator + int mid=(cr.right-cr.left)/2; + SelectObject(ps.hdc,GetStockObject(NULL_PEN)); + SelectObject(ps.hdc,br3); + POINT pts[3]; + const int smm = SWELL_UI_SCALE(2); + const int smh = scroll_margin-smm*2; + if (extra_left>0) + { + RECT fr = {cr.left, cr.bottom-scroll_margin, cr.right,cr.bottom}; + FillRect(ps.hdc,&fr,br2); + pts[0].x = mid; pts[0].y = cr.bottom - smm; + pts[1].x = mid-smh; pts[1].y = pts[0].y - smh; + pts[2].x = mid+smh; pts[2].y = pts[1].y; + Polygon(ps.hdc,pts,3); + } + // upper scroll indicator + if (hwnd->m_extra[0] > 0) + { + RECT fr = {cr.left, cr.top, cr.right, cr.top+scroll_margin}; + FillRect(ps.hdc,&fr,br2); + + pts[0].x = mid; pts[0].y = cr.top + smm; + pts[1].x = mid-smh; pts[1].y = pts[0].y + smh; + pts[2].x = mid+smh; pts[2].y = pts[1].y; + Polygon(ps.hdc,pts,3); + } + + SelectObject(ps.hdc,oldbr); + SelectObject(ps.hdc,oldpen); + DeleteObject(br); + DeleteObject(br2); + DeleteObject(br3); + DeleteObject(br_submenu_arrow); + DeleteObject(br_submenu_arrow_sel); + DeleteObject(pen); + DeleteObject(pen2); + EndPaint(hwnd,&ps); + } + } + break; + case WM_TIMER: + if (wParam==1) + { + HWND h = GetFocusIncludeMenus(); + if (h!=hwnd) + { + int a = h ? m_trackingMenus.Find(h) : -1; + if (a<0 || a < m_trackingMenus.Find(hwnd)) + { + if (m_trackingMouseFlag && m_trackingMenus.Get(0)) + { + SetFocus(m_trackingMenus.Get(0)); + m_trackingMouseFlag=0; + } + else DestroyWindow(hwnd); + } + } + } + else if (wParam == 2) + { + // menu scroll + RECT tr; + GetWindowRect(hwnd,&tr); + + POINT curM; + GetCursorPos(&curM); + const bool xmatch = (curM.x >= tr.left && curM.x < tr.right); + if (xmatch || (hwnd->m_extra[1] && hwnd->m_extra[2])) + { + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + int xFirst = wdl_max(hwnd->m_extra[0],0); + const bool ymatch = curM.y >= tr.bottom-scroll_margin && curM.y < tr.bottom+scroll_margin; + if (hwnd->m_extra[2] && (hwnd->m_extra[1] || ymatch)) + { + if (hwnd->m_extra[1] && menu->sel_vis > 0) + { + const int li = menu->items.GetSize() - (int)hwnd->m_extra[2]; + const int incr = (menu->sel_vis - li)/2 + 2; + xFirst += wdl_max(incr,1); + } + else + xFirst++; + hwnd->m_extra[0]=xFirst; + hwnd->m_extra[1]=hwnd->m_extra[2]=0; + if (ymatch) menu->sel_vis=-1; + InvalidateRect(hwnd,NULL,FALSE); + } + else if (xFirst > 0 && curM.y >= tr.top-scroll_margin && curM.y < tr.top+scroll_margin) + { + hwnd->m_extra[0]=--xFirst; + menu->sel_vis=-1; + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + else if (wParam == 5) + { + KillTimer(hwnd,5); + + RECT r; + POINT pt; + GetCursorPos(&pt); + ScreenToClient(hwnd,&pt); + GetClientRect(hwnd,&r); + if (PtInRect(&r,pt)) + SendMessage(hwnd,WM_USER+100,4,pt.y); + } + break; + case WM_KEYUP: + return 1; + case WM_MOUSEWHEEL: + { + RECT tr; + GetWindowRect(hwnd,&tr); + + POINT curM; + GetCursorPos(&curM); + const bool xmatch = (curM.x >= tr.left && curM.x < tr.right); + const bool ymatch = curM.y >= tr.bottom-scroll_margin && curM.y < tr.bottom+scroll_margin; + if (!xmatch || !ymatch) // if mouse is over scroll area, ignore + { + int amt = ((short)HIWORD(wParam))/-40; + const int xFirst = wdl_max(hwnd->m_extra[0],0); + if (amt > hwnd->m_extra[2]) amt = (int) hwnd->m_extra[2]; + if (amt < 0 ? xFirst > 0 : amt > 0) + { + hwnd->m_extra[0]=wdl_max(xFirst+amt,0); + if (amt>0) + { + hwnd->m_extra[2] -= amt; + if (hwnd->m_extra[2]<0) hwnd->m_extra[2]=0; + } + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + menu->sel_vis=-1; // clear selection + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + return 1; + + case WM_KEYDOWN: + + if (wParam >= VK_SPACE && wParam < 0x80) + { + // ignore mousemoves immediately after most keys + swell_menu_ignore_mousemove_from = GetTickCount(); + } + + if (wParam == VK_ESCAPE || wParam == VK_LEFT) + { + HWND l = m_trackingMenus.Get(m_trackingMenus.Find(hwnd)-1); + if (l) SetFocus(l); + else + { + if (wParam != VK_LEFT || menuBarNavigate(-1) < 0) + DestroyWindow(hwnd); + } + } + else if (wParam == VK_RETURN || wParam == VK_RIGHT) + { + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (wParam == VK_RIGHT) + { + MENUITEMINFO *inf = menu->items.Get(menu->sel_vis); + if (!inf || !inf->hSubMenu) + { + menuBarNavigate(1); + return 1; + } + } + SendMessage(hwnd,WM_USER+100,1,menu->sel_vis); + } + else if (wParam == VK_UP || wParam == VK_PRIOR) + { + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + int l = menu->sel_vis; + for (int i= wParam == VK_UP ? 0 : 9; i>=0; i--) + { + int mc = menu->items.GetSize(); + while (mc--) + { + if (l<1) + { + if (wParam != VK_UP) break; + l = menu->items.GetSize(); + } + MENUITEMINFO *inf = menu->items.Get(--l); + if (!inf) break; + if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) + { + menu->sel_vis=l; + break; + } + } + } + if (menu->sel_vis < hwnd->m_extra[0]) + hwnd->m_extra[0] = menu->sel_vis; + InvalidateRect(hwnd,NULL,FALSE); + } + else if (wParam == VK_DOWN || wParam == VK_NEXT) + { + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + int l = menu->sel_vis; + const int n =menu->items.GetSize()-1; + for (int i = wParam == VK_DOWN ? 0 : 9; i>=0; i--) + { + int mc = n+1; + while (mc--) + { + if (l>=n) + { + if (wParam != VK_DOWN) break; + l=-1; + hwnd->m_extra[0]=0; + } + MENUITEMINFO *inf = menu->items.Get(++l); + if (!inf) break; + if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) + { + menu->sel_vis=l; + break; + } + } + } + InvalidateRect(hwnd,NULL,FALSE); + } + else if (wParam == VK_END) + { + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + int l = menu->items.GetSize(); + while (l > 0) + { + MENUITEMINFO *inf = menu->items.Get(--l); + if (!inf) break; + if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) + { + menu->sel_vis=l; + break; + } + } + if (menu->sel_vis < hwnd->m_extra[0]) + hwnd->m_extra[0] = menu->sel_vis; + InvalidateRect(hwnd,NULL,FALSE); + } + else if (wParam == VK_HOME) + { + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + int l = 0; + while (l < menu->items.GetSize()) + { + MENUITEMINFO *inf = menu->items.Get(l++); + if (!inf) break; + if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) + { + menu->sel_vis=l-1; + break; + } + } + if (menu->sel_vis < hwnd->m_extra[0]) + hwnd->m_extra[0] = menu->sel_vis; + InvalidateRect(hwnd,NULL,FALSE); + } + else if ((lParam & FVIRTKEY) && ( + (wParam >= 'A' && wParam <= 'Z') || + (wParam >= '0' && wParam <= '9'))) + { + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + const int n=menu->items.GetSize(); + + int offs = menu->sel_vis+1; + if (offs<0||offs>=n) offs=0; + int matchcnt=0; + for(int x=0;xitems.Get(offs); + if (MenuIsStringType(inf) && + !(inf->fState & MF_GRAYED) && + inf->dwTypeData) + { + const char *p = inf->dwTypeData; + bool is_prefix_mode = xsel_vis); + } + break; + } + + if (is_prefix_mode) while (*p) + { + if (*p++ == '&') + { + if (*p != '&') break; + p++; + } + } + + if (*p > 0 && (WPARAM)toupper(*p) == wParam) + { + if (!matchcnt++) + { + menu->sel_vis = offs; + if (menu->sel_vis < hwnd->m_extra[0]) + hwnd->m_extra[0] = menu->sel_vis; + InvalidateRect(hwnd,NULL,FALSE); + } + if (!is_prefix_mode) break; + } + } + if (++offs >= n) offs=0; + } + } + + return 1; + case WM_DESTROY: + { + int a = m_trackingMenus.Find(hwnd); + m_trackingMenus.Delete(a); + if (m_trackingMenus.Get(a)) DestroyWindow(m_trackingMenus.Get(a)); + RemoveProp(hwnd,"SWELL_MenuOwner"); + } + break; + case WM_USER+100: + if (wParam == 1 || wParam == 2 || wParam == 3 || wParam == 4) + { + int which = (int) lParam; + int item_ypos = which; + + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + + int ht = top_margin; + HDC hdc=GetDC(hwnd); + if (wParam > 1) which = -1; + else item_ypos = 0; + for (int x=wdl_max(hwnd->m_extra[0],0); x < (menu->items.GetSize()); x++) + { + if (wParam == 1 && which == x) { item_ypos = ht; break; } + MENUITEMINFO *inf = menu->items.Get(x); + int lastht = ht; + BITMAP bm2={0,}; + if (inf->hbmpItem) + GetObject(inf->hbmpItem,sizeof(bm2),&bm2); + + if (MenuIsStringType(inf)) + { + RECT r={0,}; + const char *str = inf->dwTypeData; + if (!str || !*str) str="XXXXX"; + const char *pt2 = strstr(str,"\t"); + DrawText(hdc,str,pt2 ? (int)(pt2-str) : -1,&r,DT_CALCRECT|DT_SINGLELINE); + ht += wdl_max(r.bottom,bm2.bmHeight) + text_ht_pad; + } + else if (inf->fType == MFT_BITMAP) + { + BITMAP bm={16,16}; + if (inf->dwTypeData) GetObject((HBITMAP)inf->dwTypeData,sizeof(bm),&bm); + ht += wdl_max(bm.bmHeight,bm2.bmHeight) + bitmap_ht_pad; + } + else + { + ht += separator_ht; + } + if (wParam > 1 && item_ypos < ht) + { + item_ypos = lastht; + which = x; + if (wParam == 4 && inf->hSubMenu) + { + HWND nextmenu = m_trackingMenus.Get(m_trackingMenus.Find(hwnd)+1); + if (!nextmenu || GetWindowLongPtr(nextmenu,GWLP_USERDATA) != (LPARAM)inf->hSubMenu) + { + wParam = 1; // activate if not already visible + menu->sel_vis = which; + } + } + break; + } + } + ReleaseDC(hwnd,hdc); + if (wParam == 4) + { + MENUITEMINFO *inf = menu->items.Get(which); + if (inf) + { + HWND next = m_trackingMenus.Get(m_trackingMenus.Find(hwnd)+1); + if (next && (!inf->hSubMenu || (LPARAM)inf->hSubMenu != GetWindowLongPtr(next,GWLP_USERDATA))) + DestroyWindow(next); + } + menu->sel_vis = which; + return 0; + } + if (wParam == 3) + { + if (menu->sel_vis != which) + { + SetTimer(hwnd,5,300,NULL); + menu->sel_vis = which; + } + return 0; + } + + MENUITEMINFO *inf = menu->items.Get(which); + + if (inf) + { + if (inf->fState&MF_GRAYED){ } + else if (inf->hSubMenu) + { + const int nextidx = m_trackingMenus.Find(hwnd)+1; + HWND hh = m_trackingMenus.Get(nextidx); + + inf->hSubMenu->sel_vis=-1; + + if (hh) + { + m_trackingMenus.Delete(nextidx); + int a = m_trackingMenus.GetSize(); + while (a > nextidx) DestroyWindow(m_trackingMenus.Get(--a)); + } + else + { + hh = new HWND__(NULL,0,NULL,"menu",false,submenuWndProc,NULL, hwnd); + SetProp(hh,"SWELL_MenuOwner",GetProp(hwnd,"SWELL_MenuOwner")); + } + + RECT r; + GetClientRect(hwnd,&r); + m_trackingPt.x=r.right; + m_trackingPt.y=item_ypos; + m_trackingPt2.x=r.left; + m_trackingPt2.y=item_ypos; + ClientToScreen(hwnd,&m_trackingPt); + ClientToScreen(hwnd,&m_trackingPt2); + + submenuWndProc(hh, WM_CREATE,0,(LPARAM)inf->hSubMenu); + InvalidateRect(hwnd,NULL,FALSE); + } + else if (inf->wID) m_trackingRet = inf->wID; + } + } + return 0; + case WM_MOUSEMOVE: + { + if ((GetTickCount() - swell_menu_ignore_mousemove_from)<200) return 0; + + if (swell_delegate_menu_message(hwnd, lParam,uMsg, false)) + return 0; + + RECT r; + GetClientRect(hwnd,&r); + HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + const int oldsel = menu->sel_vis; + if (GET_X_LPARAM(lParam)>=r.left && + GET_X_LPARAM(lParam)top_margin + ) + { + int a = m_trackingMenus.Find(hwnd); + if (a>=0) + { + // step back through and make sure each menu has the correct item selected + HMENU__ *m = menu; + while (--a>=0) + { + HWND h = m_trackingMenus.Get(a); + if (WDL_NOT_NORMALLY(!h)) break; + HMENU__ *m2 = (HMENU__*)GetWindowLongPtr(h,GWLP_USERDATA); + if (WDL_NOT_NORMALLY(!m2)) break; + + MENUITEMINFO *s = m2->items.Get(m2->sel_vis); + if (!s || s->hSubMenu != m) + { + // find m in menu, select, invalidate + for (int x = 0; x < m2->items.GetSize(); x ++) + { + s = m2->items.Get(x); + if (s && s->hSubMenu == m) + { + m2->sel_vis = x; + InvalidateRect(h,NULL,FALSE); + break; + } + } + } + m = m2; + } + } + int mode = 3; // 4 was old-style super-fast menus + SendMessage(hwnd,WM_USER+100,mode,GET_Y_LPARAM(lParam)); + } + else menu->sel_vis = -1; + if (oldsel != menu->sel_vis) InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + { + if (swell_delegate_menu_message(hwnd, lParam, uMsg, false)) + return 0; + + RECT r; + GetClientRect(hwnd,&r); + if (GET_X_LPARAM(lParam)>=r.left && + GET_X_LPARAM(lParam)top_margin + ) + { + SendMessage(hwnd,WM_USER+100,2,GET_Y_LPARAM(lParam)); + return 0; + } + else DestroyWindow(hwnd); + } + return 0; + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + +bool PopupMenuIsActive(void) +{ + return m_trackingMenus.GetSize()>0; +} + +bool DestroyPopupMenus() +{ + if (!m_trackingMenus.GetSize()) return false; + DestroyWindow(m_trackingMenus.Get(0)); + return true; +} + +SWELL_OSWINDOW swell_ignore_focus_oswindow; +DWORD swell_ignore_focus_oswindow_until; + +int TrackPopupMenu(HMENU hMenu, int flags, int xpos, int ypos, int resvd, HWND hwnd, const RECT *r) +{ + if (WDL_NOT_NORMALLY(!hMenu) || m_trackingMenus.GetSize()) return 0; + + ReleaseCapture(); + + hMenu->Retain(); + m_trackingPar=hwnd; + m_trackingFlags=flags; + m_trackingRet=-1; + m_trackingPt2.x=m_trackingPt.x=xpos; + m_trackingPt2.y=m_trackingPt.y=ypos; + m_trackingMouseFlag = 0; + if (GetAsyncKeyState(VK_LBUTTON)) m_trackingMouseFlag |= 1; + if (GetAsyncKeyState(VK_RBUTTON)) m_trackingMouseFlag |= 2; + if (GetAsyncKeyState(VK_MBUTTON)) m_trackingMouseFlag |= 4; + +// HWND oldFoc = GetFocus(); + // bool oldFoc_child = oldFoc && (IsChild(hwnd,oldFoc) || oldFoc == hwnd || oldFoc==GetParent(hwnd)); + + if (hwnd) + { + hwnd->Retain(); + swell_ignore_focus_oswindow = hwnd->m_oswindow; + swell_ignore_focus_oswindow_until = GetTickCount()+500; + } + + + if (r && r->left == (1<<30) && r->top == (1<<30) && !r->right) + hMenu->sel_vis = r->bottom; + else + hMenu->sel_vis=-1; + + if (!resvd || resvd == 0xbeee) swell_menu_ignore_mousemove_from = GetTickCount(); + + HWND hh=new HWND__(NULL,0,NULL,"menu",false,submenuWndProc,NULL, hwnd); + + submenuWndProc(hh,WM_CREATE,0,(LPARAM)hMenu); + + SetProp(hh,"SWELL_MenuOwner",(HANDLE)hwnd); + + while (m_trackingRet<0 && m_trackingMenus.GetSize()) + { + void SWELL_RunMessageLoop(); + SWELL_RunMessageLoop(); + Sleep(10); + } + + int x=m_trackingMenus.GetSize()-1; + while (x>=0) + { + HWND h = m_trackingMenus.Get(x); + m_trackingMenus.Delete(x); + if (h) DestroyWindow(h); + x--; + } + +// if (oldFoc_child) SetFocus(oldFoc); + + if (!(flags&TPM_RETURNCMD) && m_trackingRet>0) + SendMessage(hwnd,WM_COMMAND,m_trackingRet,0); + + if (hwnd) hwnd->Release(); + + swell_ignore_focus_oswindow = NULL; + hMenu->Release(); + + m_trackingPar = NULL; + + if (flags & TPM_RETURNCMD) return m_trackingRet>0?m_trackingRet:0; + + return (resvd|1)!=0xbeef || m_trackingRet>0; +} + + + + +void SWELL_Menu_AddMenuItem(HMENU hMenu, const char *name, int idx, unsigned int flags) +{ + MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING, + (UINT)((flags)?MFS_GRAYED:0),(UINT)idx,NULL,NULL,NULL,0,(char *)name}; + if (!name) + { + mi.fType = MFT_SEPARATOR; + mi.fMask&=~(MIIM_STATE|MIIM_ID); + } + InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi); +} + + +SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; // todo: move to per-module thingy + +static SWELL_MenuResourceIndex *resById(SWELL_MenuResourceIndex *head, const char *resid) +{ + SWELL_MenuResourceIndex *p=head; + while (p) + { + if (p->resid == resid) return p; + p=p->_next; + } + return 0; +} + +HMENU SWELL_LoadMenu(SWELL_MenuResourceIndex *head, const char *resid) +{ + SWELL_MenuResourceIndex *p; + + if (!(p=resById(head,resid))) return 0; + HMENU hMenu=CreatePopupMenu(); + if (hMenu) p->createFunc(hMenu); + return hMenu; +} + +HMENU SWELL_DuplicateMenu(HMENU menu) +{ + if (WDL_NOT_NORMALLY(!menu)) return 0; + return menu->Duplicate(); +} + +BOOL SetMenu(HWND hwnd, HMENU menu) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + HMENU oldmenu = hwnd->m_menu; + + hwnd->m_menu = menu; + + if (!hwnd->m_parent && !!hwnd->m_menu != !!oldmenu) + { + WNDPROC oldwc = hwnd->m_wndproc; + hwnd->m_wndproc = DefWindowProc; + RECT r; + GetWindowRect(hwnd,&r); + + if (oldmenu) r.bottom -= g_swell_ctheme.menubar_height; // hack: we should WM_NCCALCSIZE before and after, really + else r.bottom += g_swell_ctheme.menubar_height; + + SetWindowPos(hwnd,NULL,0,0,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE); + hwnd->m_wndproc = oldwc; + // resize + } + + return TRUE; +} + +HMENU GetMenu(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + return hwnd->m_menu; +} + +void DrawMenuBar(HWND hwnd) +{ + if (WDL_NORMALLY(hwnd) && hwnd->m_menu) + { + RECT r; + GetClientRect(hwnd,&r); + r.top = - g_swell_ctheme.menubar_height; + r.bottom=0; + InvalidateRect(hwnd,&r,FALSE); + } +} + + +// copied from swell-menu.mm, can have a common impl someday +int SWELL_GenerateMenuFromList(HMENU hMenu, const void *_list, int listsz) +{ + SWELL_MenuGen_Entry *list = (SWELL_MenuGen_Entry *)_list; + const int l1=strlen(SWELL_MENUGEN_POPUP_PREFIX); + while (listsz>0) + { + int cnt=1; + if (!list->name) SWELL_Menu_AddMenuItem(hMenu,NULL,-1,0); + else if (!strcmp(list->name,SWELL_MENUGEN_ENDPOPUP)) return list + 1 - (SWELL_MenuGen_Entry *)_list; + else if (!strncmp(list->name,SWELL_MENUGEN_POPUP_PREFIX,l1)) + { + MENUITEMINFO mi={sizeof(mi),MIIM_SUBMENU|MIIM_STATE|MIIM_TYPE,MFT_STRING,0,0,CreatePopupMenuEx(list->name+l1),NULL,NULL,0,(char *)list->name+l1}; + cnt += SWELL_GenerateMenuFromList(mi.hSubMenu,list+1,listsz-1); + InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi); + } + else SWELL_Menu_AddMenuItem(hMenu,list->name,list->idx,list->flags); + + list+=cnt; + listsz -= cnt; + } + return list + 1 - (SWELL_MenuGen_Entry *)_list; +} +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menu.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menu.mm new file mode 100644 index 0000000..4328a70 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menu.mm @@ -0,0 +1,1069 @@ +/* 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. + + + This file provides basic windows menu API to interface an NSMenu + + */ + +#ifndef SWELL_PROVIDED_BY_APP + +#import + +#include "swell.h" +#include "swell-menugen.h" + +#include "swell-internal.h" + + +static void __filtnametobuf(char *out, const char *in, int outsz, NSString **eqout=NULL, unsigned int *eqmaskout=NULL); + + +bool SetMenuItemText(HMENU hMenu, int idx, int flag, const char *text) +{ + NSMenu *menu=(NSMenu *)hMenu; + if (WDL_NOT_NORMALLY(!menu)) return false; + + NSMenuItem *item; + if (flag & MF_BYPOSITION) item=[menu itemAtIndex:idx]; + else item =[menu itemWithTag:idx]; + if (!item) + { + if (!(flag & MF_BYPOSITION)) + { + const int n = (int) [menu numberOfItems]; + for (int x = 0; x < n; x ++) + { + item=[menu itemAtIndex:x]; + if (item && [item hasSubmenu]) + { + NSMenu *m=[item submenu]; + if (m && SetMenuItemText((HMENU)m,idx,flag,text)) return true; + } + } + } + return false; + } + char buf[1024]; + NSString *eq = NULL; + unsigned int eqmask = 0; + __filtnametobuf(buf,text?text:"",sizeof(buf), + (flag&MF_SWELL_DO_NOT_CALC_MODIFIERS) ? NULL : &eq,&eqmask); + NSString *label=(NSString *)SWELL_CStringToCFString(buf); + + [item setTitle:label]; + if ([item hasSubmenu] && [item submenu]) [[item submenu] setTitle:label]; + if (eq) + { + [item setKeyEquivalentModifierMask:eqmask]; + [item setKeyEquivalent:eq]; + } + + [label release]; + return true; +} + +bool EnableMenuItem(HMENU hMenu, int idx, int en) +{ + NSMenu *menu=(NSMenu *)hMenu; + if (WDL_NOT_NORMALLY(!menu)) return false; + + NSMenuItem *item; + if (en & MF_BYPOSITION) item=[menu itemAtIndex:idx]; + else item =[menu itemWithTag:idx]; + if (!item) + { + if (!(en & MF_BYPOSITION)) + { + const int n=(int)[menu numberOfItems]; + for (int x = 0; x < n; x ++) + { + item=[menu itemAtIndex:x]; + if (item && [item hasSubmenu]) + { + NSMenu *m=[item submenu]; + if (m && EnableMenuItem((HMENU)m,idx,en)) return true; + } + } + } + return false; + } + [item setEnabled:((en&MF_GRAYED)?NO:YES)]; + return true; +} + +bool CheckMenuItem(HMENU hMenu, int idx, int chk) +{ + NSMenu *menu=(NSMenu *)hMenu; + if (WDL_NOT_NORMALLY(!menu)) return false; + + NSMenuItem *item; + if (chk & MF_BYPOSITION) item=[menu itemAtIndex:idx]; + else item =[menu itemWithTag:idx]; + if (!item) + { + if (!(chk & MF_BYPOSITION)) + { + const int n=(int)[menu numberOfItems]; + for (int x = 0; x < n; x ++) + { + item=[menu itemAtIndex:x]; + if (item && [item hasSubmenu]) + { + NSMenu *m=[item submenu]; + if (m && CheckMenuItem((HMENU)m,idx,chk)) return true; + } + } + } + return false; + } + [item setState:((chk&MF_CHECKED)?NSOnState:NSOffState)]; + + return true; +} +HMENU SWELL_GetCurrentMenu() +{ + return (HMENU)[NSApp mainMenu]; +} + +extern int g_swell_terminating; + +void SWELL_SetCurrentMenu(HMENU hmenu) +{ + if (WDL_NORMALLY(hmenu && [(id)hmenu isKindOfClass:[NSMenu class]])) + { + if (!g_swell_terminating) [NSApp setMainMenu:(NSMenu *)hmenu]; + } +} + +HMENU GetSubMenu(HMENU hMenu, int pos) +{ + NSMenu *menu=(NSMenu *)hMenu; + WDL_ASSERT(menu != NULL); + + NSMenuItem *item=menu && pos >=0 && pos < [menu numberOfItems] ? [menu itemAtIndex:pos] : 0; + if (item && [item hasSubmenu]) return (HMENU)[item submenu]; + return 0; +} + +int GetMenuItemCount(HMENU hMenu) +{ + NSMenu *menu=(NSMenu *)hMenu; + WDL_ASSERT(menu != NULL); + return (int)[menu numberOfItems]; +} + +int GetMenuItemID(HMENU hMenu, int pos) +{ + NSMenu *menu=(NSMenu *)hMenu; + if (pos < 0 || pos >= (int)[menu numberOfItems]) + { + WDL_ASSERT(pos==0); // don't assert if GetMenuItemID(0) is called on an empty menu + return 0; + } + + NSMenuItem *item=[menu itemAtIndex:pos]; + if (item) + { + if ([item hasSubmenu]) return -1; + return (int)[item tag]; + } + return 0; +} + +bool SetMenuItemModifier(HMENU hMenu, int idx, int flag, int code, unsigned int mask) +{ + if (WDL_NOT_NORMALLY(hMenu == NULL)) return false; + + NSMenu *menu=(NSMenu *)hMenu; + + NSMenuItem *item; + if (flag & MF_BYPOSITION) item=[menu itemAtIndex:idx]; + else item =[menu itemWithTag:idx]; + if (!item) + { + if (!(flag & MF_BYPOSITION)) + { + const int n = (int)[menu numberOfItems]; + for (int x = 0; x < n; x ++) + { + item=[menu itemAtIndex:x]; + if (item && [item hasSubmenu]) + { + NSMenu *m=[item submenu]; + if (m && SetMenuItemModifier((HMENU)m,idx,flag,code,mask)) return true; + } + } + } + return false; + } + + bool suppressShift = false; + unichar arrowKey = 0; + + if (code >= 'A' && code <= 'Z') + { + arrowKey = (mask & FSHIFT) ? code : (code + 'a' - 'A'); + suppressShift=true; + } + else if ((code>='0' && code <= '9') || + code== ' ' || + (!(mask&FVIRTKEY) && (code >= '!' && code <= '~'))) + { + arrowKey=code; + } + else if (code >= VK_F1 && code <= VK_F24) + { + arrowKey = NSF1FunctionKey + code - VK_F1; + } + else switch (code&0xff) + { + #define DEFKP(wink,mack) case wink: arrowKey = mack; break; + DEFKP(VK_UP,NSUpArrowFunctionKey) + DEFKP(VK_DOWN,NSDownArrowFunctionKey) + DEFKP(VK_LEFT,NSLeftArrowFunctionKey) + DEFKP(VK_RIGHT,NSRightArrowFunctionKey) + DEFKP(VK_INSERT,NSInsertFunctionKey) + DEFKP(VK_DELETE,NSDeleteCharacter) + DEFKP(VK_BACK,NSBackspaceCharacter) + DEFKP(VK_HOME,NSHomeFunctionKey) + DEFKP(VK_END,NSEndFunctionKey) + DEFKP(VK_NEXT,NSPageDownFunctionKey) + DEFKP(VK_PRIOR,NSPageUpFunctionKey) + DEFKP(VK_SUBTRACT,'-') + DEFKP(VK_RETURN,'\r') + DEFKP(VK_TAB,'\t') + DEFKP(VK_ESCAPE,27) + // hmm numpad enter, what to do: DEFKP(VK_RETURN|32768, '\r') + } + + unsigned int mask2=0; + if (mask&FALT) mask2|=NSAlternateKeyMask; + if (!suppressShift) if (mask&FSHIFT) mask2|=NSShiftKeyMask; + if (mask&FCONTROL) mask2|=NSCommandKeyMask; + if (mask&FLWIN) mask2|=NSControlKeyMask; + + [item setKeyEquivalentModifierMask:mask2]; + [item setKeyEquivalent:arrowKey?[NSString stringWithCharacters:&arrowKey length:1]:@""]; + return true; +} + +static void __filtnametobuf(char *out, const char *input, int outsz, NSString **eqout, unsigned int *eqmaskout) +{ + const char *inp = input; + while (*inp && *inp != '\t' && outsz>1) + { + if (*inp == '&') inp++; + *out++=*inp++; + outsz--; + } + *out=0; + if (eqout && eqmaskout && *inp) + { + while (*inp && *inp != '\t') inp++; + if (*inp++ == '\t') + { +again: + while (*inp) + { + if (!strnicmp(inp,"Ctrl+",5)) { inp += 5; *eqmaskout |= NSCommandKeyMask; } + else if (!strnicmp(inp,"Alt+",4)) { inp += 4; *eqmaskout |= NSAlternateKeyMask; } + else if (!strnicmp(inp,"Shift+",6)) { inp += 6; *eqmaskout |= NSShiftKeyMask; } + else if (!strnicmp(inp,"Win+",4)) { inp += 4; *eqmaskout |= NSControlKeyMask; } + else break; + } + if (WDL_NOT_NORMALLY(!*inp)) return; + + char tmp[128]; + lstrcpyn(tmp,inp,sizeof(tmp)); + char *p = tmp; + while (*p && *p != ' ' && *p != ',') p++; + bool was_comma = *p == ','; + if (was_comma && p == tmp) p++; + *p=0; + if (WDL_NOT_NORMALLY(!tmp[0])) return; + + unichar arrowKey = 0; + int idx; + if (tmp[0] == 'F' && (idx=atoi(tmp+1))> 0 && idx <= 24) + arrowKey = NSF1FunctionKey + idx - 1; + else if (tmp[0] >= '0' && tmp[0] <= '9') + arrowKey = tmp[0]; + else + { + if (!stricmp(tmp,"Up")) arrowKey = NSUpArrowFunctionKey; + else if (!stricmp(tmp,"Down")) arrowKey = NSDownArrowFunctionKey; + else if (!stricmp(tmp,"Left")) arrowKey = NSLeftArrowFunctionKey; + else if (!stricmp(tmp,"Right")) arrowKey = NSRightArrowFunctionKey; + else if (!strnicmp(tmp,"Ins",3)) + { + if (was_comma) + { + while (*inp && *inp != ',') inp++; + if (*inp) + { + inp++; + while (*inp == ' ') inp++; + if (*inp) + { + *eqmaskout = 0; + goto again; + } + } + } + + arrowKey = NSInsertFunctionKey; + } + else if (!strnicmp(tmp,"Del",3)) arrowKey = NSDeleteFunctionKey; + else if (!strnicmp(tmp,"Back",4)) arrowKey = NSBackspaceCharacter; + else if (!stricmp(tmp,"Home")) arrowKey = NSHomeFunctionKey; + else if (!stricmp(tmp,"End")) arrowKey = NSEndFunctionKey; + else if (!stricmp(tmp,"PageDown") || !stricmp(tmp,"PgDn")) arrowKey = NSPageDownFunctionKey; + else if (!stricmp(tmp,"PageUp") || !stricmp(tmp,"PgUp")) arrowKey = NSPageUpFunctionKey; + else if (!stricmp(tmp,"PageUp") || !stricmp(tmp,"PgUp")) arrowKey = NSPageUpFunctionKey; + else if (!stricmp(tmp,"-")) arrowKey = '-'; + else if (!stricmp(tmp,"+")) arrowKey = '+'; + else if (!stricmp(tmp,"Enter")||!stricmp(tmp,"Return")) arrowKey = '\r'; + else if (!stricmp(tmp,"Tab")) arrowKey = '\t'; + else if (!strnicmp(tmp,"Esc",3)) arrowKey = 27; + else if (!strcmp(tmp,",")) arrowKey = tmp[0]; + else if (!stricmp(tmp,"Click")||!stricmp(tmp,"(Click)")) arrowKey = NSUpArrowFunctionKey; + else if (!stricmp(tmp,"Doubleclick")||!stricmp(tmp,"(Doubleclick)")) arrowKey = NSHomeFunctionKey; + else if (tmp[0] >= 'A' && tmp[0] <= 'Z' && !tmp[1]) + { + arrowKey = tmp[0]; + if (*eqmaskout & NSShiftKeyMask) + *eqmaskout &= ~NSShiftKeyMask; + else + arrowKey += 'a' - 'A'; + } + else if (!strcmp(tmp,"Ctrl")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSCommandKeyMask; } + else if (!strcmp(tmp,"Alt")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSAlternateKeyMask; } + else if (!strcmp(tmp,"Shift")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSShiftKeyMask; } + else if (!strcmp(tmp,"Win")) { arrowKey=NSUpArrowFunctionKey; *eqmaskout |= NSControlKeyMask; } + else + { + wdl_log("swell-cocoa: unknown key in menu text: '%s' orig '%s'\n",tmp,input); + } + } + if (arrowKey) *eqout = [NSString stringWithCharacters:&arrowKey length:1]; + } + } +} + +// #define SWELL_MENU_ACCOUNTING + +#ifdef SWELL_MENU_ACCOUNTING +struct menuTmp +{ + NSMenu *menu; + NSString *lbl; +}; + +WDL_PtrList allMenus; +#endif + +@implementation SWELL_Menu +- (id)copyWithZone:(NSZone *)zone +{ + id rv = [super copyWithZone:zone]; +#ifdef SWELL_MENU_ACCOUNTING + if (rv) + { + menuTmp *mt = new menuTmp; + mt->menu=(NSMenu *)rv; + NSString *lbl = [(SWELL_Menu *)rv title]; + mt->lbl = lbl; + [lbl retain]; + allMenus.Add(mt); + NSLog(@"copy menu, new count=%d lbl=%@\n",allMenus.GetSize(),lbl); + } +#endif + return rv; +} +-(void)dealloc +{ +#ifdef SWELL_MENU_ACCOUNTING + int x; + bool f=false; + for(x=0;xmenu == self) + { + NSLog(@"dealloc menu, found self %@\n",allMenus.Get(x)->lbl); + allMenus.Delete(x); + f=true; + break; + } + } + + NSLog(@"dealloc menu, new count=%d %@\n",allMenus.GetSize(), [self title]); + if (!f) + { + NSLog(@"deleting unfound menu!!\n"); + } +#endif + [super dealloc]; +} +@end + +HMENU CreatePopupMenu() +{ + return CreatePopupMenuEx(NULL); +} +HMENU CreatePopupMenuEx(const char *title) +{ + SWELL_Menu *m; + if (title) + { + char buf[1024]; + __filtnametobuf(buf,title,sizeof(buf)); + NSString *lbl=(NSString *)SWELL_CStringToCFString(buf); + m=[[SWELL_Menu alloc] initWithTitle:lbl]; +#ifdef SWELL_MENU_ACCOUNTING + menuTmp *mt = new menuTmp; + mt->menu=m; + mt->lbl = lbl; + [lbl retain]; + allMenus.Add(mt); + NSLog(@"alloc menu, new count=%d lbl=%@\n",allMenus.GetSize(),lbl); +#endif + [lbl release]; + } + else + { + m=[[SWELL_Menu alloc] init]; +#ifdef SWELL_MENU_ACCOUNTING + menuTmp *mt = new menuTmp; + mt->menu=m; + mt->lbl = @""; + allMenus.Add(mt); + NSLog(@"alloc menu, new count=%d lbl=%@\n",allMenus.GetSize(),@""); +#endif + } + [m setAutoenablesItems:NO]; + + return (HMENU)m; +} + +void DestroyMenu(HMENU hMenu) +{ + if (WDL_NORMALLY(hMenu)) + { + SWELL_SetMenuDestination(hMenu,NULL); + NSMenu *m=(NSMenu *)hMenu; + [m release]; + } +} + + + +int AddMenuItem(HMENU hMenu, int pos, const char *name, int tagid) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return -1; + NSMenu *m=(NSMenu *)hMenu; + NSString *label=(NSString *)SWELL_CStringToCFString(name); + NSMenuItem *item=[m insertItemWithTitle:label action:NULL keyEquivalent:@"" atIndex:pos]; + [label release]; + [item setTag:tagid]; + [item setEnabled:YES]; + return 0; +} + +bool DeleteMenu(HMENU hMenu, int idx, int flag) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return false; + NSMenu *m=(NSMenu *)hMenu; + NSMenuItem *item=NULL; + + if (flag&MF_BYPOSITION) + { + if (idx >=0 && idx < [m numberOfItems]) + item=[m itemAtIndex:idx]; + if (!item) return false; + } + else + { + item=[m itemWithTag:idx]; + if (!item) + { + const int n = (int) [m numberOfItems]; + for (int x=0;xfMask & MIIM_TYPE) + { + if ((mi->fType &~ MFT_RADIOCHECK) == MFT_STRING && mi->dwTypeData) + { + char buf[1024]; + NSString *eq=NULL; + unsigned int eqmask = 0; + __filtnametobuf(buf,mi->dwTypeData?mi->dwTypeData:"(null)",sizeof(buf), + (mi->fMask & MIIM_SWELL_DO_NOT_CALC_MODIFIERS) ? NULL : &eq,&eqmask); + NSString *label=(NSString *)SWELL_CStringToCFString(buf); + + [item setTitle:label]; + + if ([item hasSubmenu]) + { + NSMenu *subm=[item submenu]; + if (subm) [subm setTitle:label]; + } + if (eq) + { + [item setKeyEquivalentModifierMask:eqmask]; + [item setKeyEquivalent:eq]; + } + + [label release]; + } + } + if (mi->fMask & MIIM_SUBMENU) + { + NSMenu *oldMenu = [item hasSubmenu] ? [item submenu] : NULL; + NSMenu *newMenu = (NSMenu*)mi->hSubMenu; + if (oldMenu != newMenu) + { + if (oldMenu) [oldMenu retain]; // we do not destroy the old menu, caller responsibility + + if (newMenu) [newMenu setTitle:[item title]]; + [m setSubmenu:newMenu forItem:item]; + if (newMenu) [newMenu release]; // let the parent menu free it + } + } + + if (mi->fMask & MIIM_STATE) + { + [item setState:((mi->fState&MFS_CHECKED)?NSOnState:NSOffState)]; + [item setEnabled:((mi->fState&MFS_GRAYED)?NO:YES)]; + } + if (mi->fMask & MIIM_ID) + { + [item setTag:mi->wID]; + } + if (mi->fMask & MIIM_DATA) + { + SWELL_DataHold* newh = [[SWELL_DataHold alloc] initWithVal:(void*)mi->dwItemData]; + [item setRepresentedObject:newh]; + [newh release]; + } + + return true; +} + +BOOL GetMenuItemInfo(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return 0; + NSMenu *m=(NSMenu *)hMenu; + NSMenuItem *item; + if (byPos) + { + item=[m itemAtIndex:pos]; + } + else item=[m itemWithTag:pos]; + + if (!item) + { + if (!byPos) + { + const int n = (int)[m numberOfItems]; + for (int x = 0; x < n; x ++) + { + item=[m itemAtIndex:x]; + if (item && [item hasSubmenu]) + { + NSMenu *m1=[item submenu]; + if (m1 && GetMenuItemInfo((HMENU)m1,pos,byPos,mi)) return true; + } + } + } + return 0; + } + + if (mi->fMask & MIIM_TYPE) + { + if ([item isSeparatorItem]) mi->fType = MFT_SEPARATOR; + else + { + mi->fType = MFT_STRING; + if (mi->dwTypeData && mi->cch) + { + mi->dwTypeData[0]=0; + SWELL_CFStringToCString([item title], (char *)mi->dwTypeData, mi->cch); + } + } + } + + if (mi->fMask & MIIM_DATA) + { + SWELL_DataHold *h=[item representedObject]; + mi->dwItemData = (INT_PTR)(h && [h isKindOfClass:[SWELL_DataHold class]]? [h getValue] : 0); + } + + if (mi->fMask & MIIM_STATE) + { + mi->fState=0; + if ([item state]) mi->fState|=MFS_CHECKED; + if (![item isEnabled]) mi->fState|=MFS_GRAYED; + } + + if (mi->fMask & MIIM_ID) + { + mi->wID = (unsigned int)[item tag]; + } + + if(mi->fMask & MIIM_SUBMENU) + { + mi->hSubMenu = (HMENU) ([item hasSubmenu] ? [item submenu] : NULL); + } + + return 1; + +} + +void SWELL_InsertMenu(HMENU menu, int pos, unsigned int flag, UINT_PTR idx, const char *str) +{ + MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING, + (flag & ~MF_BYPOSITION),(flag&MF_POPUP) ? 0 : (unsigned int)idx,NULL,NULL,NULL,0,(char *)str}; + + if (flag&MF_POPUP) + { + mi.hSubMenu = (HMENU)idx; + mi.fMask |= MIIM_SUBMENU; + mi.fState &= ~MF_POPUP; + } + + if (flag&MF_SEPARATOR) + { + mi.fMask=MIIM_TYPE; + mi.fType=MFT_SEPARATOR; + mi.fState &= ~MF_SEPARATOR; + } + + if (flag&MF_BITMAP) + { + mi.fType=MFT_BITMAP; + mi.fState &= ~MF_BITMAP; + } + + InsertMenuItem(menu,pos,(flag&MF_BYPOSITION) ? TRUE : FALSE, &mi); +} + + +void InsertMenuItem(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) +{ + if (WDL_NOT_NORMALLY(!hMenu)) return; + NSMenu *m=(NSMenu *)hMenu; + NSMenuItem *item; + int ni = (int)[m numberOfItems]; + + if (!byPos) + { + pos = (int)[m indexOfItemWithTag:pos]; + } + if (pos < 0 || pos > ni) pos=ni; + + NSString *label=0; + if ((mi->fType & ~MFT_RADIOCHECK) == MFT_STRING) + { + char buf[1024]; + NSString *eq=NULL; + unsigned int eqmask = 0; + __filtnametobuf(buf,mi->dwTypeData?mi->dwTypeData:"(null)",sizeof(buf), + (mi->fMask & MIIM_SWELL_DO_NOT_CALC_MODIFIERS) ? NULL : &eq,&eqmask); + label=(NSString *)SWELL_CStringToCFString(buf); + item=[m insertItemWithTitle:label action:NULL keyEquivalent:@"" atIndex:pos]; + if (eq) + { + [item setKeyEquivalentModifierMask:eqmask]; + [item setKeyEquivalent:eq]; + } + } + else if (mi->fType == MFT_BITMAP) + { + item=[m insertItemWithTitle:@"(no image)" action:NULL keyEquivalent:@"" atIndex:pos]; + if (mi->dwTypeData) + { + NSImage *i=(NSImage *)GetNSImageFromHICON((HICON)mi->dwTypeData); + if (i) + { + [item setImage:i]; + [item setTitle:@""]; + } + } + } + else + { + item = [NSMenuItem separatorItem]; + [m insertItem:item atIndex:pos]; + } + + if ((mi->fMask & MIIM_SUBMENU) && mi->hSubMenu) + { + if (label) [(NSMenu *)mi->hSubMenu setTitle:label]; + [m setSubmenu:(NSMenu *)mi->hSubMenu forItem:item]; + [((NSMenu *)mi->hSubMenu) release]; // let the parent menu free it + } + if (label) [label release]; + + if (!ni) [m setAutoenablesItems:NO]; + [item setEnabled:YES]; + + if (mi->fMask & MIIM_STATE) + { + if (mi->fState&MFS_GRAYED) + { + [item setEnabled:NO]; + } + if (mi->fState&MFS_CHECKED) + { + [item setState:NSOnState]; + } + } + + if (mi->fMask & MIIM_DATA) + { + SWELL_DataHold *h=[[SWELL_DataHold alloc] initWithVal:(void*)mi->dwItemData]; + [item setRepresentedObject:h]; + [h release]; + } + else + { + [item setRepresentedObject:nil]; + } + + if (mi->fMask & MIIM_ID) + { + [item setTag:mi->wID]; + } + + int i; + ni = (int)[m numberOfItems]; + // try to find a valid action/target + for (i = 0; i < ni; i ++) + { + NSMenuItem *fi=[m itemAtIndex:i]; + if (fi && fi != item) + { + SEL act = [fi action]; + id tgt = [fi target]; + if (act || tgt) + { + if (act) [item setAction:act]; + if (tgt) [item setTarget:tgt]; + break; + } + } + if (i == 5 && ni > 14) i = ni-6; // only look at first and last 6 items or so + } +} + + + +@implementation SWELL_PopupMenuRecv +-(id) initWithWnd:(HWND)wnd +{ + if ((self = [super init])) + { + cbwnd=wnd; + m_act=0; + } + return self; +} + +-(void) onSwellCommand:(id)sender +{ + int tag=(int) [sender tag]; + if (tag) + m_act=tag; +} + +-(int) isCommand +{ + return m_act; +} + +- (void)menuNeedsUpdate:(NSMenu *)menu +{ + if (cbwnd) + { + SendMessage(cbwnd,WM_INITMENUPOPUP,(WPARAM)menu,0); + SWELL_SetMenuDestination((HMENU)menu,(HWND)self); + } +} + +@end + +static void SWELL_SetMenuDestinationInt(NSMenu *m, HWND hwnd, bool is_top_level, bool do_skip_sub) +{ + [m setDelegate:(id)hwnd]; + const int n = (int)[m numberOfItems]; + for (int x = 0; x < n; x++) + { + NSMenuItem *item=[m itemAtIndex:x]; + if (item) + { + if ([item hasSubmenu]) + { + NSMenu *mm=[item submenu]; + if (mm) + { + if (do_skip_sub) + { + id del = [mm delegate]; + NSString *cn = del ? [del className] : NULL; + if (cn) + { + char buf[1024]; + SWELL_CFStringToCString(cn, buf, sizeof(buf)); + if (strstr(buf,"NSServices")) continue; + } + } + SWELL_SetMenuDestinationInt(mm,hwnd,false, is_top_level && !x); + } + } + else + { + if ([item tag]) + { + [item setTarget:(id)hwnd]; + if (hwnd) [item setAction:@selector(onSwellCommand:)]; + } + } + } + } +} + +void SWELL_SetMenuDestination(HMENU menu, HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!menu || (hwnd && ![(id)hwnd respondsToSelector:@selector(onSwellCommand:)]))) return; + + NSMenu *m = (NSMenu *)menu, *par = [m supermenu]; + + bool do_skip_sub = false; + if (par && ![par supermenu] && [par numberOfItems]>0) + { + NSMenuItem *item = [par itemAtIndex:0]; + do_skip_sub = item && [item hasSubmenu] && [item submenu] == m; + } + SWELL_SetMenuDestinationInt(m,hwnd,!par,do_skip_sub); +} + +int TrackPopupMenu(HMENU hMenu, int flags, int xpos, int ypos, int resvd, HWND hwnd, const RECT *r) +{ + ReleaseCapture(); // match win32 -- TrackPopupMenu() ends any captures + if (WDL_NORMALLY(hMenu)) + { + NSMenu *m=(NSMenu *)hMenu; + NSView *v=(NSView *)hwnd; + if (v && [v isKindOfClass:[NSWindow class]]) v=[(NSWindow *)v contentView]; + if (!v) v=[[NSApp mainWindow] contentView]; + if (!v) return 0; + + NSEvent *event = [NSApp currentEvent]; + + { + //create a new event at these coordinates, faking it + NSWindow *w = [v window]; + NSPoint pt = NSMakePoint(xpos, ypos); + pt=[w convertScreenToBase:pt]; + pt.y -= 4; + NSInteger wn = [w windowNumber]; // event ? [event windowNumber] : [w windowNumber]; + NSTimeInterval ts = event ? [event timestamp] : 0; + NSGraphicsContext *gctx = event ? [event context] : 0; + event = [NSEvent otherEventWithType:NSApplicationDefined location:pt modifierFlags:0 timestamp:ts windowNumber:wn context:gctx subtype:0 data1:0 data2:0]; + } + + SWELL_PopupMenuRecv *recv = [[SWELL_PopupMenuRecv alloc] initWithWnd:((flags & TPM_NONOTIFY) ? 0 : hwnd)]; + + SWELL_SetMenuDestination((HMENU)m,(HWND)recv); + + if (!(flags&TPM_NONOTIFY)&&hwnd) + { + SendMessage(hwnd,WM_INITMENUPOPUP,(WPARAM)m,0); + SWELL_SetMenuDestination((HMENU)m,(HWND)recv); + } + + [NSMenu popUpContextMenu:m withEvent:event forView:v]; + + int ret=[recv isCommand]; + SWELL_SetMenuDestination((HMENU)m,(HWND)NULL); + [recv release]; + + if (ret<=0) return 0; + + if (flags & TPM_RETURNCMD) return ret; + + if (hwnd) SendMessage(hwnd,WM_COMMAND,ret,0); + + return 1; + } + return 0; +} + + + + +void SWELL_Menu_AddMenuItem(HMENU hMenu, const char *name, int idx, unsigned int flags) +{ + MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING, + (unsigned int) ((flags)?MFS_GRAYED:0),(unsigned int)idx,NULL,NULL,NULL,0,(char *)name}; + if (!name) + { + mi.fType = MFT_SEPARATOR; + mi.fMask&=~(MIIM_STATE|MIIM_ID); + } + InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi); +} + +int SWELL_GenerateMenuFromList(HMENU hMenu, const void *_list, int listsz) +{ + SWELL_MenuGen_Entry *list = (SWELL_MenuGen_Entry *)_list; + const int l1=strlen(SWELL_MENUGEN_POPUP_PREFIX); + while (listsz>0) + { + int cnt=1; + if (!list->name) SWELL_Menu_AddMenuItem(hMenu,NULL,-1,0); + else if (!strcmp(list->name,SWELL_MENUGEN_ENDPOPUP)) break; + else if (!strncmp(list->name,SWELL_MENUGEN_POPUP_PREFIX,l1)) + { + MENUITEMINFO mi={sizeof(mi),MIIM_SUBMENU|MIIM_STATE|MIIM_TYPE,MFT_STRING,0,0,CreatePopupMenuEx(list->name+l1),NULL,NULL,0,(char *)list->name+l1}; + cnt += SWELL_GenerateMenuFromList(mi.hSubMenu,list+1,listsz-1); + InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi); + } + else SWELL_Menu_AddMenuItem(hMenu,list->name,list->idx,list->flags); + + list+=cnt; + listsz -= cnt; + } + return (int) (list + 1 - (SWELL_MenuGen_Entry *)_list); +} + + +SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; // todo: move to per-module thingy + +static SWELL_MenuResourceIndex *resById(SWELL_MenuResourceIndex *head, const char *resid) +{ + SWELL_MenuResourceIndex *p=head; + while (p) + { + if (p->resid == resid) return p; + p=p->_next; + } + return 0; +} + +HMENU SWELL_LoadMenu(SWELL_MenuResourceIndex *head, const char *resid) +{ + SWELL_MenuResourceIndex *p; + + if (!(p=resById(head,resid))) return 0; + HMENU hMenu=CreatePopupMenu(); + if (hMenu) p->createFunc(hMenu); + return hMenu; +} + +HMENU SWELL_DuplicateMenu(HMENU menu) +{ + if (WDL_NOT_NORMALLY(!menu)) return 0; + NSMenu *ret = (NSMenu *)[(NSMenu *)menu copy]; + return (HMENU)ret; +} + +BOOL SetMenu(HWND hwnd, HMENU menu) +{ + if (WDL_NOT_NORMALLY(!hwnd||![(id)hwnd respondsToSelector:@selector(swellSetMenu:)])) return FALSE; + if (g_swell_terminating) return FALSE; + + [(id)hwnd swellSetMenu:(HMENU)menu]; + NSWindow *nswnd = (NSWindow *)hwnd; + if ([nswnd isKindOfClass:[NSWindow class]] || + ([nswnd isKindOfClass:[NSView class]] && (nswnd=[(NSView *)nswnd window]) && hwnd == (HWND)[nswnd contentView])) + { + if ([NSApp keyWindow]==nswnd && + [NSApp mainMenu] != (NSMenu *)menu) + { + [NSApp setMainMenu:(NSMenu *)menu]; + if (menu) SendMessage(hwnd,WM_INITMENUPOPUP,(WPARAM)menu,0); // find a better place for this! TODO !!! + } + } + + return TRUE; +} + +HMENU GetMenu(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return NULL; + if ([(id)hwnd isKindOfClass:[NSWindow class]]) hwnd = (HWND)[(NSWindow *)hwnd contentView]; + if ([(id)hwnd respondsToSelector:@selector(swellGetMenu)]) return (HMENU) [(id)hwnd swellGetMenu]; + return NULL; +} + +void DrawMenuBar(HWND hwnd) +{ +} + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menugen.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menugen.h new file mode 100644 index 0000000..c83f586 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-menugen.h @@ -0,0 +1,93 @@ +#ifndef _SWELL_MENUGEN_H_ +#define _SWELL_MENUGEN_H_ + + +/* 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. + + Dynamic menu generation + + Usage: + + See: swell_resgen.pl etc + + */ + + +#include "swell.h" + + +#ifdef BEGIN +#undef BEGIN +#endif + +#ifdef END +#undef END +#endif + + +typedef struct SWELL_MenuResourceIndex +{ + const char *resid; + void (*createFunc)(HMENU hMenu); + struct SWELL_MenuResourceIndex *_next; +} SWELL_MenuResourceIndex; +extern SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; + + +#define SWELL_MENUGEN_POPUP_PREFIX "/.BO^O:" +#define SWELL_MENUGEN_ENDPOPUP "EN%%%^:" +struct SWELL_MenuGen_Entry +{ + const char *name; // will begin with SWELL_MENUGEN_POPUP_PREFIX on submenus, and will be SWELL_MENUGEN_ENDPOPUP at the end of a submenu + unsigned short idx; + unsigned short flags; +}; + +class SWELL_MenuGenHelper +{ + public: + SWELL_MenuResourceIndex m_rec; + SWELL_MenuGenHelper(SWELL_MenuResourceIndex **h, void (*cf)(HMENU), int recid) + { + m_rec.resid=MAKEINTRESOURCE(recid); + m_rec.createFunc=cf; + m_rec._next=*h; + *h = &m_rec; + } +}; + +#define SWELL_DEFINE_MENU_RESOURCE_BEGIN(recid) \ + static void __swell_menu_cf__##recid(HMENU hMenu); \ + static SWELL_MenuGenHelper __swell_menu_cf_helper__##recid(&SWELL_curmodule_menuresource_head, __swell_menu_cf__##recid, recid); \ + static void __swell_menu_cf__##recid(HMENU hMenu) { static const SWELL_MenuGen_Entry list[]={{NULL,0,0 + +#define SWELL_DEFINE_MENU_RESOURCE_END(recid) } }; SWELL_GenerateMenuFromList(hMenu,list+1,sizeof(list)/sizeof(list[0])-1); } + + +#define GRAYED 1 +#define INACTIVE 2 +#define POPUP }, { SWELL_MENUGEN_POPUP_PREFIX +#define MENUITEM }, { +#define SEPARATOR NULL, 0xffff +#define BEGIN +#define END }, { SWELL_MENUGEN_ENDPOPUP + +#endif//_SWELL_MENUGEN_H_ + diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-misc-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-misc-generic.cpp new file mode 100644 index 0000000..3302d62 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-misc-generic.cpp @@ -0,0 +1,97 @@ +/* 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. +*/ + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-internal.h" + +#ifndef __APPLE__ +#include +#include +#endif + +bool IsRightClickEmulateEnabled() +{ + return false; +} + +void SWELL_EnableRightClickEmulate(BOOL enable) +{ +} + + +HANDLE SWELL_CreateProcessFromPID(int pid) +{ + SWELL_InternalObjectHeader_PID *buf = (SWELL_InternalObjectHeader_PID*)malloc(sizeof(SWELL_InternalObjectHeader_PID)); + buf->hdr.type = INTERNAL_OBJECT_PID; + buf->hdr.count = 1; + buf->pid = (int) pid; + buf->done = buf->result = 0; + return (HANDLE) buf; +} + +HANDLE SWELL_CreateProcess(const char *exe, int nparams, const char **params) +{ + void swell_cleanupZombies(); + swell_cleanupZombies(); + + const pid_t pid = fork(); + if (pid == 0) + { + char **pp = (char **)calloc(nparams+2,sizeof(char*)); + pp[0] = strdup(exe); + for (int x=0;xhdr.type != INTERNAL_OBJECT_PID|| !hdr->pid) return -1; + if (hdr->done) return hdr->result; + + int wstatus=0; + pid_t v = waitpid((pid_t)hdr->pid,&wstatus,WNOHANG); + if (v <= 0) return -2; + hdr->done = 1; + return hdr->result = WEXITSTATUS(wstatus); +} + + +#ifndef SWELL_TARGET_GDK +BOOL EnumDisplayMonitors(HDC hdc,const LPRECT r,MONITORENUMPROC proc,LPARAM lParam) +{ + return FALSE; +} +BOOL GetMonitorInfo(HMONITOR hmon, void *inf) +{ + return FALSE; +} +#endif + + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-misc.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-misc.mm new file mode 100644 index 0000000..8939d95 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-misc.mm @@ -0,0 +1,890 @@ +/* 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. +*/ + +#ifndef SWELL_PROVIDED_BY_APP + +//#import +#import +#include +#include +#include "swell.h" +#define SWELL_IMPLEMENT_GETOSXVERSION +#include "swell-internal.h" + +#include "../mutex.h" + +HWND g_swell_only_timerhwnd; + +@implementation SWELL_TimerFuncTarget + +-(id) initWithId:(UINT_PTR)tid hwnd:(HWND)h callback:(TIMERPROC)cb +{ + if ((self = [super init])) + { + m_hwnd=h; + m_cb=cb; + m_timerid = tid; + } + return self; +} +-(void)SWELL_Timer:(id)sender +{ + if (g_swell_only_timerhwnd && m_hwnd != g_swell_only_timerhwnd) return; + + m_cb(m_hwnd,WM_TIMER,m_timerid,GetTickCount()); +} +@end + +@implementation SWELL_DataHold +-(id) initWithVal:(void *)val +{ + if ((self = [super init])) + { + m_data=val; + } + return self; +} +-(void *) getValue +{ + return m_data; +} +@end + +void SWELL_CFStringToCString(const void *str, char *buf, int buflen) +{ + NSString *s = (NSString *)str; + if (buflen>0) *buf=0; + if (buflen <= 1 || !s || [s getCString:buf maxLength:buflen encoding:NSUTF8StringEncoding]) return; // should always work, I'd hope (ambiguous documentation ugh) + + NSData *data = [s dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + if (!data) + { + [s getCString:buf maxLength:buflen encoding:NSASCIIStringEncoding]; + return; + } + int len = (int)[data length]; + if (len > buflen-1) len=buflen-1; + [data getBytes:buf length:len]; + buf[len]=0; +} + +void *SWELL_CStringToCFString(const char *str) +{ + if (!str) str=""; + void *ret; + + ret=(void *)CFStringCreateWithCString(NULL,str,kCFStringEncodingUTF8); + if (ret) return ret; + ret=(void*)CFStringCreateWithCString(NULL,str,kCFStringEncodingASCII); + return ret; +} + +void SWELL_MakeProcessFront(HANDLE h) +{ + SWELL_InternalObjectHeader_NSTask *buf = (SWELL_InternalObjectHeader_NSTask*)h; + if (buf && buf->hdr.type == INTERNAL_OBJECT_NSTASK && buf->task) + { + ProcessSerialNumber psn; + + int pid=[(id)buf->task processIdentifier]; + // try for 1sec to get the PSN, if it fails + int n = 20; + while (GetProcessForPID(pid,&psn) != noErr && n-- > 0) + { + Sleep(50); + } + if (n>0) SetFrontProcess(&psn); + } +} + +void SWELL_ReleaseNSTask(void *p) +{ + NSTask *a =(NSTask*)p; + if (a) + { + if (![a isRunning]) [a waitUntilExit]; // workaround for the system keeping a reference held on macOS 12+ at least + [a release]; + } +} +DWORD SWELL_WaitForNSTask(void *p, DWORD msTO) +{ + NSTask *a =(NSTask*)p; + const DWORD t = GetTickCount(); + do + { + if (![a isRunning]) return WAIT_OBJECT_0; + if (msTO) Sleep(1); + } + while (msTO && (GetTickCount()-t) < msTO); + + return [a isRunning] ? WAIT_TIMEOUT : WAIT_OBJECT_0; +} + +HANDLE SWELL_CreateProcessIO(const char *exe, int nparams, const char **params, bool redirectIO) +{ + NSString *ex = (NSString *)SWELL_CStringToCFString(exe); + NSMutableArray *ar = [[NSMutableArray alloc] initWithCapacity:nparams]; + + int x; + for (x=0;x hdr.type = INTERNAL_OBJECT_NSTASK; + buf->hdr.count=1; + buf->task = tsk; + return buf; +} + +HANDLE SWELL_CreateProcess(const char *exe, int nparams, const char **params) +{ + return SWELL_CreateProcessIO(exe,nparams,params,false); +} + + +int SWELL_GetProcessExitCode(HANDLE hand) +{ + int rv=0; + SWELL_InternalObjectHeader_NSTask *hdr=(SWELL_InternalObjectHeader_NSTask*)hand; + if (!hdr || hdr->hdr.type != INTERNAL_OBJECT_NSTASK || !hdr->task) return -1; + @try { + if ([(NSTask *)hdr->task isRunning]) rv=-3; + else rv = [(NSTask *)hdr->task terminationStatus]; + } + @catch (id ex) { + rv=-2; + } + return rv; +} + +int SWELL_TerminateProcess(HANDLE hand) +{ + int rv=0; + SWELL_InternalObjectHeader_NSTask *hdr=(SWELL_InternalObjectHeader_NSTask*)hand; + if (!hdr || hdr->hdr.type != INTERNAL_OBJECT_NSTASK || !hdr->task) return -1; + @try + { + [(NSTask *)hdr->task terminate]; + } + @catch (id ex) { + rv=-2; + } + return rv; +} +int SWELL_ReadWriteProcessIO(HANDLE hand, int w/*stdin,stdout,stderr*/, char *buf, int bufsz) +{ + SWELL_InternalObjectHeader_NSTask *hdr=(SWELL_InternalObjectHeader_NSTask*)hand; + if (!hdr || hdr->hdr.type != INTERNAL_OBJECT_NSTASK || !hdr->task) return 0; + NSTask *tsk = (NSTask*)hdr->task; + NSPipe *pipe = NULL; + bool async_mode = false; + if (w & (1<<24)) + { + async_mode = true; + w &= ~ (1<<24); + } + switch (w) + { + case 0: pipe = [tsk standardInput]; break; + case 1: pipe = [tsk standardOutput]; break; + case 2: pipe = [tsk standardError]; break; + } + if (!pipe || ![pipe isKindOfClass:[NSPipe class]]) return 0; + + NSFileHandle *fh = w!=0 ? [pipe fileHandleForReading] : [pipe fileHandleForWriting]; + if (!fh) return 0; + if (w==0) + { + if (bufsz>0) + { + NSData *d = [NSData dataWithBytes:buf length:bufsz]; + @try + { + if (d) [fh writeData:d]; + else bufsz=0; + } + @catch (id ex) { bufsz=0; } + + return bufsz; + } + } + else + { + if (async_mode) + { + int handle = [fh fileDescriptor]; + if (handle >= 0) + { + struct pollfd pl = { handle, POLLIN }; + if (poll(&pl,1,0)<1) return 0; + + return (int)read(handle,buf,bufsz); + } + } + NSData *d = NULL; + @try + { + d = [fh readDataOfLength:(bufsz < 1 ? 32768 : bufsz)]; + } + @catch (id ex) { } + + if (!d || bufsz < 1) return d ? (int)[d length] : 0; + int l = (int)[d length]; + if (l > bufsz) l = bufsz; + [d getBytes:buf length:l]; + return l; + } + + return 0; +} + + +@implementation SWELL_ThreadTmp +-(void)bla:(id)obj +{ + if (a) + { + DWORD (*func)(void *); + *(void **)(&func) = a; + func(b); + } + [NSThread exit]; +} +@end + +void SWELL_EnsureMultithreadedCocoa() +{ + static int a; + if (!a) + { + a++; + if (![NSThread isMultiThreaded]) // force cocoa into multithreaded mode + { + SWELL_ThreadTmp *t=[[SWELL_ThreadTmp alloc] init]; + t->a=0; + t->b=0; + [NSThread detachNewThreadSelector:@selector(bla:) toTarget:t withObject:t]; + /// [t release]; + } + } +} + +void CreateThreadNS(void *TA, DWORD stackSize, DWORD (*ThreadProc)(LPVOID), LPVOID parm, DWORD cf, DWORD *tidOut) +{ + SWELL_ThreadTmp *t=[[SWELL_ThreadTmp alloc] init]; + t->a=(void*)ThreadProc; + t->b=parm; + return [NSThread detachNewThreadSelector:@selector(bla:) toTarget:t withObject:t]; +} + + +// used by swell.cpp (lazy these should go elsewhere) +void *SWELL_InitAutoRelease() +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + return (void *)pool; +} +void SWELL_QuitAutoRelease(void *p) +{ + if (p) + [(NSAutoreleasePool*)p release]; +} + +void SWELL_RunEvents() +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int x=100; + while (x-- > 0) + { + NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate dateWithTimeIntervalSinceNow:0.001] inMode:NSDefaultRunLoopMode dequeue:YES]; + if (!event) break; + [NSApp sendEvent:event]; + } + [pool release]; +} + +// timer stuff +typedef struct TimerInfoRec +{ + UINT_PTR timerid; + HWND hwnd; + NSTimer *timer; + struct TimerInfoRec *_next; +} TimerInfoRec; +static TimerInfoRec *m_timer_list; +static WDL_Mutex m_timermutex; +#ifndef SWELL_NO_POSTMESSAGE +static pthread_t m_pmq_mainthread; +static void SWELL_pmq_settimer(HWND h, UINT_PTR timerid, UINT rate, TIMERPROC tProc); +#endif + +UINT_PTR SetTimer(HWND hwnd, UINT_PTR timerid, UINT rate, TIMERPROC tProc) +{ + if (!hwnd && !tProc) return 0; // must have either callback or hwnd + + if (hwnd && !timerid) return 0; + +#ifndef SWELL_NO_POSTMESSAGE + if (timerid != ~(UINT_PTR)0 && m_pmq_mainthread && pthread_self()!=m_pmq_mainthread) + { + SWELL_pmq_settimer(hwnd,timerid,(rate==(UINT)-1)?((UINT)-2):rate,tProc); + return timerid; + } +#endif + + + if (hwnd && ![(id)hwnd respondsToSelector:@selector(SWELL_Timer:)]) + { + if (![(id)hwnd isKindOfClass:[NSWindow class]]) return 0; + hwnd=(HWND)[(id)hwnd contentView]; + if (![(id)hwnd respondsToSelector:@selector(SWELL_Timer:)]) return 0; + } + + WDL_MutexLock lock(&m_timermutex); + TimerInfoRec *rec=NULL; + if (hwnd||timerid) + { + rec = m_timer_list; + while (rec) + { + if (rec->timerid == timerid && rec->hwnd == hwnd) // works for both kinds + break; + rec=rec->_next; + } + } + + bool recAdd=false; + if (!rec) + { + rec=(TimerInfoRec*)malloc(sizeof(TimerInfoRec)); + recAdd=true; + } + else + { + [rec->timer invalidate]; + rec->timer=0; + } + + rec->timerid=timerid; + rec->hwnd=hwnd; + + if (!hwnd || tProc) + { + // set timer to this unique ptr + if (!hwnd) timerid = rec->timerid = (UINT_PTR)rec; + + SWELL_TimerFuncTarget *t = [[SWELL_TimerFuncTarget alloc] initWithId:timerid hwnd:hwnd callback:tProc]; + rec->timer = [NSTimer scheduledTimerWithTimeInterval:(wdl_max(rate,1)*0.001) target:t selector:@selector(SWELL_Timer:) + userInfo:t repeats:YES]; + [t release]; + + } + else + { + SWELL_DataHold *t=[[SWELL_DataHold alloc] initWithVal:(void *)timerid]; + rec->timer = [NSTimer scheduledTimerWithTimeInterval:(wdl_max(rate,1)*0.001) target:(id)hwnd selector:@selector(SWELL_Timer:) + userInfo:t repeats:YES]; + + [t release]; + } + [[NSRunLoop currentRunLoop] addTimer:rec->timer forMode:(NSString*)kCFRunLoopCommonModes]; + + if (recAdd) + { + rec->_next=m_timer_list; + m_timer_list=rec; + } + + return timerid; +} +void SWELL_RunRunLoop(int ms) +{ + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:ms*0.001]]; +} +void SWELL_RunRunLoopEx(int ms, HWND hwndOnlyTimer) +{ + HWND h=g_swell_only_timerhwnd; + g_swell_only_timerhwnd = hwndOnlyTimer; + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:ms*0.001]]; + if (g_swell_only_timerhwnd == hwndOnlyTimer) g_swell_only_timerhwnd = h; +} + +BOOL KillTimer(HWND hwnd, UINT_PTR timerid) +{ + if (!hwnd && !timerid) return FALSE; + + WDL_MutexLock lock(&m_timermutex); +#ifndef SWELL_NO_POSTMESSAGE + if (timerid != ~(UINT_PTR)0 && m_pmq_mainthread && pthread_self()!=m_pmq_mainthread) + { + SWELL_pmq_settimer(hwnd,timerid,~(UINT)0,NULL); + return TRUE; + } +#endif + BOOL rv=FALSE; + + // don't allow removing all global timers + if (timerid!=~(UINT_PTR)0 || hwnd) + { + TimerInfoRec *rec = m_timer_list, *lrec=NULL; + while (rec) + { + + if (rec->hwnd == hwnd && (timerid==~(UINT_PTR)0 || rec->timerid == timerid)) + { + TimerInfoRec *nrec = rec->_next; + + // remove self from list + if (lrec) lrec->_next = nrec; + else m_timer_list = nrec; + + [rec->timer invalidate]; + free(rec); + + rv=TRUE; + if (timerid!=~(UINT_PTR)0) break; + + rec=nrec; + } + else + { + lrec=rec; + rec=rec->_next; + } + } + } + return rv; +} + + +#ifndef SWELL_NO_POSTMESSAGE + +///////// PostMessage emulation + +// implementation of postmessage stuff + + + +typedef struct PMQ_rec +{ + HWND hwnd; + UINT msg; + WPARAM wParam; + LPARAM lParam; + + struct PMQ_rec *next; + bool is_special_timer; // if set, then msg=interval(-1 for kill),wParam=timer id, lParam = timerproc +} PMQ_rec; + +static WDL_Mutex *m_pmq_mutex; +static PMQ_rec *m_pmq, *m_pmq_empty, *m_pmq_tail; +static int m_pmq_size; +static id m_pmq_timer; +#define MAX_POSTMESSAGE_SIZE 1024 + +void SWELL_Internal_PostMessage_Init() +{ + if (m_pmq_mutex) return; + id del = [NSApp delegate]; + if (!del || ![del respondsToSelector:@selector(swellPostMessageTick:)]) return; + + m_pmq_mainthread=pthread_self(); + m_pmq_mutex = new WDL_Mutex; + + m_pmq_timer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:(id)del selector:@selector(swellPostMessageTick:) userInfo:nil repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:m_pmq_timer forMode:(NSString*)kCFRunLoopCommonModes]; + // [ release]; + // set a timer to the delegate +} + + +void SWELL_MessageQueue_Flush() +{ + if (!m_pmq_mutex) return; + + m_pmq_mutex->Enter(); + int max_amt = m_pmq_size; + PMQ_rec *p=m_pmq; + if (p) + { + m_pmq = p->next; + if (m_pmq_tail == p) m_pmq_tail=NULL; + m_pmq_size--; + } + m_pmq_mutex->Leave(); + + // process out queue + while (p) + { + if (p->is_special_timer) + { + if (p->msg == ~(UINT)0) KillTimer(p->hwnd,p->wParam); + else SetTimer(p->hwnd,p->wParam,p->msg,(TIMERPROC)p->lParam); + } + else + { + if ([(id)p->hwnd respondsToSelector:@selector(swellCanPostMessage)] && [(id)p->hwnd swellCanPostMessage]) + SendMessage(p->hwnd,p->msg,p->wParam,p->lParam); + } + + m_pmq_mutex->Enter(); + // move this message to empty list + p->next=m_pmq_empty; + m_pmq_empty = p; + + // get next queued message (if within limits) + p = (--max_amt > 0) ? m_pmq : NULL; + if (p) + { + m_pmq = p->next; + if (m_pmq_tail == p) m_pmq_tail=NULL; + m_pmq_size--; + } + m_pmq_mutex->Leave(); + } +} + +void SWELL_Internal_PMQ_ClearAllMessages(HWND hwnd) +{ + if (!m_pmq_mutex) return; + + m_pmq_mutex->Enter(); + PMQ_rec *p=m_pmq; + PMQ_rec *lastrec=NULL; + while (p) + { + if (hwnd && p->hwnd != hwnd) { lastrec=p; p=p->next; } + else + { + PMQ_rec *next=p->next; + + p->next=m_pmq_empty; // add p to empty list + m_pmq_empty=p; + m_pmq_size--; + + + if (p==m_pmq_tail) m_pmq_tail=lastrec; // update tail + + if (lastrec) p = lastrec->next = next; + else p = m_pmq = next; + } + } + m_pmq_mutex->Leave(); +} + +static void SWELL_pmq_settimer(HWND h, UINT_PTR timerid, UINT rate, TIMERPROC tProc) +{ + if (!h||!m_pmq_mutex) return; + WDL_MutexLock lock(m_pmq_mutex); + + PMQ_rec *rec=m_pmq; + while (rec) + { + if (rec->is_special_timer && rec->hwnd == h && rec->wParam == timerid) + { + rec->msg = rate; // adjust to new rate + rec->lParam = (LPARAM)tProc; + return; + } + rec=rec->next; + } + + rec=m_pmq_empty; + if (rec) m_pmq_empty=rec->next; + else rec=(PMQ_rec*)malloc(sizeof(PMQ_rec)); + rec->next=0; + rec->hwnd=h; + rec->msg=rate; + rec->wParam=timerid; + rec->lParam=(LPARAM)tProc; + rec->is_special_timer=true; + + if (m_pmq_tail) m_pmq_tail->next=rec; + else + { + PMQ_rec *p=m_pmq; + while (p && p->next) p=p->next; // shouldnt happen unless m_pmq is NULL As well but why not for safety + if (p) p->next=rec; + else m_pmq=rec; + } + m_pmq_tail=rec; + m_pmq_size++; +} + +BOOL PostMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (WDL_NORMALLY(m_pmq_mutex != NULL)) + { + return SWELL_Internal_PostMessage(hwnd,message,wParam,lParam); + } + + // legacy passthrough to delegate if caller is using its own swell impl, not threadsafe + id del=[NSApp delegate]; + if (del && [del respondsToSelector:@selector(swellPostMessage:msg:wp:lp:)]) + return !![(SWELL_DelegateExtensions*)del swellPostMessage:hwnd msg:message wp:wParam lp:lParam]; + return FALSE; +} + +void SWELL_MessageQueue_Clear(HWND h) +{ + if (WDL_NORMALLY(m_pmq_mutex != NULL)) + { + SWELL_Internal_PMQ_ClearAllMessages(h); + } + else + { + id del=[NSApp delegate]; + if (del && [del respondsToSelector:@selector(swellPostMessageClearQ:)]) + [(SWELL_DelegateExtensions*)del swellPostMessageClearQ:h]; + } +} + +BOOL SWELL_Internal_PostMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (!hwnd||!m_pmq_mutex) return FALSE; + + BOOL ret=FALSE; + m_pmq_mutex->Enter(); + + if (m_pmq_empty||m_pmq_sizenext; + else rec=(PMQ_rec*)malloc(sizeof(PMQ_rec)); + rec->next=0; + rec->hwnd=hwnd; + rec->msg=msg; + rec->wParam=wParam; + rec->lParam=lParam; + rec->is_special_timer=false; + + if (m_pmq_tail) m_pmq_tail->next=rec; + else + { + PMQ_rec *p=m_pmq; + while (p && p->next) p=p->next; // shouldnt happen unless m_pmq is NULL As well but why not for safety + if (p) p->next=rec; + else m_pmq=rec; + } + m_pmq_tail=rec; + m_pmq_size++; + + ret=TRUE; + } + + m_pmq_mutex->Leave(); + + return ret; +} +#endif + +static bool s_rightclickemulate=true; + +bool IsRightClickEmulateEnabled() +{ + return s_rightclickemulate; +} + +void SWELL_EnableRightClickEmulate(BOOL enable) +{ + s_rightclickemulate=enable; +} + +int g_swell_terminating; +void SWELL_PostQuitMessage(void *sender) +{ + g_swell_terminating=true; + + [NSApp terminate:(id)sender]; +} + + +#ifndef MAC_OS_X_VERSION_10_9 +typedef uint64_t NSActivityOptions; +enum +{ + NSActivityIdleDisplaySleepDisabled = (1ULL << 40), + NSActivityIdleSystemSleepDisabled = (1ULL << 20), + NSActivitySuddenTerminationDisabled = (1ULL << 14), + NSActivityAutomaticTerminationDisabled = (1ULL << 15), + NSActivityUserInitiated = (0x00FFFFFFULL | NSActivityIdleSystemSleepDisabled), + NSActivityUserInitiatedAllowingIdleSystemSleep = (NSActivityUserInitiated & ~NSActivityIdleSystemSleepDisabled), + NSActivityBackground = 0x000000FFULL, + NSActivityLatencyCritical = 0xFF00000000ULL, +}; + + +@interface NSProcessInfo (reaperhostadditions) +- (id)beginActivityWithOptions:(NSActivityOptions)options reason:(NSString *)reason; +- (void)endActivity:(id)activity; + +@end + +#endif + +void SWELL_DisableAppNap(int disable) +{ + if (!g_swell_terminating && floor(NSFoundationVersionNumber) > 945.00) // 10.9+ + { + static int cnt; + static id obj; + + cnt += disable; + + @try + { + if (cnt > 0) + { + if (!obj) + { + const NSActivityOptions v = NSActivityLatencyCritical | NSActivityIdleSystemSleepDisabled; + + // beginActivityWithOptions returns an autoreleased object + obj = [[NSProcessInfo processInfo] beginActivityWithOptions:v reason:@"SWELL_DisableAppNap"]; + if (obj) [obj retain]; + } + } + else + { + id a = obj; + if (a) + { + // in case we crash somehow, dont' want obj sticking around pointing to a stale object + obj = NULL; + [[NSProcessInfo processInfo] endActivity:a]; + [a release]; // apparently releasing this is enough, without the endActivity call, but the docs are quite vague + } + } + } + @catch (NSException *exception) { + } + @catch (id ex) { + } + } +} + + +BOOL EnumDisplayMonitors(HDC hdc, const LPRECT r, MONITORENUMPROC proc,LPARAM lParam) +{ + // ignores hdc + NSArray *screens = [NSScreen screens]; + const unsigned int ns = (unsigned int) [screens count]; + for (unsigned int x = 0; x < ns; x ++) + { + NSScreen *mon = [screens objectAtIndex:x]; + if (mon) + { + NSRect tr=[mon frame]; + RECT screen_rect,tmp; + NSRECT_TO_RECT(&tmp,tr); + if (r) + { + if (!IntersectRect(&screen_rect,r,&tmp)) + continue; + } + else + { + screen_rect = tmp; + } + if (!proc((HMONITOR)mon,hdc,&screen_rect,lParam)) break; + } + } + + return TRUE; +} + +BOOL GetMonitorInfo(HMONITOR hmon, void *inf) +{ + if (!hmon) return FALSE; + MONITORINFOEX *a = (MONITORINFOEX*)inf; + if (a->cbSize < sizeof(MONITORINFO)) return FALSE; + + NSScreen *mon = (NSScreen *)hmon; + NSRect tr=[mon frame]; + NSRECT_TO_RECT(&a->rcMonitor,tr); + tr = [mon visibleFrame]; + NSRECT_TO_RECT(&a->rcWork,tr); + a->dwFlags = 0; + + if (a->cbSize > sizeof(MONITORINFO)) + { + const int maxlen = (int) (a->cbSize - sizeof(MONITORINFO)); + const int displayID = [[[mon deviceDescription] valueForKey:@"NSScreenNumber"] intValue]; + snprintf(a->szDevice,maxlen,"DisplayID %d",displayID); + + + static bool init; + static CFDictionaryRef (*_IODisplayCreateInfoDictionary)(io_service_t framebuffer, IOOptionBits options); + + if (!init) + { + init = true; + void *lib = dlopen("/System/Library/Frameworks/IOKit.framework/Versions/Current/IOKit",RTLD_LAZY); + if (lib) + *(void **)&_IODisplayCreateInfoDictionary = dlsym(lib,"IODisplayCreateInfoDictionary"); + } + + if (_IODisplayCreateInfoDictionary) + { + NSDictionary *deviceInfo = (NSDictionary *)_IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName); + NSDictionary *names = [deviceInfo objectForKey:@"DisplayProductName"]; + if ([names count] > 0) + { + NSString *s = [names objectForKey:[[names allKeys] objectAtIndex:0]]; + if (s) SWELL_CFStringToCString(s,a->szDevice,maxlen); + } + [deviceInfo release]; + } + } + + return TRUE; +} + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-miscdlg-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-miscdlg-generic.cpp new file mode 100644 index 0000000..a2436d1 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-miscdlg-generic.cpp @@ -0,0 +1,2062 @@ +/* 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. + + + This file provides basic APIs for browsing for files, directories, and messageboxes. + + These APIs don't all match the Windows equivelents, but are close enough to make it not too much trouble. + + */ + + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-internal.h" +#include "swell-dlggen.h" + +#include "../wdlcstring.h" +#include "../assocarray.h" +#include "../ptrlist.h" +#include +#include + +#include "../lineparse.h" +#define WDL_HASSTRINGS_EXPORT static +#include "../has_strings.h" + + +#ifndef SWELL_BROWSE_RECENT_SIZE +#define SWELL_BROWSE_RECENT_SIZE 12 +#endif +static WDL_PtrList s_browse_rcu, s_browse_rcu_tmp; +static int recent_size() { return s_browse_rcu.GetSize() + s_browse_rcu_tmp.GetSize(); } + +static void recent_addtocb(HWND hwnd) +{ + int x; + for (x=0;x=SWELL_BROWSE_RECENT_SIZE) + s_browse_rcu.Delete(SWELL_BROWSE_RECENT_SIZE,true,free); + s_browse_rcu.Insert(0,strdup(path)); + } + + for (x=0;x<=s_browse_rcu.GetSize();x++) + { + char tmp[64]; + snprintf(tmp,sizeof(tmp),"path%d",x); + WritePrivateProfileString(".swell_recent_path",tmp, s_browse_rcu.Get(x),""); + } +} +static void recent_read() +{ + s_browse_rcu_tmp.Empty(true,free); + if (s_browse_rcu.GetSize()) return; + int x; + for (x=0;x 0 && date64 < WDL_INT64_CONST(0x793406fff)) + { + struct tm *a=localtime(&date); + if (a) strftime(buf,bufsz,"%c",a); + } + } + + void format_size(char *buf, int bufsz) + { + if (type == 1) + { + lstrcpyn_safe(buf,"",bufsz); + } + else + { + static const char *tab[]={ "bytes","KB","MB","GB" }; + int lf=0; + WDL_INT64 s=size; + if (s<1024) + { + snprintf(buf,bufsz,"%d %s",(int)s,tab[0]); + } + else + { + int w = 1; + do { w++; lf = (int)(s&1023); s/=1024; } while (s >= 1024 && w<4); + snprintf(buf,bufsz,"%d.%d %s",(int)s,(int)((lf*10.0)/1024.0+0.5),tab[w-1]); + } + } + } + + char *format_all(char *buf, int bufsz) + { + char dstr[128],sstr[128]; + format_date(dstr,sizeof(dstr)); + format_size(sstr,sizeof(sstr)); + snprintf(buf,bufsz,"%s\t%s\t%s",WDL_get_filepart(name),dstr,sstr); + return buf; + } + + }; + + void viewlist_clear() + { + rec *l = viewlist_store.Get(); + const int n = viewlist_store.GetSize(); + for (int x = 0; x < n; x ++) free(l[x].name); + viewlist_store.Resize(0); + viewlist.Empty(); + } + WDL_TypedBuf viewlist_store; + WDL_PtrList viewlist; + + bool show_hidden; + + void viewlist_sort(const char *filter) + { + if (filter) + { + viewlist.Empty(); + LineParser lp; + const bool no_filter = !*filter || !WDL_makeSearchFilter(filter,&lp); + for (int x=0;xformat_all(tmp,sizeof(tmp)),&lp)) + viewlist.Add(r); + } + } + s_sortrev = sortrev; + if (viewlist.GetSize()>1) + qsort(viewlist.GetList(), viewlist.GetSize(),sizeof(rec*), + sortcol == 1 ? sortFunc_sz : + sortcol == 2 ? sortFunc_date : + sortFunc_fn); + } + static int sortFunc_fn(const void *_a, const void *_b) + { + const rec *a = *(const rec * const*)_a, *b = *(const rec * const*)_b; + int d = a->type - b->type; + if (d) return d; + d = stricmp(a->name,b->name); + return s_sortrev ? -d : d; + } + static int sortFunc_date(const void *_a, const void *_b) + { + const rec *a = *(const rec * const*)_a, *b = *(const rec * const*)_b; + if (a->date != b->date) return s_sortrev ? (a->date>b->date?-1:1) : (a->date>b->date?1:-1); + return stricmp(a->name,b->name); + } + static int sortFunc_sz(const void *_a, const void *_b) + { + const rec *a = *(const rec * const *)_a, *b = *(const rec * const *)_b; + int d = a->type - b->type; + if (d) return s_sortrev ? -d : d; + if (a->size != b->size) return s_sortrev ? (a->size>b->size?-1:1) : (a->size>b->size?1:-1); + return stricmp(a->name,b->name); + } + + + void scan_path(const char *path, const char *filterlist, bool dir_only) + { + viewlist_clear(); + DIR *dir = opendir(path); + if (!dir) return; + char tmp[2048]; + struct dirent *ent; + while (NULL != (ent = readdir(dir))) + { + if (ent->d_name[0] == '.') + { + if (ent->d_name[1] == 0 || ent->d_name[1] == '.' || !show_hidden) continue; + } + bool is_dir = (ent->d_type == DT_DIR); + if (ent->d_type == DT_UNKNOWN) + { + snprintf(tmp,sizeof(tmp),"%s/%s",path,ent->d_name); + DIR *d = opendir(tmp); + if (d) { is_dir = true; closedir(d); } + } + else if (ent->d_type == DT_LNK) + { + snprintf(tmp,sizeof(tmp),"%s/%s",path,ent->d_name); + char *rp = realpath(tmp,NULL); + if (rp) + { + DIR *d = opendir(rp); + if (d) { is_dir = true; closedir(d); } + free(rp); + } + } + if (!dir_only || is_dir) + { + if (filterlist && *filterlist && !is_dir) + { + const char *f = filterlist; + while (*f) + { + const char *nf = f; + while (*nf && *nf != ';') nf++; + if (*f != '*') + { + const char *nw = f; + while (nw < nf && *nw != '*') nw++; + + if ((nw!=nf || f+strlen(ent->d_name) == nw) && !strncasecmp(ent->d_name,f,nw-f)) + { + // matched leading text + if (nw == nf) break; + f = nw; + } + } + + if (*f == '*') + { + f++; + if (!*f || *f == ';' || (*f == '.' && f[1] == '*')) break; + size_t l = strlen(ent->d_name); + if (f+l > nf && !strncasecmp(ent->d_name + l - (nf-f), f,nf-f)) break; + } + f = nf; + while (*f == ';') f++; + } + if (!*f) continue; // did not match + } + snprintf(tmp,sizeof(tmp),"%s/%s",path,ent->d_name); + + struct stat st={0,}; + stat(tmp,&st); + + rec r = { st.st_size, st.st_mtime, strdup(ent->d_name), is_dir?1:2 } ; + viewlist_store.Add(&r,1); + } + } + // sort viewlist + + closedir(dir); + } +}; + +char BrowseFile_State::s_sortrev; + +static void preprocess_user_path(char *buf, int bufsz) +{ + if (buf[0] == '~') + { + char *tmp = strdup(buf+1); + if (buf[1] == '/' || !buf[1]) + { + char *p = getenv("HOME"); + if (p && *p) snprintf(buf,bufsz,"%s%s",p,tmp); + } + else + { + snprintf(buf,bufsz,"/home/%s",tmp); // if someone wants to write code to lookup homedirs, please, go right ahead! + } + free(tmp); + } +} + +static LRESULT WINAPI swellFileSelectProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + enum { IDC_EDIT=0x100, IDC_LABEL, IDC_CHILD, IDC_DIR, IDC_LIST, IDC_EXT, IDC_PARENTBUTTON, IDC_FILTER, ID_SHOW_HIDDEN }; + enum { WM_UPD=WM_USER+100 }; + const int maxPathLen = 2048; + const char *multiple_files = "(multiple files)"; + switch (uMsg) + { + case WM_CREATE: + if (lParam) // swell-specific + { + SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc); + SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellFileSelectProc); + + SetWindowLong(hwnd,GWL_STYLE, GetWindowLong(hwnd,GWL_STYLE)|WS_THICKFRAME); + + SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); + BrowseFile_State *parms = (BrowseFile_State *)lParam; + + char tmp[1024]; + recent_read(); + + recent_add_tmp(parms->initialdir); + + if (parms->initialfile && *parms->initialfile != '.') + { + lstrcpyn_safe(tmp,parms->initialfile,sizeof(tmp)); + WDL_remove_filepart(tmp); + recent_add_tmp(tmp); + } + + if (parms->caption) SetWindowText(hwnd,parms->caption); + + SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false); + + HWND edit = SWELL_MakeEditField(IDC_EDIT, 0,0,0,0, 0); + SWELL_MakeButton(0, + parms->mode == BrowseFile_State::OPENDIR ? "Choose directory" : + parms->mode == BrowseFile_State::SAVE ? "Save" : "Open", + IDOK,0,0,0,0, 0); + + SWELL_MakeButton(0, "Cancel", IDCANCEL,0,0,0,0, 0); + HWND dir = SWELL_MakeCombo(IDC_DIR, 0,0,0,0, 0); + SWELL_MakeButton(0, "..", IDC_PARENTBUTTON, 0,0,0,0, 0); + SWELL_MakeEditField(IDC_FILTER, 0,0,0,0, 0); + + const char *ent = parms->mode == BrowseFile_State::OPENDIR ? "dir_browser" : "file_browser"; + GetPrivateProfileString(".swell",ent,"", tmp,sizeof(tmp),""); + int x=0,y=0,w=0,h=0, c1=0,c2=0,c3=0,extraflag=0; + int flag = SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER; + if (tmp[0] && + sscanf(tmp,"%d %d %d %d %d %d %d %d",&x,&y,&w,&h,&c1,&c2,&c3,&extraflag) >= 4) + flag &= ~SWP_NOMOVE; + if (w < 100) w=SWELL_UI_SCALE(600); + if (h < 100) h=SWELL_UI_SCALE(400); + if (extraflag&1) + parms->show_hidden=true; + + if (c1 + c2 + c3 < w/2) + { + c1=SWELL_UI_SCALE(280); + c2=SWELL_UI_SCALE(120); + c3=SWELL_UI_SCALE(140); + } + + HWND list = SWELL_MakeControl("",IDC_LIST,"SysListView32",LVS_REPORT|LVS_SHOWSELALWAYS| + (parms->mode == BrowseFile_State::OPENMULTI ? 0 : LVS_SINGLESEL)| + LVS_OWNERDATA|WS_BORDER|WS_TABSTOP,0,0,0,0,0); + if (list) + { + LVCOLUMN c={LVCF_TEXT|LVCF_WIDTH, 0, c1, (char*)"Filename" }; + ListView_InsertColumn(list,0,&c); + c.cx = c2; + c.pszText = (char*) "Size"; + ListView_InsertColumn(list,1,&c); + c.cx = c3; + c.pszText = (char*) "Date"; + ListView_InsertColumn(list,2,&c); + HWND hdr = ListView_GetHeader(list); + HDITEM hi; + memset(&hi,0,sizeof(hi)); + hi.mask = HDI_FORMAT; + hi.fmt = parms->sortrev ? HDF_SORTDOWN : HDF_SORTUP; + Header_SetItem(hdr,parms->sortcol,&hi); + } + HWND extlist = (parms->extlist && *parms->extlist) ? SWELL_MakeCombo(IDC_EXT, 0,0,0,0, CBS_DROPDOWNLIST) : NULL; + if (extlist) + { + const char *p = parms->extlist; + while (*p) + { + const char *rd=p; + p += strlen(p)+1; + if (!*p) break; + int a = SendMessage(extlist,CB_ADDSTRING,0,(LPARAM)rd); + SendMessage(extlist,CB_SETITEMDATA,a,(LPARAM)p); + p += strlen(p)+1; + } + + int sel = 0; + if (parms->initialfile && *parms->initialfile) + { + sel = ext_valid_for_extlist(WDL_get_fileext(parms->initialfile), parms->extlist); + if (sel<0) sel=0; + } + SendMessage(extlist,CB_SETCURSEL,sel,0); + } + + SWELL_MakeLabel(-1,parms->mode == BrowseFile_State::OPENDIR ? "Directory: " : "File:",IDC_LABEL, 0,0,0,0, 0); + + if (BFSF_Templ_dlgid && BFSF_Templ_dlgproc) + { + HWND dlg = SWELL_CreateDialog(BFSF_Templ_reshead, BFSF_Templ_dlgid, hwnd, BFSF_Templ_dlgproc, 0); + if (dlg) SetWindowLong(dlg,GWL_ID,IDC_CHILD); + BFSF_Templ_dlgproc=0; + BFSF_Templ_dlgid=0; + } + + SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false); + + if (edit && dir) + { + char buf[maxPathLen]; + const char *filepart = ""; + if (parms->initialfile && *parms->initialfile && *parms->initialfile != '.') + { + lstrcpyn_safe(buf,parms->initialfile,sizeof(buf)); + char *p = (char *)WDL_get_filepart(buf); + if (p > buf) + { + p[-1]=0; + filepart = p; + } + else + { + filepart = parms->initialfile; + goto get_dir; + } + } + else + { +get_dir: + if (parms->initialdir && *parms->initialdir && strcmp(parms->initialdir,".")) + { + lstrcpyn_safe(buf,parms->initialdir,sizeof(buf)); + } + else if (!getcwd(buf,sizeof(buf))) + buf[0]=0; + } + + SetWindowText(edit,filepart); + SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf); + } + + if (list) SetWindowPos(list,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); + SetWindowPos(hwnd,NULL,x,y, w,h, flag); + SendMessage(hwnd,WM_UPD,1,0); + SendMessage(edit,EM_SETSEL,0,(LPARAM)-1); + SetFocus(edit); + } + break; + case WM_DESTROY: + { + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (parms) + { + RECT r; + GetWindowRect(hwnd,&r); + HWND list = GetDlgItem(hwnd,IDC_LIST); + const int c1 = ListView_GetColumnWidth(list,0); + const int c2 = ListView_GetColumnWidth(list,1); + const int c3 = ListView_GetColumnWidth(list,2); + char tmp[128]; + int extraflag=0; + if (parms->show_hidden) extraflag|=1; + snprintf(tmp,sizeof(tmp),"%d %d %d %d %d %d %d %d",r.left,r.top,r.right-r.left,r.bottom-r.top,c1,c2,c3,extraflag); + const char *ent = parms->mode == BrowseFile_State::OPENDIR ? "dir_browser" : "file_browser"; + WritePrivateProfileString(".swell",ent, tmp, ""); + } + } + break; + case WM_UPD: + switch (wParam) + { + case IDC_DIR: // update directory combo box -- destroys buffer pointed to by lParam + if (lParam) + { + char *path = (char*)lParam; + HWND combo=GetDlgItem(hwnd,IDC_DIR); + SendMessage(combo,CB_RESETCONTENT,0,0); + WDL_remove_trailing_dirchars(path); + while (path[0]) + { + SendMessage(combo,CB_ADDSTRING,0,(LPARAM)path); + WDL_remove_filepart(path); + WDL_remove_trailing_dirchars(path); + } + SendMessage(combo,CB_ADDSTRING,0,(LPARAM)"/"); + recent_addtocb(combo); + SendMessage(combo,CB_SETCURSEL,0,0); + } + break; + case IDC_EXT: + case 1: + { + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (parms) + { + SetDlgItemText(hwnd,IDC_FILTER,""); + KillTimer(hwnd,1); + + char buf[maxPathLen]; + const char *filt = NULL; + HWND ext = GetDlgItem(hwnd,IDC_EXT); + if (ext) + { + LRESULT aidx = SendMessage(ext,CB_GETCURSEL,0,0); + if (aidx != CB_ERR) + { + filt = (const char *)SendMessage(ext,CB_GETITEMDATA,aidx,0); + if (wParam == IDC_EXT && parms->extlist && *parms->extlist) + { + GetDlgItemText(hwnd,IDC_EDIT,buf,sizeof(buf)); + + if (buf[0]) + { + const char *erd = filt; + if (*erd == '*' && erd[1] == '.' && erd[2] && erd[2] != '*') + { + const char *a = (erd+=1); + while (*erd && *erd != ';') erd++; + if (erd > a+1) + { + if (ext_valid_for_extlist(WDL_get_fileext(buf),parms->extlist)>=0) + { + WDL_remove_fileext(buf); + } + else + { + char *p = buf; + while (*p) p++; + while (p > buf && (p[-1] == ' ' || p[-1] == '.' || p[-1] == '/' || p[-1] == '\\')) p--; + *p=0; + } + if (buf[0]) + { + snprintf_append(buf,sizeof(buf),"%.*s",(int)(erd-a),a); + SetDlgItemText(hwnd,IDC_EDIT,buf); + } + } + } + } + } + } + } + + GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf)); + preprocess_user_path(buf,sizeof(buf)); + + if (buf[0]) parms->scan_path(buf, filt, parms->mode == BrowseFile_State::OPENDIR); + else parms->viewlist_clear(); + HWND list = GetDlgItem(hwnd,IDC_LIST); + ListView_SetItemCount(list, 0); // clear selection + + parms->viewlist_sort(""); + ListView_SetItemCount(list, parms->viewlist.GetSize()); + ListView_RedrawItems(list,0, parms->viewlist.GetSize()); + } + } + break; + } + break; + case WM_GETMINMAXINFO: + { + LPMINMAXINFO p=(LPMINMAXINFO)lParam; + p->ptMinTrackSize.x = 300; + p->ptMinTrackSize.y = 300; + } + break; + case WM_SIZE: + { + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + // reposition controls + RECT r; + GetClientRect(hwnd,&r); + const int buth = SWELL_UI_SCALE(24), cancelbutw = SWELL_UI_SCALE(50), okbutw = SWELL_UI_SCALE(parms->mode == BrowseFile_State::OPENDIR ? 120 : 50); + const int xborder = SWELL_UI_SCALE(4), yborder=SWELL_UI_SCALE(8); + const int fnh = SWELL_UI_SCALE(20), fnlblw = SWELL_UI_SCALE(parms->mode == BrowseFile_State::OPENDIR ? 70 : 50); + const int ypad = SWELL_UI_SCALE(4); + + int ypos = r.bottom - ypad - buth; + int xpos = r.right; + SetWindowPos(GetDlgItem(hwnd,IDCANCEL), NULL, xpos -= cancelbutw + xborder, ypos, cancelbutw,buth, SWP_NOZORDER|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwnd,IDOK), NULL, xpos -= okbutw + xborder, ypos, okbutw,buth, SWP_NOZORDER|SWP_NOACTIVATE); + + HWND emb = GetDlgItem(hwnd,IDC_CHILD); + if (emb) + { + RECT sr; + GetClientRect(emb,&sr); + if (ypos > r.bottom-ypad-sr.bottom) ypos = r.bottom-ypad-sr.bottom; + SetWindowPos(emb,NULL, xborder,ypos, xpos - xborder*2, sr.bottom, SWP_NOZORDER|SWP_NOACTIVATE); + ShowWindow(emb,SW_SHOWNA); + } + + HWND filt = GetDlgItem(hwnd,IDC_EXT); + if (filt) + { + SetWindowPos(filt, NULL, xborder*2 + fnlblw, ypos -= fnh + yborder, r.right-fnlblw-xborder*3, fnh, SWP_NOZORDER|SWP_NOACTIVATE); + } + + SetWindowPos(GetDlgItem(hwnd,IDC_EDIT), NULL, xborder*2 + fnlblw, ypos -= fnh + yborder, r.right-fnlblw-xborder*3, fnh, SWP_NOZORDER|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwnd,IDC_LABEL), NULL, xborder, ypos, fnlblw, fnh, SWP_NOZORDER|SWP_NOACTIVATE); + const int comboh = g_swell_ctheme.combo_height; + const int filterw = wdl_max(r.right/8, SWELL_UI_SCALE(50)); + SetWindowPos(GetDlgItem(hwnd,IDC_DIR), NULL, xborder, yborder/2, + r.right-xborder*4 - comboh - filterw, comboh, SWP_NOZORDER|SWP_NOACTIVATE); + + SetWindowPos(GetDlgItem(hwnd,IDC_PARENTBUTTON),NULL, + r.right-xborder*2-comboh - filterw,yborder/2, + comboh,comboh,SWP_NOZORDER|SWP_NOACTIVATE); + + SetWindowPos(GetDlgItem(hwnd,IDC_FILTER),NULL, + r.right-xborder-filterw,yborder/2 + (comboh-fnh)/2, + filterw,fnh,SWP_NOZORDER|SWP_NOACTIVATE); + + SetWindowPos(GetDlgItem(hwnd,IDC_LIST), NULL, xborder, g_swell_ctheme.combo_height+yborder, r.right-xborder*2, ypos - (g_swell_ctheme.combo_height+yborder) - yborder, SWP_NOZORDER|SWP_NOACTIVATE); + } + break; + case WM_TIMER: + if (wParam == 1) + { + KillTimer(hwnd,1); + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (parms) + { + char buf[128]; + GetDlgItemText(hwnd,IDC_FILTER,buf,sizeof(buf)); + parms->viewlist_sort(buf); + HWND list = GetDlgItem(hwnd,IDC_LIST); + ListView_SetItemCount(list, parms->viewlist.GetSize()); + ListView_RedrawItems(list,0, parms->viewlist.GetSize()); + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_FILTER: + if (HIWORD(wParam) == EN_CHANGE) + { + KillTimer(hwnd,1); + SetTimer(hwnd,1,250,NULL); + } + return 0; + case IDC_EXT: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + SendMessage(hwnd,WM_UPD,IDC_EXT,0); + } + return 0; + case IDC_PARENTBUTTON: + { + int a = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCURSEL,0,0); + int cbcnt = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCOUNT,0,0); + if (a>=0 && a < cbcnt - recent_size()) + { + SendDlgItemMessage(hwnd,IDC_DIR,CB_SETCURSEL,a+1,0); + } + else + { + char buf[maxPathLen]; + GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf)); + preprocess_user_path(buf,sizeof(buf)); + WDL_remove_filepart(buf); + if (a>=0) + SendMessage(hwnd,WM_UPD,IDC_DIR,(LPARAM)buf); + else + SetDlgItemText(hwnd,IDC_DIR,buf); + } + SendMessage(hwnd,WM_UPD,1,0); + } + return 0; + case IDC_DIR: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + int a = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCURSEL,0,0); + int cbcnt = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCOUNT,0,0); + if (a>=cbcnt - recent_size()) + { + char buf[maxPathLen]; + GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf)); + preprocess_user_path(buf,sizeof(buf)); + SendMessage(hwnd,WM_UPD,IDC_DIR,(LPARAM)buf); + } + SendMessage(hwnd,WM_UPD,1,0); + } + return 0; + case IDCANCEL: EndDialog(hwnd,0); return 0; + case IDOK: + { + char buf[maxPathLen], msg[2048]; + GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf)); + preprocess_user_path(buf,sizeof(buf)); + + if (GetFocus() == GetDlgItem(hwnd,IDC_DIR)) + { + DIR *dir = opendir(buf); + if (!dir) + { + //snprintf(msg,sizeof(msg),"Path does not exist:\r\n\r\n%s",buf); + //MessageBox(hwnd,msg,"Path not found",MB_OK); + return 0; + } + closedir(dir); + + SendMessage(hwnd,WM_UPD,1,0); + SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf); + HWND e = GetDlgItem(hwnd,IDC_EDIT); + SendMessage(e,EM_SETSEL,0,(LPARAM)-1); + SetFocus(e); + return 0; + } + + size_t buflen = strlen(buf); + if (!buflen) strcpy(buf,"/"); + else + { + if (buflen > sizeof(buf)-2) buflen = sizeof(buf)-2; + if (buf[buflen-1]!='/') { buf[buflen++] = '/'; buf[buflen]=0; } + } + GetDlgItemText(hwnd,IDC_EDIT,msg,sizeof(msg)); + preprocess_user_path(msg,sizeof(msg)); + + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + int cnt; + if (parms->mode == BrowseFile_State::OPENMULTI && (cnt=ListView_GetSelectedCount(GetDlgItem(hwnd,IDC_LIST)))>1 && (!*msg || !strcmp(msg,multiple_files))) + { + recent_write(buf); + HWND list = GetDlgItem(hwnd,IDC_LIST); + WDL_TypedBuf fs; + fs.Set(buf,strlen(buf)+1); + int a = ListView_GetNextItem(list,-1,LVNI_SELECTED); + while (a != -1 && fs.GetSize() < 4096*4096 && cnt--) + { + if (a < 0 || a >= parms->viewlist.GetSize()) break; + const struct BrowseFile_State::rec *rec = parms->viewlist.Get(a); + if (!rec) break; + + fs.Add(rec->name,strlen(rec->name)+1); + a = ListView_GetNextItem(list,a,LVNI_SELECTED); + } + fs.Add("",1); + + parms->fnout = (char*)malloc(fs.GetSize()); + if (parms->fnout) memcpy(parms->fnout,fs.Get(),fs.GetSize()); + + EndDialog(hwnd,1); + return 0; + } + else + { + if (msg[0] == '.' && (msg[1] == '.' || msg[1] == 0)) + { + if (msg[1] == '.') + { + int a = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCURSEL,0,0); + if (a>=0) SendDlgItemMessage(hwnd,IDC_DIR,CB_SETCURSEL,a+1,0); + } + SetDlgItemText(hwnd,IDC_EDIT,""); + SendMessage(hwnd,WM_UPD,1,0); + return 0; + } + else if (msg[0] == '/') lstrcpyn_safe(buf,msg,sizeof(buf)); + else lstrcatn(buf,msg,sizeof(buf)); + } + + switch (parms->mode) + { + case BrowseFile_State::OPENDIR: + if (!buf[0]) return 0; + else if (msg[0]) + { + // navigate to directory if filepart set +treatAsDir: + DIR *dir = opendir(buf); + if (!dir) + { + snprintf(msg,sizeof(msg),"Error opening directory:\r\n\r\n%.1000s\r\n\r\nCreate?",buf); + if (MessageBox(hwnd,msg,"Create directory?",MB_OKCANCEL)==IDCANCEL) return 0; + CreateDirectory(buf,NULL); + dir=opendir(buf); + } + if (!dir) { MessageBox(hwnd,"Error creating directory","Error",MB_OK); return 0; } + closedir(dir); + SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf); + SetDlgItemText(hwnd,IDC_EDIT,""); + SendMessage(hwnd,WM_UPD,1,0); + + return 0; + } + else + { + DIR *dir = opendir(buf); + if (!dir) return 0; + closedir(dir); + } + break; + case BrowseFile_State::SAVE: + if (!buf[0]) return 0; + else + { + struct stat st={0,}; + DIR *dir = opendir(buf); + if (dir) + { + closedir(dir); + SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf); + SetDlgItemText(hwnd,IDC_EDIT,""); + SendMessage(hwnd,WM_UPD,1,0); + return 0; + } + if (buf[strlen(buf)-1] == '/') goto treatAsDir; + + const char *extlist = parms->extlist; + if (extlist && *extlist && ext_valid_for_extlist(WDL_get_fileext(buf),extlist) < 0) + { + const char *erd = extlist+strlen(extlist)+1; + LRESULT combo_sel; + HWND combo = GetDlgItem(hwnd, IDC_EXT); + if (combo && (combo_sel=SendMessage(combo,CB_GETCURSEL,0,0)) != CB_ERR) + { + const char *filt = (const char *)SendMessage(combo,CB_GETITEMDATA,combo_sel,0); + if (filt && *filt == '*' && filt[1] == '.' && filt[2] && filt[2] != '*') erd = filt; + } + + if (*erd == '*' && erd[1] == '.' && erd[2] && erd[2] != '*') // add default extension + { + const char *a = (erd+=1); + while (*erd && *erd != ';') erd++; + if (erd > a+1) snprintf_append(buf,sizeof(buf),"%.*s",(int)(erd-a),a); + } + } + if (!stat(buf,&st)) + { + snprintf(msg,sizeof(msg),"File exists:\r\n\r\n%.1000s\r\n\r\nOverwrite?",buf); + if (MessageBox(hwnd,msg,"Overwrite file?",MB_OKCANCEL)==IDCANCEL) return 0; + } + } + break; + default: + if (!buf[0]) return 0; + else + { + struct stat st={0,}; + DIR *dir = opendir(buf); + if (dir) + { + closedir(dir); + SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf); + SetDlgItemText(hwnd,IDC_EDIT,""); + SendMessage(hwnd,WM_UPD,1,0); + return 0; + } + if (stat(buf,&st)) + { + //snprintf(msg,sizeof(msg),"File does not exist:\r\n\r\n%s",buf); + //MessageBox(hwnd,msg,"File not found",MB_OK); + return 0; + } + } + break; + } + if (parms->fnout) + { + lstrcpyn_safe(parms->fnout,buf,parms->fnout_sz); + } + else + { + size_t l = strlen(buf); + parms->fnout = (char*)calloc(l+2,1); + memcpy(parms->fnout,buf,l); + } + if (parms->mode != BrowseFile_State::OPENDIR) + WDL_remove_filepart(buf); + recent_write(buf); + } + EndDialog(hwnd,1); + return 0; + case ID_SHOW_HIDDEN: + { + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + parms->show_hidden = !parms->show_hidden; + SendMessage(hwnd,WM_UPD,1,0); + } + return 0; + } + break; + case WM_NOTIFY: + { + LPNMHDR l=(LPNMHDR)lParam; + if (l->code == LVN_GETDISPINFO) + { + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + NMLVDISPINFO *lpdi = (NMLVDISPINFO*) lParam; + const int idx=lpdi->item.iItem; + if (l->idFrom == IDC_LIST && parms) + { + struct BrowseFile_State::rec *rec = parms->viewlist.Get(idx); + if (rec && rec->name) + { + if (lpdi->item.mask&LVIF_TEXT) + { + switch (lpdi->item.iSubItem) + { + case 0: + lpdi->item.pszText = rec->name; + break; + case 1: + rec->format_size(lpdi->item.pszText,lpdi->item.cchTextMax); + break; + case 2: + rec->format_date(lpdi->item.pszText,lpdi->item.cchTextMax); + break; + } + } + } + } + } + else if (l->code == LVN_ODFINDITEM) + { + } + else if (l->code == LVN_ITEMCHANGED) + { + const int selidx = ListView_GetNextItem(l->hwndFrom, -1, LVNI_SELECTED); + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (selidx>=0 && parms) + { + if (parms->mode == BrowseFile_State::OPENMULTI && ListView_GetSelectedCount(l->hwndFrom)>1) + { + SetDlgItemText(hwnd,IDC_EDIT,multiple_files); + } + else + { + struct BrowseFile_State::rec *rec = parms->viewlist.Get(selidx); + if (rec) + { + SetDlgItemText(hwnd,IDC_EDIT,rec->name); + } + } + } + } + else if (l->code == NM_DBLCLK) + { + SendMessage(hwnd,WM_COMMAND,IDOK,0); + } + else if (l->code == LVN_COLUMNCLICK) + { + NMLISTVIEW* lv = (NMLISTVIEW*) l; + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + parms->sortrev=!parms->sortrev; + char lcol = parms->sortcol; + if ((int)parms->sortcol != lv->iSubItem) + { + parms->sortcol = (char)lv->iSubItem; + parms->sortrev=0; + } + + HWND hdr = ListView_GetHeader(l->hwndFrom); + HDITEM hi; + memset(&hi,0,sizeof(hi)); + hi.mask = HDI_FORMAT; + Header_SetItem(hdr,lcol,&hi); + hi.fmt = parms->sortrev ? HDF_SORTDOWN : HDF_SORTUP; + Header_SetItem(hdr,parms->sortcol,&hi); + + parms->viewlist_sort(NULL); + ListView_RedrawItems(l->hwndFrom,0, parms->viewlist.GetSize()); + } + } + break; + case WM_KEYDOWN: + if (lParam == FVIRTKEY && wParam == VK_F5) + { + SendMessage(hwnd,WM_UPD,1,0); + return 1; + } + else if (lParam == (FVIRTKEY|FCONTROL) && wParam == 'H') + { + SendMessage(hwnd,WM_COMMAND,ID_SHOW_HIDDEN,0); + return 1; + } + else if (lParam == FVIRTKEY && wParam == VK_BACK && + GetFocus() == GetDlgItem(hwnd,IDC_LIST)) + { + SendMessage(hwnd,WM_COMMAND,IDC_PARENTBUTTON,0); + return 1; + } + else if ((lParam&0xff) == FVIRTKEY && wParam == VK_RETURN && + GetFocus() == GetDlgItem(hwnd,IDC_LIST)) + { + SendMessage(hwnd,WM_COMMAND,IDOK,0); + return 1; + } + return 0; + case WM_CONTEXTMENU: + { + BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + HMENU menu = CreatePopupMenu(); + SWELL_InsertMenu(menu,0,MF_BYPOSITION|(parms->show_hidden ? MF_CHECKED:MF_UNCHECKED), ID_SHOW_HIDDEN, "Show files/directories beginning with ."); + POINT p; + GetCursorPos(&p); + TrackPopupMenu(menu,0,p.x,p.y,0,hwnd,NULL); + DestroyMenu(menu); + } + return 1; + } + return 0; +} + +// return true +bool BrowseForSaveFile(const char *text, const char *initialdir, const char *initialfile, const char *extlist, + char *fn, int fnsize) +{ + BrowseFile_State state( text, initialdir, initialfile, extlist, BrowseFile_State::SAVE, fn, fnsize ); + if (!DialogBoxParam(NULL,NULL,GetForegroundWindow(),swellFileSelectProc,(LPARAM)&state)) return false; + + return true; +} + +bool BrowseForDirectory(const char *text, const char *initialdir, char *fn, int fnsize) +{ + BrowseFile_State state( text, initialdir, initialdir, NULL, BrowseFile_State::OPENDIR, fn, fnsize ); + return !!DialogBoxParam(NULL,NULL,GetForegroundWindow(),swellFileSelectProc,(LPARAM)&state); +} + + +char *BrowseForFiles(const char *text, const char *initialdir, + const char *initialfile, bool allowmul, const char *extlist) +{ + BrowseFile_State state( text, initialdir, initialfile, extlist, + allowmul ? BrowseFile_State::OPENMULTI : BrowseFile_State::OPEN, NULL, 0 ); + return DialogBoxParam(NULL,NULL,GetForegroundWindow(),swellFileSelectProc,(LPARAM)&state) ? state.fnout : NULL; +} + +static const char *mbidtostr(int idx) +{ + switch (idx) + { + case IDOK: return "OK"; + case IDCANCEL: return "Cancel"; + case IDYES: return "Yes"; + case IDNO: return "No"; + case IDRETRY: return "Retry"; + case IDABORT: return "Abort"; + case IDIGNORE: return "Ignore"; + default: + WDL_ASSERT(idx == 0); + return ""; + } +} + +static LRESULT WINAPI swellMessageBoxProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + enum { IDC_LABEL=0x100 }; + const int button_spacing = 8; + switch (uMsg) + { + case WM_CREATE: + if (lParam) // swell-specific + { + SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc); + SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellMessageBoxProc); + void **parms = (void **)lParam; + if (parms[1]) SetWindowText(hwnd,(const char*)parms[1]); + + + int b1=IDOK, b2=0,b3=0; + + const int fmode = (int)(INT_PTR)parms[2]; + switch (fmode & 0xf) + { + case MB_ABORTRETRYIGNORE: + b1 = IDABORT; + b2 = IDRETRY; + b3 = IDIGNORE; + break; + case MB_RETRYCANCEL: + b1 = IDRETRY; + // fallthrough + case MB_OKCANCEL: + b2 = IDCANCEL; + break; + case MB_YESNOCANCEL: + b3 = IDCANCEL; + // fallthrough + case MB_YESNO: + b1 = IDYES; + b2 = IDNO; + break; + } + + SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false); + RECT labsize = {0,0,300,20}; + HWND lab = SWELL_MakeLabel(-1,parms[0] ? (const char *)parms[0] : "", IDC_LABEL, 0,0,10,10,SS_CENTER|SS_NOPREFIX); //we'll resize this manually + HDC dc=GetDC(lab); + if (lab && parms[0]) + { + DrawText(dc,(const char *)parms[0],-1,&labsize,DT_CALCRECT|DT_NOPREFIX);// if dc isnt valid yet, try anyway + } + + const int sc10 = SWELL_UI_SCALE(10); + const int sc8 = SWELL_UI_SCALE(8); + labsize.top += sc10; + labsize.bottom += sc10 + sc8; + + RECT vp; + SWELL_GetViewPort(&vp,NULL,true); + vp.bottom -= vp.top; + if (labsize.bottom > vp.bottom*7/8) + labsize.bottom = vp.bottom*7/8; + + + int x; + int button_height=0, button_total_w=0;; + const int bspace = SWELL_UI_SCALE(button_spacing); + int button_sizes[3]; + const int nbuttons = b3 ? 3 : b2 ? 2 : 1; + for (x = 0; x < nbuttons; x ++) + { + const int idx = x==0?b1:x==1?b2:b3; + RECT r={0,0,35,12}; + DrawText(dc,mbidtostr(idx),-1,&r,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE); + button_sizes[x] = r.right-r.left + sc10; + button_total_w += button_sizes[x] + (x ? bspace : 0); + if (r.bottom-r.top+sc10 > button_height) button_height = r.bottom-r.top+sc10; + } + + if (labsize.right < button_total_w+sc8*2) labsize.right = button_total_w+sc8*2; + + int xpos = labsize.right/2 - button_total_w/2; + const int def_id = (fmode & MB_DEFBUTTON3)&&b3 ? b3 : (fmode & MB_DEFBUTTON2)&&b2 ? b2 : b1; + + for (x = 0; x < nbuttons; x ++) + { + const int idx = x==0?b1:x==1?b2:b3; + SWELL_MakeButton(idx==def_id,mbidtostr(idx),idx,xpos,labsize.bottom,button_sizes[x],button_height,0); + xpos += button_sizes[x] + bspace; + } + + if (dc) ReleaseDC(lab,dc); + SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false); + SetWindowPos(hwnd,NULL,0,0, + labsize.right + sc8*2,labsize.bottom + button_height + sc8,SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOMOVE); + if (lab) SetWindowPos(lab,NULL,sc8,0,labsize.right,labsize.bottom,SWP_NOACTIVATE|SWP_NOZORDER); + SetFocus(GetDlgItem(hwnd,def_id)); + } + break; + case WM_SIZE: + { + RECT r; + GetClientRect(hwnd,&r); + HWND h = GetWindow(hwnd,GW_CHILD); + int n = 10, w[8]; + HWND tab[8],lbl=NULL; + int tabsz=0, bxwid=0, button_height=0; + while (h && n-- && tabsz<8) + { + int idx = GetWindowLong(h,GWL_ID); + if (idx == IDCANCEL || idx == IDOK || idx == IDNO || idx == IDYES || idx == IDRETRY) + { + RECT tr; + GetClientRect(h,&tr); + tab[tabsz] = h; + w[tabsz++] = tr.right - tr.left; + button_height = tr.bottom-tr.top; + bxwid += tr.right-tr.left; + } else if (idx==IDC_LABEL) lbl=h; + h = GetWindow(h,GW_HWNDNEXT); + } + const int bspace = SWELL_UI_SCALE(button_spacing), sc8 = SWELL_UI_SCALE(8); + if (lbl) SetWindowPos(lbl,NULL,sc8,0,r.right,r.bottom - sc8 - button_height, SWP_NOZORDER|SWP_NOACTIVATE); + int xo = r.right/2 - (bxwid + (tabsz-1)*bspace)/2; + for (int x=0; x255) ir=255; + if (ig<0) ig=0; else if (ig>255) ig=255; + if (ib<0) ib=0; else if (ib>255) ib=255; + return RGB(ir,ig,ib); +} + + +static void _RGB2HSV(double r, double g, double b, double *h, double *s, double *v) +{ + const double maxrgb=wdl_max(wdl_max(r,g),b); + const double df = maxrgb - wdl_min(wdl_min(r,g),b); + double d=r-g, degoffs = 240.0; + + if (g > r) + { + if (g > b) + { + degoffs=120; + d=b-r; + } + } + else if (r > b) + { + degoffs=0.0; + d=g-b; + } + + *v = maxrgb; + if (df) + { + degoffs += (d*60)/df; + if (degoffs<0.0) degoffs+=360.0; + else if (degoffs >= 360.0) degoffs-=360.0; + *h = degoffs; + *s = (df*256)/(maxrgb+1); + } + else + *h = *s = 0; +} + +static LRESULT WINAPI swellColorSelectProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static int s_reent,s_vmode; + static int wndw, custsz, buth, border, butw, edh, edlw, edew, vsize, psize, yt; + if (!wndw) + { + wndw = SWELL_UI_SCALE(400); + custsz = SWELL_UI_SCALE(20); + butw = SWELL_UI_SCALE(50); + buth = SWELL_UI_SCALE(24); + border = SWELL_UI_SCALE(4); + edh = SWELL_UI_SCALE(20); + edlw = SWELL_UI_SCALE(16); + edew = SWELL_UI_SCALE(40); + vsize = SWELL_UI_SCALE(40); + psize = border+edlw + edew; + yt = border + psize + border + (edh + border)*6; + } + + const int customperrow = (wndw-border)/(custsz+border); + switch (uMsg) + { + case WM_CREATE: + if (lParam) // swell-specific + { + SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc); + SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellColorSelectProc); + SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); + + SetWindowText(hwnd,"Choose Color"); + + SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false); + + SWELL_MakeButton(0, "OK", IDOK,0,0,0,0, 0); + SWELL_MakeButton(0, "Cancel", IDCANCEL,0,0,0,0, 0); + SWELL_MakeLabel(0, "(right click a custom color to save)", 0x500, 0,0,0,0, 0); + + static const char * const lbl[] = { "R","G","B","H","S","V"}; + for (int x=0;x<6;x++) + { + SWELL_MakeLabel(0,lbl[x], 0x100+x, 0,0,0,0, 0); + SWELL_MakeEditField(0x200+x, 0,0,0,0, 0); + } + + ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false); + int nrows = ((cs?cs->ncustom : 0 ) + customperrow-1)/wdl_max(customperrow,1); + SetWindowPos(hwnd,NULL,0,0, wndw, + yt + buth + border + nrows * (custsz+border), + SWP_NOZORDER|SWP_NOMOVE); + SendMessage(hwnd,WM_USER+100,0,3); + } + break; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + { + ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (!cs) break; + RECT r; + GetClientRect(hwnd,&r); + const int xt = r.right - edew - edlw - border*3; + + const int y = GET_Y_LPARAM(lParam); + const int x = GET_X_LPARAM(lParam); + if (x < xt && y < yt) + { + s_vmode = x >= xt-vsize; + SetCapture(hwnd); + // fall through + } + else + { + if (cs->custom && cs->ncustom && y >= yt && y < r.bottom - buth - border) + { + int row = (y-yt) / (custsz+border), rowoffs = (y-yt) % (custsz+border); + if (rowoffs < custsz) + { + int col = (x-border) / (custsz+border), coloffs = (x-border) % (custsz+border); + if (coloffs < custsz) + { + col += customperrow*row; + if (col >= 0 && col < cs->ncustom) + { + if (uMsg == WM_LBUTTONDOWN) + { + _RGB2HSV(GetRValue(cs->custom[col]),GetGValue(cs->custom[col]),GetBValue(cs->custom[col]),&cs->h,&cs->s,&cs->v); + SendMessage(hwnd,WM_USER+100,0,3); + } + else + { + cs->custom[col] = _HSV2RGBV(cs->h,cs->s,cs->v); + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + } + } + break; + } + // fall through + } + case WM_MOUSEMOVE: + if (GetCapture()==hwnd) + { + RECT r; + GetClientRect(hwnd,&r); + const int xt = r.right - edew - edlw - border*3; + ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (!cs) break; + int var = 255 - (GET_Y_LPARAM(lParam) - border)*256 / (yt-border*2); + if (var<0)var=0; + else if (var>255)var=255; + if (s_vmode) + { + if (var != cs->v) + { + cs->v=var; + SendMessage(hwnd,WM_USER+100,0,3); + } + } + else + { + int hue = (GET_X_LPARAM(lParam) - border)*360 / (xt-border - vsize); + if (hue<0) hue=0; + else if (hue>359) hue=359; + if (cs->h != hue || cs->s != var) + { + cs->h=hue; + cs->s=var; + SendMessage(hwnd,WM_USER+100,0,3); + } + } + } + break; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + ReleaseCapture(); + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (cs && BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + const int xt = r.right - edew - edlw - border*3; + if (cs->custom && cs->ncustom) + { + int ypos = yt; + int xpos = border; + for (int x = 0; x < cs->ncustom; x ++) + { + HBRUSH br = CreateSolidBrush(cs->custom[x]); + RECT tr={xpos,ypos,xpos+custsz, ypos+custsz }; + FillRect(ps.hdc,&tr,br); + DeleteObject(br); + + xpos += border+custsz; + if (xpos+custsz >= r.right) + { + xpos=border; + ypos += border + custsz; + } + } + } + + { + HBRUSH br = CreateSolidBrush(_HSV2RGBV(cs->h,cs->s,cs->v)); + RECT tr={r.right - border - psize, border, r.right-border, border+psize}; + FillRect(ps.hdc,&tr,br); + DeleteObject(br); + } + + if (!cs->bm) cs->bm = new LICE_SysBitmap(xt-border,yt-border); + else cs->bm->resize(xt-border,yt-border); + + int x1 = xt - border - vsize; + int var = cs->v; + + const int ysz = yt-border*2; + const int vary = ysz - 1 - (ysz * cs->v)/256; + + for (int y = 0; y < ysz; y ++) + { + LICE_pixel *wr = cs->bm->getBits() + cs->bm->getRowSpan() * y; + const int sat = 255 - y*256/ysz; + double xx=0.0, dx=384.0/x1; + int x; + for (x = 0; x < x1; x++) + { + *wr++ = LICE_HSV2Pix((int)(xx+0.5),sat,var,255); + xx+=dx; + } + LICE_pixel p = LICE_HSV2Pix(cs->h * 384.0/360.0,cs->s,sat ^ (y==vary ? 128 : 0),255); + for (;x < xt-border;x++) *wr++ = p; + } + LICE_pixel p = LICE_HSV2Pix((int)(cs->h+0.5),(int)(cs->s+0.5),((int)(128.5+cs->v))&255,255); + const int saty = ysz - 1 - (int) (ysz * cs->s + 0.5)/256; + const int huex = (x1*cs->h)/360; + LICE_Line(cs->bm,huex,saty-4,huex,saty+4,p,.75f,LICE_BLIT_MODE_COPY,false); + LICE_Line(cs->bm,huex-4,saty,huex+4,saty,p,.75f,LICE_BLIT_MODE_COPY,false); + + BitBlt(ps.hdc,border,border,xt-border,ysz,cs->bm->getDC(),0,0,SRCCOPY); + + EndPaint(hwnd,&ps); + } + } + + break; + case WM_GETMINMAXINFO: + { + LPMINMAXINFO p=(LPMINMAXINFO)lParam; + p->ptMinTrackSize.x = 300; + p->ptMinTrackSize.y = 300; + } + break; + case WM_USER+100: + { + ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (cs) + { + double t[6]; + t[3] = cs->h; + t[4] = cs->s; + t[5] = cs->v; + _HSV2RGB(t[3],t[4],t[5],t,t+1,t+2); + s_reent++; + for (int x=0;x<6;x++) if (lParam & ((x<3)?1:2)) SetDlgItemInt(hwnd,0x200+x,(int) (t[x]+0.5),FALSE); + s_reent--; + InvalidateRect(hwnd,NULL,FALSE); + } + } + break; + case WM_SIZE: + { + RECT r; + GetClientRect(hwnd,&r); + int tx = r.right - edew-edlw-border*2, ty = border*2 + psize; + for (int x=0;x<6;x++) + { + SetWindowPos(GetDlgItem(hwnd,0x100+x),NULL,tx, ty, edlw, edh, SWP_NOZORDER|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwnd,0x200+x),NULL,tx+edlw+border, ty, edew, edh, SWP_NOZORDER|SWP_NOACTIVATE); + ty += border+edh; + } + + r.right -= border + butw; + r.bottom -= border + buth; + SetWindowPos(GetDlgItem(hwnd,IDCANCEL), NULL, r.right, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE); + r.right -= border*2 + butw; + SetWindowPos(GetDlgItem(hwnd,IDOK), NULL, r.right, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE); + + SetWindowPos(GetDlgItem(hwnd,0x500), NULL, border, r.bottom, r.right-border*2, buth, SWP_NOZORDER|SWP_NOACTIVATE); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwnd,0); + break; + case IDOK: + EndDialog(hwnd,1); + break; + case 0x200: + case 0x201: + case 0x202: + case 0x203: + case 0x204: + case 0x205: + if (!s_reent) + { + const bool ishsv = LOWORD(wParam) >= 0x203; + int offs = ishsv ? 0x203 : 0x200; + BOOL t = FALSE; + double h = GetDlgItemInt(hwnd,offs++,&t,FALSE); + if (!t) break; + double s = GetDlgItemInt(hwnd,offs++,&t,FALSE); + if (!t) break; + double v = GetDlgItemInt(hwnd,offs++,&t,FALSE); + if (!t) break; + if (s<0) s=0; else if (s>255) s=255; + if (v<0) v=0; else if (v>255) v=255; + if (h<0) h=0; + + if (!ishsv) + { + if (h>255) h=255; + _RGB2HSV(h,s,v,&h,&s,&v); + } + else + { + if (h>360) h=360; + } + + ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (cs) + { + cs->h = h; + cs->s = s; + cs->v = v; + } + SendMessage(hwnd,WM_USER+100,0,ishsv?1:2); + } + break; + } + break; + + } + return 0; +} +#endif //SWELL_LICE_GDI + +bool SWELL_ChooseColor(HWND h, COLORREF *val, int ncustom, COLORREF *custom) +{ +#ifdef SWELL_LICE_GDI + ChooseColor_State state = { ncustom, custom }; + COLORREF c = val ? *val : 0; + _RGB2HSV(GetRValue(c),GetGValue(c),GetBValue(c),&state.h,&state.s,&state.v); + bool rv = DialogBoxParam(NULL,NULL,h,swellColorSelectProc,(LPARAM)&state)!=0; + delete state.bm; + if (rv && val) + { + *val = _HSV2RGBV(state.h,state.s,state.v); + } + return rv; +#else + return false; +#endif +} + +#if defined(SWELL_FREETYPE) && defined(SWELL_LICE_GDI) + +struct FontChooser_State +{ + FontChooser_State() + { + hFont = 0; + } + ~FontChooser_State() + { + DeleteObject(hFont); + } + + LOGFONT font; + HFONT hFont; + WDL_FastString lastfn; +}; + +extern const char *swell_last_font_filename; + +const char *swell_enumFontFiles(int x); +int swell_getLineLength(const char *buf, int *post_skip, int wrap_maxwid, HDC hdc); + +static LRESULT WINAPI swellFontChooserProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + enum { IDC_LIST=0x100, IDC_FACE, IDC_SIZE, IDC_WEIGHT, IDC_ITALIC }; + enum { preview_h = 90, _border = 4, _buth = 24 }; + + switch (uMsg) + { + case WM_CREATE: + if (lParam) // swell-specific + { + SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc); + SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellFontChooserProc); + SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); + + SetWindowText(hwnd,"Choose Font"); + + SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false); + + SWELL_MakeButton(0, "OK", IDOK,0,0,0,0, 0); + SWELL_MakeButton(0, "Cancel", IDCANCEL,0,0,0,0, 0); + SWELL_MakeListBox(IDC_LIST,0,0,0,0, LBS_OWNERDRAWFIXED); + SWELL_MakeEditField(IDC_FACE, 0,0,0,0, 0); + SWELL_MakeEditField(IDC_SIZE, 0,0,0,0, 0); + SWELL_MakeCombo(IDC_WEIGHT, 0,0,0,0, CBS_DROPDOWNLIST); + SWELL_MakeCheckBox("Italic",IDC_ITALIC,0,0,0,0, 0); + + SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_ADDSTRING,0,(LPARAM)"Normal"); + SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_ADDSTRING,0,(LPARAM)"Bold"); + SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_ADDSTRING,0,(LPARAM)"Light"); + + SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false); + + SetWindowPos(hwnd,NULL,0,0, 550,380, SWP_NOZORDER|SWP_NOMOVE); + + WDL_StringKeyedArray list; + const char *fontfile; + int x; + for (x=0; (fontfile=swell_enumFontFiles(x)); x ++) + { + char buf[512]; + lstrcpyn_safe(buf,WDL_get_filepart(fontfile),sizeof(buf)); + char *tmp = buf; + while (*tmp && *tmp != '-' && *tmp != '.') tmp++; + *tmp=0; + if (*buf) list.AddUnsorted(buf,true); + } + swell_enumFontFiles(-1); // clear cache + list.Resort(); + FontChooser_State *cs = (FontChooser_State*)lParam; + bool italics = cs->font.lfItalic!=0; + int wt = cs->font.lfWeight; + const char *lp=NULL; + int cnt=0; + for (x=0;xfont.lfFaceName)) + SendDlgItemMessage(hwnd,IDC_LIST,LB_SETCURSEL,cnt,0); + + size_t ll; + if (lp && !strncmp(p,lp,ll=strlen(lp))) + { + // if this is an extension of the last one, skip + const char *trail = p+ll; + if (strlen(trail)<=2) + { + for (int y=0;y<2;y++) + { + char c = *trail; + if (c>0) c=toupper(c); + if (c == 'B' || c == 'I' || c == 'L') trail++; + } + } + else while (*trail) + { + if (!strnicmp(trail,"Bold",4)) trail+=4; + else if (!strnicmp(trail,"Light",5)) trail+=5; + else if (!strnicmp(trail,"Italic",6)) trail+=6; + else if (!strnicmp(trail,"Oblique",7)) trail+=7; + else break; + } + if (!*trail) continue; + } + cnt++; + SendDlgItemMessage(hwnd,IDC_LIST,LB_ADDSTRING,0,(LPARAM)p); + lp=p; + } + } + SetDlgItemText(hwnd,IDC_FACE,cs->font.lfFaceName); + SetDlgItemInt(hwnd,IDC_SIZE,cs->font.lfHeight < 0 ? -cs->font.lfHeight : cs->font.lfHeight,TRUE); + SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_SETCURSEL,wt<=FW_LIGHT ? 2 : wt < FW_BOLD ? 0 : 1,0); + if (italics) + CheckDlgButton(hwnd,IDC_ITALIC,BST_CHECKED); + } + break; + case WM_DRAWITEM: + { + DRAWITEMSTRUCT *di=(DRAWITEMSTRUCT *)lParam; + FontChooser_State *cs = (FontChooser_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (cs && di->CtlID == IDC_LIST) + { + char buf[512]; + buf[0]=0; + SendDlgItemMessage(hwnd,IDC_LIST,LB_GETTEXT,di->itemID,(WPARAM)buf); + if (buf[0]) + { + HFONT font = CreateFont(g_swell_ctheme.default_font_size, 0, 0, 0, cs->font.lfWeight, cs->font.lfItalic, + FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, buf); + + HGDIOBJ oldFont = SelectObject(di->hDC,font); + DrawText(di->hDC,buf,-1,&di->rcItem,DT_VCENTER|DT_LEFT|DT_NOPREFIX); + wchar_t tmp[] = {'a','A','z','Z'}; + unsigned short ind[4]; + GetGlyphIndicesW(di->hDC,tmp,4,ind,0); + SelectObject(di->hDC,oldFont); + + int x; + for (x=0;x<4 && ind[x]==0xffff;x++); + if (x==4) + { + RECT r = di->rcItem; + r.right-=4; + DrawText(di->hDC,buf,-1,&r,DT_VCENTER|DT_RIGHT|DT_NOPREFIX); + } + DeleteObject(font); + + } + } + } + return 0; + case WM_PAINT: + { + PAINTSTRUCT ps; + FontChooser_State *cs = (FontChooser_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (cs && BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + + const int border = SWELL_UI_SCALE(_border); + const int buth = SWELL_UI_SCALE(_buth); + const int ph = SWELL_UI_SCALE(preview_h); + r.left += border; + r.right -= border; + r.bottom -= border*2 + buth; + r.top = r.bottom - ph; + + HFONT f = CreateFontIndirect(&cs->font); + + HBRUSH br = CreateSolidBrush(RGB(255,255,255)); + FillRect(ps.hdc,&r,br); + DeleteObject(br); + SetTextColor(ps.hdc,RGB(0,0,0)); + SetBkMode(ps.hdc,TRANSPARENT); + r.right-=4; + r.left+=4; + if (swell_last_font_filename) + { + r.bottom -= DrawText(ps.hdc,swell_last_font_filename,-1,&r,DT_BOTTOM|DT_NOPREFIX|DT_SINGLELINE|DT_RIGHT); + } + + HGDIOBJ oldFont = SelectObject(ps.hdc,f); + + extern const char *g_swell_fontpangram; + const char *str = g_swell_fontpangram; + // + // thanks, http://dailypangram.tumblr.com/ :) + if (!str) str = "Strangely, aerobic exercise doesn’t quite work with improvised free jazz."; + + while (*str) + { + int sk=0, lb=swell_getLineLength(str, &sk, r.right-r.left, ps.hdc); + if (!lb&&!sk) break; + if (lb>0) r.top += DrawText(ps.hdc,str,lb,&r,DT_TOP|DT_LEFT|DT_NOPREFIX|DT_SINGLELINE); + str+=lb+sk; + } + + + SelectObject(ps.hdc,oldFont); + DeleteObject(f); + + + EndPaint(hwnd,&ps); + } + } + + break; + case WM_GETMINMAXINFO: + { + LPMINMAXINFO p=(LPMINMAXINFO)lParam; + p->ptMinTrackSize.x = 400; + p->ptMinTrackSize.y = 300; + } + break; + case WM_SIZE: + { + RECT r; + GetClientRect(hwnd,&r); + const int border = SWELL_UI_SCALE(_border); + const int buth = SWELL_UI_SCALE(_buth); + const int butw = SWELL_UI_SCALE(50); + const int edh = SWELL_UI_SCALE(20); + const int size_w = SWELL_UI_SCALE(50); + const int wt_w = SWELL_UI_SCALE(80); + const int italic_w = SWELL_UI_SCALE(60); + + r.left += border; + r.right -= border; + + r.bottom -= border + buth; + SetWindowPos(GetDlgItem(hwnd,IDCANCEL), NULL, r.right - butw, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwnd,IDOK), NULL, r.right - border - butw*2, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE); + r.bottom -= SWELL_UI_SCALE(preview_h) + border; + int psz=wdl_max(g_swell_ctheme.combo_height,edh); + r.bottom -= psz + border; + SetWindowPos(GetDlgItem(hwnd,IDC_FACE),NULL,r.left,r.bottom + (psz-edh)/2, + r.right-r.left - size_w-wt_w-italic_w - border*3, edh, SWP_NOZORDER|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwnd,IDC_SIZE),NULL,r.right-size_w-wt_w-italic_w-border*2,r.bottom + (psz-edh)/2, + size_w, edh, SWP_NOZORDER|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwnd,IDC_WEIGHT),NULL,r.right-wt_w-italic_w-border,r.bottom + (psz-g_swell_ctheme.combo_height)/2, + wt_w, g_swell_ctheme.combo_height, SWP_NOZORDER|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwnd,IDC_ITALIC),NULL,r.right-italic_w,r.bottom + (psz-edh)/2, + italic_w, edh, SWP_NOZORDER|SWP_NOACTIVATE); + + SetWindowPos(GetDlgItem(hwnd,IDC_LIST), NULL, border, border, r.right, r.bottom - border*2, SWP_NOZORDER|SWP_NOACTIVATE); + + + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_LIST: + if (HIWORD(wParam) == LBN_SELCHANGE) + { + int idx = (int) SendDlgItemMessage(hwnd,IDC_LIST,LB_GETCURSEL,0,0); + if (idx>=0) + { + char buf[512]; + buf[0]=0; + SendDlgItemMessage(hwnd,IDC_LIST,LB_GETTEXT,idx,(WPARAM)buf); + if (buf[0]) SetDlgItemText(hwnd,IDC_FACE,buf); + } + } + break; + case IDC_SIZE: + case IDC_FACE: + case IDC_ITALIC: + case IDC_WEIGHT: + { + FontChooser_State *cs = (FontChooser_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (cs) + { + if (LOWORD(wParam) == IDC_FACE) + GetDlgItemText(hwnd,IDC_FACE,cs->font.lfFaceName,sizeof(cs->font.lfFaceName)); + else if (LOWORD(wParam) == IDC_SIZE) + { + BOOL t; + int a = GetDlgItemInt(hwnd,IDC_SIZE,&t,FALSE); + if (t) + { + if (cs->font.lfHeight < 0) cs->font.lfHeight = -a; + else cs->font.lfHeight = a; + } + } + else if (LOWORD(wParam) == IDC_ITALIC) cs->font.lfItalic = IsDlgButtonChecked(hwnd,IDC_ITALIC) ? 1:0; + else if (LOWORD(wParam) == IDC_WEIGHT && HIWORD(wParam) == CBN_SELCHANGE) + { + int idx = (int) SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_GETCURSEL,0,0); + if (idx==0) cs->font.lfWeight = FW_NORMAL; + else if (idx==1) cs->font.lfWeight = FW_BOLD; + else if (idx==2) cs->font.lfWeight = FW_LIGHT; + } + InvalidateRect(hwnd,NULL,FALSE); + } + } + break; + case IDCANCEL: + EndDialog(hwnd,0); + break; + case IDOK: + EndDialog(hwnd,1); + break; + } + break; + + } + return 0; +} + +void *swell_MatchFont(const char *lfFaceName, int weight, int italic, const char **fnOut); + +#endif + +bool SWELL_ChooseFont(HWND h, LOGFONT *lf) +{ +#if defined(SWELL_FREETYPE) && defined(SWELL_LICE_GDI) + FontChooser_State state; + state.font = *lf; + + bool rv = DialogBoxParam(NULL,NULL,h,swellFontChooserProc,(LPARAM)&state)!=0; + if (rv) + { + *lf = state.font; + } + return rv; +#else + return false; +#endif +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-miscdlg.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-miscdlg.mm new file mode 100644 index 0000000..e572766 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-miscdlg.mm @@ -0,0 +1,767 @@ +/* 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. + + + This file provides basic APIs for browsing for files, directories, and messageboxes. + + These APIs don't all match the Windows equivelents, but are close enough to make it not too much trouble. + + */ + + +#ifndef SWELL_PROVIDED_BY_APP + +#include "swell.h" +#include "swell-dlggen.h" +#include "../wdlcstring.h" +#import +#include "swell-internal.h" + +struct swell_autoarp { + swell_autoarp() { pool = [[NSAutoreleasePool alloc] init]; } + ~swell_autoarp() { [pool release]; } + NSAutoreleasePool *pool; +}; + +static NSMutableArray *extensionsFromList(const char *extlist, const char *def_ext=NULL) +{ + NSMutableArray *fileTypes = [[NSMutableArray alloc] initWithCapacity:30]; + while (*extlist) + { + extlist += strlen(extlist)+1; + if (!*extlist) break; + while (*extlist) + { + while (*extlist && *extlist != '.') extlist++; + if (!*extlist) break; + extlist++; + char tmp[32]; + lstrcpyn_safe(tmp,extlist,sizeof(tmp)); + if (strstr(tmp,";")) strstr(tmp,";")[0]=0; + if (tmp[0] && tmp[0]!='*') + { + NSString *s=(NSString *)SWELL_CStringToCFString(tmp); + const size_t tmp_len = strlen(tmp); + if (def_ext && *def_ext && + !strnicmp(def_ext,tmp,tmp_len) && + (!def_ext[tmp_len] || def_ext[tmp_len] == ';')) + [fileTypes insertObject:s atIndex:0]; + else + [fileTypes addObject:s]; + [s release]; + } + while (*extlist && *extlist != ';') extlist++; + if (*extlist == ';') extlist++; + } + extlist++; + } + + return fileTypes; +} + +static const char *BFSF_Templ_dlgid; +static DLGPROC BFSF_Templ_dlgproc; +static struct SWELL_DialogResourceIndex *BFSF_Templ_reshead; +void BrowseFile_SetTemplate(const char *dlgid, DLGPROC dlgProc, struct SWELL_DialogResourceIndex *reshead) +{ + BFSF_Templ_reshead=reshead; + BFSF_Templ_dlgid=dlgid; + BFSF_Templ_dlgproc=dlgProc; +} +static const char *s_browse_extsel; + +static LRESULT fileTypeChooseProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + const int wndh = 22, lblw = 80, combow = 250, def_wid = lblw + 4 + combow + 4; + switch (uMsg) + { + case WM_CREATE: + SetOpaque(hwnd,FALSE); + SWELL_MakeSetCurParms(1,1,0,0,hwnd,true,false); + SWELL_MakeLabel(1,"File type:",1001,0,2,lblw,wndh,0); + SWELL_MakeCombo(1000, lblw + 4,0, combow, wndh,3/*CBS_DROPDOWNLIST*/); + SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false); + SetWindowPos(hwnd,NULL,0,0,def_wid,wndh,SWP_NOMOVE|SWP_NOZORDER); + { + const char *extlist = ((const char **)lParam)[0]; + SetWindowLongPtr(hwnd,GWLP_USERDATA,(LPARAM)extlist); + const char *initial_file = ((const char **)lParam)[1]; + + if (initial_file) initial_file=WDL_get_fileext(initial_file); + const size_t initial_file_len = initial_file && *initial_file ? strlen(++initial_file) : 0; + + int def_sel = -1; + + HWND combo = GetDlgItem(hwnd,1000); + while (*extlist) + { + const char *next = extlist + strlen(extlist)+1; + if (!*next) break; + + if (strcmp(next,"*.*")) + { + int a = (int)SendMessage(combo,CB_ADDSTRING,0,(LPARAM)extlist); + + // userdata for each item is pointer to un-terminated extension. + const char *p = next; + while (*p && *p != '.') p++; + if (*p) p++; + const char *bestp = p; + + // scan extension list for matching initial file, use that (and set default) + if (def_sel < 0 && initial_file) while (*p) + { + if (!strnicmp(p,initial_file,initial_file_len) && (p[initial_file_len] == ';' || !p[initial_file_len])) + { + s_browse_extsel = p; + bestp = p; + def_sel = a; + break; + } + else + { + while (*p && *p != '.') p++; + if (*p) p++; + } + } + SendMessage(combo,CB_SETITEMDATA,a,(LPARAM)bestp); + } + + extlist = next + strlen(next)+1; + if (!*extlist) break; + } + SendMessage(combo,CB_SETCURSEL,def_sel>=0?def_sel:0,0); + } + return 0; + case WM_SIZE: + { + RECT r; + GetClientRect(hwnd,&r); + const int xpos = r.right / 2 - def_wid/2; + SetWindowPos(GetDlgItem(hwnd,1001),NULL, xpos,2,0,0,SWP_NOZORDER|SWP_NOSIZE); + SetWindowPos(GetDlgItem(hwnd,1000),NULL, xpos + lblw + 4,0,0,0,SWP_NOZORDER|SWP_NOSIZE); + + } + return 0; + case WM_COMMAND: + if (LOWORD(wParam) == 1000 && HIWORD(wParam) == CBN_SELCHANGE) + { + int a = (int)SendDlgItemMessage(hwnd,1000,CB_GETCURSEL,0,0); + if (a>=0) + { + const char *extlist = (const char *)GetWindowLongPtr(hwnd,GWLP_USERDATA); + if (extlist) + { + NSArray *fileTypes = extensionsFromList(extlist, + s_browse_extsel = (const char *)SendDlgItemMessage(hwnd,1000,CB_GETITEMDATA,a,0) + ); + if ([fileTypes count]>0) + { + NSSavePanel *par = (NSSavePanel*)GetWindowLongPtr(hwnd,0); + if (par && [par isKindOfClass:[NSSavePanel class]]) [(NSSavePanel *)par setAllowedFileTypes:fileTypes]; + } + [fileTypes release]; + } + } + } + return 0; + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + +static void restoreMenuForFocus() +{ + HWND h = GetFocus(); + HMENU menu = h ? GetMenu(h) : NULL; + if (!menu) + menu = SWELL_GetDefaultWindowMenu(); + if (menu) + SWELL_SetCurrentMenu(menu); +} + +// return true +bool BrowseForSaveFile(const char *text, const char *initialdir, const char *initialfile, const char *extlist, + char *fn, int fnsize) +{ + swell_autoarp auto_arp; + + NSSavePanel *panel = [NSSavePanel savePanel]; + NSMutableArray *fileTypes = extensionsFromList(extlist); + NSString *title=(NSString *)SWELL_CStringToCFString(text); + NSString *ifn=NULL, *idir = NULL; + + [panel setTitle:title]; + [panel setAccessoryView:nil]; + HWND av_parent = (HWND)panel; + + s_browse_extsel = NULL; + + if ([fileTypes count]>1) + { + const char *ar[2]={extlist,initialfile}; + av_parent = SWELL_CreateDialog(NULL,0,NULL,fileTypeChooseProc,(LPARAM)ar); + if (!av_parent) av_parent = (HWND)panel; + else SetWindowLongPtr(av_parent, 0, (LPARAM)panel); + } + + HWND oh=NULL; + if (BFSF_Templ_dlgproc && BFSF_Templ_dlgid) // create a child dialog and set it to the panel + { + oh=SWELL_CreateDialog(BFSF_Templ_reshead, BFSF_Templ_dlgid, av_parent, BFSF_Templ_dlgproc, 0); + BFSF_Templ_dlgproc=0; + BFSF_Templ_dlgid=0; + } + if (av_parent != (HWND)panel) + { + if (oh) + { + RECT r1,r2; + GetClientRect(oh,&r1); + GetClientRect(av_parent,&r2); + + SetWindowPos(oh,NULL,0,r2.bottom,0,0,SWP_NOZORDER|SWP_NOSIZE); + if (r2.right < r1.right) r2.right=r1.right; + SetWindowPos(av_parent,NULL,0,0,r2.right,r2.bottom+r1.bottom,SWP_NOZORDER|SWP_NOMOVE); + ShowWindow(oh,SW_SHOWNA); + } + oh = av_parent; + + NSWindow *oldw = [(NSView *)av_parent window]; + [panel setAccessoryView:(NSView *)av_parent]; // we resized our accessory view + SendMessage(av_parent,WM_COMMAND,(CBN_SELCHANGE<<16) | 1000,(LPARAM)GetDlgItem(av_parent,1000)); + [(NSView *)av_parent setHidden:NO]; + [oldw release]; + } + else if ([fileTypes count]>0) + { + [panel setAllowedFileTypes:fileTypes]; + } + + if (initialfile && *initialfile && *initialfile != '.') + { + char s[2048]; + lstrcpyn_safe(s,initialfile,sizeof(s)); + + if (ext_valid_for_extlist(WDL_get_fileext(initialfile),extlist)>=0) + WDL_remove_fileext(s); + + char *p=s; + while (*p) p++; + while (p >= s && *p != '/') p--; + if (p>=s) + { + *p=0; + ifn=(NSString *)SWELL_CStringToCFString(p+1); + idir=(NSString *)SWELL_CStringToCFString(s[0]?s:"/"); + } + else + ifn=(NSString *)SWELL_CStringToCFString(s); + } + if (!idir && initialdir && *initialdir) + { + idir=(NSString *)SWELL_CStringToCFString(initialdir); + } + + HMENU hm=SWELL_GetDefaultModalWindowMenu(); + if (hm) + { + hm=SWELL_DuplicateMenu(hm); + SWELL_SetCurrentMenu(hm); + } + + NSInteger result = [panel runModalForDirectory:idir file:ifn]; + restoreMenuForFocus(); + if (hm) DestroyMenu(hm); + + if (oh) SendMessage(oh,WM_DESTROY,0,0); + [panel setAccessoryView:nil]; + + bool rv = false; + + NSString *str; + if (result == NSOKButton && fn && fnsize > 0 && (str = [panel filename])) + { + SWELL_CFStringToCString(str,fn,fnsize); + if (fn[0]) + { + char tmp[256]; + + const NSUInteger nft = [fileTypes count]; + NSUInteger x = nft; + + const char *ext = WDL_get_fileext(fn); + if (*ext) + { + // see if extension is in list + ext++; + for (x = 0; x < nft; x ++) + { + NSString *s = [fileTypes objectAtIndex:x]; + if (s) + { + SWELL_CFStringToCString(s,tmp,sizeof(tmp)); + if (!stricmp(tmp,ext)) break; + } + } + } + + if (x == nft) + { + // this might no longer be necessary -- it was at one point when [accessoryview window] was not a NSSavePanel, + // but we fixed that, so the extension should always be applied (probably) + + // not in list: apply last-selected extension, then default extension, if specified, or first extension from list + if (s_browse_extsel && *s_browse_extsel) + { + lstrcatn(fn,".",fnsize); + lstrcatn(fn,s_browse_extsel,fnsize); + } + else if (initialfile && *initialfile == '.') + { + lstrcatn(fn,initialfile,fnsize); + } + else if (nft > 0) + { + NSString *s = [fileTypes objectAtIndex:0]; + if (s) + { + tmp[0] = '.'; + SWELL_CFStringToCString(s,tmp+1,sizeof(tmp)-1); + lstrcatn(fn,tmp,fnsize); + } + } + } + + rv = true; + } + } + + [title release]; + [fileTypes release]; + [idir release]; + [ifn release]; + + return rv; +} + +bool BrowseForDirectory(const char *text, const char *initialdir, char *fn, int fnsize) +{ + swell_autoarp auto_arp; + + NSOpenPanel *panel = [NSOpenPanel openPanel]; + NSString *title=(NSString *)SWELL_CStringToCFString(text); + NSString *idir=NULL; + + [panel setTitle:title]; + [panel setAllowsMultipleSelection:NO]; + [panel setCanChooseFiles:NO]; + [panel setCanCreateDirectories:YES]; + [panel setCanChooseDirectories:YES]; + [panel setResolvesAliases:YES]; + + HWND oh=NULL; + if (BFSF_Templ_dlgproc && BFSF_Templ_dlgid) // create a child dialog and set it to the panel + { + oh=SWELL_CreateDialog(BFSF_Templ_reshead, BFSF_Templ_dlgid, (HWND)panel, BFSF_Templ_dlgproc, 0); + BFSF_Templ_dlgproc=0; + BFSF_Templ_dlgid=0; + } + + if (initialdir && *initialdir) + { + idir=(NSString *)SWELL_CStringToCFString(initialdir); + } + + HMENU hm=SWELL_GetDefaultModalWindowMenu(); + if (hm) + { + hm=SWELL_DuplicateMenu(hm); + SWELL_SetCurrentMenu(hm); + } + NSInteger result = [panel runModalForDirectory:idir file:nil types:nil]; + restoreMenuForFocus(); + if (hm) DestroyMenu(hm); + + if (oh) SendMessage(oh,WM_DESTROY,0,0); + [panel setAccessoryView:nil]; + + [idir release]; + [title release]; + + if (result != NSOKButton) return 0; + + NSArray *filesToOpen = [panel filenames]; + NSInteger count = [filesToOpen count]; + + if (!count) return 0; + + NSString *aFile = [filesToOpen objectAtIndex:0]; + if (!aFile) return 0; + if (fn && fnsize>0) + { + SWELL_CFStringToCString(aFile,fn,fnsize); + fn[fnsize-1]=0; + } + return 1; +} + + +char *BrowseForFiles(const char *text, const char *initialdir, + const char *initialfile, bool allowmul, const char *extlist) +{ + swell_autoarp auto_arp; + + NSOpenPanel *panel = [NSOpenPanel openPanel]; + NSString *title=(NSString *)SWELL_CStringToCFString(text); + NSString *ifn=NULL, *idir=NULL; + NSMutableArray *fileTypes = extensionsFromList(extlist); + + HWND oh=NULL; + if (BFSF_Templ_dlgproc && BFSF_Templ_dlgid) // create a child dialog and set it to the panel + { + oh=SWELL_CreateDialog(BFSF_Templ_reshead, BFSF_Templ_dlgid, (HWND)panel, BFSF_Templ_dlgproc, 0); + BFSF_Templ_dlgproc=0; + BFSF_Templ_dlgid=0; + } + + [panel setTitle:title]; + [panel setAllowsMultipleSelection:(allowmul?YES:NO)]; + [panel setCanChooseFiles:YES]; + [panel setCanChooseDirectories:NO]; + [panel setResolvesAliases:YES]; + + if (initialfile && *initialfile) + { + char s[2048]; + lstrcpyn_safe(s,initialfile,sizeof(s)); + char *p=s; + while (*p) p++; + while (p >= s && *p != '/') p--; + if (p>=s) + { + *p=0; + ifn=(NSString *)SWELL_CStringToCFString(p+1); + idir=(NSString *)SWELL_CStringToCFString(s[0]?s:"/"); + } + else + ifn=(NSString *)SWELL_CStringToCFString(s); + } + if (!idir && initialdir && *initialdir) + { + idir=(NSString *)SWELL_CStringToCFString(initialdir); + } + + HMENU hm=SWELL_GetDefaultModalWindowMenu(); + if (hm) + { + hm=SWELL_DuplicateMenu(hm); + SWELL_SetCurrentMenu(hm); + } + NSInteger result = [panel runModalForDirectory:idir file:ifn types:([fileTypes count]>0 ? fileTypes : nil)]; + + restoreMenuForFocus(); + if (hm) DestroyMenu(hm); + + if (oh) SendMessage(oh,WM_DESTROY,0,0); + [panel setAccessoryView:nil]; + + [ifn release]; + [idir release]; + + [fileTypes release]; + [title release]; + + if (result != NSOKButton) return 0; + + NSArray *filesToOpen = [panel filenames]; + const NSInteger count = [filesToOpen count]; + + if (!count) return 0; + + char fn[2048]; + if (count==1||!allowmul) + { + NSString *aFile = [filesToOpen objectAtIndex:0]; + if (!aFile) return 0; + SWELL_CFStringToCString(aFile,fn,sizeof(fn)); + fn[sizeof(fn)-1]=0; + char *ret=(char *)malloc(strlen(fn)+2); + memcpy(ret,fn,strlen(fn)); + ret[strlen(fn)]=0; + ret[strlen(fn)+1]=0; + return ret; + } + + size_t rsize=1; + char *ret=0; + for (NSInteger i=0; i 0 ? b1 : ret < 0 ? b3 : b2; +} + +static WDL_DLGRET color_okCancelProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + SetWindowLongPtr(hwndDlg,0,0); + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + SetWindowLongPtr(hwndDlg,0,1); + break; + case IDCANCEL: + SetWindowLongPtr(hwndDlg,0,2); + break; + } + break; + } + return 0; +} +static void okcancel_create(HWND hwnd, int f) +{ + SWELL_MakeSetCurParms(1.7,1.7,0,0,hwnd,false,false); + SWELL_MakeButton(1,"OK",IDOK,48,2,44,14,0); + SWELL_MakeButton(1,"Cancel",IDCANCEL,2,2,44,14,0); + SWELL_MakeSetCurParms(1.7,1.7,0,0,NULL,false,false); +} +static HWND makeOKcancel(HWND par) +{ + const char *dlgid = ""; + SWELL_DialogResourceIndex tmph = { + dlgid, "", + SWELL_DLG_WS_FLIPPED|SWELL_DLG_WS_NOAUTOSIZE|SWELL_DLG_WS_CHILD, + okcancel_create, + (int)(98*1.7),(int)(18*1.7), + NULL + }; + return SWELL_CreateDialog(&tmph,dlgid,par,color_okCancelProc,0); +} + +static bool ColorFromNSColor(NSColor *color, COLORREF *colout) +{ + if (!color) return false; + + CGFloat r,g,b; + NSColor *color2=[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + if (!color2) return false; + + [color2 getRed:&r green:&g blue:&b alpha:NULL]; + int rv=(int)(r*255.0 + 0.5); + int gv=(int)(g*255.0 + 0.5); + int bv=(int)(b*255.0 + 0.5); + if (rv<0)rv=0; else if (rv>255)rv=255; + if (gv<0)gv=0; else if (gv>255)gv=255; + if (bv<0)bv=0; else if (bv>255)bv=255; + + *colout = RGB(rv,gv,bv); + return true; +} + +bool SWELL_ChooseColor(HWND unused, COLORREF *a, int ncustom, COLORREF *custom) +{ + NSColorPanel *pan=[NSColorPanel sharedColorPanel]; + if (!pan||!a) return false; + + NSColor *bkcol=[NSColor colorWithCalibratedRed:GetRValue(*a)/255.0f green:GetGValue(*a)/255.0f blue:GetBValue(*a)/255.0f alpha:1.0f]; + [pan setColor:bkcol]; + [pan setShowsAlpha:NO]; + [pan setAccessoryView:nil]; + + HWND h = makeOKcancel((HWND)pan); + bool hadOK=!h; + + NSModalSession ctx=[NSApp beginModalSessionForWindow:pan]; + while ([NSApp runModalSession:ctx]==NSRunContinuesResponse && [pan isVisible]) + { + const LONG_PTR res = h ? GetWindowLongPtr(h,0) : 0; + if (res) { hadOK=res==1; break; } + Sleep(3); + } + + [NSApp endModalSession:ctx]; + + if (h) + { + SendMessage(h,WM_DESTROY,0,0); + [pan setAccessoryView:nil]; // will destroy h anyway + [pan close]; + } + + return hadOK && ColorFromNSColor([pan color],a); +} + +bool SWELL_ChooseFont(HWND unused, LOGFONT *lf) +{ + if (!lf) return false; + + NSFontPanel *pan = [NSFontPanel sharedFontPanel]; + int sz=lf->lfHeight > 0 ? lf->lfHeight : lf->lfHeight < 0 ? -lf->lfHeight : lf->lfWidth; + + char buf[1024]; + lstrcpyn_safe(buf,lf->lfFaceName,sizeof(buf)); + if (lf->lfWeight >= FW_BOLD) lstrcatn(buf," Bold",sizeof(buf)); + if (lf->lfItalic) lstrcatn(buf," Italic",sizeof(buf)); + + NSString *lbl=(NSString *)SWELL_CStringToCFString(buf); + NSFont *tmpfont = [NSFont fontWithName:lbl size:sz]; + if (!tmpfont) tmpfont = [NSFont systemFontOfSize:sz]; + [lbl release]; + + [pan setPanelFont:tmpfont isMultiple:NO]; + + [pan setAccessoryView:nil]; + HWND h = makeOKcancel((HWND)pan); + + bool hadOK=!h; + NSModalSession ctx=[NSApp beginModalSessionForWindow:pan]; + while ([NSApp runModalSession:ctx]==NSRunContinuesResponse && [pan isVisible]) + { + const LONG_PTR a = h ? GetWindowLongPtr(h,0) : 0; + if (a) { hadOK=a==1; break; } + Sleep(3); + } + + [NSApp endModalSession:ctx]; + + if (h) + { + SendMessage(h,WM_DESTROY,0,0); + [pan setAccessoryView:nil]; // will destroy h + [pan close]; + } + + if (!hadOK) return false; + + NSFont *newfont = [pan panelConvertFont:tmpfont]; + int newsz = (int) ([newfont pointSize]+0.5); + LOGFONT oldf=*lf; + if (newsz != sz) + { + lf->lfWidth=0; + lf->lfHeight = newsz; + } + + SWELL_CFStringToCString([newfont familyName],lf->lfFaceName, sizeof(lf->lfFaceName)); + SWELL_CFStringToCString([newfont displayName],buf,sizeof(buf)); + + lf->lfItalic = !! strstr(buf,"Italic"); + lf->lfWeight = strstr(buf,"Bold") ? FW_BOLD : FW_NORMAL; + + return memcmp(lf,&oldf,sizeof(LOGFONT)) != 0; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-modstub-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-modstub-generic.cpp new file mode 100644 index 0000000..c059cb7 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-modstub-generic.cpp @@ -0,0 +1,150 @@ +/* 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. +*/ + +#ifdef SWELL_PROVIDED_BY_APP + +#define SWELL_API_DEFPARM(x) +#define SWELL_API_DEFINE(ret,func,parms) ret (*func) parms ; +extern "C" { +#include "swell.h" +}; + +// only include this file in projects that are linked to libSwell.so + +struct SWELL_CursorResourceIndex *SWELL_curmodule_cursorresource_head; +struct SWELL_DialogResourceIndex *SWELL_curmodule_dialogresource_head; +struct SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; + +// define the functions + +static struct +{ + const char *name; + void **func; +} api_tab[]={ + +#undef _WDL_SWELL_H_API_DEFINED_ +#undef SWELL_API_DEFINE +#define SWELL_API_DEFINE(ret, func, parms) {#func, (void **)&func }, + +#include "swell.h" + +}; + +static int dummyFunc() { return 0; } + +static int doinit(void *(*GetFunc)(const char *name)) +{ + int errcnt=0; + for (int x = 0; x < (int)(sizeof(api_tab)/sizeof(api_tab[0])); x ++) + { + *api_tab[x].func=GetFunc(api_tab[x].name); + if (!*api_tab[x].func) + { + printf("SWELL API not found: %s\n",api_tab[x].name); + errcnt++; + *api_tab[x].func = (void*)&dummyFunc; + } + } + return errcnt; +} + +#ifdef SWELL_LOAD_SWELL_DYLIB + +static void *(*__loaded_getfunc)(const char *name)=NULL; + +class SwellAPPInitializer +{ +public: + SwellAPPInitializer() + { + void *(*SWELLAPI_GetFunc)(const char *name)=NULL; + char fn[4096]; + const int nSize=sizeof(fn)-64; + int sz=readlink("/proc/self/exe",fn,nSize); + if (sz<1) + { + Dl_info inf={0,}; + if (dladdr(&api_tab,&inf) && inf.dli_fname) + sz = (int) strlen(inf.dli_fname); + else + sz = 0; + } + if (sz>=nSize)sz=nSize-1; + fn[sz]=0; + char *p=fn; + while (*p) p++; + while (p >= fn && *p != '/') p--; + p++; + strcpy(p,p==fn ? "./libSwell.so" : "libSwell.so"); + + void *tmp = dlopen(fn,RTLD_LAZY); + if (!tmp) + { + printf("Error loading '%s': %s\n",fn,dlerror()); + exit(2); + } + const char *preload_fn = (const char *)dlsym(tmp,"SWELL_WANT_LOAD_LIBRARY"); + if (preload_fn && *preload_fn) + dlopen(preload_fn,RTLD_LAZY|RTLD_GLOBAL); + + *(void **)&SWELLAPI_GetFunc = dlsym(tmp,"SWELLAPI_GetFunc"); + + if (SWELLAPI_GetFunc && SWELLAPI_GetFunc(NULL)==(void*)0x100) + { + void (*set_app_main)(INT_PTR (*AppMain)(int msg, INT_PTR parm1, INT_PTR parm2)); + *(void **)&set_app_main = dlsym(tmp,"SWELL_set_app_main"); + if (set_app_main) set_app_main(SWELLAppMain); + + __loaded_getfunc = SWELLAPI_GetFunc; + if (doinit(SWELLAPI_GetFunc)) exit(1); + } + } + ~SwellAPPInitializer() + { + } +}; + +SwellAPPInitializer m_swell_appAPIinit; + +extern "C" __attribute__ ((visibility ("default"))) void *SWELLAPI_GetFunc(const char *name) +{ + if (__loaded_getfunc) return __loaded_getfunc(name); + return NULL; +} + +#else + +extern "C" __attribute__ ((visibility ("default"))) int SWELL_dllMain(HINSTANCE hInst, DWORD callMode, LPVOID _GetFunc) +{ + if (callMode == DLL_PROCESS_ATTACH) + { + if (!_GetFunc) return 0; + doinit((void *(*)(const char *)) _GetFunc); + } + + // this returning 1 allows DllMain to be called, if available + return 1; +} + +#endif + + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-modstub.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-modstub.mm new file mode 100644 index 0000000..2a7a5b5 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-modstub.mm @@ -0,0 +1,95 @@ +/* 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. +*/ + +#ifdef SWELL_PROVIDED_BY_APP + +#import +#import +#define SWELL_API_DEFPARM(x) +#define SWELL_API_DEFINE(ret,func,parms) ret (*func) parms ; +#include "swell.h" + +// only include this file in projects that are linked to swell.dylib + +struct SWELL_DialogResourceIndex *SWELL_curmodule_dialogresource_head; +struct SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; + +// define the functions + +static struct +{ + const char *name; + void **func; +} api_tab[]={ + +#undef _WDL_SWELL_H_API_DEFINED_ +#undef SWELL_API_DEFINE +#define SWELL_API_DEFINE(ret, func, parms) {#func, (void **)&func }, + +#include "swell-functions.h" + +}; + +static int dummyFunc() { return 0; } + +class SwellAPPInitializer +{ +public: + SwellAPPInitializer() + { + void *(*SWELLAPI_GetFunc)(const char *name)=NULL; + void *(*send_msg)(id, SEL) = (void *(*)(id, SEL))objc_msgSend; + + id del = [NSApp delegate]; + if (del && [del respondsToSelector:@selector(swellGetAPPAPIFunc)]) + *(void **)&SWELLAPI_GetFunc = send_msg(del,@selector(swellGetAPPAPIFunc)); + + if (!SWELLAPI_GetFunc) NSLog(@"SWELL API provider not found\n"); + else if (SWELLAPI_GetFunc(NULL)!=(void*)0x100) + { + NSLog(@"SWELL API provider returned incorrect version\n"); + SWELLAPI_GetFunc=0; + } + + int x; + for (x = 0; x < sizeof(api_tab)/sizeof(api_tab[0]); x ++) + { + *api_tab[x].func=SWELLAPI_GetFunc?SWELLAPI_GetFunc(api_tab[x].name):0; + if (!*api_tab[x].func) + { + if (SWELLAPI_GetFunc) NSLog(@"SWELL API not found: %s\n",api_tab[x].name); + *api_tab[x].func = (void*)&dummyFunc; + } + } + } + ~SwellAPPInitializer() + { + } +}; + +SwellAPPInitializer m_swell_appAPIinit; + +extern "C" __attribute__ ((visibility ("default"))) int SWELL_dllMain(HINSTANCE hInst, DWORD callMode, LPVOID _GetFunc) +{ + // this returning 1 allows DllMain to be called, if available + return 1; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-types.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-types.h new file mode 100644 index 0000000..b33eb67 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/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/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-win32.h b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-win32.h new file mode 100644 index 0000000..222b813 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-win32.h @@ -0,0 +1,58 @@ +#ifndef _SWELL_WIN32_H_ +#define _SWELL_WIN32_H_ + +/************* +** helpers to give swell-like functionality on win32 +*/ + +#ifdef _WIN32 +void SWELL_DisableContextMenu(HWND h, bool dis); + + + +#ifndef SWELL_WIN32_DECLARE_ONLY + +#include +#include "../wdltypes.h" + +#define SWELL_DISABLE_CTX_OLDPROC_NAME "SWELLDisableCtxOldProc" + +static LRESULT WINAPI swellDisableCtxNewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HANDLE oldProc = uMsg != WM_CONTEXTMENU ? GetProp(hwnd,SWELL_DISABLE_CTX_OLDPROC_NAME) : NULL; + if (uMsg == WM_DESTROY) + { + RemoveProp(hwnd,SWELL_DISABLE_CTX_OLDPROC_NAME); + if (oldProc) SetWindowLongPtr(hwnd,GWLP_WNDPROC,(LPARAM)oldProc); + } + return oldProc ? CallWindowProc((WNDPROC)oldProc,hwnd,uMsg,wParam,lParam) : DefWindowProc(hwnd,uMsg,wParam,lParam); +} + +void SWELL_DisableContextMenu(HWND h, bool dis) +{ + char classname[512]; + if (WDL_NOT_NORMALLY(!h)) return; + if (GetClassName(h,classname,sizeof(classname)) && !strcmp(classname,"ComboBox")) + { + HWND h2 = FindWindowEx(h,NULL,"Edit",NULL); + if (h2) h = h2; + } + + if (dis) + { + if (!GetProp(h,SWELL_DISABLE_CTX_OLDPROC_NAME)) + SetProp(h,SWELL_DISABLE_CTX_OLDPROC_NAME,(HANDLE)SetWindowLongPtr(h,GWLP_WNDPROC,(LPARAM)swellDisableCtxNewWndProc)); + } + else + { + LPARAM op = (LPARAM)GetProp(h,SWELL_DISABLE_CTX_OLDPROC_NAME); + if (op) SetWindowLongPtr(h,GWLP_WNDPROC,op); + RemoveProp(h,SWELL_DISABLE_CTX_OLDPROC_NAME); + } +} + +#endif // !SWELL_WIN32_DECLARE_ONLY + +#endif // _WIN32 + +#endif // _SWELL_WIN32_H_ diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-wnd-generic.cpp b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-wnd-generic.cpp new file mode 100644 index 0000000..56eec2a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-wnd-generic.cpp @@ -0,0 +1,8716 @@ +/* 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. + + + */ + + +#ifndef SWELL_PROVIDED_BY_APP + +#ifndef WDL_NO_DEFINE_MINMAX +#define WDL_NO_DEFINE_MINMAX +#endif +#include "swell.h" + +#define SWELL_INTERNAL_MERGESORT_IMPL +#define SWELL_INTERNAL_HTREEITEM_IMPL +#include "swell-internal.h" + +#include +#include "../mutex.h" +#include "../ptrlist.h" +#include "../assocarray.h" +#include "../queue.h" +#include "../wdlcstring.h" +#include "../wdlutf8.h" + +#include "swell-dlggen.h" + +#define EDIT_CURSOR_BLINK_LEN 500 +#define EDIT_CURSOR_CYCLE_INTERVAL 3 + +bool swell_is_likely_capslock; // only used when processing dit events for a-zA-Z +SWELL_OSWINDOW SWELL_focused_oswindow; // top level window which has focus (might not map to a HWND__!) +HWND swell_captured_window; + +#define STATEIMAGEMASKTOINDEX(x) (((x)>>16)&0xff) + +static bool swell_is_virtkey_char(int c) +{ + return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); +} + +HWND__ *SWELL_topwindows; +HWND swell_oswindow_to_hwnd(SWELL_OSWINDOW w) +{ + if (!w) return NULL; + HWND a = SWELL_topwindows; + while (a && a->m_oswindow != w) a=a->m_next; + return a; +} + +SWELL_OSWINDOW swell_oswindow_from_hwnd(HWND hwnd) +{ + while (hwnd && !hwnd->m_oswindow) + hwnd = hwnd->m_parent; + return hwnd ? hwnd->m_oswindow : NULL; +} + +HWND SWELL_GetFocusedChild(HWND h); + +void swell_on_toplevel_raise(SWELL_OSWINDOW wnd) // called by swell-generic-gdk when a window is focused +{ + HWND hwnd = swell_oswindow_to_hwnd(wnd); + if (SWELL_topwindows && hwnd != SWELL_topwindows) + { + HWND c = SWELL_GetFocusedChild(SWELL_topwindows); + if (c) SendMessage(c,WM_KILLFOCUS,0,0); + } + if (hwnd && hwnd != SWELL_topwindows) + { + // implies hwnd->m_prev + + VALIDATE_HWND_LIST(SWELL_topwindows,NULL); + + // remove from list + hwnd->m_prev->m_next = hwnd->m_next; + if (hwnd->m_next) hwnd->m_next->m_prev = hwnd->m_prev; + + // insert at front of list + hwnd->m_prev = NULL; + hwnd->m_next = SWELL_topwindows; + if (SWELL_topwindows) SWELL_topwindows->m_prev = hwnd; + SWELL_topwindows = hwnd; + VALIDATE_HWND_LIST(SWELL_topwindows,NULL); + } + if (hwnd) + { + HWND c = SWELL_GetFocusedChild(hwnd); + if (c) SendMessage(c,WM_SETFOCUS,0,0); + } +} + +HWND__::HWND__(HWND par, int wID, const RECT *wndr, const char *label, bool visible, WNDPROC wndproc, DLGPROC dlgproc, HWND ownerWindow) +{ + m_refcnt=1; + m_private_data=0; + m_israised=false; + m_has_had_position=false; + m_is_maximized = false; + m_oswindow_private=0; + m_oswindow_fullscreen=0; + + m_classname = "unknown"; + m_wndproc=wndproc?wndproc:dlgproc?(WNDPROC)SwellDialogDefaultWindowProc:(WNDPROC)DefWindowProc; + m_dlgproc=dlgproc; + m_userdata=0; + m_style=0; + m_exstyle=0; + m_id=wID; + m_owned_list=m_owner=m_owned_next=m_owned_prev=NULL; + m_children=m_parent=m_next=m_prev=NULL; + m_focused_child=NULL; + if (wndr) m_position = *wndr; + else memset(&m_position,0,sizeof(m_position)); + memset(&m_extra,0,sizeof(m_extra)); + m_visible=visible; + m_hashaddestroy=0; + m_enabled=true; + m_wantfocus=true; + m_menu=NULL; + m_font=NULL; + m_oswindow = NULL; + +#ifdef SWELL_LICE_GDI + m_paintctx=0; + m_invalidated=true; + m_child_invalidated=true; + m_backingstore=0; +#endif + + if (label) m_title.Set(label); + + SetParent(this, par); + if (!par && ownerWindow) + { + m_owned_next = ownerWindow->m_owned_list; + ownerWindow->m_owned_list = this; + if (m_owned_next) m_owned_next->m_owned_prev = this; + m_owner = ownerWindow; + } +} + +static HWND s_last_rbuttondown; + +HWND__::~HWND__() +{ + if (m_wndproc) + m_wndproc(this,WM_NCDESTROY,0,0); + + if (this == s_last_rbuttondown) s_last_rbuttondown = NULL; +} + + + +HWND GetParent(HWND hwnd) +{ + if (WDL_NORMALLY(hwnd)) + { + return hwnd->m_parent ? hwnd->m_parent : hwnd->m_owner; + } + return NULL; +} + +HWND GetDlgItem(HWND hwnd, int idx) +{ + if (!idx) return hwnd; + WDL_ASSERT(hwnd != NULL); + if (hwnd) hwnd=hwnd->m_children; + while (hwnd && hwnd->m_id != (UINT)idx) hwnd=hwnd->m_next; + return hwnd; +} + + +LONG_PTR SetWindowLong(HWND hwnd, int idx, LONG_PTR val) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + if (idx==GWL_STYLE) + { + LONG ret = hwnd->m_style; + hwnd->m_style=val & ~WS_VISIBLE; + swell_oswindow_update_style(hwnd,ret); + return ret & ~WS_VISIBLE; + } + if (idx==GWL_EXSTYLE) + { + LONG ret = hwnd->m_exstyle; + hwnd->m_exstyle=val; + return ret; + } + if (idx==GWL_USERDATA) + { + LONG_PTR ret = hwnd->m_userdata; + hwnd->m_userdata=val; + return ret; + } + if (idx==GWL_ID) + { + LONG ret = hwnd->m_id; + hwnd->m_id=val; + return ret; + } + + if (idx==GWL_WNDPROC) + { + LONG_PTR ret = (LONG_PTR)hwnd->m_wndproc; + hwnd->m_wndproc=(WNDPROC)val; + return ret; + } + if (idx==DWL_DLGPROC) + { + LONG_PTR ret = (LONG_PTR)hwnd->m_dlgproc; + hwnd->m_dlgproc=(DLGPROC)val; + return ret; + } + if (idx == GWL_HWNDPARENT) + { + const LONG_PTR ret = (LONG_PTR)hwnd->m_owner; + if (val != ret) + { + if (hwnd->m_owned_next) hwnd->m_owned_next->m_owned_prev = hwnd->m_owned_prev; + if (hwnd->m_owned_prev) hwnd->m_owned_prev->m_owned_next = hwnd->m_owned_next; + if (hwnd->m_owner && hwnd->m_owner->m_owned_list == hwnd) hwnd->m_owner->m_owned_list = hwnd->m_owned_next; + hwnd->m_owned_next = hwnd->m_owned_prev = hwnd->m_owner = NULL; + HWND ownerWindow = (HWND) val; + if (ownerWindow) + { + hwnd->m_owned_next = ownerWindow->m_owned_list; + ownerWindow->m_owned_list = hwnd; + if (hwnd->m_owned_next) hwnd->m_owned_next->m_owned_prev = hwnd; + hwnd->m_owner = ownerWindow; + hwnd->m_israised=true; + } + } + return ret; + } + + if (idx>=0 && idx < 64*(int)sizeof(INT_PTR)) + { + INT_PTR ret = hwnd->m_extra[idx/sizeof(INT_PTR)]; + hwnd->m_extra[idx/sizeof(INT_PTR)]=val; + return (LONG_PTR)ret; + } + return 0; +} + +LONG_PTR GetWindowLong(HWND hwnd, int idx) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + if (idx==GWL_STYLE) + { + LONG_PTR ret = hwnd->m_style; + if (hwnd->m_visible) ret|=WS_VISIBLE; + else ret &= ~WS_VISIBLE; + return ret; + } + if (idx==GWL_EXSTYLE) + { + return hwnd->m_exstyle; + } + if (idx==GWL_USERDATA) + { + return hwnd->m_userdata; + } + if (idx==GWL_ID) + { + return hwnd->m_id; + } + + if (idx==GWL_WNDPROC) + { + return (LONG_PTR)hwnd->m_wndproc; + } + if (idx==DWL_DLGPROC) + { + return (LONG_PTR)hwnd->m_dlgproc; + } + if (idx == GWL_HWNDPARENT) + { + return (LONG_PTR)hwnd->m_owner; + } + + if (idx>=0 && idx < 64*(int)sizeof(INT_PTR)) + { + return (LONG_PTR)hwnd->m_extra[idx/sizeof(INT_PTR)]; + } + return 0; +} + + +static bool __isWindow(HWND hc, HWND hFind) +{ + while (hc) + { + if (hc == hFind || (hc->m_children && __isWindow(hc->m_children,hFind))) return true; + hc = hc->m_next; + } + return false; +} + +bool IsWindow(HWND hwnd) +{ + if (!hwnd) return false; + HWND h = SWELL_topwindows; + while (h) + { + if (hwnd == h || (h->m_children && __isWindow(h->m_children,hwnd))) return true; + h=h->m_next; + } + + return false; +} + +bool IsWindowVisible(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return false; + while (hwnd->m_visible) + { + hwnd = hwnd->m_parent; + if (!hwnd) return true; + } + return false; +} + +bool IsModalDialogBox(HWND hwnd); + +LRESULT SendMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + WNDPROC wp = hwnd->m_wndproc; + + if (msg == WM_DESTROY) + { + if (hwnd->m_hashaddestroy) return 0; + hwnd->m_hashaddestroy=1; + + if (GetCapture()==hwnd) ReleaseCapture(); + SWELL_MessageQueue_Clear(hwnd); + } + else if (hwnd->m_hashaddestroy == 2) return 0; + else if (msg==WM_CAPTURECHANGED && hwnd->m_hashaddestroy) return 0; + + hwnd->Retain(); + + LRESULT ret = wp ? wp(hwnd,msg,wParam,lParam) : 0; + + if (msg == WM_DESTROY) + { + if (GetCapture()==hwnd) ReleaseCapture(); + + SWELL_MessageQueue_Clear(hwnd); + // send WM_DESTROY to all children + HWND tmp=hwnd->m_children; + while (tmp) + { + HWND old = tmp; + tmp=tmp->m_next; + SendMessage(old,WM_DESTROY,0,0); + } + { + tmp=hwnd->m_owned_list; + while (tmp) + { + HWND old = tmp; + tmp=tmp->m_owned_next; + if (!IsModalDialogBox(old)) SendMessage(old,WM_DESTROY,0,0); + } + } + if (SWELL_focused_oswindow && SWELL_focused_oswindow == hwnd->m_oswindow) + { + HWND h = hwnd->m_owner; + while (h && !h->m_oswindow) h = h->m_parent ? h->m_parent : h->m_owner; + swell_oswindow_focus(h && h->m_oswindow ? h : NULL); + } + hwnd->m_wndproc=NULL; + hwnd->m_hashaddestroy=2; + KillTimer(hwnd,-1); + } + hwnd->Release(); + return ret; +} + +static void swell_removeWindowFromParentOrTop(HWND__ *hwnd, bool removeFromOwner) +{ + HWND par = hwnd->m_parent; + if (hwnd->m_next) hwnd->m_next->m_prev = hwnd->m_prev; + if (hwnd->m_prev) hwnd->m_prev->m_next = hwnd->m_next; + if (par) + { + if (par->m_focused_child == hwnd) par->m_focused_child=NULL; + if (par->m_children == hwnd) par->m_children = hwnd->m_next; + } + if (hwnd == SWELL_topwindows) + { + SWELL_topwindows = hwnd->m_next; + VALIDATE_HWND_LIST(SWELL_topwindows,NULL); + } + hwnd->m_next = hwnd->m_prev = hwnd->m_parent = NULL; + if (par) VALIDATE_HWND_LIST(par->m_children,par); + + if (removeFromOwner) + { + if (hwnd->m_owned_next) hwnd->m_owned_next->m_owned_prev = hwnd->m_owned_prev; + if (hwnd->m_owned_prev) hwnd->m_owned_prev->m_owned_next = hwnd->m_owned_next; + if (hwnd->m_owner && hwnd->m_owner->m_owned_list == hwnd) hwnd->m_owner->m_owned_list = hwnd->m_owned_next; + hwnd->m_owned_next = hwnd->m_owned_prev = hwnd->m_owner = NULL; + } + + if (par && !par->m_hashaddestroy) InvalidateRect(par,NULL,FALSE); +} + +void RecurseDestroyWindow(HWND hwnd) +{ + HWND tmp=hwnd->m_children; + hwnd->m_children=NULL; + + while (tmp) + { + HWND old = tmp; + tmp=tmp->m_next; + if (tmp) tmp->m_prev = NULL; + + old->m_prev = old->m_next = NULL; + RecurseDestroyWindow(old); + } + tmp=hwnd->m_owned_list; + hwnd->m_owned_list = NULL; + + while (tmp) + { + HWND old = tmp; + tmp=tmp->m_owned_next; + if (tmp) tmp->m_owned_prev = NULL; + + old->m_owned_prev = old->m_owned_next = NULL; + old->m_owner = NULL; + if (old->m_hashaddestroy) RecurseDestroyWindow(old); + } + + if (swell_captured_window == hwnd) swell_captured_window=NULL; + + swell_oswindow_destroy(hwnd); + + if (hwnd->m_menu) DestroyMenu(hwnd->m_menu); + hwnd->m_menu=0; + +#ifdef SWELL_LICE_GDI + delete hwnd->m_backingstore; + hwnd->m_backingstore=0; +#endif + + // remove from parent/global lists + swell_removeWindowFromParentOrTop(hwnd, true); + + SWELL_MessageQueue_Clear(hwnd); + KillTimer(hwnd,-1); + hwnd->Release(); +} + + +void DestroyWindow(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + if (hwnd->m_hashaddestroy) return; + + // broadcast WM_DESTROY + SendMessage(hwnd,WM_DESTROY,0,0); + + // safe to delete this window and all children directly + RecurseDestroyWindow(hwnd); + +} + + +bool IsWindowEnabled(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return false; + while (hwnd && hwnd->m_enabled) + { + hwnd=hwnd->m_parent; + } + return !hwnd; +} + +void EnableWindow(HWND hwnd, int enable) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + if (!!hwnd->m_enabled == !!enable) return; + + hwnd->m_enabled=!!enable; + swell_oswindow_update_enable(hwnd); + + if (!enable) + { + if (hwnd->m_parent && hwnd->m_parent->m_focused_child == hwnd) + hwnd->m_parent->m_focused_child = NULL; + } + InvalidateRect(hwnd,NULL,FALSE); +} + +void SetForegroundWindow(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + + HWND oldFoc = GetFocus(); + // if a child window has focus, preserve that focus + while (hwnd->m_parent && !hwnd->m_oswindow) + { + hwnd->m_parent->m_focused_child = hwnd; + hwnd = hwnd->m_parent; + } + if (hwnd) swell_oswindow_focus(hwnd); + HWND foc = GetFocus(); + if (foc && foc != oldFoc) + SendMessage(foc,WM_SETFOCUS,(WPARAM)oldFoc,0); +} + +void SetFocus(HWND hwnd) +{ + if (!hwnd) return; // windows allows SetFocus(NULL) to defocus, but we do not support that yet + HWND oldFoc = GetFocus(); + if (oldFoc && oldFoc != hwnd) + SendMessage(oldFoc,WM_KILLFOCUS,(WPARAM)hwnd,0); + hwnd->m_focused_child=NULL; // make sure this window has focus, not a child + + HWND p = hwnd; + while (p->m_parent && !p->m_oswindow) + { + p->m_parent->m_focused_child = p; + p = p->m_parent; + } + if (p) swell_oswindow_focus(p); + + if (hwnd != oldFoc) + SendMessage(hwnd,WM_SETFOCUS,(WPARAM)oldFoc,0); +} + +void SWELL_OnNavigationFocus(HWND ch) +{ + if (ch && ch->m_classname && ( + (!strcmp(ch->m_classname,"Edit") && !(ch->m_style & ES_READONLY)) || + !strcmp(ch->m_classname,"combobox") + )) + { + SendMessage(ch,EM_SETSEL,0,-1); + } + +} + +int IsChild(HWND hwndParent, HWND hwndChild) +{ + if (!hwndParent || !hwndChild || hwndParent == hwndChild) return 0; + + while (hwndChild && hwndChild != hwndParent) hwndChild = hwndChild->m_parent; + + return hwndChild == hwndParent; +} + +HWND SWELL_GetFocusedChild(HWND h) +{ + while (h) + { + HWND fc = h->m_focused_child; + if (!fc) break; + HWND s = h->m_children; + while (s && s != fc) s = s->m_next; + if (!s) break; + h = s; // descend to focused child + } + return h; +} + +HWND GetFocusIncludeMenus() +{ + HWND h = swell_is_app_inactive()>0 ? NULL : swell_oswindow_to_hwnd(SWELL_focused_oswindow); + return SWELL_GetFocusedChild(h); +} + +HWND GetForegroundWindow() +{ + return GetFocus(); +} + +HWND GetFocus() +{ + HWND h =GetFocusIncludeMenus(); + HWND ho; + while (h && (ho=(HWND)GetProp(h,"SWELL_MenuOwner"))) h=ho; + return h; +} + +void ScreenToClient(HWND hwnd, POINT *pt) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + + HWND tmp=hwnd; + while (tmp) + { + NCCALCSIZE_PARAMS p = {{ tmp->m_position, }, }; + if (tmp->m_wndproc) tmp->m_wndproc(tmp,WM_NCCALCSIZE,0,(LPARAM)&p); + + pt->x -= p.rgrc[0].left; + pt->y -= p.rgrc[0].top; + tmp = tmp->m_parent; + } +} + +void ClientToScreen(HWND hwnd, POINT *pt) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + + HWND tmp=hwnd; + while (tmp) + { + NCCALCSIZE_PARAMS p={{tmp->m_position, }, }; + if (tmp->m_wndproc) tmp->m_wndproc(tmp,WM_NCCALCSIZE,0,(LPARAM)&p); + pt->x += p.rgrc[0].left; + pt->y += p.rgrc[0].top; + tmp = tmp->m_parent; + } +} + +void GetWindowContentViewRect(HWND hwnd, RECT *r) +{ + if (WDL_NORMALLY(hwnd) && hwnd->m_oswindow) + { + *r = hwnd->m_position; + return; + } + GetWindowRect(hwnd,r); +} + +void GetClientRect(HWND hwnd, RECT *r) +{ + r->left=r->top=r->right=r->bottom=0; + if (WDL_NOT_NORMALLY(!hwnd)) return; + + r->right = hwnd->m_position.right - hwnd->m_position.left; + r->bottom = hwnd->m_position.bottom - hwnd->m_position.top; + + NCCALCSIZE_PARAMS tr={{*r, },}; + SendMessage(hwnd,WM_NCCALCSIZE,FALSE,(LPARAM)&tr); + r->right = r->left + (tr.rgrc[0].right-tr.rgrc[0].left); + r->bottom=r->top + (tr.rgrc[0].bottom-tr.rgrc[0].top); +} + + + +void SetWindowPos(HWND hwnd, HWND zorder, int x, int y, int cx, int cy, int flags) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + // todo: handle SWP_SHOWWINDOW + RECT f = hwnd->m_position; + int reposflag = 0; + if (!(flags&SWP_NOZORDER)) + { + if (hwnd->m_parent && zorder != hwnd) + { + HWND par = hwnd->m_parent; + HWND tmp = par->m_children; + while (tmp && tmp != hwnd) tmp=tmp->m_next; + if (tmp) // we are in the list, so we can do a reorder + { + // take hwnd out of list + if (hwnd->m_prev) hwnd->m_prev->m_next = hwnd->m_next; + else par->m_children = hwnd->m_next; + if (hwnd->m_next) hwnd->m_next->m_prev = hwnd->m_prev; + hwnd->m_next=hwnd->m_prev=NULL;// leave hwnd->m_parent valid since it wont change + + // add back in + tmp = par->m_children; + if (zorder == HWND_BOTTOM || !tmp) // insert at front of list + { + if (tmp) tmp->m_prev=hwnd; + hwnd->m_next = tmp; + par->m_children = hwnd; + } + else + { + // zorder could be HWND_TOP here + while (tmp && tmp != zorder && tmp->m_next) tmp=tmp->m_next; + + // tmp is either zorder or the last item in the list + hwnd->m_next = tmp->m_next; + tmp->m_next = hwnd; + if (hwnd->m_next) hwnd->m_next->m_prev = hwnd; + hwnd->m_prev = tmp; + } + reposflag|=4; + } + } + } + if (!(flags&SWP_NOMOVE)) + { + int oldw = f.right-f.left; + int oldh = f.bottom-f.top; + f.left=x; + f.right=x+oldw; + f.top=y; + f.bottom=y+oldh; + reposflag|=1; + hwnd->m_has_had_position=true; + } + if (!(flags&SWP_NOSIZE)) + { + f.right = f.left + cx; + f.bottom = f.top + cy; + reposflag|=2; + } + if (reposflag) + { + if (hwnd->m_oswindow && (reposflag&2)) + { + swell_oswindow_begin_resize(hwnd->m_oswindow); + } + + if (reposflag&3) + { + hwnd->m_position = f; + } + + if (hwnd->m_oswindow && !hwnd->m_oswindow_fullscreen) + { + swell_oswindow_resize(hwnd->m_oswindow,reposflag,f); + if (reposflag&2) SendMessage(hwnd,WM_SIZE,SIZE_RESTORED,0); + } + else + { + if (reposflag&2) SendMessage(hwnd,WM_SIZE,SIZE_RESTORED,0); + InvalidateRect(hwnd->m_parent ? hwnd->m_parent : hwnd,NULL,FALSE); + } + } + swell_oswindow_postresize(hwnd,f); +} + + +BOOL EnumWindows(BOOL (*proc)(HWND, LPARAM), LPARAM lp) +{ + HWND h = SWELL_topwindows; + if (!proc) return FALSE; + while (h) + { + if (!proc(h,lp)) return FALSE; + h = h->m_next; + } + return TRUE; +} + +HWND GetWindow(HWND hwnd, int what) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + + if (what == GW_CHILD) return hwnd->m_children; + if (what == GW_OWNER) return hwnd->m_owner; + if (what == GW_HWNDNEXT) return hwnd->m_next; + if (what == GW_HWNDPREV) return hwnd->m_prev; + if (what == GW_HWNDFIRST) + { + while (hwnd->m_prev) hwnd = hwnd->m_prev; + return hwnd; + } + if (what == GW_HWNDLAST) + { + while (hwnd->m_next) hwnd = hwnd->m_next; + return hwnd; + } + return 0; +} + +HWND SetParent(HWND hwnd, HWND newPar) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return NULL; + + HWND oldPar = hwnd->m_parent; + + swell_removeWindowFromParentOrTop(hwnd, newPar != NULL && newPar != oldPar); + + if (newPar) + { + HWND fc = newPar->m_children; + if (!fc) + { + newPar->m_children = hwnd; + } + else + { + while (fc->m_next) fc = fc->m_next; + hwnd->m_prev = fc; + fc->m_next = hwnd; + } + hwnd->m_parent = newPar; + hwnd->m_style |= WS_CHILD; + + if (newPar) VALIDATE_HWND_LIST(newPar->m_children,newPar); + } + else // add to top level windows + { + hwnd->m_next=SWELL_topwindows; + if (hwnd->m_next) hwnd->m_next->m_prev = hwnd; + SWELL_topwindows = hwnd; + VALIDATE_HWND_LIST(SWELL_topwindows,NULL); + hwnd->m_style &= ~WS_CHILD; + } + + swell_oswindow_manage(hwnd,false); + return oldPar; +} + + + + +// timer stuff +typedef struct TimerInfoRec +{ + UINT_PTR timerid; + HWND hwnd; + UINT interval; + + DWORD lastFire; + int refcnt; // 0 normally, 1 if currently processing this timer + + TIMERPROC tProc; + struct TimerInfoRec *_next; +} TimerInfoRec; + +static TimerInfoRec *m_timer_list; +static WDL_Mutex m_timermutex; + +static TimerInfoRec *spare_timers; +static void free_timer(TimerInfoRec *rec) +{ + int c = 3; // max spares + TimerInfoRec *p = spare_timers; + while (p) + { + if (--c <= 0) + { + free(rec); + return; + } + p = p->_next; + } + rec->_next=spare_timers; + spare_timers=rec; +} + + +void SWELL_RunMessageLoop() +{ + SWELL_MessageQueue_Flush(); + SWELL_RunEvents(); + + const DWORD now = GetTickCount(); + WDL_MutexLock lock(&m_timermutex); + TimerInfoRec *rec = m_timer_list; + while (rec) + { + // rec->lastFire might be after now, if the timer was set from another timer + // we should run this timer if rec->lastFire is in the interval + // [now-rec->interval-some big range,now-rec->interval] + if (WDL_TICKS_IN_RANGE_ENDING_AT(rec->lastFire, now - rec->interval, 100000)) + { + rec->lastFire = GetTickCount(); + ++rec->refcnt; + + HWND h = rec->hwnd; + TIMERPROC tProc = rec->tProc; + UINT_PTR tid = rec->timerid; + m_timermutex.Leave(); + + if (tProc) tProc(h,WM_TIMER,tid,now); + else if (h) SendMessage(h,WM_TIMER,tid,0); + + m_timermutex.Enter(); + + if (--rec->refcnt < 0) + { + free_timer(rec); + rec = m_timer_list; + continue; + } + } + rec=rec->_next; + } +} + +UINT_PTR SetTimer(HWND hwnd, UINT_PTR timerid, UINT rate, TIMERPROC tProc) +{ + if (WDL_NOT_NORMALLY(!hwnd && !tProc)) return 0; // must have either callback or hwnd + + if (WDL_NOT_NORMALLY(hwnd && !timerid)) return 0; + + if (hwnd && hwnd->m_hashaddestroy) return 0; + + WDL_MutexLock lock(&m_timermutex); + TimerInfoRec *rec=NULL; + if (hwnd||timerid) + { + rec = m_timer_list; + while (rec) + { + if (rec->timerid == timerid && rec->hwnd == hwnd) // works for both kinds + break; + rec=rec->_next; + } + } + + bool recAdd=false; + if (!rec) + { + rec = spare_timers; + if (!rec) rec = (TimerInfoRec *)malloc(sizeof(TimerInfoRec)); + else spare_timers = rec->_next; + rec->refcnt = 0; + recAdd=true; + } + + if (!hwnd) timerid = (UINT_PTR)rec; + + rec->tProc = tProc; + rec->timerid = timerid; + rec->hwnd = hwnd; + rec->interval = rate<1?1: rate; + rec->lastFire = GetTickCount(); + + if (recAdd) + { + rec->_next=m_timer_list; + m_timer_list=rec; + } + + return timerid; +} + +BOOL KillTimer(HWND hwnd, UINT_PTR timerid) +{ + if (WDL_NOT_NORMALLY(!hwnd && !timerid)) return FALSE; + + WDL_MutexLock lock(&m_timermutex); + BOOL rv=FALSE; + + // don't allow removing all global timers + if (timerid!=(UINT_PTR)-1 || hwnd) + { + TimerInfoRec *rec = m_timer_list, *lrec=NULL; + while (rec) + { + if (rec->hwnd == hwnd && (timerid==(UINT_PTR)-1 || rec->timerid == timerid)) + { + TimerInfoRec *nrec = rec->_next; + + // remove self from list + if (lrec) lrec->_next = nrec; + else m_timer_list = nrec; + + if (--rec->refcnt < 0) + free_timer(rec); + + rv=TRUE; + if (timerid!=(UINT_PTR)-1) break; + + rec=nrec; + } + else + { + lrec=rec; + rec=rec->_next; + } + } + } + return rv; +} + +BOOL SetDlgItemText(HWND hwnd, int idx, const char *text) +{ + hwnd =(idx ? GetDlgItem(hwnd,idx) : hwnd); + if (WDL_NOT_NORMALLY(!hwnd)) return false; + + if (!text) text=""; + + + if (strcmp(hwnd->m_title.Get(), text)) + { + hwnd->m_title.Set(text); + swell_oswindow_update_text(hwnd); + } + SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)text); + return true; +} + +int GetWindowTextLength(HWND hwnd) +{ + return WDL_NORMALLY(hwnd) ? hwnd->m_title.GetLength() : 0; +} + +BOOL GetDlgItemText(HWND hwnd, int idx, char *text, int textlen) +{ + *text=0; + hwnd = idx?GetDlgItem(hwnd,idx) : hwnd; + if (WDL_NOT_NORMALLY(!hwnd)) return false; + + // todo: sendmessage WM_GETTEXT etc? special casing for combo boxes etc + lstrcpyn_safe(text,hwnd->m_title.Get(), textlen); + return true; +} + +void CheckDlgButton(HWND hwnd, int idx, int check) +{ + hwnd = GetDlgItem(hwnd,idx); + if (WDL_NOT_NORMALLY(!hwnd)) return; + SendMessage(hwnd,BM_SETCHECK,check,0); +} + + +int IsDlgButtonChecked(HWND hwnd, int idx) +{ + hwnd = GetDlgItem(hwnd,idx); + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + return SendMessage(hwnd,BM_GETCHECK,0,0); +} + + +BOOL SetDlgItemInt(HWND hwnd, int idx, int val, int issigned) +{ + char buf[128]; + sprintf(buf,issigned?"%d":"%u",val); + return SetDlgItemText(hwnd,idx,buf); +} + +int GetDlgItemInt(HWND hwnd, int idx, BOOL *translated, int issigned) +{ + char buf[128]; + if (!GetDlgItemText(hwnd,idx,buf,sizeof(buf))) + { + if (translated) *translated=0; + return 0; + } + char *p=buf; + while (*p == ' ' || *p == '\t') p++; + int a=atoi(p); + if ((a<0 && !issigned) || (!a && p[0] != '0')) { if (translated) *translated=0; return 0; } + if (translated) *translated=1; + return a; +} + +void ShowWindow(HWND hwnd, int cmd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + + if (cmd==SW_SHOW||cmd==SW_SHOWNA) + { + if (hwnd->m_visible) cmd = SW_SHOWNA; // do not take focus if already visible + hwnd->m_visible=true; + } + else if (cmd == SW_RESTORE || cmd == SW_SHOWMAXIMIZED) + { + hwnd->m_visible=true; + } + else if (cmd==SW_HIDE) + { + if (hwnd->m_visible) + { + hwnd->m_visible=false; + if (hwnd->m_parent) + InvalidateRect(hwnd->m_parent,&hwnd->m_position,FALSE); + } + } + + swell_oswindow_manage(hwnd,cmd==SW_SHOW); + + if (cmd == SW_SHOW) + { + SetForegroundWindow(hwnd); + } + else if (cmd == SW_RESTORE || cmd == SW_SHOWMAXIMIZED) + swell_oswindow_maximize(hwnd,cmd == SW_SHOWMAXIMIZED); + + InvalidateRect(hwnd,NULL,FALSE); + +} + +void *SWELL_ModalWindowStart(HWND hwnd) +{ + return 0; +} + +bool SWELL_ModalWindowRun(void *ctx, int *ret) // returns false and puts retval in *ret when done +{ + return false; +} + +void SWELL_ModalWindowEnd(void *ctx) +{ + if (ctx) + { + } +} + +void SWELL_CloseWindow(HWND hwnd) +{ + DestroyWindow(hwnd); +} + + +static void Draw3DBox(HDC hdc, const RECT *r, int bgc, int topc, int botc, bool swap=false) +{ + RECT tr = *r; + tr.right--; + tr.bottom--; + if (bgc != -1) + { + tr.left++; + tr.top++; + HBRUSH br = CreateSolidBrush(bgc); + FillRect(hdc,&tr,br); + DeleteObject(br); + tr.left--; + tr.top--; + } + + if (topc == -1 && botc == -1) return; + + HPEN pen = CreatePen(PS_SOLID,0,swap?botc:topc); + HPEN pen2 = CreatePen(PS_SOLID,0,swap?topc:botc); + HGDIOBJ oldpen = SelectObject(hdc,pen); + MoveToEx(hdc,tr.left,tr.bottom,NULL); + LineTo(hdc,tr.left,tr.top); + LineTo(hdc,tr.right,tr.top); + + SelectObject(hdc,pen2); + LineTo(hdc,tr.right,tr.bottom); + LineTo(hdc,tr.left,tr.bottom); + + SelectObject(hdc,oldpen); + DeleteObject(pen); + DeleteObject(pen2); +} + + +#include "swell-dlggen.h" + +static HWND m_make_owner; +static RECT m_transform; +static bool m_doautoright; +static RECT m_lastdoauto; +static bool m_sizetofits; + +#define ACTIONTARGET (m_make_owner) + +void SWELL_MakeSetCurParms(float xscale, float yscale, float xtrans, float ytrans, HWND parent, bool doauto, bool dosizetofit) +{ + if (g_swell_ui_scale != 256 && xscale != 1.0f && yscale != 1.0f) + { + const float m = g_swell_ui_scale/256.0f; + xscale *= m; + yscale *= m; + } + m_sizetofits=dosizetofit; + m_lastdoauto.left = 0; + m_lastdoauto.top = -6553600; + m_lastdoauto.right = 0; + m_doautoright=doauto; + m_transform.left=(int)(xtrans*65536.0); + m_transform.top=(int)(ytrans*65536.0); + m_transform.right=(int)(xscale*65536.0); + m_transform.bottom=(int)(yscale*65536.0); + m_make_owner=parent; +} + +static void UpdateAutoCoords(RECT r) +{ + m_lastdoauto.right=r.left + r.right - m_lastdoauto.left; +} + + +static RECT MakeCoords(int x, int y, int w, int h, bool wantauto) +{ + if (w<0&&h<0) + { + RECT r = { -x, -y, -x-w, -y-h}; + return r; + } + + float ysc=m_transform.bottom/65536.0; + int newx=(int)((x+m_transform.left/65536.0)*m_transform.right/65536.0 + 0.5); + int newy=(int)(((((double)y+(double)m_transform.top/65536.0) )*ysc) + 0.5); + + RECT ret= { newx, + newy, + (int) (newx + w*(double)m_transform.right/65536.0+0.5), + (int) (newy + h*fabs(ysc)+0.5) + }; + + + RECT oret=ret; + if (wantauto && m_doautoright) + { + float dx = ret.left - m_lastdoauto.left; + if (fabs(dx)<32 && m_lastdoauto.top >= ret.top && m_lastdoauto.top < ret.bottom) + { + ret.left += (int) m_lastdoauto.right; + } + + m_lastdoauto.left = oret.right; + m_lastdoauto.top = (ret.top + ret.bottom)*0.5; + m_lastdoauto.right=0; + } + return ret; +} + +#define TRANSFORMFONTSIZE ((m_transform.right/65536.0+1.0)*3.7) + +static void paintDialogBackground(HWND hwnd, const RECT *r, HDC hdc) +{ + HBRUSH hbrush = (HBRUSH) SendMessage(GetParent(hwnd),WM_CTLCOLORSTATIC,(WPARAM)hdc,(LPARAM)hwnd); + if (hbrush == (HBRUSH)(INT_PTR)1) return; + + if (hbrush) + { + FillRect(hdc,r,hbrush); + } + else + { + SWELL_FillDialogBackground(hdc,r,0); + } +} + +static bool fast_has_focus(HWND hwnd) +{ + if (!hwnd || !SWELL_focused_oswindow || swell_is_app_inactive()>0) return false; + HWND par; + while ((par=hwnd->m_parent)!=NULL && par->m_focused_child==hwnd) + { + if (par->m_oswindow == SWELL_focused_oswindow) return true; + hwnd=par; + } + return false; +} + +static bool draw_focus_indicator(HWND hwnd, HDC hdc, const RECT *drawr) +{ + if (!fast_has_focus(hwnd)) return false; + + RECT r,tr; + const int sz = SWELL_UI_SCALE(3); + if (drawr) r=*drawr; + else GetClientRect(hwnd,&r); + + HBRUSH br = CreateSolidBrushAlpha(g_swell_ctheme.focus_hilight,.75f); + tr=r; tr.right = tr.left+sz; FillRect(hdc,&tr,br); + tr=r; tr.left = tr.right-sz; FillRect(hdc,&tr,br); + tr=r; tr.left+=sz; tr.right-=sz; + tr.bottom = tr.top+sz; FillRect(hdc,&tr,br); + tr.bottom = r.bottom; tr.top = tr.bottom-sz; FillRect(hdc,&tr,br); + + DeleteObject(br); + return true; +} + + +struct buttonWindowState +{ + buttonWindowState() { bitmap=0; bitmap_mode=0; state=0; } + ~buttonWindowState() { /* if (bitmap) DeleteObject(bitmap); */ } + + HICON bitmap; + int bitmap_mode; + int state; +}; + +static LRESULT WINAPI buttonWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_NCDESTROY: + delete (buttonWindowState *)hwnd->m_private_data; + hwnd->m_private_data=0; + break; + case WM_LBUTTONDOWN: + SetFocus(hwnd); + SetCapture(hwnd); + SendMessage(hwnd,WM_USER+100,0,0); // invalidate + return 0; + case WM_MOUSEMOVE: + return 0; + case WM_KEYDOWN: + if (wParam == VK_SPACE) goto fakeButtonClick; + if (wParam == VK_RETURN) + { + if (!(hwnd->m_style & 0xf)) + goto fakeButtonClick; + } + break; + case WM_LBUTTONUP: + if (GetCapture()==hwnd) + { +fakeButtonClick: + buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data; + ReleaseCapture(); // WM_CAPTURECHANGED will take care of the invalidate + RECT r; + GetClientRect(hwnd,&r); + POINT p={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)}; + hwnd->Retain(); + if ((msg==WM_KEYDOWN||PtInRect(&r,p)) && hwnd->m_id && hwnd->m_parent) + { + int sf = (hwnd->m_style & 0xf); + if (sf == BS_AUTO3STATE) + { + int a = s->state&3; + if (a==0) a=1; + else if (a==1) a=2; + else a=0; + s->state = (a) | (s->state&~3); + } + else if (sf == BS_AUTOCHECKBOX) + { + s->state = (!(s->state&3)) | (s->state&~3); + } + else if (sf == BS_AUTORADIOBUTTON) + { + int x; + for (x=0;x<2;x++) + { + HWND nw = x ? hwnd->m_next : hwnd->m_prev; + while (nw) + { + if (nw->m_classname && !strcmp(nw->m_classname,"Button")) + { + if (x && (nw->m_style & WS_GROUP)) break; + + if ((nw->m_style & 0xf) == BS_AUTORADIOBUTTON) + { + buttonWindowState *nws = (buttonWindowState*)nw->m_private_data; + if (nws && (nws->state&3)) + { + nws->state &= ~3; + InvalidateRect(nw,NULL,FALSE); + } + } + + if (nw->m_style & WS_GROUP) break; + } + else + { + break; + } + + nw=x ? nw->m_next : nw->m_prev; + } + } + + s->state = 1 | (s->state&~3); + } + SendMessage(hwnd->m_parent,WM_COMMAND,MAKEWPARAM(hwnd->m_id,BN_CLICKED),(LPARAM)hwnd); + } + if (msg == WM_KEYDOWN) InvalidateRect(hwnd,NULL,FALSE); + hwnd->Release(); + } + return 0; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data; + RECT r; + GetClientRect(hwnd,&r); + + bool pressed = GetCapture()==hwnd; + + SetBkMode(ps.hdc,TRANSPARENT); + + if (hwnd->m_enabled) + SetTextColor(ps.hdc, g_swell_ctheme.button_text); + + paintDialogBackground(hwnd,&r,ps.hdc); + + if (!hwnd->m_enabled) + SetTextColor(ps.hdc, g_swell_ctheme.button_text_disabled); + + int f=DT_VCENTER|DT_SINGLELINE; + int sf = (hwnd->m_style & 0xf); + if (sf == BS_OWNERDRAW) + { + if (hwnd->m_parent) + { + DRAWITEMSTRUCT dis = { ODT_BUTTON, hwnd->m_id, 0, 0, (UINT)(pressed?ODS_SELECTED:0),hwnd,ps.hdc,r,(DWORD_PTR)hwnd->m_userdata }; + SendMessage(hwnd->m_parent,WM_DRAWITEM,(WPARAM)hwnd->m_id,(LPARAM)&dis); + } + EndPaint(hwnd,&ps); + return 0; + } + + const bool ischk = sf == BS_AUTO3STATE || sf == BS_AUTOCHECKBOX || sf == BS_AUTORADIOBUTTON; + if (!ischk) + { + Draw3DBox(ps.hdc,&r,g_swell_ctheme.button_bg, + g_swell_ctheme.button_hilight, + g_swell_ctheme.button_shadow,pressed); + + if ((hwnd->m_style & BS_XPOSITION_MASK) == BS_LEFT) + r.left+=2; + else + f|=DT_CENTER; + if (pressed) + { + const int pad = SWELL_UI_SCALE(2); + r.left+=pad; + r.top+=pad; + if (s->bitmap) { r.right+=pad; r.bottom+=pad; } + } + } + + if (draw_focus_indicator(hwnd,ps.hdc,NULL)) + { + KillTimer(hwnd,1); + SetTimer(hwnd,1,100,NULL); + } + + if (ischk) + { + const int chksz = SWELL_UI_SCALE(12), chki = SWELL_UI_SCALE(2); + RECT tr={r.left+chki,(r.top+r.bottom)/2-chksz/2,r.left+chki+chksz}; + tr.bottom = tr.top+chksz; + + HPEN pen=CreatePen(PS_SOLID,0,g_swell_ctheme.checkbox_fg); + HGDIOBJ oldPen = SelectObject(ps.hdc,pen); + int st = (int)(s->state&3); + if (sf == BS_AUTOCHECKBOX || sf == BS_AUTO3STATE) + { + if (st==3||(st==2 && (hwnd->m_style & 0xf) == BS_AUTOCHECKBOX)) st=1; + + Draw3DBox(ps.hdc,&tr, + st==2?g_swell_ctheme.checkbox_inter: + g_swell_ctheme.checkbox_bg, + g_swell_ctheme.button_shadow, + g_swell_ctheme.button_hilight); + + if (st == 1||pressed) + { + RECT ar=tr; + ar.left+=SWELL_UI_SCALE(2); + ar.right-=SWELL_UI_SCALE(3); + ar.top+=SWELL_UI_SCALE(2); + ar.bottom-=SWELL_UI_SCALE(3); + if (pressed) + { + const int rsz=chksz/4; + ar.left+=rsz; + ar.top+=rsz; + ar.right-=rsz; + ar.bottom-=rsz; + } + MoveToEx(ps.hdc,ar.left,ar.top,NULL); + LineTo(ps.hdc,ar.right,ar.bottom); + MoveToEx(ps.hdc,ar.right,ar.top,NULL); + LineTo(ps.hdc,ar.left,ar.bottom); + } + } + else if (sf == BS_AUTORADIOBUTTON) + { + HBRUSH br = CreateSolidBrush(g_swell_ctheme.checkbox_bg); + HGDIOBJ oldBrush = SelectObject(ps.hdc,br); + Ellipse(ps.hdc,tr.left+1,tr.top+1,tr.right-1,tr.bottom-1); + SelectObject(ps.hdc,oldBrush); + DeleteObject(br); + if (st) + { + const int amt = (tr.right-tr.left)/6 + SWELL_UI_SCALE(2); + br = CreateSolidBrush(g_swell_ctheme.checkbox_fg); + oldBrush = SelectObject(ps.hdc,br); + Ellipse(ps.hdc,tr.left+amt,tr.top+amt,tr.right-amt,tr.bottom-amt); + SelectObject(ps.hdc,oldBrush); + DeleteObject(br); + } + } + SelectObject(ps.hdc,oldPen); + DeleteObject(pen); + r.left += chksz + SWELL_UI_SCALE(5); + SetTextColor(ps.hdc, + hwnd->m_enabled ? g_swell_ctheme.checkbox_text : + g_swell_ctheme.checkbox_text_disabled); + } + + if (s->bitmap) + { + BITMAP inf={0,}; + GetObject(s->bitmap,sizeof(BITMAP),&inf); + RECT cr; + cr.left = (r.right+r.left - inf.bmWidth)/2; + cr.top = (r.bottom+r.top - inf.bmHeight)/2; + cr.right = cr.left+inf.bmWidth; + cr.bottom = cr.top+inf.bmHeight; + DrawImageInRect(ps.hdc,s->bitmap,&cr); + } + else + { + char buf[512]; + buf[0]=0; + GetWindowText(hwnd,buf,sizeof(buf)); + if (buf[0]) DrawText(ps.hdc,buf,-1,&r,f); + } + + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_TIMER: + if (wParam==1) + { + if (!fast_has_focus(hwnd)) + { + KillTimer(hwnd,1); + InvalidateRect(hwnd,NULL,FALSE); + } + } + break; + case BM_GETCHECK: + if (hwnd) + { + buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data; + return (s->state&3); + } + return 0; + case BM_GETIMAGE: + if (wParam != IMAGE_BITMAP && wParam != IMAGE_ICON) return 0; // ignore unknown types + { + buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data; + return (LRESULT) s->bitmap; + } + return 0; + case BM_SETIMAGE: + if (wParam == IMAGE_BITMAP || wParam == IMAGE_ICON) + { + buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data; + LRESULT res = (LRESULT)s->bitmap; + s->bitmap = (HICON)lParam; + s->bitmap_mode = wParam; + InvalidateRect(hwnd,NULL,FALSE); + return res; + } + return 0; + case BM_SETCHECK: + if (hwnd) + { + buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data; + int check = (int)wParam; + INT_PTR op = s->state; + s->state=(check > 2 || check<0 ? 1 : (check&3)) | (s->state&~3); + if (s->state == op) break; + } + else + { + break; + } + // fall through (invalidating) + case WM_USER+100: + case WM_CAPTURECHANGED: + case WM_SETTEXT: + case WM_SETFOCUS: + case WM_KILLFOCUS: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +static HWND swell_makeButton(HWND owner, int idx, RECT *tr, const char *label, bool vis, int style) +{ + HWND hwnd = new HWND__(owner,idx,tr,label,vis,buttonWindowProc); + hwnd->m_private_data = (INT_PTR) new buttonWindowState; + hwnd->m_classname = "Button"; + hwnd->m_style = style|WS_CHILD; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + return hwnd; +} + +static LRESULT WINAPI groupWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + + const char *buf = hwnd->m_title.Get(); + int th=SWELL_UI_SCALE(20); + int tw=0; + int xp=0; + if (buf && buf[0]) + { + RECT tr={0,}; + DrawText(ps.hdc,buf,-1,&tr,DT_CALCRECT); + th=tr.bottom-tr.top; + tw=tr.right-tr.left; + } + if (hwnd->m_style & SS_CENTER) + { + xp = r.right/2 - tw/2; + } + else if (hwnd->m_style & SS_RIGHT) + { + xp = r.right - tw; + } + const int sc8 = SWELL_UI_SCALE(8); + if (xp r.right-sc8) tw=r.right-sc8-xp; + + HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.group_hilight); + HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.group_shadow); + HGDIOBJ oldPen=SelectObject(ps.hdc,pen); + + MoveToEx(ps.hdc,xp - (tw?sc8/2:0) + 1,th/2+1,NULL); + LineTo(ps.hdc,1,th/2+1); + LineTo(ps.hdc,1,r.bottom-1); + LineTo(ps.hdc,r.right-1,r.bottom-1); + LineTo(ps.hdc,r.right-1,th/2+1); + LineTo(ps.hdc,xp+tw + (tw?sc8/2:0),th/2+1); + + SelectObject(ps.hdc,pen2); + + MoveToEx(ps.hdc,xp - (tw?sc8/2:0),th/2,NULL); + LineTo(ps.hdc,0,th/2); + LineTo(ps.hdc,0,r.bottom-2); + LineTo(ps.hdc,r.right-2,r.bottom-2); + LineTo(ps.hdc,r.right-2,th/2); + LineTo(ps.hdc,xp+tw + (tw?4:0),th/2); + + + SelectObject(ps.hdc,oldPen); + DeleteObject(pen); + DeleteObject(pen2); + + SetTextColor(ps.hdc,g_swell_ctheme.group_text); + SetBkMode(ps.hdc,TRANSPARENT); + r.left = xp; + r.right = xp+tw; + r.bottom = th; + if (buf && buf[0]) DrawText(ps.hdc,buf,-1,&r,DT_LEFT|DT_TOP); + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_SETTEXT: + InvalidateRect(hwnd,NULL,TRUE); + break; + case WM_LBUTTONDBLCLK: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MOUSEMOVE: + if (GET_Y_LPARAM(lParam) >= SWELL_UI_SCALE(20)) + { + HWND par = GetParent(hwnd); + if (par) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ClientToScreen(hwnd,&pt); + ScreenToClient(par,&pt); + return SendMessage(par,msg,wParam,MAKELPARAM(pt.x,pt.y)); + } + } + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +static void calcScroll(int wh, int totalw, int scroll_x, int *thumbsz, int *thumbpos) +{ + const double isz = wh / (double) totalw; + int sz = (int) (wh * isz + 0.5); + if (sz < g_swell_ctheme.scrollbar_min_thumb_height) sz=g_swell_ctheme.scrollbar_min_thumb_height; + + *thumbpos = (int) (scroll_x * isz + 0.5); + if (*thumbpos >= wh-sz) *thumbpos = wh-sz; + + *thumbsz = sz; +} + +static void drawHorizontalScrollbar(HDC hdc, RECT cr, int vieww, int totalw, int scroll_x) +{ + if (totalw <= vieww) return; + + int thumbsz, thumbpos; + calcScroll(vieww,totalw,scroll_x,&thumbsz, &thumbpos); + + HBRUSH br = CreateSolidBrush(g_swell_ctheme.scrollbar_fg); + HBRUSH br2 = CreateSolidBrush(g_swell_ctheme.scrollbar_bg); + RECT fr = { cr.left, cr.bottom - g_swell_ctheme.scrollbar_width, cr.left + thumbpos, cr.bottom }; + if (fr.right>fr.left) FillRect(hdc,&fr,br2); + + fr.left = fr.right; + fr.right = fr.left + thumbsz; + if (fr.right>fr.left) FillRect(hdc,&fr,br); + + fr.left = fr.right; + fr.right = cr.right; + if (fr.right>fr.left) FillRect(hdc,&fr,br2); + + DeleteObject(br); + DeleteObject(br2); +} + +static void drawVerticalScrollbar(HDC hdc, RECT cr, int totalh, int scroll_y) +{ + if (totalh <= cr.bottom-cr.top) return; + + int thumbsz, thumbpos; + calcScroll(cr.bottom-cr.top,totalh,scroll_y,&thumbsz, &thumbpos); + + HBRUSH br = CreateSolidBrush(g_swell_ctheme.scrollbar_fg); + HBRUSH br2 = CreateSolidBrush(g_swell_ctheme.scrollbar_bg); + RECT fr = { cr.right - g_swell_ctheme.scrollbar_width, cr.top, cr.right,cr.top+thumbpos}; + if (fr.bottom>fr.top) FillRect(hdc,&fr,br2); + + fr.top = fr.bottom; + fr.bottom = fr.top + thumbsz; + if (fr.bottom>fr.top) FillRect(hdc,&fr,br); + + fr.top = fr.bottom; + fr.bottom = cr.bottom; + if (fr.bottom>fr.top) + { + FillRect(hdc,&fr,br2); + + fr.top=fr.bottom-1; //add a little bottom border in case there is a horizontal scrollbar too + FillRect(hdc,&fr,br2); + } + + DeleteObject(br); + DeleteObject(br2); +} + +static int editMeasureLineLength(HDC hdc, const char *str, int str_len) +{ + RECT tmp = {0,}; + DrawText(hdc,str,str_len,&tmp,DT_NOPREFIX|DT_SINGLELINE|DT_CALCRECT|DT_RIGHT); + return tmp.right; +} + + +int swell_getLineLength(const char *buf, int *post_skip, int wrap_maxwid, HDC hdc) +{ + int lb=0; + int ps = 0; + while (buf[lb] && buf[lb] != '\r' && buf[lb] != '\n') lb++; + + if (wrap_maxwid > g_swell_ctheme.scrollbar_width && hdc && lb>0) + { + wrap_maxwid -= g_swell_ctheme.scrollbar_width; + + // step through a word at a time and find the most that can fit + int x=0,best_len=0,sumw=0; + for (;;) + { + while (x < lb && buf[x] > 0 && isspace(buf[x])) x++; + while (x < lb && (buf[x]<0 || !isspace(buf[x]))) x++; + const int thisw = editMeasureLineLength(hdc,buf+best_len,x-best_len); + if (thisw+sumw > wrap_maxwid) break; + sumw+=thisw; + best_len=x; + if (x >= lb) break; + } + if (best_len == 0) + { + // todo: split individual word (ugh) + if (x>0) lb = x; + } + else + lb = best_len; + + while (buf[ps+lb] == '\t' || buf[ps+lb] == ' ') ps++; // skip any trailing whitespace + } + if (buf[ps+lb] == '\r') ps++; + if (buf[ps+lb] == '\n') ps++; + *post_skip = ps; + return lb; +} + +#define EDIT_ALLOW_MULTILINE_CACHE(wwrap,hwnd,tlen) \ + ((wwrap)>0 && ((hwnd)->m_style & (ES_READONLY|ES_MULTILINE)) == (ES_READONLY|ES_MULTILINE) && (tlen)>10000) + +struct __SWELL_editControlState +{ + __SWELL_editControlState() : cache_linelen_bytes(8192) + { + cursor_timer=0; + cursor_state=0; + sel1=sel2=-1; + cursor_pos=0; + scroll_x=scroll_y=0; + max_height=0; + max_width=0; + cache_linelen_strlen = cache_linelen_w = 0; + m_disable_contextmenu = false; + } + ~__SWELL_editControlState() {} + + int cursor_pos, sel1,sel2; // in character pos (*not* bytepos) + int cursor_state; + int cursor_timer; + int scroll_x, scroll_y; + int max_height; // only used in multiline + int max_width; + + // used for caching line lengths for multiline word-wrapping edit controls + int cache_linelen_w, cache_linelen_strlen; + WDL_TypedBuf cache_linelen_bytes; + + bool m_disable_contextmenu; + + bool deleteSelection(WDL_FastString *fs); + int getSelection(WDL_FastString *fs, const char **ptrOut) const; + void moveCursor(int cp); // extends selection if shift is held, otherwise clears + void onMouseDown(int &capmode_state, int last_cursor); + void onMouseDrag(int &capmode_state, int p); + + void autoScrollToOffset(HWND hwnd, int charpos, bool is_multiline, bool word_wrap); +}; + + +static int utf8fs_charpos_to_bytepos(const WDL_FastString *fs, int charpos) +{ + return charpos < fs->GetLength() ? WDL_utf8_charpos_to_bytepos(fs->Get(),charpos) : fs->GetLength(); +} + + + +static bool editGetCharPos(HDC hdc, const char *str, int singleline_len, int charpos, int line_h, POINT *pt, int word_wrap, + __SWELL_editControlState *es, HWND hwnd) +{ + int bytepos = WDL_utf8_charpos_to_bytepos(str,charpos); + int ypos = 0; + if (singleline_len >= 0) + { + if (bytepos > singleline_len) return false; + pt->y=0; + pt->x=editMeasureLineLength(hdc,str,bytepos); + return true; + } + + + int *use_cache = NULL, use_cache_len = 0; + int title_len = 0; + const bool allow_cache = hwnd && es && EDIT_ALLOW_MULTILINE_CACHE(word_wrap,hwnd,title_len = (int)strlen(str)); + if (allow_cache && + es->cache_linelen_w == word_wrap && + es->cache_linelen_strlen == title_len) + { + use_cache = es->cache_linelen_bytes.Get(); + use_cache_len = es->cache_linelen_bytes.GetSize(); + } + + while (*str) + { + int pskip = 0, lb; + if (!use_cache || use_cache_len < 1) + { + lb = swell_getLineLength(str,&pskip,word_wrap,hdc); + } + else + { + lb = *use_cache++; + if (WDL_NOT_NORMALLY(lb < 1)) break; + } + if (bytepos < lb+pskip) + { + pt->x=editMeasureLineLength(hdc,str,bytepos); + pt->y=ypos; + return true; + } + str += lb+pskip; + bytepos -= lb+pskip; + if (*str || (pskip>0 && str[-1] == '\n')) ypos += line_h; + } + pt->x=0; + pt->y=ypos; + return true; +} + + +static int editHitTestLine(HDC hdc, const char *str, int str_len, int xpos, int ypos) +{ + RECT mr={0,}; + DrawText(hdc,str_len == 0 ? " " : str,wdl_max(str_len,1),&mr,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT); + + if (xpos >= mr.right) return str_len; + if (xpos < 1) return 0; + + // could bsearch, but meh + int x = 0; + while (x < str_len) + { + memset(&mr,0,sizeof(mr)); + const int clen = wdl_utf8_parsechar(str+x,NULL); + DrawText(hdc,str,x+clen,&mr,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT|DT_RIGHT/*swell-only flag*/); + if (xpos < mr.right) break; + x += clen; + } + return x; +} + +static int editHitTest(HDC hdc, const char *str, int singleline_len, int xpos, int ypos, int word_wrap, + __SWELL_editControlState *es, HWND hwnd) +{ + if (singleline_len >= 0) return editHitTestLine(hdc,str,singleline_len,xpos,1); + + const char *buf = str; + int bytepos = 0; + RECT tmp={0}; + const int line_h = DrawText(hdc," ",1,&tmp,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT); + + int *use_cache = NULL, use_cache_len = 0; + int title_len = 0; + const bool allow_cache = hwnd && es && EDIT_ALLOW_MULTILINE_CACHE(word_wrap,hwnd,title_len = (int)strlen(str)); + if (allow_cache && + es->cache_linelen_w == word_wrap && + es->cache_linelen_strlen == title_len) + { + use_cache = es->cache_linelen_bytes.Get(); + use_cache_len = es->cache_linelen_bytes.GetSize(); + } + + for (;;) + { + int pskip=0; + int lb; + + if (!use_cache || use_cache_len < 1) + { + lb = swell_getLineLength(buf,&pskip,word_wrap,hdc); + } + else + { + lb = *use_cache++; + if (WDL_NOT_NORMALLY(lb < 1)) return bytepos; + } + + if (ypos < line_h) return bytepos + editHitTestLine(hdc,buf,lb, xpos,ypos); + ypos -= line_h; + + if (!buf[0] || !buf[lb]) return bytepos + lb; + + bytepos += lb+pskip; + buf += lb+pskip; + } +} + + +bool __SWELL_editControlState::deleteSelection(WDL_FastString *fs) +{ + if (sel1>=0 && sel2 > sel1) + { + int pos1 = utf8fs_charpos_to_bytepos(fs,sel1); + int pos2 = utf8fs_charpos_to_bytepos(fs,sel2); + if (pos2 == pos1) return false; + + int cp = utf8fs_charpos_to_bytepos(fs,cursor_pos); + fs->DeleteSub(pos1,pos2-pos1); + if (cp >= pos2) cp -= pos2-pos1; + else if (cp >= pos1) cp=pos1; + cursor_pos = WDL_utf8_bytepos_to_charpos(fs->Get(),cp); + + sel1=sel2=-1; + return true; + } + return false; +} + +int __SWELL_editControlState::getSelection(WDL_FastString *fs, const char **ptrOut) const +{ + if (sel1>=0 && sel2>sel1) + { + int pos1 = utf8fs_charpos_to_bytepos(fs,sel1); + int pos2 = utf8fs_charpos_to_bytepos(fs,sel2); + if (ptrOut) *ptrOut = fs->Get()+pos1; + return pos2-pos1; + } + return 0; +} +void __SWELL_editControlState::moveCursor(int cp) // extends selection if shift is held, otherwise clears +{ + if (GetAsyncKeyState(VK_SHIFT)&0x8000) + { + if (sel1>=0 && sel2>sel1 && (cursor_pos==sel1 || cursor_pos==sel2)) + { + if (cursor_pos==sel1) sel1=cp; + else sel2=cp; + if (sel2 sel2) + { + sel1=sel2; + sel2=last_cursor; + capmode_state = 3; + } + } + else + { + sel1=sel2=cursor_pos; + } +} + +void __SWELL_editControlState::onMouseDrag(int &capmode_state, int p) +{ + if (sel1 == sel2) + { + if (p < sel1) + { + sel1 = p; + capmode_state = 3; + } + else if (p > sel2) + { + sel2 = p; + capmode_state = 4; + } + } + else if (capmode_state == 3) + { + if (p < sel2) sel1 = p; + else if (p > sel2) + { + sel1 = sel2; + sel2 = p; + capmode_state=4; + } + } + else + { + if (p > sel1) sel2 = p; + else if (p < sel1) + { + sel2 = sel1; + sel1 = p; + capmode_state=3; + } + } +} + +void __SWELL_editControlState::autoScrollToOffset(HWND hwnd, int charpos, bool is_multiline, bool word_wrap) +{ + if (!hwnd) return; + HDC hdc = GetDC(hwnd); + if (!hdc) return; + RECT tmp={0,}; + const int line_h = DrawText(hdc," ",1,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX); + + GetClientRect(hwnd,&tmp); + if (is_multiline) + { + tmp.right -= g_swell_ctheme.scrollbar_width; + if (!word_wrap) tmp.bottom -= g_swell_ctheme.scrollbar_width; + } + + int wwrap = word_wrap?tmp.right:0; + POINT pt={0,}; + if (editGetCharPos(hdc, hwnd->m_title.Get(), + is_multiline? -1:hwnd->m_title.GetLength(), charpos, line_h, &pt, + wwrap, is_multiline?this:NULL,hwnd)) + { + if (!word_wrap) + { + const int padsz = wdl_max(tmp.right - line_h,line_h); + if (pt.x > scroll_x+padsz) scroll_x = pt.x - padsz; + if (pt.x < scroll_x) scroll_x=pt.x; + } + if (is_multiline) + { + if (charpos > hwnd->m_title.GetLength()) + tmp.bottom -= line_h; + if (pt.y+line_h > scroll_y+tmp.bottom) scroll_y = pt.y - tmp.bottom + line_h; + if (pt.y < scroll_y) scroll_y=pt.y; + } + if (scroll_y < 0) scroll_y=0; + if (scroll_x < 0) scroll_x=0; + } + ReleaseDC(hwnd,hdc); +} + +static bool is_word_char(char c) +{ + return c<0/*all utf-8 chars are word chars*/ || isalnum(c) || c == '_'; +} + +static int scanWord(const char *buf, int bytepos, int dir) +{ + if (dir < 0 && !bytepos) return 0; + if (dir > 0 && !buf[bytepos]) return bytepos; + + if (!buf[bytepos] && bytepos > 0) bytepos--; + + const unsigned char *bytebuf = (const unsigned char*) buf; + if (dir < 0) + { + const bool cc = is_word_char(buf[--bytepos]); + while (bytepos > 0 && is_word_char(buf[bytepos-1]) == cc) bytepos--; + while (bytepos > 0 && bytebuf[bytepos] >= 0x80 && bytebuf[bytepos] < 0xC0) bytepos--; // skip any UTF-8 continuation bytes + } + else + { + const bool cc = is_word_char(buf[bytepos]); + while (buf[bytepos+1] && is_word_char(buf[bytepos+1]) == cc) bytepos++; + bytepos++; + while (bytebuf[bytepos] >= 0x80 && bytebuf[bytepos] < 0xC0) bytepos++; // skip any UTF-8 continuation bytes + } + + return bytepos; +} + +static LRESULT OnEditKeyDown(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, + bool wantReturn, bool isMultiLine, __SWELL_editControlState *es, bool isEditCtl) +{ + if (lParam & (FCONTROL|FALT|FLWIN)) + { + if (lParam == (FVIRTKEY | FCONTROL)) + { + if (wParam == 'C' || wParam == 'X') + { + const char *s = NULL; + int slen = es->getSelection(&hwnd->m_title,&s); + if (slen > 0 && s) + { + if (!isEditCtl || !(hwnd->m_style & ES_PASSWORD)) + { + OpenClipboard(hwnd); + HANDLE h = GlobalAlloc(0,slen+1); + if (h) + { + memcpy((char*)h,s,slen); + ((char*)h)[slen] = 0; + SetClipboardData(CF_TEXT,h); + } + CloseClipboard(); + if (h && wParam == 'X' && (!isEditCtl || !(hwnd->m_style & ES_READONLY))) + { + es->deleteSelection(&hwnd->m_title); + return 7; + } + } + } + } + else if (wParam == 'V' && !(hwnd->m_style & ES_READONLY)) + { + OpenClipboard(hwnd); + HANDLE h = GetClipboardData(CF_TEXT); + const char *s = NULL; + if (h) + { + s = (const char*)GlobalLock(h); + if (s) + { + es->deleteSelection(&hwnd->m_title); + int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos); + hwnd->m_title.Insert(s,bytepos); + if (!(hwnd->m_style&ES_MULTILINE)) + { + char *p = (char *)hwnd->m_title.Get() + bytepos; + char *ep = p + strlen(s); + while (*p && p < ep) { if (*p == '\r' || *p == '\n') *p=' '; p++; } + } + es->cursor_pos += WDL_utf8_get_charlen(s); + GlobalUnlock(h); + } + } + CloseClipboard(); + if (s) return 7; + } + else if (wParam == 'A') + { + es->sel1 = 0; + es->cursor_pos = es->sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get()); + return 2; + } + } + if (lParam & (FALT | FLWIN)) return 0; + } + else + { + if ((lParam & FVIRTKEY) && wParam == VK_TAB) return 0; // pass through to window + + const bool is_numpad = wParam >= VK_NUMPAD0 && wParam <= VK_DIVIDE; + if (wParam >= 32 && (!(lParam & FVIRTKEY) || swell_is_virtkey_char((int)wParam) || is_numpad)) + { + if (lParam & FVIRTKEY) + { + if (wParam >= 'A' && wParam <= 'Z') + { + if ((lParam&FSHIFT) ^ (swell_is_likely_capslock?0:FSHIFT)) wParam += 'a' - 'A'; + } + else if (is_numpad) + { + if (wParam <= VK_NUMPAD9) wParam += '0' - VK_NUMPAD0; + else wParam += '*' - VK_MULTIPLY; + } + } + + if (hwnd->m_style & ES_READONLY) return 1; + + char b[8]; + WDL_MakeUTFChar(b,wParam,sizeof(b)); + es->deleteSelection(&hwnd->m_title); + int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos); + hwnd->m_title.Insert(b,bytepos); + es->cursor_pos++; + return 7; + } + } + + if (es && (lParam & FVIRTKEY)) switch (wParam) + { + case VK_NEXT: + case VK_PRIOR: + if (!isMultiLine) break; + case VK_UP: + case VK_DOWN: + if (isMultiLine) + { + HDC hdc=GetDC(hwnd); + if (hdc) + { + RECT tmp={0}; + const int line_h = DrawText(hdc," ",1,&tmp,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT); + POINT pt; + GetClientRect(hwnd,&tmp); + const int wwrap = (hwnd->m_style & ES_AUTOHSCROLL) ? 0 : tmp.right - g_swell_ctheme.scrollbar_width; + if (editGetCharPos(hdc, hwnd->m_title.Get(), -1, es->cursor_pos, line_h, &pt, wwrap, isEditCtl ? es : NULL, hwnd)) + { + if (wParam == VK_UP) pt.y -= line_h/2; + else if (wParam == VK_NEXT) + { + int ey = es->scroll_y + tmp.bottom - (wwrap?0:g_swell_ctheme.scrollbar_width) - line_h; + if (pt.y < ey-line_h) pt.y = ey; + else pt.y = ey + tmp.bottom - line_h - (wwrap?0:g_swell_ctheme.scrollbar_width); + } + else if (wParam == VK_PRIOR) + { + if (pt.y > es->scroll_y) pt.y = es->scroll_y; + else pt.y = es->scroll_y - (tmp.bottom-line_h/2 - g_swell_ctheme.scrollbar_width); + } + else pt.y += line_h + line_h/2; + int nextpos = editHitTest(hdc, hwnd->m_title.Get(), -1,pt.x,pt.y,wwrap,es,hwnd); + es->moveCursor(WDL_utf8_bytepos_to_charpos(hwnd->m_title.Get(),nextpos)); + } + ReleaseDC(hwnd,hdc); + } + return 3; + } + // fall through + case VK_HOME: + case VK_END: + if (isMultiLine) + { + const char *buf = hwnd->m_title.Get(); + int lpos = 0, wwrap=0; + HDC hdc=NULL; + if (!(hwnd->m_style & ES_AUTOHSCROLL)) + { + hdc = GetDC(hwnd); + RECT r; + GetClientRect(hwnd,&r); + if (hdc) wwrap = r.right - g_swell_ctheme.scrollbar_width; + } + const int cbytepos = WDL_utf8_charpos_to_bytepos(buf,es->cursor_pos); + for (;;) + { + int ps=0, lb = swell_getLineLength(buf+lpos, &ps,wwrap,hdc); + if (!buf[lpos] || (cbytepos >= lpos && cbytepos < lpos+lb+ps + (buf[lpos+lb+ps]?0:1))) + { + if (wParam == VK_HOME) es->moveCursor(WDL_utf8_bytepos_to_charpos(buf,lpos)); + else es->moveCursor(WDL_utf8_bytepos_to_charpos(buf,lpos+lb)); + return 3; + } + lpos += lb+ps; + } + if (hdc) ReleaseDC(hwnd,hdc); + } + + if (wParam == VK_UP || wParam == VK_HOME) es->moveCursor(0); + else es->moveCursor(WDL_utf8_get_charlen(hwnd->m_title.Get())); + return 3; + case VK_LEFT: + { + int cp = es->cursor_pos; + if (cp > 0) + { + const char *buf=hwnd->m_title.Get(); + if (lParam & FCONTROL) + { + cp = WDL_utf8_bytepos_to_charpos(buf, + scanWord(buf,WDL_utf8_charpos_to_bytepos(buf,cp),-1)); + } + else + { + const int p = WDL_utf8_charpos_to_bytepos(buf,--cp); + if (cp > 0 && p > 0 && buf[p] == '\n' && buf[p-1] == '\r') cp--; + } + } + es->moveCursor(cp); + } + return 3; + case VK_RIGHT: + { + int cp = es->cursor_pos; + if (cp < WDL_utf8_get_charlen(hwnd->m_title.Get())) + { + const char *buf=hwnd->m_title.Get(); + const int p = WDL_utf8_charpos_to_bytepos(buf,cp); + + if (lParam & FCONTROL) + cp = WDL_utf8_bytepos_to_charpos(buf,scanWord(buf,p,1)); + else if (buf[p] == '\r' && buf[p+1] == '\n') + cp+=2; + else + cp++; + } + es->moveCursor(cp); + } + return 3; + case VK_DELETE: + if (hwnd->m_style & ES_READONLY) return 1; + if (hwnd->m_title.GetLength()) + { + if (es->deleteSelection(&hwnd->m_title)) return 7; + + const int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos); + if (bytepos < hwnd->m_title.GetLength()) + { + const char *rd = hwnd->m_title.Get()+bytepos; + hwnd->m_title.DeleteSub(bytepos, rd[0] == '\r' && rd[1] == '\n' ? 2 : wdl_utf8_parsechar(rd,NULL)); + return 7; + } + } + return 1; + + case VK_BACK: + if (hwnd->m_style & ES_READONLY) return 1; + if (hwnd->m_title.GetLength()) + { + if (es->deleteSelection(&hwnd->m_title)) return 7; + if (es->cursor_pos > 0) + { + es->cursor_pos--; + const char *buf = hwnd->m_title.Get(); + int bytepos = WDL_utf8_charpos_to_bytepos(buf,es->cursor_pos); + if (bytepos > 0 && buf[bytepos] == '\n' && buf[bytepos-1] == '\r') + { + hwnd->m_title.DeleteSub(bytepos-1, 2); + es->cursor_pos--; + } + else hwnd->m_title.DeleteSub(bytepos, wdl_utf8_parsechar(hwnd->m_title.Get()+bytepos,NULL)); + return 7; + } + } + return 1; + case VK_RETURN: + if (wantReturn) + { + if (hwnd->m_style & ES_READONLY) return 1; + if (es->deleteSelection(&hwnd->m_title)) return 7; + int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos); + hwnd->m_title.Insert("\r\n",bytepos); + es->cursor_pos+=2; // skip \r and \n + return 7; + } + return 0; + } + return 0; +} + +static int editControlPaintLine(HDC hdc, const char *str, int str_len, int cursor_pos, int sel1, int sel2, const RECT *r, int dtflags) +{ + // cursor_pos, sel1, sel2 are all byte positions + int rv = 0; + if (str_len>0) + { + RECT outr = *r; + if (sel2 < str_len || sel1 > 0) + { + RECT tmp={0,}; + DrawText(hdc,str,str_len,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX); + rv = tmp.right; + DrawText(hdc,str,str_len,&outr,dtflags|DT_SINGLELINE|DT_NOPREFIX); + } + + const int offs = wdl_max(sel1,0); + const int endptr = wdl_min(sel2,str_len); + if (endptr > offs) + { + SetBkMode(hdc,OPAQUE); + SetBkColor(hdc,g_swell_ctheme.edit_bg_sel); + const int oldc = GetTextColor(hdc); + SetTextColor(hdc,g_swell_ctheme.edit_text_sel); + + RECT tmp={0,}; + DrawText(hdc,str,offs,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX); + outr.left += tmp.right; + DrawText(hdc,str+offs,endptr-offs,&outr,dtflags|DT_SINGLELINE|DT_NOPREFIX); + + SetBkMode(hdc,TRANSPARENT); + SetTextColor(hdc,oldc); + } + } + + if (cursor_pos >= 0 && cursor_pos <= str_len) + { + RECT mr={0,}; + if (cursor_pos>0) DrawText(hdc,str,cursor_pos,&mr,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE); + + int oc = GetTextColor(hdc); + SetTextColor(hdc,g_swell_ctheme.edit_cursor); + mr.left = r->left + mr.right - 1; + mr.right = mr.left+1; + mr.top = r->top; + mr.bottom = r->bottom; + DrawText(hdc,"|",1,&mr,dtflags|DT_SINGLELINE|DT_NOPREFIX|DT_NOCLIP); + SetTextColor(hdc,oc); + } + return rv; +} + +static void passwordify(WDL_FastString **s) +{ + const int l = WDL_utf8_get_charlen((*s)->Get()); + if (l>0) + { + static WDL_FastString tmp; + tmp.SetLen(l,false,'*'); + *s = &tmp; + } +} + +static LRESULT WINAPI editWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + __SWELL_editControlState *es = (__SWELL_editControlState*)hwnd->m_private_data; + static int s_capmode_state /* 1=vscroll, 2=hscroll, 3=move sel1, 4=move sel2*/, s_capmode_data1; + switch (msg) + { + case WM_NCDESTROY: + delete es; + hwnd->m_private_data=0; + break; + case WM_CONTEXTMENU: + { + if (es->m_disable_contextmenu) break; + + HMENU menu=CreatePopupMenu(); + MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_TYPE,MFT_STRING, 0, + (UINT) 100, NULL,NULL,NULL,0,(char*)"Copy"}; + + if (!(hwnd->m_style & ES_PASSWORD)) + InsertMenuItem(menu,0,TRUE,&mi); + mi.wID++; + mi.dwTypeData = (char*)"Paste"; + InsertMenuItem(menu,0,TRUE,&mi); + mi.wID++; + mi.dwTypeData = (char*)"Select all"; + InsertMenuItem(menu,0,TRUE,&mi); + POINT p; + GetCursorPos(&p); + + const int a = TrackPopupMenu(menu,TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTALIGN,p.x,p.y,0,hwnd,0); + DestroyMenu(menu); + if (a==100) OnEditKeyDown(hwnd,WM_KEYDOWN,'C',FVIRTKEY|FCONTROL,false,false,es,true); + else if (a==101) + { + OnEditKeyDown(hwnd,WM_KEYDOWN,'V',FVIRTKEY|FCONTROL,false,false,es,true); + SendMessage(GetParent(hwnd),WM_COMMAND,(EN_CHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + } + else if (a==102) SendMessage(hwnd,EM_SETSEL,0,-1); + + if (a) InvalidateRect(hwnd,NULL,FALSE); + + } + return 1; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + es->cursor_state=1; // next invalidate draws blinky + + // todo: if selection active and not focused, do not mouse hit test + if (msg == WM_LBUTTONDOWN) + { + WDL_FastString *title = &hwnd->m_title; + if (hwnd->m_style & ES_PASSWORD) passwordify(&title); + const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0; + RECT r; + GetClientRect(hwnd,&r); + if (multiline) + { + RECT br = r; + br.right -= g_swell_ctheme.scrollbar_width; + + if (es->max_width > br.right && (hwnd->m_style & ES_AUTOHSCROLL)) + { + if (GET_Y_LPARAM(lParam) >= br.bottom - g_swell_ctheme.scrollbar_width) + { + int xp = GET_X_LPARAM(lParam), xpos = xp; + + int thumbsz, thumbpos; + calcScroll(br.right, es->max_width,es->scroll_x,&thumbsz,&thumbpos); + + if (xpos < thumbpos) xp = thumbpos; + else if (xpos > thumbpos+thumbsz) xp = thumbpos + thumbsz; + + s_capmode_state = 2; + s_capmode_data1 = xp; + SetCapture(hwnd); + if (xpos < thumbpos || xpos > thumbpos+thumbsz) goto forceMouseMove; + return 0; + } + br.bottom -= g_swell_ctheme.scrollbar_width; + } + if (GET_X_LPARAM(lParam)>=br.right && es->max_height > br.bottom) + { + int yp = GET_Y_LPARAM(lParam), ypos = yp; + + int thumbsz, thumbpos; + calcScroll(br.bottom, es->max_height, es->scroll_y,&thumbsz,&thumbpos); + + if (ypos < thumbpos) yp = thumbpos; + else if (ypos > thumbpos+thumbsz) yp = thumbpos + thumbsz; + + s_capmode_state = 1; + s_capmode_data1 = yp; + SetCapture(hwnd); + if (ypos < thumbpos || ypos > thumbpos+thumbsz) goto forceMouseMove; + return 0; + } + } + + + int xo=2; + int yo = multiline ? 2 : 0; + HDC hdc=GetDC(hwnd); + const int last_cursor = es->cursor_pos; + const int wwrap = (hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE ? + r.right - g_swell_ctheme.scrollbar_width : 0; + es->cursor_pos = WDL_utf8_bytepos_to_charpos(title->Get(), + editHitTest(hdc,title->Get(), + multiline?-1:title->GetLength(), + GET_X_LPARAM(lParam)-xo + es->scroll_x, + GET_Y_LPARAM(lParam)-yo + es->scroll_y, + wwrap,es,hwnd) + ); + + if (msg == WM_LBUTTONDOWN) + es->onMouseDown(s_capmode_state,last_cursor); + + ReleaseDC(hwnd,hdc); + + } + SetFocus(hwnd); + if (msg == WM_LBUTTONDOWN) SetCapture(hwnd); + + InvalidateRect(hwnd,NULL,FALSE); + return 0; + case WM_MOUSEWHEEL: + if (es && (hwnd->m_style & ES_MULTILINE)) + { + if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break; // pass modified mousewheel to parent + + RECT r; + GetClientRect(hwnd,&r); + r.right -= g_swell_ctheme.scrollbar_width; + if (es->max_width > r.right && (hwnd->m_style & ES_AUTOHSCROLL)) + { + r.bottom -= g_swell_ctheme.scrollbar_width; + } + const int viewsz = r.bottom; + const int totalsz=es->max_height + g_swell_ctheme.scrollbar_width; + + const int amt = ((short)HIWORD(wParam))/-2; + + const int oldscroll = es->scroll_y; + es->scroll_y += amt; + if (es->scroll_y + viewsz > totalsz) es->scroll_y = totalsz-viewsz; + if (es->scroll_y < 0) es->scroll_y=0; + if (es->scroll_y != oldscroll) + { + InvalidateRect(hwnd,NULL,FALSE); + } + + return 1; + } + break; + case WM_MOUSEMOVE: +forceMouseMove: + if (es && GetCapture()==hwnd) + { + RECT r; + GetClientRect(hwnd,&r); + const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0; + if (multiline) + { + r.right -= g_swell_ctheme.scrollbar_width; + if (es->max_width > r.right && (hwnd->m_style & ES_AUTOHSCROLL)) + { + r.bottom -= g_swell_ctheme.scrollbar_width; + } + } + if (s_capmode_state == 1) + { + int yv = s_capmode_data1; + int amt = GET_Y_LPARAM(lParam) - yv; + + if (amt) + { + const int viewsz = r.bottom; + const int totalsz=es->max_height + g_swell_ctheme.scrollbar_width; + amt = (int)floor(amt * (double)totalsz / (double)viewsz + 0.5); + + const int oldscroll = es->scroll_y; + es->scroll_y += amt; + if (es->scroll_y + viewsz > totalsz) es->scroll_y = totalsz-viewsz; + if (es->scroll_y < 0) es->scroll_y=0; + if (es->scroll_y != oldscroll) + { + s_capmode_data1 = GET_Y_LPARAM(lParam); + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + else if (s_capmode_state == 2) + { + int xv = s_capmode_data1; + int amt = GET_X_LPARAM(lParam) - xv; + + if (amt) + { + const int viewsz = r.right; + const int totalsz=es->max_width + g_swell_ctheme.scrollbar_width; + amt = (int)floor(amt * (double)totalsz / (double)viewsz + 0.5); + + const int oldscroll = es->scroll_x; + es->scroll_x += amt; + if (es->scroll_x + viewsz > totalsz) es->scroll_x = totalsz-viewsz; + if (es->scroll_x < 0) es->scroll_x=0; + if (es->scroll_x != oldscroll) + { + s_capmode_data1 = GET_X_LPARAM(lParam); + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + else if (s_capmode_state == 3 || s_capmode_state == 4) + { + int wwrap=0; + if ((hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE) + { + wwrap = r.right; // already has scrollbar size removed + } + int xo=2; + int yo = multiline ? 2 : 0; + HDC hdc=GetDC(hwnd); + WDL_FastString *title = &hwnd->m_title; + if (hwnd->m_style & ES_PASSWORD) passwordify(&title); + int p = WDL_utf8_bytepos_to_charpos(title->Get(), + editHitTest(hdc,title->Get(), + multiline?-1:title->GetLength(), + GET_X_LPARAM(lParam)-xo + es->scroll_x, + GET_Y_LPARAM(lParam)-yo + es->scroll_y, wwrap,es,hwnd) + ); + ReleaseDC(hwnd,hdc); + + es->onMouseDrag(s_capmode_state,p); + + es->autoScrollToOffset(hwnd,p, + (hwnd->m_style & ES_MULTILINE) != 0, + (hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE); + + + InvalidateRect(hwnd,NULL,FALSE); + } + } + return 0; + case WM_LBUTTONUP: + ReleaseCapture(); + return 0; + case WM_TIMER: + if (es && wParam == 100) + { + if (++es->cursor_state >= EDIT_CURSOR_CYCLE_INTERVAL) es->cursor_state=0; + if (GetFocusIncludeMenus()!=hwnd || es->cursor_state<2) InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + case WM_LBUTTONDBLCLK: + if (es) + { + // technically this should select the word rather than all + es->sel1 = 0; + es->cursor_pos = es->sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get()); + InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + case WM_KEYDOWN: + { + const int osel1 = es && es->sel1 >= 0 && es->sel2 > es->sel1 ? es->sel1 : -1; + + int f = OnEditKeyDown(hwnd,msg,wParam,lParam, + (hwnd->m_style&ES_WANTRETURN) && (hwnd->m_style&ES_MULTILINE), + !!(hwnd->m_style&ES_MULTILINE), + es,true); + if (f) + { + if (f&4) + { + es->cache_linelen_w=0; + SendMessage(GetParent(hwnd),WM_COMMAND,(EN_CHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + } + if (f&2) + { + if ((hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_AUTOHSCROLL && + osel1 >= 0 && es->cursor_pos > osel1 && es->sel1 < 0) + es->autoScrollToOffset(hwnd,osel1,false,false); + + es->autoScrollToOffset(hwnd,es->cursor_pos, + (hwnd->m_style & ES_MULTILINE) != 0, + (hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE + ); + InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + } + } + break; + case WM_KEYUP: + return 0; + case WM_PAINT: + { + PAINTSTRUCT ps; + + const bool focused = GetFocusIncludeMenus()==hwnd; + if (es) + { + if (focused) + { + if (!es->cursor_timer) { SetTimer(hwnd,100,EDIT_CURSOR_BLINK_LEN,NULL); es->cursor_timer=1; } + } + else + { + if (es->cursor_timer) { KillTimer(hwnd,100); es->cursor_timer=0; } + } + } + + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + const RECT orig_r2 = r; + RECT orig_r = r; + WDL_FastString *title = &hwnd->m_title; + if (hwnd->m_style & ES_PASSWORD) passwordify(&title); + + bool is_secpass = false; +again: + Draw3DBox(ps.hdc,&orig_r, hwnd->m_enabled ? g_swell_ctheme.edit_bg : g_swell_ctheme.edit_bg_disabled,-1,-1); + + SetTextColor(ps.hdc, + hwnd->m_enabled ? + //(hwnd->m_style & ES_READONLY) ? g_swell_ctheme.label_text : + g_swell_ctheme.edit_text : + g_swell_ctheme.edit_text_disabled + ); + SetBkMode(ps.hdc,TRANSPARENT); + if (!focused && es->scroll_x > 0 && es->scroll_x < 4) + es->scroll_x=0; + r.left+=2 - es->scroll_x; r.right-=2; + + const bool do_cursor = es->cursor_state!=0; + const int cursor_pos = focused ? utf8fs_charpos_to_bytepos(title,es->cursor_pos) : -1; + const int sel1 = es->sel1>=0 && focused ? utf8fs_charpos_to_bytepos(title,es->sel1) : -1; + const int sel2 = es->sel2>=0 && focused ? utf8fs_charpos_to_bytepos(title,es->sel2) : -1; + + const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0; + + if (multiline) + { + r.top+=2 - es->scroll_y; + const char *buf = title->Get(), *buf_end = buf + title->GetLength(); + int bytepos = 0; + RECT tmp={0,}; + const int line_h = DrawText(ps.hdc," ",1,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX); + const int wwrap = (hwnd->m_style & ES_AUTOHSCROLL) ? 0 : orig_r.right - g_swell_ctheme.scrollbar_width; + + int *use_cache = NULL, use_cache_len = 0; + const bool allow_cache = EDIT_ALLOW_MULTILINE_CACHE(wwrap,hwnd,title->GetLength()); + if (allow_cache && + es->cache_linelen_w == wwrap && + es->cache_linelen_strlen == title->GetLength()) + { + use_cache = es->cache_linelen_bytes.Get(); + use_cache_len = es->cache_linelen_bytes.GetSize(); + } + else + { + es->cache_linelen_w=allow_cache?wwrap:0; + es->cache_linelen_strlen=allow_cache?title->GetLength():0; + es->cache_linelen_bytes.Resize(0,false); + } + + for (;;) + { + int pskip=0, lb; + + const bool vis = r.top >= -line_h && r.top < orig_r.bottom; + + if (vis || !use_cache || use_cache_len < 1) + { + lb = swell_getLineLength(buf,&pskip,wwrap,ps.hdc); + if (!use_cache && allow_cache) + { + int s = lb+pskip; + es->cache_linelen_bytes.Add(&s,1); + } + } + else + { + lb = *use_cache; + if (WDL_NOT_NORMALLY(lb < 1)) break; + } + + if (use_cache) + { + use_cache++; + use_cache_len--; + } + + if (!*buf && cursor_pos != bytepos) break; + + if (WDL_NOT_NORMALLY(buf+lb+pskip > buf_end)) break; + + if (vis) + { + int wid = editControlPaintLine(ps.hdc,buf,lb, + (do_cursor && cursor_pos >= bytepos && cursor_pos <= bytepos + lb) ? cursor_pos - bytepos : -1, + sel1 >= 0 ? (sel1 - bytepos) : -1, + sel2 >= 0 ? (sel2 - bytepos) : -1, + &r, DT_TOP); + if (wid > es->max_width) es->max_width = wid; + } + + r.top += line_h; + + if (!*buf || !buf[lb]) break; + + bytepos += lb+pskip; + buf += lb+pskip; + } + r.top += es->scroll_y; + es->max_height = r.top; + if (es->max_width > r.right && (hwnd->m_style & ES_AUTOHSCROLL)) + { + drawHorizontalScrollbar(ps.hdc,orig_r, + orig_r.right-orig_r.left - g_swell_ctheme.scrollbar_width, + es->max_width,es->scroll_x); + orig_r.bottom -= g_swell_ctheme.scrollbar_width; + r.bottom -= g_swell_ctheme.scrollbar_width; + } + if (r.top > r.bottom) + { + drawVerticalScrollbar(ps.hdc,orig_r,es->max_height,es->scroll_y); + } + } + else + { + es->max_width = editControlPaintLine(ps.hdc, title->Get(), title->GetLength(), + do_cursor ? cursor_pos : -1, sel1, sel2, &r, DT_VCENTER); + + if (es->scroll_x > 0 && !is_secpass) + { + int max_x = es->max_width - (r.right-2 - (orig_r2.bottom-orig_r2.top)/2); + if (max_x < 0) max_x = 0; + if (es->scroll_x > max_x) + { + es->scroll_x = max_x; + r = orig_r = orig_r2; + is_secpass = true; + goto again; + } + } + } + + Draw3DBox(ps.hdc,&orig_r2,-1, g_swell_ctheme.edit_shadow, g_swell_ctheme.edit_hilight); + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_SETTEXT: + if (es) + { + es->cursor_pos = + (hwnd->m_style & (ES_READONLY|ES_MULTILINE)) == (ES_READONLY|ES_MULTILINE) ? 0 : + WDL_utf8_get_charlen(hwnd->m_title.Get()); + + es->sel1=es->sel2=-1; + es->cache_linelen_w=es->cache_linelen_strlen=0; + if ((hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL))==ES_AUTOHSCROLL && + GetFocus() != hwnd) + es->autoScrollToOffset(hwnd,0,false,false); + } + InvalidateRect(hwnd,NULL,FALSE); + if (hwnd->m_id && hwnd->m_parent) + SendMessage(hwnd->m_parent,WM_COMMAND,(EN_CHANGE<<16)|hwnd->m_id,(LPARAM)hwnd); + break; + case EM_REPLACESEL: + if (lParam && es) + { + const char *p = (const char *)lParam; + int pos = wdl_min(es->sel1,es->sel2); + es->deleteSelection(&hwnd->m_title); + if (*p) + { + if (pos < 0) pos = es->cursor_pos; + int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,pos); + hwnd->m_title.Insert(p,bytepos); + } + InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + case EM_GETSEL: + if (es) + { + if (wParam) * (int *)wParam = es->sel1; + if (lParam) * (int *)lParam = es->sel2; + if (es->sel1 < 0 || es->sel1 > 65535 || + es->sel2 < 0 || es->sel2 > 65535) return -1; + return MAKELPARAM(es->sel1,es->sel2); + } + return 0; + case EM_SETSEL: + if (es) + { + es->sel1 = (int)wParam; + es->sel2 = (int)lParam; + if (!es->sel1 && es->sel2 == -1) es->sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get()); + InvalidateRect(hwnd,NULL,FALSE); + if (es->sel2>=0) + es->autoScrollToOffset(hwnd,es->sel2, + (hwnd->m_style & ES_MULTILINE) != 0, + (hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE); + + } + return 0; + case EM_SCROLL: + if (es) + { + if (wParam == SB_TOP) + { + es->scroll_x=es->scroll_y=0; + InvalidateRect(hwnd,NULL,FALSE); + } + else if (wParam == SB_BOTTOM) + { + es->autoScrollToOffset(hwnd, + hwnd->m_title.GetLength() + 1, // this should be charpos rather than bytepos, but anything longer than m_title.GetLength() ensures we go to the absolute bottom + (hwnd->m_style & ES_MULTILINE) != 0, + (hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE); + InvalidateRect(hwnd,NULL,FALSE); + } + } + return 0; + case WM_KILLFOCUS: + SendMessage(GetParent(hwnd),WM_COMMAND,(EN_KILLFOCUS<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + case WM_SETFOCUS: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +static LRESULT WINAPI progressWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case PBM_DELTAPOS: + if (hwnd->m_private_data) *(int *)hwnd->m_private_data += (int) wParam; // todo: unsigned-ness conversion? unclear + InvalidateRect(hwnd,NULL,FALSE); + break; + case PBM_SETPOS: + if (hwnd->m_private_data) *(int *)hwnd->m_private_data = (int) wParam; + InvalidateRect(hwnd,NULL,FALSE); + break; + case PBM_SETRANGE: + if (hwnd->m_private_data) ((int *)hwnd->m_private_data)[1] = (int) lParam; + InvalidateRect(hwnd,NULL,FALSE); + break; + case WM_NCDESTROY: + free((int *)hwnd->m_private_data); + hwnd->m_private_data=0; + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + + paintDialogBackground(hwnd,&r,ps.hdc); + + if (hwnd->m_private_data) + { + int pos = *(int *)hwnd->m_private_data; + const int range = ((int *)hwnd->m_private_data)[1]; + const int low = LOWORD(range), high=HIWORD(range); + if (pos > low && high > low) + { + if (pos > high) pos=high; + int dx = ((pos-low)*r.right)/(high-low); + r.right = dx; + HBRUSH br = CreateSolidBrush(g_swell_ctheme.progress); + FillRect(ps.hdc,&r,br); + DeleteObject(br); + } + } + + EndPaint(hwnd,&ps); + } + } + break; + } + + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +static LRESULT WINAPI trackbarWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + const int track_h = 10; + static int s_cap_offs; + switch (msg) + { + case WM_CREATE: + { + int *p = (int *)hwnd->m_private_data; + if (p) + { + p[1] = (1000<<16); + p[2] = -1; + } + } + break; + case TBM_SETPOS: + if (hwnd->m_private_data) *(int *)hwnd->m_private_data = (int) lParam; + if (wParam) InvalidateRect(hwnd,NULL,FALSE); + break; + case TBM_GETPOS: + if (hwnd->m_private_data) return *(int *)hwnd->m_private_data; + return 0; + case TBM_SETRANGE: + if (hwnd->m_private_data) ((int *)hwnd->m_private_data)[1] = (int) lParam; + if (wParam) InvalidateRect(hwnd,NULL,FALSE); + break; + case TBM_SETTIC: + if (hwnd->m_private_data) ((int *)hwnd->m_private_data)[2] = (int) lParam; + break; + case WM_NCDESTROY: + free((int *)hwnd->m_private_data); + hwnd->m_private_data=0; + break; + case WM_LBUTTONDBLCLK: + if (hwnd->m_private_data) + { + int *state = (int *)hwnd->m_private_data; + const int range = state[1]; + const int low = LOWORD(range), high=HIWORD(range); + const int to_val = state[2] >= low && state[2] <= high ? state[2] : (low+high)/2; + if (state[0] != to_val) + { + state[0] = to_val; + InvalidateRect(hwnd,NULL,FALSE); + SendMessage(hwnd->m_parent,WM_HSCROLL,SB_ENDSCROLL,(LPARAM)hwnd); + } + } + return 1; + case WM_LBUTTONDOWN: + SetFocus(hwnd); + SetCapture(hwnd); + + if (hwnd->m_private_data) + { + RECT r; + GetClientRect(hwnd,&r); + const int rad = wdl_min((r.bottom-r.top)/2-1,track_h); + int *state = (int *)hwnd->m_private_data; + const int range = state[1]; + const int low = LOWORD(range), high=HIWORD(range); + const int dx = ((state[2]-low)*(r.right-2*rad))/(high-low) + rad; + s_cap_offs=0; + + if (GET_X_LPARAM(lParam) >= dx-rad && GET_X_LPARAM(lParam)<=dx-rad) + { + s_cap_offs = GET_X_LPARAM(lParam)-dx; + return 1; + } + // missed knob, so treat as a move + } + + case WM_MOUSEMOVE: + if (GetCapture()==hwnd && hwnd->m_private_data) + { + RECT r; + GetClientRect(hwnd,&r); + const int rad = wdl_min((r.bottom-r.top)/2-1,track_h); + int *state = (int *)hwnd->m_private_data; + const int range = state[1]; + const int low = LOWORD(range), high=HIWORD(range); + int xpos = GET_X_LPARAM(lParam) - s_cap_offs; + int use_range = (r.right-2*rad); + if (use_range > 0) + { + int newval = low + (xpos - rad) * (high-low) / use_range; + if (newval < low) newval=low; + else if (newval > high) newval=high; + if (newval != state[0]) + { + state[0]=newval; + InvalidateRect(hwnd,NULL,FALSE); + SendMessage(hwnd->m_parent,WM_HSCROLL,0,(LPARAM)hwnd); + } + } + } + return 1; + case WM_LBUTTONUP: + if (GetCapture()==hwnd) + { + ReleaseCapture(); + SendMessage(hwnd->m_parent,WM_HSCROLL,SB_ENDSCROLL,(LPARAM)hwnd); + } + return 1; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + + HBRUSH hbrush = (HBRUSH) SendMessage(GetParent(hwnd),WM_CTLCOLORSTATIC,(WPARAM)ps.hdc,(LPARAM)hwnd); + if (hbrush == (HBRUSH)(INT_PTR)1) hbrush = NULL; + else + { + if (hbrush) FillRect(ps.hdc,&r,hbrush); + else + { + SWELL_FillDialogBackground(ps.hdc,&r,3); + } + } + + HBRUSH br = CreateSolidBrush(g_swell_ctheme.trackbar_track); + const int rad = wdl_min((r.bottom-r.top)/2-1,track_h); + + RECT sr = r; + sr.left += rad; + sr.right -= rad; + sr.top = (r.top+r.bottom)/2 - rad/2; + sr.bottom = sr.top + rad; + FillRect(ps.hdc,&sr,br); + DeleteObject(br); + + sr.top = (r.top+r.bottom)/2 - rad; + sr.bottom = sr.top + rad*2; + + if (hwnd->m_private_data) + { + const int *state = (const int *)hwnd->m_private_data; + const int range = state[1]; + const int low = LOWORD(range), high=HIWORD(range); + if (high > low) + { + if (state[2] >= low && state[2] <= high) + { + const int dx = ((state[2]-low)*(r.right-2*rad))/(high-low) + rad; + HBRUSH markbr = CreateSolidBrush(g_swell_ctheme.trackbar_mark); + RECT tr = sr; + tr.left = dx; + tr.right = dx+1; + FillRect(ps.hdc,&tr,markbr); + DeleteObject(markbr); + } + int pos = state[0]; + if (pos < low) pos=low; + else if (pos > high) pos = high; + + const int dx = ((pos-low)*(r.right-2*rad))/(high-low) + rad; + + HBRUSH cbr = CreateSolidBrush(g_swell_ctheme.trackbar_knob); + HGDIOBJ oldbr = SelectObject(ps.hdc,cbr); + HGDIOBJ oldpen = SelectObject(ps.hdc,GetStockObject(NULL_PEN)); + Ellipse(ps.hdc, dx-rad, sr.top, dx+rad, sr.bottom); + SelectObject(ps.hdc,oldbr); + SelectObject(ps.hdc,oldpen); + DeleteObject(cbr); + } + } + + EndPaint(hwnd,&ps); + } + } + break; + case WM_SETFOCUS: + case WM_KILLFOCUS: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +static LRESULT WINAPI labelWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + + SetTextColor(ps.hdc, + hwnd->m_enabled ? g_swell_ctheme.label_text : + g_swell_ctheme.label_text_disabled); + SetBkMode(ps.hdc,TRANSPARENT); + + paintDialogBackground(hwnd,&r,ps.hdc); + + const char *text = hwnd->m_title.Get(); + const int f = (hwnd->m_style & SS_NOPREFIX) ? DT_NOPREFIX : 0; + switch (hwnd->m_style & SS_TYPEMASK) + { + case SS_ETCHEDHORZ: + case SS_ETCHEDVERT: + case SS_ETCHEDFRAME: + { + HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.group_hilight); + HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.group_shadow); + HGDIOBJ oldPen=SelectObject(ps.hdc,pen); + + switch (hwnd->m_style & SS_TYPEMASK) + { + case SS_ETCHEDHORZ: + MoveToEx(ps.hdc,0,1,NULL); + LineTo(ps.hdc,r.right-1,1); + SelectObject(ps.hdc,pen2); + MoveToEx(ps.hdc,0,0,NULL); + LineTo(ps.hdc,r.right-1,0); + break; + case SS_ETCHEDVERT: + MoveToEx(ps.hdc,1,0,NULL); + LineTo(ps.hdc,1,r.bottom-1); + SelectObject(ps.hdc,pen2); + MoveToEx(ps.hdc,0,0,NULL); + LineTo(ps.hdc,0,r.bottom-1); + break; + case SS_ETCHEDFRAME: + MoveToEx(ps.hdc,1,1,NULL); + LineTo(ps.hdc,1,r.bottom-1); + LineTo(ps.hdc,r.right-1,r.bottom-1); + LineTo(ps.hdc,r.right-1,1); + LineTo(ps.hdc,1,1); + SelectObject(ps.hdc,pen2); + MoveToEx(ps.hdc,0,0,NULL); + LineTo(ps.hdc,0,r.bottom-2); + LineTo(ps.hdc,r.right-2,r.bottom-2); + LineTo(ps.hdc,r.right-2,0); + LineTo(ps.hdc,0,0); + break; + } + + SelectObject(ps.hdc,oldPen); + DeleteObject(pen); + DeleteObject(pen2); + + text = ""; + } + break; + case SS_LEFT: + if (text[0]) + { + RECT tmp={0,}; + const int line_h = DrawText(ps.hdc," ",1,&tmp,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT|f); + if (r.bottom > line_h*5/3) + { + int loffs=0; + while (text[loffs] && r.top < r.bottom) + { + int post=0, lb=swell_getLineLength(text+loffs, &post, r.right, ps.hdc); + if (lb>0) + DrawText(ps.hdc,text+loffs,lb,&r,DT_TOP|DT_SINGLELINE|DT_LEFT|f); + r.top += line_h; + loffs+=lb+post; + } + text = ""; + } + } + break; + } + + if (text[0]) + { + if (strstr(text,"\n")) + { + RECT mr={0,}; + DrawText(ps.hdc,text,-1,&mr,DT_CALCRECT|f); + const int dsz = r.right-r.left - mr.right; + if (dsz > 0) + { + if (hwnd->m_style & SS_CENTER) r.left += dsz/2; + else if (hwnd->m_style & SS_RIGHT) r.left += dsz; + } + if (r.bottom-r.top > mr.bottom) r.top += (r.bottom-r.top-mr.bottom)/2; + DrawText(ps.hdc,text,-1,&r,f); + } + else + DrawText(ps.hdc,text,-1,&r, + ((hwnd->m_style & SS_CENTER) ? DT_CENTER : + (hwnd->m_style & SS_RIGHT) ? DT_RIGHT : 0)| + (DT_SINGLELINE|DT_VCENTER)|f); + } + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_SETTEXT: + InvalidateRect(hwnd,NULL,FALSE); + break; + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + if (hwnd->m_style & SS_NOTIFY) + SendMessage(GetParent(hwnd),WM_COMMAND, + ((msg==WM_LBUTTONDOWN?STN_CLICKED:STN_DBLCLK)<<16)|(hwnd->m_id&0xffff),0); + return 1; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +struct __SWELL_ComboBoxInternalState_rec +{ + __SWELL_ComboBoxInternalState_rec(const char *_desc=NULL, LPARAM _parm=0) { desc=_desc?strdup(_desc):NULL; parm=_parm; } + ~__SWELL_ComboBoxInternalState_rec() { free(desc); } + char *desc; + LPARAM parm; + static int cmp(const __SWELL_ComboBoxInternalState_rec **a, const __SWELL_ComboBoxInternalState_rec **b) { return strcmp((*a)->desc, (*b)->desc); } +}; + +class __SWELL_ComboBoxInternalState +{ + public: + __SWELL_ComboBoxInternalState() { selidx=-1; } + ~__SWELL_ComboBoxInternalState() { } + + int selidx; + WDL_PtrList_DeleteOnDestroy<__SWELL_ComboBoxInternalState_rec> items; + __SWELL_editControlState editstate; +}; + +static LRESULT WINAPI comboWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static const int buttonwid = 16; // used in edit combobox + static int s_capmode_state; + __SWELL_ComboBoxInternalState *s = (__SWELL_ComboBoxInternalState*)hwnd->m_private_data; + if (msg >= CB_ADDSTRING && msg <= CB_INITSTORAGE) + { + if (s) + { + switch (msg) + { + case CB_ADDSTRING: + + if (!(hwnd->m_style & CBS_SORT)) + { + s->items.Add(new __SWELL_ComboBoxInternalState_rec((const char *)lParam)); + return s->items.GetSize() - 1; + } + else + { + __SWELL_ComboBoxInternalState_rec *r=new __SWELL_ComboBoxInternalState_rec((const char *)lParam); + // find position of insert for wParam + bool m; + int idx = s->items.LowerBound(r,&m,__SWELL_ComboBoxInternalState_rec::cmp); + if (s->selidx >= idx) s->selidx++; + s->items.Insert(idx,r); + return idx; + } + + case CB_INSERTSTRING: + if (wParam > (WPARAM)s->items.GetSize()) wParam=(WPARAM)s->items.GetSize(); + s->items.Insert(wParam,new __SWELL_ComboBoxInternalState_rec((const char *)lParam)); + if (s->selidx >= (int)wParam) s->selidx++; + return wParam; + + case CB_DELETESTRING: + if (wParam >= (WPARAM)s->items.GetSize()) return CB_ERR; + + s->items.Delete(wParam,true); + + if (wParam == (WPARAM)s->selidx || s->selidx >= s->items.GetSize()) + { + s->selidx=-1; + SetWindowText(hwnd,""); + InvalidateRect(hwnd,NULL,FALSE); + } + else if ((int)wParam < s->selidx) s->selidx--; + + return s->items.GetSize(); + + case CB_GETCOUNT: return s->items.GetSize(); + case CB_GETCURSEL: return s->selidx >=0 && s->selidx < s->items.GetSize() ? s->selidx : -1; + + case CB_GETLBTEXTLEN: + case CB_GETLBTEXT: + if (wParam < (WPARAM)s->items.GetSize()) + { + __SWELL_ComboBoxInternalState_rec *rec = s->items.Get(wParam); + if (!rec) return CB_ERR; + const char *ptr=rec->desc; + int l = ptr ? strlen(ptr) : 0; + if (msg == CB_GETLBTEXT && lParam) memcpy((char *)lParam,ptr?ptr:"",l+1); + return l; + } + return CB_ERR; + case CB_RESETCONTENT: + s->selidx=-1; + s->items.Empty(true); + SetWindowText(hwnd,""); + return 0; + case CB_SETCURSEL: + if (wParam >= (WPARAM)s->items.GetSize()) + { + if (s->selidx!=-1) + { + s->selidx = -1; + SetWindowText(hwnd,""); + InvalidateRect(hwnd,NULL,FALSE); + } + return CB_ERR; + } + else + { + if (s->selidx != (int)wParam) + { + s->selidx=(int)wParam; + char *ptr=s->items.Get(wParam)->desc; + SetWindowText(hwnd,ptr); + InvalidateRect(hwnd,NULL,FALSE); + } + } + return s->selidx; + + case CB_GETITEMDATA: + if (wParam < (WPARAM)s->items.GetSize()) + { + return s->items.Get(wParam)->parm; + } + return CB_ERR; + case CB_SETITEMDATA: + if (wParam < (WPARAM)s->items.GetSize()) + { + s->items.Get(wParam)->parm=lParam; + return 0; + } + return CB_ERR; + case CB_INITSTORAGE: + return 0; + + case CB_FINDSTRINGEXACT: + case CB_FINDSTRING: + { + int x; + int a = (int)wParam; + a++; + for (x=a;xitems.GetSize();x++) + if (msg == CB_FINDSTRING ? + !stricmp(s->items.Get(x)->desc,(char *)lParam) : + !strcmp(s->items.Get(x)->desc,(char *)lParam)) return x; + + for (x=0;xitems.Get(x)->desc,(char *)lParam) : + !strcmp(s->items.Get(x)->desc,(char *)lParam)) return x; + } + return CB_ERR; + } + } + } + + switch (msg) + { + case WM_NCDESTROY: + hwnd->m_private_data=0; + delete s; + break; + case WM_TIMER: + if (wParam == 100) + { + if (++s->editstate.cursor_state >= EDIT_CURSOR_CYCLE_INTERVAL) s->editstate.cursor_state=0; + if (GetFocusIncludeMenus()!=hwnd || s->editstate.cursor_state<2) InvalidateRect(hwnd,NULL,FALSE); + } + else if (wParam==1) + { + if (!fast_has_focus(hwnd)) + { + KillTimer(hwnd,1); + InvalidateRect(hwnd,NULL,FALSE); + } + } + return 0; + + case WM_LBUTTONDOWN: + { + RECT r; + GetClientRect(hwnd,&r); + if ((hwnd->m_style & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST || GET_X_LPARAM(lParam) >= r.right-SWELL_UI_SCALE(buttonwid)) + { + s_capmode_state=5; + } + else + { + int xo=3; + HDC hdc=GetDC(hwnd); + const int last_cursor = s->editstate.cursor_pos; + s->editstate.cursor_pos = + WDL_utf8_bytepos_to_charpos(hwnd->m_title.Get(), + editHitTest(hdc,hwnd->m_title.Get(), + hwnd->m_title.GetLength(), + GET_X_LPARAM(lParam)-xo + s->editstate.scroll_x, + GET_Y_LPARAM(lParam),0,NULL,NULL) + ); + + s->editstate.onMouseDown(s_capmode_state,last_cursor); + + ReleaseDC(hwnd,hdc); + + SetFocus(hwnd); + } + SetCapture(hwnd); + } + InvalidateRect(hwnd,NULL,FALSE); + return 0; + case WM_MOUSEMOVE: + if (GetCapture()==hwnd) + { + if (s_capmode_state == 3 || s_capmode_state == 4) + { + const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0; + int xo=3; + HDC hdc=GetDC(hwnd); + int p = WDL_utf8_bytepos_to_charpos(hwnd->m_title.Get(), + editHitTest(hdc,hwnd->m_title.Get(), + multiline?-1:hwnd->m_title.GetLength(), + GET_X_LPARAM(lParam)-xo + s->editstate.scroll_x, + GET_Y_LPARAM(lParam),0,NULL,NULL) + ); + ReleaseDC(hwnd,hdc); + + s->editstate.onMouseDrag(s_capmode_state,p); + s->editstate.autoScrollToOffset(hwnd,p,false,false); + + InvalidateRect(hwnd,NULL,FALSE); + } + } + return 0; + case WM_LBUTTONUP: + if (GetCapture()==hwnd) + { + ReleaseCapture(); +popupMenu: + if (s && s->items.GetSize() && s_capmode_state == 5) + { + int x; + HMENU menu = CreatePopupMenu(); + for (x=0;xitems.GetSize();x++) + { + MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING, + (UINT) (x == s->selidx?MFS_CHECKED:0), + (UINT) (100+x), NULL,NULL,NULL,0,s->items.Get(x)->desc}; + InsertMenuItem(menu,x,TRUE,&mi); + } + + hwnd->Retain(); + RECT r; + GetWindowRect(hwnd,&r); + RECT req_r = { 1<<30, 1<<30, 0, s->selidx }; + int a = TrackPopupMenu(menu,TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTALIGN,r.left,r.bottom,0,hwnd,&req_r); + DestroyMenu(menu); + if (hwnd->m_private_data && a>=100 && a < s->items.GetSize()+100) + { + s->selidx = a-100; + char *ptr=s->items.Get(s->selidx)->desc; + SetWindowText(hwnd,ptr); + InvalidateRect(hwnd,NULL,FALSE); + SendMessage(GetParent(hwnd),WM_COMMAND,(GetWindowLong(hwnd,GWL_ID)&0xffff) | (CBN_SELCHANGE<<16),(LPARAM)hwnd); + } + hwnd->Release(); + } + } + s_capmode_state=0; + return 0; + case WM_LBUTTONDBLCLK: + if (s) + { + // technically this should select the word rather than all + s->editstate.sel1 = 0; + s->editstate.cursor_pos = s->editstate.sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get()); + InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + case WM_KEYDOWN: + if ((lParam&FVIRTKEY) && wParam == VK_DOWN) { s_capmode_state=5; goto popupMenu; } + if ((hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST && + OnEditKeyDown(hwnd,msg,wParam,lParam,false,false,&s->editstate,false)) + { + if (s) s->selidx=-1; // lookup from text? + SendMessage(GetParent(hwnd),WM_COMMAND,(CBN_EDITCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + s->editstate.autoScrollToOffset(hwnd,s->editstate.cursor_pos,false,false); + InvalidateRect(hwnd,NULL,FALSE); + return 0; + } + if (wParam == VK_SPACE) { s_capmode_state=5; goto popupMenu; } + break; + case WM_KEYUP: + return 0; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + bool pressed = s_capmode_state == 5 && GetCapture()==hwnd; + + SetTextColor(ps.hdc, + hwnd->m_enabled ? g_swell_ctheme.combo_text : + g_swell_ctheme.combo_text_disabled); + SetBkMode(ps.hdc,TRANSPARENT); + + Draw3DBox(ps.hdc,&r,g_swell_ctheme.combo_bg, + g_swell_ctheme.combo_hilight, + g_swell_ctheme.combo_shadow,pressed); + + int cursor_pos = -1; + bool focused = false; + if ((hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST) + { + focused = GetFocusIncludeMenus()==hwnd; + if (focused) + { + if (!s->editstate.cursor_timer) { SetTimer(hwnd,100,EDIT_CURSOR_BLINK_LEN,NULL); s->editstate.cursor_timer=1; } + } + else + { + if (s->editstate.cursor_timer) { KillTimer(hwnd,100); s->editstate.cursor_timer=0; } + } + + HBRUSH br = CreateSolidBrush(g_swell_ctheme.combo_bg2); + RECT tr=r; + const int pad = SWELL_UI_SCALE(2); + tr.left+=pad; tr.top+=pad; tr.bottom-=pad; tr.right -= SWELL_UI_SCALE(buttonwid+2); + FillRect(ps.hdc,&tr,br); + DeleteObject(br); + + if (focused && s->editstate.cursor_state) + { + cursor_pos = utf8fs_charpos_to_bytepos(&hwnd->m_title,s->editstate.cursor_pos); + } + } + + HBRUSH br = CreateSolidBrush(pressed?g_swell_ctheme.combo_arrow_press : g_swell_ctheme.combo_arrow); + HGDIOBJ oldbr=SelectObject(ps.hdc,br); + HGDIOBJ oldpen=SelectObject(ps.hdc,GetStockObject(NULL_PEN)); + const int dw = SWELL_UI_SCALE(8); + const int dh = SWELL_UI_SCALE(4); + const int cx = r.right-dw/2-SWELL_UI_SCALE(5); + const int cy = (r.bottom+r.top)/2; + + POINT pts[3] = { + { cx-dw/2,cy-dh/2 }, + { cx,cy+dh/2 }, + { cx+dw/2,cy-dh/2 } + }; + Polygon(ps.hdc,pts,3); + + SelectObject(ps.hdc,oldpen); + SelectObject(ps.hdc,oldbr); + DeleteObject(br); + + + if (pressed) + { + r.left+=SWELL_UI_SCALE(2); + r.top+=SWELL_UI_SCALE(2); + } + + r.left+=SWELL_UI_SCALE(3); + r.right-=SWELL_UI_SCALE(3); + + if ((hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST) + { + r.right -= SWELL_UI_SCALE(buttonwid+2); + editControlPaintLine(ps.hdc, hwnd->m_title.Get(), hwnd->m_title.GetLength(), + s->editstate.cursor_state!=0 ? cursor_pos : -1, + focused ? s->editstate.sel1 : -1, focused ? s->editstate.sel2 : -1, &r, DT_VCENTER); + } + else + { + char buf[512]; + buf[0]=0; + GetWindowText(hwnd,buf,sizeof(buf)); + if (buf[0]) DrawText(ps.hdc,buf,-1,&r,DT_VCENTER|DT_SINGLELINE); + } + + if (draw_focus_indicator(hwnd,ps.hdc,NULL)) + { + KillTimer(hwnd,1); + SetTimer(hwnd,1,100,NULL); + } + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_SETTEXT: + s->editstate.cursor_pos = WDL_utf8_get_charlen(hwnd->m_title.Get()); + s->editstate.sel1 = s->editstate.sel2 = -1; + case WM_CAPTURECHANGED: + InvalidateRect(hwnd,NULL,FALSE); + break; + case EM_GETSEL: + if (s && (hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST) + { + if (wParam) * (int *)wParam = s->editstate.sel1; + if (lParam) * (int *)lParam = s->editstate.sel2; + if (s->editstate.sel1 < 0 || s->editstate.sel1 > 65535 || + s->editstate.sel2 < 0 || s->editstate.sel2 > 65535) return -1; + return MAKELPARAM(s->editstate.sel1,s->editstate.sel2); + } + return 0; + case EM_SETSEL: + if (s && (hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST) + { + s->editstate.sel1 = (int)wParam; + s->editstate.sel2 = (int)lParam; + if (!s->editstate.sel1 && s->editstate.sel2 == -1) + s->editstate.sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get()); + if (s->editstate.sel2>=0) + s->editstate.autoScrollToOffset(hwnd,s->editstate.sel2, false, false); + InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + case WM_MOUSEWHEEL: + // pass modified mousewheel to parent (not sure if we need to do this but other controls do...) + if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break; + + if (s) + { + int amt = ((short)HIWORD(wParam)); + if (GetAsyncKeyState(VK_SHIFT)&0x8000) + { + if (amt<0) amt=-1; + else if (amt>0) amt=1; + } + else + amt /= 120; + + if (amt) + { + int nv = s->selidx + amt; + if (nv>=s->items.GetSize()) nv=s->items.GetSize()-1; + if (nv < 0) nv=0; + if (nv != s->selidx && s->items.Get(nv)) + { + s->selidx=nv; + SetWindowText(hwnd,s->items.Get(nv)->desc); + InvalidateRect(hwnd,NULL,FALSE); + SendMessage(GetParent(hwnd),WM_COMMAND,(GetWindowLong(hwnd,GWL_ID)&0xffff) | (CBN_SELCHANGE<<16),(LPARAM)hwnd); + } + } + } + + return 0; + case WM_SETFOCUS: + case WM_KILLFOCUS: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + + +/// these are for swell-dlggen.h +HWND SWELL_MakeButton(int def, const char *label, int idx, int x, int y, int w, int h, int flags) +{ + UINT_PTR a=(UINT_PTR)label; + if (a < 65536) label = "ICONTEMP"; + + RECT tr=MakeCoords(x,y,w,h,true); + HWND hwnd = swell_makeButton(m_make_owner,idx,&tr,label,!(flags&SWELL_NOT_WS_VISIBLE),(def ? BS_DEFPUSHBUTTON : 0) | (flags&BS_XPOSITION_MASK)); + + if (m_doautoright) UpdateAutoCoords(tr); + if (def) { } + return hwnd; +} + + +HWND SWELL_MakeLabel( int align, const char *label, int idx, int x, int y, int w, int h, int flags) +{ + RECT tr=MakeCoords(x,y,w,h,true); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,label, !(flags&SWELL_NOT_WS_VISIBLE),labelWindowProc); + hwnd->m_classname = "Static"; + if (align > 0) flags |= SS_RIGHT; + else if (align == 0) flags |= SS_CENTER; + hwnd->m_style = (flags & ~SWELL_NOT_WS_VISIBLE)|WS_CHILD; + hwnd->m_wantfocus = false; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + if (m_doautoright) UpdateAutoCoords(tr); + return hwnd; +} +HWND SWELL_MakeEditField(int idx, int x, int y, int w, int h, int flags) +{ + RECT tr=MakeCoords(x,y,w,h,true); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(flags&SWELL_NOT_WS_VISIBLE),editWindowProc); + hwnd->m_private_data = (INT_PTR) new __SWELL_editControlState; + hwnd->m_style = WS_CHILD | (flags & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = "Edit"; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + if (m_doautoright) UpdateAutoCoords(tr); + return hwnd; +} + + +HWND SWELL_MakeCheckBox(const char *name, int idx, int x, int y, int w, int h, int flags=0) +{ + return SWELL_MakeControl(name,idx,"Button",BS_AUTOCHECKBOX|flags,x,y,w,h,0); +} + +struct SWELL_ListView_Col +{ + char *name; + int xwid; + int sortindicator; + int col_index; + int fmt; // LVCFMT_ +}; + +enum { LISTVIEW_HDR_YMARGIN = 2 }; + +enum ListViewCapMode +{ + LISTVIEW_CAP_NONE=0, + LISTVIEW_CAP_XSCROLL, // data1=xp + LISTVIEW_CAP_YSCROLL, // data1=yp + LISTVIEW_CAP_DRAG, // data1=row, data2=displaycolumnindex + LISTVIEW_CAP_COLRESIZE, // data1 = displaycolumnindex, data2=xoffset + LISTVIEW_CAP_COLCLICK, // data1 = displaycolumnindex, data2=original lparam, advances to COLREORDER with same parameters + LISTVIEW_CAP_COLREORDER, +}; + +struct listViewState +{ + listViewState(bool ownerData, bool isMultiSel, bool isListBox) + { + m_selitem=-1; + m_is_multisel = isMultiSel; + m_is_listbox = isListBox; + m_owner_data_size = ownerData ? 0 : -1; + m_last_row_height = 0; + m_scroll_x=m_scroll_y=0; + m_capmode_state=LISTVIEW_CAP_NONE; + m_capmode_data1=0; + m_capmode_data2=0; + m_status_imagelist = NULL; + m_status_imagelist_type = 0; + m_extended_style=0; + + m_color_bg = g_swell_ctheme.listview_bg; + m_color_bg_sel = g_swell_ctheme.listview_bg_sel; + m_color_bg_sel_inactive = g_swell_ctheme.listview_bg_sel_inactive; + m_color_text = g_swell_ctheme.listview_text; + m_color_text_sel = g_swell_ctheme.listview_text_sel; + m_color_text_sel_inactive = g_swell_ctheme.listview_text_sel_inactive; + m_color_grid = g_swell_ctheme.listview_grid; + memset(m_color_extras,0xff,sizeof(m_color_extras)); // if !=-1, overrides bg/fg for (focus?0:2) + m_fastclick_mask = 0; + } + ~listViewState() + { + m_data.Empty(true); + const int n=m_cols.GetSize(); + for (int x=0;x m_data; + WDL_TypedBuf m_cols; + + + int GetColumnIndex(int dispindex) const + { + if (m_is_listbox || !m_cols.GetSize()) + { + WDL_ASSERT(dispindex==0); + return 0; + } + if (WDL_NORMALLY(dispindex>=0 && dispindex < m_cols.GetSize())) + { + WDL_ASSERT(m_cols.Get()[dispindex].col_index >= 0); + WDL_ASSERT(m_cols.Get()[dispindex].col_index < m_cols.GetSize()); + return m_cols.Get()[dispindex].col_index; + } + return 0; + } + SWELL_ListView_Col *GetColumnByIndex(int idx) const + { + SWELL_ListView_Col *c = m_cols.Get(); + const int n = m_cols.GetSize(); + for (int x = 0; x < n; x ++) if (c[x].col_index == idx) return c+x; + return NULL; + } + + int GetNumItems() const { return m_owner_data_size>=0 ? m_owner_data_size : m_data.GetSize(); } + bool IsOwnerData() const { return m_owner_data_size>=0; } + bool HasColumnHeaders(HWND hwnd) const + { + if (m_is_listbox || !m_cols.GetSize()) return false; + return !(hwnd->m_style & LVS_NOCOLUMNHEADER) && (hwnd->m_style & LVS_REPORT); + } + int GetColumnHeaderHeight(HWND hwnd) const { return HasColumnHeaders(hwnd) ? m_last_row_height+LISTVIEW_HDR_YMARGIN : 0; } + + int m_owner_data_size; // -1 if m_data valid, otherwise size + int m_last_row_height; + int m_selitem; // for single sel, or used for focus for multisel + + ListViewCapMode m_capmode_state; + int m_scroll_x,m_scroll_y, m_capmode_data1,m_capmode_data2; + int m_extended_style; + + int m_fastclick_mask; + + int m_color_bg, m_color_bg_sel, m_color_bg_sel_inactive, m_color_text, m_color_text_sel, m_color_text_sel_inactive, m_color_grid; + int m_color_extras[4]; + + int getTotalWidth() const + { + int s = 0; + const SWELL_ListView_Col *col = m_cols.Get(); + const int n = m_cols.GetSize(); + for (int x=0; x < n; x ++) s += col[x].xwid; + return s; + } + + void sanitizeScroll(HWND h) + { + RECT r; + GetClientRect(h,&r); + r.right -= g_swell_ctheme.scrollbar_width; + + const int mx = getTotalWidth() - r.right; + if (m_scroll_x > mx) m_scroll_x = mx; + if (m_scroll_x < 0) m_scroll_x = 0; + + if (m_last_row_height > 0) + { + r.bottom -= GetColumnHeaderHeight(h); + if (mx>0) r.bottom -= g_swell_ctheme.scrollbar_width; + + const int vh = m_last_row_height * GetNumItems(); + if (m_scroll_y < 0 || vh <= r.bottom) m_scroll_y=0; + else if (m_scroll_y > vh - r.bottom) m_scroll_y = vh - r.bottom; + } + } + + WDL_TypedBuf m_owner_multisel_state; + + bool get_sel(int idx) + { + if (!m_is_multisel) return idx>=0 && idx == m_selitem; + if (m_owner_data_size<0) + { + SWELL_ListView_Row *p = m_data.Get(idx); + return p && (p->m_tmp&1); + } + const unsigned int mask = 1<<(idx&31); + const int szn = idx/32; + const unsigned int *p=m_owner_multisel_state.Get(); + return p && idx>=0 && szn < m_owner_multisel_state.GetSize() && (p[szn]&mask); + } + bool set_sel(int idx, bool v) // returns true if value changed + { + if (!m_is_multisel) + { + const int oldsel = m_selitem; + if (v) m_selitem = idx; + else if (oldsel == idx) m_selitem = -1; + + return oldsel != m_selitem; + } + else if (m_owner_data_size<0) + { + SWELL_ListView_Row *p = m_data.Get(idx); + if (p) + { + const int oldtmp = p->m_tmp; + return (p->m_tmp = (v ? (oldtmp|1) : (oldtmp&~1))) != oldtmp; + } + } + else + { + if (idx>=0 && idx < m_owner_data_size) + { + const int szn = idx/32; + const int oldsz=m_owner_multisel_state.GetSize(); + unsigned int *p = m_owner_multisel_state.Get(); + if (oldszm_tmp; + if (*a&1) + { + *a&=~1; + rv=true; + } + } + return rv; + } + + bool rv=false; + int n = m_owner_multisel_state.GetSize(); + if (n > m_owner_data_size) n=m_owner_data_size; + for(int x=0;x *m_status_imagelist; + int m_status_imagelist_type; + + static int compareRows(const SWELL_ListView_Row **_a, const SWELL_ListView_Row **_b) + { + const char *a, *b; + if (!_a || !(a=(*_a)->get_col_txt(0))) a=""; + if (!_b || !(b=(*_b)->get_col_txt(0))) b=""; + return strcmp(a,b); + } +}; + +// returns non-NULL if a searching string occurred +static const char *stateStringOnKey(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (lParam & (FCONTROL|FALT|FLWIN)) return NULL; + if (uMsg != WM_KEYDOWN) return NULL; + static WDL_FastString str; + static DWORD last_t; + DWORD now = GetTickCount(); + if ((now-last_t) > 500) str.Set(""); + last_t = now; + + const bool is_numpad = wParam >= VK_NUMPAD0 && wParam <= VK_DIVIDE; + if ((lParam & FVIRTKEY) && wParam == VK_BACK) + { + str.SetLen(utf8fs_charpos_to_bytepos(&str,WDL_utf8_get_charlen(str.Get())-1)); + } + else if (wParam >= 32 && (!(lParam & FVIRTKEY) || swell_is_virtkey_char((int)wParam) || is_numpad)) + { + if (lParam & FVIRTKEY) + { + if (wParam >= 'A' && wParam <= 'Z') + { + if ((lParam&FSHIFT) ^ (swell_is_likely_capslock?0:FSHIFT)) wParam += 'a' - 'A'; + } + else if (is_numpad) + { + if (wParam <= VK_NUMPAD9) wParam += '0' - VK_NUMPAD0; + else wParam += '*' - VK_MULTIPLY; + } + } + + char b[8]; + WDL_MakeUTFChar(b,wParam,sizeof(b)); + str.Append(b); + return str.Get(); + } + + return NULL; +} + + +static LRESULT listViewWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + enum { col_resize_sz = 3 }; + listViewState *lvs = (listViewState *)hwnd->m_private_data; + static POINT s_clickpt; + switch (msg) + { + case WM_MOUSEWHEEL: + if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break; // pass modified mousewheel to parent + + { + const int amt = ((short)HIWORD(wParam))/40; + if (amt && lvs) + { + if (GetAsyncKeyState(VK_SHIFT)&0x8000) + { + const int oldscroll = lvs->m_scroll_x; + lvs->m_scroll_x -= amt*4; + lvs->sanitizeScroll(hwnd); + if (lvs->m_scroll_x != oldscroll) + InvalidateRect(hwnd,NULL,FALSE); + } + else + { + const int oldscroll = lvs->m_scroll_y; + lvs->m_scroll_y -= amt*lvs->m_last_row_height; + lvs->sanitizeScroll(hwnd); + if (lvs->m_scroll_y != oldscroll) + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + return 1; + case WM_RBUTTONDOWN: + if (lvs && lvs->m_last_row_height>0 && !lvs->m_is_listbox) + { + const int hdr_size = lvs->GetColumnHeaderHeight(hwnd); + if (hdr_size > 0 && GET_Y_LPARAM(lParam) < hdr_size) break; // let it get converted to WM_CONTEXTMENU + + LVHITTESTINFO inf = { { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }, }; + + const int row = ListView_SubItemHitTest(hwnd, &inf); + const int n = ListView_GetItemCount(hwnd); + if (row>=0 && rowm_last_row_height>0 && !lvs->m_is_listbox) + { + const int hdr_size = lvs->GetColumnHeaderHeight(hwnd); + if (hdr_size > 0 && GET_Y_LPARAM(lParam) < hdr_size) break; // let it get converted to WM_CONTEXTMENU + + LVHITTESTINFO inf = { { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }, }; + + const int row = ListView_SubItemHitTest(hwnd, &inf); + NMLISTVIEW nm={{hwnd,hwnd->m_id,NM_RCLICK},row,inf.iSubItem,0,0,0, {inf.pt.x,inf.pt.y}}; + if (SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm)) + return 1; + } + break; + case WM_SETCURSOR: + if (lvs) + { + POINT p; + GetCursorPos(&p); + ScreenToClient(hwnd,&p); + const int hdr_size = lvs->GetColumnHeaderHeight(hwnd); + if (p.y >= 0 && p.y < hdr_size) + { + const SWELL_ListView_Col *col = lvs->m_cols.Get(); + p.x += lvs->m_scroll_x; + if (lvs->hasStatusImage()) p.x -= lvs->m_last_row_height; + + for (int x=0; x < lvs->m_cols.GetSize(); x ++) + { + const int minw = wdl_max(col_resize_sz+1,col[x].xwid); + if (p.x >= minw-col_resize_sz && p.x < minw) + { + SetCursor(SWELL_LoadCursor(IDC_SIZEWE)); + return 1; + } + p.x -= col[x].xwid; + } + } + } + break; + case WM_LBUTTONDBLCLK: + case WM_LBUTTONDOWN: + SetFocus(hwnd); + if (msg == WM_LBUTTONDOWN) SetCapture(hwnd); + else ReleaseCapture(); + + if (lvs && lvs->m_last_row_height>0) + { + s_clickpt.x = GET_X_LPARAM(lParam); + s_clickpt.y = GET_Y_LPARAM(lParam); + RECT r; + GetClientRect(hwnd,&r); + const int hdr_size = lvs->GetColumnHeaderHeight(hwnd); + const int hdr_size_nomargin = hdr_size>0 ? hdr_size-LISTVIEW_HDR_YMARGIN : 0; + const int n=lvs->GetNumItems(); + const int row_height = lvs->m_last_row_height; + const int totalw = lvs->getTotalWidth(); + + if (hdr_size + n * row_height > r.bottom - g_swell_ctheme.scrollbar_width) + r.right -= g_swell_ctheme.scrollbar_width; + + if (GET_Y_LPARAM(lParam) >= 0 && GET_Y_LPARAM(lParam) < hdr_size) + { + const SWELL_ListView_Col *col = lvs->m_cols.Get(); + int px = GET_X_LPARAM(lParam) + lvs->m_scroll_x; + if (lvs->hasStatusImage()) px -= lvs->m_last_row_height; + for (int x=0; x < lvs->m_cols.GetSize(); x ++) + { + const int minw = wdl_max(col_resize_sz+1,col[x].xwid); + if (px >= minw-col_resize_sz && px < minw) + { + lvs->m_capmode_state = LISTVIEW_CAP_COLRESIZE; + lvs->m_capmode_data1 = x; + lvs->m_capmode_data2 = minw-px; + return 0; + } + + if (px >= 0 && px m_capmode_state = LISTVIEW_CAP_COLCLICK; + lvs->m_capmode_data1 = x; + lvs->m_capmode_data2 = (int)lParam; + } + return 0; + } + px -= col[x].xwid; + } + } + else if (totalw > r.right && + GET_Y_LPARAM(lParam) >= r.bottom - g_swell_ctheme.scrollbar_width) + { + const int xpos = GET_X_LPARAM(lParam); + int xp = xpos; + + int thumbsz, thumbpos; + calcScroll(r.right,totalw,lvs->m_scroll_x,&thumbsz,&thumbpos); + + if (xpos < thumbpos) xp = thumbpos; // jump on first mouse move + else if (xpos > thumbpos+thumbsz) xp = thumbpos + thumbsz; + + lvs->m_capmode_state = LISTVIEW_CAP_XSCROLL; + lvs->m_capmode_data1 = xp; + if (xpos < thumbpos || xpos > thumbpos+thumbsz) goto forceMouseMove; + return 0; + } + + lvs->m_capmode_state=LISTVIEW_CAP_NONE; + const int ypos = GET_Y_LPARAM(lParam) - hdr_size_nomargin; + + if (totalw > r.right) r.bottom -= g_swell_ctheme.scrollbar_width; + if (n * row_height > r.bottom - hdr_size_nomargin && + GET_X_LPARAM(lParam) >= r.right) + { + int yp = GET_Y_LPARAM(lParam); + + int thumbsz, thumbpos; + calcScroll(r.bottom - hdr_size_nomargin, n*row_height,lvs->m_scroll_y,&thumbsz,&thumbpos); + + if (ypos < thumbpos) yp = thumbpos + hdr_size_nomargin; // jump on first mouse move + else if (ypos > thumbpos+thumbsz) yp = thumbpos + hdr_size_nomargin + thumbsz; + + lvs->m_capmode_state = LISTVIEW_CAP_YSCROLL; + lvs->m_capmode_data1 = yp; + if (ypos < thumbpos || ypos > thumbpos+thumbsz) goto forceMouseMove; + return 0; + } + + const int hit = ypos >= 0 ? ((ypos+lvs->m_scroll_y) / row_height) : -1; + if (hit < 0) return 1; + + int subitem_col = 0; + int subitem = 0; // display index + + { + const int ncol=lvs->m_cols.GetSize(); + const bool has_image = lvs->hasStatusImage(); + int xpos=0, xpt = GET_X_LPARAM(lParam) + lvs->m_scroll_x; + if (has_image) xpos += lvs->m_last_row_height; + for (int x=0;xm_cols.Get()[x].xwid; + if (xpt >= xpos && xpt < xpos+xwid) + { + subitem_col = lvs->m_cols.Get()[x].col_index; + subitem = x; + break; + } + xpos += xwid; + } + } + + if ((subitem_col >= 0 && subitem_col < 32 && (lvs->m_fastclick_mask & (1u<m_status_imagelist && GET_X_LPARAM(lParam) + lvs->m_scroll_x < lvs->m_last_row_height) // image click + ) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,msg == WM_LBUTTONDBLCLK ? NM_DBLCLK : NM_CLICK},hit,lvs->GetColumnIndex(subitem),0,0,0, {s_clickpt.x, s_clickpt.y }}; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + if (hit >= 0 && hit < n) + { + lvs->m_capmode_state = LISTVIEW_CAP_DRAG; + lvs->m_capmode_data1 = hit; + lvs->m_capmode_data2 = subitem; + } + return 0; + } + + if (!lvs->m_is_multisel) + { + const int oldsel = lvs->m_selitem; + if (hit >= 0 && hit < n) lvs->m_selitem = hit; + else lvs->m_selitem = -1; + + if (lvs->m_is_listbox) + { + //if (oldsel != lvs->m_selitem) + SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + if (msg == WM_LBUTTONDBLCLK) + SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_DBLCLK<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + } + else + { + if (hit >= 0) + { + lvs->m_capmode_state = LISTVIEW_CAP_DRAG; + lvs->m_capmode_data1 = hit; + lvs->m_capmode_data2 = subitem; + } + + if(hit < n) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,msg == WM_LBUTTONDBLCLK ? NM_DBLCLK : NM_CLICK},hit,lvs->GetColumnIndex(subitem),0,0,0, {s_clickpt.x, s_clickpt.y }}; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + if (oldsel != lvs->m_selitem) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},lvs->m_selitem,1,LVIS_SELECTED,}; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + } + InvalidateRect(hwnd,NULL,FALSE); + } + else + { + bool changed = false; + if (hit >= n) + { + changed |= lvs->clear_sel(); + lvs->m_selitem = -1; + } + else + { + const bool ctrl = (GetAsyncKeyState(VK_CONTROL)&0x8000)!=0; + if (!ctrl) + { + if (!lvs->get_sel(hit)) + changed |= lvs->clear_sel(); + } + if ((GetAsyncKeyState(VK_SHIFT)&0x8000) && lvs->m_selitem >= 0) + { + int a=lvs->m_selitem; + int b = hit; + if (a>b) { b=a; a=hit; } + while (a<=b) changed |= lvs->set_sel(a++,true); + } + else + { + lvs->m_selitem = hit; + changed |= lvs->set_sel(hit,!ctrl || !lvs->get_sel(hit)); + } + } + + if (lvs->m_is_listbox) + { + if (changed) SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + if (msg == WM_LBUTTONDBLCLK) + SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_DBLCLK<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + } + else + { + if (hit >=0 && hit < n) + { + lvs->m_capmode_state = LISTVIEW_CAP_DRAG; + lvs->m_capmode_data1 = hit; + lvs->m_capmode_data2 = subitem; + NMLISTVIEW nm={{hwnd,hwnd->m_id,msg == WM_LBUTTONDBLCLK ? NM_DBLCLK : NM_CLICK},hit,lvs->GetColumnIndex(subitem),LVIS_SELECTED,}; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + if (changed) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},hit,0,LVIS_SELECTED,}; + if (nm.iItem < 0 || nm.iItem >= n) nm.iItem=0; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + } + + InvalidateRect(hwnd,NULL,FALSE); + } + } + return 1; + case WM_MOUSEMOVE: + if (GetCapture()==hwnd && lvs) + { +forceMouseMove: + RECT r; + GetClientRect(hwnd,&r); + r.right -= g_swell_ctheme.scrollbar_width; + switch (lvs->m_capmode_state) + { + case LISTVIEW_CAP_NONE: + break; + case LISTVIEW_CAP_COLRESIZE: + { + int x = lvs->m_capmode_data1; + int xp = GET_X_LPARAM(lParam) + lvs->m_scroll_x + lvs->m_capmode_data2; + if (lvs->hasStatusImage()) xp -= lvs->m_last_row_height; + + SWELL_ListView_Col *col = lvs->m_cols.Get(); + if (x < lvs->m_cols.GetSize()) + { + for (int i = 0; i < x; i ++) xp -= col[i].xwid; + if (xp<0) xp=0; + if (col[x].xwid != xp) + { + col[x].xwid = xp; + if (lvs->m_scroll_x > 0 && GET_X_LPARAM(lParam) < 0) + lvs->m_scroll_x--; + else if (GET_X_LPARAM(lParam) > r.right+12) + lvs->m_scroll_x+=16; // additional check might not be necessary? + + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + break; + case LISTVIEW_CAP_DRAG: + if (!lvs->m_is_listbox) + { + const int dx = GET_X_LPARAM(lParam) - s_clickpt.x, dy = GET_Y_LPARAM(lParam) - s_clickpt.y; + if (dx*dx+dy*dy > 32) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_BEGINDRAG},lvs->m_capmode_data1,lvs->GetColumnIndex(lvs->m_capmode_data2)}; + lvs->m_capmode_state=LISTVIEW_CAP_NONE; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + } + break; + case LISTVIEW_CAP_YSCROLL: + { + int yv = lvs->m_capmode_data1; + int amt = GET_Y_LPARAM(lParam) - yv; + + if (amt) + { + const int totalw = lvs->getTotalWidth(); + if (totalw > r.right) r.bottom -= lvs->m_last_row_height; + + + const int viewsz = r.bottom-lvs->GetColumnHeaderHeight(hwnd); + const double totalsz=(double)lvs->GetNumItems() * (double)lvs->m_last_row_height; + amt = (int)floor(amt * totalsz / (double)viewsz + 0.5); + + const int oldscroll = lvs->m_scroll_y; + lvs->m_scroll_y += amt; + lvs->sanitizeScroll(hwnd); + if (lvs->m_scroll_y != oldscroll) + { + lvs->m_capmode_data1 = GET_Y_LPARAM(lParam); + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + break; + case LISTVIEW_CAP_XSCROLL: + { + int xv = lvs->m_capmode_data1; + int amt = GET_X_LPARAM(lParam) - xv; + + if (amt) + { + const int viewsz = r.right; + const double totalsz=(double)lvs->getTotalWidth(); + amt = (int)floor(amt * totalsz / (double)viewsz + 0.5); + + const int oldscroll = lvs->m_scroll_x; + lvs->m_scroll_x += amt; + lvs->sanitizeScroll(hwnd); + if (lvs->m_scroll_x != oldscroll) + { + lvs->m_capmode_data1 = GET_X_LPARAM(lParam); + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + break; + case LISTVIEW_CAP_COLCLICK: + if (lvs->m_extended_style & LVS_EX_HEADERDRAGDROP) + { + const int xd = GET_X_LPARAM(lParam) - GET_X_LPARAM((LPARAM)lvs->m_capmode_data2), + yd = GET_Y_LPARAM(lParam) - GET_Y_LPARAM((LPARAM)lvs->m_capmode_data2); + if (xd*xd + yd*yd < 4*4) break; + lvs->m_capmode_state = LISTVIEW_CAP_COLREORDER; + } + case LISTVIEW_CAP_COLREORDER: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + } + return 1; + case WM_LBUTTONUP: + if (GetCapture()==hwnd) + { + if (lvs->m_capmode_state == LISTVIEW_CAP_COLCLICK) + { + HWND par = hwnd->m_parent; + if (par) + { + NMLISTVIEW hdr={{hwnd,(UINT_PTR)hwnd->m_id,LVN_COLUMNCLICK},-1, lvs->GetColumnIndex(lvs->m_capmode_data1) }; + if (par->m_wndproc&&!par->m_hashaddestroy) par->m_wndproc(par,WM_NOTIFY,hwnd->m_id, (LPARAM) &hdr); + } + } + else if (lvs->m_capmode_state == LISTVIEW_CAP_COLREORDER) + { + const int oldidx = lvs->m_capmode_data1; + const int ncols=lvs->m_cols.GetSize(); + if (oldidx >= 0 && oldidx < ncols) + { + POINT p; + GetCursorPos(&p); + ScreenToClient(hwnd,&p); + int xpos=-lvs->m_scroll_x; + SWELL_ListView_Col *cols = lvs->m_cols.Get(); + int x; + for (x = 0; x < ncols; x ++) + { + int xwid = cols[x].xwid; + if (!x && lvs->hasStatusImage()) xwid += lvs->m_last_row_height; + if (p.x < xpos + xwid/2) break; + xpos += xwid; + } + + if (x != oldidx && x != oldidx+1) + { + SWELL_ListView_Col c = cols[oldidx]; + lvs->m_cols.Delete(oldidx); + + if (x > oldidx) x--; + lvs->m_cols.Insert(c,x); + + } + } + } + ReleaseCapture(); + InvalidateRect(hwnd,NULL,FALSE); + } + return 1; + case WM_KEYDOWN: + if (lvs) + { + const char *s = stateStringOnKey(msg,wParam,lParam); + if (s) + { + int col = 0; // column index (not display) + if (!lvs->m_is_listbox) + { + for (int x=0;xm_cols.GetSize();x++) + { + if (lvs->m_cols.Get()[x].sortindicator) + { + col = lvs->m_cols.Get()[x].col_index; + break; + } + } + } + + const int n = lvs->GetNumItems(); + int selitem = lvs->m_selitem; + if (selitem < 0 || selitem >= n) selitem=0; + for (int x=0;xIsOwnerData()) + { + SWELL_ListView_Row *row = lvs->m_data.Get(offs); + if (row) v = row->get_col_txt(col); + } + else + { + buf[0]=0; + NMLVDISPINFO nm={{hwnd,hwnd->m_id,LVN_GETDISPINFO},{LVIF_TEXT, offs,col, 0,0, buf, sizeof(buf), -1 }}; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + v = nm.item.pszText; + } + + if (v && !strnicmp(v,s,strlen(s))) + { + if (!lvs->m_is_multisel) + { + const int oldsel = lvs->m_selitem; + lvs->m_selitem = offs; + + if (lvs->m_is_listbox) + { + SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + } + else + { + if (oldsel != lvs->m_selitem) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},lvs->m_selitem,1,LVIS_SELECTED,}; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + } + } + else + { + bool changed = lvs->clear_sel() | lvs->set_sel(offs,true); + lvs->m_selitem = offs; + + if (lvs->m_is_listbox) + { + if (changed) SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + } + else + { + if (changed) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},offs,0,LVIS_SELECTED,}; + if (nm.iItem < 0 || nm.iItem >= n) nm.iItem=0; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + } + } + + ListView_EnsureVisible(hwnd,offs,FALSE); + InvalidateRect(hwnd,NULL,FALSE); + break; + } + } + } + } + if (lvs && (lParam & FVIRTKEY)) + { + int flag=0; + int ni; + const int oldsel = lvs->m_selitem; + + switch (wParam) + { + case VK_NEXT: + case VK_PRIOR: + case VK_UP: + case VK_DOWN: + { + RECT r; + GetClientRect(hwnd,&r); + const int page = lvs->m_last_row_height ? + (r.bottom - g_swell_ctheme.scrollbar_width)/lvs->m_last_row_height : 4; + const int cnt = lvs->GetNumItems(); + + ni = lvs->m_selitem + (wParam == VK_UP ? -1 : + wParam == VK_PRIOR ? 2-wdl_max(page,3) : + wParam == VK_NEXT ? wdl_max(page,3)-2 : + 1); + + if (ni<0) ni=0; + if (ni>cnt-1) ni=cnt-1; + const bool shift = (GetAsyncKeyState(VK_SHIFT)&0x8000)!=0; + + if (ni>=0 && (ni!=lvs->m_selitem || !shift)) + { + if (lvs->m_is_multisel) + { + if (!shift) + { + lvs->clear_sel(); + lvs->set_sel(ni,true); + } + else + { + if (lvs->get_sel(ni)) lvs->set_sel(lvs->m_selitem,false); + else lvs->set_sel(ni,true); + } + } + lvs->m_selitem=ni; + flag|=3; + } + } + flag|=4; + break; + case VK_HOME: + case VK_END: + ni = wParam == VK_HOME ? 0 : lvs->GetNumItems()-1; + if (ni != lvs->m_selitem) + { + if (lvs->m_is_multisel) + { + if (!(GetAsyncKeyState(VK_SHIFT)&0x8000)) + { + lvs->clear_sel(); + lvs->set_sel(ni,true); + } + else + { + if (wParam == VK_HOME) + { + for (ni = lvs->m_selitem; ni >= 0; ni--) + lvs->set_sel(ni,true); + } + else + { + for (int x=wdl_max(0,lvs->m_selitem); x <= ni; x++) + lvs->set_sel(x,true); + } + } + } + lvs->m_selitem=ni; + flag|=3; + } + flag|=4; + break; + } + if (flag) + { + if (flag & 2) + { + if (lvs->m_is_listbox) + { + if (oldsel != lvs->m_selitem) + SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd); + } + else + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},lvs->m_selitem,0,LVIS_SELECTED,}; + SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + } + ListView_EnsureVisible(hwnd,lvs->m_selitem,FALSE); + } + if (flag&1) InvalidateRect(hwnd,NULL,FALSE); + + return 0; + } + } + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT cr; + GetClientRect(hwnd,&cr); + RECT rr = cr; + HBRUSH bgbr = CreateSolidBrush(lvs->m_color_bg); + FillRect(ps.hdc,&cr,bgbr); + DeleteObject(bgbr); + const bool focused = GetFocus() == hwnd; + const int bgsel = lvs->m_color_extras[focused ? 0 : 2 ]; + bgbr=CreateSolidBrush(bgsel == -1 ? focused ? lvs->m_color_bg_sel : lvs->m_color_bg_sel_inactive : bgsel); + if (lvs) + { + TEXTMETRIC tm; + GetTextMetrics(ps.hdc,&tm); + const int row_height = tm.tmHeight + 4; + lvs->m_last_row_height = row_height; + lvs->sanitizeScroll(hwnd); + + const bool owner_data = lvs->IsOwnerData(); + const int nrows = owner_data ? lvs->m_owner_data_size : lvs->m_data.GetSize(); + const int hdr_size = lvs->GetColumnHeaderHeight(hwnd); + const int hdr_size_nomargin = hdr_size>0 ? hdr_size-LISTVIEW_HDR_YMARGIN : 0; + int ypos = hdr_size - lvs->m_scroll_y; + + SetBkMode(ps.hdc,TRANSPARENT); + const int ncols = lvs->m_cols.GetSize(); + const int nc = wdl_max(ncols,1); + const SWELL_ListView_Col *cols = lvs->m_cols.Get(); + + const bool has_image = lvs->hasAnyImage(); + const bool has_status_image = lvs->hasStatusImage(); + const bool has_subitem_image = (lvs->m_extended_style & LVS_EX_SUBITEMIMAGES)!=0; + const int xo = lvs->m_scroll_x; + + const int totalw = lvs->getTotalWidth(); + + const bool vscroll_area = hdr_size + nrows * row_height > cr.bottom - g_swell_ctheme.scrollbar_width; + const bool hscroll = totalw > cr.right - (vscroll_area ? g_swell_ctheme.scrollbar_width : 0); + if (hscroll) + cr.bottom -= g_swell_ctheme.scrollbar_width; + + HPEN gridpen = NULL; + HGDIOBJ oldpen = NULL; + if (!lvs->m_is_listbox && (hwnd->m_style & LVS_REPORT) && (lvs->m_extended_style&LVS_EX_GRIDLINES)) + { + gridpen = CreatePen(PS_SOLID,0,lvs->m_color_grid); + oldpen = SelectObject(ps.hdc,gridpen); + } + + HWND par = GetParent(hwnd); + NMLVCUSTOMDRAW nmlvcd={ { {hwnd,(UINT_PTR)hwnd->m_id, NM_CUSTOMDRAW}, CDDS_ITEMPREPAINT,ps.hdc, } }; + for (int rowidx = 0; rowidx < nrows && ypos < cr.bottom; rowidx ++) + { + const char *str = NULL; + char buf[4096]; + + bool sel; + { + RECT tr={cr.left,ypos,cr.right,ypos + row_height}; + if (tr.bottom < hdr_size) + { + ypos += row_height; + continue; + } + + sel = lvs->get_sel(rowidx); + if (sel) + { + FillRect(ps.hdc,&tr,bgbr); + } + } + + COLORREF text_c; + if (sel) + { + int c = lvs->m_color_extras[focused ? 1 : 3 ]; + text_c = c == -1 ? focused ? lvs->m_color_text_sel : lvs->m_color_text_sel_inactive : c; + } + else + text_c = lvs->m_color_text; + + nmlvcd.nmcd.dwItemSpec = (DWORD)rowidx; + SWELL_ListView_Row *row = lvs->m_data.Get(rowidx); + int xpos=-xo; + for (int col = 0; col < nc && xpos < cr.right; col ++) + { + const int col_idx = lvs->GetColumnIndex(col); + + nmlvcd.iSubItem = col_idx; + nmlvcd.clrText = text_c; + SendMessage(par,WM_NOTIFY,hwnd->m_id&0xffff,(LPARAM)&nmlvcd); + SetTextColor(ps.hdc, nmlvcd.clrText); + + int image_idx = 0; + if (owner_data) + { + NMLVDISPINFO nm={{hwnd,hwnd->m_id,LVN_GETDISPINFO},{LVIF_TEXT, rowidx,col_idx, 0,0, buf, sizeof(buf), -1 }}; + if ((!col || has_subitem_image) && has_image) + { + if (lvs->m_status_imagelist_type == LVSIL_STATE) nm.item.mask |= LVIF_STATE; + else if (lvs->m_status_imagelist_type == LVSIL_SMALL) nm.item.mask |= LVIF_IMAGE; + } + buf[0]=0; + SendMessage(par,WM_NOTIFY,hwnd->m_id,(LPARAM)&nm); + str=nm.item.pszText; + if ((!col || has_subitem_image) && has_image) + { + if (lvs->m_status_imagelist_type == LVSIL_STATE) image_idx=STATEIMAGEMASKTOINDEX(nm.item.state); + else if (lvs->m_status_imagelist_type == LVSIL_SMALL) image_idx = nm.item.iImage + 1; + } + } + else + { + if (!col && has_image) image_idx = row->get_img_idx(0); + if (row) str = row->get_col_txt(col_idx); + } + + RECT ar = { xpos,ypos, cr.right, ypos + row_height }; + if ((!col || has_subitem_image) && has_image) + { + if (image_idx>0) + { + HICON icon = lvs->m_status_imagelist->Get(image_idx-1); + if (icon) + { + if (has_status_image || col >= ncols) + ar.right = ar.left + row_height; + else + ar.right = ar.left + wdl_min(row_height,cols[col].xwid); + DrawImageInRect(ps.hdc,icon,&ar); + } + } + if (has_status_image) + { + xpos += row_height; + ar.left += row_height; + } + else if (image_idx > 0) + { + ar.left += row_height; + } + } + + if (lvs->m_is_listbox && (hwnd->m_style & LBS_OWNERDRAWFIXED)) + { + if (hwnd->m_parent) + { + DRAWITEMSTRUCT dis = { ODT_LISTBOX, hwnd->m_id, (UINT)rowidx, 0, + (UINT)(sel?ODS_SELECTED:0),hwnd,ps.hdc,ar,row?(DWORD_PTR)row->m_param:0 }; + dis.rcItem.left++; + if (cr.bottom-cr.top < nrows*row_height) + dis.rcItem.right -= g_swell_ctheme.scrollbar_width; + SendMessage(hwnd->m_parent,WM_DRAWITEM,(WPARAM)hwnd->m_id,(LPARAM)&dis); + } + } + else + { + if (ncols > 0) + { + ar.right = xpos + cols[col].xwid - SWELL_UI_SCALE(3); + xpos += cols[col].xwid; + } + else ar.right = cr.right; + + if (ar.right > ar.left && str) + { + const int adj = (ar.right-ar.left)/16; + const int maxadj = SWELL_UI_SCALE(4); + int fmt = ncols > 0 ? cols[col].fmt & 3 : LVCFMT_LEFT; + if (fmt != LVCFMT_LEFT) + { + RECT mr={0,}; + DrawText(ps.hdc,str,-1,&mr,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX); + if (mr.right-mr.left > ar.right-ar.left-maxadj) fmt = LVCFMT_LEFT; + } + + if (fmt == LVCFMT_CENTER) + { + ar.left += maxadj/2; + ar.right -= maxadj/2; + DrawText(ps.hdc,str,-1,&ar,DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + } + else if (fmt == LVCFMT_RIGHT) + { + ar.right -= wdl_min(adj,maxadj); + DrawText(ps.hdc,str,-1,&ar,DT_RIGHT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + } + else + { + ar.left += wdl_min(adj,maxadj); + DrawText(ps.hdc,str,-1,&ar,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + } + } + } + } + ypos += row_height; + if (gridpen) + { + MoveToEx(ps.hdc,0,ypos-1,NULL); + LineTo(ps.hdc,cr.right,ypos-1); + } + } + if (gridpen) + { + if (row_height>0) for (;;) + { + ypos += row_height; + if (ypos >= cr.bottom) break; + MoveToEx(ps.hdc,0,ypos-1,NULL); + LineTo(ps.hdc,cr.right,ypos-1); + } + int xpos=(has_status_image ? row_height : 0) - xo; + for (int col=0; col < ncols; col ++) + { + xpos += cols[col].xwid; + if (xpos > cr.right) break; + MoveToEx(ps.hdc,xpos-1,hdr_size_nomargin,NULL); + LineTo(ps.hdc,xpos-1,cr.bottom); + } + } + if (hdr_size_nomargin>0) + { + HBRUSH br = CreateSolidBrush(g_swell_ctheme.listview_hdr_bg); + int xpos=(has_status_image ? row_height : 0) - xo; + ypos=0; + SetTextColor(ps.hdc,g_swell_ctheme.listview_hdr_text); + + if (xpos>0) + { + RECT tr={0,ypos,xpos,ypos+hdr_size_nomargin }; + FillRect(ps.hdc,&tr,br); + } + for (int col=0; col < ncols; col ++) + { + RECT tr={xpos,ypos,0,ypos + hdr_size_nomargin }; + xpos += cols[col].xwid; + tr.right = xpos; + + if (tr.right > tr.left) + { + Draw3DBox(ps.hdc,&tr, + g_swell_ctheme.listview_hdr_bg, + g_swell_ctheme.listview_hdr_hilight, + g_swell_ctheme.listview_hdr_shadow); + + if (cols[col].sortindicator != 0) + { + const int tsz = (tr.bottom-tr.top)/4; + if (tr.right > tr.left + 2*tsz) + { + const int x1 = tr.left + 2; + int y2 = (tr.bottom+tr.top)/2 - tsz/2 - tsz/4; + int y1 = y2 + tsz; + if (cols[col].sortindicator >= 0) + { + int tmp=y1; + y1=y2; + y2=tmp; + } + HBRUSH hdrbr = CreateSolidBrush(g_swell_ctheme.listview_hdr_arrow); + HGDIOBJ oldBrush = SelectObject(ps.hdc,hdrbr); + HGDIOBJ oldPen = SelectObject(ps.hdc,GetStockObject(NULL_PEN)); + + POINT pts[3] = {{x1,y1}, {x1+tsz*2,y1}, {x1+tsz,y2}}; + Polygon(ps.hdc,pts,3); + SelectObject(ps.hdc,oldBrush); + SelectObject(ps.hdc,oldPen); + DeleteObject(hdrbr); + tr.left = x1 + tsz*2; + } + } + + if (cols[col].name) + { + const int maxadj = SWELL_UI_SCALE(4); + int fmt = cols[col].fmt & 3; + if (fmt != LVCFMT_LEFT) + { + RECT mr={0,}; + DrawText(ps.hdc,cols[col].name,-1,&mr,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT); + if (mr.right - mr.left > tr.right-tr.left-maxadj) fmt = LVCFMT_LEFT; + } + if (fmt == LVCFMT_CENTER) + { + tr.left += maxadj/2; + tr.right -= maxadj/2; + DrawText(ps.hdc,cols[col].name,-1,&tr,DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + } + else if (fmt == LVCFMT_RIGHT) + { + tr.right -= wdl_min((tr.right-tr.left)/4,maxadj); + DrawText(ps.hdc,cols[col].name,-1,&tr,DT_RIGHT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + } + else + { + tr.left += wdl_min((tr.right-tr.left)/4,maxadj); + DrawText(ps.hdc,cols[col].name,-1,&tr,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + } + } + } + if (xpos >= cr.right) break; + } + if (xpos < cr.right) + { + RECT tr={xpos,ypos,cr.right,ypos+hdr_size_nomargin }; + FillRect(ps.hdc,&tr,br); + } + DeleteObject(br); + if (lvs->m_capmode_state == LISTVIEW_CAP_COLREORDER && GetCapture()==hwnd) + { + const int oldidx = lvs->m_capmode_data1; + if (oldidx >= 0 && oldidx < ncols) + { + POINT p; + GetCursorPos(&p); + ScreenToClient(hwnd,&p); + xpos=-lvs->m_scroll_x; + int x; + for (x = 0; x < ncols; x ++) + { + int xwid = cols[x].xwid; + if (!x && lvs->hasStatusImage()) xwid += lvs->m_last_row_height; + if (p.x < xpos + xwid/2) break; + xpos += xwid; + } + + if (x != oldidx && x != oldidx+1) + { + const char *s = cols[lvs->m_capmode_data1].name; + int sz = cols[lvs->m_capmode_data1].xwid; + if (sz < hdr_size_nomargin) sz = hdr_size_nomargin; + br = CreateSolidBrushAlpha(g_swell_ctheme.focusrect,0.75f); + RECT r = {xpos, ypos, xpos + sz, ypos + hdr_size_nomargin }; + FillRect(ps.hdc, &r, br); + if (s) DrawText(ps.hdc,s,-1,&r,DT_SINGLELINE|DT_LEFT|DT_VCENTER|DT_NOPREFIX); + DeleteObject(br); + } + } + } + } + if (gridpen) + { + SelectObject(ps.hdc,oldpen); + DeleteObject(gridpen); + } + + cr.top += hdr_size_nomargin; + drawVerticalScrollbar(ps.hdc,cr,nrows*row_height,lvs->m_scroll_y); + + if (hscroll) + { + cr.bottom += g_swell_ctheme.scrollbar_width; + drawHorizontalScrollbar(ps.hdc,cr, + cr.right-cr.left - (vscroll_area ? g_swell_ctheme.scrollbar_width : 0), + totalw,lvs->m_scroll_x); + } + } + Draw3DBox(ps.hdc,&rr,-1, + g_swell_ctheme.listview_hilight, + g_swell_ctheme.listview_shadow); + DeleteObject(bgbr); + + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_NCDESTROY: + hwnd->m_private_data = 0; + delete lvs; + break; + case LB_ADDSTRING: + case LB_INSERTSTRING: + if (lvs && !lvs->IsOwnerData()) + { + SWELL_ListView_Row *row=new SWELL_ListView_Row; + row->add_col((const char *)lParam); + int idx; + if (msg == LB_ADDSTRING && hwnd->m_style & LBS_SORT) + { + bool isMatch; + idx=lvs->m_data.LowerBound(row,&isMatch,listViewState::compareRows); + } + else if (msg == LB_INSERTSTRING) + { + idx=(int)wParam; + if (idx<0 || idx>lvs->m_data.GetSize()) idx=lvs->m_data.GetSize(); + } + else + idx=lvs->m_data.GetSize(); + lvs->m_data.Insert(idx,row); + InvalidateRect(hwnd,NULL,FALSE); + return idx; + } + return LB_ERR; + case LB_DELETESTRING: + if (lvs && !lvs->IsOwnerData()) + { + int idx = (int) wParam; + if (idx<0 || idx>=lvs->m_data.GetSize()) return LB_ERR; + lvs->m_data.Delete(idx,true); + InvalidateRect(hwnd,NULL,FALSE); + return lvs->m_data.GetSize(); + } + return LB_ERR; + case LB_GETTEXT: + if (!lParam) return LB_ERR; + *(char *)lParam = 0; + if (lvs && !lvs->IsOwnerData()) + { + SWELL_ListView_Row *row = lvs->m_data.Get(wParam); + const char *p; + if (row && (p=row->get_col_txt(0))) + { + strcpy((char *)lParam, p); + return (LRESULT)strlen(p); + } + } + return LB_ERR; + case LB_GETTEXTLEN: + { + SWELL_ListView_Row *row=lvs->m_data.Get(wParam); + if (row) + { + const char *p=row->get_col_txt(0); + return p?strlen(p):0; + } + } + return LB_ERR; + case LB_FINDSTRINGEXACT: + if (lParam) + { + int x = (int) wParam + 1; + if (x < 0) x=0; + const int n = lvs->m_data.GetSize(); + for (int i = 0; i < n; i ++) + { + SWELL_ListView_Row *row=lvs->m_data.Get(x); + if (row) + { + const char *p = row->get_col_txt(0); + if (p && !stricmp(p,(const char *)lParam)) return x; + } + if (++x >= n) x=0; + } + } + return LB_ERR; + case LB_RESETCONTENT: + if (lvs && !lvs->IsOwnerData()) + { + lvs->m_data.Empty(true,free); + } + InvalidateRect(hwnd,NULL,FALSE); + return 0; + case LB_SETSEL: + if (lvs && lvs->m_is_multisel) + { + if (lvs->IsOwnerData()) + { + } + else + { + if ((int)lParam == -1) + { + int x; + const int n=lvs->m_data.GetSize(); + for(x=0;xm_data.Get(x); + if (row) row->m_tmp = (row->m_tmp&~1) | (wParam?1:0); + } + InvalidateRect(hwnd,NULL,FALSE); + } + else + { + SWELL_ListView_Row *row=lvs->m_data.Get((int)lParam); + if (!row) return LB_ERR; + const int otmp = row->m_tmp; + row->m_tmp = (row->m_tmp&~1) | (wParam?1:0); + if (row->m_tmp != otmp) InvalidateRect(hwnd,NULL,FALSE); + return 0; + } + } + } + return LB_ERR; + case LB_SETCURSEL: + if (lvs && !lvs->IsOwnerData() && !lvs->m_is_multisel) + { + lvs->m_selitem = (int)wParam; + InvalidateRect(hwnd,NULL,FALSE); + } + return LB_ERR; + case LB_GETSEL: + if (lvs && lvs->m_is_multisel) + { + if (lvs->IsOwnerData()) + { + } + else + { + SWELL_ListView_Row *row=lvs->m_data.Get((int)wParam); + if (!row) return LB_ERR; + return row->m_tmp&1; + } + } + return LB_ERR; + case LB_GETCURSEL: + if (lvs) + { + return (LRESULT)lvs->m_selitem; + } + return LB_ERR; + case LB_GETCOUNT: + if (lvs) return lvs->GetNumItems(); + return LB_ERR; + case LB_GETSELCOUNT: + if (lvs && lvs->m_is_multisel) + { + int cnt=0; + if (lvs->IsOwnerData()) + { + } + else + { + int x; + const int n=lvs->m_data.GetSize(); + for(x=0;xm_data.Get(x); + if (row && (row->m_tmp&1)) cnt++; + } + } + return cnt; + } + return LB_ERR; + case LB_GETITEMDATA: + if (lvs && !lvs->IsOwnerData()) + { + SWELL_ListView_Row *row = lvs->m_data.Get(wParam); + return row ? row->m_param : LB_ERR; + } + return LB_ERR; + case LB_SETITEMDATA: + if (lvs && !lvs->IsOwnerData()) + { + SWELL_ListView_Row *row = lvs->m_data.Get(wParam); + if (row) row->m_param = lParam; + return row ? 0 : LB_ERR; + } + return LB_ERR; + case WM_SETFOCUS: + case WM_KILLFOCUS: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +struct treeViewState +{ + treeViewState() + { + m_sel=NULL; + m_last_row_height=0; + m_scroll_x=m_scroll_y=m_capmode=0; + m_root.m_state = TVIS_EXPANDED; + m_root.m_haschildren=true; + } + ~treeViewState() + { + } + bool findItem(HTREEITEM item, HTREEITEM *parOut, int *idxOut) + { + if (!m_root.FindItem(item,parOut,idxOut)) return false; + if (parOut && *parOut == &m_root) *parOut = NULL; + return true; + } + + int navigateSel(HWND hwnd, int key, int pagesize) // returns 2 force invalidate, 1 if ate key + { + HTREEITEM par=NULL; + int idx=0,tmp=1; + + switch (key) + { + case VK_LEFT: + if (m_sel && findItem(m_sel,&par,NULL)) + { + if (m_sel->m_haschildren && (m_sel->m_state & TVIS_EXPANDED)) + { + NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},}; + nmhdr.action = TVE_COLLAPSE; + nmhdr.itemNew.hItem=m_sel; + nmhdr.itemNew.lParam=m_sel->m_param; + if (!SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr)) + { + m_sel->m_state &= ~TVIS_EXPANDED; + return 2; + } + else + { + return 1; + } + } + if (par) m_sel=par; + } + return 1; + case VK_HOME: + m_sel=m_root.m_children.Get(0); + return 1; + case VK_END: + par = &m_root; + while (par && par->m_haschildren && + par->m_children.GetSize() && (par->m_state & TVIS_EXPANDED)) + par = par->m_children.Get(par->m_children.GetSize()-1); + if (par && par != &m_root) m_sel=par; + return 1; + case VK_RIGHT: + if (m_sel && findItem(m_sel,NULL,NULL) && m_sel->m_haschildren) + { + if (!(m_sel->m_state&TVIS_EXPANDED)) + { + NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},}; + nmhdr.action = TVE_EXPAND; + nmhdr.itemNew.hItem=m_sel; + nmhdr.itemNew.lParam=m_sel->m_param; + if (!SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr)) + { + m_sel->m_state |= TVIS_EXPANDED; + return 2; + } + else + { + return 1; + } + } + par = m_sel->m_children.Get(0); + if (par) m_sel=par; + } + return 1; + case VK_PRIOR: + tmp = wdl_max(pagesize,2) - 1; + case VK_UP: + while (tmp-- > 0) + { + if (m_sel && findItem(m_sel,&par,&idx)) + { + if (idx>0) + { + par = (par?par:&m_root)->m_children.Get(idx-1); + while (par && (par->m_state & TVIS_EXPANDED) && + par->m_haschildren && par->m_children.GetSize()) + par = par->m_children.Get(par->m_children.GetSize()-1); + } + if (par) m_sel=par; + } + } + return 1; + case VK_NEXT: + tmp = wdl_max(pagesize,2) - 1; + case VK_DOWN: + while (tmp-- > 0) + { + if (m_sel && findItem(m_sel,&par,&idx)) + { + if (m_sel->m_haschildren && + m_sel->m_children.GetSize() && + (m_sel->m_state & TVIS_EXPANDED)) + { + par = m_sel->m_children.Get(0); + if (par) m_sel=par; + continue; + } + +next_item_in_parent: + if (par) + { + if (idx+1 < par->m_children.GetSize()) + { + par = par->m_children.Get(idx+1); + if (par) m_sel=par; + } + else if (findItem(par,&par,&idx)) goto next_item_in_parent; + } + else + { + par = m_root.m_children.Get(idx+1); + if (par) m_sel=par; + } + } + } + return 1; + } + return 0; + } + + void doDrawItem(HTREEITEM item, HDC hdc, RECT *rect, bool focus) // draws any subitems too, updates rect->top + { +#ifdef SWELL_LICE_GDI + if (!item) return; + + if (item != &m_root) + { + const int ob = rect->bottom; + rect->bottom = rect->top + m_last_row_height; + if (rect->right > rect->left) + { + int oc=0; + if (item == m_sel) + { + SetBkMode(hdc,OPAQUE); + SetBkColor(hdc,focus ? g_swell_ctheme.treeview_bg_sel : g_swell_ctheme.treeview_bg_sel_inactive); + oc = GetTextColor(hdc); + SetTextColor(hdc,focus ? g_swell_ctheme.treeview_text_sel : g_swell_ctheme.treeview_text_sel_inactive); + } + + RECT dr = *rect; + const int sz = m_last_row_height/4; + if (item->m_haschildren) + { + bool exp = (item->m_state&TVIS_EXPANDED); + POINT pts[3]; + if (exp) + { + const int yo = dr.top + sz+sz/2,xo=dr.left+1; + pts[0].x=xo; pts[0].y=yo; + pts[1].x=xo+sz*2; pts[1].y=yo; + pts[2].x=xo+sz; pts[2].y=yo+sz; + } + else + { + const int yo = dr.top + sz, xo = dr.left+sz*3/4+1; + pts[0].x=xo; pts[0].y=yo; + pts[1].x=xo+sz; pts[1].y=yo+sz; + pts[2].x=xo; pts[2].y=yo+sz*2; + } + Polygon(hdc,pts,3); + } + dr.left += sz*2+3; + + DrawText(hdc,item->m_value ? item->m_value : "",-1,&dr,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + if (item == m_sel) + { + SetBkMode(hdc,TRANSPARENT); + SetTextColor(hdc,oc); + } + } + rect->top = rect->bottom; + rect->bottom = ob; + } + + if ((item->m_state & TVIS_EXPANDED) && item->m_haschildren && item->m_children.GetSize()) + { + const int n = item->m_children.GetSize(); + rect->left += m_last_row_height; + for (int x=0;xtop < rect->bottom;x++) + { + doDrawItem(item->m_children.Get(x),hdc,rect,focus); + } + rect->left -= m_last_row_height; + } +#endif + } + HTREEITEM hitTestItem(HTREEITEM item, int *y, int *xo) + { + *y -= m_last_row_height; + if (*y < 0) return item; + if ((item->m_state & TVIS_EXPANDED) && item->m_haschildren && item->m_children.GetSize()) + { + int x; + const int n = item->m_children.GetSize(); + for (x=0;xm_children.Get(x),y,xo); + if (t) + { + if (xo) *xo += m_last_row_height; + return t; + } + } + } + return NULL; + } + int CalculateItemHeight(HTREEITEM__ *item, HTREEITEM stopAt, bool *done) + { + int h = m_last_row_height; + if (item == stopAt) { *done=true; return 0; } + + if ((item->m_state & TVIS_EXPANDED) && + item->m_haschildren && + item->m_children.GetSize()) + { + const int n = item->m_children.GetSize(); + for (int x=0;xm_children.Get(x),stopAt,done); + if (*done) break; + } + } + return h; + } + + int calculateContentsHeight(HTREEITEM item=NULL) + { + bool done=false; + const int rv = CalculateItemHeight(&m_root,item,&done); + if (item && !done) return 0; + return rv - m_last_row_height; + } + + int sanitizeScroll(HWND h) + { + RECT r; + GetClientRect(h,&r); + if (m_last_row_height > 0) + { + const int vh = calculateContentsHeight(); + if (m_scroll_y < 0 || vh <= r.bottom) m_scroll_y=0; + else if (m_scroll_y > vh - r.bottom) m_scroll_y = vh - r.bottom; + return vh; + } + return 0; + } + + void ensureItemVisible(HWND hwnd, HTREEITEM item) + { + if (m_last_row_height<1) return; + const int x = item ? calculateContentsHeight(item) : 0; + RECT r; + GetClientRect(hwnd,&r); + if (x < m_scroll_y) m_scroll_y = x; + else if (x+m_last_row_height > m_scroll_y+r.bottom) + m_scroll_y = x+m_last_row_height - r.bottom; + } + + HTREEITEM__ m_root; + HTREEITEM m_sel; + int m_last_row_height; + + int m_scroll_x,m_scroll_y; + + int m_capmode; // HIWORD is 0 for normal (then LOWORD gets 1 set if drag began), 1 for scroll (LOWORD has ypos) +}; + +static LRESULT treeViewWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static POINT s_clickpt; + treeViewState *tvs = (treeViewState *)hwnd->m_private_data; + switch (msg) + { + case WM_MOUSEWHEEL: + if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break; // pass modified mousewheel to parent + + { + const int amt = ((short)HIWORD(wParam))/40; + if (amt && tvs) + { + const int oldscroll = tvs->m_scroll_y; + tvs->m_scroll_y -= amt*tvs->m_last_row_height; + tvs->sanitizeScroll(hwnd); + if (tvs->m_scroll_y != oldscroll) + InvalidateRect(hwnd,NULL,FALSE); + + } + } + return 1; + case WM_KEYDOWN: + if (tvs && (lParam & FVIRTKEY)) + { + HTREEITEM oldSel = tvs->m_sel; + RECT r; + GetClientRect(hwnd,&r); + int flag = tvs->navigateSel(hwnd,(int)wParam,tvs->m_last_row_height ? r.bottom / tvs->m_last_row_height : 4); + if (oldSel != tvs->m_sel) + { + if (tvs->m_sel) tvs->ensureItemVisible(hwnd,tvs->m_sel); + InvalidateRect(hwnd,NULL,FALSE); + NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},}; + nm.itemNew.hItem = tvs->m_sel; + nm.itemNew.lParam = tvs->m_sel ? tvs->m_sel->m_param : 0; + SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + } + else if (flag&2) InvalidateRect(hwnd,NULL,FALSE); + + if (flag) return 0; + } + break; + case WM_LBUTTONDOWN: + SetFocus(hwnd); + SetCapture(hwnd); + if (tvs) + { + tvs->m_capmode=0; + s_clickpt.x = GET_X_LPARAM(lParam); + s_clickpt.y = GET_Y_LPARAM(lParam); + RECT cr; + GetClientRect(hwnd,&cr); + int total_h; + if (GET_X_LPARAM(lParam) >= cr.right-g_swell_ctheme.scrollbar_width && + (total_h=tvs->sanitizeScroll(hwnd)) > cr.bottom) + { + int ypos = GET_Y_LPARAM(lParam); + int yp = ypos; + + int thumbsz, thumbpos; + calcScroll(cr.bottom, total_h,tvs->m_scroll_y,&thumbsz,&thumbpos); + + if (ypos < thumbpos) yp = thumbpos; // jump on first mouse move + else if (ypos > thumbpos+thumbsz) yp = thumbpos + thumbsz; + + tvs->m_capmode = (1<<16) | (yp&0xffff); + if (ypos < thumbpos || ypos > thumbpos+thumbsz) goto forceMouseMove; + return 0; + } + + if (tvs->m_last_row_height) + { + int y = GET_Y_LPARAM(lParam) + tvs->m_scroll_y + tvs->m_last_row_height; + int xo=-tvs->m_last_row_height; + HTREEITEM hit = tvs->hitTestItem(&tvs->m_root,&y,&xo); + if (hit && GET_X_LPARAM(lParam) >= xo) + { + if (hit->m_haschildren) + { + if (GET_X_LPARAM(lParam) < xo + (tvs->m_last_row_height/4)*2+3) + { + NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},}; + nmhdr.action = (hit->m_state&TVIS_EXPANDED)!=TVIS_EXPANDED ? TVE_EXPAND : TVE_COLLAPSE; + nmhdr.itemNew.hItem=hit; + nmhdr.itemNew.lParam=hit->m_param; + if (!SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr)) + { + hit->m_state ^= TVIS_EXPANDED; + InvalidateRect(hwnd,NULL,FALSE); + } + return 0; + } + } + if (tvs->m_sel != hit) + { + tvs->m_sel = hit; + InvalidateRect(hwnd,NULL,FALSE); + NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},}; + nm.itemNew.hItem = hit; + nm.itemNew.lParam = hit ? hit->m_param : 0; + SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + } + } + } + } + return 0; + case WM_MOUSEMOVE: + if (GetCapture()==hwnd && tvs) + { +forceMouseMove: + switch (HIWORD(tvs->m_capmode)) + { + case 0: + if (!(tvs->m_capmode & 1)) + { + const int dx = GET_X_LPARAM(lParam) - s_clickpt.x, dy = GET_Y_LPARAM(lParam) - s_clickpt.y; + if (dx*dx+dy*dy > 32) + { + tvs->m_capmode|=1; + HTREEITEM item = TreeView_GetSelection(hwnd); + NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_BEGINDRAG},}; + nm.itemNew.hItem = item; + nm.itemNew.lParam = item ? item->m_param : 0; + SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + } + } + break; + case 1: + { + int yv = (short)LOWORD(tvs->m_capmode); + int amt = GET_Y_LPARAM(lParam) - yv; + + if (amt) + { + RECT r; + GetClientRect(hwnd,&r); + + const int viewsz = r.bottom; + const double totalsz=tvs->calculateContentsHeight(); + amt = (int)floor(amt * totalsz / (double)viewsz + 0.5); + + const int oldscroll = tvs->m_scroll_y; + tvs->m_scroll_y += amt; + tvs->sanitizeScroll(hwnd); + if (tvs->m_scroll_y != oldscroll) + { + tvs->m_capmode = (GET_Y_LPARAM(lParam)&0xffff) | (1<<16); + InvalidateRect(hwnd,NULL,FALSE); + } + } + } + break; + } + } + return 1; + case WM_LBUTTONUP: + if (GetCapture() == hwnd) + { + ReleaseCapture(); + } + return 1; + case WM_RBUTTONUP: + if (tvs && tvs->m_last_row_height>0) + { + NMLISTVIEW nm={{hwnd,hwnd->m_id,NM_RCLICK},0,0,0,}; + if (SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm)) + return 1; + } + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + { + HBRUSH br = CreateSolidBrush(g_swell_ctheme.treeview_bg); + FillRect(ps.hdc,&r,br); + DeleteObject(br); + } + if (tvs) + { + RECT cr=r; + SetTextColor(ps.hdc,g_swell_ctheme.treeview_text); + + const int lrh = tvs->m_last_row_height; + TEXTMETRIC tm; + GetTextMetrics(ps.hdc,&tm); + const int row_height = tm.tmHeight; + tvs->m_last_row_height = row_height; + const int total_h = tvs->sanitizeScroll(hwnd); + if (!lrh && tvs->m_sel) + tvs->ensureItemVisible(hwnd,tvs->m_sel); + + SetBkMode(ps.hdc,TRANSPARENT); + + r.top -= tvs->m_scroll_y; + + HBRUSH br = CreateSolidBrush(g_swell_ctheme.treeview_arrow); + HGDIOBJ oldpen = SelectObject(ps.hdc,GetStockObject(NULL_PEN)); + HGDIOBJ oldbr = SelectObject(ps.hdc,br); + + r.left -= tvs->m_last_row_height; + tvs->doDrawItem(&tvs->m_root,ps.hdc,&r, GetFocus()==hwnd); + + SelectObject(ps.hdc,oldbr); + SelectObject(ps.hdc,oldpen); + DeleteObject(br); + + drawVerticalScrollbar(ps.hdc,cr,total_h,tvs->m_scroll_y); + Draw3DBox(ps.hdc,&cr,-1, + g_swell_ctheme.treeview_hilight, + g_swell_ctheme.treeview_shadow); + } + + EndPaint(hwnd,&ps); + } + } + return 0; + case WM_NCDESTROY: + hwnd->m_private_data = 0; + delete tvs; + break; + case WM_SETFOCUS: + case WM_KILLFOCUS: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +struct tabControlState +{ + tabControlState() { m_curtab=0; } + ~tabControlState() { m_tabs.Empty(true,free); } + int m_curtab; + WDL_PtrList m_tabs; +}; + +#define TABCONTROL_HEIGHT SWELL_UI_SCALE(20) + +static LRESULT tabControlWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + const int xdiv = 6,xpad=4; + + tabControlState *s = (tabControlState *)hwnd->m_private_data; + switch (msg) + { + case WM_NCDESTROY: + hwnd->m_private_data = 0; + delete s; + break; + case WM_TIMER: + if (wParam==1) + { + if (!fast_has_focus(hwnd)) + { + KillTimer(hwnd,1); + InvalidateRect(hwnd,NULL,FALSE); + } + } + break; + case WM_LBUTTONUP: + if (GET_Y_LPARAM(lParam) < TABCONTROL_HEIGHT) + { + return 1; + } + break; + case WM_KEYDOWN: + if ((lParam&0xff)==FVIRTKEY && + (wParam==VK_LEFT || + wParam==VK_RIGHT || + wParam==VK_HOME || + wParam == VK_END)) + { + int ct = s->m_curtab; + if (wParam==VK_LEFT)ct--; + else ct++; + if (ct>=s->m_tabs.GetSize()||wParam == VK_END) ct=s->m_tabs.GetSize()-1; + if (ct<0||wParam==VK_HOME) ct=0; + if (ct != s->m_curtab) + { + s->m_curtab = ct; + NMHDR nm={hwnd,(UINT_PTR)hwnd->m_id,TCN_SELCHANGE}; + InvalidateRect(hwnd,NULL,FALSE); + SendMessage(GetParent(hwnd),WM_NOTIFY,nm.idFrom,(LPARAM)&nm); + } + + return 0; + } + break; + case WM_LBUTTONDOWN: + if (GET_Y_LPARAM(lParam) < TABCONTROL_HEIGHT) + { + SetFocus(hwnd); + int xp=GET_X_LPARAM(lParam),tab; + HDC dc = GetDC(hwnd); + int tabchg = -1; + for (tab = 0; tab < s->m_tabs.GetSize(); tab ++) + { + const char *buf = s->m_tabs.Get(tab); + RECT tr={0,}; + DrawText(dc,buf,-1,&tr,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE); + xp -= tr.right - tr.left + 2*SWELL_UI_SCALE(xpad) + SWELL_UI_SCALE(xdiv); + if (xp < 0) + { + if (s->m_curtab != tab) + { + tabchg = tab; + } + break; + } + } + if (tabchg >=0) + { + s->m_curtab = tabchg; + NMHDR nm={hwnd,(UINT_PTR)hwnd->m_id,TCN_SELCHANGE}; + InvalidateRect(hwnd,NULL,FALSE); + SendMessage(GetParent(hwnd),WM_NOTIFY,nm.idFrom,(LPARAM)&nm); + } + else + InvalidateRect(hwnd,NULL,FALSE); + + ReleaseDC(hwnd,dc); + return 1; + } + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + + int tab; + int xp=0; + HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.tab_hilight); + HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.tab_shadow); + + SetBkMode(ps.hdc,TRANSPARENT); + SetTextColor(ps.hdc,g_swell_ctheme.tab_text); + HGDIOBJ oldPen=SelectObject(ps.hdc,pen); + const int th = TABCONTROL_HEIGHT; + + { + RECT bgr={0,0,r.right,th}; + IntersectRect(&bgr,&bgr,&ps.rcPaint); + HBRUSH hbrush = (HBRUSH) SendMessage(hwnd,WM_CTLCOLORDLG,(WPARAM)ps.hdc,(LPARAM)hwnd); + if (hbrush && hbrush != (HBRUSH)1) FillRect(ps.hdc,&bgr,hbrush); + else SWELL_FillDialogBackground(ps.hdc,&bgr,0); + } + + int lx=0; + RECT fr={0,}; + for (tab = 0; tab < s->m_tabs.GetSize() && xp < r.right; tab ++) + { + const char *buf = s->m_tabs.Get(tab); + RECT tr={0,}; + DrawText(ps.hdc,buf,-1,&tr,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE); + int tw=tr.right-tr.left + 2*SWELL_UI_SCALE(xpad); + + const int olx=lx; + lx=xp + tw+SWELL_UI_SCALE(xdiv); + + MoveToEx(ps.hdc,xp,th-1,NULL); + LineTo(ps.hdc,xp,0); + LineTo(ps.hdc,xp+tw,0); + SelectObject(ps.hdc,pen2); + LineTo(ps.hdc,xp+tw,th-1); + + if (tab==s->m_curtab) + { + fr.left=xp; + fr.right=xp+tw; + fr.top=0; + fr.bottom=th-2; + } + + MoveToEx(ps.hdc, tab == s->m_curtab ? lx-SWELL_UI_SCALE(xdiv) : olx,th-1,NULL); + LineTo(ps.hdc,lx,th-1); + + SelectObject(ps.hdc,pen); + + tr.left = xp+SWELL_UI_SCALE(xpad); + tr.top=0; + tr.right = xp+tw-SWELL_UI_SCALE(xpad); + tr.bottom = th; + + DrawText(ps.hdc,buf,-1,&tr,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX); + xp = lx; + } + if (draw_focus_indicator(hwnd,ps.hdc,&fr)) + { + KillTimer(hwnd,1); + SetTimer(hwnd,1,100,NULL); + } + SelectObject(ps.hdc,pen2); + MoveToEx(ps.hdc,lx,th-1,NULL); + LineTo(ps.hdc,r.right,th-1); + + SelectObject(ps.hdc,oldPen); + + EndPaint(hwnd,&ps); + DeleteObject(pen); + DeleteObject(pen2); + } + } + return 0; + case WM_SETFOCUS: + case WM_KILLFOCUS: + InvalidateRect(hwnd,NULL,FALSE); + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + + + +HWND SWELL_MakeListBox(int idx, int x, int y, int w, int h, int styles) +{ + RECT tr=MakeCoords(x,y,w,h,true); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(styles&SWELL_NOT_WS_VISIBLE), listViewWindowProc); + hwnd->m_style = WS_CHILD | (styles & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = "ListBox"; + hwnd->m_private_data = (INT_PTR) new listViewState(false, !!(styles & LBS_EXTENDEDSEL), true); + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + if (m_doautoright) UpdateAutoCoords(tr); + return hwnd; +} + +typedef struct ccprocrec +{ + SWELL_ControlCreatorProc proc; + int cnt; + struct ccprocrec *next; +} ccprocrec; + +static ccprocrec *m_ccprocs; + +void SWELL_RegisterCustomControlCreator(SWELL_ControlCreatorProc proc) +{ + if (WDL_NOT_NORMALLY(!proc)) return; + + ccprocrec *p=m_ccprocs; + if (p) for (;;) + { + if (p->proc == proc) + { + p->cnt++; + return; + } + if (!p->next) break; + p=p->next; + } + ccprocrec *ent = (ccprocrec*)malloc(sizeof(ccprocrec)); + ent->proc=proc; + ent->cnt=1; + ent->next=NULL; + + if (p) p->next=ent; + else m_ccprocs=ent; +} + +void SWELL_UnregisterCustomControlCreator(SWELL_ControlCreatorProc proc) +{ + if (WDL_NOT_NORMALLY(!proc)) return; + + ccprocrec *lp=NULL; + ccprocrec *p=m_ccprocs; + while (p) + { + if (p->proc == proc) + { + if (--p->cnt <= 0) + { + if (lp) lp->next=p->next; + else m_ccprocs=p->next; + free(p); + } + return; + } + lp=p; + p=p->next; + } + WDL_ASSERT(false); +} + + + +HWND SWELL_MakeControl(const char *cname, int idx, const char *classname, int style, int x, int y, int w, int h, int exstyle) +{ + if (m_ccprocs) + { + RECT poo=MakeCoords(x,y,w,h,false); + ccprocrec *p=m_ccprocs; + while (p) + { + HWND hhh=p->proc((HWND)m_make_owner,cname,idx,classname,style,poo.left,poo.top,poo.right-poo.left,poo.bottom-poo.top); + if (hhh) + { + if (exstyle) SetWindowLong(hhh,GWL_EXSTYLE,exstyle); + return hhh; + } + p=p->next; + } + } + if (!stricmp(classname,"SysTabControl32")) + { + RECT tr=MakeCoords(x,y,w,h,false); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), tabControlWindowProc); + hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = "SysTabControl32"; + hwnd->m_private_data = (INT_PTR) new tabControlState; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + SetWindowPos(hwnd,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); + return hwnd; + } + else if (!stricmp(classname, "SysListView32")||!stricmp(classname, "SysListView32_LB")) + { + RECT tr=MakeCoords(x,y,w,h,false); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), listViewWindowProc); + hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = "SysListView32"; + if (!stricmp(classname, "SysListView32")) + hwnd->m_private_data = (INT_PTR) new listViewState(!!(style & LVS_OWNERDATA), !(style & LVS_SINGLESEL), false); + else + hwnd->m_private_data = (INT_PTR) new listViewState(false,false, true); + + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + return hwnd; + } + else if (!stricmp(classname, "SysTreeView32")) + { + RECT tr=MakeCoords(x,y,w,h,false); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), treeViewWindowProc); + hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = "SysTreeView32"; + hwnd->m_private_data = (INT_PTR) new treeViewState; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + return hwnd; + } + else if (!stricmp(classname, "msctls_progress32")) + { + RECT tr=MakeCoords(x,y,w,h,false); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), progressWindowProc); + hwnd->m_wantfocus = false; + hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = "msctls_progress32"; + int *state = (int *)calloc(2,sizeof(int)); // pos, range + if (state) state[1] = 100<<16; + hwnd->m_private_data = (INT_PTR) state; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + return hwnd; + } + else if (!stricmp(classname,"Edit")) + { + return SWELL_MakeEditField(idx,x,y,w,h,style); + } + else if (!stricmp(classname, "Static")) + { + RECT tr=MakeCoords(x,y,w,h,false); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,cname, !(style&SWELL_NOT_WS_VISIBLE),labelWindowProc); + hwnd->m_wantfocus = false; + hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = "Static"; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + if (m_doautoright) UpdateAutoCoords(tr); + return hwnd; + } + else if (!stricmp(classname,"Button")) + { + RECT tr=MakeCoords(x,y,w,h,true); + HWND hwnd = swell_makeButton(m_make_owner,idx,&tr,cname,!(style&SWELL_NOT_WS_VISIBLE),(style&~SWELL_NOT_WS_VISIBLE)|WS_CHILD); + if (m_doautoright) UpdateAutoCoords(tr); + return hwnd; + } + else if (!stricmp(classname,"REAPERhfader")||!stricmp(classname,"msctls_trackbar32")) + { + RECT tr=MakeCoords(x,y,w,h,true); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,cname, !(style&SWELL_NOT_WS_VISIBLE),trackbarWindowProc); + hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE); + hwnd->m_classname = !stricmp(classname,"REAPERhfader") ? "REAPERhfader" : "msctls_trackbar32"; + hwnd->m_private_data = (INT_PTR) calloc(3,sizeof(int)); // pos, range, tic + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + return hwnd; + } + else if (!stricmp(classname,"COMBOBOX")) + { + return SWELL_MakeCombo(idx, x, y, w, h, style); + } + return 0; +} + +HWND SWELL_MakeCombo(int idx, int x, int y, int w, int h, int flags) +{ + RECT tr=MakeCoords(x,y,w,h,true); + const int maxh = g_swell_ctheme.combo_height; + if (tr.bottom > tr.top + maxh) tr.bottom=tr.top+maxh; + HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(flags&SWELL_NOT_WS_VISIBLE),comboWindowProc); + hwnd->m_private_data = (INT_PTR) new __SWELL_ComboBoxInternalState; + hwnd->m_style = (flags & ~SWELL_NOT_WS_VISIBLE)|WS_CHILD; + hwnd->m_classname = "combobox"; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + if (m_doautoright) UpdateAutoCoords(tr); + return hwnd; +} + +HWND SWELL_MakeGroupBox(const char *name, int idx, int x, int y, int w, int h, int style) +{ + RECT tr=MakeCoords(x,y,w,h,false); + HWND hwnd = new HWND__(m_make_owner,idx,&tr,name, !(style&SWELL_NOT_WS_VISIBLE),groupWindowProc); + hwnd->m_wantfocus = false; + hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE) | BS_GROUPBOX; + hwnd->m_classname = "Button"; + hwnd->m_wndproc(hwnd,WM_CREATE,0,0); + SetWindowPos(hwnd,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); + return hwnd; +} + + +int TabCtrl_GetItemCount(HWND hwnd) +{ + tabControlState *s = hwnd ? (tabControlState*) hwnd->m_private_data : NULL; + return WDL_NORMALLY(s) ? s->m_tabs.GetSize() : 0; +} + +BOOL TabCtrl_AdjustRect(HWND hwnd, BOOL fLarger, RECT *r) +{ + if (WDL_NOT_NORMALLY(!r || !hwnd)) return FALSE; + + r->top += TABCONTROL_HEIGHT; + + return TRUE; +} + + +BOOL TabCtrl_DeleteItem(HWND hwnd, int idx) +{ + tabControlState *s = hwnd ? (tabControlState*) hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!s || !s->m_tabs.Get(idx))) return FALSE; + + s->m_tabs.Delete(idx,true); + if (s->m_curtab>0) s->m_curtab--; + InvalidateRect(hwnd,NULL,FALSE); + // todo: send notification? + + return TRUE; +} + +int TabCtrl_InsertItem(HWND hwnd, int idx, TCITEM *item) +{ + tabControlState *s = WDL_NORMALLY(hwnd) ? (tabControlState*) hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!item || !s)) return -1; + if (WDL_NOT_NORMALLY(!(item->mask & TCIF_TEXT) || !item->pszText)) return -1; + + s->m_tabs.Insert(idx, strdup(item->pszText)); + + InvalidateRect(hwnd,NULL,FALSE); + // todo: send notification if s->m_tabs.GetSize()==1 ? + + return TRUE; +} + +int TabCtrl_SetCurSel(HWND hwnd, int idx) +{ + tabControlState *s = WDL_NORMALLY(hwnd) ? (tabControlState*) hwnd->m_private_data : NULL; + if (!s || !s->m_tabs.Get(idx)) return -1; + const int lt =s->m_curtab; + s->m_curtab = idx; + InvalidateRect(hwnd,NULL,FALSE); + + return lt; +} + +int TabCtrl_GetCurSel(HWND hwnd) +{ + tabControlState *s = WDL_NORMALLY(hwnd) ? (tabControlState*) hwnd->m_private_data : NULL; + return s ? s->m_curtab : -1; +} + +void ListView_SetExtendedListViewStyleEx(HWND h, int flag, int mask) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return; + lvs->m_extended_style = (lvs->m_extended_style & ~mask) | (flag&mask); +} + +void SWELL_SetListViewFastClickMask(HWND hList, int mask) +{ + listViewState *lvs = hList ? (listViewState *)hList->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return; + lvs->m_fastclick_mask = mask; +} + +void ListView_SetImageList(HWND h, HIMAGELIST imagelist, int which) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return; + lvs->m_status_imagelist= (WDL_PtrList *)imagelist; + lvs->m_status_imagelist_type = which; +} + +int ListView_GetColumnWidth(HWND h, int pos) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return 0; + SWELL_ListView_Col *c = lvs->GetColumnByIndex(pos); + return WDL_NORMALLY(c) ? c->xwid : 0; +} + +void ListView_InsertColumn(HWND h, int pos, const LVCOLUMN *lvc) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !lvc)) return; + SWELL_ListView_Col col = { 0, 100 }; + if (lvc->mask & LVCF_WIDTH) col.xwid = lvc->cx; + if (lvc->mask & LVCF_TEXT) col.name = lvc->pszText ? strdup(lvc->pszText) : NULL; + if (lvc->mask & LVCF_FMT) col.fmt = lvc->fmt; + + for (int x = 0; x < lvs->m_cols.GetSize(); x++) + if (lvs->m_cols.Get()[x].col_index>=pos) + lvs->m_cols.Get()[x].col_index++; + + if (pos<0)pos=0; + else if (pos>lvs->m_cols.GetSize()) pos=lvs->m_cols.GetSize(); + col.col_index = pos; + + lvs->m_cols.Insert(col,pos); +} + +void ListView_GetColumn(HWND h, int pos, LVCOLUMN *lvc) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !lvc)) return; + SWELL_ListView_Col *col = lvs->GetColumnByIndex(pos); + if (WDL_NOT_NORMALLY(!col)) return; + if (lvc->mask & LVCF_WIDTH) lvc->cx = col->xwid; + if (lvc->mask & LVCF_TEXT) + { + if (WDL_NORMALLY(lvc->pszText && lvc->cchTextMax>0)) + lstrcpyn_safe(lvc->pszText, col->name?col->name:"", lvc->cchTextMax); + } + if (lvc->mask & LVCF_FMT) lvc->fmt = col->fmt; +} + +void ListView_SetColumn(HWND h, int pos, const LVCOLUMN *lvc) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !lvc)) return; + SWELL_ListView_Col *col = lvs->GetColumnByIndex(pos); + if (WDL_NOT_NORMALLY(!col)) return; + if (lvc->mask & LVCF_WIDTH) col->xwid = lvc->cx; + if (lvc->mask & LVCF_TEXT) + { + free(col->name); + col->name = lvc->pszText ? strdup(lvc->pszText) : NULL; + } + if (lvc->mask & LVCF_FMT) col->fmt = lvc->fmt; +} + +void ListView_GetItemText(HWND hwnd, int item, int subitem, char *text, int textmax) +{ + LVITEM it={LVIF_TEXT,item,subitem,0,0,text,textmax,}; + ListView_GetItem(hwnd,&it); +} + +int ListView_InsertItem(HWND h, const LVITEM *item) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData() || !item || item->iSubItem)) return 0; + + int idx = (int) item->iItem; + if (idx<0 || idx>lvs->m_data.GetSize()) idx=lvs->m_data.GetSize(); + SWELL_ListView_Row *row=new SWELL_ListView_Row; + row->add_col((item->mask&LVIF_TEXT) ? item->pszText : NULL); + row->m_param = (item->mask&LVIF_PARAM) ? item->lParam : 0; + row->m_tmp = ((item->mask & LVIF_STATE) && (item->state & LVIS_SELECTED)) ? 1:0; + lvs->m_data.Insert(idx,row); + if (item->mask&LVIF_STATE) + { + if (item->stateMask & LVIS_STATEIMAGEMASK) row->set_img_idx(0,STATEIMAGEMASKTOINDEX(item->state)); + if (item->stateMask & LVIS_SELECTED) lvs->set_sel(idx,!!(item->state&LVIS_SELECTED)); + } + InvalidateRect(h,NULL,FALSE); + return idx; +} + +void ListView_SetItemText(HWND h, int ipos, int cpos, const char *txt) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData() || cpos < 0)) return; + const int ncol = wdl_max(lvs->m_cols.GetSize(),1); + if (WDL_NOT_NORMALLY(cpos >= ncol)) return; + + SWELL_ListView_Row *row=lvs->m_data.Get(ipos); + if (!row) return; + while (row->get_num_cols()<=cpos) row->add_col(NULL); + row->set_col_txt(cpos,txt); + InvalidateRect(h,NULL,FALSE); +} + +int ListView_GetNextItem(HWND h, int istart, int flags) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return -1; + const int n = lvs->GetNumItems(); + for (int x = wdl_max(0,istart+1); x < n; x ++) + { + if (flags&LVNI_SELECTED) if (lvs->get_sel(x)) return x; + if (flags&LVNI_FOCUSED) if (lvs->m_selitem==x) return x; + } + return -1; +} + +bool ListView_SetItem(HWND h, LVITEM *item) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !item)) return false; + + const bool ownerData = lvs->IsOwnerData(); + if (!ownerData) + { + SWELL_ListView_Row *row=lvs->m_data.Get(item->iItem); + if (!row) return false; + + const int ncol = wdl_max(lvs->m_cols.GetSize(),1); + if (item->iSubItem >= 0 && item->iSubItem < ncol) + { + while (row->get_num_cols()<=item->iSubItem) row->add_col(NULL); + if (item->mask&LVIF_TEXT) + { + row->set_col_txt(item->iSubItem,item->pszText); + } + } + if (item->mask & LVIF_PARAM) + { + row->m_param = item->lParam; + } + if (item->mask&LVIF_IMAGE) + { + row->set_img_idx(item->iSubItem,item->iImage+1); + } + } + else + { + if (item->iItem < 0 || item->iItem >= lvs->GetNumItems()) return false; + } + if (item->mask & LVIF_STATE) + { + ListView_SetItemState(h,item->iItem,item->state,item->stateMask); + } + + InvalidateRect(h,NULL,FALSE); + + return true; +} + +bool ListView_GetItem(HWND h, LVITEM *item) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !item)) return false; + if (!lvs->IsOwnerData()) + { + SWELL_ListView_Row *row=lvs->m_data.Get(item->iItem); + if (!row) return false; + if ((item->mask&LVIF_TEXT)&&item->pszText && item->cchTextMax > 0) + { + const char *v=row->get_col_txt(item->iSubItem); + lstrcpyn_safe(item->pszText, v?v:"",item->cchTextMax); + } + if (item->mask & LVIF_PARAM) item->lParam = row->m_param; + } + else + { + if (item->iItem < 0 || item->iItem >= lvs->GetNumItems()) return false; + + int mask = item->mask & (LVIF_PARAM|LVIF_TEXT); + if (mask & LVIF_TEXT) + { + if (!item->pszText || item->cchTextMax < 1) mask &= ~LVIF_TEXT; + else *item->pszText = 0; + } + if (mask) + { + NMLVDISPINFO nm={{h, (UINT_PTR)GetWindowLong(h,GWL_ID), LVN_GETDISPINFO}}; + nm.item.mask = mask; + nm.item.iItem = item->iItem; + nm.item.iSubItem = item->iSubItem; + nm.item.pszText = item->pszText; + nm.item.cchTextMax = item->cchTextMax; + SendMessage(GetParent(h),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + if ((mask & LVIF_TEXT) && nm.item.pszText != item->pszText) + lstrcpyn_safe(item->pszText, nm.item.pszText ? nm.item.pszText : "", item->cchTextMax); + if (mask & LVIF_PARAM) item->lParam = nm.item.lParam; + } + } + + if (item->mask & LVIF_STATE) + { + item->state = 0; + if ((item->stateMask & LVIS_SELECTED) && lvs->get_sel(item->iItem)) item->state |= LVIS_SELECTED; + if ((item->stateMask & LVIS_FOCUSED) && lvs->m_selitem == item->iItem) item->state |= LVIS_FOCUSED; + if (item->stateMask & 0xff0000) + { + SWELL_ListView_Row *row = lvs->m_data.Get(item->iItem); + if (row) + item->state |= INDEXTOSTATEIMAGEMASK(row->get_img_idx(0)); + } + } + + return true; +} +int ListView_GetItemState(HWND h, int ipos, UINT mask) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return 0; + int ret = 0; + if (mask & LVIS_SELECTED) ret |= (lvs->get_sel(ipos) ? LVIS_SELECTED : 0 ); + if ((mask & LVIS_FOCUSED) && lvs->m_selitem == ipos) ret |= LVIS_FOCUSED; + if ((mask & LVIS_STATEIMAGEMASK) && lvs->m_status_imagelist_type == LVSIL_STATE) + { + SWELL_ListView_Row *row = lvs->m_data.Get(ipos); + if (row) + ret |= INDEXTOSTATEIMAGEMASK(row->get_img_idx(0)); + } + return ret; +} + +bool ListView_SetItemState(HWND h, int ipos, UINT state, UINT statemask) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return false; + + static int _is_doing_all; + + if (ipos == -1) + { + int x; + int n=ListView_GetItemCount(h); + _is_doing_all++; + if ((statemask & LVIS_SELECTED) && (state & LVIS_SELECTED) && !lvs->m_is_multisel) + statemask &= ~LVIS_SELECTED; + for (x = 0; x < n; x ++) + ListView_SetItemState(h,x,state,statemask); + _is_doing_all--; + ListView_RedrawItems(h,0,n-1); + return true; + } + bool changed=false; + + if (statemask & LVIS_SELECTED) changed |= lvs->set_sel(ipos,!!(state&LVIS_SELECTED)); + if (statemask & LVIS_FOCUSED) + { + if (state&LVIS_FOCUSED) + { + if (lvs->m_selitem != ipos) + { + changed=true; + lvs->m_selitem = ipos; + } + } + } + if ((statemask & LVIS_STATEIMAGEMASK) && lvs->m_status_imagelist_type == LVSIL_STATE) + { + SWELL_ListView_Row *row = lvs->m_data.Get(ipos); + if (row) + { + const int idx= row->get_img_idx(0); + row->set_img_idx(0,STATEIMAGEMASKTOINDEX(state)); + if (!changed && idx != row->get_img_idx(0)) ListView_RedrawItems(h,ipos,ipos); + } + } + + if (changed) + { + static int __rent; + if (!__rent) + { + __rent++; + NMLISTVIEW nm={{(HWND)h,(unsigned short)h->m_id,LVN_ITEMCHANGED},ipos,0,state,}; + SendMessage(GetParent(h),WM_NOTIFY,h->m_id,(LPARAM)&nm); + __rent--; + } + if (!_is_doing_all) ListView_RedrawItems(h,ipos,ipos); + } + return true; +} +void ListView_RedrawItems(HWND h, int startitem, int enditem) +{ + if (WDL_NOT_NORMALLY(!h)) return; + InvalidateRect(h,NULL,FALSE); +} + +void ListView_DeleteItem(HWND h, int ipos) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData())) return; + lvs->m_data.Delete(ipos,true); + InvalidateRect(h,NULL,FALSE); +} + +void ListView_DeleteAllItems(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData())) return; + lvs->m_data.Empty(true); + InvalidateRect(h,NULL,FALSE); +} + +int ListView_GetSelectedCount(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return 0; + const int n = lvs->GetNumItems(); + int sum=0,x; + for (x=0;xget_sel(x)) sum++; + return sum; +} + +int ListView_GetItemCount(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return 0; + return lvs->GetNumItems(); +} + +int ListView_GetSelectionMark(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return 0; + const int n = lvs->GetNumItems(); + int x; + for (x=0;xget_sel(x)) return x; + return -1; +} +int SWELL_GetListViewHeaderHeight(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + return WDL_NORMALLY(lvs) ? lvs->GetColumnHeaderHeight(h) : 0; +} + +void ListView_SetColumnWidth(HWND h, int pos, int wid) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return; + SWELL_ListView_Col *col = lvs->GetColumnByIndex(pos); + WDL_ASSERT(col || !pos); + if (col) + { + col->xwid = wid; + InvalidateRect(h,NULL,FALSE); + } +} + +int ListView_HitTest(HWND h, LVHITTESTINFO *pinf) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !pinf)) return -1; + + pinf->flags=0; + pinf->iItem=-1; + + int x=pinf->pt.x; + int y=pinf->pt.y; + + RECT r; + GetClientRect(h,&r); + + if (x < 0) pinf->flags |= LVHT_TOLEFT; + if (x >= r.right) pinf->flags |= LVHT_TORIGHT; + if (y < lvs->GetColumnHeaderHeight(h)) pinf->flags |= LVHT_ABOVE; + if (y >= r.bottom) pinf->flags |= LVHT_BELOW; + + if (!pinf->flags && lvs->m_last_row_height) + { + const int ypos = y - lvs->GetColumnHeaderHeight(h); + const int hit = ypos >= 0 ? ((ypos + lvs->m_scroll_y) / lvs->m_last_row_height) : -1; + if (hit < 0) pinf->flags |= LVHT_ABOVE; + pinf->iItem=hit < 0 || hit >= lvs->GetNumItems() ? -1 : hit; + if (pinf->iItem >= 0) + { + if (lvs->m_status_imagelist && x < lvs->m_last_row_height) + { + pinf->flags=LVHT_ONITEMSTATEICON; + } + else + { + pinf->flags=LVHT_ONITEMLABEL; + } + } + else + { + pinf->flags=LVHT_NOWHERE; + } + } + + return pinf->iItem; +} +int ListView_SubItemHitTest(HWND h, LVHITTESTINFO *pinf) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !pinf)) return -1; + + const int row = ListView_HitTest(h, pinf); + int x,xpos=-lvs->m_scroll_x,idx=0; + const int n=lvs->m_cols.GetSize(); + const bool has_image = lvs->hasStatusImage(); + if (has_image) xpos += lvs->m_last_row_height; + for (x=0;xm_cols.Get()[x].xwid; + if (pinf->pt.x >= xpos && pinf->pt.x < xpos+xwid) { idx = lvs->m_cols.Get()[x].col_index; break; } + xpos += xwid; + } + pinf->iSubItem = idx; + return row; +} + +void ListView_SetItemCount(HWND h, int cnt) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs) || !lvs->IsOwnerData()) return; + lvs->m_owner_data_size = cnt > 0 ? cnt : 0; + if (lvs->m_owner_multisel_state.GetSize() > lvs->m_owner_data_size) lvs->m_owner_multisel_state.Resize(lvs->m_owner_data_size); + if (lvs->m_selitem >= lvs->m_owner_data_size) lvs->m_selitem = -1; +} + +void ListView_EnsureVisible(HWND h, int i, BOOL pok) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs) || !lvs->m_last_row_height) return; + const int n = lvs->GetNumItems(); + if (i>=0 && i < n) + { + const int row_height = lvs->m_last_row_height; + RECT r; + GetClientRect(h,&r); + r.bottom -= lvs->GetColumnHeaderHeight(h); + if (lvs->getTotalWidth() > r.right) r.bottom -= row_height; + + const int oldy = lvs->m_scroll_y; + if (i*row_height < lvs->m_scroll_y) lvs->m_scroll_y = i*row_height; + else if ((i+1)*row_height > lvs->m_scroll_y + r.bottom) lvs->m_scroll_y = (i+1)*row_height-r.bottom; + lvs->sanitizeScroll(h); + if (oldy != lvs->m_scroll_y) + { + InvalidateRect(h,NULL,FALSE); + } + } + +} +bool ListView_GetSubItemRect(HWND h, int item, int subitem, int code, RECT *r) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !r)) return false; + + r->top = lvs->m_last_row_height * item - lvs->m_scroll_y; + r->top += lvs->GetColumnHeaderHeight(h); + RECT cr; + GetClientRect(h,&cr); + r->left=cr.left; + r->right=cr.right; + + if (subitem>0) + { + int x,xpos=-lvs->m_scroll_x; + const int n=lvs->m_cols.GetSize(); + for (x = 0; x < n; x ++) + { + int xwid = lvs->m_cols.Get()[x].xwid; + if (!x && lvs->hasStatusImage()) xwid += lvs->m_last_row_height; + if (lvs->m_cols.Get()[x].col_index == subitem) + { + r->left=xpos; + r->right=xpos+xwid; + break; + } + xpos += xwid; + } + } + + if (r->top < -64-lvs->m_last_row_height) r->top = -64 - lvs->m_last_row_height; + if (r->top > cr.bottom+64) r->top = cr.bottom+64; + + r->bottom = r->top + lvs->m_last_row_height; + + return true; +} + +bool ListView_GetItemRect(HWND h, int item, RECT *r, int code) +{ + return ListView_GetSubItemRect(h, item, -1, code, r); +} + +bool ListView_Scroll(HWND h, int xscroll, int yscroll) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs) || !lvs->m_last_row_height) return false; + const int oldy = lvs->m_scroll_y, oldx = lvs->m_scroll_x; + lvs->m_scroll_x += xscroll; + lvs->m_scroll_y += yscroll; + lvs->sanitizeScroll(h); + if (oldy != lvs->m_scroll_y || oldx != lvs->m_scroll_x) + InvalidateRect(h,NULL,FALSE); + return true; +} + +void ListView_SortItems(HWND hwnd, PFNLVCOMPARE compf, LPARAM parm) +{ + listViewState *lvs = hwnd ? (listViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || + lvs->m_is_listbox || + lvs->m_owner_data_size >= 0 || !compf)) return; + + WDL_HeapBuf tmp; + char *b = (char*)tmp.ResizeOK(lvs->m_data.GetSize()*sizeof(void *)); + if (b) + __listview_mergesort_internal(lvs->m_data.GetList(),lvs->m_data.GetSize(), + sizeof(void *),compf,parm,(char*)b); + InvalidateRect(hwnd,NULL,FALSE); +} + +bool ListView_DeleteColumn(HWND h, int pos) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return false; + SWELL_ListView_Col *c = lvs->GetColumnByIndex(pos); + if (!c) return false; + + const int cidx = c->col_index; + free(c->name); + lvs->m_cols.Delete((int) (c - lvs->m_cols.Get())); + + for (int x = 0; x < lvs->m_cols.GetSize(); x ++) + { + c = lvs->m_cols.Get()+x; + WDL_ASSERT(c->col_index != cidx); + if (c->col_index > cidx) c->col_index--; + } + InvalidateRect(h,NULL,FALSE); + return true; +} + +int ListView_GetCountPerPage(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs) || + !lvs->m_last_row_height) return 0; + + RECT cr; + GetClientRect(h,&cr); + cr.bottom -= lvs->GetColumnHeaderHeight(h); + return (cr.bottom-cr.top) / lvs->m_last_row_height; +} + +HWND ChildWindowFromPoint(HWND h, POINT p) +{ + if (WDL_NOT_NORMALLY(!h)) return 0; + + RECT r={0,}; + + for(;;) + { + HWND h2=h->m_children; + RECT sr; + + NCCALCSIZE_PARAMS tr={{h->m_position,},}; + if (h->m_wndproc) h->m_wndproc(h,WM_NCCALCSIZE,0,(LPARAM)&tr); + r.left += tr.rgrc[0].left - h->m_position.left; + r.top += tr.rgrc[0].top - h->m_position.top; + + HWND best=NULL; + RECT bestsr = { 0, }; + while (h2) + { + sr = h2->m_position; + sr.left += r.left; + sr.right += r.left; + sr.top += r.top; + sr.bottom += r.top; + + if (h2->m_visible && PtInRect(&sr,p)) + { + bestsr = sr; + best=h2; + } + + h2 = h2->m_next; + } + if (!best) break; // h is the window we care about + + h=best; // descend to best + r=bestsr; + } + + return h; +} + +static bool isHitTestOpaque(HWND h, const POINT p) +{ + return h->m_visible && HTTRANSPARENT != SendMessage(h, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y)); +} + +static HWND recurseOwnedWindowHitTest(HWND h, POINT p, int maxdepth) +{ + RECT r; + GetWindowContentViewRect(h,&r); + if (!PtInRect(&r,p)) return NULL; + + // check any owned windows first, as they are always above our window + if (h->m_owned_list && maxdepth > 0) + { + HWND owned = h->m_owned_list; + while (owned) + { + if (isHitTestOpaque(owned, p)) + { + HWND hit = recurseOwnedWindowHitTest(owned,p,maxdepth-1); + if (hit) return hit; + } + owned = owned->m_owned_next; + } + } + p.x -= r.left; + p.y -= r.top; + return ChildWindowFromPoint(h,p); +} + +HWND WindowFromPoint(POINT p) +{ + HWND h = SWELL_topwindows; + while (h) + { + if (isHitTestOpaque(h, p)) + { + HWND hit = recurseOwnedWindowHitTest(h,p,20); + if (hit) return hit; + } + h = h->m_next; + } + return NULL; +} + +#ifdef _DEBUG +int g_swell_in_paint; +#endif + +BOOL InvalidateRect(HWND hwnd, const RECT *r, int eraseBk) +{ +#ifdef _DEBUG + if (g_swell_in_paint) + { + printf("swell-generic: calling InvalidateRect() from within paint, this is allowed but bad form.\n"); + // WDL_ASSERT(false); + } +#endif + + if (WDL_NOT_NORMALLY(!hwnd) || hwnd->m_hashaddestroy) return FALSE; + +#ifdef SWELL_LICE_GDI + RECT rect; + if (r) + { + rect = *r; + } + else + { + rect = hwnd->m_position; + WinOffsetRect(&rect, -rect.left, -rect.top); + } + + // rect is in client coordinates of h + HWND h = hwnd; + for (;;) + { + if (!h->m_visible || h->m_hashaddestroy) return FALSE; + + RECT ncrect = h->m_position; + if (h->m_oswindow) WinOffsetRect(&ncrect, -ncrect.left, -ncrect.top); + + NCCALCSIZE_PARAMS tr; + memset(&tr,0,sizeof(tr)); + tr.rgrc[0] = ncrect; + if (h->m_wndproc) h->m_wndproc(h,WM_NCCALCSIZE,0,(LPARAM)&tr); + + WinOffsetRect(&rect,tr.rgrc[0].left, tr.rgrc[0].top); + + if (!IntersectRect(&rect,&rect,&ncrect)) return FALSE; + + if (h->m_oswindow) break; + + h=h->m_parent; + if (!h) return FALSE; + } + + { + hwnd->m_invalidated=true; + HWND t=hwnd->m_parent; + if (t && (t->m_style & WS_CLIPSIBLINGS)) + { + // child window, invalidate any later children that intersect us (redraw them on top of us) + HWND nw = hwnd->m_next; + while (nw) + { + RECT tmp; + if (nw->m_visible && !nw->m_invalidated && WinIntersectRect(&tmp,&hwnd->m_position,&nw->m_position)) + nw->m_invalidated=true; + nw=nw->m_next; + } + } + + while (t) + { + if (eraseBk) + { + t->m_invalidated=true; + eraseBk--; + } + t->m_child_invalidated=true; + t=t->m_parent; + } + } + swell_oswindow_invalidate(h, (hwnd!=h || r) ? &rect : NULL); +#endif + return TRUE; +} + + +HWND GetCapture() +{ + return swell_captured_window; +} + +HWND SetCapture(HWND hwnd) +{ + WDL_ASSERT(hwnd != NULL); + HWND oc = swell_captured_window; + if (oc != hwnd) + { + swell_captured_window=hwnd; + if (oc) SendMessage(oc,WM_CAPTURECHANGED,0,(LPARAM)hwnd); + } + return oc; +} + +void ReleaseCapture() +{ + if (swell_captured_window) + { + SendMessage(swell_captured_window,WM_CAPTURECHANGED,0,0); + swell_captured_window=0; + } +} + +static HWND getNextFocusWindow(HWND hwnd, bool rev, HWND foc_child) +{ + HWND ch = NULL; + if (foc_child) + { + ch = hwnd->m_children; + while (ch && ch != foc_child) ch = ch->m_next; + } + + int pass=0; + if (ch) + { + ch = rev ? ch->m_prev : ch->m_next; + } + else + { + ch = hwnd->m_children; + if (ch && rev) while (ch->m_next) ch=ch->m_next; + pass++; + } + + for (;;) + { + while (ch) + { + // scan to find next matching control + if (ch->m_wantfocus && ch->m_visible && ch->m_enabled) break; + ch = rev ? ch->m_prev : ch->m_next; + } + if (ch || ++pass>1 || hwnd->m_parent) break; + + // continue searching + ch = hwnd->m_children; + if (ch && rev) while (ch->m_next) ch=ch->m_next; + } + if (ch && ch->m_children) + { + HWND sub = getNextFocusWindow(ch,rev,NULL); + if (sub) return sub; + } + return ch; +} + + +LRESULT SwellDialogDefaultWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + DLGPROC d=(DLGPROC)GetWindowLong(hwnd,DWL_DLGPROC); + if (d) + { + if (uMsg == WM_PAINT) + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + HBRUSH hbrush = (HBRUSH) d(hwnd,WM_CTLCOLORDLG,(WPARAM)ps.hdc,(LPARAM)hwnd); + if (hbrush && hbrush != (HBRUSH)1) + { + FillRect(ps.hdc,&ps.rcPaint,hbrush); + } + else if (1) + { + SWELL_FillDialogBackground(ps.hdc,&ps.rcPaint,0); + } + + EndPaint(hwnd,&ps); + } + } + + LRESULT r=(LRESULT) d(hwnd,uMsg,wParam,lParam); + + + if (r) return r; + + if (uMsg == WM_KEYDOWN) + { + if (!hwnd->m_parent) + { + if (wParam == VK_ESCAPE) + { + if (IsWindowEnabled(hwnd) && !SendMessage(hwnd,WM_CLOSE,0,0)) + SendMessage(hwnd,WM_COMMAND,IDCANCEL,0); + return 0; + } + else if (wParam == VK_RETURN) + { + HWND c = GetWindow(hwnd,GW_CHILD); + while (c) + { + if (c->m_id && (c->m_style&BS_DEFPUSHBUTTON) && + c->m_classname && !strcmp(c->m_classname,"Button")) + { + SendMessage(hwnd,WM_COMMAND,c->m_id,0); + return 0; + } + c = GetWindow(c,GW_HWNDNEXT); + } + c = GetDlgItem(hwnd,IDOK); + if (c) SendMessage(hwnd,WM_COMMAND,IDOK,0); + return 0; + } + } + int navdir = 0; + + if (wParam == VK_TAB && (lParam&~FSHIFT) == FVIRTKEY) navdir = (lParam & FSHIFT) ? -1 : 1; + else if ((lParam&0xff) == FVIRTKEY) + { + if (wParam == VK_LEFT || wParam == VK_UP) navdir = -1; + else if (wParam == VK_RIGHT || wParam == VK_DOWN) navdir = 1; + } + + if (navdir) + { + HWND ch = getNextFocusWindow(hwnd,navdir<0,hwnd->m_focused_child); + if (ch) + { + HWND oldfoc = GetFocus(); + SetFocus(ch); + if (oldfoc != ch) SWELL_OnNavigationFocus(ch); + InvalidateRect(ch,NULL,FALSE); + return 0; + } + } + } + if (uMsg == WM_CTLCOLORSTATIC) + { + // some calling code (not in swell) may expect to be able to query the text color/brush + SetTextColor((HDC)wParam, g_swell_ctheme.label_text); + static HBRUSH br; + if (!br) br = CreateSolidBrush(g_swell_ctheme._3dface); + return (LRESULT)br; + } + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + +BOOL EndPaint(HWND hwnd, PAINTSTRUCT *ps) +{ + WDL_ASSERT(hwnd != NULL); + return TRUE; +} + + +static HFONT menubar_font; + +static bool wantRightAlignedMenuBarItem(const char *p) +{ + char c = *p; + return c > 0 && c != '&' && !isalnum(c); +} + +#define MENUBAR_SELECTED_ITEM_XPAD \ + wdl_min(g_swell_ctheme.menubar_spacing_width,g_swell_ctheme.menubar_margin_width) + +static int menuBarHitTest(HWND hwnd, int mousex, int mousey, RECT *rOut, int forceItem, int expandedItem) +{ + int rv=-1; + RECT r; + GetWindowContentViewRect(hwnd,&r); + if (forceItem >= 0 || (mousey>=r.top && mousey < r.top+g_swell_ctheme.menubar_height)) + { + HDC dc = GetWindowDC(hwnd); + + const int xselpad = MENUBAR_SELECTED_ITEM_XPAD, xpad=g_swell_ctheme.menubar_spacing_width; + int x,xpos=r.left + g_swell_ctheme.menubar_margin_width; + HMENU__ *menu = (HMENU__*)hwnd->m_menu; + HGDIOBJ oldfont = dc ? SelectObject(dc,menubar_font) : NULL; + const int n=menu->items.GetSize(); + for(x=0;xitems.Get(x); + if (inf->fType == MFT_STRING && inf->dwTypeData) + { + bool dis = !!(inf->fState & MF_GRAYED); + RECT cr={0,}; + DrawText(dc,inf->dwTypeData,-1,&cr,DT_CALCRECT); + if (x == n-1 && wantRightAlignedMenuBarItem(inf->dwTypeData)) + { + xpos = wdl_max(xpos,r.right - g_swell_ctheme.menubar_margin_width - cr.right); + cr.right = r.right - g_swell_ctheme.menubar_margin_width - xpos; + } + + if (forceItem >=0 ? forceItem == x : + expandedItem < 0 ? (mousex >= xpos - xpad && mousex < xpos + cr.right + (xpad+1)*3/4) : + x == expandedItem ? (mousex >= xpos - xselpad && mousex < xpos + cr.right + xselpad) : + mousex >= xpos && mousex < xpos + cr.right + xpad - (x == expandedItem-1 ? xselpad : 0) + ) + { + if (!dis) + { + rOut->left = xpos - xselpad; + rOut->right = xpos + cr.right; + rOut->top = r.top; + rOut->bottom = r.top + g_swell_ctheme.menubar_height; + rv=x; + } + break; + } + + xpos+=cr.right+xpad; + } + } + + if (dc) + { + SelectObject(dc,oldfont); + ReleaseDC(hwnd,dc); + } + } + return rv; +} + +static RECT g_menubar_lastrect; +static HWND g_menubar_active; +static bool g_menubar_active_drag; + +HWND swell_window_wants_all_input() +{ + return g_menubar_active_drag ? g_menubar_active : NULL; +} + +int menuBarNavigate(int dir) // -1 if no menu bar active, 0 if did nothing, 1 if navigated +{ + if (!g_menubar_active || !g_menubar_active->m_menu) return -1; + HMENU__ *menu = (HMENU__*)g_menubar_active->m_menu; + RECT r; + const int x = menuBarHitTest(g_menubar_active,0,0,&r,menu->sel_vis + dir, -1); + if (x>=0) + { + MENUITEMINFO *inf = menu->items.Get(x); + if (inf && inf->hSubMenu) + { + menu->sel_vis = x; + g_menubar_lastrect = r; + + DestroyPopupMenus(); + return 1; + } + } + return 0; +} + +RECT g_trackpopup_yroot; +static void runMenuBar(HWND hwnd, HMENU__ *menu, int x, const RECT *use_r, int flag) +{ + menu->Retain(); + MENUITEMINFO *inf = menu->items.Get(x); + RECT r = *use_r; + g_trackpopup_yroot = r; + + RECT mbr; + GetWindowContentViewRect(hwnd,&mbr); + mbr.right -= mbr.left; + mbr.left=0; + mbr.bottom = 0; + mbr.top = -g_swell_ctheme.menubar_height; + menu->sel_vis = x; + g_menubar_active = hwnd; + g_menubar_active_drag=true; + for (;;) + { + InvalidateRect(hwnd,&mbr,FALSE); + if (TrackPopupMenu(inf->hSubMenu,0,r.left,r.bottom,flag,hwnd,NULL) || menu->sel_vis == x) break; + + x = menu->sel_vis; + inf = menu->items.Get(x); + if (!inf || !inf->hSubMenu) break; + + r = g_menubar_lastrect; + } + menu->sel_vis=-1; + InvalidateRect(hwnd,&mbr,FALSE); + g_menubar_active = NULL; + g_trackpopup_yroot.top = g_trackpopup_yroot.bottom = 0; + menu->Release(); +} + +LRESULT DefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_DESTROY: + if (g_menubar_active == hwnd) g_menubar_active=NULL; + break; + case WM_NCMOUSEMOVE: + if (g_menubar_active == hwnd && hwnd->m_menu) + { + swell_delegate_menu_message(hwnd,lParam,WM_MOUSEMOVE,true); + + HMENU__ *menu = (HMENU__*)hwnd->m_menu; + RECT r; + const int x = menuBarHitTest(hwnd,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),&r,-1, menu->sel_vis); + if (x>=0 && x != menu->sel_vis) + { + MENUITEMINFO *inf = menu->items.Get(x); + if (inf && inf->hSubMenu) + { + menu->sel_vis = x; + g_menubar_lastrect = r; + + DestroyPopupMenus(); // cause new menu to be popped up + } + } + } + break; + + case WM_NCCALCSIZE: + if (!hwnd->m_parent && hwnd->m_menu) + { + RECT *r = (RECT*)lParam; + r->top += g_swell_ctheme.menubar_height; + } + break; + case WM_NCPAINT: + if (!hwnd->m_parent && hwnd->m_menu) + { + HDC dc = GetWindowDC(hwnd); + if (dc) + { + if (!menubar_font) + menubar_font = CreateFont(g_swell_ctheme.menubar_font_size,0,0,0,FW_NORMAL,0,0,0,0,0,0,0,0,g_swell_deffont_face); + + RECT r; + GetWindowContentViewRect(hwnd,&r); + r.right -= r.left; r.left=0; + r.bottom -= r.top; r.top=0; + if (r.bottom>g_swell_ctheme.menubar_height) r.bottom=g_swell_ctheme.menubar_height; + + bool isactive = false; + if (swell_is_app_inactive() == 0) // draw as inactive if maybe inactive + { + HWND foc = GetFocus(); + if (foc && (foc == hwnd || IsChild(hwnd,foc))) isactive = true; + } + + { + HBRUSH br=CreateSolidBrush(isactive ? g_swell_ctheme.menubar_bg : g_swell_ctheme.menubar_bg_inactive); + FillRect(dc,&r,br); + DeleteObject(br); + } + + HGDIOBJ oldfont = SelectObject(dc,menubar_font); + SetBkMode(dc,TRANSPARENT); + + int x,xpos=g_swell_ctheme.menubar_margin_width; + HMENU__ *menu = (HMENU__*)hwnd->m_menu; + const int n = menu->items.GetSize(); + for(x=0;xitems.Get(x); + if (inf->fType == MFT_STRING && inf->dwTypeData) + { + bool dis = !!(inf->fState & MF_GRAYED); + RECT cr={0}; + DrawText(dc,inf->dwTypeData,-1,&cr,DT_CALCRECT|DT_SINGLELINE); + + if (x == n-1 && wantRightAlignedMenuBarItem(inf->dwTypeData)) + { + cr.left = wdl_max(xpos,r.right - g_swell_ctheme.menubar_margin_width - cr.right); + cr.right = r.right - g_swell_ctheme.menubar_margin_width; + } + else + { + cr.left = xpos; + cr.right += xpos; + } + cr.top = r.top; + cr.bottom = r.bottom; + if (!dis && menu->sel_vis == x) + { + RECT crb = cr; + crb.left -= MENUBAR_SELECTED_ITEM_XPAD; + crb.right += MENUBAR_SELECTED_ITEM_XPAD; + HBRUSH br = CreateSolidBrush(g_swell_ctheme.menubar_bg_sel); + FillRect(dc,&crb,br); + DeleteObject(br); + SetTextColor(dc,g_swell_ctheme.menubar_text_sel); + } + else SetTextColor(dc, + !isactive ? g_swell_ctheme.menubar_text_inactive : + dis ? g_swell_ctheme.menubar_text_disabled : + g_swell_ctheme.menubar_text); + + DrawText(dc,inf->dwTypeData,-1,&cr,DT_VCENTER|DT_LEFT|DT_SINGLELINE); + xpos=cr.right+g_swell_ctheme.menubar_spacing_width; + } + } + + SelectObject(dc,oldfont); + ReleaseDC(hwnd,dc); + } + } + break; + case WM_RBUTTONUP: + case WM_NCRBUTTONUP: + { + POINT p={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)}; + HWND hwndDest=hwnd; + if (msg==WM_RBUTTONUP) + { + ClientToScreen(hwnd,&p); + HWND h=WindowFromPoint(p); + if (h && IsChild(hwnd,h)) hwndDest=h; + } + if (hwnd == s_last_rbuttondown) + SendMessage(hwnd,WM_CONTEXTMENU,(WPARAM)hwndDest,(p.x&0xffff)|(p.y<<16)); + } + return 1; + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + if (!hwnd->m_parent && hwnd->m_menu) + { + if (msg == WM_NCLBUTTONUP && g_menubar_active_drag) + { + g_menubar_active_drag=false; + if (swell_delegate_menu_message(hwnd,lParam,WM_LBUTTONUP,true)) return 0; + + POINT pt; + GetCursorPos(&pt); + + RECT r; + GetWindowContentViewRect(hwnd,&r); + r.bottom = r.top + g_swell_ctheme.menubar_height; + + if (!PtInRect(&r,pt)) + { + DestroyPopupMenus(); + return 0; + } + } + RECT r; + const int x = menuBarHitTest(hwnd,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),&r,-1,-1); + if (x>=0) + { + HMENU__ *menu = (HMENU__*)hwnd->m_menu; + MENUITEMINFO *inf = menu->items.Get(x); + if (inf) + { + if (inf->hSubMenu) + { + if (msg == WM_NCLBUTTONDOWN) + { + runMenuBar(hwnd,menu,x,&r,0xbeef); + } + } + else if (msg == WM_NCLBUTTONUP) + { + if (inf->wID) SendMessage(hwnd,WM_COMMAND,inf->wID,0); + } + } + } + } + break; + case WM_NCHITTEST: + if (!hwnd->m_parent && hwnd->m_menu) + { + RECT r; + GetWindowContentViewRect(hwnd,&r); + if (GET_Y_LPARAM(lParam)>=r.top && GET_Y_LPARAM(lParam) < r.top+g_swell_ctheme.menubar_height) return HTMENU; + } + // todo: WM_NCCALCSIZE etc + return HTCLIENT; + case WM_KEYDOWN: + case WM_KEYUP: + if (hwnd->m_parent) return SendMessage(hwnd->m_parent,msg,wParam,lParam); + + if (msg == WM_KEYDOWN && hwnd->m_menu && + lParam == (FVIRTKEY | FALT) && ( + (wParam >= 'A' && wParam <= 'Z') || + (wParam >= '0' && wParam <= '9') + ) + ) + { + HMENU__ *menu = (HMENU__*)hwnd->m_menu; + const int n=menu->items.GetSize(); + for(int x=0;xitems.Get(x); + if (inf->fType == MFT_STRING && + !(inf->fState & MF_GRAYED) && + inf->dwTypeData) + { + const char *p = inf->dwTypeData; + while (*p) + { + if (*p++ == '&') + { + if (*p != '&') break; + p++; + } + } + if (*p > 0 && (WPARAM)toupper(*p) == wParam) + { + if (inf->hSubMenu) + { + RECT r; + if (menuBarHitTest(hwnd,0,0,&r,x,-1)>=0) + { + runMenuBar(hwnd,menu,x,&r,0xbeee); + } + } + else + { + if (inf->wID) SendMessage(hwnd,WM_COMMAND,inf->wID,0); + } + + return 1; + } + } + } + } + return 69; + + case WM_CONTEXTMENU: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_SETCURSOR: + return hwnd->m_parent ? SendMessage(hwnd->m_parent,msg,wParam,lParam) : 0; + + case WM_SETFONT: + hwnd->m_font = (HFONT)wParam; + return 0; + + case WM_GETFONT: + return (LRESULT)(hwnd->m_font ? hwnd->m_font : SWELL_GetDefaultFont()); + + case WM_DROPFILES: + if (hwnd->m_parent && wParam && !(GetWindowLong(hwnd,GWL_EXSTYLE)&WS_EX_ACCEPTFILES)) + { + DROPFILES *df=(DROPFILES*)wParam; + ClientToScreen(hwnd,&df->pt); + ScreenToClient(hwnd->m_parent,&df->pt); + + return SendMessage(hwnd->m_parent,msg,wParam,lParam); + } + return 0; + + } + return 0; +} + + + + + + + + + + + + + + + + + + +///////////////// clipboard compatability (NOT THREAD SAFE CURRENTLY) + + +BOOL DragQueryPoint(HDROP hDrop,LPPOINT pt) +{ + if (!hDrop) return 0; + DROPFILES *df=(DROPFILES*)GlobalLock(hDrop); + BOOL rv=!df->fNC; + *pt=df->pt; + GlobalUnlock(hDrop); + return rv; +} + +void DragFinish(HDROP hDrop) +{ +//do nothing for now (caller will free hdrops) +} + +UINT DragQueryFile(HDROP hDrop, UINT wf, char *buf, UINT bufsz) +{ + if (!hDrop) return 0; + DROPFILES *df=(DROPFILES*)GlobalLock(hDrop); + + UINT rv=0; + char *p=(char*)df + df->pFiles; + if (wf == 0xFFFFFFFF) + { + while (*p) + { + rv++; + p+=strlen(p)+1; + } + } + else + { + while (*p) + { + if (!wf--) + { + if (buf) + { + lstrcpyn_safe(buf,p,bufsz); + rv=strlen(buf); + } + else rv=strlen(p); + + break; + } + p+=strlen(p)+1; + } + } + GlobalUnlock(hDrop); + return rv; +} + + +///////// PostMessage emulation + +BOOL PostMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + WDL_ASSERT(hwnd != NULL); + return SWELL_Internal_PostMessage(hwnd,message,wParam,lParam); +} + +void SWELL_MessageQueue_Clear(HWND h) +{ + SWELL_Internal_PMQ_ClearAllMessages(h); +} + + + +// implementation of postmessage stuff + + + + +typedef struct PMQ_rec +{ + HWND hwnd; + UINT msg; + WPARAM wParam; + LPARAM lParam; + + struct PMQ_rec *next; +} PMQ_rec; + +static WDL_Mutex m_pmq_mutex; +static PMQ_rec *m_pmq, *m_pmq_empty, *m_pmq_tail; +static int m_pmq_size; + +#define MAX_POSTMESSAGE_SIZE 1024 + + +void SWELL_Internal_PostMessage_Init() +{ +} + +void SWELL_MessageQueue_Flush() +{ + m_pmq_mutex.Enter(); + int max_amt = m_pmq_size; + PMQ_rec *p=m_pmq; + if (p) + { + m_pmq = p->next; + if (m_pmq_tail == p) m_pmq_tail=NULL; + m_pmq_size--; + } + m_pmq_mutex.Leave(); + + // process out up to max_amt of queue + while (p) + { + SendMessage(p->hwnd,p->msg,p->wParam,p->lParam); + + m_pmq_mutex.Enter(); + // move this message to empty list + p->next=m_pmq_empty; + m_pmq_empty = p; + + // get next queued message (if within limits) + p = (--max_amt > 0) ? m_pmq : NULL; + if (p) + { + m_pmq = p->next; + if (m_pmq_tail == p) m_pmq_tail=NULL; + m_pmq_size--; + } + m_pmq_mutex.Leave(); + } +} + +void SWELL_Internal_PMQ_ClearAllMessages(HWND hwnd) +{ + m_pmq_mutex.Enter(); + PMQ_rec *p=m_pmq; + PMQ_rec *lastrec=NULL; + while (p) + { + if (hwnd && p->hwnd != hwnd) { lastrec=p; p=p->next; } + else + { + PMQ_rec *next=p->next; + + p->next=m_pmq_empty; // add p to empty list + m_pmq_empty=p; + m_pmq_size--; + + + if (p==m_pmq_tail) m_pmq_tail=lastrec; // update tail + + if (lastrec) p = lastrec->next = next; + else p = m_pmq = next; + } + } + m_pmq_mutex.Leave(); +} + +BOOL SWELL_Internal_PostMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (WDL_NOT_NORMALLY(!hwnd) || hwnd->m_hashaddestroy) return FALSE; + + BOOL ret=FALSE; + m_pmq_mutex.Enter(); + + if (m_pmq_empty||m_pmq_sizenext; + else rec=(PMQ_rec*)malloc(sizeof(PMQ_rec)); + rec->next=0; + rec->hwnd=hwnd; + rec->msg=msg; + rec->wParam=wParam; + rec->lParam=lParam; + + if (m_pmq_tail) m_pmq_tail->next=rec; + else + { + PMQ_rec *p=m_pmq; + while (p && p->next) p=p->next; // shouldnt happen unless m_pmq is NULL As well but why not for safety + if (p) p->next=rec; + else m_pmq=rec; + } + m_pmq_tail=rec; + m_pmq_size++; + + ret=TRUE; + } + + m_pmq_mutex.Leave(); + + return ret; +} + + +int EnumPropsEx(HWND hwnd, PROPENUMPROCEX proc, LPARAM lParam) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return -1; + int x; + for (x =0 ; x < hwnd->m_props.GetSize(); x ++) + { + const char *k=""; + void *p = hwnd->m_props.Enumerate(x,&k); + if (!proc(hwnd,k,p,lParam)) return 0; + } + return 1; +} + +HANDLE GetProp(HWND hwnd, const char *name) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return NULL; + return hwnd->m_props.Get(name); +} + +BOOL SetProp(HWND hwnd, const char *name, HANDLE val) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return false; + hwnd->m_props.Insert(name,(void *)val); + return TRUE; +} + +HANDLE RemoveProp(HWND hwnd, const char *name) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return NULL; + HANDLE h =GetProp(hwnd,name); + hwnd->m_props.Delete(name); + return h; +} + + +int GetSystemMetrics(int p) +{ + switch (p) + { + case SM_CXSCREEN: + case SM_CYSCREEN: + { + RECT r; + SWELL_GetViewPort(&r, NULL, false); + return p==SM_CXSCREEN ? r.right-r.left : r.bottom-r.top; + } + case SM_CXHSCROLL: + case SM_CYHSCROLL: + case SM_CXVSCROLL: + case SM_CYVSCROLL: return g_swell_ctheme.smscrollbar_width; + case SM_CYMENU: return g_swell_ctheme.menubar_height; + } + return 0; +} + +BOOL ScrollWindow(HWND hwnd, int xamt, int yamt, const RECT *lpRect, const RECT *lpClipRect) +{ + if (WDL_NOT_NORMALLY(!hwnd) || (!xamt && !yamt)) return FALSE; + InvalidateRect(hwnd,NULL,FALSE); + + // move child windows only + hwnd = hwnd->m_children; + while (hwnd) + { + hwnd->m_position.left += xamt; + hwnd->m_position.right += xamt; + hwnd->m_position.top += yamt; + hwnd->m_position.bottom += yamt; + + hwnd=hwnd->m_next; + } + return TRUE; +} + +HWND FindWindowEx(HWND par, HWND lastw, const char *classname, const char *title) +{ + HWND h=lastw?GetWindow(lastw,GW_HWNDNEXT):par?GetWindow(par,GW_CHILD):SWELL_topwindows; + while (h) + { + bool isOk=true; + if (title && strcmp(title,h->m_title.Get())) isOk=false; + else if (classname) + { + if (!h->m_classname || strcmp(classname,h->m_classname)) isOk=false; + } + + if (isOk) return h; + h=GetWindow(h,GW_HWNDNEXT); + } + return NULL; +} + + +HTREEITEM TreeView_InsertItem(HWND hwnd, TV_INSERTSTRUCT *ins) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs || !ins)) return NULL; + + HTREEITEM__ *par=NULL; + int inspos=0; + + if (ins->hParent && ins->hParent != TVI_ROOT && ins->hParent != TVI_FIRST && ins->hParent != TVI_LAST && ins->hParent != TVI_SORT) + { + if (tvs->findItem(ins->hParent,&par,&inspos)) + { + par = ins->hParent; + } + else return 0; + } + + if (ins->hInsertAfter == TVI_FIRST) inspos=0; + else if (ins->hInsertAfter == TVI_LAST || ins->hInsertAfter == TVI_SORT || !ins->hInsertAfter) + inspos=(par ? par : &tvs->m_root)->m_children.GetSize(); + else inspos = (par ? par : &tvs->m_root)->m_children.Find(ins->hInsertAfter)+1; + + HTREEITEM__ *item=new HTREEITEM__; + if (ins->item.mask & TVIF_CHILDREN) item->m_haschildren = !!ins->item.cChildren; + if (ins->item.mask & TVIF_PARAM) item->m_param = ins->item.lParam; + if (ins->item.mask & TVIF_TEXT) item->m_value = strdup(ins->item.pszText); + + (par ? par : &tvs->m_root)->m_children.Insert(inspos,item); + + InvalidateRect(hwnd,NULL,FALSE); + return item; +} + +BOOL TreeView_Expand(HWND hwnd, HTREEITEM item, UINT flag) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs || !tvs->findItem(item,NULL,NULL))) return FALSE; + + if (flag == TVE_EXPAND && (item->m_state & TVIS_EXPANDED)) return TRUE; + if (flag == TVE_COLLAPSE && !(item->m_state & TVIS_EXPANDED)) return TRUE; + + NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},}; + nmhdr.action = flag; + nmhdr.itemNew.hItem=item; + nmhdr.itemNew.lParam=item->m_param; + if (SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr)) return TRUE; + + if (flag == TVE_EXPAND) item->m_state |= TVIS_EXPANDED; + else if (flag == TVE_COLLAPSE) item->m_state &= ~TVIS_EXPANDED; + else if (flag == TVE_TOGGLE) item->m_state ^= TVIS_EXPANDED; + + InvalidateRect(hwnd,NULL,FALSE); + return TRUE; +} + +HTREEITEM TreeView_GetSelection(HWND hwnd) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs) || + !tvs->m_sel || !tvs->findItem(tvs->m_sel,NULL,NULL)) return NULL; + return tvs->m_sel; +} + +void TreeView_DeleteItem(HWND hwnd, HTREEITEM item) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs)) return; + HTREEITEM par=NULL; + int idx=0; + if (!tvs->findItem(item,&par,&idx)) return; + + if (tvs->m_sel && (item == tvs->m_sel || item->FindItem(tvs->m_sel,NULL,NULL))) tvs->m_sel=par; + + (par ? par : &tvs->m_root)->m_children.Delete(idx,true); + InvalidateRect(hwnd,NULL,FALSE); +} + +void TreeView_DeleteAllItems(HWND hwnd) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs)) return; + tvs->m_root.m_children.Empty(true); + tvs->m_sel=NULL; + InvalidateRect(hwnd,NULL,FALSE); +} + +void TreeView_EnsureVisible(HWND hwnd, HTREEITEM item) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs)) return; + if (!item || !tvs->findItem(item,NULL,NULL)) return; + tvs->ensureItemVisible(hwnd,item); + InvalidateRect(hwnd,NULL,FALSE); +} + +void TreeView_SelectItem(HWND hwnd, HTREEITEM item) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs)) return; + + if (tvs->m_sel == item || (item && !tvs->findItem(item,NULL,NULL))) return; + + tvs->m_sel = item; + + static int __rent; + if (!__rent) + { + __rent++; + NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},}; + nm.itemNew.hItem = item; + nm.itemNew.lParam = item ? item->m_param : 0; + SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + __rent--; + } + tvs->ensureItemVisible(hwnd,tvs->m_sel); + InvalidateRect(hwnd,NULL,FALSE); +} + +BOOL TreeView_GetItem(HWND hwnd, LPTVITEM pitem) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs || !pitem) || + !(pitem->mask & TVIF_HANDLE) || !(pitem->hItem)) return FALSE; + + HTREEITEM ti = pitem->hItem; + pitem->cChildren = ti->m_haschildren ? 1:0; + pitem->lParam = ti->m_param; + if ((pitem->mask&TVIF_TEXT)&&pitem->pszText&&pitem->cchTextMax>0) + { + lstrcpyn_safe(pitem->pszText,ti->m_value?ti->m_value:"",pitem->cchTextMax); + } + pitem->state=(ti == tvs->m_sel ? TVIS_SELECTED : 0) | (ti->m_state & TVIS_EXPANDED); + + return TRUE; +} + +BOOL TreeView_SetItem(HWND hwnd, LPTVITEM pitem) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs || !pitem) || + !(pitem->mask & TVIF_HANDLE) || !(pitem->hItem)) return FALSE; + + if (!tvs->findItem(pitem->hItem,NULL,NULL)) return FALSE; + + HTREEITEM__ *ti = (HTREEITEM__*)pitem->hItem; + + if (pitem->mask & TVIF_CHILDREN) ti->m_haschildren = pitem->cChildren?1:0; + if (pitem->mask & TVIF_PARAM) ti->m_param = pitem->lParam; + + if ((pitem->mask&TVIF_TEXT)&&pitem->pszText) + { + free(ti->m_value); + ti->m_value=strdup(pitem->pszText); + InvalidateRect(hwnd, 0, FALSE); + } + + ti->m_state = (ti->m_state & ~pitem->stateMask) | (pitem->state & pitem->stateMask &~ TVIS_SELECTED); + + if (pitem->stateMask & pitem->state & TVIS_SELECTED) + { + tvs->m_sel = ti; + static int __rent; + if (!__rent) + { + __rent++; + NMTREEVIEW nm={{hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},}; + nm.itemNew.hItem = ti; + nm.itemNew.lParam = ti ? ti->m_param : 0; + SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + __rent--; + } + } + + InvalidateRect(hwnd,NULL,FALSE); + + return TRUE; +} + +HTREEITEM TreeView_HitTest(HWND hwnd, TVHITTESTINFO *hti) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs || !hti) || + !tvs->m_last_row_height) return NULL; + + RECT r; + GetClientRect(hwnd,&r); + if (!PtInRect(&r,hti->pt)) return NULL; + + int y = hti->pt.y + tvs->m_scroll_y + tvs->m_last_row_height; + HTREEITEM item = tvs->hitTestItem(&tvs->m_root,&y,NULL); + if (!item) + { + hti->flags |= TVHT_BELOW; + } + return item; +} + +HTREEITEM TreeView_GetRoot(HWND hwnd) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs)) return NULL; + return tvs->m_root.m_children.Get(0); +} + +HTREEITEM TreeView_GetParent(HWND hwnd, HTREEITEM item) +{ + if (!item) return TreeView_GetRoot(hwnd); + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + + HTREEITEM par=NULL; + int idx=0; + if (WDL_NOT_NORMALLY(!tvs || !tvs->findItem(item,&par,&idx))) return NULL; + return par; +} + +HTREEITEM TreeView_GetChild(HWND hwnd, HTREEITEM item) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!tvs)) return NULL; + return (item && item != TVI_ROOT ? item : &tvs->m_root)->m_children.Get(0); +} + +HTREEITEM TreeView_GetNextSibling(HWND hwnd, HTREEITEM item) +{ + treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL; + + HTREEITEM par=NULL; + int idx=0; + if (WDL_NOT_NORMALLY(!tvs || !tvs->findItem(item,&par,&idx))) return NULL; + + return (par ? par : &tvs->m_root)->m_children.Get(idx+1); +} +BOOL TreeView_SetIndent(HWND hwnd, int indent) +{ + return FALSE; +} + +void TreeView_SetBkColor(HWND hwnd, int color) +{ + // todo implement treeview colors +} +void TreeView_SetTextColor(HWND hwnd, int color) +{ + // todo implement treeview colors +} + +#define IS_CLASSNAME_LISTVIEW_LISTBOX(x) ((x) && (!strcmp(x,"SysListView32") || !strcmp(x,"ListBox"))) + +void ListView_SetBkColor(HWND h, int color) +{ + if (WDL_NORMALLY(h && h->m_private_data && IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname))) + { + listViewState *lvs = (listViewState *)h->m_private_data; + if (lvs) lvs->m_color_bg = color; + } +} +void ListView_SetTextBkColor(HWND h, int color) +{ +} +void ListView_SetTextColor(HWND h, int color) +{ + if (WDL_NORMALLY(h && h->m_private_data && IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname))) + { + listViewState *lvs = (listViewState *)h->m_private_data; + lvs->m_color_text = color; + } +} +void ListView_SetGridColor(HWND h, int color) +{ + if (WDL_NORMALLY(h && h->m_private_data && IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname))) + { + listViewState *lvs = (listViewState *)h->m_private_data; + lvs->m_color_grid = color; + } +} +void ListView_SetSelColors(HWND h, int *colors, int ncolors) +{ + if (WDL_NORMALLY(h && h->m_private_data && h->m_classname)) + { + if (IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname)) + { + listViewState *lvs = (listViewState *)h->m_private_data; + if (colors && ncolors > 0) + memcpy(lvs->m_color_extras,colors,wdl_min(ncolors*sizeof(int),sizeof(lvs->m_color_extras))); + } + else if (!strcmp(h->m_classname,"SysTreeView32")) + { + // todo implement treeview colors + } + else + { + WDL_ASSERT(false); + } + } +} +int ListView_GetTopIndex(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs) || !lvs->m_last_row_height) return 0; + return lvs->m_scroll_y / lvs->m_last_row_height; +} +BOOL ListView_GetColumnOrderArray(HWND h, int cnt, int* arr) +{ + if (WDL_NOT_NORMALLY(!arr)) return FALSE; + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !lvs->HasColumnHeaders(h))) return FALSE; + + for (int x=0;xm_cols.GetSize() ? lvs->m_cols.Get()[x].col_index : x; + return TRUE; +} +BOOL ListView_SetColumnOrderArray(HWND h, int cnt, int* arr) +{ + if (!arr) return FALSE; + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs || !lvs->HasColumnHeaders(h))) return FALSE; + + WDL_TypedBuf tmp; + + int x; + // O(N^2) but even with 1000 columns who cares? + for (x = 0; x < cnt; x ++) + { + SWELL_ListView_Col *c = lvs->GetColumnByIndex(arr[x]); + if (WDL_NORMALLY(c != NULL)) + { + tmp.Add(*c); + lvs->m_cols.Delete((int) (c - lvs->m_cols.Get())); + } + } + WDL_ASSERT(lvs->m_cols.GetSize()==0); + for (x = 0; x < tmp.GetSize(); x ++) + { + lvs->m_cols.Add(tmp.Get()+x,1); + } + + return TRUE; +} +HWND ListView_GetHeader(HWND h) +{ + return h; +} + +int Header_GetItemCount(HWND h) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + return WDL_NORMALLY(lvs) ? lvs->m_cols.GetSize() : 0; +} + +BOOL Header_GetItem(HWND h, int col, HDITEM* hi) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return FALSE; + const SWELL_ListView_Col *c = lvs->GetColumnByIndex(col); + if (!c) return FALSE; + + if (hi->mask&HDI_FORMAT) + { + if (c->sortindicator<0) hi->fmt = HDF_SORTUP; + else if (c->sortindicator>0) hi->fmt = HDF_SORTDOWN; + else hi->fmt=0; + } + + return TRUE; +} + +BOOL Header_SetItem(HWND h, int col, HDITEM* hi) +{ + listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL; + if (WDL_NOT_NORMALLY(!lvs)) return FALSE; + SWELL_ListView_Col *c = lvs->GetColumnByIndex(col); + if (!c) return FALSE; + + if (hi->mask&HDI_FORMAT) + { + if (hi->fmt & HDF_SORTUP) c->sortindicator=-1; + else if (hi->fmt & HDF_SORTDOWN) c->sortindicator=1; + else c->sortindicator=0; + } + + return TRUE; +} + + +BOOL EnumChildWindows(HWND hwnd, BOOL (*cwEnumFunc)(HWND,LPARAM),LPARAM lParam) +{ + if (WDL_NORMALLY(hwnd) && hwnd->m_children) + { + HWND n=hwnd->m_children; + while (n) + { + if (!cwEnumFunc(n,lParam) || !EnumChildWindows(n,cwEnumFunc,lParam)) return FALSE; + n = n->m_next; + } + } + return TRUE; +} +void SWELL_GetDesiredControlSize(HWND hwnd, RECT *r) +{ +} + +BOOL SWELL_IsGroupBox(HWND hwnd) +{ + return WDL_NORMALLY(hwnd) && hwnd->m_classname && !stricmp(hwnd->m_classname,"Button") && (hwnd->m_style & BS_GROUPBOX); +} +BOOL SWELL_IsButton(HWND hwnd) +{ + return WDL_NORMALLY(hwnd) && hwnd->m_classname && !stricmp(hwnd->m_classname,"Button") && !(hwnd->m_style & BS_GROUPBOX); +} +BOOL SWELL_IsStaticText(HWND hwnd) +{ + return WDL_NORMALLY(hwnd) && hwnd->m_classname && !stricmp(hwnd->m_classname,"Static"); +} + + +BOOL ShellExecute(HWND hwndDlg, const char *action, const char *content1, const char *content2, const char *content3, int blah) +{ + const char *xdg = "/usr/bin/xdg-open"; + const char *argv[3] = { NULL }; + char *tmp=NULL; + + if (!content1 || !*content1) return FALSE; + + if (!strnicmp(content1,"http://",7) || !strnicmp(content1,"https://",8)) + { + argv[0] = xdg; + argv[1] = content1; + } + else if (!stricmp(content1,"explorer.exe")) + { + const char *fn = content2; + if (fn && !strnicmp(fn, "/select,\"", 9)) + { + tmp = strdup(fn+9); + if (*tmp && tmp[strlen(tmp)-1]=='\"') tmp[strlen(tmp)-1]='\0'; + WDL_remove_filepart(tmp); + fn = tmp; + } + if (!fn || !*fn) return FALSE; + + argv[0] = xdg; + argv[1] = fn; + } + else if (!stricmp(content1,"notepad.exe")||!stricmp(content1,"notepad")) + { + if (!content2 || !*content2) return FALSE; + argv[0] = xdg; + argv[1] = content2; + } + else + { + if (content2 && *content2) + { + argv[0] = content1; + argv[1] = content2; + } + else + { + argv[0] = xdg; + argv[1] = content1; // default to xdg-open for whatever else + } + } + + const pid_t pid = fork(); + if (pid == 0) + { + for (int x=0;argv[x];x++) argv[x] = strdup(argv[x]); + execv(argv[0],(char *const*)argv); + exit(0); // if execv fails for some reason + } + free(tmp); + return pid>0; +} + + + +static LRESULT WINAPI focusRectWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + if (BeginPaint(hwnd,&ps)) + { + RECT r; + GetClientRect(hwnd,&r); + HBRUSH br = CreateSolidBrushAlpha(g_swell_ctheme.focusrect,0.5f); + HPEN pen = CreatePen(0,PS_SOLID,g_swell_ctheme.focusrect); + HGDIOBJ oldbr = SelectObject(ps.hdc,br); + HGDIOBJ oldpen = SelectObject(ps.hdc,pen); + Rectangle(ps.hdc,0,0,r.right,r.bottom); + SelectObject(ps.hdc,oldbr); + SelectObject(ps.hdc,oldpen); + DeleteObject(br); + DeleteObject(pen); + EndPaint(hwnd,&ps); + } + } + return 0; + + } + return DefWindowProc(hwnd,uMsg,wParam,lParam); +} + +// r=NULL to "free" handle +// otherwise r is in hwndPar coordinates +void SWELL_DrawFocusRect(HWND hwndPar, RECT *rct, void **handle) +{ + if (WDL_NOT_NORMALLY(!handle)) return; + + HWND h = (HWND) *handle; + if (h && (!rct || h->m_parent != hwndPar)) + { + DestroyWindow(h); + h->Release(); + *handle = NULL; + h = NULL; + } + + if (rct) + { + if (!h) + { + h = new HWND__(hwndPar,0,rct,"",false,focusRectWndProc); + h->m_style = WS_CHILD; // using this for top-level will also keep it out of the window list + h->Retain(); + *handle = h; + ShowWindow(h,SW_SHOWNA); + } + SetWindowPos(h,HWND_TOP,rct->left,rct->top,rct->right-rct->left,rct->bottom-rct->top,SWP_NOACTIVATE); + InvalidateRect(h,NULL,FALSE); + } + + if (hwndPar) + { + InvalidateRect(hwndPar,NULL,FALSE); + UpdateWindow(hwndPar); + } +} + +void SWELL_BroadcastMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HWND h = SWELL_topwindows; + while (h) + { + SendMessage(h,uMsg,wParam,lParam); + if (uMsg == WM_DISPLAYCHANGE) + InvalidateRect(h,NULL,FALSE); + h = h->m_next; + } +} + +void SetOpaque(HWND h, bool opaque) +{ +} +void SetAllowNoMiddleManRendering(HWND h, bool allow) +{ +} +int SWELL_GetDefaultButtonID(HWND hwndDlg, bool onlyIfEnabled) +{ + return 0; +} + +void SWELL_HideApp() +{ +} + +BOOL SWELL_GetGestureInfo(LPARAM lParam, GESTUREINFO* gi) +{ + return FALSE; +} + +void SWELL_SetWindowWantRaiseAmt(HWND h, int amt) +{ +} +int SWELL_GetWindowWantRaiseAmt(HWND h) +{ + return 0; +} + +// copied from swell-wnd.mm, can maybe have a common impl instead +void SWELL_GenerateDialogFromList(const void *_list, int listsz) +{ +#define SIXFROMLIST list->p1,list->p2,list->p3, list->p4, list->p5, list->p6 + SWELL_DlgResourceEntry *list = (SWELL_DlgResourceEntry*)_list; + while (listsz>0) + { + if (!strcmp(list->str1,"__SWELL_BUTTON")) + { + SWELL_MakeButton(list->flag1,list->str2, SIXFROMLIST); + } + else if (!strcmp(list->str1,"__SWELL_EDIT")) + { + SWELL_MakeEditField(SIXFROMLIST); + } + else if (!strcmp(list->str1,"__SWELL_COMBO")) + { + SWELL_MakeCombo(SIXFROMLIST); + } + else if (!strcmp(list->str1,"__SWELL_LISTBOX")) + { + SWELL_MakeListBox(SIXFROMLIST); + } + else if (!strcmp(list->str1,"__SWELL_GROUP")) + { + SWELL_MakeGroupBox(list->str2,SIXFROMLIST); + } + else if (!strcmp(list->str1,"__SWELL_CHECKBOX")) + { + SWELL_MakeCheckBox(list->str2,SIXFROMLIST); + } + else if (!strcmp(list->str1,"__SWELL_LABEL")) + { + SWELL_MakeLabel(list->flag1, list->str2, SIXFROMLIST); + } + else if (!strcmp(list->str1,"__SWELL_ICON")) + { + // todo (str2 is likely a (const char *)(INT_PTR)resid + } + else if (*list->str2) + { + SWELL_MakeControl(list->str1, list->flag1, list->str2, SIXFROMLIST); + } + listsz--; + list++; + } +} + +void SWELL_SetClassName(HWND hwnd, const char *p) +{ + if (WDL_NORMALLY(hwnd)) + hwnd->m_classname=p; +} + +int GetClassName(HWND hwnd, char *buf, int bufsz) +{ + if (WDL_NOT_NORMALLY(!hwnd || !hwnd->m_classname || !buf || bufsz<1)) return 0; + lstrcpyn_safe(buf,hwnd->m_classname,bufsz); + return (int)strlen(buf); +} + +void SWELL_DisableContextMenu(HWND hwnd, bool dis) +{ + if (WDL_NORMALLY(hwnd)) + { + if (!strcmp(hwnd->m_classname,"Edit")) + { + __SWELL_editControlState *es = (__SWELL_editControlState*)hwnd->m_private_data; + if (es) es->m_disable_contextmenu = dis; + } + } +} + +#ifdef _DEBUG +void VALIDATE_HWND_LIST(HWND listHead, HWND par) +{ + if (!listHead) return; + WDL_ASSERT(!listHead->m_prev); + WDL_ASSERT(listHead->m_parent == par); + WDL_ASSERT(listHead->m_next != listHead); + HWND last = listHead; + HWND list = listHead->m_next; + while (list) + { + WDL_ASSERT(list->m_prev == last); + WDL_ASSERT(list->m_parent == par); + last = list; + list = list->m_next; + } +} +#endif + + +LRESULT SWELL_SendMouseMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (!hwnd || !hwnd->m_wndproc) return -1; + if (!IsWindowEnabled(hwnd)) + { + if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN || + msg == WM_LBUTTONDBLCLK || msg == WM_RBUTTONDBLCLK || msg == WM_MBUTTONDBLCLK) + { + HWND h = DialogBoxIsActive(); + if (h) SetForegroundWindow(h); + } + return -1; + } + + if (msg == WM_RBUTTONDOWN) s_last_rbuttondown = hwnd; + + LRESULT htc=0; + if (msg != WM_MOUSEWHEEL && !GetCapture()) + { + DWORD p=GetMessagePos(); + + htc=hwnd->m_wndproc(hwnd,WM_NCHITTEST,0,p); + if (hwnd->m_hashaddestroy||!hwnd->m_wndproc) + { + return -1; // if somehow WM_NCHITTEST destroyed us, bail + } + + if (htc!=HTCLIENT || swell_window_wants_all_input() == hwnd) + { + if (msg==WM_MOUSEMOVE) return hwnd->m_wndproc(hwnd,WM_NCMOUSEMOVE,htc,p); +// if (msg==WM_MOUSEWHEEL) return hwnd->m_wndproc(hwnd,WM_NCMOUSEWHEEL,htc,p); +// if (msg==WM_MOUSEHWHEEL) return hwnd->m_wndproc(hwnd,WM_NCMOUSEHWHEEL,htc,p); + if (msg==WM_LBUTTONUP) return hwnd->m_wndproc(hwnd,WM_NCLBUTTONUP,htc,p); + if (msg==WM_LBUTTONDOWN) return hwnd->m_wndproc(hwnd,WM_NCLBUTTONDOWN,htc,p); + if (msg==WM_LBUTTONDBLCLK) return hwnd->m_wndproc(hwnd,WM_NCLBUTTONDBLCLK,htc,p); + if (msg==WM_RBUTTONUP) return hwnd->m_wndproc(hwnd,WM_NCRBUTTONUP,htc,p); + if (msg==WM_RBUTTONDOWN) return hwnd->m_wndproc(hwnd,WM_NCRBUTTONDOWN,htc,p); + if (msg==WM_RBUTTONDBLCLK) return hwnd->m_wndproc(hwnd,WM_NCRBUTTONDBLCLK,htc,p); + if (msg==WM_MBUTTONUP) return hwnd->m_wndproc(hwnd,WM_NCMBUTTONUP,htc,p); + if (msg==WM_MBUTTONDOWN) return hwnd->m_wndproc(hwnd,WM_NCMBUTTONDOWN,htc,p); + if (msg==WM_MBUTTONDBLCLK) return hwnd->m_wndproc(hwnd,WM_NCMBUTTONDBLCLK,htc,p); + } + } + + + LRESULT ret=hwnd->m_wndproc(hwnd,msg,wParam,lParam); + + if (msg==WM_LBUTTONUP || msg==WM_RBUTTONUP || msg==WM_MOUSEMOVE || msg==WM_MBUTTONUP) + { + if (!GetCapture() && (hwnd->m_hashaddestroy || !hwnd->m_wndproc || !hwnd->m_wndproc(hwnd,WM_SETCURSOR,(WPARAM)hwnd,htc | (msg<<16)))) + { + SetCursor(SWELL_LoadCursor(IDC_ARROW)); + } + } + + return ret; +} + +#endif diff --git a/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-wnd.mm b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-wnd.mm new file mode 100644 index 0000000..fdcb9c9 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/WDL/WDL/swell/swell-wnd.mm @@ -0,0 +1,7445 @@ +/* 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. + + + This file provides basic windows APIs for handling windows, as well as the stubs to enable swell-dlggen to work. + + */ + + +#ifndef SWELL_PROVIDED_BY_APP + +#import +#import +#include "swell.h" +#include "../mutex.h" +#include "../ptrlist.h" +#include "../queue.h" +#include "../wdlcstring.h" + +#include "swell-dlggen.h" + +#define SWELL_INTERNAL_MERGESORT_IMPL +#define SWELL_INTERNAL_HTREEITEM_IMPL +#include "swell-internal.h" + +static bool SWELL_NeedModernListViewHacks() +{ +#ifdef __LP64__ + return false; +#else + // only needed on 32 bit yosemite as of 10.10.3, but who knows when it will be necessary elsewhere + return SWELL_GetOSXVersion() >= 0x10a0; +#endif +} + +static LRESULT sendSwellMessage(id obj, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (obj && [obj respondsToSelector:@selector(onSwellMessage:p1:p2:)]) + return [(SWELL_hwndChild *)obj onSwellMessage:uMsg p1:wParam p2:lParam]; + return 0; +} +static void InvalidateSuperViews(NSView *view); +#define STANDARD_CONTROL_NEEDSDISPLAY_IMPL(classname) \ + - (const char *)getSwellClass { return ( classname ); } \ + - (void)setNeedsDisplay:(BOOL)flag \ + { \ + [super setNeedsDisplay:flag]; \ + if (flag) InvalidateSuperViews(self); \ + } \ + - (void)setNeedsDisplayInRect:(NSRect)rect \ + { \ + [super setNeedsDisplayInRect:rect]; \ + InvalidateSuperViews(self); \ + } + + +static WDL_PtrList s_prefix_removals; + +int g_swell_osx_readonlytext_wndbg = 0; +int g_swell_osx_style = 0; // &1 = rounded buttons, &2=big sur styled lists +static void *SWELL_CStringToCFString_FilterPrefix(const char *str) +{ + int c=0; + while (str[c] && str[c] != '&' && c++<1024); + if (!str[c] || c>=1024 || strlen(str)>=1024) return SWELL_CStringToCFString(str); + char buf[1500]; + { + const char *p=str; + char *op=buf; + while (*p) + { + if (*p == '&') p++; + if (!*p) break; + *op++=*p++; + } + *op=0; + } + + // add to recent prefix removal cache for localization + if (WDL_NOT_NORMALLY(s_prefix_removals.GetSize() > 256)) + s_prefix_removals.Delete(0,true,free); + + { + const size_t sz1 = strlen(buf), sz2 = strlen(str); + char *p = (char *)malloc(sz1+sz2+2); + if (WDL_NORMALLY(p!=NULL)) + { + memcpy(p,buf,sz1+1); + memcpy(p+sz1+1,str,sz2+1); + s_prefix_removals.Add(p); + } + } + + return SWELL_CStringToCFString(buf); + +} + +static int _nsStringSearchProc(const void *_a, const void *_b) +{ + NSString *a=(NSString *)_a; + NSString *b = (NSString *)_b; + return (int)[a compare:b]; +} +static int _nsMenuSearchProc(const void *_a, const void *_b) +{ + NSString *a=(NSString *)_a; + NSMenuItem *b = (NSMenuItem *)_b; + return (int)[a compare:[b title]]; +} +static int _listviewrowSearchFunc(const void *_a, const void *_b, const void *ctx) +{ + const char *a = (const char *)_a; + SWELL_ListView_Row *row = (SWELL_ListView_Row *)_b; + const char *b=""; + if (!row || !(b=row->get_col_txt(0))) b=""; + return strcmp(a,b); +} +static int _listviewrowSearchFunc2(const void *_a, const void *_b, const void *ctx) +{ + const char *a = (const char *)_a; + SWELL_ListView_Row *row = (SWELL_ListView_Row *)_b; + const char *b=""; + if (!row || !(b=row->get_col_txt(0))) b=""; + return strcmp(b,a); +} + +// modified bsearch: returns place item SHOULD be in if it's not found +static NSInteger arr_bsearch_mod(void *key, NSArray *arr, int (*compar)(const void *, const void *)) +{ + const NSInteger nmemb = [arr count]; + NSInteger p,lim,base=0; + + for (lim = nmemb; lim != 0; lim >>= 1) { + p = base + (lim >> 1); + int cmp = compar(key, [arr objectAtIndex:p]); + if (cmp == 0) return (p); + if (cmp > 0) { /* key > p: move right */ + // check to see if key is less than p+1, if it is, we're done + base = p + 1; + if (base >= nmemb || compar(key,[arr objectAtIndex:base])<=0) return base; + lim--; + } /* else move left */ + } + return 0; +} + + +template static int ptrlist_bsearch_mod(void *key, WDL_PtrList *arr, int (*compar)(const void *, const void *, const void *ctx), void *ctx) +{ + const int nmemb = arr->GetSize(); + int base=0, lim, p; + + for (lim = nmemb; lim != 0; lim >>= 1) { + p = base + (lim >> 1); + int cmp = compar(key, arr->Get(p),ctx); + if (cmp == 0) return (p); + if (cmp > 0) { /* key > p: move right */ + // check to see if key is less than p+1, if it is, we're done + base = p + 1; + if (base >= nmemb || compar(key,arr->Get(base),ctx)<=0) return base; + lim--; + } /* else move left */ + } + return 0; +} + + +@implementation SWELL_TabView +STANDARD_CONTROL_NEEDSDISPLAY_IMPL("SysTabControl32") + +-(void)setNotificationWindow:(id)dest +{ + m_dest=dest; +} +-(id)getNotificationWindow +{ + return m_dest; +} +-(NSInteger) tag +{ + return m_tag; +} +-(void) setTag:(NSInteger)tag +{ + m_tag=tag; +} +- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem +{ + if (m_dest) + { + NMHDR nm={(HWND)self,(UINT_PTR)[self tag],TCN_SELCHANGE}; + SendMessage((HWND)m_dest,WM_NOTIFY,nm.idFrom,(LPARAM)&nm); + } +} +@end + + +@implementation SWELL_ProgressView +STANDARD_CONTROL_NEEDSDISPLAY_IMPL("msctls_progress32") + +-(NSInteger) tag +{ + return m_tag; +} +-(void) setTag:(NSInteger)tag +{ + m_tag=tag; +} +-(LRESULT)onSwellMessage:(UINT)msg p1:(WPARAM)wParam p2:(LPARAM)lParam +{ + if (msg == PBM_SETRANGE) + { + [self setMinValue:LOWORD(lParam)]; + [self setMaxValue:HIWORD(lParam)]; + } + else if (msg==PBM_SETPOS) + { + [self setDoubleValue:(double)wParam]; + [self stopAnimation:self]; + } + else if (msg==PBM_DELTAPOS) + { + [self incrementBy:(double)wParam]; + } + return 0; +} + +@end + +@implementation SWELL_ListViewCell +-(NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + if ([controlView isKindOfClass:[SWELL_ListView class]]) + { + if (((SWELL_ListView *)controlView)->m_selColors) return nil; + } + else if ([controlView isKindOfClass:[SWELL_TreeView class]]) + { + if (((SWELL_TreeView *)controlView)->m_selColors) return nil; + } + + return [super highlightColorWithFrame:cellFrame inView:controlView]; +} +- (NSRect)drawingRectForBounds:(NSRect)rect +{ + const NSSize sz = [self cellSizeForBounds:rect]; + rect = [super drawingRectForBounds:rect]; + const int offs = (int) floor((rect.size.height - sz.height) * .5); + if (offs>0) + { + rect.origin.y += offs; + rect.size.height -= offs*2; + } + return rect; +} +@end + +@implementation SWELL_StatusCell +-(id)initNewCell:(bool)always_indent +{ + if ((self=[super initTextCell:@""])) + { + m_always_indent=always_indent; + status=0; + } + return self; +} +-(void)setStatusImage:(NSImage *)img +{ + status=img; +} +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + if (status) + { + const double fr_h = cellFrame.size.height; + const NSSize image_sz = [status size]; + const double use_h = wdl_min(image_sz.height, fr_h); + const double yo = (fr_h-use_h)*.5; + double use_w,xo; + if (use_h > cellFrame.size.width) + { + use_w = cellFrame.size.width; + xo = 0.0; + } + else + { + use_w = use_h; + xo = yo; + } + + [status drawInRect:NSMakeRect(cellFrame.origin.x + xo,cellFrame.origin.y + yo,use_w,use_h) + fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; + } + if (m_always_indent || status) + { + cellFrame.origin.x += cellFrame.size.height + 2.0; + cellFrame.size.width -= cellFrame.size.height + 2.0; + } + [super drawWithFrame:cellFrame inView:controlView]; +} + +@end + +static HTREEITEM FindTreeItemByDataHold(const WDL_PtrList *list, SWELL_DataHold *srch) +{ + for (int x = 0; x < list->GetSize(); x ++) + { + HTREEITEM item = list->Get(x); + if (item && item->m_dh == srch) return item; + } + for (int x = 0; x < list->GetSize(); x ++) + { + HTREEITEM item = list->Get(x); + if (item && item->m_children.GetSize()) + { + item = FindTreeItemByDataHold(&item->m_children,srch); + if (item) return item; + } + } + return NULL; +} + +@implementation SWELL_TreeView +STANDARD_CONTROL_NEEDSDISPLAY_IMPL("SysTreeView32") + +-(id) init +{ + if ((self = [super init])) + { + m_fakerightmouse=false; + m_items=new WDL_PtrList; + m_fgColor=0; + m_selColors=0; + } + return self; +} +-(void) dealloc +{ + if (m_items) m_items->Empty(true); + delete m_items; + m_items=0; + [m_fgColor release]; + [m_selColors release]; + [super dealloc]; +} + +-(bool) findItem:(HTREEITEM)item parOut:(HTREEITEM__ **)par idxOut:(int *)idx +{ + if (!m_items||!item) return false; + int x=m_items->Find((HTREEITEM__*)item); + if (x>=0) + { + *par=NULL; + *idx=x; + return true; + } + for (x = 0; x < m_items->GetSize(); x++) + { + if (m_items->Get(x)->FindItem(item,par,idx)) return true; + } + + return false; +} + +-(NSInteger) outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item +{ + if (item == nil) return m_items ? m_items->GetSize() : 0; + return ((HTREEITEM__*)[item getValue])->m_children.GetSize(); +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item +{ + if (item==nil) return YES; + HTREEITEM__ *it=(HTREEITEM__ *)[item getValue]; + + return it && it->m_haschildren; +} + +- (id)outlineView:(NSOutlineView *)outlineView + child:(NSInteger)index + ofItem:(id)item +{ + HTREEITEM__ *row=item ? ((HTREEITEM__*)[item getValue])->m_children.Get(index) : m_items ? m_items->Get(index) : 0; + + return (id)(row ? row->m_dh : NULL); +} + +- (id)outlineView:(NSOutlineView *)outlineView + objectValueForTableColumn:(NSTableColumn *)tableColumn + byItem:(id)item +{ + if (!item) return @""; + HTREEITEM__ *it=(HTREEITEM__ *)[item getValue]; + + if (!it || !it->m_value) return @""; + + NSString *str=(NSString *)SWELL_CStringToCFString(it->m_value); + + return [str autorelease]; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView + writeItems:(NSArray *)items + toPasteboard:(NSPasteboard *)pasteboard +{ + if (self->style & TVS_DISABLEDRAGDROP) return NO; + [pasteboard declareTypes:[NSArray arrayWithObject:@"swell_treeview"] owner:nil]; + [pasteboard setString:@"" forType:@"swell_treeview"]; + return YES; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView + acceptDrop:(id)info + item:(id)item + childIndex:(NSInteger)index +{ + HWND par = GetParent((HWND)self); + if (par && GetCapture() == par) + { + POINT p; + GetCursorPos(&p); + ScreenToClient(par,&p); + SendMessage(par,WM_LBUTTONUP,0,MAKELPARAM(p.x,p.y)); + } + return YES; +} + +/* +- (BOOL)outlineView:(NSOutlineView *)outlineView + shouldExpandItem:(id)item +{ + return NO; // optionally while dragging? +} +*/ + +- (void)outlineView:(NSOutlineView *)outlineView + draggingSession:(NSDraggingSession *)session + endedAtPoint:(NSPoint)screenPoint + operation:(NSDragOperation)operation +{ + [self unregisterDraggedTypes]; + HWND par = GetParent((HWND)self); + if (par && GetCapture() == par) + { + // usually acceptDrop above will be the one that is called, but if the user ended up elsewhere + // this might, let the caller clean up capture + POINT p; + GetCursorPos(&p); + ScreenToClient(par,&p); + SendMessage(par,WM_LBUTTONUP,0,MAKELPARAM(p.x,p.y)); + } +} + +- (void)outlineView:(NSOutlineView *)outlineView + draggingSession:(NSDraggingSession *)session + willBeginAtPoint:(NSPoint)screenPoint + forItems:(NSArray *)draggedItems +{ + if (self->style & TVS_DISABLEDRAGDROP) return; + HWND hwnd = (HWND)self, par = GetParent(hwnd); + if (par) + { + HTREEITEM hit = NULL; + if (m_items && [draggedItems count] > 0) + { + id obj = [draggedItems objectAtIndex:0]; + if ([obj isKindOfClass:[SWELL_DataHold class]]) + hit = FindTreeItemByDataHold(m_items, (SWELL_DataHold *)obj); + } + if (!hit) + { + TVHITTESTINFO tht; + memset(&tht,0,sizeof(tht)); + GetCursorPos(&tht.pt); + ScreenToClient(hwnd, &tht.pt); + hit = TreeView_HitTest(hwnd, &tht); + } + + HTREEITEM sel = TreeView_GetSelection(hwnd); + if (hit && hit != sel) + { + TreeView_SelectItem(hwnd,hit); + sel = hit; + } + + NMTREEVIEW nm={{hwnd,(UINT_PTR)[self tag],TVN_BEGINDRAG},}; + nm.itemNew.hItem = sel; + nm.itemNew.lParam = sel ? sel->m_param : 0; + SendMessage(par,WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + if (GetCapture() == par) + [self registerForDraggedTypes:[NSArray arrayWithObject: @"swell_treeview"]]; + } +} + +- (NSDragOperation)outlineView:(NSOutlineView *)outlineView + validateDrop:(id)info + proposedItem:(id)item + proposedChildIndex:(NSInteger)index +{ + HWND hwnd=(HWND)self, par = GetParent(hwnd); + if (par && GetCapture()==par) + { + POINT p; + GetCursorPos(&p); + TVHITTESTINFO tht; + memset(&tht,0,sizeof(tht)); + tht.pt = p; + + ScreenToClient(par,&p); + LRESULT move_res = SendMessage(par,WM_MOUSEMOVE,0,MAKELPARAM(p.x,p.y)); + if (move_res == (LRESULT)-1) return NSDragOperationNone; + if (move_res == (LRESULT)-2) // move to end + { + HTREEITEM par_item = NULL; + HTREEITEM li = self->m_items ? self->m_items->Get(self->m_items->GetSize()-1) : NULL; + while (li && li->m_children.GetSize()) + { + par_item = li; + li = li->m_children.Get(li->m_children.GetSize()-1); + } + if (par_item && par_item->m_children.GetSize()) [self setDropItem:par_item->m_dh dropChildIndex:par_item->m_children.GetSize()]; + } + else if (move_res >= 65536) + { + HTREEITEM paritem = NULL; + int idx=0; + // it is safe (but time consuming!) to call findItem: on a possibly-junk pointer + if ([self findItem:(HTREEITEM)(INT_PTR)move_res parOut:&paritem idxOut:&idx] && paritem) + [self setDropItem:paritem->m_dh dropChildIndex:idx]; + } + return NSDragOperationPrivate; + } + return NSDragOperationNone; + +} + +-(void)mouseDown:(NSEvent *)theEvent +{ + if (([theEvent modifierFlags] & NSControlKeyMask) && IsRightClickEmulateEnabled()) + { + m_fakerightmouse=1; + } + else + { + + NMCLICK nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_CLICK},}; + SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); + + m_fakerightmouse=0; + [super mouseDown:theEvent]; + } +} + +-(void)mouseDragged:(NSEvent *)theEvent +{ +} + +-(void)mouseUp:(NSEvent *)theEvent +{ + if (m_fakerightmouse||([theEvent modifierFlags] & NSControlKeyMask)) [self rightMouseUp:theEvent]; + else [super mouseUp:theEvent]; +} +- (void)rightMouseUp:(NSEvent *)theEvent +{ + bool wantContext=true; + + NMCLICK nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_RCLICK},}; + if (SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv)) wantContext=false; + + if (wantContext) + { + POINT p; + GetCursorPos(&p); + SendMessage((HWND)[self target],WM_CONTEXTMENU,(WPARAM)self,MAKELONG(p.x&0xffff,p.y)); + } + + m_fakerightmouse=0; +} + +- (void)highlightSelectionInClipRect:(NSRect)theClipRect +{ + if (m_selColors) + { + int a = GetFocus() == (HWND)self ? 0 : 2; + if ([m_selColors count] >= a) + { + NSColor *c=[m_selColors objectAtIndex:a]; + if (c) + { + // calculate rect of selected items, combine with theClipRect, and fill these areas with our background (phew!) + + NSInteger x = [self selectedRow]; + if (x>=0) + { + NSRect r = [self rectOfRow:x]; + r = NSIntersectionRect(r,theClipRect); + if (r.size.height>0 && r.size.width>0) + { + [c setFill]; + NSRectFill(r); + } + } + return ; + } + } + } + return [super highlightSelectionInClipRect:theClipRect]; +} + + + + +@end + + + + + +@implementation SWELL_ListView +STANDARD_CONTROL_NEEDSDISPLAY_IMPL( m_lbMode ? "SysListView32_LB" : "SysListView32" ) + +-(BOOL)accessibilityPerformShowMenu +{ + HWND par = (HWND)[self target]; + if (par) + { + int row = (int)ListView_GetNextItem((HWND)self,-1,LVNI_FOCUSED); + int col = 0; // todo + NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_RCLICK}, row, col, 0, 0, 0, }; + SendMessage(par,WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); + return YES; + } + return NO; +} + +-(LONG)getSwellStyle { return style; } + +-(void)setSwellStyle:(LONG)st +{ + bool hdrchg= ((style&LVS_NOCOLUMNHEADER) != (st&LVS_NOCOLUMNHEADER)); + style=st; + if ((style&LVS_REPORT) && hdrchg) + { + // todo some crap with NSTableView::setHeaderView, but it's complicated + } +} + +-(id) init +{ + if ((self = [super init])) + { + m_subitem_images = false; + m_selColors=0; + m_fgColor = 0; + ownermode_cnt=0; + m_status_imagelist_type=-1; + m_status_imagelist=0; + m_leftmousemovecnt=0; + m_fakerightmouse=false; + m_lbMode=0; + m_fastClickMask=0; + m_last_shift_clicked_item = m_last_plainly_clicked_item=-1; + m_start_item=-1; + m_start_subitem=-1; + m_start_item_clickmode=0; // 0=clicked item, 1=clicked image, &2=sent drag message, &4=quickclick mode + m_cols = new WDL_PtrList; + m_items=new WDL_PtrList; + } + return self; +} +-(void) dealloc +{ + if (m_items) m_items->Empty(true); + delete m_items; + delete m_cols; + m_cols=0; + m_items=0; + [m_fgColor release]; + [m_selColors release]; + [super dealloc]; +} + +-(int)getColumnPos:(int)idx // get current position of column that was originally at idx +{ + int pos=idx; + if (m_cols) + { + NSTableColumn* col=m_cols->Get(idx); + if (col) + { + NSArray* arr=[self tableColumns]; + if (arr) + { + pos=(int)[arr indexOfObject:col]; + } + } + } + return pos; +} + +- (void)highlightSelectionInClipRect:(NSRect)theClipRect +{ + if (m_selColors) + { + int a = GetFocus() == (HWND)self ? 0 : 2; + if ([m_selColors count] >= a) + { + NSColor *c=[m_selColors objectAtIndex:a]; + if (c) + { + // calculate rect of selected items, combine with theClipRect, and fill these areas with our background (phew!) + bool needfillset=true; + NSInteger x = [self rowAtPoint:NSMakePoint(0,theClipRect.origin.y)]; + if (x<0)x=0; + const NSInteger n = [self numberOfRows]; + for (;x = theClipRect.origin.y + theClipRect.size.height) break; + + if ([self isRowSelected:x]) + { + r = NSIntersectionRect(r,theClipRect); + if (r.size.height>0 && r.size.width>0) + { + if (needfillset) { needfillset=false; [c setFill]; } + NSRectFill(r); + } + } + } + return ; + } + } + } + return [super highlightSelectionInClipRect:theClipRect]; +} +-(int)getColumnIdx:(int)pos // get original index of column that is currently at position +{ + int idx=pos; + NSArray* arr=[self tableColumns]; + if (arr && pos>=0 && pos < [arr count]) + { + NSTableColumn* col=[arr objectAtIndex:pos]; + if (col && m_cols) + { + idx=m_cols->Find(col); + } + } + return idx; +} + +-(NSInteger)columnAtPoint:(NSPoint)pt +{ + int pos=(int)[super columnAtPoint:pt]; + return (NSInteger) [self getColumnIdx:pos]; +} + + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView +{ + return (!m_lbMode && (style & LVS_OWNERDATA)) ? ownermode_cnt : (m_items ? m_items->GetSize():0); +} + +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + NSString *str=NULL; + int image_idx=0; + + if (!m_lbMode && (style & LVS_OWNERDATA)) + { + HWND tgt=(HWND)[self target]; + + char buf[1024]; + NMLVDISPINFO nm={{(HWND)self, (UINT_PTR)[self tag], LVN_GETDISPINFO}}; + nm.item.mask=LVIF_TEXT; + if (m_status_imagelist_type==LVSIL_STATE) nm.item.mask |= LVIF_STATE; + else if (m_status_imagelist_type == LVSIL_SMALL) nm.item.mask |= LVIF_IMAGE; + nm.item.iImage = -1; + nm.item.iItem=(int)rowIndex; + nm.item.iSubItem=m_cols->Find(aTableColumn); + nm.item.pszText=buf; + nm.item.cchTextMax=sizeof(buf)-1; + buf[0]=0; + SendMessage(tgt,WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); + + if (m_status_imagelist_type == LVSIL_STATE) image_idx=(nm.item.state>>16)&0xff; + else if (m_status_imagelist_type == LVSIL_SMALL) image_idx = nm.item.iImage + 1; + str=(NSString *)SWELL_CStringToCFString(nm.item.pszText); + } + else + { + const char *p=NULL; + SWELL_ListView_Row *r=0; + if (m_items && m_cols && (r=m_items->Get(rowIndex))) + { + int col_idx = m_cols->Find(aTableColumn); + p=r->get_col_txt(col_idx); + if (m_status_imagelist_type == LVSIL_STATE || m_status_imagelist_type == LVSIL_SMALL) + { + if (col_idx==0 || m_subitem_images) + image_idx=r->get_img_idx(col_idx); + } + } + + str=(NSString *)SWELL_CStringToCFString(p); + + if (style & LBS_OWNERDRAWFIXED) + { + SWELL_ODListViewCell *cell=[aTableColumn dataCell]; + if ([cell isKindOfClass:[SWELL_ODListViewCell class]]) [cell setItemIdx:(int)rowIndex]; + } + } + + if (!m_lbMode && m_status_imagelist) + { + SWELL_StatusCell *cell=(SWELL_StatusCell*)[aTableColumn dataCell]; + if ([cell isKindOfClass:[SWELL_StatusCell class]]) + { + HICON icon=m_status_imagelist->Get(image_idx-1); + NSImage *img=NULL; + if (icon) img=(NSImage *)GetNSImageFromHICON(icon); + [cell setStatusImage:img]; + } + } + + return [str autorelease]; + +} + + +-(void)mouseDown:(NSEvent *)theEvent +{ + if (([theEvent modifierFlags] & NSControlKeyMask) && IsRightClickEmulateEnabled()) + { + m_fakerightmouse=1; + m_start_item=-1; + m_start_subitem=-1; + } + else + { + if ([theEvent clickCount]>1 && SWELL_NeedModernListViewHacks()) + { + [super mouseDown:theEvent]; + return; + } + m_leftmousemovecnt=0; + m_fakerightmouse=0; + + NSPoint pt=[theEvent locationInWindow]; + pt=[self convertPoint:pt fromView:nil]; + m_start_item=(int)[self rowAtPoint:pt]; + m_start_subitem=(int)[self columnAtPoint:pt]; + + + + m_start_item_clickmode=0; + if (m_start_item >=0 && (m_fastClickMask&(1< 1 ? NM_DBLCLK : NM_CLICK; + NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], msg}, m_start_item, m_start_subitem, 0, 0, 0, {NSPOINT_TO_INTS(pt)}, }; + SWELL_ListView_Row *row=m_items->Get(nmlv.iItem); + if (row) + nmlv.lParam = row->m_param; + SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); + m_start_item_clickmode=4; + } + else + { + if (m_start_item>=0 && m_status_imagelist && LVSIL_STATE == m_status_imagelist_type && pt.x <= [self rowHeight]) // in left area + { + m_start_item_clickmode=1; + } + } + } +} + +-(void)mouseDragged:(NSEvent *)theEvent +{ + if (++m_leftmousemovecnt==4) + { + if (m_start_item>=0 && !(m_start_item_clickmode&3)) + { + if (!m_lbMode) + { + // if m_start_item isnt selected, change selection to it now + if (!(m_start_item_clickmode&4) && ![self isRowSelected:m_start_item]) + { + [self selectRowIndexes:[NSIndexSet indexSetWithIndex:m_start_item] byExtendingSelection:!!(GetAsyncKeyState(VK_CONTROL)&0x8000)]; + } + NMLISTVIEW hdr={{(HWND)self,(UINT_PTR)[self tag],LVN_BEGINDRAG},m_start_item,m_start_subitem,0,}; + SendMessage((HWND)[self target],WM_NOTIFY,hdr.hdr.idFrom, (LPARAM) &hdr); + m_start_item_clickmode |= 2; + } + } + } + else if (m_leftmousemovecnt > 4 && !(m_start_item_clickmode&1)) + { + HWND tgt=(HWND)[self target]; + POINT p; + GetCursorPos(&p); + ScreenToClient(tgt,&p); + + SendMessage(tgt,WM_MOUSEMOVE,0,MAKELONG(p.x&0xffff,p.y)); + } +} + +-(void)mouseUp:(NSEvent *)theEvent +{ + if ((m_fakerightmouse||([theEvent modifierFlags] & NSControlKeyMask)) && IsRightClickEmulateEnabled()) + { + [self rightMouseUp:theEvent]; + } + else + { + if ([theEvent clickCount]>1 && SWELL_NeedModernListViewHacks()) + { + [super mouseUp:theEvent]; + return; + } + if (!(m_start_item_clickmode&1)) + { + if (m_leftmousemovecnt>=0 && m_leftmousemovecnt<4 && !(m_start_item_clickmode&4)) + { + const bool msel = [self allowsMultipleSelection]; + if (m_lbMode && !msel) // listboxes --- allow clicking to reset the selection + { + [self deselectAll:self]; + } + + if (SWELL_NeedModernListViewHacks()) + { + if (m_start_item>=0) + { + NSMutableIndexSet *m = [[NSMutableIndexSet alloc] init]; + if (GetAsyncKeyState(VK_CONTROL)&0x8000) + { + [m addIndexes:[self selectedRowIndexes]]; + if ([m containsIndex:m_start_item]) [m removeIndex:m_start_item]; + else + { + if (!msel) [m removeAllIndexes]; + [m addIndex:m_start_item]; + } + m_last_plainly_clicked_item = m_start_item; + } + else if (msel && (GetAsyncKeyState(VK_SHIFT)&0x8000)) + { + [m addIndexes:[self selectedRowIndexes]]; + const int n = ListView_GetItemCount((HWND)self); + if (m_last_plainly_clicked_item<0 || m_last_plainly_clicked_item>=n) + m_last_plainly_clicked_item=m_start_item; + + if (m_last_shift_clicked_item>=0 && + m_last_shift_clicked_item=4) + { + HWND tgt=(HWND)[self target]; + POINT p; + GetCursorPos(&p); + ScreenToClient(tgt,&p); + SendMessage(tgt,WM_LBUTTONUP,0,MAKELONG(p.x&0xffff,p.y)); + } + } + } + + if (!m_lbMode && !(m_start_item_clickmode&(2|4))) + { + NSPoint pt=[theEvent locationInWindow]; + pt=[self convertPoint:pt fromView:nil]; + int col = (int)[self columnAtPoint:pt]; + NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_CLICK}, (int)[self rowAtPoint:pt], col, 0, 0, 0, {NSPOINT_TO_INTS(pt)}, }; + SWELL_ListView_Row *row=m_items->Get(nmlv.iItem); + if (row) nmlv.lParam = row->m_param; + SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); + } +} + +- (void)rightMouseUp:(NSEvent *)theEvent +{ + bool wantContext=true; + + if (!m_lbMode) + { + NSPoint pt=[theEvent locationInWindow]; + pt=[self convertPoint:pt fromView:nil]; + + // note, windows selects on right mousedown + NSInteger row =[self rowAtPoint:pt]; + if (row >= 0 && ![self isRowSelected:row]) + { + NSIndexSet* rows=[NSIndexSet indexSetWithIndex:row]; + [self deselectAll:self]; + [self selectRowIndexes:rows byExtendingSelection:NO]; + } + + NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_RCLICK}, (int)row, (int)[self columnAtPoint:pt], 0, 0, 0, {NSPOINT_TO_INTS(pt)}, }; + if (SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv)) wantContext=false; + } + if (wantContext) + { + POINT p; + GetCursorPos(&p); + SendMessage((HWND)[self target],WM_CONTEXTMENU,(WPARAM)self,MAKELONG(p.x&0xffff,p.y)); + } + m_fakerightmouse=0; +} + +-(LRESULT)onSwellMessage:(UINT)msg p1:(WPARAM)wParam p2:(LPARAM)lParam +{ + HWND hwnd=(HWND)self; + switch (msg) + { + case LB_RESETCONTENT: + ownermode_cnt=0; + if (m_items) + { + m_items->Empty(true); + [self reloadData]; + } + return 0; + case LB_ADDSTRING: + case LB_INSERTSTRING: + { + int cnt=ListView_GetItemCount(hwnd); + if (msg == LB_ADDSTRING && (style & LBS_SORT)) + { + SWELL_ListView *tv=(SWELL_ListView*)hwnd; + if (tv->m_lbMode && tv->m_items) + { + cnt=ptrlist_bsearch_mod((char *)lParam,tv->m_items,_listviewrowSearchFunc,NULL); + } + } + if (msg==LB_ADDSTRING) wParam=cnt; + else if (wParam > cnt) wParam=cnt; + LVITEM lvi={LVIF_TEXT,(int)wParam,0,0,0,(char *)lParam}; + ListView_InsertItem(hwnd,&lvi); + } + return wParam; + case LB_GETCOUNT: return ListView_GetItemCount(hwnd); + case LB_SETSEL: + ListView_SetItemState(hwnd, (int)lParam,wParam ? LVIS_SELECTED : 0,LVIS_SELECTED); + return 0; + case LB_GETTEXT: + if (lParam) + { + SWELL_ListView_Row *row=self->m_items ? self->m_items->Get(wParam) : NULL; + *(char *)lParam = 0; + const char *p; + if (row && (p=row->get_col_txt(0))) + { + strcpy((char *)lParam, p); + return (LRESULT)strlen(p); + } + } + return LB_ERR; + case LB_GETTEXTLEN: + { + SWELL_ListView_Row *row=self->m_items ? self->m_items->Get(wParam) : NULL; + if (row) + { + const char *p=row->get_col_txt(0); + return p?strlen(p):0; + } + } + return LB_ERR; + case LB_FINDSTRINGEXACT: + if (lParam) + { + int x = (int) wParam + 1; + if (x < 0) x=0; + const int n = self->m_items ? self->m_items->GetSize() : 0; + for (int i = 0; i < n; i ++) + { + SWELL_ListView_Row *row=self->m_items->Get(x); + if (row) + { + const char *p = row->get_col_txt(0); + if (p && !stricmp(p,(const char *)lParam)) return x; + } + if (++x >= n) x=0; + } + } + return LB_ERR; + case LB_GETSEL: + return !!(ListView_GetItemState(hwnd,(int)wParam,LVIS_SELECTED)&LVIS_SELECTED); + case LB_GETCURSEL: + return [self selectedRow]; + case LB_SETCURSEL: + { + if (wParamGet(wParam); + if (row) return row->m_param; + } + } + return 0; + case LB_SETITEMDATA: + { + if (m_items) + { + SWELL_ListView_Row *row=m_items->Get(wParam); + if (row) row->m_param=lParam; + } + } + return 0; + case LB_GETSELCOUNT: + return [[self selectedRowIndexes] count]; + case LB_DELETESTRING: + ListView_DeleteItem((HWND)self, (int)wParam); + return 0; + case WM_SETREDRAW: + if (wParam) + { + if (SWELL_GetOSXVersion() >= 0x1070 && [self respondsToSelector:@selector(endUpdates)]) + { + [self endUpdates]; + // workaround for a weird 10.14.6 bug + // if the caller calls this, then invalidaterect()s the parent window right away, + // appkit will (sometimes) throw an exception unlocking focus on the NSScrollView. + // invalidating our window directly seems to prevent this. + [self setNeedsDisplay:YES]; + } + } + else + { + if (SWELL_GetOSXVersion() >= 0x1070 && [self respondsToSelector:@selector(beginUpdates)]) + [self beginUpdates]; + } + + return 0; + } + return 0; +} + +-(int)getSwellNotificationMode +{ + return m_lbMode; +} +-(void)setSwellNotificationMode:(int)lbMode +{ + m_lbMode=lbMode; +} + +-(void)onSwellCommand:(int)cmd +{ + // ignore commands +} + +@end + +@implementation SWELL_ImageButtonCell +- (NSRect)drawTitle:(NSAttributedString *)title withFrame:(NSRect)frame inView:(NSView *)controlView +{ + return NSMakeRect(0,0,0,0); +} +@end + +@implementation SWELL_ODButtonCell +- (BOOL)isTransparent +{ + return YES; +} +- (BOOL)isOpaque +{ + return NO; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + NSView *ctl=[self controlView]; + if (!ctl) { [super drawWithFrame:cellFrame inView:controlView]; return; } + + HDC hdc=SWELL_CreateGfxContext([NSGraphicsContext currentContext]); + if (hdc) + { + HWND notWnd = GetParent((HWND)ctl); + DRAWITEMSTRUCT dis={ODT_BUTTON,(UINT)[ctl tag],0,0,0,(HWND)ctl,hdc,{0,},0}; + NSRECT_TO_RECT(&dis.rcItem,cellFrame); + SendMessage(notWnd,WM_DRAWITEM,dis.CtlID,(LPARAM)&dis); + + SWELL_DeleteGfxContext(hdc); + } + +} +@end + +@implementation SWELL_ODListViewCell +-(void)setOwnerControl:(SWELL_ListView *)t { m_ownctl=t; m_lastidx=0; } +-(void)setItemIdx:(int)idx +{ + m_lastidx=idx; +} +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + if (!m_ownctl) { [super drawInteriorWithFrame:cellFrame inView:controlView]; return; } + + int itemidx=m_lastidx; + LPARAM itemData=0; + SWELL_ListView_Row *row=m_ownctl->m_items->Get(itemidx); + if (row) itemData=row->m_param; + + HDC hdc=SWELL_CreateGfxContext([NSGraphicsContext currentContext]); + if (hdc) + { + HWND notWnd = GetParent((HWND)m_ownctl); + DRAWITEMSTRUCT dis={ODT_LISTBOX,(UINT)[m_ownctl tag],(UINT)itemidx,0,0,(HWND)m_ownctl,hdc,{0,},(DWORD_PTR)itemData}; + NSRECT_TO_RECT(&dis.rcItem,cellFrame); + SendMessage(notWnd,WM_DRAWITEM,dis.CtlID,(LPARAM)&dis); + + SWELL_DeleteGfxContext(hdc); + } +} + + +@end + + + + + +HWND GetDlgItem(HWND hwnd, int idx) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + + NSView *v=0; + id pid=(id)hwnd; + if ([pid isKindOfClass:[NSWindow class]]) v=[((NSWindow *)pid) contentView]; + else if ([pid isKindOfClass:[NSView class]]) v=(NSView *)pid; + + if (!idx || !v) return (HWND)v; + + SWELL_BEGIN_TRY + + NSArray *ar = [v subviews]; + const NSInteger n=[ar count]; + for (NSInteger x=0;xhwnd && [rec->hwnd respondsToSelector:@selector(swellSetOwner:)]) + [(SWELL_ModelessWindow *)rec->hwnd swellSetOwner:(id)bla]; + rec=rec->_next; + } + } + } + } + // move all child and owned windows over to new window + NSArray *ar=[oldw childWindows]; + if (ar) + { + int x; + for (x = 0; x < [ar count]; x ++) + { + NSWindow *cw=[ar objectAtIndex:x]; + if (cw) + { + [cw retain]; + [oldw removeChildWindow:cw]; + [(NSWindow *)bla addChildWindow:cw ordered:NSWindowAbove]; + [cw release]; + + + } + } + } + + if (oldpar) [oldpar addChildWindow:(NSWindow *)bla ordered:NSWindowAbove]; + if (oldtitle[0]) SetWindowText(hwnd,oldtitle); + + [(NSWindow *)bla setFrame:fr display:dovis]; + [(NSWindow *)bla setLevel:oldlevel]; + if (dovis) ShowWindow(bla,SW_SHOW); + + DestroyWindow((HWND)oldw); + } + else + { + [oldw setContentView:tv]; + [tv release]; + } + + } + } + } + return 0; + } + + if (idx == GWL_HWNDPARENT) + { + NSWindow *window = [pid window]; + if (![window respondsToSelector:@selector(swellGetOwner)]) return 0; + + NSWindow *new_owner = val && [(id)(INT_PTR)val isKindOfClass:[NSView class]] ? [(NSView *)(INT_PTR)val window] : NULL; + if (new_owner && ![new_owner respondsToSelector:@selector(swellAddOwnedWindow:)]) new_owner=NULL; + + NSWindow *old_owner = [(SWELL_ModelessWindow *)window swellGetOwner]; + if (old_owner != new_owner) + { + if (old_owner) [(SWELL_ModelessWindow*)old_owner swellRemoveOwnedWindow:window]; + [(SWELL_ModelessWindow *)window swellSetOwner:nil]; + if (new_owner) [(SWELL_ModelessWindow *)new_owner swellAddOwnedWindow:window]; + } + return (old_owner ? (LONG_PTR)[old_owner contentView] : 0); + } + + if ([pid respondsToSelector:@selector(setSwellExtraData:value:)]) + { + WDL_ASSERT(idx>=0); // caller may be using a GWLP_* which is not yet implemented + LONG_PTR ov=0; + if ([pid respondsToSelector:@selector(getSwellExtraData:)]) ov=(LONG_PTR)[pid getSwellExtraData:idx]; + + [pid setSwellExtraData:idx value:val]; + + return ov; + } + + WDL_ASSERT(false); // caller may be using a GWLP_* which is not yet implemented, or an extra index on a non-hwndchild + SWELL_END_TRY(;) + return 0; +} + +LONG_PTR GetWindowLong(HWND hwnd, int idx) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + id pid=(id)hwnd; + + SWELL_BEGIN_TRY + + if (idx==GWL_EXSTYLE && [pid respondsToSelector:@selector(swellGetExtendedStyle)]) + { + return (LONG_PTR)[pid swellGetExtendedStyle]; + } + + if (idx==GWL_USERDATA && [pid respondsToSelector:@selector(getSwellUserData)]) + { + return (LONG_PTR)[pid getSwellUserData]; + } + if (idx==GWL_USERDATA && [pid isKindOfClass:[NSText class]]) + { + NSView *par = [pid superview]; + if (par) + { + if (![par isKindOfClass:[SWELL_TextField class]]) + par = [par superview]; + if ([par isKindOfClass:[SWELL_TextField class]]) + return [(SWELL_TextField*)par getSwellUserData]; + } + + return 0xdeadf00b; + } + + if (idx==GWL_ID && [pid respondsToSelector:@selector(tag)]) + return [pid tag]; + + + if (idx==GWL_WNDPROC && [pid respondsToSelector:@selector(getSwellWindowProc)]) + { + return (LONG_PTR)[pid getSwellWindowProc]; + } + if (idx==DWL_DLGPROC) + { + if ([pid respondsToSelector:@selector(getSwellDialogProc)]) + { + return (LONG_PTR)[pid getSwellDialogProc]; + } + return 0; // do not assert if GetWindowLongPtr DWLP_DLGPROC, used to query if something is a particular dialog + } + if (idx==GWL_STYLE) + { + int ret=0; + if (![pid isHidden]) ret |= WS_VISIBLE; + if ([pid respondsToSelector:@selector(getSwellStyle)]) + { + return (LONG_PTR)(([pid getSwellStyle]&~WS_VISIBLE) | ret); + } + + if ([pid isKindOfClass:[NSButton class]]) + { + int tmp; + if ([pid isKindOfClass:[NSPopUpButton class]]) ret |= CBS_DROPDOWNLIST; + else if ([pid allowsMixedState]) ret |= BS_AUTO3STATE; + else if ([pid isKindOfClass:[SWELL_Button class]] && (tmp = (int)[pid swellGetRadioFlags])) + { + if (tmp != 4096) + { + ret |= BS_AUTORADIOBUTTON; + if (tmp&2) ret|=WS_GROUP; + } + } + else ret |= BS_AUTOCHECKBOX; + } + + if ([pid isKindOfClass:[NSView class]]) + { + if ([[pid window] contentView] != pid) ret |= WS_CHILDWINDOW; + else + { + NSUInteger smask =[[pid window] styleMask]; + if (smask & NSTitledWindowMask) + { + ret|=WS_CAPTION; + if (smask & NSResizableWindowMask) ret|=WS_THICKFRAME; + } + } + } + + return ret; + } + if (idx == GWL_HWNDPARENT) + { + NSWindow *window = [pid window]; + if (![window respondsToSelector:@selector(swellGetOwner)]) return 0; + NSWindow *old_owner = [(SWELL_ModelessWindow *)window swellGetOwner]; + return (old_owner ? (LONG_PTR)[old_owner contentView] : 0); + } + + if ([pid respondsToSelector:@selector(getSwellExtraData:)]) + { + WDL_ASSERT(idx>=0); // caller may be using a GWLP_* which is not yet implemented + return (LONG_PTR)[pid getSwellExtraData:idx]; + } + + WDL_ASSERT(idx == GWLP_USERDATA); // assert if unknown control, unless GWLP_USERDATA + SWELL_END_TRY(;) + return 0; +} + +static bool IsWindowImpl(NSView *ch, NSView *par) +{ + if (!par || ![par isKindOfClass:[NSView class]]) return false; + + NSArray *ar = [par subviews]; + if (!ar) return false; + [ar retain]; + NSInteger x,n=[ar count]; + for (x=0;xtype != TYPE_BITMAP) return 0; + return i->bitmapptr; +} + + +@implementation SWELL_Button : NSButton + +STANDARD_CONTROL_NEEDSDISPLAY_IMPL("Button") + +-(id) init { + self = [super init]; + if (self != nil) { + m_userdata=0; + m_swellGDIimage=0; + m_radioflags=0; // =4096 if not a checkbox at all + } + return self; +} +-(int)swellGetRadioFlags { return m_radioflags; } +-(void)swellSetRadioFlags:(int)f { m_radioflags=f; } +-(LONG_PTR)getSwellUserData { return m_userdata; } +-(void)setSwellUserData:(LONG_PTR)val { m_userdata=val; } + +-(void)setSwellGDIImage:(void *)par +{ + m_swellGDIimage=par; +} +-(void *)getSwellGDIImage +{ + return m_swellGDIimage; +} + +@end + + +NSFont *SWELL_GetNSFont(HGDIOBJ__ *obj); + +LRESULT SendMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return 0; + + SWELL_BEGIN_TRY + id obj=(id)hwnd; + if ([obj respondsToSelector:@selector(onSwellMessage:p1:p2:)]) + { + return (LRESULT) [obj onSwellMessage:msg p1:wParam p2:lParam]; + } + else + { + if (msg == BM_GETCHECK && [obj isKindOfClass:[NSButton class]]) + { + NSInteger a=[(NSButton*)obj state]; + if (a==NSMixedState) return BST_INDETERMINATE; + return a!=NSOffState; + } + if (msg == BM_SETCHECK && [obj isKindOfClass:[NSButton class]]) + { + [(NSButton*)obj setState:(wParam&BST_INDETERMINATE)?NSMixedState:((wParam&BST_CHECKED)?NSOnState:NSOffState)]; + return 0; + } + if ((msg==BM_GETIMAGE || msg == BM_SETIMAGE) && [obj isKindOfClass:[SWELL_Button class]]) + { + if (wParam != IMAGE_BITMAP && wParam != IMAGE_ICON) return 0; // ignore unknown types + LONG_PTR ret=(LONG_PTR) (void *)[obj getSwellGDIImage]; + if (msg==BM_SETIMAGE) + { + NSImage *img=NULL; + if (lParam) img=(NSImage *)__GetNSImageFromHICON((HICON)lParam); + [obj setImage:img]; + [obj setSwellGDIImage:(void *)(img?lParam:0)]; + } + return ret; + } + else if (msg >= CB_ADDSTRING && msg <= CB_INITSTORAGE && ([obj isKindOfClass:[NSPopUpButton class]] || [obj isKindOfClass:[NSComboBox class]])) + { + switch (msg) + { + case CB_ADDSTRING: return SWELL_CB_AddString(hwnd,0,(char*)lParam); + case CB_DELETESTRING: SWELL_CB_DeleteString(hwnd,0,(int)wParam); return 1; + case CB_GETCOUNT: return SWELL_CB_GetNumItems(hwnd,0); + case CB_GETCURSEL: return SWELL_CB_GetCurSel(hwnd,0); + case CB_GETLBTEXT: return SWELL_CB_GetItemText(hwnd,0,(int)wParam,(char *)lParam, 1<<20); + case CB_GETLBTEXTLEN: return SWELL_CB_GetItemText(hwnd,0,(int)wParam,NULL,0); + case CB_INSERTSTRING: return SWELL_CB_InsertString(hwnd,0,(int)wParam,(char *)lParam); + case CB_RESETCONTENT: SWELL_CB_Empty(hwnd,0); return 0; + case CB_SETCURSEL: SWELL_CB_SetCurSel(hwnd,0,(int)wParam); return 0; + case CB_GETITEMDATA: return SWELL_CB_GetItemData(hwnd,0,(int)wParam); + case CB_SETITEMDATA: SWELL_CB_SetItemData(hwnd,0,(int)wParam,lParam); return 0; + case CB_FINDSTRING: + case CB_FINDSTRINGEXACT: + if (lParam) return SWELL_CB_FindString(hwnd,0,(int)wParam,(const char *)lParam,msg==CB_FINDSTRINGEXACT); + return CB_ERR; + case CB_INITSTORAGE: return 0; + } + return 0; + } + else if (msg >= TBM_GETPOS && msg <= TBM_SETRANGE && ([obj isKindOfClass:[NSSlider class]])) + { + switch (msg) + { + case TBM_GETPOS: return SWELL_TB_GetPos(hwnd,0); + case TBM_SETTIC: SWELL_TB_SetTic(hwnd,0,(int)lParam); return 1; + case TBM_SETPOS: SWELL_TB_SetPos(hwnd,0,(int)lParam); return 1; + case TBM_SETRANGE: SWELL_TB_SetRange(hwnd,0,LOWORD(lParam),HIWORD(lParam)); return 1; + } + return 0; + } + else if ((msg == EM_SETSEL || msg == EM_GETSEL || msg == EM_SETPASSWORDCHAR) && ([obj isKindOfClass:[NSTextField class]])) + { + if (msg == EM_GETSEL) + { + NSRange range={0,}; + NSResponder *rs = [[obj window] firstResponder]; + if ([rs isKindOfClass:[NSView class]] && [(NSView *)rs isDescendantOf:obj]) + { + NSText* text=[[obj window] fieldEditor:YES forObject:(NSTextField*)obj]; + if (text) range=[text selectedRange]; + } + if (wParam) *(int*)wParam=(int)range.location; + if (lParam) *(int*)lParam=(int)(range.location+range.length); + } + else if (msg == EM_SETSEL) + { + // [(NSTextField*)obj selectText:obj]; // Force the window's text field editor onto this control + // don't force it, just ignore EM_GETSEL/EM_SETSEL if not in focus + NSResponder *rs = [[obj window] firstResponder]; + if ([rs isKindOfClass:[NSView class]] && [(NSView *)rs isDescendantOf:obj]) + { + NSText* text = [[obj window] fieldEditor:YES forObject:(NSTextField*)obj]; // then get it from the window + NSUInteger sl = [[text string] length]; + if (wParam == -1) lParam = wParam = 0; + else if (lParam == -1) lParam = sl; + if (wParam>sl) wParam=sl; + if (lParam>sl) lParam=sl; + if (text) [text setSelectedRange:NSMakeRange(wParam, wdl_max(lParam-wParam,0))]; // and set the range + } + } + else if (msg == EM_SETPASSWORDCHAR) + { + // not implemented, because it requires replacing obj within its parent window + // instead caller explicitly destroy the edit control and create a new one with ES_PASSWORD + } + return 0; + } + else if (msg == WM_SETFONT && ([obj isKindOfClass:[NSTextField class]] || + [obj isKindOfClass:[NSTextView class]])) + { + NSFont *font = SWELL_GetNSFont((HGDIOBJ__*)wParam); + if (font) [obj setFont:font]; + return 0; + } + else if (msg == WM_SETFONT && ([obj isKindOfClass:[NSScrollView class]])) + { + NSView *cv=[(NSScrollView *)obj documentView]; + if (cv && [cv isKindOfClass:[NSTextView class]]) + { + NSFont *font = SWELL_GetNSFont((HGDIOBJ__*)wParam); + if (font) [(NSTextView *)cv setFont:font]; + return 0; + } + } + else + { + NSWindow *w; + NSView *v; + // if content view gets unhandled message send to window + if ([obj isKindOfClass:[NSView class]] && (w=[obj window]) && [w contentView] == obj && [w respondsToSelector:@selector(onSwellMessage:p1:p2:)]) + { + return (LRESULT) [(SWELL_hwndChild *)w onSwellMessage:msg p1:wParam p2:lParam]; + } + // if window gets unhandled message send to content view + else if ([obj isKindOfClass:[NSWindow class]] && (v=[obj contentView]) && [v respondsToSelector:@selector(onSwellMessage:p1:p2:)]) + { + return (LRESULT) [(SWELL_hwndChild *)v onSwellMessage:msg p1:wParam p2:lParam]; + } + } + } + SWELL_END_TRY(;) + return 0; +} + +static NSView *NavigateUpScrollClipViews(NSView *ch); + +void DestroyWindow(HWND hwnd) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + SWELL_BEGIN_TRY + id pid=(id)hwnd; + if ([pid isKindOfClass:[NSView class]]) + { + KillTimer(hwnd,~(UINT_PTR)0); + sendSwellMessage((id)pid,WM_DESTROY,0,0); + + pid = NavigateUpScrollClipViews(pid); + NSWindow *pw = [(NSView *)pid window]; + if (pw && [pw contentView] == pid) // destroying contentview should destroy top level window + { + DestroyWindow((HWND)pw); + } + else + { + if (pw) + { + id foc=[pw firstResponder]; + if (foc && (foc == pid || IsChild((HWND)pid,(HWND)foc))) + { + [pw makeFirstResponder:nil]; + } + } + [(NSView *)pid removeFromSuperview]; + } + } + else if ([pid isKindOfClass:[NSWindow class]]) + { + KillTimer(hwnd,~(UINT_PTR)0); + sendSwellMessage([(id)pid contentView],WM_DESTROY,0,0); + sendSwellMessage((id)pid,WM_DESTROY,0,0); + + if ([(id)pid respondsToSelector:@selector(swellDoDestroyStuff)]) + [(id)pid swellDoDestroyStuff]; + + NSWindow *par=[(NSWindow*)pid parentWindow]; + if (par) + { + [par removeChildWindow:(NSWindow*)pid]; + } + [(NSWindow *)pid close]; // this is probably bad, but close takes too long to close! + } + SWELL_END_TRY(;) +} + +void EnableWindow(HWND hwnd, int enable) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + SWELL_BEGIN_TRY + id bla=(id)hwnd; + if ([bla isKindOfClass:[NSWindow class]]) bla = [bla contentView]; + + if (bla && [bla respondsToSelector:@selector(setEnabled:)]) + { + if (!enable) + { + HWND foc = GetFocus(); + if (foc && (foc==hwnd || IsChild(hwnd,foc))) + { + HWND par = GetParent(hwnd); + if (par) SetFocus(par); + } + } + if (enable == -1000 && [bla respondsToSelector:@selector(setEnabledSwellNoFocus)]) + [(SWELL_hwndChild *)bla setEnabledSwellNoFocus]; + else + [bla setEnabled:(enable?YES:NO)]; + if ([bla isKindOfClass:[SWELL_TextField class]]) + [(SWELL_TextField*)bla initColors:-1]; + } + SWELL_END_TRY(;) +} + +void SetForegroundWindow(HWND hwnd) +{ + WDL_ASSERT(hwnd != NULL); + SetFocus(hwnd); +} + +void SetFocus(HWND hwnd) // these take NSWindow/NSView, and return NSView * +{ + id r=(id) hwnd; + if (!r) return; // on win32 SetFocus(NULL) is allowed, removes focus (maybe we should implement) + + SWELL_BEGIN_TRY + if ([r isKindOfClass:[NSWindow class]]) + { + [(NSWindow *)r makeFirstResponder:[(NSWindow *)r contentView]]; + if ([(NSWindow *)r isVisible]) [(NSWindow *)r makeKeyAndOrderFront:nil]; + } + else if (WDL_NORMALLY([r isKindOfClass:[NSView class]])) + { + NSWindow *wnd=[(NSView *)r window]; + if (wnd) + { + if ([wnd isVisible]) + [wnd makeKeyAndOrderFront:nil]; + if ([r acceptsFirstResponder]) + [wnd makeFirstResponder:r]; + } + } + SWELL_END_TRY(;) +} + +void SWELL_GetViewPort(RECT *r, const RECT *sourcerect, bool wantWork) +{ + SWELL_BEGIN_TRY + + NSArray *ar=[NSScreen screens]; + + const NSInteger cnt=[ar count]; + if (!sourcerect || cnt < 2) + { + NSScreen *sc=[NSScreen mainScreen]; + if (sc) + { + NSRect tr=wantWork ? [sc visibleFrame] : [sc frame]; + NSRECT_TO_RECT(r,tr); + } + else + { + r->left=r->top=0; + r->right=1600; + r->bottom=1200; + } + } + else + { + double best_score = -1e20; + // find screen of best intersection + RECT sr = *sourcerect; + if (sr.top > sr.bottom) { sr.top = sourcerect->bottom; sr.bottom = sourcerect->top; } + if (sr.left > sr.right) { sr.left = sourcerect->right; sr.right = sourcerect->left; } + for (NSInteger x = 0; x < cnt; x ++) + { + NSScreen *sc=[ar objectAtIndex:x]; + if (sc) + { + NSRect tr=wantWork ? [sc visibleFrame] : [sc frame]; + RECT tmp; + NSRECT_TO_RECT(&tmp,tr); + + double score; + RECT res; + if (IntersectRect(&res, &tmp, &sr)) + { + score = wdl_abs((res.right-res.left) * (res.bottom-res.top)); + } + else + { + int dx = 0, dy = 0; + if (tmp.left > sr.right) dx = tmp.left - sr.right; + else if (tmp.right < sr.left) dx = sr.left - tmp.right; + if (tmp.bottom < sr.top) dy = tmp.bottom - sr.top; + else if (tmp.top > sr.bottom) dy = tmp.top - sr.bottom; + score = - (dx*dx + dy*dy); + } + + + if (!x || score > best_score) + { + best_score = score; + *r = tmp; + } + } + } + } + SWELL_END_TRY(;) +} + +void ScreenToClient(HWND hwnd, POINT *p) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + // no need to try/catch, this should never have an issue *wince* + + id ch=(id)hwnd; + if ([ch isKindOfClass:[NSWindow class]]) ch=[((NSWindow *)ch) contentView]; + if (WDL_NOT_NORMALLY(!ch || ![ch isKindOfClass:[NSView class]])) return; + + NSWindow *window=[ch window]; + + NSPoint wndpt = [window convertScreenToBase:NSMakePoint(p->x,p->y)]; + + // todo : WM_NCCALCSIZE + NSPOINT_TO_POINT(p, [ch convertPoint:wndpt fromView:nil]); +} + +void ClientToScreen(HWND hwnd, POINT *p) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + + id ch=(id)hwnd; + if ([ch isKindOfClass:[NSWindow class]]) ch=[((NSWindow *)ch) contentView]; + if (!ch || ![ch isKindOfClass:[NSView class]]) return; + + NSWindow *window=[ch window]; + + NSPoint wndpt = [ch convertPoint:NSMakePoint(p->x,p->y) toView:nil]; + + // todo : WM_NCCALCSIZE + + NSPOINT_TO_POINT(p,[window convertBaseToScreen:wndpt]); +} + +static NSView *NavigateUpScrollClipViews(NSView *ch) +{ + NSView *par=[ch superview]; + if (par && [par isKindOfClass:[NSClipView class]]) + { + par=[par superview]; + if (par && [par isKindOfClass:[NSScrollView class]]) + { + ch=par; + } + } + return ch; +} + +HWND SWELL_NavigateUpScrollClipViews(HWND h) +{ + NSView *v = 0; + if (h && [(id)h isKindOfClass:[NSView class]]) v = (NSView *)h; + else if (h && [(id)h isKindOfClass:[NSWindow class]]) v = [(NSWindow *)h contentView]; + if (v) + return (HWND)NavigateUpScrollClipViews(v); + return 0; +} + +bool GetWindowRect(HWND hwnd, RECT *r) +{ + r->left=r->top=r->right=r->bottom=0; + if (WDL_NOT_NORMALLY(!hwnd)) return false; + + SWELL_BEGIN_TRY + + id ch=(id)hwnd; + NSWindow *nswnd; + if ([ch isKindOfClass:[NSView class]] && (nswnd=[(NSView *)ch window]) && [nswnd contentView]==ch) + ch=nswnd; + + if ([ch isKindOfClass:[NSWindow class]]) + { + NSRECT_TO_RECT(r,[ch frame]); + return true; + } + if (![ch isKindOfClass:[NSView class]]) return false; + ch=NavigateUpScrollClipViews(ch); + NSRECT_TO_RECT(r,[ch bounds]); + ClientToScreen((HWND)ch,(POINT *)r); + ClientToScreen((HWND)ch,((POINT *)r)+1); + SWELL_END_TRY(return false;) + + return true; +} + +void GetWindowContentViewRect(HWND hwnd, RECT *r) +{ + SWELL_BEGIN_TRY + NSWindow *nswnd; + if (hwnd && [(id)hwnd isKindOfClass:[NSView class]] && (nswnd=[(NSView *)hwnd window]) && [nswnd contentView]==(id)hwnd) + hwnd=(HWND)nswnd; + + if (hwnd && [(id)hwnd isKindOfClass:[NSWindow class]]) + { + NSView *ch=[(id)hwnd contentView]; + NSRECT_TO_RECT(r,[ch bounds]); + ClientToScreen(hwnd,(POINT *)r); + ClientToScreen(hwnd,((POINT *)r)+1); + } + else GetWindowRect(hwnd,r); + SWELL_END_TRY(;) +} + + +void GetClientRect(HWND hwnd, RECT *r) +{ + r->left=r->top=r->right=r->bottom=0; + if (WDL_NOT_NORMALLY(!hwnd)) return; + + SWELL_BEGIN_TRY + id ch=(id)hwnd; + if ([ch isKindOfClass:[NSWindow class]]) ch=[((NSWindow *)ch) contentView]; + if (!ch || ![ch isKindOfClass:[NSView class]]) return; + ch=NavigateUpScrollClipViews(ch); + + NSRECT_TO_RECT(r,[ch bounds]); + + // todo this may need more attention + NCCALCSIZE_PARAMS tr={{*r,},}; + SendMessage(hwnd,WM_NCCALCSIZE,FALSE,(LPARAM)&tr); + r->right = r->left + (tr.rgrc[0].right-tr.rgrc[0].left); + r->bottom = r->top + (tr.rgrc[0].bottom-tr.rgrc[0].top); + SWELL_END_TRY(;) +} + + + +void SetWindowPos(HWND hwnd, HWND hwndAfter, int x, int y, int cx, int cy, int flags) +{ + if (WDL_NOT_NORMALLY(!hwnd)) return; + + SWELL_BEGIN_TRY + NSWindow *nswnd; // content views = move window + if (hwnd && [(id)hwnd isKindOfClass:[NSView class]] && (nswnd=[(NSView *)hwnd window]) && [nswnd contentView]==(id)hwnd) + hwnd=(HWND)nswnd; + + // todo: handle SWP_SHOWWINDOW + id ch=(id)hwnd; + bool isview=false; + if ([ch isKindOfClass:[NSWindow class]] || (isview=[ch isKindOfClass:[NSView class]])) + { + if (isview) + { + ch=NavigateUpScrollClipViews(ch); + if (isview && !(flags&SWP_NOZORDER)) + { + NSView *v = (NSView *)ch; + NSView *par = [v superview]; + NSArray *subs = [par subviews]; + NSInteger idx = [subs indexOfObjectIdenticalTo:v], cnt=[subs count]; + + NSView *viewafter = NULL; + NSWindowOrderingMode omode = NSWindowAbove; + + if (cnt>1 && hwndAfter != (HWND)ch) + { + if (hwndAfter==HWND_TOPMOST||hwndAfter==HWND_NOTOPMOST) + { + } + else if (hwndAfter == HWND_TOP) + { + if (idx0) viewafter = [subs objectAtIndex:0]; + omode = NSWindowBelow; + } + else + { + NSInteger a=[subs indexOfObjectIdenticalTo:(NSView *)hwndAfter]; + if (a != NSNotFound && a != (idx-1)) viewafter = (NSView *)hwndAfter; + } + } + + if (viewafter) + { + HWND h = GetCapture(); + if (!h || (h!=(HWND)v && !IsChild((HWND)v,h))) // if this window is captured don't reorder! + { + NSView *oldfoc = (NSView*)[[v window] firstResponder]; + if (!oldfoc || ![oldfoc isKindOfClass:[NSView class]] || + (oldfoc != v && ![oldfoc isDescendantOf:v])) oldfoc=NULL; + + // better way to do this? maybe :/ + [v retain]; + [v removeFromSuperviewWithoutNeedingDisplay]; + [par addSubview:v positioned:omode relativeTo:viewafter]; + + if (oldfoc && [oldfoc isKindOfClass:[NSView class]] && [oldfoc window] == [v window]) + [[v window] makeFirstResponder:oldfoc]; + + [v release]; + } + } + } + } + NSRect f=[ch frame]; + bool repos=false; + if (!(flags&SWP_NOMOVE)) + { + f.origin.x=(float)x; + f.origin.y=(float)y; + repos=true; + } + if (!(flags&SWP_NOSIZE)) + { + f.size.width=(float)cx; + f.size.height=(float)cy; + if (f.size.height<0)f.size.height=-f.size.height; + repos=true; + } + if (repos) + { + if (!isview) + { + NSSize mins=[ch minSize]; + NSSize maxs=[ch maxSize]; + if (f.size.width < mins.width) f.size.width=mins.width; + else if (f.size.width > maxs.width) f.size.width=maxs.width; + if (f.size.height < mins.height) f.size.height=mins.height; + else if (f.size.height> maxs.height) f.size.height=maxs.height; + [ch setFrame:f display:NO]; + [ch display]; + } + else + { + // this doesnt seem to actually be a good idea anymore + // if ([[ch window] contentView] != ch && ![[ch superview] isFlipped]) +// f.origin.y -= f.size.height; + [ch setFrame:f]; + if ([ch isKindOfClass:[NSScrollView class]]) + { + NSView *cv=[ch documentView]; + if (cv && [cv isKindOfClass:[NSTextView class]]) + { + NSRect fr=[cv frame]; + NSSize sz=[ch contentSize]; + int a=0; + if (![ch hasHorizontalScroller]) {a ++; fr.size.width=sz.width; } + if (![ch hasVerticalScroller]) { a++; fr.size.height=sz.height; } + if (a) [cv setFrame:fr]; + } + } + } + } + return; + } + SWELL_END_TRY(;) +} + +BOOL EnumWindows(BOOL (*proc)(HWND, LPARAM), LPARAM lp) +{ + NSArray *ch=[NSApp windows]; + [ch retain]; + const NSInteger n=[ch count]; + for(NSInteger x=0;x0) + { + return (HWND)[ar objectAtIndex:0]; + } + return 0; + } + if (what == GW_OWNER) + { + v=NavigateUpScrollClipViews(v); + if ([[v window] contentView] == v) + { + if ([[v window] respondsToSelector:@selector(swellGetOwner)]) + { + return (HWND)[(SWELL_ModelessWindow*)[v window] swellGetOwner]; + } + return 0; + } + return (HWND)[v superview]; + } + + if (what >= GW_HWNDFIRST && what <= GW_HWNDPREV) + { + v=NavigateUpScrollClipViews(v); + if ([[v window] contentView] == v) + { + if (what <= GW_HWNDLAST) return (HWND)hwnd; // content view is only window + + return 0; // we're the content view so cant do next/prev + } + NSView *par=[v superview]; + if (par) + { + NSArray *ar=[par subviews]; + NSInteger cnt; + if (ar && (cnt=[ar count]) > 0) + { + if (what == GW_HWNDFIRST) + return (HWND)[ar objectAtIndex:0]; + if (what == GW_HWNDLAST) + return (HWND)[ar objectAtIndex:(cnt-1)]; + + NSInteger idx=[ar indexOfObjectIdenticalTo:v]; + if (idx == NSNotFound) return 0; + + if (what==GW_HWNDNEXT) idx++; + else if (what==GW_HWNDPREV) idx--; + + if (idx<0 || idx>=cnt) return 0; + + return (HWND)[ar objectAtIndex:idx]; + } + } + return 0; + } + SWELL_END_TRY(;) + return 0; +} + + +HWND GetParent(HWND hwnd) +{ + SWELL_BEGIN_TRY + if (WDL_NORMALLY(hwnd) && [(id)hwnd isKindOfClass:[NSView class]]) + { + hwnd=(HWND)NavigateUpScrollClipViews((NSView *)hwnd); + + NSView *cv=[[(NSView *)hwnd window] contentView]; + if (cv == (NSView *)hwnd) hwnd=(HWND)[(NSView *)hwnd window]; // passthrough to get window parent + else + { + HWND h=(HWND)[(NSView *)hwnd superview]; + return h; + } + } + + if (hwnd && [(id)hwnd isKindOfClass:[NSWindow class]]) + { + HWND h= (HWND)[(NSWindow *)hwnd parentWindow]; + if (h) h=(HWND)[(NSWindow *)h contentView]; + if (h) return h; + } + + if (hwnd && [(id)hwnd respondsToSelector:@selector(swellGetOwner)]) + { + HWND h= (HWND)[(SWELL_ModelessWindow *)hwnd swellGetOwner]; + if (h && [(id)h isKindOfClass:[NSWindow class]]) h=(HWND)[(NSWindow *)h contentView]; + return h; + } + + SWELL_END_TRY(;) + return 0; +} + +HWND SetParent(HWND hwnd, HWND newPar) +{ + SWELL_BEGIN_TRY + NSView *v=(NSView *)hwnd; + WDL_ASSERT(hwnd != NULL); + if (!v || ![(id)v isKindOfClass:[NSView class]]) return 0; + v=NavigateUpScrollClipViews(v); + + if ([(id)hwnd isKindOfClass:[NSView class]]) + { + NSView *tv=(NSView *)hwnd; + if ([[tv window] contentView] == tv) // if we're reparenting a contentview (aka top level window) + { + if (!newPar) return NULL; + + NSView *npv = (NSView *)newPar; + if ([npv isKindOfClass:[NSWindow class]]) npv=[(NSWindow *)npv contentView]; + if (!npv || ![npv isKindOfClass:[NSView class]]) + return NULL; + + char oldtitle[2048]; + oldtitle[0]=0; + GetWindowText(hwnd,oldtitle,sizeof(oldtitle)); + + NSWindow *oldwnd = [tv window]; + id oldown = NULL; + if ([oldwnd respondsToSelector:@selector(swellGetOwner)]) oldown=[(SWELL_ModelessWindow*)oldwnd swellGetOwner]; + + if ([tv isKindOfClass:[SWELL_hwndChild class]]) ((SWELL_hwndChild*)tv)->m_lastTopLevelOwner = oldown; + + [tv retain]; + SWELL_hwndChild *tmpview = [[SWELL_hwndChild alloc] initChild:nil Parent:(NSView *)oldwnd dlgProc:nil Param:0]; + [tmpview release]; + + [npv addSubview:tv]; + [tv release]; + + DestroyWindow((HWND)oldwnd); // close old window since its no longer used + if (oldtitle[0]) SetWindowText(hwnd,oldtitle); + return (HWND)npv; + } + else if (!newPar) // not content view, not parent (so making it a top level modeless dialog) + { + char oldtitle[2048]; + oldtitle[0]=0; + GetWindowText(hwnd,oldtitle,sizeof(oldtitle)); + + [tv retain]; + [tv removeFromSuperview]; + + + unsigned int wf=(NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask|NSResizableWindowMask); + if ([tv respondsToSelector:@selector(swellCreateWindowFlags)]) + wf=(unsigned int)[(SWELL_hwndChild *)tv swellCreateWindowFlags]; + + HWND newOwner=NULL; + if ([tv isKindOfClass:[SWELL_hwndChild class]]) + { + id oldown = ((SWELL_hwndChild*)tv)->m_lastTopLevelOwner; + if (oldown) + { + NSArray *ch=[NSApp windows]; + const NSInteger n = [ch count]; + for(NSInteger x=0;x { + pub additional_script_input: A, +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/mode_struct.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/mode_struct.rs new file mode 100644 index 0000000..d0d0631 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/mode_struct.rs @@ -0,0 +1,10772 @@ +use crate::{ + create_discrete_increment_interval, create_unit_value_interval, full_unit_interval, + negative_if, AbsoluteValue, AbstractTimestamp, ButtonUsage, ControlEvent, ControlType, + ControlValue, DiscreteIncrement, DiscreteValue, EncoderUsage, EnhancedTransformationOutput, + FeedbackScript, FeedbackScriptInput, FeedbackStyle, FeedbackValue, FireMode, Fraction, + Increment, Interval, MinIsMaxBehavior, ModeContext, NumericFeedbackValue, OutOfRangeBehavior, + PressDurationProcessor, PropProvider, TakeoverMode, Target, TextualFeedbackValue, + Transformation, TransformationInstruction, UnitIncrement, UnitValue, ValueSequence, + BASE_EPSILON, +}; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use derive_more::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use regex::Captures; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::borrow::Cow; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::{Display, Formatter}; +use std::time::{Duration, Instant}; +use strum::EnumIter; + +/// When interpreting target value, make only 4 fractional digits matter. +/// +/// If we don't do this and target min == target max, even the slightest imprecision of the actual +/// target value (which in practice often occurs with FX parameters not taking exactly the desired +/// value) could result in a totally different feedback value. Maybe it would be better to determine +/// the epsilon dependent on the source precision (e.g. 1.0/128.0 in case of short MIDI messages) +/// but right now this should suffice to solve the immediate problem. +pub const FEEDBACK_EPSILON: f64 = BASE_EPSILON; + +/// 0.01 has been chosen as default minimum step size because it corresponds to 1%. +pub const DEFAULT_STEP_SIZE: f64 = 0.01; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct ModeControlOptions { + pub enforce_rotate: bool, +} + +pub trait TransformationInputProvider { + fn additional_input(&self) -> T; +} + +// It's quite practical and makes sense to let the unit control context (basically a control context +// that is empty) to always create the default transformation input. It also saves some plumbing +// because we couldn't implement TransformationInputProvider for () in other crates. We need to do +// it here. Otherwise we would have +impl TransformationInputProvider for () { + fn additional_input(&self) -> T { + Default::default() + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct ModeFeedbackOptions { + pub source_is_virtual: bool, + pub max_discrete_source_value: Option, +} + +#[derive(Clone, Debug)] +pub enum FeedbackValueTable { + FromTextToDiscrete(NonCryptoHashMap), + FromTextToContinuous(NonCryptoHashMap), +} + +impl FeedbackValueTable { + pub fn transform_value<'a, 'c>( + &self, + value: Cow<'a, FeedbackValue<'c>>, + ) -> Option>> { + match self { + FeedbackValueTable::FromTextToDiscrete(map) => match value.as_ref() { + FeedbackValue::Textual(v) => { + let discrete_value = map.get(v.text.as_ref())?; + let numeric_value = NumericFeedbackValue::new( + v.style, + AbsoluteValue::Discrete(Fraction::new_max(*discrete_value)), + ); + Some(Cow::Owned(FeedbackValue::Numeric(numeric_value))) + } + _ => Some(value), + }, + FeedbackValueTable::FromTextToContinuous(map) => match value.as_ref() { + FeedbackValue::Textual(v) => { + let continuous_value = map.get(v.text.as_ref())?; + let numeric_value = NumericFeedbackValue::new( + v.style, + AbsoluteValue::Continuous(UnitValue::new_clamped(*continuous_value)), + ); + Some(Cow::Owned(FeedbackValue::Numeric(numeric_value))) + } + _ => Some(value), + }, + } + } +} + +impl Default for FeedbackValueTable { + fn default() -> Self { + Self::FromTextToDiscrete(HashMap::default()) + } +} + +#[derive(Clone, Debug)] +pub struct ModeSettings FeedbackScript<'a>> { + pub absolute_mode: AbsoluteMode, + pub source_value_interval: Interval, + pub discrete_source_value_interval: Interval, + pub target_value_interval: Interval, + pub discrete_target_value_interval: Interval, + /// Negative increments represent fractions (throttling), e.g. -2 fires an increment every + /// 2nd time only. + pub step_factor_interval: Interval, + pub step_size_interval: Interval, + pub jump_interval: Interval, + pub discrete_jump_interval: Interval, + pub takeover_mode: TakeoverMode, + pub encoder_usage: EncoderUsage, + pub button_usage: ButtonUsage, + pub reverse: bool, + pub rotate: bool, + pub round_target_value: bool, + pub out_of_range_behavior: OutOfRangeBehavior, + pub control_transformation: Option, + pub feedback_transformation: Option, + pub feedback_value_table: Option, + /// Converts incoming relative messages to absolute ones. + pub make_absolute: bool, + /// Not in use at the moment, should always be `false`. + pub use_discrete_processing: bool, + pub fire_mode: FireMode, + pub press_duration_interval: Interval, + pub turbo_rate: Duration, + pub target_value_sequence: ValueSequence, + pub feedback_processor: FeedbackProcessor, + pub feedback_color: Option, + pub feedback_background_color: Option, +} + +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum VirtualColor { + Rgb(RgbColor), + Prop { + #[serde(rename = "prop")] + prop: String, + }, +} + +impl VirtualColor { + fn resolve(&self, prop_provider: &impl PropProvider) -> Option { + use VirtualColor::*; + match self { + Rgb(color) => Some(*color), + Prop { prop } => { + if let PropValue::Color(color) = prop_provider.get_prop_value(prop)? { + Some(color) + } else { + None + } + } + } + } +} + +const ZERO_DURATION: Duration = Duration::from_millis(0); + +impl FeedbackScript<'a>> Default for ModeSettings { + fn default() -> Self { + ModeSettings { + absolute_mode: AbsoluteMode::Normal, + source_value_interval: full_unit_interval(), + discrete_source_value_interval: full_discrete_interval(), + target_value_interval: full_unit_interval(), + discrete_target_value_interval: full_discrete_interval(), + step_size_interval: default_step_size_interval(), + step_factor_interval: default_step_count_interval(), + jump_interval: full_unit_interval(), + discrete_jump_interval: full_discrete_interval(), + takeover_mode: Default::default(), + button_usage: Default::default(), + encoder_usage: Default::default(), + reverse: false, + round_target_value: false, + out_of_range_behavior: OutOfRangeBehavior::MinOrMax, + control_transformation: None, + feedback_transformation: None, + rotate: false, + make_absolute: false, + use_discrete_processing: false, + fire_mode: FireMode::Normal, + press_duration_interval: Interval::new(ZERO_DURATION, ZERO_DURATION), + turbo_rate: ZERO_DURATION, + target_value_sequence: Default::default(), + feedback_processor: FeedbackProcessor::Numeric, + feedback_color: None, + feedback_background_color: None, + feedback_value_table: None, + } + } +} + +/// Settings for processing all kinds of control values. +/// +/// ## How relative control values are processed (or button taps interpreted as increments). +/// +/// Here's an overview in which cases step counts are used and in which step sizes. +/// This is the same, no matter if the source emits relative increments or absolute values +/// ("relative one-direction mode"). +/// +/// - Target wants relative increments: __Step counts__ +/// - Example: Action with invocation type "Relative" +/// - Displayed as: "{count} x" +/// - Target wants absolute values +/// - Target is continuous, optionally roundable: __Step sizes__ +/// - Example: Track volume +/// - Displayed as: "{size} {unit}" +/// - Target is discrete: __Step counts__ +/// - Example: FX preset, some FX params +/// - Displayed as: "{count} x" or "{count}" (former if source emits increments) TODO I +/// think now we have only the "x" variant +#[derive(Clone, Debug)] +pub struct Mode FeedbackScript<'a>, S: AbstractTimestamp> { + settings: ModeSettings, + state: ModeState, +} + +#[derive(Clone, Debug)] +struct ModeState { + press_duration_processor: PressDurationProcessor, + /// For relative-to-absolute mode + current_absolute_value: UnitValue, + #[allow(dead_code)] + discrete_current_absolute_value: u32, + /// Counter for implementing throttling. + /// + /// Throttling is implemented by spitting out control values only every nth time. The counter + /// can take positive or negative values in order to detect direction changes. This is positive + /// when the last change was a positive increment and negative when the last change was a + /// negative increment. + increment_counter: i32, + /// This contains the previous control event at a very early stage of processing + /// (right after normalization in terms of source min/max). + /// + /// Used in absolute control for "make relative" and advanced takeover modes to calculate the + /// next value based on the previous one. + previous_source_normalized_control_event: Option>, + previous_jump_prevention_state: Option>, + /// For absolute control + unpacked_target_value_sequence: Vec, + /// For relative control + unpacked_target_value_set: BTreeSet, + /// For textual feedback + feedback_props_in_use: NonCryptoHashSet, + /// Supposed to contain the final target value after the last control with this mode. + /// + /// The mode knows the value that it produced for the consumer, so the consumer sends it + /// to the target. But the target might end up with another value actually. + final_target_value_from_previous_control: Option, +} + +#[derive(Copy, Clone, Debug)] +struct JumpPreventionState { + /// This contains the previous control event at a later stage of processing + /// (right after pepping up, e.g. applying control transformation and reverse). + /// + /// Used in absolute control for ensuring smooth takeover modes. + prepped_control_event: ControlEvent, + /// Used in absolute control for takeover modes to "hold on" during continuous movements. + takeover_in_sync: bool, + /// Resulting target value. + /// + /// Once `Some`, it should remain `Some`! Only `None` in the beginning. + last_emitted_target_value: Option, +} + +impl JumpPreventionState { + pub fn new( + prepped_control_event: ControlEvent, + takeover_in_sync: bool, + result: Option>, + prev_jump_state: &Self, + ) -> Self { + Self { + prepped_control_event, + takeover_in_sync, + last_emitted_target_value: result + .map(|r| r.value()) + .or(prev_jump_state.last_emitted_target_value), + } + } +} + +impl Default for ModeState { + fn default() -> Self { + Self { + press_duration_processor: Default::default(), + current_absolute_value: Default::default(), + discrete_current_absolute_value: 0, + increment_counter: 0, + previous_source_normalized_control_event: None, + previous_jump_prevention_state: None, + unpacked_target_value_sequence: vec![], + unpacked_target_value_set: Default::default(), + feedback_props_in_use: Default::default(), + final_target_value_from_previous_control: None, + } + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize_repr, + Deserialize_repr, +)] +#[repr(usize)] +pub enum AbsoluteMode { + #[default] + #[display(fmt = "Normal")] + Normal = 0, + #[display(fmt = "Incremental button")] + IncrementalButton = 1, + #[display(fmt = "Toggle button")] + ToggleButton = 2, + #[display(fmt = "Make relative")] + MakeRelative = 3, + #[display(fmt = "Performance control")] + PerformanceControl = 4, +} + +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize_repr, + Deserialize_repr, +)] +#[repr(usize)] +pub enum FeedbackType { + #[default] + #[display(fmt = "Numeric feedback: EEL transformation")] + Numeric = 0, + #[display(fmt = "Textual feedback: Text expression")] + Text = 1, + #[display(fmt = "Dynamic feedback: Lua script")] + Dynamic = 2, +} + +impl FeedbackType { + pub fn is_textual(self) -> bool { + matches!(self, FeedbackType::Text | FeedbackType::Dynamic) + } +} + +#[derive(Clone, Debug)] +pub enum FeedbackProcessor { + Numeric, + Text { expression: String }, + Dynamic { script: F }, +} + +impl FeedbackProcessor { + pub fn is_complex(&self) -> bool { + !matches!(self, FeedbackProcessor::Numeric) + } +} + +/// Human-readable numeric value (not normalized, not zero-rooted). +/// +/// The concrete type (decimal, discrete) just serves as a hint how to do the default formatting: +/// With or without decimal points. In general, all numeric values should be treatable the same +/// way, which is especially important if we want to add "value formatters" to the textual feedback +/// expressions in future. Numeric value formatters then should work on all numeric values in the +/// same way, the sub type shouldn't make a difference. +#[derive(Clone, PartialEq, Debug)] +pub enum NumericValue { + Decimal(f64), + /// Not zero-rooted if it's a number that represents a position. + Discrete(i32), +} + +#[derive(Clone, PartialEq, Debug)] +pub enum PropValue { + /// Aka percentage. + Normalized(UnitValue), + /// Always a number that represents a position. Zero-rooted. So not human-friendly (which is + /// the difference to `Numeric`)! Important for users to know that such a type is + /// returned because then they know that they just need to add a *one* in order to obtain a + /// human-friendly position. We don't want to provide each prop value as both 0-rooted index and + /// 1-rooted position. + Index(u32), + /// On/off style. + Boolean(bool), + /// Human-friendly numeric representation. + Numeric(NumericValue), + /// Textual representation. + Text(Cow<'static, str>), + /// Color. + Color(RgbColor), + /// Duration in millisecond precision. + DurationInMillis(u64), +} + +impl From for PropValue { + fn from(value: String) -> Self { + PropValue::Text(value.into()) + } +} + +impl From> for PropValue { + fn from(value: Cow<'static, str>) -> Self { + PropValue::Text(value) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Serialize, Deserialize)] +pub struct RgbColor(u8, u8, u8); + +impl From for image::Rgb { + fn from(value: RgbColor) -> Self { + Self([value.0, value.1, value.2]) + } +} + +impl From for image::Rgba { + fn from(value: RgbColor) -> Self { + Self([value.0, value.1, value.2, 255]) + } +} + +impl Display for RgbColor { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "rgb({}, {}, {})", self.0, self.1, self.2) + } +} + +impl RgbColor { + pub const BLACK: Self = Self::new(0x00, 0x00, 0x00); + pub const WHITE: Self = Self::new(0xFF, 0xFF, 0xFF); + + pub const fn new(r: u8, g: u8, b: u8) -> Self { + RgbColor(r, g, b) + } + + pub const fn r(&self) -> u8 { + self.0 + } + + pub const fn g(&self) -> u8 { + self.1 + } + + pub const fn b(&self) -> u8 { + self.2 + } +} + +impl Default for PropValue { + fn default() -> Self { + Self::Text(Cow::Owned(String::new())) + } +} + +impl PropValue { + pub fn to_percentage(&self) -> Option { + use PropValue::*; + if let Normalized(v) = self { + Some(AbsoluteValue::Continuous(*v)) + } else { + None + } + } + + pub fn into_textual(self) -> Cow<'static, str> { + use PropValue::*; + match self { + Normalized(v) => format!("{:.2}", v.get() * 100.0).into(), + Numeric(v) => v.into_textual().into(), + Index(i) => i.to_string().into(), + Text(text) => text, + Color(color) => format!("{color:?}").into(), + Boolean(state) => format!("{state:?}").into(), + DurationInMillis(millis) => format!("{millis}ms").into(), + } + } +} + +impl NumericValue { + pub fn into_textual(self) -> String { + use NumericValue::*; + match self { + Decimal(v) => format!("{v:.2}"), + Discrete(v) => v.to_string(), + } + } +} + +impl Mode +where + T: Transformation, + F: for<'a> FeedbackScript<'a>, + S: AbstractTimestamp, +{ + pub fn new(settings: ModeSettings) -> Self { + let state = ModeState { + press_duration_processor: PressDurationProcessor::new( + settings.fire_mode, + settings.press_duration_interval, + settings.turbo_rate, + settings.button_usage, + ), + feedback_props_in_use: { + let mut set = match &settings.feedback_processor { + FeedbackProcessor::Numeric => { + // Numeric feedback doesn't use any target props + NonCryptoHashSet::default() + } + FeedbackProcessor::Text { expression } => { + let mut set = HashSet::default(); + // Text feedback based on a text expression probably uses target props. + // We extract them statically by looking at the expression. + if expression.is_empty() { + set.insert(DEFAULT_TEXTUAL_FEEDBACK_PROP_KEY.to_string()); + } else { + set.extend( + textual_feedback_expression_regex() + .captures_iter(expression) + .map(|cap| cap[1].to_string()), + ); + } + set + } + FeedbackProcessor::Dynamic { script } => { + // Dynamic feedback based on a script probably uses target props. + script.used_props().unwrap_or_default() + } + }; + if let Some(VirtualColor::Prop { prop }) = settings.feedback_color.as_ref() { + set.insert(prop.to_string()); + } + if let Some(VirtualColor::Prop { prop }) = + settings.feedback_background_color.as_ref() + { + set.insert(prop.to_string()); + } + set + }, + ..Default::default() + }; + Mode { settings, state } + } + + pub fn settings(&self) -> &ModeSettings { + &self.settings + } + + /// Processes the given control value and maybe returns an appropriate target control value. + /// + /// `None` either means ignored or target value already has desired value. + #[cfg(test)] + fn control<'a, C: Copy + TransformationInputProvider + Into, TC>( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + ) -> Option { + self.control_with_options( + control_event, + target, + context, + ModeControlOptions::default(), + None, + )? + .into() + } + + /// Processes the given control value and maybe returns an appropriate target control value. + /// + /// `None` means the incoming source control value doesn't reach the target because it's + /// filtered out (e.g. because of button filter "Press only"). + pub fn control_with_options< + 'a, + C: Copy + TransformationInputProvider + Into, + TC, + >( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + last_non_performance_target_value: Option, + ) -> Option> { + match control_event.payload() { + ControlValue::AbsoluteContinuous(v) => self.control_absolute( + control_event.with_payload(AbsoluteValue::Continuous(v)), + target, + context, + true, + options, + last_non_performance_target_value, + ), + ControlValue::AbsoluteDiscrete(v) => self.control_absolute( + control_event.with_payload(AbsoluteValue::Discrete(v)), + target, + context, + true, + options, + last_non_performance_target_value, + ), + ControlValue::RelativeDiscrete(i) => self.control_relative( + control_event.with_payload(Increment::Discrete(i)), + target, + context, + options, + ), + ControlValue::RelativeContinuous(i) => self.control_relative( + control_event.with_payload(Increment::Continuous(i)), + target, + context, + options, + ), + } + } + + /// When `true`, one must use methods such as `build_feedback`. + pub fn wants_advanced_feedback(&self) -> bool { + self.settings.feedback_processor.is_complex() + } + + pub fn feedback_props_in_use(&self) -> &NonCryptoHashSet { + &self.state.feedback_props_in_use + } + + pub fn build_feedback( + &self, + prop_provider: &impl PropProvider, + context: ModeContext<>::AdditionalInput>, + ) -> FeedbackValue { + match &self.settings.feedback_processor { + FeedbackProcessor::Numeric => { + unreachable!("Numeric feedback processor doesn't need build step"); + } + FeedbackProcessor::Text { expression } => { + let style = self.feedback_style(prop_provider); + let text = if expression.is_empty() { + prop_provider + .get_prop_value(DEFAULT_TEXTUAL_FEEDBACK_PROP_KEY) + .unwrap_or_default() + .into_textual() + } else { + textual_feedback_expression_regex().replace_all(expression, |c: &Captures| { + prop_provider + .get_prop_value(&c[1]) + .unwrap_or_default() + .into_textual() + }) + }; + FeedbackValue::Textual(TextualFeedbackValue::new(style, text)) + } + FeedbackProcessor::Dynamic { script } => { + let input = FeedbackScriptInput { prop_provider }; + match script.feedback(input, context.additional_script_input) { + Ok(o) => o.feedback_value, + Err(e) => { + let style = self.feedback_style(prop_provider); + FeedbackValue::Textual(TextualFeedbackValue::new( + style, + e.to_string().into(), + )) + } + } + } + } + } + + pub fn feedback_style(&self, prop_provider: &impl PropProvider) -> FeedbackStyle { + FeedbackStyle { + color: self + .settings + .feedback_color + .as_ref() + .and_then(|c| c.resolve(prop_provider)), + background_color: self + .settings + .feedback_background_color + .as_ref() + .and_then(|c| c.resolve(prop_provider)), + } + } + + #[cfg(test)] + fn feedback(&self, target_value: AbsoluteValue) -> Option { + self.feedback_with_options(target_value, ModeFeedbackOptions::default()) + } + + #[cfg(test)] + fn feedback_with_options( + &self, + target_value: AbsoluteValue, + options: ModeFeedbackOptions, + ) -> Option { + let in_cow = Cow::Owned(FeedbackValue::Numeric(NumericFeedbackValue::new( + Default::default(), + target_value, + ))); + let out_cow = + self.feedback_with_options_detail(Some(in_cow), options, Default::default())?; + Some(out_cow.to_numeric()?.value) + } + + /// Takes a target value, interprets and transforms it conforming to mode rules and + /// maybe returns an appropriate source value that should be sent to the source. + pub fn feedback_with_options_detail<'a, 'c>( + &self, + target_value: Option>>, + options: ModeFeedbackOptions, + additional_transformation_input: T::AdditionalInput, + ) -> Option>> { + match target_value { + None => { + // Target didn't return any value. Return minimum value and apply at least source min/max. + // I think the best way to deal with it is to emit the minimum feedback value = 0%. Emitting 0% is different + // from off. "Off" means, the mapping and/or target is inactive. So emitting 0% makes the source aware that + // an active target is connected. + // Important: Don't feed the numeric feedback transformation with 0%. Either omit feedback + // transformation (the current way) or let feedback transformation know if a "none" value is passed + // (a possible future improvement). + let v = self.apply_feedback_source_interval( + AbsoluteValue::Continuous(UnitValue::MIN), + options, + ); + let result = NumericFeedbackValue::new(FeedbackStyle::default(), v); + Some(Cow::Owned(FeedbackValue::Numeric(result))) + } + // Numeric value + Some(Cow::Owned(FeedbackValue::Numeric(feedback_value))) => { + let res = self.feedback_numerical_target_value( + feedback_value, + options, + additional_transformation_input, + )?; + Some(Cow::Owned(res)) + } + // Numeric value + Some(Cow::Borrowed(FeedbackValue::Numeric(feedback_value))) => { + let res = self.feedback_numerical_target_value( + *feedback_value, + options, + additional_transformation_input, + )?; + Some(Cow::Owned(res)) + } + // Text or complex + Some(v) => { + // Either return directly or - if applicable - apply feedback table + if let Some(table) = self.settings.feedback_value_table.as_ref() { + table.transform_value(v) + } else { + Some(v) + } + } + } + } + + fn feedback_numerical_target_value( + &self, + feedback_value: NumericFeedbackValue, + options: ModeFeedbackOptions, + additional_transformation_input: T::AdditionalInput, + ) -> Option> { + let v = feedback_value.value; + // 4. Filter and Apply target interval (normalize) + let interval_match_result = v.matches_tolerant( + &self.settings.target_value_interval, + &self.settings.discrete_target_value_interval, + self.settings.use_discrete_processing, + FEEDBACK_EPSILON, + ); + let (mut v, min_is_max_behavior) = if interval_match_result.matches() { + // Target value is within target value interval + (v, MinIsMaxBehavior::PreferOne) + } else { + // Target value is outside target value interval + self.settings.out_of_range_behavior.process( + v, + interval_match_result, + &self.settings.target_value_interval, + &self.settings.discrete_target_value_interval, + )? + }; + // Tolerant interval bounds test because of https://github.com/helgoboss/helgobox/issues/263. + // TODO-medium The most elaborate solution to deal with discrete values would be to actually + // know which interval of floating point values represents a specific discrete target value. + // However, is there a generic way to know that? Taking the target step size as epsilon in this + // case sounds good but we still don't know if the target respects approximate values, if it + // rounds them or uses more a ceil/floor approach ... I don't think this is standardized for + // VST parameters. We could solve it for our own parameters in future. Until then, having a + // fixed epsilon deals at least with most issues I guess. + v = v.normalize( + &self.settings.target_value_interval, + &self.settings.discrete_target_value_interval, + min_is_max_behavior, + self.settings.use_discrete_processing, + FEEDBACK_EPSILON, + ); + // 3. Apply reverse + if self.settings.reverse { + let normalized_max_discrete_source_value = options.max_discrete_source_value.map(|m| { + self.settings + .discrete_source_value_interval + .normalize_to_min(m) + }); + v = v.inverse(normalized_max_discrete_source_value); + }; + // 2. Apply transformation + if let Some(transformation) = self.settings.feedback_transformation.as_ref() { + if let Ok(output) = v.transform( + transformation, + Some(v), + self.settings.use_discrete_processing, + Duration::ZERO, + Instant::now().duration(), + additional_transformation_input, + ) { + // For feedback, only absolute result values are accepted, relative ones are ignored. + v = output.value?.to_absolute_value().ok()?; + } + }; + // 1. Apply source interval + v = self.apply_feedback_source_interval(v, options); + let result = NumericFeedbackValue::new(feedback_value.style, v); + Some(FeedbackValue::Numeric(result)) + } + + fn apply_feedback_source_interval( + &self, + mut v: AbsoluteValue, + options: ModeFeedbackOptions, + ) -> AbsoluteValue { + v = v.denormalize( + &self.settings.source_value_interval, + &self.settings.discrete_source_value_interval, + self.settings.use_discrete_processing, + options.max_discrete_source_value, + ); + // Result + if !self.settings.use_discrete_processing && !options.source_is_virtual { + // If discrete processing is not explicitly enabled, we must NOT send discrete values to + // a real (non-virtual) source! This is not just for backward compatibility. It would change + // how discrete sources react in a surprising way (discrete behavior without having + // discrete processing enabled). + v = v.to_continuous_value(); + }; + v + } + + fn process_control_transformation_output( + &mut self, + output: EnhancedTransformationOutput, + ) -> Option { + match (output.value, output.instruction) { + // Neither control nor stop instruction + (None, None) => None, + // Stop instruction without control + (None, Some(TransformationInstruction::Stop)) => { + // Resetting the previous event will stop polling until the next mapping + // invocation. + self.state.previous_source_normalized_control_event = None; + None + } + // Control without stop instruction + (Some(v), None) => Some(v), + // Both control and stop instruction + (Some(v), Some(TransformationInstruction::Stop)) => { + // Resetting the previous event will stop polling until the next mapping + // invocation. + self.state.previous_source_normalized_control_event = None; + Some(v) + } + } + } + + fn calc_rel_time(&self, now: S) -> Duration { + self.state + .previous_source_normalized_control_event + .map(|evt| now - evt.timestamp()) + .unwrap_or_default() + } + + /// If this returns `true`, the `poll` method should be called, on a regular basis. + pub fn wants_to_be_polled(&self) -> bool { + self.state.press_duration_processor.wants_to_be_polled() + || self + .settings + .control_transformation + .iter() + .any(|t| t.wants_to_be_polled()) + } + + /// This function should be called regularly if the features are needed that are driven by a + /// timer (fire on length min, turbo, transitions, etc.). Returns a target control value + /// whenever it's time to fire. + pub fn poll<'a, C: Copy + TransformationInputProvider + Into, TC>( + &mut self, + target: &impl Target<'a, Context = TC>, + context: C, + timestamp: S, + ) -> Option> { + // Let the press duration processor do its job. We do that even if we a transition because + // the press might restart the transition. We want single press and fire after timeout to + // still work even when using transitions. It has priority even. + if let Some(control_value) = self.state.press_duration_processor.poll() { + return self.control_absolute( + ControlEvent::new(control_value, timestamp), + target, + context, + false, + ModeControlOptions::default(), + // Polling is only for buttons. "Performance control" mode is only for range elements. + None, + ); + }; + // If we have a transition (a transformation which depends on the current timestamp), we + // poll this one as well. + if let Some(transformation) = &self.settings.control_transformation { + if transformation.wants_to_be_polled() { + let evt = self.state.previous_source_normalized_control_event?; + let output = evt + .payload() + .transform( + transformation, + target.current_value(context.into()), + self.settings.use_discrete_processing, + self.calc_rel_time(timestamp), + timestamp.duration(), + context.additional_input(), + ) + .ok()?; + let in_cv = self.process_control_transformation_output(output)?; + let out_cv = match in_cv.to_absolute_value() { + // Absolute values might get reversed and rounded + Ok(mut abs_v) => { + let control_type = target.control_type(context.into()); + abs_v = self.apply_reverse(control_type, abs_v); + abs_v = self + .apply_rounded_target_interval_or_target_sequence(control_type, abs_v); + ControlValue::from_absolute(abs_v) + } + // Relative value stay as they are + _ => in_cv, + }; + return Some(ModeControlResult::hit_target(out_cv)); + } + } + None + } + + /// This should be called when the containing mapping gets deactivated. + /// + /// Attention: At the moment it can be called even if the mapping was already inactive. + /// So it should be idempotent! + pub fn on_deactivate(&mut self) { + // Clear the previous absolute value so we don't get jumps when using "Make relative" + // and using this mapping next time it's active again. + self.state.previous_source_normalized_control_event = None; + } + + pub fn wants_to_know_final_target_value(&self) -> bool { + self.has_jump_restrictions() + } + + fn has_jump_restrictions(&self) -> bool { + self.settings.takeover_mode.prevents_jumps() && !self.settings.jump_interval.is_full() + } + + pub fn report_final_target_value_of_last_control(&mut self, value: AbsoluteValue) { + self.state.final_target_value_from_previous_control = Some(value); + } + + /// Gives the mode the opportunity to update internal state when it's being connected to a + /// target (either initial target resolve or refreshing target resolve). + pub fn update_from_target<'a, C: Copy + Into, TC>( + &mut self, + target: &impl Target<'a, Context = TC>, + context: C, + is_just_refresh: bool, + ) { + let default_step_size = target + .control_type(context.into()) + .step_size() + .unwrap_or_else(|| UnitValue::new(DEFAULT_STEP_SIZE)); + let unpacked_sequence = self + .settings + .target_value_sequence + .unpack(default_step_size); + self.state.unpacked_target_value_set = unpacked_sequence.iter().copied().collect(); + self.state.unpacked_target_value_sequence = unpacked_sequence; + // If this is just a target refresh, we don't want to clear the following states because this + // would break takeover modes (https://github.com/helgoboss/helgobox/issues/1545). + if !is_just_refresh { + self.state.previous_jump_prevention_state = None; + self.state.final_target_value_from_previous_control = None; + } + } + + fn control_relative< + 'a, + C: Copy + TransformationInputProvider + Into, + TC, + >( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + ) -> Option> { + if !self.settings.encoder_usage.matches(control_event.payload()) { + return None; + } + if self.settings.make_absolute { + Some( + self.control_relative_to_absolute(control_event, target, context, options)? + .map(|v| ControlValue::AbsoluteContinuous(v.to_unit_value())), + ) + } else { + self.control_relative_normal(control_event.payload(), target, context, options) + } + } + + fn control_absolute< + 'a, + C: Copy + TransformationInputProvider + Into, + TC, + >( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + consider_press_duration: bool, + options: ModeControlOptions, + last_non_performance_target_value: Option, + ) -> Option> { + // Filter presses/releases. Makes sense only for absolute mode "Normal". If this is used + // a filter is used with another absolute mode, it's considered a usage fault. + let mut v = control_event.payload(); + // Press duration + if consider_press_duration { + // When press duration is considered (in all cases except polling), the press duration processor + // should decide how to interpret the button usage settings. + v = self + .state + .press_duration_processor + .process_press_or_release(v, self.settings.button_usage)?; + } else { + // When press duration must not be considered (when polling), process the button usage settings right here + if self.settings.button_usage.should_ignore(v) { + return None; + } + } + // Dispatch + let control_event = control_event.with_payload(v); + use AbsoluteMode::*; + match self.settings.absolute_mode { + Normal => Some(self.control_absolute_normal(control_event, target, context, None)?), + IncrementalButton => self.control_absolute_incremental_buttons( + control_event.with_payload(v.to_unit_value()), + target, + context, + options, + ), + ToggleButton => Some( + self.control_absolute_toggle_buttons(v, target, context)? + .map(|v| ControlValue::AbsoluteContinuous(v.to_unit_value())), + ), + MakeRelative => { + self.control_absolute_to_relative(control_event, target, context, options) + } + PerformanceControl => Some(self.control_absolute_normal( + control_event, + target, + context, + last_non_performance_target_value, + )?), + } + } + + /// Processes the given control value in absolute mode and maybe returns an appropriate target + /// value. + /// + /// Provide `last_non_performance_target_value` only if you want "Performance control". + fn control_absolute_normal< + 'a, + C: Copy + TransformationInputProvider + Into, + TC, + >( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + last_non_performance_target_value: Option, + ) -> Option> { + let res = self.pre_process_absolute_value(control_event)?; + let current_target_value = target.current_value(context.into()); + let control_type = target.control_type(context.into()); + let prepped_control_value = self.prepare_absolute_value( + res.control_event, + control_type, + current_target_value, + context.additional_input(), + last_non_performance_target_value, + )?; + match prepped_control_value { + ControlValue::AbsoluteContinuous(v) => { + let abs_res = self.hitting_target_considering_max_jump( + AbsoluteValue::Continuous(v), + current_target_value, + control_type, + res.control_event, + res.prev_control_event, + ); + abs_res.map(|v| v.map(ControlValue::from_absolute)) + } + ControlValue::AbsoluteDiscrete(v) => { + let abs_res = self.hitting_target_considering_max_jump( + AbsoluteValue::Discrete(v), + current_target_value, + control_type, + res.control_event, + res.prev_control_event, + ); + abs_res.map(|v| v.map(ControlValue::from_absolute)) + } + ControlValue::RelativeContinuous(v) => self.control_relative_normal( + Increment::Continuous(v), + target, + context, + ModeControlOptions::default(), + ), + ControlValue::RelativeDiscrete(v) => self.control_relative_normal( + Increment::Discrete(v), + target, + context, + ModeControlOptions::default(), + ), + } + } + + fn pre_process_absolute_value( + &mut self, + control_event: ControlEvent, + ) -> Option> { + let control_value = control_event.payload(); + let interval_match_result = control_value.matches_tolerant( + &self.settings.source_value_interval, + &self.settings.discrete_source_value_interval, + self.settings.use_discrete_processing, + BASE_EPSILON, + ); + let (source_bound_value, min_is_max_behavior) = if interval_match_result.matches() { + // Control value is within source value interval + (control_value, MinIsMaxBehavior::PreferOne) + } else { + // Control value is outside source value interval + // TODO-high-discrete Check if the lack of `use_discrete_processing` is a problem here (that + // we use the discrete interval although it's not currently set). It shouldn't + // cause an issue because it only has an effect if source min/max are non-default + // and then this will be normalized to 0.0 or 1.0 anyway in the next step. + // However, we should make this more clear. + // TODO-high-discrete Having all the dead code for the discrete processing logic is not good. + // That code needs to grow with the rest. Idea: Unlock discrete processing at first + // with only a few very simple operators. Hide the rest. + // Unlock more complicated ones later if necessary. + self.settings.out_of_range_behavior.process( + control_value, + interval_match_result, + &self.settings.source_value_interval, + &self.settings.discrete_source_value_interval, + )? + }; + // Control value is within source value interval + // 1. Apply source interval + let source_normalized_control_value = source_bound_value.normalize( + &self.settings.source_value_interval, + &self.settings.discrete_source_value_interval, + min_is_max_behavior, + self.settings.use_discrete_processing, + BASE_EPSILON, + ); + // Memorize as previous value for next control cycle. + let prev_absolute_control_event = self + .state + .previous_source_normalized_control_event + .replace(control_event.with_payload(source_normalized_control_value)); + let res = AbsolutePreProcessingResult { + control_event: control_event.with_payload(source_normalized_control_value), + prev_control_event: prev_absolute_control_event, + }; + Some(res) + } + + /// "Incremental button" mode: Convert absolute button presses to relative increments, + /// taking the velocity into account. + fn control_absolute_incremental_buttons< + 'a, + C: Copy + TransformationInputProvider + Into, + TC, + >( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + ) -> Option> { + // TODO-high-discrete In discrete processing, don't interpret current target value as percentage! + if control_event.payload().is_zero() + || !self + .settings + .source_value_interval + .value_matches_tolerant(control_event.payload(), BASE_EPSILON) + .matches() + { + return None; + } + if self.settings.make_absolute { + // Convert absolute button presses to increments/decrements of pseudo absolute value. + // We could just as well convert to a continuous increment (we have one now). + // However, I think it's not worth to change existing behavior at this point. First, + // nobody expects 100% accuracy incrementing/decrementing something via button-press + // velocity. Second, using "Incremental buttons" with "Make absolute" is probably + // something people rarely use. + let discrete_increment = self.convert_to_discrete_increment(control_event.payload())?; + Some( + self.control_relative_to_absolute( + control_event.with_payload(Increment::Discrete(discrete_increment)), + target, + context, + options, + )? + .map(|v| ControlValue::AbsoluteContinuous(v.to_unit_value())), + ) + } else { + // Initiate relative control via incremental buttons + self.control_absolute_incremental_buttons_normal( + control_event.payload(), + target, + context, + options, + ) + } + } + + fn control_absolute_incremental_buttons_normal<'a, C: Copy + Into, TC>( + &mut self, + control_value: UnitValue, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + ) -> Option> { + if !self.state.unpacked_target_value_set.is_empty() { + let discrete_increment = self.convert_to_discrete_increment(control_value)?; + return self.control_relative_target_value_set( + discrete_increment, + target, + context, + options, + ); + } + use ControlType::*; + let control_type = target.control_type(context.into()); + match control_type { + AbsoluteContinuous + | AbsoluteContinuousRoundable { .. } + // TODO-low I think trigger and switch targets don't make sense at all here because + // instead of +/- n they need just "trigger!" or "on/off!". + | AbsoluteContinuousRetriggerable => { + // Continuous target + // + // Settings: + // - Source value interval (for setting the input interval of relevant source + // values) + // - Minimum target step size (enables accurate minimum increment, atomic) + // - Maximum target step size (enables accurate maximum increment, clamped) + // - Target value interval (absolute, important for rotation only, clamped) + let step_size_value = control_value + .normalize( + &self.settings.source_value_interval, + MinIsMaxBehavior::PreferOne, + BASE_EPSILON + ) + .denormalize(&self.settings.step_size_interval); + let step_size_increment = + step_size_value.to_increment(negative_if(self.settings.reverse))?; + self.hit_target_absolutely_with_unit_increment( + step_size_increment, + // We don't want to adjust to a grid, target min/max are more important + // (see #577). + None, + target.current_value(context.into())?.to_unit_value(), + options, + control_type + ) + } + AbsoluteDiscrete { atomic_step_size, .. } => { + // Discrete target + // + // Settings: + // - Source value interval (for setting the input interval of relevant source + // values) + // - Minimum target step count (enables accurate normal/minimum increment, atomic) + // - Target value interval (absolute, important for rotation only, clamped) + // - Maximum target step count (enables accurate maximum increment, clamped) + let discrete_increment = self.convert_to_discrete_increment(control_value)?; + self.hit_discrete_target_absolutely(Increment::Discrete(discrete_increment), atomic_step_size, options, control_type, || { + target.current_value(context.into()) + }) + } + Relative + // This is cool! With this, we can make controllers without encoders simulate them + // by assigning one - button and one + button to the same virtual multi target. + // Of course, all we can deliver is increments/decrements since virtual targets + // don't provide a current target value. But we also don't need it because all we + // want to do is simulate an encoder. + | VirtualMulti => { + // Target wants increments so we just generate them e.g. depending on how hard the + // button has been pressed + // + // - Source value interval (for setting the input interval of relevant source + // values) + // - Minimum target step count (enables accurate normal/minimum increment, atomic) + // - Maximum target step count (enables accurate maximum increment, mapped) + let discrete_increment = self.convert_to_discrete_increment(control_value)?; + Some(ModeControlResult::hit_target(ControlValue::RelativeDiscrete(discrete_increment))) + } + VirtualButton => { + // This doesn't make sense at all. Buttons just need to be triggered, not fed with + // +/- n. + None + } + } + } + + fn control_absolute_toggle_buttons<'a, C: Copy + Into, TC>( + &mut self, + control_value: AbsoluteValue, + target: &impl Target<'a, Context = TC>, + context: C, + ) -> Option> { + // TODO-high-discrete In discrete processing, don't interpret current target value as + // percentage! + if control_value.is_zero() { + return None; + } + // Nothing we can do if we can't get the current target value. This shouldn't happen + // usually because virtual targets are not supposed to be used with toggle mode. + let current_target_value = target.current_value(context.into())?; + let desired_target_value = if self.settings.target_value_interval.min_is_max(BASE_EPSILON) { + // Special case #452 (target min == target max). + // Make it usable for exclusive toggle buttons. + if current_target_value + .matches_tolerant( + &self.settings.target_value_interval, + &self.settings.discrete_target_value_interval, + false, + BASE_EPSILON, + ) + .matches() + { + UnitValue::MIN + } else { + self.settings.target_value_interval.max_val() + } + } else { + // Normal case (target min != target max) + let center_target_value = self.settings.target_value_interval.center(); + if current_target_value.to_unit_value() > center_target_value { + // Target value is within the second half of the target range (considered as on). + self.settings.target_value_interval.min_val() + } else { + // Target value is within the first half of the target range (considered as off). + self.settings.target_value_interval.max_val() + } + }; + // If the settings make sense for toggling, the desired target value should *always* + // be different than the current value. Therefore no need to check if the target value + // already has that value. + let final_absolute_value = self.get_final_absolute_value( + AbsoluteValue::Continuous(desired_target_value), + target.control_type(context.into()), + ); + Some(ModeControlResult::hit_target(final_absolute_value)) + } + + /// Absolute-to-relative conversion mode. + fn control_absolute_to_relative< + 'a, + C: Copy + TransformationInputProvider + Into, + TC, + >( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + ) -> Option> { + // If discrete processing not explicitly enabled, go continuous right here! + // If not, discrete-relative things like step sizes or speed factors would be taken into + // account, which we don't want for the "Make relative" mode (at least if not explicitly + // enabled in future). It's important that an absolute swipe from 0% to 100% applied + // relative to a target with initial value 0% results in a target value swipe from + // 0% to 100%, no matter step sizes! That's what we expect from this mode. + let control_event = if self.settings.use_discrete_processing { + control_event + } else { + control_event.map_payload(|v| v.to_continuous_value()) + }; + let res = self.pre_process_absolute_value(control_event)?; + // We can't do anything without having a previous value to relate to. + let prev_control_value = res.prev_control_event?; + let increment = match res.control_event.payload() { + AbsoluteValue::Continuous(v) => { + // This is kind of new: Continuous relative increments. + let prev_control_value = prev_control_value.payload().continuous_value()?; + let diff = v.get() - prev_control_value.get(); + let increment = UnitIncrement::try_from(diff).ok()?; + Increment::Continuous(increment) + } + AbsoluteValue::Discrete(f) => { + // This is easy and accurate! Conforms to our normal relative control stuff, + // which has always been discrete in nature. + // Be aware, even if the source emits discrete values, if source min/max is set, + // we won't arrive in this match branch because discrete processing is not unlocked + // yet! + let prev_control_value = prev_control_value.payload().discrete_value()?; + let diff = f.actual() as i32 - prev_control_value.actual() as i32; + let increment = DiscreteIncrement::try_from(diff).ok()?; + Increment::Discrete(increment) + } + }; + if !self.settings.encoder_usage.matches(increment) { + return None; + } + // We ignore steps because the most important thing about this mode is that we can do + // full sweeps, no matter the character of the target and potential discrete steps. + self.control_relative_normal(increment, target, context, options) + } + + /// Relative-to-absolute conversion mode. + /// + /// Takes care of: + /// + /// - Conversion to absolute value + /// - Step size interval + /// - Wrap (rotate) + fn control_relative_to_absolute< + 'a, + C: Copy + TransformationInputProvider + Into, + TC, + >( + &mut self, + control_event: ControlEvent, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + ) -> Option> { + // Convert to absolute value + let mut inc = control_event + .payload() + .to_unit_increment(self.settings.step_size_interval.min_val())?; + inc = inc.clamp_to_interval(&self.settings.step_size_interval)?; + let full_unit_interval = full_unit_interval(); + let abs_input_value = if options.enforce_rotate || self.settings.rotate { + self.state + .current_absolute_value + .add_rotating(inc, &full_unit_interval, BASE_EPSILON) + } else { + self.state + .current_absolute_value + .add_clamping(inc, &full_unit_interval, BASE_EPSILON) + }; + self.state.current_absolute_value = abs_input_value; + // Do the usual absolute processing + let control_result = self.control_absolute_normal( + control_event.with_payload(AbsoluteValue::Continuous(abs_input_value)), + target, + context, + None, + ); + // At this point, we only accept absolute control results and ignore relative ones + // (relative ones wouldn't make sense as the whole point of make-absolute is to + // make something absolute) + control_result.and_then(|r| match r { + ModeControlResult::HitTarget { value } => Some(ModeControlResult::hit_target( + value.to_absolute_value().ok()?, + )), + ModeControlResult::LeaveTargetUntouched(v) => Some( + ModeControlResult::LeaveTargetUntouched(v.to_absolute_value().ok()?), + ), + }) + } + + // Classic relative mode: We are getting encoder increments from the source. + // We don't need source min/max config in this case. At least I can't think of a use case + // where one would like to totally ignore especially slow or especially fast encoder movements, + // I guess that possibility would rather cause irritation. + fn control_relative_normal<'a, C: Copy + Into, TC>( + &mut self, + increment: Increment, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + ) -> Option> { + if !self.state.unpacked_target_value_set.is_empty() { + let prepped_increment = self.prepare_increment(increment)?; + // If the incoming increment is continuous, we ignore the amount and just consider + // the direction. + return self.control_relative_target_value_set( + prepped_increment.to_discrete_increment(), + target, + context, + options, + ); + } + use ControlType::*; + let control_type = target.control_type(context.into()); + match control_type { + AbsoluteContinuous + | AbsoluteContinuousRoundable { .. } + // TODO-low Controlling a switch/trigger target with +/- n doesn't make sense. + | AbsoluteContinuousRetriggerable => { + let current_target_value = match target.current_value(context.into()) { + None => { + // We couldn't obtain the current target value. + if control_type != ControlType::AbsoluteContinuousRetriggerable { + // Target is not trigger-like, there's nothing we can do. + return None; + } + // https://github.com/helgoboss/helgobox/issues/613 + // Target is like a trigger, so we don't actually need to know its + // value to trigger it. We consider a encoder movement as triggering! + // However, we also should support decreasing the encoder sensitivity, so + // we pep up the increment first to see if we need to fire. + self.prepare_increment(increment)?; + return Some(ModeControlResult::HitTarget { + value: ControlValue::AbsoluteContinuous(UnitValue::MAX) + }); + } + Some(t) => t, + }; + // Continuous target + // + // Settings which are always necessary: + // - Minimum target step size (enables accurate minimum increment, atomic) + // - Target value interval (absolute, important for rotation only, clamped) + // + // Settings which are necessary in order to support >1-increments: + // - Maximum target step size (enables accurate maximum increment, clamped) + let potentially_reversed_increment = if self.settings.reverse { + increment.inverse() + } else { + increment + }; + let final_unit_increment = match potentially_reversed_increment { + Increment::Continuous(i) => { + let target_scale_factor = self.settings.target_value_interval.span(); + UnitIncrement::try_from(i.get() * target_scale_factor).ok()? + }, + Increment::Discrete(i) => { + let unit_increment = i + .to_unit_increment(self.settings.step_size_interval.min_val())?; + unit_increment.clamp_to_interval(&self.settings.step_size_interval)? + } + }; + self.hit_target_absolutely_with_unit_increment( + final_unit_increment, + // We don't want to adjust to a grid, target min/max are more important + // (see #577). + None, + current_target_value.to_unit_value(), + options, + control_type + ) + } + AbsoluteDiscrete { atomic_step_size, .. } => { + // Discrete target + // + // Settings which are always necessary: + // - Minimum target step count (enables accurate normal/minimum increment, atomic) + // - Target value interval (absolute, important for rotation only, clamped) + // + // Settings which are necessary in order to support >1-increments: + // - Maximum target step count (enables accurate maximum increment, clamped) + let prepped_increment = self.prepare_increment(increment)?; + self.hit_discrete_target_absolutely(prepped_increment, atomic_step_size, options, control_type, || { + target.current_value(context.into()) + }) + } + Relative | VirtualMulti => { + // Target wants increments so we just forward them after some preprocessing + // + // Settings which are always necessary: + // - Minimum target step count (enables accurate normal/minimum increment, clamped) + // + // Settings which are necessary in order to support >1-increments: + // - Maximum target step count (enables accurate maximum increment, clamped) + let prepped_increment = self.prepare_increment(increment)?; + Some(ModeControlResult::hit_target(ControlValue::from_relative(prepped_increment))) + } + VirtualButton => { + // Controlling a button target with +/- n doesn't make sense. + None + } + } + } + + /// Takes care of: + /// + /// - Target value set + /// - Wrap (rotate) + fn control_relative_target_value_set<'a, C: Copy + Into, TC>( + &mut self, + discrete_increment: DiscreteIncrement, + target: &impl Target<'a, Context = TC>, + context: C, + options: ModeControlOptions, + ) -> Option> { + // Determine next value in target value set + let current = target.current_value(context.into())?.to_unit_value(); + let target_value_set = &self.state.unpacked_target_value_set; + use std::ops::Bound::*; + let mut v = current; + for _ in 0..discrete_increment.get().abs() { + let next_value_in_direction = if discrete_increment.is_positive() { + target_value_set + .range(( + Excluded(UnitValue::new_clamped(v.get() + BASE_EPSILON)), + Unbounded, + )) + .next() + .copied() + } else { + target_value_set + .range(( + Unbounded, + Excluded(UnitValue::new_clamped(v.get() - BASE_EPSILON)), + )) + .last() + .copied() + }; + v = if let Some(v) = next_value_in_direction { + v + } else if options.enforce_rotate || self.settings.rotate { + if discrete_increment.is_positive() { + *target_value_set.iter().next().unwrap() + } else { + *target_value_set.iter().next_back().unwrap() + } + } else { + break; + }; + } + if v == current { + return None; + } + Some(ModeControlResult::hit_target( + ControlValue::AbsoluteContinuous(v), + )) + } + + fn prepare_absolute_value( + &mut self, + source_normalized_control_event: ControlEvent, + control_type: ControlType, + current_target_value: Option, + additional_transformation_input: T::AdditionalInput, + last_non_performance_target_value: Option, + ) -> Option { + let mut v = source_normalized_control_event.payload(); + // 1. Performance control (optional) + let performance_control = if let Some(y_last) = last_non_performance_target_value { + let x = v.to_unit_value().get(); + let y_last = y_last.to_unit_value().get(); + let target_min = self.settings.target_value_interval.min_val().get(); + let target_max = self.settings.target_value_interval.max_val().get(); + let y = if self.settings.reverse { + let span = (y_last - target_min).max(0.0); + y_last - x * span + } else { + let span = (target_max - y_last).max(0.0); + y_last + x * span + }; + v = AbsoluteValue::Continuous(UnitValue::new_clamped(y)); + true + } else { + false + }; + // 2. Apply transformation + if let Some(transformation) = self.settings.control_transformation.as_ref() { + if let Ok(output) = v.transform( + transformation, + current_target_value, + self.settings.use_discrete_processing, + self.calc_rel_time(source_normalized_control_event.timestamp()), + source_normalized_control_event.timestamp().duration(), + additional_transformation_input, + ) { + let output = self.process_control_transformation_output(output)?; + match output.to_absolute_value() { + Ok(abs_v) => v = abs_v, + // Relative values are not further transformed + Err(_) => return Some(output), + } + } + }; + if performance_control { + // Performance control. Just apply rounding. + if self.settings.round_target_value { + v = v.round(control_type); + }; + } else { + // No performance control + // 3. Apply reverse + v = self.apply_reverse(control_type, v); + // 4. Apply target interval and rounding OR target value sequence + v = self.apply_rounded_target_interval_or_target_sequence(control_type, v); + } + // Return + Some(ControlValue::from_absolute(v)) + } + + fn apply_rounded_target_interval_or_target_sequence( + &self, + control_type: ControlType, + mut v: AbsoluteValue, + ) -> AbsoluteValue { + if self.state.unpacked_target_value_sequence.is_empty() { + // We don't have a target value sequence. Apply target interval and rounding. + v = v.denormalize( + &self.settings.target_value_interval, + &self.settings.discrete_target_value_interval, + self.settings.use_discrete_processing, + control_type.discrete_max(), + ); + if self.settings.round_target_value { + v = v.round(control_type); + }; + } else { + // We have a target value sequence. Apply it. + let max_index = self.state.unpacked_target_value_sequence.len() - 1; + let seq_index = (v.to_unit_value().get() * max_index as f64).round() as usize; + let unit_value = self + .state + .unpacked_target_value_sequence + .get(seq_index) + .copied() + .unwrap_or_default(); + v = AbsoluteValue::Continuous(unit_value); + } + v + } + + fn apply_reverse(&self, control_type: ControlType, mut v: AbsoluteValue) -> AbsoluteValue { + if !self.settings.reverse { + return v; + } + // We must normalize the target value value and use it in the inversion operation. + // As an alternative, we could BEFORE doing all that stuff homogenize the source and + // target intervals to have the same (minimum) size? + let normalized_max_discrete_target_value = control_type.discrete_max().map(|m| { + self.settings + .discrete_target_value_interval + .normalize_to_min(m) + }); + // If this is a discrete target (which reports a discrete maximum) and discrete + // processing is disabled, the reverse operation must use a "scaling reverse", not a + // "subtraction reverse". Therefore we must turn a discrete control value into a + // continuous value in this case before applying the reverse operation. + if normalized_max_discrete_target_value.is_some() && !self.settings.use_discrete_processing + { + v = v.to_continuous_value(); + } + v = v.inverse(normalized_max_discrete_target_value); + v + } + + #[allow(clippy::redundant_locals)] + fn hitting_target_considering_max_jump( + &mut self, + prepped_control_value: AbsoluteValue, + current_target_value: Option, + control_type: ControlType, + control_event: ControlEvent, + prev_control_event: Option>, + ) -> Option> { + // If there's no target value available ... just deliver! Virtual targets take this + // shortcut. + let Some(current_target_value) = current_target_value else { + let final_absolute_value = + self.get_final_absolute_value(prepped_control_value, control_type); + return Some(ModeControlResult::hit_target(final_absolute_value)); + }; + // If there are no jump restrictions whatsoever, we can skip the logic below! + if !self.has_jump_restrictions() { + return self.hit_if_changed(prepped_control_value, current_target_value, control_type); + } + // When we are here, we know we have jump restrictions. + let prepped_control_event = control_event.with_payload(prepped_control_value); + let prev_jump_state = match self.state.previous_jump_prevention_state { + None => { + // First control of this target. We don't have a meaningful previous control value + // to relate to. Memorize the current one but don't do anything. + let fresh_state = JumpPreventionState { + prepped_control_event, + takeover_in_sync: false, + last_emitted_target_value: None, + }; + self.state.previous_jump_prevention_state = Some(fresh_state); + return None; + } + Some(s) => s, + }; + // At this point, the previous source-normalized event should also always be available! + let prev_control_event = prev_control_event?; + // In tolerant pickup mode, always accept if the target was last invoked by us + if self.settings.takeover_mode == TakeoverMode::PickupTolerant { + let last_target_value = self + .state + .final_target_value_from_previous_control + .or(prev_jump_state.last_emitted_target_value); + let target_was_last_invoked_by_us = match last_target_value { + None => false, + Some(v) => current_target_value == v, + }; + if target_was_last_invoked_by_us { + let result = + self.hit_if_changed(prepped_control_value, current_target_value, control_type); + self.state.previous_jump_prevention_state = Some(JumpPreventionState::new( + prepped_control_event, + prev_jump_state.takeover_in_sync, + result, + &prev_jump_state, + )); + return result; + } + } + // Check if this invocation still belongs to the same "movement" as the previous invocation. + // We consider it as being the same movement if the time difference is very small. + let elapsed = + prepped_control_event.timestamp() - prev_jump_state.prepped_control_event.timestamp(); + let is_new_move = elapsed > CONTROL_MOVE_TIMEOUT; + // Prepare useful variables + let prev_control_value = prev_control_event.payload().to_unit_value(); + let current_control_value = control_event.payload().to_unit_value(); + let prev_prepped_value = prev_jump_state + .prepped_control_event + .payload() + .to_unit_value(); + let current_prepped_value = prepped_control_value.to_unit_value(); + let current_target_value = current_target_value; + let jump_max = self.settings.jump_interval.max_val(); + let distance_to_target_value = if self.settings.use_discrete_processing { + prepped_control_value.calc_distance_from(current_target_value) + } else { + prepped_control_value.calc_distance_from(current_target_value.to_continuous_value()) + }; + // Check if in sync + let takeover_in_sync = takeover_is_in_sync( + prev_prepped_value, + current_prepped_value, + current_target_value.to_unit_value(), + prev_jump_state.takeover_in_sync, + is_new_move, + jump_max, + ); + if takeover_in_sync { + // No parameter jump to be expected (at least no unwanted one). + // Check if distance too small (only for being backward compatible with old presets). + if distance_to_target_value.is_lower_than( + self.settings.jump_interval.min_val(), + self.settings.discrete_jump_interval.min_val(), + ) { + return None; + } + // Distance is not too small. Hit target! + let result = + self.hit_if_changed(prepped_control_value, current_target_value, control_type); + self.state.previous_jump_prevention_state = Some(JumpPreventionState::new( + prepped_control_event, + takeover_in_sync, + result, + &prev_jump_state, + )); + return result; + } + // Check for controller jumps + let result = match self.settings.takeover_mode { + TakeoverMode::Off => unreachable!(), + TakeoverMode::Pickup | TakeoverMode::PickupTolerant => { + // Scaling not desired. Do nothing. + None + } + TakeoverMode::Parallel => { + // TODO-high-discrete Implement advanced takeover modes for discrete values, too + // We look at source-normalized values, not pepped up values. Because we are + // interested in the relative movement of the fader/knob, not the more + // processed values that eventually will hit the target. + let relative_increment = current_control_value - prev_control_value; + if relative_increment == 0.0 { + None + } else { + let relative_increment = UnitIncrement::new_clamped(relative_increment); + let restrained_increment = + relative_increment.clamp_to_interval(&self.settings.jump_interval)?; + let final_target_value = current_target_value.to_unit_value().add_clamping( + restrained_increment, + &self.settings.target_value_interval, + BASE_EPSILON, + ); + self.hit_if_changed( + AbsoluteValue::Continuous(final_target_value), + current_target_value, + control_type, + ) + } + } + TakeoverMode::LongTimeNoSee => { + // This takeover mode can actually work without a previous value. But let's keep + // things simple. The in-sync detection needs a previous value anyway. + let approach_distance = distance_to_target_value.denormalize( + &self.settings.jump_interval, + &self.settings.discrete_jump_interval, + self.settings.use_discrete_processing, + control_type.discrete_max(), + ); + let approach_increment = + approach_distance.to_unit_value().to_increment(negative_if( + prepped_control_value.to_unit_value() + < current_target_value.to_unit_value(), + ))?; + let final_target_value = current_target_value.to_unit_value().add_clamping( + approach_increment, + &self.settings.target_value_interval, + BASE_EPSILON, + ); + self.hit_if_changed( + AbsoluteValue::Continuous(final_target_value), + current_target_value, + control_type, + ) + } + TakeoverMode::CatchUp => { + let relative_increment = current_control_value - prev_control_value; + if relative_increment == 0.0 { + None + } else { + let goes_up = relative_increment.is_sign_positive(); + // We already normalized the prev/current control values on the source + // interval, so we can use 0.0..=1.0 at this point. + let source_distance_from_bound = if goes_up { + 1.0 - prev_control_value.get() + } else { + prev_control_value.get() + }; + let current_target_value = current_target_value.to_unit_value(); + let target_distance_from_bound = if goes_up { + self.settings.target_value_interval.max_val() - current_target_value + } else { + current_target_value - self.settings.target_value_interval.min_val() + } + .max(0.0); + if source_distance_from_bound == 0.0 || target_distance_from_bound == 0.0 { + None + } else { + // => -55484347409216.99 + let scaled_increment = relative_increment * target_distance_from_bound + / source_distance_from_bound; + let scaled_increment = UnitIncrement::new_clamped(scaled_increment); + let restrained_increment = + scaled_increment.clamp_to_interval(&self.settings.jump_interval)?; + let final_target_value = current_target_value.add_clamping( + restrained_increment, + &self.settings.target_value_interval, + BASE_EPSILON, + ); + self.hit_if_changed( + AbsoluteValue::Continuous(final_target_value), + AbsoluteValue::Continuous(current_target_value), + control_type, + ) + } + } + } + }; + self.state.previous_jump_prevention_state = Some(JumpPreventionState::new( + prepped_control_event, + false, + result, + &prev_jump_state, + )); + result + } + + fn hit_if_changed( + &self, + desired_target_value: AbsoluteValue, + current_target_value: AbsoluteValue, + control_type: ControlType, + ) -> Option> { + if !control_type.is_retriggerable() + && current_target_value.has_same_effect_as(desired_target_value) + { + return Some(ModeControlResult::LeaveTargetUntouched( + desired_target_value, + )); + } + let final_value = self.get_final_absolute_value(desired_target_value, control_type); + Some(ModeControlResult::hit_target(final_value)) + } + + /// Use this only if the given desired target value could be a discrete value. + fn get_final_absolute_value( + &self, + desired_target_value: AbsoluteValue, + control_type: ControlType, + ) -> AbsoluteValue { + if self.settings.use_discrete_processing || control_type.is_virtual() { + desired_target_value + } else { + // If discrete processing is not explicitly enabled, we must NOT send discrete values to + // a real target! This is not just for backward compatibility. It would change how + // discrete targets react in a surprising way (discrete behavior without having discrete + // processing enabled). The reason why we don't "go continuous" right at the start of + // the processing in this case is that we also have mappings with virtual targets. It's + // important that they don't destroy the discreteness of a value, otherwise existing + // controller presets which don't have "discrete processing" enabled would not be + // compatible with main mappings that have discrete targets and want to use discrete + // processing. There would have been other ways to deal with this (e.g. migration) but + // the concept of letting a discrete value survive as long as possible (= not turning + // it into a continuous one and thereby losing information) sounds like a good idea in + // general. + AbsoluteValue::Continuous(desired_target_value.to_unit_value()) + } + } + + /// Takes care of: + /// + /// - Applying increment (the target step size is only looked at for discrete increments) + /// - Wrap (rotate) + fn hit_discrete_target_absolutely( + &mut self, + increment: Increment, + target_step_size: UnitValue, + options: ModeControlOptions, + control_type: ControlType, + current_value: impl Fn() -> Option, + ) -> Option> { + if self.settings.use_discrete_processing { + // Discrete processing for discrete target. Good! + match current_value()? { + AbsoluteValue::Continuous(_) => { + // But target reports continuous value!? Shouldn't happen. Whatever, fall back + // to continuous processing. + self.hit_target_absolutely_with_unit_increment( + increment.to_unit_increment(target_step_size)?, + // In order to not end up on "in-between" values, we should snap the target + // interval into the conceptual grid defined by the target step size. + Some(target_step_size), + current_value()?.to_unit_value(), + options, + control_type, + ) + } + AbsoluteValue::Discrete(f) => { + let discrete_increment = increment.to_discrete_increment(); + self.hit_target_absolutely_with_discrete_increment( + discrete_increment, + f, + options, + control_type, + ) + } + } + } else { + // Continuous processing although target is discrete. Kept for backward compatibility. + self.hit_target_absolutely_with_unit_increment( + increment.to_unit_increment(target_step_size)?, + // In order to not end up on "in-between" values, we should snap the target + // interval into the conceptual grid defined by the target step size. + Some(target_step_size), + current_value()?.to_unit_value(), + options, + control_type, + ) + } + } + + /// Takes care of: + /// - Snap target interval to grid if desired (if `grid_interval_size` is given) + /// - Applying increment + /// - Wrap (rotate) + fn hit_target_absolutely_with_unit_increment( + &mut self, + increment: UnitIncrement, + grid_interval_size: Option, + current_target_value: UnitValue, + options: ModeControlOptions, + control_type: ControlType, + ) -> Option> { + let mut v = current_target_value; + let mut target_value_interval = self.settings.target_value_interval; + // If the target is discrete, it's important to make sure that the target interval + // bounds correspond exactly to discrete target values. So we snap the target interval to + // the target step size first. + if let Some(grid_interval_size) = grid_interval_size { + target_value_interval = Interval::new( + self.settings + .target_value_interval + .min_val() + .snap_to_grid_by_interval_size(grid_interval_size), + self.settings + .target_value_interval + .max_val() + .snap_to_grid_by_interval_size(grid_interval_size), + ); + // The add functions don't add anything if the current target value is not within the target + // interval in the first place. Instead they return one of the interval bounds. One issue + // that might occur is that the current target value only *appears* out-of-range + // because of numerical inaccuracies. That could lead to frustrating "it doesn't move" + // experiences. Therefore we snap the current target value to grid first in that case. + if !v.is_within_interval(&target_value_interval) { + v = v.snap_to_grid_by_interval_size(grid_interval_size) + }; + } + v = if options.enforce_rotate || self.settings.rotate { + v.add_rotating(increment, &target_value_interval, BASE_EPSILON) + } else { + v.add_clamping(increment, &target_value_interval, BASE_EPSILON) + }; + let final_value = self.hit_if_changed( + AbsoluteValue::Continuous(v), + AbsoluteValue::Continuous(current_target_value), + control_type, + )?; + Some(final_value.map(ControlValue::from_absolute)) + } + + fn hit_target_absolutely_with_discrete_increment( + &self, + increment: DiscreteIncrement, + current_target_value: Fraction, + options: ModeControlOptions, + control_type: ControlType, + ) -> Option> { + let mut v = current_target_value; + v = if options.enforce_rotate || self.settings.rotate { + v.add_rotating(increment, &self.settings.discrete_target_value_interval) + } else { + v.add_clamping(increment, &self.settings.discrete_target_value_interval) + }; + if let Some(target_max) = control_type.discrete_max() { + v = v.with_max_clamped(target_max); + } + if v.actual() == current_target_value.actual() { + return Some(ModeControlResult::LeaveTargetUntouched( + ControlValue::AbsoluteDiscrete(v), + )); + } + let final_absolute_value = + self.get_final_absolute_value(AbsoluteValue::Discrete(v), control_type); + Some(ModeControlResult::hit_target(ControlValue::from_absolute( + final_absolute_value, + ))) + } + + /// Takes care of: + /// + /// - Reverse + /// - Speed (only for discrete increments) + fn prepare_increment(&mut self, increment: Increment) -> Option { + match increment { + Increment::Continuous(i) => self + .prepare_continuous_increment(i) + .map(Increment::Continuous), + Increment::Discrete(i) => self.prepare_discrete_increment(i).map(Increment::Discrete), + } + } + + /// Takes care of: + /// + /// - Reverse + fn prepare_continuous_increment(&mut self, increment: UnitIncrement) -> Option { + let result = if self.settings.reverse { + increment.inverse() + } else { + increment + }; + Some(result) + } + + /// Takes care of: + /// + /// - Speed + /// - Reverse + fn prepare_discrete_increment( + &mut self, + original_inc: DiscreteIncrement, + ) -> Option { + let mut inc = original_inc; + // Process speed (step count) + let factor = inc.clamp_to_interval(&self.settings.step_factor_interval); + inc = if factor.is_positive() { + factor + } else { + let nth = factor.get().unsigned_abs(); + let (fire, new_counter_value) = self.its_time_to_fire(nth, inc.signum()); + self.state.increment_counter = new_counter_value; + if !fire { + return None; + } + DiscreteIncrement::new(1) + }; + inc = inc.with_direction(original_inc.signum()); + // Process reverse + if self.settings.reverse { + inc = inc.inverse(); + } + Some(inc) + } + + /// `nth` stands for "fire every nth time". `direction_signum` is either +1 or -1. + fn its_time_to_fire(&self, nth: u32, direction_signum: i32) -> (bool, i32) { + if self.state.increment_counter == 0 { + // Initial fire + return (true, direction_signum); + } + let positive_increment_counter = self.state.increment_counter.unsigned_abs(); + if positive_increment_counter >= nth { + // After having waited for a few increments, fire again. + return (true, direction_signum); + } + (false, self.state.increment_counter + direction_signum) + } + + /// Takes care of: + /// + /// - Source interval normalization + /// - Speed (step count) + /// - Reverse + fn convert_to_discrete_increment( + &mut self, + control_value: UnitValue, + ) -> Option { + let factor = control_value + .normalize( + &self.settings.source_value_interval, + MinIsMaxBehavior::PreferOne, + BASE_EPSILON, + ) + .denormalize_discrete_increment(&self.settings.step_factor_interval); + // This mode supports positive increment only. + let discrete_value = if factor.is_positive() { + factor.to_value() + } else { + let nth = factor.get().unsigned_abs(); + let (fire, new_counter_value) = self.its_time_to_fire(nth, 1); + self.state.increment_counter = new_counter_value; + if !fire { + return None; + } + DiscreteValue::new(1) + }; + discrete_value.to_increment(negative_if(self.settings.reverse)) + } +} + +/// This is largely based on the PR by mdmayfield: https://github.com/helgoboss/helgoboss-learn/pull/2/files +/// He in turn took inspiration from `controllers/softtakeover.cpp` in the Mixxx DJ project. +/// +/// The values passed here are +fn takeover_is_in_sync( + prev_pep_value: UnitValue, + current_pep_value: UnitValue, + current_target_value: UnitValue, + was_in_sync_before: bool, + is_new_move: bool, + jump_max: UnitValue, +) -> bool { + // If jump max is 100%, we are in sync by definition. + if jump_max.is_one() { + return true; + } + // If we were in sync before and we are still in the same move, stay in sync. + if was_in_sync_before && !is_new_move { + return true; + } + // Check whether we crossed the target value or are still on the same side + let current_distance_to_target = current_pep_value - current_target_value; + let prev_distance_to_target = prev_pep_value - current_target_value; + let crossed_target = current_distance_to_target.signum() != prev_distance_to_target.signum(); + // Check if we are still approaching the target or moving away from it + let still_approaching_target = + !crossed_target && (current_distance_to_target.abs() < prev_distance_to_target.abs()); + if still_approaching_target { + // Avoid awkward "backwards" jump when approaching the target. + return false; + } + current_distance_to_target.abs() <= jump_max.get() || (crossed_target && !is_new_move) +} + +/// Time in ms between CC messages to assume they are part of the one motion. +/// +/// In the strict modes, this is always relevant. In the non-strict mode this is only relevant if +/// we detected change of the target by something else (not us). +const CONTROL_MOVE_TIMEOUT: Duration = Duration::from_millis(100); + +struct AbsolutePreProcessingResult { + control_event: ControlEvent, + prev_control_event: Option>, +} + +pub fn default_step_size_interval() -> Interval { + // 0.01 has also been chosen as default maximum step size because most users probably + // want to start easy, that is without using the "press harder = more increments" + // respectively "dial harder = more increments" features. Activating them right from + // the start by choosing a higher step size maximum could lead to surprising results + // such as ugly parameters jumps, especially if the source is not suited for that. + create_unit_value_interval(DEFAULT_STEP_SIZE, DEFAULT_STEP_SIZE) +} + +pub fn default_step_count_interval() -> Interval { + // Same reasoning as with step size interval + create_discrete_increment_interval(1, 1) +} + +/// If something like this is returned from the mode, it already means that the source value +/// was not filtered out (e.g. because of button filter). +#[derive(Copy, Clone, Debug)] +pub enum ModeControlResult { + /// Target should be hit with the given value. + HitTarget { value: T }, + /// Target is reached but already has the given desired value and is not retriggerable. + /// It shouldn't be hit. + LeaveTargetUntouched(T), +} + +impl ModeControlResult { + pub fn hit_target(value: T) -> Self { + Self::HitTarget { value } + } + + pub fn map(self, f: impl FnOnce(T) -> R) -> ModeControlResult { + use ModeControlResult::*; + match self { + HitTarget { value } => HitTarget { value: f(value) }, + LeaveTargetUntouched(v) => LeaveTargetUntouched(f(v)), + } + } + + pub fn value(&self) -> T { + match self { + ModeControlResult::HitTarget { value } => *value, + ModeControlResult::LeaveTargetUntouched(value) => *value, + } + } +} + +impl Display for ModeControlResult { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + ModeControlResult::HitTarget { value } => { + write!(f, "Hit target with value {value}") + } + ModeControlResult::LeaveTargetUntouched(v) => { + write!(f, "Leave target untouched with value {v}") + } + } + } +} + +impl From> for Option { + fn from(res: ModeControlResult) -> Self { + use ModeControlResult::*; + match res { + LeaveTargetUntouched(_) => None, + HitTarget { value, .. } => Some(value), + } + } +} + +fn full_discrete_interval() -> Interval { + Interval::new(0, u32::MAX) +} + +fn textual_feedback_expression_regex() -> &'static regex::Regex { + regex!(r"\{\{ *([A-Za-z0-9._]+) *\}\}") +} + +const DEFAULT_TEXTUAL_FEEDBACK_PROP_KEY: &str = "target.text_value"; + +#[cfg(test)] +mod tests { + use super::*; + + use crate::mode::test_util::{TestFeedbackScript, TestTarget, TestTransformation}; + use crate::{create_unit_value_interval, ControlType, Fraction, NoopTimestamp}; + use approx::*; + + mod absolute_normal { + use super::*; + + mod continuous_processing { + use super::*; + use crate::ControlValueKind; + + #[test] + fn default() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.3779527559055118)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_eq!( + mode.control(abs_con_evt(0.3779527559055118), &target, ()), + None + ); + assert_eq!(mode.control(abs_dis_evt(48, 127), &target, ()), None); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(63, 127), &target, ()).unwrap(), + abs_con_val(0.49606299212598426) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn default_with_virtual_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.3779527559055118)), + control_type: ControlType::VirtualMulti, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 127) + ); + assert_eq!( + mode.control(abs_con_evt(0.3779527559055118), &target, ()), + None + ); + assert_eq!(mode.control(abs_dis_evt(48, 127), &target, ()), None); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(63, 127), &target, ()).unwrap(), + abs_dis_val(63, 127) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_dis_val(127, 127) + ); + } + + #[test] + fn default_target_is_trigger() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuousRetriggerable, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.777), &target, ()).unwrap(), + abs_con_val(0.777) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn relative_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: None, + control_type: ControlType::Relative, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn source_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.2, 0.6), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(13, 127), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(25, 127), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(51, 127), &target, ()).unwrap(), + abs_con_val(0.5039370078740157) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(76, 127), &target, ()).unwrap(), + abs_con_val(0.9960629921259844) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.8), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(80, 127), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn source_interval_out_of_range_ignore() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.2, 0.6), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert!(mode.control(abs_con_evt(0.8), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn source_interval_out_of_range_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.2, 0.6), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.8), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn source_interval_out_of_range_ignore_source_one_value() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.5, 0.5), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.4), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert!(mode.control(abs_con_evt(0.6), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn source_interval_out_of_range_min_source_one_value() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.5, 0.5), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn source_interval_out_of_range_min_max_source_one_value() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.5, 0.5), + out_of_range_behavior: OutOfRangeBehavior::MinOrMax, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn target_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.6), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.28) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.25), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.75), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.6) + ); + } + + #[test] + fn target_interval_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.6, 1.0), + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.25), &target, ()).unwrap(), + abs_con_val(0.9) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.75), &target, ()).unwrap(), + abs_con_val(0.7) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.6) + ); + } + + #[test] + fn source_and_target_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.2, 0.6), + target_value_interval: create_unit_value_interval(0.2, 0.6), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(0.6) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.8), &target, ()).unwrap(), + abs_con_val(0.6) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.6) + ); + } + + #[test] + fn source_and_target_interval_shifted() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.2, 0.6), + target_value_interval: create_unit_value_interval(0.4, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.6) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.8), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.8) + ); + } + + #[test] + fn reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn reverse_discrete_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(55, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Check that we use a "scaling reverse" instead of a "subtracting" reverse even + // though control value and target is discrete. Discrete processing is disabled! + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 10), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(5, 10), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(10, 10), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn discrete_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(4, 5)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.2), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_eq!(mode.control(abs_con_evt(0.8), &target, ()), None); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.19), &target, ()).unwrap(), + abs_con_val(0.19) + ); + + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.49), &target, ()).unwrap(), + abs_con_val(0.49) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn round() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + round_target_value: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(4, 5)), + control_type: ControlType::AbsoluteContinuousRoundable { + rounding_step_size: UnitValue::new(0.2), + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.11), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.19), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.35), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.49), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn jump_interval_max_pickup() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + takeover_mode: TakeoverMode::Pickup, + jump_interval: create_unit_value_interval(0.0, 0.2), + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.5)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + // No backwards jumps + test(0.0, None); + test(0.1, None); + test(0.3, None); + test(0.4, None); + // Pick up + test(0.6, Some(0.6)); + test(0.7, Some(0.7)); + test(0.8, Some(0.8)); + test(0.9, Some(0.9)); + test(1.0, Some(1.0)); + } + + #[test] + fn jump_interval_max_pickup_with_target_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + takeover_mode: TakeoverMode::Pickup, + jump_interval: create_unit_value_interval(0.0, 0.1), + target_value_interval: create_unit_value_interval(0.0, 0.5), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.1)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + abs_con_test(&mut mode, &target, i, o); + }; + test(0.0, None); + test(0.0, Some(0.00)); + test(0.1, Some(0.05)); + test(0.2, None); + test(0.3, Some(0.15)); + test(0.4, Some(0.20)); + test(0.5, Some(0.25)); + test(0.7, Some(0.35)); + test(1.0, Some(0.5)); + } + + #[test] + fn jump_interval_max_pickup_with_target_interval_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + takeover_mode: TakeoverMode::Pickup, + jump_interval: create_unit_value_interval(0.0, 0.1), + target_value_interval: create_unit_value_interval(0.0, 0.5), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + abs_con_test(&mut mode, &target, i, o); + }; + // Pickup mode is strict. If we never reach the actual value, we can't pick it up. + test(0.0, None); + test(0.1, None); + test(0.2, None); + test(0.3, None); + test(0.4, None); + test(0.5, None); + test(0.7, None); + test(1.0, None); + } + + #[test] + fn jump_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + takeover_mode: TakeoverMode::Pickup, + jump_interval: create_unit_value_interval(0.1, 1.0), + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.5)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + test(0.45, None); + test(0.45, None); + test(0.5, None); + test(0.7, Some(0.7)); + test(0.75, None); + test(0.85, Some(0.85)); + } + + #[test] + fn jump_interval_max_long_time_no_see() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + jump_interval: create_unit_value_interval(0.0, 0.2), + takeover_mode: TakeoverMode::LongTimeNoSee, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.5)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |input: f64, output: Option| { + if let Some(o) = output { + assert_abs_diff_eq!( + mode.control(abs_con_evt(input), &target, ()).unwrap(), + abs_con_val(o) + ); + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + target.current_value = Some(con_val(o)); + } else { + assert_eq!(mode.control(abs_con_evt(input), &target, ()), None); + } + }; + test(0.0, None); + test(0.0, Some(0.4)); + test(0.1, Some(0.34)); + test(0.4, Some(0.4)); + test(0.6, Some(0.6)); + test(0.7, Some(0.7)); + test(0.8, Some(0.8)); + test(1.0, Some(1.0)); + } + + #[test] + fn jump_interval_max_long_time_no_see_with_target_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + jump_interval: create_unit_value_interval(0.0, 0.1), + target_value_interval: create_unit_value_interval(0.0, 0.5), + takeover_mode: TakeoverMode::LongTimeNoSee, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.1)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |input: f64, output: Option| { + if let Some(o) = output { + assert_abs_diff_eq!( + mode.control(abs_con_evt(input), &target, ()).unwrap(), + abs_con_val(o) + ); + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + target.current_value = Some(con_val(o)); + } else { + assert_eq!(mode.control(abs_con_evt(input), &target, ()), None); + } + }; + test(0.0, None); + test(0.0, Some(0.00)); + test(0.1, Some(0.05)); + test(0.2, Some(0.1)); + test(0.3, Some(0.15)); + test(0.4, Some(0.20)); + test(0.5, Some(0.25)); + test(0.6, Some(0.3)); + test(0.7, Some(0.35)); + test(0.8, Some(0.4)); + test(0.9, Some(0.45)); + test(1.0, Some(0.5)); + } + + #[test] + fn jump_interval_max_long_time_no_see_with_target_interval_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + jump_interval: create_unit_value_interval(0.0, 0.1), + target_value_interval: create_unit_value_interval(0.0, 0.5), + takeover_mode: TakeoverMode::LongTimeNoSee, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |input: f64, output: Option| { + if let Some(o) = output { + assert_abs_diff_eq!( + mode.control(abs_con_evt(input), &target, ()).unwrap(), + abs_con_val(o) + ); + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + target.current_value = Some(con_val(o)); + } else { + assert_eq!(mode.control(abs_con_evt(input), &target, ()), None); + } + }; + // That's a jump, but one that we allow because target value being out of range + // is an exceptional situation. + test(0.0, None); + test(0.0, Some(0.5)); + test(0.1, Some(0.455)); + test(0.2, Some(0.41950000000000004)); + test(0.3, Some(0.39255)); + test(0.4, Some(0.373295)); + test(0.5, Some(0.3609655)); + test(0.6, Some(0.35486895)); + test(0.7, Some(0.35438205500000003)); + test(0.8, Some(0.4)); + test(0.9, Some(0.45)); + test(1.0, Some(0.5)); + } + + #[test] + fn jump_interval_max_parallel() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + jump_interval: create_unit_value_interval(0.0, 0.1), + takeover_mode: TakeoverMode::Parallel, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.1)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + // First one indeterminate + test(0.6, None); + // Raising in parallel + test(0.7, Some(0.2)); + test(0.8, Some(0.3)); + test(0.85, Some(0.35)); + test(0.9, Some(0.4)); + test(1.0, Some(0.5)); + // Falling in parallel + test(0.9, Some(0.4)); + test(0.8, Some(0.3)); + test(0.75, Some(0.25)); + test(0.7, Some(0.2)); + test(0.6, Some(0.1)); + test(0.5, Some(0.0)); + // Saturating + test(0.4, None); + test(0.3, None); + test(0.4, Some(0.1)); + // Raising in parallel without exceeding max jump + test(0.6, Some(0.2)); + test(0.7, Some(0.3)); + test(1.0, Some(0.4)); + // Falling in parallel without exceeding max jump + test(0.6, Some(0.3)); + } + + #[test] + fn jump_interval_max_parallel_with_target_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + jump_interval: create_unit_value_interval(0.0, 0.1), + target_value_interval: create_unit_value_interval(0.0, 0.5), + takeover_mode: TakeoverMode::Parallel, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + // First one indeterminate + test(0.6, None); + // Raising in parallel + test(0.7, Some(0.1)); + test(0.8, Some(0.2)); + test(0.85, Some(0.25)); + test(0.9, Some(0.30)); + test(1.0, Some(0.4)); + // Falling in parallel + test(0.9, Some(0.3)); + test(0.3, Some(0.15)); + test(0.2, Some(0.1)); + test(0.1, Some(0.05)); + test(0.0, Some(0.0)); + // Saturating + test(0.0, None); + } + + #[test] + fn jump_interval_max_catch_up() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + jump_interval: create_unit_value_interval(0.0, 0.1), + takeover_mode: TakeoverMode::CatchUp, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.1)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |input: f64, output: Option| { + if let Some(o) = output { + assert_abs_diff_eq!( + mode.control(abs_con_evt(input), &target, ()).unwrap(), + abs_con_val(o) + ); + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + target.current_value = Some(con_val(o)); + } else { + assert_eq!(mode.control(abs_con_evt(input), &target, ()), None); + } + }; + // First one indeterminate + test(0.6, None); + // Raising as fast as possible (= catching up) without exceeding max jump + test(0.7, Some(0.2)); + test(0.8, Some(0.3)); + test(0.85, Some(0.4)); + test(0.9, Some(0.5)); + test(1.0, Some(0.6)); + // Falling slower than usually (= seeking convergence) + test(0.9, Some(0.54)); + test(0.8, Some(0.48)); + test(0.75, Some(0.45)); + test(0.7, Some(0.42)); + test(0.6, Some(0.36)); + test(0.5, Some(0.30)); + // No explanation from here. Just for regression detection. + test(0.0, Some(0.0)); + test(0.0, None); + test(0.1, Some(0.1)); + test(0.3, Some(0.3)); + test(0.5, Some(0.5)); + test(0.3, Some(0.3)); + } + + #[test] + fn jump_interval_max_catch_up_corner_case() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.47, 0.53), + jump_interval: create_unit_value_interval(0.0, 0.02), + target_value_interval: create_unit_value_interval(0.0, 0.716), + takeover_mode: TakeoverMode::CatchUp, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6897)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |input: f64| { + mode.control(abs_con_evt(input), &target, ()); + }; + // In older versions this panicked because of invalid unit values/increments + test(0.0); + test(0.1); + test(0.2); + test(0.3); + test(0.4); + test(0.5); + test(0.6); + test(0.7); + test(0.8); + test(0.9); + test(1.0); + test(0.9); + test(0.8); + test(0.7); + test(0.6); + test(0.5); + test(0.4); + test(0.3); + test(0.2); + test(0.1); + test(0.0); + } + + #[test] + fn jump_interval_max_catch_up_with_target_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + jump_interval: create_unit_value_interval(0.0, 0.1), + target_value_interval: create_unit_value_interval(0.5, 1.0), + takeover_mode: TakeoverMode::CatchUp, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.1)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |input: f64, output: Option| { + if let Some(o) = output { + assert_abs_diff_eq!( + mode.control(abs_con_evt(input), &target, ()).unwrap(), + abs_con_val(o) + ); + // In order to intuitively test this takeover mode, we need to also adjust + // the current target value after each assertion. + target.current_value = Some(con_val(o)); + } else { + assert_eq!(mode.control(abs_con_evt(input), &target, ()), None); + } + }; + // First one indeterminate + test(0.6, None); + // Raising as fast as possible (= catching up) without exceeding max jump + test(0.7, Some(0.5)); + test(0.8, Some(0.6)); + test(0.85, Some(0.7)); + test(0.9, Some(0.8)); + test(1.0, Some(0.9)); + // No explanation from here. Just for detecting regressions. + test(0.9, Some(0.86)); + test(0.5, Some(0.75)); + test(0.4, Some(0.7)); + } + + #[test] + fn transformation_ok() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + control_transformation: Some(TestTransformation::new( + ControlValueKind::AbsoluteContinuous, + |input| Ok(1.0 - input), + )), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn transformation_err() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + control_transformation: Some(TestTransformation::new( + ControlValueKind::AbsoluteContinuous, + |_| Err("oh no!"), + )), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + // TODO-medium-discrete Add tests for discrete processing + #[test] + fn target_value_sequence_continuous_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_sequence: "0.2, 0.4, 0.4, 0.5, 0.0, 0.9".parse().unwrap(), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteContinuous, + }; + mode.update_from_target(&target, (), false); + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 20), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.09), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(1, 20), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(3, 20), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(4, 20), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(8, 20), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(12, 20), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.65), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(13, 20), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.8), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(16, 20), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.9) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(20, 20), &target, ()).unwrap(), + abs_con_val(0.9) + ); + } + + /// Discrete steps dictated by target itself should be ignored when a sequence is given. + #[test] + fn target_value_sequence_discrete_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_sequence: "0.2, 0.4, 0.4, 0.5, 0.0, 0.9".parse().unwrap(), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.5), + is_retriggerable: false, + }, + }; + mode.update_from_target(&target, (), false); + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 20), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.09), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(1, 20), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(3, 20), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.2), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(4, 20), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.4), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(8, 20), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.6), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(12, 20), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.65), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(13, 20), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.8), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(16, 20), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.9) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(20, 20), &target, ()).unwrap(), + abs_con_val(0.9) + ); + } + + #[test] + fn target_value_sequence_continuous_target_range() { + // Given + let target_value_sequence: ValueSequence = + "0.25 - 0.50 (0.01), 0.75, 0.50, 0.10".parse().unwrap(); + let count = target_value_sequence.unpack(UnitValue::new(0.01)).len(); + let max = count - 1; + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_sequence, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteContinuous, + }; + mode.update_from_target(&target, (), false); + // When + // Then + assert_eq!(29, count); + assert_eq!(28, max); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.0), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(25.0 / max as f64), &target, ()) + .unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.10) + ); + } + + #[test] + fn feedback() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(dis_val(0, 127)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!( + mode.feedback(dis_val(60, 127)).unwrap(), + con_val(0.47244094488188976) + ); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(dis_val(127, 127)).unwrap(), con_val(1.0)); + } + + #[test] + fn feedback_with_virtual_source() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let options = ModeFeedbackOptions { + source_is_virtual: true, + max_discrete_source_value: None, + }; + // When + // Then + assert_abs_diff_eq!( + mode.feedback_with_options(con_val(0.0), options).unwrap(), + con_val(0.0) + ); + assert_abs_diff_eq!( + mode.feedback_with_options(dis_val(0, 127), options) + .unwrap(), + dis_val(0, 127) + ); + assert_abs_diff_eq!( + mode.feedback_with_options(con_val(0.5), options).unwrap(), + con_val(0.5) + ); + assert_abs_diff_eq!( + mode.feedback_with_options(dis_val(60, 127), options) + .unwrap(), + dis_val(60, 127) + ); + assert_abs_diff_eq!( + mode.feedback_with_options(con_val(1.0), options).unwrap(), + con_val(1.0) + ); + assert_abs_diff_eq!( + mode.feedback_with_options(dis_val(127, 127), options) + .unwrap(), + dis_val(127, 127) + ); + } + + #[test] + fn feedback_reverse() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + reverse: true, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.0)); + } + + #[test] + fn feedback_target_interval() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 1.0), + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.2)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.4)).unwrap(), con_val(0.25)); + assert_abs_diff_eq!(mode.feedback(con_val(0.6)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(0.8)).unwrap(), con_val(0.75)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(1.0)); + } + + #[test] + fn feedback_target_interval_reverse() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 1.0), + reverse: true, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.2)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.4)).unwrap(), con_val(0.75)); + assert_abs_diff_eq!(mode.feedback(con_val(0.6)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(0.8)).unwrap(), con_val(0.25)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.0)); + } + + #[test] + fn feedback_source_and_target_interval() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + source_value_interval: create_unit_value_interval(0.2, 0.8), + target_value_interval: create_unit_value_interval(0.4, 1.0), + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.2)); + assert_abs_diff_eq!(mode.feedback(con_val(0.4)).unwrap(), con_val(0.2)); + assert_abs_diff_eq!(mode.feedback(con_val(0.7)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.8)); + } + + #[test] + fn feedback_out_of_range_ignore() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + // When + // Then + assert!(mode.feedback(con_val(0.0)).is_none()); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert!(mode.feedback(con_val(1.0)).is_none()); + } + + #[test] + fn feedback_out_of_range_min() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.1)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(0.9)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.0)); + } + + #[test] + fn feedback_out_of_range_min_max_okay() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.02, 0.02), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.01)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.02)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.03)).unwrap(), con_val(0.0)); + } + + #[test] + fn feedback_out_of_range_min_max_issue_263() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.03, 0.03), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.01)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.03)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.04)).unwrap(), con_val(0.0)); + } + + #[test] + fn feedback_out_of_range_min_max_issue_263_more() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.03, 0.03), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.01)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!( + mode.feedback(con_val(0.029999999329447746)).unwrap(), + con_val(1.0) + ); + assert_abs_diff_eq!(mode.feedback(con_val(0.0300000001)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.04)).unwrap(), con_val(0.0)); + } + + #[test] + fn feedback_out_of_range_min_target_one_value() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.5, 0.5), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.1)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.9)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.0)); + } + + #[test] + fn feedback_out_of_range_min_max_target_one_value() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.5, 0.5), + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.1)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.9)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(1.0)); + } + + #[test] + fn feedback_out_of_range_ignore_target_one_value() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.5, 0.5), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + // When + // Then + assert!(mode.feedback(con_val(0.0)).is_none()); + assert!(mode.feedback(con_val(0.1)).is_none()); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(1.0)); + assert!(mode.feedback(con_val(0.9)).is_none()); + assert!(mode.feedback(con_val(1.0)).is_none()); + } + + #[test] + fn feedback_transformation() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + feedback_transformation: Some(TestTransformation::new( + ControlValueKind::AbsoluteContinuous, + |input| Ok(1.0 - input), + )), + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.0)); + } + } + + mod discrete_processing { + use super::*; + use crate::ControlValueKind; + + #[test] + fn case_1_no_interval_restriction() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(48, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!(mode.control(abs_dis_evt(48, 100), &target, ()), None); + assert_eq!( + mode.feedback_with_options(dis_val(48, 200), FB_OPTS), + Some(dis_val(48, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + } + + #[test] + fn case_2_target_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(50, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!(mode.control(abs_dis_evt(48, 100), &target, ()), None); + assert_eq!( + mode.feedback_with_options(dis_val(98, 200), FB_OPTS), + Some(dis_val(48, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(150, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + } + + #[test] + fn case_3_source_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(50, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(48, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!(mode.control(abs_dis_evt(98, 100), &target, ()), None); + assert_eq!( + mode.feedback_with_options(dis_val(48, 200), FB_OPTS), + Some(dis_val(98, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + } + + #[test] + fn case_4_source_and_target_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(50, u32::MAX), + discrete_target_value_interval: Interval::new(50, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!(mode.control(abs_dis_evt(98, 100), &target, ()), None); + assert_eq!( + mode.feedback_with_options(dis_val(98, 200), FB_OPTS), + Some(dis_val(98, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + } + + #[test] + fn case_5_source_and_target_interval_min_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(0, 50), + discrete_target_value_interval: Interval::new(50, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!(mode.control(abs_dis_evt(48, 100), &target, ()), None); + assert_eq!( + mode.feedback_with_options(dis_val(98, 200), FB_OPTS), + Some(dis_val(48, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!(mode.control(abs_dis_evt(48, 100), &target, ()), None); + assert_eq!( + mode.feedback_with_options(dis_val(98, 200), FB_OPTS), + Some(dis_val(48, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + } + + #[test] + fn case_6_source_and_target_interval_disjoint() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(50, u32::MAX), + discrete_target_value_interval: Interval::new(200, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(248, 350)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 350.0), + is_retriggerable: false, + }, + }; + let fb_opts = ModeFeedbackOptions { + source_is_virtual: false, + // Count: 151 + max_discrete_source_value: Some(150), + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(50, 150), &target, ()).unwrap(), + abs_dis_val(200, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 350), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!(mode.control(abs_dis_evt(98, 150), &target, ()), None); + assert_eq!( + mode.feedback_with_options(dis_val(248, 350), fb_opts), + Some(dis_val(98, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 150), &target, ()).unwrap(), + abs_dis_val(250, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(250, 350), fb_opts), + Some(dis_val(100, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(150, 150), &target, ()).unwrap(), + abs_dis_val(300, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(300, 350), fb_opts), + Some(dis_val(150, 150)) + ); + // Out of range + assert_eq!( + mode.control(abs_dis_evt(0, 150), &target, ()).unwrap(), + abs_dis_val(200, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 350), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 350), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 350), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 350), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(350, 350), fb_opts), + Some(dis_val(150, 150)) + ); + } + + #[test] + fn case_7_interval_max_greater_than_target_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(50, 200), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 150)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 150.0), + is_retriggerable: false, + }, + }; + let fb_opts = ModeFeedbackOptions { + source_is_virtual: false, + // Count: 151 + max_discrete_source_value: Some(150), + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 150), &target, ()).unwrap(), + abs_dis_val(50, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 150), fb_opts), + Some(dis_val(0, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 150), &target, ()).unwrap(), + abs_dis_val(100, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 150), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 150), &target, ()).unwrap(), + abs_dis_val(150, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 150), fb_opts), + Some(dis_val(100, 150)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(0, 150), fb_opts), + Some(dis_val(0, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(150, 150), &target, ()).unwrap(), + abs_dis_val(150, 150) + ); + } + + #[test] + fn case_8_target_subset_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(50, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 150)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 150.0), + is_retriggerable: false, + }, + }; + let fb_opts = ModeFeedbackOptions { + source_is_virtual: false, + // Count: 151 + max_discrete_source_value: Some(150), + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 150), &target, ()).unwrap(), + abs_dis_val(50, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 150), fb_opts), + Some(dis_val(0, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 150), &target, ()).unwrap(), + abs_dis_val(100, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 150), fb_opts), + Some(dis_val(50, 150)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(0, 150), fb_opts), + Some(dis_val(0, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 150), &target, ()).unwrap(), + abs_dis_val(100, 150) + ); + assert_eq!( + mode.control(abs_dis_evt(150, 150), &target, ()).unwrap(), + abs_dis_val(100, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 150), fb_opts), + Some(dis_val(50, 150)) + ); + } + + #[test] + fn case_9_no_interval_restriction_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(48, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + } + + #[test] + fn case_10_target_interval_min_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + discrete_target_value_interval: Interval::new(50, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(150, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(0, 100)) + ); + } + + #[test] + fn case_11_source_interval_min_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + discrete_source_value_interval: Interval::new(50, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(48, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + } + + #[test] + fn case_12_source_and_target_interval_min_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + discrete_source_value_interval: Interval::new(50, u32::MAX), + discrete_target_value_interval: Interval::new(50, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(50, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(50, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + // Out of range + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS), + Some(dis_val(100, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 200), FB_OPTS), + Some(dis_val(50, 100)) + ); + } + + #[test] + fn case_13_source_and_target_interval_disjoint_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + discrete_source_value_interval: Interval::new(50, u32::MAX), + discrete_target_value_interval: Interval::new(200, u32::MAX), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(248, 350)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 350.0), + is_retriggerable: false, + }, + }; + let fb_opts = ModeFeedbackOptions { + source_is_virtual: false, + // Count: 151 + max_discrete_source_value: Some(150), + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(50, 150), &target, ()).unwrap(), + abs_dis_val(300, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(300, 350), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 150), &target, ()).unwrap(), + abs_dis_val(250, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(250, 350), fb_opts), + Some(dis_val(100, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(150, 150), &target, ()).unwrap(), + abs_dis_val(200, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(200, 350), fb_opts), + Some(dis_val(150, 150)) + ); + // Out of range + assert_eq!( + mode.control(abs_dis_evt(0, 150), &target, ()).unwrap(), + abs_dis_val(300, 350) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 350), fb_opts), + Some(dis_val(150, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 350), fb_opts), + Some(dis_val(150, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 350), fb_opts), + Some(dis_val(150, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 350), fb_opts), + Some(dis_val(150, 150)) + ); + assert_eq!( + mode.feedback_with_options(dis_val(350, 350), fb_opts), + Some(dis_val(50, 150)) + ); + } + + #[test] + fn case_14_interval_max_greater_than_target_max_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + discrete_target_value_interval: Interval::new(50, 200), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 150)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 150.0), + is_retriggerable: false, + }, + }; + let fb_opts = ModeFeedbackOptions { + source_is_virtual: false, + // Count: 151 + max_discrete_source_value: Some(150), + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 150), &target, ()).unwrap(), + abs_dis_val(150, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 150), fb_opts), + Some(dis_val(0, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 150), &target, ()).unwrap(), + abs_dis_val(100, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 150), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 150), &target, ()).unwrap(), + abs_dis_val(50, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 150), fb_opts), + Some(dis_val(100, 150)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(0, 150), fb_opts), + Some(dis_val(100, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(150, 150), &target, ()).unwrap(), + abs_dis_val(50, 150) + ); + } + + #[test] + fn case_15_target_subset_interval_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + discrete_target_value_interval: Interval::new(50, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(98, 150)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 150.0), + is_retriggerable: false, + }, + }; + let fb_opts = ModeFeedbackOptions { + source_is_virtual: false, + // Count: 151 + max_discrete_source_value: Some(150), + }; + // When + // Then + // Within range + assert_eq!( + mode.control(abs_dis_evt(0, 150), &target, ()).unwrap(), + abs_dis_val(100, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 150), fb_opts), + Some(dis_val(0, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(50, 150), &target, ()).unwrap(), + abs_dis_val(50, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 150), fb_opts), + Some(dis_val(50, 150)) + ); + // Out of range + assert_eq!( + mode.feedback_with_options(dis_val(0, 150), fb_opts), + Some(dis_val(50, 150)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 150), &target, ()).unwrap(), + abs_dis_val(50, 150) + ); + assert_eq!( + mode.control(abs_dis_evt(150, 150), &target, ()).unwrap(), + abs_dis_val(50, 150) + ); + assert_eq!( + mode.feedback_with_options(dis_val(150, 150), fb_opts), + Some(dis_val(0, 150)) + ); + } + + #[test] + fn default_with_virtual_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + ..Default::default() + }); + let target = TestTarget { + current_value: None, + control_type: ControlType::VirtualMulti, + }; + // When + // Then + assert_eq!( + mode.control(abs_dis_evt(0, 100), &target, ()).unwrap(), + abs_dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS), + Some(dis_val(0, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(63, 100), &target, ()).unwrap(), + abs_dis_val(63, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(63, 100), FB_OPTS), + Some(dis_val(63, 100)) + ); + assert_eq!( + mode.control(abs_dis_evt(100, 100), &target, ()).unwrap(), + abs_dis_val(100, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 100), FB_OPTS), + Some(dis_val(100, 100)) + ); + } + + #[test] + fn relative_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + ..Default::default() + }); + let target = TestTarget { + current_value: None, + control_type: ControlType::Relative, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 127) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(63, 127), &target, ()).unwrap(), + abs_dis_val(63, 127) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_dis_val(127, 127) + ); + } + + #[test] + fn source_interval_out_of_range_ignore() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(20, 60), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(48, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_dis_evt(0, 127), &target, ()).is_none()); + assert!(mode.control(abs_dis_evt(19, 127), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(20, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(40, 127), &target, ()).unwrap(), + abs_dis_val(20, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(60, 127), &target, ()).unwrap(), + abs_dis_val(40, 200) + ); + assert!(mode.control(abs_dis_evt(70, 127), &target, ()).is_none()); + assert!(mode.control(abs_dis_evt(100, 127), &target, ()).is_none()); + } + + #[test] + fn source_interval_out_of_range_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(20, 60), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(1, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(20, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(40, 127), &target, ()).unwrap(), + abs_dis_val(20, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(60, 127), &target, ()).unwrap(), + abs_dis_val(40, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(90, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + } + + #[test] + fn source_interval_out_of_range_ignore_source_one_value() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(60, 60), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_dis_evt(0, 127), &target, ()).is_none()); + assert!(mode.control(abs_dis_evt(59, 127), &target, ()).is_none()); + // TODO-high-discrete Not sure if this abs_dis(1, 1) is serving the actual use case + // here and in following tests... + assert_eq!( + mode.control(abs_dis_evt(60, 127), &target, ()).unwrap(), + abs_dis_val(1, 200) + ); + assert!(mode.control(abs_dis_evt(61, 127), &target, ()).is_none()); + assert!(mode.control(abs_dis_evt(127, 127), &target, ()).is_none()); + } + + #[test] + fn source_interval_out_of_range_min_source_one_value() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(60, 60), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(59, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(60, 127), &target, ()).unwrap(), + abs_dis_val(1, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(61, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(61, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + } + + #[test] + fn source_interval_out_of_range_min_max_source_one_value() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(60, 60), + out_of_range_behavior: OutOfRangeBehavior::MinOrMax, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(59, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(60, 127), &target, ()).unwrap(), + abs_dis_val(1, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(61, 127), &target, ()).unwrap(), + abs_dis_val(1, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_dis_val(1, 200) + ); + } + + #[test] + fn target_interval_reverse() { + // TODO-high-discrete Add other reverse tests with source and target interval and + // also intervals with max values! First for continuous! + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(70, 100), + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(1, 127), &target, ()).unwrap(), + abs_dis_val(99, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(15, 127), &target, ()).unwrap(), + abs_dis_val(85, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(30, 100), &target, ()).unwrap(), + abs_dis_val(70, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(40, 100), &target, ()).unwrap(), + abs_dis_val(70, 200) + ); + } + + #[test] + fn reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(127, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(63, 127), &target, ()).unwrap(), + abs_dis_val(127 - 63, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + } + + #[test] + fn round() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + round_target_value: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(2, 5)), + control_type: ControlType::AbsoluteContinuousRoundable { + rounding_step_size: UnitValue::new(0.2), + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 5) + ); + assert_eq!(mode.control(abs_dis_evt(2, 127), &target, ()), None); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(3, 127), &target, ()).unwrap(), + abs_dis_val(3, 5) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_dis_val(5, 5) + ); + } + // + // #[test] + // fn jump_interval() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // use_discrete_processing: true, + // discrete_jump_interval: Interval::new(0, 2), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(60, 200)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 200.0), + // is_retriggerable: false, + // }, + // }; + // // When + // // Then + // assert_eq!(mode.control(abs_dis_evt(0, 127), &target, ()), None); + // assert_eq!(mode.control(abs_dis_evt(57, 127), &target, ()), None); + // assert_abs_diff_eq!( + // mode.control(abs_dis_evt(58, 127), &target, ()).unwrap(), + // abs_dis_val(58, 200) + // ); + // assert_eq!(mode.control(abs_dis_evt(60, 127), &target, ()), None); + // assert_abs_diff_eq!( + // mode.control(abs_dis_evt(61, 127), &target, ()).unwrap(), + // abs_dis_val(61, 200) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_dis_evt(62, 127), &target, ()).unwrap(), + // abs_dis_val(62, 200) + // ); + // assert!(mode.control(abs_dis_evt(63, 200), &target, ()).is_none()); + // assert!(mode.control(abs_dis_evt(127, 200), &target, ()).is_none()); + // } + // + // #[test] + // fn jump_interval_min() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // use_discrete_processing: true, + // discrete_jump_interval: Interval::new(10, 100), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(60, 200)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 200.0), + // is_retriggerable: false, + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!( + // mode.control(abs_dis_evt(1, 127), &target, ()).unwrap(), + // abs_dis_val(1, 200) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_dis_evt(50, 127), &target, ()).unwrap(), + // abs_dis_val(50, 200) + // ); + // assert!(mode.control(abs_dis_evt(55, 127), &target, ()).is_none()); + // assert!(mode.control(abs_dis_evt(65, 127), &target, ()).is_none()); + // assert!(mode.control(abs_dis_evt(69, 127), &target, ()).is_none()); + // assert_abs_diff_eq!( + // mode.control(abs_dis_evt(70, 127), &target, ()).unwrap(), + // abs_dis_val(70, 200) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + // abs_dis_val(127, 200) + // ); + // } + + // #[test] + // fn jump_interval_approach() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // jump_interval: create_unit_value_interval(0.0, 0.2), + // takeover_mode: TakeoverMode::LongTimeNoSee, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(continuous_value(0.5)), + // control_type: ControlType::AbsoluteContinuous, + // }; + // // When + // // Then + // assert_abs_diff_eq!( + // mode.control(abs_con(0.0), &target, ()).unwrap(), + // abs_con(0.4) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_con(0.1), &target, ()).unwrap(), + // abs_con(0.42) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_con(0.4), &target, ()).unwrap(), + // abs_con(0.4) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_con(0.6), &target, ()).unwrap(), + // abs_con(0.6) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_con(0.7), &target, ()).unwrap(), + // abs_con(0.7) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_con(0.8), &target, ()).unwrap(), + // abs_con(0.56) + // ); + // assert_abs_diff_eq!( + // mode.control(abs_con(1.0), &target, ()).unwrap(), + // abs_con(0.6) + // ); + // } + + #[test] + fn transformation_ok() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + control_transformation: Some(TestTransformation::new( + ControlValueKind::AbsoluteDiscrete, + |input| Ok(input + 20.0), + )), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(20, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(60, 127), &target, ()).unwrap(), + abs_dis_val(80, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(120, 127), &target, ()).unwrap(), + abs_dis_val(140, 200) + ); + } + + #[test] + fn transformation_negative() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + control_transformation: Some(TestTransformation::new( + ControlValueKind::AbsoluteDiscrete, + |input| Ok(input - 20.0), + )), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(20, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(120, 127), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + } + + #[test] + fn transformation_err() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + control_transformation: Some(TestTransformation::new( + ControlValueKind::AbsoluteDiscrete, + |_| Err("oh no!"), + )), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(38, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_dis_evt(0, 127), &target, ()).unwrap(), + abs_dis_val(0, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(60, 127), &target, ()).unwrap(), + abs_dis_val(60, 200) + ); + assert_abs_diff_eq!( + mode.control(abs_dis_evt(127, 127), &target, ()).unwrap(), + abs_dis_val(127, 200) + ); + } + + #[test] + fn feedback() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 10), FB_OPTS).unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(5, 10), FB_OPTS).unwrap(), + dis_val(5, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(10, 10), FB_OPTS) + .unwrap(), + dis_val(10, 100) + ); + } + + #[test] + fn feedback_with_virtual_source() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + ..Default::default() + }); + let options = ModeFeedbackOptions { + source_is_virtual: true, + max_discrete_source_value: None, + }; + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 10), options).unwrap(), + dis_val(0, 10) + ); + assert_eq!( + mode.feedback_with_options(dis_val(5, 10), options).unwrap(), + dis_val(5, 10) + ); + assert_eq!( + mode.feedback_with_options(dis_val(10, 10), options) + .unwrap(), + dis_val(10, 10) + ); + } + + #[test] + fn feedback_reverse() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + reverse: true, + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 127), FB_OPTS) + .unwrap(), + dis_val(100, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(5, 127), FB_OPTS) + .unwrap(), + dis_val(100 - 5, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(10, 127), FB_OPTS) + .unwrap(), + dis_val(100 - 10, 100) + ); + } + + #[test] + fn feedback_target_interval() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(20, u32::MAX), + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(20, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(40, 100), FB_OPTS) + .unwrap(), + dis_val(20, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(60, 100), FB_OPTS) + .unwrap(), + dis_val(40, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(80, 100), FB_OPTS) + .unwrap(), + dis_val(60, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 100), FB_OPTS) + .unwrap(), + dis_val(80, 100) + ); + } + + #[test] + fn feedback_target_interval_reverse() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(20, u32::MAX), + reverse: true, + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 200), FB_OPTS) + .unwrap(), + dis_val(100, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(20, 200), FB_OPTS) + .unwrap(), + dis_val(100, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(40, 200), FB_OPTS) + .unwrap(), + dis_val(100 - 20, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(60, 200), FB_OPTS) + .unwrap(), + dis_val(100 - 40, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(80, 200), FB_OPTS) + .unwrap(), + dis_val(100 - 60, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 200), FB_OPTS) + .unwrap(), + dis_val(100 - 80, 100) + ); + } + + #[test] + fn feedback_source_and_target_interval() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_source_value_interval: Interval::new(20, 80), + discrete_target_value_interval: Interval::new(40, 100), + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS) + .unwrap(), + dis_val(20, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(40, 100), FB_OPTS) + .unwrap(), + dis_val(20, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(70, 100), FB_OPTS) + .unwrap(), + dis_val(50, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 100), FB_OPTS) + .unwrap(), + dis_val(80, 100) + ); + } + + #[test] + fn feedback_out_of_range_ignore() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(20, 80), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + // When + // Then + assert_eq!(mode.feedback_with_options(dis_val(0, 100), FB_OPTS), None); + assert_eq!( + mode.feedback_with_options(dis_val(50, 100), FB_OPTS) + .unwrap(), + dis_val(30, 100) + ); + assert_eq!(mode.feedback_with_options(dis_val(100, 100), FB_OPTS), None); + } + + #[test] + fn feedback_out_of_range_min() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(20, 80), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(10, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 100), FB_OPTS) + .unwrap(), + dis_val(30, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(90, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + } + + #[test] + fn feedback_out_of_range_min_max_okay() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(2, 2), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(1, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + // TODO-high-discrete Would make more sense to use SOURCE MAX instead of 1. So this + // (1, 1) thing is not that useful! We should have a way to express MAX - which is + // u32:MAX. It should then be clamped to + // min(source_interval_max, discrete_source_max). + assert_eq!( + mode.feedback_with_options(dis_val(2, 100), FB_OPTS) + .unwrap(), + dis_val(1, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(3, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + } + + #[test] + fn feedback_out_of_range_min_max_issue_263() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(3, 3), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(1, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(2, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(3, 100), FB_OPTS) + .unwrap(), + dis_val(1, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(4, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + } + + #[test] + fn feedback_out_of_range_min_target_one_value() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(50, 50), + out_of_range_behavior: OutOfRangeBehavior::Min, + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(10, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 100), FB_OPTS) + .unwrap(), + dis_val(1, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(90, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + } + + #[test] + fn feedback_out_of_range_min_max_target_one_value() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(50, 50), + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(0, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(10, 100), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 100), FB_OPTS) + .unwrap(), + dis_val(1, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(90, 100), FB_OPTS) + .unwrap(), + dis_val(1, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 100), FB_OPTS) + .unwrap(), + dis_val(1, 100) + ); + } + + #[test] + fn feedback_out_of_range_ignore_target_one_value() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + discrete_target_value_interval: Interval::new(50, 50), + out_of_range_behavior: OutOfRangeBehavior::Ignore, + ..Default::default() + }); + // When + // Then + assert_eq!(mode.feedback_with_options(dis_val(0, 100), FB_OPTS), None); + assert_eq!(mode.feedback_with_options(dis_val(10, 100), FB_OPTS), None); + assert_eq!( + mode.feedback_with_options(dis_val(50, 100), FB_OPTS) + .unwrap(), + dis_val(1, 100) + ); + assert_eq!(mode.feedback_with_options(dis_val(90, 100), FB_OPTS), None); + assert_eq!(mode.feedback_with_options(dis_val(100, 100), FB_OPTS), None); + } + + #[test] + fn feedback_transformation() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + feedback_transformation: Some(TestTransformation::new( + ControlValueKind::AbsoluteDiscrete, + |input| Ok(input - 10.0), + )), + ..Default::default() + }); + // When + // Then + assert_eq!( + mode.feedback_with_options(dis_val(00, 127), FB_OPTS) + .unwrap(), + dis_val(0, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(50, 127), FB_OPTS) + .unwrap(), + dis_val(40, 100) + ); + assert_eq!( + mode.feedback_with_options(dis_val(100, 127), FB_OPTS) + .unwrap(), + dis_val(90, 100) + ); + } + } + } + + mod absolute_toggle { + use super::*; + + #[test] + fn absolute_value_target_off() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn absolute_value_target_on() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn absolute_value_target_rather_off() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.333)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn absolute_value_target_rather_on() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.777)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn absolute_value_target_interval_target_off() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + target_value_interval: create_unit_value_interval(0.3, 0.7), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.3)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.7) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.7) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.7) + ); + } + + #[test] + fn absolute_value_target_interval_target_on() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + target_value_interval: create_unit_value_interval(0.3, 0.7), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.7)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.3) + ); + } + + #[test] + fn absolute_value_target_interval_target_rather_off() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + target_value_interval: create_unit_value_interval(0.3, 0.7), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.4)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.7) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.7) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.7) + ); + } + + #[test] + fn absolute_value_target_interval_target_rather_on() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + target_value_interval: create_unit_value_interval(0.3, 0.7), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.3) + ); + } + + #[test] + fn absolute_value_target_interval_target_too_off() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + target_value_interval: create_unit_value_interval(0.3, 0.7), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.7) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.7) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.7) + ); + } + + #[test] + fn absolute_value_target_interval_target_too_on() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + target_value_interval: create_unit_value_interval(0.3, 0.7), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.3) + ); + } + + #[test] + fn feedback() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(1.0)); + } + + #[test] + fn feedback_target_interval() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::ToggleButton, + target_value_interval: create_unit_value_interval(0.3, 0.7), + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.4)).unwrap(), con_val(0.25)); + assert_abs_diff_eq!(mode.feedback(con_val(0.7)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(1.0)); + } + } + + mod make_relative { + use super::*; + + #[test] + fn continuous_to_continuous_equal() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::MakeRelative, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + test(0.0, None); + test(0.1, Some(0.1)); + test(0.4, Some(0.4)); + test(0.5, Some(0.5)); + test(1.0, Some(1.0)); + } + + #[test] + fn continuous_to_continuous_shifted() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::MakeRelative, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.4)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + test(0.0, None); + test(0.1, Some(0.5)); + test(0.2, Some(0.6)); + test(0.4, Some(0.8)); + test(0.5, Some(0.9)); + test(0.3, Some(0.7)); + test(1.0, Some(1.0)); + test(0.9, Some(0.9)); + test(0.8, Some(0.8)); + test(0.0, Some(0.0)); + } + + /// Yes, we want to ignore the target's atomic step size! We want a full control sweep to + /// always result in a full target sweep! + #[test] + fn continuous_to_discrete_shifted() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::MakeRelative, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.4)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.5), + is_retriggerable: false, + }, + }; + // When + // Then + let mut test = |i, o| { + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + test(0.0, None); + test(0.1, Some(0.5)); + test(0.2, Some(0.6)); + test(0.4, Some(0.8)); + test(0.5, Some(0.9)); + test(0.3, Some(0.7)); + test(1.0, Some(1.0)); + test(0.9, Some(0.9)); + test(0.8, Some(0.8)); + test(0.0, Some(0.0)); + } + + /// Absolute discrete becomes relative continuous when not using discrete processing. + #[test] + fn discrete_to_continuous_shifted() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::MakeRelative, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.4)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + let mut test = |i, o| { + abs_test_cumulative(&mut mode, &mut target, ControlValue::AbsoluteDiscrete(i), o); + }; + test(Fraction::new(0, 10), None); + test(Fraction::new(1, 10), Some(0.5)); + test(Fraction::new(2, 10), Some(0.6)); + test(Fraction::new(4, 10), Some(0.8)); + test(Fraction::new(5, 10), Some(0.9)); + test(Fraction::new(3, 10), Some(0.7)); + test(Fraction::new(10, 10), Some(1.0)); + test(Fraction::new(9, 10), Some(0.9)); + test(Fraction::new(8, 10), Some(0.8)); + test(Fraction::new(0, 10), Some(0.0)); + } + + /// Absolute discrete becomes relative continuous when not using discrete processing. + #[test] + fn discrete_to_discrete_shifted() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::MakeRelative, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.4)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.5), + is_retriggerable: false, + }, + }; + // When + // Then + let mut test = |i, o| { + abs_test_cumulative(&mut mode, &mut target, ControlValue::AbsoluteDiscrete(i), o); + }; + test(Fraction::new(0, 10), None); + test(Fraction::new(0, 10), None); + test(Fraction::new(1, 10), Some(0.5)); + test(Fraction::new(2, 10), Some(0.6)); + test(Fraction::new(4, 10), Some(0.8)); + test(Fraction::new(5, 10), Some(0.9)); + test(Fraction::new(3, 10), Some(0.7)); + test(Fraction::new(10, 10), Some(1.0)); + test(Fraction::new(9, 10), Some(0.9)); + test(Fraction::new(8, 10), Some(0.8)); + test(Fraction::new(0, 10), Some(0.0)); + } + + /// We can even navigate relatively in target value sequences, but step counts are ignored. + #[test] + fn target_value_sequence() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + // Should be translated to set of 0.0, 0.2, 0.4, 0.5, 0.9! + target_value_sequence: "0.2, 0.4, 0.4, 0.5, 0.0, 0.9".parse().unwrap(), + step_factor_interval: create_discrete_increment_interval(10, 10), + absolute_mode: AbsoluteMode::MakeRelative, + ..Default::default() + }); + let mut target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteContinuous, + }; + mode.update_from_target(&target, (), false); + // When + // Then + let mut test = |i, o| { + abs_con_test_cumulative(&mut mode, &mut target, i, o); + }; + test(0.5, None); + test(0.55, Some(0.9)); + test(0.6, None); + test(0.5, Some(0.5)); + test(0.4, Some(0.4)); + test(0.3, Some(0.2)); + test(0.2, Some(0.0)); + test(0.1, None); + } + } + + mod performance_control { + use super::*; + + #[test] + fn performance_control() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::PerformanceControl, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + let last_non_performance_target_value = Some(con_val(0.5)); + // When + // Then + let mut test = |i, o| { + perf_test( + &mut mode, + &target, + abs_con_val(i), + o, + last_non_performance_target_value, + ); + }; + test(0.0, Some(0.5)); + test(0.5, Some(0.75)); + test(1.0, Some(1.0)); + } + + #[test] + fn performance_control_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::PerformanceControl, + target_value_interval: create_unit_value_interval(0.0, 0.6), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + let last_non_performance_target_value = Some(con_val(0.1)); + // When + // Then + let mut test = |i, o| { + perf_test( + &mut mode, + &target, + abs_con_val(i), + o, + last_non_performance_target_value, + ); + }; + test(0.0, Some(0.1)); + test(0.5, Some(0.35)); + test(1.0, Some(0.6)); + } + + #[test] + fn performance_control_reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::PerformanceControl, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + let last_non_performance_target_value = Some(con_val(0.5)); + // When + // Then + let mut test = |i, o| { + perf_test( + &mut mode, + &target, + abs_con_val(i), + o, + last_non_performance_target_value, + ); + }; + test(0.0, Some(0.5)); + test(0.5, Some(0.25)); + test(1.0, Some(0.0)); + } + + #[test] + fn performance_control_reverse_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::PerformanceControl, + target_value_interval: create_unit_value_interval(0.4, 1.0), + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + let last_non_performance_target_value = Some(con_val(0.9)); + // When + // Then + let mut test = |i, o| { + perf_test( + &mut mode, + &target, + abs_con_val(i), + o, + last_non_performance_target_value, + ); + }; + test(0.0, Some(0.9)); + test(0.5, Some(0.65)); + test(1.0, Some(0.4)); + } + } + + mod relative { + use super::*; + + mod absolute_continuous_target { + use super::*; + + #[test] + fn default_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.01) + ); + } + + #[test] + fn default_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn trigger_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(-2, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: None, + control_type: ControlType::AbsoluteContinuousRetriggerable, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn retriggerable_with_value_target() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(1, 1), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuousRetriggerable, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn min_step_size_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_size_interval: create_unit_value_interval(0.2, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn min_step_size_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_size_interval: create_unit_value_interval(0.2, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.6) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn max_step_size_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_size_interval: create_unit_value_interval(0.01, 0.09), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.02) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.09) + ); + } + + #[test] + fn max_step_size_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_size_interval: create_unit_value_interval(0.01, 0.09), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.91) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.98) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn rotate_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.01) + ); + } + + #[test] + fn rotate_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn target_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.2)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.21) + ); + } + + #[test] + fn target_interval_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.8)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.79) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.79) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.79) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + /// See https://github.com/helgoboss/helgobox/issues/577. + #[test] + fn target_interval_max_not_exceedable() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.0, 0.716), + step_size_interval: create_unit_value_interval(0.01, 0.01), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.71)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.716) + ); + } + + #[test] + fn target_interval_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_current_target_value_just_appearing_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.199999999999)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + // That's the point: If the current value of 0.199999999999 would be considered as + // out-of-range, we would end up on 0.2 (target min). But we should consider it as + // in-range and therefore use the usual incrementation logic. + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.20999999999900001) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.20999999999900001) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.20999999999900001) + ); + } + + /// See https://github.com/helgoboss/helgobox/issues/100. + #[test] + fn not_get_stuck() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: full_unit_interval(), + step_size_interval: create_unit_value_interval(0.01, 0.01), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.875)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.865) + ); + } + + #[test] + fn target_interval_min_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.2)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.21) + ); + } + + #[test] + fn target_interval_max_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.8)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.79) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.79) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.79) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_rotate_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + // TODO-medium-discrete Add tests for discrete processing + #[test] + fn target_value_sequence() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + // Should be translated to set of 0.0, 0.2, 0.4, 0.5, 0.9! + target_value_sequence: "0.2, 0.4, 0.4, 0.5, 0.0, 0.9".parse().unwrap(), + step_factor_interval: create_discrete_increment_interval(1, 5), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteContinuous, + }; + mode.update_from_target(&target, (), false); + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.9) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.9) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-3), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-4), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-5), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + // TODO-medium-discrete Add tests for discrete processing + #[test] + fn target_value_sequence_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + // Should be translated to set of 0.0, 0.2, 0.4, 0.5, 0.9! + target_value_sequence: "0.2, 0.4, 0.4, 0.5, 0.0, 0.9".parse().unwrap(), + step_factor_interval: create_discrete_increment_interval(1, 5), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteContinuous, + }; + mode.update_from_target(&target, (), false); + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.9) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(3), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.4) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-3), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-4), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-5), &target, ()).unwrap(), + abs_con_val(0.9) + ); + } + + #[test] + fn make_absolute_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + make_absolute: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-2), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-1), &target, ()), None); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.02) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.03) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-5), &target, ()).unwrap(), + abs_con_val(0.02) + ); + } + + #[test] + fn make_absolute_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + make_absolute: true, + step_size_interval: create_unit_value_interval(0.01, 0.05), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.5)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + // TODO-medium This behavior is debatable! Normal absolute control elements don't + // send the same absolute value multiple times when hitting knob/fader boundary. + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.03) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.08) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-5), &target, ()).unwrap(), + abs_con_val(0.03) + ); + } + } + + mod absolute_discrete_target { + use super::*; + + mod continuous_processing { + use super::*; + + #[test] + fn default_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.05) + ); + } + + #[test] + fn default_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(20, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn min_step_count_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(4, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.20) + ); + // 4x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.25) + ); + // 5x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(4), &target, ()).unwrap(), + abs_con_val(0.35) + ); + // 7x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.65) + ); + // 13x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(100), &target, ()).unwrap(), + abs_con_val(1.00) + ); + // 100x + } + + #[test] + fn min_step_count_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(4, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(20, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.35) + ); + // 13x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.75) + ); + // 5x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.8) + ); // 4x + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn max_step_count_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(1, 2), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.10) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.10) + ); + } + + #[test] + fn max_step_count_throttle() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(-2, -2), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + // No effect because already min + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + // Every 2nd time + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.05) + ); + } + + #[test] + fn max_step_count_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(1, 2), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(20, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.90) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.90) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn rotate_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.05) + ); + } + + #[test] + fn rotate_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(20, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn target_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(4, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.25) + ); + } + + #[test] + fn target_interval_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(16, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.75) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.75) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.75) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn target_interval_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_step_interval_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(1, 100), + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_snaps_to_target_step_size() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(1, 1), + target_value_interval: create_unit_value_interval(0.2, 0.71), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.2), + is_retriggerable: false, + }, + }; + // When + // Then + // Target interval should implicitly change to 0.2 - 0.8. + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.8), + ); + } + + #[test] + fn target_interval_min_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(4, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.25) + ); + } + + #[test] + fn target_interval_max_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(16, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.75) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.75) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.75) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_rotate_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn make_absolute_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + make_absolute: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-2), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-1), &target, ()), None); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.02) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.03) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-5), &target, ()).unwrap(), + abs_con_val(0.02) + ); + } + + #[test] + fn make_absolute_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + make_absolute: true, + step_size_interval: create_unit_value_interval(0.01, 0.05), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(10, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_con_val(0.03) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_con_val(0.08) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-5), &target, ()).unwrap(), + abs_con_val(0.03) + ); + } + } + + mod discrete_processing { + use super::*; + + #[test] + fn default_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 20.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-2), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-1), &target, ()), None); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_dis_val(1, 20) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_dis_val(1, 20) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_dis_val(1, 20) + ); + } + + #[test] + fn default_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(20, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 20.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_dis_val(19, 20) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_dis_val(19, 20) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_dis_val(19, 20) + ); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(2), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(10), &target, ()), None); + } + + #[test] + fn min_step_count_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + use_discrete_processing: true, + step_factor_interval: create_discrete_increment_interval(4, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(0, 200)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 200.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(rel_dis_evt(-10), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(-1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(1), &target, ()).unwrap(), + abs_dis_val(4, 200) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(2), &target, ()).unwrap(), + abs_dis_val(5, 200) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(4), &target, ()).unwrap(), + abs_dis_val(7, 200) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(10), &target, ()).unwrap(), + abs_dis_val(13, 200) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(100), &target, ()).unwrap(), + abs_dis_val(100, 200) + ); + } + + #[test] + fn min_step_count_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(4, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(dis_val(20, 20)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(1.0 / 20.0), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.35) + ); + // 13x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.75) + ); + // 5x + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.8) + ); // 4x + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + // + // #[test] + // fn max_step_count_1() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // step_count_interval: create_discrete_increment_interval(1, 2), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert!(mode.control(rel(-10), &target, ()).is_none()); + // assert!(mode.control(rel(-2), &target, ()).is_none()); + // assert!(mode.control(rel(-1), &target, ()).is_none()); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.05)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.10)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.10)); + // } + // + // #[test] + // fn max_step_count_throttle() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // step_count_interval: create_discrete_increment_interval(-2, -2), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // // No effect because already min + // assert!(mode.control(rel(-10), &target, ()).is_none()); + // assert!(mode.control(rel(-10), &target, ()).is_none()); + // assert!(mode.control(rel(-10), &target, ()).is_none()); + // assert!(mode.control(rel(-10), &target, ()).is_none()); + // // Every 2nd time + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.05)); + // assert!(mode.control(rel(1), &target, ()).is_none()); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.05)); + // assert!(mode.control(rel(2), &target, ()).is_none()); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.05)); + // } + // + // #[test] + // fn max_step_count_2() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // step_count_interval: create_discrete_increment_interval(1, 2), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(20, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!( + // mode.control(rel(-10), &target, ()).unwrap(), + // abs_con(0.90) + // ); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.90)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.95)); + // assert!(mode.control(rel(1), &target, ()).is_none()); + // assert!(mode.control(rel(2), &target, ()).is_none()); + // assert!(mode.control(rel(10), &target, ()).is_none()); + // } + // + // #[test] + // fn reverse() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // reverse: true, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!( + // mode.control(rel(-10), &target, ()).unwrap(), + // abs_con(0.05) + // ); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.05)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.05)); + // assert!(mode.control(rel(1), &target, ()).is_none()); + // assert!(mode.control(rel(2), &target, ()).is_none()); + // assert!(mode.control(rel(10), &target, ()).is_none()); + // } + // + // #[test] + // fn rotate_1() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // rotate: true, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!(mode.control(rel(-10), &target, ()).unwrap(), abs_con(1.0)); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(1.0)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(1.0)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.05)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.05)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.05)); + // } + // + // #[test] + // fn rotate_2() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // rotate: true, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(20, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!( + // mode.control(rel(-10), &target, ()).unwrap(), + // abs_con(0.95) + // ); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.95)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.95)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.0)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.0)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.0)); + // } + // + // #[test] + // fn target_interval_min() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // target_value_interval: create_unit_value_interval(0.2, 0.8), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(4, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert!(mode.control(rel(-10), &target, ()).is_none()); + // assert!(mode.control(rel(-2), &target, ()).is_none()); + // assert!(mode.control(rel(-1), &target, ()).is_none()); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.25)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.25)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.25)); + // } + // + // #[test] + // fn target_interval_max() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // target_value_interval: create_unit_value_interval(0.2, 0.8), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(16, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!( + // mode.control(rel(-10), &target, ()).unwrap(), + // abs_con(0.75) + // ); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.75)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.75)); + // assert!(mode.control(rel(1), &target, ()).is_none()); + // assert!(mode.control(rel(2), &target, ()).is_none()); + // assert!(mode.control(rel(10), &target, ()).is_none()); + // } + // + // #[test] + // fn target_interval_current_target_value_out_of_range() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // target_value_interval: create_unit_value_interval(0.2, 0.8), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!(mode.control(rel(-10), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.2)); + // } + // + // #[test] + // fn target_interval_step_interval_current_target_value_out_of_range() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // step_count_interval: create_discrete_increment_interval(1, 100), + // target_value_interval: create_unit_value_interval(0.2, 0.8), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!(mode.control(rel(-10), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.2)); + // } + // + // #[test] + // fn target_interval_min_rotate() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // target_value_interval: create_unit_value_interval(0.2, 0.8), + // rotate: true, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(4, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!(mode.control(rel(-10), &target, ()).unwrap(), abs_con(0.8)); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.8)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.8)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.25)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.25)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.25)); + // } + // + // #[test] + // fn target_interval_max_rotate() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // target_value_interval: create_unit_value_interval(0.2, 0.8), + // rotate: true, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(16, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!( + // mode.control(rel(-10), &target, ()).unwrap(), + // abs_con(0.75) + // ); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.75)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.75)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.2)); + // } + // + // #[test] + // fn target_interval_rotate_current_target_value_out_of_range() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // target_value_interval: create_unit_value_interval(0.2, 0.8), + // rotate: true, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!(mode.control(rel(-10), &target, ()).unwrap(), abs_con(0.8)); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.8)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.8)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.2)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.2)); + // } + + // #[test] + // fn make_absolute_1() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // convert_relative_to_absolute: true, + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(0, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_eq!(mode.control(rel(-10), &target, ()), None); + // assert_eq!(mode.control(rel(-2), &target, ()), None); + // assert_eq!(mode.control(rel(-1), &target, ()), None); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.01)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.02)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.03)); + // assert_abs_diff_eq!(mode.control(rel(-5), &target, ()).unwrap(), abs_con(0.02)); + // } + // + // #[test] + // fn make_absolute_2() { + // // Given + // let mut mode: TestMode = Mode::new(ModeSettings { + // convert_relative_to_absolute: true, + // step_size_interval: create_unit_value_interval(0.01, 0.05), + // ..Default::default() + // }); + // let target = TestTarget { + // current_value: Some(dis_val(10, 20)), + // control_type: ControlType::AbsoluteDiscrete { + // atomic_step_size: UnitValue::new(1.0 / 21.0), + // }, + // }; + // // When + // // Then + // assert_abs_diff_eq!(mode.control(rel(-10), &target, ()).unwrap(), abs_con(0.0)); + // assert_abs_diff_eq!(mode.control(rel(-2), &target, ()).unwrap(), abs_con(0.0)); + // assert_abs_diff_eq!(mode.control(rel(-1), &target, ()).unwrap(), abs_con(0.0)); + // assert_abs_diff_eq!(mode.control(rel(1), &target, ()).unwrap(), abs_con(0.01)); + // assert_abs_diff_eq!(mode.control(rel(2), &target, ()).unwrap(), abs_con(0.03)); + // assert_abs_diff_eq!(mode.control(rel(10), &target, ()).unwrap(), abs_con(0.08)); + // assert_abs_diff_eq!(mode.control(rel(-5), &target, ()).unwrap(), abs_con(0.03)); + // } + } + } + + mod relative_target { + use super::*; + + #[test] + fn default() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert_eq!( + mode.control(rel_dis_evt(-10), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!( + mode.control(rel_dis_evt(-2), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!( + mode.control(rel_dis_evt(-1), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!( + mode.control(rel_dis_evt(1), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!( + mode.control(rel_dis_evt(2), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!( + mode.control(rel_dis_evt(10), &target, ()), + Some(rel_dis_val(1)) + ); + } + + #[test] + fn min_step_count() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(2, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert_eq!( + mode.control(rel_dis_evt(-10), &target, ()), + Some(rel_dis_val(-11)) + ); + assert_eq!( + mode.control(rel_dis_evt(-2), &target, ()), + Some(rel_dis_val(-3)) + ); + assert_eq!( + mode.control(rel_dis_evt(-1), &target, ()), + Some(rel_dis_val(-2)) + ); + assert_eq!( + mode.control(rel_dis_evt(1), &target, ()), + Some(rel_dis_val(2)) + ); + assert_eq!( + mode.control(rel_dis_evt(2), &target, ()), + Some(rel_dis_val(3)) + ); + assert_eq!( + mode.control(rel_dis_evt(10), &target, ()), + Some(rel_dis_val(11)) + ); + } + + #[test] + fn min_step_count_throttle() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(-4, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + // So intense that reaching speedup area + assert_eq!( + mode.control(rel_dis_evt(-10), &target, ()), + Some(rel_dis_val(-6)) + ); + // Every 3rd time + assert_eq!( + mode.control(rel_dis_evt(-2), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!(mode.control(rel_dis_evt(-2), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-2), &target, ()), None); + assert_eq!( + mode.control(rel_dis_evt(-2), &target, ()), + Some(rel_dis_val(-1)) + ); + // Every 4th time (but fired before) + assert_eq!(mode.control(rel_dis_evt(-1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-1), &target, ()), None); + assert_eq!( + mode.control(rel_dis_evt(-1), &target, ()), + Some(rel_dis_val(-1)) + ); + // Direction change + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + // Every 3rd time (but fired before) + assert_eq!( + mode.control(rel_dis_evt(2), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!(mode.control(rel_dis_evt(2), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(2), &target, ()), None); + assert_eq!( + mode.control(rel_dis_evt(2), &target, ()), + Some(rel_dis_val(1)) + ); + // So intense that reaching speedup area + assert_eq!( + mode.control(rel_dis_evt(10), &target, ()), + Some(rel_dis_val(6)) + ); + } + + #[test] + fn max_step_count() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(1, 2), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert_eq!( + mode.control(rel_dis_evt(-10), &target, ()), + Some(rel_dis_val(-2)) + ); + assert_eq!( + mode.control(rel_dis_evt(-2), &target, ()), + Some(rel_dis_val(-2)) + ); + assert_eq!( + mode.control(rel_dis_evt(-1), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!( + mode.control(rel_dis_evt(1), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!( + mode.control(rel_dis_evt(2), &target, ()), + Some(rel_dis_val(2)) + ); + assert_eq!( + mode.control(rel_dis_evt(10), &target, ()), + Some(rel_dis_val(2)) + ); + } + + #[test] + fn max_step_count_throttle() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + step_factor_interval: create_discrete_increment_interval(-10, -4), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + // Every 4th time + assert_eq!( + mode.control(rel_dis_evt(-10), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!( + mode.control(rel_dis_evt(-10), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(-10), &target, ()), None); + // Every 10th time + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!( + mode.control(rel_dis_evt(1), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!(mode.control(rel_dis_evt(1), &target, ()), None); + assert_eq!( + mode.control(rel_dis_evt(1), &target, ()), + Some(rel_dis_val(1)) + ); + } + + #[test] + fn reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert_eq!( + mode.control(rel_dis_evt(-10), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!( + mode.control(rel_dis_evt(-2), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!( + mode.control(rel_dis_evt(-1), &target, ()), + Some(rel_dis_val(1)) + ); + assert_eq!( + mode.control(rel_dis_evt(1), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!( + mode.control(rel_dis_evt(2), &target, ()), + Some(rel_dis_val(-1)) + ); + assert_eq!( + mode.control(rel_dis_evt(10), &target, ()), + Some(rel_dis_val(-1)) + ); + } + } + } + + mod incremental_buttons { + use super::*; + + mod absolute_continuous_target { + use super::*; + + #[test] + fn default_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.01) + ); + } + + #[test] + fn default_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn min_step_size_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_size_interval: create_unit_value_interval(0.2, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.28) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.6) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn min_step_size_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_size_interval: create_unit_value_interval(0.2, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn max_step_size_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_size_interval: create_unit_value_interval(0.01, 0.09), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.018) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.75), &target, ()).unwrap(), + abs_con_val(0.07) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.09) + ); + } + + #[test] + fn max_step_size_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_size_interval: create_unit_value_interval(0.01, 0.09), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn source_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + source_value_interval: create_unit_value_interval(0.5, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.25), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.75), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.01) + ); + } + + #[test] + fn source_interval_step_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + source_value_interval: create_unit_value_interval(0.5, 1.0), + step_size_interval: create_unit_value_interval(0.5, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.25), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.75), &target, ()).unwrap(), + abs_con_val(0.75) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn reverse_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn reverse_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.99) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.99) + ); + } + + #[test] + fn rotate_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.01) + ); + } + + #[test] + fn rotate_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn rotate_3_almost_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.990000000001)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn reverse_and_rotate_almost_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + rotate: true, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.00999999999999)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn reverse_and_rotate_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + rotate: true, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn target_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.2)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.21) + ); + } + + #[test] + fn target_interval_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.8)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn target_interval_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_min_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.2)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.21) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.21) + ); + } + + #[test] + fn target_interval_max_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.8)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_rotate_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_rotate_reverse_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + reverse: true, + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.8) + ); + } + + #[test] + fn make_absolute_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + make_absolute: true, + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteContinuous, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.01) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.02) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.03) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.04) + ); + } + + // TODO-medium-discrete Add tests for discrete processing + #[test] + fn target_value_sequence() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + // Should be translated to set of 0.0, 0.2, 0.4, 0.5, 0.9! + target_value_sequence: "0.2, 0.4, 0.4, 0.5, 0.0, 0.9".parse().unwrap(), + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.6)), + control_type: ControlType::AbsoluteContinuous, + }; + mode.update_from_target(&target, (), false); + // When + // Then + assert_eq!(mode.control(abs_con_evt(0.0), &target, ()), None); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.9) + ); + } + } + + mod absolute_discrete_target { + use super::*; + + #[test] + fn default_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.05) + ); + } + + #[test] + fn default_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn min_step_count_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(4, 8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.4) + ); + } + + #[test] + fn min_step_count_throttle() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(-4, -4), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + // Every 4th time + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + } + + #[test] + fn min_step_count_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(4, 8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn max_step_count_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(1, 8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.1) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.4) + ); + } + + #[test] + fn max_step_count_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(1, 2), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-10), &target, ()).unwrap(), + abs_con_val(0.90) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-2), &target, ()).unwrap(), + abs_con_val(0.90) + ); + assert_abs_diff_eq!( + mode.control(rel_dis_evt(-1), &target, ()).unwrap(), + abs_con_val(0.95) + ); + assert!(mode.control(rel_dis_evt(1), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(2), &target, ()).is_none()); + assert!(mode.control(rel_dis_evt(10), &target, ()).is_none()); + } + + #[test] + fn source_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + source_value_interval: create_unit_value_interval(0.5, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.25), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.75), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.05) + ); + } + + #[test] + fn source_interval_step_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + source_value_interval: create_unit_value_interval(0.5, 1.0), + step_factor_interval: create_discrete_increment_interval(4, 8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.25), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.75), &target, ()).unwrap(), + abs_con_val(0.3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.4) + ); + } + + #[test] + fn reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn rotate_1() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.05) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.05) + ); + } + + #[test] + fn rotate_2() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(1.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.0) + ); + } + + #[test] + fn target_interval_min() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.2)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.25) + ); + } + + #[test] + fn target_interval_max() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.8)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.1), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.5), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(1.0), &target, ()).is_none()); + } + + #[test] + fn target_interval_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn step_count_interval_exceeded() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(1, 100), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.55) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(1.0) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(1.0) + ); + } + + #[test] + fn target_interval_step_interval_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(1, 100), + target_value_interval: create_unit_value_interval(0.2, 0.8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_min_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.2)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.25) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.25) + ); + } + + #[test] + fn target_interval_max_rotate() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.8)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_rotate_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.2) + ); + } + + #[test] + fn target_interval_rotate_reverse_current_target_value_out_of_range() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + target_value_interval: create_unit_value_interval(0.2, 0.8), + reverse: true, + rotate: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::AbsoluteDiscrete { + atomic_step_size: UnitValue::new(0.05), + is_retriggerable: false, + }, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + abs_con_val(0.8) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + abs_con_val(0.8) + ); + } + } + + mod relative_target { + use super::*; + + #[test] + fn default() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + rel_dis_val(1) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + rel_dis_val(1) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + rel_dis_val(1) + ); + } + + #[test] + fn min_step_count() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(2, 8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + rel_dis_val(3) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + rel_dis_val(5) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + rel_dis_val(8) + ); + } + + #[test] + fn max_step_count() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + step_factor_interval: create_discrete_increment_interval(1, 2), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + rel_dis_val(1) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + rel_dis_val(2) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + rel_dis_val(2) + ); + } + + #[test] + fn source_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + source_value_interval: create_unit_value_interval(0.5, 1.0), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.25), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + rel_dis_val(1) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + rel_dis_val(1) + ); + } + + #[test] + fn source_interval_step_interval() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + source_value_interval: create_unit_value_interval(0.5, 1.0), + step_factor_interval: create_discrete_increment_interval(4, 8), + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert!(mode.control(abs_con_evt(0.25), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + rel_dis_val(4) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + rel_dis_val(8) + ); + } + + #[test] + fn reverse() { + // Given + let mut mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + reverse: true, + ..Default::default() + }); + let target = TestTarget { + current_value: Some(con_val(0.0)), + control_type: ControlType::Relative, + }; + // When + // Then + assert!(mode.control(abs_con_evt(0.0), &target, ()).is_none()); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.1), &target, ()).unwrap(), + rel_dis_val(-1) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(0.5), &target, ()).unwrap(), + rel_dis_val(-1) + ); + assert_abs_diff_eq!( + mode.control(abs_con_evt(1.0), &target, ()).unwrap(), + rel_dis_val(-1) + ); + } + } + + mod feedback { + use super::*; + + #[test] + fn default() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(1.0)); + } + + #[test] + fn reverse() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + reverse: true, + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(1.0)); + assert_abs_diff_eq!(mode.feedback(con_val(0.5)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.0)); + } + + #[test] + fn source_and_target_interval() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + absolute_mode: AbsoluteMode::IncrementalButton, + source_value_interval: create_unit_value_interval(0.2, 0.8), + target_value_interval: create_unit_value_interval(0.4, 1.0), + ..Default::default() + }); + // When + // Then + assert_abs_diff_eq!(mode.feedback(con_val(0.0)).unwrap(), con_val(0.2)); + assert_abs_diff_eq!(mode.feedback(con_val(0.4)).unwrap(), con_val(0.2)); + assert_abs_diff_eq!(mode.feedback(con_val(0.7)).unwrap(), con_val(0.5)); + assert_abs_diff_eq!(mode.feedback(con_val(1.0)).unwrap(), con_val(0.8)); + } + } + } + + mod text_feedback { + use crate::mode::mode_struct::tests::TestMode; + use crate::{ + AbsoluteValue, FeedbackStyle, FeedbackValue, FeedbackValueTable, Fraction, Mode, + ModeFeedbackOptions, ModeSettings, NumericFeedbackValue, RgbColor, + TextualFeedbackValue, + }; + use std::borrow::Cow; + + #[test] + fn pass_through() { + // Given + let mode: TestMode = Mode::new(ModeSettings { + ..Default::default() + }); + // When + let style = FeedbackStyle { + color: Some(RgbColor::new(10, 10, 10)), + background_color: None, + }; + let playing = TextualFeedbackValue::new(style, "playing".into()); + let result = mode.feedback_with_options_detail( + Some(Cow::Owned(FeedbackValue::Textual(playing.clone()))), + ModeFeedbackOptions::default(), + (), + ); + // Then + assert_eq!(result, Some(Cow::Owned(FeedbackValue::Textual(playing)))); + } + + #[test] + fn feedback_value_table() { + // Given + let map = [("playing", 5), ("paused", 6)] + .into_iter() + .map(|(key, value)| (key.to_owned(), value)) + .collect(); + let mode: TestMode = Mode::new(ModeSettings { + feedback_value_table: Some(FeedbackValueTable::FromTextToDiscrete(map)), + ..Default::default() + }); + // When + let style = FeedbackStyle { + color: Some(RgbColor::new(10, 10, 10)), + background_color: None, + }; + let playing = TextualFeedbackValue::new(style, "playing".into()); + let matched_result = mode.feedback_with_options_detail( + Some(Cow::Owned(FeedbackValue::Textual(playing))), + ModeFeedbackOptions::default(), + (), + ); + let bla = TextualFeedbackValue::new(style, "bla".into()); + let unmatched_result = mode.feedback_with_options_detail( + Some(Cow::Owned(FeedbackValue::Textual(bla))), + ModeFeedbackOptions::default(), + (), + ); + // Then + assert_eq!( + matched_result, + Some(Cow::Owned(FeedbackValue::Numeric( + NumericFeedbackValue::new(style, AbsoluteValue::Discrete(Fraction::new_max(5))) + ))) + ); + assert_eq!(unmatched_result, None); + } + } + + /// Absolute continuous control event. + fn abs_con_evt(number: f64) -> TimelessControlEvent { + create_timeless_control_event(abs_con_val(number)) + } + + /// Absolute continuous control value. + fn abs_con_val(number: f64) -> ControlValue { + ControlValue::absolute_continuous(number) + } + + /// Absolute discrete control event. + fn abs_dis_evt(actual: u32, max: u32) -> TimelessControlEvent { + create_timeless_control_event(abs_dis_val(actual, max)) + } + + /// Absolute discrete control value. + fn abs_dis_val(actual: u32, max: u32) -> ControlValue { + ControlValue::absolute_discrete(actual, max) + } + + /// Relative discrete control event. + fn rel_dis_evt(increment: i32) -> TimelessControlEvent { + create_timeless_control_event(rel_dis_val(increment)) + } + + /// Relative discrete control value. + fn rel_dis_val(increment: i32) -> ControlValue { + ControlValue::relative(increment) + } + + fn con_val(v: f64) -> AbsoluteValue { + AbsoluteValue::Continuous(UnitValue::new(v)) + } + + fn dis_val(actual: u32, max: u32) -> AbsoluteValue { + AbsoluteValue::Discrete(Fraction::new(actual, max)) + } + + fn abs_con_test(mode: &mut TestMode, target: &TestTarget, input: f64, output: Option) { + abs_test( + mode, + target, + ControlValue::absolute_continuous(input), + output, + ) + } + + fn abs_con_test_cumulative( + mode: &mut TestMode, + target: &mut TestTarget, + input: f64, + output: Option, + ) { + abs_test_cumulative( + mode, + target, + ControlValue::absolute_continuous(input), + output, + ) + } + + fn abs_test( + mode: &mut TestMode, + target: &TestTarget, + input: ControlValue, + output: Option, + ) { + let result = mode.control(create_timeless_control_event(input), target, ()); + assert_equals(result, output) + } + + fn assert_equals(result: Option, expected: Option) { + if let Some(e) = expected { + assert_abs_diff_eq!(result.unwrap(), abs_con_val(e)); + } else { + assert_eq!(result, None); + } + } + + fn abs_test_cumulative( + mode: &mut TestMode, + target: &mut TestTarget, + input: ControlValue, + output: Option, + ) { + abs_test(mode, target, input, output); + if let Some(o) = output { + target.current_value = Some(con_val(o)); + } + } + + #[cfg(test)] + fn perf_test( + mode: &mut TestMode, + target: &TestTarget, + input: ControlValue, + output: Option, + last_non_performance_target_value: Option, + ) { + let result = mode + .control_with_options( + create_timeless_control_event(input), + target, + (), + ModeControlOptions::default(), + last_non_performance_target_value, + ) + .and_then(|r| r.into()); + assert_equals(result, output); + } + + type TestMode = Mode; + + type TimelessControlEvent

= ControlEvent; + + fn create_timeless_control_event

(payload: P) -> TimelessControlEvent

= helgoboss_learn::ControlEvent; + +/// Timestamp of a control event. +// +// Don't expose the inner field, it should stay private. We might swap the time unit in future to +// improve performance and accuracy. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ControlEventTimestamp(Duration); + +impl ControlEventTimestamp { + pub fn from_main_thread() -> Self { + let block_count = GLOBAL_AUDIO_STATE.load_block_count(); + let block_size = GLOBAL_AUDIO_STATE.load_block_size(); + let sample_count = block_count * block_size as u64; + Self::from_rt( + sample_count, + GLOBAL_AUDIO_STATE.load_sample_rate(), + DurationInSeconds::ZERO, + ) + } +} + +impl AbstractTimestamp for ControlEventTimestamp { + fn duration(&self) -> Duration { + self.0 + } +} + +impl ControlEventTimestamp { + pub fn from_rt( + sample_count: u64, + sample_rate: Hz, + intra_block_offset: DurationInSeconds, + ) -> Self { + let start_secs = sample_count as f64 / sample_rate.get(); + let final_secs = start_secs + intra_block_offset.get(); + Self(Duration::from_secs_f64(final_secs)) + } +} + +impl Sub for ControlEventTimestamp { + type Output = Duration; + + fn sub(self, rhs: Self) -> Self::Output { + self.0.saturating_sub(rhs.0) + } +} + +impl Display for ControlEventTimestamp { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/control_surface.rs b/plugin-reaper-realearn/main/src/domain/control_surface.rs new file mode 100644 index 0000000..ca42b3c --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/control_surface.rs @@ -0,0 +1,985 @@ +use crate::domain::{ + Backbone, ControlEvent, ControlEventTimestamp, DeviceControlInput, DeviceDiff, + DeviceFeedbackOutput, DomainEventHandler, FeedbackOutput, FinalSourceFeedbackValue, InstanceId, + MainProcessor, MidiDeviceChangeDetector, MidiDeviceChangePayload, + MonitoringFxChainChangeDetector, OscDeviceId, OscInputDevice, OscScanResult, + QualifiedInstanceEvent, ReaperConfigChangeDetector, ReaperMessage, ReaperTarget, + SharedInstance, SharedMainProcessors, StreamDeckDevicePayload, TargetTouchEvent, + TouchedTrackParameterType, UnitEvent, UnitId, WeakInstance, +}; +use base::{metrics_util, Global, NamedChannelSender, SenderToNormalThread}; +use crossbeam_channel::Receiver; +use reaper_high::{ + ChangeDetectionMiddleware, ChangeEvent, ControlSurfaceEvent, ControlSurfaceMiddleware, + FutureMiddleware, Fx, FxParameter, MainTaskMiddleware, Project, Reaper, +}; +use reaper_rx::ControlSurfaceRxMiddleware; +use rosc::{OscMessage, OscPacket}; +use std::cell::RefCell; + +use base::hash_util::{NonCryptoHashMap, NonCryptoIndexMap}; +use base::metrics_util::measure_time; +use itertools::{EitherOrBoth, Itertools}; +use reaper_medium::{ + CommandId, ExtSupportsExtendedTouchArgs, GetFocusedFx2Result, GetTouchStateArgs, + GetTouchedOrFocusedFxCurrentlyFocusedFxResult, MediaTrack, MidiInputDeviceId, + MidiOutputDeviceId, PositionInSeconds, ReaProject, ReaperNormalizedFxParamValue, + SectionContext, +}; +use rxrust::prelude::*; +use std::fmt::Debug; +use std::mem; +use tracing::debug; + +type OscCaptureSender = async_channel::Sender; +type TargetCaptureSender = async_channel::Sender; + +const CONTROL_SURFACE_MAIN_TASK_BULK_SIZE: usize = 10; +const INSTANCE_EVENT_BULK_SIZE: usize = 30; +const ADDITIONAL_FEEDBACK_EVENT_BULK_SIZE: usize = 30; +const INSTANCE_ORCHESTRATION_EVENT_BULK_SIZE: usize = 30; +const OSC_INCOMING_BULK_SIZE: usize = 1000; + +#[derive(Debug)] +pub struct RealearnControlSurfaceMiddleware { + change_detection_middleware: ChangeDetectionMiddleware, + change_event_queue: RefCell>, + monitoring_fx_chain_change_detector: MonitoringFxChainChangeDetector, + rx_middleware: ControlSurfaceRxMiddleware, + instances: NonCryptoIndexMap, + main_processors: SharedMainProcessors, + main_task_receiver: Receiver>, + instance_event_receiver: Receiver, + #[cfg(feature = "playtime")] + playtime: PlaytimeMiddleware, + additional_feedback_event_receiver: Receiver, + instance_orchestration_event_receiver: Receiver, + main_task_middleware: MainTaskMiddleware, + future_middleware: FutureMiddleware, + counter: u64, + full_beats: NonCryptoHashMap, + deprecated_fx_focus_state: Option, + _modern_fx_focus_state: Option, + target_capture_senders: NonCryptoHashMap, TargetCaptureSender>, + osc_capture_sender: Option, + osc_input_devices: Vec, + device_change_detector: MidiDeviceChangeDetector, + reaper_config_change_detector: ReaperConfigChangeDetector, + control_surface_event_sender: SenderToNormalThread>, + control_surface_event_receiver: crossbeam_channel::Receiver>, + #[cfg(debug_assertions)] + last_undesired_allocation_count: u32, + event_handler: Box, + osc_buffer: Vec, +} + +#[cfg(feature = "playtime")] +#[derive(Debug)] +struct PlaytimeMiddleware { + clip_matrix_event_receiver: Receiver, +} + +pub trait ControlSurfaceEventHandler: Debug { + fn midi_input_devices_changed( + &self, + diff: &DeviceDiff, + device_config_changed: bool, + ); + fn midi_output_devices_changed( + &self, + diff: &DeviceDiff, + device_config_changed: bool, + ); + + fn process_reaper_change_events(&self, change_events: &[ChangeEvent]); +} + +#[allow(clippy::large_enum_variant)] +pub enum RealearnControlSurfaceMainTask { + AddInstance(InstanceId, WeakInstance), + AddMainProcessor(MainProcessor), + RemoveMainProcessor(UnitId), + StartCapturingTargets(Option, TargetCaptureSender), + StopCapturingTargets(Option), + StartCapturingOsc(OscCaptureSender), + StopCapturingOsc, + SendAllFeedback, +} + +/// Not all events in REAPER are communicated via a control surface, e.g. action invocations. +#[derive(Debug)] +pub enum AdditionalFeedbackEvent { + ActionInvoked(ActionInvokedEvent), + FxSnapshotLoaded(FxSnapshotLoadedEvent), + /// Work around REAPER's inability to notify about parameter changes in + /// monitoring FX by simulating the notification ourselves. + /// Then parameter learning and feedback works at least for + /// ReaLearn monitoring FX instances, which is especially + /// useful for conditional activation. + RealearnMonitoringFxParameterValueChanged(RealearnMonitoringFxParameterValueChangedEvent), + ParameterAutomationTouchStateChanged(ParameterAutomationTouchStateChangedEvent), + /// Beat-changed events are emitted only when the project is playing. + /// + /// We shouldn't change that because targets such as "Marker/region: Go to" or "Project: Seek" + /// depend on this (see https://github.com/helgoboss/helgobox/issues/663). + BeatChanged(BeatChangedEvent), + MappedFxParametersChanged, + /// This event is raised when an FX window loses focus and a non-FX window gains focus, + /// and vice versa (not covered by FxFocused change event). + /// + /// Attention: This will only be fired for REAPER 7+! + FocusSwitchedBetweenMainAndFx, + /// Forwarded unit state event + /// + /// Not all unit events are forwarded, only those that might matter for other + /// units. + Unit { + unit_id: UnitId, + unit_event: UnitEvent, + }, + LastTouchedTargetChanged, +} + +#[derive(Debug)] +pub enum UnitOrchestrationEvent { + /// Sent by a ReaLearn instance X if it releases control over a source. + /// + /// This enables other instances to take over control of that source before X finally "switches + /// off lights". + SourceReleased(SourceReleasedEvent), + /// Whenever something about instance's device usage changes (either input or output or both + /// potentially change). + IoUpdated(IoUpdatedEvent), +} + +/// Communicates changes in which input and output device a ReaLearn instance uses or used. +#[derive(Debug)] +pub struct IoUpdatedEvent { + pub unit_id: UnitId, + pub control_input: Option, + pub control_input_used: bool, + pub feedback_output: Option, + pub feedback_output_used: bool, + pub feedback_output_usage_might_have_changed: bool, +} + +#[derive(Debug)] +pub struct SourceReleasedEvent { + pub unit_id: UnitId, + pub feedback_output: Option, + pub feedback_value: FinalSourceFeedbackValue, +} + +#[derive(Debug)] +pub struct BeatChangedEvent { + pub project: Project, + pub new_value: PositionInSeconds, +} + +#[derive(Debug)] +pub struct ActionInvokedEvent { + pub section_context: SectionContext<'static>, + pub command_id: CommandId, +} + +#[derive(Debug)] +pub struct FxSnapshotLoadedEvent { + pub fx: Fx, +} + +#[derive(Debug)] +pub struct RealearnMonitoringFxParameterValueChangedEvent { + pub parameter: FxParameter, + pub new_value: ReaperNormalizedFxParamValue, +} + +#[derive(Debug)] +pub struct ParameterAutomationTouchStateChangedEvent { + pub track: MediaTrack, + pub parameter_type: TouchedTrackParameterType, + pub new_value: bool, +} + +impl RealearnControlSurfaceMiddleware { + #[allow(clippy::too_many_arguments)] + pub fn new( + main_task_receiver: Receiver>, + instance_event_receiver: Receiver, + #[cfg(feature = "playtime")] clip_matrix_event_receiver: Receiver< + crate::domain::QualifiedClipMatrixEvent, + >, + additional_feedback_event_receiver: Receiver, + instance_orchestration_event_receiver: Receiver, + main_processors: SharedMainProcessors, + event_handler: Box, + ) -> Self { + let mut device_change_detector = MidiDeviceChangeDetector::new(); + // Prevent change messages to be sent on load by polling one time and ignoring result. + device_change_detector.poll_for_midi_input_device_changes(); + device_change_detector.poll_for_midi_output_device_changes(); + let (control_surface_event_sender, control_surface_event_receiver) = + SenderToNormalThread::new_unbounded_channel("control surface events"); + Self { + change_detection_middleware: ChangeDetectionMiddleware::new(), + change_event_queue: RefCell::new(Vec::with_capacity(100)), + monitoring_fx_chain_change_detector: Default::default(), + rx_middleware: ControlSurfaceRxMiddleware::new(Global::control_surface_rx().clone()), + instances: Default::default(), + main_processors, + main_task_receiver, + instance_event_receiver, + #[cfg(feature = "playtime")] + playtime: PlaytimeMiddleware { + clip_matrix_event_receiver, + }, + additional_feedback_event_receiver, + instance_orchestration_event_receiver, + main_task_middleware: Global::get().create_task_support_middleware(), + future_middleware: Global::get().create_future_support_middleware(), + counter: 0, + full_beats: Default::default(), + deprecated_fx_focus_state: Default::default(), + _modern_fx_focus_state: Default::default(), + target_capture_senders: Default::default(), + osc_capture_sender: None, + osc_input_devices: vec![], + device_change_detector, + reaper_config_change_detector: Default::default(), + control_surface_event_sender, + control_surface_event_receiver, + #[cfg(debug_assertions)] + last_undesired_allocation_count: 0, + event_handler, + osc_buffer: Default::default(), + } + } + + fn run_internal(&mut self) { + let timestamp = ControlEventTimestamp::from_main_thread(); + #[cfg(debug_assertions)] + { + // TODO-medium-playtime-refactoring This is propagated using main processors but it's a global event. We + // should use the global control surface event handler for this! Or ShutdownDetectionPanel? After all, + // this is not needed in the domain! But not urgent. It's enabled in dev builds only. + let current_undesired_allocation_count = + helgobox_allocator::undesired_allocation_count(); + if current_undesired_allocation_count != self.last_undesired_allocation_count { + self.last_undesired_allocation_count = current_undesired_allocation_count; + let event = &crate::domain::InternalInfoEvent::UndesiredAllocationCountChanged; + for p in self.main_processors.borrow_mut().iter_mut() { + p.process_info_event(event); + } + } + } + // Poll for change events that don't support notification. At the moment we execute this + // only once per run() invocation to save resources. As a consequence, we can't detect + // whether the changes were caused by ReaLearn targets or direct interaction with REAPER + // (e.g. via mouse). However, since change events detected by polling are currently not + // used for target learning / last touched target detection anyway, we don't need to know + // about their cause. + self.poll_for_more_change_events(); + // Process REAPER events that occurred since the last call of run(). All events + // accumulated up to this point are most likely *not* caused by ReaLearn, but by direct + // interaction with REAPER, e.g. changing track volume with the mouse. + // However, there's one exception: At least one ReaLearn instance might have input set to + // "Computer keyboard" and therefore process key strokes. In this case, the keyboard + // processing chain might have run before and caused ReaLearn to invoke some target. + // In this case, the backbone knows about it. + let realearn_was_matching_keyboard_input = + Backbone::get().reset_keyboard_input_match_flag(); + self.process_events(realearn_was_matching_keyboard_input); + // Execute operations scheduled by ReaLearn (in different ways) + self.main_task_middleware.run(); + self.future_middleware.run(); + self.rx_middleware.run(); + self.process_main_tasks(); + self.process_instance_orchestration_events(); + // Inform ReaLearn about various changes that are not relevant for target learning + self.detect_reaper_config_changes(); + self.emit_focus_switch_between_main_and_fx_as_feedback_event(); + self.emit_instance_events(); + self.emit_stream_deck_events(timestamp); + self.emit_beats_as_feedback_events(); + self.detect_device_changes(timestamp); + self.process_incoming_osc_messages(timestamp); + // Drive Playtime + #[cfg(feature = "playtime")] + { + self.poll_playtime(); + self.process_incoming_clip_matrix_events(); + } + // Finally let the ReaLearn main processors do their regular job (the instances) + self.run_main_processors(timestamp); + // Some control surface events might have been picked up during this call of run() while + // the change event queue was borrowed. Process them now (most likely they are turned into + // change events). + self.process_deferred_control_surface_events(); + // Process REAPER events that were accumulated within this call of run(). All events + // accumulated up to this point are caused by ReaLearn itself. + self.process_events(true); + self.counter += 1; + } + + pub fn remove_instance(&mut self, id: InstanceId) { + self.instances.remove(&id); + } + + pub fn remove_main_processor(&mut self, id: UnitId) -> anyhow::Result<()> { + self.main_processors + .try_borrow_mut()? + .retain(|p| p.unit_id() != id); + Ok(()) + } + + pub fn set_osc_input_devices(&mut self, devs: Vec) { + self.osc_input_devices = devs; + } + + pub fn shutdown(&self) { + for m in &mut *self.main_processors.borrow_mut() { + m.switch_lights_off(); + } + } + + pub fn clear_osc_input_devices(&mut self) { + self.osc_input_devices.clear(); + } + + /// Called when waking up ReaLearn (first instance appears again or the first time). + pub fn wake_up(&self) { + let mut change_events = vec![]; + self.change_detection_middleware.reset(|e| { + change_events.push(e); + }); + for i in self.instances() { + i.borrow_mut() + .process_control_surface_change_events(&change_events); + } + for m in &*self.main_processors.borrow() { + m.process_control_surface_change_events(&change_events); + } + for e in change_events { + self.rx_middleware.handle_change(e); + } + // We don't want to execute tasks which accumulated during the "downtime" of Reaper. + // So we just consume all without executing them. + self.main_task_middleware.reset(); + self.future_middleware.reset(); + } + + fn process_events(&mut self, caused_by_realearn: bool) { + self.process_change_events(caused_by_realearn); + self.process_incoming_additional_feedback(caused_by_realearn); + } + + fn process_change_events(&mut self, caused_by_realearn: bool) { + // Our goal is to always keep using the same change event queue vector so that we don't + // have to reallocate on each main loop cycle. That's also why we use `drain` further down. + // Previously, we just directly used the `change_event_queue` variable. However, an + // issue occurred: https://github.com/helgoboss/helgobox/issues/672#issuecomment-1246997201. + // A user got a panic while the main processors processed the change events ... which is not + // optimal but can happen in certain cases that are a topic for another day, see the + // comment. The issue we need to deal with here is another one: The same panic occurred + // until eternity because at the next main loop cycle the same event was still in the queue! + // That's exactly the reason why we now temporarily replace `change_event_queue` with an + // empty vector (doesn't require allocation) and replace it again with the old vector + // when everything run through successfully. To my knowledge, this is the only place where + // we need this logic because everywhere else we use channels, which have this logic baked + // in already ("pop instead of peek"). + // Long story short: The following line ensures we make a "pop" instead of a "peek" in order + // to avoid an infinite loop in case of a panic. + let mut normal_events = self.change_event_queue.replace(vec![]); + // Important because we deferred the change event handling, so it could be invalid now! + // See https://github.com/helgoboss/helgobox/issues/672. + normal_events.retain(|e| e.is_still_valid()); + let monitoring_fx_events = metrics_util::measure_time( + "helgobox.control_surface.detect_monitoring_fx_changes", + || self.monitoring_fx_chain_change_detector.poll_for_changes(), + ); + if normal_events.is_empty() && monitoring_fx_events.is_empty() { + return; + } + self.event_handler + .process_reaper_change_events(&normal_events); + for i in self.instances() { + i.borrow_mut() + .process_control_surface_change_events(&normal_events); + } + // This is for feedback processing. No Rx! + let main_processors = self.main_processors.borrow(); + for p in main_processors.iter() { + p.process_control_surface_change_events(&normal_events); + p.process_control_surface_change_events(&monitoring_fx_events); + } + // The rest is only for upper layers (e.g. UI), not for processing. + for e in normal_events + .drain(..) + .chain(monitoring_fx_events.into_iter()) + { + self.rx_middleware.handle_change(e.clone()); + if let Some(target) = ReaperTarget::touched_from_change_event(e) { + process_touched_target(target, caused_by_realearn, &self.target_capture_senders); + } + } + // Now that everything ran successfully, we can assign the old drained vector back to the + // original event queue. This ensures we can keep using the previous memory allocation. + *self.change_event_queue.borrow_mut() = normal_events; + } + + fn process_deferred_control_surface_events(&self) { + while let Ok(event) = self.control_surface_event_receiver.try_recv() { + let mut change_event_queue = self.change_event_queue.borrow_mut(); + self.handle_event_internal(&event, &mut change_event_queue); + } + } + + fn process_main_tasks(&mut self) { + for t in self + .main_task_receiver + .try_iter() + .take(CONTROL_SURFACE_MAIN_TASK_BULK_SIZE) + { + use RealearnControlSurfaceMainTask::*; + match t { + AddInstance(id, instance) => { + self.instances.insert(id, instance); + } + AddMainProcessor(p) => { + self.main_processors.borrow_mut().push(p); + } + RemoveMainProcessor(unit_id) => { + self.main_processors + .try_borrow_mut() + .expect("asynchronous removal of main processor also failed") + .retain(|p| p.unit_id() != unit_id); + } + StartCapturingTargets(instance_id, sender) => { + self.target_capture_senders.insert(instance_id, sender); + } + StopCapturingTargets(instance_id) => { + self.target_capture_senders.remove(&instance_id); + } + StartCapturingOsc(sender) => { + self.osc_capture_sender = Some(sender); + } + StopCapturingOsc => { + self.osc_capture_sender = None; + } + SendAllFeedback => { + for m in &*self.main_processors.borrow() { + m.send_all_feedback(); + } + } + } + } + } + + #[cfg(feature = "playtime")] + fn poll_playtime(&mut self) { + // Poll Playtime engine + playtime_clip_engine::PlaytimeEngine::get().poll(); + // Poll all Playtime matrixes + for instance in self.instances() { + let (instance_id, events) = { + let mut instance = instance.borrow_mut(); + (instance.id(), instance.poll_owned_clip_matrix()) + }; + for processor in &*self.main_processors.borrow() { + processor.process_polled_clip_matrix_events(instance_id, &events); + } + } + } + + fn instances(&self) -> impl Iterator + '_ { + self.instances.values().filter_map(|i| i.upgrade()) + } + + fn run_main_processors(&mut self, timestamp: ControlEventTimestamp) { + for p in &mut *self.main_processors.borrow_mut() { + p.run_essential(timestamp); + p.run_control(timestamp); + } + } + + fn process_incoming_additional_feedback(&mut self, caused_by_realearn: bool) { + for event in self + .additional_feedback_event_receiver + .try_iter() + .take(ADDITIONAL_FEEDBACK_EVENT_BULK_SIZE) + { + if let AdditionalFeedbackEvent::RealearnMonitoringFxParameterValueChanged(e) = &event { + let rx = Global::control_surface_rx(); + rx.fx_parameter_value_changed + .borrow_mut() + .next(e.parameter.clone()); + rx.fx_parameter_touched + .borrow_mut() + .next(e.parameter.clone()); + } + for p in &mut *self.main_processors.borrow_mut() { + p.process_additional_feedback_event(&event) + } + if let Some(target) = ReaperTarget::touched_from_additional_event(&event) { + process_touched_target(target, caused_by_realearn, &self.target_capture_senders); + } + } + } + + #[cfg(feature = "playtime")] + fn process_incoming_clip_matrix_events(&mut self) { + for event in self.playtime.clip_matrix_event_receiver.try_iter().take(30) { + for i in self.instances() { + i.borrow().process_non_polled_clip_matrix_event(&event); + } + for p in &mut *self.main_processors.borrow_mut() { + p.process_non_polled_clip_matrix_event(&event); + } + } + } + + fn process_instance_orchestration_events(&mut self) { + for event in self + .instance_orchestration_event_receiver + .try_iter() + .take(INSTANCE_ORCHESTRATION_EVENT_BULK_SIZE) + { + use UnitOrchestrationEvent::*; + match event { + SourceReleased(e) => { + debug!("Source of unit {} released", e.unit_id); + // We also allow the instance to take over which released the source in + // the first place! Simply because in the meanwhile, this instance + // could have found a new usage for it! E.g. likely to happen with + // preset changes. + let other_instance_took_over = self + .main_processors + .borrow() + .iter() + .any(|p| p.maybe_takeover_source(&e)); + if !other_instance_took_over { + if let Some(p) = self + .main_processors + .borrow() + .iter() + .find(|p| p.unit_id() == e.unit_id) + { + // Finally safe to switch off lights! + p.finally_switch_off_source(e.feedback_output, e.feedback_value); + } + } + } + IoUpdated(e) => { + let backbone_state = Backbone::get(); + let feedback_dev_usage_changed = backbone_state.update_io_usage( + &e.unit_id, + if e.control_input_used { + e.control_input + } else { + None + }, + if e.feedback_output_used { + e.feedback_output + } else { + None + }, + ); + if feedback_dev_usage_changed && backbone_state.is_superior(&e.unit_id) { + debug!( + "Superior unit {} {} feedback output", + e.unit_id, + if e.feedback_output_used { + "claimed" + } else { + "released" + } + ); + if let Some(feedback_output) = e.feedback_output { + // Give inferior instances the chance to cancel or reactivate. + self.main_processors + .borrow() + .iter() + .filter(|p| p.unit_id() != e.unit_id) + .for_each(|p| { + p.handle_change_of_some_upper_floor_instance(feedback_output) + }); + } + } + } + } + } + } + + fn detect_reaper_config_changes(&mut self) { + let changes = self.reaper_config_change_detector.poll_for_changes(); + for p in &*self.main_processors.borrow() { + p.process_reaper_config_changes(&changes); + } + } + + fn emit_focus_switch_between_main_and_fx_as_feedback_event(&mut self) { + let pointers = Reaper::get().medium_reaper().low().pointers(); + // TODO The modern GetTouchedOrFocusedFX way needs more testing. I tried to use it hoping it would fix + // https://github.com/helgoboss/helgobox/issues/1367 but it essentially behaves the same + // as GetFocusedFX2, so there's no urge to migrate. + // let fire = if pointers.GetTouchedOrFocusedFX.is_some() { + // // The latest and greatest way to detect focused FX + // self.detect_focus_switch_between_main_and_fx_as_feedback_event_modern() + // } else + let fire = if pointers.GetFocusedFX2.is_some() { + // The deprecated way to detect focused FX + self.detect_focus_switch_between_main_and_fx_as_feedback_event_deprecated() + } else { + // Detection of unfocusing FX (without focusing a new one) not supported in REAPER versions < 7 + false + }; + if fire { + let event = AdditionalFeedbackEvent::FocusSwitchedBetweenMainAndFx; + for p in &mut *self.main_processors.borrow_mut() { + p.process_additional_feedback_event(&event); + } + } + } + + #[allow(unused)] + fn detect_focus_switch_between_main_and_fx_as_feedback_event_modern(&mut self) -> bool { + let reaper = Reaper::get().medium_reaper(); + let new = reaper.get_touched_or_focused_fx_currently_focused_fx(); + let last = mem::replace(&mut self._modern_fx_focus_state, new); + match (last, new) { + (None, None) => { + // This happens continuously before any FX is focused + false + } + (None, Some(_)) => { + // The first time an FX is focused + true + } + (Some(_), None) => { + // Shouldn't happen because REAPER usually doesn't clear the last-focused FX. But if it happens, + // we should fire. + true + } + (Some(last), Some(new)) => { + // Only fire if we changed from "no FX focused" to "FX focused" or vice versa (not when focusing + // between FX ... this is detected better by REAPER's built-in control surface FxFocused event, + // mainly because the latter doesn't fire when an FX is removed - which would be too much) + last.is_still_focused != new.is_still_focused + } + } + } + + #[allow(deprecated)] + fn detect_focus_switch_between_main_and_fx_as_feedback_event_deprecated(&mut self) -> bool { + let reaper = Reaper::get().medium_reaper(); + let new = reaper.get_focused_fx_2(); + let last = mem::replace(&mut self.deprecated_fx_focus_state, new); + match (last, new) { + (None, None) => { + // This happens continuously before any FX is focused + false + } + (None, Some(_)) => { + // The first time an FX is focused + true + } + (Some(_), None) => { + // Shouldn't happen because REAPER usually doesn't clear the last-focused FX. But if it happens, + // we should fire. + true + } + (Some(last), Some(new)) => { + // Only fire if we changed from "no FX focused" to "FX focused" or vice versa (not when focusing + // between FX ... this is detected better by REAPER's built-in control surface FxFocused event, + // mainly because the latter doesn't fire when an FX is removed - which would be too much) + last.is_still_focused != new.is_still_focused + } + } + } + + fn emit_instance_events(&mut self) { + for event in self + .instance_event_receiver + .try_iter() + .take(INSTANCE_EVENT_BULK_SIZE) + { + for p in &mut *self.main_processors.borrow_mut() { + p.process_instance_event_for_feedback(&event) + } + } + } + + fn emit_stream_deck_events(&mut self, timestamp: ControlEventTimestamp) { + let backbone = Backbone::get(); + for msg in backbone.poll_stream_deck_messages() { + for p in &mut *self.main_processors.borrow_mut() { + if !p.wants_stream_deck_input_from(msg.dev_id) { + continue; + } + let event = ControlEvent::new(msg.msg, timestamp); + p.process_incoming_stream_deck_msg(event); + } + } + } + + fn emit_beats_as_feedback_events(&mut self) { + for project in Reaper::get().projects() { + let reference_pos = if project.is_playing() { + project.play_position_latency_compensated() + } else { + project.edit_cursor_position().unwrap_or_default() + }; + if self.record_possible_beat_change(project, reference_pos) { + let event = AdditionalFeedbackEvent::BeatChanged(BeatChangedEvent { + project, + new_value: reference_pos, + }); + for p in &mut *self.main_processors.borrow_mut() { + p.process_additional_feedback_event(&event); + } + } + } + } + + fn detect_device_changes(&mut self, timestamp: ControlEventTimestamp) { + // Check roughly every 2 seconds + if self.counter % (30 * 2) != 0 { + return; + } + // Stream deck + let added_stream_deck_device_ids = Backbone::get().detect_stream_deck_device_changes(); + // MIDI + let midi_in_diff = self + .device_change_detector + .poll_for_midi_input_device_changes(); + let midi_out_diff = self + .device_change_detector + .poll_for_midi_output_device_changes(); + // Resetting MIDI devices is necessary especially on Windows. + reset_midi_devices( + midi_in_diff.added_devices.iter().copied(), + midi_out_diff.added_devices.iter().copied(), + ); + // Pass events to event handler + if midi_in_diff.devices_changed() || midi_in_diff.device_config_changed { + self.event_handler + .midi_input_devices_changed(&midi_in_diff, midi_in_diff.device_config_changed); + } + if midi_out_diff.devices_changed() || midi_out_diff.device_config_changed { + self.event_handler + .midi_output_devices_changed(&midi_out_diff, midi_out_diff.device_config_changed); + } + // Emit as REAPER source messages + let mut msg = Vec::with_capacity(2); + if !midi_in_diff.added_devices.is_empty() || !midi_out_diff.added_devices.is_empty() { + let payload = MidiDeviceChangePayload { + input_devices: midi_in_diff.added_devices, + output_devices: midi_out_diff.added_devices, + }; + msg.push(ReaperMessage::MidiDevicesConnected(payload)); + } + if !midi_in_diff.removed_devices.is_empty() || !midi_out_diff.removed_devices.is_empty() { + let payload = MidiDeviceChangePayload { + input_devices: midi_in_diff.removed_devices, + output_devices: midi_out_diff.removed_devices, + }; + msg.push(ReaperMessage::MidiDevicesDisconnected(payload)); + } + if !added_stream_deck_device_ids.is_empty() { + msg.push(ReaperMessage::StreamDeckDevicesConnected( + StreamDeckDevicePayload { + devices: added_stream_deck_device_ids, + }, + )); + } + // Inform main processors + for p in &mut *self.main_processors.borrow_mut() { + for msg in &msg { + let evt = ControlEvent::new(msg, timestamp); + p.process_reaper_message(evt); + } + } + } + + fn process_incoming_osc_messages(&mut self, timestamp: ControlEventTimestamp) { + for dev in &mut self.osc_input_devices { + self.osc_buffer.clear(); + self.osc_buffer + .extend(dev.poll_multiple(OSC_INCOMING_BULK_SIZE)); + for proc in &mut *self.main_processors.borrow_mut() { + if proc.wants_osc_from(dev.id()) { + for packet in &self.osc_buffer { + let evt = ControlEvent::new(packet, timestamp); + proc.process_incoming_osc_packet(evt); + } + } + } + if let Some(sender) = &self.osc_capture_sender { + for packet in self.osc_buffer.drain(..) { + process_incoming_osc_packet_for_learning(*dev.id(), sender, packet) + } + } + } + } + + fn handle_event_internal( + &self, + event: &ControlSurfaceEvent, + change_event_queue: &mut Vec, + ) -> bool { + // We always need to forward to the change detection middleware even if we are in + // a mode in which the detected change event doesn't matter! + self.change_detection_middleware.process(event, |e| { + // Notify backbone whenever focused FX changes + if let ChangeEvent::FxFocused(evt) = &e { + Backbone::get().notify_fx_focused(evt.fx.clone()); + } + // We don't process change events immediately in order to be able to process + // multiple events occurring in one main loop cycle as a natural batch. This + // is important for performance reasons + // (see https://github.com/helgoboss/helgobox/issues/553). + change_event_queue.push(e); + }) + } + + fn record_possible_beat_change( + &mut self, + project: Project, + reference_pos: PositionInSeconds, + ) -> bool { + let beat_info = project.beat_info_at(reference_pos); + let new_full_beats = beat_info.full_beats.get() as _; + let full_beats = self.full_beats.entry(project.raw()).or_default(); + let beat_changed = new_full_beats != *full_beats; + *full_beats = new_full_beats; + beat_changed + } + + fn poll_for_more_change_events(&mut self) { + let mut change_event_queue = self.change_event_queue.borrow_mut(); + measure_time( + "helgobox.control_surface.poll_for_more_change_events", + || { + self.change_detection_middleware.run(&mut |change_event| { + change_event_queue.push(change_event); + }); + }, + ); + } +} + +impl ControlSurfaceMiddleware for RealearnControlSurfaceMiddleware { + fn run(&mut self) { + // Already-borrowed / reentrancy check + if self.main_processors.try_borrow_mut().is_err() { + // Main processors area already borrowed! That's possible in some rare cases. + // In any case, we need to skip processing. Otherwise, mutable borrow panics would occur! + // + // Example: + // - Preferences => Audio => Recording: Set "Prompt to save/delete/rename new files" to "on stop" + // - Add mapping that maps key "S" to transport stop action + // - Start playback + // - User presses key "S" + // - RealearnAccelerator borrows the main processors mutably to process the "S" key press to + // - Mapping triggers transport stop + // - REAPER shows the modal dialog (prompt) + // - The main loop continues running on top of the function stack, continuously invoking the run() function + // - BOOM + tracing::warn!("Main processors borrowed already. Skipping processing."); + return; + } + // Run + measure_time("helgobox.control_surface.run", || { + self.run_internal(); + }); + } + + fn handle_event(&self, event: ControlSurfaceEvent) -> bool { + // TODO-high-playtime-refactoring We should do this in reaper-medium (in a more generic way) as soon as it turns + // out to work nicely. Related to this: https://github.com/helgoboss/reaper-rs/issues/54 + match self.change_event_queue.try_borrow_mut() { + Ok(mut queue) => self.handle_event_internal(&event, &mut queue), + Err(_) => { + // When we can't borrow the control surface event queue because of reentrancy, + // we need to defer its processing. + self.control_surface_event_sender + .send_complaining(event.clone().into_owned()); + false + } + } + } + + fn get_touch_state(&self, args: GetTouchStateArgs) -> bool { + if let Ok(domain_type) = TouchedTrackParameterType::try_from_reaper(args.parameter_type) { + Backbone::target_state() + .borrow() + .automation_parameter_is_touched(args.track, domain_type) + } else { + false + } + } + + fn ext_supports_extended_touch(&self, _: ExtSupportsExtendedTouchArgs) -> i32 { + 1 + } +} + +fn process_incoming_osc_packet_for_learning( + dev_id: OscDeviceId, + sender: &OscCaptureSender, + packet: OscPacket, +) { + match packet { + OscPacket::Message(msg) => process_incoming_osc_message_for_learning(dev_id, sender, msg), + OscPacket::Bundle(bundle) => { + for p in bundle.content.into_iter() { + process_incoming_osc_packet_for_learning(dev_id, sender, p); + } + } + } +} + +fn process_incoming_osc_message_for_learning( + dev_id: OscDeviceId, + sender: &OscCaptureSender, + message: OscMessage, +) { + let scan_result = OscScanResult { + message, + dev_id: Some(dev_id), + }; + let _ = sender.try_send(scan_result); +} + +fn reset_midi_devices( + in_devs: impl Iterator, + out_devs: impl Iterator, +) { + let reaper_low = Reaper::get().medium_reaper().low(); + if reaper_low.pointers().midi_init.is_none() { + // REAPER version < 6.47 + return; + } + for res in in_devs.zip_longest(out_devs) { + let (input_arg, output_arg) = match res { + EitherOrBoth::Both(i, o) => (i.get() as i32, o.get() as i32), + EitherOrBoth::Left(i) => (i.get() as i32, -1), + EitherOrBoth::Right(o) => (-1, o.get() as i32), + }; + reaper_low.midi_init(input_arg, output_arg); + } +} + +fn process_touched_target( + target: ReaperTarget, + caused_by_realearn: bool, + target_capture_senders: &NonCryptoHashMap, TargetCaptureSender>, +) { + let touch_event = TargetTouchEvent { + target, + caused_by_realearn, + }; + for sender in target_capture_senders.values() { + let _ = sender.try_send(touch_event.clone()); + } + Backbone::get().notify_target_touched(touch_event); +} diff --git a/plugin-reaper-realearn/main/src/domain/device_change_detector.rs b/plugin-reaper-realearn/main/src/domain/device_change_detector.rs new file mode 100644 index 0000000..dc41bc7 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/device_change_detector.rs @@ -0,0 +1,78 @@ +use crate::domain::{MidiInDevsConfig, MidiOutDevsConfig}; +use base::hash_util::NonCryptoHashSet; +use reaper_high::Reaper; +use reaper_medium::{MidiInputDeviceId, MidiOutputDeviceId}; +use std::hash::Hash; + +#[derive(Debug, Default)] +pub struct MidiDeviceChangeDetector { + old_connected_in_devs: NonCryptoHashSet, + old_in_config: MidiInDevsConfig, + old_connected_out_devs: NonCryptoHashSet, + old_out_config: MidiOutDevsConfig, +} + +impl MidiDeviceChangeDetector { + pub fn new() -> Self { + Default::default() + } + + pub fn poll_for_midi_input_device_changes(&mut self) -> DeviceDiff { + let new_connected_devs: NonCryptoHashSet<_> = Reaper::get() + .midi_input_devices() + .filter(|d| d.is_connected()) + .map(|d| d.id()) + .collect(); + let new_in_config = MidiInDevsConfig::from_reaper(); + let diff = DeviceDiff::new( + &self.old_connected_in_devs, + &new_connected_devs, + new_in_config != self.old_in_config, + ); + self.old_connected_in_devs = new_connected_devs; + self.old_in_config = new_in_config; + diff + } + + pub fn poll_for_midi_output_device_changes(&mut self) -> DeviceDiff { + let new_connected_devs: NonCryptoHashSet<_> = Reaper::get() + .midi_output_devices() + .filter(|d| d.is_connected()) + .map(|d| d.id()) + .collect(); + let new_out_config = MidiOutDevsConfig::from_reaper(); + let diff = DeviceDiff::new( + &self.old_connected_out_devs, + &new_connected_devs, + new_out_config != self.old_out_config, + ); + self.old_connected_out_devs = new_connected_devs; + self.old_out_config = new_out_config; + diff + } +} + +#[derive(Clone, Debug)] +pub struct DeviceDiff { + pub added_devices: NonCryptoHashSet, + pub removed_devices: NonCryptoHashSet, + pub device_config_changed: bool, +} + +impl DeviceDiff { + fn new( + old_devs: &NonCryptoHashSet, + new_devs: &NonCryptoHashSet, + device_config_changed: bool, + ) -> Self { + Self { + added_devices: new_devs.difference(old_devs).copied().collect(), + removed_devices: old_devs.difference(new_devs).copied().collect(), + device_config_changed, + } + } + + pub fn devices_changed(&self) -> bool { + !self.added_devices.is_empty() || !self.removed_devices.is_empty() + } +} diff --git a/plugin-reaper-realearn/main/src/domain/eel_midi_source_script.rs b/plugin-reaper-realearn/main/src/domain/eel_midi_source_script.rs new file mode 100644 index 0000000..b028b88 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/eel_midi_source_script.rs @@ -0,0 +1,134 @@ +use crate::base::eel; +use helgoboss_learn::{ + create_raw_midi_events_singleton, AbsoluteValue, FeedbackValue, MidiSourceAddress, + MidiSourceScript, MidiSourceScriptOutcome, RawMidiEvent, +}; +use std::borrow::Cow; + +#[derive(Debug)] +struct EelUnit { + // Declared above VM in order to be dropped before VM is dropped. + program: eel::Program, + vm: eel::Vm, + y: eel::Variable, + msg_size: eel::Variable, + address: eel::Variable, +} + +#[derive(Debug)] +pub struct EelMidiSourceScript { + // Arc because EelUnit is not cloneable + eel_unit: EelUnit, +} + +impl EelMidiSourceScript { + pub fn compile(eel_script: &str) -> Result { + if eel_script.trim().is_empty() { + return Err("script empty".to_string()); + } + let vm = eel::Vm::new(); + let program = vm.compile(eel_script)?; + let y = vm.register_variable("y"); + let msg_size = vm.register_variable("msg_size"); + let address = vm.register_variable("address"); + let eel_unit = EelUnit { + program, + vm, + y, + msg_size, + address, + }; + Ok(Self { eel_unit }) + } +} + +impl MidiSourceScript<'_> for EelMidiSourceScript { + type AdditionalInput = (); + + fn execute( + &self, + input_value: FeedbackValue, + _additional_input: (), + ) -> Result> { + let y_value = match input_value { + // TODO-medium Find a constant for this which is defined in EEL + FeedbackValue::Off => f64::MIN, + FeedbackValue::Numeric(v) => match v.value { + AbsoluteValue::Continuous(v) => v.get(), + AbsoluteValue::Discrete(f) => f.actual() as f64, + }, + // Rest not supported for EEL + _ => f64::MIN, + }; + let (slice, address) = unsafe { + self.eel_unit.y.set(y_value); + self.eel_unit.msg_size.set(0.0); + self.eel_unit.address.set(0.0); + self.eel_unit.program.execute(); + let msg_size = self.eel_unit.msg_size.get().round() as i32; + if msg_size < 0 { + return Err("invalid message size".into()); + }; + let slice = self.eel_unit.vm.get_mem_slice(0, msg_size as u32); + let address = self.eel_unit.address.get(); + (slice, address) + }; + if slice.is_empty() { + return Err("empty message".into()); + } + let mut array = [0; RawMidiEvent::MAX_LENGTH]; + let mut i = 0u32; + for byte in slice.iter().take(RawMidiEvent::MAX_LENGTH) { + array[i as usize] = byte.round() as u8; + i += 1; + } + let raw_midi_event = RawMidiEvent::new(0, i, array); + let outcome = MidiSourceScriptOutcome { + address: if address == 0.0 { + None + } else { + Some(MidiSourceAddress::Script { + bytes: address as u64, + }) + }, + events: create_raw_midi_events_singleton(raw_midi_event), + }; + Ok(outcome) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use helgoboss_learn::{FeedbackStyle, NumericFeedbackValue, UnitValue}; + + #[test] + fn basics() { + // Given + let text = " + address = 0x4bb0; + msg_size = 3; + 0[] = 0xb0; + 1[] = 0x4b; + 2[] = y * 10; + "; + let script = EelMidiSourceScript::compile(text).unwrap(); + // When + let fb_value = NumericFeedbackValue::new( + FeedbackStyle::default(), + AbsoluteValue::Continuous(UnitValue::new(0.5)), + ); + let outcome = script + .execute(FeedbackValue::Numeric(fb_value), ()) + .unwrap(); + // Then + assert_eq!( + outcome.address, + Some(MidiSourceAddress::Script { bytes: 0x4bb0 }) + ); + assert_eq!( + outcome.events, + vec![RawMidiEvent::try_from_slice(0, &[0xb0, 0x4b, 5]).unwrap()] + ); + } +} diff --git a/plugin-reaper-realearn/main/src/domain/eel_transformation.rs b/plugin-reaper-realearn/main/src/domain/eel_transformation.rs new file mode 100644 index 0000000..9b8061d --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/eel_transformation.rs @@ -0,0 +1,312 @@ +use crate::base::eel; +use helgoboss_learn::{ + ControlValueKind, Transformation, TransformationInput, TransformationInstruction, + TransformationOutput, +}; +use std::os::raw::c_void; + +use atomic::Atomic; +use reaper_medium::reaper_str; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +#[derive(Default)] +pub struct AdditionalTransformationInput { + pub y_last: f64, +} + +#[derive(Debug)] +struct EelUnit { + // Declared above VM in order to be dropped before VM is dropped. + program: eel::Program, + // The existence in memory and the Drop is important. + _vm: eel::Vm, + _stop: eel::Variable, + _none: eel::Variable, + x: eel::Variable, + y: eel::Variable, + y_last: eel::Variable, + y_type: eel::Variable, + last_feedback_value: eel::Variable, + timestamp: eel::Variable, + rel_time: Option, +} + +#[derive(Clone, Debug)] +pub enum OutputVariable { + X, + Y, +} + +pub trait Script { + fn uses_time(&self) -> bool; + + fn produces_relative_values(&self) -> bool; + + fn evaluate( + &self, + input: TransformationInput, + ) -> Result; +} + +impl Script for () { + fn uses_time(&self) -> bool { + false + } + + fn produces_relative_values(&self) -> bool { + false + } + + fn evaluate( + &self, + input: TransformationInput, + ) -> Result { + let _ = input; + Err("not supported") + } +} + +/// Represents a value transformation done via EEL scripting language. +#[derive(Clone, Debug)] +pub struct EelTransformation { + // Arc because EelUnit is not cloneable + eel_unit: Arc, + shared_last_feedback_value: Arc>, + output_var: OutputVariable, + wants_to_be_polled: bool, +} + +impl Script for EelTransformation { + fn uses_time(&self) -> bool { + self.wants_to_be_polled() + } + + fn produces_relative_values(&self) -> bool { + let input = TransformationInput::default(); + let Ok(output) = self.transform(input) else { + return false; + }; + // For now, we only support relative-discrete + output.produced_kind == ControlValueKind::RelativeDiscrete + } + + fn evaluate( + &self, + input: TransformationInput, + ) -> Result { + self.transform(input) + } +} + +impl EelTransformation { + pub fn compile_for_control(eel_script: &str) -> Result { + EelTransformation::compile(eel_script, OutputVariable::Y) + } + + pub fn compile_for_feedback(eel_script: &str) -> Result { + EelTransformation::compile(eel_script, OutputVariable::X) + } + + pub fn set_last_feedback_value(&self, value: f64) { + self.shared_last_feedback_value + .store(value, Ordering::SeqCst); + } + + // Compiles the given script and creates an appropriate transformation. + fn compile(eel_script: &str, result_var: OutputVariable) -> Result { + if eel_script.trim().is_empty() { + return Err("script empty".to_string()); + } + let mut vm = eel::Vm::new(); + vm.register_single_arg_function(reaper_str!("stop"), stop); + vm.register_void_or_bool_function(reaper_str!("realearn_dbg"), realearn_dbg); + let program = vm.compile(eel_script)?; + let x = vm.register_variable("x"); + let y = vm.register_variable("y"); + let y_last = vm.register_variable("y_last"); + let y_type = vm.register_variable("y_type"); + let last_feedback_value = vm.register_variable("realearn_last_feedback_value"); + let rel_time_var_name = "rel_time"; + let uses_rel_time = eel_script.contains(rel_time_var_name); + let timestamp = vm.register_variable("realearn_timestamp"); + let rel_time = if uses_rel_time { + Some(vm.register_variable(rel_time_var_name)) + } else { + None + }; + let eel_unit = EelUnit { + program, + _stop: vm.register_and_set_variable("stop", STOP), + _none: vm.register_and_set_variable("none", NONE), + _vm: vm, + x, + y, + y_last, + y_type, + last_feedback_value, + timestamp, + rel_time, + }; + let transformation = EelTransformation { + eel_unit: Arc::new(eel_unit), + shared_last_feedback_value: Arc::new(Atomic::new(-1.0)), + output_var: result_var, + wants_to_be_polled: uses_rel_time, + }; + Ok(transformation) + } +} + +unsafe extern "C" fn stop(_: *mut c_void, amt: *mut f64) -> f64 { + CONTROL_AND_STOP_MAGIC + (*amt).clamp(0.0, 1.0) +} + +unsafe extern "C" fn realearn_dbg(_: *mut c_void, amt: *mut f64) -> bool { + println!("{}", *amt); + true +} + +impl Transformation for EelTransformation { + type AdditionalInput = AdditionalTransformationInput; + + fn transform( + &self, + input: TransformationInput, + ) -> Result { + let (raw_output, raw_output_type) = unsafe { + use OutputVariable::*; + let eel_unit = &*self.eel_unit; + let (input_var, output_var) = match self.output_var { + X => (eel_unit.y, eel_unit.x), + Y => (eel_unit.x, eel_unit.y), + }; + input_var.set(input.event.input_value); + output_var.set(input.context.output_value); + eel_unit + .last_feedback_value + .set(self.shared_last_feedback_value.load(Ordering::SeqCst)); + eel_unit.y_last.set(input.additional_input.y_last); + eel_unit.timestamp.set(input.event.timestamp.as_secs_f64()); + if let Some(rel_time_var) = eel_unit.rel_time { + rel_time_var.set(input.context.rel_time.as_millis() as _); + } + eel_unit.program.execute(); + (output_var.get(), self.eel_unit.y_type.get()) + }; + let (out_val, instruction) = if raw_output == STOP { + // Stop only + (None, Some(TransformationInstruction::Stop)) + } else if raw_output == NONE { + // Neither control nor stop + (None, None) + } else if (CONTROL_AND_STOP_MAGIC..=CONTROL_AND_STOP_MAGIC + 1.0).contains(&raw_output) { + // Both control and stop + ( + Some(raw_output - CONTROL_AND_STOP_MAGIC), + Some(TransformationInstruction::Stop), + ) + } else { + // Control only + (Some(raw_output), None) + }; + let raw_output_type = raw_output_type.round() as u8; + let produced_kind = ControlValueKind::try_from(raw_output_type).unwrap_or_default(); + let output = TransformationOutput { + produced_kind, + value: out_val, + instruction, + }; + Ok(output) + } + + fn wants_to_be_polled(&self) -> bool { + self.wants_to_be_polled + } +} + +/// Exposed as variable `stop`. +const STOP: f64 = f64::MAX; +/// Exposed as variable `none`. +const NONE: f64 = f64::MIN; +/// Not exposed but used internally when using function `stop`, e.g. `stop(0.5)`. +/// +/// Since all we can do at the moment is returning one number, we define a magic number. +/// If the returned value is at a maximum 1.0 greater than that magic number, we interpret that +/// as stop instruction and extract the corresponding number! +/// +/// It's good that this is encapsulated in a function. Maybe we can improve the behavior in future +/// by setting an extra output variable in the implementation of our `stop` function. +const CONTROL_AND_STOP_MAGIC: f64 = 8965019.0; + +#[cfg(test)] +mod tests { + use super::*; + use bytesize::ByteSize; + use helgoboss_learn::TransformationInputEvent; + use sysinfo::ProcessRefreshKind; + + #[test] + fn memory_usage() { + let mut system = sysinfo::System::new(); + let current_pid = sysinfo::get_current_pid().unwrap(); + system.refresh_process_specifics(current_pid, ProcessRefreshKind::new().with_memory()); + let mut last_memory = 0; + let mut print_mem = move || { + system.refresh_process_specifics(current_pid, ProcessRefreshKind::new().with_memory()); + let process = system.process(current_pid).unwrap(); + let memory = process.memory(); + let diff = memory as i64 - last_memory as i64; + let suffix = if diff.is_negative() { "-" } else { "+" }; + println!( + "Memory changed by {suffix}{}. Total memory usage so far: {} bytes", + ByteSize::b(diff.unsigned_abs()), + ByteSize::b(memory) + ); + last_memory = memory; + }; + let mut total_count = 0; + let mut create_transformations = |count| { + total_count += count; + let transformations = create_transformations(count); + println!("Created {count} more transformation units. Total amount of units created so far: {total_count}"); + print_mem(); + transformations + }; + let mut transformation_containers = vec![ + create_transformations(1), + create_transformations(1), + create_transformations(1), + create_transformations(1), + create_transformations(1), + create_transformations(1), + create_transformations(1), + create_transformations(100), + ]; + println!("Now dropping from last to first..."); + while transformation_containers.pop().is_some() { + println!("Dropped one set of transformations"); + print_mem(); + } + println!("No transformation sets left"); + print_mem(); + } + + fn create_transformations(count: usize) -> Vec { + (0..count) + .map(|i| { + let code = format!("y = x * {i}"); + let transformation = EelTransformation::compile_for_control(&code).unwrap(); + let input = TransformationInput { + event: TransformationInputEvent { + input_value: 0.5, + ..Default::default() + }, + ..Default::default() + }; + transformation.transform(input).unwrap(); + transformation + }) + .collect() + } +} diff --git a/plugin-reaper-realearn/main/src/domain/eventing.rs b/plugin-reaper-realearn/main/src/domain/eventing.rs new file mode 100644 index 0000000..59ad1b7 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/eventing.rs @@ -0,0 +1,138 @@ +use crate::domain::{ + CompartmentKind, CompoundMappingTarget, ControlLogContext, ControlLogEntry, FeedbackLogEntry, + InternalInfoEvent, MappingId, MessageCaptureResult, PluginParamIndex, PluginParams, + ProjectionFeedbackValue, QualifiedMappingId, RawParamValue, +}; +use base::hash_util::NonCryptoHashSet; +use helgoboss_learn::{AbsoluteValue, ControlValue}; +use helgobox_api::persistence::MappingModification; +use std::error::Error; +use std::fmt::Debug; + +/// An event which is sent to upper layers and processed there +#[derive(Debug)] +pub enum DomainEvent<'a> { + CapturedIncomingMessage(MessageCaptureEvent), + GlobalControlAndFeedbackStateChanged(GlobalControlAndFeedbackState), + UpdatedOnMappings(NonCryptoHashSet), + UpdatedSingleMappingOnState(UpdatedSingleMappingOnStateEvent), + UpdatedSingleParameterValue { + index: PluginParamIndex, + value: RawParamValue, + }, + UpdatedAllParameters(PluginParams), + TargetValueChanged(TargetValueChangedEvent<'a>), + Info(&'a InternalInfoEvent), + ProjectionFeedback(ProjectionFeedbackValue), + MappingMatched(MappingMatchedEvent), + HandleTargetControl(TargetControlEvent), + HandleSourceFeedback(SourceFeedbackEvent<'a>), + FullResyncRequested, + MidiDevicesChanged, + MappingEnabledChangeRequested(MappingEnabledChangeRequestedEvent), + MappingModificationRequested(MappingModificationRequestedEvent), + TimeForCelebratingSuccess, + ConditionsChanged, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct GlobalControlAndFeedbackState { + pub control_active: bool, + pub feedback_active: bool, +} + +#[derive(Clone, Debug)] +pub struct MessageCaptureEvent { + pub result: MessageCaptureResult, + pub allow_virtual_sources: bool, + pub osc_arg_index_hint: Option, +} + +#[derive(Copy, Clone, Debug)] +pub struct UpdatedSingleMappingOnStateEvent { + pub id: QualifiedMappingId, + pub is_on: bool, +} + +#[derive(Copy, Clone, Debug)] +pub struct MappingEnabledChangeRequestedEvent { + pub compartment: CompartmentKind, + pub mapping_id: MappingId, + pub is_enabled: bool, +} + +#[derive(Clone, Debug)] +pub struct MappingModificationRequestedEvent { + pub compartment: CompartmentKind, + pub mapping_id: MappingId, + pub modification: MappingModification, + pub value: ControlValue, +} + +#[derive(Copy, Clone, Debug)] +pub struct MappingMatchedEvent { + pub compartment: CompartmentKind, + pub mapping_id: MappingId, +} + +impl MappingMatchedEvent { + pub fn new(compartment: CompartmentKind, mapping_id: MappingId) -> Self { + MappingMatchedEvent { + compartment, + mapping_id, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct TargetControlEvent { + pub id: QualifiedMappingId, + pub log_context: ControlLogContext, + pub log_entry: ControlLogEntry, +} + +#[derive(Copy, Clone, Debug)] +pub struct SourceFeedbackEvent<'a> { + pub id: QualifiedMappingId, + pub log_entry: FeedbackLogEntry<'a>, +} + +impl TargetControlEvent { + pub fn new( + id: QualifiedMappingId, + log_context: ControlLogContext, + log_entry: ControlLogEntry, + ) -> Self { + Self { + id, + log_context, + log_entry, + } + } +} + +#[derive(Debug)] +pub struct TargetValueChangedEvent<'a> { + pub compartment: CompartmentKind, + pub mapping_id: MappingId, + pub targets: &'a [CompoundMappingTarget], + pub new_value: AbsoluteValue, +} + +pub trait DomainEventHandler: Debug { + fn handle_event_ignoring_error(&self, event: DomainEvent) { + let _ = self.handle_event(event); + } + + fn handle_event(&self, event: DomainEvent) -> Result<(), Box>; + + fn notify_mapping_matched(&self, compartment: CompartmentKind, mapping_id: MappingId) { + self.handle_event_ignoring_error(DomainEvent::MappingMatched(MappingMatchedEvent::new( + compartment, + mapping_id, + ))); + } + + /// Returns `true` if another preset is being loaded. + fn auto_load_different_preset_if_necessary(&self) -> anyhow::Result; +} diff --git a/plugin-reaper-realearn/main/src/domain/exclusivity.rs b/plugin-reaper-realearn/main/src/domain/exclusivity.rs new file mode 100644 index 0000000..e73633a --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/exclusivity.rs @@ -0,0 +1,359 @@ +use crate::domain::TrackExclusivity; + +pub trait HierarchyEntryProvider { + type Entry; + + fn find_entry_by_index(&self, index: u32) -> Option; + fn entry_count(&self) -> u32; +} + +pub trait HierarchyEntry: PartialEq { + fn folder_depth_change(&self) -> i32; +} + +pub fn handle_exclusivity( + provider: &impl HierarchyEntryProvider, + exclusivity: TrackExclusivity, + current_index: Option, + current_entry: &E, + mut apply: impl FnMut(u32, &E), +) { + let current_index = match current_index { + // We consider the master track as its own folder (same as non-exclusive). + None => return, + Some(i) => i, + }; + use TrackExclusivity::*; + match exclusivity { + NonExclusive => {} + ExclusiveWithinProject | ExclusiveWithinProjectOnOnly => { + for i in 0..provider.entry_count() { + let e = provider.find_entry_by_index(i).unwrap(); + if &e == current_entry { + continue; + } + apply(i, &e); + } + } + ExclusiveWithinFolder | ExclusiveWithinFolderOnOnly => { + // At first look at tracks above + { + let mut delta = 0; + for i in (0..current_index).rev() { + let e = provider.find_entry_by_index(i).unwrap(); + delta -= e.folder_depth_change(); + if delta < 0 { + // Reached parent folder + break; + } + if delta == 0 { + // Same level + apply(i, &e); + } + } + } + // Then look at current track and tracks below. + let current_track_depth_change = current_entry.folder_depth_change(); + if current_track_depth_change >= 0 { + // Current track is not the last one in the folder, so look further. + // delta will starts with 1 if the current track is a folder. + let mut delta = current_track_depth_change; + for i in (current_index + 1)..provider.entry_count() { + let e = match provider.find_entry_by_index(i) { + None => break, + Some(t) => t, + }; + if delta <= 0 { + // Same level, maybe last track in folder + apply(i, &e); + } + delta += e.folder_depth_change(); + if delta < 0 { + // Last track in folder + break; + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use maplit::hashset; + use std::collections::HashSet; + use {TestEntry as E, TestProvider as P}; + + mod exclusive_folder { + use super::*; + + #[test] + fn no_folders() { + // Given + let p = P(vec![E("-"), E("-"), E("-"), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![1, 2, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![0, 2, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![0, 1, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![0, 1, 2] + ); + } + + #[test] + fn top_folder() { + // Given + let p = P(vec![E("/"), E("-"), E("-"), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![2, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![1, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![1, 2] + ); + } + + #[test] + fn bottom_folder() { + // Given + let p = P(vec![E("-"), E("-"), E("-"), E("/")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![1, 2, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![0, 2, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![0, 1, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![0, 1, 2] + ); + } + + #[test] + fn top_next_folder() { + // Given + let p = P(vec![E("-"), E("/"), E("-"), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![1] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![0] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![2] + ); + } + + #[test] + fn bottom_previous_folder() { + // Given + let p = P(vec![E("-"), E("-"), E("/"), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![1, 2] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![0, 2] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![0, 1] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![] + ); + } + + #[test] + fn small_flat_top_folder() { + // Given + let p = P(vec![E("/"), E(r#"\"#), E("-"), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![2, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![0, 3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![0, 2] + ); + } + + #[test] + fn large_flat_top_folder() { + // Given + let p = P(vec![E("/"), E("-"), E(r#"\"#), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![2] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![1] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![0] + ); + } + + #[test] + fn large_nested_top_folder() { + // Given + let p = P(vec![E("/"), E("/"), E(r#"\\"#), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![3] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![0] + ); + } + + #[test] + fn large_deeply_nested_top_folder() { + // Given + let p = P(vec![E("/"), E("/"), E("/"), E(r#"\\\"#), E("-")]); + // When + // Then + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 0,), + hashset![4] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 1,), + hashset![] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 2,), + hashset![] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 3,), + hashset![] + ); + assert_eq!( + test(&p, TrackExclusivity::ExclusiveWithinFolder, 4,), + hashset![0] + ); + } + } + + struct TestProvider(Vec); + + impl HierarchyEntryProvider for TestProvider { + type Entry = TestEntry; + + fn find_entry_by_index(&self, index: u32) -> Option { + self.0.get(index as usize).copied() + } + + fn entry_count(&self) -> u32 { + self.0.len() as _ + } + } + + #[derive(Copy, Clone, PartialEq)] + struct TestEntry(&'static str); + + impl HierarchyEntry for TestEntry { + fn folder_depth_change(&self) -> i32 { + match self.0 { + "-" => 0, + "/" => 1, + r#"\"# => -1, + r#"\\"# => -2, + r#"\\\"# => -3, + _ => panic!("unknown entry symbol"), + } + } + } + + fn test( + provider: &TestProvider, + exclusivity: TrackExclusivity, + current_index: u32, + ) -> HashSet { + let mut affected_indexes = HashSet::new(); + handle_exclusivity( + provider, + exclusivity, + Some(current_index), + &provider.find_entry_by_index(current_index).unwrap(), + |i, _| { + affected_indexes.insert(i); + }, + ); + affected_indexes + } +} diff --git a/plugin-reaper-realearn/main/src/domain/feedback_collector.rs b/plugin-reaper-realearn/main/src/domain/feedback_collector.rs new file mode 100644 index 0000000..b012e34 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/feedback_collector.rs @@ -0,0 +1,134 @@ +use crate::domain::{ + FeedbackOutput, FinalRealFeedbackValue, FinalSourceFeedbackValue, MidiDestination, + PreliminaryRealFeedbackValue, PreliminarySourceFeedbackValue, RealearnSourceState, +}; +use base::hash_util::NonCryptoHashSet; +use helgoboss_learn::devices::x_touch::XTouchMackieLcdState; +use helgoboss_learn::{ + DisplaySpecAddress, MackieLcdScope, MidiSourceValue, RawFeedbackAddressInfo, RawMidiEvent, + XTouchMackieLcdColorRequest, +}; + +/// Responsible for collecting non-final feedback values and aggregating them into final ones. +pub struct FeedbackCollector<'a> { + x_touch_mackie_lcd_feedback_collector: Option>, +} + +struct XTouchMackieLcdFeedbackCollector<'a> { + state: &'a mut XTouchMackieLcdState, + changed_x_touch_mackie_lcd_extenders: NonCryptoHashSet, +} + +impl<'a> FeedbackCollector<'a> { + pub fn new( + global_source_state: &'a mut RealearnSourceState, + feedback_output: Option, + ) -> Self { + let x_touch_mackie_lcd_state = match feedback_output { + Some(FeedbackOutput::Midi(MidiDestination::Device(dev_id))) => { + Some(global_source_state.get_x_touch_mackie_lcd_state_mut(dev_id)) + } + // No or no direct MIDI device output. Then we can ignore this because + // the X-Touch! + _ => None, + }; + Self { + x_touch_mackie_lcd_feedback_collector: x_touch_mackie_lcd_state.map(|state| { + XTouchMackieLcdFeedbackCollector { + state, + changed_x_touch_mackie_lcd_extenders: Default::default(), + } + }), + } + } + + /// Spits the given feedback value immediately out again if it's already final or only has a + /// projection part, but collects it if it's non-final. + pub fn process( + &mut self, + preliminary_feedback_value: PreliminaryRealFeedbackValue, + ) -> Option { + match preliminary_feedback_value.source { + None => { + // Has projection part only. + FinalRealFeedbackValue::new(preliminary_feedback_value.projection, None) + } + Some(preliminary_source_feedback_value) => match preliminary_source_feedback_value { + PreliminarySourceFeedbackValue::Midi(v) => { + if let Some(req) = v.x_touch_mackie_lcd_color_request { + self.process_x_touch_mackie_lcd_color_request(req); + } + FinalRealFeedbackValue::new( + preliminary_feedback_value.projection, + Some(FinalSourceFeedbackValue::Midi(v.final_value)), + ) + } + // Is final OSC value already. + PreliminarySourceFeedbackValue::Osc(v) => FinalRealFeedbackValue::new( + preliminary_feedback_value.projection, + Some(FinalSourceFeedbackValue::Osc(v)), + ), + // Is final REAPER source value already. + PreliminarySourceFeedbackValue::Reaper(v) => FinalRealFeedbackValue::new( + preliminary_feedback_value.projection, + Some(FinalSourceFeedbackValue::Reaper(v)), + ), + // Is final StreamDeck source value already. + PreliminarySourceFeedbackValue::StreamDeck(v) => FinalRealFeedbackValue::new( + preliminary_feedback_value.projection, + Some(FinalSourceFeedbackValue::StreamDeck(v)), + ), + }, + } + } + + /// Takes the collected and aggregated material and produces the final feedback values. + pub fn generate_final_feedback_values( + self, + ) -> impl Iterator + 'a { + self.x_touch_mackie_lcd_feedback_collector + .into_iter() + .flat_map(|x_touch_collector| { + x_touch_collector + .changed_x_touch_mackie_lcd_extenders + .into_iter() + .filter_map(|extender_index| { + let sysex = x_touch_collector.state.sysex(extender_index); + let midi_event = RawMidiEvent::try_from_iter(0, sysex).ok()?; + let feedback_address = RawFeedbackAddressInfo::Display { + spec: DisplaySpecAddress::XTouchMackieLcdColors { extender_index }, + }; + let source_feedback_value = FinalSourceFeedbackValue::Midi( + MidiSourceValue::single_raw(Some(feedback_address), midi_event), + ); + FinalRealFeedbackValue::new(None, Some(source_feedback_value)) + }) + }) + } + + fn process_x_touch_mackie_lcd_color_request(&mut self, req: XTouchMackieLcdColorRequest) { + let collector = match &mut self.x_touch_mackie_lcd_feedback_collector { + None => return, + Some(c) => c, + }; + let channels = match req.channel { + None => 0..MackieLcdScope::CHANNEL_COUNT, + Some(ch) => ch..ch + 1, + }; + let mut at_least_one_color_change = false; + for ch in channels { + let changed = + collector + .state + .notify_color_requested(req.extender_index, ch, req.color_index); + if changed { + at_least_one_color_change = true; + } + } + if at_least_one_color_change { + collector + .changed_x_touch_mackie_lcd_extenders + .insert(req.extender_index); + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/flexible_midi_source_script.rs b/plugin-reaper-realearn/main/src/domain/flexible_midi_source_script.rs new file mode 100644 index 0000000..7129069 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/flexible_midi_source_script.rs @@ -0,0 +1,24 @@ +use crate::domain::{AdditionalLuaMidiSourceScriptInput, EelMidiSourceScript, LuaMidiSourceScript}; +use helgoboss_learn::{FeedbackValue, MidiSourceScript, MidiSourceScriptOutcome}; +use std::borrow::Cow; + +#[derive(Debug)] +pub enum FlexibleMidiSourceScript<'lua> { + Eel(EelMidiSourceScript), + Lua(LuaMidiSourceScript<'lua>), +} + +impl<'a, 'lua: 'a> MidiSourceScript<'a> for FlexibleMidiSourceScript<'lua> { + type AdditionalInput = AdditionalLuaMidiSourceScriptInput<'a>; + + fn execute( + &self, + input_value: FeedbackValue, + additional_input: Self::AdditionalInput, + ) -> Result> { + match self { + FlexibleMidiSourceScript::Eel(s) => s.execute(input_value, ()), + FlexibleMidiSourceScript::Lua(s) => s.execute(input_value, additional_input), + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/global_audio_state.rs b/plugin-reaper-realearn/main/src/domain/global_audio_state.rs new file mode 100644 index 0000000..8fe9cd1 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/global_audio_state.rs @@ -0,0 +1,55 @@ +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; + +use atomic::Atomic; +use reaper_medium::Hz; +use static_assertions::const_assert; + +use crate::domain::AudioBlockProps; + +pub static GLOBAL_AUDIO_STATE: GlobalAudioState = GlobalAudioState::new(); + +#[derive(Debug)] +pub struct GlobalAudioState { + block_count: AtomicU64, + block_size: AtomicU32, + sample_rate: Atomic, +} + +impl Default for GlobalAudioState { + fn default() -> Self { + Self::new() + } +} + +impl GlobalAudioState { + pub const fn new() -> Self { + const_assert!(Atomic::::is_lock_free()); + Self { + block_count: AtomicU64::new(0), + block_size: AtomicU32::new(0), + sample_rate: Atomic::new(1.0), + } + } + + /// Returns previous block count + pub fn advance(&self, block_props: AudioBlockProps) -> u64 { + let prev_block_count = self.block_count.fetch_add(1, Ordering::Relaxed); + self.block_size + .store(block_props.block_length as u32, Ordering::Relaxed); + self.sample_rate + .store(block_props.frame_rate.get(), Ordering::Relaxed); + prev_block_count + } + + pub fn load_block_count(&self) -> u64 { + self.block_count.load(Ordering::Relaxed) + } + + pub fn load_block_size(&self) -> u32 { + self.block_size.load(Ordering::Relaxed) + } + + pub fn load_sample_rate(&self) -> Hz { + Hz::new_panic(self.sample_rate.load(Ordering::Relaxed)) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/group.rs b/plugin-reaper-realearn/main/src/domain/group.rs new file mode 100644 index 0000000..c7d3500 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/group.rs @@ -0,0 +1,80 @@ +use derive_more::Display; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use uuid::Uuid; + +/// Internal technical group identifier, not persistent. +/// +/// Goals: Quick lookup, guaranteed uniqueness, cheap copy +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize, Default, +)] +#[serde(transparent)] +pub struct GroupId { + uuid: Uuid, +} + +impl GroupId { + pub fn is_default(&self) -> bool { + self.uuid.is_nil() + } + + pub fn random() -> GroupId { + GroupId { + uuid: Uuid::new_v4(), + } + } +} + +impl fmt::Display for GroupId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.uuid) + } +} + +impl FromStr for GroupId { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let uuid = Uuid::from_str(s).map_err(|_| "group ID must be a valid UUID")?; + Ok(Self { uuid }) + } +} + +/// A potentially user-defined group identifier, persistent +/// +/// Goals: For external references (e.g. from API or in projection) +#[derive( + Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Display, Serialize, Deserialize, +)] +#[serde(transparent)] +pub struct GroupKey(String); + +impl GroupKey { + pub fn random() -> Self { + Self(nanoid::nanoid!()) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl AsRef for GroupKey { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl From for GroupKey { + fn from(v: String) -> Self { + Self(v) + } +} + +impl From for String { + fn from(v: GroupKey) -> Self { + v.0 + } +} diff --git a/plugin-reaper-realearn/main/src/domain/hex.rs b/plugin-reaper-realearn/main/src/domain/hex.rs new file mode 100644 index 0000000..3b630a6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/hex.rs @@ -0,0 +1,25 @@ +use std::fmt::{Display, Formatter}; + +pub fn parse_hex_string(value: &str) -> Result, hex::FromHexError> { + let without_spaces = value.replace(' ', ""); + hex::decode(without_spaces) +} + +/// Formats the given slice of bytes as hex numbers separated by spaces. +pub fn format_as_pretty_hex(bytes: &[u8]) -> String { + DisplayAsPrettyHex(bytes).to_string() +} + +pub struct DisplayAsPrettyHex<'a>(pub &'a [u8]); + +impl Display for DisplayAsPrettyHex<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for (i, b) in self.0.iter().enumerate() { + if i > 0 { + f.write_str(" ")?; + } + write!(f, "{:02X?}", *b)?; + } + Ok(()) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/instance.rs b/plugin-reaper-realearn/main/src/domain/instance.rs new file mode 100644 index 0000000..3d59478 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/instance.rs @@ -0,0 +1,488 @@ +use crate::domain::{AnyThreadBackboneState, Backbone, ProcessorContext, RealTimeInstance, UnitId}; +#[allow(unused_imports)] +use anyhow::Context; +use base::hash_util::NonCryptoHashMap; +use base::{NamedChannelSender, SenderToNormalThread, SenderToRealTimeThread}; +use helgobox_api::persistence::PotFilterKind; +use pot::{ + CurrentPreset, OptFilter, PotFavorites, PotFilterExcludes, PotIntegration, PotUnit, PresetId, + SharedRuntimePotUnit, +}; +use reaper_high::{ChangeEvent, Fx}; +use std::cell::{Ref, RefCell, RefMut}; +use std::fmt; +use std::num::ParseIntError; +use std::rc::{Rc, Weak}; +use std::str::FromStr; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::RwLock; + +pub type SharedInstance = Rc>; +pub type WeakInstance = Weak>; + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] +pub struct InstanceId(u32); + +#[derive(Debug)] +pub struct Instance { + id: InstanceId, + processor_context: ProcessorContext, + feedback_event_sender: SenderToNormalThread, + main_unit_id: UnitId, + #[allow(unused)] + handler: Box, + /// Saves the current state for Pot preset navigation. + /// + /// Persistent. + pot_unit: PotUnit, + pub audio_hook_task_sender: base::SenderToRealTimeThread, + pub real_time_instance_task_sender: + base::SenderToRealTimeThread, + custom_data: NonCryptoHashMap, + #[cfg(feature = "playtime")] + pub playtime: PlaytimeInstance, +} + +#[cfg(feature = "playtime")] +#[derive(Debug)] +pub struct PlaytimeInstance { + clip_matrix: Option, + pub clip_matrix_event_sender: SenderToNormalThread, +} + +pub trait InstanceHandler: fmt::Debug { + #[cfg(feature = "playtime")] + fn clip_matrix_changed( + &self, + instance_id: InstanceId, + matrix: &playtime_clip_engine::base::Matrix, + events: &[playtime_clip_engine::base::ClipMatrixEvent], + is_poll: bool, + ); + #[cfg(feature = "playtime")] + fn process_control_surface_change_event_for_clip_engine( + &self, + instance_id: InstanceId, + matrix: &playtime_clip_engine::base::Matrix, + events: &[reaper_high::ChangeEvent], + ); +} + +impl Drop for Instance { + fn drop(&mut self) { + if Backbone::is_loaded() { + Backbone::get().unregister_instance(&self.id); + } + } +} + +#[derive(Debug)] +pub struct QualifiedInstanceEvent { + pub instance_id: InstanceId, + pub event: InstanceStateChanged, +} + +#[cfg(feature = "playtime")] +#[derive(Debug)] +pub struct QualifiedClipMatrixEvent { + pub instance_id: InstanceId, + pub event: playtime_clip_engine::base::ClipMatrixEvent, +} + +impl fmt::Display for InstanceId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for InstanceId { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse()?)) + } +} + +impl InstanceId { + pub fn next() -> Self { + static COUNTER: AtomicU32 = AtomicU32::new(0); + Self(COUNTER.fetch_add(1, Ordering::SeqCst)) + } +} + +impl From for InstanceId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: InstanceId) -> Self { + value.0 + } +} + +const REAL_TIME_INSTANCE_TASK_QUEUE_SIZE: usize = 200; + +impl Instance { + pub fn new( + id: InstanceId, + main_unit_id: UnitId, + processor_context: ProcessorContext, + feedback_event_sender: SenderToNormalThread, + handler: Box, + #[cfg(feature = "playtime")] clip_matrix_event_sender: SenderToNormalThread< + QualifiedClipMatrixEvent, + >, + audio_hook_task_sender: base::SenderToRealTimeThread, + ) -> (Self, RealTimeInstance) { + let (real_time_instance_task_sender, real_time_instance_task_receiver) = + SenderToRealTimeThread::new_channel( + "real-time instance tasks", + REAL_TIME_INSTANCE_TASK_QUEUE_SIZE, + ); + let rt_instance = RealTimeInstance::new(real_time_instance_task_receiver); + let instance = Self { + id, + main_unit_id, + feedback_event_sender, + handler, + processor_context, + pot_unit: Default::default(), + #[cfg(feature = "playtime")] + playtime: PlaytimeInstance { + clip_matrix: None, + clip_matrix_event_sender, + }, + audio_hook_task_sender, + real_time_instance_task_sender, + custom_data: Default::default(), + }; + (instance, rt_instance) + } + + pub fn id(&self) -> InstanceId { + self.id + } + + pub fn main_unit_id(&self) -> UnitId { + self.main_unit_id + } + + pub fn custom_data(&self) -> &NonCryptoHashMap { + &self.custom_data + } + + pub fn set_custom_data(&mut self, data: NonCryptoHashMap) { + self.custom_data = data; + } + pub fn update_custom_data_key(&mut self, key: String, value: serde_json::Value) { + self.custom_data.insert(key, value); + } + + /// Returns the runtime pot unit associated with this instance. + /// + /// If the pot unit isn't loaded yet and loading has not been attempted yet, loads it. + /// + /// Returns an error if the necessary pot database is not available. + pub fn pot_unit(&mut self) -> Result { + let integration = RealearnPotIntegration::new( + self.id, + self.processor_context.containing_fx().clone(), + self.feedback_event_sender.clone(), + ); + self.pot_unit.loaded(Box::new(integration)) + } + + /// Restores a pot unit state from persistent data. + /// + /// This doesn't load the pot unit yet. If the ReaLearn instance never accesses the pot unit, + /// it simply remains unloaded and its persistent state is kept. The persistent state is also + /// kept if loading of the pot unit fails (e.g. if the necessary pot database is not available + /// on the user's computer). + pub fn restore_pot_unit(&mut self, state: pot::PersistentState) { + self.pot_unit = PotUnit::unloaded(state); + } + + /// Returns a pot unit state suitable to be saved by the persistence logic. + pub fn save_pot_unit(&self) -> pot::PersistentState { + self.pot_unit.persistent_state() + } + + pub fn process_control_surface_change_events(&mut self, events: &[ChangeEvent]) { + #[cfg(not(feature = "playtime"))] + { + let _ = events; + } + #[cfg(feature = "playtime")] + { + if events.is_empty() { + return; + } + if let Some(matrix) = self.playtime.clip_matrix.as_mut() { + // Let matrix react to track changes etc. + matrix.process_reaper_change_events(events); + // Process for GUI + self.handler + .process_control_surface_change_event_for_clip_engine(self.id, matrix, events); + } + } + } + + pub fn notify_mappings_in_unit_changed(&self, unit_id: UnitId) { + #[cfg(not(feature = "playtime"))] + { + let _ = unit_id; + } + #[cfg(feature = "playtime")] + if unit_id == self.main_unit_id { + if let Some(matrix) = self.clip_matrix() { + matrix.notify_simple_mappings_changed(); + } + } + } + + pub fn notify_learning_target_in_unit_changed(&self, unit_id: UnitId) { + #[cfg(not(feature = "playtime"))] + { + let _ = unit_id; + } + #[cfg(feature = "playtime")] + if unit_id == self.main_unit_id { + if let Some(matrix) = self.clip_matrix() { + matrix.notify_learning_target_changed(); + } + } + } + + pub fn has_clip_matrix(&self) -> bool { + #[cfg(feature = "playtime")] + { + self.playtime.clip_matrix.is_some() + } + #[cfg(not(feature = "playtime"))] + { + false + } + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::instance::NO_CLIP_MATRIX_SET; + use crate::domain::{ + err_if_reaper_version_too_low_for_playtime, Instance, QualifiedClipMatrixEvent, + }; + use anyhow::Context; + use base::NamedChannelSender; + + impl Instance { + /// Polls the Playtime matrix of this ReaLearn instance. + pub fn poll_owned_clip_matrix( + &mut self, + ) -> Vec { + let Some(matrix) = self.playtime.clip_matrix.as_mut() else { + return vec![]; + }; + let events = matrix.poll(); + self.handler + .clip_matrix_changed(self.id, matrix, &events, true); + events + } + + pub fn process_non_polled_clip_matrix_event( + &self, + event: &crate::domain::QualifiedClipMatrixEvent, + ) { + if event.instance_id != self.id { + return; + } + let Some(matrix) = self.clip_matrix() else { + return; + }; + self.handler.clip_matrix_changed( + self.id, + matrix, + std::slice::from_ref(&event.event), + false, + ); + } + + pub fn get_playtime_matrix(&self) -> anyhow::Result<&playtime_clip_engine::base::Matrix> { + self.playtime + .clip_matrix + .as_ref() + .context(NO_CLIP_MATRIX_SET) + } + + pub fn get_playtime_matrix_mut( + &mut self, + ) -> anyhow::Result<&mut playtime_clip_engine::base::Matrix> { + self.playtime + .clip_matrix + .as_mut() + .context(NO_CLIP_MATRIX_SET) + } + + pub fn clip_matrix(&self) -> Option<&playtime_clip_engine::base::Matrix> { + self.playtime.clip_matrix.as_ref() + } + + pub fn clip_matrix_mut(&mut self) -> Option<&mut playtime_clip_engine::base::Matrix> { + self.playtime.clip_matrix.as_mut() + } + + /// Returns `Ok(true)` if it installed a Playtime matrix and `Ok(false)` if one was installed already. + /// + /// # Errors + /// + /// Returns an error if the Playtime matrix can't be created, e.g. when on the monitoring FX chain. + pub(crate) fn create_and_install_clip_matrix_if_necessary( + &mut self, + create_handler: impl FnOnce( + &Instance, + ) + -> Box, + ) -> anyhow::Result { + if self.playtime.clip_matrix.is_some() { + return Ok(false); + } + err_if_reaper_version_too_low_for_playtime()?; + let track = self.processor_context.track() + .context("Sorry, Playtime is not intended to be used from the monitoring FX chain! If you have a really good use case for that, please write to info@helgoboss.org and we will see what we can do.")?; + let matrix = + playtime_clip_engine::base::Matrix::new(create_handler(self), track.clone()); + self.update_real_time_clip_matrix(Some(matrix.real_time_matrix())); + self.set_clip_matrix(Some(matrix)); + self.playtime + .clip_matrix_event_sender + .send_complaining(QualifiedClipMatrixEvent { + instance_id: self.id, + event: playtime_clip_engine::base::ClipMatrixEvent::EverythingChanged, + }); + Ok(true) + } + + pub fn set_clip_matrix(&mut self, matrix: Option) { + if self.playtime.clip_matrix.is_some() { + tracing::debug!("Shutdown existing Playtime matrix"); + self.update_real_time_clip_matrix(None); + } + self.playtime.clip_matrix = matrix; + } + + pub(super) fn update_real_time_clip_matrix( + &self, + real_time_matrix: Option, + ) { + let rt_task = crate::domain::RealTimeInstanceTask::SetClipMatrix { + matrix: real_time_matrix, + }; + self.real_time_instance_task_sender + .send_complaining(rt_task); + } + } +} + +struct RealearnPotIntegration { + instance_id: InstanceId, + containing_fx: Fx, + sender: SenderToNormalThread, +} + +impl RealearnPotIntegration { + fn new( + instance_id: InstanceId, + containing_fx: Fx, + sender: SenderToNormalThread, + ) -> Self { + Self { + instance_id, + containing_fx, + sender, + } + } + + fn emit(&self, event: InstanceStateChanged) { + self.sender.send_complaining(QualifiedInstanceEvent { + instance_id: self.instance_id, + event, + }) + } +} + +impl PotIntegration for RealearnPotIntegration { + fn favorites(&self) -> &RwLock { + &AnyThreadBackboneState::get().pot_favorites + } + + fn set_current_fx_preset(&self, fx: Fx, preset: CurrentPreset) { + Backbone::target_state() + .borrow_mut() + .set_current_fx_preset(fx, preset); + self.emit(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::PresetLoaded, + )); + } + + fn exclude_list(&self) -> Ref { + Backbone::get().pot_filter_exclude_list() + } + + fn exclude_list_mut(&self) -> RefMut { + Backbone::get().pot_filter_exclude_list_mut() + } + + fn notify_preset_changed(&self, id: Option) { + self.emit(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::PresetChanged { id }, + )); + } + + fn notify_filter_changed(&self, kind: PotFilterKind, filter: OptFilter) { + self.emit(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::FilterItemChanged { kind, filter }, + )); + } + + fn notify_indexes_rebuilt(&self) { + self.emit(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::IndexesRebuilt, + )); + } + + fn protected_fx(&self) -> &Fx { + &self.containing_fx + } +} + +#[derive(Clone, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum InstanceStateChanged { + PotStateChanged(PotStateChangedEvent), +} + +#[derive(Clone, Debug)] +pub enum PotStateChangedEvent { + FilterItemChanged { + kind: PotFilterKind, + filter: OptFilter, + }, + PresetChanged { + id: Option, + }, + IndexesRebuilt, + PresetLoaded, +} + +#[cfg(feature = "playtime")] +const NO_CLIP_MATRIX_SET: &str = "no Playtime matrix set for this instance"; + +#[cfg(feature = "playtime")] +pub fn err_if_reaper_version_too_low_for_playtime() -> anyhow::Result<()> { + const MIN_REAPER_VERSION: &str = "7"; + if reaper_high::Reaper::get().version().revision() < MIN_REAPER_VERSION { + anyhow::bail!("Please update REAPER to version {MIN_REAPER_VERSION} to access Playtime!"); + } + Ok(()) +} diff --git a/plugin-reaper-realearn/main/src/domain/internal_info_event.rs b/plugin-reaper-realearn/main/src/domain/internal_info_event.rs new file mode 100644 index 0000000..bea7b5d --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/internal_info_event.rs @@ -0,0 +1,4 @@ +#[derive(Debug)] +pub enum InternalInfoEvent { + UndesiredAllocationCountChanged, +} diff --git a/plugin-reaper-realearn/main/src/domain/io.rs b/plugin-reaper-realearn/main/src/domain/io.rs new file mode 100644 index 0000000..4eeab0e --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/io.rs @@ -0,0 +1,92 @@ +use crate::domain::{MidiControlInput, MidiDestination, OscDeviceId}; +use reaper_medium::{MidiInputDeviceId, MidiOutputDeviceId}; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum ControlInput { + Midi(MidiControlInput), + Osc(OscDeviceId), +} + +impl ControlInput { + pub fn from_device_input(input: DeviceControlInput) -> Self { + match input { + DeviceControlInput::Midi(id) => Self::Midi(MidiControlInput::Device(id)), + DeviceControlInput::Osc(id) => Self::Osc(id), + } + } + + pub fn midi_control_input(self) -> Option { + if let ControlInput::Midi(i) = self { + Some(i) + } else { + None + } + } + + pub fn device_input(self) -> Option { + use ControlInput::*; + match self { + Midi(MidiControlInput::Device(id)) => Some(DeviceControlInput::Midi(id)), + Osc(id) => Some(DeviceControlInput::Osc(id)), + _ => None, + } + } + + pub fn is_midi_device(self) -> bool { + matches!(self, ControlInput::Midi(MidiControlInput::Device(_))) + } + + pub fn is_midi_fx_input(self) -> bool { + matches!(self, ControlInput::Midi(MidiControlInput::FxInput)) + } +} + +impl Default for ControlInput { + fn default() -> Self { + Self::Midi(MidiControlInput::FxInput) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum DeviceControlInput { + Midi(MidiInputDeviceId), + Osc(OscDeviceId), +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum FeedbackOutput { + Midi(MidiDestination), + Osc(OscDeviceId), +} + +impl FeedbackOutput { + pub fn from_device_output(output: DeviceFeedbackOutput) -> Self { + match output { + DeviceFeedbackOutput::Midi(id) => Self::Midi(MidiDestination::Device(id)), + DeviceFeedbackOutput::Osc(id) => Self::Osc(id), + } + } + + pub fn midi_destination(&self) -> Option { + if let Self::Midi(dest) = self { + Some(*dest) + } else { + None + } + } + + pub fn device_output(self) -> Option { + use FeedbackOutput::*; + match self { + Midi(MidiDestination::Device(id)) => Some(DeviceFeedbackOutput::Midi(id)), + Osc(id) => Some(DeviceFeedbackOutput::Osc(id)), + _ => None, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum DeviceFeedbackOutput { + Midi(MidiOutputDeviceId), + Osc(OscDeviceId), +} diff --git a/plugin-reaper-realearn/main/src/domain/key_source.rs b/plugin-reaper-realearn/main/src/domain/key_source.rs new file mode 100644 index 0000000..78314ff --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/key_source.rs @@ -0,0 +1,329 @@ +use crate::domain::ControlOutcome; +use enumflags2::BitFlags; +use helgoboss_learn::{ControlValue, UnitValue}; +use reaper_high::{AcceleratorKey, Reaper}; +use reaper_medium::{ + virt_keys, Accel, AccelMsgKind, AcceleratorBehavior, AcceleratorKeyCode, ReaperString, VirtKey, +}; +use std::borrow::Cow; +use std::fmt::{Display, Formatter}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct KeySource { + currently_pressed: bool, + stroke: Keystroke, +} + +impl KeySource { + pub fn new(stroke: Keystroke) -> Self { + Self { + currently_pressed: false, + stroke, + } + } + + pub fn stroke(&self) -> Keystroke { + self.stroke + } + + pub fn control(&mut self, msg: KeyMessage) -> Option> { + if !(msg.stroke() == self.stroke) { + // If strokes don't match, we can return early. All tests below assume that the stroke matches. + return None; + } + if !msg.interaction_kind().is_press_or_release() { + // On Windows, there's not just press and release but also something like "key is being + // hold", which fires continuously. We neither want to react to it (because we have our + // own fire modes) nor simply forward it to REAPER (because it would dig a hole + // into our "Filter matched events" mechanism). We let this source "consume" the message + // instead. + // Oh yes, and there's "Char". If in a text field, Windows (and maybe also other OS?) + // sends for each character key press an additional "Char" interaction. It should have + // been normalized in the accelerator and match the keystroke of the key-down event. + // As a result, we consume it as well. + return Some(ControlOutcome::Consumed); + } + let is_press = msg.interaction_kind().is_press(); + if is_press && self.currently_pressed { + // We don't want OS-triggered repeated key firing (macOS). We have our own fire modes. + return Some(ControlOutcome::Consumed); + } + let control_value = self.get_control_value(msg)?; + self.currently_pressed = is_press; + Some(ControlOutcome::Matched(control_value)) + } + + /// Non-mutating! Used for checks. + pub fn reacts_to_message_with(&self, msg: KeyMessage) -> Option { + if !msg.interaction_kind().is_press_or_release() { + return None; + } + self.get_control_value(msg) + } + + /// Assumes that relevance has been checked already. + fn get_control_value(&self, msg: KeyMessage) -> Option { + if msg.stroke != self.stroke { + return None; + } + let value = if msg.interaction_kind().is_press() { + UnitValue::MAX + } else { + UnitValue::MIN + }; + Some(ControlValue::AbsoluteContinuous(value)) + } +} + +impl Display for KeySource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.stroke.fmt(f) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct KeyMessage { + kind: AccelMsgKind, + stroke: Keystroke, +} + +impl KeyMessage { + pub fn new(kind: AccelMsgKind, stroke: Keystroke) -> Self { + Self { kind, stroke } + } + + pub fn interaction_kind(&self) -> KeyInteractionKind { + use AccelMsgKind::*; + match self.kind { + KeyDown | SysKeyDown => KeyInteractionKind::Press, + KeyUp | SysKeyUp => KeyInteractionKind::Release, + _ => KeyInteractionKind::Other, + } + } + + pub fn stroke(&self) -> Keystroke { + self.stroke + } +} + +impl Display for KeyMessage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.interaction_kind(), self.stroke) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, derive_more::Display)] +pub enum KeyInteractionKind { + Press, + Release, + Other, +} + +impl KeyInteractionKind { + pub fn is_press(&self) -> bool { + matches!(self, Self::Press) + } + + /// Checks if the kind is relevant (only key-down and key-up). + pub fn is_press_or_release(&self) -> bool { + matches!(self, Self::Press | Self::Release) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] +pub struct Keystroke { + modifiers: BitFlags, + key: AcceleratorKeyCode, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, derive_more::Display)] +pub enum KeyStrokePortability { + NonPortable(PortabilityIssue), + Portable, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, derive_more::Display)] +pub enum PortabilityIssue { + NotNormalized, + OperatingSystemRelated, + KeyboardLayoutRelated, + Other, +} + +impl Keystroke { + pub fn new(behavior: BitFlags, key: AcceleratorKeyCode) -> Self { + Self { + modifiers: behavior, + key, + } + } + + /// This normalizes the given behavior/key combination so it works cross-platform. + /// + /// When REAPER notifies us about incoming key events, the accelerator behavior and key codes + /// look slightly different depending on the operating system: + /// + /// - On all operating systems, if we have a key combination, we receive each key event + /// separately, even the modifier keys. Good! + /// - If we have a key combination (modifier key + normal key), Windows doesn't mention the + /// modifier keys in the accelerator behavior, but macOS and Linux do. We prefer the Windows + /// way because it makes more sense in this context. We receive modifier key-ups and key-downs + /// separately anyway. + /// - On Windows, umlauts are delivered as virtual keys, on macOS and Linux as character codes. + /// We prefer the macOS and Linux way. + /// - On Windows, "normal" special characters such as # and + are delivered as virtual keys. + /// On macOS and Linux, they are delivered as character codes. + /// - On Windows, "abnormal" special characters such as ^ or ` are delivered as virtual keys. + /// On macOS, they are also delivered as virtual keys but with a different code. + /// On Linux, they are delivered as character code. + /// We don't like any. Mark them as non-portable! + #[allow(clippy::if_same_then_else)] + pub fn normalized(&self) -> Self { + use AcceleratorBehavior::*; + let mut modifiers = self.modifiers; + let key = self.key; + // Remove modifier info (makes a difference on macOS and Linux only). + modifiers.remove(Shift | Control | Alt); + // Do some Windows-specific conversions. + #[cfg(windows)] + { + if modifiers.contains(VirtKey) { + // Key is a virtual key. + // On Windows, we need to convert virtual keys for umlauts or special characters to + // character codes so we match the behavior of macOS and Linux. + let character_code = unsafe { + winapi::um::winuser::MapVirtualKeyW( + key.get() as u32, + winapi::um::winuser::MAPVK_VK_TO_CHAR, + ) + }; + if character_code == 0 { + // Couldn't find corresponding character code. + Self::new(modifiers, key) + } else if character_code == key.get() as u32 { + // Character code is equal to virtual key code. In this case, macOS and Linux + // would also use the virtual key code (I hope), so we keep it. + Self::new(modifiers, key) + } else { + // We have a completely different character code. Use this one because + // macOS and Linux would also prefer the character code. + modifiers.remove(VirtKey); + Self::new(modifiers, AcceleratorKeyCode::new(character_code as u16)) + } + } else { + // Key is a character code. Use as is. + Self::new(modifiers, key) + } + } + // On Linux and macOS, this is not necessary. + #[cfg(not(windows))] + { + Self::new(modifiers, key) + } + } + + pub fn modifiers(&self) -> BitFlags { + self.modifiers + } + + pub fn key_code(&self) -> AcceleratorKeyCode { + self.key + } + + /// Returns information about portability of this keystroke across operating systems, keyboards, + /// layouts, if known. + pub fn portability(&self) -> Option { + use KeyStrokePortability::*; + use PortabilityIssue::*; + let normalized = self.normalized(); + if *self != normalized { + return Some(KeyStrokePortability::NonPortable( + PortabilityIssue::NotNormalized, + )); + } + match self.accelerator_key() { + AcceleratorKey::Character(ch) => { + match ch { + // Consider non-ASCII characters generally as non-portable. + x if x > 0x7f => Some(NonPortable(KeyboardLayoutRelated)), + a => { + let a = a as u8; + match a { + // These ones are at least on the numpad. Numpad is layout-agnostic. + b'+' | b'-' | b'*' | b'/' => Some(Portable), + // These have special behavior on some keyboard layouts. + b'`' | b'^' => Some(NonPortable(KeyboardLayoutRelated)), + // Since most ASCII characters are transmitted as virtual keys, we + // can categorize all other ASCII characters as probably not portable. + _ => None, + } + } + } + } + AcceleratorKey::VirtKey(k) => { + use virt_keys::*; + match k { + // Special keys that either every keyboard has or everybody knows a keyboard + // might not have. Anyway, no cross-platform or keyboard-layout issues usually. + ESCAPE | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | INSERT + | NUMPAD0 | NUMPAD1 | NUMPAD2 | NUMPAD3 | NUMPAD4 | NUMPAD5 | NUMPAD6 + | NUMPAD7 | NUMPAD8 | NUMPAD9 | SHIFT | CONTROL | MENU | SPACE | TAB | HOME + | END | PRIOR | NEXT | LEFT | UP | DOWN | RIGHT | RETURN | BACK | PAUSE + | CLEAR | DELETE | SNAPSHOT => Some(Portable), + CAPITAL => { + // CAPS LOCK doesn't fire on macOS. + Some(NonPortable(OperatingSystemRelated)) + } + F12 => { + // F12 is known to be treated a bit differently at times. + Some(NonPortable(PortabilityIssue::Other)) + } + // Characters + k => match u8::try_from(k.get()) { + Ok(b'A'..=b'Z' | b'0'..=b'9') => Some(Portable), + // Other basic characters don't qualify as explicitly portable. + _ => None, + }, + } + } + } + } + + pub fn accelerator_key(&self) -> AcceleratorKey { + AcceleratorKey::from_behavior_and_key_code(self.modifiers, self.key) + } + + pub fn is_modifier_key(&self) -> bool { + use virt_keys::{CONTROL, MENU, SHIFT}; + matches!( + self.accelerator_key(), + AcceleratorKey::VirtKey(CONTROL | MENU | SHIFT) + ) + } + + fn format_key_via_reaper(&self) -> ReaperString { + let accel = Accel { + f_virt: self.modifiers, + key: self.key, + cmd: 0, + }; + Reaper::get().medium_reaper().kbd_format_key_name(accel) + } +} + +impl Display for Keystroke { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let key = self.accelerator_key(); + use virt_keys::{CONTROL, MENU, SHIFT}; + const WIN: VirtKey = VirtKey::new(91); + use AcceleratorKey as K; + let label: Cow = match key { + K::VirtKey(SHIFT) => "Shift".into(), + K::VirtKey(CONTROL) => "Ctrl/Cmd".into(), + K::VirtKey(MENU) => "Alt/Opt".into(), + K::VirtKey(WIN) => "Win/^".into(), + _ => self.format_key_via_reaper().into_string().into(), + }; + f.write_str(label.as_ref()) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/lua_feedback_script.rs b/plugin-reaper-realearn/main/src/domain/lua_feedback_script.rs new file mode 100644 index 0000000..757388c --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/lua_feedback_script.rs @@ -0,0 +1,292 @@ +use crate::domain::{lua_module_path_without_ext, SafeLua, ScriptColor, ScriptFeedbackEvent}; +use anyhow::ensure; +use base::hash_util::NonCryptoHashSet; +use helgoboss_learn::{ + FeedbackScript, FeedbackScriptInput, FeedbackScriptOutput, FeedbackValue, NumericValue, + PropProvider, PropValue, +}; +use mlua::{Function, IntoLua, Lua, LuaSerdeExt, Table, Value}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::error::Error; + +#[derive(Copy, Clone, Debug, Default)] +pub struct AdditionalLuaFeedbackScriptInput<'a> { + pub compartment_lua: Option<&'a mlua::Value>, +} + +#[derive(Debug)] +pub struct LuaFeedbackScript<'a> { + lua: &'a SafeLua, + function: Function, + env: Table, + context_key: Value, +} + +unsafe impl Send for LuaFeedbackScript<'_> {} + +impl<'a> LuaFeedbackScript<'a> { + pub fn compile(lua: &'a SafeLua, lua_script: &str) -> anyhow::Result { + ensure!(!lua_script.trim().is_empty(), "script empty"); + let env = lua.create_fresh_environment(false)?; + let function = lua.compile_as_function("Feedback script", lua_script, env.clone())?; + let script = Self { + lua, + env, + function, + context_key: "context".into_lua(lua.as_ref())?, + }; + Ok(script) + } + + fn feedback_internal( + &self, + input: FeedbackScriptInput, + additional_input: as FeedbackScript<'a>>::AdditionalInput, + ) -> anyhow::Result { + let lua = self.lua.as_ref(); + let value = lua.scope(|scope| { + // Set require function + let require = scope.create_function(move |lua, path: String| { + let val = match lua_module_path_without_ext(&path) { + LUA_FEEDBACK_SCRIPT_RUNTIME_NAME => create_lua_feedback_script_runtime(lua), + "compartment" => { + additional_input.compartment_lua.cloned().unwrap_or(Value::Nil) + }, + _ => return Err(mlua::Error::runtime(format!("Feedback scripts don't support the usage of 'require' for anything else than '{LUA_FEEDBACK_SCRIPT_RUNTIME_NAME}' and 'compartment'!"))) + }; + Ok(val) + }) + .map_err(mlua::Error::runtime)?; + self.env.raw_set("require", require) + .map_err(mlua::Error::runtime)?; + // Build input data + let context_table = { + let table = lua.create_table()?; + table.set("mode", 0)?; + let prop = scope.create_function(move |_, key: String| { + let prop_value = input.prop_provider.get_prop_value(&key); + Ok(prop_value.map(LuaPropValue)) + })?; + table.set("prop", prop)?; + table + }; + self.env.raw_set(self.context_key.clone(), context_table)?; + // Invoke script + let value: Value = self.function.call(())?; + Ok(value) + })?; + // Process return value + let output: LuaScriptFeedbackOutput = self.lua.as_ref().from_value(value)?; + let feedback_value = match output.feedback_event { + None => FeedbackValue::Off, + Some(e) => e.into_api_feedback_value(), + }; + let api_output = FeedbackScriptOutput { feedback_value }; + Ok(api_output) + } +} + +pub const LUA_FEEDBACK_SCRIPT_RUNTIME_NAME: &str = "feedback_script_runtime"; + +pub fn create_lua_feedback_script_runtime(_lua: &Lua) -> mlua::Value { + // At the moment, the feedback script runtime doesn't contain any functions, just types. + // That means it's only relevant for autocompletion in the IDE. We can return nil. + Value::Nil +} + +struct LuaPropValue(PropValue); + +impl IntoLua for LuaPropValue { + fn into_lua(self, lua: &Lua) -> mlua::Result { + match self.0 { + PropValue::Normalized(p) => p.get().into_lua(lua), + PropValue::Index(i) => i.into_lua(lua), + PropValue::Numeric(NumericValue::Decimal(i)) => i.into_lua(lua), + PropValue::Numeric(NumericValue::Discrete(i)) => i.into_lua(lua), + PropValue::Boolean(state) => state.into_lua(lua), + PropValue::Text(t) => t.into_lua(lua), + PropValue::Color(c) => { + let script_color = ScriptColor::from(c); + lua.to_value(&script_color) + } + PropValue::DurationInMillis(d) => d.into_lua(lua), + } + } +} + +impl<'a> FeedbackScript<'a> for LuaFeedbackScript<'a> { + type AdditionalInput = AdditionalLuaFeedbackScriptInput<'a>; + + fn feedback( + &self, + input: FeedbackScriptInput, + additional_input: Self::AdditionalInput, + ) -> Result> { + self.feedback_internal(input, additional_input) + .map_err(|e| e.to_string().into()) + } + + fn used_props(&self) -> Result, Box> { + let prop_provider = TrackingPropProvider::default(); + let input = FeedbackScriptInput { + prop_provider: &prop_provider, + }; + self.feedback_internal(input, Default::default())?; + Ok(prop_provider.used_props.take()) + } +} + +#[derive(Default)] +struct TrackingPropProvider { + used_props: RefCell>, +} + +impl PropProvider for TrackingPropProvider { + fn get_prop_value(&self, key: &str) -> Option { + self.used_props.borrow_mut().insert(key.to_string()); + None + } +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +struct LuaScriptFeedbackOutput { + feedback_event: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use helgoboss_learn::{ + AbsoluteValue, FeedbackStyle, NumericFeedbackValue, PropValue, RgbColor, + TextualFeedbackValue, UnitValue, + }; + + #[test] + fn used_props() { + // Given + let text = r#" + local foo = context.prop("bye") + local bla = context.prop("hello") + return { + feedback_event = nil + } + "#; + let lua = SafeLua::new().unwrap(); + let script = LuaFeedbackScript::compile(&lua, text).unwrap(); + // When + let used_props = script.used_props().unwrap(); + // Then + let expected: NonCryptoHashSet<_> = ["hello".to_string(), "bye".to_string()] + .into_iter() + .collect(); + assert_eq!(used_props, expected); + } + + #[test] + fn off_feedback() { + // Given + let text = r#" + return { + feedback_event = nil + } + "#; + let lua = SafeLua::new().unwrap(); + let script = LuaFeedbackScript::compile(&lua, text).unwrap(); + // When + let input = FeedbackScriptInput { + prop_provider: &|_: &str| None, + }; + let output = script.feedback(input, Default::default()).unwrap(); + // Then + assert_eq!(output.feedback_value, FeedbackValue::Off); + } + + #[test] + fn numeric_feedback() { + // Given + let text = r#" + return { + feedback_event = { + value = 5, + color = { r = 23, g = 5, b = 122 }, + }, + } + "#; + let lua = SafeLua::new().unwrap(); + let script = LuaFeedbackScript::compile(&lua, text).unwrap(); + // When + let input = FeedbackScriptInput { + prop_provider: &|_: &str| None, + }; + let output = script.feedback(input, Default::default()).unwrap(); + // Then + assert_eq!( + output.feedback_value, + FeedbackValue::Numeric(NumericFeedbackValue::new( + FeedbackStyle { + color: Some(RgbColor::new(23, 5, 122)), + background_color: None, + }, + AbsoluteValue::Continuous(UnitValue::MAX) + )) + ); + } + + #[test] + fn text_feedback() { + // Given + let text = r#" + return { + feedback_event = { + value = "hello" + }, + } + "#; + let lua = SafeLua::new().unwrap(); + let script = LuaFeedbackScript::compile(&lua, text).unwrap(); + // When + let input = FeedbackScriptInput { + prop_provider: &|_: &str| None, + }; + let output = script.feedback(input, Default::default()).unwrap(); + // Then + assert_eq!( + output.feedback_value, + FeedbackValue::Textual(TextualFeedbackValue::new( + FeedbackStyle::default(), + "hello".into() + )) + ); + } + + #[test] + fn text_feedback_with_props() { + // Given + let text = r#" + return { + feedback_event = { + value = context.prop("name") + }, + } + "#; + let lua = SafeLua::new().unwrap(); + let script = LuaFeedbackScript::compile(&lua, text).unwrap(); + // When + let input = FeedbackScriptInput { + prop_provider: &|key: &str| match key { + "name" => Some(PropValue::Text("hello".into())), + _ => None, + }, + }; + let output = script.feedback(input, Default::default()).unwrap(); + // Then + assert_eq!( + output.feedback_value, + FeedbackValue::Textual(TextualFeedbackValue::new( + FeedbackStyle::default(), + "hello".into() + )) + ); + } +} diff --git a/plugin-reaper-realearn/main/src/domain/lua_midi_source_script.rs b/plugin-reaper-realearn/main/src/domain/lua_midi_source_script.rs new file mode 100644 index 0000000..46f773c --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/lua_midi_source_script.rs @@ -0,0 +1,293 @@ +use crate::domain::{lua_module_path_without_ext, SafeLua, ScriptColor, ScriptFeedbackEvent}; +use anyhow::ensure; +use helgoboss_learn::{ + AbsoluteValue, FeedbackValue, MidiSourceAddress, MidiSourceScript, MidiSourceScriptOutcome, + RawMidiEvent, +}; +use mlua::{Function, IntoLua, Lua, LuaSerdeExt, Table, Value}; +use std::borrow::Cow; + +#[derive(Copy, Clone, Debug, Default)] +pub struct AdditionalLuaMidiSourceScriptInput<'a> { + pub compartment_lua: Option<&'a mlua::Value>, +} + +#[derive(Debug)] +pub struct LuaMidiSourceScript<'lua> { + lua: &'lua SafeLua, + function: Function, + env: Table, + y_key: Value, + context_key: Value, +} + +unsafe impl Send for LuaMidiSourceScript<'_> {} + +impl<'lua> LuaMidiSourceScript<'lua> { + pub fn compile(lua: &'lua SafeLua, lua_script: &str) -> anyhow::Result { + ensure!(!lua_script.trim().is_empty(), "script empty"); + let env = lua.create_fresh_environment(false)?; + // Compile + let function = lua.compile_as_function("MIDI source script", lua_script, env.clone())?; + let script = Self { + lua, + env, + function, + y_key: "y".into_lua(lua.as_ref())?, + context_key: "context".into_lua(lua.as_ref())?, + }; + Ok(script) + } +} + +#[derive(serde::Serialize)] +struct ScriptContext { + feedback_event: ScriptFeedbackEvent, +} + +impl<'a, 'lua: 'a> MidiSourceScript<'a> for LuaMidiSourceScript<'lua> { + type AdditionalInput = AdditionalLuaMidiSourceScriptInput<'a>; + + fn execute( + &self, + input_value: FeedbackValue, + additional_input: Self::AdditionalInput, + ) -> Result> { + // TODO-medium We don't limit the time of each execution at the moment because not sure + // how expensive this measurement is. But it would actually be useful to do it for MIDI + // scripts! + // Build input data + let context = ScriptContext { + feedback_event: ScriptFeedbackEvent { + value: None, + color: input_value.color().map(ScriptColor::from), + background_color: input_value.background_color().map(ScriptColor::from), + }, + }; + let y_value = match input_value { + FeedbackValue::Off => Value::Nil, + FeedbackValue::Numeric(n) => match n.value { + AbsoluteValue::Continuous(v) => Value::Number(v.get()), + AbsoluteValue::Discrete(f) => Value::Integer(f.actual() as _), + }, + FeedbackValue::Textual(v) => v + .text + .into_lua(self.lua.as_ref()) + .map_err(|_| "couldn't convert string to Lua string")?, + FeedbackValue::Complex(v) => self + .lua + .as_ref() + .to_value(&v.value) + .map_err(|_| "couldn't convert complex value to Lua value")?, + }; + // Set input data as variables "y" and "context". + self.env + .raw_set(self.y_key.clone(), y_value) + .map_err(|_| "couldn't set y variable")?; + // This is important, otherwise e.g. a None color ends up as some userdata and not nil. + let mut serialize_options = mlua::SerializeOptions::new(); + serialize_options.serialize_none_to_null = false; + serialize_options.serialize_unit_to_null = false; + let context_lua_value = self + .lua + .as_ref() + .to_value_with(&context, serialize_options) + .unwrap(); + self.env + .raw_set(self.context_key.clone(), context_lua_value) + .map_err(|_| "couldn't set context variable")?; + // The rest is scoped because we want to create a scoped function + let value = self.lua + .as_ref() + .scope(|scope| { + // Set require function + let require = scope.create_function(move |lua, path: String| { + let val = match lua_module_path_without_ext(&path) { + LUA_MIDI_SCRIPT_SOURCE_RUNTIME_NAME => create_lua_midi_script_source_runtime(lua), + "compartment" => { + additional_input.compartment_lua.cloned().unwrap_or(Value::Nil) + }, + _ => return Err(mlua::Error::runtime(format!("MIDI scripts don't support the usage of 'require' for anything else than '{LUA_MIDI_SCRIPT_SOURCE_RUNTIME_NAME}' and 'compartment'!"))) + }; + Ok(val) + }) + .map_err(mlua::Error::runtime)?; + self.env.raw_set("require", require) + .map_err(mlua::Error::runtime)?; + // Invoke script + let value: Value = self.function.call(()).map_err(mlua::Error::runtime)?; + Ok(value) + }) + .map_err(|e| { + let error = e.to_string(); + tracing::debug!(msg = "Failed to execute Lua MIDI source script", %error); + error + })?; + // Process return value + let outcome: LuaScriptOutcome = self + .lua + .as_ref() + .from_value(value) + .map_err(|_| "Lua script result has wrong type")?; + let events = outcome + .messages + .into_iter() + .flat_map(|msg| RawMidiEvent::try_from_slice(0, &msg)) + .collect(); + let outcome = MidiSourceScriptOutcome { + address: outcome + .address + .map(|bytes| MidiSourceAddress::Script { bytes }), + events, + }; + Ok(outcome) + } +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +struct LuaScriptOutcome { + address: Option, + messages: Vec>, +} + +pub fn create_lua_midi_script_source_runtime(_lua: &Lua) -> mlua::Value { + // At the moment, the MIDI script source runtime doesn't contain any functions, just types. + // That means it's only relevant for autocompletion in the IDE. We can return nil. + Value::Nil +} + +pub const LUA_MIDI_SCRIPT_SOURCE_RUNTIME_NAME: &str = "midi_script_source_runtime"; + +#[cfg(test)] +mod tests { + use super::*; + use helgoboss_learn::{ + FeedbackStyle, NumericFeedbackValue, RgbColor, TextualFeedbackValue, UnitValue, + }; + + #[test] + fn basics() { + // Given + let text = " + return { + address = 0x4bb0, + messages = { + { 0xb0, 0x4b, math.floor(y * 10) } + } + } + "; + let lua = SafeLua::new().unwrap(); + let script = LuaMidiSourceScript::compile(&lua, text).unwrap(); + // When + let fb_value = NumericFeedbackValue::new( + FeedbackStyle::default(), + AbsoluteValue::Continuous(UnitValue::new(0.5)), + ); + let outcome = script + .execute(FeedbackValue::Numeric(fb_value), Default::default()) + .unwrap(); + // Then + assert_eq!( + outcome.address, + Some(MidiSourceAddress::Script { bytes: 0x4bb0 }) + ); + assert_eq!( + outcome.events, + vec![RawMidiEvent::try_from_slice(0, &[0xb0, 0x4b, 5]).unwrap()] + ); + } + + #[test] + fn text_feedback_value() { + // Given + let text = " + local lookup_table = { + playing = 5, + stopped = 6, + paused = 7, + } + return { + messages = { + { 0xb0, 0x4b, lookup_table[y] or 0 } + } + } + "; + let lua = SafeLua::new().unwrap(); + let script = LuaMidiSourceScript::compile(&lua, text).unwrap(); + // When + let matched_outcome = script + .execute( + FeedbackValue::Textual(TextualFeedbackValue::new( + FeedbackStyle::default(), + "playing".into(), + )), + Default::default(), + ) + .unwrap(); + let unmatched_outcome = script + .execute( + FeedbackValue::Numeric(NumericFeedbackValue::new( + FeedbackStyle::default(), + AbsoluteValue::Continuous(UnitValue::MAX), + )), + Default::default(), + ) + .unwrap(); + // Then + assert_eq!(matched_outcome.address, None); + assert_eq!( + matched_outcome.events, + vec![RawMidiEvent::try_from_slice(0, &[0xb0, 0x4b, 5]).unwrap()] + ); + assert_eq!(unmatched_outcome.address, None); + assert_eq!( + unmatched_outcome.events, + vec![RawMidiEvent::try_from_slice(0, &[0xb0, 0x4b, 0]).unwrap()] + ); + } + + #[test] + fn colors() { + // Given + let text = " + local color = context.feedback_event.color + if color == nil then + -- This means no specific color is set. Choose whatever you need. + color = { r = 0, g = 0, b = 0 } + end + return { + -- A unique number that identifies the LED/display. + -- (Necessary if you want correct lights-off behavior and coordination + -- between multiple mappings using the same LED/display). + address = 0x4b, + -- Whatever messages your device needs to set that color. + messages = { + { 0xf0, 0x02, 0x4b, color.r, color.g, color.b, 0xf7 } + } + } + "; + let lua = SafeLua::new().unwrap(); + let script = LuaMidiSourceScript::compile(&lua, text).unwrap(); + // When + let style = FeedbackStyle { + color: Some(RgbColor::new(255, 0, 255)), + background_color: None, + }; + let value = NumericFeedbackValue::new(style, Default::default()); + let outcome = script + .execute(FeedbackValue::Numeric(value), Default::default()) + .unwrap(); + // Then + assert_eq!( + outcome.address, + Some(MidiSourceAddress::Script { bytes: 0x4b }) + ); + assert_eq!( + outcome.events, + vec![ + RawMidiEvent::try_from_slice(0, &[0xf0, 0x02, 0x4b, 0xff, 0x00, 0xff, 0xf7]) + .unwrap() + ] + ); + } +} diff --git a/plugin-reaper-realearn/main/src/domain/lua_module_container.rs b/plugin-reaper-realearn/main/src/domain/lua_module_container.rs new file mode 100644 index 0000000..5bb3e09 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/lua_module_container.rs @@ -0,0 +1,277 @@ +use crate::domain::{compile_and_execute, create_fresh_environment}; +use anyhow::{bail, Context}; +use auto_impl::auto_impl; +use camino::{Utf8Path, Utf8PathBuf}; +use include_dir::Dir; +use mlua::{Function, Lua, Value}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::fs; +use std::rc::Rc; + +/// Allows executing Lua code as a module that may require other modules. +pub struct LuaModuleContainer { + finder: Result, + // modules: NonCryptoHashMap>, +} + +/// Trait for resolving Lua modules. +#[auto_impl(Rc)] +pub trait LuaModuleFinder { + /// Returns a short information that let's the user know what's the root of the module tree (e.g. a path). + fn module_root_path(&self) -> String; + + /// Returns the source of the Lua module at the given path or `None` if Lua module not found or doesn't have + /// UTF8-encoded content. + /// + /// Requirements: + /// + /// - The passed path must not start with a slash. + /// - The passed path should not contain .. or . components. If they do, behavior is undefined. + fn find_source_by_path(&self, path: &str) -> Option>; +} + +impl LuaModuleContainer +where + F: LuaModuleFinder + Clone + 'static, +{ + /// Creates the module container using the given module finder. + /// + /// If you pass `None`, executing Lua code will still work but any usage of `require` will yield a readable error + /// message. This way, we can inform users in scenarios where `require` intentionally is not allowed. + pub fn new(finder: Result) -> Self { + Self { finder } + } + + pub fn execute_as_module( + &self, + lua: &Lua, + normalized_path: Option, + display_name: String, + code: &str, + ) -> anyhow::Result { + execute_as_module( + lua, + normalized_path, + display_name, + code, + self.finder.clone(), + SharedAccumulator::default(), + ) + } +} + +#[derive(Default)] +struct Accumulator { + required_modules_stack: Vec, +} + +impl Accumulator { + /// The given module must be normalized, i.e. it should contain the extension. + pub fn push_module(&mut self, normalized_path: String) -> anyhow::Result<()> { + let stack = &mut self.required_modules_stack; + tracing::debug!(msg = "Pushing module onto stack", %normalized_path, ?stack); + if stack.iter().any(|path| path == &normalized_path) { + bail!("Detected cyclic Lua module dependency: {normalized_path}"); + } + stack.push(normalized_path); + Ok(()) + } + + pub fn pop_module(&mut self) { + let stack = &mut self.required_modules_stack; + tracing::debug!(msg = "Popping top module from stack", ?stack); + stack.pop(); + } +} + +type SharedAccumulator = Rc>; + +fn find_and_execute_module( + lua: &Lua, + finder: impl LuaModuleFinder + Clone + 'static, + accumulator: SharedAccumulator, + required_path: &str, +) -> anyhow::Result { + // Validate + let root_info = || format!("\n\nModule root path: {}", finder.module_root_path()); + let path = Utf8Path::new(required_path); + if path.is_absolute() { + bail!("Required paths must not start with a slash. They are always relative to the preset sub directory.{}", root_info()); + } + if path + .components() + .any(|comp| matches!(comp.as_str(), "." | "..")) + { + bail!("Required paths containing . or .. are forbidden. They are always relative to the preset sub directory.{}", root_info()); + } + // Substitute preset runtime stub + if lua_module_path_without_ext(path.as_str()) == LUA_PRESET_RUNTIME_NAME { + let table = lua.create_table()?; + let finder = finder.clone(); + let include_str = lua.create_function(move |_, path: String| { + let content = finder + .find_source_by_path(&path) + .map(|content| content.to_string()); + Ok(content) + })?; + table.set("include_str", include_str)?; + return Ok(Value::Table(table)); + } + // Find module and get its source + let (normalized_path, source) = if path + .extension() + .is_some_and(|ext| matches!(ext, "luau" | "lua")) + { + // Extension given. Just get file directly. + let source = finder + .find_source_by_path(path.as_str()) + .with_context(|| format!("Couldn't find Lua module [{path}].{}", root_info()))?; + (path.to_string(), source) + } else { + // No extension given. Try ".luau" and ".lua". + ["luau", "lua"] + .into_iter() + .find_map(|ext| { + let path_with_extension = format!("{path}.{ext}"); + tracing::debug!(msg = "Finding module by path...", %path_with_extension); + let source = finder.find_source_by_path(&path_with_extension)?; + Some((path_with_extension, source)) + }) + .with_context(|| { + format!( + "Couldn't find Lua module [{path}]. Tried with extension \".lua\" and \".luau\".{}", root_info() + ) + })? + }; + // Execute module + execute_as_module( + lua, + Some(normalized_path.clone()), + normalized_path, + source.as_ref(), + Ok(finder), + accumulator, + ) +} + +pub fn lua_module_path_without_ext(path: &str) -> &str { + path.strip_suffix(".luau") + .or_else(|| path.strip_suffix(".lua")) + .unwrap_or(path) +} + +fn execute_as_module( + lua: &Lua, + normalized_path: Option, + display_name: String, + code: &str, + finder: Result, + accumulator: SharedAccumulator, +) -> anyhow::Result { + let env = create_fresh_environment(lua, true)?; + let require = create_require_function(lua, finder, accumulator.clone())?; + env.set("require", require)?; + let pop_later = if let Some(p) = normalized_path { + accumulator.borrow_mut().push_module(p)?; + true + } else { + false + }; + let value = compile_and_execute(lua, display_name.clone(), code, env) + .with_context(|| format!("Couldn't compile and execute Lua module {display_name}"))?; + if pop_later { + accumulator.borrow_mut().pop_module(); + } + Ok(value) + // TODO-medium-performance Instead of just detecting cycles, we could cache the module execution result and return + // it whenever it's queried again. + // match self.modules.entry(path) { + // Entry::Occupied(e) => Ok(e.into_mut()), + // Entry::Vacant(e) => { + // let path = e.key(); + // let source = self + // .finder + // .find_source_by_path(path) + // .with_context(|| format!("Couldn't find Lua module {path}"))?; + // let env = safe_lua.create_fresh_environment(true)?; + // let value = safe_lua + // .compile_and_execute("Module", source.as_ref(), env) + // .with_context(|| format!("Couldn't compile and execute Lua module {path}"))?; + // Ok(e.insert(value)) + // } + // } +} + +fn create_require_function( + lua: &Lua, + finder: Result, + accumulator: SharedAccumulator, +) -> anyhow::Result { + let require = lua.create_function_mut(move |lua, required_path: String| { + let finder = finder.clone().map_err(mlua::Error::runtime)?; + let value = + find_and_execute_module(lua, finder.clone(), accumulator.clone(), &required_path) + .map_err(|e| mlua::Error::runtime(format!("{e:#}")))?; + Ok(value) + })?; + Ok(require) +} + +/// Files Lua modules within a specified binary-included directory. +#[derive(Clone)] +pub struct IncludedDirLuaModuleFinder { + dir: Dir<'static>, +} + +impl IncludedDirLuaModuleFinder { + pub fn new(dir: Dir<'static>) -> Self { + Self { dir } + } +} + +impl LuaModuleFinder for IncludedDirLuaModuleFinder { + fn module_root_path(&self) -> String { + "factory:/".to_string() + } + + fn find_source_by_path(&self, path: &str) -> Option> { + let contents = self.dir.get_file(path)?.contents_utf8()?; + Some(contents.into()) + } +} + +/// Files Lua modules within a specified file-system directory. +#[derive(Clone)] +pub struct FsDirLuaModuleFinder { + dir: Utf8PathBuf, +} + +impl FsDirLuaModuleFinder { + pub fn new(dir: Utf8PathBuf) -> Self { + Self { dir } + } +} + +impl LuaModuleFinder for FsDirLuaModuleFinder { + fn module_root_path(&self) -> String { + self.dir.to_string() + } + + fn find_source_by_path(&self, path: &str) -> Option> { + let path = Utf8Path::new(path); + // It's a precondition by contract that the given path is not absolute. However, in order to fail + // fast in case this precondition is missed, we check again here. Because on a file system, absolute + // files can actually work, but we don't want it to work. + if path.is_absolute() { + return None; + } + let absolute_path = self.dir.join(path); + tracing::debug!(msg = "find_source_by_path", ?absolute_path); + let content = fs::read_to_string(absolute_path).ok()?; + tracing::debug!(msg = "find_source_by_path successful"); + Some(content.into()) + } +} + +const LUA_PRESET_RUNTIME_NAME: &str = "preset_runtime"; diff --git a/plugin-reaper-realearn/main/src/domain/lua_script_commons.rs b/plugin-reaper-realearn/main/src/domain/lua_script_commons.rs new file mode 100644 index 0000000..7cc5084 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/lua_script_commons.rs @@ -0,0 +1,66 @@ +use helgoboss_learn::{ + AbsoluteValue, ComplexFeedbackValue, FeedbackStyle, FeedbackValue, NumericFeedbackValue, + RgbColor, TextualFeedbackValue, UnitValue, +}; + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct ScriptFeedbackEvent { + pub value: Option, + pub color: Option, + pub background_color: Option, +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +pub enum ScriptFeedbackValue { + Unit(f64), + Text(String), + Complex(serde_json::Value), +} + +impl ScriptFeedbackEvent { + pub fn into_api_feedback_value(self) -> FeedbackValue<'static> { + let style = FeedbackStyle { + color: self.color.map(|c| c.into()), + background_color: self.background_color.map(|c| c.into()), + }; + match self.value { + None => FeedbackValue::Off, + Some(ScriptFeedbackValue::Unit(v)) => { + FeedbackValue::Numeric(NumericFeedbackValue::new( + style, + AbsoluteValue::Continuous(UnitValue::new_clamped(v)), + )) + } + Some(ScriptFeedbackValue::Text(t)) => { + FeedbackValue::Textual(TextualFeedbackValue::new(style, t.into())) + } + Some(ScriptFeedbackValue::Complex(v)) => { + FeedbackValue::Complex(ComplexFeedbackValue::new(style, v)) + } + } + } +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct ScriptColor { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl From for ScriptColor { + fn from(c: RgbColor) -> Self { + Self { + r: c.r(), + g: c.g(), + b: c.b(), + } + } +} + +impl From for RgbColor { + fn from(c: ScriptColor) -> Self { + Self::new(c.r, c.g, c.b) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/lua_support.rs b/plugin-reaper-realearn/main/src/domain/lua_support.rs new file mode 100644 index 0000000..911dc00 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/lua_support.rs @@ -0,0 +1,280 @@ +use anyhow::anyhow; +use mlua::serde::de; +use mlua::{ChunkMode, Function, Lua, Table, Value, VmState}; +use serde::de::DeserializeOwned; +use std::error::Error; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct SafeLua(Lua); + +impl SafeLua { + /// Creates the Lua state. + pub fn new() -> anyhow::Result { + let lua = Lua::new(); + // TODO-medium Maybe we can avoid having to build the safe Lua environment for each + // compilation/create-fresh-env step by doing something like the following. + // // Build safe globals based on original globals + // let safe_globals = build_safe_lua_env(&lua, lua.globals())?; + // // Empty original globals + // let original_keys: Vec = lua + // .globals() + // .pairs::() + // .flat_map(|res| res?.0) + // .collect(); + // let globals = lua.globals(); + // for key in original_keys { + // globals.raw_remove(key)?; + // } + // // Fill original globals with safe globals + // for pair in safe_globals.pairs::() { + // let (key, value) = pair?; + // globals[key] = value; + // } + Ok(Self(lua)) + } + + pub fn from_value(value: Value) -> anyhow::Result + where + T: DeserializeOwned, + { + let result = T::deserialize(de::Deserializer::new(value))?; + Ok(result) + } + + /// Compiles as a function with return value (for later execution). + pub fn compile_as_function( + &self, + name: &str, + code: &str, + env: Table, + ) -> anyhow::Result { + let chunk = self + .0 + .load(code) + .set_name(name) + .set_environment(env) + .set_mode(ChunkMode::Text); + let function = chunk.into_function()?; + Ok(function) + } + + /// Compiles and executes the given code in one go (shouldn't be used for repeated execution!). + pub fn compile_and_execute( + &self, + display_name: String, + code: &str, + env: Table, + ) -> anyhow::Result { + compile_and_execute(&self.0, display_name, code, env) + } + + /// Creates a fresh environment for this Lua state. + /// + /// Setting `allow_side_effects` unlocks a few more vars, but only use that if you boot up a + /// fresh Lua state for each execution. + pub fn create_fresh_environment(&self, allow_side_effects: bool) -> anyhow::Result { + create_fresh_environment(&self.0, allow_side_effects) + } + + /// Call before executing user code in order to prevent code from taking too long to execute. + pub fn start_execution_time_limit_countdown(&self) { + const MAX_DURATION: Duration = Duration::from_millis(1000); + let instant = Instant::now(); + self.0.set_interrupt(move |_lua| { + if instant.elapsed() > MAX_DURATION { + Err(mlua::Error::ExternalError(Arc::new( + RealearnScriptError::Timeout, + ))) + } else { + Ok(VmState::Continue) + } + }); + } +} + +/// Creates a fresh environment for this Lua state. +/// +/// Setting `allow_side_effects` unlocks a few more vars, but only use that if you boot up a +/// fresh Lua state for each execution. +pub fn create_fresh_environment(lua: &Lua, allow_side_effects: bool) -> anyhow::Result
{ + build_safe_lua_env(lua, lua.globals(), allow_side_effects) +} + +/// Compiles and executes the given code in one go (shouldn't be used for repeated execution!). +pub fn compile_and_execute( + lua: &Lua, + display_name: String, + code: &str, + env: Table, +) -> anyhow::Result { + let lua_chunk = lua + .load(code) + .set_name(display_name) + .set_mode(ChunkMode::Text) + .set_environment(env); + let value = lua_chunk.eval().map_err(|e| match e { + // Box the cause if it's a callback error (used for the execution time limit feature). + mlua::Error::CallbackError { cause, .. } => { + anyhow!(cause) + } + e => anyhow!(e), + })?; + Ok(value) +} + +impl AsRef for SafeLua { + fn as_ref(&self) -> &Lua { + &self.0 + } +} + +#[derive(Debug, derive_more::Display)] +enum RealearnScriptError { + #[display(fmt = "Helgobox script took too long to execute")] + Timeout, +} + +impl Error for RealearnScriptError {} + +/// Creates a Lua environment in which we can't execute potentially malicious code +/// (by only including safe functions according to http://lua-users.org/wiki/SandBoxes). +/// +/// Setting `allow_side_effects` unlocks a few more vars, but only use that if you boot up a +/// fresh Lua state for each execution. +fn build_safe_lua_env( + lua: &Lua, + original_env: Table, + allow_side_effects: bool, +) -> anyhow::Result
{ + let safe_env = lua.create_table()?; + for var in SAFE_LUA_VARS { + copy_var_to_table(lua, &safe_env, &original_env, var)?; + } + if allow_side_effects { + for var in EXTENDED_SAFE_LUA_VARS { + copy_var_to_table(lua, &safe_env, &original_env, var)?; + } + } + Ok(safe_env) +} + +fn copy_var_to_table( + lua: &Lua, + dest_table: &Table, + src_table: &Table, + var: &str, +) -> anyhow::Result<()> { + if let Some(dot_index) = var.find('.') { + // Nested variable + let parent_var = &var[0..dot_index]; + let nested_dest_table = if let Ok(t) = dest_table.get(parent_var) { + t + } else { + let new_table = lua.create_table()?; + dest_table.set(parent_var, new_table.clone())?; + new_table + }; + let nested_src_table: Table = src_table.get(parent_var)?; + let child_var = &var[dot_index + 1..]; + copy_var_to_table(lua, &nested_dest_table, &nested_src_table, child_var)?; + Ok(()) + } else { + // Leaf variable + let original_value: Value = src_table.get(var)?; + dest_table.set(var, original_value)?; + Ok(()) + } +} + +/// Safe Lua vars according to http://lua-users.org/wiki/SandBoxes. +/// +/// Even a bit more restrictive because we don't include `io` and `coroutine`. +const SAFE_LUA_VARS: &[&str] = &[ + "assert", + "error", + "ipairs", + "next", + "pairs", + "pcall", + "print", + "select", + "tonumber", + "tostring", + "type", + "unpack", + "_VERSION", + "xpcall", + "string.byte", + "string.char", + "string.find", + "string.format", + "string.gmatch", + "string.gsub", + "string.len", + "string.lower", + "string.match", + "string.rep", + "string.reverse", + "string.sub", + "string.upper", + // "table.clone" is available in Luau only + "table.clone", + "table.insert", + "table.maxn", + "table.remove", + "table.sort", + "math.abs", + "math.acos", + "math.asin", + "math.atan", + "math.atan2", + "math.ceil", + "math.cos", + "math.cosh", + "math.deg", + "math.exp", + "math.floor", + "math.fmod", + "math.frexp", + "math.huge", + "math.ldexp", + "math.log", + "math.log10", + "math.max", + "math.min", + "math.modf", + "math.pi", + "math.pow", + "math.rad", + "math.random", + "math.sin", + "math.sinh", + "math.sqrt", + "math.tan", + "math.tanh", + "os.clock", + "os.difftime", + "os.time", + // bit32 (Lua 5.2 & Luau intersection) + "bit32.arshift", + "bit32.band", + "bit32.bnot", + "bit32.bor", + "bit32.btest", + "bit32.bxor", + "bit32.extract", + "bit32.lrotate", + "bit32.lshift", + "bit32.replace", + "bit32.rrotate", + "bit32.rshift", +]; + +/// An extended set of Lua vars that can be considered safe under certain circumstances. +/// +/// Some vars are unsafe according to http://lua-users.org/wiki/SandBoxes, but only because of the +/// side effects that it could have on other code executed in the same Lua state. In situations +/// where we create a fresh Lua state everytime, this doesn't matter. +const EXTENDED_SAFE_LUA_VARS: &[&str] = &["setmetatable"]; diff --git a/plugin-reaper-realearn/main/src/domain/main_processor.rs b/plugin-reaper-realearn/main/src/domain/main_processor.rs new file mode 100644 index 0000000..413dcd8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/main_processor.rs @@ -0,0 +1,4610 @@ +use crate::domain::{ + aggregate_target_values, format_as_pretty_hex, get_project_options, say, + AdditionalFeedbackEvent, AdditionalLuaFeedbackScriptInput, AdditionalLuaMidiSourceScriptInput, + Backbone, CompartmentKind, CompoundChangeEvent, CompoundFeedbackValue, CompoundMappingSource, + CompoundMappingSourceAddress, CompoundMappingTarget, ControlContext, ControlEvent, + ControlEventTimestamp, ControlInput, ControlLogContext, ControlLogEntry, ControlLogEntryKind, + ControlMode, ControlOutcome, DeviceFeedbackOutput, DomainEvent, DomainEventHandler, + ExtendedProcessorContext, FeedbackAudioHookTask, FeedbackCause, FeedbackCollector, + FeedbackDestinations, FeedbackLogEntry, FeedbackOutput, FeedbackRealTimeTask, + FeedbackResolution, FeedbackSendBehavior, FinalRealFeedbackValue, FinalSourceFeedbackValue, + GlobalControlAndFeedbackState, GroupId, HitInstructionContext, HitInstructionResponse, + InstanceId, InternalInfoEvent, IoUpdatedEvent, KeyMessage, MainMapping, MainSourceMessage, + MappingActivationEffect, MappingControlResult, MappingId, MappingInfo, MessageCaptureEvent, + MessageCaptureResult, MidiControlInput, MidiDestination, MidiScanResult, NoopLogger, + NormalRealTimeTask, OrderedMappingIdSet, OrderedMappingMap, OscDeviceId, OscFeedbackTask, + PluginParamIndex, PluginParams, ProcessorContext, ProjectOptions, ProjectionFeedbackValue, + QualifiedInstanceEvent, QualifiedMappingId, RawParamValue, RealTimeMappingUpdate, + RealTimeTargetUpdate, RealearnModeContext, RealearnMonitoringFxParameterValueChangedEvent, + RealearnParameterChangePayload, RealearnSourceContext, ReaperConfigChange, ReaperMessage, + ReaperSourceFeedbackValue, ReaperTarget, SharedInstance, SharedUnit, SourceFeedbackEvent, + SourceFeedbackLogger, SourceReleasedEvent, SpecificCompoundFeedbackValue, StreamDeckDeviceId, + StreamDeckMessage, StreamDeckSourceFeedbackValue, TargetControlEvent, TargetValueChangedEvent, + UnitContainer, UnitEvent, UnitOrchestrationEvent, UpdatedSingleMappingOnStateEvent, + VirtualControlElement, VirtualSourceValue, +}; +use derive_more::Display; +use enum_map::EnumMap; +use helgoboss_learn::{ + AbsoluteValue, ControlValue, GroupInteraction, MidiSourceValue, MinIsMaxBehavior, + ModeControlOptions, RawMidiEvent, Target, BASE_EPSILON, +}; +use std::borrow::Cow; +use std::cell::RefCell; + +use crate::domain::ui_util::{ + format_control_input_with_match_result, format_incoming_midi_message, format_midi_source_value, + format_osc_message, format_osc_packet, log_lifecycle_output, log_real_control_input, + log_real_feedback_output, log_real_learn_input, log_target_control, log_target_output, + log_virtual_control_input, log_virtual_feedback_output, +}; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet, NonCryptoIndexSet}; +use base::{hash_util, NamedChannelSender, SenderToNormalThread, SenderToRealTimeThread}; +use helgoboss_midi::{ControlChange14BitMessage, ParameterNumberMessage, RawShortMessage}; +use playtime_api::runtime::ControlUnitId; +use reaper_high::{ChangeEvent, Reaper}; +use reaper_medium::ReaperNormalizedFxParamValue; +use rosc::{OscMessage, OscPacket, OscType}; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::fmt; +use std::fmt::Display; +use std::hash::{Hash, Hasher}; +use std::rc::Rc; +use std::sync::atomic::{AtomicU32, Ordering}; +use strum::EnumIter; +use tracing::{debug, trace}; + +/// This can be come pretty big when multiple track volumes are adjusted at once. +const FEEDBACK_TASK_QUEUE_SIZE: usize = 20_000; +/// This has been increased because the scenario in #913 made messages pile up in this channel. +const NORMAL_TASK_BULK_SIZE: usize = 1000; +/// This has been increased because of the insight that letting messages pile up is worse than +/// having a few cycles that take longer. +const NORMAL_RT_TASK_BULK_SIZE: usize = 1000; +/// This has been increased because of the insight that letting messages pile up is worse than +/// having a few cycles that take longer. +const FEEDBACK_TASK_BULK_SIZE: usize = 1000; +/// This has been increased because of the insight that letting messages pile up is worse than +/// having a few cycles that take longer. +const CONTROL_TASK_BULK_SIZE: usize = 1000; +// I raised this from 32 to the max channel size because of +// https://github.com/helgoboss/helgobox/issues/847 (otherwise it can easily happen with lots of +// parameter modulation that the channel runs full) +const PARAMETER_TASK_BULK_SIZE: usize = 500; + +pub type SharedMainProcessors = Rc>>>; + +#[derive(Debug)] +pub struct MainProcessor { + basics: Basics, + collections: Collections, + /// Contains IDs of those mappings who need to be polled as frequently as possible. + poll_control_mappings: EnumMap, +} + +#[derive(Debug)] +struct Basics { + instance_id: InstanceId, + unit_id: UnitId, + common_lua: EnumMap>, + unit_container: &'static dyn UnitContainer, + settings: BasicSettings, + control_is_globally_enabled: bool, + // TODO-medium Now that we communicate the feedback output separately, we could limit the scope + // of its meaning to "instance enabled etc." + feedback_is_globally_enabled: bool, + event_handler: EH, + context: ProcessorContext, + control_mode: ControlMode, + // It's okay to not use a weak instance here because the main processor / unit lives shorter + // than the instance. It can't lead to the instance not being disposed. + instance: SharedInstance, + unit: SharedUnit, + channels: Channels, + // Using RefCell in the processing layer is an exception. We do it here because we can't + // safely make feedback processing mutable. I tried (see branch + // "experiment/feedback-change-detection-mutable") but it the end it turned out to be impossible + // because the reaper-rs control surface doesn't emit feedback-triggering events in a mutable + // context. Rightfully so, because it's potentially reentrant! + // TODO-low This reason is now outdated. We detected a general issue with reentrancy. + // https://github.com/helgoboss/reaper-rs/issues/54 + last_feedback_checksum_by_address: + RefCell>, + target_based_conditional_activation_processors: + EnumMap, +} + +/// Used for detecting and preventing subsequent duplicate feedback. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum FeedbackChecksum { + MidiPlain(RawShortMessage), + MidiParameterNumber(ParameterNumberMessage), + MidiControlChange14Bit(ControlChange14BitMessage), + // For OSC and raw MIDI + Hashed(u64), +} + +impl FeedbackChecksum { + fn from_value(v: &FinalSourceFeedbackValue) -> Self { + match v { + FinalSourceFeedbackValue::Midi(v) => Self::from_midi(v), + FinalSourceFeedbackValue::Osc(v) => Self::from_osc(v), + FinalSourceFeedbackValue::Reaper(v) => Self::from_reaper(v), + FinalSourceFeedbackValue::StreamDeck(v) => Self::from_stream_deck(v), + } + } + + fn from_midi(v: &MidiSourceValue) -> Self { + use MidiSourceValue::*; + match v { + Plain(v) => FeedbackChecksum::MidiPlain(*v), + ParameterNumber(v) => FeedbackChecksum::MidiParameterNumber(*v), + ControlChange14Bit(v) => FeedbackChecksum::MidiControlChange14Bit(*v), + Raw { events, .. } => { + let hash = hash_util::calculate_non_crypto_hash(events); + FeedbackChecksum::Hashed(hash) + } + Tempo(_) | BorrowedSysEx(_) => unreachable!("never sent as feedback"), + } + } + + fn from_osc(v: &OscMessage) -> Self { + let mut hasher = hash_util::create_non_crypto_hasher(); + // OscMessage doesn't implement Hash, probably because it contains floating point numbers. + // We don't care about floating point hash/equality issues because we just want a checksum + // for comparing current feedback with last feedback. + v.addr.hash(&mut hasher); + for arg in &v.args { + hash_osc_arg(arg, &mut hasher); + } + FeedbackChecksum::Hashed(hasher.finish()) + } + + fn from_stream_deck(v: &StreamDeckSourceFeedbackValue) -> Self { + let mut hasher = hash_util::create_non_crypto_hasher(); + // Doesn't implement Hash, probably because it contains floating point numbers. + // We don't care about floating point hash/equality issues because we just want a checksum + // for comparing current feedback with last feedback. + v.hash(&mut hasher); + FeedbackChecksum::Hashed(hasher.finish()) + } + + fn from_reaper(v: &ReaperSourceFeedbackValue) -> Self { + match v { + ReaperSourceFeedbackValue::Speech(s) => { + let hash = hash_util::calculate_non_crypto_hash_one_shot(s.text.as_bytes()); + FeedbackChecksum::Hashed(hash) + } + } + } +} + +fn hash_osc_arg(arg: &OscType, hasher: &mut H) { + use OscType::*; + match arg { + Int(v) => { + (0, v).hash(hasher); + } + Float(v) => { + (1, v.to_ne_bytes()).hash(hasher); + } + String(v) => { + (2, v).hash(hasher); + } + Blob(v) => { + (3, v).hash(hasher); + } + Time(v) => { + (4, v).hash(hasher); + } + Long(v) => { + (5, v).hash(hasher); + } + Double(v) => { + (6, v.to_ne_bytes()).hash(hasher); + } + Char(v) => { + (7, v).hash(hasher); + } + Color(v) => { + (8, (v.red, v.green, v.red, v.alpha)).hash(hasher); + } + Midi(v) => { + (9, (v.port, v.status, v.data1, v.data2)).hash(hasher); + } + Bool(v) => { + (10, v).hash(hasher); + } + Array(v) => { + 11.hash(hasher); + for a in &v.content { + hash_osc_arg(a, hasher); + } + } + Nil => { + 12.hash(hasher); + } + Inf => { + 13.hash(hasher); + } + } +} + +#[derive(Debug)] +struct Collections { + /// Contains mappings without virtual targets. + mappings: EnumMap>, + /// Contains mappings with virtual targets. + mappings_with_virtual_targets: OrderedMappingMap, + /// Contains IDs of those mappings which should be refreshed as soon as a target is touched. + /// At the moment only "Last touched" targets. + target_touch_dependent_mappings: EnumMap, + /// Contains IDs of those mappings whose feedback might change depending on the current beat. + beat_dependent_feedback_mappings: EnumMap, + /// Contains IDs of those mappings whose feedback might change depending on the current milli. + /// TODO-low The mappings in there are polled regularly (even if main timeline is not playing). + /// could be optimized. However, this is what makes the seek target work currently when + /// changing cursor position while stopped. + milli_dependent_feedback_mappings: EnumMap, + parameters: PluginParams, + previous_target_values: EnumMap>, +} + +#[derive(Debug)] +struct Channels { + self_feedback_sender: SenderToNormalThread, + self_normal_sender: SenderToNormalThread, + normal_task_receiver: crossbeam_channel::Receiver, + normal_real_time_to_main_thread_task_receiver: + crossbeam_channel::Receiver, + feedback_task_receiver: crossbeam_channel::Receiver, + parameter_task_receiver: crossbeam_channel::Receiver, + unit_event_receiver: crossbeam_channel::Receiver, + control_task_receiver: crossbeam_channel::Receiver, + normal_real_time_task_sender: SenderToRealTimeThread, + feedback_real_time_task_sender: SenderToRealTimeThread, + feedback_audio_hook_task_sender: SenderToRealTimeThread, + osc_feedback_task_sender: SenderToNormalThread, + additional_feedback_event_sender: SenderToNormalThread, + unit_orchestration_event_sender: SenderToNormalThread, + integration_test_feedback_sender: Option>, +} + +impl MainProcessor { + #[allow(clippy::too_many_arguments)] + pub fn new( + instance_id: InstanceId, + unit_id: UnitId, + self_normal_sender: SenderToNormalThread, + normal_task_receiver: crossbeam_channel::Receiver, + normal_real_time_to_main_thread_task_receiver: crossbeam_channel::Receiver< + NormalRealTimeToMainThreadTask, + >, + parameter_task_receiver: crossbeam_channel::Receiver, + control_task_receiver: crossbeam_channel::Receiver, + instance_feedback_event_receiver: crossbeam_channel::Receiver, + normal_real_time_task_sender: SenderToRealTimeThread, + feedback_real_time_task_sender: SenderToRealTimeThread, + feedback_audio_hook_task_sender: SenderToRealTimeThread, + additional_feedback_event_sender: SenderToNormalThread, + instance_orchestration_event_sender: SenderToNormalThread, + osc_feedback_task_sender: SenderToNormalThread, + event_handler: EH, + context: ProcessorContext, + instance: SharedInstance, + unit: SharedUnit, + instance_container: &'static dyn UnitContainer, + ) -> MainProcessor { + let (self_feedback_sender, feedback_task_receiver) = + SenderToNormalThread::new_bounded_channel( + "feedback main tasks", + FEEDBACK_TASK_QUEUE_SIZE, + ); + MainProcessor { + basics: Basics { + instance_id, + unit_id, + common_lua: Default::default(), + settings: Default::default(), + control_is_globally_enabled: false, + feedback_is_globally_enabled: false, + event_handler, + context, + control_mode: ControlMode::Controlling, + instance, + unit, + unit_container: instance_container, + channels: Channels { + self_feedback_sender, + self_normal_sender, + normal_task_receiver, + normal_real_time_to_main_thread_task_receiver, + feedback_task_receiver, + parameter_task_receiver, + unit_event_receiver: instance_feedback_event_receiver, + control_task_receiver, + normal_real_time_task_sender, + feedback_real_time_task_sender, + feedback_audio_hook_task_sender, + osc_feedback_task_sender, + additional_feedback_event_sender, + unit_orchestration_event_sender: instance_orchestration_event_sender, + integration_test_feedback_sender: None, + }, + last_feedback_checksum_by_address: Default::default(), + target_based_conditional_activation_processors: Default::default(), + }, + collections: Collections { + mappings: Default::default(), + mappings_with_virtual_targets: Default::default(), + target_touch_dependent_mappings: Default::default(), + beat_dependent_feedback_mappings: Default::default(), + milli_dependent_feedback_mappings: Default::default(), + parameters: Default::default(), + previous_target_values: Default::default(), + }, + poll_control_mappings: Default::default(), + } + } + + pub fn unit_id(&self) -> UnitId { + self.basics.unit_id + } + + /// This is the chance to take over a source from another instance (send our feedback). + /// + /// This is a very important principle when using multiple instances. It allows feedback to + /// not be accidentally cleared while still guaranteeing that feedback for non-used control + /// elements are cleared eventually - independently from the order of instance processing. + pub fn maybe_takeover_source(&self, released_event: &SourceReleasedEvent) -> bool { + if released_event.feedback_output != self.basics.settings.feedback_output { + // Difference feedback device. No source takeover of course. + return false; + } + if let Some(mapping_with_source) = self.all_mappings().find(|m| { + m.feedback_is_effectively_on() + && m.source().has_same_feedback_address_as_value( + &released_event.feedback_value, + self.basics.source_context(m.compartment()), + ) + }) { + if let Some(followed_mapping) = self.follow_maybe_virtual_mapping(mapping_with_source) { + if self.basics.unit_feedback_is_effectively_enabled() { + debug!("Taking over source {:?}...", mapping_with_source.source()); + // TODO-low Shouldn't we update the single mapping-on state here? + let feedback = followed_mapping.feedback( + true, + self.basics.control_context(followed_mapping.compartment()), + ); + self.send_feedback(FeedbackReason::TakeOverSource, feedback); + true + } else { + debug!( + "No source takeover of {:?} because feedback effectively disabled", + mapping_with_source.source() + ); + false + } + } else { + false + } + } else { + false + } + } + + /// We previously wanted to switch off lights for a source but gave other instances the chance + /// to take over. This is called if no takeover happened and it's safe to really turn the lights + /// off. + pub fn finally_switch_off_source( + &self, + feedback_output: Option, + feedback_value: FinalSourceFeedbackValue, + ) { + debug!("Finally switching off source with {:?}...", feedback_value); + self.basics.send_direct_source_feedback( + feedback_output, + FeedbackReason::FinallySwitchOffSource, + FeedbackCause::Normal, + feedback_value, + ); + } + + /// Processes control tasks coming from the real-time processor. + /// + /// This should *not* be called by the control surface when it's globally learning targets + /// because we want to pause controlling in that case! Otherwise we could control targets and + /// they would be learned although not touched via mouse, that's not good. + pub fn run_control(&mut self, timestamp: ControlEventTimestamp) { + let control_is_effectively_enabled = self.basics.unit_control_is_effectively_enabled(); + // Collect control tasks (we do that in any case to not let get channels full). + let mut count = 0; + while let Ok(task) = self.basics.channels.control_task_receiver.try_recv() { + // It's possible that control is disabled because another instance cancels us. In that case + // the RealTimeProcessor won't know about it and keeps sending MIDI. Stop it here! + if control_is_effectively_enabled { + self.process_control_task(task); + } + count += 1; + if count == CONTROL_TASK_BULK_SIZE { + break; + } + } + self.poll_control(timestamp); + } + + fn process_control_task(&mut self, task: ControlMainTask) { + use ControlMainTask::*; + match task { + ControlFromRealTime { + compartment, + mapping_id, + event, + options, + } => { + let _ = self.control_from_real_time(compartment, mapping_id, event, options); + } + LogVirtualControlInput { + event: value, + match_outcome: match_result, + } => { + log_virtual_control_input( + self.unit_id(), + format_control_input_with_match_result(value, match_result), + ); + } + LogRealControlInput { + event, + match_outcome: match_result, + } => { + let timestamp = event.timestamp(); + log_real_control_input( + self.unit_id(), + format_control_input_with_match_result( + ControlEvent::new( + format_midi_source_value(&event.into_payload()), + timestamp, + ), + match_result, + ), + ); + } + LogRealLearnInput { event } => { + let timestamp = event.timestamp(); + log_real_learn_input( + self.unit_id(), + ControlEvent::new( + format_incoming_midi_message(event.into_payload()), + timestamp, + ), + ); + } + LogTargetOutput { event } => { + log_target_output(self.unit_id(), format_as_pretty_hex(event.bytes())); + } + LogTargetControl { mapping_id, entry } => { + let logger = self + .basics + .target_control_logger(ControlLogContext::RealTime, mapping_id); + logger(entry); + } + } + } + + fn poll_control(&mut self, timestamp: ControlEventTimestamp) { + for compartment in CompartmentKind::enum_iter() { + for id in self.poll_control_mappings[compartment].iter() { + let (is_source_poll, control_result, group_interaction) = if let Some(m) = + self.collections.mappings[compartment].get_mut(id) + { + let control_context = self.basics.control_context(compartment); + let processor_context = ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ); + let mode_poll_result = if m.mode().wants_to_be_polled() { + m.poll_mode( + control_context, + processor_context, + timestamp, + self.basics.target_control_logger( + ControlLogContext::Polling, + m.qualified_id(), + ), + ) + } else { + Default::default() + }; + let (is_source_poll, mut final_poll_result) = if mode_poll_result + .at_least_one_target_was_reached + { + // Mode was polled successfully. This one has precedence. + // We poll even if control is effectively off because it might have been + // on before and user might have pressed a button which started some + // timer - and we still want that timer to fire. This is practical e.g. + // when having a single-press button with a modifier. It's not uncommon + // to shortly press the modifier, press the single-press button and + // release the modifier. If we wouldn't poll anymore in that case, the + // single press would be discarded - or worse, fired when the mapping + // is enabled again. + (false, mode_poll_result) + } else if m.source().wants_to_be_polled() && m.control_is_effectively_on() { + // Mode was either not polled at all or without result, poll source. + let res = if let Some(source_control_value) = m.poll_source() { + let control_event = ControlEvent::new(source_control_value, timestamp); + control_mapping_stage_one( + &self.basics, + &self.collections.parameters, + m, + control_event, + ControlOptions::default(), + ) + } else { + Default::default() + }; + (true, res) + } else { + // Mode was either not polled at all or without result, source doesn't + // want to be polled. + (false, mode_poll_result) + }; + control_mapping_stage_two( + &self.basics, + &mut final_poll_result, + m, + ManualFeedbackProcessing::On { + mappings_with_virtual_targets: &self + .collections + .mappings_with_virtual_targets, + }, + ); + (is_source_poll, final_poll_result, m.group_interaction()) + } else { + continue; + }; + + // When this is a mode poll, we only do target-value based group interaction after + // polling (makes sense because control-value based one has been done at control + // time already). + let needs_group_interaction = control_result.at_least_one_target_was_reached + && (is_source_poll || group_interaction.is_target_based()); + control_mapping_stage_three( + &self.basics, + &mut self.collections, + compartment, + control_result, + if needs_group_interaction { + GroupInteractionProcessing::On(GroupInteractionInput { + mapping_id: *id, + // Control value is not important because we only do target-value + // based group interaction. + control_event: ControlEvent::new( + ControlValue::AbsoluteContinuous(Default::default()), + timestamp, + ), + group_interaction, + }) + } else { + GroupInteractionProcessing::Off + }, + ); + } + } + } + + /// Processes incoming control messages from the real-time processor. + fn control_from_real_time( + &mut self, + compartment: CompartmentKind, + mapping_id: MappingId, + control_event: ControlEvent, + options: ControlOptions, + ) -> Result<(), &'static str> { + // Resolving mappings with virtual targets is not necessary anymore. It has + // been done in the real-time processor already. + let (control_result, group_interaction) = { + let m = self.collections.mappings[compartment] + .get_mut(&mapping_id) + .ok_or("mapping not found")?; + // Most of the time, the main processor won't even receive a MIDI-triggered control + // instruction from the real-time processor for a mapping for which control is disabled, + // because the real-time processor doesn't process disabled mappings. But if control is + // (temporarily) disabled because a target condition is (temporarily) not met (e.g. + // "track must be selected") and the real-time processor doesn't yet know about it, + // there might be a short amount of time where we still receive control statements. We + // filter them here. + if !m.control_is_effectively_on() { + return Ok(()); + } + let control_result = control_mapping_stage_one_and_two( + &self.basics, + &self.collections.parameters, + m, + control_event, + options, + ManualFeedbackProcessing::On { + mappings_with_virtual_targets: &self.collections.mappings_with_virtual_targets, + }, + ); + (control_result, m.group_interaction()) + }; + control_mapping_stage_three( + &self.basics, + &mut self.collections, + compartment, + control_result, + GroupInteractionProcessing::On(GroupInteractionInput { + mapping_id, + control_event, + group_interaction, + }), + ); + Ok(()) + } + + /// This should be regularly called by the control surface, even during global target learning. + pub fn run_essential(&mut self, timestamp: ControlEventTimestamp) { + if let Some(p) = self.basics.context.project() { + // Prevent possible "Project doesn't exist anymore" errors + if !p.is_available() { + return; + } + } + self.process_normal_tasks_from_real_time_processor(); + self.process_normal_tasks_from_session(timestamp); + self.process_parameter_tasks(); + self.process_feedback_tasks(); + self.process_unit_events(); + self.poll_for_feedback(); + } + + /// This goes through all mappings that returned "high" feedback resolution - which they do if + /// there are no appropriate change events to listen to and therefore need feedback polling. + #[allow(clippy::float_cmp)] + fn poll_for_feedback(&mut self) { + for compartment in CompartmentKind::enum_iter() { + for mapping_id in self.collections.milli_dependent_feedback_mappings[compartment].iter() + { + if let Some(m) = self.collections.mappings[compartment].get(mapping_id) { + let previous_target_values = &mut self.collections.previous_target_values; + let control_context = self.basics.control_context(compartment); + self.basics + .process_feedback_related_reaper_event_for_mapping( + m, + &self.collections.mappings_with_virtual_targets, + &mut |m, t| { + if m.mode().feedback_props_in_use().is_empty() { + // No feedback props are used, which means we have pure + // numeric feedback (no textual feedback, no prop-based feedback + // style settings). + // Numeric feedback is always in percentages, so we can + // safely block feedback already here if we encounter + // duplicate target values. So check for duplicate feedback! + // TODO-high-discrete Maybe not true anymore with discrete + // targets. + let (affected, new_value) = if let Some(value) = + t.current_value(control_context) + { + // Check if changed + match previous_target_values[compartment].entry(*mapping_id) + { + Entry::Occupied(mut e) => { + // We really want to resend if there's the slightest + // difference. It's okay to have direct comparison + // because we know the source of these two values is + // the same. + if e.get().to_unit_value().get() + == value.to_unit_value().get() + { + // Value hasn't changed. + (false, None) + } else { + // Value has changed. + e.insert(value); + (true, Some(value)) + } + } + Entry::Vacant(e) => { + // No feedback sent yet for that milli-dependent mapping. + e.insert(value); + (true, Some(value)) + } + } + } else { + // Couldn't determine feedback value. + (false, None) + }; + if affected { + m.update_last_non_performance_target_value_if_appropriate( + new_value, + ); + } + (affected, new_value) + } else { + // We use feedback props. That either means we have numeric + // feedback with some prop-based feedback style or we have + // text feedback. + // + // Props can change even if the main target value doesn't + // change! + // + // Also, text feedback is not necessarily based on percentages. + // This means we can have the situation that in terms of + // percentages (usually relevant for control direction), the + // current value might be below 0% or above 100%, which would + // let the percentage (unit value) stay the same. But the + // text feedback might go beyond that interval, so we should + // always update it! Example: Seek target with "Use project" + // enabled. + + // We are now required to return the current target value. + let new_value = t.current_value(control_context); + (true, new_value) + } + }, + ); + } + } + } + } + + fn process_unit_events(&mut self) { + let mut changes_conditions = false; + for event in self + .basics + .channels + .unit_event_receiver + .try_iter() + .take(FEEDBACK_TASK_BULK_SIZE) + { + // Propagate to other instances if necessary + if event.is_interesting_for_other_units() { + let global_event = AdditionalFeedbackEvent::Unit { + unit_id: self.basics.unit_id, + unit_event: event.clone(), + }; + self.basics + .channels + .additional_feedback_event_sender + .send_complaining(global_event); + } + // Process feedback + self.process_feedback_related_event(|mapping, target| { + mapping.process_change_event( + target, + CompoundChangeEvent::Unit(&event), + self.basics.control_context(mapping.compartment()), + ) + }); + // Check if this event changes conditions + if ReaperTarget::changes_conditions(CompoundChangeEvent::Unit(&event)) { + changes_conditions = true; + } + } + if changes_conditions { + self.basics.notify_conditions_changed(); + } + } + + /// Processes the given Playtime matrix events if they are relevant to this unit. + #[cfg(feature = "playtime")] + pub fn process_polled_clip_matrix_events( + &self, + instance_id: InstanceId, + events: &[playtime_clip_engine::base::ClipMatrixEvent], + ) { + if instance_id != self.basics.instance_id { + return; + } + for event in events { + self.process_clip_matrix_event_for_feedback(event); + } + } + + /// Processes the given Playtime matrix event if it's relevant to this instance. + #[cfg(feature = "playtime")] + pub fn process_non_polled_clip_matrix_event( + &self, + event: &crate::domain::QualifiedClipMatrixEvent, + ) { + if event.instance_id != self.basics.instance_id { + return; + } + self.process_clip_matrix_event_for_feedback(&event.event) + } + + pub fn process_instance_event_for_feedback(&self, event: &QualifiedInstanceEvent) { + if event.instance_id != self.basics.instance_id { + return; + } + self.process_feedback_related_event(|mapping, target| { + mapping.process_change_event( + target, + CompoundChangeEvent::Instance(&event.event), + self.basics.control_context(mapping.compartment()), + ) + }); + } + + #[cfg(feature = "playtime")] + fn process_clip_matrix_event_for_feedback( + &self, + event: &playtime_clip_engine::base::ClipMatrixEvent, + ) { + let is_position_change = matches!( + event, + playtime_clip_engine::base::ClipMatrixEvent::SlotChanged( + playtime_clip_engine::rt::QualifiedSlotChangeEvent { + event: playtime_clip_engine::rt::SlotChangeEvent::Continuous { .. }, + .. + } + ) + ); + if is_position_change { + // Position changed. This happens very frequently when a clip is playing. + // Mappings with slot seek targets are in the beat-dependent feedback + // mapping set, not in the milli-dependent one (because we don't want to + // query their feedback value more than once in one main loop cycle). + // So we don't want to iterate over all mappings but just the beat-dependent + // ones. + for compartment in CompartmentKind::enum_iter() { + for mapping_id in + self.collections.beat_dependent_feedback_mappings[compartment].iter() + { + if let Some(m) = self.collections.mappings[compartment].get(mapping_id) { + self.process_feedback_related_reaper_event_for_mapping( + m, + &mut |m, target| { + m.process_change_event( + target, + CompoundChangeEvent::ClipMatrix(event), + self.basics.control_context(m.compartment()), + ) + }, + ); + } + } + } + } else { + if ReaperTarget::changes_conditions(CompoundChangeEvent::ClipMatrix(event)) { + self.basics.notify_conditions_changed(); + } + // Other property of clip changed. + self.process_feedback_related_event(|mapping, target| { + mapping.process_change_event( + target, + CompoundChangeEvent::ClipMatrix(event), + self.basics.control_context(mapping.compartment()), + ) + }); + } + } + + fn process_feedback_tasks(&mut self) { + let mut count = 0; + while let Ok(task) = self.basics.channels.feedback_task_receiver.try_recv() { + use FeedbackMainTask::*; + match task { + MappingTargetValueChanged { + lead_mapping_id, + target_value, + } => { + self.process_conditional_activation_target_value_change( + lead_mapping_id, + true, + Some(target_value), + ); + } + } + count += 1; + if count == FEEDBACK_TASK_BULK_SIZE { + break; + } + } + } + + /// Handles the change of a target value of a lead mapping in terms of target-based conditional + /// activation. + /// + /// Pass the target value at hand, usually the result of querying the mapping to return the + /// aggregated target value. This method takes care of correctly handling the situation that + /// we have a target value but the target is inactive. + fn process_conditional_activation_target_value_change( + &mut self, + id: QualifiedMappingId, + target_is_active: bool, + target_value: Option, + ) { + // Calculate final target value + let target_value = match determine_final_target_value_for_conditional_activation( + target_is_active, + target_value, + ) { + Err(_) => return, + Ok(v) => v, + }; + // Process it + let compartment = id.compartment; + let activation_effects: Vec = self + .basics + .target_based_conditional_activation_processors[compartment] + .get_follow_mappings(id.id) + .filter_map(|follow_mapping_id| { + let follow_mapping = get_normal_or_virtual_target_mapping_mut( + &mut self.collections.mappings, + &mut self.collections.mappings_with_virtual_targets, + compartment, + follow_mapping_id, + )?; + follow_mapping.check_activation_effect_of_target_value_update(id.id, target_value) + }) + .collect(); + self.process_activation_effects(compartment, activation_effects, false); + } + + fn process_change_of_last_touched_target(&mut self) { + // The last touched target has changed! We re-resolve all "Last touched" targets. + for compartment in CompartmentKind::enum_iter() { + for mapping_id in self.collections.target_touch_dependent_mappings[compartment].iter() { + // Virtual targets are not candidates for "Last touched" so we don't + // need to consider them here. + let fb = if let Some(m) = self.collections.mappings[compartment].get_mut(mapping_id) + { + // We don't need to track activation updates because this target + // is always on. Switching off is not necessary since the last + // touched target can never be "unset". + let control_context = self.basics.control_context(m.compartment()); + let _ = m.refresh_target( + ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ), + control_context, + ); + if m.has_reaper_target() && m.has_resolved_successfully() { + if m.feedback_is_effectively_on() { + // TODO-medium Is this executed too frequently and maybe + // even sends redundant feedback!? + m.feedback(true, control_context) + } else { + None + } + } else { + None + } + } else { + None + }; + self.send_feedback(FeedbackReason::Normal, fb); + } + } + } + + fn process_parameter_tasks(&mut self) { + // In theory, this could be optimized by reducing the number of changes. E.g. if we + // find that we have multiple changes of one parameter, we could choose to just process + // the last one. I wanted to do that as part of + // https://github.com/helgoboss/helgobox/issues/847 but chose to not do it ... because + // this optimization could potentially change control behavior if that parameter is used + // for control. Imagine a toggle mode ... then the number of invocations is relevant. + let mut count = 0; + while let Ok(task) = self.basics.channels.parameter_task_receiver.try_recv() { + use ParameterMainTask::*; + match task { + UpdateAllParams(params) => { + self.update_all_params(params); + } + UpdateSingleParamValue { + index, + value, + timestamp, + } => self.update_single_param_value(index, value, timestamp), + } + count += 1; + if count == PARAMETER_TASK_BULK_SIZE { + break; + } + } + } + + // https://github.com/rust-lang/rust-clippy/issues/6066 + #[allow(clippy::needless_collect)] + fn update_single_param_value( + &mut self, + index: PluginParamIndex, + value: RawParamValue, + timestamp: ControlEventTimestamp, + ) { + debug!("Updating parameter {} to {}...", index, value); + // Work around REAPER's inability to notify about parameter changes in + // monitoring FX by simulating the notification ourselves. + // Then parameter learning and feedback works at least for + // ReaLearn monitoring FX instances, which is especially + // useful for conditional activation. + if self.basics.context.is_on_monitoring_fx_chain() { + let parameter = self + .basics + .context + .containing_fx() + .parameter_by_index(index.get()); + self.basics + .channels + .additional_feedback_event_sender + .send_complaining( + AdditionalFeedbackEvent::RealearnMonitoringFxParameterValueChanged( + RealearnMonitoringFxParameterValueChangedEvent { + parameter, + new_value: ReaperNormalizedFxParamValue::new(value as _), + }, + ), + ); + } + // Update own value (important to do first) + let param = self.collections.parameters.at_mut(index); + let previous_value = param.raw_value(); + param.set_raw_value(value); + // When our own parameters change, it's THE example of a condition change. + self.basics.notify_conditions_changed(); + // Let compartment parameter targets know + self.process_compartment_parameter_feedback(index); + // Notify domain event handler + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::UpdatedSingleParameterValue { index, value }); + // Determine and process activation effects + let compartment = CompartmentKind::by_plugin_param_index(index); + let activation_effects: Vec = self + .all_mappings_in_compartment(compartment) + .filter_map(|m| { + m.check_activation_effect_of_param_update( + &self.collections.parameters, + index, + previous_value, + ) + }) + .collect(); + self.process_activation_effects(compartment, activation_effects, true); + // Control ("Helgobox parameter source") + let control_payload = RealearnParameterChangePayload { + compartment, + parameter_index: compartment.to_compartment_param_index(index), + value, + }; + let control_msg = ReaperMessage::RealearnParameterChange(control_payload); + if self.basics.settings.real_input_logging_enabled { + self.log_incoming_message(&control_msg); + } + let control_event = ControlEvent::new(MainSourceMessage::Reaper(&control_msg), timestamp); + self.process_incoming_message_internal(control_event); + } + + /// This is called after determining mapping and/or target activation changes. + fn process_activation_effects( + &mut self, + compartment: CompartmentKind, + activation_effects: Vec, + refresh_targets: bool, + ) { + if activation_effects.is_empty() && !refresh_targets { + return; + } + // Mapping activation is supported for both compartments and target activation + // might change also in non-virtual controller mappings due to dynamic targets. + let mut changed_mappings = NonCryptoIndexSet::default(); + let mut unused_sources = self.currently_feedback_enabled_sources(compartment, true); + // In order to avoid a mutable borrow of mappings and an immutable borrow of + // parameters at the same time, we need to separate into READ activation + // effects and WRITE activation updates. + // 2. Mapping activation: Write + let mapping_updates: Vec = activation_effects + .into_iter() + .filter_map(|eff| { + changed_mappings.insert(eff.id); + let m = get_normal_or_virtual_target_mapping_mut( + &mut self.collections.mappings, + &mut self.collections.mappings_with_virtual_targets, + compartment, + eff.id, + )?; + m.update_activation_from_effect(eff) + }) + .collect(); + // 3. Mappings with real targets: Refresh targets and determine unused sources + let mut target_updates: Vec = vec![]; + for m in self.collections.mappings[compartment].values_mut() { + if refresh_targets && m.target_can_be_affected_by_parameters() { + let control_context = self.basics.control_context(m.compartment()); + let context = ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ); + if let Some(target_update) = m.refresh_target(context, control_context) { + target_updates.push(target_update); + changed_mappings.insert(m.id()); + } + } + if m.feedback_is_effectively_on() { + // Mark source as used + if let Some(addr) = m + .source() + .extract_feedback_address(self.basics.source_context(m.compartment())) + { + unused_sources.remove(&addr); + } + } + } + // 4. Mappings with virtual targets: Determine unused sources + if compartment == CompartmentKind::Controller { + for m in self.collections.mappings_with_virtual_targets.values() { + if !m.feedback_is_effectively_on() { + continue; + } + // A mapping with virtual target is only considered as active if there's a + // corresponding active main mapping. + // (https://github.com/helgoboss/helgobox/issues/563) + let has_active_main_mapping = + find_active_main_mapping_connected_to_virtual_control_element( + &self.collections.mappings[CompartmentKind::Main], + m.virtual_target_control_element().unwrap(), + ) + .is_some(); + if !has_active_main_mapping { + continue; + } + // Mark source as used + if let Some(addr) = m + .source() + .extract_feedback_address(self.basics.source_context(m.compartment())) + { + unused_sources.remove(&addr); + } + } + } + self.process_mapping_updates_due_to_activation_changes( + compartment, + mapping_updates, + target_updates, + unused_sources, + changed_mappings.into_iter(), + ); + } + + fn update_all_params(&mut self, params: PluginParams) { + debug!("Updating all parameters..."); + self.collections.parameters = params.clone(); + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::UpdatedAllParameters(params)); + for compartment in CompartmentKind::enum_iter() { + let mut mapping_updates: Vec = vec![]; + let mut target_updates: Vec = vec![]; + let mut changed_mappings = vec![]; + let mut unused_sources = self.currently_feedback_enabled_sources(compartment, true); + for m in all_mappings_in_compartment_mut( + &mut self.collections.mappings, + &mut self.collections.mappings_with_virtual_targets, + compartment, + ) { + if m.activation_can_be_affected_by_parameters() { + if let Some(update) = + m.update_activation_from_params(&self.collections.parameters) + { + mapping_updates.push(update); + changed_mappings.push(m.id()) + } + } + if m.target_can_be_affected_by_parameters() { + let control_context = self.basics.control_context(m.compartment()); + let context = ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ); + if let Some(target_update) = m.refresh_target(context, control_context) { + target_updates.push(target_update); + changed_mappings.push(m.id()) + } + } + if m.feedback_is_effectively_on() { + // Mark source as used + if let Some(addr) = m + .source() + .extract_feedback_address(self.basics.source_context(compartment)) + { + unused_sources.remove(&addr); + } + } + } + self.process_mapping_updates_due_to_activation_changes( + compartment, + mapping_updates, + target_updates, + unused_sources, + changed_mappings.into_iter(), + ); + } + } + + fn process_normal_tasks_from_session(&mut self, timestamp: ControlEventTimestamp) { + let mut count = 0; + let mut conditions_changed = false; + while let Ok(task) = self.basics.channels.normal_task_receiver.try_recv() { + use NormalMainTask::*; + match task { + UpdateSettings(settings) => { + self.update_settings(settings); + } + UpdateCompartmentSettings(compartment, settings) => { + self.update_compartment_settings(compartment, settings); + } + UpdateAllMappings(compartment, mappings) => { + self.update_all_mappings(compartment, mappings); + } + NotifyRealearnUnitStarted => { + debug!("NotifyRealearnUnitStarted received"); + let evt = ControlEvent::new(&ReaperMessage::RealearnUnitStarted, timestamp); + self.process_reaper_message(evt); + } + NotifyRealearnCompartmentLoaded(kind) => { + debug!("NotifyRealearnCompartmentLoaded received"); + let message = ReaperMessage::RealearnCompartmentLoaded(kind); + let evt = ControlEvent::new(&message, timestamp); + self.process_reaper_message(evt); + } + HitTarget { id, value } => { + self.hit_target(id, value); + } + NotifyConditionsChanged => { + conditions_changed = true; + } + UpdateSingleMapping(mapping) => { + self.update_single_mapping(mapping); + } + UpdatePersistentMappingProcessingState { id, state } => { + self.update_persistent_mapping_processing_state(id, state); + } + SendAllFeedback => { + self.send_all_feedback(); + } + LogDebugInfo => { + self.log_debug_info(); + } + LogMapping(compartment, mapping_id) => { + self.log_mapping(compartment, mapping_id); + } + StartLearnSource { + allow_virtual_sources, + osc_arg_index_hint, + } => { + debug!("Start learning source"); + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::StartLearnSource { + allow_virtual_sources, + }); + self.basics.control_mode = ControlMode::LearningSource { + allow_virtual_sources, + osc_arg_index_hint, + }; + } + DisableControl => { + debug!("Disable control"); + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::DisableControl); + self.basics.control_mode = ControlMode::Disabled; + } + ReturnToControlMode => { + debug!("Return to control mode"); + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::ReturnToControlMode); + self.basics.control_mode = ControlMode::Controlling; + } + UseIntegrationTestFeedbackSender(sender) => { + self.basics.channels.integration_test_feedback_sender = Some(sender); + } + PotentiallyEnableOrDisableControlOrFeedback => { + self.potentially_enable_or_disable_control_or_feedback( + self.any_main_mapping_is_effectively_on(), + ); + } + } + count += 1; + if count == NORMAL_TASK_BULK_SIZE { + break; + } + } + if conditions_changed { + self.process_changed_conditions(); + } + } + + fn potentially_enable_or_disable_control_or_feedback( + &mut self, + any_main_mapping_is_effectively_on: bool, + ) { + let project_options = get_project_options(); + self.potentially_enable_or_disable_control( + any_main_mapping_is_effectively_on, + project_options, + ); + self.potentially_enable_or_disable_feedback( + any_main_mapping_is_effectively_on, + project_options, + ); + let event = GlobalControlAndFeedbackState { + control_active: self.basics.control_is_globally_enabled, + feedback_active: self.basics.feedback_is_globally_enabled, + }; + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::GlobalControlAndFeedbackStateChanged(event)); + } + + fn potentially_enable_or_disable_control( + &mut self, + any_main_mapping_is_effectively_on: bool, + project_options: ProjectOptions, + ) { + self.basics.potentially_enable_or_disable_control_internal( + any_main_mapping_is_effectively_on, + project_options, + ); + } + + fn potentially_enable_or_disable_feedback( + &mut self, + any_main_mapping_is_effectively_on: bool, + project_options: ProjectOptions, + ) { + let new_feedback_is_enabled = self.basics.potentially_enable_or_disable_feedback_internal( + any_main_mapping_is_effectively_on, + project_options, + ); + if let Some(new_feedback_is_enabled) = new_feedback_is_enabled { + if new_feedback_is_enabled { + for compartment in CompartmentKind::enum_iter() { + self.handle_feedback_after_having_updated_all_mappings( + compartment, + HashMap::default(), + ); + } + } else { + // Clear it completely. Other instances that might take over maybe don't use + // all control elements and we don't want to leave traces. + self.clear_all_feedback_allowing_source_takeover(); + }; + } + } + + /// This should be called on events such as track list change, FX focus etc. + /// + /// It will trigger a refresh of all targets (re-resolve) or even a preset change (if + /// auto-load is enabled). + /// + /// Shouldn't be called directly when the REAPER change event occurs but in the next main loop + /// cycle. That's especially important for auto-load because REAPER first needs to digest info + /// such as "Is the window open?" and "What FX is the focused FX?". + fn process_changed_conditions(&mut self) { + debug!("Conditions changed"); + // Invoke auto-load if necessary + if self + .basics + .event_handler + .auto_load_different_preset_if_necessary() + .is_ok_and(|loaded| loaded) + { + // If another preset was loaded, we don't need to refresh all targets because + // another preset is being loaded anyway. + return; + } + // Refresh all targets + debug!("Refreshing all targets..."); + for compartment in CompartmentKind::enum_iter() { + let mut target_updates: Vec = vec![]; + let mut changed_mappings = vec![]; + let mut unused_sources = self.currently_feedback_enabled_sources(compartment, false); + // Mappings with virtual targets don't have to be refreshed because virtual + // targets are always active and never change depending on circumstances. + for m in self.collections.mappings[compartment].values_mut() { + let control_context = self.basics.control_context(compartment); + let context = ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ); + if let Some(target_update) = m.refresh_target(context, control_context) { + target_updates.push(target_update); + changed_mappings.push(m.id()) + } + if m.feedback_is_effectively_on() { + // Mark source as used + if let Some(addr) = m + .source() + .extract_feedback_address(self.basics.source_context(m.compartment())) + { + unused_sources.remove(&addr); + } + } + } + self.process_target_updates(compartment, target_updates); + // Important to send IO event first ... + self.notify_feedback_dev_usage_might_have_changed(compartment); + self.handle_feedback_after_having_updated_particular_mappings( + compartment, + unused_sources, + changed_mappings.into_iter(), + ); + } + // Update on mappings + self.update_on_mappings(); + // Notify session (for UI purposes) + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::ConditionsChanged); + } + + fn update_compartment_settings( + &mut self, + compartment: CompartmentKind, + settings: CompartmentSettings, + ) { + self.basics.common_lua[compartment] = settings.common_lua; + } + + fn update_settings(&mut self, settings: BasicSettings) { + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::UpdateSettings(settings)); + let any_main_mapping_is_effectively_on = self.any_main_mapping_is_effectively_on(); + self.basics + .update_settings_internal(settings, any_main_mapping_is_effectively_on); + self.potentially_enable_or_disable_control_or_feedback(any_main_mapping_is_effectively_on); + Backbone::get() + .register_stream_deck_usage(self.basics.unit_id, settings.streamdeck_device_id); + } + + fn update_all_mappings( + &mut self, + compartment: CompartmentKind, + mut mappings: Vec, + ) { + tracing::debug!("Updating {} mappings in {}...", mappings.len(), compartment,); + self.basics.clear_last_feedback(); + let mut mappings_by_group: NonCryptoHashMap> = HashMap::default(); + let mut mapping_infos: NonCryptoHashMap = + HashMap::default(); + let mut unused_sources = self.currently_feedback_enabled_sources(compartment, true); + self.collections.target_touch_dependent_mappings[compartment].clear(); + self.collections.beat_dependent_feedback_mappings[compartment].clear(); + self.collections.milli_dependent_feedback_mappings[compartment].clear(); + self.basics.target_based_conditional_activation_processors[compartment].clear(); + self.collections.previous_target_values[compartment].clear(); + self.poll_control_mappings[compartment].clear(); + // Refresh and splinter real-time mappings + let real_time_mappings = mappings + .iter_mut() + .map(|m| { + mappings_by_group + .entry(m.group_id()) + .or_default() + .push(m.id()); + mapping_infos.insert(m.qualified_id(), m.take_mapping_info()); + let control_context = self.basics.control_context(m.compartment()); + m.init_target_and_activation( + ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ), + control_context, + ); + if m.feedback_is_effectively_on() { + // Mark source as used + if let Some(addr) = m + .source() + .extract_feedback_address(self.basics.source_context(m.compartment())) + { + unused_sources.remove(&addr); + } + } + if m.needs_refresh_when_target_touched() { + self.collections.target_touch_dependent_mappings[compartment].insert(m.id()); + } + let feedback_resolution = m.feedback_resolution(); + if feedback_resolution == Some(FeedbackResolution::Beat) { + self.collections.beat_dependent_feedback_mappings[compartment].insert(m.id()); + } + if feedback_resolution == Some(FeedbackResolution::High) { + self.collections.milli_dependent_feedback_mappings[compartment].insert(m.id()); + } + if m.wants_to_be_polled_for_control() { + self.poll_control_mappings[compartment].insert(m.id()); + } + let target_value_activation_reference_mappings = + m.activation_can_be_affected_by_target_values(); + self.basics.target_based_conditional_activation_processors[compartment] + .notify_usage_add_only(m.id(), target_value_activation_reference_mappings); + m.splinter_real_time_mapping() + }) + .collect(); + // Update instance state + { + let mut instance_state = self.basics.unit.borrow_mut(); + instance_state.set_mappings_by_group(compartment, mappings_by_group); + instance_state.set_mapping_infos(mapping_infos); + } + // Put into hash map in order to quickly look up mappings by ID + let mapping_tuples = mappings.into_iter().map(|m| (m.id(), m)); + if compartment == CompartmentKind::Controller { + let (virtual_target_mappings, normal_mappings) = + mapping_tuples.partition(|(_, m)| m.has_virtual_target()); + self.collections.mappings[compartment] = normal_mappings; + self.collections.mappings_with_virtual_targets = virtual_target_mappings; + } else { + self.collections.mappings[compartment] = mapping_tuples.collect(); + } + // Sync to real-time processor + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::UpdateAllMappings( + compartment, + real_time_mappings, + )); + // Important to send IO event first ... + self.notify_feedback_dev_usage_might_have_changed(compartment); + // ... and then mapping update. Otherwise, if this is an upper-floor instance + // clearing all mappings, other instances won't see yet that they are actually + // allowed to take over sources! Which might delay the reactivation of + // lower-floor instances. + self.handle_feedback_after_having_updated_all_mappings(compartment, unused_sources); + self.update_on_mappings(); + // Evaluate target-based activation conditions. We do it by reporting + // target value updates for all lead mappings. + let lead_mapping_ids = + self.basics.target_based_conditional_activation_processors[compartment].lead_mappings(); + self.process_conditional_activation_target_value_changes(compartment, lead_mapping_ids); + } + + fn process_normal_tasks_from_real_time_processor(&mut self) { + for task in self + .basics + .channels + .normal_real_time_to_main_thread_task_receiver + .try_iter() + .take(NORMAL_RT_TASK_BULK_SIZE) + { + use NormalRealTimeToMainThreadTask::*; + match task { + CaptureMidi { + scan_result, + allow_virtual_sources, + } => { + let event = MessageCaptureEvent { + result: MessageCaptureResult::Midi(scan_result), + allow_virtual_sources, + osc_arg_index_hint: None, + }; + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::CapturedIncomingMessage(event)); + } + FullResyncToRealTimeProcessorPlease => { + // We cannot provide everything that the real-time processor needs so we need + // to delegate to the session in order to let it do the resync (could be + // changed by also holding unnecessary things but for now, why not taking the + // session detour). + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::FullResyncRequested); + } + LogLifecycleOutput { value } => { + log_lifecycle_output(self.basics.unit_id, format_midi_source_value(&value)); + } + LogToConsole(msg) => { + Reaper::get().show_console_msg(msg); + } + } + } + } + + fn any_main_mapping_is_effectively_on(&self) -> bool { + self.collections.mappings[CompartmentKind::Main] + .values() + .any(|m| m.is_effectively_on()) + } + + fn notify_feedback_dev_usage_might_have_changed(&self, compartment: CompartmentKind) { + // A device is only considered to be "in use" if there's at least one + // *main* mapping. It doesn't depend on + // controller mappings. + if compartment == CompartmentKind::Main { + let event = self.basics.feedback_output_usage_might_have_changed_event( + self.any_main_mapping_is_effectively_on(), + ); + debug!( + "IO event. Feedback output used: {:?}", + event.feedback_output_used + ); + self.basics.send_io_update_complaining(event); + } + } + + fn send_io_update_if_space(&self, event: IoUpdatedEvent) { + self.basics + .channels + .unit_orchestration_event_sender + .send_if_space(UnitOrchestrationEvent::IoUpdated(event)); + } + + fn get_normal_or_virtual_target_mapping( + &self, + compartment: CompartmentKind, + id: MappingId, + ) -> Option<&MainMapping> { + self.collections.mappings[compartment].get(&id).or( + if compartment == CompartmentKind::Controller { + self.collections.mappings_with_virtual_targets.get(&id) + } else { + None + }, + ) + } + + fn get_normal_or_virtual_target_mapping_mut( + &mut self, + id: QualifiedMappingId, + ) -> Option<&mut MainMapping> { + self.collections.mappings[id.compartment] + .get_mut(&id.id) + .or(if id.compartment == CompartmentKind::Controller { + self.collections + .mappings_with_virtual_targets + .get_mut(&id.id) + } else { + None + }) + } + + pub fn process_reaper_config_changes(&self, changes: &[ReaperConfigChange]) { + for change in changes { + match change { + ReaperConfigChange::ProjectOptions(_) => { + self.basics.channels.self_normal_sender.send_complaining( + NormalMainTask::PotentiallyEnableOrDisableControlOrFeedback, + ); + } + } + } + } + + fn process_compartment_parameter_feedback(&mut self, param_index: PluginParamIndex) { + let change_event = CompoundChangeEvent::CompartmentParameter(param_index); + self.process_feedback_related_event(|mapping, target| { + mapping.process_change_event( + target, + change_event, + self.basics.control_context(mapping.compartment()), + ) + }); + } + + pub fn process_additional_feedback_event(&mut self, event: &AdditionalFeedbackEvent) { + if matches!(event, AdditionalFeedbackEvent::LastTouchedTargetChanged) { + self.process_change_of_last_touched_target(); + } + if let AdditionalFeedbackEvent::BeatChanged(_) = event { + // This is fired very frequently so we don't want to iterate over all mappings, + // just the ones that need to be notified for feedback or whatever. + for compartment in CompartmentKind::enum_iter() { + for mapping_id in + self.collections.beat_dependent_feedback_mappings[compartment].iter() + { + if let Some(m) = self.collections.mappings[compartment].get(mapping_id) { + self.process_feedback_related_reaper_event_for_mapping( + m, + &mut |m, target| { + m.process_change_event( + target, + CompoundChangeEvent::Additional(event), + self.basics.control_context(m.compartment()), + ) + }, + ); + } + } + } + } else { + if ReaperTarget::changes_conditions(CompoundChangeEvent::Additional(event)) { + self.basics.notify_conditions_changed(); + } + // Okay, not fired that frequently, we can iterate over all mappings + self.process_feedback_related_event(|mapping, target| { + mapping.process_change_event( + target, + CompoundChangeEvent::Additional(event), + self.basics.control_context(mapping.compartment()), + ) + }); + } + } + + pub fn process_control_surface_change_events(&self, events: &[ChangeEvent]) { + if events.is_empty() { + return; + } + // Potentially enable/disable control/feedback + let influences_global_control_and_feedback = events.iter().any(|event| { + match event { + // Whenever ReaLearn is enabled/disabled via checkbox or dedicated API method. + ChangeEvent::FxEnabledChanged(evt) + if &evt.fx == self.basics.context.containing_fx() => + { + true + } + // Whenever ReaLearn is enabled/disabled via Bypass parameter (e.g. via automation). + ChangeEvent::FxParameterValueChanged(evt) + if evt.parameter.index() == self.basics.context.bypass_param_index() + && evt.parameter.fx() == self.basics.context.containing_fx() => + { + true + } + // Whenever ReaLearn's containing track is armed or disarmed. + ChangeEvent::TrackArmChanged(evt) + if Some(&evt.track) == self.basics.context.containing_fx().track() => + { + true + } + // Whenever the project tab is switched. + ChangeEvent::ProjectSwitched(_) => true, + _ => false, + } + }); + if influences_global_control_and_feedback { + self.basics + .channels + .self_normal_sender + .send_complaining(NormalMainTask::PotentiallyEnableOrDisableControlOrFeedback); + } + // Refresh targets if necessary + let we_have_a_potential_target_change_event = events + .iter() + .any(|evt| ReaperTarget::changes_conditions(CompoundChangeEvent::Reaper(evt))); + if we_have_a_potential_target_change_event { + // Handle dynamic target changes and target activation depending on REAPER state. + // + // Whenever anything changes that just affects the main processor targets, resync all + // targets to the main processor. We don't want to resync to the real-time processor + // just because another track has been selected. First, it would reset any source state + // (e.g. short/long press timers). Second, it wouldn't change anything about the + // sources. We also don't want to resync modes to the main processor. First, + // it would reset any mode state (e.g. throttling data). Second, it would - + // again - not result in any change. There are several global conditions + // which affect whether feedback will be sent from a target or not. Similar + // global conditions decide what exactly produces the feedback values (e.g. + // when there's a target which uses , then a track selection + // change changes the feedback value producer). + + // We don't have mutable access to self here (for good reentrancy reasons) so we + // do the refresh in the next main loop cycle. This is what we always did, also when + // this was still based on Rx! + self.basics.notify_conditions_changed(); + } + // Process for feedback + for event in events { + self.process_feedback_related_event(|mapping, target| { + mapping.process_change_event( + target, + CompoundChangeEvent::Reaper(event), + self.basics.control_context(mapping.compartment()), + ) + }); + } + } + + /// The given function should return if the current target value is affected by this change + /// and the new value. We do this because querying the value *immediately* + /// using the target's `current_value()` method will in some or even many (?) cases give us the + /// old value - which can lead to confusing feedback! In the past we unknowingly worked around + /// this by deferring the value query to the next main cycle, but now that we have the nice + /// non-rx change detection technique, we can do it right here, feedback without delay and + /// avoid a redundant query. + fn process_feedback_related_event( + &self, + mut f: impl Fn(&MainMapping, &ReaperTarget) -> (bool, Option), + ) { + for compartment in CompartmentKind::enum_iter() { + // Mappings with virtual targets don't need to be considered here because they don't + // cause feedback themselves. + for m in self.collections.mappings[compartment].values() { + self.process_feedback_related_reaper_event_for_mapping(m, &mut f); + } + } + } + + /// The given function f is NOW required to return the current target value. + fn process_feedback_related_reaper_event_for_mapping( + &self, + m: &MainMapping, + f: &mut impl FnMut(&MainMapping, &ReaperTarget) -> (bool, Option), + ) { + self.basics + .process_feedback_related_reaper_event_for_mapping( + m, + &self.collections.mappings_with_virtual_targets, + f, + ); + } + + fn wants_messages_in_general(&self) -> bool { + match &self.basics.control_mode { + ControlMode::Disabled => false, + ControlMode::Controlling => self.basics.unit_control_is_effectively_enabled(), + ControlMode::LearningSource { .. } => self.basics.control_is_globally_enabled, + } + } + + pub fn wants_keyboard_input(&self) -> bool { + self.wants_messages_in_general() && self.basics.settings.wants_keyboard_input + } + + pub fn wants_osc_from(&self, device_id: &OscDeviceId) -> bool { + self.wants_messages_in_general() + && self.basics.settings.control_input == ControlInput::Osc(*device_id) + } + + pub fn process_info_event(&mut self, evt: &InternalInfoEvent) { + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::Info(evt)); + } + + pub fn process_reaper_message(&mut self, evt: ControlEvent<&ReaperMessage>) { + // First process internally. + // Convenience: Send all feedback whenever a MIDI device is connected. + if let ReaperMessage::MidiDevicesConnected(payload) = evt.payload() { + if let Some(FeedbackOutput::Midi(MidiDestination::Device(dev_id))) = + self.basics.settings.feedback_output + { + if payload.output_devices.contains(&dev_id) { + self.basics + .channels + .self_normal_sender + .send_if_space(NormalMainTask::SendAllFeedback); + } + } + } + // Convenience: Send all feedback whenever a Stream Deck device is connected. + if let ReaperMessage::StreamDeckDevicesConnected(payload) = evt.payload() { + if let Some(dev_id) = self.basics.settings.streamdeck_device_id { + if payload.devices.contains(&dev_id) { + self.basics + .channels + .self_normal_sender + .send_if_space(NormalMainTask::SendAllFeedback); + } + } + } + // Inform UI of MIDI device changes + if matches!( + evt.payload(), + ReaperMessage::MidiDevicesConnected(_) | ReaperMessage::MidiDevicesDisconnected(_) + ) { + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::MidiDevicesChanged); + } + // Then let mappings with REAPER sources process them, if controlling enabled. + if self.basics.control_mode != ControlMode::Controlling { + return; + } + if self.basics.settings.real_input_logging_enabled { + log_real_control_input(self.basics.unit_id, evt); + } + if !self.basics.unit_control_is_effectively_enabled() { + return; + } + let evt = evt.map_payload(MainSourceMessage::Reaper); + let virtual_result = self + .basics + .process_controller_mappings_with_virtual_targets( + &mut self.collections.mappings_with_virtual_targets, + &mut self.collections.mappings[CompartmentKind::Main], + evt, + &self.collections.parameters, + ); + for r in virtual_result.control_results { + control_mapping_stage_three( + &self.basics, + &mut self.collections, + r.compartment, + r.control_result, + GroupInteractionProcessing::On(r.group_interaction_input), + ) + } + self.process_mappings_with_real_targets(evt); + } + + fn log_incoming_message(&self, msg: T) { + match self.basics.control_mode { + ControlMode::Controlling => { + log_real_control_input(self.basics.unit_id, msg); + } + ControlMode::LearningSource { .. } => { + log_real_learn_input(self.basics.unit_id, msg); + } + ControlMode::Disabled => {} + } + } + + /// This doesn't check if control enabled! You need to check before. + pub fn process_incoming_key_msg( + &mut self, + evt: ControlEvent, + ) -> KeyProcessingResult { + if self.basics.settings.real_input_logging_enabled { + self.log_incoming_message(evt); + } + let match_outcome = + self.process_incoming_message_internal(evt.map_payload(MainSourceMessage::Key)); + // Because modifiers are processed as separate key events, not as compound shortcuts, the + // following example works nicely: Cmd+B is let through to REAPER (= not filtered out) if there is an active + // mapping listening to "Cmd" (a modifier!) but there is no active mapping with source 'B'. The 'B' will + // not be consumed in this case and will be forwarded to REAPER as a compound shortcut. + let filter_out_event = match_outcome.matched_or_consumed(); + KeyProcessingResult { + match_outcome, + filter_out_event, + } + } + + /// This doesn't check if control enabled! You need to check before. + pub fn process_incoming_stream_deck_msg(&mut self, evt: ControlEvent) { + if self.basics.settings.real_input_logging_enabled { + self.log_incoming_message(evt); + } + self.process_incoming_message_internal(evt.map_payload(MainSourceMessage::StreamDeck)); + } + + pub fn wants_stream_deck_input_from(&self, dev: StreamDeckDeviceId) -> bool { + self.wants_messages_in_general() + && self + .basics + .settings + .streamdeck_device_id + .is_some_and(|d| d == dev) + } + + fn process_incoming_msg_for_controlling( + &mut self, + evt: ControlEvent, + ) -> MatchOutcome { + let virtual_result = self + .basics + .process_controller_mappings_with_virtual_targets( + &mut self.collections.mappings_with_virtual_targets, + &mut self.collections.mappings[CompartmentKind::Main], + evt, + &self.collections.parameters, + ); + for r in virtual_result.control_results { + control_mapping_stage_three( + &self.basics, + &mut self.collections, + r.compartment, + r.control_result, + GroupInteractionProcessing::On(r.group_interaction_input), + ) + } + let real_match_outcome = self.process_mappings_with_real_targets(evt); + virtual_result.match_outcome.merge_with(real_match_outcome) + } + + /// This doesn't check if control enabled! You need to check before. + pub fn process_incoming_osc_packet(&mut self, evt: ControlEvent<&OscPacket>) { + if self.basics.settings.real_input_logging_enabled { + let timestamp = evt.timestamp(); + self.log_incoming_message(ControlEvent::new( + format_osc_packet(evt.into_payload()), + timestamp, + )); + } + match evt.payload() { + OscPacket::Message(msg) => { + let msg = MainSourceMessage::Osc(msg); + self.process_incoming_message_internal(evt.with_payload(msg)); + } + OscPacket::Bundle(bundle) => { + for p in bundle.content.iter() { + self.process_incoming_osc_packet(evt.with_payload(p)); + } + } + } + } + + fn process_incoming_message_internal( + &mut self, + evt: ControlEvent, + ) -> MatchOutcome { + match self.basics.control_mode { + ControlMode::Controlling => self.process_incoming_msg_for_controlling(evt), + ControlMode::LearningSource { + allow_virtual_sources, + osc_arg_index_hint, + } => { + if let Some(capture_result) = evt.payload().create_capture_result() { + self.process_incoming_msg_for_learning( + allow_virtual_sources, + osc_arg_index_hint, + capture_result, + ); + MatchOutcome::Consumed + } else { + // Should only happen with REAPER sources (where the match outcome isn't + // relevant). + MatchOutcome::Unmatched + } + } + ControlMode::Disabled => { + // "Disabled" means we use global learning, which is why we could consider it at + // least as consumed ... normally. However, global learning for keyboard keys is not + // supported yet, so we should not filter the event out! For OSC, we don't need the + // info at all because OSC doesn't support filtering out events. + MatchOutcome::Unmatched + } + } + } + + fn process_incoming_msg_for_learning( + &mut self, + allow_virtual_sources: bool, + osc_arg_index_hint: Option, + result: MessageCaptureResult, + ) { + let event = MessageCaptureEvent { + result, + allow_virtual_sources, + osc_arg_index_hint, + }; + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::CapturedIncomingMessage(event)); + } + + /// Controls mappings with real targets in *both* compartments. + fn process_mappings_with_real_targets( + &mut self, + evt: ControlEvent, + ) -> MatchOutcome { + let match_inactive = self.basics.settings.match_even_inactive_mappings; + let mut match_outcome = MatchOutcome::Unmatched; + for compartment in CompartmentKind::enum_iter() { + let mut enforce_target_refresh = false; + // Search for 958 to know why we use a for loop here instead of collect(). + let mut results = vec![]; + for m in self.collections.mappings[compartment] + .values_mut() + // Consider only control-enabled real mappings + .filter(|m| m.core.options.control_is_enabled) + { + let mapping_is_active = m.is_active(); + if !mapping_is_active && !match_inactive { + continue; + } + let control_outcome = m.control_source(evt.payload()); + match_outcome.upgrade_from(control_outcome.into()); + if !mapping_is_active { + continue; + } + let control_value = match control_outcome { + Some(ControlOutcome::Matched(v)) => v, + _ => continue, + }; + let control_event = evt.with_payload(control_value); + let options = ControlOptions { + enforce_target_refresh, + ..Default::default() + }; + let control_result = control_mapping_stage_one_and_two( + &self.basics, + &self.collections.parameters, + m, + control_event, + options, + ManualFeedbackProcessing::On { + mappings_with_virtual_targets: &self + .collections + .mappings_with_virtual_targets, + }, + ); + enforce_target_refresh = true; + let extended_control_result = ExtendedMappingControlResult { + control_result, + compartment, + group_interaction_input: GroupInteractionInput { + mapping_id: m.id(), + group_interaction: m.group_interaction(), + control_event, + }, + }; + results.push(extended_control_result); + } + for r in results { + control_mapping_stage_three( + &self.basics, + &mut self.collections, + r.compartment, + r.control_result, + GroupInteractionProcessing::On(r.group_interaction_input), + ) + } + } + match_outcome + } + + fn process_mapping_updates_due_to_activation_changes( + &mut self, + compartment: CompartmentKind, + mapping_updates: Vec, + target_updates: Vec, + unused_sources: UnusedSources, + changed_mappings: impl Iterator, + ) { + // Send feedback + self.handle_feedback_after_having_updated_particular_mappings( + compartment, + unused_sources, + changed_mappings, + ); + // Updating the "on" mappings is not exactly cheap as it turned out in + // https://github.com/helgoboss/helgobox/issues/913. And it also turns out we can skip it in many cases + // by checking if all updates are empty. + if mapping_updates.is_empty() && target_updates.is_empty() { + return; + } + // Propagate updates to real-time processor + if !mapping_updates.is_empty() { + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::UpdateMappingsPartially( + compartment, + mapping_updates, + )); + } + self.process_target_updates(compartment, target_updates); + // Update on mappings + self.update_on_mappings(); + } + + fn process_target_updates( + &mut self, + compartment: CompartmentKind, + target_updates: Vec, + ) { + if target_updates.is_empty() { + return; + } + // #692 If the target turns active/inactive or it changes to something else, we want it + // to have an effect on target-based conditional activation. + for target_update in &target_updates { + if !self.basics.target_based_conditional_activation_processors[compartment] + .is_lead_mapping(target_update.id) + { + continue; + } + let (target_is_active, target_value) = + match self.collections.mappings[compartment].get(&target_update.id) { + None => continue, + Some(mapping) => { + let target_is_active = mapping.target_is_active(); + let target_value = mapping.current_aggregated_target_value( + self.basics.control_context(mapping.compartment()), + ); + (target_is_active, target_value) + } + }; + let qualified_id = QualifiedMappingId::new(compartment, target_update.id); + self.process_conditional_activation_target_value_change( + qualified_id, + target_is_active, + target_value, + ); + } + // In some cases like closing projects, it's possible that this will + // fail because the real-time processor is + // already gone. But it doesn't matter. + self.basics + .channels + .normal_real_time_task_sender + .send_if_space(NormalRealTimeTask::UpdateTargetsPartially( + compartment, + target_updates, + )); + } + + fn update_single_mapping_on_state(&self, id: QualifiedMappingId) { + let is_on = + if let Some(m) = self.get_normal_or_virtual_target_mapping(id.compartment, id.id) { + m.is_effectively_on() + } else { + false + }; + self.basics.event_handler.handle_event_ignoring_error( + DomainEvent::UpdatedSingleMappingOnState(UpdatedSingleMappingOnStateEvent { + id, + is_on, + }), + ); + } + + fn update_on_mappings(&self) { + let on_mappings = self + .all_mappings() + .filter(|m| m.is_effectively_on()) + .map(MainMapping::qualified_id) + .collect(); + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::UpdatedOnMappings(on_mappings)); + } + + fn send_feedback( + &self, + reason: FeedbackReason, + feedback_values: impl IntoIterator, + ) { + self.basics.send_feedback( + &self.collections.mappings_with_virtual_targets, + reason, + feedback_values, + ); + } + + fn all_mappings(&self) -> impl Iterator { + self.all_mappings_without_virtual_targets() + .chain(self.collections.mappings_with_virtual_targets.values()) + } + + /// Includes virtual mappings if the controller mapping compartment is queried. + fn all_mappings_in_compartment( + &self, + compartment: CompartmentKind, + ) -> impl Iterator { + self.collections.mappings[compartment].values().chain( + self.collections + .mappings_with_virtual_targets + .values() + // Include virtual target mappings if we are talking about controller compartment. + .filter(move |_| compartment == CompartmentKind::Controller), + ) + } + + fn all_mappings_without_virtual_targets(&self) -> impl Iterator { + all_mappings_without_virtual_targets(&self.collections.mappings) + } + + pub fn send_all_feedback(&self) { + self.basics.clear_last_feedback(); + self.send_feedback(FeedbackReason::Normal, self.feedback_all()); + } + + fn feedback_all(&self) -> Vec { + // Virtual targets don't cause feedback themselves + self.all_mappings_without_virtual_targets() + .filter_map(|m| { + if m.feedback_is_effectively_on() { + m.feedback(true, self.basics.control_context(m.compartment())) + } else { + None + } + }) + .collect() + } + + fn feedback_particular_mappings( + &self, + compartment: CompartmentKind, + mapping_ids: impl Iterator, + ) -> Vec { + mapping_ids + .filter_map(|id| { + let m = self.get_normal_or_virtual_target_mapping(compartment, id)?; + if m.feedback_is_effectively_on() { + self.get_mapping_feedback_follow_virtual(m) + } else { + None + } + }) + .collect() + } + + fn feedback_all_in_compartment( + &self, + compartment: CompartmentKind, + ) -> Vec { + self.all_mappings_in_compartment(compartment) + .filter_map(|m| { + if m.feedback_is_effectively_on() { + self.get_mapping_feedback_follow_virtual(m) + } else { + None + } + }) + .collect() + } + + fn get_mapping_feedback_follow_virtual( + &self, + m: &MainMapping, + ) -> Option { + let followed_mapping = self.follow_maybe_virtual_mapping(m)?; + followed_mapping.feedback( + true, + self.basics.control_context(followed_mapping.compartment()), + ) + } + + fn follow_maybe_virtual_mapping<'a>(&'a self, m: &'a MainMapping) -> Option<&'a MainMapping> { + if let Some(control_element) = m.virtual_target_control_element() { + find_active_main_mapping_connected_to_virtual_control_element( + &self.collections.mappings[CompartmentKind::Main], + control_element, + ) + } else { + Some(m) + } + } + + pub fn handle_change_of_some_upper_floor_instance( + &self, + feedback_output: DeviceFeedbackOutput, + ) { + self.update_on_mappings(); + if self + .basics + .settings + .feedback_output + .and_then(FeedbackOutput::device_output) + == Some(feedback_output) + { + if self.basics.unit_feedback_is_effectively_enabled() { + debug!("Reactivating unit..."); + // For this to really work reliably (eventual feedback consistency), it was + // necessary to let the direct MIDI device feedback process in the global + // *audio hook*, not in the real-time processor. Because there's only one audio + // hook can guarantee a deterministic feedback send order. + self.send_all_feedback(); + } else { + debug!("Cancelling unit..."); + self.send_feedback(FeedbackReason::SuspendInstance, self.feedback_all_zero()); + } + } + } + + /// When feedback gets globally disabled. + fn clear_all_feedback_allowing_source_takeover(&self) { + debug!("Clearing all feedback allowing source takeover..."); + self.send_feedback( + FeedbackReason::ClearAllAllowingSourceTakeover, + self.feedback_all_zero(), + ); + } + + pub fn switch_lights_off(&mut self) { + if self.basics.unit_feedback_is_effectively_enabled() { + // We clear feedback right here and now because that's the last chance. + // Other instances can take over the feedback output afterwards. + self.clear_all_feedback_preventing_source_takeover(); + } + } + + /// When main processor goes away for good. + fn clear_all_feedback_preventing_source_takeover(&self) { + debug!("Clearing all feedback preventing source takeover..."); + self.send_feedback( + FeedbackReason::ClearAllPreventingSourceTakeover, + self.feedback_all_zero(), + ); + } + + fn feedback_all_zero(&self) -> Vec { + // Mappings with virtual targets should not be included here because they might not be in + // use and therefore should not *directly* send zeros. However, they will receive zeros + // if one of the main mappings with virtual sources are connected to them. + self.all_mappings_without_virtual_targets() + .filter(|m| m.feedback_is_effectively_on()) + .filter_map(|m| { + let logger = self.basics.source_feedback_logger(m.qualified_id()); + m.off_feedback(self.basics.source_context(m.compartment()), logger) + }) + .collect() + } + + /// Returns a hash map where each key is the source address of a source whose mapping + /// emits feedback and the value is the "off feedback" message. + /// + /// This will be used to check which sources are not in use anymore and then send the + /// "off feedback" message accordingly. + fn currently_feedback_enabled_sources( + &self, + compartment: CompartmentKind, + include_virtual: bool, + ) -> UnusedSources { + if include_virtual { + self.all_mappings_in_compartment(compartment) + .filter(|m| m.feedback_is_effectively_on()) + .filter_map(|m| { + Some(( + m.source() + .extract_feedback_address(self.basics.source_context(compartment))?, + m.off_feedback(self.basics.source_context(compartment), NoopLogger)?, + )) + }) + .collect() + } else { + self.collections.mappings[compartment] + .values() + .filter(|m| m.feedback_is_effectively_on()) + .filter_map(|m| { + Some(( + m.source().extract_feedback_address( + self.basics.source_context(m.compartment()), + )?, + m.off_feedback(self.basics.source_context(m.compartment()), NoopLogger)?, + )) + }) + .collect() + } + } + + fn handle_feedback_after_having_updated_all_mappings( + &mut self, + compartment: CompartmentKind, + now_unused_sources: UnusedSources, + ) { + self.send_feedback( + FeedbackReason::Normal, + self.feedback_all_in_compartment(compartment), + ); + // It's important to send that *after* sending normal feedback since #660 because we might + // have global source state (e.g. X-Touch Color state) and the order how we apply feedback + // values to that global source state matters. It must correspond to the order in which + // feedback messages are ultimately sent. "Off" feedback will always be sent *after* other + // feedback because it's first subject to source takeover by other instances. + self.send_off_feedback_for_unused_sources(now_unused_sources); + } + + fn handle_feedback_after_having_updated_particular_mappings( + &mut self, + compartment: CompartmentKind, + now_unused_sources: UnusedSources, + mapping_ids: impl Iterator, + ) { + self.send_feedback( + FeedbackReason::Normal, + self.feedback_particular_mappings(compartment, mapping_ids), + ); + // It's important to send that *after* sending normal feedback since #660 because we might + // have global source state (e.g. X-Touch Color state) and the order how we apply feedback + // values to that global source state matters. It must correspond to the order in which + // feedback messages are ultimately sent. "Off" feedback will always be sent *after* other + // feedback because it's first subject to source takeover by other instances. + self.send_off_feedback_for_unused_sources(now_unused_sources); + } + + /// Indicate via off feedback the sources which are not in use anymore. + fn send_off_feedback_for_unused_sources(&self, now_unused_sources: UnusedSources) { + for feedback_value in now_unused_sources.into_values() { + self.send_feedback(FeedbackReason::ClearUnusedSource, Some(feedback_value)); + } + } + + fn log_debug_info(&mut self) { + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::LogDebugInfo); + // Summary + let msg = format!( + "\n\ + # Main processor\n\ + \n\ + - State: {:?} \n\ + - Total main mapping count: {} \n\ + - Enabled main mapping count: {} \n\ + - Total non-virtual controller mapping count: {} \n\ + - Enabled non-virtual controller mapping count: {} \n\ + - Total virtual controller mapping count: {} \n\ + - Enabled virtual controller mapping count: {} \n\ + - Control task count: {} \n\ + - Feedback task count: {} \n\ + - Parameters: {:?} \n\ + ", + self.basics.control_mode, + self.collections.mappings[CompartmentKind::Main].len(), + self.collections.mappings[CompartmentKind::Main] + .values() + .filter(|m| m.control_is_effectively_on() || m.feedback_is_effectively_on()) + .count(), + self.collections.mappings[CompartmentKind::Controller].len(), + self.collections.mappings[CompartmentKind::Controller] + .values() + .filter(|m| m.control_is_effectively_on() || m.feedback_is_effectively_on()) + .count(), + self.collections.mappings_with_virtual_targets.len(), + self.collections + .mappings_with_virtual_targets + .values() + .filter(|m| m.control_is_effectively_on() || m.feedback_is_effectively_on()) + .count(), + self.basics.channels.control_task_receiver.len(), + self.basics.channels.feedback_task_receiver.len(), + &self.collections.parameters, + ); + Reaper::get().show_console_msg(msg); + // Detailed + trace!( + "\n\ + # Main processor\n\ + \n\ + {:#?} + ", + self + ); + } + + fn log_mapping(&self, compartment: CompartmentKind, mapping_id: MappingId) { + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::LogMapping(compartment, mapping_id)); + // Summary + let mapping = self + .all_mappings_in_compartment(compartment) + .find(|m| m.id() == mapping_id); + let msg = format!( + "\n\ + # Main processor\n\ + \n\ + Mapping with ID {mapping_id}:\n\ + {mapping:#?} + " + ); + Reaper::get().show_console_msg(msg); + } + + fn update_single_mapping(&mut self, mut mapping: Box) { + let compartment = mapping.compartment(); + debug!( + "Updating single mapping {:?} in {}...", + mapping.id(), + compartment, + ); + self.basics.clear_last_feedback(); + // Refresh + let control_context = self.basics.control_context(compartment); + mapping.init_target_and_activation( + ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ), + control_context, + ); + let initial_target_value = mapping.initial_target_value(); + let lead_mapping_ids = mapping.activation_can_be_affected_by_target_values(); + // Sync to real-time processor + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::UpdateSingleMapping( + compartment, + Box::new(mapping.splinter_real_time_mapping()), + )); + // Update and feedback + let id = QualifiedMappingId::new(compartment, mapping.id()); + // Important to do this before calculating diff feedback (because we might have + // a textual feedback expression that contains the mapping name property). + self.basics + .unit + .borrow_mut() + .update_mapping_info(id, mapping.take_mapping_info()); + let diff_feedback = self.calc_diff_feedback_complicated( + self.get_normal_or_virtual_target_mapping(mapping.compartment(), mapping.id()), + &mapping, + ); + let target_is_active = mapping.target_is_active(); + self.update_map_entries(compartment, *mapping); + self.send_diff_feedback(diff_feedback); + self.update_single_mapping_on_state(id); + // This could be a lead mapping in terms of target-based conditional activation. If so, + // the target value probably changed and all follow mappings can be affected. + self.process_conditional_activation_target_value_change( + id, + target_is_active, + initial_target_value, + ); + // But it could also be a follow mapping. + self.process_conditional_activation_target_value_changes(compartment, lead_mapping_ids); + } + + fn process_conditional_activation_target_value_changes( + &mut self, + compartment: CompartmentKind, + lead_mapping_ids: impl Iterator, + ) { + for lead_mapping_id in lead_mapping_ids { + let (target_is_active, target_value) = + match self.collections.mappings[compartment].get(&lead_mapping_id) { + None => continue, + Some(mapping) => { + let target_is_active = mapping.target_is_active(); + let target_value = mapping.current_aggregated_target_value( + self.basics.control_context(mapping.compartment()), + ); + (target_is_active, target_value) + } + }; + let qualified_id = QualifiedMappingId::new(compartment, lead_mapping_id); + self.process_conditional_activation_target_value_change( + qualified_id, + target_is_active, + target_value, + ); + } + } + + fn update_persistent_mapping_processing_state( + &mut self, + id: QualifiedMappingId, + state: PersistentMappingProcessingState, + ) { + debug!( + "Updating persistent processing state of mapping {:?} in {}", + id.id, id.compartment + ); + // Sync to real-time processor + self.basics + .channels + .normal_real_time_task_sender + .send_complaining(NormalRealTimeTask::UpdatePersistentMappingProcessingState { + id, + state, + }); + // Update + let (was_on_before, is_on_now) = + if let Some(m) = self.get_normal_or_virtual_target_mapping_mut(id) { + let was_on_before = m.feedback_is_effectively_on(); + m.update_persistent_processing_state(state); + (was_on_before, m.feedback_is_effectively_on()) + } else { + (false, false) + }; + // Send feedback if necessary (right now we assume that changed processing state doesn't + // change anything about the source or target, so we use a much more simple mechanism to + // determine necessary diff feedback than when updating the complete mapping). + if was_on_before != is_on_now { + if let Some(m) = self.get_normal_or_virtual_target_mapping(id.compartment, id.id) { + let fb = if is_on_now { + Fb::normal(self.get_mapping_feedback_follow_virtual(m)) + } else { + Fb::unused( + m.off_feedback(self.basics.source_context(m.compartment()), NoopLogger), + ) + }; + self.send_feedback(fb.0, fb.1); + } + } + self.update_single_mapping_on_state(id); + } + + /// Collect feedback (important to send later as soon as mappings updated). + #[must_use] + fn calc_diff_feedback_complicated( + &self, + previous_mapping: Option<&MainMapping>, + mapping: &MainMapping, + ) -> (Fb, Fb) { + if let Some(previous_mapping) = previous_mapping { + // An existing mapping is being overwritten. + if previous_mapping.feedback_is_effectively_on() { + // And its light is currently on. + if mapping.source().has_same_feedback_address_as_source( + previous_mapping.source(), + self.basics.source_context(mapping.compartment()), + ) { + // Source is the same. + if mapping.feedback_is_effectively_on() { + // Lights should still be on. + // Send new lights. + ( + Fb::none(), + Fb::normal(self.get_mapping_feedback_follow_virtual(mapping)), + ) + } else { + // Lights should now be off. + ( + Fb::unused(mapping.off_feedback( + self.basics.source_context(mapping.compartment()), + NoopLogger, + )), + Fb::none(), + ) + } + } else { + // Source has changed. + // Switch previous source light off. + let fb1 = Fb::unused(previous_mapping.off_feedback( + self.basics.source_context(mapping.compartment()), + NoopLogger, + )); + let fb2 = if mapping.feedback_is_effectively_on() { + // Lights should be on. Send new lights. + Fb::normal(self.get_mapping_feedback_follow_virtual(mapping)) + } else { + Fb::none() + }; + // Unused *after* new (because sent in that order, important for #660) + (fb2, fb1) + } + } else { + // Previous lights were off. + if mapping.feedback_is_effectively_on() { + // Now should be on. + ( + Fb::none(), + Fb::normal(self.get_mapping_feedback_follow_virtual(mapping)), + ) + } else { + // Still off. + (Fb::none(), Fb::none()) + } + } + } else { + // This mapping is new. + if mapping.feedback_is_effectively_on() { + // Lights on. + ( + Fb::none(), + Fb::normal(self.get_mapping_feedback_follow_virtual(mapping)), + ) + } else { + // Lights off. + (Fb::none(), Fb::none()) + } + } + } + + fn send_diff_feedback(&self, (fb1, fb2): (Fb, Fb)) { + self.send_feedback(fb1.0, fb1.1); + self.send_feedback(fb2.0, fb2.1); + } + + fn update_map_entries(&mut self, compartment: CompartmentKind, m: MainMapping) { + if m.needs_refresh_when_target_touched() { + self.collections.target_touch_dependent_mappings[compartment].insert(m.id()); + } else { + self.collections.target_touch_dependent_mappings[compartment].shift_remove(&m.id()); + } + let influence = m.feedback_resolution(); + if influence == Some(FeedbackResolution::Beat) { + self.collections.beat_dependent_feedback_mappings[compartment].insert(m.id()); + } else { + self.collections.beat_dependent_feedback_mappings[compartment].shift_remove(&m.id()); + } + if influence == Some(FeedbackResolution::High) { + self.collections.milli_dependent_feedback_mappings[compartment].insert(m.id()); + } else { + self.collections.milli_dependent_feedback_mappings[compartment].shift_remove(&m.id()); + self.collections.previous_target_values[compartment].remove(&m.id()); + } + if m.wants_to_be_polled_for_control() { + self.poll_control_mappings[compartment].insert(m.id()); + } else { + self.poll_control_mappings[compartment].shift_remove(&m.id()); + } + let target_value_activation_reference_mappings = + m.activation_can_be_affected_by_target_values(); + self.basics.target_based_conditional_activation_processors[compartment] + .notify_usage(m.id(), target_value_activation_reference_mappings); + let relevant_map = if m.has_virtual_target() { + self.collections.mappings[compartment].shift_remove(&m.id()); + &mut self.collections.mappings_with_virtual_targets + } else { + self.collections + .mappings_with_virtual_targets + .shift_remove(&m.id()); + &mut self.collections.mappings[compartment] + }; + relevant_map.insert(m.id(), m); + } + + fn hit_target(&mut self, id: QualifiedMappingId, value: ControlValue) { + let control_result = if let Some(m) = + self.collections.mappings[id.compartment].get_mut(&id.id) + { + let control_context = self.basics.control_context(id.compartment); + let mut control_result = m.control_from_target_directly( + control_context, + ExtendedProcessorContext::new( + &self.basics.context, + &self.collections.parameters, + control_context, + ), + value, + self.basics + .target_control_logger(ControlLogContext::Direct, m.qualified_id()), + ); + control_mapping_stage_two( + &self.basics, + &mut control_result, + m, + ManualFeedbackProcessing::On { + mappings_with_virtual_targets: &self.collections.mappings_with_virtual_targets, + }, + ); + Some(control_result) + } else { + None + }; + if let Some(control_result) = control_result { + control_mapping_stage_three( + &self.basics, + &mut self.collections, + id.compartment, + control_result, + GroupInteractionProcessing::Off, + ); + } + } +} + +/// State that contains only those properties of a mapping which ... +/// +/// - make a difference in terms of processing +/// - are changed in response to processing +/// - and are persisted as part of the session. +/// +/// These properties follow an unusual data flow, but still an unidirectional one: They are +/// propagated from the processing layer to the session (via synchronous event), persisted into the +/// session and sent back (asynchronously via channel) to the processor - which causes the actual +/// change. +#[derive(Copy, Clone, Debug)] +pub struct PersistentMappingProcessingState { + pub is_enabled: bool, +} + +/// A task which is sent from time to time. +#[derive(Debug)] +pub enum NormalMainTask { + /// Clears all mappings and uses the passed ones. + UpdateAllMappings(CompartmentKind, Vec), + /// Replaces the given mapping. + // Boxed because much larger struct size than other variants. + UpdateSingleMapping(Box), + // Available separately for performance reasons, because these updates are also triggered + // triggered by processing itself, so it should happen fast. + UpdatePersistentMappingProcessingState { + id: QualifiedMappingId, + state: PersistentMappingProcessingState, + }, + /// Invokes the "ReaLearn instance started" source. + NotifyRealearnUnitStarted, + /// Invokes the "ReaLearn compartment loaded" source. + NotifyRealearnCompartmentLoaded(CompartmentKind), + /// Instructs the main processor to hit the target directly. + /// + /// This doesn't invoke group interaction because it's meant to totally skip the mode. + HitTarget { + id: QualifiedMappingId, + value: ControlValue, + }, + /// This should be sent on events such as track list change, FX focus etc. + /// + /// It will trigger a refresh of all targets (re-resolve) or even a preset change (if + /// auto-load is enabled). + NotifyConditionsChanged, + UpdateSettings(BasicSettings), + UpdateCompartmentSettings(CompartmentKind, CompartmentSettings), + PotentiallyEnableOrDisableControlOrFeedback, + SendAllFeedback, + LogDebugInfo, + LogMapping(CompartmentKind, MappingId), + StartLearnSource { + allow_virtual_sources: bool, + osc_arg_index_hint: Option, + }, + DisableControl, + ReturnToControlMode, + UseIntegrationTestFeedbackSender(SenderToNormalThread), +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct BasicSettings { + pub unit_enabled: bool, + pub control_input: ControlInput, + pub wants_keyboard_input: bool, + pub streamdeck_device_id: Option, + pub feedback_output: Option, + pub real_input_logging_enabled: bool, + pub real_output_logging_enabled: bool, + pub virtual_input_logging_enabled: bool, + pub virtual_output_logging_enabled: bool, + pub target_control_logging_enabled: bool, + pub send_feedback_only_if_armed: bool, + pub match_even_inactive_mappings: bool, + pub let_matched_events_through: bool, + pub let_unmatched_events_through: bool, + pub reset_feedback_when_releasing_source: bool, + pub stay_active_when_project_in_background: StayActiveWhenProjectInBackground, +} + +#[derive(Clone, Debug, Default)] +pub struct CompartmentSettings { + pub common_lua: Option, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + serde::Serialize, + serde::Deserialize, + EnumIter, + derive_more::Display, +)] +pub enum StayActiveWhenProjectInBackground { + /// Never. + #[display(fmt = "Never")] + Never, + /// Respecting the REAPER project tab settings such as "Run background projects". + #[display(fmt = "Only if background project is running")] + OnlyIfBackgroundProjectIsRunning, + /// As far as possible. + #[display(fmt = "Always (more or less)")] + Always, +} + +impl Default for StayActiveWhenProjectInBackground { + fn default() -> Self { + Self::OnlyIfBackgroundProjectIsRunning + } +} + +impl BasicSettings { + pub fn target_control_logger<'a>( + &'a self, + instance_state: &'a SharedUnit, + context: ControlLogContext, + mapping_id: QualifiedMappingId, + ) -> impl Fn(ControlLogEntry) + 'a { + move |entry| { + if !self.target_control_logging_enabled { + return; + } + if context == ControlLogContext::Polling + && entry.error.is_empty() + && entry.kind == ControlLogEntryKind::IgnoredByGlue + { + // This pollutes the log massively. + return; + } + let instance_state = instance_state.borrow(); + let mapping_name = if let Some(info) = instance_state.get_mapping_info(mapping_id) { + info.name.as_str() + } else { + "" + }; + log_target_control( + instance_state.id(), + format!("Mapping {mapping_name}: {entry} ({context})"), + ); + } + } + /// For real-time processor usage. + pub fn midi_control_input(&self) -> MidiControlInput { + self.control_input + .midi_control_input() + .unwrap_or(MidiControlInput::FxInput) + } + + /// For real-time processor usage. + pub fn midi_destination(&self) -> Option { + self.feedback_output + .and_then(|output| output.midi_destination()) + } +} + +/// A task which is sent from time to time from real-time to main processor. +#[derive(Debug)] +pub enum NormalRealTimeToMainThreadTask { + CaptureMidi { + scan_result: MidiScanResult, + allow_virtual_sources: bool, + }, + /// This is sent by the real-time processor after it has not been called for a while because + /// the audio device was closed. It wants everything resynced: + /// + /// - All mappings + /// - Instance settings + /// - Feedback + FullResyncToRealTimeProcessorPlease, + LogLifecycleOutput { + value: MidiSourceValue<'static, RawShortMessage>, + }, + LogToConsole(String), +} + +/// A parameter-related task (which is potentially sent very frequently, just think of automation). +#[derive(Debug)] +pub enum ParameterMainTask { + UpdateSingleParamValue { + index: PluginParamIndex, + value: RawParamValue, + timestamp: ControlEventTimestamp, + }, + UpdateAllParams(PluginParams), +} + +/// A feedback-related task (which is potentially sent very frequently). +#[derive(Debug)] +pub enum FeedbackMainTask { + /// Only sent if this mapping is somewhere referenced in target value activation conditions. + /// + /// It's used for asynchronously re-evaluating the follow mappings' activation conditions. + /// At this point, it can be assumed that the target is active! + MappingTargetValueChanged { + lead_mapping_id: QualifiedMappingId, + target_value: AbsoluteValue, + }, +} + +/// A control-related task (which is potentially sent very frequently). +pub enum ControlMainTask { + /// Control event coming in from real-time processor (MIDI). + ControlFromRealTime { + compartment: CompartmentKind, + mapping_id: MappingId, + event: ControlEvent, + options: ControlOptions, + }, + LogVirtualControlInput { + event: ControlEvent, + match_outcome: MatchOutcome, + }, + LogRealControlInput { + event: ControlEvent>, + match_outcome: MatchOutcome, + }, + LogRealLearnInput { + event: ControlEvent, + }, + LogTargetOutput { + event: Box, + }, + LogTargetControl { + mapping_id: QualifiedMappingId, + entry: ControlLogEntry, + }, +} + +pub enum OwnedIncomingMidiMessage { + Short(RawShortMessage), + SysEx(Vec), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct ControlOptions { + pub enforce_send_feedback_after_control: bool, + pub mode_control_options: ModeControlOptions, + /// Set this flag if this control operation is part of processing multiple mappings within one + /// transaction. + /// Reason: Possibly triggered change events (e.g. change of selected track) will result in + /// refreshing all targets *after* the transaction, which might be to late if the user relies on + /// mapping order! Setting `refresh_target` will enforce refreshing (without updating cache). + pub enforce_target_refresh: bool, + /// This means control was initiated in the real-time processor (currently MIDI only). + /// + /// This information is used by some particular targets whose work is partially done in real-time and partially + /// in the main thread. + pub coming_from_real_time: bool, +} + +impl Drop for MainProcessor { + fn drop(&mut self) { + debug!("Dropping main processor..."); + self.switch_lights_off(); + let released_event = self + .basics + .io_released_event(self.any_main_mapping_is_effectively_on()); + self.send_io_update_if_space(released_event); + } +} + +/// Different feedback reasons can but don't have to result in slightly different behavior. +/// +/// In any case, they are nice for tracing when debugging feedback issues. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum FeedbackReason { + /// When ReaLearn detects a single source as unused. + ClearUnusedSource, + /// When all feedback for that instance gets disabled (e.g. by instance deactivation) but other + /// instances should get a chance to grab some sources. Must be processed even if feedback + /// globally disabled (because sent *after* globally disabling feedback). + ClearAllAllowingSourceTakeover, + /// When all feedback for that instance gets disabled and switching off is more important than + /// letting other instances take over (e.g. when removing instance completely). Only needs to + /// be processed when feedback enabled. + ClearAllPreventingSourceTakeover, + /// When a lower-floor ReaLearn instance is cancelled by an upper-floor one. Must be processed + /// even if feedback is effectively disabled (because sent at a time when the lower-floor + /// instance is covered by the higher-floor instance already). + SuspendInstance, + /// Normal feedback scenarios. + Normal, + /// When a ReaLearn instance X takes control of a source after Y has released the source. + TakeOverSource, + /// When no ReaLearn instance has taken over a source and now it's finally being switched off. + FinallySwitchOffSource, +} + +impl FeedbackReason { + /// When this returns true, it allows source takeover by other instances. + pub fn is_source_release(self) -> bool { + use FeedbackReason::*; + matches!( + self, + ClearUnusedSource | ClearAllAllowingSourceTakeover | SuspendInstance + ) + } + + /// When this returns true, the processor will initiate the feedback send logic (including + /// source takeover) always regardless if feedback is globally enabled or not. + pub fn is_always_allowed(self) -> bool { + matches!( + self, + FeedbackReason::SuspendInstance | FeedbackReason::ClearAllAllowingSourceTakeover + ) + } + + /// When this returns true, it's feedback that's supposed to reset the source (fader, lights) + /// because it has been released. This kind of feedback will only be sent if + /// "Reset feedback when releasing source" is enabled. + pub fn is_reset_because_of_source_release(self) -> bool { + use FeedbackReason::*; + matches!( + self, + ClearUnusedSource | ClearAllPreventingSourceTakeover | FinallySwitchOffSource + ) + } +} + +pub struct BasicSourceFeedbackLogger<'a, EH: DomainEventHandler> { + basics: &'a Basics, + mapping_id: QualifiedMappingId, +} + +impl SourceFeedbackLogger for BasicSourceFeedbackLogger<'_, EH> { + fn log(&self, entry: FeedbackLogEntry) { + let event = SourceFeedbackEvent { + id: self.mapping_id, + log_entry: entry, + }; + self.basics + .event_handler + .handle_event_ignoring_error(DomainEvent::HandleSourceFeedback(event)); + } +} + +impl Basics { + pub fn notify_conditions_changed(&self) { + self.channels + .self_normal_sender + .send_complaining(NormalMainTask::NotifyConditionsChanged); + } + + pub fn celebrate_success(&self) { + self.event_handler + .handle_event_ignoring_error(DomainEvent::TimeForCelebratingSuccess); + } + + pub fn target_control_logger( + &self, + context: ControlLogContext, + mapping_id: QualifiedMappingId, + ) -> impl Fn(ControlLogEntry) + '_ { + let console_logger = self + .settings + .target_control_logger(&self.unit, context, mapping_id); + move |entry| { + // Handle logging to mapping panel + if context != ControlLogContext::Polling { + let event = TargetControlEvent::new(mapping_id, context, entry); + self.event_handler + .handle_event_ignoring_error(DomainEvent::HandleTargetControl(event)); + } + // Handle logging to console + console_logger(entry); + } + } + + pub fn source_context(&self, compartment: CompartmentKind) -> RealearnSourceContext { + RealearnSourceContext { + additional_script_input: AdditionalLuaMidiSourceScriptInput { + compartment_lua: self.common_lua[compartment].as_ref(), + }, + } + } + + pub fn mode_context(&self, compartment: CompartmentKind) -> RealearnModeContext { + RealearnModeContext { + additional_script_input: AdditionalLuaFeedbackScriptInput { + compartment_lua: self.common_lua[compartment].as_ref(), + }, + } + } + + pub fn source_feedback_logger( + &self, + mapping_id: QualifiedMappingId, + ) -> impl SourceFeedbackLogger + '_ { + BasicSourceFeedbackLogger { + basics: self, + mapping_id, + } + } + + pub fn update_settings_internal( + &mut self, + settings: BasicSettings, + any_main_mapping_is_effectively_on: bool, + ) { + self.clear_last_feedback(); + // Send released event (important to create before updating settings) + let released_event = self.io_released_event(any_main_mapping_is_effectively_on); + self.send_io_update_complaining(released_event); + // Update settings and feedback + self.settings = settings; + } + + pub fn potentially_enable_or_disable_control_internal( + &mut self, + any_main_mapping_is_effectively_on: bool, + project_options: ProjectOptions, + ) { + let new_control_is_enabled = + self.potentially_enable_or_disable_control_very_internal(project_options); + // Important to send this to real-time processor even if state hasn't changed. It's possible that the + // real-time processor has requested a full resync and therefore discarded a previous state change. + self.channels.normal_real_time_task_sender.send_complaining( + NormalRealTimeTask::UpdateControlIsGloballyEnabled(self.control_is_globally_enabled), + ); + if let Some(new_control_is_enabled) = new_control_is_enabled { + debug!( + "Updated control_is_globally_enabled to {}", + new_control_is_enabled + ); + let event = IoUpdatedEvent { + ..self.create_basic_io_changed_event( + any_main_mapping_is_effectively_on, + &self.current_control_feedback_settings(), + ) + }; + self.send_io_update_complaining(event); + } + } + + /// Returns `Some` with new value if has actually changed. + pub fn potentially_enable_or_disable_feedback_internal( + &mut self, + any_main_mapping_is_effectively_on: bool, + project_options: ProjectOptions, + ) -> Option { + let new_feedback_is_enabled = + self.potentially_enable_or_disable_feedback_very_internal(project_options); + // Important to send this to real-time processor even if state hasn't changed. It's possible that the + // real-time processor has requested a full resync and therefore discarded a previous state change. + self.channels.normal_real_time_task_sender.send_complaining( + NormalRealTimeTask::UpdateFeedbackIsGloballyEnabled(self.feedback_is_globally_enabled), + ); + if let Some(new_feedback_is_enabled) = new_feedback_is_enabled { + debug!( + "Updated feedback_is_globally_enabled to {}", + new_feedback_is_enabled + ); + self.clear_last_feedback(); + let changed_event = self + .feedback_output_usage_might_have_changed_event(any_main_mapping_is_effectively_on); + self.send_io_update_complaining(changed_event); + } + new_feedback_is_enabled + } + + /// Returns `Some` with new value if has actually changed. + fn potentially_enable_or_disable_control_very_internal( + &mut self, + project_options: ProjectOptions, + ) -> Option { + let new_value = + determine_control_globally_enabled(&self.context, &self.settings, project_options); + let changed = new_value != self.control_is_globally_enabled; + self.control_is_globally_enabled = new_value; + if changed { + Some(new_value) + } else { + None + } + } + + /// Returns `Some` with new value if has actually changed. + fn potentially_enable_or_disable_feedback_very_internal( + &mut self, + project_options: ProjectOptions, + ) -> Option { + let new_value = + determine_feedback_globally_enabled(&self.context, &self.settings, project_options); + let changed = new_value != self.feedback_is_globally_enabled; + self.feedback_is_globally_enabled = new_value; + if changed { + Some(new_value) + } else { + None + } + } + + fn send_io_update_complaining(&self, event: IoUpdatedEvent) { + self.channels + .unit_orchestration_event_sender + .send_complaining(UnitOrchestrationEvent::IoUpdated(event)); + } + + fn io_released_event(&self, any_main_mapping_is_effectively_on: bool) -> IoUpdatedEvent { + IoUpdatedEvent { + control_input_used: false, + feedback_output_used: false, + ..self + .feedback_output_usage_might_have_changed_event(any_main_mapping_is_effectively_on) + } + } + + fn current_control_feedback_settings(&self) -> ControlFeedbackSettings { + ControlFeedbackSettings { + control_input: self.settings.control_input, + feedback_output: self.settings.feedback_output, + control_is_globally_enabled: self.control_is_globally_enabled, + feedback_is_globally_enabled: self.feedback_is_globally_enabled, + } + } + + fn feedback_output_usage_might_have_changed_event( + &self, + any_main_mapping_is_effectively_on: bool, + ) -> IoUpdatedEvent { + IoUpdatedEvent { + feedback_output_usage_might_have_changed: true, + ..self.create_basic_io_changed_event( + any_main_mapping_is_effectively_on, + &self.current_control_feedback_settings(), + ) + } + } + + fn create_basic_io_changed_event( + &self, + any_main_mapping_is_effectively_on: bool, + settings: &ControlFeedbackSettings, + ) -> IoUpdatedEvent { + IoUpdatedEvent { + unit_id: self.unit_id, + control_input: settings.control_input.device_input(), + control_input_used: settings.control_is_globally_enabled + && any_main_mapping_is_effectively_on, + feedback_output: settings.feedback_output.and_then(|o| o.device_output()), + feedback_output_used: settings.feedback_is_globally_enabled + && any_main_mapping_is_effectively_on, + feedback_output_usage_might_have_changed: false, + } + } + + pub fn clear_last_feedback(&self) { + self.last_feedback_checksum_by_address.borrow_mut().clear(); + } + + pub fn control_context(&self, compartment: CompartmentKind) -> ControlContext { + ControlContext { + feedback_audio_hook_task_sender: &self.channels.feedback_audio_hook_task_sender, + feedback_real_time_task_sender: &self.channels.feedback_real_time_task_sender, + osc_feedback_task_sender: &self.channels.osc_feedback_task_sender, + feedback_output: self.settings.feedback_output, + stream_deck_dev_id: self.settings.streamdeck_device_id, + unit_container: self.unit_container, + instance_id: self.instance_id, + instance: &self.instance, + unit: &self.unit, + unit_id: self.unit_id, + output_logging_enabled: self.settings.real_output_logging_enabled, + source_context: self.source_context(compartment), + mode_context: self.mode_context(compartment), + processor_context: &self.context, + } + } + + pub fn process_group_interaction( + &self, + collections: &mut Collections, + compartment: CompartmentKind, + mapping_id: MappingId, + control_event: ControlEvent, + control_was_successful: bool, + ) { + if let Some(m) = collections.mappings[compartment].get(&mapping_id) { + // Group interaction + let group_id = m.group_id(); + use GroupInteraction::*; + match m.group_interaction() { + None => {} + SameControl | InverseControl => { + let control_value = if m.group_interaction().is_inverse() { + control_event.payload().inverse() + } else { + control_event.payload() + }; + let control_event = control_event.with_payload(control_value); + self.process_other_mappings( + collections, + compartment, + mapping_id, + group_id, + |other_mapping, basics, parameters| { + let options = ControlOptions { + // Previous mappings in this transaction could affect + // subsequent mappings! + enforce_target_refresh: true, + ..Default::default() + }; + control_mapping_stage_one_and_two( + basics, + parameters, + other_mapping, + control_event, + options, + ManualFeedbackProcessing::Off, + ) + }, + ); + } + SameTargetValue + | InverseTargetValue + | InverseTargetValueOnOnly + | InverseTargetValueOffOnly => { + if !control_was_successful { + return; + } + let context = self.control_context(compartment); + if let Some(reference_value) = m.current_aggregated_target_value(context) { + let is_on = reference_value.is_on(); + if (m.group_interaction() == InverseTargetValueOnOnly && !is_on) + || (m.group_interaction() == InverseTargetValueOffOnly && is_on) + { + return; + } + let normalized_target_value = reference_value.normalize( + &m.mode().settings().target_value_interval, + &m.mode().settings().discrete_target_value_interval, + MinIsMaxBehavior::PreferOne, + m.mode().settings().use_discrete_processing, + BASE_EPSILON, + ); + let inverse = m.group_interaction().is_inverse(); + self.process_other_mappings( + collections, + compartment, + mapping_id, + group_id, + |other_mapping, basics, parameters| { + let control_context = + basics.control_context(other_mapping.compartment()); + other_mapping.control_from_target_via_group_interaction( + normalized_target_value, + ControlOptions { + // Previous mappings in this transaction could affect + // subsequent mappings! + enforce_target_refresh: true, + ..Default::default() + }, + control_context, + inverse, + ExtendedProcessorContext::new( + &self.context, + parameters, + control_context, + ), + basics.target_control_logger( + ControlLogContext::GroupInteraction, + other_mapping.qualified_id(), + ), + ) + }, + ); + } + } + } + } + } + + fn process_other_mappings( + &self, + collections: &mut Collections, + compartment: CompartmentKind, + mapping_id: MappingId, + group_id: GroupId, + f: impl Fn(&mut MainMapping, &Basics, &PluginParams) -> MappingControlResult, + ) { + let other_mappings = collections.mappings[compartment] + .values_mut() + .filter(|other_m| { + other_m.id() != mapping_id + && other_m.group_id() == group_id + && other_m.control_is_effectively_on() + }); + // Interestingly, we can't use closures like for_each or filter_map here in the same way + // (fails with mutable + immutable borrow error). So we use a for loop and fill the + // result vector manually. + // TODO-low Rust question 958: Figure out the difference to the for loop. + let mut hit_instructions = vec![]; + for other_mapping in other_mappings { + let other_control_result = f(other_mapping, self, &collections.parameters); + if let Some(new_value) = other_control_result.new_target_value { + self.notify_target_value_changed(other_mapping, new_value); + } + self.send_feedback( + &collections.mappings_with_virtual_targets, + FeedbackReason::Normal, + other_control_result.feedback_value, + ); + if let Some(hi) = other_control_result.hit_instruction { + hit_instructions.push(hi); + } + } + for hi in hit_instructions { + hi.execute(HitInstructionContext { + mappings: &mut collections.mappings[compartment], + control_context: self.control_context(compartment), + domain_event_handler: &self.event_handler, + basic_settings: &self.settings, + processor_context: ExtendedProcessorContext::new( + &self.context, + &collections.parameters, + self.control_context(compartment), + ), + }); + } + } + + /// The given function f is NOW required to return the current target value. + // https://github.com/rust-lang/rust-clippy/issues/6066 + #[allow(clippy::needless_collect)] + pub fn process_feedback_related_reaper_event_for_mapping( + &self, + m: &MainMapping, + mappings_with_virtual_targets: &OrderedMappingMap, + f: &mut impl FnMut(&MainMapping, &ReaperTarget) -> (bool, Option), + ) { + // It's enough if one of the resolved targets is affected. Then we are going to need the + // values of all of them! + let mut at_least_one_target_is_affected = false; + let new_values = m.targets().iter().filter_map(|target| { + let target = match target { + CompoundMappingTarget::Reaper(t) => t, + _ => return None, + }; + // Immediate value capturing. Makes OSC feedback *much* smoother in + // combination with high-throughput thread. Especially quick pulls + // of many faders at once profit from it because intermediate + // values are captured and immediately sent so user doesn't see + // stuttering faders on their device. + // It's important to capture the current value from the event because + // querying *at this time* from the target itself might result in + // the old value to be returned. This is the case with FX parameter + // changes for examples and especially in case of on/off targets this + // can lead to horribly wrong feedback. Previously we didn't have this + // issue because we always deferred to the next main loop cycle. + let (value_changed, new_value) = f(m, target); + if value_changed { + at_least_one_target_is_affected = true; + } + Some(new_value) + }); + let new_target_value = aggregate_target_values(new_values); + if !at_least_one_target_is_affected { + return; + } + // Feedback + let mapping_feedback_is_effectively_on = m.feedback_is_effectively_on(); + let with_projection_feedback = mapping_feedback_is_effectively_on; + let with_source_feedback = + self.unit_feedback_is_effectively_enabled() && mapping_feedback_is_effectively_on; + let logger = self.source_feedback_logger(m.qualified_id()); + let feedback_value = m.feedback_entry_point( + FeedbackDestinations { + with_projection_feedback, + with_source_feedback, + }, + new_target_value, + self.control_context(m.compartment()), + logger, + ); + self.send_feedback( + mappings_with_virtual_targets, + FeedbackReason::Normal, + feedback_value, + ); + if let Some(v) = new_target_value { + self.notify_target_value_changed(m, v); + } + } + + /// Inform session, e.g. for UI updates, but also for target-based conditional activation. + fn notify_target_value_changed(&self, m: &MainMapping, new_value: AbsoluteValue) { + self.process_target_value_change_for_conditional_activation(m.qualified_id(), new_value); + self.notify_session_about_target_value_change(m, new_value); + } + + fn process_target_value_change_for_conditional_activation( + &self, + mapping_id: QualifiedMappingId, + new_value: AbsoluteValue, + ) { + // Defer evaluation of target-based activation conditions (defer because we don't have + // mutable access at this point). + if self.target_based_conditional_activation_processors[mapping_id.compartment] + .is_lead_mapping(mapping_id.id) + { + let task = FeedbackMainTask::MappingTargetValueChanged { + lead_mapping_id: mapping_id, + target_value: new_value, + }; + self.channels.self_feedback_sender.send_complaining(task); + } + } + + fn notify_session_about_target_value_change(&self, m: &MainMapping, new_value: AbsoluteValue) { + let event = DomainEvent::TargetValueChanged(TargetValueChangedEvent { + compartment: m.compartment(), + mapping_id: m.id(), + targets: m.targets(), + new_value, + }); + self.event_handler.handle_event_ignoring_error(event); + } + + /// Processes (controller) mappings with virtual targets. + /// + /// This also includes controlling the (main) mappings with corresponding virtual sources. + #[must_use] + pub fn process_controller_mappings_with_virtual_targets( + &self, + mappings_with_virtual_targets: &mut OrderedMappingMap, + // Contains mappings with virtual sources + main_mappings: &mut OrderedMappingMap, + evt: ControlEvent, + params: &PluginParams, + ) -> ProcessVirtualResult { + // Control + let mut match_outcome = MatchOutcome::Unmatched; + let mut extended_control_results: Vec<_> = mappings_with_virtual_targets + .values_mut() + // For mappings with virtual targets, the setting "Match even inactive mappings" isn't relevant. + // If such mappings are inactive, they will never be considered as matched. Only associated main mappings + // decide about the match result. + .filter(|m| m.control_is_effectively_on()) + .flat_map(|m| { + let virtual_source_value = match m.control_virtualizing(evt) { + // When matched, the existence of associated main mappings will decide over the match result! + Some(ControlOutcome::Matched(v)) => v, + // When unmatched or consumed, don't process further, but at least update match result. + // I was wondering if it's inconsequential to allow virtual mappings without associated + // main mappings be able to *consume* an event. I mean, even a *matching* virtual mapping + // with associated main mappings might not get that power! But I came to the conclusion that it's + // consequential. "Consume" means that e.g. an NRPN source eats an event which contains only + // a partial message, to check if later events lead to the *complete* message. In the real-time + // processor, we have the same behavior. It's not relevant whether the mapping actually leads to + // some action or not. + unmatched_or_consumed => { + match_outcome.upgrade_from(unmatched_or_consumed.into()); + return vec![]; + } + }; + self.event_handler + .notify_mapping_matched(CompartmentKind::Controller, m.id()); + let virtual_result = self.process_main_mappings_with_virtual_sources( + main_mappings, + evt.with_payload(virtual_source_value), + ControlOptions { + // We inherit "Send feedback after control" if it's + // enabled for the virtual mapping. That's the easy way to do it. + // Downside: If multiple real control elements are mapped to one + // virtual control element, + // "feedback after control" will be sent to all of + // those, which is technically not + // necessary. It would be enough to just send it + // to the one that was touched. However, it also doesn't really + // hurt. + enforce_send_feedback_after_control: m.options().feedback_send_behavior + == FeedbackSendBehavior::SendFeedbackAfterControl, + mode_control_options: m.mode_control_options(), + // Not yet important at this point because one virtual target can't + // affect a subsequent one. + enforce_target_refresh: false, + // If we are here, we know control originated in main thread, not in real-time + coming_from_real_time: false, + }, + params, + ); + match_outcome.upgrade_from(virtual_result.match_outcome); + if self.settings.virtual_input_logging_enabled { + log_virtual_control_input( + self.unit_id, + format_control_input_with_match_result( + virtual_source_value, + virtual_result.match_outcome, + ), + ); + } + virtual_result.control_results + }) + .collect(); + // Feedback + self.send_feedback( + mappings_with_virtual_targets, + FeedbackReason::Normal, + extended_control_results + .iter_mut() + .filter_map(|r| r.control_result.feedback_value.take()), + ); + ProcessVirtualResult { + control_results: extended_control_results, + match_outcome, + } + } + + /// Sends both direct and virtual-source feedback. + pub fn send_feedback( + &self, + mappings_with_virtual_targets: &OrderedMappingMap, + feedback_reason: FeedbackReason, + feedback_values: impl IntoIterator, + ) { + let mut global_source_state = Backbone::source_state().borrow_mut(); + let mut feedback_collector = + FeedbackCollector::new(&mut global_source_state, self.settings.feedback_output); + for feedback_value in feedback_values.into_iter() { + match feedback_value.value { + SpecificCompoundFeedbackValue::Virtual { + destinations, + value, + } => { + // At this point we still include controller mappings for which feedback + // is explicitly not enabled (not supported by controller) in order to + // support at least projection feedback (#414)! + if self.settings.virtual_output_logging_enabled { + log_virtual_feedback_output(self.unit_id, &value); + } + // Iterate over (controller) mappings with virtual targets. + for m in mappings_with_virtual_targets + .values() + .filter(|m| m.feedback_is_effectively_on()) + { + // Should always be true. + if let Some(t) = m.virtual_target() { + if t.control_element() == value.control_element() { + // Virtual source matched virtual target. The following method + // will always produce real target values (because controller + // mappings can't have virtual sources). + let compound_feedback_value = m.feedback_given_target_value( + // This clone is unavoidable because we are producing + // real feedback values and these will be sent to another + // thread, so they must be self-contained. + Some(Cow::Borrowed(value.feedback_value())), + FeedbackDestinations { + with_source_feedback: destinations.with_source_feedback + && m.feedback_is_enabled(), + ..destinations + }, + self.source_context(m.compartment()), + self.source_feedback_logger(m.qualified_id()), + feedback_value.cause, + ); + if let Some(SpecificCompoundFeedbackValue::Real( + preliminary_feedback_value, + )) = compound_feedback_value + { + // Successful virtual-to-real feedback + if let Some(final_feedback_value) = + feedback_collector.process(preliminary_feedback_value) + { + if let Some(t) = &m.mode().settings().control_transformation + { + if let Some(numeric_fb_value) = + value.feedback_value().to_numeric() + { + t.set_last_feedback_value( + numeric_fb_value.value.to_unit_value().get(), + ); + } + } + self.send_direct_feedback( + feedback_reason, + feedback_value.cause, + final_feedback_value, + ); + } + } + } + } + } + } + SpecificCompoundFeedbackValue::Real(preliminary_feedback_value) => { + if let Some(final_feedback_value) = + feedback_collector.process(preliminary_feedback_value) + { + self.send_direct_feedback( + feedback_reason, + feedback_value.cause, + final_feedback_value, + ); + } + } + } + } + // Send special collected feedback + for final_feedback_value in feedback_collector.generate_final_feedback_values() { + self.send_direct_feedback(feedback_reason, FeedbackCause::Normal, final_feedback_value); + } + } + + pub fn send_direct_source_feedback( + &self, + output: Option, + reason: FeedbackReason, + cause: FeedbackCause, + source_feedback_value: FinalSourceFeedbackValue, + ) { + if reason.is_reset_because_of_source_release() + && !self.settings.reset_feedback_when_releasing_source + { + return; + } + // Record feedback checksum and block duplicate feedback in some cases. + if let Some(address) = source_feedback_value.extract_address() { + // Extracting a feedback address is not super cheap for OSC and MIDI Raw because it has to + // clone the address string. On the other hand, address strings are not large, so what. + let checksum = FeedbackChecksum::from_value(&source_feedback_value); + let previous_checksum = self + .last_feedback_checksum_by_address + .borrow_mut() + .insert(address, checksum); + // Block duplicates in certain cases. If it's feedback-after-control, we always need to + // send because that's sort of the point of this feature. If it's a source-takeover, we + // also need to send because we don't know what the other instance sent before that + // (https://github.com/helgoboss/helgobox/issues/727). + if cause != FeedbackCause::FeedbackAfterControl + && reason != FeedbackReason::TakeOverSource + && Some(checksum) == previous_checksum + { + trace!( + "Block feedback because duplicate (reason: {:?}): {:?}", + reason, + source_feedback_value + ); + return; + } + } + trace!( + "Schedule sending feedback because {:?}: {:?}", + reason, + source_feedback_value + ); + if let Some(test_sender) = self.channels.integration_test_feedback_sender.as_ref() { + // Integration test + // Test receiver could already be gone (if the test didn't wait long enough). + test_sender.send_if_space(source_feedback_value); + } else { + // Production + match (source_feedback_value, output) { + (FinalSourceFeedbackValue::Midi(v), Some(FeedbackOutput::Midi(midi_output))) => { + match midi_output { + MidiDestination::FxOutput => { + if self.settings.real_output_logging_enabled { + log_real_feedback_output( + self.unit_id, + reason, + format_midi_source_value(&v), + ); + } + self.channels + .feedback_real_time_task_sender + .send_complaining(FeedbackRealTimeTask::FxOutputFeedback(v)); + } + MidiDestination::Device(dev_id) => { + // We send to the audio hook in this case (the default case) because there's + // only one audio hook (not one per instance as with real-time processors), + // so it can guarantee us a globally deterministic order. This is necessary + // to achieve "eventual feedback consistency" by using instance + // orchestration techniques in the main thread. If + // we don't do that, we can prepare the most perfect + // feedback ordering in the backbone control surface (main + // thread, in order to support multiple instances with the same device) ... + // it won't be useful at all if the real-time processors send the feedback + // in the order of instance instantiation. + if self.settings.real_output_logging_enabled { + log_real_feedback_output( + self.unit_id, + reason, + format_midi_source_value(&v), + ); + } + self.channels + .feedback_audio_hook_task_sender + .send_complaining(FeedbackAudioHookTask::MidiDeviceFeedback( + dev_id, v, + )); + } + } + } + (FinalSourceFeedbackValue::Osc(msg), Some(FeedbackOutput::Osc(dev_id))) => { + if self.settings.real_output_logging_enabled { + log_real_feedback_output(self.unit_id, reason, format_osc_message(&msg)); + } + self.channels + .osc_feedback_task_sender + .send_complaining(OscFeedbackTask::new(dev_id, msg)); + } + (FinalSourceFeedbackValue::Reaper(ReaperSourceFeedbackValue::Speech(v)), _) => { + let _ = say(v); + } + (FinalSourceFeedbackValue::StreamDeck(v), _) => { + if let Some(dev_id) = self.settings.streamdeck_device_id { + let _ = Backbone::get().send_stream_deck_feedback(dev_id, v); + } + } + _ => {} + } + } + } + + fn send_direct_feedback( + &self, + reason: FeedbackReason, + cause: FeedbackCause, + value: FinalRealFeedbackValue, + ) { + self.send_direct_device_feedback(reason, cause, value.source); + self.send_direct_projection_feedback(value.projection); + } + + fn send_direct_projection_feedback(&self, feedback_value: Option) { + if let Some(projection_feedback_value) = feedback_value { + self.event_handler + .handle_event_ignoring_error(DomainEvent::ProjectionFeedback( + projection_feedback_value, + )); + } + } + + fn send_direct_device_feedback( + &self, + reason: FeedbackReason, + cause: FeedbackCause, + value: Option, + ) { + if !reason.is_always_allowed() && !self.unit_feedback_is_effectively_enabled() { + return; + } + if let Some(source_feedback_value) = value { + // At this point we can be sure that this mapping can't have a + // virtual source. + if reason.is_source_release() { + // Possible interference with other instances. Don't switch off yet! + // Give other instances the chance to take over. + let event = UnitOrchestrationEvent::SourceReleased(SourceReleasedEvent { + unit_id: self.unit_id.to_owned(), + feedback_output: self.settings.feedback_output, + feedback_value: source_feedback_value, + }); + self.channels + .unit_orchestration_event_sender + .send_complaining(event); + } else { + // Send feedback right now. + self.send_direct_source_feedback( + self.settings.feedback_output, + reason, + cause, + source_feedback_value, + ); + } + } + } + + pub fn unit_control_is_effectively_enabled(&self) -> bool { + self.control_is_globally_enabled + && Backbone::get().control_is_allowed(&self.unit_id, self.settings.control_input) + } + + pub fn unit_feedback_is_effectively_enabled(&self) -> bool { + if !self.feedback_is_globally_enabled { + return false; + } + if let Some(fo) = self.settings.feedback_output { + // Feedback output is set + Backbone::get().feedback_is_allowed(&self.unit_id, fo) + } else { + // Pointless but allowed + true + } + } + + /// Processes main mappings with virtual sources. + /// + /// Returns a list of all invocation results. Empty if no mapping matched at all. + fn process_main_mappings_with_virtual_sources( + &self, + main_mappings: &mut OrderedMappingMap, + evt: ControlEvent, + options: ControlOptions, + params: &PluginParams, + ) -> ProcessVirtualResult { + // Controller mappings can't have virtual sources, so for now we only need to check + // main mappings. + let mut enforce_target_refresh = false; + let match_inactive = self.settings.match_even_inactive_mappings; + let mut match_outcome = MatchOutcome::Unmatched; + let control_results = main_mappings + .values_mut() + // Consider only control-enabled main mappings + .filter(|m| m.core.options.control_is_enabled) + .filter_map(|m| { + let mapping_is_active = m.is_active(); + if !mapping_is_active && !match_inactive { + return None; + } + let CompoundMappingSource::Virtual(s) = &m.source() else { + return None; + }; + let control_value = s.control(&evt.payload())?; + // We found an associated main mapping, so it's not just consumed, it's matched. + match_outcome = MatchOutcome::Matched; + if !mapping_is_active { + return None; + } + let control_event = evt.with_payload(control_value); + let options = ControlOptions { + enforce_target_refresh, + ..options + }; + let control_result = control_mapping_stage_one_and_two( + self, + params, + m, + control_event, + options, + ManualFeedbackProcessing::Off, + ); + enforce_target_refresh = true; + let extended_control_result = ExtendedMappingControlResult { + control_result, + compartment: m.compartment(), + group_interaction_input: GroupInteractionInput { + mapping_id: m.id(), + group_interaction: m.group_interaction(), + control_event, + }, + }; + Some(extended_control_result) + }) + .collect(); + ProcessVirtualResult { + control_results, + match_outcome, + } + } +} + +/// Includes virtual mappings if the controller mapping compartment is queried. +fn all_mappings_in_compartment_mut<'a>( + mappings: &'a mut EnumMap>, + mappings_with_virtual_targets: &'a mut OrderedMappingMap, + compartment: CompartmentKind, +) -> impl Iterator { + mappings[compartment].values_mut().chain( + mappings_with_virtual_targets + .values_mut() + // Include virtual target mappings if we are talking about controller compartment. + .filter(move |_| compartment == CompartmentKind::Controller), + ) +} + +fn get_normal_or_virtual_target_mapping_mut<'a>( + mappings: &'a mut EnumMap>, + mappings_with_virtual_targets: &'a mut OrderedMappingMap, + compartment: CompartmentKind, + id: MappingId, +) -> Option<&'a mut MainMapping> { + mappings[compartment] + .get_mut(&id) + .or(if compartment == CompartmentKind::Controller { + mappings_with_virtual_targets.get_mut(&id) + } else { + None + }) +} + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] +pub struct UnitId(u32); + +impl fmt::Display for UnitId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl UnitId { + pub fn next() -> Self { + static COUNTER: AtomicU32 = AtomicU32::new(0); + Self(COUNTER.fetch_add(1, Ordering::SeqCst)) + } +} + +impl From for UnitId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: UnitId) -> Self { + value.0 + } +} + +impl From for UnitId { + fn from(value: ControlUnitId) -> Self { + UnitId(value.get()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Display)] +pub enum MatchOutcome { + /// Message didn't match at all, was not even consumed. + #[display(fmt = "unmatched")] + Unmatched = 0, + /// Message was not processed but at least consumed (= "eaten"). + /// + /// That means it should not be forwarded if "Let matched events through" is `false`. + #[display(fmt = "consumed")] + Consumed = 1, + /// Message was processed. + /// + /// That means it should not be forwarded if "Let matched events through" is `false`. + #[display(fmt = "matched")] + Matched = 2, +} + +impl MatchOutcome { + pub fn matched_or_consumed(&self) -> bool { + matches!(self, Self::Matched | Self::Consumed) + } + + pub fn matched(&self) -> bool { + matches!(self, Self::Matched) + } + + pub fn merge_with(self, rhs: MatchOutcome) -> MatchOutcome { + [self, rhs].into_iter().max().unwrap() + } + + pub fn upgrade_from(&mut self, rhs: MatchOutcome) { + if rhs > *self { + *self = rhs; + } + } +} + +impl From>> for MatchOutcome { + fn from(v: Option>) -> Self { + match v { + None => Self::Unmatched, + Some(ControlOutcome::Consumed) => MatchOutcome::Consumed, + Some(ControlOutcome::Matched(_)) => MatchOutcome::Matched, + } + } +} + +#[must_use] +fn control_mapping_stage_one_and_two( + basics: &Basics, + params: &PluginParams, + m: &mut MainMapping, + control_event: ControlEvent, + options: ControlOptions, + feedback_handling: ManualFeedbackProcessing, +) -> MappingControlResult { + let mut control_result = control_mapping_stage_one(basics, params, m, control_event, options); + control_mapping_stage_two(basics, &mut control_result, m, feedback_handling); + control_result +} + +/// Executes stage one of a typical mapping control invocation. +/// +/// Takes care of: +/// +/// 1. Notifying that mapping matched +/// 2. Controlling with given control value (probably produced by source) starting from mode. +#[must_use] +fn control_mapping_stage_one( + basics: &Basics, + params: &PluginParams, + m: &mut MainMapping, + control_event: ControlEvent, + options: ControlOptions, +) -> MappingControlResult { + basics + .event_handler + .notify_mapping_matched(m.compartment(), m.id()); + let result = m.control_from_mode( + control_event, + options, + basics.control_context(m.compartment()), + ExtendedProcessorContext::new( + &basics.context, + params, + basics.control_context(m.compartment()), + ), + m.last_non_performance_target_value(), + basics.target_control_logger(ControlLogContext::Normal, m.qualified_id()), + ); + if result.at_least_one_target_caused_effect && result.celebrate_success { + basics.celebrate_success(); + } + result +} + +/// Executes stage two of a typical mapping control invocation. +/// +/// Takes care of: +/// +/// 1. Sending manual feedback due to target or "Send feedback after control". +fn control_mapping_stage_two( + basics: &Basics, + control_result: &mut MappingControlResult, + m: &mut MainMapping, + feedback_handling: ManualFeedbackProcessing, +) { + if let Some(new_value) = control_result.new_target_value { + basics.notify_target_value_changed(m, new_value); + } + if let ManualFeedbackProcessing::On { + mappings_with_virtual_targets, + } = feedback_handling + { + basics.send_feedback( + mappings_with_virtual_targets, + FeedbackReason::Normal, + control_result.feedback_value.take(), + ); + } +} + +/// Executes stage three of a typical mapping control invocation. +/// +/// Takes care of: +/// +/// 1. Executing a possible hit instruction (and in a possible second pass all resulting hit +/// instructions). A second pass is not just theory, it makes a lot of sense in practice, e.g. +/// when we control "Enable/disable mappings" via "Browse group mappings". However, we should +/// stop there in order to prevent infinite loops. If we really need more in future, we can add +/// a third pass. +/// 2. Processing group interaction (if enabled). +fn control_mapping_stage_three( + basics: &Basics, + collections: &mut Collections, + compartment: CompartmentKind, + control_result: MappingControlResult, + group_interaction_processing: GroupInteractionProcessing, +) { + if let Some(hi) = control_result.hit_instruction { + let control_context = basics.control_context(compartment); + let processor_context = ExtendedProcessorContext::new( + &basics.context, + &collections.parameters, + control_context, + ); + let response = hi.execute(HitInstructionContext { + mappings: &mut collections.mappings[compartment], + control_context, + domain_event_handler: &basics.event_handler, + processor_context, + basic_settings: &basics.settings, + }); + if let HitInstructionResponse::CausedEffect(pass_2_control_results) = response { + // Second pass, without group interaction this time! + for pass_2_control_result in pass_2_control_results { + if let Some(pass_2_hi) = pass_2_control_result.hit_instruction { + pass_2_hi.execute(HitInstructionContext { + mappings: &mut collections.mappings[compartment], + control_context, + domain_event_handler: &basics.event_handler, + processor_context, + basic_settings: &basics.settings, + }); + } + } + if control_result.celebrate_success { + basics.celebrate_success(); + } + } + } + if let GroupInteractionProcessing::On(input) = group_interaction_processing { + if input.group_interaction != GroupInteraction::None { + basics.process_group_interaction( + collections, + compartment, + input.mapping_id, + input.control_event, + control_result.at_least_one_target_was_reached, + ); + } + } +} + +enum ManualFeedbackProcessing<'a> { + Off, + On { + mappings_with_virtual_targets: &'a OrderedMappingMap, + }, +} + +enum GroupInteractionProcessing { + Off, + On(GroupInteractionInput), +} + +struct ExtendedMappingControlResult { + control_result: MappingControlResult, + compartment: CompartmentKind, + group_interaction_input: GroupInteractionInput, +} + +struct GroupInteractionInput { + mapping_id: MappingId, + group_interaction: GroupInteraction, + control_event: ControlEvent, +} + +struct Fb(FeedbackReason, Option); + +impl Fb { + fn none() -> Self { + Fb(FeedbackReason::Normal, None) + } + + fn unused(value: Option) -> Self { + Fb(FeedbackReason::ClearUnusedSource, value) + } + + fn normal(value: Option) -> Self { + Fb(FeedbackReason::Normal, value) + } +} + +struct ControlFeedbackSettings { + control_input: ControlInput, + feedback_output: Option, + control_is_globally_enabled: bool, + feedback_is_globally_enabled: bool, +} + +fn determine_control_globally_enabled( + context: &ProcessorContext, + settings: &BasicSettings, + project_options: ProjectOptions, +) -> bool { + settings.unit_enabled + && context.containing_fx().is_enabled() + && passes_background_project_check( + context, + settings.stay_active_when_project_in_background, + project_options, + ) +} + +fn determine_feedback_globally_enabled( + context: &ProcessorContext, + settings: &BasicSettings, + project_options: ProjectOptions, +) -> bool { + settings.unit_enabled + && (settings.feedback_output.is_some() || settings.streamdeck_device_id.is_some()) + && context.containing_fx().is_enabled() + && track_arm_conditions_are_met(context, settings) + && passes_background_project_check( + context, + settings.stay_active_when_project_in_background, + project_options, + ) +} + +pub fn passes_background_project_check( + context: &ProcessorContext, + stay_active_when_project_in_background: StayActiveWhenProjectInBackground, + opts: ProjectOptions, +) -> bool { + use StayActiveWhenProjectInBackground::*; + match stay_active_when_project_in_background { + Never => is_current_project(context), + OnlyIfBackgroundProjectIsRunning => { + match ( + opts.run_background_projects, + opts.run_stopped_background_projects, + ) { + (false, _) => is_current_project(context), + (true, false) => is_current_project(context) || is_playing(context), + (true, true) => true, + } + } + Always => true, + } +} + +fn is_current_project(context: &ProcessorContext) -> bool { + if let Some(project) = context.project() { + project == Reaper::get().current_project() + } else { + // When ReaLearn is on the monitoring FX chain, we always control the current project. + true + } +} + +fn is_playing(context: &ProcessorContext) -> bool { + context.project_or_current_project().is_playing() +} + +fn track_arm_conditions_are_met(context: &ProcessorContext, settings: &BasicSettings) -> bool { + if !context.containing_fx().is_input_fx() && !settings.send_feedback_only_if_armed { + return true; + } + match context.track() { + None => true, + Some(t) => t.is_available() && t.is_armed(false), + } +} + +fn find_active_main_mapping_connected_to_virtual_control_element( + main_mappings: &OrderedMappingMap, + control_element: VirtualControlElement, +) -> Option<&MainMapping> { + main_mappings.values().find(|m| { + m.virtual_source_control_element() == Some(control_element) + && m.feedback_is_effectively_on() + }) +} + +fn all_mappings_without_virtual_targets( + mappings: &EnumMap>, +) -> impl Iterator { + CompartmentKind::enum_iter().flat_map(move |compartment| mappings[compartment].values()) +} + +#[derive(Debug, Default)] +struct TargetBasedConditionalActivationProcessor { + mapping_relations: NonCryptoHashSet, +} + +#[derive(Eq, PartialEq, Hash, Debug)] +struct MappingRelation { + lead_mapping: MappingId, + follow_mapping: MappingId, +} + +impl TargetBasedConditionalActivationProcessor { + pub fn get_follow_mappings( + &self, + lead_mapping: MappingId, + ) -> impl Iterator + '_ { + self.mapping_relations + .iter() + .filter(move |r| r.lead_mapping == lead_mapping) + .map(|r| r.follow_mapping) + } + + pub fn clear(&mut self) { + self.mapping_relations.clear(); + } + + pub fn notify_usage( + &mut self, + follow_mapping: MappingId, + lead_mappings: impl Iterator, + ) { + // At first remove all occurrences of follow mapping + self.mapping_relations + .retain(|relation| relation.follow_mapping != follow_mapping); + // Then add + self.notify_usage_add_only(follow_mapping, lead_mappings); + } + + pub fn notify_usage_add_only( + &mut self, + follow_mapping: MappingId, + lead_mappings: impl Iterator, + ) { + for lead_mapping in lead_mappings { + let relation = MappingRelation { + lead_mapping, + follow_mapping, + }; + self.mapping_relations.insert(relation); + } + } + + pub fn lead_mappings(&self) -> impl Iterator { + let lead_mapping_id_set: NonCryptoHashSet<_> = self + .mapping_relations + .iter() + .map(|rel| rel.lead_mapping) + .collect(); + lead_mapping_id_set.into_iter() + } + + pub fn is_lead_mapping(&self, mapping_id: MappingId) -> bool { + self.mapping_relations + .iter() + .any(|r| r.lead_mapping == mapping_id) + } +} + +fn determine_final_target_value_for_conditional_activation( + target_is_active: bool, + target_value: Option, +) -> Result, &'static str> { + if target_is_active { + if let Some(v) = target_value { + // Normal case. Target is active. We provide the new value. + Ok(Some(v)) + } else { + // Target is active (implies it successfully resolved) but it can't return a + // current value. We consider this as something temporary and don't do anything. + Err("target value not available") + } + } else { + // Target is inactive. We let the condition distinguish this case by passing `None`. + Ok(None) + } +} + +pub struct KeyProcessingResult { + pub match_outcome: MatchOutcome, + /// Whether this message should be filtered out from the keyboard processing chain. + pub filter_out_event: bool, +} + +type UnusedSources = NonCryptoHashMap; + +struct ProcessVirtualResult { + control_results: Vec, + match_outcome: MatchOutcome, +} diff --git a/plugin-reaper-realearn/main/src/domain/mapping.rs b/plugin-reaper-realearn/main/src/domain/mapping.rs new file mode 100644 index 0000000..cadec4c --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/mapping.rs @@ -0,0 +1,2925 @@ +use crate::domain::{ + prop_feedback_resolution, prop_is_affected_by, ActivationChange, ActivationCondition, + BoxedHitInstruction, CompartmentParamIndex, CompoundChangeEvent, ControlContext, ControlEvent, + ControlEventTimestamp, ControlOptions, ExtendedProcessorContext, FeedbackResolution, GroupId, + HitResponse, KeyMessage, KeySource, MappingActivationEffect, MappingControlContext, + MappingData, MappingInfo, MappingPropProvider, MessageCaptureEvent, MidiScanResult, MidiSource, + Mode, OscDeviceId, OscScanResult, PersistentMappingProcessingState, PluginParamIndex, + PluginParams, RealTimeMappingUpdate, RealTimeReaperTarget, RealTimeTargetUpdate, + RealearnParameterChangePayload, RealearnParameterSource, RealearnSourceContext, RealearnTarget, + ReaperMessage, ReaperSource, ReaperSourceFeedbackValue, ReaperTarget, ReaperTargetType, + StreamDeckDeviceId, StreamDeckMessage, StreamDeckScanResult, StreamDeckSource, + StreamDeckSourceAddress, StreamDeckSourceFeedbackValue, Tag, TargetCharacter, TrackExclusivity, + UnresolvedReaperTarget, VirtualControlElement, VirtualFeedbackValue, VirtualSource, + VirtualSourceAddress, VirtualSourceValue, VirtualTarget, COMPARTMENT_PARAMETER_COUNT, +}; +use derive_more::Display; +use enum_map::Enum; +use helgoboss_learn::{ + format_percentage_without_unit, parse_percentage_without_unit, AbsoluteValue, ControlResult, + ControlType, ControlValue, FeedbackValue, GroupInteraction, MidiSourceAddress, MidiSourceValue, + ModeControlOptions, ModeControlResult, ModeFeedbackOptions, NumericFeedbackValue, NumericValue, + OscSource, OscSourceAddress, PreliminaryMidiSourceFeedbackValue, PropValue, RawMidiEvent, + SourceCharacter, Target, UnitValue, ValueFormatter, ValueParser, +}; +use helgoboss_midi::{Channel, RawShortMessage, ShortMessage}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::borrow::Cow; +use std::cell::Cell; + +use crate::domain::unresolved_reaper_target::UnresolvedReaperTargetDef; +use base::hash_util::{NonCryptoHashSet, NonCryptoIndexMap, NonCryptoIndexSet}; +use playtime_api::persistence::{ColumnAddress, RowAddress, SlotAddress}; +use reaper_high::{Fx, Project, Track, TrackRoute}; +use reaper_medium::MidiInputDeviceId; +use rosc::OscMessage; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::fmt; +use std::fmt::{Display, Formatter, Write}; +use std::ops::RangeInclusive; +use std::rc::Rc; +use std::time::{Duration, Instant}; +use strum::{EnumIter, IntoEnumIterator}; +use tracing::debug; +use uuid::Uuid; + +#[derive(Copy, Clone, Debug)] +pub struct ProcessorMappingOptions { + /// In the main processor mapping this might be overridden by the unresolved target's + /// is_always_active() result. The real-time processor always gets the effective result of the + /// main processor mapping. + pub target_is_active: bool, + pub persistent_processing_state: PersistentMappingProcessingState, + pub control_is_enabled: bool, + pub feedback_is_enabled: bool, + pub feedback_send_behavior: FeedbackSendBehavior, + pub beep_on_success: bool, +} + +impl ProcessorMappingOptions { + pub fn control_is_effectively_enabled(&self) -> bool { + self.persistent_processing_state.is_enabled && self.control_is_enabled + } + + pub fn feedback_is_effectively_enabled(&self) -> bool { + self.persistent_processing_state.is_enabled && self.feedback_is_enabled + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Enum, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum FeedbackSendBehavior { + #[display(fmt = "Normal")] + Normal, + #[display(fmt = "Send feedback after control")] + SendFeedbackAfterControl, + #[display(fmt = "Prevent echo feedback")] + PreventEchoFeedback, +} + +impl Default for FeedbackSendBehavior { + fn default() -> Self { + Self::Normal + } +} + +/// Internal technical mapping identifier, not persistent. +/// +/// Goals: Quick lookup, guaranteed uniqueness, cheap copy +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Serialize, Deserialize, +)] +#[serde(transparent)] +pub struct MappingId(Uuid); + +impl MappingId { + pub fn random() -> MappingId { + Self(Uuid::new_v4()) + } +} + +impl Default for MappingId { + fn default() -> Self { + Self::random() + } +} + +/// A potentially user-defined mapping identifier, persistent +/// +/// Goals: For external references (e.g. from API or in projection) +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Serialize, Deserialize)] +#[serde(transparent)] +pub struct MappingKey(String); + +impl MappingKey { + pub fn random() -> Self { + Self(nanoid::nanoid!()) + } +} + +impl AsRef for MappingKey { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl From for MappingKey { + fn from(v: String) -> Self { + Self(v) + } +} + +impl From for String { + fn from(v: MappingKey) -> Self { + v.0 + } +} + +const MAX_ECHO_FEEDBACK_DELAY: Duration = Duration::from_millis(100); + +#[derive(Debug)] +pub enum LifecycleMidiMessage { + #[allow(unused)] + Short(RawShortMessage), + Raw(Box), +} + +#[derive(Debug, Default)] +pub struct LifecycleMidiData { + pub activation_midi_messages: Vec, + pub deactivation_midi_messages: Vec, +} + +#[derive(Debug, Default)] +pub struct MappingExtension { + /// If it's None, it means it's splintered already. + lifecycle_midi_data: Option, +} + +impl MappingExtension { + pub fn new(lifecycle_midi_data: LifecycleMidiData) -> Self { + Self { + lifecycle_midi_data: Some(lifecycle_midi_data), + } + } +} + +// TODO-low The name is confusing. It should be MainThreadMapping or something because +// this can also be a controller mapping (a mapping in the controller compartment). +#[derive(Debug)] +pub struct MainMapping { + pub core: MappingCore, + // We need to clone this when producing feedback, pretty often ... so wrapping it in a Rc + // saves us from doing too much copying and allocation that potentially slows down things + // (albeit only marginally). + key: Rc, + /// This is set only temporarily during mapping sync. + name: Option, + tags: Vec, + /// Is `Some` if the user-provided target data is complete. + unresolved_target: Option, + /// Is non-empty if the target resolved successfully. + targets: Vec, + activation_condition_1: ActivationCondition, + activation_condition_2: ActivationCondition, + activation_state: ActivationState, + extension: MappingExtension, + initial_target_value: Option, + /// Called "y_last" in the control transformation formula. + last_non_performance_target_value: Cell>, +} + +#[derive(Default, Debug)] +struct ActivationState { + is_active_1: bool, + is_active_2: bool, +} + +impl ActivationState { + pub fn is_active(&self) -> bool { + self.is_active_1 && self.is_active_2 + } +} + +impl MainMapping { + #[allow(clippy::too_many_arguments)] + pub fn new( + compartment: CompartmentKind, + id: MappingId, + key: &MappingKey, + group_id: GroupId, + name: String, + tags: Vec, + source: CompoundMappingSource, + mode: Mode, + group_interaction: GroupInteraction, + unresolved_target: Option, + activation_condition_1: ActivationCondition, + activation_condition_2: ActivationCondition, + options: ProcessorMappingOptions, + extension: MappingExtension, + ) -> MainMapping { + MainMapping { + core: MappingCore { + compartment, + id, + group_id, + source, + mode, + group_interaction, + options, + time_of_last_control: None, + invocation_count: 0, + }, + key: { + let key_str: &str = key.as_ref(); + key_str.into() + }, + name: Some(name), + tags, + unresolved_target, + targets: vec![], + activation_condition_1, + activation_condition_2, + activation_state: Default::default(), + extension, + initial_target_value: None, + last_non_performance_target_value: Cell::new(None), + } + } + + fn beep_on_success(&self) -> bool { + self.core.options.beep_on_success + } + + /// This is for: + /// + /// 1. Determining whether to send feedback and optionally, what feedback value to send. + /// 2. Updating y_last (for performance mappings) + /// + /// This method is not required to already return the new target value. If not, the consumer + /// must query the target for the current value. + pub fn process_change_event( + &self, + target: &ReaperTarget, + evt: CompoundChangeEvent, + context: ControlContext, + ) -> (bool, Option) { + // Textual feedback relates to whatever properties are mentioned in the text expression. + // But even numeric feedback can use properties - as part of the feedback style + // (color etc.). That means we need to check for each of these mentioned properties if + // they might be affected by the incoming event. + let props_are_affected = self + .core + .mode + .feedback_props_in_use() + .iter() + .any(|p| prop_is_affected_by(p, evt, self, target, context)); + let (is_affected, new_value, handle_performance_mapping) = + if self.core.mode.wants_advanced_feedback() { + // For textual feedback only those props matter. Updating y_last is not relevant because + // textual feedback is feedback-only. + (props_are_affected, None, false) + } else { + // Numeric feedback implicitly always relates to the main target value, so we always + // ask the target directly. + let (main_target_value_is_affected, value) = + target.process_change_event(evt, context); + ( + main_target_value_is_affected || props_are_affected, + value, + true, + ) + }; + if is_affected { + let new_value = new_value.or_else(|| target.current_value(context)); + if handle_performance_mapping { + self.update_last_non_performance_target_value_if_appropriate(new_value); + } + (true, new_value) + } else { + (false, None) + } + } + + pub fn update_last_non_performance_target_value_if_appropriate( + &self, + value: Option, + ) { + if let Some(v) = value { + if self.control_is_enabled() && !self.is_echo() { + self.last_non_performance_target_value.set(Some(v)); + } + } + } + + pub fn is_echo(&self) -> bool { + self.core.is_echo() + } + + pub fn update_last_non_performance_target_value(&self, value: AbsoluteValue) { + self.last_non_performance_target_value.set(Some(value)); + } + + pub fn last_non_performance_target_value(&self) -> Option { + self.last_non_performance_target_value.get() + } + + pub fn take_mapping_info(&mut self) -> MappingInfo { + MappingInfo { + name: self.name.take().unwrap_or_default(), + } + } + + pub fn initial_target_value(&self) -> Option { + self.initial_target_value + } + + pub fn update_persistent_processing_state(&mut self, state: PersistentMappingProcessingState) { + self.core.update_persistent_processing_state(state); + } + + pub fn tags(&self) -> &[Tag] { + &self.tags + } + + pub fn has_any_tag(&self, tags: &NonCryptoHashSet) -> bool { + self.tags.iter().any(|t| tags.contains(t)) + } + + pub fn compartment(&self) -> CompartmentKind { + self.core.compartment + } + + pub fn id(&self) -> MappingId { + self.core.id + } + + pub fn qualified_id(&self) -> QualifiedMappingId { + QualifiedMappingId::new(self.core.compartment, self.core.id) + } + + pub fn options(&self) -> &ProcessorMappingOptions { + &self.core.options + } + + pub fn mode_control_options(&self) -> ModeControlOptions { + ModeControlOptions { + enforce_rotate: self.core.mode.settings().rotate, + } + } + + pub fn splinter_real_time_mapping(&mut self) -> RealTimeMapping { + RealTimeMapping { + core: MappingCore { + options: ProcessorMappingOptions { + target_is_active: self.target_is_effectively_active(), + ..self.core.options + }, + ..self.core.splinter_real_time_core() + }, + is_active: self.is_active_in_terms_of_activation_state(), + target_category: self.unresolved_target.as_ref().map(|t| match t { + UnresolvedCompoundMappingTarget::Reaper(_) => UnresolvedTargetCategory::Reaper, + UnresolvedCompoundMappingTarget::Virtual(_) => UnresolvedTargetCategory::Virtual, + }), + target_is_resolved: !self.targets.is_empty(), + resolved_target: self.splinter_first_real_time_target(), + lifecycle_midi_data: self + .extension + .lifecycle_midi_data + .take() + .unwrap_or_default(), + } + } + + pub fn splinter_first_real_time_target(&self) -> Option { + self.targets + .first() + .and_then(|t| t.splinter_real_time_target()) + } + + pub fn has_virtual_target(&self) -> bool { + self.virtual_target().is_some() + } + + pub fn virtual_target(&self) -> Option<&VirtualTarget> { + if let Some(UnresolvedCompoundMappingTarget::Virtual(t)) = self.unresolved_target.as_ref() { + Some(t) + } else { + None + } + } + + pub fn has_reaper_target(&self) -> bool { + matches!( + self.unresolved_target, + Some(UnresolvedCompoundMappingTarget::Reaper(_)) + ) + } + + pub fn has_resolved_successfully(&self) -> bool { + !self.targets.is_empty() + } + + pub fn check_activation_effect_of_target_value_update( + &self, + lead_mapping_id: MappingId, + target_value: Option, + ) -> Option { + let effect_1 = self + .activation_condition_1 + .process_target_value_update(lead_mapping_id, target_value); + let effect_2 = self + .activation_condition_2 + .process_target_value_update(lead_mapping_id, target_value); + MappingActivationEffect::new(self.id(), effect_1, effect_2) + } + + /// Returns `Some` if this affects the mapping's activation state in any way. + pub fn check_activation_effect_of_param_update( + &self, + params: &PluginParams, + plugin_param_index: PluginParamIndex, + previous_value: f32, + ) -> Option { + let compartment_params = params.compartment_params(self.core.compartment); + let compartment_param_index = self + .core + .compartment + .to_compartment_param_index(plugin_param_index); + let effect_1 = self.activation_condition_1.process_param_update( + compartment_params, + compartment_param_index, + previous_value, + ); + let effect_2 = self.activation_condition_2.process_param_update( + compartment_params, + compartment_param_index, + previous_value, + ); + MappingActivationEffect::new(self.id(), effect_1, effect_2) + } + + /// Returns if this target is dynamic. + pub fn target_can_be_affected_by_parameters(&self) -> bool { + match &self.unresolved_target { + Some(UnresolvedCompoundMappingTarget::Reaper(t)) => t.can_be_affected_by_parameters(), + _ => false, + } + } + + /// Returns if the mapping's activation conditions can be affected by parameter changes in + /// general. + pub fn activation_can_be_affected_by_parameters(&self) -> bool { + self.activation_condition_1.can_be_affected_by_parameters() + || self.activation_condition_2.can_be_affected_by_parameters() + } + + /// Returns if the mapping's activation conditions can be affected by target value changes + /// of other mappings. + /// + /// In particular, it returns the IDs of the lead mappings (the ones which provide the + /// target values that influence the activation state). + pub fn activation_can_be_affected_by_target_values(&self) -> impl Iterator { + self.activation_condition_1 + .target_value_lead_mapping() + .into_iter() + .chain(self.activation_condition_2.target_value_lead_mapping()) + } + + pub fn update_activation_from_effect( + &mut self, + activation_effect: MappingActivationEffect, + ) -> Option { + let was_active_before = self.is_active_in_terms_of_activation_state(); + self.activation_state.is_active_1 = activation_effect + .active_1_effect + .unwrap_or(self.activation_state.is_active_1); + self.activation_state.is_active_2 = activation_effect + .active_2_effect + .unwrap_or(self.activation_state.is_active_2); + self.post_process_activation_update(was_active_before) + } + + fn post_process_activation_update( + &mut self, + was_active_before: bool, + ) -> Option { + let now_is_active = self.is_active_in_terms_of_activation_state(); + if now_is_active == was_active_before { + return None; + } + if !now_is_active { + self.core.on_deactivate(); + } + let update = RealTimeMappingUpdate { + id: self.id(), + activation_change: Some(ActivationChange { + is_active: now_is_active, + }), + }; + Some(update) + } + + pub fn init_target_and_activation( + &mut self, + context: ExtendedProcessorContext, + control_context: ControlContext, + ) { + let (targets, is_active) = self.resolve_target(context, control_context, false); + self.targets = targets; + self.core.options.target_is_active = is_active; + self.update_activation_from_params(context.params()); + let target_value = self.current_aggregated_target_value(control_context); + self.initial_target_value = target_value; + self.last_non_performance_target_value = Cell::new(target_value); + } + + fn resolve_target( + &mut self, + context: ExtendedProcessorContext, + control_context: ControlContext, + is_just_refresh: bool, + ) -> (Vec, bool) { + match self.unresolved_target.as_ref() { + None => (vec![], false), + Some(ut) => match ut.resolve(context, self.core.compartment).ok() { + None => (vec![], false), + Some(resolved_targets) => { + // Successfully resolved. + if let Some(t) = resolved_targets.first() { + // We have at least one target, great! + self.core + .mode + .update_from_target(t, control_context, is_just_refresh); + let met = ut.conditions_are_met(&resolved_targets); + (resolved_targets, met) + } else { + // Resolved to zero targets. Consider as inactive. + (vec![], false) + } + } + }, + } + } + + pub fn needs_refresh_when_target_touched(&self) -> bool { + matches!( + &self.unresolved_target, + Some(UnresolvedCompoundMappingTarget::Reaper( + t + )) if matches!(**t, UnresolvedReaperTarget::LastTouched(_)) + ) + } + + /// `None` means that no polling is necessary for feedback because we are notified via events. + pub fn feedback_resolution(&self) -> Option { + let t = self.unresolved_target.as_ref()?; + let max_resolution_required_by_props = self + .core + .mode + .feedback_props_in_use() + .iter() + .filter_map(|p| prop_feedback_resolution(p, self, t)) + .max(); + if self.mode().wants_advanced_feedback() { + // For textual feedback, we just need to look at the props. + max_resolution_required_by_props + } else { + // Numeric feedback always implicitly relates to the main target value, therefore + // we also need to ask the target directly. + t.feedback_resolution() + .into_iter() + .chain(max_resolution_required_by_props) + .max() + } + } + + pub fn wants_to_be_polled_for_control(&self) -> bool { + self.core.source.wants_to_be_polled() || self.core.mode.wants_to_be_polled() + } + + /// The boolean return value tells if the resolved target changed in some way, the activation + /// change says if activation changed from off to on or on to off. + #[must_use] + pub fn refresh_target( + &mut self, + context: ExtendedProcessorContext, + control_context: ControlContext, + ) -> Option { + match self.unresolved_target.as_ref() { + None => return None, + Some(t) => { + if !t.can_be_affected_by_change_events() { + return None; + } + } + } + let was_effectively_active_before = self.target_is_effectively_active(); + let (targets, is_active) = self.resolve_target(context, control_context, true); + let target_changed = targets != self.targets; + self.targets = targets; + self.core.options.target_is_active = is_active; + // Build real-time target update if necessary + let activation_changed = + self.target_is_effectively_active() != was_effectively_active_before; + if !target_changed && !activation_changed { + return None; + } + let update = RealTimeTargetUpdate { + id: self.id(), + activation_change: if activation_changed { + Some(ActivationChange { is_active }) + } else { + None + }, + target_change: if target_changed { + Some(self.splinter_first_real_time_target()) + } else { + None + }, + }; + Some(update) + } + + pub fn update_activation_from_params( + &mut self, + params: &PluginParams, + ) -> Option { + let compartment_params = params.compartment_params(self.core.compartment); + self.update_activation( + self.activation_condition_1.is_fulfilled(compartment_params), + self.activation_condition_2.is_fulfilled(compartment_params), + ) + } + + fn update_activation( + &mut self, + is_active_1: Option, + is_active_2: Option, + ) -> Option { + let was_active_before = self.is_active_in_terms_of_activation_state(); + if let Some(is_active) = is_active_1 { + self.activation_state.is_active_1 = is_active; + } + if let Some(is_active) = is_active_2 { + self.activation_state.is_active_2 = is_active; + } + self.post_process_activation_update(was_active_before) + } + + /// Doesn't check if explicitly enabled or disabled. + pub fn is_active_in_terms_of_activation_state(&self) -> bool { + self.activation_state.is_active() + } + + /// A target is considered as active if it resolves successfully to at least one real target + /// and the target activation conditions (e.g. track must be selected) are met. + pub fn target_is_active(&self) -> bool { + self.core.options.target_is_active + } + + /// Returns `true` if the mapping is active. + /// + /// A mapping is considered as active if all the following conditions are true: + /// + /// - The mapping is enabled + /// - The mapping is active, according to conditional activation ("Active" property of mapping) + /// - The target is active + /// + /// It doesn't check the control-enabled or feedback-enabled flags. + pub fn is_active(&self) -> bool { + is_effectively_active( + &self.core.options, + &self.activation_state, + self.unresolved_target.as_ref(), + ) + } + + fn target_is_effectively_active(&self) -> bool { + target_is_effectively_active(&self.core.options, self.unresolved_target.as_ref()) + } + + /// Returns `true` if mapping & target is active and control or feedback is enabled. + pub fn is_effectively_on(&self) -> bool { + self.is_active() && (self.control_is_enabled() || self.feedback_is_enabled()) + } + + pub fn control_is_effectively_on(&self) -> bool { + self.is_active() && self.control_is_enabled() + } + + pub fn control_is_enabled(&self) -> bool { + self.core.options.control_is_effectively_enabled() + } + + pub fn feedback_is_enabled(&self) -> bool { + self.core.options.feedback_is_effectively_enabled() + } + + pub fn feedback_is_effectively_on(&self) -> bool { + feedback_is_effectively_on( + &self.core.options, + &self.activation_state, + self.unresolved_target.as_ref(), + ) + } + + pub fn source(&self) -> &CompoundMappingSource { + &self.core.source + } + + pub fn targets(&self) -> &[CompoundMappingTarget] { + &self.targets + } + + /// This makes the button fire modes work (e.g. "Fire after delay"). + #[must_use] + pub fn poll_mode( + &mut self, + context: ControlContext, + processor_context: ExtendedProcessorContext, + timestamp: ControlEventTimestamp, + log_mode_control_result: impl Fn(ControlLogEntry), + ) -> MappingControlResult { + self.control_internal( + ControlOptions::default(), + context, + processor_context, + true, + log_mode_control_result, + |_, context, mode, target| mode.poll(target, context, timestamp), + ) + } + + pub fn group_interaction(&self) -> GroupInteraction { + self.core.group_interaction + } + + /// Controls mode => target. + /// + /// Don't execute in real-time processor because this executes REAPER main-thread-only + /// functions. If `send_feedback_after_control` is on, this might return feedback. + #[allow(clippy::too_many_arguments)] + #[must_use] + pub fn control_from_mode( + &mut self, + source_control_event: ControlEvent, + options: ControlOptions, + context: ControlContext, + processor_context: ExtendedProcessorContext, + last_non_performance_target_value: Option, + log_mode_control_result: impl Fn(ControlLogEntry), + ) -> MappingControlResult { + let result = self.control_internal( + options, + context, + processor_context, + false, + log_mode_control_result, + |options, context, mode, target| { + mode.control_with_options( + source_control_event, + target, + context, + options.mode_control_options, + last_non_performance_target_value, + ) + }, + ); + if self.core.mode.wants_to_know_final_target_value() + && result.at_least_one_target_was_reached + { + if let Some(final_target_value) = self.current_aggregated_target_value(context) { + self.core + .mode + .report_final_target_value_of_last_control(final_target_value); + } + } + result + } + + /// Controls target directly without using mode. + /// + /// Don't execute in real-time processor because this executes REAPER main-thread-only + /// functions. If `send_feedback_after_control` is on, this might return feedback. + #[allow(clippy::too_many_arguments)] + #[must_use] + pub fn control_from_target_via_group_interaction( + &mut self, + value: AbsoluteValue, + options: ControlOptions, + context: ControlContext, + inverse: bool, + processor_context: ExtendedProcessorContext, + log_mode_control_result: impl Fn(ControlLogEntry), + ) -> MappingControlResult { + self.control_internal( + options, + context, + processor_context, + false, + log_mode_control_result, + |_, _, mode, target| { + let mut v = value; + let control_type = target.control_type(context); + // This is very similar to the mode logic, but just a small subset. + if inverse { + let normalized_max = control_type.discrete_max().map(|m| { + mode.settings() + .discrete_target_value_interval + .normalize_to_min(m) + }); + v = v.inverse(normalized_max); + } + v = v.denormalize( + &mode.settings().target_value_interval, + &mode.settings().discrete_target_value_interval, + mode.settings().use_discrete_processing, + control_type.discrete_max(), + ); + Some(ModeControlResult::hit_target(ControlValue::from_absolute( + v, + ))) + }, + ) + } + + fn data(&self) -> MappingData { + MappingData { + compartment: self.core.compartment, + mapping_id: self.core.id, + group_id: self.core.group_id, + last_non_performance_target_value: self.last_non_performance_target_value(), + } + } + + #[must_use] + pub fn control_from_target_directly( + &mut self, + context: ControlContext, + // TODO-low Strictly spoken, this is not necessary, because control_internal uses this only + // if target refresh is enforced, which is not the case here. + processor_context: ExtendedProcessorContext, + value: ControlValue, + log_mode_control_result: impl Fn(ControlLogEntry), + ) -> MappingControlResult { + self.control_internal( + ControlOptions::default(), + context, + processor_context, + false, + log_mode_control_result, + |_, _, _, _| Some(ModeControlResult::hit_target(value)), + ) + } + + #[allow(clippy::too_many_arguments)] + #[must_use] + fn control_internal( + &mut self, + options: ControlOptions, + context: ControlContext, + processor_context: ExtendedProcessorContext, + is_polling: bool, + log_mode_control_result: impl Fn(ControlLogEntry), + get_mode_control_result: impl Fn( + ControlOptions, + MappingControlContext, + &mut Mode, + &ReaperTarget, + ) -> Option>, + ) -> MappingControlResult { + let mut send_manual_feedback_because_of_target = false; + let mut at_least_one_relevant_target_exists = false; + let mut at_least_one_target_was_reached = false; + let mut at_least_one_target_caused_effect = false; + let mut first_hit_instruction = None; + use ModeControlResult::*; + let enforce_target_refresh = options.enforce_target_refresh + // Respect targets that want to keep their state by opting out from refresh. This is respected on a normal + // non-enforced refresh too! + && self + .unresolved_target + .as_ref() + .is_some_and(|t| t.can_be_affected_by_change_events()); + + let mut fresh_targets = if enforce_target_refresh { + let (targets, conditions_are_met) = + self.resolve_target(processor_context, context, true); + if !conditions_are_met { + // In this case, we don't log and don't increase the invocation counter because + // the target is inactive - so the complete mapping is also considered inactive. + return MappingControlResult::default(); + } + targets + } else { + vec![] + }; + let ctx = MappingControlContext { + control_context: context, + mapping_data: self.data(), + coming_from_real_time: options.coming_from_real_time, + }; + let actual_targets = if enforce_target_refresh { + &mut fresh_targets + } else { + &mut self.targets + }; + self.core.increase_invocation_count(); + for (target_index, target) in actual_targets.iter_mut().enumerate() { + let target = if let CompoundMappingTarget::Reaper(t) = target { + t + } else { + continue; + }; + at_least_one_relevant_target_exists = true; + let (log_entry_kind, control_value, error) = + match get_mode_control_result(options, ctx, &mut self.core.mode, target) { + None => { + // The incoming source value doesn't reach the target because the source value + // was filtered out. If `send_feedback_after_control` is enabled, we + // still send feedback - this can be useful with controllers which insist on + // controlling the LED on their own. The feedback sent by ReaLearn + // will fix this self-controlled LED state. + (ControlLogEntryKind::IgnoredByGlue, None, "") + } + Some(HitTarget { value }) => { + at_least_one_target_was_reached = true; + if !is_polling { + self.core.time_of_last_control = Some(Instant::now()); + } + // Be graceful here. + let (log_entry_kind, error) = match target.hit(value, ctx) { + Ok(response) => { + if response.caused_effect { + at_least_one_target_caused_effect = true; + } + let log_entry_kind = if let Some(hi) = response.hit_instruction { + // We have a hit instruction! Save it so it can be executed in + // the next step. + // TODO-low For now, the first hit instruction wins (at the moment we don't + // have multi-targets in which multiple targets send hit instructions + // anyway). + if first_hit_instruction.is_none() { + first_hit_instruction = Some(hi); + ControlLogEntryKind::CreatedHitInstruction + } else { + ControlLogEntryKind::DiscardedHitInstruction + } + } else if response.caused_effect { + ControlLogEntryKind::HitSuccessfully + } else { + ControlLogEntryKind::IgnoredByTarget + }; + (log_entry_kind, "") + } + Err(msg) => { + debug!("Control failed: {}", msg); + (ControlLogEntryKind::HitFailed, msg) + } + }; + if should_send_manual_feedback_due_to_target( + target, + &self.core.options, + &self.activation_state, + self.unresolved_target.as_ref(), + ) { + send_manual_feedback_because_of_target = true; + } + (log_entry_kind, Some(value), error) + } + Some(LeaveTargetUntouched(v)) => { + // The target already has the desired value. + // If `send_feedback_after_control` is enabled, we still send feedback - this + // can be useful with controllers which insist on controlling the LED on their + // own. The feedback sent by ReaLearn will fix this self-controlled LED state. + at_least_one_target_was_reached = true; + (ControlLogEntryKind::LeftTargetUntouched, Some(v), "") + } + }; + // Log + let log_entry = ControlLogEntry { + kind: log_entry_kind, + control_value, + target_index, + invocation_count: self.core.invocation_count(), + error, + }; + log_mode_control_result(log_entry); + } + if send_manual_feedback_because_of_target { + let new_target_value = self.current_aggregated_target_value(context); + MappingControlResult { + at_least_one_target_was_reached, + at_least_one_target_caused_effect, + new_target_value, + feedback_value: self.manual_feedback_because_of_target(new_target_value, context), + hit_instruction: first_hit_instruction, + celebrate_success: self.beep_on_success(), + } + } else { + MappingControlResult { + at_least_one_target_was_reached, + at_least_one_target_caused_effect, + new_target_value: None, + feedback_value: if !is_polling && at_least_one_relevant_target_exists { + // Before #396, we only sent "feedback after control" if the target was not hit at all. + // Reasoning was that if the target was hit, there must have been a value change + // (because we usually don't hit a target if it already has the desired value) + // and this value change would cause automatic feedback anyway. Then it wouldn't + // be necessary to send additional manual feedback. + // + // But this conclusion is wrong in some cases: + // 1. The target value might be very, very close to the desired value but not + // the same. The target would be hit then (for being safe) but no feedback + // might be generated because the difference might be insignificant regarding + // our FEEDBACK_EPSILON (checked when polling feedback). This also depends a + // bit on how the target interprets super-tiny value changes. + // 2. If we have a retriggerable target, we would always hit it, even if its + // value wouldn't change. + // + // The new strategy is: Better redundant feedback messages than omitting + // important ones. This is just a workaround for weird controllers anyway! + // At the very least they should be able to cope with a few more feedback + // messages. + // TODO-bkl-medium we could optimize this in future by checking + // significance of the difference within the mapping (should be easy now that + // we have mutable access to self here). + self.manual_feedback_after_control_if_enabled(options, context) + } else { + None + }, + hit_instruction: first_hit_instruction, + celebrate_success: self.beep_on_success(), + } + } + } + + pub fn virtual_source_control_element(&self) -> Option { + match &self.core.source { + CompoundMappingSource::Virtual(s) => Some(s.control_element()), + _ => None, + } + } + + pub fn virtual_target_control_element(&self) -> Option { + match self.unresolved_target.as_ref()? { + UnresolvedCompoundMappingTarget::Virtual(t) => Some(t.control_element()), + _ => None, + } + } + + fn manual_feedback_because_of_target( + &self, + new_target_value: Option, + control_context: ControlContext, + ) -> Option { + let value = self.feedback_entry_point( + FeedbackDestinations { + with_projection_feedback: true, + with_source_feedback: true, + }, + new_target_value, + control_context, + NoopLogger, + )?; + let compound_value = CompoundFeedbackValue { + value: value.value, + cause: FeedbackCause::ManualFeedbackBecauseOfTarget, + }; + Some(compound_value) + } + + /// Returns `None` when used on mappings with virtual targets. + pub fn feedback( + &self, + with_projection_feedback: bool, + context: ControlContext, + ) -> Option { + self.feedback_entry_point( + FeedbackDestinations { + with_projection_feedback, + with_source_feedback: true, + }, + self.current_aggregated_target_value(context), + context, + NoopLogger, + ) + } + + /// This is the primary entry point to feedback! + /// + /// Returns `None` when used on mappings with virtual targets. + pub fn feedback_entry_point( + &self, + destinations: FeedbackDestinations, + combined_target_value: Option, + control_context: ControlContext, + logger: impl SourceFeedbackLogger, + ) -> Option { + // - We shouldn't ask the source if it wants the given numerical feedback value or a textual + // value because a virtual source wouldn't know! Even asking a real source wouldn't make + // much sense because real sources could be capable of processing both numerical and + // textual feedback (and indeed that makes sense for an LCD source!). + // - Neither should we ask the target because the target is not supposed to dictate which + // form of feedback it sends, it just provides us with options and we can choose. + // - This leaves us with asking the mode. That means the user needs to explicitly choose + // whether it wants numerical or textual feedback. + let prop_provider = MappingPropProvider::new(self, control_context); + let feedback_value = if self.core.mode.wants_advanced_feedback() { + // Advanced feedback (e.g. text expression) + let feedback_value = self + .core + .mode + .build_feedback(&prop_provider, control_context.mode_context); + Some(feedback_value) + } else { + // Normal numeric feedback + let style = self.core.mode.feedback_style(&prop_provider); + // https://github.com/helgoboss/helgobox/issues/1311: + // Before falling back to UnitValue::MIN, targets that didn't return a value (e.g. actions with invocation + // mode trigger) wouldn't cause any feedback. That means nothing happens to the connected feedback element. + // It's like feedback is disabled. + // This behavior turns out to be weird as soon as you have a display source that wants to display something + // (e.g. text), no matter whether the target supports feedback or not: The display will just be empty, + // nothing will happen to it. It will not even be switched off, so old content stays there ... + // which is misleading. I realized this when building Stream Deck buttons that show a constant text and + // control a trigger-like action. + // I think the best way to deal with it is to emit the minimum feedback value = 0%. Emitting 0% is different + // from off. "Off" means, the mapping and/or target is inactive. So emitting 0% makes the source aware that + // an active target is connected. However, we shouldn't fall back to 0% already now. We should do it + // later in the feedback processing pipeline, otherwise numeric feedback transformation would be fed + // with a 0% value and might turn it into something that doesn't correspond to 0% + // (not knowing that actually no valid value was returned). This was the cause of + // https://github.com/helgoboss/helgobox/issues/1090. + combined_target_value.map(|final_target_value| { + FeedbackValue::Numeric(NumericFeedbackValue::new(style, final_target_value)) + }) + }; + let cause = if self.core.is_echo() { + FeedbackCause::Echo + } else { + FeedbackCause::Normal + }; + let specific_compound_value = self.feedback_given_target_value( + feedback_value.map(Cow::Owned), + destinations, + control_context.source_context, + logger, + cause, + )?; + let compound_value = CompoundFeedbackValue { + value: specific_compound_value, + cause, + }; + Some(compound_value) + } + + pub fn current_aggregated_target_value( + &self, + context: ControlContext, + ) -> Option { + let values = self.targets.iter().map(|t| t.current_value(context)); + aggregate_target_values(values) + } + + pub fn mode(&self) -> &Mode { + &self.core.mode + } + + pub fn group_id(&self) -> GroupId { + self.core.group_id + } + + /// If the feedback value is `None`, it means the target didn't return a value. This can happen with + /// targets that don't have the concept of a current value (e.g., trigger-like actions) or with targets + /// that are in a state in which no control is possible (e.g., control unit scrolling, but without any + /// possibility to scroll because the dimension is not large enough). + /// + /// Taking the feedback value as a Cow is better than taking a reference because with a + /// reference we would for sure have to clone a textual feedback value, even if the consumer + /// can give us ownership of the feedback value. It's also better than taking an owned value + /// because it's possible that we don't produce a feedback value at all! In which a consumer + /// that can't give up ownership would need to make a clone in advance - for nothing! + pub fn feedback_given_target_value( + &self, + feedback_value: Option>, + destinations: FeedbackDestinations, + source_context: RealearnSourceContext, + logger: impl SourceFeedbackLogger, + cause: FeedbackCause, + ) -> Option { + let destinations = destinations.resolve(cause, self.core.options.feedback_send_behavior); + let options = ModeFeedbackOptions { + source_is_virtual: self.core.source.is_virtual(), + max_discrete_source_value: self.core.source.max_discrete_value(), + }; + let mode_value = self.core.mode.feedback_with_options_detail( + feedback_value, + options, + Default::default(), + )?; + logger.log(FeedbackLogEntry { + feedback_value: mode_value.as_ref(), + }); + self.feedback_given_mode_value(mode_value, destinations, source_context) + } + + fn feedback_given_mode_value( + &self, + mode_value: Cow, + destinations: FeedbackDestinations, + source_context: RealearnSourceContext, + ) -> Option { + SpecificCompoundFeedbackValue::from_mode_value( + self.core.compartment, + self.key.clone(), + &self.core.source, + mode_value, + destinations, + source_context, + ) + } + + /// This returns a "lights off" feedback. + /// + /// Used when mappings get inactive. + pub fn off_feedback( + &self, + source_context: RealearnSourceContext, + logger: impl SourceFeedbackLogger, + ) -> Option { + // TODO-medium "Unused" and "zero" could be a difference for projection so we should + // have different values for that (at the moment it's not though). + let feedback_value = FeedbackValue::Off; + logger.log(FeedbackLogEntry { + feedback_value: &feedback_value, + }); + let value = self.feedback_given_mode_value( + Cow::Owned(feedback_value), + FeedbackDestinations { + with_projection_feedback: true, + with_source_feedback: true, + }, + source_context, + )?; + let compound_value = CompoundFeedbackValue { + value, + cause: FeedbackCause::Normal, + }; + Some(compound_value) + } + + fn manual_feedback_after_control_if_enabled( + &self, + options: ControlOptions, + context: ControlContext, + ) -> Option { + if self.core.options.feedback_send_behavior + == FeedbackSendBehavior::SendFeedbackAfterControl + || options.enforce_send_feedback_after_control + { + if self.feedback_is_effectively_on() { + // No projection feedback in this case! Just the source controller needs this hack. + let value = self.feedback_entry_point( + FeedbackDestinations { + with_projection_feedback: false, + with_source_feedback: true, + }, + self.current_aggregated_target_value(context), + context, + NoopLogger, + )?; + let compound_value = CompoundFeedbackValue { + value: value.value, + cause: FeedbackCause::FeedbackAfterControl, + }; + Some(compound_value) + } else { + None + } + } else { + None + } + } + + /// Controls the source only. + /// + /// Doesn't consider MIDI sources because they are handled completely in the real-time mapping. + pub fn control_source( + &mut self, + msg: MainSourceMessage, + ) -> Option> { + let compartment = self.compartment(); + match (msg, &mut self.core.source) { + (MainSourceMessage::Osc(m), CompoundMappingSource::Osc(s)) => { + // With OSC sources, we don't distinguish between matched or consumed because + // there's no such thing such as "letting messages through". + s.control(m).map(ControlOutcome::Matched) + } + (MainSourceMessage::Reaper(m), CompoundMappingSource::Reaper(s)) => { + // With REAPER sources, we don't distinguish between matched or consumed because + // there's no such thing such as "letting messages through". + s.control(m, compartment).map(ControlOutcome::Matched) + } + (MainSourceMessage::Key(m), CompoundMappingSource::Key(s)) => s.control(m), + (MainSourceMessage::StreamDeck(m), CompoundMappingSource::StreamDeck(s)) => { + s.control(m).map(ControlOutcome::Matched) + } + _ => None, + } + } + + /// Polls the source. + pub fn poll_source(&mut self) -> Option { + match &mut self.core.source { + CompoundMappingSource::Reaper(s) => s.poll(), + _ => None, + } + } + + pub fn control_virtualizing( + &mut self, + evt: ControlEvent, + ) -> Option> { + if self.targets.is_empty() { + return None; + } + let control_value = match self.control_source(evt.payload())? { + ControlOutcome::Consumed => { + return Some(ControlOutcome::Consumed); + } + ControlOutcome::Matched(v) => v, + }; + // First target is enough because this does nothing yet. + let virtual_source_value = match self.targets.first()? { + CompoundMappingTarget::Virtual(t) => { + match_partially(&mut self.core, t, evt.with_payload(control_value)) + } + CompoundMappingTarget::Reaper(_) => None, + }; + Some(ControlOutcome::Matched(virtual_source_value?)) + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum MainSourceMessage<'a> { + Osc(&'a OscMessage), + Reaper(&'a ReaperMessage), + Key(KeyMessage), + StreamDeck(StreamDeckMessage), +} + +impl MainSourceMessage<'_> { + /// Extracts data if this kind of message supports source learning, filtering etc., otherwise + /// returns `None`. + pub fn create_capture_result(&self) -> Option { + use MainSourceMessage::*; + let res = match *self { + Osc(msg) => MessageCaptureResult::Osc(OscScanResult { + message: msg.clone(), + dev_id: None, + }), + Key(msg) => MessageCaptureResult::Keyboard(msg), + StreamDeck(msg) => MessageCaptureResult::StreamDeck(StreamDeckScanResult { + message: msg, + dev_id: None, + }), + Reaper(msg) => { + use ReaperMessage::*; + match msg { + MidiDevicesConnected(_) + | MidiDevicesDisconnected(_) + | StreamDeckDevicesConnected(_) + | RealearnUnitStarted + | RealearnCompartmentLoaded(_) => return None, + RealearnParameterChange(payload) => { + MessageCaptureResult::RealearnParameter(*payload) + } + } + } + }; + Some(res) + } +} + +#[derive(Debug)] +pub struct RealTimeMapping { + pub core: MappingCore, + is_active: bool, + /// Is `Some` if user-provided target data is complete. + target_category: Option, + pub target_is_resolved: bool, + /// Is `Some` if virtual or this target needs to be processed in real-time. + pub resolved_target: Option, + pub lifecycle_midi_data: LifecycleMidiData, +} + +#[derive(Debug)] +pub enum UnresolvedTargetCategory { + Reaper, + Virtual, +} + +#[derive(Copy, Clone, Debug)] +pub enum LifecyclePhase { + Activation, + Deactivation, +} + +impl From for LifecyclePhase { + fn from(v: bool) -> Self { + use LifecyclePhase::*; + if v { + Activation + } else { + Deactivation + } + } +} + +impl RealTimeMapping { + pub fn id(&self) -> MappingId { + self.core.id + } + + pub fn compartment(&self) -> CompartmentKind { + self.core.compartment + } + + pub fn lifecycle_midi_messages(&self, phase: LifecyclePhase) -> &[LifecycleMidiMessage] { + use LifecyclePhase::*; + match phase { + Activation => &self.lifecycle_midi_data.activation_midi_messages, + Deactivation => &self.lifecycle_midi_data.deactivation_midi_messages, + } + } + + pub fn control_is_effectively_on(&self) -> bool { + self.is_active() && self.control_is_enabled() + } + + pub fn control_is_enabled(&self) -> bool { + self.core.options.control_is_effectively_enabled() + } + + pub fn feedback_is_effectively_on(&self) -> bool { + self.is_active() && self.core.options.feedback_is_effectively_enabled() + } + + pub fn feedback_is_effectively_on_ignoring_mapping_activation(&self) -> bool { + self.is_effectively_active_ignoring_mapping_activation() + && self.core.options.feedback_is_effectively_enabled() + } + + pub fn feedback_is_effectively_on_ignoring_target_activation(&self) -> bool { + self.is_effectively_active_ignoring_target_activation() + && self.core.options.feedback_is_effectively_enabled() + } + + /// Returns `true` if the mapping is active. + /// + /// A mapping is considered as active if all the following conditions are true: + /// + /// - The mapping is enabled + /// - The mapping is active, according to conditional activation ("Active" property of mapping) + /// - The target is active + /// + /// It doesn't check the control-enabled or feedback-enabled flags. + pub fn is_active(&self) -> bool { + self.core.options.persistent_processing_state.is_enabled + && self.is_active + && self.core.options.target_is_active + } + + fn is_effectively_active_ignoring_target_activation(&self) -> bool { + self.is_active + } + + fn is_effectively_active_ignoring_mapping_activation(&self) -> bool { + self.core.options.target_is_active + } + + pub fn update_persistent_processing_state(&mut self, state: PersistentMappingProcessingState) { + self.core.update_persistent_processing_state(state); + } + + pub fn update_target(&mut self, update: &mut RealTimeTargetUpdate) { + if let Some(c) = update.activation_change { + self.core.options.target_is_active = c.is_active; + } + if let Some(rt_target) = update.target_change.take() { + self.resolved_target = rt_target; + } + } + + pub fn update(&mut self, update: &RealTimeMappingUpdate) { + if let Some(c) = update.activation_change { + let was_active_before = self.is_active; + self.is_active = c.is_active; + if was_active_before && !c.is_active { + self.core.on_deactivate(); + } + } + } + + pub fn source(&self) -> &CompoundMappingSource { + &self.core.source + } + + pub fn has_reaper_target(&self) -> bool { + matches!(self.target_category, Some(UnresolvedTargetCategory::Reaper)) + } + + pub fn consumes(&self, msg: RawShortMessage) -> bool { + self.core.source.consumes(&msg) + } + + pub fn options(&self) -> &ProcessorMappingOptions { + &self.core.options + } + + pub fn mode_control_options(&self) -> ModeControlOptions { + ModeControlOptions { + enforce_rotate: self.core.mode.settings().rotate, + } + } +} + +#[derive(Clone, Debug)] +pub struct MappingCore { + compartment: CompartmentKind, + pub id: MappingId, + group_id: GroupId, + pub source: CompoundMappingSource, + pub mode: Mode, + group_interaction: GroupInteraction, + pub options: ProcessorMappingOptions, + /// Used for preventing echo feedback. + time_of_last_control: Option, + /// Invocation counter. + /// + /// Increasing with every source match, increasing even when glue filtered the message out. + /// For multi-targets, this increases only once even when controlling multiple targets in one + /// go. + invocation_count: u32, +} + +impl MappingCore { + pub fn splinter_real_time_core(&self) -> Self { + // This clone is special. Some compiled feedback scripts deep inside the object graph (in + // source and mode, in particular) will not really be cloned because the real-time + // processor will not use them anyway! Look up `CloneAsDefault`. + self.clone() + } + + pub fn invocation_count(&self) -> u32 { + self.invocation_count + } + + pub fn increase_invocation_count(&mut self) { + self.invocation_count = self.invocation_count.wrapping_add(1); + } + + fn is_echo(&self) -> bool { + if let Some(t) = self.time_of_last_control { + t.elapsed() <= MAX_ECHO_FEEDBACK_DELAY + } else { + false + } + } + + fn update_persistent_processing_state(&mut self, state: PersistentMappingProcessingState) { + let was_enabled_before = self.options.persistent_processing_state.is_enabled; + self.options.persistent_processing_state = state; + if was_enabled_before && !state.is_enabled { + self.on_deactivate(); + } + } + + fn on_deactivate(&mut self) { + self.source.on_deactivate(); + self.mode.on_deactivate(); + } +} + +// PartialEq because we want to put it into a Prop. +#[derive(Clone, PartialEq, Debug)] +pub enum CompoundMappingSource { + Never, + Midi(MidiSource), + Osc(OscSource), + Virtual(VirtualSource), + Reaper(ReaperSource), + Key(KeySource), + StreamDeck(StreamDeckSource), +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum CompoundMappingSourceAddress { + Midi(MidiSourceAddress), + Osc(OscSourceAddress), + Virtual(VirtualSourceAddress), + Reaper(ReaperSourceAddress), + StreamDeck(StreamDeckSourceAddress), +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum ReaperSourceAddress { + /// At the moment, we allow only one speaker speaking at a time. + /// + /// Our TTS logic is also built in a way that we have only one static TTS instance and saying + /// something interrupts whatever is currently said. + GlobalSpeech, +} + +impl CompoundMappingSource { + /// This should be called when the containing mapping gets deactivated. + /// + /// Attention: At the moment it can be called even if the mapping was already inactive. + /// So it should be idempotent! + pub fn on_deactivate(&mut self) { + use CompoundMappingSource::*; + match self { + Reaper(s) => s.on_deactivate(), + _ => {} + } + } + + /// If this returns `true`, the `poll` method should be called, on a regular basis. + pub fn wants_to_be_polled(&self) -> bool { + use CompoundMappingSource::*; + match self { + Reaper(s) => s.wants_to_be_polled(), + _ => false, + } + } + + /// Extracts the address of the source control element for feedback purposes. + /// + /// Use this if you really need an owned representation of the source address. If you just want + /// to compare addresses, use [`Self::has_same_feedback_address_as_value`] + /// or [`Self::has_same_feedback_address_as_source`] instead. It can avoid the cloning. + // TODO-medium There are quite some places in which we are fine with a borrowed version but + // the problem is the MIDI source can't simply give us a borrowed one. Maybe we should + // create one at MIDI source creation time! But for this we need to make MidiSource a struct. + pub fn extract_feedback_address( + &self, + source_context: RealearnSourceContext, + ) -> Option { + use CompoundMappingSource::*; + match self { + Midi(s) => Some(CompoundMappingSourceAddress::Midi( + s.extract_feedback_address(source_context)?, + )), + Osc(s) => Some(CompoundMappingSourceAddress::Osc( + s.feedback_address().clone(), + )), + Virtual(s) => Some(CompoundMappingSourceAddress::Virtual(*s.feedback_address())), + Reaper(s) => s + .extract_feedback_address() + .map(CompoundMappingSourceAddress::Reaper), + StreamDeck(s) => Some(CompoundMappingSourceAddress::StreamDeck( + s.feedback_address(), + )), + _ => None, + } + } + + /// Checks if the given message is directed to the same address as the one of this source. + /// + /// Used for: + /// + /// - Source takeover (feedback) + pub fn has_same_feedback_address_as_value( + &self, + value: &FinalSourceFeedbackValue, + source_context: RealearnSourceContext, + ) -> bool { + use CompoundMappingSource::*; + match (self, value) { + (Osc(s), FinalSourceFeedbackValue::Osc(v)) => s.has_same_feedback_address_as_value(v), + (Midi(s), FinalSourceFeedbackValue::Midi(v)) => { + s.has_same_feedback_address_as_value(v, source_context) + } + (StreamDeck(s), FinalSourceFeedbackValue::StreamDeck(v)) => { + s.has_same_feedback_address_as_value(v) + } + _ => false, + } + } + + /// Checks if this and the given source share the same address. + /// + /// Used for: + /// + /// - Feedback diffing + pub fn has_same_feedback_address_as_source( + &self, + other: &Self, + source_context: RealearnSourceContext, + ) -> bool { + use CompoundMappingSource::*; + match (self, other) { + (Osc(s1), Osc(s2)) => s1.has_same_feedback_address_as_source(s2), + (Midi(s1), Midi(s2)) => s1.has_same_feedback_address_as_source(s2, source_context), + (StreamDeck(s1), StreamDeck(s2)) => s1.has_same_feedback_address_as_source(s2), + (Virtual(s1), Virtual(s2)) => s1.has_same_feedback_address_as_source(s2), + _ => false, + } + } + + /// Can be used to check if this mapping would react to the given message. + /// + /// The important difference to controlling is that it doesn't mutate the source. + /// + /// Used for: + /// + /// - Source learning (including source virtualization) + /// - Source filtering/finding (including source virtualization) + pub fn reacts_to_source_value_with( + &self, + value: IncomingCompoundSourceValue, + ) -> Option { + use CompoundMappingSource::*; + match (self, value) { + (Midi(s), IncomingCompoundSourceValue::Midi(v)) => s.control_flexible(v), + (Osc(s), IncomingCompoundSourceValue::Osc(m)) => { + s.control(m).map(ControlResult::Processed) + } + (Virtual(s), IncomingCompoundSourceValue::Virtual(m)) => { + s.control(m).map(ControlResult::Processed) + } + (Key(s), IncomingCompoundSourceValue::Key(m)) => { + s.reacts_to_message_with(m).map(ControlResult::Processed) + } + (StreamDeck(s), IncomingCompoundSourceValue::StreamDeck(m)) => { + s.control(m).map(ControlResult::Processed) + } + _ => None, + } + } + + pub fn from_message_capture_event(event: MessageCaptureEvent) -> Option { + use MessageCaptureResult::*; + let res = match event.result { + Midi(scan_result) => { + let midi_source = + MidiSource::from_source_value(scan_result.value, scan_result.character)?; + Self::Midi(midi_source) + } + Osc(msg) => { + let osc_source = + OscSource::from_source_value(msg.message, event.osc_arg_index_hint); + Self::Osc(osc_source) + } + Keyboard(msg) => { + let key_source = KeySource::new(msg.stroke()); + Self::Key(key_source) + } + StreamDeck(scan_result) => { + let source = + StreamDeckSource::new(scan_result.message.button_index, Default::default()); + Self::StreamDeck(source) + } + RealearnParameter(payload) => { + let reaper_source = ReaperSource::RealearnParameter(RealearnParameterSource { + parameter_index: payload.parameter_index, + }); + Self::Reaper(reaper_source) + } + }; + Some(res) + } + + pub fn format_control_value(&self, value: ControlValue) -> Result { + use CompoundMappingSource::*; + match self { + Midi(s) => s.format_control_value(value), + Virtual(s) => s.format_control_value(value), + Osc(s) => s.format_control_value(value), + Reaper(s) => s.format_control_value(value), + Never | Key(_) | StreamDeck(_) => { + Ok(format_percentage_without_unit(value.to_unit_value()?.get())) + } + } + } + + pub fn parse_control_value(&self, text: &str) -> Result { + use CompoundMappingSource::*; + match self { + Midi(s) => s.parse_control_value(text), + Virtual(s) => s.parse_control_value(text), + Osc(s) => s.parse_control_value(text), + Reaper(s) => s.parse_control_value(text), + Never | Key(_) | StreamDeck(_) => parse_percentage_without_unit(text)?.try_into(), + } + } + + pub fn character(&self) -> ExtendedSourceCharacter { + use CompoundMappingSource::*; + match self { + Midi(s) => ExtendedSourceCharacter::Normal(s.character()), + Virtual(s) => s.character(), + Osc(s) => ExtendedSourceCharacter::Normal(s.character()), + Reaper(s) => ExtendedSourceCharacter::Normal(s.character()), + Never => ExtendedSourceCharacter::VirtualContinuous, + Key(_) | StreamDeck(_) => { + ExtendedSourceCharacter::Normal(SourceCharacter::MomentaryButton) + } + } + } + + pub fn feedback( + &self, + feedback_value: Cow, + source_context: RealearnSourceContext, + ) -> Option { + use CompoundMappingSource::*; + match self { + Midi(s) => s + .feedback_flexible(feedback_value.into_owned(), source_context) + .map(PreliminarySourceFeedbackValue::Midi), + Osc(s) => s + .feedback(feedback_value.into_owned()) + .map(PreliminarySourceFeedbackValue::Osc), + Reaper(s) => s + .feedback(&feedback_value) + .map(PreliminarySourceFeedbackValue::Reaper), + StreamDeck(s) => s + .feedback(&feedback_value) + .map(PreliminarySourceFeedbackValue::StreamDeck), + // This is handled in a special way by consumers. + Virtual(_) => None, + // No feedback for other sources. + Key(_) | Never => None, + } + } + + pub fn consumes(&self, msg: &impl ShortMessage) -> bool { + use CompoundMappingSource::*; + match self { + Midi(s) => s.consumes(msg), + Reaper(_) | Virtual(_) | Osc(_) | Never | Key(_) | StreamDeck(_) => false, + } + } + + pub fn is_virtual(&self) -> bool { + matches!(self, CompoundMappingSource::Virtual(_)) + } + + pub fn max_discrete_value(&self) -> Option { + use CompoundMappingSource::*; + match self { + Midi(s) => s.max_discrete_value(), + // TODO-medium OSC will also support discrete values as soon as we allow integers and + // configuring max values + Reaper(_) | Virtual(_) | Osc(_) | Never | Key(_) | StreamDeck(_) => None, + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct CompoundFeedbackValue { + pub value: SpecificCompoundFeedbackValue, + pub cause: FeedbackCause, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum FeedbackCause { + /// Target value change was caused e.g. by a mouse move in REAPER and was observed by ReaLearn. + Normal, + /// Target value change was caused by the ReaLearn mapping itself and was observed by ReaLearn. + Echo, + /// Target value itself can't be observed. Sending manually after control. + ManualFeedbackBecauseOfTarget, + /// Feedback sent because "Send feedback after control" enabled. + FeedbackAfterControl, +} + +impl CompoundFeedbackValue { + pub fn new(value: SpecificCompoundFeedbackValue, cause: FeedbackCause) -> Self { + Self { value, cause } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub enum SpecificCompoundFeedbackValue { + Virtual { + value: VirtualFeedbackValue, + destinations: FeedbackDestinations, + }, + Real(PreliminaryRealFeedbackValue), +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct FeedbackDestinations { + /// Feedback to projection clients. + pub with_projection_feedback: bool, + /// Feedback to controller itself. + pub with_source_feedback: bool, +} + +impl FeedbackDestinations { + pub fn is_all_off(&self) -> bool { + !self.with_source_feedback && !self.with_projection_feedback + } + + pub fn resolve( + &self, + cause: FeedbackCause, + feedback_send_behavior: FeedbackSendBehavior, + ) -> Self { + let source_feedback_is_okay = !(feedback_send_behavior + == FeedbackSendBehavior::PreventEchoFeedback + && cause == FeedbackCause::Echo); + Self { + with_projection_feedback: self.with_projection_feedback, + with_source_feedback: self.with_source_feedback && source_feedback_is_okay, + } + } +} + +impl SpecificCompoundFeedbackValue { + pub fn from_mode_value( + compartment: CompartmentKind, + mapping_key: Rc, + source: &CompoundMappingSource, + mode_value: Cow, + destinations: FeedbackDestinations, + source_context: RealearnSourceContext, + ) -> Option { + if destinations.is_all_off() { + return None; + } + let val = if let CompoundMappingSource::Virtual(vs) = &source { + // Virtual source + SpecificCompoundFeedbackValue::Virtual { + destinations, + value: vs.feedback(mode_value.into_owned()), + } + } else { + // Real source + let projection = if destinations.with_projection_feedback + && compartment == CompartmentKind::Controller + { + // TODO-medium Support textual projection feedback + mode_value.to_numeric().map(|v| { + ProjectionFeedbackValue::new(compartment, mapping_key, v.value.to_unit_value()) + }) + } else { + None + }; + let source = if destinations.with_source_feedback { + source.feedback(mode_value, source_context) + } else { + None + }; + SpecificCompoundFeedbackValue::Real(PreliminaryRealFeedbackValue::new( + projection, source, + )?) + }; + Some(val) + } +} + +pub type PreliminaryRealFeedbackValue = AbstractRealFeedbackValue; +pub type FinalRealFeedbackValue = AbstractRealFeedbackValue; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct AbstractRealFeedbackValue { + /// Feedback to be sent to projection. + /// + /// This is an option because there are situations when we don't want projection feedback but + /// source feedback (e.g. for "Feedback after control" because of too clever controllers). + pub projection: Option, + /// Feedback to be sent to the source. + /// + /// This is an option because there are situations when we don't want source feedback but + /// projection feedback (e.g. if "MIDI feedback output" is set to None). + pub source: Option, +} + +impl AbstractRealFeedbackValue { + pub fn new(projection: Option, source: Option) -> Option { + if projection.is_none() && source.is_none() { + return None; + } + let val = Self { projection, source }; + Some(val) + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct ProjectionFeedbackValue { + pub compartment: CompartmentKind, + pub mapping_key: Rc, + pub value: UnitValue, +} + +impl ProjectionFeedbackValue { + pub fn new(compartment: CompartmentKind, mapping_key: Rc, value: UnitValue) -> Self { + Self { + compartment, + mapping_key, + value, + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub enum PreliminarySourceFeedbackValue { + Midi(PreliminaryMidiSourceFeedbackValue<'static, RawShortMessage>), + Osc(OscMessage), + Reaper(ReaperSourceFeedbackValue), + StreamDeck(StreamDeckSourceFeedbackValue), +} + +#[derive(Clone, PartialEq, Debug)] +pub enum FinalSourceFeedbackValue { + Midi(MidiSourceValue<'static, RawShortMessage>), + Osc(OscMessage), + Reaper(ReaperSourceFeedbackValue), + StreamDeck(StreamDeckSourceFeedbackValue), +} + +impl FinalSourceFeedbackValue { + pub fn extract_address(&self) -> Option { + match self { + FinalSourceFeedbackValue::Midi(v) => v + .extract_feedback_address() + .map(CompoundMappingSourceAddress::Midi), + FinalSourceFeedbackValue::Osc(v) => { + Some(CompoundMappingSourceAddress::Osc(v.addr.clone())) + } + FinalSourceFeedbackValue::Reaper(v) => v + .extract_feedback_address() + .map(CompoundMappingSourceAddress::Reaper), + FinalSourceFeedbackValue::StreamDeck(v) => Some( + CompoundMappingSourceAddress::StreamDeck(v.feedback_address()), + ), + } + } +} + +#[derive(Debug)] +pub enum UnresolvedCompoundMappingTarget { + Reaper(Box), + Virtual(VirtualTarget), +} + +impl UnresolvedCompoundMappingTarget { + pub fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + use UnresolvedCompoundMappingTarget::*; + let resolved_targets = match self { + Reaper(t) => { + let reaper_targets = t.resolve(context, compartment)?; + reaper_targets + .into_iter() + .map(|t| CompoundMappingTarget::Reaper(Box::new(t))) + .collect() + } + Virtual(t) => vec![CompoundMappingTarget::Virtual(*t)], + }; + Ok(resolved_targets) + } + + pub fn conditions_are_met(&self, targets: &[CompoundMappingTarget]) -> bool { + use UnresolvedCompoundMappingTarget::*; + targets.iter().all(|target| match (self, target) { + (Reaper(t), CompoundMappingTarget::Reaper(rt)) => t.conditions_are_met(rt), + (Virtual(_), CompoundMappingTarget::Virtual(_)) => true, + _ => unreachable!(), + }) + } + + pub fn can_be_affected_by_change_events(&self) -> bool { + use UnresolvedCompoundMappingTarget::*; + match self { + Reaper(t) => t.can_be_affected_by_change_events(), + Virtual(_) => false, + } + } + + /// `None` means that no polling is necessary for feedback because we are notified via events. + pub fn feedback_resolution(&self) -> Option { + use UnresolvedCompoundMappingTarget::*; + match self { + Reaper(t) => t.feedback_resolution(), + Virtual(_) => None, + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub enum CompoundMappingTarget { + Reaper(Box), + Virtual(VirtualTarget), +} + +impl CompoundMappingTarget { + pub fn splinter_real_time_target(&self) -> Option { + match self { + CompoundMappingTarget::Reaper(t) => t + .splinter_real_time_target() + .map(RealTimeCompoundMappingTarget::Reaper), + CompoundMappingTarget::Virtual(t) => Some(RealTimeCompoundMappingTarget::Virtual(*t)), + } + } + + pub fn is_virtual(&self) -> bool { + matches!(self, CompoundMappingTarget::Virtual(_)) + } +} + +#[derive(Clone, PartialEq, Debug)] +pub enum RealTimeCompoundMappingTarget { + Reaper(RealTimeReaperTarget), + Virtual(VirtualTarget), +} + +pub struct WithControlContext<'a, T> { + control_context: ControlContext<'a>, + value: &'a T, +} + +impl<'a, T> WithControlContext<'a, T> { + pub fn new(control_context: ControlContext<'a>, value: &'a T) -> Self { + Self { + control_context, + value, + } + } +} + +impl ValueFormatter for WithControlContext<'_, CompoundMappingTarget> { + fn format_value(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + f.write_str( + &self + .value + .format_value_without_unit(value, self.control_context), + ) + } + + fn format_step(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + f.write_str( + &self + .value + .format_step_size_without_unit(value, self.control_context), + ) + } +} + +impl ValueParser for WithControlContext<'_, CompoundMappingTarget> { + fn parse_value(&self, text: &str) -> Result { + self.value.parse_as_value(text, self.control_context) + } + + fn parse_step(&self, text: &str) -> Result { + self.value.parse_as_step_size(text, self.control_context) + } +} + +impl RealearnTarget for CompoundMappingTarget { + fn character(&self, context: ControlContext) -> TargetCharacter { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.character(context), + Virtual(t) => t.character(), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.text_value(context), + Virtual(_) => None, + } + } + + fn numeric_value(&self, context: ControlContext) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.numeric_value(context), + Virtual(_) => None, + } + } + + fn control_type_and_character( + &self, + context: ControlContext, + ) -> (ControlType, TargetCharacter) { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.control_type_and_character(context), + Virtual(t) => (t.control_type(()), t.character()), + } + } + + fn open(&self, context: ControlContext) { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.open(context), + Virtual(_) => {} + }; + } + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.parse_as_value(text, context), + Virtual(_) => Err("not supported for virtual targets"), + } + } + + /// Parses the given text as a target step size and returns it as unit value. + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.parse_as_step_size(text, context), + Virtual(_) => Err("not supported for virtual targets"), + } + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + context: ControlContext, + ) -> Result { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.convert_unit_value_to_discrete_value(input, context), + Virtual(_) => Err("not supported for virtual targets"), + } + } + + fn format_value_without_unit(&self, value: UnitValue, context: ControlContext) -> String { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.format_value_without_unit(value, context), + Virtual(_) => String::new(), + } + } + + fn format_step_size_without_unit( + &self, + step_size: UnitValue, + context: ControlContext, + ) -> String { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.format_step_size_without_unit(step_size, context), + Virtual(_) => String::new(), + } + } + + fn hide_formatted_value(&self, context: ControlContext) -> bool { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.hide_formatted_value(context), + Virtual(_) => false, + } + } + + fn hide_formatted_step_size(&self, context: ControlContext) -> bool { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.hide_formatted_step_size(context), + Virtual(_) => false, + } + } + + fn value_unit(&self, context: ControlContext) -> &'static str { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.value_unit(context), + Virtual(_) => "", + } + } + + fn step_size_unit(&self, context: ControlContext) -> &'static str { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.step_size_unit(context), + Virtual(_) => "", + } + } + + fn format_value(&self, value: UnitValue, context: ControlContext) -> String { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.format_value(value, context), + Virtual(_) => String::new(), + } + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.hit(value, context), + Virtual(_) => Err("not supported for virtual targets"), + } + } + + fn can_report_current_value(&self) -> bool { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.can_report_current_value(), + Virtual(_) => false, + } + } + + fn is_available(&self, context: ControlContext) -> bool { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.is_available(context), + Virtual(_) => true, + } + } + + fn project(&self) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.project(), + Virtual(_) => None, + } + } + + fn track(&self) -> Option<&Track> { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.track(), + Virtual(_) => None, + } + } + + fn clip_slot_address(&self) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.clip_slot_address(), + Virtual(_) => None, + } + } + + fn clip_column_address(&self) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.clip_column_address(), + Virtual(_) => None, + } + } + + fn clip_row_address(&self) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.clip_row_address(), + Virtual(_) => None, + } + } + + fn fx(&self) -> Option<&Fx> { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.fx(), + Virtual(_) => None, + } + } + + fn route(&self) -> Option<&TrackRoute> { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.route(), + Virtual(_) => None, + } + } + + fn track_exclusivity(&self) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.track_exclusivity(), + Virtual(_) => None, + } + } + + fn supports_automatic_feedback(&self) -> bool { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.supports_automatic_feedback(), + Virtual(_) => false, + } + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + control_context: ControlContext, + ) -> (bool, Option) { + // TODO-medium I think this abstraction is not in use + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.process_change_event(evt, control_context), + Virtual(_) => (false, None), + } + } + + fn splinter_real_time_target(&self) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.splinter_real_time_target(), + Virtual(_) => None, + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + context: ControlContext, + ) -> Result { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.convert_discrete_value_to_unit_value(value, context), + Virtual(_) => Err("not supported for virtual targets"), + } + } + + fn prop_value(&self, key: &str, context: ControlContext) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.prop_value(key, context), + Virtual(_) => None, + } + } + + fn numeric_value_unit(&self, context: ControlContext) -> &'static str { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.numeric_value_unit(context), + Virtual(_) => "", + } + } + + fn reaper_target_type(&self) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.reaper_target_type(), + Virtual(_) => None, + } + } +} + +impl<'a> Target<'a> for CompoundMappingTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext) -> Option { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.current_value(context), + Virtual(t) => t.current_value(()), + } + } + + fn control_type(&self, context: ControlContext) -> ControlType { + use CompoundMappingTarget::*; + match self { + Reaper(t) => t.control_type(context), + Virtual(t) => t.control_type(()), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct QualifiedMappingId { + pub compartment: CompartmentKind, + pub id: MappingId, +} + +impl QualifiedMappingId { + pub fn new(compartment: CompartmentKind, id: MappingId) -> Self { + Self { compartment, id } + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Enum, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + strum::Display, + strum::AsRefStr, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum CompartmentKind { + // It's important for `RealTimeProcessor` logic that this is the first element! We use array + // destructuring. + #[strum(serialize = "controller compartment")] + Controller, + #[strum(serialize = "main compartment")] + Main, +} + +impl CompartmentKind { + /// We could also use the generated `into_enum_iter()` everywhere but IDE completion + /// in IntelliJ Rust doesn't work for that at the time of this writing. + pub fn enum_iter() -> impl ExactSizeIterator { + CompartmentKind::iter() + } + + /// Returns the compartment to which the given plug-in parameter index belongs. + pub fn by_plugin_param_index(plugin_param_index: PluginParamIndex) -> CompartmentKind { + Self::enum_iter() + .find(|c| c.plugin_param_range().contains(&plugin_param_index)) + .unwrap() + } + + /// Translates the given plug-in parameter index to a compartment-local index. + pub fn translate_plugin_param_index( + index: PluginParamIndex, + ) -> (CompartmentKind, CompartmentParamIndex) { + let compartment = Self::by_plugin_param_index(index); + (compartment, compartment.to_compartment_param_index(index)) + } + + pub fn to_plugin_param_index( + self, + compartment_param_index: CompartmentParamIndex, + ) -> PluginParamIndex { + PluginParamIndex::try_from(self.plugin_param_offset().get() + compartment_param_index.get()) + .unwrap() + } + + /// Returns the compartment-local parameter index corresponding to the given plug-in parameter + /// index. + pub fn to_compartment_param_index( + self, + plugin_param_index: PluginParamIndex, + ) -> CompartmentParamIndex { + CompartmentParamIndex::try_from(plugin_param_index.get() - self.plugin_param_offset().get()) + .unwrap() + } + + /// Returns the plug-in parameter range corresponding to this compartment. + pub fn plugin_param_range(self) -> RangeInclusive { + let offset = self.plugin_param_offset(); + offset..=(offset + (COMPARTMENT_PARAMETER_COUNT - 1)).unwrap() + } + + fn plugin_param_offset(self) -> PluginParamIndex { + let raw_offset = match self { + CompartmentKind::Controller => 100u32, + CompartmentKind::Main => 0u32, + }; + PluginParamIndex::try_from(raw_offset).unwrap() + } +} + +pub enum ExtendedSourceCharacter { + Normal(SourceCharacter), + VirtualContinuous, +} + +pub fn match_partially( + core: &mut MappingCore, + target: &VirtualTarget, + control_event: ControlEvent, +) -> Option { + // Determine resulting virtual control value in real-time processor. + // It's important to do that here. We need to know the result in order to + // return if there was actually a match of *real* non-virtual mappings. + // Unlike with REAPER targets, we also don't have threading issues here :) + // TODO-medium If we want to support fire after timeout and turbo for mappings with + // virtual targets one day, we need to poll this in real-time processor and OSC + // processing, too! + let res = core.mode.control_with_options( + control_event, + target, + (), + ModeControlOptions::default(), + // Performance control not relevant in virtual context. + None, + )?; + let transformed_control_value: Option = res.into(); + let transformed_control_value = transformed_control_value?; + core.time_of_last_control = Some(Instant::now()); + let res = VirtualSourceValue::new(target.control_element(), transformed_control_value); + Some(res) +} + +#[derive(PartialEq, Debug)] +pub(crate) enum ControlMode { + Disabled, + Controlling, + LearningSource { + /// Just passed through + allow_virtual_sources: bool, + osc_arg_index_hint: Option, + }, +} + +/// Supposed to be used to aggregate values of all resolved targets of one mapping into one single +/// value. At the moment we just take the maximum. +pub fn aggregate_target_values( + values: impl Iterator>, +) -> Option { + values.flatten().max() +} + +#[derive(Default)] +pub struct MappingControlResult { + /// `true` if target hit or almost hit but left untouched because it already has desired value. + /// `false` e.g. if source message filtered out (e.g. because of button filter) or no target. + pub at_least_one_target_was_reached: bool, + /// `true` if at least one target has been invoked already *with an effect*. + /// Can only be `true` if at least one target has been reached is also `true`. + pub at_least_one_target_caused_effect: bool, + /// In case the target doesn't support automatic feedback (even polling not enabled for it), + /// this should contain the target value determined at the occasion of hitting the target. + pub new_target_value: Option, + /// Even if not hit, this can contain a feedback value (if "Send feedback after control" on)! + pub feedback_value: Option, + pub hit_instruction: Option, + pub celebrate_success: bool, +} + +/// Not usable for mappings with virtual targets. +fn should_send_manual_feedback_due_to_target( + target: &ReaperTarget, + options: &ProcessorMappingOptions, + activation_state: &ActivationState, + unresolved_target: Option<&UnresolvedCompoundMappingTarget>, +) -> bool { + if target.supports_automatic_feedback() { + // The target value was changed and that triggered feedback. Therefore we don't + // need to send it here a second time (even if `send_feedback_after_control` is + // enabled). This happens in the majority of cases. + false + } else { + // The target value was changed but the target doesn't support feedback. What a virtual + // control mapping says shouldn't be relevant here because this is about the target + // supporting feedback, not about the controller needing the "Send feedback after control" + // workaround. Therefore we don't forward any "enforce" options. + feedback_is_effectively_on(options, activation_state, unresolved_target) + } +} + +fn feedback_is_effectively_on( + options: &ProcessorMappingOptions, + activation_state: &ActivationState, + unresolved_target: Option<&UnresolvedCompoundMappingTarget>, +) -> bool { + is_effectively_active(options, activation_state, unresolved_target) + && options.feedback_is_effectively_enabled() +} + +/// Returns `true` if the mapping itself and the target is active. +fn is_effectively_active( + options: &ProcessorMappingOptions, + activation_state: &ActivationState, + unresolved_target: Option<&UnresolvedCompoundMappingTarget>, +) -> bool { + options.persistent_processing_state.is_enabled + && activation_state.is_active() + && target_is_effectively_active(options, unresolved_target) +} + +fn target_is_effectively_active( + options: &ProcessorMappingOptions, + unresolved_target: Option<&UnresolvedCompoundMappingTarget>, +) -> bool { + if options.target_is_active { + return true; + } + if let Some(UnresolvedCompoundMappingTarget::Reaper(t)) = unresolved_target { + t.is_always_active() + } else { + false + } +} + +pub type OrderedMappingMap = NonCryptoIndexMap; +pub type OrderedMappingIdSet = NonCryptoIndexSet; + +#[derive(Clone, PartialEq, Debug)] +pub enum MessageCaptureResult { + Midi(MidiScanResult), + Osc(OscScanResult), + Keyboard(KeyMessage), + StreamDeck(StreamDeckScanResult), + RealearnParameter(RealearnParameterChangePayload), +} + +impl MessageCaptureResult { + /// Returns the captured source value. + /// + /// Used for source filtering, source virtualization, learning (ignoring sources). + pub fn message(&self) -> IncomingCompoundSourceValue { + use MessageCaptureResult::*; + match self { + Midi(res) => IncomingCompoundSourceValue::Midi(&res.value), + Osc(res) => IncomingCompoundSourceValue::Osc(&res.message), + Keyboard(res) => IncomingCompoundSourceValue::Key(*res), + StreamDeck(res) => IncomingCompoundSourceValue::StreamDeck(res.message), + RealearnParameter(payload) => IncomingCompoundSourceValue::RealearnParameter(*payload), + } + } + + /// For finding sessions matching a certain source. + pub fn to_input_descriptor(&self, ignore_midi_channel: bool) -> Option { + use MessageCaptureResult::*; + let res = match self { + Midi(r) => InputDescriptor::Midi { + device_id: r.dev_id?, + channel: if ignore_midi_channel { + None + } else { + r.value.channel() + }, + }, + Osc(r) => InputDescriptor::Osc { + device_id: r.dev_id?, + }, + Keyboard(_) => InputDescriptor::Keyboard, + StreamDeck(r) => InputDescriptor::StreamDeck { + device_id: r.dev_id?, + }, + RealearnParameter(_) => return None, + }; + Some(res) + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum IncomingCompoundSourceValue<'a> { + Midi(&'a MidiSourceValue<'a, RawShortMessage>), + Osc(&'a OscMessage), + Virtual(&'a VirtualSourceValue), + Key(KeyMessage), + StreamDeck(StreamDeckMessage), + RealearnParameter(RealearnParameterChangePayload), +} + +pub enum InputDescriptor { + Midi { + device_id: MidiInputDeviceId, + channel: Option, + }, + Osc { + device_id: OscDeviceId, + }, + Keyboard, + StreamDeck { + device_id: StreamDeckDeviceId, + }, +} + +#[derive(Copy, Clone)] +pub enum ControlOutcome { + Consumed, + Matched(T), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, derive_more::Display)] +pub enum ControlLogContext { + #[display(fmt = "normal control")] + Normal, + #[display(fmt = "polling")] + Polling, + #[display(fmt = "group navigation")] + GroupNavigation, + #[display(fmt = "real-time control")] + RealTime, + #[display(fmt = "direct control")] + Direct, + #[display(fmt = "group interaction")] + GroupInteraction, + #[display(fmt = "loading mapping snapshot")] + LoadingMappingSnapshot, +} + +#[derive(Copy, Clone, Debug)] +pub struct FeedbackLogEntry<'a> { + feedback_value: &'a FeedbackValue<'a>, +} + +impl Display for FeedbackLogEntry<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.feedback_value.fmt(f) + } +} + +pub trait SourceFeedbackLogger { + fn log(&self, entry: FeedbackLogEntry); +} + +pub struct NoopLogger; + +impl SourceFeedbackLogger for NoopLogger { + fn log(&self, entry: FeedbackLogEntry) { + let _ = entry; + } +} + +#[derive(Copy, Clone, Debug)] +pub struct ControlLogEntry { + /// The kind of thing that happened. + pub kind: ControlLogEntryKind, + /// The control value if there was one. + /// + /// `None` if the glue section didn't produce a final control value (usually because it filtered + /// the message out). + pub control_value: Option, + /// For multi-targets, this can be greater than 0. + pub target_index: usize, + /// Invocation counter. + /// + /// Increasing with every source match, increasing even when glue filtered the message out. + /// For multi-targets, this increases only once even when controlling multiple targets in one + /// go. + pub invocation_count: u32, + /// Empty if no error. + pub error: &'static str, +} + +impl Display for ControlLogEntry { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.kind.fmt(f)?; + if !self.error.is_empty() { + write!(f, ": {}", self.error)?; + } + write!( + f, + " [invocation = {}, target = {}", + self.invocation_count, + self.target_index + 1 + )?; + if let Some(v) = self.control_value { + write!(f, ", control value = {v}")?; + } + f.write_char(']')?; + Ok(()) + } +} + +#[allow(dead_code)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, derive_more::Display)] +pub enum ControlLogEntryKind { + #[display(fmt = "Control value ignored by glue")] + IgnoredByGlue, + #[display(fmt = "Left target untouched because it already has desired value")] + LeftTargetUntouched, + #[display(fmt = "Control value ignored by target")] + IgnoredByTarget, + #[display(fmt = "Hit target successfully")] + HitSuccessfully, + #[display(fmt = "Failed to hit target")] + HitFailed, + /// Target created a hit instruction (to be executed later). + #[display(fmt = "Created target hit instruction")] + CreatedHitInstruction, + /// Target created a hit instruction but it was discarded because there was another target + /// in that mapping whose hit instruction "won" (at the moment, we support only one hit + /// instruction for multi-targets). + #[display(fmt = "Discarded target hit instruction")] + DiscardedHitInstruction, + #[display(fmt = "Executed hit instruction successfully")] + ExecutedHitInstructionSuccessfully, + #[display(fmt = "Failed to execute hit instruction")] + FailedExecutingHitInstruction, +} diff --git a/plugin-reaper-realearn/main/src/domain/mapping_snapshot.rs b/plugin-reaper-realearn/main/src/domain/mapping_snapshot.rs new file mode 100644 index 0000000..3036158 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/mapping_snapshot.rs @@ -0,0 +1,145 @@ +use crate::domain::{MappingId, Tag, TagScope, VirtualMappingSnapshotIdForLoad}; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use base::{convert_to_identifier, SmallAsciiString}; +use helgoboss_learn::AbsoluteValue; +use std::str::FromStr; + +#[derive(Debug, Default)] +pub struct MappingSnapshotContainer { + snapshots: NonCryptoHashMap, + active_snapshot_id_by_tag: NonCryptoHashMap, +} + +impl MappingSnapshotContainer { + /// Creates the container. + pub fn new( + snapshots: NonCryptoHashMap, + active_snapshot_id_by_tag: NonCryptoHashMap, + ) -> Self { + Self { + snapshots, + active_snapshot_id_by_tag, + } + } + + /// Updates the contents of the given snapshot. + pub fn update_snapshot(&mut self, id: MappingSnapshotId, snapshot: MappingSnapshot) { + self.snapshots.insert(id, snapshot); + } + + /// Returns the last loaded snapshot ID for the given tags. + /// + /// If there's no record of a last loaded snapshot for any of the given tags, it returns `None`. + /// + /// If the last loaded snapshot IDs differ between the tags, it also returns `None`. + pub fn last_loaded_snapshot_id(&self, scope: &TagScope) -> Option { + let active_snapshot_ids: NonCryptoHashSet<_> = scope + .tags + .iter() + .map(|tag| self.active_snapshot_id_by_tag.get(tag)) + .collect(); + if active_snapshot_ids.len() > 1 { + // Active snapshots differ. + return None; + } + let single_active_snapshot_id = active_snapshot_ids.iter().next()?.cloned()?; + Some(single_active_snapshot_id) + } + + /// Marks the given snapshot as the active one for all tags in the given scope. + pub fn mark_snapshot_active( + &mut self, + tag_scope: &TagScope, + snapshot_id: &VirtualMappingSnapshotIdForLoad, + ) { + for tag in &tag_scope.tags { + match snapshot_id { + VirtualMappingSnapshotIdForLoad::Initial => { + self.active_snapshot_id_by_tag.remove(tag); + } + VirtualMappingSnapshotIdForLoad::ById(id) => { + self.active_snapshot_id_by_tag + .insert(tag.clone(), id.clone()); + } + } + } + } + + /// Returns `true` if for all tags in the given scope the given snapshot is the currently + /// active one. + pub fn snapshot_is_active( + &self, + tag_scope: &TagScope, + snapshot_id: &VirtualMappingSnapshotIdForLoad, + ) -> bool { + tag_scope.tags.iter().all(|tag| { + if let Some(active_snapshot_id) = self.active_snapshot_id_by_tag.get(tag) { + if let VirtualMappingSnapshotIdForLoad::ById(snapshot_id) = snapshot_id { + snapshot_id == active_snapshot_id + } else { + false + } + } else { + matches!(snapshot_id, VirtualMappingSnapshotIdForLoad::Initial) + } + }) + } + + pub fn active_snapshot_id_by_tag(&self) -> &NonCryptoHashMap { + &self.active_snapshot_id_by_tag + } + + /// Returns the snapshot contents associated with the given snapshot ID. + pub fn find_snapshot_by_id(&self, id: &MappingSnapshotId) -> Option<&MappingSnapshot> { + self.snapshots.get(id) + } + + /// Returns all snapshots in this container. + pub fn snapshots(&self) -> impl Iterator { + self.snapshots.iter() + } +} + +#[derive(Debug, Default)] +pub struct MappingSnapshot { + target_values: NonCryptoHashMap, +} + +impl MappingSnapshot { + pub fn new(target_values: NonCryptoHashMap) -> Self { + Self { target_values } + } + + pub fn find_target_value_by_mapping_id(&self, id: MappingId) -> Option { + self.target_values.get(&id).copied() + } + + pub fn target_values(&self) -> impl Iterator + '_ { + self.target_values + .iter() + .map(|(id, target_value)| (*id, *target_value)) + } +} + +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Hash, + derive_more::Display, + serde_with::SerializeDisplay, + serde_with::DeserializeFromStr, +)] +pub struct MappingSnapshotId(SmallAsciiString); + +impl FromStr for MappingSnapshotId { + type Err = &'static str; + + fn from_str(text: &str) -> Result { + let small_ascii_string = convert_to_identifier(text)?; + Ok(Self(small_ascii_string)) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/midi_clock_calculator.rs b/plugin-reaper-realearn/main/src/domain/midi_clock_calculator.rs new file mode 100644 index 0000000..8873290 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/midi_clock_calculator.rs @@ -0,0 +1,33 @@ +use crate::domain::ControlEventTimestamp; +use reaper_common_types::Bpm; +use simple_moving_average::{SumTreeSMA, SMA}; +use std::convert::TryInto; + +#[derive(Debug)] +pub struct MidiClockCalculator { + previous_timestamp: Option, + moving_avg_calculator: SumTreeSMA, +} + +impl Default for MidiClockCalculator { + fn default() -> Self { + Self { + previous_timestamp: None, + moving_avg_calculator: SumTreeSMA::new(), + } + } +} + +impl MidiClockCalculator { + pub fn feed(&mut self, timestamp: ControlEventTimestamp) -> Option { + let prev_timestamp = self.previous_timestamp.replace(timestamp)?; + let duration_since_last = timestamp - prev_timestamp; + let num_ticks_per_sec = 1.0 / duration_since_last.as_secs_f64(); + let num_beats_per_sec = num_ticks_per_sec / 24.0; + let new_bpm = num_beats_per_sec * 60.0; + self.moving_avg_calculator.add_sample(new_bpm); + let avg_bpm = self.moving_avg_calculator.get_average(); + let avg_bpm: Bpm = avg_bpm.try_into().ok()?; + Some(avg_bpm) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/midi_dev_management.rs b/plugin-reaper-realearn/main/src/domain/midi_dev_management.rs new file mode 100644 index 0000000..5528cb5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/midi_dev_management.rs @@ -0,0 +1,128 @@ +use reaper_high::Reaper; +use reaper_medium::{MidiInputDeviceId, MidiOutputDeviceId}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct MidiInDevsConfig { + midiins: u128, + midiins_nowarn: u128, +} + +impl MidiInDevsConfig { + pub const ALL_ENABLED: Self = Self { + midiins: u128::MAX, + midiins_nowarn: u128::MAX, + }; + + pub fn from_reaper() -> Self { + Self { + midiins: get_midi_dev_var("midiins"), + midiins_nowarn: get_midi_dev_var("midiins_nowarn"), + } + } + + pub fn apply_to_reaper(&self) { + set_midi_dev_var("midiins", self.midiins); + set_midi_dev_var("midiins_nowarn", self.midiins_nowarn); + } + + pub fn with_dev_enabled(&self, dev_id: MidiInputDeviceId) -> Self { + let index = dev_id.get(); + Self { + midiins: self.midiins | (1 << index), + midiins_nowarn: self.midiins_nowarn | (1 << index), + } + } + + pub fn to_ini_entries(self) -> impl Iterator { + to_ini_entries("midiins", self.midiins) + .chain(to_ini_entries("midiins_nowarn", self.midiins_nowarn)) + } +} + +fn to_ini_entries(name: &str, devs: u128) -> impl Iterator { + [ + ( + name.to_string(), + (devs & 0x00000000_00000000_00000000_FFFFFFFF) as u32, + ), + ( + format!("{name}_h"), + ((devs & 0x00000000_00000000_FFFFFFFF_00000000) >> 32) as u32, + ), + ( + format!("{name}_x"), + ((devs & 0x00000000_FFFFFFFF_00000000_00000000) >> 64) as u32, + ), + ( + format!("{name}_x_h"), + ((devs & 0xFFFFFFFF_00000000_00000000_00000000) >> 96) as u32, + ), + ] + .into_iter() +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct MidiOutDevsConfig { + midiouts: u128, + midiouts_nowarn: u128, + midiouts_noreset: u128, +} + +impl MidiOutDevsConfig { + pub fn from_reaper() -> Self { + Self { + midiouts: get_midi_dev_var("midiouts"), + midiouts_nowarn: get_midi_dev_var("midiouts_nowarn"), + midiouts_noreset: get_midi_dev_var("midiouts_noreset"), + } + } + + pub fn apply_to_reaper(&self) { + set_midi_dev_var("midiouts", self.midiouts); + set_midi_dev_var("midiouts_nowarn", self.midiouts_nowarn); + set_midi_dev_var("midiouts_noreset", self.midiouts_noreset); + } + + pub fn with_dev_enabled(&self, dev_id: MidiOutputDeviceId) -> Self { + let index = dev_id.get(); + Self { + midiouts: self.midiouts | (1 << index), + midiouts_nowarn: self.midiouts_nowarn | (1 << index), + midiouts_noreset: self.midiouts_noreset | (1 << index), + } + } + + pub fn to_ini_entries(self) -> impl Iterator { + to_ini_entries("midiouts", self.midiouts) + .chain(to_ini_entries("midiouts_nowarn", self.midiouts_nowarn)) + .chain(to_ini_entries("midiouts_noreset", self.midiouts_noreset)) + } +} + +fn get_midi_dev_var(name: &str) -> u128 { + let reaper = Reaper::get(); + match reaper.get_preference_ref::(name) { + Ok(v) => *v, + Err(_) => { + // Older REAPER versions supported only 64 MIDI devices + match reaper.get_preference_ref::(name) { + Ok(v) => *v as u128, + Err(_) => 0, + } + } + } +} + +fn set_midi_dev_var(name: &str, value: u128) { + let reaper = Reaper::get(); + match reaper.get_preference_ref::(name) { + Ok(v) => *v = value, + Err(_) => { + // Older REAPER versions supported only 64 MIDI devices + match reaper.get_preference_ref::(name) { + Ok(v) => *v = value as u64, + Err(_) => {} + } + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/midi_source.rs b/plugin-reaper-realearn/main/src/domain/midi_source.rs new file mode 100644 index 0000000..abd8423 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/midi_source.rs @@ -0,0 +1,44 @@ +use crate::base::CloneAsDefault; +use crate::domain::{AdditionalLuaMidiSourceScriptInput, FlexibleMidiSourceScript}; +use helgoboss_learn::{FeedbackValue, MidiSourceScript, MidiSourceScriptOutcome}; +use std::borrow::Cow; + +/// The helgoboss-learn MidiSource, integrated into ReaLearn. +/// +/// Now this needs some explanation: Why do we wrap the MIDI source script type with +/// `CloneAsDefault>`!? Because the script is compiled and therefore doesn't suit itself +/// to being cloned. But we need the MidiSource to be cloneable because we clone it whenever we +/// sync the mapping(s) from the main processor to the real-time processor. Fortunately, the +/// real-time processor doesn't use the compiled scripts anyway because those scripts are +/// responsible for feedback only. +/// +/// Using `Arc` sounds like a good solution at first but it means that deallocation of the compiled +/// script could be triggered *in the real-time thread*. Now, we have a custom global deallocator +/// for automatically deferring deallocation if we are in a real-time thread. **But!** We use +/// non-Rust script engines (EEL and Lua), so they are not aware of our global allocator ... and +/// that means we would still get a real-time deallocation :/ Yes, we could handle this by manually +/// sending the obsolete structs to a deallocation thread *before* the Rust wrappers around the +/// script engines are even dropped (as we did before), but go there if the real-time processor +/// doesn't even use the scripts. +/// +/// Introducing a custom method (not `clone`) would be quite much effort because we can't +/// derive its usage. +type ScriptType = CloneAsDefault>>; + +pub type MidiSource = helgoboss_learn::MidiSource; + +impl<'a> MidiSourceScript<'a> for ScriptType { + type AdditionalInput = AdditionalLuaMidiSourceScriptInput<'a>; + + fn execute( + &self, + input_value: FeedbackValue, + additional_input: Self::AdditionalInput, + ) -> Result> { + let script = self + .get() + .as_ref() + .ok_or(Cow::Borrowed("script was removed on clone"))?; + script.execute(input_value, additional_input) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/midi_source_scanner.rs b/plugin-reaper-realearn/main/src/domain/midi_source_scanner.rs new file mode 100644 index 0000000..ae20b40 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/midi_source_scanner.rs @@ -0,0 +1,573 @@ +use helgoboss_learn::{MidiSourceValue, RawMidiEvent, SourceCharacter}; +use helgoboss_midi::{ + Channel, ControlChange14BitMessageScanner, ControllerNumber, + PollingParameterNumberMessageScanner, RawShortMessage, ShortMessage, ShortMessageFactory, + StructuredShortMessage, U7, +}; +use reaper_medium::MidiInputDeviceId; +use std::cmp::Ordering; +use std::time::{Duration, Instant}; + +const MAX_CC_MSG_COUNT: usize = 10; +const MAX_CC_WAITING_TIME: Duration = Duration::from_millis(250); + +#[derive(Debug)] +pub struct MidiScanner { + // Scanners for more complex MIDI message types + nrpn_scanner: PollingParameterNumberMessageScanner, + cc_14_bit_scanner: ControlChange14BitMessageScanner, + state: State, + dev_id: Option, +} + +impl Default for MidiScanner { + fn default() -> Self { + Self { + nrpn_scanner: PollingParameterNumberMessageScanner::new(Duration::from_millis(1)), + cc_14_bit_scanner: Default::default(), + state: State::Initial, + dev_id: None, + } + } +} + +#[derive(Debug)] +enum State { + Initial, + WaitingForMoreCcMsgs(ControlChangeState), +} + +#[derive(Debug)] +struct ControlChangeState { + start_time: Instant, + channel: Channel, + controller_number: ControllerNumber, + msg_count: usize, + values: [U7; MAX_CC_MSG_COUNT], +} + +impl ControlChangeState { + fn new(channel: Channel, controller_number: ControllerNumber) -> ControlChangeState { + ControlChangeState { + start_time: Instant::now(), + channel, + controller_number, + msg_count: 0, + values: [U7::MIN; MAX_CC_MSG_COUNT], + } + } + + fn add_value(&mut self, value: U7) { + assert!(self.msg_count < MAX_CC_MSG_COUNT); + self.values[self.msg_count] = value; + self.msg_count += 1; + } + + fn time_to_guess(&self) -> bool { + self.msg_count >= MAX_CC_MSG_COUNT || Instant::now() - self.start_time > MAX_CC_WAITING_TIME + } + + fn matches(&self, channel: Channel, controller_number: ControllerNumber) -> bool { + channel == self.channel && controller_number == self.controller_number + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct MidiScanResult { + pub value: MidiSourceValue<'static, RawShortMessage>, + pub dev_id: Option, + pub character: Option, +} + +impl MidiScanResult { + pub fn new( + value: MidiSourceValue<'static, RawShortMessage>, + dev_id: Option, + character: Option, + ) -> Self { + Self { + value, + dev_id, + character, + } + } + + /// This allocates! + pub fn try_from_bytes( + bytes: &[u8], + dev_id: Option, + ) -> Result { + let raw_event = RawMidiEvent::try_from_slice(0, bytes)?; + // This allocates! + let res = MidiScanResult { + dev_id, + value: { + // We don't use this as feedback value. + let feedback_address_info = None; + MidiSourceValue::single_raw(feedback_address_info, raw_event) + }, + character: None, + }; + Ok(res) + } +} + +impl MidiScanner { + pub fn feed_short( + &mut self, + msg: RawShortMessage, + dev_id: Option, + ) -> Option { + if let Some(nrpn_msg) = self.nrpn_scanner.feed(&msg)[0] { + let res = self.feed( + MidiSourceValue::::ParameterNumber(nrpn_msg), + dev_id, + ); + if res.is_some() { + return res; + } + } + if let Some(cc14_msg) = self.cc_14_bit_scanner.feed(&msg) { + let res = self.feed( + MidiSourceValue::::ControlChange14Bit(cc14_msg), + dev_id, + ); + if res.is_some() { + return res; + } + } + self.feed(MidiSourceValue::Plain(msg), dev_id) + } + + fn feed( + &mut self, + source_value: MidiSourceValue, + dev_id: Option, + ) -> Option { + // First encountered device ID rules. + if self.dev_id.is_none() { + self.dev_id = dev_id; + } + match &mut self.state { + State::Initial => { + if let MidiSourceValue::Plain(msg) = source_value { + if let StructuredShortMessage::ControlChange { + channel, + controller_number, + control_value, + } = msg.to_structured() + { + let mut cc_state = ControlChangeState::new(channel, controller_number); + cc_state.add_value(control_value); + self.state = State::WaitingForMoreCcMsgs(cc_state); + None + } else { + Some(MidiScanResult::new( + source_value.try_into_owned().ok()?, + dev_id, + None, + )) + } + } else { + Some(MidiScanResult::new( + source_value.try_into_owned().ok()?, + dev_id, + None, + )) + } + } + State::WaitingForMoreCcMsgs(cc_state) => { + if let MidiSourceValue::Plain(msg) = source_value { + if let StructuredShortMessage::ControlChange { + channel, + controller_number, + control_value, + } = msg.to_structured() + { + if cc_state.matches(channel, controller_number) { + cc_state.add_value(control_value); + } + } + self.guess_or_not() + } else { + // Looks like in the meantime, the composite scanners ((N)RPN or + // 14-bit CC) have figured out that the combination is a composite + // message. This fixes https://github.com/helgoboss/helgobox/issues/95. + let res = + MidiScanResult::new(source_value.try_into_owned().ok()?, dev_id, None); + self.reset(); + Some(res) + } + } + } + } + + pub fn poll(&mut self) -> Option { + for ch in 0..16 { + if let Some(nrpn_msg) = self.nrpn_scanner.poll(Channel::new(ch)) { + let source_value = MidiSourceValue::::ParameterNumber(nrpn_msg); + let res = self.feed(source_value, None); + if res.is_some() { + return res; + } + } + } + self.guess_or_not() + } + + pub fn reset(&mut self) { + self.nrpn_scanner.reset(); + self.cc_14_bit_scanner.reset(); + self.state = State::Initial; + } + + fn guess_or_not(&mut self) -> Option { + if let State::WaitingForMoreCcMsgs(cc_state) = &self.state { + if cc_state.time_to_guess() { + let guessed_result = guess(cc_state, self.dev_id); + self.reset(); + Some(guessed_result) + } else { + None + } + } else { + None + } + } +} + +fn guess(cc_state: &ControlChangeState, dev_id: Option) -> MidiScanResult { + let first_cc_msg = RawShortMessage::control_change( + cc_state.channel, + cc_state.controller_number, + cc_state.values[0], + ); + MidiScanResult { + value: MidiSourceValue::Plain(first_cc_msg), + dev_id, + character: Some(guess_custom_character( + &cc_state.values[0..cc_state.msg_count - 1], + )), + } +} + +fn contains_direction_change(values: &[U7]) -> bool { + #[derive(Copy, Clone, PartialEq)] + enum Direction { + Clockwise, + CounterClockwise, + } + fn determine_direction(a: U7, b: U7) -> Option { + use Direction::*; + use Ordering::*; + match b.cmp(&a) { + Greater => Some(Clockwise), + Less => Some(CounterClockwise), + Equal => None, + } + } + let mut direction_so_far: Option = None; + for i in 1..values.len() { + let new_direction = determine_direction(values[i - 1], values[i]); + if new_direction.is_none() { + continue; + } + if direction_so_far.is_none() { + direction_so_far = new_direction; + continue; + } + if new_direction != direction_so_far { + return true; + } + } + false +} + +fn contains_consecutive_duplicates(values: &[U7]) -> bool { + for i in 1..values.len() { + if values[i] == values[i - 1] { + return true; + } + } + false +} + +fn guess_custom_character(values: &[U7]) -> SourceCharacter { + use SourceCharacter::*; + // We don't just interpret 127 or 100 as button because we consider typical keyboard keys also + // as buttons. They can be velocity-sensitive and therefore transmit any value. + #[allow(clippy::if_same_then_else)] + if values.len() == 1 { + // Only one message received. Looks like a button has been pressed and not released. + MomentaryButton + } else if values.len() == 2 && values[1] == U7::MIN { + // Two messages received and second message has value 0. Looks like a button has been + // pressed and released. + MomentaryButton + } else { + // Multiple messages received. Button character is ruled out already. Check continuity. + if contains_direction_change(values) { + // A direction change means it's very likely a (relative) encoder. + guess_encoder_type(values) + } else if contains_consecutive_duplicates(values) { + if values.contains(&U7::MIN) { + // For relative, zero means "don't do anything" - which is a bit pointless + // to send. So it's probably an encoder which is + // configured to transmit absolute values hitting + // the lower boundary. + RangeElement + } else if values.contains(&U7::MAX) { + // Here we rely on the fact that the user should turn clock-wise. So it + // can't be relative type 1 because 127 means + // decrement. It's also unlikely to be the + // other relative types because this would happen with extreme acceleration + // only. So it's probably an encoder which is configured to transmit + // absolute values hitting the upper boundary. + RangeElement + } else { + guess_encoder_type(values) + } + } else { + // Was continuous without duplicates until now so it's probably a knob/fader. + SourceCharacter::RangeElement + } + } +} + +/// Unfortunately, encoder type 3 clockwise movement is not really distinguishable from 1 or 2. +/// So we won't support its detection. +fn guess_encoder_type(values: &[U7]) -> SourceCharacter { + use SourceCharacter::*; + match values[0].get() { + 1..=7 | 121..=127 => Encoder1, + 57..=71 => Encoder2, + // The remaining values are supported but not so typical for encoders because they only + // happen at high accelerations. + _ => RangeElement, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod scanning { + use super::*; + use helgoboss_midi::test_util::{channel, control_change, nrpn_14_bit, u14}; + use helgoboss_midi::{ParameterNumberMessage, ParameterNumberMessageScanner}; + + #[test] + fn scan_nrpn() { + // Given + let mut source_scanner = MidiScanner::default(); + let mut nrpn_scanner = ParameterNumberMessageScanner::new(); + // When + use MidiSourceValue::{ParameterNumber, Plain}; + // Message 1 + let msg_1 = control_change(1, 99, 0); + let nrpn_1 = nrpn_scanner.feed(&msg_1); + assert_eq!(nrpn_1, None); + let source_1 = source_scanner.feed(Plain(msg_1), None); + // Message 2 + let msg_2 = control_change(1, 98, 99); + let nrpn_2 = nrpn_scanner.feed(&msg_2); + let source_2 = source_scanner.feed(Plain(msg_1), None); + assert_eq!(nrpn_2, None); + // Message 3 + let msg_3 = control_change(1, 38, 3); + let nrpn_3 = nrpn_scanner.feed(&msg_3); + assert_eq!(nrpn_3, None); + let source_3 = source_scanner.feed(Plain(msg_3), None); + // Message 4 + let msg_4 = control_change(1, 6, 2); + let nrpn_4 = nrpn_scanner.feed(&msg_4).unwrap(); + assert_eq!( + nrpn_4, + ParameterNumberMessage::non_registered_14_bit(channel(1), u14(99), u14(259)) + ); + let source_4_nrpn = source_scanner.feed(ParameterNumber(nrpn_4), None); + let source_4_short = source_scanner.feed(Plain(msg_4), None); + // Then + // Even our source scanner is already waiting for more CC messages with the same number, + // a suddenly arriving (N)RPN message should take precedence! Because our real-time + // processor constantly scans for (N)RPN, it would detect at some point that this looks + // like a valid (N)RPN message. This needs to happen *before* the 250 millis + // MAX_CC_WAITING_TIME have expired. In practice this is always the case because there + // should never be much delay between the single messages making up one (N)RPN message. + assert_eq!(source_1, None); + assert_eq!(source_2, None); + assert_eq!(source_3, None); + assert_eq!( + source_4_nrpn.unwrap(), + MidiScanResult { + value: MidiSourceValue::ParameterNumber(nrpn_14_bit(1, 99, 259)), + dev_id: None, + character: None + } + ); + assert_eq!(source_4_short, None); + } + } + + mod source_character_guessing { + use super::*; + use helgoboss_midi::test_util::u7; + use SourceCharacter::*; + + #[test] + fn typical_range() { + assert_eq!(guess(&[40, 41, 42, 43, 44]), RangeElement); + } + + #[test] + fn typical_range_counter_clockwise() { + assert_eq!(guess(&[44, 43, 42, 41, 40]), RangeElement); + } + + #[test] + fn typical_trigger_button() { + assert_eq!(guess(&[100]), MomentaryButton); + assert_eq!(guess(&[127]), MomentaryButton); + } + + #[test] + fn typical_switch_button() { + assert_eq!(guess(&[100, 0]), MomentaryButton); + assert_eq!(guess(&[127, 0]), MomentaryButton); + } + + #[test] + fn typical_encoder_1() { + assert_eq!(guess(&[1, 1, 1, 1, 1]), Encoder1); + } + + #[test] + fn typical_encoder_2() { + assert_eq!(guess(&[65, 65, 65, 65, 65]), Encoder2); + } + + #[test] + fn typical_encoder_2_counter_clockwise() { + assert_eq!(guess(&[63, 63, 63, 63, 63]), Encoder2); + } + + #[test] + fn velocity_sensitive_trigger_button() { + assert_eq!(guess(&[79]), MomentaryButton); + assert_eq!(guess(&[10]), MomentaryButton); + } + + #[test] + fn velocity_sensitive_switch_button() { + assert_eq!(guess(&[79, 0]), MomentaryButton); + assert_eq!(guess(&[10, 0]), MomentaryButton); + } + + #[test] + fn range_with_gaps() { + assert_eq!(guess(&[40, 42, 43, 46]), RangeElement); + } + + #[test] + fn range_with_gaps_counter_clockwise() { + assert_eq!(guess(&[44, 41, 40, 37, 35]), RangeElement); + } + + #[test] + fn very_lower_range() { + assert_eq!(guess(&[0, 1, 2, 3]), RangeElement); + } + + #[test] + fn lower_range() { + assert_eq!(guess(&[1, 2, 3, 4]), RangeElement); + } + + #[test] + fn very_upper_range_counter_clockwise() { + assert_eq!(guess(&[127, 126, 125, 124]), RangeElement); + } + + #[test] + fn upper_range_counter_clockwise() { + assert_eq!(guess(&[126, 125, 124, 123]), RangeElement); + } + + #[test] + fn encoder_1_with_acceleration() { + assert_eq!(guess(&[1, 2, 2, 1, 1]), Encoder1); + } + + #[test] + fn encoder_1_with_acceleration_counter_clockwise() { + assert_eq!(guess(&[127, 126, 126, 127, 127]), Encoder1); + } + + #[test] + fn encoder_1_with_more_acceleration() { + assert_eq!(guess(&[1, 2, 5, 5, 2]), Encoder1); + } + + #[test] + fn encoder_1_with_more_acceleration_counter_clockwise() { + assert_eq!(guess(&[127, 126, 122, 122, 126]), Encoder1); + } + + #[test] + fn encoder_2_with_acceleration() { + assert_eq!(guess(&[65, 66, 66, 65, 65]), Encoder2); + } + + #[test] + fn encoder_2_with_acceleration_counter_clockwise() { + assert_eq!(guess(&[63, 62, 62, 63, 63]), Encoder2); + } + + #[test] + fn encoder_2_with_more_acceleration() { + assert_eq!(guess(&[65, 66, 68, 68, 66]), Encoder2); + } + + #[test] + fn encoder_2_with_more_acceleration_counter_clockwise() { + assert_eq!(guess(&[63, 62, 59, 59, 62]), Encoder2); + } + + #[test] + fn absolute_encoder_hitting_upper_boundary() { + assert_eq!(guess(&[127, 127, 127, 127, 127]), RangeElement); + assert_eq!(guess(&[125, 126, 127, 127, 127]), RangeElement); + } + + #[test] + fn absolute_encoder_hitting_lower_boundary_counter_clockwise() { + assert_eq!(guess(&[0, 0, 0, 0, 0]), RangeElement); + assert_eq!(guess(&[2, 1, 0, 0, 0]), RangeElement); + } + + #[test] + fn lower_range_with_duplicate_elements() { + assert_eq!(guess(&[0, 0, 1, 1, 2, 2]), RangeElement); + } + + #[test] + fn lower_range_with_duplicate_elements_counter_clockwise() { + assert_eq!(guess(&[2, 2, 1, 1, 0, 0]), RangeElement); + } + + #[test] + fn neutral_zone_range_with_duplicate_elements() { + assert_eq!(guess(&[37, 37, 37, 38, 38, 38, 39, 39]), RangeElement); + } + + #[test] + fn neutral_zone_range_with_duplicate_elements_counter_clockwise() { + assert_eq!(guess(&[100, 100, 100, 99, 99, 99, 98, 98]), RangeElement); + } + + fn guess(values: &[u8]) -> SourceCharacter { + let u7_values: Vec<_> = values.iter().map(|v| u7(*v)).collect(); + guess_custom_character(&u7_values) + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/midi_transformation_container.rs b/plugin-reaper-realearn/main/src/domain/midi_transformation_container.rs new file mode 100644 index 0000000..2447cd0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/midi_transformation_container.rs @@ -0,0 +1,71 @@ +use helgoboss_learn::RawMidiEvent; +use reaper_common_types::Hz; +use reaper_medium::MidiInputDeviceId; + +#[derive(Debug)] +pub struct MidiTransformationContainer { + /// Emptied right after reading the input buffer of a device. + same_device_events: Vec, + /// Emptied later, after reading the input buffers of **all** devices (should have a larger capacity). + other_device_events: Vec, + /// Always updated with the current device sample rate. + current_device_sample_rate: Hz, +} + +#[derive(Debug)] +pub struct DevQualifiedRawMidiEvent { + pub input_device_id: MidiInputDeviceId, + pub event: RawMidiEvent, +} + +impl DevQualifiedRawMidiEvent { + fn new(input_device_id: MidiInputDeviceId, event: RawMidiEvent) -> Self { + Self { + input_device_id, + event, + } + } +} + +impl Default for MidiTransformationContainer { + fn default() -> Self { + Self::new() + } +} + +impl MidiTransformationContainer { + pub fn new() -> Self { + Self { + same_device_events: Vec::with_capacity(100), + other_device_events: Vec::with_capacity(900), + current_device_sample_rate: Hz::default(), + } + } + + pub fn prepare(&mut self, device_sample_rate: Hz) { + self.current_device_sample_rate = device_sample_rate; + } + + pub fn current_device_sample_rate(&self) -> Hz { + self.current_device_sample_rate + } + + pub fn push(&mut self, device: Option, event: RawMidiEvent) { + if let Some(dev) = device { + self.other_device_events + .push(DevQualifiedRawMidiEvent::new(dev, event)); + } else { + self.same_device_events.push(event); + } + } + + pub fn drain_same_device_events(&mut self) -> impl Iterator + '_ { + self.same_device_events.drain(..) + } + + pub fn drain_other_device_events( + &mut self, + ) -> impl Iterator + '_ { + self.other_device_events.drain(..) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/midi_types.rs b/plugin-reaper-realearn/main/src/domain/midi_types.rs new file mode 100644 index 0000000..ec17086 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/midi_types.rs @@ -0,0 +1,56 @@ +use reaper_common_types::DurationInSeconds; +use reaper_medium::{Hz, MIDI_INPUT_FRAME_RATE}; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct MidiEvent { + offset: SampleOffset, + payload: T, +} + +impl MidiEvent { + pub fn without_offset(msg: T) -> Self { + Self::new(SampleOffset::ZERO, msg) + } + + pub fn new(offset: SampleOffset, payload: T) -> Self { + Self { offset, payload } + } + + pub fn offset(&self) -> SampleOffset { + self.offset + } + + pub fn payload(&self) -> T { + self.payload + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct SampleOffset(u64); + +impl SampleOffset { + pub const ZERO: SampleOffset = SampleOffset(0); + + pub fn from_midi_input_frame_offset(frame_offset: u32, sample_rate: Hz) -> Self { + let offset_in_secs = frame_offset as f64 / MIDI_INPUT_FRAME_RATE.get(); + let offset_in_samples = (offset_in_secs * sample_rate.get()).round() as u64; + SampleOffset(offset_in_samples) + } + + pub fn to_midi_input_frame_offset(self, sample_rate: Hz) -> u32 { + let offset_in_secs = self.0 as f64 / sample_rate.get(); + (offset_in_secs * MIDI_INPUT_FRAME_RATE.get()).round() as u32 + } + + pub fn new(value: u64) -> Self { + SampleOffset(value) + } + + pub fn get(self) -> u64 { + self.0 + } + + pub fn to_seconds(self, sample_rate: Hz) -> DurationInSeconds { + DurationInSeconds::new_panic(self.0 as f64 / sample_rate.get()) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/midi_util.rs b/plugin-reaper-realearn/main/src/domain/midi_util.rs new file mode 100644 index 0000000..c04b858 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/midi_util.rs @@ -0,0 +1,43 @@ +use crate::domain::IncomingMidiMessage; +use helgoboss_midi::{ShortMessage, ShortMessageType}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum MidiMessageClassification { + Normal, + Ignored, + Timing, +} + +pub fn classify_midi_message(msg: IncomingMidiMessage) -> MidiMessageClassification { + match msg { + IncomingMidiMessage::SysEx(_) => MidiMessageClassification::Normal, + IncomingMidiMessage::Short(msg) => { + use ShortMessageType::*; + match msg.r#type() { + NoteOff + | NoteOn + | PolyphonicKeyPressure + | ControlChange + | ProgramChange + | ChannelPressure + | PitchBendChange + | Start + | Continue + | Stop => MidiMessageClassification::Normal, + SystemExclusiveStart + | TimeCodeQuarterFrame + | SongPositionPointer + | SongSelect + | SystemCommonUndefined1 + | SystemCommonUndefined2 + | TuneRequest + | SystemExclusiveEnd + | SystemRealTimeUndefined1 + | SystemRealTimeUndefined2 + | ActiveSensing + | SystemReset => MidiMessageClassification::Ignored, + TimingClock => MidiMessageClassification::Timing, + } + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/mod.rs b/plugin-reaper-realearn/main/src/domain/mod.rs new file mode 100644 index 0000000..38949e9 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/mod.rs @@ -0,0 +1,182 @@ +mod real_time_processor; +pub use real_time_processor::*; + +mod main_processor; +pub use main_processor::*; + +mod mapping; +pub use mapping::*; + +mod control_surface; +pub use control_surface::*; + +mod feedback_collector; +pub use feedback_collector::*; + +mod audio_hook; +pub use audio_hook::*; + +mod mode; +pub use mode::*; + +mod source; +pub use source::*; + +mod midi_source; +pub use midi_source::*; + +mod eel_transformation; +pub use eel_transformation::*; + +mod eel_midi_source_script; +pub use eel_midi_source_script::*; + +mod lua_script_commons; +pub use lua_script_commons::*; + +mod lua_midi_source_script; +pub use lua_midi_source_script::*; + +mod lua_feedback_script; +pub use lua_feedback_script::*; + +mod flexible_midi_source_script; +pub use flexible_midi_source_script::*; + +mod realearn_target; +pub use realearn_target::*; + +mod reaper_target; +pub use reaper_target::*; + +mod unresolved_reaper_target; +pub use unresolved_reaper_target::*; + +mod processor_context; +pub use processor_context::*; + +mod r#virtual; +pub use r#virtual::*; + +mod midi_util; +pub use midi_util::*; + +mod midi_source_scanner; +pub use midi_source_scanner::*; + +mod midi_transformation_container; +pub use midi_transformation_container::*; + +mod midi_clock_calculator; +pub use midi_clock_calculator::*; + +mod conditional_activation; +pub use conditional_activation::*; + +mod eventing; +pub use eventing::*; + +pub mod ui_util; + +mod realearn_target_context; +pub use realearn_target_context::*; + +mod realearn_source_context; +pub use realearn_source_context::*; + +mod backbone; +pub use backbone::*; + +mod unit; +pub use unit::*; + +mod osc; +pub use osc::*; + +mod exclusivity; +pub use exclusivity::*; + +mod io; +pub use io::*; + +mod targets; +pub use targets::*; + +mod group; +pub use group::*; + +mod midi_types; +pub use midi_types::*; + +mod reaper_source; +pub use reaper_source::*; + +mod key_source; +pub use key_source::*; + +mod stream_deck_device; +pub use stream_deck_device::*; + +mod stream_deck_source; +pub use stream_deck_source::*; + +mod device_change_detector; +pub use device_change_detector::*; + +mod reaper_config_change_detector; +pub use reaper_config_change_detector::*; + +mod monitoring_fx_chain_change_detector; +pub use monitoring_fx_chain_change_detector::*; + +mod tag; +pub use tag::*; + +mod mapping_snapshot; +pub use mapping_snapshot::*; + +mod organization; +pub use organization::*; + +mod props; +pub use props::*; + +mod accelerator; +pub use accelerator::*; + +mod parameter; +pub use parameter::*; + +mod parameter_manager; +pub use parameter_manager::*; + +mod control_event; +pub use control_event::*; + +mod lua_support; +pub use lua_support::*; + +mod lua_module_container; +pub use lua_module_container::*; + +mod internal_info_event; +pub use internal_info_event::*; + +mod instance; +pub use instance::*; + +mod real_time_instance; +pub use real_time_instance::*; + +mod midi_dev_management; +pub use midi_dev_management::*; + +#[cfg(feature = "playtime")] +mod playtime_util; + +mod hex; +pub use hex::*; + +mod global_audio_state; + +pub use global_audio_state::*; diff --git a/plugin-reaper-realearn/main/src/domain/mode.rs b/plugin-reaper-realearn/main/src/domain/mode.rs new file mode 100644 index 0000000..756c4c0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/mode.rs @@ -0,0 +1,39 @@ +use crate::base::CloneAsDefault; +use crate::domain::{ + AdditionalLuaFeedbackScriptInput, ControlEventTimestamp, EelTransformation, LuaFeedbackScript, +}; +use base::hash_util::NonCryptoHashSet; +use helgoboss_learn::{FeedbackScript, FeedbackScriptInput, FeedbackScriptOutput, ModeContext}; +use std::borrow::Cow; +use std::error::Error; + +pub type RealearnModeContext<'a> = ModeContext>; + +/// See [`crate::domain::MidiSource`] for an explanation of the feedback script wrapping. +type FeedbackScriptType = CloneAsDefault>>; + +pub type Mode = helgoboss_learn::Mode; + +impl FeedbackScriptType { + fn get_script(&self) -> Result<&LuaFeedbackScript<'static>, Cow<'static, str>> { + self.get() + .as_ref() + .ok_or(Cow::Borrowed("script was removed on clone")) + } +} + +impl<'a> FeedbackScript<'a> for FeedbackScriptType { + type AdditionalInput = AdditionalLuaFeedbackScriptInput<'a>; + + fn feedback( + &self, + input: FeedbackScriptInput, + additional_input: Self::AdditionalInput, + ) -> Result> { + self.get_script()?.feedback(input, additional_input) + } + + fn used_props(&self) -> Result, Box> { + self.get_script()?.used_props() + } +} diff --git a/plugin-reaper-realearn/main/src/domain/monitoring_fx_chain_change_detector.rs b/plugin-reaper-realearn/main/src/domain/monitoring_fx_chain_change_detector.rs new file mode 100644 index 0000000..18f86bc --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/monitoring_fx_chain_change_detector.rs @@ -0,0 +1,143 @@ +use base::hash_util::NonCryptoHashMap; +use either::Either; +use reaper_high::{ + ChangeEvent, Fx, FxAddedEvent, FxClosedEvent, FxEnabledChangedEvent, FxOpenedEvent, + FxRemovedEvent, FxReorderedEvent, Guid, Reaper, +}; +use std::iter; + +/// It's a known fact that REAPER doesn't inform about changes on the monitoring FX chain via +/// control surface callback methods. +/// +/// There are various ways we handle this: +/// +/// - **FX parameter value changes:** By polling (only mapped parameters though, because iterating +/// over all parameters on each main loop cycle could be very resource-consuming). +/// - **FX focused:** Seems to be detected correctly already. +/// - **FX on/off, FX added/removed/reordered, FX closed/open:** We do that here by polling all +/// monitoring FX instances on each main loop cycle. +/// - **FX preset changed:** We don't do that because it takes quite a long time compared to all the +/// other checks (checked with REAPER 6.56). +#[derive(Debug, Default)] +pub struct MonitoringFxChainChangeDetector { + items: NonCryptoHashMap, +} + +#[derive(Debug)] +struct Item { + fx: Fx, + index: u32, + enabled: bool, + open: bool, +} + +impl MonitoringFxChainChangeDetector { + pub fn poll_for_changes(&mut self) -> Vec { + let new_items = gather_monitoring_fxs(); + let change_events = diff(&self.items, &new_items); + self.items = new_items; + change_events + } +} + +fn diff( + previous_items: &NonCryptoHashMap, + next_items: &NonCryptoHashMap, +) -> Vec { + // Removed + let mut at_least_one_removed = false; + let removed = previous_items.iter().filter_map(|(prev_guid, prev_item)| { + if next_items.contains_key(prev_guid) { + None + } else { + at_least_one_removed = true; + let event = FxRemovedEvent { + fx: prev_item.fx.clone(), + }; + Some(ChangeEvent::FxRemoved(event)) + } + }); + // Added + let mut at_least_one_added = false; + let added = next_items.iter().filter_map(|(next_guid, next_item)| { + if previous_items.contains_key(next_guid) { + None + } else { + at_least_one_added = true; + let event = FxAddedEvent { + fx: next_item.fx.clone(), + }; + Some(ChangeEvent::FxAdded(event)) + } + }); + // Reordered + let is_reordered = previous_items.iter().any(|(prev_guid, prev_item)| { + if let Some(next_item) = next_items.get(prev_guid) { + next_item.index != prev_item.index + } else { + false + } + }); + let reordered = if is_reordered { + let event = FxReorderedEvent { + track: Reaper::get() + .current_project() + .master_track() + .expect("master track of current project must exist"), + }; + Either::Left(iter::once(ChangeEvent::FxReordered(event))) + } else { + Either::Right(iter::empty()) + }; + // Changed + let changed = previous_items.iter().flat_map(|(prev_guid, prev_item)| { + if let Some(next_item) = next_items.get(prev_guid) { + let opened_closed = if next_item.open != prev_item.open { + let fx = next_item.fx.clone(); + let event = if next_item.open { + ChangeEvent::FxOpened(FxOpenedEvent { fx }) + } else { + ChangeEvent::FxClosed(FxClosedEvent { fx }) + }; + Some(event) + } else { + None + }; + let enabled = if next_item.enabled != prev_item.enabled { + Some(ChangeEvent::FxEnabledChanged(FxEnabledChangedEvent { + fx: next_item.fx.clone(), + new_value: next_item.enabled, + })) + } else { + None + }; + Either::Left(opened_closed.into_iter().chain(enabled)) + } else { + Either::Right(iter::empty()) + } + }); + // Combined + removed + .chain(added) + .chain(reordered) + .chain(changed) + .collect() +} + +fn gather_monitoring_fxs() -> NonCryptoHashMap { + Reaper::get() + .monitoring_fx_chain() + .fxs() + .enumerate() + .map(|(i, fx)| { + let key = fx.guid().expect("monitoring FX no GUID"); + let value = Item { + index: i as u32, + enabled: fx.is_enabled(), + open: fx.window_is_open(), + fx, + }; + (key, value) + }) + .collect() +} diff --git a/plugin-reaper-realearn/main/src/domain/organization.rs b/plugin-reaper-realearn/main/src/domain/organization.rs new file mode 100644 index 0000000..0fdc9cc --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/organization.rs @@ -0,0 +1,68 @@ +use crate::domain::{Exclusivity, Tag}; +use base::hash_util::NonCryptoHashSet; +use std::hash::Hash; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TagScope { + pub tags: NonCryptoHashSet, +} + +impl TagScope { + pub fn determine_enable_disable_change( + &self, + exclusivity: Exclusivity, + tags: &[Tag], + is_enable: bool, + ) -> Option { + use Exclusivity::*; + if exclusivity == Exclusive || (exclusivity == ExclusiveOnOnly && is_enable) { + // Exclusive + if self.has_tags() { + // Set mappings that match the scope tags and unset all others as long as they + // have tags! + if tags.is_empty() { + None + } else { + Some(has_any_of(&self.tags, tags)) + } + } else { + // Scope doesn't define any tags. Unset *all* mappings as long as + // they have tags. + if tags.is_empty() { + None + } else { + Some(false) + } + } + } else { + // Non-exclusive + if !self.has_tags() || has_any_of(&self.tags, tags) { + // Non-exclusive, so we just add to or remove from mappings that are + // currently active (= relative). + Some(true) + } else { + // Don't touch mappings that don't match the tags. + None + } + } + } + + pub fn any_tag_matches(&self, other_tags: &[Tag]) -> bool { + has_any_of(&self.tags, other_tags) + } + + pub fn has_tags(&self) -> bool { + !self.tags.is_empty() + } + + pub fn overlaps_with(&self, tag_scope: &TagScope) -> bool { + has_any_of(&self.tags, &tag_scope.tags) + } +} + +fn has_any_of<'a, T: 'a + Eq + Hash>( + self_tags: &NonCryptoHashSet, + other_tags: impl IntoIterator, +) -> bool { + other_tags.into_iter().any(|t| self_tags.contains(t)) +} diff --git a/plugin-reaper-realearn/main/src/domain/osc.rs b/plugin-reaper-realearn/main/src/domain/osc.rs new file mode 100644 index 0000000..07f7c5b --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/osc.rs @@ -0,0 +1,304 @@ +use crossbeam_channel::Receiver; +use derive_more::Display; +use rosc::{OscBundle, OscMessage, OscPacket}; +use serde::{Deserialize, Serialize}; + +use std::error::Error; +use std::io; +use std::net::{SocketAddrV4, UdpSocket}; + +use anyhow::Context; +use core::mem; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::JoinHandle; +use std::time::Duration; +use tracing::{trace, warn}; +use uuid::Uuid; + +const MAX_INCOMING_PACKET_SIZE: usize = 10_000; +const OSC_OUTGOING_BULK_SIZE: usize = 16; + +pub struct OscFeedbackTask { + dev_id: OscDeviceId, + msg: OscMessage, +} + +impl OscFeedbackTask { + pub fn new(dev_id: OscDeviceId, msg: OscMessage) -> Self { + Self { dev_id, msg } + } +} + +#[derive(Debug)] +pub struct OscFeedbackProcessor { + state: State, +} + +#[derive(Debug)] +enum State { + Stopped(StoppedState), + Starting, + Running(RunningState), + Stopping, +} + +#[derive(Debug)] +struct StoppedState { + task_receiver: Receiver, +} + +#[derive(Debug)] +struct RunningState { + request_stop: Arc, + join_handle: JoinHandle, +} + +impl OscFeedbackProcessor { + pub fn new(task_receiver: Receiver) -> Self { + Self { + state: State::Stopped(StoppedState { task_receiver }), + } + } + + pub fn start(&mut self, osc_output_devices: Vec) { + if osc_output_devices.is_empty() || !matches!(&self.state, State::Stopped(_)) { + return; + } + let state = if let State::Stopped(s) = mem::replace(&mut self.state, State::Starting) { + s + } else { + panic!("manager was not stopped"); + }; + let mut handler = OscFeedbackHandler { + task_receiver: state.task_receiver, + osc_output_devices, + }; + let request_stop = Arc::new(AtomicBool::new(false)); + let request_stop_clone = request_stop.clone(); + let join_handle = std::thread::Builder::new() + .name("ReaLearn OSC sender".to_owned()) + .spawn(move || { + while !request_stop_clone.load(Ordering::SeqCst) { + handler.cycle(); + } + handler + }) + .unwrap(); + self.state = State::Running(RunningState { + request_stop, + join_handle, + }); + } + + pub fn stop(&mut self) { + if !matches!(&self.state, State::Running(_)) { + return; + } + let state = if let State::Running(s) = mem::replace(&mut self.state, State::Stopping) { + s + } else { + panic!("manager was not started"); + }; + state.request_stop.store(true, Ordering::SeqCst); + let handler = state.join_handle.join().unwrap(); + self.state = State::Stopped(StoppedState { + task_receiver: handler.return_task_receiver(), + }); + } +} + +struct OscFeedbackHandler { + task_receiver: Receiver, + osc_output_devices: Vec, +} + +impl OscFeedbackHandler { + pub fn cycle(&mut self) { + use itertools::Itertools; + let grouped_by_device = self + .task_receiver + .try_iter() + .take(OSC_OUTGOING_BULK_SIZE) + .sorted_by_key(|task| task.dev_id) + .group_by(|task| task.dev_id); + for (dev_id, group) in grouped_by_device.into_iter() { + if let Some(dev) = self.osc_output_devices.iter().find(|d| d.id() == dev_id) { + let _ = dev.send(group.map(|task| task.msg)); + } + } + std::thread::sleep(Duration::from_millis(1)); + } + + pub fn return_task_receiver(self) -> Receiver { + self.task_receiver + } +} + +#[derive(Debug)] +pub struct OscInputDevice { + id: OscDeviceId, + socket: UdpSocket, + osc_buffer: [u8; MAX_INCOMING_PACKET_SIZE], +} + +impl OscInputDevice { + pub fn bind(id: OscDeviceId, socket: UdpSocket) -> Result> { + let dev = OscInputDevice { + id, + socket, + osc_buffer: [0; MAX_INCOMING_PACKET_SIZE], + }; + Ok(dev) + } + + pub fn id(&self) -> &OscDeviceId { + &self.id + } + + pub fn poll(&mut self) -> Result, &'static str> { + match self.socket.recv(&mut self.osc_buffer) { + Ok(num_bytes) => match rosc::decoder::decode_udp(&self.osc_buffer[..num_bytes]) { + Ok((_, packet)) => { + trace!("Received packet with {} bytes: {:#?}", num_bytes, &packet); + Ok(Some(packet)) + } + Err(err) => { + warn!("Error trying to decode OSC packet: {:?}", err); + Err("error trying to decode OSC messages") + } + }, + Err(ref err) if err.kind() != io::ErrorKind::WouldBlock => { + warn!("Error trying to receive OSC packet: {}", err); + Err("error trying to receive OSC message") + } + // We don't need to handle "would block" because we are running in a loop anyway. + _ => Ok(None), + } + } + + pub fn poll_multiple(&mut self, n: usize) -> impl Iterator + '_ { + (0..n).flat_map(move |_| self.poll().ok().flatten()) + } +} + +#[derive(Debug)] +pub struct OscOutputDevice { + id: OscDeviceId, + socket: UdpSocket, + dest_address: SocketAddrV4, + can_deal_with_bundles: bool, +} + +impl OscOutputDevice { + pub fn new( + id: OscDeviceId, + socket: UdpSocket, + dest_address: SocketAddrV4, + can_deal_with_bundles: bool, + ) -> Self { + // Attention: It's important that we don't use `UdpSocket::connect` here as this breaks + // control. No idea why exactly, but it must have something to do with the fact that we + // clone the control socket (in order to support "respond to sending port" scenario). + // See https://github.com/helgoboss/helgobox/issues/706 and + // https://github.com/helgoboss/helgobox/issues/551. + OscOutputDevice { + id, + socket, + dest_address, + can_deal_with_bundles, + } + } + + pub fn id(&self) -> OscDeviceId { + self.id + } + + pub fn send(&self, messages: impl Iterator) -> Result<(), &'static str> { + if self.can_deal_with_bundles { + // Haven't realized a performance difference between sending a bundle or single + // messages. However, REAPER sends a bundle (maybe in order to use time tags). + // Let's do it, too, if the device supports it. + self.send_as_bundle(messages) + } else { + self.send_as_messages(messages) + } + } + + fn send_as_bundle( + &self, + messages: impl Iterator, + ) -> Result<(), &'static str> { + let bundle = OscBundle { + // That should be "immediately" according to the OSC Time Tag spec. + timetag: (0, 1).into(), + content: messages.map(OscPacket::Message).collect(), + }; + let packet = OscPacket::Bundle(bundle); + let bytes = rosc::encoder::encode(&packet) + .map_err(|_| "error trying to encode OSC bundle packet")?; + trace!( + "Sending bundle packet with {} bytes: {:#?}", + bytes.len(), + &packet + ); + self.socket + .send_to(&bytes, self.dest_address) + .map_err(|_| "error trying to send OSC bundle packet")?; + Ok(()) + } + + fn send_as_messages( + &self, + messages: impl Iterator, + ) -> Result<(), &'static str> { + for m in messages { + let packet = OscPacket::Message(m); + let bytes = rosc::encoder::encode(&packet) + .map_err(|_| "error trying to encode OSC message packet")?; + trace!( + "Sending message packet with {} bytes: {:#?}", + bytes.len(), + &packet + ); + self.socket + .send_to(&bytes, self.dest_address) + .map_err(|_| "error trying to send OSC message packet")?; + } + Ok(()) + } +} + +/// An OSC device ID. +/// +/// This uniquely identifies an OSC device according to ReaLearn's device configuration. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Serialize, Deserialize, +)] +#[serde(transparent)] +pub struct OscDeviceId(uuid::Uuid); + +impl OscDeviceId { + pub fn random() -> OscDeviceId { + OscDeviceId(Uuid::new_v4()) + } + + pub fn fmt_short(&self) -> String { + self.0.to_string().chars().take(5).collect() + } +} + +impl FromStr for OscDeviceId { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(OscDeviceId(s.parse().context("invalid OSC device ID")?)) + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct OscScanResult { + pub message: OscMessage, + pub dev_id: Option, +} diff --git a/plugin-reaper-realearn/main/src/domain/parameter.rs b/plugin-reaper-realearn/main/src/domain/parameter.rs new file mode 100644 index 0000000..9021fec --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/parameter.rs @@ -0,0 +1,414 @@ +use crate::domain::CompartmentKind; +use base::default_util::{deserialize_null_default, is_default}; +use derive_more::Display; +use enum_map::EnumMap; +use helgoboss_learn::UnitValue; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::num::NonZeroU32; +use std::ops::{Add, RangeInclusive}; + +/// Total number of parameters of the plug-in. +pub const PLUGIN_PARAMETER_COUNT: u32 = 200; + +/// Number of parameters per compartment. +pub const COMPARTMENT_PARAMETER_COUNT: u32 = 100; + +/// Returns an iterator over the range of compartment parameter indices. +pub fn compartment_param_index_iter() -> impl Iterator { + convert_compartment_param_index_range_to_iter(&compartment_param_index_range()) +} + +/// Returns the range of compartment parameter indices. +pub fn compartment_param_index_range() -> RangeInclusive { + CompartmentParamIndex(0)..=CompartmentParamIndex(COMPARTMENT_PARAMETER_COUNT - 1) +} + +/// We need this because the `step_trait` is not stabilized yet. +pub fn convert_plugin_param_index_range_to_iter( + range: &RangeInclusive, +) -> impl Iterator { + (range.start().get()..=range.end().get()).map(PluginParamIndex) +} + +/// We need this because the `step_trait` is not stabilized yet. +pub fn convert_compartment_param_index_range_to_iter( + range: &RangeInclusive, +) -> impl Iterator { + (range.start().get()..=range.end().get()).map(CompartmentParamIndex) +} + +/// Raw parameter value. +pub type RawParamValue = f32; + +/// Effective parameter value. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum EffectiveParamValue { + Continuous(f64), + Discrete(u32), +} + +impl Display for EffectiveParamValue { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + EffectiveParamValue::Continuous(v) => write!(f, "{:.3}", *v), + EffectiveParamValue::Discrete(v) => v.fmt(f), + } + } +} + +impl From for f64 { + fn from(v: EffectiveParamValue) -> Self { + match v { + EffectiveParamValue::Continuous(v) => v, + EffectiveParamValue::Discrete(v) => v as f64, + } + } +} + +/// Parameter setting. +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ParamSetting { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub key: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub name: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub value_count: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub value_labels: Vec, +} + +impl ParamSetting { + fn is_default(&self) -> bool { + self.key.is_none() && self.name.is_empty() && self.value_count.is_none() + } + + pub fn discrete_values(&self) -> Option> + '_> { + let value_count = self.value_count?; + let iter = (0..value_count.get()).map(|v| { + self.find_label_for_value(v) + .map(|v| v.into()) + .unwrap_or_else(|| v.to_string().into()) + }); + Some(iter) + } + + /// Checks if the given key matches the key of this parameter (if a key is defined). + pub fn key_matches(&self, key: &str) -> bool { + if let Some(k) = self.key.as_ref() { + k == key + } else { + false + } + } + + pub fn with_raw_value(&self, value: RawParamValue) -> impl Display + '_ { + SettingAndValue { + setting: self, + value, + } + } + + pub fn convert_to_value(&self, raw_value: RawParamValue) -> EffectiveParamValue { + let raw_value = UnitValue::new_clamped(raw_value as _); + if let Some(value_count) = self.value_count { + let scaled = raw_value.get() * (value_count.get() - 1) as f64; + EffectiveParamValue::Discrete(scaled.round() as u32) + } else { + EffectiveParamValue::Continuous(raw_value.get()) + } + } + + fn convert_to_raw_value(&self, effective_value: f64) -> RawParamValue { + let raw_value = if let Some(value_count) = self.value_count { + effective_value / (value_count.get() - 1) as f64 + } else { + effective_value + }; + UnitValue::new_clamped(raw_value).get() as RawParamValue + } + + pub fn find_label_for_value(&self, value: u32) -> Option<&str> { + self.value_labels.get(value as usize).map(|s| s.as_str()) + } + + /// Attempts to parse the given text to a raw parameter value. + pub fn parse_to_raw_value(&self, text: &str) -> Result { + let effective_value: f64 = text.parse().map_err(|_| "couldn't parse as number")?; + Ok(self.convert_to_raw_value(effective_value)) + } +} + +struct SettingAndValue<'a> { + setting: &'a ParamSetting, + value: RawParamValue, +} + +impl Display for SettingAndValue<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let effective_value = self.setting.convert_to_value(self.value); + if let EffectiveParamValue::Discrete(v) = effective_value { + if let Some(label) = self.setting.find_label_for_value(v) { + return label.fmt(f); + } + } + effective_value.fmt(f) + } +} + +/// Setting and value combined. +#[derive(Clone, Debug, Default)] +pub struct Param { + setting: ParamSetting, + value: RawParamValue, +} + +impl Param { + /// Creates a new parameter. + pub fn new(setting: ParamSetting, value: RawParamValue) -> Self { + Self { setting, value } + } + + /// Returns the effective parameter value (taking the parameter setting into account). + pub fn effective_value(&self) -> EffectiveParamValue { + self.setting.convert_to_value(self.value) + } + + /// Returns the setting of this parameter. + pub fn setting(&self) -> &ParamSetting { + &self.setting + } + + /// Sets the setting of this parameter. + pub fn set_setting(&mut self, setting: ParamSetting) { + self.setting = setting; + } + + /// Returns the raw value of this parameter. + pub fn raw_value(&self) -> RawParamValue { + self.value + } + + /// Sets the raw value of this parameter. + pub fn set_raw_value(&mut self, value: RawParamValue) { + self.value = value; + } +} + +impl Display for Param { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let setting_and_value = SettingAndValue { + setting: &self.setting, + value: self.value, + }; + setting_and_value.fmt(f) + } +} + +/// All parameters for a particular compartment. +#[derive(Clone, Debug)] +pub struct CompartmentParams(Vec); + +impl Default for CompartmentParams { + fn default() -> Self { + let vector = vec![Default::default(); COMPARTMENT_PARAMETER_COUNT as usize]; + Self(vector) + } +} + +impl CompartmentParams { + /// Returns the parameter at the given index. + pub fn at(&self, index: CompartmentParamIndex) -> &Param { + self.0.get(index.get() as usize).unwrap() + } + + /// Returns the parameter at the given index, mutable. + pub fn at_mut(&mut self, index: CompartmentParamIndex) -> &mut Param { + self.0.get_mut(index.get() as usize).unwrap() + } + + /// Returns the name of the parameter including its position. + pub fn get_parameter_name(&self, index: CompartmentParamIndex) -> Cow { + let setting = &self.at(index).setting; + if setting.name.is_empty() { + Cow::Owned(format!("Param {}", index.get() + 1)) + } else { + Cow::Borrowed(&setting.name) + } + } + + /// Returns a map of all parameter settings that don't correspond to the defaults. + pub fn non_default_settings(&self) -> Vec<(CompartmentParamIndex, ParamSetting)> { + self.0 + .iter() + .map(|p| &p.setting) + .enumerate() + .filter(|(_, s)| !s.is_default()) + .map(|(i, s)| { + ( + CompartmentParamIndex::try_from(i as u32).unwrap(), + s.clone(), + ) + }) + .collect() + } + + /// Applies the given settings. + pub fn apply_given_settings(&mut self, settings: Vec<(CompartmentParamIndex, ParamSetting)>) { + for (i, setting) in settings { + self.at_mut(i).setting = setting; + } + } + + /// Resets all settings and values to the defaults. + pub fn reset_all(&mut self) { + *self = Default::default(); + } + + pub fn find_setting_by_key(&self, key: &str) -> Option<(CompartmentParamIndex, &ParamSetting)> { + self.0 + .iter() + .enumerate() + .find(|(_, s)| s.setting.key.as_ref().map(|k| k == key).unwrap_or(false)) + .map(|(i, s)| { + ( + CompartmentParamIndex::try_from(i as u32).unwrap(), + &s.setting, + ) + }) + } +} + +/// All parameters for the complete plug-in. +#[derive(Clone, Debug, Default)] +pub struct PluginParams { + compartment_params: EnumMap, +} + +impl PluginParams { + /// Returns the parameter at the given index. + pub fn at(&self, index: PluginParamIndex) -> &Param { + let (compartment, index) = CompartmentKind::translate_plugin_param_index(index); + self.compartment_params(compartment).at(index) + } + + /// Returns the parameter at the given index, mutable. + pub fn at_mut(&mut self, index: PluginParamIndex) -> &mut Param { + let (compartment, index) = CompartmentKind::translate_plugin_param_index(index); + self.compartment_params_mut(compartment).at_mut(index) + } + + /// Returns the parameter for the given compartment. + pub fn compartment_params(&self, compartment: CompartmentKind) -> &CompartmentParams { + &self.compartment_params[compartment] + } + + /// Returns the parameter for the given compartment, mutable. + pub fn compartment_params_mut( + &mut self, + compartment: CompartmentKind, + ) -> &mut CompartmentParams { + &mut self.compartment_params[compartment] + } + + /// Returns the parameter name prefixed with compartment label. + pub fn build_qualified_parameter_name(&self, index: PluginParamIndex) -> String { + let (compartment, index) = CompartmentKind::translate_plugin_param_index(index); + let compartment_param_name = self + .compartment_params(compartment) + .get_parameter_name(index); + let compartment_label = match compartment { + CompartmentKind::Controller => "Ctrl", + CompartmentKind::Main => "Main", + }; + format!( + "{} p{}: {}", + compartment_label, + index.get() + 1, + compartment_param_name + ) + } +} + +/// Refers to a parameter within the complete set of plug-in parameters. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Debug, Default, Display)] +pub struct PluginParamIndex(u32); + +impl PluginParamIndex { + /// Returns the raw index. + pub fn get(&self) -> u32 { + self.0 + } +} + +impl Add for PluginParamIndex { + type Output = Option; + + fn add(self, rhs: u32) -> Self::Output { + Self::try_from(self.0 + rhs).ok() + } +} + +impl TryFrom for PluginParamIndex { + type Error = &'static str; + + fn try_from(value: u32) -> Result { + if value >= PLUGIN_PARAMETER_COUNT { + return Err("invalid plug-in parameter index"); + } + Ok(Self(value)) + } +} + +/// Refers to a parameter within one compartment. +#[derive( + Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Debug, Default, Serialize, Deserialize, Display, +)] +#[serde(try_from = "u32")] +pub struct CompartmentParamIndex(u32); + +impl CompartmentParamIndex { + /// Returns the raw index. + pub fn get(&self) -> u32 { + self.0 + } +} + +impl Add for CompartmentParamIndex { + type Output = Option; + + fn add(self, rhs: u32) -> Self::Output { + Self::try_from(self.0 + rhs).ok() + } +} + +impl TryFrom for CompartmentParamIndex { + type Error = &'static str; + + fn try_from(value: u32) -> Result { + if value >= COMPARTMENT_PARAMETER_COUNT { + return Err("invalid compartment parameter index"); + } + Ok(Self(value)) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/parameter_manager.rs b/plugin-reaper-realearn/main/src/domain/parameter_manager.rs new file mode 100644 index 0000000..2e654df --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/parameter_manager.rs @@ -0,0 +1,107 @@ +use crate::domain::{ + CompartmentKind, CompartmentParamIndex, CompartmentParams, ControlEventTimestamp, ParamSetting, + ParameterMainTask, PluginParamIndex, PluginParams, RawParamValue, +}; +use base::{blocking_read_lock, blocking_write_lock, NamedChannelSender, SenderToNormalThread}; +use reaper_high::Reaper; +use reaper_medium::ProjectRef; +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Debug)] +pub struct ParameterManager { + /// Canonical parameters. + /// + /// Locked by a read-write lock because this will be accessed from different threads. At least + /// the values. But we want to keep settings and values tightly together for reasons of + /// simplicity, so we put them into the read-write lock as well. + params: RwLock, + parameter_main_task_sender: SenderToNormalThread, +} + +impl ParameterManager { + pub fn new(parameter_main_task_sender: SenderToNormalThread) -> Self { + Self { + params: Default::default(), + parameter_main_task_sender, + } + } + + pub fn update_certain_compartment_param_settings( + &self, + compartment: CompartmentKind, + settings: Vec<(CompartmentParamIndex, ParamSetting)>, + ) { + let mut plugin_params = self.params_mut(); + let compartment_params = plugin_params.compartment_params_mut(compartment); + compartment_params.apply_given_settings(settings); + } + + pub fn update_compartment_params( + &self, + compartment: CompartmentKind, + params: CompartmentParams, + ) { + let mut plugin_params = self.params_mut(); + let compartment_params = plugin_params.compartment_params_mut(compartment); + *compartment_params = params; + // Propagate + // send_if_space because https://github.com/helgoboss/helgobox/issues/847 + self.parameter_main_task_sender + .send_if_space(ParameterMainTask::UpdateAllParams(plugin_params.clone())); + } + + pub fn set_all_parameters(&self, params: PluginParams) { + let mut plugin_params = self.params_mut(); + *plugin_params = params; + // Propagate + // send_if_space because https://github.com/helgoboss/helgobox/issues/847 + self.parameter_main_task_sender + .send_if_space(ParameterMainTask::UpdateAllParams(plugin_params.clone())); + } + + pub fn set_single_parameter(&self, index: PluginParamIndex, value: RawParamValue) { + let mut params = self.params_mut(); + let param = params.at_mut(index); + let current_value = param.raw_value(); + if current_value == value { + // No need to update. This can happen a lot if a ReaLearn parameter is being automated. + return; + } + // Update synchronously so that a subsequent `get_parameter` will immediately + // return the new value. + param.set_raw_value(value); + // We immediately send to the main processor. Sending to the session and using the + // session parameter list as single source of truth is no option because this method + // will be called in a processing thread, not in the main thread. Not even a mutex would + // help here because the session is conceived for main-thread usage only! I was not + // aware of this being called in another thread and it led to subtle errors of course + // (https://github.com/helgoboss/helgobox/issues/59). + // When rendering, we don't do it because that will accumulate until the rendering is + // finished, which is pointless. + if !is_rendering() { + let timestamp = ControlEventTimestamp::from_main_thread(); + self.parameter_main_task_sender.send_complaining( + ParameterMainTask::UpdateSingleParamValue { + index, + value, + timestamp, + }, + ); + } + } + + pub fn params(&self) -> RwLockReadGuard { + blocking_read_lock(&self.params, "ParameterManager params") + } + + fn params_mut(&self) -> RwLockWriteGuard { + blocking_write_lock(&self.params, "ParameterManager params_mut") + } +} + +fn is_rendering() -> bool { + Reaper::get() + .medium_reaper() + .enum_projects(ProjectRef::CurrentlyRendering, 0) + .is_some() +} diff --git a/plugin-reaper-realearn/main/src/domain/playtime_util.rs b/plugin-reaper-realearn/main/src/domain/playtime_util.rs new file mode 100644 index 0000000..4daa86f --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/playtime_util.rs @@ -0,0 +1,66 @@ +use crate::domain::{ + transport_is_enabled_unit_value, Backbone, CompartmentKind, ExtendedProcessorContext, + TrackResolveError, VirtualPlaytimeColumn, +}; +use helgoboss_learn::UnitValue; +use helgobox_api::persistence::ClipColumnTrackContext; +use reaper_high::Track; + +/// In clip slot targets, the resolve phase makes sure that the targeted slot actually exists. +/// So if we get a `None` value from some of the clip slot methods, it's because the slot doesn't +/// have a clip, which is a valid state and should return *something*. The contract of the target +/// `current_value()` is that if it returns `None`, it means it can't get a value at the moment, +/// probably just temporarily. In that case, the feedback is simply not updated. +pub fn interpret_current_clip_slot_value(value: Option) -> Option { + Some(value.unwrap_or_default()) +} + +pub fn resolve_virtual_track_by_playtime_column( + context: ExtendedProcessorContext, + compartment: CompartmentKind, + column: &VirtualPlaytimeColumn, + track_context: &ClipColumnTrackContext, +) -> Result, TrackResolveError> { + // TODO-low Not very helpful. Do we even use this error type? + let generic_error = || TrackResolveError::TrackNotFound { + guid: None, + name: None, + index: None, + }; + let clip_column_index = column + .resolve(context, compartment) + .map_err(|_| generic_error())?; + let track = Backbone::get() + .with_clip_matrix(context.control_context.instance(), |matrix| { + let column = matrix.get_column(clip_column_index)?; + match track_context { + ClipColumnTrackContext::Playback => column.playback_track().cloned(), + ClipColumnTrackContext::Recording => column.effective_recording_track(), + } + }) + .map_err(|_| generic_error())? + .map_err(|_| generic_error())?; + Ok(vec![track]) +} + +// Panics if called with repeat or record. +#[cfg(feature = "playtime")] +pub(crate) fn clip_play_state_unit_value( + action: helgobox_api::persistence::PlaytimeSlotTransportAction, + play_state: playtime_clip_engine::rt::ClipPlayState, +) -> UnitValue { + use helgobox_api::persistence::PlaytimeSlotTransportAction::*; + use playtime_clip_engine::rt::ClipPlayState; + match action { + Trigger | PlayStop | PlayPause | RecordPlayStop => play_state.feedback_value(), + Stop => transport_is_enabled_unit_value(matches!( + play_state, + ClipPlayState::Stopped | ClipPlayState::Ignited + )), + Pause => transport_is_enabled_unit_value(play_state == ClipPlayState::Paused), + RecordStop | OverdubPlay => { + transport_is_enabled_unit_value(play_state.is_and_will_keep_recording()) + } + Looped => panic!("wrong argument"), + } +} diff --git a/plugin-reaper-realearn/main/src/domain/processor_context.rs b/plugin-reaper-realearn/main/src/domain/processor_context.rs new file mode 100644 index 0000000..fce0d34 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/processor_context.rs @@ -0,0 +1,151 @@ +use crate::domain::{ControlContext, PluginParams}; +use anyhow::{bail, Context}; +use derivative::Derivative; +use reaper_high::{Fx, FxChainContext, Project, Reaper, Track}; +use reaper_low::{static_plugin_context, PluginContext}; +use reaper_medium::{MainThreadScope, ParamId, TrackFxLocation, TypeSpecificPluginContext}; +use std::ptr::NonNull; +use vst::host::Host; +use vst::plugin::HostCallback; + +#[derive(Copy, Clone, Debug)] +pub struct ExtendedProcessorContext<'a> { + pub context: &'a ProcessorContext, + pub params: &'a PluginParams, + pub control_context: ControlContext<'a>, +} + +impl<'a> ExtendedProcessorContext<'a> { + pub fn new( + context: &'a ProcessorContext, + params: &'a PluginParams, + control_context: ControlContext<'a>, + ) -> Self { + Self { + context, + params, + control_context, + } + } + + pub fn context(&self) -> &'a ProcessorContext { + self.context + } + + pub fn params(&self) -> &'a PluginParams { + self.params + } + + pub fn control_context(&self) -> ControlContext { + self.control_context + } +} + +#[derive(Clone, Derivative)] +#[derivative(Debug)] +pub struct ProcessorContext { + #[derivative(Debug = "ignore")] + host: HostCallback, + containing_fx: Fx, + project: Option, + bypass_param_index: u32, +} + +pub const HELGOBOX_INSTANCE_ID_KEY: &str = "instance_id"; + +impl ProcessorContext { + pub fn from_host(host: HostCallback) -> anyhow::Result { + let fx = get_containing_fx(&host)?; + let project = fx.project(); + let bypass_param = fx + .parameter_by_id(ParamId::Bypass) + .context("bypass parameter not found")?; + let context = ProcessorContext { + host, + containing_fx: fx, + project, + bypass_param_index: bypass_param.index(), + }; + Ok(context) + } + + pub fn containing_fx(&self) -> &Fx { + &self.containing_fx + } + + pub fn track(&self) -> Option<&Track> { + self.containing_fx.track() + } + + /// Returns the index of ReaLearn's "Bypass" parameter. + pub fn bypass_param_index(&self) -> u32 { + self.bypass_param_index + } + + /// This falls back to the current project if on the monitoring FX chain. + pub fn project_or_current_project(&self) -> Project { + self.project + .unwrap_or_else(|| Reaper::get().current_project()) + } + + pub fn project(&self) -> Option { + self.project + } + + pub fn is_on_monitoring_fx_chain(&self) -> bool { + matches!( + self.containing_fx.chain().context(), + FxChainContext::Monitoring + ) + } + + pub fn notify_dirty(&self) { + self.host.automate(-1, 0.0); + } +} + +/// Calling this in the `new()` method is too early. The containing FX can't generally be found +/// when we just open a REAPER project. We must wait for `init()` to be called. No! Even longer. +/// We need to wait until the next main loop cycle. +fn get_containing_fx(host: &HostCallback) -> anyhow::Result { + let aeffect = NonNull::new(host.raw_effect()).context("aeffect must not be null")?; + // We must not use the plug-in context from the global `Reaper` instance because this was + // probably initialized by the extension entry point or another instance. + let plugin_context = PluginContext::from_vst_plugin(host, static_plugin_context()) + .context("host callback not available")?; + let plugin_context = reaper_medium::PluginContext::<'_, MainThreadScope>::new(&plugin_context); + let vst_context = match plugin_context.type_specific() { + TypeSpecificPluginContext::Vst(ctx) => ctx, + _ => unreachable!(), + }; + let fx_location = unsafe { + // This would fail if we would call it too soon. That's why this function needs to be + // called in the main loop cycle after loading the VST. + vst_context + .request_containing_fx_location(aeffect) + .context("This version of ReaLearn needs REAPER >= v6.11.")? + }; + let fx = if let Some(track) = unsafe { vst_context.request_containing_track(aeffect) } { + let project = unsafe { vst_context.request_containing_project(aeffect) }; + let track = Track::new(track, Some(project)); + let (fx_chain, index) = match fx_location { + TrackFxLocation::NormalFxChain(index) => (track.normal_fx_chain(), index), + TrackFxLocation::InputFxChain(index) => (track.input_fx_chain(), index), + TrackFxLocation::Unknown(_) => { + bail!("Unknown ReaLearn FX location"); + } + }; + fx_chain + .fx_by_index(index) + .context("couldn't find containing FX on track FX chains")? + } else if let Some(_take) = unsafe { vst_context.request_containing_take(aeffect) } { + bail!("Sorry, Helgobox doesn't support operation as item/take FX!"); + } else { + let TrackFxLocation::InputFxChain(index) = fx_location else { + bail!("Sorry, Helgobox doesn't support operation within an FX container!",); + }; + Reaper::get().monitoring_fx_chain().fx_by_index(index) + .context("Couldn't find containing FX on monitoring FX chain. It's okay if this occurs during plug-in scanning.")? + }; + Ok(fx) +} diff --git a/plugin-reaper-realearn/main/src/domain/props.rs b/plugin-reaper-realearn/main/src/domain/props.rs new file mode 100644 index 0000000..a23a074 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/props.rs @@ -0,0 +1,702 @@ +use crate::domain::{ + convert_reaper_color_to_helgoboss_learn, get_fx_name, get_track_name, Backbone, + CompoundChangeEvent, CompoundMappingTarget, ControlContext, FeedbackResolution, MainMapping, + RealearnTarget, ReaperTarget, UnresolvedCompoundMappingTarget, +}; +use enum_dispatch::enum_dispatch; +use helgoboss_learn::{AbsoluteValue, NumericValue, PropProvider, PropValue, Target}; +use helgobox_api::persistence::TrackScope; +use reaper_high::ChangeEvent; +use std::str::FromStr; + +/// `None` means that no polling is necessary for feedback because we are notified via events. +pub fn prop_feedback_resolution( + key: &str, + mapping: &MainMapping, + target: &UnresolvedCompoundMappingTarget, +) -> Option { + match key.parse::().ok() { + Some(props) => props.feedback_resolution(mapping, target), + None => { + // Maybe target-specific placeholder. At the moment we should only have target-specific + // placeholders whose feedback resolution is the same resolution as the one of the + // main target value, so the following is good enough. If this changes in future, we + // should introduce a similar function in ReaLearn target (one that takes a key). + target.feedback_resolution() + } + } +} + +pub fn prop_is_affected_by( + key: &str, + event: CompoundChangeEvent, + mapping: &MainMapping, + target: &ReaperTarget, + control_context: ControlContext, +) -> bool { + match key.parse::().ok() { + Some(props) => { + // TODO-medium Not very consequent? Here we take the first target and for + // target-specific placeholders the given one. A bit hard to change though. Let's see. + props.is_affected_by(event, mapping, mapping.targets().first(), control_context) + } + None => { + // Maybe target-specific placeholder. At the moment we should only have target-specific + // placeholders that are affected by changes of the main target value, so the following + // is good enough. If this changes in future, we should introduce a similar function + // in ReaLearn target (one that takes a key). + if key.starts_with("target.") { + target.process_change_event(event, control_context).0 + } else { + false + } + } + } +} + +pub struct MappingPropProvider<'a> { + mapping: &'a MainMapping, + context: ControlContext<'a>, +} + +impl<'a> MappingPropProvider<'a> { + pub fn new(mapping: &'a MainMapping, context: ControlContext<'a>) -> Self { + Self { mapping, context } + } +} + +impl PropProvider for MappingPropProvider<'_> { + fn get_prop_value(&self, key: &str) -> Option { + match key.parse::().ok() { + Some(props) => { + props.get_value(self.mapping, self.mapping.targets().first(), self.context) + } + None => { + let target = self.mapping.targets().first()?; + if key == "y" { + let y = target.current_value(self.context)?; + Some(PropValue::Normalized(y.to_unit_value())) + } else if let Some(key) = key.strip_prefix("target.") { + target.prop_value(key, self.context) + } else { + None + } + } + } + } +} + +enum Props { + Global(GlobalProps), + Mapping(MappingProps), + Target(TargetProps), +} + +impl Props { + /// `None` means that no polling is necessary for feedback because we are notified via events. + pub fn feedback_resolution( + &self, + mapping: &MainMapping, + target: &UnresolvedCompoundMappingTarget, + ) -> Option { + match self { + Props::Global(p) => { + let args = PropFeedbackResolutionArgs { object: () }; + p.feedback_resolution(args) + } + Props::Mapping(p) => { + let args = PropFeedbackResolutionArgs { object: mapping }; + p.feedback_resolution(args) + } + Props::Target(p) => { + let args = PropFeedbackResolutionArgs { + object: MappingAndUnresolvedTarget { mapping, target }, + }; + p.feedback_resolution(args) + } + } + } + + /// Returns whether the value of this property could be affected by the given change event. + pub fn is_affected_by( + &self, + event: CompoundChangeEvent, + mapping: &MainMapping, + target: Option<&CompoundMappingTarget>, + control_context: ControlContext, + ) -> bool { + match self { + Props::Global(p) => { + let args = PropIsAffectedByArgs { + event, + object: (), + control_context, + }; + p.is_affected_by(args) + } + Props::Mapping(p) => { + let args = PropIsAffectedByArgs { + event, + object: mapping, + control_context, + }; + p.is_affected_by(args) + } + Props::Target(p) => target + .map(|target| { + let args = PropIsAffectedByArgs { + event, + object: MappingAndTarget { mapping, target }, + control_context, + }; + p.is_affected_by(args) + }) + .unwrap_or(false), + } + } + + /// Returns the current value of this property. + pub fn get_value( + &self, + mapping: &MainMapping, + target: Option<&CompoundMappingTarget>, + control_context: ControlContext, + ) -> Option { + match self { + Props::Global(p) => { + let args = PropGetValueArgs { + object: (), + control_context, + }; + p.get_value(args) + } + Props::Mapping(p) => { + let args = PropGetValueArgs { + object: mapping, + control_context, + }; + p.get_value(args) + } + Props::Target(p) => target.and_then(|target| { + let args = PropGetValueArgs { + object: MappingAndTarget { mapping, target }, + control_context, + }; + p.get_value(args) + }), + } + } +} + +impl FromStr for Props { + type Err = strum::ParseError; + + fn from_str(s: &str) -> Result { + s.parse::() + .map(Props::Global) + .or_else(|_| s.parse::().map(Props::Mapping)) + .or_else(|_| s.parse::().map(Props::Target)) + } +} + +#[enum_dispatch] +#[derive(strum::EnumString)] +enum GlobalProps { + #[strum(serialize = "global.realearn.time")] + GlobalRealearnTime(GlobalRealearnTimeProp), +} + +#[enum_dispatch] +#[derive(strum::EnumString)] +enum MappingProps { + #[strum(serialize = "mapping.name")] + Name(MappingNameProp), +} + +#[enum_dispatch] +#[derive(strum::EnumString)] +enum TargetProps { + #[strum(serialize = "target.type.name")] + TargetTypeName(TargetTypeNameProp), + #[strum(serialize = "target.type.long_name")] + TargetTypeLongName(TargetTypeLongNameProp), + #[strum(serialize = "target.available")] + TargetAvailable(TargetAvailableProp), + #[strum(serialize = "target.text_value")] + TextValue(TargetTextValueProp), + #[strum(serialize = "target.discrete_value")] + DiscreteValue(TargetDiscreteValueProp), + #[strum(serialize = "target.discrete_value_count")] + DiscreteValueCount(TargetDiscreteValueCountProp), + #[strum(serialize = "target.numeric_value")] + NumericValue(TargetNumericValueProp), + #[strum(serialize = "target.numeric_value.unit")] + NumericValueUnit(TargetNumericValueUnitProp), + #[strum(serialize = "target.normalized_value")] + NormalizedValue(TargetNormalizedValueProp), + #[strum(serialize = "target.track.index")] + TrackIndex(TargetTrackIndexProp), + #[strum(serialize = "target.track.name")] + TrackName(TargetTrackNameProp), + #[strum(serialize = "target.track.color")] + TrackColor(TargetTrackColorProp), + #[strum(serialize = "target.fx.index")] + FxIndex(TargetFxIndexProp), + #[strum(serialize = "target.fx.name")] + FxName(TargetFxNameProp), + #[strum(serialize = "target.route.index")] + RouteIndex(TargetRouteIndexProp), + #[strum(serialize = "target.route.name")] + RouteName(TargetRouteNameProp), + #[strum(serialize = "target.slot.color")] + PlaytimeSlotColor(TargetPlaytimeSlotColorProp), +} + +#[enum_dispatch(GlobalProps)] +trait GlobalProp { + /// `None` means that no polling is necessary for feedback because we are notified via events. + fn feedback_resolution( + &self, + args: PropFeedbackResolutionArgs<()>, + ) -> Option { + let _ = args; + None + } + + /// Returns whether the value of this property could be affected by the given change event. + fn is_affected_by(&self, args: PropIsAffectedByArgs<()>) -> bool; + + /// Returns the current value of this property. + fn get_value(&self, args: PropGetValueArgs<()>) -> Option; +} + +#[enum_dispatch(MappingProps)] +trait MappingProp { + /// `None` means that no polling is necessary for feedback because we are notified via events. + fn feedback_resolution( + &self, + args: PropFeedbackResolutionArgs<&MainMapping>, + ) -> Option { + let _ = args; + None + } + + /// Returns whether the value of this property could be affected by the given change event. + fn is_affected_by(&self, args: PropIsAffectedByArgs<&MainMapping>) -> bool; + + /// Returns the current value of this property. + fn get_value(&self, args: PropGetValueArgs<&MainMapping>) -> Option; +} + +#[enum_dispatch(TargetProps)] +trait TargetProp { + /// `None` means that no polling is necessary for feedback because we are notified via events. + fn feedback_resolution( + &self, + args: PropFeedbackResolutionArgs, + ) -> Option { + let _ = args; + None + } + + /// Returns whether the value of this property could be affected by the given change event. + fn is_affected_by(&self, args: PropIsAffectedByArgs) -> bool { + // Many target props change whenever the main target value changes. So this is the default. + args.object + .target + .process_change_event(args.event, args.control_context) + .0 + } + + /// Returns the current value of this property. + fn get_value(&self, args: PropGetValueArgs) -> Option; +} + +#[allow(dead_code)] +struct MappingAndTarget<'a> { + pub mapping: &'a MainMapping, + pub target: &'a CompoundMappingTarget, +} + +#[allow(dead_code)] +struct MappingAndUnresolvedTarget<'a> { + pub mapping: &'a MainMapping, + pub target: &'a UnresolvedCompoundMappingTarget, +} + +#[allow(dead_code)] +struct PropFeedbackResolutionArgs { + object: T, +} + +struct PropIsAffectedByArgs<'a, T> { + event: CompoundChangeEvent<'a>, + object: T, + control_context: ControlContext<'a>, +} + +struct PropGetValueArgs<'a, T> { + object: T, + control_context: ControlContext<'a>, +} + +#[derive(Default)] +struct GlobalRealearnTimeProp; + +impl GlobalProp for GlobalRealearnTimeProp { + fn feedback_resolution(&self, _: PropFeedbackResolutionArgs<()>) -> Option { + Some(FeedbackResolution::High) + } + + fn is_affected_by(&self, _: PropIsAffectedByArgs<()>) -> bool { + false + } + + fn get_value(&self, _: PropGetValueArgs<()>) -> Option { + Some(PropValue::DurationInMillis( + Backbone::get().duration_since_time_of_start().as_millis() as _, + )) + } +} + +#[derive(Default)] +struct MappingNameProp; + +impl MappingProp for MappingNameProp { + fn is_affected_by(&self, _: PropIsAffectedByArgs<&MainMapping>) -> bool { + // Mapping name changes will result in a full mapping resync anyway. + false + } + + fn get_value(&self, input: PropGetValueArgs<&MainMapping>) -> Option { + let instance_state = input.control_context.unit.borrow(); + let info = instance_state.get_mapping_info(input.object.qualified_id())?; + Some(PropValue::Text(info.name.clone().into())) + } +} + +#[derive(Default)] +struct TargetTextValueProp; + +impl TargetProp for TargetTextValueProp { + fn feedback_resolution( + &self, + args: PropFeedbackResolutionArgs, + ) -> Option { + args.object.target.feedback_resolution() + } + + fn get_value(&self, input: PropGetValueArgs) -> Option { + Some(PropValue::Text( + input.object.target.text_value(input.control_context)?, + )) + } +} + +#[derive(Default)] +struct TargetDiscreteValueCountProp; + +impl TargetProp for TargetDiscreteValueCountProp { + fn get_value(&self, args: PropGetValueArgs) -> Option { + let discrete_count = args + .object + .target + .control_type_and_character(args.control_context) + .0 + .discrete_count()?; + Some(PropValue::Numeric(NumericValue::Discrete( + discrete_count as _, + ))) + } +} + +#[derive(Default)] +struct TargetDiscreteValueProp; + +impl TargetProp for TargetDiscreteValueProp { + fn get_value(&self, args: PropGetValueArgs) -> Option { + let i = match args.object.target.current_value(args.control_context)? { + AbsoluteValue::Continuous(uv) => { + let step_size = args + .object + .target + .control_type_and_character(args.control_context) + .0 + .step_size()?; + (uv.get() / step_size.get()).round() as u32 + } + AbsoluteValue::Discrete(d) => d.actual(), + }; + Some(PropValue::Index(i)) + } +} + +#[derive(Default)] +struct TargetNumericValueProp; + +impl TargetProp for TargetNumericValueProp { + fn feedback_resolution( + &self, + args: PropFeedbackResolutionArgs, + ) -> Option { + args.object.target.feedback_resolution() + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Numeric( + args.object.target.numeric_value(args.control_context)?, + )) + } +} + +#[derive(Default)] +struct TargetNormalizedValueProp; + +impl TargetProp for TargetNormalizedValueProp { + fn feedback_resolution( + &self, + args: PropFeedbackResolutionArgs, + ) -> Option { + args.object.target.feedback_resolution() + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Normalized( + args.object + .target + .current_value(args.control_context)? + .to_unit_value(), + )) + } +} + +#[derive(Default)] +struct TargetTrackIndexProp; + +impl TargetProp for TargetTrackIndexProp { + fn is_affected_by(&self, args: PropIsAffectedByArgs) -> bool { + matches!( + args.event, + CompoundChangeEvent::Reaper( + ChangeEvent::TrackAdded(_) + | ChangeEvent::TrackRemoved(_) + | ChangeEvent::TracksReordered(_) + ) + ) + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Index(args.object.target.track()?.index()?)) + } +} + +#[derive(Default)] +struct TargetFxIndexProp; + +impl TargetProp for TargetFxIndexProp { + fn feedback_resolution( + &self, + _: PropFeedbackResolutionArgs, + ) -> Option { + // This is unfortunately necessary because it's possible that the targeted FX is on the + // monitoring FX chain. This chain doesn't support notifications, so `is_affected_by` + // won't work. + Some(FeedbackResolution::High) + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Index(args.object.target.fx()?.index())) + } +} + +#[derive(Default)] +struct TargetTrackNameProp; + +impl TargetProp for TargetTrackNameProp { + fn is_affected_by(&self, args: PropIsAffectedByArgs) -> bool { + // This could be more specific (taking the track into account) but so what. + // This doesn't happen that frequently. + matches!(args.event, CompoundChangeEvent::Reaper(ChangeEvent::TrackNameChanged(e)) if Some(&e.track) == args.object.target.track()) + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + let name = get_track_name(args.object.target.track()?, TrackScope::AllTracks); + Some(PropValue::Text(name.into())) + } +} + +#[derive(Default)] +struct TargetNumericValueUnitProp; + +impl TargetProp for TargetNumericValueUnitProp { + fn is_affected_by(&self, _: PropIsAffectedByArgs) -> bool { + // Static in nature (change only when target settings change). + false + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Text( + args.object + .target + .numeric_value_unit(args.control_context) + .into(), + )) + } +} + +#[derive(Default)] +struct TargetTypeNameProp; + +impl TargetProp for TargetTypeNameProp { + fn is_affected_by(&self, _: PropIsAffectedByArgs) -> bool { + // Static in nature (change only when target settings change). + false + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Text( + args.object.target.reaper_target_type()?.short_name().into(), + )) + } +} + +#[derive(Default)] +struct TargetTypeLongNameProp; + +impl TargetProp for TargetTypeLongNameProp { + fn is_affected_by(&self, _: PropIsAffectedByArgs) -> bool { + // Static in nature (change only when target settings change). + false + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Text( + args.object.target.reaper_target_type()?.to_string().into(), + )) + } +} + +#[derive(Default)] +struct TargetAvailableProp; + +impl TargetProp for TargetAvailableProp { + fn get_value(&self, args: PropGetValueArgs) -> Option { + let is_available = args.object.target.is_available(args.control_context); + Some(PropValue::Boolean(is_available)) + } +} + +#[derive(Default)] +struct TargetTrackColorProp; + +impl TargetProp for TargetTrackColorProp { + fn feedback_resolution( + &self, + _: PropFeedbackResolutionArgs, + ) -> Option { + // There are no appropriate change events for this property so we fall back to polling. + Some(FeedbackResolution::High) + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + let color = + convert_reaper_color_to_helgoboss_learn(args.object.target.track()?.custom_color()?); + Some(PropValue::Color(color)) + } +} + +#[derive(Default)] +struct TargetPlaytimeSlotColorProp; + +impl TargetProp for TargetPlaytimeSlotColorProp { + fn is_affected_by(&self, args: PropIsAffectedByArgs) -> bool { + #[cfg(not(feature = "playtime"))] + { + let _ = args; + false + } + #[cfg(feature = "playtime")] + { + use playtime_clip_engine::base::*; + use playtime_clip_engine::rt::*; + matches!( + args.event, + CompoundChangeEvent::ClipMatrix( + ClipMatrixEvent::TrackChanged(_) + | ClipMatrixEvent::ClipChanged(QualifiedClipChangeEvent { + event: ClipChangeEvent::Content | ClipChangeEvent::Everything, + .. + }) + ) + ) + } + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + #[cfg(not(feature = "playtime"))] + { + let _ = args; + None + } + #[cfg(feature = "playtime")] + { + let slot_address = args.object.target.clip_slot_address()?; + let instance = args.control_context.instance.borrow(); + let matrix = instance.clip_matrix()?; + let reaper_color = matrix.resolve_slot_color(slot_address)?; + let final_color = convert_reaper_color_to_helgoboss_learn(reaper_color); + Some(PropValue::Color(final_color)) + } + } +} + +#[derive(Default)] +struct TargetFxNameProp; + +// There are no appropriate REAPER change events for this property. +impl TargetProp for TargetFxNameProp { + fn feedback_resolution( + &self, + _: PropFeedbackResolutionArgs, + ) -> Option { + // This is unfortunately necessary because it's possible that the targeted FX is on the + // monitoring FX chain. This chain doesn't support notifications, so `is_affected_by` + // won't work. + Some(FeedbackResolution::High) + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + let name = get_fx_name(args.object.target.fx()?).into(); + Some(PropValue::Text(name)) + } +} + +#[derive(Default)] +struct TargetRouteIndexProp; + +// There are no appropriate REAPER change events for this property. +impl TargetProp for TargetRouteIndexProp { + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Index(args.object.target.route()?.index())) + } +} + +#[derive(Default)] +struct TargetRouteNameProp; + +impl TargetProp for TargetRouteNameProp { + fn is_affected_by(&self, args: PropIsAffectedByArgs) -> bool { + // This could be more specific (taking the route partner into account) but so what. + // Track names are not changed that frequently. + matches!( + args.event, + CompoundChangeEvent::Reaper(ChangeEvent::TrackNameChanged(_)) + ) + } + + fn get_value(&self, args: PropGetValueArgs) -> Option { + Some(PropValue::Text( + args.object.target.route()?.name().into_string().into(), + )) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/real_time_instance.rs b/plugin-reaper-realearn/main/src/domain/real_time_instance.rs new file mode 100644 index 0000000..9d84da4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/real_time_instance.rs @@ -0,0 +1,118 @@ +use crate::domain::AudioBlockProps; +use std::sync::{Arc, Mutex, Weak}; + +pub type SharedRealTimeInstance = Arc>; +pub type WeakRealTimeInstance = Weak>; + +const NORMAL_BULK_SIZE: usize = 100; + +#[derive(Debug)] +pub struct RealTimeInstance { + task_receiver: crossbeam_channel::Receiver, + #[cfg(feature = "playtime")] + playtime: PlaytimeRtInstance, +} + +#[cfg(feature = "playtime")] +#[derive(Debug)] +struct PlaytimeRtInstance { + clip_matrix: Option, + clip_engine_fx_hook: playtime_clip_engine::rt::fx_hook::PlaytimeFxHook, +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum RealTimeInstanceTask { + #[cfg(feature = "playtime")] + SetClipMatrix { + matrix: Option, + }, + #[cfg(feature = "playtime")] + PlaytimeClipEngineCommand(playtime_clip_engine::rt::fx_hook::PlaytimeFxHookCommand), +} + +impl RealTimeInstance { + pub fn new(task_receiver: crossbeam_channel::Receiver) -> Self { + Self { + task_receiver, + #[cfg(feature = "playtime")] + playtime: PlaytimeRtInstance { + clip_matrix: None, + clip_engine_fx_hook: playtime_clip_engine::rt::fx_hook::PlaytimeFxHook::new(), + }, + } + } + + #[cfg(feature = "playtime")] + pub fn clip_matrix(&self) -> Option<&playtime_clip_engine::rt::WeakRtMatrix> { + self.playtime.clip_matrix.as_ref() + } + + /// To be called from audio hook when `post == false`. + pub fn pre_poll(&mut self, block_props: AudioBlockProps) { + #[cfg(not(feature = "playtime"))] + { + let _ = block_props; + } + #[cfg(feature = "playtime")] + { + if let Some(clip_matrix) = self.playtime.clip_matrix.as_ref().and_then(|m| m.upgrade()) + { + clip_matrix.lock().pre_poll(block_props.to_playtime()); + } + } + #[allow(clippy::never_loop)] + for task in self.task_receiver.try_iter().take(NORMAL_BULK_SIZE) { + match task { + #[cfg(feature = "playtime")] + RealTimeInstanceTask::SetClipMatrix { matrix } => { + self.playtime.clip_matrix = matrix; + } + #[cfg(feature = "playtime")] + RealTimeInstanceTask::PlaytimeClipEngineCommand(command) => { + let _ = self + .playtime + .clip_engine_fx_hook + .process_command(command, block_props.to_playtime()); + } + } + } + } + + /// To be called from audio hook when `post == true`. + pub fn post_poll(&mut self) { + #[cfg(feature = "playtime")] + { + if let Some(clip_matrix) = self.playtime.clip_matrix.as_ref().and_then(|m| m.upgrade()) + { + clip_matrix.lock().post_poll(); + } + } + } + + #[cfg(feature = "playtime")] + pub fn run_from_vst( + &mut self, + buffer: &mut vst::buffer::AudioBuffer, + block_props: AudioBlockProps, + ) { + let inputs = VstChannelInputs(buffer.split().0); + self.playtime + .clip_engine_fx_hook + .poll(&inputs, block_props.to_playtime()); + } +} + +#[cfg(feature = "playtime")] +struct VstChannelInputs<'a>(vst::buffer::Inputs<'a, f64>); + +#[cfg(feature = "playtime")] +impl playtime_clip_engine::rt::fx_hook::ChannelInputs for VstChannelInputs<'_> { + fn channel_count(&self) -> usize { + self.0.len() + } + + fn get_channel_data(&self, channel_index: usize) -> &[f64] { + self.0.get(channel_index) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/real_time_processor.rs b/plugin-reaper-realearn/main/src/domain/real_time_processor.rs new file mode 100644 index 0000000..72a800b --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/real_time_processor.rs @@ -0,0 +1,1821 @@ +use crate::domain::{ + classify_midi_message, match_partially, BasicSettings, CompartmentKind, CompoundMappingSource, + ControlEvent, ControlEventTimestamp, ControlLogEntry, ControlLogEntryKind, ControlMainTask, + ControlMode, ControlOptions, FeedbackSendBehavior, LifecycleMidiMessage, LifecyclePhase, + MappingCore, MappingId, MatchOutcome, MidiClockCalculator, MidiEvent, + MidiMessageClassification, MidiScanResult, MidiScanner, MidiTransformationContainer, + NormalRealTimeToMainThreadTask, OrderedMappingMap, OwnedIncomingMidiMessage, + PersistentMappingProcessingState, QualifiedMappingId, RealTimeCompoundMappingTarget, + RealTimeControlContext, RealTimeMapping, RealTimeReaperTarget, SampleOffset, UnitId, + VirtualSourceValue, WeakRealTimeInstance, +}; +use helgoboss_learn::{ControlValue, MidiSourceValue, ModeControlResult, RawMidiEvent}; +use helgoboss_midi::{ + Channel, ControlChange14BitMessage, ControlChange14BitMessageScanner, DataEntryByteOrder, + ParameterNumberMessage, PollingParameterNumberMessageScanner, RawShortMessage, ShortMessage, + ShortMessageFactory, ShortMessageType, +}; +use reaper_high::{MidiOutputDevice, Reaper}; +use reaper_medium::{ + Hz, MidiInputDeviceId, MidiOutputDeviceId, OnAudioBufferArgs, ProjectRef, SendMidiTime, +}; + +use base::{NamedChannelSender, SenderToNormalThread, SenderToRealTimeThread}; +use enum_map::{enum_map, EnumMap}; +use helgobox_allocator::permit_alloc; +use std::convert::TryInto; +use std::ptr::null_mut; +use std::time::Duration; +use tracing::{debug, trace}; +use vst::api::{EventType, Events, SysExEvent}; +use vst::host::Host; +use vst::plugin::HostCallback; + +const NORMAL_BULK_SIZE: usize = 100; +const FEEDBACK_BULK_SIZE: usize = 100; + +#[derive(Debug)] +pub struct RealTimeProcessor { + unit_id: UnitId, + // Synced processing settings + settings: BasicSettings, + control_mode: ControlMode, + mappings: EnumMap>, + // State + control_is_globally_enabled: bool, + feedback_is_globally_enabled: bool, + // Inter-thread communication + normal_task_receiver: crossbeam_channel::Receiver, + feedback_task_receiver: crossbeam_channel::Receiver, + feedback_task_sender: SenderToRealTimeThread, + normal_main_task_sender: SenderToNormalThread, + control_main_task_sender: SenderToNormalThread, + // Scanners for more complex MIDI message types + nrpn_scanner: PollingParameterNumberMessageScanner, + cc_14_bit_scanner: ControlChange14BitMessageScanner, + // For MIDI capturing + midi_scanner: MidiScanner, + // For MIDI timing clock calculations + midi_clock_calculator: MidiClockCalculator, + sample_rate: Hz, + instance: WeakRealTimeInstance, +} + +impl RealTimeProcessor { + #[allow(clippy::too_many_arguments)] + pub fn new( + unit_id: UnitId, + instance: WeakRealTimeInstance, + normal_task_receiver: crossbeam_channel::Receiver, + feedback_task_receiver: crossbeam_channel::Receiver, + feedback_task_sender: SenderToRealTimeThread, + normal_main_task_sender: SenderToNormalThread, + control_main_task_sender: SenderToNormalThread, + ) -> RealTimeProcessor { + use CompartmentKind::*; + RealTimeProcessor { + unit_id, + instance, + settings: Default::default(), + control_mode: ControlMode::Controlling, + normal_task_receiver, + feedback_task_receiver, + feedback_task_sender, + normal_main_task_sender, + control_main_task_sender, + mappings: enum_map! { + Controller => ordered_map_with_capacity(1000), + Main => ordered_map_with_capacity(5000), + }, + nrpn_scanner: PollingParameterNumberMessageScanner::new(Duration::from_millis(1)), + cc_14_bit_scanner: Default::default(), + midi_scanner: Default::default(), + midi_clock_calculator: Default::default(), + control_is_globally_enabled: false, + feedback_is_globally_enabled: false, + sample_rate: Hz::new_panic(1.0), + } + } + + pub fn process_incoming_midi_from_vst( + &mut self, + event: ControlEvent>, + is_transport_start: bool, + host: &HostCallback, + ) { + if self.settings.midi_control_input() == MidiControlInput::FxInput { + // TODO-medium Maybe also filter when transport stopping + if is_transport_start + && event + .payload() + .payload() + .might_be_automatically_generated_by_reaper() + { + // Ignore note off messages which are a result of starting the transport. They + // are generated by REAPER in order to stop instruments from sounding. But ReaLearn + // is not an instrument in the classical sense. We don't want to reset target values + // just because play has been pressed! + self.process_unmatched(event.payload(), Caller::Vst(host)); + return; + } + self.process_incoming_midi(event, Caller::Vst(host), None); + } else { + // #33, #290 If MIDI input device is not set to , we want to pass through all + // messages that arrive on FX input. + self.send_incoming_midi_to_fx_output(event.payload(), Caller::Vst(host)) + } + } + + pub fn run_from_vst(&mut self, host: &HostCallback) { + self.process_feedback_tasks(Caller::Vst(host)); + } + + /// This should be regularly called by audio hook in normal mode. + pub fn run_from_audio_hook_all( + &mut self, + might_be_rebirth: bool, + timestamp: ControlEventTimestamp, + ) { + self.run_from_audio_hook_essential(might_be_rebirth); + self.run_from_audio_hook_control_and_learn(timestamp); + } + + pub fn midi_control_input(&self) -> MidiControlInput { + self.settings.midi_control_input() + } + + pub fn control_is_globally_enabled(&self) -> bool { + self.control_is_globally_enabled + } + + /// This should be called by audio hook in normal mode whenever it receives a MIDI message that + /// is relevant *for this ReaLearn instance* (the input device is not checked again). + /// + /// Returns whether this message should be filtered out from the global MIDI stream. + pub fn process_incoming_midi_from_audio_hook( + &mut self, + event: ControlEvent>, + transformation_container: &mut MidiTransformationContainer, + ) -> bool { + let match_outcome = + self.process_incoming_midi(event, Caller::AudioHook, Some(transformation_container)); + let let_through = (match_outcome.matched_or_consumed() + && self.settings.let_matched_events_through) + || (!match_outcome.matched_or_consumed() && self.settings.let_unmatched_events_through); + !let_through + } + + fn request_full_sync_and_discard_tasks_if_successful(&mut self) { + if self + .normal_main_task_sender + .try_to_send(NormalRealTimeToMainThreadTask::FullResyncToRealTimeProcessorPlease) + { + // Requesting a full resync was successful so we can safely discard accumulated tasks. + let discarded_normal_task_count = self.normal_task_receiver.try_iter().count(); + let discarded_feedback_task_count = self.feedback_task_receiver.try_iter().count(); + permit_alloc(|| { + debug!( + "Successfully requested full sync. Discarded {} normal and {} feedback tasks.", + discarded_normal_task_count, discarded_feedback_task_count + ); + }); + } else { + permit_alloc(|| { + debug!( + "Small audio device outage detected but probably related to project load so no action taken.", + ); + }); + } + } + + /// This should be regularly called by audio hook even during global MIDI source learning. + /// + /// The rebirth parameter is `true` if this could be the first audio cycle after an "unplanned" + /// downtime of the audio device. It could also be just a downtime related to opening the + /// project itself, which we detect to some degree. See the code that reacts to this parameter. + pub fn run_from_audio_hook_essential(&mut self, might_be_rebirth: bool) { + if might_be_rebirth { + self.request_full_sync_and_discard_tasks_if_successful(); + } + // Process occasional tasks sent from other thread (probably main thread) + let normal_task_count = self.normal_task_receiver.len(); + for task in self.normal_task_receiver.try_iter().take(NORMAL_BULK_SIZE) { + use NormalRealTimeTask::*; + match task { + UpdateControlIsGloballyEnabled(is_enabled) => { + self.control_is_globally_enabled = is_enabled; + } + UpdateFeedbackIsGloballyEnabled(is_enabled) => { + // Handle lifecycle MIDI + if self.settings.midi_destination().is_some() + && is_enabled != self.feedback_is_globally_enabled + { + self.send_lifecycle_midi_for_all_mappings(is_enabled.into()); + } + // Set + self.feedback_is_globally_enabled = is_enabled; + } + UpdateAllMappings(compartment, mappings) => { + permit_alloc(|| { + debug!("Updating {} mappings in {}...", mappings.len(), compartment); + }); + // Handle deactivation MIDI + if self.processor_feedback_is_effectively_on() { + self.send_lifecycle_midi_for_all_mappings_in( + compartment, + LifecyclePhase::Deactivation, + ); + } + // Clear existing mappings + self.mappings[compartment].clear(); + // Set new mappings + self.mappings[compartment].extend(mappings.into_iter().map(|m| (m.id(), m))); + // Handle activation MIDI + if self.processor_feedback_is_effectively_on() { + self.send_lifecycle_midi_for_all_mappings_in( + compartment, + LifecyclePhase::Activation, + ); + } + } + UpdateSingleMapping(compartment, m) => { + permit_alloc(|| { + debug!("Updating single mapping {:?} in {}...", m.id(), compartment,); + }); + // Send lifecycle MIDI + if self.processor_feedback_is_effectively_on() { + let was_on_before = self.mappings[compartment] + .get(&m.id()) + .is_some_and(|m| m.feedback_is_effectively_on()); + let is_on_now = m.feedback_is_effectively_on(); + self.send_lifecycle_midi_diff(&m, was_on_before, is_on_now) + } + // Update + self.mappings[compartment].insert(m.id(), *m); + } + UpdatePersistentMappingProcessingState { id, state } => { + permit_alloc(|| { + debug!( + "Updating persistent state of {:?} in {}...", + id.id, id.compartment + ); + }); + // Update + let (was_on_before, is_on_now) = + if let Some(m) = self.mappings[id.compartment].get_mut(&id.id) { + let was_on_before = m.feedback_is_effectively_on(); + m.update_persistent_processing_state(state); + (was_on_before, m.feedback_is_effectively_on()) + } else { + (false, false) + }; + // Send lifecycle MIDI + if self.processor_feedback_is_effectively_on() { + if let Some(m) = self.mappings[id.compartment].get(&id.id) { + self.send_lifecycle_midi_diff(m, was_on_before, is_on_now); + } + } + } + UpdateTargetsPartially(compartment, mut target_updates) => { + // Apply updates + for update in target_updates.iter_mut() { + if let Some(m) = self.mappings[compartment].get_mut(&update.id) { + m.update_target(update); + } + } + // Handle lifecycle MIDI + if self.processor_feedback_is_effectively_on() { + for update in target_updates.iter() { + if let Some(activation_change) = update.activation_change { + if let Some(m) = self.mappings[compartment].get(&update.id) { + if m.feedback_is_effectively_on_ignoring_target_activation() { + self.send_lifecycle_midi_to_feedback_output_from_audio_hook( + m, + activation_change.is_active.into(), + ); + } + } + } + } + } + } + UpdateSettings(settings) => { + permit_alloc(|| { + debug!("Updating settings..."); + }); + let prev_midi_destination = self.settings.midi_destination(); + let next_midi_destination = settings.midi_destination(); + self.settings = settings; + let midi_destination_changing = prev_midi_destination != next_midi_destination; + // Handle deactivation + if self.processor_feedback_is_effectively_on() && midi_destination_changing { + self.send_lifecycle_midi_for_all_mappings(LifecyclePhase::Deactivation); + } + // Handle activation + if self.processor_feedback_is_effectively_on() && midi_destination_changing { + self.send_lifecycle_midi_for_all_mappings(LifecyclePhase::Activation); + } + } + UpdateSampleRate(sample_rate) => { + permit_alloc(|| { + debug!("Updating sample rate"); + }); + self.sample_rate = sample_rate; + } + StartLearnSource { + allow_virtual_sources, + } => { + permit_alloc(|| { + debug!("Start learning source"); + }); + self.control_mode = ControlMode::LearningSource { + allow_virtual_sources, + osc_arg_index_hint: None, + }; + self.midi_scanner.reset(); + } + DisableControl => { + permit_alloc(|| { + debug!("Disable control"); + }); + self.control_mode = ControlMode::Disabled; + } + ReturnToControlMode => { + permit_alloc(|| { + debug!("Return to control mode"); + }); + self.control_mode = ControlMode::Controlling; + self.nrpn_scanner.reset(); + self.cc_14_bit_scanner.reset(); + } + LogDebugInfo => { + self.log_debug_info(normal_task_count); + } + LogMapping(compartment, mapping_id) => { + self.log_mapping(compartment, mapping_id); + } + UpdateMappingsPartially(compartment, mapping_updates) => { + permit_alloc(|| { + debug!("Updating mapping activations..."); + }); + // Apply updates + for update in mapping_updates.iter() { + if let Some(m) = self.mappings[compartment].get_mut(&update.id) { + m.update(update); + } + } + // Handle lifecycle MIDI + if self.processor_feedback_is_effectively_on() { + for update in mapping_updates.iter() { + if let Some(m) = self.mappings[compartment].get(&update.id) { + if let Some(activation_change) = update.activation_change { + if m.feedback_is_effectively_on_ignoring_mapping_activation() { + self.send_lifecycle_midi_to_feedback_output_from_audio_hook( + m, + activation_change.is_active.into(), + ); + } + } + } + } + } + } + } + } + } + + fn send_lifecycle_midi_diff(&self, m: &RealTimeMapping, was_on_before: bool, is_on_now: bool) { + if is_on_now { + self.send_lifecycle_midi_to_feedback_output_from_audio_hook( + m, + LifecyclePhase::Activation, + ); + } else if was_on_before { + self.send_lifecycle_midi_to_feedback_output_from_audio_hook( + m, + LifecyclePhase::Deactivation, + ); + } + } + + fn processor_feedback_is_effectively_on(&self) -> bool { + self.feedback_is_globally_enabled && self.settings.midi_destination().is_some() + } + + fn send_lifecycle_midi_for_all_mappings(&self, phase: LifecyclePhase) { + for compartment in CompartmentKind::enum_iter() { + self.send_lifecycle_midi_for_all_mappings_in(compartment, phase); + } + } + + fn send_lifecycle_midi_for_all_mappings_in( + &self, + compartment: CompartmentKind, + phase: LifecyclePhase, + ) { + for m in self.mappings[compartment].values() { + if m.feedback_is_effectively_on() { + self.send_lifecycle_midi_to_feedback_output_from_audio_hook(m, phase); + } + } + } + + /// This should *not* be called by the global audio hook when it's globally capturing MIDI + /// because we want to pause controlling in that case! + fn run_from_audio_hook_control_and_learn(&mut self, timestamp: ControlEventTimestamp) { + match self.control_mode { + ControlMode::Disabled => {} + ControlMode::Controlling => { + // This NRPN scanner is just for controlling, not for learning. + if self.control_is_globally_enabled { + // Poll (N)RPN scanner + for ch in 0..16 { + if let Some(nrpn_msg) = self.nrpn_scanner.poll(Channel::new(ch)) { + // TODO-medium We should memorize the offset of the latest short message + // making up the NRPN message instead! + let midi_event = MidiEvent::without_offset(nrpn_msg); + let control_event = ControlEvent::new(midi_event, timestamp); + self.process_incoming_midi_normal_nrpn( + control_event, + Caller::AudioHook, + &mut None, + ); + } + } + } + } + ControlMode::LearningSource { + allow_virtual_sources, + .. + } => { + // For local learning/filtering + if let Some(res) = self.midi_scanner.poll() { + self.send_captured_midi(res, allow_virtual_sources); + } + } + } + } + + fn process_feedback_tasks(&self, caller: Caller) { + // Process (frequent) feedback tasks sent from other thread (probably main thread) + for task in self + .feedback_task_receiver + .try_iter() + .take(FEEDBACK_BULK_SIZE) + { + use FeedbackRealTimeTask::*; + match task { + FxOutputFeedback(v) => { + // If the feedback driver is not VST, this will be discarded, no problem. + self.send_midi_feedback(v, caller); + } + SendLifecycleMidi(compartment, mapping_id, phase) => { + if let Some(m) = self.mappings[compartment].get(&mapping_id) { + self.send_lifecycle_midi_to_fx_output( + m.lifecycle_midi_messages(phase), + caller, + ); + } + } + NonAllocatingFxOutputFeedback(evt, sample_offset) => { + send_raw_midi_to_fx_output(evt.bytes(), sample_offset, caller); + } + } + } + } + + fn log_debug_info(&self, task_count: usize) { + // Summary + permit_alloc(|| { + let msg = format!( + "\n\ + # Real-time processor\n\ + \n\ + - Instance ID: {} \n\ + - State: {:?} \n\ + - Total main mapping count: {} \n\ + - Enabled main mapping count: {} \n\ + - Total controller mapping count: {} \n\ + - Enabled controller mapping count: {} \n\ + - Normal task count: {} \n\ + - Feedback task count: {} \n\ + ", + self.unit_id, + self.control_mode, + self.mappings[CompartmentKind::Main].len(), + self.mappings[CompartmentKind::Main] + .values() + .filter(|m| m.control_is_effectively_on()) + .count(), + self.mappings[CompartmentKind::Controller].len(), + self.mappings[CompartmentKind::Controller] + .values() + .filter(|m| m.control_is_effectively_on()) + .count(), + task_count, + self.feedback_task_receiver.len(), + ); + self.normal_main_task_sender + .send_complaining(NormalRealTimeToMainThreadTask::LogToConsole(msg)); + // Detailled + trace!( + "\n\ + # Real-time processor\n\ + \n\ + {:#?} + ", + self + ); + }); + } + + fn log_mapping(&self, compartment: CompartmentKind, mapping_id: MappingId) { + permit_alloc(|| { + let mapping = self.mappings[compartment].get(&mapping_id); + let msg = format!( + "\n\ + # Real-time processor\n\ + \n\ + Mapping with ID {mapping_id}:\n\ + {mapping:#?} + " + ); + self.normal_main_task_sender + .send_complaining(NormalRealTimeToMainThreadTask::LogToConsole(msg)); + }); + } + + fn process_incoming_midi( + &mut self, + event: ControlEvent>, + caller: Caller, + mut transformation_container: Option<&mut MidiTransformationContainer>, + ) -> MatchOutcome { + use MidiMessageClassification::*; + match classify_midi_message(event.payload().payload()) { + Normal => self.process_incoming_midi_normal(event, caller, transformation_container), + Ignored => { + // ReaLearn doesn't process those. Forward them if user wants it. + self.process_unmatched(event.payload(), caller); + MatchOutcome::Unmatched + } + Timing => { + // Timing clock messages are treated special (calculates BPM). Matching each tick with + // mappings would be a waste of resources. We know they come in very densely. + // This is control-only, we never learn it. + if self.control_is_globally_enabled { + if let Some(bpm) = self.midi_clock_calculator.feed(event.timestamp()) { + let source_value = MidiSourceValue::::Tempo(bpm); + self.control_midi( + event.with_payload(MidiEvent::new( + event.payload().offset(), + &source_value, + )), + caller, + &mut transformation_container, + ) + } else { + MatchOutcome::Unmatched + } + } else { + MatchOutcome::Unmatched + } + } + } + } + + /// This basically splits the stream of short MIDI messages into 3 streams: + /// + /// - (N)RPN messages + /// - 14-bit CC messages + /// - Short MIDI messaages + fn process_incoming_midi_normal( + &mut self, + event: ControlEvent>, + caller: Caller, + mut transformation_container: Option<&mut MidiTransformationContainer>, + ) -> MatchOutcome { + match self.control_mode { + ControlMode::Controlling => { + if self.control_is_globally_enabled { + // Even if a composite message ((N)RPN or CC 14-bit) was scanned, we still + // process the plain short MIDI message. This is desired. + // Rationale: If there's no mapping with a composite source + // of this kind, then all the CCs potentially involved in + // composite messages can still be used separately (e.g. CC + // 6, 38, 98, etc.). That's important! However, if there's + // at least one mapping source that listens to composite + // messages of the incoming kind, we need to make sure that the + // single messages can't be used anymore! Otherwise it would be + // confusing. They are consumed. That's the reason why + // we do the consumption check at a later state. + let plain_match_outcome = self.process_incoming_midi_normal_plain( + event, + caller, + &mut transformation_container, + ); + let midi_event = event.payload(); + let (nrpn_match_outcome, cc14_match_outcome) = match midi_event.payload() { + IncomingMidiMessage::Short(short_msg) => { + let mut nrpn_match_outcome = MatchOutcome::Unmatched; + for nrpn_msg in self.nrpn_scanner.feed(&short_msg).iter().flatten() { + let nrpn_event = event + .with_payload(MidiEvent::new(midi_event.offset(), *nrpn_msg)); + let child_match_outcome = self.process_incoming_midi_normal_nrpn( + nrpn_event, + caller, + &mut transformation_container, + ); + nrpn_match_outcome.upgrade_from(child_match_outcome); + } + let cc14_match_outcome = if let Some(cc14_msg) = + self.cc_14_bit_scanner.feed(&short_msg) + { + let cc14_event = event + .with_payload(MidiEvent::new(midi_event.offset(), cc14_msg)); + self.process_incoming_midi_normal_cc14( + cc14_event, + caller, + &mut transformation_container, + ) + } else { + MatchOutcome::Unmatched + }; + (nrpn_match_outcome, cc14_match_outcome) + } + // A sys-ex message is never part of a compound message. + IncomingMidiMessage::SysEx(_) => { + (MatchOutcome::Unmatched, MatchOutcome::Unmatched) + } + }; + plain_match_outcome + .merge_with(nrpn_match_outcome) + .merge_with(cc14_match_outcome) + } else { + MatchOutcome::Unmatched + } + } + ControlMode::LearningSource { + allow_virtual_sources, + .. + } => { + if self.settings.real_input_logging_enabled { + self.log_real_learn_input(event.map_payload(|e| e.payload())); + } + let scan_result = match event.payload().payload() { + IncomingMidiMessage::Short(short_msg) => { + self.midi_scanner.feed_short(short_msg, None) + } + IncomingMidiMessage::SysEx(bytes) => { + // It's okay here to temporarily permit allocation because crackling during + // learning is not a showstopper. + permit_alloc(|| MidiScanResult::try_from_bytes(bytes, None).ok()) + } + }; + if let Some(source) = scan_result { + self.send_captured_midi(source, allow_virtual_sources); + } + MatchOutcome::Consumed + } + ControlMode::Disabled => { + // "Disabled" means we use this for global learning! We consider this therefore as + // consumed. + MatchOutcome::Consumed + } + } + } + + /// Returns whether this message matched. + fn process_incoming_midi_normal_nrpn( + &mut self, + event: ControlEvent>, + caller: Caller, + transformation_container: &mut Option<&mut MidiTransformationContainer>, + ) -> MatchOutcome { + let midi_event = event.payload(); + let source_value = + MidiSourceValue::::ParameterNumber(midi_event.payload()); + let match_outcome = self.control_midi( + event.with_payload(MidiEvent::new(midi_event.offset(), &source_value)), + caller, + transformation_container, + ); + if self.settings.real_input_logging_enabled { + self.log_real_control_input_internal(event.with_payload(source_value), match_outcome); + } + if self.settings.midi_control_input() == MidiControlInput::FxInput + && ((match_outcome.matched_or_consumed() && self.settings.let_matched_events_through) + || (!match_outcome.matched_or_consumed() + && self.settings.let_unmatched_events_through)) + { + for m in event + .payload() + .payload() + .to_short_messages::(DataEntryByteOrder::MsbFirst) + .iter() + .flatten() + { + self.send_short_midi_to_fx_output( + MidiEvent::new(event.payload().offset(), *m), + caller, + ); + } + } + match_outcome + } + + /// Might allocate! + fn log_real_control_input( + &self, + evt: ControlEvent>, + consumed: bool, + matched: bool, + ) { + let match_outcome = if consumed { + MatchOutcome::Consumed + } else if matched { + MatchOutcome::Matched + } else { + MatchOutcome::Unmatched + }; + self.log_real_control_input_internal(evt, match_outcome) + } + + fn log_real_control_input_internal( + &self, + evt: ControlEvent>, + match_outcome: MatchOutcome, + ) { + // It's okay to crackle when logging input. + let timestamp = evt.timestamp(); + let owned_msg = permit_alloc(|| evt.into_payload().try_into_owned()); + if let Ok(msg) = owned_msg { + self.control_main_task_sender + .send_complaining(ControlMainTask::LogRealControlInput { + event: ControlEvent::new(msg, timestamp), + match_outcome, + }); + } + } + + /// Might allocate! + fn log_real_learn_input(&self, evt: ControlEvent) { + // It's okay if we crackle when logging input. + let owned_msg = permit_alloc(|| evt.payload().to_owned()); + self.control_main_task_sender + .send_complaining(ControlMainTask::LogRealLearnInput { + event: evt.with_payload(owned_msg), + }); + } + + /// Might allocate! + fn log_lifecycle_output(&self, value: MidiSourceValue) { + // It's okay to crackle when logging input. + if let Ok(value) = permit_alloc(|| value.try_into_owned()) { + self.normal_main_task_sender + .send_complaining(NormalRealTimeToMainThreadTask::LogLifecycleOutput { value }); + } + } + + fn send_captured_midi(&mut self, scan_result: MidiScanResult, allow_virtual_sources: bool) { + // If plug-in dropped, the receiver might be gone already because main processor is + // unregistered synchronously. + self.normal_main_task_sender + .send_if_space(NormalRealTimeToMainThreadTask::CaptureMidi { + scan_result, + allow_virtual_sources, + }); + } + + /// Returns whether this message matched. + fn process_incoming_midi_normal_cc14( + &mut self, + event: ControlEvent>, + caller: Caller, + transformation_container: &mut Option<&mut MidiTransformationContainer>, + ) -> MatchOutcome { + let midi_event = event.payload(); + let source_value = + MidiSourceValue::::ControlChange14Bit(midi_event.payload()); + let match_outcome = self.control_midi( + event.with_payload(MidiEvent::new(midi_event.offset(), &source_value)), + caller, + transformation_container, + ); + if self.settings.real_input_logging_enabled { + self.log_real_control_input_internal(event.with_payload(source_value), match_outcome); + } + if self.settings.midi_control_input() == MidiControlInput::FxInput + && ((match_outcome.matched_or_consumed() && self.settings.let_matched_events_through) + || (!match_outcome.matched_or_consumed() + && self.settings.let_unmatched_events_through)) + { + for m in midi_event + .payload() + .to_short_messages::() + .iter() + { + let short_event = MidiEvent::new(midi_event.offset(), *m); + self.send_short_midi_to_fx_output(short_event, caller); + } + } + match_outcome + } + + fn process_incoming_midi_normal_plain( + &mut self, + event: ControlEvent>, + caller: Caller, + transformation_container: &mut Option<&mut MidiTransformationContainer>, + ) -> MatchOutcome { + let midi_event = event.payload(); + let source_value = midi_event.payload().to_source_value(); + if self.is_consumed_by_at_least_one_source(midi_event.payload()) { + if self.settings.real_input_logging_enabled { + self.log_real_control_input(event.with_payload(source_value), true, false); + } + // Some short MIDI messages are just parts of bigger composite MIDI messages, + // e.g. (N)RPN or 14-bit CCs. If we reach this point, the incoming message + // could potentially match one of the (N)RPN or 14-bit CC mappings in the list + // and therefore doesn't qualify anymore as a candidate for normal CC sources. + return MatchOutcome::Consumed; + } + let match_outcome = self.control_midi( + event.with_payload(MidiEvent::new(midi_event.offset(), &source_value)), + caller, + transformation_container, + ); + if self.settings.real_input_logging_enabled { + self.log_real_control_input_internal(event.with_payload(source_value), match_outcome); + } + // At this point, we shouldn't have "consumed" anymore because for MIDI sources, no + // control will be done at all if a message is consumed by at least one mapping (see above). + if match_outcome.matched_or_consumed() { + self.process_matched_short(midi_event, caller); + } else { + self.process_unmatched(midi_event, caller); + } + match_outcome + } + + fn all_mappings(&self) -> impl Iterator { + CompartmentKind::enum_iter() + .flat_map(move |compartment| self.mappings[compartment].values()) + } + + fn control_midi( + &mut self, + value_event: ControlEvent>>, + caller: Caller, + transformation_container: &mut Option<&mut MidiTransformationContainer>, + ) -> MatchOutcome { + let is_rendering = is_rendering(); + // We do pattern matching in order to use Rust's borrow splitting. + let controller_outcome = if let [ref mut controller_mappings, ref mut main_mappings] = + self.mappings.as_mut_slice() + { + control_controller_mappings_midi( + &self.settings, + &self.control_main_task_sender, + &self.feedback_task_sender, + controller_mappings, + main_mappings, + value_event, + caller, + &self.instance, + is_rendering, + transformation_container, + ) + } else { + unreachable!() + }; + let main_outcome = self.control_main_mappings_midi( + value_event, + caller, + is_rendering, + transformation_container, + ); + controller_outcome.merge_with(main_outcome) + } + + fn control_main_mappings_midi( + &mut self, + source_value_event: ControlEvent>>, + caller: Caller, + is_rendering: bool, + transformation_container: &mut Option<&mut MidiTransformationContainer>, + ) -> MatchOutcome { + let match_inactive = self.settings.match_even_inactive_mappings; + let compartment = CompartmentKind::Main; + let mut match_outcome = MatchOutcome::Unmatched; + for m in self.mappings[compartment] + .values_mut() + // Consider only control-enabled real mappings. + // The UI prevents creating main mappings with virtual targets but a JSON import + // doesn't. Check again that it's a REAPER target. + .filter(|m| m.core.options.control_is_enabled && m.has_reaper_target()) + { + let matched_already_before = match_outcome.matched(); + let mapping_is_active = m.is_active(); + if !mapping_is_active && !match_inactive { + continue; + } + let CompoundMappingSource::Midi(s) = &m.source() else { + continue; + }; + let midi_event = source_value_event.payload(); + let Some(control_value) = s.control(midi_event.payload()) else { + continue; + }; + // It can't be consumed because we checked this before for all mappings. + if mapping_is_active { + let args = ProcessRtMappingArgs { + main_task_sender: &self.control_main_task_sender, + rt_feedback_sender: &self.feedback_task_sender, + compartment, + value_event: source_value_event + .with_payload(MidiEvent::new(midi_event.offset(), control_value)), + options: ControlOptions { + enforce_send_feedback_after_control: false, + mode_control_options: Default::default(), + enforce_target_refresh: matched_already_before, + coming_from_real_time: true, + }, + caller, + midi_feedback_output: self.settings.midi_destination(), + log_options: LogOptions::from_basic_settings(&self.settings), + instance: &self.instance, + is_rendering, + transformation_container, + }; + process_real_mapping(m, args); + } + match_outcome = MatchOutcome::Matched; + } + match_outcome + } + + fn process_matched_short(&self, event: MidiEvent, caller: Caller) { + if self.settings.midi_control_input() != MidiControlInput::FxInput { + return; + } + if !self.settings.let_matched_events_through { + return; + } + self.send_incoming_midi_to_fx_output(event, caller); + } + + fn process_unmatched(&self, event: MidiEvent, caller: Caller) { + if self.settings.midi_control_input() != MidiControlInput::FxInput { + return; + } + if !self.settings.let_unmatched_events_through { + return; + } + self.send_incoming_midi_to_fx_output(event, caller); + } + + fn is_consumed_by_at_least_one_source(&self, msg: IncomingMidiMessage) -> bool { + use IncomingMidiMessage::*; + match msg { + Short(msg) => self + .all_mappings() + .any(|m| m.control_is_effectively_on() && m.consumes(msg)), + // Sys-ex is never part of a compound message. + SysEx(_) => false, + } + } + + fn send_midi_feedback(&self, value: MidiSourceValue, caller: Caller) { + if let Some(evts) = value.to_raw() { + // TODO-medium We can implement in a way so we only need one host.process_events() call. + for evt in evts { + send_raw_midi_to_fx_output(evt.bytes(), SampleOffset::ZERO, caller); + } + } else { + let shorts = value.to_short_messages(DataEntryByteOrder::MsbFirst); + if shorts[0].is_none() { + return; + } + for short in shorts.iter().flatten() { + self.send_short_midi_to_fx_output(MidiEvent::without_offset(*short), caller); + } + } + } + + fn send_lifecycle_midi_to_feedback_output_from_audio_hook( + &self, + m: &RealTimeMapping, + phase: LifecyclePhase, + ) { + if let Some(output) = self.settings.midi_destination() { + match output { + MidiDestination::FxOutput => { + // We can't send it now because we don't have safe access to the host callback + // because this method is being called from the audio hook. + self.feedback_task_sender.send_if_space( + FeedbackRealTimeTask::SendLifecycleMidi(m.compartment(), m.id(), phase), + ); + } + MidiDestination::Device(dev_id) => { + MidiOutputDevice::new(dev_id).with_midi_output(|mo| { + if let Some(mo) = mo { + for m in m.lifecycle_midi_messages(phase) { + match m { + LifecycleMidiMessage::Short(msg) => { + if self.settings.real_output_logging_enabled { + self.log_lifecycle_output(MidiSourceValue::Plain(*msg)); + } + mo.send(*msg, SendMidiTime::Instantly); + } + LifecycleMidiMessage::Raw(data) => { + if self.settings.real_output_logging_enabled { + permit_alloc(|| { + // We don't use this as feedback value, + // at least not in the sense that it + // participates in feedback relay. + let feedback_address_info = None; + let value = MidiSourceValue::single_raw( + feedback_address_info, + *data.clone(), + ); + self.log_lifecycle_output(value); + }); + } + mo.send_msg(**data, SendMidiTime::Instantly); + } + } + } + } + }); + } + }; + } + } + + fn send_lifecycle_midi_to_fx_output(&self, messages: &[LifecycleMidiMessage], caller: Caller) { + for m in messages { + match m { + LifecycleMidiMessage::Short(msg) => { + if self.settings.real_output_logging_enabled { + self.log_lifecycle_output(MidiSourceValue::Plain(*msg)); + } + self.send_short_midi_to_fx_output(MidiEvent::without_offset(*msg), caller) + } + LifecycleMidiMessage::Raw(data) => { + if self.settings.real_output_logging_enabled { + permit_alloc(|| { + // We don't use this as feedback value, + // at least not in the sense that it + // participates in feedback relay. + let feedback_address_info = None; + let value = + MidiSourceValue::single_raw(feedback_address_info, *data.clone()); + self.log_lifecycle_output(value); + }); + } + send_raw_midi_to_fx_output(data.bytes(), SampleOffset::ZERO, caller) + } + } + } + } + + fn send_incoming_midi_to_fx_output( + &self, + event: MidiEvent, + caller: Caller, + ) { + match event.payload() { + IncomingMidiMessage::Short(s) => { + self.send_short_midi_to_fx_output(MidiEvent::new(event.offset(), s), caller); + } + IncomingMidiMessage::SysEx(s) => send_raw_midi_to_fx_output(s, event.offset(), caller), + } + } + + fn send_short_midi_to_fx_output(&self, event: MidiEvent, caller: Caller) { + let host = match caller { + Caller::Vst(h) => h, + _ => { + // We must not forward MIDI to VST output if this was called from the global audio + // hook. First, it could lead to strange effects because + // `HostCallback::process_events()` is supposed to be called only + // from the VST processing method. Second, it could even lead to a + // crash because the real-time processor is removed from + // the audio hook *after* the plug-in has been already unregistered, and then + // invoking the host callback (in particular dereferencing the + // AEffect) would be illegal. This is just a last safety check. + // Processing should stop before even calling this method. + return; + } + }; + let vst_event = build_short_midi_vst_event(event); + let vst_events = build_vst_events(&vst_event as *const _ as _); + host.process_events(&vst_events); + } +} + +fn build_vst_events(event: *mut vst::api::Event) -> Events { + Events { + num_events: 1, + _reserved: 0, + events: [event, null_mut()], + } +} + +fn build_sysex_midi_vst_event(bytes: &[u8], offset: SampleOffset) -> SysExEvent { + SysExEvent { + event_type: EventType::SysEx, + byte_size: std::mem::size_of::() as _, + delta_frames: offset.get() as _, + _flags: 0, + data_size: bytes.len() as _, + _reserved1: 0, + system_data: bytes.as_ptr() as _, + _reserved2: 0, + } +} + +fn build_short_midi_vst_event(event: MidiEvent) -> vst::api::MidiEvent { + let bytes = event.payload().to_bytes(); + vst::api::MidiEvent { + event_type: EventType::Midi, + byte_size: std::mem::size_of::() as _, + delta_frames: event.offset().get() as _, + flags: vst::api::MidiEventFlags::REALTIME_EVENT.bits(), + note_length: 0, + note_offset: 0, + midi_data: [bytes.0, bytes.1.get(), bytes.2.get()], + _midi_reserved: 0, + detune: 0, + note_off_velocity: 0, + _reserved1: 0, + _reserved2: 0, + } +} + +#[derive(Copy, Clone)] +pub enum Caller<'a> { + Vst(&'a HostCallback), + AudioHook, +} + +impl Caller<'_> { + pub fn is_vst(&self) -> bool { + matches!(self, Self::Vst(_)) + } +} + +/// A task which is sent from time to time. +#[derive(Debug)] +pub enum NormalRealTimeTask { + UpdateAllMappings(CompartmentKind, Vec), + UpdateSingleMapping(CompartmentKind, Box), + UpdatePersistentMappingProcessingState { + id: QualifiedMappingId, + state: PersistentMappingProcessingState, + }, + UpdateSettings(BasicSettings), + /// This takes care of propagating target activation states and/or real-time target updates + /// (for non-virtual mappings). + UpdateTargetsPartially(CompartmentKind, Vec), + /// Updates the activation state of multiple mappings. + /// + /// The given vector contains updates just for affected mappings. This is because when a + /// parameter update occurs we can determine in a very granular way which targets are affected. + UpdateMappingsPartially(CompartmentKind, Vec), + LogDebugInfo, + LogMapping(CompartmentKind, MappingId), + UpdateSampleRate(Hz), + StartLearnSource { + allow_virtual_sources: bool, + }, + DisableControl, + ReturnToControlMode, + UpdateControlIsGloballyEnabled(bool), + UpdateFeedbackIsGloballyEnabled(bool), +} + +#[derive(Copy, Clone, Debug)] +pub struct MappingActivationEffect { + pub id: MappingId, + pub active_1_effect: Option, + pub active_2_effect: Option, +} + +impl MappingActivationEffect { + pub fn new( + id: MappingId, + active_1_effect: Option, + active_2_effect: Option, + ) -> Option { + if active_1_effect.is_none() && active_2_effect.is_none() { + return None; + } + let and = MappingActivationEffect { + id, + active_1_effect, + active_2_effect, + }; + Some(and) + } +} + +/// Depending on the context this can be about mapping activation or target activation. +/// +/// It's important that this reflects an actual change, otherwise the real-time processor might +/// send lifecycle MIDI data in the wrong situations. +#[derive(Copy, Clone, Debug)] +pub struct ActivationChange { + pub is_active: bool, +} + +#[derive(Debug)] +pub struct RealTimeTargetUpdate { + pub id: MappingId, + pub activation_change: Option, + pub target_change: Option>, +} + +#[derive(Debug)] +pub struct RealTimeMappingUpdate { + pub id: MappingId, + pub activation_change: Option, +} + +/// A feedback task (which is potentially sent very frequently). +#[derive(Debug)] +// TODO-high-playtime-refactoring Might want to fix this. +#[allow(clippy::large_enum_variant)] +pub enum FeedbackRealTimeTask { + /// When it comes to MIDI feedback, the real-time processor is only responsible for FX output + /// feedback. Direct-device feedback is taken care of by the global audio hook for reasons of + /// proper ordering. + FxOutputFeedback(MidiSourceValue<'static, RawShortMessage>), + /// If we send raw MIDI events from the "MIDI: Send message" target to "FX output" and the input + /// is a MIDI device (not FX input), we must very shortly defer sending the message. + /// Reason: This message arrives from the audio hook. However, we can't forward to FX output + /// from the audio hook, we must wait until the VST process method is invoked. In order to let + /// the MIDI event survive, we need to copy it. But we are not allowed to allocate, so the + /// usual MidiSourceValue Raw variant is not suited. + NonAllocatingFxOutputFeedback(RawMidiEvent, SampleOffset), + /// Used only if feedback output is , otherwise done synchronously. + SendLifecycleMidi(CompartmentKind, MappingId, LifecyclePhase), +} + +impl Drop for RealTimeProcessor { + fn drop(&mut self) { + permit_alloc(|| { + debug!("Dropping real-time processor..."); + }); + } +} + +/// MIDI source which provides ReaLearn control data. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum MidiControlInput { + /// Processes MIDI messages which are fed into ReaLearn FX. + FxInput, + /// Processes MIDI messages coming directly from a MIDI input device. + Device(MidiInputDeviceId), +} + +/// MIDI destination to which e.g. ReaLearn's feedback data can be sent. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum MidiDestination { + /// Routes messages to the ReaLearn FX output. + FxOutput, + /// Routes messages directly to a MIDI output device. + Device(MidiOutputDeviceId), +} + +#[allow(clippy::too_many_arguments)] +fn control_controller_mappings_midi( + settings: &BasicSettings, + main_task_sender: &SenderToNormalThread, + rt_feedback_sender: &SenderToRealTimeThread, + // Mappings with virtual targets + controller_mappings: &mut OrderedMappingMap, + // Mappings with virtual sources + main_mappings: &mut OrderedMappingMap, + value_event: ControlEvent>>, + caller: Caller, + instance: &WeakRealTimeInstance, + is_rendering: bool, + transformation_container: &mut Option<&mut MidiTransformationContainer>, +) -> MatchOutcome { + let match_inactive = settings.match_even_inactive_mappings; + let midi_feedback_output = settings.midi_destination(); + let log_options = LogOptions::from_basic_settings(settings); + let mut match_outcome = MatchOutcome::Unmatched; + let mut enforce_target_refresh = false; + let evt = flatten_control_midi_event(value_event); + for m in controller_mappings + .values_mut() + .filter(|m| m.core.options.control_is_enabled) + { + let mapping_is_active = m.is_active(); + if !mapping_is_active && !match_inactive { + continue; + } + let virtual_target = + if let Some(RealTimeCompoundMappingTarget::Virtual(t)) = m.resolved_target.as_ref() { + Some(t) + } else { + None + }; + if virtual_target.is_some() && !mapping_is_active { + // For mappings with virtual targets, the setting "Match even inactive mappings" isn't relevant. + // If such mappings are inactive, they will never be considered as matched. Only associated main mappings + // decide about the match result. + continue; + } + let CompoundMappingSource::Midi(s) = &m.core.source else { + continue; + }; + let Some(control_value) = s.control(evt.payload()) else { + continue; + }; + if let Some(virtual_target) = virtual_target { + // Virtual control + let Some(virtual_source_value) = + match_partially(&mut m.core, virtual_target, evt.with_payload(control_value)) + else { + continue; + }; + let virtual_match_outcome = control_main_mappings_virtual( + settings, + main_task_sender, + rt_feedback_sender, + main_mappings, + value_event.with_payload(MidiEvent::new( + value_event.payload().offset(), + virtual_source_value, + )), + ControlOptions { + // We inherit "Send feedback after control" to the main processor if it's + // enabled for the virtual mapping. That's the easy way to do it. + // Downside: If multiple real control elements are mapped to one virtual + // control element, "feedback after control" will be sent to all of those, + // which is technically not necessary. It would be enough to just send it + // to the one that was touched. However, it also doesn't really hurt. + enforce_send_feedback_after_control: m.options().feedback_send_behavior + == FeedbackSendBehavior::SendFeedbackAfterControl, + mode_control_options: m.mode_control_options(), + // Not important yet at this point because virtual targets can't affect + // subsequent virtual targets. + enforce_target_refresh: false, + coming_from_real_time: true, + }, + caller, + instance, + is_rendering, + transformation_container, + ); + if log_options.virtual_input_logging_enabled { + log_virtual_control_input( + main_task_sender, + value_event.with_payload(virtual_source_value), + virtual_match_outcome, + ); + } + match_outcome.upgrade_from(virtual_match_outcome); + } else { + // Real control + match_outcome.upgrade_from(MatchOutcome::Matched); + if !mapping_is_active { + continue; + } + if !m.target_is_resolved { + continue; + } + let args = ProcessRtMappingArgs { + main_task_sender, + rt_feedback_sender, + compartment: CompartmentKind::Controller, + value_event: value_event.with_payload(MidiEvent::new( + value_event.payload().offset(), + control_value, + )), + options: ControlOptions { + enforce_send_feedback_after_control: false, + mode_control_options: Default::default(), + enforce_target_refresh, + coming_from_real_time: true, + }, + caller, + midi_feedback_output, + log_options, + instance, + is_rendering, + transformation_container, + }; + process_real_mapping(m, args); + // We do this only for transactions of *real* target matches. + enforce_target_refresh = true; + } + } + match_outcome +} + +struct ProcessRtMappingArgs<'a, 'b> { + main_task_sender: &'a SenderToNormalThread, + rt_feedback_sender: &'a SenderToRealTimeThread, + compartment: CompartmentKind, + value_event: ControlEvent>, + options: ControlOptions, + caller: Caller<'a>, + midi_feedback_output: Option, + log_options: LogOptions, + instance: &'a WeakRealTimeInstance, + is_rendering: bool, + transformation_container: &'a mut Option<&'b mut MidiTransformationContainer>, +} + +#[allow(clippy::too_many_arguments)] +fn process_real_mapping(mapping: &mut RealTimeMapping, mut args: ProcessRtMappingArgs) { + let pure_control_event = flatten_control_midi_event(args.value_event); + // At first check if this target is capable of real-time control (= not taking a detour into the main thread). + if let Some(RealTimeCompoundMappingTarget::Reaper(reaper_target)) = + mapping.resolved_target.as_mut() + { + // We have a real-time-capable target + if reaper_target.wants_real_time_control(args.caller, args.is_rendering) { + let forward_to_main_thread = process_real_mapping_in_real_time( + &mut mapping.core, + &mut args, + pure_control_event, + reaper_target, + ); + if !forward_to_main_thread { + // We are done here. + return; + } + } + } + // Looks like forwarding to main thread is necessary, e.g. because target is either not a real-time target (in most + // cases) or doesn't want real-time control at the moment or detected that some things can only be done in the main + // thread. + if args.is_rendering { + return; + } + forward_control_to_main_processor( + args.main_task_sender, + args.compartment, + mapping.id(), + pure_control_event, + args.options, + ); +} + +/// This returns `true` if the consumer should also forward this event to the main thread (used for example for +/// Playtime's trigger-slot action in order to possibly record when slot empty and/or activate the triggered slot). +fn process_real_mapping_in_real_time( + mapping_core: &mut MappingCore, + args: &mut ProcessRtMappingArgs, + pure_control_event: ControlEvent, + reaper_target: &mut RealTimeReaperTarget, +) -> bool { + // REAPER real-time target actually wants real-time control. Try to process directly here in real-time. + let control_context = RealTimeControlContext { + instance: args.instance, + _p: &(), + }; + let mode_control_result = mapping_core.mode.control_with_options( + pure_control_event, + reaper_target, + control_context, + args.options.mode_control_options, + // Performance control not supported when controlling real-time + None, + ); + let (log_entry_kind, control_value, error) = match mode_control_result { + None => (ControlLogEntryKind::IgnoredByGlue, None, ""), + Some(ModeControlResult::LeaveTargetUntouched(v)) => { + (ControlLogEntryKind::LeftTargetUntouched, Some(v), "") + } + Some(ModeControlResult::HitTarget { + value: control_value, + }) => { + let hit_result = match reaper_target { + RealTimeReaperTarget::SendMidi(t) => t.midi_send_target_send_midi_in_rt_thread( + args.caller, + control_value, + args.midi_feedback_output, + args.log_options, + args.main_task_sender, + args.rt_feedback_sender, + args.value_event.payload(), + args.transformation_container, + ), + RealTimeReaperTarget::PlaytimeSlotTransport(t) => { + let result = t.hit(control_value, control_context); + if result.is_ok_and(|forward_to_main_thread| forward_to_main_thread) { + // Important: We must forward to main thread in this case in order to possibly record! + return true; + } + result.map(|_| ()) + } + RealTimeReaperTarget::PlaytimeColumn(t) => t.hit(control_value, control_context), + RealTimeReaperTarget::PlaytimeRow(t) => t.hit(control_value, control_context), + RealTimeReaperTarget::PlaytimeMatrix(t) => { + let result = t.hit(control_value, control_context); + if result.is_ok_and(|forward_to_main_thread| forward_to_main_thread) { + // Important: We must forward to main thread in this case! + return true; + } + result.map(|_| ()) + } + RealTimeReaperTarget::FxParameter(t) => t.hit(control_value), + }; + match hit_result { + Ok(_) => ( + ControlLogEntryKind::HitSuccessfully, + Some(control_value), + "", + ), + Err(e) => (ControlLogEntryKind::HitFailed, Some(control_value), e), + } + } + }; + mapping_core.increase_invocation_count(); + if args.log_options.target_control_logging_enabled { + let entry = ControlLogEntry { + kind: log_entry_kind, + control_value, + target_index: 0, + invocation_count: mapping_core.invocation_count(), + error, + }; + args.main_task_sender + .send_complaining(ControlMainTask::LogTargetControl { + mapping_id: QualifiedMappingId::new(args.compartment, mapping_core.id), + entry, + }); + } + // This means: Everything done. Don't forward event to main thread. + false +} + +fn forward_control_to_main_processor( + sender: &SenderToNormalThread, + compartment: CompartmentKind, + mapping_id: MappingId, + control_event: ControlEvent, + options: ControlOptions, +) { + let task = ControlMainTask::ControlFromRealTime { + compartment, + mapping_id, + event: control_event, + options, + }; + // If plug-in dropped, the receiver might be gone already because main processor is + // unregistered synchronously. + sender.send_if_space(task); +} + +#[allow(clippy::too_many_arguments)] +fn control_main_mappings_virtual( + settings: &BasicSettings, + main_task_sender: &SenderToNormalThread, + rt_feedback_sender: &SenderToRealTimeThread, + main_mappings: &mut OrderedMappingMap, + value_event: ControlEvent>, + options: ControlOptions, + caller: Caller, + instance: &WeakRealTimeInstance, + is_rendering: bool, + transformation_container: &mut Option<&mut MidiTransformationContainer>, +) -> MatchOutcome { + let midi_feedback_output = settings.midi_destination(); + let log_options = LogOptions::from_basic_settings(settings); + // Controller mappings can't have virtual sources, so for now we only need to check + // main mappings. + let match_inactive = settings.match_even_inactive_mappings; + let mut match_outcome = MatchOutcome::Unmatched; + let mut controlled_at_least_one = false; + for m in main_mappings + .values_mut() + // Consider only control-enabled main mappings + .filter(|m| m.core.options.control_is_enabled) + { + let mapping_is_active = m.is_active(); + if !mapping_is_active && !match_inactive { + continue; + } + let CompoundMappingSource::Virtual(s) = &m.source() else { + continue; + }; + let midi_event = value_event.payload(); + let Some(control_value) = s.control(&midi_event.payload()) else { + continue; + }; + // We found an associated main mapping, so it's not just consumed, it's matched. + match_outcome = MatchOutcome::Matched; + if !mapping_is_active { + continue; + } + let args = ProcessRtMappingArgs { + main_task_sender, + rt_feedback_sender, + compartment: CompartmentKind::Main, + value_event: value_event + .with_payload(MidiEvent::new(midi_event.offset(), control_value)), + options: ControlOptions { + enforce_target_refresh: controlled_at_least_one, + ..options + }, + caller, + midi_feedback_output, + log_options, + instance, + is_rendering, + transformation_container, + }; + process_real_mapping(m, args); + controlled_at_least_one = true; + } + match_outcome +} + +pub fn send_raw_midi_to_fx_output(bytes: &[u8], offset: SampleOffset, caller: Caller) { + let host = match caller { + Caller::Vst(h) => h, + _ => return, + }; + let event = build_sysex_midi_vst_event(bytes, offset); + let events = build_vst_events(&event as *const _ as _); + host.process_events(&events); +} + +fn ordered_map_with_capacity(cap: usize) -> OrderedMappingMap { + let mut map = OrderedMappingMap::with_capacity_and_hasher(cap, Default::default()); + // This is a workaround for an indexmap bug which allocates space for entries on the + // first extend/reserve call although it should have been done already when creating + // it via with_capacity. Remember: We must not allocate in real-time thread! + map.reserve(0); + map +} + +#[derive(Copy, Clone)] +pub enum IncomingMidiMessage<'a> { + Short(RawShortMessage), + SysEx(&'a [u8]), +} + +impl<'a> MidiEvent> { + pub fn from_vst(e: vst::event::Event<'a>) -> Result { + let msg = IncomingMidiMessage::from_vst(e)?; + let delta_frames = match e { + vst::event::Event::Midi(e) => e.delta_frames, + vst::event::Event::SysEx(e) => e.delta_frames, + vst::event::Event::Deprecated(e) => e.delta_frames, + }; + // Negative offset was observed in the wild, see + // https://github.com/helgoboss/helgobox/issues/54. Don't know what that's + // supposed to mean but falling back to zero should be okay in our case. + let offset = SampleOffset::new(std::cmp::max(0, delta_frames) as u64); + Ok(MidiEvent::new(offset, msg)) + } + + pub fn from_reaper( + e: &'a reaper_medium::MidiEvent, + sample_rate: Hz, + ) -> Result { + let msg = IncomingMidiMessage::from_reaper(e.message())?; + // Frame offset is given in 1/1024000 of a second, *not* sample frames! + let offset = SampleOffset::from_midi_input_frame_offset(e.frame_offset(), sample_rate); + Ok(MidiEvent::new(offset, msg)) + } +} + +impl<'a> IncomingMidiMessage<'a> { + pub fn from_vst(e: vst::event::Event<'a>) -> Result { + let res = match e { + vst::event::Event::Midi(e) => { + let short_msg = RawShortMessage::from_bytes(( + e.data[0], + e.data[1] + .try_into() + .map_err(|_| "first data byte invalid")?, + e.data[2] + .try_into() + .map_err(|_| "second data byte invalid")?, + )); + let short_msg = short_msg.map_err(|_| "invalid status byte")?; + IncomingMidiMessage::Short(short_msg) + } + vst::event::Event::SysEx(e) => IncomingMidiMessage::SysEx(e.payload), + vst::event::Event::Deprecated(_) => return Err("deprecated message"), + }; + Ok(res) + } + + pub fn from_reaper(m: &'a reaper_medium::MidiMessage) -> Result { + let short = m.to_short_message().map_err(|_| "invalid short message")?; + let res = if short.r#type() == ShortMessageType::SystemExclusiveStart { + Self::SysEx(m.as_slice()) + } else { + Self::Short(short) + }; + Ok(res) + } + + fn to_owned(self) -> OwnedIncomingMidiMessage { + use IncomingMidiMessage::*; + match self { + Short(m) => OwnedIncomingMidiMessage::Short(m), + SysEx(m) => OwnedIncomingMidiMessage::SysEx(m.to_owned()), + } + } + + fn might_be_automatically_generated_by_reaper(&self) -> bool { + match self { + // TODO-medium Maybe also filter all-sound-off (v6.36+dev0920). + IncomingMidiMessage::Short(m) => m.r#type() == ShortMessageType::NoteOff, + IncomingMidiMessage::SysEx(_) => false, + } + } + + fn to_source_value(self) -> MidiSourceValue<'a, RawShortMessage> { + use IncomingMidiMessage::*; + match self { + Short(msg) => MidiSourceValue::Plain(msg), + SysEx(msg) => MidiSourceValue::BorrowedSysEx(msg), + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct AudioBlockProps { + pub block_length: usize, + pub frame_rate: Hz, +} + +impl AudioBlockProps { + pub fn from_vst(buffer: &vst::buffer::AudioBuffer, sample_rate: Hz) -> Self { + Self { + block_length: buffer.samples(), + frame_rate: sample_rate, + } + } + + pub fn from_on_audio_buffer_args(args: &OnAudioBufferArgs) -> Self { + Self { + block_length: args.len as _, + frame_rate: args.srate, + } + } + + #[cfg(feature = "playtime")] + pub fn to_playtime(self) -> playtime_clip_engine::rt::BasicAudioRequestProps { + playtime_clip_engine::rt::BasicAudioRequestProps { + block_length: self.block_length, + frame_rate: self.frame_rate, + } + } +} + +fn log_virtual_control_input( + sender: &SenderToNormalThread, + value: ControlEvent, + match_outcome: MatchOutcome, +) { + sender.send_complaining(ControlMainTask::LogVirtualControlInput { + event: value, + match_outcome, + }); +} + +fn flatten_control_midi_event(evt: ControlEvent>) -> ControlEvent { + // TODO-medium We could have sample-accurate control event times by converting the MIDI event + // sample offset to something like microseconds (according to the current sample rate or by + // using REAPER's MidiFrameOffset type instead of SampleOffset in the first place) and using + // this microsecond unit in ControlEvent time. + evt.map_payload(|midi_evt| midi_evt.payload()) +} + +#[derive(Copy, Clone)] +pub struct LogOptions { + pub virtual_input_logging_enabled: bool, + pub output_logging_enabled: bool, + pub target_control_logging_enabled: bool, +} + +impl LogOptions { + fn from_basic_settings(settings: &BasicSettings) -> Self { + LogOptions { + virtual_input_logging_enabled: settings.virtual_input_logging_enabled, + output_logging_enabled: settings.real_output_logging_enabled, + target_control_logging_enabled: settings.target_control_logging_enabled, + } + } +} + +fn is_rendering() -> bool { + Reaper::get() + .medium_reaper() + .enum_projects(ProjectRef::CurrentlyRendering, 0) + .is_some() +} diff --git a/plugin-reaper-realearn/main/src/domain/realearn_source_context.rs b/plugin-reaper-realearn/main/src/domain/realearn_source_context.rs new file mode 100644 index 0000000..9912f7b --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/realearn_source_context.rs @@ -0,0 +1,20 @@ +use base::hash_util::NonCryptoHashMap; +use helgoboss_learn::devices::x_touch::XTouchMackieLcdState; +use reaper_medium::MidiOutputDeviceId; + +/// Global state about sources. +#[derive(Default)] +pub struct RealearnSourceState { + x_touch_mackie_lcd_state_by_device: NonCryptoHashMap, +} + +impl RealearnSourceState { + pub fn get_x_touch_mackie_lcd_state_mut( + &mut self, + device: MidiOutputDeviceId, + ) -> &mut XTouchMackieLcdState { + self.x_touch_mackie_lcd_state_by_device + .entry(device) + .or_default() + } +} diff --git a/plugin-reaper-realearn/main/src/domain/realearn_target.rs b/plugin-reaper-realearn/main/src/domain/realearn_target.rs new file mode 100644 index 0000000..d9d4928 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/realearn_target.rs @@ -0,0 +1,1142 @@ +use crate::application::SharedUnitModel; +use crate::domain::ui_util::{ + format_as_percentage_without_unit, log_output, parse_unit_value_from_percentage, OutputReason, +}; +use crate::domain::{ + format_as_pretty_hex, new_set_track_ui_functions_are_available, scoped_track_index, + AdditionalFeedbackEvent, AdditionalTransformationInput, BasicSettings, CompartmentKind, + DomainEventHandler, Exclusivity, ExtendedProcessorContext, FeedbackAudioHookTask, + FeedbackOutput, FeedbackRealTimeTask, GroupId, InstanceId, InstanceStateChanged, MainMapping, + MappingControlResult, MappingId, OrderedMappingMap, OscFeedbackTask, PluginParamIndex, + ProcessorContext, QualifiedMappingId, RealTimeReaperTarget, RealearnModeContext, + RealearnSourceContext, ReaperTarget, SharedInstance, SharedUnit, StreamDeckDeviceId, Tag, + TagScope, TargetCharacter, TrackExclusivity, UnitEvent, UnitId, WeakRealTimeInstance, + ACTION_TARGET, ALL_TRACK_FX_ENABLE_TARGET, ANY_ON_TARGET, AUTOMATION_MODE_OVERRIDE_TARGET, + BROWSE_FXS_TARGET, BROWSE_GROUP_MAPPINGS_TARGET, BROWSE_POT_FILTER_ITEMS_TARGET, + BROWSE_POT_PRESETS_TARGET, COMPARTMENT_PARAMETER_VALUE_TARGET, DUMMY_TARGET, + ENABLE_INSTANCES_TARGET, ENABLE_MAPPINGS_TARGET, ENABLE_UNITS_TARGET, FX_ENABLE_TARGET, + FX_ONLINE_TARGET, FX_OPEN_TARGET, FX_PARAMETER_TARGET, FX_PARAMETER_TOUCH_STATE_TARGET, + FX_PRESET_TARGET, FX_TOOL_TARGET, GO_TO_BOOKMARK_TARGET, LAST_TOUCHED_TARGET, + LEARN_MAPPING_TARGET, LOAD_FX_SNAPSHOT_TARGET, LOAD_MAPPING_SNAPSHOT_TARGET, + LOAD_POT_PRESET_TARGET, MIDI_SEND_TARGET, MOUSE_TARGET, OSC_SEND_TARGET, PLAYRATE_TARGET, + PREVIEW_POT_PRESET_TARGET, ROUTE_AUTOMATION_MODE_TARGET, ROUTE_MONO_TARGET, ROUTE_MUTE_TARGET, + ROUTE_PAN_TARGET, ROUTE_PHASE_TARGET, ROUTE_TOUCH_STATE_TARGET, ROUTE_VOLUME_TARGET, + SAVE_MAPPING_SNAPSHOT_TARGET, SEEK_TARGET, SELECTED_TRACK_TARGET, + STREAM_DECK_BRIGHTNESS_TARGET, TEMPO_TARGET, TRACK_ARM_TARGET, TRACK_AUTOMATION_MODE_TARGET, + TRACK_MONITORING_MODE_TARGET, TRACK_MUTE_TARGET, TRACK_PAN_TARGET, TRACK_PARENT_SEND_TARGET, + TRACK_PEAK_TARGET, TRACK_PHASE_TARGET, TRACK_SELECTION_TARGET, TRACK_SHOW_TARGET, + TRACK_SOLO_TARGET, TRACK_TOOL_TARGET, TRACK_TOUCH_STATE_TARGET, TRACK_VOLUME_TARGET, + TRACK_WIDTH_TARGET, TRANSPORT_TARGET, +}; +use base::hash_util::NonCryptoHashSet; +use base::{SenderToNormalThread, SenderToRealTimeThread}; +use enum_dispatch::enum_dispatch; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, PropValue, RawMidiEvent, RgbColor, + TransformationInputProvider, UnitValue, +}; +use helgobox_api::persistence::{InstanceTagKind, LearnableTargetKind, TrackScope}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use reaper_high::{ChangeEvent, Fx, Guid, Project, Reaper, Track, TrackRoute}; +use reaper_medium::CommandId; +use serde_repr::*; +use std::borrow::Cow; +use std::fmt::{Debug, Display, Formatter}; +use strum::IntoEnumIterator; + +#[enum_dispatch(ReaperTarget)] +pub trait RealearnTarget { + // TODO-low Instead of taking the ControlContext as parameter in each method, we could also + // choose to implement RealearnTarget for a wrapper that contains the control context. + // We did this with ValueFormatter and ValueParser. + fn character(&self, context: ControlContext) -> TargetCharacter { + self.control_type_and_character(context).1 + } + + fn reaper_target_type(&self) -> Option { + None + } + + fn control_type_and_character( + &self, + _context: ControlContext, + ) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn open(&self, context: ControlContext) { + let _ = context; + if let Some(fx) = self.fx() { + let _ = fx.show_in_floating_window(); + return; + } + if let Some(track) = self.track() { + track.select_exclusively(); + // Scroll to track + Reaper::get() + .main_section() + .action_by_command_id(CommandId::new(40913)) + .invoke_as_trigger(Some(track.project()), None) + .expect("built-in action should exist"); + } + } + + /// Parses the given text as a target value and returns it as unit value. + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + let _ = context; + parse_unit_value_from_percentage(text) + } + + /// Parses the given text as a target step size and returns it as unit value. + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + let _ = context; + parse_unit_value_from_percentage(text) + } + + /// This converts the given normalized value to a discrete value. + /// + /// Used for displaying discrete target values in edit fields. + /// Must be implemented for discrete targets only which don't support parsing according to + /// `can_parse_values()`, e.g. FX preset. This target reports a step size. If we want to + /// display an increment or a particular value in an edit field, we don't show normalized + /// values of course but a discrete number, by using this function. Should be the reverse of + /// `convert_discrete_value_to_unit_value()` because latter is used for parsing. + /// + /// # Errors + /// + /// Returns an error if this target doesn't report a step size. + fn convert_unit_value_to_discrete_value( + &self, + value: UnitValue, + context: ControlContext, + ) -> Result { + let _ = value; + let _ = context; + Err("not supported") + } + + /// Formats the given value without unit. + /// + /// Shown in the mapping panel text field for continuous targets. + fn format_value_without_unit(&self, value: UnitValue, context: ControlContext) -> String { + self.format_as_discrete_or_percentage(value, context) + } + + /// Shown as default textual feedback. + fn text_value(&self, context: ControlContext) -> Option> { + let _ = context; + None + } + + /// Usable in textual feedback expressions as + /// [`helgoboss_learn::target_prop_keys::NUMERIC_VALUE`]. Don't implement for on/off values! + fn numeric_value(&self, context: ControlContext) -> Option { + let _ = context; + None + } + + /// Formats the given step size without unit. + fn format_step_size_without_unit( + &self, + step_size: UnitValue, + context: ControlContext, + ) -> String { + self.format_as_discrete_or_percentage(step_size, context) + } + + /// Reusable function + // TODO-medium Never overwritten. Can be factored out! + fn format_as_discrete_or_percentage( + &self, + value: UnitValue, + context: ControlContext, + ) -> String { + match self.character(context) { + TargetCharacter::Discrete => self + .convert_unit_value_to_discrete_value(value, context) + .map(|v| v.to_string()) + .unwrap_or_default(), + TargetCharacter::Trigger => String::new(), + _ => format_as_percentage_without_unit(value), + } + } + + /// If this returns true, a value will not be printed (e.g. because it's already in the edit + /// field). + fn hide_formatted_value(&self, context: ControlContext) -> bool { + let _ = context; + false + } + + /// If this returns true, a step size will not be printed (e.g. because it's already in the + /// edit field). + fn hide_formatted_step_size(&self, context: ControlContext) -> bool { + let _ = context; + false + } + + /// For mapping panel. + fn value_unit(&self, context: ControlContext) -> &'static str { + self.value_unit_default(context) + } + + fn value_unit_default(&self, context: ControlContext) -> &'static str { + match self.character(context) { + TargetCharacter::Trigger | TargetCharacter::Discrete => "", + _ => "%", + } + } + + /// For textual feedback. + fn numeric_value_unit(&self, context: ControlContext) -> &'static str { + self.value_unit(context) + } + + fn step_size_unit(&self, context: ControlContext) -> &'static str { + if self.character(context) == TargetCharacter::Discrete { + "" + } else { + "%" + } + } + /// Formats the value completely (including a possible unit). + fn format_value(&self, value: UnitValue, context: ControlContext) -> String { + self.format_value_generic(value, context) + } + + // TODO-medium Never overwritten. Can be factored out! + fn format_value_generic(&self, value: UnitValue, context: ControlContext) -> String { + format!( + "{} {}", + self.format_value_without_unit(value, context), + self.value_unit(context) + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let (_, _) = (value, context); + Err("not supported") + } + + fn can_report_current_value(&self) -> bool { + // We will quickly realize if not. + true + } + + /// Used for target "Global: Last touched" and queryable as target prop. + fn is_available(&self, _context: ControlContext) -> bool { + true + } + + fn project(&self) -> Option { + None + } + fn track(&self) -> Option<&Track> { + None + } + fn fx(&self) -> Option<&Fx> { + None + } + fn route(&self) -> Option<&TrackRoute> { + None + } + fn track_exclusivity(&self) -> Option { + None + } + fn clip_slot_address(&self) -> Option { + None + } + fn clip_column_address(&self) -> Option { + None + } + fn clip_row_address(&self) -> Option { + None + } + + /// Whether the target supports automatic feedback in response to some events or polling. + /// + /// If the target doesn't support automatic feedback, you are left with a choice: + /// + /// - a) Use polling (continuously poll the target value). + /// - b) Set this to `false` (will send feedback after control only). + /// + /// Choose (a) if the target value is a real, global target value that also can affect + /// other mappings. Polling is obviously not the optimal choice because of the performance + /// drawback ... but at least multiple mappings can participate. + /// + /// Choose (b) if the target value is not global but artificial, that is, attached to the + /// mapping itself - and can therefore not have any effect on other mappings. This is also + /// not the optimal choice because other mappings can't participate in the feedback value ... + /// but at least it's fast. + fn supports_automatic_feedback(&self) -> bool { + // Usually yes. We will quickly realize if not. + true + } + + /// Might return the new value if changed but is not required to! If it doesn't and the consumer + /// wants to know the new value, it should just query the current value of the target. + /// + /// Is called in any case (even if feedback not enabled). So we can use it for general-purpose + /// change event reactions such as reacting to transport stop. + fn process_change_event( + &self, + evt: CompoundChangeEvent, + context: ControlContext, + ) -> (bool, Option) { + let (_, _) = (evt, context); + (false, None) + } + + fn splinter_real_time_target(&self) -> Option { + None + } + + /// Like `convert_unit_value_to_discrete_value()` but in the other direction. + /// + /// Used for parsing discrete values of discrete targets that can't do real parsing according to + /// `can_parse_values()`. + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + context: ControlContext, + ) -> Result { + let _ = value; + let _ = context; + Err("not supported") + } + + fn parse_value_from_discrete_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.convert_discrete_value_to_unit_value( + text.parse().map_err(|_| "not a discrete value")?, + context, + ) + } + + /// Returns a value for the given key if the target supports it. + /// + /// You don't need to implement the commonly supported prop values here! They will + /// be handed out automatically as a fallback by the calling method in case you return `None`. + // + // Requiring owned strings here makes the API more pleasant and is probably not a big deal + // performance-wise (feedback strings don't get large). If we want to optimize this in future, + // don't use Cows. The only real performance win would be to use a writer API. + // With Cows, we would still need to turn ReaperStr into owned String. With writer API, + // we could just read borrowed ReaperStr as str and write into the result buffer. However, in + // practice we don't often get borrowed strings from Reaper anyway. + // TODO-low Use a formatter API instead of returning owned strings (for this to work, we also + // need to adjust the textual feedback expression parsing to take advantage of it). + fn prop_value(&self, key: &str, context: ControlContext) -> Option { + let _ = key; + let _ = context; + None + } +} + +#[derive(Copy, Clone, Debug)] +pub enum CompoundChangeEvent<'a> { + Reaper(&'a ChangeEvent), + Additional(&'a AdditionalFeedbackEvent), + Instance(&'a InstanceStateChanged), + Unit(&'a UnitEvent), + CompartmentParameter(PluginParamIndex), + #[cfg(feature = "playtime")] + ClipMatrix(&'a playtime_clip_engine::base::ClipMatrixEvent), +} + +pub fn get_track_name(t: &Track, scope: TrackScope) -> String { + if let Some(n) = t.name() { + if n.to_str().is_empty() { + let track_index = scoped_track_index(t, scope); + format!("Track {}", track_index.unwrap_or(0) + 1) + } else { + n.into_string() + } + } else { + "".to_string() + } +} + +pub fn convert_reaper_color_to_helgoboss_learn(color: reaper_medium::RgbColor) -> RgbColor { + let reaper_medium::RgbColor { r, g, b } = color; + RgbColor::new(r, g, b) +} + +pub trait UnitContainer: Debug { + fn find_session_by_id(&self, session_id: &str) -> Option; + + fn find_session_by_instance_id(&self, instance_id: UnitId) -> Option; + + /// Returns activated tags if they don't correspond to the tags in the args. + /// + /// This is the case when disabling instances and using the "exclusive" option (causing **other** instances + /// to become enabled). + fn enable_instances(&self, args: EnableInstancesArgs) -> Option>; + + /// Returns activated tags if they don't correspond to the tags in the args. + /// + /// This is the case when disabling units and using the "exclusive" option (causing **other** units + /// to become enabled). + fn enable_units(&self, args: EnableUnitsArgs) -> Option>; + + fn change_instance_fx(&self, args: ChangeInstanceFxArgs) -> Result<(), &'static str>; + + fn change_instance_track(&self, args: ChangeInstanceTrackArgs) -> Result<(), &'static str>; +} + +pub struct EnableInstancesArgs<'a> { + pub tag_kind: InstanceTagKind, + pub common: ModifyUnitContainerCommonArgs<'a>, + pub is_enable: bool, + pub exclusivity: Exclusivity, +} + +pub struct EnableUnitsArgs<'a> { + pub common: ModifyUnitContainerCommonArgs<'a>, + pub is_enable: bool, + pub exclusivity: Exclusivity, +} + +pub struct ChangeInstanceFxArgs<'a> { + pub common: ModifyUnitContainerCommonArgs<'a>, + pub request: InstanceFxChangeRequest, +} + +pub struct ChangeInstanceTrackArgs<'a> { + pub common: ModifyUnitContainerCommonArgs<'a>, + pub request: InstanceTrackChangeRequest, +} + +#[derive(Copy, Clone, Debug)] +pub enum InstanceFxChangeRequest { + Pin { + /// `None` means master track. + track_guid: Option, + is_input_fx: bool, + fx_guid: Guid, + }, + SetFromMapping(QualifiedMappingId), +} + +#[derive(Copy, Clone, Debug)] +pub enum InstanceTrackChangeRequest { + /// `None` means master track. + Pin(Option), + SetFromMapping(QualifiedMappingId), +} + +pub struct ModifyUnitContainerCommonArgs<'a> { + pub initiator_instance_id: InstanceId, + pub initiator_unit_id: UnitId, + /// `None` if monitoring FX. + pub initiator_project: Option, + pub scope: &'a TagScope, +} + +#[derive(Copy, Clone, Debug)] +pub struct ControlContext<'a> { + pub feedback_audio_hook_task_sender: &'a SenderToRealTimeThread, + pub feedback_real_time_task_sender: &'a SenderToRealTimeThread, + pub osc_feedback_task_sender: &'a SenderToNormalThread, + pub feedback_output: Option, + pub stream_deck_dev_id: Option, + pub unit_container: &'a dyn UnitContainer, + pub instance: &'a SharedInstance, + pub unit: &'a SharedUnit, + pub instance_id: InstanceId, + pub unit_id: UnitId, + pub output_logging_enabled: bool, + pub source_context: RealearnSourceContext<'a>, + pub mode_context: RealearnModeContext<'a>, + pub processor_context: &'a ProcessorContext, +} + +#[derive(Copy, Clone, Debug)] +pub struct RealTimeControlContext<'a> { + pub instance: &'a WeakRealTimeInstance, + pub _p: &'a (), +} + +impl RealTimeControlContext<'_> { + #[cfg(feature = "playtime")] + pub fn clip_matrix(&self) -> Result { + let instance = self.instance.upgrade().ok_or("real-time instance gone")?; + let instance = base::non_blocking_lock(&*instance, "real-time instance"); + instance + .clip_matrix() + .ok_or("real-time Playtime matrix not yet initialized")? + .upgrade() + .ok_or("real-time Playtime matrix doesn't exist anymore") + } +} + +impl TransformationInputProvider for RealTimeControlContext<'_> { + fn additional_input(&self) -> AdditionalTransformationInput { + AdditionalTransformationInput::default() + } +} + +impl ControlContext<'_> { + pub fn instance(&self) -> &SharedInstance { + self.instance + } + + pub fn log_outgoing_target_midi(&self, events: &[RawMidiEvent]) { + if self.output_logging_enabled { + for e in events { + log_output( + self.unit_id, + OutputReason::TargetOutput, + format_as_pretty_hex(e.bytes()), + ); + } + } + } + + pub fn create_modify_unit_container_common_args<'c>( + &self, + scope: &'c TagScope, + ) -> ModifyUnitContainerCommonArgs<'c> { + ModifyUnitContainerCommonArgs { + initiator_instance_id: self.instance_id, + initiator_unit_id: self.unit_id, + initiator_project: self.processor_context.project(), + scope, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct MappingControlContext<'a> { + pub control_context: ControlContext<'a>, + pub mapping_data: MappingData, + /// This means control was initiated in the real-time processor (currently MIDI only). + /// + /// This information is used by some particular targets whose work is partially done in real-time and partially + /// in the main thread. + pub coming_from_real_time: bool, +} + +impl TransformationInputProvider for MappingControlContext<'_> { + fn additional_input(&self) -> AdditionalTransformationInput { + AdditionalTransformationInput { + y_last: self + .mapping_data + .last_non_performance_target_value + .map(|v| v.to_unit_value().get()) + .unwrap_or_default(), + } + } +} + +impl<'a> From> for ControlContext<'a> { + fn from(v: MappingControlContext<'a>) -> Self { + v.control_context + } +} + +#[derive(Copy, Clone, Debug)] +pub struct MappingData { + pub compartment: CompartmentKind, + pub mapping_id: MappingId, + pub group_id: GroupId, + pub last_non_performance_target_value: Option, +} + +impl MappingData { + pub fn qualified_mapping_id(&self) -> QualifiedMappingId { + QualifiedMappingId::new(self.compartment, self.mapping_id) + } +} + +pub type BoxedHitInstruction = Box; + +pub struct HitResponse { + /// If the target invocation caused an effect. + /// + /// Typical example: `false` if the button was released and the target only does something + /// when the button is pressed. + /// + /// At the moment, we also return `true` if the target invocation was done but the value stays + /// the same. That's okay. + /// + /// This can be `true` even if a hit instruction is given ... maybe part of the invocation can + /// be done before the hit instruction and has an effect already. + pub caused_effect: bool, + /// A hit instruction to be executed later. + pub hit_instruction: Option, +} + +impl HitResponse { + pub fn ignored() -> Self { + Self { + caused_effect: false, + hit_instruction: None, + } + } + + pub fn processed_with_effect() -> Self { + Self { + caused_effect: true, + hit_instruction: None, + } + } + + /// Creates a response that says "no effect so far, but please execute the given hit + /// instruction". + pub fn hit_instruction(hit_instruction: Box) -> Self { + Self { + caused_effect: false, + hit_instruction: Some(hit_instruction), + } + } +} + +pub enum HitInstructionResponse { + /// Hit instruction ignored. + Ignored, + /// Hit instruction caused effect. + /// + /// Caller should inspect the given "inner" mapping control results and possibly execute + /// the hit instructions returned from them in a second pass. + CausedEffect(Vec), +} + +pub trait HitInstruction { + fn execute(self: Box, context: HitInstructionContext) -> HitInstructionResponse; +} + +pub struct HitInstructionContext<'a> { + /// All mappings in the relevant compartment. + pub mappings: &'a mut OrderedMappingMap, + // TODO-medium This became part of ExtendedProcessorContext, so redundant (not just here BTW) + pub control_context: ControlContext<'a>, + pub domain_event_handler: &'a dyn DomainEventHandler, + pub processor_context: ExtendedProcessorContext<'a>, + pub basic_settings: &'a BasicSettings, +} + +/// Type of a target +/// +/// Display implementation produces single-line medium-length names that are supposed to be shown +/// e.g. in the dropdown. +/// +/// IMPORTANT: Don't change the numbers! They are serialized. +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + Serialize_repr, + Deserialize_repr, + strum::EnumIter, + strum::EnumCount, + TryFromPrimitive, + IntoPrimitive, +)] +#[repr(usize)] +pub enum ReaperTargetType { + // Global targets + LastTouched = 20, + Mouse = 57, + AutomationModeOverride = 26, + + // Project targets + AnyOn = 43, + BrowseTracks = 14, + Action = 0, + Transport = 16, + Seek = 23, + PlayRate = 11, + Tempo = 10, + + // Marker/region targets + GoToBookmark = 22, + + // Track targets + TrackTool = 44, + TrackArm = 5, + AllTrackFxEnable = 15, + TrackParentSend = 56, + TrackMute = 7, + TrackPeak = 34, + TrackPhase = 39, + TrackSelection = 6, + TrackAutomationMode = 25, + TrackTouchState = 21, + TrackMonitoringMode = 49, + TrackPan = 4, + TrackWidth = 17, + TrackVolume = 2, + TrackShow = 24, + TrackSolo = 8, + + // FX chain targets + BrowseFxs = 28, + + // FX targets + FxTool = 54, + FxPreset = 13, + FxEnable = 12, + FxOnline = 42, + LoadFxSnapshot = 19, + FxOpen = 27, + + // FX parameter targets + FxParameterTouchState = 47, + #[default] + FxParameterValue = 1, + + // Pot targets + BrowsePotFilterItems = 61, + BrowsePotPresets = 58, + PreviewPotPreset = 59, + LoadPotPreset = 60, + + // Send targets + RouteTouchState = 48, + RouteMono = 41, + RouteMute = 18, + RoutePhase = 40, + RouteAutomationMode = 45, + RoutePan = 9, + RouteVolume = 3, + + // Clip targets + PlaytimeSlotManagementAction = 46, + PlaytimeSlotTransportAction = 31, + PlaytimeSlotSeek = 32, + PlaytimeSlotVolume = 33, + + // Clip column targets + PlaytimeColumnAction = 50, + + // Clip row targets + PlaytimeRowAction = 52, + + // Playtime matrix + PlaytimeMatrixAction = 51, + PlaytimeControlUnitScroll = 64, + PlaytimeBrowseCells = 65, + + // Misc + SendMidi = 29, + SendOsc = 30, + StreamDeckBrightness = 66, + + // ReaLearn targets + Dummy = 53, + EnableInstances = 38, + EnableUnits = 67, + EnableMappings = 36, + ModifyMapping = 62, + CompartmentParameterValue = 63, + LoadMappingSnapshot = 35, + TakeMappingSnapshot = 55, + BrowseGroup = 37, +} + +impl Display for ReaperTargetType { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + let def = self.definition(); + write!(f, "{}: {}", def.section, def.name) + } +} + +impl ReaperTargetType { + pub fn from_target(target: &ReaperTarget) -> ReaperTargetType { + target + .reaper_target_type() + .expect("a REAPER target should always return a REAPER target type") + } + + pub fn from_learnable_target_kind(kind: LearnableTargetKind) -> ReaperTargetType { + use LearnableTargetKind as A; + use ReaperTargetType as B; + match kind { + A::TrackVolume => B::TrackVolume, + A::TrackPan => B::TrackPan, + A::RouteVolume => B::RouteVolume, + A::RoutePan => B::RoutePan, + A::TrackArmState => B::TrackArm, + A::TrackMuteState => B::TrackMute, + A::TrackSoloState => B::TrackSolo, + A::TrackSelectionState => B::TrackSelection, + A::FxOnOffState => B::FxEnable, + A::FxParameterValue => B::FxParameterValue, + A::BrowseFxPresets => B::FxPreset, + A::PlayRate => B::PlayRate, + A::Tempo => B::Tempo, + A::TrackAutomationMode => B::TrackAutomationMode, + A::TrackMonitoringMode => B::TrackMonitoringMode, + A::AutomationModeOverride => B::AutomationModeOverride, + A::ReaperAction => B::Action, + A::TransportAction => B::Transport, + } + } + + pub fn from_learnable_target_kinds( + set: impl IntoIterator, + ) -> NonCryptoHashSet { + set.into_iter() + .map(ReaperTargetType::from_learnable_target_kind) + .collect() + } + + pub fn all() -> NonCryptoHashSet { + ReaperTargetType::iter().collect() + } + + pub fn supports_feedback_resolution(self) -> bool { + use ReaperTargetType::*; + matches!(self, PlaytimeSlotSeek | Seek) + } + + pub fn supports_poll_for_feedback(self) -> bool { + self.definition().supports_poll_for_feedback() + } + + pub const fn definition(self) -> &'static TargetTypeDef { + use ReaperTargetType::*; + match self { + Mouse => &MOUSE_TARGET, + LastTouched => &LAST_TOUCHED_TARGET, + AutomationModeOverride => &AUTOMATION_MODE_OVERRIDE_TARGET, + AnyOn => &ANY_ON_TARGET, + Action => &ACTION_TARGET, + Transport => &TRANSPORT_TARGET, + BrowseTracks => &SELECTED_TRACK_TARGET, + Seek => &SEEK_TARGET, + PlayRate => &PLAYRATE_TARGET, + Tempo => &TEMPO_TARGET, + GoToBookmark => &GO_TO_BOOKMARK_TARGET, + TrackArm => &TRACK_ARM_TARGET, + TrackParentSend => &TRACK_PARENT_SEND_TARGET, + AllTrackFxEnable => &ALL_TRACK_FX_ENABLE_TARGET, + TrackTool => &TRACK_TOOL_TARGET, + TrackMute => &TRACK_MUTE_TARGET, + TrackPeak => &TRACK_PEAK_TARGET, + TrackPhase => &TRACK_PHASE_TARGET, + TrackSelection => &TRACK_SELECTION_TARGET, + TrackAutomationMode => &TRACK_AUTOMATION_MODE_TARGET, + TrackMonitoringMode => &TRACK_MONITORING_MODE_TARGET, + TrackTouchState => &TRACK_TOUCH_STATE_TARGET, + TrackPan => &TRACK_PAN_TARGET, + TrackWidth => &TRACK_WIDTH_TARGET, + TrackVolume => &TRACK_VOLUME_TARGET, + TrackShow => &TRACK_SHOW_TARGET, + TrackSolo => &TRACK_SOLO_TARGET, + FxTool => &FX_TOOL_TARGET, + BrowseFxs => &BROWSE_FXS_TARGET, + FxEnable => &FX_ENABLE_TARGET, + FxOnline => &FX_ONLINE_TARGET, + LoadFxSnapshot => &LOAD_FX_SNAPSHOT_TARGET, + FxPreset => &FX_PRESET_TARGET, + FxOpen => &FX_OPEN_TARGET, + FxParameterValue => &FX_PARAMETER_TARGET, + FxParameterTouchState => &FX_PARAMETER_TOUCH_STATE_TARGET, + RouteAutomationMode => &ROUTE_AUTOMATION_MODE_TARGET, + RouteMono => &ROUTE_MONO_TARGET, + RouteMute => &ROUTE_MUTE_TARGET, + RoutePhase => &ROUTE_PHASE_TARGET, + RoutePan => &ROUTE_PAN_TARGET, + RouteVolume => &ROUTE_VOLUME_TARGET, + RouteTouchState => &ROUTE_TOUCH_STATE_TARGET, + PlaytimeSlotTransportAction => &crate::domain::PLAYTIME_SLOT_TRANSPORT_TARGET, + PlaytimeColumnAction => &crate::domain::PLAYTIME_COLUMN_TARGET, + PlaytimeRowAction => &crate::domain::PLAYTIME_ROW_TARGET, + PlaytimeSlotSeek => &crate::domain::PLAYTIME_SLOT_SEEK_TARGET, + PlaytimeSlotVolume => &crate::domain::PLAYTIME_SLOT_VOLUME_TARGET, + PlaytimeSlotManagementAction => &crate::domain::PLAYTIME_SLOT_MANAGEMENT_TARGET, + PlaytimeMatrixAction => &crate::domain::PLAYTIME_MATRIX_TARGET, + PlaytimeControlUnitScroll => &crate::domain::PLAYTIME_CONTROL_UNIT_SCROLL_TARGET, + PlaytimeBrowseCells => &crate::domain::PLAYTIME_BROWSE_CELLS_TARGET, + SendMidi => &MIDI_SEND_TARGET, + SendOsc => &OSC_SEND_TARGET, + Dummy => &DUMMY_TARGET, + EnableInstances => &ENABLE_INSTANCES_TARGET, + EnableUnits => &ENABLE_UNITS_TARGET, + EnableMappings => &ENABLE_MAPPINGS_TARGET, + ModifyMapping => &LEARN_MAPPING_TARGET, + LoadMappingSnapshot => &LOAD_MAPPING_SNAPSHOT_TARGET, + TakeMappingSnapshot => &SAVE_MAPPING_SNAPSHOT_TARGET, + BrowseGroup => &BROWSE_GROUP_MAPPINGS_TARGET, + BrowsePotFilterItems => &BROWSE_POT_FILTER_ITEMS_TARGET, + BrowsePotPresets => &BROWSE_POT_PRESETS_TARGET, + PreviewPotPreset => &PREVIEW_POT_PRESET_TARGET, + LoadPotPreset => &LOAD_POT_PRESET_TARGET, + CompartmentParameterValue => &COMPARTMENT_PARAMETER_VALUE_TARGET, + StreamDeckBrightness => &STREAM_DECK_BRIGHTNESS_TARGET, + } + } + + pub fn supports_track(self) -> bool { + self.definition().supports_track() + } + + pub fn supports_track_must_be_selected(self) -> bool { + self.definition().supports_track_must_be_selected() + } + + pub fn supports_track_scrolling(self) -> bool { + self.definition().supports_track_scrolling() + } + + pub fn supports_clip_slot(self) -> bool { + self.definition().supports_clip_slot() + } + + pub fn supports_fx(self) -> bool { + self.definition().supports_fx() + } + + pub fn supports_seek_behavior(self) -> bool { + self.definition().supports_seek_behavior() + } + + pub fn supports_tags(self) -> bool { + self.definition().supports_tags() + } + + pub fn supports_fx_chain(self) -> bool { + self.definition().supports_fx_chain() + } + + pub fn supports_fx_display_type(self) -> bool { + self.definition().supports_fx_display_type() + } + + pub fn supports_send(self) -> bool { + self.definition().supports_send() + } + + pub fn supports_track_exclusivity(self) -> bool { + self.definition().supports_track_exclusivity() + } + + pub fn supports_exclusivity(self) -> bool { + self.definition().supports_exclusivity() + } + + pub fn supports_fx_parameter(self) -> bool { + self.definition().supports_fx_parameter() + } + + pub fn supports_control(&self) -> bool { + self.definition().supports_control() + } + + pub fn supports_feedback(&self) -> bool { + self.definition().supports_feedback() + } + + pub fn hint(&self) -> &'static str { + self.definition().hint() + } + + /// Produces a shorter name than the Display implementation. + /// + /// For example, it doesn't contain the leading context information. + pub fn short_name(&self) -> &'static str { + self.definition().short_name() + } +} + +#[derive( + Copy, Clone, Eq, PartialEq, Hash, Debug, strum::AsRefStr, strum::Display, strum::EnumIter, +)] +pub enum TargetSection { + Global, + Project, + #[strum(serialize = "Marker/region")] + Bookmark, + Track, + #[strum(serialize = "FX chain")] + FxChain, + #[strum(serialize = "FX")] + Fx, + #[strum(serialize = "FX parameter")] + FxParameter, + Pot, + Send, + Playtime, + #[strum(serialize = "MIDI")] + Midi, + #[strum(serialize = "OSC")] + Osc, + #[strum(serialize = "Stream Deck")] + StreamDeck, + ReaLearn, +} + +pub struct TargetTypeDef { + pub lua_only: bool, + pub section: TargetSection, + pub name: &'static str, + pub short_name: &'static str, + pub hint: &'static str, + pub supports_track: bool, + pub supports_mouse_button: bool, + pub supports_axis: bool, + pub supports_gang_selected: bool, + pub supports_gang_grouping: bool, + pub if_so_supports_track_must_be_selected: bool, + pub supports_track_scrolling: bool, + pub supports_clip_slot: bool, + pub supports_fx: bool, + pub supports_fx_parameter: bool, + pub supports_fx_chain: bool, + pub supports_fx_display_type: bool, + pub supports_tags: bool, + pub supports_send: bool, + pub supports_track_exclusivity: bool, + pub supports_exclusivity: bool, + pub supports_poll_for_feedback: bool, + pub supports_feedback_resolution: bool, + pub supports_control: bool, + pub supports_feedback: bool, + pub supports_seek_behavior: bool, + pub supports_track_grouping_only_gang_behavior: bool, + pub supports_real_time_control: bool, + pub supports_included_targets: bool, +} + +impl TargetTypeDef { + pub const fn section(&self) -> TargetSection { + self.section + } + pub const fn name(&self) -> &'static str { + self.name + } + pub const fn short_name(&self) -> &'static str { + self.short_name + } + pub const fn hint(&self) -> &'static str { + self.hint + } + pub const fn supports_track(&self) -> bool { + self.supports_track + } + pub const fn supports_mouse_button(&self) -> bool { + self.supports_mouse_button + } + pub const fn supports_axis(&self) -> bool { + self.supports_axis + } + pub const fn supports_gang_selected(&self) -> bool { + self.supports_gang_selected + } + pub const fn supports_gang_grouping(&self) -> bool { + self.supports_gang_grouping + } + pub fn supports_track_grouping_only_gang_behavior(&self) -> bool { + self.supports_track_grouping_only_gang_behavior + || new_set_track_ui_functions_are_available() + } + pub const fn supports_track_must_be_selected(&self) -> bool { + self.supports_track() && self.if_so_supports_track_must_be_selected + } + pub const fn supports_track_scrolling(&self) -> bool { + self.supports_track_scrolling + } + pub const fn supports_clip_slot(&self) -> bool { + self.supports_clip_slot + } + pub const fn supports_seek_behavior(&self) -> bool { + self.supports_seek_behavior + } + pub const fn supports_fx(&self) -> bool { + self.supports_fx + } + pub const fn supports_fx_parameter(&self) -> bool { + self.supports_fx_parameter + } + pub const fn supports_fx_chain(&self) -> bool { + self.supports_fx() || self.supports_fx_chain + } + pub const fn supports_fx_display_type(&self) -> bool { + self.supports_fx_display_type + } + pub const fn supports_tags(&self) -> bool { + self.supports_tags + } + pub const fn supports_send(&self) -> bool { + self.supports_send + } + pub const fn supports_track_exclusivity(&self) -> bool { + self.supports_track_exclusivity + } + pub const fn supports_exclusivity(&self) -> bool { + self.supports_exclusivity + } + pub const fn supports_poll_for_feedback(&self) -> bool { + self.supports_poll_for_feedback + } + pub const fn supports_feedback_resolution(&self) -> bool { + self.supports_feedback_resolution + } + pub const fn supports_control(&self) -> bool { + self.supports_control + } + pub const fn supports_feedback(&self) -> bool { + self.supports_feedback + } + pub const fn supports_real_time_control(&self) -> bool { + self.supports_real_time_control + } + pub const fn supports_included_targets(&self) -> bool { + self.supports_included_targets + } +} + +pub const DEFAULT_TARGET: TargetTypeDef = TargetTypeDef { + lua_only: false, + section: TargetSection::Global, + name: "", + short_name: "", + hint: "", + supports_control: true, + supports_feedback: true, + supports_track: false, + supports_mouse_button: false, + supports_axis: false, + supports_gang_selected: false, + supports_gang_grouping: false, + if_so_supports_track_must_be_selected: true, + supports_track_scrolling: false, + supports_clip_slot: false, + supports_fx: false, + supports_fx_parameter: false, + supports_fx_chain: false, + supports_fx_display_type: false, + supports_tags: false, + supports_send: false, + supports_track_exclusivity: false, + supports_exclusivity: false, + supports_poll_for_feedback: false, + supports_feedback_resolution: false, + supports_seek_behavior: false, + supports_track_grouping_only_gang_behavior: false, + supports_real_time_control: false, + supports_included_targets: false, +}; + +pub const AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY: &str = "Automatic feedback via polling only"; diff --git a/plugin-reaper-realearn/main/src/domain/realearn_target_context.rs b/plugin-reaper-realearn/main/src/domain/realearn_target_context.rs new file mode 100644 index 0000000..8e91177 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/realearn_target_context.rs @@ -0,0 +1,184 @@ +use crate::domain::{ + AdditionalFeedbackEvent, FxSnapshotLoadedEvent, ParameterAutomationTouchStateChangedEvent, + TouchedTrackParameterType, TrackGangBehavior, +}; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use base::{NamedChannelSender, SenderToNormalThread}; +use reaper_high::{Fx, Track, TrackSetSmartOpts}; +use reaper_medium::MediaTrack; + +/// Feedback for most targets comes from REAPER itself but there are some targets for which ReaLearn +/// holds the state. It's in this struct. +/// +/// Some of this state can be persistent. This raises the question which ReaLearn instance should +/// be responsible for saving it. If you need persistent state, first think about if it shouldn't +/// rather be part of `InstanceState`. Then it's owned by a particular instance, which is then also +/// responsible for saving it. But we also have global REAPER things such as additional FX state. +/// In this case, we should put it here and track for each state which instance is responsible for +/// saving it! +pub struct RealearnTargetState { + /// For notifying ReaLearn about state changes. + additional_feedback_event_sender: SenderToNormalThread, + /// Memorizes for each FX the hash of its last FX snapshot loaded via "Load FX snapshot" target. + /// + /// Persistent. + // TODO-high-pot Restore on load (by looking up snapshot chunk) + fx_snapshot_chunk_hash_by_fx: NonCryptoHashMap, + /// Memorizes for each FX some infos about its last loaded Pot preset. + /// + /// Persistent. + // TODO-high-pot Restore on load (by looking up DB) + current_pot_preset_by_fx: NonCryptoHashMap, + /// Memorizes all currently touched track parameters. + /// + /// For "Touch automation state" target. + /// + /// Not persistent. + touched_things: NonCryptoHashSet, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +struct TouchedThing { + track: MediaTrack, + parameter_type: TouchedTrackParameterType, +} + +impl TouchedThing { + pub fn new(track: MediaTrack, parameter_type: TouchedTrackParameterType) -> Self { + Self { + track, + parameter_type, + } + } +} + +impl RealearnTargetState { + pub fn new( + additional_feedback_event_sender: SenderToNormalThread, + ) -> Self { + Self { + additional_feedback_event_sender, + fx_snapshot_chunk_hash_by_fx: Default::default(), + touched_things: Default::default(), + current_pot_preset_by_fx: Default::default(), + } + } + + pub fn current_fx_preset(&self, fx: &Fx) -> Option<&pot::CurrentPreset> { + let fx = fx.guid_based()?; + self.current_pot_preset_by_fx.get(&fx) + } + + pub fn set_current_fx_preset(&mut self, fx: Fx, current_preset: pot::CurrentPreset) { + // reaper_high::Fx is really not a good candidate for hashing. At the moment, we must + // "normalize" it to be guid-based to really match. But it's still dirty, even the FX + // chain and track matching. We need several distinct types! + let Some(fx) = fx.guid_based() else { + return; + }; + self.current_pot_preset_by_fx.insert(fx, current_preset); + self.additional_feedback_event_sender + .send_complaining(AdditionalFeedbackEvent::MappedFxParametersChanged); + } + + pub fn current_fx_snapshot_chunk_hash(&self, fx: &Fx) -> Option { + self.fx_snapshot_chunk_hash_by_fx.get(fx).copied() + } + + pub fn load_fx_snapshot( + &mut self, + fx: Fx, + chunk: &str, + chunk_hash: u64, + ) -> Result<(), &'static str> { + fx.set_tag_chunk(chunk)?; + // fx.set_vst_chunk_encoded(chunk.to_string())?; + self.fx_snapshot_chunk_hash_by_fx + .insert(fx.clone(), chunk_hash); + self.additional_feedback_event_sender.send_complaining( + AdditionalFeedbackEvent::FxSnapshotLoaded(FxSnapshotLoadedEvent { fx }), + ); + Ok(()) + } + + pub fn touch_automation_parameter( + &mut self, + track: &Track, + parameter_type: TouchedTrackParameterType, + gang_behavior: TrackGangBehavior, + ) { + let Ok(raw_track) = track.raw() else { + return; + }; + self.touched_things + .insert(TouchedThing::new(raw_track, parameter_type)); + self.post_process_touch(track, parameter_type, gang_behavior, true); + self.additional_feedback_event_sender.send_complaining( + AdditionalFeedbackEvent::ParameterAutomationTouchStateChanged( + ParameterAutomationTouchStateChangedEvent { + track: raw_track, + parameter_type, + new_value: true, + }, + ), + ); + } + + pub fn untouch_automation_parameter( + &mut self, + track: &Track, + parameter_type: TouchedTrackParameterType, + gang_behavior: TrackGangBehavior, + ) { + let Ok(raw_track) = track.raw() else { + return; + }; + self.touched_things + .remove(&TouchedThing::new(raw_track, parameter_type)); + self.post_process_touch(track, parameter_type, gang_behavior, false); + self.additional_feedback_event_sender.send_complaining( + AdditionalFeedbackEvent::ParameterAutomationTouchStateChanged( + ParameterAutomationTouchStateChangedEvent { + track: raw_track, + parameter_type, + new_value: false, + }, + ), + ); + } + + fn post_process_touch( + &mut self, + track: &Track, + parameter_type: TouchedTrackParameterType, + gang_behavior: TrackGangBehavior, + touched: bool, + ) { + let (gang_behavior, grouping_behavior) = gang_behavior.gang_and_grouping_behavior(); + let opts = TrackSetSmartOpts { + grouping_behavior, + gang_behavior, + done: !touched, + }; + match parameter_type { + TouchedTrackParameterType::Volume => { + let _ = track.set_volume_smart(track.volume(), opts); + } + TouchedTrackParameterType::Pan => { + let _ = track.set_pan_smart(track.pan().reaper_value(), opts); + } + TouchedTrackParameterType::Width => { + let _ = track.set_width_smart(track.width().reaper_value(), opts); + } + } + } + + pub fn automation_parameter_is_touched( + &self, + track: MediaTrack, + parameter_type: TouchedTrackParameterType, + ) -> bool { + self.touched_things + .contains(&TouchedThing::new(track, parameter_type)) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/reaper_config_change_detector.rs b/plugin-reaper-realearn/main/src/domain/reaper_config_change_detector.rs new file mode 100644 index 0000000..f564265 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/reaper_config_change_detector.rs @@ -0,0 +1,44 @@ +use reaper_high::Reaper; + +#[derive(Debug, Default)] +pub struct ReaperConfigChangeDetector { + project_options: ProjectOptions, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct ProjectOptions { + pub run_background_projects: bool, + pub run_stopped_background_projects: bool, +} + +#[derive(Debug)] +pub enum ReaperConfigChange { + ProjectOptions(ProjectOptions), +} + +impl ReaperConfigChangeDetector { + pub fn poll_for_changes(&mut self) -> Vec { + let mut changes = vec![]; + let project_options = get_project_options(); + if project_options != self.project_options { + self.project_options = project_options; + changes.push(ReaperConfigChange::ProjectOptions(project_options)); + } + changes + } +} + +pub fn get_project_options() -> ProjectOptions { + if let Some(res) = Reaper::get().medium_reaper().get_config_var("multiprojopt") { + assert!(res.size > 0, "multiprojopt value should have size > 0"); + let bit_mask = unsafe { res.value.cast::().as_ref() }; + ProjectOptions { + // Bit 0 = disable_background_projects + run_background_projects: (*bit_mask & (1 << 0)) == 0, + // Bit 1 = enable_stopped_background_projects + run_stopped_background_projects: (*bit_mask & (1 << 1)) != 0, + } + } else { + Default::default() + } +} diff --git a/plugin-reaper-realearn/main/src/domain/reaper_source.rs b/plugin-reaper-realearn/main/src/domain/reaper_source.rs new file mode 100644 index 0000000..86557b9 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/reaper_source.rs @@ -0,0 +1,324 @@ +use crate::domain::{ + CompartmentKind, CompartmentParamIndex, RawParamValue, ReaperSourceAddress, StreamDeckDeviceId, +}; +use base::hash_util::NonCryptoHashSet; +use core::fmt; +use derive_more::Display; +use helgoboss_learn::{ + format_percentage_without_unit, parse_percentage_without_unit, ControlValue, + DetailedSourceCharacter, FeedbackValue, SourceCharacter, UnitValue, +}; +use reaper_medium::{MidiInputDeviceId, MidiOutputDeviceId}; +use std::convert::TryInto; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::time::{Duration, Instant}; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum ReaperSource { + MidiDeviceChanges, + RealearnInstanceStart, + RealearnCompartmentLoaded, + Timer(TimerSource), + RealearnParameter(RealearnParameterSource), + Speech(SpeechSource), +} + +#[derive(Clone, Eq, PartialEq, Debug, Default)] +pub struct SpeechSource {} + +impl SpeechSource { + pub fn new() -> Self { + Self::default() + } + + pub fn feedback(&self, feedback_value: &FeedbackValue) -> SpeechSourceFeedbackValue { + SpeechSourceFeedbackValue { + text: feedback_value.to_textual().text.to_string(), + } + } +} + +pub fn say(feedback_value: SpeechSourceFeedbackValue) -> Result<(), Box> { + #[cfg(any(target_os = "windows", target_os = "macos"))] + { + fn get_default_tts() -> Result { + #[cfg(target_os = "macos")] + { + let mut tts = tts::Tts::default()?; + // On macOS, at least with AVFoundation, it's necessary to set a voice first. + // Prefer an English voice as default. + if let Ok(voices) = tts.voices() { + let voice = voices + .iter() + .find(|v| v.language().language.as_str() == "en") + .or_else(|| voices.first()); + if let Some(v) = voice { + tts.set_voice(v)?; + } + } + Ok(tts) + } + #[cfg(target_os = "windows")] + { + tts::Tts::default() + } + } + use once_cell::sync::Lazy; + use std::sync::Mutex; + static TTS: Lazy, tts::Error>> = + Lazy::new(|| get_default_tts().map(Mutex::new)); + let tts = TTS.as_ref()?; + // TODO-medium This is only necessary because tts exposes a non-optimal API. + let mut tts = tts.lock()?; + // TODO-medium This cloning is totally unnecessary but ... non-optimal API. + tts.speak(feedback_value.text, true)?; + Ok(()) + } + #[cfg(target_os = "linux")] + { + let _ = feedback_value; + Err("speech source not yet supported on Linux".into()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct RealearnParameterSource { + pub parameter_index: CompartmentParamIndex, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct TimerSource { + duration: Duration, + last_fire: Option, +} + +impl TimerSource { + pub fn new(interval: Duration) -> Self { + Self { + duration: interval, + last_fire: None, + } + } + + pub fn on_deactivate(&mut self) { + self.last_fire = None; + } + + pub fn poll(&mut self) -> Option { + let now = Instant::now(); + if let Some(last_fire) = self.last_fire { + let elapsed = now - last_fire; + if elapsed >= self.duration { + Some(self.fire(now)) + } else { + None + } + } else { + Some(self.fire(now)) + } + } + + fn fire(&mut self, now: Instant) -> ControlValue { + self.last_fire = Some(now); + ControlValue::AbsoluteContinuous(UnitValue::MAX) + } +} + +impl ReaperSource { + pub fn extract_feedback_address(&self) -> Option { + use ReaperSource::*; + match self { + Speech(_) => Some(ReaperSourceAddress::GlobalSpeech), + _ => None, + } + } + + pub fn on_deactivate(&mut self) { + match self { + ReaperSource::Timer(s) => s.on_deactivate(), + _ => {} + } + } + + /// If this returns `true`, the `poll` method should be called, on a regular basis. + pub fn wants_to_be_polled(&self) -> bool { + matches!(self, ReaperSource::Timer(_)) + } + + pub fn possible_detailed_characters(&self) -> Vec { + use ReaperSource::*; + match self { + MidiDeviceChanges => vec![DetailedSourceCharacter::MomentaryOnOffButton], + RealearnInstanceStart => vec![DetailedSourceCharacter::MomentaryOnOffButton], + RealearnCompartmentLoaded => vec![DetailedSourceCharacter::Trigger], + Timer(_) => vec![DetailedSourceCharacter::Trigger], + RealearnParameter(_) => vec![ + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + ], + Speech(_) => vec![DetailedSourceCharacter::RangeControl], + } + } + + pub fn format_control_value(&self, value: ControlValue) -> Result { + let formatted = format_percentage_without_unit(value.to_unit_value()?.get()); + Ok(formatted) + } + + pub fn parse_control_value(&self, text: &str) -> Result { + parse_percentage_without_unit(text)?.try_into() + } + + pub fn character(&self) -> SourceCharacter { + use ReaperSource::*; + match self { + MidiDeviceChanges | RealearnInstanceStart | RealearnCompartmentLoaded | Timer(_) => { + SourceCharacter::MomentaryButton + } + RealearnParameter(_) => SourceCharacter::RangeElement, + Speech(_) => SourceCharacter::RangeElement, + } + } + + pub fn poll(&mut self) -> Option { + if let ReaperSource::Timer(t) = self { + t.poll() + } else { + None + } + } + + pub fn control( + &mut self, + msg: &ReaperMessage, + compartment: CompartmentKind, + ) -> Option { + use ReaperMessage::*; + let control_value = match msg { + StreamDeckDevicesConnected(_) => { + // We don't have a corresponding source yet + return None; + } + MidiDevicesConnected(_) => match self { + ReaperSource::MidiDeviceChanges => ControlValue::AbsoluteContinuous(UnitValue::MAX), + _ => return None, + }, + MidiDevicesDisconnected(_) => match self { + ReaperSource::MidiDeviceChanges => ControlValue::AbsoluteContinuous(UnitValue::MIN), + _ => return None, + }, + RealearnUnitStarted => match self { + ReaperSource::RealearnInstanceStart => { + ControlValue::AbsoluteContinuous(UnitValue::MAX) + } + _ => return None, + }, + RealearnCompartmentLoaded(kind) => match self { + ReaperSource::RealearnCompartmentLoaded if *kind == compartment => { + ControlValue::AbsoluteContinuous(UnitValue::MAX) + } + _ => return None, + }, + RealearnParameterChange(c) => match self { + ReaperSource::RealearnParameter(s) + if c.compartment == compartment && c.parameter_index == s.parameter_index => + { + ControlValue::AbsoluteContinuous(UnitValue::new_clamped(c.value as f64)) + } + _ => return None, + }, + }; + Some(control_value) + } + + pub fn feedback(&self, feedback_value: &FeedbackValue) -> Option { + use ReaperSource::*; + match self { + MidiDeviceChanges + | RealearnInstanceStart + | RealearnCompartmentLoaded + | Timer(_) + | RealearnParameter(_) => None, + Speech(s) => Some(ReaperSourceFeedbackValue::Speech( + s.feedback(feedback_value), + )), + } + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum ReaperSourceFeedbackValue { + Speech(SpeechSourceFeedbackValue), +} + +impl ReaperSourceFeedbackValue { + pub fn extract_feedback_address(&self) -> Option { + match self { + ReaperSourceFeedbackValue::Speech(_) => Some(ReaperSourceAddress::GlobalSpeech), + } + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct SpeechSourceFeedbackValue { + pub text: String, +} + +#[derive(PartialEq, Debug, Display)] +pub enum ReaperMessage { + #[display(fmt = "MidiDevicesConnected ({_0})")] + MidiDevicesConnected(MidiDeviceChangePayload), + #[display(fmt = "MidiDevicesDisconnected ({_0})")] + MidiDevicesDisconnected(MidiDeviceChangePayload), + RealearnUnitStarted, + RealearnCompartmentLoaded(CompartmentKind), + RealearnParameterChange(RealearnParameterChangePayload), + StreamDeckDevicesConnected(StreamDeckDevicePayload), +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct RealearnParameterChangePayload { + pub compartment: CompartmentKind, + pub parameter_index: CompartmentParamIndex, + pub value: RawParamValue, +} + +impl Display for RealearnParameterChangePayload { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "Parameter {} with value {}", + self.parameter_index, self.value + ) + } +} + +#[derive(Eq, PartialEq, Debug)] +pub struct StreamDeckDevicePayload { + pub devices: NonCryptoHashSet, +} + +impl Display for StreamDeckDevicePayload { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Devices: {:?}", &self.devices,) + } +} + +#[derive(Eq, PartialEq, Debug)] +pub struct MidiDeviceChangePayload { + pub input_devices: NonCryptoHashSet, + pub output_devices: NonCryptoHashSet, +} + +impl Display for MidiDeviceChangePayload { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "Input devices: {:?}, Output devices: {:?}", + &self.input_devices, &self.output_devices + ) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/reaper_target.rs b/plugin-reaper-realearn/main/src/domain/reaper_target.rs new file mode 100644 index 0000000..caa3638 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/reaper_target.rs @@ -0,0 +1,1557 @@ +use std::borrow::Cow; +use std::convert::TryInto; +use std::rc::Rc; + +use derive_more::Display; +use enum_dispatch::enum_dispatch; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use reaper_high::{ + Action, AvailablePanValue, BookmarkType, ChangeEvent, Fx, FxChain, FxParameter, + GroupingBehavior, Pan, PanExt, PlayRate, Project, Reaper, ReaperError, Tempo, Track, + TrackRoute, Width, +}; +use reaper_medium::{ + AutomationMode, Bpm, GangBehavior, GlobalAutomationModeOverride, NormalizedPlayRate, ParamId, + PlaybackSpeedFactor, PositionInSeconds, SectionContext, +}; +use rxrust::prelude::*; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use strum::EnumIter; + +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, PropValue, Target, UnitValue, +}; +use helgobox_api::persistence::{ActionScope, SeekBehavior, TrackScope}; + +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + get_reaper_track_area_of_scope, handle_exclusivity, ActionTarget, AdditionalFeedbackEvent, + AllTrackFxEnableTarget, AutomationModeOverrideTarget, BrowseFxsTarget, + BrowsePotFilterItemsTarget, BrowsePotPresetsTarget, BrowseTracksTarget, Caller, + CompartmentParameterValueTarget, ControlContext, DummyTarget, EnableUnitsTarget, + EnigoMouseTarget, FxEnableTarget, FxOnlineTarget, FxOpenTarget, FxParameterTarget, + FxParameterTouchStateTarget, FxPresetTarget, FxToolTarget, GoToBookmarkTarget, HierarchyEntry, + HierarchyEntryProvider, LoadFxSnapshotTarget, LoadPotPresetTarget, MappingControlContext, + MidiSendTarget, ModifyMappingTarget, OscSendTarget, PlayrateTarget, PlaytimeColumnActionTarget, + PlaytimeMatrixActionTarget, PlaytimeRowActionTarget, PlaytimeSlotTransportTarget, + PreviewPotPresetTarget, RealTimeControlContext, RealTimeFxParameterTarget, RouteMuteTarget, + RoutePanTarget, RouteTouchStateTarget, RouteVolumeTarget, SeekTarget, + StreamDeckBrightnessTarget, TakeMappingSnapshotTarget, TargetTypeDef, TempoTarget, + TrackArmTarget, TrackAutomationModeTarget, TrackMonitoringModeTarget, TrackMuteTarget, + TrackPanTarget, TrackParentSendTarget, TrackPeakTarget, TrackSelectionTarget, TrackShowTarget, + TrackSoloTarget, TrackTouchStateTarget, TrackVolumeTarget, TrackWidthTarget, TransportTarget, + UnitEvent, +}; +use crate::domain::{ + AnyOnTarget, BrowseGroupMappingsTarget, CompoundChangeEvent, EnableInstancesTarget, + EnableMappingsTarget, HitResponse, LoadMappingSnapshotTarget, RealearnTarget, ReaperTargetType, + RouteAutomationModeTarget, RouteMonoTarget, RoutePhaseTarget, TrackPhaseTarget, + TrackToolTarget, +}; +use base::default_util::is_default; +use base::Global; + +/// This target character is just used for GUI and auto-correct settings! It doesn't have influence +/// on control/feedback. +/// +/// This relates more to the character of the target value, actually. Not the way the target wants +/// to be controlled. That's why introducing a "Relative" character is not a good idea. The value +/// can still be discrete or continuous, it's orthogonal. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum TargetCharacter { + /// "Fire-only", so like switch but it only makes sense to send "on", not "off". + /// + /// Rendered as one trigger button. + Trigger, + /// When there are just two states: "on" and "off". + /// + /// Rendered as on/off buttons. + Switch, + /// Whenever there's a certain, discrete number of target values (steps). + /// + /// Rendered as slider or -/+ buttons (if target type is relative). + Discrete, + /// Whenever the step size between two target values can get arbitrarily small. + /// + /// Rendered as slider or -/+ buttons (if target type is relative). + Continuous, + /// When the target is a virtual control element that allows for more than 2 states. + VirtualMulti, + /// When the target is a virtual control element that allows for a maximum of 2 states. + VirtualButton, +} + +impl TargetCharacter { + pub fn is_button_like(self) -> bool { + use TargetCharacter::*; + matches!(self, Trigger | Switch | VirtualButton) + } +} + +/// This is a ReaLearn target. +/// +/// Unlike TargetModel, the real target has everything resolved already (e.g. track and FX) and +/// is immutable. +// TODO-medium Rename to RealTarget +#[enum_dispatch] +#[derive(Clone, Debug, PartialEq)] +pub enum ReaperTarget { + Mouse(EnigoMouseTarget), + Action(ActionTarget), + FxTool(FxToolTarget), + FxParameter(FxParameterTarget), + FxParameterTouchState(FxParameterTouchStateTarget), + TrackVolume(TrackVolumeTarget), + TrackTool(TrackToolTarget), + TrackPeak(TrackPeakTarget), + TrackRouteVolume(RouteVolumeTarget), + TrackPan(TrackPanTarget), + TrackWidth(TrackWidthTarget), + TrackArm(TrackArmTarget), + TrackParentSend(TrackParentSendTarget), + TrackSelection(TrackSelectionTarget), + TrackMute(TrackMuteTarget), + TrackPhase(TrackPhaseTarget), + TrackShow(TrackShowTarget), + TrackSolo(TrackSoloTarget), + TrackAutomationMode(TrackAutomationModeTarget), + TrackMonitoringMode(TrackMonitoringModeTarget), + RoutePan(RoutePanTarget), + RouteMute(RouteMuteTarget), + RoutePhase(RoutePhaseTarget), + RouteMono(RouteMonoTarget), + RouteAutomationMode(RouteAutomationModeTarget), + RouteTouchState(RouteTouchStateTarget), + Tempo(TempoTarget), + Playrate(PlayrateTarget), + AutomationModeOverride(AutomationModeOverrideTarget), + FxEnable(FxEnableTarget), + FxOnline(FxOnlineTarget), + FxOpen(FxOpenTarget), + FxPreset(FxPresetTarget), + BrowseTracks(BrowseTracksTarget), + BrowseFxs(BrowseFxsTarget), + AllTrackFxEnable(AllTrackFxEnableTarget), + Transport(TransportTarget), + AnyOn(AnyOnTarget), + LoadFxSnapshot(LoadFxSnapshotTarget), + TrackAutomationTouchState(TrackTouchStateTarget), + GoToBookmark(GoToBookmarkTarget), + Seek(SeekTarget), + SendMidi(MidiSendTarget), + SendOsc(OscSendTarget), + Dummy(DummyTarget), + PlaytimeMatrixAction(PlaytimeMatrixActionTarget), + PlaytimeControlUnitScroll(crate::domain::PlaytimeControlUnitScrollTarget), + PlaytimeBrowseCells(crate::domain::PlaytimeBrowseCellsTarget), + PlaytimeSlotTransportAction(PlaytimeSlotTransportTarget), + PlaytimeColumnAction(PlaytimeColumnActionTarget), + PlaytimeRowAction(PlaytimeRowActionTarget), + PlaytimeSlotSeek(crate::domain::PlaytimeSlotSeekTarget), + PlaytimeSlotVolume(crate::domain::PlaytimeSlotVolumeTarget), + PlaytimeSlotManagementAction(crate::domain::PlaytimeSlotManagementActionTarget), + LoadMappingSnapshot(LoadMappingSnapshotTarget), + TakeMappingSnapshot(TakeMappingSnapshotTarget), + EnableMappings(EnableMappingsTarget), + ModifyMapping(ModifyMappingTarget), + EnableInstances(EnableInstancesTarget), + EnableUnits(EnableUnitsTarget), + BrowseGroupMappings(BrowseGroupMappingsTarget), + BrowsePotFilterItems(BrowsePotFilterItemsTarget), + BrowsePotPresets(BrowsePotPresetsTarget), + PreviewPotPreset(PreviewPotPresetTarget), + LoadPotPreset(LoadPotPresetTarget), + CompartmentParameterValue(CompartmentParameterValueTarget), + StreamDeckBrightness(StreamDeckBrightnessTarget), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum SendMidiDestinationType { + #[serde(rename = "fx-output")] + #[display(fmt = "FX output")] + FxOutput, + #[serde(rename = "feedback-output")] + #[display(fmt = "Feedback output")] + FeedbackOutput, + #[serde(rename = "input-device")] + #[display(fmt = "Input device")] + InputDevice, +} + +impl Default for SendMidiDestinationType { + fn default() -> Self { + Self::FxOutput + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SeekOptions { + #[serde(default, skip_serializing_if = "is_default")] + pub use_time_selection: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub use_loop_points: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub use_regions: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub use_project: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub move_view: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub seek_play: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub feedback_resolution: FeedbackResolution, +} + +/// Determines in which granularity the play position influences feedback of a target. +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +// Don't change the numbers! They are important for ordering. Higher number means higher resolution. +pub enum FeedbackResolution { + /// Query for feedback every beat that's played on the main timeline. + #[serde(rename = "beat")] + #[display(fmt = "Beat")] + Beat = 0, + /// Query for feedback as frequently as possible (results in brute-force polling once per + /// main loop cycle). + #[serde(rename = "high")] + #[display(fmt = "Fast")] + High = 1, +} + +impl Default for FeedbackResolution { + fn default() -> Self { + Self::Beat + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum FxDisplayType { + #[serde(rename = "floating")] + #[display(fmt = "Floating window")] + FloatingWindow, + #[serde(rename = "chain")] + #[display(fmt = "FX chain (limited feedback)")] + Chain, +} + +impl Default for FxDisplayType { + fn default() -> Self { + Self::FloatingWindow + } +} + +impl ReaperTarget { + /// Returns `true` if the given change event can be a reason for re-resolving targets or + /// auto-loading another main preset. + pub fn changes_conditions(evt: CompoundChangeEvent) -> bool { + match evt { + CompoundChangeEvent::Reaper(evt) => { + use ChangeEvent::*; + match + evt { + | FxClosed(_) + | FxOpened(_) + | FxFocused(_) + // For FX-to-preset links that also have preset name as criteria + | FxPresetChanged(_) + | ProjectSwitched(_) + | TrackAdded(_) + | TrackRemoved(_) + | TracksReordered(_) + | TrackNameChanged(_) + | FxAdded(_) + | FxRemoved(_) + | FxReordered(_) + | BookmarksChanged(_) + | ReceiveCountChanged(_) + | TrackSendCountChanged(_) + | HardwareOutputSendCountChanged(_) + | TrackSelectedChanged(_) + | TrackVisibilityChanged(_) => true, + _ => false, + } + } + CompoundChangeEvent::CompartmentParameter(_) => true, + CompoundChangeEvent::Additional(evt) => { + use AdditionalFeedbackEvent::*; + matches!( + evt, + // Auto-load should load other preset (REAPER 7+ only!) + FocusSwitchedBetweenMainAndFx + // Dynamic FX parameter expression should be re-resolved + | MappedFxParametersChanged + ) + } + CompoundChangeEvent::Unit(e) => { + matches!(e, UnitEvent::ControlUnitTopLeftCornerChanged(_)) + } + CompoundChangeEvent::Instance(_) => false, + #[cfg(feature = "playtime")] + CompoundChangeEvent::ClipMatrix(e) => { + use playtime_clip_engine::base::ClipMatrixEvent; + matches!( + e, + ClipMatrixEvent::EverythingChanged | ClipMatrixEvent::ActiveCellChanged + ) + } + } + } + + pub fn touched_from_additional_event(evt: &AdditionalFeedbackEvent) -> Option { + let target = match evt { + AdditionalFeedbackEvent::ActionInvoked(e) => { + if e.section_context != SectionContext::MainSection { + return None; + } + let action = Reaper::get() + .main_section() + .action_by_command_id(e.command_id); + determine_target_for_action(action) + } + _ => return None, + }; + Some(target) + } + + /// This is eventually going to replace Rx (touched method), at least for domain layer. + pub fn touched_from_change_event(evt: ChangeEvent) -> Option { + use ChangeEvent::*; + use ReaperTarget::*; + let target = match evt { + TrackVolumeChanged(e) if e.touched => TrackVolume(TrackVolumeTarget { + track: e.track, + gang_behavior: Default::default(), + }), + TrackPanChanged(e) if e.touched => { + if let AvailablePanValue::Complete(new_value) = e.new_value { + figure_out_touched_pan_component(e.track, e.old_value, new_value) + } else { + // Shouldn't result in this if touched. + return None; + } + } + TrackRouteVolumeChanged(e) if e.touched => { + TrackRouteVolume(RouteVolumeTarget { route: e.route }) + } + TrackRoutePanChanged(e) if e.touched => RoutePan(RoutePanTarget { route: e.route }), + TrackArmChanged(e) => TrackArm(TrackArmTarget { + track: e.track, + exclusivity: Default::default(), + gang_behavior: Default::default(), + }), + TrackMuteChanged(e) if e.touched => TrackMute(TrackMuteTarget { + track: e.track, + exclusivity: Default::default(), + gang_behavior: Default::default(), + }), + TrackSoloChanged(e) => { + // When we press the solo button of some track, REAPER actually sends many + // change events, starting with the change event for the master track. This is + // not cool for learning because we could only ever learn master-track solo, + // which doesn't even make sense. So let's just filter it out. + if e.track.is_master_track() { + return None; + } + TrackSolo(TrackSoloTarget { + track: e.track, + behavior: Default::default(), + exclusivity: Default::default(), + gang_behavior: Default::default(), + }) + } + TrackSelectedChanged(e) if e.new_value => { + if track_sel_on_mouse_is_enabled() { + // If this REAPER preference is enabled, it's often a false positive so better + // we don't let this happen at all. + return None; + } + TrackSelection(TrackSelectionTarget { + track: e.track, + exclusivity: Default::default(), + scroll_arrange_view: false, + scroll_mixer: false, + }) + } + FxEnabledChanged(e) => FxEnable(FxEnableTarget { + fx: e.fx, + bypass_param_index: None, + }), + FxParameterValueChanged(e) if e.touched && !is_bypass_param(&e.parameter) => { + FxParameter(FxParameterTarget { + is_real_time_ready: false, + param: e.parameter, + // This must be false (#849) because this target will be picked up by + // "Global: Last touched". UnresolvedLastTouchedTarget doesn't take care of + // putting the corresponding mapping into the set of polled mappings, so + // polling won't work. It also wouldn't be useful to do so because mappings + // which need polling for feedback wouldn't be picked up by "Global: Last + // touched" anyway! + poll_for_feedback: false, + retrigger: false, + real_time_even_if_not_rendering: false, + }) + } + FxPresetChanged(e) => FxPreset(FxPresetTarget { fx: e.fx }), + MasterTempoChanged(e) if e.touched => Tempo(TempoTarget { + // TODO-low In future this might come from a certain project + project: Reaper::get().current_project(), + }), + MasterPlayRateChanged(e) if e.touched => Playrate(PlayrateTarget { + // TODO-low In future this might come from a certain project + project: Reaper::get().current_project(), + }), + TrackAutomationModeChanged(e) => TrackAutomationMode(TrackAutomationModeTarget { + track: e.track, + exclusivity: Default::default(), + mode: e.new_value, + }), + TrackInputMonitoringChanged(e) => TrackMonitoringMode(TrackMonitoringModeTarget { + track: e.track, + exclusivity: Default::default(), + mode: e.new_value, + gang_behavior: Default::default(), + }), + GlobalAutomationOverrideChanged(e) => { + AutomationModeOverride(AutomationModeOverrideTarget { + mode_override: e.new_value, + }) + } + _ => return None, + }; + Some(target) + } + + // TODO-medium This is the last Rx trace we have in processing logic and we should replace it + // in favor of async/await or direct calls. Still used by local learning and "Filter target". + pub fn touched() -> impl LocalObservable<'static, Item = Rc, Err = ()> + 'static { + use ReaperTarget::*; + let reaper = Reaper::get(); + let csurf_rx = Global::control_surface_rx(); + let action_rx = Global::action_rx(); + observable::empty() + .merge(csurf_rx.fx_enabled_changed().map(move |fx| { + FxEnable(FxEnableTarget { + fx, + bypass_param_index: None, + }) + .into() + })) + .merge(csurf_rx.fx_parameter_touched().filter_map(move |param| { + if is_bypass_param(¶m) { + return None; + } + let t = FxParameterTarget { + is_real_time_ready: false, + param, + poll_for_feedback: false, + retrigger: false, + real_time_even_if_not_rendering: false, + }; + Some(FxParameter(t).into()) + })) + .merge( + csurf_rx + .fx_preset_changed() + .map(move |fx| FxPreset(FxPresetTarget { fx }).into()), + ) + .merge(csurf_rx.track_volume_touched().map(move |track| { + TrackVolume(TrackVolumeTarget { + track, + gang_behavior: Default::default(), + }) + .into() + })) + .merge(csurf_rx.track_pan_touched().map(move |(track, old, new)| { + figure_out_touched_pan_component(track, old, new).into() + })) + .merge(csurf_rx.track_arm_changed().map(move |track| { + TrackArm(TrackArmTarget { + track, + exclusivity: Default::default(), + gang_behavior: Default::default(), + }) + .into() + })) + .merge( + csurf_rx + .track_selected_changed() + .filter(|(_, new_value)| { + // If this REAPER preference is enabled, it's often a false positive so + // better we don't let this happen at all. + *new_value && !track_sel_on_mouse_is_enabled() + }) + .map(move |(track, _)| { + TrackSelection(TrackSelectionTarget { + track, + exclusivity: Default::default(), + scroll_arrange_view: false, + scroll_mixer: false, + }) + .into() + }), + ) + .merge(csurf_rx.track_mute_touched().map(move |track| { + TrackMute(TrackMuteTarget { + track, + exclusivity: Default::default(), + gang_behavior: Default::default(), + }) + .into() + })) + .merge(csurf_rx.track_automation_mode_changed().map(move |track| { + let mode = track.automation_mode(); + TrackAutomationMode(TrackAutomationModeTarget { + track, + exclusivity: Default::default(), + mode, + }) + .into() + })) + .merge(csurf_rx.track_input_monitoring_changed().map(move |track| { + let mode = track.input_monitoring_mode(); + TrackMonitoringMode(TrackMonitoringModeTarget { + track, + exclusivity: Default::default(), + mode, + gang_behavior: Default::default(), + }) + .into() + })) + .merge( + csurf_rx + .track_solo_changed() + // When we press the solo button of some track, REAPER actually sends many + // change events, starting with the change event for the master track. This is + // not cool for learning because we could only ever learn master-track solo, + // which doesn't even make sense. So let's just filter it out. + .filter(|track| !track.is_master_track()) + .map(move |track| { + TrackSolo(TrackSoloTarget { + track, + behavior: Default::default(), + exclusivity: Default::default(), + gang_behavior: Default::default(), + }) + .into() + }), + ) + .merge( + csurf_rx + .track_route_volume_touched() + .map(move |route| TrackRouteVolume(RouteVolumeTarget { route }).into()), + ) + .merge( + csurf_rx + .track_route_pan_touched() + .map(move |route| RoutePan(RoutePanTarget { route }).into()), + ) + .merge( + action_rx + .action_invoked() + .map(move |action| determine_target_for_action((*action).clone()).into()), + ) + .merge( + csurf_rx + .master_tempo_touched() + // TODO-low In future this might come from a certain project + .map(move |_| { + Tempo(TempoTarget { + project: reaper.current_project(), + }) + .into() + }), + ) + .merge( + csurf_rx + .master_playrate_touched() + // TODO-low In future this might come from a certain project + .map(move |_| { + Playrate(PlayrateTarget { + project: reaper.current_project(), + }) + .into() + }), + ) + .merge(csurf_rx.global_automation_override_changed().map(move |_| { + AutomationModeOverride(AutomationModeOverrideTarget { + mode_override: Reaper::get().global_automation_override(), + }) + .into() + })) + } +} + +impl<'a> Target<'a> for ReaperTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext) -> Option { + use ReaperTarget::*; + match self { + Mouse(t) => t.current_value(context), + SendOsc(t) => t.current_value(context), + SendMidi(t) => t.current_value(()), + Dummy(t) => t.current_value(()), + TrackPeak(t) => t.current_value(context), + Action(t) => t.current_value(context), + FxParameter(t) => t.current_value(context), + FxParameterTouchState(t) => t.current_value(context), + TrackVolume(t) => t.current_value(context), + TrackTool(t) => t.current_value(context), + TrackPan(t) => t.current_value(context), + TrackWidth(t) => t.current_value(context), + TrackArm(t) => t.current_value(context), + TrackParentSend(t) => t.current_value(context), + TrackRouteVolume(t) => t.current_value(context), + TrackSelection(t) => t.current_value(context), + TrackMute(t) => t.current_value(context), + TrackPhase(t) => t.current_value(context), + TrackShow(t) => t.current_value(context), + TrackSolo(t) => t.current_value(context), + TrackAutomationMode(t) => t.current_value(context), + TrackMonitoringMode(t) => t.current_value(context), + RoutePan(t) => t.current_value(context), + RouteMute(t) => t.current_value(context), + RoutePhase(t) => t.current_value(context), + RouteMono(t) => t.current_value(context), + RouteAutomationMode(t) => t.current_value(context), + RouteTouchState(t) => t.current_value(context), + Tempo(t) => t.current_value(context), + Playrate(t) => t.current_value(context), + AutomationModeOverride(t) => t.current_value(context), + FxTool(t) => t.current_value(context), + FxEnable(t) => t.current_value(context), + FxOnline(t) => t.current_value(context), + FxOpen(t) => t.current_value(context), + // Discrete + FxPreset(t) => t.current_value(context), + LoadFxSnapshot(t) => t.current_value(context), + // Discrete + BrowseTracks(t) => t.current_value(context), + // Discrete + BrowseFxs(t) => t.current_value(context), + AllTrackFxEnable(t) => t.current_value(context), + Transport(t) => t.current_value(context), + AnyOn(t) => t.current_value(context), + TrackAutomationTouchState(t) => t.current_value(context), + GoToBookmark(t) => t.current_value(context), + Seek(t) => t.current_value(context), + PlaytimeSlotTransportAction(t) => t.current_value(context), + PlaytimeColumnAction(t) => t.current_value(context), + PlaytimeRowAction(t) => t.current_value(context), + PlaytimeSlotSeek(t) => t.current_value(context), + PlaytimeSlotVolume(t) => t.current_value(context), + PlaytimeSlotManagementAction(t) => t.current_value(context), + PlaytimeMatrixAction(t) => t.current_value(context), + PlaytimeControlUnitScroll(t) => t.current_value(context), + PlaytimeBrowseCells(t) => t.current_value(context), + LoadMappingSnapshot(t) => t.current_value(context), + TakeMappingSnapshot(t) => t.current_value(context), + EnableMappings(t) => t.current_value(context), + ModifyMapping(t) => t.current_value(context), + EnableInstances(t) => t.current_value(context), + EnableUnits(t) => t.current_value(context), + BrowseGroupMappings(t) => t.current_value(context), + BrowsePotFilterItems(t) => t.current_value(context), + BrowsePotPresets(t) => t.current_value(context), + PreviewPotPreset(t) => t.current_value(context), + LoadPotPreset(t) => t.current_value(context), + CompartmentParameterValue(t) => t.current_value(context), + StreamDeckBrightness(t) => t.current_value(context), + } + } + + fn control_type(&self, context: ControlContext) -> ControlType { + self.control_type_and_character(context).0 + } +} +impl<'a> Target<'a> for RealTimeReaperTarget { + type Context = RealTimeControlContext<'a>; + + fn current_value(&self, ctx: RealTimeControlContext) -> Option { + use RealTimeReaperTarget::*; + match self { + SendMidi(t) => t.current_value(()), + // We can safely use a mutex (without contention) if the preview registers get_samples() + // and this code here is called in the same real-time thread. If live FX multiprocessing + // is enabled, this is not the case and then we can have contention and dropouts! If we + // need to support that one day, we can alternatively use senders. The downside is that + // we have fire-and-forget then. We can't query the current value (at least not without + // more complex logic). So the target itself should support toggle play/stop etc. + PlaytimeSlotTransport(t) => t.current_value(ctx), + PlaytimeColumn(t) => t.current_value(ctx), + PlaytimeRow(t) => t.current_value(ctx), + PlaytimeMatrix(t) => t.current_value(ctx), + FxParameter(t) => t.current_value(ctx), + } + } + + fn control_type(&self, ctx: RealTimeControlContext) -> ControlType { + use RealTimeReaperTarget::*; + match self { + SendMidi(t) => t.control_type(()), + PlaytimeSlotTransport(t) => t.control_type(ctx), + PlaytimeColumn(t) => t.control_type(ctx), + PlaytimeRow(t) => t.control_type(ctx), + PlaytimeMatrix(t) => t.control_type(ctx), + FxParameter(t) => t.control_type(ctx), + } + } +} + +pub fn current_value_of_bookmark( + project: Project, + bookmark_type: BookmarkType, + index: u32, + pos: PositionInSeconds, +) -> UnitValue { + let current_bookmark = project.current_bookmark_at(pos); + let relevant_index = match bookmark_type { + BookmarkType::Marker => current_bookmark.marker_index, + BookmarkType::Region => current_bookmark.region_index, + }; + let is_current = relevant_index == Some(index); + convert_bool_to_unit_value(is_current) +} + +/// Converts a number of possible values to a step size. +pub fn convert_count_to_step_size(count: u32) -> UnitValue { + // Dividing 1.0 by n would divide the unit interval (0..=1) into n same-sized + // sub intervals, which means we would have n + 1 possible values. We want to + // represent just n values, so we need n - 1 same-sized sub intervals. + if count == 0 || count == 1 { + return UnitValue::MAX; + } + UnitValue::new(1.0 / (count - 1) as f64) +} + +pub fn format_value_as_playback_speed_factor_without_unit(value: UnitValue) -> String { + let play_rate = PlayRate::from_normalized_value(NormalizedPlayRate::new(value.get())); + format_playback_speed(play_rate.playback_speed_factor().get()) +} + +fn format_playback_speed(speed: f64) -> String { + format!("{speed:.4}") +} + +pub fn format_step_size_as_playback_speed_factor_without_unit(value: UnitValue) -> String { + // 0.0 => 0.0x + // 1.0 => 3.75x + let speed_increment = value.get() * playback_speed_factor_span(); + format_playback_speed(speed_increment) +} + +pub fn format_value_as_bpm_without_unit(value: UnitValue) -> String { + let tempo = Tempo::from_normalized_value(value.get()); + format_bpm(tempo.bpm().get()) +} + +pub fn format_step_size_as_bpm_without_unit(value: UnitValue) -> String { + // 0.0 => 0.0 bpm + // 1.0 => 959.0 bpm + let bpm_increment = value.get() * bpm_span(); + format_bpm(bpm_increment) +} + +// Should be 959.0 +pub fn bpm_span() -> f64 { + Bpm::NINE_HUNDRED_SIXTY_BPM.get() - Bpm::ONE_BPM.get() +} + +fn format_bpm(bpm: f64) -> String { + format!("{bpm:.4}") +} + +pub fn format_value_as_pan(value: UnitValue) -> String { + Pan::from_normalized_value(value.get()).to_string() +} + +pub fn format_value_as_on_off(value: UnitValue) -> &'static str { + format_bool_as_on_off(!value.is_zero()) +} + +pub fn format_bool_as_on_off(value: bool) -> &'static str { + if value { + "On" + } else { + "Off" + } +} + +pub fn convert_unit_value_to_preset_index(fx: &Fx, value: UnitValue) -> Option { + convert_unit_to_discrete_value_with_none(value, fx.preset_index_and_count().count) +} + +pub fn convert_unit_value_to_fx_index(fx_chain: &FxChain, value: UnitValue) -> Option { + convert_unit_to_discrete_value_with_none(value, fx_chain.fx_count()) +} + +pub fn convert_unit_to_discrete_value_with_none(value: UnitValue, count: u32) -> Option { + // Example: + 4 presets + if value.is_zero() { + // 0.00 => + None + } else { + // 0.25 => 0 + // 0.50 => 1 + // 0.75 => 2 + // 1.00 => 3 + + // Example: value = 0.75 + let step_size = 1.0 / count as f64; // 0.25 + let zero_based_value = (value.get() - step_size).max(0.0); // 0.5 + Some((zero_based_value * count as f64).round() as u32) // 2 + } +} + +pub fn convert_unit_to_discrete_value(value: UnitValue, count: u32) -> u32 { + if count == 0 { + return 0; + } + (value.get() * (count - 1) as f64).round() as u32 +} + +pub fn convert_discrete_to_unit_value(value: u32, count: u32) -> UnitValue { + if count < 2 { + return UnitValue::MIN; + } + UnitValue::new_clamped(value as f64 / (count - 1) as f64) +} + +pub fn scoped_track_index(track: &Track, scope: TrackScope) -> Option { + let global_index = track.index()?; + use TrackScope::*; + match scope { + AllTracks => Some(global_index), + TracksVisibleInTcp | TracksVisibleInMcp => { + let track_area = get_reaper_track_area_of_scope(scope); + track + .project() + .tracks() + // Global counting (counts all tracks) + .enumerate() + .filter(|(_, t)| t.is_shown(track_area)) + // Local counting (counts only visible tracks) + .enumerate() + .find(|(_, (global_i, _))| *global_i == global_index as usize) + .map(|(local_i, _)| local_i as u32) + } + } +} + +pub fn shown_fx_unit_value(fx_chain: &FxChain, index: Option) -> UnitValue { + convert_discrete_to_unit_value_with_none(index, fx_chain.fx_count()) +} + +pub fn fx_preset_unit_value(fx: &Fx, index: Option) -> UnitValue { + convert_discrete_to_unit_value_with_none(index, fx.preset_index_and_count().count) +} + +pub fn convert_discrete_to_unit_value_with_none(value: Option, count: u32) -> UnitValue { + // Example: + 4 presets + match value { + // => 0.00 + None => UnitValue::MIN, + // 0 => 0.25 + // 1 => 0.50 + // 2 => 0.75 + // 3 => 1.00 + Some(i) => { + if count == 0 { + return UnitValue::MIN; + } + // Example: i = 2 + let zero_based_value = i as f64 / count as f64; // 0.5 + let step_size = 1.0 / count as f64; // 0.25 + let value = (zero_based_value + step_size).min(1.0); // 0.75 + UnitValue::new(value) + } + } +} + +pub fn parse_value_from_pan(text: &str) -> Result { + let pan: Pan = text.parse()?; + pan.normalized_value().try_into() +} + +pub fn parse_value_from_playback_speed_factor(text: &str) -> Result { + let decimal: f64 = text.parse().map_err(|_| "not a decimal value")?; + let factor: PlaybackSpeedFactor = decimal.try_into().map_err(|_| "not in play rate range")?; + PlayRate::from_playback_speed_factor(factor) + .normalized_value() + .get() + .try_into() +} + +pub fn parse_step_size_from_playback_speed_factor(text: &str) -> Result { + // 0.0x => 0.0 + // 3.75x => 1.0 + let decimal: f64 = text.parse().map_err(|_| "not a decimal value")?; + let span = playback_speed_factor_span(); + if decimal < 0.0 || decimal > span { + return Err("not in playback speed factor increment range"); + } + Ok(UnitValue::new(decimal / span)) +} + +/// Should be 3.75 +pub fn playback_speed_factor_span() -> f64 { + PlaybackSpeedFactor::MAX.get() - PlaybackSpeedFactor::MIN.get() +} + +pub fn parse_value_from_bpm(text: &str) -> Result { + let decimal: f64 = text.parse().map_err(|_| "not a decimal value")?; + let bpm: Bpm = decimal.try_into().map_err(|_| "not in BPM range")?; + Tempo::from_bpm(bpm).normalized_value().try_into() +} + +pub fn parse_step_size_from_bpm(text: &str) -> Result { + // 0.0 bpm => 0.0 + // 959.0 bpm => 1.0 + let decimal: f64 = text.parse().map_err(|_| "not a decimal value")?; + let span = bpm_span(); + if decimal < 0.0 || decimal > span { + return Err("not in BPM increment range"); + } + Ok(UnitValue::new(decimal / span)) +} + +/// How to invoke an action target +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + Serialize_repr, + Deserialize_repr, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum ActionInvocationType { + #[display(fmt = "Trigger")] + Trigger = 0, + #[default] + #[display(fmt = "Absolute 14-bit")] + Absolute14Bit = 1, + #[display(fmt = "Relative")] + Relative = 2, + #[display(fmt = "Absolute 7-bit")] + Absolute7Bit = 3, +} + +impl ActionInvocationType { + pub fn is_absolute(&self) -> bool { + matches!(self, Self::Absolute14Bit | Self::Absolute7Bit) + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum TransportAction { + #[default] + #[serde(rename = "playStop")] + #[display(fmt = "Play/stop")] + PlayStop, + #[serde(rename = "playPause")] + #[display(fmt = "Play/pause")] + PlayPause, + #[serde(rename = "stop")] + #[display(fmt = "Stop")] + Stop, + #[serde(rename = "pause")] + #[display(fmt = "Pause")] + Pause, + #[serde(rename = "record")] + #[display(fmt = "Record/stop")] + RecordStop, + #[serde(rename = "repeat")] + #[display(fmt = "Repeat")] + Repeat, +} + +impl TransportAction { + pub fn control_type_and_character(&self) -> (ControlType, TargetCharacter) { + use TransportAction::*; + match self { + // Retriggerable because we want to be able to retrigger play! + PlayStop | PlayPause => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ), + Stop | Pause | RecordStop | Repeat => { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + } + } +} + +fn determine_target_for_action(action: Action) -> ReaperTarget { + let project = Reaper::get().current_project(); + match action.command_id().expect("should be available").get() { + // Play button | stop button + 1007 | 1016 => ReaperTarget::Transport(TransportTarget { + project, + action: TransportAction::PlayStop, + }), + // Pause button + 1008 => ReaperTarget::Transport(TransportTarget { + project, + action: TransportAction::PlayPause, + }), + // Record button + 1013 => ReaperTarget::Transport(TransportTarget { + project, + action: TransportAction::RecordStop, + }), + // Repeat button + 1068 => ReaperTarget::Transport(TransportTarget { + project, + action: TransportAction::Repeat, + }), + _ => { + let section_id = action.section().map(|s| s.id()).unwrap_or_default(); + ReaperTarget::Action(ActionTarget { + action, + scope: ActionScope::guess_from_section_id(section_id.get()), + invocation_type: ActionInvocationType::Trigger, + project, + track: None, + }) + } + } +} + +fn figure_out_touched_pan_component( + track: Track, + old: reaper_medium::Pan, + new: reaper_medium::Pan, +) -> ReaperTarget { + if old.width() != new.width() { + ReaperTarget::TrackWidth(TrackWidthTarget { + track, + gang_behavior: Default::default(), + }) + } else { + ReaperTarget::TrackPan(TrackPanTarget { + track, + gang_behavior: Default::default(), + }) + } +} + +pub fn pan_unit_value(pan: Pan) -> UnitValue { + UnitValue::new(pan.normalized_value()) +} + +pub fn width_unit_value(width: Width) -> UnitValue { + UnitValue::new(width.normalized_value()) +} + +pub fn track_arm_unit_value(is_armed: bool) -> UnitValue { + convert_bool_to_unit_value(is_armed) +} + +pub fn track_selected_unit_value(is_selected: bool) -> UnitValue { + convert_bool_to_unit_value(is_selected) +} + +pub fn mute_unit_value(is_mute: bool) -> UnitValue { + convert_bool_to_unit_value(is_mute) +} + +pub fn touched_unit_value(is_touched: bool) -> UnitValue { + convert_bool_to_unit_value(is_touched) +} + +pub fn track_solo_unit_value(is_solo: bool) -> UnitValue { + convert_bool_to_unit_value(is_solo) +} + +pub fn automation_mode_unit_value( + desired_automation_mode: AutomationMode, + actual_automation_mode: AutomationMode, +) -> UnitValue { + let is_on = desired_automation_mode == actual_automation_mode; + convert_bool_to_unit_value(is_on) +} + +pub fn global_automation_mode_override_unit_value( + desired_mode_override: Option, + actual_mode_override: Option, +) -> UnitValue { + convert_bool_to_unit_value(actual_mode_override == desired_mode_override) +} + +pub fn tempo_unit_value(tempo: Tempo) -> UnitValue { + UnitValue::new(tempo.normalized_value()) +} + +pub fn playrate_unit_value(playrate: PlayRate) -> UnitValue { + UnitValue::new(playrate.normalized_value().get()) +} + +pub fn fx_enable_unit_value(is_enabled: bool) -> UnitValue { + convert_bool_to_unit_value(is_enabled) +} + +pub fn fx_online_unit_value(is_online: bool) -> UnitValue { + convert_bool_to_unit_value(is_online) +} + +pub fn all_track_fx_enable_unit_value(is_enabled: bool) -> UnitValue { + convert_bool_to_unit_value(is_enabled) +} + +pub fn transport_is_enabled_unit_value(is_enabled: bool) -> UnitValue { + convert_bool_to_unit_value(is_enabled) +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + Serialize_repr, + Deserialize_repr, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum SoloBehavior { + // We could choose ReaperPreference as default but that would be a bit against ReaLearn's + // initial idea of being the number one tool for very project-specific mappings. + #[default] + #[display(fmt = "Solo in place")] + InPlace, + #[display(fmt = "Solo (ignore routing)")] + IgnoreRouting, + #[display(fmt = "Use REAPER preference")] + ReaperPreference, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum TrackGangBehavior { + Off, + SelectionOnly, + GroupingOnly, + SelectionAndGrouping, +} + +impl TrackGangBehavior { + pub fn from_bools( + target_type_def: &TargetTypeDef, + use_selection_ganging: bool, + use_track_grouping: bool, + ) -> Self { + let unfixed = match (use_selection_ganging, use_track_grouping) { + (false, false) => TrackGangBehavior::Off, + (false, true) => TrackGangBehavior::GroupingOnly, + (true, false) => TrackGangBehavior::SelectionOnly, + (true, true) => TrackGangBehavior::SelectionAndGrouping, + }; + unfixed.fixed(target_type_def) + } + + pub fn gang_and_grouping_behavior(&self) -> (GangBehavior, GroupingBehavior) { + match self { + TrackGangBehavior::Off => (GangBehavior::DenyGang, GroupingBehavior::PreventGrouping), + TrackGangBehavior::SelectionOnly => { + (GangBehavior::AllowGang, GroupingBehavior::PreventGrouping) + } + TrackGangBehavior::GroupingOnly => { + (GangBehavior::DenyGang, GroupingBehavior::UseGrouping) + } + TrackGangBehavior::SelectionAndGrouping => { + (GangBehavior::AllowGang, GroupingBehavior::UseGrouping) + } + } + } + + pub fn fixed(&self, target_type_def: &TargetTypeDef) -> Self { + match self { + Self::GroupingOnly if !target_type_def.supports_track_grouping_only_gang_behavior() => { + Self::SelectionAndGrouping + } + _ => *self, + } + } + + pub fn use_selection_ganging(&self) -> bool { + matches!(self, Self::SelectionOnly | Self::SelectionAndGrouping) + } + + pub fn use_track_grouping(&self) -> bool { + matches!(self, Self::GroupingOnly | Self::SelectionAndGrouping) + } +} + +impl Default for TrackGangBehavior { + fn default() -> Self { + Self::Off + } +} + +/// Returns if "Mouse click on volume/pan faders and track buttons changes track selection" +/// is enabled in the REAPER preferences. +fn track_sel_on_mouse_is_enabled() -> bool { + use once_cell::sync::Lazy; + static IS_ENABLED: Lazy = Lazy::new(query_track_sel_on_mouse_is_enabled); + *IS_ENABLED +} + +fn query_track_sel_on_mouse_is_enabled() -> bool { + if let Some(res) = Reaper::get() + .medium_reaper() + .get_config_var("trackselonmouse") + { + if res.size != 4 { + // Shouldn't be. + return false; + } + let ptr = res.value.as_ptr() as *const u32; + let value = unsafe { *ptr }; + // The second flag corresponds to that setting. + (value & 2) != 0 + } else { + false + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + Serialize_repr, + Deserialize_repr, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum TrackExclusivity { + #[default] + #[display(fmt = "No")] + NonExclusive, + #[display(fmt = "Within project")] + ExclusiveWithinProject, + #[display(fmt = "Within folder")] + ExclusiveWithinFolder, + #[display(fmt = "Within project (on only)")] + ExclusiveWithinProjectOnOnly, + #[display(fmt = "Within folder (on only)")] + ExclusiveWithinFolderOnOnly, +} + +impl TrackExclusivity { + pub fn is_on_only(self) -> bool { + use TrackExclusivity::*; + matches!( + self, + ExclusiveWithinProjectOnOnly | ExclusiveWithinFolderOnOnly + ) + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + Serialize_repr, + Deserialize_repr, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum Exclusivity { + #[default] + #[display(fmt = "Non-exclusive")] + NonExclusive, + #[display(fmt = "Exclusive")] + Exclusive, + #[display(fmt = "Exclusive (on only)")] + ExclusiveOnOnly, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum SimpleExclusivity { + #[default] + #[display(fmt = "Non-exclusive")] + NonExclusive, + #[display(fmt = "Exclusive")] + Exclusive, +} + +impl From for SimpleExclusivity { + fn from(e: Exclusivity) -> Self { + use Exclusivity::*; + match e { + NonExclusive => Self::NonExclusive, + Exclusive | ExclusiveOnOnly => Self::Exclusive, + } + } +} + +impl From for Exclusivity { + fn from(e: SimpleExclusivity) -> Self { + use SimpleExclusivity::*; + match e { + NonExclusive => Self::NonExclusive, + Exclusive => Self::Exclusive, + } + } +} + +impl HierarchyEntryProvider for Project { + type Entry = Track; + + fn find_entry_by_index(&self, index: u32) -> Option { + // TODO-medium This could be made faster by separating between heavy-weight and + // light-weight tracks in reaper-rs. + self.track_by_index(index) + } + + fn entry_count(&self) -> u32 { + self.track_count() + } +} + +impl HierarchyEntry for Track { + fn folder_depth_change(&self) -> i32 { + self.folder_depth_change() + } +} + +pub fn change_track_prop( + track: &Track, + exclusivity: TrackExclusivity, + control_value: UnitValue, + mut enable: impl FnMut(&Track), + mut disable: impl FnMut(&Track), +) { + if control_value.is_zero() { + // Case: Switch off + if !exclusivity.is_on_only() { + // Enable property for other tracks + handle_exclusivity( + &track.project(), + exclusivity, + track.index(), + track, + |_, track| enable(track), + ); + } + // Disable property for this track + disable(track); + } else { + // Case: Switch on + // Disable property for other tracks + handle_exclusivity( + &track.project(), + exclusivity, + track.index(), + track, + |_, track| disable(track), + ); + // Enable property for this track + enable(track); + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum RealTimeReaperTarget { + SendMidi(MidiSendTarget), + PlaytimeSlotTransport(crate::domain::RealTimePlaytimeSlotTransportTarget), + PlaytimeColumn(crate::domain::RealTimePlaytimeColumnTarget), + PlaytimeRow(crate::domain::RealTimePlaytimeRowTarget), + PlaytimeMatrix(crate::domain::RealTimePlaytimeMatrixTarget), + FxParameter(RealTimeFxParameterTarget), +} + +impl RealTimeReaperTarget { + /// Some targets such as the FX parameter target are not always controlled from the + /// real-time thread. Only under certain conditions. + pub fn wants_real_time_control(&self, caller: Caller, is_rendering: bool) -> bool { + use RealTimeReaperTarget::*; + match self { + FxParameter(t) => t.wants_real_time_control(caller, is_rendering), + _ => true, + } + } +} + +pub fn get_control_type_and_character_for_track_exclusivity( + exclusivity: TrackExclusivity, +) -> (ControlType, TargetCharacter) { + if exclusivity == TrackExclusivity::NonExclusive { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } else { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } +} + +pub fn with_solo_behavior(behavior: SoloBehavior, f: impl FnOnce()) { + use SoloBehavior::*; + match behavior { + ReaperPreference => f(), + InPlace | IgnoreRouting => { + Reaper::get().with_solo_in_place(behavior == InPlace, f); + } + } +} + +pub fn with_seek_behavior(behavior: SeekBehavior, f: impl FnOnce()) { + use SeekBehavior::*; + match behavior { + ReaperPreference => f(), + Immediate | Smooth => { + Reaper::get().with_smooth_seek(behavior == Smooth, f); + } + } +} + +pub fn new_set_track_ui_functions_are_available() -> bool { + Reaper::get() + .medium_reaper() + .low() + .pointers() + .SetTrackUIPolarity + .is_some() +} + +pub fn with_gang_behavior( + project: Project, + behavior: TrackGangBehavior, + target_type_def: &TargetTypeDef, + f: impl FnOnce(GangBehavior, GroupingBehavior) -> Result<(), ReaperError>, +) -> Result<(), &'static str> { + let (gang_behavior, grouping_behavior) = behavior.gang_and_grouping_behavior(); + if new_set_track_ui_functions_are_available() { + // REAPER >= 6.69+dev1027 makes things much easier. + f(gang_behavior, grouping_behavior)?; + return Ok(()); + } + use TrackGangBehavior::*; + match behavior { + Off => { + if target_type_def.supports_track_grouping_only_gang_behavior { + // CSurf_OnMuteChangeEx, CSurf_OnSoloChangeEx, CSurf_OnRecArmChangeEx respect + // track grouping even when passing DenyGang. So we need to switch it off + // temporarily. + project.with_track_grouping(false, || f(gang_behavior, grouping_behavior))?; + } else { + f(gang_behavior, grouping_behavior)?; + } + } + SelectionOnly => { + project.with_track_grouping(false, || f(gang_behavior, grouping_behavior))?; + } + GroupingOnly => { + if target_type_def.supports_track_grouping_only_gang_behavior { + // CSurf_OnMuteChangeEx, CSurf_OnSoloChangeEx, CSurf_OnRecArmChangeEx respect + // track grouping even when passing DenyGang. Perfect. + f(gang_behavior, grouping_behavior)?; + } else { + return Err("grouping-only is not supported for this target"); + } + } + SelectionAndGrouping => { + f(GangBehavior::AllowGang, GroupingBehavior::UseGrouping)?; + } + }; + Ok(()) +} + +fn is_bypass_param(param: &FxParameter) -> bool { + let bypass_param = param.fx().parameter_by_id(ParamId::Bypass); + Some(param) == bypass_param.as_ref() +} diff --git a/plugin-reaper-realearn/main/src/domain/source.rs b/plugin-reaper-realearn/main/src/domain/source.rs new file mode 100644 index 0000000..8aa48c4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/source.rs @@ -0,0 +1,4 @@ +use crate::domain::AdditionalLuaMidiSourceScriptInput; +use helgoboss_learn::SourceContext; + +pub type RealearnSourceContext<'a> = SourceContext>; diff --git a/plugin-reaper-realearn/main/src/domain/stream_deck_device.rs b/plugin-reaper-realearn/main/src/domain/stream_deck_device.rs new file mode 100644 index 0000000..33b7516 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/stream_deck_device.rs @@ -0,0 +1,99 @@ +use crate::domain::UnitId; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use hidapi::HidApi; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use streamdeck::{pids, StreamDeck}; + +pub struct ProbedStreamDeckDevice { + pub dev: StreamDeckDevice, + pub available: bool, +} + +#[derive(Copy, Clone, Debug)] +pub struct StreamDeckDevice { + pub id: StreamDeckDeviceId, + pub name: &'static str, +} + +impl StreamDeckDevice { + pub const fn new(vid: u16, pid: u16, name: &'static str) -> Self { + let id = StreamDeckDeviceId { vid, pid }; + Self { id, name } + } +} + +pub fn probe_stream_deck_devices() -> anyhow::Result> { + let mut api = HidApi::new()?; + api.refresh_devices()?; + let connected_devs: NonCryptoHashSet<_> = api + .device_list() + .map(|info| StreamDeckDeviceId { + vid: info.vendor_id(), + pid: info.product_id(), + }) + .collect(); + let probed_devs = SUPPORTED_DEVICES + .iter() + .copied() + .map(|dev| ProbedStreamDeckDevice { + dev, + available: connected_devs.contains(&dev.id), + }) + .collect(); + Ok(probed_devs) +} + +const ELGATO_VENDOR_ID: u16 = 0x0fd9; + +const SUPPORTED_DEVICES: &[StreamDeckDevice] = &[ + StreamDeckDevice::new(ELGATO_VENDOR_ID, pids::ORIGINAL, "Original"), + StreamDeckDevice::new(ELGATO_VENDOR_ID, pids::ORIGINAL_V2, "Original v2"), + StreamDeckDevice::new(ELGATO_VENDOR_ID, pids::MINI, "Mini"), + StreamDeckDevice::new(ELGATO_VENDOR_ID, pids::XL, "XL"), + StreamDeckDevice::new(ELGATO_VENDOR_ID, pids::MK2, "MK2"), + StreamDeckDevice::new(ELGATO_VENDOR_ID, pids::REVISED_MINI, "Revised Mini"), +]; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +pub struct StreamDeckDeviceId { + /// Vendor ID. + pub vid: u16, + /// Product ID. + pub pid: u16, + // Serial number (for distinguishing between multiple devices of the same type). + // pub serial_number: Option, +} + +impl Display for StreamDeckDeviceId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}/{}", self.vid, self.pid) + } +} + +impl StreamDeckDeviceId { + pub fn connect(&self) -> Result { + let mut sd = StreamDeck::connect(self.vid, self.pid, None)?; + sd.set_blocking(false)?; + Ok(sd) + } +} + +#[derive(Debug, Default)] +pub struct StreamDeckDeviceManager { + device_usage: NonCryptoHashMap, +} + +impl StreamDeckDeviceManager { + pub fn register_device_usage(&mut self, unit_id: UnitId, device: Option) { + if let Some(d) = device { + self.device_usage.insert(unit_id, d); + } else { + self.device_usage.remove(&unit_id); + } + } + + pub fn devices_in_use(&self) -> NonCryptoHashSet { + self.device_usage.values().copied().collect() + } +} diff --git a/plugin-reaper-realearn/main/src/domain/stream_deck_source.rs b/plugin-reaper-realearn/main/src/domain/stream_deck_source.rs new file mode 100644 index 0000000..3d371e6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/stream_deck_source.rs @@ -0,0 +1,180 @@ +use crate::domain::StreamDeckDeviceId; +use derivative::Derivative; +use helgoboss_learn::{ControlValue, FeedbackValue, RgbColor, UnitValue}; +use helgobox_api::persistence::StreamDeckButtonDesign; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct StreamDeckSource { + pub button_index: u32, + pub button_design: StreamDeckButtonDesign, +} + +impl StreamDeckSource { + pub fn new(button_index: u32, button_design: StreamDeckButtonDesign) -> Self { + Self { + button_index, + button_design, + } + } + + pub fn feedback_address(&self) -> StreamDeckSourceAddress { + StreamDeckSourceAddress { + button_index: self.button_index, + } + } + + /// Checks if the given message is directed to the same address as the one of this source. + /// + /// Used for: + /// + /// - Source takeover (feedback) + pub fn has_same_feedback_address_as_value( + &self, + value: &StreamDeckSourceFeedbackValue, + ) -> bool { + self.feedback_address() == value.feedback_address() + } + + /// Checks if this and the given source share the same address. + /// + /// Used for: + /// + /// - Feedback diffing + pub fn has_same_feedback_address_as_source(&self, other: &Self) -> bool { + self.feedback_address() == other.feedback_address() + } + + pub fn control(&self, msg: StreamDeckMessage) -> Option { + if msg.button_index != self.button_index { + return None; + } + let val = if msg.press { + UnitValue::MAX + } else { + UnitValue::MIN + }; + Some(ControlValue::AbsoluteContinuous(val)) + } + + pub fn feedback( + &self, + feedback_value: &FeedbackValue, + ) -> Option { + let value = match feedback_value { + FeedbackValue::Off => StreamDeckSourceFeedbackValue { + button_index: self.button_index, + payload: StreamDeckSourceFeedbackPayload::Off, + }, + FeedbackValue::Numeric(v) => StreamDeckSourceFeedbackValue { + button_index: self.button_index, + payload: StreamDeckSourceFeedbackPayload::On(StreamDeckSourceFeedbackOnPayload { + button_design: self.button_design.clone(), + background_color: v.style.background_color, + foreground_color: v.style.color, + numeric_value: Some(v.value.to_unit_value()), + text_value: None, + }), + }, + FeedbackValue::Textual(v) => StreamDeckSourceFeedbackValue { + button_index: self.button_index, + payload: StreamDeckSourceFeedbackPayload::On(StreamDeckSourceFeedbackOnPayload { + button_design: self.button_design.clone(), + background_color: v.style.background_color, + foreground_color: v.style.color, + numeric_value: None, + text_value: Some(v.text.to_string()), + }), + }, + FeedbackValue::Complex(_) => { + // TODO-medium At some point we might want to support complex dynamically generated feedback (by glue section) + return None; + } + }; + Some(value) + } +} + +impl Display for StreamDeckSource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Button {}", self.button_index + 1) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct StreamDeckMessage { + pub button_index: u32, + pub press: bool, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct QualifiedStreamDeckMessage { + pub dev_id: StreamDeckDeviceId, + pub msg: StreamDeckMessage, +} + +impl StreamDeckMessage { + pub fn new(button_index: u32, press: bool) -> Self { + Self { + button_index, + press, + } + } +} + +impl Display for StreamDeckMessage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.button_index, self.press) + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct StreamDeckSourceFeedbackValue { + pub button_index: u32, + pub payload: StreamDeckSourceFeedbackPayload, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum StreamDeckSourceFeedbackPayload { + Off, + On(StreamDeckSourceFeedbackOnPayload), +} + +#[derive(Clone, Eq, PartialEq, Debug, Derivative)] +#[derivative(Hash)] +pub struct StreamDeckSourceFeedbackOnPayload { + pub button_design: StreamDeckButtonDesign, + pub background_color: Option, + pub foreground_color: Option, + #[derivative(Hash(hash_with = "hash_opt_unit_value_for_change_detection"))] + pub numeric_value: Option, + pub text_value: Option, +} + +fn hash_opt_unit_value_for_change_detection(value: &Option, state: &mut H) +where + H: Hasher, +{ + let raw = value.map(|v| v.get().to_ne_bytes()); + raw.hash(state); +} + +impl StreamDeckSourceFeedbackValue { + pub fn feedback_address(&self) -> StreamDeckSourceAddress { + StreamDeckSourceAddress { + button_index: self.button_index, + } + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct StreamDeckSourceAddress { + pub button_index: u32, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct StreamDeckScanResult { + pub message: StreamDeckMessage, + pub dev_id: Option, +} diff --git a/plugin-reaper-realearn/main/src/domain/tag.rs b/plugin-reaper-realearn/main/src/domain/tag.rs new file mode 100644 index 0000000..d7fb4b2 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/tag.rs @@ -0,0 +1,46 @@ +use base::{convert_to_identifier, SmallAsciiString}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::str::FromStr; + +/// We reduce the number of possible letters in case we want to use tags in the audio thread in +/// future (and therefore need to avoid allocation). +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Hash, + derive_more::Display, + SerializeDisplay, + DeserializeFromStr, +)] +pub struct Tag(SmallAsciiString); + +impl FromStr for Tag { + type Err = &'static str; + + fn from_str(text: &str) -> Result { + let small_ascii_string = convert_to_identifier(text)?; + Ok(Self(small_ascii_string)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn parse_tags() { + assert_eq!(Tag::from_str("hey").unwrap().to_string(), "hey"); + assert_eq!(Tag::from_str("_hey").unwrap().to_string(), "_hey"); + assert_eq!(Tag::from_str("HeY").unwrap().to_string(), "hey"); + assert_eq!(Tag::from_str("hey_test").unwrap().to_string(), "hey_test"); + assert_eq!( + Tag::from_str("1ähey1ätest").unwrap().to_string(), + "hey1test" + ); + assert!(Tag::from_str("1ä").is_err()); + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/action_target.rs b/plugin-reaper-realearn/main/src/domain/targets/action_target.rs new file mode 100644 index 0000000..bd3fb1b --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/action_target.rs @@ -0,0 +1,319 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + format_bool_as_on_off, get_effective_tracks, ActionInvocationType, AdditionalFeedbackEvent, + CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use camino::Utf8Path; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, Target, UnitValue}; +use helgoboss_midi::{U14, U7}; +use helgobox_api::persistence::ActionScope; +use reaper_high::{Action, ActionCharacter, Project, Reaper, Track}; +use reaper_medium::{ + ActionValueChange, CommandId, Hwnd, MasterTrackBehavior, OpenMediaExplorerMode, +}; +use std::borrow::Cow; +use std::convert::TryFrom; + +#[derive(Debug)] +pub struct UnresolvedActionTarget { + pub action: Action, + pub scope: ActionScope, + pub invocation_type: ActionInvocationType, + pub track_descriptor: Option, +} + +impl UnresolvedReaperTargetDef for UnresolvedActionTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let project = context.context().project_or_current_project(); + let resolved_targets = if let Some(td) = &self.track_descriptor { + get_effective_tracks(context, &td.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::Action(ActionTarget { + action: self.action.clone(), + scope: self.scope, + invocation_type: self.invocation_type, + project, + track: Some(track), + }) + }) + .collect() + } else { + vec![ReaperTarget::Action(ActionTarget { + action: self.action.clone(), + scope: self.scope, + invocation_type: self.invocation_type, + project, + track: None, + })] + }; + Ok(resolved_targets) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + self.track_descriptor.as_ref() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ActionTarget { + pub action: Action, + pub scope: ActionScope, + pub invocation_type: ActionInvocationType, + pub project: Project, + pub track: Option, +} + +impl RealearnTarget for ActionTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + match self.invocation_type { + ActionInvocationType::Trigger => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + ActionInvocationType::Absolute14Bit | ActionInvocationType::Absolute7Bit => { + match self.action.character() { + Ok(ActionCharacter::Toggle) => { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + Ok(ActionCharacter::Trigger) => { + // If the action character is not toggle, it will emit ReaLearn-generated "fake" values, + // because natively, trigger-like REAPER actions don't have the concept of a current value. + // If that fake value is incorrect and the control type is not retriggerable, ReaLearn won't + // fire. Bad. That's why we need to make it retriggerable. + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Continuous, + ) + } + Err(_) => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Continuous, + ), + } + } + ActionInvocationType::Relative => (ControlType::Relative, TargetCharacter::Discrete), + } + } + + fn open(&self, _: ControlContext) { + // Just open action window + Reaper::get() + .main_section() + .action_by_command_id(CommandId::new(40605)) + .invoke_as_trigger(Some(self.project), None) + .expect("built-in action should exist"); + } + + fn format_value(&self, _: UnitValue, _: ControlContext) -> String { + "".to_owned() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if let Some(track) = &self.track { + if !track.is_selected() + || self + .project + .selected_track_count(MasterTrackBehavior::IncludeMasterTrack) + > 1 + { + track.select_exclusively(); + } + } + let response = match value { + ControlValue::AbsoluteContinuous(v) => match self.invocation_type { + ActionInvocationType::Trigger => { + if v.is_zero() { + HitResponse::ignored() + } else { + self.invoke_absolute_with_unit_value(v, false)?; + HitResponse::processed_with_effect() + } + } + ActionInvocationType::Absolute14Bit => { + self.invoke_absolute_with_unit_value(v, false)?; + HitResponse::processed_with_effect() + } + ActionInvocationType::Absolute7Bit => { + self.invoke_absolute_with_unit_value(v, true)?; + HitResponse::processed_with_effect() + } + ActionInvocationType::Relative => { + return Err("relative invocation type can't take absolute values"); + } + }, + ControlValue::AbsoluteDiscrete(f) => match self.invocation_type { + ActionInvocationType::Trigger => { + if f.is_zero() { + HitResponse::ignored() + } else { + self.invoke_absolute_with_fraction(f, false)?; + HitResponse::processed_with_effect() + } + } + ActionInvocationType::Absolute14Bit => { + self.invoke_absolute_with_fraction(f, false)?; + HitResponse::processed_with_effect() + } + ActionInvocationType::Absolute7Bit => { + self.invoke_absolute_with_fraction(f, true)?; + HitResponse::processed_with_effect() + } + ActionInvocationType::Relative => { + return Err("relative invocation type can't take absolute values"); + } + }, + ControlValue::RelativeDiscrete(i) => { + if let ActionInvocationType::Relative = self.invocation_type { + self.action.invoke_relative( + i.get(), + Some(self.project), + self.get_context_window(), + )?; + HitResponse::processed_with_effect() + } else { + return Err("relative values need relative invocation type"); + } + } + ControlValue::RelativeContinuous(i) => { + if let ActionInvocationType::Relative = self.invocation_type { + let i = i.to_discrete_increment(); + self.action.invoke_relative( + i.get(), + Some(self.project), + self.get_context_window(), + )?; + HitResponse::processed_with_effect() + } else { + return Err("relative values need relative invocation type"); + } + } + }; + Ok(response) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.action.is_available() + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + // We can't provide a value from the event itself because the action hooks don't + // pass values. + CompoundChangeEvent::Additional(AdditionalFeedbackEvent::ActionInvoked(e)) + if Ok(e.command_id) == self.action.command_id() => + { + (true, None) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(format_bool_as_on_off(self.action.is_on().ok()??).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::Action) + } +} + +impl<'a> Target<'a> for ActionTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = if let Some(state) = self.action.is_on().ok()? { + // Toggle action: Return toggle state as 0 or 1. + convert_bool_to_unit_value(state) + } else if self.invocation_type.is_absolute() { + // Absolute non-toggle action. Try to return current absolute "fake" value if this is a + // MIDI CC/mousewheel action. + if let Some(value) = self.action.normalized_value() { + UnitValue::new(value) + } else { + UnitValue::MIN + } + } else { + // Relative or trigger. Not returning any "fake" value here because this will let the + // glue section make wrong assumptions, especially for "Trigger" invocation mode. + return None; + }; + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +impl ActionTarget { + fn invoke_absolute_with_fraction( + &self, + f: Fraction, + enforce_7_bit: bool, + ) -> Result<(), &'static str> { + let value_change = if enforce_7_bit { + let v = U7::try_from(f.actual()).map_err(|_| "couldn't convert to U7")?; + ActionValueChange::AbsoluteLowRes(v) + } else { + let v = U14::try_from(f.actual()).map_err(|_| "couldn't convert to U14")?; + ActionValueChange::AbsoluteHighRes(v) + }; + self.action.invoke_directly( + value_change, + self.get_context_window(), + self.project.context(), + )?; + Ok(()) + } + + fn invoke_absolute_with_unit_value( + &self, + v: UnitValue, + enforce_7_bit: bool, + ) -> Result<(), &'static str> { + self.action.invoke_absolute( + v.get(), + Some(self.project), + enforce_7_bit, + self.get_context_window(), + )?; + Ok(()) + } + + fn get_context_window(&self) -> Option { + match self.scope { + ActionScope::Main => None, + ActionScope::ActiveMidiEditor | ActionScope::ActiveMidiEventListEditor => { + Reaper::get().medium_reaper().midi_editor_get_active() + } + ActionScope::MediaExplorer => Reaper::get() + .medium_reaper() + .open_media_explorer(Utf8Path::new(""), OpenMediaExplorerMode::Select), + } + } +} + +pub const ACTION_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Project, + name: "Invoke REAPER action", + short_name: "Action", + hint: "Limited feedback only", + supports_track: true, + if_so_supports_track_must_be_selected: false, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/all_track_fx_enable_target.rs b/plugin-reaper-realearn/main/src/domain/targets/all_track_fx_enable_target.rs new file mode 100644 index 0000000..cb87a0d --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/all_track_fx_enable_target.rs @@ -0,0 +1,135 @@ +use crate::domain::{ + all_track_fx_enable_unit_value, change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, CompartmentKind, + ControlContext, ExtendedProcessorContext, FeedbackResolution, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, TrackExclusivity, UnresolvedReaperTargetDef, + AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedAllTrackFxEnableTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub poll_for_feedback: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedAllTrackFxEnableTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::AllTrackFxEnable(AllTrackFxEnableTarget { + track, + exclusivity: self.exclusivity, + poll_for_feedback: self.poll_for_feedback, + }) + }) + .collect(), + ) + } + + fn feedback_resolution(&self) -> Option { + if self.poll_for_feedback { + Some(FeedbackResolution::High) + } else { + None + } + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AllTrackFxEnableTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub poll_for_feedback: bool, +} + +impl RealearnTarget for AllTrackFxEnableTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + change_track_prop( + &self.track, + self.exclusivity, + value.to_unit_value()?, + |t| t.enable_fx(), + |t| t.disable_fx(), + ); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn supports_automatic_feedback(&self) -> bool { + self.poll_for_feedback + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::AllTrackFxEnable) + } +} + +impl<'a> Target<'a> for AllTrackFxEnableTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = all_track_fx_enable_unit_value(self.track.fx_is_enabled()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ALL_TRACK_FX_ENABLE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Enable/disable all FX", + short_name: "Enable/disable all track FX", + hint: AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + supports_poll_for_feedback: true, + supports_track: true, + supports_track_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/any_on_target.rs b/plugin-reaper-realearn/main/src/domain/targets/any_on_target.rs new file mode 100644 index 0000000..f636de5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/any_on_target.rs @@ -0,0 +1,188 @@ +use crate::domain::{ + format_value_as_on_off, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use derive_more::Display; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use reaper_high::{ChangeEvent, GroupingBehavior, Project}; +use reaper_medium::GangBehavior; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use strum::EnumIter; + +#[derive(Debug)] +pub struct UnresolvedAnyOnTarget { + pub parameter: AnyOnParameter, +} + +impl UnresolvedReaperTargetDef for UnresolvedAnyOnTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::AnyOn(AnyOnTarget { + project: context.context().project_or_current_project(), + parameter: self.parameter, + })]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AnyOnTarget { + pub project: Project, + pub parameter: AnyOnParameter, +} + +impl RealearnTarget for AnyOnTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // Retriggerable because the logic of this target is unusual: Pressing a button (= receiving + // on = 100%) is supposed to switch everything to *off*. So the desired target value doesn't + // correspond to the incoming value. + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + for t in self.project.tracks() { + use AnyOnParameter::*; + match self.parameter { + TrackSolo => t.unsolo(GangBehavior::DenyGang, GroupingBehavior::PreventGrouping), + TrackMute => t.unmute(GangBehavior::DenyGang, GroupingBehavior::PreventGrouping), + TrackArm => t.disarm( + false, + GangBehavior::DenyGang, + GroupingBehavior::PreventGrouping, + ), + TrackSelection => t.unselect(), + } + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + use AnyOnParameter::*; + use CompoundChangeEvent::*; + match evt { + Reaper(ChangeEvent::TrackSoloChanged(e)) + if self.parameter == TrackSolo && e.track.project() == self.project => + { + (true, None) + } + Reaper(ChangeEvent::TrackMuteChanged(e)) + if self.parameter == TrackMute && e.track.project() == self.project => + { + (true, None) + } + Reaper(ChangeEvent::TrackArmChanged(e)) + if self.parameter == TrackArm && e.track.project() == self.project => + { + (true, None) + } + Reaper(ChangeEvent::TrackSelectedChanged(e)) + if self.parameter == TrackSelection && e.track.project() == self.project => + { + (true, None) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::AnyOn) + } +} + +impl<'a> Target<'a> for AnyOnTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + use AnyOnParameter::*; + let on = match self.parameter { + TrackSolo => self.project.any_solo(), + TrackMute => self.project.tracks().any(|t| t.is_muted()), + TrackArm => self.project.tracks().any(|t| t.is_armed(false)), + TrackSelection => self.project.tracks().any(|t| t.is_selected()), + }; + Some(AbsoluteValue::from_bool(on)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +#[allow(clippy::enum_variant_names)] +pub enum AnyOnParameter { + #[serde(rename = "track-solo")] + #[display(fmt = "Track solo")] + TrackSolo, + #[serde(rename = "track-mute")] + #[display(fmt = "Track mute")] + TrackMute, + #[serde(rename = "track-arm")] + #[display(fmt = "Track arm")] + TrackArm, + #[serde(rename = "track-selection")] + #[display(fmt = "Track selection")] + TrackSelection, +} + +impl Default for AnyOnParameter { + fn default() -> Self { + Self::TrackSolo + } +} + +pub const ANY_ON_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Project, + name: "Any on (solo/mute/...)", + short_name: "Any on", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/automation_mode_override_target.rs b/plugin-reaper-realearn/main/src/domain/targets/automation_mode_override_target.rs new file mode 100644 index 0000000..d2deac5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/automation_mode_override_target.rs @@ -0,0 +1,112 @@ +use crate::domain::{ + format_value_as_on_off, global_automation_mode_override_unit_value, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Reaper}; +use reaper_medium::GlobalAutomationModeOverride; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedAutomationModeOverrideTarget { + pub mode_override: Option, +} + +impl UnresolvedReaperTargetDef for UnresolvedAutomationModeOverrideTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::AutomationModeOverride( + AutomationModeOverrideTarget { + mode_override: self.mode_override, + }, + )]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AutomationModeOverrideTarget { + pub mode_override: Option, +} + +impl RealearnTarget for AutomationModeOverrideTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // Retriggerable because of #277 + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + Reaper::get().set_global_automation_override(None); + } else { + Reaper::get().set_global_automation_override(self.mode_override); + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::GlobalAutomationOverrideChanged(e)) => ( + true, + Some(AbsoluteValue::Continuous( + global_automation_mode_override_unit_value(self.mode_override, e.new_value), + )), + ), + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::AutomationModeOverride) + } +} + +impl<'a> Target<'a> for AutomationModeOverrideTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let value = global_automation_mode_override_unit_value( + self.mode_override, + Reaper::get().global_automation_override(), + ); + Some(AbsoluteValue::Continuous(value)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const AUTOMATION_MODE_OVERRIDE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Global, + name: "Set automation mode override", + short_name: "Automation override", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/browse_fxs_target.rs b/plugin-reaper-realearn/main/src/domain/targets/browse_fxs_target.rs new file mode 100644 index 0000000..72c61b5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/browse_fxs_target.rs @@ -0,0 +1,263 @@ +use crate::domain::{ + convert_count_to_step_size, convert_unit_value_to_fx_index, get_fx_chains, get_fx_name, + shown_fx_unit_value, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, FxDisplayType, HitResponse, MappingControlContext, RealearnTarget, + ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, Fraction, NumericValue, Target, UnitValue, +}; +use reaper_high::{ChangeEvent, Fx, FxChain, Project, Track}; +use reaper_medium::FxChainVisibility; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedBrowseFxsTarget { + pub track_descriptor: TrackDescriptor, + pub is_input_fx: bool, + pub display_type: FxDisplayType, +} + +impl UnresolvedReaperTargetDef for UnresolvedBrowseFxsTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let fx_chains = get_fx_chains( + context, + &self.track_descriptor.track, + self.is_input_fx, + compartment, + )?; + let targets = fx_chains + .into_iter() + .map(|fx_chain| { + ReaperTarget::BrowseFxs(BrowseFxsTarget { + fx_chain, + display_type: self.display_type, + }) + }) + .collect(); + Ok(targets) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BrowseFxsTarget { + pub fx_chain: FxChain, + pub display_type: FxDisplayType, +} + +impl RealearnTarget for BrowseFxsTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // `+ 1` because "" is also a possible value. + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size(self.fx_chain.fx_count() + 1), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + _: ControlContext, + ) -> Result { + let value = convert_unit_value_to_fx_index(&self.fx_chain, input) + .map(|i| i + 1) + .unwrap_or(0); + Ok(value) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + match convert_unit_value_to_fx_index(&self.fx_chain, value) { + None => "".to_string(), + Some(i) => (i + 1).to_string(), + } + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let fx_index = match value.to_absolute_value()? { + AbsoluteValue::Continuous(v) => convert_unit_value_to_fx_index(&self.fx_chain, v), + AbsoluteValue::Discrete(f) => { + if f.actual() == 0 { + None + } else { + Some(f.actual() - 1) + } + } + }; + use FxDisplayType::*; + match fx_index { + None => match self.display_type { + FloatingWindow => { + self.fx_chain.hide_all_floating_windows(); + } + Chain => { + self.fx_chain.hide()?; + } + }, + Some(fx_index) => match self.display_type { + FloatingWindow => { + for (i, fx) in self.fx_chain.index_based_fxs().enumerate() { + if i == fx_index as usize { + fx.show_in_floating_window()?; + } else { + fx.hide_floating_window()?; + } + } + } + Chain => { + let fx = self + .fx_chain + .index_based_fx_by_index(fx_index) + .ok_or("FX not available")?; + fx.show_in_chain()?; + } + }, + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.fx_chain.is_available() + } + + fn project(&self) -> Option { + self.fx_chain.project() + } + + fn track(&self) -> Option<&Track> { + self.fx_chain.track() + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + use CompoundChangeEvent::*; + match evt { + Reaper(ChangeEvent::FxOpened(e)) if e.fx.chain() == &self.fx_chain => (true, None), + Reaper(ChangeEvent::FxClosed(e)) if e.fx.chain() == &self.fx_chain => (true, None), + _ => (false, None), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + _: ControlContext, + ) -> Result { + let index = if value == 0 { None } else { Some(value - 1) }; + Ok(shown_fx_unit_value(&self.fx_chain, index)) + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(get_fx_name(&self.current_fx()?).into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + let index = self.current_fx_index()?; + Some(NumericValue::Discrete(index as i32 + 1)) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::BrowseFxs) + } +} + +impl BrowseFxsTarget { + fn current_fx(&self) -> Option { + use FxDisplayType::*; + match self.display_type { + FloatingWindow => self + .fx_chain + .index_based_fxs() + .find(|fx| fx.floating_window().is_some()), + Chain => { + use FxChainVisibility::*; + match self.fx_chain.visibility() { + Hidden | Visible(None) | Unknown(_) => None, + Visible(Some(i)) => Some(self.fx_chain.fx_by_index_untracked(i)), + } + } + } + } + + fn current_fx_index(&self) -> Option { + use FxDisplayType::*; + match self.display_type { + FloatingWindow => self + .fx_chain + .index_based_fxs() + .position(|fx| fx.floating_window().is_some()) + .map(|i| i as u32), + Chain => { + use FxChainVisibility::*; + match self.fx_chain.visibility() { + Hidden | Visible(None) | Unknown(_) => None, + Visible(Some(i)) => Some(i), + } + } + } + } +} + +impl<'a> Target<'a> for BrowseFxsTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let fx_count = self.fx_chain.fx_count(); + // Because we count "" as a possible value, this is equal. + let max_value = fx_count; + let fx_index = self.current_fx_index(); + let actual_value = fx_index.map(|i| i + 1).unwrap_or(0); + Some(AbsoluteValue::Discrete(Fraction::new( + actual_value, + max_value, + ))) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const BROWSE_FXS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::FxChain, + name: "Browse FXs", + short_name: "Browse FXs", + supports_track: true, + supports_fx_chain: true, + supports_fx_display_type: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/browse_group_mappings_target.rs b/plugin-reaper-realearn/main/src/domain/targets/browse_group_mappings_target.rs new file mode 100644 index 0000000..255a582 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/browse_group_mappings_target.rs @@ -0,0 +1,272 @@ +use crate::domain::{ + convert_count_to_step_size, convert_discrete_to_unit_value, convert_unit_to_discrete_value, + CompartmentKind, CompoundChangeEvent, ControlContext, ControlLogContext, + ExtendedProcessorContext, GroupId, HitInstruction, HitInstructionContext, + HitInstructionResponse, HitResponse, MappingControlContext, MappingId, QualifiedMappingId, + RealearnTarget, ReaperTarget, ReaperTargetType, SimpleExclusivity, TargetCharacter, + TargetSection, TargetTypeDef, UnitEvent, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, Fraction, NumericValue, Target, UnitValue, +}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedBrowseGroupTarget { + pub compartment: CompartmentKind, + pub group_id: GroupId, + pub exclusivity: SimpleExclusivity, +} + +impl UnresolvedReaperTargetDef for UnresolvedBrowseGroupTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::BrowseGroupMappings( + BrowseGroupMappingsTarget { + compartment: self.compartment, + group_id: self.group_id, + exclusivity: self.exclusivity, + }, + )]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BrowseGroupMappingsTarget { + /// This must always correspond to the compartment of the containing mapping, otherwise it will + /// not have any effect when controlling (only when querying the values). + pub compartment: CompartmentKind, + pub group_id: GroupId, + pub exclusivity: SimpleExclusivity, +} + +impl BrowseGroupMappingsTarget { + fn count(&self, context: ControlContext) -> u32 { + context + .unit + .borrow() + .get_on_mappings_within_group(self.compartment, self.group_id) + .count() as _ + } +} + +impl RealearnTarget for BrowseGroupMappingsTarget { + fn control_type_and_character( + &self, + context: ControlContext, + ) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: { + let count = self.count(context); + convert_count_to_step_size(count) + }, + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let value = value.to_absolute_value()?; + let mut instance_state = context.control_context.unit.borrow_mut(); + let desired_mapping_id = { + let mapping_ids: Vec<_> = instance_state + .get_on_mappings_within_group(self.compartment, self.group_id) + .collect(); + let count = mapping_ids.len(); + let desired_index = match value { + AbsoluteValue::Continuous(v) => convert_unit_to_discrete_value(v, count as _), + AbsoluteValue::Discrete(f) => f.actual(), + }; + *mapping_ids + .get(desired_index as usize) + .ok_or("mapping index out of bounds")? + }; + instance_state.set_active_mapping_within_group( + self.compartment, + self.group_id, + desired_mapping_id, + ); + struct BrowseGroupMappingsInstruction { + group_id: GroupId, + exclusivity: SimpleExclusivity, + desired_mapping_id: MappingId, + } + impl HitInstruction for BrowseGroupMappingsInstruction { + fn execute(self: Box, context: HitInstructionContext) -> HitInstructionResponse { + let mut control_results = vec![]; + for m in context.mappings.values_mut() { + let glue = m.mode().settings(); + let v = if m.id() == self.desired_mapping_id { + if glue.reverse { + glue.target_value_interval.min_val() + } else { + glue.target_value_interval.max_val() + } + } else if self.exclusivity == SimpleExclusivity::Exclusive + && m.group_id() == self.group_id + { + if glue.reverse { + glue.target_value_interval.max_val() + } else { + glue.target_value_interval.min_val() + } + } else { + continue; + }; + context + .domain_event_handler + .notify_mapping_matched(m.compartment(), m.id()); + let res = m.control_from_target_directly( + context.control_context, + context.processor_context, + ControlValue::AbsoluteContinuous(v), + context.basic_settings.target_control_logger( + context.processor_context.control_context.unit, + ControlLogContext::GroupNavigation, + m.qualified_id(), + ), + ); + control_results.push(res); + } + HitInstructionResponse::CausedEffect(control_results) + } + } + let instruction = BrowseGroupMappingsInstruction { + group_id: self.group_id, + exclusivity: self.exclusivity, + desired_mapping_id, + }; + Ok(HitResponse::hit_instruction(Box::new(instruction))) + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + context: ControlContext, + ) -> Result { + let count = self.count(context); + Ok(convert_unit_to_discrete_value(input, count)) + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + context: ControlContext, + ) -> Result { + let count = self.count(context); + Ok(convert_discrete_to_unit_value(value, count as _)) + } + + fn is_available(&self, context: ControlContext) -> bool { + context + .unit + .borrow() + .get_on_mappings_within_group(self.compartment, self.group_id) + .count() + > 0 + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Unit(UnitEvent::ActiveMappingWithinGroup { + compartment, + group_id, + .. + }) if *compartment == self.compartment && *group_id == self.group_id => (true, None), + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + let (mapping_id, _) = self.current_mapping_with_position(context)?; + let instance_state = context.unit.borrow(); + let info = instance_state + .get_mapping_info(QualifiedMappingId::new(self.compartment, mapping_id))?; + Some(info.name.clone().into()) + } + + fn numeric_value(&self, context: ControlContext) -> Option { + let (_, fraction) = self.current_mapping_with_position(context)?; + Some(NumericValue::Discrete(fraction.actual() as i32 + 1)) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::BrowseGroup) + } +} + +impl BrowseGroupMappingsTarget { + fn current_mapping_with_position( + &self, + context: ControlContext, + ) -> Option<(MappingId, Fraction)> { + let instance_state = context.unit.borrow(); + if let Some(mapping_id) = + instance_state.get_active_mapping_within_group(self.compartment, self.group_id) + { + let mapping_ids: Vec<_> = instance_state + .get_on_mappings_within_group(self.compartment, self.group_id) + .collect(); + if !mapping_ids.is_empty() { + let max_value = mapping_ids.len() - 1; + if let Some(index) = mapping_ids.iter().position(|id| *id == mapping_id) { + return Some((mapping_id, Fraction::new(index as _, max_value as _))); + } + } + } + None + } +} + +impl<'a> Target<'a> for BrowseGroupMappingsTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext) -> Option { + let fraction = self + .current_mapping_with_position(context) + .map(|(_, f)| f) + .unwrap_or(Fraction::MIN); + Some(AbsoluteValue::Discrete(fraction)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const BROWSE_GROUP_MAPPINGS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Browse group mappings", + short_name: "Browse group mappings", + supports_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/browse_pot_filter_items_target.rs b/plugin-reaper-realearn/main/src/domain/targets/browse_pot_filter_items_target.rs new file mode 100644 index 0000000..e28d4f6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/browse_pot_filter_items_target.rs @@ -0,0 +1,286 @@ +use crate::domain::{ + convert_count_to_step_size, convert_discrete_to_unit_value_with_none, + convert_unit_to_discrete_value_with_none, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, InstanceStateChanged, MappingControlContext, + PotStateChangedEvent, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::blocking_lock_arc; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, Fraction, NumericValue, PropValue, Target, UnitValue, +}; +use helgobox_api::persistence::PotFilterKind; +use pot::{Debounce, FilterItemId}; +use pot::{FilterItem, RuntimePotUnit}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedBrowsePotFilterItemsTarget { + pub settings: PotFilterItemsTargetSettings, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct PotFilterItemsTargetSettings { + pub kind: PotFilterKind, +} + +impl UnresolvedReaperTargetDef for UnresolvedBrowsePotFilterItemsTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::BrowsePotFilterItems( + BrowsePotFilterItemsTarget { + settings: self.settings.clone(), + }, + )]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BrowsePotFilterItemsTarget { + pub settings: PotFilterItemsTargetSettings, +} + +impl RealearnTarget for BrowsePotFilterItemsTarget { + fn control_type_and_character( + &self, + context: ControlContext, + ) -> (ControlType, TargetCharacter) { + // `+ 1` because "" is also a possible value. + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = match instance_state.pot_unit() { + Ok(u) => u, + Err(_) => return (ControlType::AbsoluteContinuous, TargetCharacter::Continuous), + }; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 1"); + let count = self.item_count(&pot_unit) + 1; + let atomic_step_size = convert_count_to_step_size(count); + ( + ControlType::AbsoluteDiscrete { + atomic_step_size, + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + value: UnitValue, + context: ControlContext, + ) -> Result { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 2"); + let value = self + .convert_unit_value_to_item_index(&pot_unit, value) + .map(|i| i + 1) + .unwrap_or(0); + Ok(value) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let mut instance_state = context.control_context.instance().borrow_mut(); + let shared_pot_unit = instance_state.pot_unit()?; + let mut pot_unit = blocking_lock_arc( + &shared_pot_unit, + "PotUnit from BrowsePotFilterItemsTarget hit", + ); + let item_index = self.convert_unit_value_to_item_index(&pot_unit, value.to_unit_value()?); + let item_id = match item_index { + None => None, + Some(i) => { + let id = pot_unit + .find_filter_item_id_at_index(self.settings.kind, i) + .ok_or("no filter item found for that index")?; + Some(id) + } + }; + pot_unit.set_filter( + self.settings.kind, + item_id, + shared_pot_unit.clone(), + Debounce::Yes, + ); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, context: ControlContext) -> bool { + let mut instance_state = context.instance().borrow_mut(); + let Ok(shared_pot_unit) = instance_state.pot_unit() else { + return false; + }; + let pot_unit = blocking_lock_arc( + &shared_pot_unit, + "PotUnit from BrowsePotFilterItemsTarget is_available", + ); + pot_unit.supports_filter_kind(self.settings.kind) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + context: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Instance(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::FilterItemChanged { kind, filter: id }, + )) if *kind == self.settings.kind => { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = match instance_state.pot_unit() { + Ok(u) => u, + Err(_) => return (false, None), + }; + let pot_unit = + blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 3"); + let value = self.convert_item_id_to_absolute_value(&pot_unit, *id); + (true, Some(value)) + } + CompoundChangeEvent::Instance(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::IndexesRebuilt, + )) => (true, None), + _ => (false, None), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + context: ControlContext, + ) -> Result { + let index = if value == 0 { None } else { Some(value - 1) }; + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 4"); + let uv = convert_discrete_to_unit_value_with_none(index, self.item_count(&pot_unit)); + Ok(uv) + } + + fn text_value(&self, context: ControlContext) -> Option> { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit().ok()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 5"); + let item_id = match self.current_item_id(&pot_unit) { + None => return Some("Any".into()), + Some(id) => id, + }; + let item = match self.find_item_by_id(&pot_unit, item_id) { + None => return Some("".into()), + Some(p) => p, + }; + Some(item.effective_leaf_name().to_string().into()) + } + + fn numeric_value(&self, context: ControlContext) -> Option { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit().ok()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 6"); + let item_id = self.current_item_id(&pot_unit)?; + let item_index = self.find_index_of_item(&pot_unit, item_id)?; + Some(NumericValue::Discrete(item_index as i32 + 1)) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::BrowsePotFilterItems) + } + + fn prop_value(&self, key: &str, context: ControlContext) -> Option { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit().ok()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 7"); + let item_id = self.current_item_id(&pot_unit)?; + let item = self.find_item_by_id(&pot_unit, item_id)?; + match key { + "item.parent.name" => Some(PropValue::Text(item.parent_name?.into())), + "item.name" => Some(PropValue::Text(item.name?.into())), + _ => None, + } + } +} + +impl<'a> Target<'a> for BrowsePotFilterItemsTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit().ok()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 8"); + let item_id = self.current_item_id(&pot_unit); + Some(self.convert_item_id_to_absolute_value(&pot_unit, item_id)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +impl BrowsePotFilterItemsTarget { + fn convert_item_id_to_absolute_value( + &self, + pot_unit: &RuntimePotUnit, + item_id: Option, + ) -> AbsoluteValue { + let item_index = item_id.and_then(|id| self.find_index_of_item(pot_unit, id)); + let actual = match item_index { + None => 0, + Some(i) => i + 1, + }; + let max = self.item_count(pot_unit); + AbsoluteValue::Discrete(Fraction::new(actual, max)) + } + + fn item_count(&self, pot_unit: &RuntimePotUnit) -> u32 { + pot_unit.count_filter_items(self.settings.kind) + } + + fn convert_unit_value_to_item_index( + &self, + pot_unit: &RuntimePotUnit, + value: UnitValue, + ) -> Option { + convert_unit_to_discrete_value_with_none(value, self.item_count(pot_unit)) + } + + fn current_item_id(&self, pot_unit: &RuntimePotUnit) -> Option { + pot_unit.get_filter(self.settings.kind) + } + + fn find_item_by_id(&self, pot_unit: &RuntimePotUnit, id: FilterItemId) -> Option { + pot_unit + .find_filter_item_by_id(self.settings.kind, id) + .cloned() + } + + fn find_index_of_item(&self, pot_unit: &RuntimePotUnit, id: FilterItemId) -> Option { + pot_unit.find_index_of_filter_item(self.settings.kind, id) + } +} + +pub const BROWSE_POT_FILTER_ITEMS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Pot, + name: "Browse filter items", + short_name: "Browse Pot filter items", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/browse_pot_presets_target.rs b/plugin-reaper-realearn/main/src/domain/targets/browse_pot_presets_target.rs new file mode 100644 index 0000000..394db59 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/browse_pot_presets_target.rs @@ -0,0 +1,244 @@ +use crate::domain::{ + convert_count_to_step_size, convert_discrete_to_unit_value_with_none, + convert_unit_to_discrete_value_with_none, get_preset_property, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + InstanceStateChanged, MappingControlContext, PotStateChangedEvent, RealearnTarget, + ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::{blocking_lock, blocking_lock_arc}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, Fraction, NumericValue, PropValue, Target, UnitValue, +}; +use pot::{PotPreset, PresetId, RuntimePotUnit}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedBrowsePotPresetsTarget {} + +impl UnresolvedReaperTargetDef for UnresolvedBrowsePotPresetsTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::BrowsePotPresets( + BrowsePotPresetsTarget {}, + )]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BrowsePotPresetsTarget {} + +impl RealearnTarget for BrowsePotPresetsTarget { + fn control_type_and_character( + &self, + context: ControlContext, + ) -> (ControlType, TargetCharacter) { + let mut instance_state = context.instance().borrow_mut(); + // `+ 1` because "" is also a possible value. + let pot_unit = match instance_state.pot_unit() { + Ok(u) => u, + Err(_) => return (ControlType::AbsoluteContinuous, TargetCharacter::Continuous), + }; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotFilterItemsTarget 9"); + let count = self.preset_count(&pot_unit) + 1; + let atomic_step_size = convert_count_to_step_size(count); + ( + ControlType::AbsoluteDiscrete { + atomic_step_size, + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + value: UnitValue, + context: ControlContext, + ) -> Result { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotPresetsTarget 1"); + let value = self + .convert_unit_value_to_preset_index(&pot_unit, value) + .map(|i| i + 1) + .unwrap_or(0); + Ok(value) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let mut instance_state = context.control_context.instance().borrow_mut(); + let shared_pot_unit = instance_state.pot_unit()?; + let mut pot_unit = + blocking_lock(&*shared_pot_unit, "PotUnit from BrowsePotPresetsTarget 2"); + let preset_index = + self.convert_unit_value_to_preset_index(&pot_unit, value.to_unit_value()?); + let preset_id = match preset_index { + None => None, + Some(i) => { + let id = pot_unit + .find_preset_id_at_index(i) + .ok_or("no preset found for that index")?; + Some(id) + } + }; + pot_unit.set_preset_id(preset_id); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + context: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Instance(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::PresetChanged { id }, + )) => { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = match instance_state.pot_unit() { + Ok(u) => u, + Err(_) => return (false, None), + }; + let pot_unit = + blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotPresetsTarget 3"); + let value = self.convert_preset_id_to_absolute_value(&pot_unit, *id); + (true, Some(value)) + } + CompoundChangeEvent::Instance(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::IndexesRebuilt, + )) => (true, None), + _ => (false, None), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + context: ControlContext, + ) -> Result { + let index = if value == 0 { None } else { Some(value - 1) }; + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotPresetsTarget 4"); + let uv = convert_discrete_to_unit_value_with_none(index, self.preset_count(&pot_unit)); + Ok(uv) + } + + fn text_value(&self, context: ControlContext) -> Option> { + if let PropValue::Text(text) = self.prop_value("preset.name", context)? { + Some(text) + } else { + None + } + } + + fn numeric_value(&self, context: ControlContext) -> Option { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit().ok()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotPresetsTarget 5"); + let preset_index = pot_unit.find_index_of_preset(pot_unit.preset_id()?)?; + Some(NumericValue::Discrete(preset_index as i32 + 1)) + } + + fn prop_value(&self, key: &str, context: ControlContext) -> Option { + self.with_selected_preset(context, |p| get_preset_property(p?, key)) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::BrowsePotPresets) + } +} + +impl<'a> Target<'a> for BrowsePotPresetsTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit().ok()?; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotPresetsTarget 6"); + let preset_id = pot_unit.preset_id(); + Some(self.convert_preset_id_to_absolute_value(&pot_unit, preset_id)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +impl BrowsePotPresetsTarget { + fn convert_preset_id_to_absolute_value( + &self, + pot_unit: &RuntimePotUnit, + preset_id: Option, + ) -> AbsoluteValue { + let preset_index = preset_id.and_then(|id| pot_unit.find_index_of_preset(id)); + let actual = match preset_index { + None => 0, + Some(i) => i + 1, + }; + let max = self.preset_count(pot_unit); + AbsoluteValue::Discrete(Fraction::new(actual, max)) + } + + fn preset_count(&self, pot_unit: &RuntimePotUnit) -> u32 { + pot_unit.preset_count() + } + + fn convert_unit_value_to_preset_index( + &self, + pot_unit: &RuntimePotUnit, + value: UnitValue, + ) -> Option { + convert_unit_to_discrete_value_with_none(value, self.preset_count(pot_unit)) + } + + fn with_selected_preset( + &self, + context: ControlContext, + f: impl FnOnce(Option<&PotPreset>) -> R, + ) -> R { + let mut instance_state = context.instance().borrow_mut(); + if let Ok(pot_unit) = instance_state.pot_unit() { + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from BrowsePotPresetsTarget 4"); + let preset = pot_unit.find_currently_selected_preset(); + f(preset.as_ref()) + } else { + f(None) + } + } +} + +pub const BROWSE_POT_PRESETS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Pot, + name: "Browse presets", + short_name: "Browse Pot presets", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/browse_tracks_target.rs b/plugin-reaper-realearn/main/src/domain/targets/browse_tracks_target.rs new file mode 100644 index 0000000..657363b --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/browse_tracks_target.rs @@ -0,0 +1,358 @@ +use crate::domain::{ + convert_count_to_step_size, convert_discrete_to_unit_value_with_none, + convert_unit_to_discrete_value_with_none, get_reaper_track_area_of_scope, + get_track_by_scoped_index, get_track_name, scoped_track_index, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, Fraction, NumericValue, Target, UnitValue, +}; +use helgobox_api::persistence::{BrowseTracksMode, TrackScope}; +use reaper_high::{ChangeEvent, Project, Reaper, Track}; +use reaper_medium::{CommandId, MasterTrackBehavior}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedBrowseTracksTarget { + pub scroll_arrange_view: bool, + pub scroll_mixer: bool, + pub mode: BrowseTracksMode, +} + +impl UnresolvedReaperTargetDef for UnresolvedBrowseTracksTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::BrowseTracks(BrowseTracksTarget { + project: context.context().project_or_current_project(), + scroll_arrange_view: self.scroll_arrange_view, + scroll_mixer: self.scroll_mixer, + mode: self.mode, + })]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BrowseTracksTarget { + pub project: Project, + pub scroll_arrange_view: bool, + pub scroll_mixer: bool, + pub mode: BrowseTracksMode, +} + +impl RealearnTarget for BrowseTracksTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: self.step_size(), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + _: ControlContext, + ) -> Result { + let value = convert_unit_value_to_track_index(self.project, self.mode.scope(), input) + .map(|i| i + 1) + .unwrap_or(0); + Ok(value) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + match convert_unit_value_to_track_index(self.project, self.mode.scope(), value) { + None => "".to_string(), + Some(i) => (i + 1).to_string(), + } + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let track_index = match value.to_absolute_value()? { + AbsoluteValue::Continuous(v) => { + convert_unit_value_to_track_index(self.project, self.mode.scope(), v) + } + AbsoluteValue::Discrete(f) => { + if f.actual() == 0 { + None + } else { + Some(f.actual() - 1) + } + } + }; + let track = match track_index { + None => self.project.master_track()?, + Some(i) => get_track_by_scoped_index(self.project, i, self.mode.scope()) + .ok_or("track not available")?, + }; + select_track_exclusively_scoped(&track, self.mode); + if self.scroll_arrange_view { + Reaper::get() + .main_section() + .action_by_command_id(CommandId::new(40913)) + .invoke_as_trigger(Some(track.project()), None) + .expect("built-in action should exist"); + } + if self.scroll_mixer { + track.scroll_mixer(); + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.project.is_available() + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackSelectedChanged(e)) + if e.track.project() == self.project => + { + (true, None) + } + _ => (false, None), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + _: ControlContext, + ) -> Result { + let index = if value == 0 { None } else { Some(value - 1) }; + let track_count = scoped_track_count(self.project, self.mode.scope()); + let uv = convert_discrete_to_unit_value_with_none(index, track_count); + Ok(uv) + } + + fn text_value(&self, _: ControlContext) -> Option> { + match self.first_selected_track()? { + ScopedTrack::InScope(t) => { + let name = get_track_name(&t, self.mode.scope()); + Some(name.into()) + } + ScopedTrack::OutOfScope { floor_track } => { + let name = get_track_name(&floor_track, self.mode.scope()); + Some(format!("After {name}").into()) + } + } + } + + fn numeric_value(&self, _: ControlContext) -> Option { + match self.first_selected_track()? { + ScopedTrack::InScope(t) => { + let index = scoped_track_index(&t, self.mode.scope())?; + Some(NumericValue::Discrete(index as i32 + 1)) + } + ScopedTrack::OutOfScope { floor_track } => { + let index = scoped_track_index(&floor_track, self.mode.scope())?; + Some(NumericValue::Decimal(index as f64 + 1.5)) + } + } + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::BrowseTracks) + } +} + +impl<'a> Target<'a> for BrowseTracksTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + match self.first_selected_track() { + None => Some(self.percentage_for(None)), + Some(ScopedTrack::InScope(track)) => { + let track_index = scoped_track_index(&track, self.mode.scope()); + Some(self.percentage_for(track_index)) + } + Some(ScopedTrack::OutOfScope { floor_track }) => { + let floor_track_index = scoped_track_index(&floor_track, self.mode.scope()); + let floor_percentage = self.percentage_for(floor_track_index); + // Add half of the atomic step size to indicate that it's inbetween two values! + let step_size = self.step_size(); + let inbetween_percentage = + floor_percentage.to_unit_value().get() + step_size.get() / 2.0; + UnitValue::try_new(inbetween_percentage).map(AbsoluteValue::Continuous) + } + } + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +impl BrowseTracksTarget { + fn percentage_for(&self, track_index: Option) -> AbsoluteValue { + percentage_for_scoped_track_within_project(self.project, self.mode.scope(), track_index) + } + + fn first_selected_track(&self) -> Option { + first_selected_track_scoped(self.project, self.mode) + } + + fn step_size(&self) -> UnitValue { + // `+ 1` because "" is also a possible value. + let count = scoped_track_count(self.project, self.mode.scope()) + 1; + convert_count_to_step_size(count) + } +} + +enum ScopedTrack { + InScope(Track), + /// Selected track is out of scope (e.g. we are interested in TCP scope but it#s only visible + /// in MCP). + OutOfScope { + /// Out-of-scope track comes after this in-scope track. + floor_track: Track, + }, +} + +pub const SELECTED_TRACK_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Project, + name: "Browse tracks", + short_name: "Browse tracks", + supports_track_scrolling: true, + ..DEFAULT_TARGET +}; + +pub fn percentage_for_scoped_track_within_project( + project: Project, + policy: TrackScope, + track_index: Option, +) -> AbsoluteValue { + let track_count = scoped_track_count(project, policy); + // Because we count "" as a possible value, this is equal. + let max_value = track_count; + let actual_value = track_index.map(|i| i + 1).unwrap_or(0); + AbsoluteValue::Discrete(Fraction::new(actual_value, max_value)) +} + +fn scoped_track_count(project: Project, scope: TrackScope) -> u32 { + use TrackScope::*; + match scope { + AllTracks => project.track_count(), + TracksVisibleInTcp | TracksVisibleInMcp => { + let track_area = get_reaper_track_area_of_scope(scope); + project.tracks().filter(|t| t.is_shown(track_area)).count() as _ + } + } +} + +fn convert_unit_value_to_track_index( + project: Project, + scope: TrackScope, + value: UnitValue, +) -> Option { + convert_unit_to_discrete_value_with_none(value, scoped_track_count(project, scope)) +} + +fn select_track_exclusively_scoped(track: &Track, mode: BrowseTracksMode) { + use BrowseTracksMode::*; + match mode { + AllTracks | TracksVisibleInTcp | TracksVisibleInMcp => { + track.select_exclusively(); + } + TracksVisibleInTcpAllowTwoSelections | TracksVisibleInMcpAllowTwoSelections => { + let track_area = get_reaper_track_area_of_scope(mode.scope()); + for t in track + .project() + .tracks() + .filter(|t| t != track && t.is_shown(track_area)) + { + t.unselect(); + } + track.select(); + } + } +} + +fn first_selected_track_scoped(project: Project, mode: BrowseTracksMode) -> Option { + use BrowseTracksMode::*; + let master_track_behavior = MasterTrackBehavior::ExcludeMasterTrack; + match mode { + AllTracks => project + .first_selected_track(master_track_behavior) + .map(ScopedTrack::InScope), + TracksVisibleInTcp | TracksVisibleInMcp => { + let first_selected_track = project.first_selected_track(master_track_behavior)?; + let track_area = get_reaper_track_area_of_scope(mode.scope()); + if first_selected_track.is_shown(track_area) { + Some(ScopedTrack::InScope(first_selected_track)) + } else { + let selected_track_index = first_selected_track.index()?; + // Find the first visible track above the currently selected one + project + .tracks() + // Enumerate from first track + .enumerate() + // Search starting from last track + .rev() + .find(|(i, t)| *i < selected_track_index as usize && t.is_shown(track_area)) + .map(|(_, floor_track)| ScopedTrack::OutOfScope { floor_track }) + } + } + TracksVisibleInTcpAllowTwoSelections | TracksVisibleInMcpAllowTwoSelections => { + let mut candidate = None; + for t in project.selected_tracks(master_track_behavior) { + let track_area = get_reaper_track_area_of_scope(mode.scope()); + let other_track_area = get_other_track_area(track_area); + if t.is_shown(track_area) { + // Track is shown in the relevant area. Good. + if candidate.is_none() && t.is_shown(other_track_area) { + // Track is also shown in the other area, so it might not be the final + // result, but it's at least a candidate. + candidate = Some(t); + } else { + // Track is only shown in the relevant area. Perfect. + return Some(ScopedTrack::InScope(t)); + } + } + } + candidate.map(ScopedTrack::InScope) + } + } +} + +fn get_other_track_area(track_area: reaper_medium::TrackArea) -> reaper_medium::TrackArea { + use reaper_medium::TrackArea::*; + match track_area { + Tcp => Mcp, + Mcp => Tcp, + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/compartment_parameter_value_target.rs b/plugin-reaper-realearn/main/src/domain/targets/compartment_parameter_value_target.rs new file mode 100644 index 0000000..78d7c92 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/compartment_parameter_value_target.rs @@ -0,0 +1,193 @@ +use crate::domain::ui_util::parse_unit_value_from_percentage; +use crate::domain::{ + convert_count_to_step_size, CompartmentKind, CompartmentParamIndex, CompoundChangeEvent, + ControlContext, EffectiveParamValue, ExtendedProcessorContext, HitResponse, + MappingControlContext, PluginParamIndex, RealearnTarget, ReaperTarget, ReaperTargetType, + TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, Target, UnitValue}; +use reaper_medium::ReaperNormalizedFxParamValue; +use std::num::NonZeroU32; + +#[derive(Debug)] +pub struct UnresolvedCompartmentParameterValueTarget { + pub compartment: CompartmentKind, + pub index: CompartmentParamIndex, +} + +impl UnresolvedReaperTargetDef for UnresolvedCompartmentParameterValueTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::CompartmentParameterValue( + CompartmentParameterValueTarget { + compartment: self.compartment, + index: self.index, + }, + )]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CompartmentParameterValueTarget { + pub compartment: CompartmentKind, + pub index: CompartmentParamIndex, +} + +impl CompartmentParameterValueTarget { + fn value_count(&self, context: ControlContext) -> Option { + context + .unit + .borrow() + .parameter_manager() + .params() + .compartment_params(self.compartment) + .at(self.index) + .setting() + .value_count + } + + fn plugin_param_index(&self) -> PluginParamIndex { + self.compartment.to_plugin_param_index(self.index) + } +} + +impl RealearnTarget for CompartmentParameterValueTarget { + fn control_type_and_character( + &self, + context: ControlContext, + ) -> (ControlType, TargetCharacter) { + let value_count = self.value_count(context); + if let Some(c) = value_count { + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size(c.get()), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } else { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let unit_value = value.to_unit_value()?; + let plugin_param_index = self.plugin_param_index(); + if context.control_context.unit.borrow().is_main_unit() { + // The main unit of an instance is special in that its compartment parameters are + // connected to the VST plug-in parameters. That's why we should change the VST plug-in + // parameter directly for reasons of unidirectional data flow. + context + .control_context + .processor_context + .containing_fx() + .parameter_by_index(plugin_param_index.get()) + .set_reaper_normalized_value(ReaperNormalizedFxParamValue::new(unit_value.get()))?; + } else { + // Compartment parameters of additional units are purely internal, so we need to + // control them internally. + context + .control_context + .unit + .borrow() + .parameter_manager() + .set_single_parameter(plugin_param_index, unit_value.get() as _); + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::CompartmentParameter(index) + if index == self.plugin_param_index() => + { + (true, None) + } + _ => (false, None), + } + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + match self.value_count(context) { + None => parse_unit_value_from_percentage(text), + Some(_) => self.parse_value_from_discrete_value(text, context), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + context: ControlContext, + ) -> Result { + let value_count = self.value_count(context).ok_or("not supported")?; + let step_size = convert_count_to_step_size(value_count.get()); + let result = (value as f64 * step_size.get()).try_into()?; + Ok(result) + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + context: ControlContext, + ) -> Result { + let value_count = self.value_count(context).ok_or("not supported")?; + let step_size = convert_count_to_step_size(value_count.get()); + let val = (input.get() / step_size.get()).round() as u32; + Ok(val) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::CompartmentParameterValue) + } +} + +impl<'a> Target<'a> for CompartmentParameterValueTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + let unit = context.unit.borrow(); + let params = unit.parameter_manager().params(); + let param = params.compartment_params(self.compartment).at(self.index); + let value = param.effective_value(); + let abs_val = match value { + EffectiveParamValue::Continuous(v) => { + AbsoluteValue::Continuous(UnitValue::new_clamped(v)) + } + EffectiveParamValue::Discrete(v) => { + let value_count = param.setting().value_count.unwrap(); + AbsoluteValue::Discrete(Fraction::new(v, value_count.get() - 1)) + } + }; + Some(abs_val) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const COMPARTMENT_PARAMETER_VALUE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Set compartment parameter value", + short_name: "Set compartment parameter value", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/dummy_target.rs b/plugin-reaper-realearn/main/src/domain/targets/dummy_target.rs new file mode 100644 index 0000000..90b23e2 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/dummy_target.rs @@ -0,0 +1,90 @@ +use crate::domain::{ + CompartmentKind, ControlContext, ExtendedProcessorContext, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; + +#[derive(Debug)] +pub struct UnresolvedDummyTarget; + +impl UnresolvedReaperTargetDef for UnresolvedDummyTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::Dummy(DummyTarget::new())]) + } + + fn can_be_affected_by_change_events(&self) -> bool { + // We don't want to be refreshed because we maintain an artificial value. + false + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct DummyTarget { + // For making basic toggle/relative control possible. + artificial_value: AbsoluteValue, +} + +impl DummyTarget { + pub fn new() -> Self { + Self::default() + } + + fn control_type_and_character_simple(&self) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Continuous, + ) + } +} + +impl RealearnTarget for DummyTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + self.control_type_and_character_simple() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let value = value.to_absolute_value()?; + self.artificial_value = value; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn supports_automatic_feedback(&self) -> bool { + false + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::Dummy) + } +} + +impl Target<'_> for DummyTarget { + type Context = (); + + fn current_value(&self, _context: ()) -> Option { + Some(self.artificial_value) + } + + fn control_type(&self, _: Self::Context) -> ControlType { + self.control_type_and_character_simple().0 + } +} + +pub const DUMMY_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Dummy target", + short_name: "Dummy", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/enable_instances_target.rs b/plugin-reaper-realearn/main/src/domain/targets/enable_instances_target.rs new file mode 100644 index 0000000..0400f4a --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/enable_instances_target.rs @@ -0,0 +1,137 @@ +use crate::domain::{ + format_value_as_on_off, CompartmentKind, CompoundChangeEvent, ControlContext, + EnableInstancesArgs, Exclusivity, ExtendedProcessorContext, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TagScope, TargetCharacter, TargetSection, + TargetTypeDef, UnitEvent, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use helgobox_api::persistence::InstanceTagKind; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedEnableInstancesTarget { + pub scope: TagScope, + pub tag_kind: InstanceTagKind, + pub exclusivity: Exclusivity, +} + +impl UnresolvedReaperTargetDef for UnresolvedEnableInstancesTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::EnableInstances(EnableInstancesTarget { + scope: self.scope.clone(), + tag_kind: self.tag_kind, + exclusivity: self.exclusivity, + })]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EnableInstancesTarget { + pub scope: TagScope, + pub tag_kind: InstanceTagKind, + pub exclusivity: Exclusivity, +} + +impl RealearnTarget for EnableInstancesTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + let is_enable = !value.is_zero(); + let args = EnableInstancesArgs { + tag_kind: self.tag_kind, + common: context + .control_context + .create_modify_unit_container_common_args(&self.scope), + is_enable, + exclusivity: self.exclusivity, + }; + let tags = context + .control_context + .unit_container + .enable_instances(args); + let mut instance_state = context.control_context.unit.borrow_mut(); + use Exclusivity::*; + if self.exclusivity == Exclusive || (self.exclusivity == ExclusiveOnOnly && is_enable) { + // Completely replace + let new_active_tags = tags.unwrap_or_else(|| self.scope.tags.clone()); + instance_state.set_active_instance_tags(new_active_tags); + } else { + // Add or remove + instance_state.activate_or_deactivate_instance_tags(&self.scope.tags, is_enable); + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Unit(UnitEvent::ActiveInstanceTags) => (true, None), + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::EnableInstances) + } +} + +impl<'a> Target<'a> for EnableInstancesTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + let instance_state = context.unit.borrow(); + use Exclusivity::*; + let active = match self.exclusivity { + NonExclusive => { + instance_state.at_least_those_instance_tags_are_active(&self.scope.tags) + } + Exclusive | ExclusiveOnOnly => { + instance_state.only_these_instance_tags_are_active(&self.scope.tags) + } + }; + let uv = if active { + UnitValue::MAX + } else { + UnitValue::MIN + }; + Some(AbsoluteValue::Continuous(uv)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ENABLE_INSTANCES_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Enable/disable instances", + short_name: "Enable/disable instances", + supports_tags: true, + supports_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/enable_mappings_target.rs b/plugin-reaper-realearn/main/src/domain/targets/enable_mappings_target.rs new file mode 100644 index 0000000..8bb3973 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/enable_mappings_target.rs @@ -0,0 +1,193 @@ +use crate::domain::{ + format_value_as_on_off, CompartmentKind, CompoundChangeEvent, ControlContext, DomainEvent, + Exclusivity, ExtendedProcessorContext, HitInstruction, HitInstructionContext, + HitInstructionResponse, HitResponse, MappingControlContext, MappingData, + MappingEnabledChangeRequestedEvent, RealearnTarget, ReaperTarget, ReaperTargetType, TagScope, + TargetCharacter, TargetSection, TargetTypeDef, UnitEvent, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use std::borrow::Cow; +use std::collections::HashSet; + +#[derive(Debug)] +pub struct UnresolvedEnableMappingsTarget { + pub compartment: CompartmentKind, + pub scope: TagScope, + pub exclusivity: Exclusivity, +} + +impl UnresolvedReaperTargetDef for UnresolvedEnableMappingsTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::EnableMappings(EnableMappingsTarget { + compartment: self.compartment, + scope: self.scope.clone(), + exclusivity: self.exclusivity, + })]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EnableMappingsTarget { + /// This must always correspond to the compartment of the containing mapping, otherwise it will + /// lead to strange behavior. + pub compartment: CompartmentKind, + pub scope: TagScope, + pub exclusivity: Exclusivity, +} + +impl RealearnTarget for EnableMappingsTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + let is_enable = !value.is_zero(); + struct EnableMappingsInstruction { + compartment: CompartmentKind, + scope: TagScope, + mapping_data: MappingData, + is_enable: bool, + exclusivity: Exclusivity, + } + impl HitInstruction for EnableMappingsInstruction { + fn execute(self: Box, context: HitInstructionContext) -> HitInstructionResponse { + let mut activated_inverse_tags = HashSet::default(); + for m in context.mappings.values_mut() { + // Don't touch ourselves. + if m.id() == self.mapping_data.mapping_id { + continue; + } + // Determine how to change the mappings. + let flag = match self.scope.determine_enable_disable_change( + self.exclusivity, + m.tags(), + self.is_enable, + ) { + None => continue, + Some(f) => f, + }; + if self.exclusivity == Exclusivity::Exclusive && !self.is_enable { + // Collect all *other* mapping tags because they are going to be activated + // and we have to know about them! + activated_inverse_tags.extend(m.tags().iter().cloned()); + } + // Finally request change of mapping enabled state! + context.domain_event_handler.handle_event_ignoring_error( + DomainEvent::MappingEnabledChangeRequested( + MappingEnabledChangeRequestedEvent { + compartment: m.compartment(), + mapping_id: m.id(), + is_enabled: if self.is_enable { flag } else { !flag }, + }, + ), + ); + } + let mut instance_state = context.control_context.unit.borrow_mut(); + use Exclusivity::*; + if self.exclusivity == Exclusive + || (self.exclusivity == ExclusiveOnOnly && self.is_enable) + { + // Completely replace + let new_active_tags = if self.is_enable { + self.scope.tags.clone() + } else { + activated_inverse_tags + }; + instance_state.set_active_mapping_tags(self.compartment, new_active_tags); + } else { + // Add or remove + instance_state.activate_or_deactivate_mapping_tags( + self.compartment, + &self.scope.tags, + self.is_enable, + ); + } + HitInstructionResponse::CausedEffect(vec![]) + } + } + let instruction = EnableMappingsInstruction { + compartment: self.compartment, + // So far this clone is okay because enabling/disable mappings is not something that + // happens every few milliseconds. No need to use a ref to this target. + scope: self.scope.clone(), + mapping_data: context.mapping_data, + is_enable, + exclusivity: self.exclusivity, + }; + Ok(HitResponse::hit_instruction(Box::new(instruction))) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Unit(UnitEvent::ActiveMappingTags { compartment, .. }) + if *compartment == self.compartment => + { + (true, None) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::EnableMappings) + } +} + +impl<'a> Target<'a> for EnableMappingsTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + let instance_state = context.unit.borrow(); + use Exclusivity::*; + let active = match self.exclusivity { + NonExclusive => instance_state + .at_least_those_mapping_tags_are_active(self.compartment, &self.scope.tags), + Exclusive | ExclusiveOnOnly => instance_state + .only_these_mapping_tags_are_active(self.compartment, &self.scope.tags), + }; + let uv = if active { + UnitValue::MAX + } else { + UnitValue::MIN + }; + Some(AbsoluteValue::Continuous(uv)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ENABLE_MAPPINGS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Enable/disable mappings", + short_name: "Enable/disable mappings", + supports_tags: true, + supports_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/enable_units_target.rs b/plugin-reaper-realearn/main/src/domain/targets/enable_units_target.rs new file mode 100644 index 0000000..400bf09 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/enable_units_target.rs @@ -0,0 +1,129 @@ +use crate::domain::{ + format_value_as_on_off, CompartmentKind, CompoundChangeEvent, ControlContext, EnableUnitsArgs, + Exclusivity, ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, + ReaperTarget, ReaperTargetType, TagScope, TargetCharacter, TargetSection, TargetTypeDef, + UnitEvent, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedEnableUnitsTarget { + pub scope: TagScope, + pub exclusivity: Exclusivity, +} + +impl UnresolvedReaperTargetDef for UnresolvedEnableUnitsTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let target = EnableUnitsTarget { + scope: self.scope.clone(), + exclusivity: self.exclusivity, + }; + Ok(vec![ReaperTarget::EnableUnits(target)]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EnableUnitsTarget { + pub scope: TagScope, + pub exclusivity: Exclusivity, +} + +impl RealearnTarget for EnableUnitsTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + let is_enable = !value.is_zero(); + let args = EnableUnitsArgs { + common: context + .control_context + .create_modify_unit_container_common_args(&self.scope), + is_enable, + exclusivity: self.exclusivity, + }; + let tags = context.control_context.unit_container.enable_units(args); + let mut unit = context.control_context.unit.borrow_mut(); + if self.exclusivity == Exclusivity::Exclusive + || (self.exclusivity == Exclusivity::ExclusiveOnOnly && is_enable) + { + // Completely replace + let new_active_tags = tags.unwrap_or_else(|| self.scope.tags.clone()); + unit.set_active_unit_tags(new_active_tags); + } else { + // Add or remove + unit.activate_or_deactivate_unit_tags(&self.scope.tags, is_enable); + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Unit(UnitEvent::ActiveUnitTags) => (true, None), + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::EnableUnits) + } +} + +impl<'a> Target<'a> for EnableUnitsTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + let unit_state = context.unit.borrow(); + use Exclusivity::*; + let active = match self.exclusivity { + NonExclusive => unit_state.at_least_those_unit_tags_are_active(&self.scope.tags), + Exclusive | ExclusiveOnOnly => { + unit_state.only_these_unit_tags_are_active(&self.scope.tags) + } + }; + let uv = if active { + UnitValue::MAX + } else { + UnitValue::MIN + }; + Some(AbsoluteValue::Continuous(uv)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ENABLE_UNITS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Enable/disable units", + short_name: "Enable/disable units", + supports_tags: true, + supports_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/fx_enable_target.rs b/plugin-reaper-realearn/main/src/domain/targets/fx_enable_target.rs new file mode 100644 index 0000000..85661fd --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/fx_enable_target.rs @@ -0,0 +1,136 @@ +use crate::domain::{ + format_value_as_on_off, fx_enable_unit_value, CompartmentKind, CompoundChangeEvent, + ControlContext, ExtendedProcessorContext, FxDescriptor, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Fx, Project, Track}; +use reaper_medium::ParamId; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedFxEnableTarget { + pub fx_descriptor: FxDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedFxEnableTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok(self + .fx_descriptor + .resolve(context, compartment)? + .into_iter() + .map(|fx| { + ReaperTarget::FxEnable(FxEnableTarget { + bypass_param_index: fx.parameter_by_id(ParamId::Bypass).map(|p| p.index()), + fx, + }) + }) + .collect()) + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + Some(&self.fx_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FxEnableTarget { + pub fx: Fx, + pub bypass_param_index: Option, +} + +impl RealearnTarget for FxEnableTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + self.fx.disable()?; + } else { + self.fx.enable()?; + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.fx.is_available() + } + + fn project(&self) -> Option { + self.fx.project() + } + + fn track(&self) -> Option<&Track> { + self.fx.track() + } + + fn fx(&self) -> Option<&Fx> { + Some(&self.fx) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::FxEnabledChanged(e)) if e.fx == self.fx => ( + true, + Some(AbsoluteValue::Continuous(fx_enable_unit_value(e.new_value))), + ), + CompoundChangeEvent::Reaper(ChangeEvent::FxParameterValueChanged(e)) + if Some(e.parameter.index()) == self.bypass_param_index + && e.parameter.fx() == &self.fx => + { + (true, None) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::FxEnable) + } +} + +impl<'a> Target<'a> for FxEnableTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + Some(AbsoluteValue::Continuous(fx_enable_unit_value( + self.fx.is_enabled(), + ))) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const FX_ENABLE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Fx, + name: "Enable/disable", + short_name: "Enable/disable FX", + hint: "No feedback from automation", + supports_track: true, + supports_fx: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/fx_online_target.rs b/plugin-reaper-realearn/main/src/domain/targets/fx_online_target.rs new file mode 100644 index 0000000..caacad6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/fx_online_target.rs @@ -0,0 +1,109 @@ +use crate::domain::{ + format_value_as_on_off, fx_online_unit_value, CompartmentKind, ControlContext, + ExtendedProcessorContext, FeedbackResolution, FxDescriptor, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Fx, Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedFxOnlineTarget { + pub fx_descriptor: FxDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedFxOnlineTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok(self + .fx_descriptor + .resolve(context, compartment)? + .into_iter() + .map(|fx| ReaperTarget::FxOnline(FxOnlineTarget { fx })) + .collect()) + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + Some(&self.fx_descriptor) + } + + fn feedback_resolution(&self) -> Option { + Some(FeedbackResolution::High) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FxOnlineTarget { + pub fx: Fx, +} + +impl RealearnTarget for FxOnlineTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let online = !value.to_unit_value()?.is_zero(); + self.fx.set_online(online)?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.fx.is_available() + } + + fn project(&self) -> Option { + self.fx.project() + } + + fn track(&self) -> Option<&Track> { + self.fx.track() + } + + fn fx(&self) -> Option<&Fx> { + Some(&self.fx) + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::FxOnline) + } +} + +impl<'a> Target<'a> for FxOnlineTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + Some(AbsoluteValue::Continuous(fx_online_unit_value( + self.fx.is_online(), + ))) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const FX_ONLINE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Fx, + name: "Set online/offline", + short_name: "On/off-line FX", + supports_track: true, + supports_fx: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/fx_open_target.rs b/plugin-reaper-realearn/main/src/domain/targets/fx_open_target.rs new file mode 100644 index 0000000..b86aafe --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/fx_open_target.rs @@ -0,0 +1,157 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + format_value_as_on_off, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, FxDescriptor, FxDisplayType, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Fx, Project, Track}; +use reaper_medium::FxChainVisibility; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedFxOpenTarget { + pub fx_descriptor: FxDescriptor, + pub display_type: FxDisplayType, +} + +impl UnresolvedReaperTargetDef for UnresolvedFxOpenTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok(self + .fx_descriptor + .resolve(context, compartment)? + .into_iter() + .map(|fx| { + ReaperTarget::FxOpen(FxOpenTarget { + fx, + display_type: self.display_type, + }) + }) + .collect()) + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + Some(&self.fx_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FxOpenTarget { + pub fx: Fx, + pub display_type: FxDisplayType, +} + +impl RealearnTarget for FxOpenTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + use FxDisplayType::*; + if value.to_unit_value()?.is_zero() { + match self.display_type { + FloatingWindow => { + self.fx.hide_floating_window()?; + } + Chain => { + self.fx.chain().hide()?; + } + } + } else { + match self.display_type { + FloatingWindow => { + self.fx.show_in_floating_window()?; + } + Chain => { + self.fx.show_in_chain()?; + } + } + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.fx.is_available() + } + + fn project(&self) -> Option { + self.fx.project() + } + + fn track(&self) -> Option<&Track> { + self.fx.track() + } + + fn fx(&self) -> Option<&Fx> { + Some(&self.fx) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + use CompoundChangeEvent::*; + match evt { + Reaper(ChangeEvent::FxOpened(e)) if e.fx == self.fx => (true, None), + Reaper(ChangeEvent::FxClosed(e)) if e.fx == self.fx => (true, None), + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::FxOpen) + } +} + +impl<'a> Target<'a> for FxOpenTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + use FxDisplayType::*; + let is_open = match self.display_type { + FloatingWindow => self.fx.floating_window().is_some(), + Chain => { + use FxChainVisibility::*; + match self.fx.chain().visibility() { + Hidden | Visible(None) | Unknown(_) => false, + Visible(Some(i)) => self.fx.index() == i, + } + } + }; + Some(AbsoluteValue::Continuous(convert_bool_to_unit_value( + is_open, + ))) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const FX_OPEN_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Fx, + name: "Open/close", + short_name: "Open/close FX", + supports_track: true, + supports_fx: true, + supports_fx_display_type: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/fx_parameter_target.rs b/plugin-reaper-realearn/main/src/domain/targets/fx_parameter_target.rs new file mode 100644 index 0000000..2da6902 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/fx_parameter_target.rs @@ -0,0 +1,480 @@ +use crate::domain::ui_util::parse_unit_value_from_percentage; +use crate::domain::{ + get_fx_params, AdditionalFeedbackEvent, Backbone, Caller, CompartmentKind, CompoundChangeEvent, + ControlContext, ExtendedProcessorContext, FeedbackResolution, FxParameterDescriptor, + HitResponse, MappingControlContext, RealTimeControlContext, RealTimeReaperTarget, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, PropValue, Target, UnitValue}; +use pot::{MacroParam, MacroParamBank}; +use reaper_high::{ChangeEvent, Fx, FxParameter, FxParameterCharacter, Project, Reaper, Track}; +use reaper_medium::{ + GetParamExResult, GetParameterStepSizesResult, MediaTrack, ReaperNormalizedFxParamValue, + TrackFxLocation, +}; +use std::borrow::Cow; +use std::convert::TryInto; +use tracing::warn; + +#[derive(Debug)] +pub struct UnresolvedFxParameterTarget { + pub fx_parameter_descriptor: FxParameterDescriptor, + pub poll_for_feedback: bool, + pub retrigger: bool, + pub real_time_even_if_not_rendering: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedFxParameterTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let params = get_fx_params(context, &self.fx_parameter_descriptor, compartment)?; + let targets = params + .into_iter() + .map(|param| { + let is_real_time_ready = reaper_is_ready_for_real_time_fx_param_control() + && fx_is_on_same_track_as_realearn(context, ¶m); + let target = FxParameterTarget { + is_real_time_ready, + param, + poll_for_feedback: self.poll_for_feedback, + retrigger: self.retrigger, + real_time_even_if_not_rendering: self.real_time_even_if_not_rendering, + }; + ReaperTarget::FxParameter(target) + }) + .collect(); + Ok(targets) + } + + fn fx_parameter_descriptor(&self) -> Option<&FxParameterDescriptor> { + Some(&self.fx_parameter_descriptor) + } + + fn feedback_resolution(&self) -> Option { + if self.poll_for_feedback { + Some(FeedbackResolution::High) + } else { + None + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FxParameterTarget { + pub is_real_time_ready: bool, + pub param: FxParameter, + pub poll_for_feedback: bool, + pub retrigger: bool, + pub real_time_even_if_not_rendering: bool, +} + +impl FxParameterTarget { + fn with_macro_param( + &self, + f: impl FnOnce(&MacroParamBank, u32, &MacroParam) -> R, + ) -> Option { + let target_state = Backbone::target_state().borrow(); + let current_preset = target_state.current_fx_preset(self.param.fx())?; + // Our target doesn't have the concept of a macro param. It's always resolved using an FX + // parameter ID. So we have to do a reverse lookup here. + // TODO-low We could improve that by adding a where the result of the + // dynamic expression is interpreted as macro param index. But not urgent. Reverse lookup + // should be okay performance-wise (because not many macro params) and logically (because + // usually no duplicate parameters in macro param definitions). + let (bank, slot_index, param) = + current_preset.find_first_macro_param_with_fx_param_index(self.param.index())?; + Some(f(bank, slot_index, param)) + } +} + +impl RealearnTarget for FxParameterTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + determine_param_control_type_and_character( + || self.param.step_sizes(), + || self.param.value_range(), + self.retrigger, + ) + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + if self.param.character() == FxParameterCharacter::Discrete { + self.parse_value_from_discrete_value(text, context) + } else { + parse_unit_value_from_percentage(text) + } + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + if self.param.character() == FxParameterCharacter::Discrete { + self.parse_value_from_discrete_value(text, context) + } else { + parse_unit_value_from_percentage(text) + } + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + _: ControlContext, + ) -> Result { + // Example (target value count = 11, target step size = 0.10): + // - 0 => 0 + // - 0.05 => 1 + // - 0.10 => 1 + // - 0.15 => 2 + // - 0.20 => 2 + // Extreme example (target value count = 2, step size = 1.0): + // - 0.0..0.5 => 0 + // - 0.5..=1.0 => 1 + let step_size = self.param.step_size().ok_or("not supported")?; + let val = (input.get() / step_size).round() as u32; + Ok(val) + } + + fn format_value(&self, value: UnitValue, context: ControlContext) -> String { + let formatted_value = self + .param + // Even if a REAPER-normalized value can take numbers > 1.0, the usual value range + // is in fact normalized in the classical sense (unit interval). + .format_reaper_normalized_value(ReaperNormalizedFxParamValue::new(value.get())) + .map(|s| s.into_string()); + formatted_value.unwrap_or_else(|_| self.format_value_generic(value, context)) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + // It's okay to just convert this to a REAPER-normalized value. We don't support + // values above the maximum (or buggy plug-ins). + let v = ReaperNormalizedFxParamValue::new(value.to_unit_value()?.get()); + self.param + .set_reaper_normalized_value(v) + .map_err(|_| "couldn't set FX parameter value")?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.param.is_available() + } + + fn project(&self) -> Option { + self.param.fx().project() + } + + fn track(&self) -> Option<&Track> { + self.param.fx().track() + } + + fn fx(&self) -> Option<&Fx> { + Some(self.param.fx()) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + if self.poll_for_feedback { + return (false, None); + } + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::FxParameterValueChanged(e)) + if e.parameter == self.param => + { + ( + true, + Some(fx_parameter_absolute_value(&e.parameter, e.new_value)), + ) + } + CompoundChangeEvent::Additional( + AdditionalFeedbackEvent::RealearnMonitoringFxParameterValueChanged(e), + ) if e.parameter == self.param => ( + true, + Some(fx_parameter_absolute_value(&e.parameter, e.new_value)), + ), + _ => (false, None), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + _: ControlContext, + ) -> Result { + let step_size = self.param.step_size().ok_or("not supported")?; + let result = (value as f64 * step_size).try_into()?; + Ok(result) + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(self.param.formatted_value().ok()?.into_string().into()) + } + + fn prop_value(&self, key: &str, _: ControlContext) -> Option { + match key { + "fx_parameter.index" => Some(PropValue::Index(self.param.index())), + "fx_parameter.name" => Some(PropValue::Text( + self.param + .name() + .ok()? + .into_inner() + .to_string_lossy() + .to_string() + .into(), + )), + "fx_parameter.macro.name" => { + self.with_macro_param(|_, _, p| PropValue::Text(p.name.clone().into())) + } + "fx_parameter.macro.bank.name" => { + self.with_macro_param(|b, _, _| PropValue::Text(b.name().into())) + } + "fx_parameter.macro.section.name" => self.with_macro_param(|b, i, _| { + let section_name = b.resolve_param_section(i).unwrap_or_default(); + PropValue::Text(section_name.to_string().into()) + }), + "fx_parameter.macro.section.index" => self.with_macro_param(|b, i, _| { + let section = b.resolve_param_section_index(i)?; + Some(PropValue::Index(section)) + })?, + "fx_parameter.macro.new_section.name" => self.with_macro_param(|_, _, p| { + PropValue::Text(p.section.clone().unwrap_or_default().into()) + }), + _ => None, + } + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::FxParameterValue) + } + + fn splinter_real_time_target(&self) -> Option { + if !self.is_real_time_ready { + // Real-time controlling not possible. + return None; + } + let target = RealTimeFxParameterTarget { + track: self.param.fx().track()?.raw().ok()?, + fx_location: self.param.fx().query_index(), + param_index: self.param.index(), + retrigger: self.retrigger, + real_time_even_if_not_rendering: self.real_time_even_if_not_rendering, + }; + Some(RealTimeReaperTarget::FxParameter(target)) + } +} + +impl<'a> Target<'a> for FxParameterTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + Some(fx_parameter_absolute_value( + &self.param, + self.param.reaper_normalized_value(), + )) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RealTimeFxParameterTarget { + track: MediaTrack, + fx_location: TrackFxLocation, + param_index: u32, + retrigger: bool, + real_time_even_if_not_rendering: bool, +} + +unsafe impl Send for RealTimeFxParameterTarget {} + +impl RealTimeFxParameterTarget { + pub fn wants_real_time_control(&self, caller: Caller, is_rendering: bool) -> bool { + if !caller.is_vst() { + // Setting the target FX parameter value in real-time is only safe if we are in the + // processing callstack of the target FX track. The resolve step of this target makes + // sure that a real-time target doesn't even exist if the ReaLearn track doesn't match + // the target FX track. But we still need to make sure here that we are in the same + // processing callstack. This is the case if we are in a processing method of the + // ReaLearn VST plug-in (control input = FX input). It's not the case if we are called + // from the audio hook (control input = MIDI hardware device). + return false; + } + if !self.real_time_even_if_not_rendering && !is_rendering { + // By default, we want real-time control only during rendering. + return false; + } + true + } + + pub fn hit(&mut self, value: ControlValue) -> Result<(), &'static str> { + // It's okay to just convert this to a REAPER-normalized value. We don't support + // values above the maximum (or buggy plug-ins). + let v = ReaperNormalizedFxParamValue::new(value.to_unit_value()?.get()); + let result = unsafe { + Reaper::get().medium_reaper().track_fx_set_param_normalized( + self.track, + self.fx_location, + self.param_index, + v, + ) + }; + result.map_err(|_| "couldn't set FX parameter value")?; + Ok(()) + } +} + +impl<'a> Target<'a> for RealTimeFxParameterTarget { + type Context = RealTimeControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let reaper_normalize_value = unsafe { + Reaper::get().medium_reaper().track_fx_get_param_normalized( + self.track, + self.fx_location, + self.param_index, + ) + }; + let unit_value = UnitValue::new_clamped(reaper_normalize_value.get()); + Some(AbsoluteValue::Continuous(unit_value)) + } + + fn control_type(&self, _: Self::Context) -> ControlType { + determine_param_control_type_and_character( + || unsafe { + Reaper::get() + .medium_reaper() + .track_fx_get_parameter_step_sizes( + self.track, + self.fx_location, + self.param_index, + ) + }, + || unsafe { + Reaper::get().medium_reaper().track_fx_get_param_ex( + self.track, + self.fx_location, + self.param_index, + ) + }, + self.retrigger, + ) + .0 + } +} + +pub const FX_PARAMETER_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::FxParameter, + name: "Set value", + short_name: "FX parameter value", + supports_poll_for_feedback: true, + supports_track: true, + supports_fx: true, + supports_fx_parameter: true, + ..DEFAULT_TARGET +}; + +fn determine_param_control_type_and_character( + get_step_sizes: impl FnOnce() -> Option, + get_value_range: impl FnOnce() -> GetParamExResult, + retrigger: bool, +) -> (ControlType, TargetCharacter) { + match get_step_sizes() { + None => { + let control_type = if retrigger { + ControlType::AbsoluteContinuousRetriggerable + } else { + ControlType::AbsoluteContinuous + }; + (control_type, TargetCharacter::Continuous) + } + Some(GetParameterStepSizesResult::Normal { + normal_step, + small_step, + .. + }) => { + // The reported step sizes relate to the reported value range, which is not + // always the unit interval! Easy to test with JS + // FX. + let range = get_value_range(); + // We are primarily interested in the smallest step size that makes sense. + // We can always create multiples of it. + let span = (range.max_value - range.min_value).abs(); + if span == 0.0 { + return (ControlType::AbsoluteContinuous, TargetCharacter::Continuous); + } + let pref_step_size = small_step.unwrap_or(normal_step); + let step_size = pref_step_size / span; + let step_size = UnitValue::try_new(step_size).unwrap_or_else(|| { + // Happens sometimes with Altiverb: https://github.com/helgoboss/helgobox/issues/1274 + warn!("Step size reported for FX parameter not in unit interval: effective = {step_size}, small = {small_step:?}, normal = {normal_step}"); + UnitValue::new_clamped(step_size) + }); + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: step_size, + is_retriggerable: retrigger, + }, + TargetCharacter::Discrete, + ) + } + Some(GetParameterStepSizesResult::Toggle) => { + let control_type = if retrigger { + ControlType::AbsoluteContinuousRetriggerable + } else { + ControlType::AbsoluteContinuous + }; + (control_type, TargetCharacter::Switch) + } + } +} + +fn fx_parameter_absolute_value( + param: &FxParameter, + value: ReaperNormalizedFxParamValue, +) -> AbsoluteValue { + AbsoluteValue::Continuous(fx_parameter_unit_value(param, value)) +} + +fn fx_parameter_unit_value(param: &FxParameter, value: ReaperNormalizedFxParamValue) -> UnitValue { + let v = value.get(); + UnitValue::try_new(v).unwrap_or_else(|| { + // Either the FX reports a wrong value range (e.g. TAL Flanger Sync Speed) + // or the value range exceeded a "normal" range (e.g. ReaPitch Wet). We can't + // know. In future, we might offer further customization possibilities here. + // For now, we just report it as 0.0 or 1.0 and log a warning. + // TODO-medium As soon as we migrate to tracing, build this into the real-time target, too. + warn!( + "FX parameter reported normalized value {:?} which is not in unit interval: {:?}", + v, param + ); + UnitValue::new_clamped(v) + }) +} + +fn reaper_is_ready_for_real_time_fx_param_control() -> bool { + Reaper::get().version().revision() >= "6.52+dev0323" +} + +/// If ReaLearn is not on the same track as the FX whose parameters it should control, +/// controlling from a real-time thread is unsafe. +/// See here: https://forum.cockos.com/showpost.php?p=2525657&postcount=2009 +fn fx_is_on_same_track_as_realearn(context: ExtendedProcessorContext, param: &FxParameter) -> bool { + let realearn_track = context.context.track(); + realearn_track.is_some() && realearn_track == param.fx().track() +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/fx_parameter_touch_state_target.rs b/plugin-reaper-realearn/main/src/domain/targets/fx_parameter_touch_state_target.rs new file mode 100644 index 0000000..1a76da8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/fx_parameter_touch_state_target.rs @@ -0,0 +1,109 @@ +use crate::domain::{ + format_value_as_on_off, get_fx_params, CompartmentKind, ControlContext, + ExtendedProcessorContext, FxParameterDescriptor, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Fx, FxParameter, Project, Track}; + +#[derive(Debug)] +pub struct UnresolvedFxParameterTouchStateTarget { + pub fx_parameter_descriptor: FxParameterDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedFxParameterTouchStateTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let params = get_fx_params(context, &self.fx_parameter_descriptor, compartment)?; + let targets = params + .into_iter() + .map(|param| ReaperTarget::FxParameterTouchState(FxParameterTouchStateTarget { param })) + .collect(); + Ok(targets) + } + + fn fx_parameter_descriptor(&self) -> Option<&FxParameterDescriptor> { + Some(&self.fx_parameter_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FxParameterTouchStateTarget { + pub param: FxParameter, +} + +impl RealearnTarget for FxParameterTouchStateTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.is_on() { + // Correct! Here, we only want an effect if the button is *released*. + return Ok(HitResponse::ignored()); + } + self.param.end_edit().map_err(|e| e.message())?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.param.is_available() + } + + fn project(&self) -> Option { + self.param.fx().project() + } + + fn track(&self) -> Option<&Track> { + self.param.fx().track() + } + + fn fx(&self) -> Option<&Fx> { + Some(self.param.fx()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::FxParameterTouchState) + } + + fn can_report_current_value(&self) -> bool { + false + } +} + +impl<'a> Target<'a> for FxParameterTouchStateTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + None + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const FX_PARAMETER_TOUCH_STATE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::FxParameter, + name: "Set automation touch state", + short_name: "FX parameter touch state", + supports_track: true, + supports_fx: true, + supports_fx_parameter: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/fx_preset_target.rs b/plugin-reaper-realearn/main/src/domain/targets/fx_preset_target.rs new file mode 100644 index 0000000..a6d4df2 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/fx_preset_target.rs @@ -0,0 +1,197 @@ +use crate::domain::{ + convert_count_to_step_size, convert_unit_value_to_preset_index, fx_preset_unit_value, + CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, FxDescriptor, + HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, + TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, Fraction, NumericValue, Target, UnitValue, +}; +use reaper_high::{ChangeEvent, Fx, Project, Track}; +use reaper_medium::FxPresetRef; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedFxPresetTarget { + pub fx_descriptor: FxDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedFxPresetTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok(self + .fx_descriptor + .resolve(context, compartment)? + .into_iter() + .map(|fx| ReaperTarget::FxPreset(FxPresetTarget { fx })) + .collect()) + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + Some(&self.fx_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FxPresetTarget { + pub fx: Fx, +} + +impl RealearnTarget for FxPresetTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // `+ 1` because "" is also a possible value. + let preset_count = self.fx.preset_index_and_count().count; + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size(preset_count + 1), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + _: ControlContext, + ) -> Result { + let value = convert_unit_value_to_preset_index(&self.fx, input) + .map(|i| i + 1) + .unwrap_or(0); + Ok(value) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + match convert_unit_value_to_preset_index(&self.fx, value) { + None => "".to_string(), + Some(i) => (i + 1).to_string(), + } + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let preset_index = match value.to_absolute_value()? { + AbsoluteValue::Continuous(v) => convert_unit_value_to_preset_index(&self.fx, v), + AbsoluteValue::Discrete(f) => { + if f.actual() == 0 { + None + } else { + Some(f.actual() - 1) + } + } + }; + let preset_ref = match preset_index { + None => FxPresetRef::FactoryPreset, + Some(i) => FxPresetRef::Preset(i), + }; + self.fx.activate_preset(preset_ref)?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.fx.is_available() + } + + fn project(&self) -> Option { + self.fx.project() + } + + fn track(&self) -> Option<&Track> { + self.fx.track() + } + + fn fx(&self) -> Option<&Fx> { + Some(&self.fx) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::FxPresetChanged(e)) if e.fx == self.fx => { + (true, None) + } + CompoundChangeEvent::Reaper(ChangeEvent::FxParameterValueChanged(e)) + if e.parameter.fx() == &self.fx => + { + (true, None) + } + _ => (false, None), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + _: ControlContext, + ) -> Result { + let index = if value == 0 { None } else { Some(value - 1) }; + Ok(fx_preset_unit_value(&self.fx, index)) + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(self.fx.preset_name()?.into_string().into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + let index = self.fx.preset_index_and_count().index?; + Some(NumericValue::Discrete(index as i32 + 1)) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::FxPreset) + } +} + +impl<'a> Target<'a> for FxPresetTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let res = self.fx.preset_index_and_count(); + // Because we count "" as a possible value, this is equal. + let max_value = res.count; + let actual_value = res.index.map(|i| i + 1).unwrap_or(0); + Some(AbsoluteValue::Discrete(Fraction::new( + actual_value, + max_value, + ))) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const FX_PRESET_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Fx, + name: "Browse presets", + short_name: "Browse FX presets", + hint: "Automatic feedback since REAPER v6.13", + supports_track: true, + supports_fx: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/fx_tool_target.rs b/plugin-reaper-realearn/main/src/domain/targets/fx_tool_target.rs new file mode 100644 index 0000000..8249751 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/fx_tool_target.rs @@ -0,0 +1,199 @@ +use crate::domain::{ + get_fx_name, ChangeInstanceFxArgs, CompartmentKind, ControlContext, ExtendedProcessorContext, + FxDescriptor, HitResponse, InstanceFxChangeRequest, MappingControlContext, RealearnTarget, + ReaperTarget, ReaperTargetType, TagScope, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, NumericValue, Target}; +use helgobox_api::persistence::FxToolAction; +use reaper_high::{Fx, FxChain, Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedFxToolTarget { + pub fx_descriptor: FxDescriptor, + pub action: FxToolAction, + pub scope: TagScope, +} + +impl UnresolvedReaperTargetDef for UnresolvedFxToolTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let fxs = self + .fx_descriptor + .resolve(context, compartment) + .and_then(|fxs| { + if fxs.is_empty() { + Err("resolved to zero FXs") + } else { + Ok(fxs) + } + }); + let targets = match fxs { + Ok(fxs) => fxs + .into_iter() + .map(|fx| { + ReaperTarget::FxTool(FxToolTarget { + fx: Some(fx), + action: self.action, + scope: self.scope.clone(), + }) + }) + .collect(), + Err(e) => { + if self.action == FxToolAction::SetAsUnitFx { + // If we just want to *set* the (unresolved) FX as instance FX, we + // don't need a resolved target. + let target = ReaperTarget::FxTool(FxToolTarget { + fx: None, + action: self.action, + scope: self.scope.clone(), + }); + vec![target] + } else { + // Otherwise we should classify the target as inactive. + return Err(e); + } + } + }; + Ok(targets) + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + Some(&self.fx_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FxToolTarget { + pub fx: Option, + pub action: FxToolAction, + pub scope: TagScope, +} + +impl RealearnTarget for FxToolTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + + fn is_available(&self, _: ControlContext) -> bool { + match &self.fx { + None => false, + Some(fx) => fx.is_available(), + } + } + + fn project(&self) -> Option { + self.fx.as_ref()?.project() + } + + fn track(&self) -> Option<&Track> { + self.fx.as_ref()?.track() + } + + fn fx(&self) -> Option<&Fx> { + self.fx.as_ref() + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(get_fx_name(self.fx.as_ref()?).into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + let position = self.fx.as_ref()?.index() + 1; + Some(NumericValue::Discrete(position as _)) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::FxTool) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + let request = match self.action { + FxToolAction::DoNothing => return Ok(HitResponse::ignored()), + FxToolAction::SetAsUnitFx => { + InstanceFxChangeRequest::SetFromMapping(context.mapping_data.qualified_mapping_id()) + } + FxToolAction::PinAsUnitFx => { + let fx = self.fx.as_ref().ok_or("FX could not be resolved")?; + InstanceFxChangeRequest::Pin { + track_guid: fx.track().and_then(|t| { + if t.is_master_track() { + None + } else { + Some(*t.guid()) + } + }), + is_input_fx: fx.is_input_fx(), + fx_guid: fx.get_or_query_guid()?, + } + } + }; + let args = ChangeInstanceFxArgs { + common: context + .control_context + .create_modify_unit_container_common_args(&self.scope), + request, + }; + context + .control_context + .unit_container + .change_instance_fx(args)?; + Ok(HitResponse::processed_with_effect()) + } + + fn can_report_current_value(&self) -> bool { + matches!(self.action, FxToolAction::DoNothing) + } +} + +impl<'a> Target<'a> for FxToolTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + match self.action { + FxToolAction::DoNothing => { + let fx = self.fx.as_ref()?; + let fx_index = fx.index(); + percentage_for_fx_within_chain(fx.chain(), fx_index) + } + FxToolAction::SetAsUnitFx | FxToolAction::PinAsUnitFx => { + // In future, we might support feedback here. + None + } + } + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const FX_TOOL_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Fx, + name: "FX", + short_name: "FX", + supports_fx: true, + supports_track: true, + supports_tags: true, + ..DEFAULT_TARGET +}; + +fn percentage_for_fx_within_chain(fx_chain: &FxChain, fx_index: u32) -> Option { + let fx_count = fx_chain.fx_count(); + let max_value = fx_count.checked_sub(1)?; + Some(AbsoluteValue::Discrete(Fraction::new(fx_index, max_value))) +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/go_to_bookmark_target.rs b/plugin-reaper-realearn/main/src/domain/targets/go_to_bookmark_target.rs new file mode 100644 index 0000000..9c0f916 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/go_to_bookmark_target.rs @@ -0,0 +1,246 @@ +use crate::application::BookmarkAnchorType; +use crate::domain::{ + current_value_of_bookmark, find_bookmark, format_value_as_on_off, with_seek_behavior, + AdditionalFeedbackEvent, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, FeedbackResolution, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, PropValue, RgbColor, Target, UnitValue, +}; +use helgobox_api::persistence::SeekBehavior; +use reaper_high::{BookmarkType, ChangeEvent, FindBookmarkResult, Project, Reaper}; +use reaper_medium::{AutoSeekBehavior, BookmarkRef, SetEditCurPosOptions}; +use std::borrow::Cow; +use std::num::NonZeroU32; + +#[derive(Debug)] +pub struct UnresolvedGoToBookmarkTarget { + pub bookmark_type: BookmarkType, + pub bookmark_anchor_type: BookmarkAnchorType, + pub bookmark_ref: u32, + pub set_time_selection: bool, + pub set_loop_points: bool, + pub seek_behavior: SeekBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedGoToBookmarkTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let project = context.context().project_or_current_project(); + let res = find_bookmark( + project, + self.bookmark_type, + self.bookmark_anchor_type, + self.bookmark_ref, + )?; + Ok(vec![ReaperTarget::GoToBookmark(GoToBookmarkTarget { + project, + bookmark_type: self.bookmark_type, + index: res.index, + position: NonZeroU32::new(res.index_within_type + 1).unwrap(), + set_time_selection: self.set_time_selection, + set_loop_points: self.set_loop_points, + seek_behavior: self.seek_behavior, + })]) + } + + fn feedback_resolution(&self) -> Option { + Some(FeedbackResolution::Beat) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct GoToBookmarkTarget { + pub project: Project, + pub bookmark_type: BookmarkType, + // This counts both markers and regions. We need it for getting the current value. + pub index: u32, + // This counts either only markers or only regions. We need it for control. The alternative + // would be an ID but unfortunately, marker IDs are not unique which means we would + // unnecessarily lack reliability to go to markers in a position-based way. + pub position: NonZeroU32, + pub set_time_selection: bool, + pub set_loop_points: bool, + pub seek_behavior: SeekBehavior, +} + +impl GoToBookmarkTarget { + fn find_bookmark(&self) -> Option { + self.project + .find_bookmark_by_type_and_index(self.bookmark_type, self.position.get() - 1) + } +} + +impl RealearnTarget for GoToBookmarkTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + return Ok(HitResponse::ignored()); + } + match self.bookmark_type { + BookmarkType::Marker => { + with_seek_behavior(self.seek_behavior, || { + self.project + .go_to_marker(BookmarkRef::Position(self.position)) + }); + } + BookmarkType::Region => { + let smooth_seek = match self.seek_behavior { + SeekBehavior::Immediate => false, + SeekBehavior::Smooth => true, + SeekBehavior::ReaperPreference => Reaper::get().smooth_seek_is_enabled(), + }; + if smooth_seek { + // At the moment, "Smooth seek" with regions always means playing until the end + // of the region. + self.project + .go_to_region_with_smooth_seek(BookmarkRef::Position(self.position)); + } else if let Some(bookmark) = self.find_bookmark() { + with_seek_behavior(SeekBehavior::Immediate, || { + self.project.set_edit_cursor_position( + bookmark.basic_info.position, + SetEditCurPosOptions { + move_view: false, + seek_play: true, + }, + ); + }); + } + if self.set_loop_points || self.set_time_selection { + if let Some(bookmark) = self.find_bookmark() { + if let Some(end_pos) = bookmark.basic_info.region_end_position { + if self.set_loop_points { + self.project.set_loop_points( + bookmark.basic_info.position, + end_pos, + AutoSeekBehavior::DenyAutoSeek, + ); + } + if self.set_time_selection { + self.project + .set_time_selection(bookmark.basic_info.position, end_pos); + } + } + } + } + } + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.project.is_available() + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + // Handled both from control-surface and non-control-surface callbacks. + use CompoundChangeEvent::*; + match evt { + Reaper(ChangeEvent::BookmarksChanged(e)) if e.project == self.project => (true, None), + Additional(AdditionalFeedbackEvent::BeatChanged(e)) if e.project == self.project => { + let v = current_value_of_bookmark( + self.project, + self.bookmark_type, + self.index, + e.new_value, + ); + (true, Some(AbsoluteValue::Continuous(v))) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::GoToBookmark) + } + + fn prop_value(&self, key: &str, _: ControlContext) -> Option { + match key { + "bookmark.color" => { + let res = self.find_bookmark()?; + let reaper_medium::RgbColor { r, g, b } = Reaper::get() + .medium_reaper() + .color_from_native(res.basic_info.color); + Some(PropValue::Color(RgbColor::new(r, g, b))) + } + "bookmark.id" => { + let res = self.find_bookmark()?; + Some(PropValue::Numeric(NumericValue::Discrete( + res.basic_info.id.get() as i32, + ))) + } + "bookmark.index" => { + let res = self.find_bookmark()?; + Some(PropValue::Index(res.index)) + } + "bookmark.index_within_type" => { + let res = self.find_bookmark()?; + Some(PropValue::Index(res.index_within_type)) + } + "bookmark.name" => { + let res = self.find_bookmark()?; + Some(PropValue::Text(res.bookmark.name().into())) + } + _ => None, + } + } +} + +impl<'a> Target<'a> for GoToBookmarkTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = current_value_of_bookmark( + self.project, + self.bookmark_type, + self.index, + self.project + .play_or_edit_cursor_position() + .unwrap_or_default(), + ); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const GO_TO_BOOKMARK_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Bookmark, + name: "Go to", + short_name: "Go to bookmark", + supports_seek_behavior: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/last_touched_target.rs b/plugin-reaper-realearn/main/src/domain/targets/last_touched_target.rs new file mode 100644 index 0000000..91b3abc --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/last_touched_target.rs @@ -0,0 +1,41 @@ +use crate::domain::realearn_target::RealearnTarget; +use crate::domain::{ + Backbone, CompartmentKind, ExtendedProcessorContext, LastTouchedTargetFilter, ReaperTarget, + ReaperTargetType, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::hash_util::NonCryptoHashSet; +use helgobox_api::persistence::TargetTouchCause; + +#[derive(Debug)] +pub struct UnresolvedLastTouchedTarget { + pub included_targets: NonCryptoHashSet, + pub touch_cause: TargetTouchCause, +} + +impl UnresolvedReaperTargetDef for UnresolvedLastTouchedTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let filter = LastTouchedTargetFilter { + included_target_types: &self.included_targets, + touch_cause: self.touch_cause, + }; + let last_touched_target = Backbone::get() + .find_last_touched_target(filter) + .ok_or("no last touched target")?; + if !last_touched_target.is_available(context.control_context()) { + return Err("last touched target gone"); + } + Ok(vec![last_touched_target]) + } +} + +pub const LAST_TOUCHED_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Global, + name: "Last touched", + short_name: "Last touched", + supports_included_targets: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/load_fx_snapshot_target.rs b/plugin-reaper-realearn/main/src/domain/targets/load_fx_snapshot_target.rs new file mode 100644 index 0000000..976f385 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/load_fx_snapshot_target.rs @@ -0,0 +1,151 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + format_value_as_on_off, AdditionalFeedbackEvent, Backbone, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, FxDescriptor, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::hash_util; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; +use reaper_high::{Fx, Project, Track}; +use std::borrow::Cow; +use std::rc::Rc; + +#[derive(Debug)] +pub struct UnresolvedLoadFxSnapshotTarget { + pub fx_descriptor: FxDescriptor, + pub chunk: Rc, +} + +impl UnresolvedReaperTargetDef for UnresolvedLoadFxSnapshotTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let targets = self + .fx_descriptor + .resolve(context, compartment)? + .into_iter() + .map(|fx| { + ReaperTarget::LoadFxSnapshot(LoadFxSnapshotTarget { + fx, + chunk: self.chunk.clone(), + chunk_hash: hash_util::calculate_non_crypto_hash_one_shot( + self.chunk.as_bytes(), + ), + }) + }) + .collect(); + Ok(targets) + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + Some(&self.fx_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LoadFxSnapshotTarget { + pub fx: Fx, + pub chunk: Rc, + pub chunk_hash: u64, +} + +impl RealearnTarget for LoadFxSnapshotTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + return Ok(HitResponse::ignored()); + } + Backbone::target_state().borrow_mut().load_fx_snapshot( + self.fx.clone(), + &self.chunk, + self.chunk_hash, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.fx.is_available() + } + + fn project(&self) -> Option { + self.fx.project() + } + + fn track(&self) -> Option<&Track> { + self.fx.track() + } + + fn fx(&self) -> Option<&Fx> { + Some(&self.fx) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + // We can't provide a value from the event itself because it's on/off depending on + // the mappings which use the FX snapshot target with that FX and which chunk (hash) + // their snapshot has. + CompoundChangeEvent::Additional(AdditionalFeedbackEvent::FxSnapshotLoaded(e)) + if e.fx == self.fx => + { + (true, None) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some( + format_value_as_on_off(self.current_value(context)?.to_unit_value()) + .to_string() + .into(), + ) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::LoadFxSnapshot) + } +} + +impl<'a> Target<'a> for LoadFxSnapshotTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let is_loaded = Backbone::target_state() + .borrow() + .current_fx_snapshot_chunk_hash(&self.fx) + == Some(self.chunk_hash); + Some(AbsoluteValue::Continuous(convert_bool_to_unit_value( + is_loaded, + ))) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const LOAD_FX_SNAPSHOT_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Fx, + name: "Load snapshot", + short_name: "Load FX snapshot", + supports_track: true, + supports_fx: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/load_mapping_snapshot_target.rs b/plugin-reaper-realearn/main/src/domain/targets/load_mapping_snapshot_target.rs new file mode 100644 index 0000000..3aeb6eb --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/load_mapping_snapshot_target.rs @@ -0,0 +1,269 @@ +use crate::domain::{ + CompartmentKind, CompoundChangeEvent, ControlContext, ControlLogContext, + ExtendedProcessorContext, HitInstruction, HitInstructionContext, HitInstructionResponse, + HitResponse, MainMapping, MappingControlContext, MappingControlResult, MappingSnapshotId, + RealearnTarget, ReaperTarget, ReaperTargetType, TagScope, TargetCharacter, TargetSection, + TargetTypeDef, Unit, UnitEvent, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; +use helgobox_api::persistence::MappingSnapshotDescForLoad; + +#[derive(Debug)] +pub struct UnresolvedLoadMappingSnapshotTarget { + pub compartment: CompartmentKind, + /// Mappings which are in the snapshot but not in the tag scope will be ignored. + pub scope: TagScope, + /// If `false`, mappings which are contained in the snapshot but are now inactive + /// due to conditional activation will be ignored. + /// + /// Mappings for which control is disabled are ignored anyway because it would be misleading + /// to load snapshots for them ... a no is a no. That's consequent and can't lead to + /// surprises. It's the same for group interaction. If one wants to use load snapshots for some + /// mappings but don't control them, one can always use the "None" source. + // + // TODO-medium There's one issue though: At the moment, mappings which are completely disabled + // (upper-left checkbox in the row) are also *always* ignored ... same as in group + // interaction! But we wanted to consider "not active due to conditional activation" as + // equivalent to "not active due to completely disabled". Maybe this goal is nonsense. After + // all, one would expect the mapping to be totally disabled when unticking that checkbox, + // "more disabled" than just unchecking "Control". For group interaction, it's + // consistent ATM because it *always* ignores mappings which are inactive due to conditional + // activation. So it ignores completely disabled, control-disabled and + // inactive-due-to-conditional-activation mappings. + // ... So the question is, which consistency is more important: + // a) top-left-checkbox = conditional-activation + // b) top-left-checkbox = control-checkbox + pub active_mappings_only: bool, + pub snapshot_id: VirtualMappingSnapshotIdForLoad, + pub default_value: Option, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum VirtualMappingSnapshotIdForLoad { + Initial, + ById(MappingSnapshotId), +} + +impl VirtualMappingSnapshotIdForLoad { + pub fn id(&self) -> Option<&MappingSnapshotId> { + match self { + VirtualMappingSnapshotIdForLoad::Initial => None, + VirtualMappingSnapshotIdForLoad::ById(id) => Some(id), + } + } +} + +impl TryFrom for VirtualMappingSnapshotIdForLoad { + type Error = &'static str; + + fn try_from(value: MappingSnapshotDescForLoad) -> Result { + let res = match value { + MappingSnapshotDescForLoad::Initial => Self::Initial, + MappingSnapshotDescForLoad::ById { id } => Self::ById(id.parse()?), + }; + Ok(res) + } +} + +impl From for MappingSnapshotDescForLoad { + fn from(value: VirtualMappingSnapshotIdForLoad) -> Self { + match value { + VirtualMappingSnapshotIdForLoad::Initial => Self::Initial, + VirtualMappingSnapshotIdForLoad::ById(s) => Self::ById { id: s.to_string() }, + } + } +} + +impl UnresolvedReaperTargetDef for UnresolvedLoadMappingSnapshotTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::LoadMappingSnapshot( + LoadMappingSnapshotTarget { + compartment: self.compartment, + scope: self.scope.clone(), + active_mappings_only: self.active_mappings_only, + snapshot_id: self.snapshot_id.clone(), + default_value: self.default_value, + }, + )]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LoadMappingSnapshotTarget { + pub compartment: CompartmentKind, + pub scope: TagScope, + pub active_mappings_only: bool, + pub snapshot_id: VirtualMappingSnapshotIdForLoad, + pub default_value: Option, +} + +impl RealearnTarget for LoadMappingSnapshotTarget { + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::LoadMappingSnapshot) + } + + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + return Ok(HitResponse::ignored()); + } + let instruction = LoadMappingSnapshotInstruction { + // So far this clone is okay because loading a snapshot is not something that happens + // every few milliseconds. No need to use a ref to this target. + compartment: self.compartment, + scope: self.scope.clone(), + active_mappings_only: self.active_mappings_only, + snapshot: self.snapshot_id.clone(), + default_value: self.default_value, + }; + Ok(HitResponse::hit_instruction(Box::new(instruction))) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Unit(UnitEvent::MappingSnapshotActivated { + compartment, + tag_scope, + .. + }) if *compartment == self.compartment && self.scope.overlaps_with(tag_scope) => { + (true, None) + } + _ => (false, None), + } + } +} + +impl<'a> Target<'a> for LoadMappingSnapshotTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + let instance_state = context.unit.borrow(); + let is_active = instance_state + .mapping_snapshot_container(self.compartment) + .snapshot_is_active(&self.scope, &self.snapshot_id); + Some(AbsoluteValue::from_bool(is_active)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const LOAD_MAPPING_SNAPSHOT_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Load mapping snapshot", + short_name: "Load mapping snapshot", + supports_tags: true, + ..DEFAULT_TARGET +}; + +struct LoadMappingSnapshotInstruction { + compartment: CompartmentKind, + scope: TagScope, + active_mappings_only: bool, + snapshot: VirtualMappingSnapshotIdForLoad, + default_value: Option, +} + +impl LoadMappingSnapshotInstruction { + fn load_snapshot( + &self, + context: &mut HitInstructionContext, + get_snapshot_value: impl Fn(&MainMapping) -> Option, + ) -> Vec { + context + .mappings + .values_mut() + .filter_map(|m| { + if !m.control_is_enabled() { + return None; + } + if self.scope.has_tags() && !m.has_any_tag(&self.scope.tags) { + return None; + } + if self.active_mappings_only && !m.is_active() { + return None; + } + let snapshot_value = get_snapshot_value(m).or_else(|| { + let default_value = self.default_value?; + // Sometimes we want to consider 0% as "on" and 100% as "off" when loading the + // default value. For example, it's quite common to unmute particular tracks, + // essentially activating them. So we have to reverse the "Track: Mute/unmute" + // target: It should mute at 0% and unmute at 100%. + let effective_value = if m.mode().settings().reverse { + default_value.inverse(None) + } else { + default_value + }; + Some(effective_value) + })?; + context + .domain_event_handler + .notify_mapping_matched(m.compartment(), m.id()); + let res = m.control_from_target_directly( + context.control_context, + context.processor_context, + ControlValue::from_absolute(snapshot_value), + context.basic_settings.target_control_logger( + context.processor_context.control_context.unit, + ControlLogContext::LoadingMappingSnapshot, + m.qualified_id(), + ), + ); + if res.at_least_one_target_was_reached { + m.update_last_non_performance_target_value(snapshot_value); + } + Some(res) + }) + .collect() + } + + fn mark_snapshot_as_active(&self, instance_state: &mut Unit) { + instance_state.mark_snapshot_active(self.compartment, &self.scope, &self.snapshot); + } +} + +impl HitInstruction for LoadMappingSnapshotInstruction { + fn execute(self: Box, mut context: HitInstructionContext) -> HitInstructionResponse { + let results = match &self.snapshot { + VirtualMappingSnapshotIdForLoad::Initial => { + self.load_snapshot(&mut context, |m| m.initial_target_value()) + } + VirtualMappingSnapshotIdForLoad::ById(id) => { + let instance_state = context.control_context.unit.borrow(); + let snapshot_container = + instance_state.mapping_snapshot_container(self.compartment); + let snapshot = snapshot_container.find_snapshot_by_id(id); + self.load_snapshot(&mut context, |m| { + snapshot.and_then(|s| s.find_target_value_by_mapping_id(m.id())) + }) + } + }; + // Mark snapshot as active. + let mut instance_state = context.control_context.unit.borrow_mut(); + self.mark_snapshot_as_active(&mut instance_state); + HitInstructionResponse::CausedEffect(results) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/load_pot_preset_target.rs b/plugin-reaper-realearn/main/src/domain/targets/load_pot_preset_target.rs new file mode 100644 index 0000000..c39a4da --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/load_pot_preset_target.rs @@ -0,0 +1,187 @@ +use crate::domain::{ + Backbone, CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, + FxDescriptor, HitResponse, InstanceStateChanged, MappingControlContext, PotStateChangedEvent, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::blocking_lock_arc; +use derivative::Derivative; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, PropValue, Target}; +use pot::{pot_db, Destination, LoadPresetOptions, PotPreset}; +use reaper_high::{Fx, Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedLoadPotPresetTarget { + pub fx_descriptor: FxDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedLoadPotPresetTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let fxs = self + .fx_descriptor + .resolve(context, compartment)? + .into_iter() + .map(|fx| ReaperTarget::LoadPotPreset(LoadPotPresetTarget { fx })) + .collect(); + Ok(fxs) + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + Some(&self.fx_descriptor) + } +} + +#[derive(Clone, Debug, Derivative)] +#[derivative(Eq, PartialEq)] +pub struct LoadPotPresetTarget { + pub fx: Fx, +} + +impl RealearnTarget for LoadPotPresetTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + let mut instance_state = context.control_context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit()?; + let mut pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from LoadPotPresetTarget 3"); + let preset_id = pot_unit.preset_id().ok_or("no preset selected")?; + let preset = pot_db() + .find_preset_by_id(preset_id) + .ok_or("preset not found")?; + let fx_index = self.fx.index(); + pot_unit + .load_preset_at(&preset, LoadPresetOptions::default(), &|_| { + let dest = Destination { + chain: self.fx.chain().clone(), + fx_index, + }; + Ok(dest) + }) + .map_err(|_| "couldn't load preset")?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, context: ControlContext) -> bool { + if !self.fx.is_available() { + return false; + } + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = match instance_state.pot_unit() { + Ok(u) => u, + Err(_) => return false, + }; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from LoadPotPresetTarget 1"); + match pot_unit.find_currently_selected_preset() { + None => false, + Some(p) => p.common.is_available && p.common.is_supported, + } + } + + fn project(&self) -> Option { + self.fx.project() + } + + fn track(&self) -> Option<&Track> { + self.fx.track() + } + + fn fx(&self) -> Option<&Fx> { + Some(&self.fx) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::LoadPotPreset) + } + + fn can_report_current_value(&self) -> bool { + false + } + + fn prop_value(&self, key: &str, _: ControlContext) -> Option { + self.with_loaded_preset(|p| get_preset_property(p?, key)) + } + + fn text_value(&self, context: ControlContext) -> Option> { + if let PropValue::Text(text) = self.prop_value("preset.name", context)? { + Some(text) + } else { + None + } + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Instance(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::PresetLoaded | PotStateChangedEvent::PresetChanged { .. }, + )) => (true, None), + _ => (false, None), + } + } +} + +impl<'a> Target<'a> for LoadPotPresetTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + None + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +impl LoadPotPresetTarget { + fn with_loaded_preset(&self, f: impl FnOnce(Option<&PotPreset>) -> R) -> R { + match Backbone::target_state() + .borrow() + .current_fx_preset(&self.fx) + { + None => f(None), + Some(p) => f(Some(&p.preset)), + } + } +} + +pub const LOAD_POT_PRESET_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Pot, + name: "Load preset", + short_name: "Load Pot preset", + supports_track: true, + supports_fx: true, + ..DEFAULT_TARGET +}; + +pub fn get_preset_property(p: &PotPreset, key: &str) -> Option { + let value = match key { + "preset.name" => p.common.name.clone().into(), + "preset.product.name" => p.common.product_name.as_ref()?.clone().into(), + "preset.file_ext" => p.kind.file_extension()?.to_string().into(), + "preset.author" => p.common.metadata.author.as_ref()?.clone().into(), + "preset.vendor" => p.common.metadata.vendor.as_ref()?.clone().into(), + "preset.comment" => p.common.metadata.comment.as_ref()?.clone().into(), + _ => return None, + }; + Some(value) +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/midi_send_target.rs b/plugin-reaper-realearn/main/src/domain/targets/midi_send_target.rs new file mode 100644 index 0000000..9145aaa --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/midi_send_target.rs @@ -0,0 +1,373 @@ +use crate::domain::{ + real_time_processor, Caller, CompartmentKind, ControlContext, ControlMainTask, + ExtendedProcessorContext, FeedbackAudioHookTask, FeedbackOutput, FeedbackRealTimeTask, + HitResponse, LogOptions, MappingControlContext, MidiDestination, MidiEvent, + MidiTransformationContainer, RealTimeReaperTarget, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use base::{NamedChannelSender, SenderToNormalThread, SenderToRealTimeThread}; +use helgoboss_learn::{ + create_raw_midi_events_singleton, AbsoluteValue, ControlType, ControlValue, Fraction, + MidiSourceValue, RawMidiPattern, Target, UnitValue, +}; +use helgobox_allocator::permit_alloc; +use helgobox_api::persistence::SendMidiDestination; +use reaper_high::MidiOutputDevice; +use reaper_medium::{MidiInputDeviceId, SendMidiTime}; +use std::convert::TryInto; + +#[derive(Debug)] +pub struct UnresolvedMidiSendTarget { + pub pattern: RawMidiPattern, + pub destination: SendMidiDestination, +} + +impl UnresolvedReaperTargetDef for UnresolvedMidiSendTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::SendMidi(MidiSendTarget::new( + self.pattern.clone(), + self.destination, + ))]) + } + + fn can_be_affected_by_change_events(&self) -> bool { + // We don't want to be refreshed because we maintain an artificial value. + false + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MidiSendTarget { + pattern: RawMidiPattern, + destination: SendMidiDestination, + // For making basic toggle/relative control possible. + artificial_value: AbsoluteValue, +} + +impl MidiSendTarget { + pub fn new(pattern: RawMidiPattern, destination: SendMidiDestination) -> Self { + let max_discrete_value = pattern.max_discrete_value(); + Self { + pattern, + destination, + artificial_value: AbsoluteValue::Discrete(Fraction::new(0, max_discrete_value as _)), + } + } + + pub fn pattern(&self) -> &RawMidiPattern { + &self.pattern + } + + pub fn destination(&self) -> SendMidiDestination { + self.destination + } + + #[allow(clippy::too_many_arguments)] + pub fn midi_send_target_send_midi_in_rt_thread( + &mut self, + caller: Caller, + control_value: ControlValue, + midi_feedback_output: Option, + log_options: LogOptions, + main_task_sender: &SenderToNormalThread, + rt_feedback_sender: &SenderToRealTimeThread, + value_event: MidiEvent, + transformation_container: &mut Option<&mut MidiTransformationContainer>, + ) -> Result<(), &'static str> { + let v = control_value.to_absolute_value()?; + // This is a type of mapping that we should process right here because we want to + // send a MIDI message and this needs to happen in the audio thread. + // Going to the main thread and back would be such a waste! + let raw_midi_event = match self.destination { + SendMidiDestination::FxOutput | SendMidiDestination::FeedbackOutput => { + // The frame offset of the RawMidiEvent is irrelevant in this case. We pass it in other ways. + let raw_midi_event = self.pattern().to_concrete_midi_event(0, v); + let midi_destination = match caller { + Caller::Vst(_) => match self.destination() { + SendMidiDestination::FxOutput => MidiDestination::FxOutput, + SendMidiDestination::FeedbackOutput => { + midi_feedback_output.ok_or("no feedback output set")? + } + _ => unreachable!(), + }, + Caller::AudioHook => match self.destination() { + SendMidiDestination::FxOutput => MidiDestination::FxOutput, + SendMidiDestination::FeedbackOutput => { + midi_feedback_output.ok_or("no feedback output set")? + } + _ => unreachable!(), + }, + }; + match midi_destination { + MidiDestination::FxOutput => { + match caller { + Caller::Vst(_) => { + real_time_processor::send_raw_midi_to_fx_output( + raw_midi_event.bytes(), + value_event.offset(), + caller, + ); + } + Caller::AudioHook => { + // We can't send to FX output here directly. Need to wait until VST processing + // starts (same processing cycle). + rt_feedback_sender.send_complaining( + FeedbackRealTimeTask::NonAllocatingFxOutputFeedback( + raw_midi_event, + value_event.offset(), + ), + ); + } + } + } + MidiDestination::Device(dev_id) => { + MidiOutputDevice::new(dev_id).with_midi_output( + |mo| -> Result<(), &'static str> { + let mo = mo.ok_or("couldn't open MIDI output device")?; + let sample_offset = value_event.offset().get() as u32; + mo.send_msg( + raw_midi_event, + SendMidiTime::AtFrameOffset(sample_offset), + ); + Ok(()) + }, + )?; + } + }; + raw_midi_event + } + SendMidiDestination::InputDevice(d) => { + // The frame offset of the RawMidiEvent is relevant here. It is supposed to be provided in + // MIDI input device frames (1/1024000s of a second), *not* in sample frames! At the very beginning of + // the signal flow we normalized the MIDI input device frames to sample frames according to the + // current device sample rate. Simply because in most cases that's what we need. But now we need + // MIDI input device frames again, so we need to convert it back LOL. + let container = transformation_container.as_mut().ok_or( + "can't send to device input when MIDI doesn't come from device directly", + )?; + let midi_frame = value_event + .offset() + .to_midi_input_frame_offset(container.current_device_sample_rate()); + let raw_midi_event = self.pattern().to_concrete_midi_event(midi_frame, v); + let dev_id = d.device_id.map(MidiInputDeviceId::new); + container.push(dev_id, raw_midi_event); + raw_midi_event + } + }; + // We end up here only if the message was successfully sent + self.artificial_value = v; + if log_options.output_logging_enabled { + permit_alloc(|| { + main_task_sender.send_complaining(ControlMainTask::LogTargetOutput { + event: Box::new(raw_midi_event), + }); + }); + } + Ok(()) + } + + fn control_type_and_character_simple(&self) -> (ControlType, TargetCharacter) { + match self.pattern.step_size() { + None => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + Some(step_size) => { + if self.pattern.resolution() == 1 { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } else { + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: step_size, + is_retriggerable: true, + }, + TargetCharacter::Discrete, + ) + } + } + } + } +} + +impl RealearnTarget for MidiSendTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + self.control_type_and_character_simple() + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + _: ControlContext, + ) -> Result { + let step_size = self.pattern.step_size().ok_or("not supported")?; + let discrete_value = (input.get() / step_size.get()).round() as _; + Ok(discrete_value) + } + + fn format_value_without_unit(&self, value: UnitValue, context: ControlContext) -> String { + if let Ok(discrete_value) = self.convert_unit_value_to_discrete_value(value, context) { + discrete_value.to_string() + } else { + "0".to_owned() + } + } + + fn format_step_size_without_unit( + &self, + step_size: UnitValue, + context: ControlContext, + ) -> String { + if let Ok(discrete_value) = self.convert_unit_value_to_discrete_value(step_size, context) { + discrete_value.to_string() + } else { + "0".to_owned() + } + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "" + } + + fn step_size_unit(&self, _: ControlContext) -> &'static str { + "" + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let value = value.to_absolute_value()?; + // We arrive here only if controlled via OSC, group interaction (as follower), mapping + // snapshot or autoload. Sending MIDI in response to incoming MIDI messages is handled + // directly in the real-time processor. + let resolved_destination = + match self.destination { + SendMidiDestination::FxOutput => MidiDestination::FxOutput, + SendMidiDestination::FeedbackOutput => { + let feedback_output = context + .control_context + .feedback_output + .ok_or("no feedback output set")?; + if let FeedbackOutput::Midi(dest) = feedback_output { + dest + } else { + return Err("feedback output is not MIDI"); + } + } + SendMidiDestination::InputDevice(_) => return Err( + "sending to device input is only possible in response to a MIDI source event coming from a MIDI device", + ), + }; + self.artificial_value = value; + let raw_midi_events = + create_raw_midi_events_singleton(self.pattern.to_concrete_midi_event(0, value)); + context + .control_context + .log_outgoing_target_midi(&raw_midi_events); + match resolved_destination { + MidiDestination::FxOutput => { + let source_value = MidiSourceValue::Raw { + feedback_address_info: None, + events: raw_midi_events, + }; + context + .control_context + .feedback_real_time_task_sender + .send_complaining(FeedbackRealTimeTask::FxOutputFeedback(source_value)); + } + MidiDestination::Device(dev_id) => { + context + .control_context + .feedback_audio_hook_task_sender + .send_complaining(FeedbackAudioHookTask::SendMidi(dev_id, raw_midi_events)); + } + }; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn supports_automatic_feedback(&self) -> bool { + false + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + _: ControlContext, + ) -> Result { + let unit_value = if let Some(step_size) = self.pattern.step_size() { + (value as f64 * step_size.get()).try_into()? + } else { + UnitValue::MIN + }; + Ok(unit_value) + } + + fn splinter_real_time_target(&self) -> Option { + Some(RealTimeReaperTarget::SendMidi(self.clone())) + } + + fn parse_value_from_discrete_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.convert_discrete_value_to_unit_value( + text.parse().map_err(|_| "not a discrete value")?, + context, + ) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::SendMidi) + } +} + +impl Target<'_> for MidiSendTarget { + type Context = (); + + fn current_value(&self, _context: ()) -> Option { + Some(self.artificial_value) + } + + fn control_type(&self, _: Self::Context) -> ControlType { + self.control_type_and_character_simple().0 + } +} + +pub const MIDI_SEND_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Midi, + name: "Send message", + short_name: "Send MIDI", + supports_feedback: false, + supports_real_time_control: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/mod.rs b/plugin-reaper-realearn/main/src/domain/targets/mod.rs new file mode 100644 index 0000000..edfaf9c --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/mod.rs @@ -0,0 +1,203 @@ +mod midi_send_target; +pub use midi_send_target::*; + +mod osc_send_target; +pub use osc_send_target::*; + +mod dummy_target; +pub use dummy_target::*; + +mod mouse_target; +pub use mouse_target::*; + +mod playtime_slot_transport_target; +pub use playtime_slot_transport_target::*; + +mod playtime_column_action_target; +pub use playtime_column_action_target::*; + +mod playtime_row_action_target; +pub use playtime_row_action_target::*; + +mod playtime_matrix_action_target; +pub use playtime_matrix_action_target::*; + +mod playtime_slot_seek_target; +pub use playtime_slot_seek_target::*; + +mod playtime_slot_volume_target; +pub use playtime_slot_volume_target::*; + +mod playtime_slot_management_action_target; +pub use playtime_slot_management_action_target::*; + +mod playtime_control_unit_scroll_target; +pub use playtime_control_unit_scroll_target::*; + +mod playtime_browse_cells_target; +pub use playtime_browse_cells_target::*; + +mod track_peak_target; +pub use track_peak_target::*; + +mod action_target; +pub use action_target::*; + +mod tempo_target; +pub use tempo_target::*; + +mod playrate_target; +pub use playrate_target::*; + +mod automation_mode_override_target; +pub use automation_mode_override_target::*; + +mod fx_parameter_target; +pub use fx_parameter_target::*; + +mod fx_enable_target; +pub use fx_enable_target::*; + +mod fx_online_target; +pub use fx_online_target::*; + +mod fx_open_target; +pub use fx_open_target::*; + +mod fx_preset_target; +pub use fx_preset_target::*; + +mod load_fx_snapshot_target; +pub use load_fx_snapshot_target::*; + +mod browse_tracks_target; +pub use browse_tracks_target::*; + +mod browse_fxs_target; +pub use browse_fxs_target::*; + +mod all_track_fx_enable_target; +pub use all_track_fx_enable_target::*; + +mod transport_target; +pub use transport_target::*; + +mod track_touch_state_target; +pub use track_touch_state_target::*; + +mod go_to_bookmark_target; +pub use go_to_bookmark_target::*; + +mod seek_target; +pub use seek_target::*; + +mod track_volume_target; +pub use track_volume_target::*; + +mod track_tool_target; +pub use track_tool_target::*; + +mod fx_tool_target; +pub use fx_tool_target::*; + +mod route_volume_target; +pub use route_volume_target::*; + +mod route_pan_target; +pub use route_pan_target::*; + +mod route_mute_target; +pub use route_mute_target::*; + +mod route_phase_target; +pub use route_phase_target::*; + +mod route_mono_target; +pub use route_mono_target::*; + +mod route_automation_mode_target; +pub use route_automation_mode_target::*; + +mod route_touch_state_target; +pub use route_touch_state_target::*; + +mod track_pan_target; +pub use track_pan_target::*; + +mod track_width_target; +pub use track_width_target::*; + +mod track_arm_target; +pub use track_arm_target::*; + +mod track_parent_send_target; +pub use track_parent_send_target::*; + +mod track_selection_target; +pub use track_selection_target::*; + +mod track_mute_target; +pub use track_mute_target::*; + +mod track_phase_target; +pub use track_phase_target::*; + +mod track_show_target; +pub use track_show_target::*; + +mod track_solo_target; +pub use track_solo_target::*; + +mod track_automation_mode_target; +pub use track_automation_mode_target::*; + +mod track_monitoring_mode_target; +pub use track_monitoring_mode_target::*; + +mod load_mapping_snapshot_target; +pub use load_mapping_snapshot_target::*; + +mod take_mapping_snapshot_target; +pub use take_mapping_snapshot_target::*; + +mod enable_mappings_target; +pub use enable_mappings_target::*; + +mod modify_mapping_target; +pub use modify_mapping_target::*; + +mod enable_instances_target; +pub use enable_instances_target::*; + +mod enable_units_target; +pub use enable_units_target::*; + +mod browse_group_mappings_target; +pub use browse_group_mappings_target::*; + +mod any_on_target; +pub use any_on_target::*; + +mod last_touched_target; +pub use last_touched_target::*; + +mod fx_parameter_touch_state_target; +pub use fx_parameter_touch_state_target::*; + +mod browse_pot_filter_items_target; +pub use browse_pot_filter_items_target::*; + +mod browse_pot_presets_target; +pub use browse_pot_presets_target::*; + +mod preview_pot_preset_target; +pub use preview_pot_preset_target::*; + +mod load_pot_preset_target; +pub use load_pot_preset_target::*; + +mod compartment_parameter_value_target; +pub use compartment_parameter_value_target::*; + +mod stream_deck_brightness_target; +pub use stream_deck_brightness_target::*; diff --git a/plugin-reaper-realearn/main/src/domain/targets/modify_mapping_target.rs b/plugin-reaper-realearn/main/src/domain/targets/modify_mapping_target.rs new file mode 100644 index 0000000..490da1e --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/modify_mapping_target.rs @@ -0,0 +1,250 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + format_value_as_on_off, AdditionalFeedbackEvent, CompartmentKind, CompoundChangeEvent, + ControlContext, DomainEvent, DomainEventHandler, ExtendedProcessorContext, HitInstruction, + HitInstructionContext, HitInstructionResponse, HitResponse, MappingControlContext, MappingId, + MappingKey, MappingModificationRequestedEvent, QualifiedMappingId, RealearnTarget, + ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, Unit, UnitEvent, + UnitId, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; +use helgobox_api::persistence::MappingModification; +use std::borrow::Cow; +use std::rc::Rc; + +#[derive(Debug)] +pub struct UnresolvedModifyMappingTarget { + pub compartment: CompartmentKind, + pub mapping_ref: MappingRef, + pub modification: MappingModification, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum MappingRef { + OwnMapping { + mapping_id: MappingId, + }, + ForeignMapping { + // We can't use instance ID here because at the time when an unresolved target is created, + // the other instance might not exist yet. We need to resolve the other instance at a later + // point in time. Either at target resolve time (which would be in line with our existing + // stuff) or at control time (which would be simpler and not a performance issue because + // learning a mapping is not something done super frequently). Let's do the latter: Lazy + // resolve at control time. Otherwise we would need to refresh all ReaLearn instances + // whenever an instance was loaded. No important performance gain in this case, + // not worth the added complexity. + session_id: String, + // For the same reason, we can't use mapping ID here. + mapping_key: MappingKey, + }, +} + +impl UnresolvedReaperTargetDef for UnresolvedModifyMappingTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::ModifyMapping(ModifyMappingTarget { + compartment: self.compartment, + modification: self.modification.clone(), + mapping_ref: self.mapping_ref.clone(), + })]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ModifyMappingTarget { + /// This must always correspond to the compartment of the containing mapping, otherwise it will + /// lead to strange behavior. + pub compartment: CompartmentKind, + pub modification: MappingModification, + pub mapping_ref: MappingRef, +} + +impl RealearnTarget for ModifyMappingTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + match &self.modification { + MappingModification::LearnTarget(_) => { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + MappingModification::SetTargetToLastTouched(_) => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + } + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + struct ModifyMappingInstruction { + compartment: CompartmentKind, + instance_id: Option, + mapping_id: MappingId, + modification: MappingModification, + value: ControlValue, + } + impl HitInstruction for ModifyMappingInstruction { + fn execute(self: Box, context: HitInstructionContext) -> HitInstructionResponse { + let event = + DomainEvent::MappingModificationRequested(MappingModificationRequestedEvent { + compartment: self.compartment, + mapping_id: self.mapping_id, + modification: self.modification, + value: self.value, + }); + if let Some(instance_id) = self.instance_id { + if let Some(session) = context + .control_context + .unit_container + .find_session_by_instance_id(instance_id) + { + let session = Rc::downgrade(&session); + session.handle_event_ignoring_error(event) + } + } else { + context + .domain_event_handler + .handle_event_ignoring_error(event) + }; + HitInstructionResponse::CausedEffect(vec![]) + } + } + let (instance_id, mapping_id) = match &self.mapping_ref { + MappingRef::OwnMapping { mapping_id } => (None, *mapping_id), + MappingRef::ForeignMapping { + session_id, + mapping_key, + } => { + let session = context + .control_context + .unit_container + .find_session_by_id(session_id) + .ok_or("other ReaLearn unit not found")?; + let session = session.borrow(); + let mapping_id = session + .find_mapping_id_by_key(self.compartment, mapping_key) + .ok_or("mapping in other ReaLearn unit not found")?; + (Some(session.unit_id()), mapping_id) + } + }; + let instruction = ModifyMappingInstruction { + compartment: self.compartment, + modification: self.modification.clone(), + instance_id, + value, + mapping_id, + }; + Ok(HitResponse::hit_instruction(Box::new(instruction))) + } + + fn can_report_current_value(&self) -> bool { + matches!(&self.modification, MappingModification::LearnTarget(_)) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match &self.modification { + MappingModification::LearnTarget(_) => match evt { + CompoundChangeEvent::Unit(UnitEvent::MappingWhichLearnsTargetChanged { + .. + }) if matches!(&self.mapping_ref, MappingRef::OwnMapping { .. }) => (true, None), + CompoundChangeEvent::Additional(AdditionalFeedbackEvent::Unit { .. }) + if matches!(&self.mapping_ref, MappingRef::ForeignMapping { .. }) => + { + (true, None) + } + _ => (false, None), + }, + MappingModification::SetTargetToLastTouched(_) => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + match &self.modification { + MappingModification::LearnTarget(_) => { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + MappingModification::SetTargetToLastTouched(_) => None, + } + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::ModifyMapping) + } +} + +struct GetArgs<'a> { + instance_state: &'a Unit, + id: QualifiedMappingId, +} + +impl ModifyMappingTarget { + fn get_current_value( + &self, + context: ControlContext, + get: impl FnOnce(GetArgs) -> Option, + ) -> Option { + match &self.mapping_ref { + MappingRef::OwnMapping { mapping_id } => { + let args = GetArgs { + instance_state: &context.unit.borrow(), + id: QualifiedMappingId::new(self.compartment, *mapping_id), + }; + get(args) + } + MappingRef::ForeignMapping { + session_id, + mapping_key, + } => { + let session = context.unit_container.find_session_by_id(session_id)?; + let session = session.borrow(); + let mapping_id = session.find_mapping_id_by_key(self.compartment, mapping_key)?; + let args = GetArgs { + instance_state: &session.unit().borrow(), + id: QualifiedMappingId::new(self.compartment, mapping_id), + }; + get(args) + } + } + } +} + +impl<'a> Target<'a> for ModifyMappingTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: Self::Context) -> Option { + match &self.modification { + MappingModification::LearnTarget(_) => self.get_current_value(context, |args| { + bool_to_current_value(args.instance_state.mapping_is_learning_target(args.id)) + }), + MappingModification::SetTargetToLastTouched(_) => None, + } + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +fn bool_to_current_value(on: bool) -> Option { + Some(AbsoluteValue::Continuous(convert_bool_to_unit_value(on))) +} + +pub const LEARN_MAPPING_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Modify mapping", + short_name: "Modify mapping", + supports_included_targets: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/mouse_target.rs b/plugin-reaper-realearn/main/src/domain/targets/mouse_target.rs new file mode 100644 index 0000000..9b2c7e7 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/mouse_target.rs @@ -0,0 +1,243 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + convert_count_to_step_size, CompartmentKind, ControlContext, ExtendedProcessorContext, + HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, + TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::enigo::EnigoMouse; +use base::{Mouse, MouseCursorPosition}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, Target}; +use helgobox_api::persistence::{Axis, MouseButton}; +use std::fmt::Debug; +use strum::EnumIter; + +#[derive(Debug)] +pub struct UnresolvedMouseTarget { + pub action_type: MouseActionType, + pub axis: Axis, + pub button: MouseButton, +} + +impl UnresolvedReaperTargetDef for UnresolvedMouseTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::Mouse(EnigoMouseTarget { + mouse: EnigoMouse::new(), + action_type: self.action_type, + axis: self.axis, + button: self.button, + })]) + } +} + +pub type EnigoMouseTarget = MouseTarget; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MouseTarget { + mouse: M, + action_type: MouseActionType, + axis: Axis, + button: MouseButton, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + derive_more::Display, + EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum MouseActionType { + #[display(fmt = "Move cursor to")] + MoveTo, + #[display(fmt = "Move cursor by")] + MoveBy, + #[display(fmt = "Press or release button")] + PressOrRelease, + #[display(fmt = "Turn scroll wheel")] + Scroll, +} + +impl Default for MouseActionType { + fn default() -> Self { + Self::MoveTo + } +} + +impl RealearnTarget for MouseTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + use MouseActionType::*; + match self.action_type { + MoveTo => { + let control_type = ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size(self.axis_size()), + is_retriggerable: false, + }; + (control_type, TargetCharacter::Discrete) + } + MoveBy => (ControlType::Relative, TargetCharacter::Discrete), + PressOrRelease => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ), + Scroll => (ControlType::Relative, TargetCharacter::Discrete), + } + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + use MouseActionType::*; + match self.action_type { + MoveTo | MoveBy => self.move_cursor(value), + PressOrRelease => self.click_button(value), + Scroll => self.scroll_wheel(value), + } + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::Mouse) + } + + fn supports_automatic_feedback(&self) -> bool { + false + } + + fn can_report_current_value(&self) -> bool { + use MouseActionType::*; + match self.action_type { + MoveTo | MoveBy => self.mouse.cursor_position().is_ok(), + PressOrRelease => self.mouse.is_pressed(self.button).is_ok(), + Scroll => false, + } + } +} + +impl MouseTarget { + fn axis_size(&self) -> u32 { + self.mouse.axis_size(self.axis) + } + + fn move_cursor(&mut self, value: ControlValue) -> Result { + let instruction = match value { + // Move to pixel + ControlValue::AbsoluteDiscrete(v) => MoveCursorInstruction::To(v.actual()), + // Move by pixels + ControlValue::RelativeDiscrete(v) => MoveCursorInstruction::By(v.get()), + // Move to percentage of canvas + ControlValue::AbsoluteContinuous(v) => { + let axis_size = self.axis_size(); + let new_pos = v.get() * axis_size as f64; + MoveCursorInstruction::To(new_pos.round() as u32) + } + // Move by percentage of canvas + ControlValue::RelativeContinuous(v) => { + let axis_size = self.axis_size(); + let amount = v.get() * axis_size as f64; + MoveCursorInstruction::By(amount.round() as i32) + } + }; + match instruction { + MoveCursorInstruction::To(pos) => { + let current_pos = self.mouse.cursor_position()?; + let new_pos = match self.axis { + Axis::X => MouseCursorPosition::new(pos, current_pos.y), + Axis::Y => MouseCursorPosition::new(current_pos.x, pos), + }; + self.mouse.set_cursor_position(new_pos)?; + } + MoveCursorInstruction::By(delta) => { + let (x_delta, y_delta) = match self.axis { + Axis::X => (delta, 0), + Axis::Y => (0, delta), + }; + self.mouse.adjust_cursor_position(x_delta, y_delta)?; + } + } + Ok(HitResponse::processed_with_effect()) + } + + fn scroll_wheel(&mut self, value: ControlValue) -> Result { + let delta = match value { + ControlValue::RelativeContinuous(v) => v.to_discrete_increment().get(), + ControlValue::RelativeDiscrete(v) => v.get(), + _ => return Err("needs to be controlled relatively"), + }; + self.mouse.scroll(self.axis, delta)?; + Ok(HitResponse::processed_with_effect()) + } + + fn click_button(&mut self, value: ControlValue) -> Result { + if value.is_on() { + self.mouse.press(self.button)?; + } else { + self.mouse.release(self.button)?; + } + Ok(HitResponse::processed_with_effect()) + } +} + +enum MoveCursorInstruction { + To(u32), + By(i32), +} + +impl<'a, M: Mouse> Target<'a> for MouseTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + use MouseActionType::*; + match self.action_type { + MoveTo | MoveBy => { + let axis_size = self.axis_size(); + let pos = self.mouse.cursor_position().ok()?; + let pos_on_axis = get_pos_on_axis(pos, self.axis); + let fraction = Fraction::new(pos_on_axis, axis_size); + Some(AbsoluteValue::Discrete(fraction)) + } + PressOrRelease => { + let is_pressed = self.mouse.is_pressed(self.button).ok()?; + Some(AbsoluteValue::Continuous(convert_bool_to_unit_value( + is_pressed, + ))) + } + Scroll => None, + } + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +fn get_pos_on_axis(pos: MouseCursorPosition, axis: Axis) -> u32 { + match axis { + Axis::X => pos.x, + Axis::Y => pos.y, + } +} + +pub const MOUSE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Global, + name: "Mouse", + short_name: "Mouse", + #[cfg(target_os = "macos")] + hint: "Needs macOS accessibility permissions", + supports_axis: true, + supports_mouse_button: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/osc_send_target.rs b/plugin-reaper-realearn/main/src/domain/targets/osc_send_target.rs new file mode 100644 index 0000000..663b16f --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/osc_send_target.rs @@ -0,0 +1,181 @@ +use crate::domain::ui_util::{format_osc_message, log_target_output}; +use crate::domain::{ + CompartmentKind, ControlContext, ExtendedProcessorContext, FeedbackOutput, HitResponse, + MappingControlContext, OscDeviceId, OscFeedbackTask, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use base::NamedChannelSender; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, FeedbackValue, NumericFeedbackValue, + OscArgDescriptor, OscTypeTag, Target, +}; +use rosc::OscMessage; + +#[derive(Debug)] +pub struct UnresolvedOscSendTarget { + pub address_pattern: String, + pub arg_descriptor: Option, + pub device_id: Option, +} + +impl UnresolvedReaperTargetDef for UnresolvedOscSendTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::SendOsc(OscSendTarget::new( + self.address_pattern.clone(), + self.arg_descriptor, + self.device_id, + ))]) + } + + fn can_be_affected_by_change_events(&self) -> bool { + // We don't want to be refreshed because we maintain an artificial value. + false + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct OscSendTarget { + address_pattern: String, + arg_descriptor: Option, + device_id: Option, + // For making basic toggle/relative control possible. + artificial_value: AbsoluteValue, +} + +impl OscSendTarget { + pub fn new( + address_pattern: String, + arg_descriptor: Option, + device_id: Option, + ) -> Self { + Self { + address_pattern, + arg_descriptor, + device_id, + artificial_value: Default::default(), + } + } + + pub fn address_pattern(&self) -> &str { + &self.address_pattern + } + + pub fn arg_descriptor(&self) -> Option { + self.arg_descriptor + } + + pub fn device_id(&self) -> Option { + self.device_id + } +} + +impl RealearnTarget for OscSendTarget { + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::SendOsc) + } + + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + if let Some(desc) = self.arg_descriptor { + use OscTypeTag::*; + match desc.type_tag() { + Float | Double => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Continuous, + ), + Bool => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ), + Nil | Inf => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + _ => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + } + } else { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + let msg = OscMessage { + addr: self.address_pattern.clone(), + args: if let Some(desc) = self.arg_descriptor { + let fb_value = + NumericFeedbackValue::new(Default::default(), AbsoluteValue::Continuous(value)); + desc.to_concrete_args(FeedbackValue::Numeric(fb_value)) + .ok_or("sending of this OSC type not supported")? + } else { + vec![] + }, + }; + let effective_dev_id = self + .device_id + .or_else(|| { + if let FeedbackOutput::Osc(dev_id) = context.control_context.feedback_output? { + Some(dev_id) + } else { + None + } + }) + .ok_or("no destination device for sending OSC")?; + if context.control_context.output_logging_enabled { + let text = format!( + "Device {} | {}", + effective_dev_id.fmt_short(), + format_osc_message(&msg) + ); + log_target_output(context.control_context.unit_id, text); + } + context + .control_context + .osc_feedback_task_sender + .send_complaining(OscFeedbackTask::new(effective_dev_id, msg)); + self.artificial_value = AbsoluteValue::Continuous(value); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn supports_automatic_feedback(&self) -> bool { + false + } +} + +impl<'a> Target<'a> for OscSendTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + Some(self.artificial_value) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const OSC_SEND_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Osc, + name: "Send message", + short_name: "Send OSC", + supports_feedback: false, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/playrate_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playrate_target.rs new file mode 100644 index 0000000..d5df540 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playrate_target.rs @@ -0,0 +1,154 @@ +use crate::domain::{ + format_step_size_as_playback_speed_factor_without_unit, + format_value_as_playback_speed_factor_without_unit, parse_step_size_from_playback_speed_factor, + parse_value_from_playback_speed_factor, playback_speed_factor_span, playrate_unit_value, + CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, NumericValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, PlayRate, Project}; +use reaper_medium::NormalizedPlayRate; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedPlayrateTarget; + +impl UnresolvedReaperTargetDef for UnresolvedPlayrateTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::Playrate(PlayrateTarget { + project: context.context().project_or_current_project(), + })]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PlayrateTarget { + pub project: Project, +} + +impl RealearnTarget for PlayrateTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRoundable { + rounding_step_size: UnitValue::new(1.0 / (playback_speed_factor_span() * 100.0)), + }, + TargetCharacter::Continuous, + ) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_playback_speed_factor(text) + } + + fn parse_as_step_size(&self, text: &str, _: ControlContext) -> Result { + parse_step_size_from_playback_speed_factor(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_playback_speed_factor_without_unit(value) + } + + fn format_step_size_without_unit(&self, step_size: UnitValue, _: ControlContext) -> String { + format_step_size_as_playback_speed_factor_without_unit(step_size) + } + + fn hide_formatted_value(&self, _: ControlContext) -> bool { + true + } + + fn hide_formatted_step_size(&self, _: ControlContext) -> bool { + true + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "x" + } + + fn step_size_unit(&self, _: ControlContext) -> &'static str { + "x" + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let play_rate = + PlayRate::from_normalized_value(NormalizedPlayRate::new(value.to_unit_value()?.get())); + self.project.set_play_rate(play_rate); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.project.is_available() + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::MasterPlayRateChanged(e)) + if e.project == self.project => + { + ( + true, + Some(AbsoluteValue::Continuous(playrate_unit_value( + PlayRate::from_playback_speed_factor(e.new_value), + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(format!("{:.2}", self.playrate().playback_speed_factor().get()).into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal( + self.playrate().playback_speed_factor().get(), + )) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlayRate) + } +} + +impl PlayrateTarget { + fn playrate(&self) -> PlayRate { + self.project.play_rate() + } +} + +impl<'a> Target<'a> for PlayrateTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = playrate_unit_value(self.playrate()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const PLAYRATE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Project, + name: "Set playrate", + short_name: "Playrate", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_browse_cells_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_browse_cells_target.rs new file mode 100644 index 0000000..2e0f28d --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_browse_cells_target.rs @@ -0,0 +1,203 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; + +use helgobox_api::persistence::Axis; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeBrowseCellsTarget { + pub axis: Axis, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeBrowseCellsTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeBrowseCellsTarget { axis: self.axis }; + Ok(vec![ReaperTarget::PlaytimeBrowseCells(target)]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PlaytimeBrowseCellsTarget { + axis: Axis, +} + +pub const PLAYTIME_BROWSE_CELLS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Browse cells", + short_name: "Playtime browse cells", + supports_axis: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ControlContext, PlaytimeBrowseCellsTarget, RealearnTarget}; + use helgoboss_learn::Target; + + impl RealearnTarget for PlaytimeBrowseCellsTarget {} + impl<'a> Target<'a> for PlaytimeBrowseCellsTarget { + type Context = ControlContext<'a>; + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::{ + convert_count_to_step_size, convert_unit_to_discrete_value_with_none, CompoundChangeEvent, + ControlContext, HitResponse, MappingControlContext, PlaytimeBrowseCellsTarget, + RealearnTarget, ReaperTargetType, TargetCharacter, + }; + use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, Target, UnitValue}; + use helgobox_api::persistence::Axis; + use playtime_api::runtime::CellAddress; + #[cfg(feature = "playtime")] + use playtime_clip_engine::base::ClipMatrixEvent; + + impl PlaytimeBrowseCellsTarget { + fn column_or_row_count(&self, context: ControlContext) -> u32 { + context + .instance() + .borrow() + .clip_matrix() + .map(|m| match self.axis { + Axis::X => m.column_count(), + Axis::Y => m.row_count(), + } as u32) + .unwrap_or(0) + } + + fn calculate_value( + &self, + context: ControlContext, + cell_address: CellAddress, + ) -> Option { + let index = match self.axis { + Axis::X => cell_address.column_index, + Axis::Y => cell_address.row_index, + }; + let v = if let Some(i) = index { i + 1 } else { 0 }; + let fraction = Fraction::new(v as u32, self.column_or_row_count(context)); + Some(AbsoluteValue::Discrete(fraction)) + } + } + + impl RealearnTarget for PlaytimeBrowseCellsTarget { + fn control_type_and_character( + &self, + context: ControlContext, + ) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size( + self.column_or_row_count(context) + 1, + ), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let new_value = match value.to_absolute_value()? { + AbsoluteValue::Continuous(v) => convert_unit_value_to_target_value( + v, + self.column_or_row_count(context.control_context), + ), + AbsoluteValue::Discrete(f) => f.actual(), + }; + let new_index = if new_value == 0 { + None + } else { + Some(new_value as usize - 1) + }; + let mut instance = context.control_context.instance.borrow_mut(); + let matrix = instance.clip_matrix_mut().ok_or("no matrix")?; + let current_cell = matrix.active_cell(); + let new_cell = match self.axis { + Axis::X => CellAddress::new(new_index, current_cell.row_index), + Axis::Y => CellAddress::new(current_cell.column_index, new_index), + }; + matrix + .activate_cell(new_cell) + .map_err(|_| "cell doesn't exist")?; + Ok(HitResponse::processed_with_effect()) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _context: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::ClipMatrix( + ClipMatrixEvent::EverythingChanged | ClipMatrixEvent::ActiveCellChanged, + ) => (true, None), + _ => (false, None), + } + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeBrowseCells) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn parse_as_value( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn parse_as_step_size( + &self, + text: &str, + context: ControlContext, + ) -> Result { + self.parse_value_from_discrete_value(text, context) + } + + fn convert_unit_value_to_discrete_value( + &self, + input: UnitValue, + context: ControlContext, + ) -> Result { + let value = + convert_unit_value_to_target_value(input, self.column_or_row_count(context)); + Ok(value) + } + } + + impl<'a> Target<'a> for PlaytimeBrowseCellsTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + let instance = context.instance.borrow(); + let matrix = instance.clip_matrix()?; + let active_cell = matrix.active_cell(); + self.calculate_value(context, active_cell) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } + + fn convert_unit_value_to_target_value(input: UnitValue, column_or_row_count: u32) -> u32 { + convert_unit_to_discrete_value_with_none(input, column_or_row_count) + .map(|i| i + 1) + .unwrap_or(0) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_column_action_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_column_action_target.rs new file mode 100644 index 0000000..8d968f7 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_column_action_target.rs @@ -0,0 +1,295 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, VirtualPlaytimeColumn, DEFAULT_TARGET, +}; + +use helgobox_api::persistence::PlaytimeColumnAction; + +pub const PLAYTIME_COLUMN_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Column action", + short_name: "Playtime column action", + supports_real_time_control: true, + ..DEFAULT_TARGET +}; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeColumnActionTarget { + pub column: VirtualPlaytimeColumn, + pub action: PlaytimeColumnAction, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeColumnActionTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeColumnActionTarget { + column_index: self.column.resolve(context, compartment)?, + action: self.action, + }; + Ok(vec![ReaperTarget::PlaytimeColumnAction(target)]) + } + + fn clip_column_descriptor(&self) -> Option<&VirtualPlaytimeColumn> { + Some(&self.column) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PlaytimeColumnActionTarget { + pub column_index: usize, + pub action: PlaytimeColumnAction, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RealTimePlaytimeColumnTarget { + column_index: usize, + action: PlaytimeColumnAction, +} + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ + ControlContext, PlaytimeColumnActionTarget, RealTimeControlContext, + RealTimePlaytimeColumnTarget, RealearnTarget, + }; + use helgoboss_learn::{ControlValue, Target}; + + impl RealearnTarget for PlaytimeColumnActionTarget {} + impl<'a> Target<'a> for PlaytimeColumnActionTarget { + type Context = ControlContext<'a>; + } + impl<'a> Target<'a> for RealTimePlaytimeColumnTarget { + type Context = RealTimeControlContext<'a>; + } + impl RealTimePlaytimeColumnTarget { + pub fn hit( + &mut self, + _value: ControlValue, + _context: RealTimeControlContext, + ) -> Result<(), &'static str> { + Err("Playtime not available") + } + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::{ + format_value_as_on_off, Backbone, CompoundChangeEvent, ControlContext, HitResponse, + MappingControlContext, PlaytimeColumnActionTarget, RealTimeControlContext, + RealTimePlaytimeColumnTarget, RealTimeReaperTarget, RealearnTarget, ReaperTargetType, + TargetCharacter, + }; + use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; + use helgobox_api::persistence::PlaytimeColumnAction; + use playtime_api::persistence::ColumnAddress; + use playtime_api::runtime::CellAddress; + use playtime_clip_engine::base::ClipMatrixEvent; + use playtime_clip_engine::rt::{QualifiedSlotChangeEvent, SlotChangeEvent}; + use std::borrow::Cow; + + impl<'a> Target<'a> for PlaytimeColumnActionTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + let is_on = Backbone::get() + .with_clip_matrix(context.instance(), |matrix| match self.action { + PlaytimeColumnAction::Stop => matrix.column_is_stoppable(self.column_index), + PlaytimeColumnAction::ArmState | PlaytimeColumnAction::ArmStateExclusive => { + matrix.column_is_armed_for_recording(self.column_index) + } + PlaytimeColumnAction::Activate => { + matrix.active_cell().column_index == Some(self.column_index) + } + }) + .ok()?; + Some(AbsoluteValue::from_bool(is_on)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } + + impl<'a> Target<'a> for RealTimePlaytimeColumnTarget { + type Context = RealTimeControlContext<'a>; + + fn current_value(&self, context: RealTimeControlContext<'a>) -> Option { + match self.action { + PlaytimeColumnAction::Stop => { + let matrix = context.clip_matrix().ok()?; + let matrix = matrix.lock(); + let is_stoppable = matrix.column_is_stoppable(self.column_index); + Some(AbsoluteValue::from_bool(is_stoppable)) + } + PlaytimeColumnAction::ArmState + | PlaytimeColumnAction::ArmStateExclusive + | PlaytimeColumnAction::Activate => None, + } + } + + fn control_type(&self, _: RealTimeControlContext<'a>) -> ControlType { + control_type_and_character(self.action).0 + } + } + + impl RealearnTarget for PlaytimeColumnActionTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + control_type_and_character(self.action) + } + + fn clip_column_address(&self) -> Option { + Some(ColumnAddress::new(self.column_index)) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let response = Backbone::get() + .with_clip_matrix_mut( + context.control_context.instance(), + |matrix| -> anyhow::Result { + match self.action { + PlaytimeColumnAction::Stop => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.stop_column(self.column_index); + } + PlaytimeColumnAction::ArmState => { + matrix.set_column_armed(self.column_index, value.is_on())?; + } + PlaytimeColumnAction::ArmStateExclusive => { + matrix.set_column_armed_exclusively( + self.column_index, + value.is_on(), + )?; + } + PlaytimeColumnAction::Activate => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.activate_cell(CellAddress::new( + Some(self.column_index), + None, + ))?; + } + } + Ok(HitResponse::processed_with_effect()) + }, + ) + .map_err(|_| "couldn't acquire matrix")? + .map_err(|_| "couldn't carry out column action")?; + Ok(response) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + if matches!( + evt, + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::EverythingChanged) + ) { + return (true, None); + } + match self.action { + PlaytimeColumnAction::Stop => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::SlotChanged( + QualifiedSlotChangeEvent { + slot_address: sc, + event, + }, + )) if sc.column() == self.column_index => match event { + SlotChangeEvent::PlayState(_) => (true, None), + SlotChangeEvent::Clips(_) => (true, None), + _ => (false, None), + }, + _ => (false, None), + }, + PlaytimeColumnAction::ArmState | PlaytimeColumnAction::ArmStateExclusive => { + match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::TrackChanged(_)) => { + (true, None) + } + _ => (false, None), + } + } + PlaytimeColumnAction::Activate => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::ActiveCellChanged) => { + (true, None) + } + _ => (false, None), + }, + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeColumnAction) + } + + fn splinter_real_time_target(&self) -> Option { + if !matches!(self.action, PlaytimeColumnAction::Stop) { + return None; + } + let t = RealTimePlaytimeColumnTarget { + column_index: self.column_index, + action: self.action, + }; + Some(RealTimeReaperTarget::PlaytimeColumn(t)) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + } + + fn control_type_and_character(action: PlaytimeColumnAction) -> (ControlType, TargetCharacter) { + use PlaytimeColumnAction::*; + match action { + Stop | Activate => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + ArmState | ArmStateExclusive => { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + } + } + + impl RealTimePlaytimeColumnTarget { + pub fn hit( + &mut self, + value: ControlValue, + context: RealTimeControlContext, + ) -> Result<(), &'static str> { + match self.action { + PlaytimeColumnAction::Stop => { + if !value.is_on() { + return Ok(()); + } + let matrix = context.clip_matrix()?; + let mut matrix = matrix.lock(); + matrix.stop_column(self.column_index) + } + PlaytimeColumnAction::ArmState + | PlaytimeColumnAction::ArmStateExclusive + | PlaytimeColumnAction::Activate => Err("real-time control not supported"), + } + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_control_unit_scroll_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_control_unit_scroll_target.rs new file mode 100644 index 0000000..95847eb --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_control_unit_scroll_target.rs @@ -0,0 +1,176 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; + +use helgobox_api::persistence::Axis; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeControlUnitScrollTarget { + pub axis: Axis, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeControlUnitScrollTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeControlUnitScrollTarget { axis: self.axis }; + Ok(vec![ReaperTarget::PlaytimeControlUnitScroll(target)]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PlaytimeControlUnitScrollTarget { + axis: Axis, +} + +pub const PLAYTIME_CONTROL_UNIT_SCROLL_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Control unit scroll", + short_name: "Playtime scroll", + supports_axis: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ControlContext, PlaytimeControlUnitScrollTarget, RealearnTarget}; + use helgoboss_learn::Target; + + impl RealearnTarget for PlaytimeControlUnitScrollTarget {} + impl<'a> Target<'a> for PlaytimeControlUnitScrollTarget { + type Context = ControlContext<'a>; + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::{ + convert_count_to_step_size, convert_unit_to_discrete_value, CompoundChangeEvent, + ControlContext, HitResponse, MappingControlContext, PlaytimeControlUnitScrollTarget, + RealearnTarget, ReaperTargetType, TargetCharacter, UnitEvent, + }; + use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, Target}; + use helgobox_api::persistence::Axis; + use playtime_api::persistence::SlotAddress; + #[cfg(feature = "playtime")] + use playtime_clip_engine::base::ClipMatrixEvent; + + impl PlaytimeControlUnitScrollTarget { + fn value_count(&self, context: ControlContext) -> u32 { + let total_count = context + .instance() + .borrow() + .clip_matrix() + .map(|m| match self.axis { + Axis::X => m.column_count(), + Axis::Y => m.row_count(), + } as u32) + .unwrap_or(0); + let unit = context.unit.borrow(); + let control_unit_count = match self.axis { + Axis::X => unit.control_unit_column_count(), + Axis::Y => unit.control_unit_row_count(), + }; + (total_count + 1).saturating_sub(control_unit_count) + } + + fn calculate_value( + &self, + context: ControlContext, + slot_address: SlotAddress, + ) -> Option { + let index = match self.axis { + Axis::X => slot_address.column_index, + Axis::Y => slot_address.row_index, + }; + let count = self.value_count(context); + if count < 2 { + // The count is 1 if the control unit column/row count fits exactly the size of the matrix. + // It's 0 if the matrix is even smaller. In both cases, navigation is pointless, so we should return + // `None`. + return None; + } + Some(AbsoluteValue::Discrete(Fraction::new( + index as u32, + count.saturating_sub(1), + ))) + } + } + + impl RealearnTarget for PlaytimeControlUnitScrollTarget { + fn control_type_and_character( + &self, + context: ControlContext, + ) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size(self.value_count(context)), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let new_index = match value.to_absolute_value()? { + AbsoluteValue::Continuous(v) => { + convert_unit_to_discrete_value(v, self.value_count(context.control_context)) + } + AbsoluteValue::Discrete(f) => f.actual(), + }; + let mut unit = context.control_context.unit.borrow_mut(); + let current_value = unit.control_unit_top_left_corner(); + let new_value = match self.axis { + Axis::X => SlotAddress::new(new_index as usize, current_value.row_index), + Axis::Y => SlotAddress::new(current_value.column_index, new_index as usize), + }; + unit.set_control_unit_top_left_corner(new_value); + Ok(HitResponse::processed_with_effect()) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + context: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::ClipMatrix( + ClipMatrixEvent::EverythingChanged | ClipMatrixEvent::ControlUnitsChanged, + ) => (true, None), + CompoundChangeEvent::Unit(UnitEvent::ControlUnitTopLeftCornerChanged(address)) => { + (true, self.calculate_value(context, *address)) + } + _ => (false, None), + } + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeControlUnitScroll) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + } + + impl<'a> Target<'a> for PlaytimeControlUnitScrollTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + let unit = context.unit.borrow(); + let current_value = unit.control_unit_top_left_corner(); + self.calculate_value(context, current_value) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_matrix_action_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_matrix_action_target.rs new file mode 100644 index 0000000..ae1087a --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_matrix_action_target.rs @@ -0,0 +1,573 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgobox_api::persistence::PlaytimeMatrixAction; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeMatrixActionTarget { + pub action: PlaytimeMatrixAction, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeMatrixActionTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeMatrixActionTarget { + action: self.action, + }; + Ok(vec![ReaperTarget::PlaytimeMatrixAction(target)]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PlaytimeMatrixActionTarget { + pub action: PlaytimeMatrixAction, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RealTimePlaytimeMatrixTarget { + action: PlaytimeMatrixAction, +} + +pub const PLAYTIME_MATRIX_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Matrix action", + short_name: "Playtime matrix action", + supports_real_time_control: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ + ControlContext, PlaytimeMatrixActionTarget, RealTimeControlContext, + RealTimePlaytimeMatrixTarget, RealearnTarget, + }; + use helgoboss_learn::{ControlValue, Target}; + + impl RealearnTarget for PlaytimeMatrixActionTarget {} + impl<'a> Target<'a> for PlaytimeMatrixActionTarget { + type Context = ControlContext<'a>; + } + impl<'a> Target<'a> for RealTimePlaytimeMatrixTarget { + type Context = RealTimeControlContext<'a>; + } + impl RealTimePlaytimeMatrixTarget { + pub fn hit( + &mut self, + _value: ControlValue, + _context: RealTimeControlContext, + ) -> Result { + Err("Playtime not available") + } + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::{ + convert_count_to_step_size, format_value_as_on_off, Backbone, CompoundChangeEvent, + ControlContext, HitResponse, MappingControlContext, PlaytimeMatrixActionTarget, + RealTimeControlContext, RealTimePlaytimeMatrixTarget, RealTimeReaperTarget, RealearnTarget, + ReaperTargetType, TargetCharacter, + }; + use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, Target, UnitValue}; + + use helgobox_api::persistence::PlaytimeMatrixAction; + use playtime_api::persistence::{EvenQuantization, RecordLengthMode}; + use playtime_clip_engine::base::{ClipMatrixEvent, Matrix, SequencerStatus}; + use playtime_clip_engine::rt::{QualifiedSlotChangeEvent, SlotChangeEvent}; + + use std::borrow::Cow; + + impl PlaytimeMatrixActionTarget { + fn invoke(&self, matrix: &mut Matrix, value: ControlValue) -> anyhow::Result { + match self.action { + PlaytimeMatrixAction::Stop => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.stop(); + } + PlaytimeMatrixAction::Undo => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + let _ = matrix.undo(); + } + PlaytimeMatrixAction::Redo => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + let _ = matrix.redo(); + } + PlaytimeMatrixAction::BuildScene => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.build_scene_in_first_empty_row()?; + } + PlaytimeMatrixAction::SetRecordLengthMode => { + let mode = convert_control_value_to_record_length_mode(value)?; + matrix.set_record_length_mode(mode); + } + PlaytimeMatrixAction::SetCustomRecordLengthInBars => { + let value = value + .to_discrete_value(RECORD_LENGTH_BARS_COUNT) + .map_err(anyhow::Error::msg)?; + let quantization = EvenQuantization::new(value.actual() + 1, 1)?; + matrix.set_custom_record_length(quantization); + } + PlaytimeMatrixAction::ClickOnOffState => { + matrix.set_click_enabled(value.is_on()); + } + PlaytimeMatrixAction::MidiAutoQuantizationOnOffState => { + matrix.set_midi_auto_quantize_enabled(value.is_on()); + } + PlaytimeMatrixAction::SmartRecord => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.trigger_smart_record(true)?; + } + PlaytimeMatrixAction::StartOrStopPlayback => { + if value.is_on() { + matrix.play_all_ignited(); + } else { + matrix.enter_silence_mode(); + } + } + PlaytimeMatrixAction::SilenceModeOnOffState => { + if value.is_on() { + matrix.enter_silence_mode(); + } else { + matrix.leave_silence_mode(); + } + } + PlaytimeMatrixAction::Panic => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.panic(); + } + PlaytimeMatrixAction::SequencerRecordOnOffState => { + if value.is_on() { + matrix.record_new_sequence(); + } else { + matrix.stop_sequencer(); + } + } + PlaytimeMatrixAction::SequencerPlayOnOffState => { + if value.is_on() { + matrix.play_active_sequence()?; + } else { + matrix.stop_sequencer(); + } + } + PlaytimeMatrixAction::TapTempo => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.tap_tempo(); + } + } + Ok(HitResponse::processed_with_effect()) + } + } + + impl RealearnTarget for PlaytimeMatrixActionTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + control_type_and_character(self.action) + } + + fn format_value_without_unit(&self, value: UnitValue, context: ControlContext) -> String { + match self.action { + PlaytimeMatrixAction::SetRecordLengthMode => { + convert_control_value_to_record_length_mode(ControlValue::AbsoluteContinuous( + value, + )) + .map(|m| serde_plain::to_string(&m).unwrap()) + .unwrap_or_else(|_| "".to_string()) + } + PlaytimeMatrixAction::SetCustomRecordLengthInBars => { + self.format_as_discrete_or_percentage(value, context) + } + _ => format_value_as_on_off(value).to_string(), + } + } + + fn convert_unit_value_to_discrete_value( + &self, + value: UnitValue, + _context: ControlContext, + ) -> Result { + match self.action { + PlaytimeMatrixAction::SetRecordLengthMode => { + Ok(value.to_discrete(RECORD_LENGTH_MODES_COUNT - 1)) + } + PlaytimeMatrixAction::SetCustomRecordLengthInBars => { + Ok(value.to_discrete(RECORD_LENGTH_BARS_COUNT - 1) + 1) + } + _ => Err("not supported"), + } + } + + fn convert_discrete_value_to_unit_value( + &self, + value: u32, + _context: ControlContext, + ) -> Result { + match self.action { + PlaytimeMatrixAction::SetRecordLengthMode => { + let uv = value as f64 / (RECORD_LENGTH_MODES_COUNT - 1) as f64; + Ok(UnitValue::new_clamped(uv)) + } + PlaytimeMatrixAction::SetCustomRecordLengthInBars => { + convert_record_length_numerator_to_unit_value(value) + } + _ => Err("not supported"), + } + } + + fn parse_as_value( + &self, + text: &str, + _context: ControlContext, + ) -> Result { + match self.action { + PlaytimeMatrixAction::SetRecordLengthMode => { + let mode: RecordLengthMode = + serde_plain::from_str(text).map_err(|_| "invalid mode")?; + let uv = UnitValue::new_clamped( + mode as usize as f64 / (RECORD_LENGTH_MODES_COUNT - 1) as f64, + ); + Ok(uv) + } + PlaytimeMatrixAction::SetCustomRecordLengthInBars => { + let numerator = text.parse().map_err(|_| "no valid number")?; + convert_record_length_numerator_to_unit_value(numerator) + } + _ => Err("not supported"), + } + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let mut instance = context.control_context.instance().borrow_mut(); + let matrix = instance + .clip_matrix_mut() + .ok_or("couldn't acquire matrix")?; + let response = self + .invoke(matrix, value) + .map_err(|_| "couldn't carry out matrix action")?; + Ok(response) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + if matches!( + evt, + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::EverythingChanged) + ) { + return (true, None); + } + match self.action { + PlaytimeMatrixAction::Stop | PlaytimeMatrixAction::BuildScene => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::SlotChanged( + QualifiedSlotChangeEvent { event, .. }, + )) => match event { + SlotChangeEvent::PlayState(_) => (true, None), + SlotChangeEvent::Clips(_) => (true, None), + _ => (false, None), + }, + _ => (false, None), + }, + PlaytimeMatrixAction::SetRecordLengthMode + | PlaytimeMatrixAction::SetCustomRecordLengthInBars => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::MatrixSettingsChanged) => { + (true, None) + } + _ => (false, None), + }, + PlaytimeMatrixAction::ClickOnOffState => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::ClickEnabledChanged) => { + (true, None) + } + _ => (false, None), + }, + PlaytimeMatrixAction::MidiAutoQuantizationOnOffState => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::MatrixSettingsChanged) => { + (true, None) + } + _ => (false, None), + }, + PlaytimeMatrixAction::SmartRecord => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::SlotChanged( + QualifiedSlotChangeEvent { + event: SlotChangeEvent::PlayState(_), + .. + }, + )) => (true, None), + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::SequencerPlayStateChanged) => { + (true, None) + } + _ => (false, None), + }, + PlaytimeMatrixAction::StartOrStopPlayback + | PlaytimeMatrixAction::SilenceModeOnOffState => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::SilenceModeChanged) => { + (true, None) + } + _ => (false, None), + }, + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeMatrixAction) + } + + fn splinter_real_time_target(&self) -> Option { + use PlaytimeMatrixAction::*; + let supports_rt_control = matches!( + self.action, + Stop | SmartRecord | StartOrStopPlayback | SilenceModeOnOffState | Panic | TapTempo + ); + if !supports_rt_control { + return None; + } + let t = RealTimePlaytimeMatrixTarget { + action: self.action, + }; + Some(RealTimeReaperTarget::PlaytimeMatrix(t)) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn value_unit(&self, context: ControlContext) -> &'static str { + match self.action { + PlaytimeMatrixAction::SetCustomRecordLengthInBars => "bars", + _ => self.value_unit_default(context), + } + } + } + impl<'a> Target<'a> for PlaytimeMatrixActionTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let bool_value = match self.action { + PlaytimeMatrixAction::Stop | PlaytimeMatrixAction::BuildScene => { + matrix.is_stoppable() + } + PlaytimeMatrixAction::Undo => matrix.can_undo(), + PlaytimeMatrixAction::Redo => matrix.can_redo(), + PlaytimeMatrixAction::SetRecordLengthMode => { + let value = matrix.settings().clip_record_settings.length_mode as usize; + let fraction = Fraction::new(value as _, RECORD_LENGTH_MODES_COUNT - 1); + return Some(AbsoluteValue::Discrete(fraction)); + } + PlaytimeMatrixAction::SetCustomRecordLengthInBars => { + let custom_length = + matrix.settings().clip_record_settings.custom_length; + if custom_length.denominator() != 1 { + return None; + } + let fraction = Fraction::new( + custom_length.numerator() - 1, + RECORD_LENGTH_BARS_COUNT - 1, + ); + return Some(AbsoluteValue::Discrete(fraction)); + } + PlaytimeMatrixAction::ClickOnOffState => matrix.click_is_enabled(), + PlaytimeMatrixAction::MidiAutoQuantizationOnOffState => { + matrix.midi_auto_quantize_enabled() + } + PlaytimeMatrixAction::SmartRecord => { + matrix.sequencer().status() == SequencerStatus::Recording + || matrix.num_really_recording_clips() > 0 + } + PlaytimeMatrixAction::StartOrStopPlayback => !matrix.is_in_silence_mode(), + PlaytimeMatrixAction::SilenceModeOnOffState => matrix.is_in_silence_mode(), + PlaytimeMatrixAction::Panic => { + return None; + } + PlaytimeMatrixAction::SequencerRecordOnOffState => { + matrix.sequencer().status() == SequencerStatus::Recording + } + PlaytimeMatrixAction::SequencerPlayOnOffState => { + matrix.sequencer().status() == SequencerStatus::Playing + } + PlaytimeMatrixAction::TapTempo => return None, + }; + Some(AbsoluteValue::from_bool(bool_value)) + }) + .ok()? + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } + impl RealTimePlaytimeMatrixTarget { + /// This returns `Ok(true)` if the event should still be forwarded to the main thread because there's still + /// something to do, but it can only be done in the main thread (e.g. when using smart record and there's + /// no tempo detection recording to be stopped). + pub fn hit( + &mut self, + value: ControlValue, + context: RealTimeControlContext, + ) -> Result { + use PlaytimeMatrixAction::*; + let matrix = context.clip_matrix()?; + let mut matrix = matrix.lock(); + match self.action { + Stop => { + if !value.is_on() { + return Ok(false); + } + matrix.stop(); + Ok(false) + } + SmartRecord => { + // We only have a real-time shortcut here for the "tempo detection stop" case. + // And this one is quite useful because it results in a low latency recording stop. + if !value.is_on() { + return Ok(false); + } + let forward_to_main_thread = !matrix.maybe_stop_tempo_detection_recording(); + Ok(forward_to_main_thread) + } + StartOrStopPlayback => { + if value.is_on() { + matrix.play_all_ignited(); + } else { + matrix.enter_silence_mode(); + } + Ok(false) + } + SilenceModeOnOffState => { + if value.is_on() { + matrix.enter_silence_mode(); + } else { + matrix.leave_silence_mode(); + } + Ok(false) + } + Panic => { + if value.is_on() { + return Ok(false); + } + matrix.panic(false); + Ok(false) + } + TapTempo => { + if !value.is_on() { + return Ok(false); + } + matrix.tap_tempo(); + Ok(false) + } + _ => Err("this action doesn't have real-time target support"), + } + } + } + impl<'a> Target<'a> for RealTimePlaytimeMatrixTarget { + type Context = RealTimeControlContext<'a>; + + fn current_value(&self, context: RealTimeControlContext<'a>) -> Option { + // The following is NOT necessary for feedback (ReaLearn always uses the non-rt-target for feedback) + use PlaytimeMatrixAction::*; + let matrix = context.clip_matrix().ok()?; + let matrix = matrix.lock(); + let bool_value = match self.action { + Stop => matrix.is_stoppable(), + StartOrStopPlayback => !matrix.is_in_silence_mode(), + SilenceModeOnOffState => matrix.is_in_silence_mode(), + SmartRecord => { + // Only relevant for feedback + return None; + } + Panic | TapTempo => { + // Not relevant in general + return None; + } + _ => return None, + }; + Some(AbsoluteValue::from_bool(bool_value)) + } + + fn control_type(&self, _: RealTimeControlContext<'a>) -> ControlType { + control_type_and_character(self.action).0 + } + } + fn control_type_and_character(action: PlaytimeMatrixAction) -> (ControlType, TargetCharacter) { + use PlaytimeMatrixAction::*; + match action { + SetRecordLengthMode => ( + ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size(RECORD_LENGTH_MODES_COUNT), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ), + SetCustomRecordLengthInBars => ( + ControlType::AbsoluteDiscrete { + atomic_step_size: convert_count_to_step_size(RECORD_LENGTH_BARS_COUNT), + is_retriggerable: false, + }, + TargetCharacter::Discrete, + ), + Stop | Undo | Redo | BuildScene | Panic | SmartRecord | TapTempo => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + ClickOnOffState + | MidiAutoQuantizationOnOffState + | SilenceModeOnOffState + | SequencerRecordOnOffState + | SequencerPlayOnOffState + | StartOrStopPlayback => (ControlType::AbsoluteContinuous, TargetCharacter::Switch), + } + } + + fn convert_control_value_to_record_length_mode( + value: ControlValue, + ) -> anyhow::Result { + let value = value + .to_discrete_value(RECORD_LENGTH_MODES_COUNT) + .map_err(anyhow::Error::msg)?; + let mode = RecordLengthMode::try_from(value.actual() as usize)?; + Ok(mode) + } + + fn convert_record_length_numerator_to_unit_value( + value: u32, + ) -> Result { + let shifted_value = value + .checked_sub(1) + .ok_or("length of 0 bars is not possible")?; + let uv = shifted_value as f64 / (RECORD_LENGTH_BARS_COUNT - 1) as f64; + Ok(UnitValue::new_clamped(uv)) + } + + const RECORD_LENGTH_MODES_COUNT: u32 = 10; + const RECORD_LENGTH_BARS_COUNT: u32 = 64; +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_row_action_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_row_action_target.rs new file mode 100644 index 0000000..37b4f5e --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_row_action_target.rs @@ -0,0 +1,299 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, VirtualPlaytimeRow, DEFAULT_TARGET, +}; + +use helgobox_api::persistence::PlaytimeRowAction; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeRowActionTarget { + pub row: VirtualPlaytimeRow, + pub action: PlaytimeRowAction, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeRowActionTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeRowActionTarget { + basics: ClipRowTargetBasics { + row_index: self.row.resolve(context, compartment)?, + action: self.action, + }, + }; + Ok(vec![ReaperTarget::PlaytimeRowAction(target)]) + } + + fn clip_row_descriptor(&self) -> Option<&VirtualPlaytimeRow> { + Some(&self.row) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct PlaytimeRowActionTarget { + pub basics: ClipRowTargetBasics, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ClipRowTargetBasics { + pub row_index: usize, + pub action: PlaytimeRowAction, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct RealTimePlaytimeRowTarget { + basics: ClipRowTargetBasics, +} + +pub const PLAYTIME_ROW_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Row action", + short_name: "Playtime row action", + supports_real_time_control: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + + use crate::domain::{ + ControlContext, PlaytimeRowActionTarget, RealTimeControlContext, RealTimePlaytimeRowTarget, + RealearnTarget, + }; + use helgoboss_learn::{ControlValue, Target}; + + impl RealearnTarget for PlaytimeRowActionTarget {} + impl<'a> Target<'a> for PlaytimeRowActionTarget { + type Context = ControlContext<'a>; + } + impl<'a> Target<'a> for RealTimePlaytimeRowTarget { + type Context = RealTimeControlContext<'a>; + } + impl RealTimePlaytimeRowTarget { + pub fn hit( + &mut self, + _value: ControlValue, + _context: RealTimeControlContext, + ) -> Result<(), &'static str> { + Err("Playtime not available") + } + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::{ + Backbone, CompoundChangeEvent, ControlContext, HitResponse, MappingControlContext, + PlaytimeRowActionTarget, RealTimeControlContext, RealTimePlaytimeRowTarget, + RealTimeReaperTarget, RealearnTarget, ReaperTargetType, TargetCharacter, + }; + use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; + use helgobox_api::persistence::PlaytimeRowAction; + use playtime_api::persistence::RowAddress; + use playtime_api::runtime::CellAddress; + use playtime_clip_engine::base::ClipMatrixEvent; + + impl PlaytimeRowActionTarget { + fn hit_internal( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> anyhow::Result { + match self.basics.action { + PlaytimeRowAction::PlayScene => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context.control_context, |matrix| { + matrix.play_scene(self.basics.row_index); + })?; + Ok(HitResponse::processed_with_effect()) + } + PlaytimeRowAction::BuildScene => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context.control_context, |matrix| { + matrix.build_scene(self.basics.row_index)?; + Ok(HitResponse::processed_with_effect()) + })? + } + PlaytimeRowAction::CopyOrPasteScene => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context.control_context, |matrix| { + if matrix.row_is_empty(self.basics.row_index) { + matrix.paste_row(self.basics.row_index)?; + } else { + matrix.copy_row(self.basics.row_index)?; + } + Ok(HitResponse::processed_with_effect()) + })? + } + PlaytimeRowAction::ClearScene => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context.control_context, |matrix| { + matrix.clear_row(self.basics.row_index)?; + Ok(HitResponse::processed_with_effect()) + })? + } + PlaytimeRowAction::Activate => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context.control_context, |matrix| { + matrix + .activate_cell(CellAddress::new(None, Some(self.basics.row_index)))?; + Ok(HitResponse::processed_with_effect()) + })? + } + } + } + + fn with_matrix( + &self, + context: ControlContext, + f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + Backbone::get().with_clip_matrix_mut(context.instance(), f) + } + } + + impl RealearnTarget for PlaytimeRowActionTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + control_type_and_character(self.basics.action) + } + + fn clip_row_address(&self) -> Option { + Some(RowAddress::new(self.basics.row_index)) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + self.hit_internal(value, context) + .map_err(|_| "couldn't carry out row action") + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeRowAction) + } + + fn splinter_real_time_target(&self) -> Option { + if !matches!(self.basics.action, PlaytimeRowAction::PlayScene) { + return None; + } + let t = RealTimePlaytimeRowTarget { + basics: self.basics.clone(), + }; + Some(RealTimeReaperTarget::PlaytimeRow(t)) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn can_report_current_value(&self) -> bool { + match self.basics.action { + PlaytimeRowAction::PlayScene => false, + PlaytimeRowAction::BuildScene => false, + PlaytimeRowAction::CopyOrPasteScene => true, + PlaytimeRowAction::ClearScene => true, + PlaytimeRowAction::Activate => true, + } + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match self.basics.action { + PlaytimeRowAction::Activate => match evt { + CompoundChangeEvent::ClipMatrix( + ClipMatrixEvent::ActiveCellChanged | ClipMatrixEvent::EverythingChanged, + ) => (true, None), + _ => (false, None), + }, + _ => (false, None), + } + } + } + + impl<'a> Target<'a> for PlaytimeRowActionTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + use PlaytimeRowAction::*; + match self.basics.action { + PlayScene => None, + BuildScene => None, + CopyOrPasteScene | ClearScene => { + let row_is_empty = self + .with_matrix(context, |matrix| matrix.row_is_empty(self.basics.row_index)) + .ok()?; + Some(AbsoluteValue::from_bool(!row_is_empty)) + } + Activate => { + let row_is_active = self + .with_matrix(context, |matrix| { + matrix.active_cell().column_index == Some(self.basics.row_index) + }) + .ok()?; + Some(AbsoluteValue::from_bool(row_is_active)) + } + } + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } + + impl RealTimePlaytimeRowTarget { + pub fn hit( + &mut self, + value: ControlValue, + context: RealTimeControlContext, + ) -> Result<(), &'static str> { + match self.basics.action { + PlaytimeRowAction::PlayScene => { + if !value.is_on() { + return Ok(()); + } + let matrix = context.clip_matrix()?; + let mut matrix = matrix.lock(); + matrix.play_scene(self.basics.row_index); + Ok(()) + } + _ => Err("only row-play is supported in real-time"), + } + } + } + + impl<'a> Target<'a> for RealTimePlaytimeRowTarget { + type Context = RealTimeControlContext<'a>; + + fn current_value(&self, _: RealTimeControlContext<'a>) -> Option { + None + } + + fn control_type(&self, _: RealTimeControlContext<'a>) -> ControlType { + control_type_and_character(self.basics.action).0 + } + } + + fn control_type_and_character(_action: PlaytimeRowAction) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_management_action_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_management_action_target.rs new file mode 100644 index 0000000..019dabc --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_management_action_target.rs @@ -0,0 +1,297 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, VirtualPlaytimeSlot, DEFAULT_TARGET, +}; + +use helgobox_api::persistence::PlaytimeSlotManagementAction; +use playtime_api::persistence::SlotAddress; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeSlotManagementActionTarget { + pub slot: VirtualPlaytimeSlot, + pub action: PlaytimeSlotManagementAction, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeSlotManagementActionTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeSlotManagementActionTarget { + slot_address: self.slot.resolve(context, compartment)?, + action: self.action, + }; + Ok(vec![ReaperTarget::PlaytimeSlotManagementAction(target)]) + } + + fn clip_slot_descriptor(&self) -> Option<&VirtualPlaytimeSlot> { + Some(&self.slot) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct PlaytimeSlotManagementActionTarget { + pub slot_address: SlotAddress, + pub action: PlaytimeSlotManagementAction, +} + +pub const PLAYTIME_SLOT_MANAGEMENT_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Slot management action", + short_name: "Playtime slot management action", + supports_clip_slot: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ControlContext, PlaytimeSlotManagementActionTarget, RealearnTarget}; + use helgoboss_learn::Target; + + impl RealearnTarget for PlaytimeSlotManagementActionTarget {} + impl<'a> Target<'a> for PlaytimeSlotManagementActionTarget { + type Context = ControlContext<'a>; + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::ui_util::convert_bool_to_unit_value; + use crate::domain::{ + Backbone, CompoundChangeEvent, ControlContext, HitResponse, MappingControlContext, + PlaytimeSlotManagementActionTarget, RealearnTarget, ReaperTargetType, TargetCharacter, + }; + use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, PropValue, Target}; + use helgobox_api::persistence::PlaytimeSlotManagementAction; + use playtime_api::persistence::SlotAddress; + use playtime_clip_engine::base::{ClipAddress, ClipMatrixEvent}; + use playtime_clip_engine::rt::{ClipChangeEvent, QualifiedClipChangeEvent}; + + impl PlaytimeSlotManagementActionTarget { + fn hit_internal( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> anyhow::Result { + use PlaytimeSlotManagementAction as A; + match self.action { + A::ClearSlot => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context, |matrix| { + matrix.clear_slot(self.slot_address)?; + Ok(HitResponse::processed_with_effect()) + })? + } + A::FillSlotWithSelectedItem => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context, |matrix| { + matrix.import_selected_items(self.slot_address)?; + Ok(HitResponse::processed_with_effect()) + })? + } + A::EditClip => self.with_matrix(context, |matrix| { + let clip_address = ClipAddress::new(self.slot_address, 0); + if value.is_on() { + matrix.start_editing_clip(clip_address)?; + } else { + matrix.stop_editing_clip(clip_address)?; + } + Ok(HitResponse::processed_with_effect()) + })?, + A::DoubleClipSectionLength | A::HalveClipSectionLength => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context, |matrix| { + let factor = if self.action == A::DoubleClipSectionLength { + 2.0 + } else { + 0.5 + }; + matrix.adjust_slot_dynamic_section_length(self.slot_address, factor)?; + Ok(HitResponse::processed_with_effect()) + })? + } + A::CopyOrPasteClip => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context, |matrix| { + if matrix.slot_is_empty(self.slot_address) { + matrix.paste_slot(self.slot_address)?; + } else { + matrix.copy_slot(self.slot_address)?; + } + Ok(HitResponse::processed_with_effect()) + })? + } + A::QuantizationOnOffState => self.with_matrix(context, |matrix| { + if value.is_on() { + matrix.quantize_clip(ClipAddress::new(self.slot_address, 0))?; + } else { + matrix.unquantize_clip(ClipAddress::new(self.slot_address, 0))?; + } + Ok(HitResponse::processed_with_effect()) + })?, + A::Duplicate => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context, |matrix| { + matrix.duplicate_slot(self.slot_address)?; + Ok(HitResponse::processed_with_effect()) + })? + } + A::Activate => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + self.with_matrix(context, |matrix| { + matrix.activate_cell(self.slot_address.to_cell_address())?; + Ok(HitResponse::processed_with_effect()) + })? + } + } + } + + fn with_matrix( + &self, + context: MappingControlContext, + f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + Backbone::get().with_clip_matrix_mut(context.control_context.instance(), f) + } + } + + impl RealearnTarget for PlaytimeSlotManagementActionTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + use PlaytimeSlotManagementAction as A; + match self.action { + A::ClearSlot + | A::FillSlotWithSelectedItem + | A::CopyOrPasteClip + | A::Duplicate + | A::HalveClipSectionLength + | A::DoubleClipSectionLength + | A::Activate => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + A::EditClip | A::QuantizationOnOffState => { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + } + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + self.hit_internal(value, context) + .map_err(|_| "couldn't carry out clip management action") + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeSlotManagementAction) + } + + // TODO-high-playtime-after-release Return clip as result of clip() function for all clip targets (just like track()) + // and make this property available in all clip targets. + // TODO-high-playtime-after-release Also add a "Clip" target, just like "Track" target + fn prop_value(&self, key: &str, context: ControlContext) -> Option { + match key { + "clip.name" => Backbone::get() + .with_clip_matrix_mut(context.instance(), |matrix| { + let clip = matrix.find_slot(self.slot_address)?.clips().next()?; + let name = clip.name()?; + Some(PropValue::Text(name.to_string().into())) + }) + .ok()?, + _ => None, + } + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn clip_slot_address(&self) -> Option { + Some(self.slot_address) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _context: ControlContext, + ) -> (bool, Option) { + match self.action { + PlaytimeSlotManagementAction::QuantizationOnOffState => match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::ClipChanged( + QualifiedClipChangeEvent { + clip_address, + event: ClipChangeEvent::Content | ClipChangeEvent::Everything, + }, + )) if clip_address.slot_address == self.slot_address => (true, None), + _ => (false, None), + }, + PlaytimeSlotManagementAction::Activate => match evt { + CompoundChangeEvent::ClipMatrix( + ClipMatrixEvent::ActiveCellChanged | ClipMatrixEvent::EverythingChanged, + ) => (true, None), + _ => (false, None), + }, + _ => (false, None), + } + } + } + + impl<'a> Target<'a> for PlaytimeSlotManagementActionTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + use PlaytimeSlotManagementAction as A; + match self.action { + A::ClearSlot + | A::FillSlotWithSelectedItem + | A::CopyOrPasteClip + | A::DoubleClipSectionLength + | A::HalveClipSectionLength + | A::Duplicate => Some(AbsoluteValue::default()), + A::Activate => Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let is_editing = + matrix.active_cell() == self.slot_address.to_cell_address(); + let value = convert_bool_to_unit_value(is_editing); + Some(AbsoluteValue::Continuous(value)) + }) + .ok()?, + A::EditClip => Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let clip_address = ClipAddress::new(self.slot_address, 0); + let is_editing = matrix.is_editing_clip(clip_address); + let value = convert_bool_to_unit_value(is_editing); + Some(AbsoluteValue::Continuous(value)) + }) + .ok()?, + A::QuantizationOnOffState => Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let clip_address = ClipAddress::new(self.slot_address, 0); + let is_quantized = matrix.clip_is_quantized(clip_address).ok()?; + let value = convert_bool_to_unit_value(is_quantized); + Some(AbsoluteValue::Continuous(value)) + }) + .ok()?, + } + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_seek_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_seek_target.rs new file mode 100644 index 0000000..1700423 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_seek_target.rs @@ -0,0 +1,226 @@ +use playtime_api::persistence::SlotAddress; + +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, FeedbackResolution, ReaperTarget, TargetSection, + TargetTypeDef, UnresolvedReaperTargetDef, VirtualPlaytimeSlot, DEFAULT_TARGET, +}; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeSlotSeekTarget { + pub slot: VirtualPlaytimeSlot, + pub feedback_resolution: FeedbackResolution, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeSlotSeekTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeSlotSeekTarget { + slot_coordinates: self.slot.resolve(context, compartment)?, + feedback_resolution: self.feedback_resolution, + }; + Ok(vec![ReaperTarget::PlaytimeSlotSeek(target)]) + } + + fn feedback_resolution(&self) -> Option { + // We always report beat resolution, even if feedback resolution is "high", in order to NOT + // be continuously queried on each main loop iteration as part of ReaLearn's generic main + // loop polling. There's a special clip polling logic in the main processor which detects + // if there were any position changes. If there were, process_change_event() will be + // called on the clip seek target. We need to do that special clip polling in any case. + // If we would doing it a second time in ReaLearn's generic main loop polling, that would be + // wasteful, in particular we would have to lock the clip preview register mutex twice + // per main loop. + Some(FeedbackResolution::Beat) + } + + fn clip_slot_descriptor(&self) -> Option<&VirtualPlaytimeSlot> { + Some(&self.slot) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PlaytimeSlotSeekTarget { + pub slot_coordinates: SlotAddress, + pub feedback_resolution: FeedbackResolution, +} + +pub const PLAYTIME_SLOT_SEEK_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + hint: "Experimental, don't use!", + name: "Slot seek", + short_name: "Playtime slot seek", + supports_feedback_resolution: true, + supports_clip_slot: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ControlContext, PlaytimeSlotSeekTarget, RealearnTarget}; + use helgoboss_learn::Target; + + impl RealearnTarget for PlaytimeSlotSeekTarget {} + impl<'a> Target<'a> for PlaytimeSlotSeekTarget { + type Context = ControlContext<'a>; + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use reaper_medium::PositionInSeconds; + use std::borrow::Cow; + + use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, Target, UnitValue, + }; + use playtime_api::persistence::SlotAddress; + + use crate::domain::playtime_util::interpret_current_clip_slot_value; + use crate::domain::{ + AdditionalFeedbackEvent, Backbone, CompoundChangeEvent, ControlContext, FeedbackResolution, + HitResponse, MappingControlContext, PlaytimeSlotSeekTarget, RealearnTarget, + ReaperTargetType, TargetCharacter, + }; + use playtime_clip_engine::{ + base::ClipMatrixEvent, + rt::supplier::audio::GlobalBlockProvider, + rt::{ClipPlayState, QualifiedSlotChangeEvent, SlotChangeEvent}, + }; + + impl RealearnTarget for PlaytimeSlotSeekTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn clip_slot_address(&self) -> Option { + Some(self.slot_coordinates) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + Backbone::get() + .with_clip_matrix( + context.control_context.instance(), + |matrix| -> anyhow::Result { + matrix.seek_slot(self.slot_coordinates, value)?; + Ok(HitResponse::processed_with_effect()) + }, + ) + .map_err(|_| "couldn't acquire matrix")? + .map_err(|_| "couldn't carry out seek action") + } + + fn is_available(&self, _: ControlContext) -> bool { + // TODO-medium With clip targets we should check the control context (instance state) if + // slot filled. + true + } + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + // When feedback resolution is beat, we only react to the main timeline beat changes. + // TODO-low Beat-changed events are emitted only when the project is playing (important) + CompoundChangeEvent::Additional(AdditionalFeedbackEvent::BeatChanged(_)) + if self.feedback_resolution == FeedbackResolution::Beat => + { + (true, None) + } + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::SlotChanged( + QualifiedSlotChangeEvent { + slot_address: si, + event, + }, + )) if *si == self.slot_coordinates => match event { + // If feedback resolution is high, we use the special ClipChangedEvent to do our job + // (in order to not lock mutex of playing clips more than once per main loop cycle). + SlotChangeEvent::Continuous(events) + if self.feedback_resolution == FeedbackResolution::High => + { + if let Some(first_event) = events.first() { + ( + true, + Some(AbsoluteValue::Continuous(first_event.proportional)), + ) + } else { + (false, None) + } + } + SlotChangeEvent::PlayState(ClipPlayState::Stopped | ClipPlayState::Ignited) => { + (true, Some(AbsoluteValue::Continuous(UnitValue::MIN))) + } + _ => (false, None), + }, + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + let seconds = self.position_in_seconds(context)?; + Some(format!("{:.3} s", seconds.get()).into()) + } + + fn numeric_value(&self, context: ControlContext) -> Option { + let seconds = self.position_in_seconds(context)?; + Some(NumericValue::Decimal(seconds.get())) + } + + fn numeric_value_unit(&self, _: ControlContext) -> &'static str { + "s" + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeSlotSeek) + } + } + + impl PlaytimeSlotSeekTarget { + fn position_in_seconds(&self, context: ControlContext) -> Option { + Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let column = matrix.find_column(self.slot_coordinates.column_index)?; + let slot = column.find_slot(self.slot_coordinates.row_index)?; + let timeline = matrix.timeline(); + let tempo = timeline.next_block().tempo_entry.props.tempo; + slot.relevant_clip_employments() + .primary_logical_pos_latency_corrected_timeline_secs(tempo) + .ok() + }) + .ok()? + } + } + + impl<'a> Target<'a> for PlaytimeSlotSeekTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + let val = Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let column = matrix.find_column(self.slot_coordinates.column_index)?; + let slot = column.find_slot(self.slot_coordinates.row_index)?; + let relevant_content = slot.relevant_clip_employments(); + let timeline = matrix.timeline(); + let tempo = timeline.next_block().tempo_entry.props.tempo; + let val = relevant_content + .primary_logical_pos_latency_corrected_proportional(tempo) + .ok()?; + Some(AbsoluteValue::Continuous(val)) + }) + .ok()?; + interpret_current_clip_slot_value(val) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_transport_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_transport_target.rs new file mode 100644 index 0000000..b669c4a --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_transport_target.rs @@ -0,0 +1,587 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, VirtualPlaytimeSlot, DEFAULT_TARGET, +}; +use helgobox_api::persistence::PlaytimeSlotTransportAction; +use playtime_api::persistence::SlotAddress; +use reaper_high::Project; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeSlotTransportTarget { + pub slot: VirtualPlaytimeSlot, + pub action: PlaytimeSlotTransportAction, + pub options: ClipTransportOptions, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct ClipTransportOptions { + /// This is only ever taken care of from the main thread because only the main thread can decide whether + /// to record *or* stop the column. + pub stop_column_if_slot_empty: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeSlotTransportTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let project = context.context.project_or_current_project(); + let target = PlaytimeSlotTransportTarget { + project, + basics: ClipTransportTargetBasics { + slot_address: self.slot.resolve(context, compartment)?, + action: self.action, + options: self.options, + }, + }; + Ok(vec![ReaperTarget::PlaytimeSlotTransportAction(target)]) + } + + fn clip_slot_descriptor(&self) -> Option<&VirtualPlaytimeSlot> { + Some(&self.slot) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct PlaytimeSlotTransportTarget { + pub project: Project, + pub basics: ClipTransportTargetBasics, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ClipTransportTargetBasics { + pub slot_address: SlotAddress, + pub action: PlaytimeSlotTransportAction, + pub options: ClipTransportOptions, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct RealTimePlaytimeSlotTransportTarget { + project: Project, + basics: ClipTransportTargetBasics, +} + +pub const PLAYTIME_SLOT_TRANSPORT_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Slot transport action", + short_name: "Playtime slot transport action", + supports_track: false, + supports_clip_slot: true, + supports_real_time_control: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ + ControlContext, PlaytimeSlotTransportTarget, RealTimeControlContext, + RealTimePlaytimeSlotTransportTarget, RealearnTarget, + }; + use helgoboss_learn::{ControlValue, Target}; + + impl RealearnTarget for PlaytimeSlotTransportTarget {} + impl<'a> Target<'a> for PlaytimeSlotTransportTarget { + type Context = ControlContext<'a>; + } + impl<'a> Target<'a> for RealTimePlaytimeSlotTransportTarget { + type Context = RealTimeControlContext<'a>; + } + impl RealTimePlaytimeSlotTransportTarget { + pub fn hit( + &mut self, + _value: ControlValue, + _context: RealTimeControlContext, + ) -> Result { + Err("Playtime not available") + } + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::{ + format_value_as_on_off, transport_is_enabled_unit_value, Backbone, + ClipTransportTargetBasics, CompoundChangeEvent, ControlContext, HitResponse, + MappingControlContext, PlaytimeSlotTransportTarget, RealTimeControlContext, + RealTimePlaytimeSlotTransportTarget, RealTimeReaperTarget, RealearnTarget, + ReaperTargetType, TargetCharacter, + }; + use anyhow::bail; + use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, PropValue, Target, UnitValue}; + use playtime_api::persistence::SlotAddress; + + use crate::domain::playtime_util::{ + clip_play_state_unit_value, interpret_current_clip_slot_value, + }; + use helgobox_api::persistence::PlaytimeSlotTransportAction; + use playtime_clip_engine::base::ClipAddress; + use playtime_clip_engine::rt::{ClipPlayState, TriggerSlotMainOptions}; + #[cfg(feature = "playtime")] + use playtime_clip_engine::{ + base::ClipMatrixEvent, + rt::{ + ClipChangeEvent, ColumnPlaySlotOptions, QualifiedClipChangeEvent, + QualifiedSlotChangeEvent, SlotChangeEvent, + }, + }; + use reaper_high::{ChangeEvent, Project}; + use std::borrow::Cow; + + impl PlaytimeSlotTransportTarget { + pub fn clip_play_state( + &self, + matrix: &playtime_clip_engine::base::Matrix, + ) -> Option { + let slot = matrix.find_slot(self.basics.slot_address)?; + if slot.is_empty() { + return None; + } + Some(slot.play_state()) + } + + fn hit_internal( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> anyhow::Result { + use PlaytimeSlotTransportAction::*; + Backbone::get().with_clip_matrix_mut(context.control_context.instance(), |matrix| { + let response = match self.basics.action { + Trigger => { + // This action is special in that it some of it can be carried out directly in the real-time + // thread **but not everything**! Other parts can only be done from main thread, such as + // starting to record into an empty slot and/or auto-activating the triggered slot. The + // stuff done in the real-time thread (play/stop) must NOT be repeated here. + let velocity = value.to_unit_value().map_err(anyhow::Error::msg)?; + matrix.trigger_slot( + self.basics.slot_address, + velocity, + TriggerSlotMainOptions { + stop_column_if_slot_empty: self + .basics + .options + .stop_column_if_slot_empty, + allow_activate: true, + // If control was initiated from real-time context (MIDI), don't take care of + // starting/stopping. Because it's done in the real-time matrix already. + allow_start_stop: !context.coming_from_real_time, + }, + )?; + HitResponse::processed_with_effect() + } + PlayStop => { + if value.is_on() { + matrix.play_slot( + self.basics.slot_address, + self.basics.play_options(value), + ); + } else { + matrix.stop_slot(self.basics.slot_address); + } + HitResponse::processed_with_effect() + } + PlayPause => { + if value.is_on() { + matrix.play_slot( + self.basics.slot_address, + self.basics.play_options(value), + ); + } else { + matrix.pause_clip(self.basics.slot_address)?; + } + HitResponse::processed_with_effect() + } + Stop => { + if value.is_on() { + matrix.stop_slot(self.basics.slot_address); + HitResponse::processed_with_effect() + } else { + HitResponse::ignored() + } + } + Pause => { + if value.is_on() { + matrix.pause_clip(self.basics.slot_address)?; + HitResponse::processed_with_effect() + } else { + HitResponse::ignored() + } + } + RecordStop => { + if value.is_on() { + if !matrix + .column_is_armed_for_recording(self.basics.slot_address.column()) + { + bail!(NOT_RECORDING_BECAUSE_NOT_ARMED); + } + matrix.record_or_overdub_slot(self.basics.slot_address)?; + } else { + matrix.stop_slot(self.basics.slot_address); + } + HitResponse::processed_with_effect() + } + RecordPlayStop => { + if value.is_on() { + if matrix.slot_is_empty(self.basics.slot_address) { + // Slot is empty. + // Record only if armed. + if matrix.column_is_armed_for_recording( + self.basics.slot_address.column(), + ) { + // Is armed, so record. + matrix.record_or_overdub_slot(self.basics.slot_address)?; + } else if self.basics.options.stop_column_if_slot_empty { + // Not armed but column stopping on empty slots enabled. + // Since we already know that the slot is empty, we do + // it explicitly without invoking play passing that option. + matrix.stop_column(self.basics.slot_address.column()); + } else { + bail!(NOT_RECORDING_BECAUSE_NOT_ARMED); + } + } else { + // Slot is filled. + matrix.play_slot( + self.basics.slot_address, + self.basics.play_options(value), + ); + } + } else { + matrix.stop_slot(self.basics.slot_address); + } + HitResponse::processed_with_effect() + } + Looped => { + matrix.set_slot_looped(self.basics.slot_address, value.is_on())?; + HitResponse::processed_with_effect() + } + OverdubPlay => { + if value.is_on() { + matrix + .midi_overdub_clip(ClipAddress::new(self.basics.slot_address, 0))?; + } else { + matrix.stop_slot(self.basics.slot_address); + } + HitResponse::processed_with_effect() + } + }; + Ok(response) + })? + } + } + + impl RealearnTarget for PlaytimeSlotTransportTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + control_type_and_character(self.basics.action) + } + + fn clip_slot_address(&self) -> Option { + Some(self.basics.slot_address) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + self.hit_internal(value, context) + .map_err(|_| "error while hitting clip transport target") + } + + fn is_available(&self, _: ControlContext) -> bool { + // TODO-medium With clip targets we should check the control context (instance state) if + // slot filled. + true + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::EverythingChanged) => (true, None), + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::SlotChanged( + QualifiedSlotChangeEvent { + slot_address: sc, + event, + }, + )) if *sc == self.basics.slot_address => { + use PlaytimeSlotTransportAction::*; + match event { + SlotChangeEvent::PlayState(new_state) => match self.basics.action { + Trigger | PlayStop | PlayPause | Stop | Pause | RecordStop + | RecordPlayStop | OverdubPlay => { + tracing::debug!( + "Changed play state of {} to {new_state:?}", + self.basics.slot_address + ); + let uv = clip_play_state_unit_value(self.basics.action, *new_state); + (true, Some(AbsoluteValue::Continuous(uv))) + } + Looped => (false, None), + }, + SlotChangeEvent::Clips(_) => (true, None), + _ => (false, None), + } + } + CompoundChangeEvent::Reaper(ChangeEvent::TrackArmChanged(_)) => (true, None), + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::ClipChanged( + QualifiedClipChangeEvent { + clip_address, + event, + }, + )) if clip_address.slot_address == self.basics.slot_address => { + use PlaytimeSlotTransportAction::*; + match event { + ClipChangeEvent::Looped(new_state) => match self.basics.action { + Looped => ( + true, + Some(AbsoluteValue::Continuous(transport_is_enabled_unit_value( + *new_state, + ))), + ), + _ => (false, None), + }, + _ => (false, None), + } + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeSlotTransportAction) + } + + fn splinter_real_time_target(&self) -> Option { + use PlaytimeSlotTransportAction::*; + if matches!( + self.basics.action, + RecordStop | RecordPlayStop | OverdubPlay | Looped + ) { + // These are not for real-time usage. + return None; + } + let t = RealTimePlaytimeSlotTransportTarget { + project: self.project, + basics: self.basics.clone(), + }; + Some(RealTimeReaperTarget::PlaytimeSlotTransport(t)) + } + + fn prop_value(&self, key: &str, context: ControlContext) -> Option { + match key { + "slot_state.id" => Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let id_string = match self.clip_play_state(matrix) { + None => { + // Slot is empty + match matrix.find_column(self.basics.slot_address.column_index) { + None => "playtime.slot_state.empty", + Some(col) => { + if col.is_armed_for_recording() { + "playtime.slot_state.armed" + } else { + "playtime.slot_state.empty" + } + } + } + } + Some(s) => s.id_string(), + }; + Some(PropValue::Text(id_string.into())) + }) + .ok()?, + _ => None, + } + } + } + + impl<'a> Target<'a> for PlaytimeSlotTransportTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + let val = Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + use PlaytimeSlotTransportAction::*; + let val = match self.basics.action { + Trigger | PlayStop | PlayPause | Stop | Pause | RecordStop + | RecordPlayStop | OverdubPlay => { + let play_state = self.clip_play_state(matrix)?; + tracing::trace!( + "Current play state of {}: {play_state:?}", + self.basics.slot_address + ); + clip_play_state_unit_value(self.basics.action, play_state) + } + Looped => { + let is_looped = + matrix.find_slot(self.basics.slot_address)?.looped().ok()?; + transport_is_enabled_unit_value(is_looped) + } + }; + Some(AbsoluteValue::Continuous(val)) + }) + .ok()?; + interpret_current_clip_slot_value(val) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } + + impl RealTimePlaytimeSlotTransportTarget { + /// This returns `Ok(true)` if the event should still be forwarded to the main thread because there's still + /// something to do, but it can only be done in the main thread (e.g. when triggering in order to possibly + /// record when slot empty or to activate the slot). + pub fn hit( + &mut self, + value: ControlValue, + context: RealTimeControlContext, + ) -> Result { + use PlaytimeSlotTransportAction::*; + let matrix = context.clip_matrix()?; + let mut matrix = matrix.lock(); + match self.basics.action { + Trigger => { + // Whenever the value is on, we want to also forward control to the main (non-real-time) target. + // Reasons: + // 1. Only the main target can take care of activating the slot (`activate_slot_on_trigger`). + // 2. Only the main target can check whether the track is armed and start recording. + let forward_to_main_thread = value.is_on(); + let execute_in_real_time = if forward_to_main_thread { + // We must carefully separate between what we do *here* (in real-time) and in the main target. + if matrix.slot_is_filled(self.basics.slot_address) { + // Slot is filled, so all the main thread will do is to activate the slot. + // We can safely execute. + true + } else { + // Careful! In addition to slot activation, the main target could decide to record. + // In that case we don't want to do anything in real-time. If `stop_column_if_slot_empty` + // is enabled, it's the responsibility to do that if it decides to *not record*. + false + } + } else { + // Easy. No main target that can get interfere. + true + }; + if execute_in_real_time { + matrix.trigger_slot(self.basics.slot_address, value.to_unit_value()?)?; + } + Ok(forward_to_main_thread) + } + PlayStop => { + if value.is_on() { + matrix + .play_slot(self.basics.slot_address, self.basics.play_options(value))?; + } else { + matrix.stop_slot(self.basics.slot_address)?; + } + Ok(false) + } + PlayPause => { + if value.is_on() { + matrix + .play_slot(self.basics.slot_address, self.basics.play_options(value))?; + } else { + matrix.pause_slot(self.basics.slot_address)?; + } + // Completely handled in real-time, no need to forward to main thread + Ok(false) + } + Stop => { + if value.is_on() { + matrix.stop_slot(self.basics.slot_address)?; + } + // Completely handled in real-time, no need to forward to main thread + Ok(false) + } + Pause => { + if value.is_on() { + matrix.pause_slot(self.basics.slot_address)?; + } + // Completely handled in real-time, no need to forward to main thread + Ok(false) + } + RecordStop | RecordPlayStop | OverdubPlay => { + Err("record not supported for real-time target") + } + Looped => Err("setting looped not supported for real-time target"), + } + } + } + + impl<'a> Target<'a> for RealTimePlaytimeSlotTransportTarget { + type Context = RealTimeControlContext<'a>; + + fn current_value(&self, context: RealTimeControlContext<'a>) -> Option { + use PlaytimeSlotTransportAction::*; + let matrix = context.clip_matrix().ok()?; + let matrix = matrix.lock(); + let column = matrix.column(self.basics.slot_address.column()).ok()?; + let column = column.blocking_lock(); + let slot = column.slot(self.basics.slot_address.row()).ok()?; + let Some(first_clip) = slot.first_clip() else { + return Some(AbsoluteValue::Continuous(UnitValue::MIN)); + }; + let val = match self.basics.action { + Trigger | PlayStop | PlayPause | Stop | Pause | RecordStop | RecordPlayStop + | OverdubPlay => { + clip_play_state_unit_value(self.basics.action, first_clip.play_state()) + } + Looped => return None, + }; + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, _: RealTimeControlContext<'a>) -> ControlType { + control_type_and_character(self.basics.action).0 + } + } + + impl ClipTransportTargetBasics { + fn play_options(&self, control_value: ControlValue) -> ColumnPlaySlotOptions { + ColumnPlaySlotOptions { + velocity: Some(control_value.to_unit_value().unwrap_or(UnitValue::MAX)), + stop_column_if_slot_empty: self.options.stop_column_if_slot_empty, + handle_ignited_clips: false, + play_at_timeline_zero: false, + } + } + } + const NOT_RECORDING_BECAUSE_NOT_ARMED: &str = "not recording because not armed"; + + fn control_type_and_character( + action: PlaytimeSlotTransportAction, + ) -> (ControlType, TargetCharacter) { + use PlaytimeSlotTransportAction::*; + match action { + Trigger => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Continuous, + ), + // Retriggerable because we want to be able to retrigger play! + PlayStop | PlayPause | RecordPlayStop | OverdubPlay => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ), + Stop | Pause | RecordStop | Looped => { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_volume_target.rs b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_volume_target.rs new file mode 100644 index 0000000..cfadd4b --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/playtime_slot_volume_target.rs @@ -0,0 +1,186 @@ +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, ReaperTarget, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, VirtualPlaytimeSlot, DEFAULT_TARGET, +}; + +use playtime_api::persistence::SlotAddress; + +#[derive(Debug)] +pub struct UnresolvedPlaytimeSlotVolumeTarget { + pub slot: VirtualPlaytimeSlot, +} + +impl UnresolvedReaperTargetDef for UnresolvedPlaytimeSlotVolumeTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let target = PlaytimeSlotVolumeTarget { + slot_coordinates: self.slot.resolve(context, compartment)?, + }; + Ok(vec![ReaperTarget::PlaytimeSlotVolume(target)]) + } + + fn clip_slot_descriptor(&self) -> Option<&VirtualPlaytimeSlot> { + Some(&self.slot) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PlaytimeSlotVolumeTarget { + pub slot_coordinates: SlotAddress, +} + +pub const PLAYTIME_SLOT_VOLUME_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Playtime, + name: "Slot volume", + short_name: "Playtime slot volume", + supports_clip_slot: true, + ..DEFAULT_TARGET +}; + +#[cfg(not(feature = "playtime"))] +mod no_playtime_impl { + use crate::domain::{ControlContext, PlaytimeSlotVolumeTarget, RealearnTarget}; + use helgoboss_learn::Target; + + impl RealearnTarget for PlaytimeSlotVolumeTarget {} + impl<'a> Target<'a> for PlaytimeSlotVolumeTarget { + type Context = ControlContext<'a>; + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::domain::playtime_util::interpret_current_clip_slot_value; + use crate::domain::ui_util::{ + db_unit_value, format_value_as_db, format_value_as_db_without_unit, parse_value_from_db, + volume_unit_value, + }; + use crate::domain::{ + Backbone, CompoundChangeEvent, ControlContext, HitResponse, MappingControlContext, + PlaytimeSlotVolumeTarget, RealearnTarget, ReaperTargetType, TargetCharacter, + }; + use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, Target, UnitValue, + }; + use playtime_api::persistence::SlotAddress; + use playtime_clip_engine::{ + base::ClipMatrixEvent, + rt::{ClipChangeEvent, QualifiedClipChangeEvent}, + }; + use reaper_high::SliderVolume; + use std::borrow::Cow; + + impl RealearnTarget for PlaytimeSlotVolumeTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn clip_slot_address(&self) -> Option { + Some(self.slot_coordinates) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_db(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_db_without_unit(value) + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "dB" + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_db(value) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let volume = + SliderVolume::try_from_normalized_slider_value(value.to_unit_value()?.get()) + .unwrap_or_default(); + let db = volume.db(); + Backbone::get() + .with_clip_matrix_mut( + context.control_context.instance(), + |matrix| -> anyhow::Result { + matrix.set_slot_volume(self.slot_coordinates, db)?; + Ok(HitResponse::processed_with_effect()) + }, + ) + .map_err(|_| "couldn't acquire matrix")? + .map_err(|_| "couldn't carry out volume action") + } + + fn is_available(&self, _: ControlContext) -> bool { + // TODO-medium With clip targets we should check the control context (instance state) if + // slot filled. + true + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::ClipMatrix(ClipMatrixEvent::ClipChanged( + QualifiedClipChangeEvent { + clip_address, + event: ClipChangeEvent::Volume(new_value), + }, + )) if clip_address.slot_address == self.slot_coordinates => ( + true, + Some(AbsoluteValue::Continuous(db_unit_value(*new_value))), + ), + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(self.volume(context)?.to_string().into()) + } + + fn numeric_value(&self, context: ControlContext) -> Option { + Some(NumericValue::Decimal(self.volume(context)?.db().get())) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PlaytimeSlotVolume) + } + } + + impl PlaytimeSlotVolumeTarget { + fn volume(&self, context: ControlContext) -> Option { + Backbone::get() + .with_clip_matrix(context.instance(), |matrix| { + let db = matrix.find_slot(self.slot_coordinates)?.volume().ok()?; + Some(SliderVolume::from_db(db)) + }) + .ok()? + } + } + + impl<'a> Target<'a> for PlaytimeSlotVolumeTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, context: ControlContext<'a>) -> Option { + let val = self + .volume(context) + .map(volume_unit_value) + .map(AbsoluteValue::Continuous); + interpret_current_clip_slot_value(val) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/preview_pot_preset_target.rs b/plugin-reaper-realearn/main/src/domain/targets/preview_pot_preset_target.rs new file mode 100644 index 0000000..376e2dc --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/preview_pot_preset_target.rs @@ -0,0 +1,121 @@ +use crate::domain::{ + CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + InstanceStateChanged, MappingControlContext, PotStateChangedEvent, RealearnTarget, + ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::blocking_lock_arc; +use derivative::Derivative; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; +use pot::{preview_exists, PresetId, RuntimePotUnit}; +use reaper_high::Reaper; + +#[derive(Debug)] +pub struct UnresolvedPreviewPotPresetTarget {} + +impl UnresolvedReaperTargetDef for UnresolvedPreviewPotPresetTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::PreviewPotPreset( + PreviewPotPresetTarget {}, + )]) + } +} + +#[derive(Clone, Debug, Derivative)] +#[derivative(Eq, PartialEq)] +pub struct PreviewPotPresetTarget {} + +impl RealearnTarget for PreviewPotPresetTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let mut instance_state = context.control_context.instance().borrow_mut(); + let pot_unit = instance_state.pot_unit()?; + let mut pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from PreviewPotPresetTarget 1"); + if value.is_on() { + let preset_id = self + .current_preset_id(&pot_unit) + .ok_or("no Pot preset selected")?; + pot_unit + .play_preview(preset_id) + .map_err(|_| "couldn't play preview")?; + Ok(HitResponse::processed_with_effect()) + } else { + pot_unit + .stop_preview() + .map_err(|_| "couldn't stop preview")?; + Ok(HitResponse::processed_with_effect()) + } + } + + fn is_available(&self, context: ControlContext) -> bool { + let mut instance_state = context.instance().borrow_mut(); + let pot_unit = match instance_state.pot_unit() { + Ok(u) => u, + Err(_) => return false, + }; + let pot_unit = blocking_lock_arc(&pot_unit, "PotUnit from PreviewPotPresetTarget 2"); + match pot_unit.find_currently_selected_preset() { + None => false, + Some(p) => preview_exists(&p, &Reaper::get().resource_path()), + } + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Instance(InstanceStateChanged::PotStateChanged( + PotStateChangedEvent::PresetChanged { .. }, + )) => (true, None), + _ => (false, None), + } + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::PreviewPotPreset) + } + + fn can_report_current_value(&self) -> bool { + false + } +} + +impl<'a> Target<'a> for PreviewPotPresetTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + None + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +impl PreviewPotPresetTarget { + fn current_preset_id(&self, pot_unit: &RuntimePotUnit) -> Option { + pot_unit.preset_id() + } +} +pub const PREVIEW_POT_PRESET_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Pot, + name: "Preview preset", + short_name: "Preview Pot preset", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/route_automation_mode_target.rs b/plugin-reaper-realearn/main/src/domain/targets/route_automation_mode_target.rs new file mode 100644 index 0000000..4b1ab11 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/route_automation_mode_target.rs @@ -0,0 +1,137 @@ +use crate::domain::{ + automation_mode_unit_value, format_value_as_on_off, get_track_routes, CompartmentKind, + ControlContext, ExtendedProcessorContext, FeedbackResolution, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackRouteDescriptor, UnresolvedReaperTargetDef, + AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track, TrackRoute}; +use reaper_medium::AutomationMode; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedRouteAutomationModeTarget { + pub descriptor: TrackRouteDescriptor, + pub mode: AutomationMode, + pub poll_for_feedback: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedRouteAutomationModeTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let routes = get_track_routes(context, &self.descriptor, compartment)?; + let targets = routes + .into_iter() + .map(|route| { + ReaperTarget::RouteAutomationMode(RouteAutomationModeTarget { + route, + poll_for_feedback: self.poll_for_feedback, + mode: self.mode, + }) + }) + .collect(); + Ok(targets) + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + Some(&self.descriptor) + } + + fn feedback_resolution(&self) -> Option { + if self.poll_for_feedback { + Some(FeedbackResolution::High) + } else { + None + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RouteAutomationModeTarget { + pub route: TrackRoute, + pub poll_for_feedback: bool, + pub mode: AutomationMode, +} + +impl RealearnTarget for RouteAutomationModeTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // Retriggerable because of #277 + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + self.route.set_automation_mode(AutomationMode::TrimRead)?; + } else { + self.route.set_automation_mode(self.mode)?; + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.route.is_available() + } + + fn project(&self) -> Option { + Some(self.route.track().project()) + } + + fn track(&self) -> Option<&Track> { + Some(self.route.track()) + } + + fn route(&self) -> Option<&TrackRoute> { + Some(&self.route) + } + + fn supports_automatic_feedback(&self) -> bool { + self.poll_for_feedback + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::RouteAutomationMode) + } +} + +impl<'a> Target<'a> for RouteAutomationModeTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = automation_mode_unit_value(self.mode, self.route.automation_mode()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ROUTE_AUTOMATION_MODE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Send, + name: "Automation mode", + short_name: "Send automation mode", + hint: AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + supports_poll_for_feedback: true, + supports_track: true, + supports_send: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/route_mono_target.rs b/plugin-reaper-realearn/main/src/domain/targets/route_mono_target.rs new file mode 100644 index 0000000..70afd26 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/route_mono_target.rs @@ -0,0 +1,129 @@ +use crate::domain::{ + format_value_as_on_off, get_track_routes, mute_unit_value, CompartmentKind, ControlContext, + ExtendedProcessorContext, FeedbackResolution, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + TrackRouteDescriptor, UnresolvedReaperTargetDef, AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track, TrackRoute}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedRouteMonoTarget { + pub descriptor: TrackRouteDescriptor, + pub poll_for_feedback: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedRouteMonoTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let routes = get_track_routes(context, &self.descriptor, compartment)?; + let targets = routes + .into_iter() + .map(|route| { + ReaperTarget::RouteMono(RouteMonoTarget { + route, + poll_for_feedback: self.poll_for_feedback, + }) + }) + .collect(); + Ok(targets) + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + Some(&self.descriptor) + } + + fn feedback_resolution(&self) -> Option { + if self.poll_for_feedback { + Some(FeedbackResolution::High) + } else { + None + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RouteMonoTarget { + pub route: TrackRoute, + pub poll_for_feedback: bool, +} + +impl RealearnTarget for RouteMonoTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + self.route.set_mono(false)?; + } else { + self.route.set_mono(true)?; + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.route.is_available() + } + + fn project(&self) -> Option { + Some(self.route.track().project()) + } + + fn track(&self) -> Option<&Track> { + Some(self.route.track()) + } + + fn route(&self) -> Option<&TrackRoute> { + Some(&self.route) + } + + fn supports_automatic_feedback(&self) -> bool { + self.poll_for_feedback + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::RouteMono) + } +} + +impl<'a> Target<'a> for RouteMonoTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = mute_unit_value(self.route.is_mono()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ROUTE_MONO_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Send, + name: "Mono/stereo", + short_name: "Send mono/stereo", + hint: AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + supports_poll_for_feedback: true, + supports_track: true, + supports_send: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/route_mute_target.rs b/plugin-reaper-realearn/main/src/domain/targets/route_mute_target.rs new file mode 100644 index 0000000..4be5757 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/route_mute_target.rs @@ -0,0 +1,129 @@ +use crate::domain::{ + format_value_as_on_off, get_track_routes, mute_unit_value, CompartmentKind, ControlContext, + ExtendedProcessorContext, FeedbackResolution, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + TrackRouteDescriptor, UnresolvedReaperTargetDef, AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track, TrackRoute}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedRouteMuteTarget { + pub descriptor: TrackRouteDescriptor, + pub poll_for_feedback: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedRouteMuteTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let routes = get_track_routes(context, &self.descriptor, compartment)?; + let targets = routes + .into_iter() + .map(|route| { + ReaperTarget::RouteMute(RouteMuteTarget { + route, + poll_for_feedback: self.poll_for_feedback, + }) + }) + .collect(); + Ok(targets) + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + Some(&self.descriptor) + } + + fn feedback_resolution(&self) -> Option { + if self.poll_for_feedback { + Some(FeedbackResolution::High) + } else { + None + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RouteMuteTarget { + pub route: TrackRoute, + pub poll_for_feedback: bool, +} + +impl RealearnTarget for RouteMuteTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + self.route.unmute()?; + } else { + self.route.mute()?; + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.route.is_available() + } + + fn project(&self) -> Option { + Some(self.route.track().project()) + } + + fn track(&self) -> Option<&Track> { + Some(self.route.track()) + } + + fn route(&self) -> Option<&TrackRoute> { + Some(&self.route) + } + + fn supports_automatic_feedback(&self) -> bool { + self.poll_for_feedback + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::RouteMute) + } +} + +impl<'a> Target<'a> for RouteMuteTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = mute_unit_value(self.route.is_muted().ok()?); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ROUTE_MUTE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Send, + name: "Mute/unmute", + short_name: "(Un)mute send", + hint: AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + supports_poll_for_feedback: true, + supports_track: true, + supports_send: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/route_pan_target.rs b/plugin-reaper-realearn/main/src/domain/targets/route_pan_target.rs new file mode 100644 index 0000000..e0b1c9e --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/route_pan_target.rs @@ -0,0 +1,161 @@ +use crate::domain::{ + format_value_as_pan, get_track_routes, pan_unit_value, parse_value_from_pan, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackRouteDescriptor, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, NumericValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Pan, Project, ReaperError, Track, TrackRoute}; +use reaper_medium::EditMode; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedRoutePanTarget { + pub descriptor: TrackRouteDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedRoutePanTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let routes = get_track_routes(context, &self.descriptor, compartment)?; + let targets = routes + .into_iter() + .map(|route| ReaperTarget::RoutePan(RoutePanTarget { route })) + .collect(); + Ok(targets) + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + Some(&self.descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RoutePanTarget { + pub route: TrackRoute, +} + +impl RealearnTarget for RoutePanTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_pan(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_pan(value) + } + + fn hide_formatted_value(&self, _: ControlContext) -> bool { + true + } + + fn hide_formatted_step_size(&self, _: ControlContext) -> bool { + true + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "" + } + + fn step_size_unit(&self, _: ControlContext) -> &'static str { + "" + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_pan(value) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let pan = Pan::from_normalized_value(value.to_unit_value()?.get()); + self.route + .set_pan(pan, EditMode::NormalTweak) + .map_err(|_| "couldn't set route pan")?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.route.is_available() + } + + fn project(&self) -> Option { + Some(self.route.track().project()) + } + + fn track(&self) -> Option<&Track> { + Some(self.route.track()) + } + + fn route(&self) -> Option<&TrackRoute> { + Some(&self.route) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackRoutePanChanged(e)) + if e.route == self.route => + { + ( + true, + Some(AbsoluteValue::Continuous(pan_unit_value( + Pan::from_reaper_value(e.new_value), + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(self.pan().ok()?.to_string().into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal(self.pan().ok()?.reaper_value().get())) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::RoutePan) + } +} + +impl RoutePanTarget { + fn pan(&self) -> Result { + self.route.pan() + } +} + +impl<'a> Target<'a> for RoutePanTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = pan_unit_value(self.pan().ok()?); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ROUTE_PAN_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Send, + name: "Set pan", + short_name: "Send pan", + supports_track: true, + supports_send: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/route_phase_target.rs b/plugin-reaper-realearn/main/src/domain/targets/route_phase_target.rs new file mode 100644 index 0000000..b3a52bf --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/route_phase_target.rs @@ -0,0 +1,129 @@ +use crate::domain::{ + format_value_as_on_off, get_track_routes, mute_unit_value, CompartmentKind, ControlContext, + ExtendedProcessorContext, FeedbackResolution, HitResponse, MappingControlContext, + RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, + TrackRouteDescriptor, UnresolvedReaperTargetDef, AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track, TrackRoute}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedRoutePhaseTarget { + pub descriptor: TrackRouteDescriptor, + pub poll_for_feedback: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedRoutePhaseTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let routes = get_track_routes(context, &self.descriptor, compartment)?; + let targets = routes + .into_iter() + .map(|route| { + ReaperTarget::RoutePhase(RoutePhaseTarget { + route, + poll_for_feedback: self.poll_for_feedback, + }) + }) + .collect(); + Ok(targets) + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + Some(&self.descriptor) + } + + fn feedback_resolution(&self) -> Option { + if self.poll_for_feedback { + Some(FeedbackResolution::High) + } else { + None + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RoutePhaseTarget { + pub route: TrackRoute, + pub poll_for_feedback: bool, +} + +impl RealearnTarget for RoutePhaseTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Switch) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.to_unit_value()?.is_zero() { + self.route.set_phase_inverted(false)?; + } else { + self.route.set_phase_inverted(true)?; + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.route.is_available() + } + + fn project(&self) -> Option { + Some(self.route.track().project()) + } + + fn track(&self) -> Option<&Track> { + Some(self.route.track()) + } + + fn route(&self) -> Option<&TrackRoute> { + Some(&self.route) + } + + fn supports_automatic_feedback(&self) -> bool { + self.poll_for_feedback + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::RoutePhase) + } +} + +impl<'a> Target<'a> for RoutePhaseTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = mute_unit_value(self.route.phase_is_inverted()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ROUTE_PHASE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Send, + name: "Phase invert/normal", + short_name: "Send phase", + hint: AUTOMATIC_FEEDBACK_VIA_POLLING_ONLY, + supports_poll_for_feedback: true, + supports_track: true, + supports_send: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/route_touch_state_target.rs b/plugin-reaper-realearn/main/src/domain/targets/route_touch_state_target.rs new file mode 100644 index 0000000..18094e7 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/route_touch_state_target.rs @@ -0,0 +1,152 @@ +use crate::domain::{ + format_value_as_on_off, get_track_routes, CompartmentKind, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackRouteDescriptor, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track, TrackRoute}; +use reaper_medium::EditMode; +use strum::EnumIter; + +#[derive(Debug)] +pub struct UnresolvedRouteTouchStateTarget { + pub descriptor: TrackRouteDescriptor, + pub parameter_type: TouchedRouteParameterType, +} + +impl UnresolvedReaperTargetDef for UnresolvedRouteTouchStateTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let routes = get_track_routes(context, &self.descriptor, compartment)?; + let targets = routes + .into_iter() + .map(|route| { + ReaperTarget::RouteTouchState(RouteTouchStateTarget { + route, + parameter_type: self.parameter_type, + }) + }) + .collect(); + Ok(targets) + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + Some(&self.descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RouteTouchStateTarget { + pub route: TrackRoute, + pub parameter_type: TouchedRouteParameterType, +} + +impl RealearnTarget for RouteTouchStateTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if value.is_on() { + // Correct! Here, we only want an effect if the button is *released*. + return Ok(HitResponse::ignored()); + } + match self.parameter_type { + TouchedRouteParameterType::Volume => { + let current_value = self.route.volume().map_err(|e| e.message())?; + self.route + .set_volume(current_value, EditMode::EndOfEdit) + .map_err(|e| e.message())?; + } + TouchedRouteParameterType::Pan => { + let current_value = self.route.pan().map_err(|e| e.message())?; + self.route + .set_pan(current_value, EditMode::EndOfEdit) + .map_err(|e| e.message())?; + } + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.route.is_available() + } + + fn project(&self) -> Option { + Some(self.route.track().project()) + } + + fn track(&self) -> Option<&Track> { + Some(self.route.track()) + } + + fn route(&self) -> Option<&TrackRoute> { + Some(&self.route) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::RouteTouchState) + } + + fn can_report_current_value(&self) -> bool { + false + } +} + +impl<'a> Target<'a> for RouteTouchStateTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + None + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ROUTE_TOUCH_STATE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Send, + name: "Set automation touch state", + short_name: "Send touch state", + supports_track: true, + supports_send: true, + ..DEFAULT_TARGET +}; + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + serde::Serialize, + serde::Deserialize, + EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + derive_more::Display, +)] +#[repr(usize)] +pub enum TouchedRouteParameterType { + #[default] + Volume, + Pan, +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/route_volume_target.rs b/plugin-reaper-realearn/main/src/domain/targets/route_volume_target.rs new file mode 100644 index 0000000..cab1f7c --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/route_volume_target.rs @@ -0,0 +1,169 @@ +use crate::domain::ui_util::{ + format_value_as_db, format_value_as_db_without_unit, parse_value_from_db, volume_unit_value, +}; +use crate::domain::{ + get_track_routes, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackRouteDescriptor, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, NumericValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, ReaperError, SliderVolume, Track, TrackRoute}; +use reaper_medium::EditMode; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedRouteVolumeTarget { + pub descriptor: TrackRouteDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedRouteVolumeTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let routes = get_track_routes(context, &self.descriptor, compartment)?; + let targets = routes + .into_iter() + .map(|route| ReaperTarget::TrackRouteVolume(RouteVolumeTarget { route })) + .collect(); + Ok(targets) + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + Some(&self.descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RouteVolumeTarget { + pub route: TrackRoute, +} + +impl RealearnTarget for RouteVolumeTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_db(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_db_without_unit(value) + } + + fn hide_formatted_value(&self, _: ControlContext) -> bool { + true + } + + fn hide_formatted_step_size(&self, _: ControlContext) -> bool { + true + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "dB" + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_db(value) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let volume = SliderVolume::try_from_normalized_slider_value(value.to_unit_value()?.get()); + self.route + .set_volume( + volume.unwrap_or(SliderVolume::MIN).reaper_value(), + EditMode::NormalTweak, + ) + .map_err(|_| "couldn't set route volume")?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.route.is_available() + } + + fn project(&self) -> Option { + Some(self.route.track().project()) + } + + fn track(&self) -> Option<&Track> { + Some(self.route.track()) + } + + fn route(&self) -> Option<&TrackRoute> { + Some(&self.route) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackRouteVolumeChanged(e)) + if e.route == self.route => + { + ( + true, + None, // TODO-medium Weird: CSURF_EXT_SETSENDVOLUME exhibits volume values in the + // VolumeSliderValue unit when automation mode is Write, Touch or similar. + // This is a bug because it's supposed to use the ReaperVolumeValue unit. + // As soon as this is fixed in REAPER (probably 6.44), we can use the value + // again, until then we shouldn't rely on it since it leads to weird feedback + // while writing automation (#513). + // Some(AbsoluteValue::Continuous(volume_unit_value( + // Volume::from_reaper_value(e.new_value), + // ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(self.volume().ok()?.to_string().into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal(self.volume().ok()?.db().get())) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::RouteVolume) + } +} + +impl RouteVolumeTarget { + fn volume(&self) -> Result { + Ok(SliderVolume::from_reaper_value(self.route.volume()?)) + } +} + +impl<'a> Target<'a> for RouteVolumeTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = volume_unit_value(self.volume().ok()?); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const ROUTE_VOLUME_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Send, + name: "Set volume", + short_name: "Send volume", + supports_track: true, + supports_send: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/seek_target.rs b/plugin-reaper-realearn/main/src/domain/targets/seek_target.rs new file mode 100644 index 0000000..c1b71ce --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/seek_target.rs @@ -0,0 +1,390 @@ +use crate::domain::{ + with_seek_behavior, AdditionalFeedbackEvent, CompartmentKind, CompoundChangeEvent, + ControlContext, ExtendedProcessorContext, FeedbackResolution, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, SeekOptions, + TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, PropValue, Target, UnitValue, +}; +use helgobox_api::persistence::SeekBehavior; +use reaper_high::{Project, Reaper}; +use reaper_medium::{ + GetLoopTimeRange2Result, PositionInSeconds, SetEditCurPosOptions, TimeMode, TimeModeOverride, +}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedSeekTarget { + pub options: SeekOptions, + pub behavior: SeekBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedSeekTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let project = context.context().project_or_current_project(); + Ok(vec![ReaperTarget::Seek(SeekTarget { + project, + options: self.options, + behavior: self.behavior, + })]) + } + + fn feedback_resolution(&self) -> Option { + Some(self.options.feedback_resolution) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SeekTarget { + pub project: Project, + pub options: SeekOptions, + pub behavior: SeekBehavior, +} + +impl RealearnTarget for SeekTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // TODO-low "Seek" could support rounding/discrete (beats, measures, seconds, ...) + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + let info = get_seek_info(self.project, self.options, false); + let desired_pos_within_range = value.get() * info.length(); + let desired_pos = info.start_pos + desired_pos_within_range; + with_seek_behavior(self.behavior, || { + self.project.set_edit_cursor_position( + desired_pos, + SetEditCurPosOptions { + move_view: self.options.move_view, + seek_play: self.options.seek_play, + }, + ); + }); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.project.is_available() + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Additional(AdditionalFeedbackEvent::BeatChanged(e)) + if e.project == self.project => + { + let v = current_value_of_seek(self.project, self.options, e.new_value); + (true, Some(AbsoluteValue::Continuous(v))) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(format!("{:.3} s", self.corrected_display_pos().get()).into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + let seconds = self.corrected_display_pos(); + Some(NumericValue::Decimal(seconds.get())) + } + + fn numeric_value_unit(&self, _: ControlContext) -> &'static str { + "s" + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::Seek) + } + + fn prop_value(&self, key: &str, _: ControlContext) -> Option { + let mut iter = key.split('.'); + match (iter.next(), iter.next(), iter.next()) { + (Some("position"), Some(pos_type), suffix) => { + use TimeMode::*; + use TimeModeOverride::*; + let mode = match pos_type { + "project_default" => match self.project.transport_time_mode() { + ProjectDefault => self.project.ruler_time_mode(), + Mode(m) => m, + }, + "time" => Time, + "measures_beats_time" => MeasuresBeatsTime, + "measures_beats" => MeasuresBeats, + "measures_beats_minimal" => MeasuresBeatsMinimal, + "seconds" => Seconds, + "samples" => Samples, + "hmsf" => HoursMinutesSecondsFrames, + "absolute_frames" => AbsoluteFrames, + _ => return None, + }; + let reaper = Reaper::get().medium_reaper(); + match suffix { + // Use native REAPER time string format + None => { + let text = reaper.format_timestr_pos( + self.reversely_corrected_display_pos(), + 32, + TimeModeOverride::Mode(mode), + ); + Some(PropValue::Text(text.into_string().into())) + } + // Use format tailored to Mackie timecode display + Some("mcu") => { + let text = match mode { + Samples => { + let text = reaper.format_timestr_pos( + self.reversely_corrected_display_pos(), + 32, + TimeModeOverride::Mode(TimeMode::Samples), + ); + text.into_string() + } + Time => { + let text = reaper.format_timestr_pos( + self.reversely_corrected_display_pos(), + 32, + TimeModeOverride::Mode(TimeMode::Time), + ); + // [*h:]?m:ss.fff + let mut comp = text.to_str().split(&[':', '.'][..]); + match (comp.next(), comp.next(), comp.next(), comp.next()) { + (Some(m), Some(ss), Some(fff), None) => { + format!("{m:0>2}{ss:0>2}{fff:0>3}") + } + (Some(h), Some(m), Some(ss), Some(fff)) => { + format!("{h}{m:0>2}{ss:0>2}{fff:0>3}") + } + _ => String::new(), + } + } + MeasuresBeatsTime | MeasuresBeats | MeasuresBeatsMinimal => { + let text = reaper.format_timestr_pos( + self.reversely_corrected_display_pos(), + 32, + TimeModeOverride::Mode(TimeMode::MeasuresBeatsTime), + ); + // *m.b.ff + let mut comp = text.to_str().split('.'); + if let (Some(m), Some(b), Some(ff)) = + (comp.next(), comp.next(), comp.next()) + { + format!("{m}{b:>2} {ff:0>2}") + } else { + String::new() + } + } + Seconds => { + let pos = self.corrected_display_pos().get(); + format!( + "{}{} {:02}", + if pos.is_sign_negative() { "-" } else { "" }, + pos.abs() as i32, + (pos.abs() * 100.0) as i32 % 100 + ) + } + HoursMinutesSecondsFrames => { + let text = reaper.format_timestr_pos( + self.reversely_corrected_display_pos(), + 32, + TimeModeOverride::Mode(TimeMode::HoursMinutesSecondsFrames), + ); + // *hh:mm:ss:ff + let mut comp = text.to_str().split(':'); + if let (Some(hh), Some(mm), Some(ss), Some(ff)) = + (comp.next(), comp.next(), comp.next(), comp.next()) + { + format!("{hh}{mm:0>2}{ss:0>2} {ff:0>2}") + } else { + String::new() + } + } + AbsoluteFrames => { + let text = reaper.format_timestr_pos( + self.reversely_corrected_display_pos(), + 32, + TimeModeOverride::Mode(TimeMode::AbsoluteFrames), + ); + text.into_string() + } + Unknown(m) => format!("{m:?}"), + }; + Some(PropValue::Text(text.into())) + } + _ => None, + } + } + _ => None, + } + } +} + +impl SeekTarget { + /// Substracts the project time offset if the seek context is not the project. + pub fn reversely_corrected_display_pos(&self) -> PositionInSeconds { + let (pos, seek_info) = self.internal_display_info(); + if seek_info.context == SeekContext::Project { + pos + } else { + pos - self.project.time_offset() + } + } + + /// Adds the project time offset if the seek context is the project. + pub fn corrected_display_pos(&self) -> PositionInSeconds { + let (pos, seek_info) = self.internal_display_info(); + if seek_info.context == SeekContext::Project { + self.project.time_offset() + pos + } else { + pos + } + } + + fn internal_display_info(&self) -> (PositionInSeconds, SeekInfo) { + let pos = self + .project + .play_or_edit_cursor_position() + .unwrap_or_default(); + let info = get_seek_info(self.project, self.options, true); + (pos - info.start_pos, info) + } +} + +#[derive(Eq, PartialEq)] +enum SeekContext { + TimeSelection, + LoopPoints, + Region, + Project, + Viewport, +} + +impl<'a> Target<'a> for SeekTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = current_value_of_seek( + self.project, + self.options, + self.project + .play_or_edit_cursor_position() + .unwrap_or_default(), + ); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +fn current_value_of_seek( + project: Project, + options: SeekOptions, + pos: PositionInSeconds, +) -> UnitValue { + let info = get_seek_info(project, options, false); + if pos < info.start_pos { + UnitValue::MIN + } else { + let pos_within_range = pos.get() - info.start_pos.get(); + UnitValue::new_clamped(pos_within_range / info.length()) + } +} + +fn get_seek_info(project: Project, options: SeekOptions, ignore_project_length: bool) -> SeekInfo { + if options.use_time_selection { + if let Some(r) = project.time_selection() { + return SeekInfo::from_time_range(SeekContext::TimeSelection, r); + } + } + if options.use_loop_points { + if let Some(r) = project.loop_points() { + return SeekInfo::from_time_range(SeekContext::LoopPoints, r); + } + } + if options.use_regions { + let bm = project.current_bookmark(); + if let Some(i) = bm.region_index { + if let Some(bm) = project.find_bookmark_by_index(i) { + let info = bm.basic_info(); + if let Some(end_pos) = info.region_end_position { + return SeekInfo::new(SeekContext::Region, info.position, end_pos); + } + } + } + } + if options.use_project { + if ignore_project_length { + return SeekInfo::new( + SeekContext::Project, + PositionInSeconds::ZERO, + PositionInSeconds::new_panic(f64::MAX), + ); + } else { + let length = project.length(); + if length.get() > 0.0 { + return SeekInfo::new(SeekContext::Project, PositionInSeconds::ZERO, length.into()); + } + } + } + // Last fallback: Viewport seeking. We always have a viewport + let result = Reaper::get() + .medium_reaper() + .get_set_arrange_view_2_get(project.context(), 0, 0); + SeekInfo::new(SeekContext::Viewport, result.start_time, result.end_time) +} + +struct SeekInfo { + pub context: SeekContext, + pub start_pos: PositionInSeconds, + pub end_pos: PositionInSeconds, +} + +impl SeekInfo { + pub fn new( + context: SeekContext, + start_pos: PositionInSeconds, + end_pos: PositionInSeconds, + ) -> Self { + Self { + context, + start_pos, + end_pos, + } + } + + fn from_time_range(context: SeekContext, range: GetLoopTimeRange2Result) -> Self { + Self::new(context, range.start, range.end) + } + + pub fn length(&self) -> f64 { + self.end_pos.get() - self.start_pos.get() + } +} + +pub const SEEK_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Project, + name: "Seek", + short_name: "Seek", + supports_feedback_resolution: true, + supports_seek_behavior: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/stream_deck_brightness_target.rs b/plugin-reaper-realearn/main/src/domain/targets/stream_deck_brightness_target.rs new file mode 100644 index 0000000..17eccbe --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/stream_deck_brightness_target.rs @@ -0,0 +1,80 @@ +use crate::domain::{ + Backbone, CompartmentKind, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; +use std::fmt::Debug; + +#[derive(Debug)] +pub struct UnresolvedStreamDeckBrightnessTarget {} + +impl UnresolvedReaperTargetDef for UnresolvedStreamDeckBrightnessTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + let target = StreamDeckBrightnessTarget {}; + Ok(vec![ReaperTarget::StreamDeckBrightness(target)]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StreamDeckBrightnessTarget {} + +impl RealearnTarget for StreamDeckBrightnessTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + let dev_id = context + .control_context + .stream_deck_dev_id + .ok_or("no stream deck device selected")?; + Backbone::get() + .set_stream_deck_brightness(dev_id, value.to_unit_value()?) + .map_err(|_| "couldn't set brightness")?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::StreamDeckBrightness) + } + + fn supports_automatic_feedback(&self) -> bool { + false + } + + fn can_report_current_value(&self) -> bool { + false + } +} + +impl<'a> Target<'a> for StreamDeckBrightnessTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + None + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const STREAM_DECK_BRIGHTNESS_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::StreamDeck, + name: "Brightness", + short_name: "Brightness", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/take_mapping_snapshot_target.rs b/plugin-reaper-realearn/main/src/domain/targets/take_mapping_snapshot_target.rs new file mode 100644 index 0000000..232961c --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/take_mapping_snapshot_target.rs @@ -0,0 +1,186 @@ +use crate::domain::{ + CompartmentKind, ControlContext, ExtendedProcessorContext, HitInstruction, + HitInstructionContext, HitInstructionResponse, HitResponse, MappingControlContext, + MappingSnapshot, MappingSnapshotId, RealearnTarget, ReaperTarget, ReaperTargetType, TagScope, + TargetCharacter, TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target}; +use helgobox_api::persistence::MappingSnapshotDescForTake; + +#[derive(Debug)] +pub struct UnresolvedTakeMappingSnapshotTarget { + pub compartment: CompartmentKind, + /// Mappings which are not in the tag scope don't make it into the snapshot. + pub scope: TagScope, + /// Defines whether mappings that are inactive due to conditional activation should make it + /// into the snapshot or not. + /// + /// Mappings which are explicitly disabled for control are ignored anyway because they won't be + /// loaded with the "Load mapping snapshot" target anyway. + pub active_mappings_only: bool, + pub snapshot_id: VirtualMappingSnapshotIdForTake, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum VirtualMappingSnapshotIdForTake { + LastLoaded, + ById(MappingSnapshotId), +} + +impl VirtualMappingSnapshotIdForTake { + pub fn id(&self) -> Option<&MappingSnapshotId> { + match self { + VirtualMappingSnapshotIdForTake::LastLoaded => None, + VirtualMappingSnapshotIdForTake::ById(id) => Some(id), + } + } +} + +impl TryFrom for VirtualMappingSnapshotIdForTake { + type Error = &'static str; + + fn try_from(value: MappingSnapshotDescForTake) -> Result { + let res = match value { + MappingSnapshotDescForTake::LastLoaded => Self::LastLoaded, + MappingSnapshotDescForTake::ById { id } => Self::ById(id.parse()?), + }; + Ok(res) + } +} + +impl From for MappingSnapshotDescForTake { + fn from(value: VirtualMappingSnapshotIdForTake) -> Self { + match value { + VirtualMappingSnapshotIdForTake::LastLoaded => Self::LastLoaded, + VirtualMappingSnapshotIdForTake::ById(s) => Self::ById { id: s.to_string() }, + } + } +} + +impl UnresolvedReaperTargetDef for UnresolvedTakeMappingSnapshotTarget { + fn resolve( + &self, + _: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::TakeMappingSnapshot( + TakeMappingSnapshotTarget { + compartment: self.compartment, + scope: self.scope.clone(), + active_mappings_only: self.active_mappings_only, + snapshot_id: self.snapshot_id.clone(), + }, + )]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TakeMappingSnapshotTarget { + pub compartment: CompartmentKind, + pub scope: TagScope, + pub active_mappings_only: bool, + pub snapshot_id: VirtualMappingSnapshotIdForTake, +} + +impl RealearnTarget for TakeMappingSnapshotTarget { + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TakeMappingSnapshot) + } + + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + let instruction = TakeMappingSnapshotInstruction { + compartment: self.compartment, + // So far this clone is okay because saveing a snapshot is not something that happens + // every few milliseconds. No need to use a ref to this target. + scope: self.scope.clone(), + active_mappings_only: self.active_mappings_only, + snapshot_id: self.snapshot_id.clone(), + }; + Ok(HitResponse::hit_instruction(Box::new(instruction))) + } + + fn can_report_current_value(&self) -> bool { + false + } + + fn is_available(&self, _: ControlContext) -> bool { + true + } +} + +impl<'a> Target<'a> for TakeMappingSnapshotTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + None + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const SAVE_MAPPING_SNAPSHOT_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::ReaLearn, + name: "Take mapping snapshot", + short_name: "Take mapping snapshot", + supports_tags: true, + ..DEFAULT_TARGET +}; + +struct TakeMappingSnapshotInstruction { + compartment: CompartmentKind, + scope: TagScope, + active_mappings_only: bool, + snapshot_id: VirtualMappingSnapshotIdForTake, +} + +impl HitInstruction for TakeMappingSnapshotInstruction { + fn execute(self: Box, context: HitInstructionContext) -> HitInstructionResponse { + let target_values = context + .mappings + .values_mut() + .filter_map(|m| { + if !m.control_is_enabled() { + return None; + } + if self.scope.has_tags() && !m.has_any_tag(&self.scope.tags) { + return None; + } + if self.active_mappings_only && !m.is_active() { + return None; + } + let target_value = m.current_aggregated_target_value(context.control_context)?; + Some((m.id(), target_value)) + }) + .collect(); + let snapshot = MappingSnapshot::new(target_values); + let mut instance_state = context.control_context.unit.borrow_mut(); + let snapshot_container = instance_state.mapping_snapshot_container_mut(self.compartment); + let resolved_snapshot_id = match self.snapshot_id { + VirtualMappingSnapshotIdForTake::LastLoaded => { + match snapshot_container.last_loaded_snapshot_id(&self.scope) { + None => return HitInstructionResponse::Ignored, + Some(id) => id, + } + } + VirtualMappingSnapshotIdForTake::ById(id) => id, + }; + snapshot_container.update_snapshot(resolved_snapshot_id, snapshot); + HitInstructionResponse::CausedEffect(vec![]) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/tempo_target.rs b/plugin-reaper-realearn/main/src/domain/targets/tempo_target.rs new file mode 100644 index 0000000..d029db4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/tempo_target.rs @@ -0,0 +1,150 @@ +use crate::domain::{ + bpm_span, format_step_size_as_bpm_without_unit, format_value_as_bpm_without_unit, + parse_step_size_from_bpm, parse_value_from_bpm, tempo_unit_value, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, NumericValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Tempo}; +use reaper_medium::UndoBehavior; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTempoTarget; + +impl UnresolvedReaperTargetDef for UnresolvedTempoTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::Tempo(TempoTarget { + project: context.context().project_or_current_project(), + })]) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TempoTarget { + pub project: Project, +} + +impl RealearnTarget for TempoTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRoundable { + rounding_step_size: UnitValue::new(1.0 / bpm_span()), + }, + TargetCharacter::Continuous, + ) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_bpm(text) + } + + fn parse_as_step_size(&self, text: &str, _: ControlContext) -> Result { + parse_step_size_from_bpm(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_bpm_without_unit(value) + } + + fn format_step_size_without_unit(&self, step_size: UnitValue, _: ControlContext) -> String { + format_step_size_as_bpm_without_unit(step_size) + } + + fn hide_formatted_value(&self, _: ControlContext) -> bool { + true + } + + fn hide_formatted_step_size(&self, _: ControlContext) -> bool { + true + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "bpm" + } + + fn step_size_unit(&self, _: ControlContext) -> &'static str { + "bpm" + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let tempo = reaper_high::Tempo::from_normalized_value(value.to_unit_value()?.get()); + self.project.set_tempo(tempo, UndoBehavior::OmitUndoPoint)?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.project.is_available() + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::MasterTempoChanged(e)) + if e.project == self.project => + { + ( + true, + Some(AbsoluteValue::Continuous(tempo_unit_value( + reaper_high::Tempo::from_bpm(e.new_value), + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(format!("{:.2} bpm", self.tempo().bpm().get()).into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal(self.tempo().bpm().get())) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::Tempo) + } +} + +impl TempoTarget { + fn tempo(&self) -> Tempo { + self.project.tempo() + } +} + +impl<'a> Target<'a> for TempoTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = tempo_unit_value(self.tempo()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TEMPO_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Project, + name: "Set tempo", + short_name: "Tempo", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_arm_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_arm_target.rs new file mode 100644 index 0000000..c9430ef --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_arm_target.rs @@ -0,0 +1,151 @@ +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, + track_arm_unit_value, with_gang_behavior, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + TrackExclusivity, TrackGangBehavior, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackArmTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackArmTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackArm(TrackArmTarget { + track, + exclusivity: self.exclusivity, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackArmTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackArmTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + with_gang_behavior( + self.track.project(), + self.gang_behavior, + &TRACK_ARM_TARGET, + |gang_behavior, grouping_behavior| { + change_track_prop( + &self.track, + self.exclusivity, + value, + |t| t.arm(false, gang_behavior, grouping_behavior), + |t| t.disarm(false, gang_behavior, grouping_behavior), + ); + Ok(()) + }, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackArmChanged(e)) + if e.track == self.track => + { + ( + true, + Some(AbsoluteValue::Continuous(track_arm_unit_value(e.new_value))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackArm) + } +} + +impl<'a> Target<'a> for TrackArmTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = track_arm_unit_value(self.track.is_armed(false)); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_ARM_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Arm/disarm", + short_name: "(Dis)arm track", + supports_track: true, + supports_track_exclusivity: true, + supports_gang_selected: true, + supports_gang_grouping: true, + supports_track_grouping_only_gang_behavior: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_automation_mode_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_automation_mode_target.rs new file mode 100644 index 0000000..6c1c663 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_automation_mode_target.rs @@ -0,0 +1,153 @@ +use crate::domain::{ + automation_mode_unit_value, change_track_prop, format_value_as_on_off, get_effective_tracks, + CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, TrackExclusivity, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Track}; +use reaper_medium::AutomationMode; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackAutomationModeTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub mode: AutomationMode, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackAutomationModeTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackAutomationMode(TrackAutomationModeTarget { + track, + exclusivity: self.exclusivity, + mode: self.mode, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackAutomationModeTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub mode: AutomationMode, +} + +impl RealearnTarget for TrackAutomationModeTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // Retriggerable because of #277 + if self.exclusivity == TrackExclusivity::NonExclusive { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } else { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + change_track_prop( + &self.track, + self.exclusivity, + value.to_unit_value()?, + |t| t.set_automation_mode(self.mode), + |t| t.set_automation_mode(AutomationMode::TrimRead), + ); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackAutomationModeChanged(e)) + if e.track == self.track => + { + ( + true, + Some(AbsoluteValue::Continuous(automation_mode_unit_value( + self.mode, + e.new_value, + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackAutomationMode) + } +} + +impl<'a> Target<'a> for TrackAutomationModeTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = automation_mode_unit_value(self.mode, self.track.automation_mode()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_AUTOMATION_MODE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Set automation mode", + short_name: "Track automation mode", + supports_track: true, + supports_track_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_monitoring_mode_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_monitoring_mode_target.rs new file mode 100644 index 0000000..70e324a --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_monitoring_mode_target.rs @@ -0,0 +1,184 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + change_track_prop, format_value_as_on_off, get_effective_tracks, with_gang_behavior, + CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, TrackExclusivity, TrackGangBehavior, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Track}; +use reaper_medium::InputMonitoringMode; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackMonitoringModeTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub mode: InputMonitoringMode, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackMonitoringModeTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackMonitoringMode(TrackMonitoringModeTarget { + track, + exclusivity: self.exclusivity, + mode: self.mode, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackMonitoringModeTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub mode: InputMonitoringMode, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackMonitoringModeTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + // Retriggerable because of #277 + if self.exclusivity == TrackExclusivity::NonExclusive { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Switch, + ) + } else { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + with_gang_behavior( + self.track.project(), + self.gang_behavior, + &TRACK_MONITORING_MODE_TARGET, + |gang_behavior, grouping_behavior| { + change_track_prop( + &self.track, + self.exclusivity, + value, + |t| t.set_input_monitoring_mode(self.mode, gang_behavior, grouping_behavior), + |t| { + t.set_input_monitoring_mode( + InputMonitoringMode::Off, + gang_behavior, + grouping_behavior, + ) + }, + ); + Ok(()) + }, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackInputMonitoringChanged(e)) + if e.track == self.track => + { + ( + true, + Some(AbsoluteValue::Continuous(monitoring_mode_unit_value( + self.mode, + e.new_value, + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackMonitoringMode) + } +} + +impl<'a> Target<'a> for TrackMonitoringModeTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = monitoring_mode_unit_value(self.mode, self.track.input_monitoring_mode()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_MONITORING_MODE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Set monitoring mode", + short_name: "Track monitoring mode", + hint: "grouping support since REAPER v6.70", + supports_track: true, + supports_track_exclusivity: true, + supports_gang_selected: true, + supports_gang_grouping: true, + supports_track_grouping_only_gang_behavior: true, + ..DEFAULT_TARGET +}; + +pub fn monitoring_mode_unit_value( + desired_mode: InputMonitoringMode, + actual_mode: InputMonitoringMode, +) -> UnitValue { + let is_on = desired_mode == actual_mode; + convert_bool_to_unit_value(is_on) +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_mute_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_mute_target.rs new file mode 100644 index 0000000..ddf1139 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_mute_target.rs @@ -0,0 +1,151 @@ +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, mute_unit_value, + with_gang_behavior, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + TrackExclusivity, TrackGangBehavior, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackMuteTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackMuteTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackMute(TrackMuteTarget { + track, + exclusivity: self.exclusivity, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackMuteTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackMuteTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + with_gang_behavior( + self.track.project(), + self.gang_behavior, + &TRACK_MUTE_TARGET, + |gang_behavior, grouping_behavior| { + change_track_prop( + &self.track, + self.exclusivity, + value, + |t| t.mute(gang_behavior, grouping_behavior), + |t| t.unmute(gang_behavior, grouping_behavior), + ); + Ok(()) + }, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackMuteChanged(e)) + if e.track == self.track => + { + ( + true, + Some(AbsoluteValue::Continuous(mute_unit_value(e.new_value))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackMute) + } +} + +impl<'a> Target<'a> for TrackMuteTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = mute_unit_value(self.track.is_muted()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_MUTE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Mute/unmute", + short_name: "(Un)mute track", + supports_track: true, + supports_track_exclusivity: true, + supports_gang_selected: true, + supports_gang_grouping: true, + supports_track_grouping_only_gang_behavior: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_pan_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_pan_target.rs new file mode 100644 index 0000000..467715e --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_pan_target.rs @@ -0,0 +1,201 @@ +use crate::domain::{ + format_value_as_pan, get_effective_tracks, pan_unit_value, parse_value_from_pan, + with_gang_behavior, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + TrackGangBehavior, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, PropValue, Target, UnitValue, + BASE_EPSILON, +}; +use reaper_high::{AvailablePanValue, ChangeEvent, Pan, PanExt, Project, Track, TrackSetSmartOpts}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackPanTarget { + pub track_descriptor: TrackDescriptor, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackPanTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackPan(TrackPanTarget { + track, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackPanTarget { + pub track: Track, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackPanTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_pan(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_pan(value) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn hide_formatted_value(&self, _: ControlContext) -> bool { + true + } + + fn hide_formatted_step_size(&self, _: ControlContext) -> bool { + true + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "" + } + + fn step_size_unit(&self, _: ControlContext) -> &'static str { + "" + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_pan(value) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let pan = Pan::from_normalized_value(value.to_unit_value()?.get()); + with_gang_behavior( + self.track.project(), + self.gang_behavior, + &TRACK_PAN_TARGET, + |gang_behavior, grouping_behavior| { + self.track.set_pan_smart( + pan.reaper_value(), + TrackSetSmartOpts { + gang_behavior, + grouping_behavior, + // Important because we don't want undo points for each little fader movement! + done: false, + }, + ) + }, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackPanChanged(e)) + if e.track == self.track => + { + (true, { + let pan = match e.new_value { + AvailablePanValue::Complete(v) => v.main_pan(), + AvailablePanValue::Incomplete(pan) => pan, + }; + Some(AbsoluteValue::Continuous(pan_unit_value( + Pan::from_reaper_value(pan), + ))) + }) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(self.pan().to_string().into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal(self.pan().reaper_value().get())) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackPan) + } + + fn prop_value(&self, key: &str, _: ControlContext) -> Option { + match key { + "pan.mcu" => { + let pan = self.pan().reaper_value().get(); + let text: Cow<'static, str> = if pan.abs() < BASE_EPSILON { + " ".into() + } else if pan < 0.0 { + format!("<{:>3.0} ", pan.abs() * 100.0).into() + } else { + format!(" {:<3.0}>", pan * 100.0).into() + }; + Some(PropValue::Text(text)) + } + _ => None, + } + } +} + +impl TrackPanTarget { + fn pan(&self) -> Pan { + self.track.pan() + } +} + +impl<'a> Target<'a> for TrackPanTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = pan_unit_value(self.pan()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_PAN_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Set pan", + short_name: "Track pan", + supports_track: true, + supports_gang_selected: true, + supports_gang_grouping: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_parent_send_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_parent_send_target.rs new file mode 100644 index 0000000..3f4feec --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_parent_send_target.rs @@ -0,0 +1,135 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, TrackExclusivity, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackParentSendTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackParentSendTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackParentSend(TrackParentSendTarget { + track, + exclusivity: self.exclusivity, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackParentSendTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, +} + +impl RealearnTarget for TrackParentSendTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + change_track_prop( + &self.track, + self.exclusivity, + value.to_unit_value()?, + |t| t.set_parent_send_enabled(true), + |t| t.set_parent_send_enabled(false), + ); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + // Yes, for some reason, changing send to parent triggers a pan-changed event. + CompoundChangeEvent::Reaper(ChangeEvent::TrackPanChanged(e)) + if e.track == self.track => + { + (true, None) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackParentSend) + } +} + +impl<'a> Target<'a> for TrackParentSendTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = convert_bool_to_unit_value(self.track.parent_send_enabled()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_PARENT_SEND_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Enable/disable parent send", + short_name: "Enable/disable parent send", + supports_track: true, + supports_track_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_peak_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_peak_target.rs new file mode 100644 index 0000000..f25971e --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_peak_target.rs @@ -0,0 +1,129 @@ +use crate::domain::ui_util::{ + format_value_as_db_without_unit, parse_value_from_db, volume_unit_value, +}; +use crate::domain::{ + get_effective_tracks, CompartmentKind, ControlContext, ExtendedProcessorContext, + FeedbackResolution, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use base::peak_util; +use helgoboss_learn::{AbsoluteValue, ControlType, NumericValue, Target, UnitValue}; +use reaper_high::{Project, SliderVolume, Track}; +use reaper_medium::ReaperVolumeValue; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackPeakTarget { + pub track_descriptor: TrackDescriptor, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackPeakTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| ReaperTarget::TrackPeak(TrackPeakTarget { track })) + .collect(), + ) + } + + fn feedback_resolution(&self) -> Option { + Some(FeedbackResolution::High) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackPeakTarget { + pub track: Track, +} + +impl<'a> Target<'a> for TrackPeakTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let vol = self.peak()?; + let val = volume_unit_value(vol); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +impl TrackPeakTarget { + fn peak(&self) -> Option { + if peak_util::peaks_should_be_hidden(&self.track) { + return Some(SliderVolume::MIN); + } + let peaks = peak_util::get_track_peaks(self.track.raw().ok()?); + let channel_count = peaks.len(); + if channel_count == 0 { + return None; + } + let sum: f64 = peaks.map(|v| v.get()).sum(); + let avg = sum / channel_count as f64; + let vol = ReaperVolumeValue::new_panic(avg); + Some(SliderVolume::from_reaper_value(vol)) + } +} + +impl RealearnTarget for TrackPeakTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_db(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_db_without_unit(value) + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "dB" + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(self.peak()?.to_string().into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal(self.peak()?.db().get())) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackPeak) + } +} + +pub const TRACK_PEAK_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Peak", + short_name: "Track peak", + hint: "Feedback only, no control", + supports_track: true, + supports_control: false, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_phase_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_phase_target.rs new file mode 100644 index 0000000..fa55580 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_phase_target.rs @@ -0,0 +1,144 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, CompartmentKind, + ControlContext, ExtendedProcessorContext, FeedbackResolution, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, TrackExclusivity, TrackGangBehavior, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track}; +use reaper_medium::TrackPolarity; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackPhaseTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, + pub poll_for_feedback: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackPhaseTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackPhase(TrackPhaseTarget { + track, + exclusivity: self.exclusivity, + gang_behavior: self.gang_behavior, + poll_for_feedback: self.poll_for_feedback, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } + + fn feedback_resolution(&self) -> Option { + if self.poll_for_feedback { + Some(FeedbackResolution::High) + } else { + None + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackPhaseTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, + pub poll_for_feedback: bool, +} + +impl RealearnTarget for TrackPhaseTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let (gang_behavior, grouping_behavior) = self.gang_behavior.gang_and_grouping_behavior(); + change_track_prop( + &self.track, + self.exclusivity, + value.to_unit_value()?, + |t| t.set_phase_inverted(TrackPolarity::Inverted, gang_behavior, grouping_behavior), + |t| t.set_phase_inverted(TrackPolarity::Normal, gang_behavior, grouping_behavior), + ); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn supports_automatic_feedback(&self) -> bool { + self.poll_for_feedback + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackPhase) + } +} + +impl<'a> Target<'a> for TrackPhaseTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = convert_bool_to_unit_value(self.track.phase_is_inverted()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_PHASE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Phase invert/normal", + short_name: "Track phase", + hint: "Ganging/grouping support from REAPER v6.70", + supports_track: true, + supports_track_exclusivity: true, + supports_gang_selected: true, + supports_gang_grouping: true, + supports_track_grouping_only_gang_behavior: true, + supports_poll_for_feedback: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_selection_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_selection_target.rs new file mode 100644 index 0000000..5653f18 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_selection_target.rs @@ -0,0 +1,169 @@ +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, + track_selected_unit_value, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + TrackExclusivity, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Reaper, Track}; +use reaper_medium::CommandId; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackSelectionTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub scroll_arrange_view: bool, + pub scroll_mixer: bool, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackSelectionTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackSelection(TrackSelectionTarget { + track, + exclusivity: self.exclusivity, + scroll_arrange_view: self.scroll_arrange_view, + scroll_mixer: self.scroll_mixer, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackSelectionTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub scroll_arrange_view: bool, + pub scroll_mixer: bool, +} + +impl RealearnTarget for TrackSelectionTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + use TrackExclusivity::*; + let select_exclusively_within_project = !value.is_zero() + && matches!( + self.exclusivity, + ExclusiveWithinProject | ExclusiveWithinProjectOnOnly + ); + if select_exclusively_within_project { + // We have a dedicated REAPER function to select the track exclusively. + self.track.select_exclusively(); + } else { + change_track_prop( + &self.track, + self.exclusivity, + value, + |t| t.select(), + |t| t.unselect(), + ); + } + if self.scroll_arrange_view { + Reaper::get() + .main_section() + .action_by_command_id(CommandId::new(40913)) + .invoke_as_trigger(Some(self.track.project()), None) + .expect("built-in action should exist"); + } + if self.scroll_mixer { + self.track.scroll_mixer(); + } + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackSelectedChanged(e)) + if e.track == self.track => + { + ( + true, + Some(AbsoluteValue::Continuous(track_selected_unit_value( + e.new_value, + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackSelection) + } +} + +impl<'a> Target<'a> for TrackSelectionTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = track_selected_unit_value(self.track.is_selected()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_SELECTION_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Select/unselect", + short_name: "(Un)select track", + supports_track: true, + if_so_supports_track_must_be_selected: false, + supports_track_scrolling: true, + supports_track_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_show_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_show_target.rs new file mode 100644 index 0000000..9cb0c0a --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_show_target.rs @@ -0,0 +1,145 @@ +use crate::domain::ui_util::convert_bool_to_unit_value; +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, TrackExclusivity, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Track}; +use reaper_medium::TrackArea; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackShowTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub area: TrackArea, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackShowTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackShow(TrackShowTarget { + track, + exclusivity: self.exclusivity, + area: self.area, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackShowTarget { + pub track: Track, + pub exclusivity: TrackExclusivity, + pub area: TrackArea, +} + +impl RealearnTarget for TrackShowTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + change_track_prop( + &self.track, + self.exclusivity, + value.to_unit_value()?, + |t| t.set_shown(self.area, true), + |t| t.set_shown(self.area, false), + ); + Ok(HitResponse::processed_with_effect()) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackVisibilityChanged(e)) + if e.track == self.track => + { + let is_shown = match self.area { + TrackArea::Tcp => e.new_value.tcp, + TrackArea::Mcp => e.new_value.mcp, + }; + let feedback_value = + AbsoluteValue::Continuous(convert_bool_to_unit_value(is_shown)); + (true, Some(feedback_value)) + } + _ => (false, None), + } + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackShow) + } +} + +impl<'a> Target<'a> for TrackShowTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let is_shown = self.track.is_shown(self.area); + let val = convert_bool_to_unit_value(is_shown); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_SHOW_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Show/hide", + short_name: "Show/hide track", + supports_track: true, + supports_track_exclusivity: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_solo_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_solo_target.rs new file mode 100644 index 0000000..5dc60cb --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_solo_target.rs @@ -0,0 +1,159 @@ +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, + track_solo_unit_value, with_gang_behavior, with_solo_behavior, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, SoloBehavior, + TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, TrackExclusivity, + TrackGangBehavior, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackSoloTarget { + pub track_descriptor: TrackDescriptor, + pub exclusivity: TrackExclusivity, + pub behavior: SoloBehavior, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackSoloTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackSolo(TrackSoloTarget { + track, + exclusivity: self.exclusivity, + behavior: self.behavior, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackSoloTarget { + pub track: Track, + pub behavior: SoloBehavior, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackSoloTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let value = value.to_unit_value()?; + with_gang_behavior( + self.track.project(), + self.gang_behavior, + &TRACK_SOLO_TARGET, + |gang_behavior, grouping_behavior| { + with_solo_behavior(self.behavior, || { + change_track_prop( + &self.track, + self.exclusivity, + value, + |t| t.solo(gang_behavior, grouping_behavior), + |t| t.unsolo(gang_behavior, grouping_behavior), + ); + }); + Ok(()) + }, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackSoloChanged(e)) + if e.track == self.track => + { + ( + true, + Some(AbsoluteValue::Continuous(track_solo_unit_value( + e.new_value, + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackSolo) + } +} + +impl<'a> Target<'a> for TrackSoloTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = track_solo_unit_value(self.track.is_solo()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_SOLO_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Solo/unsolo", + short_name: "(Un)solo track", + supports_track: true, + supports_track_exclusivity: true, + supports_gang_selected: true, + supports_gang_grouping: true, + supports_track_grouping_only_gang_behavior: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_tool_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_tool_target.rs new file mode 100644 index 0000000..b4c8ed3 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_tool_target.rs @@ -0,0 +1,189 @@ +use crate::domain::{ + get_effective_tracks, get_track_name, percentage_for_scoped_track_within_project, + ChangeInstanceTrackArgs, CompartmentKind, ControlContext, ExtendedProcessorContext, + HitResponse, InstanceTrackChangeRequest, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TagScope, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, NumericValue, Target}; +use helgobox_api::persistence::{TrackScope, TrackToolAction}; +use reaper_high::{Project, Track}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackToolTarget { + pub track_descriptor: TrackDescriptor, + pub action: TrackToolAction, + pub scope: TagScope, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackToolTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + let tracks = get_effective_tracks(context, &self.track_descriptor.track, compartment) + .and_then(|tracks| { + if tracks.is_empty() { + Err("resolved to zero tracks") + } else { + Ok(tracks) + } + }); + let targets = match tracks { + Ok(tracks) => tracks + .into_iter() + .map(|track| { + ReaperTarget::TrackTool(TrackToolTarget { + track: Some(track), + action: self.action, + scope: self.scope.clone(), + }) + }) + .collect(), + Err(e) => { + if self.action == TrackToolAction::SetAsUnitTrack { + // If we just want to *set* the (unresolved) track as instance track, we + // don't need a resolved target. + let target = ReaperTarget::TrackTool(TrackToolTarget { + track: None, + action: self.action, + scope: self.scope.clone(), + }); + vec![target] + } else { + // Otherwise we should classify the target as inactive. + return Err(e); + } + } + }; + Ok(targets) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackToolTarget { + pub track: Option, + pub action: TrackToolAction, + pub scope: TagScope, +} + +impl RealearnTarget for TrackToolTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ) + } + + fn is_available(&self, _: ControlContext) -> bool { + match &self.track { + None => false, + Some(t) => t.is_available(), + } + } + + fn project(&self) -> Option { + Some(self.track.as_ref()?.project()) + } + + fn track(&self) -> Option<&Track> { + self.track.as_ref() + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(get_track_name(self.track.as_ref()?, TrackScope::AllTracks).into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + let position = match self.track.as_ref()?.index() { + None => 0, + Some(i) => i + 1, + }; + Some(NumericValue::Discrete(position as _)) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackTool) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + let request = match self.action { + TrackToolAction::DoNothing => return Ok(HitResponse::ignored()), + TrackToolAction::SetAsUnitTrack => InstanceTrackChangeRequest::SetFromMapping( + context.mapping_data.qualified_mapping_id(), + ), + TrackToolAction::PinAsUnitTrack => { + let track = self.track.as_ref().ok_or("track could not be resolved")?; + let guid = if track.is_master_track() { + None + } else { + Some(*track.guid()) + }; + InstanceTrackChangeRequest::Pin(guid) + } + }; + let args = ChangeInstanceTrackArgs { + common: context + .control_context + .create_modify_unit_container_common_args(&self.scope), + request, + }; + context + .control_context + .unit_container + .change_instance_track(args)?; + Ok(HitResponse::processed_with_effect()) + } + + fn can_report_current_value(&self) -> bool { + matches!(self.action, TrackToolAction::DoNothing) + } +} + +impl<'a> Target<'a> for TrackToolTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + match self.action { + TrackToolAction::DoNothing => { + let track_index = self.track.as_ref()?.index(); + let percentage = percentage_for_scoped_track_within_project( + self.track.as_ref()?.project(), + TrackScope::AllTracks, + track_index, + ); + Some(percentage) + } + TrackToolAction::SetAsUnitTrack | TrackToolAction::PinAsUnitTrack => { + // In future, we might support feedback here. + None + } + } + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_TOOL_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Track", + short_name: "Track", + supports_track: true, + supports_tags: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_touch_state_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_touch_state_target.rs new file mode 100644 index 0000000..b858a71 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_touch_state_target.rs @@ -0,0 +1,200 @@ +use crate::domain::{ + change_track_prop, format_value_as_on_off, + get_control_type_and_character_for_track_exclusivity, get_effective_tracks, touched_unit_value, + AdditionalFeedbackEvent, Backbone, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + TrackExclusivity, TrackGangBehavior, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{Project, Track}; +use std::borrow::Cow; +use strum::EnumIter; + +#[derive(Debug)] +pub struct UnresolvedTrackTouchStateTarget { + pub track_descriptor: TrackDescriptor, + pub parameter_type: TouchedTrackParameterType, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackTouchStateTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackAutomationTouchState(TrackTouchStateTarget { + track, + parameter_type: self.parameter_type, + exclusivity: self.exclusivity, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackTouchStateTarget { + pub track: Track, + pub parameter_type: TouchedTrackParameterType, + pub exclusivity: TrackExclusivity, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackTouchStateTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + get_control_type_and_character_for_track_exclusivity(self.exclusivity) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let target_context = Backbone::target_state(); + change_track_prop( + &self.track, + self.exclusivity, + value.to_unit_value()?, + |t| { + target_context.borrow_mut().touch_automation_parameter( + t, + self.parameter_type, + self.gang_behavior, + ) + }, + |t| { + target_context.borrow_mut().untouch_automation_parameter( + t, + self.parameter_type, + self.gang_behavior, + ) + }, + ); + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn track_exclusivity(&self) -> Option { + Some(self.exclusivity) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Additional( + AdditionalFeedbackEvent::ParameterAutomationTouchStateChanged(e), + ) if self.track.raw().is_ok_and(|t| e.track == t) + && e.parameter_type == self.parameter_type => + { + ( + true, + Some(AbsoluteValue::Continuous(touched_unit_value(e.new_value))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackTouchState) + } +} + +impl<'a> Target<'a> for TrackTouchStateTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let is_touched = Backbone::target_state() + .borrow() + .automation_parameter_is_touched(self.track.raw().ok()?, self.parameter_type); + Some(AbsoluteValue::Continuous(touched_unit_value(is_touched))) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_TOUCH_STATE_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Set automation touch state", + short_name: "Track touch state", + supports_track: true, + supports_track_exclusivity: true, + supports_gang_selected: true, + supports_gang_grouping: true, + ..DEFAULT_TARGET +}; + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + serde_repr::Serialize_repr, + serde_repr::Deserialize_repr, + EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + derive_more::Display, +)] +#[repr(usize)] +pub enum TouchedTrackParameterType { + #[default] + Volume, + Pan, + Width, +} + +impl TouchedTrackParameterType { + pub fn try_from_reaper( + reaper_type: reaper_medium::TouchedParameterType, + ) -> Result { + use reaper_medium::TouchedParameterType::*; + let res = match reaper_type { + Volume => Self::Volume, + Pan => Self::Pan, + Width => Self::Width, + Unknown(_) => return Err("unknown touch parameter type"), + }; + Ok(res) + } +} diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_volume_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_volume_target.rs new file mode 100644 index 0000000..58beaf2 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_volume_target.rs @@ -0,0 +1,176 @@ +use crate::domain::ui_util::{ + format_value_as_db, format_value_as_db_without_unit, parse_value_from_db, volume_unit_value, +}; +use crate::domain::{ + get_effective_tracks, with_gang_behavior, CompartmentKind, CompoundChangeEvent, ControlContext, + ExtendedProcessorContext, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, + TrackGangBehavior, UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, NumericValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, SliderVolume, Track, TrackSetSmartOpts}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackVolumeTarget { + pub track_descriptor: TrackDescriptor, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackVolumeTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackVolume(TrackVolumeTarget { + track, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackVolumeTarget { + pub track: Track, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackVolumeTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_value_from_db(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_db_without_unit(value) + } + + fn hide_formatted_value(&self, _: ControlContext) -> bool { + true + } + + fn hide_formatted_step_size(&self, _: ControlContext) -> bool { + true + } + + fn value_unit(&self, _: ControlContext) -> &'static str { + "dB" + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_db(value) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let volume = SliderVolume::try_from_normalized_slider_value(value.to_unit_value()?.get()); + with_gang_behavior( + self.track.project(), + self.gang_behavior, + &TRACK_VOLUME_TARGET, + |gang_behavior, grouping_behavior| { + self.track.set_volume_smart( + volume.unwrap_or(SliderVolume::MIN).reaper_value(), + TrackSetSmartOpts { + gang_behavior, + grouping_behavior, + // Important because we don't want undo points for each little fader movement! + done: false, + }, + ) + }, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackVolumeChanged(e)) + if e.track == self.track => + { + ( + true, + Some(AbsoluteValue::Continuous(volume_unit_value( + SliderVolume::from_reaper_value(e.new_value), + ))), + ) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(self.volume().to_string().into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal(self.volume().db().get())) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackVolume) + } +} + +impl TrackVolumeTarget { + fn volume(&self) -> SliderVolume { + SliderVolume::from_reaper_value(self.track.volume()) + } +} + +impl<'a> Target<'a> for TrackVolumeTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = volume_unit_value(self.volume()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_VOLUME_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Set volume", + short_name: "Track volume", + supports_track: true, + supports_gang_selected: true, + supports_gang_grouping: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/track_width_target.rs b/plugin-reaper-realearn/main/src/domain/targets/track_width_target.rs new file mode 100644 index 0000000..49f8a04 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/track_width_target.rs @@ -0,0 +1,198 @@ +use crate::domain::ui_util::{ + format_as_double_percentage_without_unit, format_as_symmetric_percentage_without_unit, + parse_from_double_percentage, parse_from_symmetric_percentage, +}; +use crate::domain::{ + get_effective_tracks, width_unit_value, with_gang_behavior, CompartmentKind, + CompoundChangeEvent, ControlContext, ExtendedProcessorContext, HitResponse, + MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, + TargetSection, TargetTypeDef, TrackDescriptor, TrackGangBehavior, UnresolvedReaperTargetDef, + DEFAULT_TARGET, +}; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, NumericValue, PropValue, Target, UnitValue, +}; +use reaper_high::{ + AvailablePanValue, ChangeEvent, PanExt, Project, Track, TrackSetSmartOpts, Width, +}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTrackWidthTarget { + pub track_descriptor: TrackDescriptor, + pub gang_behavior: TrackGangBehavior, +} + +impl UnresolvedReaperTargetDef for UnresolvedTrackWidthTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + Ok( + get_effective_tracks(context, &self.track_descriptor.track, compartment)? + .into_iter() + .map(|track| { + ReaperTarget::TrackWidth(TrackWidthTarget { + track, + gang_behavior: self.gang_behavior, + }) + }) + .collect(), + ) + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + Some(&self.track_descriptor) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrackWidthTarget { + pub track: Track, + pub gang_behavior: TrackGangBehavior, +} + +impl RealearnTarget for TrackWidthTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + (ControlType::AbsoluteContinuous, TargetCharacter::Continuous) + } + + fn parse_as_value(&self, text: &str, _: ControlContext) -> Result { + parse_from_symmetric_percentage(text) + } + + fn parse_as_step_size(&self, text: &str, _: ControlContext) -> Result { + parse_from_double_percentage(text) + } + + fn format_value_without_unit(&self, value: UnitValue, _: ControlContext) -> String { + format_as_symmetric_percentage_without_unit(value) + } + + fn format_step_size_without_unit(&self, step_size: UnitValue, _: ControlContext) -> String { + format_as_double_percentage_without_unit(step_size) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.track.is_available() + } + + fn hide_formatted_value(&self, _: ControlContext) -> bool { + true + } + + fn hide_formatted_step_size(&self, _: ControlContext) -> bool { + true + } + + fn project(&self) -> Option { + Some(self.track.project()) + } + + fn track(&self) -> Option<&Track> { + Some(&self.track) + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + let width = Width::from_normalized_value(value.to_unit_value()?.get()); + with_gang_behavior( + self.track.project(), + self.gang_behavior, + &TRACK_WIDTH_TARGET, + |gang_behavior, grouping_behavior| { + self.track.set_width_smart( + width.reaper_value(), + TrackSetSmartOpts { + gang_behavior, + grouping_behavior, + // Important because we don't want undo points for each little fader movement! + done: false, + }, + ) + }, + )?; + Ok(HitResponse::processed_with_effect()) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(ChangeEvent::TrackPanChanged(e)) + if e.track == self.track => + { + ( + true, + match e.new_value { + AvailablePanValue::Complete(v) => v.width().map(|width| { + AbsoluteValue::Continuous(width_unit_value(Width::from_reaper_value( + width, + ))) + }), + AvailablePanValue::Incomplete(_) => None, + }, + ) + } + _ => (false, None), + } + } + + fn text_value(&self, _: ControlContext) -> Option> { + Some(format!("{:.2}", self.width().reaper_value().get()).into()) + } + + fn numeric_value(&self, _: ControlContext) -> Option { + Some(NumericValue::Decimal(self.width().reaper_value().get())) + } + + fn prop_value(&self, key: &str, _: ControlContext) -> Option { + match key { + "width.mcu" => { + let width = self.width().reaper_value().get(); + let text = format!("{:>4.0}W", width * 100.0); + Some(PropValue::Text(text.into())) + } + _ => None, + } + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::TrackWidth) + } +} + +impl TrackWidthTarget { + fn width(&self) -> Width { + self.track.width() + } +} + +impl<'a> Target<'a> for TrackWidthTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + let val = width_unit_value(self.width()); + Some(AbsoluteValue::Continuous(val)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRACK_WIDTH_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Track, + name: "Set stereo pan width", + short_name: "Track pan width", + supports_track: true, + supports_gang_selected: true, + supports_gang_grouping: true, + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/targets/transport_target.rs b/plugin-reaper-realearn/main/src/domain/targets/transport_target.rs new file mode 100644 index 0000000..e6dd0cc --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/targets/transport_target.rs @@ -0,0 +1,222 @@ +use crate::domain::{ + format_value_as_on_off, transport_is_enabled_unit_value, AdditionalFeedbackEvent, + CompartmentKind, CompoundChangeEvent, ControlContext, ExtendedProcessorContext, + FeedbackResolution, HitResponse, MappingControlContext, RealearnTarget, ReaperTarget, + ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TransportAction, + UnresolvedReaperTargetDef, DEFAULT_TARGET, +}; +use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Target, UnitValue}; +use reaper_high::{ChangeEvent, Project, Reaper}; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct UnresolvedTransportTarget { + pub action: TransportAction, +} + +impl UnresolvedReaperTargetDef for UnresolvedTransportTarget { + fn resolve( + &self, + context: ExtendedProcessorContext, + _: CompartmentKind, + ) -> Result, &'static str> { + Ok(vec![ReaperTarget::Transport(TransportTarget { + project: context.context().project_or_current_project(), + action: self.action, + })]) + } + + fn feedback_resolution(&self) -> Option { + Some(FeedbackResolution::Beat) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TransportTarget { + pub project: Project, + pub action: TransportAction, +} + +impl RealearnTarget for TransportTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + self.action.control_type_and_character() + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + _: MappingControlContext, + ) -> Result { + use TransportAction::*; + let on = !value.to_unit_value()?.is_zero(); + let response = match self.action { + PlayStop => { + if on { + self.project.play(); + } else { + self.project.stop(); + } + HitResponse::processed_with_effect() + } + PlayPause => { + if on { + self.project.play(); + } else { + self.project.pause(); + } + HitResponse::processed_with_effect() + } + Stop => { + if on { + self.project.stop(); + HitResponse::processed_with_effect() + } else { + HitResponse::ignored() + } + } + Pause => { + if on { + self.project.pause(); + HitResponse::processed_with_effect() + } else { + HitResponse::ignored() + } + } + RecordStop => { + if on { + Reaper::get().enable_record_in_current_project(); + } else { + Reaper::get().disable_record_in_current_project(); + } + HitResponse::processed_with_effect() + } + Repeat => { + if on { + self.project.enable_repeat(); + } else { + self.project.disable_repeat(); + } + HitResponse::processed_with_effect() + } + }; + Ok(response) + } + + fn is_available(&self, _: ControlContext) -> bool { + self.project.is_available() + } + + fn project(&self) -> Option { + Some(self.project) + } + + fn process_change_event( + &self, + evt: CompoundChangeEvent, + _: ControlContext, + ) -> (bool, Option) { + match evt { + CompoundChangeEvent::Reaper(evt) => { + use ChangeEvent::*; + use TransportAction::*; + match self.action { + PlayStop | PlayPause => match evt { + PlayStateChanged(e) if e.project == self.project => ( + true, + Some(AbsoluteValue::Continuous(transport_is_enabled_unit_value( + e.new_value.is_playing, + ))), + ), + _ => (false, None), + }, + Stop => match evt { + PlayStateChanged(e) if e.project == self.project => ( + true, + Some(AbsoluteValue::Continuous(transport_is_enabled_unit_value( + !e.new_value.is_playing && !e.new_value.is_paused, + ))), + ), + _ => (false, None), + }, + Pause => match evt { + PlayStateChanged(e) if e.project == self.project => ( + true, + Some(AbsoluteValue::Continuous(transport_is_enabled_unit_value( + e.new_value.is_paused, + ))), + ), + _ => (false, None), + }, + RecordStop => match evt { + PlayStateChanged(e) if e.project == self.project => ( + true, + Some(AbsoluteValue::Continuous(transport_is_enabled_unit_value( + e.new_value.is_recording, + ))), + ), + _ => (false, None), + }, + Repeat => match evt { + RepeatStateChanged(e) if e.project == self.project => ( + true, + Some(AbsoluteValue::Continuous(transport_is_enabled_unit_value( + e.new_value, + ))), + ), + _ => (false, None), + }, + } + } + CompoundChangeEvent::Additional(AdditionalFeedbackEvent::BeatChanged(e)) + if self.action != TransportAction::Repeat + && e.project == self.project + && e.project != Reaper::get().current_project() => + { + (true, None) + } + _ => (false, None), + } + } + + fn text_value(&self, context: ControlContext) -> Option> { + Some(format_value_as_on_off(self.current_value(context)?.to_unit_value()).into()) + } + + fn reaper_target_type(&self) -> Option { + Some(ReaperTargetType::Transport) + } +} + +impl<'a> Target<'a> for TransportTarget { + type Context = ControlContext<'a>; + + fn current_value(&self, _: Self::Context) -> Option { + use TransportAction::*; + let play_state = self.project.play_state(); + let value = match self.action { + PlayStop | PlayPause => transport_is_enabled_unit_value(play_state.is_playing), + Stop => { + transport_is_enabled_unit_value(!play_state.is_playing && !play_state.is_paused) + } + Pause => transport_is_enabled_unit_value(play_state.is_paused), + RecordStop => transport_is_enabled_unit_value(play_state.is_recording), + Repeat => transport_is_enabled_unit_value(self.project.repeat_is_enabled()), + }; + Some(AbsoluteValue::Continuous(value)) + } + + fn control_type(&self, context: Self::Context) -> ControlType { + self.control_type_and_character(context).0 + } +} + +pub const TRANSPORT_TARGET: TargetTypeDef = TargetTypeDef { + section: TargetSection::Project, + name: "Invoke transport action", + short_name: "Transport", + ..DEFAULT_TARGET +}; diff --git a/plugin-reaper-realearn/main/src/domain/ui_util.rs b/plugin-reaper-realearn/main/src/domain/ui_util.rs new file mode 100644 index 0000000..08399c5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/ui_util.rs @@ -0,0 +1,225 @@ +use crate::domain::{ + format_as_pretty_hex, FeedbackReason, MatchOutcome, OwnedIncomingMidiMessage, Tag, UnitId, +}; +use derive_more::Display; +use helgoboss_learn::{ + format_percentage_without_unit, parse_percentage_without_unit, MidiSourceValue, UnitValue, +}; +use helgoboss_midi::{RawShortMessage, ShortMessage}; +use itertools::Itertools; +use reaper_high::{Reaper, SliderVolume}; +use reaper_medium::Db; +use rosc::{OscMessage, OscPacket}; +use std::convert::TryInto; +use std::fmt::Display; + +pub fn format_as_percentage_without_unit(value: UnitValue) -> String { + format_percentage_without_unit(value.get()) +} + +pub fn format_as_symmetric_percentage_without_unit(value: UnitValue) -> String { + let symmetric_unit_value = value.get() * 2.0 - 1.0; + format_percentage_without_unit(symmetric_unit_value) +} + +pub fn format_as_double_percentage_without_unit(value: UnitValue) -> String { + let double_unit_value = value.get() * 2.0; + format_percentage_without_unit(double_unit_value) +} + +pub fn parse_unit_value_from_percentage(text: &str) -> Result { + parse_percentage_without_unit(text)?.try_into() +} + +pub fn parse_from_symmetric_percentage(text: &str) -> Result { + let percentage: f64 = text.parse().map_err(|_| "not a valid decimal value")?; + let symmetric_unit_value = percentage / 100.0; + ((symmetric_unit_value + 1.0) / 2.0).try_into() +} + +pub fn parse_from_double_percentage(text: &str) -> Result { + let percentage: f64 = text.parse().map_err(|_| "not a valid decimal value")?; + let doble_unit_value = percentage / 100.0; + (doble_unit_value / 2.0).try_into() +} + +/// Parses the given string as a dB value up to 12 dB. +pub fn parse_value_from_db(text: &str) -> Result { + let decimal: f64 = text.parse().map_err(|_| "not a decimal value")?; + let db: Db = decimal.try_into().map_err(|_| "not in dB range")?; + SliderVolume::from_db(db) + .normalized_slider_value() + .try_into() +} + +pub fn format_value_as_db_without_unit(value: UnitValue) -> String { + let volume = + SliderVolume::try_from_normalized_slider_value(value.get()).unwrap_or(SliderVolume::MIN); + format_volume_as_db_without_unit(volume) +} + +pub fn format_volume_as_db_without_unit(volume: SliderVolume) -> String { + let db = volume.db(); + if db == Db::MINUS_INF { + "-inf".to_string() + } else { + format!("{:.4}", db.get()) + } +} + +#[allow(unused)] +pub fn db_unit_value(volume: Db) -> UnitValue { + volume_unit_value(SliderVolume::from_db(volume)) +} + +/// Returns the given volume as unit value, clamping to 1.0 if the volume is higher than 12 dB. +pub fn volume_unit_value(volume: SliderVolume) -> UnitValue { + // The soft-normalized value can be > 1.0, e.g. when we have a volume of 12 dB and then + // lower the volume fader limit to a lower value. In that case we just report the + // highest possible value ... not much else we can do. + UnitValue::new_clamped(volume.normalized_slider_value()) +} + +pub fn convert_bool_to_unit_value(on: bool) -> UnitValue { + if on { + UnitValue::MAX + } else { + UnitValue::MIN + } +} + +pub fn format_value_as_db(value: UnitValue) -> String { + SliderVolume::try_from_normalized_slider_value(value.get()) + .unwrap_or(SliderVolume::MIN) + .to_string() +} + +pub fn format_control_input_with_match_result( + msg: impl Display, + match_result: MatchOutcome, +) -> String { + format!("{msg} ({match_result})") +} + +pub fn log_virtual_control_input(unit_id: UnitId, msg: impl Display) { + log(unit_id, "Virtual control", msg); +} + +pub fn log_real_control_input(unit_id: UnitId, msg: impl Display) { + log(unit_id, "Real control", msg); +} + +pub fn log_real_learn_input(unit_id: UnitId, msg: impl Display) { + log(unit_id, "Real learn", msg); +} + +pub fn log_target_control(unit_id: UnitId, msg: impl Display) { + log(unit_id, "Target control", msg); +} + +pub fn log_virtual_feedback_output(unit_id: UnitId, msg: impl Display) { + log_output(unit_id, OutputReason::VirtualFeedback, msg); +} + +pub fn log_real_feedback_output( + unit_id: UnitId, + feedback_reason: FeedbackReason, + msg: impl Display, +) { + log_output( + unit_id, + OutputReason::RealFeedback, + format!("{msg} ({feedback_reason:?})"), + ); +} + +pub fn log_lifecycle_output(unit_id: UnitId, msg: impl Display) { + log_output(unit_id, OutputReason::Lifecycle, msg); +} + +pub fn log_target_output(unit_id: UnitId, msg: impl Display) { + log_output(unit_id, OutputReason::TargetOutput, msg); +} + +pub fn log_output(unit_id: UnitId, reason: OutputReason, msg: impl Display) { + log(unit_id, reason, msg); +} + +#[derive(Copy, Clone, Debug, Display)] +pub enum OutputReason { + #[display(fmt = "Real feedback")] + RealFeedback, + #[display(fmt = "Virtual feedback")] + VirtualFeedback, + #[display(fmt = "Lifecycle output")] + Lifecycle, + #[display(fmt = "Target output")] + TargetOutput, +} + +/// Used for logging at the moment. +pub fn format_midi_source_value(value: &MidiSourceValue) -> String { + use MidiSourceValue::*; + match value { + Plain(m) => format_short_midi_message(*m), + ParameterNumber(m) => serde_json::to_string(&m).unwrap(), + ControlChange14Bit(m) => serde_json::to_string(&m).unwrap(), + Tempo(bpm) => format!("{bpm:?}"), + Raw { events, .. } => { + let event_strings: Vec<_> = events + .iter() + .map(|event| format_as_pretty_hex(event.bytes())) + .collect(); + event_strings.join(", ") + } + BorrowedSysEx(bytes) => format_as_pretty_hex(bytes), + } +} + +pub fn format_osc_packet(packet: &OscPacket) -> String { + format!("{packet:?}") +} + +pub fn format_osc_message(msg: &OscMessage) -> String { + format!("{msg:?}") +} + +fn format_short_midi_message(msg: RawShortMessage) -> String { + let bytes = msg.to_bytes(); + let decimal = format!("[{}, {}, {}]", bytes.0, bytes.1, bytes.2); + let structured = format!("{:?}", msg.to_structured()); + let hex = format!( + "[{:02X}, {:02X}, {:02X}]", + bytes.0, + bytes.1.get(), + bytes.2.get() + ); + format!("{hex} = {decimal} = {structured}") +} + +pub fn format_incoming_midi_message(msg: OwnedIncomingMidiMessage) -> String { + use OwnedIncomingMidiMessage::*; + match msg { + Short(m) => format_short_midi_message(m), + SysEx(m) => format_as_pretty_hex(&m), + } +} + +fn log(unit_id: UnitId, label: impl Display, msg: impl Display) { + let reaper = Reaper::get(); + reaper.show_console_msg(format!( + "{:.3} | ReaLearn {} | {:<16} | {}\n", + reaper.medium_reaper().low().time_precise(), + unit_id, + label, + msg + )); +} + +pub fn format_tags_as_csv<'a>(tags: impl IntoIterator) -> String { + format_as_csv(tags) +} + +fn format_as_csv(iter: impl IntoIterator) -> String { + iter.into_iter().join(", ") +} diff --git a/plugin-reaper-realearn/main/src/domain/unit.rs b/plugin-reaper-realearn/main/src/domain/unit.rs new file mode 100644 index 0000000..6d8cbb8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/unit.rs @@ -0,0 +1,701 @@ +use std::cell::RefCell; +use std::cmp::min; +use std::rc::{Rc, Weak}; +use std::sync::Arc; + +use enum_map::EnumMap; +use rxrust::prelude::*; +use serde_json::json; + +use crate::base::Prop; +use crate::domain::{ + CompartmentKind, FxDescriptor, GlobalControlAndFeedbackState, GroupId, MappingId, + MappingSnapshotContainer, ParameterManager, QualifiedMappingId, SharedInstance, Tag, TagScope, + TrackDescriptor, UnitId, VirtualMappingSnapshotIdForLoad, WeakInstance, +}; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use base::{serde_json_util, NamedChannelSender, SenderToNormalThread}; +use playtime_api::persistence::SlotAddress; + +pub type SharedUnit = Rc>; +pub type WeakUnit = Weak>; + +/// Just the old term as alias for easier class search. +type _InstanceState = Unit; + +/// State connected to the instance which also needs to be accessible from layers *above* the +/// processing layer (otherwise it could reside in the main processor). +/// +/// This also contains ReaLearn-specific target state that is persistent, even if the state is of +/// more global nature than an instance. Rationale: Otherwise we would need to come up with a +/// new place to persist state, e.g. the project data. But this makes things more complex and +/// raises questions like whether to keep the data in the project even the last ReaLearn instance is +/// gone and what to do if there's no project context - on the monitoring FX chain. +/// +/// For state of global nature which doesn't need to be persisted, see `RealearnTargetState`. +#[derive(Debug)] +pub struct Unit { + id: UnitId, + is_main_unit: bool, + instance: WeakInstance, + event_sender: SenderToNormalThread, + /// Which mappings are in which group. + /// + /// - Not persistent + /// - Used for target "ReaLearn: Browse group mappings" + /// - Automatically filled by main processor on sync + /// - Completely derived from mappings, so it's redundant state. + /// - Could be kept in main processor because it's only accessed by the processing layer, + /// but it's very related to the active mapping by group, so we decided to keep it here too. + mappings_by_group: EnumMap>>, + /// Which is the active mapping in which group. + /// + /// - Persistent + /// - Set by target "ReaLearn: Browse group mappings". + /// - Non-redundant state! + active_mapping_by_group: EnumMap>, + /// Additional info about mappings. + /// + /// - Not persistent + /// - Completely derived from mappings, so it's redundant state. + /// - Could be kept in main processor because it's only accessed by the processing layer. + mapping_infos: NonCryptoHashMap, + /// The mappings which are on. + /// + /// - Not persistent + /// - "on" = enabled & control or feedback enabled & mapping active & target active + /// - Completely derived from mappings, so it's redundant state. + /// - It's needed by both processing layer and layers above. + on_mappings: Prop>, + /// Whether control/feedback are globally active. + /// + /// Not persistent. + global_control_and_feedback_state: Prop, + /// All mapping tags whose mappings have been switched on via tag. + /// + /// - Persistent + /// - Set by target "ReaLearn: Enable/disable mappings". + /// - Non-redundant state! + active_mapping_tags: EnumMap>, + /// All instance tags whose instances have been switched on via tag. + /// + /// - Persistent + /// - Set by target "ReaLearn: Enable/disable instances". + /// - Non-redundant state! + active_instance_tags: NonCryptoHashSet, + /// All unit tags whose instances have been switched on via tag. + /// + /// - Persistent + /// - Set by target "ReaLearn: Enable/disable units". + /// - Non-redundant state! + active_unit_tags: NonCryptoHashSet, + /// Instance track. + /// + /// The instance track is persistent but it's persisted from the session, not from here. + /// This track descriptor is a descriptor suited for runtime, not for persistence. + instance_track_descriptor: TrackDescriptor, + /// Instance FX. + /// + /// See instance track to learn about persistence. + // TODO-low It's not so cool that we have the target activation condition as part of the + // descriptors (e.g. enable_only_if_track_selected) because we must be sure to set them + // to false. + // We should probably use the following distinction instead: + // - TrackTargetDescriptor contains TrackDescriptor contains VirtualTrack + // - FxTargetDescriptor contains FxDescriptor contains VirtualFx + // ... where TrackTargetDescriptor contains the condition and TrackDescriptor doesn't. + instance_fx_descriptor: FxDescriptor, + /// Mapping snapshots. + /// + /// Persistent. + mapping_snapshot_container: EnumMap, + mapping_which_learns_source: Prop>, + mapping_which_learns_target: Prop>, + parameter_manager: Arc, + custom_compartment_data: EnumMap>, + control_unit_top_left_corner: playtime_api::persistence::SlotAddress, +} + +#[derive(Debug)] +pub struct MappingInfo { + pub name: String, +} + +impl Unit { + pub(crate) fn new( + id: UnitId, + is_main_unit: bool, + parent_instance: WeakInstance, + feedback_event_sender: SenderToNormalThread, + parameter_manager: ParameterManager, + ) -> Self { + Self { + id, + is_main_unit, + instance: parent_instance, + event_sender: feedback_event_sender, + mappings_by_group: Default::default(), + active_mapping_by_group: Default::default(), + mapping_infos: Default::default(), + on_mappings: Default::default(), + global_control_and_feedback_state: Default::default(), + active_mapping_tags: Default::default(), + active_instance_tags: Default::default(), + active_unit_tags: Default::default(), + instance_track_descriptor: Default::default(), + instance_fx_descriptor: Default::default(), + mapping_snapshot_container: Default::default(), + mapping_which_learns_source: Default::default(), + mapping_which_learns_target: Default::default(), + parameter_manager: Arc::new(parameter_manager), + custom_compartment_data: Default::default(), + control_unit_top_left_corner: Default::default(), + } + } + + pub fn set_custom_compartment_data( + &mut self, + compartment: CompartmentKind, + data: NonCryptoHashMap, + ) { + self.custom_compartment_data[compartment] = data; + self.notify_matrix_control_units_changed(); + } + + pub fn update_custom_compartment_data_key( + &mut self, + compartment: CompartmentKind, + key: String, + value: serde_json::Value, + ) { + self.custom_compartment_data[compartment].insert(key, value); + self.notify_matrix_control_units_changed(); + } + + pub fn custom_compartment_data( + &self, + compartment: CompartmentKind, + ) -> &NonCryptoHashMap { + &self.custom_compartment_data[compartment] + } + + pub fn is_main_unit(&self) -> bool { + self.is_main_unit + } + + pub fn parameter_manager(&self) -> &Arc { + &self.parameter_manager + } + + pub fn set_mapping_which_learns_source( + &mut self, + mapping_id: Option, + ) -> Option { + let previous_value = self.mapping_which_learns_source.replace(mapping_id); + self.instance() + .borrow() + .notify_learning_target_in_unit_changed(self.id); + self.event_sender + .send_complaining(UnitEvent::MappingWhichLearnsSourceChanged { mapping_id }); + previous_value + } + + fn instance(&self) -> SharedInstance { + self.instance.upgrade().expect("parent instance gone") + } + + pub fn control_unit_palette_color(&self) -> Option { + self.get_playtime_main_compartment_data_as_u32("/control_unit/palette_color") + } + + pub fn set_control_unit_palette_color(&mut self, value: Option) { + let patch = json!({ + "control_unit": { + "palette_color": value + } + }); + self.patch_playtime_main_compartment_data(patch); + } + + pub fn control_unit_column_count(&self) -> u32 { + self.get_playtime_main_compartment_data_as_u32("/control_unit/column_count") + .unwrap_or(0) + } + + pub fn set_control_unit_column_count(&mut self, value: u32) { + let patch = json!({ + "control_unit": { + "column_count": value + } + }); + self.patch_playtime_main_compartment_data(patch); + } + + pub fn control_unit_row_count(&self) -> u32 { + self.get_playtime_main_compartment_data_as_u32("/control_unit/row_count") + .unwrap_or(0) + } + + pub fn set_control_unit_row_count(&mut self, value: u32) { + let patch = json!({ + "control_unit": { + "row_count": value + } + }); + self.patch_playtime_main_compartment_data(patch); + } + + fn get_playtime_main_compartment_data_as_u32(&self, pointer: &str) -> Option { + self.get_playtime_main_compartment_data(pointer) + .and_then(|v| v.as_u64()) + .map(|v| v as u32) + } + + fn get_playtime_main_compartment_data(&self, pointer: &str) -> Option<&serde_json::Value> { + self.custom_compartment_data[CompartmentKind::Main] + .get("playtime")? + .pointer(pointer) + } + + fn patch_playtime_main_compartment_data(&mut self, patch: serde_json::Value) { + let playtime = self.custom_compartment_data[CompartmentKind::Main] + .entry("playtime".to_string()) + .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new())); + serde_json_util::merge(playtime, patch); + self.notify_matrix_control_units_changed(); + } + + pub fn control_unit_top_left_corner(&self) -> playtime_api::persistence::SlotAddress { + self.control_unit_top_left_corner + } + + pub fn invalidate_control_unit_scroll_pos(&mut self, column_count: usize, row_count: usize) { + let actual_column = self.control_unit_top_left_corner.column_index; + let fixed_column = fix_scroll_pos( + actual_column, + self.control_unit_column_count() as usize, + column_count, + ); + let actual_row = self.control_unit_top_left_corner.row_index; + let fixed_row = fix_scroll_pos( + actual_row, + self.control_unit_row_count() as usize, + row_count, + ); + if actual_column == fixed_column && actual_row == fixed_row { + return; + } + // Control unit scroll position is invalid + self.set_control_unit_top_left_corner(SlotAddress::new(fixed_column, fixed_row)); + } + + pub fn set_control_unit_top_left_corner( + &mut self, + value: playtime_api::persistence::SlotAddress, + ) { + self.control_unit_top_left_corner = value; + self.notify_matrix_control_units_changed(); + self.event_sender + .send_complaining(UnitEvent::ControlUnitTopLeftCornerChanged(value)); + } + + fn notify_matrix_control_units_changed(&self) { + #[cfg(feature = "playtime")] + { + let instance = self.instance(); + if let Some(m) = instance.borrow().clip_matrix() { + m.notify_control_units_changed(); + }; + } + } + + pub fn set_mapping_which_learns_target( + &mut self, + mapping_id: Option, + ) -> Option { + let previous_value = self.mapping_which_learns_target.replace(mapping_id); + self.event_sender + .send_complaining(UnitEvent::MappingWhichLearnsTargetChanged { mapping_id }); + previous_value + } + + pub fn mapping_which_learns_source(&self) -> &Prop> { + &self.mapping_which_learns_source + } + + pub fn mapping_which_learns_target(&self) -> &Prop> { + &self.mapping_which_learns_target + } + + pub fn mapping_is_learning_source(&self, id: QualifiedMappingId) -> bool { + match self.mapping_which_learns_source.get_ref() { + None => false, + Some(i) => *i == id, + } + } + + pub fn mapping_is_learning_target(&self, id: QualifiedMappingId) -> bool { + match self.mapping_which_learns_target.get_ref() { + None => false, + Some(i) => *i == id, + } + } + + pub fn set_mapping_snapshot_container( + &mut self, + compartment: CompartmentKind, + container: MappingSnapshotContainer, + ) { + self.mapping_snapshot_container[compartment] = container; + } + + pub fn mapping_snapshot_container( + &self, + compartment: CompartmentKind, + ) -> &MappingSnapshotContainer { + &self.mapping_snapshot_container[compartment] + } + + pub fn mapping_snapshot_container_mut( + &mut self, + compartment: CompartmentKind, + ) -> &mut MappingSnapshotContainer { + &mut self.mapping_snapshot_container[compartment] + } + + /// Marks the given snapshot as the active one for all tags in the given scope and sends + /// instance feedback. + pub fn mark_snapshot_active( + &mut self, + compartment: CompartmentKind, + tag_scope: &TagScope, + snapshot_id: &VirtualMappingSnapshotIdForLoad, + ) { + self.mapping_snapshot_container[compartment].mark_snapshot_active(tag_scope, snapshot_id); + self.event_sender + .send_complaining(UnitEvent::MappingSnapshotActivated { + compartment, + tag_scope: Box::new(tag_scope.clone()), + snapshot_id: snapshot_id.clone(), + }) + } + + pub fn instance_track_descriptor(&self) -> &TrackDescriptor { + &self.instance_track_descriptor + } + + pub fn set_instance_track_descriptor(&mut self, descriptor: TrackDescriptor) { + self.instance_track_descriptor = descriptor; + } + + pub fn instance_fx_descriptor(&self) -> &FxDescriptor { + &self.instance_fx_descriptor + } + + pub fn set_instance_fx_descriptor(&mut self, fx: FxDescriptor) { + self.instance_fx_descriptor = fx; + } + + pub fn id(&self) -> UnitId { + self.id + } + + pub fn set_mapping_infos( + &mut self, + mapping_infos: NonCryptoHashMap, + ) { + self.mapping_infos = mapping_infos; + } + + pub fn update_mapping_info(&mut self, id: QualifiedMappingId, info: MappingInfo) { + self.mapping_infos.insert(id, info); + } + + pub fn get_mapping_info(&self, id: QualifiedMappingId) -> Option<&MappingInfo> { + self.mapping_infos.get(&id) + } + + pub fn only_these_mapping_tags_are_active( + &self, + compartment: CompartmentKind, + tags: &NonCryptoHashSet, + ) -> bool { + tags == &self.active_mapping_tags[compartment] + } + + pub fn at_least_those_mapping_tags_are_active( + &self, + compartment: CompartmentKind, + tags: &NonCryptoHashSet, + ) -> bool { + tags.is_subset(&self.active_mapping_tags[compartment]) + } + + pub fn activate_or_deactivate_mapping_tags( + &mut self, + compartment: CompartmentKind, + tags: &NonCryptoHashSet, + activate: bool, + ) { + if activate { + self.active_mapping_tags[compartment].extend(tags.iter().cloned()); + } else { + self.active_mapping_tags[compartment].retain(|t| !tags.contains(t)); + } + self.notify_active_mapping_tags_changed(compartment); + } + + pub fn set_active_mapping_tags( + &mut self, + compartment: CompartmentKind, + tags: NonCryptoHashSet, + ) { + self.active_mapping_tags[compartment] = tags; + self.notify_active_mapping_tags_changed(compartment); + } + + fn notify_active_mapping_tags_changed(&mut self, compartment: CompartmentKind) { + let instance_event = UnitEvent::ActiveMappingTags { compartment }; + self.event_sender.send_complaining(instance_event); + } + + pub fn only_these_instance_tags_are_active(&self, tags: &NonCryptoHashSet) -> bool { + tags == &self.active_instance_tags + } + + pub fn at_least_those_instance_tags_are_active(&self, tags: &NonCryptoHashSet) -> bool { + tags.is_subset(&self.active_instance_tags) + } + + pub fn activate_or_deactivate_instance_tags( + &mut self, + tags: &NonCryptoHashSet, + activate: bool, + ) { + if activate { + self.active_instance_tags.extend(tags.iter().cloned()); + } else { + self.active_instance_tags.retain(|t| !tags.contains(t)); + } + self.notify_active_instance_tags_changed(); + } + + pub fn active_instance_tags(&self) -> &NonCryptoHashSet { + &self.active_instance_tags + } + + pub fn set_active_instance_tags_without_notification(&mut self, tags: NonCryptoHashSet) { + self.active_instance_tags = tags; + } + + pub fn set_active_instance_tags(&mut self, tags: NonCryptoHashSet) { + self.active_instance_tags = tags; + self.notify_active_instance_tags_changed(); + } + + fn notify_active_instance_tags_changed(&mut self) { + self.event_sender + .send_complaining(UnitEvent::ActiveInstanceTags); + } + + pub fn only_these_unit_tags_are_active(&self, tags: &NonCryptoHashSet) -> bool { + tags == &self.active_unit_tags + } + + pub fn at_least_those_unit_tags_are_active(&self, tags: &NonCryptoHashSet) -> bool { + tags.is_subset(&self.active_unit_tags) + } + + pub fn activate_or_deactivate_unit_tags( + &mut self, + tags: &NonCryptoHashSet, + activate: bool, + ) { + if activate { + self.active_unit_tags.extend(tags.iter().cloned()); + } else { + self.active_unit_tags.retain(|t| !tags.contains(t)); + } + self.notify_active_unit_tags_changed(); + } + + pub fn active_unit_tags(&self) -> &NonCryptoHashSet { + &self.active_unit_tags + } + + pub fn set_active_unit_tags_without_notification(&mut self, tags: NonCryptoHashSet) { + self.active_unit_tags = tags; + } + + pub fn set_active_unit_tags(&mut self, tags: NonCryptoHashSet) { + self.active_unit_tags = tags; + self.notify_active_unit_tags_changed(); + } + + fn notify_active_unit_tags_changed(&mut self) { + self.event_sender + .send_complaining(UnitEvent::ActiveUnitTags); + } + + pub fn mapping_is_on(&self, id: QualifiedMappingId) -> bool { + self.on_mappings.get_ref().contains(&id) + } + + pub fn global_control_and_feedback_state(&self) -> GlobalControlAndFeedbackState { + self.global_control_and_feedback_state.get() + } + + pub fn on_mappings_changed( + &self, + ) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.on_mappings.changed() + } + + pub fn global_control_and_feedback_state_changed( + &self, + ) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.global_control_and_feedback_state.changed() + } + + pub fn set_on_mappings(&mut self, on_mappings: NonCryptoHashSet) { + self.on_mappings.set(on_mappings); + } + + pub fn set_global_control_and_feedback_state(&mut self, state: GlobalControlAndFeedbackState) { + self.global_control_and_feedback_state.set(state); + } + + pub fn set_mapping_on(&mut self, id: QualifiedMappingId, is_on: bool) { + self.on_mappings.mut_in_place(|m| { + if is_on { + m.insert(id); + } else { + m.remove(&id); + } + }); + } + + pub fn active_mapping_by_group( + &self, + compartment: CompartmentKind, + ) -> &NonCryptoHashMap { + &self.active_mapping_by_group[compartment] + } + + pub fn active_mapping_tags(&self, compartment: CompartmentKind) -> &NonCryptoHashSet { + &self.active_mapping_tags[compartment] + } + + pub fn set_active_mapping_by_group( + &mut self, + compartment: CompartmentKind, + value: NonCryptoHashMap, + ) { + self.active_mapping_by_group[compartment] = value; + } + + /// Sets the ID of the currently active mapping within the given group. + pub fn set_active_mapping_within_group( + &mut self, + compartment: CompartmentKind, + group_id: GroupId, + mapping_id: MappingId, + ) { + self.active_mapping_by_group[compartment].insert(group_id, mapping_id); + let instance_event = UnitEvent::ActiveMappingWithinGroup { + compartment, + group_id, + mapping_id: Some(mapping_id), + }; + self.event_sender.send_complaining(instance_event); + } + + /// Gets the ID of the currently active mapping within the given group. + pub fn get_active_mapping_within_group( + &self, + compartment: CompartmentKind, + group_id: GroupId, + ) -> Option { + self.active_mapping_by_group[compartment] + .get(&group_id) + .copied() + } + + pub fn set_mappings_by_group( + &mut self, + compartment: CompartmentKind, + mappings_by_group: NonCryptoHashMap>, + ) { + for group_id in self.active_mapping_by_group[compartment].keys() { + if !mappings_by_group.contains_key(group_id) { + let event = UnitEvent::ActiveMappingWithinGroup { + compartment, + group_id: *group_id, + mapping_id: None, + }; + self.event_sender.send_complaining(event); + } + } + self.mappings_by_group[compartment] = mappings_by_group; + } + + pub fn get_on_mappings_within_group( + &self, + compartment: CompartmentKind, + group_id: GroupId, + ) -> impl Iterator + '_ { + self.mappings_by_group[compartment] + .get(&group_id) + .into_iter() + .flatten() + .copied() + .filter(move |id| self.mapping_is_on(QualifiedMappingId::new(compartment, *id))) + } +} + +#[derive(Clone, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum UnitEvent { + /// For the "ReaLearn: Browse group mappings" target. + ActiveMappingWithinGroup { + compartment: CompartmentKind, + group_id: GroupId, + mapping_id: Option, + }, + /// For the "ReaLearn: Enable/disable mappings" target. + ActiveMappingTags { + compartment: CompartmentKind, + }, + /// For the "ReaLearn: Enable/disable instances" target. + ActiveInstanceTags, + /// For the "ReaLearn: Enable/disable units" target. + ActiveUnitTags, + /// For the "ReaLearn: Load mapping snapshot" target. + MappingSnapshotActivated { + compartment: CompartmentKind, + tag_scope: Box, + snapshot_id: VirtualMappingSnapshotIdForLoad, + }, + MappingWhichLearnsSourceChanged { + mapping_id: Option, + }, + MappingWhichLearnsTargetChanged { + mapping_id: Option, + }, + ControlUnitTopLeftCornerChanged(playtime_api::persistence::SlotAddress), +} + +impl UnitEvent { + pub fn is_interesting_for_other_units(&self) -> bool { + matches!(self, UnitEvent::MappingWhichLearnsTargetChanged { .. }) + } +} + +fn fix_scroll_pos(start_pos: usize, span: usize, count: usize) -> usize { + if count == 0 || span == 0 { + return 0; + } + let exclusive_end_pos = start_pos + span; + let fixed_exclusive_end_pos = min(count, exclusive_end_pos); + fixed_exclusive_end_pos.saturating_sub(span) +} diff --git a/plugin-reaper-realearn/main/src/domain/unresolved_reaper_target.rs b/plugin-reaper-realearn/main/src/domain/unresolved_reaper_target.rs new file mode 100644 index 0000000..3a18ac6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/unresolved_reaper_target.rs @@ -0,0 +1,2318 @@ +use crate::application::BookmarkAnchorType; +use crate::domain::realearn_target::RealearnTarget; +use crate::domain::{ + scoped_track_index, Backbone, CompartmentKind, CompartmentParamIndex, CompartmentParams, + ControlContext, ExtendedProcessorContext, FeedbackResolution, ReaperTarget, + UnresolvedActionTarget, UnresolvedAllTrackFxEnableTarget, UnresolvedAnyOnTarget, + UnresolvedAutomationModeOverrideTarget, UnresolvedBrowseFxsTarget, UnresolvedBrowseGroupTarget, + UnresolvedBrowsePotFilterItemsTarget, UnresolvedBrowsePotPresetsTarget, + UnresolvedBrowseTracksTarget, UnresolvedCompartmentParameterValueTarget, UnresolvedDummyTarget, + UnresolvedEnableInstancesTarget, UnresolvedEnableMappingsTarget, UnresolvedEnableUnitsTarget, + UnresolvedFxEnableTarget, UnresolvedFxOnlineTarget, UnresolvedFxOpenTarget, + UnresolvedFxParameterTarget, UnresolvedFxParameterTouchStateTarget, UnresolvedFxPresetTarget, + UnresolvedFxToolTarget, UnresolvedGoToBookmarkTarget, UnresolvedLastTouchedTarget, + UnresolvedLoadFxSnapshotTarget, UnresolvedLoadMappingSnapshotTarget, + UnresolvedLoadPotPresetTarget, UnresolvedMidiSendTarget, UnresolvedModifyMappingTarget, + UnresolvedMouseTarget, UnresolvedOscSendTarget, UnresolvedPlayrateTarget, + UnresolvedPreviewPotPresetTarget, UnresolvedRouteAutomationModeTarget, + UnresolvedRouteMonoTarget, UnresolvedRouteMuteTarget, UnresolvedRoutePanTarget, + UnresolvedRoutePhaseTarget, UnresolvedRouteTouchStateTarget, UnresolvedRouteVolumeTarget, + UnresolvedSeekTarget, UnresolvedStreamDeckBrightnessTarget, + UnresolvedTakeMappingSnapshotTarget, UnresolvedTempoTarget, UnresolvedTrackArmTarget, + UnresolvedTrackAutomationModeTarget, UnresolvedTrackMonitoringModeTarget, + UnresolvedTrackMuteTarget, UnresolvedTrackPanTarget, UnresolvedTrackParentSendTarget, + UnresolvedTrackPeakTarget, UnresolvedTrackPhaseTarget, UnresolvedTrackSelectionTarget, + UnresolvedTrackShowTarget, UnresolvedTrackSoloTarget, UnresolvedTrackToolTarget, + UnresolvedTrackTouchStateTarget, UnresolvedTrackVolumeTarget, UnresolvedTrackWidthTarget, + UnresolvedTransportTarget, +}; +use derive_more::{Display, Error}; +use enum_dispatch::enum_dispatch; +use fasteval::{Compiler, Evaler, Instruction, Slab}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +use helgobox_api::persistence::{ + FxChainDescriptor, FxDescriptorCommons, TrackDescriptorCommons, TrackScope, +}; +use playtime_api::persistence::SlotAddress; +use reaper_high::{ + BookmarkType, FindBookmarkResult, Fx, FxChain, FxParameter, Guid, Project, Reaper, + SendPartnerType, Track, TrackRoute, +}; +use reaper_medium::{BookmarkId, MasterTrackBehavior, TrackArea}; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use std::error::Error; +use std::fmt; +use std::fmt::Formatter; +use strum::EnumIter; +use wildmatch::WildMatch; + +/// Maximum number of "allow multiple" resolves (e.g. affected tracks). +const MAX_MULTIPLE: usize = 1000; + +#[enum_dispatch] +#[derive(Debug)] +pub enum UnresolvedReaperTarget { + Mouse(UnresolvedMouseTarget), + Action(UnresolvedActionTarget), + FxParameter(UnresolvedFxParameterTarget), + FxParameterTouchState(UnresolvedFxParameterTouchStateTarget), + TrackVolume(UnresolvedTrackVolumeTarget), + TrackTool(UnresolvedTrackToolTarget), + TrackPeak(UnresolvedTrackPeakTarget), + TrackSendVolume(UnresolvedRouteVolumeTarget), + TrackPan(UnresolvedTrackPanTarget), + TrackWidth(UnresolvedTrackWidthTarget), + TrackArm(UnresolvedTrackArmTarget), + TrackParentSend(UnresolvedTrackParentSendTarget), + TrackSelection(UnresolvedTrackSelectionTarget), + TrackMute(UnresolvedTrackMuteTarget), + TrackPhase(UnresolvedTrackPhaseTarget), + TrackShow(UnresolvedTrackShowTarget), + TrackSolo(UnresolvedTrackSoloTarget), + TrackAutomationMode(UnresolvedTrackAutomationModeTarget), + TrackMonitoringMode(UnresolvedTrackMonitoringModeTarget), + RoutePan(UnresolvedRoutePanTarget), + RouteMute(UnresolvedRouteMuteTarget), + RoutePhase(UnresolvedRoutePhaseTarget), + RouteMono(UnresolvedRouteMonoTarget), + RouteAutomationMode(UnresolvedRouteAutomationModeTarget), + RouteTouchState(UnresolvedRouteTouchStateTarget), + Tempo(UnresolvedTempoTarget), + Playrate(UnresolvedPlayrateTarget), + AutomationModeOverride(UnresolvedAutomationModeOverrideTarget), + FxTool(UnresolvedFxToolTarget), + FxEnable(UnresolvedFxEnableTarget), + FxOnline(UnresolvedFxOnlineTarget), + FxOpen(UnresolvedFxOpenTarget), + FxPreset(UnresolvedFxPresetTarget), + SelectedTrack(UnresolvedBrowseTracksTarget), + BrowseFxs(UnresolvedBrowseFxsTarget), + AllTrackFxEnable(UnresolvedAllTrackFxEnableTarget), + Transport(UnresolvedTransportTarget), + LoadFxPreset(UnresolvedLoadFxSnapshotTarget), + TrackTouchState(UnresolvedTrackTouchStateTarget), + GoToBookmark(UnresolvedGoToBookmarkTarget), + Seek(UnresolvedSeekTarget), + SendMidi(UnresolvedMidiSendTarget), + SendOsc(UnresolvedOscSendTarget), + Dummy(UnresolvedDummyTarget), + PlaytimeSlotTransportAction(crate::domain::UnresolvedPlaytimeSlotTransportTarget), + PlaytimeColumnAction(crate::domain::UnresolvedPlaytimeColumnActionTarget), + PlaytimeRowAction(crate::domain::UnresolvedPlaytimeRowActionTarget), + PlaytimeSlotSeek(crate::domain::UnresolvedPlaytimeSlotSeekTarget), + PlaytimeSlotVolume(crate::domain::UnresolvedPlaytimeSlotVolumeTarget), + PlaytimeSlotManagementAction(crate::domain::UnresolvedPlaytimeSlotManagementActionTarget), + PlaytimeMatrixAction(crate::domain::UnresolvedPlaytimeMatrixActionTarget), + PlaytimeControlUnitScroll(crate::domain::UnresolvedPlaytimeControlUnitScrollTarget), + PlaytimeBrowseCells(crate::domain::UnresolvedPlaytimeBrowseCellsTarget), + LoadMappingSnapshot(UnresolvedLoadMappingSnapshotTarget), + TakeMappingSnapshot(UnresolvedTakeMappingSnapshotTarget), + EnableMappings(UnresolvedEnableMappingsTarget), + ModifyMapping(UnresolvedModifyMappingTarget), + BrowseGroup(UnresolvedBrowseGroupTarget), + EnableInstances(UnresolvedEnableInstancesTarget), + EnableUnits(UnresolvedEnableUnitsTarget), + AnyOn(UnresolvedAnyOnTarget), + LastTouched(UnresolvedLastTouchedTarget), + BrowsePotFilterItems(UnresolvedBrowsePotFilterItemsTarget), + BrowsePotPresets(UnresolvedBrowsePotPresetsTarget), + PreviewPotPreset(UnresolvedPreviewPotPresetTarget), + LoadPotPreset(UnresolvedLoadPotPresetTarget), + CompartmentParameterValue(UnresolvedCompartmentParameterValueTarget), + StreamDeckBrightness(UnresolvedStreamDeckBrightnessTarget), +} + +impl UnresolvedReaperTarget { + pub fn is_always_active(&self) -> bool { + matches!(self, Self::LastTouched(_)) + } + + /// Returns whether all conditions for this target to be active are met. + /// + /// Targets conditions are for example "track selected" or "FX focused". + pub fn conditions_are_met(&self, target: &ReaperTarget) -> bool { + let descriptors = self.unpack_descriptors(); + if let Some(desc) = descriptors.track { + if desc.enable_only_if_track_selected { + if let Some(track) = target.track() { + if !track.is_selected() { + return false; + } + } + } + } + if let Some(desc) = descriptors.fx { + if desc.enable_only_if_fx_has_focus { + if let Some(fx) = target.fx() { + if !fx.window_has_focus() { + return false; + } + } + } + } + true + } + + /// Should return true if the target should be refreshed (re-resolved) on parameter changes. + /// Usually true for all targets that use `` selector. + pub fn can_be_affected_by_parameters(&self) -> bool { + let descriptors = self.unpack_descriptors(); + if let Some(desc) = descriptors.track { + if desc.track.can_be_affected_by_parameters() { + return true; + } + } + if let Some(desc) = descriptors.fx { + if desc.fx.can_be_affected_by_parameters() { + return true; + } + } + if let Some(desc) = descriptors.route { + if desc.route.can_be_affected_by_parameters() { + return true; + } + } + if let Some(desc) = descriptors.fx_param { + if desc.fx_parameter.can_be_affected_by_parameters() { + return true; + } + } + if let Some(slot) = descriptors.clip_slot { + if slot.can_be_affected_by_parameters() { + return true; + } + } + if let Some(col) = descriptors.clip_column { + if col.can_be_affected_by_parameters() { + return true; + } + } + if let Some(row) = descriptors.clip_row { + if row.can_be_affected_by_parameters() { + return true; + } + } + false + } + + fn unpack_descriptors(&self) -> Descriptors { + if let Some(d) = self.fx_parameter_descriptor() { + return Descriptors { + track: Some(&d.fx_descriptor.track_descriptor), + fx: Some(&d.fx_descriptor), + fx_param: Some(d), + ..Default::default() + }; + } + if let Some(d) = self.fx_descriptor() { + return Descriptors { + track: Some(&d.track_descriptor), + fx: Some(d), + ..Default::default() + }; + } + if let Some(d) = self.route_descriptor() { + return Descriptors { + track: Some(&d.track_descriptor), + route: Some(d), + ..Default::default() + }; + } + if let Some(d) = self.track_descriptor() { + return Descriptors { + track: Some(d), + ..Default::default() + }; + } + if let Some(d) = self.clip_slot_descriptor() { + return Descriptors { + clip_slot: Some(d), + ..Default::default() + }; + } + if let Some(d) = self.clip_column_descriptor() { + return Descriptors { + clip_column: Some(d), + ..Default::default() + }; + } + if let Some(d) = self.clip_row_descriptor() { + return Descriptors { + clip_row: Some(d), + ..Default::default() + }; + } + Default::default() + } +} + +pub fn get_effective_tracks( + context: ExtendedProcessorContext, + virtual_track: &VirtualTrack, + compartment: CompartmentKind, +) -> Result, &'static str> { + virtual_track + .resolve(context, compartment) + .map_err(|_| "track couldn't be resolved") +} + +// Returns an error if that send (or track) doesn't exist. +pub fn get_track_routes( + context: ExtendedProcessorContext, + descriptor: &TrackRouteDescriptor, + compartment: CompartmentKind, +) -> Result, &'static str> { + let tracks = get_effective_tracks(context, &descriptor.track_descriptor.track, compartment)?; + let routes = tracks + .into_iter() + .flat_map(|track| { + descriptor + .route + .resolve(&track, context, compartment) + .map_err(|_| "route doesn't exist") + }) + .collect(); + Ok(routes) +} + +#[derive(Debug, Default)] +pub struct TrackDescriptor { + pub track: VirtualTrack, + pub enable_only_if_track_selected: bool, +} + +impl TrackDescriptor { + pub fn from_api( + api_desc: helgobox_api::persistence::TrackDescriptor, + ) -> Result> { + use helgobox_api::persistence::TrackDescriptor::*; + let (track, commons) = match api_desc { + This { commons } => (VirtualTrack::This, commons), + Master { commons } => (VirtualTrack::Master, commons), + Instance { commons } => (VirtualTrack::Unit, commons), + Selected { allow_multiple } => ( + VirtualTrack::Selected { + allow_multiple: allow_multiple.unwrap_or(false), + }, + TrackDescriptorCommons::default(), + ), + Dynamic { + expression, + commons, + scope, + } => { + let evaluator = ExpressionEvaluator::compile(&expression)?; + ( + VirtualTrack::Dynamic { + evaluator: Box::new(evaluator), + scope: scope.unwrap_or_default(), + }, + commons, + ) + } + ById { id, commons } => { + let id = id.as_ref().ok_or("no ID given")?; + ( + VirtualTrack::ById(Guid::from_string_without_braces(id)?), + commons, + ) + } + ByIndex { + index, + commons, + scope, + } => ( + VirtualTrack::ByIndex { + index, + scope: scope.unwrap_or_default(), + }, + commons, + ), + ByName { + name, + allow_multiple, + .. + } => ( + VirtualTrack::ByName { + wild_match: WildMatch::new(&name), + allow_multiple: allow_multiple.unwrap_or(false), + }, + TrackDescriptorCommons::default(), + ), + FromClipColumn { + column, + context, + commons, + } => { + let column = VirtualPlaytimeColumn::from_descriptor(&column)?; + (VirtualTrack::FromClipColumn { column, context }, commons) + } + }; + let desc = Self { + track, + // TODO-low The default value should ideally come from infrastructure::api::defaults + // but this is in the infrastructure layer. + enable_only_if_track_selected: commons.track_must_be_selected.unwrap_or(false), + }; + Ok(desc) + } +} + +#[derive(Debug, Default)] +pub struct FxDescriptor { + pub track_descriptor: TrackDescriptor, + pub fx: VirtualFx, + pub enable_only_if_fx_has_focus: bool, +} + +impl FxDescriptor { + pub fn from_api( + api_desc: helgobox_api::persistence::FxDescriptor, + ) -> Result> { + use helgobox_api::persistence::FxDescriptor; + let (track_descriptor, fx, commons): (TrackDescriptor, VirtualFx, FxDescriptorCommons) = + match api_desc { + FxDescriptor::This { commons } => (Default::default(), VirtualFx::This, commons), + FxDescriptor::Focused => ( + Default::default(), + VirtualFx::LastFocused, + Default::default(), + ), + FxDescriptor::Instance { commons } => { + (Default::default(), VirtualFx::Unit, commons) + } + FxDescriptor::Dynamic { + commons, + chain: FxChainDescriptor::Track { track, chain }, + expression, + } => { + let chain = chain.unwrap_or_default(); + let evaluator = ExpressionEvaluator::compile(&expression)?; + ( + TrackDescriptor::from_api(track.unwrap_or_default())?, + VirtualFx::ChainFx { + is_input_fx: chain.is_input_fx(), + chain_fx: VirtualChainFx::Dynamic(Box::new(evaluator)), + }, + commons, + ) + } + FxDescriptor::ById { + commons, + chain: FxChainDescriptor::Track { track, chain }, + id, + } => { + let chain = chain.unwrap_or_default(); + let id = id.as_ref().ok_or("no ID given")?; + let guid = Guid::from_string_without_braces(id)?; + ( + TrackDescriptor::from_api(track.unwrap_or_default())?, + VirtualFx::ChainFx { + is_input_fx: chain.is_input_fx(), + chain_fx: VirtualChainFx::ById(guid, None), + }, + commons, + ) + } + FxDescriptor::ByIndex { + commons, + chain: FxChainDescriptor::Track { track, chain }, + index, + } => { + let chain = chain.unwrap_or_default(); + ( + TrackDescriptor::from_api(track.unwrap_or_default())?, + VirtualFx::ChainFx { + is_input_fx: chain.is_input_fx(), + chain_fx: VirtualChainFx::ByIndex(index), + }, + commons, + ) + } + + FxDescriptor::ByName { + commons, + chain: FxChainDescriptor::Track { track, chain }, + name, + allow_multiple, + } => { + let chain = chain.unwrap_or_default(); + ( + TrackDescriptor::from_api(track.unwrap_or_default())?, + VirtualFx::ChainFx { + is_input_fx: chain.is_input_fx(), + chain_fx: VirtualChainFx::ByName { + wild_match: WildMatch::new(&name), + allow_multiple: allow_multiple.unwrap_or(false), + }, + }, + commons, + ) + } + }; + let desc = Self { + track_descriptor, + fx, + // TODO-low The default value should ideally come from infrastructure::api::defaults + // but this is in the infrastructure layer. + enable_only_if_fx_has_focus: commons.fx_must_have_focus.unwrap_or(false), + }; + Ok(desc) + } + + // Returns an error if the FX doesn't exist. + pub fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str> { + match &self.fx { + VirtualFx::This => { + let fx = context.context().containing_fx(); + if fx.is_available() { + Ok(vec![fx.clone()]) + } else { + Err("this FX not available anymore") + } + } + VirtualFx::LastFocused => { + let this_realearn_fx = context.control_context.processor_context.containing_fx(); + if let Some(fx) = + Backbone::get().last_relevant_available_focused_fx(this_realearn_fx) + { + Ok(vec![fx]) + } else { + Err("No relevant FX focused yet") + } + } + VirtualFx::Unit => { + let instance_state = context.control_context.unit.borrow(); + let instance_fx = instance_state.instance_fx_descriptor(); + if matches!(instance_fx.fx, VirtualFx::Unit) { + return Err("circular reference"); + } + instance_fx.resolve(context, compartment) + } + VirtualFx::ChainFx { + is_input_fx, + chain_fx, + } => { + enum MaybeOwned<'a, T> { + Owned(T), + Borrowed(&'a T), + } + impl MaybeOwned<'_, T> { + fn get(&self) -> &T { + match self { + MaybeOwned::Owned(o) => o, + MaybeOwned::Borrowed(b) => b, + } + } + } + let chain_fx = match chain_fx { + VirtualChainFx::ByIdOrIndex(_, index) => { + // Actually it's not that important whether we create an index-based or + // GUID-based FX. The session listeners will recreate and + // resync the FX whenever something has changed anyway. But + // for monitoring FX it could still be good (which we don't get notified + // about unfortunately). + if matches!(self.track_descriptor.track, VirtualTrack::Selected { .. }) { + MaybeOwned::Owned(VirtualChainFx::ByIndex(*index)) + } else { + MaybeOwned::Borrowed(chain_fx) + } + } + _ => MaybeOwned::Borrowed(chain_fx), + }; + let fx_chains = get_fx_chains( + context, + &self.track_descriptor.track, + *is_input_fx, + compartment, + )?; + chain_fx + .get() + .resolve(&fx_chains, context, compartment) + .map_err(|_| "couldn't resolve particular FX") + } + } + } +} + +#[derive(Debug)] +pub struct FxParameterDescriptor { + pub fx_descriptor: FxDescriptor, + pub fx_parameter: VirtualFxParameter, +} + +#[derive(Debug)] +pub struct TrackRouteDescriptor { + pub track_descriptor: TrackDescriptor, + pub route: VirtualTrackRoute, +} + +impl TrackRouteDescriptor { + pub fn resolve_first( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result> { + let tracks = self.track_descriptor.track.resolve(context, compartment)?; + let track = tracks.first().ok_or("didn't resolve to any track")?; + let route = self.route.resolve(track, context, compartment)?; + Ok(route) + } +} + +#[derive(Debug)] +pub struct VirtualTrackRoute { + pub r#type: TrackRouteType, + pub selector: TrackRouteSelector, +} + +#[derive(Debug)] +pub enum TrackRouteSelector { + Dynamic(Box), + ById(Guid), + ByName(WildMatch), + ByIndex(u32), +} + +impl TrackRouteSelector { + pub fn resolve( + &self, + track: &Track, + route_type: TrackRouteType, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + use TrackRouteSelector::*; + let route = match self { + Dynamic(evaluator) => { + let i = Self::evaluate_to_route_index(evaluator, context, compartment)?; + resolve_track_route_by_index(track, route_type, i)? + } + ById(guid) => { + let related_track = track + .project() + .track_by_guid(guid) + .map_err(|_| TrackRouteResolveError::ProjectNotAvailable)?; + let route = find_route_by_related_track(track, &related_track, route_type)?; + route.ok_or(TrackRouteResolveError::TrackRouteNotFound { + guid: Some(*guid), + name: None, + index: None, + })? + } + ByName(name) => find_route_by_name(track, name, route_type).ok_or_else(|| { + TrackRouteResolveError::TrackRouteNotFound { + guid: None, + name: Some(name.clone()), + index: None, + } + })?, + ByIndex(i) => resolve_track_route_by_index(track, route_type, *i)?, + }; + Ok(route) + } + + pub fn calculated_route_index( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Option { + if let TrackRouteSelector::Dynamic(evaluator) = self { + Some(Self::evaluate_to_route_index(evaluator, context, compartment).ok()?) + } else { + None + } + } + + fn evaluate_to_route_index( + evaluator: &ExpressionEvaluator, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + let compartment_params = context.params().compartment_params(compartment); + let result = evaluator + .evaluate_with_params(compartment_params) + .map_err(|_| TrackRouteResolveError::ExpressionFailed)? + .round() as i32; + if result < 0 { + return Err(TrackRouteResolveError::OutOfRange); + } + Ok(result as u32) + } + + pub fn id(&self) -> Option { + use TrackRouteSelector::*; + match self { + ById(id) => Some(*id), + _ => None, + } + } + + pub fn index(&self) -> Option { + use TrackRouteSelector::*; + match self { + ByIndex(i) => Some(*i), + _ => None, + } + } + + pub fn name(&self) -> Option { + use TrackRouteSelector::*; + match self { + ByName(name) => Some(name.to_string()), + _ => None, + } + } +} + +impl fmt::Display for VirtualTrackRoute { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use TrackRouteSelector::*; + match &self.selector { + Dynamic(_) => f.write_str(""), + ById(id) => write!(f, "{}", id.to_string_without_braces()), + ByName(name) => write!(f, "\"{name}\""), + ByIndex(i) => write!(f, "#{}", i + 1), + } + } +} + +impl VirtualTrackRoute { + pub fn resolve( + &self, + track: &Track, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + self.selector + .resolve(track, self.r#type, context, compartment) + } + + pub fn id(&self) -> Option { + self.selector.id() + } + + pub fn index(&self) -> Option { + self.selector.index() + } + + pub fn name(&self) -> Option { + self.selector.name() + } + + pub fn can_be_affected_by_parameters(&self) -> bool { + matches!( + self, + VirtualTrackRoute { + selector: TrackRouteSelector::Dynamic(_), + .. + } + ) + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum TrackRouteType { + #[serde(rename = "send")] + #[display(fmt = "Send")] + Send, + #[serde(rename = "receive")] + #[display(fmt = "Receive")] + Receive, + #[serde(rename = "output")] + #[display(fmt = "Output")] + HardwareOutput, +} + +impl Default for TrackRouteType { + fn default() -> Self { + Self::Send + } +} + +#[derive(Debug)] +pub enum VirtualPlaytimeSlot { + Active, + ByIndex(playtime_api::persistence::SlotAddress), + Dynamic { + column_evaluator: Box, + row_evaluator: Box, + }, +} + +impl VirtualPlaytimeSlot { + pub fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + use VirtualPlaytimeSlot::*; + let coordinates = match self { + Active => { + #[cfg(not(feature = "playtime"))] + { + return Err("Playtime not available"); + } + #[cfg(feature = "playtime")] + { + let instance = context.control_context.instance.borrow(); + let matrix = instance + .get_playtime_matrix() + .map_err(|_| "couldn't get matrix")?; + matrix + .active_cell() + .to_slot_address() + .ok_or("no slot active")? + } + } + ByIndex(address) => *address, + Dynamic { + column_evaluator, + row_evaluator, + } => { + let compartment_params = context.params().compartment_params(compartment); + let column_index = + to_slot_coordinate(column_evaluator.evaluate_with_params_and_additional_vars( + compartment_params, + additional_playtime_vars(context.control_context), + ))?; + let row_index = + to_slot_coordinate(row_evaluator.evaluate_with_params_and_additional_vars( + compartment_params, + additional_playtime_vars(context.control_context), + ))?; + SlotAddress::new(column_index, row_index) + } + }; + // let slot_exists = BackboneState::get() + // .with_clip_matrix(context.control_context.instance_state, |matrix| { + // matrix.slot_exists(coordinates) + // })?; + // if !slot_exists { + // return Err("slot doesn't exist"); + // } + Ok(coordinates) + } + + pub fn can_be_affected_by_parameters(&self) -> bool { + matches!(self, VirtualPlaytimeSlot::Dynamic { .. }) + } +} + +#[derive(Debug)] +pub enum VirtualPlaytimeColumn { + Active, + ByIndex(usize), + Dynamic(Box), +} + +impl Default for VirtualPlaytimeColumn { + fn default() -> Self { + Self::Active + } +} + +impl VirtualPlaytimeColumn { + pub fn from_descriptor( + descriptor: &helgobox_api::persistence::PlaytimeColumnDescriptor, + ) -> Result { + use helgobox_api::persistence::PlaytimeColumnDescriptor::*; + let column = match descriptor { + Active => VirtualPlaytimeColumn::Active, + ByIndex(address) => VirtualPlaytimeColumn::ByIndex(address.index), + Dynamic { + expression: index_expression, + } => { + let index_evaluator = ExpressionEvaluator::compile(index_expression) + .map_err(|_| "couldn't evaluate column index")?; + VirtualPlaytimeColumn::Dynamic(Box::new(index_evaluator)) + } + }; + Ok(column) + } + + pub fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + use VirtualPlaytimeColumn::*; + let index = match self { + Active => { + #[cfg(not(feature = "playtime"))] + { + return Err("Playtime not available"); + } + #[cfg(feature = "playtime")] + { + let instance = context.control_context.instance.borrow(); + let matrix = instance + .get_playtime_matrix() + .map_err(|_| "couldn't get matrix")?; + matrix + .active_cell() + .column_index + .ok_or("no column selected")? + } + } + ByIndex(index) => *index, + Dynamic(evaluator) => { + let compartment_params = context.params().compartment_params(compartment); + to_slot_coordinate(evaluator.evaluate_with_params_and_additional_vars( + compartment_params, + additional_playtime_vars(context.control_context), + ))? + } + }; + // let column_exists = BackboneState::get() + // .with_clip_matrix(context.control_context.instance_state, |matrix| { + // index < matrix.column_count() + // })?; + // if !column_exists { + // return Err("column doesn't exist"); + // } + Ok(index) + } + + pub fn can_be_affected_by_parameters(&self) -> bool { + matches!(self, VirtualPlaytimeColumn::Dynamic { .. }) + } +} + +#[derive(Debug)] +pub enum VirtualPlaytimeRow { + Active, + ByIndex(usize), + Dynamic(Box), +} + +impl Default for VirtualPlaytimeRow { + fn default() -> Self { + Self::Active + } +} + +impl VirtualPlaytimeRow { + pub fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + use VirtualPlaytimeRow::*; + let index = match self { + Active => { + #[cfg(not(feature = "playtime"))] + { + return Err("Playtime not available"); + } + #[cfg(feature = "playtime")] + { + let instance = context.control_context.instance.borrow(); + let matrix = instance + .get_playtime_matrix() + .map_err(|_| "couldn't get matrix")?; + matrix.active_cell().row_index.ok_or("no row selected")? + } + } + ByIndex(index) => *index, + Dynamic(evaluator) => { + let compartment_params = context.params().compartment_params(compartment); + to_slot_coordinate(evaluator.evaluate_with_params_and_additional_vars( + compartment_params, + additional_playtime_vars(context.control_context), + ))? + } + }; + Ok(index) + } + + pub fn can_be_affected_by_parameters(&self) -> bool { + matches!(self, VirtualPlaytimeRow::Dynamic { .. }) + } +} + +fn to_slot_coordinate(eval_result: Result) -> Result { + let res = eval_result.map_err(|_| "couldn't evaluate clip slot coordinate")?; + if res < 0.0 { + return Err("negative clip slot coordinate"); + } + Ok(res.round() as usize) +} + +#[derive(Debug)] +pub enum VirtualTrack { + /// Current track (the one which contains the ReaLearn instance). + This, + /// Currently selected track. + Selected { allow_multiple: bool }, + /// Position in project based on parameter values. + Dynamic { + evaluator: Box, + scope: TrackScope, + }, + /// Master track. + Master, + /// Particular. + ById(Guid), + /// Particular. + ByName { + wild_match: WildMatch, + allow_multiple: bool, + }, + /// Particular. + ByIndex { index: u32, scope: TrackScope }, + /// This is the old default for targeting a particular track and it exists solely for backward + /// compatibility. + ByIdOrName(Guid, WildMatch), + /// Uses the track from the given clip column. + FromClipColumn { + column: VirtualPlaytimeColumn, + context: helgobox_api::persistence::ClipColumnTrackContext, + }, + /// Unit track + Unit, +} + +impl Default for VirtualTrack { + fn default() -> Self { + Self::This + } +} + +#[derive(Debug)] +pub enum VirtualFxParameter { + Dynamic(Box), + ByName(WildMatch), + ById(u32), + ByIndex(u32), +} + +impl VirtualFxParameter { + pub fn resolve( + &self, + fx: &Fx, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + use VirtualFxParameter::*; + match self { + Dynamic(evaluator) => { + let i = Self::evaluate_to_fx_parameter_index(evaluator, context, compartment, fx)?; + resolve_parameter_by_index(fx, i) + } + ByName(name) => fx + .parameters() + // Parameter names are not reliably UTF-8-encoded (e.g. "JS: Stereo Width") + .find(|p| { + if let Ok(param_name) = p.name() { + name.matches(¶m_name.into_inner().to_string_lossy()) + } else { + false + } + }) + .ok_or_else(|| FxParameterResolveError::FxParameterNotFound { + name: Some(name.clone()), + index: None, + }), + ByIndex(i) | ById(i) => resolve_parameter_by_index(fx, *i), + } + } + + pub fn calculated_fx_parameter_index( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + fx: &Fx, + ) -> Option { + if let VirtualFxParameter::Dynamic(evaluator) = self { + Some(Self::evaluate_to_fx_parameter_index(evaluator, context, compartment, fx).ok()?) + } else { + None + } + } + + pub fn can_be_affected_by_parameters(&self) -> bool { + matches!(self, VirtualFxParameter::Dynamic(_)) + } + + fn evaluate_to_fx_parameter_index( + evaluator: &ExpressionEvaluator, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + fx: &Fx, + ) -> Result { + let compartment_params = context.params().compartment_params(compartment); + let result = evaluator + .evaluate_with_params_and_additional_vars(compartment_params, |name, args| match name { + "mapped_fx_parameter_indexes" => { + let slot_index = extract_first_arg_as_positive_integer(args)?; + let target_state = Backbone::target_state().borrow(); + let preset = target_state.current_fx_preset(fx); + let index = preset + .and_then(|p| { + p.find_macro_param_at(slot_index)? + .fx_param? + .resolved_param_index + }) + .map(|i| i as f64) + .unwrap_or(EXPRESSION_NONE_VALUE); + Some(index) + } + "tcp_fx_parameter_indexes" => { + let i = extract_first_arg_as_positive_integer(args)?; + let project = context.context.project_or_current_project(); + let index = fx + .track() + .and_then(|t| unsafe { + let t = t.raw().ok()?; + Reaper::get() + .medium_reaper() + .get_tcp_fx_parm(project.context(), t, i) + .ok() + }) + .map(|res| res.param_index as f64) + .unwrap_or(EXPRESSION_NONE_VALUE); + Some(index) + } + _ => None, + }) + .map_err(|_| FxParameterResolveError::ExpressionFailed)? + .round() as i32; + if result < 0 { + return Err(FxParameterResolveError::OutOfRange); + } + Ok(result as u32) + } + + pub fn index(&self) -> Option { + use VirtualFxParameter::*; + match self { + ByIndex(i) | ById(i) => Some(*i), + _ => None, + } + } + + pub fn name(&self) -> Option { + use VirtualFxParameter::*; + match self { + ByName(name) => Some(name.to_string()), + _ => None, + } + } +} + +impl fmt::Display for VirtualFxParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use VirtualFxParameter::*; + match self { + Dynamic(_) => f.write_str(""), + ByName(name) => write!(f, "\"{name}\""), + ByIndex(i) | ById(i) => write!(f, "#{}", i + 1), + } + } +} + +#[derive(Debug)] +pub struct ExpressionEvaluator { + slab: Slab, + instruction: Instruction, +} + +impl ExpressionEvaluator { + pub fn compile(expression: &str) -> Result> { + let parser = fasteval::Parser::new(); + let mut slab = fasteval::Slab::new(); + let instruction = parser + .parse(expression, &mut slab.ps)? + .from(&slab.ps) + .compile(&slab.ps, &mut slab.cs); + let evaluator = Self { slab, instruction }; + Ok(evaluator) + } + + pub fn evaluate_with_params(&self, params: &CompartmentParams) -> Result { + self.evaluate_internal(Some(params), |_, _| None) + } + + pub fn evaluate_with_additional_vars( + &self, + vars: impl Fn(&str, &[f64]) -> Option, + ) -> Result { + self.evaluate_internal(None, vars) + } + + pub fn evaluate_with_params_and_additional_vars( + &self, + parameters: &CompartmentParams, + additional_vars: impl Fn(&str, &[f64]) -> Option, + ) -> Result { + self.evaluate_internal(Some(parameters), additional_vars) + } + + fn evaluate_internal( + &self, + params: Option<&CompartmentParams>, + additional_vars: impl Fn(&str, &[f64]) -> Option, + ) -> Result { + use fasteval::eval_compiled_ref; + let mut cb = |name: &str, args: Vec| -> Option { + // Use-case specific variables + if let Some(value) = additional_vars(name, &args) { + return Some(value); + } + match name { + "none" => Some(EXPRESSION_NONE_VALUE), + // Parameter array + "p" => { + if let [index] = args.as_slice() { + if *index < 0.0 { + return None; + } + let params = params?; + let index = index.round() as u32; + let index = CompartmentParamIndex::try_from(index).ok()?; + Some(params.at(index).effective_value().into()) + } else { + None + } + } + // Parameter variables (p1, p2, ...) + _ => { + if !name.starts_with('p') { + return None; + } + let one_based_position: u32 = name[1..].parse().ok()?; + if one_based_position == 0 { + return None; + } + let params = params?; + let index = one_based_position - 1; + let index = CompartmentParamIndex::try_from(index).ok()?; + Some(params.at(index).effective_value().into()) + } + } + }; + #[allow(unexpected_cfgs)] + let val = eval_compiled_ref!(&self.instruction, &self.slab, &mut cb); + Ok(val) + } +} + +/// If fasteval encounters usage of a non-existing variable, it fails. Good. But sometimes we need +/// to express that the variable is there and the value is just "None", e.g. in order to use it in a +/// boolean check and react to it accordingly. fasteval doesn't have a +/// dedicated value for that, so we just define an exotic f64 to represent it! We need one that +/// is equal to itself, so NAN and NEG_INFINITY are no options. +/// +/// It's important that this is lower than zero because we want `foo > 0` evaluate to `false` if +/// `foo` is "None". +pub const EXPRESSION_NONE_VALUE: f64 = f64::MIN; + +impl fmt::Display for VirtualTrack { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use VirtualTrack::*; + match self { + This => f.write_str(""), + Selected { allow_multiple } => f.write_str(if *allow_multiple { + "" + } else { + "" + }), + Master => f.write_str(""), + Unit => f.write_str(""), + Dynamic { scope, .. } => { + let text = match scope { + TrackScope::AllTracks => "", + TrackScope::TracksVisibleInTcp => "", + TrackScope::TracksVisibleInMcp => "", + }; + f.write_str(text) + } + ByIdOrName(id, name) => write!(f, "{} or \"{}\"", id.to_string_without_braces(), name), + ById(id) => write!(f, "{}", id.to_string_without_braces()), + ByName { + wild_match, + allow_multiple, + } => write!( + f, + "\"{}\"{}", + wild_match, + if *allow_multiple { " (all)" } else { "" } + ), + ByIndex { index, scope } => { + let suffix = match scope { + TrackScope::AllTracks => "", + TrackScope::TracksVisibleInTcp => " in TCP", + TrackScope::TracksVisibleInMcp => " in MCP", + }; + write!(f, "#{}{}", index + 1, suffix) + } + FromClipColumn { .. } => f.write_str("From a clip column"), + } + } +} + +#[derive(Debug)] +pub enum VirtualFx { + /// This ReaLearn FX (nice for controlling conditional activation parameters). + This, + /// Last relevant focused FX, even if FX focus lost or if FX window is closed. + /// + /// Doesn't include current ReaLearn instance. + LastFocused, + /// Unit FX. + Unit, + /// Particular FX. + ChainFx { + is_input_fx: bool, + chain_fx: VirtualChainFx, + }, +} + +impl Default for VirtualFx { + fn default() -> Self { + // Important to keep it "Focused" for compatibility with + // "Auto-load depending on focused FX". + Self::LastFocused + } +} + +impl fmt::Display for VirtualFx { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use VirtualFx::*; + match self { + This => f.write_str(""), + LastFocused => f.write_str(""), + Unit => f.write_str(""), + ChainFx { + chain_fx, + is_input_fx, + } => { + chain_fx.fmt(f)?; + if *is_input_fx { + f.write_str(" (input FX)")?; + } + Ok(()) + } + } + } +} + +impl VirtualFx { + pub fn id(&self) -> Option { + match self { + VirtualFx::This => None, + VirtualFx::LastFocused => None, + VirtualFx::Unit => None, + VirtualFx::ChainFx { chain_fx, .. } => chain_fx.id(), + } + } + + pub fn is_input_fx(&self) -> bool { + match self { + // In case of , it doesn't matter. + VirtualFx::This => false, + VirtualFx::LastFocused => false, + VirtualFx::Unit => false, + VirtualFx::ChainFx { is_input_fx, .. } => *is_input_fx, + } + } + + pub fn index(&self) -> Option { + match self { + VirtualFx::This => None, + VirtualFx::LastFocused => None, + VirtualFx::Unit => None, + VirtualFx::ChainFx { chain_fx, .. } => chain_fx.index(), + } + } + + pub fn name(&self) -> Option { + match self { + VirtualFx::This => None, + VirtualFx::LastFocused => None, + VirtualFx::Unit => None, + VirtualFx::ChainFx { chain_fx, .. } => chain_fx.name(), + } + } + + pub fn can_be_affected_by_parameters(&self) -> bool { + matches!( + self, + VirtualFx::ChainFx { + chain_fx: VirtualChainFx::Dynamic(_), + .. + } + ) + } +} + +impl VirtualTrack { + pub fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, TrackResolveError> { + use VirtualTrack::*; + let project = context.context().project_or_current_project(); + let tracks = match self { + This => { + let single = context + .context() + .containing_fx() + .track() + .cloned() + // If this is monitoring FX, we want this to resolve to the master track since + // in most functions, monitoring FX chain is the "input FX chain" of the master + // track. + .or_else(|| project.master_track().ok()) + .ok_or(TrackResolveError::ProjectNotAvailable)?; + vec![single] + } + Selected { allow_multiple } => project + .selected_tracks(MasterTrackBehavior::IncludeMasterTrack) + .take(if *allow_multiple { MAX_MULTIPLE } else { 1 }) + .collect(), + Dynamic { + evaluator: expression_evaluator, + scope, + } => { + let index = + Self::evaluate_to_track_index(expression_evaluator, context, compartment)?; + let single = resolve_track_by_index(project, index, *scope)?; + vec![single] + } + Master => vec![project + .master_track() + .map_err(|_| TrackResolveError::ProjectNotAvailable)?], + Unit => { + let instance_state = context.control_context.unit.borrow(); + let instance_track = instance_state.instance_track_descriptor(); + if matches!(&instance_track.track, VirtualTrack::Unit) { + return Err(TrackResolveError::CircularReference); + } + return instance_track.track.resolve(context, compartment); + } + ByIdOrName(guid, name) => { + let t = project + .track_by_guid(guid) + .map_err(|_| TrackResolveError::ProjectNotAvailable)?; + let single = if t.is_available() { + t + } else { + find_track_by_name(project, name).ok_or(TrackResolveError::TrackNotFound { + guid: Some(*guid), + name: Some(name.clone()), + index: None, + })? + }; + vec![single] + } + ById(guid) => { + let single = project + .track_by_guid(guid) + .map_err(|_| TrackResolveError::ProjectNotAvailable)?; + if !single.is_available() { + return Err(TrackResolveError::TrackNotFound { + guid: Some(*guid), + name: None, + index: None, + }); + } + vec![single] + } + ByName { + wild_match, + allow_multiple, + } => find_tracks_by_name(project, wild_match) + .take(if *allow_multiple { MAX_MULTIPLE } else { 1 }) + .collect(), + ByIndex { index, scope } => { + let single = resolve_track_by_index(project, *index as i32, *scope)?; + vec![single] + } + FromClipColumn { + column, + context: track_context, + } => { + #[cfg(not(feature = "playtime"))] + { + let _ = (column, track_context); + vec![] + } + #[cfg(feature = "playtime")] + { + crate::domain::playtime_util::resolve_virtual_track_by_playtime_column( + context, + compartment, + column, + track_context, + )? + } + } + }; + Ok(tracks) + } + + #[allow(clippy::match_like_matches_macro)] + pub fn can_be_affected_by_parameters(&self) -> bool { + match self { + VirtualTrack::Dynamic { .. } => true, + VirtualTrack::FromClipColumn { + column: VirtualPlaytimeColumn::Dynamic(_), + .. + } => true, + _ => false, + } + } + + pub fn calculated_track_index( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Option { + if let VirtualTrack::Dynamic { + evaluator: expression_evaluator, + .. + } = self + { + Some(Self::evaluate_to_track_index(expression_evaluator, context, compartment).ok()?) + } else { + None + } + } + + fn evaluate_to_track_index( + evaluator: &ExpressionEvaluator, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result { + let compartment_params = context.params().compartment_params(compartment); + let result = evaluator + .evaluate_with_params_and_additional_vars(compartment_params, |name, args| { + match name { + "this_track_index" => { + let track = context.context().track()?; + Some(get_track_index_for_expression(track)) + } + "instance_track_index" + | "unit_track_index" + | "instance_track_tcp_index" + | "unit_track_tcp_index" + | "instance_track_mcp_index" + | "unit_track_mcp_index" => { + let scope = match name { + "instance_track_index" | "unit_track_index" => TrackScope::AllTracks, + "instance_track_tcp_index" | "unit_track_tcp_index" => { + TrackScope::TracksVisibleInTcp + } + "instance_track_mcp_index" | "unit_track_mcp_index" => { + TrackScope::TracksVisibleInMcp + } + _ => unreachable!(), + }; + let instance_track = context + .control_context + .unit + // We do this in order to prevent infinite recursion in case the + // instance FX also uses "instance_track_index". + .try_borrow_mut() + .ok()? + .instance_track_descriptor() + .track + .resolve(context, compartment) + .ok() + .and_then(|tracks| tracks.into_iter().next()); + Some(get_scoped_track_index_for_expression(instance_track, scope)) + } + "selected_track_index" + | "selected_track_tcp_index" + | "selected_track_mcp_index" => { + let scope = match name { + "selected_track_index" => TrackScope::AllTracks, + "selected_track_tcp_index" => TrackScope::TracksVisibleInTcp, + "selected_track_mcp_index" => TrackScope::TracksVisibleInMcp, + _ => unreachable!(), + }; + let project = context.context().project_or_current_project(); + let selected_track = first_selected_track_scoped( + project, + scope, + MasterTrackBehavior::IncludeMasterTrack, + ); + Some(get_scoped_track_index_for_expression(selected_track, scope)) + } + "selected_track_indexes" => { + let i = extract_first_arg_as_positive_integer(args)?; + let reaper = Reaper::get().medium_reaper(); + let project = context.context().project_or_current_project(); + let raw_track = reaper.get_selected_track_2( + project.context(), + i, + MasterTrackBehavior::IncludeMasterTrack, + ); + match raw_track { + None => Some(EXPRESSION_NONE_VALUE), + Some(raw_track) => { + let t = Track::new(raw_track, Some(project.raw())); + Some(get_track_index_for_expression(&t)) + } + } + } + _ => None, + } + }) + .map_err(|_| TrackResolveError::ExpressionFailed)? + .round() as i32; + if result < -1 { + return Err(TrackResolveError::OutOfRange); + } + Ok(result) + } + + pub fn id(&self) -> Option { + use VirtualTrack::*; + match self { + ById(id) | ByIdOrName(id, _) => Some(*id), + _ => None, + } + } + + pub fn index(&self) -> Option { + use VirtualTrack::*; + match self { + ByIndex { index, .. } => Some(*index), + _ => None, + } + } + + pub fn scope(&self) -> Option { + use VirtualTrack::*; + match self { + ByIndex { scope, .. } | Dynamic { scope, .. } => Some(*scope), + _ => None, + } + } + + pub fn name(&self) -> Option { + use VirtualTrack::*; + match self { + ByName { + wild_match: name, .. + } + | ByIdOrName(_, name) => Some(name.to_string()), + _ => None, + } + } + + pub fn clip_column(&self) -> Option<&VirtualPlaytimeColumn> { + if let VirtualTrack::FromClipColumn { column, .. } = self { + Some(column) + } else { + None + } + } + + pub fn clip_column_track_context( + &self, + ) -> Option { + if let VirtualTrack::FromClipColumn { context, .. } = self { + Some(*context) + } else { + None + } + } +} + +#[derive(Debug)] +pub enum VirtualChainFx { + /// Position in FX chain based on parameter values. + Dynamic(Box), + /// This is the new default. + /// + /// The index is just used as performance hint, not as fallback. + ById(Guid, Option), + ByName { + wild_match: WildMatch, + allow_multiple: bool, + }, + ByIndex(u32), + /// This is the old default. + /// + /// The index comes into play as fallback whenever track is "" or the GUID can't be + /// determined (is `None`). I'm not sure how latter is possible but I keep it for backward + /// compatibility. + ByIdOrIndex(Option, u32), +} + +impl fmt::Display for VirtualChainFx { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use VirtualChainFx::*; + match self { + Dynamic(_) => f.write_str(""), + ById(guid, _) => { + write!(f, "{}", guid.to_string_without_braces()) + } + ByName { + wild_match, + allow_multiple, + } => write!( + f, + "\"{}\"{}", + wild_match, + if *allow_multiple { " (all)" } else { "" } + ), + ByIdOrIndex(None, i) | ByIndex(i) => write!(f, "#{}", i + 1), + ByIdOrIndex(Some(guid), i) => { + write!(f, "{} ({})", guid.to_string_without_braces(), i + 1) + } + } + } +} + +fn find_track_by_name(project: Project, name: &WildMatch) -> Option { + project.tracks().find(|t| match t.name() { + None => false, + Some(n) => name.matches(n.to_str()), + }) +} + +fn find_tracks_by_name(project: Project, name: &WildMatch) -> impl Iterator + '_ { + project.tracks().filter(move |t| match t.name() { + None => false, + Some(n) => name.matches(n.to_str()), + }) +} + +#[derive(Clone, Debug, Display, Error)] +pub enum TrackResolveError { + #[display(fmt = "ExpressionFailed")] + ExpressionFailed, + #[display(fmt = "OutOfRange")] + OutOfRange, + ProjectNotAvailable, + #[display(fmt = "TrackNotFound")] + TrackNotFound { + guid: Option, + name: Option, + index: Option, + }, + NoTrackSelected, + CircularReference, +} + +#[derive(Clone, Debug, Display, Error)] +pub enum FxParameterResolveError { + #[display(fmt = "ExpressionFailed")] + ExpressionFailed, + #[display(fmt = "OutOfRange")] + OutOfRange, + #[display(fmt = "FxParameterNotFound")] + FxParameterNotFound { + name: Option, + index: Option, + }, +} + +#[derive(Clone, Debug, Display, Error)] +pub enum TrackRouteResolveError { + #[display(fmt = "ExpressionFailed")] + ExpressionFailed, + #[display(fmt = "OutOfRange")] + OutOfRange, + #[display(fmt = "InvalidRoute")] + InvalidRoute, + ProjectNotAvailable, + #[display(fmt = "TrackRouteNotFound")] + TrackRouteNotFound { + guid: Option, + name: Option, + index: Option, + }, +} + +impl VirtualChainFx { + pub fn resolve( + &self, + fx_chains: &[FxChain], + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, FxResolveError> { + use VirtualChainFx::*; + let fxs = match self { + Dynamic(evaluator) => fx_chains + .iter() + .flat_map(|fx_chain| { + let index = + Self::evaluate_to_fx_index(evaluator, context, compartment, fx_chain)?; + get_index_based_fx_on_chain(fx_chain, index).map_err(|_| { + FxResolveError::FxNotFound { + guid: None, + name: None, + index: Some(index), + } + }) + }) + .collect(), + ById(guid, index) => { + let fx_not_found_error = || FxResolveError::FxNotFound { + guid: Some(*guid), + name: None, + index: None, + }; + // It doesn't make sense to search for the same FX ID on multiple tracks, so we + // only take the first one. + let fx_chain = fx_chains.first().ok_or_else(fx_not_found_error)?; + let single = + get_guid_based_fx_by_guid_on_chain_with_index_hint(fx_chain, guid, *index) + .map_err(|_| fx_not_found_error())?; + vec![single] + } + ByName { + wild_match, + allow_multiple, + } => find_fxs_by_name(fx_chains, wild_match) + .take(if *allow_multiple { MAX_MULTIPLE } else { 1 }) + .collect(), + ByIndex(index) | ByIdOrIndex(None, index) => fx_chains + .iter() + .flat_map(|fx_chain| { + get_index_based_fx_on_chain(fx_chain, *index).map_err(|_| { + FxResolveError::FxNotFound { + guid: None, + name: None, + index: Some(*index), + } + }) + }) + .collect(), + ByIdOrIndex(Some(guid), index) => { + let fx_not_found_error = || FxResolveError::FxNotFound { + guid: Some(*guid), + name: None, + index: Some(*index), + }; + // It doesn't make sense to search for the same FX ID on multiple tracks, so we + // only take the first one. + let fx_chain = fx_chains.first().ok_or_else(fx_not_found_error)?; + // Track by GUID because target relates to a very particular FX + let single = get_guid_based_fx_by_guid_on_chain_with_index_hint( + fx_chain, + guid, + Some(*index), + ) + // Fall back to index-based + .or_else(|_| get_index_based_fx_on_chain(fx_chain, *index)) + .map_err(|_| fx_not_found_error())?; + vec![single] + } + }; + Ok(fxs) + } + + pub fn calculated_fx_index( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + chain: &FxChain, + ) -> Option { + if let VirtualChainFx::Dynamic(evaluator) = self { + Some(Self::evaluate_to_fx_index(evaluator, context, compartment, chain).ok()?) + } else { + None + } + } + + fn evaluate_to_fx_index( + evaluator: &ExpressionEvaluator, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + chain: &FxChain, + ) -> Result { + let compartment_params = context.params().compartment_params(compartment); + let result = evaluator + .evaluate_with_params_and_additional_vars(compartment_params, |name, args| match name { + "this_fx_index" => { + let fx = context.context().containing_fx(); + Some(fx.index() as f64) + } + "instance_fx_index" | "unit_fx_index" => { + let index = context + .control_context + .unit + // We do this in order to prevent infinite recursion in case the + // instance FX also uses "unit_fx_index". + .try_borrow_mut() + .ok()? + .instance_fx_descriptor() + .resolve(context, compartment) + .ok() + .and_then(|fxs| fxs.into_iter().next()) + .map(|fx| fx.index() as f64); + Some(index.unwrap_or(EXPRESSION_NONE_VALUE)) + } + "tcp_fx_indexes" => { + let i = extract_first_arg_as_positive_integer(args)?; + if chain.is_input_fx() { + // Only FX parameters from the normal FX chain can be displayed in TCP. + return Some(EXPRESSION_NONE_VALUE); + } + let project = context.context.project_or_current_project(); + let index = chain + .track() + .and_then(|t| unsafe { + let t = t.raw().ok()?; + Reaper::get() + .medium_reaper() + .get_tcp_fx_parm(project.context(), t, i) + .ok() + }) + .map(|res| res.fx_location.to_raw() as f64) + .unwrap_or(EXPRESSION_NONE_VALUE); + Some(index) + } + _ => None, + }) + .map_err(|_| FxResolveError::ExpressionFailed)? + .round() as i32; + if result < 0 { + return Err(FxResolveError::OutOfRange); + } + Ok(result as u32) + } + + pub fn id(&self) -> Option { + use VirtualChainFx::*; + match self { + ById(id, _) => Some(*id), + ByIdOrIndex(id, _) => *id, + _ => None, + } + } + + pub fn index(&self) -> Option { + use VirtualChainFx::*; + match self { + ByIndex(i) | ByIdOrIndex(_, i) => Some(*i), + ById(_, index_hint) => *index_hint, + _ => None, + } + } + + pub fn name(&self) -> Option { + use VirtualChainFx::*; + match self { + ByName { wild_match, .. } => Some(wild_match.to_string()), + _ => None, + } + } +} + +fn find_fxs_by_name<'a>( + chains: &'a [FxChain], + name: &'a WildMatch, +) -> impl Iterator + 'a { + chains + .iter() + .flat_map(|chain| chain.fxs()) + .filter(move |fx| with_fx_name(fx, |fx_name| name.matches(fx_name.as_ref()))) +} + +/// Correctly transforms the FX name into a UTF-8 string. +/// +/// See [`with_fx_name`]. +pub fn get_fx_name(fx: &Fx) -> String { + with_fx_name(fx, |name| name.into_owned()) +} + +/// Correctly transforms the FX name into a UTF-8 string. +/// +/// Replaces invalid UTF-8 sequences with replacement characters. +/// +/// https://github.com/helgoboss/helgobox/issues/595 +pub fn with_fx_name(fx: &Fx, f: impl FnOnce(Cow) -> R) -> R { + f(fx.name().into_inner().to_string_lossy()) +} + +#[derive(Clone, Debug, Display, Error)] +pub enum FxResolveError { + #[display(fmt = "ExpressionFailed")] + ExpressionFailed, + #[display(fmt = "OutOfRange")] + OutOfRange, + #[display(fmt = "FxNotFound")] + FxNotFound { + guid: Option, + name: Option, + index: Option, + }, +} + +pub fn get_non_present_virtual_track_label(track: &VirtualTrack) -> String { + format!(" ({track})") +} + +pub fn get_non_present_virtual_route_label(route: &VirtualTrackRoute) -> String { + format!(" ({route})") +} + +// Returns an error if that param (or FX) doesn't exist. +pub fn get_fx_params( + context: ExtendedProcessorContext, + fx_parameter_descriptor: &FxParameterDescriptor, + compartment: CompartmentKind, +) -> Result, &'static str> { + let fxs = fx_parameter_descriptor + .fx_descriptor + .resolve(context, compartment)?; + let parameters = fxs + .into_iter() + .flat_map(|fx| { + fx_parameter_descriptor + .fx_parameter + .resolve(&fx, context, compartment) + .map_err(|_| "parameter doesn't exist") + }) + .collect(); + Ok(parameters) +} + +fn get_index_based_fx_on_chain(fx_chain: &FxChain, fx_index: u32) -> Result { + let fx = fx_chain.fx_by_index_untracked(fx_index); + if !fx.is_available() { + return Err("no FX at that index"); + } + Ok(fx) +} + +fn resolve_parameter_by_index(fx: &Fx, index: u32) -> Result { + let param = fx.parameter_by_index(index); + if !param.is_available() { + return Err(FxParameterResolveError::FxParameterNotFound { + name: None, + index: Some(index), + }); + } + Ok(param) +} + +fn resolve_track_by_index( + project: Project, + index: i32, + scope: TrackScope, +) -> Result { + if index >= 0 { + let i = index as u32; + get_track_by_scoped_index(project, i, scope).ok_or(TrackResolveError::TrackNotFound { + guid: None, + name: None, + index: Some(i), + }) + } else { + project + .master_track() + .map_err(|_| TrackResolveError::ProjectNotAvailable) + } +} + +pub fn resolve_track_route_by_index( + track: &Track, + route_type: TrackRouteType, + index: u32, +) -> Result { + let option = match route_type { + TrackRouteType::Send => track.typed_send_by_index(SendPartnerType::Track, index), + TrackRouteType::Receive => track.receive_by_index(index), + TrackRouteType::HardwareOutput => { + track.typed_send_by_index(SendPartnerType::HardwareOutput, index) + } + }; + if let Some(route) = option { + Ok(route) + } else { + Err(TrackRouteResolveError::TrackRouteNotFound { + guid: None, + name: None, + index: Some(index), + }) + } +} + +pub fn get_fx_chains( + context: ExtendedProcessorContext, + track: &VirtualTrack, + is_input_fx: bool, + compartment: CompartmentKind, +) -> Result, &'static str> { + let fx_chains = get_effective_tracks(context, track, compartment)? + .into_iter() + .map(|track| get_fx_chain(track, is_input_fx)) + .collect(); + Ok(fx_chains) +} + +fn get_fx_chain(track: Track, is_input_fx: bool) -> FxChain { + if is_input_fx { + if track.is_master_track() { + // The combination "Master track + input FX chain" by convention represents the + // monitoring FX chain in REAPER. It's a bit unfortunate that we have 2 representations + // of the same thing: A special monitoring FX enum variant and this convention. + // E.g. it leads to the result that both representations are not equal from a reaper-rs + // perspective. We should enforce the enum variant whenever possible because the + // convention is somehow flawed. E.g. what if we have 2 master tracks of different + // projects? This should be done in reaper-high, there's already a to-do there. + Reaper::get().monitoring_fx_chain() + } else { + track.input_fx_chain() + } + } else { + track.normal_fx_chain() + } +} + +fn get_guid_based_fx_by_guid_on_chain_with_index_hint( + fx_chain: &FxChain, + guid: &Guid, + fx_index: Option, +) -> Result { + let fx = if let Some(i) = fx_index { + fx_chain.fx_by_guid_and_index(guid, i) + } else { + fx_chain.fx_by_guid(guid) + }; + // is_available() also invalidates the index if necessary + // TODO-low This is too implicit. + if !fx.is_available() { + return Err("no FX with that GUID"); + } + Ok(fx) +} + +pub fn find_bookmark( + project: Project, + bookmark_type: BookmarkType, + anchor_type: BookmarkAnchorType, + bookmark_ref: u32, +) -> Result { + if !project.is_available() { + return Err("project not available"); + } + match anchor_type { + BookmarkAnchorType::Index => project + .find_bookmark_by_type_and_index(bookmark_type, bookmark_ref) + .ok_or("bookmark with that type and index not found"), + BookmarkAnchorType::Id => project + .find_bookmark_by_type_and_id(bookmark_type, BookmarkId::new(bookmark_ref)) + .ok_or("bookmark with that type and ID not found"), + } +} + +fn find_route_by_related_track( + main_track: &Track, + related_track: &Track, + route_type: TrackRouteType, +) -> Result, TrackRouteResolveError> { + let option = match route_type { + TrackRouteType::Send => main_track.find_send_by_destination_track(related_track), + TrackRouteType::Receive => main_track.find_receive_by_source_track(related_track), + TrackRouteType::HardwareOutput => { + return Err(TrackRouteResolveError::InvalidRoute); + } + }; + Ok(option) +} + +fn find_route_by_name( + track: &Track, + name: &WildMatch, + route_type: TrackRouteType, +) -> Option { + let matcher = |r: &TrackRoute| name.matches(r.name().to_str()); + match route_type { + TrackRouteType::Send => track.typed_sends(SendPartnerType::Track).find(matcher), + TrackRouteType::Receive => track.receives().find(matcher), + TrackRouteType::HardwareOutput => track + .typed_sends(SendPartnerType::HardwareOutput) + .find(matcher), + } +} + +#[derive(Default)] +struct Descriptors<'a> { + track: Option<&'a TrackDescriptor>, + fx: Option<&'a FxDescriptor>, + route: Option<&'a TrackRouteDescriptor>, + fx_param: Option<&'a FxParameterDescriptor>, + clip_slot: Option<&'a VirtualPlaytimeSlot>, + clip_column: Option<&'a VirtualPlaytimeColumn>, + clip_row: Option<&'a VirtualPlaytimeRow>, +} + +#[enum_dispatch(UnresolvedReaperTarget)] +pub trait UnresolvedReaperTargetDef { + fn is_always_active(&self) -> bool { + false + } + + fn resolve( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Result, &'static str>; + + /// `None` means that no polling is necessary for feedback because we are notified via events. + fn feedback_resolution(&self) -> Option { + None + } + + /// Should return true if the target should be refreshed (reresolved) on changes such as track + /// selection etc. (see [`ReaperTarget::is_potential_change_event`]). If in doubt or too lazy to + /// make a distinction depending on the selector, better return true! This makes sure things + /// stay up-to-date. Doing an unnecessary refreshment can have the following effects: + /// - Slightly reduce performance: Not refreshing is of course cheaper (but resolving is + /// generally fast so this shouldn't matter) + /// - Removes target state: If the resolved target contains state, it's going to be disappear + /// when the target is resolved again. Matters for some targets (but usually not). + fn can_be_affected_by_change_events(&self) -> bool { + true + } + + fn track_descriptor(&self) -> Option<&TrackDescriptor> { + None + } + + fn fx_descriptor(&self) -> Option<&FxDescriptor> { + None + } + + fn route_descriptor(&self) -> Option<&TrackRouteDescriptor> { + None + } + + fn fx_parameter_descriptor(&self) -> Option<&FxParameterDescriptor> { + None + } + + fn clip_slot_descriptor(&self) -> Option<&VirtualPlaytimeSlot> { + None + } + + fn clip_column_descriptor(&self) -> Option<&VirtualPlaytimeColumn> { + None + } + + fn clip_row_descriptor(&self) -> Option<&VirtualPlaytimeRow> { + None + } +} + +/// Special: Index -1 means master track. +fn get_track_index_for_expression(track: &Track) -> f64 { + track.index().map(|i| i as f64).unwrap_or(-1.0) +} + +fn get_scoped_track_index_for_expression(track: Option, scope: TrackScope) -> f64 { + match track { + None => EXPRESSION_NONE_VALUE, + Some(t) => { + if t.is_master_track() { + -1.0 + } else { + scoped_track_index(&t, scope) + .map(|i| i as f64) + .unwrap_or(EXPRESSION_NONE_VALUE) + } + } + } +} + +fn extract_first_arg_as_positive_integer(args: &[f64]) -> Option { + let i = match args { + [i] => i, + _ => return None, + }; + if *i < 0.0 { + return None; + } + Some(i.round() as u32) +} + +pub fn get_track_by_scoped_index(project: Project, index: u32, scope: TrackScope) -> Option { + use TrackScope::*; + match scope { + AllTracks => project.track_by_index(index), + TracksVisibleInTcp | TracksVisibleInMcp => { + let track_area = get_reaper_track_area_of_scope(scope); + project + .tracks() + .filter(|t| t.is_shown(track_area)) + .enumerate() + .find(|(i, _)| *i == index as usize) + .map(|(_, t)| t) + } + } +} + +pub fn get_reaper_track_area_of_scope(scope: TrackScope) -> reaper_medium::TrackArea { + if scope == TrackScope::TracksVisibleInTcp { + TrackArea::Tcp + } else { + TrackArea::Mcp + } +} + +fn first_selected_track_scoped( + project: Project, + scope: TrackScope, + master_track_behavior: MasterTrackBehavior, +) -> Option { + use TrackScope::*; + match scope { + AllTracks => project.first_selected_track(master_track_behavior), + TracksVisibleInTcp | TracksVisibleInMcp => { + let track_area = get_reaper_track_area_of_scope(scope); + project + .selected_tracks(master_track_behavior) + .find(|t| t.is_shown(track_area)) + } + } +} + +fn additional_playtime_vars(context: ControlContext) -> impl Fn(&str, &[f64]) -> Option + '_ { + |name, _| match name { + "control_unit_column_index" => Some( + context + .unit + .borrow() + .control_unit_top_left_corner() + .column_index as f64, + ), + "control_unit_row_index" => Some( + context + .unit + .borrow() + .control_unit_top_left_corner() + .row_index as f64, + ), + _ => None, + } +} diff --git a/plugin-reaper-realearn/main/src/domain/virtual.rs b/plugin-reaper-realearn/main/src/domain/virtual.rs new file mode 100644 index 0000000..a1d3926 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/virtual.rs @@ -0,0 +1,558 @@ +use crate::domain::ui_util::{format_as_percentage_without_unit, parse_unit_value_from_percentage}; +use crate::domain::{ExtendedSourceCharacter, TargetCharacter}; +use ascii::{AsciiString, ToAsciiChar}; +use base::SmallAsciiString; +use derivative::Derivative; +use helgoboss_learn::{ + AbsoluteValue, ControlType, ControlValue, FeedbackValue, SourceCharacter, Target, UnitValue, +}; +use helgobox_api::persistence::VirtualControlElementCharacter; +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] +pub struct VirtualTarget { + pub control_element: VirtualControlElement, + pub learnable: bool, +} + +impl VirtualTarget { + pub fn control_element(&self) -> VirtualControlElement { + self.control_element + } + + pub fn character(&self) -> TargetCharacter { + use VirtualControlElementCharacter::*; + match self.control_element.character() { + Multi => TargetCharacter::VirtualMulti, + Button => TargetCharacter::VirtualButton, + } + } +} + +impl Target<'_> for VirtualTarget { + type Context = (); + + fn current_value(&self, _: ()) -> Option { + None + } + + fn control_type(&self, _: ()) -> ControlType { + use VirtualControlElementCharacter::*; + match self.control_element.character() { + Multi => ControlType::VirtualMulti, + Button => ControlType::VirtualButton, + } + } +} + +/// With virtual sources it's easy: The control element is the address. +pub type VirtualSourceAddress = VirtualControlElement; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct VirtualSource { + control_element: VirtualControlElement, +} + +impl VirtualSource { + pub fn feedback_address(&self) -> &VirtualSourceAddress { + &self.control_element + } + + /// Checks if this and the given source share the same address. + /// + /// Used for: + /// + /// - Feedback diffing + pub fn has_same_feedback_address_as_source(&self, other: &Self) -> bool { + self.control_element == other.control_element + } + + pub fn new(control_element: VirtualControlElement) -> VirtualSource { + VirtualSource { control_element } + } + + pub fn from_source_value(source_value: VirtualSourceValue) -> VirtualSource { + VirtualSource::new(source_value.control_element) + } + + pub fn control_element(&self) -> VirtualControlElement { + self.control_element + } + + pub fn control(&self, value: &VirtualSourceValue) -> Option { + if self.control_element != value.control_element { + return None; + } + Some(value.control_value) + } + + pub fn feedback(&self, feedback_value: FeedbackValue) -> VirtualFeedbackValue { + VirtualFeedbackValue::new(self.control_element, feedback_value.make_owned()) + } + + pub fn format_control_value(&self, value: ControlValue) -> Result { + let absolute_value = value.to_unit_value()?; + Ok(format_as_percentage_without_unit(absolute_value)) + } + + pub fn parse_control_value(&self, text: &str) -> Result { + parse_unit_value_from_percentage(text) + } + + pub fn character(&self) -> ExtendedSourceCharacter { + use VirtualControlElementCharacter::*; + match self.control_element.character() { + Button => ExtendedSourceCharacter::Normal(SourceCharacter::MomentaryButton), + Multi => ExtendedSourceCharacter::VirtualContinuous, + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct VirtualSourceValue { + control_element: VirtualControlElement, + control_value: ControlValue, +} + +impl Display for VirtualSourceValue { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{} with value {}", + self.control_element, self.control_value + ) + } +} + +impl VirtualSourceValue { + pub fn new( + control_element: VirtualControlElement, + control_value: ControlValue, + ) -> VirtualSourceValue { + VirtualSourceValue { + control_element, + control_value, + } + } + + pub fn control_element(&self) -> VirtualControlElement { + self.control_element + } + + pub fn control_value(&self) -> ControlValue { + self.control_value + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct VirtualFeedbackValue { + control_element: VirtualControlElement, + feedback_value: FeedbackValue<'static>, +} + +impl Display for VirtualFeedbackValue { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{} with value {}", + self.control_element, self.feedback_value + ) + } +} + +impl VirtualFeedbackValue { + pub fn new( + control_element: VirtualControlElement, + feedback_value: FeedbackValue<'static>, + ) -> Self { + VirtualFeedbackValue { + control_element, + feedback_value, + } + } + + pub fn control_element(&self) -> VirtualControlElement { + self.control_element + } + + pub fn feedback_value(&self) -> &FeedbackValue { + &self.feedback_value + } +} + +/// The combination of virtual control element ID and character. +/// +/// When matching indexed (numbered) control elements, it makes a difference whether the character is a multi or +/// a button! The character is part of the identifier, so to say. The reason is that indexed control elements +/// were designed to model typical "8 knob & 8 buttons" controllers. In this case it's important to +/// consider knob 1 as a different control element than button 1. +/// +/// For named control elements, the character doesn't act as an identifier, just as a hint for the UI optimizations. +/// Two named control elements are considered the same even they have 2 different characters. The rationale is that +/// named control elements have the freedom to use different names and should do so in order to avoid confusion. +/// +/// Maybe it would be better to make indexed control elements behave like named ones (= ignore the character). +/// But we have to maintain backwards compatibility. Named control elements were added much later, so in the beginning +/// it was vital to distinguish between characters, and thus I suspect there are many presets still using this system. +#[derive(Copy, Clone, Ord, PartialOrd, Debug, Derivative)] +#[derivative(Eq, PartialEq, Hash)] +pub enum VirtualControlElement { + Indexed { + id: u32, + character: VirtualControlElementCharacter, + }, + Named { + id: SmallAsciiString, + #[derivative(PartialEq = "ignore", Hash = "ignore")] + character: VirtualControlElementCharacter, + }, +} + +impl VirtualControlElement { + pub fn new(id: VirtualControlElementId, character: VirtualControlElementCharacter) -> Self { + match id { + VirtualControlElementId::Indexed(id) => Self::Indexed { id, character }, + VirtualControlElementId::Named(id) => Self::Named { id, character }, + } + } + + pub fn id(&self) -> VirtualControlElementId { + match self { + VirtualControlElement::Indexed { id, .. } => VirtualControlElementId::Indexed(*id), + VirtualControlElement::Named { id, .. } => VirtualControlElementId::Named(*id), + } + } + + pub fn character(&self) -> VirtualControlElementCharacter { + match self { + VirtualControlElement::Indexed { character, .. } => *character, + VirtualControlElement::Named { character, .. } => *character, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] +pub enum VirtualControlElementId { + Indexed(u32), + // No full String because we don't want heap allocations due to clones in real-time thread. + Named(SmallAsciiString), +} + +impl FromStr for VirtualControlElementId { + type Err = &'static str; + + fn from_str(text: &str) -> Result { + if let Ok(position) = text.parse::() { + let index = std::cmp::max(0, position - 1) as u32; + Ok(Self::Indexed(index)) + } else { + let small_ascii_string = create_control_element_name_lossy(text)?; + Ok(Self::Named(small_ascii_string)) + } + } +} + +/// Keeps only alphanumeric and punctuation ASCII characters and crops the string if too long. +fn create_control_element_name_lossy(text: &str) -> Result { + let ascii_string: AsciiString = text + .chars() + .filter_map(|c| c.to_ascii_char().ok()) + .filter(|ch| ch.is_ascii_alphanumeric() || ch.is_ascii_punctuation()) + .collect(); + if ascii_string.is_empty() { + return Err("empty virtual control element name"); + } + Ok(SmallAsciiString::from_ascii_str_cropping(&ascii_string)) +} + +impl Default for VirtualControlElementId { + fn default() -> Self { + Self::Indexed(0) + } +} + +impl Display for VirtualControlElement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} {}", self.character(), self.id()) + } +} + +impl Display for VirtualControlElementId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use VirtualControlElementId::*; + match self { + Indexed(index) => write!(f, "{}", index + 1), + Named(name) => name.fmt(f), + } + } +} + +pub mod control_element_domains { + pub mod daw { + pub const PREDEFINED_VIRTUAL_MULTI_NAMES: &[&str] = &[ + "main/fader", + "ch1/fader", + "ch2/fader", + "ch3/fader", + "ch4/fader", + "ch5/fader", + "ch6/fader", + "ch7/fader", + "ch8/fader", + "ch1/v-pot", + "ch2/v-pot", + "ch3/v-pot", + "ch4/v-pot", + "ch5/v-pot", + "ch6/v-pot", + "ch7/v-pot", + "ch8/v-pot", + "ch1/v-pot/boost-cut", + "ch2/v-pot/boost-cut", + "ch3/v-pot/boost-cut", + "ch4/v-pot/boost-cut", + "ch5/v-pot/boost-cut", + "ch6/v-pot/boost-cut", + "ch7/v-pot/boost-cut", + "ch8/v-pot/boost-cut", + "ch1/v-pot/single", + "ch2/v-pot/single", + "ch3/v-pot/single", + "ch4/v-pot/single", + "ch5/v-pot/single", + "ch6/v-pot/single", + "ch7/v-pot/single", + "ch8/v-pot/single", + "ch1/v-pot/spread", + "ch2/v-pot/spread", + "ch3/v-pot/spread", + "ch4/v-pot/spread", + "ch5/v-pot/spread", + "ch6/v-pot/spread", + "ch7/v-pot/spread", + "ch8/v-pot/spread", + "jog", + "lcd/assignment", + "lcd/timecode", + "ch1/lcd/line1", + "ch1/lcd/line2", + "ch2/lcd/line1", + "ch2/lcd/line2", + "ch3/lcd/line1", + "ch3/lcd/line2", + "ch4/lcd/line1", + "ch4/lcd/line2", + "ch5/lcd/line1", + "ch5/lcd/line2", + "ch6/lcd/line1", + "ch6/lcd/line2", + "ch7/lcd/line1", + "ch7/lcd/line2", + "ch8/lcd/line1", + "ch8/lcd/line2", + ]; + + pub const PREDEFINED_VIRTUAL_BUTTON_NAMES: &[&str] = &[ + "ch1/v-select", + "ch2/v-select", + "ch3/v-select", + "ch4/v-select", + "ch5/v-select", + "ch6/v-select", + "ch7/v-select", + "ch8/v-select", + "ch1/select", + "ch2/select", + "ch3/select", + "ch4/select", + "ch5/select", + "ch6/select", + "ch7/select", + "ch8/select", + "ch1/mute", + "ch2/mute", + "ch3/mute", + "ch4/mute", + "ch5/mute", + "ch6/mute", + "ch7/mute", + "ch8/mute", + "ch1/solo", + "ch2/solo", + "ch3/solo", + "ch4/solo", + "ch5/solo", + "ch6/solo", + "ch7/solo", + "ch8/solo", + "ch1/record-ready", + "ch2/record-ready", + "ch3/record-ready", + "ch4/record-ready", + "ch5/record-ready", + "ch6/record-ready", + "ch7/record-ready", + "ch8/record-ready", + "main/fader/touch", + "ch1/fader/touch", + "ch2/fader/touch", + "ch3/fader/touch", + "ch4/fader/touch", + "ch5/fader/touch", + "ch6/fader/touch", + "ch7/fader/touch", + "ch8/fader/touch", + "marker", + "read", + "write", + "rewind", + "fast-fwd", + "play", + "stop", + "record", + "cycle", + "zoom", + "scrub", + "nudge", + "drop", + "replace", + "click", + "solo", + "f1", + "f2", + "f3", + "f4", + "f5", + "f6", + "f7", + "f8", + "smpte-beats", + // Chose to make the following buttons, not multis - although ReaLearn would allow to + // convert them into multis in the virtual controller mapping. Reason: On + // Mackie consoles these are usually buttons. Exposing them as buttons has + // the benefit that we can use Realearn's button-specific features in the + // main mapping such as advanced fire modes. + "ch-left", + "ch-right", + "bank-left", + "bank-right", + "cursor-left", + "cursor-right", + "cursor-up", + "cursor-down", + ]; + } + pub mod grid { + pub const PREDEFINED_VIRTUAL_MULTI_NAMES: &[&str] = &[]; + pub const PREDEFINED_VIRTUAL_BUTTON_NAMES: &[&str] = &[ + "col1/stop", + "col2/stop", + "col3/stop", + "col4/stop", + "col5/stop", + "col6/stop", + "col7/stop", + "col8/stop", + "row1/play", + "row2/play", + "row3/play", + "row4/play", + "row5/play", + "row6/play", + "row7/play", + "row8/play", + "col1/row1/pad", + "col1/row2/pad", + "col1/row3/pad", + "col1/row4/pad", + "col1/row5/pad", + "col1/row6/pad", + "col1/row7/pad", + "col1/row8/pad", + "col2/row1/pad", + "col2/row2/pad", + "col2/row3/pad", + "col2/row4/pad", + "col2/row5/pad", + "col2/row6/pad", + "col2/row7/pad", + "col2/row8/pad", + "col3/row1/pad", + "col3/row2/pad", + "col3/row3/pad", + "col3/row4/pad", + "col3/row5/pad", + "col3/row6/pad", + "col3/row7/pad", + "col3/row8/pad", + "col4/row1/pad", + "col4/row2/pad", + "col4/row3/pad", + "col4/row4/pad", + "col4/row5/pad", + "col4/row6/pad", + "col4/row7/pad", + "col4/row8/pad", + "col5/row1/pad", + "col5/row2/pad", + "col5/row3/pad", + "col5/row4/pad", + "col5/row5/pad", + "col5/row6/pad", + "col5/row7/pad", + "col5/row8/pad", + "col6/row1/pad", + "col6/row2/pad", + "col6/row3/pad", + "col6/row4/pad", + "col6/row5/pad", + "col6/row6/pad", + "col6/row7/pad", + "col6/row8/pad", + "col7/row1/pad", + "col7/row2/pad", + "col7/row3/pad", + "col7/row4/pad", + "col7/row5/pad", + "col7/row6/pad", + "col7/row7/pad", + "col7/row8/pad", + "col8/row1/pad", + "col8/row2/pad", + "col8/row3/pad", + "col8/row4/pad", + "col8/row5/pad", + "col8/row6/pad", + "col8/row7/pad", + "col8/row8/pad", + "shift", + "click", + "undo", + "delete", + "quantize", + "duplicate", + "double", + "record", + "record-arm", + "track-select", + "mute", + "solo", + "volume", + "pan", + "sends", + "stop-clip", + "cursor-up", + "cursor-down", + "cursor-left", + "cursor-right", + "session", + "note", + "device", + "user", + // Found this one on the APC Key 25 + "stop-all-clips", + ]; + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/defaults.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/defaults.rs new file mode 100644 index 0000000..26fa60a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/defaults.rs @@ -0,0 +1,52 @@ +use helgobox_api::persistence::Interval; + +pub const MAPPING_CONTROL_ENABLED: bool = true; +pub const MAPPING_FEEDBACK_ENABLED: bool = true; +pub const MAPPING_ENABLED: bool = true; +pub const MAPPING_VISIBLE_IN_PROJECTION: bool = true; + +pub const GROUP_CONTROL_ENABLED: bool = true; +pub const GROUP_FEEDBACK_ENABLED: bool = true; + +pub const SOURCE_OSC_IS_RELATIVE: bool = false; +pub const SOURCE_MACKIE_LCD_EXTENDER_INDEX: u8 = 0; +pub const SOURCE_X_TOUCH_MACKIE_LCD_EXTENDER_INDEX: u8 = 0; + +pub const UNIT_INTERVAL: Interval = Interval(0.0, 1.0); +pub const GLUE_STEP_SIZE_INTERVAL: Interval = Interval(0.01, 0.01); +// Should be the same as GLUE_STEP_SIZE_INTERVAL because the native data structure only saves one. +pub const GLUE_STEP_FACTOR_INTERVAL: Interval = Interval(1, 1); +pub const GLUE_SOURCE_INTERVAL: Interval = UNIT_INTERVAL; +pub const GLUE_TARGET_INTERVAL: Interval = UNIT_INTERVAL; +pub const GLUE_JUMP_INTERVAL: Interval = UNIT_INTERVAL; +pub const GLUE_REVERSE: bool = false; +pub const GLUE_WRAP: bool = false; +pub const GLUE_ROUND_TARGET_VALUE: bool = false; +pub const FIRE_MODE_PRESS_DURATION_INTERVAL: Interval = Interval(0, 0); +pub const FIRE_MODE_TIMEOUT: u32 = 0; +pub const FIRE_MODE_RATE: u32 = 0; +pub const FIRE_MODE_SINGLE_PRESS_MAX_DURATION: u32 = 0; + +pub const TARGET_TRACK_MUST_BE_SELECTED: bool = false; +pub const TARGET_FX_MUST_HAVE_FOCUS: bool = false; +pub const TARGET_TRACK_SELECTED_ALLOW_MULTIPLE: bool = false; +pub const TARGET_BY_NAME_ALLOW_MULTIPLE: bool = false; +pub const TARGET_BOOKMARK_SET_TIME_SELECTION: bool = false; +pub const TARGET_BOOKMARK_SET_LOOP_POINTS: bool = false; +pub const TARGET_POLL_FOR_FEEDBACK: bool = true; +pub const TARGET_RETRIGGER: bool = false; +pub const TARGET_REAL_TIME: bool = false; +pub const TARGET_TRACK_SELECTION_SCROLL_ARRANGE_VIEW: bool = false; +pub const TARGET_TRACK_SELECTION_SCROLL_MIXER: bool = false; +pub const TARGET_SEEK_USE_TIME_SELECTION: bool = false; +pub const TARGET_SEEK_USE_LOOP_POINTS: bool = false; +pub const TARGET_SEEK_USE_REGIONS: bool = false; +pub const TARGET_SEEK_USE_PROJECT: bool = true; +pub const TARGET_SEEK_MOVE_VIEW: bool = true; +pub const TARGET_SEEK_SEEK_PLAY: bool = true; +pub const TARGET_LOAD_MAPPING_SNAPSHOT_ACTIVE_MAPPINGS_ONLY: bool = false; +pub const TARGET_SAVE_MAPPING_SNAPSHOT_ACTIVE_MAPPINGS_ONLY: bool = false; +pub const TARGET_STOP_COLUMN_IF_SLOT_EMPTY: bool = false; +pub const TARGET_USE_SELECTION_GANGING: bool = false; +pub const TARGET_USE_TRACK_GROUPING: bool = false; +pub const TARGET_LEARNABLE: bool = true; diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/compartment.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/compartment.rs new file mode 100644 index 0000000..1eff742 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/compartment.rs @@ -0,0 +1,43 @@ +use crate::infrastructure::api::convert::from_data::{ + convert_group, convert_mapping, convert_parameter, ConversionStyle, +}; +use crate::infrastructure::api::convert::{convert_multiple, ConversionResult}; +use crate::infrastructure::data::CompartmentModelData; +use helgobox_api::persistence; + +pub fn convert_compartment( + data: CompartmentModelData, + style: ConversionStyle, +) -> ConversionResult { + let compartment = persistence::Compartment { + default_group: { + let v = if let Some(group_data) = data.default_group { + Some(convert_group(group_data, style)?) + } else { + None + }; + style.optional_value(v) + }, + parameters: { + let v: Result, _> = data + .parameters + .into_iter() + .map(|(key, value)| convert_parameter(key, value, style)) + .collect(); + style.required_value(v?) + }, + groups: { + let v = convert_multiple(data.groups, |g| convert_group(g, style))?; + style.required_value(v) + }, + mappings: { + let v = convert_multiple(data.mappings, |m| convert_mapping(m, style))?; + style.required_value(v) + }, + common_lua: style.required_value(data.common_lua), + custom_data: style.required_value(data.custom_data.into_iter().collect()), + notes: style.required_value(data.notes), + unknown_props: Default::default(), + }; + Ok(compartment) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/glue.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/glue.rs new file mode 100644 index 0000000..6c681b8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/glue.rs @@ -0,0 +1,234 @@ +use crate::infrastructure::api::convert::from_data::ConversionStyle; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::ModeModelData; +use helgoboss_learn::{ + AbsoluteMode, ButtonUsage, DiscreteIncrement, EncoderUsage, FeedbackType, FireMode, + GroupInteraction, OutOfRangeBehavior, TakeoverMode, UnitValue, VirtualColor, +}; +use helgobox_api::persistence; +use helgobox_api::persistence::{DynamicFeedback, NumericFeedback, PropColor, TextFeedback}; + +pub fn convert_glue( + data: ModeModelData, + style: ConversionStyle, +) -> ConversionResult { + let glue = persistence::Glue { + absolute_mode: convert_absolute_mode(data.r#type, style), + source_interval: style.required_value_with_default( + convert_unit_interval(data.min_source_value, data.max_source_value), + defaults::GLUE_SOURCE_INTERVAL, + ), + target_interval: style.required_value_with_default( + convert_unit_interval(data.min_target_value, data.max_target_value), + defaults::GLUE_TARGET_INTERVAL, + ), + reverse: style.required_value_with_default(data.reverse_is_enabled, defaults::GLUE_REVERSE), + wrap: style.required_value_with_default(data.rotate_is_enabled, defaults::GLUE_WRAP), + jump_interval: style.required_value_with_default( + convert_unit_interval(data.min_target_jump, data.max_target_jump), + defaults::GLUE_JUMP_INTERVAL, + ), + step_size_interval: { + style.required_value_with_default( + persistence::Interval( + data.min_step_size.get().abs(), + data.max_step_size.get().abs(), + ), + defaults::GLUE_STEP_SIZE_INTERVAL, + ) + }, + step_factor_interval: { + let interval = persistence::Interval( + data.min_step_factor + .unwrap_or(DiscreteIncrement::POSITIVE_MIN) + .get(), + data.max_step_factor + .unwrap_or(DiscreteIncrement::POSITIVE_MIN) + .get(), + ); + style.required_value_with_default(interval, defaults::GLUE_STEP_FACTOR_INTERVAL) + }, + out_of_range_behavior: { + use persistence::OutOfRangeBehavior as T; + use OutOfRangeBehavior::*; + let v = match data.out_of_range_behavior { + MinOrMax => T::MinOrMax, + Min => T::Min, + Ignore => T::Ignore, + }; + style.required_value(v) + }, + takeover_mode: { + use persistence::TakeoverMode as T; + use TakeoverMode::*; + let v = match data.takeover_mode { + Off => T::Off, + PickupTolerant => T::PickUpTolerant, + Pickup => T::PickUp, + LongTimeNoSee => T::LongTimeNoSee, + Parallel => T::Parallel, + CatchUp => T::CatchUp, + }; + style.required_value(v) + }, + round_target_value: style.required_value_with_default( + data.round_target_value, + defaults::GLUE_ROUND_TARGET_VALUE, + ), + control_transformation: style.required_value(data.eel_control_transformation), + button_filter: { + use persistence::ButtonFilter as T; + use ButtonUsage::*; + match data.button_usage { + Both => None, + PressOnly => Some(T::PressOnly), + ReleaseOnly => Some(T::ReleaseOnly), + } + }, + encoder_filter: { + use persistence::EncoderFilter as T; + use EncoderUsage::*; + match data.encoder_usage { + Both => None, + IncrementOnly => Some(T::IncrementOnly), + DecrementOnly => Some(T::DecrementOnly), + } + }, + relative_mode: { + let v = if data.make_absolute_enabled { + persistence::RelativeMode::MakeAbsolute + } else { + persistence::RelativeMode::Normal + }; + style.required_value(v) + }, + interaction: { + use persistence::Interaction as T; + use GroupInteraction::*; + match data.group_interaction { + None => Option::None, + SameControl => Some(T::SameControl), + SameTargetValue => Some(T::SameTargetValue), + InverseControl => Some(T::InverseControl), + InverseTargetValue => Some(T::InverseTargetValue), + InverseTargetValueOnOnly => Some(T::InverseTargetValueOnOnly), + InverseTargetValueOffOnly => Some(T::InverseTargetValueOffOnly), + } + }, + target_value_sequence: style.required_value(data.target_value_sequence.to_string()), + feedback: { + use persistence::Feedback as T; + use FeedbackType::*; + let v = match data.feedback_type { + Numeric => T::Numeric(NumericFeedback { + commons: convert_feedback_commons( + data.feedback_color, + data.feedback_background_color, + )?, + transformation: style.required_value(data.eel_feedback_transformation), + }), + Text => T::Text(TextFeedback { + commons: convert_feedback_commons( + data.feedback_color, + data.feedback_background_color, + )?, + text_expression: style.required_value(data.eel_feedback_transformation), + }), + Dynamic => T::Dynamic(DynamicFeedback { + commons: convert_feedback_commons( + data.feedback_color, + data.feedback_background_color, + )?, + script: style.required_value(data.eel_feedback_transformation), + }), + }; + style.required_value(v) + }, + fire_mode: { + use persistence::FireMode as T; + use FireMode::*; + let v = match data.fire_mode { + Normal => T::Normal(persistence::NormalFireMode { + press_duration_interval: { + let interval = persistence::Interval( + data.min_press_millis as _, + data.max_press_millis as _, + ); + style.required_value_with_default( + interval, + defaults::FIRE_MODE_PRESS_DURATION_INTERVAL, + ) + }, + }), + AfterTimeout => T::AfterTimeout(persistence::AfterTimeoutFireMode { + timeout: style.required_value_with_default( + data.min_press_millis as _, + defaults::FIRE_MODE_TIMEOUT, + ), + }), + AfterTimeoutKeepFiring => { + T::AfterTimeoutKeepFiring(persistence::AfterTimeoutKeepFiringFireMode { + timeout: style.required_value_with_default( + data.min_press_millis as _, + defaults::FIRE_MODE_TIMEOUT, + ), + rate: style.required_value_with_default( + data.turbo_rate as _, + defaults::FIRE_MODE_RATE, + ), + }) + } + OnSinglePress => T::OnSinglePress(persistence::OnSinglePressFireMode { + max_duration: style.required_value_with_default( + data.max_press_millis as _, + defaults::FIRE_MODE_SINGLE_PRESS_MAX_DURATION, + ), + }), + OnDoublePress => T::OnDoublePress, + }; + style.required_value(v) + }, + feedback_value_table: data.feedback_value_table, + }; + Ok(glue) +} + +fn convert_absolute_mode( + v: AbsoluteMode, + style: ConversionStyle, +) -> Option { + use persistence::AbsoluteMode as T; + use AbsoluteMode::*; + let mode = match v { + Normal => T::Normal, + IncrementalButton => T::IncrementalButton, + ToggleButton => T::ToggleButton, + MakeRelative => T::MakeRelative, + PerformanceControl => T::PerformanceControl, + }; + style.required_value(mode) +} + +fn convert_unit_interval(min: UnitValue, max: UnitValue) -> persistence::Interval { + persistence::Interval(min.get(), max.get()) +} + +fn convert_virtual_color(v: VirtualColor) -> persistence::VirtualColor { + use persistence::VirtualColor as T; + use VirtualColor::*; + match v { + Rgb(c) => T::Rgb(persistence::RgbColor(c.r(), c.g(), c.b())), + Prop { prop } => T::Prop(PropColor { prop }), + } +} + +fn convert_feedback_commons( + color: Option, + background_color: Option, +) -> ConversionResult { + let commons = persistence::FeedbackCommons { + color: color.map(convert_virtual_color), + background_color: background_color.map(convert_virtual_color), + }; + Ok(commons) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/group.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/group.rs new file mode 100644 index 0000000..ee099ec --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/group.rs @@ -0,0 +1,33 @@ +use crate::infrastructure::api::convert::from_data::{ + convert_activation_condition, convert_tags, ConversionStyle, +}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::GroupModelData; +use helgobox_api::persistence; + +pub fn convert_group( + data: GroupModelData, + style: ConversionStyle, +) -> ConversionResult { + let group = persistence::Group { + id: { + if data.id.is_empty() { + None + } else { + Some(data.id.into()) + } + }, + name: style.required_value(data.name), + tags: convert_tags(&data.tags, style), + control_enabled: style.required_value_with_default( + data.enabled_data.control_is_enabled, + defaults::GROUP_CONTROL_ENABLED, + ), + feedback_enabled: style.required_value_with_default( + data.enabled_data.feedback_is_enabled, + defaults::GROUP_FEEDBACK_ENABLED, + ), + activation_condition: convert_activation_condition(data.activation_condition_data), + }; + Ok(group) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/mapping.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/mapping.rs new file mode 100644 index 0000000..6f29b4c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/mapping.rs @@ -0,0 +1,149 @@ +use crate::application::{ + LifecycleMidiMessageModel, LifecycleModel, MappingExtensionModel, RawMidiMessage, +}; +use crate::infrastructure::api::convert::from_data::{ + convert_activation_condition, convert_glue, convert_source, convert_tags, convert_target, + ConversionStyle, NewSourceProps, +}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::MappingModelData; +use helgobox_api::persistence; +use helgobox_api::persistence::LifecycleHook; + +pub fn convert_mapping( + data: MappingModelData, + style: ConversionStyle, +) -> ConversionResult { + let advanced = convert_advanced(data.advanced, style)?; + let mapping = persistence::Mapping { + id: style.optional_value(data.id.map(|id| id.into())), + name: style.required_value(data.name), + tags: convert_tags(&data.tags, style), + group: style.required_value(data.group_id.into()), + visible_in_projection: style.required_value_with_default( + data.visible_in_projection, + defaults::MAPPING_VISIBLE_IN_PROJECTION, + ), + enabled: style.required_value_with_default(data.is_enabled, defaults::MAPPING_ENABLED), + control_enabled: style.required_value_with_default( + data.enabled_data.control_is_enabled, + defaults::MAPPING_CONTROL_ENABLED, + ), + feedback_enabled: style.required_value_with_default( + data.enabled_data.feedback_is_enabled, + defaults::MAPPING_FEEDBACK_ENABLED, + ), + activation_condition: convert_activation_condition(data.activation_condition_data), + on_activate: style.optional_value(advanced.extension_desc.on_activate), + on_deactivate: style.optional_value(advanced.extension_desc.on_deactivate), + source: { + let new_source_props = NewSourceProps { + prevent_echo_feedback: data.prevent_echo_feedback, + send_feedback_after_control: data.send_feedback_after_control, + }; + style.required_value(convert_source(data.source, new_source_props, style)?) + }, + glue: style.required_value(convert_glue(data.mode, style)?), + target: style.required_value(convert_target(data.target, style)?), + success_audio_feedback: data.success_audio_feedback, + unprocessed: style.optional_value(advanced.unprocessed), + }; + Ok(mapping) +} + +#[derive(Default)] +struct AdvancedDesc { + extension_desc: ExtensionDesc, + unprocessed: Option>, +} + +#[derive(Default)] +struct ExtensionDesc { + on_activate: Option, + on_deactivate: Option, +} + +fn convert_advanced( + advanced: Option, + style: ConversionStyle, +) -> ConversionResult { + let mut advanced = match advanced { + None => return Ok(Default::default()), + Some(a) => a, + }; + // Move known properties into own YAML mapping + let mut known_yaml = serde_yaml::mapping::Mapping::new(); + let on_activate_key = serde_yaml::Value::String("on_activate".to_string()); + let on_deactivate_key = serde_yaml::Value::String("on_deactivate".to_string()); + if let Some(on_activate) = advanced.remove(&on_activate_key) { + known_yaml.insert(on_activate_key, on_activate); + } + if let Some(on_deactivate) = advanced.remove(&on_deactivate_key) { + known_yaml.insert(on_deactivate_key, on_deactivate); + } + let desc = AdvancedDesc { + extension_desc: { + let extension_model = serde_yaml::from_value(serde_yaml::Value::Mapping(known_yaml))?; + convert_extension_model(extension_model, style)? + }, + // Sort out unknown properties as "unprocessed" + unprocessed: { + let json_value = serde_json::to_value(advanced)?; + if let serde_json::Value::Object(map) = json_value { + Some(map) + } else { + panic!("impossible that a YAML mapping is not serialized as JSON object") + } + }, + }; + Ok(desc) +} + +fn convert_extension_model( + extension_model: MappingExtensionModel, + style: ConversionStyle, +) -> ConversionResult { + let desc = ExtensionDesc { + on_activate: convert_lifecycle_model(extension_model.on_activate, style)?, + on_deactivate: convert_lifecycle_model(extension_model.on_deactivate, style)?, + }; + Ok(desc) +} + +fn convert_lifecycle_model( + lifecycle_model: LifecycleModel, + style: ConversionStyle, +) -> ConversionResult> { + let hook = LifecycleHook { + send_midi_feedback: { + let actions: Result, _> = lifecycle_model + .send_midi_feedback + .into_iter() + .map(convert_lifecycle_midi_message_model) + .collect(); + style.required_value(actions?) + }, + }; + Ok(style.required_value(hook)) +} + +fn convert_lifecycle_midi_message_model( + model: LifecycleMidiMessageModel, +) -> ConversionResult { + let action = match model { + LifecycleMidiMessageModel::Raw(msg) => { + let message = convert_raw_midi_msg(msg)?; + persistence::SendMidiFeedbackAction::Raw { message } + } + }; + Ok(action) +} + +fn convert_raw_midi_msg(msg: RawMidiMessage) -> ConversionResult { + use persistence::RawMidiMessage as T; + let v = match msg { + RawMidiMessage::HexString(s) => T::HexString(s.to_string()), + RawMidiMessage::ByteArray(a) => T::ByteArray(a.0), + }; + Ok(v) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/mod.rs new file mode 100644 index 0000000..03df524 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/mod.rs @@ -0,0 +1,197 @@ +mod source; +pub use source::*; +mod glue; +pub use glue::*; +mod group; +pub use group::*; +mod parameter; +pub use parameter::*; +mod mapping; +pub use mapping::*; +mod compartment; +pub use compartment::*; +mod target; + +use crate::application::ActivationType; +use crate::domain::{Keystroke, Tag}; +use crate::infrastructure::data::{ + ActivationConditionData, OscValueRange, VirtualControlElementIdData, +}; +use helgoboss_learn::OscTypeTag; +use helgobox_api::persistence; +use helgobox_api::persistence::ParamRef; +pub use target::*; + +fn convert_control_element_id( + v: VirtualControlElementIdData, +) -> persistence::VirtualControlElementId { + use persistence::VirtualControlElementId as T; + use VirtualControlElementIdData::*; + match v { + Indexed(i) => T::Indexed(i), + Named(n) => T::Named(n), + } +} + +fn convert_keystroke(v: Keystroke) -> persistence::Keystroke { + persistence::Keystroke { + modifiers: v.modifiers().bits(), + key: v.key_code().get(), + } +} + +fn convert_osc_argument( + arg_index: Option, + arg_type: OscTypeTag, + value_range: OscValueRange, + style: ConversionStyle, +) -> Option { + let arg_index = arg_index?; + let arg = persistence::OscArgument { + index: Some(arg_index), + arg_kind: style.required_value(convert_osc_arg_kind(arg_type)), + value_range: style.required_value(convert_osc_value_range(value_range)), + }; + style.required_value(arg) +} + +fn convert_osc_value_range(v: OscValueRange) -> persistence::Interval { + let interval = v.to_interval(); + persistence::Interval(interval.min_val(), interval.max_val()) +} + +fn convert_osc_arg_kind(v: OscTypeTag) -> persistence::OscArgKind { + use persistence::OscArgKind as T; + use OscTypeTag::*; + match v { + Float => T::Float, + Double => T::Double, + Bool => T::Bool, + Nil => T::Nil, + Inf => T::Inf, + Int => T::Int, + String => T::String, + Blob => T::Blob, + Time => T::Time, + Long => T::Long, + Char => T::Char, + Color => T::Color, + Midi => T::Midi, + Array => T::Array, + } +} + +fn convert_tags(tags: &[Tag], style: ConversionStyle) -> Option> { + let tags = tags.iter().map(|t| t.to_string()).collect(); + style.required_value(tags) +} + +#[derive(Copy, Clone)] +pub enum ConversionStyle { + Minimal, + IncludeDefaultValues, +} + +impl ConversionStyle { + pub fn required_value(&self, value: T) -> Option { + self.required_value_with_default(value, T::default()) + } + + pub fn required_value_with_default( + &self, + value: T, + default_value: T, + ) -> Option { + use ConversionStyle::*; + match self { + Minimal => { + if value == default_value { + None + } else { + Some(value) + } + } + IncludeDefaultValues => Some(value), + } + } + + pub fn optional_value(&self, value: Option) -> Option { + self.optional_value_with_default(value, T::default()) + } + + pub fn optional_value_with_default( + &self, + value: Option, + default_value: T, + ) -> Option { + use ConversionStyle::*; + match self { + Minimal => value.filter(|v| v != &default_value), + IncludeDefaultValues => { + if value.is_some() { + value + } else { + Some(default_value) + } + } + } + } +} + +fn convert_activation_condition( + condition_data: ActivationConditionData, +) -> Option { + use persistence::ActivationCondition as T; + use ActivationType::*; + match condition_data.activation_type { + Always => None, + Modifiers => { + let condition = persistence::ModifierActivationCondition { + modifiers: { + let mod_conditions = [ + condition_data.modifier_condition_1, + condition_data.modifier_condition_2, + ]; + let mod_states: Vec<_> = mod_conditions + .into_iter() + .filter_map(|c| { + let state = persistence::ModifierState { + parameter: ParamRef::Index(c.param_index?.get()), + on: c.is_on, + }; + Some(state) + }) + .collect(); + Some(mod_states) + }, + }; + Some(T::Modifier(condition)) + } + Bank => { + let condition = persistence::BankActivationCondition { + parameter: ParamRef::Index(condition_data.program_condition.param_index.get()), + bank_index: condition_data.program_condition.bank_index, + }; + Some(T::Bank(condition)) + } + Eel => { + let condition = persistence::EelActivationCondition { + condition: condition_data.eel_condition, + }; + Some(T::Eel(condition)) + } + Expression => { + let condition = persistence::ExpressionActivationCondition { + condition: condition_data.eel_condition, + }; + Some(T::Expression(condition)) + } + TargetValue => { + let condition = persistence::TargetValueActivationCondition { + mapping: condition_data.mapping_key.map(|key| key.into()), + condition: condition_data.eel_condition, + }; + Some(T::TargetValue(condition)) + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/parameter.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/parameter.rs new file mode 100644 index 0000000..ddb3e33 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/parameter.rs @@ -0,0 +1,19 @@ +use crate::domain::ParamSetting; +use crate::infrastructure::api::convert::from_data::ConversionStyle; +use crate::infrastructure::api::convert::ConversionResult; +use helgobox_api::persistence; + +pub fn convert_parameter( + index: String, + data: ParamSetting, + style: ConversionStyle, +) -> ConversionResult { + let p = persistence::Parameter { + index: index.parse()?, + id: data.key, + name: Some(data.name), + value_count: data.value_count, + value_labels: style.required_value(data.value_labels), + }; + Ok(p) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/source.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/source.rs new file mode 100644 index 0000000..c248236 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/source.rs @@ -0,0 +1,339 @@ +use crate::application::{MidiSourceType, ReaperSourceType, SourceCategory}; +use crate::infrastructure::api::convert::from_data::{ + convert_control_element_id, convert_keystroke, convert_osc_argument, ConversionStyle, +}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::SourceModelData; +use helgoboss_learn::{ + DisplayType, MackieSevenSegmentDisplayScope, MidiClockTransportMessage, SourceCharacter, +}; +use helgoboss_midi::{Channel, U14}; +use helgobox_api::persistence; +use std::convert::TryInto; + +pub struct NewSourceProps { + pub prevent_echo_feedback: bool, + pub send_feedback_after_control: bool, +} + +pub fn convert_source( + data: SourceModelData, + new_source_props: NewSourceProps, + style: ConversionStyle, +) -> ConversionResult { + let feedback_behavior = { + use persistence::FeedbackBehavior as T; + let v = if new_source_props.prevent_echo_feedback { + // Took precedence if both checkboxes were ticked (was possible in ReaLearn < 2.10.0). + T::PreventEchoFeedback + } else if new_source_props.send_feedback_after_control { + T::SendFeedbackAfterControl + } else { + T::Normal + }; + style.required_value(v) + }; + use SourceCategory::*; + let source = match data.category { + Never => persistence::Source::None, + Midi => { + use MidiSourceType::*; + match data.r#type { + ControlChangeValue => { + let s = persistence::MidiControlChangeValueSource { + feedback_behavior, + channel: convert_channel(data.channel), + controller_number: convert_controller_number(data.number), + character: convert_character(data.character, style), + fourteen_bit: data.is_14_bit, + }; + persistence::Source::MidiControlChangeValue(s) + } + NoteVelocity => { + let s = persistence::MidiNoteVelocitySource { + feedback_behavior, + channel: convert_channel(data.channel), + key_number: convert_key_number(data.number), + }; + persistence::Source::MidiNoteVelocity(s) + } + NoteKeyNumber => { + let s = persistence::MidiNoteKeyNumberSource { + feedback_behavior, + channel: convert_channel(data.channel), + }; + persistence::Source::MidiNoteKeyNumber(s) + } + PitchBendChangeValue => { + let s = persistence::MidiPitchBendChangeValueSource { + feedback_behavior, + channel: convert_channel(data.channel), + }; + persistence::Source::MidiPitchBendChangeValue(s) + } + ChannelPressureAmount => { + let s = persistence::MidiChannelPressureAmountSource { + feedback_behavior, + channel: convert_channel(data.channel), + }; + persistence::Source::MidiChannelPressureAmount(s) + } + ProgramChangeNumber => { + let s = persistence::MidiProgramChangeNumberSource { + feedback_behavior, + channel: convert_channel(data.channel), + }; + persistence::Source::MidiProgramChangeNumber(s) + } + SpecificProgramChange => { + let s = persistence::MidiSpecificProgramChangeSource { + feedback_behavior, + channel: convert_channel(data.channel), + program_number: convert_program_number(data.number), + }; + persistence::Source::MidiSpecificProgramChange(s) + } + ParameterNumberValue => { + let s = persistence::MidiParameterNumberValueSource { + feedback_behavior, + channel: convert_channel(data.channel), + number: convert_parameter_number(data.number), + fourteen_bit: data.is_14_bit, + registered: data.is_registered, + character: convert_character(data.character, style), + }; + persistence::Source::MidiParameterNumberValue(s) + } + PolyphonicKeyPressureAmount => { + let s = persistence::MidiPolyphonicKeyPressureAmountSource { + feedback_behavior, + channel: convert_channel(data.channel), + key_number: convert_key_number(data.number), + }; + persistence::Source::MidiPolyphonicKeyPressureAmount(s) + } + ClockTempo => persistence::Source::MidiClockTempo, + ClockTransport => { + let s = persistence::MidiClockTransportSource { + message: convert_transport_msg(data.message), + }; + persistence::Source::MidiClockTransport(s) + } + Raw => { + let s = persistence::MidiRawSource { + feedback_behavior, + pattern: style.required_value(data.raw_midi_pattern), + character: convert_character(data.character, style), + }; + persistence::Source::MidiRaw(s) + } + Script => { + let s = persistence::MidiScriptSource { + script_kind: style.required_value(data.midi_script_kind), + script: style.required_value(data.midi_script), + }; + persistence::Source::MidiScript(s) + } + Display => { + use DisplayType::*; + match data.display_type { + MackieLcd => { + let s = persistence::MackieLcdSource { + extender_index: style.required_value_with_default( + 0, + defaults::SOURCE_MACKIE_LCD_EXTENDER_INDEX, + ), + channel: data.display_id, + line: data.line, + }; + persistence::Source::MackieLcd(s) + } + MackieXtLcd => { + let s = persistence::MackieLcdSource { + extender_index: style.required_value_with_default( + 1, + defaults::SOURCE_MACKIE_LCD_EXTENDER_INDEX, + ), + channel: data.display_id, + line: data.line, + }; + persistence::Source::MackieLcd(s) + } + XTouchMackieLcd => { + let s = persistence::XTouchMackieLcdSource { + extender_index: style.required_value_with_default( + 0, + defaults::SOURCE_MACKIE_LCD_EXTENDER_INDEX, + ), + channel: data.display_id, + line: data.line, + }; + persistence::Source::XTouchMackieLcd(s) + } + XTouchMackieXtLcd => { + let s = persistence::XTouchMackieLcdSource { + extender_index: style.required_value_with_default( + 1, + defaults::SOURCE_MACKIE_LCD_EXTENDER_INDEX, + ), + channel: data.display_id, + line: data.line, + }; + persistence::Source::XTouchMackieLcd(s) + } + MackieSevenSegmentDisplay => { + let s = persistence::MackieSevenSegmentDisplaySource { + scope: data.display_id.and_then(|id| { + convert_mackie_seven_segment_display_scope( + (id as usize).try_into().ok()?, + ) + }), + }; + persistence::Source::MackieSevenSegmentDisplay(s) + } + SlKeyboardDisplay => { + let s = persistence::SlKeyboardDisplaySource { + section: data.display_id, + line: data.line, + }; + persistence::Source::SlKeyboardDisplay(s) + } + SiniConE24 => { + let s = persistence::SiniConE24DisplaySource { + cell_index: data.display_id, + item_index: data.line, + }; + persistence::Source::SiniConE24Display(s) + } + LaunchpadProScrollingText => { + persistence::Source::LaunchpadProScrollingTextDisplay + } + } + } + } + } + Osc => { + let s = persistence::OscSource { + feedback_behavior, + address: style.required_value(data.osc_address_pattern), + argument: convert_osc_argument( + data.osc_arg_index, + data.osc_arg_type, + data.osc_arg_value_range, + style, + ), + relative: style.required_value_with_default( + data.osc_arg_is_relative, + defaults::SOURCE_OSC_IS_RELATIVE, + ), + feedback_arguments: style.required_value(data.osc_feedback_args), + }; + persistence::Source::Osc(s) + } + Reaper => { + use ReaperSourceType::*; + match data.reaper_source_type { + MidiDeviceChanges => persistence::Source::MidiDeviceChanges, + RealearnUnitStart => persistence::Source::RealearnInstanceStart, + RealearnCompartmentLoaded => persistence::Source::RealearnCompartmentLoaded, + Timer => persistence::Source::Timer(persistence::TimerSource { + duration: data.timer_millis, + }), + RealearnParameter => { + persistence::Source::RealearnParameter(persistence::RealearnParameterSource { + parameter_index: data.parameter_index.get(), + }) + } + Speech => persistence::Source::Speech, + } + } + Virtual => { + let s = persistence::VirtualSource { + id: convert_control_element_id(data.control_element_index), + character: style.required_value(data.control_element_type), + }; + persistence::Source::Virtual(s) + } + Keyboard => { + let s = persistence::KeySource { + keystroke: data.keystroke.map(convert_keystroke), + }; + persistence::Source::Key(s) + } + StreamDeck => { + let s = persistence::StreamDeckSource { + button_index: data.button_index, + button_design: data.button_design, + }; + persistence::Source::StreamDeck(s) + } + }; + Ok(source) +} + +fn convert_channel(v: Option) -> Option { + Some(v?.get()) +} + +fn convert_controller_number(v: Option) -> Option { + Some(v?.get() as _) +} + +fn convert_parameter_number(v: Option) -> Option { + Some(v?.get() as _) +} + +fn convert_key_number(v: Option) -> Option { + Some(v?.get() as _) +} + +fn convert_program_number(v: Option) -> Option { + Some(v?.get() as _) +} + +fn convert_character( + v: SourceCharacter, + style: ConversionStyle, +) -> Option { + use persistence::SourceCharacter as T; + use SourceCharacter::*; + let res = match v { + RangeElement => T::Range, + MomentaryButton => T::Button, + Encoder1 => T::Relative1, + Encoder2 => T::Relative2, + Encoder3 => T::Relative3, + ToggleButton => T::StatefulButton, + }; + style.required_value(res) +} + +fn convert_transport_msg( + v: MidiClockTransportMessage, +) -> Option { + use persistence::MidiClockTransportMessage as T; + use MidiClockTransportMessage::*; + let res = match v { + Start => T::Start, + Continue => T::Continue, + Stop => T::Stop, + }; + Some(res) +} + +fn convert_mackie_seven_segment_display_scope( + v: MackieSevenSegmentDisplayScope, +) -> Option { + use persistence::MackieSevenSegmentDisplayScope as T; + use MackieSevenSegmentDisplayScope::*; + let res = match v { + All => T::All, + Assignment => T::Assignment, + Tc => T::Tc, + TcHoursBars => T::TcHoursBars, + TcMinutesBeats => T::TcMinutesBeats, + TcSecondsSub => T::TcSecondsSub, + TcFramesTicks => T::TcFramesTicks, + }; + Some(res) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/target.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/target.rs new file mode 100644 index 0000000..4239ace --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/from_data/target.rs @@ -0,0 +1,1113 @@ +use crate::application::{ + AutomationModeOverrideType, BookmarkAnchorType, MappingModificationKind, + RealearnAutomationMode, RealearnTrackArea, TargetCategory, TargetUnit, TrackRouteSelectorType, + VirtualFxParameterType, VirtualFxType, VirtualTrackType, +}; +use crate::domain::{ + ActionInvocationType, AnyOnParameter, Exclusivity, FeedbackResolution, FxDisplayType, + ReaperTargetType, SendMidiDestinationType, SoloBehavior, TouchedRouteParameterType, + TouchedTrackParameterType, TrackExclusivity, TrackRouteType, TransportAction, +}; +use crate::infrastructure::api::convert::from_data::{ + convert_control_element_id, convert_osc_argument, convert_tags, ConversionStyle, +}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::{ + deserialize_fx, deserialize_fx_parameter, deserialize_track, deserialize_track_route, + MigrationDescriptor, TargetModelData, TrackData, TrackDeserializationInput, +}; +use base::hash_util::convert_into_other_hash_set; +use helgobox_api::persistence; +use helgobox_api::persistence::{ + AllTrackFxOnOffStateTarget, AnyOnTarget, AutomationModeOverrideTarget, + BackwardCompatibleMappingSnapshotDescForTake, BookmarkDescriptor, BookmarkRef, + BrowseFxChainTarget, BrowseFxPresetsTarget, BrowseGroupMappingsTarget, + BrowsePotFilterItemsTarget, BrowsePotPresetsTarget, BrowseTracksTarget, + CompartmentParameterDescriptor, CompartmentParameterValueTarget, DummyTarget, + EnableInstancesTarget, EnableMappingsTarget, EnableUnitsTarget, FxOnOffStateTarget, + FxOnlineOfflineStateTarget, FxParameterAutomationTouchStateTarget, FxParameterValueTarget, + FxToolTarget, FxVisibilityTarget, GoToBookmarkTarget, InputDeviceMidiDestination, + LastTouchedTarget, LearnTargetMappingModification, LoadFxSnapshotTarget, + LoadMappingSnapshotTarget, LoadPotPresetTarget, MappingModification, ModifyMappingTarget, + MouseTarget, PlayRateTarget, PreviewPotPresetTarget, ReaperActionTarget, + RouteAutomationModeTarget, RouteMonoStateTarget, RouteMuteStateTarget, RoutePanTarget, + RoutePhaseTarget, RouteTouchStateTarget, RouteVolumeTarget, SeekTarget, SendMidiTarget, + SendOscTarget, SetTargetToLastTouchedMappingModification, StreamDeckBrightnessTarget, + TakeMappingSnapshotTarget, TempoTarget, TrackArmStateTarget, TrackAutomationModeTarget, + TrackAutomationTouchStateTarget, TrackMonitoringModeTarget, TrackMuteStateTarget, + TrackPanTarget, TrackParentSendStateTarget, TrackPeakTarget, TrackPhaseTarget, + TrackSelectionStateTarget, TrackSoloStateTarget, TrackToolTarget, TrackVisibilityTarget, + TrackVolumeTarget, TrackWidthTarget, TransportActionTarget, +}; + +pub fn convert_target( + data: TargetModelData, + style: ConversionStyle, +) -> ConversionResult { + use TargetCategory::*; + match data.category { + Reaper => convert_real_target(data, style), + Virtual => Ok(convert_virtual_target(data, style)), + } +} + +fn convert_real_target( + data: TargetModelData, + style: ConversionStyle, +) -> ConversionResult { + use persistence::Target as T; + use ReaperTargetType::*; + let commons = convert_commons(data.unit, style)?; + let target = match data.r#type { + Mouse => T::Mouse(MouseTarget { + commons, + action: data.mouse_action, + }), + LastTouched => T::LastTouched(LastTouchedTarget { + commons, + included_targets: data.included_targets.map(|s| s.into_iter().collect()), + touch_cause: style.required_value(data.touch_cause), + }), + AutomationModeOverride => { + let t = AutomationModeOverrideTarget { + commons, + override_value: convert_automation_mode_override( + data.automation_mode_override_type, + data.track_automation_mode, + ), + }; + T::AutomationModeOverride(t) + } + Action => T::ReaperAction(ReaperActionTarget { + commons, + scope: style.required_value(data.action_scope), + command: { + if let Some(n) = data.command_name { + let v = match n.parse::() { + Ok(id) => persistence::ReaperCommand::Id(id), + Err(_) => persistence::ReaperCommand::Name(n), + }; + Some(v) + } else { + None + } + }, + invocation: { + use persistence::ActionInvocationKind as T; + use ActionInvocationType::*; + let v = match data.invocation_type { + Trigger => T::Trigger, + Absolute14Bit => T::Absolute14Bit, + Absolute7Bit => T::Absolute7Bit, + Relative => T::Relative, + }; + style.required_value(v) + }, + track: if data.with_track { + convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ) + } else { + None + }, + }), + Transport => T::TransportAction(TransportActionTarget { + commons, + action: convert_transport_action(data.transport_action), + }), + AnyOn => T::AnyOn(AnyOnTarget { + commons, + parameter: convert_any_on_parameter(data.any_on_parameter), + }), + GoToBookmark => T::GoToBookmark(GoToBookmarkTarget { + commons, + bookmark: { + let bookmark_ref = match data.bookmark_data.anchor { + BookmarkAnchorType::Id => BookmarkRef::ById { + id: data.bookmark_data.r#ref, + }, + BookmarkAnchorType::Index => BookmarkRef::ByIndex { + index: data.bookmark_data.r#ref, + }, + }; + if data.bookmark_data.is_region { + BookmarkDescriptor::Region(bookmark_ref) + } else { + BookmarkDescriptor::Marker(bookmark_ref) + } + }, + set_time_selection: style.required_value_with_default( + data.seek_options.use_time_selection, + defaults::TARGET_BOOKMARK_SET_TIME_SELECTION, + ), + set_loop_points: style.required_value_with_default( + data.seek_options.use_loop_points, + defaults::TARGET_BOOKMARK_SET_LOOP_POINTS, + ), + seek_behavior: style.optional_value(data.seek_behavior), + }), + TrackAutomationMode => T::TrackAutomationMode(TrackAutomationModeTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + mode: convert_automation_mode(data.track_automation_mode), + }), + TrackMonitoringMode => T::TrackMonitoringMode(TrackMonitoringModeTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + mode: data.track_monitoring_mode, + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + }), + TrackTouchState => T::TrackAutomationTouchState(TrackAutomationTouchStateTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + touched_parameter: { + use persistence::TouchedTrackParameter as T; + use TouchedTrackParameterType::*; + match data.touched_parameter_type { + Volume => T::Volume, + Pan => T::Pan, + Width => T::Width, + } + }, + }), + TrackShow => T::TrackVisibility(TrackVisibilityTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + poll_for_feedback: None, + area: { + match data.track_area { + RealearnTrackArea::Tcp => persistence::TrackArea::Tcp, + RealearnTrackArea::Mcp => persistence::TrackArea::Mcp, + } + }, + }), + BrowseFxs => T::BrowseFxChain(BrowseFxChainTarget { + commons, + display_kind: convert_fx_display_kind(data.fx_display_type, style), + chain: convert_fx_chain_descriptor(data, style), + }), + FxParameterValue => T::FxParameterValue(FxParameterValueTarget { + commons, + poll_for_feedback: style.required_value_with_default( + data.poll_for_feedback, + defaults::TARGET_POLL_FOR_FEEDBACK, + ), + retrigger: style + .required_value_with_default(data.retrigger, defaults::TARGET_RETRIGGER), + real_time: style + .required_value_with_default(data.real_time, defaults::TARGET_REAL_TIME), + parameter: convert_fx_parameter_descriptor(data, style), + }), + CompartmentParameterValue => { + T::CompartmentParameterValue(CompartmentParameterValueTarget { + commons, + parameter: CompartmentParameterDescriptor::ById { + index: data.fx_parameter_data.index, + }, + }) + } + FxParameterTouchState => { + T::FxParameterAutomationTouchState(FxParameterAutomationTouchStateTarget { + commons, + parameter: convert_fx_parameter_descriptor(data, style), + }) + } + RouteAutomationMode => T::RouteAutomationMode(RouteAutomationModeTarget { + commons, + mode: convert_automation_mode(data.track_automation_mode), + poll_for_feedback: style.required_value_with_default( + data.poll_for_feedback, + defaults::TARGET_POLL_FOR_FEEDBACK, + ), + route: convert_route_descriptor(data, style), + }), + RouteMono => T::RouteMonoState(RouteMonoStateTarget { + commons, + poll_for_feedback: style.required_value_with_default( + data.poll_for_feedback, + defaults::TARGET_POLL_FOR_FEEDBACK, + ), + route: convert_route_descriptor(data, style), + }), + RouteMute => T::RouteMuteState(RouteMuteStateTarget { + commons, + poll_for_feedback: Some(data.poll_for_feedback), + route: convert_route_descriptor(data, style), + }), + RoutePhase => T::RoutePhase(RoutePhaseTarget { + commons, + poll_for_feedback: style.required_value_with_default( + data.poll_for_feedback, + defaults::TARGET_POLL_FOR_FEEDBACK, + ), + route: convert_route_descriptor(data, style), + }), + RoutePan => T::RoutePan(RoutePanTarget { + commons, + route: convert_route_descriptor(data, style), + }), + RouteVolume => T::RouteVolume(RouteVolumeTarget { + commons, + route: convert_route_descriptor(data, style), + }), + RouteTouchState => T::RouteTouchState(RouteTouchStateTarget { + commons, + touched_parameter: { + use persistence::TouchedRouteParameter as T; + use TouchedRouteParameterType::*; + match data.touched_route_parameter_type { + Volume => T::Volume, + Pan => T::Pan, + } + }, + route: convert_route_descriptor(data, style), + }), + PlaytimeSlotTransportAction => T::PlaytimeSlotTransportAction( + helgobox_api::persistence::PlaytimeSlotTransportActionTarget { + commons, + slot: data.clip_slot.unwrap_or_default(), + action: data.clip_transport_action.unwrap_or_default(), + stop_column_if_slot_empty: style.required_value_with_default( + data.stop_column_if_slot_empty, + defaults::TARGET_STOP_COLUMN_IF_SLOT_EMPTY, + ), + }, + ), + PlaytimeColumnAction => { + T::PlaytimeColumnAction(helgobox_api::persistence::PlaytimeColumnActionTarget { + commons, + column: data.clip_column, + action: data.clip_column_action, + }) + } + PlaytimeRowAction => { + T::PlaytimeRowAction(helgobox_api::persistence::PlaytimeRowActionTarget { + commons, + row: data.clip_row, + action: data.clip_row_action, + }) + } + PlaytimeMatrixAction => { + T::PlaytimeMatrixAction(helgobox_api::persistence::PlaytimeMatrixActionTarget { + commons, + action: data.clip_matrix_action, + }) + } + PlaytimeControlUnitScroll => T::PlaytimeControlUnitScroll( + helgobox_api::persistence::PlaytimeControlUnitScrollTarget { + commons, + axis: data.axis, + }, + ), + PlaytimeBrowseCells => { + T::PlaytimeBrowseCells(helgobox_api::persistence::PlaytimeBrowseCellsTarget { + commons, + axis: data.axis, + }) + } + PlaytimeSlotSeek => { + T::PlaytimeSlotSeek(helgobox_api::persistence::PlaytimeSlotSeekTarget { + commons, + slot: data.clip_slot.unwrap_or_default(), + feedback_resolution: convert_feedback_resolution( + data.seek_options.feedback_resolution, + style, + ), + }) + } + PlaytimeSlotVolume => { + T::PlaytimeSlotVolume(helgobox_api::persistence::PlaytimeSlotVolumeTarget { + commons, + slot: data.clip_slot.unwrap_or_default(), + }) + } + PlaytimeSlotManagementAction => T::PlaytimeSlotManagementAction( + helgobox_api::persistence::PlaytimeSlotManagementActionTarget { + commons, + slot: data.clip_slot.unwrap_or_default(), + action: data.clip_management_action, + }, + ), + SendMidi => T::SendMidi(SendMidiTarget { + commons, + message: style.required_value(data.raw_midi_pattern), + destination: { + use persistence::SendMidiDestination as T; + use SendMidiDestinationType::*; + let dest = match data.send_midi_destination { + FxOutput => T::FxOutput, + FeedbackOutput => T::FeedbackOutput, + InputDevice => T::InputDevice(InputDeviceMidiDestination { + device_id: data.midi_input_device_id.map(|id| id.get()), + }), + }; + style.required_value(dest) + }, + }), + Dummy => T::Dummy(DummyTarget { commons }), + BrowseTracks => T::BrowseTracks(BrowseTracksTarget { + commons, + scroll_arrange_view: style.required_value_with_default( + data.scroll_arrange_view, + defaults::TARGET_TRACK_SELECTION_SCROLL_ARRANGE_VIEW, + ), + scroll_mixer: style.required_value_with_default( + data.scroll_mixer, + defaults::TARGET_TRACK_SELECTION_SCROLL_MIXER, + ), + mode: style.required_value(data.browse_tracks_mode), + }), + Seek => T::Seek(SeekTarget { + commons, + use_time_selection: style.required_value_with_default( + data.seek_options.use_time_selection, + defaults::TARGET_SEEK_USE_TIME_SELECTION, + ), + use_loop_points: style.required_value_with_default( + data.seek_options.use_loop_points, + defaults::TARGET_SEEK_USE_LOOP_POINTS, + ), + use_regions: style.required_value_with_default( + data.seek_options.use_regions, + defaults::TARGET_SEEK_USE_REGIONS, + ), + use_project: style.required_value_with_default( + data.seek_options.use_project, + defaults::TARGET_SEEK_USE_PROJECT, + ), + move_view: style.required_value_with_default( + data.seek_options.move_view, + defaults::TARGET_SEEK_MOVE_VIEW, + ), + seek_play: style.required_value_with_default( + data.seek_options.seek_play, + defaults::TARGET_SEEK_SEEK_PLAY, + ), + feedback_resolution: convert_feedback_resolution( + data.seek_options.feedback_resolution, + style, + ), + behavior: style.optional_value(data.seek_behavior), + }), + PlayRate => T::PlayRate(PlayRateTarget { commons }), + Tempo => T::Tempo(TempoTarget { commons }), + TrackArm => T::TrackArmState(TrackArmStateTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + }), + TrackParentSend => T::TrackParentSendState(TrackParentSendStateTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + }), + AllTrackFxEnable => T::AllTrackFxOnOffState(AllTrackFxOnOffStateTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + poll_for_feedback: style.required_value_with_default( + data.poll_for_feedback, + defaults::TARGET_POLL_FOR_FEEDBACK, + ), + }), + TrackMute => T::TrackMuteState(TrackMuteStateTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + }), + TrackPeak => T::TrackPeak(TrackPeakTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + }), + TrackPhase => T::TrackPhase(TrackPhaseTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + poll_for_feedback: style.required_value_with_default( + data.poll_for_feedback, + defaults::TARGET_POLL_FOR_FEEDBACK, + ), + }), + TrackSelection => T::TrackSelectionState(TrackSelectionStateTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + scroll_arrange_view: style.required_value_with_default( + data.scroll_arrange_view, + defaults::TARGET_TRACK_SELECTION_SCROLL_ARRANGE_VIEW, + ), + scroll_mixer: style.required_value_with_default( + data.scroll_mixer, + defaults::TARGET_TRACK_SELECTION_SCROLL_MIXER, + ), + }), + TrackPan => T::TrackPan(TrackPanTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + }), + TrackWidth => T::TrackWidth(TrackWidthTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + }), + TrackVolume => T::TrackVolume(TrackVolumeTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + }), + TrackTool => T::TrackTool(TrackToolTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + action: Some(data.track_tool_action), + instance_tags: convert_tags(&data.tags, style), + }), + TrackSolo => T::TrackSoloState(TrackSoloStateTarget { + commons, + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + exclusivity: convert_track_exclusivity(data.track_exclusivity), + behavior: { + use persistence::SoloBehavior as T; + use SoloBehavior::*; + let v = data.solo_behavior.map(|b| match b { + InPlace => T::InPlace, + IgnoreRouting => T::IgnoreRouting, + ReaperPreference => T::ReaperPreference, + }); + style.optional_value(v) + }, + use_track_grouping: style.optional_value_with_default( + data.use_track_grouping, + defaults::TARGET_USE_TRACK_GROUPING, + ), + use_selection_ganging: style.optional_value_with_default( + data.use_selection_ganging, + defaults::TARGET_USE_SELECTION_GANGING, + ), + }), + FxTool => T::FxTool(FxToolTarget { + commons, + action: Some(data.fx_tool_action), + instance_tags: convert_tags(&data.tags, style), + fx: convert_fx_descriptor(data, style), + }), + FxEnable => T::FxOnOffState(FxOnOffStateTarget { + commons, + fx: convert_fx_descriptor(data, style), + }), + FxOnline => T::FxOnlineOfflineState(FxOnlineOfflineStateTarget { + commons, + fx: convert_fx_descriptor(data, style), + }), + LoadFxSnapshot => T::LoadFxSnapshot(LoadFxSnapshotTarget { + commons, + snapshot: { + data.fx_snapshot.as_ref().map(|s| persistence::FxSnapshot { + fx_kind: style.required_value(s.fx_type.clone()), + fx_name: style.required_value(s.fx_name.clone()), + preset_name: style.optional_value(s.preset_name.clone()), + content: { + persistence::FxSnapshotContent::Chunk { + chunk: (*s.chunk).clone(), + } + }, + }) + }, + fx: convert_fx_descriptor(data, style), + }), + FxPreset => T::BrowseFxPresets(BrowseFxPresetsTarget { + commons, + fx: convert_fx_descriptor(data, style), + }), + FxOpen => T::FxVisibility(FxVisibilityTarget { + commons, + display_kind: convert_fx_display_kind(data.fx_display_type, style), + fx: convert_fx_descriptor(data, style), + }), + SendOsc => T::SendOsc(SendOscTarget { + commons, + address: style.required_value(data.osc_address_pattern), + argument: convert_osc_argument( + data.osc_arg_index, + data.osc_arg_type, + data.osc_arg_value_range, + style, + ), + destination: { + use persistence::OscDestination as T; + let v = match data.osc_dev_id { + None => T::FeedbackOutput, + Some(id) => T::Device { id: id.to_string() }, + }; + style.required_value(v) + }, + }), + EnableInstances => T::EnableInstances(EnableInstancesTarget { + commons, + tag_kind: data.instance_tag_kind, + tags: convert_tags(&data.tags, style), + exclusivity: { + use persistence::InstanceExclusivity as T; + use Exclusivity::*; + match data.exclusivity { + NonExclusive => None, + Exclusive => Some(T::Exclusive), + ExclusiveOnOnly => Some(T::ExclusiveOnOnly), + } + }, + }), + EnableUnits => T::EnableUnits(EnableUnitsTarget { + commons, + tags: convert_tags(&data.tags, style), + exclusivity: { + use persistence::UnitExclusivity as T; + use Exclusivity::*; + match data.exclusivity { + NonExclusive => None, + Exclusive => Some(T::Exclusive), + ExclusiveOnOnly => Some(T::ExclusiveOnOnly), + } + }, + }), + EnableMappings => T::EnableMappings(EnableMappingsTarget { + commons, + tags: convert_tags(&data.tags, style), + exclusivity: { + use persistence::MappingExclusivity as T; + use Exclusivity::*; + match data.exclusivity { + NonExclusive => None, + Exclusive => Some(T::Exclusive), + ExclusiveOnOnly => Some(T::ExclusiveOnOnly), + } + }, + }), + ModifyMapping => T::ModifyMapping(ModifyMappingTarget { + commons, + modification: match data.mapping_modification_kind { + MappingModificationKind::LearnTarget => { + MappingModification::LearnTarget(LearnTargetMappingModification { + included_targets: data.included_targets.map(|s| s.into_iter().collect()), + touch_cause: style.required_value(data.touch_cause), + }) + } + MappingModificationKind::SetTargetToLastTouched => { + MappingModification::SetTargetToLastTouched( + SetTargetToLastTouchedMappingModification { + included_targets: data + .included_targets + .map(convert_into_other_hash_set), + touch_cause: style.required_value(data.touch_cause), + }, + ) + } + }, + session: data.session_id, + mapping: data.mapping_key.map(|key| key.into()), + }), + LoadMappingSnapshot => T::LoadMappingSnapshot(LoadMappingSnapshotTarget { + commons, + tags: convert_tags(&data.tags, style), + active_mappings_only: Some(data.active_mappings_only), + snapshot: style.required_value(data.mapping_snapshot), + default_value: data.mapping_snapshot_default_value, + }), + TakeMappingSnapshot => T::TakeMappingSnapshot(TakeMappingSnapshotTarget { + commons, + tags: convert_tags(&data.tags, style), + active_mappings_only: Some(data.active_mappings_only), + snapshot: data + .take_mapping_snapshot + .map(BackwardCompatibleMappingSnapshotDescForTake::New) + .unwrap_or_default(), + }), + BrowseGroup => T::BrowseGroupMappings(BrowseGroupMappingsTarget { + commons, + exclusivity: { + use persistence::GroupMappingExclusivity as T; + use Exclusivity::*; + match data.exclusivity { + NonExclusive => None, + Exclusive | ExclusiveOnOnly => Some(T::Exclusive), + } + }, + group: style.required_value(data.group_id.into()), + }), + BrowsePotFilterItems => T::BrowsePotFilterItems(BrowsePotFilterItemsTarget { + commons, + item_kind: style.required_value(data.pot_filter_item_kind), + }), + BrowsePotPresets => T::BrowsePotPresets(BrowsePotPresetsTarget { commons }), + PreviewPotPreset => T::PreviewPotPreset(PreviewPotPresetTarget { commons }), + LoadPotPreset => T::LoadPotPreset(LoadPotPresetTarget { + commons, + fx: convert_fx_descriptor(data, style), + }), + StreamDeckBrightness => T::StreamDeckBrightness(StreamDeckBrightnessTarget { commons }), + }; + Ok(target) +} + +fn convert_commons( + unit: TargetUnit, + style: ConversionStyle, +) -> ConversionResult { + let commons = persistence::TargetCommons { + unit: { + use persistence::TargetUnit as T; + use TargetUnit::*; + let unit = match unit { + Native => T::Native, + Percent => T::Percent, + }; + style.required_value(unit) + }, + }; + Ok(commons) +} + +fn convert_automation_mode_override( + r#type: AutomationModeOverrideType, + mode: RealearnAutomationMode, +) -> Option { + use persistence::AutomationModeOverride as T; + match r#type { + AutomationModeOverrideType::None => None, + AutomationModeOverrideType::Bypass => Some(T::Bypass), + AutomationModeOverrideType::Override => Some(T::Mode { + mode: convert_automation_mode(mode), + }), + } +} + +fn convert_transport_action(transport_action: TransportAction) -> persistence::TransportAction { + use persistence::TransportAction as T; + use TransportAction::*; + match transport_action { + PlayStop => T::PlayStop, + PlayPause => T::PlayPause, + Stop => T::Stop, + Pause => T::Pause, + RecordStop => T::Record, + Repeat => T::Repeat, + } +} + +fn convert_any_on_parameter(parameter: AnyOnParameter) -> persistence::AnyOnParameter { + use persistence::AnyOnParameter as T; + use AnyOnParameter::*; + match parameter { + TrackSolo => T::TrackSolo, + TrackMute => T::TrackMute, + TrackArm => T::TrackArm, + TrackSelection => T::TrackSelection, + } +} + +fn convert_automation_mode(mode: RealearnAutomationMode) -> persistence::AutomationMode { + use persistence::AutomationMode as T; + use RealearnAutomationMode::*; + match mode { + TrimRead => T::TrimRead, + Read => T::Read, + Touch => T::Touch, + Write => T::Write, + Latch => T::Latch, + LatchPreview => T::LatchPreview, + } +} + +fn convert_track_exclusivity( + exclusivity: TrackExclusivity, +) -> Option { + use persistence::TrackExclusivity as T; + use TrackExclusivity::*; + match exclusivity { + NonExclusive => None, + ExclusiveWithinProject => Some(T::WithinProject), + ExclusiveWithinFolder => Some(T::WithinFolder), + ExclusiveWithinProjectOnOnly => Some(T::WithinProjectOnOnly), + ExclusiveWithinFolderOnOnly => Some(T::WithinFolderOnOnly), + } +} + +fn convert_fx_display_kind( + display_type: FxDisplayType, + style: ConversionStyle, +) -> Option { + use persistence::FxDisplayKind as T; + use FxDisplayType::*; + let v = match display_type { + FloatingWindow => T::FloatingWindow, + Chain => T::Chain, + }; + style.required_value(v) +} + +fn convert_virtual_target(data: TargetModelData, style: ConversionStyle) -> persistence::Target { + persistence::Target::Virtual(persistence::VirtualTarget { + id: convert_control_element_id(data.control_element_index), + character: style.required_value(data.control_element_type), + learnable: style.required_value_with_default(data.learnable, defaults::TARGET_LEARNABLE), + }) +} + +fn convert_track_descriptor( + data: TrackData, + only_if_track_selected: bool, + clip_column: &helgobox_api::persistence::PlaytimeColumnDescriptor, + style: ConversionStyle, +) -> Option { + let input = TrackDeserializationInput { + track_data: &data, + clip_column, + }; + let props = deserialize_track(input); + use persistence::TrackDescriptor as T; + use VirtualTrackType::*; + let commons = persistence::TrackDescriptorCommons { + track_must_be_selected: style.required_value_with_default( + only_if_track_selected, + defaults::TARGET_TRACK_MUST_BE_SELECTED, + ), + }; + let desc = match props.r#type { + This => T::This { commons }, + Selected | AllSelected => T::Selected { + allow_multiple: style.required_value_with_default( + props.r#type == AllSelected, + defaults::TARGET_TRACK_SELECTED_ALLOW_MULTIPLE, + ), + }, + Dynamic | DynamicTcp | DynamicMcp => T::Dynamic { + commons, + expression: props.expression, + scope: style.optional_value(props.r#type.virtual_track_scope()), + }, + Master => T::Master { commons }, + Unit => T::Instance { commons }, + ById | ByIdOrName => T::ById { + commons, + id: props.id.map(|guid| guid.to_string_without_braces()), + }, + ByName | AllByName => T::ByName { + commons, + name: props.name, + allow_multiple: style.required_value_with_default( + props.r#type == AllByName, + defaults::TARGET_BY_NAME_ALLOW_MULTIPLE, + ), + }, + ByIndex | ByIndexTcp | ByIndexMcp => T::ByIndex { + commons, + index: props.index, + scope: style.optional_value(props.r#type.virtual_track_scope()), + }, + FromClipColumn => T::FromClipColumn { + commons, + column: props.clip_column, + context: props.clip_column_track_context, + }, + }; + style.required_value(desc) +} + +fn convert_fx_chain_descriptor( + data: TargetModelData, + style: ConversionStyle, +) -> persistence::FxChainDescriptor { + persistence::FxChainDescriptor::Track { + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + chain: convert_fx_chain(data.fx_data.is_input_fx, style), + } +} + +fn convert_fx_chain( + is_input_fx: bool, + style: ConversionStyle, +) -> Option { + let chain = if is_input_fx { + persistence::TrackFxChain::Input + } else { + persistence::TrackFxChain::Normal + }; + style.required_value(chain) +} + +fn convert_fx_parameter_descriptor( + data: TargetModelData, + style: ConversionStyle, +) -> persistence::FxParameterDescriptor { + let props = deserialize_fx_parameter(&data.fx_parameter_data); + use persistence::FxParameterDescriptor as T; + use VirtualFxParameterType::*; + match props.r#type { + Dynamic => T::Dynamic { + expression: props.expression, + fx: convert_fx_descriptor(data, style), + }, + ByName => T::ByName { + name: props.name, + fx: convert_fx_descriptor(data, style), + }, + ById => T::ById { + index: props.index, + fx: convert_fx_descriptor(data, style), + }, + ByIndex => T::ByIndex { + index: props.index, + fx: convert_fx_descriptor(data, style), + }, + } +} + +fn convert_route_descriptor( + data: TargetModelData, + style: ConversionStyle, +) -> persistence::RouteDescriptor { + let props = deserialize_track_route(&data.track_route_data); + use persistence::RouteDescriptor as T; + use TrackRouteSelectorType::*; + let commons = persistence::RouteDescriptorCommons { + track: convert_track_descriptor( + data.track_data, + data.enable_only_if_track_is_selected, + &data.clip_column, + style, + ), + route_kind: { + use persistence::TrackRouteKind as T; + use TrackRouteType::*; + let kind = match data.track_route_data.r#type { + Send => T::Send, + Receive => T::Receive, + HardwareOutput => T::HardwareOutput, + }; + style.required_value(kind) + }, + }; + match props.selector_type { + Dynamic => T::Dynamic { + commons, + expression: props.expression, + }, + ByName => T::ByName { + commons, + name: props.name, + }, + ById => T::ById { + commons, + id: props.id.map(|guid| guid.to_string_without_braces()), + }, + ByIndex => T::ByIndex { + commons, + index: props.index, + }, + } +} + +fn convert_fx_descriptor( + data: TargetModelData, + style: ConversionStyle, +) -> Option { + let props = deserialize_fx(&data.fx_data, None, &MigrationDescriptor::default()); + use persistence::FxDescriptor as T; + use VirtualFxType::*; + let commons = persistence::FxDescriptorCommons { + fx_must_have_focus: style.required_value_with_default( + data.enable_only_if_fx_has_focus, + defaults::TARGET_FX_MUST_HAVE_FOCUS, + ), + }; + let v = match props.r#type { + This => T::This { commons }, + Focused => T::Focused, + Unit => T::Instance { commons }, + Dynamic => T::Dynamic { + commons, + expression: props.expression, + chain: convert_fx_chain_descriptor(data, style), + }, + ById | ByIdOrIndex => T::ById { + commons, + id: props.id.map(|guid| guid.to_string_without_braces()), + chain: convert_fx_chain_descriptor(data, style), + }, + ByName | AllByName => T::ByName { + commons, + name: props.name, + allow_multiple: style.required_value_with_default( + props.r#type == AllByName, + defaults::TARGET_BY_NAME_ALLOW_MULTIPLE, + ), + chain: convert_fx_chain_descriptor(data, style), + }, + ByIndex => T::ByIndex { + commons, + index: props.index, + chain: convert_fx_chain_descriptor(data, style), + }, + }; + style.required_value(v) +} + +fn convert_feedback_resolution( + r: FeedbackResolution, + style: ConversionStyle, +) -> Option { + use persistence::FeedbackResolution as T; + use FeedbackResolution::*; + let v = match r { + Beat => T::Beat, + High => T::High, + }; + style.required_value(v) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/mod.rs new file mode 100644 index 0000000..073eb21 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/mod.rs @@ -0,0 +1,12 @@ +pub mod defaults; +pub mod from_data; +pub mod to_data; + +type ConversionResult = anyhow::Result; + +fn convert_multiple( + input: Vec, + f: impl Fn(A) -> ConversionResult, +) -> ConversionResult> { + input.into_iter().map(f).collect() +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/compartment.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/compartment.rs new file mode 100644 index 0000000..f9f5ca9 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/compartment.rs @@ -0,0 +1,83 @@ +use base::hash_util::{convert_into_other_hash_map, NonCryptoHashMap}; + +use crate::domain::{CompartmentKind, CompartmentParamIndex, ParamSetting}; +use crate::infrastructure::api::convert::to_data::group::convert_group; +use crate::infrastructure::api::convert::to_data::parameter::convert_parameter; +use crate::infrastructure::api::convert::to_data::{convert_mapping, ApiToDataConversionContext}; +use crate::infrastructure::api::convert::{convert_multiple, ConversionResult}; +use crate::infrastructure::data::{CompartmentModelData, GroupModelData}; +use helgobox_api::persistence::*; + +pub fn convert_compartment( + compartment: CompartmentKind, + compartment_content: Compartment, +) -> ConversionResult { + struct ConversionContext { + compartment: CompartmentKind, + parameters: NonCryptoHashMap, + groups: Vec, + } + fn param_index_by_key( + parameters: &NonCryptoHashMap, + key: &str, + ) -> Option { + parameters + .iter() + .find(|(_, p)| p.key_matches(key)) + .map(|(i, _)| *i) + } + impl ApiToDataConversionContext for ConversionContext { + fn compartment(&self) -> CompartmentKind { + self.compartment + } + + fn param_index_by_key(&self, key: &str) -> Option { + param_index_by_key(&self.parameters, key) + } + } + let parameters = { + let res: ConversionResult> = compartment_content + .parameters + .unwrap_or_default() + .into_iter() + .map(|p| { + Ok(( + CompartmentParamIndex::try_from(p.index).map_err(anyhow::Error::msg)?, + convert_parameter(p)?, + )) + }) + .collect(); + res? + }; + let groups = convert_multiple(compartment_content.groups.unwrap_or_default(), |g| { + convert_group(g, false, |key| param_index_by_key(¶meters, key)) + })?; + let context = ConversionContext { + compartment, + parameters, + groups, + }; + let data = CompartmentModelData { + default_group: Some(convert_group( + compartment_content.default_group.unwrap_or_default(), + true, + |key| context.param_index_by_key(key), + )?), + mappings: convert_multiple(compartment_content.mappings.unwrap_or_default(), |m| { + convert_mapping(m, &context) + })?, + parameters: context + .parameters + .iter() + .map(|(key, value)| (key.to_string(), value.clone())) + .collect(), + groups: context.groups, + custom_data: compartment_content + .custom_data + .map(convert_into_other_hash_map) + .unwrap_or_default(), + common_lua: compartment_content.common_lua.unwrap_or_default(), + notes: compartment_content.notes.unwrap_or_default(), + }; + Ok(data) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/glue.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/glue.rs new file mode 100644 index 0000000..333ad8a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/glue.rs @@ -0,0 +1,263 @@ +use crate::infrastructure::api::convert::defaults; +use crate::infrastructure::api::convert::ConversionResult; +use crate::infrastructure::data::ModeModelData; +use helgoboss_learn::{DiscreteIncrement, SoftSymmetricUnitValue, UnitValue}; +use helgobox_api::persistence::*; +use std::convert::TryInto; + +pub fn convert_glue(g: Glue) -> ConversionResult { + let source_interval = + convert_unit_value_interval(g.source_interval.unwrap_or(defaults::GLUE_SOURCE_INTERVAL))?; + let target_interval = + convert_unit_value_interval(g.target_interval.unwrap_or(defaults::GLUE_TARGET_INTERVAL))?; + let jump_interval = + convert_unit_value_interval(g.jump_interval.unwrap_or(defaults::GLUE_JUMP_INTERVAL))?; + let step_size_interval = convert_step_size_interval( + g.step_size_interval + .unwrap_or(defaults::GLUE_STEP_SIZE_INTERVAL), + )?; + let step_factor_interval = convert_step_factor_interval( + g.step_factor_interval + .unwrap_or(defaults::GLUE_STEP_FACTOR_INTERVAL), + )?; + let fire_mode = g.fire_mode.unwrap_or_default(); + struct FbCommonsData { + color: Option, + background_color: Option, + } + struct FbData { + feedback_type: helgoboss_learn::FeedbackType, + commons: FbCommonsData, + transformation: String, + } + let fb_data = { + use helgoboss_learn::FeedbackType as T; + use Feedback::*; + fn convert_fb_commons(commons: FeedbackCommons) -> FbCommonsData { + FbCommonsData { + color: commons.color.map(convert_virtual_color), + background_color: commons.background_color.map(convert_virtual_color), + } + } + match g.feedback.unwrap_or_default() { + Numeric(fb) => FbData { + feedback_type: T::Numeric, + commons: convert_fb_commons(fb.commons), + transformation: fb.transformation.unwrap_or_default(), + }, + Text(fb) => FbData { + feedback_type: T::Text, + commons: convert_fb_commons(fb.commons), + transformation: fb.text_expression.unwrap_or_default(), + }, + Dynamic(fb) => FbData { + feedback_type: T::Dynamic, + commons: convert_fb_commons(fb.commons), + transformation: fb.script.unwrap_or_default(), + }, + } + }; + let (min_press_millis, max_press_millis) = { + use FireMode::*; + match &fire_mode { + Normal(m) => { + let api_interval = m + .press_duration_interval + .unwrap_or(defaults::FIRE_MODE_PRESS_DURATION_INTERVAL); + let interval = helgoboss_learn::Interval::try_new( + api_interval.0 as u64, + api_interval.1 as u64, + ) + .map_err(anyhow::Error::msg)?; + (interval.min_val(), interval.max_val()) + } + OnSinglePress(m) => { + let max = m + .max_duration + .unwrap_or(defaults::FIRE_MODE_SINGLE_PRESS_MAX_DURATION) + as u64; + (0, max) + } + AfterTimeout(m) => { + let min = m.timeout.unwrap_or(defaults::FIRE_MODE_TIMEOUT) as u64; + (min, min) + } + AfterTimeoutKeepFiring(m) => { + let min = m.timeout.unwrap_or(defaults::FIRE_MODE_TIMEOUT) as u64; + (min, min) + } + OnDoublePress => (0, 0), + } + }; + let data = ModeModelData { + r#type: { + use helgoboss_learn::AbsoluteMode as T; + use AbsoluteMode::*; + match g.absolute_mode.unwrap_or_default() { + Normal => T::Normal, + IncrementalButton => T::IncrementalButton, + ToggleButton => T::ToggleButton, + MakeRelative => T::MakeRelative, + PerformanceControl => T::PerformanceControl, + } + }, + min_source_value: source_interval.min_val(), + max_source_value: source_interval.max_val(), + min_target_value: target_interval.min_val(), + max_target_value: target_interval.max_val(), + min_target_jump: jump_interval.min_val(), + max_target_jump: jump_interval.max_val(), + min_step_size: step_size_interval.min_val(), + max_step_size: step_size_interval.max_val(), + min_step_factor: Some(step_factor_interval.min_val()), + max_step_factor: Some(step_factor_interval.max_val()), + min_press_millis, + max_press_millis, + turbo_rate: { + use FireMode::*; + match &fire_mode { + AfterTimeoutKeepFiring(m) => m.rate.unwrap_or(defaults::FIRE_MODE_RATE) as u64, + _ => 0, + } + }, + eel_control_transformation: g.control_transformation.unwrap_or_default(), + eel_feedback_transformation: fb_data.transformation, + reverse_is_enabled: g.reverse.unwrap_or(defaults::GLUE_REVERSE), + feedback_color: fb_data.commons.color, + feedback_background_color: fb_data.commons.background_color, + ignore_out_of_range_source_values_is_enabled: false, + out_of_range_behavior: { + use helgoboss_learn::OutOfRangeBehavior as T; + use OutOfRangeBehavior::*; + match g.out_of_range_behavior.unwrap_or_default() { + MinOrMax => T::MinOrMax, + Min => T::Min, + Ignore => T::Ignore, + } + }, + fire_mode: { + use helgoboss_learn::FireMode as T; + use FireMode::*; + match &fire_mode { + Normal(_) => T::Normal, + AfterTimeout(_) => T::AfterTimeout, + AfterTimeoutKeepFiring(_) => T::AfterTimeoutKeepFiring, + OnSinglePress(_) => T::OnSinglePress, + OnDoublePress => T::OnDoublePress, + } + }, + round_target_value: g + .round_target_value + .unwrap_or(defaults::GLUE_ROUND_TARGET_VALUE), + scale_mode_enabled: false, + takeover_mode: { + use helgoboss_learn::TakeoverMode as T; + use TakeoverMode::*; + match g.takeover_mode.unwrap_or_default() { + Off => T::Off, + PickUpTolerant => T::PickupTolerant, + PickUp => T::Pickup, + LongTimeNoSee => T::LongTimeNoSee, + Parallel => T::Parallel, + CatchUp => T::CatchUp, + } + }, + button_usage: { + use helgoboss_learn::ButtonUsage as T; + if let Some(f) = g.button_filter { + use ButtonFilter::*; + match f { + PressOnly => T::PressOnly, + ReleaseOnly => T::ReleaseOnly, + } + } else { + T::Both + } + }, + encoder_usage: { + use helgoboss_learn::EncoderUsage as T; + if let Some(f) = g.encoder_filter { + use EncoderFilter::*; + match f { + IncrementOnly => T::IncrementOnly, + DecrementOnly => T::DecrementOnly, + } + } else { + T::Both + } + }, + rotate_is_enabled: g.wrap.unwrap_or(defaults::GLUE_WRAP), + make_absolute_enabled: g.relative_mode.unwrap_or_default() == RelativeMode::MakeAbsolute, + group_interaction: { + use helgoboss_learn::GroupInteraction as T; + if let Some(i) = g.interaction { + use Interaction::*; + match i { + SameControl => T::SameControl, + SameTargetValue => T::SameTargetValue, + InverseControl => T::InverseControl, + InverseTargetValue => T::InverseTargetValue, + InverseTargetValueOnOnly => T::InverseTargetValueOnOnly, + InverseTargetValueOffOnly => T::InverseTargetValueOffOnly, + } + } else { + T::None + } + }, + target_value_sequence: if let Some(s) = g.target_value_sequence { + s.parse().map_err(anyhow::Error::msg)? + } else { + Default::default() + }, + feedback_type: fb_data.feedback_type, + feedback_value_table: g.feedback_value_table, + }; + Ok(data) +} + +fn convert_step_factor_interval( + i: Interval, +) -> ConversionResult> { + let result = helgoboss_learn::Interval::try_new( + i.0.try_into().unwrap_or(DiscreteIncrement::POSITIVE_MIN), + i.1.try_into().unwrap_or(DiscreteIncrement::POSITIVE_MIN), + ) + .map_err(anyhow::Error::msg)?; + Ok(result) +} + +fn convert_step_size_interval( + i: Interval, +) -> ConversionResult> { + let uv_interval = convert_unit_value_interval(i)?; + let result = helgoboss_learn::Interval::try_new( + uv_interval.min_val().to_symmetric(), + uv_interval.max_val().to_symmetric(), + ) + .map_err(anyhow::Error::msg)?; + Ok(result) +} + +fn convert_unit_value_interval( + interval: Interval, +) -> ConversionResult> { + let result = helgoboss_learn::Interval::try_new( + interval.0.try_into().map_err(anyhow::Error::msg)?, + interval.1.try_into().map_err(anyhow::Error::msg)?, + ) + .map_err(anyhow::Error::msg)?; + Ok(result) +} + +fn convert_virtual_color(color: VirtualColor) -> helgoboss_learn::VirtualColor { + use helgoboss_learn::VirtualColor as T; + use VirtualColor::*; + match color { + Rgb(c) => T::Rgb(convert_rgb_color(c)), + Prop(c) => T::Prop { prop: c.prop }, + } +} + +fn convert_rgb_color(color: RgbColor) -> helgoboss_learn::RgbColor { + helgoboss_learn::RgbColor::new(color.0, color.1, color.2) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/group.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/group.rs new file mode 100644 index 0000000..2c22d33 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/group.rs @@ -0,0 +1,35 @@ +use crate::domain::{CompartmentParamIndex, GroupKey}; +use crate::infrastructure::api::convert::to_data::{convert_activation, convert_tags}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::{EnabledData, GroupModelData}; +use helgobox_api::persistence::*; + +pub fn convert_group( + g: Group, + is_default_group: bool, + param_index_by_key: impl Fn(&str) -> Option, +) -> ConversionResult { + let data = GroupModelData { + id: if is_default_group { + GroupKey::default() + } else { + g.id.map(|id| id.into()).unwrap_or_default() + }, + name: g.name.unwrap_or_default(), + tags: convert_tags(g.tags.unwrap_or_default())?, + enabled_data: { + EnabledData { + control_is_enabled: g.control_enabled.unwrap_or(defaults::GROUP_CONTROL_ENABLED), + feedback_is_enabled: g + .feedback_enabled + .unwrap_or(defaults::GROUP_FEEDBACK_ENABLED), + } + }, + activation_condition_data: if let Some(cond) = g.activation_condition { + convert_activation(cond, ¶m_index_by_key)? + } else { + Default::default() + }, + }; + Ok(data) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/mapping.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/mapping.rs new file mode 100644 index 0000000..f676e9b --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/mapping.rs @@ -0,0 +1,154 @@ +use super::convert_source; +use crate::application; +use crate::application::{ + LifecycleMidiMessageModel, LifecycleModel, MappingExtensionModel, RawByteArrayMidiMessage, +}; +use crate::domain::Tag; +use crate::infrastructure::api::convert::to_data::glue::convert_glue; +use crate::infrastructure::api::convert::to_data::target::convert_target; +use crate::infrastructure::api::convert::to_data::{ + convert_activation, ApiToDataConversionContext, +}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::{EnabledData, MappingModelData}; +use helgobox_api::persistence::*; +use std::convert::TryInto; +use std::str::FromStr; + +pub fn convert_mapping( + m: Mapping, + conversion_context: &impl ApiToDataConversionContext, +) -> ConversionResult { + let (prevent_echo_feedback, send_feedback_after_control) = + if let Some(source) = m.source.as_ref() { + let feedback_behavior = match source { + Source::MidiNoteVelocity(s) => s.feedback_behavior, + Source::MidiNoteKeyNumber(s) => s.feedback_behavior, + Source::MidiPolyphonicKeyPressureAmount(s) => s.feedback_behavior, + Source::MidiControlChangeValue(s) => s.feedback_behavior, + Source::MidiProgramChangeNumber(s) => s.feedback_behavior, + Source::MidiChannelPressureAmount(s) => s.feedback_behavior, + Source::MidiPitchBendChangeValue(s) => s.feedback_behavior, + Source::MidiParameterNumberValue(s) => s.feedback_behavior, + Source::MidiRaw(s) => s.feedback_behavior, + Source::Osc(s) => s.feedback_behavior, + _ => None, + }; + match feedback_behavior.unwrap_or_default() { + FeedbackBehavior::Normal => (false, false), + FeedbackBehavior::SendFeedbackAfterControl => (false, true), + FeedbackBehavior::PreventEchoFeedback => (true, false), + } + } else { + (false, false) + }; + let v = MappingModelData { + id: m.id.map(|id| id.into()), + name: m.name.unwrap_or_default(), + tags: convert_tags(m.tags.unwrap_or_default())?, + group_id: m.group.map(|g| g.into()).unwrap_or_default(), + source: convert_source(m.source.unwrap_or_default())?, + mode: convert_glue(m.glue.unwrap_or_default())?, + target: convert_target(m.target.unwrap_or_default())?, + is_enabled: m.enabled.unwrap_or(defaults::MAPPING_ENABLED), + enabled_data: { + EnabledData { + control_is_enabled: m + .control_enabled + .unwrap_or(defaults::MAPPING_CONTROL_ENABLED), + feedback_is_enabled: m + .feedback_enabled + .unwrap_or(defaults::MAPPING_FEEDBACK_ENABLED), + } + }, + activation_condition_data: if let Some(cond) = m.activation_condition { + convert_activation(cond, &|key| conversion_context.param_index_by_key(key))? + } else { + Default::default() + }, + prevent_echo_feedback, + send_feedback_after_control, + advanced: convert_advanced(m.on_activate, m.on_deactivate, m.unprocessed)?, + visible_in_projection: m + .visible_in_projection + .unwrap_or(defaults::MAPPING_VISIBLE_IN_PROJECTION), + success_audio_feedback: m.success_audio_feedback, + }; + Ok(v) +} + +pub fn convert_tags(tag_strings: Vec) -> ConversionResult> { + tag_strings.into_iter().map(convert_tag).collect() +} + +fn convert_tag(tag_string: String) -> ConversionResult { + let tag = Tag::from_str(&tag_string).map_err(anyhow::Error::msg)?; + Ok(tag) +} + +fn convert_advanced( + on_activate: Option, + on_deactivate: Option, + unprocessed: Option>, +) -> ConversionResult> { + fn into_yaml_mapping(value: serde_yaml::Value) -> serde_yaml::mapping::Mapping { + if let serde_yaml::Value::Mapping(m) = value { + m + } else { + panic!("must serialize as YAML mapping") + } + } + if on_activate.is_none() && on_deactivate.is_none() && unprocessed.is_none() { + return Ok(None); + } + let extension_model = MappingExtensionModel { + on_activate: convert_lifecycle_hook(on_activate)?, + on_deactivate: convert_lifecycle_hook(on_deactivate)?, + }; + let value = serde_yaml::to_value(extension_model)?; + let mut mapping = into_yaml_mapping(value); + if let Some(u) = unprocessed { + let unprocessed_value = serde_yaml::to_value(u)?; + let unprocessed_mapping = into_yaml_mapping(unprocessed_value); + for (key, value) in unprocessed_mapping.into_iter() { + mapping.insert(key, value); + } + } + Ok(Some(mapping)) +} + +fn convert_lifecycle_hook(hook: Option) -> ConversionResult { + let v = LifecycleModel { + send_midi_feedback: { + let actions: Result, _> = hook + .unwrap_or_default() + .send_midi_feedback + .unwrap_or_default() + .into_iter() + .map(convert_send_midi_feedback_action) + .collect(); + actions? + }, + }; + Ok(v) +} + +fn convert_send_midi_feedback_action( + action: SendMidiFeedbackAction, +) -> ConversionResult { + let v = match action { + SendMidiFeedbackAction::Raw { message } => { + LifecycleMidiMessageModel::Raw(convert_raw_midi_message(message)?) + } + }; + Ok(v) +} + +fn convert_raw_midi_message(msg: RawMidiMessage) -> ConversionResult { + use application::RawMidiMessage as T; + let v = match msg { + RawMidiMessage::HexString(s) => T::HexString(s.try_into()?), + RawMidiMessage::ByteArray(a) => T::ByteArray(RawByteArrayMidiMessage(a)), + }; + Ok(v) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/mod.rs new file mode 100644 index 0000000..0beb57a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/mod.rs @@ -0,0 +1,150 @@ +use crate::application::{BankConditionModel, ModifierConditionModel}; +use crate::domain::{CompartmentKind, CompartmentParamIndex}; +use crate::infrastructure::api::convert::ConversionResult; +use crate::infrastructure::data; +use crate::infrastructure::data::{ActivationConditionData, OscValueRange}; +use crate::{application, domain}; +use anyhow::anyhow; +pub use compartment::*; +use enumflags2::BitFlags; +use helgobox_api::persistence::{ + ActivationCondition, Interval, Keystroke, ModifierState, OscArgKind, ParamRef, + VirtualControlElementId, +}; +pub use mapping::*; +use reaper_medium::AcceleratorKeyCode; +use source::*; + +mod compartment; +mod glue; +mod group; +mod mapping; +mod parameter; +mod source; +mod target; + +fn convert_keystroke(s: Keystroke) -> domain::Keystroke { + domain::Keystroke::new( + BitFlags::from_bits_truncate(s.modifiers), + AcceleratorKeyCode::new(s.key), + ) +} + +fn convert_control_element_id(s: VirtualControlElementId) -> data::VirtualControlElementIdData { + use data::VirtualControlElementIdData as T; + use VirtualControlElementId::*; + match s { + Indexed(i) => T::Indexed(i), + Named(s) => T::Named(s), + } +} + +fn convert_osc_arg_type(s: OscArgKind) -> helgoboss_learn::OscTypeTag { + use helgoboss_learn::OscTypeTag as T; + use OscArgKind::*; + match s { + Float => T::Float, + Double => T::Double, + Bool => T::Bool, + Nil => T::Nil, + Inf => T::Inf, + Int => T::Int, + String => T::String, + Blob => T::Blob, + Time => T::Time, + Long => T::Long, + Char => T::Char, + Color => T::Color, + Midi => T::Midi, + Array => T::Array, + } +} + +fn convert_osc_value_range(v: Option>) -> OscValueRange { + v.map(|v| { + let domain_interval = helgoboss_learn::Interval::new_auto(v.0, v.1); + OscValueRange::from_interval(domain_interval) + }) + .unwrap_or_default() +} + +pub trait ApiToDataConversionContext { + fn compartment(&self) -> CompartmentKind; + fn param_index_by_key(&self, key: &str) -> Option; +} + +fn convert_activation( + a: ActivationCondition, + param_index_by_key: &impl Fn(&str) -> Option, +) -> ConversionResult { + use application::ActivationType; + use ActivationCondition::*; + let data = match a { + Modifier(c) => { + let create_model = + |state: Option<&ModifierState>| -> ConversionResult { + let res = if let Some(s) = state { + let i = resolve_parameter_ref(&s.parameter, param_index_by_key)?; + ModifierConditionModel { + param_index: Some(i), + is_on: s.on, + } + } else { + Default::default() + }; + Ok(res) + }; + ActivationConditionData { + activation_type: ActivationType::Modifiers, + modifier_condition_1: create_model( + c.modifiers.as_ref().map(|m| m.first()).unwrap_or_default(), + )?, + modifier_condition_2: create_model( + c.modifiers.as_ref().map(|m| m.get(1)).unwrap_or_default(), + )?, + ..Default::default() + } + } + Bank(c) => { + let param_index = resolve_parameter_ref(&c.parameter, param_index_by_key)?; + ActivationConditionData { + activation_type: ActivationType::Bank, + program_condition: BankConditionModel { + param_index, + bank_index: c.bank_index, + }, + ..Default::default() + } + } + Eel(c) => ActivationConditionData { + activation_type: ActivationType::Eel, + eel_condition: c.condition, + ..Default::default() + }, + Expression(c) => ActivationConditionData { + activation_type: ActivationType::Expression, + eel_condition: c.condition, + ..Default::default() + }, + TargetValue(c) => ActivationConditionData { + activation_type: ActivationType::TargetValue, + mapping_key: c.mapping.map(|m| m.into()), + eel_condition: c.condition, + ..Default::default() + }, + }; + Ok(data) +} + +fn resolve_parameter_ref( + param_ref: &ParamRef, + param_index_by_key: &impl Fn(&str) -> Option, +) -> ConversionResult { + let res = match param_ref { + ParamRef::Index(i) => CompartmentParamIndex::try_from(*i).map_err(anyhow::Error::msg)?, + ParamRef::Key(key) => { + param_index_by_key(key).ok_or_else(|| anyhow!("Parameter {key} not defined"))? + } + }; + Ok(res) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/parameter.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/parameter.rs new file mode 100644 index 0000000..018d1d8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/parameter.rs @@ -0,0 +1,13 @@ +use crate::domain::ParamSetting; +use crate::infrastructure::api::convert::ConversionResult; +use helgobox_api::persistence::*; + +pub fn convert_parameter(p: Parameter) -> ConversionResult { + let data = ParamSetting { + key: p.id, + name: p.name.unwrap_or_default(), + value_count: p.value_count, + value_labels: p.value_labels.unwrap_or_default(), + }; + Ok(data) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/source.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/source.rs new file mode 100644 index 0000000..b2b57cb --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/source.rs @@ -0,0 +1,302 @@ +use crate::application::{MidiSourceType, ReaperSourceType, SourceCategory}; +use crate::infrastructure::api::convert::to_data::{ + convert_control_element_id, convert_keystroke, convert_osc_arg_type, convert_osc_value_range, +}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::SourceModelData; +use anyhow::bail; +use helgoboss_learn::DisplayType; +use helgoboss_midi::{Channel, U14}; +use helgobox_api::persistence::*; +use std::convert::TryInto; + +pub fn convert_source(s: Source) -> ConversionResult { + let data = SourceModelData { + category: convert_category(&s), + r#type: convert_midi_source_type(&s), + channel: convert_midi_channel(&s)?, + number: convert_midi_number(&s)?, + character: match &s { + Source::MidiControlChangeValue(s) => convert_character(s.character), + Source::MidiParameterNumberValue(s) => convert_character(s.character), + Source::MidiRaw(s) => convert_character(s.character), + _ => Default::default(), + }, + is_registered: match &s { + Source::MidiParameterNumberValue(s) => s.registered, + _ => None, + }, + is_14_bit: match &s { + Source::MidiControlChangeValue(s) => s.fourteen_bit, + Source::MidiParameterNumberValue(s) => s.fourteen_bit, + _ => None, + }, + message: match &s { + Source::MidiClockTransport(s) => convert_midi_clock_transport_message(s.message), + _ => Default::default(), + }, + raw_midi_pattern: match &s { + Source::MidiRaw(s) => s.pattern.as_ref().cloned().unwrap_or_default(), + _ => Default::default(), + }, + midi_script_kind: match &s { + Source::MidiScript(s) => s.script_kind.unwrap_or_default(), + _ => Default::default(), + }, + midi_script: match &s { + Source::MidiScript(s) => s.script.as_ref().cloned().unwrap_or_default(), + _ => Default::default(), + }, + display_type: match &s { + Source::MackieLcd(s) => { + let extender_index = s + .extender_index + .unwrap_or(defaults::SOURCE_MACKIE_LCD_EXTENDER_INDEX); + match extender_index { + 0 => DisplayType::MackieLcd, + 1 => DisplayType::MackieXtLcd, + _ => { + bail!("at the moment, only extender indexes 0 and 1 are supported"); + } + } + } + Source::XTouchMackieLcd(s) => { + let extender_index = s + .extender_index + .unwrap_or(defaults::SOURCE_X_TOUCH_MACKIE_LCD_EXTENDER_INDEX); + match extender_index { + 0 => DisplayType::XTouchMackieLcd, + 1 => DisplayType::XTouchMackieXtLcd, + _ => { + bail!("at the moment, only extender indexes 0 and 1 are supported"); + } + } + } + Source::MackieSevenSegmentDisplay(_) => DisplayType::MackieSevenSegmentDisplay, + Source::SiniConE24Display(_) => DisplayType::SiniConE24, + Source::LaunchpadProScrollingTextDisplay => DisplayType::LaunchpadProScrollingText, + _ => Default::default(), + }, + display_id: match &s { + Source::MackieLcd(s) => s.channel, + Source::XTouchMackieLcd(s) => s.channel, + Source::MackieSevenSegmentDisplay(s) => s + .scope + .map(convert_mackie_seven_segment_display_scope) + .map(|s| usize::from(s) as _), + Source::SiniConE24Display(s) => s.cell_index, + _ => None, + }, + line: match &s { + Source::MackieLcd(s) => s.line, + Source::XTouchMackieLcd(s) => s.line, + Source::MackieSevenSegmentDisplay(_) => None, + Source::SiniConE24Display(s) => s.item_index, + _ => None, + }, + osc_address_pattern: match &s { + Source::Osc(s) => s.address.as_ref().cloned().unwrap_or_default(), + _ => Default::default(), + }, + osc_arg_index: match &s { + Source::Osc(s) => s.argument.and_then(|arg| arg.index), + _ => None, + }, + osc_arg_type: match &s { + Source::Osc(s) => s + .argument + .map(|arg| convert_osc_arg_type(arg.arg_kind.unwrap_or_default())) + .unwrap_or_default(), + _ => Default::default(), + }, + osc_arg_is_relative: match &s { + Source::Osc(s) => s.relative.unwrap_or(defaults::SOURCE_OSC_IS_RELATIVE), + _ => false, + }, + osc_arg_value_range: match &s { + Source::Osc(s) => convert_osc_value_range(s.argument.and_then(|a| a.value_range)), + _ => Default::default(), + }, + osc_feedback_args: match &s { + Source::Osc(s) => s.feedback_arguments.as_ref().cloned().unwrap_or_default(), + _ => Default::default(), + }, + keystroke: match &s { + Source::Key(s) => s.keystroke.map(convert_keystroke), + _ => Default::default(), + }, + button_index: match &s { + Source::StreamDeck(s) => s.button_index, + _ => Default::default(), + }, + button_design: match &s { + Source::StreamDeck(s) => s.button_design.clone(), + _ => Default::default(), + }, + control_element_type: match &s { + Source::Virtual(s) => s.character.unwrap_or_default(), + _ => Default::default(), + }, + control_element_index: match &s { + Source::Virtual(s) => convert_control_element_id(s.id.clone()), + _ => Default::default(), + }, + reaper_source_type: match &s { + Source::MidiDeviceChanges => ReaperSourceType::MidiDeviceChanges, + Source::RealearnInstanceStart => ReaperSourceType::RealearnUnitStart, + Source::Timer(_) => ReaperSourceType::Timer, + Source::RealearnParameter(_) => ReaperSourceType::RealearnParameter, + Source::RealearnCompartmentLoaded => ReaperSourceType::RealearnCompartmentLoaded, + _ => Default::default(), + }, + timer_millis: match &s { + Source::Timer(t) => t.duration, + _ => Default::default(), + }, + parameter_index: match &s { + Source::RealearnParameter(s) => { + s.parameter_index.try_into().map_err(anyhow::Error::msg)? + } + _ => Default::default(), + }, + }; + Ok(data) +} + +fn convert_category(s: &Source) -> SourceCategory { + use Source::*; + match s { + None => SourceCategory::Never, + MidiDeviceChanges + | RealearnInstanceStart + | RealearnCompartmentLoaded + | Timer(_) + | RealearnParameter(_) + | Speech => SourceCategory::Reaper, + MidiNoteVelocity(_) + | MidiNoteKeyNumber(_) + | MidiPolyphonicKeyPressureAmount(_) + | MidiControlChangeValue(_) + | MidiProgramChangeNumber(_) + | MidiSpecificProgramChange(_) + | MidiChannelPressureAmount(_) + | MidiPitchBendChangeValue(_) + | MidiParameterNumberValue(_) + | MidiClockTempo + | MidiClockTransport(_) + | MidiRaw(_) + | MidiScript(_) + | MackieLcd(_) + | XTouchMackieLcd(_) + | MackieSevenSegmentDisplay(_) + | SiniConE24Display(_) + | SlKeyboardDisplay(_) + | LaunchpadProScrollingTextDisplay => SourceCategory::Midi, + Osc(_) => SourceCategory::Osc, + Key(_) => SourceCategory::Keyboard, + StreamDeck(_) => SourceCategory::StreamDeck, + Virtual(_) => SourceCategory::Virtual, + } +} + +fn convert_midi_source_type(s: &Source) -> MidiSourceType { + use Source::*; + match s { + MidiNoteVelocity(_) => MidiSourceType::NoteVelocity, + MidiNoteKeyNumber(_) => MidiSourceType::PolyphonicKeyPressureAmount, + MidiPolyphonicKeyPressureAmount(_) => MidiSourceType::PolyphonicKeyPressureAmount, + MidiControlChangeValue(_) => MidiSourceType::ControlChangeValue, + MidiProgramChangeNumber(_) => MidiSourceType::ProgramChangeNumber, + MidiSpecificProgramChange(_) => MidiSourceType::SpecificProgramChange, + MidiChannelPressureAmount(_) => MidiSourceType::ChannelPressureAmount, + MidiPitchBendChangeValue(_) => MidiSourceType::PitchBendChangeValue, + MidiParameterNumberValue(_) => MidiSourceType::ParameterNumberValue, + MidiClockTempo => MidiSourceType::ClockTempo, + MidiClockTransport(_) => MidiSourceType::ClockTransport, + MidiRaw(_) => MidiSourceType::Raw, + MidiScript(_) => MidiSourceType::Script, + MackieLcd(_) | XTouchMackieLcd(_) | MackieSevenSegmentDisplay(_) | SiniConE24Display(_) => { + MidiSourceType::Display + } + _ => MidiSourceType::default(), + } +} + +fn convert_midi_channel(s: &Source) -> ConversionResult> { + let ch = match s { + Source::MidiNoteVelocity(s) => s.channel, + Source::MidiNoteKeyNumber(s) => s.channel, + Source::MidiPolyphonicKeyPressureAmount(s) => s.channel, + Source::MidiControlChangeValue(s) => s.channel, + Source::MidiProgramChangeNumber(s) => s.channel, + Source::MidiSpecificProgramChange(s) => s.channel, + Source::MidiChannelPressureAmount(s) => s.channel, + Source::MidiPitchBendChangeValue(s) => s.channel, + Source::MidiParameterNumberValue(s) => s.channel, + _ => None, + }; + if let Some(ch) = ch { + let ch: Channel = ch.try_into()?; + Ok(Some(ch)) + } else { + Ok(None) + } +} + +fn convert_midi_number(s: &Source) -> ConversionResult> { + let n = match s { + Source::MidiNoteVelocity(s) => s.key_number.map(|n| n as u16), + Source::MidiPolyphonicKeyPressureAmount(s) => s.key_number.map(|n| n as u16), + Source::MidiControlChangeValue(s) => s.controller_number.map(|n| n as u16), + Source::MidiSpecificProgramChange(s) => s.program_number.map(|n| n as u16), + Source::MidiParameterNumberValue(s) => s.number, + _ => None, + }; + if let Some(n) = n { + let n: U14 = n.try_into()?; + Ok(Some(n)) + } else { + Ok(None) + } +} + +fn convert_character(s: Option) -> helgoboss_learn::SourceCharacter { + use helgoboss_learn::SourceCharacter as T; + use SourceCharacter::*; + match s.unwrap_or_default() { + Range => T::RangeElement, + Button => T::MomentaryButton, + Relative1 => T::Encoder1, + Relative2 => T::Encoder2, + Relative3 => T::Encoder3, + StatefulButton => T::ToggleButton, + } +} + +fn convert_midi_clock_transport_message( + s: Option, +) -> helgoboss_learn::MidiClockTransportMessage { + use helgoboss_learn::MidiClockTransportMessage as T; + use MidiClockTransportMessage::*; + match s.unwrap_or_default() { + Start => T::Start, + Continue => T::Continue, + Stop => T::Stop, + } +} + +fn convert_mackie_seven_segment_display_scope( + s: MackieSevenSegmentDisplayScope, +) -> helgoboss_learn::MackieSevenSegmentDisplayScope { + use helgoboss_learn::MackieSevenSegmentDisplayScope as T; + use MackieSevenSegmentDisplayScope::*; + match s { + All => T::All, + Assignment => T::Assignment, + Tc => T::Tc, + TcHoursBars => T::TcHoursBars, + TcMinutesBeats => T::TcMinutesBeats, + TcSecondsSub => T::TcSecondsSub, + TcFramesTicks => T::TcFramesTicks, + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/target.rs b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/target.rs new file mode 100644 index 0000000..7b75f92 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/convert/to_data/target.rs @@ -0,0 +1,1514 @@ +use crate::application::{ + AutomationModeOverrideType, BookmarkAnchorType, FxParameterPropValues, FxPropValues, + MappingModificationKind, RealearnAutomationMode, RealearnTrackArea, TargetCategory, + TrackPropValues, TrackRoutePropValues, TrackRouteSelectorType, VirtualFxParameterType, + VirtualFxType, VirtualTrackType, +}; +use crate::domain::{ + ActionInvocationType, Exclusivity, FxDisplayType, ReaperTargetType, SeekOptions, + SendMidiDestinationType, TouchedRouteParameterType, TrackRouteType, +}; +use crate::infrastructure::api::convert::to_data::{ + convert_control_element_id, convert_osc_arg_type, convert_osc_value_range, convert_tags, +}; +use crate::infrastructure::api::convert::{defaults, ConversionResult}; +use crate::infrastructure::data::{ + serialize_fx, serialize_fx_parameter, serialize_track, serialize_track_route, BookmarkData, + FxData, FxParameterData, TargetModelData, TrackData, TrackRouteData, +}; +use crate::{application, domain}; +use base::hash_util::convert_into_other_hash_set; +use helgobox_api::persistence::*; +use reaper_high::Guid; +use reaper_medium::MidiInputDeviceId; +use std::rc::Rc; + +pub fn convert_target(t: Target) -> ConversionResult { + let data = match t { + Target::Mouse(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::Mouse, + mouse_action: d.action, + ..init(d.commons) + }, + Target::LastTouched(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::LastTouched, + included_targets: d.included_targets.map(|m| m.into_iter().collect()), + touch_cause: d.touch_cause.unwrap_or_default(), + ..init(d.commons) + }, + Target::AutomationModeOverride(d) => { + let (t, m): (AutomationModeOverrideType, RealearnAutomationMode) = { + use AutomationModeOverrideType as T; + match d.override_value { + None => (T::None, Default::default()), + Some(o) => match o { + AutomationModeOverride::Bypass => (T::Bypass, Default::default()), + AutomationModeOverride::Mode { mode } => { + (T::Override, convert_automation_mode(mode)) + } + }, + } + }; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::AutomationModeOverride, + automation_mode_override_type: t, + track_automation_mode: m, + ..init(d.commons) + } + } + #[allow(unused_mut)] + Target::ReaperAction(d) => { + let mut track_desc = if let Some(td) = d.track { + Some(convert_track_desc(td)?) + } else { + None + }; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::Action, + action_scope: d.scope.unwrap_or_default(), + command_name: d.command.map(|cmd| match cmd { + ReaperCommand::Id(id) => id.to_string(), + ReaperCommand::Name(n) => n, + }), + invocation_type: { + use ActionInvocationKind as K; + use ActionInvocationType as T; + match d.invocation.unwrap_or_default() { + K::Trigger => T::Trigger, + K::Absolute14Bit => T::Absolute14Bit, + K::Absolute7Bit => T::Absolute7Bit, + K::Relative => T::Relative, + } + }, + with_track: track_desc.is_some(), + enable_only_if_track_is_selected: track_desc + .as_ref() + .map(|d| d.track_must_be_selected) + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + clip_column: track_desc + .as_mut() + .and_then(|d| d.clip_column.take()) + .unwrap_or_default(), + track_data: track_desc.map(|d| d.track_data).unwrap_or_default(), + ..init(d.commons) + } + } + Target::TransportAction(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::Transport, + transport_action: convert_transport_action(d.action), + ..init(d.commons) + }, + Target::AnyOn(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::AnyOn, + any_on_parameter: convert_any_on_parameter(d.parameter), + ..init(d.commons) + }, + Target::BrowseTracks(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::BrowseTracks, + scroll_arrange_view: d + .scroll_arrange_view + .unwrap_or(defaults::TARGET_TRACK_SELECTION_SCROLL_ARRANGE_VIEW), + scroll_mixer: d + .scroll_mixer + .unwrap_or(defaults::TARGET_TRACK_SELECTION_SCROLL_MIXER), + browse_tracks_mode: d.mode.unwrap_or_default(), + ..init(d.commons) + }, + Target::Seek(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::Seek, + seek_options: SeekOptions { + use_time_selection: d + .use_time_selection + .unwrap_or(defaults::TARGET_SEEK_USE_TIME_SELECTION), + use_loop_points: d + .use_loop_points + .unwrap_or(defaults::TARGET_SEEK_USE_LOOP_POINTS), + use_regions: d.use_regions.unwrap_or(defaults::TARGET_SEEK_USE_REGIONS), + use_project: d.use_project.unwrap_or(defaults::TARGET_SEEK_USE_PROJECT), + move_view: d.move_view.unwrap_or(defaults::TARGET_SEEK_MOVE_VIEW), + seek_play: d.seek_play.unwrap_or(defaults::TARGET_SEEK_SEEK_PLAY), + feedback_resolution: convert_feedback_resolution( + d.feedback_resolution.unwrap_or_default(), + ), + }, + seek_behavior: d.behavior, + ..init(d.commons) + }, + Target::PlayRate(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlayRate, + ..init(d.commons) + }, + Target::Tempo(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::Tempo, + ..init(d.commons) + }, + Target::GoToBookmark(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::GoToBookmark, + bookmark_data: { + match d.bookmark { + BookmarkDescriptor::Marker(r) => { + let (anchor, r#ref) = convert_bookmark_ref(r); + BookmarkData { + anchor, + r#ref, + is_region: false, + } + } + BookmarkDescriptor::Region(r) => { + let (anchor, r#ref) = convert_bookmark_ref(r); + BookmarkData { + anchor, + r#ref, + is_region: true, + } + } + } + }, + seek_options: SeekOptions { + use_time_selection: d + .set_time_selection + .unwrap_or(defaults::TARGET_BOOKMARK_SET_TIME_SELECTION), + use_loop_points: d + .set_loop_points + .unwrap_or(defaults::TARGET_BOOKMARK_SET_LOOP_POINTS), + + ..Default::default() + }, + seek_behavior: d.seek_behavior, + ..init(d.commons) + }, + Target::TrackArmState(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackArm, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + ..init(d.commons) + } + } + Target::TrackParentSendState(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackParentSend, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + ..init(d.commons) + } + } + Target::AllTrackFxOnOffState(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::AllTrackFxEnable, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + poll_for_feedback: d + .poll_for_feedback + .unwrap_or(defaults::TARGET_POLL_FOR_FEEDBACK), + ..init(d.commons) + } + } + Target::TrackMuteState(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackMute, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + ..init(d.commons) + } + } + Target::TrackPeak(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackPeak, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + ..init(d.commons) + } + } + Target::TrackPhase(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackPhase, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + poll_for_feedback: d + .poll_for_feedback + .unwrap_or(defaults::TARGET_POLL_FOR_FEEDBACK), + ..init(d.commons) + } + } + Target::TrackSelectionState(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackSelection, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + scroll_arrange_view: d + .scroll_arrange_view + .unwrap_or(defaults::TARGET_TRACK_SELECTION_SCROLL_ARRANGE_VIEW), + scroll_mixer: d + .scroll_mixer + .unwrap_or(defaults::TARGET_TRACK_SELECTION_SCROLL_MIXER), + ..init(d.commons) + } + } + Target::TrackAutomationMode(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackAutomationMode, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + track_automation_mode: convert_automation_mode(d.mode), + ..init(d.commons) + } + } + Target::TrackMonitoringMode(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackMonitoringMode, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + track_monitoring_mode: d.mode, + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + ..init(d.commons) + } + } + Target::TrackAutomationTouchState(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackTouchState, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + touched_parameter_type: { + use domain::TouchedTrackParameterType as T; + use TouchedTrackParameter::*; + match d.touched_parameter { + Volume => T::Volume, + Pan => T::Pan, + Width => T::Width, + } + }, + ..init(d.commons) + } + } + Target::TrackPan(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackPan, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + ..init(d.commons) + } + } + Target::TrackWidth(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackWidth, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + ..init(d.commons) + } + } + Target::TrackVolume(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackVolume, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + ..init(d.commons) + } + } + Target::TrackTool(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackTool, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_tool_action: d.action.unwrap_or_default(), + tags: convert_tags(d.instance_tags.unwrap_or_default())?, + ..init(d.commons) + } + } + Target::TrackVisibility(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackShow, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + track_area: { + match d.area { + TrackArea::Tcp => RealearnTrackArea::Tcp, + TrackArea::Mcp => RealearnTrackArea::Mcp, + } + }, + ..init(d.commons) + } + } + Target::TrackSoloState(d) => { + let track_desc = convert_track_desc(d.track.unwrap_or_default())?; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TrackSolo, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_exclusivity: convert_track_exclusivity(d.exclusivity), + solo_behavior: { + use domain::SoloBehavior as T; + use SoloBehavior::*; + let v = match d.behavior.unwrap_or_default() { + InPlace => T::InPlace, + IgnoreRouting => T::IgnoreRouting, + ReaperPreference => T::ReaperPreference, + }; + Some(v) + }, + use_track_grouping: Some( + d.use_track_grouping + .unwrap_or(defaults::TARGET_USE_TRACK_GROUPING), + ), + use_selection_ganging: Some( + d.use_selection_ganging + .unwrap_or(defaults::TARGET_USE_SELECTION_GANGING), + ), + ..init(d.commons) + } + } + Target::BrowseFxChain(d) => { + let chain_desc = convert_chain_desc(d.chain)?; + let track_desc = chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::BrowseFxs, + fx_display_type: convert_fx_display_kind(d.display_kind.unwrap_or_default()), + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: FxData { + is_input_fx: chain_desc.is_input_fx, + ..Default::default() + }, + ..init(d.commons) + } + } + Target::FxTool(d) => { + let fx_desc = convert_fx_desc(d.fx.unwrap_or_default())?; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::FxTool, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + fx_tool_action: d.action.unwrap_or_default(), + tags: convert_tags(d.instance_tags.unwrap_or_default())?, + ..init(d.commons) + } + } + Target::FxOnOffState(d) => { + let fx_desc = convert_fx_desc(d.fx.unwrap_or_default())?; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::FxEnable, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + ..init(d.commons) + } + } + Target::FxOnlineOfflineState(d) => { + let fx_desc = convert_fx_desc(d.fx.unwrap_or_default())?; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::FxOnline, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + ..init(d.commons) + } + } + Target::LoadFxSnapshot(d) => { + let fx_desc = convert_fx_desc(d.fx.unwrap_or_default())?; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::LoadFxSnapshot, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + fx_snapshot: d.snapshot.map(|s| application::FxSnapshot { + fx_type: s.fx_kind.unwrap_or_default(), + fx_name: s.fx_name.unwrap_or_default(), + preset_name: s.preset_name, + chunk: match s.content { + FxSnapshotContent::Chunk { chunk } => Rc::new(chunk), + }, + }), + ..init(d.commons) + } + } + Target::BrowseFxPresets(d) => { + let fx_desc = convert_fx_desc(d.fx.unwrap_or_default())?; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::FxPreset, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + ..init(d.commons) + } + } + Target::FxVisibility(d) => { + let fx_desc = convert_fx_desc(d.fx.unwrap_or_default())?; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::FxOpen, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + fx_display_type: convert_fx_display_kind(d.display_kind.unwrap_or_default()), + ..init(d.commons) + } + } + Target::FxParameterValue(d) => { + let fx_parameter_desc = convert_fx_parameter_desc(d.parameter)?; + let fx_desc = fx_parameter_desc.fx_desc; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::FxParameterValue, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + fx_parameter_data: fx_parameter_desc.fx_parameter_data, + poll_for_feedback: d + .poll_for_feedback + .unwrap_or(defaults::TARGET_POLL_FOR_FEEDBACK), + retrigger: d.retrigger.unwrap_or(defaults::TARGET_RETRIGGER), + real_time: d.real_time.unwrap_or(defaults::TARGET_REAL_TIME), + ..init(d.commons) + } + } + Target::CompartmentParameterValue(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::CompartmentParameterValue, + fx_parameter_data: FxParameterData { + r#type: None, + index: match d.parameter { + CompartmentParameterDescriptor::ById { index } => index, + }, + name: None, + expression: None, + }, + ..init(d.commons) + }, + Target::FxParameterAutomationTouchState(d) => { + let fx_parameter_desc = convert_fx_parameter_desc(d.parameter)?; + let fx_desc = fx_parameter_desc.fx_desc; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::FxParameterTouchState, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + fx_parameter_data: fx_parameter_desc.fx_parameter_data, + ..init(d.commons) + } + } + Target::RouteAutomationMode(d) => { + let route_desc = convert_route_desc(d.route)?; + let track_desc = route_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::RouteAutomationMode, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_route_data: route_desc.track_route_data, + poll_for_feedback: d + .poll_for_feedback + .unwrap_or(defaults::TARGET_POLL_FOR_FEEDBACK), + track_automation_mode: convert_automation_mode(d.mode), + ..init(d.commons) + } + } + Target::RouteMonoState(d) => { + let route_desc = convert_route_desc(d.route)?; + let track_desc = route_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::RouteMono, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_route_data: route_desc.track_route_data, + poll_for_feedback: d + .poll_for_feedback + .unwrap_or(defaults::TARGET_POLL_FOR_FEEDBACK), + ..init(d.commons) + } + } + Target::RouteMuteState(d) => { + let route_desc = convert_route_desc(d.route)?; + let track_desc = route_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::RouteMute, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_route_data: route_desc.track_route_data, + poll_for_feedback: d + .poll_for_feedback + .unwrap_or(defaults::TARGET_POLL_FOR_FEEDBACK), + ..init(d.commons) + } + } + Target::RoutePhase(d) => { + let route_desc = convert_route_desc(d.route)?; + let track_desc = route_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::RoutePhase, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_route_data: route_desc.track_route_data, + poll_for_feedback: d + .poll_for_feedback + .unwrap_or(defaults::TARGET_POLL_FOR_FEEDBACK), + ..init(d.commons) + } + } + Target::RoutePan(d) => { + let route_desc = convert_route_desc(d.route)?; + let track_desc = route_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::RoutePan, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_route_data: route_desc.track_route_data, + ..init(d.commons) + } + } + Target::RouteVolume(d) => { + let route_desc = convert_route_desc(d.route)?; + let track_desc = route_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::RouteVolume, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_route_data: route_desc.track_route_data, + ..init(d.commons) + } + } + Target::RouteTouchState(d) => { + let route_desc = convert_route_desc(d.route)?; + let track_desc = route_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::RouteTouchState, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + track_route_data: route_desc.track_route_data, + touched_route_parameter_type: match d.touched_parameter { + TouchedRouteParameter::Volume => TouchedRouteParameterType::Volume, + TouchedRouteParameter::Pan => TouchedRouteParameterType::Pan, + }, + ..init(d.commons) + } + } + Target::PlaytimeSlotTransportAction(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeSlotTransportAction, + clip_slot: Some(d.slot), + clip_transport_action: Some(d.action), + stop_column_if_slot_empty: d + .stop_column_if_slot_empty + .unwrap_or(defaults::TARGET_STOP_COLUMN_IF_SLOT_EMPTY), + ..init(d.commons) + }, + Target::PlaytimeColumnAction(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeColumnAction, + clip_column: d.column, + clip_column_action: d.action, + ..init(d.commons) + }, + Target::PlaytimeRowAction(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeRowAction, + clip_row: d.row, + clip_row_action: d.action, + ..init(d.commons) + }, + Target::PlaytimeSlotSeek(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeSlotSeek, + clip_slot: Some(d.slot), + seek_options: SeekOptions { + feedback_resolution: convert_feedback_resolution( + d.feedback_resolution.unwrap_or_default(), + ), + ..Default::default() + }, + ..init(d.commons) + }, + Target::PlaytimeSlotVolume(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeSlotVolume, + clip_slot: Some(d.slot), + ..init(d.commons) + }, + Target::PlaytimeSlotManagementAction(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeSlotManagementAction, + clip_slot: Some(d.slot), + clip_management_action: d.action, + ..init(d.commons) + }, + Target::PlaytimeMatrixAction(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeMatrixAction, + clip_matrix_action: d.action, + ..init(d.commons) + }, + Target::PlaytimeControlUnitScroll(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeControlUnitScroll, + axis: d.axis, + ..init(d.commons) + }, + Target::PlaytimeBrowseCells(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PlaytimeBrowseCells, + axis: d.axis, + ..init(d.commons) + }, + Target::SendMidi(d) => { + let (dest_type, midi_input_device_id) = match d.destination.unwrap_or_default() { + SendMidiDestination::FxOutput => (SendMidiDestinationType::FxOutput, None), + SendMidiDestination::FeedbackOutput => { + (SendMidiDestinationType::FeedbackOutput, None) + } + SendMidiDestination::InputDevice(d) => ( + SendMidiDestinationType::InputDevice, + d.device_id.map(MidiInputDeviceId::new), + ), + }; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::SendMidi, + raw_midi_pattern: d.message.unwrap_or_default(), + send_midi_destination: dest_type, + midi_input_device_id, + ..init(d.commons) + } + } + Target::SendOsc(d) => { + let (osc_arg_index, osc_arg_type, osc_arg_value_range) = if let Some(a) = d.argument { + ( + a.index, + convert_osc_arg_type(a.arg_kind.unwrap_or_default()), + convert_osc_value_range(a.value_range), + ) + } else { + (None, Default::default(), Default::default()) + }; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::SendOsc, + osc_address_pattern: d.address.unwrap_or_default(), + osc_arg_index, + osc_arg_type, + osc_arg_value_range, + osc_dev_id: match d.destination.unwrap_or_default() { + OscDestination::FeedbackOutput => None, + OscDestination::Device { id } => Some(id.parse()?), + }, + ..init(d.commons) + } + } + Target::Dummy(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::Dummy, + ..init(d.commons) + }, + Target::EnableInstances(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::EnableInstances, + instance_tag_kind: d.tag_kind, + tags: convert_tags(d.tags.unwrap_or_default())?, + exclusivity: { + use Exclusivity as T; + use InstanceExclusivity::*; + match d.exclusivity { + None => T::NonExclusive, + Some(Exclusive) => T::Exclusive, + Some(ExclusiveOnOnly) => T::ExclusiveOnOnly, + } + }, + ..init(d.commons) + }, + Target::EnableUnits(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::EnableUnits, + tags: convert_tags(d.tags.unwrap_or_default())?, + exclusivity: { + use Exclusivity as T; + use UnitExclusivity::*; + match d.exclusivity { + None => T::NonExclusive, + Some(Exclusive) => T::Exclusive, + Some(ExclusiveOnOnly) => T::ExclusiveOnOnly, + } + }, + ..init(d.commons) + }, + Target::EnableMappings(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::EnableMappings, + tags: convert_tags(d.tags.unwrap_or_default())?, + exclusivity: { + use Exclusivity as T; + use MappingExclusivity::*; + match d.exclusivity { + None => T::NonExclusive, + Some(Exclusive) => T::Exclusive, + Some(ExclusiveOnOnly) => T::ExclusiveOnOnly, + } + }, + ..init(d.commons) + }, + Target::ModifyMapping(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::ModifyMapping, + mapping_modification_kind: MappingModificationKind::from_modification(&d.modification), + touch_cause: match &d.modification { + MappingModification::SetTargetToLastTouched(t) => t.touch_cause.unwrap_or_default(), + _ => Default::default(), + }, + included_targets: match d.modification { + MappingModification::LearnTarget(t) => { + t.included_targets.map(convert_into_other_hash_set) + } + MappingModification::SetTargetToLastTouched(t) => { + t.included_targets.map(convert_into_other_hash_set) + } + }, + session_id: d.session, + mapping_key: d.mapping.map(|id| id.into()), + ..init(d.commons) + }, + Target::LoadMappingSnapshot(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::LoadMappingSnapshot, + tags: convert_tags(d.tags.unwrap_or_default())?, + active_mappings_only: d + .active_mappings_only + .unwrap_or(defaults::TARGET_LOAD_MAPPING_SNAPSHOT_ACTIVE_MAPPINGS_ONLY), + mapping_snapshot: d.snapshot.unwrap_or_default(), + mapping_snapshot_default_value: d.default_value, + ..init(d.commons) + }, + Target::TakeMappingSnapshot(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::TakeMappingSnapshot, + tags: convert_tags(d.tags.unwrap_or_default())?, + active_mappings_only: d + .active_mappings_only + .unwrap_or(defaults::TARGET_SAVE_MAPPING_SNAPSHOT_ACTIVE_MAPPINGS_ONLY), + take_mapping_snapshot: { + let final_snapshot = match d.snapshot { + BackwardCompatibleMappingSnapshotDescForTake::Old(id) => { + MappingSnapshotDescForTake::ById { id } + } + BackwardCompatibleMappingSnapshotDescForTake::New(snapshot) => snapshot, + }; + Some(final_snapshot) + }, + ..init(d.commons) + }, + Target::BrowseGroupMappings(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::BrowseGroup, + exclusivity: { + use Exclusivity as T; + use GroupMappingExclusivity::*; + match d.exclusivity { + None => T::NonExclusive, + Some(Exclusive) => T::Exclusive, + } + }, + group_id: d.group.map(|g| g.into()).unwrap_or_default(), + ..init(d.commons) + }, + Target::BrowsePotFilterItems(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::BrowsePotFilterItems, + pot_filter_item_kind: d.item_kind.unwrap_or_default(), + ..init(d.commons) + }, + Target::BrowsePotPresets(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::BrowsePotPresets, + ..init(d.commons) + }, + Target::PreviewPotPreset(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::PreviewPotPreset, + ..init(d.commons) + }, + Target::LoadPotPreset(d) => { + let fx_desc = convert_fx_desc(d.fx.unwrap_or_default())?; + let track_desc = fx_desc.chain_desc.track_desc; + TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::LoadPotPreset, + track_data: track_desc.track_data, + enable_only_if_track_is_selected: track_desc.track_must_be_selected, + clip_column: track_desc.clip_column.unwrap_or_default(), + fx_data: fx_desc.fx_data, + enable_only_if_fx_has_focus: fx_desc.fx_must_have_focus, + ..init(d.commons) + } + } + Target::StreamDeckBrightness(d) => TargetModelData { + category: TargetCategory::Reaper, + r#type: ReaperTargetType::StreamDeckBrightness, + ..init(d.commons) + }, + Target::Virtual(d) => TargetModelData { + category: TargetCategory::Virtual, + control_element_type: d.character.unwrap_or_default(), + control_element_index: convert_control_element_id(d.id), + learnable: d.learnable.unwrap_or(defaults::TARGET_LEARNABLE), + ..Default::default() + }, + }; + Ok(data) +} + +fn init(commons: TargetCommons) -> TargetModelData { + TargetModelData { + unit: { + use application::TargetUnit as T; + use TargetUnit::*; + match commons.unit.unwrap_or_default() { + Native => T::Native, + Percent => T::Percent, + } + }, + ..Default::default() + } +} + +fn convert_automation_mode(mode: AutomationMode) -> RealearnAutomationMode { + use AutomationMode::*; + use RealearnAutomationMode as T; + match mode { + TrimRead => T::TrimRead, + Read => T::Read, + Touch => T::Touch, + Write => T::Write, + Latch => T::Latch, + LatchPreview => T::LatchPreview, + } +} + +#[derive(Default)] +struct TrackDesc { + track_data: TrackData, + track_must_be_selected: bool, + clip_column: Option, +} + +#[derive(Default)] +struct FxChainDesc { + track_desc: TrackDesc, + is_input_fx: bool, +} + +#[derive(Default)] +struct RouteDesc { + track_desc: TrackDesc, + track_route_data: TrackRouteData, +} + +#[derive(Default)] +struct FxDesc { + chain_desc: FxChainDesc, + fx_data: FxData, + fx_must_have_focus: bool, +} + +#[derive(Default)] +struct FxParameterDesc { + fx_desc: FxDesc, + fx_parameter_data: FxParameterData, +} + +fn convert_track_desc(t: TrackDescriptor) -> ConversionResult { + use TrackDescriptor::*; + let (props, track_must_be_selected) = match t { + This { commons } => ( + TrackPropValues { + r#type: VirtualTrackType::This, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + Master { commons } => ( + TrackPropValues { + r#type: VirtualTrackType::Master, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + Instance { commons } => ( + TrackPropValues { + r#type: VirtualTrackType::Unit, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + Selected { allow_multiple } => ( + TrackPropValues { + r#type: if allow_multiple.unwrap_or(defaults::TARGET_TRACK_SELECTED_ALLOW_MULTIPLE) + { + VirtualTrackType::AllSelected + } else { + VirtualTrackType::Selected + }, + ..Default::default() + }, + false, + ), + Dynamic { + commons, + expression, + scope, + } => ( + TrackPropValues { + r#type: match scope.unwrap_or_default() { + TrackScope::AllTracks => VirtualTrackType::Dynamic, + TrackScope::TracksVisibleInTcp => VirtualTrackType::DynamicTcp, + TrackScope::TracksVisibleInMcp => VirtualTrackType::DynamicMcp, + }, + expression, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + ById { commons, id } => ( + TrackPropValues { + r#type: VirtualTrackType::ById, + id: if let Some(id) = id { + Some(Guid::from_string_without_braces(&id)?) + } else { + None + }, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + ByIndex { + commons, + index, + scope, + } => ( + TrackPropValues { + r#type: match scope.unwrap_or_default() { + TrackScope::AllTracks => VirtualTrackType::ByIndex, + TrackScope::TracksVisibleInTcp => VirtualTrackType::ByIndexTcp, + TrackScope::TracksVisibleInMcp => VirtualTrackType::ByIndexMcp, + }, + index, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + ByName { + commons, + name, + allow_multiple, + } => ( + TrackPropValues { + r#type: if allow_multiple.unwrap_or(defaults::TARGET_BY_NAME_ALLOW_MULTIPLE) { + VirtualTrackType::AllByName + } else { + VirtualTrackType::ByName + }, + name, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + FromClipColumn { + commons, + column, + context, + } => ( + TrackPropValues { + r#type: VirtualTrackType::FromClipColumn, + clip_column: column, + clip_column_track_context: context, + ..Default::default() + }, + commons + .track_must_be_selected + .unwrap_or(defaults::TARGET_TRACK_MUST_BE_SELECTED), + ), + }; + let output = serialize_track(props); + let desc = TrackDesc { + track_data: output.track_data, + track_must_be_selected, + clip_column: output.clip_column, + }; + Ok(desc) +} + +fn convert_chain_desc(t: FxChainDescriptor) -> ConversionResult { + use FxChainDescriptor::*; + let desc = match t { + Track { track, chain } => FxChainDesc { + track_desc: convert_track_desc(track.unwrap_or_default())?, + is_input_fx: convert_chain(chain), + }, + }; + Ok(desc) +} + +fn convert_chain(chain: Option) -> bool { + chain.unwrap_or_default() == TrackFxChain::Input +} + +fn convert_route_desc(t: RouteDescriptor) -> ConversionResult { + use RouteDescriptor::*; + let (track_desc, props) = match t { + Dynamic { + commons, + expression, + } => ( + convert_track_desc(commons.track.unwrap_or_default())?, + TrackRoutePropValues { + selector_type: TrackRouteSelectorType::Dynamic, + r#type: convert_route_kind(commons.route_kind.unwrap_or_default()), + expression, + ..Default::default() + }, + ), + ById { commons, id } => ( + convert_track_desc(commons.track.unwrap_or_default())?, + TrackRoutePropValues { + selector_type: TrackRouteSelectorType::ById, + r#type: convert_route_kind(commons.route_kind.unwrap_or_default()), + id: if let Some(id) = id { + Some(Guid::from_string_without_braces(&id)?) + } else { + None + }, + ..Default::default() + }, + ), + ByIndex { commons, index } => ( + convert_track_desc(commons.track.unwrap_or_default())?, + TrackRoutePropValues { + selector_type: TrackRouteSelectorType::ByIndex, + r#type: convert_route_kind(commons.route_kind.unwrap_or_default()), + index, + ..Default::default() + }, + ), + ByName { commons, name } => ( + convert_track_desc(commons.track.unwrap_or_default())?, + TrackRoutePropValues { + selector_type: TrackRouteSelectorType::ByName, + r#type: convert_route_kind(commons.route_kind.unwrap_or_default()), + name, + ..Default::default() + }, + ), + }; + let desc = RouteDesc { + track_desc, + track_route_data: serialize_track_route(props), + }; + Ok(desc) +} + +fn convert_route_kind(kind: TrackRouteKind) -> TrackRouteType { + use TrackRouteKind::*; + use TrackRouteType as T; + match kind { + Send => T::Send, + Receive => T::Receive, + HardwareOutput => T::HardwareOutput, + } +} + +fn convert_fx_desc(t: FxDescriptor) -> ConversionResult { + use FxDescriptor::*; + let (chain_desc, props, fx_must_have_focus) = match t { + Focused => ( + FxChainDesc::default(), + FxPropValues { + r#type: VirtualFxType::Focused, + ..Default::default() + }, + false, + ), + Instance { commons } => ( + FxChainDesc::default(), + FxPropValues { + r#type: VirtualFxType::Unit, + ..Default::default() + }, + commons + .fx_must_have_focus + .unwrap_or(defaults::TARGET_FX_MUST_HAVE_FOCUS), + ), + This { commons } => ( + FxChainDesc::default(), + FxPropValues { + r#type: VirtualFxType::This, + ..Default::default() + }, + commons + .fx_must_have_focus + .unwrap_or(defaults::TARGET_FX_MUST_HAVE_FOCUS), + ), + Dynamic { + commons, + chain, + expression, + } => ( + convert_chain_desc(chain)?, + FxPropValues { + r#type: VirtualFxType::Dynamic, + expression, + ..Default::default() + }, + commons + .fx_must_have_focus + .unwrap_or(defaults::TARGET_FX_MUST_HAVE_FOCUS), + ), + ById { commons, chain, id } => ( + convert_chain_desc(chain)?, + FxPropValues { + r#type: VirtualFxType::ById, + id: if let Some(id) = id { + Some(Guid::from_string_without_braces(&id)?) + } else { + None + }, + ..Default::default() + }, + commons + .fx_must_have_focus + .unwrap_or(defaults::TARGET_FX_MUST_HAVE_FOCUS), + ), + ByIndex { + commons, + chain, + index, + } => ( + convert_chain_desc(chain)?, + FxPropValues { + r#type: VirtualFxType::ByIndex, + index, + ..Default::default() + }, + commons + .fx_must_have_focus + .unwrap_or(defaults::TARGET_FX_MUST_HAVE_FOCUS), + ), + ByName { + commons, + chain, + name, + allow_multiple, + } => ( + convert_chain_desc(chain)?, + FxPropValues { + r#type: if allow_multiple.unwrap_or(defaults::TARGET_BY_NAME_ALLOW_MULTIPLE) { + VirtualFxType::AllByName + } else { + VirtualFxType::ByName + }, + name, + ..Default::default() + }, + commons + .fx_must_have_focus + .unwrap_or(defaults::TARGET_FX_MUST_HAVE_FOCUS), + ), + }; + let desc = FxDesc { + fx_data: FxData { + is_input_fx: chain_desc.is_input_fx, + ..serialize_fx(props) + }, + chain_desc, + fx_must_have_focus, + }; + Ok(desc) +} + +fn convert_fx_parameter_desc(t: FxParameterDescriptor) -> ConversionResult { + use FxParameterDescriptor::*; + let (fx_desc, props) = match t { + Dynamic { fx, expression } => ( + convert_fx_desc(fx.unwrap_or_default())?, + FxParameterPropValues { + r#type: VirtualFxParameterType::Dynamic, + expression, + ..Default::default() + }, + ), + ById { fx, index } => ( + convert_fx_desc(fx.unwrap_or_default())?, + FxParameterPropValues { + r#type: VirtualFxParameterType::ById, + index, + ..Default::default() + }, + ), + ByIndex { fx, index } => ( + convert_fx_desc(fx.unwrap_or_default())?, + FxParameterPropValues { + r#type: VirtualFxParameterType::ByIndex, + index, + ..Default::default() + }, + ), + ByName { fx, name } => ( + convert_fx_desc(fx.unwrap_or_default())?, + FxParameterPropValues { + r#type: VirtualFxParameterType::ByName, + name, + ..Default::default() + }, + ), + }; + let desc = FxParameterDesc { + fx_desc, + fx_parameter_data: serialize_fx_parameter(props), + }; + Ok(desc) +} + +fn convert_transport_action(transport_action: TransportAction) -> domain::TransportAction { + use domain::TransportAction as T; + use TransportAction::*; + match transport_action { + PlayStop => T::PlayStop, + PlayPause => T::PlayPause, + Stop => T::Stop, + Pause => T::Pause, + Record => T::RecordStop, + Repeat => T::Repeat, + } +} + +fn convert_any_on_parameter(parameter: AnyOnParameter) -> domain::AnyOnParameter { + use domain::AnyOnParameter as T; + use AnyOnParameter::*; + match parameter { + TrackSolo => T::TrackSolo, + TrackMute => T::TrackMute, + TrackArm => T::TrackArm, + TrackSelection => T::TrackSelection, + } +} + +fn convert_feedback_resolution(r: FeedbackResolution) -> domain::FeedbackResolution { + use domain::FeedbackResolution as T; + use FeedbackResolution::*; + match r { + Beat => T::Beat, + High => T::High, + } +} + +fn convert_bookmark_ref(r: BookmarkRef) -> (BookmarkAnchorType, u32) { + use BookmarkAnchorType as T; + match r { + BookmarkRef::ById { id } => (T::Id, id), + BookmarkRef::ByIndex { index } => (T::Index, index), + } +} + +fn convert_track_exclusivity(exclusivity: Option) -> domain::TrackExclusivity { + use domain::TrackExclusivity as T; + use TrackExclusivity::*; + match exclusivity { + None => T::NonExclusive, + Some(e) => match e { + WithinProject => T::ExclusiveWithinProject, + WithinFolder => T::ExclusiveWithinFolder, + WithinProjectOnOnly => T::ExclusiveWithinProjectOnOnly, + WithinFolderOnOnly => T::ExclusiveWithinFolderOnOnly, + }, + } +} + +fn convert_fx_display_kind(display_kind: FxDisplayKind) -> FxDisplayType { + use domain::FxDisplayType as T; + use FxDisplayKind::*; + match display_kind { + FloatingWindow => T::FloatingWindow, + Chain => T::Chain, + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/api/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/api/mod.rs new file mode 100644 index 0000000..b5b6721 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/api/mod.rs @@ -0,0 +1 @@ +pub mod convert; diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/activation_condition_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/activation_condition_data.rs new file mode 100644 index 0000000..77005ee --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/activation_condition_data.rs @@ -0,0 +1,85 @@ +use crate::application::{ + ActivationConditionCommand, ActivationConditionModel, ActivationType, BankConditionModel, + Change, ModifierConditionModel, +}; +use crate::domain::MappingKey; +use crate::infrastructure::data::{DataToModelConversionContext, ModelToDataConversionContext}; +use base::default_util::{deserialize_null_default, is_default}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ActivationConditionData { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub activation_type: ActivationType, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub modifier_condition_1: ModifierConditionModel, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub modifier_condition_2: ModifierConditionModel, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub program_condition: BankConditionModel, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub eel_condition: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub mapping_key: Option, +} + +impl ActivationConditionData { + pub fn from_model( + model: &ActivationConditionModel, + conversion_context: &impl ModelToDataConversionContext, + ) -> ActivationConditionData { + ActivationConditionData { + activation_type: model.activation_type(), + modifier_condition_1: model.modifier_condition_1(), + modifier_condition_2: model.modifier_condition_2(), + program_condition: model.bank_condition(), + eel_condition: model.script().to_owned(), + mapping_key: model + .mapping_id() + .and_then(|id| conversion_context.mapping_key_by_id(id)), + } + } + + pub fn apply_to_model( + &self, + model: &mut ActivationConditionModel, + conversion_context: &impl DataToModelConversionContext, + ) { + use ActivationConditionCommand as V; + model.change(V::SetActivationType(self.activation_type)); + model.change(V::SetModifierCondition1(self.modifier_condition_1)); + model.change(V::SetModifierCondition2(self.modifier_condition_2)); + model.change(V::SetBankCondition(self.program_condition)); + model.change(V::SetScript(self.eel_condition.clone())); + let mapping_id = self + .mapping_key + .as_ref() + .and_then(|key| conversion_context.mapping_id_by_key(key)); + model.change(V::SetMappingId(mapping_id)); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/common.rs b/plugin-reaper-realearn/main/src/infrastructure/data/common.rs new file mode 100644 index 0000000..27c5dbb --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/common.rs @@ -0,0 +1,40 @@ +use helgoboss_learn::{Interval, DEFAULT_OSC_ARG_VALUE_RANGE}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct IntervalData { + min: T, + max: T, +} + +impl IntervalData { + pub fn from_interval(interval: Interval) -> Self { + Self { + min: interval.min_val(), + max: interval.max_val(), + } + } + + pub fn to_interval(&self) -> Interval { + Interval::new_auto(self.min, self.max) + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct OscValueRange(IntervalData); + +impl Default for OscValueRange { + fn default() -> Self { + Self(IntervalData::from_interval(DEFAULT_OSC_ARG_VALUE_RANGE)) + } +} + +impl OscValueRange { + pub fn from_interval(interval: Interval) -> Self { + Self(IntervalData::from_interval(interval)) + } + + pub fn to_interval(&self) -> Interval { + self.0.to_interval() + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/compartment_model_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/compartment_model_data.rs new file mode 100644 index 0000000..edd2a29 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/compartment_model_data.rs @@ -0,0 +1,177 @@ +use crate::application::{CompartmentInUnit, CompartmentModel, GroupModel, UnitModel}; +use crate::domain::{ + CompartmentKind, CompartmentParamIndex, GroupId, GroupKey, MappingId, MappingKey, ParamSetting, +}; +use crate::infrastructure::data::{ + GroupModelData, MappingModelData, MigrationDescriptor, ModelToDataConversionContext, + SimpleDataToModelConversionContext, +}; +use base::default_util::{deserialize_null_default, is_default}; +use base::hash_util::NonCryptoHashMap; +use base::validation_util::{ensure_no_duplicate, ValidationError}; +use semver::Version; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CompartmentModelData { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub default_group: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub groups: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub mappings: Vec, + // String key workaround because otherwise deserialization doesn't work with flattening. + // (https://github.com/serde-rs/serde/issues/1183) + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub parameters: NonCryptoHashMap, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub common_lua: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub custom_data: NonCryptoHashMap, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub notes: String, +} + +impl ModelToDataConversionContext for CompartmentModel { + fn non_default_group_key_by_id(&self, group_id: GroupId) -> Option { + let group = self.groups.iter().find(|g| g.id() == group_id)?; + Some(group.key().clone()) + } + + fn mapping_key_by_id(&self, mapping_id: MappingId) -> Option { + let mapping = self.mappings.iter().find(|m| m.id() == mapping_id)?; + Some(mapping.key().clone()) + } +} + +impl CompartmentModelData { + pub fn from_model(model: &CompartmentModel) -> Self { + Self { + default_group: Some(GroupModelData::from_model(&model.default_group, model)), + groups: model + .groups + .iter() + .map(|g| GroupModelData::from_model(g, model)) + .collect(), + mappings: model + .mappings + .iter() + .map(|m| MappingModelData::from_model(m, model)) + .collect(), + parameters: model + .parameters + .iter() + .map(|(key, value)| (key.to_string(), value.clone())) + .collect(), + common_lua: model.common_lua.clone(), + custom_data: model.custom_data.clone(), + notes: model.notes.clone(), + } + } + + /// Pass a session if you want to create mapping and group models with IDs of the current + /// sessions (instead of creating new random ones). + pub fn to_model( + &self, + version: Option<&Version>, + compartment: CompartmentKind, + session: Option<&UnitModel>, + ) -> anyhow::Result { + ensure_no_duplicate_compartment_data( + &self.mappings, + &self.groups, + self.parameters.values(), + )?; + let migration_descriptor = MigrationDescriptor::new(version); + let conversion_context = SimpleDataToModelConversionContext::from_session_or_random( + &self.groups, + &self.mappings, + session.map(|s| CompartmentInUnit::new(s, compartment)), + ); + let final_default_group = self + .default_group + .as_ref() + .map(|g| g.to_model(compartment, true, &conversion_context)) + .unwrap_or_else(|| GroupModel::default_for_compartment(compartment)); + let groups = self + .groups + .iter() + .map(|g| g.to_model(compartment, false, &conversion_context)) + .collect(); + let mappings: anyhow::Result> = self + .mappings + .iter() + .map(|m| { + m.to_model_for_preset( + compartment, + &migration_descriptor, + version, + &conversion_context, + ) + .map_err(anyhow::Error::msg) + }) + .collect(); + let model = CompartmentModel { + default_group: final_default_group, + mappings: mappings?, + parameters: self + .parameters + .iter() + .filter_map(|(key, value)| { + let index: CompartmentParamIndex = key.parse::().ok()?.try_into().ok()?; + Some((index, value.clone())) + }) + .collect(), + groups, + common_lua: self.common_lua.clone(), + custom_data: self.custom_data.clone(), + notes: self.notes.clone(), + }; + Ok(model) + } +} + +pub fn ensure_no_duplicate_compartment_data<'a>( + mappings: &[MappingModelData], + groups: &[GroupModelData], + parameters: impl Iterator, +) -> Result<(), ValidationError> { + ensure_no_duplicate("mapping IDs", mappings.iter().filter_map(|m| m.id.as_ref()))?; + ensure_no_duplicate( + "group IDs", + groups + .iter() + .filter_map(|g| if g.id.is_empty() { None } else { Some(&g.id) }), + )?; + ensure_no_duplicate("parameter IDs", parameters.filter_map(|p| p.key.as_ref()))?; + Ok(()) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/compartment_preset_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/compartment_preset_data.rs new file mode 100644 index 0000000..cfd02ef --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/compartment_preset_data.rs @@ -0,0 +1,55 @@ +use crate::application::CompartmentPresetModel; +use crate::domain::CompartmentKind; +use crate::infrastructure::data::CompartmentModelData; +use base::default_util::{deserialize_null_default, is_default}; + +use crate::infrastructure::plugin::BackboneShell; +use semver::Version; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CompartmentPresetData { + // Since ReaLearn 1.12.0-pre18 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + version: Option, + #[serde(skip_deserializing, skip_serializing_if = "is_default")] + id: Option, + name: String, + #[serde(flatten)] + data: CompartmentModelData, +} + +impl CompartmentPresetData { + pub fn from_model(preset: &CompartmentPresetModel) -> CompartmentPresetData { + CompartmentPresetData { + version: Some(BackboneShell::version().clone()), + id: Some(preset.id().to_string()), + data: CompartmentModelData::from_model(preset.model()), + name: preset.name().to_string(), + } + } + + pub fn to_model( + &self, + id: String, + compartment: CompartmentKind, + ) -> anyhow::Result { + let preset = CompartmentPresetModel::new( + id, + self.name.clone(), + compartment, + self.data + .to_model(self.version.as_ref(), compartment, None)?, + ); + Ok(preset) + } + + pub fn clear_id(&mut self) { + self.id = None; + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/controller_manager.rs b/plugin-reaper-realearn/main/src/infrastructure/data/controller_manager.rs new file mode 100644 index 0000000..d0e9d9a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/controller_manager.rs @@ -0,0 +1,190 @@ +use crate::domain::{format_as_pretty_hex, RequestMidiDeviceIdentityReply}; +use anyhow::Context; +use camino::Utf8PathBuf; +use helgobox_api::persistence::{ + Controller, ControllerConfig, ControllerConnection, MidiControllerConnection, MidiInputPort, +}; +use nanoid::nanoid; +use reaper_medium::{MidiInputDeviceId, MidiOutputDeviceId}; +use std::cell::RefCell; +use std::fmt::Debug; +use std::rc::Rc; +use std::{fs, mem}; + +pub type SharedControllerManager = Rc>; + +#[derive(Debug)] +pub struct ControllerManager { + controller_config_path: Utf8PathBuf, + event_handler: Box, + controller_config: ControllerConfig, +} + +pub trait ControllerManagerEventHandler: Debug { + fn controller_config_changed(&self, source: &ControllerManager); +} + +impl ControllerManager { + pub fn new( + controller_config_path: Utf8PathBuf, + event_handler: Box, + ) -> Self { + Self { + controller_config_path, + event_handler, + controller_config: Default::default(), + } + } + + pub fn controller_config(&self) -> &ControllerConfig { + &self.controller_config + } + + pub fn find_controller_connected_to_midi_input( + &self, + dev_id: MidiInputDeviceId, + ) -> Option<&Controller> { + self.find_controller_by_midi_connection(|con| { + con.input_port + .is_some_and(|p| p.get() == dev_id.get() as u32) + }) + } + + pub fn find_controller_connected_to_midi_output( + &self, + dev_id: MidiOutputDeviceId, + ) -> Option<&Controller> { + self.find_controller_by_midi_connection(|con| { + con.output_port + .is_some_and(|p| p.get() == dev_id.get() as u32) + }) + } + + pub fn find_controller_by_midi_connection( + &self, + f: impl Fn(&MidiControllerConnection) -> bool, + ) -> Option<&Controller> { + self.controller_config + .controllers + .iter() + .find(|controller| match &controller.connection { + Some(ControllerConnection::Midi(con)) => f(con), + _ => false, + }) + } + + pub fn save_controller( + &mut self, + mut controller: Controller, + ) -> anyhow::Result { + let new_midi_output_device_id = get_controller_midi_output_device_id(&controller); + let outcome = if controller.id.is_empty() { + let new_id = nanoid!(); + controller.id.clone_from(&new_id); + self.controller_config.controllers.push(controller); + SaveControllerOutcome { + id: new_id, + new_midi_output_device_id, + connection_changed: true, + } + } else { + let existing_controller = self.find_controller_by_id_mut(&controller.id)?; + let connection_changed = controller.connection != existing_controller.connection; + let old_controller = mem::replace(existing_controller, controller); + SaveControllerOutcome { + id: old_controller.id, + new_midi_output_device_id, + connection_changed, + } + }; + self.save()?; + self.event_handler.controller_config_changed(self); + Ok(outcome) + } + + pub fn update_controller_device_identity( + &mut self, + controller_id: &str, + reply: Option, + ) -> anyhow::Result<()> { + let controller = self.find_controller_by_id_mut(controller_id)?; + let connection = controller + .connection + .as_mut() + .context("controller has no connection")?; + match connection { + ControllerConnection::Midi(c) => { + if let Some(reply) = reply { + // Convenience feature: Set input port automatically + c.input_port = Some(MidiInputPort::new(reply.input_device_id.get() as u32)); + c.identity_response = + Some(format_as_pretty_hex(&reply.device_inquiry_reply.message)); + } else { + c.identity_response = None; + } + } + ControllerConnection::Osc(_) => {} + } + self.save()?; + self.event_handler.controller_config_changed(self); + Ok(()) + } + + pub fn find_controller_by_id(&self, controller_id: &str) -> Option<&Controller> { + self.controller_config + .controllers + .iter() + .find(|c| c.id == controller_id) + } + + fn find_controller_by_id_mut( + &mut self, + controller_id: &str, + ) -> anyhow::Result<&mut Controller> { + self.controller_config + .controllers + .iter_mut() + .find(|c| c.id == controller_id) + .context("controller not found") + } + + pub fn delete_controller(&mut self, controller_id: &str) -> anyhow::Result<()> { + self.controller_config + .controllers + .retain(|c| c.id != controller_id); + self.save()?; + self.event_handler.controller_config_changed(self); + Ok(()) + } + + pub fn load_controllers_from_disk(&mut self) -> anyhow::Result<()> { + let json = fs::read_to_string(&self.controller_config_path)?; + self.controller_config = serde_json::from_str(&json)?; + Ok(()) + } + + fn save(&self) -> anyhow::Result<()> { + let json = serde_json::to_string_pretty(&self.controller_config)?; + fs::create_dir_all( + self.controller_config_path + .parent() + .context("controller config path has no parent")?, + )?; + fs::write(&self.controller_config_path, json)?; + Ok(()) + } +} + +pub struct SaveControllerOutcome { + pub id: String, + pub new_midi_output_device_id: Option, + pub connection_changed: bool, +} + +fn get_controller_midi_output_device_id(controller: &Controller) -> Option { + let id = match controller.connection.as_ref()? { + ControllerConnection::Midi(c) => MidiOutputDeviceId::new(c.output_port?.get() as u8), + ControllerConnection::Osc(_) => return None, + }; + Some(id) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/deserializers.rs b/plugin-reaper-realearn/main/src/infrastructure/data/deserializers.rs new file mode 100644 index 0000000..b4f1bda --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/deserializers.rs @@ -0,0 +1,55 @@ +use serde::{Deserialize, Deserializer}; +use std::convert::TryFrom; + +/// Behaves like the built-in deserializer for `Option` but also accepts `-1` as `None`. +/// +/// Also accepts decimal numbers. +/// +/// Based on this: https://stackoverflow.com/a/56384732/5418870 +pub fn none_if_minus_one<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + TryFrom, +{ + // we define a local enum type inside of the function + // because it is untagged, serde will deserialize as the first variant + // that it can + #[derive(Deserialize)] + #[serde(untagged)] + enum MaybeMinusOne { + // If it can be parsed as Option, it's perfect. + TargetValue(Option), + // Otherwise try parsing as decimal number. + DecimalNumber(f64), + } + + let value: MaybeMinusOne = Deserialize::deserialize(deserializer)?; + match value { + MaybeMinusOne::TargetValue(v) => Ok(v), + MaybeMinusOne::DecimalNumber(n) => { + if (n - -1.0).abs() < f64::EPSILON { + Ok(None) + } else { + match T::try_from(n as u64) { + Ok(t) => Ok(Some(t)), + Err(_) => Err(serde::de::Error::custom("invalid number")), + } + } + } + } +} + +/// This is like the u32 deserializer but is also fine with decimal numbers (e.g. 4.0). +/// +/// For some reason we have serialized positive integers as floating point numbers in ReaLearn C++. +pub fn f32_as_u32<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let value: f32 = Deserialize::deserialize(deserializer)?; + let integer = value as i32; + if integer < 0 { + return Err(serde::de::Error::custom("number must not be negative")); + } + Ok(integer as u32) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/enabled_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/enabled_data.rs new file mode 100644 index 0000000..385c737 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/enabled_data.rs @@ -0,0 +1,11 @@ +use base::default_util::{bool_true, is_bool_true}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EnabledData { + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + pub control_is_enabled: bool, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + pub feedback_is_enabled: bool, +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/group_model_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/group_model_data.rs new file mode 100644 index 0000000..2dec350 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/group_model_data.rs @@ -0,0 +1,104 @@ +use crate::application::{Change, GroupCommand, GroupModel}; +use crate::domain::{CompartmentKind, GroupId, GroupKey, Tag}; +use crate::infrastructure::data::{ + ActivationConditionData, DataToModelConversionContext, EnabledData, + ModelToDataConversionContext, +}; +use base::default_util::{deserialize_null_default, is_default}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GroupModelData { + /// Doesn't have to be a UUID since 2.11.0-pre.13 and corresponds to the model *key* instead! + /// Because default group UUID is the default, it won't be serialized. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + // Saved only in some ReaLearn 2.11.0-pre-releases. Later we persist this in "id" field again. + // So this is just for being compatible with those few pre-releases! + #[serde(alias = "key")] + pub id: GroupKey, + // Because default group name is empty, it won't be serialized. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub name: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub tags: Vec, + #[serde(flatten)] + pub enabled_data: EnabledData, + #[serde(flatten)] + pub activation_condition_data: ActivationConditionData, +} + +impl GroupModelData { + pub fn from_model( + model: &GroupModel, + conversion_context: &impl ModelToDataConversionContext, + ) -> GroupModelData { + GroupModelData { + id: model.key().clone(), + name: model.name().to_owned(), + tags: model.tags().to_owned(), + enabled_data: EnabledData { + control_is_enabled: model.control_is_enabled(), + feedback_is_enabled: model.feedback_is_enabled(), + }, + activation_condition_data: ActivationConditionData::from_model( + model.activation_condition_model(), + conversion_context, + ), + } + } + + pub fn to_model( + &self, + compartment: CompartmentKind, + is_default_group: bool, + conversion_context: &impl DataToModelConversionContext, + ) -> GroupModel { + let mut model = GroupModel::new_from_data( + compartment, + if is_default_group { + GroupId::default() + } else { + conversion_context + .group_id_by_key(&self.id) + .unwrap_or_else(GroupId::random) + }, + if is_default_group { + GroupKey::default() + } else { + self.id.clone() + }, + ); + self.apply_to_model(&mut model, conversion_context); + model + } + + fn apply_to_model( + &self, + model: &mut GroupModel, + conversion_context: &impl DataToModelConversionContext, + ) { + model.change(GroupCommand::SetName(self.name.clone())); + model.change(GroupCommand::SetTags(self.tags.clone())); + model.change(GroupCommand::SetControlIsEnabled( + self.enabled_data.control_is_enabled, + )); + model.change(GroupCommand::SetFeedbackIsEnabled( + self.enabled_data.feedback_is_enabled, + )); + self.activation_condition_data + .apply_to_model(&mut model.activation_condition_model, conversion_context); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/instance_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/instance_data.rs new file mode 100644 index 0000000..d69b390 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/instance_data.rs @@ -0,0 +1,94 @@ +use crate::domain::Tag; +use crate::infrastructure::data::UnitData; +use anyhow::Context; +use base::default_util::{deserialize_null_default, is_default}; +use base::hash_util::NonCryptoHashMap; +use helgobox_api::persistence::InstanceSettings; +use playtime_api::persistence::FlexibleMatrix; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceData { + pub main_unit: UnitData, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub tags: Vec, + pub additional_units: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub settings: InstanceSettings, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub pot_state: pot::PersistentState, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_matrix: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub custom_data: NonCryptoHashMap, +} + +impl InstanceData { + pub fn parse(data: &[u8]) -> anyhow::Result { + let instance_data: Self = match serde_json::from_slice(data) { + Ok(d) => d, + Err(instance_data_error) => { + // Couldn't parse as instance data. Unhappy path. Let's make some inspections. + let json: serde_json::Value = serde_json::from_slice(data) + .with_context(|| create_parse_error_msg(data, "parsing as JSON"))?; + // Parsing was successful + if json.get("mainUnit").is_some() { + // It's really meant as InstanceData. Fail! + Err(instance_data_error).with_context(|| { + create_parse_error_msg(data, "interpreting JSON as instance data") + })?; + } + // Ah, this could be a preset for the pre-2.16 era, meant as data for a single unit. + let data: UnitData = serde_json::from_value(json).with_context(|| { + create_parse_error_msg(data, "interpreting JSON as unit data") + })?; + convert_old_unit_to_instance_data(data) + } + }; + Ok(instance_data) + } +} + +fn create_parse_error_msg(data: &[u8], label: &str) -> String { + let data_as_str = std::str::from_utf8(data).unwrap_or(""); + format!( + "Helgobox couldn't restore this instance while {label}. Please attach the following text if you want to report this: \n\n\ + {data_as_str}\n\n" + ) +} + +#[allow(deprecated)] +fn convert_old_unit_to_instance_data(mut d: UnitData) -> InstanceData { + InstanceData { + // Migrate pot state from unit data + pot_state: d.pot_state.take().unwrap_or_default(), + // Migrate Playtime matrix state from unit data + clip_matrix: d.clip_matrix.take(), + main_unit: d, + additional_units: vec![], + tags: vec![], + settings: Default::default(), + custom_data: Default::default(), + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/license_management.rs b/plugin-reaper-realearn/main/src/infrastructure/data/license_management.rs new file mode 100644 index 0000000..86f67be --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/license_management.rs @@ -0,0 +1,112 @@ +use anyhow::{anyhow, Context}; +use helgoboss_license_api::persistence::{LicenseData, LicenseKey}; +use helgoboss_license_api::runtime::License; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::fmt::Debug; +use std::fs; +use std::path::PathBuf; +use std::rc::Rc; + +pub type SharedLicenseManager = Rc>; + +pub trait LicenseManagerEventHandler: Debug { + fn licenses_changed(&self, source: &LicenseManager); +} + +#[derive(Debug)] +pub struct LicenseManager { + licensing_file_path: PathBuf, + licensing: Licensing, + event_handler: Box, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +struct LicensingData { + license_keys: Vec, +} + +#[derive(Clone, Debug, Default)] +struct Licensing { + licenses: Vec, +} + +impl LicenseManager { + /// Creates a manager using the given licensing file. + /// + /// Immediately loads the licenses from the licensing file if it exists. + pub fn new( + licensing_file_path: PathBuf, + event_handler: Box, + ) -> Self { + let mut manager = Self { + licensing_file_path, + licensing: Default::default(), + event_handler, + }; + let _ = manager.load(); + manager + } + + pub fn licenses(&self) -> &[License] { + &self.licensing.licenses + } + + pub fn add_license(&mut self, key: LicenseKey) -> anyhow::Result<()> { + let license_data = LicenseData::try_from_key(&key) + .context("This doesn't look like a proper license key.")?; + let license = License::try_from(license_data).context("Couldn't decode license key.")?; + self.licensing.licenses.push(license); + self.save()?; + self.event_handler.licenses_changed(self); + Ok(()) + } + + fn load(&mut self) -> anyhow::Result<()> { + let json = fs::read_to_string(&self.licensing_file_path) + .with_context(|| "couldn't read licensing file")?; + let data: LicensingData = + serde_json::from_str(&json).with_context(|| "licensing file has wrong format")?; + self.licensing = data.into(); + Ok(()) + } + + fn save(&mut self) -> anyhow::Result<()> { + let data: LicensingData = self.licensing.clone().into(); + let json = serde_json::to_string_pretty(&data) + .with_context(|| "Couldn't serialize licensing data")?; + let parent_dir = self + .licensing_file_path + .parent() + .ok_or_else(|| anyhow!("Wrong licensing path"))?; + fs::create_dir_all(parent_dir) + .with_context(|| "Couldn't create licensing file parent directory")?; + fs::write(&self.licensing_file_path, json) + .with_context(|| "Couldn't write licensing file")?; + Ok(()) + } +} + +impl From for Licensing { + fn from(value: LicensingData) -> Self { + Self { + licenses: value + .license_keys + .into_iter() + .filter_map(|key| License::try_from(LicenseData::try_from_key(&key).ok()?).ok()) + .collect(), + } + } +} + +impl From for LicensingData { + fn from(value: Licensing) -> Self { + Self { + license_keys: value + .licenses + .into_iter() + .map(|l| LicenseData::from(l).to_key()) + .collect(), + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/mapping_model_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/mapping_model_data.rs new file mode 100644 index 0000000..038dcc0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/mapping_model_data.rs @@ -0,0 +1,260 @@ +use crate::application::{Change, MappingCommand, MappingModel}; +use crate::domain::{ + CompartmentKind, ExtendedProcessorContext, FeedbackSendBehavior, GroupId, GroupKey, MappingId, + MappingKey, Tag, +}; +use crate::infrastructure::data::{ + ActivationConditionData, DataToModelConversionContext, EnabledData, MigrationDescriptor, + ModeModelData, ModelToDataConversionContext, SourceModelData, TargetModelData, +}; +use base::default_util::{bool_true, deserialize_null_default, is_bool_true, is_default}; +use helgobox_api::persistence::SuccessAudioFeedback; +use semver::Version; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MappingModelData { + // Saved since ReaLearn 1.12.0, doesn't have to be a UUID since 2.11.0-pre.13 and corresponds + // to the model *key* instead! + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + // Saved only in some ReaLearn 2.11.0-pre-releases under "key". Later we persist this in "id" + // field again. So this is just for being compatible with those few pre-releases! + #[serde(alias = "key")] + pub id: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub name: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub tags: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub group_id: GroupKey, + pub source: SourceModelData, + pub mode: ModeModelData, + pub target: TargetModelData, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + pub is_enabled: bool, + #[serde(flatten)] + pub enabled_data: EnabledData, + #[serde(flatten)] + pub activation_condition_data: ActivationConditionData, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub prevent_echo_feedback: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub send_feedback_after_control: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub advanced: Option, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + pub visible_in_projection: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub success_audio_feedback: Option, +} + +impl MappingModelData { + pub fn from_model( + model: &MappingModel, + conversion_context: &impl ModelToDataConversionContext, + ) -> MappingModelData { + MappingModelData { + id: Some(model.key().clone()), + name: model.name().to_owned(), + tags: model.tags().to_owned(), + group_id: { + conversion_context + .group_key_by_id(model.group_id()) + .unwrap_or_default() + }, + source: SourceModelData::from_model(&model.source_model), + mode: ModeModelData::from_model(&model.mode_model), + target: TargetModelData::from_model(&model.target_model, conversion_context), + is_enabled: model.is_enabled(), + enabled_data: EnabledData { + control_is_enabled: model.control_is_enabled(), + feedback_is_enabled: model.feedback_is_enabled(), + }, + prevent_echo_feedback: model.feedback_send_behavior() + == FeedbackSendBehavior::PreventEchoFeedback, + send_feedback_after_control: model.feedback_send_behavior() + == FeedbackSendBehavior::SendFeedbackAfterControl, + activation_condition_data: ActivationConditionData::from_model( + model.activation_condition_model(), + conversion_context, + ), + advanced: model.advanced_settings().cloned(), + visible_in_projection: model.visible_in_projection(), + success_audio_feedback: if model.beep_on_success() { + Some(SuccessAudioFeedback::Simple) + } else { + None + }, + } + } + + pub fn to_model( + &self, + compartment: CompartmentKind, + conversion_context: &impl DataToModelConversionContext, + processor_context: Option, + version: Option<&Version>, + ) -> anyhow::Result { + self.to_model_flexible( + compartment, + &MigrationDescriptor::default(), + version, + conversion_context, + processor_context, + ) + } + + /// Use this for integrating the resulting model into a preset. + pub fn to_model_for_preset( + &self, + compartment: CompartmentKind, + migration_descriptor: &MigrationDescriptor, + preset_version: Option<&Version>, + conversion_context: &impl DataToModelConversionContext, + ) -> anyhow::Result { + self.to_model_flexible( + compartment, + migration_descriptor, + preset_version, + conversion_context, + // We don't need the context because additional track/FX properties don't + // need to be resolved when just creating a preset. + None, + ) + } + + /// The context - if available - will be used to resolve some track/FX properties for UI + /// convenience. The context is necessary if there's the possibility of loading data saved with + /// ReaLearn < 1.12.0. + pub fn to_model_flexible( + &self, + compartment: CompartmentKind, + migration_descriptor: &MigrationDescriptor, + preset_version: Option<&Version>, + conversion_context: &impl DataToModelConversionContext, + processor_context: Option, + ) -> anyhow::Result { + let (key, id) = if let Some(key) = self.id.clone() { + let id = conversion_context + .mapping_id_by_key(&key) + .unwrap_or_default(); + (key, id) + } else { + (MappingKey::random(), MappingId::random()) + }; + // Preliminary group ID + let mut model = MappingModel::new(compartment, GroupId::default(), key, id); + self.apply_to_model_internal( + migration_descriptor, + preset_version, + conversion_context, + processor_context, + &mut model, + )?; + Ok(model) + } + + /// This is for realtime mapping modification (with notification, no ID changes), e.g. for copy + /// & paste within one ReaLearn version. + pub fn apply_to_model( + &self, + model: &mut MappingModel, + conversion_context: &impl DataToModelConversionContext, + processor_context: Option, + version: Option<&Version>, + ) -> anyhow::Result<()> { + self.apply_to_model_internal( + &MigrationDescriptor::default(), + version, + conversion_context, + processor_context, + model, + ) + } + + /// The processor context - if available - will be used to resolve some track/FX properties for + /// UI convenience. The context is necessary if there's the possibility of loading data saved + /// with ReaLearn < 1.12.0. + fn apply_to_model_internal( + &self, + migration_descriptor: &MigrationDescriptor, + preset_version: Option<&Version>, + conversion_context: &impl DataToModelConversionContext, + processor_context: Option, + model: &mut MappingModel, + ) -> anyhow::Result<()> { + use MappingCommand as P; + model.change(P::SetName(self.name.clone())); + model.change(P::SetTags(self.tags.clone())); + let group_id = conversion_context + .group_id_by_key(&self.group_id) + .unwrap_or_default(); + model.change(P::SetGroupId(group_id)); + self.activation_condition_data + .apply_to_model(&mut model.activation_condition_model, conversion_context); + let compartment = model.compartment(); + self.source + .apply_to_model_flexible(&mut model.source_model, compartment, preset_version); + self.mode + .apply_to_model_flexible(&mut model.mode_model, migration_descriptor, &self.name); + self.target.apply_to_model_flexible( + &mut model.target_model, + processor_context, + preset_version, + compartment, + conversion_context, + migration_descriptor, + )?; + model.change(P::SetIsEnabled(self.is_enabled)); + model.change(P::SetControlIsEnabled(self.enabled_data.control_is_enabled)); + model.change(P::SetFeedbackIsEnabled( + self.enabled_data.feedback_is_enabled, + )); + let feedback_send_behavior = if self.prevent_echo_feedback { + // Took precedence if both checkboxes were ticked (was possible in ReaLearn < 2.10.0). + FeedbackSendBehavior::PreventEchoFeedback + } else if self.send_feedback_after_control { + FeedbackSendBehavior::SendFeedbackAfterControl + } else { + FeedbackSendBehavior::Normal + }; + model.change(P::SetFeedbackSendBehavior(feedback_send_behavior)); + let _ = model.set_advanced_settings(self.advanced.clone()); + model.change(P::SetVisibleInProjection(self.visible_in_projection)); + model.change(P::SetBeepOnSuccess(self.success_audio_feedback.is_some())); + Ok(()) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/migration.rs b/plugin-reaper-realearn/main/src/infrastructure/data/migration.rs new file mode 100644 index 0000000..9911cd4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/migration.rs @@ -0,0 +1,54 @@ +use semver::Version; + +/// The default of this struct is a no-op! +#[derive(Default)] +pub struct MigrationDescriptor { + /// Invert target interval of mapping when migrating from old version. + /// + /// https://github.com/helgoboss/helgobox/issues/117 + pub target_interval_transformation_117: bool, + /// If the FX selector was before the "Unit FX" concept was introduced, we + /// transform it to (and set the instance FX by default to ). + /// + /// https://github.com/helgoboss/helgobox/issues/188 + pub fx_selector_transformation_188: bool, + /// https://github.com/helgoboss/helgobox/issues/485 + pub jump_overhaul_485: bool, + /// https://github.com/helgoboss/helgobox/issues/761 + pub action_invocation_swap_761: bool, + /// https://github.com/helgoboss/helgobox/issues/928 + pub fire_mode_after_timeout_release_928: bool, +} + +impl MigrationDescriptor { + pub fn new(preset_version: Option<&Version>) -> MigrationDescriptor { + MigrationDescriptor { + // None means it's < 1.12.0-pre18. + target_interval_transformation_117: preset_version.is_none(), + fx_selector_transformation_188: if let Some(v) = preset_version { + let instance_fx_introduction_version = &Version::parse("2.13.0-pre.9").unwrap(); + v < instance_fx_introduction_version + } else { + false + }, + jump_overhaul_485: if let Some(v) = preset_version { + let jump_overhaul_version = &Version::parse("2.14.0-pre.10").unwrap(); + v < jump_overhaul_version + } else { + true + }, + action_invocation_swap_761: if let Some(v) = preset_version { + let absolute_7_bit_intro_version = &Version::parse("2.13.0-pre.1").unwrap(); + let swap_fix_version = &Version::parse("2.14.0-pre.11").unwrap(); + v >= absolute_7_bit_intro_version && v < swap_fix_version + } else { + false + }, + fire_mode_after_timeout_release_928: if let Some(v) = preset_version { + v < &Version::parse("2.14.0-pre.15").unwrap() + } else { + true + }, + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/data/mod.rs new file mode 100644 index 0000000..52fbbb6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/mod.rs @@ -0,0 +1,62 @@ +mod controller_manager; +pub use controller_manager::*; + +mod compartment_model_data; +pub use compartment_model_data::*; + +mod mapping_model_data; +pub use mapping_model_data::*; + +mod group_model_data; +pub use group_model_data::*; + +mod mode_model_data; +pub use mode_model_data::*; + +mod unit_data; +pub use unit_data::*; + +mod instance_data; +pub use instance_data::*; + +mod source_model_data; +pub use source_model_data::*; + +mod target_model_data; +pub use target_model_data::*; + +mod parameter_data; +pub use parameter_data::*; + +mod activation_condition_data; +pub use activation_condition_data::*; + +mod enabled_data; +pub use enabled_data::*; + +mod preset; +pub use preset::*; + +mod compartment_preset_data; +pub use compartment_preset_data::*; + +mod preset_link; +pub use preset_link::*; + +mod deserializers; +use deserializers::*; + +mod migration; +pub use migration::*; + +mod osc_device_management; +pub use osc_device_management::*; + +mod virtual_control; +pub use virtual_control::*; + +mod license_management; +pub use license_management::*; + +mod common; +pub use common::*; diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/mode_model_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/mode_model_data.rs new file mode 100644 index 0000000..8246d95 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/mode_model_data.rs @@ -0,0 +1,419 @@ +use crate::application::{Change, ModeCommand, ModeModel}; +use crate::infrastructure::data::MigrationDescriptor; +use base::default_util::{deserialize_null_default, is_default}; +use helgoboss_learn::{ + AbsoluteMode, ButtonUsage, DiscreteIncrement, EncoderUsage, FeedbackType, FireMode, + GroupInteraction, Interval, OutOfRangeBehavior, SoftSymmetricUnitValue, TakeoverMode, + UnitValue, ValueSequence, VirtualColor, +}; +use helgobox_api::persistence::FeedbackValueTable; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tracing::debug; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ModeModelData { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub r#type: AbsoluteMode, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub min_source_value: UnitValue, + #[serde(default = "unit_value_one", skip_serializing_if = "is_unit_value_one")] + pub max_source_value: UnitValue, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub min_target_value: UnitValue, + #[serde(default = "unit_value_one", skip_serializing_if = "is_unit_value_one")] + pub max_target_value: UnitValue, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub min_target_jump: UnitValue, + #[serde(default = "unit_value_one", skip_serializing_if = "is_unit_value_one")] + pub max_target_jump: UnitValue, + #[serde( + default = "default_step_size", + skip_serializing_if = "is_default_step_size" + )] + /// Step sizes in versions < 2.13.0-pre.10 can be negative because those fields have been + /// used to persist the step factor as well (which can be negative). That's why we still can't + /// use UnitValue here. + pub min_step_size: SoftSymmetricUnitValue, + #[serde( + default = "default_step_size", + skip_serializing_if = "is_default_step_size" + )] + pub max_step_size: SoftSymmetricUnitValue, + /// Step factors are persisted separately from step size since 2.13.0-pre.10 in order to + /// get rid of the annoying mismatch with ReaLearn script. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub min_step_factor: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub max_step_factor: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub min_press_millis: u64, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub max_press_millis: u64, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub turbo_rate: u64, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub eel_control_transformation: String, + /// Also used as text expression for text feedback + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub eel_feedback_transformation: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub reverse_is_enabled: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub feedback_color: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub feedback_background_color: Option, + // Serialization skipped because this is deprecated in favor of out_of_range_behavior + // since ReaLearn v1.11.0. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing + )] + pub ignore_out_of_range_source_values_is_enabled: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub out_of_range_behavior: OutOfRangeBehavior, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub fire_mode: FireMode, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub round_target_value: bool, + // Serialization skipped because this is deprecated in favor of takeover_mode + // since ReaLearn v2.8.0-pre3. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing + )] + pub scale_mode_enabled: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub takeover_mode: TakeoverMode, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub button_usage: ButtonUsage, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub encoder_usage: EncoderUsage, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub rotate_is_enabled: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub make_absolute_enabled: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub group_interaction: GroupInteraction, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub target_value_sequence: ValueSequence, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub feedback_type: FeedbackType, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub feedback_value_table: Option, +} + +fn default_step_size() -> SoftSymmetricUnitValue { + SoftSymmetricUnitValue::new(0.01) +} + +fn is_default_step_size(v: &SoftSymmetricUnitValue) -> bool { + *v == default_step_size() +} + +impl ModeModelData { + pub fn from_model(model: &ModeModel) -> Self { + Self { + r#type: model.absolute_mode(), + min_source_value: model.source_value_interval().min_val(), + max_source_value: model.source_value_interval().max_val(), + min_target_value: model.target_value_interval().min_val(), + max_target_value: model.target_value_interval().max_val(), + min_target_jump: model + .legacy_jump_interval() + .map(|i| i.min_val()) + .unwrap_or(UnitValue::MIN), + max_target_jump: model + .legacy_jump_interval() + .map(|i| i.max_val()) + .unwrap_or(UnitValue::MAX), + min_step_size: model.step_size_interval().min_val().to_symmetric(), + max_step_size: model.step_size_interval().max_val().to_symmetric(), + min_step_factor: Some(model.step_factor_interval().min_val()), + max_step_factor: Some(model.step_factor_interval().max_val()), + min_press_millis: model.press_duration_interval().min_val().as_millis() as _, + max_press_millis: model.press_duration_interval().max_val().as_millis() as _, + turbo_rate: model.turbo_rate().as_millis() as _, + eel_control_transformation: model.eel_control_transformation().to_owned(), + eel_feedback_transformation: if model.feedback_type().is_textual() { + model.textual_feedback_expression().to_owned() + } else { + model.eel_feedback_transformation().to_owned() + }, + feedback_color: model.feedback_color().cloned(), + feedback_background_color: model.feedback_background_color().cloned(), + reverse_is_enabled: model.reverse(), + // Not used anymore since ReaLearn v1.11.0 + ignore_out_of_range_source_values_is_enabled: false, + out_of_range_behavior: model.out_of_range_behavior(), + fire_mode: model.fire_mode(), + round_target_value: model.round_target_value(), + // Not used anymore since ReaLearn v2.8.0-pre3 + scale_mode_enabled: false, + takeover_mode: model.takeover_mode(), + button_usage: model.button_usage(), + encoder_usage: model.encoder_usage(), + rotate_is_enabled: model.rotate(), + make_absolute_enabled: model.make_absolute(), + group_interaction: model.group_interaction(), + target_value_sequence: model.target_value_sequence().clone(), + feedback_type: model.feedback_type(), + feedback_value_table: model.feedback_value_table().cloned(), + } + } + + pub fn apply_to_model(&self, model: &mut ModeModel) { + self.apply_to_model_flexible(model, &MigrationDescriptor::default(), ""); + } + + pub fn apply_to_model_flexible( + &self, + model: &mut ModeModel, + migration_descriptor: &MigrationDescriptor, + mapping_name: &str, + ) { + use ModeCommand as P; + model.change(P::SetAbsoluteMode(self.r#type)); + model.change(P::SetSourceValueInterval(Interval::new( + self.min_source_value, + self.max_source_value, + ))); + { + let saved_target_interval = Interval::new(self.min_target_value, self.max_target_value); + let actual_target_interval = if migration_descriptor.target_interval_transformation_117 + && self.reverse_is_enabled + && self.r#type == AbsoluteMode::Normal + { + debug!( + "Migration: Inverting target interval of mapping {} in order to not break existing behavior because of #117", + mapping_name + ); + saved_target_interval.inverse() + } else { + saved_target_interval + }; + model.change(P::SetTargetValueInterval(actual_target_interval)); + } + model.change(P::SetStepSizeInterval(Interval::new_auto( + self.min_step_size.abs(), + self.max_step_size.abs(), + ))); + if let (Some(min), Some(max)) = (self.min_step_factor, self.max_step_factor) { + // This must be ReaLearn >= 2.13.0-pre.10. + model.change(P::SetStepFactorInterval(Interval::new_auto(min, max))); + } + model.change(P::SetPressDurationInterval(Interval::new( + Duration::from_millis(self.min_press_millis), + Duration::from_millis(self.max_press_millis), + ))); + model.change(P::SetTurboRate(Duration::from_millis(self.turbo_rate))); + let has_custom_jump_interval = + self.min_target_jump.get() > 0.0 || self.max_target_jump.get() < 1.0; + let (legacy_jump_interval, takeover_mode) = if has_custom_jump_interval { + // We have a custom jump interval. + let interval = if self.takeover_mode == TakeoverMode::default() + && !migration_descriptor.jump_overhaul_485 + { + // However, we have the new "Normal". So jump interval doesn't make sense at all. + // Fix that. + None + } else { + // We really have a custom jump interval that could make a difference (not possible + // to create in GUI anymore). + Some(Interval::new(self.min_target_jump, self.max_target_jump)) + }; + let takeover_mode = if migration_descriptor.jump_overhaul_485 + && self.takeover_mode == TakeoverMode::default() + { + if self.scale_mode_enabled { + // ReaLearn < 2.8.0-pre3 used this flag instead of the enum. + TakeoverMode::LongTimeNoSee + } else { + // In a bit newer ReaLearn versions, pickup was the default takeover mode. + TakeoverMode::Pickup + } + } else { + self.takeover_mode + }; + (interval, takeover_mode) + } else { + // We don't have a custom jump interval. In older versions, this means there's no + // jump prevention at all. In newer versions, it depends on the takeover mode. + // In any case, we don't need to set a custom jump interval. + let takeover_mode = if migration_descriptor.jump_overhaul_485 { + // We have an old preset. In old presets, no takeover mode had any effect when + // the jump interval was the default. Make sure it remains that way by choosing + // the new "no-op" takeover mode "Normal". + TakeoverMode::Off + } else { + // We have a new preset. Set whatever takeover mode is stored. + // In new versions and if only using the GUI, we should only run into this branch! + self.takeover_mode + }; + (None, takeover_mode) + }; + model.change(P::SetLegacyJumpInterval(legacy_jump_interval)); + model.change(P::SetTakeoverMode(takeover_mode)); + model.change(P::SetEelControlTransformation( + self.eel_control_transformation.clone(), + )); + let (eel_fb_transformation, textual_fb_expression) = if self.feedback_type.is_textual() { + (String::new(), self.eel_feedback_transformation.clone()) + } else { + (self.eel_feedback_transformation.clone(), String::new()) + }; + model.change(P::SetEelFeedbackTransformation(eel_fb_transformation)); + model.change(P::SetTextualFeedbackExpression(textual_fb_expression)); + model.change(P::SetFeedbackColor(self.feedback_color.clone())); + model.change(P::SetFeedbackBackgroundColor( + self.feedback_background_color.clone(), + )); + model.change(P::SetReverse(self.reverse_is_enabled)); + let actual_out_of_range_behavior = if self.ignore_out_of_range_source_values_is_enabled { + // Data saved with ReaLearn version < 1.11.0 + OutOfRangeBehavior::Ignore + } else { + self.out_of_range_behavior + }; + model.change(P::SetFireMode(self.fire_mode)); + model.change(P::SetOutOfRangeBehavior(actual_out_of_range_behavior)); + model.change(P::SetRoundTargetValue(self.round_target_value)); + let button_usage = if migration_descriptor.fire_mode_after_timeout_release_928 { + if matches!( + self.fire_mode, + FireMode::AfterTimeout | FireMode::AfterTimeoutKeepFiring + ) { + ButtonUsage::PressOnly + } else { + self.button_usage + } + } else { + self.button_usage + }; + model.change(P::SetButtonUsage(button_usage)); + model.change(P::SetEncoderUsage(self.encoder_usage)); + model.change(P::SetRotate(self.rotate_is_enabled)); + model.change(P::SetMakeAbsolute(self.make_absolute_enabled)); + model.change(P::SetGroupInteraction(self.group_interaction)); + model.change(P::SetTargetValueSequence( + self.target_value_sequence.clone(), + )); + model.change(P::SetFeedbackType(self.feedback_type)); + model.change(P::SetFeedbackValueTable(self.feedback_value_table.clone())); + } +} + +fn is_unit_value_one(v: &UnitValue) -> bool { + *v == UnitValue::MAX +} + +fn unit_value_one() -> UnitValue { + UnitValue::MAX +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/osc_device_management.rs b/plugin-reaper-realearn/main/src/infrastructure/data/osc_device_management.rs new file mode 100644 index 0000000..e6ec4ee --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/osc_device_management.rs @@ -0,0 +1,352 @@ +use crate::base::AsyncNotifier; +use crate::domain::{OscDeviceId, OscInputDevice, OscOutputDevice}; +use base::default_util::{bool_true, deserialize_null_default, is_bool_true, is_default}; +use camino::Utf8PathBuf; +use derive_more::Display; +use rx_util::Notifier; +use rxrust::prelude::*; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::error::Error; +use std::fs; +use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket}; +use std::rc::Rc; +use strum::EnumIs; + +pub type SharedOscDeviceManager = Rc>; + +#[derive(Debug)] +pub struct OscDeviceManager { + config: OscDeviceConfig, + changed_subject: LocalSubject<'static, (), ()>, + osc_device_config_file_path: Utf8PathBuf, +} + +impl OscDeviceManager { + pub fn new(osc_device_config_file_path: Utf8PathBuf) -> OscDeviceManager { + OscDeviceManager { + config: Default::default(), + osc_device_config_file_path, + changed_subject: Default::default(), + } + } + + pub fn load_osc_devices_from_disk(&mut self) -> Result<(), String> { + let json = fs::read_to_string(&self.osc_device_config_file_path) + .map_err(|_| "couldn't read OSC device config file".to_string())?; + let config: OscDeviceConfig = serde_json::from_str(&json) + .map_err(|e| format!("OSC device config file isn't valid. Details:\n\n{e}"))?; + self.config = config; + Ok(()) + } + + fn save(&mut self) -> Result<(), String> { + fs::create_dir_all(self.osc_device_config_file_path.parent().unwrap()) + .map_err(|_| "couldn't create OSC device config file parent directory")?; + let json = serde_json::to_string_pretty(&self.config) + .map_err(|_| "couldn't serialize OSC device config")?; + fs::write(&self.osc_device_config_file_path, json) + .map_err(|_| "couldn't write OSC device config file")?; + Ok(()) + } + + pub fn devices(&self) -> impl ExactSizeIterator { + self.config.devices.iter() + } + + pub fn find_index_by_id(&self, id: &OscDeviceId) -> Option { + self.config.devices.iter().position(|dev| dev.id() == id) + } + + pub fn find_device_by_id(&self, id: &OscDeviceId) -> Option<&OscDevice> { + self.config.devices.iter().find(|dev| dev.id() == id) + } + + pub fn find_device_by_index(&self, index: usize) -> Option<&OscDevice> { + self.config.devices.get(index) + } + + pub fn connect_all_enabled_inputs_and_outputs( + &mut self, + ) -> (Vec, Vec) { + let (input_devs, output_devs): (Vec<_>, Vec<_>) = self + .config + .devices + .iter_mut() + .flat_map(|dev| dev.connect()) + .unzip(); + ( + input_devs.into_iter().flatten().collect(), + output_devs.into_iter().flatten().collect(), + ) + } + + pub fn changed(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.changed_subject.clone() + } + + pub fn add_device(&mut self, dev: OscDevice) -> Result<(), &'static str> { + self.config.devices.push(dev); + self.save_and_notify_changed()?; + Ok(()) + } + + pub fn update_device(&mut self, dev: OscDevice) -> Result<(), &'static str> { + let old_dev = self + .config + .devices + .iter_mut() + .find(|d| d.id() == dev.id()) + .ok_or("couldn't find OSC device")?; + let _ = std::mem::replace(old_dev, dev); + self.save_and_notify_changed()?; + Ok(()) + } + + pub fn remove_device_by_id(&mut self, dev_id: OscDeviceId) -> Result<(), &'static str> { + self.config.devices.retain(|dev| dev.id != dev_id); + self.save_and_notify_changed()?; + Ok(()) + } + + fn save_and_notify_changed(&mut self) -> Result<(), &'static str> { + self.save() + .map_err(|_| "error when saving OSC device configuration")?; + AsyncNotifier::notify(&mut self.changed_subject, &()); + Ok(()) + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct OscDeviceConfig { + #[serde(default)] + devices: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OscDevice { + id: OscDeviceId, + name: String, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + is_enabled_for_control: bool, + /// For receiving control messages. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + local_port: Option, + #[serde(skip)] + has_input_connection_problem: bool, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + is_enabled_for_feedback: bool, + /// For sending feedback messages. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + device_host: Option, + /// For sending feedback messages. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + device_port: Option, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + can_deal_with_bundles: bool, + #[serde(skip)] + has_output_connection_problem: bool, +} + +impl Default for OscDevice { + fn default() -> Self { + Self { + id: OscDeviceId::random(), + name: "".to_string(), + is_enabled_for_control: true, + is_enabled_for_feedback: true, + local_port: None, + device_host: None, + device_port: None, + can_deal_with_bundles: true, + has_input_connection_problem: false, + has_output_connection_problem: false, + } + } +} + +impl OscDevice { + pub fn connect( + &mut self, + ) -> Result<(Option, Option), Box> { + if !self.is_enabled_for_control && !self.is_enabled_for_feedback { + return Err("neither control nor feedback enabled".into()); + } + let ip = Ipv4Addr::UNSPECIFIED; + let bind_address = if self.is_enabled_for_control { + // Control. We need to bind to the defined local port. + SocketAddrV4::new(ip, self.local_port.ok_or("local port not specified")?) + } else { + // Feedback only. We don't care to which port to connect locally because we don't + // want to receive control messages. + SocketAddrV4::new(ip, 0) + }; + let socket = UdpSocket::bind(bind_address)?; + let input_dev = if self.is_enabled_for_control { + let result = self.connect_input_internal(socket.try_clone()?); + self.has_input_connection_problem = result.is_err(); + Some(result?) + } else { + None + }; + let output_dev = if self.is_enabled_for_feedback { + let result = self.connect_output_internal(socket); + self.has_output_connection_problem = result.is_err(); + Some(result?) + } else { + None + }; + Ok((input_dev, output_dev)) + } + + fn connect_input_internal(&self, socket: UdpSocket) -> Result> { + socket.set_nonblocking(true)?; + OscInputDevice::bind(self.id, socket) + } + + fn connect_output_internal( + &self, + socket: UdpSocket, + ) -> Result> { + let dest_addr = SocketAddrV4::new( + self.device_host.ok_or("device host not specified")?, + self.device_port.ok_or("local port not specified")?, + ); + let dev = OscOutputDevice::new(self.id, socket, dest_addr, self.can_deal_with_bundles); + Ok(dev) + } + + pub fn id(&self) -> &OscDeviceId { + &self.id + } + + fn is_configured_for_input(&self) -> bool { + self.local_port.is_some() + } + + fn is_configured_for_output(&self) -> bool { + self.device_host.is_some() && self.device_port.is_some() + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn local_port(&self) -> Option { + self.local_port + } + + pub fn device_host(&self) -> Option { + self.device_host + } + + pub fn device_port(&self) -> Option { + self.device_port + } + + pub fn is_enabled_for_control(&self) -> bool { + self.is_enabled_for_control + } + + pub fn is_enabled_for_feedback(&self) -> bool { + self.is_enabled_for_feedback + } + + pub fn can_deal_with_bundles(&self) -> bool { + self.can_deal_with_bundles + } + + pub fn input_status(&self) -> OscDeviceStatus { + use OscDeviceStatus::*; + if !self.is_configured_for_input() { + return Incomplete; + } + if !self.is_enabled_for_control { + return Disabled; + } + if self.has_input_connection_problem { + return UnableToBind; + } + Connected + } + + pub fn output_status(&self) -> OscDeviceStatus { + use OscDeviceStatus::*; + if !self.is_configured_for_output() { + return Incomplete; + } + if !self.is_enabled_for_feedback { + return Disabled; + } + if self.has_output_connection_problem { + return UnableToBind; + } + Connected + } + + pub fn set_name(&mut self, name: String) { + self.name = name; + } + + pub fn set_local_port(&mut self, local_port: Option) { + self.local_port = local_port; + } + + pub fn set_device_host(&mut self, device_host: Option) { + self.device_host = device_host; + } + + pub fn set_device_port(&mut self, device_port: Option) { + self.device_port = device_port; + } + + pub fn toggle_control(&mut self) { + self.is_enabled_for_control = !self.is_enabled_for_control; + } + + pub fn toggle_feedback(&mut self) { + self.is_enabled_for_feedback = !self.is_enabled_for_feedback; + } + + pub fn toggle_can_deal_with_bundles(&mut self) { + self.can_deal_with_bundles = !self.can_deal_with_bundles; + } + + pub fn get_list_label(&self, is_output: bool) -> String { + format!( + "{}{}", + self.name(), + if is_output { + self.output_status() + } else { + self.input_status() + } + ) + } +} + +#[derive(Display, EnumIs)] +pub enum OscDeviceStatus { + #[display(fmt = " ")] + Incomplete, + #[display(fmt = " ")] + Disabled, + #[display(fmt = " ")] + UnableToBind, + #[display(fmt = "")] + Connected, +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/parameter_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/parameter_data.rs new file mode 100644 index 0000000..c1e64d3 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/parameter_data.rs @@ -0,0 +1,16 @@ +use crate::domain::ParamSetting; +use base::default_util::{deserialize_null_default, is_default}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ParameterData { + #[serde(flatten)] + pub setting: ParamSetting, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub value: f32, +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/preset.rs b/plugin-reaper-realearn/main/src/infrastructure/data/preset.rs new file mode 100644 index 0000000..9355cfe --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/preset.rs @@ -0,0 +1,742 @@ +use crate::application::{CompartmentPresetManager, CompartmentPresetModel}; + +use crate::base::notification; +use crate::base::notification::{warn_user_about_anyhow_error, warn_user_on_anyhow_error}; +use crate::domain::{ + CompartmentKind, FsDirLuaModuleFinder, IncludedDirLuaModuleFinder, LuaModuleContainer, + LuaModuleFinder, SafeLua, +}; +use crate::infrastructure::api::convert::to_data::convert_compartment; +use crate::infrastructure::data::CompartmentPresetData; +use crate::infrastructure::plugin::{midi_output_port_patterns_match, BackboneShell}; +use anyhow::{anyhow, bail, Context}; +use base::byte_pattern::BytePattern; +use base::file_util; +use base::file_util::is_hidden; +use helgobox_api::persistence::{ + CommonPresetMetaData, ControllerPresetMetaData, MainPresetMetaData, VirtualControlSchemeId, +}; +use include_dir::{include_dir, Dir}; +use itertools::Itertools; +use mlua::LuaSerdeExt; +use reaper_high::Reaper; +use rxrust::prelude::*; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::fmt::Formatter; +use std::path::{Path, PathBuf}; +use std::rc::Rc; +use std::str::FromStr; + +use camino::{Utf8Path, Utf8PathBuf}; +use nanoid::nanoid; +use slug::slugify; +use std::{fmt, fs}; +use strum::EnumIs; +use walkdir::WalkDir; + +pub type SharedControllerPresetManager = Rc>; +pub type FileBasedControllerPresetManager = + FileBasedCompartmentPresetManager; + +pub type SharedMainPresetManager = Rc>; +pub type FileBasedMainPresetManager = FileBasedCompartmentPresetManager; + +#[derive(Debug)] +pub struct FileBasedCompartmentPresetManager { + compartment: CompartmentKind, + preset_dir_path: Utf8PathBuf, + preset_infos: Vec>, + changed_subject: LocalSubject<'static, (), ()>, + event_handler: Box>, +} + +impl FileBasedCompartmentPresetManager { + pub fn find_controller_preset_compatible_with_device( + &self, + midi_identity_reply: &[u8], + midi_output_port_name: &str, + ) -> Option<&PresetInfo> { + self.preset_infos.iter().find(|info| { + // Check device identity + let Some(midi_identity_pattern) = + info.specific_meta_data.midi_identity_pattern.as_ref() + else { + return false; + }; + let Ok(byte_pattern) = BytePattern::from_str(midi_identity_pattern) else { + return false; + }; + let identity_matches = byte_pattern.matches(midi_identity_reply); + if !identity_matches { + return false; + } + // Additionally check device identity + midi_output_port_patterns_match( + &info.specific_meta_data.midi_output_port_patterns, + midi_output_port_name, + ) + }) + } +} + +#[derive(Copy, Clone)] +pub struct MainPresetSelectionConditions { + pub at_least_one_instance_has_playtime_clip_matrix: bool, +} + +impl FileBasedCompartmentPresetManager { + pub fn find_most_suitable_main_preset_for_schemes( + &self, + provided_schemes: &[VirtualControlSchemeId], + conditions: MainPresetSelectionConditions, + ) -> Option<&PresetInfo> { + // Find main preset candidates and calculate their individual "matching" rank + let mut candidates: Vec<_> = self + .preset_infos + .iter() + .filter_map(|preset| { + let rank = MainPresetRank::calculate(&preset.specific_meta_data, provided_schemes)?; + Some(MainPresetCandidate { preset, rank }) + }) + .collect(); + // Sort ascending by suitability + candidates.sort_unstable_by(|candidate_a, candidate_b| { + // Prefer Playtime preset if at least one instance has a Playtime matrix + if conditions.at_least_one_instance_has_playtime_clip_matrix { + let candidate_a_requires_playtime = + candidate_a.preset.specific_meta_data.requires_playtime(); + let candidate_b_requires_playtime = + &candidate_b.preset.specific_meta_data.requires_playtime(); + let ord = candidate_a_requires_playtime.cmp(candidate_b_requires_playtime); + if ord.is_ne() { + return ord; + } + } + // Apart from that, prefer higher rank + candidate_a.rank.cmp(&candidate_b.rank) + }); + // Now choose *from the right* (the maximum) + Some(candidates.pop()?.preset) + } +} + +#[derive(Debug)] +struct MainPresetCandidate<'a> { + preset: &'a PresetInfo, + rank: MainPresetRank, +} + +#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)] +struct MainPresetRank { + specificity: u8, + coverage: u8, +} + +impl MainPresetRank { + fn calculate( + main_preset: &MainPresetMetaData, + provided_schemes: &[VirtualControlSchemeId], + ) -> Option { + let coverage = main_preset.calc_scheme_coverage(provided_schemes); + if coverage == 0 { + return None; + } + let rank = Self { + specificity: main_preset.calc_scheme_specificity(provided_schemes)?, + coverage, + }; + Some(rank) + } +} + +pub trait CompartmentPresetManagerEventHandler: fmt::Debug { + type Source; + + fn presets_changed(&self, source: &Self::Source); +} + +pub trait CommonCompartmentPresetManager { + fn remove_preset(&mut self, id: &str) -> anyhow::Result<()>; + fn common_preset_infos(&self) -> Box + '_>; + fn common_preset_info_by_id(&self, id: &str) -> Option<&CommonPresetInfo>; + fn export_preset_workspace( + &mut self, + include_factory_presets: bool, + ) -> anyhow::Result; +} + +pub struct PresetWorkspaceDescriptor { + pub name: String, + pub dir: Utf8PathBuf, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct PresetInfo { + pub common: CommonPresetInfo, + pub specific_meta_data: S, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct CommonPresetInfo { + pub id: String, + /// Path relative to the namespace root including extension. + pub normalized_relative_path: String, + pub file_type: PresetFileType, + pub origin: PresetOrigin, + pub meta_data: CommonPresetMetaData, +} + +#[derive(Clone, Eq, PartialEq, Debug, EnumIs)] +pub enum PresetOrigin { + User { + /// The ID should actually be equal to the path, but to not run into any + /// breaking change in edge cases, we keep track of the actual path here. + absolute_file_path: PathBuf, + }, + Factory { + compartment: CompartmentKind, + relative_file_path: PathBuf, + }, +} + +impl fmt::Display for PresetOrigin { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PresetOrigin::User { + absolute_file_path: absolute_path, + } => absolute_path.display().fmt(f), + PresetOrigin::Factory { + compartment, + relative_file_path, + } => { + let compartment_id = match compartment { + CompartmentKind::Controller => "controller", + CompartmentKind::Main => "main", + }; + write!( + f, + "factory:/{compartment_id}/{}", + relative_file_path.display() + ) + } + } + } +} + +struct PresetBasics { + /// Path relative to the **preset** root, without extension. + id: String, + /// Path relative to the **preset namespace** root, including extension. + relative_path_within_namespace: String, + file_type: PresetFileType, +} + +impl PresetBasics { + pub fn from_relative_path(relative_file_path: &Path, prefix: &str) -> Option { + let file_name = relative_file_path.file_name()?.to_str()?; + let file_type_mappings = [ + (".json", PresetFileType::Json), + (".preset.luau", PresetFileType::Lua), + ]; + let (id_leaf, file_type) = + file_type_mappings + .into_iter() + .find_map(|(suffix, file_type)| { + let id_leaf = file_name.strip_suffix(suffix)?; + Some((id_leaf, file_type)) + })?; + let relative_dir_path = relative_file_path.parent()?; + let id = if relative_dir_path.parent().is_none() { + // Preset is in root + format!("{prefix}{id_leaf}") + } else { + // Preset is in sub directory + let relative_dir_path_with_slashes = + relative_dir_path.to_string_lossy().replace('\\', "/"); + format!("{prefix}{relative_dir_path_with_slashes}/{id_leaf}") + }; + let relative_path_within_namespace: PathBuf = + relative_file_path.components().skip(1).collect(); + let basics = Self { + id, + relative_path_within_namespace: relative_path_within_namespace + .to_string_lossy() + .to_string(), + file_type, + }; + Some(basics) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum PresetFileType { + Json, + Lua, +} + +#[derive(Deserialize)] +struct CombinedPresetMetaData { + #[serde(flatten)] + common: CommonPresetMetaData, + #[serde(flatten)] + specific: S, +} + +pub trait SpecificPresetMetaData: fmt::Debug + for<'a> Deserialize<'a> {} + +impl SpecificPresetMetaData for ControllerPresetMetaData {} + +impl SpecificPresetMetaData for MainPresetMetaData {} + +impl CombinedPresetMetaData { + pub fn from_json(json: &str) -> anyhow::Result { + Ok(serde_json::from_str(json)?) + } + + pub fn from_lua_code(lua_code: &str) -> anyhow::Result { + parse_lua_frontmatter(lua_code) + } +} + +impl FileBasedCompartmentPresetManager { + pub fn new( + compartment: CompartmentKind, + preset_dir_path: Utf8PathBuf, + event_handler: Box>, + ) -> FileBasedCompartmentPresetManager { + FileBasedCompartmentPresetManager { + compartment, + preset_dir_path, + preset_infos: vec![], + changed_subject: Default::default(), + event_handler, + } + } + + pub fn load_presets_from_disk(&mut self) -> Result<(), String> { + self.load_presets_from_disk_without_notification()?; + self.notify_presets_changed(); + Ok(()) + } + + pub fn load_presets_from_disk_without_notification(&mut self) -> Result<(), String> { + // Load factory preset infos + let compartment = self.compartment; + let factory_preset_dir = get_factory_preset_dir(compartment); + let mut all_preset_infos = vec![]; + walk_included_dir(factory_preset_dir, false, &mut |file| { + let relative_file_path = file.path(); + let Some(basics) = PresetBasics::from_relative_path(relative_file_path, "factory/") + else { + return Ok(()); + }; + let file_content = file + .contents_utf8() + .context("Factory preset not UTF-8 encoded")?; + let origin = PresetOrigin::Factory { + compartment, + relative_file_path: relative_file_path.to_path_buf(), + }; + let preset_info = load_preset_info(origin, basics, file_content)?; + all_preset_infos.push(preset_info); + Ok(()) + }); + // Load user preset infos + let user_preset_infos = WalkDir::new(&self.preset_dir_path) + .follow_links(true) + .max_depth(4) + .into_iter() + .filter_entry(|e| !file_util::is_hidden(e.file_name())) + .filter_map(|entry| { + let entry = entry.ok()?; + if !entry.file_type().is_file() { + return None; + } + let relative_file_path = entry + .path() + .strip_prefix(&self.preset_dir_path) + .unwrap() + .to_path_buf(); + if relative_file_path.iter().next().is_some_and(|component| { + component.to_string_lossy().eq_ignore_ascii_case("factory") + }) { + // User presets must not get mixed up with factory presets. Most importantly, + // it's not allowed to override factory presets. That could create a mess. + return None; + } + let basics = PresetBasics::from_relative_path(&relative_file_path, "")?; + match self.build_user_preset_info(entry.into_path(), basics) { + Ok(p) => Some(p), + Err(e) => { + notification::warn(e.to_string()); + None + } + } + }); + // Combine them + all_preset_infos.extend(user_preset_infos); + self.preset_infos = all_preset_infos; + Ok(()) + } + + fn build_user_preset_info( + &mut self, + absolute_file_path: PathBuf, + basics: PresetBasics, + ) -> anyhow::Result> { + let origin = PresetOrigin::User { + absolute_file_path: absolute_file_path.clone(), + }; + let file_content = fs::read_to_string(&absolute_file_path).map_err(|_| { + anyhow!( + "Couldn't read preset file \"{}\".", + absolute_file_path.display() + ) + })?; + load_preset_info(origin, basics, &file_content) + } + + pub fn add_preset(&mut self, preset: CompartmentPresetModel) -> anyhow::Result<()> { + let path = self.preset_dir_path.join(format!("{}.json", preset.id())); + fs::create_dir_all(path.parent().context("impossible")?) + .context("couldn't create preset directory")?; + let mut data = CompartmentPresetData::from_model(&preset); + // We don't want to have the ID in the file - because the file name itself is the ID + data.clear_id(); + let json = serde_json::to_string_pretty(&data).context("couldn't serialize preset")?; + fs::write(path, json).context("couldn't write preset file")?; + let _ = self.load_presets_from_disk(); + Ok(()) + } + + pub fn update_preset(&mut self, preset: CompartmentPresetModel) -> anyhow::Result<()> { + self.add_preset(preset) + } + + pub fn changed(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.changed_subject.clone() + } + + pub fn log_debug_info(&self) { + let msg = format!( + "\n\ + # Preset manager\n\ + \n\ + - Preset count: {}\n\ + ", + self.preset_infos.len(), + ); + Reaper::get().show_console_msg(msg); + } + + pub fn find_preset_info_by_id(&self, id: &str) -> Option<&PresetInfo> { + self.preset_infos.iter().find(|info| info.common.id == id) + } + + pub fn preset_infos(&self) -> &[PresetInfo] { + &self.preset_infos + } + + fn notify_presets_changed(&mut self) { + self.event_handler.presets_changed(self); + self.changed_subject.next(()); + } + + fn load_full_preset( + &self, + preset_info: &PresetInfo, + ) -> anyhow::Result { + let file_content: Cow = match &preset_info.common.origin { + PresetOrigin::User { absolute_file_path } => fs::read_to_string(absolute_file_path) + .map_err(|_| { + anyhow!( + "Couldn't read preset file \"{}\" anymore.", + &preset_info.common.origin + ) + })? + .into(), + PresetOrigin::Factory { + compartment, + relative_file_path, + } => get_factory_preset_content(*compartment, relative_file_path)?.into(), + }; + match preset_info.common.file_type { + PresetFileType::Json => { + let data: CompartmentPresetData = + serde_json::from_str(&file_content).map_err(|e| { + anyhow!( + "Preset file {} isn't valid anymore. Details:\n\n{}", + &preset_info.common.origin, + e + ) + })?; + data.to_model(preset_info.common.id.clone(), self.compartment) + } + PresetFileType::Lua => { + let lua = SafeLua::new()?; + let script_name = preset_info.common.origin.to_string(); + let origin = &preset_info.common.origin; + let module_finder: Result, _> = match origin { + PresetOrigin::User { + absolute_file_path: _, + } => { + let relative_path = Utf8Path::new(&preset_info.common.id); + let mut components = relative_path.components(); + let first_component = components + .next() + .expect("user preset with empty path shouldn't happen here"); + if components.next().is_some() { + // That means the user preset is in a subdirectory of the preset folder. + // This is our root for resolving Lua modules (the subdirectory serves as namespace). + let module_root = self.preset_dir_path.join(first_component); + Ok(Rc::new(FsDirLuaModuleFinder::new(module_root))) + } else { + // The preset resides in the root of the preset folder. This is discouraged nowadays + // because it makes sharing presets more difficult (conflicting file names etc.). + // That's why we don't allow using require in this case! + Err( + r#"Using "require" in Lua presets is only supported if they are located in a subfolder of the main or controller preset folder."#, + ) + } + } + PresetOrigin::Factory { compartment, .. } => { + let module_root = get_factory_preset_dir(*compartment).clone(); + Ok(Rc::new(IncludedDirLuaModuleFinder::new(module_root))) + } + }; + lua.start_execution_time_limit_countdown(); + let module_container = LuaModuleContainer::new(module_finder); + let value = module_container.execute_as_module( + lua.as_ref(), + Some(preset_info.common.normalized_relative_path.clone()), + script_name, + &file_content, + )?; + let compartment_content: helgobox_api::persistence::Compartment = + lua.as_ref().from_value(value)?; + let compartment_data = convert_compartment(self.compartment, compartment_content)?; + let mut compartment_model = compartment_data.to_model( + preset_info.common.meta_data.realearn_version.as_ref(), + self.compartment, + None, + )?; + // Write some descriptive preset meta data into the compartment notes, if the compartment + // model doesn't contain any notes itself already. + if compartment_model.notes.is_empty() { + let meta_data = &preset_info.common.meta_data; + let notes = &mut compartment_model.notes; + if let Some(text) = &meta_data.description { + notes.push_str("## Preset description\n\n"); + notes.push_str(text); + } + if let Some(text) = &meta_data.setup_instructions { + notes.push_str("\n\n## Preset setup instructions\n\n"); + notes.push_str(text); + } + } + let preset_model = CompartmentPresetModel::new( + preset_info.common.id.to_string(), + preset_info.common.meta_data.name.to_string(), + self.compartment, + compartment_model, + ); + Ok(preset_model) + } + } + } +} + +fn get_factory_preset_content( + compartment: CompartmentKind, + relative_file_path: &Path, +) -> anyhow::Result<&'static str> { + let factory_preset_dir = get_factory_preset_dir(compartment); + let file = factory_preset_dir + .get_file(relative_file_path) + .context("Couldn't find factory preset anymore")?; + file.contents_utf8() + .context("Factory preset not UTF-8 anymore!?") +} + +impl CommonCompartmentPresetManager + for FileBasedCompartmentPresetManager +{ + fn remove_preset(&mut self, id: &str) -> anyhow::Result<()> { + let preset_info = self + .preset_infos + .iter() + .find(|info| info.common.id == id) + .context("preset to be removed not found")?; + let PresetOrigin::User { + absolute_file_path: absolute_path, + } = &preset_info.common.origin + else { + bail!("can't delete factory presets"); + }; + fs::remove_file(absolute_path).context("couldn't delete preset file")?; + let _ = self.load_presets_from_disk(); + Ok(()) + } + + fn common_preset_infos(&self) -> Box + '_> { + Box::new(self.preset_infos.iter().map(|info| &info.common)) + } + + fn common_preset_info_by_id(&self, id: &str) -> Option<&CommonPresetInfo> { + self.preset_infos + .iter() + .find(|info| info.common.id == id) + .map(|info| &info.common) + } + + fn export_preset_workspace( + &mut self, + include_factory_presets: bool, + ) -> anyhow::Result { + let workspace_name = slugify(nanoid!(10)); + let workspace_dir = self.preset_dir_path.join(&workspace_name); + let factory_dir = get_factory_preset_dir(self.compartment); + walk_included_dir(factory_dir, true, &mut |file| { + if !include_factory_presets + && file.path().components().count() > 1 + && file + .path() + .components() + .next() + .is_some_and(|c| c.as_os_str() != ".vscode") + { + return Ok(()); + } + let utf8_path = Utf8Path::from_path(file.path()).context("non-UTF-8 impossible")?; + let abs_path = workspace_dir.join(utf8_path); + fs::create_dir_all(abs_path.parent().context("non-parent impossible")?)?; + fs::write(abs_path, file.contents())?; + Ok(()) + }); + let _ = self.load_presets_from_disk(); + let desc = PresetWorkspaceDescriptor { + name: workspace_name, + dir: workspace_dir, + }; + Ok(desc) + } +} + +impl CompartmentPresetManager for FileBasedCompartmentPresetManager { + fn find_by_id(&self, id: &str) -> Option { + let preset_info = self.preset_infos.iter().find(|info| info.common.id == id)?; + match self.load_full_preset(preset_info) { + Ok(p) => Some(p), + Err(e) => { + warn_user_about_anyhow_error(e); + None + } + } + } +} + +impl CompartmentPresetManager for Rc> { + fn find_by_id(&self, id: &str) -> Option { + self.borrow().find_by_id(id) + } +} + +pub static FACTORY_CONTROLLER_PRESETS_DIR: Dir<'_> = + include_dir!("$CARGO_MANIFEST_DIR/../resources/controller-presets/factory"); +pub static FACTORY_MAIN_PRESETS_DIR: Dir<'_> = + include_dir!("$CARGO_MANIFEST_DIR/../resources/main-presets/factory"); + +fn walk_included_dir( + dir: &Dir, + include_hidden: bool, + on_file: &mut impl FnMut(&include_dir::File) -> anyhow::Result<()>, +) { + use include_dir::DirEntry; + for entry in dir.entries() { + match entry { + DirEntry::Dir(dir) => { + if !include_hidden && is_hidden(dir.path().file_name().unwrap()) { + // E.g. useful to prevent walking into .vscode folders + continue; + } + walk_included_dir(dir, include_hidden, on_file); + } + DirEntry::File(file) => { + if !include_hidden && is_hidden(file.path().file_name().unwrap()) { + continue; + } + warn_user_on_anyhow_error(on_file(file)); + } + } + } +} + +fn load_preset_info( + origin: PresetOrigin, + basics: PresetBasics, + file_content: &str, +) -> anyhow::Result> { + let preset_meta_data_result = match basics.file_type { + PresetFileType::Json => CombinedPresetMetaData::from_json(file_content), + PresetFileType::Lua => CombinedPresetMetaData::from_lua_code(file_content), + }; + let preset_meta_data = preset_meta_data_result.map_err(|e| { + anyhow!("Couldn't read preset meta data from \"{origin}\". Details:\n\n{e}",) + })?; + if let Some(v) = preset_meta_data.common.realearn_version.as_ref() { + if BackboneShell::version() < v { + bail!( + "Skipped loading of preset \"{origin}\" because it has been created with \ + ReaLearn {v}, which is newer than the installed version {}. \ + Please update your ReaLearn version.", + BackboneShell::version() + ); + } + } + let preset_info = PresetInfo { + common: CommonPresetInfo { + id: basics.id, + normalized_relative_path: basics.relative_path_within_namespace, + file_type: basics.file_type, + origin, + meta_data: preset_meta_data.common, + }, + specific_meta_data: preset_meta_data.specific, + }; + Ok(preset_info) +} + +pub fn get_factory_preset_dir(compartment: CompartmentKind) -> &'static Dir<'static> { + match compartment { + CompartmentKind::Controller => &FACTORY_CONTROLLER_PRESETS_DIR, + CompartmentKind::Main => &FACTORY_MAIN_PRESETS_DIR, + } +} + +pub fn parse_lua_frontmatter Deserialize<'a>>(lua_code: &str) -> anyhow::Result { + const PREFIX: &str = "--- "; + let frontmatter = lua_code + .lines() + .map_while(|line| { + if let Some(yaml_line) = line.strip_prefix(PREFIX) { + // Valid non-empty YAML frontmatter line + Some(yaml_line) + } else if line.starts_with("---") { + // Valid empty SYAML frontmatter line + Some("") + } else { + // End of frontmatter + None + } + }) + .join("\n"); + if frontmatter.is_empty() { + bail!("Lua presets need at least a \"{PREFIX} name: ...\" line at the very top!"); + } + let value: T = serde_yaml::from_str(&frontmatter).map_err(|e| { + anyhow!("Error while parsing Lua preset frontmatter:\n\n{e}\n\nFrontmatter was:\n===\n{frontmatter}\n===\n") + })?; + Ok(value) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/preset_link.rs b/plugin-reaper-realearn/main/src/infrastructure/data/preset_link.rs new file mode 100644 index 0000000..68d0e5b --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/preset_link.rs @@ -0,0 +1,71 @@ +use crate::application::{FxId, FxPresetLinkConfig, PresetLinkManager, PresetLinkMutator}; +use camino::Utf8PathBuf; +use std::cell::RefCell; +use std::fs; +use std::rc::Rc; + +pub type SharedPresetLinkManager = Rc>; + +#[derive(Debug)] +pub struct FileBasedPresetLinkManager { + auto_load_configs_dir_path: Utf8PathBuf, + config: FxPresetLinkConfig, +} + +impl FileBasedPresetLinkManager { + pub fn new(auto_load_configs_dir_path: Utf8PathBuf) -> FileBasedPresetLinkManager { + FileBasedPresetLinkManager { + auto_load_configs_dir_path, + config: Default::default(), + } + } + + pub fn config(&self) -> &FxPresetLinkConfig { + &self.config + } + + fn fx_config_file_path(&self) -> Utf8PathBuf { + self.auto_load_configs_dir_path.join("fx.json") + } + + pub fn load_preset_links_from_disk(&mut self) -> Result<(), String> { + let json = fs::read_to_string(self.fx_config_file_path()) + .map_err(|_| "couldn't read FX preset link config file".to_string())?; + self.config = serde_json::from_str(&json) + .map_err(|e| format!("FX preset link config file isn't valid. Details:\n\n{e}"))?; + Ok(()) + } + + fn save_fx_config(&self) -> Result<(), String> { + fs::create_dir_all(&self.auto_load_configs_dir_path) + .map_err(|_| "couldn't create auto-load-configs directory")?; + let json = serde_json::to_string_pretty(&self.config) + .map_err(|_| "couldn't serialize FX preset link config")?; + fs::write(self.fx_config_file_path(), json) + .map_err(|_| "couldn't write FX preset link config file")?; + Ok(()) + } +} + +impl PresetLinkManager for SharedPresetLinkManager { + fn find_preset_linked_to_fx(&self, fx_id: &FxId) -> Option { + self.borrow().config().find_preset_linked_to_fx(fx_id) + } +} + +impl PresetLinkMutator for FileBasedPresetLinkManager { + fn update_fx_id(&mut self, old_fx_id: FxId, new_fx_id: FxId) { + self.config.update_fx_id(old_fx_id, new_fx_id); + self.save_fx_config().unwrap(); + } + + fn remove_link(&mut self, fx_id: &FxId) { + self.config.remove_link(fx_id); + self.save_fx_config().unwrap(); + } + + fn link_preset_to_fx(&mut self, preset_id: String, fx_id: FxId) { + self.config.link_preset_to_fx(preset_id, fx_id); + self.save_fx_config().unwrap(); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/source_model_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/source_model_data.rs new file mode 100644 index 0000000..254f030 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/source_model_data.rs @@ -0,0 +1,545 @@ +use super::none_if_minus_one; +use crate::application::{ + Change, MidiSourceType, ReaperSourceType, SourceCategory, SourceCommand, SourceModel, +}; +use crate::base::notification; +use crate::domain::{CompartmentKind, CompartmentParamIndex, Keystroke}; +use crate::infrastructure::data::common::OscValueRange; +use crate::infrastructure::data::VirtualControlElementIdData; +use base::default_util::{deserialize_null_default, is_default}; +use helgoboss_learn::{DisplayType, MidiClockTransportMessage, OscTypeTag, SourceCharacter}; +use helgoboss_midi::{Channel, U14, U7}; +use helgobox_api::persistence::{ + MidiScriptKind, StreamDeckButtonBackground, StreamDeckButtonDesign, StreamDeckButtonForeground, + VirtualControlElementCharacter, +}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; + +/// This is the structure in which source settings are loaded and saved. It's optimized for being +/// represented as JSON. The JSON representation must be 100% backward-compatible. +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SourceModelData { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub category: SourceCategory, + // MIDI + // midi_type would be a better name but we need backwards compatibility + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub r#type: MidiSourceType, + #[serde( + deserialize_with = "none_if_minus_one", + default, + skip_serializing_if = "is_default" + )] + pub channel: Option, + #[serde( + deserialize_with = "none_if_minus_one", + default, + skip_serializing_if = "is_default" + )] + pub number: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub character: SourceCharacter, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub is_registered: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub is_14_bit: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub message: MidiClockTransportMessage, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub raw_midi_pattern: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub midi_script_kind: MidiScriptKind, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub midi_script: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub display_type: DisplayType, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub display_id: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub line: Option, + // OSC + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_address_pattern: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_arg_index: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_arg_type: OscTypeTag, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_arg_is_relative: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_arg_value_range: OscValueRange, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_feedback_args: Vec, + // Keyboard + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub keystroke: Option, + // StreamDeck + #[serde(default)] + pub button_index: u32, + #[serde(default)] + pub button_design: StreamDeckButtonDesign, + // Virtual + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub control_element_type: VirtualControlElementCharacter, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub control_element_index: VirtualControlElementIdData, + // REAPER + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub reaper_source_type: ReaperSourceType, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub timer_millis: u64, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub parameter_index: CompartmentParamIndex, +} + +impl SourceModelData { + pub fn from_model(model: &SourceModel) -> Self { + Self { + category: model.category(), + r#type: model.midi_source_type(), + channel: model.channel(), + number: if model.midi_source_type() == MidiSourceType::ParameterNumberValue { + model.parameter_number_message_number() + } else { + model.midi_message_number().map(|n| n.into()) + }, + character: model.custom_character(), + is_registered: model.is_registered(), + is_14_bit: model.is_14_bit(), + message: model.midi_clock_transport_message(), + raw_midi_pattern: model.raw_midi_pattern().to_owned(), + midi_script_kind: model.midi_script_kind(), + midi_script: model.midi_script().to_owned(), + display_type: model.display_type(), + display_id: model.display_id(), + line: model.line(), + osc_address_pattern: model.osc_address_pattern().to_owned(), + osc_arg_index: model.osc_arg_index(), + osc_arg_type: model.osc_arg_type_tag(), + osc_arg_is_relative: model.osc_arg_is_relative(), + osc_arg_value_range: OscValueRange::from_interval(model.osc_arg_value_range()), + osc_feedback_args: model.osc_feedback_args().to_vec(), + keystroke: model.keystroke(), + button_index: model.button_index(), + button_design: model.create_stream_deck_button_design(), + control_element_type: model.control_element_character(), + control_element_index: VirtualControlElementIdData::from_model( + model.control_element_id(), + ), + reaper_source_type: model.reaper_source_type(), + timer_millis: model.timer_millis(), + parameter_index: model.parameter_index(), + } + } + + pub fn apply_to_model(&self, model: &mut SourceModel, compartment: CompartmentKind) { + self.apply_to_model_flexible(model, compartment, None); + } + + /// Applies this data to the given source model. Doesn't proceed if data is invalid. + pub fn apply_to_model_flexible( + &self, + model: &mut SourceModel, + compartment: CompartmentKind, + preset_version: Option<&Version>, + ) { + use SourceCommand as P; + let final_category = if self.category.is_allowed_in(compartment) { + self.category + } else { + SourceCategory::default_for(compartment) + }; + model.change(P::SetCategory(final_category)); + if self.r#type == MidiSourceType::ParameterNumberValue { + model.change(P::SetParameterNumberMessageNumber(self.number)); + } else { + let number: Option = match self.number { + None => None, + Some(v) => match v.try_into() { + Ok(number) => Some(number), + Err(_) => { + notification::warn("MIDI message number too high".to_string()); + None + } + }, + }; + model.change(P::SetMidiMessageNumber(number)); + }; + model.change(P::SetMidiSourceType(self.r#type)); + model.change(P::SetChannel(self.channel)); + let character = if self.category == SourceCategory::Midi + && self.r#type == MidiSourceType::ControlChangeValue + && self.is_14_bit == Some(true) + { + // In old versions, 14-bit CC didn't support custom characters. We don't want it to + // interfere even it was set. + let is_old_preset = preset_version + .map(|v| v < &Version::parse("2.8.0-rc.1").unwrap()) + .unwrap_or(true); + if is_old_preset { + SourceCharacter::RangeElement + } else { + self.character + } + } else { + self.character + }; + model.change(P::SetCustomCharacter(character)); + model.change(P::SetIsRegistered(self.is_registered)); + model.change(P::SetIs14Bit(self.is_14_bit)); + model.change(P::SetMidiClockTransportMessage(self.message)); + model.change(P::SetRawMidiPattern(self.raw_midi_pattern.clone())); + model.change(P::SetMidiScriptKind(self.midi_script_kind)); + model.change(P::SetMidiScript(self.midi_script.clone())); + model.change(P::SetDisplayType(self.display_type)); + model.change(P::SetDisplayId(self.display_id)); + model.change(P::SetLine(self.line)); + model.change(P::SetOscAddressPattern(self.osc_address_pattern.clone())); + model.change(P::SetOscArgIndex(self.osc_arg_index)); + model.change(P::SetOscArgTypeTag(self.osc_arg_type)); + model.change(P::SetOscArgIsRelative(self.osc_arg_is_relative)); + model.change(P::SetOscArgValueRange( + self.osc_arg_value_range.to_interval(), + )); + model.change(P::SetOscFeedbackArgs(self.osc_feedback_args.clone())); + model.change(P::SetControlElementCharacter(self.control_element_type)); + model.change(P::SetControlElementId( + self.control_element_index.to_model(), + )); + model.change(P::SetReaperSourceType(self.reaper_source_type)); + model.change(P::SetTimerMillis(self.timer_millis)); + model.change(P::SetParameterIndex(self.parameter_index)); + model.change(P::SetKeystroke(self.keystroke)); + model.change(P::SetButtonIndex(self.button_index)); + model.change(P::SetButtonBackgroundType( + (&self.button_design.background).into(), + )); + match &self.button_design.background { + StreamDeckButtonBackground::Color(_) => {} + StreamDeckButtonBackground::Image(b) => { + model.change(P::SetButtonBackgroundImagePath(b.path.clone().into())); + } + } + match &self.button_design.foreground { + StreamDeckButtonForeground::None => {} + StreamDeckButtonForeground::FadingColor(_) => {} + StreamDeckButtonForeground::FadingImage(b) => { + model.change(P::SetButtonForegroundImagePath(b.path.clone().into())); + } + StreamDeckButtonForeground::SlidingImage(b) => { + model.change(P::SetButtonForegroundImagePath(b.path.clone().into())); + } + StreamDeckButtonForeground::FullBar(_) => {} + StreamDeckButtonForeground::Knob(_) => {} + } + model.change(P::SetButtonForegroundType( + (&self.button_design.foreground).into(), + )); + model.change(P::SetButtonStaticText( + self.button_design.static_text.clone(), + )); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use helgoboss_midi::test_util::*; + use serde_json::json; + + #[test] + fn deserialize_1() { + // Given + let json = json!( + { + "channel": 0, + "character": 0, + "is14Bit": false, + "number": 0, + "type": 0 + } + ); + // When + let data: SourceModelData = serde_json::from_value(json).unwrap(); + // Then + assert_eq!( + data, + SourceModelData { + category: SourceCategory::Midi, + r#type: MidiSourceType::ControlChangeValue, + channel: Some(Channel::new(0)), + number: Some(U14::new(0)), + character: SourceCharacter::RangeElement, + is_registered: None, + is_14_bit: Some(false), + message: MidiClockTransportMessage::Start, + control_element_type: VirtualControlElementCharacter::Multi, + ..Default::default() + } + ); + } + + #[test] + fn deserialize_2() { + // Given + let json = json!( + { + "channel": -1, + "is14Bit": true, + "isRegistered": true, + "number": 12542, + "type": 6 + } + ); + // When + let data: SourceModelData = serde_json::from_value(json).unwrap(); + // Then + assert_eq!( + data, + SourceModelData { + category: SourceCategory::Midi, + r#type: MidiSourceType::ParameterNumberValue, + number: Some(U14::new(12542)), + character: SourceCharacter::RangeElement, + is_registered: Some(true), + is_14_bit: Some(true), + message: MidiClockTransportMessage::Start, + control_element_type: VirtualControlElementCharacter::Multi, + ..Default::default() + } + ); + } + + #[test] + fn apply_1() { + // Given + let data = SourceModelData { + category: SourceCategory::Midi, + r#type: MidiSourceType::ParameterNumberValue, + channel: Some(Channel::new(8)), + character: SourceCharacter::RangeElement, + is_registered: Some(true), + is_14_bit: Some(true), + message: MidiClockTransportMessage::Start, + control_element_type: VirtualControlElementCharacter::Multi, + ..Default::default() + }; + let mut model = SourceModel::new(); + // When + data.apply_to_model_flexible(&mut model, CompartmentKind::Main, None); + // Then + assert_eq!( + model.midi_source_type(), + MidiSourceType::ParameterNumberValue + ); + assert_eq!(model.channel(), Some(channel(8))); + assert_eq!(model.midi_message_number(), None); + assert_eq!(model.parameter_number_message_number(), None); + assert_eq!(model.custom_character(), SourceCharacter::RangeElement); + assert_eq!( + model.midi_clock_transport_message(), + MidiClockTransportMessage::Start + ); + assert_eq!(model.is_registered(), Some(true)); + assert_eq!(model.is_14_bit(), Some(true)); + } + + #[test] + fn apply_2() { + // Given + let data = SourceModelData { + category: SourceCategory::Midi, + r#type: MidiSourceType::ClockTransport, + number: Some(U14::new(112)), + character: SourceCharacter::RangeElement, + is_14_bit: Some(false), + message: MidiClockTransportMessage::Stop, + control_element_type: VirtualControlElementCharacter::Multi, + ..Default::default() + }; + let mut model = SourceModel::new(); + // When + data.apply_to_model_flexible(&mut model, CompartmentKind::Main, None); + // Then + assert_eq!(model.midi_source_type(), MidiSourceType::ClockTransport); + assert_eq!(model.channel(), None); + assert_eq!(model.midi_message_number(), Some(u7(112))); + assert_eq!(model.parameter_number_message_number(), None); + assert_eq!(model.custom_character(), SourceCharacter::RangeElement); + assert_eq!( + model.midi_clock_transport_message(), + MidiClockTransportMessage::Stop + ); + assert_eq!(model.is_registered(), None); + assert_eq!(model.is_14_bit(), Some(false)); + } + + #[test] + fn from_1() { + // Given + use SourceCommand as C; + let mut model = SourceModel::new(); + model.change(C::SetCategory(SourceCategory::Midi)); + model.change(C::SetMidiSourceType(MidiSourceType::ControlChangeValue)); + model.change(C::SetChannel(Some(channel(15)))); + model.change(C::SetMidiMessageNumber(Some(u7(12)))); + model.change(C::SetCustomCharacter(SourceCharacter::Encoder2)); + model.change(C::SetIs14Bit(Some(true))); + // When + let data = SourceModelData::from_model(&model); + // Then + assert_eq!( + data, + SourceModelData { + category: SourceCategory::Midi, + r#type: MidiSourceType::ControlChangeValue, + channel: Some(Channel::new(15)), + number: Some(U14::new(12)), + character: SourceCharacter::Encoder2, + is_registered: Some(false), + is_14_bit: Some(true), + message: MidiClockTransportMessage::Start, + osc_arg_index: Some(0), + control_element_type: VirtualControlElementCharacter::Multi, + ..Default::default() + } + ); + } + + #[test] + fn from_2() { + // Given + use SourceCommand as C; + let mut model = SourceModel::new(); + model.change(C::SetCategory(SourceCategory::Midi)); + model.change(C::SetMidiSourceType(MidiSourceType::ParameterNumberValue)); + model.change(C::SetChannel(None)); + model.change(C::SetMidiMessageNumber(Some(u7(77)))); + model.change(C::SetParameterNumberMessageNumber(Some(u14(78)))); + model.change(C::SetCustomCharacter(SourceCharacter::Encoder1)); + model.change(C::SetIs14Bit(Some(true))); + model.change(C::SetIsRegistered(Some(true))); + model.change(C::SetMidiClockTransportMessage( + MidiClockTransportMessage::Continue, + )); + // When + let data = SourceModelData::from_model(&model); + // Then + assert_eq!( + data, + SourceModelData { + category: SourceCategory::Midi, + r#type: MidiSourceType::ParameterNumberValue, + channel: None, + number: Some(U14::new(78)), + character: SourceCharacter::Encoder1, + is_registered: Some(true), + is_14_bit: Some(true), + message: MidiClockTransportMessage::Continue, + osc_arg_index: Some(0), + osc_arg_type: Default::default(), + control_element_type: VirtualControlElementCharacter::Multi, + ..Default::default() + } + ); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/target_model_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/target_model_data.rs new file mode 100644 index 0000000..44fd356 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/target_model_data.rs @@ -0,0 +1,1894 @@ +use super::f32_as_u32; +use super::none_if_minus_one; +use crate::application::{ + AutomationModeOverrideType, BookmarkAnchorType, Change, FxParameterPropValues, FxPropValues, + FxSnapshot, MappingModificationKind, MappingRefModel, MappingSnapshotTypeForLoad, + MappingSnapshotTypeForTake, RealearnAutomationMode, RealearnTrackArea, TargetCategory, + TargetCommand, TargetModel, TargetUnit, TrackPropValues, TrackRoutePropValues, + TrackRouteSelectorType, VirtualFxParameterType, VirtualFxType, VirtualTrackType, +}; +use crate::domain::{ + get_fx_chains, ActionInvocationType, AnyOnParameter, CompartmentKind, Exclusivity, + ExtendedProcessorContext, FxDisplayType, GroupKey, MappingKey, OscDeviceId, ReaperTargetType, + SeekOptions, SendMidiDestinationType, SoloBehavior, Tag, TouchedRouteParameterType, + TouchedTrackParameterType, TrackExclusivity, TrackGangBehavior, TrackRouteType, + TransportAction, VirtualTrack, +}; +use crate::infrastructure::data::common::OscValueRange; +use crate::infrastructure::data::{ + DataToModelConversionContext, MigrationDescriptor, ModelToDataConversionContext, + VirtualControlElementIdData, +}; +use crate::infrastructure::plugin::BackboneShell; +use anyhow::Context; +use base::default_util::{ + bool_true, deserialize_null_default, is_bool_true, is_default, is_none_or_some_default, +}; +use base::hash_util::NonCryptoHashSet; +use helgoboss_learn::{AbsoluteValue, Fraction, OscTypeTag, UnitValue}; +use helgobox_api::persistence::{ + ActionScope, Axis, BrowseTracksMode, FxToolAction, InstanceTagKind, LearnableTargetKind, + MappingSnapshotDescForLoad, MappingSnapshotDescForTake, MonitoringMode, MouseAction, + PotFilterKind, SeekBehavior, TargetTouchCause, TargetValue, TrackScope, TrackToolAction, + VirtualControlElementCharacter, +}; +use helgobox_api::persistence::{ + ClipColumnTrackContext, PlaytimeColumnAction, PlaytimeColumnDescriptor, PlaytimeMatrixAction, + PlaytimeRowAction, PlaytimeRowDescriptor, PlaytimeSlotDescriptor, PlaytimeSlotManagementAction, + PlaytimeSlotTransportAction, +}; +use reaper_high::{BookmarkType, Fx, Guid}; +use reaper_medium::MidiInputDeviceId; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TargetModelData { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub category: TargetCategory, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub unit: TargetUnit, + // reaper_type would be a better name but we need backwards compatibility + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub r#type: ReaperTargetType, + // Action target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub action_scope: ActionScope, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub command_name: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub invocation_type: ActionInvocationType, + // Until ReaLearn 1.0.0-beta6 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing + )] + pub invoke_relative: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub with_track: bool, + // Track target + #[serde(flatten)] + pub track_data: TrackData, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub enable_only_if_track_is_selected: bool, + // FX target + #[serde(flatten)] + pub fx_data: FxData, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub enable_only_if_fx_has_focus: bool, + /// Introduced with ReaLearn v2.14.0-pre.5. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub use_selection_ganging: Option, + /// Introduced with ReaLearn v2.14.0-pre.5. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub use_track_grouping: Option, + // Track route target + #[serde(flatten)] + pub track_route_data: TrackRouteData, + // FX parameter target + #[serde(flatten)] + pub fx_parameter_data: FxParameterData, + // Track selection target (replaced with `track_exclusivity` since v2.4.0) + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub select_exclusively: Option, + // Track solo target (since v2.4.0, also changed default from "ignore routing" to "in place") + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_none_or_some_default" + )] + pub solo_behavior: Option, + // Seek and goto bookmark target, available from v2.14.0-pre.2 + #[serde(default, deserialize_with = "deserialize_null_default")] + pub seek_behavior: Option, + // Toggleable track targets (since v2.4.0) + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub track_exclusivity: TrackExclusivity, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub track_tool_action: TrackToolAction, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub fx_tool_action: FxToolAction, + // Transport target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub transport_action: TransportAction, + // Any-on target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub any_on_parameter: AnyOnParameter, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub control_element_type: VirtualControlElementCharacter, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub control_element_index: VirtualControlElementIdData, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + pub learnable: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub fx_snapshot: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub touched_parameter_type: TouchedTrackParameterType, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub touched_route_parameter_type: TouchedRouteParameterType, + // Bookmark target + #[serde(flatten)] + pub bookmark_data: BookmarkData, + // Seek target + #[serde(flatten)] + pub seek_options: SeekOptions, + // Track show target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub track_area: RealearnTrackArea, + // Track automation mode target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub track_automation_mode: RealearnAutomationMode, + // Track monitoring mode target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub track_monitoring_mode: MonitoringMode, + // Automation mode override target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub automation_mode_override_type: AutomationModeOverrideType, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + #[serde(alias = "cycleThroughTracksMode")] + pub browse_tracks_mode: BrowseTracksMode, + // FX Open and Browse FXs target + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub fx_display_type: FxDisplayType, + // Track selection related targets + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub scroll_arrange_view: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub scroll_mixer: bool, + // Send MIDI + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub send_midi_destination: SendMidiDestinationType, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub midi_input_device_id: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub raw_midi_pattern: String, + // Send OSC + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_address_pattern: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_arg_index: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_arg_type: OscTypeTag, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_arg_value_range: OscValueRange, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub osc_dev_id: Option, + // Mouse + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub mouse_action: MouseAction, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + pub poll_for_feedback: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub retrigger: bool, + #[serde(default, skip_serializing_if = "is_default")] + pub real_time: bool, + /// Not set in presets < ReaLearn v2.18.3. In this, it must default to [InstanceTagKind::UnitTags] + /// to remain backward-compatible. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub instance_tag_kind: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub tags: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub mapping_snapshot: MappingSnapshotDescForLoad, + /// Introduced with ReaLearn v2.14.0-pre.1. + /// Before that, it was always "By ID" and encoded as part of "mapping_snapshot". + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub take_mapping_snapshot: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub mapping_snapshot_default_value: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub exclusivity: Exclusivity, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub group_id: GroupKey, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub active_mappings_only: bool, + /// Replaced with `clip_slot` since v2.12.0-pre.5 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub slot_index: usize, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_management_action: PlaytimeSlotManagementAction, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub axis: Axis, + /// Not supported anymore since v2.12.0-pre.5 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub next_bar: bool, + /// Not supported anymore since v2.12.0-pre.5 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub buffered: bool, + /// New since ReaLearn v2.12.0-pre.5 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_slot: Option, + /// Playtime matrix column. + /// + /// For track targets, this contains the clip column from which we want to "borrow" the track. + /// + /// For clip column targets, this contains the clip column to which we want to refer. + /// + /// New since ReaLearn v2.13.0-pre.4 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_column: PlaytimeColumnDescriptor, + /// New since ReaLearn v2.13.0-pre.4 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_row: PlaytimeRowDescriptor, + /// New since ReaLearn v2.13.0-pre.4. + /// + /// Migrated from `transport_action` if not given. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_transport_action: Option, + /// New since ReaLearn v2.13.0-pre.4. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_column_action: PlaytimeColumnAction, + /// New since ReaLearn v2.13.0-pre.4. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_row_action: PlaytimeRowAction, + /// New since ReaLearn v2.13.0-pre.4. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_matrix_action: PlaytimeMatrixAction, + /// New since ReaLearn v2.13.0-pre.4 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub stop_column_if_slot_empty: bool, + /// New since ReaLearn v2.13.0-pre.4 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub pot_filter_item_kind: PotFilterKind, + /// New since ReaLearn v2.15.0-pre.1 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default", + alias = "mapping_modification", + alias = "mappingModification" + )] + pub mapping_modification_kind: MappingModificationKind, + /// New since ReaLearn v2.15.0-pre.1 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub session_id: Option, + /// New since ReaLearn v2.15.0-pre.1 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub mapping_key: Option, + /// New since ReaLearn v2.15.0-pre.1 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default", + alias = "targets" + )] + pub included_targets: Option>, + /// New since ReaLearn v2.15.0-pre.2 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub touch_cause: TargetTouchCause, +} + +impl TargetModelData { + pub fn from_model( + model: &TargetModel, + conversion_context: &impl ModelToDataConversionContext, + ) -> Self { + let output = serialize_track(model.track()); + let track_data = output.track_data; + let (session_id, mapping_key) = match model.mapping_ref() { + MappingRefModel::OwnMapping { mapping_id } => { + let mapping_key = + mapping_id.and_then(|id| conversion_context.mapping_key_by_id(id)); + (None, mapping_key) + } + MappingRefModel::ForeignMapping { + session_id, + mapping_key, + } => (Some(session_id.clone()), mapping_key.clone()), + }; + Self { + category: model.category(), + unit: model.unit(), + r#type: model.target_type(), + action_scope: model.action_scope(), + command_name: model.smart_command_name().map(|n| n.to_string()), + invocation_type: model.action_invocation_type(), + // Not serialized anymore because deprecated + invoke_relative: None, + track_data, + enable_only_if_track_is_selected: model.enable_only_if_track_selected(), + with_track: model.with_track(), + fx_data: serialize_fx(model.fx()), + enable_only_if_fx_has_focus: model.enable_only_if_fx_has_focus(), + use_selection_ganging: Some(model.fixed_gang_behavior().use_selection_ganging()), + use_track_grouping: Some(model.fixed_gang_behavior().use_track_grouping()), + track_route_data: serialize_track_route(model.track_route()), + fx_parameter_data: serialize_fx_parameter(model.fx_parameter()), + select_exclusively: None, + solo_behavior: Some(model.solo_behavior()), + seek_behavior: Some(model.seek_behavior()), + track_exclusivity: model.track_exclusivity(), + track_tool_action: model.track_tool_action(), + fx_tool_action: model.fx_tool_action(), + transport_action: model.transport_action(), + any_on_parameter: model.any_on_parameter(), + control_element_type: model.control_element_character(), + control_element_index: VirtualControlElementIdData::from_model( + model.control_element_id(), + ), + learnable: model.learnable(), + fx_snapshot: model.fx_snapshot().cloned(), + touched_parameter_type: model.touched_track_parameter_type(), + touched_route_parameter_type: model.touched_route_parameter_type(), + bookmark_data: BookmarkData { + anchor: model.bookmark_anchor_type(), + r#ref: model.bookmark_ref(), + is_region: model.bookmark_type() == BookmarkType::Region, + }, + seek_options: model.seek_options(), + track_area: model.track_area(), + track_automation_mode: model.automation_mode(), + track_monitoring_mode: model.monitoring_mode(), + automation_mode_override_type: model.automation_mode_override_type(), + browse_tracks_mode: model.browse_tracks_mode(), + fx_display_type: model.fx_display_type(), + scroll_arrange_view: model.scroll_arrange_view(), + scroll_mixer: model.scroll_mixer(), + send_midi_destination: model.send_midi_destination_type(), + midi_input_device_id: model.midi_input_device(), + raw_midi_pattern: model.raw_midi_pattern().to_owned(), + osc_address_pattern: model.osc_address_pattern().to_owned(), + osc_arg_index: model.osc_arg_index(), + osc_arg_type: model.osc_arg_type_tag(), + osc_arg_value_range: OscValueRange::from_interval(model.osc_arg_value_range()), + osc_dev_id: model.osc_dev_id(), + slot_index: 0, + clip_management_action: model.playtime_slot_management_action(), + next_bar: false, + buffered: false, + poll_for_feedback: model.poll_for_feedback(), + retrigger: model.retrigger(), + real_time: model.real_time(), + instance_tag_kind: Some(model.instance_tag_kind()), + tags: model.tags().to_vec(), + mapping_snapshot: model.mapping_snapshot_desc_for_load(), + take_mapping_snapshot: Some(model.mapping_snapshot_desc_for_take()), + mapping_snapshot_default_value: model + .mapping_snapshot_default_value() + .map(convert_target_value_to_api), + exclusivity: model.exclusivity(), + group_id: conversion_context + .group_key_by_id(model.group_id()) + .unwrap_or_default(), + active_mappings_only: model.active_mappings_only(), + clip_slot: if model.target_type().supports_clip_slot() { + Some(model.playtime_slot().clone()) + } else { + None + }, + clip_column: output + .clip_column + .unwrap_or_else(|| model.playtime_column().clone()), + clip_row: model.playtime_row().clone(), + clip_transport_action: if model.target_type() + == ReaperTargetType::PlaytimeSlotTransportAction + { + Some(model.playtime_slot_transport_action()) + } else { + None + }, + clip_column_action: model.playtime_column_action(), + clip_row_action: model.playtime_row_action(), + clip_matrix_action: model.playtime_matrix_action(), + stop_column_if_slot_empty: model.stop_column_if_slot_empty(), + axis: model.axis(), + mouse_action: model.mouse_action(), + pot_filter_item_kind: model.pot_filter_item_kind(), + mapping_modification_kind: model.mapping_modification_kind(), + session_id, + mapping_key, + included_targets: if model.target_type().definition().supports_included_targets() { + Some(model.included_targets().clone()) + } else { + None + }, + touch_cause: model.touch_cause(), + } + } + + pub fn apply_to_model( + &self, + model: &mut TargetModel, + compartment: CompartmentKind, + context: ExtendedProcessorContext, + conversion_context: &impl DataToModelConversionContext, + ) -> anyhow::Result<()> { + self.apply_to_model_flexible( + model, + Some(context), + Some(BackboneShell::version()), + compartment, + conversion_context, + &MigrationDescriptor::default(), + ) + } + + /// The context - if available - will be used to resolve some track/FX properties for UI + /// convenience. The context is necessary if there's the possibility of loading data saved with + /// ReaLearn < 1.12.0. + pub fn apply_to_model_flexible( + &self, + model: &mut TargetModel, + context: Option, + preset_version: Option<&Version>, + compartment: CompartmentKind, + conversion_context: &impl DataToModelConversionContext, + migration_descriptor: &MigrationDescriptor, + ) -> anyhow::Result<()> { + use TargetCommand as C; + let final_category = if self.category.is_allowed_in(compartment) { + self.category + } else { + TargetCategory::default_for(compartment) + }; + model.change(C::SetCategory(final_category)); + model.change(C::SetUnit(self.unit)); + model.change(C::SetTargetType(self.r#type)); + if self.category == TargetCategory::Reaper && self.r#type == ReaperTargetType::Action { + model.change(C::SetActionScope(self.action_scope)); + model.change(C::SetSmartCommandName(self.command_name.clone())); + } + let invocation_type = if let Some(invoke_relative) = self.invoke_relative { + // Very old ReaLearn version + if invoke_relative { + ActionInvocationType::Relative + } else { + ActionInvocationType::Absolute14Bit + } + } else if migration_descriptor.action_invocation_swap_761 { + match self.invocation_type { + ActionInvocationType::Absolute14Bit => ActionInvocationType::Absolute7Bit, + ActionInvocationType::Absolute7Bit => ActionInvocationType::Absolute14Bit, + x => x, + } + } else { + self.invocation_type + }; + model.change(C::SetActionInvocationType(invocation_type)); + let track_des_input = TrackDeserializationInput { + track_data: &self.track_data, + clip_column: &self.clip_column, + }; + let track_prop_values = deserialize_track(track_des_input); + let _ = model.set_track_from_prop_values( + track_prop_values, + false, + context.map(|c| c.context()), + ); + model.change(C::SetEnableOnlyIfTrackSelected( + self.enable_only_if_track_is_selected, + )); + let gang_behavior = match (self.use_selection_ganging, self.use_track_grouping) { + (Some(use_selection_ganging), Some(use_track_grouping)) => { + TrackGangBehavior::from_bools( + self.r#type.definition(), + use_selection_ganging, + use_track_grouping, + ) + } + _ => { + // Older versions had a target-specific behavior. + use ReaperTargetType::*; + match self.r#type { + TrackArm | TrackMute | TrackSolo => TrackGangBehavior::GroupingOnly, + TrackPan | TrackVolume | TrackWidth => TrackGangBehavior::Off, + TrackMonitoringMode => TrackGangBehavior::Off, + _ => Default::default(), + } + } + }; + model.change(C::SetGangBehavior(gang_behavior)); + model.change(C::SetBrowseTracksMode(self.browse_tracks_mode)); + model.change(C::SetWithTrack(self.with_track)); + let virtual_track = model.virtual_track().unwrap_or(VirtualTrack::This); + let fx_prop_values = deserialize_fx( + &self.fx_data, + context.map(|c| (c, compartment, &virtual_track)), + migration_descriptor, + ); + let _ = model.set_fx_from_prop_values(fx_prop_values, false, context, compartment); + model.change(C::SetEnableOnlyIfFxHasFocus( + self.enable_only_if_fx_has_focus, + )); + let route_prop_values = deserialize_track_route(&self.track_route_data); + let _ = model.set_route(route_prop_values); + let fx_param_prop_values = deserialize_fx_parameter(&self.fx_parameter_data); + let _ = model.set_fx_parameter(fx_param_prop_values); + let track_exclusivity = if let Some(select_exclusively) = self.select_exclusively { + // Should only be set in versions < 2.4.0. + if select_exclusively { + TrackExclusivity::ExclusiveWithinProject + } else { + TrackExclusivity::NonExclusive + } + } else { + self.track_exclusivity + }; + model.change(C::SetTrackExclusivity(track_exclusivity)); + let solo_behavior = self.solo_behavior.unwrap_or_else(|| { + let is_old_preset = preset_version + .map(|v| v < &Version::new(2, 4, 0)) + .unwrap_or(true); + if is_old_preset { + SoloBehavior::IgnoreRouting + } else { + SoloBehavior::InPlace + } + }); + model.change(C::SetSoloBehavior(solo_behavior)); + let seek_behavior = self.seek_behavior.unwrap_or_else(|| { + // Older version didn't have an explicit seek behavior. Determine old behavior. + match self.r#type { + ReaperTargetType::Seek => SeekBehavior::ReaperPreference, + ReaperTargetType::GoToBookmark => { + if self.bookmark_data.is_region { + // When targeting a region, we always used smooth region seeking + SeekBehavior::Smooth + } else { + // Otherwise we followed the REAPER preference + SeekBehavior::ReaperPreference + } + } + // Shouldn't matter for other targets + _ => Default::default(), + } + }); + model.change(C::SetSeekBehavior(seek_behavior)); + model.change(C::SetTransportAction(self.transport_action)); + model.change(C::SetAnyOnParameter(self.any_on_parameter)); + model.change(C::SetControlElementCharacter(self.control_element_type)); + model.change(C::SetControlElementId( + self.control_element_index.to_model(), + )); + model.change(C::SetLearnable(self.learnable)); + model.change(C::SetFxSnapshot(self.fx_snapshot.clone())); + model.change(C::SetTouchedTrackParameterType(self.touched_parameter_type)); + model.change(C::SetTouchedRouteParameterType( + self.touched_route_parameter_type, + )); + let bookmark_type = if self.bookmark_data.is_region { + BookmarkType::Region + } else { + BookmarkType::Marker + }; + model.change(C::SetBookmarkType(bookmark_type)); + model.change(C::SetBookmarkAnchorType(self.bookmark_data.anchor)); + model.change(C::SetBookmarkRef(self.bookmark_data.r#ref)); + let _ = model.set_seek_options(self.seek_options); + model.change(C::SetTrackArea(self.track_area)); + model.change(C::SetAutomationMode(self.track_automation_mode)); + model.change(C::SetMonitoringMode(self.track_monitoring_mode)); + model.change(C::SetAutomationModeOverrideType( + self.automation_mode_override_type, + )); + model.change(C::SetFxDisplayType(self.fx_display_type)); + model.change(C::SetScrollArrangeView(self.scroll_arrange_view)); + let scroll_mixer = if self.category == TargetCategory::Reaper + && self.r#type == ReaperTargetType::TrackSelection + { + let is_old_preset = preset_version + .map(|v| v < &Version::new(2, 8, 0)) + .unwrap_or(true); + if is_old_preset { + true + } else { + self.scroll_mixer + } + } else { + self.scroll_mixer + }; + model.change(C::SetScrollMixer(scroll_mixer)); + model.change(C::SetSendMidiDestinationType(self.send_midi_destination)); + model.change(C::SetMidiInputDevice(self.midi_input_device_id)); + model.change(C::SetRawMidiPattern(self.raw_midi_pattern.clone())); + model.change(C::SetOscAddressPattern(self.osc_address_pattern.clone())); + model.change(C::SetOscArgIndex(self.osc_arg_index)); + model.change(C::SetOscArgTypeTag(self.osc_arg_type)); + model.change(C::SetOscArgValueRange( + self.osc_arg_value_range.to_interval(), + )); + model.change(C::SetOscDevId(self.osc_dev_id)); + model.change(C::SetPollForFeedback(self.poll_for_feedback)); + model.change(C::SetRetrigger(self.retrigger)); + model.change(C::SetRealTime(self.real_time)); + let instance_tag_kind = self.instance_tag_kind.unwrap_or(InstanceTagKind::UnitTags); + model.change(C::SetInstanceTagKind(instance_tag_kind)); + model.change(C::SetTags(self.tags.clone())); + model.change(C::SetExclusivity(self.exclusivity)); + let group_id = conversion_context + .group_id_by_key(&self.group_id) + .unwrap_or_default(); + model.change(C::SetGroupId(group_id)); + model.change(C::SetActiveMappingsOnly(self.active_mappings_only)); + let slot_descriptor = self + .clip_slot + .clone() + .unwrap_or(PlaytimeSlotDescriptor::ByIndex( + playtime_api::persistence::SlotAddress::new(self.slot_index, 0), + )); + model.change(C::SetPlaytimeSlot(slot_descriptor)); + model.change(C::SetPlaytimeColumn(self.clip_column.clone())); + model.change(C::SetPlaytimeRow(self.clip_row.clone())); + model.change(C::SetPlaytimeSlotManagementAction( + self.clip_management_action, + )); + let clip_transport_action = self.clip_transport_action.unwrap_or_else(|| { + use PlaytimeSlotTransportAction as T; + use TransportAction::*; + match self.transport_action { + PlayStop => T::PlayStop, + PlayPause => T::PlayPause, + Stop => T::Stop, + Pause => T::Pause, + RecordStop => T::RecordStop, + Repeat => T::Looped, + } + }); + model.change(C::SetPlaytimeSlotTransportAction(clip_transport_action)); + model.change(C::SetPlaytimeColumnAction(self.clip_column_action)); + model.change(C::SetPlaytimeRowAction(self.clip_row_action)); + model.change(C::SetPlaytimeMatrixAction(self.clip_matrix_action)); + model.change(C::SetStopColumnIfSlotEmpty(self.stop_column_if_slot_empty)); + if self.category == TargetCategory::Reaper + && matches!( + self.r#type, + ReaperTargetType::PlaytimeControlUnitScroll | ReaperTargetType::PlaytimeBrowseCells + ) + { + // We set this only when we actually have the control unit scroll target. Because + // the axis model property is also used for other things. + model.change(C::SetAxis(self.axis)); + } + model.change(C::SetTrackToolAction(self.track_tool_action)); + model.change(C::SetFxToolAction(self.fx_tool_action)); + // "Load mapping snapshot" stuff + let mapping_snapshot_id_for_load = { + let (mapping_snapshot_type, mapping_snapshot_id) = match &self.mapping_snapshot { + MappingSnapshotDescForLoad::Initial => (MappingSnapshotTypeForLoad::Initial, None), + MappingSnapshotDescForLoad::ById { id } => { + (MappingSnapshotTypeForLoad::ById, id.parse().ok()) + } + }; + model.change(C::SetMappingSnapshotTypeForLoad(mapping_snapshot_type)); + let mapping_snapshot_default_value = match self.mapping_snapshot_default_value.as_ref() + { + None => None, + Some(v) => Some(convert_target_value_to_model(v)?), + }; + model.change(C::SetMappingSnapshotDefaultValue( + mapping_snapshot_default_value, + )); + mapping_snapshot_id + }; + // "Take mapping snapshot" stuff + let mapping_snapshot_id_for_take = { + let (mapping_snapshot_type, mapping_snapshot_id) = match &self.take_mapping_snapshot { + None => { + // Was written with ReaLearn < 2.14.0-pre.1 (take info from mapping_snapshot). + ( + MappingSnapshotTypeForTake::ById, + self.mapping_snapshot.id().and_then(|id| id.parse().ok()), + ) + } + Some(desc) => match desc { + MappingSnapshotDescForTake::LastLoaded => { + (MappingSnapshotTypeForTake::LastLoaded, None) + } + MappingSnapshotDescForTake::ById { id } => { + (MappingSnapshotTypeForTake::ById, id.parse().ok()) + } + }, + }; + model.change(C::SetMappingSnapshotTypeForTake(mapping_snapshot_type)); + mapping_snapshot_id + }; + model.change(C::SetMappingSnapshotId( + mapping_snapshot_id_for_load.or(mapping_snapshot_id_for_take), + )); + if self.category == TargetCategory::Reaper && self.r#type == ReaperTargetType::Mouse { + // We set this only when we actually have the mouse target. Because the axis model + // property is also used for other things. + model.set_mouse_action_without_notification(self.mouse_action); + } + model.change(C::SetPotFilterItemKind(self.pot_filter_item_kind)); + model.change(C::SetMappingModificationKind( + self.mapping_modification_kind, + )); + let mapping_ref = if let Some(session_id) = self.session_id.as_ref() { + MappingRefModel::ForeignMapping { + session_id: session_id.clone(), + mapping_key: self.mapping_key.clone(), + } + } else { + MappingRefModel::OwnMapping { + mapping_id: self + .mapping_key + .as_ref() + .and_then(|key| conversion_context.mapping_id_by_key(key)), + } + }; + model.change(C::SetMappingRef(mapping_ref)); + let target_kinds = self.included_targets.clone().unwrap_or_else(|| { + // ReaLearn versions < 2.15.0-pre.1 didn't have a way of configuring the + // included targets. The following targets were included. Notably, the action + // and transport targets were not included because actions weren't picked up at that + // time when using target "Global: Last touched"! + use LearnableTargetKind::*; + let old_kinds = [ + TrackVolume, + TrackPan, + RouteVolume, + RoutePan, + TrackArmState, + TrackMuteState, + TrackSoloState, + TrackSelectionState, + FxOnOffState, + FxParameterValue, + BrowseFxPresets, + PlayRate, + Tempo, + TrackAutomationMode, + TrackMonitoringMode, + AutomationModeOverride, + ]; + HashSet::from_iter(old_kinds) + }); + model.change(C::SetLearnableTargetKinds(target_kinds)); + model.change(C::SetTouchCause(self.touch_cause)); + Ok(()) + } +} + +pub struct TrackSerializationOutput { + pub track_data: TrackData, + pub clip_column: Option, +} + +/// This function is so annoying because of backward compatibility. Once made the bad decision +/// to not introduce an explicit track type. +pub fn serialize_track(track: TrackPropValues) -> TrackSerializationOutput { + use VirtualTrackType::*; + let mut clip_column = None; + let track_data = match track.r#type { + This => TrackData::default(), + Selected => TrackData { + guid: Some("selected".to_string()), + ..Default::default() + }, + AllSelected => TrackData { + guid: Some("selected*".to_string()), + ..Default::default() + }, + Master => TrackData { + guid: Some("master".to_string()), + ..Default::default() + }, + Unit => TrackData { + guid: Some("instance".to_string()), + ..Default::default() + }, + ByIdOrName => TrackData { + guid: track.id.map(|id| id.to_string_without_braces()), + name: Some(track.name), + ..Default::default() + }, + ById => TrackData { + guid: track.id.map(|id| id.to_string_without_braces()), + ..Default::default() + }, + ByName => TrackData { + name: Some(track.name), + ..Default::default() + }, + AllByName => TrackData { + guid: Some("name*".to_string()), + name: Some(track.name), + ..Default::default() + }, + ByIndex => TrackData { + index: Some(track.index), + ..Default::default() + }, + ByIndexTcp => TrackData { + guid: Some("index_tcp".to_string()), + index: Some(track.index), + ..Default::default() + }, + ByIndexMcp => TrackData { + guid: Some("index_mcp".to_string()), + index: Some(track.index), + ..Default::default() + }, + Dynamic => TrackData { + expression: Some(track.expression), + ..Default::default() + }, + DynamicTcp => TrackData { + guid: Some("dynamic_tcp".to_string()), + expression: Some(track.expression), + ..Default::default() + }, + DynamicMcp => TrackData { + guid: Some("dynamic_mcp".to_string()), + expression: Some(track.expression), + ..Default::default() + }, + FromClipColumn => { + clip_column = Some(track.clip_column); + TrackData { + guid: Some("from-clip-column".to_string()), + expression: Some(track.expression), + clip_column_track_context: track.clip_column_track_context, + ..Default::default() + } + } + }; + TrackSerializationOutput { + track_data, + clip_column, + } +} + +pub fn serialize_fx(fx: FxPropValues) -> FxData { + use VirtualFxType::*; + match fx.r#type { + This => FxData { + anchor: Some(VirtualFxType::This), + guid: None, + index: None, + name: None, + is_input_fx: false, + expression: None, + }, + Focused => FxData { + anchor: Some(VirtualFxType::Focused), + guid: None, + index: None, + name: None, + is_input_fx: false, + expression: None, + }, + Unit => FxData { + anchor: Some(VirtualFxType::Unit), + guid: None, + index: None, + name: None, + is_input_fx: false, + expression: None, + }, + Dynamic => FxData { + anchor: Some(VirtualFxType::Dynamic), + guid: None, + index: None, + name: None, + is_input_fx: fx.is_input_fx, + expression: Some(fx.expression), + }, + ById => FxData { + anchor: Some(VirtualFxType::ById), + index: Some(fx.index), + guid: fx.id.map(|id| id.to_string_without_braces()), + name: None, + is_input_fx: fx.is_input_fx, + expression: None, + }, + ByName => FxData { + anchor: Some(VirtualFxType::ByName), + index: None, + guid: None, + name: Some(fx.name), + is_input_fx: fx.is_input_fx, + expression: None, + }, + AllByName => FxData { + anchor: Some(VirtualFxType::AllByName), + index: None, + guid: None, + name: Some(fx.name), + is_input_fx: fx.is_input_fx, + expression: None, + }, + ByIndex => FxData { + anchor: Some(VirtualFxType::ByIndex), + index: Some(fx.index), + guid: None, + name: None, + is_input_fx: fx.is_input_fx, + expression: None, + }, + ByIdOrIndex => FxData { + anchor: Some(VirtualFxType::ByIdOrIndex), + index: Some(fx.index), + guid: fx.id.map(|id| id.to_string_without_braces()), + name: None, + is_input_fx: fx.is_input_fx, + expression: None, + }, + } +} + +pub fn serialize_fx_parameter(param: FxParameterPropValues) -> FxParameterData { + use VirtualFxParameterType::*; + match param.r#type { + Dynamic => FxParameterData { + r#type: Some(param.r#type), + index: 0, + name: None, + expression: Some(param.expression), + }, + ByName => FxParameterData { + r#type: Some(param.r#type), + index: 0, + name: Some(param.name), + expression: None, + }, + ById => FxParameterData { + // Before 2.8.0 we didn't have a type and this was the default ... let's leave it + // at that. + r#type: None, + index: param.index, + name: None, + expression: None, + }, + ByIndex => FxParameterData { + // Before 2.8.0 we didn't have a type and this was the default ... let's leave it + // at that. + r#type: Some(param.r#type), + index: param.index, + name: None, + expression: None, + }, + } +} + +pub fn serialize_track_route(route: TrackRoutePropValues) -> TrackRouteData { + use TrackRouteSelectorType::*; + match route.selector_type { + Dynamic => TrackRouteData { + selector_type: Some(route.selector_type), + r#type: route.r#type, + index: None, + guid: None, + name: None, + expression: Some(route.expression), + }, + ById => TrackRouteData { + selector_type: Some(route.selector_type), + r#type: route.r#type, + index: None, + guid: route.id.map(|id| id.to_string_without_braces()), + name: None, + expression: None, + }, + ByName => TrackRouteData { + selector_type: Some(route.selector_type), + r#type: route.r#type, + index: None, + guid: None, + name: Some(route.name), + expression: None, + }, + ByIndex => TrackRouteData { + // Before 2.8.0 we didn't have a selector type and this was the default ... let's leave + // it at that. + selector_type: None, + r#type: route.r#type, + index: Some(route.index), + guid: None, + name: None, + expression: None, + }, + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FxParameterData { + #[serde( + rename = "paramType", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub r#type: Option, + #[serde( + rename = "paramIndex", + default, + deserialize_with = "f32_as_u32", + skip_serializing_if = "is_default" + )] + pub index: u32, + #[serde(rename = "paramName", default, skip_serializing_if = "is_default")] + pub name: Option, + #[serde( + rename = "paramExpression", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub expression: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TrackRouteData { + #[serde( + rename = "routeSelectorType", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub selector_type: Option, + #[serde( + rename = "routeType", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub r#type: TrackRouteType, + /// The only reason this is an option is that in ReaLearn < 1.11.0 we allowed the send + /// index to be undefined (-1). However, going with a default of 0 is also okay so + /// `None` and `Some(0)` means essentially the same thing to us now. + #[serde( + rename = "sendIndex", + default, + deserialize_with = "none_if_minus_one", + skip_serializing_if = "is_none_or_some_default" + )] + pub index: Option, + #[serde( + rename = "routeGuid", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub guid: Option, + #[serde( + rename = "routeName", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub name: Option, + #[serde( + rename = "routeExpression", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub expression: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FxData { + /// Since 1.12.0-pre8. This is an option because we changed the default and wanted an easy + /// way to detect when an old preset is loaded. + // TODO-low If we would have a look at the version number at deserialization time, we could + // make it work without the option. Then we could also go without redundant "fxAnchor": "id" in + // current JSON. However, we introduced version numbers in 1.12.0-pre18 so this could + // negatively effect some prerelease testers. Another way to get rid of the redundant + // "fxAnchor" property would be to set this to none if the target type doesn't support FX. + #[serde( + rename = "fxAnchor", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub anchor: Option, + /// The only reason this is an option is that in ReaLearn < 1.11.0 we allowed the FX + /// index to be undefined (-1). However, going with a default of 0 is also okay so + /// `None` and `Some(0)` means essentially the same thing to us now. + #[serde( + rename = "fxIndex", + default, + deserialize_with = "none_if_minus_one", + skip_serializing_if = "is_none_or_some_default" + )] + pub index: Option, + /// Since 1.12.0-pre1 + #[serde( + rename = "fxGUID", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub guid: Option, + /// Since 1.12.0-pre8 + #[serde( + rename = "fxName", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub name: Option, + // TODO-medium This is actually a property of the track FX chain, not the FX + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub is_input_fx: bool, + #[serde( + rename = "fxExpression", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub expression: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TrackData { + // None means "This" track + #[serde( + rename = "trackGUID", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub guid: Option, + #[serde( + rename = "trackName", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub name: Option, + #[serde( + rename = "trackIndex", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub index: Option, + #[serde( + rename = "trackExpression", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub expression: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_column_track_context: ClipColumnTrackContext, +} + +pub struct TrackDeserializationInput<'a> { + pub track_data: &'a TrackData, + pub clip_column: &'a PlaytimeColumnDescriptor, +} + +/// This function is so annoying because of backward compatibility. Once made the bad decision +/// to not introduce an explicit track type. +pub fn deserialize_track(input: TrackDeserializationInput) -> TrackPropValues { + match input.track_data { + TrackData { + guid: None, + name: None, + index: None, + expression: None, + .. + } => TrackPropValues::from_virtual_track(VirtualTrack::This), + TrackData { guid: Some(g), .. } if g == "master" => { + TrackPropValues::from_virtual_track(VirtualTrack::Master) + } + TrackData { guid: Some(g), .. } if g == "instance" => { + TrackPropValues::from_virtual_track(VirtualTrack::Unit) + } + TrackData { guid: Some(g), .. } if g == "selected" => { + TrackPropValues::from_virtual_track(VirtualTrack::Selected { + allow_multiple: false, + }) + } + TrackData { guid: Some(g), .. } if g == "selected*" => { + TrackPropValues::from_virtual_track(VirtualTrack::Selected { + allow_multiple: true, + }) + } + TrackData { + guid: Some(g), + index: Some(i), + .. + } if g == "index_tcp" => TrackPropValues::from_virtual_track(VirtualTrack::ByIndex { + index: *i, + scope: TrackScope::TracksVisibleInTcp, + }), + TrackData { + guid: Some(g), + index: Some(i), + .. + } if g == "index_mcp" => TrackPropValues::from_virtual_track(VirtualTrack::ByIndex { + index: *i, + scope: TrackScope::TracksVisibleInMcp, + }), + TrackData { + guid: Some(g), + expression: Some(e), + .. + } if g == "dynamic_tcp" => TrackPropValues { + r#type: VirtualTrackType::DynamicTcp, + expression: e.clone(), + ..Default::default() + }, + TrackData { + guid: Some(g), + expression: Some(e), + .. + } if g == "dynamic_mcp" => TrackPropValues { + r#type: VirtualTrackType::DynamicMcp, + expression: e.clone(), + ..Default::default() + }, + TrackData { + guid: Some(g), + clip_column_track_context, + .. + } if g == "from-clip-column" => TrackPropValues { + r#type: VirtualTrackType::FromClipColumn, + clip_column: input.clip_column.clone(), + clip_column_track_context: *clip_column_track_context, + ..Default::default() + }, + TrackData { + guid: Some(g), + name: Some(n), + .. + } if g == "name*" => TrackPropValues { + r#type: VirtualTrackType::AllByName, + name: n.clone(), + ..Default::default() + }, + TrackData { + guid: Some(g), + name, + .. + } => { + let id = Guid::from_string_without_braces(g).ok(); + match name { + None => TrackPropValues { + r#type: VirtualTrackType::ById, + id, + ..Default::default() + }, + Some(n) => TrackPropValues { + r#type: VirtualTrackType::ByIdOrName, + id, + name: n.clone(), + ..Default::default() + }, + } + } + TrackData { + guid: None, + name: Some(n), + .. + } => TrackPropValues { + r#type: VirtualTrackType::ByName, + name: n.clone(), + ..Default::default() + }, + TrackData { + guid: None, + name: None, + index: Some(i), + .. + } => TrackPropValues { + r#type: VirtualTrackType::ByIndex, + index: *i, + ..Default::default() + }, + TrackData { + guid: None, + name: None, + index: None, + expression: Some(e), + .. + } => TrackPropValues { + r#type: VirtualTrackType::Dynamic, + expression: e.clone(), + ..Default::default() + }, + } +} + +/// The context and so on is only necessary if you want to load < 1.12.0 presets. +pub fn deserialize_fx( + fx_data: &FxData, + ctx: Option<(ExtendedProcessorContext, CompartmentKind, &VirtualTrack)>, + migration_descriptor: &MigrationDescriptor, +) -> FxPropValues { + match fx_data { + // Special case: for ReaLearn < 2.8.0-pre4. + FxData { guid: Some(g), .. } if g == "focused" => FxPropValues { + r#type: VirtualFxType::Unit, + ..Default::default() + }, + // Before ReaLearn 1.12.0 only the index was saved, even if it was (implicitly) always + // IdOrIndex anchor. The GUID was looked up at runtime whenever loading the project. Do it! + FxData { + anchor: None, + guid: None, + expression: None, + index: Some(i), + is_input_fx, + .. + } => { + let (context, compartment, virtual_track) = + ctx.expect("trying to load < 1.12.0 FX target without processor context"); + let fx = get_first_guid_based_fx_at_index( + context, + virtual_track, + *is_input_fx, + *i, + compartment, + ) + .ok(); + FxPropValues { + r#type: VirtualFxType::ByIdOrIndex, + is_input_fx: *is_input_fx, + id: fx.and_then(|f| f.guid()), + index: *i, + ..Default::default() + } + } + // In ReaLearn 1.12.0-pre1 we started also saving the GUID, even for IdOrIndex anchor. We + // still want to support that, even if no anchor is given. + FxData { + anchor: None, + guid: Some(guid_string), + name: None, + expression: None, + index: Some(index), + is_input_fx, + } => { + let id = Guid::from_string_without_braces(guid_string).ok(); + FxPropValues { + r#type: VirtualFxType::ByIdOrIndex, + is_input_fx: *is_input_fx, + id, + index: *index, + ..Default::default() + } + } + // Since ReaLearn 1.12.0-pre8 we support Index anchor. We can't distinguish from < 1.12.0 + // data without explicitly given anchor. + FxData { + anchor: Some(VirtualFxType::ByIndex), + guid: None, + expression: None, + index: Some(i), + is_input_fx, + .. + } => FxPropValues { + r#type: VirtualFxType::ByIndex, + is_input_fx: *is_input_fx, + index: *i, + ..Default::default() + }, + // From ReaLearn 1.12.0 to 2.8.0-pre2. We try to guess the anchor (what a mess). + FxData { + anchor: None, + guid: Some(guid_string), + name: _, + expression: _, + index, + is_input_fx, + } => { + let id = Guid::from_string_without_braces(guid_string).ok(); + FxPropValues { + r#type: VirtualFxType::ById, + is_input_fx: *is_input_fx, + id, + index: index.unwrap_or_default(), + ..Default::default() + } + } + FxData { + anchor: None, + index: _, + guid: _, + name: Some(name), + is_input_fx, + expression: None, + } => FxPropValues { + r#type: VirtualFxType::ByName, + is_input_fx: *is_input_fx, + name: name.clone(), + ..Default::default() + }, + FxData { + // Here we don't necessarily need the name anchor because there's no ambiguity. + anchor: None, + index: _, + guid: _, + name: _, + is_input_fx: _, + expression: Some(e), + } => FxPropValues { + r#type: VirtualFxType::Dynamic, + expression: e.clone(), + ..Default::default() + }, + // >= 2.8.0-pre3. Take everything we can get but watch the anchor. + FxData { + anchor: Some(fx_type), + index, + guid, + name, + is_input_fx, + expression, + } => FxPropValues { + r#type: if *fx_type == VirtualFxType::Focused + && migration_descriptor.fx_selector_transformation_188 + { + VirtualFxType::Unit + } else { + *fx_type + }, + is_input_fx: *is_input_fx, + id: guid + .as_ref() + .and_then(|g| Guid::from_string_without_braces(g).ok()), + name: name.clone().unwrap_or_default(), + expression: expression.clone().unwrap_or_default(), + index: index.unwrap_or_default(), + }, + FxData { + anchor: None, + index: None, + guid: None, + name: None, + expression: None, + is_input_fx: _, + } => FxPropValues::default(), + } +} + +pub fn deserialize_fx_parameter(param_data: &FxParameterData) -> FxParameterPropValues { + match param_data { + // This is the case for versions < 2.8.0. + FxParameterData { + // Important (because index is always given we need this as distinction). + r#type: None, + index: i, + .. + } => FxParameterPropValues { + r#type: VirtualFxParameterType::ById, + index: *i, + ..Default::default() + }, + FxParameterData { + name: Some(name), .. + } => FxParameterPropValues { + r#type: VirtualFxParameterType::ByName, + name: name.clone(), + ..Default::default() + }, + FxParameterData { + expression: Some(e), + .. + } => FxParameterPropValues { + r#type: VirtualFxParameterType::Dynamic, + expression: e.clone(), + ..Default::default() + }, + FxParameterData { + r#type: Some(VirtualFxParameterType::ByIndex), + index: i, + .. + } => FxParameterPropValues { + r#type: VirtualFxParameterType::ByIndex, + index: *i, + ..Default::default() + }, + _ => FxParameterPropValues::default(), + } +} + +pub fn deserialize_track_route(data: &TrackRouteData) -> TrackRoutePropValues { + match data { + // This is the case for versions < 2.8.0. + TrackRouteData { + // Important (because index is always given we need this as distinction). + selector_type: None, + r#type: TrackRouteType::Send, + index: Some(i), + .. + } => TrackRoutePropValues { + selector_type: TrackRouteSelectorType::ByIndex, + r#type: TrackRouteType::Send, + index: *i, + ..Default::default() + }, + // These are the new ones. + TrackRouteData { + selector_type: Some(TrackRouteSelectorType::ById), + r#type: t, + guid: Some(g), + .. + } => { + let id = Guid::from_string_without_braces(g).ok(); + TrackRoutePropValues { + selector_type: TrackRouteSelectorType::ById, + r#type: *t, + id, + ..Default::default() + } + } + TrackRouteData { + selector_type: Some(TrackRouteSelectorType::ByIndex) | None, + r#type: t, + index: i, + .. + } => TrackRoutePropValues { + selector_type: TrackRouteSelectorType::ByIndex, + r#type: *t, + index: i.unwrap_or(0), + ..Default::default() + }, + TrackRouteData { + selector_type: Some(TrackRouteSelectorType::ByName), + r#type: t, + name: Some(name), + .. + } => TrackRoutePropValues { + selector_type: TrackRouteSelectorType::ByName, + r#type: *t, + name: name.clone(), + ..Default::default() + }, + TrackRouteData { + selector_type: Some(TrackRouteSelectorType::Dynamic), + r#type: t, + expression: Some(e), + .. + } => TrackRoutePropValues { + selector_type: TrackRouteSelectorType::Dynamic, + r#type: *t, + expression: e.clone(), + ..Default::default() + }, + _ => TrackRoutePropValues::default(), + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BookmarkData { + #[serde( + rename = "bookmarkAnchor", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub anchor: BookmarkAnchorType, + #[serde( + rename = "bookmarkRef", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub r#ref: u32, + #[serde( + rename = "bookmarkIsRegion", + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub is_region: bool, +} + +pub fn get_first_guid_based_fx_at_index( + context: ExtendedProcessorContext, + track: &VirtualTrack, + is_input_fx: bool, + fx_index: u32, + compartment: CompartmentKind, +) -> Result { + let fx_chains = get_fx_chains(context, track, is_input_fx, compartment)?; + let fx_chain = fx_chains.first().ok_or("empty list of FX chains")?; + fx_chain.fx_by_index(fx_index).ok_or("no FX at that index") +} + +pub fn convert_target_value_to_api(value: AbsoluteValue) -> TargetValue { + match value { + AbsoluteValue::Continuous(v) => TargetValue::Unit { value: v.get() }, + AbsoluteValue::Discrete(v) => TargetValue::Discrete { value: v.actual() }, + } +} + +pub fn convert_target_value_to_model(value: &TargetValue) -> anyhow::Result { + match value { + TargetValue::Unit { value } => Ok(AbsoluteValue::Continuous( + UnitValue::try_from(*value) + .map_err(anyhow::Error::msg) + .context("convert unit target value")?, + )), + TargetValue::Discrete { value } => Ok(AbsoluteValue::Discrete(Fraction::new_max(*value))), + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/unit_data.rs b/plugin-reaper-realearn/main/src/infrastructure/data/unit_data.rs new file mode 100644 index 0000000..79dd0be --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/unit_data.rs @@ -0,0 +1,1153 @@ +#![allow(deprecated)] +use crate::application::{ + reaper_supports_global_midi_filter, AutoLoadMode, CompartmentCommand, CompartmentInUnit, + CompartmentModel, FxPresetLinkConfig, GroupModel, SharedUnitModel, UnitCommand, UnitModel, + WeakUnitModel, +}; +use crate::domain::{ + compartment_param_index_iter, CompartmentKind, CompartmentParamIndex, CompartmentParams, + ControlInput, FeedbackOutput, GroupId, GroupKey, MappingId, MappingKey, + MappingSnapshotContainer, MappingSnapshotId, MidiControlInput, MidiDestination, OscDeviceId, + Param, PluginParams, StayActiveWhenProjectInBackground, StreamDeckDeviceId, Tag, Unit, +}; +use crate::infrastructure::data::{ + convert_target_value_to_api, convert_target_value_to_model, + ensure_no_duplicate_compartment_data, CompartmentModelData, GroupModelData, MappingModelData, + MigrationDescriptor, ParameterData, +}; +use crate::infrastructure::plugin::{update_auto_units_async, BackboneShell}; +use base::default_util::{bool_true, deserialize_null_default, is_bool_true, is_default}; + +use crate::base::notification; +use crate::infrastructure::api::convert::to_data::ApiToDataConversionContext; +use anyhow::Context; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use helgobox_api::persistence::{ + FxDescriptor, MappingInSnapshot, MappingSnapshot, TrackDescriptor, +}; +use reaper_medium::{MidiInputDeviceId, MidiOutputDeviceId}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::ops::Deref; +use std::rc::Rc; + +/// This is the structure for loading and saving a ReaLearn session. +/// +/// It's optimized for being represented as JSON. The JSON representation must be 100% +/// backward-compatible. +// TODO-low Maybe call PluginData because it also contains parameter values (which are not part of +// the session. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UnitData { + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + pub enabled: bool, + // Since ReaLearn 1.12.0-pre18 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub version: Option, + // Since ReaLearn 1.12.0-pre? + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + id: Option, + // Since ReaLearn 2.16.0-pre9 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + name: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + let_matched_events_through: bool, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + let_unmatched_events_through: bool, + /// Introduced with ReaLearn 2.14.0-pre.1. Before that "Always". + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + stay_active_when_project_in_background: Option, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + lives_on_upper_floor: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + // false by default because in older versions, feedback was always sent no matter if armed or + // not + send_feedback_only_if_armed: bool, + #[serde(default = "bool_true", skip_serializing_if = "is_bool_true")] + reset_feedback_when_releasing_source: bool, + /// `None` means "" + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + control_device_id: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + wants_keyboard_input: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + match_even_inactive_mappings: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + stream_deck_device_id: Option, + /// + /// - `None` means "\" + /// - `Some("fx-output")` means "\" + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + feedback_device_id: Option, + // Not set before 1.12.0-pre9 + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + default_group: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + groups: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + default_controller_group: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller_groups: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + mappings: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller_mappings: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller_custom_data: NonCryptoHashMap, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + main_custom_data: NonCryptoHashMap, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller_common_lua: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + main_common_lua: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller_notes: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + main_notes: String, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + active_controller_id: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + active_main_preset_id: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + main_preset_auto_load_mode: AutoLoadMode, + // String key workaround because otherwise deserialization doesn't work with flattening, + // which is used in CompartmentModelData. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + parameters: NonCryptoHashMap, + // String key workaround because otherwise deserialization doesn't work with flattening, + // which is used in CompartmentModelData. + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller_parameters: NonCryptoHashMap, + // New since 2.12.0-pre.5 + #[deprecated(note = "Moved to InstanceData")] + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub clip_matrix: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub tags: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller: CompartmentState, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + main: CompartmentState, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + active_instance_tags: NonCryptoHashSet, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + instance_preset_link_config: FxPresetLinkConfig, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + use_instance_preset_links_only: bool, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + instance_track: TrackDescriptor, + #[serde(default = "focused_fx_descriptor")] + instance_fx: FxDescriptor, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + mapping_snapshots: Vec, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + controller_mapping_snapshots: Vec, + #[deprecated(note = "Moved to InstanceData")] + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub pot_state: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default", + alias = "memorizedMainCompartment" + )] + auto_load_fallback: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +enum AutoLoadFallbackData { + Compartment(CompartmentModelData), + Preset(String), +} + +impl AutoLoadFallbackData { + fn from_model( + fallback_preset_id: Option<&str>, + fallback_compartment_model: Option<&CompartmentModel>, + ) -> Option { + fallback_preset_id + .map(|id| Self::Preset(id.to_string())) + .or_else(|| { + fallback_compartment_model + .map(|m| Self::Compartment(CompartmentModelData::from_model(m))) + }) + } +} + +fn focused_fx_descriptor() -> FxDescriptor { + FxDescriptor::Focused +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +struct CompartmentState { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + active_mapping_by_group: NonCryptoHashMap, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + active_mapping_tags: NonCryptoHashSet, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + active_mapping_snapshots: NonCryptoHashMap, +} + +impl CompartmentState { + fn from_instance_state(instance_state: &Unit, compartment: CompartmentKind) -> Self { + CompartmentState { + active_mapping_by_group: instance_state.active_mapping_by_group(compartment).clone(), + active_mapping_tags: instance_state.active_mapping_tags(compartment).clone(), + active_mapping_snapshots: instance_state + .mapping_snapshot_container(compartment) + .active_snapshot_id_by_tag() + .clone(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +enum ControlDeviceId { + #[deprecated( + since = "2.16.4", + note = "Keyboard input can now be enabled in addition to the input device" + )] + Keyboard(KeyboardDevice), + Osc(OscDeviceId), + Midi(String), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[deprecated( + since = "2.16.4", + note = "Keyboard input can now be enabled in addition to the input device" +)] +enum KeyboardDevice { + #[serde(rename = "keyboard")] + TheKeyboard, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +enum FeedbackDeviceId { + Osc(OscDeviceId), + MidiOrFxOutput(String), +} + +impl Default for UnitData { + #[allow(deprecated)] + fn default() -> Self { + use crate::application::unit_defaults; + Self { + version: Some(BackboneShell::version().clone()), + id: None, + enabled: true, + name: None, + let_matched_events_through: unit_defaults::LET_MATCHED_EVENTS_THROUGH, + let_unmatched_events_through: unit_defaults::LET_UNMATCHED_EVENTS_THROUGH, + stay_active_when_project_in_background: Some( + unit_defaults::STAY_ACTIVE_WHEN_PROJECT_IN_BACKGROUND, + ), + lives_on_upper_floor: unit_defaults::LIVES_ON_UPPER_FLOOR, + send_feedback_only_if_armed: unit_defaults::SEND_FEEDBACK_ONLY_IF_ARMED, + reset_feedback_when_releasing_source: + unit_defaults::RESET_FEEDBACK_WHEN_RELEASING_SOURCE, + control_device_id: None, + wants_keyboard_input: unit_defaults::WANTS_KEYBOARD_INPUT, + match_even_inactive_mappings: unit_defaults::MATCH_EVEN_INACTIVE_MAPPINGS, + stream_deck_device_id: None, + feedback_device_id: None, + default_group: None, + default_controller_group: None, + groups: vec![], + controller_groups: vec![], + mappings: vec![], + controller_mappings: vec![], + controller_custom_data: Default::default(), + main_custom_data: Default::default(), + controller_common_lua: Default::default(), + main_common_lua: Default::default(), + controller_notes: Default::default(), + main_notes: Default::default(), + active_controller_id: None, + active_main_preset_id: None, + main_preset_auto_load_mode: unit_defaults::MAIN_PRESET_AUTO_LOAD_MODE, + parameters: Default::default(), + controller_parameters: Default::default(), + clip_matrix: None, + tags: vec![], + controller: Default::default(), + main: Default::default(), + active_instance_tags: Default::default(), + instance_preset_link_config: Default::default(), + use_instance_preset_links_only: false, + instance_track: Default::default(), + instance_fx: unit_defaults::INSTANCE_FX_DESCRIPTOR, + mapping_snapshots: vec![], + controller_mapping_snapshots: vec![], + pot_state: Default::default(), + auto_load_fallback: None, + } + } +} + +impl UnitData { + /// The given parameters are the canonical ones from `RealearnPluginParameters`. + #[allow(deprecated)] + pub fn from_model(session: &UnitModel) -> UnitData { + let from_mappings = |compartment| { + let compartment_in_session = CompartmentInUnit::new(session, compartment); + session + .mappings(compartment) + .map(|m| MappingModelData::from_model(m.borrow().deref(), &compartment_in_session)) + .collect() + }; + let from_groups = |compartment| { + session + .groups(compartment) + .map(|m| { + let compartment_in_session = CompartmentInUnit::new(session, compartment); + GroupModelData::from_model(m.borrow().deref(), &compartment_in_session) + }) + .collect() + }; + let from_group = |compartment| { + let compartment_in_session = CompartmentInUnit::new(session, compartment); + let group_model_data = GroupModelData::from_model( + session.default_group(compartment).borrow().deref(), + &compartment_in_session, + ); + Some(group_model_data) + }; + let main_preset_auto_load_mode = session.auto_load_mode.get(); + let unit = session.unit().borrow(); + let plugin_params = unit.parameter_manager().params(); + UnitData { + enabled: session.is_enabled(), + version: Some(BackboneShell::version().clone()), + id: Some(session.unit_key().to_string()), + name: session.name.clone(), + let_matched_events_through: session.let_matched_events_through.get(), + let_unmatched_events_through: session.let_unmatched_events_through.get(), + stay_active_when_project_in_background: Some( + session.stay_active_when_project_in_background.get(), + ), + lives_on_upper_floor: session.lives_on_upper_floor.get(), + send_feedback_only_if_armed: session.send_feedback_only_if_armed.get(), + reset_feedback_when_releasing_source: session + .reset_feedback_when_releasing_source + .get(), + control_device_id: { + match session.control_input() { + ControlInput::Midi(MidiControlInput::FxInput) => None, + ControlInput::Midi(MidiControlInput::Device(dev_id)) => { + Some(ControlDeviceId::Midi(dev_id.to_string())) + } + ControlInput::Osc(dev_id) => Some(ControlDeviceId::Osc(dev_id)), + } + }, + wants_keyboard_input: session.wants_keyboard_input(), + match_even_inactive_mappings: session.match_even_inactive_mappings(), + stream_deck_device_id: session.stream_deck_device_id(), + feedback_device_id: { + session.feedback_output().map(|output| match output { + FeedbackOutput::Midi(MidiDestination::FxOutput) => { + FeedbackDeviceId::MidiOrFxOutput("fx-output".to_owned()) + } + FeedbackOutput::Midi(MidiDestination::Device(dev_id)) => { + FeedbackDeviceId::MidiOrFxOutput(dev_id.to_string()) + } + FeedbackOutput::Osc(dev_id) => FeedbackDeviceId::Osc(dev_id), + }) + }, + default_group: from_group(CompartmentKind::Main), + default_controller_group: from_group(CompartmentKind::Controller), + groups: from_groups(CompartmentKind::Main), + controller_groups: from_groups(CompartmentKind::Controller), + mappings: from_mappings(CompartmentKind::Main), + controller_mappings: from_mappings(CompartmentKind::Controller), + controller_custom_data: unit + .custom_compartment_data(CompartmentKind::Controller) + .clone(), + main_custom_data: unit.custom_compartment_data(CompartmentKind::Main).clone(), + controller_common_lua: session + .compartment_common_lua(CompartmentKind::Controller) + .to_owned(), + main_common_lua: session + .compartment_common_lua(CompartmentKind::Main) + .to_owned(), + controller_notes: session + .compartment_notes(CompartmentKind::Controller) + .to_owned(), + main_notes: session.compartment_notes(CompartmentKind::Main).to_owned(), + active_controller_id: session + .active_preset_id(CompartmentKind::Controller) + .map(|id| id.to_string()), + active_main_preset_id: session + .active_preset_id(CompartmentKind::Main) + .map(|id| id.to_string()), + main_preset_auto_load_mode, + parameters: get_parameter_data_map(&plugin_params, CompartmentKind::Main), + controller_parameters: get_parameter_data_map( + &plugin_params, + CompartmentKind::Controller, + ), + clip_matrix: None, + tags: session.tags().to_vec(), + controller: CompartmentState::from_instance_state(&unit, CompartmentKind::Controller), + main: CompartmentState::from_instance_state(&unit, CompartmentKind::Main), + active_instance_tags: unit.active_instance_tags().clone(), + instance_preset_link_config: session.instance_preset_link_config().clone(), + use_instance_preset_links_only: session.use_unit_preset_links_only(), + instance_track: session.instance_track_descriptor().clone(), + instance_fx: session.instance_fx_descriptor().clone(), + mapping_snapshots: convert_mapping_snapshots_to_api( + session, + &unit, + CompartmentKind::Main, + ), + controller_mapping_snapshots: convert_mapping_snapshots_to_api( + session, + &unit, + CompartmentKind::Controller, + ), + pot_state: None, + auto_load_fallback: AutoLoadFallbackData::from_model( + session.auto_load_fallback_preset_id(), + session.auto_load_fallback_compartment(), + ), + } + } + + /// Applies this session data to the given session. + #[allow(unused_variables)] + pub fn apply_to_model(&self, shared_session: &SharedUnitModel) -> anyhow::Result<()> { + let mut session = shared_session.borrow_mut(); + if let Some(v) = self.version.as_ref() { + if BackboneShell::version() < v { + notification::warn(format!( + "The session that is about to load was saved with ReaLearn {}, which is \ + newer than the installed version {}. Things might not work as expected. \ + Even more importantly: Saving might result in loss of the data that was \ + saved with the new ReaLearn version! Please consider upgrading your \ + ReaLearn installation to the latest version.", + v, + BackboneShell::version() + )); + } + } + let result = self.apply_to_model_internal(&mut session, Rc::downgrade(shared_session)); + notification::notify_user_on_anyhow_error(result); + // Notify + session.notify_everything_has_changed(); + session.notify_compartment_loaded(CompartmentKind::Main); + session.notify_compartment_loaded(CompartmentKind::Controller); + Ok(()) + } + + /// Applies this session data to the given session. + /// + /// Doesn't notify listeners! Consumers must inform session that everything has changed. + /// + /// # Errors + /// + /// Returns and error if this session data is invalid. + #[allow(unused_variables)] + fn apply_to_model_internal( + &self, + session: &mut UnitModel, + weak_session: WeakUnitModel, + ) -> anyhow::Result<()> { + // Validation + let params = self.create_params(); + let main_conversion_context = SimpleDataToModelConversionContext::from_session_or_random( + &self.groups, + &self.mappings, + Some(CompartmentInUnit::new(session, CompartmentKind::Main)), + ); + ensure_no_duplicate_compartment_data( + &self.mappings, + &self.groups, + self.parameters.values().map(|p| &p.setting), + )?; + ensure_no_duplicate_compartment_data( + &self.controller_mappings, + &self.controller_groups, + self.controller_parameters.values().map(|p| &p.setting), + )?; + let (control_input, wants_keyboard_input_legacy) = match self.control_device_id.as_ref() { + None => (ControlInput::Midi(MidiControlInput::FxInput), false), + Some(dev_id) => { + use ControlDeviceId::*; + match dev_id { + Keyboard(_) => (ControlInput::Midi(MidiControlInput::FxInput), true), + Midi(midi_dev_id_string) => { + let raw_midi_dev_id = midi_dev_id_string + .parse::() + .context("invalid MIDI input device ID")?; + let midi_dev_id = MidiInputDeviceId::new(raw_midi_dev_id); + ( + ControlInput::Midi(MidiControlInput::Device(midi_dev_id)), + false, + ) + } + Osc(osc_dev_id) => (ControlInput::Osc(*osc_dev_id), false), + } + } + }; + let feedback_output = match self.feedback_device_id.as_ref() { + None => None, + Some(dev_id) => { + use FeedbackDeviceId::*; + let output = match dev_id { + MidiOrFxOutput(s) if s == "fx-output" => { + FeedbackOutput::Midi(MidiDestination::FxOutput) + } + MidiOrFxOutput(midi_dev_id_string) => { + let midi_dev_id = midi_dev_id_string + .parse::() + .map(MidiOutputDeviceId::new) + .context("invalid MIDI output device ID")?; + FeedbackOutput::Midi(MidiDestination::Device(midi_dev_id)) + } + Osc(osc_dev_id) => FeedbackOutput::Osc(*osc_dev_id), + }; + Some(output) + } + }; + let main_mapping_snapshot_container = convert_mapping_snapshots_to_model( + &self.mapping_snapshots, + &self.main.active_mapping_snapshots, + &main_conversion_context, + )?; + let controller_mapping_snapshot_container = convert_mapping_snapshots_to_model( + &self.controller_mapping_snapshots, + &self.controller.active_mapping_snapshots, + &main_conversion_context, + )?; + // Mutation + let migration_descriptor = MigrationDescriptor::new(self.version.as_ref()); + session.lives_on_upper_floor.set(self.lives_on_upper_floor); + session + .send_feedback_only_if_armed + .set_without_notification(self.send_feedback_only_if_armed); + session + .reset_feedback_when_releasing_source + .set_without_notification(self.reset_feedback_when_releasing_source); + session + .control_input + .set_without_notification(control_input); + session + .feedback_output + .set_without_notification(feedback_output); + let _ = session.change(UnitCommand::SetWantsKeyboardInput( + self.wants_keyboard_input || wants_keyboard_input_legacy, + )); + let _ = session.change(UnitCommand::SetStreamDeckDevice(self.stream_deck_device_id)); + let _ = session.change(UnitCommand::SetMatchEvenInactiveMappings( + self.match_even_inactive_mappings, + )); + // Let events through or not + { + let is_old_preset = self + .version + .as_ref() + .map(|v| v < &Version::parse("2.10.0-pre.10").unwrap()) + .unwrap_or(true); + let (matched, unmatched) = if is_old_preset && session.control_input().is_midi_device() + { + // Old presets using MIDI device input didn't support global MIDI filtering. For + // backward compatibility, make sure that all messages are let through then! + (true, true) + } else if reaper_supports_global_midi_filter() { + // This is a new preset and REAPER supports global MIDI filtering. + ( + self.let_matched_events_through, + self.let_unmatched_events_through, + ) + } else { + // This is a new preset but REAPER doesn't support global MIDI filtering. + (true, true) + }; + session + .let_matched_events_through + .set_without_notification(matched); + session + .let_unmatched_events_through + .set_without_notification(unmatched); + let stay_active_when_project_in_background = self + .stay_active_when_project_in_background + .unwrap_or(StayActiveWhenProjectInBackground::Always); + session + .stay_active_when_project_in_background + .set_without_notification(stay_active_when_project_in_background); + } + // Groups + let controller_conversion_context = + SimpleDataToModelConversionContext::from_session_or_random( + &self.controller_groups, + &self.controller_mappings, + Some(CompartmentInUnit::new(session, CompartmentKind::Controller)), + ); + let conversion_context = |compartment: CompartmentKind| match compartment { + CompartmentKind::Controller => &controller_conversion_context, + CompartmentKind::Main => &main_conversion_context, + }; + let get_final_default_group = + |def_group: Option<&GroupModelData>, compartment: CompartmentKind| { + def_group + .map(|g| g.to_model(compartment, true, conversion_context(compartment))) + .unwrap_or_else(|| GroupModel::default_for_compartment(compartment)) + }; + let main_default_group = + get_final_default_group(self.default_group.as_ref(), CompartmentKind::Main); + let controller_default_group = get_final_default_group( + self.default_controller_group.as_ref(), + CompartmentKind::Controller, + ); + session + .default_group(CompartmentKind::Main) + .replace(main_default_group); + let main_groups: Vec<_> = self + .groups + .iter() + .map(|g| { + g.to_model( + CompartmentKind::Main, + false, + conversion_context(CompartmentKind::Main), + ) + }) + .collect(); + let controller_groups: Vec<_> = self + .controller_groups + .iter() + .map(|g| { + g.to_model( + CompartmentKind::Controller, + false, + conversion_context(CompartmentKind::Controller), + ) + }) + .collect(); + session.set_groups_without_notification(CompartmentKind::Main, main_groups); + session + .default_group(CompartmentKind::Controller) + .replace(controller_default_group); + session.set_groups_without_notification(CompartmentKind::Controller, controller_groups); + // Mappings + + let mut apply_mappings = + |compartment, mappings: &Vec| -> anyhow::Result<()> { + let mappings: Result, _> = mappings + .iter() + .map(|m| { + m.to_model_flexible( + compartment, + &migration_descriptor, + self.version.as_ref(), + conversion_context(compartment), + Some(session.extended_context_with_params(¶ms)), + ) + }) + .collect(); + session.set_mappings_without_notification(compartment, mappings?); + Ok(()) + }; + apply_mappings(CompartmentKind::Main, &self.mappings)?; + apply_mappings(CompartmentKind::Controller, &self.controller_mappings)?; + let _ = session.change(UnitCommand::ChangeCompartment( + CompartmentKind::Controller, + CompartmentCommand::SetCommonLua(self.controller_common_lua.clone()), + )); + let _ = session.change(UnitCommand::ChangeCompartment( + CompartmentKind::Main, + CompartmentCommand::SetCommonLua(self.main_common_lua.clone()), + )); + let _ = session.change(UnitCommand::ChangeCompartment( + CompartmentKind::Controller, + CompartmentCommand::SetNotes(self.controller_notes.clone()), + )); + let _ = session.change(UnitCommand::ChangeCompartment( + CompartmentKind::Main, + CompartmentCommand::SetNotes(self.main_notes.clone()), + )); + session.set_active_controller_id_without_notification(self.active_controller_id.clone()); + session.set_active_main_preset_id_without_notification(self.active_main_preset_id.clone()); + session + .auto_load_mode + .set_without_notification(self.main_preset_auto_load_mode); + let _ = session.change(UnitCommand::SetUnitTags(self.tags.clone())); + session.set_instance_preset_link_config(self.instance_preset_link_config.clone()); + session.set_use_unit_preset_links_only(self.use_instance_preset_links_only); + if let Some(id) = &self.id { + let _ = session.change(UnitCommand::SetUnitKey(id.clone())); + }; + let _ = session.change(UnitCommand::SetEnabled(self.enabled)); + let _ = session.change(UnitCommand::SetUnitName(self.name.clone())); + let _ = session.change(UnitCommand::SetInstanceTrack(self.instance_track.clone())); + let _ = session.change(UnitCommand::SetInstanceFx(self.instance_fx.clone())); + let (auto_load_fallback_preset_id, auto_load_fallback_compartment_model) = + match &self.auto_load_fallback { + None => (None, None), + Some(AutoLoadFallbackData::Preset(id)) => (Some(id.clone()), None), + Some(AutoLoadFallbackData::Compartment(data)) => { + let compartment_model = + data.to_model(self.version.as_ref(), CompartmentKind::Main, Some(session))?; + (None, Some(compartment_model)) + } + }; + session.set_auto_load_fallback_preset_id(auto_load_fallback_preset_id); + session.set_auto_load_fallback_compartment_model(auto_load_fallback_compartment_model); + // Instance state (don't borrow sooner because the session methods might also borrow it) + { + let unit = session.unit().clone(); + let mut unit = unit.borrow_mut(); + unit.set_custom_compartment_data( + CompartmentKind::Controller, + self.controller_custom_data.clone(), + ); + unit.set_custom_compartment_data(CompartmentKind::Main, self.main_custom_data.clone()); + unit.parameter_manager().set_all_parameters(params); + unit.set_active_instance_tags_without_notification(self.active_instance_tags.clone()); + // Compartment-specific + // Active mapping by group + unit.set_active_mapping_by_group( + CompartmentKind::Controller, + self.controller.active_mapping_by_group.clone(), + ); + unit.set_active_mapping_by_group( + CompartmentKind::Main, + self.main.active_mapping_by_group.clone(), + ); + // Active mapping tags + unit.set_active_mapping_tags( + CompartmentKind::Controller, + self.controller.active_mapping_tags.clone(), + ); + unit.set_active_mapping_tags( + CompartmentKind::Main, + self.main.active_mapping_tags.clone(), + ); + // Mapping snapshots (contents) and IDs + unit.set_mapping_snapshot_container( + CompartmentKind::Main, + main_mapping_snapshot_container, + ); + unit.set_mapping_snapshot_container( + CompartmentKind::Controller, + controller_mapping_snapshot_container, + ); + } + update_auto_units_async(); + Ok(()) + } + + fn create_params(&self) -> PluginParams { + let mut params = PluginParams::default(); + fill_compartment_params( + &self.parameters, + params.compartment_params_mut(CompartmentKind::Main), + ); + fill_compartment_params( + &self.controller_parameters, + params.compartment_params_mut(CompartmentKind::Controller), + ); + params + } +} + +fn fill_compartment_params( + data: &NonCryptoHashMap, + model: &mut CompartmentParams, +) { + for (index_string, p) in data.iter() { + let index = index_string + .parse::() + .ok() + .and_then(|i| CompartmentParamIndex::try_from(i).ok()); + if let Some(i) = index { + let param = Param::new(p.setting.clone(), p.value); + *model.at_mut(i) = param; + } + } +} + +fn get_parameter_data_map( + plugin_params: &PluginParams, + compartment: CompartmentKind, +) -> NonCryptoHashMap { + let compartment_params = plugin_params.compartment_params(compartment); + compartment_param_index_iter() + .filter_map(|i| { + let param = compartment_params.at(i); + let value = param.raw_value(); + let setting = param.setting(); + if value == 0.0 && setting.name.is_empty() { + return None; + } + let data = ParameterData { + setting: setting.clone(), + value, + }; + Some((i.to_string(), data)) + }) + .collect() +} + +impl ModelToDataConversionContext for CompartmentInUnit<'_> { + fn non_default_group_key_by_id(&self, group_id: GroupId) -> Option { + let group = self.unit.find_group_by_id(self.compartment, group_id)?; + Some(group.borrow().key().clone()) + } + + fn mapping_key_by_id(&self, mapping_id: MappingId) -> Option { + let mapping = self.unit.find_mapping_by_id(self.compartment, mapping_id)?; + Some(mapping.borrow().key().clone()) + } +} + +impl DataToModelConversionContext for CompartmentInUnit<'_> { + fn non_default_group_id_by_key(&self, key: &GroupKey) -> Option { + let group = self.unit.find_group_by_key(self.compartment, key)?; + Some(group.borrow().id()) + } + + fn mapping_id_by_key(&self, key: &MappingKey) -> Option { + self.unit.find_mapping_id_by_key(self.compartment, key) + } +} + +impl ApiToDataConversionContext for CompartmentInUnit<'_> { + fn compartment(&self) -> CompartmentKind { + self.compartment + } + + fn param_index_by_key(&self, key: &str) -> Option { + let (i, _) = self + .unit + .params() + .compartment_params(self.compartment) + .find_setting_by_key(key)?; + Some(i) + } +} + +/// Consists of methods that return a persistent business ID ("key") for a given transient technical +/// ID ("ID"). +pub trait ModelToDataConversionContext { + fn group_key_by_id(&self, group_id: GroupId) -> Option { + if group_id.is_default() { + return Some(GroupKey::default()); + } + self.non_default_group_key_by_id(group_id) + } + + fn non_default_group_key_by_id(&self, group_id: GroupId) -> Option; + + fn mapping_key_by_id(&self, mapping_id: MappingId) -> Option; +} + +/// Consists of methods that return a transient technical ID ("ID") for a given persistent +/// business ID ("key"). +pub trait DataToModelConversionContext { + fn group_id_by_key(&self, key: &GroupKey) -> Option { + if key.is_empty() { + return Some(GroupId::default()); + } + self.non_default_group_id_by_key(key) + } + + fn non_default_group_id_by_key(&self, key: &GroupKey) -> Option; + + fn mapping_id_by_key(&self, key: &MappingKey) -> Option; +} + +/// Defines a direct translation from keys to IDs. +pub struct SimpleDataToModelConversionContext { + group_id_by_key: NonCryptoHashMap, + mapping_id_by_key: NonCryptoHashMap, +} + +impl SimpleDataToModelConversionContext { + /// Prefers IDs from existing session if it can find a group/mapping with the same key but + /// generates new, random IDs if it doesn't. + /// + /// It's important that IDs are picked up from the session because we have some data that is + /// not part of the compartment but the session, which is not going to replaced but refers to + /// existing technical mapping IDs, for example mapping snapshots. If we would always come up + /// with random technical IDs, these snapshots would immediately get orphans. See + /// https://github.com/helgoboss/helgobox/issues/652. + pub fn from_session_or_random( + groups: &[GroupModelData], + mappings: &[MappingModelData], + compartment_in_session: Option, + ) -> Self { + Self { + group_id_by_key: groups + .iter() + .map(|g| { + let technical_id = compartment_in_session + .and_then(|cs| cs.group_id_by_key(&g.id)) + .unwrap_or_else(GroupId::random); + (g.id.clone(), technical_id) + }) + .collect(), + mapping_id_by_key: mappings + .iter() + .filter_map(|m| { + let key = m.id.as_ref()?; + let technical_id = compartment_in_session + .and_then(|cs| cs.mapping_id_by_key(key)) + .unwrap_or_else(MappingId::random); + Some((key.clone(), technical_id)) + }) + .collect(), + } + } +} + +impl DataToModelConversionContext for SimpleDataToModelConversionContext { + fn non_default_group_id_by_key(&self, key: &GroupKey) -> Option { + self.group_id_by_key.get(key).copied() + } + + fn mapping_id_by_key(&self, key: &MappingKey) -> Option { + self.mapping_id_by_key.get(key).copied() + } +} + +fn convert_mapping_snapshots_to_api( + session: &UnitModel, + instance_state: &Unit, + compartment: CompartmentKind, +) -> Vec { + let compartment_in_session = CompartmentInUnit::new(session, compartment); + convert_mapping_snapshots_to_api_internal( + instance_state.mapping_snapshot_container(compartment), + &compartment_in_session, + ) +} + +fn convert_mapping_snapshots_to_api_internal( + container: &MappingSnapshotContainer, + conversion_context: &impl ModelToDataConversionContext, +) -> Vec { + container + .snapshots() + .map(|(snapshot_id, snapshot)| MappingSnapshot { + id: snapshot_id.to_string(), + mappings: snapshot + .target_values() + .filter_map(|(mapping_id, target_value)| { + let m = MappingInSnapshot { + id: conversion_context.mapping_key_by_id(mapping_id)?.into(), + target_value: convert_target_value_to_api(target_value), + }; + Some(m) + }) + .collect(), + }) + .collect() +} + +fn convert_mapping_snapshots_to_model( + api_snapshots: &[MappingSnapshot], + active_snapshot_id_by_tag: &NonCryptoHashMap, + conversion_context: &impl DataToModelConversionContext, +) -> anyhow::Result { + let snapshots: anyhow::Result< + NonCryptoHashMap, + > = api_snapshots + .iter() + .map(|api_snapshot| { + let id: MappingSnapshotId = api_snapshot.id.parse().map_err(anyhow::Error::msg)?; + let target_values: anyhow::Result> = api_snapshot + .mappings + .iter() + .map(|api_mapping| { + let mapping_key: MappingKey = api_mapping.id.clone().into(); + let id: MappingId = conversion_context + .mapping_id_by_key(&mapping_key) + .context("couldn't find mapping with key")?; + let absolute_value = convert_target_value_to_model(&api_mapping.target_value)?; + Ok((id, absolute_value)) + }) + .collect(); + let snapshot = crate::domain::MappingSnapshot::new(target_values?); + Ok((id, snapshot)) + }) + .collect(); + Ok(MappingSnapshotContainer::new( + snapshots?, + active_snapshot_id_by_tag.clone(), + )) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/data/virtual_control.rs b/plugin-reaper-realearn/main/src/infrastructure/data/virtual_control.rs new file mode 100644 index 0000000..6027377 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/data/virtual_control.rs @@ -0,0 +1,31 @@ +use crate::domain::VirtualControlElementId; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum VirtualControlElementIdData { + Indexed(u32), + Named(String), +} + +impl Default for VirtualControlElementIdData { + fn default() -> Self { + VirtualControlElementIdData::Indexed(0) + } +} + +impl VirtualControlElementIdData { + pub fn from_model(model: VirtualControlElementId) -> Self { + match model { + VirtualControlElementId::Indexed(i) => VirtualControlElementIdData::Indexed(i), + VirtualControlElementId::Named(n) => VirtualControlElementIdData::Named(n.to_string()), + } + } + + pub fn to_model(&self) -> VirtualControlElementId { + match self { + VirtualControlElementIdData::Indexed(i) => VirtualControlElementId::Indexed(*i), + VirtualControlElementIdData::Named(n) => n.parse().unwrap_or_default(), + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/mod.rs new file mode 100644 index 0000000..96cc3b2 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/mod.rs @@ -0,0 +1,7 @@ +mod api; +mod data; +mod plugin; +mod proto; +mod server; +mod test; +mod ui; diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/actions.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/actions.rs new file mode 100644 index 0000000..53c08fa --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/actions.rs @@ -0,0 +1,253 @@ +use crate::base::notification::alert; +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::test::run_test; +use enumflags2::make_bitflags; +use reaper_high::{ActionKind, KeyBinding, KeyBindingKind, Reaper}; +use reaper_medium::{AcceleratorBehavior, AcceleratorKeyCode}; +use std::ffi::CStr; +use swell_ui::menu_tree::{item, menu, Entry}; + +pub const ACTION_SHOW_HIDE_PLAYTIME_COMMAND_NAME: &str = "HB_SHOW_HIDE_PLAYTIME"; +pub const ACTION_SHOW_HIDE_CUSTOM_PLAYTIME_COMMAND_NAME: &str = "HB_SHOW_HIDE_CUSTOM_PLAYTIME"; +pub const ACTION_TOGGLE_APP_FOCUS_COMMAND_NAME: &str = "HB_TOGGLE_APP_FOCUS"; +pub const ACTION_SHOW_WELCOME_SCREEN_LABEL: &str = "Show welcome screen"; + +pub const ACTION_DEFS: &[ActionDef] = &[ + ActionDef { + section: ActionSection::General, + command_name: "HB_SHOW_WELCOME_SCREEN", + action_name: ACTION_SHOW_WELCOME_SCREEN_LABEL, + op: BackboneShell::show_welcome_screen, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::General, + command_name: "REALEARN_RESOLVE_SYMBOLS", + action_name: "Resolve symbols from clipboard", + op: BackboneShell::resolve_symbols_from_clipboard, + developer: true, + requires_instance: false, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::General, + command_name: "REALEARN_INTEGRATION_TEST", + action_name: "Run integration test", + op: run_test, + developer: true, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::ReaLearn, + command_name: "realearnLearnSourceForLastTouchedTarget", + action_name: "Learn source for last touched target (reassigning target)", + op: BackboneShell::learn_source_for_last_touched_target, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::ReaLearn, + command_name: "REALEARN_LEARN_MAPPING_REASSIGNING_SOURCE", + action_name: "Learn single mapping (reassigning source)", + op: BackboneShell::learn_mapping_reassigning_learnable_source, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::ReaLearn, + command_name: "REALEARN_LEARN_MAPPING_REASSIGNING_SOURCE_OPEN", + action_name: "Learn single mapping (reassigning source) and open it", + op: BackboneShell::learn_mapping_reassigning_learnable_source_open, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::ReaLearn, + command_name: "REALEARN_FIND_FIRST_MAPPING_BY_SOURCE", + action_name: "Find first mapping by source", + op: BackboneShell::find_first_mapping_by_learnable_source, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::ReaLearn, + command_name: "REALEARN_FIND_FIRST_MAPPING_BY_TARGET", + action_name: "Find first mapping by target", + op: BackboneShell::find_first_mapping_by_target, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::ReaLearn, + command_name: "REALEARN_SEND_ALL_FEEDBACK", + action_name: "Send feedback for all instances", + op: BackboneShell::send_feedback_for_all_instances, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::ReaLearn, + command_name: "REALEARN_DETECT_CONTROLLERS_WITH_LOGGING", + action_name: "Auto-detect controllers (with logging)", + op: BackboneShell::detect_controllers_with_logging, + requires_instance: true, + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::Playtime, + command_name: ACTION_SHOW_HIDE_PLAYTIME_COMMAND_NAME, + action_name: "Show/hide Playtime", + op: BackboneShell::show_hide_playtime, + add_toolbar_button: true, + icon: Some(c"toolbar_playtime"), + default_key_binding: Some(KeyBinding { + behavior: make_bitflags!(AcceleratorBehavior::{Shift | Control | VirtKey}), + key_code: AcceleratorKeyCode::new(b'P' as _), + kind: KeyBindingKind::GlobalText, + }), + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::Playtime, + command_name: ACTION_SHOW_HIDE_CUSTOM_PLAYTIME_COMMAND_NAME, + action_name: "Show/hide custom Playtime", + op: BackboneShell::show_hide_custom_playtime, + icon: Some(c"toolbar_playtime_custom"), + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::General, + command_name: ACTION_TOGGLE_APP_FOCUS_COMMAND_NAME, + action_name: "Toggle app focus", + op: BackboneShell::toggle_app_focus, + default_key_binding: Some(KeyBinding { + behavior: make_bitflags!(AcceleratorBehavior::{Shift | Control | VirtKey}), + key_code: AcceleratorKeyCode::new(b'H' as _), + kind: KeyBindingKind::GlobalText, + }), + ..DEFAULT_DEF + }, + ActionDef { + section: ActionSection::SoundPot, + command_name: "REALEARN_OPEN_FIRST_POT_BROWSER", + action_name: "Open first Pot Browser", + op: BackboneShell::open_first_pot_browser, + requires_instance: true, + ..DEFAULT_DEF + }, + #[cfg(debug_assertions)] + ActionDef { + section: ActionSection::General, + command_name: "HB_PANIC", + action_name: "Simulate error", + developer: true, + op: crate::infrastructure::plugin::sandbox::simulate_error, + ..DEFAULT_DEF + }, +]; + +pub struct ActionDef { + pub section: ActionSection, + pub command_name: &'static str, + pub action_name: &'static str, + pub op: fn(), + pub developer: bool, + pub requires_instance: bool, + pub default_key_binding: Option, + pub icon: Option<&'static CStr>, + pub add_toolbar_button: bool, +} + +const DEFAULT_DEF: ActionDef = ActionDef { + section: ActionSection::General, + command_name: "", + action_name: "", + op: || {}, + developer: false, + requires_instance: false, + default_key_binding: None, + icon: None, + add_toolbar_button: false, +}; + +impl ActionDef { + pub fn register(&self) { + let requires_instance = self.requires_instance; + let op = self.op; + Reaper::get().register_action( + self.command_name, + self.build_full_action_name(), + self.default_key_binding, + move || { + if requires_instance && BackboneShell::get().instance_count() == 0 { + alert("Please add a Helgobox plug-in instance first!"); + return; + } + op(); + }, + ActionKind::NotToggleable, + ); + } + + pub fn should_appear_in_menu(&self) -> bool { + !self.developer || cfg!(debug_assertions) + } + + pub fn build_full_action_name(&self) -> String { + format!( + "{}Helgobox/{}: {}{}", + self.developer_prefix(), + self.section, + self.action_name, + self.instance_suffix(), + ) + } + + pub fn build_menu_item(&self) -> Entry<&'static str> { + item( + format!( + "{}{}{}", + self.developer_prefix(), + self.action_name, + self.instance_suffix() + ), + self.command_name, + ) + } + + pub fn developer_prefix(&self) -> &'static str { + if self.developer { + "[developer] " + } else { + "" + } + } + + pub fn instance_suffix(&self) -> &'static str { + "" + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, strum::Display, strum::EnumIter)] +pub enum ActionSection { + General, + ReaLearn, + Playtime, + #[strum(serialize = "SoundPot (experimental)")] + SoundPot, +} + +impl ActionSection { + pub fn build_menu(&self) -> Option> { + let items: Vec<_> = ACTION_DEFS + .iter() + .filter(|def| def.section == *self && def.should_appear_in_menu()) + .map(|def| def.build_menu_item()) + .collect(); + if items.is_empty() { + return None; + } + Some(menu(self.to_string(), items)) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/api_impl.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/api_impl.rs new file mode 100644 index 0000000..d47dd86 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/api_impl.rs @@ -0,0 +1,109 @@ +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::ui::AppPage; +use anyhow::Context; +use helgobox_api::runtime::{register_helgobox_api, HelgoboxApi}; +use reaper_high::{OrCurrentProject, Project, Reaper}; +use reaper_low::raw::ReaProject; +use reaper_medium::{ReaperStr, RegistrationObject}; +use std::borrow::Cow; +use std::ffi::c_int; + +struct HelgoboxAllApi; + +impl HelgoboxApi for HelgoboxAllApi { + extern "C" fn HB_FindFirstHelgoboxInstanceInProject(project: *mut ReaProject) -> c_int { + find_first_helgobox_instance_in_project(project).unwrap_or(-1) + } +} + +impl playtime_api::runtime::PlaytimeApi for HelgoboxAllApi { + extern "C" fn HB_FindFirstPlaytimeHelgoboxInstanceInProject(project: *mut ReaProject) -> c_int { + find_first_playtime_helgobox_instance_in_project(project).unwrap_or(-1) + } + + extern "C" fn HB_CreateClipMatrix(instance_id: c_int) { + let _ = create_clip_matrix(instance_id); + } + + extern "C" fn HB_ShowOrHidePlaytime(instance_id: c_int) { + let _ = show_or_hide_playtime(instance_id); + } +} + +fn find_first_playtime_helgobox_instance_in_project( + project: *mut ReaProject, +) -> anyhow::Result { + let project = reaper_medium::ReaProject::new(project) + .map(Project::new) + .or_current_project(); + let instance_id = BackboneShell::get() + .find_first_playtime_helgobox_instance_in_project(project) + .context("Project doesn't contain Helgobox instance with a Playtime matrix")?; + Ok(u32::from(instance_id) as _) +} + +fn find_first_helgobox_instance_in_project(project: *mut ReaProject) -> anyhow::Result { + let project = reaper_medium::ReaProject::new(project) + .map(Project::new) + .or_current_project(); + let instance_id = BackboneShell::get() + .find_first_helgobox_instance_in_project(project) + .context("Project doesn't contain Helgobox instance")?; + Ok(u32::from(instance_id) as _) +} + +fn create_clip_matrix(instance_id: c_int) -> anyhow::Result<()> { + let instance_id = u32::try_from(instance_id)?.into(); + let instance_shell = BackboneShell::get() + .find_instance_shell_by_instance_id(instance_id) + .context("instance not found")?; + instance_shell.insert_owned_clip_matrix_if_necessary()?; + Ok(()) +} + +fn show_or_hide_playtime(instance_id: c_int) -> anyhow::Result<()> { + let instance_id = u32::try_from(instance_id)?; + let instance_panel = BackboneShell::get() + .find_instance_panel_by_instance_id(instance_id.into()) + .context("Instance not found")?; + instance_panel.start_show_or_hide_app_instance(Some(AppPage::Playtime)); + Ok(()) +} + +pub fn register_api() -> anyhow::Result<()> { + let mut session = Reaper::get().medium_session(); + register_or_unregister_api(|reg| unsafe { + session.plugin_register_add(reg)?; + Ok(()) + }) +} + +pub fn unregister_api() -> anyhow::Result<()> { + let mut session = Reaper::get().medium_session(); + register_or_unregister_api(|reg| unsafe { + session.plugin_register_remove(reg); + Ok(()) + }) +} + +fn register_or_unregister_api( + mut op: impl FnMut(RegistrationObject) -> anyhow::Result<()>, +) -> anyhow::Result<()> { + register_helgobox_api::(|name, ptr| unsafe { + op(RegistrationObject::Api( + Cow::Borrowed(ReaperStr::from_ptr(name.as_ptr())), + ptr, + ))?; + Ok(()) + })?; + playtime_api::runtime::register_playtime_api::( + |name, ptr| unsafe { + op(RegistrationObject::Api( + Cow::Borrowed(ReaperStr::from_ptr(name.as_ptr())), + ptr, + ))?; + Ok(()) + }, + )?; + Ok(()) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/auto_units.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/auto_units.rs new file mode 100644 index 0000000..7e0a38d --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/auto_units.rs @@ -0,0 +1,428 @@ +use crate::application::{ + AutoUnitData, ControllerPresetUsage, ControllerSuitability, MainPresetSuitability, +}; +use crate::base::notification::notify_user_on_anyhow_error; +use crate::domain::{parse_hex_string, DeviceControlInput, DeviceFeedbackOutput, OscDeviceId}; +use crate::infrastructure::data::PresetInfo; +use crate::infrastructure::plugin::{BackboneShell, InstanceShellInfo}; +use anyhow::Context; +use base::byte_pattern::BytePattern; +use base::hash_util::NonCryptoHashMap; +use base::Global; +use helgobox_api::persistence::{ + Controller, ControllerConnection, ControllerPresetMetaData, MainPresetMetaData, + MidiControllerConnection, MidiPortPattern, +}; +use reaper_high::{MidiInputDevice, MidiOutputDevice, Reaper}; +use reaper_medium::{MidiInputDeviceId, MidiOutputDeviceId}; +use std::cmp::Ordering; +use std::str::FromStr; +use wildmatch::WildMatch; + +/// To be called whenever some event might lead to addition/removal of auto units. +pub fn update_auto_units_async() { + Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(|| { + update_auto_units(); + }) + .unwrap(); +} + +fn update_auto_units() { + tracing::debug!("Updating auto units..."); + // Get a list of all enabled controllers + let controller_manager = BackboneShell::get().controller_manager().borrow(); + let controllers = controller_manager + .controller_config() + .controllers + .iter() + .filter(|c| c.enabled); + // Build global auto units + let mut global_auto_units: NonCryptoHashMap<_, _> = controllers + .filter_map(build_auto_unit_from_controller) + .map(|au| (au.controller_id.clone(), au)) + .collect(); + // Sort all instances in a project-first + let mut project_first_instances = + BackboneShell::get().with_instance_shell_infos(|infos| infos.to_vec()); + project_first_instances.sort_unstable_by(instance_comparator); + // Give each instance the chance to give a veto for a global auto unit. Or even instantiate + // its own local auto unit, in which case the project-first ordering is important + // (because project instances have priority over monitoring FX instances when it comes to + // controller overrides). + for instance_shell in project_first_instances + .iter() + .filter_map(|i| i.instance_shell.upgrade()) + { + instance_shell.judge_auto_unit_candidates(&mut global_auto_units); + } + // Distribute the remaining global auto units in reverse order (monitoring FX first). + // Reason: We want the global units to be as long-lived as possible. If there's a ReaLearn + // instance on the monitoring FX chain and global control is enabled for it, it should get + // most of the global auto units. + for instance_shell in project_first_instances + .iter() + .rev() + .filter_map(|i| i.instance_shell.upgrade()) + { + let result = instance_shell.apply_auto_units(&mut global_auto_units); + notify_user_on_anyhow_error(result); + } +} + +fn build_auto_unit_from_controller(controller: &Controller) -> Option { + // Ignore if no connection info or no main preset + let connection = controller.connection.as_ref()?; + let main_preset_id = controller.default_main_preset.as_ref()?; + let main_preset_manager = BackboneShell::get().main_preset_manager().borrow(); + let main_preset_info = main_preset_manager.find_preset_info_by_id(main_preset_id.get())?; + // Choose suitable controller preset + let default_controller_preset_id = controller + .default_controller_preset + .as_ref() + .map(|id| id.get().to_string()); + let controller_preset_usage = choose_suitable_controller_preset_id( + connection, + main_preset_info, + default_controller_preset_id, + ) + .ok()?; + // Translate connection info + let (input, output) = translate_connection_info(connection); + // Ignore if neither input nor output given + if input.is_none() && output.is_none() { + return None; + } + // Ignore if input not connected + if let Some(input) = input { + if !input_is_connected(input) { + return None; + } + } + // Ignore if output not connected + if let Some(output) = output { + if !output_is_connected(output) { + return None; + } + } + // Build data + let data = AutoUnitData { + controller_id: controller.id.clone(), + controller_palette_color: controller.palette_color, + controller_preset_usage, + input, + output, + main_preset_id: main_preset_id.get().to_string(), + }; + Some(data) +} + +fn choose_suitable_controller_preset_id( + connection: &ControllerConnection, + main_preset_info: &PresetInfo, + default_controller_preset_id: Option, +) -> anyhow::Result> { + let controller_preset_manager = BackboneShell::get().controller_preset_manager().borrow(); + if let Some(id) = &default_controller_preset_id { + // Check if default preset meets main preset requirements. Then simply use that one. + let controller_preset_info = controller_preset_manager + .find_preset_info_by_id(id) + .context("default controller preset not found")?; + let main_preset_suitability = get_suitability_of_controller_preset_for_main_preset( + &controller_preset_info.specific_meta_data, + &main_preset_info.specific_meta_data, + ); + if main_preset_suitability.is_generally_suitable() { + let usage = ControllerPresetUsage { + controller_preset_id: id.clone(), + main_preset_suitability: Some(main_preset_suitability), + // The user should make sure that it suites his controller, this won't be checked. + controller_suitability: None, + }; + return Ok(Some(usage)); + } + } + // Default controller preset not given or doesn't meet requirements. Search for a suitable one. + let chosen_candidate = controller_preset_manager + .preset_infos() + .iter() + .filter_map(|info| { + let main_preset_suitability = get_suitability_of_controller_preset_for_main_preset( + &info.specific_meta_data, + &main_preset_info.specific_meta_data, + ); + if !main_preset_suitability.is_generally_suitable() { + return None; + } + Some((info, main_preset_suitability)) + }) + // We have a list of controller presets that are suitable in terms of main preset + // requirements. Now we need to look for the ones that match our controller. + .filter_map(|(info, main_preset_suitability)| { + let controller_suitability = get_suitability_of_controller_preset_for_controller( + &info.specific_meta_data, + connection, + ); + if controller_suitability == ControllerSuitability::NotSuitable { + return None; + } + Some((info, main_preset_suitability, controller_suitability)) + }) + // We have a list of candidates. Now do the ranking. Take the one with the highest combined + // suitability. + .max_by_key(|(_, main_preset_suitability, controller_suitability)| { + (*main_preset_suitability, *controller_suitability) + }); + let usage = chosen_candidate + .map( + |(info, main_preset_suitability, controller_suitability)| ControllerPresetUsage { + controller_preset_id: info.common.id.clone(), + main_preset_suitability: Some(main_preset_suitability), + controller_suitability: Some(controller_suitability), + }, + ) + .or_else(|| { + // No suitable preset found. Not good but maybe the default controller preset just misses + // the necessary meta data. So choose that one and hope that it works. + let controller_preset_id = default_controller_preset_id?; + let usage = ControllerPresetUsage { + controller_preset_id, + main_preset_suitability: None, + controller_suitability: None, + }; + Some(usage) + }); + Ok(usage) +} + +/// The returned number is the number of matching schemes. The higher the number, the more suitable. +fn get_suitability_of_controller_preset_for_main_preset( + controller_preset_meta_data: &ControllerPresetMetaData, + main_preset_meta_data: &MainPresetMetaData, +) -> MainPresetSuitability { + let coverage = + main_preset_meta_data.calc_scheme_coverage(&controller_preset_meta_data.provided_schemes); + MainPresetSuitability::new(coverage) +} + +fn get_suitability_of_controller_preset_for_controller( + controller_preset_meta_data: &ControllerPresetMetaData, + connection: &ControllerConnection, +) -> ControllerSuitability { + match connection { + ControllerConnection::Midi(con) => { + // This is a MIDI controller + let identity_pattern_suitability = + if let Some(p) = &controller_preset_meta_data.midi_identity_pattern { + get_suitability_of_midi_identity_pattern_for_controller(p, con) + } else { + // Controller preset doesn't define any identity pattern, which could just be laziness + ControllerSuitability::MaybeSuitable + }; + if identity_pattern_suitability.is_not_suitable() { + // Identity doesn't match, this is a no-go + return ControllerSuitability::NotSuitable; + } + let patterns = &controller_preset_meta_data.midi_output_port_patterns; + if patterns.is_empty() { + // Controller preset doesn't define any MIDI output port patterns. This is perfectly valid. + return identity_pattern_suitability; + } + // MIDI output port patterns given. Take them into account as well. + let Some(output_port) = con.output_port else { + // If the controller has not output, it can't match an output port pattern. + return ControllerSuitability::NotSuitable; + }; + let out_dev_id = MidiOutputDeviceId::new(output_port.get() as _); + let Some(out_dev_name) = Reaper::get().midi_output_device_by_id(out_dev_id).name() + else { + // If the controller doesn't have a name / is not available, it can't match an output port pattern. + return ControllerSuitability::NotSuitable; + }; + let out_dev_name = out_dev_name.to_string_lossy(); + if midi_output_port_patterns_match(patterns, &out_dev_name) { + identity_pattern_suitability + } else { + ControllerSuitability::NotSuitable + } + } + ControllerConnection::Osc(_) => { + // This is an OSC controller + if controller_preset_meta_data.midi_identity_pattern.is_some() + || !controller_preset_meta_data + .midi_output_port_patterns + .is_empty() + { + // We have a preset for a MIDI controller but this is an OSC controller. + ControllerSuitability::NotSuitable + } else { + // We don't have any checks for OSC + ControllerSuitability::MaybeSuitable + } + } + } +} + +fn get_suitability_of_midi_identity_pattern_for_controller( + midi_identity_pattern: &str, + connection: &MidiControllerConnection, +) -> ControllerSuitability { + match &connection.identity_response { + None => { + // Controller doesn't expect any specific identity response. + ControllerSuitability::MaybeSuitable + } + Some(identity_response) => { + if midi_identity_response_matches_pattern(identity_response, midi_identity_pattern) { + ControllerSuitability::Suitable + } else { + ControllerSuitability::NotSuitable + } + } + } +} + +fn midi_identity_response_matches_pattern( + identity_response: &str, + midi_identity_pattern: &str, +) -> bool { + match BytePattern::from_str(midi_identity_pattern) { + Ok(byte_pattern) => { + match parse_hex_string(identity_response) { + Ok(identity_response_bytes) => { + if byte_pattern.matches(&identity_response_bytes) { + tracing::debug!("Pattern matches identity response"); + true + } else { + false + } + } + Err(_) => { + // Invalid response + tracing::warn!( + "Invalid MIDI identity response in controller: {identity_response}", + ); + false + } + } + } + Err(_) => { + // Invalid pattern + tracing::warn!( + "Invalid MIDI identity pattern in controller preset: {midi_identity_pattern}", + ); + false + } + } +} + +fn translate_connection_info( + connection: &ControllerConnection, +) -> (Option, Option) { + match connection { + ControllerConnection::Midi(c) => { + let input = c + .input_port + .map(|p| DeviceControlInput::Midi(MidiInputDeviceId::new(p.get() as u8))); + let output = c + .output_port + .map(|p| DeviceFeedbackOutput::Midi(MidiOutputDeviceId::new(p.get() as u8))); + (input, output) + } + ControllerConnection::Osc(c) => { + let dev_id = c + .osc_device_id + .as_ref() + .and_then(|id| OscDeviceId::from_str(id.get()).ok()); + if let Some(dev_id) = dev_id { + ( + Some(DeviceControlInput::Osc(dev_id)), + Some(DeviceFeedbackOutput::Osc(dev_id)), + ) + } else { + (None, None) + } + } + } +} + +fn input_is_connected(input: DeviceControlInput) -> bool { + match input { + DeviceControlInput::Midi(id) => MidiInputDevice::new(id).is_connected(), + DeviceControlInput::Osc(id) => osc_device_is_connected(id), + } +} + +fn output_is_connected(output: DeviceFeedbackOutput) -> bool { + match output { + DeviceFeedbackOutput::Midi(id) => MidiOutputDevice::new(id).is_connected(), + DeviceFeedbackOutput::Osc(id) => osc_device_is_connected(id), + } +} + +fn osc_device_is_connected(_dev_id: OscDeviceId) -> bool { + // No easy way to check with OSC. Just return true for now. + true +} + +/// Order between instances. Starting with the ones in the current project (ascending by track and +/// position in FX chain), then in other projects (ascending by track and position in FX chain) +/// and then on the monitoring FX chain (ascending by position in FX chain). +fn instance_comparator(a: &InstanceShellInfo, b: &InstanceShellInfo) -> Ordering { + use Ordering::*; + // Monitoring FX chain (track == None) trumps track FX chain (track == Some) + let fx_a = a.processor_context.containing_fx(); + let fx_b = b.processor_context.containing_fx(); + match (fx_a.track(), fx_b.track()) { + (None, Some(_)) => Greater, + (Some(_), None) => Less, + (None, None) => { + // Both are on monitoring FX chain. Higher position in chain trumps lower position. + fx_a.index().cmp(&fx_b.index()) + } + (Some(track_a), Some(track_b)) => { + // Both are on tracks. Other project trumps current project. + let current_project = Reaper::get().current_project(); + match ( + track_a.project() == current_project, + track_b.project() == current_project, + ) { + (false, true) => return Greater, + (true, false) => return Less, + _ => {} + }; + // Both instances are in the same project. Master track trumps normal track. + match (track_a.index(), track_b.index()) { + (None, Some(_)) => Greater, + (Some(_), None) => Less, + (None, None) => { + // Both are on master track. Higher position in chain trumps lower position. + fx_a.index().cmp(&fx_b.index()) + } + (Some(index_a), Some(index_b)) => { + // Both are on normal tracks. Now ascending by track and index. + let ord = index_a.cmp(&index_b); + if ord.is_ne() { + return ord; + } + fx_a.index().cmp(&fx_b.index()) + } + } + } + } +} + +pub fn midi_output_port_patterns_match(patterns: &[MidiPortPattern], out_port_name: &str) -> bool { + if patterns.is_empty() { + // If no patterns are defined, all device names are okay + return true; + } + let lower_case_out_port_name = out_port_name.to_lowercase(); + patterns.iter().any(|pattern| { + if !pattern.scope_matches() { + return false; + } + let wild_match = WildMatch::new(&pattern.name_pattern.to_lowercase()); + wild_match.matches(&lower_case_out_port_name) + }) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/backbone_shell.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/backbone_shell.rs new file mode 100644 index 0000000..8a70721 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/backbone_shell.rs @@ -0,0 +1,3409 @@ +use crate::application::{ + RealearnControlSurfaceMainTaskSender, SharedMapping, SharedUnitModel, UnitCommand, UnitModel, + WeakUnitModel, +}; +use crate::base::notification; +use crate::domain::{ + ActionInvokedEvent, AdditionalFeedbackEvent, Backbone, ChangeInstanceFxArgs, + ChangeInstanceTrackArgs, CompartmentKind, ControlSurfaceEventHandler, DeviceDiff, + EnableInstancesArgs, EnableUnitsArgs, Exclusivity, FeedbackAudioHookTask, GroupId, + HelgoboxWindowSnitch, InputDescriptor, InstanceFxChangeRequest, InstanceId, + InstanceTrackChangeRequest, LastTouchedTargetFilter, MainProcessor, MessageCaptureEvent, + MessageCaptureResult, MidiScanResult, ModifyUnitContainerCommonArgs, NormalAudioHookTask, + OscDeviceId, OscFeedbackProcessor, OscFeedbackTask, OscScanResult, ProcessorContext, + QualifiedInstanceEvent, QualifiedMappingId, RealearnAccelerator, RealearnAudioHook, + RealearnControlSurfaceMainTask, RealearnControlSurfaceMiddleware, RealearnTarget, + RealearnTargetState, ReaperTarget, ReaperTargetType, RequestMidiDeviceIdentityCommand, + RequestMidiDeviceIdentityReply, SharedInstance, SharedMainProcessors, SharedRealTimeProcessor, + Tag, UnitContainer, UnitId, UnitOrchestrationEvent, WeakInstance, WeakUnit, GLOBAL_AUDIO_STATE, +}; +use crate::infrastructure::data::{ + CommonCompartmentPresetManager, CompartmentPresetManagerEventHandler, ControllerManager, + ControllerManagerEventHandler, FileBasedControllerPresetManager, FileBasedMainPresetManager, + FileBasedPresetLinkManager, LicenseManager, LicenseManagerEventHandler, OscDevice, + OscDeviceManager, SharedControllerManager, SharedControllerPresetManager, SharedLicenseManager, + SharedMainPresetManager, SharedOscDeviceManager, SharedPresetLinkManager, +}; +use crate::infrastructure::server; +use crate::infrastructure::server::{ + MetricsReporter, RealearnServer, SharedRealearnServer, COMPANION_WEB_APP_URL, +}; +use crate::infrastructure::ui::{ + app_window_is_in_text_entry_mode, is_app_window, menus, MessagePanel, +}; +use base::default_util::is_default; +use base::{ + make_available_globally_in_main_thread_on_demand, spawn_in_main_thread, Global, + NamedChannelSender, SenderToNormalThread, SenderToRealTimeThread, +}; + +use crate::base::allocator::{RealearnAllocatorIntegration, RealearnDeallocator, GLOBAL_ALLOCATOR}; +use crate::base::notification::notify_user_about_anyhow_error; +use crate::infrastructure::plugin::actions::ACTION_DEFS; +use crate::infrastructure::plugin::api_impl::{register_api, unregister_api}; +use crate::infrastructure::plugin::debug_util::resolve_symbols_from_clipboard; +use crate::infrastructure::plugin::dynamic_toolbar::{ + add_or_remove_toolbar_button, custom_toolbar_api_is_available, ToolbarChangeDetector, +}; +use crate::infrastructure::plugin::helgobox_plugin::HELGOBOX_UNIQUE_VST_PLUGIN_ADD_STRING; +use crate::infrastructure::plugin::hidden_helper_panel::HiddenHelperPanel; +use crate::infrastructure::plugin::remote_config::HelgoboxRemoteConfig; +use crate::infrastructure::plugin::tracing_util::TracingHook; +use crate::infrastructure::plugin::{ + built_info, controller_detection, sentry, update_auto_units_async, SharedInstanceShell, + WeakInstanceShell, +}; +use crate::infrastructure::server::services::Services; +use crate::infrastructure::ui::instance_panel::InstancePanel; +use crate::infrastructure::ui::util::open_child_panel; +use crate::infrastructure::ui::welcome_panel::WelcomePanel; +use anyhow::{anyhow, bail, Context}; +use base::future_util::millis; +use base::hash_util::NonCryptoHashSet; +use base::metrics_util::MetricsHook; +use camino::{Utf8Path, Utf8PathBuf}; +use helgobox_allocator::{start_async_deallocation_thread, AsyncDeallocatorCommandReceiver}; +use helgobox_api::persistence::{ + Envelope, FxChainDescriptor, FxDescriptor, InstanceTagKind, TargetTouchCause, TrackDescriptor, + TrackFxChain, VirtualControlElementCharacter, +}; +use itertools::Itertools; +use once_cell::sync::Lazy; +use reaper_high::{ + ChangeEvent, Fx, Guid, MiddlewareControlSurface, PluginInfo, Project, Reaper, Track, +}; +use reaper_low::{raw, register_plugin_destroy_hook, PluginContext, PluginDestroyHook, Swell}; +use reaper_macros::reaper_extension_plugin; +use reaper_medium::{ + AccelMsg, AcceleratorPosition, ActionValueChange, CommandId, Hmenu, HookCustomMenu, + HookPostCommand, HookPostCommand2, Hwnd, HwndInfo, HwndInfoType, MenuHookFlag, + MidiInputDeviceId, MidiOutputDeviceId, ReaProject, ReaperStr, RegistrationHandle, + SectionContext, ToolbarIconMap, WindowContext, +}; +use reaper_rx::{ActionRxHookPostCommand, ActionRxHookPostCommand2}; +use rxrust::prelude::*; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::cell::{Ref, RefCell}; +use std::collections::{HashMap, HashSet}; +use std::future::Future; +use std::rc::{Rc, Weak}; +use std::sync::{Arc, OnceLock}; +use std::thread::JoinHandle; +use std::time::Duration; +use std::{fs, mem}; +use strum::IntoEnumIterator; +use swell_ui::{Menu, SharedView, View, ViewManager, Window}; +use tempfile::TempDir; +use tokio::runtime::Runtime; +use tracing::debug; +use url::Url; + +/// Generates a REAPER-extension-like entry point. It also generates everything that +/// `reaper_vst_plugin!` macro would generate, so we don't need that anymore. +/// +/// This needs some explanation: No, we are not a REAPER extension! This extension entry point will +/// not be called by REAPER (because our shared library is located in the "UserPlugins/FX" +/// directory, not in "UserPlugins", so REAPER will treat us as VST plug-in). It will +/// be called by our own Helgobox Extension. And the reason for this is that we want +/// to eagerly initialize certain things already at REAPER start time, without requiring +/// the user to add a VST plug-in instance first. This is the easiest way and doesn't require +/// us to split much logic between extension lib and VST plug-in lib. +/// +/// If the Helgobox Extension is not installed, this extension entry point will *not* be called. +/// Things will still work though! The only difference is that things are not initialized eagerly. +/// They will be at the time the first plug-in instance is added. +#[reaper_extension_plugin] +fn plugin_main(context: PluginContext) -> Result<(), Box> { + init_backbone_shell(context); + Ok(()) +} + +pub fn init_backbone_shell(context: PluginContext) { + BackboneShell::make_available_globally(|| { + let backbone_shell = BackboneShell::init(context); + register_plugin_destroy_hook(PluginDestroyHook { + name: "BackboneShell", + callback: || { + let backbone_shell = BackboneShell::get(); + let _ = backbone_shell.go_to_sleep(); + backbone_shell.dispose(); + }, + }); + backbone_shell + }); + BackboneShell::get().show_welcome_screen_if_necessary(); +} + +/// Queue size for sending feedback tasks to audio hook. +/// +/// If we have very many instances, this might not be enough. But the task size is so +/// small, so why not make it a great number? It's global, not per instance. For one +/// instance we had 2000 before and it worked great. With 100_000 we can easily cover 50 instances +/// and yet it's only around 8 MB memory usage (globally). We are on the safe side! +const FEEDBACK_AUDIO_HOOK_TASK_QUEUE_SIZE: usize = 100_000; +/// Queue size for sending less frequent tasks to audio hook. +const NORMAL_AUDIO_HOOK_TASK_QUEUE_SIZE: usize = 2000; +/// Queue size for deferring deallocation in real-time threads to a dedicated deallocator thread. +/// +/// - A capacity of 1 means 3 * usize, so 3 * 64 bit = 24 byte. +/// - A capacity of 1000 means around 24 kb then. +/// - So we can easily make this large without using much memory. +/// - Although probably not necessary because deallocation in real-time threads doesn't happen +/// often. +/// - Still, we are on the safe side. Because if the channel is full, it will start deallocating in +/// real-time thread until there's capacity again. +const DEALLOCATOR_THREAD_CAPACITY: usize = 10000; + +make_available_globally_in_main_thread_on_demand!(BackboneShell); + +static APP_LIBRARY: std::sync::OnceLock> = + std::sync::OnceLock::new(); + +pub type RealearnSessionAccelerator = + RealearnAccelerator; + +pub type RealearnControlSurface = + MiddlewareControlSurface>; + +/// Just the old term as alias for easier class search. +type _App = BackboneShell; + +#[derive(Debug)] +pub struct BackboneShell { + /// This should always be set except in the destructor. + /// + /// The only reason why this is optional is that we need to take ownership of the runtime when the shell is + /// dropped. + async_runtime: RefCell>, + /// RAII + _tracing_hook: Option, + /// RAII + _metrics_hook: Option, + state: RefCell, + license_manager: SharedLicenseManager, + controller_preset_manager: SharedControllerPresetManager, + main_preset_manager: SharedMainPresetManager, + preset_link_manager: SharedPresetLinkManager, + osc_device_manager: SharedOscDeviceManager, + controller_manager: SharedControllerManager, + server: SharedRealearnServer, + config: RefCell, + sessions_changed_subject: RefCell>, + control_surface_main_task_sender: RealearnControlSurfaceMainTaskSender, + instance_event_sender: SenderToNormalThread, + #[cfg(feature = "playtime")] + clip_matrix_event_sender: SenderToNormalThread, + osc_feedback_task_sender: SenderToNormalThread, + additional_feedback_event_sender: SenderToNormalThread, + feedback_audio_hook_task_sender: SenderToRealTimeThread, + instance_orchestration_event_sender: SenderToNormalThread, + audio_hook_task_sender: SenderToRealTimeThread, + instance_shell_infos: RefCell>, + unit_infos: RefCell>, + message_panel: SharedView, + osc_feedback_processor: Rc>, + proto_hub: crate::infrastructure::proto::ProtoHub, + welcome_panel: RefCell>>, + toolbar_change_detector: Option>, + /// We need to keep this panel in memory in order to be informed when it's destroyed. + _shutdown_detection_panel: SharedView, +} + +#[derive(Clone, Debug)] +pub struct InstanceShellInfo { + pub instance_id: InstanceId, + pub processor_context: ProcessorContext, + /// Representation of the instance in the infrastructure layer. + pub instance_shell: WeakInstanceShell, + /// Representation of the instance in the domain layer. + pub instance: WeakInstance, +} + +/// Contains all constant info about a Helgobox instance including references to the +/// corresponding instance representations in each onion layer. +#[derive(Debug)] +pub struct UnitInfo { + pub unit_id: UnitId, + pub instance_id: InstanceId, + /// Whether this is the main unit of an instance. + pub is_main_unit: bool, + /// Whether this is an automatically loaded unit (according to controller configuration). + pub is_auto_unit: bool, + /// Representation of the unit's parent instance in the domain layer. + pub instance: WeakInstance, + /// User interface of the unit's parent instance in the infrastructure layer. + pub instance_panel: Weak, + /// Representation of the unit in the application layer. + pub unit_model: WeakUnitModel, + /// Representation of the unit in the domain layer. + pub unit: WeakUnit, +} + +#[derive(Debug)] +enum AppState { + /// As long as no ReaLearn instance is loaded. + /// + /// Happens once very shortly at startup and then whenever the last ReaLearn instance + /// disappears. This can happen multiple times if REAPER preference "Allow complete unload of + /// VST plug-ins" is disabled (the default). + Sleeping(SleepingState), + /// Whenever the first ReaLearn instance is loading. + WakingUp, + /// As long as at least one ReaLearn instance is loaded. + Awake(AwakeState), + /// Whenever the last ReaLearn instance is unloading. + GoingToSleep, + /// Whenever one (not necessarily the last) ReaLearn instance is unloading. + Suspended, +} + +#[derive(Debug)] +struct SleepingState { + control_surface: Box, + audio_hook: Box, + accelerator: Box, + async_deallocation_receiver: AsyncDeallocatorCommandReceiver, +} + +#[derive(Debug)] +struct AwakeState { + control_surface_handle: RegistrationHandle, + audio_hook_handle: RegistrationHandle, + accelerator_handle: RegistrationHandle, + async_deallocation_thread: JoinHandle, +} + +impl BackboneShell { + /// Executed globally just once when module loaded. + /// + /// This should fire up everything that must be around even while asleep (even without any + /// VST plug-in instance being around). The less the better! Users shouldn't pay for stuff they + /// don't need! + /// + /// The opposite function is [Self::dispose]. + pub fn init(context: PluginContext) -> Self { + // Start async runtime + // The main reason why we start it already here (and not wait until wake_up) is the async Sentry error reporting + // initialization. However, more things might be added in the future. Plus, this runtime runs in its own thread, + // so it doesn't clutter REAPER's main thread. It's fine if it runs in the background with nothing to do most + // of the time. + let async_runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name("Helgobox async runtime") + .worker_threads(1) + .build() + .expect("couldn't start Helgobox async runtime"); + // Use "ring" as crypto provider (instead of "aws-lc-rs", because this is harder to build) + let _ = rustls::crypto::ring::default_provider().install_default(); + // We need Swell already without VST plug-in instance to populate the extension menu. As soon as an instance + // exists, we also need it for all the native GUI stuff. + let _ = Swell::make_available_globally(Swell::load(context)); + // We need access to REAPER as soon as possible, of course + // TODO-medium This needs around 10 MB of RAM. Of course only once, not per instance, + // so not a big deal. Still, maybe could be improved? + let _ = Reaper::setup_with_defaults(context, create_plugin_info()); + // The API contains functions that must be around without any VST plug-in instance being active + register_api().expect("couldn't register API"); + // Senders and receivers are initialized here but used only when awake. Yes, they already consume memory + // when asleep but most of them are unbounded and therefore consume a minimal amount of memory as long as + // they are not used. + let config = BackboneConfig::load().unwrap_or_else(|e| { + debug!("{}", e); + Default::default() + }); + // Init error reporting + Reaper::get().set_report_crashes_to_sentry(config.send_errors_to_dev()); + Reaper::get().set_log_crashes_to_console(config.show_errors_in_console()); + // Fetch remote config if update notifications or error reporting enabled + if config.wants_remote_config() { + let also_init_sentry = config.send_errors_to_dev(); + async_runtime.spawn(fetch_remote_config(also_init_sentry)); + } + // Create channels + let (control_surface_main_task_sender, control_surface_main_task_receiver) = + SenderToNormalThread::new_unbounded_channel("control surface main tasks"); + let control_surface_main_task_sender = + RealearnControlSurfaceMainTaskSender(control_surface_main_task_sender); + #[cfg(feature = "playtime")] + let (clip_matrix_event_sender, clip_matrix_event_receiver) = + SenderToNormalThread::new_unbounded_channel("Playtime matrix events"); + let (osc_feedback_task_sender, osc_feedback_task_receiver) = + SenderToNormalThread::new_unbounded_channel("osc feedback tasks"); + let (additional_feedback_event_sender, additional_feedback_event_receiver) = + SenderToNormalThread::new_unbounded_channel("additional feedback events"); + let (instance_orchestration_event_sender, instance_orchestration_event_receiver) = + SenderToNormalThread::new_unbounded_channel("instance orchestration events"); + let (feedback_audio_hook_task_sender, feedback_audio_hook_task_receiver) = + SenderToRealTimeThread::new_channel( + "feedback audio hook tasks", + FEEDBACK_AUDIO_HOOK_TASK_QUEUE_SIZE, + ); + let (instance_event_sender, instance_event_receiver) = + SenderToNormalThread::new_unbounded_channel("instance state change events"); + let (audio_hook_task_sender, normal_audio_hook_task_receiver) = + SenderToRealTimeThread::new_channel( + "normal audio hook tasks", + NORMAL_AUDIO_HOOK_TASK_QUEUE_SIZE, + ); + // We initialize tracing here already instead of activating/deactivating it when waking up + // or go to sleep. Reason: It doesn't matter that the async logger thread is lurking around + // even when no Helgobox instance exists anymore, because the HELGOBOX_LOG env variable is + // opt-in and should only be used for debugging purposes anyway. Also, + // activating/deactivating would be more difficult because the global tracing subscriber can + // be set only once. There's no way to unset it. + let tracing_hook = TracingHook::init(); + // We initialize metrics here already for the same reasons. + let metrics_hook = MetricsHook::init(); + // The global allocator uses a dedicated thread to offload deallocation from real-time + // threads. However, this thread will only exist when awake. + let async_deallocation_receiver = GLOBAL_ALLOCATOR.init( + DEALLOCATOR_THREAD_CAPACITY, + RealearnAllocatorIntegration::new( + Reaper::get() + .medium_reaper() + .low() + .pointers() + .IsInRealTimeAudio + .expect("couldn't get IsInRealTimeAudio function from REAPER"), + ), + ); + // License management + let license_manager = LicenseManager::new( + BackboneShell::helgoboss_resource_dir_path() + .join("licensing.json") + .into(), + Box::new(BackboneLicenseManagerEventHandler), + ); + // This just initializes the clip engine, it doesn't add any Playtime matrix yet, so resource consumption is low. + #[cfg(feature = "playtime")] + playtime_impl::init_clip_engine(&license_manager); + // This is the backbone representation in the domain layer, of course we need this now already. + let backbone_state = Backbone::new( + additional_feedback_event_sender.clone(), + RealearnTargetState::new(additional_feedback_event_sender.clone()), + ); + Backbone::make_available_globally(|| backbone_state); + // This just sets up the server event wiring but doesn't send any events yet => low resource usage. + let sessions_changed_subject: RefCell> = Default::default(); + server::http::keep_informing_clients_about_sessions( + sessions_changed_subject.borrow().clone(), + ); + // Presets, preset links and controllers are all global things, so we wire everything now already. However, + // the actual presets are read from disk when waking up. + let controller_preset_manager = FileBasedControllerPresetManager::new( + CompartmentKind::Controller, + BackboneShell::realearn_compartment_preset_dir_path(CompartmentKind::Controller), + Box::new(BackboneControllerPresetManagerEventHandler), + ); + let main_preset_manager = FileBasedMainPresetManager::new( + CompartmentKind::Main, + BackboneShell::realearn_compartment_preset_dir_path(CompartmentKind::Main), + Box::new(BackboneMainPresetManagerEventHandler), + ); + let preset_link_manager = + FileBasedPresetLinkManager::new(BackboneShell::realearn_auto_load_configs_dir_path()); + let controller_manager = ControllerManager::new( + Self::realearn_controller_config_file_path(), + Box::new(BackboneControllerManagerEventHandler), + ); + // This doesn't yet load devices or start listening for OSC messages (will happen on wake up) + let osc_device_manager = + OscDeviceManager::new(BackboneShell::realearn_osc_device_config_file_path()); + // This doesn't yet start the server (will happen on wake up) + let server = RealearnServer::new( + config.main.server_http_port, + config.main.server_https_port, + config.main.server_grpc_port, + BackboneShell::server_resource_dir_path() + .join("certificates") + .into(), + MetricsReporter::new(), + ); + // OSC devices are reconnected only if device list changes (= while instance active) + let osc_feedback_processor = OscFeedbackProcessor::new(osc_feedback_task_receiver); + osc_device_manager + .changed() + .subscribe(|_| BackboneShell::get().reconnect_osc_devices()); + let shared_main_processors = SharedMainProcessors::default(); + // This doesn't yet activate the control surface (will happen on wake up) + let control_surface = MiddlewareControlSurface::new(RealearnControlSurfaceMiddleware::new( + control_surface_main_task_receiver, + instance_event_receiver, + #[cfg(feature = "playtime")] + clip_matrix_event_receiver, + additional_feedback_event_receiver, + instance_orchestration_event_receiver, + shared_main_processors.clone(), + Box::new(BackboneControlSurfaceEventHandler), + )); + // This doesn't yet activate the audio hook (will happen on wake up) + let audio_hook = RealearnAudioHook::new( + normal_audio_hook_task_receiver, + feedback_audio_hook_task_receiver, + ); + // This doesn't yet activate the accelerator (will happen on wake up) + let accelerator = + RealearnAccelerator::new(shared_main_processors, BackboneHelgoboxWindowSnitch); + // Silently decompress app and load library in background so it's ready when needed. We want to do this + // already here in order to let actions such as "Show/hide Playtime" work instantly without delay. + let _ = std::thread::Builder::new() + .name("Helgobox app loader".to_string()) + .spawn(|| { + let result = decompress_app().and_then(|_| load_app_library()); + let _ = APP_LIBRARY.set(result); + }); + // We want actions and menu entries to be available even in sleeping state because there are some convenience + // actions among them that boot up an instance when none is found yet. + Self::register_actions(); + let sleeping_state = SleepingState { + control_surface: Box::new(control_surface), + audio_hook: Box::new(audio_hook), + accelerator: Box::new(accelerator), + async_deallocation_receiver, + }; + // We wake up reaper-rs here already, otherwise the registered actions wouldn't show up yet. + Reaper::get().wake_up().expect("couldn't wake up REAPER"); + // Must be called after registering actions and waking REAPER up, otherwise it won't find the command IDs. + let _ = Self::register_extension_menu(); + let _ = Self::register_toolbar_icon_map(); + let toolbar_change_detector = if custom_toolbar_api_is_available() { + let observed_commands = ACTION_DEFS.iter().filter_map(|def| { + if !def.add_toolbar_button { + return None; + } + // Auto-add previously enabled dynamic toolbar buttons, if not present already + let enabled = config.toolbar.get(def.command_name).is_some_and(|v| *v > 0); + if enabled { + let _ = add_or_remove_toolbar_button(def.command_name, true); + } + // Create change detector to automatically disable a dynamic toolbar button if the user removes the + // button manually. + let command_id = Reaper::get() + .action_by_command_name(def.command_name) + .command_id() + .ok()?; + Some((command_id, def.command_name.to_string())) + }); + let detector = ToolbarChangeDetector::new(observed_commands.collect()); + Some(RefCell::new(detector)) + } else { + None + }; + // Open a hidden child window to take care of a few things: + // - Shutdown detection (works only until REAPER 7.51, is replaced with "at_exit" hook in REAPER 7.55) + // - Playtime & toolbar timers (could probably be replaced with REAPER timers at some point, but as long + // as we want to support REAPER <= 7.51, there's no good reason to change that because we need the + // hidden window anyway) + let shutdown_detection_panel = SharedView::new(HiddenHelperPanel::new()); + shutdown_detection_panel.clone().open(reaper_main_window()); + // For REAPER 7.55+, registering an at-exit hook will succeed + extern "C" fn at_exit() { + BackboneShell::get().shutdown(); + } + let _ = Reaper::get() + .medium_session() + .plugin_register_add_at_exit(at_exit); + BackboneShell { + async_runtime: RefCell::new(Some(async_runtime)), + _tracing_hook: tracing_hook, + _metrics_hook: metrics_hook, + state: RefCell::new(AppState::Sleeping(sleeping_state)), + license_manager: Rc::new(RefCell::new(license_manager)), + controller_preset_manager: Rc::new(RefCell::new(controller_preset_manager)), + main_preset_manager: Rc::new(RefCell::new(main_preset_manager)), + preset_link_manager: Rc::new(RefCell::new(preset_link_manager)), + osc_device_manager: Rc::new(RefCell::new(osc_device_manager)), + controller_manager: Rc::new(RefCell::new(controller_manager)), + server: Rc::new(RefCell::new(server)), + config: RefCell::new(config), + sessions_changed_subject, + control_surface_main_task_sender, + instance_event_sender, + #[cfg(feature = "playtime")] + clip_matrix_event_sender, + osc_feedback_task_sender, + additional_feedback_event_sender, + feedback_audio_hook_task_sender, + instance_orchestration_event_sender, + audio_hook_task_sender, + instance_shell_infos: RefCell::new(vec![]), + unit_infos: Default::default(), + message_panel: Default::default(), + osc_feedback_processor: Rc::new(RefCell::new(osc_feedback_processor)), + proto_hub: crate::infrastructure::proto::ProtoHub::new(), + welcome_panel: Default::default(), + toolbar_change_detector, + _shutdown_detection_panel: shutdown_detection_panel, + } + } + + /// Called when static is destroyed (REAPER exit or - on Windows if configured - complete VST + /// unload if extension not loaded, just VST) + pub fn dispose(&self) { + println!("Disposing BackboneShell..."); + // Shutdown async runtime + tracing::info!("Shutting down async runtime..."); + if let Ok(mut async_runtime) = self.async_runtime.try_borrow_mut() { + if let Some(async_runtime) = async_runtime.take() { + // 1 second timeout caused a freeze sometimes (Windows) + async_runtime.shutdown_background(); + } + } + tracing::info!("Async runtime shut down successfully"); + let _ = Reaper::get().go_to_sleep(); + self.message_panel.close(); + let _ = unregister_api(); + } + + /// Returns the remote configuration once it has been successfully fetched. + /// + /// The remote configuration will only be fetched on start and only if either update notification or + /// error-reporting-to-dev is enabled in the local config. + pub fn remote_config() -> Option<&'static HelgoboxRemoteConfig> { + HELGOBOX_REMOTE_CONFIG.get() + } + + pub fn show_welcome_screen_if_necessary(&self) { + { + let mut config = self.config.borrow_mut(); + let showed_already = mem::replace(&mut config.main.showed_welcome_screen, 1) == 1; + if showed_already { + return; + } + notification::warn_user_on_anyhow_error(config.save()); + }; + Self::show_welcome_screen(); + } + + pub fn detailed_version_label() -> &'static str { + static VALUE: Lazy = Lazy::new(build_detailed_version); + &VALUE + } + + pub fn version() -> &'static Version { + static VALUE: Lazy = Lazy::new(|| { + Version::parse(crate::infrastructure::plugin::built_info::PKG_VERSION).unwrap() + }); + &VALUE + } + + pub fn create_envelope(value: T) -> Envelope { + Envelope { + version: Some(Self::version().clone()), + value, + } + } + + pub fn warn_if_envelope_version_higher(envelope_version: Option<&Version>) { + if let Some(v) = envelope_version { + if Self::version() < v { + notification::warn(format!( + "The given snippet was created for ReaLearn {}, which is \ + newer than the installed version {}. Things might not work as expected. \ + Please consider upgrading your \ + ReaLearn installation to the latest version.", + v, + BackboneShell::version() + )); + } + } + } + + pub fn get_temp_dir() -> Option<&'static TempDir> { + static TEMP_DIR: Lazy> = + Lazy::new(|| tempfile::Builder::new().prefix("realearn-").tempdir().ok()); + TEMP_DIR.as_ref() + } + + /// Creates a new Helgobox instance on the given track. + pub async fn create_new_instance_on_track(track: &Track) -> anyhow::Result { + if Reaper::get().version().revision() < "6.69" { + // Version too old to support TrackFX_AddByName with only VST2-UID specified + bail!("Please update REAPER to the latest version to access this feature!"); + } + let fx = track + .normal_fx_chain() + .add_fx_by_original_name(HELGOBOX_UNIQUE_VST_PLUGIN_ADD_STRING) + .with_context(|| { + if Reaper::get().vst_scan_is_enabled() { + "Looks like Helgobox VST plug-in is not installed! Did you follow the official installation instructions?" + } else { + "It was not possible to add Helgobox VST plug-in, probably because you have VST scanning disabled in the REAPER preferences!\n\nPlease open REAPER's FX browser and press F5 to rescan. After that, try again!\n\nAs an alternative, re-enable VST scanning in Preferences/Plug-ins/VST and restart REAPER." + } + })?; + fx.hide_floating_window()?; + // The rest needs to be done async because the instance initializes itself async + // (because FX not yet available when plug-in instantiated). + millis(1).await; + let instance_shell = Self::get().with_instance_shell_infos( + |infos| -> anyhow::Result { + let last_info = infos.last().context("instance was not registered")?; + let shared_instance = last_info + .instance_shell + .upgrade() + .context("instance gone")?; + Ok(shared_instance) + }, + )?; + let outcome = NewInstanceOutcome { fx, instance_shell }; + Ok(outcome) + } + + /// This will cause all main processors to "switch all lights off". + /// + /// Doing this on main processor drop is too late as audio won't be running anymore and the MIDI devices will + /// already be closed. Opening them again - in a destructor - is not good practice. + /// + /// This should be called early in the REAPER shutdown procedure. At the moment, we call it when a hidden window + /// is destroyed and additionally - in later REAPER versions - by a REAPER exit hook. + pub fn shutdown(&self) { + // In later REAPER versions with REAPER exit hooks, this can be called twice! To avoid a crash on exit, + // we should not panic if the following operation doesn't succeed. + let _ = self.temporarily_reclaim_control_surface_ownership(|control_surface| { + let middleware = control_surface.middleware_mut(); + middleware.shutdown(); + }); + sleep_for_a_few_audio_blocks(); + if cfg!(target_os = "windows") { + // On Windows, we traditionally executed destroy hooks. Mainly because REAPER for + // Windows provides the preference "VST => Allow complete unload of VST plug-ins". + // On other OS, this option is not available. Therefore, cleaning up highly static + // resources is not really necessary or even possible. + // Well, even on Windows, lately "complete unload" leads to crashes. Not sure why. + // But anyway, we still try our best. + // + // We execute those hooks latest on DLL_PROCESS_DETACH. + // But executing the plug-in destroy hooks **here already** has the advantage that arbitrary tear-down + // code can be run. The code that can be called on Windows when dropping everything when the + // DLL gets detached is limited (if not following this rule, panics may occur and that + // would abort REAPER because the panic occurs in drop). + // Still, ideally, the tear-down code itself should be safe to execute on DLL_PROCESS_DETACH + // as well. + tracing::info!("Executing plug-in destroy hooks from hidden helper panel..."); + reaper_low::execute_plugin_destroy_hooks(); + } + } + + fn reconnect_osc_devices(&self) { + self.temporarily_reclaim_control_surface_ownership(|control_surface| { + let middleware = control_surface.middleware_mut(); + // Disconnect inputs + middleware.clear_osc_input_devices(); + // Disconnect outputs + let mut processor = self.osc_feedback_processor.borrow_mut(); + processor.stop(); + // Reconnect inputs and outputs + let (osc_input_devices, osc_output_devices) = self + .osc_device_manager + .borrow_mut() + .connect_all_enabled_inputs_and_outputs(); + middleware.set_osc_input_devices(osc_input_devices); + processor.start(osc_output_devices); + }) + .expect("should reconnect OSC devices"); + } + + fn create_services(&self) -> Services { + Services { + helgobox_service: server::services::helgobox_service::create_server(&self.proto_hub), + } + } + + fn with_async_runtime(&self, f: impl FnOnce(&Runtime) -> R) -> anyhow::Result { + let runtime = self + .async_runtime + .try_borrow() + .context("async runtime already borrowed")?; + let runtime = runtime + .as_ref() + .context("async runtime already destroyed")?; + Ok(f(runtime)) + } + + /// Executed whenever the first Helgobox instance is loaded. + /// + /// This should fire up stuff that only needs to be around while awake (= as long as at least one Helgobox VST + /// plug-in instance is around). Stuff that must be around even while asleep should be put into [Self::init]. + /// + /// The opposite function is [Self::go_to_sleep]. + /// + /// # Errors + /// + /// Returns and error if not sleeping. + pub fn wake_up(&self) -> anyhow::Result<()> { + let prev_state = self.state.replace(AppState::WakingUp); + let AppState::Sleeping(mut sleeping_state) = prev_state else { + self.state.replace(prev_state); + bail!("App was not sleeping"); + }; + // (Re)load presets, links and controllers + let _ = self + .controller_preset_manager + .borrow_mut() + .load_presets_from_disk_without_notification(); + let _ = self + .main_preset_manager + .borrow_mut() + .load_presets_from_disk_without_notification(); + let _ = self + .preset_link_manager + .borrow_mut() + .load_preset_links_from_disk(); + let _ = self + .controller_manager + .borrow_mut() + .load_controllers_from_disk(); + let _ = self + .osc_device_manager + .borrow_mut() + .load_osc_devices_from_disk(); + // Start thread for async deallocation + let async_deallocation_thread = start_async_deallocation_thread( + RealearnDeallocator::with_metrics("helgobox.allocator.async_deallocation"), + sleeping_state.async_deallocation_receiver, + ); + // Activate server + if self.config.borrow().server_is_enabled() { + let _ = self.with_async_runtime(|runtime| { + self.server() + .borrow_mut() + .start(runtime, self.create_services()) + .unwrap_or_else(warn_about_failed_server_start); + }); + } + let mut session = Reaper::get().medium_session(); + // Action hooks + session + .plugin_register_add_hook_post_command::>() + .unwrap(); + session + .plugin_register_add_hook_post_command::() + .unwrap(); + // Window hooks (fails before REAPER 6.29) + let _ = session.plugin_register_add_hwnd_info::(); + // This fails before REAPER 6.20 and therefore we don't have MIDI CC action feedback. + let _ = + session.plugin_register_add_hook_post_command_2::>(); + let _ = session.plugin_register_add_hook_post_command_2::(); + // Audio hook + debug!("Registering ReaLearn audio hook and control surface..."); + let audio_hook_handle = session + .audio_reg_hardware_hook_add(sleeping_state.audio_hook) + .expect("couldn't register ReaLearn audio hook"); + // OSC devices + let (osc_input_devices, osc_output_devices) = self + .osc_device_manager + .borrow_mut() + .connect_all_enabled_inputs_and_outputs(); + // OSC processor + self.osc_feedback_processor + .borrow_mut() + .start(osc_output_devices); + // Control surface + let middleware = sleeping_state.control_surface.middleware_mut(); + middleware.set_osc_input_devices(osc_input_devices); + sleeping_state.control_surface.middleware().wake_up(); + let control_surface_handle = session + .plugin_register_add_csurf_inst(sleeping_state.control_surface) + .expect("couldn't register ReaLearn control surface"); + // Accelerator + let accelerator_handle = session + .plugin_register_add_accelerator_register( + sleeping_state.accelerator, + AcceleratorPosition::Front, + ) + .expect("couldn't register ReaLearn accelerator"); + // Awake state + let awake_state = AwakeState { + control_surface_handle, + audio_hook_handle, + accelerator_handle, + async_deallocation_thread, + }; + self.state.replace(AppState::Awake(awake_state)); + Ok(()) + } + + // Executed whenever the last ReaLearn instance goes away. + pub fn go_to_sleep(&self) -> anyhow::Result<()> { + let prev_state = self.state.replace(AppState::GoingToSleep); + let AppState::Awake(awake_state) = prev_state else { + bail!("App was not awake when trying to go to sleep"); + }; + let mut session = Reaper::get().medium_session(); + debug!("Unregistering ReaLearn control surface and audio hook..."); + let (accelerator, mut control_surface, audio_hook) = unsafe { + let accelerator = session + .plugin_register_remove_accelerator(awake_state.accelerator_handle) + .context("accelerator was not registered")?; + let control_surface = session + .plugin_register_remove_csurf_inst(awake_state.control_surface_handle) + .context("control surface was not registered")?; + sleep_for_a_few_audio_blocks(); + let audio_hook = session + .audio_reg_hardware_hook_remove(awake_state.audio_hook_handle) + .context("control surface was not registered")?; + (accelerator, control_surface, audio_hook) + }; + // Close OSC connections + let middleware = control_surface.middleware_mut(); + middleware.clear_osc_input_devices(); + self.osc_feedback_processor.borrow_mut().stop(); + // Window hooks + session.plugin_register_remove_hwnd_info::(); + // Actions + session.plugin_register_remove_hook_post_command_2::(); + session.plugin_register_remove_hook_post_command_2::>(); + session.plugin_register_remove_hook_post_command::(); + session.plugin_register_remove_hook_post_command::>(); + // Server + self.server().borrow_mut().stop(); + // Stop async deallocation thread + GLOBAL_ALLOCATOR.stop_async_deallocation(); + let async_deallocation_receiver = awake_state + .async_deallocation_thread + .join() + .map_err(|_| anyhow!("couldn't join deallocation thread"))?; + // Finally go to sleep + let sleeping_state = SleepingState { + control_surface, + audio_hook, + accelerator, + async_deallocation_receiver, + }; + self.state.replace(AppState::Sleeping(sleeping_state)); + Ok(()) + } + + /// Attention: The real-time processor is removed *async*! That means it can still be called + /// by the audio hook, even after this method has executed. The benefit is that it will still + /// be able to do clean-up work after the plug-in instance is gone as long as another one is + /// still around. The problem is that the real-time processor has a reference to the plug-in + /// host callback and is *must not* call it when the plug-in is already gone, otherwise boom! + /// Unfortunately there's no easy way to detect if it's gone or not. + /// + /// What options do we have (we decided for option 7)? + /// + /// 1. Setting the callback to None async by sending a message via channel? No! + /// - This would affect the next audio thread cycle only. But the audio hook first runs + /// real-time processors and then tasks. + /// - And even if we would swap the order (which would defeat the purpose), this would still + /// leave the possibility that the real-time processor will be executed before the task is + /// processed. + /// 2. Setting the callback to None by calling the real-time processor directly? No! + /// - The real-time processor must only be invoked from the audio thread. We want to avoid + /// wrapping the processor with a mutex because in all other cases we don't need + /// synchronized access (we work with channels instead). + /// - Also, a similar issue as in point 1 could arise: The real-time processor could just be + /// in the middle of its run call! We would need the mutex to wait for any current call to + /// end. + /// 3. Unregister audio hook, remove real-time processor and register it again? Maybe. + /// - First, this would only work if REAPER ensures that it waits until the current call of + /// this hook is finished before returning from the removal function. Justin confirmed + /// that it works that way, yay! + /// - We would lose the possibility of doing clean-up work though. + /// 4. Wrapping the host callback as `Arc>>`? Okay. + /// - Then the synchronized access would be concentrated on this tiny necessity only, which + /// would be okay. + /// - Looks like a lot of fuzz though. + /// 5. Use `Arc` and `Weak` and always check if upgrade fails? Okay. + /// - Nice! But how about performance if we always have to inc/dec the `AtomicUsize`? + /// 6. Use `HostCallback#is_effect_valid()` magic number logic as "gone" check? Interesting. + /// - Could be better than 4 and 5 performance-wise. + /// - But what if the audio thread is just in the middle of the host callback call while the + /// plug-in is being unloaded? Probably unlikely in practice and maybe not harmful if it + /// happens. But still leaves a bad aftertaste. I mean, if we would call the host callback + /// from the plug-in processing method only (like normal people do), the host would + /// probably take care that this never happens...? This also concerns 4 and 5 by the way. + /// 7. Just don't use the host callback when called from audio hook? **Yes!**. + /// - We use the host callback for checking play state and sending MIDI to FX output. + /// - Checking play state could be avoided completely by using REAPER functions. + /// - Sending MIDI to FX output is a bit strange anyway if called by the audio hook, maybe + /// even something one shouldn't do? It's something global driving something which is + /// built to be local only. + /// - **Most importantly:** Sending MIDI to FX output while VST processing is stopped will + /// not have any effect anyway because the rest of the FX chain is stopped! + /// - Solution: Drive the real-time processor from both plug-in `process()` method **and** + /// audio hook and make sure that only the call from the plug-in ever sends MIDI to FX + /// output. + fn unregister_unit_real_time_processor(&self, unit_id: UnitId) { + self.audio_hook_task_sender + .send_complaining(NormalAudioHookTask::RemoveRealTimeProcessor(unit_id)); + } + + /// We remove the main processor synchronously because it allows us to keep its fail-fast + /// behavior. E.g. we can still panic if DomainEventHandler (weak session) or channel + /// receivers are gone because we know it's not supposed to happen. Also, unlike with + /// real-time processor, whatever cleanup work is necessary, we can do right here because we + /// are in main thread already. + fn unregister_unit_main_processor(&self, unit_id: UnitId) { + let result = self.temporarily_reclaim_control_surface_ownership(|control_surface| { + // Remove main processor. + control_surface + .middleware_mut() + .remove_main_processor(unit_id) + }); + // Removing the main processor can fail if the removal of that ReaLearn instance was triggered by + // ReaLearn itself (possibly by another instance) ... Sentry example ID: 0be87e420ee041538000e8628d941741. + // Reentrancy! We can't simply leave it at that because this would mean that the main processor is still in the + // list, so it would keep working without the plug-in being around anymore ... that would be super strange. + // That's why we remove the main processor asynchronously as a fallback. A quick-and-dirty solution, yes. + // TODO-medium This is ugly, to be honest. Ideally, we would have another solution: + // a) A non-shared list of owned main processors + a shared list of weak main processors references. + // Then we could infallibly remove the owned main processors right here because we don't need to borrow. + // Code which uses the shared list of weak main processors can throw out the dead references at some point. + // Having the weak processor references in that list a bit longer doesn't hurt. They will just be skipped. + // b) We would execute relevant targets (mainly "Project: Invoke REAPER action") asynchronously, that is, in + // the next main loop cycle. Then the main processors wouldn't be borrowed during the actual action + // execution. However, that would need a bit more testing. + // c) Or always remove asynchronously. But that would also need some careful testing. + if result.is_err() { + self.control_surface_main_task_sender + .remove_main_processor(unit_id); + } + } + + pub fn feedback_audio_hook_task_sender( + &self, + ) -> &SenderToRealTimeThread { + &self.feedback_audio_hook_task_sender + } + + pub fn instance_event_sender(&self) -> &SenderToNormalThread { + &self.instance_event_sender + } + + #[cfg(feature = "playtime")] + pub fn clip_matrix_event_sender( + &self, + ) -> &SenderToNormalThread { + &self.clip_matrix_event_sender + } + + pub fn normal_audio_hook_task_sender(&self) -> &SenderToRealTimeThread { + &self.audio_hook_task_sender + } + + pub fn additional_feedback_event_sender( + &self, + ) -> SenderToNormalThread { + self.additional_feedback_event_sender.clone() + } + + pub fn instance_orchestration_event_sender( + &self, + ) -> SenderToNormalThread { + self.instance_orchestration_event_sender.clone() + } + + pub fn osc_feedback_task_sender(&self) -> &SenderToNormalThread { + &self.osc_feedback_task_sender + } + + pub fn control_surface_main_task_sender(&self) -> &RealearnControlSurfaceMainTaskSender { + &self.control_surface_main_task_sender + } + + pub fn proto_hub(&self) -> &crate::infrastructure::proto::ProtoHub { + &self.proto_hub + } + + fn temporarily_reclaim_control_surface_ownership( + &self, + f: impl FnOnce(&mut RealearnControlSurface) -> R, + ) -> anyhow::Result { + let (result, next_state) = match self.state.replace(AppState::Suspended) { + AppState::Sleeping(mut s) => { + let result = f(&mut s.control_surface); + (result, AppState::Sleeping(s)) + } + AppState::Awake(s) => { + let mut session = Reaper::get().medium_session(); + let mut control_surface = unsafe { + session + .plugin_register_remove_csurf_inst(s.control_surface_handle) + .context("control surface was not registered")? + }; + // Execute necessary operations + let result = f(&mut control_surface); + // Give it back to REAPER. + let control_surface_handle = session + .plugin_register_add_csurf_inst(control_surface) + .context("reregister ReaLearn control surface")?; + let awake_state = AwakeState { + control_surface_handle, + audio_hook_handle: s.audio_hook_handle, + accelerator_handle: s.accelerator_handle, + async_deallocation_thread: s.async_deallocation_thread, + }; + (result, AppState::Awake(awake_state)) + } + _ => bail!("Backbone was neither in sleeping nor in awake state"), + }; + self.state.replace(next_state); + Ok(result) + } + + /// Spawns the given future on the Helgobox async runtime. + /// + /// # Panics + /// + /// Panics if called in any state other than awake. + #[allow(dead_code)] + pub fn spawn_in_async_runtime( + &self, + f: impl Future + Send + 'static, + ) -> tokio::task::JoinHandle + where + R: Send + 'static, + { + self.with_async_runtime(|runtime| runtime.spawn(f)) + .expect("async runtime not available") + } + + pub fn license_manager(&self) -> &SharedLicenseManager { + &self.license_manager + } + + pub fn controller_preset_manager(&self) -> &SharedControllerPresetManager { + &self.controller_preset_manager + } + + pub fn main_preset_manager(&self) -> &SharedMainPresetManager { + &self.main_preset_manager + } + + pub fn controller_manager(&self) -> &SharedControllerManager { + &self.controller_manager + } + + pub fn compartment_preset_manager( + &self, + compartment: CompartmentKind, + ) -> Rc> { + match compartment { + CompartmentKind::Controller => self.controller_preset_manager().clone(), + CompartmentKind::Main => self.main_preset_manager().clone(), + } + } + + pub fn preset_link_manager(&self) -> SharedPresetLinkManager { + self.preset_link_manager.clone() + } + + pub fn osc_device_manager(&self) -> SharedOscDeviceManager { + self.osc_device_manager.clone() + } + + pub fn do_with_osc_device(&self, dev_id: OscDeviceId, f: impl FnOnce(&mut OscDevice)) { + let mut dev = BackboneShell::get() + .osc_device_manager() + .borrow() + .find_device_by_id(&dev_id) + .unwrap() + .clone(); + f(&mut dev); + BackboneShell::get() + .osc_device_manager() + .borrow_mut() + .update_device(dev) + .unwrap(); + } + + pub fn server(&self) -> &SharedRealearnServer { + &self.server + } + + pub fn config(&self) -> Ref { + self.config.borrow() + } + + pub fn server_is_running(&self) -> bool { + self.server.borrow().is_running() + } + + pub fn start_server_persistently(&self) -> Result<(), String> { + let res = self.with_async_runtime(|runtime| { + let start_result = self + .server + .borrow_mut() + .start(runtime, self.create_services()); + self.change_config(BackboneConfig::enable_server); + start_result + }); + res.unwrap_or_else(|e| Err(e.to_string())) + } + + pub fn stop_server_persistently(&self) { + self.change_config(BackboneConfig::disable_server); + self.server.borrow_mut().stop(); + } + + pub fn set_send_errors_to_dev_persistently(&self, value: bool) { + // Persistence + self.change_config(|c| c.set_send_errors_to_dev(value)); + // It's okay to initialize Sentry after restart only + Reaper::get().set_report_crashes_to_sentry(value); + } + + pub fn set_show_errors_in_console_persistently(&self, value: bool) { + // Persistence + self.change_config(|c| c.set_show_errors_in_console(value)); + // Actual behavior + Reaper::get().set_log_crashes_to_console(value); + } + + pub fn set_notify_about_updates_persistently(&self, value: bool) { + // Persistence + self.change_config(|c| c.set_notify_about_updates(value)); + // It's okay if this takes effect after restart! + } + + pub fn toggle_background_colors(&self) { + self.change_config(BackboneConfig::toggle_background_colors); + } + + /// Requires REAPER version >= 711+dev0305. + pub fn toggle_toolbar_button_dynamically(&self, command_name: &str) -> anyhow::Result<()> { + self.change_config(|config| { + // Adjust config + let value = config.toolbar.entry(command_name.to_string()).or_insert(0); + let enable = *value == 0; + *value = enable.into(); + // Apply + add_or_remove_toolbar_button(command_name, enable)?; + Ok(()) + }) + } + + /// To be called regularly, maybe once a second. + /// + /// See https://github.com/helgoboss/helgobox/issues/1331. + pub fn disable_manually_removed_dynamic_toolbar_buttons(&self) { + let Some(detector) = &self.toolbar_change_detector else { + return; + }; + for command_name in detector.borrow_mut().detect_manually_removed_commands() { + self.change_config(|config| { + config.toolbar.insert(command_name.to_string(), 0); + }) + } + } + + /// Logging debug info is always initiated by a particular session. + pub fn log_debug_info(&self, session_id: &str) { + let msg = format!( + "\n\ + # App\n\ + \n\ + - State: {:#?}\n\ + - Session count: {}\n\ + - Module base address: {:?}\n\ + ", + self.state.borrow(), + self.unit_infos.borrow().len(), + determine_module_base_address().map(|addr| format!("0x{addr:x}")), + ); + Reaper::get().show_console_msg(msg); + self.server.borrow().log_debug_info(session_id); + self.controller_preset_manager.borrow().log_debug_info(); + } + + pub fn changed(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.sessions_changed_subject.borrow().clone() + } + + fn change_config(&self, op: impl FnOnce(&mut BackboneConfig) -> R) -> R { + let mut config = self.config.borrow_mut(); + let result = op(&mut config); + notification::warn_user_on_anyhow_error(config.save()); + self.notify_changed(); + result + } + + fn helgoboss_resource_dir_path() -> Utf8PathBuf { + Reaper::get().resource_path().join("Helgoboss") + } + + pub fn app_dir_path() -> Utf8PathBuf { + BackboneShell::helgoboss_resource_dir_path().join("App") + } + + pub fn app_binary_base_dir_path() -> Utf8PathBuf { + BackboneShell::app_dir_path().join("bin") + } + + pub fn app_config_dir_path() -> Utf8PathBuf { + BackboneShell::app_dir_path().join("etc") + } + + pub fn app_settings_file_path() -> Utf8PathBuf { + BackboneShell::app_config_dir_path().join("settings.json") + } + + pub fn read_app_settings() -> Option { + fs::read_to_string(Self::app_settings_file_path()).ok() + } + + pub fn write_app_settings(settings: String) -> anyhow::Result<()> { + let file_path = Self::app_settings_file_path(); + fs::create_dir_all( + file_path + .parent() + .context("app settings file should have parent")?, + )?; + fs::write(file_path, settings)?; + Ok(()) + } + + fn realearn_resource_dir_path() -> Utf8PathBuf { + BackboneShell::helgoboss_resource_dir_path().join("ReaLearn") + } + + pub fn helgobox_data_dir_path() -> Utf8PathBuf { + Reaper::get() + .resource_path() + .join("Data/helgoboss/helgobox") + } + + pub fn realearn_data_dir_path() -> Utf8PathBuf { + Reaper::get() + .resource_path() + .join("Data/helgoboss/realearn") + } + + pub fn app_archive_file_path() -> Utf8PathBuf { + Reaper::get() + .resource_path() + .join("Data/helgoboss/archives/helgobox-app.tar.zst") + } + + pub fn realearn_high_click_sound_path() -> Option<&'static Utf8Path> { + static PATH: Lazy> = Lazy::new(|| { + // Before including the audio file in the binary, there was an actual file distributed + // via ReaPack. However, we had to copy it to a temporary directory anyway, otherwise + // we would risk an error on Windows when attempting to install a new ReaLearn version + // via ReaPack while still having ReaLearn open. + // https://github.com/helgoboss/helgobox/issues/780 + // Encoding the file in the binary frees us from having to distribute it. + let bytes = include_bytes!("../../../../resources/sounds/click-high.mp3"); + let dest_path = BackboneShell::get_temp_dir()?.path().join("click-high.mp3"); + fs::write(&dest_path, bytes).ok()?; + dest_path.try_into().ok() + }); + PATH.as_ref().map(|p| p.as_path()) + } + + #[cfg(feature = "egui")] + pub fn realearn_pot_preview_template_path() -> Option<&'static Utf8Path> { + static PATH: Lazy> = Lazy::new(|| { + let bytes = include_bytes!( + "../../../../resources/template-projects/pot-preview/pot-preview.RPP" + ); + let dest_path = BackboneShell::get_temp_dir()? + .path() + .join("pot-preview.RPP"); + fs::write(&dest_path, bytes).ok()?; + dest_path.try_into().ok() + }); + PATH.as_ref().map(|p| p.as_path()) + } + + pub fn realearn_preset_dir_path() -> Utf8PathBuf { + Self::realearn_data_dir_path().join("presets") + } + + pub fn realearn_compartment_preset_dir_path(compartment: CompartmentKind) -> Utf8PathBuf { + let sub_dir = match compartment { + CompartmentKind::Controller => "controller", + CompartmentKind::Main => "main", + }; + Self::realearn_preset_dir_path().join(sub_dir) + } + + pub fn realearn_auto_load_configs_dir_path() -> Utf8PathBuf { + Self::realearn_data_dir_path().join("auto-load-configs") + } + + pub fn realearn_osc_device_config_file_path() -> Utf8PathBuf { + BackboneShell::realearn_resource_dir_path().join("osc.json") + } + + pub fn realearn_controller_config_file_path() -> Utf8PathBuf { + BackboneShell::realearn_resource_dir_path().join("controllers.json") + } + + pub fn get_app_library() -> anyhow::Result<&'static crate::infrastructure::ui::AppLibrary> { + let app_library = APP_LIBRARY + .get() + .context("App not loaded yet. Please try again later.")? + .as_ref(); + app_library.map_err(|e| anyhow::anyhow!(format!("{e:?}"))) + } + + pub fn has_unit_with_key(&self, unit_key: &str) -> bool { + self.find_unit_model_by_key(unit_key).is_some() + } + + pub fn find_unit_model_by_key(&self, session_id: &str) -> Option { + self.find_session(|session| { + let Ok(session) = session.try_borrow() else { + return false; + }; + session.unit_key() == session_id + }) + } + + pub fn get_instance_shell_by_instance_id( + &self, + instance_id: InstanceId, + ) -> anyhow::Result { + self.find_instance_shell_by_instance_id(instance_id) + .context("couldn't find instance") + } + + pub fn find_instance_shell_by_instance_id( + &self, + instance_id: InstanceId, + ) -> Option { + self.instance_shell_infos + .borrow() + .iter() + .find(|info| info.instance_id == instance_id) + .and_then(|info| info.instance_shell.upgrade()) + } + + #[allow(dead_code)] + pub fn find_instance_by_instance_id(&self, instance_id: InstanceId) -> Option { + self.find_main_unit_info_by_instance_id(instance_id) + .and_then(|i| i.instance.upgrade()) + } + + #[allow(dead_code)] + pub fn find_instance_panel_by_instance_id( + &self, + instance_id: InstanceId, + ) -> Option> { + self.find_main_unit_info_by_instance_id(instance_id) + .and_then(|i| i.instance_panel.upgrade()) + } + + fn find_main_unit_info_by_instance_id(&self, instance_id: InstanceId) -> Option> { + let units = self.unit_infos.borrow(); + Ref::filter_map(units, |units| { + units + .iter() + .find(|i| i.is_main_unit && i.instance_id == instance_id) + }) + .ok() + } + + #[cfg(feature = "playtime")] + pub fn with_clip_matrix( + &self, + clip_matrix_id: InstanceId, + f: impl FnOnce(&playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + let instance = self + .find_instance_by_instance_id(clip_matrix_id) + .context("instance not found")?; + Backbone::get().with_clip_matrix(&instance, f) + } + + #[cfg(feature = "playtime")] + pub fn with_clip_matrix_mut( + &self, + clip_matrix_id: InstanceId, + f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + let instance = self + .find_instance_by_instance_id(clip_matrix_id) + .context("instance not found")?; + Backbone::get().with_clip_matrix_mut(&instance, f) + } + + #[allow(unused)] + pub fn create_clip_matrix(&self, clip_matrix_id: InstanceId) -> anyhow::Result<()> { + let instance_shell = self + .find_instance_shell_by_instance_id(clip_matrix_id) + .context("instance not found")?; + instance_shell.insert_owned_clip_matrix_if_necessary()?; + Ok(()) + } + + pub fn find_session_by_id_ignoring_borrowed_ones( + &self, + session_id: &str, + ) -> Option { + self.find_session(|session| { + if let Ok(session) = session.try_borrow() { + session.unit_key() == session_id + } else { + false + } + }) + } + + pub fn find_unit_model_by_unit_id_ignoring_borrowed_ones( + &self, + instance_id: UnitId, + ) -> Option { + self.find_session(|session| { + if let Ok(session) = session.try_borrow() { + session.unit_id() == instance_id + } else { + false + } + }) + } + + fn find_original_mapping( + &self, + initiator_instance_id: UnitId, + id: QualifiedMappingId, + ) -> Result { + let session = self + .find_unit_model_by_unit_id_ignoring_borrowed_ones(initiator_instance_id) + .ok_or("initiator session not found")?; + let session = session.borrow(); + let mapping = session + .find_mapping_by_id(id.compartment, id.id) + .ok_or("origin mapping not found")?; + Ok(mapping.clone()) + } + + pub fn find_session( + &self, + predicate: impl FnMut(&SharedUnitModel) -> bool, + ) -> Option { + self.unit_infos + .borrow() + .iter() + .filter_map(|s| s.unit_model.upgrade()) + .find(predicate) + } + + pub fn find_first_helgobox_instance_matching( + &self, + meets_criteria: impl Fn(&InstanceShellInfo) -> bool, + ) -> Option { + self.instance_shell_infos + .borrow() + .iter() + .filter_map(|info| { + if !meets_criteria(info) { + return None; + } + let track_index = info.processor_context.track()?.index()?; + Some((track_index, info)) + }) + .sorted_by_key(|(track_index, _)| *track_index) + .next() + .map(|(_, instance)| instance.instance_id) + } + + pub fn instance_count(&self) -> usize { + self.instance_shell_infos.borrow().len() + } + + pub fn with_instance_shell_infos(&self, f: impl FnOnce(&[InstanceShellInfo]) -> R) -> R { + f(&self.instance_shell_infos.borrow()) + } + + pub fn with_unit_infos(&self, f: impl FnOnce(&[UnitInfo]) -> R) -> R { + f(&self.unit_infos.borrow()) + } + + pub fn register_instance(&self, instance_shell: &SharedInstanceShell) { + debug!("Registering new instance..."); + let instance_model = instance_shell.model(); + let instance = Rc::downgrade(instance_model.borrow().instance()); + let instance_id = instance_shell.instance_id(); + let rt_instance = instance_shell.rt_instance(); + let info = InstanceShellInfo { + instance_id, + processor_context: instance_shell.processor_context(), + instance_shell: Arc::downgrade(instance_shell), + instance: instance.clone(), + }; + self.instance_shell_infos.borrow_mut().push(info); + Backbone::get().register_instance(instance_id, instance.clone()); + self.audio_hook_task_sender + .send_complaining(NormalAudioHookTask::AddRealTimeInstance( + instance_id, + rt_instance, + )); + self.control_surface_main_task_sender.0.send_complaining( + RealearnControlSurfaceMainTask::AddInstance(instance_id, instance), + ); + self.proto_hub.notify_instances_changed(); + } + + pub fn unregister_instance(&self, instance_id: InstanceId) { + debug!("Unregistering instance..."); + self.instance_shell_infos + .borrow_mut() + .retain(|i| i.instance_id != instance_id); + let _ = self.temporarily_reclaim_control_surface_ownership(|control_surface| { + // Remove main processor. + control_surface + .middleware_mut() + .remove_instance(instance_id); + }); + self.audio_hook_task_sender + .send_complaining(NormalAudioHookTask::RemoveRealTimeInstance(instance_id)); + self.proto_hub.notify_instances_changed(); + } + + pub fn register_unit( + &self, + unit_info: UnitInfo, + real_time_processor: SharedRealTimeProcessor, + main_processor: MainProcessor, + ) { + let unit_id = unit_info.unit_id; + debug!("Registering new unit {unit_id}..."); + let mut units = self.unit_infos.borrow_mut(); + if !unit_info.is_auto_unit { + update_auto_units_async(); + } + units.push(unit_info); + debug!("Unit {unit_id} registered. Unit count: {}", units.len()); + self.audio_hook_task_sender + .send_complaining(NormalAudioHookTask::AddRealTimeProcessor( + unit_id, + real_time_processor, + )); + self.control_surface_main_task_sender.0.send_complaining( + RealearnControlSurfaceMainTask::AddMainProcessor(main_processor), + ); + } + + pub fn unregister_unit(&self, unit_id: UnitId) { + self.unregister_unit_main_processor(unit_id); + self.unregister_unit_real_time_processor(unit_id); + debug!("Unregistering unit..."); + let mut units = self.unit_infos.borrow_mut(); + units.retain(|i| { + if i.unit_id != unit_id { + // Keep unit + return true; + } + // Remove that unit + if !i.is_auto_unit { + update_auto_units_async(); + } + false + }); + debug!( + "Unit unregistered. Remaining count of units: {}", + units.len() + ); + } + + pub fn show_message_panel( + &self, + title: impl Into, + msg: impl Into, + on_close: impl FnOnce() + 'static, + ) { + self.message_panel + .set_content(title.into(), msg.into(), on_close); + if !self.message_panel.is_open() { + self.message_panel.clone().open_without_parent(); + } + } + + pub fn close_message_panel(&self) { + self.message_panel.close(); + } + + fn register_extension_menu() -> anyhow::Result<()> { + let reaper = Reaper::get(); + reaper.medium_reaper().add_extensions_main_menu(); + reaper + .medium_session() + .plugin_register_add_hook_custom_menu::()?; + Ok(()) + } + + fn register_toolbar_icon_map() -> anyhow::Result<()> { + Reaper::get() + .medium_session() + .plugin_register_add_toolbar_icon_map::()?; + Ok(()) + } + + fn register_actions() { + for def in ACTION_DEFS { + def.register(); + } + } + + pub fn show_welcome_screen() { + let shell = Self::get(); + open_child_panel( + &shell.welcome_panel, + WelcomePanel::new(), + reaper_main_window(), + ); + } + + pub fn resolve_symbols_from_clipboard() { + if let Err(e) = resolve_symbols_from_clipboard() { + Reaper::get().show_console_msg(format!("{e}\n")); + } + } + + pub fn find_first_mapping_by_target() { + Global::future_support().spawn_in_main_thread_from_main_thread(async { + let _ = BackboneShell::get() + .find_first_mapping_by_target_async(CompartmentKind::Main) + .await; + Ok(()) + }); + } + + pub fn open_first_pot_browser() { + let Some(session) = BackboneShell::get().find_first_relevant_session_monitoring_first() + else { + return; + }; + session.borrow().ui().show_pot_browser(); + } + + pub fn find_first_mapping_by_learnable_source() { + Global::future_support().spawn_in_main_thread_from_main_thread(async { + let _ = BackboneShell::get() + .find_first_mapping_by_learnable_source_async(CompartmentKind::Main) + .await; + Ok(()) + }); + } + + pub fn learn_mapping_reassigning_learnable_source_open() { + Global::future_support().spawn_in_main_thread_from_main_thread(async { + let _ = BackboneShell::get() + .learn_mapping_reassigning_learnable_source_async(CompartmentKind::Main, true) + .await; + Ok(()) + }); + } + + pub fn send_feedback_for_all_instances() { + Self::get() + .control_surface_main_task_sender + .0 + .send_complaining(RealearnControlSurfaceMainTask::SendAllFeedback); + } + + pub fn detect_controllers_with_logging() { + spawn_in_main_thread(async { + let reaper = Reaper::get(); + reaper.show_console_msg("== ReaLearn: Detecting controllers, please wait ...\n\n"); + let devices = Reaper::get() + .midi_output_devices() + .filter(|dev| dev.is_connected()) + .map(|dev| dev.id()); + let probe = controller_detection::detect_controllers(devices.collect()) + .await + .inspect_err(notify_user_about_anyhow_error)?; + for probe in probe { + reaper.show_console_msg(probe.to_string()) + } + reaper.show_console_msg("Controller detection finished.\n"); + Ok(()) + }); + } + + pub fn learn_mapping_reassigning_learnable_source() { + Global::future_support().spawn_in_main_thread_from_main_thread(async { + let _ = BackboneShell::get() + .learn_mapping_reassigning_learnable_source_async(CompartmentKind::Main, false) + .await; + Ok(()) + }); + } + + pub fn learn_source_for_last_touched_target() { + let included_target_types = ReaperTargetType::iter().collect(); + let filter = LastTouchedTargetFilter { + included_target_types: &included_target_types, + touch_cause: TargetTouchCause::Any, + }; + let target = Backbone::get().find_last_touched_target(filter); + let target = match target.as_ref() { + None => return, + Some(t) => t, + }; + BackboneShell::get().start_learning_source_for_target(CompartmentKind::Main, target); + } + + pub fn toggle_app_focus() { + let _ = Self::get().toggle_app_focus_internal(); + } + + fn toggle_app_focus_internal(&self) -> anyhow::Result<()> { + let project = Reaper::get().current_project(); + let instance_id = self + .find_first_helgobox_instance_in_project(project) + .context("no Helgobox instance")?; + let instance_panel = self + .find_instance_panel_by_instance_id(instance_id) + .context("instance panel not found")?; + instance_panel.toggle_app_instance_focus(); + Ok(()) + } + + pub fn find_first_helgobox_instance_in_project(&self, project: Project) -> Option { + self.find_first_helgobox_instance_matching(|instance| { + instance + .processor_context + .project() + .is_some_and(|p| p == project) + }) + } + + pub fn find_first_playtime_helgobox_instance_in_project( + &self, + project: Project, + ) -> Option { + self.find_first_helgobox_instance_matching(|info| { + if info.processor_context.project() != Some(project) { + return false; + } + let Some(instance) = info.instance.upgrade() else { + return false; + }; + let instance_state = instance.borrow(); + instance_state.has_clip_matrix() + }) + } + + pub fn show_hide_playtime() { + #[cfg(feature = "playtime")] + { + playtime_impl::execute_playtime_show_hide_action(async { + playtime_impl::show_or_hide_playtime(false).await + }); + } + } + + pub fn show_hide_custom_playtime() { + #[cfg(feature = "playtime")] + { + playtime_impl::execute_playtime_show_hide_action(async { + playtime_impl::show_or_hide_playtime(true).await + }); + } + } + + async fn find_first_mapping_by_learnable_source_async( + &self, + compartment: CompartmentKind, + ) -> Result<(), &'static str> { + self.toggle_guard()?; + self.show_message_panel("ReaLearn", "Touch some control elements!", || { + BackboneShell::stop_learning_sources(); + }); + let midi_receiver = self.request_next_midi_messages(); + let osc_receiver = self.request_next_osc_messages(); + loop { + let capture_result = tokio::select! { + Ok(r) = midi_receiver.recv() => { + Some(MessageCaptureResult::Midi(r)) + } + Ok(r) = osc_receiver.recv() => { + Some(MessageCaptureResult::Osc(r)) + } + else => None + }; + let Some(r) = capture_result else { + break; + }; + if let Some(outcome) = + self.find_first_relevant_session_with_source_matching(compartment, &r) + { + if outcome.source_is_learnable { + self.close_message_panel(); + outcome + .unit_model + .borrow() + .show_mapping(compartment, outcome.mapping.borrow().id()); + } + } + } + Ok(()) + } + + async fn find_first_mapping_by_target_async( + &self, + compartment: CompartmentKind, + ) -> Result<(), &'static str> { + self.toggle_guard()?; + self.show_message_panel("ReaLearn", "Touch some targets!", || { + BackboneShell::get() + .control_surface_main_task_sender + .stop_capturing_targets(None); + }); + let receiver = self.control_surface_main_task_sender.capture_targets(None); + while let Ok(event) = receiver.recv().await { + if event.caused_by_realearn { + continue; + } + if let Some((session, mapping)) = + self.find_first_relevant_session_with_target(compartment, &event.target) + { + self.close_message_panel(); + session + .borrow() + .show_mapping(compartment, mapping.borrow().id()); + } + } + Ok(()) + } + + async fn learn_mapping_reassigning_learnable_source_async( + &self, + compartment: CompartmentKind, + open_mapping: bool, + ) -> Result<(), &'static str> { + self.toggle_guard()?; + if self.find_first_relevant_session_project_first().is_none() { + self.close_message_panel_with_alert( + "At first you need to add a ReaLearn instance to the monitoring FX chain or this project! Don't forget to set the MIDI control input.", + ); + return Err("no ReaLearn unit"); + } + self.show_message_panel("ReaLearn", "Touch a control element!", || { + BackboneShell::stop_learning_sources(); + }); + let midi_receiver = self.request_next_midi_messages(); + let osc_receiver = self.request_next_osc_messages(); + // Try until we found the first learnable message + let (unit_model, existing_mapping, capture_result) = loop { + // Capture next result + let capture_result = tokio::select! { + Ok(r) = midi_receiver.recv() => { + Some(MessageCaptureResult::Midi(r)) + } + Ok(r) = osc_receiver.recv() => { + Some(MessageCaptureResult::Osc(r)) + } + else => None + }; + let Some(capture_result) = capture_result else { + return Ok(()); + }; + // Make sure that there's at least one unit which has that control input + let unit_model = if let Some(s) = capture_result + .to_input_descriptor(false) + .and_then(|id| self.find_first_relevant_session_with_input_from(&id)) + { + s + } else { + self.close_message_panel_with_alert( + "No ReaLearn unit found which has this control input! First please add one to the monitoring FX chain or this project and set the MIDI control input accordingly!", + ); + return Err("no ReaLearn unit with that input"); + }; + // Find mapping that matches the source + if let Some(outcome) = + self.find_first_relevant_session_with_source_matching(compartment, &capture_result) + { + // We found a mapping! + if outcome.source_is_learnable { + // Yo, we found a matching mapping. Use that one for the rest of the procedure. + break (outcome.unit_model, Some(outcome.mapping), capture_result); + } else { + // Wait for next message because this source is not learnable + continue; + } + } else { + // Couldn't find that one. Check if mapping is learnable in the unit that we found. + let virtualization = unit_model + .borrow() + .virtualize_source_value(capture_result.message()); + if let Some(v) = virtualization { + if !v.learnable { + // Ignore because this source is not learnable in that unit model. + continue; + } + } + // We will have to create a new mapping. + break (unit_model, None, capture_result); + }; + }; + // Now learn target + let reaper_target = self + .prompt_for_next_reaper_target("Now touch the desired target!") + .await?; + // Close panel + self.close_message_panel(); + // Modify existing mapping or add new mapping + let mapping = if let Some(mapping) = existing_mapping { + // There's already a mapping with that source. Change target of that mapping. + { + let mut m = mapping.borrow_mut(); + unit_model.borrow_mut().change_target_with_closure( + &mut m, + None, + Rc::downgrade(&unit_model), + |ctx| { + ctx.mapping.target_model.apply_from_target( + &reaper_target, + ctx.extended_context, + compartment, + ) + }, + ); + } + mapping + } else { + // There's no mapping with that source yet. Add it to the previously determined first + // session. + let mapping = { + let mut s = unit_model.borrow_mut(); + let mapping = s.add_default_mapping( + compartment, + GroupId::default(), + VirtualControlElementCharacter::Multi, + ); + let mut m = mapping.borrow_mut(); + let event = MessageCaptureEvent { + result: capture_result, + allow_virtual_sources: true, + osc_arg_index_hint: None, + }; + let compound_source = s + .create_compound_source_for_learning(event) + .ok_or("couldn't create compound source")?; + let _ = m.source_model.apply_from_source(&compound_source); + let _ = m.target_model.apply_from_target( + &reaper_target, + s.extended_context(), + compartment, + ); + drop(m); + mapping + }; + mapping + }; + if open_mapping { + unit_model + .borrow() + .show_mapping(compartment, mapping.borrow().id()); + } + Ok(()) + } + + fn close_message_panel_with_alert(&self, msg: &str) { + self.close_message_panel(); + notification::alert(msg); + } + + fn toggle_guard(&self) -> Result<(), &'static str> { + if self.message_panel.is_open() { + self.close_message_panel(); + return Err("a message panel action was already executing, cancelled it"); + } + // Continue + Ok(()) + } + + fn stop_learning_sources() { + BackboneShell::get() + .audio_hook_task_sender + .send_complaining(NormalAudioHookTask::StopCapturingMidi); + BackboneShell::get() + .control_surface_main_task_sender + .0 + .send_complaining(RealearnControlSurfaceMainTask::StopCapturingOsc); + } + + fn request_next_midi_messages(&self) -> async_channel::Receiver { + let (sender, receiver) = async_channel::bounded(500); + self.audio_hook_task_sender + .send_complaining(NormalAudioHookTask::StartCapturingMidi(sender)); + receiver + } + + fn request_next_osc_messages(&self) -> async_channel::Receiver { + let (sender, receiver) = async_channel::bounded(500); + self.control_surface_main_task_sender + .0 + .send_complaining(RealearnControlSurfaceMainTask::StartCapturingOsc(sender)); + receiver + } + + pub async fn request_midi_device_identity( + &self, + output_device_id: MidiOutputDeviceId, + input_device_id: Option, + ) -> anyhow::Result { + let reply_receiver = + self.request_midi_device_identity_internal(output_device_id, input_device_id); + reply_receiver + .recv() + .await + .map_err(|_| anyhow!("no MIDI device identity reply received")) + } + + fn request_midi_device_identity_internal( + &self, + output_device_id: MidiOutputDeviceId, + input_device_id: Option, + ) -> async_channel::Receiver { + let (sender, receiver) = async_channel::bounded(10); + let command = RequestMidiDeviceIdentityCommand { + output_device_id, + input_device_id, + sender, + }; + self.audio_hook_task_sender + .send_complaining(NormalAudioHookTask::RequestMidiDeviceIdentity(command)); + receiver + } + + async fn prompt_for_next_reaper_target(&self, msg: &str) -> Result { + self.show_message_panel("ReaLearn", msg, || { + BackboneShell::get() + .control_surface_main_task_sender + .stop_capturing_targets(None); + }); + let receiver = self.control_surface_main_task_sender.capture_targets(None); + while let Ok(event) = receiver.recv().await { + if event.caused_by_realearn { + continue; + } + return Ok(event.target); + } + Err("capturing ended") + } + + fn start_learning_source_for_target( + &self, + compartment: CompartmentKind, + target: &ReaperTarget, + ) { + // Try to find an existing session which has a target with that parameter + let session = self + .find_first_relevant_session_with_target(compartment, target) + .map(|(s, _)| s) + // If not found, find the instance on the parameter's track (if there's one) + .or_else(|| { + target + .track() + .and_then(|t| self.find_first_session_on_track(t)) + }) + // If not found, find a random instance + .or_else(|| self.find_first_relevant_session_project_first()); + match session { + None => { + notification::alert( + "No suitable ReaLearn unit found! First please add one to the monitoring FX chain or this project!", + ); + } + Some(s) => { + let mapping = + s.borrow_mut() + .toggle_learn_source_for_target(&s, compartment, target, false); + s.borrow().show_mapping(compartment, mapping.borrow().id()); + } + } + } + + fn find_first_relevant_session_with_target( + &self, + compartment: CompartmentKind, + target: &ReaperTarget, + ) -> Option<(SharedUnitModel, SharedMapping)> { + let in_current_project = self.find_first_session_with_target( + Some(Reaper::get().current_project()), + compartment, + target, + ); + in_current_project + .or_else(|| self.find_first_session_with_target(None, compartment, target)) + } + + fn find_first_session_with_target( + &self, + project: Option, + compartment: CompartmentKind, + target: &ReaperTarget, + ) -> Option<(SharedUnitModel, SharedMapping)> { + self.unit_infos.borrow().iter().find_map(|session| { + let session = session.unit_model.upgrade()?; + let mapping = { + let s = session.borrow(); + if s.processor_context().project() != project { + return None; + } + s.find_mapping_with_target(compartment, target)?.clone() + }; + Some((session, mapping)) + }) + } + + fn find_first_session_on_track(&self, track: &Track) -> Option { + self.find_session(|session| { + let session = session.borrow(); + session.processor_context().track() == Some(track) + }) + } + + fn find_first_relevant_session_monitoring_first(&self) -> Option { + self.find_first_session_in_project(None) + .or_else(|| self.find_first_session_in_project(Some(Reaper::get().current_project()))) + } + + fn find_first_relevant_session_project_first(&self) -> Option { + self.find_first_session_in_project(Some(Reaper::get().current_project())) + .or_else(|| self.find_first_session_in_project(None)) + } + + /// Project None means monitoring FX chain. + fn find_first_session_in_project(&self, project: Option) -> Option { + self.find_session(|session| { + let session = session.borrow(); + session.processor_context().project() == project + }) + } + + fn find_first_relevant_session_with_input_from( + &self, + input_descriptor: &InputDescriptor, + ) -> Option { + let in_current_project = self.find_first_session_with_input_from( + Some(Reaper::get().current_project()), + input_descriptor, + ); + in_current_project + .or_else(|| self.find_first_session_with_input_from(None, input_descriptor)) + } + + fn find_first_session_with_input_from( + &self, + project: Option, + input_descriptor: &InputDescriptor, + ) -> Option { + self.find_session(|session| { + let session = session.borrow(); + session.processor_context().project() == project + && session.receives_input_from(input_descriptor) + }) + } + + fn find_first_relevant_session_with_source_matching( + &self, + compartment: CompartmentKind, + capture_result: &MessageCaptureResult, + ) -> Option { + let in_current_project = self.find_first_session_with_source_matching( + Some(Reaper::get().current_project()), + compartment, + capture_result, + ); + in_current_project.or_else(|| { + self.find_first_session_with_source_matching(None, compartment, capture_result) + }) + } + + fn find_first_session_with_source_matching( + &self, + project: Option, + compartment: CompartmentKind, + capture_result: &MessageCaptureResult, + ) -> Option { + self.unit_infos.borrow().iter().find_map(|session| { + let unit_model = session.unit_model.upgrade()?; + let outcome = { + let s = unit_model.borrow(); + if s.processor_context().project() != project { + return None; + } + let input_descriptor = capture_result.to_input_descriptor(true)?; + if !s.receives_input_from(&input_descriptor) { + return None; + } + s.find_mapping_with_source(compartment, capture_result.message())? + }; + let outcome = MatchingSourceOutcome { + unit_model, + mapping: outcome.mapping, + source_is_learnable: outcome.source_is_learnable, + }; + Some(outcome) + }) + } + + fn server_resource_dir_path() -> Utf8PathBuf { + Self::helgoboss_resource_dir_path().join("Server") + } + + fn notify_changed(&self) { + self.sessions_changed_subject.borrow_mut().next(()); + } + + fn do_with_initiator_session_or_sessions_matching_tags( + &self, + common_args: &ModifyUnitContainerCommonArgs, + f: impl Fn(&mut UnitModel, WeakUnitModel), + ) -> Result<(), &'static str> { + if common_args.scope.has_tags() { + // Modify all sessions whose tags match. + for instance in self.unit_infos.borrow().iter() { + if let Some(session) = instance.unit_model.upgrade() { + let mut session = session.borrow_mut(); + // Don't leave the context (project if in project, FX chain if monitoring FX). + let context = session.processor_context(); + if context.project() != common_args.initiator_project { + continue; + } + // Skip unmatched tags. + let session_tags = session.tags(); + if !common_args.scope.any_tag_matches(session_tags) { + continue; + } + f(&mut session, instance.unit_model.clone()) + } + } + } else { + // Modify the initiator session only. + let shared_session = self + .find_unit_model_by_unit_id_ignoring_borrowed_ones(common_args.initiator_unit_id) + .ok_or("initiator session not found")?; + let mut session = shared_session.borrow_mut(); + f(&mut session, Rc::downgrade(&shared_session)); + } + Ok(()) + } +} + +impl Drop for BackboneShell { + fn drop(&mut self) { + self.dispose(); + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(default)] +pub struct BackboneConfig { + main: MainConfig, + // Map from command name (e.g. HB_SHOW_HIDE_PLAYTIME to state integer, 0 for disabled , 1 for enabled) + toolbar: HashMap, +} + +impl BackboneConfig { + pub fn load() -> anyhow::Result { + let ini_content = + fs::read_to_string(Self::config_file_path()).context("couldn't read config file")?; + let config = serde_ini::from_str(&ini_content)?; + Ok(config) + } + + pub fn save(&self) -> anyhow::Result<()> { + let ini_content = serde_ini::to_string(self).context("couldn't serialize config")?; + let config_file = Self::config_file_path(); + let config_file_dir = config_file.parent().unwrap(); + fs::create_dir_all(config_file_dir).with_context(|| { + format!( + "Couldn't create configuration directory '{config_file_dir}'. Make sure that REAPER has write access to this directory!" + ) + })?; + fs::write(&config_file, ini_content) + .with_context(|| format!("Couldn't write config file '{config_file}'. Make sure that REAPER has write access to this file!"))?; + Ok(()) + } + + pub fn wants_remote_config(&self) -> bool { + self.send_errors_to_dev() || self.notify_about_updates() + } + + pub fn send_errors_to_dev(&self) -> bool { + self.main.send_errors_to_dev > 0 + } + + pub fn set_send_errors_to_dev(&mut self, value: bool) { + self.main.send_errors_to_dev = value.into(); + } + + pub fn show_errors_in_console(&self) -> bool { + self.main.show_errors_in_console > 0 + } + + pub fn set_show_errors_in_console(&mut self, value: bool) { + self.main.show_errors_in_console = value.into(); + } + + pub fn notify_about_updates(&self) -> bool { + self.main.notify_about_updates > 0 + } + + pub fn set_notify_about_updates(&mut self, value: bool) { + self.main.notify_about_updates = value.into(); + } + + pub fn enable_server(&mut self) { + self.main.server_enabled = 1; + } + + pub fn disable_server(&mut self) { + self.main.server_enabled = 0; + } + + pub fn server_is_enabled(&self) -> bool { + self.main.server_enabled > 0 + } + + pub fn companion_web_app_url(&self) -> url::Url { + Url::parse(&self.main.companion_web_app_url).expect("invalid companion web app URL") + } + + pub fn background_colors_enabled(&self) -> bool { + self.main.background_colors_enabled > 0 + } + + pub fn toggle_background_colors(&mut self) { + self.main.background_colors_enabled = if self.background_colors_enabled() { + 0 + } else { + 1 + }; + } + + pub fn toolbar_button_is_enabled(&self, command_name: &str) -> bool { + self.toolbar.get(command_name).is_some_and(|v| *v != 0) + } + + fn config_file_path() -> Utf8PathBuf { + BackboneShell::realearn_resource_dir_path().join("realearn.ini") + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct MainConfig { + #[serde(default, skip_serializing_if = "is_default")] + server_enabled: u8, + #[serde( + default = "default_server_http_port", + skip_serializing_if = "is_default_server_http_port" + )] + server_http_port: u16, + #[serde( + default = "default_server_https_port", + skip_serializing_if = "is_default_server_https_port" + )] + server_https_port: u16, + #[serde( + default = "default_server_grpc_port", + skip_serializing_if = "is_default_server_grpc_port" + )] + server_grpc_port: u16, + #[serde( + default = "default_companion_web_app_url", + skip_serializing_if = "is_default_companion_web_app_url" + )] + companion_web_app_url: String, + showed_welcome_screen: u8, + #[serde( + default = "default_background_colors_enabled", + skip_serializing_if = "is_default_background_colors_enabled" + )] + background_colors_enabled: u8, + #[serde( + default = "default_send_errors_to_dev", + skip_serializing_if = "is_default_send_errors_to_dev" + )] + send_errors_to_dev: u8, + #[serde( + default = "default_show_errors_in_console", + skip_serializing_if = "is_default_show_errors_in_console" + )] + show_errors_in_console: u8, + #[serde( + default = "default_notify_about_updates", + skip_serializing_if = "is_default_notify_about_updates" + )] + notify_about_updates: u8, +} + +const DEFAULT_SERVER_HTTP_PORT: u16 = 39080; +const DEFAULT_SERVER_HTTPS_PORT: u16 = 39443; +const DEFAULT_SERVER_GRPC_PORT: u16 = 39051; +const DEFAULT_BACKGROUND_COLORS_ENABLED: u8 = 1; +const DEFAULT_SHOW_ERRORS_IN_CONSOLE: u8 = 1; +/// For existing installations, we don't enable this (would change behavior). +const DEFAULT_SEND_ERRORS_TO_DEV: u8 = 0; +/// We enable this for existing installations as well, because our primary goal is to reduce +/// reports about errors that have already been fixed. +const DEFAULT_NOTIFY_ABOUT_UPDATES: u8 = 1; + +fn default_background_colors_enabled() -> u8 { + DEFAULT_BACKGROUND_COLORS_ENABLED +} + +fn is_default_background_colors_enabled(v: &u8) -> bool { + *v == DEFAULT_BACKGROUND_COLORS_ENABLED +} + +fn default_show_errors_in_console() -> u8 { + DEFAULT_SHOW_ERRORS_IN_CONSOLE +} + +fn default_notify_about_updates() -> u8 { + DEFAULT_NOTIFY_ABOUT_UPDATES +} + +fn is_default_show_errors_in_console(v: &u8) -> bool { + *v == DEFAULT_SHOW_ERRORS_IN_CONSOLE +} + +fn is_default_notify_about_updates(v: &u8) -> bool { + *v == DEFAULT_NOTIFY_ABOUT_UPDATES +} + +fn default_send_errors_to_dev() -> u8 { + DEFAULT_SEND_ERRORS_TO_DEV +} + +fn is_default_send_errors_to_dev(v: &u8) -> bool { + *v == DEFAULT_SEND_ERRORS_TO_DEV +} + +fn default_server_http_port() -> u16 { + DEFAULT_SERVER_HTTP_PORT +} + +fn is_default_server_http_port(v: &u16) -> bool { + *v == DEFAULT_SERVER_HTTP_PORT +} + +fn default_server_https_port() -> u16 { + DEFAULT_SERVER_HTTPS_PORT +} + +fn is_default_server_https_port(v: &u16) -> bool { + *v == DEFAULT_SERVER_HTTPS_PORT +} + +fn default_server_grpc_port() -> u16 { + DEFAULT_SERVER_GRPC_PORT +} + +fn is_default_server_grpc_port(v: &u16) -> bool { + *v == DEFAULT_SERVER_GRPC_PORT +} + +fn default_companion_web_app_url() -> String { + COMPANION_WEB_APP_URL.to_string() +} + +fn is_default_companion_web_app_url(v: &str) -> bool { + v == COMPANION_WEB_APP_URL +} + +impl Default for MainConfig { + fn default() -> Self { + // This default implementation is used when Helgobox hasn't been used before. It's the initial config. + MainConfig { + server_enabled: 0, + server_http_port: default_server_http_port(), + server_https_port: default_server_https_port(), + server_grpc_port: default_server_grpc_port(), + companion_web_app_url: default_companion_web_app_url(), + showed_welcome_screen: 0, + background_colors_enabled: default_background_colors_enabled(), + // + show_errors_in_console: default_show_errors_in_console(), + // For new installations, this is an opt-out. The welcome screen will be shown for sure, so that's okay. + send_errors_to_dev: 1, + notify_about_updates: default_notify_about_updates(), + } + } +} + +fn build_detailed_version() -> String { + use crate::infrastructure::plugin::built_info::*; + let dirty_mark = if GIT_DIRTY == Some(true) { + "-dirty" + } else { + "" + }; + let date_info = if let Ok(d) = chrono::DateTime::parse_from_rfc2822(BUILT_TIME_UTC) { + d.format("%Y-%m-%d").to_string() + } else { + BUILT_TIME_UTC.to_string() + }; + let debug_mark = if PROFILE == "debug" { "-debug" } else { "" }; + format!("v{PKG_VERSION}/{CFG_TARGET_ARCH}{debug_mark}{dirty_mark} ({date_info})") +} + +pub fn warn_about_failed_server_start(info: String) { + Reaper::get().show_console_msg(format!( + "Couldn't start ReaLearn projection server because {info}" + )) +} + +fn determine_module_base_address() -> Option { + let hinstance = Reaper::get() + .medium_reaper() + .plugin_context() + .h_instance()?; + Some(hinstance.as_ptr() as usize) +} + +impl HookPostCommand for BackboneShell { + fn call(command_id: CommandId, _flag: i32) { + BackboneShell::get() + .additional_feedback_event_sender + .send_complaining(AdditionalFeedbackEvent::ActionInvoked(ActionInvokedEvent { + section_context: SectionContext::MainSection, + command_id, + })); + } +} + +impl HwndInfo for BackboneShell { + fn call(hwnd: Option, info_type: HwndInfoType, msg: Option) -> i32 { + let Some(hwnd) = hwnd else { + return 0; + }; + const IGNORE: i32 = 0; + const PASS_TO_WINDOW: i32 = 1; + const PROCESS_GLOBALLY: i32 = -1; + // Special handling for SPACE key: Always let it process by Helgobox App even if defined as Global. + // Without that, users who defined SPACE as global hotkey wouldn't be able to enjoy the special SPACE key + // behavior in Playtime (playing Playtime only without REAPER when App window is focused). + if let Some(msg) = msg { + if msg.key().get() as u32 == raw::VK_SPACE && is_app_window(hwnd) { + debug!("Pressed global space in app window"); + return PASS_TO_WINDOW; + } + } + // Continue if no special SPACE handling invoked + match info_type { + HwndInfoType::IsTextField => { + let window = Window::from_hwnd(hwnd); + // Check if egui App window is in text entry mode. + if let Some(realearn_view) = + BackboneHelgoboxWindowSnitch.find_closest_realearn_view(window) + { + if realearn_view.wants_raw_keyboard_input() { + // We are in an egui app. Let's just always assume it's in text entry mode. In practice, + // this is mostly the case. https://github.com/helgoboss/helgobox/issues/1288 + return PASS_TO_WINDOW; + } + } + // Check if Helgobox App window is in text entry mode. + // This one is only necessary on Windows, see https://github.com/helgoboss/helgobox/issues/1083. + // REAPER detected a global hotkey press while having a child window focused. It wants to know whether + // this child window is currently in text-entry mode, in which case it would NOT execute the action + // associated with the global hotkey but direct the key to the window. We must check here whether + // the Helgobox App is currently in text entry mode. This is only necessary on Windows, because + // Flutter essentially just uses one big HWND on windows ... text fields are not different HWNDs and + // therefore not identifiable as text field (via Window classes "Edit", "RichEdit" etc.). + // When we end up here, we are on Windows (for macOS, the hook is not registered). + // The queried window has a parent + if !cfg!(windows) { + return IGNORE; + } + let Some(parent_window) = window.parent() else { + return IGNORE; + }; + match app_window_is_in_text_entry_mode(parent_window.raw_hwnd()) { + None => { + // Probably not a Helgobox App window + IGNORE + } + Some(false) => { + // It's a Helgobox App window, but we are not in text entry mode + PROCESS_GLOBALLY + } + Some(true) => { + // It's a Helgobox App, and we are in text entry mode + PASS_TO_WINDOW + } + } + } + HwndInfoType::ShouldProcessGlobalHotkeys | HwndInfoType::Unknown(_) => { + // This is called when the hotkey is defined with scope "Global + text fields". In this case, + // we don't need to do anything because we want the global hotkey to fire. + 0 + } + } + } +} + +impl HookPostCommand2 for BackboneShell { + fn call( + section: SectionContext, + command_id: CommandId, + _value_change: ActionValueChange, + window: WindowContext, + project: ReaProject, + ) { + if section == SectionContext::MainSection { + // Process executed main action as feedback + BackboneShell::get() + .additional_feedback_event_sender + .send_complaining(AdditionalFeedbackEvent::ActionInvoked(ActionInvokedEvent { + section_context: SectionContext::MainSection, + command_id, + })); + } + #[cfg(not(feature = "playtime"))] + { + let _ = (window, project); + } + #[cfg(feature = "playtime")] + { + post_process_action_invocation_for_playtime(section, command_id, window, project); + } + } +} + +#[cfg(feature = "playtime")] +fn post_process_action_invocation_for_playtime( + context: SectionContext, + command_id: CommandId, + window: WindowContext, + project: ReaProject, +) { + match context { + SectionContext::MainSection => { + let toggle_metronome_command_id = CommandId::new(40364); + if command_id == toggle_metronome_command_id { + // Metronome toggle + let toggle_metronome_action = Reaper::get() + .main_section() + .action_by_command_id(command_id); + if toggle_metronome_action.is_on() == Ok(Some(false)) { + // Switched metronome off. Switch Playtime clicks off as well! + BackboneShell::get().with_instance_shell_infos(|infos| { + for instance in infos.iter().flat_map(|info| info.instance.upgrade()) { + let mut instance = instance.borrow_mut(); + if let Some(matrix) = instance.clip_matrix_mut() { + matrix.set_click_enabled(false); + } + } + }); + } + } + } + SectionContext::Sec(sec) => { + let WindowContext::Win(hwnd) = window else { + return; + }; + if sec.unique_id().get() == 32060 { + // MIDI editor section + let play_stop_command_id = CommandId::new(40016); + if command_id == play_stop_command_id { + // Playback within a MIDI editor has been started or stopped. If this is a MIDI editor for a + // Playtime clip, we should stop playback of that clip. In both cases. + BackboneShell::get().with_instance_shell_infos(|infos| { + let relevant_helgobox_instances = infos + .iter() + .filter(|info| { + info.processor_context.project_or_current_project().raw() == project + }) + .flat_map(|info| info.instance.upgrade()); + for instance in relevant_helgobox_instances { + let mut instance = instance.borrow_mut(); + if let Some(matrix) = instance.clip_matrix_mut() { + matrix.panic_slot_with_open_midi_editor(hwnd); + } + } + }); + } + } + } + } +} + +impl UnitContainer for BackboneShell { + fn find_session_by_id(&self, session_id: &str) -> Option { + BackboneShell::get().find_session_by_id_ignoring_borrowed_ones(session_id) + } + + fn find_session_by_instance_id(&self, instance_id: UnitId) -> Option { + BackboneShell::get().find_unit_model_by_unit_id_ignoring_borrowed_ones(instance_id) + } + + fn enable_instances(&self, args: EnableInstancesArgs) -> Option> { + let mut activated_inverse_tags = HashSet::default(); + for unit_info in self.unit_infos.borrow().iter() { + // Don't touch ourselves. + let this_is_our_unit = match args.tag_kind { + InstanceTagKind::InstanceTags => { + unit_info.instance_id == args.common.initiator_instance_id + } + // When addressing instances via unit tags, we consider only the initiator unit as "self", not the + // surrounding instance. This is quite counter-intuitive because it means this change + // might actually affect the current instance (if it contains other units). But we need to + // keep that behavior to remain backward-compatible. Addressing instances via unit tags is legacy + // anyway. + InstanceTagKind::UnitTags => unit_info.unit_id == args.common.initiator_unit_id, + }; + if this_is_our_unit { + continue; + } + let Some(unit_model) = unit_info.unit_model.upgrade() else { + continue; + }; + let unit_model = unit_model.borrow(); + // Don't leave the context (project if in project, FX chain if monitoring FX). + let context = unit_model.processor_context(); + if context.project() != args.common.initiator_project { + continue; + } + // Determine how to change the instances. + let instance_model = unit_model.instance_model().borrow(); + let relevant_tags = match args.tag_kind { + InstanceTagKind::InstanceTags => instance_model.tags(), + InstanceTagKind::UnitTags => unit_model.tags(), + }; + let flag = match args.common.scope.determine_enable_disable_change( + args.exclusivity, + relevant_tags, + args.is_enable, + ) { + None => continue, + Some(f) => f, + }; + if args.exclusivity == Exclusivity::Exclusive && !args.is_enable { + // Collect all *other* instance tags because they are going to be activated + // and we have to know about them! + activated_inverse_tags.extend(relevant_tags.iter().cloned()); + } + let enable = if args.is_enable { flag } else { !flag }; + let fx = context.containing_fx(); + if enable { + let _ = fx.enable(); + } else { + let _ = fx.disable(); + } + } + if args.exclusivity == Exclusivity::Exclusive && !args.is_enable { + Some(activated_inverse_tags) + } else { + None + } + } + + fn enable_units(&self, args: EnableUnitsArgs) -> Option> { + let mut activated_inverse_tags = HashSet::default(); + for unit_info in self.unit_infos.borrow().iter() { + // Don't leave the context (current instance) + if unit_info.instance_id != args.common.initiator_instance_id { + continue; + } + let Some(unit_model) = unit_info.unit_model.upgrade() else { + continue; + }; + let mut unit_model = unit_model.borrow_mut(); + // Don't touch ourselves. + if unit_model.unit_id() == args.common.initiator_unit_id { + continue; + } + // Determine how to change the instances. + let relevant_tags = unit_model.tags(); + let flag = match args.common.scope.determine_enable_disable_change( + args.exclusivity, + relevant_tags, + args.is_enable, + ) { + None => continue, + Some(f) => f, + }; + if args.exclusivity == Exclusivity::Exclusive && !args.is_enable { + // Collect all *other* unit tags because they are going to be activated + // and we have to know about them! + activated_inverse_tags.extend(relevant_tags.iter().cloned()); + } + let enable = if args.is_enable { flag } else { !flag }; + unit_model.change_with_notification( + UnitCommand::SetEnabled(enable), + None, + unit_info.unit_model.clone(), + ); + } + if args.exclusivity == Exclusivity::Exclusive && !args.is_enable { + Some(activated_inverse_tags) + } else { + None + } + } + + fn change_instance_fx(&self, args: ChangeInstanceFxArgs) -> Result<(), &'static str> { + // At first create the FX descriptor that we want to set/pin in the destination sessions. + let fx_descriptor = match args.request { + InstanceFxChangeRequest::Pin { + track_guid, + is_input_fx, + fx_guid, + } => { + let track_desc = convert_optional_guid_to_api_track_descriptor(track_guid); + let chain_desc = if is_input_fx { + TrackFxChain::Input + } else { + TrackFxChain::Normal + }; + FxDescriptor::ById { + commons: Default::default(), + chain: FxChainDescriptor::Track { + track: Some(track_desc), + chain: Some(chain_desc), + }, + id: Some(fx_guid.to_string_without_braces()), + } + } + InstanceFxChangeRequest::SetFromMapping(id) => { + let mapping = self.find_original_mapping(args.common.initiator_unit_id, id)?; + let mapping = mapping.borrow(); + mapping.target_model.api_fx_descriptor() + } + }; + self.do_with_initiator_session_or_sessions_matching_tags( + &args.common, + |session, weak_session| { + session.change_with_notification( + UnitCommand::SetInstanceFx(fx_descriptor.clone()), + None, + weak_session, + ); + }, + ) + } + + fn change_instance_track(&self, args: ChangeInstanceTrackArgs) -> Result<(), &'static str> { + // At first create the track descriptor that we want to set/pin in the destination sessions. + let track_descriptor = match args.request { + InstanceTrackChangeRequest::Pin(guid) => { + convert_optional_guid_to_api_track_descriptor(guid) + } + InstanceTrackChangeRequest::SetFromMapping(id) => { + let mapping = self.find_original_mapping(args.common.initiator_unit_id, id)?; + let mapping = mapping.borrow(); + mapping.target_model.api_track_descriptor() + } + }; + self.do_with_initiator_session_or_sessions_matching_tags( + &args.common, + |session, weak_session| { + session.change_with_notification( + UnitCommand::SetInstanceTrack(track_descriptor.clone()), + None, + weak_session, + ); + }, + ) + } +} + +fn convert_optional_guid_to_api_track_descriptor(guid: Option) -> TrackDescriptor { + if let Some(guid) = guid { + TrackDescriptor::ById { + commons: Default::default(), + id: Some(guid.to_string_without_braces()), + } + } else { + TrackDescriptor::Master { + commons: Default::default(), + } + } +} + +#[derive(Debug)] +pub struct BackboneHelgoboxWindowSnitch; + +impl HelgoboxWindowSnitch for BackboneHelgoboxWindowSnitch { + fn find_closest_realearn_view(&self, window: Window) -> Option> { + let view_manager = ViewManager::get(); + let mut current_window = Some(window); + while let Some(w) = current_window { + if let Some(v) = view_manager.get_associated_view(w) { + // It's one of our views! + return Some(v); + } + current_window = w.parent(); + } + None + } +} + +#[derive(Debug)] +pub struct BackboneControlSurfaceEventHandler; + +impl ControlSurfaceEventHandler for BackboneControlSurfaceEventHandler { + fn midi_input_devices_changed( + &self, + _diff: &DeviceDiff, + _device_config_changed: bool, + ) { + BackboneShell::get() + .proto_hub() + .notify_midi_input_devices_changed(); + update_auto_units_async(); + } + + fn midi_output_devices_changed( + &self, + diff: &DeviceDiff, + _device_config_changed: bool, + ) { + BackboneShell::get() + .proto_hub() + .notify_midi_output_devices_changed(); + update_auto_units_async(); + let added_devices = diff.added_devices.iter().copied().collect(); + spawn_in_main_thread(async move { + let _ = controller_detection::detect_controllers(added_devices).await; + Ok(()) + }); + } + + fn process_reaper_change_events(&self, change_events: &[ChangeEvent]) { + BackboneShell::get() + .proto_hub + .send_global_events_caused_by_reaper_change_events(change_events); + } +} + +#[derive(Debug)] +pub struct BackboneMainPresetManagerEventHandler; + +impl CompartmentPresetManagerEventHandler for BackboneMainPresetManagerEventHandler { + type Source = FileBasedMainPresetManager; + + fn presets_changed(&self, source: &Self::Source) { + BackboneShell::get() + .proto_hub() + .notify_main_presets_changed(source); + } +} + +#[derive(Debug)] +pub struct BackboneControllerManagerEventHandler; + +impl ControllerManagerEventHandler for BackboneControllerManagerEventHandler { + fn controller_config_changed(&self, source: &ControllerManager) { + tracing::debug!("Controller config changed"); + BackboneShell::get() + .proto_hub() + .notify_controller_config_changed(source); + update_auto_units_async(); + } +} + +#[derive(Debug)] +pub struct BackboneLicenseManagerEventHandler; + +impl LicenseManagerEventHandler for BackboneLicenseManagerEventHandler { + fn licenses_changed(&self, source: &LicenseManager) { + let shell = BackboneShell::get(); + // Inform Playtime, currently the only module which contains license-only functions + #[cfg(feature = "playtime")] + { + use helgobox_api::runtime::GlobalInfoEvent; + // Let the Playtime Clip Engine check if it finds a suitable license + let success = playtime_clip_engine::PlaytimeEngine::get() + .main() + .handle_changed_licenses(source.licenses()); + // Send a notification to the app (if it wants to display "success") + let info_event = if success { + GlobalInfoEvent::PlaytimeActivationSucceeded + } else { + GlobalInfoEvent::PlaytimeActivationFailed + }; + shell.proto_hub().notify_about_global_info_event(info_event); + // Give all Playtime instances a chance to load previously unloaded matrices + shell.with_instance_shell_infos(|infos| { + for instance in infos.iter().filter_map(|info| info.instance.upgrade()) { + let mut instance = instance.borrow_mut(); + if let Some(matrix) = instance.clip_matrix_mut() { + let result = matrix.notify_license_state_changed(); + notification::notify_user_on_anyhow_error(result); + } + } + }); + } + // Broadcast new license list (if the app wants to display it) + shell.proto_hub().notify_licenses_changed(source); + } +} + +#[derive(Debug)] +pub struct BackboneControllerPresetManagerEventHandler; + +impl CompartmentPresetManagerEventHandler for BackboneControllerPresetManagerEventHandler { + type Source = FileBasedControllerPresetManager; + + fn presets_changed(&self, source: &Self::Source) { + BackboneShell::get() + .proto_hub() + .notify_controller_presets_changed(source); + } +} + +fn load_app_library() -> anyhow::Result { + tracing::info!("Loading app library..."); + let app_base_dir = BackboneShell::app_binary_base_dir_path(); + let lib = crate::infrastructure::ui::AppLibrary::load(app_base_dir.into()); + match lib.as_ref() { + Ok(_) => { + tracing::info!("App library loaded successfully"); + } + Err(e) => { + tracing::warn!("App library loading failed: {e:#}"); + } + } + lib +} + +fn decompress_app() -> anyhow::Result<()> { + // Check if decompression necessary + let archive_file = &BackboneShell::app_archive_file_path(); + let destination_dir = &BackboneShell::app_binary_base_dir_path(); + let archive_metadata = archive_file.metadata()?; + let archive_size = archive_metadata.len(); + let archive_modified = archive_metadata + .modified()? + .duration_since(std::time::SystemTime::UNIX_EPOCH)?; + let archive_id = format!("{archive_size},{}", archive_modified.as_millis()); + let archive_id_file = destination_dir.join("ARCHIVE"); + if let Ok(unpacked_archive_id) = fs::read_to_string(&archive_id_file) { + if archive_id == unpacked_archive_id { + tracing::info!("App is already decompressed."); + return Ok(()); + } + } + // Decompress + tracing::info!("Decompressing app..."); + let archive_file = + fs::File::open(archive_file).context("Couldn't open app archive file. Maybe you installed ReaLearn manually (without ReaPack) and forgot to add the app archive?")?; + let tar = zstd::Decoder::new(&archive_file).context("Couldn't decode app archive file.")?; + let mut archive = tar::Archive::new(tar); + if destination_dir.exists() { + #[cfg(target_family = "windows")] + let context = "Couldn't clean up existing app directory. This can happen if you have \"Allow complete unload of VST plug-ins\" enabled in REAPER preferences => Plug-ins => VST. Turn this option off and restart REAPER before using the app."; + #[cfg(target_family = "unix")] + let context = "Couldn't remove existing app directory"; + fs::remove_dir_all(destination_dir).context(context)?; + } + archive + .unpack(destination_dir) + .context("Couldn't unpack app archive.")?; + fs::write(archive_id_file, archive_id)?; + tracing::info!("App decompressed successfully"); + Ok(()) +} + +impl HookCustomMenu for BackboneShell { + fn call(menuidstr: &ReaperStr, menu: Hmenu, flag: MenuHookFlag) { + if flag != MenuHookFlag::Init || menuidstr.to_str() != "Main extensions" { + return; + } + let swell_menu = Menu::new(menu.as_ptr()); + let pure_menu = menus::extension_menu(); + swell_ui::menu_tree::add_all_entries_of_menu(swell_menu, &pure_menu); + } +} + +impl ToolbarIconMap for BackboneShell { + fn call( + _toolbar_name: &ReaperStr, + command_id: CommandId, + _toggle_state: Option, + ) -> Option<&'static ReaperStr> { + Reaper::get().with_our_command(command_id, |command| { + let command_name = command?.command_name(); + let action_def = ACTION_DEFS + .iter() + .find(|d| d.command_name == command_name)?; + action_def + .icon + .map(|icon| unsafe { ReaperStr::from_ptr(icon.as_ptr()) }) + }) + } +} + +pub fn reaper_main_window() -> Window { + Window::from_hwnd(Reaper::get().main_window()) +} + +pub struct NewInstanceOutcome { + pub fx: Fx, + pub instance_shell: SharedInstanceShell, +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use crate::base::notification; + use crate::domain::err_if_reaper_version_too_low_for_playtime; + use crate::infrastructure::data::LicenseManager; + use crate::infrastructure::plugin::{BackboneShell, NewInstanceOutcome}; + use crate::infrastructure::ui::util::open_in_browser; + use crate::infrastructure::ui::AppPage; + use anyhow::Context; + use base::future_util::millis; + use base::metrics_util::{record_duration, record_occurrence}; + use base::spawn_in_main_thread; + use camino::Utf8PathBuf; + use futures::future::BoxFuture; + use playtime_api::persistence::PlaytimeSettings; + use playtime_clip_engine::PlaytimeEngine; + use reaper_high::{GroupingBehavior, Project, Reaper}; + use reaper_medium::{ + GangBehavior, InputMonitoringMode, MessageBoxResult, MessageBoxType, OpenProjectBehavior, + RecordingInput, + }; + use std::fs; + use std::future::Future; + + impl BackboneShell { + pub(crate) fn read_playtime_settings() -> Option { + let json = fs::read_to_string(Self::playtime_settings_file_path()).ok()?; + serde_json::from_str(&json).ok() + } + + pub fn playtime_settings_file_path() -> Utf8PathBuf { + Self::playtime_dir_path().join("settings.json") + } + + pub fn playtime_dir_path() -> Utf8PathBuf { + Self::helgoboss_resource_dir_path().join("Playtime") + } + } + + pub fn execute_playtime_show_hide_action( + future: impl Future> + 'static, + ) { + // It's possible that the backbone shell is not yet woken up at this point, in which case + // spawning wouldn't work. + let _ = BackboneShell::get().wake_up(); + spawn_in_main_thread(async { + let result = future.await; + notification::notify_user_on_anyhow_error(result); + Ok(()) + }) + } + /// Creates a new track in the given project and adds a new Helgobox instance to it. + pub async fn create_new_instance_in_project( + project: Project, + track_name: &str, + ) -> anyhow::Result { + let track = project.insert_track_at(0)?; + track.set_name(track_name); + track.set_recording_input(Some(RecordingInput::Midi { + device_id: None, + channel: None, + })); + track.arm( + false, + GangBehavior::DenyGang, + GroupingBehavior::PreventGrouping, + ); + track.set_input_monitoring_mode( + InputMonitoringMode::Normal, + GangBehavior::DenyGang, + GroupingBehavior::PreventGrouping, + ); + BackboneShell::create_new_instance_on_track(&track).await + } + + pub fn init_clip_engine(license_manager: &LicenseManager) { + #[derive(Debug)] + struct RealearnMetricsRecorder; + impl playtime_clip_engine::MetricsRecorder for RealearnMetricsRecorder { + fn record_duration(&self, id: &'static str, delta: std::time::Duration) { + record_duration(id, delta); + } + + fn record_occurrence(&self, id: &'static str) { + record_occurrence(id); + } + } + let metrics_recorder: Option = + if base::metrics_util::metrics_are_enabled() { + Some(&RealearnMetricsRecorder) + } else { + None + }; + #[derive(Debug)] + struct HelgoboxPlaytimeIntegration; + impl playtime_clip_engine::PlaytimeIntegration for HelgoboxPlaytimeIntegration { + fn export_to_clipboard( + &self, + item: &dyn playtime_clip_engine::PlaytimeItem, + ) -> anyhow::Result<()> { + let text = crate::infrastructure::ui::lua_serializer::to_string(item)?; + crate::infrastructure::ui::copy_text_to_clipboard(text)?; + Ok(()) + } + + fn changed_settings(&self, settings: PlaytimeSettings) -> anyhow::Result<()> { + BackboneShell::get() + .proto_hub + .notify_playtime_settings_changed(); + let json = serde_json::to_string_pretty(&settings)?; + let settings_path = BackboneShell::playtime_settings_file_path(); + fs::create_dir_all( + settings_path + .parent() + .context("Playtime settings file has not parent")?, + )?; + fs::write(settings_path, json)?; + Ok(()) + } + + fn spawn_in_async_runtime(&self, f: BoxFuture<'static, ()>) { + BackboneShell::get().spawn_in_async_runtime(f); + } + } + let args = playtime_clip_engine::PlaytimeEngineInitArgs { + available_licenses: license_manager.licenses(), + settings: BackboneShell::read_playtime_settings(), + metrics_recorder, + integration: Box::new(HelgoboxPlaytimeIntegration), + }; + PlaytimeEngine::make_available_globally(PlaytimeEngine::new(args)); + } + + fn enable_playtime_for_first_helgobox_instance_and_show_it() -> anyhow::Result<()> { + // let plugin_context = Reaper::get().medium_reaper().low().plugin_context(); + // We don't really need to do that via the external API but on the other hand, this is the only + // example so far where we actually use our exposed API! If we don't "eat our own dogfood", we would have + // to add integration tests in order to quickly realize if this works or not. + // TODO-low Add integration tests instead of using API here. + // let helgobox_api = helgobox_api::runtime::HelgoboxApiSession::load(plugin_context) + // .context("Couldn't load Helgobox API even after adding Helgobox. Old version?")?; + // let playtime_api = playtime_api::runtime::PlaytimeApiSession::load(plugin_context) + // .context("Couldn't load Playtime API even after adding Helgobox. Old version? Or Helgobox built without Playtime?")?; + // let instance_id = helgobox_api.HB_FindFirstHelgoboxInstanceInProject(std::ptr::null_mut()); + // playtime_api.HB_CreateClipMatrix(instance_id); + // playtime_api.HB_ShowOrHidePlaytime(instance_id); + let project = Reaper::get().current_project(); + let instance_id = BackboneShell::get() + .find_first_helgobox_instance_in_project(project) + .context("Couldn't find any Helgobox instance in this project.")?; + let instance_shell = BackboneShell::get() + .find_instance_shell_by_instance_id(instance_id) + .context("instance not found")?; + instance_shell + .clone() + .insert_owned_clip_matrix_if_necessary()?; + instance_shell + .panel() + .start_show_or_hide_app_instance(Some(AppPage::Playtime)); + Ok(()) + } + + pub async fn show_or_hide_playtime(use_template: bool) -> anyhow::Result<()> { + let project = Reaper::get().current_project(); + match BackboneShell::get().find_first_playtime_helgobox_instance_in_project(project) { + None => { + // Project doesn't have any Playtime-enabled Helgobox instance yet. Add one. + err_if_reaper_version_too_low_for_playtime()?; + if use_template { + let resource_path = Reaper::get().resource_path(); + let template_path = + resource_path.join("TrackTemplates/Playtime.RTrackTemplate"); + if !template_path.exists() { + let msg = "Before using this function, please create a custom top-level REAPER track template called \"Playtime\"!\n\nIt should include your customized Playtime track as well as any used column tracks.\n\nDo you need further help?"; + let result = Reaper::get().medium_reaper().show_message_box( + msg, + "Playtime", + MessageBoxType::YesNo, + ); + if result == MessageBoxResult::Yes { + open_in_browser( + "https://docs.helgoboss.org/playtime/goto#start-custom-playtime", + ); + } + return Ok(()); + } + Reaper::get() + .medium_reaper() + .main_open_project(&template_path, OpenProjectBehavior::default()); + millis(100).await; + let instance_id = BackboneShell::get() + .find_first_helgobox_instance_in_project(project) + .context("Looks like your track template \"Playtime\" doesn't contain any Helgobox instance. Please recreate the Playtime track template and try again!")?; + let instance_shell = BackboneShell::get() + .find_instance_shell_by_instance_id(instance_id) + .context("instance not found")?; + instance_shell + .panel() + .start_show_or_hide_app_instance(Some(AppPage::Playtime)); + } else { + create_new_instance_in_project(project, "Playtime").await?; + enable_playtime_for_first_helgobox_instance_and_show_it()?; + } + } + Some(instance_id) => { + let instance_panel = BackboneShell::get() + .find_instance_panel_by_instance_id(instance_id) + .context("Instance not found")?; + instance_panel.start_show_or_hide_app_instance(Some(AppPage::Playtime)); + } + } + Ok(()) + } +} + +struct MatchingSourceOutcome { + unit_model: SharedUnitModel, + mapping: SharedMapping, + source_is_learnable: bool, +} + +fn create_plugin_info() -> PluginInfo { + PluginInfo { + plugin_name: "Helgobox".to_string(), + plugin_version: built_info::PKG_VERSION.to_string(), + plugin_version_long: BackboneShell::detailed_version_label().to_string(), + support_email_address: "info@helgoboss.org".to_string(), + update_url: "https://www.helgoboss.org/projects/helgobox".to_string(), + } +} + +async fn fetch_remote_config(also_init_sentry: bool) { + if let Err(e) = fetch_remote_config_internal(also_init_sentry).await { + tracing::warn!(msg = "Couldn't fetch remote config", ?e) + } +} + +async fn fetch_remote_config_internal(also_init_sentry: bool) -> anyhow::Result<()> { + let remote_config = HelgoboxRemoteConfig::fetch().await?; + if also_init_sentry { + if let Err(e) = sentry::init_sentry(&create_plugin_info(), &remote_config.sentry) { + tracing::warn!(msg = "Couldn't init Sentry", ?e) + } + } + let _ = HELGOBOX_REMOTE_CONFIG.set(remote_config); + Ok(()) +} + +static HELGOBOX_REMOTE_CONFIG: OnceLock = OnceLock::new(); + +/// At project close or exit, it's important to wait a bit, otherwise we risk the MIDI is not being sent. +/// +/// We wait for 3 audio blocks, a maximum of 100 milliseconds. Justin's recommendation. +fn sleep_for_a_few_audio_blocks() { + let initial_block_count = GLOBAL_AUDIO_STATE.load_block_count(); + for _ in 0..100 { + std::thread::sleep(Duration::from_millis(1)); + let elapsed_blocks = GLOBAL_AUDIO_STATE + .load_block_count() + .saturating_sub(initial_block_count); + if elapsed_blocks > 2 { + debug!( + "Waited a total of {elapsed_blocks} blocks after sending shutdown MIDI messages" + ); + break; + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/clip_matrix_handler.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/clip_matrix_handler.rs new file mode 100644 index 0000000..ee5c3ce --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/clip_matrix_handler.rs @@ -0,0 +1,270 @@ +use crate::application::{SharedUnitModel, WeakUnitModel}; +use crate::domain::{ + CompartmentKind, InstanceId, PlaytimeColumnActionTarget, PlaytimeMatrixActionTarget, + PlaytimeRowActionTarget, PlaytimeSlotTransportTarget, QualifiedClipMatrixEvent, + RealTimeInstanceTask, ReaperTarget, +}; +use crate::infrastructure::plugin::WeakInstanceShell; +use anyhow::Context; +use base::{spawn_in_main_thread, Global, NamedChannelSender}; +use helgobox_api::persistence::{ + PlaytimeColumnAction, PlaytimeMatrixAction, PlaytimeRowAction, PlaytimeSlotTransportAction, +}; +use playtime_api::runtime::{ + ControlUnit, ControlUnitId, SimpleMappingContainer, SimpleMappingTarget, +}; +use playtime_clip_engine::base::ClipMatrixEvent; +use reaper_high::Reaper; + +#[derive(Debug)] +pub struct MatrixHandler { + instance_id: InstanceId, + audio_hook_task_sender: base::SenderToRealTimeThread, + real_time_instance_task_sender: + base::SenderToRealTimeThread, + event_sender: base::SenderToNormalThread, + weak_instance_shell: WeakInstanceShell, + main_unit_model: WeakUnitModel, +} + +impl MatrixHandler { + pub fn new( + instance_id: InstanceId, + audio_hook_task_sender: base::SenderToRealTimeThread, + real_time_instance_task_sender: base::SenderToRealTimeThread< + crate::domain::RealTimeInstanceTask, + >, + event_sender: base::SenderToNormalThread, + weak_instance_shell: WeakInstanceShell, + main_unit_model: WeakUnitModel, + ) -> Self { + Self { + instance_id, + audio_hook_task_sender, + real_time_instance_task_sender, + event_sender, + weak_instance_shell, + main_unit_model, + } + } + + fn invalidate_control_units(&self) { + let weak_instance_shell = self.weak_instance_shell.clone(); + spawn_in_main_thread(async move { + let instance_shell = weak_instance_shell + .upgrade() + .context("instance shell gone")?; + let instance_model = instance_shell.model().borrow(); + let instance = instance_model.instance().borrow(); + let matrix = instance.clip_matrix().context("no matrix")?; + let column_count = matrix.column_count(); + let row_count = matrix.row_count(); + for unit_model in instance_shell.additional_unit_models() { + unit_model + .borrow() + .unit() + .borrow_mut() + .invalidate_control_unit_scroll_pos(column_count, row_count); + } + Ok(()) + }); + } + + fn do_async_with_main_unit_model(&self, f: impl FnOnce(SharedUnitModel) + 'static) { + let session = self.main_unit_model.clone(); + Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(move || { + let shared_session = session.upgrade().expect("session gone"); + f(shared_session); + }) + .unwrap(); + } +} + +impl playtime_clip_engine::base::ClipMatrixHandler for MatrixHandler { + fn init_recording(&self, command: playtime_clip_engine::base::HandlerInitRecordingCommand) { + use crate::domain::NormalAudioHookTask; + use playtime_clip_engine::rt::audio_hook::PlaytimeAudioHookCommand; + use playtime_clip_engine::rt::fx_hook::PlaytimeFxHookCommand; + match command.create_specific_command() { + playtime_clip_engine::base::SpecificInitRecordingCommand::HardwareInput(t) => { + let playtime_command = PlaytimeAudioHookCommand::InitRecording(t); + self.audio_hook_task_sender.send_complaining( + NormalAudioHookTask::PlaytimeClipEngineCommand(playtime_command), + ); + } + playtime_clip_engine::base::SpecificInitRecordingCommand::FxInput(t) => { + let playtime_command = PlaytimeFxHookCommand::InitRecording(t); + self.real_time_instance_task_sender.send_complaining( + RealTimeInstanceTask::PlaytimeClipEngineCommand(playtime_command), + ); + } + } + } + + fn emit_event(&self, event: playtime_clip_engine::base::ClipMatrixEvent) { + // Check if we should do something special with that event + if matches!(event, ClipMatrixEvent::EverythingChanged) { + self.invalidate_control_units(); + } + // Send event to receiver + let event = QualifiedClipMatrixEvent { + instance_id: self.instance_id, + event, + }; + self.event_sender.send_complaining(event); + } + + fn get_simple_mappings(&self) -> SimpleMappingContainer { + let session = self.main_unit_model.upgrade().expect("session gone"); + let session = session.borrow(); + let simple_mappings = session + .mappings(CompartmentKind::Main) + .filter_map(|m| m.borrow().get_simple_mapping()); + SimpleMappingContainer { + mappings: simple_mappings.collect(), + } + } + + fn get_control_units(&self) -> Vec { + let Some(instance_shell) = self.weak_instance_shell.upgrade() else { + return vec![]; + }; + instance_shell + .additional_unit_models() + .into_iter() + .map(|unit_model| { + let unit_model = unit_model.borrow(); + let unit = unit_model.unit().borrow(); + ControlUnit { + id: ControlUnitId::new(unit_model.unit_id().into()), + name: unit_model.name_or_key().to_string(), + palette_color: unit.control_unit_palette_color(), + top_left_corner: unit.control_unit_top_left_corner(), + column_count: unit.control_unit_column_count(), + row_count: unit.control_unit_row_count(), + } + }) + .collect() + } + + fn get_currently_learning_target(&self) -> Option { + let shared_session = self.main_unit_model.upgrade().expect("session gone"); + let session = shared_session.borrow(); + let instance_state = session.unit(); + let instance_state = instance_state.borrow(); + let learning_mapping_id = instance_state.mapping_which_learns_source().get()?; + if learning_mapping_id.compartment != CompartmentKind::Main { + return None; + } + let mapping = session.find_mapping_by_qualified_id(learning_mapping_id)?; + let target = mapping.borrow().target_model.simple_target(); + target + } + + fn toggle_learn_source_by_target(&self, target: SimpleMappingTarget) { + self.do_async_with_main_unit_model(move |shared_session| { + let mut session = shared_session.borrow_mut(); + let mapping_desc = SimpleMappingDesc::from_simple_target(target); + session.toggle_learn_source_for_target( + &shared_session, + CompartmentKind::Main, + &mapping_desc.reaper_target, + mapping_desc.toggle, + ); + }); + } + + fn remove_mapping_by_target(&self, target: SimpleMappingTarget) { + self.do_async_with_main_unit_model(move |shared_session| { + let mut session = shared_session.borrow_mut(); + let mapping_desc = SimpleMappingDesc::from_simple_target(target); + session.remove_mapping_by_target(CompartmentKind::Main, &mapping_desc.reaper_target); + }); + } + + fn warn_about_less_important_issue(&self, message: &str) { + // The "!SHOW:" prefix makes the console window not open since REAPER 7. + Reaper::get().show_console_msg(format!("!SHOW:Helgobox: {message}")); + } +} + +struct SimpleMappingDesc { + pub reaper_target: ReaperTarget, + pub toggle: bool, +} + +impl SimpleMappingDesc { + pub fn from_simple_target(simple_target: SimpleMappingTarget) -> Self { + use SimpleMappingTarget::*; + match simple_target { + TriggerMatrix => Self { + reaper_target: ReaperTarget::PlaytimeMatrixAction(PlaytimeMatrixActionTarget { + action: PlaytimeMatrixAction::Stop, + }), + toggle: false, + }, + TriggerColumn(t) => Self { + reaper_target: ReaperTarget::PlaytimeColumnAction(PlaytimeColumnActionTarget { + column_index: t.index, + action: PlaytimeColumnAction::Stop, + }), + toggle: false, + }, + TriggerRow(t) => Self { + reaper_target: ReaperTarget::PlaytimeRowAction(PlaytimeRowActionTarget { + basics: crate::domain::ClipRowTargetBasics { + row_index: t.index, + action: PlaytimeRowAction::PlayScene, + }, + }), + toggle: false, + }, + TriggerSlot(t) => Self { + reaper_target: ReaperTarget::PlaytimeSlotTransportAction( + PlaytimeSlotTransportTarget { + project: Reaper::get().current_project(), + basics: crate::domain::ClipTransportTargetBasics { + slot_address: t, + action: PlaytimeSlotTransportAction::Trigger, + options: Default::default(), + }, + }, + ), + toggle: false, + }, + SmartRecord => Self { + reaper_target: ReaperTarget::PlaytimeMatrixAction(PlaytimeMatrixActionTarget { + action: PlaytimeMatrixAction::SmartRecord, + }), + toggle: false, + }, + EnterSilenceModeOrPlayIgnited => Self { + reaper_target: { + ReaperTarget::PlaytimeMatrixAction(PlaytimeMatrixActionTarget { + action: PlaytimeMatrixAction::StartOrStopPlayback, + }) + }, + toggle: true, + }, + SequencerRecordOnOffState => Self { + reaper_target: ReaperTarget::PlaytimeMatrixAction(PlaytimeMatrixActionTarget { + action: PlaytimeMatrixAction::SequencerRecordOnOffState, + }), + toggle: true, + }, + SequencerPlayOnOffState => Self { + reaper_target: ReaperTarget::PlaytimeMatrixAction(PlaytimeMatrixActionTarget { + action: PlaytimeMatrixAction::SequencerPlayOnOffState, + }), + toggle: true, + }, + TapTempo => Self { + reaper_target: ReaperTarget::PlaytimeMatrixAction(PlaytimeMatrixActionTarget { + action: PlaytimeMatrixAction::TapTempo, + }), + toggle: false, + }, + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/controller_detection.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/controller_detection.rs new file mode 100644 index 0000000..5bdd59b --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/controller_detection.rs @@ -0,0 +1,359 @@ +use crate::domain::{ + format_as_pretty_hex, MidiInDevsConfig, MidiOutDevsConfig, RequestMidiDeviceIdentityReply, +}; +use crate::infrastructure::data::MainPresetSelectionConditions; +use crate::infrastructure::plugin::{ini_util, BackboneShell}; +use anyhow::{anyhow, bail}; +use helgobox_api::persistence::{ + CompartmentPresetId, Controller, ControllerConnection, MidiControllerConnection, MidiInputPort, + MidiOutputPort, +}; +use helgobox_api::runtime::{AutoAddedControllerEvent, GlobalInfoEvent}; +use reaper_high::Reaper; +use reaper_medium::MidiOutputDeviceId; +use std::fmt::{Display, Formatter}; +use std::sync::atomic::{AtomicBool, Ordering}; + +pub struct ControllerProbe { + pub out_dev_id: MidiOutputDeviceId, + pub result: Result, +} + +impl Display for ControllerProbe { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let dev = Reaper::get().midi_output_device_by_id(self.out_dev_id); + writeln!( + f, + "=== MIDI output device [{}] {:?}\n", + self.out_dev_id, + dev.name().unwrap_or_default() + )?; + match &self.result { + Ok(probe) => probe.fmt(f)?, + Err(probe) => probe.fmt(f)?, + } + writeln!(f)?; + Ok(()) + } +} + +pub struct SuccessfulControllerProbe { + pub outcome: ControllerProbeOutcome, + pub payload: ControllerProbePayload, +} + +impl SuccessfulControllerProbe { + pub fn new(outcome: ControllerProbeOutcome, payload: ControllerProbePayload) -> Self { + Self { outcome, payload } + } +} + +impl Display for SuccessfulControllerProbe { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Outcome: {}\n\n{}", &self.outcome, &self.payload) + } +} + +pub struct FailedControllerProbe { + pub error: anyhow::Error, + pub payload: ControllerProbePayload, +} + +impl FailedControllerProbe { + pub fn new(error: anyhow::Error, payload: ControllerProbePayload) -> Self { + Self { error, payload } + } +} + +impl Display for FailedControllerProbe { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Error: {:#?}\n\n{}", &self.error, &self.payload) + } +} + +#[derive(Default)] +pub struct ControllerProbePayload { + pub identity_reply: Option, +} + +impl Display for ControllerProbePayload { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let Some(r) = &self.identity_reply { + let in_dev = Reaper::get().midi_input_device_by_id(r.input_device_id); + writeln!( + f, + "Identity reply: {}\nInput device: [{}] {:?}", + &r.device_inquiry_reply, + in_dev.id(), + in_dev.name().unwrap_or_default(), + )? + } + Ok(()) + } +} + +pub enum ControllerProbeOutcome { + OutputInUseAlready(Controller), + InputInUseAlready(Controller), + CreatedController(Controller), +} + +impl Display for ControllerProbeOutcome { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControllerProbeOutcome::OutputInUseAlready(c) => { + write!( + f, + "Output port already in use by controller \"{}\"", + &c.name + )?; + } + ControllerProbeOutcome::InputInUseAlready(c) => { + write!(f, "Input port already in use by controller \"{}\"", &c.name)?; + } + ControllerProbeOutcome::CreatedController(c) => { + write!(f, "Created controller \"{}\"", &c.name)?; + } + } + Ok(()) + } +} + +pub async fn detect_controllers( + out_dev_ids: Vec, +) -> anyhow::Result> { + let reaper_is_compatible = Reaper::get() + .medium_reaper() + .low() + .pointers() + .midi_init + .is_none(); + if reaper_is_compatible { + // REAPER version < 6.47 + bail!("Detecting controllers is not possible in REAPER versions < 6.47."); + } + // Prevent multiple tasks of this kind from running at the same time + static IS_RUNNING: AtomicBool = AtomicBool::new(false); + if IS_RUNNING.swap(true, Ordering::Relaxed) { + bail!("Controller detection is already running. Please try again later."); + } + // Now, let's go + let mut probes = vec![]; + for out_dev_id in out_dev_ids { + let outcome = maybe_create_controller_for_device(out_dev_id).await; + let probe = ControllerProbe { + out_dev_id, + result: outcome, + }; + probes.push(probe) + } + IS_RUNNING.store(false, Ordering::Relaxed); + Ok(probes) +} + +async fn maybe_create_controller_for_device( + out_dev_id: MidiOutputDeviceId, +) -> Result { + let mut payload = ControllerProbePayload::default(); + // Don't create controller if there is one already which uses that device + let existing_controller = BackboneShell::get() + .controller_manager() + .borrow() + .find_controller_connected_to_midi_output(out_dev_id) + .cloned(); + if let Some(controller) = existing_controller { + // Output used already by existing controller + return Ok(SuccessfulControllerProbe::new( + ControllerProbeOutcome::OutputInUseAlready(controller), + payload, + )); + } + // Make sure that MIDI output device is enabled + tracing::debug!(msg = "Temporarily enabling MIDI output device", %out_dev_id); + let old_midi_outs = MidiOutDevsConfig::from_reaper(); + let tmp_midi_outs = old_midi_outs.with_dev_enabled(out_dev_id); + tmp_midi_outs.apply_to_reaper(); + // Temporarily enable all input devices (so they can listen to the device identity reply) + tracing::debug!(msg = "Temporarily enabling all input devices..."); + let old_midi_ins = MidiInDevsConfig::from_reaper(); + let tmp_midi_ins = MidiInDevsConfig::ALL_ENABLED; + tmp_midi_ins.apply_to_reaper(); + // Apply changes + Reaper::get().medium_reaper().low().midi_init(-1, -1); + // Send device identity request to MIDI output device + tracing::debug!(msg = "Sending device request to MIDI output device...", %out_dev_id); + let identity_reply_result = BackboneShell::get() + .request_midi_device_identity(out_dev_id, None) + .await; + // As soon as possible, reset MIDI devices to old state (we don't want to leave traces) + tracing::debug!(msg = "Resetting MIDI output and input devices to previous state..."); + old_midi_outs.apply_to_reaper(); + old_midi_ins.apply_to_reaper(); + Reaper::get().medium_reaper().low().midi_init(-1, -1); + // Process identity reply + let identity_reply = match identity_reply_result { + Ok(r) => r, + Err(e) => { + return Err(FailedControllerProbe::new(e, payload)); + } + }; + payload.identity_reply = Some(identity_reply.clone()); + let in_dev_id = identity_reply.input_device_id; + tracing::info!( + msg = "Received identity reply from MIDI device", + %out_dev_id, + %in_dev_id, + reply = %identity_reply.device_inquiry_reply + ); + // Check if input already used by existing controller + tracing::debug!(msg = "Check if input used already used by existing controller..."); + let existing_controller = BackboneShell::get() + .controller_manager() + .borrow() + .find_controller_connected_to_midi_input(in_dev_id) + .cloned(); + if let Some(controller) = existing_controller { + // Input already used by existing controller + tracing::debug!(msg = "Input already used"); + return Ok(SuccessfulControllerProbe::new( + ControllerProbeOutcome::InputInUseAlready(controller), + payload, + )); + } + // Neither output nor input used yet. Maybe this is a known controller! + let controller_preset_manager = BackboneShell::get().controller_preset_manager().borrow(); + let out_port_name = Reaper::get().midi_output_device_by_id(out_dev_id).name(); + let Some(out_port_name) = out_port_name else { + let probe = FailedControllerProbe::new( + anyhow!("MIDI output device doesn't return name / is not available"), + payload, + ); + return Err(probe); + }; + let out_port_name = out_port_name.to_string_lossy(); + tracing::info!(msg = "Input not yet used. Finding matching controller preset...", %out_port_name); + let controller_preset = controller_preset_manager + .find_controller_preset_compatible_with_device( + &identity_reply.device_inquiry_reply.message, + &out_port_name, + ); + let Some(controller_preset) = controller_preset else { + let probe = + FailedControllerProbe::new(anyhow!("no controller preset matching device"), payload); + return Err(probe); + }; + let device_name = controller_preset.common.meta_data.device_name.as_ref(); + let Some(device_name) = device_name else { + let probe = FailedControllerProbe::new( + anyhow!("controller preset doesn't have device name"), + payload, + ); + return Err(probe); + }; + // Search for suitable main preset + let controller_preset_id = &controller_preset.common.id; + tracing::debug!(msg = "Found controller preset", %controller_preset_id); + tracing::debug!(msg = "Finding main preset..."); + let main_preset_manager = BackboneShell::get().main_preset_manager().borrow(); + let conditions = MainPresetSelectionConditions { + at_least_one_instance_has_playtime_clip_matrix: { + BackboneShell::get() + .find_first_helgobox_instance_matching(|info| { + let Some(instance) = info.instance.upgrade() else { + return false; + }; + let instance_state = instance.borrow(); + instance_state.has_clip_matrix() + }) + .is_some() + }, + }; + let main_preset = main_preset_manager.find_most_suitable_main_preset_for_schemes( + &controller_preset.specific_meta_data.provided_schemes, + conditions, + ); + tracing::debug!(msg = "Main preset result available", ?main_preset); + let default_main_preset = main_preset.map(|mp| CompartmentPresetId::new(mp.common.id.clone())); + // Make sure the involved MIDI devices are enabled + tracing::debug!( + "Enabling MIDI input device {in_dev_id} and MIDI output device {out_dev_id}..." + ); + let new_midi_outs = old_midi_outs.with_dev_enabled(out_dev_id); + new_midi_outs.apply_to_reaper(); + let new_midi_ins = old_midi_ins.with_dev_enabled(in_dev_id); + new_midi_ins.apply_to_reaper(); + Reaper::get().medium_reaper().low().midi_init(-1, -1); + // Persist the changes + tracing::debug!( + msg = "Persisting MIDI in/out dev changes...", + ?new_midi_ins, + ?new_midi_outs + ); + if let Err(e) = write_midi_devs_config_to_reaper_ini(new_midi_ins, new_midi_outs) { + let probe = FailedControllerProbe::new( + e.context("Failed to write MIDI device config to reaper.ini"), + payload, + ); + return Err(probe); + }; + // Auto-create controller + tracing::debug!("Auto-creating controller..."); + let controller = Controller { + id: "".to_string(), + name: device_name.clone(), + enabled: true, + palette_color: None, + connection: Some(ControllerConnection::Midi(MidiControllerConnection { + identity_response: Some(format_as_pretty_hex( + &identity_reply.device_inquiry_reply.message, + )), + input_port: Some(MidiInputPort::new( + identity_reply.input_device_id.get() as u32 + )), + output_port: Some(MidiOutputPort::new(out_dev_id.get() as u32)), + })), + default_controller_preset: None, + default_main_preset, + }; + let save_outcome = BackboneShell::get() + .controller_manager() + .borrow_mut() + .save_controller(controller.clone()); + let save_outcome = match save_outcome { + Ok(o) => o, + Err(e) => { + return Err(FailedControllerProbe::new( + e.context("Couldn't save controller"), + payload, + )); + } + }; + BackboneShell::get() + .proto_hub() + .notify_about_global_info_event(GlobalInfoEvent::AutoAddedController( + AutoAddedControllerEvent { + controller_id: save_outcome.id, + }, + )); + let probe = SuccessfulControllerProbe::new( + ControllerProbeOutcome::CreatedController(controller), + payload, + ); + Ok(probe) +} + +fn write_midi_devs_config_to_reaper_ini( + midi_in_devs: MidiInDevsConfig, + midi_out_devs: MidiOutDevsConfig, +) -> anyhow::Result<()> { + let reaper = Reaper::get(); + let reaper_ini = reaper.medium_reaper().get_ini_file(|p| p.to_path_buf()); + // Replace existing entries + for (key, val) in midi_in_devs + .to_ini_entries() + .chain(midi_out_devs.to_ini_entries()) + { + ini_util::write_ini_entry(reaper_ini.as_str(), "REAPER", key, val.to_string())?; + } + Ok(()) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/debug_util.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/debug_util.rs new file mode 100644 index 0000000..414d7ba --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/debug_util.rs @@ -0,0 +1,7 @@ +use crate::infrastructure::ui::get_text_from_clipboard; +use reaper_high::resolve_symbols_from_text; + +pub fn resolve_symbols_from_clipboard() -> Result<(), Box> { + let text = get_text_from_clipboard().ok_or("Couldn't read from clipboard.")?; + resolve_symbols_from_text(&text) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/dynamic_toolbar.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/dynamic_toolbar.rs new file mode 100644 index 0000000..3a4aa0f --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/dynamic_toolbar.rs @@ -0,0 +1,103 @@ +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use reaper_high::Reaper; +use reaper_medium::{CommandId, MenuOrToolbarItem, PositionDescriptor, UiRefreshBehavior}; + +pub fn custom_toolbar_api_is_available() -> bool { + Reaper::get() + .medium_reaper() + .low() + .pointers() + .GetCustomMenuOrToolbarItem + .is_some() +} + +/// Dynamically adds or removes a toolbar button without persisting it, returning the command ID. +/// +/// Requires REAPER version >= 711+dev0305. +/// +/// # Errors +/// +/// Returns and error if the command doesn't exist. +/// +/// # Panics +/// +/// Panics if the REAPER version is too low. +pub fn add_or_remove_toolbar_button(command_name: &str, add: bool) -> anyhow::Result { + let action = Reaper::get().action_by_command_name(command_name); + let command_id = action.command_id()?; + let reaper = Reaper::get().medium_reaper(); + match scan_toolbar_for_command_id(command_id) { + Some(pos) => { + if !add { + reaper.delete_custom_menu_or_toolbar_item( + "Main toolbar", + pos, + UiRefreshBehavior::Refresh, + )?; + } + } + None => { + if add { + reaper.add_custom_menu_or_toolbar_item_command( + "Main toolbar", + PositionDescriptor::Append, + command_id, + 0, + action.name().unwrap_or_default(), + None, + UiRefreshBehavior::Refresh, + )?; + } + } + } + Ok(command_id) +} + +#[derive(Debug)] +pub struct ToolbarChangeDetector { + /// Map from command ID to command name. + observed_commands: NonCryptoHashMap, + present_commands: NonCryptoHashSet, +} + +impl ToolbarChangeDetector { + pub fn new(observed_commands: NonCryptoHashMap) -> Self { + Self { + observed_commands, + present_commands: Default::default(), + } + } + + pub fn detect_manually_removed_commands(&mut self) -> Vec<&str> { + self.observed_commands + .iter() + .filter(|(command_id, _)| { + if scan_toolbar_for_command_id(**command_id).is_some() { + self.present_commands.insert(**command_id); + false + } else { + self.present_commands.remove(command_id) + } + }) + .map(|(_, command_name)| command_name.as_str()) + .collect() + } +} + +fn scan_toolbar_for_command_id(command_id: CommandId) -> Option { + let reaper = Reaper::get().medium_reaper(); + let mut i = 0; + loop { + let pos = + reaper.get_custom_menu_or_toolbar_item("Main toolbar", i, |result| match result? { + MenuOrToolbarItem::Command(item) if item.command_id == command_id => Some(Some(i)), + _ => Some(None), + })?; + match pos { + None => i += 1, + Some(pos) => { + return Some(pos); + } + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/helgobox_plugin.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/helgobox_plugin.rs new file mode 100644 index 0000000..4aa54e6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/helgobox_plugin.rs @@ -0,0 +1,541 @@ +use vst::editor::Editor; +use vst::plugin; +use vst::plugin::{ + CanDo, Category, HostCallback, Info, Plugin, PluginParameterCharacter, PluginParameterInfo, + PluginParameters, +}; + +use crate::domain::{ + ControlEvent, ControlEventTimestamp, InstanceId, MidiEvent, ParameterManager, PluginParamIndex, + ProcessorContext, RawParamValue, GLOBAL_AUDIO_STATE, PLUGIN_PARAMETER_COUNT, +}; +use crate::infrastructure::plugin::instance_parameter_container::InstanceParameterContainer; +use crate::infrastructure::plugin::{init_backbone_shell, SET_STATE_PARAM_NAME}; +use base::Global; +use helgobox_allocator::*; +use reaper_high::{Reaper, ReaperGuard}; +use reaper_low::{static_plugin_context, PluginContext}; +use reaper_medium::{Hz, ReaperStr}; + +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_void}; + +use std::sync::{Arc, OnceLock}; + +use crate::infrastructure::plugin::backbone_shell::BackboneShell; + +use crate::base::notification; +use crate::infrastructure::data::InstanceData; +use crate::infrastructure::plugin::helgobox_plugin_editor::HelgoboxPluginEditor; +use crate::infrastructure::plugin::instance_shell::InstanceShell; +use crate::infrastructure::ui::instance_panel::InstancePanel; +use anyhow::{anyhow, Context}; +use std::convert::TryInto; +use std::ptr::null_mut; +use std::rc::Rc; +use std::slice; +use swell_ui::SharedView; +use vst::api::{Events, Supported}; +use vst::buffer::AudioBuffer; +use vst::host::Host; + +/// Just the old term as alias for easier class search. +type _RealearnPlugin = HelgoboxPlugin; + +/// In C++ this is the same like "hbrl" (= Helgoboss ReaLearn) +pub const HELGOBOX_UNIQUE_VST_PLUGIN_ID: i32 = 1751282284; + +/// Can be passed to `add_fx_by_original_name`. +pub const HELGOBOX_UNIQUE_VST_PLUGIN_ADD_STRING: &str = "<1751282284"; + +/// The actual VST plug-in and thus main entry point. +/// +/// Owns the instance shell, but not immediately. It's created as soon as the containing FX is +/// available. +pub struct HelgoboxPlugin { + instance_id: InstanceId, + host: HostCallback, + param_container: Arc, + // For detecting play state changes + was_playing_in_last_cycle: bool, + /// I think this sample rate can be different from the device sample rate in some cases (oversampling). + sample_rate: Hz, + block_size: u32, + /// **After `init` has been called**, this should be `true` if the plug-in was loaded by the + /// REAPER VST scan (and is not going to be used "for real"). + is_plugin_scan: bool, + // This will be set as soon as the containing FX is known (one main loop cycle after `init()`). + lazy_data: OnceLock, + instance_panel: SharedView, + /// This will be set on `init()`. + /// + /// This should be the last because the other members should be dropped first (especially lazy + /// data including instance shell)! + _reaper_guard: Option>, +} + +impl Drop for HelgoboxPlugin { + fn drop(&mut self) { + tracing::debug!("Dropping HelgoboxPlugin"); + } +} + +#[derive(Clone)] +struct LazyData { + instance_shell: Arc, + /// Only the parameters of the main unit are exposed as VST parameters. + main_unit_parameter_manager: Arc, +} + +impl Default for HelgoboxPlugin { + fn default() -> Self { + HelgoboxPlugin::new(Default::default()) + } +} + +unsafe impl Send for HelgoboxPlugin {} + +impl Plugin for HelgoboxPlugin { + fn new(host: HostCallback) -> Self { + let instance_id = InstanceId::next(); + Self { + instance_id, + host, + _reaper_guard: None, + param_container: Arc::new(InstanceParameterContainer::new()), + was_playing_in_last_cycle: false, + sample_rate: Default::default(), + block_size: 0, + is_plugin_scan: false, + lazy_data: OnceLock::new(), + instance_panel: Rc::new(InstancePanel::new(instance_id)), + } + } + + fn get_info(&self) -> Info { + Info { + name: "Helgobox - ReaLearn & Playtime".to_string(), + vendor: "Helgoboss".to_string(), + unique_id: HELGOBOX_UNIQUE_VST_PLUGIN_ID, + preset_chunks: true, + category: Category::Synth, + parameters: PLUGIN_PARAMETER_COUNT as i32, + f64_precision: true, + inputs: 2, + outputs: 0, + ..Default::default() + } + } + + fn get_parameter_info(&self, index: i32) -> Option { + let i = PluginParamIndex::try_from(index as u32).ok()?; + let params = self.lazy_data.get()?.main_unit_parameter_manager.params(); + let param = params.at(i); + let value_count = param.setting().value_count?; + let mut info = PluginParameterInfo::default(); + info.character = PluginParameterCharacter::Discrete { + min: 0, + max: (value_count.get() - 1) as i32, + steps: None, + }; + Some(info) + } + + fn init(&mut self) { + // Trick to find out whether we are only being scanned. + self.is_plugin_scan = unsafe { (*self.host.raw_effect()).reserved1 == 0 }; + if self.is_plugin_scan { + tracing::debug!("Helgobox is being scanned by REAPER"); + return; + } + tracing::debug!("Helgobox is being opened by REAPER"); + self._reaper_guard = Some(self.ensure_reaper_setup()); + // At this point, REAPER cannot reliably give us the containing FX. As a + // consequence we also don't have a instance shell yet, because creating an incomplete + // instance shell pushes the problem of not knowing the containing FX into the application + // logic, which we for sure don't want. In the next main loop cycle, it should be + // possible to identify the containing FX. + let host = self.host; + Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(move || { + let plugin = unsafe { (*host.raw_effect()).get_plugin() }; + plugin.vendor_specific(INIT_INSTANCE_SHELL, 0, null_mut(), 0.0); + }) + .unwrap(); + } + + fn get_editor(&mut self) -> Option> { + // Unfortunately, vst-rs calls `get_editor` before the plug-in is initialized by the + // host, e.g. in order to check if it should the hasEditor flag or not. That means + // we don't know yet if this is a plug-in scan or not. We have to create the editor. + let boxed: Box = + Box::new(HelgoboxPluginEditor::new(self.instance_panel.clone())); + Some(boxed) + } + + fn can_do(&self, can_do: CanDo) -> Supported { + use CanDo::*; + use Supported::*; + #[allow(overflowing_literals)] + match can_do { + SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | ReceiveSysExEvent => { + Supported::Yes + } + // If we don't do this, REAPER for Linux won't give us a SWELL plug-in window, which + // leads to a horrible crash when doing CreateDialogParam. In our UI we use SWELL + // to put controls into the plug-in window. SWELL assumes that the parent window for + // controls is also a SWELL window. + Other(s) => match s.as_str() { + "hasCockosViewAsConfig" => Custom(0xbeef_0000), + "hasCockosExtensions" => Custom(0xbeef_0000), + // This is necessary for REAPER 6.48 - 6.51 on macOS to not let the background + // turn black. These REAPER versions introduced a change putting third-party + // VSTs into a container window. The following line prevents that. For + // REAPER v6.52+ it's not necessary anymore because it also reacts to + // "hasCockosViewAsConfig". + "hasCockosNoScrollUI" => Custom(0xbeef_0000), + _ => Maybe, + }, + _ => Maybe, + } + } + + fn get_parameter_object(&mut self) -> Arc { + self.param_container.clone() + } + + fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { + // tracing_debug!("VST vendor specific (index = {})", index); + self.handle_vendor_specific(index, value, ptr, opt) + } + + fn process_events(&mut self, events: &Events) { + assert_no_alloc(|| { + let is_transport_start = !self.was_playing_in_last_cycle && self.is_now_playing(); + let block_count = GLOBAL_AUDIO_STATE.load_block_count(); + let sample_count = block_count * self.block_size as u64; + let device_sample_rate = GLOBAL_AUDIO_STATE.load_sample_rate(); + for e in events.events() { + let our_event = match MidiEvent::from_vst(e) { + Err(_) => { + // Just ignore if not a valid MIDI message. Invalid MIDI message was + // observed in the wild: https://github.com/helgoboss/helgobox/issues/82. + continue; + } + Ok(e) => e, + }; + let timestamp = ControlEventTimestamp::from_rt( + sample_count, + device_sample_rate, + our_event.offset().to_seconds(self.sample_rate), + ); + let our_event = ControlEvent::new(our_event, timestamp); + if let Some(lazy_data) = self.lazy_data.get() { + lazy_data.instance_shell.process_incoming_midi_from_plugin( + our_event, + is_transport_start, + self.host, + ); + } + } + }); + } + + fn process_f64(&mut self, buffer: &mut AudioBuffer) { + assert_no_alloc(|| { + // Get current time information so we can detect changes in play state reliably + // (TimeInfoFlags::TRANSPORT_CHANGED doesn't work the way we want it). + self.was_playing_in_last_cycle = self.is_now_playing(); + if let Some(lazy_data) = self.lazy_data.get() { + #[cfg(feature = "playtime")] + lazy_data.instance_shell.run_playtime_from_plugin( + buffer, + crate::domain::AudioBlockProps::from_vst(buffer, self.sample_rate), + ); + lazy_data.instance_shell.run_from_plugin(self.host); + } + }); + let _ = buffer; + } + + fn set_sample_rate(&mut self, rate: f32) { + tracing::debug!("VST set sample rate"); + self.sample_rate = Hz::new_panic(rate as _); + if let Some(lazy_data) = self.lazy_data.get() { + lazy_data.instance_shell.set_sample_rate(rate); + } + } + + fn suspend(&mut self) { + tracing::debug!("VST suspend"); + } + + fn resume(&mut self) { + tracing::debug!("VST resume"); + } + + fn set_block_size(&mut self, size: i64) { + tracing::debug!("VST set block size"); + self.block_size = size as u32; + } + + fn start_process(&mut self) { + tracing::debug!("VST start process"); + } + + fn stop_process(&mut self) { + tracing::debug!("VST stop process"); + } +} + +impl HelgoboxPlugin { + /// Should be called in real-time thread only. + fn is_now_playing(&self) -> bool { + use vst::api::TimeInfoFlags; + let time_info = self + .host + .get_time_info(TimeInfoFlags::TRANSPORT_PLAYING.bits()); + match time_info { + None => false, + Some(ti) => { + let flags = TimeInfoFlags::from_bits_truncate(ti.flags); + flags.intersects(TimeInfoFlags::TRANSPORT_PLAYING) + } + } + } + + fn ensure_reaper_setup(&mut self) -> Arc { + Reaper::guarded( + // We let reaper-rs wake up and go to sleep in Backbone init/drop in order to let it be awake even + // if not VST plug-in instance is around + false, + || { + let context = + PluginContext::from_vst_plugin(&self.host, static_plugin_context()).unwrap(); + // If the Helgobox Extension is installed, it already called our eager-loading + // extension entry point. In this case, the following call will not have any + // effect. And that's exactly what we want, because the App already has been + // initialized then! + init_backbone_shell(context); + }, + || { + let _ = BackboneShell::get().wake_up(); + || { + if BackboneShell::is_loaded() { + let _ = BackboneShell::get().go_to_sleep(); + } + } + }, + ) + } + + fn get_lazy_data(&self) -> Result<&LazyData, &'static str> { + self.lazy_data.get().ok_or("lazy data not available yet") + } + + fn get_parameter_display(&self, index: u32, raw_value: f32) -> Result { + let i = PluginParamIndex::try_from(index)?; + let display = self + .get_lazy_data()? + .main_unit_parameter_manager + .params() + .at(i) + .setting() + .with_raw_value(raw_value) + .to_string(); + Ok(display) + } + + /// Returns `None` if REAPER string is empty (REAPER's way of checking whether + /// we support this). + fn string_to_parameter( + &self, + index: u32, + reaper_str: &ReaperStr, + ) -> Result, &'static str> { + let text_input = reaper_str.to_str(); + if text_input.is_empty() { + // REAPER checks if we support this. + return Ok(None); + } + let i = PluginParamIndex::try_from(index)?; + let params = self.get_lazy_data()?.main_unit_parameter_manager.params(); + let param = params.at(i); + let raw_value = param.setting().parse_to_raw_value(text_input)?; + Ok(Some(raw_value)) + } + + fn get_named_config_param( + &self, + param_name: &str, + buffer: &mut [c_char], + ) -> Result<(), &'static str> { + if buffer.is_empty() { + return Err("empty buffer"); + } + match param_name { + crate::domain::HELGOBOX_INSTANCE_ID_KEY => { + let instance_id_c_string = + CString::new(self.instance_id.to_string()).expect("should be number"); + let mut bytes = instance_id_c_string + .as_bytes_with_nul() + .iter() + .map(|v| *v as c_char); + buffer[0..bytes.len()].fill_with(|| bytes.next().unwrap()); + Ok(()) + } + _ => Err("unhandled config param"), + } + } + + fn set_named_config_param( + &self, + param_name: &str, + buffer: *const c_char, + ) -> anyhow::Result<()> { + match param_name { + SET_STATE_PARAM_NAME => { + let c_str = unsafe { CStr::from_ptr(buffer) }; + let data = InstanceData::parse(c_str.to_bytes())?; + let lazy_data = self.lazy_data.get().context("lazy data not yet set")?; + lazy_data.instance_shell.clone().apply_data(data)?; + Ok(()) + } + _ => Err(anyhow!("unhandled config param")), + } + } + + fn init_instance_shell(&self) -> anyhow::Result<()> { + let processor_context = ProcessorContext::from_host(self.host)?; + let instance_shell = InstanceShell::new( + self.instance_id, + processor_context, + self.instance_panel.clone(), + ); + let instance_shell = Arc::new(instance_shell); + BackboneShell::get().register_instance(&instance_shell); + self.instance_panel + .notify_shell_available(instance_shell.clone()); + instance_shell.set_sample_rate(self.sample_rate.get() as _); + let main_unit_parameter_manager = instance_shell + .main_unit_shell() + .model() + .borrow() + .unit() + .borrow() + .parameter_manager() + .clone(); + let lazy_data = LazyData { + instance_shell, + main_unit_parameter_manager, + }; + self.lazy_data + .set(lazy_data.clone()) + .map_err(|_| anyhow!("lazy data already initialized"))?; + self.param_container.notify_instance_shell_available( + &lazy_data.instance_shell, + lazy_data.main_unit_parameter_manager, + ); + Ok(()) + } + + fn handle_vendor_specific( + &mut self, + index: i32, + value: isize, + ptr: *mut c_void, + opt: f32, + ) -> isize { + use plugin::OpCode::*; + fn interpret_as_param_name(value: isize) -> Result<&'static str, &'static str> { + let param_name = unsafe { CStr::from_ptr(value as *const c_char) }; + param_name.to_str().map_err(|_| "invalid parameter name") + } + match index { + INIT_INSTANCE_SHELL => { + if let Err(e) = self.init_instance_shell() { + notification::alert(e.to_string()); + } + return 0; + } + _ => {} + } + let cockos_opcode: plugin::OpCode = match index.try_into() { + Ok(c) => c, + Err(_) => return 0, + }; + match cockos_opcode { + // Cockos: named_parameter_name (http://reaper.fm/sdk/vst/vst_ext.php) + GetData if value != 0 => { + let param_name = match interpret_as_param_name(value) { + Ok(n) => n, + Err(_) => return 0, + }; + let buffer = + unsafe { std::slice::from_raw_parts_mut(ptr as *mut c_char, opt as _) }; + if self.get_named_config_param(param_name, buffer).is_ok() { + 0xf00d + } else { + 0 + } + } + // Cockos: named_parameter_name (http://reaper.fm/sdk/vst/vst_ext.php) + SetData if value != 0 => { + let param_name = match interpret_as_param_name(value) { + Ok(n) => n, + Err(_) => return 0, + }; + if self + .set_named_config_param(param_name, ptr as *const c_char) + .is_ok() + { + 0xf00d + } else { + 0 + } + } + // Cockos: Format parameter value without setting it (http://reaper.fm/sdk/vst/vst_ext.php) + GetParameterDisplay if !ptr.is_null() && value >= 0 => { + let Ok(display) = self.get_parameter_display(value as u32, opt) else { + return 0; + }; + if write_to_c_str(ptr, display).is_err() { + return 0; + } + 0xbeef + } + // Cockos: Parse parameter value without setting it (http://reaper.fm/sdk/vst/vst_ext.php) + StringToParameter if !ptr.is_null() && value >= 0 => { + let reaper_str = unsafe { ReaperStr::from_ptr(ptr as *const c_char) }; + let Ok(raw_value) = self.string_to_parameter(value as u32, reaper_str) else { + return 0; + }; + let Some(raw_value) = raw_value else { + // REAPER checks if we support this. + return 0xbeef; + }; + if write_to_c_str(ptr, raw_value.to_string()).is_err() { + return 0; + } + 0xbeef + } + _ => 0, + } + } +} + +fn write_to_c_str(dest: *mut c_void, src: String) -> Result<(), &'static str> { + let c_string = match CString::new(src) { + Ok(s) => s, + Err(_) => return Err("Rust string contained nul byte"), + }; + let bytes = c_string.as_bytes_with_nul(); + let dest_slice = unsafe { slice::from_raw_parts_mut(dest as *mut u8, 256) }; + dest_slice[..bytes.len()].copy_from_slice(bytes); + Ok(()) +} + +/// This is our own code. We call ourselves in order to safe us an Arc around +/// the instance shell. Why use an Arc (and therefore make each internal access to the instance shell have to +/// dereference a pointer) if we already have a pointer from outside. +const INIT_INSTANCE_SHELL: i32 = -235978234; diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/helgobox_plugin_editor.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/helgobox_plugin_editor.rs new file mode 100644 index 0000000..f2881f3 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/helgobox_plugin_editor.rs @@ -0,0 +1,44 @@ +use reaper_low::raw::HWND; + +use std::os::raw::c_void; + +use crate::infrastructure::ui::instance_panel::InstancePanel; +use swell_ui::{SharedView, View, Window}; +use vst::editor::Editor; + +pub struct HelgoboxPluginEditor { + instance_panel: SharedView, +} + +impl HelgoboxPluginEditor { + pub fn new(unit_panel: SharedView) -> Self { + Self { + instance_panel: unit_panel, + } + } +} + +impl Editor for HelgoboxPluginEditor { + fn size(&self) -> (i32, i32) { + self.instance_panel.dimensions().to_vst() + } + + fn position(&self) -> (i32, i32) { + (0, 0) + } + + fn close(&mut self) { + self.instance_panel.close(); + } + + fn open(&mut self, parent: *mut c_void) -> bool { + self.instance_panel + .clone() + .open_with_resize(Window::new(parent as HWND).expect("no parent window")); + true + } + + fn is_open(&mut self) -> bool { + self.instance_panel.is_open() + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/hidden_helper_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/hidden_helper_panel.rs new file mode 100644 index 0000000..b27e78f --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/hidden_helper_panel.rs @@ -0,0 +1,62 @@ +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::ui::bindings::root; +use std::time::Duration; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Debug, Default)] +pub struct HiddenHelperPanel { + view: ViewContext, +} + +const PLAYTIME_ENGINE_STATS_TIMER_ID: usize = 322; +const HELGOBOX_TOOLBAR_CHECK_TIMER_ID: usize = 323; + +impl HiddenHelperPanel { + pub fn new() -> Self { + Self::default() + } +} + +impl View for HiddenHelperPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_HIDDEN_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn show_window_on_init(&self) -> bool { + false + } + + fn opened(self: SharedView, window: Window) -> bool { + window.set_timer(PLAYTIME_ENGINE_STATS_TIMER_ID, Duration::from_millis(200)); + window.set_timer(HELGOBOX_TOOLBAR_CHECK_TIMER_ID, Duration::from_millis(3000)); + false + } + + fn on_destroy(self: SharedView, _window: Window) { + // Switch lights off. Essential to call this here and not later on drop! + BackboneShell::get().shutdown(); + } + + fn timer(&self, id: usize) -> bool { + match id { + PLAYTIME_ENGINE_STATS_TIMER_ID => { + #[cfg(feature = "playtime")] + { + BackboneShell::get() + .proto_hub() + .notify_engine_stats_changed(); + } + true + } + HELGOBOX_TOOLBAR_CHECK_TIMER_ID => { + BackboneShell::get().disable_manually_removed_dynamic_toolbar_buttons(); + true + } + _ => false, + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/ini_util.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/ini_util.rs new file mode 100644 index 0000000..bd94496 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/ini_util.rs @@ -0,0 +1,23 @@ +use anyhow::bail; +use reaper_low::Swell; +use reaper_medium::ReaperStringArg; + +pub fn write_ini_entry<'a>( + file: impl Into>, + section: impl Into>, + key: impl Into>, + value: impl Into>, +) -> anyhow::Result<()> { + let success = unsafe { + Swell::get().WritePrivateProfileString( + section.into().as_ptr(), + key.into().as_ptr(), + value.into().as_ptr(), + file.into().as_ptr(), + ) + }; + if success < 1 { + bail!("Couldn't write INI entry"); + } + Ok(()) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/instance_parameter_container.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/instance_parameter_container.rs new file mode 100644 index 0000000..3cbb449 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/instance_parameter_container.rs @@ -0,0 +1,225 @@ +use crate::base::notification; +use std::sync; + +use crate::domain::{ParameterManager, PluginParamIndex}; +use crate::infrastructure::data::InstanceData; +use crate::infrastructure::plugin::instance_shell::InstanceShell; +use reaper_high::Reaper; +use std::sync::{Arc, OnceLock, RwLock}; +use vst::plugin::PluginParameters; + +/// Container that owns all plug-in parameters (descriptions and values) and is responsible +/// for handling parameter changes as well as saving and loading plug-in state. +/// +/// vst-rs requires us to make this separate from the actual plug-in struct. +/// +/// It's good to have this separate from the [InstanceShell] because data chunk and parameters might +/// be set or queried by the host *before* the containing FX - and thus the unit shell - is +/// available. +#[derive(Debug)] +pub struct InstanceParameterContainer { + /// Will be set as soon as the containing FX is known. + lazy_data: OnceLock, + /// We may have to cache some data that the host wants us to load because we are not ready + /// for loading data as long as the unit shell is not available. + pending_data_to_be_loaded: RwLock>>, +} + +#[derive(Debug)] +struct LazyData { + instance_shell: sync::Weak, + /// Only the parameters of the main unit are exposed as VST parameters. + main_unit_parameter_manager: Arc, +} + +impl InstanceParameterContainer { + /// Creates the parameter container. + pub fn new() -> Self { + Self { + lazy_data: OnceLock::new(), + pending_data_to_be_loaded: Default::default(), + } + } + + /// Sets the instance shell. + /// + /// Also checks if there's pending state to be loaded into the instance shell and if yes, loads it. + pub fn notify_instance_shell_available( + &self, + instance_shell: &Arc, + main_unit_parameter_manager: Arc, + ) { + if let Some(data) = self.pending_data_to_be_loaded.write().unwrap().take() { + tracing::debug!("Loading pending data into unit shell"); + load_data_or_warn(instance_shell.clone(), &data); + } + let lazy_data = LazyData { + instance_shell: Arc::downgrade(instance_shell), + main_unit_parameter_manager, + }; + self.lazy_data.set(lazy_data).unwrap(); + } + + fn get_parameter_name_internal(&self, index: i32) -> Result { + let index = PluginParamIndex::try_from(index as u32)?; + let lazy_data = self.get_lazy_data()?; + let name = lazy_data + .main_unit_parameter_manager + .params() + .build_qualified_parameter_name(index); + Ok(name) + } + + fn get_parameter_text_internal(&self, index: i32) -> Result { + let index = PluginParamIndex::try_from(index as u32)?; + let lazy_data = self.get_lazy_data()?; + let text = lazy_data + .main_unit_parameter_manager + .params() + .at(index) + .to_string(); + Ok(text) + } + + fn set_parameter_internal(&self, index: i32, value: f32) -> Result<(), &'static str> { + let index = PluginParamIndex::try_from(index as u32)?; + let lazy_data = self.get_lazy_data()?; + lazy_data + .main_unit_parameter_manager + .set_single_parameter(index, value); + Ok(()) + } + + fn string_to_parameter_internal(&self, index: i32, text: String) -> Result { + let index = PluginParamIndex::try_from(index as u32)?; + let lazy_data = self.get_lazy_data()?; + let parse_result = lazy_data + .main_unit_parameter_manager + .params() + .at(index) + .setting() + .parse_to_raw_value(&text); + let res = if let Ok(raw_value) = parse_result { + lazy_data + .main_unit_parameter_manager + .set_single_parameter(index, raw_value); + true + } else { + false + }; + Ok(res) + } + + fn get_lazy_data(&self) -> Result<&LazyData, &'static str> { + self.lazy_data.get().ok_or("lazy data not available yet") + } + + fn get_parameter_internal(&self, index: i32) -> Result { + let index = PluginParamIndex::try_from(index as u32)?; + let lazy_data = self.get_lazy_data()?; + // It's super important that we don't get the parameter from the session because if + // the parameter is set shortly before via `set_parameter()`, it can happen that we + // don't get this latest value from the session - it will arrive there a bit later + // because we use async messaging to let the session know about the new parameter + // value. Getting an old value is terrible for clients which use the current value + // for calculating a new value, e.g. ReaLearn itself when used with relative encoders. + // Turning the encoder will result in increments not being applied reliably. + let value = lazy_data + .main_unit_parameter_manager + .params() + .at(index) + .raw_value(); + Ok(value) + } +} + +/// This will be returned if ReaLearn cannot return reasonable bank data yet. +const NOT_READY_YET: &str = "not-ready-yet"; + +impl PluginParameters for InstanceParameterContainer { + fn get_bank_data(&self) -> Vec { + let Some(lazy_data) = self.lazy_data.get() else { + return match self.pending_data_to_be_loaded.read().unwrap().as_ref() { + None => NOT_READY_YET.to_string().into_bytes(), + Some(d) => d.clone(), + }; + }; + lazy_data + .instance_shell + .upgrade() + .expect("instance shell gone") + .save() + // If acquiring the data was not possible (reentrancy issue), we panic! Reasons: + // 1. We don't want it to silently save *nothing*. REAPER doesn't complain if we return empty vec (0). + // 2. And second, we need to know about such situations (error report), to be able to + // fix the root reentrancy issue. + .expect("could not acquire Helgobox plug-in data") + } + + fn load_bank_data(&self, data: &[u8]) { + // TODO-medium-performance We could optimize by getting the config var only once, saving it in a global + // struct and then just dereferencing the var whenever we need it. Justin said that the result of + // get_config_var never changes throughout the lifetime of REAPER. + if let Ok(pref) = Reaper::get().get_preference_ref::("__fx_loadstate_ctx") { + if *pref == b'U' { + // REAPER is loading an updated undo state. We don't want to participate in REAPER's undo because + // it often leads to unpleasant surprises. ReaLearn is its own world. And Playtime even has its + // own undo system. + return; + } + } + if data == NOT_READY_YET.as_bytes() { + if let Some(lazy_data) = self.lazy_data.get() { + // Looks like someone activated the "Reset to factory default" preset. + lazy_data + .instance_shell + .upgrade() + .expect("instance shell gone") + .apply_data(InstanceData::default()) + .expect("couldn't load factory default"); + } + return; + } + let Some(lazy_data) = self.lazy_data.get() else { + // Unit shell is not available yet. Memorize data so we can apply it + // as soon as the shell is available. + self.pending_data_to_be_loaded + .write() + .unwrap() + .replace(data.to_vec()); + return; + }; + let instance_shell = lazy_data + .instance_shell + .upgrade() + .expect("instance shell gone"); + load_data_or_warn(instance_shell, data); + } + + fn get_parameter_name(&self, index: i32) -> String { + self.get_parameter_name_internal(index).unwrap_or_default() + } + + fn get_parameter(&self, index: i32) -> f32 { + self.get_parameter_internal(index).unwrap_or_default() + } + + fn set_parameter(&self, index: i32, value: f32) { + let _ = self.set_parameter_internal(index, value); + } + + fn get_parameter_text(&self, index: i32) -> String { + self.get_parameter_text_internal(index).unwrap_or_default() + } + + fn string_to_parameter(&self, index: i32, text: String) -> bool { + self.string_to_parameter_internal(index, text) + .unwrap_or_default() + } +} + +pub const SET_STATE_PARAM_NAME: &str = "set-state"; + +fn load_data_or_warn(instance_shell: Arc, data: &[u8]) { + notification::warn_user_on_anyhow_error(instance_shell.load(data)); +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/instance_shell.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/instance_shell.rs new file mode 100644 index 0000000..a38503f --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/instance_shell.rs @@ -0,0 +1,735 @@ +use crate::application::{ + AutoUnitData, InstanceCommand, InstanceModel, SharedInstanceModel, SharedUnitModel, +}; +use crate::domain::{ + ControlEvent, IncomingMidiMessage, Instance, InstanceHandler, InstanceId, MidiEvent, + ProcessorContext, SharedRealTimeInstance, UnitId, +}; +use crate::infrastructure::data::{InstanceData, UnitData}; +use crate::infrastructure::plugin::unit_shell::UnitShell; +use crate::infrastructure::plugin::{update_auto_units_async, BackboneShell}; +use crate::infrastructure::ui::instance_panel::InstancePanel; +use crate::infrastructure::ui::UnitPanel; +use anyhow::Context; +use base::hash_util::NonCryptoHashMap; +use base::{blocking_read_lock, blocking_write_lock, non_blocking_try_read_lock}; +use fragile::Fragile; +use helgobox_api::persistence::{instance_features, InstanceSettings}; +use playtime_api::persistence::FlexibleMatrix; +use reaper_high::Project; +use std::cell::RefCell; +use std::iter::once; +use std::rc::Rc; +use std::sync; +use std::sync::{Arc, Mutex, RwLock}; +use swell_ui::SharedView; +use vst::plugin::HostCallback; + +pub type SharedInstanceShell = Arc; +pub type WeakInstanceShell = sync::Weak; + +/// Represents a Helgobox instance in the infrastructure layer. +/// +/// Each Helgobox FX instance owns exactly one Helgobox instance. The instance shell owns all +/// its associated unit shells. +/// +/// This was previously simply a part of the `RealearnPlugin` struct. +#[derive(Debug)] +pub struct InstanceShell { + /// A non-persistent counting ID. Must be unique. + instance_id: InstanceId, + model: Fragile, + panel: Fragile>, + // TODO-low Not too cool that we need to make this fragile. Related to reaper-high cells. + processor_context: Fragile, + rt_instance: SharedRealTimeInstance, + main_unit_shell: UnitShell, + settings: Fragile>, + /// Additional unit shells. + /// + /// This needs to be protected because we might add/remove/set instances in the main + /// thread at a later point in time. There should be no contention whatsoever unless + /// we are in the process of modifying the set of instances, which happens in the main thread. + /// + /// That's why each access from the audio thread should fail fast if there's contention. It + /// should never wait for a lock. That way, the audio thread will not be blocked for sure. + additional_unit_shells: RwLock>, +} + +impl Drop for InstanceShell { + fn drop(&mut self) { + tracing::debug!("Dropping InstanceShell..."); + if BackboneShell::is_loaded() { + BackboneShell::get().unregister_instance(self.instance_id); + } + } +} + +#[derive(Debug)] +struct CustomInstanceHandler { + #[allow(unused)] + project: Option, +} + +impl InstanceHandler for CustomInstanceHandler { + #[cfg(feature = "playtime")] + fn clip_matrix_changed( + &self, + instance_id: InstanceId, + matrix: &playtime_clip_engine::base::Matrix, + events: &[playtime_clip_engine::base::ClipMatrixEvent], + is_poll: bool, + ) { + // TODO-medium If we would make the instance ID generic, we could save the string conversion + BackboneShell::get().proto_hub().notify_clip_matrix_changed( + instance_id, + matrix, + events, + is_poll, + ); + } + + #[cfg(feature = "playtime")] + fn process_control_surface_change_event_for_clip_engine( + &self, + instance_id: InstanceId, + matrix: &playtime_clip_engine::base::Matrix, + events: &[reaper_high::ChangeEvent], + ) { + BackboneShell::get() + .proto_hub() + .send_occasional_matrix_updates_caused_by_reaper(instance_id, matrix, events); + } +} + +impl InstanceShell { + pub fn rt_instance(&self) -> SharedRealTimeInstance { + self.rt_instance.clone() + } + + /// Creates an instance shell with exactly one unit shell. + pub fn new( + instance_id: InstanceId, + processor_context: ProcessorContext, + panel: SharedView, + ) -> Self { + let main_unit_id = UnitId::next(); + let instance_handler = CustomInstanceHandler { + project: processor_context.project(), + }; + let (instance, rt_instance) = Instance::new( + instance_id, + main_unit_id, + processor_context.clone(), + BackboneShell::get().instance_event_sender().clone(), + Box::new(instance_handler), + #[cfg(feature = "playtime")] + BackboneShell::get().clip_matrix_event_sender().clone(), + BackboneShell::get().normal_audio_hook_task_sender().clone(), + ); + let rt_instance = Arc::new(Mutex::new(rt_instance)); + let instance_id = instance.id(); + let instance = Rc::new(RefCell::new(instance)); + let instance_model = InstanceModel::new(instance, Box::new(Rc::downgrade(&panel))); + let instance_model = Rc::new(RefCell::new(instance_model)); + let main_unit_shell = UnitShell::new( + main_unit_id, + Some("Main".into()), + instance_id, + processor_context.clone(), + instance_model.clone(), + Arc::downgrade(&rt_instance), + SharedView::downgrade(&panel), + true, + None, + ); + Self { + model: Fragile::new(instance_model), + rt_instance: rt_instance.clone(), + main_unit_shell, + settings: Default::default(), + additional_unit_shells: Default::default(), + panel: Fragile::new(panel), + processor_context: Fragile::new(processor_context), + instance_id, + } + } + + pub fn processor_context(&self) -> ProcessorContext { + self.processor_context.get().clone() + } + + pub fn settings(&self) -> InstanceSettings { + self.settings.get().borrow().clone() + } + + pub fn toggle_global_control(&self) { + self.change_settings(|settings| settings.control.global_control_enabled ^= true); + } + + pub fn change_settings(&self, f: impl FnOnce(&mut InstanceSettings)) { + f(&mut self.settings.get().borrow_mut()); + self.handle_changed_settings(); + } + + fn handle_changed_settings(&self) { + update_auto_units_async(); + BackboneShell::get() + .proto_hub() + .notify_instance_settings_changed(self); + } + + pub fn instance_id(&self) -> InstanceId { + self.instance_id + } + + pub fn model(&self) -> &SharedInstanceModel { + self.model.get() + } + + pub fn main_unit_shell(&self) -> &UnitShell { + &self.main_unit_shell + } + + pub fn all_unit_models(&self) -> impl Iterator { + let additional_unit_models = self.additional_unit_models(); + once(self.main_unit_shell().model().clone()).chain(additional_unit_models) + } + + pub fn find_unit_model_by_id(&self, unit_id: Option) -> Option { + match unit_id { + None => Some(self.main_unit_shell.model().clone()), + Some(unit_id) => self + .additional_unit_models() + .into_iter() + .find(|m| m.borrow().unit_id() == unit_id), + } + } + + pub fn additional_unit_models(&self) -> Vec { + blocking_read_lock(&self.additional_unit_shells, "additional_unit_models") + .iter() + .map(|s| s.model().clone()) + .collect() + } + + pub fn additional_unit_count(&self) -> usize { + blocking_read_lock(&self.additional_unit_shells, "additional_unit_panel_count").len() + } + + pub fn find_unit_index_and_model_by_id( + &self, + id: Option, + ) -> Option<(Option, SharedUnitModel)> { + self.find_unit_prop_by_id_simple(id, |i, unit_shell| (i, unit_shell.model().clone())) + } + + pub fn panel(&self) -> &SharedView { + self.panel.get() + } + + pub fn find_unit_panel_by_id(&self, id: Option) -> Option> { + self.find_unit_prop_by_id_simple(id, |_, unit_shell| unit_shell.panel().clone()) + } + + pub fn find_unit_prop_by_id_simple( + &self, + id: Option, + f: impl FnOnce(Option, &UnitShell) -> R, + ) -> Option { + match id { + None => Some(f(None, &self.main_unit_shell)), + Some(i) => self.find_unit_prop_by_id(i, f), + } + } + + fn find_unit_prop_by_id( + &self, + id: UnitId, + f: impl FnOnce(Option, &UnitShell) -> R, + ) -> Option { + if self.main_unit_shell.id() == id { + return Some(f(None, &self.main_unit_shell)); + } + blocking_read_lock( + &self.additional_unit_shells, + "find_additional_unit_prop_by_index", + ) + .iter() + .enumerate() + .find(|(_, u)| u.id() == id) + .map(|(i, u)| f(Some(i), u)) + } + + pub fn add_unit(&self) -> UnitId { + let unit_shell = self.create_additional_unit_shell(None); + let id = unit_shell.id(); + blocking_write_lock(&self.additional_unit_shells, "add_unit").push(unit_shell); + self.notify_units_changed(); + id + } + + fn create_additional_unit_shell(&self, auto_unit: Option) -> UnitShell { + let initial_name = auto_unit.as_ref().and_then(|au| { + BackboneShell::get() + .controller_manager() + .borrow() + .find_controller_by_id(&au.controller_id)? + .name + .clone() + .into() + }); + UnitShell::new( + UnitId::next(), + initial_name, + self.instance_id, + self.processor_context.get().clone(), + self.model.get().clone(), + Arc::downgrade(&self.rt_instance), + SharedView::downgrade(self.panel.get()), + false, + auto_unit, + ) + } + + fn notify_units_changed(&self) { + self.panel.get().notify_units_changed(); + self.main_unit_shell.panel().notify_units_changed(); + BackboneShell::get() + .proto_hub() + .notify_instance_units_changed(self); + #[cfg(feature = "playtime")] + if let Some(matrix) = self.model.get().borrow().instance().borrow().clip_matrix() { + matrix.notify_control_units_changed(); + } + for unit_shell in blocking_read_lock(&self.additional_unit_shells, "add_unit").iter() { + unit_shell.panel().notify_units_changed(); + } + } + + pub fn unit_exists(&self, id: UnitId) -> bool { + blocking_read_lock(&self.additional_unit_shells, "unit_exists") + .iter() + .any(|u| u.id() == id) + } + + pub fn remove_unit(&self, id: UnitId) -> anyhow::Result<()> { + { + let mut additional_unit_shells = + blocking_write_lock(&self.additional_unit_shells, "remove_unit"); + additional_unit_shells.retain(|u| u.id() != id); + } + self.notify_units_changed(); + Ok(()) + } + + /// The idea is that this instance removes each auto unit candidate from the given map if it + /// feels like it shouldn't be loaded. Which can be the case if it wants to use its input/output + /// in another way (global auto unit override). + pub fn judge_auto_unit_candidates( + &self, + auto_unit_candidates: &mut NonCryptoHashMap, + ) { + // TODO-medium In future, we can build local auto units here, that is, check if the + // controller should be used in a different way in this instance and create an appropriate + // auto unit. We should do that only if the instance belongs to the current project. + auto_unit_candidates.retain(|_, auto_unit_candidate| { + if !self + .main_unit_shell + .model() + .borrow() + .is_fine_with_global_auto_unit(auto_unit_candidate) + { + return false; + } + let additional_unit_shells = blocking_read_lock( + &self.additional_unit_shells, + "participate_in_global_auto_unit_calculation", + ); + if !additional_unit_shells.iter().all(|s| { + s.model() + .borrow() + .is_fine_with_global_auto_unit(auto_unit_candidate) + }) { + return false; + } + true + }); + } + + /// Here the instance gets the chance to load some of the given auto units. If it decides to + /// load it, it must remove it from the map in order to prevent the unit to be loaded + /// in other instances as well (would cause input/output conflicts). + pub fn apply_auto_units( + &self, + desired_auto_units: &mut NonCryptoHashMap, + ) -> anyhow::Result<()> { + if !self.settings.get().borrow().control.global_control_enabled { + // Global control is not enabled. Remove auto units if some exist. + blocking_write_lock(&self.additional_unit_shells, "apply_auto_units") + .retain(|u| u.model().borrow().auto_unit().is_none()); + self.notify_units_changed(); + return Ok(()); + } + { + // Include only auto units that are suitable for this instance + let mut additional_unit_shells = + blocking_write_lock(&self.additional_unit_shells, "apply_auto_units"); + // At first we update or remove any existing auto units + additional_unit_shells.retain_mut(|unit_shell| { + let mut unit_model = unit_shell.model().borrow_mut(); + if let Some(existing_auto_unit) = unit_model.auto_unit() { + // This is an existing auto unit + if let Some(matching_auto_unit) = self.remove_auto_unit_if_requirements_met( + desired_auto_units, + &existing_auto_unit.controller_id, + ) { + // The existing auto unit must be updated + unit_model.update_auto_unit(matching_auto_unit); + true + } else { + // The existing auto unit is obsolete + false + } + } else { + // This is a manual unit + true + } + }); + // All required auto units that are still left must be added + desired_auto_units.retain(|_, auto_unit| { + if !self.has_all_features_required_by_main_preset(&auto_unit.main_preset_id) { + // Our instance doesn't satisfy the requirements. Don't consume auto unit. + return true; + } + tracing::debug!(msg = "Creating auto-unit shell", ?auto_unit); + let unit_shell = self.create_additional_unit_shell(Some(auto_unit.clone())); + additional_unit_shells.push(unit_shell); + false + }); + } + self.notify_units_changed(); + Ok(()) + } + + /// Returns the state of the current unit in serialized form. + /// + /// This must not lock any data that's accessed from real-time threads. + /// + /// Must be called from the main thread. + /// + /// # Errors + /// + /// This fails if the instance is already mutably borrowed (reentrancy issue). + pub fn save(&self) -> anyhow::Result> { + let instance_data = self + .create_data() + .context("couldn't create instance data")?; + let bytes = + serde_json::to_vec(&instance_data).context("couldn't serialize instance data")?; + Ok(bytes) + } + + fn remove_auto_unit_if_requirements_met( + &self, + auto_units: &mut NonCryptoHashMap, + controller_id: &str, + ) -> Option { + let auto_unit = auto_units.get(controller_id)?; + if self.has_all_features_required_by_main_preset(&auto_unit.main_preset_id) { + auto_units.remove(controller_id) + } else { + None + } + } + + fn has_all_features_required_by_main_preset(&self, main_preset_id: &str) -> bool { + let main_preset_manager = BackboneShell::get().main_preset_manager().borrow(); + let Some(preset) = main_preset_manager.find_preset_info_by_id(main_preset_id) else { + return false; + }; + preset + .specific_meta_data + .required_features + .iter() + .all(|f| self.has_feature(f)) + } + + fn has_feature(&self, feature: &str) -> bool { + match feature { + instance_features::PLAYTIME => self + .model + .get() + .borrow() + .instance() + .borrow() + .has_clip_matrix(), + _ => false, + } + } + + /// # Errors + /// + /// This fails if the instance is already mutably borrowed (reentrancy issue). + pub fn create_data(&self) -> anyhow::Result { + let additional_unit_datas = { + let mut additional_unit_datas = vec![]; + let additional_unit_shells = + blocking_read_lock(&self.additional_unit_shells, "create_instance_data"); + for us in additional_unit_shells.iter() { + let model = us + .model() + .try_borrow() + .context("couldn't borrow additional unit model")?; + if model.auto_unit().is_some() { + // Auto units are not saved + continue; + } + let unit_data = UnitData::from_model(&model); + additional_unit_datas.push(unit_data); + } + additional_unit_datas + }; + let instance_model = self + .model + .get() + .try_borrow() + .context("couldn't borrow instance model")?; + let instance = instance_model + .instance() + .try_borrow() + .context("couldn't borrow instance")?; + let unit_model = self + .main_unit_shell + .model() + .try_borrow() + .context("couldn't borrow main unit model")?; + let data = InstanceData { + tags: instance_model.tags().to_vec(), + main_unit: UnitData::from_model(&unit_model), + additional_units: additional_unit_datas, + settings: self.settings.get().borrow().clone(), + pot_state: instance.save_pot_unit(), + // TODO-medium Subject to improvement: If ReaLearn has been compiled without playtime feature, a + // potential Playtime matrix in the instance data will be silently discarded and disappear when saving + // next time. It would be better to memorize the data in the shell. + clip_matrix: { + #[cfg(feature = "playtime")] + { + instance.clip_matrix().map(|matrix| matrix.save()) + } + #[cfg(not(feature = "playtime"))] + None + }, + custom_data: instance.custom_data().clone(), + }; + Ok(data) + } + + /// Restores instance shell state from the given serialized data. + /// + /// This must not lock any data that's accessed from real-time threads. + /// + /// To be called from main thread. + pub fn load(self: SharedInstanceShell, data: &[u8]) -> anyhow::Result<()> { + let left_json_object_brace = data + .iter() + .position(|b| *b == 0x7b) + .context("couldn't find left JSON brace in bank data")?; + // ReaLearn C++ saved some IPlug binary data in front of the actual JSON object. Find + // start of JSON data. + let data = &data[left_json_object_brace..]; + let instance_data = InstanceData::parse(data)?; + self.apply_data_internal(instance_data)?; + Ok(()) + } + + pub fn apply_data( + self: SharedInstanceShell, + instance_data: InstanceData, + ) -> anyhow::Result<()> { + self.apply_data_internal(instance_data) + } + + fn apply_data_internal( + self: SharedInstanceShell, + instance_data: InstanceData, + ) -> anyhow::Result<()> { + let instance = { + let mut instance_model = self.model.get().borrow_mut(); + let _ = instance_model.change(InstanceCommand::SetTags(instance_data.tags)); + instance_model.instance().clone() + }; + // General properties + *self.settings.get().borrow_mut() = instance_data.settings; + // Pot state + instance + .borrow_mut() + .restore_pot_unit(instance_data.pot_state.clone()); + // Custom data + instance + .borrow_mut() + .set_custom_data(instance_data.custom_data); + // Playtime matrix + #[cfg(feature = "playtime")] + { + if let Some(m) = instance_data.clip_matrix { + let instance_model = self.model.get().borrow(); + let mut instance = instance_model.instance().borrow_mut(); + self.clone() + .get_or_insert_owned_clip_matrix(&mut instance)? + .load(m)?; + } else { + instance.borrow_mut().set_clip_matrix(None); + } + } + // Main unit + self.main_unit_shell.apply_data(&instance_data.main_unit)?; + // Additional units + let additional_unit_shells: anyhow::Result> = instance_data + .additional_units + .into_iter() + .map(|ud| { + let unit_shell = self.create_additional_unit_shell(None); + unit_shell.apply_data(&ud)?; + Ok(unit_shell) + }) + .collect(); + *blocking_write_lock(&self.additional_unit_shells, "InstanceShell apply_data") = + additional_unit_shells?; + // Apply auto units + update_auto_units_async(); + // Inform hub + BackboneShell::get() + .proto_hub() + .notify_everything_in_instance_has_changed(self.instance_id); + Ok(()) + } + + /// Forwards the given MIDI event to all units. + /// + /// To be called from real-time thread. + pub fn process_incoming_midi_from_plugin( + &self, + event: ControlEvent>, + is_transport_start: bool, + host: HostCallback, + ) { + let Some(unit_shells) = non_blocking_try_read_lock(&self.additional_unit_shells) else { + // Better miss one block than blocking the entire audio thread + return; + }; + for instance_shell in once(&self.main_unit_shell).chain(&*unit_shells) { + instance_shell.process_incoming_midi_from_vst(event, is_transport_start, host); + } + } + + /// Invokes the processing function for each unit. + /// + /// To be called from real-time thread (in the plug-in's processing function). + pub fn run_from_plugin(&self, host: HostCallback) { + let Some(unit_shells) = non_blocking_try_read_lock(&self.additional_unit_shells) else { + // Better miss one block than blocking the entire audio thread + return; + }; + for unit_shell in once(&self.main_unit_shell).chain(&*unit_shells) { + unit_shell.run_from_vst(host); + } + } + + #[cfg(feature = "playtime")] + pub fn run_playtime_from_plugin( + &self, + buffer: &mut vst::buffer::AudioBuffer, + block_props: crate::domain::AudioBlockProps, + ) { + base::non_blocking_lock(&*self.rt_instance, "RealTimeInstance") + .run_from_vst(buffer, block_props); + } + + /// Informs all units about a sample rate change. + /// + /// To be called from main thread. + pub fn set_sample_rate(&self, rate: f32) { + // Called in main thread. There should not be any contention from audio thread because + // this is only called when plug-in suspended. + let unit_shells = blocking_read_lock(&self.additional_unit_shells, "set_sample_rate"); + for unit_shell in once(&self.main_unit_shell).chain(&*unit_shells) { + unit_shell.set_sample_rate(rate); + } + } + + /// Fails if Playtime feature is not available. + pub fn insert_owned_clip_matrix_if_necessary(self: SharedInstanceShell) -> anyhow::Result<()> { + #[cfg(not(feature = "playtime"))] + { + anyhow::bail!("Playtime feature not available") + } + #[cfg(feature = "playtime")] + { + let instance_model = self.model.get().borrow(); + let mut instance = instance_model.instance().borrow_mut(); + if instance.clip_matrix().is_some() { + return Ok(()); + } + self.clone() + .get_or_insert_owned_clip_matrix(&mut instance)?; + // For convenience, we automatically switch global control on. This, combined with automatically creating + // a controller for a known device (see `maybe_create_controller_for_device`) leads to newly connected + // controllers working automagically! + self.change_settings(|settings| settings.control.global_control_enabled = true); + Ok(()) + } + } + + pub fn load_clip_matrix( + self: SharedInstanceShell, + matrix: Option, + ) -> anyhow::Result<()> { + #[cfg(not(feature = "playtime"))] + { + let _ = matrix; + anyhow::bail!("Playtime feature not enabled"); + } + #[cfg(feature = "playtime")] + { + let instance_model = self.model().borrow(); + let mut instance = instance_model.instance().borrow_mut(); + if let Some(matrix) = matrix { + self.clone() + .get_or_insert_owned_clip_matrix(&mut instance)? + .load(matrix)?; + } else { + instance.set_clip_matrix(None); + } + Ok(()) + } + } + + /// Returns and - if necessary - installs an owned Playtime matrix from/into the given instance. + /// + /// If this instance already contains an owned Playtime matrix, returns it. If not, creates + /// and installs one, removing a possibly existing foreign matrix reference. + #[cfg(feature = "playtime")] + pub fn get_or_insert_owned_clip_matrix( + self: SharedInstanceShell, + instance: &mut Instance, + ) -> anyhow::Result<&mut playtime_clip_engine::base::Matrix> { + let main_unit_model = Rc::downgrade(self.main_unit_shell.model()); + let weak_instance_shell = Arc::downgrade(&self); + let create_handler = + move |instance: &Instance| -> Box { + let handler = crate::infrastructure::plugin::MatrixHandler::new( + instance.id(), + instance.audio_hook_task_sender.clone(), + instance.real_time_instance_task_sender.clone(), + instance.playtime.clip_matrix_event_sender.clone(), + weak_instance_shell, + main_unit_model, + ); + Box::new(handler) + }; + instance.create_and_install_clip_matrix_if_necessary(create_handler)?; + Ok(instance.clip_matrix_mut().unwrap()) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/mod.rs new file mode 100644 index 0000000..5ced36b --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/mod.rs @@ -0,0 +1,41 @@ +mod api_impl; +mod backbone_shell; +mod debug_util; +mod helgobox_plugin_editor; +mod tracing_spam_filter; +mod tracing_util; +pub use backbone_shell::*; +pub mod helgobox_plugin; +mod instance_parameter_container; +mod instance_shell; +pub use instance_shell::*; +mod auto_units; +pub use auto_units::*; +mod actions; +mod ini_util; +pub use actions::*; +mod controller_detection; +mod remote_config; +pub use remote_config::*; +mod sentry; + +pub mod dynamic_toolbar; +mod hidden_helper_panel; +mod sandbox; +mod unit_shell; +pub use unit_shell::*; + +#[cfg(feature = "playtime")] +mod clip_matrix_handler; + +#[cfg(feature = "playtime")] +pub use clip_matrix_handler::*; + +pub use instance_parameter_container::*; + +#[allow(unused)] +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +vst::plugin_main!(helgobox_plugin::HelgoboxPlugin); diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/remote_config.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/remote_config.rs new file mode 100644 index 0000000..952da3f --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/remote_config.rs @@ -0,0 +1,33 @@ +use semver::Version; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct HelgoboxRemoteConfig { + pub sentry: SentryRemoteConfig, + pub plugin: PluginRemoteConfig, + pub app: AppRemoteConfig, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SentryRemoteConfig { + pub dsn: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PluginRemoteConfig { + pub latest_version: Version, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AppRemoteConfig { + pub latest_version: Version, +} + +impl HelgoboxRemoteConfig { + pub async fn fetch() -> anyhow::Result { + let response = reqwest::get("https://helgoboss.org/projects/helgobox/config.yaml").await?; + let yaml = response.text().await?; + let remote_config: HelgoboxRemoteConfig = serde_yaml::from_str(&yaml)?; + Ok(remote_config) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/sandbox.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/sandbox.rs new file mode 100644 index 0000000..84954ef --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/sandbox.rs @@ -0,0 +1,5 @@ +//! Some experimental code for trying things out. +#[allow(unused)] +pub fn simulate_error() { + panic!("Simulated error"); +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/sentry.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/sentry.rs new file mode 100644 index 0000000..b1fe02c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/sentry.rs @@ -0,0 +1,15 @@ +use crate::infrastructure::plugin::remote_config::SentryRemoteConfig; +use reaper_high::{PluginInfo, Reaper, SentryConfig}; + +pub fn init_sentry( + plugin_info: &PluginInfo, + remote_config: &SentryRemoteConfig, +) -> anyhow::Result<()> { + let sentry_config = SentryConfig { + plugin_info, + dsn: remote_config.dsn.parse()?, + in_app_include: vec!["helgobox"], + }; + Reaper::get().init_sentry(sentry_config); + Ok(()) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/tracing_spam_filter.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/tracing_spam_filter.rs new file mode 100644 index 0000000..dc2f061 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/tracing_spam_filter.rs @@ -0,0 +1,193 @@ +use base::hash_util::NonCryptoHashMap; +use reaper_low::module_is_attached; +use std::any::TypeId; +use std::collections::hash_map::Entry; +use std::fmt::Debug; +use std::sync::Mutex; +use std::time::{Duration, Instant}; +use tracing::field; +use tracing_core::callsite::Identifier; +use tracing_core::span::{Attributes, Id, Record}; +use tracing_core::{Dispatch, Event, Field, Interest, LevelFilter, Metadata, Subscriber}; + +/// A tracing subscriber that introduces special handling for tracing events with a "spam" field: It prevents the +/// console from being spammed by repeated events of one kind. +/// +/// - `spam = false`: Logs as usual, no limits +/// - `spam = true`: Logs at a maximum one time +/// - `spam = 5`: Logs at a maximum 5 times +/// +/// The counter for each event is reset after a configurable amount of time. +pub struct SpamFilter { + default_max_log_count: u32, + reset_after: Duration, + already_logged_spam: Mutex>, + subscriber: S, +} + +struct Spam { + max_log_count: u32, + num_occurrences: u32, + last_timestamp: Instant, +} + +impl SpamFilter { + pub fn new(default_max_log_count: u32, reset_after: Duration, subscriber: S) -> Self { + assert!( + default_max_log_count > 0, + "default_max_log_count must be > 0" + ); + Self { + default_max_log_count, + reset_after, + already_logged_spam: Default::default(), + subscriber, + } + } + + fn include_event(&self, event: &Event) -> bool { + if !module_is_attached() { + return false; + } + if event.metadata().fields().field("spam").is_none() { + return true; + } + let mut map = self.already_logged_spam.lock().unwrap(); + match map.entry(event.metadata().callsite()) { + Entry::Occupied(e) => { + let spam = e.into_mut(); + if spam.max_log_count == 0 { + // Special case (allows us to write "spam = 0", which makes sense if 0 comes from a variable) + return true; + } + if spam.last_timestamp.elapsed() < self.reset_after { + // Not yet reset time + let include = if spam.num_occurrences >= spam.max_log_count { + // Threshold exceeded + if spam.num_occurrences == spam.max_log_count { + let payload = event.metadata().name(); + tracing::warn!( + "Tracing event {payload} occurred again but will not be logged anymore unless it calms down for {:?}", + self.reset_after + ); + } + false + } else { + // Threshold not exceeded yet + true + }; + spam.last_timestamp = Instant::now(); + spam.num_occurrences += 1; + include + } else { + // Reset + spam.last_timestamp = Instant::now(); + spam.num_occurrences = 1; + true + } + } + Entry::Vacant(e) => { + let mut max_log_count_extractor = MaxLogCountExtractor { + max_log_count: self.default_max_log_count, + }; + event.record(&mut max_log_count_extractor); + let initial = Spam { + max_log_count: max_log_count_extractor.max_log_count, + num_occurrences: 1, + last_timestamp: Instant::now(), + }; + e.insert(initial); + true + } + } + } +} + +impl Subscriber for SpamFilter { + fn on_register_dispatch(&self, subscriber: &Dispatch) { + self.subscriber.on_register_dispatch(subscriber) + } + + fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { + self.subscriber.register_callsite(metadata) + } + + fn enabled(&self, metadata: &Metadata<'_>) -> bool { + self.subscriber.enabled(metadata) + } + + fn max_level_hint(&self) -> Option { + self.subscriber.max_level_hint() + } + + fn new_span(&self, span: &Attributes<'_>) -> Id { + self.subscriber.new_span(span) + } + + fn record(&self, span: &Id, values: &Record<'_>) { + self.subscriber.record(span, values) + } + + fn record_follows_from(&self, span: &Id, follows: &Id) { + self.subscriber.record_follows_from(span, follows) + } + + fn event_enabled(&self, event: &Event<'_>) -> bool { + self.subscriber.event_enabled(event) + } + + fn event(&self, event: &Event<'_>) { + if self.include_event(event) { + self.subscriber.event(event) + } + } + + fn enter(&self, span: &Id) { + self.subscriber.enter(span) + } + + fn exit(&self, span: &Id) { + self.subscriber.exit(span) + } + + fn clone_span(&self, id: &Id) -> Id { + self.subscriber.clone_span(id) + } + + #[allow(deprecated)] + fn drop_span(&self, id: Id) { + self.subscriber.drop_span(id) + } + + fn try_close(&self, id: Id) -> bool { + self.subscriber.try_close(id) + } + + fn current_span(&self) -> tracing_core::span::Current { + self.subscriber.current_span() + } + + unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { + self.subscriber.downcast_raw(id) + } +} + +struct MaxLogCountExtractor { + max_log_count: u32, +} + +impl field::Visit for MaxLogCountExtractor { + fn record_i64(&mut self, field: &Field, value: i64) { + if field.name() == "spam" { + self.max_log_count = value as u32; + } + } + + fn record_bool(&mut self, field: &Field, value: bool) { + if field.name() == "spam" { + self.max_log_count = value.into(); + } + } + + fn record_debug(&mut self, _field: &Field, _value: &dyn Debug) {} +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/tracing_util.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/tracing_util.rs new file mode 100644 index 0000000..0c2127d --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/tracing_util.rs @@ -0,0 +1,140 @@ +use std::fmt::{Arguments, Debug}; +use std::io::{IoSlice, Write}; +use std::sync::mpsc::{Receiver, Sender}; +use std::time::Duration; +use std::{mem, thread}; + +use reaper_high::Reaper; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::{registry, EnvFilter}; + +use crate::infrastructure::plugin::tracing_spam_filter::SpamFilter; + +#[derive(Debug)] +pub struct TracingHook { + sender: Sender, +} + +impl TracingHook { + /// Initializes tracing/logging if the env variable `HELGOBOX_LOG` is set. + /// + /// This doesn't just set the global tracing subscriber, it also starts an async logger + /// thread, which is responsible for logging messages that come from real-time threads (reducing + /// the amount of audio stuttering). + /// + /// This should be called only once within the lifetime of the loaded shared library! On Linux + /// and macOS, this means it must only be called once within the lifetime of REAPER because once + /// a shared library is loaded, it's not unloaded anymore. On Windows, it can be called again + /// after REAPER unloaded the library via `FreeLibrary` and reloaded it again. + /// + /// The returned tracing hook must be dropped before the library is unloaded, otherwise the + /// async logger thread sticks around and that can't be good. + pub fn init() -> Option { + let env_var = std::env::var("HELGOBOX_LOG").ok()?; + let env_filter = EnvFilter::new(env_var); + let (sender, receiver) = std::sync::mpsc::channel(); + thread::Builder::new() + .name(String::from("Helgobox async logger")) + .spawn(move || keep_logging(receiver, std::io::stdout())) + .expect("Helgobox async logger thread couldn't be created"); + // In the beginning, I wrapped the subscriber in one that calls permit_alloc() in on_event() + // in order to prevent assert_no_alloc() from aborting because of logging. However, this was + // not enough. Some tracing-core stuff also did allocations and it was not possible to wrap it. + // The approach now is that we wrap the log macros (see example in playtime-clip-matrix crate). + // TODO-medium Maybe we can improve on that by triggering the initial allocations in the + // audio hook and using a large ring buffer to avoid any allocation when sending the log + // messages? + let cloned_sender = sender.clone(); + let fmt_layer = tracing_subscriber::fmt::layer() + .pretty() + // We don't want ANSI color codes at release time because I might instruct people to enable + // logging into a file. + .with_ansi(cfg!(all(debug_assertions, not(windows)))) + // .with_thread_ids(true) + // .with_thread_names(true) + // .compact() + .with_writer(move || AsyncWriter::new(std::io::stdout(), cloned_sender.clone())); + let inner_subscriber = registry().with(env_filter).with(fmt_layer); + let spam_filter = SpamFilter::new(1, Duration::from_secs(10), inner_subscriber); + tracing::subscriber::set_global_default(spam_filter) + .expect("setting default subscriber failed"); + let hook = Self { sender }; + Some(hook) + } +} + +impl Drop for TracingHook { + fn drop(&mut self) { + // This prevents the metrics recorder thread from lurking around after the library + // is unloaded (which is of importance if "Allow complete unload of VST plug-ins" + // is enabled in REAPER for Windows). Ideally, we would just destroy the sender to achieve + // the same effect. But the sender is buried within the tracing framework and probably + // cloned multiple times. Unloading the library will just free the memory without triggering + // the drop, so that wouldn't work either. + let _ = self.sender.send(AsyncLoggerCommand::Finish); + // Joining the thread here somehow leads to a deadlock. Not sure why. It doesn't + // seem to be necessary anyway. The thread will end no matter what. + } +} + +struct AsyncWriter { + sender: Sender, + inner: W, + data: Vec, +} + +enum AsyncLoggerCommand { + Finish, + Log(Vec), +} + +impl AsyncWriter { + pub fn new(inner: W, sender: Sender) -> Self { + Self { + sender, + inner, + data: vec![], + } + } +} + +fn keep_logging(receiver: Receiver, mut inner: impl Write) { + while let Ok(cmd) = receiver.recv() { + match cmd { + AsyncLoggerCommand::Finish => break, + AsyncLoggerCommand::Log(msg) => { + inner.write_all(&msg).unwrap(); + inner.flush().unwrap(); + } + } + } +} + +impl Write for AsyncWriter { + fn write(&mut self, _buf: &[u8]) -> std::io::Result { + unimplemented!() + } + + fn write_vectored(&mut self, _bufs: &[IoSlice<'_>]) -> std::io::Result { + unimplemented!() + } + + fn flush(&mut self) -> std::io::Result<()> { + unimplemented!() + } + + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + if Reaper::get().medium_reaper().is_in_real_time_audio() { + self.data.write_all(buf)?; + let data = mem::take(&mut self.data); + let _ = self.sender.send(AsyncLoggerCommand::Log(data)); + Ok(()) + } else { + self.inner.write_all(buf) + } + } + + fn write_fmt(&mut self, _fmt: Arguments<'_>) -> std::io::Result<()> { + unimplemented!() + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/plugin/unit_shell.rs b/plugin-reaper-realearn/main/src/infrastructure/plugin/unit_shell.rs new file mode 100644 index 0000000..1638bcd --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/plugin/unit_shell.rs @@ -0,0 +1,271 @@ +use vst::plugin::HostCallback; + +use crate::domain::{ + ControlEvent, IncomingMidiMessage, InstanceId, MainProcessor, MidiEvent, ParameterManager, + ProcessorContext, RealTimeProcessorLocker, SharedRealTimeProcessor, Unit, UnitId, + WeakRealTimeInstance, +}; +use crate::domain::{NormalRealTimeTask, RealTimeProcessor}; +use crate::infrastructure::plugin::UnitInfo; +use crate::infrastructure::ui::UnitPanel; +use base::{NamedChannelSender, SenderToNormalThread, SenderToRealTimeThread}; +use reaper_medium::Hz; + +use std::cell::{Cell, RefCell}; + +use std::rc::Rc; + +use fragile::Fragile; +use std::sync::{Arc, Mutex}; +use swell_ui::{SharedView, WeakView}; +use tracing::debug; + +use crate::application::{AutoUnitData, SharedInstanceModel, SharedUnitModel, UnitModel}; +use crate::infrastructure::plugin::backbone_shell::BackboneShell; + +use crate::infrastructure::data::UnitData; +use crate::infrastructure::server::http::keep_informing_clients_about_session_events; +use crate::infrastructure::ui::instance_panel::InstancePanel; + +const NORMAL_REAL_TIME_TASK_QUEUE_SIZE: usize = 1000; +const FEEDBACK_REAL_TIME_TASK_QUEUE_SIZE: usize = 2000; +const NORMAL_REAL_TIME_TO_MAIN_TASK_QUEUE_SIZE: usize = 10_000; +const CONTROL_MAIN_TASK_QUEUE_SIZE: usize = 5000; +/// I increased this for https://github.com/helgoboss/helgobox/issues/913. More processed +/// OSC messages processed at once in combination with target "FX parameter: Set value" +/// caused this queue to explode. +const PARAMETER_MAIN_TASK_QUEUE_SIZE: usize = 20_000; + +#[derive(Debug)] +pub struct UnitShell { + /// A non-persistent counting ID. Must be unique. + id: UnitId, + /// Fragile because we can't access this from any other thread than the main thread. + model: Fragile, + panel: Fragile>, + normal_real_time_task_sender: SenderToRealTimeThread, + // Called in real-time audio thread only. + // We keep it in this struct in order to be able to inform it about incoming FX MIDI messages + // and drive its processing without detour. Well, almost. We share it with the global ReaLearn + // audio hook that also drives processing (because in some cases the VST processing is + // stopped). That's why we need an Rc/RefCell. + real_time_processor: SharedRealTimeProcessor, + notified_unit_about_start: Fragile>, +} + +impl UnitShell { + #[allow(clippy::too_many_arguments)] + pub fn new( + unit_id: UnitId, + initial_name: Option, + instance_id: InstanceId, + processor_context: ProcessorContext, + instance_model: SharedInstanceModel, + rt_instance: WeakRealTimeInstance, + instance_panel: WeakView, + is_main_unit: bool, + auto_unit: Option, + ) -> Self { + let is_auto_unit = auto_unit.is_some(); + let (normal_real_time_task_sender, normal_real_time_task_receiver) = + SenderToRealTimeThread::new_channel( + "normal real-time tasks", + NORMAL_REAL_TIME_TASK_QUEUE_SIZE, + ); + let (feedback_real_time_task_sender, feedback_real_time_task_receiver) = + SenderToRealTimeThread::new_channel( + "feedback real-time tasks", + FEEDBACK_REAL_TIME_TASK_QUEUE_SIZE, + ); + let (normal_main_task_sender, normal_main_task_receiver) = + SenderToNormalThread::new_unbounded_channel("normal main tasks"); + let (normal_rt_to_main_task_sender, normal_rt_to_main_task_receiver) = + SenderToNormalThread::new_bounded_channel( + "normal real-time to main tasks", + NORMAL_REAL_TIME_TO_MAIN_TASK_QUEUE_SIZE, + ); + let (control_main_task_sender, control_main_task_receiver) = + SenderToNormalThread::new_bounded_channel( + "control main tasks", + CONTROL_MAIN_TASK_QUEUE_SIZE, + ); + let (parameter_main_task_sender, parameter_main_task_receiver) = + SenderToNormalThread::new_bounded_channel( + "parameter main tasks", + PARAMETER_MAIN_TASK_QUEUE_SIZE, + ); + let real_time_processor = RealTimeProcessor::new( + unit_id, + rt_instance, + normal_real_time_task_receiver, + feedback_real_time_task_receiver, + feedback_real_time_task_sender.clone(), + normal_rt_to_main_task_sender, + control_main_task_sender, + ); + let real_time_processor = Arc::new(Mutex::new(real_time_processor)); + // This is necessary since Rust 1.62.0 (or 1.63.0, not sure). Since those versions, + // locking a mutex the first time apparently allocates. If we don't lock the + // mutex now for the first time but do it in the real-time thread, assert_no_alloc will + // complain in debug builds. + drop(real_time_processor.lock_recover()); + // Start create session + // Instance state (domain - shared) + let (unit_feedback_event_sender, unit_feedback_event_receiver) = + SenderToNormalThread::new_unbounded_channel("unit state change events"); + let parameter_manager = ParameterManager::new(parameter_main_task_sender); + let instance = instance_model.borrow().instance().clone(); + let unit = Unit::new( + unit_id, + is_main_unit, + Rc::downgrade(&instance), + unit_feedback_event_sender, + parameter_manager, + ); + let unit = Rc::new(RefCell::new(unit)); + // Session (application - shared) + let unit_model = UnitModel::new( + instance_id, + unit_id, + initial_name, + processor_context.clone(), + normal_main_task_sender.clone(), + BackboneShell::get(), + BackboneShell::get().controller_preset_manager().clone(), + BackboneShell::get().main_preset_manager().clone(), + BackboneShell::get().preset_link_manager(), + instance_model.clone(), + unit.clone(), + BackboneShell::get().feedback_audio_hook_task_sender(), + feedback_real_time_task_sender.clone(), + BackboneShell::get().osc_feedback_task_sender(), + BackboneShell::get().control_surface_main_task_sender(), + auto_unit, + ); + let shared_unit_model = Rc::new(RefCell::new(unit_model)); + let weak_session = Rc::downgrade(&shared_unit_model); + let unit_panel = UnitPanel::new(instance_id, weak_session.clone(), instance_panel.clone()); + shared_unit_model + .borrow_mut() + .set_ui(Rc::downgrade(&unit_panel)); + keep_informing_clients_about_session_events(&shared_unit_model); + // TODO-high-playtime-refactoring We should register this like the instance - one layer higher and only + // by passing the UnitShell (the root for everything unit-related). + let unit_info = UnitInfo { + unit_id, + instance_id, + instance: Rc::downgrade(&instance), + unit_model: weak_session.clone(), + instance_panel, + is_main_unit, + unit: Rc::downgrade(&unit), + is_auto_unit, + }; + // Main processor - (domain, owned by REAPER control surface) + // Register the main processor with the global ReaLearn control surface. We let it + // call by the control surface because it must be called regularly, + // even when the ReaLearn UI is closed. That means, the VST GUI idle + // callback is not suited. + let main_processor = MainProcessor::new( + instance_id, + unit_id, + normal_main_task_sender.clone(), + normal_main_task_receiver, + normal_rt_to_main_task_receiver, + parameter_main_task_receiver, + control_main_task_receiver, + unit_feedback_event_receiver, + normal_real_time_task_sender.clone(), + feedback_real_time_task_sender, + BackboneShell::get() + .feedback_audio_hook_task_sender() + .clone(), + BackboneShell::get().additional_feedback_event_sender(), + BackboneShell::get().instance_orchestration_event_sender(), + BackboneShell::get().osc_feedback_task_sender().clone(), + weak_session.clone(), + processor_context.clone(), + instance, + unit.clone(), + BackboneShell::get(), + ); + BackboneShell::get().register_unit(unit_info, real_time_processor.clone(), main_processor); + shared_unit_model + .borrow_mut() + .activate(weak_session.clone()); + // End create session + Self { + id: unit_id, + // InstanceShell is the main owner of the InstanceModel. Everywhere else the InstanceModel is + // just temporarily upgraded, never stored as Rc, only as Weak. + model: Fragile::new(shared_unit_model), + panel: Fragile::new(unit_panel), + normal_real_time_task_sender, + real_time_processor, + notified_unit_about_start: Default::default(), + } + } + + pub fn id(&self) -> UnitId { + self.id + } + + /// Panics if not called from main thread. + pub fn model(&self) -> &SharedUnitModel { + self.model.get() + } + + /// Panics if not called from main thread. + pub fn panel(&self) -> &SharedView { + self.panel.get() + } + + pub fn apply_data(&self, session_data: &UnitData) -> anyhow::Result<()> { + let model = self.model.get(); + session_data.apply_to_model(model)?; + if !self.notified_unit_about_start.get().replace(true) { + model.borrow().notify_realearn_unit_started(); + } + Ok(()) + } + + pub fn process_incoming_midi_from_vst( + &self, + event: ControlEvent>, + is_transport_start: bool, + host: HostCallback, + ) { + self.real_time_processor + .lock_recover() + .process_incoming_midi_from_vst(event, is_transport_start, &host); + } + + pub fn run_from_vst(&self, host: HostCallback) { + self.real_time_processor.lock_recover().run_from_vst(&host); + } + + pub fn set_sample_rate(&self, rate: f32) { + // This is called in main thread, so we need to send it to the real-time processor via + // channel. Real-time processor needs sample rate to do some MIDI clock calculations. + // If task queue is full or audio not running, so what. Don't spam the user with error + // messages. + self.normal_real_time_task_sender + .send_if_space(NormalRealTimeTask::UpdateSampleRate(Hz::new_panic( + rate as _, + ))); + } +} + +impl Drop for UnitShell { + fn drop(&mut self) { + debug!("Dropping UnitShell {}...", self.id); + let session = self.model.get(); + if BackboneShell::is_loaded() { + BackboneShell::get().unregister_unit(self.id); + } + debug!( + "{} pointers are still referring to this session", + Rc::strong_count(session) + ); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/ext.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/ext.rs new file mode 100644 index 0000000..6666f19 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/ext.rs @@ -0,0 +1,967 @@ +use enumflags2::BitFlags; +use reaper_high::{FxChainContext, Reaper}; +use reaper_medium::{EnumPitchShiftModesResult, PlayState, ReaperStr}; + +use helgobox_api::runtime::{ControllerPreset, LicenseInfo, MainPreset, ValidatedLicense}; + +use crate::application::UnitModel; +use crate::domain::CompartmentKind; +use crate::infrastructure::data::{ + ControllerManager, FileBasedControllerPresetManager, FileBasedMainPresetManager, LicenseManager, +}; +use crate::infrastructure::plugin::{BackboneShell, HelgoboxRemoteConfig, InstanceShell}; +use crate::infrastructure::proto::{ + event_reply, fx_chain_location_info, occasional_global_update, occasional_instance_update, + qualified_occasional_unit_update, ArrangementPlayState, AudioInputChannel, AudioInputChannels, + CellAddress, Compartment, ContinuousColumnUpdate, ContinuousMatrixUpdate, Empty, + FxChainLocationInfo, FxLocationInfo, GetContinuousColumnUpdatesReply, + GetContinuousMatrixUpdatesReply, GetContinuousSlotUpdatesReply, GetOccasionalClipUpdatesReply, + GetOccasionalColumnUpdatesReply, GetOccasionalGlobalUpdatesReply, + GetOccasionalInstanceUpdatesReply, GetOccasionalMatrixUpdatesReply, + GetOccasionalPlaytimeEngineUpdatesReply, GetOccasionalRowUpdatesReply, + GetOccasionalSlotUpdatesReply, GetOccasionalTrackUpdatesReply, GetOccasionalUnitUpdatesReply, + HelgoboxInstance, HelgoboxInstanceData, HelgoboxInstances, HostColorScheme, MidiDeviceStatus, + MidiInputDevice, MidiInputDevices, MidiOutputDevice, MidiOutputDevices, OccasionalGlobalUpdate, + OccasionalInstanceUpdate, OccasionalMatrixUpdate, OccasionalPlaytimeEngineUpdate, + PitchShiftMode, PitchShiftModes, PitchShiftSubMode, ProjectLocationInfo, + QualifiedContinuousSlotUpdate, QualifiedOccasionalClipUpdate, QualifiedOccasionalColumnUpdate, + QualifiedOccasionalRowUpdate, QualifiedOccasionalSlotUpdate, QualifiedOccasionalTrackUpdate, + QualifiedOccasionalUnitUpdate, ResampleMode, ResampleModes, RgbColor, Scope, Severity, + SlotAddress, TrackFxChainLocationInfo, TrackLocationInfo, Unit, Units, UpdateInfo, Warning, + Warnings, +}; +use crate::infrastructure::server::data::get_controller_routing; + +impl occasional_instance_update::Update { + pub fn info_event(event: helgobox_api::runtime::InstanceInfoEvent) -> Self { + let json = + serde_json::to_string(&event).expect("couldn't represent instance info event as JSON"); + Self::InfoEvent(json) + } + + pub fn settings(instance_shell: &InstanceShell) -> Self { + let settings = instance_shell.settings(); + let json = + serde_json::to_string(&settings).expect("couldn't represent instance settings as JSON"); + Self::Settings(json) + } + + pub fn units(instance_shell: &InstanceShell) -> Self { + let units = instance_shell.all_unit_models().map(|unit_model| { + let unit_model = unit_model.borrow(); + Unit { + id: unit_model.unit_id().into(), + key: unit_model.unit_key().to_string(), + name: unit_model.name().map(|n| n.to_string()), + } + }); + Self::Units(Units { + units: units.collect(), + }) + } + + pub fn warning(severity: Severity, scope: Option, message: String) -> Warning { + Warning { + severity: severity.into(), + scope: scope.map(|s| s.into()), + message, + } + } + + pub fn warnings(warnings: Vec) -> Self { + Self::Warnings(Warnings { warnings }) + } +} + +impl qualified_occasional_unit_update::Update { + pub fn controller_routing(unit_model: &UnitModel) -> Self { + let controller_routing = get_controller_routing(unit_model); + let json = serde_json::to_string(&controller_routing) + .expect("couldn't represent controller routing as JSON"); + Self::ControllerRouting(json) + } +} + +impl RgbColor { + pub fn from_engine(color: Option) -> Self { + Self { + color: color + .map(|c| (((c.r as u32) << 16) + ((c.g as u32) << 8) + (c.b as u32)) as i32), + } + } + + pub fn to_engine(&self) -> Option { + let c = self.color?; + let dest = reaper_medium::RgbColor { + r: ((c >> 16) & 0xFF) as u8, + g: ((c >> 8) & 0xFF) as u8, + b: (c & 0xFF) as u8, + }; + Some(dest) + } +} + +impl occasional_global_update::Update { + pub fn host_color_scheme() -> Self { + let reaper = Reaper::get().medium_reaper(); + let colors = REAPER_COLOR_KEYS + .iter() + .filter_map(|key| { + let native_color = reaper.get_theme_color(*key, BitFlags::empty()).ok()?; + let rgb_color = reaper.color_from_native(native_color); + Some((key.to_string(), RgbColor::from_engine(Some(rgb_color)))) + }) + .collect(); + Self::HostColorScheme(HostColorScheme { colors }) + } + + pub fn arrangement_play_state(play_state: PlayState) -> Self { + Self::ArrangementPlayState(ArrangementPlayState::from_engine(play_state).into()) + } + + pub fn info_event(event: helgobox_api::runtime::GlobalInfoEvent) -> Self { + let json = + serde_json::to_string(&event).expect("couldn't represent global info event as JSON"); + Self::InfoEvent(json) + } + + pub fn update_info() -> Option { + let remote_config = BackboneShell::remote_config()?; + Some(Self::UpdateInfo(UpdateInfo::from_engine(remote_config))) + } + + pub fn instances() -> Self { + Self::Instances(HelgoboxInstances::discover()) + } + + pub fn midi_input_devices() -> Self { + Self::MidiInputDevices(MidiInputDevices::from_engine( + Reaper::get() + .midi_input_devices() + .filter(|d| d.is_available()), + )) + } + + pub fn midi_output_devices() -> Self { + Self::MidiOutputDevices(MidiOutputDevices::from_engine( + Reaper::get() + .midi_output_devices() + .filter(|d| d.is_available()), + )) + } + + pub fn audio_input_channels() -> Self { + Self::AudioInputChannels(AudioInputChannels::from_engine( + Reaper::get().input_channels(), + )) + } + + pub fn resample_modes() -> Self { + Self::ResampleModes(ResampleModes::from_engine(Reaper::get().resample_modes())) + } + + pub fn pitch_shift_modes() -> Self { + Self::PitchShiftModes(PitchShiftModes::from_engine( + Reaper::get().pitch_shift_modes(), + )) + } + + pub fn controller_presets(manager: &FileBasedControllerPresetManager) -> Self { + let api_presets: Vec<_> = manager + .preset_infos() + .iter() + .map(|info| ControllerPreset { + id: info.common.id.clone(), + common: info.common.meta_data.clone(), + specific: info.specific_meta_data.clone(), + }) + .collect(); + let json = serde_json::to_string(&api_presets) + .expect("couldn't represent controller presets as JSON"); + Self::ControllerPresets(json) + } + + pub fn main_presets(manager: &FileBasedMainPresetManager) -> Self { + let api_presets: Vec<_> = manager + .preset_infos() + .iter() + .map(|info| MainPreset { + id: info.common.id.clone(), + common: info.common.meta_data.clone(), + specific: info.specific_meta_data.clone(), + }) + .collect(); + let json = + serde_json::to_string(&api_presets).expect("couldn't represent main presets as JSON"); + Self::MainPresets(json) + } + + pub fn controller_config(manager: &ControllerManager) -> Self { + let json = serde_json::to_string(manager.controller_config()) + .expect("couldn't represent controller config as JSON"); + Self::ControllerConfig(json) + } + + pub fn license_info(license_manager: &LicenseManager) -> Self { + let license_info = LicenseInfo { + licenses: license_manager + .licenses() + .iter() + .map(|license| { + let valid = { + #[cfg(not(feature = "playtime"))] + { + false + } + #[cfg(feature = "playtime")] + { + playtime_clip_engine::PlaytimeMainEngine::validate_license(license) + .is_ok() + } + }; + ValidatedLicense { + license: license.clone().into(), + valid, + } + }) + .collect(), + }; + let json = + serde_json::to_string(&license_info).expect("couldn't represent license info as JSON"); + Self::LicenseInfo(json) + } +} + +impl HelgoboxInstances { + pub fn discover() -> Self { + Self { + instances: BackboneShell::get().with_instance_shell_infos(|infos| { + infos + .iter() + .filter_map(|info| { + let instance_shell = info.instance_shell.upgrade()?; + let main_unit_model = instance_shell.main_unit_shell().model().clone(); + let main_unit_model = main_unit_model.borrow(); + let data = HelgoboxInstanceData { + id: info.instance_id.into(), + key: main_unit_model.unit_key().to_string(), + tags: main_unit_model + .tags() + .iter() + .map(|t| t.to_string()) + .collect(), + }; + let fx = info.processor_context.containing_fx(); + let fx_chain_location = match fx.chain().context() { + FxChainContext::Monitoring => { + fx_chain_location_info::Location::MonitoringFx(Empty {}) + } + FxChainContext::Track { track, is_input_fx } => { + let project = track.project(); + let project_location = ProjectLocationInfo { + index: project.index().ok()?, + path: project.file().map(|f| f.into_string()), + }; + let track_location = TrackLocationInfo { + project: Some(project_location), + id: track.guid().to_string_without_braces(), + index: track.index()?, + name: track.name()?.to_string(), + }; + fx_chain_location_info::Location::TrackFx( + TrackFxChainLocationInfo { + track: Some(track_location), + input_fx: *is_input_fx, + }, + ) + } + // Not supported anyway + FxChainContext::Take(_) => return None, + }; + let fx_chain_location = FxChainLocationInfo { + location: Some(fx_chain_location), + }; + let fx_location = FxLocationInfo { + fx_chain: Some(fx_chain_location), + id: fx.get_or_query_guid().ok()?.to_string_without_braces(), + index: fx.index(), + name: fx.name().to_string(), + }; + let helgobox_instance = HelgoboxInstance { + data: Some(data), + fx: Some(fx_location), + }; + Some(helgobox_instance) + }) + .collect() + }), + } + } +} + +impl UpdateInfo { + pub fn from_engine(config: &HelgoboxRemoteConfig) -> Self { + Self { + latest_host_version: config.plugin.latest_version.to_string(), + latest_app_version: config.app.latest_version.to_string(), + } + } +} + +impl MidiInputDevices { + pub fn from_engine(devs: impl Iterator) -> Self { + Self { + devices: devs.map(MidiInputDevice::from_engine).collect(), + } + } +} + +impl Compartment { + pub fn to_engine(self) -> CompartmentKind { + match self { + Compartment::Controller => CompartmentKind::Controller, + Compartment::Main => CompartmentKind::Main, + } + } +} + +impl MidiInputDevice { + pub fn from_engine(dev: reaper_high::MidiInputDevice) -> Self { + MidiInputDevice { + id: dev.id().get() as _, + name: dev + .name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(), + status: MidiDeviceStatus::from_engine(dev.is_open(), dev.is_connected()).into(), + } + } +} + +impl MidiOutputDevices { + pub fn from_engine(devs: impl Iterator) -> Self { + Self { + devices: devs.map(MidiOutputDevice::from_engine).collect(), + } + } +} + +impl MidiOutputDevice { + pub fn from_engine(dev: reaper_high::MidiOutputDevice) -> Self { + MidiOutputDevice { + id: dev.id().get() as _, + name: dev + .name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(), + status: MidiDeviceStatus::from_engine(dev.is_open(), dev.is_connected()).into(), + } + } +} + +impl MidiDeviceStatus { + pub fn from_engine(open: bool, connected: bool) -> Self { + use MidiDeviceStatus::*; + match (open, connected) { + (false, false) => Disconnected, + (false, true) => ConnectedButDisabled, + // Shouldn't happen but cope with it. + (true, false) => Disconnected, + (true, true) => Connected, + } + } +} + +impl AudioInputChannels { + pub fn from_engine(channels: impl Iterator) -> Self { + Self { + channels: channels + .enumerate() + .map(|(i, name)| AudioInputChannel { + index: i as u32, + name, + }) + .collect(), + } + } +} + +impl ResampleModes { + pub fn from_engine(modes: impl Iterator) -> Self { + Self { + modes: modes + .enumerate() + .map(|(i, name)| ResampleMode { + index: i as u32, + name: name.to_string(), + }) + .collect(), + } + } +} + +impl PitchShiftModes { + pub fn from_engine(modes: impl Iterator>) -> Self { + Self { + modes: modes + .enumerate() + .filter_map(|(i, res)| match res { + EnumPitchShiftModesResult::Unsupported => None, + EnumPitchShiftModesResult::Supported { name } => { + Some(PitchShiftMode::from_engine( + reaper_medium::PitchShiftMode::new(i as u32), + name, + )) + } + }) + .collect(), + } + } +} + +impl PitchShiftMode { + pub fn from_engine(mode: reaper_medium::PitchShiftMode, name: &ReaperStr) -> Self { + Self { + index: mode.get(), + name: name.to_string(), + sub_modes: Reaper::get() + .pitch_shift_sub_modes(mode) + .enumerate() + .map(|(i, name)| PitchShiftSubMode { + index: i as u32, + name: name.to_string(), + }) + .collect(), + } + } +} + +impl SlotAddress { + pub fn from_engine(address: playtime_api::persistence::SlotAddress) -> Self { + Self { + column_index: address.column() as _, + row_index: address.row() as _, + } + } + + pub fn to_engine(&self) -> playtime_api::persistence::SlotAddress { + playtime_api::persistence::SlotAddress::new(self.column_index as _, self.row_index as _) + } +} + +impl CellAddress { + pub fn from_engine(address: playtime_api::runtime::CellAddress) -> Self { + Self { + column_index: address.column_index.map(|i| i as _), + row_index: address.row_index.map(|i| i as _), + } + } + + pub fn to_engine(&self) -> playtime_api::runtime::CellAddress { + playtime_api::runtime::CellAddress::new( + self.column_index.map(|i| i as _), + self.row_index.map(|i| i as _), + ) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalGlobalUpdatesReply(GetOccasionalGlobalUpdatesReply { + global_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalInstanceUpdatesReply(GetOccasionalInstanceUpdatesReply { + instance_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalUnitUpdatesReply(GetOccasionalUnitUpdatesReply { + unit_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalMatrixUpdatesReply(GetOccasionalMatrixUpdatesReply { + matrix_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalTrackUpdatesReply(GetOccasionalTrackUpdatesReply { + track_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalColumnUpdatesReply(GetOccasionalColumnUpdatesReply { + column_updates: value, + }) + } +} +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalRowUpdatesReply(GetOccasionalRowUpdatesReply { + row_updates: value, + }) + } +} +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalSlotUpdatesReply(GetOccasionalSlotUpdatesReply { + slot_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalClipUpdatesReply(GetOccasionalClipUpdatesReply { + clip_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::OccasionalPlaytimeEngineUpdatesReply( + GetOccasionalPlaytimeEngineUpdatesReply { updates: value }, + ) + } +} + +impl From for event_reply::Value { + fn from(value: ContinuousMatrixUpdate) -> Self { + event_reply::Value::ContinuousMatrixUpdatesReply(GetContinuousMatrixUpdatesReply { + matrix_update: Some(value), + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::ContinuousColumnUpdatesReply(GetContinuousColumnUpdatesReply { + column_updates: value, + }) + } +} + +impl From> for event_reply::Value { + fn from(value: Vec) -> Self { + event_reply::Value::ContinuousSlotUpdatesReply(GetContinuousSlotUpdatesReply { + slot_updates: value, + }) + } +} + +impl ArrangementPlayState { + pub fn from_engine(play_state: PlayState) -> Self { + if play_state.is_recording { + if play_state.is_paused { + Self::RecordingPaused + } else { + Self::Recording + } + } else if play_state.is_playing { + if play_state.is_paused { + Self::PlayingPaused + } else { + Self::Playing + } + } else if play_state.is_paused { + Self::PlayingPaused + } else { + Self::Stopped + } + } +} + +const REAPER_COLOR_KEYS: &[&str] = &[ + "col_main_bg2", + "col_main_text2", + "col_main_textshadow", + "col_main_3dhl", + "col_main_3dsh", + "col_main_resize2", + "col_main_text", + "col_main_bg", + "col_main_editbk", + "col_nodarkmodemiscwnd", + "col_transport_editbk", + "col_toolbar_text", + "col_toolbar_text_on", + "col_toolbar_frame", + "toolbararmed_color", + "toolbararmed_drawmode", + "io_text", + "io_3dhl", + "io_3dsh", + "genlist_bg", + "genlist_fg", + "genlist_grid", + "genlist_selbg", + "genlist_selfg", + "genlist_seliabg", + "genlist_seliafg", + "genlist_hilite", + "genlist_hilite_sel", + "col_buttonbg", + "col_tcp_text", + "col_tcp_textsel", + "col_seltrack", + "col_seltrack2", + "tcplocked_color", + "tcplocked_drawmode", + "col_tracklistbg", + "col_mixerbg", + "col_arrangebg", + "arrange_vgrid", + "col_fadearm", + "col_fadearm2", + "col_fadearm3", + "col_tl_fg", + "col_tl_fg2", + "col_tl_bg", + "col_tl_bgsel", + "timesel_drawmode", + "col_tl_bgsel2", + "col_trans_bg", + "col_trans_fg", + "playrate_edited", + "selitem_dot", + "col_mi_label", + "col_mi_label_sel", + "col_mi_label_float", + "col_mi_label_float_sel", + "col_mi_bg", + "col_mi_bg2", + "col_tr1_itembgsel", + "col_tr2_itembgsel", + "itembg_drawmode", + "col_tr1_peaks", + "col_tr2_peaks", + "col_tr1_ps2", + "col_tr2_ps2", + "col_peaksedge", + "col_peaksedge2", + "col_peaksedgesel", + "col_peaksedgesel2", + "cc_chase_drawmode", + "col_peaksfade", + "col_peaksfade2", + "col_mi_fades", + "fadezone_color", + "fadezone_drawmode", + "fadearea_color", + "fadearea_drawmode", + "col_mi_fade2", + "col_mi_fade2_drawmode", + "item_grouphl", + "col_offlinetext", + "col_stretchmarker", + "col_stretchmarker_h0", + "col_stretchmarker_h1", + "col_stretchmarker_h2", + "col_stretchmarker_b", + "col_stretchmarkerm", + "col_stretchmarker_text", + "col_stretchmarker_tm", + "take_marker", + "selitem_tag", + "activetake_tag", + "col_tr1_bg", + "col_tr2_bg", + "selcol_tr1_bg", + "selcol_tr2_bg", + "track_lane_tabcol", + "track_lanesolo_tabcol", + "track_lanesolo_text", + "track_lane_gutter", + "track_lane_gutter_drawmode", + "col_tr1_divline", + "col_tr2_divline", + "col_envlane1_divline", + "col_envlane2_divline", + "mute_overlay_col", + "mute_overlay_mode", + "inactive_take_overlay_col", + "inactive_take_overlay_mode", + "locked_overlay_col", + "locked_overlay_mode", + "marquee_fill", + "marquee_drawmode", + "marquee_outline", + "marqueezoom_fill", + "marqueezoom_drawmode", + "marqueezoom_outline", + "areasel_fill", + "areasel_drawmode", + "areasel_outline", + "areasel_outlinemode", + "linkedlane_fill", + "linkedlane_fillmode", + "linkedlane_outline", + "linkedlane_outlinemode", + "linkedlane_unsynced", + "linkedlane_unsynced_mode", + "col_cursor", + "col_cursor2", + "playcursor_color", + "playcursor_drawmode", + "col_gridlines2", + "col_gridlines2dm", + "col_gridlines3", + "col_gridlines3dm", + "col_gridlines", + "col_gridlines1dm", + "guideline_color", + "guideline_drawmode", + "region", + "region_lane_bg", + "region_lane_text", + "marker", + "marker_lane_bg", + "marker_lane_text", + "col_tsigmark", + "ts_lane_bg", + "ts_lane_text", + "timesig_sel_bg", + "col_routinghl1", + "col_routinghl2", + "col_routingact", + "col_vudoint", + "col_vuclip", + "col_vutop", + "col_vumid", + "col_vubot", + "col_vuintcol", + "vu_gr_bgcol", + "vu_gr_fgcol", + "col_vumidi", + "col_vuind1", + "col_vuind2", + "col_vuind3", + "col_vuind4", + "mcp_sends_normal", + "mcp_sends_muted", + "mcp_send_midihw", + "mcp_sends_levels", + "mcp_fx_normal", + "mcp_fx_bypassed", + "mcp_fx_offlined", + "mcp_fxparm_normal", + "mcp_fxparm_bypassed", + "mcp_fxparm_offlined", + "tcp_list_scrollbar", + "tcp_list_scrollbar_mode", + "tcp_list_scrollbar_mouseover", + "tcp_list_scrollbar_mouseover_mode", + "mcp_list_scrollbar", + "mcp_list_scrollbar_mode", + "mcp_list_scrollbar_mouseover", + "mcp_list_scrollbar_mouseover_mode", + "midi_rulerbg", + "midi_rulerfg", + "midi_grid2", + "midi_griddm2", + "midi_grid3", + "midi_griddm3", + "midi_grid1", + "midi_griddm1", + "midi_trackbg1", + "midi_trackbg2", + "midi_trackbg_outer1", + "midi_trackbg_outer2", + "midi_selpitch1", + "midi_selpitch2", + "midi_selbg", + "midi_selbg_drawmode", + "midi_gridhc", + "midi_gridhcdm", + "midi_gridh", + "midi_gridhdm", + "midi_ccbut", + "midi_ccbut_text", + "midi_ccbut_arrow", + "midioct", + "midi_inline_trackbg1", + "midi_inline_trackbg2", + "midioct_inline", + "midi_endpt", + "midi_notebg", + "midi_notefg", + "midi_notemute", + "midi_notemute_sel", + "midi_itemctl", + "midi_ofsn", + "midi_ofsnsel", + "midi_editcurs", + "midi_pkey1", + "midi_pkey2", + "midi_pkey3", + "midi_noteon_flash", + "midi_leftbg", + "midifont_col_light_unsel", + "midifont_col_dark_unsel", + "midifont_mode_unsel", + "midifont_col_light", + "midifont_col_dark", + "midifont_mode", + "score_bg", + "score_fg", + "score_sel", + "score_timesel", + "score_loop", + "midieditorlist_bg", + "midieditorlist_fg", + "midieditorlist_grid", + "midieditorlist_selbg", + "midieditorlist_selfg", + "midieditorlist_seliabg", + "midieditorlist_seliafg", + "midieditorlist_bg2", + "midieditorlist_fg2", + "midieditorlist_selbg2", + "midieditorlist_selfg2", + "col_explorer_sel", + "col_explorer_seldm", + "col_explorer_seledge", + "explorer_grid", + "explorer_pitchtext", + "docker_shadow", + "docker_selface", + "docker_unselface", + "docker_text", + "docker_text_sel", + "docker_bg", + "windowtab_bg", + "auto_item_unsel", + "col_env1", + "col_env2", + "env_trim_vol", + "col_env3", + "col_env4", + "env_track_mute", + "col_env5", + "col_env6", + "col_env7", + "col_env8", + "col_env9", + "col_env10", + "env_sends_mute", + "col_env11", + "col_env12", + "col_env13", + "col_env14", + "col_env15", + "col_env16", + "env_item_vol", + "env_item_pan", + "env_item_mute", + "env_item_pitch", + "wiring_grid2", + "wiring_grid", + "wiring_border", + "wiring_tbg", + "wiring_ticon", + "wiring_recbg", + "wiring_recitem", + "wiring_media", + "wiring_recv", + "wiring_send", + "wiring_fader", + "wiring_parent", + "wiring_parentwire_border", + "wiring_parentwire_master", + "wiring_parentwire_folder", + "wiring_pin_normal", + "wiring_pin_connected", + "wiring_pin_disconnected", + "wiring_horz_col", + "wiring_sendwire", + "wiring_hwoutwire", + "wiring_recinputwire", + "wiring_hwout", + "wiring_recinput", + "wiring_activity", + "autogroup", + "group_0", + "group_1", + "group_2", + "group_3", + "group_4", + "group_5", + "group_6", + "group_7", + "group_8", + "group_9", + "group_10", + "group_11", + "group_12", + "group_13", + "group_14", + "group_15", + "group_16", + "group_17", + "group_18", + "group_19", + "group_20", + "group_21", + "group_22", + "group_23", + "group_24", + "group_25", + "group_26", + "group_27", + "group_28", + "group_29", + "group_30", + "group_31", + "group_32", + "group_33", + "group_34", + "group_35", + "group_36", + "group_37", + "group_38", + "group_39", + "group_40", + "group_41", + "group_42", + "group_43", + "group_44", + "group_45", + "group_46", + "group_47", + "group_48", + "group_49", + "group_50", + "group_51", + "group_52", + "group_53", + "group_54", + "group_55", + "group_56", + "group_57", + "group_58", + "group_59", + "group_60", + "group_61", + "group_62", + "group_63", +]; diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/generated.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/generated.rs new file mode 100644 index 0000000..34e67e8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/generated.rs @@ -0,0 +1,6230 @@ +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Request { + /// This should be the instance ID of the app instance sending this request. + /// Although the instance ID is encoded in many of the actual requests below, having the + /// instance ID here at one place makes it easier for us to send error messages back to the correct app instance. + #[prost(uint32, tag = "1")] + pub instance_id: u32, + #[prost(oneof = "request::Value", tags = "2, 3")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `Request`. +pub mod request { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + #[prost(message, tag = "2")] + CommandRequest(super::CommandRequest), + #[prost(message, tag = "3")] + QueryRequest(super::QueryRequest), + } +} +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Reply { + #[prost(oneof = "reply::Value", tags = "1, 2")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `Reply`. +pub mod reply { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + #[prost(message, tag = "1")] + EventReply(super::EventReply), + #[prost(message, tag = "2")] + QueryReply(super::QueryReply), + } +} +/// Should contain all possible *command* requests (without return value) from above service. +/// +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CommandRequest { + #[prost( + oneof = "command_request::Value", + tags = "1, 54, 3, 4, 5, 52, 6, 7, 55, 8, 9, 10, 11, 50, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 37, 53, 40, 25, 26, 27, 34, 28, 29, 31, 32, 33, 35, 36, 38, 39, 41, 42, 43, 44, 51, 45, 46, 47, 48, 49" + )] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `CommandRequest`. +pub mod command_request { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + /// Embedding + #[prost(message, tag = "1")] + NotifyAppIsReady(super::NotifyAppIsReadyRequest), + /// This is necessary on Windows so that REAPER doesn't execute global shortcuts when the + /// App is in text entry mode. + #[prost(message, tag = "54")] + SetAppIsInTextEntryMode(super::SetAppIsInTextEntryModeRequest), + /// Normal commands + #[prost(message, tag = "3")] + TriggerMatrix(super::TriggerMatrixRequest), + #[prost(message, tag = "4")] + SetMatrixSettings(super::SetMatrixSettingsRequest), + #[prost(message, tag = "5")] + SetMatrixTempo(super::SetMatrixTempoRequest), + #[prost(message, tag = "52")] + SetMatrixPlayRate(super::SetMatrixPlayRateRequest), + #[prost(message, tag = "6")] + SetMatrixVolume(super::SetMatrixVolumeRequest), + #[prost(message, tag = "7")] + SetMatrixPan(super::SetMatrixPanRequest), + #[prost(message, tag = "55")] + SetMatrixClickChannel(super::SetMatrixClickChannelRequest), + #[prost(message, tag = "8")] + TriggerColumn(super::TriggerColumnRequest), + #[prost(message, tag = "9")] + SetColumnSettings(super::SetColumnSettingsRequest), + #[prost(message, tag = "10")] + SetTrackVolume(super::SetTrackVolumeRequest), + #[prost(message, tag = "11")] + SetTrackPan(super::SetTrackPanRequest), + #[prost(message, tag = "50")] + OpenTrackFx(super::OpenTrackFxRequest), + #[prost(message, tag = "12")] + SetColumnTrack(super::SetColumnTrackRequest), + #[prost(message, tag = "13")] + DragColumn(super::DragColumnRequest), + #[prost(message, tag = "14")] + SetTrackName(super::SetTrackNameRequest), + #[prost(message, tag = "15")] + SetTrackInput(super::SetTrackInputRequest), + #[prost(message, tag = "16")] + SetTrackInputMonitoring(super::SetTrackInputMonitoringRequest), + #[prost(message, tag = "17")] + TriggerRow(super::TriggerRowRequest), + #[prost(message, tag = "18")] + SetRowData(super::SetRowDataRequest), + #[prost(message, tag = "19")] + DragRow(super::DragRowRequest), + #[prost(message, tag = "20")] + TriggerSlot(super::TriggerSlotRequest), + #[prost(message, tag = "21")] + DragSlot(super::DragSlotRequest), + #[prost(message, tag = "22")] + TriggerClip(super::TriggerClipRequest), + #[prost(message, tag = "23")] + SetClipName(super::SetClipNameRequest), + #[prost(message, tag = "24")] + SetClipData(super::SetClipDataRequest), + /// Event re-subscription commands (only for occasional aggregate events, the rest will be sent anyway) + #[prost(message, tag = "37")] + GetOccasionalGlobalUpdates(super::GetOccasionalGlobalUpdatesRequest), + #[prost(message, tag = "53")] + GetOccasionalPlaytimeEngineUpdates(super::GetOccasionalPlaytimeEngineUpdatesRequest), + #[prost(message, tag = "40")] + GetOccasionalInstanceUpdates(super::GetOccasionalInstanceUpdatesRequest), + #[prost(message, tag = "25")] + GetOccasionalMatrixUpdates(super::GetOccasionalMatrixUpdatesRequest), + #[prost(message, tag = "26")] + GetOccasionalTrackUpdates(super::GetOccasionalTrackUpdatesRequest), + #[prost(message, tag = "27")] + GetOccasionalSlotUpdates(super::GetOccasionalSlotUpdatesRequest), + #[prost(message, tag = "34")] + GetOccasionalClipUpdates(super::GetOccasionalClipUpdatesRequest), + #[prost(message, tag = "28")] + TriggerTrack(super::TriggerTrackRequest), + #[prost(message, tag = "29")] + SetMatrixTimeSignature(super::SetMatrixTimeSignatureRequest), + #[prost(message, tag = "31")] + SetTrackColor(super::SetTrackColorRequest), + #[prost(message, tag = "32")] + DragClip(super::DragClipRequest), + #[prost(message, tag = "33")] + ImportFiles(super::ImportFilesRequest), + #[prost(message, tag = "35")] + TriggerSequence(super::TriggerSequenceRequest), + #[prost(message, tag = "36")] + SetSequenceInfo(super::SetSequenceInfoRequest), + #[prost(message, tag = "38")] + SaveController(super::SaveControllerRequest), + #[prost(message, tag = "39")] + DeleteController(super::DeleteControllerRequest), + #[prost(message, tag = "41")] + SetInstanceSettings(super::SetInstanceSettingsRequest), + #[prost(message, tag = "42")] + AddLicense(super::AddLicenseRequest), + #[prost(message, tag = "43")] + SetAppSettings(super::SetAppSettingsRequest), + #[prost(message, tag = "44")] + SaveCustomCompartmentData(super::SaveCustomCompartmentDataRequest), + #[prost(message, tag = "51")] + SetCustomInstanceData(super::SetCustomInstanceDataRequest), + #[prost(message, tag = "45")] + GetOccasionalUnitUpdates(super::GetOccasionalUnitUpdatesRequest), + #[prost(message, tag = "46")] + TriggerGlobal(super::TriggerGlobalRequest), + #[prost(message, tag = "47")] + InsertColumns(super::InsertColumnsRequest), + #[prost(message, tag = "48")] + TriggerInstance(super::TriggerInstanceRequest), + #[prost(message, tag = "49")] + SetPlaytimeEngineSettings(super::SetPlaytimeEngineSettingsRequest), + } +} +/// Envelope for queries. +/// +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryRequest { + /// / Second part of composite ID for being able to associate replies with requests. + /// / The first part is the instance ID. The second part is specific to queries, it's not + /// / necessary for commands. + #[prost(uint32, tag = "1")] + pub id: u32, + #[prost(message, optional, tag = "2")] + pub query: ::core::option::Option, +} +/// Should contain all possible *query* requests (with return value) from above service. +/// +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Query { + #[prost(oneof = "query::Value", tags = "1, 2, 3, 4, 5, 6, 7, 8")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `Query`. +pub mod query { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + #[prost(message, tag = "1")] + ProveAuthenticity(super::ProveAuthenticityRequest), + #[prost(message, tag = "2")] + GetClipDetail(super::GetClipDetailRequest), + #[prost(message, tag = "3")] + GetProjectDir(super::GetProjectDirRequest), + #[prost(message, tag = "4")] + GetHostInfo(super::GetHostInfoRequest), + #[prost(message, tag = "5")] + GetArrangementInfo(super::GetArrangementInfoRequest), + #[prost(message, tag = "6")] + GetAppSettings(super::GetAppSettingsRequest), + #[prost(message, tag = "7")] + GetCompartmentData(super::GetCompartmentDataRequest), + #[prost(message, tag = "8")] + GetCustomInstanceData(super::GetCustomInstanceDataRequest), + } +} +/// Envelope for query results. +/// +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryReply { + /// / ID for being able to associate replies with requests. + #[prost(uint32, tag = "1")] + pub id: u32, + #[prost(message, optional, tag = "2")] + pub result: ::core::option::Option, +} +/// Should contain all possible *query* results (with return value) from above service. +/// +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryResult { + #[prost(oneof = "query_result::Value", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `QueryResult`. +pub mod query_result { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + #[prost(string, tag = "1")] + Error(::prost::alloc::string::String), + #[prost(message, tag = "2")] + ProveAuthenticityReply(super::ProveAuthenticityReply), + #[prost(message, tag = "3")] + GetClipDetailReply(super::GetClipDetailReply), + #[prost(message, tag = "4")] + GetProjectDirReply(super::GetProjectDirReply), + #[prost(message, tag = "5")] + GetHostInfoReply(super::GetHostInfoReply), + #[prost(message, tag = "6")] + GetArrangementInfoReply(super::GetArrangementInfoReply), + #[prost(message, tag = "7")] + GetAppSettingsReply(super::GetAppSettingsReply), + #[prost(message, tag = "8")] + GetCompartmentDataReply(super::GetCompartmentDataReply), + #[prost(message, tag = "9")] + GetCustomInstanceDataReply(super::GetCustomInstanceDataReply), + } +} +/// Should contain all possible *event* replies from above service. +/// +/// Only necessary for in-process communication (without gRPC). +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EventReply { + #[prost( + oneof = "event_reply::Value", + tags = "1, 15, 13, 14, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12" + )] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `EventReply`. +pub mod event_reply { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + /// Normal events + #[prost(message, tag = "1")] + OccasionalGlobalUpdatesReply(super::GetOccasionalGlobalUpdatesReply), + #[prost(message, tag = "15")] + OccasionalPlaytimeEngineUpdatesReply(super::GetOccasionalPlaytimeEngineUpdatesReply), + #[prost(message, tag = "13")] + OccasionalInstanceUpdatesReply(super::GetOccasionalInstanceUpdatesReply), + #[prost(message, tag = "14")] + OccasionalUnitUpdatesReply(super::GetOccasionalUnitUpdatesReply), + #[prost(message, tag = "2")] + OccasionalMatrixUpdatesReply(super::GetOccasionalMatrixUpdatesReply), + #[prost(message, tag = "3")] + ContinuousMatrixUpdatesReply(super::GetContinuousMatrixUpdatesReply), + #[prost(message, tag = "4")] + OccasionalColumnUpdatesReply(super::GetOccasionalColumnUpdatesReply), + #[prost(message, tag = "5")] + ContinuousColumnUpdatesReply(super::GetContinuousColumnUpdatesReply), + #[prost(message, tag = "6")] + OccasionalTrackUpdatesReply(super::GetOccasionalTrackUpdatesReply), + #[prost(message, tag = "7")] + OccasionalRowUpdatesReply(super::GetOccasionalRowUpdatesReply), + #[prost(message, tag = "8")] + OccasionalSlotUpdatesReply(super::GetOccasionalSlotUpdatesReply), + #[prost(message, tag = "9")] + ContinuousSlotUpdatesReply(super::GetContinuousSlotUpdatesReply), + #[prost(message, tag = "10")] + OccasionalClipUpdatesReply(super::GetOccasionalClipUpdatesReply), + /// Embedding + /// This should be interpreted as an instruction to refetch all state again. + #[prost(message, tag = "11")] + Reset(super::Empty), + /// A generic error message for the user used for returning errors that occur when processing a command + /// (and ideally should not occur). When using gRPC, this is done using the typical gRPC error status method instead. + #[prost(string, tag = "12")] + ErrorMessage(::prost::alloc::string::String), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullColumnAddress { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(uint32, tag = "2")] + pub column_index: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullTrackAddress { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(string, tag = "2")] + pub track_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullRowAddress { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(uint32, tag = "2")] + pub row_index: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullSlotAddress { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(message, optional, tag = "2")] + pub slot_address: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullSequenceId { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(string, tag = "2")] + pub sequence_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullClipId { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(string, tag = "2")] + pub clip_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullClipAddress { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(message, optional, tag = "2")] + pub clip_address: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClipAddress { + #[prost(message, optional, tag = "1")] + pub slot_address: ::core::option::Option, + #[prost(uint32, tag = "2")] + pub clip_index: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CellAddress { + #[prost(uint32, optional, tag = "1")] + pub column_index: ::core::option::Option, + #[prost(uint32, optional, tag = "2")] + pub row_index: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SlotAddress { + #[prost(uint32, tag = "1")] + pub column_index: u32, + #[prost(uint32, tag = "2")] + pub row_index: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetMatrixTempoRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(double, tag = "2")] + pub bpm: f64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetMatrixPlayRateRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + /// Unit: Playback speed factor + #[prost(double, tag = "2")] + pub play_rate: f64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetMatrixTimeSignatureRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(message, optional, tag = "2")] + pub time_signature: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetMatrixVolumeRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(enumeration = "MatrixVolumeKind", tag = "2")] + pub kind: i32, + #[prost(double, tag = "3")] + pub db: f64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetMatrixPanRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(double, tag = "2")] + pub pan: f64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetMatrixClickChannelRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(uint32, tag = "2")] + pub channel: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackVolumeRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(double, tag = "2")] + pub db: f64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackNameRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackColorRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub color: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackPanRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(double, tag = "2")] + pub pan: f64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OpenTrackFxRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(uint32, tag = "2")] + pub fx_index: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetColumnTrackRequest { + #[prost(message, optional, tag = "1")] + pub column_address: ::core::option::Option, + #[prost(string, optional, tag = "2")] + pub track_id: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackInputMonitoringRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(enumeration = "TrackInputMonitoring", tag = "2")] + pub input_monitoring: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackInputRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub input: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Empty {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AddLicenseRequest { + #[prost(string, tag = "1")] + pub license_key: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SaveControllerRequest { + /// Controller as JSON + #[prost(string, tag = "1")] + pub controller: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteControllerRequest { + #[prost(string, tag = "1")] + pub controller_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerGlobalRequest { + #[prost(enumeration = "TriggerGlobalAction", tag = "1")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerInstanceRequest { + #[prost(uint32, tag = "1")] + pub instance_id: u32, + #[prost(enumeration = "TriggerInstanceAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerMatrixRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(enumeration = "TriggerMatrixAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetMatrixSettingsRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + /// Matrix settings as JSON + #[prost(string, tag = "2")] + pub settings: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NotifyAppIsReadyRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(uint64, tag = "2")] + pub app_callback_address: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetAppIsInTextEntryModeRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(bool, tag = "2")] + pub is_in_text_entry_mode: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetHostInfoRequest {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetHostInfoReply { + /// The current user-facing semantic version of the host (increased frequently). + #[prost(string, tag = "1")] + pub public_version: ::prost::alloc::string::String, + /// The internal API version of the host (increased less frequently). + #[prost(string, tag = "2")] + pub api_version: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAppSettingsRequest {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCustomInstanceDataReply { + /// Custom instance data as JSON + #[prost(string, optional, tag = "1")] + pub data: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAppSettingsReply { + /// App settings as JSON + #[prost(string, optional, tag = "1")] + pub app_settings: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetAppSettingsRequest { + /// App settings as JSON + #[prost(string, tag = "1")] + pub app_settings: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetPlaytimeEngineSettingsRequest { + /// Settings as JSON + #[prost(string, tag = "1")] + pub settings: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProveAuthenticityRequest { + #[prost(bytes = "vec", tag = "1")] + pub challenge: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetProjectDirRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetArrangementInfoRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProveAuthenticityReply { + #[prost(bytes = "vec", tag = "1")] + pub signature: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetProjectDirReply { + #[prost(string, tag = "1")] + pub project_dir: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetArrangementInfoReply { + #[prost(bool, tag = "1")] + pub clean: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TrackList { + #[prost(message, repeated, tag = "1")] + pub tracks: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerTrackRequest { + #[prost(message, optional, tag = "1")] + pub track_address: ::core::option::Option, + #[prost(enumeration = "TriggerTrackAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerColumnRequest { + #[prost(message, optional, tag = "1")] + pub column_address: ::core::option::Option, + #[prost(enumeration = "TriggerColumnAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InsertColumnsRequest { + #[prost(message, optional, tag = "1")] + pub column_address: ::core::option::Option, + #[prost(uint32, tag = "2")] + pub count: u32, + #[prost(enumeration = "ColumnKind", tag = "3")] + pub kind: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetColumnSettingsRequest { + #[prost(message, optional, tag = "1")] + pub column_address: ::core::option::Option, + /// Column settings as JSON + #[prost(string, tag = "2")] + pub settings: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetInstanceSettingsRequest { + #[prost(uint32, tag = "1")] + pub instance_id: u32, + /// Instance settings as JSON + #[prost(string, tag = "2")] + pub settings: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCustomInstanceDataRequest { + #[prost(uint32, tag = "1")] + pub instance_id: u32, + /// The custom-data key, e.g. "app". + #[prost(string, tag = "2")] + pub custom_key: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetCustomInstanceDataRequest { + #[prost(uint32, tag = "1")] + pub instance_id: u32, + /// The custom-data key, e.g. "app". + #[prost(string, tag = "2")] + pub custom_key: ::prost::alloc::string::String, + /// Custom instance data as JSON. + #[prost(string, tag = "3")] + pub custom_data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SaveCustomCompartmentDataRequest { + #[prost(message, optional, tag = "1")] + pub compartment_id: ::core::option::Option, + /// The custom-data key, e.g. "companion" or "playtime". + #[prost(string, tag = "2")] + pub custom_key: ::prost::alloc::string::String, + /// Custom compartment data as JSON. + #[prost(string, tag = "3")] + pub custom_data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullCompartmentId { + #[prost(uint32, tag = "1")] + pub instance_id: u32, + #[prost(uint32, tag = "2")] + pub unit_id: u32, + #[prost(enumeration = "Compartment", tag = "3")] + pub compartment: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerRowRequest { + #[prost(message, optional, tag = "1")] + pub row_address: ::core::option::Option, + #[prost(enumeration = "TriggerRowAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetRowDataRequest { + #[prost(message, optional, tag = "1")] + pub row_address: ::core::option::Option, + /// Row data as JSON + #[prost(string, tag = "2")] + pub data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DragColumnRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(uint32, tag = "2")] + pub source_column_index: u32, + #[prost(uint32, tag = "3")] + pub destination_column_index: u32, + #[prost(enumeration = "DragColumnAction", tag = "4")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DragRowRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(uint32, tag = "2")] + pub source_row_index: u32, + #[prost(uint32, tag = "3")] + pub destination_row_index: u32, + #[prost(enumeration = "DragRowAction", tag = "4")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerSlotRequest { + #[prost(message, optional, tag = "1")] + pub slot_address: ::core::option::Option, + #[prost(enumeration = "TriggerSlotAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerClipRequest { + #[prost(message, optional, tag = "1")] + pub clip_address: ::core::option::Option, + #[prost(enumeration = "TriggerClipAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TriggerSequenceRequest { + #[prost(message, optional, tag = "1")] + pub sequence_id: ::core::option::Option, + #[prost(enumeration = "TriggerSequenceAction", tag = "2")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DragSlotRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(message, optional, tag = "2")] + pub source_slot_address: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub destination_slot_address: ::core::option::Option, + #[prost(enumeration = "DragSlotAction", tag = "4")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ImportFilesRequest { + #[prost(message, optional, tag = "1")] + pub slot_address: ::core::option::Option, + #[prost(string, repeated, tag = "2")] + pub files: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DragClipRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, + #[prost(message, optional, tag = "2")] + pub source_clip_address: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub destination_slot_address: ::core::option::Option, + #[prost(enumeration = "DragClipAction", tag = "4")] + pub action: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetClipNameRequest { + #[prost(message, optional, tag = "1")] + pub clip_address: ::core::option::Option, + #[prost(string, optional, tag = "2")] + pub name: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetClipDataRequest { + #[prost(message, optional, tag = "1")] + pub clip_address: ::core::option::Option, + /// Clip data as JSON + #[prost(string, tag = "2")] + pub data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetSequenceInfoRequest { + #[prost(message, optional, tag = "1")] + pub sequence_id: ::core::option::Option, + /// Sequence info as JSON + #[prost(string, tag = "2")] + pub data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCompartmentDataRequest { + #[prost(message, optional, tag = "1")] + pub compartment_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetClipDetailRequest { + #[prost(message, optional, tag = "1")] + pub clip_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCompartmentDataReply { + /// Compartment data as JSON + #[prost(string, tag = "1")] + pub data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetClipDetailReply { + #[prost(bytes = "vec", optional, tag = "1")] + pub rea_peaks: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalGlobalUpdatesRequest {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalPlaytimeEngineUpdatesRequest {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalInstanceUpdatesRequest { + #[prost(uint32, tag = "1")] + pub instance_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalUnitUpdatesRequest { + #[prost(uint32, tag = "1")] + pub instance_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalMatrixUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalTrackUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalSlotUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalClipUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetContinuousMatrixUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalColumnUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalRowUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetContinuousColumnUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetContinuousSlotUpdatesRequest { + #[prost(uint32, tag = "1")] + pub matrix_id: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalGlobalUpdatesReply { + /// For each global updated property + #[prost(message, repeated, tag = "1")] + pub global_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalPlaytimeEngineUpdatesReply { + #[prost(message, repeated, tag = "1")] + pub updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalInstanceUpdatesReply { + /// For each updated instance property + #[prost(message, repeated, tag = "1")] + pub instance_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OccasionalInstanceUpdate { + #[prost(oneof = "occasional_instance_update::Update", tags = "1, 2, 3, 4, 5")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `OccasionalInstanceUpdate`. +pub mod occasional_instance_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Settings data as JSON. + #[prost(string, tag = "1")] + Settings(::prost::alloc::string::String), + /// The set of units within the instance has changed (added or removed). + #[prost(message, tag = "2")] + Units(super::Units), + /// Everything within the instance has changed (e.g. instance data load). + #[prost(bool, tag = "3")] + EverythingHasChanged(bool), + /// Info event as JSON. Usually shortly displayed as toast or snack bar. + #[prost(string, tag = "4")] + InfoEvent(::prost::alloc::string::String), + /// Warnings to be displayed permanently. + #[prost(message, tag = "5")] + Warnings(super::Warnings), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Warnings { + #[prost(message, repeated, tag = "1")] + pub warnings: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Warning { + #[prost(enumeration = "Severity", tag = "1")] + pub severity: i32, + #[prost(string, tag = "2")] + pub message: ::prost::alloc::string::String, + #[prost(enumeration = "Scope", optional, tag = "3")] + pub scope: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalUnitUpdatesReply { + /// For each updated unit property + #[prost(message, repeated, tag = "1")] + pub unit_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QualifiedOccasionalUnitUpdate { + #[prost(uint32, tag = "1")] + pub unit_id: u32, + #[prost(oneof = "qualified_occasional_unit_update::Update", tags = "2, 3")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `QualifiedOccasionalUnitUpdate`. +pub mod qualified_occasional_unit_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Everything within the unit has changed (e.g. unit data load or replacing compartment). + #[prost(bool, tag = "2")] + EverythingHasChanged(bool), + /// Updated controller routing (for projection). + #[prost(string, tag = "3")] + ControllerRouting(::prost::alloc::string::String), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Units { + #[prost(message, repeated, tag = "1")] + pub units: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Unit { + #[prost(uint32, tag = "1")] + pub id: u32, + #[prost(string, tag = "2")] + pub key: ::prost::alloc::string::String, + #[prost(string, optional, tag = "3")] + pub name: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalMatrixUpdatesReply { + /// For each updated matrix property + #[prost(message, repeated, tag = "1")] + pub matrix_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QualifiedOccasionalColumnUpdate { + #[prost(uint32, tag = "1")] + pub column_index: u32, + #[prost(oneof = "qualified_occasional_column_update::Update", tags = "2")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `QualifiedOccasionalColumnUpdate`. +pub mod qualified_occasional_column_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Column settings as JSON + #[prost(string, tag = "2")] + Settings(::prost::alloc::string::String), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QualifiedOccasionalRowUpdate { + #[prost(uint32, tag = "1")] + pub row_index: u32, + #[prost(oneof = "qualified_occasional_row_update::Update", tags = "2")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `QualifiedOccasionalRowUpdate`. +pub mod qualified_occasional_row_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Row data as JSON + #[prost(string, tag = "2")] + Data(::prost::alloc::string::String), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalTrackUpdatesReply { + /// For each updated column track + #[prost(message, repeated, tag = "1")] + pub track_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalSlotUpdatesReply { + /// For each updated slot AND slot property + #[prost(message, repeated, tag = "1")] + pub slot_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalClipUpdatesReply { + /// For each updated clip AND clip property + #[prost(message, repeated, tag = "1")] + pub clip_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetContinuousMatrixUpdatesReply { + #[prost(message, optional, tag = "1")] + pub matrix_update: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalColumnUpdatesReply { + /// For each updated column property + #[prost(message, repeated, tag = "1")] + pub column_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOccasionalRowUpdatesReply { + /// For each updated row property + #[prost(message, repeated, tag = "1")] + pub row_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetContinuousColumnUpdatesReply { + /// For each column + #[prost(message, repeated, tag = "1")] + pub column_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetContinuousSlotUpdatesReply { + /// For each updated slot + #[prost(message, repeated, tag = "1")] + pub slot_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ContinuousMatrixUpdate { + #[prost(double, tag = "1")] + pub second: f64, + #[prost(sint32, tag = "2")] + pub bar: i32, + #[prost(double, tag = "3")] + pub beat: f64, + /// Numbers between 0 and 1. + #[prost(double, repeated, tag = "4")] + pub peaks: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ContinuousColumnUpdate { + /// Numbers between 0 and 1. + #[prost(double, repeated, tag = "1")] + pub peaks: ::prost::alloc::vec::Vec, + /// / Current play lookahead in seconds (due to PDC primarily). + #[prost(double, tag = "2")] + pub play_lookahead: f64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QualifiedContinuousSlotUpdate { + #[prost(message, optional, tag = "1")] + pub slot_address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub update: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QualifiedOccasionalTrackUpdate { + #[prost(string, tag = "1")] + pub track_id: ::prost::alloc::string::String, + /// For each updated track property + #[prost(message, repeated, tag = "2")] + pub track_updates: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OccasionalPlaytimeEngineUpdate { + #[prost(oneof = "occasional_playtime_engine_update::Update", tags = "1, 2, 3")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `OccasionalPlaytimeEngineUpdate`. +pub mod occasional_playtime_engine_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Engine settings as JSON + #[prost(string, tag = "1")] + EngineSettings(::prost::alloc::string::String), + /// Contains the license state of Playtime. + #[prost(message, tag = "2")] + LicenseState(super::LicenseState), + /// Engine stats. + #[prost(message, tag = "3")] + EngineStats(super::PlaytimeEngineStats), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PlaytimeEngineStats { + #[prost(uint32, tag = "1")] + pub min_buffered_blocks: u32, + #[prost(uint32, tag = "2")] + pub avg_buffered_blocks: u32, + #[prost(uint32, tag = "3")] + pub max_buffered_blocks: u32, + #[prost(uint32, tag = "4")] + pub future_size_in_blocks: u32, + #[prost(uint32, tag = "6")] + pub num_pre_buffer_fallbacks: u32, + #[prost(uint32, tag = "5")] + pub num_pre_buffer_misses: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OccasionalGlobalUpdate { + #[prost( + oneof = "occasional_global_update::Update", + tags = "1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16" + )] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `OccasionalGlobalUpdate`. +pub mod occasional_global_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Global controller config as JSON + #[prost(string, tag = "1")] + ControllerConfig(::prost::alloc::string::String), + /// MIDI input devices (= REAPER MIDI input devices) + #[prost(message, tag = "2")] + MidiInputDevices(super::MidiInputDevices), + /// MIDI output devices (= REAPER MIDI output devices) + #[prost(message, tag = "3")] + MidiOutputDevices(super::MidiOutputDevices), + /// Controller presets as JSON + #[prost(string, tag = "4")] + ControllerPresets(::prost::alloc::string::String), + /// Main presets as JSON + #[prost(string, tag = "5")] + MainPresets(::prost::alloc::string::String), + /// Audio input channels (= REAPER hardware input channels) + #[prost(message, tag = "6")] + AudioInputChannels(super::AudioInputChannels), + /// Info event as JSON. + #[prost(string, tag = "7")] + InfoEvent(::prost::alloc::string::String), + /// License info as JSON. + #[prost(string, tag = "8")] + LicenseInfo(::prost::alloc::string::String), + /// Arrangement play state (= REAPER transport play state) + #[prost(enumeration = "super::ArrangementPlayState", tag = "10")] + ArrangementPlayState(i32), + /// Requests the app to change the location (route). + #[prost(string, tag = "11")] + GoToLocation(::prost::alloc::string::String), + /// Color scheme of the host. + #[prost(message, tag = "12")] + HostColorScheme(super::HostColorScheme), + /// Resample modes. + #[prost(message, tag = "13")] + ResampleModes(super::ResampleModes), + /// Pitch-shift modes. + #[prost(message, tag = "14")] + PitchShiftModes(super::PitchShiftModes), + /// Helgobox instances. + #[prost(message, tag = "15")] + Instances(super::HelgoboxInstances), + /// Update information. + #[prost(message, tag = "16")] + UpdateInfo(super::UpdateInfo), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateInfo { + /// (Possibly new) latest available host version (user-facing semantic version). + #[prost(string, tag = "1")] + pub latest_host_version: ::prost::alloc::string::String, + /// (Possibly new) latest available app version (user-facing semantic version). + #[prost(string, tag = "2")] + pub latest_app_version: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HostColorScheme { + #[prost(map = "string, message", tag = "1")] + pub colors: ::std::collections::HashMap<::prost::alloc::string::String, RgbColor>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LicenseState { + /// If there's a valid license, this contains the license payload as JSON, otherwise not set. + #[prost(string, optional, tag = "1")] + pub license_payload: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OccasionalMatrixUpdate { + #[prost( + oneof = "occasional_matrix_update::Update", + tags = "1, 2, 3, 27, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 28, 24, 25, 26" + )] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `OccasionalMatrixUpdate`. +pub mod occasional_matrix_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Matrix volume (= REAPER master track volume) + #[prost(double, tag = "1")] + MasterVolume(f64), + /// Matrix pan (= REAPER master track pan) + #[prost(double, tag = "2")] + Pan(f64), + /// Matrix tempo (= REAPER master tempo) + #[prost(double, tag = "3")] + Tempo(f64), + /// Matrix play rate (= REAPER master play rate) + /// Unit: Playback speed factor + #[prost(double, tag = "27")] + PlayRate(f64), + /// Complete persistent data of the matrix has changed, including topology and other settings! + /// This contains the complete persistent matrix as JSON. + /// + /// This is ONLY sent as initial event. Whenever "everything_has_changed" is fired, this + /// should probably be refetched by the client by resubscribing to occasional matrix updates. + #[prost(string, tag = "7")] + CompletePersistentData(::prost::alloc::string::String), + /// Playtime matrix history state + #[prost(message, tag = "8")] + HistoryState(super::HistoryState), + /// Click on/off + #[prost(bool, tag = "9")] + ClickEnabled(bool), + /// Time signature (= REAPER master time signature) + #[prost(message, tag = "10")] + TimeSignature(super::TimeSignature), + /// Settings data as JSON. + #[prost(string, tag = "11")] + Settings(::prost::alloc::string::String), + /// Matrix mute on/off + #[prost(bool, tag = "12")] + Mute(bool), + /// List of all tracks + #[prost(message, tag = "13")] + TrackList(super::TrackList), + /// Whether matrix exists at all + #[prost(bool, tag = "14")] + MatrixExists(bool), + /// Whether silence mode is active (= Playtime is stopped) + #[prost(bool, tag = "15")] + SilenceMode(bool), + /// Sequencer play state (= transport state of matrix sequencer) + #[prost(enumeration = "super::SequencerPlayState", tag = "16")] + SequencerPlayState(i32), + /// Sequencer as JSON. + #[prost(string, tag = "17")] + Sequencer(::prost::alloc::string::String), + /// Info event as JSON. + #[prost(string, tag = "18")] + InfoEvent(::prost::alloc::string::String), + /// Simple mappings as JSON. + #[prost(string, tag = "19")] + SimpleMappingContainer(::prost::alloc::string::String), + /// Learn state + #[prost(message, tag = "20")] + LearnState(super::LearnState), + /// Active cell + #[prost(message, tag = "21")] + ActiveCell(super::CellAddress), + /// Control units as JSON. + #[prost(string, tag = "22")] + ControlUnitConfig(::prost::alloc::string::String), + /// Metronome volume + #[prost(double, tag = "23")] + ClickVolume(f64), + /// Metronome output channel + #[prost(uint32, tag = "28")] + ClickChannel(u32), + /// Volume of the temp-tap sound + #[prost(double, tag = "24")] + TempoTapVolume(f64), + /// This is true if Playtime attempted to load a non-empty matrix but ultimately didn't + /// load it because it's not licensed. + #[prost(bool, tag = "25")] + HasUnloadedContent(bool), + /// This is sent whenever the complete matrix has changed but NOT as initial event. The client can decide itself + /// which streams to resubscribe to. Previously we sent complete_persistent_data instead and the + /// client decided what to resubscribe. However, this is not just sent on changes but also + /// initially, which is a problem if the client wants to resubscribe to occasional matrix updates + /// itself - he would run into a infinite loop. + #[prost(bool, tag = "26")] + EverythingHasChanged(bool), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LearnState { + /// If not set, it means that no target is learning. + #[prost(string, optional, tag = "1")] + pub simple_mapping_target: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HistoryState { + #[prost(string, tag = "1")] + pub undo_label: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub redo_label: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TimeSignature { + #[prost(uint32, tag = "1")] + pub numerator: u32, + #[prost(uint32, tag = "2")] + pub denominator: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OccasionalTrackUpdate { + #[prost( + oneof = "occasional_track_update::Update", + tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11" + )] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `OccasionalTrackUpdate`. +pub mod occasional_track_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Track name + #[prost(string, tag = "1")] + Name(::prost::alloc::string::String), + /// Track color + #[prost(message, tag = "2")] + Color(super::RgbColor), + /// Track recording input + #[prost(message, tag = "3")] + Input(super::TrackInput), + /// Track record-arm on/off + #[prost(bool, tag = "4")] + Armed(bool), + /// Track recording input monitoring setting + #[prost(enumeration = "super::TrackInputMonitoring", tag = "5")] + InputMonitoring(i32), + /// Track mute on/off + #[prost(bool, tag = "6")] + Mute(bool), + /// Track solo on/off + #[prost(bool, tag = "7")] + Solo(bool), + /// Track selected or not + #[prost(bool, tag = "8")] + Selected(bool), + /// Track volume + #[prost(double, tag = "9")] + Volume(f64), + /// Track pan + #[prost(double, tag = "10")] + Pan(f64), + /// Normal FX chain + #[prost(message, tag = "11")] + NormalFxChain(super::FxChain), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FxChain { + #[prost(message, repeated, tag = "1")] + pub fxs: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Fx { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(bool, tag = "2")] + pub instrument: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RgbColor { + #[prost(int32, optional, tag = "1")] + pub color: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TrackInput { + #[prost(oneof = "track_input::Input", tags = "1, 2, 4, 5, 3")] + pub input: ::core::option::Option, +} +/// Nested message and enum types in `TrackInput`. +pub mod track_input { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Input { + #[prost(uint32, tag = "1")] + Mono(u32), + #[prost(uint32, tag = "2")] + Stereo(u32), + #[prost(uint32, tag = "4")] + LoopbackMono(u32), + #[prost(uint32, tag = "5")] + LoopbackStereo(u32), + #[prost(message, tag = "3")] + Midi(super::TrackMidiInput), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TrackMidiInput { + #[prost(uint32, optional, tag = "1")] + pub device: ::core::option::Option, + #[prost(uint32, optional, tag = "2")] + pub channel: ::core::option::Option, + #[prost(uint32, optional, tag = "3")] + pub destination_channel: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HelgoboxInstances { + #[prost(message, repeated, tag = "1")] + pub instances: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HelgoboxInstance { + #[prost(message, optional, tag = "1")] + pub data: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub fx: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HelgoboxInstanceData { + /// Instance ID (generated, immutable during runtime, not persistent) + #[prost(uint32, tag = "1")] + pub id: u32, + /// Instance key (user-defined, therefore mutable, persistent) + #[prost(string, tag = "2")] + pub key: ::prost::alloc::string::String, + /// Instance tags + #[prost(string, repeated, tag = "3")] + pub tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FxLocationInfo { + /// Location of the containing FX chain. + #[prost(message, optional, tag = "1")] + pub fx_chain: ::core::option::Option, + /// Unique ID of the FX. + #[prost(string, tag = "2")] + pub id: ::prost::alloc::string::String, + /// Index of FX within chain. + #[prost(uint32, tag = "3")] + pub index: u32, + /// FX name. + #[prost(string, tag = "4")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FxChainLocationInfo { + #[prost(oneof = "fx_chain_location_info::Location", tags = "1, 2")] + pub location: ::core::option::Option, +} +/// Nested message and enum types in `FxChainLocationInfo`. +pub mod fx_chain_location_info { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Location { + #[prost(message, tag = "1")] + TrackFx(super::TrackFxChainLocationInfo), + #[prost(message, tag = "2")] + MonitoringFx(super::Empty), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TrackFxChainLocationInfo { + /// Location of the containing track. + #[prost(message, optional, tag = "1")] + pub track: ::core::option::Option, + /// Whether it's the normal or input FX chain. + #[prost(bool, tag = "2")] + pub input_fx: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TrackLocationInfo { + /// Location of the containing project. + #[prost(message, optional, tag = "1")] + pub project: ::core::option::Option, + /// Unique ID of the track. + #[prost(string, tag = "2")] + pub id: ::prost::alloc::string::String, + /// Index of the track within the project. + #[prost(uint32, tag = "3")] + pub index: u32, + /// Track name. + #[prost(string, tag = "4")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProjectLocationInfo { + /// Index within the currently open project tabs. + #[prost(uint32, tag = "1")] + pub index: u32, + /// Project path (if saved). + #[prost(string, optional, tag = "2")] + pub path: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MidiInputDevices { + #[prost(message, repeated, tag = "1")] + pub devices: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MidiInputDevice { + #[prost(uint32, tag = "1")] + pub id: u32, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(enumeration = "MidiDeviceStatus", tag = "3")] + pub status: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MidiOutputDevices { + #[prost(message, repeated, tag = "1")] + pub devices: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MidiOutputDevice { + #[prost(uint32, tag = "1")] + pub id: u32, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(enumeration = "MidiDeviceStatus", tag = "3")] + pub status: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TrackInList { + #[prost(string, tag = "1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(uint32, tag = "3")] + pub level: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AudioInputChannels { + #[prost(message, repeated, tag = "1")] + pub channels: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AudioInputChannel { + #[prost(uint32, tag = "1")] + pub index: u32, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResampleModes { + #[prost(message, repeated, tag = "1")] + pub modes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResampleMode { + #[prost(uint32, tag = "1")] + pub index: u32, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PitchShiftModes { + #[prost(message, repeated, tag = "1")] + pub modes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PitchShiftMode { + #[prost(uint32, tag = "1")] + pub index: u32, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "3")] + pub sub_modes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PitchShiftSubMode { + #[prost(uint32, tag = "1")] + pub index: u32, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QualifiedOccasionalSlotUpdate { + #[prost(message, optional, tag = "1")] + pub slot_address: ::core::option::Option, + #[prost(oneof = "qualified_occasional_slot_update::Update", tags = "2, 3")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `QualifiedOccasionalSlotUpdate`. +pub mod qualified_occasional_slot_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// Slot play state + #[prost(enumeration = "super::SlotPlayState", tag = "2")] + PlayState(i32), + /// The complete persistent data of this slot has changed, that's mainly the + /// list of clips and their contents. This contains the complete slot as JSON. + #[prost(string, tag = "3")] + CompletePersistentData(::prost::alloc::string::String), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QualifiedOccasionalClipUpdate { + #[prost(message, optional, tag = "1")] + pub clip_address: ::core::option::Option, + #[prost(oneof = "qualified_occasional_clip_update::Update", tags = "2, 3")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `QualifiedOccasionalClipUpdate`. +pub mod qualified_occasional_clip_update { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// The complete persistent data of this clip has changed, e.g. its name. + /// This contains the complete clip as JSON. + #[prost(string, tag = "2")] + CompletePersistentData(::prost::alloc::string::String), + #[prost(message, tag = "3")] + ContentInfo(super::ClipContentInfo), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClipContentInfo { + #[prost(oneof = "clip_content_info::Info", tags = "1, 2")] + pub info: ::core::option::Option, +} +/// Nested message and enum types in `ClipContentInfo`. +pub mod clip_content_info { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Info { + #[prost(message, tag = "1")] + Midi(super::MidiClipContentInfo), + #[prost(message, tag = "2")] + Audio(super::AudioClipContentInfo), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MidiClipContentInfo { + #[prost(bool, tag = "1")] + pub quantized: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AudioClipContentInfo { + #[prost(bool, tag = "1")] + pub online: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ContinuousSlotUpdate { + /// For each clip in the slot + #[prost(message, repeated, tag = "1")] + pub clip_update: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ContinuousClipUpdate { + /// Number between 0 and 1, to be interpreted as percentage within the playable portion (e.g. within section bounds). + /// + /// Perfect for a simple clip progress visualization. + #[prost(double, tag = "1")] + pub proportional_position: f64, + /// Position within the playable portion in seconds. + /// + /// This will be negative during the count-in phase, which makes it suitable for building a count-down. + #[prost(double, tag = "2")] + pub position_in_seconds: f64, + /// Position within the inner-most source in frames (sample-rate dependent). + /// + /// If the section length exceeds the source end, this number can be larger than the source length. + /// + /// Suitable for indicating the position in a waveform editor. + #[prost(int64, tag = "3")] + pub source_position_in_frames: i64, + /// Like `source_position_in_frames` but driven by calculations instead of the actual position. + /// + /// Primarily a tool for debugging, to check whether actual position doesn't deviate too + /// much from the hypothetical one. + #[prost(int64, tag = "4")] + pub hypothetical_source_position_in_frames: i64, + /// Number between 0 and 1. + #[prost(double, tag = "5")] + pub peak: f64, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum MatrixVolumeKind { + Master = 0, + Click = 1, + TempoTap = 2, +} +impl MatrixVolumeKind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + MatrixVolumeKind::Master => "MATRIX_VOLUME_KIND_MASTER", + MatrixVolumeKind::Click => "MATRIX_VOLUME_KIND_CLICK", + MatrixVolumeKind::TempoTap => "MATRIX_VOLUME_KIND_TEMPO_TAP", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "MATRIX_VOLUME_KIND_MASTER" => Some(Self::Master), + "MATRIX_VOLUME_KIND_CLICK" => Some(Self::Click), + "MATRIX_VOLUME_KIND_TEMPO_TAP" => Some(Self::TempoTap), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerInstanceAction { + ShowHelgoboxPlugin = 0, + ArrangementTogglePlayStop = 1, + ArrangementPlay = 2, + ArrangementStop = 3, + ArrangementPause = 4, + ArrangementStartRecording = 5, + ArrangementStopRecording = 6, + CloseApp = 7, + HideApp = 8, + SaveProject = 9, +} +impl TriggerInstanceAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerInstanceAction::ShowHelgoboxPlugin => { + "TRIGGER_INSTANCE_ACTION_SHOW_HELGOBOX_PLUGIN" + } + TriggerInstanceAction::ArrangementTogglePlayStop => { + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_TOGGLE_PLAY_STOP" + } + TriggerInstanceAction::ArrangementPlay => "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_PLAY", + TriggerInstanceAction::ArrangementStop => "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_STOP", + TriggerInstanceAction::ArrangementPause => "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_PAUSE", + TriggerInstanceAction::ArrangementStartRecording => { + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_START_RECORDING" + } + TriggerInstanceAction::ArrangementStopRecording => { + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_STOP_RECORDING" + } + TriggerInstanceAction::CloseApp => "TRIGGER_INSTANCE_ACTION_CLOSE_APP", + TriggerInstanceAction::HideApp => "TRIGGER_INSTANCE_ACTION_HIDE_APP", + TriggerInstanceAction::SaveProject => "TRIGGER_INSTANCE_ACTION_SAVE_PROJECT", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_INSTANCE_ACTION_SHOW_HELGOBOX_PLUGIN" => Some(Self::ShowHelgoboxPlugin), + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_TOGGLE_PLAY_STOP" => { + Some(Self::ArrangementTogglePlayStop) + } + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_PLAY" => Some(Self::ArrangementPlay), + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_STOP" => Some(Self::ArrangementStop), + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_PAUSE" => Some(Self::ArrangementPause), + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_START_RECORDING" => { + Some(Self::ArrangementStartRecording) + } + "TRIGGER_INSTANCE_ACTION_ARRANGEMENT_STOP_RECORDING" => { + Some(Self::ArrangementStopRecording) + } + "TRIGGER_INSTANCE_ACTION_CLOSE_APP" => Some(Self::CloseApp), + "TRIGGER_INSTANCE_ACTION_HIDE_APP" => Some(Self::HideApp), + "TRIGGER_INSTANCE_ACTION_SAVE_PROJECT" => Some(Self::SaveProject), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerMatrixAction { + StopAllClips = 0, + Undo = 7, + Redo = 8, + ToggleClick = 9, + Panic = 10, + ToggleMute = 11, + CreateMatrix = 12, + ShowMasterFx = 13, + ShowMasterRouting = 14, + TapTempo = 15, + EnterSilenceMode = 38, + ToggleSilenceMode = 16, + PlayAllIgnitedClips = 17, + SequencerRecord = 18, + SequencerPlay = 19, + SequencerStop = 20, + SequencerWriteToArrangementAtStart = 21, + SequencerWriteToArrangementAtEnd = 41, + SequencerWriteToArrangementAtCursor = 42, + SequencerCleanArrangement = 22, + TriggerSmartRecord = 25, + ToggleLearnSimpleMappingTrigger = 23, + ToggleLearnSimpleMappingSmartRecord = 26, + ToggleLearnSimpleMappingEnterSilenceModeOrPlayIgnited = 27, + ToggleLearnSimpleMappingSequencerRecordOnOffState = 28, + ToggleLearnSimpleMappingSequencerPlayOnOffState = 29, + ToggleLearnSimpleMappingTapTempo = 35, + RemoveSimpleMappingTrigger = 24, + RemoveSimpleMappingSmartRecord = 30, + RemoveSimpleMappingEnterSilenceModeOrPlayIgnited = 31, + RemoveSimpleMappingSequencerRecordOnOffState = 32, + RemoveSimpleMappingSequencerPlayOnOffState = 33, + RemoveSimpleMappingTapTempo = 36, + Activate = 34, + /// Brings all clips online again. + ReloadAllClips = 37, + ExportToClipboard = 39, + ExportToArrangement = 40, +} +impl TriggerMatrixAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerMatrixAction::StopAllClips => "TRIGGER_MATRIX_ACTION_STOP_ALL_CLIPS", + TriggerMatrixAction::Undo => "TRIGGER_MATRIX_ACTION_UNDO", + TriggerMatrixAction::Redo => "TRIGGER_MATRIX_ACTION_REDO", + TriggerMatrixAction::ToggleClick => "TRIGGER_MATRIX_ACTION_TOGGLE_CLICK", + TriggerMatrixAction::Panic => "TRIGGER_MATRIX_ACTION_PANIC", + TriggerMatrixAction::ToggleMute => "TRIGGER_MATRIX_ACTION_TOGGLE_MUTE", + TriggerMatrixAction::CreateMatrix => "TRIGGER_MATRIX_ACTION_CREATE_MATRIX", + TriggerMatrixAction::ShowMasterFx => "TRIGGER_MATRIX_ACTION_SHOW_MASTER_FX", + TriggerMatrixAction::ShowMasterRouting => { + "TRIGGER_MATRIX_ACTION_SHOW_MASTER_ROUTING" + } + TriggerMatrixAction::TapTempo => "TRIGGER_MATRIX_ACTION_TAP_TEMPO", + TriggerMatrixAction::EnterSilenceMode => { + "TRIGGER_MATRIX_ACTION_ENTER_SILENCE_MODE" + } + TriggerMatrixAction::ToggleSilenceMode => { + "TRIGGER_MATRIX_ACTION_TOGGLE_SILENCE_MODE" + } + TriggerMatrixAction::PlayAllIgnitedClips => { + "TRIGGER_MATRIX_ACTION_PLAY_ALL_IGNITED_CLIPS" + } + TriggerMatrixAction::SequencerRecord => { + "TRIGGER_MATRIX_ACTION_SEQUENCER_RECORD" + } + TriggerMatrixAction::SequencerPlay => "TRIGGER_MATRIX_ACTION_SEQUENCER_PLAY", + TriggerMatrixAction::SequencerStop => "TRIGGER_MATRIX_ACTION_SEQUENCER_STOP", + TriggerMatrixAction::SequencerWriteToArrangementAtStart => { + "TRIGGER_MATRIX_ACTION_SEQUENCER_WRITE_TO_ARRANGEMENT_AT_START" + } + TriggerMatrixAction::SequencerWriteToArrangementAtEnd => { + "TRIGGER_MATRIX_ACTION_SEQUENCER_WRITE_TO_ARRANGEMENT_AT_END" + } + TriggerMatrixAction::SequencerWriteToArrangementAtCursor => { + "TRIGGER_MATRIX_ACTION_SEQUENCER_WRITE_TO_ARRANGEMENT_AT_CURSOR" + } + TriggerMatrixAction::SequencerCleanArrangement => { + "TRIGGER_MATRIX_ACTION_SEQUENCER_CLEAN_ARRANGEMENT" + } + TriggerMatrixAction::TriggerSmartRecord => { + "TRIGGER_MATRIX_ACTION_TRIGGER_SMART_RECORD" + } + TriggerMatrixAction::ToggleLearnSimpleMappingTrigger => { + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_TRIGGER" + } + TriggerMatrixAction::ToggleLearnSimpleMappingSmartRecord => { + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_SMART_RECORD" + } + TriggerMatrixAction::ToggleLearnSimpleMappingEnterSilenceModeOrPlayIgnited => { + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_ENTER_SILENCE_MODE_OR_PLAY_IGNITED" + } + TriggerMatrixAction::ToggleLearnSimpleMappingSequencerRecordOnOffState => { + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_SEQUENCER_RECORD_ON_OFF_STATE" + } + TriggerMatrixAction::ToggleLearnSimpleMappingSequencerPlayOnOffState => { + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_SEQUENCER_PLAY_ON_OFF_STATE" + } + TriggerMatrixAction::ToggleLearnSimpleMappingTapTempo => { + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_TAP_TEMPO" + } + TriggerMatrixAction::RemoveSimpleMappingTrigger => { + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_TRIGGER" + } + TriggerMatrixAction::RemoveSimpleMappingSmartRecord => { + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_SMART_RECORD" + } + TriggerMatrixAction::RemoveSimpleMappingEnterSilenceModeOrPlayIgnited => { + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_ENTER_SILENCE_MODE_OR_PLAY_IGNITED" + } + TriggerMatrixAction::RemoveSimpleMappingSequencerRecordOnOffState => { + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_SEQUENCER_RECORD_ON_OFF_STATE" + } + TriggerMatrixAction::RemoveSimpleMappingSequencerPlayOnOffState => { + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_SEQUENCER_PLAY_ON_OFF_STATE" + } + TriggerMatrixAction::RemoveSimpleMappingTapTempo => { + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_TAP_TEMPO" + } + TriggerMatrixAction::Activate => "TRIGGER_MATRIX_ACTION_ACTIVATE", + TriggerMatrixAction::ReloadAllClips => { + "TRIGGER_MATRIX_ACTION_RELOAD_ALL_CLIPS" + } + TriggerMatrixAction::ExportToClipboard => { + "TRIGGER_MATRIX_ACTION_EXPORT_TO_CLIPBOARD" + } + TriggerMatrixAction::ExportToArrangement => { + "TRIGGER_MATRIX_ACTION_EXPORT_TO_ARRANGEMENT" + } + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_MATRIX_ACTION_STOP_ALL_CLIPS" => Some(Self::StopAllClips), + "TRIGGER_MATRIX_ACTION_UNDO" => Some(Self::Undo), + "TRIGGER_MATRIX_ACTION_REDO" => Some(Self::Redo), + "TRIGGER_MATRIX_ACTION_TOGGLE_CLICK" => Some(Self::ToggleClick), + "TRIGGER_MATRIX_ACTION_PANIC" => Some(Self::Panic), + "TRIGGER_MATRIX_ACTION_TOGGLE_MUTE" => Some(Self::ToggleMute), + "TRIGGER_MATRIX_ACTION_CREATE_MATRIX" => Some(Self::CreateMatrix), + "TRIGGER_MATRIX_ACTION_SHOW_MASTER_FX" => Some(Self::ShowMasterFx), + "TRIGGER_MATRIX_ACTION_SHOW_MASTER_ROUTING" => Some(Self::ShowMasterRouting), + "TRIGGER_MATRIX_ACTION_TAP_TEMPO" => Some(Self::TapTempo), + "TRIGGER_MATRIX_ACTION_ENTER_SILENCE_MODE" => Some(Self::EnterSilenceMode), + "TRIGGER_MATRIX_ACTION_TOGGLE_SILENCE_MODE" => Some(Self::ToggleSilenceMode), + "TRIGGER_MATRIX_ACTION_PLAY_ALL_IGNITED_CLIPS" => { + Some(Self::PlayAllIgnitedClips) + } + "TRIGGER_MATRIX_ACTION_SEQUENCER_RECORD" => Some(Self::SequencerRecord), + "TRIGGER_MATRIX_ACTION_SEQUENCER_PLAY" => Some(Self::SequencerPlay), + "TRIGGER_MATRIX_ACTION_SEQUENCER_STOP" => Some(Self::SequencerStop), + "TRIGGER_MATRIX_ACTION_SEQUENCER_WRITE_TO_ARRANGEMENT_AT_START" => { + Some(Self::SequencerWriteToArrangementAtStart) + } + "TRIGGER_MATRIX_ACTION_SEQUENCER_WRITE_TO_ARRANGEMENT_AT_END" => { + Some(Self::SequencerWriteToArrangementAtEnd) + } + "TRIGGER_MATRIX_ACTION_SEQUENCER_WRITE_TO_ARRANGEMENT_AT_CURSOR" => { + Some(Self::SequencerWriteToArrangementAtCursor) + } + "TRIGGER_MATRIX_ACTION_SEQUENCER_CLEAN_ARRANGEMENT" => { + Some(Self::SequencerCleanArrangement) + } + "TRIGGER_MATRIX_ACTION_TRIGGER_SMART_RECORD" => { + Some(Self::TriggerSmartRecord) + } + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_TRIGGER" => { + Some(Self::ToggleLearnSimpleMappingTrigger) + } + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_SMART_RECORD" => { + Some(Self::ToggleLearnSimpleMappingSmartRecord) + } + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_ENTER_SILENCE_MODE_OR_PLAY_IGNITED" => { + Some(Self::ToggleLearnSimpleMappingEnterSilenceModeOrPlayIgnited) + } + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_SEQUENCER_RECORD_ON_OFF_STATE" => { + Some(Self::ToggleLearnSimpleMappingSequencerRecordOnOffState) + } + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_SEQUENCER_PLAY_ON_OFF_STATE" => { + Some(Self::ToggleLearnSimpleMappingSequencerPlayOnOffState) + } + "TRIGGER_MATRIX_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING_TAP_TEMPO" => { + Some(Self::ToggleLearnSimpleMappingTapTempo) + } + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_TRIGGER" => { + Some(Self::RemoveSimpleMappingTrigger) + } + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_SMART_RECORD" => { + Some(Self::RemoveSimpleMappingSmartRecord) + } + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_ENTER_SILENCE_MODE_OR_PLAY_IGNITED" => { + Some(Self::RemoveSimpleMappingEnterSilenceModeOrPlayIgnited) + } + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_SEQUENCER_RECORD_ON_OFF_STATE" => { + Some(Self::RemoveSimpleMappingSequencerRecordOnOffState) + } + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_SEQUENCER_PLAY_ON_OFF_STATE" => { + Some(Self::RemoveSimpleMappingSequencerPlayOnOffState) + } + "TRIGGER_MATRIX_ACTION_REMOVE_SIMPLE_MAPPING_TAP_TEMPO" => { + Some(Self::RemoveSimpleMappingTapTempo) + } + "TRIGGER_MATRIX_ACTION_ACTIVATE" => Some(Self::Activate), + "TRIGGER_MATRIX_ACTION_RELOAD_ALL_CLIPS" => Some(Self::ReloadAllClips), + "TRIGGER_MATRIX_ACTION_EXPORT_TO_CLIPBOARD" => Some(Self::ExportToClipboard), + "TRIGGER_MATRIX_ACTION_EXPORT_TO_ARRANGEMENT" => { + Some(Self::ExportToArrangement) + } + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerGlobalAction { + FocusHost = 0, + MidiPanic = 1, +} +impl TriggerGlobalAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerGlobalAction::FocusHost => "TRIGGER_GLOBAL_ACTION_FOCUS_HOST", + TriggerGlobalAction::MidiPanic => "TRIGGER_GLOBAL_ACTION_MIDI_PANIC", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_GLOBAL_ACTION_FOCUS_HOST" => Some(Self::FocusHost), + "TRIGGER_GLOBAL_ACTION_MIDI_PANIC" => Some(Self::MidiPanic), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ColumnKind { + Audio = 0, + Midi = 1, +} +impl ColumnKind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + ColumnKind::Audio => "COLUMN_KIND_AUDIO", + ColumnKind::Midi => "COLUMN_KIND_MIDI", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "COLUMN_KIND_AUDIO" => Some(Self::Audio), + "COLUMN_KIND_MIDI" => Some(Self::Midi), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Compartment { + Controller = 0, + Main = 1, +} +impl Compartment { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Compartment::Controller => "COMPARTMENT_CONTROLLER", + Compartment::Main => "COMPARTMENT_MAIN", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "COMPARTMENT_CONTROLLER" => Some(Self::Controller), + "COMPARTMENT_MAIN" => Some(Self::Main), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerTrackAction { + ToggleMute = 0, + ToggleSolo = 1, + ToggleArm = 2, + ShowFx = 3, + ShowRouting = 4, + ToggleLearnInput = 5, +} +impl TriggerTrackAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerTrackAction::ToggleMute => "TRIGGER_TRACK_ACTION_TOGGLE_MUTE", + TriggerTrackAction::ToggleSolo => "TRIGGER_TRACK_ACTION_TOGGLE_SOLO", + TriggerTrackAction::ToggleArm => "TRIGGER_TRACK_ACTION_TOGGLE_ARM", + TriggerTrackAction::ShowFx => "TRIGGER_TRACK_ACTION_SHOW_FX", + TriggerTrackAction::ShowRouting => "TRIGGER_TRACK_ACTION_SHOW_ROUTING", + TriggerTrackAction::ToggleLearnInput => "TRIGGER_TRACK_ACTION_TOGGLE_LEARN_INPUT", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_TRACK_ACTION_TOGGLE_MUTE" => Some(Self::ToggleMute), + "TRIGGER_TRACK_ACTION_TOGGLE_SOLO" => Some(Self::ToggleSolo), + "TRIGGER_TRACK_ACTION_TOGGLE_ARM" => Some(Self::ToggleArm), + "TRIGGER_TRACK_ACTION_SHOW_FX" => Some(Self::ShowFx), + "TRIGGER_TRACK_ACTION_SHOW_ROUTING" => Some(Self::ShowRouting), + "TRIGGER_TRACK_ACTION_TOGGLE_LEARN_INPUT" => Some(Self::ToggleLearnInput), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerColumnAction { + Stop = 0, + Remove = 1, + RemoveWithoutTrack = 11, + Duplicate = 2, + Insert = 3, + InsertForEachSelectedTrack = 10, + Panic = 4, + ToggleLearnSimpleMapping = 5, + RemoveSimpleMapping = 6, + Activate = 7, + ExportToClipboard = 8, + ExportToArrangement = 9, +} +impl TriggerColumnAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerColumnAction::Stop => "TRIGGER_COLUMN_ACTION_STOP", + TriggerColumnAction::Remove => "TRIGGER_COLUMN_ACTION_REMOVE", + TriggerColumnAction::RemoveWithoutTrack => "TRIGGER_COLUMN_ACTION_REMOVE_WITHOUT_TRACK", + TriggerColumnAction::Duplicate => "TRIGGER_COLUMN_ACTION_DUPLICATE", + TriggerColumnAction::Insert => "TRIGGER_COLUMN_ACTION_INSERT", + TriggerColumnAction::InsertForEachSelectedTrack => { + "TRIGGER_COLUMN_ACTION_INSERT_FOR_EACH_SELECTED_TRACK" + } + TriggerColumnAction::Panic => "TRIGGER_COLUMN_ACTION_PANIC", + TriggerColumnAction::ToggleLearnSimpleMapping => { + "TRIGGER_COLUMN_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING" + } + TriggerColumnAction::RemoveSimpleMapping => { + "TRIGGER_COLUMN_ACTION_REMOVE_SIMPLE_MAPPING" + } + TriggerColumnAction::Activate => "TRIGGER_COLUMN_ACTION_ACTIVATE", + TriggerColumnAction::ExportToClipboard => "TRIGGER_COLUMN_ACTION_EXPORT_TO_CLIPBOARD", + TriggerColumnAction::ExportToArrangement => { + "TRIGGER_COLUMN_ACTION_EXPORT_TO_ARRANGEMENT" + } + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_COLUMN_ACTION_STOP" => Some(Self::Stop), + "TRIGGER_COLUMN_ACTION_REMOVE" => Some(Self::Remove), + "TRIGGER_COLUMN_ACTION_REMOVE_WITHOUT_TRACK" => Some(Self::RemoveWithoutTrack), + "TRIGGER_COLUMN_ACTION_DUPLICATE" => Some(Self::Duplicate), + "TRIGGER_COLUMN_ACTION_INSERT" => Some(Self::Insert), + "TRIGGER_COLUMN_ACTION_INSERT_FOR_EACH_SELECTED_TRACK" => { + Some(Self::InsertForEachSelectedTrack) + } + "TRIGGER_COLUMN_ACTION_PANIC" => Some(Self::Panic), + "TRIGGER_COLUMN_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING" => { + Some(Self::ToggleLearnSimpleMapping) + } + "TRIGGER_COLUMN_ACTION_REMOVE_SIMPLE_MAPPING" => Some(Self::RemoveSimpleMapping), + "TRIGGER_COLUMN_ACTION_ACTIVATE" => Some(Self::Activate), + "TRIGGER_COLUMN_ACTION_EXPORT_TO_CLIPBOARD" => Some(Self::ExportToClipboard), + "TRIGGER_COLUMN_ACTION_EXPORT_TO_ARRANGEMENT" => Some(Self::ExportToArrangement), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerRowAction { + Play = 0, + Clear = 1, + Copy = 2, + Cut = 3, + Paste = 4, + Remove = 5, + Duplicate = 6, + Insert = 7, + ToggleLearnSimpleMapping = 9, + RemoveSimpleMapping = 10, + BuildSceneFromPlayingSlots = 11, + Activate = 12, + ExportToArrangement = 13, +} +impl TriggerRowAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerRowAction::Play => "TRIGGER_ROW_ACTION_PLAY", + TriggerRowAction::Clear => "TRIGGER_ROW_ACTION_CLEAR", + TriggerRowAction::Copy => "TRIGGER_ROW_ACTION_COPY", + TriggerRowAction::Cut => "TRIGGER_ROW_ACTION_CUT", + TriggerRowAction::Paste => "TRIGGER_ROW_ACTION_PASTE", + TriggerRowAction::Remove => "TRIGGER_ROW_ACTION_REMOVE", + TriggerRowAction::Duplicate => "TRIGGER_ROW_ACTION_DUPLICATE", + TriggerRowAction::Insert => "TRIGGER_ROW_ACTION_INSERT", + TriggerRowAction::ToggleLearnSimpleMapping => { + "TRIGGER_ROW_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING" + } + TriggerRowAction::RemoveSimpleMapping => "TRIGGER_ROW_ACTION_REMOVE_SIMPLE_MAPPING", + TriggerRowAction::BuildSceneFromPlayingSlots => { + "TRIGGER_ROW_ACTION_BUILD_SCENE_FROM_PLAYING_SLOTS" + } + TriggerRowAction::Activate => "TRIGGER_ROW_ACTION_ACTIVATE", + TriggerRowAction::ExportToArrangement => "TRIGGER_ROW_ACTION_EXPORT_TO_ARRANGEMENT", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_ROW_ACTION_PLAY" => Some(Self::Play), + "TRIGGER_ROW_ACTION_CLEAR" => Some(Self::Clear), + "TRIGGER_ROW_ACTION_COPY" => Some(Self::Copy), + "TRIGGER_ROW_ACTION_CUT" => Some(Self::Cut), + "TRIGGER_ROW_ACTION_PASTE" => Some(Self::Paste), + "TRIGGER_ROW_ACTION_REMOVE" => Some(Self::Remove), + "TRIGGER_ROW_ACTION_DUPLICATE" => Some(Self::Duplicate), + "TRIGGER_ROW_ACTION_INSERT" => Some(Self::Insert), + "TRIGGER_ROW_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING" => { + Some(Self::ToggleLearnSimpleMapping) + } + "TRIGGER_ROW_ACTION_REMOVE_SIMPLE_MAPPING" => Some(Self::RemoveSimpleMapping), + "TRIGGER_ROW_ACTION_BUILD_SCENE_FROM_PLAYING_SLOTS" => { + Some(Self::BuildSceneFromPlayingSlots) + } + "TRIGGER_ROW_ACTION_ACTIVATE" => Some(Self::Activate), + "TRIGGER_ROW_ACTION_EXPORT_TO_ARRANGEMENT" => Some(Self::ExportToArrangement), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DragColumnAction { + Reorder = 0, +} +impl DragColumnAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DragColumnAction::Reorder => "DRAG_COLUMN_ACTION_REORDER", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DRAG_COLUMN_ACTION_REORDER" => Some(Self::Reorder), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DragRowAction { + MoveContent = 0, + CopyContent = 1, + Reorder = 2, +} +impl DragRowAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DragRowAction::MoveContent => "DRAG_ROW_ACTION_MOVE_CONTENT", + DragRowAction::CopyContent => "DRAG_ROW_ACTION_COPY_CONTENT", + DragRowAction::Reorder => "DRAG_ROW_ACTION_REORDER", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DRAG_ROW_ACTION_MOVE_CONTENT" => Some(Self::MoveContent), + "DRAG_ROW_ACTION_COPY_CONTENT" => Some(Self::CopyContent), + "DRAG_ROW_ACTION_REORDER" => Some(Self::Reorder), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerSlotAction { + Play = 0, + Stop = 1, + Record = 2, + Clear = 4, + Copy = 5, + Cut = 6, + Paste = 7, + ImportSelectedItems = 8, + Panic = 9, + CreateEmptyMidiClip = 10, + ToggleLearnSimpleMapping = 11, + RemoveSimpleMapping = 12, + TriggerOn = 13, + TriggerOff = 14, + Activate = 15, + ExportToClipboard = 16, + ExportToArrangement = 17, +} +impl TriggerSlotAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerSlotAction::Play => "TRIGGER_SLOT_ACTION_PLAY", + TriggerSlotAction::Stop => "TRIGGER_SLOT_ACTION_STOP", + TriggerSlotAction::Record => "TRIGGER_SLOT_ACTION_RECORD", + TriggerSlotAction::Clear => "TRIGGER_SLOT_ACTION_CLEAR", + TriggerSlotAction::Copy => "TRIGGER_SLOT_ACTION_COPY", + TriggerSlotAction::Cut => "TRIGGER_SLOT_ACTION_CUT", + TriggerSlotAction::Paste => "TRIGGER_SLOT_ACTION_PASTE", + TriggerSlotAction::ImportSelectedItems => "TRIGGER_SLOT_ACTION_IMPORT_SELECTED_ITEMS", + TriggerSlotAction::Panic => "TRIGGER_SLOT_ACTION_PANIC", + TriggerSlotAction::CreateEmptyMidiClip => "TRIGGER_SLOT_ACTION_CREATE_EMPTY_MIDI_CLIP", + TriggerSlotAction::ToggleLearnSimpleMapping => { + "TRIGGER_SLOT_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING" + } + TriggerSlotAction::RemoveSimpleMapping => "TRIGGER_SLOT_ACTION_REMOVE_SIMPLE_MAPPING", + TriggerSlotAction::TriggerOn => "TRIGGER_SLOT_ACTION_TRIGGER_ON", + TriggerSlotAction::TriggerOff => "TRIGGER_SLOT_ACTION_TRIGGER_OFF", + TriggerSlotAction::Activate => "TRIGGER_SLOT_ACTION_ACTIVATE", + TriggerSlotAction::ExportToClipboard => "TRIGGER_SLOT_ACTION_EXPORT_TO_CLIPBOARD", + TriggerSlotAction::ExportToArrangement => "TRIGGER_SLOT_ACTION_EXPORT_TO_ARRANGEMENT", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_SLOT_ACTION_PLAY" => Some(Self::Play), + "TRIGGER_SLOT_ACTION_STOP" => Some(Self::Stop), + "TRIGGER_SLOT_ACTION_RECORD" => Some(Self::Record), + "TRIGGER_SLOT_ACTION_CLEAR" => Some(Self::Clear), + "TRIGGER_SLOT_ACTION_COPY" => Some(Self::Copy), + "TRIGGER_SLOT_ACTION_CUT" => Some(Self::Cut), + "TRIGGER_SLOT_ACTION_PASTE" => Some(Self::Paste), + "TRIGGER_SLOT_ACTION_IMPORT_SELECTED_ITEMS" => Some(Self::ImportSelectedItems), + "TRIGGER_SLOT_ACTION_PANIC" => Some(Self::Panic), + "TRIGGER_SLOT_ACTION_CREATE_EMPTY_MIDI_CLIP" => Some(Self::CreateEmptyMidiClip), + "TRIGGER_SLOT_ACTION_TOGGLE_LEARN_SIMPLE_MAPPING" => { + Some(Self::ToggleLearnSimpleMapping) + } + "TRIGGER_SLOT_ACTION_REMOVE_SIMPLE_MAPPING" => Some(Self::RemoveSimpleMapping), + "TRIGGER_SLOT_ACTION_TRIGGER_ON" => Some(Self::TriggerOn), + "TRIGGER_SLOT_ACTION_TRIGGER_OFF" => Some(Self::TriggerOff), + "TRIGGER_SLOT_ACTION_ACTIVATE" => Some(Self::Activate), + "TRIGGER_SLOT_ACTION_EXPORT_TO_CLIPBOARD" => Some(Self::ExportToClipboard), + "TRIGGER_SLOT_ACTION_EXPORT_TO_ARRANGEMENT" => Some(Self::ExportToArrangement), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerClipAction { + MidiOverdub = 0, + Edit = 1, + Remove = 2, + Promote = 3, + OpenInMediaExplorer = 4, + Quantize = 5, + Unquantize = 6, + ExportToClipboard = 7, + ExportToArrangement = 8, + ToggleMidiOverdub = 9, +} +impl TriggerClipAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerClipAction::MidiOverdub => "TRIGGER_CLIP_ACTION_MIDI_OVERDUB", + TriggerClipAction::Edit => "TRIGGER_CLIP_ACTION_EDIT", + TriggerClipAction::Remove => "TRIGGER_CLIP_ACTION_REMOVE", + TriggerClipAction::Promote => "TRIGGER_CLIP_ACTION_PROMOTE", + TriggerClipAction::OpenInMediaExplorer => "TRIGGER_CLIP_ACTION_OPEN_IN_MEDIA_EXPLORER", + TriggerClipAction::Quantize => "TRIGGER_CLIP_ACTION_QUANTIZE", + TriggerClipAction::Unquantize => "TRIGGER_CLIP_ACTION_UNQUANTIZE", + TriggerClipAction::ExportToClipboard => "TRIGGER_CLIP_ACTION_EXPORT_TO_CLIPBOARD", + TriggerClipAction::ExportToArrangement => "TRIGGER_CLIP_ACTION_EXPORT_TO_ARRANGEMENT", + TriggerClipAction::ToggleMidiOverdub => "TRIGGER_CLIP_ACTION_TOGGLE_MIDI_OVERDUB", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_CLIP_ACTION_MIDI_OVERDUB" => Some(Self::MidiOverdub), + "TRIGGER_CLIP_ACTION_EDIT" => Some(Self::Edit), + "TRIGGER_CLIP_ACTION_REMOVE" => Some(Self::Remove), + "TRIGGER_CLIP_ACTION_PROMOTE" => Some(Self::Promote), + "TRIGGER_CLIP_ACTION_OPEN_IN_MEDIA_EXPLORER" => Some(Self::OpenInMediaExplorer), + "TRIGGER_CLIP_ACTION_QUANTIZE" => Some(Self::Quantize), + "TRIGGER_CLIP_ACTION_UNQUANTIZE" => Some(Self::Unquantize), + "TRIGGER_CLIP_ACTION_EXPORT_TO_CLIPBOARD" => Some(Self::ExportToClipboard), + "TRIGGER_CLIP_ACTION_EXPORT_TO_ARRANGEMENT" => Some(Self::ExportToArrangement), + "TRIGGER_CLIP_ACTION_TOGGLE_MIDI_OVERDUB" => Some(Self::ToggleMidiOverdub), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TriggerSequenceAction { + Activate = 0, + Remove = 1, +} +impl TriggerSequenceAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TriggerSequenceAction::Activate => "TRIGGER_SEQUENCE_ACTION_ACTIVATE", + TriggerSequenceAction::Remove => "TRIGGER_SEQUENCE_ACTION_REMOVE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRIGGER_SEQUENCE_ACTION_ACTIVATE" => Some(Self::Activate), + "TRIGGER_SEQUENCE_ACTION_REMOVE" => Some(Self::Remove), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DragSlotAction { + Move = 0, + Copy = 1, +} +impl DragSlotAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DragSlotAction::Move => "DRAG_SLOT_ACTION_MOVE", + DragSlotAction::Copy => "DRAG_SLOT_ACTION_COPY", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DRAG_SLOT_ACTION_MOVE" => Some(Self::Move), + "DRAG_SLOT_ACTION_COPY" => Some(Self::Copy), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DragClipAction { + Move = 0, +} +impl DragClipAction { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DragClipAction::Move => "DRAG_CLIP_ACTION_MOVE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DRAG_CLIP_ACTION_MOVE" => Some(Self::Move), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Scope { + Realearn = 0, + Playtime = 1, +} +impl Scope { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Scope::Realearn => "SCOPE_REALEARN", + Scope::Playtime => "SCOPE_PLAYTIME", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SCOPE_REALEARN" => Some(Self::Realearn), + "SCOPE_PLAYTIME" => Some(Self::Playtime), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Severity { + Low = 0, + Medium = 1, + High = 2, +} +impl Severity { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Severity::Low => "SEVERITY_LOW", + Severity::Medium => "SEVERITY_MEDIUM", + Severity::High => "SEVERITY_HIGH", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SEVERITY_LOW" => Some(Self::Low), + "SEVERITY_MEDIUM" => Some(Self::Medium), + "SEVERITY_HIGH" => Some(Self::High), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum TrackInputMonitoring { + Unknown = 0, + Off = 1, + On = 2, + Auto = 3, +} +impl TrackInputMonitoring { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + TrackInputMonitoring::Unknown => "TRACK_INPUT_MONITORING_UNKNOWN", + TrackInputMonitoring::Off => "TRACK_INPUT_MONITORING_OFF", + TrackInputMonitoring::On => "TRACK_INPUT_MONITORING_ON", + TrackInputMonitoring::Auto => "TRACK_INPUT_MONITORING_AUTO", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TRACK_INPUT_MONITORING_UNKNOWN" => Some(Self::Unknown), + "TRACK_INPUT_MONITORING_OFF" => Some(Self::Off), + "TRACK_INPUT_MONITORING_ON" => Some(Self::On), + "TRACK_INPUT_MONITORING_AUTO" => Some(Self::Auto), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SlotPlayState { + Unknown = 0, + Stopped = 1, + Ignited = 2, + ScheduledForPlayStart = 3, + Playing = 4, + Paused = 5, + ScheduledForPlayRestart = 6, + ScheduledForPlayStop = 7, + ScheduledForRecordingStart = 8, + Recording = 9, + ScheduledForRecordingStop = 10, +} +impl SlotPlayState { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SlotPlayState::Unknown => "SLOT_PLAY_STATE_UNKNOWN", + SlotPlayState::Stopped => "SLOT_PLAY_STATE_STOPPED", + SlotPlayState::Ignited => "SLOT_PLAY_STATE_IGNITED", + SlotPlayState::ScheduledForPlayStart => "SLOT_PLAY_STATE_SCHEDULED_FOR_PLAY_START", + SlotPlayState::Playing => "SLOT_PLAY_STATE_PLAYING", + SlotPlayState::Paused => "SLOT_PLAY_STATE_PAUSED", + SlotPlayState::ScheduledForPlayRestart => "SLOT_PLAY_STATE_SCHEDULED_FOR_PLAY_RESTART", + SlotPlayState::ScheduledForPlayStop => "SLOT_PLAY_STATE_SCHEDULED_FOR_PLAY_STOP", + SlotPlayState::ScheduledForRecordingStart => { + "SLOT_PLAY_STATE_SCHEDULED_FOR_RECORDING_START" + } + SlotPlayState::Recording => "SLOT_PLAY_STATE_RECORDING", + SlotPlayState::ScheduledForRecordingStop => { + "SLOT_PLAY_STATE_SCHEDULED_FOR_RECORDING_STOP" + } + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SLOT_PLAY_STATE_UNKNOWN" => Some(Self::Unknown), + "SLOT_PLAY_STATE_STOPPED" => Some(Self::Stopped), + "SLOT_PLAY_STATE_IGNITED" => Some(Self::Ignited), + "SLOT_PLAY_STATE_SCHEDULED_FOR_PLAY_START" => Some(Self::ScheduledForPlayStart), + "SLOT_PLAY_STATE_PLAYING" => Some(Self::Playing), + "SLOT_PLAY_STATE_PAUSED" => Some(Self::Paused), + "SLOT_PLAY_STATE_SCHEDULED_FOR_PLAY_RESTART" => Some(Self::ScheduledForPlayRestart), + "SLOT_PLAY_STATE_SCHEDULED_FOR_PLAY_STOP" => Some(Self::ScheduledForPlayStop), + "SLOT_PLAY_STATE_SCHEDULED_FOR_RECORDING_START" => { + Some(Self::ScheduledForRecordingStart) + } + "SLOT_PLAY_STATE_RECORDING" => Some(Self::Recording), + "SLOT_PLAY_STATE_SCHEDULED_FOR_RECORDING_STOP" => Some(Self::ScheduledForRecordingStop), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum MidiDeviceStatus { + Disconnected = 0, + ConnectedButDisabled = 1, + Connected = 2, +} +impl MidiDeviceStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + MidiDeviceStatus::Disconnected => "MIDI_DEVICE_STATUS_DISCONNECTED", + MidiDeviceStatus::ConnectedButDisabled => "MIDI_DEVICE_STATUS_CONNECTED_BUT_DISABLED", + MidiDeviceStatus::Connected => "MIDI_DEVICE_STATUS_CONNECTED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "MIDI_DEVICE_STATUS_DISCONNECTED" => Some(Self::Disconnected), + "MIDI_DEVICE_STATUS_CONNECTED_BUT_DISABLED" => Some(Self::ConnectedButDisabled), + "MIDI_DEVICE_STATUS_CONNECTED" => Some(Self::Connected), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ArrangementPlayState { + Unknown = 0, + Stopped = 1, + Playing = 2, + PlayingPaused = 3, + Recording = 4, + RecordingPaused = 5, +} +impl ArrangementPlayState { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + ArrangementPlayState::Unknown => "ARRANGEMENT_PLAY_STATE_UNKNOWN", + ArrangementPlayState::Stopped => "ARRANGEMENT_PLAY_STATE_STOPPED", + ArrangementPlayState::Playing => "ARRANGEMENT_PLAY_STATE_PLAYING", + ArrangementPlayState::PlayingPaused => "ARRANGEMENT_PLAY_STATE_PLAYING_PAUSED", + ArrangementPlayState::Recording => "ARRANGEMENT_PLAY_STATE_RECORDING", + ArrangementPlayState::RecordingPaused => "ARRANGEMENT_PLAY_STATE_RECORDING_PAUSED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "ARRANGEMENT_PLAY_STATE_UNKNOWN" => Some(Self::Unknown), + "ARRANGEMENT_PLAY_STATE_STOPPED" => Some(Self::Stopped), + "ARRANGEMENT_PLAY_STATE_PLAYING" => Some(Self::Playing), + "ARRANGEMENT_PLAY_STATE_PLAYING_PAUSED" => Some(Self::PlayingPaused), + "ARRANGEMENT_PLAY_STATE_RECORDING" => Some(Self::Recording), + "ARRANGEMENT_PLAY_STATE_RECORDING_PAUSED" => Some(Self::RecordingPaused), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SequencerPlayState { + Unknown = 0, + Stopped = 1, + Playing = 2, + Recording = 3, +} +impl SequencerPlayState { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SequencerPlayState::Unknown => "SEQUENCER_PLAY_STATE_UNKNOWN", + SequencerPlayState::Stopped => "SEQUENCER_PLAY_STATE_STOPPED", + SequencerPlayState::Playing => "SEQUENCER_PLAY_STATE_PLAYING", + SequencerPlayState::Recording => "SEQUENCER_PLAY_STATE_RECORDING", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SEQUENCER_PLAY_STATE_UNKNOWN" => Some(Self::Unknown), + "SEQUENCER_PLAY_STATE_STOPPED" => Some(Self::Stopped), + "SEQUENCER_PLAY_STATE_PLAYING" => Some(Self::Playing), + "SEQUENCER_PLAY_STATE_RECORDING" => Some(Self::Recording), + _ => None, + } + } +} +/// Generated server implementations. +pub mod helgobox_service_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with HelgoboxServiceServer. + #[async_trait] + pub trait HelgoboxService: Send + Sync + 'static { + /// General global queries + async fn get_host_info( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn prove_authenticity( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_app_settings( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// General instance queries + async fn get_custom_instance_data( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime matrix queries + async fn get_project_dir( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_arrangement_info( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime clip queries + async fn get_clip_detail( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// ReaLearn compartment queries + async fn get_compartment_data( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// General global commands + async fn trigger_global( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_app_settings( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn add_license( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn save_controller( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn delete_controller( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime global commands + async fn set_playtime_engine_settings( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// General instance commands + async fn trigger_instance( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_instance_settings( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_custom_instance_data( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime matrix commands + async fn trigger_matrix( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_matrix_settings( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_matrix_tempo( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_matrix_time_signature( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_matrix_play_rate( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_matrix_volume( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_matrix_pan( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_matrix_click_channel( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime column commands + async fn trigger_column( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn insert_columns( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_column_settings( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_column_track( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn drag_column( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime track commands + async fn trigger_track( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_track_name( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_track_color( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_track_input( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_track_input_monitoring( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_track_volume( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_track_pan( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn open_track_fx( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime row commands + async fn trigger_row( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_row_data( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn drag_row( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime slot commands + async fn trigger_slot( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn drag_slot( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn import_files( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime clip commands + async fn trigger_clip( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_clip_name( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_clip_data( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn drag_clip( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Playtime sequence commands + async fn trigger_sequence( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_sequence_info( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// ReaLearn compartment commands + async fn save_custom_compartment_data( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the GetOccasionalGlobalUpdates method. + type GetOccasionalGlobalUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// General global events + async fn get_occasional_global_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetOccasionalPlaytimeEngineUpdates method. + type GetOccasionalPlaytimeEngineUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result< + super::GetOccasionalPlaytimeEngineUpdatesReply, + tonic::Status, + >, + > + Send + + 'static; + /// Playtime global events + async fn get_occasional_playtime_engine_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetOccasionalInstanceUpdates method. + type GetOccasionalInstanceUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// General instance events + async fn get_occasional_instance_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetOccasionalUnitUpdates method. + type GetOccasionalUnitUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + async fn get_occasional_unit_updates( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the GetOccasionalMatrixUpdates method. + type GetOccasionalMatrixUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// Playtime matrix events + async fn get_occasional_matrix_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetContinuousMatrixUpdates method. + type GetContinuousMatrixUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + async fn get_continuous_matrix_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetOccasionalColumnUpdates method. + type GetOccasionalColumnUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// Playtime column events + async fn get_occasional_column_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetContinuousColumnUpdates method. + type GetContinuousColumnUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + async fn get_continuous_column_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetOccasionalTrackUpdates method. + type GetOccasionalTrackUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// Playtime track events + async fn get_occasional_track_updates( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetOccasionalRowUpdates method. + type GetOccasionalRowUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// Playtime row events + async fn get_occasional_row_updates( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the GetOccasionalSlotUpdates method. + type GetOccasionalSlotUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// Playtime slot events + async fn get_occasional_slot_updates( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the GetContinuousSlotUpdates method. + type GetContinuousSlotUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + async fn get_continuous_slot_updates( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the GetOccasionalClipUpdates method. + type GetOccasionalClipUpdatesStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + Send + + 'static; + /// Playtime clip events + async fn get_occasional_clip_updates( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + #[derive(Debug)] + pub struct HelgoboxServiceServer { + inner: _Inner, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + struct _Inner(Arc); + impl HelgoboxServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for HelgoboxServiceServer + where + T: HelgoboxService, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/generated.HelgoboxService/GetHostInfo" => { + #[allow(non_camel_case_types)] + struct GetHostInfoSvc(pub Arc); + impl tonic::server::UnaryService + for GetHostInfoSvc + { + type Response = super::GetHostInfoReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_host_info(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetHostInfoSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/ProveAuthenticity" => { + #[allow(non_camel_case_types)] + struct ProveAuthenticitySvc(pub Arc); + impl + tonic::server::UnaryService + for ProveAuthenticitySvc + { + type Response = super::ProveAuthenticityReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::prove_authenticity(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = ProveAuthenticitySvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetAppSettings" => { + #[allow(non_camel_case_types)] + struct GetAppSettingsSvc(pub Arc); + impl + tonic::server::UnaryService + for GetAppSettingsSvc + { + type Response = super::GetAppSettingsReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_app_settings(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetAppSettingsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetCustomInstanceData" => { + #[allow(non_camel_case_types)] + struct GetCustomInstanceDataSvc(pub Arc); + impl + tonic::server::UnaryService + for GetCustomInstanceDataSvc + { + type Response = super::GetCustomInstanceDataReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_custom_instance_data(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetCustomInstanceDataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetProjectDir" => { + #[allow(non_camel_case_types)] + struct GetProjectDirSvc(pub Arc); + impl + tonic::server::UnaryService + for GetProjectDirSvc + { + type Response = super::GetProjectDirReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_project_dir(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetProjectDirSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetArrangementInfo" => { + #[allow(non_camel_case_types)] + struct GetArrangementInfoSvc(pub Arc); + impl + tonic::server::UnaryService + for GetArrangementInfoSvc + { + type Response = super::GetArrangementInfoReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_arrangement_info(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetArrangementInfoSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetClipDetail" => { + #[allow(non_camel_case_types)] + struct GetClipDetailSvc(pub Arc); + impl + tonic::server::UnaryService + for GetClipDetailSvc + { + type Response = super::GetClipDetailReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_clip_detail(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetClipDetailSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetCompartmentData" => { + #[allow(non_camel_case_types)] + struct GetCompartmentDataSvc(pub Arc); + impl + tonic::server::UnaryService + for GetCompartmentDataSvc + { + type Response = super::GetCompartmentDataReply; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_compartment_data(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetCompartmentDataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerGlobal" => { + #[allow(non_camel_case_types)] + struct TriggerGlobalSvc(pub Arc); + impl + tonic::server::UnaryService + for TriggerGlobalSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_global(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerGlobalSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetAppSettings" => { + #[allow(non_camel_case_types)] + struct SetAppSettingsSvc(pub Arc); + impl + tonic::server::UnaryService + for SetAppSettingsSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_app_settings(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetAppSettingsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/AddLicense" => { + #[allow(non_camel_case_types)] + struct AddLicenseSvc(pub Arc); + impl tonic::server::UnaryService + for AddLicenseSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::add_license(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = AddLicenseSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SaveController" => { + #[allow(non_camel_case_types)] + struct SaveControllerSvc(pub Arc); + impl + tonic::server::UnaryService + for SaveControllerSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::save_controller(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SaveControllerSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/DeleteController" => { + #[allow(non_camel_case_types)] + struct DeleteControllerSvc(pub Arc); + impl + tonic::server::UnaryService + for DeleteControllerSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_controller(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = DeleteControllerSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetPlaytimeEngineSettings" => { + #[allow(non_camel_case_types)] + struct SetPlaytimeEngineSettingsSvc(pub Arc); + impl + tonic::server::UnaryService + for SetPlaytimeEngineSettingsSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_playtime_engine_settings( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetPlaytimeEngineSettingsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerInstance" => { + #[allow(non_camel_case_types)] + struct TriggerInstanceSvc(pub Arc); + impl + tonic::server::UnaryService + for TriggerInstanceSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_instance(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerInstanceSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetInstanceSettings" => { + #[allow(non_camel_case_types)] + struct SetInstanceSettingsSvc(pub Arc); + impl + tonic::server::UnaryService + for SetInstanceSettingsSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_instance_settings(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetInstanceSettingsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetCustomInstanceData" => { + #[allow(non_camel_case_types)] + struct SetCustomInstanceDataSvc(pub Arc); + impl + tonic::server::UnaryService + for SetCustomInstanceDataSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_custom_instance_data(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetCustomInstanceDataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerMatrix" => { + #[allow(non_camel_case_types)] + struct TriggerMatrixSvc(pub Arc); + impl + tonic::server::UnaryService + for TriggerMatrixSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_matrix(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerMatrixSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetMatrixSettings" => { + #[allow(non_camel_case_types)] + struct SetMatrixSettingsSvc(pub Arc); + impl + tonic::server::UnaryService + for SetMatrixSettingsSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_matrix_settings(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetMatrixSettingsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetMatrixTempo" => { + #[allow(non_camel_case_types)] + struct SetMatrixTempoSvc(pub Arc); + impl + tonic::server::UnaryService + for SetMatrixTempoSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_matrix_tempo(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetMatrixTempoSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetMatrixTimeSignature" => { + #[allow(non_camel_case_types)] + struct SetMatrixTimeSignatureSvc(pub Arc); + impl + tonic::server::UnaryService + for SetMatrixTimeSignatureSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_matrix_time_signature(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetMatrixTimeSignatureSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetMatrixPlayRate" => { + #[allow(non_camel_case_types)] + struct SetMatrixPlayRateSvc(pub Arc); + impl + tonic::server::UnaryService + for SetMatrixPlayRateSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_matrix_play_rate(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetMatrixPlayRateSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetMatrixVolume" => { + #[allow(non_camel_case_types)] + struct SetMatrixVolumeSvc(pub Arc); + impl + tonic::server::UnaryService + for SetMatrixVolumeSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_matrix_volume(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetMatrixVolumeSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetMatrixPan" => { + #[allow(non_camel_case_types)] + struct SetMatrixPanSvc(pub Arc); + impl tonic::server::UnaryService + for SetMatrixPanSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_matrix_pan(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetMatrixPanSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetMatrixClickChannel" => { + #[allow(non_camel_case_types)] + struct SetMatrixClickChannelSvc(pub Arc); + impl + tonic::server::UnaryService + for SetMatrixClickChannelSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_matrix_click_channel(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetMatrixClickChannelSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerColumn" => { + #[allow(non_camel_case_types)] + struct TriggerColumnSvc(pub Arc); + impl + tonic::server::UnaryService + for TriggerColumnSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_column(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerColumnSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/InsertColumns" => { + #[allow(non_camel_case_types)] + struct InsertColumnsSvc(pub Arc); + impl + tonic::server::UnaryService + for InsertColumnsSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::insert_columns(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = InsertColumnsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetColumnSettings" => { + #[allow(non_camel_case_types)] + struct SetColumnSettingsSvc(pub Arc); + impl + tonic::server::UnaryService + for SetColumnSettingsSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_column_settings(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetColumnSettingsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetColumnTrack" => { + #[allow(non_camel_case_types)] + struct SetColumnTrackSvc(pub Arc); + impl + tonic::server::UnaryService + for SetColumnTrackSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_column_track(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetColumnTrackSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/DragColumn" => { + #[allow(non_camel_case_types)] + struct DragColumnSvc(pub Arc); + impl tonic::server::UnaryService + for DragColumnSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::drag_column(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = DragColumnSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerTrack" => { + #[allow(non_camel_case_types)] + struct TriggerTrackSvc(pub Arc); + impl tonic::server::UnaryService + for TriggerTrackSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_track(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerTrackSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetTrackName" => { + #[allow(non_camel_case_types)] + struct SetTrackNameSvc(pub Arc); + impl tonic::server::UnaryService + for SetTrackNameSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_track_name(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetTrackNameSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetTrackColor" => { + #[allow(non_camel_case_types)] + struct SetTrackColorSvc(pub Arc); + impl + tonic::server::UnaryService + for SetTrackColorSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_track_color(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetTrackColorSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetTrackInput" => { + #[allow(non_camel_case_types)] + struct SetTrackInputSvc(pub Arc); + impl + tonic::server::UnaryService + for SetTrackInputSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_track_input(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetTrackInputSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetTrackInputMonitoring" => { + #[allow(non_camel_case_types)] + struct SetTrackInputMonitoringSvc(pub Arc); + impl + tonic::server::UnaryService + for SetTrackInputMonitoringSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_track_input_monitoring(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetTrackInputMonitoringSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetTrackVolume" => { + #[allow(non_camel_case_types)] + struct SetTrackVolumeSvc(pub Arc); + impl + tonic::server::UnaryService + for SetTrackVolumeSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_track_volume(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetTrackVolumeSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetTrackPan" => { + #[allow(non_camel_case_types)] + struct SetTrackPanSvc(pub Arc); + impl tonic::server::UnaryService + for SetTrackPanSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_track_pan(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetTrackPanSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/OpenTrackFx" => { + #[allow(non_camel_case_types)] + struct OpenTrackFxSvc(pub Arc); + impl tonic::server::UnaryService + for OpenTrackFxSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::open_track_fx(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = OpenTrackFxSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerRow" => { + #[allow(non_camel_case_types)] + struct TriggerRowSvc(pub Arc); + impl tonic::server::UnaryService + for TriggerRowSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_row(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerRowSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetRowData" => { + #[allow(non_camel_case_types)] + struct SetRowDataSvc(pub Arc); + impl tonic::server::UnaryService + for SetRowDataSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_row_data(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetRowDataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/DragRow" => { + #[allow(non_camel_case_types)] + struct DragRowSvc(pub Arc); + impl tonic::server::UnaryService for DragRowSvc { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::drag_row(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = DragRowSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerSlot" => { + #[allow(non_camel_case_types)] + struct TriggerSlotSvc(pub Arc); + impl tonic::server::UnaryService + for TriggerSlotSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_slot(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerSlotSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/DragSlot" => { + #[allow(non_camel_case_types)] + struct DragSlotSvc(pub Arc); + impl tonic::server::UnaryService for DragSlotSvc { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::drag_slot(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = DragSlotSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/ImportFiles" => { + #[allow(non_camel_case_types)] + struct ImportFilesSvc(pub Arc); + impl tonic::server::UnaryService + for ImportFilesSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::import_files(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = ImportFilesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerClip" => { + #[allow(non_camel_case_types)] + struct TriggerClipSvc(pub Arc); + impl tonic::server::UnaryService + for TriggerClipSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_clip(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerClipSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetClipName" => { + #[allow(non_camel_case_types)] + struct SetClipNameSvc(pub Arc); + impl tonic::server::UnaryService + for SetClipNameSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_clip_name(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetClipNameSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetClipData" => { + #[allow(non_camel_case_types)] + struct SetClipDataSvc(pub Arc); + impl tonic::server::UnaryService + for SetClipDataSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_clip_data(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetClipDataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/DragClip" => { + #[allow(non_camel_case_types)] + struct DragClipSvc(pub Arc); + impl tonic::server::UnaryService for DragClipSvc { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::drag_clip(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = DragClipSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/TriggerSequence" => { + #[allow(non_camel_case_types)] + struct TriggerSequenceSvc(pub Arc); + impl + tonic::server::UnaryService + for TriggerSequenceSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::trigger_sequence(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TriggerSequenceSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SetSequenceInfo" => { + #[allow(non_camel_case_types)] + struct SetSequenceInfoSvc(pub Arc); + impl + tonic::server::UnaryService + for SetSequenceInfoSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_sequence_info(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SetSequenceInfoSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/SaveCustomCompartmentData" => { + #[allow(non_camel_case_types)] + struct SaveCustomCompartmentDataSvc(pub Arc); + impl + tonic::server::UnaryService + for SaveCustomCompartmentDataSvc + { + type Response = super::Empty; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::save_custom_compartment_data( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = SaveCustomCompartmentDataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalGlobalUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalGlobalUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalGlobalUpdatesRequest, + > for GetOccasionalGlobalUpdatesSvc + { + type Response = super::GetOccasionalGlobalUpdatesReply; + type ResponseStream = T::GetOccasionalGlobalUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_global_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalGlobalUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalPlaytimeEngineUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalPlaytimeEngineUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalPlaytimeEngineUpdatesRequest, + > for GetOccasionalPlaytimeEngineUpdatesSvc + { + type Response = super::GetOccasionalPlaytimeEngineUpdatesReply; + type ResponseStream = T::GetOccasionalPlaytimeEngineUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request< + super::GetOccasionalPlaytimeEngineUpdatesRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_playtime_engine_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalPlaytimeEngineUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalInstanceUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalInstanceUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalInstanceUpdatesRequest, + > for GetOccasionalInstanceUpdatesSvc + { + type Response = super::GetOccasionalInstanceUpdatesReply; + type ResponseStream = T::GetOccasionalInstanceUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_instance_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalInstanceUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalUnitUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalUnitUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalUnitUpdatesRequest, + > for GetOccasionalUnitUpdatesSvc + { + type Response = super::GetOccasionalUnitUpdatesReply; + type ResponseStream = T::GetOccasionalUnitUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_unit_updates(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalUnitUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalMatrixUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalMatrixUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalMatrixUpdatesRequest, + > for GetOccasionalMatrixUpdatesSvc + { + type Response = super::GetOccasionalMatrixUpdatesReply; + type ResponseStream = T::GetOccasionalMatrixUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_matrix_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalMatrixUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetContinuousMatrixUpdates" => { + #[allow(non_camel_case_types)] + struct GetContinuousMatrixUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetContinuousMatrixUpdatesRequest, + > for GetContinuousMatrixUpdatesSvc + { + type Response = super::GetContinuousMatrixUpdatesReply; + type ResponseStream = T::GetContinuousMatrixUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_continuous_matrix_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetContinuousMatrixUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalColumnUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalColumnUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalColumnUpdatesRequest, + > for GetOccasionalColumnUpdatesSvc + { + type Response = super::GetOccasionalColumnUpdatesReply; + type ResponseStream = T::GetOccasionalColumnUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_column_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalColumnUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetContinuousColumnUpdates" => { + #[allow(non_camel_case_types)] + struct GetContinuousColumnUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetContinuousColumnUpdatesRequest, + > for GetContinuousColumnUpdatesSvc + { + type Response = super::GetContinuousColumnUpdatesReply; + type ResponseStream = T::GetContinuousColumnUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_continuous_column_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetContinuousColumnUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalTrackUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalTrackUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalTrackUpdatesRequest, + > for GetOccasionalTrackUpdatesSvc + { + type Response = super::GetOccasionalTrackUpdatesReply; + type ResponseStream = T::GetOccasionalTrackUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_track_updates( + &inner, request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalTrackUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalRowUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalRowUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService + for GetOccasionalRowUpdatesSvc + { + type Response = super::GetOccasionalRowUpdatesReply; + type ResponseStream = T::GetOccasionalRowUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_row_updates(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalRowUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalSlotUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalSlotUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalSlotUpdatesRequest, + > for GetOccasionalSlotUpdatesSvc + { + type Response = super::GetOccasionalSlotUpdatesReply; + type ResponseStream = T::GetOccasionalSlotUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_slot_updates(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalSlotUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetContinuousSlotUpdates" => { + #[allow(non_camel_case_types)] + struct GetContinuousSlotUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetContinuousSlotUpdatesRequest, + > for GetContinuousSlotUpdatesSvc + { + type Response = super::GetContinuousSlotUpdatesReply; + type ResponseStream = T::GetContinuousSlotUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_continuous_slot_updates(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetContinuousSlotUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/generated.HelgoboxService/GetOccasionalClipUpdates" => { + #[allow(non_camel_case_types)] + struct GetOccasionalClipUpdatesSvc(pub Arc); + impl + tonic::server::ServerStreamingService< + super::GetOccasionalClipUpdatesRequest, + > for GetOccasionalClipUpdatesSvc + { + type Response = super::GetOccasionalClipUpdatesReply; + type ResponseStream = T::GetOccasionalClipUpdatesStream; + type Future = + BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_occasional_clip_updates(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOccasionalClipUpdatesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => Box::pin(async move { + Ok(http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap()) + }), + } + } + } + impl Clone for HelgoboxServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } + impl tonic::server::NamedService for HelgoboxServiceServer { + const NAME: &'static str = "generated.HelgoboxService"; + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/hub.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/hub.rs new file mode 100644 index 0000000..ae30034 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/hub.rs @@ -0,0 +1,886 @@ +use reaper_high::ChangeEvent; + +use helgobox_api::runtime::{GlobalInfoEvent, InstanceInfoEvent}; + +use crate::application::UnitModel; +use crate::domain::{InstanceId, UnitId}; +use crate::infrastructure::data::{ + ControllerManager, FileBasedControllerPresetManager, FileBasedMainPresetManager, LicenseManager, +}; +use crate::infrastructure::plugin::InstanceShell; +use crate::infrastructure::proto::helgobox_service_server::HelgoboxServiceServer; +use crate::infrastructure::proto::{ + occasional_global_update, occasional_instance_update, qualified_occasional_unit_update, + HelgoboxServiceImpl, OccasionalGlobalUpdate, OccasionalInstanceUpdate, + OccasionalInstanceUpdateBatch, OccasionalUnitUpdateBatch, ProtoRequestHandler, ProtoSenders, + QualifiedOccasionalUnitUpdate, +}; + +#[derive(Debug)] +pub struct ProtoHub { + senders: ProtoSenders, +} + +impl Default for ProtoHub { + fn default() -> Self { + Self::new() + } +} + +impl ProtoHub { + pub fn new() -> Self { + Self { + senders: ProtoSenders::new(), + } + } + + pub fn senders(&self) -> &ProtoSenders { + &self.senders + } + + pub fn create_service(&self) -> HelgoboxServiceServer { + HelgoboxServiceServer::new(HelgoboxServiceImpl::new( + ProtoRequestHandler, + self.senders.clone(), + )) + } + + pub fn notify_about_instance_info_event( + &self, + instance_id: InstanceId, + info_event: InstanceInfoEvent, + ) { + self.send_occasional_instance_updates(instance_id, || { + [occasional_instance_update::Update::info_event(info_event)] + }); + } + + pub fn notify_about_global_info_event(&self, info_event: GlobalInfoEvent) { + self.send_occasional_global_updates(|| { + [occasional_global_update::Update::info_event(info_event)] + }); + } + + pub fn notify_instances_changed(&self) { + self.send_occasional_global_updates(|| [occasional_global_update::Update::instances()]); + } + + pub fn notify_midi_input_devices_changed(&self) { + self.send_occasional_global_updates(|| { + [occasional_global_update::Update::midi_input_devices()] + }); + } + + pub fn notify_midi_output_devices_changed(&self) { + self.send_occasional_global_updates(|| { + [occasional_global_update::Update::midi_output_devices()] + }); + } + + pub fn notify_controller_presets_changed( + &self, + preset_manager: &FileBasedControllerPresetManager, + ) { + self.send_occasional_global_updates(|| { + [occasional_global_update::Update::controller_presets( + preset_manager, + )] + }); + } + + pub fn notify_main_presets_changed(&self, preset_manager: &FileBasedMainPresetManager) { + self.send_occasional_global_updates(|| { + [occasional_global_update::Update::main_presets( + preset_manager, + )] + }); + } + + pub fn notify_licenses_changed(&self, license_manager: &LicenseManager) { + self.send_occasional_global_updates(|| { + [occasional_global_update::Update::license_info( + license_manager, + )] + }); + #[cfg(feature = "playtime")] + self.send_occasional_playtime_engine_updates(|| { + [crate::infrastructure::proto::occasional_playtime_engine_update::Update::playtime_license_state()] + }); + } + + pub fn notify_controller_routing_changed(&self, unit_model: &UnitModel) { + let unit_id = unit_model.unit_id(); + self.send_occasional_unit_updates(unit_model.instance_id(), || { + [QualifiedOccasionalUnitUpdate { + unit_id: unit_id.into(), + update: Some( + qualified_occasional_unit_update::Update::controller_routing(unit_model), + ), + }] + }) + } + + pub fn notify_controller_config_changed(&self, controller_manager: &ControllerManager) { + self.send_occasional_global_updates(|| { + [occasional_global_update::Update::controller_config( + controller_manager, + )] + }); + } + + pub fn send_global_events_caused_by_reaper_change_events(&self, change_events: &[ChangeEvent]) { + self.send_occasional_global_updates(|| { + change_events.iter().filter_map(|event| match event { + ChangeEvent::PlayStateChanged(e) => Some( + occasional_global_update::Update::arrangement_play_state(e.new_value), + ), + _ => None, + }) + }) + } + + fn send_occasional_global_updates(&self, create_updates: F) + where + F: FnOnce() -> I, + I: IntoIterator, + { + let sender = &self.senders.occasional_global_update_sender; + if sender.receiver_count() == 0 { + return; + } + let wrapped_updates = create_updates() + .into_iter() + .map(|u| OccasionalGlobalUpdate { update: Some(u) }); + let _ = sender.send(wrapped_updates.collect()); + } + + fn send_occasional_instance_updates(&self, instance_id: InstanceId, create_updates: F) + where + F: FnOnce() -> I, + I: IntoIterator, + { + let sender = &self.senders.occasional_instance_update_sender; + if sender.receiver_count() == 0 { + return; + } + let wrapped_updates = create_updates() + .into_iter() + .map(|u| OccasionalInstanceUpdate { update: Some(u) }); + let batch_event = OccasionalInstanceUpdateBatch { + instance_id, + value: wrapped_updates.collect(), + }; + let _ = sender.send(batch_event); + } + + fn send_occasional_unit_updates(&self, instance_id: InstanceId, create_updates: F) + where + F: FnOnce() -> I, + I: IntoIterator, + { + let sender = &self.senders.occasional_unit_update_sender; + if sender.receiver_count() == 0 { + return; + } + let batch_event = OccasionalUnitUpdateBatch { + instance_id, + value: create_updates().into_iter().collect(), + }; + let _ = sender.send(batch_event); + } + + pub fn notify_instance_units_changed(&self, instance_shell: &InstanceShell) { + self.send_occasional_instance_updates(instance_shell.instance_id(), || { + [occasional_instance_update::Update::units(instance_shell)] + }); + } + + pub fn notify_instance_settings_changed(&self, instance_shell: &InstanceShell) { + self.send_occasional_instance_updates(instance_shell.instance_id(), || { + [occasional_instance_update::Update::settings(instance_shell)] + }); + } + + pub fn notify_everything_in_instance_has_changed(&self, instance_id: InstanceId) { + self.send_occasional_instance_updates(instance_id, || { + [occasional_instance_update::Update::EverythingHasChanged( + true, + )] + }); + } + + pub fn notify_everything_in_unit_has_changed(&self, instance_id: InstanceId, unit_id: UnitId) { + self.send_occasional_unit_updates(instance_id, || { + [QualifiedOccasionalUnitUpdate { + unit_id: unit_id.into(), + update: Some(qualified_occasional_unit_update::Update::EverythingHasChanged(true)), + }] + }); + } +} + +#[cfg(feature = "playtime")] +mod playtime_impl { + use std::collections::HashMap; + + use reaper_high::{AvailablePanValue, ChangeEvent, Guid, PanExt, Project, Track}; + use reaper_medium::Db; + + use base::hash_util::NonCryptoHashMap; + use base::peak_util; + use playtime_api::persistence::EvenQuantization; + use playtime_clip_engine::{ + base::{ClipMatrixEvent, Matrix, PlaytimeTrackInputProps}, + clip_timeline, + rt::{ + ClipChangeEvent, QualifiedClipChangeEvent, QualifiedSlotChangeEvent, SlotChangeEvent, + }, + Laziness, Timeline, + }; + + use crate::domain::InstanceId; + use crate::infrastructure::proto; + use crate::infrastructure::proto::senders::{ + ContinuousColumnUpdateBatch, ContinuousMatrixUpdateBatch, ContinuousSlotUpdateBatch, + OccasionalClipUpdateBatch, OccasionalColumnUpdateBatch, OccasionalMatrixUpdateBatch, + OccasionalRowUpdateBatch, OccasionalSlotUpdateBatch, OccasionalTrackUpdateBatch, + }; + use crate::infrastructure::proto::{ + occasional_matrix_update, occasional_track_update, qualified_occasional_clip_update, + qualified_occasional_column_update, qualified_occasional_row_update, + qualified_occasional_slot_update, ContinuousColumnUpdate, ContinuousMatrixUpdate, + ContinuousSlotUpdate, OccasionalMatrixUpdate, OccasionalTrackUpdate, + QualifiedContinuousSlotUpdate, QualifiedOccasionalClipUpdate, + QualifiedOccasionalColumnUpdate, QualifiedOccasionalRowUpdate, + QualifiedOccasionalSlotUpdate, QualifiedOccasionalTrackUpdate, SlotAddress, + }; + use crate::infrastructure::proto::{ + occasional_playtime_engine_update, OccasionalPlaytimeEngineUpdate, ProtoHub, + }; + + impl ProtoHub { + pub fn notify_engine_stats_changed(&self) { + self.send_occasional_playtime_engine_updates(|| { + [occasional_playtime_engine_update::Update::engine_stats()] + }); + } + + pub fn notify_playtime_settings_changed(&self) { + self.send_occasional_playtime_engine_updates(|| { + [occasional_playtime_engine_update::Update::engine_settings()] + }); + } + + pub fn send_occasional_playtime_engine_updates(&self, create_updates: F) + where + F: FnOnce() -> I, + I: IntoIterator, + { + let sender = &self.senders.occasional_playtime_engine_update_sender; + if sender.receiver_count() == 0 { + return; + } + let wrapped_updates = create_updates() + .into_iter() + .map(|u| OccasionalPlaytimeEngineUpdate { update: Some(u) }); + let _ = sender.send(wrapped_updates.collect()); + } + + pub fn notify_clip_matrix_changed( + &self, + matrix_id: InstanceId, + matrix: &Matrix, + events: &[ClipMatrixEvent], + is_poll: bool, + ) { + let project = matrix.project(); + self.send_occasional_matrix_updates_caused_by_matrix(matrix_id, matrix, events); + self.send_occasional_track_updates_caused_by_matrix(matrix_id, events); + self.send_occasional_column_updates(matrix_id, matrix, events); + self.send_occasional_row_updates(matrix_id, matrix, events); + self.send_occasional_slot_updates(matrix_id, matrix, events); + self.send_occasional_clip_updates(matrix_id, matrix, events); + self.send_continuous_slot_updates(matrix_id, events); + if is_poll { + self.send_continuous_matrix_updates(matrix_id, project); + self.send_continuous_column_updates(matrix_id, matrix); + } + } + + fn send_occasional_matrix_updates_caused_by_matrix( + &self, + matrix_id: InstanceId, + matrix: &Matrix, + events: &[ClipMatrixEvent], + ) { + let sender = &self.senders.occasional_matrix_update_sender; + if sender.receiver_count() == 0 { + return; + } + let mut updates = vec![]; + let everything_changed = events + .iter() + .any(|e| matches!(e, ClipMatrixEvent::EverythingChanged)); + if everything_changed { + let changed_update = OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::EverythingHasChanged(true)), + }; + updates.push(changed_update); + } else { + // Misc updates + let misc_updates = events.iter().flat_map(|event| match event { + ClipMatrixEvent::MatrixSettingsChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::settings(matrix)), + }), + ClipMatrixEvent::HistoryChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::history_state(matrix)), + }), + ClipMatrixEvent::ClickEnabledChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::click_enabled(matrix)), + }), + ClipMatrixEvent::ClickVolumeChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::click_volume(matrix)), + }), + ClipMatrixEvent::ClickChannelChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::click_channel(matrix)), + }), + ClipMatrixEvent::TempoTapVolumeChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::tempo_tap_volume(matrix)), + }), + ClipMatrixEvent::SilenceModeChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::silence_mode(matrix)), + }), + ClipMatrixEvent::ActiveCellChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::active_cell(matrix)), + }), + ClipMatrixEvent::ControlUnitsChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::control_unit_config( + matrix, + )), + }), + ClipMatrixEvent::TimeSignatureChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::time_signature( + matrix.project(), + )), + }), + ClipMatrixEvent::SequencerPlayStateChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::sequencer_play_state( + matrix.sequencer().status(), + )), + }), + ClipMatrixEvent::SequencerChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::sequencer( + matrix.sequencer(), + )), + }), + ClipMatrixEvent::Info(evt) => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::info_event(evt.clone())), + }), + ClipMatrixEvent::SimpleMappingsChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::simple_mappings(matrix)), + }), + ClipMatrixEvent::LearningTargetChanged => Some(OccasionalMatrixUpdate { + update: Some(occasional_matrix_update::Update::learn_state(matrix)), + }), + _ => None, + }); + updates.extend(misc_updates); + } + // Send all updates + if !updates.is_empty() { + let batch_event = OccasionalMatrixUpdateBatch { + instance_id: matrix_id, + value: updates, + }; + let _ = sender.send(batch_event); + } + } + + fn send_occasional_track_updates_caused_by_matrix( + &self, + matrix_id: InstanceId, + events: &[ClipMatrixEvent], + ) { + let sender = &self.senders.occasional_track_update_sender; + if sender.receiver_count() == 0 { + return; + } + fn track_update( + track: &Track, + update: occasional_track_update::Update, + ) -> QualifiedOccasionalTrackUpdate { + QualifiedOccasionalTrackUpdate { + track_id: track.guid().to_string_without_braces(), + track_updates: vec![OccasionalTrackUpdate { + update: Some(update), + }], + } + } + let mut updates = vec![]; + for e in events { + if let ClipMatrixEvent::TrackChanged(track) = e { + let input_props = PlaytimeTrackInputProps::from_reaper_track(track); + let arm_update = track_update( + track, + occasional_track_update::Update::armed(input_props.armed), + ); + let input_monitoring_update = track_update( + track, + occasional_track_update::Update::input_monitoring( + input_props.input_monitoring, + ), + ); + let input_update = + track_update(track, occasional_track_update::Update::input(track)); + let color_update = + track_update(track, occasional_track_update::Update::color(track)); + updates.push(arm_update); + updates.push(input_monitoring_update); + updates.push(input_update); + updates.push(color_update); + } + } + // Send all updates + if !updates.is_empty() { + let batch_event = OccasionalTrackUpdateBatch { + instance_id: matrix_id, + value: updates, + }; + let _ = sender.send(batch_event); + } + } + + fn send_occasional_column_updates( + &self, + matrix_id: InstanceId, + matrix: &Matrix, + events: &[ClipMatrixEvent], + ) { + let sender = &self.senders.occasional_column_update_sender; + if sender.receiver_count() == 0 { + return; + } + let updates: Vec<_> = events + .iter() + .filter_map(|event| match event { + ClipMatrixEvent::ColumnSettingsChanged(column_index) => { + let update = qualified_occasional_column_update::Update::settings( + matrix, + *column_index, + ) + .ok()?; + Some(QualifiedOccasionalColumnUpdate { + column_index: *column_index as _, + update: Some(update), + }) + } + _ => None, + }) + .collect(); + if !updates.is_empty() { + let batch_event = OccasionalColumnUpdateBatch { + instance_id: matrix_id, + value: updates, + }; + let _ = sender.send(batch_event); + } + } + + fn send_occasional_row_updates( + &self, + matrix_id: InstanceId, + matrix: &Matrix, + events: &[ClipMatrixEvent], + ) { + let sender = &self.senders.occasional_row_update_sender; + if sender.receiver_count() == 0 { + return; + } + let updates: Vec<_> = events + .iter() + .filter_map(|event| match event { + ClipMatrixEvent::RowChanged(row_index) => { + let update = + qualified_occasional_row_update::Update::data(matrix, *row_index) + .ok()?; + Some(QualifiedOccasionalRowUpdate { + row_index: *row_index as _, + update: Some(update), + }) + } + _ => None, + }) + .collect(); + if !updates.is_empty() { + let batch_event = OccasionalRowUpdateBatch { + instance_id: matrix_id, + value: updates, + }; + let _ = sender.send(batch_event); + } + } + + fn send_occasional_slot_updates( + &self, + matrix_id: InstanceId, + matrix: &Matrix, + events: &[ClipMatrixEvent], + ) { + let sender = &self.senders.occasional_slot_update_sender; + if sender.receiver_count() == 0 { + return; + } + let updates: Vec<_> = events + .iter() + .filter_map(|event| match event { + ClipMatrixEvent::SlotChanged(QualifiedSlotChangeEvent { + slot_address: slot_coordinates, + event, + }) => { + use SlotChangeEvent::*; + let update = match event { + PlayState(play_state) => { + qualified_occasional_slot_update::Update::play_state(*play_state) + } + Clips(_) => { + let slot = matrix.find_slot(*slot_coordinates)?; + qualified_occasional_slot_update::Update::complete_persistent_data( + matrix, slot, + ) + } + Continuous { .. } => return None, + }; + Some(QualifiedOccasionalSlotUpdate { + slot_address: Some(SlotAddress::from_engine(*slot_coordinates)), + update: Some(update), + }) + } + _ => None, + }) + .collect(); + if !updates.is_empty() { + let batch_event = OccasionalSlotUpdateBatch { + instance_id: matrix_id, + value: updates, + }; + let _ = sender.send(batch_event); + } + } + + fn send_occasional_clip_updates( + &self, + matrix_id: InstanceId, + matrix: &Matrix, + events: &[ClipMatrixEvent], + ) { + let sender = &self.senders.occasional_clip_update_sender; + if sender.receiver_count() == 0 { + return; + } + let updates: Vec<_> = events + .iter() + .filter_map(|event| match event { + ClipMatrixEvent::ClipChanged(QualifiedClipChangeEvent { + clip_address, + event, + }) => { + use ClipChangeEvent::*; + let employment = matrix.find_clip_employment(*clip_address)?; + let update = match event { + Everything | Volume(_) | Looped(_) => { + qualified_occasional_clip_update::Update::complete_persistent_data( + matrix, + &employment.clip, + ) + .ok()? + } + Content => { + qualified_occasional_clip_update::Update::content_info(employment) + } + }; + Some(QualifiedOccasionalClipUpdate { + clip_address: Some(proto::ClipAddress::from_engine(*clip_address)), + update: Some(update), + }) + } + _ => None, + }) + .collect(); + if !updates.is_empty() { + let batch_event = OccasionalClipUpdateBatch { + instance_id: matrix_id, + value: updates, + }; + let _ = sender.send(batch_event); + } + } + + pub fn send_occasional_matrix_updates_caused_by_reaper( + &self, + matrix_id: InstanceId, + matrix: &Matrix, + events: &[ChangeEvent], + ) { + use occasional_track_update::Update; + enum R { + Matrix(occasional_matrix_update::Update), + Track(QualifiedOccasionalTrackUpdate), + } + let matrix_update_sender = &self.senders.occasional_matrix_update_sender; + let track_update_sender = &self.senders.occasional_track_update_sender; + if matrix_update_sender.receiver_count() == 0 + && track_update_sender.receiver_count() == 0 + { + return; + } + fn track_normal_fx_chain_update(track: &Track) -> R { + track_update(track, || Update::normal_fx_chain(track)) + } + fn track_update(track: &Track, create_update: impl FnOnce() -> Update) -> R { + R::Track(QualifiedOccasionalTrackUpdate { + track_id: track.guid().to_string_without_braces(), + track_updates: vec![OccasionalTrackUpdate { + update: Some(create_update()), + }], + }) + } + fn column_track_update( + matrix: &Matrix, + track: &Track, + create_update: impl FnOnce() -> Update, + ) -> Option { + if matrix.uses_playback_track(track) { + Some(track_update(track, create_update)) + } else { + None + } + } + // Handle batches of events + let track_list_might_have_changed = events.iter().any(|evt| { + matches!( + evt, + ChangeEvent::TrackAdded(_) + | ChangeEvent::TrackRemoved(_) + | ChangeEvent::TracksReordered(_) + ) + }); + if track_list_might_have_changed { + let update = occasional_matrix_update::Update::track_list(matrix.project()); + let _ = matrix_update_sender.send(OccasionalMatrixUpdateBatch { + instance_id: matrix_id, + value: vec![OccasionalMatrixUpdate { + update: Some(update), + }], + }); + } + // Handle single events + for event in events { + let update: Option = match event { + ChangeEvent::TrackVolumeChanged(e) => { + let db = e.new_value.to_db_ex(Db::MINUS_INF); + if e.track.is_master_track() { + Some(R::Matrix(occasional_matrix_update::Update::master_volume( + db, + ))) + } else { + column_track_update(matrix, &e.track, || Update::volume(db)) + } + } + ChangeEvent::TrackPanChanged(e) => { + let val = match e.new_value { + AvailablePanValue::Complete(v) => v.main_pan(), + AvailablePanValue::Incomplete(v) => v, + }; + if e.track.is_master_track() { + Some(R::Matrix(occasional_matrix_update::Update::pan(val))) + } else { + column_track_update(matrix, &e.track, || Update::pan(val)) + } + } + ChangeEvent::FxAdded(e) => e.fx.track().map(track_normal_fx_chain_update), + ChangeEvent::FxRemoved(e) => e.fx.track().map(track_normal_fx_chain_update), + ChangeEvent::FxReordered(e) => Some(track_normal_fx_chain_update(&e.track)), + ChangeEvent::TrackNameChanged(e) => { + Some(track_update(&e.track, || Update::name(&e.track))) + } + ChangeEvent::TrackInputChanged(e) => { + column_track_update(matrix, &e.track, || Update::input(&e.track)) + } + ChangeEvent::TrackInputMonitoringChanged(e) => { + column_track_update(matrix, &e.track, || { + let input_props = PlaytimeTrackInputProps::from_reaper_track(&e.track); + Update::input_monitoring(input_props.input_monitoring) + }) + } + ChangeEvent::TrackArmChanged(e) => { + column_track_update(matrix, &e.track, || { + let input_props = PlaytimeTrackInputProps::from_reaper_track(&e.track); + Update::armed(input_props.armed) + }) + } + ChangeEvent::TrackMuteChanged(e) => { + if e.track.is_master_track() { + Some(R::Matrix(occasional_matrix_update::Update::mute( + e.new_value, + ))) + } else { + column_track_update(matrix, &e.track, || Update::mute(e.new_value)) + } + } + ChangeEvent::TrackSoloChanged(e) => { + column_track_update(matrix, &e.track, || Update::solo(e.new_value)) + } + ChangeEvent::TrackSelectedChanged(e) => { + column_track_update(matrix, &e.track, || Update::selected(e.new_value)) + } + ChangeEvent::MasterTempoChanged(_) => { + // It's important not to take the given tempo value because REAPER doesn't really support + // project-specific tempo notifications. The change could come from any project. We query + // our matrix explicitly (and it might just as well turn out that the tempo for that matrix + // hasn't changed at all, but that's okay). + Some(R::Matrix(occasional_matrix_update::Update::tempo(matrix))) + } + ChangeEvent::MasterPlayRateChanged(_) => { + // It's the same with the play rate + Some(R::Matrix(occasional_matrix_update::Update::play_rate( + matrix, + ))) + } + _ => None, + }; + if let Some(update) = update { + match update { + R::Matrix(u) => { + let _ = matrix_update_sender.send(OccasionalMatrixUpdateBatch { + instance_id: matrix_id, + value: vec![OccasionalMatrixUpdate { update: Some(u) }], + }); + } + R::Track(u) => { + let _ = track_update_sender.send(OccasionalTrackUpdateBatch { + instance_id: matrix_id, + value: vec![u], + }); + } + } + } + } + } + + fn send_continuous_slot_updates(&self, matrix_id: InstanceId, events: &[ClipMatrixEvent]) { + let sender = &self.senders.continuous_slot_update_sender; + if sender.receiver_count() == 0 { + return; + } + let updates: Vec<_> = events + .iter() + .filter_map(|event| { + if let ClipMatrixEvent::SlotChanged(QualifiedSlotChangeEvent { + slot_address: slot_coordinates, + event: SlotChangeEvent::Continuous(clip_events), + }) = event + { + Some(QualifiedContinuousSlotUpdate { + slot_address: Some(SlotAddress::from_engine(*slot_coordinates)), + update: Some(ContinuousSlotUpdate::from_engine(clip_events)), + }) + } else { + None + } + }) + .collect(); + if !updates.is_empty() { + let batch_event = ContinuousSlotUpdateBatch { + instance_id: matrix_id, + value: updates, + }; + let _ = sender.send(batch_event); + } + } + + fn send_continuous_matrix_updates(&self, matrix_id: InstanceId, project: Project) { + let sender = &self.senders.continuous_matrix_update_sender; + if sender.receiver_count() == 0 { + return; + } + let timeline = clip_timeline(project, false); + let pos = timeline.cursor_pos().get(); + let bar_quantization = EvenQuantization::ONE_BAR; + let next_bar = timeline.next_quantized_pos_at( + pos, + bar_quantization, + Laziness::SuperEagerForNextPos, + ); + // TODO-high-playtime-refactoring We are mainly interested in beats relative to the bar in order to get a + // typical position display and a useful visual metronome. It's not super urgent because + // at least with the steady timeline, the app can derive a good metronome view because + // of the way the steady timeline is constructed. However, it gets wrong as soon as we + // use the REAPER timeline *and* use tempo markers. But this needs more attention anyway. + let full_beats = timeline.full_beats_at_pos(pos); + let batch_event = ContinuousMatrixUpdateBatch { + instance_id: matrix_id, + value: ContinuousMatrixUpdate { + second: pos.get(), + bar: (next_bar.position() - 1) as i32, + beat: full_beats.get(), + peaks: project + .master_track() + .map(|t| get_track_peaks(&t)) + .unwrap_or_default(), + }, + }; + let _ = sender.send(batch_event); + } + + fn send_continuous_column_updates(&self, matrix_id: InstanceId, matrix: &Matrix) { + let sender = &self.senders.continuous_column_update_sender; + if sender.receiver_count() == 0 { + return; + } + let column_update_by_track_guid: NonCryptoHashMap = + HashMap::with_capacity_and_hasher(matrix.column_count(), Default::default()); + let column_updates: Vec<_> = matrix + .columns() + .map(|column| { + if let Ok(track) = column.playback_track() { + if let Some(existing_update) = column_update_by_track_guid.get(track.guid()) + { + // We have already collected the update for this column's playback track. + existing_update.clone() + } else { + // We haven't yet collected the update for this column's playback track. + ContinuousColumnUpdate { + peaks: get_track_peaks(track), + play_lookahead: column.play_lookahead().get().get(), + } + } + } else { + ContinuousColumnUpdate { + peaks: vec![], + play_lookahead: 0.0, + } + } + }) + .collect(); + if !column_updates.is_empty() { + let batch_event = ContinuousColumnUpdateBatch { + instance_id: matrix_id, + value: column_updates, + }; + let _ = sender.send(batch_event); + } + } + } + + fn get_track_peaks(track: &Track) -> Vec { + if peak_util::peaks_should_be_hidden(track) { + return vec![]; + } + let Ok(track) = track.raw() else { + return vec![]; + }; + peak_util::get_track_peaks(track) + .map(|vol| vol.to_db_ex(Db::MINUS_INF).get()) + .collect() + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/initial_events.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/initial_events.rs new file mode 100644 index 0000000..cef17cd --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/initial_events.rs @@ -0,0 +1,123 @@ +use crate::infrastructure::plugin::{BackboneShell, InstanceShell}; +use crate::infrastructure::proto::{ + occasional_global_update, occasional_instance_update, qualified_occasional_unit_update, + OccasionalGlobalUpdate, OccasionalInstanceUpdate, QualifiedOccasionalUnitUpdate, Scope, + Severity, +}; +use reaper_high::Reaper; +use std::iter; + +pub fn create_initial_global_updates() -> Vec { + use occasional_global_update::Update; + fn create(updates: impl Iterator) -> Vec { + updates + .into_iter() + .map(|u| OccasionalGlobalUpdate { update: Some(u) }) + .collect() + } + let global_updates = [ + Update::instances(), + Update::midi_input_devices(), + Update::midi_output_devices(), + Update::host_color_scheme(), + Update::arrangement_play_state(Reaper::get().current_project().play_state()), + // TODO-high-playtime-after-release Update when changed + Update::audio_input_channels(), + Update::resample_modes(), + Update::pitch_shift_modes(), + Update::controller_presets(&BackboneShell::get().controller_preset_manager().borrow()), + Update::main_presets(&BackboneShell::get().main_preset_manager().borrow()), + Update::controller_config(&BackboneShell::get().controller_manager().borrow()), + Update::license_info(&BackboneShell::get().license_manager().borrow()), + ]; + create(global_updates.into_iter().chain(Update::update_info())) +} + +pub fn create_initial_instance_updates( + instance_shell: &InstanceShell, +) -> Vec { + use occasional_instance_update::Update; + fn create(updates: impl Iterator) -> Vec { + updates + .into_iter() + .map(|u| OccasionalInstanceUpdate { update: Some(u) }) + .collect() + } + let fixed_instance_updates = [ + Update::settings(instance_shell), + Update::units(instance_shell), + ]; + let reaper_version = Reaper::get().version(); + let reaper_revision = reaper_version.revision(); + let mut warnings = vec![]; + // Check macOS throttle mouse settings + if cfg!(target_os = "macos") { + if let Ok(var) = Reaper::get().get_preference_ref::("osxdisplayoptions") { + let flags = *var as u32; + let wheel_flag = flags & 64 > 0; + let swipe_flag = flags & 128 > 0; + let move_flag = flags & 256 > 0; + if !wheel_flag || !swipe_flag || !move_flag { + let msg = "At least one of the checkboxes in the REAPER preference \"Options → Preferences/Settings... → General → Advanced UI/system tweaks... → Throttle mouse-events\" is not enabled. This will cause temporary user interface lags in REAPER and Playtime while using the mouse or touchpad in REAPER, e.g. when adjusting the track volume. Enabling all checkboxes will improve your REAPER experience in general, not just when using Playtime!"; + warnings.push(Update::warning( + Severity::Low, + Some(Scope::Playtime), + msg.to_string(), + )); + } + } + } + // Playtime checks + if cfg!(feature = "playtime") { + #[cfg(feature = "playtime")] + { + if playtime_clip_engine::thread_util::setting_thread_prios_failed() { + let mut msg = "Setting thread priorities was not successful! This means that clip playback and recording performance will not be optimal!".to_string(); + if cfg!(target_os = "linux") && reaper_revision < "7.30+dev0116" { + msg += " This issue should disappear as soon as you update to the upcoming REAPER version 7.31+."; + } + warnings.push(Update::warning(Severity::High, Some(Scope::Playtime), msg)); + } + } + // Check minimum REAPER version + if reaper_revision < MIN_REAPER_VERSION_FOR_PLAYTIME { + let msg = format!("You are using REAPER version {reaper_revision}, which is not optimal for running Playtime. You may experience issues of all kinds (timing, keyboard control, ...)! For an optimal experience, please upgrade to at least REAPER version {MIN_REAPER_VERSION_FOR_PLAYTIME}!"); + warnings.push(Update::warning(Severity::High, Some(Scope::Playtime), msg)); + } + // Check REAPER preference "Stop/repeat playback at and of project" + if let Ok(var) = Reaper::get().get_preference_ref::("stopprojlen") { + let stop_at_end = *var; + if stop_at_end > 0 { + let msg = "You have enabled the REAPER preference \"Options → Preferences/Settings... → Audio → Playback → Stop/repeat playback at end of project\". This prevents Playtime from playing along with your REAPER arrangement if the arrangement is empty or ends prematurely. To ensure smooth operation, we highly recommend disabling this option."; + warnings.push(Update::warning( + Severity::High, + Some(Scope::Playtime), + msg.to_string(), + )); + } + } + } + create( + fixed_instance_updates + .into_iter() + .chain(iter::once(Update::warnings(warnings))), + ) +} + +const MIN_REAPER_VERSION_FOR_PLAYTIME: &str = "7.23"; + +pub fn create_initial_unit_updates( + instance_shell: &InstanceShell, +) -> Vec { + use qualified_occasional_unit_update::Update; + instance_shell + .all_unit_models() + .map(|unit_model| { + let unit_model = unit_model.borrow(); + QualifiedOccasionalUnitUpdate { + unit_id: unit_model.unit_id().into(), + update: Some(Update::controller_routing(&unit_model)), + } + }) + .collect() +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/initial_playtime_events.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/initial_playtime_events.rs new file mode 100644 index 0000000..204d6ab --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/initial_playtime_events.rs @@ -0,0 +1,148 @@ +use std::iter; + +use reaper_high::{Guid, Track}; +use reaper_medium::Db; + +use base::hash_util::NonCryptoHashMap; +use playtime_clip_engine::base::{Matrix, PlaytimeTrackInputProps}; + +use crate::infrastructure::proto::{ + occasional_matrix_update, occasional_track_update, qualified_occasional_clip_update, + qualified_occasional_slot_update, ClipAddress, OccasionalMatrixUpdate, + OccasionalPlaytimeEngineUpdate, OccasionalTrackUpdate, QualifiedOccasionalClipUpdate, + QualifiedOccasionalSlotUpdate, QualifiedOccasionalTrackUpdate, SlotAddress, +}; + +pub fn create_initial_engine_updates() -> Vec { + use crate::infrastructure::proto::generated::occasional_playtime_engine_update::Update; + fn create(updates: impl Iterator) -> Vec { + updates + .into_iter() + .map(|u| OccasionalPlaytimeEngineUpdate { update: Some(u) }) + .collect() + } + let updates = [Update::playtime_license_state(), Update::engine_settings()]; + create(updates.into_iter()) +} + +pub fn create_initial_matrix_updates(matrix: Option<&Matrix>) -> Vec { + use occasional_matrix_update::Update; + fn create(updates: impl Iterator) -> Vec { + updates + .into_iter() + .map(|u| OccasionalMatrixUpdate { update: Some(u) }) + .collect() + } + let Some(matrix) = matrix else { + return create(iter::once(Update::MatrixExists(false))); + }; + let project = matrix.project(); + let master_track = project.master_track().expect("project gone"); + let updates = [ + Update::MatrixExists(true), + Update::master_volume(master_track.volume().to_db_ex(Db::MINUS_INF)), + Update::click_volume(matrix), + Update::tempo_tap_volume(matrix), + Update::pan(master_track.pan().reaper_value()), + Update::mute(master_track.is_muted()), + Update::tempo(matrix), + Update::play_rate(matrix), + Update::sequencer_play_state(matrix.sequencer().status()), + Update::complete_persistent_data(matrix), + Update::history_state(matrix), + Update::click_enabled(matrix), + Update::silence_mode(matrix), + Update::has_unloaded_content(matrix), + Update::time_signature(project), + Update::track_list(project), + Update::simple_mappings(matrix), + Update::learn_state(matrix), + Update::active_cell(matrix), + Update::control_unit_config(matrix), + ]; + create(updates.into_iter()) +} + +pub fn create_initial_track_updates( + matrix: Option<&Matrix>, +) -> Vec { + let Some(matrix) = matrix else { + return vec![]; + }; + let track_by_guid: NonCryptoHashMap = matrix + .columns() + .flat_map(|column| { + column + .playback_track() + .into_iter() + .cloned() + .chain(column.effective_recording_track()) + }) + .map(|track| (*track.guid(), track)) + .collect(); + track_by_guid + .into_iter() + .map(|(guid, track)| { + let input_props = PlaytimeTrackInputProps::from_reaper_track(&track); + use occasional_track_update::Update; + QualifiedOccasionalTrackUpdate { + track_id: guid.to_string_without_braces(), + track_updates: [ + Update::name(&track), + Update::color(&track), + Update::input(&track), + Update::armed(input_props.armed), + Update::input_monitoring(input_props.input_monitoring), + Update::mute(track.is_muted()), + Update::solo(track.is_solo()), + Update::selected(track.is_selected()), + Update::volume(track.volume().to_db_ex(Db::MINUS_INF)), + Update::pan(track.pan().reaper_value()), + Update::normal_fx_chain(&track), + ] + .into_iter() + .map(|update| OccasionalTrackUpdate { + update: Some(update), + }) + .collect(), + } + }) + .collect() +} + +pub fn create_initial_slot_updates(matrix: Option<&Matrix>) -> Vec { + let Some(matrix) = matrix else { + return vec![]; + }; + matrix + .slots() + .map(|slot| { + let play_state = slot.value().play_state(); + let address = SlotAddress { + column_index: slot.column_index() as u32, + row_index: slot.value().index() as u32, + }; + QualifiedOccasionalSlotUpdate { + slot_address: Some(address), + update: Some(qualified_occasional_slot_update::Update::play_state( + play_state, + )), + } + }) + .collect() +} + +pub fn create_initial_clip_updates(matrix: Option<&Matrix>) -> Vec { + let Some(matrix) = matrix else { + return vec![]; + }; + matrix + .clip_employments() + .map(|item| QualifiedOccasionalClipUpdate { + clip_address: Some(ClipAddress::from_engine(item.address)), + update: Some(qualified_occasional_clip_update::Update::content_info( + item.clip_employment, + )), + }) + .collect() +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/mod.rs new file mode 100644 index 0000000..0438717 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/mod.rs @@ -0,0 +1,44 @@ +#[allow(clippy::enum_variant_names)] +mod generated; +pub use generated::*; + +mod ext; + +#[cfg(feature = "playtime")] +mod playtime_ext; + +mod service_impl; +pub use service_impl::*; + +mod request_handler; +pub use request_handler::*; + +#[cfg(feature = "playtime")] +mod playtime_request_handler; +#[cfg(feature = "playtime")] +pub use playtime_request_handler::*; + +mod hub; +pub use hub::*; + +mod senders; +pub use senders::*; + +mod initial_events; +pub use initial_events::*; + +#[cfg(feature = "playtime")] +mod initial_playtime_events; +#[cfg(feature = "playtime")] +pub use initial_playtime_events::*; + +/// The app will check this version and bail if it's not compatible! +/// +/// Its major number must be increased whenever there's a breaking change in the API that we provide +/// to the app. This includes the Protobuf/gRPC API and things related to embedding (such as the +/// way how the host callback works). +/// +/// It's important to get this right to get good error messages from customers. Knowing +/// that they just have an incompatible version mix (e.g., due to manual installing) makes it +/// trivial to respond to bug reports. +pub const HOST_API_VERSION: &str = "16.0.0"; diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/playtime_ext.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/playtime_ext.rs new file mode 100644 index 0000000..4139905 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/playtime_ext.rs @@ -0,0 +1,549 @@ +use std::num::NonZeroU32; + +use helgoboss_license_api::persistence::LicenseData; +use helgoboss_license_api::runtime::License; +use helgoboss_midi::Channel; +use reaper_high::{Project, Reaper}; +use reaper_medium::{Db, MidiInputDeviceId, ReaperPanValue, RecordingInput}; + +use playtime_api::runtime::ControlUnitConfig; +use playtime_clip_engine::base::{ + Clip, ClipEmployment, ClipSource, ColumnTrackInputMonitoring, History, Matrix, MatrixSequencer, + SaveOptions, SequencerStatus, Slot, +}; +use playtime_clip_engine::rt::{ + ClipPlayState, ContinuousClipChangeEvent, ContinuousClipChangeEvents, +}; +use playtime_clip_engine::{ + base, clip_timeline, PlaytimeEngine, SteadyProjectTimelineHandle, Timeline, +}; + +use crate::infrastructure::proto::track_input::Input; +use crate::infrastructure::proto::{ + clip_content_info, generated, occasional_matrix_update, occasional_playtime_engine_update, + occasional_track_update, qualified_occasional_clip_update, qualified_occasional_column_update, + qualified_occasional_row_update, qualified_occasional_slot_update, AudioClipContentInfo, + CellAddress, ClipAddress, ClipContentInfo, ColumnKind, ContinuousClipUpdate, + ContinuousSlotUpdate, Fx, FxChain, HistoryState, LearnState, LicenseState, MidiClipContentInfo, + PlaytimeEngineStats, RgbColor, SequencerPlayState, SlotAddress, SlotPlayState, TimeSignature, + TrackInput, TrackInputMonitoring, TrackList, TrackMidiInput, +}; + +impl occasional_playtime_engine_update::Update { + pub fn engine_settings() -> Self { + let settings = PlaytimeEngine::get().main().settings(); + let json = serde_json::to_string(&settings).expect("couldn't serialize playtime settings"); + Self::EngineSettings(json) + } + + pub fn engine_stats() -> Self { + let project = Reaper::get().current_project(); + SteadyProjectTimelineHandle::new(project).with_timeline(|timeline| { + let pre_buffer_stat = timeline.last_pre_buffered_blocks_of_playing_clips_stat(); + let engine_stats = PlaytimeEngine::get().stats(); + let stats = PlaytimeEngineStats { + min_buffered_blocks: pre_buffer_stat.min() as _, + avg_buffered_blocks: pre_buffer_stat.avg() as _, + max_buffered_blocks: pre_buffer_stat.max() as _, + future_size_in_blocks: timeline.tempo_buffer_future_size() as _, + num_pre_buffer_fallbacks: engine_stats.num_pre_buffer_fallbacks, + num_pre_buffer_misses: engine_stats.num_pre_buffer_misses, + }; + Self::EngineStats(stats) + }) + } + + pub fn playtime_license_state() -> Self { + let value = { + #[cfg(feature = "playtime")] + { + let clip_engine = playtime_clip_engine::PlaytimeEngine::get().main(); + if clip_engine.has_valid_license() { + clip_engine.license() + } else { + None + } + } + #[cfg(not(feature = "playtime"))] + { + None + } + }; + let json = value.map(|license: License| { + let license_data = LicenseData::from(license); + serde_json::to_string(&license_data.payload) + .expect("couldn't represent license payload as JSON") + }); + Self::LicenseState(LicenseState { + license_payload: json, + }) + } +} + +impl occasional_matrix_update::Update { + pub fn master_volume(db: Db) -> Self { + Self::MasterVolume(db.get()) + } + + pub fn click_volume(matrix: &Matrix) -> Self { + Self::ClickVolume(matrix.click_volume().get()) + } + + pub fn click_channel(matrix: &Matrix) -> Self { + Self::ClickChannel(matrix.click_channel()) + } + + pub fn tempo_tap_volume(matrix: &Matrix) -> Self { + Self::TempoTapVolume(matrix.tempo_tap_volume().get()) + } + + pub fn pan(pan: ReaperPanValue) -> Self { + Self::Pan(pan.get()) + } + + pub fn mute(mute: bool) -> Self { + Self::Mute(mute) + } + + pub fn tempo(matrix: &Matrix) -> Self { + Self::Tempo(matrix.project_tempo().get()) + } + + pub fn play_rate(matrix: &Matrix) -> Self { + Self::PlayRate(matrix.play_rate().get()) + } + + pub fn sequencer_play_state(play_state: base::SequencerStatus) -> Self { + Self::SequencerPlayState(SequencerPlayState::from_engine(play_state).into()) + } + + pub fn sequencer(sequencer: &MatrixSequencer) -> Self { + let api_sequencer = sequencer.save(SaveOptions { + include_contents: false, + }); + let json = + serde_json::to_string(&api_sequencer).expect("couldn't represent sequencer as JSON"); + Self::Sequencer(json) + } + + pub fn info_event(event: playtime_api::runtime::InfoEvent) -> Self { + let json = serde_json::to_string(&event).expect("couldn't represent info event as JSON"); + Self::InfoEvent(json) + } + + pub fn simple_mappings(matrix: &Matrix) -> Self { + let container = matrix.get_simple_mappings(); + let json = + serde_json::to_string(&container).expect("couldn't represent simple mappings as JSON"); + Self::SimpleMappingContainer(json) + } + + pub fn learn_state(matrix: &Matrix) -> Self { + let learning_target = matrix.get_currently_learning_target(); + Self::LearnState(LearnState { + simple_mapping_target: learning_target.map(|s| { + serde_json::to_string(&s).expect("couldn't represent learning target as JSON") + }), + }) + } + + pub fn active_cell(matrix: &Matrix) -> Self { + let active_cell = matrix.active_cell(); + Self::ActiveCell(CellAddress::from_engine(active_cell)) + } + + pub fn control_unit_config(matrix: &Matrix) -> Self { + let config = ControlUnitConfig { + control_units: matrix.get_control_units(), + }; + let json = + serde_json::to_string(&config).expect("couldn't represent control unit config as JSON"); + Self::ControlUnitConfig(json) + } + + pub fn complete_persistent_data(matrix: &Matrix) -> Self { + let matrix_json = serde_json::to_string(&matrix.save_without_contents()) + .expect("couldn't represent matrix as JSON"); + Self::CompletePersistentData(matrix_json) + } + + pub fn settings(matrix: &Matrix) -> Self { + let settings_json = serde_json::to_string(&matrix.all_matrix_settings_combined()) + .expect("couldn't represent matrix settings as JSON"); + Self::Settings(settings_json) + } + + pub fn history_state(matrix: &Matrix) -> Self { + Self::HistoryState(HistoryState::from_engine(matrix.history())) + } + + pub fn click_enabled(matrix: &Matrix) -> Self { + Self::ClickEnabled(matrix.click_is_enabled()) + } + + pub fn silence_mode(matrix: &Matrix) -> Self { + Self::SilenceMode(matrix.is_in_silence_mode()) + } + + pub fn has_unloaded_content(matrix: &Matrix) -> Self { + Self::HasUnloadedContent(matrix.has_unloaded_content()) + } + + pub fn time_signature(project: Project) -> Self { + Self::TimeSignature(TimeSignature::from_engine(project)) + } + + pub fn track_list(project: Project) -> Self { + Self::TrackList(TrackList::from_engine(project)) + } +} + +impl generated::TrackList { + pub fn from_engine(project: Project) -> Self { + let mut level = 0i32; + let tracks = project.tracks().map(|t| { + let folder_depth_change = t.folder_depth_change(); + let track = generated::TrackInList::from_engine(t, level.unsigned_abs()); + level += folder_depth_change; + track + }); + Self { + tracks: tracks.collect(), + } + } +} + +impl generated::TrackInList { + pub fn from_engine(track: reaper_high::Track, level: u32) -> Self { + Self { + id: track.guid().to_string_without_braces(), + name: track.name().unwrap_or_default().into_string(), + level, + } + } +} + +impl ColumnKind { + pub fn to_engine(self) -> playtime_clip_engine::base::ColumnKind { + match self { + ColumnKind::Audio => base::ColumnKind::Audio, + ColumnKind::Midi => base::ColumnKind::Midi, + } + } +} + +impl TimeSignature { + pub fn to_engine(&self) -> Result { + let sig = reaper_medium::TimeSignature { + numerator: NonZeroU32::new(self.numerator).ok_or("numerator is zero")?, + denominator: NonZeroU32::new(self.denominator).ok_or("denominator is zero")?, + }; + Ok(sig) + } + + pub fn from_engine(project: Project) -> Self { + let timeline = clip_timeline(project, true); + let time_signature = timeline.time_signature_at(timeline.cursor_pos().get()); + TimeSignature { + numerator: time_signature.numerator.get(), + denominator: time_signature.denominator.get(), + } + } +} + +impl occasional_track_update::Update { + pub fn name(track: &reaper_high::Track) -> Self { + Self::Name(track.name().unwrap_or_default().into_string()) + } + + pub fn color(track: &reaper_high::Track) -> Self { + Self::Color(RgbColor::from_engine(track.custom_color())) + } + + pub fn input(track: &reaper_high::Track) -> Self { + let engine_track_input = base::TrackInput::from_track(track); + Self::Input(TrackInput::from_engine(engine_track_input)) + } + + pub fn armed(value: bool) -> Self { + Self::Armed(value) + } + + pub fn input_monitoring(value: Option) -> Self { + let api_value = TrackInputMonitoring::from_engine(value); + Self::InputMonitoring(api_value.into()) + } + + pub fn mute(value: bool) -> Self { + Self::Mute(value) + } + + pub fn solo(value: bool) -> Self { + Self::Solo(value) + } + + pub fn selected(value: bool) -> Self { + Self::Selected(value) + } + + pub fn volume(db: Db) -> Self { + Self::Volume(db.get()) + } + + pub fn pan(pan: ReaperPanValue) -> Self { + Self::Pan(pan.get()) + } + + pub fn normal_fx_chain(track: &reaper_high::Track) -> Self { + Self::NormalFxChain(FxChain::from_engine(&track.normal_fx_chain())) + } +} + +impl FxChain { + pub fn from_engine(fx_chain: &reaper_high::FxChain) -> Self { + Self { + fxs: fx_chain + .index_based_fxs() + .map(|fx| Fx::from_engine(&fx)) + .collect(), + } + } +} + +impl Fx { + pub fn from_engine(fx: &reaper_high::Fx) -> Self { + Self { + name: fx.name().to_string(), + // No need to fall back to chunk-based FX info because Playtime doesn't support old REAPER versions anyway. + instrument: fx + .info() + .map(|info| info.sub_type_expression.ends_with('i')) + .unwrap_or(false), + } + } +} + +impl qualified_occasional_slot_update::Update { + pub fn play_state(play_state: ClipPlayState) -> Self { + Self::PlayState(SlotPlayState::from_engine(play_state).into()) + } + + pub fn complete_persistent_data(matrix: &Matrix, slot: &Slot) -> Self { + let base_dir = matrix.base_dir(); + let api_slot = slot + .save(base_dir.as_deref(), SaveOptions::without_contents()) + .unwrap_or(playtime_api::persistence::Slot { + id: slot.id().clone(), + row: slot.index(), + clip_old: None, + clips: None, + ignited: false, + }); + let json = serde_json::to_string(&api_slot).expect("couldn't represent slot as JSON"); + Self::CompletePersistentData(json) + } +} + +impl qualified_occasional_column_update::Update { + pub fn settings(matrix: &Matrix, column_index: usize) -> anyhow::Result { + let column = matrix.get_column(column_index)?; + let json = serde_json::to_string(&column.all_column_settings_combined()) + .expect("couldn't represent slot as JSON"); + Ok(Self::Settings(json)) + } +} + +impl qualified_occasional_row_update::Update { + pub fn data(matrix: &Matrix, row_index: usize) -> anyhow::Result { + let row = matrix.get_row(row_index)?; + let json = serde_json::to_string(&row.save()).expect("couldn't represent row as JSON"); + Ok(Self::Data(json)) + } +} + +impl qualified_occasional_clip_update::Update { + pub fn complete_persistent_data(matrix: &Matrix, clip: &Clip) -> anyhow::Result { + let base_dir = matrix.base_dir(); + let api_clip = clip.save(base_dir.as_deref(), SaveOptions::without_contents())?; + let json = serde_json::to_string(&api_clip).expect("couldn't represent clip as JSON"); + Ok(Self::CompletePersistentData(json)) + } + + pub fn content_info(clip_employment: &ClipEmployment) -> Self { + Self::ContentInfo(ClipContentInfo { + info: Some(clip_content_info::Info::from_engine(clip_employment)), + }) + } +} + +impl clip_content_info::Info { + pub fn from_engine(clip_employment: &ClipEmployment) -> Self { + match clip_employment.clip.source() { + ClipSource::File(_) => Self::Audio(AudioClipContentInfo { + online: clip_employment.online_data.is_some(), + }), + ClipSource::Midi(s) => Self::Midi(MidiClipContentInfo { + quantized: s.sequence().is_quantized(), + }), + } + } +} + +impl HistoryState { + pub fn from_engine(history: &History) -> Self { + Self { + undo_label: history + .next_undo_label() + .map(|l| l.to_string()) + .unwrap_or_default(), + redo_label: history + .next_redo_label() + .map(|l| l.to_string()) + .unwrap_or_default(), + } + } +} + +impl ClipAddress { + pub fn from_engine(address: base::ClipAddress) -> Self { + Self { + slot_address: Some(SlotAddress::from_engine(address.slot_address)), + clip_index: address.clip_index as _, + } + } + + pub fn to_engine(&self) -> Result { + let addr = base::ClipAddress { + slot_address: self + .slot_address + .as_ref() + .ok_or("slot address missing")? + .to_engine(), + clip_index: self.clip_index as usize, + }; + Ok(addr) + } +} + +impl SlotPlayState { + pub fn from_engine(play_state: ClipPlayState) -> Self { + use ClipPlayState::*; + match play_state { + Stopped => Self::Stopped, + Ignited => Self::Ignited, + ScheduledForPlayStart => Self::ScheduledForPlayStart, + Playing => Self::Playing, + Paused => Self::Paused, + ScheduledForPlayRestart => Self::ScheduledForPlayRestart, + ScheduledForPlayStop => Self::ScheduledForPlayStop, + ScheduledForRecordingStart => Self::ScheduledForRecordingStart, + Recording => Self::Recording, + ScheduledForRecordingStop => Self::ScheduledForRecordingStop, + } + } +} + +impl TrackInput { + pub fn from_engine(input: base::TrackInput) -> Self { + use RecordingInput::*; + let input = match input.rec_input { + Some(Mono(ch)) => Some(Input::Mono(ch)), + Some(Stereo(ch)) => Some(Input::Stereo(ch)), + Some(MonoReaRoute(ch)) => Some(Input::LoopbackMono(ch - 256)), + Some(StereoReaRoute(ch)) => Some(Input::LoopbackStereo(ch - 256)), + Some(Midi { device_id, channel }) => { + let midi_input = TrackMidiInput { + device: device_id.map(|id| id.get() as _), + channel: channel.map(|ch| ch.get() as _), + destination_channel: input.map_to_midi_channel.map(|ch| ch.get() as _), + }; + Some(Input::Midi(midi_input)) + } + _ => None, + }; + Self { input } + } + + pub fn to_engine(&self) -> base::TrackInput { + let (rec_input, map_to_midi_channel) = match self.input.as_ref() { + None => (None, None), + Some(Input::Mono(ch)) => (Some(RecordingInput::Mono(*ch)), None), + Some(Input::Stereo(ch)) => (Some(RecordingInput::Stereo(*ch)), None), + Some(Input::LoopbackMono(ch)) => (Some(RecordingInput::MonoReaRoute(256 + ch)), None), + Some(Input::LoopbackStereo(ch)) => { + (Some(RecordingInput::StereoReaRoute(256 + ch)), None) + } + Some(Input::Midi(input)) => { + let midi_input = RecordingInput::Midi { + device_id: input.device.map(|id| MidiInputDeviceId::new(id as _)), + channel: input.channel.map(|id| Channel::new(id as _)), + }; + let map_to_midi_channel = input + .destination_channel + .and_then(|ch| Channel::try_from(ch).ok()); + (Some(midi_input), map_to_midi_channel) + } + }; + base::TrackInput { + rec_input, + map_to_midi_channel, + } + } +} + +impl TrackInputMonitoring { + pub fn from_engine(value: Option) -> Self { + use ColumnTrackInputMonitoring::*; + match value { + None => Self::Unknown, + Some(Off) => Self::Off, + Some(Auto) => Self::Auto, + Some(On) => Self::On, + } + } + + pub fn to_engine(self) -> Option { + use ColumnTrackInputMonitoring as T; + use TrackInputMonitoring::*; + match self { + Unknown => None, + Off => Some(T::Off), + Auto => Some(T::Auto), + On => Some(T::On), + } + } +} + +impl SequencerPlayState { + pub fn from_engine(play_state: base::SequencerStatus) -> Self { + use SequencerStatus::*; + match play_state { + Stopped => Self::Stopped, + Playing => Self::Playing, + Recording => Self::Recording, + } + } +} + +impl ContinuousSlotUpdate { + pub fn from_engine(clip_events: &ContinuousClipChangeEvents) -> Self { + Self { + clip_update: clip_events + .iter() + .map(ContinuousClipUpdate::from_engine) + .collect(), + } + } +} + +impl ContinuousClipUpdate { + pub fn from_engine(event: &ContinuousClipChangeEvent) -> Self { + Self { + proportional_position: event.proportional.get(), + position_in_seconds: event.seconds.get(), + source_position_in_frames: event.source_pos_in_frames, + hypothetical_source_position_in_frames: event.hypothetical_source_pos_in_frames, + peak: event.peak.get(), + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/playtime_request_handler.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/playtime_request_handler.rs new file mode 100644 index 0000000..e442e3a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/playtime_request_handler.rs @@ -0,0 +1,932 @@ +use anyhow::Context; +use reaper_high::{GroupingBehavior, Guid, Track, TrackSetSmartOpts}; +use reaper_medium::{Bpm, Db, GangBehavior, PlaybackSpeedFactor, ReaperPanValue, SoloMode}; +use tonic::{Response, Status}; + +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::proto; +use crate::infrastructure::proto::{ + ColumnKind, DragClipAction, DragClipRequest, DragColumnAction, DragColumnRequest, + DragRowAction, DragRowRequest, DragSlotAction, DragSlotRequest, Empty, FullClipAddress, + FullClipId, FullColumnAddress, FullRowAddress, FullSequenceId, FullSlotAddress, + FullTrackAddress, GetArrangementInfoReply, GetArrangementInfoRequest, GetClipDetailReply, + GetClipDetailRequest, GetProjectDirReply, GetProjectDirRequest, ImportFilesRequest, + InsertColumnsRequest, MatrixVolumeKind, OpenTrackFxRequest, ProveAuthenticityReply, + ProveAuthenticityRequest, SetClipDataRequest, SetClipNameRequest, SetColumnSettingsRequest, + SetColumnTrackRequest, SetMatrixClickChannelRequest, SetMatrixPanRequest, + SetMatrixPlayRateRequest, SetMatrixSettingsRequest, SetMatrixTempoRequest, + SetMatrixTimeSignatureRequest, SetMatrixVolumeRequest, SetPlaytimeEngineSettingsRequest, + SetRowDataRequest, SetSequenceInfoRequest, SetTrackColorRequest, + SetTrackInputMonitoringRequest, SetTrackInputRequest, SetTrackNameRequest, SetTrackPanRequest, + SetTrackVolumeRequest, TriggerClipAction, TriggerClipRequest, TriggerColumnAction, + TriggerColumnRequest, TriggerMatrixAction, TriggerMatrixRequest, TriggerRowAction, + TriggerRowRequest, TriggerSequenceAction, TriggerSequenceRequest, TriggerSlotAction, + TriggerSlotRequest, TriggerTrackAction, TriggerTrackRequest, +}; +use base::future_util; +use base::tracing_util::ok_or_log_as_warn; +use helgoboss_learn::UnitValue; +use playtime_api::persistence::{ + ClipId, ColumnAddress, MatrixSequenceId, PlaytimeSettings, RowAddress, SlotAddress, TrackId, +}; +use playtime_api::runtime::{CellAddress, SimpleMappingTarget}; +use playtime_clip_engine::base::WriteArrangementPosition; +use playtime_clip_engine::rt::TriggerSlotMainOptions; +use playtime_clip_engine::PlaytimeEngine; +#[cfg(feature = "playtime")] +use playtime_clip_engine::{ + base::ClipAddress, base::Matrix, rt::ColumnPlaySlotOptions, PlaytimeMainEngine, +}; + +#[derive(Debug)] +pub struct PlaytimeProtoRequestHandler; + +impl PlaytimeProtoRequestHandler { + pub fn trigger_slot(&self, req: TriggerSlotRequest) -> Result, Status> { + let action = TriggerSlotAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger slot action"))?; + self.handle_slot_command(&req.slot_address, |matrix, slot_address| { + match action { + TriggerSlotAction::Play => { + matrix.play_slot( + slot_address, + ColumnPlaySlotOptions { + velocity: Some(UnitValue::MAX), + stop_column_if_slot_empty: false, + handle_ignited_clips: false, + play_at_timeline_zero: false, + }, + ); + } + TriggerSlotAction::Stop => { + matrix.stop_slot(slot_address); + } + TriggerSlotAction::Record => matrix.record_slot(slot_address)?, + TriggerSlotAction::Clear => matrix.clear_slot(slot_address)?, + TriggerSlotAction::Copy => matrix.copy_slot(slot_address)?, + TriggerSlotAction::Cut => matrix.cut_slot(slot_address)?, + TriggerSlotAction::Paste => matrix.paste_slot(slot_address)?, + TriggerSlotAction::ImportSelectedItems => { + matrix.import_selected_items(slot_address)? + } + TriggerSlotAction::Panic => matrix.panic_slot(slot_address), + TriggerSlotAction::CreateEmptyMidiClip => { + matrix.create_empty_midi_clip_in_slot(slot_address)? + } + TriggerSlotAction::ToggleLearnSimpleMapping => { + matrix.toggle_learn_source_by_target(SimpleMappingTarget::TriggerSlot( + slot_address, + )); + } + TriggerSlotAction::RemoveSimpleMapping => { + matrix.remove_mapping_by_target(SimpleMappingTarget::TriggerSlot(slot_address)); + } + TriggerSlotAction::TriggerOn => matrix.trigger_slot( + slot_address, + UnitValue::MAX, + TriggerSlotMainOptions { + stop_column_if_slot_empty: false, + allow_start_stop: true, + // Activating from GUI side ... no. + allow_activate: false, + }, + )?, + TriggerSlotAction::TriggerOff => matrix.trigger_slot( + slot_address, + UnitValue::MIN, + TriggerSlotMainOptions { + stop_column_if_slot_empty: false, + allow_start_stop: true, + // Activating from GUI side ... no. + allow_activate: false, + }, + )?, + TriggerSlotAction::Activate => { + matrix.activate_cell(slot_address.to_cell_address())? + } + TriggerSlotAction::ExportToClipboard => { + matrix.export_slot_to_clipboard(slot_address)? + } + TriggerSlotAction::ExportToArrangement => { + todo!() + } + } + Ok(()) + }) + } + + pub fn import_files(&self, req: ImportFilesRequest) -> Result, Status> { + self.handle_slot_command(&req.slot_address, |matrix, slot_address| { + matrix.import_files(slot_address, req.files) + }) + } + + pub fn trigger_clip(&self, req: TriggerClipRequest) -> Result, Status> { + let action = TriggerClipAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger clip action"))?; + self.handle_clip_command(&req.clip_address, |matrix, clip_address| match action { + TriggerClipAction::MidiOverdub => matrix.midi_overdub_clip(clip_address), + TriggerClipAction::ToggleMidiOverdub => matrix.toggle_midi_overdub_clip(clip_address), + TriggerClipAction::Edit => matrix.start_editing_clip(clip_address), + TriggerClipAction::Remove => matrix.remove_clip_from_slot(clip_address), + TriggerClipAction::Promote => matrix.promote_clip_within_slot(clip_address), + TriggerClipAction::Quantize => matrix.quantize_clip(clip_address), + TriggerClipAction::Unquantize => matrix.unquantize_clip(clip_address), + TriggerClipAction::OpenInMediaExplorer => { + matrix.open_clip_source_in_media_explorer(clip_address) + } + TriggerClipAction::ExportToClipboard => matrix.export_clip_to_clipboard(clip_address), + TriggerClipAction::ExportToArrangement => { + matrix.export_clip_to_arrangement(clip_address) + } + }) + } + + pub fn drag_slot(&self, req: DragSlotRequest) -> Result, Status> { + let action = DragSlotAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown drag slot action"))?; + let source_slot_address = convert_slot_address_to_engine(&req.source_slot_address)?; + let dest_slot_address = convert_slot_address_to_engine(&req.destination_slot_address)?; + self.handle_matrix_command(req.matrix_id, |matrix| match action { + DragSlotAction::Move => matrix.move_slot_to(source_slot_address, dest_slot_address), + DragSlotAction::Copy => matrix.copy_slot_to(source_slot_address, dest_slot_address), + }) + } + + pub fn drag_clip(&self, req: DragClipRequest) -> Result, Status> { + let action = DragClipAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown drag clip action"))?; + let source_clip_address = convert_clip_address_to_engine(&req.source_clip_address)?; + let dest_slot_address = convert_slot_address_to_engine(&req.destination_slot_address)?; + self.handle_matrix_command(req.matrix_id, |matrix| match action { + DragClipAction::Move => matrix.move_clip_to(source_clip_address, dest_slot_address), + }) + } + + pub fn drag_row(&self, req: DragRowRequest) -> Result, Status> { + let action = DragRowAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown drag row action"))?; + self.handle_matrix_command(req.matrix_id, |matrix| match action { + DragRowAction::MoveContent => matrix + .move_row_content_to(req.source_row_index as _, req.destination_row_index as _), + DragRowAction::CopyContent => matrix + .copy_row_content_to(req.source_row_index as _, req.destination_row_index as _), + DragRowAction::Reorder => { + matrix.reorder_rows(req.source_row_index as _, req.destination_row_index as _) + } + }) + } + + pub fn drag_column(&self, req: DragColumnRequest) -> Result, Status> { + let action = DragColumnAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown drag column action"))?; + self.handle_matrix_command(req.matrix_id, |matrix| match action { + DragColumnAction::Reorder => matrix.reorder_columns( + req.source_column_index as _, + req.destination_column_index as _, + ), + }) + } + + pub fn set_track_name(&self, req: SetTrackNameRequest) -> Result, Status> { + self.handle_track_command(&req.track_address, |_matrix, track| { + track.set_name(req.name); + Ok(()) + }) + } + + pub fn set_track_color(&self, req: SetTrackColorRequest) -> Result, Status> { + self.handle_track_command(&req.track_address, |matrix, track| { + let color = req.color.and_then(|tc| tc.to_engine()); + matrix.set_track_color(track, color); + Ok(()) + }) + } + + pub fn set_clip_name(&self, req: SetClipNameRequest) -> Result, Status> { + self.handle_clip_command(&req.clip_address, |matrix, clip_address| { + matrix.set_clip_name(clip_address, req.name) + }) + } + + pub fn set_clip_data(&self, req: SetClipDataRequest) -> Result, Status> { + let clip = + serde_json::from_str(&req.data).map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_clip_command(&req.clip_address, |matrix, clip_address| { + matrix.set_clip_data(clip_address, clip) + }) + } + + pub fn trigger_sequence(&self, req: TriggerSequenceRequest) -> Result, Status> { + let action: TriggerSequenceAction = TriggerSequenceAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger sequence action"))?; + self.handle_sequence_command(req.sequence_id, |matrix, seq_id| match action { + TriggerSequenceAction::Activate => matrix.activate_sequence(seq_id), + TriggerSequenceAction::Remove => matrix.remove_sequence(seq_id), + }) + } + + pub fn set_playtime_engine_settings( + &self, + request: SetPlaytimeEngineSettingsRequest, + ) -> Result, Status> { + let settings: PlaytimeSettings = serde_json::from_str(&request.settings) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + PlaytimeEngine::get() + .main() + .set_settings(settings) + .map_err(|e| Status::unknown(e.to_string()))?; + Ok(Response::new(Empty {})) + } + + pub fn insert_columns(&self, req: InsertColumnsRequest) -> Result, Status> { + let kind = ColumnKind::try_from(req.kind) + .map_err(|_| Status::invalid_argument("unknown column kind"))?; + self.handle_column_command(&req.column_address, |matrix, column_index| { + matrix.insert_columns(column_index, req.count as usize, kind.to_engine()) + }) + } + + pub fn set_sequence_info( + &self, + req: SetSequenceInfoRequest, + ) -> Result, Status> { + self.handle_sequence_command(req.sequence_id, |matrix, seq_id| { + let sequence_info = serde_json::from_str(&req.data) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + matrix.set_sequence_info(seq_id, sequence_info) + }) + } + + pub fn trigger_matrix(&self, req: TriggerMatrixRequest) -> Result, Status> { + let action = TriggerMatrixAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger matrix action"))?; + if action == TriggerMatrixAction::CreateMatrix { + self.create_matrix(req.matrix_id) + .map_err(|e| Status::not_found(e.to_string()))?; + return Ok(Response::new(Empty {})); + } + self.handle_matrix_command(req.matrix_id, |matrix| match action { + TriggerMatrixAction::CreateMatrix => { + unreachable!("matrix creation handled above") + } + TriggerMatrixAction::ReloadAllClips => { + matrix.reload_all_clips(); + Ok(()) + } + TriggerMatrixAction::SequencerCleanArrangement => matrix.clean_arrangement(), + TriggerMatrixAction::SequencerWriteToArrangementAtStart => { + matrix.write_active_sequence_to_arrangement(WriteArrangementPosition::Start) + } + TriggerMatrixAction::SequencerWriteToArrangementAtEnd => { + matrix.write_active_sequence_to_arrangement(WriteArrangementPosition::End) + } + TriggerMatrixAction::SequencerWriteToArrangementAtCursor => { + matrix.write_active_sequence_to_arrangement(WriteArrangementPosition::Cursor) + } + TriggerMatrixAction::SequencerPlay => { + matrix.play_active_sequence()?; + Ok(()) + } + TriggerMatrixAction::SequencerRecord => { + matrix.record_new_sequence(); + Ok(()) + } + TriggerMatrixAction::SequencerStop => { + matrix.stop_sequencer(); + Ok(()) + } + TriggerMatrixAction::ToggleSilenceMode => { + matrix.toggle_silence_mode(); + Ok(()) + } + TriggerMatrixAction::EnterSilenceMode => { + matrix.enter_silence_mode(); + Ok(()) + } + TriggerMatrixAction::PlayAllIgnitedClips => { + matrix.play_all_ignited(); + Ok(()) + } + TriggerMatrixAction::StopAllClips => { + matrix.stop(); + Ok(()) + } + TriggerMatrixAction::Undo => matrix.undo(), + TriggerMatrixAction::Redo => matrix.redo(), + TriggerMatrixAction::ToggleClick => { + matrix.toggle_click(); + Ok(()) + } + TriggerMatrixAction::Panic => { + matrix.panic(); + Ok(()) + } + TriggerMatrixAction::ToggleMute => { + matrix.toggle_mute(); + Ok(()) + } + TriggerMatrixAction::ShowMasterFx => { + matrix.show_master_fx(); + Ok(()) + } + TriggerMatrixAction::ShowMasterRouting => { + matrix.show_master_routing(); + Ok(()) + } + TriggerMatrixAction::TapTempo => { + matrix.tap_tempo(); + Ok(()) + } + TriggerMatrixAction::ToggleLearnSimpleMappingTrigger => { + matrix.toggle_learn_source_by_target(SimpleMappingTarget::TriggerMatrix); + Ok(()) + } + TriggerMatrixAction::ToggleLearnSimpleMappingEnterSilenceModeOrPlayIgnited => { + matrix.toggle_learn_source_by_target( + SimpleMappingTarget::EnterSilenceModeOrPlayIgnited, + ); + Ok(()) + } + TriggerMatrixAction::ToggleLearnSimpleMappingSmartRecord => { + matrix.toggle_learn_source_by_target(SimpleMappingTarget::SmartRecord); + Ok(()) + } + TriggerMatrixAction::ToggleLearnSimpleMappingSequencerPlayOnOffState => { + matrix.toggle_learn_source_by_target(SimpleMappingTarget::SequencerPlayOnOffState); + Ok(()) + } + TriggerMatrixAction::ToggleLearnSimpleMappingSequencerRecordOnOffState => { + matrix + .toggle_learn_source_by_target(SimpleMappingTarget::SequencerRecordOnOffState); + Ok(()) + } + TriggerMatrixAction::ToggleLearnSimpleMappingTapTempo => { + matrix.toggle_learn_source_by_target(SimpleMappingTarget::TapTempo); + Ok(()) + } + TriggerMatrixAction::RemoveSimpleMappingTrigger => { + matrix.remove_mapping_by_target(SimpleMappingTarget::TriggerMatrix); + Ok(()) + } + TriggerMatrixAction::RemoveSimpleMappingEnterSilenceModeOrPlayIgnited => { + matrix.remove_mapping_by_target(SimpleMappingTarget::EnterSilenceModeOrPlayIgnited); + Ok(()) + } + TriggerMatrixAction::RemoveSimpleMappingSmartRecord => { + matrix.remove_mapping_by_target(SimpleMappingTarget::SmartRecord); + Ok(()) + } + TriggerMatrixAction::RemoveSimpleMappingSequencerPlayOnOffState => { + matrix.remove_mapping_by_target(SimpleMappingTarget::SequencerPlayOnOffState); + Ok(()) + } + TriggerMatrixAction::RemoveSimpleMappingSequencerRecordOnOffState => { + matrix.remove_mapping_by_target(SimpleMappingTarget::SequencerRecordOnOffState); + Ok(()) + } + TriggerMatrixAction::RemoveSimpleMappingTapTempo => { + matrix.remove_mapping_by_target(SimpleMappingTarget::TapTempo); + Ok(()) + } + TriggerMatrixAction::TriggerSmartRecord => matrix.trigger_smart_record(false), + TriggerMatrixAction::Activate => matrix.activate_cell(CellAddress::matrix()), + TriggerMatrixAction::ExportToClipboard => matrix.export_to_clipboard(), + TriggerMatrixAction::ExportToArrangement => matrix.export_to_arrangement(), + }) + } + + pub fn set_matrix_settings( + &self, + req: SetMatrixSettingsRequest, + ) -> Result, Status> { + let matrix_settings = serde_json::from_str(&req.settings) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_matrix_command(req.matrix_id, |matrix| matrix.set_settings(matrix_settings)) + } + + pub fn trigger_column(&self, req: TriggerColumnRequest) -> Result, Status> { + let action = TriggerColumnAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger column action"))?; + self.handle_column_command(&req.column_address, |matrix, column_index| { + match action { + TriggerColumnAction::Stop => matrix.stop_column(column_index), + TriggerColumnAction::Remove => matrix.remove_column_with_track(column_index)?, + TriggerColumnAction::RemoveWithoutTrack => { + matrix.remove_column_without_track(column_index)? + } + TriggerColumnAction::Duplicate => matrix.duplicate_column(column_index)?, + TriggerColumnAction::Insert => matrix.insert_column(column_index)?, + TriggerColumnAction::Panic => matrix.panic_column(column_index), + TriggerColumnAction::ToggleLearnSimpleMapping => { + matrix.toggle_learn_source_by_target(SimpleMappingTarget::TriggerColumn( + ColumnAddress { + index: column_index, + }, + )); + } + TriggerColumnAction::RemoveSimpleMapping => { + matrix.remove_mapping_by_target(SimpleMappingTarget::TriggerColumn( + ColumnAddress { + index: column_index, + }, + )); + } + TriggerColumnAction::Activate => { + matrix.activate_cell(CellAddress::column(column_index))? + } + TriggerColumnAction::ExportToClipboard => { + matrix.export_column_to_clipboard(column_index)? + } + TriggerColumnAction::ExportToArrangement => { + matrix.export_column_to_arrangement(column_index)? + } + TriggerColumnAction::InsertForEachSelectedTrack => { + matrix.insert_column_for_each_selected_track(column_index)? + } + } + Ok(()) + }) + } + + pub fn trigger_track(&self, req: TriggerTrackRequest) -> Result, Status> { + let action = TriggerTrackAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger track action"))?; + self.handle_track_command(&req.track_address, |matrix, track| match action { + TriggerTrackAction::ToggleMute => { + track.set_mute( + !track.is_muted(), + GangBehavior::DenyGang, + GroupingBehavior::PreventGrouping, + ); + Ok(()) + } + TriggerTrackAction::ToggleSolo => { + let new_solo_mode = if track.is_solo() { + SoloMode::Off + } else { + SoloMode::SoloInPlace + }; + track.set_solo_mode(new_solo_mode); + Ok(()) + } + TriggerTrackAction::ToggleArm => { + matrix.toggle_track_armed(track); + Ok(()) + } + TriggerTrackAction::ShowFx => { + matrix.show_track_fx(track); + Ok(()) + } + TriggerTrackAction::ShowRouting => { + matrix.show_track_routing(&track); + Ok(()) + } + TriggerTrackAction::ToggleLearnInput => { + matrix.toggle_learn_input(&track); + Ok(()) + } + }) + } + + pub fn set_column_settings( + &self, + req: SetColumnSettingsRequest, + ) -> Result, Status> { + let column_settings = serde_json::from_str(&req.settings) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_column_command(&req.column_address, |matrix, column_index| { + matrix.set_column_settings(column_index, column_settings) + }) + } + + pub fn trigger_row(&self, req: TriggerRowRequest) -> Result, Status> { + let action = TriggerRowAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger row action"))?; + self.handle_row_command(&req.row_address, |matrix, row_index| match action { + TriggerRowAction::Play => { + matrix.play_scene(row_index); + Ok(()) + } + TriggerRowAction::Clear => matrix.clear_row(row_index), + TriggerRowAction::Copy => matrix.copy_row(row_index), + TriggerRowAction::Cut => matrix.cut_row(row_index), + TriggerRowAction::Paste => matrix.paste_row(row_index), + TriggerRowAction::Remove => matrix.remove_row(row_index), + TriggerRowAction::Duplicate => matrix.duplicate_row(row_index), + TriggerRowAction::Insert => matrix.insert_row(row_index), + TriggerRowAction::ToggleLearnSimpleMapping => { + matrix.toggle_learn_source_by_target(SimpleMappingTarget::TriggerRow(RowAddress { + index: row_index, + })); + Ok(()) + } + TriggerRowAction::RemoveSimpleMapping => { + matrix.remove_mapping_by_target(SimpleMappingTarget::TriggerRow(RowAddress { + index: row_index, + })); + Ok(()) + } + TriggerRowAction::BuildSceneFromPlayingSlots => matrix.build_scene(row_index), + TriggerRowAction::Activate => matrix.activate_cell(CellAddress::row(row_index)), + TriggerRowAction::ExportToArrangement => matrix.export_row_to_arrangement(row_index), + }) + } + + pub fn set_row_data(&self, req: SetRowDataRequest) -> Result, Status> { + let row_data = + serde_json::from_str(&req.data).map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_row_command(&req.row_address, |matrix, row_index| { + matrix.set_row_data(row_index, row_data) + }) + } + + pub fn set_matrix_tempo(&self, req: SetMatrixTempoRequest) -> Result, Status> { + let bpm = Bpm::try_from(req.bpm).map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_matrix_command(req.matrix_id, |matrix| { + matrix.set_tempo(bpm); + Ok(()) + }) + } + + pub fn set_matrix_play_rate( + &self, + req: SetMatrixPlayRateRequest, + ) -> Result, Status> { + let play_rate = PlaybackSpeedFactor::try_from(req.play_rate) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_matrix_command(req.matrix_id, |matrix| { + matrix.set_play_rate(play_rate); + Ok(()) + }) + } + + pub fn set_matrix_time_signature( + &self, + req: SetMatrixTimeSignatureRequest, + ) -> Result, Status> { + let time_sig = req + .time_signature + .ok_or_else(|| Status::invalid_argument("no time signature given"))? + .to_engine() + .map_err(Status::invalid_argument)?; + self.handle_matrix_command(req.matrix_id, |matrix| { + matrix.set_time_signature(time_sig); + Ok(()) + }) + } + + pub fn set_matrix_volume( + &self, + req: SetMatrixVolumeRequest, + ) -> Result, Status> { + let db = Db::try_from(req.db).map_err(|e| Status::invalid_argument(e.to_string()))?; + let kind = MatrixVolumeKind::try_from(req.kind) + .map_err(|_| Status::invalid_argument("unknown matrix volume kind"))?; + self.handle_matrix_command(req.matrix_id, |matrix| { + match kind { + MatrixVolumeKind::Master => { + let project = matrix.project(); + project.master_track()?.set_volume_smart( + db.to_linear_volume_value(), + TrackSetSmartOpts::default(), + )?; + } + MatrixVolumeKind::Click => { + matrix.set_click_volume(db); + } + MatrixVolumeKind::TempoTap => { + matrix.set_tempo_tap_volume(db); + } + } + Ok(()) + }) + } + + pub fn set_matrix_pan(&self, req: SetMatrixPanRequest) -> Result, Status> { + let pan = ReaperPanValue::try_from(req.pan) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_matrix_command(req.matrix_id, |matrix| { + let project = matrix.project(); + project + .master_track()? + .set_pan_smart(pan, TrackSetSmartOpts::default())?; + Ok(()) + }) + } + + pub fn set_matrix_click_channel( + &self, + req: SetMatrixClickChannelRequest, + ) -> Result, Status> { + self.handle_matrix_command(req.matrix_id, |matrix| { + matrix.set_click_channel(req.channel); + Ok(()) + }) + } + + pub fn set_track_volume(&self, req: SetTrackVolumeRequest) -> Result, Status> { + let db = Db::try_from(req.db).map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_track_command(&req.track_address, |_matrix, track| { + track.set_volume_smart(db.to_linear_volume_value(), TrackSetSmartOpts::default())?; + Ok(()) + }) + } + + pub fn set_track_pan(&self, req: SetTrackPanRequest) -> Result, Status> { + let pan = ReaperPanValue::new_panic(req.pan.clamp(-1.0, 1.0)); + self.handle_track_command(&req.track_address, |_matrix, track| { + track.set_pan_smart(pan, TrackSetSmartOpts::default())?; + Ok(()) + }) + } + + pub fn open_track_fx(&self, req: OpenTrackFxRequest) -> Result, Status> { + self.handle_track_command(&req.track_address, |_matrix, track| { + let fx = track + .normal_fx_chain() + .fx_by_index(req.fx_index) + .context("no FX at that index")?; + fx.show_in_floating_window() + .map_err(|e| Status::unknown(e.to_string()))?; + Ok(()) + }) + } + + pub async fn set_column_track( + &self, + req: SetColumnTrackRequest, + ) -> Result, Status> { + // We shouldn't just change the column track directly, otherwise we get abrupt clicks + // (audio) and hanging notes (MIDI). The following is a dirty but efficient solution to + // prevent this. + // Immediately stop everything in that column (gracefully) + self.handle_column_internal(&req.column_address, |matrix, column_index| { + matrix.get_column(column_index)?.panic(true); + Ok(()) + })?; + // Make sure to wait long enough until fade outs and stuff finished + future_util::millis(50).await; + // Finally change column track + self.handle_column_command(&req.column_address, |matrix, column_index| { + let track_id = req.track_id.map(TrackId::new); + matrix.set_column_playback_track(column_index, track_id.as_ref())?; + Ok(()) + }) + } + + pub fn set_track_input_monitoring( + &self, + req: SetTrackInputMonitoringRequest, + ) -> Result, Status> { + self.handle_track_command(&req.track_address, |matrix, track| { + matrix.set_track_input_monitoring(track, req.input_monitoring().to_engine()); + Ok(()) + }) + } + + pub fn set_track_input(&self, req: SetTrackInputRequest) -> Result, Status> { + let input = req + .input + .ok_or_else(|| Status::invalid_argument("missing track input"))?; + self.handle_track_command(&req.track_address, |matrix, track| { + matrix.set_track_input(track, input.to_engine()); + Ok(()) + }) + } + + pub async fn get_clip_detail( + &self, + req: GetClipDetailRequest, + ) -> Result, Status> { + let peak_file_future = + self.handle_clip_by_id_internal(req.clip_id, |matrix, clip_id| { + let peak_file_future = matrix.get_clip_peak_file_contents(&clip_id)?; + Ok(peak_file_future) + })?; + let reply = GetClipDetailReply { + rea_peaks: ok_or_log_as_warn(peak_file_future.await), + }; + Ok(Response::new(reply)) + } + + pub async fn prove_authenticity( + &self, + req: ProveAuthenticityRequest, + ) -> Result, Status> { + let signature = PlaytimeMainEngine::prove_authenticity(&req.challenge) + .ok_or_else(|| Status::unknown("authenticity proof failed"))?; + Ok(Response::new(ProveAuthenticityReply { signature })) + } + + pub async fn get_project_dir( + &self, + req: GetProjectDirRequest, + ) -> Result, Status> { + let project_dir = self.handle_matrix_internal(req.matrix_id, |matrix| { + let project = matrix.project(); + let project_dir = project + .directory() + .unwrap_or_else(|| project.recording_path()); + Ok(project_dir) + })?; + let reply = GetProjectDirReply { + project_dir: project_dir.into_string(), + }; + Ok(Response::new(reply)) + } + + pub async fn get_arrangement_info( + &self, + req: GetArrangementInfoRequest, + ) -> Result, Status> { + let clean = + self.handle_matrix_internal(req.matrix_id, |matrix| Ok(matrix.arrangement_is_clean()))?; + let reply = GetArrangementInfoReply { clean }; + Ok(Response::new(reply)) + } + + fn handle_matrix_command( + &self, + matrix_id: u32, + handler: impl FnOnce(&mut Matrix) -> anyhow::Result<()>, + ) -> Result, Status> { + self.handle_matrix_internal(matrix_id, handler)?; + Ok(Response::new(Empty {})) + } + + fn handle_matrix_internal( + &self, + matrix_id: u32, + handler: impl FnOnce(&mut Matrix) -> anyhow::Result, + ) -> Result { + let r = self + .with_matrix_mut(matrix_id, handler) + .map_err(|e| Status::not_found(format!("{e:#}")))? + .map_err(|e| Status::unknown(format!("{e:#}")))?; + Ok(r) + } + + fn handle_column_command( + &self, + full_column_id: &Option, + handler: impl FnOnce(&mut Matrix, usize) -> anyhow::Result<()>, + ) -> Result, Status> { + self.handle_column_internal(full_column_id, handler)?; + Ok(Response::new(Empty {})) + } + + fn handle_column_internal( + &self, + full_column_id: &Option, + handler: impl FnOnce(&mut Matrix, usize) -> anyhow::Result, + ) -> Result { + let full_column_id = full_column_id + .as_ref() + .ok_or_else(|| Status::invalid_argument("need full column address"))?; + let column_index = full_column_id.column_index as usize; + self.handle_matrix_internal(full_column_id.matrix_id, |matrix| { + handler(matrix, column_index) + }) + } + + fn handle_sequence_command( + &self, + full_sequence_id: Option, + handler: impl FnOnce(&mut Matrix, MatrixSequenceId) -> anyhow::Result<()>, + ) -> Result, Status> { + self.handle_sequence_internal(full_sequence_id, handler)?; + Ok(Response::new(Empty {})) + } + + fn handle_sequence_internal( + &self, + full_sequence_id: Option, + handler: impl FnOnce(&mut Matrix, MatrixSequenceId) -> anyhow::Result, + ) -> Result { + let full_sequence_id = + full_sequence_id.ok_or_else(|| Status::invalid_argument("need full sequence ID"))?; + let sequence_id = MatrixSequenceId::new(full_sequence_id.sequence_id); + self.handle_matrix_internal(full_sequence_id.matrix_id, |matrix| { + handler(matrix, sequence_id) + }) + } + + fn handle_row_command( + &self, + full_row_id: &Option, + handler: impl FnOnce(&mut Matrix, usize) -> anyhow::Result<()>, + ) -> Result, Status> { + let full_row_id = full_row_id + .as_ref() + .ok_or_else(|| Status::invalid_argument("need full row address"))?; + let row_index = full_row_id.row_index as usize; + self.handle_matrix_command(full_row_id.matrix_id, |matrix| handler(matrix, row_index)) + } + + fn handle_slot_command( + &self, + full_slot_address: &Option, + handler: impl FnOnce(&mut Matrix, SlotAddress) -> anyhow::Result<()>, + ) -> Result, Status> { + let full_slot_address = full_slot_address + .as_ref() + .ok_or_else(|| Status::invalid_argument("need full slot address"))?; + let slot_addr = convert_slot_address_to_engine(&full_slot_address.slot_address)?; + self.handle_matrix_command(full_slot_address.matrix_id, |matrix| { + handler(matrix, slot_addr) + }) + } + + fn handle_clip_command( + &self, + full_clip_address: &Option, + handler: impl FnOnce(&mut Matrix, ClipAddress) -> anyhow::Result<()>, + ) -> Result, Status> { + self.handle_clip_internal(full_clip_address, handler)?; + Ok(Response::new(Empty {})) + } + + pub(crate) fn handle_clip_internal( + &self, + full_clip_address: &Option, + handler: impl FnOnce(&mut Matrix, ClipAddress) -> anyhow::Result, + ) -> Result { + let full_clip_address = full_clip_address + .as_ref() + .ok_or_else(|| Status::invalid_argument("need full clip address"))?; + let clip_addr = convert_clip_address_to_engine(&full_clip_address.clip_address)?; + self.handle_matrix_internal(full_clip_address.matrix_id, |matrix| { + handler(matrix, clip_addr) + }) + } + + pub(crate) fn handle_clip_by_id_internal( + &self, + full_clip_id: Option, + handler: impl FnOnce(&mut Matrix, ClipId) -> anyhow::Result, + ) -> Result { + let full_clip_id = + full_clip_id.ok_or_else(|| Status::invalid_argument("need full clip ID"))?; + let clip_id = ClipId::new(full_clip_id.clip_id); + self.handle_matrix_internal(full_clip_id.matrix_id, |matrix| handler(matrix, clip_id)) + } + + fn handle_track_command( + &self, + full_track_address: &Option, + handler: impl FnOnce(&Matrix, Track) -> anyhow::Result<()>, + ) -> Result, Status> { + let full_track_address = full_track_address + .as_ref() + .ok_or_else(|| Status::invalid_argument("need full track address"))?; + self.handle_track_internal(full_track_address, handler)?; + Ok(Response::new(Empty {})) + } + + fn handle_track_internal( + &self, + track_address: &FullTrackAddress, + handler: impl FnOnce(&Matrix, Track) -> anyhow::Result, + ) -> Result { + self.handle_matrix_internal(track_address.matrix_id, |matrix| { + let guid = Guid::from_string_without_braces(&track_address.track_id) + .map_err(anyhow::Error::msg)?; + let track = matrix.project().track_by_guid(&guid)?; + handler(matrix, track) + }) + } + + fn with_matrix_mut( + &self, + clip_matrix_id: u32, + f: impl FnOnce(&mut Matrix) -> R, + ) -> anyhow::Result { + BackboneShell::get().with_clip_matrix_mut(clip_matrix_id.into(), f) + } + + fn create_matrix(&self, clip_matrix_id: u32) -> anyhow::Result<()> { + BackboneShell::get().create_clip_matrix(clip_matrix_id.into()) + } +} + +fn convert_slot_address_to_engine( + addr: &Option, +) -> Result { + let addr = addr + .as_ref() + .ok_or_else(|| Status::invalid_argument("need slot address"))? + .to_engine(); + Ok(addr) +} + +fn convert_clip_address_to_engine( + addr: &Option, +) -> Result { + let addr = addr + .as_ref() + .ok_or_else(|| Status::invalid_argument("need clip address"))? + .to_engine() + .map_err(Status::invalid_argument)?; + Ok(addr) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/request_handler.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/request_handler.rs new file mode 100644 index 0000000..da9a1f3 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/request_handler.rs @@ -0,0 +1,850 @@ +use anyhow::Context; +use helgoboss_license_api::persistence::LicenseKey; +use reaper_high::{OrCurrentProject, Reaper}; +use reaper_medium::CommandId; +use tonic::{Response, Status}; + +use base::spawn_in_main_thread; +use helgobox_api::runtime::{GlobalInfoEvent, InstanceInfoEvent}; +use swell_ui::Window; + +use crate::domain::{CompartmentKind, UnitId}; +use crate::infrastructure::api::convert::from_data; +use crate::infrastructure::api::convert::from_data::ConversionStyle; +use crate::infrastructure::data::CompartmentModelData; +use crate::infrastructure::plugin::{BackboneShell, InstanceShell, UnitShell}; +#[cfg(feature = "playtime")] +use crate::infrastructure::proto::PlaytimeProtoRequestHandler; +use crate::infrastructure::proto::{ + AddLicenseRequest, Compartment, DeleteControllerRequest, DragClipRequest, DragColumnRequest, + DragRowRequest, DragSlotRequest, Empty, FullCompartmentId, GetAppSettingsReply, + GetAppSettingsRequest, GetArrangementInfoReply, GetArrangementInfoRequest, GetClipDetailReply, + GetClipDetailRequest, GetCompartmentDataReply, GetCompartmentDataRequest, + GetCustomInstanceDataReply, GetCustomInstanceDataRequest, GetHostInfoReply, GetHostInfoRequest, + GetProjectDirReply, GetProjectDirRequest, ImportFilesRequest, InsertColumnsRequest, + OpenTrackFxRequest, ProveAuthenticityReply, ProveAuthenticityRequest, SaveControllerRequest, + SaveCustomCompartmentDataRequest, SetAppSettingsRequest, SetClipDataRequest, + SetClipNameRequest, SetColumnSettingsRequest, SetColumnTrackRequest, + SetCustomInstanceDataRequest, SetInstanceSettingsRequest, SetMatrixClickChannelRequest, + SetMatrixPanRequest, SetMatrixPlayRateRequest, SetMatrixSettingsRequest, SetMatrixTempoRequest, + SetMatrixTimeSignatureRequest, SetMatrixVolumeRequest, SetPlaytimeEngineSettingsRequest, + SetRowDataRequest, SetSequenceInfoRequest, SetTrackColorRequest, + SetTrackInputMonitoringRequest, SetTrackInputRequest, SetTrackNameRequest, SetTrackPanRequest, + SetTrackVolumeRequest, TriggerClipRequest, TriggerColumnRequest, TriggerGlobalAction, + TriggerGlobalRequest, TriggerInstanceAction, TriggerInstanceRequest, TriggerMatrixRequest, + TriggerRowRequest, TriggerSequenceRequest, TriggerSlotRequest, TriggerTrackRequest, + HOST_API_VERSION, +}; + +#[derive(Debug)] +pub struct ProtoRequestHandler; + +impl ProtoRequestHandler { + pub fn trigger_slot(&self, req: TriggerSlotRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.trigger_slot(req) + } + } + + pub fn import_files(&self, req: ImportFilesRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.import_files(req) + } + } + + pub fn trigger_clip(&self, req: TriggerClipRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.trigger_clip(req) + } + } + + pub fn drag_slot(&self, req: DragSlotRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.drag_slot(req) + } + } + + pub fn drag_clip(&self, req: DragClipRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.drag_clip(req) + } + } + + pub fn drag_row(&self, req: DragRowRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.drag_row(req) + } + } + + pub fn drag_column(&self, req: DragColumnRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.drag_column(req) + } + } + + pub fn set_track_name(&self, req: SetTrackNameRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_track_name(req) + } + } + + pub fn set_track_color(&self, req: SetTrackColorRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_track_color(req) + } + } + + pub fn set_clip_name(&self, req: SetClipNameRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_clip_name(req) + } + } + + pub fn set_clip_data(&self, req: SetClipDataRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_clip_data(req) + } + } + + pub fn trigger_sequence(&self, req: TriggerSequenceRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.trigger_sequence(req) + } + } + + pub fn set_sequence_info( + &self, + req: SetSequenceInfoRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_sequence_info(req) + } + } + + pub fn add_license(&self, req: AddLicenseRequest) -> Result, Status> { + let license_key = LicenseKey::new(req.license_key.trim().to_string()); + BackboneShell::get() + .license_manager() + .borrow_mut() + .add_license(license_key) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + Ok(Response::new(Empty {})) + } + + pub fn save_controller(&self, req: SaveControllerRequest) -> Result, Status> { + let controller = serde_json::from_str(&req.controller) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + let outcome = BackboneShell::get() + .controller_manager() + .borrow_mut() + .save_controller(controller) + .map_err(|e| Status::unknown(e.to_string()))?; + if outcome.connection_changed { + if let Some(dev_id) = outcome.new_midi_output_device_id { + spawn_in_main_thread(async move { + let reply = BackboneShell::get() + .request_midi_device_identity(dev_id, None) + .await; + let _ = BackboneShell::get() + .controller_manager() + .borrow_mut() + .update_controller_device_identity(&outcome.id, reply.ok()); + Ok(()) + }) + } + } + Ok(Response::new(Empty {})) + } + + pub fn delete_controller( + &self, + req: DeleteControllerRequest, + ) -> Result, Status> { + BackboneShell::get() + .controller_manager() + .borrow_mut() + .delete_controller(&req.controller_id) + .map_err(|e| Status::unknown(e.to_string()))?; + Ok(Response::new(Empty {})) + } + + pub fn set_instance_settings( + &self, + req: SetInstanceSettingsRequest, + ) -> Result, Status> { + let settings = serde_json::from_str(&req.settings) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_instance_command(req.instance_id, |instance_shell| { + instance_shell.change_settings(|current_settings| *current_settings = settings); + Ok(()) + }) + } + + pub fn set_playtime_engine_settings( + &self, + req: SetPlaytimeEngineSettingsRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_playtime_engine_settings(req) + } + } + + pub fn insert_columns(&self, req: InsertColumnsRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.insert_columns(req) + } + } + + pub fn trigger_global(&self, req: TriggerGlobalRequest) -> Result, Status> { + let action = TriggerGlobalAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger global action"))?; + match action { + TriggerGlobalAction::FocusHost => { + Window::from_hwnd(Reaper::get().main_window()).focus(); + } + TriggerGlobalAction::MidiPanic => { + let _ = Reaper::get() + .main_section() + .action_by_command_id(CommandId::new(40345)) + .invoke_as_trigger(None, None); + BackboneShell::get() + .proto_hub() + .notify_about_global_info_event(GlobalInfoEvent::generic( + "All MIDI outputs and plug-ins have been instructed to shut up.", + )); + } + } + Ok(Response::new(Empty {})) + } + + pub fn trigger_matrix(&self, req: TriggerMatrixRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.trigger_matrix(req) + } + } + + pub fn trigger_instance(&self, req: TriggerInstanceRequest) -> Result, Status> { + let action = TriggerInstanceAction::try_from(req.action) + .map_err(|_| Status::invalid_argument("unknown trigger instance action"))?; + self.handle_instance_command(req.instance_id, |instance| { + let project = instance.processor_context().project().or_current_project(); + match action { + TriggerInstanceAction::ShowHelgoboxPlugin => { + instance + .processor_context() + .containing_fx() + .show_in_floating_window()?; + } + TriggerInstanceAction::CloseApp => { + instance.panel().stop_app_instance(); + } + TriggerInstanceAction::HideApp => { + instance.panel().hide_app_instance(); + } + TriggerInstanceAction::ArrangementTogglePlayStop => { + if project.is_playing() { + project.stop(); + } else { + project.play(); + } + } + TriggerInstanceAction::ArrangementPlay => { + project.play(); + } + TriggerInstanceAction::ArrangementStop => { + project.stop(); + } + TriggerInstanceAction::ArrangementPause => { + project.pause(); + } + TriggerInstanceAction::ArrangementStartRecording => { + // Recording not supported per project + Reaper::get().enable_record_in_current_project(); + } + TriggerInstanceAction::ArrangementStopRecording => { + // Recording not supported per project + Reaper::get().disable_record_in_current_project(); + } + TriggerInstanceAction::SaveProject => { + let save_project_command_id = CommandId::new(40026); + Reaper::get() + .main_section() + .action_by_command_id(save_project_command_id) + .invoke_as_trigger(Some(project), None)?; + BackboneShell::get() + .proto_hub() + .notify_about_instance_info_event( + instance.instance_id(), + InstanceInfoEvent::generic("Saved REAPER project"), + ); + } + } + Ok(()) + }) + } + + pub fn set_matrix_settings( + &self, + req: SetMatrixSettingsRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_matrix_settings(req) + } + } + + pub fn trigger_column(&self, req: TriggerColumnRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.trigger_column(req) + } + } + + pub fn trigger_track(&self, req: TriggerTrackRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.trigger_track(req) + } + } + + pub fn set_column_settings( + &self, + req: SetColumnSettingsRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_column_settings(req) + } + } + + pub fn trigger_row(&self, req: TriggerRowRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.trigger_row(req) + } + } + + pub fn set_row_data(&self, req: SetRowDataRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_row_data(req) + } + } + + pub fn set_matrix_tempo(&self, req: SetMatrixTempoRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_matrix_tempo(req) + } + } + + pub fn set_matrix_play_rate( + &self, + req: SetMatrixPlayRateRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_matrix_play_rate(req) + } + } + + pub fn set_matrix_time_signature( + &self, + req: SetMatrixTimeSignatureRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_matrix_time_signature(req) + } + } + + pub fn set_matrix_volume( + &self, + req: SetMatrixVolumeRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_matrix_volume(req) + } + } + + pub fn set_matrix_pan(&self, req: SetMatrixPanRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_matrix_pan(req) + } + } + + pub fn set_matrix_click_channel( + &self, + req: SetMatrixClickChannelRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_matrix_click_channel(req) + } + } + + pub fn set_track_volume(&self, req: SetTrackVolumeRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_track_volume(req) + } + } + + pub fn set_track_pan(&self, req: SetTrackPanRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_track_pan(req) + } + } + + pub fn open_track_fx(&self, req: OpenTrackFxRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.open_track_fx(req) + } + } + + pub async fn set_column_track( + &self, + req: SetColumnTrackRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_column_track(req).await + } + } + + pub fn set_track_input_monitoring( + &self, + req: SetTrackInputMonitoringRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_track_input_monitoring(req) + } + } + + pub fn set_track_input(&self, req: SetTrackInputRequest) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + playtime_not_available() + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.set_track_input(req) + } + } + + pub async fn get_clip_detail( + &self, + req: GetClipDetailRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.get_clip_detail(req).await + } + } + + pub async fn get_app_settings( + &self, + _req: GetAppSettingsRequest, + ) -> Result, Status> { + Ok(Response::new(GetAppSettingsReply { + app_settings: BackboneShell::read_app_settings(), + })) + } + + pub fn set_app_settings(&self, req: SetAppSettingsRequest) -> Result, Status> { + BackboneShell::write_app_settings(req.app_settings) + .map_err(|e| Status::unknown(e.to_string()))?; + Ok(Response::new(Empty {})) + } + + pub fn get_compartment_data( + &self, + request: GetCompartmentDataRequest, + ) -> Result, Status> { + self.handle_compartment_command_internal( + &request.compartment_id, + |unit_shell, compartment| { + let unit_model = unit_shell.model().borrow(); + let compartment_model = unit_model.extract_compartment_model(compartment); + let compartment_model_data = CompartmentModelData::from_model(&compartment_model); + let compartment_mode_api = from_data::convert_compartment( + compartment_model_data, + ConversionStyle::IncludeDefaultValues, + )?; + let reply = GetCompartmentDataReply { + data: serde_json::to_string(&compartment_mode_api)?, + }; + Ok(Response::new(reply)) + }, + ) + } + + pub fn save_custom_compartment_data( + &self, + request: SaveCustomCompartmentDataRequest, + ) -> Result, Status> { + let value: serde_json::Value = serde_json::from_str(&request.custom_data) + .map_err(|e| Status::invalid_argument(e.to_string()))?; + self.handle_compartment_command_internal( + &request.compartment_id, + |unit_shell, compartment| { + let unit_model = unit_shell.model().borrow(); + let unit = unit_model.unit(); + unit.borrow_mut().update_custom_compartment_data_key( + compartment, + request.custom_key, + value, + ); + Ok(Response::new(Empty {})) + }, + ) + } + + pub fn get_custom_instance_data( + &self, + request: GetCustomInstanceDataRequest, + ) -> Result, Status> { + self.handle_instance_command_internal(request.instance_id, |instance| { + let instance_model = instance.model().borrow(); + let instance = instance_model.instance().borrow(); + let data = instance.custom_data().get(&request.custom_key); + let reply = GetCustomInstanceDataReply { + data: if let Some(d) = data { + Some(serde_json::to_string(d)?) + } else { + None + }, + }; + Ok(Response::new(reply)) + }) + } + + pub fn set_custom_instance_data( + &self, + request: SetCustomInstanceDataRequest, + ) -> Result, Status> { + self.handle_instance_command(request.instance_id, |instance| { + let value = serde_json::from_str(&request.custom_data)?; + instance + .model() + .borrow() + .instance() + .borrow_mut() + .update_custom_data_key(request.custom_key, value); + Ok(()) + }) + } + + pub async fn get_host_info( + &self, + _req: GetHostInfoRequest, + ) -> Result, Status> { + use crate::infrastructure::plugin::built_info::*; + Ok(Response::new(GetHostInfoReply { + public_version: PKG_VERSION.to_string(), + api_version: HOST_API_VERSION.to_string(), + })) + } + + pub async fn prove_authenticity( + &self, + req: ProveAuthenticityRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.prove_authenticity(req).await + } + } + + pub async fn get_project_dir( + &self, + req: GetProjectDirRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.get_project_dir(req).await + } + } + + pub async fn get_arrangement_info( + &self, + req: GetArrangementInfoRequest, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + PlaytimeProtoRequestHandler.get_arrangement_info(req).await + } + } + + fn handle_instance_command( + &self, + instance_id: u32, + handler: impl FnOnce(&InstanceShell) -> anyhow::Result<()>, + ) -> Result, Status> { + self.handle_instance_command_internal(instance_id, handler)?; + Ok(Response::new(Empty {})) + } + + fn handle_compartment_command_internal( + &self, + full_compartment_id: &Option, + handler: impl FnOnce(&UnitShell, CompartmentKind) -> anyhow::Result, + ) -> Result { + let full_compartment_id = full_compartment_id + .as_ref() + .ok_or_else(|| Status::invalid_argument("need full compartment ID"))?; + let compartment = Compartment::try_from(full_compartment_id.compartment) + .map_err(|_| Status::invalid_argument("unknown compartment"))?; + + self.handle_unit_command_internal( + full_compartment_id.instance_id, + Some(full_compartment_id.unit_id), + |_, unit_shell| handler(unit_shell, compartment.to_engine()), + ) + } + + fn handle_unit_command_internal( + &self, + instance_id: u32, + unit_id: Option, + handler: impl FnOnce(&InstanceShell, &UnitShell) -> anyhow::Result, + ) -> Result { + self.handle_instance_command_internal(instance_id, |instance_shell| { + let unit_id = unit_id.map(UnitId::from); + instance_shell + .find_unit_prop_by_id_simple(unit_id, |_, unit_shell| { + handler(instance_shell, unit_shell) + }) + .context("Unit not found")? + }) + } + + fn handle_instance_command_internal( + &self, + instance_id: u32, + handler: impl FnOnce(&InstanceShell) -> anyhow::Result, + ) -> Result { + let instance_shell = BackboneShell::get() + .get_instance_shell_by_instance_id(instance_id.into()) + .map_err(|e| Status::not_found(format!("{e:#}")))?; + let r = handler(&instance_shell).map_err(|e| Status::unknown(format!("{e:#}")))?; + Ok(r) + } +} + +#[cfg(not(feature = "playtime"))] +pub fn playtime_not_available() -> Result, Status> { + Err(playtime_not_available_status()) +} + +#[cfg(not(feature = "playtime"))] +pub fn playtime_not_available_status() -> Status { + Status::not_found("Playtime not available") +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/senders.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/senders.rs new file mode 100644 index 0000000..447bfcd --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/senders.rs @@ -0,0 +1,222 @@ +use crate::domain::InstanceId; +use crate::infrastructure::proto::{ + event_reply, ContinuousColumnUpdate, ContinuousMatrixUpdate, EventReply, + OccasionalGlobalUpdate, OccasionalInstanceUpdate, OccasionalMatrixUpdate, + OccasionalPlaytimeEngineUpdate, QualifiedContinuousSlotUpdate, QualifiedOccasionalClipUpdate, + QualifiedOccasionalColumnUpdate, QualifiedOccasionalRowUpdate, QualifiedOccasionalSlotUpdate, + QualifiedOccasionalTrackUpdate, QualifiedOccasionalUnitUpdate, +}; +use futures::future; +use tokio::sync::broadcast::{Receiver, Sender}; + +/// This must be a global object because it's responsible for supplying one gRPC endpoint with +/// streaming data, and we have only one endpoint for all matrices. +#[derive(Clone, Debug)] +pub struct ProtoSenders { + pub occasional_global_update_sender: Sender, + pub occasional_instance_update_sender: Sender, + pub occasional_unit_update_sender: Sender, + pub occasional_playtime_engine_update_sender: Sender, + pub occasional_matrix_update_sender: Sender, + pub occasional_track_update_sender: Sender, + pub occasional_column_update_sender: Sender, + pub occasional_row_update_sender: Sender, + pub occasional_slot_update_sender: Sender, + pub occasional_clip_update_sender: Sender, + pub continuous_matrix_update_sender: Sender, + pub continuous_column_update_sender: Sender, + pub continuous_slot_update_sender: Sender, +} + +#[derive(Debug)] +pub struct ProtoReceivers { + pub occasional_global_update_receiver: Receiver, + pub occasional_instance_update_receiver: Receiver, + pub occasional_unit_update_receiver: Receiver, + pub occasional_playtime_engine_update_receiver: Receiver, + pub occasional_matrix_update_receiver: Receiver, + pub occasional_track_update_receiver: Receiver, + pub occasional_column_update_receiver: Receiver, + pub occasional_row_update_receiver: Receiver, + pub occasional_slot_update_receiver: Receiver, + pub occasional_clip_update_receiver: Receiver, + pub continuous_matrix_update_receiver: Receiver, + pub continuous_column_update_receiver: Receiver, + pub continuous_slot_update_receiver: Receiver, +} + +impl ProtoReceivers { + pub async fn keep_processing_updates( + &mut self, + instance_id: InstanceId, + process: &impl Fn(EventReply), + ) { + future::join5( + future::join5( + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.continuous_matrix_update_receiver, + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.continuous_column_update_receiver, + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.continuous_slot_update_receiver, + ), + keep_processing_updates(process, &mut self.occasional_global_update_receiver), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_instance_update_receiver, + ), + ), + future::join5( + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_matrix_update_receiver, + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_track_update_receiver, + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_column_update_receiver, + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_row_update_receiver, + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_slot_update_receiver, + ), + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_clip_update_receiver, + ), + keep_processing_session_filtered_updates( + instance_id, + process, + &mut self.occasional_unit_update_receiver, + ), + keep_processing_updates( + process, + &mut self.occasional_playtime_engine_update_receiver, + ), + ) + .await; + } +} + +async fn keep_processing_session_filtered_updates( + instance_id: InstanceId, + process: impl Fn(EventReply), + receiver: &mut Receiver>, +) where + T: Clone + Into, +{ + loop { + if let Ok(batch) = receiver.recv().await { + if batch.instance_id != instance_id { + continue; + } + let reply = EventReply { + value: Some(batch.value.into()), + }; + process(reply); + } + } +} + +async fn keep_processing_updates(process: impl Fn(EventReply), receiver: &mut Receiver) +where + T: Clone + Into, +{ + loop { + if let Ok(batch) = receiver.recv().await { + let reply = EventReply { + value: Some(batch.into()), + }; + process(reply); + } + } +} + +impl Default for ProtoSenders { + fn default() -> Self { + Self::new() + } +} + +impl ProtoSenders { + pub fn new() -> Self { + Self { + occasional_global_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_instance_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_unit_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_playtime_engine_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_matrix_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_track_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_column_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_row_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_slot_update_sender: tokio::sync::broadcast::channel(100).0, + occasional_clip_update_sender: tokio::sync::broadcast::channel(100).0, + continuous_slot_update_sender: tokio::sync::broadcast::channel(1000).0, + continuous_column_update_sender: tokio::sync::broadcast::channel(500).0, + continuous_matrix_update_sender: tokio::sync::broadcast::channel(500).0, + } + } + + pub fn subscribe_to_all(&self) -> ProtoReceivers { + ProtoReceivers { + occasional_global_update_receiver: self.occasional_global_update_sender.subscribe(), + occasional_instance_update_receiver: self.occasional_instance_update_sender.subscribe(), + occasional_unit_update_receiver: self.occasional_unit_update_sender.subscribe(), + occasional_playtime_engine_update_receiver: self + .occasional_playtime_engine_update_sender + .subscribe(), + occasional_matrix_update_receiver: self.occasional_matrix_update_sender.subscribe(), + occasional_track_update_receiver: self.occasional_track_update_sender.subscribe(), + occasional_column_update_receiver: self.occasional_column_update_sender.subscribe(), + occasional_row_update_receiver: self.occasional_row_update_sender.subscribe(), + occasional_slot_update_receiver: self.occasional_slot_update_sender.subscribe(), + occasional_clip_update_receiver: self.occasional_clip_update_sender.subscribe(), + continuous_matrix_update_receiver: self.continuous_matrix_update_sender.subscribe(), + continuous_column_update_receiver: self.continuous_column_update_sender.subscribe(), + continuous_slot_update_receiver: self.continuous_slot_update_sender.subscribe(), + } + } +} + +#[derive(Clone)] +pub struct WithInstanceId { + pub instance_id: InstanceId, + pub value: T, +} + +pub type OccasionalGlobalUpdateBatch = Vec; +pub type OccasionalInstanceUpdateBatch = WithInstanceId>; +pub type OccasionalUnitUpdateBatch = WithInstanceId>; +pub type OccasionalPlaytimeEngineUpdateBatch = Vec; +pub type OccasionalMatrixUpdateBatch = WithInstanceId>; +pub type OccasionalTrackUpdateBatch = WithInstanceId>; +pub type OccasionalColumnUpdateBatch = WithInstanceId>; +pub type OccasionalRowUpdateBatch = WithInstanceId>; +pub type OccasionalSlotUpdateBatch = WithInstanceId>; +pub type OccasionalClipUpdateBatch = WithInstanceId>; +pub type ContinuousMatrixUpdateBatch = WithInstanceId; +pub type ContinuousColumnUpdateBatch = WithInstanceId>; +pub type ContinuousSlotUpdateBatch = WithInstanceId>; diff --git a/plugin-reaper-realearn/main/src/infrastructure/proto/service_impl.rs b/plugin-reaper-realearn/main/src/infrastructure/proto/service_impl.rs new file mode 100644 index 0000000..cb60f10 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/proto/service_impl.rs @@ -0,0 +1,855 @@ +use crate::infrastructure::plugin::BackboneShell; +#[cfg(not(feature = "playtime"))] +use crate::infrastructure::proto::playtime_not_available_status; +use crate::infrastructure::proto::senders::{ProtoSenders, WithInstanceId}; +use crate::infrastructure::proto::{ + create_initial_global_updates, create_initial_instance_updates, create_initial_unit_updates, + helgobox_service_server, AddLicenseRequest, DeleteControllerRequest, DragClipRequest, + DragColumnRequest, DragRowRequest, DragSlotRequest, Empty, GetAppSettingsReply, + GetAppSettingsRequest, GetArrangementInfoReply, GetArrangementInfoRequest, GetClipDetailReply, + GetClipDetailRequest, GetCompartmentDataReply, GetCompartmentDataRequest, + GetContinuousColumnUpdatesReply, GetContinuousColumnUpdatesRequest, + GetContinuousMatrixUpdatesReply, GetContinuousMatrixUpdatesRequest, + GetContinuousSlotUpdatesReply, GetContinuousSlotUpdatesRequest, GetCustomInstanceDataReply, + GetCustomInstanceDataRequest, GetOccasionalClipUpdatesReply, GetOccasionalClipUpdatesRequest, + GetOccasionalColumnUpdatesReply, GetOccasionalColumnUpdatesRequest, + GetOccasionalGlobalUpdatesReply, GetOccasionalGlobalUpdatesRequest, + GetOccasionalInstanceUpdatesReply, GetOccasionalInstanceUpdatesRequest, + GetOccasionalMatrixUpdatesReply, GetOccasionalMatrixUpdatesRequest, + GetOccasionalPlaytimeEngineUpdatesReply, GetOccasionalPlaytimeEngineUpdatesRequest, + GetOccasionalRowUpdatesReply, GetOccasionalRowUpdatesRequest, GetOccasionalSlotUpdatesReply, + GetOccasionalSlotUpdatesRequest, GetOccasionalTrackUpdatesReply, + GetOccasionalTrackUpdatesRequest, GetOccasionalUnitUpdatesReply, + GetOccasionalUnitUpdatesRequest, GetProjectDirReply, GetProjectDirRequest, ImportFilesRequest, + InsertColumnsRequest, OpenTrackFxRequest, ProtoRequestHandler, ProveAuthenticityReply, + ProveAuthenticityRequest, SaveControllerRequest, SaveCustomCompartmentDataRequest, + SetAppSettingsRequest, SetClipDataRequest, SetClipNameRequest, SetColumnSettingsRequest, + SetColumnTrackRequest, SetCustomInstanceDataRequest, SetInstanceSettingsRequest, + SetMatrixClickChannelRequest, SetMatrixPanRequest, SetMatrixPlayRateRequest, + SetMatrixSettingsRequest, SetMatrixTempoRequest, SetMatrixTimeSignatureRequest, + SetMatrixVolumeRequest, SetPlaytimeEngineSettingsRequest, SetRowDataRequest, + SetTrackColorRequest, SetTrackInputMonitoringRequest, SetTrackInputRequest, + SetTrackNameRequest, SetTrackPanRequest, SetTrackVolumeRequest, TriggerClipRequest, + TriggerColumnRequest, TriggerGlobalRequest, TriggerInstanceRequest, TriggerMatrixRequest, + TriggerRowRequest, TriggerSlotRequest, TriggerTrackRequest, +}; +use base::future_util; +use futures::{FutureExt, Stream, StreamExt}; +#[cfg(feature = "playtime")] +use playtime_clip_engine::base::Matrix; +use std::pin::Pin; +use std::{future, iter}; +use tokio::sync::broadcast::Receiver; +use tokio_stream::wrappers::BroadcastStream; +use tonic::{Request, Response, Status}; + +#[derive(Debug)] +pub struct HelgoboxServiceImpl { + command_handler: ProtoRequestHandler, + senders: ProtoSenders, +} + +impl HelgoboxServiceImpl { + pub(crate) fn new(command_handler: ProtoRequestHandler, senders: ProtoSenders) -> Self { + Self { + command_handler, + senders, + } + } + + #[cfg(feature = "playtime")] + fn with_matrix( + &self, + clip_matrix_id: u32, + f: impl FnOnce(&Matrix) -> R, + ) -> anyhow::Result { + BackboneShell::get().with_clip_matrix(clip_matrix_id.into(), f) + } +} + +#[tonic::async_trait] +impl helgobox_service_server::HelgoboxService for HelgoboxServiceImpl { + type GetContinuousMatrixUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_continuous_matrix_updates( + &self, + request: Request, + ) -> Result, Status> { + let receiver = self.senders.continuous_matrix_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |matrix_update| GetContinuousMatrixUpdatesReply { + matrix_update: Some(matrix_update), + }, + iter::empty(), + ) + } + type GetContinuousColumnUpdatesStream = + SyncBoxStream<'static, Result>; + async fn get_continuous_column_updates( + &self, + request: Request, + ) -> Result, Status> { + let receiver = self.senders.continuous_column_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |column_updates| GetContinuousColumnUpdatesReply { column_updates }, + iter::empty(), + ) + } + + type GetOccasionalColumnUpdatesStream = + SyncBoxStream<'static, Result>; + + type GetOccasionalRowUpdatesStream = + SyncBoxStream<'static, Result>; + + type GetOccasionalSlotUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_slot_updates( + &self, + request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = request; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + // Initial + let initial_updates = self + .with_matrix(request.get_ref().matrix_id, |matrix| { + crate::infrastructure::proto::create_initial_slot_updates(Some(matrix)) + }) + .unwrap_or_else(|_| { + crate::infrastructure::proto::create_initial_slot_updates(None) + }); + // On change + let receiver = self.senders.occasional_slot_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |slot_updates| GetOccasionalSlotUpdatesReply { slot_updates }, + Some(GetOccasionalSlotUpdatesReply { + slot_updates: initial_updates, + }) + .into_iter(), + ) + } + } + + async fn get_occasional_column_updates( + &self, + request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = request; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + // On change + let receiver = self.senders.occasional_column_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |column_updates| GetOccasionalColumnUpdatesReply { column_updates }, + iter::empty(), + ) + } + } + + async fn get_occasional_row_updates( + &self, + request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = request; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + // On change + let receiver = self.senders.occasional_row_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |row_updates| GetOccasionalRowUpdatesReply { row_updates }, + iter::empty(), + ) + } + } + + type GetOccasionalClipUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_clip_updates( + &self, + request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = request; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + // Initial + let initial_updates = self + .with_matrix(request.get_ref().matrix_id, |matrix| { + crate::infrastructure::proto::create_initial_clip_updates(Some(matrix)) + }) + .unwrap_or_else(|_| { + crate::infrastructure::proto::create_initial_clip_updates(None) + }); + // On change + let receiver = self.senders.occasional_clip_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |clip_updates| GetOccasionalClipUpdatesReply { clip_updates }, + Some(GetOccasionalClipUpdatesReply { + clip_updates: initial_updates, + }) + .into_iter(), + ) + } + } + + type GetContinuousSlotUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_continuous_slot_updates( + &self, + request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = request; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + let receiver = self.senders.continuous_slot_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |slot_updates| GetContinuousSlotUpdatesReply { slot_updates }, + iter::empty(), + ) + } + } + + type GetOccasionalGlobalUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_global_updates( + &self, + _request: Request, + ) -> Result, Status> { + let initial_updates = create_initial_global_updates(); + let receiver = self.senders.occasional_global_update_sender.subscribe(); + stream( + receiver, + |global_updates| GetOccasionalGlobalUpdatesReply { global_updates }, + |_| true, + Some(GetOccasionalGlobalUpdatesReply { + global_updates: initial_updates, + }) + .into_iter(), + ) + } + + type GetOccasionalMatrixUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_matrix_updates( + &self, + request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = request; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + let initial_updates = self + .with_matrix(request.get_ref().matrix_id, |matrix| { + crate::infrastructure::proto::create_initial_matrix_updates(Some(matrix)) + }) + .unwrap_or_else(|_| { + crate::infrastructure::proto::create_initial_matrix_updates(None) + }); + let receiver = self.senders.occasional_matrix_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |matrix_updates| GetOccasionalMatrixUpdatesReply { matrix_updates }, + Some(GetOccasionalMatrixUpdatesReply { + matrix_updates: initial_updates, + }) + .into_iter(), + ) + } + } + + type GetOccasionalPlaytimeEngineUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_playtime_engine_updates( + &self, + _request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + let initial_updates = crate::infrastructure::proto::create_initial_engine_updates(); + let receiver = self + .senders + .occasional_playtime_engine_update_sender + .subscribe(); + stream( + receiver, + |updates| GetOccasionalPlaytimeEngineUpdatesReply { updates }, + |_| true, + Some(GetOccasionalPlaytimeEngineUpdatesReply { + updates: initial_updates, + }) + .into_iter(), + ) + } + } + + type GetOccasionalInstanceUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_instance_updates( + &self, + request: Request, + ) -> Result, Status> { + let instance_shell = BackboneShell::get() + .get_instance_shell_by_instance_id(request.get_ref().instance_id.into()) + .map_err(|e| Status::not_found(e.to_string()))?; + let initial_updates = create_initial_instance_updates(&instance_shell); + let receiver = self.senders.occasional_instance_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().instance_id, + receiver, + |instance_updates| GetOccasionalInstanceUpdatesReply { instance_updates }, + Some(GetOccasionalInstanceUpdatesReply { + instance_updates: initial_updates, + }) + .into_iter(), + ) + } + + type GetOccasionalUnitUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_unit_updates( + &self, + request: Request, + ) -> Result, Status> { + let instance_shell = BackboneShell::get() + .get_instance_shell_by_instance_id(request.get_ref().instance_id.into()) + .map_err(|e| Status::not_found(e.to_string()))?; + let initial_updates = create_initial_unit_updates(&instance_shell); + let receiver = self.senders.occasional_unit_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().instance_id, + receiver, + |unit_updates| GetOccasionalUnitUpdatesReply { unit_updates }, + Some(GetOccasionalUnitUpdatesReply { + unit_updates: initial_updates, + }) + .into_iter(), + ) + } + + type GetOccasionalTrackUpdatesStream = + SyncBoxStream<'static, Result>; + + async fn get_occasional_track_updates( + &self, + request: Request, + ) -> Result, Status> { + #[cfg(not(feature = "playtime"))] + { + let _ = request; + Err(playtime_not_available_status()) + } + #[cfg(feature = "playtime")] + { + let initial_reply = self + .with_matrix(request.get_ref().matrix_id, |matrix| { + crate::infrastructure::proto::create_initial_track_updates(Some(matrix)) + }) + .unwrap_or_else(|_| { + crate::infrastructure::proto::create_initial_track_updates(None) + }); + let receiver = self.senders.occasional_track_update_sender.subscribe(); + stream_by_session_id( + request.into_inner().matrix_id, + receiver, + |track_updates| GetOccasionalTrackUpdatesReply { track_updates }, + Some(GetOccasionalTrackUpdatesReply { + track_updates: initial_reply, + }) + .into_iter(), + ) + } + } + + async fn trigger_slot( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_slot(request.into_inner()) + } + + async fn trigger_clip( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_clip(request.into_inner()) + } + + async fn drag_slot( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.drag_slot(request.into_inner()) + } + + async fn drag_clip( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.drag_clip(request.into_inner()) + } + + async fn drag_row(&self, request: Request) -> Result, Status> { + self.command_handler.drag_row(request.into_inner()) + } + + async fn drag_column( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.drag_column(request.into_inner()) + } + + async fn set_track_name( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_track_name(request.into_inner()) + } + + async fn set_track_color( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_track_color(request.into_inner()) + } + + async fn set_clip_name( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_clip_name(request.into_inner()) + } + + async fn set_clip_data( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_clip_data(request.into_inner()) + } + + async fn trigger_sequence( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_sequence(request.into_inner()) + } + + async fn set_sequence_info( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_sequence_info(request.into_inner()) + } + + async fn trigger_matrix( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_matrix(request.into_inner()) + } + + async fn set_matrix_settings( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_matrix_settings(request.into_inner()) + } + + async fn trigger_column( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_column(request.into_inner()) + } + + async fn trigger_track( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_track(request.into_inner()) + } + + async fn set_column_settings( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_column_settings(request.into_inner()) + } + + async fn trigger_row( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_row(request.into_inner()) + } + + async fn set_row_data( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_row_data(request.into_inner()) + } + + async fn set_matrix_tempo( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_matrix_tempo(request.into_inner()) + } + + async fn set_matrix_play_rate( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_matrix_play_rate(request.into_inner()) + } + + async fn set_matrix_time_signature( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_matrix_time_signature(request.into_inner()) + } + + async fn set_matrix_volume( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_matrix_volume(request.into_inner()) + } + + async fn set_matrix_pan( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_matrix_pan(request.into_inner()) + } + + async fn set_matrix_click_channel( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_matrix_click_channel(request.into_inner()) + } + + async fn set_track_volume( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_track_volume(request.into_inner()) + } + + async fn set_track_pan( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_track_pan(request.into_inner()) + } + + async fn open_track_fx( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.open_track_fx(request.into_inner()) + } + + async fn set_column_track( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_column_track(request.into_inner()) + .await + } + + async fn get_clip_detail( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .get_clip_detail(request.into_inner()) + .await + } + + async fn get_host_info( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .get_host_info(request.into_inner()) + .await + } + + async fn prove_authenticity( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .prove_authenticity(request.into_inner()) + .await + } + + async fn get_project_dir( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .get_project_dir(request.into_inner()) + .await + } + + async fn get_arrangement_info( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .get_arrangement_info(request.into_inner()) + .await + } + + async fn set_track_input_monitoring( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_track_input_monitoring(request.into_inner()) + } + + async fn set_track_input( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_track_input(request.into_inner()) + } + + async fn import_files( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.import_files(request.into_inner()) + } + + async fn add_license( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.add_license(request.into_inner()) + } + + async fn save_controller( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.save_controller(request.into_inner()) + } + + async fn delete_controller( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.delete_controller(request.into_inner()) + } + + async fn set_instance_settings( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_instance_settings(request.into_inner()) + } + + async fn get_app_settings( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .get_app_settings(request.into_inner()) + .await + } + + async fn set_app_settings( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.set_app_settings(request.into_inner()) + } + + async fn get_compartment_data( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .get_compartment_data(request.into_inner()) + } + + async fn save_custom_compartment_data( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .save_custom_compartment_data(request.into_inner()) + } + + async fn get_custom_instance_data( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .get_custom_instance_data(request.into_inner()) + } + + async fn set_custom_instance_data( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_custom_instance_data(request.into_inner()) + } + + async fn trigger_global( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_global(request.into_inner()) + } + + async fn trigger_instance( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.trigger_instance(request.into_inner()) + } + + async fn insert_columns( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler.insert_columns(request.into_inner()) + } + + async fn set_playtime_engine_settings( + &self, + request: Request, + ) -> Result, Status> { + self.command_handler + .set_playtime_engine_settings(request.into_inner()) + } +} + +type SyncBoxStream<'a, T> = Pin + Send + Sync + 'a>>; + +fn stream_by_session_id( + requested_instance_id: u32, + receiver: Receiver>, + create_result: F, + initial: I, +) -> Result>>, Status> +where + T: Clone + Send + 'static, + R: Send + Sync + 'static, + F: Fn(T) -> R + Send + Sync + 'static, + I: Iterator + Send + Sync + 'static, +{ + stream( + receiver, + move |v| create_result(v.value), + move |v| v.instance_id == requested_instance_id.into(), + initial, + ) +} + +fn stream( + receiver: Receiver, + create_result: F, + include: Include, + initial: I, +) -> Result>>, Status> +where + T: Clone + Send + 'static, + R: Send + Sync + 'static, + F: Fn(T) -> R + Send + Sync + 'static, + I: Iterator + Send + Sync + 'static, + Include: Fn(&T) -> bool + Send + Sync + 'static, +{ + // Stream that waits 1 millisecond and emits nothing + // This is done to (hopefully) prevent the following client-side Dart error, which otherwise + // would occur sporadically when attempting to connect: + // [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: gRPC Error (code: 2, codeName: UNKNOWN, message: HTTP/2 error: Connection error: Connection is being forcefully terminated. (errorCode: 1), details: null, rawResponse: null, trailers: {}) + let wait_one_milli = future_util::millis(1) + .map(|_| Err(Status::unknown("skipped"))) + .into_stream() + .skip(1); + // Stream for sending the initial state + let initial_stream = futures::stream::iter(initial.map(|r| Ok(r))); + // Stream for sending occasional updates + let receiver_stream = BroadcastStream::new(receiver).filter_map(move |value| { + let res = match value { + // Error + Err(e) => Some(Err(Status::unknown(e.to_string()))), + // Playtime matrix ID matches + Ok(value) if include(&value) => Some(Ok(create_result(value))), + // Playtime matrix ID doesn't match + _ => None, + }; + future::ready(res) + }); + Ok(Response::new(Box::pin( + wait_one_milli.chain(initial_stream).chain(receiver_stream), + ))) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/data.rs b/plugin-reaper-realearn/main/src/infrastructure/server/data.rs new file mode 100644 index 0000000..6e8c608 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/data.rs @@ -0,0 +1,373 @@ +//! Contains the actual application interface and implementation without any HTTP-specific stuff. + +use crate::application::{ + CompartmentPresetManager, CompartmentPresetModel, SourceCategory, TargetCategory, UnitModel, +}; +use crate::domain::{CompartmentKind, MappingKey, ProjectionFeedbackValue}; +use crate::infrastructure::data::CompartmentPresetData; +use crate::infrastructure::plugin::BackboneShell; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use helgoboss_learn::UnitValue; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::rc::Rc; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +// Right now just a placeholder +pub struct SessionResponseData {} + +pub enum DataError { + SessionNotFound, + SessionHasNoActiveController, + ControllerNotFound, + OnlyPatchReplaceIsSupported, + OnlyCustomDataKeyIsSupportedAsPatchPath, + ControllerUpdateFailed, +} + +pub enum DataErrorCategory { + NotFound, + BadRequest, + MethodNotAllowed, + InternalServerError, +} + +impl DataError { + pub fn description(&self) -> &'static str { + use DataError::*; + match self { + SessionNotFound => "session not found", + SessionHasNoActiveController => "session doesn't have an active controller", + ControllerNotFound => "session has controller but controller not found", + OnlyPatchReplaceIsSupported => "only 'replace' is supported as op", + OnlyCustomDataKeyIsSupportedAsPatchPath => { + "only '/customData/{key}' is supported as path" + } + ControllerUpdateFailed => "couldn't update controller", + } + } + + pub fn category(&self) -> DataErrorCategory { + use DataError::*; + match self { + SessionNotFound | SessionHasNoActiveController | ControllerNotFound => { + DataErrorCategory::NotFound + } + OnlyPatchReplaceIsSupported => DataErrorCategory::MethodNotAllowed, + OnlyCustomDataKeyIsSupportedAsPatchPath => DataErrorCategory::BadRequest, + ControllerUpdateFailed => DataErrorCategory::InternalServerError, + } + } +} + +#[derive(Deserialize)] +pub struct PatchRequest { + op: PatchRequestOp, + path: String, + value: serde_json::value::Value, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +enum PatchRequestOp { + Replace, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ControllerRouting { + main_preset: Option, + routes: NonCryptoHashMap>, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct LightMainPresetData { + id: String, + name: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct TargetDescriptor { + label: String, +} + +pub fn get_session_data(session_id: String) -> Result { + let _ = BackboneShell::get() + .find_unit_model_by_key(&session_id) + .ok_or(DataError::SessionNotFound)?; + Ok(SessionResponseData {}) +} + +pub fn get_controller_routing_by_session_id( + session_id: String, +) -> Result { + let session = BackboneShell::get() + .find_unit_model_by_key(&session_id) + .ok_or(DataError::SessionNotFound)?; + let routing = get_controller_routing(&session.borrow()); + Ok(routing) +} + +pub fn get_controller_preset_data(session_id: String) -> Result { + let session = BackboneShell::get() + .find_unit_model_by_key(&session_id) + .ok_or(DataError::SessionNotFound)?; + let session = session.borrow(); + get_controller_preset_data_internal(&session) +} + +pub fn get_controller_routing(session: &UnitModel) -> ControllerRouting { + let main_preset = session.active_main_preset().map(|mp| LightMainPresetData { + id: mp.id().to_string(), + name: mp.name().to_string(), + }); + let instance_state = session.unit().borrow(); + let routes = session + .mappings(CompartmentKind::Controller) + .filter_map(|m| { + let m = m.borrow(); + if !m.visible_in_projection() { + return None; + } + let target_descriptor = if instance_state.mapping_is_on(m.qualified_id()) { + if m.target_model.category() == TargetCategory::Virtual { + // Virtual + let control_element = m.target_model.create_control_element(); + let matching_main_mappings = + session.mappings(CompartmentKind::Main).filter(|mp| { + let mp = mp.borrow(); + mp.visible_in_projection() + && mp.source_model.category() == SourceCategory::Virtual + && mp.source_model.create_control_element() == control_element + && instance_state.mapping_is_on(mp.qualified_id()) + }); + let descriptors: Vec<_> = matching_main_mappings + .map(|m| { + let m = m.borrow(); + TargetDescriptor { + label: m.effective_name(), + } + }) + .collect(); + if descriptors.is_empty() { + return None; + } + descriptors + } else { + // Direct + let single_descriptor = TargetDescriptor { + label: m.effective_name(), + }; + vec![single_descriptor] + } + } else { + return None; + }; + Some((m.key().clone(), target_descriptor)) + }) + .collect(); + ControllerRouting { + main_preset, + routes, + } +} + +pub fn patch_controller(controller_id: String, req: PatchRequest) -> Result<(), DataError> { + if req.op != PatchRequestOp::Replace { + return Err(DataError::OnlyPatchReplaceIsSupported); + } + let split_path: Vec<_> = req.path.split('/').collect(); + let custom_data_key = if let ["", "customData", key] = split_path.as_slice() { + key + } else { + return Err(DataError::OnlyCustomDataKeyIsSupportedAsPatchPath); + }; + // Update the global controller preset. + let controller_manager = BackboneShell::get().controller_preset_manager(); + let mut controller_manager = controller_manager.borrow_mut(); + let mut controller_preset = controller_manager + .find_by_id(&controller_id) + .ok_or(DataError::ControllerNotFound)?; + controller_preset.patch_custom_data(custom_data_key.to_string(), req.value.clone()); + controller_manager + .update_preset(controller_preset) + .map_err(|_| DataError::ControllerUpdateFailed)?; + // Update all sessions which use this preset. If we don't do that, the Companion app will not + // get the saved changes - they will just disappear (#591) - because we made a change in + // v1.13.0-pre.4 that /realearn/session/.../controller queries the session, not the global + // controller preset. + // TODO-low In future versions of the Companion app, we should not update the controller + // source directly but update a session. This makes more sense because now ReaLearn treats + // custom data exactly like mappings - it's saved with the session. + BackboneShell::get().with_unit_infos(|infos| { + let units = infos.iter().filter_map(|s| s.unit.upgrade()); + for unit in units { + let mut unit = unit.borrow_mut(); + unit.update_custom_compartment_data_key( + CompartmentKind::Controller, + custom_data_key.to_string(), + req.value.clone(), + ); + } + }); + Ok(()) +} + +#[derive(Deserialize)] +pub struct WebSocketRequest { + pub topics: String, +} + +impl WebSocketRequest { + pub fn parse_topics(&self) -> Topics { + self.topics.split(',').flat_map(Topic::try_from).collect() + } +} + +pub type Topics = NonCryptoHashSet; + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum Topic { + Session { session_id: String }, + ActiveController { session_id: String }, + ControllerRouting { session_id: String }, + Feedback { session_id: String }, +} + +impl TryFrom<&str> for Topic { + type Error = &'static str; + + fn try_from(topic_expression: &str) -> Result { + let topic_segments: Vec<_> = topic_expression.split('/').skip(1).collect(); + let topic = match topic_segments.as_slice() { + ["realearn", "session", id, "controller-routing"] => Topic::ControllerRouting { + session_id: id.to_string(), + }, + ["realearn", "session", id, "controller"] => Topic::ActiveController { + session_id: id.to_string(), + }, + ["realearn", "session", id, "feedback"] => Topic::Feedback { + session_id: id.to_string(), + }, + ["realearn", "session", id] => Topic::Session { + session_id: id.to_string(), + }, + _ => return Err("invalid topic expression"), + }; + Ok(topic) + } +} + +pub fn send_initial_feedback(session_id: &str) { + if let Some(session) = BackboneShell::get().find_unit_model_by_key(session_id) { + session.borrow_mut().send_all_feedback(); + } +} + +pub fn get_active_controller_updated_event( + session_id: &str, + session: Option<&UnitModel>, +) -> Event> { + Event::put( + format!("/realearn/session/{session_id}/controller"), + session.and_then(get_controller), + ) +} + +pub fn get_projection_feedback_event( + session_id: &str, + feedback_value: ProjectionFeedbackValue, +) -> Event, UnitValue>> { + let mut map = HashMap::default(); + map.insert(feedback_value.mapping_key, feedback_value.value); + Event::patch(format!("/realearn/session/{session_id}/feedback"), map) +} + +pub fn get_session_updated_event( + session_id: &str, + session_data: Option, +) -> Event> { + Event::put(format!("/realearn/session/{session_id}"), session_data) +} + +pub fn get_controller_routing_updated_event( + session_id: &str, + session: Option<&UnitModel>, +) -> Event> { + Event::put( + format!("/realearn/session/{session_id}/controller-routing"), + session.map(get_controller_routing), + ) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Event { + /// Roughly corresponds to the HTTP method of the resource. + r#type: EventType, + /// Corresponds to the HTTP path of the resource. + path: String, + /// Corresponds to the HTTP body. + /// + /// HTTP 404 corresponds to this value being `null` or undefined in JSON. If this is not enough + /// in future use cases, we can still add another field that resembles the HTTP status. + body: T, +} + +impl Event { + pub fn put(path: String, body: T) -> Event { + Event { + r#type: EventType::Put, + path, + body, + } + } + + pub fn patch(path: String, body: T) -> Event { + Event { + r#type: EventType::Patch, + path, + body, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize)] +#[serde(rename_all = "lowercase")] +enum EventType { + Put, + Patch, +} + +fn get_controller(session: &UnitModel) -> Option { + get_controller_preset_data_internal(session).ok() +} + +fn get_controller_preset_data_internal( + session: &UnitModel, +) -> Result { + let data = session.extract_compartment_model(CompartmentKind::Controller); + if data.mappings.is_empty() { + return Err(DataError::SessionHasNoActiveController); + } + let id = session.active_controller_preset_id(); + let name = id + .and_then(|id| { + BackboneShell::get() + .controller_preset_manager() + .borrow() + .find_by_id(id) + }) + .map(|preset| preset.name().to_string()); + let preset = CompartmentPresetModel::new( + id.map(|id| id.to_string()).unwrap_or_default(), + name.unwrap_or_else(|| "".to_string()), + CompartmentKind::Controller, + data, + ); + Ok(CompartmentPresetData::from_model(&preset)) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/grpc/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/server/grpc/mod.rs new file mode 100644 index 0000000..fce9461 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/grpc/mod.rs @@ -0,0 +1,3 @@ +mod server; + +pub use server::*; diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/grpc/server.rs b/plugin-reaper-realearn/main/src/infrastructure/server/grpc/server.rs new file mode 100644 index 0000000..70d2ddd --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/grpc/server.rs @@ -0,0 +1,13 @@ +use crate::infrastructure::server::services::Services; +use std::net::SocketAddr; + +pub async fn start_grpc_server( + address: SocketAddr, + services: Services, +) -> Result<(), tonic::transport::Error> { + tonic::transport::Server::builder() + .layer(crate::infrastructure::server::layers::MainThreadLayer) + .add_service(services.helgobox_service) + .serve(address) + .await +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/http/client.rs b/plugin-reaper-realearn/main/src/infrastructure/server/http/client.rs new file mode 100644 index 0000000..afbe29c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/http/client.rs @@ -0,0 +1,27 @@ +//! Contains the mainly technical HTTP/WebSocket server code. + +use crate::infrastructure::server::data::{Topic, Topics}; +use base::hash_util::NonCryptoHashMap; +use serde::Serialize; +use std::sync::Arc; +use tokio::sync::mpsc; + +// We don't take the async RwLock by Tokio because we need to access this in sync code, too! +pub type ServerClients = Arc>>; + +#[derive(Debug, Clone)] +pub struct WebSocketClient { + pub topics: Topics, + pub sender: mpsc::UnboundedSender, +} + +impl WebSocketClient { + pub fn send(&self, msg: impl Serialize) -> Result<(), &'static str> { + let json = serde_json::to_string(&msg).map_err(|_| "couldn't serialize")?; + self.sender.send(json).map_err(|_| "couldn't send") + } + + pub fn is_subscribed_to(&self, topic: &Topic) -> bool { + self.topics.contains(topic) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/http/handlers.rs b/plugin-reaper-realearn/main/src/infrastructure/server/http/handlers.rs new file mode 100644 index 0000000..a28e867 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/http/handlers.rs @@ -0,0 +1,138 @@ +use crate::infrastructure::data::CompartmentPresetData; +use crate::infrastructure::server::data::{ + get_controller_preset_data, get_controller_routing_by_session_id, patch_controller, + ControllerRouting, DataError, DataErrorCategory, PatchRequest, SessionResponseData, Topics, +}; +use crate::infrastructure::server::http::{send_initial_events, ServerClients, WebSocketClient}; +use crate::infrastructure::server::MetricsReporter; +use axum::body::Body; +use axum::extract::ws::{Message, WebSocket}; +use axum::extract::Path; +use axum::http::{Response, StatusCode}; +use axum::response::Html; +use axum::Json; +use base::Global; +use std::sync::atomic::{AtomicUsize, Ordering}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; + +type SimpleResponse = (StatusCode, &'static str); + +pub async fn welcome_handler() -> Html<&'static str> { + Html(include_str!("../http/welcome_page.html")) +} + +/// Needs to be executed in the main thread! +pub async fn session_handler( + Path(session_id): Path, +) -> Result, SimpleResponse> { + let session_data = crate::infrastructure::server::data::get_session_data(session_id) + .map_err(translate_data_error)?; + Ok(Json(session_data)) +} + +/// Needs to be executed in the main thread! +pub async fn session_controller_handler( + Path(session_id): Path, +) -> Result, SimpleResponse> { + let controller_data = get_controller_preset_data(session_id).map_err(translate_data_error)?; + Ok(Json(controller_data)) +} + +/// Needs to be executed in the main thread! +pub async fn controller_routing_handler( + Path(session_id): Path, +) -> Result, SimpleResponse> { + let controller_routing = + get_controller_routing_by_session_id(session_id).map_err(translate_data_error)?; + Ok(Json(controller_routing)) +} + +/// Needs to be executed in the main thread! +pub async fn patch_controller_handler( + Path(controller_id): Path, + Json(patch_request): Json, +) -> Result { + patch_controller(controller_id, patch_request).map_err(translate_data_error)?; + Ok(StatusCode::OK) +} + +pub fn create_cert_response(cert: String, cert_file_name: &str) -> Response { + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/pkix-cert") + .header( + "Content-Disposition", + format!("attachment; filename=\"{cert_file_name}\""), + ) + .body(Body::from(cert)) + .unwrap() +} + +pub async fn create_metrics_response(metrics_reporter: MetricsReporter) -> Response { + let (status_code, text) = match metrics_reporter.render_as_prometheus() { + Ok(t) => (StatusCode::OK, t), + Err(e) => (StatusCode::NOT_IMPLEMENTED, e.to_string()), + }; + Response::builder() + .status(status_code) + .body(Body::from(text)) + .unwrap() +} + +pub async fn handle_websocket_upgrade(socket: WebSocket, topics: Topics, clients: ServerClients) { + use futures::{FutureExt, StreamExt}; + let (ws_sender_sink, mut ws_receiver_stream) = socket.split(); + let (client_sender, client_receiver) = mpsc::unbounded_channel(); + let client_receiver_stream = UnboundedReceiverStream::new(client_receiver); + // Keep forwarding received messages in client channel to websocket sender sink + tokio::task::spawn( + client_receiver_stream + .map(|json| Ok(Message::Text(json))) + .forward(ws_sender_sink) + .map(|result| { + if let Err(e) = result { + eprintln!("error sending websocket msg: {e}"); + } + }), + ); + // Create client struct + static NEXT_CLIENT_ID: AtomicUsize = AtomicUsize::new(1); + let client_id = NEXT_CLIENT_ID.fetch_add(1, Ordering::Relaxed); + let client = WebSocketClient { + topics, + sender: client_sender, + }; + // Memorize client + clients.write().unwrap().insert(client_id, client.clone()); + // Send initial events + Global::task_support() + .do_later_in_main_thread_asap(move || { + send_initial_events(&client); + }) + .unwrap(); + // Keep receiving websocket receiver stream messages + while let Some(result) = ws_receiver_stream.next().await { + // We will need this as soon as we are interested in what the client says + let _msg = match result { + Ok(msg) => msg, + Err(e) => { + eprintln!("websocket error: {e}"); + break; + } + }; + } + // Stream closed up, so remove from the client list + clients.write().unwrap().remove(&client_id); +} + +fn translate_data_error(e: DataError) -> SimpleResponse { + use DataErrorCategory::*; + let status_code = match e.category() { + NotFound => StatusCode::NOT_FOUND, + BadRequest => StatusCode::BAD_REQUEST, + MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED, + InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, + }; + (status_code, e.description()) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/http/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/server/http/mod.rs new file mode 100644 index 0000000..6f265cf --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/http/mod.rs @@ -0,0 +1,8 @@ +mod client; +mod handlers; +mod send; +mod server; + +pub use client::*; +pub use send::*; +pub use server::*; diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/http/send.rs b/plugin-reaper-realearn/main/src/infrastructure/server/http/send.rs new file mode 100644 index 0000000..262bfb6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/http/send.rs @@ -0,0 +1,212 @@ +//! Contains functions for sending data to WebSocket clients. +use crate::application::{SharedUnitModel, UnitModel}; +use crate::base::when; +use crate::domain::ProjectionFeedbackValue; +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::server::data::{ + get_active_controller_updated_event, get_controller_routing_updated_event, + get_projection_feedback_event, get_session_updated_event, send_initial_feedback, + SessionResponseData, Topic, +}; +use crate::infrastructure::server::http::client::WebSocketClient; +use base::Global; +use rxrust::prelude::*; +use serde::Serialize; +use std::rc::Rc; + +pub fn send_initial_events(client: &WebSocketClient) { + for topic in &client.topics { + let _ = send_initial_events_for_topic(client, topic); + } +} + +fn send_initial_events_for_topic( + client: &WebSocketClient, + topic: &Topic, +) -> Result<(), &'static str> { + use Topic::*; + match topic { + Session { session_id } => send_initial_session(client, session_id), + ControllerRouting { session_id } => send_initial_controller_routing(client, session_id), + ActiveController { session_id } => send_initial_controller(client, session_id), + Feedback { session_id } => { + send_initial_feedback(session_id); + Ok(()) + } + } +} + +pub fn send_initial_session( + client: &WebSocketClient, + session_id: &str, +) -> Result<(), &'static str> { + let event = if BackboneShell::get() + .find_unit_model_by_key(session_id) + .is_some() + { + get_session_updated_event(session_id, Some(SessionResponseData {})) + } else { + get_session_updated_event(session_id, None) + }; + client.send(event) +} + +fn send_initial_controller_routing( + client: &WebSocketClient, + session_id: &str, +) -> Result<(), &'static str> { + let event = if let Some(session) = BackboneShell::get().find_unit_model_by_key(session_id) { + get_controller_routing_updated_event(session_id, Some(&session.borrow())) + } else { + get_controller_routing_updated_event(session_id, None) + }; + client.send(event) +} + +fn send_initial_controller(client: &WebSocketClient, session_id: &str) -> Result<(), &'static str> { + let event = if let Some(session) = BackboneShell::get().find_unit_model_by_key(session_id) { + get_active_controller_updated_event(session_id, Some(&session.borrow())) + } else { + get_active_controller_updated_event(session_id, None) + }; + client.send(&event) +} + +pub fn send_updated_active_controller(session: &UnitModel) -> Result<(), &'static str> { + send_to_clients_subscribed_to( + &Topic::ActiveController { + session_id: session.unit_key().to_string(), + }, + || { + Some(get_active_controller_updated_event( + session.unit_key(), + Some(session), + )) + }, + ) +} + +pub fn send_updated_controller_routing(session: &UnitModel) -> Result<(), &'static str> { + BackboneShell::get() + .proto_hub() + .notify_controller_routing_changed(session); + send_to_clients_subscribed_to( + &Topic::ControllerRouting { + session_id: session.unit_key().to_string(), + }, + || { + Some(get_controller_routing_updated_event( + session.unit_key(), + Some(session), + )) + }, + ) +} + +pub fn send_projection_feedback_to_subscribed_clients( + session_id: &str, + value: ProjectionFeedbackValue, +) -> Result<(), &'static str> { + send_to_clients_subscribed_to( + &Topic::Feedback { + session_id: session_id.to_string(), + }, + || Some(get_projection_feedback_event(session_id, value)), + ) +} + +fn send_to_clients_subscribed_to( + topic: &Topic, + create_message: impl FnOnce() -> Option, +) -> Result<(), &'static str> { + for_each_client( + |client, cached| { + if let Some(cached) = cached { + if client.is_subscribed_to(topic) { + let _ = client.send(cached); + } + } + }, + create_message, + ) +} + +pub fn for_each_client( + op: impl Fn(&WebSocketClient, &T), + cache: impl FnOnce() -> T, +) -> Result<(), &'static str> { + let server = BackboneShell::get().server().borrow(); + if !server.is_running() { + return Ok(()); + } + let clients = server.clients()?.clone(); + let clients = clients + .read() + .map_err(|_| "couldn't get read lock for client")?; + if clients.is_empty() { + return Ok(()); + } + let cached = cache(); + for client in clients.values() { + op(client, &cached); + } + Ok(()) +} + +pub fn keep_informing_clients_about_sessions( + sessions_changed: impl LocalObservable<'static, Item = (), Err = ()> + 'static, +) { + sessions_changed.subscribe(|_| { + Global::task_support() + .do_later_in_main_thread_asap(|| { + send_sessions_to_subscribed_clients(); + }) + .unwrap(); + }); +} + +pub fn send_sessions_to_subscribed_clients() { + for_each_client( + |client, _| { + for t in client.topics.iter() { + if let Topic::Session { session_id } = t { + let _ = send_initial_session(client, session_id); + } + } + }, + || (), + ) + .unwrap(); +} + +pub fn keep_informing_clients_about_session_events(shared_session: &SharedUnitModel) { + let session = shared_session.borrow(); + let instance_state = session.unit().borrow(); + when( + instance_state + .on_mappings_changed() + .merge(session.mapping_list_changed().map_to(())), + ) + .with(Rc::downgrade(shared_session)) + .do_async(|session, _| { + let _ = send_updated_controller_routing(&session.borrow()); + }); + when( + BackboneShell::get() + .controller_preset_manager() + .borrow() + .changed(), + ) + .with(Rc::downgrade(shared_session)) + .do_async(|session, _| { + let _ = send_updated_active_controller(&session.borrow()); + }); + when(session.everything_changed()) + .with(Rc::downgrade(shared_session)) + .do_async(|session, _| { + send_sessions_to_subscribed_clients(); + let session = session.borrow(); + let _ = send_updated_active_controller(&session); + let _ = send_updated_controller_routing(&session); + }); +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/http/server.rs b/plugin-reaper-realearn/main/src/infrastructure/server/http/server.rs new file mode 100644 index 0000000..3186d83 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/http/server.rs @@ -0,0 +1,108 @@ +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::server::http::ServerClients; +use axum::extract::{Query, WebSocketUpgrade}; +use axum::handler::Handler; +use axum::http::header::CONTENT_TYPE; +use axum::http::Method; +use axum::routing::{get, patch}; +use axum::Router; +use std::io; +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; + +use crate::infrastructure::server::data::WebSocketRequest; +pub use crate::infrastructure::server::http::handlers::*; +use crate::infrastructure::server::layers::MainThreadLayer; +use crate::infrastructure::server::MetricsReporter; +use base::Global; + +#[allow(clippy::too_many_arguments)] +pub async fn start_http_server( + http_port: u16, + https_port: u16, + clients: ServerClients, + (key, cert): (String, String), + metrics_reporter: MetricsReporter, +) -> Result<(), io::Error> { + // Router + let router = create_router(cert.clone(), clients, metrics_reporter); + // Binding + let http_future = { + let addr = SocketAddr::from(([0, 0, 0, 0], http_port)); + axum_server::bind(addr).serve(router.clone().into_make_service()) + }; + let https_future = { + let addr = SocketAddr::from(([0, 0, 0, 0], https_port)); + let rustls_config = + axum_server::tls_rustls::RustlsConfig::from_pem(cert.into(), key.into()) + .await + .unwrap(); + axum_server::bind_rustls(addr, rustls_config).serve(router.into_make_service()) + }; + // Notify UI + Global::task_support() + .do_later_in_main_thread_asap(|| { + BackboneShell::get().server().borrow_mut().notify_started(); + }) + .unwrap(); + // Actually await the bind futures + let (http_result, https_result) = futures::future::join(http_future, https_future).await; + http_result?; + https_result?; + Ok(()) +} + +fn create_router( + cert: String, + clients: ServerClients, + metrics_reporter: MetricsReporter, +) -> Router { + let router = Router::new() + .route("/", get(welcome_handler)) + .route( + "/realearn.cer", + get(|| async move { create_cert_response(cert.clone(), "realearn.cer") }), + ) + .route( + "/realearn/session/:id", + get(session_handler.layer(MainThreadLayer)), + ) + .route( + "/realearn/session/:id/controller", + get(session_controller_handler.layer(MainThreadLayer)), + ) + .route( + "/realearn/session/:id/controller-routing", + get(controller_routing_handler.layer(MainThreadLayer)), + ) + .route( + "/realearn/controller/:id", + patch(patch_controller_handler.layer(MainThreadLayer)), + ) + .route( + "/realearn/metrics", + get(move || async move { create_metrics_response(metrics_reporter).await }), + ); + router + .layer( + CorsLayer::new() + .allow_origin(Any) + .allow_methods(vec![ + Method::GET, + Method::POST, + Method::PUT, + Method::DELETE, + Method::PATCH, + ]) + .allow_headers(vec![CONTENT_TYPE]), + ) + .route( + "/ws", + get( + |ws: WebSocketUpgrade, Query(req): Query| async move { + let topics = req.parse_topics(); + ws.on_upgrade(|socket| handle_websocket_upgrade(socket, topics, clients)) + }, + ), + ) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/http/welcome_page.html b/plugin-reaper-realearn/main/src/infrastructure/server/http/welcome_page.html new file mode 100644 index 0000000..b6deaa9 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/http/welcome_page.html @@ -0,0 +1,25 @@ + + + + + ReaLearn + + + +
+

Welcome to ReaLearn web server!

+

You have successfully accepted the server certificate. Please navigate back to the ReaLearn companion app.

+
+ + \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/layers/main_thread.rs b/plugin-reaper-realearn/main/src/infrastructure/server/layers/main_thread.rs new file mode 100644 index 0000000..de0d538 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/layers/main_thread.rs @@ -0,0 +1,71 @@ +use axum::http::Response; +use base::Global; +use futures::channel::oneshot; +use futures::future::BoxFuture; +use std::task::{Context, Poll}; +use tower::{Layer, Service}; + +/// A Tower layer that will cause wrapped services to be executed in REAPER's main thread. +#[derive(Clone)] +pub struct MainThreadLayer; + +impl Layer for MainThreadLayer { + type Service = MainThreadService; + + fn layer(&self, inner: S) -> Self::Service { + MainThreadService { inner } + } +} + +// See https://docs.rs/axum/0.3.4/axum/index.html#writing-your-own-middleware. +#[derive(Clone)] +pub struct MainThreadService { + inner: S, +} + +impl Service for MainThreadService +where + // The wrapped service must be a service that creates a HTTP response. + S: Service, Error = E>, + // Both the future generating the response as well as its output must be send over to the main + // thread, so it's mandatory that all of that implements Send. + S::Future: Send + 'static, + ResBody: Default + Send + 'static, + E: Send + 'static, +{ + // The wrapper produces a HTTP response as well. + type Response = Response; + type Error = S::Error; + // The type of the returned future can't be statically expressed, so we need a BoxFuture. + type Future = BoxFuture<'static, Result, S::Error>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, request: Req) -> Self::Future { + // Obtain future from inner service (the future that will generate the response). + // But don't await that future! We must do that in REAPER's main thread, that's the point + // of this middleware. + let inner_response_future = self.inner.call(request); + // Spawn task on REAPER's main thread. + let (tx, rx) = oneshot::channel::, S::Error>>(); + Global::future_support().spawn_in_main_thread(async move { + // Process request and create response in main thread + let response = inner_response_future.await; + // Push result via channel to Tokio runtime thread + let _ = tx.send(response); + Ok(()) + }); + let response_future = async { + rx.await.unwrap_or_else(|_| { + let response = Response::builder() + .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) + .body(ResBody::default()) + .unwrap(); + Ok(response) + }) + }; + Box::pin(response_future) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/layers/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/server/layers/mod.rs new file mode 100644 index 0000000..0aedba9 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/layers/mod.rs @@ -0,0 +1,2 @@ +mod main_thread; +pub use main_thread::*; diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/server/mod.rs new file mode 100644 index 0000000..076568b --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/mod.rs @@ -0,0 +1,442 @@ +//! Contains the ReaLearn server interface and runtime. + +use crate::infrastructure::plugin::BackboneShell; + +use rcgen::{BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, SanType}; +use reaper_high::Reaper; +use rxrust::prelude::*; +use std::cell::RefCell; +use std::fmt::Debug; +use std::fs; + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::path::{Path, PathBuf}; +use std::rc::Rc; + +use url::Url; + +use crate::infrastructure::server::grpc::start_grpc_server; +use crate::infrastructure::server::http::start_http_server; +use crate::infrastructure::server::http::ServerClients; +use crate::infrastructure::server::services::Services; +use derivative::Derivative; +use tokio::runtime::Runtime; + +pub type SharedRealearnServer = Rc>; + +pub mod data; +mod grpc; +pub mod http; +mod layers; +pub mod services; + +#[derive(Debug)] +pub struct RealearnServer { + http_port: u16, + https_port: u16, + grpc_port: u16, + state: ServerState, + certs_dir_path: PathBuf, + changed_subject: LocalSubject<'static, (), ()>, + local_ip: Option, + metrics_reporter: MetricsReporter, +} + +/// Responsible for reporting application metrics. +/// +/// We don't use `PrometheusHandle` directly because `metrics_exporter_prometheus` depends on +/// the `quanta` crate which in turn depends on `mach` crate on macOS. `mach` links to the kernel +/// function [mach_continuous_time](https://developer.apple.com/documentation/kernel/1646199-mach_continuous_time) +/// which is only available for macOS 10.12+. That means the ReaLearn dylib wouldn't load anymore on +/// older macOS systems, e.g. El Capitan (10.11). Some old MacBooks (e.g. 2009) can't go any further +/// than El Capitan. In order to make ReaLearn still work on such old MacBooks, we switch off +/// metrics reporting for the macOS Intel CPU architectures, so metrics reporting will only work on +/// the new aarch64 architectures. This is more restrictive than necessary, but it's easier than +/// using weak linking, which would require changes in the `mach` crate. +#[derive(Clone, Derivative)] +#[derivative(Debug)] +pub struct MetricsReporter { + #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))] + #[derivative(Debug = "ignore")] + prometheus_handle: metrics_exporter_prometheus::PrometheusHandle, +} +impl MetricsReporter { + pub fn new() -> Self { + #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] + { + Self {} + } + #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))] + { + let prometheus_builder = metrics_exporter_prometheus::PrometheusBuilder::new(); + let prometheus_handle = prometheus_builder.install_recorder().unwrap(); + Self { prometheus_handle } + } + } + + pub fn render_as_prometheus(&self) -> Result { + #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] + { + Err("not supported on Intel Macs") + } + #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))] + { + Ok(self.prometheus_handle.render()) + } + } +} + +#[derive(Debug)] +enum ServerState { + Stopped, + Starting(ServerRuntimeData), + Running(ServerRuntimeData), +} + +#[derive(Debug)] +struct ServerRuntimeData { + clients: ServerClients, + server_join_handle: tokio::task::JoinHandle<()>, +} + +impl ServerState { + pub fn is_starting_or_running(&self) -> bool { + use ServerState::*; + match self { + Starting { .. } | Running { .. } => true, + Stopped => false, + } + } +} + +pub const COMPANION_WEB_APP_URL: &str = "https://realearn.helgoboss.org/"; + +impl RealearnServer { + pub fn new( + http_port: u16, + https_port: u16, + grpc_port: u16, + certs_dir_path: PathBuf, + metrics_reporter: MetricsReporter, + ) -> RealearnServer { + RealearnServer { + http_port, + https_port, + grpc_port, + state: ServerState::Stopped, + certs_dir_path, + changed_subject: Default::default(), + local_ip: get_local_ip(), + metrics_reporter, + } + } + + /// Idempotent + pub fn start(&mut self, runtime: &Runtime, services: Services) -> Result<(), String> { + if self.state.is_starting_or_running() { + return Ok(()); + } + check_port(PortType::Http, self.http_port)?; + check_port(PortType::Https, self.https_port)?; + check_port(PortType::Grpc, self.grpc_port)?; + let clients: ServerClients = Default::default(); + let clients_clone = clients.clone(); + let http_port = self.http_port; + let https_port = self.https_port; + let grpc_port = self.grpc_port; + let key_and_cert = self.key_and_cert(); + let metrics_reporter = self.metrics_reporter.clone(); + let server_join_handle = runtime.spawn(start_servers( + http_port, + https_port, + grpc_port, + clients_clone, + key_and_cert, + metrics_reporter, + services, + )); + let runtime_data = ServerRuntimeData { + clients, + server_join_handle, + }; + self.state = ServerState::Starting(runtime_data); + self.notify_changed(); + Ok(()) + } + + fn effective_ip(&self) -> IpAddr { + self.local_ip().unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)) + } + + fn key_and_cert(&self) -> (String, String) { + get_key_and_cert(self.effective_ip(), &self.certs_dir_path) + } + + fn notify_started(&mut self) { + // TODO-low Okay, temporarily replacing with Stopped just to gain ownership feels weird. + if let ServerState::Starting(runtime_data) = + std::mem::replace(&mut self.state, ServerState::Stopped) + { + self.state = ServerState::Running(runtime_data); + } + self.notify_changed(); + } + + /// Idempotent. + pub fn stop(&mut self) { + match std::mem::replace(&mut self.state, ServerState::Stopped) { + ServerState::Running(runtime_data) | ServerState::Starting(runtime_data) => { + runtime_data.server_join_handle.abort(); + } + ServerState::Stopped => {} + } + } + + fn notify_changed(&mut self) { + self.changed_subject.next(()); + } + + pub fn clients(&self) -> Result<&ServerClients, &'static str> { + if let ServerState::Running(runtime_data) = &self.state { + Ok(&runtime_data.clients) + } else { + Err("server not running") + } + } + + pub fn is_running(&self) -> bool { + matches!(&self.state, ServerState::Running { .. }) + } + + pub fn generate_full_companion_app_url(&self, session_id: &str, localhost: bool) -> String { + let host = if localhost { + None + } else { + self.local_ip().map(|ip| ip.to_string()) + }; + Url::parse_with_params( + BackboneShell::get() + .config() + .companion_web_app_url() + .join("controller-routing") + .unwrap() + .as_str(), + &[ + ("host", host.unwrap_or_else(|| "localhost".to_string())), + ("http-port", self.http_port().to_string()), + ("https-port", self.https_port().to_string()), + ("session-id", session_id.to_string()), + // In order to indicate that the URL has not been entered manually and therefore + // typos are out of question (for a proper error message if connection is not + // possible). + ("generated", "true".to_string()), + ], + ) + .expect("invalid URL") + .into() + } + + pub fn local_ip(&self) -> Option { + self.local_ip + } + + pub fn local_hostname(&self) -> Option { + let hn = hostname::get().ok()?; + Some(hn.to_string_lossy().into()) + } + + pub fn local_hostname_dns(&self) -> Option { + let ip = self.local_ip()?; + dns_lookup::lookup_addr(&ip).ok() + } + + pub fn http_port(&self) -> u16 { + self.http_port + } + + pub fn https_port(&self) -> u16 { + self.https_port + } + + pub fn grpc_port(&self) -> u16 { + self.grpc_port + } + + pub fn log_debug_info(&self, session_id: &str) { + let msg = format!( + "\n\ + # Server\n\ + \n\ + - ReaLearn app URL: {}\n\ + - ReaLearn local hostname: {:?}\n\ + - ReaLearn local hostname with DNS lookup: {:?}\n\ + - ReaLearn local IP address: {:?}\n\ + ", + self.generate_full_companion_app_url(session_id, false), + self.local_hostname(), + self.local_hostname_dns(), + self.local_ip() + ); + Reaper::get().show_console_msg(msg); + } + + pub fn changed(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.changed_subject.clone() + } +} + +// TODO-medium-playtime Use https://github.com/faern/triggered instead of channel-based shutdown +#[allow(clippy::too_many_arguments)] +async fn start_servers( + http_port: u16, + https_port: u16, + grpc_port: u16, + clients: ServerClients, + (key, cert): (String, String), + metrics_reporter: MetricsReporter, + services: Services, +) { + let http_server_future = start_http_server( + http_port, + https_port, + clients, + (key, cert), + metrics_reporter, + ); + let grpc_server_future = + start_grpc_server(SocketAddr::from(([0, 0, 0, 0], grpc_port)), services); + let (http_result, grpc_result) = + futures::future::join(http_server_future, grpc_server_future).await; + http_result.expect("HTTP server error"); + grpc_result.expect("gRPC server error"); +} + +fn get_key_and_cert(ip: IpAddr, cert_dir_path: &Path) -> (String, String) { + if let Some(tuple) = find_key_and_cert(ip, cert_dir_path) { + return tuple; + } + // No key/cert yet for that host. Generate self-signed. + let (key, cert) = add_key_and_cert(ip); + fs::create_dir_all(cert_dir_path).expect("couldn't create certificate directory"); + let (key_file_path, cert_file_path) = get_key_and_cert_paths(ip, cert_dir_path); + fs::write(key_file_path, &key).expect("couldn't save key"); + fs::write(cert_file_path, &cert).expect("couldn't save certificate"); + (key, cert) +} + +#[allow(clippy::field_reassign_with_default)] +fn add_key_and_cert(ip: IpAddr) -> (String, String) { + let mut params = CertificateParams::default(); + params.subject_alt_names = vec![SanType::IpAddress(ip)]; + // This needs to be set to qualify as a root certificate, which is in turn important for being + // able to accept it on iOS as described in + // https://apple.stackexchange.com/questions/283348/how-do-i-trust-a-self-signed-certificate-in-ios-10-3 + // and https://medium.com/collaborne-engineering/self-signed-certificates-in-ios-apps-ff489bf8b96e + params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + let mut dn = DistinguishedName::new(); + dn.push(DnType::CommonName, format!("Helgobox on {ip}")); + params.distinguished_name = dn; + let certificate = rcgen::Certificate::from_params(params) + .expect("couldn't create self-signed server certificate"); + ( + certificate.serialize_private_key_pem(), + certificate + .serialize_pem() + .expect("couldn't serialize self-signed server certificate"), + ) +} + +fn find_key_and_cert(ip: IpAddr, cert_dir_path: &Path) -> Option<(String, String)> { + let (key_file_path, cert_file_path) = get_key_and_cert_paths(ip, cert_dir_path); + Some(( + fs::read_to_string(key_file_path).ok()?, + fs::read_to_string(cert_file_path).ok()?, + )) +} + +fn get_key_and_cert_paths(ip: IpAddr, cert_dir_path: &Path) -> (PathBuf, PathBuf) { + let ip_string = ip.to_string(); + let key_file_path = cert_dir_path.join(format!("{ip_string}.key")); + let cert_file_path = cert_dir_path.join(format!("{ip_string}.cer")); + (key_file_path, cert_file_path) +} + +/// Inspired by local_ipaddress crate. +pub fn get_local_ip() -> Option { + let socket = match std::net::UdpSocket::bind("0.0.0.0:0") { + Ok(s) => s, + Err(_) => return None, + }; + match socket.connect("8.8.8.8:80") { + Ok(()) => (), + Err(_) => return None, + }; + match socket.local_addr() { + Ok(addr) => Some(addr.ip()), + Err(_) => None, + } +} + +#[derive(derive_more::Display)] +enum PortType { + #[display(fmt = "HTTP")] + Http, + #[display(fmt = "HTTPS")] + Https, + #[display(fmt = "gRPC")] + Grpc, +} + +impl PortType { + fn config_key(&self) -> &'static str { + match self { + PortType::Http => "http", + PortType::Https => "https", + PortType::Grpc => "grpc", + } + } + + fn alternate_port_example(&self) -> u16 { + match self { + PortType::Http => 40080, + PortType::Https => 40443, + PortType::Grpc => 40051, + } + } +} + +fn check_port(port_type: PortType, port: u16) -> Result<(), String> { + if !local_port_available(port) { + let msg = format!( + r#"{port_type_display_label} port {port} is not available. Possible causes and solutions: + +(1) You are already running another instance of REAPER with ReaLearn. + +If you don't use ReaLearn's projection feature or the Playtime App, switch the ReaLearn server off +by clicking the Projection button and following the instructions. If you use one of those, you +should not start more than one REAPER instance at once. If you really need that, you can +make it work using multiple portable REAPER installations. + +(2) There's some kind of hickup. + +Restart REAPER and ReaLearn. + +(3) Another application grabbed the same port already (unlikely but possible). + +Set another {port_type_display_label} port in "realearn.ini", for example: + + server_{lower_case_port_label}_port = {alternate_port} +"#, + lower_case_port_label = port_type.config_key(), + port_type_display_label = port_type, + port = port, + alternate_port = port_type.alternate_port_example(), + ); + return Err(msg); + } + Ok(()) +} + +fn local_port_available(port: u16) -> bool { + std::net::TcpListener::bind(("0.0.0.0", port)).is_ok() +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/services/helgobox_service.rs b/plugin-reaper-realearn/main/src/infrastructure/server/services/helgobox_service.rs new file mode 100644 index 0000000..2b47cb3 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/services/helgobox_service.rs @@ -0,0 +1,8 @@ +use crate::infrastructure::proto::helgobox_service_server::HelgoboxServiceServer; +use crate::infrastructure::proto::{HelgoboxServiceImpl, ProtoHub}; + +pub type DefaultHelgoboxServiceServer = HelgoboxServiceServer; + +pub fn create_server(hub: &ProtoHub) -> DefaultHelgoboxServiceServer { + hub.create_service() +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/server/services/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/server/services/mod.rs new file mode 100644 index 0000000..098d342 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/server/services/mod.rs @@ -0,0 +1,5 @@ +pub mod helgobox_service; + +pub struct Services { + pub helgobox_service: helgobox_service::DefaultHelgoboxServiceServer, +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/test/mod.rs new file mode 100644 index 0000000..f33558c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/mod.rs @@ -0,0 +1,1603 @@ +use crate::domain::{FinalSourceFeedbackValue, PLUGIN_PARAMETER_COUNT}; +use crate::infrastructure::plugin::{BackboneShell, NewInstanceOutcome, SET_STATE_PARAM_NAME}; +use approx::assert_abs_diff_eq; +use base::future_util::millis; +use base::{Global, SenderToNormalThread}; +use helgoboss_learn::{MidiSourceValue, BASE_EPSILON, FEEDBACK_EPSILON}; +use helgoboss_midi::test_util::*; +use helgoboss_midi::{DataEntryByteOrder, ParameterNumberMessage, RawShortMessage, ShortMessage}; +use reaper_high::{FxParameter, Reaper, Track}; +use reaper_medium::{Db, ReaperPanValue, StuffMidiMessageTarget}; +use std::ffi::CString; +use std::future::Future; +use FinalSourceFeedbackValue::Midi; +use MidiSourceValue::{ParameterNumber, Plain}; + +pub fn run_test() { + Global::future_support().spawn_in_main_thread_from_main_thread(async { + Test::new().test().await; + Ok(()) + }) +} + +#[derive(Default)] +struct Test { + current_step: usize, +} + +impl Test { + pub fn new() -> Test { + Default::default() + } + + pub async fn test(&mut self) { + #[cfg(target_os = "macos")] + { + use crate::base::notification::notify_user_on_anyhow_error; + let screenshot_result = self + .step("Take screenshots", macos_impl::take_screenshots()) + .await; + notify_user_on_anyhow_error(screenshot_result); + } + self.step("Basics", basics()).await; + self.step("(N)RPN", nrpn_test()).await; + self.step( + "Load mapping snapshot - All mappings", + load_mapping_snapshot_all_mappings(), + ) + .await; + self.step( + "Load mapping snapshot - Some mappings", + load_mapping_snapshot_some_mappings(), + ) + .await; + self.step("Toggle mode", toggle_mode()).await; + self.step( + "Send feedback after control - Normal mode - Arm", + send_feedback_after_control_normal_mode_arm(), + ) + .await; + self.step( + "Send feedback after control - Toggle mode - Arm", + send_feedback_after_control_toggle_mode_arm(), + ) + .await; + self.step( + "#396 - Send feedback after control", + issue_396_send_feedback_after_control(), + ) + .await; + self.step( + "Send feedback after control - Normal mode - Volume", + send_feedback_after_control_normal_mode_volume(), + ) + .await; + self.step( + "Basics in controller compartment", + basics_controller_compartment(), + ) + .await; + self.step("Track by ID", track_by_id()).await; + self.step("Track by position", track_by_position()).await; + self.step("Track by name", track_by_name()).await; + self.step("FX by ID", fx_by_id()).await; + self.step("FX by position", fx_by_position()).await; + self.step("FX by name", fx_by_name()).await; + self.step( + "Conditional activation - Modifiers", + conditional_activation_modifiers(), + ) + .await; + self.step( + "Conditional activation - Program", + conditional_activation_program(), + ) + .await; + self.step("Conditional activation - EEL", conditional_activation_eel()) + .await; + self.step("Virtual", virtual_mapping()).await; + log("\nTests executed successfully!") + } + + async fn step(&mut self, label: &str, f: impl Future) -> T { + millis(1).await; + log(format!("{}. {}\n", self.current_step + 1, label)); + self.current_step += 1; + f.await + } +} + +fn log(msg: impl AsRef) { + Reaper::get().show_console_msg(msg.as_ref()); +} + +async fn moment() { + millis(200).await; +} + +async fn setup() -> RealearnTestInstance { + // When + let reaper = Reaper::get(); + let project = reaper.create_empty_project_in_new_tab(); + let track = project.add_track().unwrap(); + let outcome = BackboneShell::create_new_instance_on_track(&track) + .await + .expect("couldn't create test instance"); + // Then + assert!(outcome.fx.parameter_count() >= PLUGIN_PARAMETER_COUNT + 2); + let unit_model = outcome.instance_shell.main_unit_shell().model(); + let (feedback_sender, feedback_receiver) = + SenderToNormalThread::new_unbounded_channel("test feedback"); + unit_model + .borrow() + .use_integration_test_feedback_sender(feedback_sender); + RealearnTestInstance { + outcome, + feedback_receiver, + } +} + +struct RealearnTestInstance { + outcome: NewInstanceOutcome, + feedback_receiver: crossbeam_channel::Receiver, +} + +impl RealearnTestInstance { + /// Returns the containing track. + pub fn track(&self) -> &Track { + self.outcome.fx.track().unwrap() + } + + /// Returns the ReaLearn VST parameter at the given index. + pub fn parameter_by_index(&self, index: u32) -> FxParameter { + self.outcome.fx.parameter_by_index(index) + } + + /// Returns all recorded feedback and removes it from the list. + fn pop_feedback(&self) -> Vec { + self.feedback_receiver.try_iter().collect() + } +} + +async fn basics() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset(&realearn, include_str!("presets/basics.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91)))], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + // Whenever we write Db::MINUS_150_DB here, we expected Db::MINUS_INF before! Turns out that the minimum value + // set by the ReaLearn volume targets was (at least recently) *always* only -150, not -inf! But because of all + // the pointless conversions in reaper_high::Volume (now SliderVolume and fixed in reaper-rs commit 88686932), + // we ended up getting -inf when reading the track volume, which was wrong (if that makes any difference anyway). + // So now it's correct. It's a bit weird that using SetMediaTrackInfo_Value() with a value of 0.0 doesn't give + // -inf (as opposed to SetMediaTrackInfo_Value with D_VOL) but that's another story. + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME, + "NOTE OFF should turn down volume completely" + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::TWELVE_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127)))], + "feedback should be sent on target value change" + ); +} + +async fn nrpn_test() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset(&realearn, include_str!("presets/nrpn.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(ParameterNumber(nrpn(0, 100, 91)))], + "feedback should be sent after loading preset" + ); + // When + send_midi_multi( + nrpn(0, 100, 0).to_short_messages::(DataEntryByteOrder::MsbFirst), + ) + .await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME, + "NOTE OFF should turn down volume completely" + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(ParameterNumber(nrpn(0, 100, 0)))], + "feedback should be sent on target value change" + ); + // When + send_midi_multi( + ParameterNumberMessage::non_registered_increment(channel(0), u14(100), u7(50)) + .to_short_messages::(DataEntryByteOrder::MsbFirst), + ) + .await; + // Then + assert_ne!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME, + "increment should turn volume up a bit" + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(ParameterNumber(nrpn(0, 100, 1)))], + "feedback should be sent on target value change" + ); + // When + send_midi_multi( + ParameterNumberMessage::non_registered_decrement(channel(0), u14(100), u7(50)) + .to_short_messages::(DataEntryByteOrder::MsbFirst), + ) + .await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME, + "decrement should turn volume down a bit again" + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(ParameterNumber(nrpn(0, 100, 0)))], + "feedback should be sent on target value change" + ); +} + +async fn load_mapping_snapshot_all_mappings() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset( + &realearn, + include_str!("presets/load_mapping_snapshot_all_mappings.json"), + ); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![ + // Vol + Midi(Plain(note_on(0, 64, 91))), + // Pan + Midi(Plain(note_on(0, 63, 64))), + // Mute + Midi(Plain(note_on(0, 62, 0))), + // Load snapshot + Midi(Plain(note_on(0, 65, 127))), + ], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 0)).await; + send_midi(note_on(0, 63, 0)).await; + send_midi(note_on(0, 62, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME, + "volume should be MIN because muted" + ); + assert_eq!(realearn.track().pan().reaper_value(), ReaperPanValue::LEFT); + assert!(realearn.track().is_muted()); + assert_eq!( + realearn.pop_feedback(), + vec![ + // Vol + Midi(Plain(note_on(0, 64, 0))), + // Pan + Midi(Plain(note_on(0, 63, 0))), + // Mute + Midi(Plain(note_on(0, 62, 127))), + ], + "feedback should be sent on target value changes" + ); + // When + send_midi(note_on(0, 65, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!( + realearn.track().pan().reaper_value(), + ReaperPanValue::CENTER + ); + assert!(!realearn.track().is_muted()); + assert_eq!( + realearn.pop_feedback(), + vec![ + // Vol + Midi(Plain(note_on(0, 64, 91))), + // Pan + Midi(Plain(note_on(0, 63, 64))), + // Mute + Midi(Plain(note_on(0, 62, 0))), + ], + "feedback should be sent when loading snapshot" + ); +} + +async fn load_mapping_snapshot_some_mappings() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset( + &realearn, + include_str!("presets/load_mapping_snapshot_some_mappings.json"), + ); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![ + // Vol + Midi(Plain(note_on(0, 64, 91))), + // Pan + Midi(Plain(note_on(0, 63, 64))), + // Mute + Midi(Plain(note_on(0, 62, 0))), + // Load snapshot + Midi(Plain(note_on(0, 65, 127))), + ], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 0)).await; + send_midi(note_on(0, 63, 0)).await; + send_midi(note_on(0, 62, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME, + "volume should be MIN because muted" + ); + assert_eq!(realearn.track().pan().reaper_value(), ReaperPanValue::LEFT); + assert!(realearn.track().is_muted()); + assert_eq!( + realearn.pop_feedback(), + vec![ + // Vol + Midi(Plain(note_on(0, 64, 0))), + // Pan + Midi(Plain(note_on(0, 63, 0))), + // Mute + Midi(Plain(note_on(0, 62, 127))), + ], + "feedback should be sent on target value changes" + ); + // When + send_midi(note_on(0, 65, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!(realearn.track().pan().reaper_value(), ReaperPanValue::LEFT); + assert!(!realearn.track().is_muted()); + assert_eq!( + realearn.pop_feedback(), + vec![ + // Vol + Midi(Plain(note_on(0, 64, 91))), + // Mute + Midi(Plain(note_on(0, 62, 0))), + ], + "feedback should be sent when loading snapshot" + ); +} + +async fn toggle_mode() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset(&realearn, include_str!("presets/toggle-mode.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert!(realearn.track().is_armed(false)); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127))),], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(realearn.track().is_armed(false)); + assert_eq!(realearn.pop_feedback(), vec![]); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert!(!realearn.track().is_armed(false)); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0))),], + "feedback should be sent on target value change" + ); +} + +async fn send_feedback_after_control_toggle_mode_arm() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset( + &realearn, + include_str!("presets/send-feedback-after-control-toggle-mode-arm.json"), + ); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert!(realearn.track().is_armed(false)); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127))),], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(realearn.track().is_armed(false)); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127)))], + "should send feedback even if target value not changed" + ); +} + +async fn send_feedback_after_control_normal_mode_arm() { + // Given + let realearn = setup().await; + assert!(!realearn.track().is_armed(false)); + // When + load_realearn_preset( + &realearn, + include_str!("presets/send-feedback-after-control-normal-mode-arm.json"), + ); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert!(realearn.track().is_armed(false)); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127))),], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(!realearn.track().is_armed(false)); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0))),], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(!realearn.track().is_armed(false)); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "should send feedback even if target value not changed" + ); +} + +async fn issue_396_send_feedback_after_control() { + // Given + let realearn = setup().await; + assert_abs_diff_eq!( + realearn + .parameter_by_index(0) + .reaper_normalized_value() + .get(), + 0.0 + ); + // When + load_realearn_preset( + &realearn, + include_str!("presets/issue-396-send-feedback-after-control.json"), + ); + moment().await; + // Then + // Initial parameter value in preset should be respected + assert_abs_diff_eq!( + realearn + .parameter_by_index(0) + .reaper_normalized_value() + .get(), + 0.5 + ); + assert_eq!( + realearn.pop_feedback(), + vec![ + // Even though the initial parameter value is 0.50, the feedback is zero because + // target min/max is set to 0.01 and out-of-range behavior to "Min". + Midi(Plain(note_on(0, 64, 0))), + // More than necessary + Midi(Plain(note_on(0, 64, 0))), + ], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert_abs_diff_eq!( + realearn + .parameter_by_index(0) + .reaper_normalized_value() + .get(), + // Because target min/max is set to 0.01 + 0.01, + epsilon = FEEDBACK_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![ + Midi(Plain(note_on(0, 64, 127))), + ], + "maximum feedback value should be sent because target has changed to exactly target min/max" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert_abs_diff_eq!( + realearn + .parameter_by_index(0) + .reaper_normalized_value() + .get(), + // Because target min/max is (still) set to 0.01 + 0.01, + epsilon = FEEDBACK_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127)))], + // That's the whole point of "Send feedback after control". + "should send feedback even if target value not changed (after another NOTE ON)" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_abs_diff_eq!( + realearn + .parameter_by_index(0) + .reaper_normalized_value() + .get(), + // Because target min/max is (still) set to 0.01 + 0.01, + epsilon = FEEDBACK_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127))),], + // That's the whole point of "Send feedback after control". + "should send feedback even if target value not changed (after NOTE OFF)" + ); +} +async fn send_feedback_after_control_normal_mode_volume() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset( + &realearn, + include_str!("presets/send-feedback-after-control-normal-mode-volume.json"), + ); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91)))], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0))),], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent even if value still same" + ); +} + +async fn basics_controller_compartment() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset( + &realearn, + include_str!("presets/basics-controller-compartment.json"), + ); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91)))], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::TWELVE_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127)))], + "feedback should be sent on target value change" + ); +} + +async fn virtual_mapping() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset(&realearn, include_str!("presets/virtual.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91)))], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent on target value change" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::TWELVE_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127)))], + "feedback should be sent on target value change" + ); +} + +/// Tests that non-existing track ID doesn't cause errors. +async fn track_by_id() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset(&realearn, include_str!("presets/track-by-id.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent after loading preset because target track doesn't exist yet" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!(realearn.pop_feedback(), vec![]); + // When + let track_2 = realearn.track().project().add_track().unwrap(); + moment().await; + // Then + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), Db::ZERO_DB); + assert_eq!(realearn.pop_feedback(), vec![]); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), Db::ZERO_DB); + assert_eq!(realearn.pop_feedback(), vec![]); +} + +async fn track_by_position() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset(&realearn, include_str!("presets/track-by-position.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent after loading preset because target track doesn't exist yet" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + let track_2 = realearn.track().project().add_track().unwrap(); + moment().await; + // Then + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), Db::ZERO_DB); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91)))], + "feedback should be sent because track appears at targeted position" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), MIN_VOLUME); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent on target value change" + ); +} + +async fn fx_by_position() { + // Given + let realearn = setup().await; + let project = Reaper::get().current_project(); + let chain = project.add_track().unwrap().normal_fx_chain(); + let delay = chain.add_fx_by_original_name("ReaDelay (Cockos)").unwrap(); + let eq = chain.add_fx_by_original_name("ReaEQ (Cockos)").unwrap(); + let synth = chain.add_fx_by_original_name("ReaSynth (Cockos)").unwrap(); + fn is_zero(param: FxParameter) -> bool { + param.reaper_normalized_value().get() == 0.0 + } + assert!(!is_zero(eq.parameter_by_index(1))); + assert!(!is_zero(synth.parameter_by_index(1))); + assert!(!is_zero(delay.parameter_by_index(1))); + // When + load_realearn_preset(&realearn, include_str!("presets/fx-by-position.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 64))),], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 10)).await; + // Then + assert_abs_diff_eq!( + eq.parameter_by_index(1).reaper_normalized_value().get(), + 10.0 / 127.0, + epsilon = BASE_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 10))),], + "feedback should be sent on target value change" + ); + // When + chain.remove_fx(&eq).unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + // Zero because ReaSynth Release parameter is roughly at zero by default. + vec![Midi(Plain(note_on(0, 64, 0))),], + "feedback should be sent when ReaSynth FX appears at targeted position because of removal" + ); + // When + send_midi(note_on(0, 64, 10)).await; + // Then + assert_abs_diff_eq!( + synth.parameter_by_index(1).reaper_normalized_value().get(), + 10.0 / 127.0, + epsilon = BASE_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 10))),], + "feedback should be sent on target value change" + ); + // When + chain.move_fx(&delay, 1).unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 64))),], + "feedback should be sent when ReaDelay FX appears at targeted position because of reorder" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(is_zero(delay.parameter_by_index(1))); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0))),], + "feedback should be sent on target value change" + ); +} + +async fn fx_by_name() { + // Given + let realearn = setup().await; + let project = Reaper::get().current_project(); + let chain = project.add_track().unwrap().normal_fx_chain(); + let delay = chain.add_fx_by_original_name("ReaDelay (Cockos)").unwrap(); + let eq = chain.add_fx_by_original_name("ReaEQ (Cockos)").unwrap(); + let synth = chain.add_fx_by_original_name("ReaSynth (Cockos)").unwrap(); + fn is_zero(param: FxParameter) -> bool { + param.reaper_normalized_value().get() == 0.0 + } + assert!(!is_zero(eq.parameter_by_index(1))); + assert!(!is_zero(synth.parameter_by_index(1))); + assert!(!is_zero(delay.parameter_by_index(1))); + // When + load_realearn_preset(&realearn, include_str!("presets/fx-by-name.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 64))),], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 10)).await; + // Then + assert_abs_diff_eq!( + eq.parameter_by_index(1).reaper_normalized_value().get(), + 10.0 / 127.0, + epsilon = FEEDBACK_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 10))),], + "feedback should be sent on target value change" + ); + // When + chain.remove_fx(&eq).unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "gone feedback" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(!is_zero(synth.parameter_by_index(1))); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + chain.move_fx(&delay, 1).unwrap(); + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(!is_zero(delay.parameter_by_index(1))); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); +} + +async fn fx_by_id() { + // Given + let realearn = setup().await; + let project = Reaper::get().current_project(); + let chain = project.add_track().unwrap().normal_fx_chain(); + let delay = chain.add_fx_by_original_name("ReaDelay (Cockos)").unwrap(); + let eq = chain.add_fx_by_original_name("ReaEQ (Cockos)").unwrap(); + let eq_guid_string = eq.guid().unwrap().to_string_without_braces(); + let synth = chain.add_fx_by_original_name("ReaSynth (Cockos)").unwrap(); + fn is_zero(param: FxParameter) -> bool { + param.reaper_normalized_value().get() == 0.0 + } + assert!(!is_zero(eq.parameter_by_index(1))); + assert!(!is_zero(synth.parameter_by_index(1))); + assert!(!is_zero(delay.parameter_by_index(1))); + // When + load_realearn_preset( + &realearn, + &include_str!("presets/fx-by-id.json").replace("$EQ_GUID", &eq_guid_string), + ); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 64))),], + "feedback should be sent after loading preset" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(is_zero(eq.parameter_by_index(1))); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0))),], + "feedback should be sent on target value change" + ); + // When + chain.remove_fx(&eq).unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + // vec![concrete_midi(Plain(note_on(0, 64, 0)))], + vec![], + "gone feedback" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(!is_zero(synth.parameter_by_index(1))); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + chain.move_fx(&delay, 1).unwrap(); + send_midi(note_on(0, 64, 0)).await; + // Then + assert!(!is_zero(delay.parameter_by_index(1))); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); +} + +async fn track_by_name() { + // Given + let realearn = setup().await; + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + // When + load_realearn_preset(&realearn, include_str!("presets/track-by-name.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent after loading preset because target track doesn't exist yet" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + let track_2 = realearn.track().project().add_track().unwrap(); + moment().await; + // Then + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), Db::ZERO_DB); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if track added and target track doesn't exist yet" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), Db::ZERO_DB); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + track_2.set_name("Find me!"); + moment().await; + // Then + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), Db::ZERO_DB); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91)))], + "feedback should be sent if track with targeted name appears" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!(track_2.volume().to_db_ex(Db::MINUS_INF), MIN_VOLUME); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent on target value change" + ); +} + +async fn conditional_activation_modifiers() { + // Given + let realearn = setup().await; + // When + load_realearn_preset(&realearn, include_str!("presets/modifier-condition.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent after loading preset because activation condition not yet met" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + // Switch first modifier on (condition says it must be on) + realearn + .parameter_by_index(82) + .set_reaper_normalized_value(0.5) + .unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91))),], + "feedback should be sent as soon as activation condition is met (met first time)" + ); + // When + send_midi(note_on(0, 64, 10)).await; + // Then + assert_abs_diff_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF).get(), + -60.244583841299885, + epsilon = BASE_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 10)))], + "feedback should be sent on target value change" + ); + // When + // Switch second modifier on (condition says it must be off) + realearn + .parameter_by_index(13) + .set_reaper_normalized_value(1.0) + .unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + // TODO-medium Why no gone feedback!? When I test this in real, it works! + // vec![concrete_midi(Plain(note_on(0, 64, 0)))], + vec![], + "gone feedback finally here, hooray" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + // Value should remain unchanged + assert_abs_diff_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF).get(), + -60.244583841299885, + epsilon = BASE_EPSILON + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + // Switch second modifier off (condition says it must be off) + realearn + .parameter_by_index(13) + .set_reaper_normalized_value(0.0) + .unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + // If the "gone" feedback above would work, this would be sent. Right now, it's omitted + // because of duplicate-feedback prevention measures - which in itself is correct. + // vec![concrete_midi(Plain(note_on(0, 64, 10))),], + vec![], + "feedback should be sent as soon as activation condition is met (met again)" + ); + // When + send_midi(note_on(0, 64, 127)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::TWELVE_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 127)))], + "feedback should be sent on target value change" + ); +} + +async fn conditional_activation_program() { + // Given + let realearn = setup().await; + // When + load_realearn_preset(&realearn, include_str!("presets/program-condition.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent after loading preset because activation condition not yet met" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + realearn + .parameter_by_index(82) + .set_reaper_normalized_value(0.4) + .unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if parameter changed but activation condition not yet met" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + realearn + .parameter_by_index(82) + .set_reaper_normalized_value(0.5) + .unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91)))], + "feedback should be sent as soon as activation condition is met" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent on target value change" + ); +} + +async fn conditional_activation_eel() { + // Given + let realearn = setup().await; + // When + load_realearn_preset(&realearn, include_str!("presets/eel-condition.json")); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent after loading preset because activation condition not yet met" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + realearn + .parameter_by_index(66) + .set_reaper_normalized_value(0.3) + .unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if parameter changed but activation condition not yet met" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + Db::ZERO_DB + ); + assert_eq!( + realearn.pop_feedback(), + vec![], + "no feedback should be sent if target value not changed" + ); + // When + realearn + .parameter_by_index(66) + .set_reaper_normalized_value(0.6) + .unwrap(); + moment().await; + // Then + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 91))),], + "feedback should be sent as soon as activation condition is met" + ); + // When + send_midi(note_on(0, 64, 0)).await; + // Then + assert_eq!( + realearn.track().volume().to_db_ex(Db::MINUS_INF), + MIN_VOLUME + ); + assert_eq!( + realearn.pop_feedback(), + vec![Midi(Plain(note_on(0, 64, 0)))], + "feedback should be sent on target value change" + ); +} + +fn load_realearn_preset(realearn: &RealearnTestInstance, json: &str) { + let preset_c_string = CString::new(json).expect("couldn't convert preset into c string"); + unsafe { + let bytes = preset_c_string.into_bytes_with_nul(); + // TODO-medium Use instance shell directly instead + realearn + .outcome + .fx + .set_named_config_param(SET_STATE_PARAM_NAME, bytes.as_ptr() as _) + .unwrap(); + } +} + +async fn send_midi(message: impl ShortMessage) { + Reaper::get().stuff_midi_message(StuffMidiMessageTarget::VirtualMidiKeyboardQueue, message); + moment().await; +} + +async fn send_midi_multi(messages: [Option; I]) { + for msg in messages.iter().flatten() { + Reaper::get().stuff_midi_message(StuffMidiMessageTarget::VirtualMidiKeyboardQueue, *msg); + } + moment().await; +} + +#[cfg(target_os = "macos")] +mod macos_impl { + use super::*; + use crate::application::UnitModel; + use crate::domain::CompartmentKind; + use crate::infrastructure::plugin::UnitShell; + use crate::infrastructure::ui::copy_text_to_clipboard; + use anyhow::Context; + use std::borrow::Cow; + use std::cell::Ref; + use std::fs; + use std::path::PathBuf; + use swell_ui::View; + use swell_ui::Window; + use xcap::image::{imageops, DynamicImage}; + + pub async fn take_screenshots() -> anyhow::Result<()> { + // Given + let realearn = setup().await; + let project = realearn.track().project(); + let reverb_track = project.add_track().unwrap(); + reverb_track.set_name("Reverb"); + let piano_track = project.add_track().unwrap(); + piano_track.set_name("Piano"); + let synth_track = project.add_track().unwrap(); + synth_track.set_name("Synth"); + synth_track.add_send_to(&reverb_track); + // When + load_realearn_preset(&realearn, include_str!("presets/screenshots.json")); + moment().await; + // Then + realearn.outcome.fx.show_in_floating_window().unwrap(); + let instance_shell = realearn.outcome.instance_shell; + let main_unit_shell = instance_shell.main_unit_shell(); + let shooter = + Screenshooter::new(dirs::download_dir().unwrap().join("realearn-screenshots")); + // Main panel + let main_panel_window = Window::from_hwnd(realearn.outcome.fx.floating_window().unwrap()); + let main_panel_image = shooter.capture(main_panel_window).await; + shooter.save_image(&main_panel_image, "main-panel")?; + let main_panel_parts = [ + ("main-panel-input-output", (14, 110, 714, 124)), + ("main-panel-menu-bar", (720, 112, 774, 66)), + ("main-panel-let-through-checkboxes", (820, 168, 660, 66)), + ("main-panel-show-buttons", (2, 236, 1490, 62)), + ("main-panel-preset", (2, 302, 1490, 62)), + ("main-panel-group", (6, 362, 994, 62)), + ("main-panel-notes-button", (1350, 362, 136, 62)), + ("main-panel-mapping-toolbar", (6, 423, 1492, 62)), + ("main-panel-mapping-row", (4, 477, 1450, 148)), + ("main-panel-bottom", (10, 1224, 1482, 120)), + ]; + for (name, crop) in main_panel_parts { + shooter.save_image_part(&main_panel_image, name, crop)?; + } + // Mapping panel + let mapping = main_unit_model(main_unit_shell) + .mappings(CompartmentKind::Main) + .next() + .cloned() + .unwrap(); + let mapping_panel = main_unit_shell + .panel() + .panel_manager() + .borrow_mut() + .edit_mapping(&mapping); + let mapping_window = mapping_panel.view_context().require_window(); + let mapping_image = shooter.capture(mapping_window).await; + shooter.save_image(&mapping_image, "mapping-panel")?; + let mapping_panel_parts = [ + ("mapping-panel-general", (4, 52, 1434, 190)), + ("mapping-panel-source", (0, 240, 558, 462)), + ("mapping-panel-target", (564, 240, 878, 462)), + ("mapping-panel-glue", (0, 704, 1438, 662)), + ("mapping-panel-bottom", (2, 1370, 1438, 172)), + ]; + for (name, crop) in mapping_panel_parts { + shooter.save_image_part(&mapping_image, name, crop)?; + } + // Group panel + let group_panel = main_unit_shell + .panel() + .header_panel() + .edit_group() + .context("edit group")?; + let group_window = group_panel.view_context().require_window(); + shooter.save(group_window, "group-panel").await?; + group_panel.close(); + // Log and copy screenshot directory + log(format!("Screenshot directory: {:?}\n", &shooter.dir)); + copy_text_to_clipboard(shooter.dir.to_string_lossy().to_string())?; + Ok(()) + } + + fn main_unit_model(main_unit_shell: &UnitShell) -> Ref { + main_unit_shell.model().borrow() + } + + struct Screenshooter { + dir: PathBuf, + } + + impl Screenshooter { + pub fn new(dir: PathBuf) -> Self { + fs::create_dir_all(&dir).unwrap(); + Self { dir } + } + + pub async fn save(&self, window: Window, name: &str) -> anyhow::Result<()> { + let img = self.capture(window).await; + self.save_image(&img, name)?; + Ok(()) + } + + pub async fn capture(&self, window: Window) -> DynamicImage { + millis(100).await; + xcap::Window::all() + .unwrap() + .iter() + .find(|w| w.app_name() == "REAPER" && w.title() == window.text().unwrap()) + .expect("couldn't find window to take screenshot from") + .capture_image() + .unwrap() + .into() + } + + pub fn save_image_part( + &self, + img: &DynamicImage, + name: &str, + (x, y, width, height): (u32, u32, u32, u32), + ) -> anyhow::Result<()> { + let cropped_img = img.crop_imm(x, y, width, height); + self.save_image(&cropped_img, name) + .context("save image part")?; + Ok(()) + } + + pub fn save_image(&self, img: &DynamicImage, name: &str) -> anyhow::Result<()> { + const MAX_DIM: u32 = 900; + let img = if img.height() > MAX_DIM || img.width() > MAX_DIM { + Cow::Owned(img.resize(MAX_DIM, MAX_DIM, imageops::FilterType::Lanczos3)) + } else { + Cow::Borrowed(img) + }; + img.save(self.dir.join(format!("{name}.png"))) + .context("save image")?; + Ok(()) + } + } +} + +const MIN_VOLUME: Db = Db::MINUS_INF; diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/basics-controller-compartment.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/basics-controller-compartment.json new file mode 100644 index 0000000..ef806e0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/basics-controller-compartment.json @@ -0,0 +1,20 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "controllerMappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/basics.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/basics.json new file mode 100644 index 0000000..0ed15e5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/basics.json @@ -0,0 +1,20 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/eel-condition.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/eel-condition.json new file mode 100644 index 0000000..41e039c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/eel-condition.json @@ -0,0 +1,22 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2 + }, + "activationType": "eel", + "eelCondition": "y = p67 > 0.4" + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-id.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-id.json new file mode 100644 index 0000000..e8879bd --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-id.json @@ -0,0 +1,23 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "trackIndex": 1, + "fxAnchor": "id", + "fxGUID": "$EQ_GUID", + "paramIndex": 1 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-name.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-name.json new file mode 100644 index 0000000..6d69ba1 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-name.json @@ -0,0 +1,23 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "trackIndex": 1, + "fxAnchor": "name", + "fxName": "VST: ReaEQ (Cockos)", + "paramIndex": 1 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-position.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-position.json new file mode 100644 index 0000000..bff7df9 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/fx-by-position.json @@ -0,0 +1,23 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "trackIndex": 1, + "fxAnchor": "index", + "fxIndex": 1, + "paramIndex": 1 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/issue-396-send-feedback-after-control.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/issue-396-send-feedback-after-control.json new file mode 100644 index 0000000..7910c0a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/issue-396-send-feedback-after-control.json @@ -0,0 +1,31 @@ +{ + "version": "2.9.1", + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "controllerMappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": { + "minTargetValue": 0.01, + "maxTargetValue": 0.01, + "outOfRangeBehavior": "min" + }, + "target": { + "fxAnchor": "this" + }, + "sendFeedbackAfterControl": true + } + ], + "parameters": { + "0": { + "value": 0.5 + } + } +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/load_mapping_snapshot_all_mappings.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/load_mapping_snapshot_all_mappings.json new file mode 100644 index 0000000..3f577c7 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/load_mapping_snapshot_all_mappings.json @@ -0,0 +1,88 @@ +{ + "version": "2.11.0-pre.1", + "id": "I3iqzlfV", + "letMatchedEventsThrough": true, + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "defaultGroup": {}, + "groups": [ + { + "id": "c81ea0ea-c0fd-49ca-a6c2-abf4f3179da9", + "name": "Snapshotting" + }, + { + "id": "c9b3e9f8-a794-4ccf-9b56-2a0ee88201b4", + "name": "Other" + } + ], + "defaultControllerGroup": {}, + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "Vol", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2, + "fxAnchor": "id", + "soloBehavior": 1 + } + }, + { + "id": "6c5dbdc3-afdf-44b1-88ae-8155080c619d", + "name": "Pan", + "source": { + "type": 1, + "channel": 0, + "number": 63 + }, + "mode": {}, + "target": { + "type": 4, + "fxAnchor": "id", + "soloBehavior": 1 + } + }, + { + "id": "377ae3e2-a738-422a-b279-92269b1960e9", + "name": "Mute", + "groupId": "c9b3e9f8-a794-4ccf-9b56-2a0ee88201b4", + "source": { + "type": 1, + "channel": 0, + "number": 62 + }, + "mode": {}, + "target": { + "type": 7, + "fxAnchor": "id", + "soloBehavior": 1 + } + }, + { + "id": "e7ac80b3-845e-49be-8998-b9a1d91ddd27", + "name": "Load snapshot", + "groupId": "c81ea0ea-c0fd-49ca-a6c2-abf4f3179da9", + "source": { + "type": 1, + "channel": 0, + "number": 65 + }, + "mode": { + "maxStepSize": 0.05 + }, + "target": { + "type": 35, + "fxAnchor": "id", + "useProject": true, + "moveView": true, + "seekPlay": true, + "oscArgIndex": 0 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/load_mapping_snapshot_some_mappings.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/load_mapping_snapshot_some_mappings.json new file mode 100644 index 0000000..61214c0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/load_mapping_snapshot_some_mappings.json @@ -0,0 +1,99 @@ +{ + "version": "2.11.0-pre.1", + "id": "I3iqzlfV", + "letMatchedEventsThrough": true, + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "defaultGroup": {}, + "groups": [ + { + "id": "c81ea0ea-c0fd-49ca-a6c2-abf4f3179da9", + "name": "Snapshotting" + }, + { + "id": "c9b3e9f8-a794-4ccf-9b56-2a0ee88201b4", + "name": "Other" + } + ], + "defaultControllerGroup": {}, + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "Vol", + "tags": [ + "load" + ], + "groupId": "c81ea0ea-c0fd-49ca-a6c2-abf4f3179da9", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2, + "fxAnchor": "id", + "soloBehavior": 1 + } + }, + { + "id": "6c5dbdc3-afdf-44b1-88ae-8155080c619d", + "name": "Pan", + "groupId": "c81ea0ea-c0fd-49ca-a6c2-abf4f3179da9", + "source": { + "type": 1, + "channel": 0, + "number": 63 + }, + "mode": {}, + "target": { + "type": 4, + "fxAnchor": "id", + "soloBehavior": 1 + } + }, + { + "id": "377ae3e2-a738-422a-b279-92269b1960e9", + "name": "Mute", + "tags": [ + "load" + ], + "groupId": "c9b3e9f8-a794-4ccf-9b56-2a0ee88201b4", + "source": { + "type": 1, + "channel": 0, + "number": 62 + }, + "mode": {}, + "target": { + "type": 7, + "fxAnchor": "id", + "soloBehavior": 1 + } + }, + { + "id": "e7ac80b3-845e-49be-8998-b9a1d91ddd27", + "name": "Load snapshot", + "groupId": "c81ea0ea-c0fd-49ca-a6c2-abf4f3179da9", + "source": { + "type": 1, + "channel": 0, + "number": 65 + }, + "mode": { + "maxStepSize": 0.05 + }, + "target": { + "type": 35, + "fxAnchor": "id", + "useProject": true, + "moveView": true, + "seekPlay": true, + "oscArgIndex": 0, + "tags": [ + "load" + ] + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/modifier-condition.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/modifier-condition.json new file mode 100644 index 0000000..a8ff765 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/modifier-condition.json @@ -0,0 +1,29 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2 + }, + "activationType": "modifiers", + "modifierCondition1": { + "paramIndex": 82, + "isOn": true + }, + "modifierCondition2": { + "paramIndex": 13, + "isOn": false + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/nrpn.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/nrpn.json new file mode 100644 index 0000000..d9f5e68 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/nrpn.json @@ -0,0 +1,28 @@ +{ + "version": "2.11.0-pre.2", + "id": "I3iqzlfV", + "letMatchedEventsThrough": true, + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "defaultGroup": {}, + "defaultControllerGroup": {}, + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 6, + "channel": 0, + "number": 100, + "isRegistered": false, + "is14Bit": false + }, + "mode": {}, + "target": { + "type": 2, + "fxAnchor": "id", + "soloBehavior": 1 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/program-condition.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/program-condition.json new file mode 100644 index 0000000..d3bdf58 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/program-condition.json @@ -0,0 +1,25 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2 + }, + "activationType": "program", + "programCondition": { + "paramIndex": 82, + "programIndex": 50 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/screenshots.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/screenshots.json new file mode 100644 index 0000000..8b6212d --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/screenshots.json @@ -0,0 +1,176 @@ +{ + "mainUnit": { + "version": "2.16.10", + "id": "I3iqzlfV", + "stayActiveWhenProjectInBackground": "Always", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "defaultGroup": {}, + "defaultControllerGroup": {}, + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "Track volume", + "source": { + "category": "virtual", + "type": 1, + "channel": 0, + "number": 64, + "controlElementIndex": "ch1/fader" + }, + "mode": { + "minStepFactor": 1, + "maxStepFactor": 5 + }, + "target": { + "type": 2, + "trackIndex": 2, + "fxAnchor": "id", + "useSelectionGanging": false, + "useTrackGrouping": false, + "soloBehavior": 1, + "seekBehavior": "Immediate", + "mouseAction": { + "kind": "MoveTo", + "axis": "X" + }, + "takeMappingSnapshot": { + "kind": "ById", + "id": "" + } + } + }, + { + "id": "HYAJ4Toj7uJ1-c7rgYdiV", + "name": "Send volume", + "source": { + "category": "virtual", + "type": 1, + "channel": 0, + "number": 64, + "controlElementIndex": "ch1/v-pot" + }, + "mode": { + "minStepFactor": 1, + "maxStepFactor": 5 + }, + "target": { + "type": 3, + "trackName": "Synth", + "fxAnchor": "id", + "useSelectionGanging": false, + "useTrackGrouping": false, + "soloBehavior": 1, + "seekBehavior": "Immediate", + "mouseAction": { + "kind": "MoveTo", + "axis": "X" + }, + "takeMappingSnapshot": { + "kind": "ById", + "id": "" + } + } + }, + { + "id": "9Ag8hTcHPi6bm8WtAh7hH", + "name": "Track pan", + "source": { + "type": 7, + "channel": 0, + "number": 64, + "controlElementIndex": "ch1/v-pot" + }, + "mode": { + "minStepFactor": 1, + "maxStepFactor": 5 + }, + "target": { + "type": 4, + "trackName": "Piano", + "fxAnchor": "id", + "useSelectionGanging": false, + "useTrackGrouping": false, + "soloBehavior": 1, + "seekBehavior": "Immediate", + "mouseAction": { + "kind": "MoveTo", + "axis": "X" + }, + "takeMappingSnapshot": { + "kind": "ById", + "id": "" + } + } + }, + { + "id": "DKpyUNeMHTkNF8ARLrVJK", + "name": "Selected track volume", + "source": { + "type": 3, + "channel": 0, + "number": 64, + "controlElementIndex": "ch1/v-pot" + }, + "mode": { + "minStepFactor": 1, + "maxStepFactor": 5 + }, + "target": { + "type": 2, + "trackGUID": "selected", + "fxAnchor": "id", + "useSelectionGanging": false, + "useTrackGrouping": false, + "soloBehavior": 1, + "seekBehavior": "Immediate", + "mouseAction": { + "kind": "MoveTo", + "axis": "X" + }, + "takeMappingSnapshot": { + "kind": "ById", + "id": "" + } + } + }, + { + "id": "0QrJZ0hi6dyw6aKlLvgqT", + "name": "Arm selected", + "source": { + "category": "virtual", + "type": 3, + "channel": 0, + "number": 64, + "controlElementType": "Button", + "controlElementIndex": "ch1/record-ready" + }, + "mode": { + "minStepFactor": 1, + "maxStepFactor": 5 + }, + "target": { + "type": 5, + "trackGUID": "selected", + "fxAnchor": "id", + "useSelectionGanging": false, + "useTrackGrouping": false, + "soloBehavior": 1, + "seekBehavior": "Immediate", + "mouseAction": { + "kind": "MoveTo", + "axis": "X" + }, + "takeMappingSnapshot": { + "kind": "ById", + "id": "" + } + } + } + ], + "instanceFx": { + "address": "Focused" + } + }, + "additionalUnits": [] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-normal-mode-arm.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-normal-mode-arm.json new file mode 100644 index 0000000..ed15fdb --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-normal-mode-arm.json @@ -0,0 +1,21 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 5 + }, + "sendFeedbackAfterControl": true + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-normal-mode-volume.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-normal-mode-volume.json new file mode 100644 index 0000000..4c1058b --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-normal-mode-volume.json @@ -0,0 +1,21 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2 + }, + "sendFeedbackAfterControl": true + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-toggle-mode-arm.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-toggle-mode-arm.json new file mode 100644 index 0000000..770d433 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/send-feedback-after-control-toggle-mode-arm.json @@ -0,0 +1,23 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": { + "type": 2 + }, + "target": { + "type": 5 + }, + "sendFeedbackAfterControl": true + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/toggle-mode.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/toggle-mode.json new file mode 100644 index 0000000..c9dd0dc --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/toggle-mode.json @@ -0,0 +1,22 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": { + "type": 2 + }, + "target": { + "type": 5 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-id.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-id.json new file mode 100644 index 0000000..0a3f3fe --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-id.json @@ -0,0 +1,21 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2, + "trackGUID": "21259751-9E5C-479B-9DE9-28D72DEA49C6" + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-name.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-name.json new file mode 100644 index 0000000..99ea00f --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-name.json @@ -0,0 +1,21 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2, + "trackName": "Find me!" + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-position.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-position.json new file mode 100644 index 0000000..b70e13e --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/track-by-position.json @@ -0,0 +1,21 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "type": 2, + "trackIndex": 1 + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/test/presets/virtual.json b/plugin-reaper-realearn/main/src/infrastructure/test/presets/virtual.json new file mode 100644 index 0000000..f086883 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/test/presets/virtual.json @@ -0,0 +1,34 @@ +{ + "id": "I3iqzlfV", + "controlDeviceId": "62", + "feedbackDeviceId": "fx-output", + "mappings": [ + { + "id": "134104a5-8eb1-4a07-b8a9-b65d0a8f74d3", + "name": "1", + "source": { + "category": "virtual", + "type": 2 + }, + "mode": {}, + "target": { + "type": 2 + } + } + ], + "controllerMappings": [ + { + "id": "e586e1e3-53f5-48c8-b594-4635668722eb", + "name": "1", + "source": { + "type": 1, + "channel": 0, + "number": 64 + }, + "mode": {}, + "target": { + "category": "virtual" + } + } + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/advanced_script_editor_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/advanced_script_editor_panel.rs new file mode 100644 index 0000000..fa5fde5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/advanced_script_editor_panel.rs @@ -0,0 +1,130 @@ +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::egui_views::advanced_script_editor; +use crate::infrastructure::ui::egui_views::advanced_script_editor::{ + SharedValue, State, Toolbox, Value, +}; +use crate::infrastructure::ui::{egui_views, ScriptEditorInput}; +use base::{blocking_lock, SenderToNormalThread}; +use crossbeam_channel::Receiver; +use derivative::Derivative; +use reaper_low::raw; +use semver::Version; +use std::cell::RefCell; +use std::time::Duration; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Debug)] +pub struct ScriptTemplateGroup { + pub name: &'static str, + pub templates: &'static [ScriptTemplate], +} + +#[derive(Debug)] +pub struct ScriptTemplate { + #[allow(dead_code)] + pub min_realearn_version: Option, + pub name: &'static str, + pub description: &'static str, + pub control_styles: &'static [ControlStyle], + pub content: &'static str, +} + +#[derive(Copy, Clone, Debug, derive_more::Display)] +pub enum ControlStyle { + #[display(fmt = "range elements")] + RangeElement, + #[display(fmt = "buttons")] + Button, +} + +impl ControlStyle { + pub fn examples(&self) -> &'static str { + match self { + ControlStyle::RangeElement => { + "knobs, faders, touch strips, wheels, aftertouch, velocity" + } + ControlStyle::Button => "buttons, pads, keys", + } + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct AdvancedScriptEditorPanel { + view: ViewContext, + state: RefCell>, + value_receiver: Receiver, + #[derivative(Debug = "ignore")] + set_value: Box, +} +const TIMER_ID: usize = 321; + +impl AdvancedScriptEditorPanel { + pub fn new( + input: ScriptEditorInput, + script_template_groups: &'static [ScriptTemplateGroup], + ) -> Self { + let (value_sender, value_receiver) = + SenderToNormalThread::new_unbounded_channel("advanced script editor apply"); + Self { + view: Default::default(), + state: { + let toolbox = Toolbox { + engine: input.engine, + help_url: input.help_url, + script_template_groups, + value_sender, + }; + RefCell::new(Some(State::new(input.initial_value, toolbox))) + }, + value_receiver, + set_value: Box::new(input.set_value), + } + } +} + +impl View for AdvancedScriptEditorPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_EMPTY_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + window.set_timer(TIMER_ID, Duration::from_millis(30)); + let state = self.state.take().expect("state already in use"); + egui_views::open( + window, + "Script editor", + state, + advanced_script_editor::run_ui, + ); + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Escape key + raw::IDCANCEL => self.close(), + _ => {} + } + } + + fn timer(&self, _: usize) -> bool { + if let Some(v) = self.value_receiver.try_iter().last() { + let v = blocking_lock(&v, "AdvancedScriptEditor value receiver (timer)"); + (self.set_value)(v.clone()); + } + true + } + + fn resized(self: SharedView) -> bool { + egui_views::on_parent_window_resize(self.view.require_window()) + } + + fn wants_raw_keyboard_input(&self) -> bool { + true + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/app/app_instance.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/app/app_instance.rs new file mode 100644 index 0000000..795b8bf --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/app/app_instance.rs @@ -0,0 +1,462 @@ +use crate::domain::{InstanceId, UnitId}; +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::proto::{ + event_reply, occasional_global_update, reply, EventReply, GetOccasionalGlobalUpdatesReply, + OccasionalGlobalUpdate, ProtoReceivers, Reply, +}; +use crate::infrastructure::ui::util::open_in_browser; +use crate::infrastructure::ui::{called_from_dart, AppHandle}; +use anyhow::{anyhow, bail, Context, Result}; +use base::hash_util::NonCryptoHashMap; +use fragile::Fragile; +use once_cell::sync::Lazy; +use prost::Message; +use reaper_high::Reaper; +use reaper_medium::{Hwnd, MessageBoxResult, MessageBoxType}; +use std::cell::RefCell; +use std::fmt::Debug; +use std::rc::Rc; +use swell_ui::Window; +use tokio::task::JoinHandle; + +pub type SharedAppInstance = Rc>; + +pub trait AppInstance: Debug { + fn is_running(&self) -> bool; + + fn has_focus(&self) -> bool; + + fn is_visible(&self) -> bool; + + fn start_or_show(&mut self, owning_window: Window, location: Option) -> Result<()>; + + fn hide(&mut self) -> Result<()>; + + fn stop(&mut self) -> Result<()>; + + fn send(&self, reply: &Reply) -> Result<()>; + + fn notify_app_is_ready(&mut self, callback: AppCallback); + + /// Returns the app window. + /// + /// On Windows, that is the app handle (HWND), which is the **parent** window of whatever REAPER passes into + /// the `HwndInfo` hook. + /// + /// On macOS, this is the content view (NSView) of the app handle (NSWindow), which is exactly what + /// REAPER passes into the `HwndInfo` hook. + fn window(&self) -> Option; + + fn notify_app_is_in_text_entry_mode(&mut self, is_in_text_entry_mode: bool); +} + +#[allow(clippy::if_same_then_else)] +pub fn create_shared_app_instance(instance_id: InstanceId) -> SharedAppInstance { + fn share(value: impl AppInstance + 'static) -> SharedAppInstance { + Rc::new(RefCell::new(value)) + } + // I was experimenting with 2 different ways of embedding the app GUI into REAPER: + // + // - Parented mode: We create a new SWELL window on ReaLearn side (HWND on Windows, NSView on + // macOS) and the app renders its GUI *within* it. + // - Standalone mode: The app fires up its own window. + // + // Embedding the app in "parented" mode is in theory preferable because: + // + // 1. Only "parented" mode makes it possible to dock the app GUI (in case we want to do that + // one day). + // 2. ReaLearn has full control over the window and can listen to its events. + // 3. We stop sending events when the app window is hidden, not wasting resources when the + // app is not shown anyway. (However, with just a bit more effort, we could implement this + // for standalone mode as well.) + if cfg!(target_os = "windows") { + // On Windows, parented mode works in general. With a few tricks (see AppPanel View + // implementation). However, I had issues completely removing the window title bar, maybe + // because SWELL windows are dialog windows and they work differently? Anyway, this was + // the reason that I switched to a standalone window. + // let app_panel = AppPanel::new(session); + // let instance = ParentedAppInstance { + // panel: SharedView::new(app_panel), + // }; + // share(instance) + let instance = StandaloneAppInstance { + instance_id, + running_state: None, + }; + share(instance) + } else if cfg!(target_os = "macos") { + // On macOS, parented mode is possible only by using Cocoa child windows (see app side + // embedding docs). This means that the app doesn't really render itself in the NSView + // provided by ReaLearn but places an NSWindow on top of the NSWindow provided by ReaLearn. + // It works but it needs a few keyboard tricks and - most importantly - it doesn't work + // well if the app itself wants to control its window (e.g. going full screen or changing + // the window opacity). It will try to control the child window, not the outer window. + // This could be solved on app side by navigating up the child/parent window chain, but + // it's not something I want to do now as long as we don't support docking anyway. + // Therefore: Standalone mode on macOS! + let instance = StandaloneAppInstance { + instance_id, + running_state: None, + }; + share(instance) + } else { + share(DummyAppInstance) + } +} + +#[derive(Debug)] +struct DummyAppInstance; + +impl AppInstance for DummyAppInstance { + fn is_running(&self) -> bool { + false + } + + fn has_focus(&self) -> bool { + false + } + + fn is_visible(&self) -> bool { + false + } + + fn start_or_show(&mut self, _owning_window: Window, _page: Option) -> Result<()> { + let msg = + "Linux support for the Helgobox App (including the Playtime user interface) is currently at stage 1!\n\ + That means it can't yet run embedded within REAPER, but it's possible to run it as a separate program that connects to REAPER (\"remote mode\").\n\ + \n\ + Do you want to open the instructions?" + ; + let result = + Reaper::get() + .medium_reaper() + .show_message_box(msg, "Helgobox", MessageBoxType::YesNo); + if result == MessageBoxResult::Yes { + open_in_browser("https://docs.helgoboss.org/helgobox/goto#app-remote-mode"); + }; + Ok(()) + } + + fn hide(&mut self) -> Result<()> { + bail!("not implemented for Linux") + } + + fn stop(&mut self) -> Result<()> { + bail!("not implemented for Linux") + } + + fn send(&self, _reply: &Reply) -> Result<()> { + bail!("not implemented for Linux") + } + + fn notify_app_is_ready(&mut self, _callback: AppCallback) {} + + fn window(&self) -> Option { + None + } + + fn notify_app_is_in_text_entry_mode(&mut self, _is_in_text_entry_mode: bool) {} +} + +pub enum AppPage { + Projection(UnitId), + Playtime, +} + +impl AppPage { + pub fn location(&self) -> String { + match self { + AppPage::Playtime => "/instance/.host/playtime".to_string(), + AppPage::Projection(unit_id) => { + format!("/instance/.host/unit/{unit_id}/projection") + } + } + } +} + +/// App will run in its own window. +/// +/// This is possible on all OS. +#[derive(Debug)] +struct StandaloneAppInstance { + instance_id: InstanceId, + running_state: Option, +} + +impl StandaloneAppInstance { + fn register_app_window(&mut self, is_in_text_entry_mode: bool) { + let mut map = REGISTERED_APP_WINDOWS.get().borrow_mut(); + let Some(hwnd) = self.window() else { + return; + }; + let window_state = AppWindowState { + is_in_text_entry_mode, + }; + map.insert(hwnd, window_state); + } +} + +#[derive(Debug)] +struct StandaloneAppRunningState { + common_state: CommonAppRunningState, + event_subscription_join_handle: Option>, +} + +impl Drop for StandaloneAppRunningState { + fn drop(&mut self) { + if let Some(join_handle) = self.event_subscription_join_handle.take() { + join_handle.abort(); + } + } +} + +impl AppInstance for StandaloneAppInstance { + fn is_running(&self) -> bool { + self.running_state.is_some() + } + + fn has_focus(&self) -> bool { + match &self.running_state { + None => false, + Some(state) => state.common_state.has_focus(), + } + } + + fn is_visible(&self) -> bool { + match &self.running_state { + None => false, + Some(state) => state.common_state.is_visible(), + } + } + + fn start_or_show(&mut self, _owning_window: Window, page: Option) -> Result<()> { + let app_library = BackboneShell::get_app_library()?; + if let Some(running_state) = &self.running_state { + app_library.show_app_instance(None, running_state.common_state.app_handle)?; + if let Some(page) = page { + // Hmmm, yeah ... + let _ = self.send(&Reply { + value: Some(reply::Value::EventReply(EventReply { + value: Some(event_reply::Value::OccasionalGlobalUpdatesReply( + GetOccasionalGlobalUpdatesReply { + global_updates: vec![OccasionalGlobalUpdate { + update: Some(occasional_global_update::Update::GoToLocation( + page.location(), + )), + }], + }, + )), + })), + }); + } + return Ok(()); + } + let start_location = page + .map(|p| p.location()) + .unwrap_or_else(|| "/".to_string()); + let app_handle = app_library.start_app_instance(None, self.instance_id, start_location)?; + let running_state = StandaloneAppRunningState { + common_state: CommonAppRunningState { + app_handle, + app_callback: None, + }, + event_subscription_join_handle: None, + }; + self.running_state = Some(running_state); + Ok(()) + } + + fn hide(&mut self) -> Result<()> { + self.running_state + .as_ref() + .ok_or(anyhow!("app was already stopped"))? + .common_state + .hide() + } + + fn stop(&mut self) -> Result<()> { + // TODO-high CONTINUE With the new Flutter version containing the "Great thread merge", stopping + // the application can easily lead to a crash. That's why we just hide it, temporarily. This works but it prevents + // the user from truly shutting down the user interface, freeing resources that might be valuable. + // More importantly, even after closing a project, resources might not be freed up (to be verified), causing + // multiple engine instances to fill up memory over time. + // On macOS, I tried to find out why this happens. + // It looks like the (new) FlutterRunLoop continues to run even after the view and engine has stopped. + // It asynchronously performs a task (ResizeSynchronizer task) that has been scheduled to be executed async, and it performs this + // task when the engine is already gone. This task probably tries to access the engine and things crash. + // Relevant log messages: + // Deiniting StandaloneFlutterWindow + // Deiniting StandaloneFlutterViewController + // Communicating on a dead channel. + // embedder.cc (3235): 'FlutterEngineOnVsync' returned 'kInvalidArguments'. Invalid engine handle. + // See https://github.com/flutter/flutter/blob/da72d5936d697169c8ee2535ad6f615b0352dabd/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterRunLoop.swift#L70 + // The FlutterRunLoop is a static singleton. So disposing it by destroying it while REAPER is still running + // looks impossible. I can think of a few approaches to fix this: + // - Make the FlutterRunLoop public and give it some kind of "disable" method + // - Prevent the ResizeSynchronizer from scheduling the culprit task in the first place, maybe by looking + // at the isShuttingDown flag before scheduling: https://github.com/flutter/flutter/blob/da72d5936d697169c8ee2535ad6f615b0352dabd/engine/src/flutter/shell/platform/darwin/macos/framework/Source/ResizeSynchronizer.swift#L124 + // All this needs some in-depth native debugging in XCode with the correct Flutter engine source files + // available. + self.hide() + // self.running_state + // .take() + // .ok_or(anyhow!("app was already stopped"))? + // .common_state + // .stop(None) + } + + fn send(&self, reply: &Reply) -> Result<()> { + self.running_state + .as_ref() + .context("app not open")? + .common_state + .send(reply) + } + + fn notify_app_is_ready(&mut self, callback: AppCallback) { + let Some(running_state) = &mut self.running_state else { + return; + }; + let instance_id = self.instance_id; + // Handshake finished! The app has the host callback and we have the app callback. + running_state.common_state.app_callback = Some(callback); + // Now we can start passing events to the app callback + let mut receivers = subscribe_to_events(); + let join_handle = BackboneShell::get().spawn_in_async_runtime(async move { + receivers + .keep_processing_updates(instance_id, &|event_reply| { + let reply = Reply { + value: Some(reply::Value::EventReply(event_reply)), + }; + send_to_app(callback, &reply); + }) + .await; + }); + running_state.event_subscription_join_handle = Some(join_handle); + // Register app window + self.register_app_window(false); + } + + fn window(&self) -> Option { + let running_state = self.running_state.as_ref()?; + running_state.common_state.window() + } + + fn notify_app_is_in_text_entry_mode(&mut self, is_in_text_entry_mode: bool) { + self.register_app_window(is_in_text_entry_mode); + } +} + +static REGISTERED_APP_WINDOWS: Lazy>>> = + Lazy::new(Default::default); + +#[derive(Default)] +struct AppWindowState { + is_in_text_entry_mode: bool, +} + +/// Relevant for all OS. +pub fn is_app_window(hwnd: Hwnd) -> bool { + let yes = REGISTERED_APP_WINDOWS.get().borrow().contains_key(&hwnd); + if yes { + return true; + } + if let Some(parent) = Window::from_hwnd(hwnd).parent() { + is_app_window(parent.raw_hwnd()) + } else { + false + } +} + +/// Relevant on Windows only. +pub fn app_window_is_in_text_entry_mode(hwnd: Hwnd) -> Option { + let map = REGISTERED_APP_WINDOWS.get().borrow(); + let state = map.get(&hwnd)?; + Some(state.is_in_text_entry_mode) +} + +#[derive(Debug)] +struct CommonAppRunningState { + app_handle: AppHandle, + app_callback: Option, +} + +impl CommonAppRunningState { + pub fn send(&self, reply: &Reply) -> Result<()> { + let app_callback = self.app_callback.context("app callback not known yet")?; + send_to_app(app_callback, reply); + Ok(()) + } + + pub fn window(&self) -> Option { + let app_library = BackboneShell::get_app_library().ok()?; + app_library + .app_instance_get_window(self.app_handle) + .ok() + .flatten() + } + + pub fn is_visible(&self) -> bool { + let Ok(app_library) = BackboneShell::get_app_library() else { + return false; + }; + app_library + .app_instance_is_visible(self.app_handle) + .unwrap_or(false) + } + + pub fn has_focus(&self) -> bool { + let Ok(app_library) = BackboneShell::get_app_library() else { + return false; + }; + app_library + .app_instance_has_focus(self.app_handle) + .unwrap_or(false) + } + + pub fn hide(&self) -> Result<()> { + BackboneShell::get_app_library()?.hide_app_instance(self.app_handle) + } + + pub fn stop(&self, window: Option) -> Result<()> { + BackboneShell::get_app_library()?.stop_app_instance(window, self.app_handle) + } +} + +fn send_to_app(app_callback: AppCallback, reply: &Reply) { + let bytes = reply.encode_to_vec(); + if called_from_dart() { + // We must never call back into Dart if we are being called by Dart! + BackboneShell::get().spawn_in_async_runtime(async move { + send_to_app_internal(app_callback, bytes); + }); + } else { + send_to_app_internal(app_callback, bytes); + } +} + +fn send_to_app_internal(app_callback: AppCallback, bytes: Vec) { + let length = bytes.len(); + let boxed_slice = bytes.into_boxed_slice(); + // The app side is responsible for freeing the memory! + // We really need to pass owned data to the app because it's written in Dart and Dart code + // doesn't execute on the same thread. It will execute the code asynchronously in another + // thread and at that point the data still needs to be valid. + let raw_ptr = Box::into_raw(boxed_slice); + // This can lead to a User-mode data execution prevention (DEP) violation if the DLL + // is unloaded at this point. This was possible on Windows on REAPER exit. However, it shouldn't happen anymore + // now that we drop everything in the HiddenHelperPanel instead of waiting for the DLL detach. + unsafe { + app_callback(raw_ptr as *const _, length as _); + } +} + +/// Signature of the function that's used from the host in order to call the external app. +pub type AppCallback = unsafe extern "C" fn(data: *const u8, length: i32); + +fn subscribe_to_events() -> ProtoReceivers { + BackboneShell::get() + .proto_hub() + .senders() + .subscribe_to_all() +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/app/app_library.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/app/app_library.rs new file mode 100644 index 0000000..685805d --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/app/app_library.rs @@ -0,0 +1,855 @@ +use crate::infrastructure::plugin::{reaper_main_window, BackboneShell}; +use crate::infrastructure::proto; +use crate::infrastructure::proto::{ + create_initial_global_updates, create_initial_instance_updates, create_initial_unit_updates, + event_reply, query_result, reply, request, EventReply, ProtoRequestHandler, QueryReply, + QueryResult, Reply, Request, +}; +use crate::infrastructure::ui::{AppCallback, SharedAppInstance}; +use anyhow::{anyhow, bail, Context, Result}; +use base::Global; +use libloading::{Library, Symbol}; +use std::cell::Cell; + +use crate::domain::InstanceId; +#[cfg(feature = "playtime")] +use playtime_clip_engine::base::Matrix; +use prost::Message; +use reaper_high::Reaper; +use reaper_low::raw::HWND; +use reaper_medium::Hwnd; +use semver::Version; +use std::env; +use std::ffi::{c_char, c_uint, c_void, CStr, CString}; +use std::future::Future; +use std::path::{Path, PathBuf}; +use std::ptr::{null_mut, NonNull}; +use swell_ui::Window; +use tonic::Status; +use tracing::debug; + +#[derive(Debug)] +pub struct AppLibrary { + app_base_dir: PathBuf, + main_library: Library, + _dependencies: Vec, +} + +impl AppLibrary { + pub fn load(app_base_dir: PathBuf) -> Result { + let (main_library, dependencies) = if cfg!(target_os = "windows") { + ( + "helgobox.dll", + [ + // Important: This must be the first. Because below plug-in libraries + // depend on it. + "flutter_windows.dll", + // The rest can have an arbitrary order. + "desktop_drop_plugin.dll", + "native_context_menu_plugin.dll", + "screen_retriever_plugin.dll", + "url_launcher_windows_plugin.dll", + "window_manager_plugin.dll", + "pointer_lock_plugin.dll", + ] + .as_slice(), + ) + } else if cfg!(target_os = "macos") { + ( + "Contents/MacOS/helgobox", + [ + // Important: This must be the first. Because below plug-in libraries + // depend on it. + "Contents/Frameworks/FlutterMacOS.framework/FlutterMacOS", + // The rest can have an arbitrary order. + "Contents/Frameworks/cryptography_flutter.framework/cryptography_flutter", + "Contents/Frameworks/device_info_plus.framework/device_info_plus", + "Contents/Frameworks/desktop_drop.framework/desktop_drop", + "Contents/Frameworks/native_context_menu.framework/native_context_menu", + "Contents/Frameworks/path_provider_foundation.framework/path_provider_foundation", + "Contents/Frameworks/screen_retriever.framework/screen_retriever", + "Contents/Frameworks/url_launcher_macos.framework/url_launcher_macos", + "Contents/Frameworks/window_manager.framework/window_manager", + "Contents/Frameworks/pointer_lock.framework/pointer_lock", + ].as_slice(), + ) + } else if cfg!(target_os = "linux") { + ( + "helgobox.so", + ["flutter_linux.so", "url_launcher_linux_plugin.so"].as_slice(), + ) + } else { + bail!("OS not supported"); + }; + let loaded_dependencies: Result> = dependencies + .iter() + .map(|dep| load_library(&app_base_dir.join(dep))) + .collect(); + let library = AppLibrary { + main_library: load_library(&app_base_dir.join(main_library))?, + app_base_dir, + _dependencies: loaded_dependencies?, + }; + library.verify_version_compatibility()?; + Ok(library) + } + + fn verify_version_compatibility(&self) -> Result<()> { + let version = self.get_app_api_version()?; + if version < MIN_APP_API_VERSION || version.major > MIN_APP_API_VERSION.major { + bail!("App API version doesn't match. Expected version: {MIN_APP_API_VERSION}. Actual version: {version}."); + } + Ok(()) + } + + fn get_app_api_version(&self) -> Result { + let mut buf = [0; 32]; + let version_str = unsafe { + let get_app_version: Symbol = self + .main_library + .get(b"get_app_api_version\0") + .map_err(|_| anyhow!("Failed to load get_api_version function"))?; + get_app_version(buf.as_mut_ptr() as *mut c_char, buf.len()); + CStr::from_bytes_until_nul(&buf)?.to_str()? + }; + let version = Version::parse(version_str)?; + Ok(version) + } + + pub fn start_app_instance( + &self, + parent_window: Option, + instance_id: InstanceId, + location: String, + ) -> Result { + let app_base_dir_str = self + .app_base_dir + .to_str() + .ok_or(anyhow!("app base dir is not an UTF-8 string"))?; + let app_base_dir_c_string = CString::new(app_base_dir_str) + .map_err(|_| anyhow!("app base dir contains a nul byte"))?; + let location_c_string = + CString::new(location).map_err(|_| anyhow!("location contains a nul byte"))?; + with_temporarily_changed_working_directory(&self.app_base_dir, || { + prepare_app_start(); + let app_handle = unsafe { + let start_app_instance: Symbol = self + .main_library + .get(b"start_app_instance\0") + .map_err(|_| anyhow!("failed to load start_app_instance function"))?; + start_app_instance( + parent_window.map(|w| w.raw()).unwrap_or(null_mut()), + app_base_dir_c_string.as_ptr(), + invoke_host, + instance_id.into(), + location_c_string.as_ptr(), + Reaper::get().main_window().as_ptr(), + ) + }; + let Some(app_handle) = app_handle else { + bail!("couldn't start app"); + }; + Ok(app_handle) + }) + } + + pub fn show_app_instance( + &self, + parent_window: Option, + app_handle: AppHandle, + ) -> Result<()> { + unsafe { + let show_app_instance: Symbol = self + .main_library + .get(b"show_app_instance\0") + .map_err(|_| anyhow!("failed to load show_app_instance function"))?; + show_app_instance( + parent_window.map(|w| w.raw()).unwrap_or(null_mut()), + app_handle, + ); + }; + Ok(()) + } + + pub fn hide_app_instance(&self, app_handle: AppHandle) -> Result<()> { + unsafe { + let hide_app_instance: Symbol = self + .main_library + .get(b"hide_app_instance\0") + .map_err(|_| anyhow!("failed to load hide_app_instance function"))?; + hide_app_instance(app_handle, reaper_main_window().raw()); + }; + Ok(()) + } + + pub fn app_instance_is_visible(&self, app_handle: AppHandle) -> Result { + let visible = unsafe { + let app_instance_is_visible: Symbol = self + .main_library + .get(b"app_instance_is_visible\0") + .map_err(|_| anyhow!("failed to load app_instance_is_visible function"))?; + app_instance_is_visible(app_handle) + }; + Ok(visible) + } + + pub fn app_instance_get_window(&self, app_handle: AppHandle) -> Result> { + let hwnd = unsafe { + let get_app_instance_window: Symbol = self + .main_library + .get(b"get_app_instance_window\0") + .map_err(|_| anyhow!("failed to load get_app_instance_window function"))?; + get_app_instance_window(app_handle) + }; + Ok(Hwnd::new(hwnd)) + } + + pub fn app_instance_has_focus(&self, app_handle: AppHandle) -> Result { + let visible = unsafe { + let app_instance_has_focus: Symbol = self + .main_library + .get(b"app_instance_has_focus\0") + .map_err(|_| anyhow!("failed to load app_instance_has_focus function"))?; + app_instance_has_focus(app_handle) + }; + Ok(visible) + } + + pub fn stop_app_instance( + &self, + parent_window: Option, + app_handle: AppHandle, + ) -> Result<()> { + unsafe { + let stop_app_instance: Symbol = self + .main_library + .get(b"stop_app_instance\0") + .map_err(|_| anyhow!("failed to load stop_app_instance function"))?; + stop_app_instance( + parent_window.map(|w| w.raw()).unwrap_or(null_mut()), + app_handle, + ); + }; + Ok(()) + } +} + +/// Function that's used from Dart in order to call the host. +/// +/// Attention: This is *not* called from the main thread but from some special Flutter UI thread. +#[no_mangle] +extern "C" fn invoke_host(data: *const u8, length: i32) { + // Decode payload + let bytes = unsafe { std::slice::from_raw_parts(data, length as usize) }; + let request = Request::decode(bytes).unwrap(); + // Extract values + let Some(request_value) = request.value else { + tracing::error!(msg = "incoming app request didn't have value"); + return; + }; + // Process request + if let Err(error) = process_request(request.instance_id.into(), request_value) { + tracing::error!(msg = "error in synchronous phase of request processing", %error); + } +} + +/// Processes the given request. +/// +/// # Errors +/// +/// Returns an error if something in the synchronous part of the request processing went wrong. +fn process_request(instance_id: InstanceId, request_value: request::Value) -> Result<()> { + use proto::request::Value; + match request_value { + // It's a command (fire-and-forget) + Value::CommandRequest(command_request) => { + let command_request_value = command_request + .value + .context("incoming app command request didn't have value")?; + process_command_request(instance_id, command_request_value) + .context("processing command request")?; + Ok(()) + } + // It's a query (with async response) + Value::QueryRequest(query_request) => { + let query_request_value = query_request + .query + .context("incoming app query request didn't have query")? + .value + .context("incoming app query didn't have value")?; + process_query_request(instance_id, query_request.id, query_request_value) + .context("processing query request")?; + Ok(()) + } + } +} + +pub type AppHandle = NonNull; + +/// Signature of the function that we use to query the version of the app API. +/// +/// This is not the official version off the app, just the semantic version that affects how +/// the host can talk to the app. The general idea is: +/// +/// 1. Host (ReaLearn) queries this app API version number (before doing anything else). +/// 2. Host checks if that version number matches his expectations (semantic versioning semantics). +/// 3. Host refuses to load the app if the expectations are not matched. +type GetAppApiVersion = unsafe extern "C" fn(version: *mut c_char, buf_size: usize); + +/// Signature of the function that we use to start an app instance and show it for the first time. +/// +/// # Arguments +/// +/// * `parent_window` - Optional parent window handle. If you pass this, the app (if supported for +/// the OS) will render itself *within* that parent window. On macOS, this is should be an NSView. +/// * `app_base_dir_utf8_c_str`- Directory where the app is located +/// * `host_callback` - Pointer to host callback function +/// * `instance_id` - Instance ID of the ReaLearn instance associated with this new app instance. +/// * `location` - Initial location (route) within the app. +/// * `main_window` - Handle to REAPER's main window +type StartAppInstance = unsafe extern "C" fn( + parent_window: HWND, + app_base_dir_utf8_c_str: *const c_char, + host_callback: HostCallback, + instance_id: c_uint, + location: *const c_char, + main_window: HWND, +) -> Option; + +/// Signature of the function that we use to show an app instance. +type ShowAppInstance = unsafe extern "C" fn(parent_window: HWND, app_handle: AppHandle); + +/// Signature of the function that we use to hide an app instance. +type HideAppInstance = unsafe extern "C" fn(app_handle: AppHandle, host_window: HWND); + +/// Signature of the function that we use to check whether an app instance has focus. +type AppInstanceHasFocus = unsafe extern "C" fn(app_handle: AppHandle) -> bool; + +/// Signature of the function that we use to check whether an app instance is visible. +type AppInstanceIsVisible = unsafe extern "C" fn(app_handle: AppHandle) -> bool; + +/// Signature of the function that we use to acquire the app window. +type GetAppInstanceWindow = unsafe extern "C" fn(app_handle: AppHandle) -> HWND; + +/// Signature of the function that we use to stop an app instance. +type StopAppInstance = unsafe extern "C" fn(parent_window: HWND, app_handle: AppHandle); + +/// Signature of the function that's used from the app in order to call the host. +type HostCallback = extern "C" fn(data: *const u8, length: i32); + +fn load_library(path: &Path) -> Result { + match path.try_exists() { + Ok(false) => bail!("App library {path:?} not found."), + Err(e) => bail!("App library {path:?} not accessible: {e}"), + _ => {} + } + let lib = unsafe { Library::new(path) }; + lib.with_context(|| format!("Failed to load app library {path:?}.")) +} + +fn with_temporarily_changed_working_directory( + new_dir: impl AsRef, + f: impl FnOnce() -> R, +) -> R { + let previous_dir = env::current_dir(); + let dir_change_was_successful = env::set_current_dir(new_dir).is_ok(); + let r = f(); + if dir_change_was_successful { + if let Ok(d) = previous_dir { + let _ = env::set_current_dir(d); + } + } + r +} + +fn prepare_app_start() { + #[cfg(target_os = "macos")] + { + // This is only necessary and only considered by Flutter Engine when Flutter is compiled in + // debug mode. In release mode, Flutter will work with AOT data embedded in the binary. + let env_vars = [ + ("FLUTTER_ENGINE_SWITCHES", "3"), + ("FLUTTER_ENGINE_SWITCH_1", "snapshot-asset-path=Contents/Frameworks/App.framework/Versions/A/Resources/flutter_assets"), + ("FLUTTER_ENGINE_SWITCH_2", "vm-snapshot-data=vm_snapshot_data"), + ("FLUTTER_ENGINE_SWITCH_3", "isolate-snapshot-data=isolate_snapshot_data"), + ]; + for (key, value) in env_vars { + env::set_var(key, value); + } + } +} + +/// Executes the given command in the main thread. +/// +/// # Errors +/// +/// Returns an error if the main thread task queue is full. +fn process_command_request( + instance_id: InstanceId, + value: proto::command_request::Value, +) -> Result<()> { + // The handler to execute for this command + let handler = move || { + // Execute command + let result = process_command(instance_id, value); + // Handle possible error + if let Err(status) = result { + // Log error + tracing::error!(msg = "error in asynchronous phase of command request processing", %status); + // Send it to the app as notification + let _ = send_to_app( + instance_id, + reply::Value::EventReply(EventReply { + value: Some(event_reply::Value::ErrorMessage( + status.message().to_string(), + )), + }), + ); + } + }; + if Reaper::get().is_in_main_thread() { + debug!("Handle Dart command coming from main thread"); + // We are already in the main thread! Since Helgobox 2.18.3-pre.2 this is the default! + // Because the app associated with this version was updated to Flutter 3.38.8 (previously 3.29.3) + // and therefore is affected by the "Great thread merge". + // (https://docs.flutter.dev/release/breaking-changes/macos-windows-merged-threads and https://www.youtube.com/watch?v=miW7vCmQwnw&vl=de) + // It's quite nice that we are called from the main thread now! But it's important to + // consider that we must never call back into Dart from the same callstack! That's why we + // do the thread-local stuff here. + CALLED_FROM_DART.set(true); + scopeguard::defer! { + CALLED_FROM_DART.set(false); + } + handler(); + } else { + debug!("Handle Dart command coming from non-main thread"); + // We are being called from another thread, not the main thread! This was most of the time the case + // in previous Helgobox versions and should almost never happen nowadays. Since most command handlers + // necessarily need to execute in the main thread, we schedule the handler fo execution on the main thread! + Global::task_support() + .do_later_in_main_thread_asap(handler) + .map_err(|e| anyhow!(e))?; + } + Ok(()) +} + +fn process_query_request( + instance_id: InstanceId, + req_id: u32, + query: proto::query::Value, +) -> Result<()> { + use proto::query::Value::*; + let handler = ProtoRequestHandler; + match query { + ProveAuthenticity(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.prove_authenticity(req).await?.into_inner(); + Ok(query_result::Value::ProveAuthenticityReply(value)) + }); + } + GetClipDetail(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.get_clip_detail(req).await?.into_inner(); + Ok(query_result::Value::GetClipDetailReply(value)) + }); + } + GetProjectDir(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.get_project_dir(req).await?.into_inner(); + Ok(query_result::Value::GetProjectDirReply(value)) + }); + } + GetHostInfo(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.get_host_info(req).await?.into_inner(); + Ok(query_result::Value::GetHostInfoReply(value)) + }); + } + GetArrangementInfo(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.get_arrangement_info(req).await?.into_inner(); + Ok(query_result::Value::GetArrangementInfoReply(value)) + }); + } + GetAppSettings(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.get_app_settings(req).await?.into_inner(); + Ok(query_result::Value::GetAppSettingsReply(value)) + }); + } + GetCompartmentData(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.get_compartment_data(req)?.into_inner(); + Ok(query_result::Value::GetCompartmentDataReply(value)) + }); + } + GetCustomInstanceData(req) => { + send_query_reply_to_app(instance_id, req_id, async move { + let value = handler.get_custom_instance_data(req)?.into_inner(); + Ok(query_result::Value::GetCustomInstanceDataReply(value)) + }); + } + } + Ok(()) +} + +fn process_command( + instance_id: InstanceId, + req: proto::command_request::Value, +) -> std::result::Result<(), Status> { + let handler = ProtoRequestHandler; + use proto::command_request::Value::*; + match req { + // Embedding + NotifyAppIsReady(req) => { + // App instance is started. Put the app instance callback at the correct position. + let ptr = req.app_callback_address as *const (); + let app_callback: AppCallback = unsafe { std::mem::transmute(ptr) }; + find_app_instance(req.matrix_id.into()) + .map_err(to_status)? + .borrow_mut() + .notify_app_is_ready(app_callback); + } + SetAppIsInTextEntryMode(req) => { + find_app_instance(req.matrix_id.into()) + .map_err(to_status)? + .borrow_mut() + .notify_app_is_in_text_entry_mode(req.is_in_text_entry_mode); + } + // Event subscription commands + GetOccasionalGlobalUpdates(_) => { + send_initial_events_to_app(instance_id, create_initial_global_updates) + .map_err(to_status)?; + } + GetOccasionalInstanceUpdates(req) => { + send_initial_events_to_app(instance_id, || { + let instance_shell = BackboneShell::get() + .find_instance_shell_by_instance_id(req.instance_id.into()) + .unwrap(); + create_initial_instance_updates(&instance_shell) + }) + .map_err(to_status)?; + } + GetOccasionalUnitUpdates(req) => { + send_initial_events_to_app(instance_id, || { + let instance_shell = BackboneShell::get() + .find_instance_shell_by_instance_id(req.instance_id.into()) + .unwrap(); + create_initial_unit_updates(&instance_shell) + }) + .map_err(to_status)?; + } + GetOccasionalPlaytimeEngineUpdates(_) => { + #[cfg(not(feature = "playtime"))] + { + return playtime_not_available(); + } + #[cfg(feature = "playtime")] + { + send_initial_events_to_app( + instance_id, + crate::infrastructure::proto::create_initial_engine_updates, + ) + .map_err(to_status)?; + } + } + GetOccasionalMatrixUpdates(req) => { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + return playtime_not_available(); + } + #[cfg(feature = "playtime")] + { + send_initial_matrix_events_to_app( + instance_id, + req.matrix_id.into(), + proto::create_initial_matrix_updates, + ) + .map_err(to_status)?; + } + } + GetOccasionalTrackUpdates(req) => { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + return playtime_not_available(); + } + #[cfg(feature = "playtime")] + { + send_initial_matrix_events_to_app( + instance_id, + req.matrix_id.into(), + proto::create_initial_track_updates, + ) + .map_err(to_status)?; + } + } + GetOccasionalSlotUpdates(req) => { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + return playtime_not_available(); + } + #[cfg(feature = "playtime")] + { + send_initial_matrix_events_to_app( + instance_id, + req.matrix_id.into(), + proto::create_initial_slot_updates, + ) + .map_err(to_status)?; + } + } + GetOccasionalClipUpdates(req) => { + #[cfg(not(feature = "playtime"))] + { + let _ = req; + return playtime_not_available(); + } + #[cfg(feature = "playtime")] + { + send_initial_matrix_events_to_app( + instance_id, + req.matrix_id.into(), + proto::create_initial_clip_updates, + ) + .map_err(to_status)?; + } + } + // Normal commands + AddLicense(req) => { + handler.add_license(req)?; + } + SaveController(req) => { + handler.save_controller(req)?; + } + DeleteController(req) => { + handler.delete_controller(req)?; + } + TriggerMatrix(req) => { + handler.trigger_matrix(req)?; + } + TriggerInstance(req) => { + handler.trigger_instance(req)?; + } + TriggerGlobal(req) => { + handler.trigger_global(req)?; + } + SetPlaytimeEngineSettings(req) => { + handler.set_playtime_engine_settings(req)?; + } + SetMatrixSettings(req) => { + handler.set_matrix_settings(req)?; + } + SetMatrixTempo(req) => { + handler.set_matrix_tempo(req)?; + } + SetMatrixPlayRate(req) => { + handler.set_matrix_play_rate(req)?; + } + SetMatrixTimeSignature(req) => { + handler.set_matrix_time_signature(req)?; + } + SetMatrixVolume(req) => { + handler.set_matrix_volume(req)?; + } + SetMatrixClickChannel(req) => { + handler.set_matrix_click_channel(req)?; + } + SetMatrixPan(req) => { + handler.set_matrix_pan(req)?; + } + TriggerColumn(req) => { + handler.trigger_column(req)?; + } + TriggerTrack(req) => { + handler.trigger_track(req)?; + } + SetColumnSettings(req) => { + handler.set_column_settings(req)?; + } + SetTrackVolume(req) => { + handler.set_track_volume(req)?; + } + SetTrackPan(req) => { + handler.set_track_pan(req)?; + } + OpenTrackFx(req) => { + handler.open_track_fx(req)?; + } + SetColumnTrack(req) => { + Global::future_support().spawn_in_main_thread_from_main_thread(async move { + handler.set_column_track(req).await?; + Ok(()) + }); + } + DragColumn(req) => { + handler.drag_column(req)?; + } + SetTrackName(req) => { + handler.set_track_name(req)?; + } + SetTrackColor(req) => { + handler.set_track_color(req)?; + } + SetTrackInput(req) => { + handler.set_track_input(req)?; + } + SetTrackInputMonitoring(req) => { + handler.set_track_input_monitoring(req)?; + } + TriggerRow(req) => { + handler.trigger_row(req)?; + } + SetRowData(req) => { + handler.set_row_data(req)?; + } + DragRow(req) => { + handler.drag_row(req)?; + } + TriggerSlot(req) => { + handler.trigger_slot(req)?; + } + ImportFiles(req) => { + handler.import_files(req)?; + } + DragSlot(req) => { + handler.drag_slot(req)?; + } + DragClip(req) => { + handler.drag_clip(req)?; + } + TriggerClip(req) => { + handler.trigger_clip(req)?; + } + SetClipName(req) => { + handler.set_clip_name(req)?; + } + SetClipData(req) => { + handler.set_clip_data(req)?; + } + TriggerSequence(req) => { + handler.trigger_sequence(req)?; + } + SetSequenceInfo(req) => { + handler.set_sequence_info(req)?; + } + SetInstanceSettings(req) => { + handler.set_instance_settings(req)?; + } + SetAppSettings(req) => { + handler.set_app_settings(req)?; + } + SaveCustomCompartmentData(req) => { + handler.save_custom_compartment_data(req)?; + } + InsertColumns(req) => { + handler.insert_columns(req)?; + } + SetCustomInstanceData(req) => { + handler.set_custom_instance_data(req)?; + } + } + Ok(()) +} + +fn send_initial_events_to_app>( + instance_id: InstanceId, + create_reply: impl FnOnce() -> T + Copy, +) -> Result<()> { + let reply = create_reply().into(); + send_event_reply_to_app(instance_id, reply) +} + +/// The matrix ID should actually always be the same as the instance ID. We use different +/// parameters because one is for identifying the matrix and the other one the destination app +/// instance. In practice, there's a one-to-one relationship between +/// Helgobox instance <=> Matrix instance <=> App instance. +#[cfg(feature = "playtime")] +fn send_initial_matrix_events_to_app>( + instance_id: InstanceId, + matrix_id: InstanceId, + create_reply: impl FnOnce(Option<&Matrix>) -> T + Copy, +) -> Result<()> { + let reply = BackboneShell::get() + .with_clip_matrix(matrix_id, |matrix| create_reply(Some(matrix)).into()) + .unwrap_or_else(|_| create_reply(None).into()); + send_event_reply_to_app(instance_id, reply) +} + +fn send_event_reply_to_app(instance_id: InstanceId, value: event_reply::Value) -> Result<()> { + send_to_app( + instance_id, + reply::Value::EventReply(EventReply { value: Some(value) }), + ) +} + +fn send_query_reply_to_app( + instance_id: InstanceId, + req_id: u32, + future: impl Future> + Send + 'static, +) { + Global::future_support().spawn_in_main_thread(async move { + let query_result_value = match future.await { + Ok(outcome) => outcome, + Err(error) => query_result::Value::Error(error.to_string()), + }; + let reply_value = reply::Value::QueryReply(QueryReply { + id: req_id, + result: Some(QueryResult { + value: Some(query_result_value), + }), + }); + send_to_app(instance_id, reply_value)?; + Ok(()) + }); +} + +fn send_to_app(instance_id: InstanceId, reply_value: reply::Value) -> Result<()> { + let app_instance = find_app_instance(instance_id)?; + let reply = Reply { + value: Some(reply_value), + }; + app_instance.borrow().send(&reply)?; + Ok(()) +} + +fn find_app_instance(instance_id: InstanceId) -> Result { + let instance_panel = BackboneShell::get() + .find_instance_panel_by_instance_id(instance_id) + .ok_or_else(|| anyhow!("Helgobox instance {instance_id} not found"))?; + Ok(instance_panel.app_instance().clone()) +} + +fn to_status(err: anyhow::Error) -> Status { + Status::unknown(err.to_string()) +} + +/// The minimum version of the app API that the host (Helgobox) requires to properly +/// communicate with it. Keep this up to date! +/// +/// This doesn't necessarily need to match the `HOST_API_VERSION`, it's too different things. +/// In practice, it might be equal or similar because host (plug-in) and app are developed tightly together. +pub const MIN_APP_API_VERSION: Version = Version::new(16, 0, 0); + +#[cfg(not(feature = "playtime"))] +fn playtime_not_available() -> Result<(), Status> { + Err(Status::not_found("Playtime feature not available")) +} + +/// Returns whether we are currently being called from Dart (= the app). +/// +/// ## Background +/// +/// If we want to send something to the app (= invoke the native Dart callback), we MUST NOT do +/// it while we are being called from Dart (not in the same callstack)!!! +/// That's forbidden by the Dart VM and will lead to immediate abort. Instead, we must **defer** +/// the execution of that callback - it can be any thread! Just another callstack. +pub fn called_from_dart() -> bool { + CALLED_FROM_DART.get() +} + +thread_local! { + static CALLED_FROM_DART: Cell = const { Cell::new(false) }; +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/app/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/app/mod.rs new file mode 100644 index 0000000..33d2b82 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/app/mod.rs @@ -0,0 +1,5 @@ +mod app_instance; +pub use app_instance::*; + +mod app_library; +pub use app_library::*; diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/bindings.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/bindings.rs new file mode 100644 index 0000000..a9aef2a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/bindings.rs @@ -0,0 +1,327 @@ +pub mod root { + #[cfg(target_os = "linux")] + pub const GLOBAL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const GLOBAL_Y_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const GLOBAL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const GLOBAL_HEIGHT_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const GLOBAL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const GLOBAL_Y_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const GLOBAL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const GLOBAL_HEIGHT_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const GLOBAL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const GLOBAL_Y_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const GLOBAL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const GLOBAL_HEIGHT_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const MAPPING_PANEL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const MAPPING_PANEL_Y_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const MAPPING_PANEL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const MAPPING_PANEL_HEIGHT_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const MAPPING_PANEL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const MAPPING_PANEL_Y_SCALE: f64 = 0.8000; + #[cfg(target_os = "windows")] + pub const MAPPING_PANEL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const MAPPING_PANEL_HEIGHT_SCALE: f64 = 0.8000; + #[cfg(target_os = "macos")] + pub const MAPPING_PANEL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const MAPPING_PANEL_Y_SCALE: f64 = 0.9200; + #[cfg(target_os = "macos")] + pub const MAPPING_PANEL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const MAPPING_PANEL_HEIGHT_SCALE: f64 = 0.9200; + #[cfg(target_os = "linux")] + pub const HEADER_PANEL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const HEADER_PANEL_Y_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const HEADER_PANEL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "linux")] + pub const HEADER_PANEL_HEIGHT_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const HEADER_PANEL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const HEADER_PANEL_Y_SCALE: f64 = 0.8000; + #[cfg(target_os = "windows")] + pub const HEADER_PANEL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "windows")] + pub const HEADER_PANEL_HEIGHT_SCALE: f64 = 0.8000; + #[cfg(target_os = "macos")] + pub const HEADER_PANEL_X_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const HEADER_PANEL_Y_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const HEADER_PANEL_WIDTH_SCALE: f64 = 1.0000; + #[cfg(target_os = "macos")] + pub const HEADER_PANEL_HEIGHT_SCALE: f64 = 1.0000; + pub const ID_GROUP_PANEL: u32 = 30000; + pub const ID_GROUP_PANEL_OK: u32 = 30001; + pub const ID_HEADER_PANEL: u32 = 30042; + pub const ID_CONTROL_INPUT_BUTTON: u32 = 30003; + pub const ID_FEEDBACK_OUTPUT_BUTTON: u32 = 30005; + pub const ID_MENU_BUTTON: u32 = 30006; + pub const ID_IMPORT_BUTTON: u32 = 30007; + pub const ID_EXPORT_BUTTON: u32 = 30008; + pub const ID_PROJECTION_BUTTON: u32 = 30009; + pub const ID_MAIN_HELP_BUTTON: u32 = 30010; + pub const ID_LET_THROUGH_LABEL_TEXT: u32 = 30011; + pub const ID_LET_MATCHED_EVENTS_THROUGH_CHECK_BOX: u32 = 30012; + pub const ID_LET_UNMATCHED_EVENTS_THROUGH_CHECK_BOX: u32 = 30013; + pub const ID_HEADER_PANEL_SHOW_LABEL_TEXT: u32 = 30014; + pub const ID_CONTROLLER_COMPARTMENT_RADIO_BUTTON: u32 = 30015; + pub const ID_MAIN_COMPARTMENT_RADIO_BUTTON: u32 = 30016; + pub const ID_PRESET_LABEL_TEXT: u32 = 30017; + pub const ID_PRESET_BROWSE_BUTTON: u32 = 30018; + pub const ID_PRESET_SAVE_BUTTON: u32 = 30019; + pub const ID_PRESET_SAVE_AS_BUTTON: u32 = 30020; + pub const ID_PRESET_DELETE_BUTTON: u32 = 30021; + pub const ID_AUTO_LOAD_LABEL_TEXT: u32 = 30022; + pub const ID_AUTO_LOAD_COMBO_BOX: u32 = 30023; + pub const ID_GROUP_COMBO_BOX: u32 = 30025; + pub const ID_GROUP_ADD_BUTTON: u32 = 30026; + pub const ID_GROUP_DELETE_BUTTON: u32 = 30027; + pub const ID_GROUP_EDIT_BUTTON: u32 = 30028; + pub const ID_NOTES_BUTTON: u32 = 30029; + pub const ID_ADD_MAPPING_BUTTON: u32 = 30031; + pub const ID_LEARN_MANY_MAPPINGS_BUTTON: u32 = 30032; + pub const ID_HEADER_SEARCH_EDIT_CONTROL: u32 = 30034; + pub const ID_CLEAR_SEARCH_BUTTON: u32 = 30035; + pub const ID_FILTER_BY_SOURCE_BUTTON: u32 = 30036; + pub const ID_CLEAR_SOURCE_FILTER_BUTTON: u32 = 30037; + pub const ID_FILTER_BY_TARGET_BUTTON: u32 = 30038; + pub const ID_CLEAR_TARGET_FILTER_BUTTON: u32 = 30039; + pub const ID_MAPPING_PANEL: u32 = 30190; + pub const ID_MAPPING_PANEL_MAPPING_LABEL: u32 = 30043; + pub const ID_MAPPING_PANEL_FEEDBACK_LABEL: u32 = 30044; + pub const ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX: u32 = 30045; + pub const ID_MAPPING_SHOW_IN_PROJECTION_CHECK_BOX: u32 = 30046; + pub const ID_MAPPING_ADVANCED_BUTTON: u32 = 30047; + pub const ID_MAPPING_FIND_IN_LIST_BUTTON: u32 = 30048; + pub const ID_MAPPING_PANEL_SOURCE_LABEL: u32 = 30049; + pub const ID_SOURCE_LEARN_BUTTON: u32 = 30050; + pub const ID_MAPPING_PANEL_SOURCE_CATEGORY_LABEL: u32 = 30051; + pub const ID_SOURCE_CATEGORY_COMBO_BOX: u32 = 30052; + pub const ID_SOURCE_TYPE_LABEL_TEXT: u32 = 30053; + pub const ID_SOURCE_TYPE_COMBO_BOX: u32 = 30054; + pub const ID_SOURCE_MIDI_MESSAGE_TYPE_LABEL_TEXT: u32 = 30055; + pub const ID_SOURCE_CHANNEL_LABEL: u32 = 30056; + pub const ID_SOURCE_CHANNEL_COMBO_BOX: u32 = 30057; + pub const ID_SOURCE_LINE_3_EDIT_CONTROL: u32 = 30058; + pub const ID_SOURCE_NOTE_OR_CC_NUMBER_LABEL_TEXT: u32 = 30059; + pub const ID_SOURCE_RPN_CHECK_BOX: u32 = 30060; + pub const ID_SOURCE_LINE_4_COMBO_BOX_1: u32 = 30061; + pub const ID_SOURCE_NUMBER_EDIT_CONTROL: u32 = 30062; + pub const ID_SOURCE_NUMBER_COMBO_BOX: u32 = 30063; + pub const ID_SOURCE_LINE_4_BUTTON: u32 = 30064; + pub const ID_SOURCE_CHARACTER_LABEL_TEXT: u32 = 30065; + pub const ID_SOURCE_CHARACTER_COMBO_BOX: u32 = 30066; + pub const ID_SOURCE_LINE_5_BUTTON: u32 = 30067; + pub const ID_SOURCE_LINE_5_EDIT_CONTROL: u32 = 30068; + pub const ID_SOURCE_14_BIT_CHECK_BOX: u32 = 30069; + pub const ID_SOURCE_OSC_ADDRESS_LABEL_TEXT: u32 = 30070; + pub const ID_SOURCE_OSC_ADDRESS_PATTERN_EDIT_CONTROL: u32 = 30071; + pub const ID_SOURCE_SCRIPT_DETAIL_BUTTON: u32 = 30072; + pub const ID_MAPPING_PANEL_TARGET_LABEL: u32 = 30073; + pub const ID_TARGET_LEARN_BUTTON: u32 = 30074; + pub const ID_TARGET_MENU_BUTTON: u32 = 30075; + pub const ID_TARGET_HINT: u32 = 30076; + pub const ID_MAPPING_PANEL_TARGET_TYPE_LABEL: u32 = 30077; + pub const ID_TARGET_CATEGORY_COMBO_BOX: u32 = 30078; + pub const ID_TARGET_TYPE_BUTTON: u32 = 30079; + pub const ID_TARGET_LINE_2_LABEL_2: u32 = 30080; + pub const ID_TARGET_LINE_2_LABEL_3: u32 = 30081; + pub const ID_TARGET_LINE_2_LABEL_1: u32 = 30082; + pub const ID_TARGET_LINE_2_COMBO_BOX_1: u32 = 30083; + pub const ID_TARGET_LINE_2_EDIT_CONTROL: u32 = 30084; + pub const ID_TARGET_LINE_2_COMBO_BOX_2: u32 = 30085; + pub const ID_TARGET_LINE_2_BUTTON: u32 = 30086; + pub const ID_TARGET_LINE_3_LABEL_1: u32 = 30087; + pub const ID_TARGET_LINE_3_COMBO_BOX_1: u32 = 30088; + pub const ID_TARGET_LINE_3_EDIT_CONTROL: u32 = 30089; + pub const ID_TARGET_LINE_3_COMBO_BOX_2: u32 = 30090; + pub const ID_TARGET_LINE_3_LABEL_2: u32 = 30091; + pub const ID_TARGET_LINE_3_LABEL_3: u32 = 30092; + pub const ID_TARGET_LINE_3_BUTTON: u32 = 30093; + pub const ID_TARGET_LINE_4_LABEL_1: u32 = 30094; + pub const ID_TARGET_LINE_4_COMBO_BOX_1: u32 = 30095; + pub const ID_TARGET_LINE_4_EDIT_CONTROL: u32 = 30096; + pub const ID_TARGET_LINE_4_COMBO_BOX_2: u32 = 30097; + pub const ID_TARGET_LINE_4_LABEL_2: u32 = 30098; + pub const ID_TARGET_LINE_4_BUTTON: u32 = 30099; + pub const ID_TARGET_LINE_4_LABEL_3: u32 = 30100; + pub const ID_TARGET_LINE_5_LABEL_1: u32 = 30101; + pub const ID_TARGET_LINE_5_EDIT_CONTROL: u32 = 30102; + pub const ID_TARGET_CHECK_BOX_1: u32 = 30103; + pub const ID_TARGET_CHECK_BOX_2: u32 = 30104; + pub const ID_TARGET_CHECK_BOX_3: u32 = 30105; + pub const ID_TARGET_CHECK_BOX_4: u32 = 30106; + pub const ID_TARGET_CHECK_BOX_5: u32 = 30107; + pub const ID_TARGET_CHECK_BOX_6: u32 = 30108; + pub const ID_TARGET_VALUE_LABEL_TEXT: u32 = 30109; + pub const ID_TARGET_VALUE_OFF_BUTTON: u32 = 30110; + pub const ID_TARGET_VALUE_ON_BUTTON: u32 = 30111; + pub const ID_TARGET_VALUE_SLIDER_CONTROL: u32 = 30112; + pub const ID_TARGET_VALUE_EDIT_CONTROL: u32 = 30113; + pub const ID_TARGET_VALUE_TEXT: u32 = 30114; + pub const ID_TARGET_UNIT_BUTTON: u32 = 30115; + pub const ID_MAPPING_PANEL_GLUE_LABEL: u32 = 30116; + pub const ID_SETTINGS_RESET_BUTTON: u32 = 30117; + pub const ID_SETTINGS_SOURCE_LABEL: u32 = 30118; + #[allow(dead_code)] + pub const ID_SETTINGS_SOURCE_GROUP: u32 = 30119; + pub const ID_SETTINGS_SOURCE_MIN_LABEL: u32 = 30120; + pub const ID_SETTINGS_MIN_SOURCE_VALUE_SLIDER_CONTROL: u32 = 30121; + pub const ID_SETTINGS_MIN_SOURCE_VALUE_EDIT_CONTROL: u32 = 30122; + pub const ID_SETTINGS_SOURCE_MAX_LABEL: u32 = 30123; + pub const ID_SETTINGS_MAX_SOURCE_VALUE_SLIDER_CONTROL: u32 = 30124; + pub const ID_SETTINGS_MAX_SOURCE_VALUE_EDIT_CONTROL: u32 = 30125; + pub const ID_MODE_OUT_OF_RANGE_LABEL_TEXT: u32 = 30126; + pub const ID_MODE_OUT_OF_RANGE_COMBOX_BOX: u32 = 30127; + pub const ID_MODE_GROUP_INTERACTION_LABEL_TEXT: u32 = 30128; + pub const ID_MODE_GROUP_INTERACTION_COMBO_BOX: u32 = 30129; + pub const ID_SETTINGS_TARGET_LABEL_TEXT: u32 = 30130; + pub const ID_SETTINGS_TARGET_SEQUENCE_LABEL_TEXT: u32 = 30131; + pub const ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL: u32 = 30132; + #[allow(dead_code)] + pub const ID_SETTINGS_TARGET_GROUP: u32 = 30133; + pub const ID_SETTINGS_MIN_TARGET_LABEL_TEXT: u32 = 30134; + pub const ID_SETTINGS_MIN_TARGET_VALUE_SLIDER_CONTROL: u32 = 30135; + pub const ID_SETTINGS_MIN_TARGET_VALUE_EDIT_CONTROL: u32 = 30136; + pub const ID_SETTINGS_MIN_TARGET_VALUE_TEXT: u32 = 30137; + pub const ID_SETTINGS_MAX_TARGET_LABEL_TEXT: u32 = 30138; + pub const ID_SETTINGS_MAX_TARGET_VALUE_SLIDER_CONTROL: u32 = 30139; + pub const ID_SETTINGS_MAX_TARGET_VALUE_EDIT_CONTROL: u32 = 30140; + pub const ID_SETTINGS_MAX_TARGET_VALUE_TEXT: u32 = 30141; + pub const ID_SETTINGS_REVERSE_CHECK_BOX: u32 = 30142; + pub const IDC_MODE_FEEDBACK_TYPE_COMBO_BOX: u32 = 30143; + pub const ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL: u32 = 30144; + pub const IDC_MODE_FEEDBACK_TYPE_BUTTON: u32 = 30145; + pub const ID_MODE_KNOB_FADER_GROUP_BOX: u32 = 30146; + pub const ID_SETTINGS_MODE_LABEL: u32 = 30147; + pub const ID_SETTINGS_MODE_COMBO_BOX: u32 = 30148; + pub const ID_MODE_TAKEOVER_LABEL: u32 = 30149; + pub const ID_MODE_TAKEOVER_MODE: u32 = 30150; + pub const ID_SETTINGS_ROUND_TARGET_VALUE_CHECK_BOX: u32 = 30151; + pub const ID_MODE_EEL_CONTROL_TRANSFORMATION_LABEL: u32 = 30152; + pub const ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL: u32 = 30153; + pub const ID_MODE_EEL_CONTROL_TRANSFORMATION_DETAIL_BUTTON: u32 = 30154; + pub const ID_MODE_RELATIVE_GROUP_BOX: u32 = 30155; + pub const ID_SETTINGS_STEP_SIZE_LABEL_TEXT: u32 = 30156; + #[allow(dead_code)] + pub const ID_SETTINGS_STEP_SIZE_GROUP: u32 = 30157; + pub const ID_SETTINGS_MIN_STEP_SIZE_LABEL_TEXT: u32 = 30158; + pub const ID_SETTINGS_MIN_STEP_SIZE_SLIDER_CONTROL: u32 = 30159; + pub const ID_SETTINGS_MIN_STEP_SIZE_EDIT_CONTROL: u32 = 30160; + pub const ID_SETTINGS_MIN_STEP_SIZE_VALUE_TEXT: u32 = 30161; + pub const ID_SETTINGS_MAX_STEP_SIZE_LABEL_TEXT: u32 = 30162; + pub const ID_SETTINGS_MAX_STEP_SIZE_SLIDER_CONTROL: u32 = 30163; + pub const ID_SETTINGS_MAX_STEP_SIZE_EDIT_CONTROL: u32 = 30164; + pub const ID_SETTINGS_MAX_STEP_SIZE_VALUE_TEXT: u32 = 30165; + pub const ID_MODE_RELATIVE_FILTER_COMBO_BOX: u32 = 30166; + pub const ID_SETTINGS_ROTATE_CHECK_BOX: u32 = 30167; + pub const ID_SETTINGS_MAKE_ABSOLUTE_CHECK_BOX: u32 = 30168; + pub const ID_MODE_BUTTON_GROUP_BOX: u32 = 30169; + pub const ID_MODE_FIRE_COMBO_BOX: u32 = 30170; + pub const ID_MODE_BUTTON_FILTER_COMBO_BOX: u32 = 30171; + pub const ID_MODE_FIRE_LINE_2_LABEL_1: u32 = 30172; + pub const ID_MODE_FIRE_LINE_2_SLIDER_CONTROL: u32 = 30173; + pub const ID_MODE_FIRE_LINE_2_EDIT_CONTROL: u32 = 30174; + pub const ID_MODE_FIRE_LINE_2_LABEL_2: u32 = 30175; + pub const ID_MODE_FIRE_LINE_3_LABEL_1: u32 = 30176; + pub const ID_MODE_FIRE_LINE_3_SLIDER_CONTROL: u32 = 30177; + pub const ID_MODE_FIRE_LINE_3_EDIT_CONTROL: u32 = 30178; + pub const ID_MODE_FIRE_LINE_3_LABEL_2: u32 = 30179; + pub const ID_MAPPING_HELP_LEFT_SUBJECT_LABEL: u32 = 30180; + pub const ID_MAPPING_HELP_LEFT_CONTENT_LABEL: u32 = 30181; + pub const IDC_MAPPING_MATCHED_INDICATOR_TEXT: u32 = 30182; + pub const ID_MAPPING_HELP_RIGHT_SUBJECT_LABEL: u32 = 30183; + pub const ID_MAPPING_HELP_RIGHT_CONTENT_LABEL: u32 = 30184; + pub const IDC_BEEP_ON_SUCCESS_CHECK_BOX: u32 = 30185; + pub const ID_MAPPING_PANEL_PREVIOUS_BUTTON: u32 = 30186; + pub const ID_MAPPING_PANEL_OK: u32 = 30187; + pub const ID_MAPPING_PANEL_NEXT_BUTTON: u32 = 30188; + pub const IDC_MAPPING_ENABLED_CHECK_BOX: u32 = 30189; + pub const ID_MAPPING_ROW_PANEL: u32 = 30207; + pub const ID_MAPPING_ROW_MAPPING_LABEL: u32 = 30191; + pub const IDC_MAPPING_ROW_ENABLED_CHECK_BOX: u32 = 30192; + pub const ID_MAPPING_ROW_EDIT_BUTTON: u32 = 30193; + pub const ID_MAPPING_ROW_DUPLICATE_BUTTON: u32 = 30194; + pub const ID_MAPPING_ROW_REMOVE_BUTTON: u32 = 30195; + pub const ID_MAPPING_ROW_LEARN_SOURCE_BUTTON: u32 = 30196; + pub const ID_MAPPING_ROW_LEARN_TARGET_BUTTON: u32 = 30197; + pub const ID_MAPPING_ROW_CONTROL_CHECK_BOX: u32 = 30198; + pub const ID_MAPPING_ROW_FEEDBACK_CHECK_BOX: u32 = 30199; + pub const ID_MAPPING_ROW_SOURCE_LABEL_TEXT: u32 = 30200; + pub const ID_MAPPING_ROW_TARGET_LABEL_TEXT: u32 = 30201; + pub const ID_MAPPING_ROW_GROUP_LABEL: u32 = 30202; + pub const IDC_MAPPING_ROW_MATCHED_INDICATOR_TEXT: u32 = 30203; + pub const ID_UP_BUTTON: u32 = 30205; + pub const ID_DOWN_BUTTON: u32 = 30206; + pub const ID_MAPPING_ROWS_PANEL: u32 = 30210; + pub const ID_DISPLAY_ALL_GROUPS_BUTTON: u32 = 30208; + pub const ID_GROUP_IS_EMPTY_TEXT: u32 = 30209; + pub const ID_MESSAGE_PANEL: u32 = 30212; + pub const ID_MESSAGE_TEXT: u32 = 30211; + pub const ID_SHARED_GROUP_MAPPING_PANEL: u32 = 30228; + pub const ID_MAPPING_NAME_LABEL: u32 = 30213; + pub const ID_MAPPING_NAME_EDIT_CONTROL: u32 = 30214; + pub const ID_MAPPING_TAGS_LABEL: u32 = 30215; + pub const ID_MAPPING_TAGS_EDIT_CONTROL: u32 = 30216; + pub const ID_MAPPING_CONTROL_ENABLED_CHECK_BOX: u32 = 30217; + pub const ID_MAPPING_FEEDBACK_ENABLED_CHECK_BOX: u32 = 30218; + pub const ID_MAPPING_ACTIVATION_TYPE_LABEL: u32 = 30219; + pub const ID_MAPPING_ACTIVATION_TYPE_COMBO_BOX: u32 = 30220; + pub const ID_MAPPING_ACTIVATION_SETTING_1_LABEL_TEXT: u32 = 30221; + pub const ID_MAPPING_ACTIVATION_SETTING_1_BUTTON: u32 = 30222; + pub const ID_MAPPING_ACTIVATION_SETTING_1_CHECK_BOX: u32 = 30223; + pub const ID_MAPPING_ACTIVATION_SETTING_2_LABEL_TEXT: u32 = 30224; + pub const ID_MAPPING_ACTIVATION_SETTING_2_BUTTON: u32 = 30225; + pub const ID_MAPPING_ACTIVATION_SETTING_2_CHECK_BOX: u32 = 30226; + pub const ID_MAPPING_ACTIVATION_EDIT_CONTROL: u32 = 30227; + pub const ID_INSTANCE_PANEL: u32 = 30229; + pub const ID_MAIN_PANEL: u32 = 30237; + pub const ID_MAIN_PANEL_STATUS_1_TEXT: u32 = 30231; + pub const ID_MAIN_PANEL_STATUS_2_TEXT: u32 = 30232; + pub const IDC_UNIT_ENABLED_CHECK_BOX: u32 = 30233; + pub const IDC_UNIT_BUTTON: u32 = 30234; + pub const IDC_EDIT_TAGS_BUTTON: u32 = 30235; + pub const ID_MAIN_PANEL_VERSION_TEXT: u32 = 30236; + pub const ID_YAML_EDITOR_PANEL: u32 = 30242; + #[allow(dead_code)] + pub const ID_YAML_TEXT_EDITOR_BUTTON: u32 = 30238; + pub const ID_YAML_EDIT_CONTROL: u32 = 30239; + pub const ID_YAML_HELP_BUTTON: u32 = 30240; + pub const ID_YAML_EDIT_INFO_TEXT: u32 = 30241; + #[allow(dead_code)] + pub const ID_EMPTY_PANEL: u32 = 30243; + #[allow(dead_code)] + pub const ID_SETUP_PANEL: u32 = 30244; + pub const ID_SETUP_INTRO_TEXT_1: u32 = 30245; + pub const ID_SETUP_INTRO_TEXT_2: u32 = 30246; + pub const ID_SETUP_ADD_PLAYTIME_TOOLBAR_BUTTON: u32 = 30247; + pub const ID_SETUP_SEND_ERRORS_TO_DEV: u32 = 30248; + pub const ID_SETUP_SHOW_ERRORS_IN_CONSOLE: u32 = 30249; + pub const ID_SETUP_NOTIFY_ABOUT_UPDATES: u32 = 30250; + pub const ID_SETUP_COMMENT: u32 = 30251; + pub const ID_SETUP_TIP_TEXT: u32 = 30252; + pub const ID_SETUP_PANEL_OK: u32 = 30253; + pub const ID_COLOR_PANEL: u32 = 30254; + pub const ID_HIDDEN_PANEL: u32 = 30255; +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/clipboard.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/clipboard.rs new file mode 100644 index 0000000..67af77b --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/clipboard.rs @@ -0,0 +1,18 @@ +use anyhow::Context; +use arboard::Clipboard; +use once_cell::sync::Lazy; +use std::sync::Mutex; + +static CLIPBOARD: Lazy> = + Lazy::new(|| Mutex::new(Clipboard::new().expect("couldn't create clipboard"))); + +pub fn copy_text_to_clipboard(text: String) -> anyhow::Result<()> { + let mut clipboard = CLIPBOARD.lock().unwrap(); + clipboard.set_text(text).context("set clipboard contents")?; + Ok(()) +} + +pub fn get_text_from_clipboard() -> Option { + let mut clipboard = CLIPBOARD.lock().unwrap(); + clipboard.get_text().ok() +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/color_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/color_panel.rs new file mode 100644 index 0000000..8552268 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/color_panel.rs @@ -0,0 +1,101 @@ +use reaper_low::{raw, Swell}; +use reaper_medium::Hbrush; +use std::fmt::Debug; +use std::ptr::null_mut; + +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::util; +use crate::infrastructure::ui::util::colors::ColorPair; +use swell_ui::{ + DeviceContext, DialogScaling, DialogUnits, Dimensions, Point, SharedView, View, ViewContext, + Window, ZOrder, +}; + +/// A panel painted in a certain color and put below a specific section of the parent window. +/// +/// We use the color panel on macOS and Linux only because Windows doesn't seem to like +/// overlapping child windows, it flickers like hell. +#[derive(Debug)] +pub struct ColorPanel { + view: ViewContext, + desc: ColorPanelDesc, +} + +impl ColorPanel { + pub fn new(desc: ColorPanelDesc) -> Self { + Self { + view: Default::default(), + desc, + } + } + + /// Used on Windows only. + pub fn paint_manually(&self, device_context: DeviceContext, window: Window) { + let swell = Swell::get(); + let pos: Point<_> = window.convert_to_pixels(self.desc.scaled_position()); + let size: Dimensions<_> = window.convert_to_pixels(self.desc.scaled_size()); + let rc = raw::RECT { + left: pos.x.get() as _, + top: pos.y.get() as _, + right: (pos.x + size.width).get() as _, + bottom: (pos.y + size.height).get() as _, + }; + let brush = util::view::get_brush_for_color_pair(self.desc.color_pair) + .map(|b| b.as_ptr()) + .unwrap_or(null_mut()); + unsafe { + swell.FillRect(device_context.as_ptr(), &rc, brush); + } + } +} + +impl View for ColorPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_COLOR_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + window.set_everything_in_dialog_units( + self.desc.scaled_position(), + self.desc.scaled_size(), + ZOrder::Bottom, + ); + false + } + + fn control_color_dialog( + self: SharedView, + _device_context: DeviceContext, + _window: Window, + ) -> Option { + if !BackboneShell::get().config().background_colors_enabled() { + return None; + } + util::view::get_brush_for_color_pair(self.desc.color_pair) + } +} + +#[derive(Debug)] +pub struct ColorPanelDesc { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, + pub color_pair: ColorPair, + pub scaling: DialogScaling, +} + +impl ColorPanelDesc { + pub fn scaled_position(&self) -> Point { + Point::new(DialogUnits(self.x), DialogUnits(self.y)).scale(&self.scaling) + } + + pub fn scaled_size(&self) -> Dimensions { + Dimensions::new(DialogUnits(self.width), DialogUnits(self.height)).scale(&self.scaling) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/companion_app_presenter.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/companion_app_presenter.rs new file mode 100644 index 0000000..dfffa04 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/companion_app_presenter.rs @@ -0,0 +1,235 @@ +// This is is to prevent Clippy from complaining about the activation_class assignment in the +// Askama template. +#![allow(clippy::useless_let_if_seq)] +use askama::Template; + +use crate::application::{Affected, SharedUnitModel, UnitProp, WeakUnitModel}; +use crate::base::when; +use crate::infrastructure::plugin::BackboneShell; + +use qrcode::QrCode; + +use rxrust::prelude::*; + +use crate::infrastructure::ui::util::open_in_browser; +use std::cell::Cell; +use std::path::{Path, PathBuf}; +use std::rc::Rc; +use tracing::debug; + +#[derive(Debug)] +pub struct CompanionAppPresenter { + session: WeakUnitModel, + party_is_over_subject: LocalSubject<'static, (), ()>, + /// `true` as soon as requested within this ReaLearn session execution at least once + app_info_requested: Cell, +} + +impl CompanionAppPresenter { + pub fn new(session: WeakUnitModel) -> Rc { + let m = CompanionAppPresenter { + session, + party_is_over_subject: Default::default(), + app_info_requested: Cell::new(false), + }; + let shared = Rc::new(m); + shared.register_listeners(); + shared + } + + pub fn handle_affected(&self, affected: &Affected, _initiator: Option) { + use crate::application::Affected::One; + use crate::application::UnitProp::UnitKey; + match affected { + One(UnitKey) => { + if self.app_info_requested.get() { + self.update_app_info(); + } + } + _ => {} + } + } + + pub fn show_app_info(&self) { + self.app_info_requested.set(true); + let index_file = self.update_app_info(); + open_in_browser(&index_file.to_string_lossy()); + } + + fn update_app_info(&self) -> PathBuf { + let dir = BackboneShell::get_temp_dir().expect("app setup temp dir not lazily created"); + let session = self.session(); + let session = session.borrow(); + let app = BackboneShell::get(); + let server = app.server().borrow(); + let full_companion_app_url = + server.generate_full_companion_app_url(session.unit_key(), false); + let server_is_running = server.is_running(); + let qr_code_image_file_name = "qr-code.png"; + let (width, height) = self + .generate_qr_code( + &full_companion_app_url, + &dir.path().join(qr_code_image_file_name), + ) + .expect("couldn't generate QR code image file"); + let config = app.config(); + let state = AppSetupState { + server_is_running, + server_is_enabled: config.server_is_enabled(), + qr_code_image_uri: qr_code_image_file_name.to_string(), + qr_code_image_width: width, + qr_code_image_height: height, + companion_web_app_url: config.companion_web_app_url().to_string(), + full_companion_web_app_url: server + .generate_full_companion_app_url(session.unit_key(), true), + server_host: server + .local_ip() + .map(|ip| ip.to_string()) + .unwrap_or_else(|| "".to_string()), + server_http_port: server.http_port(), + server_https_port: server.https_port(), + session_id: session.unit_key().to_string(), + os: std::env::consts::OS, + }; + let index_file = dir.path().join("index.html"); + std::fs::write( + &index_file, + state + .render() + .expect("couldn't render app setup page template"), + ) + .expect("couldn't write app setup page to file"); + index_file + } + + fn session(&self) -> SharedUnitModel { + self.session.upgrade().expect("session gone") + } + + fn generate_qr_code(&self, content: &str, target_file: &Path) -> anyhow::Result<(u32, u32)> { + let code = QrCode::new(content)?; + type P = image::LumaA; + let min_size = 250; + let max_size = 500; + let image = code + .render::

() + // Background should be transparent + .light_color(image::LumaA([255, 0])) + .min_dimensions(min_size, min_size) + .max_dimensions(max_size, max_size) + .build(); + image.save(target_file)?; + Ok((image.width(), image.height())) + } + + fn register_listeners(self: &Rc) { + let app = BackboneShell::get(); + when( + app.changed() + .merge(app.server().borrow().changed()) + .take_until(self.party_is_over()), + ) + .with(Rc::downgrade(self)) + .do_async(move |view, _| { + if view.app_info_requested.get() { + view.update_app_info(); + } + }); + } + + fn party_is_over(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.party_is_over_subject.clone() + } +} + +impl Drop for CompanionAppPresenter { + fn drop(&mut self) { + debug!("Dropping mapping panel manager..."); + self.party_is_over_subject.next(()); + } +} + +#[derive(Template)] +#[template(path = "companion-app-setup.html")] +struct AppSetupState { + // Can change globally + server_is_running: bool, + // Can change globally + server_is_enabled: bool, + // Can change per session + qr_code_image_uri: String, + qr_code_image_width: u32, + qr_code_image_height: u32, + // Can't change at all + companion_web_app_url: String, + // Can change per session + full_companion_web_app_url: String, + // Can only change after restart + server_host: String, + // Can only change after restart + server_http_port: u16, + // Can only change after restart + server_https_port: u16, + // Can change per session + session_id: String, + // Can't change at all + os: &'static str, +} + +#[cfg(target_os = "windows")] +pub fn add_firewall_rule( + http_port: u16, + https_port: u16, + grpc_port: u16, +) -> Result<(), &'static str> { + fn add( + http_port: u16, + https_port: u16, + grpc_port: u16, + direction: &str, + ) -> Result<(), &'static str> { + let exit_status = runas::Command::new("netsh") + .args(&[ + "advfirewall", + "firewall", + "add", + "rule", + "name=ReaLearn Server", + "action=allow", + "protocol=TCP", + ]) + .arg(format!("dir={}", direction)) + .arg(format!( + "localport={},{},{}", + http_port, https_port, grpc_port + )) + .gui(true) + .show(false) + .status() + .map_err(|_| "couldn't execute command")?; + if !exit_status.success() { + return Err("command returned non-success exit code"); + } + Ok(()) + } + add(http_port, https_port, grpc_port, "in")?; + Ok(()) +} + +#[cfg(target_os = "macos")] +pub fn add_firewall_rule( + _http_port: u16, + _https_port: u16, + _grpc_port: u16, +) -> Result<(), &'static str> { + Err("not supported on macOS") +} + +#[cfg(target_os = "linux")] +pub fn add_firewall_rule( + _http_port: u16, + _https_port: u16, + _grpc_port: u16, +) -> Result<(), &'static str> { + Err("not supported on Linux") +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/control_transformation_templates.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/control_transformation_templates.rs new file mode 100644 index 0000000..035daa0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/control_transformation_templates.rs @@ -0,0 +1,147 @@ +use crate::infrastructure::ui::ControlStyle::{Button, RangeElement}; +use crate::infrastructure::ui::{ScriptTemplate, ScriptTemplateGroup}; + +pub const CONTROL_TRANSFORMATION_TEMPLATES: &[ScriptTemplateGroup] = &[ + ScriptTemplateGroup { + name: "Normal", + templates: &[ + ScriptTemplate { + name: "Reverse", + content: "y = 1 - x;", + description: r#"Simple formula which has the same effect as the "Reverse" checkbox."#, + control_styles: &[RangeElement, Button], + min_realearn_version: None, + }, + ScriptTemplate { + name: "Exponential curve", + content: "y = pow(x, 8);", + description: r#"Simple exponential curve."#, + control_styles: &[RangeElement], + min_realearn_version: None, + }, + ScriptTemplate { + name: "ReaComp Threshold linearization", + content: r#"// Parameters + +min_db = -120; +max_db = 6; + +// Code + +shift = -min_db; +compress = max_db - min_db; +y = (10.0 ^ ((compress * x - shift) / 20)) / 2"#, + description: r#"You can use this when controlling the ReaComp Threshold parameter to get a linear dB scale. This essentially inverses the logarithmic curve that ReaComp uses to interpret incoming parameter values. Probably also works with other logarithmic ReaPlug parameters."#, + control_styles: &[RangeElement], + min_realearn_version: None, + }, + ], + }, + ScriptTemplateGroup { + name: "Modulations", + templates: &[ + ScriptTemplate { + min_realearn_version: None, + name: "Sinus LFO", + description: r#"Modulate in sinus shape while button pressed."#, + control_styles: &[Button], + content: r#"// Parameters + +period = 2; + +// Code + +y = x > 0 ? ( + secs = rel_time / 1000; + scaled = secs * ($pi * 2) / period; + (sin(scaled - $pi / 2) + 1) / 2 +) : ( + stop +);"#, + }, + ScriptTemplate { + min_realearn_version: None, + name: "Global sinus LFO", + description: r#"Modulate in sinus shape while button pressed, continue when button pressed again."#, + control_styles: &[Button], + content: r#"// Parameters + +period = 2; + +// Code + +rel_time == 0 ? ( + time_offset += prev_rel_time; +); +prev_rel_time = rel_time; +global_time = time_offset + rel_time; +y = x > 0 ? ( + secs = rel_time / 1000; + scaled = secs * ($pi * 2) / period; + (sin(scaled - $pi / 2) + 1) / 2 +) : ( + stop +);"#, + }, + ], + }, + ScriptTemplateGroup { + name: "Transitions", + templates: &[ScriptTemplate { + min_realearn_version: None, + name: "Linear transition", + description: r#"Transition to incoming value (e.g. velocity of button press) within a certain amount of time."#, + control_styles: &[Button], + content: r#"// Parameters + +transition_time_in_ms = 1000; + +// Code + +y = abs(x - y) > 0.05 ? ( + x * min(rel_time / transition_time_in_ms, 1) +) : ( + stop +);"#, + }], + }, + ScriptTemplateGroup { + name: "Other", + templates: &[ + ScriptTemplate { + name: "Delayed button", + content: r#"// Parameters + +delay_in_ms = 1000; + +// Code + +y = rel_time < delay_in_ms ? none : stop(x);"#, + description: "Delays the press/release of a button by a fixed amount of time.", + control_styles: &[Button], + min_realearn_version: None, + }, + ScriptTemplate { + name: "Debounce", + content: r#"// Parameters + +timeout_in_ms = 200; + +// Code + +y = y != 1 ? ( + 1 +) : ( + rel_time < timeout_in_ms ? none : stop(0) +);"#, + description: "Turns target on as soon as you start moving the knob/fader.\ + Turns it off shortly after you stop moving. \ + Good for the mouse target to simulate a \ + mouse drag but also good with \"Track: Set automation touch state\" target to \ + implement automatic touch/release when you don't have a touch-sensitive fader at hand.", + control_styles: &[RangeElement], + min_realearn_version: None, + }, + ], + }, +]; diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/dialog_util.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/dialog_util.rs new file mode 100644 index 0000000..75f038c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/dialog_util.rs @@ -0,0 +1,30 @@ +use crate::application::SharedUnitModel; +use crate::domain::{CompartmentKind, GroupId}; +use reaper_high::Reaper; + +/// Attention: This blocks the thread but continues the event loop, so you shouldn't have +/// anything borrowed while calling this unless you want errors due to reentrancy. +pub fn prompt_for(caption: &str, initial_value: &str) -> Option { + let captions_csv = format!("{caption},separator=|,extrawidth=200"); + Reaper::get() + .medium_reaper() + .get_user_inputs("Helgobox", 1, captions_csv, initial_value, 256) + .map(|r| r.to_str().trim().to_owned()) +} + +pub fn add_group_via_dialog( + session: SharedUnitModel, + compartment: CompartmentKind, +) -> Result { + if let Some(name) = prompt_for("Group name", "") { + if name.trim().is_empty() { + return Err("empty group name"); + } + let id = session + .borrow_mut() + .add_group_with_default_values(compartment, name); + Ok(id) + } else { + Err("cancelled") + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/dialogs.cpp b/plugin-reaper-realearn/main/src/infrastructure/ui/dialogs.cpp new file mode 100644 index 0000000..6829377 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/dialogs.cpp @@ -0,0 +1,27 @@ +// This file handles dialog generation. This is necessary on Linux and Mac OS X to make Swell::CreateDialogParam() work +// with our UI created in ResEdit. This is still done using C++ because that mechanism is weird. There's no point +// in investing much effort into porting this to Rust. It will not offer any big benefit. + +// We want to use the SWELL functions offered by REAPER instead of compiling SWELL into our plug-in. +#define SWELL_PROVIDED_BY_APP + +// Some preparation for dialog generation. +#include "../../../../target/generated/resource.h" +#include "../../../lib/WDL/WDL/swell/swell.h" +// Make sure the following factors correspond to the ones in `units.rs` (function `effective_scale_factors`). +// Leave them at 1.0 if possible (we can do OS-specific scaling now right when generating the RC). +#ifdef __APPLE__ +#define SWELL_DLG_SCALE_AUTOGEN 1.6 +#define SWELL_DLG_SCALE_AUTOGEN_YADJ 0.95 +#else +#define SWELL_DLG_SCALE_AUTOGEN 1.9 +#define SWELL_DLG_SCALE_AUTOGEN_YADJ 0.92 +#endif +#include "../../../lib/WDL/WDL/swell/swell-dlggen.h" +#define CBS_HASSTRINGS 0 +#define WS_EX_LEFT +#define WC_COMBOBOX "ComboBox" +#define SS_WORDELLIPSIS 0 + +// This is the result of the dialog RC file conversion (via PHP script). +#include "../../../../target/generated/msvc.rc_mac_dlg" \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/advanced_script_editor.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/advanced_script_editor.rs new file mode 100644 index 0000000..e57ef37 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/advanced_script_editor.rs @@ -0,0 +1,344 @@ +use crate::domain::AdditionalTransformationInput; +use crate::infrastructure::ui::{ScriptEngine, ScriptTemplate, ScriptTemplateGroup}; +use base::{blocking_lock, NamedChannelSender, SenderToNormalThread}; +use derivative::Derivative; +use egui::plot::{Legend, MarkerShape, Plot, Points, VLine}; +use egui::{CentralPanel, Color32, RichText, ScrollArea, Ui}; +use egui::{Context, SidePanel, TextEdit}; +use helgoboss_learn::{ + AbstractTimestamp, TransformationInput, TransformationInputContext, TransformationInputEvent, + TransformationInstruction, TransformationOutput, UnitValue, +}; +use std::ptr; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +pub type Value = String; + +pub type SharedValue = Arc>; + +pub fn run_ui(ctx: &Context, state: &mut State) { + SidePanel::left("left-panel") + .default_width(ctx.available_rect().width() * 0.5) + .show(ctx, |ui| { + ui.horizontal(|ui| { + let response = ui.menu_button("Templates", |ui| { + for group in state.toolbox.script_template_groups { + ui.menu_button(group.name, |ui| { + for template in group.templates { + let response = ui.button(template.name); + if response.hovered() { + // Preview template + let template_changed = state + .template_in_preview + .as_ref() + .map(|t| !ptr::eq(t.template, template)) + .unwrap_or(true); + if template_changed { + let build_outcome = state.toolbox.build(template.content); + let template_in_preview = TemplateInPreview { + template, + build_outcome, + }; + state.template_in_preview = Some(template_in_preview); + } + } + if response.clicked() { + // Apply template + let mut content = String::new(); + content += "// "; + content += template.name; + if template.description.is_empty() { + content += "\n"; + } else { + content += ": "; + for (i, comment_line) in + template.description.lines().enumerate() + { + if i > 0 { + content += "// "; + } + content += comment_line; + content += "\n"; + } + }; + content += "\n"; + content += template.content; + *blocking_lock( + &state.shared_value, + "AdvancedScriptEditor run_ui 1", + ) = content; + state.invalidate_and_send(); + ui.close_menu(); + } + } + }); + } + }); + if response.response.clicked_elsewhere() { + // Menu closed + state.template_in_preview = None; + } + ui.hyperlink_to("Help", state.toolbox.help_url).clicked(); + }); + ScrollArea::vertical().show(ui, |ui| { + let response = { + let mut content = + blocking_lock(&state.shared_value, "AdvancedScriptEditor run_ui 2"); + let text_edit = TextEdit::multiline(&mut *content) + .code_editor() + // .desired_rows(10) + .lock_focus(true); + ui.add_sized(ui.available_size(), text_edit) + }; + if response.changed() { + state.invalidate_and_send(); + } + }); + }); + CentralPanel::default().show(ctx, |ui| { + if let Some(template_in_preview) = &state.template_in_preview { + // A template is being hovered. Show a preview! + // Description + ui.label(template_in_preview.template.description); + ui.label("Usable with:"); + for control_style in template_in_preview.template.control_styles { + ui.horizontal(|ui| { + ui.label("- "); + ui.label( + RichText::new(control_style.to_string()) + .color(ui.visuals().hyperlink_color), + ); + ui.label(format!(" ({})", control_style.examples())); + }); + } + // Plot preview + plot_build_outcome(ui, &template_in_preview.build_outcome); + } else { + // Plot our script + plot_build_outcome(ui, &state.last_build_outcome); + } + }); +} + +fn plot_build_outcome(ui: &mut Ui, build_outcome: &BuildOutcome) { + if !build_outcome.error.is_empty() { + ui.colored_label(ui.visuals().error_fg_color, &build_outcome.error); + return; + } + let mut plot = Plot::new("transformation_plot") + .allow_boxed_zoom(false) + .allow_drag(false) + .allow_scroll(false) + .allow_zoom(false) + .width(ui.available_width()) + .height(ui.available_height()) + .data_aspect(1.0) + .view_aspect(1.0) + .include_x(1.0) + .include_y(1.0) + .show_background(false) + .legend(Legend::default()); + if build_outcome.uses_time { + plot = plot.x_axis_formatter(|v, _| format!("{v}s")); + } + plot.show(ui, |plot_ui| { + let mut x = 0.0; + let mut prev_y = 0.0; + let mut normal_points = vec![]; + let mut none_points = vec![]; + let mut stop_points = vec![]; + // plot_ui.set_plot_bounds(PlotBounds::from_min_max([0.0, 0.0], [1.0, 1.0])); + for e in &build_outcome.plot_entries { + x = e.input; + let unit_output_value = e + .output + .extract_control_value(None) + .and_then(|v| v.to_unit_value().ok()); + prev_y = match (unit_output_value, e.output.instruction) { + (None, None) => { + none_points.push([x, prev_y]); + prev_y + } + (Some(v), None) => { + normal_points.push([x, v.get()]); + v.get() + } + (Some(v), Some(TransformationInstruction::Stop)) => { + stop_points.push([x, v.get()]); + v.get() + } + (None, Some(TransformationInstruction::Stop)) => { + stop_points.push([x, prev_y]); + prev_y + } + }; + } + let visuals = &plot_ui.ctx().style().visuals; + plot_ui.points( + Points::new(normal_points) + .color(visuals.hyperlink_color) + .name("Control"), + ); + plot_ui.points( + Points::new(none_points) + .color(Color32::from_white_alpha(1)) + .name("Nothing"), + ); + plot_ui.points( + Points::new(stop_points) + .shape(MarkerShape::Square) + .color(visuals.error_fg_color) + .filled(true) + .name("Stop") + .radius(6.0), + ); + if build_outcome.uses_time { + plot_ui.ctx().request_repaint(); + let time = plot_ui.ctx().input(|i| i.time); + let bar_color = if visuals.dark_mode { + Color32::LIGHT_GRAY + } else { + Color32::DARK_GRAY + }; + plot_ui.vline(VLine::new(time % x).color(bar_color)); + } + }); +} + +#[derive(Debug)] +pub struct State { + shared_value: SharedValue, + last_build_outcome: BuildOutcome, + template_in_preview: Option, + toolbox: Toolbox, +} + +#[derive(Debug)] +struct TemplateInPreview { + template: &'static ScriptTemplate, + build_outcome: BuildOutcome, +} + +#[derive(Debug, Default)] +struct BuildOutcome { + plot_entries: Vec, + uses_time: bool, + error: String, +} + +#[derive(Debug)] +struct PlotEntry { + input: f64, + output: TransformationOutput, +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Toolbox { + #[derivative(Debug = "ignore")] + pub engine: Box, + pub help_url: &'static str, + pub script_template_groups: &'static [ScriptTemplateGroup], + pub value_sender: SenderToNormalThread, +} + +/// How much time to cover in the plot for time-dependent scripts. +const MAX_TIME_IN_MILLIS: u32 = 10_000; +/// The rate of invocations of time-dependent scripts (per second). +const INVOCATION_RATE: u32 = 30; + +impl Toolbox { + fn build(&self, content: &str) -> BuildOutcome { + match self.engine.compile(content) { + Ok(script) => { + let uses_time = script.uses_time(); + let sample_count = if uses_time { + // One sample per invocation over 10 seconds + MAX_TIME_IN_MILLIS * INVOCATION_RATE / 1000 + } else { + // 101 samples from 0.0 to 1.0 + 101 + }; + let mut prev_y = UnitValue::MIN; + let mut plot_entries = vec![]; + let instant = Instant::now(); + for i in 0..sample_count { + let (x, rel_time_millis) = if uses_time { + (1.0, i * 1000 / INVOCATION_RATE) + } else { + (0.01 * i as f64, 0) + }; + let input = TransformationInput { + event: TransformationInputEvent { + input_value: x, + timestamp: instant.duration(), + }, + context: TransformationInputContext { + output_value: prev_y.get(), + rel_time: Duration::from_millis(rel_time_millis as u64), + }, + additional_input: AdditionalTransformationInput { y_last: 0.0 }, + }; + let Some(output) = script.evaluate(input).ok() else { + // No sample for that point + continue; + }; + let entry = PlotEntry { + input: if uses_time { + rel_time_millis as f64 / 1000.0 + } else { + x + }, + output, + }; + if let Some(v) = output + .extract_control_value(None) + .and_then(|v| v.to_unit_value().ok()) + { + prev_y = v; + } + plot_entries.push(entry); + if output.instruction == Some(TransformationInstruction::Stop) { + break; + } + } + BuildOutcome { + plot_entries, + uses_time, + error: "".to_string(), + } + } + Err(e) => BuildOutcome { + plot_entries: vec![], + uses_time: false, + error: e.to_string(), + }, + } + } +} + +impl State { + pub fn new(initial_value: Value, toolbox: Toolbox) -> Self { + let mut state = State { + shared_value: SharedValue::new(Mutex::new(initial_value)), + last_build_outcome: Default::default(), + template_in_preview: None, + toolbox, + }; + state.invalidate(); + state + } + + pub fn invalidate_and_send(&mut self) { + self.invalidate(); + self.toolbox + .value_sender + .send_complaining(self.shared_value.clone()); + } + + pub fn invalidate(&mut self) { + let content = blocking_lock(&self.shared_value, "AdvancedScriptEditor State 1"); + self.last_build_outcome = self.toolbox.build(&content); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/mod.rs new file mode 100644 index 0000000..37127c4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/mod.rs @@ -0,0 +1,108 @@ +use egui::{Context, Visuals}; +use fragile::Fragile; +use reaper_low::firewall; +use swell_ui::{SwellWindow, Window}; + +pub mod advanced_script_editor; +pub mod target_filter_panel; + +pub fn open( + window: Window, + title: impl Into, + state: S, + run_ui: impl Fn(&egui::Context, &mut S) + Send + Sync + 'static, +) { + let title = title.into(); + window.set_text(title.as_str()); + let window_size = window.client_size(); + #[cfg(windows)] + let (width, height, scale) = { + let dpi_factor = window.dpi_scaling_factor(); + let width = window_size.width.get() as f64 / dpi_factor; + let height = window_size.height.get() as f64 / dpi_factor; + let scale = baseview::WindowScalePolicy::ScaleFactor(dpi_factor); + (width, height, scale) + }; + #[cfg(not(windows))] + let (width, height, scale) = { + let width = window_size.width.get() as f64; + let height = window_size.height.get() as f64; + let scale = baseview::WindowScalePolicy::SystemScaleFactor; + (width, height, scale) + }; + let settings = baseview::WindowOpenOptions { + title, + size: baseview::Size::new(width, height), + scale, + gl_config: Some(Default::default()), + }; + let window = get_egui_parent_window(window); + // egui-baseview requires the state to be Send but we want to support state that is not Send, + // that's why we use Fragile, which checks that we access the state from the main thread only. + // + // It's slightly inconvenient to require Send because the Advanced Script Editor has state that + // contains `ScriptEditorInput` which uses a Box. We would have to make this + // Send and this would either require some restructuring (viable but too lazy now) or make mlua + // use the "send" feature, which would have a big effect on the Lua integration (that would be + // possible but would make everything harder for no win, because we currently don't want to use Lua in + // any other thread than the main thread anyway). + // Fortunately, at least on macOS and Windows, the Send requirement of egui-baseview seems to be + // not really necessary since the state is only being used from the main thread anyway. + // It think with baseview for X11, it's a different story. It seems to be running on its own thread + // there. But we don't have egui enabled on Linux anyway - exactly for that reason. Because that + // it's not the main thread there also causes other issues. I was hoping to one day write an egui + // integration for X11 that just uses the main thread. + let fragile_state = Fragile::new(state); + egui_baseview::EguiWindow::open_parented( + &window, + settings, + fragile_state, + |ctx: &egui::Context, _queue: &mut egui_baseview::Queue, _state: &mut Fragile| { + firewall(|| { + init_ui(ctx, Window::dark_mode_is_enabled()); + }); + }, + move |ctx: &egui::Context, _queue: &mut egui_baseview::Queue, state: &mut Fragile| { + firewall(|| { + run_ui(ctx, state.get_mut()); + }); + }, + ); +} + +fn get_egui_parent_window(window: Window) -> SwellWindow { + #[cfg(target_os = "linux")] + { + let x_bridge_window = swell_ui::XBridgeWindow::create(window).unwrap(); + SwellWindow::XBridge(x_bridge_window) + } + #[cfg(not(target_os = "linux"))] + { + SwellWindow::Normal(window) + } +} + +/// To be called in the `resized` callback of the container window. Takes care of resizing +/// the egui child window to exactly the same size as parent. Works at least on Windows. +pub fn on_parent_window_resize(parent: Window) -> bool { + #[cfg(windows)] + { + parent.resize_first_child_according_to_parent(); + true + } + #[cfg(not(windows))] + { + let _ = parent; + false + } +} + +fn init_ui(ctx: &Context, dark_mode_is_enabled: bool) { + let mut style: egui::Style = (*ctx.style()).clone(); + style.visuals = if dark_mode_is_enabled { + Visuals::dark() + } else { + Visuals::light() + }; + ctx.set_style(style); +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/target_filter_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/target_filter_panel.rs new file mode 100644 index 0000000..e9a5bd4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/egui_views/target_filter_panel.rs @@ -0,0 +1,79 @@ +use crate::domain::ReaperTargetType; +use base::hash_util::NonCryptoHashSet; +use base::{NamedChannelSender, SenderToNormalThread}; +use derivative::Derivative; +use egui::CentralPanel; +use egui::Context; +use helgobox_api::persistence::{LearnableTargetKind, TargetTouchCause}; +use strum::IntoEnumIterator; + +pub fn run_ui(ctx: &Context, state: &mut State) { + CentralPanel::default().show(ctx, |ui: &mut egui::Ui| { + let mut changed = false; + // Target types + ui.label("Consider target types:"); + ui.horizontal_wrapped(|ui| { + for kind in LearnableTargetKind::iter() { + let checked_initially = state.value.included_targets.contains(&kind); + let mut checked = checked_initially; + let target_type = ReaperTargetType::from_learnable_target_kind(kind); + ui.checkbox(&mut checked, target_type.definition().short_name); + if checked != checked_initially { + changed = true; + if checked { + state.value.included_targets.insert(kind); + } else { + state.value.included_targets.remove(&kind); + } + } + } + }); + ui.separator(); + // Touch cause + ui.label("Consider target invocations:"); + let initial_touch_cause = state.value.touch_cause; + egui::ComboBox::from_id_source("touch-cause") + .selected_text(state.value.touch_cause.to_string()) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_min_width(60.0); + for touch_cause in TargetTouchCause::iter() { + ui.selectable_value( + &mut state.value.touch_cause, + touch_cause, + touch_cause.to_string(), + ); + } + }); + if state.value.touch_cause != initial_touch_cause { + changed = true; + } + // Notify if something changed + if changed { + state.value_sender.send_complaining(state.value.clone()); + } + }); +} + +#[derive(Clone, Debug)] +pub struct Value { + pub included_targets: NonCryptoHashSet, + pub touch_cause: TargetTouchCause, +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct State { + value: Value, + #[derivative(Debug = "ignore")] + value_sender: SenderToNormalThread, +} + +impl State { + pub fn new(initial_value: Value, value_sender: SenderToNormalThread) -> Self { + Self { + value: initial_value, + value_sender, + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/group_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/group_panel.rs new file mode 100644 index 0000000..1ec71d5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/group_panel.rs @@ -0,0 +1,106 @@ +use crate::application::{ + Affected, CompartmentProp, GroupProp, UnitProp, WeakGroup, WeakUnitModel, +}; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::util::MAPPING_PANEL_SCALING; +use crate::infrastructure::ui::{ItemProp, MappingHeaderPanel}; +use reaper_low::raw; +use swell_ui::{DialogUnits, Point, SharedView, View, ViewContext, Window}; + +#[derive(Debug)] +pub struct GroupPanel { + view: ViewContext, + mapping_header_panel: SharedView, +} + +impl GroupPanel { + pub fn new(session: WeakUnitModel, group: WeakGroup) -> GroupPanel { + GroupPanel { + view: Default::default(), + mapping_header_panel: SharedView::new(MappingHeaderPanel::new( + session, + Point::new(DialogUnits(7), DialogUnits(5)).scale(&MAPPING_PANEL_SCALING), + Some(group), + )), + } + } + + pub fn handle_affected( + self: &SharedView, + affected: &Affected, + initiator: Option, + ) { + use Affected::*; + use CompartmentProp::*; + use UnitProp::*; + match affected { + One(InCompartment(_, One(InGroup(_, affected)))) => match affected { + Multiple => { + self.mapping_header_panel.invalidate_controls(); + } + One(prop) => { + use GroupProp as P; + match prop { + P::Name => { + self.mapping_header_panel + .invalidate_due_to_changed_prop(ItemProp::Name, initiator); + } + P::Tags => { + self.mapping_header_panel + .invalidate_due_to_changed_prop(ItemProp::Tags, initiator); + } + P::ControlIsEnabled => { + self.mapping_header_panel.invalidate_due_to_changed_prop( + ItemProp::ControlEnabled, + initiator, + ); + } + P::FeedbackIsEnabled => { + self.mapping_header_panel.invalidate_due_to_changed_prop( + ItemProp::FeedbackEnabled, + initiator, + ); + } + P::InActivationCondition(p) => match p { + Multiple => { + self.mapping_header_panel.invalidate_controls(); + } + One(p) => { + let item_prop = ItemProp::from_activation_condition_prop(p); + self.mapping_header_panel + .invalidate_due_to_changed_prop(item_prop, initiator); + } + }, + } + } + }, + _ => {} + } + } +} + +impl View for GroupPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_GROUP_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + self.mapping_header_panel.clone().open(window); + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + use root::*; + match resource_id { + // IDCANCEL is escape button + ID_GROUP_PANEL_OK | raw::IDCANCEL => { + self.close(); + } + _ => {} + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/header_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/header_panel.rs new file mode 100644 index 0000000..c6144b2 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/header_panel.rs @@ -0,0 +1,3403 @@ +use std::convert::TryInto; + +use std::rc::{Rc, Weak}; + +use rxrust::prelude::*; +use std::iter; + +use reaper_high::Reaper; + +use swell_ui::{DeviceContext, Pixels, Point, SharedView, View, ViewContext, WeakView, Window}; + +use crate::application::{ + reaper_supports_global_midi_filter, Affected, AutoLoadMode, CompartmentCommand, + CompartmentPresetManager, CompartmentPresetModel, CompartmentProp, FxId, FxPresetLinkConfig, + InstanceCommand, MakeFxNonStickyMode, MakeTrackNonStickyMode, MappingCommand, MappingModel, + PresetLinkMutator, SharedInstanceModel, SharedMapping, SharedUnitModel, UnitCommand, UnitProp, + WeakUnitModel, +}; +use crate::base::when; +use crate::domain::{ + convert_compartment_param_index_range_to_iter, Backbone, CompartmentKind, + CompartmentParamIndex, ControlInput, FeedbackOutput, GroupId, MessageCaptureEvent, OscDeviceId, + ParamSetting, ReaperTarget, StayActiveWhenProjectInBackground, COMPARTMENT_PARAMETER_COUNT, +}; +use crate::domain::{MidiControlInput, MidiDestination}; +use crate::infrastructure::data::{ + CommonCompartmentPresetManager, CommonPresetInfo, CompartmentModelData, + FileBasedMainPresetManager, MappingModelData, OscDevice, PresetFileType, PresetOrigin, + UnitData, +}; +use crate::infrastructure::plugin::{ + update_auto_units_async, warn_about_failed_server_start, BackboneShell, +}; + +use crate::infrastructure::ui::bindings::root; + +use crate::base::notification::{notify_processing_result, notify_user_about_anyhow_error}; +use crate::domain::ui_util::format_tags_as_csv; +use crate::infrastructure::api::convert::from_data::ConversionStyle; +use crate::infrastructure::api::convert::to_data; +use crate::infrastructure::ui::color_panel::{ColorPanel, ColorPanelDesc}; +use crate::infrastructure::ui::dialog_util::add_group_via_dialog; +use crate::infrastructure::ui::instance_panel::InstancePanel; +use crate::infrastructure::ui::menus::{ + build_compartment_preset_menu_entries, get_midi_input_device_list_label, + get_midi_output_device_list_label, get_osc_device_list_label, + menu_containing_compartment_presets, ControlInputMenuAction, FeedbackOutputMenuAction, + OscDeviceManagementAction, CONTROL_INPUT_MIDI_FX_INPUT_LABEL, FEEDBACK_OUTPUT_MIDI_FX_OUTPUT, + FEEDBACK_OUTPUT_NONE_LABEL, +}; +use crate::infrastructure::ui::stream_deck_tool::StreamDeckToolbarOptions; +use crate::infrastructure::ui::util::{ + close_child_panel_if_open, colors, open_child_panel, open_child_panel_dyn, open_in_browser, + open_in_file_manager, parse_tags_from_csv, view, HEADER_PANEL_SCALING, +}; +use crate::infrastructure::ui::{ + add_firewall_rule, copy_text_to_clipboard, deserialize_api_object_from_lua, + deserialize_data_object, deserialize_data_object_from_json, dry_run_lua_script, + get_text_from_clipboard, menus, serialize_data_object, serialize_data_object_to_json, + serialize_data_object_to_lua, stream_deck_tool, AppPage, DataObject, GroupFilter, GroupPanel, + IndependentPanelManager, LuaCompartmentCommonScriptEngine, MappingRowsPanel, PlainTextEngine, + ScriptEditorInput, SearchExpression, SerializationFormat, SharedIndependentPanelManager, + SharedMainState, SimpleScriptEditorPanel, SourceFilter, UntaggedDataObject, +}; +use crate::infrastructure::ui::{dialog_util, CompanionAppPresenter}; +use anyhow::{bail, Context}; +use helgobox_api::persistence::{Envelope, VirtualControlElementCharacter}; +use itertools::Itertools; +use reaper_medium::Hbrush; +use semver::Version; +use std::cell::{Cell, RefCell}; +use std::error::Error; +use std::net::Ipv4Addr; +use std::ops::{DerefMut, RangeInclusive}; +use strum::IntoEnumIterator; +use tracing::debug; + +const PARAM_BATCH_SIZE: u32 = 5; + +/// The upper part of the main panel, containing buttons such as "Add mapping". +#[derive(Debug)] +pub struct HeaderPanel { + view: ViewContext, + session: WeakUnitModel, + instance_panel: WeakView, + main_state: SharedMainState, + companion_app_presenter: Rc, + show_color_panel: SharedView, + panel_manager: Weak>, + group_panel: RefCell>>, + extra_panel: RefCell>>, + pot_browser_panel: RefCell>>, + is_invoked_programmatically: Cell, +} + +impl HeaderPanel { + pub fn new( + session: WeakUnitModel, + main_state: SharedMainState, + panel_manager: Weak>, + instance_panel: WeakView, + ) -> HeaderPanel { + HeaderPanel { + view: Default::default(), + session: session.clone(), + instance_panel, + main_state, + companion_app_presenter: CompanionAppPresenter::new(session), + show_color_panel: SharedView::new(ColorPanel::new(build_show_color_panel_desc())), + panel_manager, + group_panel: Default::default(), + extra_panel: Default::default(), + pot_browser_panel: Default::default(), + is_invoked_programmatically: false.into(), + } + } + + fn edit_compartment_notes(&self) { + let compartment = self.active_compartment(); + let session = self.session(); + let initial_notes = session.borrow().compartment_notes(compartment).to_owned(); + let weak_session = self.session.clone(); + let input = ScriptEditorInput { + initial_value: initial_notes, + engine: Box::new(PlainTextEngine), + help_url: "", + set_value: move |edited_notes| { + let weak_session = weak_session.clone(); + if let Some(session) = weak_session.upgrade() { + session.borrow_mut().change_with_notification( + UnitCommand::ChangeCompartment( + compartment, + CompartmentCommand::SetNotes(edited_notes), + ), + None, + weak_session, + ) + } + }, + }; + let editor = SimpleScriptEditorPanel::new(input); + self.open_extra_panel(editor); + } + + fn edit_instance_tags(&self) { + let instance_model = self.instance_model(); + let initial_tags_as_csv = format_tags_as_csv(instance_model.borrow().tags()); + // Show UI + let csv = Reaper::get().medium_reaper().get_user_inputs( + "ReaLearn", + 1, + "Tags,separator=|,extrawidth=200", + initial_tags_as_csv, + 1024, + ); + // Check if cancelled + let Some(csv) = csv else { + return; + }; + let tags = parse_tags_from_csv(csv.to_str()); + instance_model.borrow_mut().change_with_notification( + InstanceCommand::SetTags(tags), + None, + Rc::downgrade(&instance_model), + ); + } + + fn edit_compartment_common_lua(&self) { + let compartment = self.active_compartment(); + let session = self.session(); + let initial_notes = session + .borrow() + .compartment_common_lua(compartment) + .to_owned(); + let weak_session = self.session.clone(); + let input = ScriptEditorInput { + initial_value: initial_notes, + engine: Box::new(LuaCompartmentCommonScriptEngine::new()), + help_url: "", + set_value: move |edited_notes| { + let weak_session = weak_session.clone(); + if let Some(session) = weak_session.upgrade() { + session.borrow_mut().change_with_notification( + UnitCommand::ChangeCompartment( + compartment, + CompartmentCommand::SetCommonLua(edited_notes), + ), + None, + weak_session, + ) + } + }, + }; + let editor = SimpleScriptEditorPanel::new(input); + self.open_extra_panel(editor); + } + + fn open_extra_panel(&self, panel: impl View + 'static) { + open_child_panel_dyn(&self.extra_panel, panel, self.view.require_window()); + } + + fn close_open_child_panels(&self) { + close_child_panel_if_open(&self.group_panel); + close_child_panel_if_open(&self.extra_panel); + close_child_panel_if_open(&self.pot_browser_panel); + } + + pub fn handle_changed_midi_devices(&self) { + if !self.is_open() { + return; + } + self.invalidate_control_input_button(); + self.invalidate_feedback_output_button(); + } + + pub fn handle_affected(&self, affected: &Affected, initiator: Option) { + self.companion_app_presenter + .handle_affected(affected, initiator); + if !self.is_open() { + return; + } + use Affected::*; + use CompartmentProp::*; + use UnitProp::*; + match affected { + One(WantsKeyboardInput) => { + self.invalidate_control_input_button(); + } + One(StreamDeckDeviceId) => { + self.invalidate_control_input_button(); + } + One(InCompartment(compartment, One(InGroup(_, _)))) + if *compartment == self.active_compartment() => + { + self.invalidate_group_controls(); + } + One(InCompartment(compartment, One(Notes))) + if *compartment == self.active_compartment() => + { + self.invalidate_notes_button(); + } + _ => {} + } + if let Some(open_group_panel) = self.group_panel.borrow_mut().as_ref() { + open_group_panel.handle_affected(affected, initiator); + } + } + + fn instance_model(&self) -> SharedInstanceModel { + self.session().borrow().instance_model().clone() + } + + fn session(&self) -> SharedUnitModel { + self.session.upgrade().expect("session gone") + } + + /// If you know a function in this view can be invoked by something else than the dialog + /// process, wrap your function body with this. Basically all pub functions! + /// + /// This prevents edit control text change events fired by windows to be processed. + fn invoke_programmatically(&self, f: impl FnOnce()) { + self.is_invoked_programmatically.set(true); + scopeguard::defer! { self.is_invoked_programmatically.set(false); } + f(); + } + + fn active_compartment(&self) -> CompartmentKind { + self.main_state.borrow().active_compartment.get() + } + + fn active_group_id(&self) -> Option { + let group_filter = self + .main_state + .borrow() + .displayed_group_for_active_compartment()?; + Some(group_filter.group_id()) + } + + fn panel_manager(&self) -> SharedIndependentPanelManager { + self.panel_manager.upgrade().expect("panel manager gone") + } + + fn toggle_learn_many_mappings(&self) { + self.main_state.borrow_mut().stop_filter_learning(); + let session = self.session(); + if session.borrow().is_learning_many_mappings() { + session.borrow_mut().stop_learning_many_mappings(); + self.panel_manager().borrow().close_message_panel(); + } else { + let compartment = self.active_compartment(); + let control_element_type = match compartment { + CompartmentKind::Controller => match self.prompt_for_control_element_character() { + None => return, + Some(t) => t, + }, + CompartmentKind::Main => { + // Doesn't matter + VirtualControlElementCharacter::Multi + } + }; + session.borrow_mut().start_learning_many_mappings( + &session, + compartment, + self.active_group_id().unwrap_or_default(), + control_element_type, + ); + self.panel_manager().borrow().open_message_panel(); + } + } + + fn prompt_for_control_element_character(&self) -> Option { + let menu = { + use swell_ui::menu_tree::*; + anonymous_menu(vec![ + item( + "Multis (faders, knobs, encoders, ...)", + VirtualControlElementCharacter::Multi, + ), + item("Buttons", VirtualControlElementCharacter::Button), + ]) + }; + self.view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()) + } + + fn prompt_whether_to_open_projection_in_app(&self) -> Option { + let menu = { + use swell_ui::menu_tree::*; + anonymous_menu(vec![ + item("Open in browser (old)", false), + item("Open in app (new)", true), + ]) + }; + self.view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()) + } + + fn browse_presets(&self) { + let menu = { + let compartment = self.active_compartment(); + let session = self.session(); + let session = session.borrow(); + let active_preset_id = session.active_preset_id(compartment); + menu_containing_compartment_presets(compartment, active_preset_id) + }; + let result = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(selected_preset_id) = result { + self.update_preset(selected_preset_id); + } + } + + fn update_preset(&self, preset_id: Option) { + self.main_state.borrow_mut().stop_filter_learning(); + let session = self.session(); + let compartment = self.active_compartment(); + let compartment_is_dirty = session.borrow().compartment_or_preset_is_dirty(compartment); + if compartment_is_dirty + && !self + .view + .require_window() + .confirm("ReaLearn", COMPARTMENT_CHANGES_WARNING_TEXT) + { + self.invalidate_preset_browse_button(); + return; + } + let mut session = session.borrow_mut(); + match compartment { + CompartmentKind::Controller => { + session.activate_controller_preset(preset_id); + } + CompartmentKind::Main => session.activate_main_preset(preset_id), + }; + } + + fn add_group(&self) { + if let Ok(group_id) = self.add_group_internal() { + self.main_state + .borrow_mut() + .set_displayed_group_for_active_compartment(Some(GroupFilter(group_id))); + } + } + + fn add_group_internal(&self) -> Result { + add_group_via_dialog(self.session(), self.active_compartment()) + } + + fn add_mapping(&self) { + self.main_state.borrow_mut().clear_all_filters(); + self.session().borrow_mut().add_default_mapping( + self.active_compartment(), + self.active_group_id().unwrap_or_default(), + VirtualControlElementCharacter::Multi, + ); + } + + fn execute_osc_dev_management_action(&self, action: OscDeviceManagementAction) { + use OscDeviceManagementAction::*; + match action { + EditNewOscDevice => edit_new_osc_device(), + EditExistingOscDevice(dev_id) => edit_existing_osc_device(dev_id), + RemoveOscDevice(dev_id) => remove_osc_device(self.view.require_window(), dev_id), + ToggleOscDeviceControl(dev_id) => { + BackboneShell::get().do_with_osc_device(dev_id, |d| d.toggle_control()) + } + ToggleOscDeviceFeedback(dev_id) => { + BackboneShell::get().do_with_osc_device(dev_id, |d| d.toggle_feedback()) + } + ToggleOscDeviceBundles(dev_id) => BackboneShell::get() + .do_with_osc_device(dev_id, |d| d.toggle_can_deal_with_bundles()), + } + } + + fn open_main_menu(&self, location: Point) -> anyhow::Result<()> { + let app = BackboneShell::get(); + let pure_menu = { + use swell_ui::menu_tree::*; + let preset_link_manager = BackboneShell::get().preset_link_manager(); + let preset_link_manager = preset_link_manager.borrow(); + let main_preset_manager = BackboneShell::get().main_preset_manager(); + let main_preset_manager = main_preset_manager.borrow(); + let text_from_clipboard = Rc::new(get_text_from_clipboard().unwrap_or_default()); + let text_from_clipboard_clone = text_from_clipboard.clone(); + let app_is_open = self.instance_panel().app_instance_is_running(); + let data_object_from_clipboard = if text_from_clipboard.is_empty() { + None + } else { + deserialize_data_object_from_json(&text_from_clipboard).ok() + }; + let clipboard_could_contain_lua = + !text_from_clipboard.is_empty() && data_object_from_clipboard.is_none(); + let instance_shell = self.instance_panel().shell().unwrap(); + let session = self.session(); + let session = session.borrow(); + let compartment = self.active_compartment(); + let group_id = self.active_group_id(); + let last_relevant_focused_fx_id = Backbone::get() + .last_relevant_available_focused_fx(session.processor_context().containing_fx()) + .and_then(|fx| { + if fx.is_available() { + FxId::from_fx(&fx, true).ok() + } else { + None + } + }); + let entries = vec![ + // "View" scope + item( + "Copy listed mappings", + MainMenuAction::CopyListedMappingsAsJson, + ), + { + if let Some(DataObject::Mappings(env)) = data_object_from_clipboard { + item( + format!("Paste {} mappings (replace all in group)", env.value.len()), + MainMenuAction::PasteReplaceAllInGroup(env), + ) + } else { + disabled_item("Paste mappings (replace all in group)") + } + }, + menu( + "Modify multiple mappings", + vec![ + item( + "Auto-name listed mappings", + MainMenuAction::AutoNameListedMappings, + ), + item( + "Name listed mappings after source", + MainMenuAction::NameListedMappingsAfterSource, + ), + item( + "Make sources of all main mappings virtual", + MainMenuAction::MakeSourcesOfMainMappingsVirtual, + ), + item( + "Make targets of listed mappings sticky", + MainMenuAction::MakeTargetsOfListedMappingsSticky, + ), + menu( + "Make targets of listed mappings non-sticky", + MakeTrackNonStickyMode::iter() + .map(|track_mode| { + menu( + format!("With track \"{track_mode}\" ..."), + MakeFxNonStickyMode::iter() + .map(|fx_mode| item( + format!("... and FX \"{fx_mode}\""), + MainMenuAction::MakeTargetsOfListedMappingsNonSticky(track_mode, fx_mode) + )).collect(), + ) + }) + .collect(), + ), + ], + ), + menu( + "Move listed mappings to group", + iter::once(item( + "", + MainMenuAction::MoveListedMappingsToGroup(None), + )) + .chain(session.groups_sorted(compartment).map(move |g| { + let g = g.borrow(); + let g_id = g.id(); + item_with_opts( + g.to_string(), + ItemOpts { + enabled: group_id != Some(g_id), + checked: false, + }, + MainMenuAction::MoveListedMappingsToGroup(Some(g_id)), + ) + })) + .collect(), + ), + menu( + "Advanced", + vec![ + item( + "Copy listed mappings as Lua", + MainMenuAction::CopyListedMappingsAsLua(ConversionStyle::Minimal), + ), + item( + "Copy listed mappings as Lua (include default values)", + MainMenuAction::CopyListedMappingsAsLua( + ConversionStyle::IncludeDefaultValues, + ), + ), + item_with_opts( + "Paste from Lua (replace all in group)", + ItemOpts { + enabled: clipboard_could_contain_lua, + checked: false, + }, + MainMenuAction::PasteFromLuaReplaceAllInGroup(text_from_clipboard), + ), + item_with_opts( + "Dry-run Lua script from clipboard", + ItemOpts { + enabled: clipboard_could_contain_lua, + checked: false, + }, + MainMenuAction::DryRunLuaScript(text_from_clipboard_clone), + ), + // item_with_opts( + // "Freeze Playtime matrix", + // ItemOpts { + // enabled: has_clip_matrix, + // checked: false, + // }, + // MainMenuAction::FreezeClipMatrix, + // ), + ], + ), + labeled_separator(format!("Compartment-related ({compartment})")), + menu( + "Compartment parameters", + (0..COMPARTMENT_PARAMETER_COUNT / PARAM_BATCH_SIZE) + .map(|batch_index| { + let offset = + CompartmentParamIndex::try_from(batch_index * PARAM_BATCH_SIZE) + .unwrap(); + let inclusive_end = (offset + (PARAM_BATCH_SIZE - 1)).unwrap(); + let range = offset..=inclusive_end; + menu( + format!( + "Parameters {} - {}", + range.start().get() + 1, + range.end().get() + 1 + ), + convert_compartment_param_index_range_to_iter(&range) + .map(|i| { + let param_name = session + .params() + .compartment_params(compartment) + .get_parameter_name(i); + let range = range.clone(); + item( + format!("{param_name}..."), + MainMenuAction::EditCompartmentParameter( + compartment, + range, + ), + ) + }) + .collect(), + ) + }) + .collect(), + ), + menu( + PRESET_RELATED_MENU_LABEL, + vec![ + item( + build_create_compartment_preset_workspace_label(false), + MainMenuAction::CreateCompartmentPresetWorkspace, + ), + item( + build_create_compartment_preset_workspace_label(true), + MainMenuAction::CreateCompartmentPresetWorkspaceIncludingFactoryPresets, + ), + item("Open compartment preset folder", MainMenuAction::OpenCompartmentPresetFolder), + item( + "Reload all compartment presets from disk", + MainMenuAction::ReloadAllCompartmentPresets, + ), + ], + ), + menu( + "Compartment tools", + vec![ + menu( + "Convert toolbar to Stream Deck mappings", + ["Main toolbar".to_string()].into_iter().chain((0..32).map(|i| format!("Floating toolbar {}", i + 1))).map(|toolbar_name| { + item( + toolbar_name.clone(), + MainMenuAction::ConvertToolbarToStreamDeckMappings(toolbar_name) + ) + }).collect() + ) + ] + ), + item( + "Edit compartment-wide Lua code", + MainMenuAction::EditCompartmentWideLuaCode, + ), + labeled_separator("Unit-related"), + // Unit scope + menu( + "Unit options", + vec![ + item_with_opts( + "Match even inactive mappings", + ItemOpts { + enabled: true, + checked: session.match_even_inactive_mappings(), + }, + MainMenuAction::ToggleMatchEvenInactiveMappings, + ), + item_with_opts( + "Send feedback only if track armed", + if session.containing_fx_is_in_input_fx_chain() { + ItemOpts { + enabled: false, + checked: true, + } + } else { + ItemOpts { + enabled: true, + checked: session.send_feedback_only_if_armed.get(), + } + }, + MainMenuAction::ToggleSendFeedbackOnlyIfTrackArmed, + ), + item_with_opts( + "Reset feedback when releasing source", + ItemOpts { + enabled: true, + checked: session.reset_feedback_when_releasing_source.get(), + }, + MainMenuAction::ToggleResetFeedbackWhenReleasingSource, + ), + item_with_opts( + "Make unit superior", + ItemOpts { + enabled: true, + checked: session.lives_on_upper_floor.get(), + }, + MainMenuAction::ToggleUpperFloorMembership, + ), + item_with_opts( + "Use unit-wide FX-to-preset links only", + ItemOpts { + enabled: true, + checked: session.use_unit_preset_links_only(), + }, + MainMenuAction::ToggleUseUnitPresetLinksOnly, + ), + menu( + "Stay active when project in background", + StayActiveWhenProjectInBackground::iter() + .map(|option| { + item_with_opts( + option.to_string(), + ItemOpts { + enabled: true, + checked: session + .stay_active_when_project_in_background + .get() + == option, + }, + MainMenuAction::SetStayActiveWhenProjectInBackground( + option, + ), + ) + }) + .collect(), + ), + ], + ), + menu( + "Unit-wide FX-to-preset links", + generate_fx_to_preset_links_menu_entries( + last_relevant_focused_fx_id.as_ref(), + &main_preset_manager, + session.instance_preset_link_config(), + PresetLinkScope::Instance, + ), + ), + menu( + "Logging", + vec![ + item("Log debug info (now)", MainMenuAction::LogDebugInfo), + item_with_opts( + "Log real control messages", + ItemOpts { + enabled: true, + checked: session.real_input_logging_enabled.get(), + }, + MainMenuAction::ToggleRealInputLogging, + ), + item_with_opts( + "Log virtual control messages", + ItemOpts { + enabled: true, + checked: session.virtual_input_logging_enabled.get(), + }, + MainMenuAction::ToggleVirtualInputLogging, + ), + item_with_opts( + "Log target control", + ItemOpts { + enabled: true, + checked: session.target_control_logging_enabled.get(), + }, + MainMenuAction::ToggleTargetControlLogging, + ), + item_with_opts( + "Log virtual feedback messages", + ItemOpts { + enabled: true, + checked: session.virtual_output_logging_enabled.get(), + }, + MainMenuAction::ToggleVirtualOutputLogging, + ), + item_with_opts( + "Log real feedback messages", + ItemOpts { + enabled: true, + checked: session.real_output_logging_enabled.get(), + }, + MainMenuAction::ToggleRealOutputLogging, + ), + ], + ), + item("Send feedback now", MainMenuAction::SendFeedbackNow), + labeled_separator("Instance-related"), + // Instance scope + menu( + "Instance options", + vec![item_with_opts( + "Enable global control (auto units)", + ItemOpts { + enabled: true, + checked: instance_shell.settings().control.global_control_enabled, + }, + MainMenuAction::ToggleGlobalControl, + )], + ), + item("Instance tags...", MainMenuAction::EditInstanceTags), + item("Open Pot Browser", MainMenuAction::OpenPotBrowser), + item("Show App", MainMenuAction::ShowApp), + item_with_opts( + "Close App", + ItemOpts { + enabled: app_is_open, + checked: false, + }, + MainMenuAction::CloseApp, + ), + labeled_separator("Global"), + // Global scope + menu( + "User interface", + vec![ + item_with_opts( + "Background colors", + ItemOpts { + enabled: true, + checked: BackboneShell::get().config().background_colors_enabled(), + }, + MainMenuAction::ToggleBackgroundColors, + ), + ], + ), + menu( + "Server", + vec![ + item( + if BackboneShell::get().server_is_running() { + "Disable and stop!" + } else if BackboneShell::get().config().server_is_enabled() { + "Start! (currently enabled but failed to start)" + } else { + "Enable and start!" + }, + MainMenuAction::ToggleServer, + ), + item("Add firewall rule", MainMenuAction::AddFirewallRule), + item("Open app folder", MainMenuAction::OpenAppFolder), + ], + ), + menu( + "Global FX-to-preset links", + generate_fx_to_preset_links_menu_entries( + last_relevant_focused_fx_id.as_ref(), + &main_preset_manager, + preset_link_manager.config(), + PresetLinkScope::Global, + ), + ), + ]; + anonymous_menu(entries) + }; + let result = self + .view + .require_window() + .open_popup_menu(pure_menu, location) + .context("no entry selected")?; + match result { + MainMenuAction::None => {} + MainMenuAction::CopyListedMappingsAsJson => { + self.notify_user_on_anyhow_error(self.copy_listed_mappings_as_json()); + } + MainMenuAction::AutoNameListedMappings => self.auto_name_listed_mappings(), + MainMenuAction::NameListedMappingsAfterSource => { + self.named_listed_mappings_after_source() + } + MainMenuAction::MakeSourcesOfMainMappingsVirtual => { + self.make_sources_of_main_mappings_virtual() + } + MainMenuAction::MakeTargetsOfListedMappingsSticky => { + self.make_targets_of_listed_mappings_sticky() + } + MainMenuAction::MakeTargetsOfListedMappingsNonSticky(track_mode, fx_mode) => { + self.make_targets_of_listed_mappings_non_sticky(track_mode, fx_mode); + } + MainMenuAction::MoveListedMappingsToGroup(group_id) => { + self.notify_user_on_anyhow_error(self.move_listed_mappings_to_group(group_id)); + } + MainMenuAction::PasteReplaceAllInGroup(mapping_datas) => { + self.paste_replace_all_in_group(mapping_datas) + } + MainMenuAction::CopyListedMappingsAsLua(style) => { + self.copy_listed_mappings_as_lua(style)? + } + MainMenuAction::PasteFromLuaReplaceAllInGroup(text) => { + self.paste_from_lua_replace_all_in_group(&text); + } + MainMenuAction::DryRunLuaScript(text) => { + self.dry_run_lua_script(&text); + } + MainMenuAction::EditCompartmentParameter(compartment, range) => { + let _ = edit_compartment_parameter(self.session(), compartment, range); + } + MainMenuAction::ToggleGlobalControl => self.toggle_global_control(), + MainMenuAction::ToggleMatchEvenInactiveMappings => { + self.toggle_match_even_inactive_mappings() + } + MainMenuAction::ToggleRealInputLogging => self.toggle_real_input_logging(), + MainMenuAction::ToggleVirtualInputLogging => self.toggle_virtual_input_logging(), + MainMenuAction::ToggleRealOutputLogging => self.toggle_real_output_logging(), + MainMenuAction::ToggleVirtualOutputLogging => self.toggle_virtual_output_logging(), + MainMenuAction::ToggleTargetControlLogging => self.toggle_target_control_logging(), + MainMenuAction::ToggleSendFeedbackOnlyIfTrackArmed => { + self.toggle_send_feedback_only_if_armed() + } + MainMenuAction::ToggleResetFeedbackWhenReleasingSource => { + self.toggle_reset_feedback_when_releasing_source() + } + MainMenuAction::ToggleUpperFloorMembership => self.toggle_upper_floor_membership(), + MainMenuAction::SetStayActiveWhenProjectInBackground(option) => { + self.set_stay_active_when_project_in_background(option) + } + MainMenuAction::ToggleBackgroundColors => { + BackboneShell::get().toggle_background_colors(); + self.view.require_window().alert( + "Helgobox", + "You might need to restart REAPER for this to take effect.", + ); + } + MainMenuAction::ToggleServer => { + if app.server_is_running() { + app.stop_server_persistently(); + self.view.require_window().alert( + "Helgobox", + "Stopped projection server and permanently disabled it.", + ); + } else { + match app.start_server_persistently() { + Ok(_) => { + self.view + .require_window() + .alert("Helgobox", "Successfully started projection server and permanently enabled it."); + } + Err(info) => { + warn_about_failed_server_start(info); + } + }; + } + } + MainMenuAction::OpenAppFolder => self.open_app_folder(), + MainMenuAction::ToggleUseUnitPresetLinksOnly => { + self.toggle_use_unit_preset_links_only() + } + MainMenuAction::AddFirewallRule => { + let (http_port, https_port, grpc_port) = { + let server = app.server().borrow(); + (server.http_port(), server.https_port(), server.grpc_port()) + }; + let msg = match add_firewall_rule(http_port, https_port, grpc_port) { + Ok(_) => "Successfully added firewall rule.".to_string(), + Err(reason) => format!( + "Couldn't add firewall rule because {reason}. Please try to do it manually!", + ), + }; + self.view.require_window().alert("Helgobox", msg); + } + MainMenuAction::CreateCompartmentPresetWorkspace => { + self.create_compartment_preset_workspace(false) + } + MainMenuAction::CreateCompartmentPresetWorkspaceIncludingFactoryPresets => { + self.create_compartment_preset_workspace(true) + } + MainMenuAction::ReloadAllCompartmentPresets => self.reload_all_compartment_presets(), + MainMenuAction::EditCompartmentWideLuaCode => self.edit_compartment_common_lua(), + MainMenuAction::EditInstanceTags => self.edit_instance_tags(), + MainMenuAction::OpenPotBrowser => { + self.show_pot_browser(); + } + MainMenuAction::ShowApp => { + self.show_app(); + } + MainMenuAction::CloseApp => { + self.close_app(); + } + MainMenuAction::OpenCompartmentPresetFolder => self.open_compartment_preset_folder(), + MainMenuAction::SendFeedbackNow => self.session().borrow().send_all_feedback(), + MainMenuAction::LogDebugInfo => self.log_debug_info(), + MainMenuAction::EditPresetLinkFxId(scope, fx_id) => { + with_scoped_preset_link_mutator(scope, &self.session, |m| { + edit_preset_link_fx_id(m, fx_id); + }); + } + MainMenuAction::RemovePresetLink(scope, fx_id) => { + with_scoped_preset_link_mutator(scope, &self.session, |m| { + remove_preset_link(m, fx_id); + }); + } + MainMenuAction::LinkToPreset(scope, fx_id, preset_id) => { + with_scoped_preset_link_mutator(scope, &self.session, |m| { + link_to_preset(m, fx_id, preset_id); + }); + } + MainMenuAction::ConvertToolbarToStreamDeckMappings(toolbar_name) => { + self.notify_user_on_anyhow_error( + self.convert_toolbar_to_stream_deck_mappings(&toolbar_name), + ); + } + }; + Ok(()) + } + + fn open_help_menu(&self, location: Point) -> Result<(), &'static str> { + let pure_menu = { + use swell_ui::menu_tree::*; + let entries = vec![ + menu( + "ReaLearn", + vec![ + item("Helgobox Wiki (online)", HelpMenuAction::OpenHelgoboxWiki), + item( + "ReaLearn Reference for this version (PDF, offline)", + HelpMenuAction::OpenRealearnOfflineReference, + ), + item( + "ReaLearn Reference for latest version (online)", + HelpMenuAction::OpenRealearnOnlineReference, + ), + item( + "List of controllers", + HelpMenuAction::OpenRealearnControllerList, + ), + item("Forum", HelpMenuAction::OpenRealearnForum), + item("Website", HelpMenuAction::OpenRealearnWebsite), + ], + ), + item("Contact developer", HelpMenuAction::ContactDeveloper), + item("Donate", HelpMenuAction::Donate), + item("About", HelpMenuAction::OpenAboutPage), + ]; + anonymous_menu(entries) + }; + let result = self + .view + .require_window() + .open_popup_menu(pure_menu, location) + .ok_or("no entry selected")?; + match result { + HelpMenuAction::OpenRealearnOfflineReference => self.open_realearn_reference_offline(), + HelpMenuAction::OpenRealearnOnlineReference => self.open_realearn_reference_online(), + HelpMenuAction::OpenHelgoboxWiki => self.open_helgobox_wiki(), + HelpMenuAction::OpenRealearnControllerList => self.open_realearn_controller_list(), + HelpMenuAction::OpenRealearnForum => self.open_realearn_forum(), + HelpMenuAction::ContactDeveloper => self.contact_developer(), + HelpMenuAction::OpenRealearnWebsite => self.open_realearn_website(), + HelpMenuAction::OpenAboutPage => self.open_about_page(), + HelpMenuAction::Donate => self.donate(), + }; + Ok(()) + } + + fn copy_listed_mappings_as_json(&self) -> anyhow::Result<()> { + let data_object = self.get_listened_mappings_as_data_object(); + let json = serialize_data_object_to_json(data_object)?; + copy_text_to_clipboard(json)?; + Ok(()) + } + + fn copy_listed_mappings_as_lua(&self, conversion_style: ConversionStyle) -> anyhow::Result<()> { + let data_object = self.get_listened_mappings_as_data_object(); + let json = serialize_data_object_to_lua(data_object, conversion_style)?; + copy_text_to_clipboard(json)?; + Ok(()) + } + + fn get_listened_mappings_as_data_object(&self) -> DataObject { + let session = self.session(); + let session = session.borrow(); + let compartment = self.active_compartment(); + let compartment_in_session = session.compartment_in_unit(compartment); + let mapping_datas = self + .get_listened_mappings(compartment) + .iter() + .map(|m| MappingModelData::from_model(&m.borrow(), &compartment_in_session)) + .collect(); + DataObject::Mappings(BackboneShell::create_envelope(mapping_datas)) + } + + fn auto_name_listed_mappings(&self) { + self.named_listed_mappings( + |count| + format!( + "This clears the names of {count} mappings, which in turn makes them use the auto-generated name. Do you really want to continue?", + ), + |_| String::new(), + ); + } + + fn named_listed_mappings_after_source(&self) { + self.named_listed_mappings( + |count| { + format!( + "This modifies the names of {count} mappings. Do you really want to continue?", + ) + }, + |m| { + m.source_model + .to_string() + .lines() + .map(|l| l.to_string()) + .next() + .unwrap_or_default() + }, + ); + } + + fn named_listed_mappings( + &self, + get_confirmation_msg: impl FnOnce(usize) -> String, + get_name: impl Fn(&MappingModel) -> String, + ) { + let listed_mappings = self.get_listened_mappings(self.active_compartment()); + if listed_mappings.is_empty() { + return; + } + if !self + .view + .require_window() + .confirm("ReaLearn", get_confirmation_msg(listed_mappings.len())) + { + return; + } + let session = self.session(); + let mut session = session.borrow_mut(); + for m in listed_mappings { + let mut mapping = m.borrow_mut(); + let new_name = get_name(&mapping); + session.change_mapping_from_ui_expert( + &mut mapping, + MappingCommand::SetName(new_name), + None, + self.session.clone(), + ); + } + } + + fn make_sources_of_main_mappings_virtual(&self) { + if !self.view.require_window().confirm( + "ReaLearn", + "This will attempt to make the sources in the main compartment virtual by matching them with the sources in the controller compartment. Do you really want to continue?", + ) { + return; + } + let shared_session = self.session(); + let result = { + let mut session = shared_session.borrow_mut(); + session.virtualize_main_mappings() + }; + self.notify_user_on_error(result.map_err(|e| e.into())); + } + + fn make_targets_of_listed_mappings_sticky(&self) { + let compartment = self.active_compartment(); + let listed_mappings = self.get_listened_mappings(compartment); + if listed_mappings.is_empty() { + return; + } + if !self.view.require_window().confirm( + "ReaLearn", + format!( + "This will change the targets of {} mappings to use sticky track/FX/send selectors such as , and Particular. Do you really want to continue?", + listed_mappings.len() + ), + ) { + return; + } + let session = self.session(); + let mut session = session.borrow_mut(); + let context = session.extended_context(); + let errors: Vec<_> = listed_mappings + .iter() + .filter_map(|m| { + let mut m = m.borrow_mut(); + m.make_target_sticky(context).err().map(|e| { + format!( + "Couldn't make target of mapping {} sticky because {}", + m.effective_name(), + e + ) + }) + }) + .collect(); + session.mark_dirty(); + session.notify_everything_has_changed(); + if !errors.is_empty() { + notify_processing_result("Errors occurred when making targets sticky", errors); + } + } + + fn make_targets_of_listed_mappings_non_sticky( + &self, + track_mode: MakeTrackNonStickyMode, + fx_mode: MakeFxNonStickyMode, + ) { + let compartment = self.active_compartment(); + let listed_mappings = self.get_listened_mappings(compartment); + if listed_mappings.is_empty() { + return; + } + if !self.view.require_window().confirm( + "ReaLearn", + format!( + "This will modify the targets of {} mappings, wherever applicable. Do you really want to continue?", + listed_mappings.len() + ), + ) { + return; + } + let session = self.session(); + let mut session = session.borrow_mut(); + let context = session.extended_context(); + for m in &listed_mappings { + let mut m = m.borrow_mut(); + m.make_target_non_sticky(context, track_mode, fx_mode); + } + session.mark_dirty(); + session.notify_everything_has_changed(); + } + + fn move_listed_mappings_to_group(&self, group_id: Option) -> anyhow::Result<()> { + let group_id = group_id + .or_else(|| self.add_group_internal().ok()) + .context("no group selected")?; + let compartment = self.active_compartment(); + let listed_mappings = self.get_listened_mappings(compartment); + if listed_mappings.is_empty() { + bail!("mapping list empty"); + } + if !self.view.require_window().confirm( + "ReaLearn", + format!( + "Do you really want to move {} mappings to the specified group?", + listed_mappings.len() + ), + ) { + bail!("cancelled"); + } + let session = self.session(); + let mut session = session.borrow_mut(); + let mapping_ids: Vec<_> = listed_mappings + .into_iter() + .map(|m| m.borrow().id()) + .collect(); + session.move_mappings_to_group( + compartment, + &mapping_ids, + group_id, + self.session.clone(), + )?; + Ok(()) + } + + fn get_listened_mappings(&self, compartment: CompartmentKind) -> Vec { + let main_state = self.main_state.borrow(); + let session = self.session(); + let session = session.borrow(); + MappingRowsPanel::filtered_mappings(&session, &main_state, compartment, false) + .cloned() + .collect() + } + + fn paste_from_lua_replace_all_in_group(&self, text: &str) { + let result = self.paste_from_lua_replace_all_in_group_internal(text); + self.notify_user_on_error(result); + } + + fn dry_run_lua_script(&self, text: &str) { + let result = dry_run_lua_script(text, self.active_compartment()); + self.notify_user_on_anyhow_error(result); + } + + fn paste_from_lua_replace_all_in_group_internal( + &self, + text: &str, + ) -> Result<(), Box> { + let active_compartment = self.active_compartment(); + let api_object = deserialize_api_object_from_lua(text, active_compartment)?; + let api_mappings = api_object + .into_mappings() + .ok_or("Can only paste a list of mappings into a mapping group.")?; + let data_mappings = { + let session = self.session(); + let session = session.borrow(); + let compartment_in_session = session.compartment_in_unit(active_compartment); + DataObject::try_from_api_mappings(api_mappings.value, &compartment_in_session)? + }; + self.paste_replace_all_in_group(Envelope::new(api_mappings.version, data_mappings)); + Ok(()) + } + + // https://github.com/rust-lang/rust-clippy/issues/6066 + #[allow(clippy::needless_collect)] + fn paste_replace_all_in_group(&self, mapping_datas: Envelope>) { + let main_state = self.main_state.borrow(); + let group_id = main_state + .displayed_group_for_active_compartment() + .map(|f| f.group_id()) + .unwrap_or_default(); + let compartment = main_state.active_compartment.get(); + let session = self.session(); + let mut session = session.borrow_mut(); + let group_key = if let Some(group) = + session.find_group_by_id_including_default_group(compartment, group_id) + { + group.borrow().key().clone() + } else { + return; + }; + let mapping_models: Result, _> = mapping_datas + .value + .into_iter() + .map(|mut data| { + data.id = None; + data.group_id = group_key.clone(); + data.to_model( + compartment, + &session.compartment_in_unit(compartment), + Some(session.extended_context()), + mapping_datas.version.as_ref(), + ) + }) + .collect(); + match mapping_models { + Ok(mapping_models) => { + session.replace_mappings_of_group(compartment, group_id, mapping_models.into_iter()) + } + Err(e) => self.notify_user_about_error(e.into()), + } + } + + fn toggle_learn_source_filter(&self) { + let mut main_state = self.main_state.borrow_mut(); + let active_compartment = main_state.active_compartment.get(); + let learning = &mut main_state.is_learning_source_filter; + if learning.get() { + // Stop learning + learning.set(false); + } else { + // Start learning + learning.set(true); + let main_state_1 = self.main_state.clone(); + let main_state_2 = self.main_state.clone(); + when( + self.session() + .borrow() + .incoming_msg_captured( + true, + active_compartment != CompartmentKind::Controller, + None, + ) + .take_until(learning.changed_to(false)) + .take_until(self.view.closed()), + ) + .with(self.session.clone()) + .finally(move |_| { + main_state_1 + .borrow_mut() + .is_learning_source_filter + .set(false); + }) + .do_async(move |session, capture_event: MessageCaptureEvent| { + let virtual_source_value = if capture_event.allow_virtual_sources { + if let Some(virtualization) = session + .borrow() + .virtualize_source_value(capture_event.result.message()) + { + if !virtualization.learnable { + return; + } + Some(virtualization.virtual_source_value) + } else { + None + } + } else { + None + }; + let filter = SourceFilter { + message_capture_result: capture_event.result, + virtual_source_value, + }; + main_state_2.borrow_mut().source_filter.set(Some(filter)); + }); + } + } + + fn toggle_learn_target_filter(&self) { + let mut main_state = self.main_state.borrow_mut(); + let learning = &mut main_state.is_learning_target_filter; + if learning.get() { + // Stop learning + learning.set(false); + } else { + // Start learning + learning.set(true); + when( + ReaperTarget::touched() + .take_until(learning.changed_to(false)) + .take_until(self.view.closed()) + .take(1), + ) + .with(Rc::downgrade(&self.main_state)) + .finally(|main_state| { + main_state.borrow_mut().is_learning_target_filter.set(false); + }) + .do_sync(|main_state, target| { + main_state + .borrow_mut() + .target_filter + .set(Some((*target).clone())); + }); + } + } + + fn clear_source_filter(&self) { + let mut main_state = self.main_state.borrow_mut(); + main_state.clear_source_filter(); + // When clearing the source filter while still learning, we want the learning to stop, too. + let learning = &mut main_state.is_learning_source_filter; + if learning.get() { + // Stop learning + learning.set(false); + } + } + + fn clear_target_filter(&self) { + self.main_state.borrow_mut().clear_target_filter(); + } + + fn clear_search_expression(&self) { + self.main_state + .borrow_mut() + .clear_search_expression_filter(); + } + + fn update_let_matched_events_through(&self) { + self.session().borrow_mut().let_matched_events_through.set( + self.view + .require_control(root::ID_LET_MATCHED_EVENTS_THROUGH_CHECK_BOX) + .is_checked(), + ); + } + + fn update_let_unmatched_events_through(&self) { + self.session() + .borrow_mut() + .let_unmatched_events_through + .set( + self.view + .require_control(root::ID_LET_UNMATCHED_EVENTS_THROUGH_CHECK_BOX) + .is_checked(), + ); + } + + // TODO-high-playtime-after-release As soon as we implement this, we need to fix the clippy error. + #[allow(clippy::await_holding_refcell_ref)] + #[allow(dead_code)] + fn freeze_clip_matrix(&self) { + #[cfg(feature = "playtime")] + { + let weak_session = self.session.clone(); + base::Global::future_support().spawn_in_main_thread_from_main_thread(async move { + let shared_session = weak_session.upgrade().expect("session gone"); + let instance = shared_session.borrow().instance().clone(); + instance + .borrow_mut() + .clip_matrix_mut() + .expect("this instance has no Playtime matrix") + .freeze() + .await; + Ok(()) + }); + } + } + + fn toggle_send_feedback_only_if_armed(&self) { + self.session() + .borrow_mut() + .send_feedback_only_if_armed + .set_with(|prev| !*prev); + } + + fn set_stay_active_when_project_in_background(&self, value: StayActiveWhenProjectInBackground) { + self.session() + .borrow_mut() + .stay_active_when_project_in_background + .set(value); + } + + fn toggle_reset_feedback_when_releasing_source(&self) { + self.session() + .borrow_mut() + .reset_feedback_when_releasing_source + .set_with(|prev| !*prev); + } + + fn toggle_global_control(&self) { + self.instance_panel() + .shell() + .unwrap() + .toggle_global_control(); + } + + fn toggle_match_even_inactive_mappings(&self) { + if let Some(session) = self.session.clone().upgrade() { + let mut session = session.borrow_mut(); + let current_value = session.match_even_inactive_mappings(); + session.change_with_notification( + UnitCommand::SetMatchEvenInactiveMappings(!current_value), + None, + self.session.clone(), + ); + } + } + + fn toggle_real_input_logging(&self) { + self.session() + .borrow_mut() + .real_input_logging_enabled + .set_with(|prev| !*prev); + } + + fn toggle_virtual_input_logging(&self) { + self.session() + .borrow_mut() + .virtual_input_logging_enabled + .set_with(|prev| !*prev); + } + + fn toggle_real_output_logging(&self) { + self.session() + .borrow_mut() + .real_output_logging_enabled + .set_with(|prev| !*prev); + } + + fn toggle_virtual_output_logging(&self) { + self.session() + .borrow_mut() + .virtual_output_logging_enabled + .set_with(|prev| !*prev); + } + + fn toggle_target_control_logging(&self) { + self.session() + .borrow_mut() + .target_control_logging_enabled + .set_with(|prev| !*prev); + } + + fn toggle_use_unit_preset_links_only(&self) { + let session = self.session(); + let mut session = session.borrow_mut(); + let new_state = !session.use_unit_preset_links_only(); + session.set_use_unit_preset_links_only(new_state); + } + + fn toggle_upper_floor_membership(&self) { + let enabled = { + let session = self.session(); + let mut session = session.borrow_mut(); + let new_state = !session.lives_on_upper_floor.get(); + session.lives_on_upper_floor.set(new_state); + new_state + }; + if enabled { + let msg = "This ReaLearn unit is now superior. When this unit is active (contains active main mappings), it will disable other ReaLearn units with the same control input and/or feedback output that don't have this setting turned on."; + self.view.require_window().alert("ReaLearn", msg); + }; + } + + fn fill_all_controls(&self) { + self.fill_preset_auto_load_mode_combo_box(); + } + + fn invalidate_all_controls(&self) { + self.invalidate_control_input_button(); + self.invalidate_feedback_output_button(); + self.invalidate_compartment_combo_box(); + self.invalidate_preset_controls(); + self.invalidate_group_controls(); + self.invalidate_let_through_controls(); + self.invalidate_source_filter_buttons(); + self.invalidate_target_filter_buttons(); + self.invalidate_add_one_button(); + self.invalidate_learn_many_button(); + self.invalidate_notes_button(); + } + + fn invalidate_let_through_controls(&self) { + let label = self.view.require_control(root::ID_LET_THROUGH_LABEL_TEXT); + let matched_box = self + .view + .require_control(root::ID_LET_MATCHED_EVENTS_THROUGH_CHECK_BOX); + let unmatched_box = self + .view + .require_control(root::ID_LET_UNMATCHED_EVENTS_THROUGH_CHECK_BOX); + let session = self.session(); + let session = session.borrow(); + let controls = [label, matched_box, unmatched_box]; + let (visible, enabled) = match session.control_input() { + ControlInput::Midi(input) => ( + true, + input == MidiControlInput::FxInput || reaper_supports_global_midi_filter(), + ), + ControlInput::Osc(_) => (false, false), + }; + for c in controls { + c.set_visible(visible); + } + if visible { + for c in controls { + c.set_enabled(enabled); + } + matched_box.set_checked(session.let_matched_events_through.get()); + unmatched_box.set_checked(session.let_unmatched_events_through.get()); + } + } + + fn invalidate_compartment_combo_box(&self) { + let controller_radio = self + .view + .require_control(root::ID_CONTROLLER_COMPARTMENT_RADIO_BUTTON); + let main_radio = self + .view + .require_control(root::ID_MAIN_COMPARTMENT_RADIO_BUTTON); + match self.active_compartment() { + CompartmentKind::Controller => { + controller_radio.check(); + main_radio.uncheck(); + } + CompartmentKind::Main => { + controller_radio.uncheck(); + main_radio.check() + } + }; + } + + fn invalidate_preset_auto_load_mode_combo_box(&self) { + let label = self.view.require_control(root::ID_AUTO_LOAD_LABEL_TEXT); + let combo = self.view.require_control(root::ID_AUTO_LOAD_COMBO_BOX); + if self.active_compartment() == CompartmentKind::Main { + label.show(); + combo.show(); + combo.select_combo_box_item_by_index( + self.session().borrow().auto_load_mode.get().into(), + ); + } else { + label.hide(); + combo.hide(); + } + } + + fn invalidate_group_controls(&self) { + self.invalidate_group_combo_box(); + self.invalidate_group_buttons(); + } + + fn invalidate_group_combo_box(&self) { + self.fill_group_combo_box(); + self.invalidate_group_combo_box_value(); + } + + fn fill_group_combo_box(&self) { + let combo = self.view.require_control(root::ID_GROUP_COMBO_BOX); + let vec = vec![(-1isize, "".to_string())]; + let compartment = self.active_compartment(); + combo.fill_combo_box_with_data_small( + vec.into_iter().chain( + self.session() + .borrow() + .groups_sorted(compartment) + .enumerate() + .map(|(i, g)| (i as isize, g.borrow().to_string())), + ), + ); + } + + fn invalidate_group_combo_box_value(&self) { + let combo = self.view.require_control(root::ID_GROUP_COMBO_BOX); + let enabled = !self.mappings_are_read_only(); + let compartment = self.active_compartment(); + let data = match self + .main_state + .borrow() + .displayed_group_for_active_compartment() + { + None => -1isize, + Some(GroupFilter(id)) => { + match self + .session() + .borrow() + .find_group_index_by_id_sorted(compartment, id) + { + None => { + combo.select_new_combo_box_item(format!(" ({id})")); + return; + } + Some(i) => i as isize, + } + } + }; + combo.select_combo_box_item_by_data(data).unwrap(); + combo.set_enabled(enabled); + } + + fn invalidate_group_buttons(&self) { + let add_button = self.view.require_control(root::ID_GROUP_ADD_BUTTON); + let remove_button = self.view.require_control(root::ID_GROUP_DELETE_BUTTON); + let edit_button = self.view.require_control(root::ID_GROUP_EDIT_BUTTON); + let (add_enabled, remove_enabled, edit_enabled) = if self.mappings_are_read_only() { + (false, false, false) + } else { + match self + .main_state + .borrow() + .displayed_group_for_active_compartment() + { + None => (true, false, false), + Some(GroupFilter(id)) if id.is_default() => (true, false, true), + _ => (true, true, true), + } + }; + add_button.set_enabled(add_enabled); + remove_button.set_enabled(remove_enabled); + edit_button.set_enabled(edit_enabled); + } + + fn invalidate_preset_controls(&self) { + self.invalidate_preset_label_text(); + self.invalidate_preset_browse_button(); + self.invalidate_preset_buttons(); + self.invalidate_preset_auto_load_mode_combo_box(); + } + + fn invalidate_preset_label_text(&self) { + let text = match self.active_compartment() { + CompartmentKind::Controller => "Controller preset", + CompartmentKind::Main => "Main preset", + }; + self.view + .require_control(root::ID_PRESET_LABEL_TEXT) + .set_text(text); + } + + fn invalidate_preset_buttons(&self) { + let save_button = self.view.require_control(root::ID_PRESET_SAVE_BUTTON); + let save_as_button = self.view.require_control(root::ID_PRESET_SAVE_AS_BUTTON); + let delete_button = self.view.require_control(root::ID_PRESET_DELETE_BUTTON); + let (save_button_enabled, save_as_button_enabled, delete_button_enabled) = { + if self.mappings_are_read_only() { + (false, false, false) + } else { + let session = self.session(); + let session = session.borrow(); + let compartment = self.active_compartment(); + let user_preset_is_active_and_exists = + if let Some(preset_id) = session.active_preset_id(compartment) { + BackboneShell::get() + .compartment_preset_manager(compartment) + .borrow() + .common_preset_info_by_id(preset_id) + .is_some_and(|info| info.origin.is_user()) + } else { + false + }; + let preset_is_dirty = session.compartment_or_preset_is_dirty(compartment); + ( + user_preset_is_active_and_exists && preset_is_dirty, + true, + user_preset_is_active_and_exists, + ) + } + }; + save_button.set_enabled(save_button_enabled); + save_as_button.set_enabled(save_as_button_enabled); + delete_button.set_enabled(delete_button_enabled); + } + + fn invalidate_preset_browse_button(&self) { + let button = self.view.require_control(root::ID_PRESET_BROWSE_BUTTON); + let enabled = !self.mappings_are_read_only(); + let session = self.session(); + let session = session.borrow(); + let compartment = self.active_compartment(); + let preset_manager = BackboneShell::get().compartment_preset_manager(compartment); + let text = match session.active_preset_id(compartment) { + None => "".to_string(), + Some(id) => match preset_manager.borrow().common_preset_info_by_id(id) { + None => { + format!(" ({id})") + } + Some(info) => info.meta_data.name.clone(), + }, + }; + button.set_text(text); + button.set_enabled(enabled); + } + + fn fill_preset_auto_load_mode_combo_box(&self) { + self.view + .require_control(root::ID_AUTO_LOAD_COMBO_BOX) + .fill_combo_box_indexed(AutoLoadMode::iter()); + } + + fn invalidate_control_input_button(&self) { + let unit_model = self.session(); + let unit = unit_model.borrow(); + let mut text = match unit.control_input() { + ControlInput::Midi(midi_control_input) => match midi_control_input { + MidiControlInput::FxInput => CONTROL_INPUT_MIDI_FX_INPUT_LABEL.to_string(), + MidiControlInput::Device(dev_id) => { + let dev = Reaper::get().midi_input_device_by_id(dev_id); + get_midi_input_device_list_label(dev) + } + }, + ControlInput::Osc(osc_device_id) => get_osc_dev_list_label(&osc_device_id, false), + }; + if unit.wants_keyboard_input() { + text.insert_str(0, "[Keyboard] + "); + } + if unit.stream_deck_device_id().is_some() { + text.insert_str(0, "[Stream Deck] + "); + } + self.view + .require_control(root::ID_CONTROL_INPUT_BUTTON) + .set_text(text); + } + + fn invalidate_feedback_output_button(&self) { + let text = match self.session().borrow().feedback_output() { + None => FEEDBACK_OUTPUT_NONE_LABEL.to_string(), + Some(FeedbackOutput::Midi(midi_dest)) => match midi_dest { + MidiDestination::FxOutput => FEEDBACK_OUTPUT_MIDI_FX_OUTPUT.to_string(), + MidiDestination::Device(dev_id) => { + let dev = Reaper::get().midi_output_device_by_id(dev_id); + get_midi_output_device_list_label(dev) + } + }, + Some(FeedbackOutput::Osc(osc_device_id)) => { + get_osc_dev_list_label(&osc_device_id, true) + } + }; + self.view + .require_control(root::ID_FEEDBACK_OUTPUT_BUTTON) + .set_text(text); + } + + fn update_search_expression(&self) { + let ec = self + .view + .require_control(root::ID_HEADER_SEARCH_EDIT_CONTROL); + let text = ec.text().unwrap_or_else(|_| "".to_string()); + self.main_state + .borrow_mut() + .search_expression + .set_with_initiator( + SearchExpression::new(&text), + Some(root::ID_HEADER_SEARCH_EDIT_CONTROL), + ); + } + + fn invalidate_search_expression(&self, initiator: Option) { + let main_state = self.main_state.borrow(); + let search_expression = main_state.search_expression.get_ref().to_string(); + self.view + .require_control(root::ID_CLEAR_SEARCH_BUTTON) + .set_enabled(!search_expression.is_empty()); + if initiator != Some(root::ID_HEADER_SEARCH_EDIT_CONTROL) { + self.view + .require_control(root::ID_HEADER_SEARCH_EDIT_CONTROL) + .set_text(search_expression); + } + } + + fn pick_control_input(&self) { + let (current_control_input, current_wants_keyboard_input, current_stream_deck_dev_id) = { + let session = self.session(); + let session = session.borrow(); + ( + session.control_input(), + session.wants_keyboard_input(), + session.stream_deck_device_id(), + ) + }; + let result = self.view.require_window().open_popup_menu( + menus::control_input_menu( + current_control_input, + current_wants_keyboard_input, + current_stream_deck_dev_id, + ), + Window::cursor_pos(), + ); + if let Some(action) = result { + match action { + ControlInputMenuAction::Nothing => {} + ControlInputMenuAction::SelectControlInput(input) => { + self.session().borrow_mut().control_input.set(input); + update_auto_units_async(); + } + ControlInputMenuAction::ManageOsc(action) => { + self.execute_osc_dev_management_action(action); + } + ControlInputMenuAction::ToggleWantsKeyboardInput => { + if let Some(session) = self.session.clone().upgrade() { + let mut session = session.borrow_mut(); + let current_value = session.wants_keyboard_input(); + session.change_with_notification( + UnitCommand::SetWantsKeyboardInput(!current_value), + None, + self.session.clone(), + ) + } + } + ControlInputMenuAction::SelectStreamDeckDevice(dev) => { + if let Some(session) = self.session.clone().upgrade() { + session.borrow_mut().change_with_notification( + UnitCommand::SetStreamDeckDevice(dev), + None, + self.session.clone(), + ) + } + } + } + } + } + + fn pick_feedback_output(&self) { + let current_value = self.session().borrow().feedback_output(); + let result = self.view.require_window().open_popup_menu( + menus::feedback_output_menu(current_value), + Window::cursor_pos(), + ); + if let Some(action) = result { + match action { + FeedbackOutputMenuAction::SelectFeedbackOutput(output) => { + self.session().borrow_mut().feedback_output.set(output); + update_auto_units_async(); + } + FeedbackOutputMenuAction::ManageOsc(action) => { + self.execute_osc_dev_management_action(action); + } + } + } + } + + fn activate_compartment(&self, compartment: CompartmentKind) { + let mut main_state = self.main_state.borrow_mut(); + main_state.stop_filter_learning(); + main_state.clear_all_filters(); + main_state.active_compartment.set(compartment); + } + + fn remove_group(&self) { + let id = match self + .main_state + .borrow() + .displayed_group_for_active_compartment() + { + Some(GroupFilter(id)) if !id.is_default() => id, + _ => return, + }; + let compartment = self.active_compartment(); + let delete_mappings_result = if self + .session() + .borrow() + .group_contains_mappings(compartment, id) + { + let msg = "Do you also want to delete all mappings in that group? If you choose no, they will be automatically moved to the default group."; + self.view + .require_window() + .ask_yes_no_or_cancel("ReaLearn", msg) + } else { + Some(false) + }; + if let Some(delete_mappings) = delete_mappings_result { + self.main_state + .borrow_mut() + .set_displayed_group_for_active_compartment(Some(GroupFilter(GroupId::default()))); + self.session() + .borrow_mut() + .remove_group(compartment, id, delete_mappings); + } + } + + pub fn edit_group(&self) -> anyhow::Result> { + let compartment = self.active_compartment(); + let weak_group = match self + .main_state + .borrow() + .displayed_group_for_active_compartment() + { + Some(GroupFilter(id)) => { + if id.is_default() { + Rc::downgrade(self.session().borrow().default_group(compartment)) + } else { + let session = self.session(); + let session = session.borrow(); + let group = session + .find_group_by_id(compartment, id) + .expect("group not existing"); + Rc::downgrade(group) + } + } + _ => bail!("no specific group selected"), + }; + let panel = GroupPanel::new(self.session.clone(), weak_group); + let shared_panel = open_child_panel(&self.group_panel, panel, self.view.require_window()); + Ok(shared_panel) + } + + fn update_group(&self) { + let compartment = self.active_compartment(); + let group_filter = match self + .view + .require_control(root::ID_GROUP_COMBO_BOX) + .selected_combo_box_item_data() + { + -1 => None, + i if i >= 0 => { + let session = self.session(); + let session = session.borrow(); + let group = session + .find_group_by_index_sorted(compartment, i as usize) + .expect("group not existing") + .borrow(); + Some(GroupFilter(group.id())) + } + _ => unreachable!("unexpected group combo box item data"), + }; + self.main_state + .borrow_mut() + .set_displayed_group_for_active_compartment(group_filter); + } + + fn update_preset_auto_load_mode(&self) { + self.main_state.borrow_mut().stop_filter_learning(); + let mode: AutoLoadMode = self + .view + .require_control(root::ID_AUTO_LOAD_COMBO_BOX) + .selected_combo_box_item_index() + .try_into() + .expect("invalid preset auto-load mode"); + let result = self.session().borrow_mut().activate_auto_load_mode(mode); + if let Err(e) = result { + self.invalidate_preset_auto_load_mode_combo_box(); + self.notify_user_about_anyhow_error(e); + } + } + + fn mappings_are_read_only(&self) -> bool { + self.session() + .borrow() + .mappings_are_read_only(self.active_compartment()) + } + + fn invalidate_learn_many_button(&self) { + let is_learning = self.session().borrow().is_learning_many_mappings(); + let learn_button_text = if is_learning { "Stop" } else { "Learn many" }; + let button = self + .view + .require_control(root::ID_LEARN_MANY_MAPPINGS_BUTTON); + button.set_text(learn_button_text); + let enabled = !(self.active_compartment() == CompartmentKind::Main + && self.session().borrow().main_preset_is_auto_loaded()); + button.set_enabled(enabled); + } + + fn invalidate_notes_button(&self) { + let compartment = self.active_compartment(); + let notes_empty = self + .session() + .borrow() + .compartment_notes(compartment) + .is_empty(); + let text = if notes_empty { "Notes" } else { "Notes*" }; + let button = self.view.require_control(root::ID_NOTES_BUTTON); + button.set_text(text); + } + + fn invalidate_add_one_button(&self) { + self.view + .require_control(root::ID_ADD_MAPPING_BUTTON) + .set_enabled(!self.mappings_are_read_only()); + } + + fn invalidate_source_filter_buttons(&self) { + let main_state = self.main_state.borrow(); + self.invalidate_filter_buttons( + main_state.is_learning_source_filter.get(), + main_state.source_filter.get_ref().is_some(), + "Filter source", + root::ID_FILTER_BY_SOURCE_BUTTON, + root::ID_CLEAR_SOURCE_FILTER_BUTTON, + ); + } + + fn invalidate_target_filter_buttons(&self) { + let main_state = self.main_state.borrow(); + self.invalidate_filter_buttons( + main_state.is_learning_target_filter.get(), + main_state.target_filter.get_ref().is_some(), + "Filter target", + root::ID_FILTER_BY_TARGET_BUTTON, + root::ID_CLEAR_TARGET_FILTER_BUTTON, + ); + } + + fn invalidate_filter_buttons( + &self, + is_learning: bool, + is_set: bool, + learn_text: &str, + learn_button_id: u32, + clear_button_id: u32, + ) { + let learn_button_text = if is_learning { "Stop" } else { learn_text }; + self.view + .require_control(learn_button_id) + .set_text(learn_button_text); + self.view + .require_control(clear_button_id) + .set_enabled(is_set); + } + + fn instance_panel(&self) -> SharedView { + self.instance_panel.upgrade().expect("instance panel gone") + } + + pub fn import_from_clipboard(&self) -> anyhow::Result<()> { + let text = get_text_from_clipboard().context("Couldn't read from clipboard.")?; + let res = { + let session = self.session(); + let session = session.borrow(); + let compartment_in_session = session.compartment_in_unit(self.active_compartment()); + deserialize_data_object(&text, &compartment_in_session)? + }; + BackboneShell::warn_if_envelope_version_higher(res.version()); + use UntaggedDataObject::*; + match res { + PresetLike(preset_data) => { + let compartment = self.active_compartment(); + self.import_compartment(compartment, preset_data.version.as_ref(), preset_data.data); + } + Tagged(DataObject::Instance(Envelope { value: d, .. })) => { + if self.view.require_window().confirm( + "Helgobox", + "Do you want to continue replacing the complete Helgobox instance with the data in the clipboard?", + ) { + let instance_panel = self.instance_panel(); + instance_panel.show_unit(None); + let instance_shell = instance_panel.shell()?; + instance_shell.apply_data(*d)?; + } + } + Tagged(DataObject::Unit(Envelope { value: d, .. })) => { + if self.view.require_window().confirm( + "ReaLearn", + "Do you want to continue replacing this complete ReaLearn unit with the data in the clipboard?", + ) { + let session = self.session(); + d.apply_to_model(&session)?; + } + } + Tagged(DataObject::ClipMatrix(Envelope { value, .. })) => { + #[cfg(not(feature = "playtime"))] + { + let _ = value; + bail!("Playtime not available"); + } + #[cfg(feature = "playtime")] + { + use playtime_api::persistence::FlexibleMatrix; + let old_matrix_label = match self.session().borrow().instance().borrow().clip_matrix() { + None => EMPTY_CLIP_MATRIX_LABEL.to_owned(), + Some(matrix) => get_clip_matrix_label(matrix.column_count()) + }; + let new_matrix_label = match &*value { + None => EMPTY_CLIP_MATRIX_LABEL.to_owned(), + Some(m) => { + let column_count = match m { + FlexibleMatrix::Unsigned(m) => m.column_count(), + FlexibleMatrix::Signed(m) => { + m.decode_value()?.column_count() + } + }; + get_clip_matrix_label(column_count) + } + }; + if self.view.require_window().confirm( + "Playtime", + format!("Do you want to replace the current {old_matrix_label} with the {new_matrix_label} in the clipboard?"), + ) { + self.instance_panel().shell()?.load_clip_matrix(*value)?; + } + } + } + Tagged(DataObject::MainCompartment(Envelope { value, version })) => { + let compartment = CompartmentKind::Main; + self.import_compartment(compartment, version.as_ref(), value); + self.activate_compartment(compartment); + } + Tagged(DataObject::ControllerCompartment(Envelope { value, version })) => { + let compartment = CompartmentKind::Controller; + self.import_compartment(compartment, version.as_ref(), value); + self.activate_compartment(compartment); + } + Tagged(DataObject::Mappings { .. }) => { + bail!("The clipboard contains just a lose collection of mappings. Please import them using the context menus.") + } + Tagged(DataObject::Mapping { .. }) => { + bail!("The clipboard contains just one single mapping. Please import it using the context menus.") + } + _ => { + bail!("The clipboard contains only a part of a mapping. Please import it using the context menus in the mapping area.") + } + } + Ok(()) + } + + fn import_compartment( + &self, + compartment: CompartmentKind, + version: Option<&Version>, + data: Box, + ) { + if self.view.require_window().confirm( + "ReaLearn", + format!("Do you want to continue replacing the {compartment}?",), + ) { + let session = self.session(); + let mut session = session.borrow_mut(); + match data.to_model(version, compartment, Some(&session)) { + Ok(model) => { + session.import_compartment(compartment, Some(model)); + } + Err(e) => { + self.view.require_window().alert("ReaLearn", e.to_string()); + } + } + } + } + + pub fn export_to_clipboard(&self) -> anyhow::Result<()> { + #[allow(dead_code)] + enum MenuAction { + None, + ExportInstance(SerializationFormat), + ExportUnit(SerializationFormat), + ExportClipMatrix(SerializationFormat), + ExportCompartment(SerializationFormat), + } + impl Default for MenuAction { + fn default() -> Self { + Self::None + } + } + let compartment = self.active_compartment(); + let pure_menu = { + use swell_ui::menu_tree::*; + let entries = vec![ + item( + "Export instance as JSON", + MenuAction::ExportInstance(SerializationFormat::JsonDataObject), + ), + item( + "Export unit as JSON", + MenuAction::ExportUnit(SerializationFormat::JsonDataObject), + ), + item( + format!("Export {compartment} as JSON"), + MenuAction::ExportCompartment(SerializationFormat::JsonDataObject), + ), + item( + format!("Export {compartment} as Lua"), + MenuAction::ExportCompartment(SerializationFormat::LuaApiObject( + ConversionStyle::Minimal, + )), + ), + item( + format!("Export {compartment} as Lua (include default values)"), + MenuAction::ExportCompartment(SerializationFormat::LuaApiObject( + ConversionStyle::IncludeDefaultValues, + )), + ), + separator(), + item_with_opts( + "Export Playtime matrix as JSON", + ItemOpts { + enabled: cfg!(feature = "playtime"), + checked: false, + }, + MenuAction::ExportClipMatrix(SerializationFormat::JsonDataObject), + ), + item_with_opts( + "Export Playtime matrix as Lua", + ItemOpts { + enabled: cfg!(feature = "playtime"), + checked: false, + }, + MenuAction::ExportClipMatrix(SerializationFormat::LuaApiObject( + ConversionStyle::Minimal, + )), + ), + ]; + anonymous_menu(entries) + }; + let result = match self + .view + .require_window() + .open_popup_menu(pure_menu, Window::cursor_pos()) + { + None => return Ok(()), + Some(i) => i, + }; + // Execute action + match result { + MenuAction::None => {} + MenuAction::ExportInstance(_) => { + let instance_panel = self + .instance_panel + .upgrade() + .context("instance panel gone")?; + let instance_shell = instance_panel.shell()?; + let data = instance_shell + .create_data() + .context("couldn't acquire instance data")?; + let data_object = + DataObject::Instance(BackboneShell::create_envelope(Box::new(data))); + let json = serialize_data_object_to_json(data_object).unwrap(); + copy_text_to_clipboard(json)?; + } + MenuAction::ExportUnit(_) => { + let session = self.session(); + let session_data = UnitData::from_model(&session.borrow()); + let data_object = + DataObject::Unit(BackboneShell::create_envelope(Box::new(session_data))); + let json = serialize_data_object_to_json(data_object).unwrap(); + copy_text_to_clipboard(json)?; + } + MenuAction::ExportClipMatrix(format) => { + #[cfg(not(feature = "playtime"))] + { + let _ = format; + } + #[cfg(feature = "playtime")] + { + let matrix = self + .session() + .borrow() + .instance() + .borrow() + .clip_matrix() + .map(|matrix| matrix.save()); + let envelope = BackboneShell::create_envelope(Box::new(matrix)); + let data_object = DataObject::ClipMatrix(envelope); + let text = serialize_data_object(data_object, format)?; + copy_text_to_clipboard(text)?; + } + } + MenuAction::ExportCompartment(format) => { + let session = self.session(); + let session = session.borrow(); + let model = session.extract_compartment_model(compartment); + let data = CompartmentModelData::from_model(&model); + let envelope = BackboneShell::create_envelope(Box::new(data)); + let data_object = match compartment { + CompartmentKind::Controller => DataObject::ControllerCompartment(envelope), + CompartmentKind::Main => DataObject::MainCompartment(envelope), + }; + let text = serialize_data_object(data_object, format)?; + copy_text_to_clipboard(text)?; + } + }; + Ok(()) + } + + fn notify_user_on_error(&self, result: Result<(), Box>) { + if let Err(e) = result { + self.notify_user_about_error(e); + } + } + + fn notify_user_on_anyhow_error(&self, result: anyhow::Result<()>) { + if let Err(e) = result { + self.notify_user_about_anyhow_error(e); + } + } + + fn notify_user_about_error(&self, e: Box) { + self.view.require_window().alert("ReaLearn", e.to_string()); + } + + fn notify_user_about_anyhow_error(&self, e: anyhow::Error) { + self.view + .require_window() + .alert("ReaLearn", format!("{e:#}")); + } + + fn delete_active_preset(&self) -> anyhow::Result<()> { + if !self + .view + .require_window() + .confirm("ReaLearn", "Do you really want to remove this preset?") + { + return Ok(()); + } + let session = self.session(); + let mut session = session.borrow_mut(); + let compartment = self.active_compartment(); + let preset_manager = BackboneShell::get().compartment_preset_manager(compartment); + let active_preset_id = session + .active_preset_id(compartment) + .context("no preset selected")? + .to_string(); + match compartment { + CompartmentKind::Controller => session.activate_controller_preset(None), + CompartmentKind::Main => session.activate_main_preset(None), + }; + preset_manager + .borrow_mut() + .remove_preset(&active_preset_id)?; + Ok(()) + } + + fn create_compartment_preset_workspace(&self, include_factory_presets: bool) { + let compartment = self.active_compartment(); + let result = BackboneShell::get() + .compartment_preset_manager(compartment) + .borrow_mut() + .export_preset_workspace(include_factory_presets); + match result { + Ok(descriptor) => { + let name = descriptor.name; + let text = format!( + "ReaLearn created a new {compartment} preset workspace named \"{name}\" for you.\n\ + \n\ + In the next step, ReaLearn will open the workspace folder in your file manager, where you can rename the folder and start developing presets. For details, consult the file \"README.md\" in the workspace root! + " + ); + self.view.require_window().alert("Success!", text); + let _ = open_in_file_manager(descriptor.dir.as_std_path()); + } + Err(e) => notify_user_about_anyhow_error(&e), + } + } + + fn reload_all_compartment_presets(&self) { + match self.active_compartment() { + CompartmentKind::Controller => { + let _ = BackboneShell::get() + .controller_preset_manager() + .borrow_mut() + .load_presets_from_disk(); + } + CompartmentKind::Main => { + let _ = BackboneShell::get() + .main_preset_manager() + .borrow_mut() + .load_presets_from_disk(); + } + } + } + + pub fn show_pot_browser(&self) { + #[cfg(not(feature = "egui"))] + { + crate::infrastructure::ui::util::alert_feature_not_available(); + } + #[cfg(feature = "egui")] + { + let result = self.show_pot_browser_internal(); + // Important to not use the header window to show the error because the pot browser + // might be opened without any ReaLearn window being open! + crate::base::notification::notify_user_on_error(result); + } + } + + #[cfg(feature = "egui")] + fn show_pot_browser_internal(&self) -> Result<(), Box> { + let session = self.session(); + let pot_unit = session.borrow().instance().borrow_mut().pot_unit()?; + let panel = crate::infrastructure::ui::PotBrowserPanel::new(pot_unit); + open_child_panel_dyn( + &self.pot_browser_panel, + panel, + Window::from_hwnd(Reaper::get().main_window()), + ); + Ok(()) + } + + fn show_projection(&self) { + if let Some(show_in_app) = self.prompt_whether_to_open_projection_in_app() { + if show_in_app { + self.show_projection_in_app(); + } else { + self.show_projection_in_browser(); + } + } + } + + fn show_projection_in_browser(&self) { + self.companion_app_presenter.show_app_info(); + } + + fn show_projection_in_app(&self) { + let unit_id = self.session().borrow().unit_id(); + self.instance_panel() + .start_or_show_app_instance(Some(AppPage::Projection(unit_id))); + } + + fn show_app(&self) { + self.instance_panel().start_or_show_app_instance(None); + } + + fn close_app(&self) { + self.instance_panel().stop_app_instance(); + } + + fn open_compartment_preset_folder(&self) { + let path = BackboneShell::realearn_compartment_preset_dir_path(self.active_compartment()); + let result = open_in_file_manager(path.as_std_path()).map_err(|e| e.into()); + self.notify_user_on_error(result); + } + + fn open_app_folder(&self) { + let path = BackboneShell::app_binary_base_dir_path(); + let result = open_in_file_manager(path.as_std_path()).map_err(|e| e.into()); + self.notify_user_on_error(result); + } + + fn continue_after_project_independence_check(&self) -> bool { + let mappings_have_project_references = { + let compartment = self.active_compartment(); + let session = self.session(); + let session = session.borrow(); + session.mappings_have_project_references(compartment) + }; + if !mappings_have_project_references { + // Safe! + return true; + } + let msg = "Some of the mappings have references to this particular project. This doesn't make sense for a preset that's supposed to be reusable among different projects. Please consider using \"Menu => Modify multiple mappings => Make targets of listed mappings non-sticky\" before saving the preset. Do you still want to save the preset now?"; + self.view.require_window().confirm("ReaLearn", msg) + } + + fn save_active_preset(&self) -> anyhow::Result<()> { + if !self.continue_after_project_independence_check() { + return Ok(()); + } + let session = self.session(); + let mut session = session.borrow_mut(); + let compartment = self.active_compartment(); + let preset_id = session + .active_preset_id(compartment) + .context("no active preset")?; + let compartment_model = session.extract_compartment_model(compartment); + match compartment { + CompartmentKind::Controller => { + let preset_manager = BackboneShell::get().controller_preset_manager(); + let mut controller_preset = preset_manager + .borrow() + .find_by_id(preset_id) + .context("controller preset not found")?; + controller_preset.set_model(compartment_model); + preset_manager + .borrow_mut() + .update_preset(controller_preset)?; + } + CompartmentKind::Main => { + let preset_manager = BackboneShell::get().main_preset_manager(); + let mut main_preset = preset_manager + .borrow() + .find_by_id(preset_id) + .context("main preset not found")?; + main_preset.set_model(compartment_model); + preset_manager.borrow_mut().update_preset(main_preset)?; + } + }; + session.compartment_is_dirty[compartment].set(false); + Ok(()) + } + + fn get_active_preset_info(&self, compartment: CompartmentKind) -> Option { + let session = self.session(); + let session = session.borrow(); + let preset_id = session.active_preset_id(compartment)?; + BackboneShell::get() + .compartment_preset_manager(compartment) + .borrow() + .common_preset_info_by_id(preset_id) + .cloned() + } + + fn save_as_preset(&self) -> anyhow::Result<()> { + let compartment = self.active_compartment(); + let active_preset_info = self.get_active_preset_info(compartment); + if let Some(info) = &active_preset_info { + if let PresetOrigin::Factory { .. } = &info.origin { + if info.file_type == PresetFileType::Lua { + let menu_entry_label = build_create_compartment_preset_workspace_label(true); + let text = format!( + "This factory preset was written in the scripting language Lua. If you continue, ReaLearn will save it as user preset which contains a simple flat list of mappings (no code). Do you want to continue?\n\ + \n\ + If you are you familiar with Lua and want to customize the Lua code to your own needs, do this instead: Main menu => {PRESET_RELATED_MENU_LABEL} => {menu_entry_label}.", + ); + if !self.view.require_window().confirm("ReaLearn", text) { + return Ok(()); + } + } + } + } + let current_preset_name = active_preset_info + .map(|info| info.meta_data.name) + .unwrap_or_default(); + let preset_name = match dialog_util::prompt_for("Preset name", ¤t_preset_name) { + None => return Ok(()), + Some(n) => n, + }; + if preset_name.trim().is_empty() { + return Ok(()); + } + if !self.continue_after_project_independence_check() { + return Ok(()); + } + let session = self.session(); + let mut session = session.borrow_mut(); + let preset_id = format!("{}/{}", whoami::username(), slug::slugify(&preset_name)); + let compartment_model = session.extract_compartment_model(compartment); + match compartment { + CompartmentKind::Controller => { + let controller = CompartmentPresetModel::new( + preset_id.clone(), + preset_name, + CompartmentKind::Controller, + compartment_model, + ); + BackboneShell::get() + .controller_preset_manager() + .borrow_mut() + .add_preset(controller)?; + session.activate_controller_preset(Some(preset_id)); + } + CompartmentKind::Main => { + let main_preset = CompartmentPresetModel::new( + preset_id.clone(), + preset_name, + CompartmentKind::Main, + compartment_model, + ); + BackboneShell::get() + .main_preset_manager() + .borrow_mut() + .add_preset(main_preset)?; + session.activate_main_preset(Some(preset_id)); + } + }; + Ok(()) + } + + fn reset(&self) { + self.main_state + .borrow_mut() + .set_displayed_group_for_active_compartment(Some(GroupFilter(GroupId::default()))); + self.close_open_child_panels(); + self.invalidate_all_controls(); + } + + fn log_debug_info(&self) { + let session = self.session(); + let session = session.borrow(); + session.log_debug_info(); + BackboneShell::get().log_debug_info(session.unit_key()); + } + + fn open_realearn_reference_offline(&self) { + let user_guide_pdf = + BackboneShell::realearn_data_dir_path().join("doc/realearn-user-guide.pdf"); + if open::that(user_guide_pdf).is_err() { + self.view.require_window().alert( + "ReaLearn", + "Couldn't open offline user guide. Please try the online version!", + ) + } + } + + fn open_realearn_reference_online(&self) { + open_in_browser("https://docs.helgoboss.org/realearn"); + } + + fn open_helgobox_wiki(&self) { + open_in_browser("https://github.com/helgoboss/helgobox/wiki"); + } + + fn open_realearn_controller_list(&self) { + open_in_browser("https://github.com/helgoboss/helgobox/wiki/ReaLearn-Controllers"); + } + + fn donate(&self) { + open_in_browser("https://paypal.me/helgoboss"); + } + + fn open_realearn_forum(&self) { + open_in_browser("https://forum.cockos.com/showthread.php?t=178015"); + } + + fn contact_developer(&self) { + open_in_browser("mailto:info@helgoboss.org"); + } + + fn open_realearn_website(&self) { + open_in_browser("https://www.helgoboss.org/projects/realearn/"); + } + + fn open_about_page(&self) { + let about_html = BackboneShell::helgobox_data_dir_path().join("doc/about.html"); + open_in_browser(about_html.as_str()); + } + + fn register_listeners(self: SharedView) { + let shared_session = self.session(); + let session = shared_session.borrow(); + self.when(session.everything_changed(), |view, _| { + view.reset(); + }); + self.when( + session + .let_matched_events_through + .changed() + .merge(session.let_unmatched_events_through.changed()), + |view, _| { + view.invalidate_let_through_controls(); + }, + ); + self.when(session.learn_many_state_changed(), |view, _| { + view.invalidate_all_controls(); + }); + self.when(session.control_input.changed(), |view, _| { + view.invalidate_control_input_button(); + view.invalidate_let_through_controls(); + let shared_session = view.session(); + let mut session = shared_session.borrow_mut(); + let control_input = session.control_input(); + if control_input.is_midi_device() && !reaper_supports_global_midi_filter() { + session.let_matched_events_through.set(true); + session.let_unmatched_events_through.set(true); + } + }); + self.when(session.feedback_output.changed(), |view, _| { + view.invalidate_feedback_output_button() + }); + let main_state = self.main_state.borrow(); + self.when( + main_state.displayed_group_for_any_compartment_changed(), + |view, _| { + view.invalidate_group_controls(); + }, + ); + self.when( + main_state.search_expression.changed_with_initiator(), + |view, initiator| { + view.invoke_programmatically(|| { + view.invalidate_search_expression(initiator); + }); + }, + ); + self.when( + main_state + .is_learning_target_filter + .changed() + .merge(main_state.target_filter.changed()), + |view, _| { + view.invalidate_target_filter_buttons(); + }, + ); + self.when( + main_state + .is_learning_source_filter + .changed() + .merge(main_state.source_filter.changed()), + |view, _| { + view.invalidate_source_filter_buttons(); + }, + ); + self.when(main_state.active_compartment.changed(), |view, _| { + view.invalidate_all_controls(); + }); + self.when(session.auto_load_mode.changed(), |view, _| { + view.invalidate_all_controls(); + }); + self.when(session.group_list_changed(), |view, _| { + view.invalidate_group_controls(); + }); + when( + BackboneShell::get() + .controller_preset_manager() + .borrow() + .changed() + .merge( + BackboneShell::get() + .main_preset_manager() + .borrow() + .changed(), + ) + .take_until(self.view.closed()), + ) + .with(Rc::downgrade(&self)) + .do_async(move |view, _| { + view.invalidate_preset_controls(); + }); + when( + BackboneShell::get() + .osc_device_manager() + .borrow() + .changed() + .take_until(self.view.closed()), + ) + .with(Rc::downgrade(&self)) + .do_async(move |view, _| { + view.invalidate_control_input_button(); + view.invalidate_feedback_output_button(); + }); + // Enables/disables save button depending on dirty state. + when( + session.compartment_is_dirty[CompartmentKind::Controller] + .changed() + .merge(session.compartment_is_dirty[CompartmentKind::Main].changed()) + .take_until(self.view.closed()), + ) + .with(Rc::downgrade(&self)) + .do_sync(move |view, _| { + view.invalidate_preset_buttons(); + }); + } + + fn when( + self: &SharedView, + event: impl LocalObservable<'static, Item = I, Err = ()> + 'static, + reaction: impl Fn(SharedView, I) + 'static + Clone, + ) { + when(event.take_until(self.view.closed())) + .with(Rc::downgrade(self)) + .do_sync(reaction); + } + + fn is_invoked_programmatically(&self) -> bool { + self.is_invoked_programmatically.get() + } + + fn convert_toolbar_to_stream_deck_mappings(&self, toolbar_name: &str) -> anyhow::Result<()> { + let api_compartment = stream_deck_tool::create_stream_deck_compartment_reflecting_toolbar( + toolbar_name, + StreamDeckToolbarOptions { + use_sliding_images: true, + }, + )?; + let compartment_kind = CompartmentKind::Main; + let data_compartment = + to_data::convert_compartment(CompartmentKind::Main, api_compartment)?; + self.import_compartment( + compartment_kind, + Some(BackboneShell::version()), + data_compartment.into(), + ); + self.activate_compartment(compartment_kind); + Ok(()) + } +} + +fn build_create_compartment_preset_workspace_label(include_factory_presets: bool) -> String { + let suffix = if include_factory_presets { + " (including factory presets)" + } else { + "" + }; + format!("Create compartment preset workspace{suffix}") +} + +impl View for HeaderPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_HEADER_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + window.taborder_first(); + if cfg!(unix) { + self.show_color_panel.clone().open(window); + } + self.fill_all_controls(); + self.invalidate_all_controls(); + self.invalidate_search_expression(None); + self.register_listeners(); + true + } + + fn erase_background(self: SharedView, device_context: DeviceContext) -> bool { + if cfg!(unix) { + // On macOS/Linux we use color panels as real child windows. + return false; + } + if !BackboneShell::get().config().background_colors_enabled() { + return false; + } + let window = self.view.require_window(); + self.show_color_panel.paint_manually(device_context, window); + true + } + + fn control_color_static( + self: SharedView, + device_context: DeviceContext, + window: Window, + ) -> Option { + if cfg!(target_os = "macos") { + // On macOS, we fortunately don't need to do this nonsense. And it wouldn't be possible + // anyway because SWELL macOS can't distinguish between different child controls. + return None; + } + if !BackboneShell::get().config().background_colors_enabled() { + return None; + } + device_context.set_bk_mode_to_transparent(); + let color_pair = match window.resource_id() { + root::ID_HEADER_PANEL_SHOW_LABEL_TEXT + | root::ID_CONTROLLER_COMPARTMENT_RADIO_BUTTON + | root::ID_MAIN_COMPARTMENT_RADIO_BUTTON => colors::show_background(), + _ => return None, + }; + view::get_brush_for_color_pair(color_pair) + } + + fn on_destroy(self: SharedView, _window: Window) { + self.main_state.borrow_mut().stop_filter_learning(); + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + root::ID_CONTROL_INPUT_BUTTON => self.pick_control_input(), + root::ID_FEEDBACK_OUTPUT_BUTTON => self.pick_feedback_output(), + root::ID_PRESET_BROWSE_BUTTON => self.browse_presets(), + root::ID_GROUP_ADD_BUTTON => self.add_group(), + root::ID_GROUP_DELETE_BUTTON => self.remove_group(), + root::ID_GROUP_EDIT_BUTTON => { + let _ = self.edit_group(); + } + root::ID_NOTES_BUTTON => self.edit_compartment_notes(), + root::ID_ADD_MAPPING_BUTTON => self.add_mapping(), + root::ID_LEARN_MANY_MAPPINGS_BUTTON => { + self.toggle_learn_many_mappings(); + } + root::ID_FILTER_BY_SOURCE_BUTTON => self.toggle_learn_source_filter(), + root::ID_FILTER_BY_TARGET_BUTTON => self.toggle_learn_target_filter(), + root::ID_CLEAR_SOURCE_FILTER_BUTTON => self.clear_source_filter(), + root::ID_CLEAR_TARGET_FILTER_BUTTON => self.clear_target_filter(), + root::ID_CLEAR_SEARCH_BUTTON => self.clear_search_expression(), + root::ID_MENU_BUTTON => { + let _ = self.open_main_menu(Window::cursor_pos()); + } + root::ID_MAIN_HELP_BUTTON => { + let _ = self.open_help_menu(Window::cursor_pos()); + } + root::ID_IMPORT_BUTTON => { + let result = self.import_from_clipboard(); + self.notify_user_on_anyhow_error(result); + } + root::ID_EXPORT_BUTTON => { + self.notify_user_on_anyhow_error(self.export_to_clipboard()); + } + root::ID_LET_MATCHED_EVENTS_THROUGH_CHECK_BOX => { + self.update_let_matched_events_through() + } + root::ID_LET_UNMATCHED_EVENTS_THROUGH_CHECK_BOX => { + self.update_let_unmatched_events_through() + } + root::ID_PRESET_DELETE_BUTTON => { + self.notify_user_on_anyhow_error(self.delete_active_preset()); + } + root::ID_PRESET_SAVE_AS_BUTTON => { + self.notify_user_on_anyhow_error(self.save_as_preset()); + } + root::ID_PRESET_SAVE_BUTTON => { + self.notify_user_on_anyhow_error(self.save_active_preset()); + } + root::ID_PROJECTION_BUTTON => { + self.show_projection(); + } + root::ID_CONTROLLER_COMPARTMENT_RADIO_BUTTON => { + self.activate_compartment(CompartmentKind::Controller) + } + root::ID_MAIN_COMPARTMENT_RADIO_BUTTON => { + self.activate_compartment(CompartmentKind::Main) + } + _ => {} + } + } + + fn option_selected(self: SharedView, resource_id: u32) { + match resource_id { + root::ID_GROUP_COMBO_BOX => self.update_group(), + root::ID_AUTO_LOAD_COMBO_BOX => self.update_preset_auto_load_mode(), + _ => {} + } + } + + fn edit_control_changed(self: SharedView, resource_id: u32) -> bool { + if self.is_invoked_programmatically() { + // We don't want to continue if the edit control change was not caused by the user. + // Although the edit control text is changed programmatically, it also triggers the + // change handler. Ignore it! Most of those events are filtered out already + // by the dialog proc reentrancy check, but this one is not because the + // dialog proc is not reentered - we are just reacting (async) to a change. + return false; + } + match resource_id { + root::ID_HEADER_SEARCH_EDIT_CONTROL => self.update_search_expression(), + _ => {} + } + true + } + + fn context_menu_wanted(self: SharedView, location: Point) -> bool { + let _ = self.open_main_menu(location); + true + } +} + +impl Drop for HeaderPanel { + fn drop(&mut self) { + debug!("Dropping header panel..."); + self.close_open_child_panels(); + } +} + +fn edit_preset_link_fx_id(mutator: &mut dyn PresetLinkMutator, old_fx_id: FxId) { + let new_fx_id = match edit_fx_id(&old_fx_id) { + Ok(d) => d, + Err(EditFxIdError::Cancelled) => return, + res => res.unwrap(), + }; + mutator.update_fx_id(old_fx_id, new_fx_id); +} + +fn remove_preset_link(mutator: &mut dyn PresetLinkMutator, fx_id: FxId) { + mutator.remove_link(&fx_id); +} + +fn link_to_preset(mutator: &mut dyn PresetLinkMutator, fx_id: FxId, preset_id: String) { + mutator.link_preset_to_fx(preset_id, fx_id); +} + +fn edit_fx_id(fx_id: &FxId) -> Result { + let csv = Reaper::get() + .medium_reaper() + .get_user_inputs( + "ReaLearn", + 3, + "FX name,FX file name,FX preset name,separator=;,extrawidth=80", + format!( + "{};{};{}", + fx_id.name(), + fx_id.file_name(), + fx_id.preset_name() + ), + 512, + ) + .ok_or(EditFxIdError::Cancelled)?; + let split: Vec<_> = csv.to_str().split(';').collect(); + if let [name, file_name, preset_name] = split.as_slice() { + let new_fx_id = FxId { + name: name.to_string(), + file_name: file_name.to_string(), + preset_name: preset_name.to_string(), + }; + Ok(new_fx_id) + } else { + Err(EditFxIdError::Unexpected("couldn't split")) + } +} + +#[derive(Debug)] +enum EditFxIdError { + Cancelled, + #[allow(dead_code)] + Unexpected(&'static str), +} + +fn edit_new_osc_device() { + let dev = match edit_osc_device(OscDevice::default()) { + Ok(d) => d, + Err(EditOscDevError::Cancelled) => return, + res => res.unwrap(), + }; + BackboneShell::get() + .osc_device_manager() + .borrow_mut() + .add_device(dev) + .unwrap(); +} + +fn edit_existing_osc_device(dev_id: OscDeviceId) { + let dev = BackboneShell::get() + .osc_device_manager() + .borrow() + .find_device_by_id(&dev_id) + .unwrap() + .clone(); + let dev = match edit_osc_device(dev) { + Ok(d) => d, + Err(EditOscDevError::Cancelled) => return, + res => res.unwrap(), + }; + BackboneShell::get() + .osc_device_manager() + .borrow_mut() + .update_device(dev) + .unwrap(); +} + +fn remove_osc_device(parent_window: Window, dev_id: OscDeviceId) { + if !parent_window.confirm( + "ReaLearn", + "Do you really want to remove this OSC device? This is a global action. As a consequence, all existing ReaLearn instances which use this device will point to a device that doesn't exist anymore.", + ) { + return; + } + BackboneShell::get() + .osc_device_manager() + .borrow_mut() + .remove_device_by_id(dev_id) + .unwrap(); +} + +fn edit_compartment_parameter( + session: SharedUnitModel, + compartment: CompartmentKind, + range: RangeInclusive, +) -> Result<(), &'static str> { + let current_settings: Vec<_> = { + let session = session.borrow(); + convert_compartment_param_index_range_to_iter(&range) + .map(|i| { + session + .params() + .compartment_params(compartment) + .at(i) + .setting() + .clone() + }) + .collect() + }; + let modified_settings = edit_compartment_parameter_internal(*range.start(), ¤t_settings)?; + let range_iter = convert_compartment_param_index_range_to_iter(&range); + session + .borrow_mut() + .update_certain_param_settings(compartment, range_iter.zip(modified_settings).collect()); + Ok(()) +} + +#[derive(Debug)] +enum EditOscDevError { + Cancelled, + #[allow(dead_code)] + Unexpected(&'static str), +} + +/// Pass max 5 settings. +fn edit_compartment_parameter_internal( + offset: CompartmentParamIndex, + settings: &[ParamSetting], +) -> Result, &'static str> { + let mut captions_csv = (offset.get()..) + .zip(settings) + .map(|(i, _)| format!("Param {} name,Value count", i + 1)) + .join(","); + captions_csv.push_str(",separator=;,extrawidth=80"); + let initial_csv = settings + .iter() + .flat_map(|s| { + [ + s.name.clone(), + s.value_count.map(|v| v.to_string()).unwrap_or_default(), + ] + }) + .join(";"); + let csv = Reaper::get() + .medium_reaper() + .get_user_inputs( + "ReaLearn", + (settings.len() * 2) as u32, + captions_csv, + initial_csv, + 1024, + ) + .ok_or("cancelled")?; + let tuples = csv.to_str().split(';').tuples(); + let out_settings: Vec<_> = tuples + .zip(settings) + .map(|((name, value_count), old_setting)| ParamSetting { + key: old_setting.key.clone(), + name: name.trim().to_owned(), + value_count: { value_count.parse().ok() }, + value_labels: vec![], + }) + .collect(); + if out_settings.len() != settings.len() { + return Err("unexpected length difference"); + } + Ok(out_settings) +} + +fn edit_osc_device(mut dev: OscDevice) -> Result { + let csv = Reaper::get() + .medium_reaper() + .get_user_inputs( + "ReaLearn", + 4, + "Name,Local port (e.g. 7878),Device host (e.g. 192.168.x.y),Device port (e.g. 7878),separator=;,extrawidth=80", + format!( + "{};{};{};{}", + dev.name(), + dev.local_port().map(|p| p.to_string()).unwrap_or_default(), + dev.device_host().map(|a| a.to_string()).unwrap_or_default(), + dev.device_port().map(|p| p.to_string()).unwrap_or_default(), + ), + 512, + ) + .ok_or(EditOscDevError::Cancelled)?; + let splitted: Vec<_> = csv.to_str().split(';').collect(); + if let [name, local_port, device_host, device_port] = splitted.as_slice() { + dev.set_name(name.to_string()); + dev.set_local_port(local_port.parse::().ok()); + dev.set_device_host(device_host.parse::().ok()); + dev.set_device_port(device_port.parse::().ok()); + Ok(dev) + } else { + Err(EditOscDevError::Unexpected("couldn't split")) + } +} + +const COMPARTMENT_CHANGES_WARNING_TEXT: &str = "Mapping/group/parameter changes in this compartment will be lost. Consider to save them first. Do you really want to continue?"; + +#[cfg(feature = "playtime")] +const EMPTY_CLIP_MATRIX_LABEL: &str = "empty Playtime matrix"; + +#[cfg(feature = "playtime")] +fn get_clip_matrix_label(column_count: usize) -> String { + format!("Playtime matrix with {column_count} columns") +} + +enum MainMenuAction { + None, + CopyListedMappingsAsJson, + CopyListedMappingsAsLua(ConversionStyle), + AutoNameListedMappings, + NameListedMappingsAfterSource, + MakeTargetsOfListedMappingsSticky, + MakeTargetsOfListedMappingsNonSticky(MakeTrackNonStickyMode, MakeFxNonStickyMode), + MakeSourcesOfMainMappingsVirtual, + MoveListedMappingsToGroup(Option), + PasteReplaceAllInGroup(Envelope>), + PasteFromLuaReplaceAllInGroup(Rc), + DryRunLuaScript(Rc), + ToggleGlobalControl, + ToggleRealInputLogging, + ToggleVirtualInputLogging, + ToggleRealOutputLogging, + ToggleVirtualOutputLogging, + ToggleTargetControlLogging, + ToggleSendFeedbackOnlyIfTrackArmed, + ToggleResetFeedbackWhenReleasingSource, + ToggleMatchEvenInactiveMappings, + ToggleUpperFloorMembership, + SetStayActiveWhenProjectInBackground(StayActiveWhenProjectInBackground), + ToggleServer, + OpenAppFolder, + ToggleBackgroundColors, + ToggleUseUnitPresetLinksOnly, + AddFirewallRule, + EditPresetLinkFxId(PresetLinkScope, FxId), + RemovePresetLink(PresetLinkScope, FxId), + LinkToPreset(PresetLinkScope, FxId, String), + ReloadAllCompartmentPresets, + OpenPotBrowser, + EditInstanceTags, + ShowApp, + CloseApp, + OpenCompartmentPresetFolder, + EditCompartmentParameter(CompartmentKind, RangeInclusive), + SendFeedbackNow, + LogDebugInfo, + EditCompartmentWideLuaCode, + CreateCompartmentPresetWorkspace, + CreateCompartmentPresetWorkspaceIncludingFactoryPresets, + ConvertToolbarToStreamDeckMappings(String), +} + +enum HelpMenuAction { + OpenRealearnOfflineReference, + OpenRealearnOnlineReference, + OpenHelgoboxWiki, + OpenRealearnControllerList, + OpenRealearnForum, + OpenRealearnWebsite, + OpenAboutPage, + ContactDeveloper, + Donate, +} + +impl Default for MainMenuAction { + fn default() -> Self { + Self::None + } +} + +#[derive(Copy, Clone)] +enum PresetLinkScope { + Global, + Instance, +} + +fn generate_fx_to_preset_links_menu_entries( + last_focused_fx_id: Option<&FxId>, + main_preset_manager: &FileBasedMainPresetManager, + config: &FxPresetLinkConfig, + scope: PresetLinkScope, +) -> Vec> { + use std::iter::once; + use swell_ui::menu_tree::*; + let add_link_entry = if let Some(fx_id) = last_focused_fx_id { + menu( + format!("", &fx_id.name), + build_compartment_preset_menu_entries( + main_preset_manager.common_preset_infos(), + move |info| { + let fx_id = fx_id.clone(); + let preset_id = info.id.clone(); + MainMenuAction::LinkToPreset(scope, fx_id, preset_id) + }, + None, + ), + ) + } else { + disabled_item("") + }; + let link_entries = config.links().map(|link| { + let fx_id_0 = link.fx_id.clone(); + let fx_id_1 = link.fx_id.clone(); + let fx_id_2 = link.fx_id.clone(); + let preset_id_0 = link.preset_id.clone(); + menu( + link.fx_id.to_string(), + once(item( + "", + MainMenuAction::EditPresetLinkFxId(scope, fx_id_0), + )) + .chain(once(item( + "", + MainMenuAction::RemovePresetLink(scope, fx_id_1), + ))) + .chain(build_compartment_preset_menu_entries( + main_preset_manager.common_preset_infos(), + move |info| { + let fx_id = fx_id_2.clone(); + let preset_id = info.id.clone(); + MainMenuAction::LinkToPreset(scope, fx_id, preset_id) + }, + Some(&preset_id_0), + )) + .chain(once( + if main_preset_manager.find_by_id(&link.preset_id).is_some() { + Entry::Nothing + } else { + disabled_item(format!(" ({})", link.preset_id)) + }, + )) + .collect(), + ) + }); + once(add_link_entry).chain(link_entries).collect() +} + +fn with_scoped_preset_link_mutator( + scope: PresetLinkScope, + session: &WeakUnitModel, + f: impl FnOnce(&mut dyn PresetLinkMutator), +) { + match scope { + PresetLinkScope::Global => { + let preset_link_manager = BackboneShell::get().preset_link_manager(); + let mut mutator = preset_link_manager.borrow_mut(); + f(mutator.deref_mut()); + } + PresetLinkScope::Instance => { + let session = session.upgrade().expect("session gone"); + let mut session = session.borrow_mut(); + let mutator = session.instance_preset_link_config_mut(); + f(mutator); + } + } +} + +fn get_osc_dev_list_label(osc_device_id: &OscDeviceId, is_output: bool) -> String { + let dev_manager = BackboneShell::get().osc_device_manager(); + let dev_manager = dev_manager.borrow(); + if let Some(dev) = dev_manager.find_device_by_id(osc_device_id) { + get_osc_device_list_label(dev, is_output) + } else { + format!("OSC: ({osc_device_id})") + } +} + +const PRESET_RELATED_MENU_LABEL: &str = "Compartment presets"; + +fn build_show_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 0, + y: 41, + width: 469, + height: 21, + color_pair: colors::show_background(), + scaling: HEADER_PANEL_SCALING, + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/help.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/help.rs new file mode 100644 index 0000000..5c4c7a8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/help.rs @@ -0,0 +1,235 @@ +use derive_more::Display; +use helgoboss_learn::ModeParameter; +use include_dir::{include_dir, Dir}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)] +pub enum HelpTopic { + Concept(ConceptTopic), + Mapping(MappingTopic), + Source(SourceTopic), + Target(TargetTopic), + Glue(ModeParameter), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)] +pub enum ConceptTopic { + Mapping, + Source, + Target, + Glue, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)] +pub enum MappingTopic { + Name, + Tags, + #[display(fmt = "Control enabled")] + ControlEnabled, + #[display(fmt = "Feedback enabled")] + FeedbackEnabled, + Active, + #[display(fmt = "Feedback mode")] + FeedbackMode, + #[display(fmt = "Show in projection")] + ShowInProjection, + #[display(fmt = "Advanced settings")] + AdvancedSettings, + #[display(fmt = "Find in mapping list")] + FindInMappingList, + #[display(fmt = "Beep on success")] + BeepOnSuccess, + #[display(fmt = "Go to previous mapping")] + PreviousMapping, + #[display(fmt = "Go to next mapping")] + NextMapping, + Enabled, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)] +pub enum SourceTopic { + Learn, + #[display(fmt = "Source category")] + Category, + #[display(fmt = "Source type")] + Type, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)] +pub enum TargetTopic { + Learn, + Menu, + #[display(fmt = "Target category")] + Category, + #[display(fmt = "Target type")] + Type, + #[display(fmt = "Current value")] + CurrentValue, + #[display(fmt = "Display unit")] + DisplayUnit, +} + +pub struct HelpTopicDetails { + pub doc_url: String, + pub description: HelpTopicDescription, +} + +pub enum HelpTopicDescription { + General(&'static str), + DirectionDependent { + control_desc: Option<&'static str>, + feedback_desc: Option<&'static str>, + }, +} + +pub static PARTIALS_DIR: Dir<'_> = + include_dir!("$CARGO_MANIFEST_DIR/../doc/realearn/modules/ROOT/partials"); + +fn get_partial_content(path: &str) -> Option<&'static str> { + let file = PARTIALS_DIR.get_file(path)?; + file.contents_utf8() +} + +impl HelpTopic { + pub fn get_details(&self) -> Option { + let (help_section, id) = self.qualified_id()?; + let help_section_id: &'static str = help_section.into(); + let details = HelpTopicDetails { + doc_url: format!("{}#{id}", help_section.doc_base_url()), + description: if let Some(content) = + get_partial_content(&format!("{help_section_id}/{id}/general.txt")) + { + HelpTopicDescription::General(content) + } else { + HelpTopicDescription::DirectionDependent { + control_desc: get_partial_content(&format!( + "{help_section_id}/{id}/control.txt" + )), + feedback_desc: get_partial_content(&format!( + "{help_section_id}/{id}/feedback.txt" + )), + } + }, + }; + Some(details) + } + + fn qualified_id(&self) -> Option<(HelpSection, &'static str)> { + let qualified_id = match self { + HelpTopic::Concept(t) => { + use ConceptTopic::*; + let id = match t { + Mapping => "mapping", + Source => "source", + Target => "target", + Glue => "glue", + }; + (HelpSection::Concept, id) + } + HelpTopic::Mapping(t) => { + use MappingTopic::*; + match t { + Name => (HelpSection::MappingTop, "name"), + Tags => (HelpSection::MappingTop, "tags"), + ControlEnabled => (HelpSection::MappingTop, "control-enabled"), + FeedbackEnabled => (HelpSection::MappingTop, "feedback-enabled"), + Active => (HelpSection::MappingTop, "active"), + FeedbackMode => (HelpSection::MappingTop, "feedback-mode"), + ShowInProjection => (HelpSection::MappingTop, "show-in-projection"), + AdvancedSettings => (HelpSection::MappingTop, "advanced-settings"), + FindInMappingList => (HelpSection::MappingTop, "find-in-mapping-list"), + BeepOnSuccess => (HelpSection::MappingBottom, "beep-on-success"), + PreviousMapping => (HelpSection::MappingBottom, "previous"), + NextMapping => (HelpSection::MappingBottom, "next"), + Enabled => (HelpSection::MappingBottom, "enabled"), + } + } + HelpTopic::Source(t) => { + use SourceTopic::*; + let id = match t { + Learn => "learn", + Category => "category", + Type => "type", + }; + (HelpSection::Source, id) + } + HelpTopic::Target(t) => { + use TargetTopic::*; + let id = match t { + Learn => "learn", + Menu => "menu", + Category => "category", + Type => "type", + CurrentValue => "current-value", + DisplayUnit => "display-unit", + }; + (HelpSection::Target, id) + } + HelpTopic::Glue(mode_parameter) => { + use ModeParameter::*; + let id = match mode_parameter { + SourceMinMax => "source-min-max", + Reverse => "reverse", + OutOfRangeBehavior => "out-of-range-behavior", + TakeoverMode => "takeover-mode", + ControlTransformation => "control-transformation", + TargetValueSequence => "value-sequence", + TargetMinMax => "target-min-max", + FeedbackType | FeedbackTransformation | TextualFeedbackExpression => { + "feedback-type" + } + StepSizeMin | StepSizeMax => "step-size-min-max", + StepFactorMin | StepFactorMax => "speed-min-max", + RelativeFilter => "encoder-filter", + Rotate => "wrap", + FireMode => "fire-mode", + ButtonFilter => "button-filter", + MakeAbsolute => "make-absolute", + RoundTargetValue => "round-target-value", + AbsoluteMode => "absolute-mode", + GroupInteraction => "group-interaction", + _ => return None, + }; + (HelpSection::Glue, id) + } + }; + Some(qualified_id) + } +} + +#[derive(Copy, Clone, strum::IntoStaticStr)] +#[strum(serialize_all = "kebab-case")] +enum HelpSection { + MappingTop, + MappingBottom, + Source, + Target, + Glue, + Concept, +} + +impl HelpSection { + fn doc_base_url(&self) -> String { + match self { + HelpSection::Concept => { + format!("{GENERAL_DOC_BASE_URL}/key-concepts.html") + } + HelpSection::MappingTop => { + format!("{GENERAL_DOC_BASE_URL}/user-interface/mapping-panel/top-section") + } + HelpSection::Source => { + format!("{GENERAL_DOC_BASE_URL}/user-interface/mapping-panel/source-section") + } + HelpSection::Target => { + format!("{GENERAL_DOC_BASE_URL}/user-interface/mapping-panel/target-section") + } + HelpSection::Glue => { + format!("{GENERAL_DOC_BASE_URL}/user-interface/mapping-panel/glue-section") + } + HelpSection::MappingBottom => { + format!("{GENERAL_DOC_BASE_URL}/user-interface/mapping-panel/bottom-section") + } + } + } +} + +const GENERAL_DOC_BASE_URL: &str = "https://docs.helgoboss.org/realearn"; diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/import.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/import.rs new file mode 100644 index 0000000..3383606 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/import.rs @@ -0,0 +1,403 @@ +use anyhow::{bail, Context}; +use std::collections::BTreeMap; +use std::error::Error; +use std::fmt::Debug; +use std::os::raw::c_void; + +use serde::{Deserialize, Serialize}; + +use crate::base::notification; +use crate::domain::{CompartmentKind, FsDirLuaModuleFinder, LuaModuleContainer, SafeLua}; +use crate::infrastructure::api::convert::from_data::ConversionStyle; +use crate::infrastructure::api::convert::to_data::ApiToDataConversionContext; +use crate::infrastructure::api::convert::{from_data, to_data}; +use crate::infrastructure::data::{ + parse_lua_frontmatter, ActivationConditionData, CompartmentModelData, InstanceData, + MappingModelData, ModeModelData, SourceModelData, TargetModelData, UnitData, +}; +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::ui::lua_serializer; +use crate::infrastructure::ui::util::open_in_browser; +use base::hash_util::NonCryptoHashSet; +use helgobox_api::persistence; +use helgobox_api::persistence::{ApiObject, CommonPresetMetaData, Envelope}; +use mlua::prelude::LuaError; +use mlua::Value; +use playtime_api::persistence::FlexibleMatrix; +use reaper_high::Reaper; +use semver::Version; + +pub enum UntaggedDataObject { + Tagged(DataObject), + PresetLike(CommonPresetData), +} + +impl UntaggedDataObject { + pub fn version(&self) -> Option<&Version> { + match self { + UntaggedDataObject::Tagged(o) => o.version(), + UntaggedDataObject::PresetLike(d) => d.version.as_ref(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum DataObject { + /// A complete Helgobox instance. + Instance(Envelope>), + /// A Helgobox unit (within an instance). + #[serde(alias = "Session")] + Unit(Envelope>), + /// A Playtime matrix. + ClipMatrix(Envelope>>), + /// Main compartment. + MainCompartment(Envelope>), + /// Controller compartment. + ControllerCompartment(Envelope>), + /// Flat list of mappings. + Mappings(Envelope>), + /// Single mapping. + Mapping(Envelope>), + /// Mapping source. + Source(Envelope>), + /// Mapping glue. + #[serde(alias = "Mode")] + Glue(Envelope>), + /// Mapping target. + Target(Envelope>), + /// Mapping activation condition. + ActivationCondition(Envelope>), +} + +/// This corresponds to the way controller and main presets are structured. +/// +/// They don't have an envelope. We also want to be able to import their data. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommonPresetData { + #[serde(default)] + pub version: Option, + pub name: String, + #[serde(flatten)] + pub data: Box, +} + +impl DataObject { + pub fn try_from_api_object( + api_object: ApiObject, + conversion_context: &impl ApiToDataConversionContext, + ) -> anyhow::Result { + let data_object = match api_object { + ApiObject::ClipMatrix(envelope) => { + if let Some(FlexibleMatrix::Unsigned(m)) = &*envelope.value { + warn_about_unknown_props("importing Playtime matrix", &m.unknown_props); + } + DataObject::ClipMatrix(envelope) + } + ApiObject::MainCompartment(Envelope { value: c, version }) => { + warn_about_unknown_props("importing main compartment", &c.unknown_props); + let data_compartment = to_data::convert_compartment(CompartmentKind::Main, *c)?; + DataObject::MainCompartment(Envelope::new(version, Box::new(data_compartment))) + } + ApiObject::ControllerCompartment(Envelope { value: c, version }) => { + warn_about_unknown_props("importing controller compartment", &c.unknown_props); + let data_compartment = + to_data::convert_compartment(CompartmentKind::Controller, *c)?; + DataObject::ControllerCompartment(Envelope::new( + version, + Box::new(data_compartment), + )) + } + ApiObject::Mappings(Envelope { + value: mappings, + version, + }) => { + let data_mappings = Self::try_from_api_mappings(mappings, conversion_context); + DataObject::Mappings(Envelope::new(version, data_mappings?)) + } + ApiObject::Mapping(Envelope { value: m, version }) => { + let data_mapping = to_data::convert_mapping(*m, conversion_context)?; + DataObject::Mapping(Envelope::new(version, Box::new(data_mapping))) + } + }; + Ok(data_object) + } + + pub fn try_from_api_mappings( + api_mappings: Vec, + conversion_context: &impl ApiToDataConversionContext, + ) -> anyhow::Result> { + api_mappings + .into_iter() + .map(|m| to_data::convert_mapping(m, conversion_context)) + .collect() + } + + pub fn try_into_api_object( + self, + conversion_style: ConversionStyle, + ) -> anyhow::Result { + let api_object = match self { + DataObject::Unit(Envelope { .. }) => todo!("session API not yet implemented"), + DataObject::ClipMatrix(envelope) => ApiObject::ClipMatrix(envelope), + DataObject::MainCompartment(Envelope { value: c, version }) => { + let api_compartment = from_data::convert_compartment(*c, conversion_style)?; + ApiObject::MainCompartment(Envelope::new(version, Box::new(api_compartment))) + } + DataObject::ControllerCompartment(Envelope { value: c, version }) => { + let api_compartment = from_data::convert_compartment(*c, conversion_style)?; + ApiObject::ControllerCompartment(Envelope::new(version, Box::new(api_compartment))) + } + DataObject::Mappings(Envelope { + value: mappings, + version, + }) => { + let api_mappings: Result, _> = mappings + .into_iter() + .map(|m| from_data::convert_mapping(m, conversion_style)) + .collect(); + ApiObject::Mappings(Envelope::new(version, api_mappings?)) + } + DataObject::Mapping(Envelope { value: m, version }) => { + let api_mapping = from_data::convert_mapping(*m, conversion_style)?; + ApiObject::Mapping(Envelope::new(version, Box::new(api_mapping))) + } + _ => { + bail!("conversion from source/mode/target data object not supported at the moment"); + } + }; + Ok(api_object) + } + + pub fn version(&self) -> Option<&Version> { + use DataObject::*; + match self { + Instance(v) => v.version.as_ref(), + Unit(v) => v.version.as_ref(), + ClipMatrix(v) => v.version.as_ref(), + MainCompartment(v) => v.version.as_ref(), + ControllerCompartment(v) => v.version.as_ref(), + Mappings(v) => v.version.as_ref(), + Mapping(v) => v.version.as_ref(), + Source(v) => v.version.as_ref(), + Glue(v) => v.version.as_ref(), + Target(v) => v.version.as_ref(), + ActivationCondition(v) => v.version.as_ref(), + } + } +} + +/// Attempts to deserialize a data object supporting both JSON and Lua. +pub fn deserialize_data_object( + text: &str, + conversion_context: &impl ApiToDataConversionContext, +) -> anyhow::Result { + // Try as JSON data object + let json_data_object_result = + serde_json::from_str::(text).map(UntaggedDataObject::Tagged); + let json_data_object_error = match json_data_object_result { + Ok(object) => return Ok(object), + Err(e) => e, + }; + // That didn't work. Try as JSON preset. + let json_preset_result = + serde_json::from_str::(text).map(UntaggedDataObject::PresetLike); + let json_preset_error = match json_preset_result { + Ok(object) => return Ok(object), + Err(e) => e, + }; + // That didn't work. Execute as Lua. + let lua = SafeLua::new()?; + let lua_execution_result = + execute_lua_import_script(&lua, text, conversion_context.compartment(), true); + let (lua_execution_error, lua_api_object_error, lua_preset_error) = match lua_execution_result { + Err(e) => (Some(e), None, None), + Ok(value) => { + // At first try deserializing as Lua API object + let lua_api_object_result = SafeLua::from_value::(value.clone()) + .and_then(|api_object| { + DataObject::try_from_api_object(api_object, conversion_context) + .context("converting API object to data object") + }) + .map(UntaggedDataObject::Tagged); + let lua_api_object_error = match lua_api_object_result { + Ok(object) => return Ok(object), + Err(e) => e, + }; + // That wasn't it. Try deserializing as Lua preset. + // We don't need the full metadata here (controller/main-preset specific), just the common one. + // Actually only the version is important because it might influence import behavior. + let lua_preset_result = parse_lua_frontmatter::(text) + .and_then(|meta_data| { + let compartment = SafeLua::from_value::(value)?; + warn_about_unknown_props("importing as Lua preset", &compartment.unknown_props); + // When importing a Lua preset, we expect at least the "mappings" property. It's not strictly necessary + // for a preset to have mappings, but when importing stuff it's important that we have good error reporting. + // If we accept pretty much all possible tables as valid Lua preset, the user will never see an error + // message when he made a grave error, e.g. providing a completely different data structure with only + // unknown properties. + compartment + .mappings + .as_ref() + .context("property \"mappings\" not provided")?; + Ok((meta_data, compartment)) + }) + .and_then(|(meta_data, compartment)| { + let compartment_data = to_data::convert_compartment( + conversion_context.compartment(), + compartment, + )?; + let common_preset_data = CommonPresetData { + version: meta_data.realearn_version, + name: meta_data.name, + data: Box::new(compartment_data), + }; + Ok(common_preset_data) + }) + .map(UntaggedDataObject::PresetLike); + let lua_preset_error = match lua_preset_result { + Ok(object) => return Ok(object), + Err(e) => e, + }; + (None, Some(lua_api_object_error), Some(lua_preset_error)) + } + }; + // Nothing fits :( + bail!( + "Clipboard content doesn't look like proper ReaLearn import data:\n\n\ + Invalid JSON API object:\n\ + {json_data_object_error}\n\n\ + Invalid JSON preset:\n\ + {json_preset_error:#}\n\n\ + Invalid Lua code:\n\ + {lua_execution_error:#?}\n\n\ + Invalid Lua API object:\n\ + {lua_api_object_error:#?}\n\n\ + Invalid Lua preset:\n\ + {lua_preset_error:#?}" + ); +} + +pub fn deserialize_data_object_from_json(text: &str) -> Result> { + Ok(serde_json::from_str(text)?) +} + +pub fn serialize_data_object_to_json(object: DataObject) -> anyhow::Result { + serde_json::to_string_pretty(&object).context("couldn't serialize object") +} + +/// Runs without importing the result and also doesn't have an execution time limit. +pub fn dry_run_lua_script(text: &str, active_compartment: CompartmentKind) -> anyhow::Result<()> { + let lua = SafeLua::new()?; + let value = execute_lua_import_script(&lua, text, active_compartment, false)?; + let json = serde_json::to_string_pretty(&value)?; + match BackboneShell::get_temp_dir() { + None => { + Reaper::get().show_console_msg(json); + } + Some(dir) => { + let json_file = dir.path().join("dry-run.json"); + std::fs::write(&json_file, json)?; + open_in_browser(&json_file.to_string_lossy()) + } + } + Ok(()) +} + +pub enum SerializationFormat { + JsonDataObject, + LuaApiObject(ConversionStyle), +} + +pub fn serialize_data_object( + data_object: DataObject, + format: SerializationFormat, +) -> anyhow::Result { + match format { + SerializationFormat::JsonDataObject => serialize_data_object_to_json(data_object), + SerializationFormat::LuaApiObject(style) => { + serialize_data_object_to_lua(data_object, style) + } + } +} + +pub fn serialize_data_object_to_lua( + data_object: DataObject, + conversion_style: ConversionStyle, +) -> anyhow::Result { + let api_object = data_object.try_into_api_object(conversion_style)?; + Ok(lua_serializer::to_string(&api_object)?) +} + +pub fn deserialize_api_object_from_lua( + text: &str, + active_compartment: CompartmentKind, +) -> anyhow::Result { + let lua = SafeLua::new()?; + let value = execute_lua_import_script(&lua, text, active_compartment, true)?; + SafeLua::from_value(value) +} + +fn verify_no_recursive_tables(value: &Value) -> Result<(), LuaError> { + verify_no_recursive_tables_internal(value, &mut Default::default(), &mut Default::default()) +} + +fn verify_no_recursive_tables_internal( + value: &Value, + visited_tables: &mut NonCryptoHashSet<*const c_void>, + key_stack: &mut Vec, +) -> Result<(), LuaError> { + if let Value::Table(t) = value { + let table_pointer = t.to_pointer(); + if !visited_tables.insert(table_pointer) { + let msg = format!("Detected recursive table at {key_stack:?}"); + return Err(LuaError::runtime(msg)); + } + t.for_each(|key: Value, value: Value| { + key_stack.push(key.to_string().unwrap_or_default()); + verify_no_recursive_tables_internal(&key, visited_tables, key_stack)?; + verify_no_recursive_tables_internal(&value, visited_tables, key_stack)?; + key_stack.pop(); + Ok(()) + })?; + visited_tables.remove(&table_pointer); + } + Ok(()) +} + +fn execute_lua_import_script( + lua: &SafeLua, + code: &str, + active_compartment: CompartmentKind, + limit_execution_time: bool, +) -> anyhow::Result { + if limit_execution_time { + lua.start_execution_time_limit_countdown(); + } + // Add support for require, but only for the logged-in user's presets. That means the module root will be the + // subdirectory within the preset directory that has the name as the logged-in user's name. + let preset_dir = BackboneShell::realearn_compartment_preset_dir_path(active_compartment); + let module_finder = FsDirLuaModuleFinder::new(preset_dir.join(whoami::username())); + let module_container = LuaModuleContainer::new(Ok(module_finder)); + let value = + module_container.execute_as_module(lua.as_ref(), None, "Import".to_string(), code)?; + // Recursive tables are always forbidden in import scenarios, so we check for them right here + verify_no_recursive_tables(&value)?; + Ok(value) +} + +fn warn_about_unknown_props( + label: &str, + unknown_props: &Option>, +) { + let Some(unknown_props) = unknown_props.as_ref() else { + return; + }; + if unknown_props.is_empty() { + return; + } + let keys: Vec<_> = unknown_props.keys().collect(); + let msg = format!("The following imported properties were ignored when {label}: {keys:?}"); + notification::warn(msg); +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/independent_panel_manager.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/independent_panel_manager.rs new file mode 100644 index 0000000..af5ad73 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/independent_panel_manager.rs @@ -0,0 +1,194 @@ +use crate::infrastructure::ui::{MappingPanel, SessionMessagePanel, UnitPanel}; +use std::cell::OnceCell; +use tracing::debug; + +use crate::application::{Affected, SharedMapping, UnitModel, UnitProp, WeakUnitModel}; +use crate::domain::{ + CompartmentKind, MappingId, MappingMatchedEvent, SourceFeedbackEvent, TargetControlEvent, + TargetValueChangedEvent, +}; +use crate::infrastructure::plugin::reaper_main_window; +use swell_ui::{SharedView, View, WeakView}; + +const MAX_PANEL_COUNT: u32 = 4; + +/// Responsible for managing the currently open top-level mapping panels. +#[derive(Debug)] +pub struct IndependentPanelManager { + unit_model: WeakUnitModel, + unit_panel: OnceCell>, + mapping_panels: Vec>, + message_panel: SharedView, +} + +impl IndependentPanelManager { + pub fn new(session: WeakUnitModel) -> IndependentPanelManager { + Self { + unit_model: session.clone(), + unit_panel: OnceCell::new(), + mapping_panels: Default::default(), + message_panel: SharedView::new(SessionMessagePanel::new(session)), + } + } + + pub fn set_main_panel(&mut self, main_panel: WeakView) { + self.unit_panel.set(main_panel).expect("can set only once") + } + + pub fn handle_changed_target_value(&self, event: TargetValueChangedEvent) { + self.do_with_mapping_panel(event.compartment, event.mapping_id, |p| { + p.handle_changed_target_value(event.targets, event.new_value) + }); + } + + pub fn handle_matched_mapping(&self, event: MappingMatchedEvent) { + self.do_with_mapping_panel(event.compartment, event.mapping_id, |p| { + p.handle_matched_mapping(); + }); + } + + pub fn handle_target_control_event(&self, event: TargetControlEvent) { + self.do_with_mapping_panel(event.id.compartment, event.id.id, |p| { + p.handle_target_control_event(event); + }); + } + + pub fn handle_source_feedback_event(&self, event: SourceFeedbackEvent) { + self.do_with_mapping_panel(event.id.compartment, event.id.id, |p| { + p.handle_source_feedback_event(event); + }); + } + + pub fn handle_affected(&self, affected: &Affected, initiator: Option) { + for p in self.mapping_panels.iter().filter(|p| p.is_open()) { + p.handle_affected(affected, initiator); + } + } + + fn do_with_mapping_panel( + &self, + compartment: CompartmentKind, + mapping_id: MappingId, + f: impl Fn(SharedView), + ) { + for p in &self.mapping_panels { + if let Some(m) = p.displayed_mapping() { + let is_our_mapping = { + let m = m.borrow(); + m.compartment() == compartment && m.id() == mapping_id + }; + if is_our_mapping { + f(p.clone()); + } + } + } + } + + pub fn handle_changed_parameters(&self, session: &UnitModel) { + for p in &self.mapping_panels { + let _ = p.clone().notify_parameters_changed(session); + } + } + + pub fn handle_changed_conditions(&self) { + for p in &self.mapping_panels { + p.clone().handle_changed_conditions(); + } + } + + pub fn open_message_panel(&self) { + self.message_panel.clone().open(reaper_main_window()); + } + + pub fn close_message_panel(&self) { + self.message_panel.clone().close(); + } + + /// Opens a panel for editing the given mapping. + /// + /// If the window is already open, it will be closed and reopened. + pub fn edit_mapping(&mut self, mapping: &SharedMapping) -> SharedView { + let existing_panel = self + .mapping_panels + .iter() + .find(|p| p.mapping_ptr() == mapping.as_ptr()); + if let Some(p) = existing_panel { + // There's a panel already which show's this mapping. + p.bring_to_foreground(); + return p.clone(); + } + let panel = self.request_panel(); + panel.clone().show(mapping.clone()); + panel + } + + /// Hides panels of mappings which don't exist anymore. + pub fn close_orphan_panels(&mut self) { + let shared_session = self.unit_model.upgrade().expect("session gone"); + let session = shared_session.borrow(); + for p in &self.mapping_panels { + if !session.has_mapping(p.mapping_ptr()) { + p.hide(); + } + } + } + + /// Closes and removes all independent panels + fn destroy(&mut self) { + self.message_panel.close(); + for p in &self.mapping_panels { + p.close() + } + self.mapping_panels.clear(); + } + + fn request_panel(&mut self) -> SharedView { + self.find_free_panel() + .or_else(|| self.create_new_panel_if_not_exhausted()) + .unwrap_or_else(|| self.hijack_existing_panel()) + } + + fn find_free_panel(&self) -> Option> { + self.mapping_panels.iter().find(|p| p.is_free()).cloned() + } + + fn create_new_panel_if_not_exhausted(&mut self) -> Option> { + if self.mapping_panels.len() < MAX_PANEL_COUNT as _ { + Some(self.create_new_panel()) + } else { + None + } + } + + fn main_panel(&self) -> &WeakView { + self.unit_panel.get().expect("main panel not set") + } + + fn create_new_panel(&mut self) -> SharedView { + let panel = SharedView::new(MappingPanel::new( + self.unit_model.clone(), + self.main_panel().clone(), + )); + let panel_clone_1 = panel.clone(); + let panel_clone_2 = panel.clone(); + self.mapping_panels.push(panel); + panel_clone_1.open(reaper_main_window()); + panel_clone_2 + } + + fn hijack_existing_panel(&self) -> SharedView { + self.mapping_panels + .first() + .expect("no existing panel") + .clone() + } +} + +impl Drop for IndependentPanelManager { + fn drop(&mut self) { + debug!("Dropping mapping panel manager..."); + // Those are (intentionally) REAPER child windows, not ReaLearn child windows. So we need to + // close them manually as soon as ReaLearn is unloaded. + self.destroy(); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/instance_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/instance_panel.rs new file mode 100644 index 0000000..5707878 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/instance_panel.rs @@ -0,0 +1,348 @@ +use crate::infrastructure::ui::{build_unit_label, util, AppPage, UnitPanel}; +use anyhow::Context; +use reaper_medium::Hbrush; +use std::cell::{Cell, OnceCell, RefCell}; +use std::fmt::Debug; +use std::rc::Weak; +use std::sync; +use std::sync::Arc; + +use crate::application::{Affected, InstanceProp, InstanceUi, SharedInstanceModel}; +use crate::domain::{InstanceId, UnitId}; +use crate::infrastructure::plugin::{ + reaper_main_window, BackboneShell, InstanceShell, SharedInstanceShell, +}; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::util::colors; +use swell_ui::{DeviceContext, Dimensions, Pixels, SharedView, View, ViewContext, Window}; + +#[derive(Debug)] +pub struct InstancePanel { + view: ViewContext, + dimensions: Cell>>, + shell: OnceCell>, + /// This is necessary *only* to keep the currently displayed view in memory shortly after its + /// unit has been removed. Without that, the view manager would not be able to upgrade the + /// weak view for the last few dispatched calls. + displayed_unit_panel: RefCell>>, + displayed_unit_id: Cell>, + /// We have at most one app instance open per ReaLearn instance. + app_instance: crate::infrastructure::ui::SharedAppInstance, +} + +impl Drop for InstancePanel { + fn drop(&mut self) { + tracing::debug!("Dropping InstancePanel..."); + let _ = self.app_instance.borrow_mut().stop(); + } +} + +impl InstancePanel { + pub fn new(instance_id: InstanceId) -> InstancePanel { + InstancePanel { + view: Default::default(), + shell: OnceCell::new(), + dimensions: None.into(), + displayed_unit_id: Default::default(), + app_instance: crate::infrastructure::ui::create_shared_app_instance(instance_id), + displayed_unit_panel: RefCell::new(None), + } + } + + pub fn app_instance(&self) -> &crate::infrastructure::ui::SharedAppInstance { + &self.app_instance + } + + pub fn toggle_app_instance_focus(&self) { + let mut app_instance = self.app_instance.borrow_mut(); + if app_instance.has_focus() { + reaper_main_window().focus(); + } else { + let _ = app_instance.start_or_show(reaper_main_window(), None); + } + } + + pub fn start_or_show_app_instance(&self, page: Option) { + let result = self + .app_instance + .borrow_mut() + .start_or_show(reaper_main_window(), page); + crate::base::notification::notify_user_on_anyhow_error(result); + } + + pub fn start_show_or_hide_app_instance(&self, page: Option) { + if self.app_instance.borrow_mut().is_visible() { + self.hide_app_instance(); + } else { + self.start_or_show_app_instance(page); + } + } + + pub fn hide_app_instance(&self) { + let _ = self.app_instance.borrow_mut().hide(); + } + + pub fn stop_app_instance(&self) { + let _ = self.app_instance.borrow_mut().stop(); + } + + pub fn app_instance_is_running(&self) -> bool { + self.app_instance.borrow().is_running() + } + + pub fn dimensions(&self) -> Dimensions { + self.dimensions + .get() + .unwrap_or_else(|| util::main_panel_dimensions().in_pixels()) + } + + pub fn open_with_resize(self: SharedView, parent_window: Window) -> Option { + #[cfg(target_family = "windows")] + { + // On Windows, the first time opening the dialog window is just to determine the best + // dimensions based on HiDPI settings. + // TODO-low If we skip this, the dimensions would be saved. Wouldn't that be better? + // I guess if there are multiple screens, keeping this line is better. Then it's a + // matter of reopening the GUI to improve scaling. Test it! + self.dimensions.replace(None); + } + self.open(parent_window) + } + + pub fn notify_shell_available(&self, shell: Arc) { + self.displayed_unit_id.set(None); + self.shell + .set(sync::Arc::downgrade(&shell)) + .expect("instance shell already set"); + if let Some(window) = self.view.window() { + // Window is currently open. Add main unit panel as subview. + let main_unit_panel = shell.main_unit_shell().panel().clone(); + main_unit_panel.clone().open(window); + } + } + + pub fn displayed_unit_id(&self) -> Option { + self.displayed_unit_id.get() + } + + pub fn open_unit_popup_menu(&self) { + #[allow(clippy::enum_variant_names)] + enum MenuAction { + RemoveCurrentUnit, + ShowUnit(Option), + AddUnit, + } + let menu = { + use swell_ui::menu_tree::*; + let shell = self.shell().expect("shell should be available"); + let additional_unit_models = shell.additional_unit_models(); + let displayed_unit_id = self.displayed_unit_id.get(); + let main_unit_model = shell.main_unit_shell().model().borrow(); + anonymous_menu( + [ + item_with_opts( + "Remove current unit", + ItemOpts { + enabled: displayed_unit_id.is_some(), + checked: false, + }, + MenuAction::RemoveCurrentUnit, + ), + separator(), + item_with_opts( + build_unit_label(&main_unit_model, None, None), + ItemOpts { + enabled: true, + checked: displayed_unit_id.is_none(), + }, + MenuAction::ShowUnit(None), + ), + ] + .into_iter() + .chain( + additional_unit_models + .iter() + .enumerate() + .map(|(i, unit_model)| { + let unit_model = unit_model.borrow(); + let unit_id = unit_model.unit_id(); + item_with_opts( + build_unit_label(&unit_model, Some(i), None), + ItemOpts { + enabled: true, + checked: displayed_unit_id == Some(unit_id), + }, + MenuAction::ShowUnit(Some(unit_id)), + ) + }), + ) + .chain([separator(), item("Add unit", MenuAction::AddUnit)]) + .collect(), + ) + }; + let action = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(action) = action { + match action { + MenuAction::RemoveCurrentUnit => self.remove_current_unit(), + MenuAction::ShowUnit(id) => self.show_unit(id), + MenuAction::AddUnit => { + self.add_unit(); + } + } + } + } + + fn add_unit(&self) { + let shell = self.shell().unwrap(); + let id = shell.add_unit(); + self.show_unit(Some(id)); + } + + pub fn notify_units_changed(&self) { + // The currently displayed unit might have been removed. + if self.view.window().is_none() { + // Window not open, nothing to worry + return; + } + let Some(id) = self.displayed_unit_id.get() else { + // The main unit can't be removed, we are safe + return; + }; + if !self.shell().unwrap().unit_exists(id) { + // The currently displayed unit doesn't exist anymore. Show main unit. + self.show_unit(None); + } + } + + fn remove_current_unit(&self) { + let Some(id) = self.displayed_unit_id.get() else { + // The main unit can't be removed + return; + }; + let _ = self.shell().unwrap().remove_unit(id); + } + + pub fn show_unit(&self, unit_id: Option) { + self.displayed_unit_id.set(unit_id); + if let Some(window) = self.view.window() { + if let Some(child_window) = window.first_child() { + tracing::debug!("Closing previous unit window"); + child_window.close(); + } + let shell = self.shell().expect("shell gone"); + self.open_unit_panel_as_child(&shell, window); + } + } + + fn open_unit_panel_as_child(&self, shell: &SharedInstanceShell, parent_window: Window) { + let panel = shell + .find_unit_panel_by_id(self.displayed_unit_id.get()) + .unwrap_or_else(|| { + self.displayed_unit_id.set(None); + shell.main_unit_shell().panel().clone() + }); + self.displayed_unit_panel + .borrow_mut() + .replace(panel.clone()); + panel.clone().open(parent_window); + } + + /// # Errors + /// + /// Returns an error if the instance shell is not set yet or gone. + pub fn instance_model(&self) -> anyhow::Result { + Ok(self.shell()?.model().clone()) + } + + /// # Errors + /// + /// Returns an error if the instance shell is not set yet or gone. + pub fn shell(&self) -> anyhow::Result> { + self.shell + .get() + .context("instance shell not yet set")? + .upgrade() + .context("instance shell gone") + } + + fn handle_affected( + self: SharedView, + affected: Affected, + _initiator: Option, + ) -> anyhow::Result<()> { + let unit_panel = self + .displayed_unit_panel + .borrow() + .clone() + .context("get displayed unit panel")?; + match affected { + Affected::One(InstanceProp::Tags) => { + unit_panel.invalidate_instance_tags(); + } + _ => {} + } + Ok(()) + } +} + +impl InstanceUi for Weak { + fn handle_affected( + &self, + affected: Affected, + initiator: Option, + ) -> anyhow::Result<()> { + let panel = self.upgrade().context("instance panel gone")?; + panel.handle_affected(affected, initiator) + } +} + +impl View for InstancePanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_INSTANCE_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + #[cfg(target_family = "windows")] + if self.dimensions.get().is_none() { + // The dialog has been opened by user request but the optimal dimensions have not yet + // been figured out. Figure them out now. + self.dimensions.replace(Some( + window.convert_to_pixels(util::main_panel_dimensions()), + )); + // Close and reopen window, this time with `dimensions()` returning the optimal size to + // the host. + let parent_window = window.parent().expect("must have parent"); + window.destroy(); + self.open(parent_window); + return false; + } + // Optimal dimensions have been calculated and window has been reopened. Now add sub panels! + if let Ok(shell) = self.shell() { + self.open_unit_panel_as_child(&shell, window); + } + true + } + + fn on_destroy(self: SharedView, _window: Window) { + // No need to keep currently displayed unit panel in memory when window closed + self.displayed_unit_panel.borrow_mut().take(); + } + + fn control_color_dialog( + self: SharedView, + _device_context: DeviceContext, + _window: Window, + ) -> Option { + if !BackboneShell::get().config().background_colors_enabled() { + return None; + } + util::view::get_brush_for_color_pair(colors::instance_panel_background()) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/lua_serializer.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/lua_serializer.rs new file mode 100644 index 0000000..ab00bbd --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/lua_serializer.rs @@ -0,0 +1,430 @@ +use serde::ser::Impossible; +use serde::{ser, Serialize}; +use std::fmt::{Display, Formatter}; + +pub type Result = std::result::Result; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + Message(String), + Unsupported(&'static str), +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use Error::*; + match self { + Message(m) => f.write_str(m), + Unsupported(kind) => { + write!(f, "serializing {kind} is currently not supported") + } + } + } +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl std::error::Error for Error {} + +pub struct Serializer { + output: String, + current_indent: usize, + has_value: bool, + serialize_string_as_map_key: bool, + indent: &'static str, +} + +pub fn to_string(value: &T) -> Result +where + T: Serialize + ?Sized, +{ + let mut serializer = Serializer { + output: String::new(), + current_indent: 0, + has_value: false, + serialize_string_as_map_key: false, + indent: " ", + }; + value.serialize(&mut serializer)?; + Ok(serializer.output) +} + +impl ser::Serializer for &mut Serializer { + type Ok = (); + type Error = Error; + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Impossible<(), Error>; + type SerializeMap = Self; + type SerializeStruct = Self; + type SerializeStructVariant = Impossible<(), Error>; + + fn serialize_bool(self, v: bool) -> Result<()> { + self.output += if v { "true" } else { "false" }; + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result<()> { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i16(self, v: i16) -> Result<()> { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i32(self, v: i32) -> Result<()> { + self.serialize_i64(i64::from(v)) + } + + // Not particularly efficient but this is example code anyway. A more + // performant approach would be to use the `itoa` crate. + fn serialize_i64(self, v: i64) -> Result<()> { + self.output += &v.to_string(); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result<()> { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u16(self, v: u16) -> Result<()> { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u32(self, v: u32) -> Result<()> { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u64(self, v: u64) -> Result<()> { + self.output += &v.to_string(); + Ok(()) + } + + fn serialize_f32(self, v: f32) -> Result<()> { + self.serialize_f64(f64::from(v)) + } + + fn serialize_f64(self, v: f64) -> Result<()> { + self.output += &v.to_string(); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result<()> { + self.serialize_str(&v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result<()> { + if self.serialize_string_as_map_key { + write_map_key(&mut self.output, v); + } else { + let contains_newlines = v.contains(&['\r', '\n'][..]); + if contains_newlines { + self.output += "[[\n"; + self.output += v; + self.output += "]]"; + } else { + self.output += "\""; + self.output.extend(v.escape_default()); + self.output += "\""; + } + } + Ok(()) + } + + fn serialize_bytes(self, v: &[u8]) -> Result<()> { + use serde::ser::SerializeSeq; + let mut seq = self.serialize_seq(Some(v.len()))?; + for byte in v { + seq.serialize_element(byte)?; + } + seq.end() + } + + fn serialize_none(self) -> Result<()> { + self.output += "nil"; + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result<()> { + Err(Error::Unsupported("unit")) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + Err(Error::Unsupported("unit struct")) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result<()> { + self.serialize_str(variant) + } + + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(Error::Unsupported("newtype variant")) + } + + fn serialize_seq(self, len: Option) -> Result { + self.has_value = false; + let len = len.ok_or_else(|| Error::Message("length of seq not given".to_string()))?; + if len > 0 { + self.current_indent += 1; + self.output += "{"; + } + Ok(self) + } + + fn serialize_tuple(self, _len: usize) -> Result { + self.output += "{"; + Ok(self) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + self.output += "{"; + Ok(self) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::Unsupported("tuple variant")) + } + + fn serialize_map(self, _len: Option) -> Result { + self.current_indent += 1; + self.has_value = false; + self.output += "{"; + Ok(self) + } + + fn serialize_struct(self, _name: &'static str, len: usize) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::Unsupported("struct variant")) + } +} + +impl ser::SerializeSeq for &mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.output += "\n"; + indent(&mut self.output, self.current_indent, self.indent); + value.serialize(&mut **self)?; + self.output += ","; + self.has_value = true; + Ok(()) + } + + fn end(self) -> Result<()> { + if self.has_value { + self.current_indent -= 1; + self.output += "\n"; + indent(&mut self.output, self.current_indent, self.indent); + self.output += "}"; + } else { + // It's important to not encode an empty sequence as "{}" because this will + // be interpreted as map on deserialization. + // Error message: "invalid type: map, expected a sequence". + // Solution: Use "nil". This requires the sequence (Vec) to be optional! So each + // Vec needs to be wrapped in an Optional. + self.output += "nil"; + } + Ok(()) + } +} + +impl ser::SerializeTuple for &mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> std::result::Result<(), Self::Error> + where + T: Serialize + ?Sized, + { + if !self.output.ends_with('{') { + self.output += ", "; + } + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + self.output += "}"; + Ok(()) + } +} + +impl ser::SerializeTupleStruct for &mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('{') { + self.output += ", "; + } + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + self.output += "}"; + Ok(()) + } +} + +impl ser::SerializeMap for &mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.output += "\n"; + indent(&mut self.output, self.current_indent, self.indent); + self.serialize_string_as_map_key = true; + key.serialize(&mut **self)?; + self.serialize_string_as_map_key = false; + self.has_value = true; + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.output += " = "; + value.serialize(&mut **self)?; + self.output += ","; + Ok(()) + } + + fn end(self) -> Result<()> { + self.current_indent -= 1; + if self.has_value { + self.output += "\n"; + indent(&mut self.output, self.current_indent, self.indent); + } + self.output += "}"; + Ok(()) + } +} + +impl ser::SerializeStruct for &mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + ensure_proper_identifier(key)?; + self.output += "\n"; + indent(&mut self.output, self.current_indent, self.indent); + self.output += key; + self.output += " = "; + value.serialize(&mut **self)?; + self.output += ","; + self.has_value = true; + Ok(()) + } + + fn end(self) -> Result<()> { + self.current_indent -= 1; + if self.has_value { + self.output += "\n"; + indent(&mut self.output, self.current_indent, self.indent); + } + self.output += "}"; + Ok(()) + } +} + +fn indent(wr: &mut String, n: usize, s: &str) { + for _ in 0..n { + wr.push_str(s); + } +} + +fn write_map_key(wr: &mut String, v: &str) { + if ensure_proper_identifier(v).is_ok() { + wr.push_str(v); + } else { + wr.push_str("[\""); + wr.push_str(v); + wr.push_str("\"]"); + } +} + +fn ensure_proper_identifier(v: &str) -> Result<()> { + fn is_identifier_char(ch: char) -> bool { + ch.is_ascii_alphanumeric() || ch == '_' + } + let contains_non_identifier_chars = v.contains(|ch: char| !is_identifier_char(ch)); + if contains_non_identifier_chars { + return Err(Error::Message(format!( + "can't serialize string {v:?} as identifier" + ))); + } + if LUA_KEYWORDS.contains(&v) { + return Err(Error::Message(format!("{v:?} is a Lua identifier"))); + } + Ok(()) +} + +const LUA_KEYWORDS: [&str; 21] = [ + "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", + "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", +]; diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_header_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_header_panel.rs new file mode 100644 index 0000000..efd310c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_header_panel.rs @@ -0,0 +1,1030 @@ +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::util::{parse_tags_from_csv, symbols}; + +use std::cell::{Cell, RefCell}; +use std::convert::TryInto; + +use std::rc::{Rc, Weak}; + +use crate::application::{ + ActivationConditionCommand, ActivationConditionProp, ActivationType, BankConditionModel, + GroupCommand, GroupModel, MappingCommand, MappingModel, ModifierConditionModel, + SharedUnitModel, UnitModel, WeakUnitModel, +}; +use crate::domain::ui_util::format_tags_as_csv; +use crate::domain::{CompartmentKind, MappingId, Tag}; +use crate::infrastructure::ui::menus; +use derivative::Derivative; +use std::fmt::Debug; +use strum::IntoEnumIterator; +use swell_ui::{DialogUnits, Point, SharedView, View, ViewContext, WeakView, Window}; + +type SharedItem = Rc>; +type WeakItem = Weak>; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct MappingHeaderPanel { + view: ViewContext, + session: WeakUnitModel, + item: RefCell>, + is_invoked_programmatically: Cell, + position: Point, + #[derivative(Debug = "ignore")] + parent_view: RefCell>>, +} + +pub trait Item: Debug { + fn compartment(&self) -> CompartmentKind; + fn supports_name_change(&self) -> bool; + fn name(&self) -> &str; + fn set_name(&mut self, session: WeakUnitModel, name: String, initiator: u32); + fn tags(&self) -> &[Tag]; + fn set_tags(&mut self, session: WeakUnitModel, tags: Vec, initiator: u32); + fn control_is_enabled(&self) -> bool; + fn set_control_is_enabled(&mut self, session: WeakUnitModel, value: bool); + fn feedback_is_enabled(&self) -> bool; + fn set_feedback_is_enabled(&mut self, session: WeakUnitModel, value: bool); + fn activation_type(&self) -> ActivationType; + fn set_activation_type(&mut self, session: WeakUnitModel, value: ActivationType); + fn modifier_condition_1(&self) -> ModifierConditionModel; + fn set_modifier_condition_1(&mut self, session: WeakUnitModel, value: ModifierConditionModel); + fn modifier_condition_2(&self) -> ModifierConditionModel; + fn set_modifier_condition_2(&mut self, session: WeakUnitModel, value: ModifierConditionModel); + fn bank_condition(&self) -> BankConditionModel; + fn set_bank_condition(&mut self, session: WeakUnitModel, value: BankConditionModel); + fn script(&self) -> &str; + fn set_script(&mut self, session: WeakUnitModel, value: String, initiator: u32); + fn mapping_id(&self) -> Option; + fn set_mapping_id(&mut self, session: WeakUnitModel, value: Option); +} + +pub enum ItemProp { + Name, + Tags, + ControlEnabled, + FeedbackEnabled, + ActivationType, + ModifierCondition1, + ModifierCondition2, + BankCondition, + Script, + MappingId, +} + +impl ItemProp { + pub fn from_activation_condition_prop(prop: &ActivationConditionProp) -> Self { + use ActivationConditionProp as S; + match prop { + S::ActivationType => Self::ActivationType, + S::ModifierCondition1 => Self::ModifierCondition1, + S::ModifierCondition2 => Self::ModifierCondition2, + S::BankCondition => Self::BankCondition, + S::Script => Self::Script, + S::MappingId => Self::MappingId, + } + } +} + +impl MappingHeaderPanel { + pub fn new( + session: WeakUnitModel, + position: Point, + initial_item: Option, + ) -> MappingHeaderPanel { + MappingHeaderPanel { + view: Default::default(), + session, + item: RefCell::new(initial_item), + is_invoked_programmatically: false.into(), + position, + parent_view: Default::default(), + } + } + + pub fn set_parent_view(&self, parent_view: WeakView) { + self.parent_view.replace(Some(parent_view)); + } + + fn get_parent_view(&self) -> Option> { + self.parent_view.borrow().as_ref().and_then(|v| v.upgrade()) + } + + pub fn set_invoked_programmatically(&self, value: bool) { + self.is_invoked_programmatically.set(value); + } + + pub fn clear_item(&self) { + self.item.replace(None); + } + + pub fn set_item(self: SharedView, item: SharedItem) { + self.invoke_programmatically(|| { + self.invalidate_controls_internal(&*item.borrow()); + self.item.replace(Some(Rc::downgrade(&item))); + // If this is the first time the window is opened, the following is unnecessary, but if + // we reuse a window it's important to reset focus for better keyboard control. + self.view + .require_control(root::ID_MAPPING_NAME_EDIT_CONTROL) + .focus(); + }); + } + + /// If you know a function in this view can be invoked by something else than the dialog + /// process, wrap your function body with this. Basically all pub functions! + /// + /// This prevents edit control text change events fired by windows to be processed. + fn invoke_programmatically(&self, f: impl FnOnce()) { + self.is_invoked_programmatically.set(true); + scopeguard::defer! { self.is_invoked_programmatically.set(false); } + f(); + } + + pub fn invalidate_controls(&self) { + self.with_item_if_set(Self::invalidate_controls_internal); + } + + fn invalidate_controls_internal(&self, item: &dyn Item) { + self.invalidate_name_edit_control(item, None); + self.invalidate_tags_edit_control(item, None); + self.invalidate_control_enabled_check_box(item); + self.invalidate_feedback_enabled_check_box(item); + self.invalidate_activation_controls(item); + } + + fn init_controls(&self) { + self.view + .require_control(root::ID_MAPPING_CONTROL_ENABLED_CHECK_BOX) + .set_text(format!("{} Control", symbols::arrow_right_symbol())); + self.view + .require_control(root::ID_MAPPING_FEEDBACK_ENABLED_CHECK_BOX) + .set_text(format!("{} Feedback", symbols::arrow_left_symbol())); + self.view + .require_control(root::ID_MAPPING_ACTIVATION_TYPE_COMBO_BOX) + .fill_combo_box_indexed(ActivationType::iter()); + self.invalidate_controls(); + } + + fn invalidate_name_edit_control(&self, item: &dyn Item, initiator: Option) { + if initiator == Some(root::ID_MAPPING_NAME_EDIT_CONTROL) { + return; + } + let c = self + .view + .require_control(root::ID_MAPPING_NAME_EDIT_CONTROL); + c.set_text(item.name()); + c.set_enabled(item.supports_name_change()); + } + + fn invalidate_tags_edit_control(&self, item: &dyn Item, initiator: Option) { + if initiator == Some(root::ID_MAPPING_TAGS_EDIT_CONTROL) { + return; + } + let c = self + .view + .require_control(root::ID_MAPPING_TAGS_EDIT_CONTROL); + c.set_text(format_tags_as_csv(item.tags())); + } + + fn invalidate_control_enabled_check_box(&self, item: &dyn Item) { + self.view + .require_control(root::ID_MAPPING_CONTROL_ENABLED_CHECK_BOX) + .set_checked(item.control_is_enabled()); + } + + fn invalidate_feedback_enabled_check_box(&self, item: &dyn Item) { + self.view + .require_control(root::ID_MAPPING_FEEDBACK_ENABLED_CHECK_BOX) + .set_checked(item.feedback_is_enabled()); + } + + fn invalidate_activation_controls(&self, item: &dyn Item) { + self.invalidate_activation_type_combo_box(item); + self.invalidate_activation_setting_1_controls(item); + self.invalidate_activation_setting_2_controls(item, None); + } + + fn invalidate_activation_type_combo_box(&self, item: &dyn Item) { + self.view + .require_control(root::ID_MAPPING_ACTIVATION_TYPE_COMBO_BOX) + .select_combo_box_item_by_index(item.activation_type().into()); + } + + fn invalidate_activation_setting_1_controls(&self, item: &dyn Item) { + let session = self.session(); + let session = session.borrow(); + let button = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_1_BUTTON); + let check_box = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_1_CHECK_BOX); + use ActivationType::*; + let label = match item.activation_type() { + Modifiers => { + self.invalidate_mapping_activation_modifier_controls( + &session, + button, + check_box, + item.compartment(), + item.modifier_condition_1(), + ); + Some("Modifier A") + } + Bank => { + button.show(); + check_box.hide(); + let text = menus::get_optional_param_name( + &session, + item.compartment(), + Some(item.bank_condition().param_index()), + ); + button.set_text(text); + Some("Parameter") + } + TargetValue => { + button.show(); + check_box.hide(); + let text = if let Some(mapping_id) = item.mapping_id() { + if let Some(mapping) = + session.find_mapping_by_id(item.compartment(), mapping_id) + { + let mapping = mapping.borrow(); + let group = session.find_group_by_id_including_default_group( + item.compartment(), + mapping.group_id(), + ); + let group_name = if let Some(group) = group { + group.borrow().effective_name().to_string() + } else { + "".to_string() + }; + format!("{} - {}", group_name, mapping.effective_name()) + } else { + "".to_string() + } + } else { + menus::NONE.to_string() + }; + button.set_text(text); + Some("Mapping") + } + _ => { + button.hide(); + check_box.hide(); + None + } + }; + self.view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_1_LABEL_TEXT) + .set_text_or_hide(label); + } + + fn invalidate_activation_setting_2_controls(&self, item: &dyn Item, initiator: Option) { + if initiator == Some(root::ID_MAPPING_ACTIVATION_EDIT_CONTROL) { + return; + } + let session = self.session(); + let session = session.borrow(); + let button = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_2_BUTTON); + let check_box = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_2_CHECK_BOX); + let edit_control = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_EDIT_CONTROL); + use ActivationType::*; + let label = match item.activation_type() { + Modifiers => { + self.invalidate_mapping_activation_modifier_controls( + &session, + button, + check_box, + item.compartment(), + item.modifier_condition_2(), + ); + edit_control.hide(); + Some("Modifier B") + } + Bank => { + button.show(); + check_box.hide(); + edit_control.hide(); + let bank_index = item.bank_condition().bank_index(); + let text = menus::get_bank_name( + &session, + item, + item.bank_condition().param_index, + bank_index, + ); + button.set_text(text); + Some("Bank") + } + TargetValue => { + button.hide(); + check_box.hide(); + edit_control.show(); + edit_control.set_text(item.script()); + Some("Ex: y > 0") + } + Eel => { + button.hide(); + check_box.hide(); + edit_control.show(); + edit_control.set_text(item.script()); + Some("Ex: y = p1 > 0") + } + Expression => { + button.hide(); + check_box.hide(); + edit_control.show(); + edit_control.set_text(item.script()); + Some("Ex: p[0] == 2") + } + Always => { + button.hide(); + check_box.hide(); + edit_control.hide(); + None + } + }; + self.view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_2_LABEL_TEXT) + .set_text_or_hide(label); + } + + fn invalidate_mapping_activation_modifier_controls( + &self, + session: &UnitModel, + button: Window, + check_box: Window, + compartment: CompartmentKind, + modifier_condition: ModifierConditionModel, + ) { + check_box.show(); + button.show(); + let text = + menus::get_optional_param_name(session, compartment, modifier_condition.param_index()); + button.set_text(text); + check_box.set_checked(modifier_condition.is_on()); + } + + fn is_invoked_programmatically(&self) -> bool { + self.is_invoked_programmatically.get() + } + + fn update_control_enabled(&self, session: WeakUnitModel, item: &mut dyn Item) { + item.set_control_is_enabled( + session, + self.view + .require_control(root::ID_MAPPING_CONTROL_ENABLED_CHECK_BOX) + .is_checked(), + ); + } + + fn update_feedback_enabled(&self, session: WeakUnitModel, item: &mut dyn Item) { + item.set_feedback_is_enabled( + session, + self.view + .require_control(root::ID_MAPPING_FEEDBACK_ENABLED_CHECK_BOX) + .is_checked(), + ); + } + + fn update_activation_setting_1_on(&self, session: WeakUnitModel, item: &mut dyn Item) { + let checked = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_1_CHECK_BOX) + .is_checked(); + item.set_modifier_condition_1(session, item.modifier_condition_1().with_is_on(checked)); + } + + fn update_activation_setting_2_on(&self, session: WeakUnitModel, item: &mut dyn Item) { + let checked = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_SETTING_2_CHECK_BOX) + .is_checked(); + item.set_modifier_condition_2(session, item.modifier_condition_2().with_is_on(checked)); + } + + fn update_name(&self, session: WeakUnitModel, item: &mut dyn Item) { + let value = self + .view + .require_control(root::ID_MAPPING_NAME_EDIT_CONTROL) + .text() + .unwrap_or_else(|_| "".to_string()); + item.set_name(session, value, root::ID_MAPPING_NAME_EDIT_CONTROL); + } + + fn update_tags(&self, session: WeakUnitModel, item: &mut dyn Item) { + let value = self + .view + .require_control(root::ID_MAPPING_TAGS_EDIT_CONTROL) + .text() + .unwrap_or_else(|_| "".to_string()); + item.set_tags( + session, + parse_tags_from_csv(&value), + root::ID_MAPPING_TAGS_EDIT_CONTROL, + ); + } + + fn update_activation_script(&self, session: WeakUnitModel, item: &mut dyn Item) { + let value = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_EDIT_CONTROL) + .text() + .unwrap_or_else(|_| "".to_string()); + item.set_script(session, value, root::ID_MAPPING_ACTIVATION_EDIT_CONTROL); + } + + fn update_activation_type(&self, session: WeakUnitModel, item: &mut dyn Item) { + let b = self + .view + .require_control(root::ID_MAPPING_ACTIVATION_TYPE_COMBO_BOX); + item.set_activation_type( + session, + b.selected_combo_box_item_index() + .try_into() + .expect("invalid activation type"), + ); + } + + fn pick_activation_setting_1_option(&self, session: WeakUnitModel, item: SharedItem) { + use ActivationType::*; + let activation_type = item.borrow().activation_type(); + match activation_type { + Modifiers => self.pick_modifier_condition_param( + session, + item, + |item| item.modifier_condition_1(), + |session, item, value| item.set_modifier_condition_1(session, value), + ), + Bank => { + let (bank_condition, menu) = { + let item = item.borrow(); + let bank_condition = item.bank_condition(); + let menu = menus::menu_containing_realearn_params( + &session, + item.compartment(), + bank_condition.param_index, + ); + (bank_condition, menu) + }; + let result = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(param_index) = result { + item.borrow_mut() + .set_bank_condition(session, bank_condition.with_param_index(param_index)); + } + } + TargetValue => { + let menu = { + let item = item.borrow(); + menus::menu_containing_mappings(&session, item.compartment(), item.mapping_id()) + }; + let result = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(mapping_id) = result { + item.borrow_mut().set_mapping_id(session, mapping_id); + } + } + _ => {} + } + } + + fn pick_activation_setting_2_option(&self, session: WeakUnitModel, item: SharedItem) { + use ActivationType::*; + let activation_type = item.borrow().activation_type(); + match activation_type { + Modifiers => self.pick_modifier_condition_param( + session, + item, + |item| item.modifier_condition_2(), + |session, item, value| item.set_modifier_condition_2(session, value), + ), + Bank => { + let (bank_condition, menu) = { + let item = item.borrow(); + let bank_condition = item.bank_condition(); + let menu = menus::menu_containing_banks( + &session, + item.compartment(), + bank_condition.param_index, + bank_condition.bank_index, + ); + (bank_condition, menu) + }; + let result = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(bank_index) = result { + item.borrow_mut() + .set_bank_condition(session, bank_condition.with_bank_index(bank_index)); + } + } + _ => {} + }; + } + + fn pick_modifier_condition_param( + &self, + session: WeakUnitModel, + item: SharedItem, + get: impl FnOnce(&dyn Item) -> ModifierConditionModel, + set: impl FnOnce(WeakUnitModel, &mut dyn Item, ModifierConditionModel), + ) { + let (modifier_condition, menu) = { + let item = item.borrow(); + let modifier_condition = get(&*item); + let menu = menus::menu_containing_realearn_params_optional( + &session, + item.compartment(), + modifier_condition.param_index, + ); + (modifier_condition, menu) + }; + let result = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(param_index) = result { + set( + session, + &mut *item.borrow_mut(), + modifier_condition.with_param_index(param_index), + ); + } + } + + fn with_item_if_set(&self, f: impl FnOnce(&Self, &dyn Item)) { + if let Some(weak_item) = self.item.borrow().as_ref() { + if let Some(item) = weak_item.upgrade() { + f(self, &*item.borrow()); + } + } + } + + fn with_session_and_item(&self, f: impl FnOnce(&Self, WeakUnitModel, &mut dyn Item)) { + self.with_session_and_unborrowed_item(|panel, session, item| { + f(panel, session, &mut *item.borrow_mut()) + }); + } + + fn with_session_and_unborrowed_item(&self, f: impl FnOnce(&Self, WeakUnitModel, SharedItem)) { + let opt_item = self.item.borrow(); + let weak_item = opt_item.as_ref().expect("item not set"); + let item = weak_item.upgrade().expect("item gone"); + f(self, self.session.clone(), item); + } + + pub fn invalidate_due_to_changed_prop(&self, prop: ItemProp, initiator: Option) { + self.with_item_if_set(|_, item| { + self.invoke_programmatically(|| { + use ItemProp::*; + match prop { + Name => self.invalidate_name_edit_control(item, initiator), + Tags => self.invalidate_tags_edit_control(item, initiator), + ControlEnabled => self.invalidate_control_enabled_check_box(item), + FeedbackEnabled => self.invalidate_feedback_enabled_check_box(item), + ActivationType => self.invalidate_activation_controls(item), + ModifierCondition1 => self.invalidate_activation_setting_1_controls(item), + ModifierCondition2 => { + self.invalidate_activation_setting_2_controls(item, initiator) + } + BankCondition => { + self.invalidate_activation_setting_1_controls(item); + self.invalidate_activation_setting_2_controls(item, initiator); + } + Script => self.invalidate_activation_setting_2_controls(item, initiator), + MappingId => self.invalidate_activation_setting_1_controls(item), + }; + }); + }); + } + + fn session(&self) -> SharedUnitModel { + self.session.upgrade().expect("session gone") + } +} + +impl View for MappingHeaderPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_SHARED_GROUP_MAPPING_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + window.move_to_dialog_units(self.position); + self.init_controls(); + true + } + + fn close_requested(self: SharedView) -> bool { + self.view.require_window().parent().unwrap().close(); + // If we return false, the child window is closed. + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + use root::*; + match resource_id { + ID_MAPPING_CONTROL_ENABLED_CHECK_BOX => { + self.with_session_and_item(Self::update_control_enabled); + } + ID_MAPPING_FEEDBACK_ENABLED_CHECK_BOX => { + self.with_session_and_item(Self::update_feedback_enabled); + } + ID_MAPPING_ACTIVATION_SETTING_1_BUTTON => { + self.with_session_and_unborrowed_item(Self::pick_activation_setting_1_option); + } + ID_MAPPING_ACTIVATION_SETTING_1_CHECK_BOX => { + self.with_session_and_item(Self::update_activation_setting_1_on); + } + ID_MAPPING_ACTIVATION_SETTING_2_BUTTON => { + self.with_session_and_unborrowed_item(Self::pick_activation_setting_2_option); + } + ID_MAPPING_ACTIVATION_SETTING_2_CHECK_BOX => { + self.with_session_and_item(Self::update_activation_setting_2_on); + } + _ => {} + } + } + + fn option_selected(self: SharedView, resource_id: u32) { + use root::*; + match resource_id { + ID_MAPPING_ACTIVATION_TYPE_COMBO_BOX => { + self.with_session_and_item(Self::update_activation_type); + } + _ => {} + } + } + + fn edit_control_changed(self: SharedView, resource_id: u32) -> bool { + if self.is_invoked_programmatically() { + // We don't want to continue if the edit control change was not caused by the user. + // Although the edit control text is changed programmatically, it also triggers the + // change handler. Ignore it! Most of those events are filtered out already + // by the dialog proc reentrancy check, but this one is not because the + // dialog proc is not reentered - we are just reacting (async) to a change. + return false; + } + use root::*; + match resource_id { + ID_MAPPING_NAME_EDIT_CONTROL => { + self.with_session_and_item(Self::update_name); + } + ID_MAPPING_TAGS_EDIT_CONTROL => { + self.with_session_and_item(Self::update_tags); + } + ID_MAPPING_ACTIVATION_EDIT_CONTROL => { + self.with_session_and_item(Self::update_activation_script); + } + _ => return false, + }; + true + } + + fn edit_control_focus_killed(self: SharedView, resource_id: u32) -> bool { + // This is also called when the window is hidden. + // The edit control which is currently edited by the user doesn't get invalidated during + // `edit_control_changed()`, for good reasons. But as soon as the edit control loses + // focus, we should invalidate it. This is especially important if the user + // entered an invalid value. Because we are lazy and edit controls are not + // manipulated very frequently, we just invalidate all controls. + // If this fails (because the mapping is not filled anymore), it's not a problem. + self.with_item_if_set(|s, item| match resource_id { + root::ID_MAPPING_NAME_EDIT_CONTROL => s.invalidate_name_edit_control(item, None), + root::ID_MAPPING_TAGS_EDIT_CONTROL => s.invalidate_tags_edit_control(item, None), + _ => {} + }); + false + } + + fn help_requested(&self) -> bool { + if let Some(parent_view) = self.get_parent_view() { + parent_view.help_requested() + } else { + false + } + } + + fn mouse_test(self: SharedView, position: Point) -> bool { + if let Some(parent_view) = self.get_parent_view() { + parent_view.mouse_test(position) + } else { + false + } + } +} + +impl Item for MappingModel { + fn compartment(&self) -> CompartmentKind { + self.compartment() + } + + fn supports_name_change(&self) -> bool { + true + } + + fn name(&self) -> &str { + self.name() + } + + fn set_name(&mut self, session: WeakUnitModel, name: String, initiator: u32) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::SetName(name), + Some(initiator), + ); + } + + fn tags(&self) -> &[Tag] { + self.tags() + } + + fn set_tags(&mut self, session: WeakUnitModel, tags: Vec, initiator: u32) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::SetTags(tags), + Some(initiator), + ); + } + + fn control_is_enabled(&self) -> bool { + self.control_is_enabled() + } + + fn set_control_is_enabled(&mut self, session: WeakUnitModel, value: bool) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::SetControlIsEnabled(value), + None, + ); + } + + fn feedback_is_enabled(&self) -> bool { + self.feedback_is_enabled() + } + + fn set_feedback_is_enabled(&mut self, session: WeakUnitModel, value: bool) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::SetFeedbackIsEnabled(value), + None, + ); + } + + fn activation_type(&self) -> ActivationType { + self.activation_condition_model().activation_type() + } + + fn set_activation_type(&mut self, session: WeakUnitModel, value: ActivationType) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::ChangeActivationCondition( + ActivationConditionCommand::SetActivationType(value), + ), + None, + ); + } + + fn modifier_condition_1(&self) -> ModifierConditionModel { + self.activation_condition_model().modifier_condition_1() + } + + fn set_modifier_condition_1(&mut self, session: WeakUnitModel, value: ModifierConditionModel) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::ChangeActivationCondition( + ActivationConditionCommand::SetModifierCondition1(value), + ), + None, + ); + } + + fn modifier_condition_2(&self) -> ModifierConditionModel { + self.activation_condition_model().modifier_condition_2() + } + + fn set_modifier_condition_2(&mut self, session: WeakUnitModel, value: ModifierConditionModel) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::ChangeActivationCondition( + ActivationConditionCommand::SetModifierCondition2(value), + ), + None, + ); + } + + fn bank_condition(&self) -> BankConditionModel { + self.activation_condition_model().bank_condition() + } + + fn set_bank_condition(&mut self, session: WeakUnitModel, value: BankConditionModel) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::ChangeActivationCondition( + ActivationConditionCommand::SetBankCondition(value), + ), + None, + ); + } + + fn script(&self) -> &str { + self.activation_condition_model().script() + } + + fn set_script(&mut self, session: WeakUnitModel, value: String, initiator: u32) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::ChangeActivationCondition(ActivationConditionCommand::SetScript(value)), + Some(initiator), + ); + } + + fn mapping_id(&self) -> Option { + self.activation_condition_model().mapping_id() + } + + fn set_mapping_id(&mut self, session: WeakUnitModel, value: Option) { + UnitModel::change_mapping_from_ui_simple( + session, + self, + MappingCommand::ChangeActivationCondition(ActivationConditionCommand::SetMappingId( + value, + )), + None, + ); + } +} + +impl Item for GroupModel { + fn compartment(&self) -> CompartmentKind { + self.compartment() + } + + fn supports_name_change(&self) -> bool { + !self.is_default_group() + } + + fn name(&self) -> &str { + self.effective_name() + } + + fn set_name(&mut self, session: WeakUnitModel, name: String, initiator: u32) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::SetName(name), + Some(initiator), + ); + } + + fn tags(&self) -> &[Tag] { + self.tags() + } + + fn set_tags(&mut self, session: WeakUnitModel, tags: Vec, initiator: u32) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::SetTags(tags), + Some(initiator), + ); + } + + fn control_is_enabled(&self) -> bool { + self.control_is_enabled() + } + + fn set_control_is_enabled(&mut self, session: WeakUnitModel, value: bool) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::SetControlIsEnabled(value), + None, + ); + } + + fn feedback_is_enabled(&self) -> bool { + self.feedback_is_enabled() + } + + fn set_feedback_is_enabled(&mut self, session: WeakUnitModel, value: bool) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::SetFeedbackIsEnabled(value), + None, + ); + } + + fn activation_type(&self) -> ActivationType { + self.activation_condition_model().activation_type() + } + + fn set_activation_type(&mut self, session: WeakUnitModel, value: ActivationType) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::ChangeActivationCondition(ActivationConditionCommand::SetActivationType( + value, + )), + None, + ); + } + + fn modifier_condition_1(&self) -> ModifierConditionModel { + self.activation_condition_model().modifier_condition_1() + } + + fn set_modifier_condition_1(&mut self, session: WeakUnitModel, value: ModifierConditionModel) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::ChangeActivationCondition( + ActivationConditionCommand::SetModifierCondition1(value), + ), + None, + ); + } + + fn modifier_condition_2(&self) -> ModifierConditionModel { + self.activation_condition_model().modifier_condition_2() + } + + fn set_modifier_condition_2(&mut self, session: WeakUnitModel, value: ModifierConditionModel) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::ChangeActivationCondition( + ActivationConditionCommand::SetModifierCondition2(value), + ), + None, + ); + } + + fn bank_condition(&self) -> BankConditionModel { + self.activation_condition_model().bank_condition() + } + + fn set_bank_condition(&mut self, session: WeakUnitModel, value: BankConditionModel) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::ChangeActivationCondition(ActivationConditionCommand::SetBankCondition( + value, + )), + None, + ); + } + + fn script(&self) -> &str { + self.activation_condition_model().script() + } + + fn set_script(&mut self, session: WeakUnitModel, value: String, initiator: u32) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::ChangeActivationCondition(ActivationConditionCommand::SetScript(value)), + Some(initiator), + ); + } + + fn mapping_id(&self) -> Option { + self.activation_condition_model().mapping_id() + } + + fn set_mapping_id(&mut self, session: WeakUnitModel, value: Option) { + UnitModel::change_group_from_ui_simple( + session, + self, + GroupCommand::ChangeActivationCondition(ActivationConditionCommand::SetMappingId( + value, + )), + None, + ); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_panel.rs new file mode 100644 index 0000000..0e7a3a1 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_panel.rs @@ -0,0 +1,8835 @@ +use anyhow::{bail, Context}; +use camino::Utf8PathBuf; +use derive_more::Display; +use helgoboss_midi::{Channel, ShortMessageType, U7}; +use itertools::Itertools; +use reaper_high::{ + BookmarkType, Fx, FxChain, Project, Reaper, SendPartnerType, Track, TrackRoutePartner, +}; +use reaper_low::raw; +use reaper_medium::{ + Hbrush, InitialAction, NativeColor, PromptForActionResult, SectionId, WindowContext, +}; +use rxrust::prelude::*; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::convert::TryInto; +use std::ptr::null; +use std::rc::Rc; +use std::time::Duration; +use std::{cmp, iter}; +use strum::IntoEnumIterator; + +use helgoboss_learn::{ + format_percentage_without_unit, AbsoluteMode, AbsoluteValue, ButtonUsage, ControlValue, + DiscreteIncrement, DisplayType, EncoderUsage, FeedbackType, FireMode, GroupInteraction, + Interval, MackieSevenSegmentDisplayScope, MidiClockTransportMessage, ModeParameter, OscTypeTag, + OutOfRangeBehavior, PercentIo, RgbColor, SourceCharacter, TakeoverMode, Target, UnitValue, + ValueSequence, VirtualColor, DEFAULT_OSC_ARG_VALUE_RANGE, +}; +use helgobox_api::persistence::{ + ActionScope, Axis, BrowseTracksMode, FxDescriptor, FxToolAction, InstanceTagKind, + LearnableTargetKind, MidiScriptKind, MonitoringMode, MouseButton, PlaytimeColumnAction, + PlaytimeColumnDescriptor, PlaytimeColumnDescriptorKind, PlaytimeMatrixAction, + PlaytimeRowAction, PlaytimeRowDescriptor, PlaytimeRowDescriptorKind, PlaytimeSlotDescriptor, + PlaytimeSlotDescriptorKind, PlaytimeSlotManagementAction, PlaytimeSlotTransportAction, + PotFilterKind, SeekBehavior, TrackToolAction, VirtualControlElementCharacter, +}; +use swell_ui::{ + DeviceContext, DialogUnits, Point, SharedView, SwellStringArg, View, ViewContext, WeakView, + Window, +}; + +use crate::application::{ + build_smart_command_name_from_action, format_osc_feedback_args, get_bookmark_label_by_id, + get_fx_label, get_fx_param_label, get_non_present_bookmark_label, get_optional_fx_label, + get_route_label, parse_osc_feedback_args, Affected, AutomationModeOverrideType, + BookmarkAnchorType, Change, CompartmentProp, ConcreteFxInstruction, ConcreteTrackInstruction, + MappingChangeContext, MappingCommand, MappingModel, MappingModificationKind, MappingProp, + MappingRefModel, MappingSnapshotTypeForLoad, MappingSnapshotTypeForTake, MidiSourceType, + ModeCommand, ModeModel, ModeProp, RealearnAutomationMode, RealearnTrackArea, ReaperSourceType, + SharedMapping, SharedUnitModel, SourceCategory, SourceCommand, SourceModel, SourceProp, + StreamDeckButtonBackgroundType, StreamDeckButtonForegroundType, TargetCategory, TargetCommand, + TargetModel, TargetModelFormatVeryShort, TargetModelWithContext, TargetProp, TargetUnit, + TrackRouteSelectorType, UnitModel, UnitProp, VirtualFxParameterType, VirtualFxType, + VirtualTrackType, WeakUnitModel, KEY_UNDEFINED_LABEL, +}; +use crate::base::{notification, when, Prop}; +use crate::domain::ui_util::{ + format_as_percentage_without_unit, format_tags_as_csv, parse_unit_value_from_percentage, +}; +use crate::domain::{ + control_element_domains, AnyOnParameter, Backbone, ControlContext, Exclusivity, + FeedbackSendBehavior, KeyStrokePortability, MouseActionType, PortabilityIssue, ReaperTarget, + ReaperTargetType, SendMidiDestinationType, SimpleExclusivity, SourceFeedbackEvent, + TargetControlEvent, TouchedRouteParameterType, TrackGangBehavior, WithControlContext, +}; +use crate::domain::{ + get_non_present_virtual_route_label, get_non_present_virtual_track_label, + resolve_track_route_by_index, ActionInvocationType, CompartmentKind, CompoundMappingTarget, + ExtendedProcessorContext, FeedbackResolution, FxDisplayType, QualifiedMappingId, + RealearnTarget, SoloBehavior, TargetCharacter, TouchedTrackParameterType, TrackExclusivity, + TrackRouteType, TransportAction, VirtualControlElement, VirtualControlElementId, VirtualFx, +}; +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::color_panel::{ColorPanel, ColorPanelDesc}; +use crate::infrastructure::ui::help::{ + ConceptTopic, HelpTopic, HelpTopicDescription, MappingTopic, SourceTopic, TargetTopic, +}; +use crate::infrastructure::ui::menus::{ + build_enum_variants_menu, get_midi_input_device_list_label, get_param_name, +}; +use crate::infrastructure::ui::ui_element_container::UiElementContainer; +use crate::infrastructure::ui::util::colors::ColorPair; +use crate::infrastructure::ui::util::{ + close_child_panel_if_open, colors, compartment_parameter_dropdown_contents, + open_child_panel_dyn, open_in_browser, parse_tags_from_csv, symbols, view, + MAPPING_PANEL_SCALING, +}; +use crate::infrastructure::ui::{ + menus, EelControlTransformationEngine, EelFeedbackTransformationEngine, EelMidiScriptEngine, + ItemProp, LuaFeedbackScriptEngine, LuaMidiScriptEngine, MappingHeaderPanel, MappingRowsPanel, + OscFeedbackArgumentsEngine, PlainTextEngine, RawMidiScriptEngine, ScriptEditorInput, + ScriptEngine, SimpleScriptEditorPanel, TextualFeedbackExpressionEngine, UnitPanel, + YamlEditorPanel, +}; +use base::hash_util::NonCryptoHashMap; +use base::Global; +use playtime_api::persistence::{ColumnAddress, RowAddress, SlotAddress}; + +#[derive(Debug)] +pub struct MappingPanel { + view: ViewContext, + session: WeakUnitModel, + mapping: RefCell>, + main_panel: WeakView, + mapping_color_panel: SharedView, + source_color_panel: SharedView, + target_color_panel: SharedView, + glue_color_panel: SharedView, + help_color_panel: SharedView, + mapping_header_panel: SharedView, + is_invoked_programmatically: Cell, + window_cache: RefCell>, + extra_panel: RefCell>>, + active_help_topic: RefCell>>, + ui_element_container: RefCell, + // Fires when a mapping is about to change or the panel is hidden. + party_is_over_subject: RefCell>, +} + +struct ImmutableMappingPanel<'a> { + session: &'a UnitModel, + mapping: &'a MappingModel, + source: &'a SourceModel, + mode: &'a ModeModel, + target: &'a TargetModel, + view: &'a ViewContext, + panel: &'a SharedView, +} + +struct MutableMappingPanel<'a> { + session: &'a mut UnitModel, + mapping: &'a mut MappingModel, + panel: &'a SharedView, + view: &'a ViewContext, +} + +#[derive(Debug)] +struct WindowCache { + mode_min_target_value: Window, + mode_max_target_value: Window, + mode_min_source_value: Window, + mode_max_source_value: Window, + mode_min_step_size: Window, + mode_max_step_size: Window, + mode_fire_line_2: Window, + mode_fire_line_3: Window, + target_value: Window, +} + +const SAME_AS_INPUT_DEV: &str = ""; + +impl MappingPanel { + pub fn new(session: WeakUnitModel, main_panel: WeakView) -> MappingPanel { + MappingPanel { + view: Default::default(), + session: session.clone(), + mapping: None.into(), + main_panel, + mapping_color_panel: SharedView::new(ColorPanel::new(build_mapping_color_panel_desc())), + source_color_panel: SharedView::new(ColorPanel::new(build_source_color_panel_desc())), + target_color_panel: SharedView::new(ColorPanel::new(build_target_color_panel_desc())), + glue_color_panel: SharedView::new(ColorPanel::new(build_glue_color_panel_desc())), + help_color_panel: SharedView::new(ColorPanel::new(build_help_color_panel_desc())), + mapping_header_panel: SharedView::new(MappingHeaderPanel::new( + session, + Point::new(DialogUnits(7 + 5), DialogUnits(12)).scale(&MAPPING_PANEL_SCALING), + None, + )), + is_invoked_programmatically: false.into(), + window_cache: None.into(), + extra_panel: Default::default(), + active_help_topic: Default::default(), + ui_element_container: Default::default(), + party_is_over_subject: Default::default(), + } + } + + fn handle_hovered_ui_element(&self, resource_id: Option) { + let help_topic = resource_id.and_then(find_help_topic_for_resource); + self.active_help_topic.borrow_mut().set(help_topic); + } + + fn set_invoked_programmatically(&self, value: bool) { + self.is_invoked_programmatically.set(value); + // I already ran into a borrow error because the mapping header panel was updated + // as well as part of a programmatic reaction but didn't have that flag set. So set it + // there as well! + self.mapping_header_panel + .set_invoked_programmatically(value); + } + + pub fn handle_affected( + self: &SharedView, + affected: &Affected, + initiator: Option, + ) { + use Affected::*; + use CompartmentProp::*; + use UnitProp::*; + match affected { + One(InCompartment(compartment, One(InMapping(mapping_id, affected)))) + if Some(QualifiedMappingId::new(*compartment, *mapping_id)) + == self.qualified_mapping_id() => + { + // At this point we know already it's a prop change for *our* mapping. + // Mark as programmatic invocation. + let panel_clone = self.clone(); + panel_clone.set_invoked_programmatically(true); + scopeguard::defer! { panel_clone.set_invoked_programmatically(false); } + // If the reaction can't be displayed anymore because the mapping is not filled anymore, + // so what. + let _ = self.clone().read(|view| match affected { + Multiple => { + view.invalidate_all_controls(); + } + One(prop) => { + use MappingProp as P; + match prop { + P::Name => { + view.invalidate_window_title(); + view.panel + .mapping_header_panel + .invalidate_due_to_changed_prop(ItemProp::Name, initiator); + } + P::Tags => { + view.panel + .mapping_header_panel + .invalidate_due_to_changed_prop(ItemProp::Tags, initiator); + } + P::AdvancedSettings => { + view.invalidate_mapping_advanced_settings_button(); + } + P::ControlIsEnabled => { + view.panel + .mapping_header_panel + .invalidate_due_to_changed_prop( + ItemProp::ControlEnabled, + initiator, + ); + view.invalidate_mode_controls(); + } + P::FeedbackIsEnabled => { + view.panel + .mapping_header_panel + .invalidate_due_to_changed_prop( + ItemProp::FeedbackEnabled, + initiator, + ); + view.invalidate_mode_controls(); + } + P::BeepOnSuccess => { + view.invalidate_beep_on_success_checkbox(); + } + P::IsEnabled => { + view.invalidate_mapping_enabled_check_box(); + } + P::VisibleInProjection => { + view.invalidate_mapping_visible_in_projection_check_box(); + } + P::FeedbackSendBehavior => { + view.invalidate_mapping_feedback_send_behavior_combo_box(); + } + P::GroupId => {} + P::InActivationCondition(p) => match p { + Multiple => { + view.panel.mapping_header_panel.invalidate_controls(); + } + One(p) => { + let item_prop = ItemProp::from_activation_condition_prop(p); + view.panel + .mapping_header_panel + .invalidate_due_to_changed_prop(item_prop, initiator); + } + }, + P::InSource(p) => match p { + Multiple => { + view.invalidate_source_controls(); + view.invalidate_mode_controls(); + } + One(p) => { + use SourceProp as P; + match p { + P::Category | P::MidiSourceType | P::ReaperSourceType | P::ControlElementType => { + view.invalidate_source_controls(); + view.invalidate_mode_controls(); + } + P::Channel => { + view.invalidate_source_control_visibilities(); + view.invalidate_source_line_3_combo_box(); + view.invalidate_source_line_5_combo_box(); + } + P::MidiMessageNumber => { + view.invalidate_source_line_4_combo_box_2(); + } + P::ParameterNumberMessageNumber | + P::ControlElementId => { + view.invalidate_source_line_4_edit_control(initiator); + } + P::OscArgIndex => { + view.invalidate_source_line_4(initiator); + view.invalidate_source_line_5(initiator); + view.invalidate_mode_controls(); + } + P::CustomCharacter | + P::OscArgTypeTag => { + view.invalidate_source_line_4_combo_box_2(); + view.invalidate_source_line_5(initiator); + view.invalidate_mode_controls(); + } + P::MidiClockTransportMessage => { + view.invalidate_source_line_3_combo_box(); + } + P::IsRegistered => { + view.invalidate_source_line_4_check_box(); + } + P::Is14Bit => { + view.invalidate_source_controls(); + view.invalidate_mode_controls(); + } + P::DisplayType => { + view.invalidate_source_controls(); + } + P::DisplayId => { + view.invalidate_source_line_4_combo_box_2(); + } + P::Line => { + view.invalidate_source_line_5_combo_box(); + } + P::OscAddressPattern | P::TimerMillis => { + view.invalidate_source_line_3_edit_control(initiator); + } + P::RawMidiPattern => { + view.invalidate_source_line_7_edit_control(initiator); + } + P::ParameterIndex => { + view.invalidate_source_line_3_combo_box() + } + P::MidiScriptKind => { + view.invalidate_source_line_3(initiator); + } + P::MidiScript | P::OscFeedbackArgs => { + view.invalidate_source_line_7_edit_control(initiator); + } + P::OscArgValueRange => { + view.invalidate_source_line_5(initiator); + } + P::OscArgIsRelative => { + view.invalidate_source_controls(); + view.invalidate_mode_controls(); + } + P::Keystroke => { + view.invalidate_source_line_3(initiator); + } + P::ButtonIndex => { view.invalidate_source_line_3(initiator); } + P::ButtonBackgroundType | P::ButtonBackgroundImagePath => { + view.invalidate_source_line_4(initiator); + } + P::ButtonForegroundType | P::ButtonForegroundImagePath => { + view.invalidate_source_line_5(initiator); + } + P::ButtonStaticText => { + view.invalidate_source_line_7_edit_control(initiator); + } + } + } + } + P::InMode(p) => match p { + Multiple => { + view.invalidate_mode_controls(); + } + One(p) => { + use ModeProp as P; + match p { + P::AbsoluteMode => { + view.invalidate_mode_controls(); + } + P::TargetValueInterval => { + view.invalidate_mode_min_target_value_controls( + initiator, + ); + view.invalidate_mode_max_target_value_controls( + initiator, + ); + } + P::SourceValueInterval => { + view.invalidate_mode_source_value_controls(initiator); + } + P::Reverse => { + view.invalidate_mode_reverse_check_box(); + } + P::FireMode => { + view.invalidate_mode_controls_internal(initiator); + } + P::PressDurationInterval | P::TurboRate => { + view.invalidate_mode_fire_controls(initiator); + } + P::OutOfRangeBehavior => { + view.invalidate_mode_out_of_range_behavior_combo_box(); + } + P::RoundTargetValue => { + view.invalidate_mode_round_target_value_check_box(); + } + P::TakeoverMode => { + view.invalidate_mode_takeover_mode_combo_box(); + } + P::ButtonUsage => { + view.invalidate_mode_button_usage_combo_box(); + } + P::EncoderUsage => { + view.invalidate_mode_encoder_usage_combo_box(); + } + P::EelControlTransformation => { + view.invalidate_mode_controls_internal(initiator); + } + P::EelFeedbackTransformation | P::TextualFeedbackExpression => { + view.invalidate_mode_eel_feedback_transformation_edit_control(initiator); + } + P::StepSizeInterval | P::StepFactorInterval => { + view.invalidate_mode_step_controls(initiator); + } + P::Rotate => { + view.invalidate_mode_rotate_check_box(); + } + P::MakeAbsolute => { + view.invalidate_mode_controls(); + } + P::GroupInteraction => { + view.invalidate_mode_group_interaction_combo_box(); + } + P::TargetValueSequence => { + view.invalidate_mode_target_value_sequence_edit_control(initiator); + } + P::FeedbackType => { + view.invalidate_mode_controls(); + } + P::FeedbackColor | P::FeedbackBackgroundColor => { + view.invalidate_mode_feedback_type_button(); + } + P::FeedbackValueTable => { + // No representation in GUI at the moment. + } + P::LegacyJumpInterval => { + // Not supported in UI anymore since 2.14.0-pre.10 + } + } + } + }, + + P::InTarget(p) => match p { + Multiple => { + view.invalidate_target_controls(None); + view.invalidate_mode_controls(); + } + One(p) => { + use TargetProp as P; + match p { + P::Category | P::TargetType | P::ControlElementType => { + view.invalidate_window_title(); + view.invalidate_target_controls(None); + view.invalidate_mode_controls(); + } + P::TrackType | P::TrackIndex | P::TrackId | P::TrackName + | P::TrackExpression | P::BookmarkType | P::BookmarkAnchorType + | P::BookmarkRef | P::TransportAction | P::AnyOnParameter + | P::SmartCommandName | P::ActionScope => { + view.invalidate_window_title(); + view.invalidate_target_controls(initiator); + view.invalidate_mode_controls(); + } + P::BrowseTracksMode => { + view.invalidate_target_line_2(initiator); + } + P::MappingSnapshotTypeForLoad | P::MappingSnapshotTypeForTake | P::MappingSnapshotId => { + view.invalidate_target_line_2(initiator); + } + P::MappingSnapshotDefaultValue => { + view.invalidate_target_line_3(initiator); + } + P::ControlElementId => { + view.invalidate_window_title(); + view.invalidate_target_line_2(initiator); + } + P::Learnable => { + view.invalidate_target_check_boxes(); + } + P::Unit => { + view.invalidate_target_value_controls(); + view.invalidate_mode_controls(); + } + P::FxType | P::FxIndex | P::FxId | P::FxName | P::FxExpression | P::FxIsInputFx => { + view.invalidate_window_title(); + view.invalidate_target_controls(initiator); + view.invalidate_mode_controls(); + } + P::RouteSelectorType | P::RouteType | P::RouteIndex | P::RouteId | P::RouteName | P::RouteExpression => { + view.invalidate_target_controls(initiator); + view.invalidate_mode_controls(); + } + P::ParamType | P::ParamName | P::ParamExpression => { + view.invalidate_target_controls(initiator); + view.invalidate_mode_controls(); + } + P::ParamIndex => { + view.invalidate_target_value_controls(); + view.invalidate_mode_controls(); + // This one is for compartment parameter target + view.invalidate_target_line_2_label_2(); + } + P::ActionInvocationType => { + view.invalidate_target_line_3(None); + view.invalidate_target_value_controls(); + view.invalidate_mode_controls(); + } + P::SeekBehavior | P::TouchedTrackParameterType | P::AutomationMode | P::MonitoringMode | P::TrackArea => { + view.invalidate_target_line_3(None); + } + P::SoloBehavior => { + view.invalidate_target_line_3(None); + view.invalidate_target_check_boxes(); + } + P::AutomationModeOverrideType => { + view.invalidate_window_title(); + view.invalidate_target_line_2_combo_box_2(initiator); + view.invalidate_target_line_3(None); + } + P::FxSnapshot | P::FxDisplayType => { + view.invalidate_target_line_4(None); + view.invalidate_target_value_controls(); + } + P::TrackExclusivity => { + view.invalidate_target_line_4(initiator); + view.invalidate_target_value_controls(); + view.invalidate_mode_controls(); + } + P::TrackToolAction | P::FxToolAction => { + view.invalidate_target_line_4(initiator); + view.invalidate_target_value_controls(); + view.invalidate_mode_controls(); + } + P::GroupId => { + view.invalidate_target_line_2(initiator); + view.invalidate_target_value_controls(); + view.invalidate_mode_controls(); + } + P::OscArgIndex | + P::OscArgTypeTag => { + view.invalidate_target_line_4(initiator); + view.invalidate_target_line_5(initiator); + view.invalidate_target_value_controls(); + view.invalidate_mode_controls(); + } + P::OscArgValueRange => { + view.invalidate_target_line_5(initiator); + } + P::MouseActionType => { + view.invalidate_target_controls(initiator); + } + P::PotFilterItemKind => { + view.invalidate_target_controls(initiator); + view.invalidate_mode_controls(); + } + P::Axis => { + view.invalidate_target_line_3(initiator); + } + P::MouseButton => { + view.invalidate_target_line_4(initiator); + } + P::ScrollArrangeView | P::SeekPlay => { + view.invalidate_target_check_boxes(); + view.invalidate_target_value_controls(); + } + P::GangBehavior => { + view.invalidate_target_check_boxes(); + } + P::EnableOnlyIfTrackSelected | P::ScrollMixer | P::MoveView => { + view.invalidate_target_check_boxes(); + } + P::WithTrack => { + view.invalidate_target_controls(None); + } + P::EnableOnlyIfFxHasFocus | P::UseProject => { + view.invalidate_target_check_boxes(); + } + P::UseRegions => { + view.invalidate_target_check_boxes(); + } + P::UseLoopPoints | P::PollForFeedback | P::Retrigger | P::RealTime => { + view.invalidate_target_check_boxes(); + } + P::UseTimeSelection => { + view.invalidate_target_check_boxes(); + } + P::FeedbackResolution => { + view.invalidate_target_line_2_combo_box_1(); + } + P::OscAddressPattern => { + view.invalidate_target_line_3(initiator); + view.invalidate_mode_controls(); + } + P::RawMidiPattern => { + view.invalidate_target_line_4(initiator); + view.invalidate_mode_controls(); + } + P::OscDevId => { + view.invalidate_target_line_2(None); + } + P::SendMidiDestination => { + view.invalidate_target_line_2(None); + view.invalidate_target_line_3(None); + } + P::MidiInputDevice => { + view.invalidate_target_line_3(None); + } + P::InstanceTagKind => { + view.invalidate_target_line_3(initiator); + } + P::Tags => { + view.invalidate_target_line_4_edit_control(initiator); + } + P::Exclusivity => { + view.invalidate_target_line_4_combo_box_1(); + } + P::ActiveMappingsOnly => { + view.invalidate_target_check_box_2(); + } + P::PlaytimeRow | P::PlaytimeRowAction | P::StopColumnIfSlotEmpty | P::PlaytimeSlot | P::PlaytimeColumn | P::PlaytimeSlotManagementAction | P::PlaytimeSlotTransportAction | P::PlaytimeColumnAction | P::PlaytimeMatrixAction => { + view.invalidate_target_controls(initiator); + view.invalidate_mode_controls(); + } + P::TouchedRouteParameterType => { + view.invalidate_target_line_3_combo_box_2(); + } + P::MappingModificationKind => { + view.invalidate_target_line_2(initiator); + } + P::MappingRef => { + view.invalidate_target_line_3(initiator); + view.invalidate_target_line_4(initiator); + } + P::IncludedTargets => { + view.invalidate_target_line_2(initiator); + } + P::TouchCause => { + // Not shown in mapping panel at the moment, only + // in egui view (in a non-reactive way). + } + } + } + } + } + } + }); + } + _ => {} + } + } + + fn source_match_indicator_control(&self) -> Window { + self.view + .require_control(root::IDC_MAPPING_MATCHED_INDICATOR_TEXT) + } + + fn toggle_learn_source(&self) { + let session = self.session(); + session + .borrow_mut() + .toggle_learning_source( + self.session.clone(), + self.qualified_mapping_id().expect("no mapping displayed"), + ) + .expect("mapping must exist"); + } + + fn toggle_learn_target(&self) { + let session = self.session(); + session.borrow_mut().toggle_learning_target( + self.session.clone(), + self.qualified_mapping_id().expect("no mapping"), + ); + } + + fn handle_target_line_2_button_press(self: SharedView) -> Result<(), &'static str> { + let mapping = self.displayed_mapping().ok_or("no mapping set")?; + let category = mapping.borrow().target_model.category(); + match category { + TargetCategory::Reaper => { + let target_type = mapping.borrow().target_model.target_type(); + match target_type { + ReaperTargetType::LastTouched => { + self.open_learnable_targets_picker(mapping); + } + ReaperTargetType::ModifyMapping => { + self.open_learnable_targets_picker(mapping); + } + ReaperTargetType::CompartmentParameterValue => { + self.pick_compartment_parameter(mapping); + } + _ => { + self.write(|p| p.handle_target_line_2_button_press_internal()); + } + } + } + TargetCategory::Virtual => { + let control_element_type = + mapping.borrow().target_model.control_element_character(); + let window = self.view.require_window(); + let text = pick_virtual_control_element( + window, + control_element_type, + &HashMap::default(), + true, + true, + ) + .ok_or("nothing picked")?; + let element_id = text.parse().unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetControlElementId(element_id), + )); + } + }; + Ok(()) + } + + fn open_learnable_targets_picker(self: SharedView, mapping: SharedMapping) { + #[cfg(not(feature = "egui"))] + { + let _ = mapping; + crate::infrastructure::ui::util::alert_feature_not_available(); + } + #[cfg(feature = "egui")] + { + let value = { + let mapping = mapping.borrow(); + crate::infrastructure::ui::egui_views::target_filter_panel::Value { + included_targets: mapping.target_model.included_targets().clone(), + touch_cause: mapping.target_model.touch_cause(), + } + }; + let session = self.session.clone(); + let panel = crate::infrastructure::ui::TargetFilterPanel::new(value, move |value| { + let mut mapping = mapping.borrow_mut(); + UnitModel::change_mapping_from_ui_simple( + session.clone(), + &mut mapping, + MappingCommand::ChangeTarget(TargetCommand::SetLearnableTargetKinds( + value.included_targets, + )), + None, + ); + UnitModel::change_mapping_from_ui_simple( + session.clone(), + &mut mapping, + MappingCommand::ChangeTarget(TargetCommand::SetTouchCause(value.touch_cause)), + None, + ); + }); + self.open_extra_panel(panel); + } + } + + fn pick_compartment_parameter(&self, mapping: SharedMapping) { + let menu = { + let mapping = mapping.borrow(); + let param_index = mapping.target_model.compartment_param_index(); + menus::menu_containing_realearn_params( + &self.session, + mapping.compartment(), + param_index, + ) + }; + let result = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(param_index) = result { + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetParamIndex( + param_index.get(), + ))); + } + } + + fn pick_target_midi_input_device(&self, mapping: SharedMapping) { + let menu = { + let mapping = mapping.borrow(); + let current_value = mapping.target_model.midi_input_device(); + menus::midi_device_input_menu(current_value, SAME_AS_INPUT_DEV) + }; + let result = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(midi_input_device) = result { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMidiInputDevice(midi_input_device), + )); + } + } + + fn handle_target_line_3_button_press(&self) -> Result<(), &'static str> { + let mapping = self.displayed_mapping().ok_or("no mapping set")?; + let target_type = mapping.borrow().target_model.target_type(); + match target_type { + ReaperTargetType::SendMidi => { + self.pick_target_midi_input_device(mapping); + } + ReaperTargetType::ModifyMapping => { + let menu = { + let mapping = mapping.borrow(); + let current_other_session_id = mapping.target_model.mapping_ref().session_id(); + let session = self.session(); + let session = session.borrow(); + menus::menu_containing_sessions(&session, current_other_session_id) + }; + let new_other_session_id = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + if let Some(new_other_session_id) = new_other_session_id { + // Chosen something in the menu + let new_mapping_ref = if let Some(session_id) = new_other_session_id { + // Chosen another instance + MappingRefModel::ForeignMapping { + session_id, + mapping_key: None, + } + } else { + // Chosen this instance + MappingRefModel::OwnMapping { mapping_id: None } + }; + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMappingRef(new_mapping_ref), + )); + } + } + _ => {} + } + Ok(()) + } + + fn handle_source_line_4_button_press(&self) -> Result<(), &'static str> { + let m = self.displayed_mapping().ok_or("no mapping set")?; + let source_category = m.borrow().source_model.category(); + match source_category { + SourceCategory::StreamDeck => { + let current_value = m.borrow().source_model.button_background_type(); + let menu = + build_enum_variants_menu(StreamDeckButtonBackgroundType::iter(), current_value); + if let Some(new_value) = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()) + { + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetButtonBackgroundType(new_value), + )); + if new_value.wants_image() { + let current_file = m + .borrow() + .source_model + .button_background_image_path() + .to_path_buf(); + if let Some(new_file_name) = pick_png_file(current_file) { + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetButtonBackgroundImagePath(new_file_name), + )); + } + } + } + } + SourceCategory::Virtual => { + let (control_element_type, control_enabled, feedback_enabled) = { + let m = m.borrow(); + ( + m.source_model.control_element_character(), + m.control_is_enabled(), + m.feedback_is_enabled(), + ) + }; + let window = self.view.require_window(); + let controller_mappings: Vec<_> = { + let session = self.session(); + let session = session.borrow(); + session + .mappings(CompartmentKind::Controller) + .cloned() + .collect() + }; + let grouped_mappings = + group_mappings_by_virtual_control_element(controller_mappings.iter()); + let text = pick_virtual_control_element( + window, + control_element_type, + &grouped_mappings, + control_enabled, + feedback_enabled, + ) + .ok_or("nothing picked")?; + let control_element_id = text.parse().unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetControlElementId(control_element_id), + )); + } + _ => {} + } + Ok(()) + } + + fn handle_source_line_5_button_press(&self) -> Result<(), &'static str> { + let m = self.displayed_mapping().ok_or("no mapping set")?; + let source_category = m.borrow().source_model.category(); + match source_category { + SourceCategory::StreamDeck => { + let current_value = m.borrow().source_model.button_foreground_type(); + let menu = + build_enum_variants_menu(StreamDeckButtonForegroundType::iter(), current_value); + if let Some(new_value) = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()) + { + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetButtonForegroundType(new_value), + )); + if new_value.wants_image() { + let current_file = m + .borrow() + .source_model + .button_foreground_image_path() + .to_path_buf(); + if let Some(new_file_name) = pick_png_file(current_file) { + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetButtonForegroundImagePath(new_file_name), + )); + } + } + } + } + _ => {} + } + Ok(()) + } + + fn feedback_type_button_pressed(&self) -> Result<(), &'static str> { + let mapping = self.displayed_mapping().ok_or("no mapping set")?; + let current_color = mapping.borrow().mode_model.feedback_color().cloned(); + let current_background_color = mapping + .borrow() + .mode_model + .feedback_background_color() + .cloned(); + let result = show_feedback_popup_menu( + self.view.require_window(), + current_color, + current_background_color, + )?; + match result { + FeedbackPopupMenuResult::EditMultiLine => { + self.edit_feedback_descriptor(); + } + FeedbackPopupMenuResult::ChangeColor(instruction) => { + let cmd = match instruction.target { + ColorTarget::Color => ModeCommand::SetFeedbackColor(instruction.color), + ColorTarget::BackgroundColor => { + ModeCommand::SetFeedbackBackgroundColor(instruction.color) + } + }; + self.change_mapping(MappingCommand::ChangeMode(cmd)); + } + } + Ok(()) + } + + fn open_detailed_help_for_current_ui_element(&self) -> Option<()> { + let topic = self.active_help_topic.borrow().get()?; + let help_url = topic.get_details()?; + open_in_browser(&help_url.doc_url); + Some(()) + } + + fn change_mapping(&self, val: MappingCommand) { + self.change_mapping_with_initiator(val, None); + } + + fn change_mapping_with_initiator(&self, val: MappingCommand, initiator: Option) { + let session = self.session(); + let mut session = session.borrow_mut(); + let mapping = self.displayed_mapping().expect("no mapping set"); + let mut mapping = mapping.borrow_mut(); + session.change_mapping_from_ui_expert(&mut mapping, val, initiator, self.session.clone()); + } + + fn notify_user_on_anyhow_error(&self, result: anyhow::Result<()>) { + if let Err(e) = result { + self.notify_user_about_anyhow_error(e); + } + } + + fn notify_user_about_anyhow_error(&self, e: anyhow::Error) { + self.view + .require_window() + .alert("ReaLearn", format!("{e:#}")); + } + + fn handle_target_line_4_button_press(&self) -> anyhow::Result<()> { + let mapping = self.displayed_mapping().context("no mapping set")?; + let target_type = mapping.borrow().target_model.target_type(); + match target_type { + ReaperTargetType::Action => { + let reaper = Reaper::get().medium_reaper(); + use InitialAction::*; + let initial_action = match mapping.borrow().target_model.resolve_action() { + None => NoneSelected, + Some(a) => Selected(a.command_id()?), + }; + let action_scope = mapping.borrow().target_model.action_scope(); + let section_id = SectionId::new(action_scope.section_id()); + // TODO-low Add this to reaper-high with rxRust + if reaper.low().pointers().PromptForAction.is_none() { + bail!("Please update to REAPER >= 6.12 in order to pick actions!"); + } + reaper.prompt_for_action_create(initial_action, section_id); + let shared_mapping = self.mapping(); + let weak_session = self.session.clone(); + Global::control_surface_rx() + .main_thread_idle() + .take_until(self.party_is_over()) + .map(move |_| { + Reaper::get() + .medium_reaper() + .prompt_for_action_poll(section_id) + }) + .filter(|r| *r != PromptForActionResult::NoneSelected) + .take_while(|r| *r != PromptForActionResult::ActionWindowGone) + .subscribe_complete( + move |r| { + if let PromptForActionResult::Selected(command_id) = r { + let session = weak_session.upgrade().expect("session gone"); + let action = Reaper::get() + .main_section() + .action_by_command_id(command_id); + let mut mapping = shared_mapping.borrow_mut(); + let cmd = MappingCommand::ChangeTarget( + TargetCommand::SetSmartCommandName( + build_smart_command_name_from_action(&action), + ), + ); + session.borrow_mut().change_mapping_from_ui_expert( + &mut mapping, + cmd, + None, + weak_session.clone(), + ); + } + }, + move || { + Reaper::get() + .medium_reaper() + .prompt_for_action_finish(section_id); + }, + ); + } + ReaperTargetType::LoadFxSnapshot => { + // Important that neither session nor mapping is mutably borrowed while doing this + // because state of our ReaLearn instance is not unlikely to be + // queried as well! + let compartment = mapping.borrow().compartment(); + let fx_snapshot = mapping + .borrow() + .target_model + .take_fx_snapshot(self.session().borrow().extended_context(), compartment)?; + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetFxSnapshot( + Some(fx_snapshot), + ))); + } + ReaperTargetType::ModifyMapping => { + let (compartment, current_mapping_ref) = { + let mapping = mapping.borrow(); + ( + mapping.compartment(), + mapping.target_model.mapping_ref().clone(), + ) + }; + let new_mapping_ref = match current_mapping_ref { + MappingRefModel::OwnMapping { mapping_id } => { + let menu = + menus::menu_containing_mappings(&self.session, compartment, mapping_id); + let mapping_id = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + mapping_id.map(|mapping_id| MappingRefModel::OwnMapping { mapping_id }) + } + MappingRefModel::ForeignMapping { + session_id, + mapping_key, + } => { + let session = BackboneShell::get() + .find_unit_model_by_key(&session_id) + .context("session not found")?; + let mapping_id = { + mapping_key.and_then(|key| { + session.borrow().find_mapping_id_by_key(compartment, &key) + }) + }; + let menu = menus::menu_containing_mappings( + &Rc::downgrade(&session), + compartment, + mapping_id, + ); + let mapping_id = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + mapping_id.map(|mapping_id| { + let mapping_key = mapping_id.map(|mapping_id| { + let session = session.borrow(); + let mapping = session + .find_mapping_by_id(compartment, mapping_id) + .expect("currently picked mapping not found"); + let mapping = mapping.borrow(); + mapping.key().clone() + }); + MappingRefModel::ForeignMapping { + session_id, + mapping_key, + } + }) + } + }; + if let Some(new_mapping_ref) = new_mapping_ref { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMappingRef(new_mapping_ref), + )); + } + } + ReaperTargetType::SendMidi => { + if let Some(action) = open_send_midi_menu(self.view.require_window()) { + match action { + SendMidiMenuAction::PitchBendChangePreset { channel } => { + let status_byte: u8 = ShortMessageType::PitchBendChange.into(); + let msg = + format!("{:02X} [0gfe dcba] [0nml kjih]", status_byte + channel); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetRawMidiPattern(msg), + )); + } + SendMidiMenuAction::DoubleDataBytePreset { + channel, + msg_type, + i, + } => { + let status_byte: u8 = msg_type.into(); + let msg = + format!("{:02X} {:02X} [0gfe dcba]", status_byte + channel, i); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetRawMidiPattern(msg), + )); + } + SendMidiMenuAction::SingleDataBytePreset { + channel, + msg_type, + last_byte, + } => { + let status_byte: u8 = msg_type.into(); + let msg = format!( + "{:02X} [0gfe dcba] {:02X}", + status_byte + channel, + last_byte + ); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetRawMidiPattern(msg), + )); + } + SendMidiMenuAction::EditMultiLine => { + let session = self.session.clone(); + let engine = Box::new(RawMidiScriptEngine); + let help_url = + "https://docs.helgoboss.org/realearn/goto#target-midi-send-message"; + self.edit_script_in_simple_editor( + engine, + help_url, + |m| m.target_model.raw_midi_pattern().to_owned(), + move |m, text| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeTarget( + TargetCommand::SetRawMidiPattern(text), + ), + None, + ); + }, + ); + } + } + } + } + _ => {} + } + Ok(()) + } + + pub fn is_free(&self) -> bool { + self.mapping.borrow().is_none() + } + + pub fn mapping_ptr(&self) -> *const MappingModel { + match self.mapping.borrow().as_ref() { + None => null(), + Some(m) => m.as_ptr() as _, + } + } + + fn qualified_mapping_id(&self) -> Option { + let mapping = self.mapping.borrow(); + let mapping = mapping.as_ref()?; + let mapping = mapping.borrow(); + Some(mapping.qualified_id()) + } + + pub fn force_scroll_to_mapping_in_main_panel(&self) { + if let Some(id) = self.qualified_mapping_id() { + self.main_panel().force_scroll_to_mapping(id); + } + } + + fn main_panel(&self) -> SharedView { + self.main_panel.upgrade().expect("main view gone") + } + + fn handle_source_line_7_button_press(&self) { + let mapping = self.mapping(); + let category = mapping.borrow().source_model.category(); + match category { + SourceCategory::Midi => { + let source_type = mapping.borrow().source_model.midi_source_type(); + match source_type { + MidiSourceType::Raw => { + let session = self.session.clone(); + let engine = Box::new(RawMidiScriptEngine); + let help_url = "https://docs.helgoboss.org/realearn/goto#source-midi-raw"; + self.edit_script_in_simple_editor( + engine, + help_url, + |m| m.source_model.raw_midi_pattern().to_owned(), + move |m, text| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeSource(SourceCommand::SetRawMidiPattern( + text, + )), + None, + ); + }, + ); + } + MidiSourceType::Script => { + let session = self.session.clone(); + self.edit_midi_source_script_internal( + |m| m.source_model.midi_script().to_owned(), + move |m, eel| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeSource(SourceCommand::SetMidiScript(eel)), + None, + ); + }, + ); + } + _ => {} + } + } + SourceCategory::Osc => { + let session = self.session.clone(); + self.edit_osc_feedback_arguments_internal( + |m| format_osc_feedback_args(m.source_model.osc_feedback_args()), + move |m, text| { + let args = parse_osc_feedback_args(&text); + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeSource(SourceCommand::SetOscFeedbackArgs(args)), + None, + ); + }, + ); + } + SourceCategory::StreamDeck => { + let session = self.session.clone(); + self.edit_stream_deck_static_text_internal( + |m| m.source_model.button_static_text().to_string(), + move |m, text| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeSource(SourceCommand::SetButtonStaticText(text)), + None, + ); + }, + ); + } + _ => {} + } + } + + fn edit_control_transformation(&self) { + let session = self.session.clone(); + let engine = Box::new(EelControlTransformationEngine); + let help_url = "https://docs.helgoboss.org/realearn/goto#control-transformation"; + let get_value = |m: &MappingModel| m.mode_model.eel_control_transformation().to_owned(); + let set_value = move |m: &mut MappingModel, eel: String| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeMode(ModeCommand::SetEelControlTransformation(eel)), + None, + ); + }; + #[cfg(feature = "egui")] + self.edit_script_in_advanced_editor(engine, help_url, get_value, set_value); + #[cfg(not(feature = "egui"))] + self.edit_script_in_simple_editor(engine, help_url, get_value, set_value); + } + + fn edit_feedback_descriptor(&self) { + let mapping = self.mapping(); + let feedback_type = mapping.borrow().mode_model.feedback_type(); + match feedback_type { + FeedbackType::Numeric => self.edit_feedback_transformation(), + FeedbackType::Text => self.edit_textual_feedback_expression(), + FeedbackType::Dynamic => self.edit_lua_feedback_script(), + } + } + + fn edit_feedback_transformation(&self) { + let session = self.session.clone(); + self.edit_script_in_simple_editor( + Box::new(EelFeedbackTransformationEngine), + "https://docs.helgoboss.org/realearn/goto#numeric-feedback-type", + |m| m.mode_model.eel_feedback_transformation().to_owned(), + move |m, eel| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeMode(ModeCommand::SetEelFeedbackTransformation(eel)), + None, + ); + }, + ); + } + + fn edit_textual_feedback_expression(&self) { + let session = self.session.clone(); + self.edit_script_in_simple_editor( + Box::new(TextualFeedbackExpressionEngine), + "https://docs.helgoboss.org/realearn/goto#text-feedback", + |m| m.mode_model.textual_feedback_expression().to_owned(), + move |m, eel| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeMode(ModeCommand::SetTextualFeedbackExpression(eel)), + None, + ); + }, + ); + } + + fn edit_lua_feedback_script(&self) { + let session = self.session.clone(); + self.edit_script_in_simple_editor( + Box::new(LuaFeedbackScriptEngine::new()), + "https://docs.helgoboss.org/realearn/goto#dynamic-feedback", + |m| m.mode_model.textual_feedback_expression().to_owned(), + move |m, eel| { + UnitModel::change_mapping_from_ui_simple( + session.clone(), + m, + MappingCommand::ChangeMode(ModeCommand::SetTextualFeedbackExpression(eel)), + None, + ); + }, + ); + } + + fn edit_osc_feedback_arguments_internal( + &self, + get_initial_value: impl Fn(&MappingModel) -> String, + apply: impl Fn(&mut MappingModel, String) + 'static, + ) { + let engine = Box::new(OscFeedbackArgumentsEngine); + let help_url = "https://docs.helgoboss.org/realearn/goto#osc-feedback-arguments-expression"; + self.edit_script_in_simple_editor(engine, help_url, get_initial_value, apply); + } + + fn edit_stream_deck_static_text_internal( + &self, + get_initial_value: impl Fn(&MappingModel) -> String, + apply: impl Fn(&mut MappingModel, String) + 'static, + ) { + let engine = Box::new(PlainTextEngine); + let help_url = "https://docs.helgoboss.org/realearn/sources/stream-deck.html#linux"; + self.edit_script_in_simple_editor(engine, help_url, get_initial_value, apply); + } + + fn edit_midi_source_script_internal( + &self, + get_initial_value: impl Fn(&MappingModel) -> String, + apply: impl Fn(&mut MappingModel, String) + 'static, + ) { + let mapping = self.mapping(); + let engine: Box = match mapping.borrow().source_model.midi_script_kind() { + MidiScriptKind::Eel => Box::new(EelMidiScriptEngine), + MidiScriptKind::Lua => Box::new(LuaMidiScriptEngine::new()), + }; + let help_url = "https://docs.helgoboss.org/realearn/goto#source-midi-script"; + self.edit_script_in_simple_editor(engine, help_url, get_initial_value, apply); + } + + fn edit_script_in_simple_editor( + &self, + engine: Box, + help_url: &'static str, + get_initial_content: impl Fn(&MappingModel) -> String, + apply: impl Fn(&mut MappingModel, String) + 'static, + ) { + let mapping = self.mapping(); + let weak_mapping = Rc::downgrade(&mapping); + let initial_content = { get_initial_content(&mapping.borrow()) }; + let input = ScriptEditorInput { + initial_value: initial_content, + engine, + help_url, + set_value: move |edited_script| { + let m = match weak_mapping.upgrade() { + None => return, + Some(m) => m, + }; + apply(&mut m.borrow_mut(), edited_script); + }, + }; + let editor = SimpleScriptEditorPanel::new(input); + self.open_extra_panel(editor); + } + + #[cfg(feature = "egui")] + fn edit_script_in_advanced_editor( + &self, + engine: Box, + help_url: &'static str, + get_initial_content: impl Fn(&MappingModel) -> String, + apply: impl Fn(&mut MappingModel, String) + 'static, + ) { + let mapping = self.mapping(); + let weak_mapping = Rc::downgrade(&mapping); + let initial_content = { get_initial_content(&mapping.borrow()) }; + let input = ScriptEditorInput { + initial_value: initial_content, + engine, + help_url, + set_value: move |edited_script| { + let m = match weak_mapping.upgrade() { + None => return, + Some(m) => m, + }; + apply(&mut m.borrow_mut(), edited_script); + }, + }; + let editor = crate::infrastructure::ui::AdvancedScriptEditorPanel::new( + input, + crate::infrastructure::ui::CONTROL_TRANSFORMATION_TEMPLATES, + ); + self.open_extra_panel(editor); + } + + fn open_extra_panel(&self, panel: impl View + 'static) { + open_child_panel_dyn(&self.extra_panel, panel, self.view.require_window()); + } + + fn close_open_child_windows(&self) { + close_child_panel_if_open(&self.extra_panel); + } + + fn edit_yaml( + &self, + get_initial_value: impl Fn(&MappingModel) -> Option, + apply: impl Fn(&mut MappingModel, Option) -> Result<(), String> + 'static, + ) { + let mapping = self.mapping(); + let weak_mapping = Rc::downgrade(&mapping); + let initial_value = { get_initial_value(&mapping.borrow()) }; + let editor = YamlEditorPanel::new(initial_value, move |yaml_mapping| { + let m = match weak_mapping.upgrade() { + None => return, + Some(m) => m, + }; + let result = apply(&mut m.borrow_mut(), yaml_mapping); + if let Err(e) = result { + notification::alert(format!( + "Your changes have been applied and saved but they contain the following error and therefore won't have any effect:\n\n{e}" + )); + }; + }); + self.open_extra_panel(editor); + } + + fn edit_advanced_settings(&self) { + let session = self.session.clone(); + self.edit_yaml( + |m| m.advanced_settings().cloned(), + move |m, yaml| { + let session = session.upgrade().expect("session gone"); + let result = session.borrow_mut().change_mapping_with_closure( + m, + None, + Rc::downgrade(&session), + |ctx| ctx.mapping.set_advanced_settings(yaml), + ); + result + }, + ); + } + + pub fn handle_matched_mapping(self: SharedView) { + self.source_match_indicator_control().enable(); + self.view + .require_window() + .set_timer(SOURCE_MATCH_INDICATOR_TIMER_ID, Duration::from_millis(50)); + } + + pub fn handle_target_control_event(self: SharedView, event: TargetControlEvent) { + self.invoke_programmatically(|| { + let body = format!("Control: {} ({})", event.log_entry, event.log_context); + self.set_simple_help_text(Side::Left, ACTIVITY_INFO, &body); + }); + } + + pub fn handle_source_feedback_event(self: SharedView, event: SourceFeedbackEvent) { + self.invoke_programmatically(|| { + let body = format!("Feedback: {}", event.log_entry); + self.set_simple_help_text(Side::Right, ACTIVITY_INFO, &body); + }); + } + + fn set_simple_help_text(&self, side: Side, title: &str, body: &str) { + self.view + .require_control(root::ID_MAPPING_HELP_LEFT_SUBJECT_LABEL) + .set_text(title); + let label_control_id = match side { + Side::Left => root::ID_MAPPING_HELP_LEFT_CONTENT_LABEL, + Side::Right => root::ID_MAPPING_HELP_RIGHT_CONTENT_LABEL, + }; + self.view.require_control(label_control_id).set_text(body); + } + + pub fn handle_changed_target_value( + self: SharedView, + targets: &[CompoundMappingTarget], + new_value: AbsoluteValue, + ) { + self.invoke_programmatically(|| { + let session = self.session(); + let session = session.borrow(); + invalidate_target_controls_free( + // We use the target only to derive some characteristics. When having multiple + // targets, they should all share the same characteristics, so we can just take + // the first one. + targets.first(), + self.view + .require_control(root::ID_TARGET_VALUE_SLIDER_CONTROL), + self.view + .require_control(root::ID_TARGET_VALUE_EDIT_CONTROL), + self.view.require_control(root::ID_TARGET_VALUE_TEXT), + new_value, + None, + root::ID_TARGET_VALUE_EDIT_CONTROL, + true, + false, + self.displayed_mapping() + .map(|m| m.borrow().target_model.unit()) + .unwrap_or_default(), + session.control_context(), + ); + }); + } + + pub fn handle_changed_conditions(self: SharedView) { + self.clone().invoke_programmatically(|| { + let _ = self.read(|view| { + // These changes can happen because of removals (e.g. project close, FX deletions, + // track deletions etc.). We want to update whatever is possible. But if the own + // project is missing, this was a project close and we don't need to do anything + // at all. + if !view.target_with_context().project().is_available() { + return; + } + view.invalidate_target_controls(None); + view.invalidate_mode_controls(); + }); + }); + } + + pub fn notify_parameters_changed( + self: SharedView, + session: &UnitModel, + ) -> Result<(), &'static str> { + let mapping = self.displayed_mapping().ok_or("no mapping")?; + let mapping = mapping.borrow(); + self.invoke_programmatically(|| { + invalidate_target_line_2_expression_result( + &mapping.target_model, + session.extended_context(), + self.view.require_control(root::ID_TARGET_LINE_2_LABEL_3), + mapping.compartment(), + ); + invalidat_target_line_3_expression_result( + &mapping.target_model, + session.extended_context(), + self.view.require_control(root::ID_TARGET_LINE_3_LABEL_3), + mapping.compartment(), + ); + invalidate_target_line_4_expression_result( + &mapping.target_model, + session.extended_context(), + self.view.require_control(root::ID_TARGET_LINE_4_LABEL_3), + mapping.compartment(), + ); + }); + Ok(()) + } + + pub fn hide(&self) { + self.stop_party(); + self.view.require_window().hide(); + self.mapping.replace(None); + self.close_open_child_windows(); + self.mapping_header_panel.clear_item(); + } + + pub fn navigate_in_mappings( + self: SharedView, + direction: isize, + ) -> Result<(), &'static str> { + let current_id = self.qualified_mapping_id().ok_or("no current mapping")?; + let session = self.session(); + let session = session.borrow(); + let main_panel = self.main_panel(); + let main_state = main_panel.state(); + let main_state = main_state.borrow(); + let mappings: Vec<&SharedMapping> = MappingRowsPanel::filtered_mappings( + &session, + &main_state, + current_id.compartment, + false, + ) + .collect(); + let current_index = mappings + .iter() + .position(|m| m.borrow().id() == current_id.id) + .ok_or("current mapping not found in filtered list")?; + let new_index = + (current_index as isize + direction).rem_euclid(mappings.len() as isize) as usize; + let new_mapping = mappings.get(new_index).ok_or("new mapping not found")?; + self.show((**new_mapping).clone()); + Ok(()) + } + + pub fn show(self: SharedView, mapping: SharedMapping) { + self.invoke_programmatically(|| { + self.stop_party(); + self.mapping.replace(Some(mapping.clone())); + self.clone().start_party(); + self.mapping_header_panel.clone().set_item(mapping); + self.bring_to_foreground(); + }); + } + + pub fn bring_to_foreground(&self) { + let window = self.view.require_window(); + window.hide(); + window.show(); + } + + /// If you know a function in this view can be invoked by something else than the dialog + /// process, wrap your function body with this. Basically all pub functions! + /// + /// This prevents edit control text change events fired by windows to be processed. + /// It also prevents the function from being executed if the window is not open anymore, + /// which can happen when closing things. + fn invoke_programmatically(&self, f: impl FnOnce()) { + if self.view.window().is_none() { + return; + } + self.set_invoked_programmatically(true); + scopeguard::defer! { self.set_invoked_programmatically(false); } + f() + } + + /// Unregisters listeners. + fn stop_party(&self) { + self.party_is_over_subject.borrow_mut().next(()); + } + + /// Invalidates everything and registers listeners. + fn start_party(self: SharedView) { + self.read(|p| { + p.clear_help(); + p.fill_all_controls(); + p.invalidate_all_controls(); + p.register_listeners(); + }) + .expect("mapping must be filled at this point"); + } + + fn session(&self) -> SharedUnitModel { + self.session.upgrade().expect("session gone") + } + + pub fn displayed_mapping(&self) -> Option { + self.mapping.borrow().clone() + } + + fn mapping(&self) -> SharedMapping { + self.displayed_mapping().expect("mapping not filled") + } + + // TODO-low I think MappingHeaderPanel has a better solution. Maybe refactor. + fn read( + self: SharedView, + op: impl Fn(&ImmutableMappingPanel) -> R, + ) -> Result { + let shared_session = self.session(); + let session = shared_session.borrow(); + let shared_mapping = self.mapping.borrow(); + let shared_mapping = shared_mapping.as_ref().ok_or("mapping not filled")?; + let mapping = shared_mapping.borrow(); + let p = ImmutableMappingPanel { + session: &session, + mapping: &mapping, + source: &mapping.source_model, + mode: &mapping.mode_model, + target: &mapping.target_model, + view: &self.view, + panel: &self, + }; + Ok(op(&p)) + } + + fn open_target_menu(self: SharedView) { + enum MenuAction { + SetTarget(Box), + GoToTarget, + } + let menu = { + use swell_ui::menu_tree::*; + let mapping = self.mapping(); + let session = self.session(); + let session = session.borrow(); + let compartment = mapping.borrow().compartment(); + let context = session.extended_context(); + let recently_touched_items = Backbone::get() + .extract_last_touched_targets() + .into_iter() + .rev() + .map(|t| { + let mut target_model = TargetModel::default_for_compartment(compartment); + let _ = target_model.apply_from_target(&t, context, compartment); + // let target_type_label = ReaperTargetType::from_target(&t); + let target_label = TargetModelFormatVeryShort(&target_model); + // let label = format!("{target_type_label} / {target_label}"); + item(target_label.to_string(), MenuAction::SetTarget(Box::new(t))) + }) + .collect(); + anonymous_menu(vec![ + menu( + "Pick recently touched target (by type)", + recently_touched_items, + ), + item("Go to target (if supported)", MenuAction::GoToTarget), + ]) + }; + let menu_action = self + .view + .require_window() + .open_popup_menu(menu, Window::cursor_pos()); + let Some(menu_action) = menu_action else { + return; + }; + self.write(|panel| match &menu_action { + MenuAction::SetTarget(t) => { + panel.set_mapping_target(t); + } + MenuAction::GoToTarget => { + panel.go_to_target(); + } + }); + } + + fn show_target_type_menu(&self) { + let window = self.view.require_window(); + let (target_category, target_type, control_element_type) = { + let mapping = self.mapping(); + let mapping = mapping.borrow(); + ( + mapping.target_model.category(), + mapping.target_model.target_type(), + mapping.target_model.control_element_character(), + ) + }; + match target_category { + TargetCategory::Reaper => { + let menu = menus::reaper_target_type_menu(target_type); + if let Some(t) = window.open_popup_menu(menu, Window::cursor_pos()) { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetTargetType(t), + )); + } + } + TargetCategory::Virtual => { + let menu = menus::virtual_control_element_character_menu(control_element_type); + if let Some(t) = window.open_popup_menu(menu, Window::cursor_pos()) { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetControlElementCharacter(t), + )); + } + } + } + } + + fn write(self: SharedView, op: impl Fn(&mut MutableMappingPanel) -> R) -> R { + let shared_session = self.session(); + let mut session = shared_session.borrow_mut(); + let mut shared_mapping = self.mapping.borrow_mut(); + let shared_mapping = shared_mapping.as_mut().expect("mapping not filled"); + let mut mapping = shared_mapping.borrow_mut(); + let mut p = MutableMappingPanel { + session: &mut session, + mapping: &mut mapping, + panel: &self, + view: &self.view, + }; + op(&mut p) + } + + fn is_invoked_programmatically(&self) -> bool { + self.is_invoked_programmatically.get() + } + + fn init_controls(&self) { + let view = &self.view; + let sliders = WindowCache { + mode_min_target_value: view + .require_control(root::ID_SETTINGS_MIN_TARGET_VALUE_SLIDER_CONTROL), + mode_max_target_value: view + .require_control(root::ID_SETTINGS_MAX_TARGET_VALUE_SLIDER_CONTROL), + mode_min_source_value: view + .require_control(root::ID_SETTINGS_MIN_SOURCE_VALUE_SLIDER_CONTROL), + mode_max_source_value: view + .require_control(root::ID_SETTINGS_MAX_SOURCE_VALUE_SLIDER_CONTROL), + mode_min_step_size: view + .require_control(root::ID_SETTINGS_MIN_STEP_SIZE_SLIDER_CONTROL), + mode_max_step_size: view + .require_control(root::ID_SETTINGS_MAX_STEP_SIZE_SLIDER_CONTROL), + mode_fire_line_2: view.require_control(root::ID_MODE_FIRE_LINE_2_SLIDER_CONTROL), + mode_fire_line_3: view.require_control(root::ID_MODE_FIRE_LINE_3_SLIDER_CONTROL), + target_value: view.require_control(root::ID_TARGET_VALUE_SLIDER_CONTROL), + }; + self.window_cache.replace(Some(sliders)); + let indicator = self + .view + .require_control(root::IDC_MAPPING_MATCHED_INDICATOR_TEXT); + self.view + .require_control(root::ID_MAPPING_PANEL_PREVIOUS_BUTTON) + .set_text(symbols::arrow_left_symbol()); + self.view + .require_control(root::ID_MAPPING_PANEL_NEXT_BUTTON) + .set_text(symbols::arrow_right_symbol()); + indicator.set_text(symbols::indicator_symbol()); + indicator.disable(); + } + + fn party_is_over(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.view + .closed() + .merge(self.party_is_over_subject.borrow().clone()) + } + + fn when( + self: &SharedView, + event: impl LocalObservable<'static, Item = I, Err = ()> + 'static, + reaction: impl Fn(&ImmutableMappingPanel, I) + 'static + Copy, + ) { + when(event.take_until(self.party_is_over())) + .with(Rc::downgrade(self)) + .do_sync(decorate_reaction(reaction)); + } + + /// Returns self if not handled. + fn handle_potential_min_max_edit_control_change( + self: SharedView, + resource_id: u32, + ) -> Option> { + match resource_id { + root::ID_SETTINGS_MIN_TARGET_VALUE_EDIT_CONTROL => { + self.write(|p| p.update_mode_min_target_value_from_edit_control()); + } + root::ID_SETTINGS_MAX_TARGET_VALUE_EDIT_CONTROL => { + self.write(|p| p.update_mode_max_target_value_from_edit_control()); + } + root::ID_SETTINGS_MIN_SOURCE_VALUE_EDIT_CONTROL => { + self.write(|p| p.update_mode_min_source_value_from_edit_control()); + } + root::ID_SETTINGS_MAX_SOURCE_VALUE_EDIT_CONTROL => { + self.write(|p| p.update_mode_max_source_value_from_edit_control()); + } + root::ID_SETTINGS_MIN_STEP_SIZE_EDIT_CONTROL => { + self.write(|p| p.update_mode_min_step_from_edit_control()); + } + root::ID_SETTINGS_MAX_STEP_SIZE_EDIT_CONTROL => { + self.write(|p| p.update_mode_max_step_from_edit_control()); + } + root::ID_MODE_FIRE_LINE_2_EDIT_CONTROL => { + self.write(|p| p.handle_mode_fire_line_2_edit_control_change()); + } + root::ID_MODE_FIRE_LINE_3_EDIT_CONTROL => { + self.write(|p| p.handle_mode_fire_line_3_edit_control_change()); + } + _ => return Some(self), + }; + None + } +} + +impl Drop for MappingPanel { + fn drop(&mut self) { + self.close_open_child_windows(); + } +} + +fn decorate_reaction( + reaction: impl Fn(&ImmutableMappingPanel, I) + 'static + Copy, +) -> impl Fn(Rc, I) + Copy { + move |view, item| { + let view_mirror = view.clone(); + view_mirror.invoke_programmatically(|| { + // If the reaction can't be displayed anymore because the mapping is not filled anymore, + // so what. + let _ = view.read(move |p| reaction(p, item.clone())); + }); + } +} + +impl<'a> MutableMappingPanel<'a> { + fn resolved_targets(&self) -> Vec { + self.target_with_context().resolve().unwrap_or_default() + } + + fn first_resolved_target(&self) -> Option { + self.resolved_targets().into_iter().next() + } + + fn set_mapping_target(&mut self, target: &ReaperTarget) { + self.change_target_with_closure(None, |ctx| { + ctx.mapping.target_model.apply_from_target( + target, + ctx.extended_context, + ctx.mapping.compartment(), + ) + }); + } + + fn go_to_target(&self) { + if let Some(t) = self.first_resolved_target() { + let session = self.panel.session(); + Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(move || { + let session = session.borrow(); + t.open(session.control_context()) + }) + .unwrap(); + } + } + + fn handle_target_line_2_button_press_internal(&mut self) { + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::GoToBookmark => { + let project = self + .session + .processor_context() + .project_or_current_project(); + let current_bookmark_data = project.current_bookmark(); + let (bookmark_type, bookmark_index) = match ( + current_bookmark_data.marker_index, + current_bookmark_data.region_index, + ) { + (None, None) => return, + (Some(i), None) => (BookmarkType::Marker, i), + (None, Some(i)) => (BookmarkType::Region, i), + (Some(mi), Some(ri)) => match self.mapping.target_model.bookmark_type() { + BookmarkType::Marker => (BookmarkType::Marker, mi), + BookmarkType::Region => (BookmarkType::Region, ri), + }, + }; + let bookmark_id = project + .find_bookmark_by_index(bookmark_index) + .unwrap() + .basic_info() + .id; + self.change_target_with_closure(None, |ctx| { + let target = &mut ctx.mapping.target_model; + target.change(TargetCommand::SetBookmarkAnchorType(BookmarkAnchorType::Id)); + target.change(TargetCommand::SetBookmarkType(bookmark_type)); + target.change(TargetCommand::SetBookmarkRef(bookmark_id.get())); + Some(Affected::Multiple) + }); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn update_mapping_feedback_send_behavior(&mut self) { + let behavior = self + .view + .require_control(root::ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX) + .selected_combo_box_item_index() + .try_into() + .expect("invalid feedback send behavior"); + self.change_mapping(MappingCommand::SetFeedbackSendBehavior(behavior)); + } + + fn change_mapping(&mut self, val: MappingCommand) { + self.change_mapping_with_initiator(val, None); + } + + fn change_target_with_closure( + &mut self, + initiator: Option, + f: impl FnOnce(MappingChangeContext) -> Option>, + ) { + self.session.change_target_with_closure( + self.mapping, + initiator, + self.panel.session.clone(), + f, + ); + } + + fn change_mapping_with_initiator(&mut self, val: MappingCommand, initiator: Option) { + self.session.change_mapping_from_ui_expert( + self.mapping, + val, + initiator, + self.panel.session.clone(), + ); + } + + fn update_beep_on_success(&mut self) { + let checked = self + .view + .require_control(root::IDC_BEEP_ON_SUCCESS_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::SetBeepOnSuccess(checked)); + } + + fn update_mapping_is_enabled(&mut self) { + let checked = self + .view + .require_control(root::IDC_MAPPING_ENABLED_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::SetIsEnabled(checked)); + } + + fn update_mapping_is_visible_in_projection(&mut self) { + let checked = self + .view + .require_control(root::ID_MAPPING_SHOW_IN_PROJECTION_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::SetVisibleInProjection(checked)); + } + + fn handle_source_line_4_check_box_change(&mut self) { + let checked = self + .view + .require_control(root::ID_SOURCE_RPN_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetIsRegistered(Some(checked)), + )); + } + + fn handle_source_check_box_2_change(&mut self) { + let checked = self + .view + .require_control(root::ID_SOURCE_14_BIT_CHECK_BOX) + .is_checked(); + use SourceCategory::*; + match self.mapping.source_model.category() { + Midi => { + self.change_mapping(MappingCommand::ChangeSource(SourceCommand::SetIs14Bit( + Some(checked), + ))); + } + Osc => { + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetOscArgIsRelative(checked), + )); + } + Reaper | Virtual | Never | Keyboard | StreamDeck => {} + }; + } + + fn handle_source_line_3_combo_box_change(&mut self) { + let b = self.view.require_control(root::ID_SOURCE_CHANNEL_COMBO_BOX); + use SourceCategory::*; + match self.mapping.source_model.category() { + Midi => match self.mapping.source_model.midi_source_type() { + MidiSourceType::ClockTransport => { + let i = b.selected_combo_box_item_index(); + let msg_type = i.try_into().expect("invalid MTC message type"); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetMidiClockTransportMessage(msg_type), + )); + } + MidiSourceType::Display => { + let i = b.selected_combo_box_item_index(); + let display_type = i.try_into().expect("invalid display type"); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetDisplayType(display_type), + )); + } + MidiSourceType::Script => { + let i = b.selected_combo_box_item_index(); + let kind = i.try_into().expect("invalid source script kind"); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetMidiScriptKind(kind), + )); + } + t if t.supports_channel() => { + let value = match b.selected_combo_box_item_data() { + -1 => None, + id => Some(Channel::new(id as _)), + }; + self.change_mapping(MappingCommand::ChangeSource(SourceCommand::SetChannel( + value, + ))); + } + _ => {} + }, + Reaper => match self.mapping.source_model.reaper_source_type() { + ReaperSourceType::RealearnParameter => { + let index = b.selected_combo_box_item_index() as u32; + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetParameterIndex( + index.try_into().expect("invalid param index"), + ), + )); + } + _ => {} + }, + StreamDeck => { + let index = b.selected_combo_box_item_index() as u32; + self.change_mapping(MappingCommand::ChangeSource(SourceCommand::SetButtonIndex( + index, + ))); + } + _ => {} + }; + } + + fn handle_source_line_4_combo_box_1_change(&mut self) { + let combo = self + .view + .require_control(root::ID_SOURCE_LINE_4_COMBO_BOX_1); + use SourceCategory::*; + match self.mapping.source_model.category() { + Osc => { + let index = get_osc_arg_index_from_combo(combo); + self.change_mapping(MappingCommand::ChangeSource(SourceCommand::SetOscArgIndex( + index, + ))); + } + _ => {} + } + } + + fn handle_source_line_4_combo_box_2_change(&mut self) { + let b = self.view.require_control(root::ID_SOURCE_NUMBER_COMBO_BOX); + use SourceCategory::*; + match self.mapping.source_model.category() { + Midi => { + use MidiSourceType::*; + match self.mapping.source_model.midi_source_type() { + Display => { + use DisplayType::*; + let value = match self.mapping.source_model.display_type() { + MackieLcd | MackieXtLcd | XTouchMackieLcd | XTouchMackieXtLcd + | SiniConE24 | SlKeyboardDisplay => { + match b.selected_combo_box_item_data() { + -1 => None, + id => Some(id as u8), + } + } + MackieSevenSegmentDisplay => { + Some(b.selected_combo_box_item_index() as u8) + } + LaunchpadProScrollingText => return, + }; + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetDisplayId(value), + )); + } + t if t.supports_midi_message_number() => { + let value = match b.selected_combo_box_item_data() { + -1 => None, + id => Some(U7::new(id as _)), + }; + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetMidiMessageNumber(value), + )); + } + _ => {} + } + } + Osc => { + let tag = get_osc_type_tag_from_combo(b); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetOscArgTypeTag(tag), + )); + } + _ => {} + } + } + + fn handle_source_line_5_combo_box_change(&mut self) { + let b = self + .view + .require_control(root::ID_SOURCE_CHARACTER_COMBO_BOX); + use SourceCategory::*; + match self.mapping.source_model.category() { + Midi => { + use MidiSourceType::*; + match self.mapping.source_model.midi_source_type() { + Display => { + let value = match b.selected_combo_box_item_data() { + -1 => None, + id => Some(id as u8), + }; + self.change_mapping(MappingCommand::ChangeSource(SourceCommand::SetLine( + value, + ))); + } + t if t.supports_custom_character() => { + let i = b.selected_combo_box_item_index(); + let character = i.try_into().expect("invalid source character"); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetCustomCharacter(character), + )); + } + _ => {} + } + } + _ => {} + } + } + + fn update_source_category(&mut self) { + let b = self + .view + .require_control(root::ID_SOURCE_CATEGORY_COMBO_BOX); + let category = b + .selected_combo_box_item_index() + .try_into() + .expect("invalid source category"); + self.change_mapping(MappingCommand::ChangeSource(SourceCommand::SetCategory( + category, + ))); + } + + fn handle_source_line_2_combo_box_change(&mut self) { + let b = self.view.require_control(root::ID_SOURCE_TYPE_COMBO_BOX); + let i = b.selected_combo_box_item_index(); + use SourceCategory::*; + match self.mapping.source_model.category() { + Midi => { + let source_type = i.try_into().expect("invalid MIDI source type"); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetMidiSourceType(source_type), + )); + } + Reaper => { + let source_type = i.try_into().expect("invalid REAPER source type"); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetReaperSourceType(source_type), + )); + } + Virtual => { + let element_type = i.try_into().expect("invalid virtual source type"); + self.change_mapping(MappingCommand::ChangeSource( + SourceCommand::SetControlElementCharacter(element_type), + )); + } + _ => {} + }; + } + + fn handle_source_line_4_edit_control_change(&mut self) { + let edit_control_id = root::ID_SOURCE_NUMBER_EDIT_CONTROL; + let c = self.view.require_control(edit_control_id); + let text = c.text().unwrap_or_default(); + use SourceCategory::*; + match self.mapping.source_model.category() { + Midi => { + let value = text.parse().ok(); + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetParameterNumberMessageNumber( + value, + )), + Some(edit_control_id), + ); + } + StreamDeck => { + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetButtonBackgroundImagePath( + text.into(), + )), + Some(edit_control_id), + ); + } + Virtual => { + let value = text.parse().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetControlElementId(value)), + Some(edit_control_id), + ); + } + Reaper | Never | Keyboard | Osc => {} + }; + } + + fn handle_source_line_5_edit_control_change(&mut self) { + let edit_control_id = root::ID_SOURCE_LINE_5_EDIT_CONTROL; + let c = self.view.require_control(edit_control_id); + let text = c.text().unwrap_or_default(); + use SourceCategory::*; + match self.mapping.source_model.category() { + StreamDeck => { + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetButtonForegroundImagePath( + text.into(), + )), + Some(edit_control_id), + ); + } + Osc => { + let v = parse_osc_arg_value_range(&text); + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetOscArgValueRange(v)), + Some(edit_control_id), + ); + } + _ => {} + }; + } + + fn handle_source_line_3_edit_control_change(&mut self) { + let edit_control_id = root::ID_SOURCE_LINE_3_EDIT_CONTROL; + let c = self.view.require_control(edit_control_id); + if let Ok(value) = c.text() { + use SourceCategory::*; + match self.mapping.source_model.category() { + Osc => { + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetOscAddressPattern(value)), + Some(edit_control_id), + ); + } + Reaper => match self.mapping.source_model.reaper_source_type() { + ReaperSourceType::Timer => { + let value = value.parse().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetTimerMillis(value)), + Some(edit_control_id), + ) + } + _ => {} + }, + Midi | Virtual | Never | Keyboard | StreamDeck => {} + } + } + } + + fn handle_source_line_7_edit_control_change(&mut self) { + let edit_control_id = root::ID_SOURCE_OSC_ADDRESS_PATTERN_EDIT_CONTROL; + let c = self.view.require_control(edit_control_id); + let value = c.text().unwrap_or_default(); + use SourceCategory::*; + match self.mapping.source_model.category() { + Midi => match self.mapping.source_model.midi_source_type() { + MidiSourceType::Raw => { + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetRawMidiPattern(value)), + Some(edit_control_id), + ); + } + MidiSourceType::Script => { + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetMidiScript(value)), + Some(edit_control_id), + ); + } + _ => {} + }, + Osc => { + let args = parse_osc_feedback_args(&value); + self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetOscFeedbackArgs(args)), + Some(edit_control_id), + ) + } + StreamDeck => self.change_mapping_with_initiator( + MappingCommand::ChangeSource(SourceCommand::SetButtonStaticText(value)), + Some(edit_control_id), + ), + _ => {} + } + } + + fn update_mode_rotate(&mut self) { + let checked = self + .view + .require_control(root::ID_SETTINGS_ROTATE_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetRotate(checked))); + } + + fn update_mode_feedback_type(&mut self) { + let index = self + .view + .require_control(root::IDC_MODE_FEEDBACK_TYPE_COMBO_BOX) + .selected_combo_box_item_index(); + let feedback_type = index.try_into().expect("unknown feedback type"); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetFeedbackType( + feedback_type, + ))); + } + + fn update_mode_make_absolute(&mut self) { + let checked = self + .view + .require_control(root::ID_SETTINGS_MAKE_ABSOLUTE_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetMakeAbsolute( + checked, + ))); + } + + fn update_mode_out_of_range_behavior(&mut self) { + let behavior = self + .view + .require_control(root::ID_MODE_OUT_OF_RANGE_COMBOX_BOX) + .selected_combo_box_item_index() + .try_into() + .expect("invalid out-of-range behavior"); + self.change_mapping(MappingCommand::ChangeMode( + ModeCommand::SetOutOfRangeBehavior(behavior), + )); + } + + fn update_mode_group_interaction(&mut self) { + let interaction = self + .view + .require_control(root::ID_MODE_GROUP_INTERACTION_COMBO_BOX) + .selected_combo_box_item_index() + .try_into() + .expect("invalid group interaction"); + self.change_mapping(MappingCommand::ChangeMode( + ModeCommand::SetGroupInteraction(interaction), + )); + } + + fn update_mode_fire_mode(&mut self) { + let mode = self + .view + .require_control(root::ID_MODE_FIRE_COMBO_BOX) + .selected_combo_box_item_index() + .try_into() + .expect("invalid fire mode"); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetFireMode(mode))); + } + + fn update_mode_round_target_value(&mut self) { + let checked = self + .view + .require_control(root::ID_SETTINGS_ROUND_TARGET_VALUE_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::ChangeMode( + ModeCommand::SetRoundTargetValue(checked), + )); + } + + fn update_takeover_mode(&mut self) { + let mode = self + .view + .require_control(root::ID_MODE_TAKEOVER_MODE) + .selected_combo_box_item_index() + .try_into() + .expect("invalid takeover mode"); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetTakeoverMode( + mode, + ))); + } + + fn update_button_usage(&mut self) { + let mode = self + .view + .require_control(root::ID_MODE_BUTTON_FILTER_COMBO_BOX) + .selected_combo_box_item_index() + .try_into() + .expect("invalid button usage"); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetButtonUsage( + mode, + ))); + } + + fn update_encoder_usage(&mut self) { + let mode = self + .view + .require_control(root::ID_MODE_RELATIVE_FILTER_COMBO_BOX) + .selected_combo_box_item_index() + .try_into() + .expect("invalid encoder usage"); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetEncoderUsage( + mode, + ))); + } + + fn update_mode_reverse(&mut self) { + let checked = self + .view + .require_control(root::ID_SETTINGS_REVERSE_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetReverse(checked))); + } + + fn reset_mode(&mut self) { + let _ = self.session.change_mapping_with_closure( + self.mapping, + None, + self.panel.session.clone(), + |ctx| Ok(ctx.mapping.reset_mode(ctx.extended_context)), + ); + } + + fn update_mode_type(&mut self) { + let b = self.view.require_control(root::ID_SETTINGS_MODE_COMBO_BOX); + let mode = b + .selected_combo_box_item_index() + .try_into() + .expect("invalid mode type"); + let _ = self.session.change_mapping_with_closure( + self.mapping, + None, + self.panel.session.clone(), + |ctx| { + Ok(ctx + .mapping + .set_absolute_mode_and_preferred_values(ctx.extended_context, mode)) + }, + ); + } + + fn update_mode_min_target_value_from_edit_control(&mut self) { + let control_id = root::ID_SETTINGS_MIN_TARGET_VALUE_EDIT_CONTROL; + let value = self + .get_value_from_target_edit_control(control_id) + .unwrap_or(UnitValue::MIN); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetMinTargetValue(value)), + Some(control_id), + ); + } + + fn get_value_from_target_edit_control(&self, edit_control_id: u32) -> Option { + let target = self.first_resolved_target()?; + let text = self.view.require_control(edit_control_id).text().ok()?; + let control_context = self.session.control_context(); + match self.mapping.target_model.unit() { + TargetUnit::Native => target.parse_as_value(text.as_str(), control_context).ok(), + TargetUnit::Percent => parse_unit_value_from_percentage(&text).ok(), + } + } + + fn get_step_size_from_target_edit_control(&self, edit_control_id: u32) -> Option { + let target = self.first_resolved_target()?; + let text = self.view.require_control(edit_control_id).text().ok()?; + let control_context = self.session.control_context(); + match self.mapping.target_model.unit() { + TargetUnit::Native => target + .parse_as_step_size(text.as_str(), control_context) + .ok(), + TargetUnit::Percent => parse_unit_value_from_percentage(&text).ok(), + } + } + + fn update_mode_max_target_value_from_edit_control(&mut self) { + let control_id = root::ID_SETTINGS_MAX_TARGET_VALUE_EDIT_CONTROL; + let value = self + .get_value_from_target_edit_control(control_id) + .unwrap_or(UnitValue::MAX); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetMaxTargetValue(value)), + Some(control_id), + ); + } + + fn update_mode_min_source_value_from_edit_control(&mut self) { + let control_id = root::ID_SETTINGS_MIN_SOURCE_VALUE_EDIT_CONTROL; + let value = self + .get_value_from_source_edit_control(control_id) + .unwrap_or(UnitValue::MIN); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetMinSourceValue(value)), + Some(control_id), + ); + } + + fn get_value_from_source_edit_control(&self, edit_control_id: u32) -> Option { + let text = self.view.require_control(edit_control_id).text().ok()?; + self.mapping + .source_model + .parse_control_value(text.as_str()) + .ok() + } + + fn update_mode_max_source_value_from_edit_control(&mut self) { + let control_id = root::ID_SETTINGS_MAX_SOURCE_VALUE_EDIT_CONTROL; + let value = self + .get_value_from_source_edit_control(control_id) + .unwrap_or(UnitValue::MAX); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetMaxSourceValue(value)), + Some(control_id), + ); + } + + fn update_mode_min_step_from_edit_control(&mut self) { + self.update_mode_step_from_edit_control( + root::ID_SETTINGS_MIN_STEP_SIZE_EDIT_CONTROL, + ModeCommand::SetMinStepFactor, + DiscreteIncrement::new(1), + ModeCommand::SetMinStepSize, + UnitValue::MIN, + ); + } + + fn update_mode_step_from_edit_control( + &mut self, + control_id: u32, + factor_command: impl FnOnce(DiscreteIncrement) -> ModeCommand, + default_factor: DiscreteIncrement, + size_command: impl FnOnce(UnitValue) -> ModeCommand, + default_size: UnitValue, + ) { + if self.mapping_uses_step_factors() { + let value = self + .get_value_from_step_factor_edit_control(control_id) + .unwrap_or(default_factor); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(factor_command(value)), + Some(control_id), + ); + } else { + let value = self + .get_value_from_step_size_edit_control(control_id) + .unwrap_or(default_size); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(size_command(value)), + Some(control_id), + ); + } + } + + fn handle_mode_fire_line_2_edit_control_change(&mut self) { + let control_id = root::ID_MODE_FIRE_LINE_2_EDIT_CONTROL; + let value = self + .get_value_from_duration_edit_control(control_id) + .unwrap_or_else(|| Duration::from_millis(0)); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetMinPressDuration(value)), + Some(control_id), + ); + } + + fn get_value_from_duration_edit_control(&self, edit_control_id: u32) -> Option { + let text = self.view.require_control(edit_control_id).text().ok()?; + text.parse::().ok().map(Duration::from_millis) + } + + fn get_value_from_step_factor_edit_control( + &self, + edit_control_id: u32, + ) -> Option { + let text = self.view.require_control(edit_control_id).text().ok()?; + let number: i32 = text.parse().ok()?; + number.try_into().ok() + } + + fn get_value_from_step_size_edit_control(&self, edit_control_id: u32) -> Option { + self.get_step_size_from_target_edit_control(edit_control_id) + } + + fn update_mode_max_step_from_edit_control(&mut self) { + self.update_mode_step_from_edit_control( + root::ID_SETTINGS_MAX_STEP_SIZE_EDIT_CONTROL, + ModeCommand::SetMaxStepFactor, + DiscreteIncrement::new(100), + ModeCommand::SetMaxStepSize, + UnitValue::MAX, + ); + } + + fn handle_mode_fire_line_3_edit_control_change(&mut self) { + let control_id = root::ID_MODE_FIRE_LINE_3_EDIT_CONTROL; + let value = self + .get_value_from_duration_edit_control(control_id) + .unwrap_or_else(|| Duration::from_millis(0)); + self.handle_mode_fire_line_3_duration_change(value, Some(control_id)); + } + + fn update_mode_target_value_sequence(&mut self) { + let control_id = root::ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL; + let text = self + .view + .require_control(control_id) + .text() + .unwrap_or_else(|_| "".to_string()); + let sequence = match self.mapping.target_model.unit() { + TargetUnit::Native => { + if let Some(t) = self.first_resolved_target() { + let t = WithControlContext::new(self.session.control_context(), &t); + ValueSequence::parse(&t, &text) + } else { + ValueSequence::parse(&PercentIo, &text) + } + } + TargetUnit::Percent => ValueSequence::parse(&PercentIo, &text), + }; + let sequence = sequence.unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetTargetValueSequence(sequence)), + Some(control_id), + ); + } + + fn update_mode_eel_control_transformation(&mut self) { + let control_id = root::ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL; + let value = self + .view + .require_control(control_id) + .text() + .unwrap_or_else(|_| "".to_string()); + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetEelControlTransformation(value)), + Some(control_id), + ); + } + + fn update_mode_feedback_transformation(&mut self) { + let control_id = root::ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL; + let value = self + .view + .require_control(control_id) + .text() + .unwrap_or_else(|_| "".to_string()); + let cmd = if self.mapping.mode_model.feedback_type().is_textual() { + ModeCommand::SetTextualFeedbackExpression(value) + } else { + ModeCommand::SetEelFeedbackTransformation(value) + }; + self.change_mapping_with_initiator(MappingCommand::ChangeMode(cmd), Some(control_id)); + } + + fn update_mode_min_target_value_from_slider(&mut self, slider: Window) { + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetMinTargetValue( + slider.slider_unit_value(), + ))); + } + + fn update_mode_max_target_value_from_slider(&mut self, slider: Window) { + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetMaxTargetValue( + slider.slider_unit_value(), + ))); + } + + fn update_mode_min_source_value_from_slider(&mut self, slider: Window) { + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetMinSourceValue( + slider.slider_unit_value(), + ))); + } + + fn update_mode_max_source_value_from_slider(&mut self, slider: Window) { + self.change_mapping(MappingCommand::ChangeMode(ModeCommand::SetMaxSourceValue( + slider.slider_unit_value(), + ))); + } + + fn update_mode_min_step_from_slider(&mut self, slider: Window) { + self.update_mode_step_from_slider( + slider, + ModeCommand::SetMinStepFactor, + ModeCommand::SetMinStepSize, + ) + } + + fn update_mode_max_step_from_slider(&mut self, slider: Window) { + self.update_mode_step_from_slider( + slider, + ModeCommand::SetMaxStepFactor, + ModeCommand::SetMaxStepSize, + ) + } + + fn update_mode_step_from_slider( + &mut self, + slider: Window, + factor_command: impl FnOnce(DiscreteIncrement) -> ModeCommand, + size_command: impl FnOnce(UnitValue) -> ModeCommand, + ) { + if self.mapping_uses_step_factors() { + let value = slider.slider_discrete_increment(); + self.change_mapping(MappingCommand::ChangeMode(factor_command(value))); + } else { + let value = slider.slider_unit_value(); + self.change_mapping(MappingCommand::ChangeMode(size_command(value))); + }; + } + + fn handle_mode_fire_line_2_slider_change(&mut self, slider: Window) { + self.change_mapping(MappingCommand::ChangeMode( + ModeCommand::SetMinPressDuration(slider.slider_duration()), + )); + } + + fn handle_mode_fire_line_3_slider_change(&mut self, slider: Window) { + let value = slider.slider_duration(); + self.handle_mode_fire_line_3_duration_change(value, None); + } + + fn handle_mode_fire_line_3_duration_change(&mut self, value: Duration, initiator: Option) { + match self.mapping.mode_model.fire_mode() { + FireMode::Normal | FireMode::OnSinglePress | FireMode::OnDoublePress => { + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetMaxPressDuration(value)), + initiator, + ); + } + FireMode::AfterTimeout => {} + FireMode::AfterTimeoutKeepFiring => { + self.change_mapping_with_initiator( + MappingCommand::ChangeMode(ModeCommand::SetTurboRate(value)), + initiator, + ); + } + } + } + + fn mapping_uses_step_factors(&self) -> bool { + self.mapping + .with_context(self.session.extended_context()) + .uses_step_factors() + } + + fn handle_target_check_box_1_change(&mut self) { + let is_checked = self + .view + .require_control(root::ID_TARGET_CHECK_BOX_1) + .is_checked(); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Action => { + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetWithTrack( + is_checked, + ))); + } + ReaperTargetType::GoToBookmark => { + let bookmark_type = if is_checked { + BookmarkType::Region + } else { + BookmarkType::Marker + }; + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetBookmarkType(bookmark_type), + )); + } + ReaperTargetType::PlaytimeSlotTransportAction => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetStopColumnIfSlotEmpty(is_checked), + )); + } + _ if self.mapping.target_model.supports_fx_chain() => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetFxIsInputFx(is_checked), + )); + } + t if t.supports_track_scrolling() => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetScrollArrangeView(is_checked), + )); + } + ReaperTargetType::Seek => { + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetSeekPlay( + is_checked, + ))); + } + _ => {} + }, + TargetCategory::Virtual => { + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetLearnable( + is_checked, + ))); + } + } + } + + fn handle_target_check_box_2_change(&mut self) { + let is_checked = self + .view + .require_control(root::ID_TARGET_CHECK_BOX_2) + .is_checked(); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + _ if self.mapping.target_model.supports_track_must_be_selected() => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetEnableOnlyIfTrackSelected(is_checked), + )); + } + t if t.supports_track_scrolling() => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetScrollMixer(is_checked), + )); + } + ReaperTargetType::Seek => { + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetMoveView( + is_checked, + ))); + } + ReaperTargetType::LoadMappingSnapshot => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetActiveMappingsOnly(is_checked), + )); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_check_box_3_change(&mut self) { + let is_checked = self + .view + .require_control(root::ID_TARGET_CHECK_BOX_3) + .is_checked(); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + t if t.supports_fx() => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetEnableOnlyIfFxHasFocus(is_checked), + )); + } + ReaperTargetType::Seek => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetUseProject(is_checked), + )); + } + t if self.mapping.target_model.supports_gang_selected() => { + let gang_behavior = TrackGangBehavior::from_bools( + t.definition(), + is_checked, + self.mapping + .target_model + .fixed_gang_behavior() + .use_track_grouping(), + ); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetGangBehavior(gang_behavior), + )); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_check_box_4_change(&mut self) { + let is_checked = self + .view + .require_control(root::ID_TARGET_CHECK_BOX_4) + .is_checked(); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Seek => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetUseRegions(is_checked), + )); + } + t if t.supports_poll_for_feedback() => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPollForFeedback(is_checked), + )); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_check_box_5_change(&mut self) { + let is_checked = self + .view + .require_control(root::ID_TARGET_CHECK_BOX_5) + .is_checked(); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::FxParameterValue => self.change_mapping( + MappingCommand::ChangeTarget(TargetCommand::SetRetrigger(is_checked)), + ), + ReaperTargetType::Seek | ReaperTargetType::GoToBookmark => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetUseLoopPoints(is_checked), + )); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_check_box_6_change(&mut self) { + let is_checked = self + .view + .require_control(root::ID_TARGET_CHECK_BOX_6) + .is_checked(); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::FxParameterValue => self.change_mapping( + MappingCommand::ChangeTarget(TargetCommand::SetRealTime(is_checked)), + ), + ReaperTargetType::Seek | ReaperTargetType::GoToBookmark => { + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetUseTimeSelection(is_checked), + )); + } + t if self.mapping.target_model.supports_gang_grouping() => { + let gang_behavior = TrackGangBehavior::from_bools( + t.definition(), + self.mapping + .target_model + .fixed_gang_behavior() + .use_selection_ganging(), + is_checked, + ); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetGangBehavior(gang_behavior), + )); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_unit_button_press(&mut self) { + use TargetUnit::*; + let next_unit = match self.mapping.target_model.unit() { + Native => Percent, + Percent => Native, + }; + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetUnit( + next_unit, + ))); + } + + fn update_target_category(&mut self) { + let b = self + .view + .require_control(root::ID_TARGET_CATEGORY_COMBO_BOX); + let category = b + .selected_combo_box_item_index() + .try_into() + .expect("invalid target category"); + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetCategory( + category, + ))); + } + + fn handle_target_line_2_combo_box_1_change(&mut self) { + let combo_id = root::ID_TARGET_LINE_2_COMBO_BOX_1; + let combo = self.view.require_control(combo_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::GoToBookmark => { + let bookmark_anchor_type = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetBookmarkAnchorType(bookmark_anchor_type), + )); + } + ReaperTargetType::LoadMappingSnapshot => { + let snapshot_type = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMappingSnapshotTypeForLoad(snapshot_type), + )); + } + ReaperTargetType::TakeMappingSnapshot => { + let snapshot_type = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMappingSnapshotTypeForTake(snapshot_type), + )); + } + t if t.supports_feedback_resolution() => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid feedback resolution"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetFeedbackResolution(v), + )); + } + _ if self.mapping.target_model.supports_track() => { + let track_type = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_target_with_closure(Some(combo_id), |ctx| { + ctx.mapping + .target_model + .set_track_type_from_ui(track_type, ctx.extended_context.context) + }); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_3_combo_box_1_change(&mut self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_3_COMBO_BOX_1); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Action => { + let scope: ActionScope = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetActionScope(scope), + )); + } + ReaperTargetType::EnableInstances => { + let tag_kind: InstanceTagKind = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetInstanceTagKind(tag_kind), + )); + } + ReaperTargetType::PlaytimeColumnAction => { + let kind = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + let desc = PlaytimeColumnDescriptor::from_kind(kind); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeColumn(desc), + )); + } + ReaperTargetType::PlaytimeRowAction => { + let kind = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + let desc = PlaytimeRowDescriptor::from_kind(kind); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeRow(desc), + )); + } + ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction + | ReaperTargetType::PlaytimeSlotSeek + | ReaperTargetType::PlaytimeSlotVolume => { + let kind = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + let desc = PlaytimeSlotDescriptor::from_kind(kind); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeSlot(desc), + )); + } + t if t.supports_fx() => { + let fx_type = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_target_with_closure(None, |ctx| { + ctx.mapping.target_model.set_fx_type_from_ui( + fx_type, + ctx.extended_context, + ctx.mapping.compartment(), + ) + }); + } + t if t.supports_send() => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid route type"); + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetRouteType( + v, + ))); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_4_combo_box_1_change(&mut self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_4_COMBO_BOX_1); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc => { + let index = get_osc_arg_index_from_combo(combo); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetOscArgIndex(index), + )); + } + ReaperTargetType::BrowseGroup => { + let exclusivity: SimpleExclusivity = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetExclusivity(exclusivity.into()), + )); + } + ReaperTargetType::TrackTool => { + let action: TrackToolAction = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetTrackToolAction(action), + )); + } + ReaperTargetType::FxTool => { + let action: FxToolAction = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetFxToolAction(action), + )); + } + t if t.supports_fx_parameter() => { + let param_type = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetParamType( + param_type, + ))); + } + t if t.supports_exclusivity() => { + let exclusivity = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetExclusivity(exclusivity), + )); + } + t if t.supports_send() => { + let selector_type = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetRouteSelectorType(selector_type), + )); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_2_combo_box_2_change(&mut self) { + let combo_id = root::ID_TARGET_LINE_2_COMBO_BOX_2; + let combo = self.view.require_control(combo_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::GoToBookmark => { + let value: u32 = combo.selected_combo_box_item_data() as _; + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetBookmarkRef(value), + )); + } + ReaperTargetType::AutomationModeOverride => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid automation mode override type"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetAutomationModeOverrideType(v), + )); + } + ReaperTargetType::Mouse => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid mouse action type"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMouseActionType(v), + )); + } + ReaperTargetType::Transport => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid transport action"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetTransportAction(v), + )); + } + ReaperTargetType::AnyOn => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid any-on parameter"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetAnyOnParameter(v), + )); + } + ReaperTargetType::BrowseGroup => { + let i = combo.selected_combo_box_item_index(); + let group_id = self + .session + .find_group_by_index_sorted(self.mapping.compartment(), i) + .expect("group not existing") + .borrow() + .id(); + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetGroupId( + group_id, + ))); + } + ReaperTargetType::SendMidi => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid send MIDI destination"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetSendMidiDestinationType(v), + )); + } + ReaperTargetType::SendOsc => { + let dev_id = match combo.selected_combo_box_item_data() { + -1 => None, + i if i >= 0 => BackboneShell::get() + .osc_device_manager() + .borrow() + .find_device_by_index(i as usize) + .map(|dev| *dev.id()), + _ => None, + }; + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetOscDevId( + dev_id, + ))); + } + ReaperTargetType::BrowseTracks => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid track indexing policy"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetBrowseTracksMode(v), + )); + } + ReaperTargetType::BrowsePotFilterItems => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid pot filter item kind"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPotFilterItemKind(v), + )); + } + ReaperTargetType::ModifyMapping => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid mapping feature"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMappingModificationKind(v), + )); + } + ReaperTargetType::PlaytimeMatrixAction => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid matrix action"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeMatrixAction(v), + )); + } + ReaperTargetType::PlaytimeColumnAction => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid column action"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeColumnAction(v), + )); + } + ReaperTargetType::PlaytimeRowAction => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid row action"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeRowAction(v), + )); + } + ReaperTargetType::PlaytimeSlotManagementAction => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid slot management action"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeSlotManagementAction(v), + )); + } + ReaperTargetType::PlaytimeSlotTransportAction => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid slot transport action"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetPlaytimeSlotTransportAction(v), + )); + } + _ if self.mapping.target_model.supports_track() => { + let project = self + .session + .processor_context() + .project_or_current_project(); + let i = combo.selected_combo_box_item_index(); + if let Some(track) = project.track_by_index(i as _) { + self.change_target_with_closure(Some(combo_id), |ctx| { + ctx.mapping.target_model.set_concrete_track( + ConcreteTrackInstruction::ByIdWithTrack(track), + false, + true, + ) + }); + } + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_3_combo_box_2_change(&mut self) { + let combo_id = root::ID_TARGET_LINE_3_COMBO_BOX_2; + let combo = self.view.require_control(combo_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + t if t.supports_fx() => { + if let Ok(track) = self.target_with_context().first_effective_track() { + let chain = if self.mapping.target_model.fx_is_input_fx() { + track.input_fx_chain() + } else { + track.normal_fx_chain() + }; + let i = combo.selected_combo_box_item_index(); + if let Some(fx) = chain.fx_by_index(i as _) { + self.change_target_with_closure(None, |ctx| { + ctx.mapping.target_model.set_concrete_fx( + ConcreteFxInstruction::ByIdWithFx(fx), + false, + true, + ) + }); + } + } + } + t if t.supports_seek_behavior() => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid seek behavior"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetSeekBehavior(v), + )); + } + ReaperTargetType::Action => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid action invocation type"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetActionInvocationType(v), + )); + } + ReaperTargetType::TrackSolo => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid solo behavior"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetSoloBehavior(v), + )); + } + ReaperTargetType::TrackShow => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid track area"); + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetTrackArea( + v, + ))); + } + ReaperTargetType::TrackAutomationMode + | ReaperTargetType::AutomationModeOverride + | ReaperTargetType::RouteAutomationMode => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid automation mode"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetAutomationMode(v), + )); + } + ReaperTargetType::TrackMonitoringMode => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid monitoring mode"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMonitoringMode(v), + )); + } + ReaperTargetType::TrackTouchState => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid touched track parameter type"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetTouchedTrackParameterType(v), + )); + } + ReaperTargetType::RouteTouchState => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid touched route parameter type"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetTouchedRouteParameterType(v), + )); + } + _ if self.mapping.target_model.supports_axis() => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid axis type"); + self.change_mapping(MappingCommand::ChangeTarget(TargetCommand::SetAxis(v))); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_4_combo_box_2_change(&mut self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_4_COMBO_BOX_2); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc => { + let v = get_osc_type_tag_from_combo(combo); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetOscArgTypeTag(v), + )); + } + ReaperTargetType::Mouse if self.mapping.target_model.supports_mouse_button() => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid mouse button type"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetMouseButton(v), + )); + } + t if t.supports_fx_parameter() => { + let fx = get_relevant_target_fx(self.mapping, self.session); + if let Some(fx) = fx { + let i = combo.selected_combo_box_item_index(); + let param = fx.parameter_by_index(i as _); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetParamIndex(i as _), + )); + // We also set name so that we can easily switch between types. + // Parameter names are not reliably UTF-8-encoded (e.g. "JS: Stereo Width") + let param_name = param + .name() + .unwrap() + .into_inner() + .to_string_lossy() + .to_string(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetParamName(param_name), + )); + if self.mapping.target_model.fx_type() == VirtualFxType::Focused { + self.panel.set_simple_help_text( + Side::Right, + "Target warning", + r#"ATTENTION: You just picked a parameter for the last focused FX. This is okay but you should know that as soon as you focus another type of FX, the parameter list will change and your mapping will control a completely different parameter! You probably want to use this in combination with the "Auto-load" feature, which lets you link FX types to mapping presets. See https://docs.helgoboss.org/realearn/goto#auto-load"#, + ); + } + } + } + t if t.supports_track_exclusivity() => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid track exclusivity"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetTrackExclusivity(v), + )); + } + t if t.supports_fx_display_type() => { + let i = combo.selected_combo_box_item_index(); + let v = i.try_into().expect("invalid FX display type"); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetFxDisplayType(v), + )); + } + t if t.supports_send() => { + if let Ok(track) = self.target_with_context().first_effective_track() { + let i = combo.selected_combo_box_item_index(); + let route_type = self.mapping.target_model.route_type(); + if let Ok(route) = resolve_track_route_by_index(&track, route_type, i as _) + { + if let Some(TrackRoutePartner::Track(t)) = route.partner() { + // Track send/receive. We use the partner track ID as stable ID! + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetRouteId(Some(*t.guid())), + )); + } + // We also set index and name. First because hardware output relies on + // the index as "ID", but also so we can easily switch between + // selector types. + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetRouteIndex(i as _), + )); + let route_name = route.name().into_string(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetRouteName(route_name), + )); + } + } + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_2_edit_control_change(&mut self) { + let edit_control_id = root::ID_TARGET_LINE_2_EDIT_CONTROL; + let control = self.view.require_control(edit_control_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::GoToBookmark => { + let human_value: u32 = control + .text() + .unwrap_or_default() + .parse() + .unwrap_or_default(); + let internal_value = human_value.saturating_sub(1); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetBookmarkRef(internal_value)), + Some(edit_control_id), + ); + } + _ if self.mapping.target_model.supports_mapping_snapshot_id() => { + let id = control.text().unwrap_or_default().parse().ok(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetMappingSnapshotId(id)), + Some(edit_control_id), + ); + } + _ if self.mapping.target_model.supports_track() => { + match self.mapping.target_model.track_type() { + t if t.is_dynamic() => { + let expression = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetTrackExpression( + expression, + )), + Some(edit_control_id), + ); + } + VirtualTrackType::ByName | VirtualTrackType::AllByName => { + let name = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetTrackName(name)), + Some(edit_control_id), + ); + } + t if t.is_by_index() => { + let index = parse_position_as_index(control); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetTrackIndex(index)), + Some(edit_control_id), + ); + } + _ => {} + } + } + _ => {} + }, + TargetCategory::Virtual => { + let text = control.text().unwrap_or_default(); + let v = text.parse().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetControlElementId(v)), + Some(edit_control_id), + ); + } + } + } + + fn handle_target_line_3_edit_control_change(&mut self) { + let edit_control_id = root::ID_TARGET_LINE_3_EDIT_CONTROL; + let control = self.view.require_control(edit_control_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc => { + let pattern = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetOscAddressPattern(pattern)), + Some(edit_control_id), + ); + } + ReaperTargetType::LoadMappingSnapshot => { + let text = control.text().unwrap_or_default(); + let value = parse_unit_value_from_percentage(&text) + .map(AbsoluteValue::Continuous) + .ok(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget( + TargetCommand::SetMappingSnapshotDefaultValue(value), + ), + Some(edit_control_id), + ); + } + ReaperTargetType::PlaytimeColumnAction => { + let text = control.text().unwrap_or_default(); + match self.mapping.target_model.playtime_column() { + PlaytimeColumnDescriptor::Active => {} + PlaytimeColumnDescriptor::ByIndex(_) => { + let position: usize = text.parse().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeColumn( + PlaytimeColumnDescriptor::ByIndex(ColumnAddress { + index: position.saturating_sub(1), + }), + )), + Some(edit_control_id), + ); + } + PlaytimeColumnDescriptor::Dynamic { .. } => { + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeColumn( + PlaytimeColumnDescriptor::Dynamic { expression: text }, + )), + Some(edit_control_id), + ); + } + } + } + ReaperTargetType::PlaytimeRowAction => { + let text = control.text().unwrap_or_default(); + match self.mapping.target_model.playtime_row() { + PlaytimeRowDescriptor::Active => {} + PlaytimeRowDescriptor::ByIndex(_) => { + let position: usize = text.parse().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeRow( + PlaytimeRowDescriptor::ByIndex(RowAddress { + index: position.saturating_sub(1), + }), + )), + Some(edit_control_id), + ); + } + PlaytimeRowDescriptor::Dynamic { .. } => { + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeRow( + PlaytimeRowDescriptor::Dynamic { expression: text }, + )), + Some(edit_control_id), + ); + } + } + } + ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction + | ReaperTargetType::PlaytimeSlotVolume + | ReaperTargetType::PlaytimeSlotSeek => { + let text = control.text().unwrap_or_default(); + match self.mapping.target_model.playtime_slot() { + PlaytimeSlotDescriptor::Active => {} + PlaytimeSlotDescriptor::ByIndex(address) => { + let position: usize = text.parse().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeSlot( + PlaytimeSlotDescriptor::ByIndex(SlotAddress::new( + position.saturating_sub(1), + address.row_index, + )), + )), + Some(edit_control_id), + ); + } + PlaytimeSlotDescriptor::Dynamic { row_expression, .. } => { + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeSlot( + PlaytimeSlotDescriptor::Dynamic { + column_expression: text, + row_expression: row_expression.clone(), + }, + )), + Some(edit_control_id), + ); + } + } + } + t if t.supports_fx() => match self.mapping.target_model.fx_type() { + VirtualFxType::Dynamic => { + let expression = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetFxExpression( + expression, + )), + Some(edit_control_id), + ); + } + VirtualFxType::ByName | VirtualFxType::AllByName => { + let name = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetFxName(name)), + Some(edit_control_id), + ); + } + VirtualFxType::ByIndex => { + let index = parse_position_as_index(control); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetFxIndex(index)), + Some(edit_control_id), + ); + } + _ => {} + }, + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_4_edit_control_change(&mut self) { + let edit_control_id = root::ID_TARGET_LINE_4_EDIT_CONTROL; + let control = self.view.require_control(edit_control_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + t if t.supports_fx_parameter() => match self.mapping.target_model.param_type() { + VirtualFxParameterType::Dynamic => { + let expression = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetParamExpression( + expression, + )), + Some(edit_control_id), + ); + } + VirtualFxParameterType::ByName => { + let name = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetParamName(name)), + Some(edit_control_id), + ); + } + VirtualFxParameterType::ByIndex => { + let index = parse_position_as_index(control); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetParamIndex(index)), + Some(edit_control_id), + ); + } + VirtualFxParameterType::ById => {} + }, + ReaperTargetType::SendMidi => { + let text = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetRawMidiPattern(text)), + Some(edit_control_id), + ); + } + ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction + | ReaperTargetType::PlaytimeSlotVolume + | ReaperTargetType::PlaytimeSlotSeek => { + let text = control.text().unwrap_or_default(); + match self.mapping.target_model.playtime_slot() { + PlaytimeSlotDescriptor::Active => {} + PlaytimeSlotDescriptor::ByIndex(address) => { + let position: usize = text.parse().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeSlot( + PlaytimeSlotDescriptor::ByIndex(SlotAddress::new( + address.column_index, + position.saturating_sub(1), + )), + )), + Some(edit_control_id), + ); + } + PlaytimeSlotDescriptor::Dynamic { + column_expression, .. + } => { + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetPlaytimeSlot( + PlaytimeSlotDescriptor::Dynamic { + column_expression: column_expression.clone(), + row_expression: text, + }, + )), + Some(edit_control_id), + ); + } + } + } + t if t.supports_send() => match self.mapping.target_model.route_selector_type() { + TrackRouteSelectorType::Dynamic => { + let expression = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetRouteExpression( + expression, + )), + Some(edit_control_id), + ); + } + TrackRouteSelectorType::ByName => { + let name = control.text().unwrap_or_default(); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetRouteName(name)), + Some(edit_control_id), + ); + } + TrackRouteSelectorType::ByIndex => { + let index = parse_position_as_index(control); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetRouteIndex(index)), + Some(edit_control_id), + ); + } + _ => {} + }, + t if t.supports_tags() => { + let text = control.text().unwrap_or_default(); + let v = parse_tags_from_csv(&text); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetTags(v)), + Some(edit_control_id), + ); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn handle_target_line_5_edit_control_change(&mut self) { + let edit_control_id = root::ID_TARGET_LINE_5_EDIT_CONTROL; + let control = self.view.require_control(edit_control_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc => { + let text = control.text().unwrap_or_default(); + let v = parse_osc_arg_value_range(&text); + self.change_mapping_with_initiator( + MappingCommand::ChangeTarget(TargetCommand::SetOscArgValueRange(v)), + Some(edit_control_id), + ); + } + _ => {} + }, + TargetCategory::Virtual => {} + } + } + + fn target_category(&self) -> TargetCategory { + self.mapping.target_model.category() + } + + fn reaper_target_type(&self) -> ReaperTargetType { + self.mapping.target_model.target_type() + } + + fn target_with_context(&'a self) -> TargetModelWithContext<'a> { + self.mapping + .target_model + .with_context(self.session.extended_context(), self.mapping.compartment()) + } +} + +const NO_HELP_ELEMENTS: &[u32] = &[ + 0, + root::ID_MODE_BUTTON_GROUP_BOX, + root::ID_MODE_RELATIVE_GROUP_BOX, + root::ID_MODE_KNOB_FADER_GROUP_BOX, +]; +const SOURCE_MIN_MAX_ELEMENTS: &[u32] = &[ + root::ID_SETTINGS_SOURCE_LABEL, + #[cfg(not(target_os = "macos"))] + root::ID_SETTINGS_SOURCE_GROUP, + root::ID_SETTINGS_SOURCE_MIN_LABEL, + root::ID_SETTINGS_SOURCE_MAX_LABEL, + root::ID_SETTINGS_MIN_SOURCE_VALUE_EDIT_CONTROL, + root::ID_SETTINGS_MIN_SOURCE_VALUE_SLIDER_CONTROL, + root::ID_SETTINGS_MAX_SOURCE_VALUE_EDIT_CONTROL, + root::ID_SETTINGS_MAX_SOURCE_VALUE_SLIDER_CONTROL, +]; +const OUT_OF_RANGE_ELEMENTS: &[u32] = &[ + root::ID_MODE_OUT_OF_RANGE_LABEL_TEXT, + root::ID_MODE_OUT_OF_RANGE_COMBOX_BOX, +]; +const GROUP_INTERACTION_ELEMENTS: &[u32] = &[ + root::ID_MODE_GROUP_INTERACTION_LABEL_TEXT, + root::ID_MODE_GROUP_INTERACTION_COMBO_BOX, +]; +const VALUE_SEQUENCE_ELEMENTS: &[u32] = &[ + root::ID_SETTINGS_TARGET_SEQUENCE_LABEL_TEXT, + root::ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL, +]; +const TARGET_MIN_MAX_ELEMENTS: &[u32] = &[ + root::ID_SETTINGS_TARGET_LABEL_TEXT, + #[cfg(not(target_os = "macos"))] + root::ID_SETTINGS_TARGET_GROUP, + root::ID_SETTINGS_MIN_TARGET_LABEL_TEXT, + root::ID_SETTINGS_MIN_TARGET_VALUE_SLIDER_CONTROL, + root::ID_SETTINGS_MIN_TARGET_VALUE_EDIT_CONTROL, + root::ID_SETTINGS_MIN_TARGET_VALUE_TEXT, + root::ID_SETTINGS_MAX_TARGET_LABEL_TEXT, + root::ID_SETTINGS_MAX_TARGET_VALUE_SLIDER_CONTROL, + root::ID_SETTINGS_MAX_TARGET_VALUE_EDIT_CONTROL, + root::ID_SETTINGS_MAX_TARGET_VALUE_TEXT, +]; +const FEEDBACK_TYPE_ELEMENTS: &[u32] = &[root::IDC_MODE_FEEDBACK_TYPE_COMBO_BOX]; +const ROUND_TARGET_VALUE_ELEMENTS: &[u32] = &[root::ID_SETTINGS_ROUND_TARGET_VALUE_CHECK_BOX]; +const TAKEOVER_MODE_ELEMENTS: &[u32] = &[root::ID_MODE_TAKEOVER_LABEL, root::ID_MODE_TAKEOVER_MODE]; +const CONTROL_TRANSFORMATION_ELEMENTS: &[u32] = &[ + root::ID_MODE_EEL_CONTROL_TRANSFORMATION_LABEL, + root::ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL, + root::ID_MODE_EEL_CONTROL_TRANSFORMATION_DETAIL_BUTTON, +]; +const ABSOLUTE_MODE_ELEMENTS: &[u32] = &[ + root::ID_SETTINGS_MODE_COMBO_BOX, + root::ID_SETTINGS_MODE_LABEL, +]; +const STEP_MIN_ELEMENTS: &[u32] = &[ + root::ID_SETTINGS_MIN_STEP_SIZE_LABEL_TEXT, + #[cfg(not(target_os = "macos"))] + root::ID_SETTINGS_STEP_SIZE_GROUP, + root::ID_SETTINGS_MIN_STEP_SIZE_SLIDER_CONTROL, + root::ID_SETTINGS_MIN_STEP_SIZE_EDIT_CONTROL, + root::ID_SETTINGS_MIN_STEP_SIZE_VALUE_TEXT, +]; +const STEP_MAX_ELEMENTS: &[u32] = &[ + root::ID_SETTINGS_MAX_STEP_SIZE_LABEL_TEXT, + root::ID_SETTINGS_MAX_STEP_SIZE_SLIDER_CONTROL, + root::ID_SETTINGS_MAX_STEP_SIZE_EDIT_CONTROL, + root::ID_SETTINGS_MAX_STEP_SIZE_VALUE_TEXT, +]; +const MAKE_ABSOLUTE_ELEMENTS: &[u32] = &[root::ID_SETTINGS_MAKE_ABSOLUTE_CHECK_BOX]; +const RELATIVE_FILTER_ELEMENTS: &[u32] = &[root::ID_MODE_RELATIVE_FILTER_COMBO_BOX]; +const FIRE_MODE_ELEMENTS: &[u32] = &[ + root::ID_MODE_FIRE_COMBO_BOX, + root::ID_MODE_FIRE_LINE_2_LABEL_1, + root::ID_MODE_FIRE_LINE_2_SLIDER_CONTROL, + root::ID_MODE_FIRE_LINE_2_EDIT_CONTROL, + root::ID_MODE_FIRE_LINE_2_LABEL_2, + root::ID_MODE_FIRE_LINE_3_LABEL_1, + root::ID_MODE_FIRE_LINE_3_SLIDER_CONTROL, + root::ID_MODE_FIRE_LINE_3_EDIT_CONTROL, + root::ID_MODE_FIRE_LINE_3_LABEL_2, +]; +const REVERSE_ELEMENTS: &[u32] = &[root::ID_SETTINGS_REVERSE_CHECK_BOX]; +const FEEDBACK_TRANSFORMATION_ELEMENTS: &[u32] = &[ + root::ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL, + root::IDC_MODE_FEEDBACK_TYPE_BUTTON, +]; +const ROTATE_ELEMENTS: &[u32] = &[root::ID_SETTINGS_ROTATE_CHECK_BOX]; +const BUTTON_FILTER_ELEMENTS: &[u32] = &[root::ID_MODE_BUTTON_FILTER_COMBO_BOX]; + +impl<'a> ImmutableMappingPanel<'a> { + // For hitting target with on/off or -/+ depending on target character. + fn hit_target_special(&self, state: bool) -> Result<(), &'static str> { + let target = self + .first_resolved_target() + .ok_or("couldn't resolve to target")?; + let target_is_relative = target + .control_type(self.session.control_context()) + .is_relative(); + let value = if target_is_relative { + let amount = if state { 1 } else { -1 }; + ControlValue::RelativeDiscrete(DiscreteIncrement::new(amount)) + } else { + let value = if state { + UnitValue::MAX + } else { + UnitValue::MIN + }; + ControlValue::AbsoluteContinuous(value) + }; + self.session.hit_target(self.mapping.qualified_id(), value); + Ok(()) + } + + fn hit_target(&self, value: UnitValue) { + self.session.hit_target( + self.mapping.qualified_id(), + ControlValue::AbsoluteContinuous(value), + ); + } + + fn fill_all_controls(&self) { + self.fill_mapping_feedback_send_behavior_combo_box(); + self.fill_source_category_combo_box(); + self.fill_mode_out_of_range_behavior_combo_box(); + self.fill_mode_group_interaction_combo_box(); + self.fill_mode_takeover_mode_combo_box(); + self.fill_mode_button_usage_combo_box(); + self.fill_mode_encoder_usage_combo_box(); + self.fill_mode_fire_mode_combo_box(); + self.fill_mode_feedback_type_combo_box(); + self.fill_target_category_combo_box(); + } + + fn invalidate_all_controls(&self) { + self.invalidate_window_title(); + self.panel.mapping_header_panel.invalidate_controls(); + self.invalidate_beep_on_success_checkbox(); + self.invalidate_mapping_enabled_check_box(); + self.invalidate_mapping_feedback_send_behavior_combo_box(); + self.invalidate_mapping_visible_in_projection_check_box(); + self.invalidate_mapping_advanced_settings_button(); + self.invalidate_source_controls(); + self.invalidate_target_controls(None); + self.invalidate_mode_controls(); + } + + fn invalidate_help(&self) { + if self.invalidate_help_internal().is_none() { + self.clear_help(); + } + } + + fn invalidate_help_internal(&self) -> Option<()> { + let topic = self.panel.active_help_topic.borrow().get()?; + let details = topic.get_details()?; + const HAS_NO_EFFECT: &str = "has no effect"; + let left_label = self + .view + .require_control(root::ID_MAPPING_HELP_LEFT_SUBJECT_LABEL); + let left_content = self + .view + .require_control(root::ID_MAPPING_HELP_LEFT_CONTENT_LABEL); + let right_label = self + .view + .require_control(root::ID_MAPPING_HELP_RIGHT_SUBJECT_LABEL); + let right_content = self + .view + .require_control(root::ID_MAPPING_HELP_RIGHT_CONTENT_LABEL); + match details.description { + HelpTopicDescription::General(desc) => { + left_label.set_text(format!("{topic}:")); + left_content.set_multi_line_text(format!("This {desc}.")); + right_label.set_text(""); + right_content.set_multi_line_text(""); + } + HelpTopicDescription::DirectionDependent { + control_desc, + feedback_desc, + } => { + left_label.set_text(format!("In control direction, \"{topic}\" ...")); + left_content + .set_multi_line_text(format!("... {}.", control_desc.unwrap_or(HAS_NO_EFFECT))); + right_label.set_text("In feedback direction, ... (Press F1 to learn more)"); + right_content.set_multi_line_text(format!( + "... it {}.", + feedback_desc.unwrap_or(HAS_NO_EFFECT) + )); + } + } + Some(()) + } + + fn clear_help(&self) { + self.view + .require_control(root::ID_MAPPING_HELP_LEFT_SUBJECT_LABEL) + .set_text(""); + for id in [ + root::ID_MAPPING_HELP_RIGHT_SUBJECT_LABEL, + root::ID_MAPPING_HELP_LEFT_CONTENT_LABEL, + root::ID_MAPPING_HELP_RIGHT_CONTENT_LABEL, + ] { + self.view.require_control(id).set_text(""); + } + } + + fn invalidate_window_title(&self) { + let mapping_is_on = self + .session + .unit() + .borrow() + .mapping_is_on(self.mapping.qualified_id()); + let suffix = if mapping_is_on { "" } else { " (inactive)" }; + let text = format!("Mapping \"{}\"{}", self.mapping.effective_name(), suffix); + self.view.require_window().set_text(text); + } + + fn invalidate_mapping_feedback_send_behavior_combo_box(&self) { + let combo = self + .view + .require_control(root::ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX); + combo.select_combo_box_item_by_index(self.mapping.feedback_send_behavior().into()); + } + + fn invalidate_beep_on_success_checkbox(&self) { + self.view + .require_control(root::IDC_BEEP_ON_SUCCESS_CHECK_BOX) + .set_checked(self.mapping.beep_on_success()); + } + + fn invalidate_mapping_enabled_check_box(&self) { + self.view + .require_control(root::IDC_MAPPING_ENABLED_CHECK_BOX) + .set_checked(self.mapping.is_enabled()); + } + + fn invalidate_mapping_visible_in_projection_check_box(&self) { + let cb = self + .view + .require_control(root::ID_MAPPING_SHOW_IN_PROJECTION_CHECK_BOX); + cb.set_checked(self.mapping.visible_in_projection()); + } + + fn invalidate_mapping_advanced_settings_button(&self) { + let cb = self.view.require_control(root::ID_MAPPING_ADVANCED_BUTTON); + let suffix = if let Some(m) = self.mapping.advanced_settings() { + format!(" ({})", m.len()) + } else { + "".to_owned() + }; + let text = format!("Advanced settings {suffix}"); + cb.set_text(text); + } + + fn invalidate_source_controls(&self) { + self.invalidate_source_control_visibilities(); + self.invalidate_source_learn_button(); + self.invalidate_source_category_combo_box(); + self.invalidate_source_line_2(); + self.invalidate_source_line_3(None); + self.invalidate_source_line_4(None); + self.invalidate_source_line_5(None); + self.invalidate_source_check_box_2(); + self.invalidate_source_line_7(None); + } + + fn invalidate_source_control_visibilities(&self) { + let source = self.source; + // Show/hide stuff + self.enable_if( + source.supports_type(), + &[ + root::ID_SOURCE_TYPE_LABEL_TEXT, + root::ID_SOURCE_TYPE_COMBO_BOX, + ], + ); + } + + fn enable_if(&self, condition: bool, control_resource_ids: &[u32]) { + for id in control_resource_ids { + if let Some(control) = self.view.require_window().find_control(*id) { + self.panel + .ui_element_container + .borrow_mut() + .set_visible(*id, condition); + control.set_visible(condition); + } + } + } + + fn invalidate_source_category_combo_box(&self) { + self.view + .require_control(root::ID_SOURCE_CATEGORY_COMBO_BOX) + .select_combo_box_item_by_index(self.source.category().into()); + } + + fn invalidate_target_category_combo_box(&self) { + // Don't allow main mappings to have virtual target + self.view + .require_control(root::ID_TARGET_CATEGORY_COMBO_BOX) + .set_visible(self.mapping.compartment() != CompartmentKind::Main); + self.view + .require_control(root::ID_TARGET_CATEGORY_COMBO_BOX) + .select_combo_box_item_by_index(self.target.category().into()); + } + + fn invalidate_source_line_2(&self) { + self.invalidate_source_line_2_combo_box(); + } + + fn invalidate_source_line_2_combo_box(&self) { + self.fill_source_type_combo_box(); + self.invalidate_source_type_combo_box_value(); + } + + fn invalidate_source_type_combo_box_value(&self) { + use SourceCategory::*; + let item_index = match self.source.category() { + Midi => self.source.midi_source_type().into(), + Reaper => self.source.reaper_source_type().into(), + Virtual => self.source.control_element_character().into(), + _ => return, + }; + let b = self.view.require_control(root::ID_SOURCE_TYPE_COMBO_BOX); + b.select_combo_box_item_by_index(item_index); + } + + fn invalidate_source_learn_button(&self) { + self.invalidate_learn_button( + self.session + .unit() + .borrow() + .mapping_is_learning_source(self.mapping.qualified_id()), + root::ID_SOURCE_LEARN_BUTTON, + ); + } + + fn invalidate_learn_button(&self, is_learning: bool, control_resource_id: u32) { + let text = if is_learning { + "Stop learning" + } else { + "Learn" + }; + self.view + .require_control(control_resource_id) + .set_text(text); + } + + fn invalidate_source_line_3(&self, initiator: Option) { + self.invalidate_source_line_3_label_1(); + self.invalidate_source_line_3_label_2(); + self.invalidate_source_line_3_combo_box(); + self.invalidate_source_line_3_edit_control(initiator); + } + + fn invalidate_source_line_3_label_1(&self) { + use SourceCategory::*; + let text = match self.source.category() { + Midi => match self.source.midi_source_type() { + MidiSourceType::Script => Some("Kind"), + t if t.supports_channel() => Some("Channel"), + _ => None, + }, + Osc => Some("Address"), + Reaper => match self.source.reaper_source_type() { + ReaperSourceType::Timer => Some("Millis"), + ReaperSourceType::RealearnParameter => Some("Param"), + _ => None, + }, + Keyboard => Some("Key"), + StreamDeck => Some("Button"), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_CHANNEL_LABEL) + .set_text_or_hide(text); + } + + fn invalidate_source_line_3_label_2(&self) { + use SourceCategory::*; + let text = match self.source.category() { + Midi => match self.source.midi_source_type() { + MidiSourceType::ClockTransport => Some("Message"), + MidiSourceType::Display => Some("Protocol"), + _ => None, + }, + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_MIDI_MESSAGE_TYPE_LABEL_TEXT) + .set_text_or_hide(text); + } + + fn invalidate_source_line_3_combo_box(&self) { + let b = self.view.require_control(root::ID_SOURCE_CHANNEL_COMBO_BOX); + use SourceCategory::*; + match self.source.category() { + Midi => match self.source.midi_source_type() { + MidiSourceType::ClockTransport => { + b.show(); + b.fill_combo_box_indexed(MidiClockTransportMessage::iter()); + b.select_combo_box_item_by_index( + self.source.midi_clock_transport_message().into(), + ); + } + MidiSourceType::Display => { + b.show(); + b.fill_combo_box_indexed(DisplayType::iter()); + b.select_combo_box_item_by_index(self.source.display_type().into()); + } + MidiSourceType::Script => { + b.fill_combo_box_indexed(MidiScriptKind::iter()); + b.show(); + b.select_combo_box_item_by_index(self.source.midi_script_kind().into()); + } + t if t.supports_channel() => { + b.fill_combo_box_with_data_small( + iter::once((-1isize, " (no feedback)".to_string())) + .chain((0..16).map(|i| (i as isize, (i + 1).to_string()))), + ); + b.show(); + match self.source.channel() { + None => { + b.select_combo_box_item_by_data(-1).unwrap(); + } + Some(ch) => { + b.select_combo_box_item_by_data(ch.get() as _).unwrap(); + } + }; + } + _ => b.hide(), + }, + Reaper => match self.source.reaper_source_type() { + ReaperSourceType::RealearnParameter => { + let contents = compartment_parameter_dropdown_contents( + self.session, + self.mapping.compartment(), + ); + b.fill_combo_box_with_data_small(contents); + b.show(); + b.select_combo_box_item_by_index(self.source.parameter_index().get() as usize); + } + _ => b.hide(), + }, + StreamDeck => { + b.fill_combo_box_indexed((0..32).map(|i| (i + 1).to_string())); + b.select_combo_box_item_by_index(self.source.button_index() as _); + b.show(); + } + _ => { + b.hide(); + } + }; + } + + fn invalidate_source_check_box_2(&self) { + use SourceCategory::*; + let state = match self.source.category() { + Midi => { + match self.source.midi_source_type() { + t if t.supports_14_bit() => { + Some(( + "14-bit values", + self.source + .is_14_bit() + // 14-bit == None not yet supported + .unwrap_or(false), + )) + } + _ => None, + } + } + Osc => Some(("Is relative", self.source.osc_arg_is_relative())), + _ => None, + }; + self.invalidate_check_box(root::ID_SOURCE_14_BIT_CHECK_BOX, state); + } + + fn invalidate_source_line_4_check_box(&self) { + use SourceCategory::*; + let state = match self.source.category() { + Midi => { + match self.source.midi_source_type() { + t if t.supports_is_registered() => Some(( + "RPN", + self.source + .is_registered() + // registered == None not yet supported + .unwrap_or(false), + )), + _ => None, + } + } + _ => None, + }; + self.invalidate_check_box(root::ID_SOURCE_RPN_CHECK_BOX, state); + } + + fn invalidate_source_line_4(&self, initiator: Option) { + self.invalidate_source_line_4_label(); + self.invalidate_source_line_4_button(); + self.invalidate_source_line_4_check_box(); + self.invalidate_source_line_4_combo_box_1(); + self.invalidate_source_line_4_combo_box_2(); + self.invalidate_source_line_4_edit_control(initiator); + } + + fn invalidate_source_line_4_button(&self) { + use SourceCategory::*; + let text = match self.source.category() { + StreamDeck => Some(self.source.button_background_type().to_string()), + Virtual => Some("Pick!".to_string()), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_LINE_4_BUTTON) + .set_text_or_hide(text); + } + + fn invalidate_source_line_4_label(&self) { + use SourceCategory::*; + let text = match self.source.category() { + Midi => { + use DisplayType::*; + use MidiSourceType::*; + match self.source.midi_source_type() { + Display => { + if matches!(self.source.display_type(), LaunchpadProScrollingText) { + None + } else { + Some("Display") + } + } + t if t.supports_midi_message_number() + || t.supports_parameter_number_message_number() => + { + Some(t.number_label()) + } + _ => None, + } + } + Virtual => Some("ID"), + Osc => Some("Argument"), + StreamDeck => Some("Back"), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_NOTE_OR_CC_NUMBER_LABEL_TEXT) + .set_text_or_hide(text); + } + + fn invalidate_source_line_4_combo_box_1(&self) { + let b = self + .view + .require_control(root::ID_SOURCE_LINE_4_COMBO_BOX_1); + use SourceCategory::*; + match self.source.category() { + Osc => { + invalidate_with_osc_arg_index(b, self.source.osc_arg_index()); + } + _ => { + b.hide(); + } + }; + } + + fn invalidate_source_line_4_combo_box_2(&self) { + let b = self.view.require_control(root::ID_SOURCE_NUMBER_COMBO_BOX); + use SourceCategory::*; + match self.source.category() { + Midi => { + use MidiSourceType::*; + match self.source.midi_source_type() { + Display => { + use DisplayType::*; + match self.source.display_type() { + MackieLcd | MackieXtLcd | XTouchMackieLcd | XTouchMackieXtLcd + | SiniConE24 | SlKeyboardDisplay => { + b.show(); + let display_count = self.source.display_count() as isize; + b.fill_combo_box_with_data_vec( + iter::once((-1isize, "".to_string())) + .chain((0..display_count).map(|i| (i, (i + 1).to_string()))) + .collect(), + ); + let data = match self.source.mackie_lcd_scope().channel { + None => -1, + Some(n) => n as _, + }; + b.select_combo_box_item_by_data(data).unwrap(); + } + MackieSevenSegmentDisplay => { + b.show(); + b.fill_combo_box_indexed(MackieSevenSegmentDisplayScope::iter()); + b.select_combo_box_item_by_index( + self.source.mackie_7_segment_display_scope().into(), + ); + } + LaunchpadProScrollingText => { + b.hide(); + } + } + } + t if t.supports_midi_message_number() => { + b.fill_combo_box_with_data_vec( + iter::once((-1isize, " (no feedback)".to_string())) + .chain((0..128).map(|i| (i as isize, i.to_string()))) + .collect(), + ); + b.show(); + let data = match self.source.midi_message_number() { + None => -1, + Some(n) => n.get() as _, + }; + b.select_combo_box_item_by_data(data).unwrap(); + } + _ => { + b.hide(); + } + } + } + Osc if self.source.osc_arg_index().is_some() => { + let tag = self.source.osc_arg_type_tag(); + invalidate_with_osc_arg_type_tag(b, tag); + } + _ => { + b.hide(); + } + }; + } + + fn invalidate_source_line_4_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_SOURCE_NUMBER_EDIT_CONTROL) { + return; + } + use SourceCategory::*; + let text = match self.source.category() { + Midi => match self.source.midi_source_type() { + t if t.supports_parameter_number_message_number() => { + match self.source.parameter_number_message_number() { + None => Some("".to_owned()), + Some(n) => Some(n.to_string()), + } + } + _ => None, + }, + StreamDeck if self.source.button_background_type().wants_image() => { + Some(self.source.button_background_image_path().to_string()) + } + Virtual => Some(self.source.control_element_id().to_string()), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_NUMBER_EDIT_CONTROL) + .set_text_or_hide(text) + } + + fn invalidate_source_line_7(&self, initiator: Option) { + self.invalidate_source_line_7_label(); + self.invalidate_source_line_7_edit_control(initiator); + self.invalidate_source_line_7_button(); + } + + fn invalidate_source_line_7_button(&self) { + use SourceCategory::*; + let text = match self.source.category() { + Midi if matches!( + self.source.midi_source_type(), + MidiSourceType::Raw | MidiSourceType::Script + ) => + { + Some("...") + } + Osc | StreamDeck => Some("..."), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_SCRIPT_DETAIL_BUTTON) + .set_text_or_hide(text); + } + + fn invalidate_source_line_7_label(&self) { + use SourceCategory::*; + let text = match self.source.category() { + Midi => match self.source.midi_source_type() { + MidiSourceType::Raw => Some("Pattern"), + MidiSourceType::Script => Some("Script"), + _ => None, + }, + Osc => Some("Feedback arguments"), + StreamDeck => Some("Default text"), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_OSC_ADDRESS_LABEL_TEXT) + .set_text_or_hide(text); + } + + fn invalidate_source_line_3_edit_control(&self, initiator: Option) { + let control_id = root::ID_SOURCE_LINE_3_EDIT_CONTROL; + if initiator == Some(control_id) { + return; + } + let c = self.view.require_control(control_id); + use SourceCategory::*; + let content = match self.source.category() { + Osc => Some((self.source.osc_address_pattern().to_owned(), true)), + Reaper => match self.source.reaper_source_type() { + ReaperSourceType::Timer => Some((self.source.timer_millis().to_string(), true)), + _ => None, + }, + Keyboard => { + let text = self + .source + .create_key_source() + .map(|s| { + let comment = match s.stroke().portability() { + Some(KeyStrokePortability::Portable) => "", + Some(KeyStrokePortability::NonPortable(issue)) => match issue { + PortabilityIssue::OperatingSystemRelated => { + " (not cross-platform!)" + } + PortabilityIssue::KeyboardLayoutRelated => { + " (not portable, special character!)" + } + PortabilityIssue::Other => " (not portable!)", + PortabilityIssue::NotNormalized => " (not normalized!)", + }, + None => " (probably not portable)", + }; + format!("{}{}", s.stroke(), comment) + }) + .unwrap_or_else(|| KEY_UNDEFINED_LABEL.to_string()); + Some((text, false)) + } + _ => None, + }; + if let Some((value_text, enabled)) = content { + c.show(); + c.set_enabled(enabled); + c.set_text(value_text); + } else { + c.hide(); + } + } + + fn invalidate_source_line_7_edit_control(&self, initiator: Option) { + let control_id = root::ID_SOURCE_OSC_ADDRESS_PATTERN_EDIT_CONTROL; + if initiator == Some(control_id) { + return; + } + let c = self.view.require_control(control_id); + use SourceCategory::*; + let (value_text, read_only) = match self.source.category() { + Midi => match self.source.midi_source_type() { + MidiSourceType::Raw => { + let text = self.source.raw_midi_pattern(); + (Some(text.to_owned()), text.chars().count() > 30) + } + MidiSourceType::Script => { + let text = self.source.midi_script(); + ( + Some(extract_first_line(text).to_owned()), + has_multiple_lines(text), + ) + } + _ => (None, false), + }, + Osc => { + let text = format_osc_feedback_args(self.source.osc_feedback_args()); + (Some(text), false) + } + StreamDeck => { + let text = self.source.button_static_text(); + (Some(text.to_string()), has_multiple_lines(text)) + } + _ => (None, false), + }; + c.set_text_or_hide(value_text); + c.set_enabled(!read_only); + } + + fn invalidate_source_line_5(&self, initiator: Option) { + self.invalidate_source_line_5_label(); + self.invalidate_source_line_5_button(); + self.invalidate_source_line_5_combo_box(); + self.invalidate_source_line_5_edit_control(initiator); + } + + fn invalidate_source_line_5_label(&self) { + use SourceCategory::*; + let text = match self.source.category() { + Midi => { + use MidiSourceType::*; + match self.source.midi_source_type() { + Display => { + if self.source.display_type().line_count() > 1 { + Some("Line") + } else { + None + } + } + t if t.supports_custom_character() => Some("Character"), + _ => None, + } + } + Osc if self.source.supports_osc_arg_value_range() => Some("Range"), + StreamDeck => Some("Front"), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_CHARACTER_LABEL_TEXT) + .set_text_or_hide(text); + } + + fn invalidate_source_line_5_button(&self) { + use SourceCategory::*; + let text = match self.source.category() { + StreamDeck => Some(self.source.button_foreground_type().to_string()), + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_LINE_5_BUTTON) + .set_text_or_hide(text); + } + + fn invalidate_source_line_5_combo_box(&self) { + let b = self + .view + .require_control(root::ID_SOURCE_CHARACTER_COMBO_BOX); + use SourceCategory::*; + match self.source.category() { + Midi => { + use MidiSourceType::*; + match self.source.midi_source_type() { + Display => { + b.show(); + let line_count = self.source.display_type().line_count(); + if line_count > 1 { + b.fill_combo_box_with_data_vec( + iter::once((-1isize, "".to_string())) + .chain( + (0..line_count).map(|i| (i as isize, (i + 1).to_string())), + ) + .collect(), + ); + let data = match self.source.line() { + None => -1, + Some(n) => n.min(line_count) as _, + }; + b.select_combo_box_item_by_data(data).unwrap(); + } else { + b.hide(); + } + } + t if t.supports_custom_character() => { + b.show(); + b.fill_combo_box_indexed(SourceCharacter::iter()); + b.select_combo_box_item_by_index(self.source.custom_character().into()); + } + _ => { + b.hide(); + } + } + } + _ => { + b.hide(); + } + }; + } + + fn invalidate_source_line_5_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_SOURCE_LINE_5_EDIT_CONTROL) { + return; + } + use SourceCategory::*; + let text = match self.source.category() { + Osc if self.source.supports_osc_arg_value_range() => { + let text = format_osc_arg_value_range( + self.source.osc_arg_value_range(), + self.source.osc_arg_type_tag(), + ); + Some(text) + } + StreamDeck if self.source.button_foreground_type().wants_image() => { + Some(self.source.button_foreground_image_path().to_string()) + } + _ => None, + }; + self.view + .require_control(root::ID_SOURCE_LINE_5_EDIT_CONTROL) + .set_text_or_hide(text); + } + + fn invalidate_target_controls(&self, initiator: Option) { + self.invalidate_target_category_combo_box(); + self.invalidate_target_type_button(); + self.invalidate_target_line_2(initiator); + self.invalidate_target_line_3(initiator); + self.invalidate_target_line_4(initiator); + self.invalidate_target_line_5(initiator); + self.invalidate_target_value_controls(); + self.invalidate_target_learn_button(); + self.invalidate_target_check_boxes(); + } + + fn invalidate_target_check_boxes(&self) { + self.invalidate_target_check_box_1(); + self.invalidate_target_check_box_2(); + self.invalidate_target_check_box_3(); + self.invalidate_target_check_box_4(); + self.invalidate_target_check_box_5(); + self.invalidate_target_check_box_6(); + } + + fn invalidate_target_type_button(&self) { + use TargetCategory::*; + let (label, hint) = match self.target.category() { + Reaper => { + let label = self.target.target_type().to_string(); + let hint = match self.target.target_type().definition() { + d if d.lua_only => "Use Lua to configure this target!", + d if d.supports_real_time_control => "Supports MIDI real-time control!", + d => d.hint, + }; + (label, hint.to_string()) + } + Virtual => { + let label = self.target.control_element_character().to_string(); + (label, "".to_owned()) + } + }; + self.view + .require_control(root::ID_TARGET_TYPE_BUTTON) + .set_text(label); + self.view + .require_control(root::ID_TARGET_HINT) + .set_text(hint); + } + + fn target_category(&self) -> TargetCategory { + self.target.category() + } + + fn reaper_target_type(&self) -> ReaperTargetType { + self.target.target_type() + } + + fn invalidate_target_line_2_label_1(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::LastTouched => Some("Targets"), + ReaperTargetType::BrowsePotFilterItems => Some("Kind"), + ReaperTargetType::CompartmentParameterValue => Some("Parameter"), + ReaperTargetType::Mouse => Some("Action"), + ReaperTargetType::Transport => Some("Action"), + ReaperTargetType::AnyOn => Some("Parameter"), + ReaperTargetType::AutomationModeOverride => Some("Behavior"), + ReaperTargetType::GoToBookmark => match self.target.bookmark_type() { + BookmarkType::Marker => Some("Marker"), + BookmarkType::Region => Some("Region"), + }, + ReaperTargetType::SendMidi => Some("Output"), + ReaperTargetType::SendOsc => Some("Output"), + ReaperTargetType::LoadMappingSnapshot => Some("Snapshot"), + ReaperTargetType::TakeMappingSnapshot => Some("Snapshot ID"), + ReaperTargetType::BrowseGroup => Some("Group"), + ReaperTargetType::BrowseTracks => Some("Scope"), + ReaperTargetType::ModifyMapping => Some("Kind"), + ReaperTargetType::PlaytimeMatrixAction + | ReaperTargetType::PlaytimeRowAction + | ReaperTargetType::PlaytimeColumnAction + | ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction => Some("Action"), + t if t.supports_feedback_resolution() => Some("Feedback"), + _ if self.target.supports_track() => Some("Track"), + _ => None, + }, + TargetCategory::Virtual => Some("ID"), + }; + self.view + .require_control(root::ID_TARGET_LINE_2_LABEL_1) + .set_text_or_hide(text); + } + + fn invalidate_target_line_2_label_2(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::LastTouched => { + let enabled_count = self.target.included_targets().len(); + let total_count = LearnableTargetKind::iter().count(); + Some(format!("{enabled_count} of {total_count} targets enabled")) + } + ReaperTargetType::CompartmentParameterValue => { + let param_index = self.target.compartment_param_index(); + let unit = self.session.unit().borrow(); + let params = unit.parameter_manager().params(); + let compartment_params = params.compartment_params(self.mapping.compartment()); + Some(get_param_name(compartment_params, param_index)) + } + _ => None, + }, + _ => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_2_LABEL_2) + .set_text_or_hide(text); + } + + fn invalidate_target_line_2_label_3(&self) { + invalidate_target_line_2_expression_result( + self.target, + self.session.extended_context(), + self.view.require_control(root::ID_TARGET_LINE_2_LABEL_3), + self.mapping.compartment(), + ); + } + + fn invalidate_target_line_2_combo_box_1(&self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_2_COMBO_BOX_1); + match self.target_category() { + TargetCategory::Reaper => match self.target.target_type() { + _ if self.target.supports_track() => { + combo.show(); + combo.fill_combo_box_indexed(VirtualTrackType::iter()); + combo.select_combo_box_item_by_index(self.target.track_type().into()); + } + ReaperTargetType::GoToBookmark => { + combo.show(); + combo.fill_combo_box_indexed(BookmarkAnchorType::iter()); + combo.select_combo_box_item_by_index(self.target.bookmark_anchor_type().into()); + } + ReaperTargetType::LoadMappingSnapshot => { + combo.show(); + combo.fill_combo_box_indexed(MappingSnapshotTypeForLoad::iter()); + combo.select_combo_box_item_by_index( + self.mapping + .target_model + .mapping_snapshot_type_for_load() + .into(), + ); + } + ReaperTargetType::TakeMappingSnapshot => { + combo.show(); + combo.fill_combo_box_indexed(MappingSnapshotTypeForTake::iter()); + combo.select_combo_box_item_by_index( + self.mapping + .target_model + .mapping_snapshot_type_for_take() + .into(), + ); + } + t if t.supports_feedback_resolution() => { + combo.show(); + combo.fill_combo_box_indexed(FeedbackResolution::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.feedback_resolution().into(), + ); + } + _ => { + combo.hide(); + } + }, + TargetCategory::Virtual => { + combo.hide(); + } + } + } + + fn invalidate_target_line_2_combo_box_2(&self, initiator: Option) { + let combo_id = root::ID_TARGET_LINE_2_COMBO_BOX_2; + if initiator == Some(combo_id) { + return; + } + let combo = self.view.require_control(combo_id); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Mouse => { + combo.show(); + combo.fill_combo_box_indexed(MouseActionType::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.mouse_action_type().into(), + ); + } + ReaperTargetType::Transport => { + combo.show(); + combo.fill_combo_box_indexed(TransportAction::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.transport_action().into(), + ); + } + ReaperTargetType::AnyOn => { + combo.show(); + combo.fill_combo_box_indexed(AnyOnParameter::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.any_on_parameter().into(), + ); + } + ReaperTargetType::AutomationModeOverride => { + combo.show(); + combo.fill_combo_box_indexed(AutomationModeOverrideType::iter()); + combo.select_combo_box_item_by_index( + self.mapping + .target_model + .automation_mode_override_type() + .into(), + ); + } + ReaperTargetType::GoToBookmark + if self.target.bookmark_anchor_type() == BookmarkAnchorType::Id => + { + combo.show(); + let project = self.target_with_context().project(); + let bookmark_type = self.target.bookmark_type(); + let bookmarks = bookmark_combo_box_entries(project, bookmark_type); + combo.fill_combo_box_with_data_vec(bookmarks.collect()); + select_bookmark_in_combo_box( + combo, + self.target.bookmark_anchor_type(), + self.target.bookmark_ref(), + ); + } + ReaperTargetType::BrowseGroup => { + combo.show(); + let compartment = self.mapping.compartment(); + // Fill box + combo.fill_combo_box_with_data_small( + self.session + .groups_sorted(compartment) + .enumerate() + .map(|(i, g)| (i as isize, g.borrow().to_string())), + ); + // Select value + let group_id = self.target.group_id(); + match self + .session + .find_group_index_by_id_sorted(compartment, group_id) + { + None => { + combo.select_new_combo_box_item(format!(" ({group_id})")); + } + Some(i) => { + combo.select_combo_box_item_by_data(i as isize).unwrap(); + } + } + } + ReaperTargetType::SendMidi => { + combo.show(); + combo.fill_combo_box_indexed(SendMidiDestinationType::iter()); + combo.select_combo_box_item_by_index( + self.mapping + .target_model + .send_midi_destination_type() + .into(), + ); + } + ReaperTargetType::SendOsc => { + combo.show(); + let osc_device_manager = BackboneShell::get().osc_device_manager(); + let osc_device_manager = osc_device_manager.borrow(); + let osc_devices = osc_device_manager.devices(); + combo.fill_combo_box_with_data_small( + std::iter::once((-1isize, "".to_string())).chain( + osc_devices + .enumerate() + .map(|(i, dev)| (i as isize, dev.get_list_label(true))), + ), + ); + if let Some(dev_id) = self.mapping.target_model.osc_dev_id() { + match osc_device_manager.find_index_by_id(&dev_id) { + None => { + combo + .select_new_combo_box_item(format!(" ({dev_id})")); + } + Some(i) => combo.select_combo_box_item_by_data(i as isize).unwrap(), + } + } else { + combo.select_combo_box_item_by_data(-1).unwrap(); + }; + } + ReaperTargetType::BrowseTracks => { + combo.show(); + combo.fill_combo_box_indexed(BrowseTracksMode::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.browse_tracks_mode().into(), + ); + } + ReaperTargetType::BrowsePotFilterItems => { + combo.show(); + combo.fill_combo_box_indexed(PotFilterKind::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.pot_filter_item_kind().into(), + ); + } + ReaperTargetType::ModifyMapping => { + combo.show(); + combo.fill_combo_box_indexed(MappingModificationKind::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.mapping_modification_kind().into(), + ); + } + ReaperTargetType::PlaytimeMatrixAction => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeMatrixAction::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.playtime_matrix_action().into(), + ); + } + ReaperTargetType::PlaytimeColumnAction => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeColumnAction::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.playtime_column_action().into(), + ); + } + ReaperTargetType::PlaytimeRowAction => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeRowAction::iter()); + combo.select_combo_box_item_by_index( + self.mapping.target_model.playtime_row_action().into(), + ); + } + ReaperTargetType::PlaytimeSlotManagementAction => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeSlotManagementAction::iter()); + combo.select_combo_box_item_by_index( + self.mapping + .target_model + .playtime_slot_management_action() + .into(), + ); + } + ReaperTargetType::PlaytimeSlotTransportAction => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeSlotTransportAction::iter()); + combo.select_combo_box_item_by_index( + self.mapping + .target_model + .playtime_slot_transport_action() + .into(), + ); + } + _ if self.target.supports_track() => { + if matches!( + self.target.track_type(), + VirtualTrackType::ById | VirtualTrackType::ByIdOrName + ) { + combo.show(); + let context = self.session.extended_context(); + let project = context.context().project_or_current_project(); + // Fill + combo.fill_combo_box_indexed(track_combo_box_entries(project)); + // Set + if let Some(virtual_track) = self.target.virtual_track() { + if let Some(resolved_track) = virtual_track + .resolve(context, self.mapping.compartment()) + .ok() + .and_then(|tracks| tracks.into_iter().next()) + { + let i = resolved_track.index().unwrap(); + combo.select_combo_box_item_by_index(i as _); + } else { + combo.select_new_combo_box_item( + get_non_present_virtual_track_label(&virtual_track), + ); + } + } else { + combo.select_new_combo_box_item(""); + } + } else { + combo.hide(); + } + } + _ => { + combo.hide(); + } + }, + TargetCategory::Virtual => { + combo.hide(); + } + } + } + + fn invalidate_target_line_2_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_TARGET_LINE_2_EDIT_CONTROL) { + return; + } + let control = self + .view + .require_control(root::ID_TARGET_LINE_2_EDIT_CONTROL); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + _ if self.target.supports_track() => { + control.show(); + let text = match self.target.track_type() { + t if t.is_dynamic() => self.target.track_expression().to_owned(), + t if t.is_by_index() => { + let index = self.target.track_index(); + (index + 1).to_string() + } + VirtualTrackType::ByName | VirtualTrackType::AllByName => { + self.target.track_name().to_owned() + } + _ => { + control.hide(); + return; + } + }; + control.set_text(text); + } + ReaperTargetType::GoToBookmark + if self.target.bookmark_anchor_type() == BookmarkAnchorType::Index => + { + control.show(); + let text = (self.target.bookmark_ref() + 1).to_string(); + control.set_text(text); + } + _ if self.mapping.target_model.supports_mapping_snapshot_id() => { + control.show(); + let text = self + .target + .mapping_snapshot_id() + .map(|id| id.to_string()) + .unwrap_or_default(); + control.set_text(text); + } + _ => { + control.hide(); + } + }, + TargetCategory::Virtual => { + let text = self.target.control_element_id().to_string(); + control.set_text(text); + control.show(); + } + } + } + + fn invalidate_target_line_2(&self, initiator: Option) { + self.invalidate_target_line_2_label_1(); + self.invalidate_target_line_2_label_2(); + self.invalidate_target_line_2_label_3(); + self.invalidate_target_line_2_combo_box_1(); + self.invalidate_target_line_2_combo_box_2(initiator); + self.invalidate_target_line_2_edit_control(initiator); + self.invalidate_target_line_2_button(); + } + + fn invalidate_target_line_2_button(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::GoToBookmark => Some("Now!"), + ReaperTargetType::LastTouched => Some("Pick!"), + ReaperTargetType::CompartmentParameterValue => Some("Pick!"), + ReaperTargetType::ModifyMapping => match self.target.mapping_modification_kind() { + MappingModificationKind::LearnTarget + | MappingModificationKind::SetTargetToLastTouched => Some("..."), + }, + _ => None, + }, + TargetCategory::Virtual => Some("Pick!"), + }; + self.view + .require_control(root::ID_TARGET_LINE_2_BUTTON) + .set_text_or_hide(text); + } + + fn target_with_context(&'a self) -> TargetModelWithContext<'a> { + self.mapping + .target_model + .with_context(self.session.extended_context(), self.mapping.compartment()) + } + + fn invalidate_target_line_3(&self, initiator: Option) { + self.invalidate_target_line_3_label_1(); + self.invalidate_target_line_3_label_2(); + self.invalidate_target_line_3_label_3(); + self.invalidate_target_line_3_combo_box_1(); + self.invalidate_target_line_3_combo_box_2(); + self.invalidate_target_line_3_edit_control(initiator); + self.invalidate_target_line_3_button(); + } + + fn invalidate_target_line_4(&self, initiator: Option) { + self.invalidate_target_line_4_label_1(); + self.invalidate_target_line_4_label_2(); + self.invalidate_target_line_4_label_3(); + self.invalidate_target_line_4_combo_box_1(); + self.invalidate_target_line_4_combo_box_2(); + self.invalidate_target_line_4_edit_control(initiator); + self.invalidate_target_line_4_button(); + } + + fn invalidate_target_line_5(&self, initiator: Option) { + self.invalidate_target_line_5_label_1(); + self.invalidate_target_line_5_edit_control(initiator); + } + + fn invalidate_target_line_3_button(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::ModifyMapping => Some("Pick!"), + ReaperTargetType::SendMidi + if self.mapping.target_model.send_midi_destination_type() + == SendMidiDestinationType::InputDevice => + { + Some("Pick!") + } + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_3_BUTTON) + .set_text_or_hide(text); + } + + fn invalidate_target_line_4_button(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Action => Some("Pick!"), + ReaperTargetType::LoadFxSnapshot => Some("Take!"), + ReaperTargetType::ModifyMapping => Some("Pick!"), + ReaperTargetType::SendMidi => Some("..."), + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_4_BUTTON) + .set_text_or_hide(text); + } + + fn invalidate_target_line_3_label_3(&self) { + invalidat_target_line_3_expression_result( + self.target, + self.session.extended_context(), + self.view.require_control(root::ID_TARGET_LINE_3_LABEL_3), + self.mapping.compartment(), + ); + } + + fn invalidate_target_line_4_label_3(&self) { + invalidate_target_line_4_expression_result( + self.target, + self.session.extended_context(), + self.view.require_control(root::ID_TARGET_LINE_4_LABEL_3), + self.mapping.compartment(), + ); + } + + fn invalidate_target_line_5_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_TARGET_LINE_5_EDIT_CONTROL) { + return; + } + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc if self.target.supports_osc_arg_value_range() => { + let text = format_osc_arg_value_range( + self.target.osc_arg_value_range(), + self.target.osc_arg_type_tag(), + ); + Some(text) + } + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_5_EDIT_CONTROL) + .set_text_or_hide(text); + } + + fn invalidate_target_line_4_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_TARGET_LINE_4_EDIT_CONTROL) { + return; + } + let control = self + .view + .require_control(root::ID_TARGET_LINE_4_EDIT_CONTROL); + let (text, read_only) = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendMidi => { + let text = self.target.raw_midi_pattern().to_owned(); + let read_only = text.chars().count() > 30; + (Some(text), read_only) + } + ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction + | ReaperTargetType::PlaytimeSlotSeek + | ReaperTargetType::PlaytimeSlotVolume => { + let text = match self.target.playtime_slot() { + PlaytimeSlotDescriptor::Active => None, + PlaytimeSlotDescriptor::ByIndex(a) => Some((a.row_index + 1).to_string()), + PlaytimeSlotDescriptor::Dynamic { row_expression, .. } => { + Some(row_expression.clone()) + } + }; + (text, false) + } + t if t.supports_fx_parameter() => { + let text = match self.target.param_type() { + VirtualFxParameterType::Dynamic => { + Some(self.target.param_expression().to_owned()) + } + VirtualFxParameterType::ByName => Some(self.target.param_name().to_owned()), + VirtualFxParameterType::ByIndex => { + let index = self.target.param_index(); + Some((index + 1).to_string()) + } + VirtualFxParameterType::ById => None, + }; + (text, false) + } + t if t.supports_send() => { + let text = match self.target.route_selector_type() { + TrackRouteSelectorType::Dynamic => { + Some(self.target.route_expression().to_owned()) + } + TrackRouteSelectorType::ByName => Some(self.target.route_name().to_owned()), + TrackRouteSelectorType::ByIndex => { + let index = self.target.route_index(); + Some((index + 1).to_string()) + } + _ => None, + }; + (text, false) + } + t if t.supports_tags() => { + let text = format_tags_as_csv(self.target.tags()); + (Some(text), false) + } + _ => (None, false), + }, + TargetCategory::Virtual => (None, false), + }; + control.set_text_or_hide(text); + control.set_enabled(!read_only); + } + + fn invalidate_target_line_3_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_TARGET_LINE_3_EDIT_CONTROL) { + return; + } + let c = self + .view + .require_control(root::ID_TARGET_LINE_3_EDIT_CONTROL); + let (value_text, read_only) = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc => { + let text = self.target.osc_address_pattern().to_owned(); + (Some(text), false) + } + ReaperTargetType::LoadMappingSnapshot => { + let text = self + .target + .mapping_snapshot_default_value() + .map(|v| { + format!("{} %", format_as_percentage_without_unit(v.to_unit_value())) + }) + .unwrap_or_default(); + (Some(text), false) + } + ReaperTargetType::PlaytimeColumnAction => { + let text = match self.target.playtime_column() { + PlaytimeColumnDescriptor::Active => None, + PlaytimeColumnDescriptor::ByIndex(a) => Some((a.index + 1).to_string()), + PlaytimeColumnDescriptor::Dynamic { expression } => { + Some(expression.clone()) + } + }; + (text, false) + } + ReaperTargetType::PlaytimeRowAction => { + let text = match self.target.playtime_row() { + PlaytimeRowDescriptor::Active => None, + PlaytimeRowDescriptor::ByIndex(a) => Some((a.index + 1).to_string()), + PlaytimeRowDescriptor::Dynamic { expression } => Some(expression.clone()), + }; + (text, false) + } + ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction + | ReaperTargetType::PlaytimeSlotSeek + | ReaperTargetType::PlaytimeSlotVolume => { + let text = match self.target.playtime_slot() { + PlaytimeSlotDescriptor::Active => None, + PlaytimeSlotDescriptor::ByIndex(a) => { + Some((a.column_index + 1).to_string()) + } + PlaytimeSlotDescriptor::Dynamic { + column_expression, .. + } => Some(column_expression.clone()), + }; + (text, false) + } + t if t.supports_fx() => { + let text = match self.target.fx_type() { + VirtualFxType::Dynamic => Some(self.target.fx_expression().to_owned()), + VirtualFxType::ByIndex => { + let index = self.target.fx_index(); + Some((index + 1).to_string()) + } + VirtualFxType::ByName | VirtualFxType::AllByName => { + Some(self.target.fx_name().to_owned()) + } + _ => None, + }; + (text, false) + } + _ => (None, false), + }, + TargetCategory::Virtual => (None, false), + }; + c.set_text_or_hide(value_text); + c.set_enabled(!read_only); + } + + fn invalidate_target_line_3_label_1(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Action => Some("Invoke"), + ReaperTargetType::TrackSolo => Some("Behavior"), + ReaperTargetType::TrackShow => Some("Area"), + ReaperTargetType::TrackTouchState => Some("Type"), + ReaperTargetType::SendOsc => Some("Address"), + ReaperTargetType::TrackMonitoringMode => Some("Mode"), + ReaperTargetType::LoadMappingSnapshot => Some("Default"), + ReaperTargetType::ModifyMapping => Some("Unit"), + ReaperTargetType::EnableInstances => Some("Tag kind"), + ReaperTargetType::SendMidi + if self.mapping.target_model.send_midi_destination_type() + == SendMidiDestinationType::InputDevice => + { + Some("Device") + } + ReaperTargetType::PlaytimeColumnAction => Some("Column"), + ReaperTargetType::PlaytimeRowAction => Some("Row"), + ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction + | ReaperTargetType::PlaytimeSlotSeek + | ReaperTargetType::PlaytimeSlotVolume => Some("Slot"), + _ if self.target.supports_automation_mode() => Some("Mode"), + _ if self.mapping.target_model.supports_axis() => Some("Axis"), + t if t.supports_fx() => Some("FX"), + t if t.supports_seek_behavior() => Some("Behavior"), + t if t.supports_send() => Some("Kind"), + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_3_LABEL_1) + .set_text_or_hide(text); + } + + fn invalidate_target_line_5_label_1(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc if self.target.supports_osc_arg_value_range() => { + Some("Range") + } + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_5_LABEL_1) + .set_text_or_hide(text); + } + + fn invalidate_target_line_4_label_1(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Mouse if self.mapping.target_model.supports_mouse_button() => { + Some("Button") + } + ReaperTargetType::Action => Some("Action"), + ReaperTargetType::LoadFxSnapshot => Some("Snapshot"), + ReaperTargetType::SendOsc => Some("Argument"), + ReaperTargetType::TrackTool | ReaperTargetType::FxTool => Some("Act/Tags"), + ReaperTargetType::ModifyMapping => Some("Mapping"), + ReaperTargetType::SendMidi => Some("Pattern"), + t if t.supports_fx_parameter() => Some("Parameter"), + t if t.supports_track_exclusivity() => Some("Exclusive"), + t if t.supports_fx_display_type() => Some("Display"), + t if t.supports_tags() => Some("Tags"), + t if t.supports_exclusivity() => Some("Exclusivity"), + t if t.supports_send() => match self.target.route_type() { + TrackRouteType::Send => Some("Send"), + TrackRouteType::Receive => Some("Receive"), + TrackRouteType::HardwareOutput => Some("Output"), + }, + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_4_LABEL_1) + .set_text_or_hide(text); + } + + fn invalidate_target_line_3_label_2(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendMidi + if self.mapping.target_model.send_midi_destination_type() + == SendMidiDestinationType::InputDevice => + { + let text = if let Some(dev_id) = self.target.midi_input_device() { + let dev = Reaper::get().midi_input_device_by_id(dev_id); + get_midi_input_device_list_label(dev) + } else { + SAME_AS_INPUT_DEV.to_string() + }; + Some(text) + } + ReaperTargetType::ModifyMapping => match self.target.mapping_ref() { + MappingRefModel::OwnMapping { .. } => Some("".to_string()), + MappingRefModel::ForeignMapping { session_id, .. } => { + if let Some(session) = + BackboneShell::get().find_unit_model_by_key(session_id) + { + Some(session.borrow().to_string()) + } else { + Some("".to_string()) + } + } + }, + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_3_LABEL_2) + .set_text_or_hide(text); + } + + fn invalidate_target_line_4_label_2(&self) { + let text = match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Action => Some(self.target.action_name_label().to_string()), + ReaperTargetType::LoadFxSnapshot => { + let label = if let Some(snapshot) = self.target.fx_snapshot() { + snapshot.to_string() + } else { + "".to_owned() + }; + Some(label) + } + ReaperTargetType::ModifyMapping => { + const NONE: &str = ""; + const MAPPING_DOESNT_EXIST: &str = ""; + let compartment = self.mapping.compartment(); + let label = match self.target.mapping_ref() { + MappingRefModel::OwnMapping { mapping_id } => match mapping_id { + None => NONE.to_string(), + Some(id) => match self.session.find_mapping_by_id(compartment, *id) { + None => MAPPING_DOESNT_EXIST.to_string(), + Some(mapping) => mapping.borrow().effective_name(), + }, + }, + MappingRefModel::ForeignMapping { + session_id, + mapping_key, + } => match mapping_key { + None => NONE.to_string(), + Some(mapping_key) => { + if let Some(other_session) = BackboneShell::get() + .find_session_by_id_ignoring_borrowed_ones(session_id) + { + let other_session = other_session.borrow(); + match other_session + .find_mapping_by_key(compartment, mapping_key) + { + None => MAPPING_DOESNT_EXIST.to_string(), + Some(mapping) => mapping.borrow().effective_name(), + } + } else { + "-".to_string() + } + } + }, + }; + Some(label) + } + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.view + .require_control(root::ID_TARGET_LINE_4_LABEL_2) + .set_text_or_hide(text); + } + + fn invalidate_target_line_3_combo_box_1(&self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_3_COMBO_BOX_1); + match self.target_category() { + TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::Action => { + combo.show(); + combo.fill_combo_box_indexed(ActionScope::iter()); + combo.select_combo_box_item_by_index(self.target.action_scope().into()); + } + ReaperTargetType::EnableInstances => { + combo.show(); + combo.fill_combo_box_indexed(InstanceTagKind::iter()); + combo.select_combo_box_item_by_index(self.target.instance_tag_kind().into()); + } + ReaperTargetType::PlaytimeColumnAction => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeColumnDescriptorKind::iter()); + combo.select_combo_box_item_by_index( + self.target.playtime_column().kind().into(), + ); + } + ReaperTargetType::PlaytimeRowAction => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeRowDescriptorKind::iter()); + combo.select_combo_box_item_by_index(self.target.playtime_row().kind().into()); + } + ReaperTargetType::PlaytimeSlotManagementAction + | ReaperTargetType::PlaytimeSlotTransportAction + | ReaperTargetType::PlaytimeSlotSeek + | ReaperTargetType::PlaytimeSlotVolume => { + combo.show(); + combo.fill_combo_box_indexed(PlaytimeSlotDescriptorKind::iter()); + combo.select_combo_box_item_by_index(self.target.playtime_slot().kind().into()); + } + t if t.supports_fx() => { + combo.show(); + combo.fill_combo_box_indexed(VirtualFxType::iter()); + combo.select_combo_box_item_by_index(self.target.fx_type().into()); + } + t if t.supports_send() => { + combo.show(); + combo.fill_combo_box_indexed(TrackRouteType::iter()); + combo.select_combo_box_item_by_index(self.target.route_type().into()); + } + _ => combo.hide(), + }, + TargetCategory::Virtual => { + combo.hide(); + } + } + } + + fn invalidate_target_line_4_combo_box_1(&self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_4_COMBO_BOX_1); + match self.target_category() { + TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::SendOsc => { + invalidate_with_osc_arg_index(combo, self.target.osc_arg_index()); + } + ReaperTargetType::BrowseGroup => { + combo.show(); + combo.fill_combo_box_indexed(SimpleExclusivity::iter()); + let simple_exclusivity: SimpleExclusivity = self.target.exclusivity().into(); + combo.select_combo_box_item_by_index(simple_exclusivity.into()); + } + ReaperTargetType::TrackTool => { + combo.show(); + combo.fill_combo_box_indexed(TrackToolAction::iter()); + let action: TrackToolAction = self.target.track_tool_action(); + combo.select_combo_box_item_by_index(action.into()); + } + ReaperTargetType::FxTool => { + combo.show(); + combo.fill_combo_box_indexed(FxToolAction::iter()); + let action: FxToolAction = self.target.fx_tool_action(); + combo.select_combo_box_item_by_index(action.into()); + } + t if t.supports_fx_parameter() => { + combo.show(); + combo.fill_combo_box_indexed(VirtualFxParameterType::iter()); + combo.select_combo_box_item_by_index(self.target.param_type().into()); + } + t if t.supports_exclusivity() => { + combo.show(); + combo.fill_combo_box_indexed(Exclusivity::iter()); + combo.select_combo_box_item_by_index(self.target.exclusivity().into()); + } + t if t.supports_send() => { + combo.show(); + combo.fill_combo_box_indexed(TrackRouteSelectorType::iter()); + combo.select_combo_box_item_by_index(self.target.route_selector_type().into()); + } + _ => combo.hide(), + }, + TargetCategory::Virtual => { + combo.hide(); + } + } + } + + fn invalidate_target_line_3_combo_box_2(&self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_3_COMBO_BOX_2); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + t if t.supports_fx() => { + if matches!( + self.target.fx_type(), + VirtualFxType::ById | VirtualFxType::ByIdOrIndex + ) { + combo.show(); + if self.target.track_type().is_sticky() { + let context = self.session.extended_context(); + if let Ok(track) = self + .target + .with_context(context, self.mapping.compartment()) + .first_effective_track() + { + // Fill + let chain = if self.target.fx_is_input_fx() { + track.input_fx_chain() + } else { + track.normal_fx_chain() + }; + combo.fill_combo_box_indexed(fx_combo_box_entries(&chain)); + // Set + if let Some(VirtualFx::ChainFx { chain_fx, .. }) = + self.target.virtual_fx() + { + if let Some(fx) = chain_fx + .resolve(&[chain], context, self.mapping.compartment()) + .ok() + .and_then(|fxs| fxs.into_iter().next()) + { + combo.select_combo_box_item_by_index(fx.index() as _); + } else { + combo.select_new_combo_box_item(get_optional_fx_label( + &chain_fx, None, + )); + } + } else { + combo.select_new_combo_box_item(""); + } + } else { + combo.select_only_combo_box_item(""); + } + } else { + combo.select_only_combo_box_item( + "Use 'Particular' only if track is particular as well!", + ); + } + } else { + combo.hide(); + } + } + t if t.supports_seek_behavior() => { + combo.show(); + combo.fill_combo_box_indexed(SeekBehavior::iter()); + combo.select_combo_box_item_by_index(self.target.seek_behavior().into()); + } + ReaperTargetType::Action => { + combo.show(); + combo.fill_combo_box_indexed(ActionInvocationType::iter()); + combo.select_combo_box_item_by_index( + self.target.action_invocation_type().into(), + ); + } + ReaperTargetType::TrackSolo => { + combo.show(); + combo.fill_combo_box_indexed(SoloBehavior::iter()); + combo.select_combo_box_item_by_index(self.target.solo_behavior().into()); + } + ReaperTargetType::TrackShow => { + combo.show(); + combo.fill_combo_box_indexed(RealearnTrackArea::iter()); + combo.select_combo_box_item_by_index(self.target.track_area().into()); + } + ReaperTargetType::RouteTouchState => { + combo.show(); + combo.fill_combo_box_indexed(TouchedRouteParameterType::iter()); + combo.select_combo_box_item_by_index( + self.target.touched_route_parameter_type().into(), + ); + } + ReaperTargetType::TrackMonitoringMode => { + combo.show(); + combo.fill_combo_box_indexed(MonitoringMode::iter()); + combo.select_combo_box_item_by_index(self.target.monitoring_mode().into()); + } + _ if self.target.supports_automation_mode() => { + combo.show(); + combo.fill_combo_box_indexed(RealearnAutomationMode::iter()); + combo.select_combo_box_item_by_index(self.target.automation_mode().into()); + } + ReaperTargetType::TrackTouchState => { + combo.show(); + combo.fill_combo_box_indexed(TouchedTrackParameterType::iter()); + combo.select_combo_box_item_by_index( + self.target.touched_track_parameter_type().into(), + ); + } + _ if self.mapping.target_model.supports_axis() => { + combo.show(); + combo.fill_combo_box_indexed(Axis::iter()); + combo.select_combo_box_item_by_index(self.target.axis().into()); + } + _ => { + combo.hide(); + } + }, + TargetCategory::Virtual => { + combo.hide(); + } + } + } + + fn invalidate_target_line_4_combo_box_2(&self) { + let combo = self + .view + .require_control(root::ID_TARGET_LINE_4_COMBO_BOX_2); + match self.target_category() { + TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::SendOsc if self.target.osc_arg_index().is_some() => { + let tag = self.target.osc_arg_type_tag(); + invalidate_with_osc_arg_type_tag(combo, tag); + } + ReaperTargetType::Mouse if self.mapping.target_model.supports_mouse_button() => { + combo.show(); + combo.fill_combo_box_indexed(MouseButton::iter()); + combo.select_combo_box_item_by_index(self.target.mouse_button().into()); + } + t if t.supports_fx_parameter() + && self.target.param_type() == VirtualFxParameterType::ById => + { + combo.show(); + let fx = get_relevant_target_fx(self.mapping, self.session); + if let Some(fx) = fx { + combo.fill_combo_box_indexed(fx_parameter_combo_box_entries(&fx)); + let param_index = self.target.param_index(); + combo + .select_combo_box_item_by_index_checked(param_index as _) + .unwrap_or_else(|_| { + let label = get_fx_param_label(None, param_index); + combo.select_new_combo_box_item(label.into_owned()); + }); + } else { + combo.select_only_combo_box_item(""); + } + } + t if t.supports_track_exclusivity() => { + combo.show(); + combo.fill_combo_box_indexed(TrackExclusivity::iter()); + combo.select_combo_box_item_by_index(self.target.track_exclusivity().into()); + } + t if t.supports_fx_display_type() => { + combo.show(); + combo.fill_combo_box_indexed(FxDisplayType::iter()); + combo.select_combo_box_item_by_index(self.target.fx_display_type().into()); + } + t if t.supports_send() => { + if self.target.route_selector_type() == TrackRouteSelectorType::ById { + combo.show(); + let context = self.session.extended_context(); + let target_with_context = self + .target + .with_context(context, self.mapping.compartment()); + if let Ok(track) = target_with_context.first_effective_track() { + // Fill + let route_type = self.target.route_type(); + combo.fill_combo_box_indexed_vec(send_combo_box_entries( + &track, route_type, + )); + // Set + if route_type == TrackRouteType::HardwareOutput { + // Hardware output uses indexes, not IDs. + let i = self.target.route_index(); + combo + .select_combo_box_item_by_index_checked(i as _) + .unwrap_or_else(|_| { + let pity_label = format!("{}. ", i + 1); + combo.select_new_combo_box_item(pity_label); + }); + } else { + // This is the real case. We use IDs. + if let Ok(virtual_route) = self.target.virtual_track_route() { + if let Ok(route) = virtual_route.resolve( + &track, + context, + self.mapping.compartment(), + ) { + let i = route.track_route_index().unwrap(); + combo.select_combo_box_item_by_index(i as _); + } else { + combo.select_new_combo_box_item( + get_non_present_virtual_route_label(&virtual_route), + ); + } + } else { + combo.select_new_combo_box_item(""); + } + } + } else { + combo.select_only_combo_box_item(""); + } + } else { + combo.hide(); + } + } + _ => { + combo.hide(); + } + }, + TargetCategory::Virtual => { + combo.hide(); + } + } + } + + fn invalidate_target_check_box_1(&self) { + let state = match self.target.category() { + TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::Action => Some(("With track", self.target.with_track())), + ReaperTargetType::GoToBookmark => { + let is_regions = self.target.bookmark_type() == BookmarkType::Region; + Some(("Regions", is_regions)) + } + ReaperTargetType::Seek => Some(("Seek play", self.target.seek_play())), + ReaperTargetType::PlaytimeSlotTransportAction => { + Some(("Stop if empty", self.target.stop_column_if_slot_empty())) + } + _ if self.target.supports_fx_chain() => { + let is_input_fx = self.target.fx_is_input_fx(); + let label = if self.target.track_type() == VirtualTrackType::Master { + "Monitoring FX" + } else { + "Input FX" + }; + Some((label, is_input_fx)) + } + t if t.supports_track_scrolling() => { + Some(("Scroll TCP", self.target.scroll_arrange_view())) + } + _ => None, + }, + TargetCategory::Virtual => Some(("Learnable", self.target.learnable())), + }; + self.invalidate_check_box(root::ID_TARGET_CHECK_BOX_1, state); + } + + fn invalidate_target_check_box_2(&self) { + let state = match self.target.category() { + TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::LoadMappingSnapshot => { + Some(("Active mappings only", self.target.active_mappings_only())) + } + _ if self.mapping.target_model.supports_track_must_be_selected() => { + if self + .target + .track_type() + .track_selected_condition_makes_sense() + { + Some(( + "Track must be selected", + self.target.enable_only_if_track_selected(), + )) + } else { + None + } + } + t if t.supports_track_scrolling() => { + Some(("Scroll MCP", self.target.scroll_mixer())) + } + ReaperTargetType::Seek => Some(("Move view", self.target.move_view())), + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.invalidate_check_box(root::ID_TARGET_CHECK_BOX_2, state); + } + + fn invalidate_target_check_box_3(&self) { + let state = match self.target.category() { + TargetCategory::Reaper => match self.target.target_type() { + t if t.supports_fx() => { + if self.target.fx_type() == VirtualFxType::Focused { + None + } else { + Some(( + "FX must have focus", + self.target.enable_only_if_fx_has_focus(), + )) + } + } + ReaperTargetType::Seek => Some(("Use project", self.target.use_project())), + _ if self.target.supports_gang_selected() => Some(( + "Selection ganging", + self.target.fixed_gang_behavior().use_selection_ganging(), + )), + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.invalidate_check_box(root::ID_TARGET_CHECK_BOX_3, state); + } + + fn invalidate_target_check_box_4(&self) { + let state = match self.target.category() { + TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::Seek => Some(("Use regions", self.target.use_regions())), + t if t.supports_poll_for_feedback() => { + Some(("Poll for feedback", self.target.poll_for_feedback())) + } + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.invalidate_check_box(root::ID_TARGET_CHECK_BOX_4, state); + } + + fn invalidate_target_check_box_5(&self) { + let checkbox_id = root::ID_TARGET_CHECK_BOX_5; + let state = match self.target.category() { + TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::FxParameterValue => Some(("Retrigger", self.target.retrigger())), + ReaperTargetType::Seek => Some(("Use loop points", self.target.use_loop_points())), + ReaperTargetType::GoToBookmark => { + Some(("Set loop points", self.target.use_loop_points())) + } + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.invalidate_check_box(checkbox_id, state); + } + + fn invalidate_target_check_box_6(&self) { + let state = match self.target.category() { + TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::FxParameterValue => Some(("Real-time", self.target.real_time())), + ReaperTargetType::Seek => { + Some(("Use time selection", self.target.use_time_selection())) + } + ReaperTargetType::GoToBookmark => { + Some(("Set time selection", self.target.use_time_selection())) + } + _ if self.target.supports_gang_grouping() => Some(( + "Respect grouping", + self.target.fixed_gang_behavior().use_track_grouping(), + )), + _ => None, + }, + TargetCategory::Virtual => None, + }; + self.invalidate_check_box(root::ID_TARGET_CHECK_BOX_6, state); + } + + fn invalidate_check_box<'b>( + &self, + checkbox_id: u32, + state: Option<(impl Into>, bool)>, + ) { + let b = self.view.require_control(checkbox_id); + if let Some((label, is_checked)) = state { + b.set_text(label); + b.set_checked(is_checked); + b.show(); + } else { + b.hide(); + } + } + + fn invalidate_target_value_controls(&self) { + // TODO-low This might set the value slider to the wrong value because it only takes the + // first resolved target into account. + let (error_msg, read_enabled, write_enabled, character, is_relative) = + if let Some(t) = self.first_resolved_target() { + let control_context = self.session.control_context(); + let (error_msg, read_enabled, write_enabled) = if t.is_virtual() { + // Makes no sense to display any value controls for virtual targets. They neither + // have a value nor would moving a slider make any difference. + (None, false, false) + } else if t.can_report_current_value() { + let value = t.current_value(control_context).unwrap_or_default(); + self.invalidate_target_value_controls_with_value(value); + (None, true, true) + } else { + // Target is real but can't report values (e.g. load mapping snapshot) + (None, false, true) + }; + let is_relative = t.control_type(control_context).is_relative(); + ( + error_msg, + read_enabled, + write_enabled, + Some(t.character(control_context)), + is_relative, + ) + } else { + (Some("Target inactive!"), false, false, None, false) + }; + self.enable_if( + read_enabled, + &[ + root::ID_TARGET_VALUE_LABEL_TEXT, + root::ID_TARGET_VALUE_EDIT_CONTROL, + root::ID_TARGET_UNIT_BUTTON, + ], + ); + // Slider or buttons + let button_1 = self.view.require_control(root::ID_TARGET_VALUE_OFF_BUTTON); + let button_2 = self.view.require_control(root::ID_TARGET_VALUE_ON_BUTTON); + let slider_control = self + .view + .require_control(root::ID_TARGET_VALUE_SLIDER_CONTROL); + if write_enabled { + if is_relative { + slider_control.hide(); + button_1.show(); + button_2.show(); + button_1.set_text("-"); + button_2.set_text("+"); + } else { + use TargetCharacter::*; + match character { + Some(Trigger) => { + slider_control.hide(); + button_1.hide(); + button_2.show(); + button_2.set_text("Trigger!"); + } + Some(Switch) => { + slider_control.hide(); + button_1.show(); + button_2.show(); + button_1.set_text("Off"); + button_2.set_text("On"); + } + _ => { + button_1.hide(); + button_2.hide(); + slider_control.show(); + } + } + } + } else { + slider_control.hide(); + button_1.hide(); + button_2.hide(); + } + // Maybe display grey error message instead of value text + let value_text = self.view.require_control(root::ID_TARGET_VALUE_TEXT); + if let Some(msg) = error_msg { + value_text.show(); + value_text.disable(); + value_text.set_text(msg); + } else if read_enabled { + value_text.show(); + value_text.enable(); + // Value text already set above + } else { + value_text.hide(); + } + } + + fn invalidate_target_value_controls_with_value(&self, value: AbsoluteValue) { + self.invalidate_target_controls_internal( + root::ID_TARGET_VALUE_SLIDER_CONTROL, + root::ID_TARGET_VALUE_EDIT_CONTROL, + root::ID_TARGET_VALUE_TEXT, + value, + None, + false, + ); + self.invalidate_target_unit_button(); + } + + fn invalidate_target_unit_button(&self) { + let unit = self.mapping.target_model.unit(); + let control_context = self.session.control_context(); + let (value_unit, step_size_unit) = match unit { + TargetUnit::Native => self + .first_resolved_target() + .map(|t| { + let vu = t.value_unit(control_context); + let vu = if vu.is_empty() { None } else { Some(vu) }; + let su = t.step_size_unit(control_context); + let su = if su.is_empty() { None } else { Some(su) }; + (vu, su) + }) + .unwrap_or((None, None)), + TargetUnit::Percent => (Some("%"), Some("%")), + }; + let text = format!( + "{}. {} ({})", + usize::from(unit) + 1, + value_unit.unwrap_or("-"), + step_size_unit.unwrap_or("-") + ); + self.view + .require_control(root::ID_TARGET_UNIT_BUTTON) + .set_text(text); + } + + fn invalidate_target_learn_button(&self) { + self.invalidate_learn_button( + self.session + .unit() + .borrow() + .mapping_is_learning_target(self.mapping.qualified_id()), + root::ID_TARGET_LEARN_BUTTON, + ); + } + + fn register_listeners(&self) { + self.register_session_listeners(); + self.register_help_listeners(); + } + + fn register_help_listeners(&self) { + self.panel.when( + self.panel.active_help_topic.borrow().changed(), + |view, _| { + view.invalidate_help(); + }, + ); + } + + fn register_session_listeners(&self) { + self.panel.when( + self.session.unit().borrow().on_mappings_changed(), + |view, _| { + view.invalidate_window_title(); + }, + ); + self.panel.when( + self.session + .unit() + .borrow() + .mapping_which_learns_source() + .changed(), + |view, _| { + view.invalidate_source_learn_button(); + }, + ); + self.panel.when( + self.session + .unit() + .borrow() + .mapping_which_learns_target() + .changed(), + |view, _| { + view.invalidate_target_learn_button(); + }, + ); + } + + fn invalidate_mode_controls(&self) { + self.invalidate_mode_controls_internal(None); + } + + fn invalidate_mode_controls_internal(&self, initiator: Option) { + self.fill_mode_type_combo_box(); + self.invalidate_mode_type_combo_box(); + self.invalidate_mode_control_appearance(); + self.invalidate_mode_source_value_controls(initiator); + self.invalidate_mode_target_value_controls(initiator); + self.invalidate_mode_step_controls(initiator); + self.invalidate_mode_fire_controls(initiator); + self.invalidate_mode_rotate_check_box(); + self.invalidate_mode_make_absolute_check_box(); + self.invalidate_mode_feedback_type_combo_box(); + self.invalidate_mode_feedback_type_button(); + self.invalidate_mode_out_of_range_behavior_combo_box(); + self.invalidate_mode_group_interaction_combo_box(); + self.invalidate_mode_round_target_value_check_box(); + self.invalidate_mode_takeover_mode_combo_box(); + self.invalidate_mode_button_usage_combo_box(); + self.invalidate_mode_encoder_usage_combo_box(); + self.invalidate_mode_reverse_check_box(); + self.invalidate_mode_target_value_sequence_edit_control(initiator); + self.invalidate_mode_eel_control_transformation_edit_control(initiator); + self.invalidate_mode_eel_feedback_transformation_edit_control(initiator); + } + + fn invalidate_mode_type_combo_box(&self) { + self.view + .require_control(root::ID_SETTINGS_MODE_COMBO_BOX) + .select_combo_box_item_by_index(self.mode.absolute_mode().into()); + } + + fn invalidate_mode_control_appearance(&self) { + self.invalidate_mode_control_labels(); + self.invalidate_mode_control_visibilities(); + } + + fn mapping_uses_step_factors(&self) -> bool { + self.mapping + .with_context(self.session.extended_context()) + .uses_step_factors() + } + + fn invalidate_mode_control_labels(&self) { + let step_label = if self.mapping_uses_step_factors() { + "Speed" + } else { + "Step size" + }; + self.view + .require_control(root::ID_SETTINGS_STEP_SIZE_LABEL_TEXT) + .set_text(step_label); + } + + #[allow(clippy::needless_bool)] + fn invalidate_mode_control_visibilities(&self) { + let relevant_source_characters = self.mapping.source_model.possible_detailed_characters(); + let base_input = self.mapping.base_mode_applicability_check_input(); + let is_relevant = |mode_parameter: ModeParameter| { + self.mapping.mode_parameter_is_relevant( + mode_parameter, + base_input, + &relevant_source_characters, + ) + }; + let real_target = self.first_resolved_target(); + let target_can_report_current_value = real_target + .as_ref() + .map(|t| t.can_report_current_value()) + .unwrap_or_default(); + let target_wants_relative_control = real_target + .as_ref() + .map(|t| t.control_type(self.session.control_context()).is_relative()) + .unwrap_or_default(); + let feedback_is_on = self.mapping.feedback_is_enabled_and_supported(); + // For all source characters + { + let show_source_min_max = is_relevant(ModeParameter::SourceMinMax); + self.enable_if(show_source_min_max, SOURCE_MIN_MAX_ELEMENTS); + let show_reverse = is_relevant(ModeParameter::Reverse); + self.enable_if(show_reverse, REVERSE_ELEMENTS); + let show_out_of_range_behavior = is_relevant(ModeParameter::OutOfRangeBehavior); + self.enable_if(show_out_of_range_behavior, OUT_OF_RANGE_ELEMENTS); + let show_group_interaction = is_relevant(ModeParameter::GroupInteraction); + self.enable_if(show_group_interaction, GROUP_INTERACTION_ELEMENTS); + let target_controls_make_sense = if target_wants_relative_control + && (!feedback_is_on || !target_can_report_current_value) + { + false + } else { + true + }; + let show_target_value_sequence = target_controls_make_sense + && is_relevant(ModeParameter::TargetValueSequence) + && real_target.is_some(); + self.enable_if(show_target_value_sequence, VALUE_SEQUENCE_ELEMENTS); + let show_target_min_max = target_controls_make_sense + && is_relevant(ModeParameter::TargetMinMax) + && real_target.is_some(); + self.enable_if(show_target_min_max, TARGET_MIN_MAX_ELEMENTS); + let show_feedback_transformation = is_relevant(ModeParameter::FeedbackTransformation) + || is_relevant(ModeParameter::TextualFeedbackExpression); + self.enable_if( + show_feedback_transformation, + FEEDBACK_TRANSFORMATION_ELEMENTS, + ); + let show_feedback_type = is_relevant(ModeParameter::FeedbackType); + self.enable_if(show_feedback_type, FEEDBACK_TYPE_ELEMENTS); + } + // For knobs/faders and buttons + { + let show_round_controls = is_relevant(ModeParameter::RoundTargetValue) + && self.target_with_context().is_known_to_be_roundable(); + self.enable_if(show_round_controls, ROUND_TARGET_VALUE_ELEMENTS); + let show_takeover = + target_can_report_current_value && is_relevant(ModeParameter::TakeoverMode); + self.enable_if(show_takeover, TAKEOVER_MODE_ELEMENTS); + let show_control_transformation = is_relevant(ModeParameter::ControlTransformation); + self.enable_if(show_control_transformation, CONTROL_TRANSFORMATION_ELEMENTS); + let show_absolute_mode = is_relevant(ModeParameter::AbsoluteMode); + self.enable_if(show_absolute_mode, ABSOLUTE_MODE_ELEMENTS); + self.enable_if( + show_round_controls + || show_takeover + || show_control_transformation + || show_absolute_mode, + &[root::ID_MODE_KNOB_FADER_GROUP_BOX], + ); + } + // For encoders and incremental buttons + { + let step_min_is_relevant = real_target.is_some() + && (is_relevant(ModeParameter::StepSizeMin) + || is_relevant(ModeParameter::StepFactorMin)); + let step_max_is_relevant = real_target.is_some() + && (is_relevant(ModeParameter::StepSizeMax) + || is_relevant(ModeParameter::StepFactorMax)); + self.enable_if( + step_min_is_relevant || step_max_is_relevant, + &[root::ID_SETTINGS_STEP_SIZE_LABEL_TEXT], + ); + self.enable_if(step_min_is_relevant, STEP_MIN_ELEMENTS); + self.enable_if(step_max_is_relevant, STEP_MAX_ELEMENTS); + let show_rotate = is_relevant(ModeParameter::Rotate); + self.enable_if(show_rotate, ROTATE_ELEMENTS); + let show_make_absolute = is_relevant(ModeParameter::MakeAbsolute); + self.enable_if(show_make_absolute, MAKE_ABSOLUTE_ELEMENTS); + let show_relative_filter = is_relevant(ModeParameter::RelativeFilter); + self.enable_if(show_relative_filter, RELATIVE_FILTER_ELEMENTS); + self.enable_if( + step_min_is_relevant + || step_max_is_relevant + || show_rotate + || show_make_absolute + || show_relative_filter, + &[root::ID_MODE_RELATIVE_GROUP_BOX], + ); + } + // For buttons + { + let show_button_filter = is_relevant(ModeParameter::ButtonFilter); + self.enable_if(show_button_filter, BUTTON_FILTER_ELEMENTS); + let show_fire_mode = is_relevant(ModeParameter::FireMode); + self.enable_if(show_fire_mode, FIRE_MODE_ELEMENTS); + self.enable_if( + show_button_filter || show_fire_mode, + &[root::ID_MODE_BUTTON_GROUP_BOX], + ); + } + } + + fn invalidate_mode_source_value_controls(&self, initiator: Option) { + self.invalidate_mode_min_source_value_controls(initiator); + self.invalidate_mode_max_source_value_controls(initiator); + } + + fn invalidate_mode_target_value_controls(&self, initiator: Option) { + self.invalidate_mode_min_target_value_controls(initiator); + self.invalidate_mode_max_target_value_controls(initiator); + } + + fn invalidate_mode_min_source_value_controls(&self, initiator: Option) { + self.invalidate_mode_source_value_controls_internal( + root::ID_SETTINGS_MIN_SOURCE_VALUE_SLIDER_CONTROL, + root::ID_SETTINGS_MIN_SOURCE_VALUE_EDIT_CONTROL, + self.mode.source_value_interval().min_val(), + initiator, + ); + } + + fn invalidate_mode_max_source_value_controls(&self, initiator: Option) { + self.invalidate_mode_source_value_controls_internal( + root::ID_SETTINGS_MAX_SOURCE_VALUE_SLIDER_CONTROL, + root::ID_SETTINGS_MAX_SOURCE_VALUE_EDIT_CONTROL, + self.mode.source_value_interval().max_val(), + initiator, + ); + } + + fn invalidate_mode_source_value_controls_internal( + &self, + slider_control_id: u32, + edit_control_id: u32, + value: UnitValue, + initiator: Option, + ) { + let formatted_value = self + .source + .format_control_value(ControlValue::AbsoluteContinuous(value)) + .unwrap_or_else(|_| "".to_string()); + if initiator != Some(edit_control_id) { + self.view + .require_control(edit_control_id) + .set_text(formatted_value); + } + self.view + .require_control(slider_control_id) + .set_slider_unit_value(value); + } + + fn invalidate_mode_min_target_value_controls(&self, initiator: Option) { + self.invalidate_target_controls_internal( + root::ID_SETTINGS_MIN_TARGET_VALUE_SLIDER_CONTROL, + root::ID_SETTINGS_MIN_TARGET_VALUE_EDIT_CONTROL, + root::ID_SETTINGS_MIN_TARGET_VALUE_TEXT, + AbsoluteValue::Continuous(self.mode.target_value_interval().min_val()), + initiator, + false, + ); + } + + fn invalidate_mode_max_target_value_controls(&self, initiator: Option) { + self.invalidate_target_controls_internal( + root::ID_SETTINGS_MAX_TARGET_VALUE_SLIDER_CONTROL, + root::ID_SETTINGS_MAX_TARGET_VALUE_EDIT_CONTROL, + root::ID_SETTINGS_MAX_TARGET_VALUE_TEXT, + AbsoluteValue::Continuous(self.mode.target_value_interval().max_val()), + initiator, + false, + ); + } + + #[allow(clippy::too_many_arguments)] + fn invalidate_target_controls_internal( + &self, + slider_control_id: u32, + edit_control_id: u32, + value_text_control_id: u32, + value: AbsoluteValue, + initiator: Option, + use_step_sizes: bool, + ) { + invalidate_target_controls_free( + // It's okay to use the first resolved target only because we use it solely to gather + // some target characteristics, no the value. + self.first_resolved_target().as_ref(), + self.view.require_control(slider_control_id), + self.view.require_control(edit_control_id), + self.view.require_control(value_text_control_id), + value, + initiator, + edit_control_id, + false, + use_step_sizes, + self.target.unit(), + self.session.control_context(), + ); + } + + fn invalidate_mode_step_controls(&self, initiator: Option) { + self.invalidate_mode_min_step_controls(initiator); + self.invalidate_mode_max_step_controls(initiator); + } + + fn invalidate_mode_fire_controls(&self, initiator: Option) { + let base_input = self.mapping.base_mode_applicability_check_input(); + let possible_source_characters = self.mapping.source_model.possible_detailed_characters(); + if self.mapping.mode_parameter_is_relevant( + ModeParameter::FireMode, + base_input, + &possible_source_characters, + ) { + self.invalidate_mode_fire_mode_combo_box(); + self.invalidate_mode_fire_line_2_controls(initiator); + self.invalidate_mode_fire_line_3_controls(initiator); + } + } + + fn invalidate_mode_min_step_controls(&self, initiator: Option) { + self.invalidate_mode_step_controls_internal( + root::ID_SETTINGS_MIN_STEP_SIZE_SLIDER_CONTROL, + root::ID_SETTINGS_MIN_STEP_SIZE_EDIT_CONTROL, + root::ID_SETTINGS_MIN_STEP_SIZE_VALUE_TEXT, + self.mode.step_size_interval().min_val(), + self.mode.step_factor_interval().min_val(), + initiator, + ); + } + + fn invalidate_mode_fire_line_2_controls(&self, initiator: Option) { + let label = match self.mapping.mode_model.fire_mode() { + FireMode::Normal => Some("Min"), + FireMode::AfterTimeout | FireMode::AfterTimeoutKeepFiring => Some("Timeout"), + FireMode::OnDoublePress | FireMode::OnSinglePress => None, + }; + self.view + .require_control(root::ID_MODE_FIRE_LINE_2_LABEL_1) + .set_text_or_hide(label); + if label.is_some() { + self.invalidate_mode_fire_controls_internal( + root::ID_MODE_FIRE_LINE_2_SLIDER_CONTROL, + root::ID_MODE_FIRE_LINE_2_EDIT_CONTROL, + root::ID_MODE_FIRE_LINE_2_LABEL_2, + self.mode.press_duration_interval().min_val(), + initiator, + ); + } + self.enable_if( + label.is_some(), + &[ + root::ID_MODE_FIRE_LINE_2_SLIDER_CONTROL, + root::ID_MODE_FIRE_LINE_2_EDIT_CONTROL, + root::ID_MODE_FIRE_LINE_2_LABEL_1, + root::ID_MODE_FIRE_LINE_2_LABEL_2, + ], + ); + } + + fn invalidate_mode_max_step_controls(&self, initiator: Option) { + self.invalidate_mode_step_controls_internal( + root::ID_SETTINGS_MAX_STEP_SIZE_SLIDER_CONTROL, + root::ID_SETTINGS_MAX_STEP_SIZE_EDIT_CONTROL, + root::ID_SETTINGS_MAX_STEP_SIZE_VALUE_TEXT, + self.mode.step_size_interval().max_val(), + self.mode.step_factor_interval().max_val(), + initiator, + ); + } + + fn invalidate_mode_fire_line_3_controls(&self, initiator: Option) { + let option = match self.mapping.mode_model.fire_mode() { + FireMode::Normal | FireMode::OnSinglePress => { + Some(("Max", self.mode.press_duration_interval().max_val())) + } + FireMode::AfterTimeout | FireMode::OnDoublePress => None, + FireMode::AfterTimeoutKeepFiring => Some(("Rate", self.mode.turbo_rate())), + }; + if let Some((label, value)) = option { + self.view + .require_control(root::ID_MODE_FIRE_LINE_3_LABEL_1) + .set_text(label); + self.invalidate_mode_fire_controls_internal( + root::ID_MODE_FIRE_LINE_3_SLIDER_CONTROL, + root::ID_MODE_FIRE_LINE_3_EDIT_CONTROL, + root::ID_MODE_FIRE_LINE_3_LABEL_2, + value, + initiator, + ); + } + self.enable_if( + option.is_some(), + &[ + root::ID_MODE_FIRE_LINE_3_SLIDER_CONTROL, + root::ID_MODE_FIRE_LINE_3_EDIT_CONTROL, + root::ID_MODE_FIRE_LINE_3_LABEL_1, + root::ID_MODE_FIRE_LINE_3_LABEL_2, + ], + ); + } + + fn invalidate_mode_step_controls_internal( + &self, + slider_control_id: u32, + edit_control_id: u32, + value_text_control_id: u32, + size_value: UnitValue, + factor_value: DiscreteIncrement, + initiator: Option, + ) { + enum Val { + Abs(UnitValue), + Rel(DiscreteIncrement), + } + let (val, edit_text, value_text) = match &self.first_resolved_target() { + Some(target) => { + if self.mapping_uses_step_factors() { + let edit_text = factor_value.to_string(); + let val = Val::Rel(factor_value); + // "count {x}" + (val, edit_text, "x".to_string()) + } else { + // "{size} {unit}" + let control_context = self.session.control_context(); + let edit_text = + target.format_step_size_without_unit(size_value, control_context); + let value_text = get_text_right_to_step_size_edit_control( + target, + size_value, + control_context, + ); + (Val::Abs(size_value), edit_text, value_text) + } + } + None => (Val::Abs(UnitValue::MIN), "".to_string(), "".to_string()), + }; + match val { + Val::Abs(v) => { + self.view + .require_control(slider_control_id) + .set_slider_unit_value(v); + } + Val::Rel(v) => { + self.view + .require_control(slider_control_id) + .set_slider_discrete_increment(v); + } + } + if initiator != Some(edit_control_id) { + self.view + .require_control(edit_control_id) + .set_text(edit_text); + } + self.view + .require_control(value_text_control_id) + .set_text(value_text) + } + + fn invalidate_mode_fire_controls_internal( + &self, + slider_control_id: u32, + edit_control_id: u32, + value_text_control_id: u32, + duration: Duration, + initiator: Option, + ) { + self.view + .require_control(slider_control_id) + .set_slider_duration(duration); + if initiator != Some(edit_control_id) { + self.view + .require_control(edit_control_id) + .set_text(duration.as_millis().to_string()); + } + self.view + .require_control(value_text_control_id) + .set_text("ms") + } + + fn invalidate_mode_rotate_check_box(&self) { + self.view + .require_control(root::ID_SETTINGS_ROTATE_CHECK_BOX) + .set_checked(self.mode.rotate()); + } + + fn invalidate_mode_feedback_type_button(&self) { + let text = if self.mode.feedback_color().is_some() + || self.mode.feedback_background_color().is_some() + { + "...*" + } else { + "..." + }; + self.view + .require_control(root::IDC_MODE_FEEDBACK_TYPE_BUTTON) + .set_text(text); + } + + fn invalidate_mode_feedback_type_combo_box(&self) { + self.view + .require_control(root::IDC_MODE_FEEDBACK_TYPE_COMBO_BOX) + .select_combo_box_item_by_index(self.mode.feedback_type().into()); + } + + fn invalidate_mode_make_absolute_check_box(&self) { + self.view + .require_control(root::ID_SETTINGS_MAKE_ABSOLUTE_CHECK_BOX) + .set_checked(self.mode.make_absolute()); + } + + fn invalidate_mode_out_of_range_behavior_combo_box(&self) { + self.view + .require_control(root::ID_MODE_OUT_OF_RANGE_COMBOX_BOX) + .select_combo_box_item_by_index(self.mode.out_of_range_behavior().into()); + } + + fn invalidate_mode_group_interaction_combo_box(&self) { + self.view + .require_control(root::ID_MODE_GROUP_INTERACTION_COMBO_BOX) + .select_combo_box_item_by_index(self.mode.group_interaction().into()); + } + + fn invalidate_mode_fire_mode_combo_box(&self) { + let combo = self.view.require_control(root::ID_MODE_FIRE_COMBO_BOX); + combo.set_enabled(self.target_category() != TargetCategory::Virtual); + combo.select_combo_box_item_by_index(self.mapping.mode_model.fire_mode().into()); + } + + fn invalidate_mode_round_target_value_check_box(&self) { + self.view + .require_control(root::ID_SETTINGS_ROUND_TARGET_VALUE_CHECK_BOX) + .set_checked(self.mode.round_target_value()); + } + + fn invalidate_mode_takeover_mode_combo_box(&self) { + let mode = self.mode.takeover_mode(); + self.view + .require_control(root::ID_MODE_TAKEOVER_MODE) + .select_combo_box_item_by_index(mode.into()); + } + + fn invalidate_mode_button_usage_combo_box(&self) { + let usage = self.mode.button_usage(); + self.view + .require_control(root::ID_MODE_BUTTON_FILTER_COMBO_BOX) + .select_combo_box_item_by_index(usage.into()); + } + + fn invalidate_mode_encoder_usage_combo_box(&self) { + let usage = self.mode.encoder_usage(); + self.view + .require_control(root::ID_MODE_RELATIVE_FILTER_COMBO_BOX) + .select_combo_box_item_by_index(usage.into()); + } + + fn invalidate_mode_reverse_check_box(&self) { + self.view + .require_control(root::ID_SETTINGS_REVERSE_CHECK_BOX) + .set_checked(self.mode.reverse()); + } + + fn invalidate_mode_target_value_sequence_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL) { + return; + } + let sequence = self.mode.target_value_sequence(); + let formatted = match self.target.unit() { + TargetUnit::Native => { + if let Some(t) = self.first_resolved_target() { + let t = WithControlContext::new(self.session.control_context(), &t); + let displayable = sequence.displayable(&t); + displayable.to_string() + } else { + sequence.displayable(&PercentIo).to_string() + } + } + TargetUnit::Percent => sequence.displayable(&PercentIo).to_string(), + }; + self.view + .require_control(root::ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL) + .set_text(formatted); + } + + fn invalidate_mode_eel_control_transformation_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL) { + return; + } + let control = self + .view + .require_control(root::ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL); + let text = self.mode.eel_control_transformation(); + control.set_text(extract_first_line(text)); + control.set_enabled(!has_multiple_lines(text)); + } + + fn invalidate_mode_eel_feedback_transformation_edit_control(&self, initiator: Option) { + if initiator == Some(root::ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL) { + return; + } + let control = self + .view + .require_control(root::ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL); + let text = if self.mode.feedback_type().is_textual() { + self.mode.textual_feedback_expression() + } else { + self.mode.eel_feedback_transformation() + }; + control.set_text(extract_first_line(text)); + control.set_enabled(!has_multiple_lines(text)); + } + + fn fill_source_category_combo_box(&self) { + let b = self + .view + .require_control(root::ID_SOURCE_CATEGORY_COMBO_BOX); + let is_main_mapping = self.mapping.compartment() == CompartmentKind::Main; + b.fill_combo_box_small( + SourceCategory::iter() + // Don't allow controller mappings to have virtual source + .filter(|c| is_main_mapping || *c != SourceCategory::Virtual), + ); + } + + fn fill_mapping_feedback_send_behavior_combo_box(&self) { + let b = self + .view + .require_control(root::ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX); + b.fill_combo_box_indexed(FeedbackSendBehavior::iter()); + } + + fn fill_target_category_combo_box(&self) { + let b = self + .view + .require_control(root::ID_TARGET_CATEGORY_COMBO_BOX); + b.fill_combo_box_indexed(TargetCategory::iter()); + } + + fn fill_source_type_combo_box(&self) { + let b = self.view.require_control(root::ID_SOURCE_TYPE_COMBO_BOX); + use SourceCategory::*; + match self.source.category() { + Midi => b.fill_combo_box_indexed(MidiSourceType::iter()), + Reaper => b.fill_combo_box_indexed(ReaperSourceType::iter()), + Virtual => b.fill_combo_box_indexed(VirtualControlElementCharacter::iter()), + Osc | Never | Keyboard | StreamDeck => {} + }; + } + + fn fill_mode_type_combo_box(&self) { + let base_input = self.mapping.base_mode_applicability_check_input(); + let relevant_source_characters = self.mapping.source_model.possible_detailed_characters(); + let items = AbsoluteMode::iter().map(|m| { + let applicable = self.mapping.mode_model.mode_parameter_is_relevant( + ModeParameter::SpecificAbsoluteMode(m), + base_input, + &relevant_source_characters, + true, + false, + ); + let suffix = if applicable { "" } else { " (NOT APPLICABLE)" }; + format!("{m}{suffix}") + }); + self.view + .require_control(root::ID_SETTINGS_MODE_COMBO_BOX) + .fill_combo_box_indexed(items); + } + + fn fill_mode_out_of_range_behavior_combo_box(&self) { + self.view + .require_control(root::ID_MODE_OUT_OF_RANGE_COMBOX_BOX) + .fill_combo_box_indexed(OutOfRangeBehavior::iter()); + } + + fn fill_mode_group_interaction_combo_box(&self) { + self.view + .require_control(root::ID_MODE_GROUP_INTERACTION_COMBO_BOX) + .fill_combo_box_indexed(GroupInteraction::iter()); + } + + fn fill_mode_fire_mode_combo_box(&self) { + self.view + .require_control(root::ID_MODE_FIRE_COMBO_BOX) + .fill_combo_box_indexed(FireMode::iter()); + } + + fn fill_mode_feedback_type_combo_box(&self) { + self.view + .require_control(root::IDC_MODE_FEEDBACK_TYPE_COMBO_BOX) + .fill_combo_box_indexed(FeedbackType::iter()); + } + + fn fill_mode_takeover_mode_combo_box(&self) { + self.view + .require_control(root::ID_MODE_TAKEOVER_MODE) + .fill_combo_box_indexed(TakeoverMode::iter()); + } + + fn fill_mode_button_usage_combo_box(&self) { + self.view + .require_control(root::ID_MODE_BUTTON_FILTER_COMBO_BOX) + .fill_combo_box_indexed(ButtonUsage::iter()); + } + + fn fill_mode_encoder_usage_combo_box(&self) { + self.view + .require_control(root::ID_MODE_RELATIVE_FILTER_COMBO_BOX) + .fill_combo_box_indexed(EncoderUsage::iter()); + } + + fn resolved_targets(&self) -> Vec { + self.target_with_context().resolve().unwrap_or_default() + } + + fn first_resolved_target(&self) -> Option { + self.resolved_targets().into_iter().next() + } +} + +impl View for MappingPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_MAPPING_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + self.init_controls(); + self.mapping_header_panel.clone().open(window); + if cfg!(unix) && BackboneShell::get().config().background_colors_enabled() { + self.mapping_color_panel.clone().open(window); + self.source_color_panel.clone().open(window); + self.target_color_panel.clone().open(window); + self.glue_color_panel.clone().open(window); + self.help_color_panel.clone().open(window); + } + self.ui_element_container + .borrow_mut() + .fill_with_window_children(window); + let dyn_self: Rc = self.clone(); + self.mapping_header_panel + .set_parent_view(Rc::downgrade(&dyn_self)); + true + } + + fn erase_background(self: SharedView, device_context: DeviceContext) -> bool { + if cfg!(unix) { + // On macOS/Linux we use color panels as real child windows. + return false; + } + if !BackboneShell::get().config().background_colors_enabled() { + return false; + } + let window = self.view.require_window(); + self.mapping_color_panel + .paint_manually(device_context, window); + self.source_color_panel + .paint_manually(device_context, window); + self.target_color_panel + .paint_manually(device_context, window); + self.glue_color_panel.paint_manually(device_context, window); + self.help_color_panel.paint_manually(device_context, window); + true + } + + fn control_color_static( + self: SharedView, + device_context: DeviceContext, + window: Window, + ) -> Option { + if cfg!(target_os = "macos") { + // On macOS, we fortunately don't need to do this nonsense. And it wouldn't be possible + // anyway because SWELL macOS can't distinguish between different child controls. + return None; + } + if !BackboneShell::get().config().background_colors_enabled() { + return None; + } + device_context.set_bk_mode_to_transparent(); + let section = Section::from_resource_id(window.resource_id())?; + view::get_brush_for_color_pair(section.color_pair()) + } + + fn close_requested(self: SharedView) -> bool { + self.hide(); + true + } + + fn on_destroy(self: SharedView, _window: Window) { + self.window_cache.replace(None); + self.ui_element_container.replace(Default::default()); + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Mapping + root::IDC_BEEP_ON_SUCCESS_CHECK_BOX => { + self.write(|p| p.update_beep_on_success()); + } + root::IDC_MAPPING_ENABLED_CHECK_BOX => { + self.write(|p| p.update_mapping_is_enabled()); + } + root::ID_MAPPING_SHOW_IN_PROJECTION_CHECK_BOX => { + self.write(|p| p.update_mapping_is_visible_in_projection()); + } + root::ID_MAPPING_ADVANCED_BUTTON => { + self.edit_advanced_settings(); + } + root::ID_MAPPING_FIND_IN_LIST_BUTTON => { + self.force_scroll_to_mapping_in_main_panel(); + } + // IDCANCEL is escape button + root::ID_MAPPING_PANEL_OK | raw::IDCANCEL => { + self.hide(); + } + // When pressing ENTER in text fields + raw::IDOK => { + // Ideally we would do the same here as the tab key. But since SWELL doesn't support + // GetNextDlgTabItem, doing this cross-platform would require a bit more effort. Whatever. + } + root::ID_MAPPING_PANEL_PREVIOUS_BUTTON => { + let _ = self.navigate_in_mappings(-1); + } + root::ID_MAPPING_PANEL_NEXT_BUTTON => { + let _ = self.navigate_in_mappings(1); + } + // Source + root::ID_SOURCE_LEARN_BUTTON => self.toggle_learn_source(), + root::ID_SOURCE_RPN_CHECK_BOX => { + self.write(|p| p.handle_source_line_4_check_box_change()) + } + root::ID_SOURCE_14_BIT_CHECK_BOX => { + self.write(|p| p.handle_source_check_box_2_change()) + } + root::ID_SOURCE_LINE_4_BUTTON => { + let _ = self.handle_source_line_4_button_press(); + } + root::ID_SOURCE_LINE_5_BUTTON => { + let _ = self.handle_source_line_5_button_press(); + } + root::ID_MODE_EEL_CONTROL_TRANSFORMATION_DETAIL_BUTTON => { + self.edit_control_transformation() + } + root::ID_SOURCE_SCRIPT_DETAIL_BUTTON => self.handle_source_line_7_button_press(), + // Mode + root::ID_SETTINGS_ROTATE_CHECK_BOX => self.write(|p| p.update_mode_rotate()), + root::ID_SETTINGS_MAKE_ABSOLUTE_CHECK_BOX => { + self.write(|p| p.update_mode_make_absolute()) + } + root::ID_SETTINGS_ROUND_TARGET_VALUE_CHECK_BOX => { + self.write(|p| p.update_mode_round_target_value()) + } + root::ID_SETTINGS_REVERSE_CHECK_BOX => self.write(|p| p.update_mode_reverse()), + root::ID_SETTINGS_RESET_BUTTON => self.write(|p| p.reset_mode()), + root::IDC_MODE_FEEDBACK_TYPE_BUTTON => { + let _ = self.feedback_type_button_pressed(); + } + // Target + root::ID_TARGET_TYPE_BUTTON => self.show_target_type_menu(), + root::ID_TARGET_CHECK_BOX_1 => self.write(|p| p.handle_target_check_box_1_change()), + root::ID_TARGET_CHECK_BOX_2 => self.write(|p| p.handle_target_check_box_2_change()), + root::ID_TARGET_CHECK_BOX_3 => self.write(|p| p.handle_target_check_box_3_change()), + root::ID_TARGET_CHECK_BOX_4 => self.write(|p| p.handle_target_check_box_4_change()), + root::ID_TARGET_CHECK_BOX_5 => self.write(|p| p.handle_target_check_box_5_change()), + root::ID_TARGET_CHECK_BOX_6 => self.write(|p| p.handle_target_check_box_6_change()), + root::ID_TARGET_LEARN_BUTTON => self.toggle_learn_target(), + root::ID_TARGET_MENU_BUTTON => self.open_target_menu(), + root::ID_TARGET_LINE_2_BUTTON => { + let _ = self.handle_target_line_2_button_press(); + } + root::ID_TARGET_LINE_3_BUTTON => { + let _ = self.handle_target_line_3_button_press(); + } + root::ID_TARGET_LINE_4_BUTTON => { + self.notify_user_on_anyhow_error(self.handle_target_line_4_button_press()); + } + root::ID_TARGET_VALUE_OFF_BUTTON => { + let _ = self.read(|p| p.hit_target_special(false)); + } + root::ID_TARGET_VALUE_ON_BUTTON => { + let _ = self.read(|p| p.hit_target_special(true)); + } + root::ID_TARGET_UNIT_BUTTON => self.write(|p| p.handle_target_unit_button_press()), + _ => {} + } + } + + fn option_selected(self: SharedView, resource_id: u32) { + match resource_id { + // Mapping + root::ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX => { + self.write(|p| p.update_mapping_feedback_send_behavior()) + } + // Source + root::ID_SOURCE_CATEGORY_COMBO_BOX => self.write(|p| p.update_source_category()), + root::ID_SOURCE_TYPE_COMBO_BOX => { + self.write(|p| p.handle_source_line_2_combo_box_change()) + } + root::ID_SOURCE_CHANNEL_COMBO_BOX => { + self.write(|p| p.handle_source_line_3_combo_box_change()) + } + root::ID_SOURCE_NUMBER_COMBO_BOX => { + self.write(|p| p.handle_source_line_4_combo_box_2_change()) + } + root::ID_SOURCE_LINE_4_COMBO_BOX_1 => { + self.write(|p| p.handle_source_line_4_combo_box_1_change()) + } + root::ID_SOURCE_CHARACTER_COMBO_BOX => { + self.write(|p| p.handle_source_line_5_combo_box_change()) + } + // Mode + root::ID_SETTINGS_MODE_COMBO_BOX => self.write(|p| p.update_mode_type()), + root::ID_MODE_OUT_OF_RANGE_COMBOX_BOX => { + self.write(|p| p.update_mode_out_of_range_behavior()) + } + root::ID_MODE_GROUP_INTERACTION_COMBO_BOX => { + self.write(|p| p.update_mode_group_interaction()) + } + root::ID_MODE_TAKEOVER_MODE => self.write(|p| p.update_takeover_mode()), + root::ID_MODE_BUTTON_FILTER_COMBO_BOX => self.write(|p| p.update_button_usage()), + root::ID_MODE_RELATIVE_FILTER_COMBO_BOX => self.write(|p| p.update_encoder_usage()), + root::ID_MODE_FIRE_COMBO_BOX => self.write(|p| p.update_mode_fire_mode()), + root::IDC_MODE_FEEDBACK_TYPE_COMBO_BOX => self.write(|p| p.update_mode_feedback_type()), + // Target + root::ID_TARGET_CATEGORY_COMBO_BOX => self.write(|p| p.update_target_category()), + root::ID_TARGET_LINE_2_COMBO_BOX_1 => { + self.write(|p| p.handle_target_line_2_combo_box_1_change()) + } + root::ID_TARGET_LINE_2_COMBO_BOX_2 => { + self.write(|p| p.handle_target_line_2_combo_box_2_change()) + } + root::ID_TARGET_LINE_3_COMBO_BOX_1 => { + self.write(|p| p.handle_target_line_3_combo_box_1_change()); + } + root::ID_TARGET_LINE_3_COMBO_BOX_2 => { + self.write(|p| p.handle_target_line_3_combo_box_2_change()); + } + root::ID_TARGET_LINE_4_COMBO_BOX_1 => { + self.write(|p| p.handle_target_line_4_combo_box_1_change()) + } + root::ID_TARGET_LINE_4_COMBO_BOX_2 => { + self.write(|p| p.handle_target_line_4_combo_box_2_change()) + } + _ => {} + } + } + + fn slider_moved(self: SharedView, slider: Window) { + let cloned_self = self.clone(); + let sliders = cloned_self.window_cache.borrow(); + let sliders = sliders.as_ref().expect("sliders not set"); + match slider { + // Mode + s if s == sliders.mode_min_target_value => { + self.write(|p| p.update_mode_min_target_value_from_slider(s)); + } + s if s == sliders.mode_max_target_value => { + self.write(|p| p.update_mode_max_target_value_from_slider(s)); + } + s if s == sliders.mode_min_source_value => { + self.write(|p| p.update_mode_min_source_value_from_slider(s)); + } + s if s == sliders.mode_max_source_value => { + self.write(|p| p.update_mode_max_source_value_from_slider(s)); + } + s if s == sliders.mode_min_step_size => { + self.write(|p| p.update_mode_min_step_from_slider(s)); + } + s if s == sliders.mode_max_step_size => { + self.write(|p| p.update_mode_max_step_from_slider(s)); + } + s if s == sliders.mode_fire_line_2 => { + self.write(|p| p.handle_mode_fire_line_2_slider_change(s)); + } + s if s == sliders.mode_fire_line_3 => { + self.write(|p| p.handle_mode_fire_line_3_slider_change(s)); + } + s if s == sliders.target_value => { + let _ = self.read(|p| p.hit_target(s.slider_unit_value())); + } + _ => {} + }; + } + + fn edit_control_changed(self: SharedView, resource_id: u32) -> bool { + if self.is_invoked_programmatically() { + // We don't want to continue if the edit control change was not caused by the user. + // Although the edit control text is changed programmatically, it also triggers the + // change handler. Ignore it! Most of those events are filtered out already + // by the dialog proc reentrancy check, but this one is not because the + // dialog proc is not reentered - we are just reacting (async) to a change. + return false; + } + let view = if cfg!(target_os = "linux") { + // On Linux we handle the change immediately because SWELL on Linux doesn't support + // notification on focus kill. + match self.handle_potential_min_max_edit_control_change(resource_id) { + // Processed + None => return true, + // Not processed + Some(v) => v, + } + } else { + // On macOS and Windows we don't update the min/max values instantly but when leaving + // the edit field. This prevents annoying too clever behavior where e.g. entering the + // max value would "fix" a previously entered min value too soon. + self + }; + match resource_id { + // Source + root::ID_SOURCE_LINE_3_EDIT_CONTROL => { + view.write(|p| p.handle_source_line_3_edit_control_change()); + } + root::ID_SOURCE_NUMBER_EDIT_CONTROL => { + view.write(|p| p.handle_source_line_4_edit_control_change()); + } + root::ID_SOURCE_LINE_5_EDIT_CONTROL => { + view.write(|p| p.handle_source_line_5_edit_control_change()); + } + root::ID_SOURCE_OSC_ADDRESS_PATTERN_EDIT_CONTROL => { + view.write(|p| p.handle_source_line_7_edit_control_change()); + } + // Mode + root::ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL => { + view.write(|p| p.update_mode_target_value_sequence()); + } + root::ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL => { + view.write(|p| p.update_mode_eel_control_transformation()); + } + root::ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL => { + view.write(|p| p.update_mode_feedback_transformation()); + } + // Target + root::ID_TARGET_LINE_2_EDIT_CONTROL => { + view.write(|p| p.handle_target_line_2_edit_control_change()) + } + root::ID_TARGET_LINE_3_EDIT_CONTROL => { + view.write(|p| p.handle_target_line_3_edit_control_change()) + } + root::ID_TARGET_LINE_4_EDIT_CONTROL => { + view.write(|p| p.handle_target_line_4_edit_control_change()) + } + root::ID_TARGET_LINE_5_EDIT_CONTROL => { + view.write(|p| p.handle_target_line_5_edit_control_change()) + } + root::ID_TARGET_VALUE_EDIT_CONTROL => { + let value = view.clone().write(|p| { + p.get_value_from_target_edit_control(root::ID_TARGET_VALUE_EDIT_CONTROL) + .unwrap_or(UnitValue::MIN) + }); + let _ = view.read(|p| p.hit_target(value)); + } + _ => return false, + }; + true + } + + // This is not called on Linux anyway, so this guard is just for making sure that nothing breaks + // or is done two times if SWELL supports focus kill notification at some point on Linux. + #[cfg(not(target_os = "linux"))] + fn edit_control_focus_killed(self: SharedView, resource_id: u32) -> bool { + if self.is_invoked_programmatically() { + return false; + } + if self.session().try_borrow_mut().is_err() { + // This happens e.g. when the edit control had focus while removing the mapping. + return false; + } + let view = self.clone(); + self.handle_potential_min_max_edit_control_change(resource_id); + // This is also called when the window is hidden. + // The edit control which is currently edited by the user doesn't get invalidated during + // `edit_control_changed()`, for good reasons. But as soon as the edit control loses + // focus, we should invalidate it. This is especially important if the user + // entered an invalid value. Because we are lazy and edit controls are not + // manipulated very frequently, we just invalidate all controls. + // If this fails (because the mapping is not filled anymore), it's not a problem. + let _ = view.read(|p| { + p.invalidate_all_controls(); + }); + false + } + + fn timer(&self, id: usize) -> bool { + if id == SOURCE_MATCH_INDICATOR_TIMER_ID { + self.view + .require_window() + .kill_timer(SOURCE_MATCH_INDICATOR_TIMER_ID); + self.source_match_indicator_control().disable(); + true + } else { + false + } + } + + fn mouse_test(self: SharedView, position: Point) -> bool { + let position = self.view.require_window().screen_to_client_point(position); + let container = self.ui_element_container.borrow(); + let mut resource_ids = container.hit_test(Point::new(position.x, position.y)); + let resource_id = resource_ids.find(|id| !NO_HELP_ELEMENTS.contains(id)); + self.handle_hovered_ui_element(resource_id); + false + } + + fn help_requested(&self) -> bool { + self.open_detailed_help_for_current_ui_element().is_some() + } +} + +const SOURCE_MATCH_INDICATOR_TIMER_ID: usize = 570; + +trait WindowExt { + fn slider_unit_value(&self) -> UnitValue; + fn slider_discrete_increment(&self) -> DiscreteIncrement; + fn slider_duration(&self) -> Duration; + fn set_slider_unit_value(&self, value: UnitValue); + fn set_slider_discrete_increment(&self, increment: DiscreteIncrement); + fn set_slider_duration(&self, value: Duration); +} + +impl WindowExt for Window { + fn slider_unit_value(&self) -> UnitValue { + let discrete_value = self.slider_value(); + UnitValue::new_clamped(discrete_value as f64 / 100.0) + } + + fn slider_discrete_increment(&self) -> DiscreteIncrement { + let discrete_value = self.slider_value() as i32 * 2 - 100; + discrete_value + .try_into() + .unwrap_or(DiscreteIncrement::POSITIVE_MIN) + } + + fn slider_duration(&self) -> Duration { + let discrete_value = self.slider_value(); + Duration::from_millis((discrete_value * 50) as _) + } + + fn set_slider_unit_value(&self, value: UnitValue) { + // TODO-low Refactor that map_to_interval stuff to be more generic and less boilerplate + self.set_slider_range(0, 100); + let val = (value.get() * 100.0).round() as u32; + self.set_slider_value(val); + } + + fn set_slider_discrete_increment(&self, increment: DiscreteIncrement) { + let val = cmp::max(0, (increment.get() + 100) / 2) as u32; + self.set_slider_value(val); + } + + fn set_slider_duration(&self, value: Duration) { + // 0 = 0ms, 1 = 50ms, ..., 100 = 5s + self.set_slider_range(0, 100); + let val = (value.as_millis() / 50) as u32; + self.set_slider_value(val); + } +} + +fn group_mappings_by_virtual_control_element<'a>( + mappings: impl Iterator, +) -> NonCryptoHashMap> { + let key_fn = |m: &SharedMapping| { + let m = m.borrow(); + match m.target_model.category() { + TargetCategory::Reaper => None, + TargetCategory::Virtual => Some(m.target_model.create_control_element()), + } + }; + // Group by Option + let grouped_by_option = mappings + .sorted_by_key(|m| key_fn(m)) + .group_by(|m| key_fn(m)); + // Filter out None keys and collect to map with vector values + grouped_by_option + .into_iter() + .filter_map(|(key, group)| key.map(|k| (k, group.collect()))) + .collect() +} + +#[allow(clippy::too_many_arguments)] +fn invalidate_target_controls_free( + real_target: Option<&CompoundMappingTarget>, + slider_control: Window, + edit_control: Window, + value_text_control: Window, + value: AbsoluteValue, + initiator: Option, + edit_control_id: u32, + set_text_only_if_edit_control_not_focused: bool, + // Whether to display/enter step sizes instead of absolute values. + use_step_sizes: bool, + unit: TargetUnit, + control_context: ControlContext, +) { + // TODO-high-discrete Handle discrete value in a better way. + let value = value.to_unit_value(); + let (edit_text, value_text) = match real_target { + Some(target) => match unit { + TargetUnit::Native => { + if target.character(control_context) == TargetCharacter::Discrete { + let edit_text = target + .convert_unit_value_to_discrete_value(value, control_context) + .map(|v| v.to_string()) + .unwrap_or_else(|_| "".to_string()); + ( + edit_text, + get_text_right_to_target_edit_control(target, value, control_context), + ) + } else if use_step_sizes { + ( + target.format_step_size_without_unit(value, control_context), + get_text_right_to_step_size_edit_control(target, value, control_context), + ) + } else { + ( + target.format_value_without_unit(value, control_context), + get_text_right_to_target_edit_control(target, value, control_context), + ) + } + } + TargetUnit::Percent => (format_percentage_without_unit(value.get()), "%".to_owned()), + }, + None => ("".to_string(), "".to_string()), + }; + slider_control.set_slider_unit_value(value); + // Value edit control + if initiator != Some(edit_control_id) + && (!set_text_only_if_edit_control_not_focused || !edit_control.has_focus()) + { + edit_control.set_text(edit_text); + } + // Value label + value_text_control.set_text(value_text); +} + +fn get_text_right_to_target_edit_control( + t: &CompoundMappingTarget, + value: UnitValue, + control_context: ControlContext, +) -> String { + if t.hide_formatted_value(control_context) { + t.value_unit(control_context).to_string() + } else if t.character(control_context) == TargetCharacter::Discrete { + // Please note that discrete FX parameters can only show their *current* value, + // unless they implement the REAPER VST extension functions. + t.format_value(value, control_context) + } else { + format!( + "{} {}", + t.value_unit(control_context), + t.format_value(value, control_context) + ) + } +} + +fn get_text_right_to_step_size_edit_control( + t: &CompoundMappingTarget, + step_size: UnitValue, + control_context: ControlContext, +) -> String { + if t.hide_formatted_step_size(control_context) { + t.step_size_unit(control_context).to_string() + } else { + format!( + "{} {}", + t.step_size_unit(control_context), + t.format_step_size_without_unit(step_size, control_context) + ) + } +} + +fn track_combo_box_entries(project: Project) -> impl ExactSizeIterator { + let mut current_folder_level: i32 = 0; + project.tracks().enumerate().map(move |(i, track)| { + let indentation = ".".repeat(current_folder_level.unsigned_abs() as usize * 4); + let space = if indentation.is_empty() { "" } else { " " }; + let name = track.name().expect("non-master track must have name"); + let label = format!("{}. {}{}{}", i + 1, indentation, space, name.to_str()); + current_folder_level += track.folder_depth_change(); + label + }) +} + +fn fx_combo_box_entries(chain: &FxChain) -> impl ExactSizeIterator + '_ { + chain + .fxs() + .enumerate() + .map(|(i, fx)| get_fx_label(i as u32, &fx)) +} + +fn send_combo_box_entries(track: &Track, route_type: TrackRouteType) -> Vec { + match route_type { + TrackRouteType::Send => track + .typed_sends(SendPartnerType::Track) + .map(|route| get_route_label(&route)) + .collect(), + TrackRouteType::Receive => track + .receives() + .map(|route| get_route_label(&route)) + .collect(), + TrackRouteType::HardwareOutput => track + .typed_sends(SendPartnerType::HardwareOutput) + .map(|route| get_route_label(&route)) + .collect(), + } +} + +fn fx_parameter_combo_box_entries(fx: &Fx) -> impl ExactSizeIterator + '_ { + fx.parameters() + .map(|param| get_fx_param_label(Some(¶m), param.index()).to_string()) +} + +fn bookmark_combo_box_entries( + project: Project, + bookmark_type: BookmarkType, +) -> impl Iterator { + project + .bookmarks() + .map(|b| (b, b.basic_info())) + .filter(move |(_, info)| info.bookmark_type() == bookmark_type) + .map(|(b, info)| { + let name = b.name(); + let label = get_bookmark_label_by_id(info.bookmark_type(), info.id, &name); + (info.id.get() as isize, label) + }) +} + +fn select_bookmark_in_combo_box(combo: Window, anchor_type: BookmarkAnchorType, bookmark_ref: u32) { + let successful = match anchor_type { + BookmarkAnchorType::Id => combo + .select_combo_box_item_by_data(bookmark_ref as _) + .is_ok(), + BookmarkAnchorType::Index => combo + .select_combo_box_item_by_index_checked(bookmark_ref as _) + .is_ok(), + }; + if !successful { + combo.select_new_combo_box_item( + get_non_present_bookmark_label(anchor_type, bookmark_ref).as_str(), + ); + } +} + +fn invalidate_target_line_2_expression_result( + target: &TargetModel, + context: ExtendedProcessorContext, + label: Window, + compartment: CompartmentKind, +) { + let text = match target.category() { + TargetCategory::Reaper => { + if target.target_type().supports_track() && target.track_type().is_dynamic() { + target + .virtual_track() + .and_then(|t| t.calculated_track_index(context, compartment)) + .map(|i| i.to_string()) + } else { + None + } + } + TargetCategory::Virtual => None, + }; + label.set_text_or_hide(text); +} + +fn invalidat_target_line_3_expression_result( + target: &TargetModel, + context: ExtendedProcessorContext, + label: Window, + compartment: CompartmentKind, +) { + let text = match target.category() { + TargetCategory::Reaper => { + if target.target_type().supports_fx() && target.fx_type() == VirtualFxType::Dynamic { + target + .with_context(context, compartment) + .first_fx_chain() + .ok() + .and_then(|chain| { + target + .virtual_chain_fx() + .and_then(|fx| fx.calculated_fx_index(context, compartment, &chain)) + .map(|i| i.to_string()) + }) + } else { + None + } + } + TargetCategory::Virtual => None, + }; + label.set_text_or_hide(text); +} + +fn invalidate_target_line_4_expression_result( + target: &TargetModel, + context: ExtendedProcessorContext, + label: Window, + compartment: CompartmentKind, +) { + let text = match target.category() { + TargetCategory::Reaper => match target.target_type() { + t if t.supports_fx_parameter() + && target.param_type() == VirtualFxParameterType::Dynamic => + { + target + .with_context(context, compartment) + .first_fx() + .ok() + .and_then(|fx| { + target + .virtual_fx_parameter() + .and_then(|p| { + p.calculated_fx_parameter_index(context, compartment, &fx) + }) + .map(|i| i.to_string()) + }) + } + t if t.supports_send() + && target.route_selector_type() == TrackRouteSelectorType::Dynamic => + { + target + .track_route_selector() + .and_then(|p| p.calculated_route_index(context, compartment)) + .map(|i| i.to_string()) + } + _ => None, + }, + TargetCategory::Virtual => None, + }; + label.set_text_or_hide(text); +} + +fn parse_position_as_index(edit_control: Window) -> u32 { + let position: i32 = edit_control + .text() + .ok() + .and_then(|text| text.parse().ok()) + .unwrap_or(1); + std::cmp::max(position - 1, 0) as u32 +} + +fn chunked_number_menu( + count: u32, + batch_size: u32, + format_one_rooted: bool, + f: impl Fn(u32) -> swell_ui::menu_tree::Entry, +) -> Vec> { + use swell_ui::menu_tree::*; + (0..count / batch_size) + .map(|batch_index| { + let offset = batch_index * batch_size; + let range = offset..(offset + batch_size); + menu( + format!( + "{} - {}", + if format_one_rooted { + range.start + 1 + } else { + range.start + }, + if format_one_rooted { + range.end + } else { + range.end - 1 + } + ), + range.map(&f).collect(), + ) + }) + .collect() +} + +fn channel_menu(f: impl Fn(u8) -> R) -> Vec { + (0..16).map(&f).collect() +} + +fn invalidate_with_osc_arg_type_tag(b: Window, tag: OscTypeTag) { + b.fill_combo_box_indexed(OscTypeTag::iter()); + b.show(); + b.select_combo_box_item_by_index(tag.into()); +} + +fn invalidate_with_osc_arg_index(combo: Window, index: Option) { + combo.fill_combo_box_with_data_small(osc_arg_indexes()); + combo.show(); + let data = index.map(|i| i as isize).unwrap_or(-1); + combo.select_combo_box_item_by_data(data).unwrap(); +} + +fn get_osc_type_tag_from_combo(combo: Window) -> OscTypeTag { + let i = combo.selected_combo_box_item_index(); + i.try_into().expect("invalid OSC type tag") +} + +fn get_osc_arg_index_from_combo(combo: Window) -> Option { + match combo.selected_combo_box_item_data() { + -1 => None, + i => Some(i as u32), + } +} + +fn osc_arg_indexes() -> impl Iterator { + iter::once((-1isize, "-".to_string())).chain((0..9).map(|i| (i as isize, (i + 1).to_string()))) +} + +fn pick_virtual_control_element( + window: Window, + character: VirtualControlElementCharacter, + grouped_mappings: &NonCryptoHashMap>, + for_control: bool, + for_feedback: bool, +) -> Option { + let pure_menu = { + use swell_ui::menu_tree::*; + let daw_control_names: Vec<_> = + control_element_domains::daw::PREDEFINED_VIRTUAL_BUTTON_NAMES + .iter() + .chain(control_element_domains::daw::PREDEFINED_VIRTUAL_MULTI_NAMES.iter()) + .copied() + .collect(); + let grid_control_names: Vec<_> = + control_element_domains::grid::PREDEFINED_VIRTUAL_BUTTON_NAMES + .iter() + .chain(control_element_domains::grid::PREDEFINED_VIRTUAL_MULTI_NAMES.iter()) + .copied() + .collect(); + + let include_actual_element = |mappings: &[&SharedMapping]| -> bool { + mappings.iter().any(|m| { + let m = m.borrow(); + if !m.is_enabled() { + return false; + } + for_control && m.control_is_enabled() || for_feedback && m.feedback_is_enabled() + }) + }; + let currently_available_elements: Vec<_> = grouped_mappings + .iter() + .filter(|(e, mappings)| match e { + VirtualControlElement::Indexed { character: ch, .. } => { + *ch == character && include_actual_element(mappings) + } + VirtualControlElement::Named { .. } => include_actual_element(mappings), + }) + .map(|(e, _)| e.id().to_string()) + .collect(); + let entries = vec![ + menu( + "", + build_slash_menu_entries(¤tly_available_elements, ""), + ), + menu( + "DAW control", + build_slash_menu_entries(&daw_control_names, ""), + ), + menu("Grid", build_slash_menu_entries(&grid_control_names, "")), + menu( + "Numbered", + chunked_number_menu(100, 10, true, |i| { + let label = { + let pos = i + 1; + let element = VirtualControlElement::new( + VirtualControlElementId::Indexed(i), + character, + ); + match grouped_mappings.get(&element) { + None => pos.to_string(), + Some(mappings) => { + let first_mapping = mappings[0].borrow(); + let first_mapping_name = first_mapping.effective_name(); + if mappings.len() == 1 { + format!("{pos} ({first_mapping_name})") + } else { + format!( + "{} ({} + {})", + pos, + first_mapping_name, + mappings.len() - 1 + ) + } + } + } + }; + item(label, (i + 1).to_string()) + }), + ), + ]; + anonymous_menu(entries) + }; + window.open_popup_menu(pure_menu, Window::cursor_pos()) +} + +#[derive(Copy, Clone, Display)] +enum ColorTarget { + #[display(fmt = "Color (if supported)")] + Color, + #[display(fmt = "Background color (if supported)")] + BackgroundColor, +} + +enum FeedbackPopupMenuResult { + EditMultiLine, + ChangeColor(ChangeColorInstruction), +} + +struct ChangeColorInstruction { + target: ColorTarget, + color: Option, +} + +impl ChangeColorInstruction { + fn new(target: ColorTarget, color: Option) -> Self { + Self { target, color } + } +} + +fn show_feedback_popup_menu( + window: Window, + current_color: Option, + current_background_color: Option, +) -> Result { + enum MenuAction { + ControllerDefault(ColorTarget), + OpenColorPicker(ColorTarget), + UseColorProp(ColorTarget, &'static str), + EditMultiLine, + } + let pure_menu = { + use swell_ui::menu_tree::*; + use MenuAction::*; + let create_color_target_menu = |color_target: ColorTarget| { + let relevant_color = match color_target { + ColorTarget::Color => ¤t_color, + ColorTarget::BackgroundColor => ¤t_background_color, + }; + menu( + color_target.to_string(), + [ + item_with_opts( + "", + ItemOpts { + enabled: true, + checked: relevant_color.is_none(), + }, + ControllerDefault(color_target), + ), + item_with_opts( + "", + ItemOpts { + enabled: true, + checked: matches!(relevant_color, Some(VirtualColor::Rgb(_))), + }, + OpenColorPicker(color_target), + ), + ].into_iter() + .chain(["target.track.color", "target.bookmark.color", "target.slot.color"].into_iter().map(|key| { + item_with_opts( + key, + ItemOpts { + enabled: true, + checked: { + matches!(relevant_color, Some(VirtualColor::Prop{prop}) if key == prop) + }, + }, + UseColorProp(color_target, key), + ) + })) + .collect(), + ) + }; + let entries = vec![ + item("Edit multi-line...", MenuAction::EditMultiLine), + create_color_target_menu(ColorTarget::Color), + create_color_target_menu(ColorTarget::BackgroundColor), + ]; + anonymous_menu(entries) + }; + let item = window + .open_popup_menu(pure_menu, Window::cursor_pos()) + .ok_or("color selection cancelled")?; + let result = match item { + MenuAction::EditMultiLine => FeedbackPopupMenuResult::EditMultiLine, + MenuAction::ControllerDefault(target) => { + let instruction = ChangeColorInstruction::new(target, None); + FeedbackPopupMenuResult::ChangeColor(instruction) + } + MenuAction::UseColorProp(target, key) => { + let instruction = ChangeColorInstruction::new( + target, + Some(VirtualColor::Prop { + prop: key.to_string(), + }), + ); + FeedbackPopupMenuResult::ChangeColor(instruction) + } + MenuAction::OpenColorPicker(color_target) => { + let relevant_color = match color_target { + ColorTarget::Color => ¤t_color, + ColorTarget::BackgroundColor => ¤t_background_color, + }; + let reaper = Reaper::get().medium_reaper(); + let current_color = if let Some(VirtualColor::Rgb(c)) = relevant_color { + reaper.color_to_native(reaper_medium::RgbColor::rgb(c.r(), c.g(), c.b())) + } else { + NativeColor::default() + }; + if let Some(native_color) = + reaper.gr_select_color(WindowContext::Win(window.raw_hwnd()), current_color) + { + let reaper_medium::RgbColor { r, g, b } = reaper.color_from_native(native_color); + let instruction = ChangeColorInstruction::new( + color_target, + Some(VirtualColor::Rgb(RgbColor::new(r, g, b))), + ); + FeedbackPopupMenuResult::ChangeColor(instruction) + } else { + return Err("color picking cancelled"); + } + } + }; + Ok(result) +} + +enum SendMidiMenuAction { + EditMultiLine, + SingleDataBytePreset { + channel: u8, + msg_type: ShortMessageType, + last_byte: u8, + }, + DoubleDataBytePreset { + channel: u8, + msg_type: ShortMessageType, + i: u32, + }, + PitchBendChangePreset { + channel: u8, + }, +} + +fn open_send_midi_menu(window: Window) -> Option { + fn fmt_ch(ch: u8) -> String { + format!("Channel {}", ch + 1) + } + fn double_data_byte_msg_menu( + source_type: MidiSourceType, + msg_type: ShortMessageType, + label: &str, + ) -> swell_ui::menu_tree::Entry { + use swell_ui::menu_tree::*; + menu( + source_type.to_string(), + channel_menu(|ch| { + menu( + fmt_ch(ch), + chunked_number_menu(128, 8, false, |i| { + item( + format!("{label} {i}"), + SendMidiMenuAction::DoubleDataBytePreset { + channel: ch, + msg_type, + i, + }, + ) + }), + ) + }), + ) + } + + fn single_data_byte_msg_menu( + source_type: MidiSourceType, + msg_type: ShortMessageType, + last_byte: u8, + ) -> swell_ui::menu_tree::Entry { + use swell_ui::menu_tree::*; + menu( + source_type.to_string(), + channel_menu(|ch| { + item( + fmt_ch(ch), + SendMidiMenuAction::SingleDataBytePreset { + channel: ch, + msg_type, + last_byte, + }, + ) + }), + ) + } + + let pure_menu = { + use swell_ui::menu_tree::*; + + use SendMidiMenuAction::*; + let entries = vec![ + item("Edit multi-line...", EditMultiLine), + double_data_byte_msg_menu( + MidiSourceType::ControlChangeValue, + ShortMessageType::ControlChange, + "CC", + ), + double_data_byte_msg_menu( + MidiSourceType::NoteVelocity, + ShortMessageType::NoteOn, + "Note", + ), + single_data_byte_msg_menu(MidiSourceType::NoteKeyNumber, ShortMessageType::NoteOn, 127), + menu( + MidiSourceType::PitchBendChangeValue.to_string(), + channel_menu(|ch| { + item( + fmt_ch(ch), + SendMidiMenuAction::PitchBendChangePreset { channel: ch }, + ) + }), + ), + single_data_byte_msg_menu( + MidiSourceType::ChannelPressureAmount, + ShortMessageType::ChannelPressure, + 0, + ), + single_data_byte_msg_menu( + MidiSourceType::ProgramChangeNumber, + ShortMessageType::ProgramChange, + 0, + ), + double_data_byte_msg_menu( + MidiSourceType::PolyphonicKeyPressureAmount, + ShortMessageType::PolyphonicKeyPressure, + "Note", + ), + ]; + anonymous_menu(entries) + }; + window.open_popup_menu(pure_menu, Window::cursor_pos()) +} + +fn build_slash_menu_entries( + names: &[impl AsRef], + prefix: &str, +) -> Vec> { + use swell_ui::menu_tree::*; + let mut entries = Vec::new(); + for (key, group) in &names + .iter() + .map(|e| e.as_ref()) + .sorted() + .group_by(|name| extract_first_segment(name)) + { + if key.is_empty() { + // All non-nested entries on this level (items). + for name in group { + let full_name = if prefix.is_empty() { + name.to_string() + } else { + format!("{prefix}/{name}") + }; + entries.push(item(name, full_name)); + } + } else { + // A nested entry (menu). + let remaining_names: Vec<_> = group.map(extract_remaining_name).collect(); + let new_prefix = if prefix.is_empty() { + key.to_string() + } else { + format!("{prefix}/{key}") + }; + let inner_entries = build_slash_menu_entries(&remaining_names, &new_prefix); + entries.push(menu(key, inner_entries)); + } + } + entries +} + +fn extract_first_segment(text: &str) -> &str { + if let Some(slash_index) = text.find('/') { + &text[0..slash_index] + } else { + "" + } +} + +fn extract_remaining_name(text: &str) -> &str { + if let Some(slash_index) = text.find('/') { + &text[slash_index + 1..] + } else { + text + } +} + +fn format_osc_arg_value_range(range: Interval, type_tag: OscTypeTag) -> String { + if type_tag.is_discrete() { + format!("{:.0} - {:.0}", range.min_val(), range.max_val()) + } else { + format!("{:.4} - {:.4}", range.min_val(), range.max_val()) + } +} + +fn parse_osc_arg_value_range(text: &str) -> Interval { + use nom::character::complete::space0; + use nom::number::complete::double; + use nom::sequence::separated_pair; + use nom::{character::complete::char, sequence::tuple, IResult}; + fn parse_range(input: &str) -> IResult<&str, Interval> { + let mut parser = separated_pair(double, tuple((space0, char('-'), space0)), double); + let (remainder, (from, to)) = parser(input)?; + Ok((remainder, Interval::new_auto(from, to))) + } + parse_range(text) + .map(|r| r.1) + .unwrap_or(DEFAULT_OSC_ARG_VALUE_RANGE) +} + +fn extract_first_line(text: &str) -> &str { + text.lines().next().unwrap_or_default() +} + +fn has_multiple_lines(text: &str) -> bool { + text.lines().count() > 1 +} + +fn get_relevant_target_fx(mapping: &MappingModel, session: &UnitModel) -> Option { + let is_focused_fx_type = { + let fx_type = mapping.target_model.fx_type(); + if fx_type == VirtualFxType::Unit { + session.instance_fx_descriptor() == &FxDescriptor::Focused + } else { + fx_type == VirtualFxType::Focused + } + }; + if is_focused_fx_type { + // We need to choose the last focused FX, even if it's not focused anymore. See + // https://github.com/helgoboss/helgobox/issues/778. + let containing_fx = session.control_context().processor_context.containing_fx(); + Backbone::get().last_relevant_available_focused_fx(containing_fx) + } else { + // Choose whatever FX the selector currently resolves to + mapping + .target_model + .with_context(session.extended_context(), mapping.compartment()) + .first_fx() + .ok() + } +} + +const ACTIVITY_INFO: &str = "Activity info"; + +enum Side { + Left, + Right, +} + +fn build_mapping_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 0, + y: 0, + width: 451, + height: 67, + color_pair: colors::mapping(), + scaling: MAPPING_PANEL_SCALING, + } +} + +fn build_source_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 0, + y: 67, + width: 175, + height: 165, + color_pair: colors::source(), + scaling: MAPPING_PANEL_SCALING, + } +} + +fn build_target_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 175, + y: 67, + width: 276, + height: 165, + color_pair: colors::target(), + scaling: MAPPING_PANEL_SCALING, + } +} + +fn build_glue_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 0, + y: 232, + width: 451, + height: 239, + color_pair: colors::glue(), + scaling: MAPPING_PANEL_SCALING, + } +} + +fn build_help_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 0, + y: 471, + width: 451, + height: 61, + color_pair: colors::help(), + scaling: MAPPING_PANEL_SCALING, + } +} + +enum Section { + Mapping, + Source, + Target, + Glue, + Help, +} + +impl Section { + pub fn from_resource_id(id: u32) -> Option { + use root::*; + let section = match id { + ID_MAPPING_PANEL_MAPPING_LABEL + | ID_MAPPING_PANEL_FEEDBACK_LABEL + | ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX + | ID_MAPPING_SHOW_IN_PROJECTION_CHECK_BOX + | ID_MAPPING_ADVANCED_BUTTON + | ID_MAPPING_FIND_IN_LIST_BUTTON => Self::Mapping, + ID_MAPPING_PANEL_SOURCE_LABEL + | ID_SOURCE_LEARN_BUTTON + | ID_MAPPING_PANEL_SOURCE_CATEGORY_LABEL + | ID_SOURCE_CATEGORY_COMBO_BOX + | ID_SOURCE_TYPE_LABEL_TEXT + | ID_SOURCE_TYPE_COMBO_BOX + | ID_SOURCE_MIDI_MESSAGE_TYPE_LABEL_TEXT + | ID_SOURCE_CHANNEL_LABEL + | ID_SOURCE_CHANNEL_COMBO_BOX + | ID_SOURCE_LINE_3_EDIT_CONTROL + | ID_SOURCE_NOTE_OR_CC_NUMBER_LABEL_TEXT + | ID_SOURCE_RPN_CHECK_BOX + | ID_SOURCE_LINE_4_COMBO_BOX_1 + | ID_SOURCE_NUMBER_EDIT_CONTROL + | ID_SOURCE_NUMBER_COMBO_BOX + | ID_SOURCE_LINE_4_BUTTON + | ID_SOURCE_CHARACTER_LABEL_TEXT + | ID_SOURCE_CHARACTER_COMBO_BOX + | ID_SOURCE_LINE_5_EDIT_CONTROL + | ID_SOURCE_14_BIT_CHECK_BOX + | ID_SOURCE_OSC_ADDRESS_LABEL_TEXT + | ID_SOURCE_OSC_ADDRESS_PATTERN_EDIT_CONTROL + | ID_SOURCE_SCRIPT_DETAIL_BUTTON => Self::Source, + ID_MAPPING_PANEL_TARGET_LABEL + | ID_TARGET_LEARN_BUTTON + | ID_TARGET_MENU_BUTTON + | ID_TARGET_HINT + | ID_MAPPING_PANEL_TARGET_TYPE_LABEL + | ID_TARGET_CATEGORY_COMBO_BOX + | ID_TARGET_TYPE_BUTTON + | ID_TARGET_LINE_2_LABEL_2 + | ID_TARGET_LINE_2_LABEL_3 + | ID_TARGET_LINE_2_LABEL_1 + | ID_TARGET_LINE_2_COMBO_BOX_1 + | ID_TARGET_LINE_2_EDIT_CONTROL + | ID_TARGET_LINE_2_COMBO_BOX_2 + | ID_TARGET_LINE_2_BUTTON + | ID_TARGET_LINE_3_LABEL_1 + | ID_TARGET_LINE_3_COMBO_BOX_1 + | ID_TARGET_LINE_3_EDIT_CONTROL + | ID_TARGET_LINE_3_COMBO_BOX_2 + | ID_TARGET_LINE_3_LABEL_2 + | ID_TARGET_LINE_3_LABEL_3 + | ID_TARGET_LINE_3_BUTTON + | ID_TARGET_LINE_4_LABEL_1 + | ID_TARGET_LINE_4_COMBO_BOX_1 + | ID_TARGET_LINE_4_EDIT_CONTROL + | ID_TARGET_LINE_4_COMBO_BOX_2 + | ID_TARGET_LINE_4_LABEL_2 + | ID_TARGET_LINE_4_BUTTON + | ID_TARGET_LINE_4_LABEL_3 + | ID_TARGET_LINE_5_LABEL_1 + | ID_TARGET_LINE_5_EDIT_CONTROL + | ID_TARGET_CHECK_BOX_1 + | ID_TARGET_CHECK_BOX_2 + | ID_TARGET_CHECK_BOX_3 + | ID_TARGET_CHECK_BOX_4 + | ID_TARGET_CHECK_BOX_5 + | ID_TARGET_CHECK_BOX_6 + | ID_TARGET_VALUE_LABEL_TEXT + | ID_TARGET_VALUE_OFF_BUTTON + | ID_TARGET_VALUE_ON_BUTTON + | ID_TARGET_VALUE_SLIDER_CONTROL + | ID_TARGET_VALUE_EDIT_CONTROL + | ID_TARGET_VALUE_TEXT + | ID_TARGET_UNIT_BUTTON => Self::Target, + ID_MAPPING_PANEL_GLUE_LABEL + | ID_SETTINGS_RESET_BUTTON + | ID_SETTINGS_SOURCE_LABEL + | ID_SETTINGS_SOURCE_GROUP + | ID_SETTINGS_SOURCE_MIN_LABEL + | ID_SETTINGS_MIN_SOURCE_VALUE_SLIDER_CONTROL + | ID_SETTINGS_MIN_SOURCE_VALUE_EDIT_CONTROL + | ID_SETTINGS_SOURCE_MAX_LABEL + | ID_SETTINGS_MAX_SOURCE_VALUE_SLIDER_CONTROL + | ID_SETTINGS_MAX_SOURCE_VALUE_EDIT_CONTROL + | ID_MODE_OUT_OF_RANGE_LABEL_TEXT + | ID_MODE_OUT_OF_RANGE_COMBOX_BOX + | ID_MODE_GROUP_INTERACTION_LABEL_TEXT + | ID_MODE_GROUP_INTERACTION_COMBO_BOX + | ID_SETTINGS_TARGET_LABEL_TEXT + | ID_SETTINGS_TARGET_SEQUENCE_LABEL_TEXT + | ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL + | ID_SETTINGS_TARGET_GROUP + | ID_SETTINGS_MIN_TARGET_LABEL_TEXT + | ID_SETTINGS_MIN_TARGET_VALUE_SLIDER_CONTROL + | ID_SETTINGS_MIN_TARGET_VALUE_EDIT_CONTROL + | ID_SETTINGS_MIN_TARGET_VALUE_TEXT + | ID_SETTINGS_MAX_TARGET_LABEL_TEXT + | ID_SETTINGS_MAX_TARGET_VALUE_SLIDER_CONTROL + | ID_SETTINGS_MAX_TARGET_VALUE_EDIT_CONTROL + | ID_SETTINGS_MAX_TARGET_VALUE_TEXT + | ID_SETTINGS_REVERSE_CHECK_BOX + | IDC_MODE_FEEDBACK_TYPE_COMBO_BOX + | ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL + | IDC_MODE_FEEDBACK_TYPE_BUTTON + | ID_MODE_KNOB_FADER_GROUP_BOX + | ID_SETTINGS_MODE_LABEL + | ID_SETTINGS_MODE_COMBO_BOX + | ID_MODE_TAKEOVER_LABEL + | ID_MODE_TAKEOVER_MODE + | ID_SETTINGS_ROUND_TARGET_VALUE_CHECK_BOX + | ID_MODE_EEL_CONTROL_TRANSFORMATION_LABEL + | ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL + | ID_MODE_EEL_CONTROL_TRANSFORMATION_DETAIL_BUTTON + | ID_MODE_RELATIVE_GROUP_BOX + | ID_SETTINGS_STEP_SIZE_LABEL_TEXT + | ID_SETTINGS_STEP_SIZE_GROUP + | ID_SETTINGS_MIN_STEP_SIZE_LABEL_TEXT + | ID_SETTINGS_MIN_STEP_SIZE_SLIDER_CONTROL + | ID_SETTINGS_MIN_STEP_SIZE_EDIT_CONTROL + | ID_SETTINGS_MIN_STEP_SIZE_VALUE_TEXT + | ID_SETTINGS_MAX_STEP_SIZE_LABEL_TEXT + | ID_SETTINGS_MAX_STEP_SIZE_SLIDER_CONTROL + | ID_SETTINGS_MAX_STEP_SIZE_EDIT_CONTROL + | ID_SETTINGS_MAX_STEP_SIZE_VALUE_TEXT + | ID_MODE_RELATIVE_FILTER_COMBO_BOX + | ID_SETTINGS_ROTATE_CHECK_BOX + | ID_SETTINGS_MAKE_ABSOLUTE_CHECK_BOX + | ID_MODE_BUTTON_GROUP_BOX + | ID_MODE_FIRE_COMBO_BOX + | ID_MODE_BUTTON_FILTER_COMBO_BOX + | ID_MODE_FIRE_LINE_2_LABEL_1 + | ID_MODE_FIRE_LINE_2_SLIDER_CONTROL + | ID_MODE_FIRE_LINE_2_EDIT_CONTROL + | ID_MODE_FIRE_LINE_2_LABEL_2 + | ID_MODE_FIRE_LINE_3_LABEL_1 + | ID_MODE_FIRE_LINE_3_SLIDER_CONTROL + | ID_MODE_FIRE_LINE_3_EDIT_CONTROL + | ID_MODE_FIRE_LINE_3_LABEL_2 => Self::Glue, + ID_MAPPING_HELP_LEFT_SUBJECT_LABEL + | ID_MAPPING_HELP_LEFT_CONTENT_LABEL + | IDC_MAPPING_MATCHED_INDICATOR_TEXT + | ID_MAPPING_HELP_RIGHT_SUBJECT_LABEL + | ID_MAPPING_HELP_RIGHT_CONTENT_LABEL + | IDC_BEEP_ON_SUCCESS_CHECK_BOX + | ID_MAPPING_PANEL_PREVIOUS_BUTTON + | ID_MAPPING_PANEL_OK + | ID_MAPPING_PANEL_NEXT_BUTTON + | IDC_MAPPING_ENABLED_CHECK_BOX => Self::Help, + _ => return None, + }; + Some(section) + } + + pub fn color_pair(&self) -> ColorPair { + match self { + Section::Mapping => colors::mapping(), + Section::Source => colors::source(), + Section::Target => colors::target(), + Section::Glue => colors::glue(), + Section::Help => colors::help(), + } + } +} + +fn find_help_topic_for_resource(id: u32) -> Option { + const RESOURCES_TO_HELP_TOPIC: &[(&[u32], HelpTopic)] = &[ + // Concepts + ( + &[root::ID_MAPPING_PANEL_MAPPING_LABEL], + HelpTopic::Concept(ConceptTopic::Mapping), + ), + ( + &[root::ID_MAPPING_PANEL_SOURCE_LABEL], + HelpTopic::Concept(ConceptTopic::Source), + ), + ( + &[root::ID_SETTINGS_TARGET_LABEL_TEXT], + HelpTopic::Concept(ConceptTopic::Target), + ), + ( + &[root::ID_MAPPING_PANEL_GLUE_LABEL], + HelpTopic::Concept(ConceptTopic::Glue), + ), + // Mapping + ( + &[ + root::ID_MAPPING_NAME_LABEL, + root::ID_MAPPING_NAME_EDIT_CONTROL, + ], + HelpTopic::Mapping(MappingTopic::Name), + ), + ( + &[ + root::ID_MAPPING_TAGS_LABEL, + root::ID_MAPPING_TAGS_EDIT_CONTROL, + ], + HelpTopic::Mapping(MappingTopic::Tags), + ), + ( + &[root::ID_MAPPING_CONTROL_ENABLED_CHECK_BOX], + HelpTopic::Mapping(MappingTopic::ControlEnabled), + ), + ( + &[root::ID_MAPPING_FEEDBACK_ENABLED_CHECK_BOX], + HelpTopic::Mapping(MappingTopic::FeedbackEnabled), + ), + ( + &[ + root::ID_MAPPING_ACTIVATION_TYPE_LABEL, + root::ID_MAPPING_ACTIVATION_TYPE_COMBO_BOX, + ], + HelpTopic::Mapping(MappingTopic::Active), + ), + ( + &[ + root::ID_MAPPING_PANEL_FEEDBACK_LABEL, + root::ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX, + ], + HelpTopic::Mapping(MappingTopic::FeedbackMode), + ), + ( + &[root::ID_MAPPING_ADVANCED_BUTTON], + HelpTopic::Mapping(MappingTopic::AdvancedSettings), + ), + ( + &[root::ID_MAPPING_FIND_IN_LIST_BUTTON], + HelpTopic::Mapping(MappingTopic::FindInMappingList), + ), + ( + &[root::ID_MAPPING_SHOW_IN_PROJECTION_CHECK_BOX], + HelpTopic::Mapping(MappingTopic::ShowInProjection), + ), + ( + &[root::IDC_BEEP_ON_SUCCESS_CHECK_BOX], + HelpTopic::Mapping(MappingTopic::BeepOnSuccess), + ), + ( + &[root::IDC_MAPPING_ENABLED_CHECK_BOX], + HelpTopic::Mapping(MappingTopic::Enabled), + ), + ( + &[root::ID_MAPPING_PANEL_PREVIOUS_BUTTON], + HelpTopic::Mapping(MappingTopic::PreviousMapping), + ), + ( + &[root::ID_MAPPING_PANEL_NEXT_BUTTON], + HelpTopic::Mapping(MappingTopic::NextMapping), + ), + // Source + ( + &[root::ID_SOURCE_LEARN_BUTTON], + HelpTopic::Source(SourceTopic::Learn), + ), + ( + &[ + root::ID_MAPPING_PANEL_SOURCE_CATEGORY_LABEL, + root::ID_SOURCE_CATEGORY_COMBO_BOX, + ], + HelpTopic::Source(SourceTopic::Category), + ), + ( + &[ + root::ID_SOURCE_TYPE_LABEL_TEXT, + root::ID_SOURCE_TYPE_COMBO_BOX, + ], + HelpTopic::Source(SourceTopic::Type), + ), + // Target + ( + &[root::ID_TARGET_LEARN_BUTTON], + HelpTopic::Target(TargetTopic::Learn), + ), + ( + &[root::ID_TARGET_MENU_BUTTON], + HelpTopic::Target(TargetTopic::Menu), + ), + ( + &[root::ID_TARGET_CATEGORY_COMBO_BOX], + HelpTopic::Target(TargetTopic::Category), + ), + ( + &[ + root::ID_MAPPING_PANEL_TARGET_TYPE_LABEL, + root::ID_TARGET_TYPE_BUTTON, + ], + HelpTopic::Target(TargetTopic::Type), + ), + ( + &[ + root::ID_TARGET_VALUE_LABEL_TEXT, + root::ID_TARGET_VALUE_OFF_BUTTON, + root::ID_TARGET_VALUE_ON_BUTTON, + root::ID_TARGET_VALUE_SLIDER_CONTROL, + root::ID_TARGET_VALUE_EDIT_CONTROL, + root::ID_TARGET_VALUE_TEXT, + ], + HelpTopic::Target(TargetTopic::CurrentValue), + ), + ( + &[root::ID_TARGET_UNIT_BUTTON], + HelpTopic::Target(TargetTopic::DisplayUnit), + ), + // Glue + ( + SOURCE_MIN_MAX_ELEMENTS, + HelpTopic::Glue(ModeParameter::SourceMinMax), + ), + (REVERSE_ELEMENTS, HelpTopic::Glue(ModeParameter::Reverse)), + ( + OUT_OF_RANGE_ELEMENTS, + HelpTopic::Glue(ModeParameter::OutOfRangeBehavior), + ), + ( + TAKEOVER_MODE_ELEMENTS, + HelpTopic::Glue(ModeParameter::TakeoverMode), + ), + ( + CONTROL_TRANSFORMATION_ELEMENTS, + HelpTopic::Glue(ModeParameter::ControlTransformation), + ), + ( + VALUE_SEQUENCE_ELEMENTS, + HelpTopic::Glue(ModeParameter::TargetValueSequence), + ), + ( + TARGET_MIN_MAX_ELEMENTS, + HelpTopic::Glue(ModeParameter::TargetMinMax), + ), + ( + STEP_MIN_ELEMENTS, + HelpTopic::Glue(ModeParameter::StepSizeMin), + ), + ( + STEP_MAX_ELEMENTS, + HelpTopic::Glue(ModeParameter::StepSizeMax), + ), + ( + RELATIVE_FILTER_ELEMENTS, + HelpTopic::Glue(ModeParameter::RelativeFilter), + ), + (FIRE_MODE_ELEMENTS, HelpTopic::Glue(ModeParameter::FireMode)), + ( + MAKE_ABSOLUTE_ELEMENTS, + HelpTopic::Glue(ModeParameter::MakeAbsolute), + ), + ( + FEEDBACK_TYPE_ELEMENTS, + HelpTopic::Glue(ModeParameter::FeedbackType), + ), + ( + ROUND_TARGET_VALUE_ELEMENTS, + HelpTopic::Glue(ModeParameter::RoundTargetValue), + ), + ( + ABSOLUTE_MODE_ELEMENTS, + HelpTopic::Glue(ModeParameter::AbsoluteMode), + ), + ( + GROUP_INTERACTION_ELEMENTS, + HelpTopic::Glue(ModeParameter::GroupInteraction), + ), + ( + FEEDBACK_TRANSFORMATION_ELEMENTS, + HelpTopic::Glue(ModeParameter::FeedbackTransformation), + ), + (ROTATE_ELEMENTS, HelpTopic::Glue(ModeParameter::Rotate)), + ( + BUTTON_FILTER_ELEMENTS, + HelpTopic::Glue(ModeParameter::ButtonFilter), + ), + ]; + RESOURCES_TO_HELP_TOPIC + .iter() + .find_map(|(elements, param)| { + if !elements.contains(&id) { + return None; + } + Some(*param) + }) +} + +fn pick_png_file(current_file: Utf8PathBuf) -> Option { + let current_file = if current_file.as_str().trim().is_empty() { + Reaper::get().resource_path() + } else { + current_file + }; + Reaper::get() + .medium_reaper() + .get_user_file_name_for_read(¤t_file, "Pick image", "png") +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_row_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_row_panel.rs new file mode 100644 index 0000000..7430823 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_row_panel.rs @@ -0,0 +1,1176 @@ +use crate::application::{ + Affected, CompartmentProp, MappingCommand, MappingModel, MappingProp, SharedMapping, + SharedUnitModel, SourceCategory, TargetCategory, TargetModelFormatMultiLine, UnitModel, + UnitProp, WeakUnitModel, +}; +use crate::base::when; +use crate::domain::{CompartmentKind, GroupId, GroupKey, MappingId, QualifiedMappingId}; + +use crate::domain::ui_util::format_tags_as_csv; +use crate::infrastructure::api::convert::from_data::ConversionStyle; +use crate::infrastructure::data::{ + ActivationConditionData, MappingModelData, ModeModelData, SourceModelData, TargetModelData, +}; +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::color_panel::{ColorPanel, ColorPanelDesc}; +use crate::infrastructure::ui::dialog_util::add_group_via_dialog; +use crate::infrastructure::ui::util::{ + colors, mapping_row_panel_height, symbols, view, GLOBAL_SCALING, +}; +use crate::infrastructure::ui::{ + copy_text_to_clipboard, deserialize_api_object_from_lua, deserialize_data_object_from_json, + get_text_from_clipboard, serialize_data_object, DataObject, IndependentPanelManager, + SerializationFormat, SharedMainState, +}; +use anyhow::{bail, Context}; +use core::iter; +use helgobox_api::persistence::{ApiObject, Envelope}; +use reaper_medium::Hbrush; +use rxrust::prelude::*; +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::{Rc, Weak}; +use std::time::Duration; +use swell_ui::{DeviceContext, DialogUnits, Pixels, Point, SharedView, View, ViewContext, Window}; +use tracing::debug; + +pub type SharedIndependentPanelManager = Rc>; + +/// Panel containing the summary data of one mapping and buttons such as "Remove". +#[derive(Debug)] +pub struct MappingRowPanel { + view: ViewContext, + session: WeakUnitModel, + main_state: SharedMainState, + mapping_color_panel: SharedView, + source_color_panel: SharedView, + target_color_panel: SharedView, + row_index: u32, + // We use virtual scrolling to be able to show a large number of rows without any + // performance issues. That means there's a fixed number of mapping rows, and they just + // display different mappings depending on the current scroll position. If there are fewer + // mappings than the fixed number, some rows remain unused. In this case, their mapping is + // `None`, which will make the row hide itself. + mapping: RefCell>, + // Fires when a mapping is about to change. + party_is_over_subject: RefCell>, + panel_manager: Weak>, +} + +impl MappingRowPanel { + pub fn new( + session: WeakUnitModel, + row_index: u32, + panel_manager: Weak>, + main_state: SharedMainState, + ) -> MappingRowPanel { + MappingRowPanel { + view: Default::default(), + session, + main_state, + mapping_color_panel: SharedView::new(ColorPanel::new(build_mapping_color_panel_desc())), + source_color_panel: SharedView::new(ColorPanel::new(build_source_color_panel_desc())), + target_color_panel: SharedView::new(ColorPanel::new(build_target_color_panel_desc())), + row_index, + party_is_over_subject: Default::default(), + mapping: None.into(), + panel_manager, + } + } + + pub fn handle_affected(&self, affected: &Affected, _initiator: Option) { + // If the reaction can't be displayed anymore because the mapping is not filled anymore, + // so what. + use Affected::*; + use CompartmentProp::*; + use UnitProp::*; + self.with_mapping(|_, m| { + match affected { + One(InCompartment(compartment, One(InGroup(_, _)))) + if *compartment == m.compartment() => + { + // Refresh to display potentially new inherited tags. + self.invalidate_name_labels(m); + } + One(InCompartment(compartment, One(InMapping(mapping_id, affected)))) + if *compartment == m.compartment() && *mapping_id == m.id() => + { + match affected { + Multiple => { + self.invalidate_all_controls(m); + } + One(prop) => { + use MappingProp as P; + match prop { + P::Name | P::Tags => { + self.invalidate_name_labels(m); + } + P::IsEnabled => { + self.invalidate_enabled_check_box(m); + } + P::ControlIsEnabled => { + self.invalidate_control_check_box(m); + } + P::FeedbackIsEnabled => { + self.invalidate_feedback_check_box(m); + } + P::InSource(_) => { + self.invalidate_source_label(m); + } + P::InTarget(_) => { + self.invalidate_name_labels(m); + self.invalidate_target_label(m); + } + _ => {} + } + } + } + } + _ => {} + } + }); + } + + pub fn handle_matched_mapping(&self) { + self.source_match_indicator_control().enable(); + self.view + .require_window() + .set_timer(SOURCE_MATCH_INDICATOR_TIMER_ID, Duration::from_millis(50)); + } + + pub fn handle_changed_conditions(&self) { + self.with_mapping(|p, m| { + p.invalidate_name_labels(m); + p.invalidate_target_label(m); + }); + } + + fn source_match_indicator_control(&self) -> Window { + self.view + .require_control(root::IDC_MAPPING_ROW_MATCHED_INDICATOR_TEXT) + } + + pub fn mapping_id(&self) -> Option { + let mapping = self.optional_mapping()?; + let mapping = mapping.borrow(); + Some(mapping.id()) + } + + pub fn set_mapping(self: &SharedView, mapping: Option) { + self.party_is_over_subject.borrow_mut().next(()); + match &mapping { + None => self.view.require_window().hide(), + Some(m) => { + self.view.require_window().show(); + self.invalidate_all_controls(&m.borrow()); + self.register_listeners(); + } + } + self.mapping.replace(mapping); + } + + fn invalidate_all_controls(&self, mapping: &MappingModel) { + self.invalidate_name_labels(mapping); + self.invalidate_source_label(mapping); + self.invalidate_target_label(mapping); + self.invalidate_learn_source_button(mapping); + self.invalidate_learn_target_button(mapping); + self.invalidate_enabled_check_box(mapping); + self.invalidate_control_check_box(mapping); + self.invalidate_feedback_check_box(mapping); + self.invalidate_on_indicator(mapping); + self.invalidate_button_enabled_states(); + } + + fn invalidate_name_labels(&self, mapping: &MappingModel) { + let main_state = self.main_state.borrow(); + // Left label + self.view + .require_window() + .require_control(root::ID_MAPPING_ROW_MAPPING_LABEL) + .set_text(mapping.effective_name()); + // Initialize right label with tags + let session = self.session(); + let session = session.borrow(); + let group_id = mapping.group_id(); + let compartment = main_state.active_compartment.get(); + let group = session.find_group_by_id_including_default_group(compartment, group_id); + let mut right_label = if let Some(g) = group { + // Group present. Merge group tags with mapping tags. + let g = g.borrow(); + format_tags_as_csv(g.tags().iter().chain(mapping.tags())) + } else { + // Group not present. Use mapping tags only. + format_tags_as_csv(mapping.tags()) + }; + // Add group name to right label if all groups are shown. + if main_state + .displayed_group_for_active_compartment() + .is_none() + { + let group_label = if let Some(g) = group { + g.borrow().effective_name().to_owned() + } else { + "".to_owned() + }; + if !right_label.is_empty() { + right_label += " | "; + } + right_label += &group_label; + }; + self.view + .require_window() + .require_control(root::ID_MAPPING_ROW_GROUP_LABEL) + .set_text(right_label); + } + + fn session(&self) -> SharedUnitModel { + self.session.upgrade().expect("session gone") + } + + fn invalidate_source_label(&self, mapping: &MappingModel) { + let plain_label = mapping.source_model.to_string(); + let rich_label = if mapping.source_model.category() == SourceCategory::Virtual { + let session = self.session(); + let session = session.borrow(); + let controller_mappings = session.mappings(CompartmentKind::Controller); + let mappings: Vec<_> = controller_mappings + .filter(|m| { + let m = m.borrow(); + m.target_model.category() == TargetCategory::Virtual + && m.target_model.create_control_element() + == mapping.source_model.create_control_element() + }) + .collect(); + if mappings.is_empty() { + plain_label + } else { + let first_mapping = mappings[0].borrow(); + if first_mapping.name().is_empty() { + plain_label + } else { + let first_mapping_name = first_mapping.effective_name(); + if mappings.len() == 1 { + format!("{plain_label}\n({first_mapping_name})") + } else { + format!( + "{}\n({} + {})", + plain_label, + first_mapping_name, + mappings.len() - 1 + ) + } + } + } + } else { + plain_label + }; + self.view + .require_window() + .require_control(root::ID_MAPPING_ROW_SOURCE_LABEL_TEXT) + .set_text(rich_label); + } + + fn invalidate_target_label(&self, mapping: &MappingModel) { + let session = self.session(); + let session = session.borrow(); + if !session + .processor_context() + .project_or_current_project() + .is_available() + { + // Prevent error on project close + return; + } + let target_model_string = + TargetModelFormatMultiLine::new(&mapping.target_model, &session, mapping.compartment()) + .to_string(); + self.view + .require_window() + .require_control(root::ID_MAPPING_ROW_TARGET_LABEL_TEXT) + .set_text(target_model_string); + } + + fn invalidate_learn_source_button(&self, mapping: &MappingModel) { + let text = if self + .session() + .borrow() + .unit() + .borrow() + .mapping_is_learning_source(mapping.qualified_id()) + { + "Stop" + } else { + "Learn source" + }; + self.view + .require_control(root::ID_MAPPING_ROW_LEARN_SOURCE_BUTTON) + .set_text(text); + } + + fn invalidate_learn_target_button(&self, mapping: &MappingModel) { + let text = if self + .session() + .borrow() + .unit() + .borrow() + .mapping_is_learning_target(mapping.qualified_id()) + { + "Stop" + } else { + "Learn target" + }; + self.view + .require_control(root::ID_MAPPING_ROW_LEARN_TARGET_BUTTON) + .set_text(text); + } + + fn init_symbol_controls(&self) { + self.view + .require_control(root::ID_MAPPING_ROW_CONTROL_CHECK_BOX) + .set_text(symbols::arrow_right_symbol().to_string()); + self.view + .require_control(root::ID_MAPPING_ROW_FEEDBACK_CHECK_BOX) + .set_text(symbols::arrow_left_symbol().to_string()); + self.view + .require_control(root::ID_UP_BUTTON) + .set_text(symbols::arrow_up_symbol().to_string()); + self.view + .require_control(root::ID_DOWN_BUTTON) + .set_text(symbols::arrow_down_symbol().to_string()); + let indicator = self + .view + .require_control(root::IDC_MAPPING_ROW_MATCHED_INDICATOR_TEXT); + indicator.set_text(symbols::indicator_symbol().to_string()); + indicator.disable(); + } + + fn invalidate_enabled_check_box(&self, mapping: &MappingModel) { + self.view + .require_control(root::IDC_MAPPING_ROW_ENABLED_CHECK_BOX) + .set_checked(mapping.is_enabled()); + } + + fn invalidate_control_check_box(&self, mapping: &MappingModel) { + self.view + .require_control(root::ID_MAPPING_ROW_CONTROL_CHECK_BOX) + .set_checked(mapping.control_is_enabled()); + } + + fn invalidate_feedback_check_box(&self, mapping: &MappingModel) { + self.view + .require_control(root::ID_MAPPING_ROW_FEEDBACK_CHECK_BOX) + .set_checked(mapping.feedback_is_enabled()); + } + + fn invalidate_on_indicator(&self, mapping: &MappingModel) { + let is_on = self + .session() + .borrow() + .mapping_is_on(mapping.qualified_id()); + self.view + .require_control(root::ID_MAPPING_ROW_MAPPING_LABEL) + .set_enabled(is_on); + self.view + .require_control(root::ID_MAPPING_ROW_SOURCE_LABEL_TEXT) + .set_enabled(is_on); + self.view + .require_control(root::ID_MAPPING_ROW_TARGET_LABEL_TEXT) + .set_enabled(is_on); + self.view + .require_control(root::ID_MAPPING_ROW_GROUP_LABEL) + .set_enabled(is_on); + } + + fn mappings_are_read_only(&self) -> bool { + self.session() + .borrow() + .mappings_are_read_only(self.active_compartment()) + } + + fn invalidate_button_enabled_states(&self) { + let enabled = !self.mappings_are_read_only(); + let buttons = [ + root::ID_UP_BUTTON, + root::ID_DOWN_BUTTON, + root::ID_MAPPING_ROW_CONTROL_CHECK_BOX, + root::ID_MAPPING_ROW_FEEDBACK_CHECK_BOX, + root::ID_MAPPING_ROW_EDIT_BUTTON, + root::ID_MAPPING_ROW_DUPLICATE_BUTTON, + root::ID_MAPPING_ROW_REMOVE_BUTTON, + root::ID_MAPPING_ROW_LEARN_SOURCE_BUTTON, + root::ID_MAPPING_ROW_LEARN_TARGET_BUTTON, + ]; + for b in buttons.iter() { + self.view.require_control(*b).set_enabled(enabled); + } + } + + fn register_listeners(self: &SharedView) { + let session = self.session(); + let session = session.borrow(); + let instance_state = session.unit().borrow(); + self.when( + instance_state.mapping_which_learns_source().changed(), + |view| { + view.with_mapping(Self::invalidate_learn_source_button); + }, + ); + self.when( + instance_state.mapping_which_learns_target().changed(), + |view| { + view.with_mapping(Self::invalidate_learn_target_button); + }, + ); + self.when(instance_state.on_mappings_changed(), |view| { + view.with_mapping(Self::invalidate_on_indicator); + }); + self.when( + session + .auto_load_mode + .changed() + .merge(session.learn_many_state_changed()), + |view| { + view.invalidate_button_enabled_states(); + }, + ); + } + + fn with_mapping(&self, use_mapping: impl Fn(&Self, &MappingModel)) { + let mapping = self.mapping.borrow(); + if let Some(m) = mapping.as_ref() { + use_mapping(self, m.borrow().deref()) + } + } + + fn closed_or_mapping_will_change( + &self, + ) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.view + .closed() + .merge(self.party_is_over_subject.borrow().clone()) + } + + fn get_mapping(&self) -> anyhow::Result { + self.optional_mapping().context("row mapping not available") + } + + fn optional_mapping(&self) -> Option { + self.mapping.clone().into_inner() + } + + fn get_qualified_mapping_id(&self) -> anyhow::Result { + Ok(self.get_mapping()?.borrow().qualified_id()) + } + + fn edit_mapping(&self) -> anyhow::Result<()> { + self.main_state.borrow_mut().stop_filter_learning(); + let mapping = self.get_mapping()?; + self.panel_manager().borrow_mut().edit_mapping(&mapping); + Ok(()) + } + + fn panel_manager(&self) -> SharedIndependentPanelManager { + self.panel_manager.upgrade().expect("panel manager gone") + } + + fn move_mapping_within_list(&self, increment: isize) -> anyhow::Result<()> { + // When we route keyboard input to ReaLearn and press space, it presses the "Up" button, + // even if we don't display the rows. Don't know why, but suppress a panic here. + let mapping = self.get_mapping()?; + let within_same_group = self + .main_state + .borrow() + .displayed_group_for_active_compartment() + .is_some(); + let _ = self.session().borrow_mut().move_mapping_within_list( + self.active_compartment(), + mapping.borrow().id(), + within_same_group, + increment, + ); + Ok(()) + } + + fn active_compartment(&self) -> CompartmentKind { + self.main_state.borrow().active_compartment.get() + } + + fn remove_mapping(&self) -> anyhow::Result<()> { + let user_confirmed = self + .view + .require_window() + .confirm("ReaLearn", "Do you really want to remove this mapping?"); + if !user_confirmed { + return Ok(()); + } + self.session() + .borrow_mut() + .remove_mapping(self.get_qualified_mapping_id()?); + Ok(()) + } + + fn duplicate_mapping(&self) -> anyhow::Result<()> { + self.session() + .borrow_mut() + .duplicate_mapping(self.get_qualified_mapping_id()?) + } + + fn toggle_learn_source(&self) -> anyhow::Result<()> { + let mapping_id = self.get_qualified_mapping_id()?; + let shared_session = self.session(); + shared_session + .borrow_mut() + .toggle_learning_source(self.session.clone(), mapping_id)?; + Ok(()) + } + + fn toggle_learn_target(&self) -> anyhow::Result<()> { + let mapping_id = self.get_qualified_mapping_id()?; + let shared_session = self.session(); + shared_session + .borrow_mut() + .toggle_learning_target(self.session.clone(), mapping_id); + Ok(()) + } + + fn update_is_enabled(&self) -> anyhow::Result<()> { + let checked = self + .view + .require_control(root::IDC_MAPPING_ROW_ENABLED_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::SetIsEnabled(checked)) + } + + fn update_control_is_enabled(&self) -> anyhow::Result<()> { + let checked = self + .view + .require_control(root::ID_MAPPING_ROW_CONTROL_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::SetControlIsEnabled(checked)) + } + + fn update_feedback_is_enabled(&self) -> anyhow::Result<()> { + let checked = self + .view + .require_control(root::ID_MAPPING_ROW_FEEDBACK_CHECK_BOX) + .is_checked(); + self.change_mapping(MappingCommand::SetFeedbackIsEnabled(checked)) + } + + fn change_mapping(&self, cmd: MappingCommand) -> anyhow::Result<()> { + let mapping = self.get_mapping()?; + let mut mapping = mapping.borrow_mut(); + UnitModel::change_mapping_from_ui_simple(self.session.clone(), &mut mapping, cmd, None); + Ok(()) + } + + fn notify_user_on_anyhow_error(&self, result: anyhow::Result<()>) { + if let Err(e) = result { + self.notify_user_about_anyhow_error(e); + } + } + + fn notify_user_about_anyhow_error(&self, e: anyhow::Error) { + self.view + .require_window() + .alert("ReaLearn", format!("{e:#}")); + } + + fn paste_from_lua_replace(&self, text: &str) -> anyhow::Result<()> { + let active_compartment = self.active_compartment(); + let api_object = deserialize_api_object_from_lua(text, active_compartment)?; + if !matches!(api_object, ApiObject::Mapping(Envelope { value: _, .. })) { + bail!("There's more than one mapping in the clipboard."); + } + let data_object = { + let session = self.session(); + let session = session.borrow(); + let compartment_in_session = session.compartment_in_unit(active_compartment); + DataObject::try_from_api_object(api_object, &compartment_in_session)? + }; + paste_data_object_in_place(data_object, self.session(), self.mapping_triple()?)?; + Ok(()) + } + + fn paste_from_lua_insert_below(&self, text: &str) -> anyhow::Result<()> { + let active_compartment = self.active_compartment(); + let api_object = deserialize_api_object_from_lua(text, active_compartment)?; + let api_mappings = api_object + .into_mappings() + .context("Can only insert a list of mappings.")?; + let data_mappings = { + let session = self.session(); + let session = session.borrow(); + let compartment_in_session = session.compartment_in_unit(active_compartment); + DataObject::try_from_api_mappings(api_mappings.value, &compartment_in_session)? + }; + let triple = self.mapping_triple()?; + paste_mappings( + Envelope::new(api_mappings.version, data_mappings), + self.session(), + triple.compartment, + Some(triple.mapping_id), + triple.group_id, + ) + } + + fn mapping_triple(&self) -> anyhow::Result { + let mapping = self.get_mapping()?; + let mapping = mapping.borrow(); + let triple = MappingTriple { + compartment: mapping.compartment(), + mapping_id: mapping.id(), + group_id: mapping.group_id(), + }; + Ok(triple) + } + + fn open_context_menu(&self, location: Point) -> anyhow::Result<()> { + enum MenuAction { + None, + PasteObjectInPlace(DataObject), + PasteMappings(Envelope>), + CopyPart(ObjectType), + MoveMappingToGroup(Option), + CopyMappingAsLua(ConversionStyle), + PasteFromLuaReplace(String), + PasteFromLuaInsertBelow(String), + LogDebugInfo, + } + impl Default for MenuAction { + fn default() -> Self { + Self::None + } + } + let pure_menu = { + use swell_ui::menu_tree::*; + let shared_session = self.session(); + let session = shared_session.borrow(); + let mapping = self.mapping.borrow(); + let mapping = mapping.as_ref().context("row contains no mapping")?; + let mapping = mapping.borrow(); + let compartment = mapping.compartment(); + let text_from_clipboard = get_text_from_clipboard(); + let data_object_from_clipboard = text_from_clipboard + .as_ref() + .and_then(|text| deserialize_data_object_from_json(text).ok()); + let clipboard_could_contain_lua = + text_from_clipboard.is_some() && data_object_from_clipboard.is_none(); + let text_from_clipboard_clone = text_from_clipboard.clone(); + let data_object_from_clipboard_clone = data_object_from_clipboard.clone(); + let group_id = mapping.group_id(); + let entries = vec![ + item("Copy", MenuAction::CopyPart(ObjectType::Mapping)), + { + let desc = match data_object_from_clipboard { + Some(DataObject::Mapping(Envelope { value: m, version })) => Some(( + format!("Paste mapping \"{}\" (replace)", &m.name), + DataObject::Mapping(Envelope { value: m, version }), + )), + Some(DataObject::Source(Envelope { value: s, version })) => Some(( + format!("Paste source ({})", s.category), + DataObject::Source(Envelope { value: s, version }), + )), + Some(DataObject::Glue(Envelope { value: m, version })) => Some(( + "Paste glue".to_owned(), + DataObject::Glue(Envelope { value: m, version }), + )), + Some(DataObject::Target(Envelope { value: t, version })) => Some(( + format!("Paste target ({})", t.category), + DataObject::Target(Envelope { value: t, version }), + )), + Some(DataObject::ActivationCondition(Envelope { value: t, version })) => { + Some(( + format!("Paste activation condition ({})", t.activation_type), + DataObject::ActivationCondition(Envelope { value: t, version }), + )) + } + _ => None, + }; + if let Some((label, obj)) = desc { + item(label, MenuAction::PasteObjectInPlace(obj)) + } else { + disabled_item("Paste (replace)") + } + }, + { + let desc = match data_object_from_clipboard_clone { + Some(DataObject::Mapping(Envelope { value: m, version })) => Some(( + format!("Paste mapping \"{}\" (insert below)", &m.name), + Envelope::new(version, vec![*m]), + )), + Some(DataObject::Mappings(Envelope { + value: vec, + version, + })) => Some(( + format!("Paste {} mappings below", vec.len()), + Envelope::new(version, vec), + )), + _ => None, + }; + if let Some((label, datas)) = desc { + item(label, MenuAction::PasteMappings(datas)) + } else { + disabled_item("Paste (insert below)") + } + }, + menu( + "Copy part", + vec![ + item( + "Copy activation condition", + MenuAction::CopyPart(ObjectType::ActivationCondition), + ), + item("Copy source", MenuAction::CopyPart(ObjectType::Source)), + item("Copy glue", MenuAction::CopyPart(ObjectType::Glue)), + item("Copy target", MenuAction::CopyPart(ObjectType::Target)), + ], + ), + menu( + "Move to group", + iter::once(item("", MenuAction::MoveMappingToGroup(None))) + .chain(session.groups_sorted(compartment).map(move |g| { + let g = g.borrow(); + let g_id = g.id(); + item_with_opts( + g.to_string(), + ItemOpts { + enabled: group_id != g_id, + checked: false, + }, + MenuAction::MoveMappingToGroup(Some(g_id)), + ) + })) + .collect(), + ), + menu( + "Advanced", + vec![ + item( + "Copy as Lua", + MenuAction::CopyMappingAsLua(ConversionStyle::Minimal), + ), + item( + "Copy as Lua (include default values)", + MenuAction::CopyMappingAsLua(ConversionStyle::IncludeDefaultValues), + ), + item_with_opts( + "Paste from Lua (replace)", + ItemOpts { + enabled: clipboard_could_contain_lua, + checked: false, + }, + MenuAction::PasteFromLuaReplace( + text_from_clipboard.unwrap_or_default(), + ), + ), + item_with_opts( + "Paste from Lua (insert below)", + ItemOpts { + enabled: clipboard_could_contain_lua, + checked: false, + }, + MenuAction::PasteFromLuaInsertBelow( + text_from_clipboard_clone.unwrap_or_default(), + ), + ), + item("Log debug info (now)", MenuAction::LogDebugInfo), + ], + ), + ]; + anonymous_menu(entries) + }; + let result = self + .view + .require_window() + .open_popup_menu(pure_menu, location) + .context("no entry selected")?; + let triple = self.mapping_triple()?; + let result = match result { + MenuAction::None => Ok(()), + MenuAction::PasteObjectInPlace(obj) => { + paste_data_object_in_place(obj, self.session(), triple) + } + MenuAction::PasteFromLuaReplace(text) => self.paste_from_lua_replace(&text), + MenuAction::PasteMappings(datas) => paste_mappings( + datas, + self.session(), + triple.compartment, + Some(triple.mapping_id), + triple.group_id, + ), + MenuAction::PasteFromLuaInsertBelow(text) => self.paste_from_lua_insert_below(&text), + MenuAction::CopyPart(obj_type) => copy_mapping_object( + self.session(), + triple.compartment, + triple.mapping_id, + obj_type, + SerializationFormat::JsonDataObject, + ), + MenuAction::CopyMappingAsLua(style) => copy_mapping_object( + self.session(), + triple.compartment, + triple.mapping_id, + ObjectType::Mapping, + SerializationFormat::LuaApiObject(style), + ), + MenuAction::MoveMappingToGroup(group_id) => move_mapping_to_group( + self.session(), + triple.compartment, + triple.mapping_id, + group_id, + ), + MenuAction::LogDebugInfo => self + .session() + .borrow() + .log_mapping(triple.compartment, triple.mapping_id), + }; + self.notify_user_on_anyhow_error(result); + Ok(()) + } + + fn when( + self: &SharedView, + event: impl LocalObservable<'static, Item = (), Err = ()> + 'static, + reaction: impl Fn(SharedView) + 'static + Copy, + ) { + when(event.take_until(self.closed_or_mapping_will_change())) + .with(Rc::downgrade(self)) + .do_sync(move |panel, _| reaction(panel)); + } +} + +impl View for MappingRowPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_MAPPING_ROW_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + window.hide(); + if cfg!(unix) { + self.source_color_panel.clone().open(window); + self.target_color_panel.clone().open(window); + // Must be the last because we want it below the others + self.mapping_color_panel.clone().open(window); + } + window.move_to_dialog_units(Point::new( + DialogUnits(0), + mapping_row_panel_height() * self.row_index, + )); + self.init_symbol_controls(); + false + } + + fn erase_background(self: SharedView, device_context: DeviceContext) -> bool { + if cfg!(unix) { + // On macOS/Linux we use color panels as real child windows. + return false; + } + if !BackboneShell::get().config().background_colors_enabled() { + return false; + } + let window = self.view.require_window(); + // Must be the first because we want it below the others + self.mapping_color_panel + .paint_manually(device_context, window); + self.source_color_panel + .paint_manually(device_context, window); + self.target_color_panel + .paint_manually(device_context, window); + true + } + + fn control_color_static( + self: SharedView, + device_context: DeviceContext, + window: Window, + ) -> Option { + if cfg!(target_os = "macos") { + // On macOS, we fortunately don't need to do this nonsense. And it wouldn't be possible + // anyway because SWELL macOS can't distinguish between different child controls. + return None; + } + if !BackboneShell::get().config().background_colors_enabled() { + return None; + } + device_context.set_bk_mode_to_transparent(); + let color_pair = match window.resource_id() { + root::ID_MAPPING_ROW_SOURCE_LABEL_TEXT => colors::source(), + root::ID_MAPPING_ROW_TARGET_LABEL_TEXT => colors::target(), + root::ID_MAPPING_ROW_MAPPING_LABEL | root::ID_MAPPING_ROW_GROUP_LABEL => { + colors::show_background() + } + _ => colors::mapping(), + }; + view::get_brush_for_color_pair(color_pair) + } + + fn button_clicked(self: SharedView, resource_id: u32) { + let result = match resource_id { + root::IDC_MAPPING_ROW_ENABLED_CHECK_BOX => self.update_is_enabled(), + root::ID_MAPPING_ROW_EDIT_BUTTON => self.edit_mapping(), + root::ID_UP_BUTTON => self.move_mapping_within_list(-1), + root::ID_DOWN_BUTTON => self.move_mapping_within_list(1), + root::ID_MAPPING_ROW_REMOVE_BUTTON => self.remove_mapping(), + root::ID_MAPPING_ROW_DUPLICATE_BUTTON => self.duplicate_mapping(), + root::ID_MAPPING_ROW_LEARN_SOURCE_BUTTON => self.toggle_learn_source(), + root::ID_MAPPING_ROW_LEARN_TARGET_BUTTON => self.toggle_learn_target(), + root::ID_MAPPING_ROW_CONTROL_CHECK_BOX => self.update_control_is_enabled(), + root::ID_MAPPING_ROW_FEEDBACK_CHECK_BOX => self.update_feedback_is_enabled(), + _ => Ok(()), + }; + self.notify_user_on_anyhow_error(result); + } + + fn context_menu_wanted(self: SharedView, location: Point) -> bool { + self.notify_user_on_anyhow_error(self.open_context_menu(location)); + true + } + + fn timer(&self, id: usize) -> bool { + if id == SOURCE_MATCH_INDICATOR_TIMER_ID { + self.view + .require_window() + .kill_timer(SOURCE_MATCH_INDICATOR_TIMER_ID); + self.source_match_indicator_control().disable(); + true + } else { + false + } + } +} + +impl Drop for MappingRowPanel { + fn drop(&mut self) { + debug!("Dropping mapping row panel..."); + } +} + +fn move_mapping_to_group( + session: SharedUnitModel, + compartment: CompartmentKind, + mapping_id: MappingId, + group_id: Option, +) -> anyhow::Result<()> { + let cloned_session = session.clone(); + let group_id = group_id + .or_else(move || add_group_via_dialog(cloned_session, compartment).ok()) + .context("no group selected")?; + session.borrow_mut().move_mappings_to_group( + compartment, + &[mapping_id], + group_id, + Rc::downgrade(&session), + )?; + Ok(()) +} + +fn copy_mapping_object( + session: SharedUnitModel, + compartment: CompartmentKind, + mapping_id: MappingId, + object_type: ObjectType, + format: SerializationFormat, +) -> anyhow::Result<()> { + let session = session.borrow(); + let mapping = session + .find_mapping_by_id(compartment, mapping_id) + .context("mapping not found")?; + use ObjectType::*; + let mapping = mapping.borrow(); + let compartment_in_session = session.compartment_in_unit(compartment); + let data_object = match object_type { + Mapping => DataObject::Mapping(BackboneShell::create_envelope(Box::new( + MappingModelData::from_model(&mapping, &compartment_in_session), + ))), + Source => DataObject::Source(BackboneShell::create_envelope(Box::new( + SourceModelData::from_model(&mapping.source_model), + ))), + Glue => DataObject::Glue(BackboneShell::create_envelope(Box::new( + ModeModelData::from_model(&mapping.mode_model), + ))), + Target => DataObject::Target(BackboneShell::create_envelope(Box::new( + TargetModelData::from_model(&mapping.target_model, &compartment_in_session), + ))), + ActivationCondition => DataObject::ActivationCondition(BackboneShell::create_envelope( + Box::new(ActivationConditionData::from_model( + &mapping.activation_condition_model, + &compartment_in_session, + )), + )), + }; + let text = serialize_data_object(data_object, format)?; + copy_text_to_clipboard(text)?; + Ok(()) +} + +enum ObjectType { + Mapping, + Source, + Glue, + Target, + ActivationCondition, +} + +fn paste_data_object_in_place( + data_object: DataObject, + shared_session: SharedUnitModel, + triple: MappingTriple, +) -> anyhow::Result<()> { + let mut session = shared_session.borrow_mut(); + let mapping = session + .find_mapping_by_id(triple.compartment, triple.mapping_id) + .context("mapping not found")? + .clone(); + BackboneShell::warn_if_envelope_version_higher(data_object.version()); + let mut mapping = mapping.borrow_mut(); + match data_object { + DataObject::Mapping(Envelope { + value: mut m, + version, + }) => { + m.group_id = { + if triple.group_id.is_default() { + GroupKey::default() + } else { + let group = session + .find_group_by_id(triple.compartment, triple.group_id) + .context("couldn't find group")?; + group.borrow().key().clone() + } + }; + let conversion_context = session.compartment_in_unit(mapping.compartment()); + // TODO-medium It would simplify things if we would just translate this into a new model + // and then call a Session method to completely replace a model by its ID. Same with + // other data object types. + m.apply_to_model( + &mut mapping, + &conversion_context, + Some(session.extended_context()), + version.as_ref(), + )?; + } + DataObject::Source(Envelope { value: s, .. }) => { + s.apply_to_model(&mut mapping.source_model, triple.compartment); + } + DataObject::Glue(Envelope { value: m, .. }) => { + m.apply_to_model(&mut mapping.mode_model); + } + DataObject::Target(Envelope { value: t, .. }) => { + let compartment_in_session = session.compartment_in_unit(triple.compartment); + t.apply_to_model( + &mut mapping.target_model, + triple.compartment, + session.extended_context(), + &compartment_in_session, + )?; + } + DataObject::ActivationCondition(Envelope { value: c, .. }) => { + let compartment_in_session = session.compartment_in_unit(triple.compartment); + c.apply_to_model( + &mut mapping.activation_condition_model, + &compartment_in_session, + ); + } + _ => bail!("can only paste mapping, source, mode and target in place"), + }; + session.notify_mapping_has_changed(mapping.qualified_id(), Rc::downgrade(&shared_session)); + Ok(()) +} + +/// If `below_mapping_id` not given, it's added at the end. +// https://github.com/rust-lang/rust-clippy/issues/6066 +#[allow(clippy::needless_collect)] +pub fn paste_mappings( + mapping_datas: Envelope>, + session: SharedUnitModel, + compartment: CompartmentKind, + below_mapping_id: Option, + group_id: GroupId, +) -> anyhow::Result<()> { + let mut session = session.borrow_mut(); + let index = if let Some(id) = below_mapping_id { + session + .find_mapping_and_index_by_id(compartment, id) + .context("mapping not found")? + .0 + } else { + session.mapping_count(compartment) + }; + let group_key = { + if group_id.is_default() { + GroupKey::default() + } else { + let group = session + .find_group_by_id(compartment, group_id) + .context("couldn't find group")?; + let group = group.borrow(); + group.key().clone() + } + }; + let new_mappings: Result, _> = mapping_datas + .value + .into_iter() + .map(|mut data| { + data.id = None; + data.group_id = group_key.clone(); + data.to_model( + compartment, + &session.compartment_in_unit(compartment), + Some(session.extended_context()), + mapping_datas.version.as_ref(), + ) + }) + .collect(); + session.insert_mappings_at(compartment, index + 1, new_mappings?.into_iter()); + Ok(()) +} + +const SOURCE_MATCH_INDICATOR_TIMER_ID: usize = 571; + +struct MappingTriple { + compartment: CompartmentKind, + mapping_id: MappingId, + group_id: GroupId, +} + +fn build_mapping_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 0, + y: 0, + width: 460, + height: COLOR_PANEL_HEIGHT, + color_pair: colors::mapping(), + scaling: GLOBAL_SCALING, + } +} + +fn build_source_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 43, + y: 0, + width: 94, + height: COLOR_PANEL_HEIGHT, + color_pair: colors::source(), + scaling: GLOBAL_SCALING, + } +} + +fn build_target_color_panel_desc() -> ColorPanelDesc { + ColorPanelDesc { + x: 161, + y: 0, + width: 182, + height: COLOR_PANEL_HEIGHT, + color_pair: colors::target(), + scaling: GLOBAL_SCALING, + } +} + +const COLOR_PANEL_HEIGHT: u32 = 48; diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_rows_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_rows_panel.rs new file mode 100644 index 0000000..668ce8f --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/mapping_rows_panel.rs @@ -0,0 +1,697 @@ +use std::cell::{Cell, RefCell}; +use std::rc::{Rc, Weak}; + +use crate::base::when; +use crate::infrastructure::ui::{ + bindings::root, deserialize_data_object_from_json, get_text_from_clipboard, paste_mappings, + DataObject, IndependentPanelManager, MainState, MappingRowPanel, ScrollStatus, + SharedIndependentPanelManager, SharedMainState, +}; +use helgobox_api::persistence::Envelope; +use reaper_high::Reaper; +use reaper_low::raw; +use rxrust::prelude::*; +use std::cmp; +use tracing::debug; + +use crate::application::{ + Affected, SharedMapping, SharedUnitModel, UnitModel, UnitProp, WeakUnitModel, +}; +use crate::domain::{CompartmentKind, MappingId, MappingMatchedEvent, QualifiedMappingId}; +use crate::infrastructure::data::MappingModelData; +use swell_ui::{DialogUnits, Pixels, Point, SharedView, View, ViewContext, Window}; + +#[derive(Debug)] +pub struct MappingRowsPanel { + view: ViewContext, + position: Point, + session: WeakUnitModel, + main_state: SharedMainState, + rows: Vec>, + panel_manager: Weak>, + scroll_position: Cell, +} + +impl MappingRowsPanel { + pub fn new( + session: WeakUnitModel, + panel_manager: Weak>, + main_state: SharedMainState, + position: Point, + ) -> MappingRowsPanel { + let row_count = helgobox_dialogs::constants::MAPPING_ROW_COUNT; + MappingRowsPanel { + view: Default::default(), + rows: (0..row_count) + .map(|i| { + let panel = MappingRowPanel::new( + session.clone(), + i, + panel_manager.clone(), + main_state.clone(), + ); + SharedView::new(panel) + }) + .collect(), + session, + panel_manager, + scroll_position: 0.into(), + main_state, + position, + } + } + + fn session(&self) -> SharedUnitModel { + self.session.upgrade().expect("session gone") + } + + pub fn handle_matched_mapping(&self, event: MappingMatchedEvent) { + if event.compartment != self.active_compartment() { + return; + } + for row in &self.rows { + if row.mapping_id() == Some(event.mapping_id) { + row.handle_matched_mapping(); + } + } + } + + pub fn handle_changed_conditions(&self) { + for row in &self.rows { + row.handle_changed_conditions(); + } + } + + pub fn handle_affected(&self, affected: &Affected, initiator: Option) { + if !self.is_open() { + return; + } + for row in &self.rows { + row.handle_affected(affected, initiator); + } + } + + /// Also opens main panel, clears filters and switches compartment if necessary. + /// + /// Really tries to get mapping on top. + pub fn force_scroll_to_mapping(&self, id: QualifiedMappingId) { + let shared_session = self.session(); + let session = shared_session.borrow(); + let index = match session.index_of_mapping(id.compartment, id.id) { + None => return, + Some(i) => i, + }; + if !self.is_open() { + session.show_in_floating_window(); + } + { + let mut main_state = self.main_state.borrow_mut(); + main_state.active_compartment.set(id.compartment); + main_state.clear_all_filters_and_displayed_group(); + } + self.scroll(index); + } + + /// Doesn't switch compartment! + fn ensure_mapping_is_visible(&self, compartment: CompartmentKind, mapping_id: MappingId) { + let index = match self.determine_index_of_mapping_in_list(compartment, mapping_id) { + None => return, + Some(i) => i, + }; + let amount = self.get_scroll_amount_to_make_item_visible(index); + self.scroll_relative(amount); + } + + fn scroll_relative(&self, amount: isize) { + if amount == 0 { + return; + } + let new_scroll_pos = self.scroll_position.get() as isize + amount; + if new_scroll_pos >= 0 { + self.scroll(new_scroll_pos as usize); + } + } + + fn get_scroll_amount_to_make_item_visible(&self, index: usize) -> isize { + let from_index = self.scroll_position.get(); + if index < from_index { + return index as isize - from_index as isize; + } + let to_index = from_index + self.rows.len() - 1; + if index > to_index { + return index as isize - to_index as isize; + } + 0 + } + + fn determine_index_of_mapping_in_list( + &self, + compartment: CompartmentKind, + mapping_id: MappingId, + ) -> Option { + let shared_session = self.session(); + let session = shared_session.borrow(); + let main_state = self.main_state.borrow(); + let mut mappings = Self::filtered_mappings(&session, &main_state, compartment, false); + mappings.position(|m| m.borrow().id() == mapping_id) + } + + pub fn edit_mapping(&self, compartment: CompartmentKind, mapping_id: MappingId) { + if let Some(m) = self + .session() + .borrow() + .find_mapping_by_id(compartment, mapping_id) + { + self.panel_manager().borrow_mut().edit_mapping(m); + } + } + + fn panel_manager(&self) -> SharedIndependentPanelManager { + self.panel_manager.upgrade().expect("panel manager gone") + } + + fn active_compartment(&self) -> CompartmentKind { + self.main_state.borrow().active_compartment.get() + } + + fn open_mapping_rows(&self, window: Window) { + for row in self.rows.iter() { + row.clone().open(window); + } + } + + fn invalidate_scroll_info(&self) { + let item_count = self.filtered_mapping_count(); + self.update_scroll_status_msg(item_count); + let scroll_info = raw::SCROLLINFO { + cbSize: std::mem::size_of::() as _, + fMask: raw::SIF_PAGE | raw::SIF_RANGE, + nMin: 0, + nMax: self.get_max_item_index(item_count) as _, + nPage: self.rows.len() as _, + nPos: 0, + nTrackPos: 0, + }; + unsafe { + Reaper::get().medium_reaper().low().CoolSB_SetScrollInfo( + self.view.require_window().raw() as _, + raw::SB_VERT as _, + &scroll_info as *const _ as *mut _, + 1, + ); + } + self.adjust_scrolling(&scroll_info); + self.show_or_hide_scroll_bar(&scroll_info); + } + + fn show_or_hide_scroll_bar(&self, scroll_info: &raw::SCROLLINFO) { + let show = scroll_info.nMax >= scroll_info.nPage as i32; + unsafe { + Reaper::get().medium_reaper().low().CoolSB_ShowScrollBar( + self.view.require_window().raw() as _, + raw::SB_VERT as _, + show as _, + ); + } + } + + fn adjust_scrolling(&self, scroll_info: &raw::SCROLLINFO) { + let max_scroll_pos = + cmp::max(0, (scroll_info.nMax + 1) - scroll_info.nPage as i32) as usize; + if max_scroll_pos == 0 { + self.scroll(0); + return; + } + let scroll_pos = self.scroll_position.get(); + if scroll_pos > max_scroll_pos || (scroll_pos == max_scroll_pos - 1 && scroll_pos > 0) { + self.scroll(max_scroll_pos); + } + } + + fn scroll(&self, pos: usize) -> bool { + let item_count = self.filtered_mapping_count(); + let fixed_pos = pos.min(self.get_max_scroll_position(item_count)); + let scroll_pos = self.scroll_position.get(); + if fixed_pos == scroll_pos { + return false; + } + unsafe { + Reaper::get().medium_reaper().low().CoolSB_SetScrollPos( + self.view.require_window().raw() as _, + raw::SB_VERT as _, + fixed_pos as _, + 1, + ); + } + self.scroll_position.set(fixed_pos); + self.update_scroll_status_msg(item_count); + self.invalidate_mapping_rows(); + true + } + + fn update_scroll_status_msg(&self, item_count: usize) { + let from_pos = cmp::min(self.scroll_position.get() + 1, item_count); + let to_pos = cmp::min(from_pos + self.rows.len() - 1, item_count); + let scroll_status = ScrollStatus { + from_pos, + to_pos, + item_count, + }; + self.main_state + .borrow_mut() + .scroll_status + .set(scroll_status); + } + + fn get_max_item_index(&self, item_count: usize) -> usize { + cmp::max(0, item_count as isize - 1) as usize + } + + fn get_max_scroll_position(&self, item_count: usize) -> usize { + cmp::max(0, item_count as isize - self.rows.len() as isize) as usize + } + + fn filtered_mapping_count(&self) -> usize { + let shared_session = self.session(); + let session = shared_session.borrow(); + let main_state = self.main_state.borrow(); + if !main_state.filter_and_displayed_group_is_active() { + return session.mapping_count(self.active_compartment()); + } + session + .mappings(self.active_compartment()) + .filter(|m| Self::mapping_matches_filter(&session, &main_state, m, false)) + .count() + } + + // TODO-low Document all those scrolling functions. It needs explanation. + fn scroll_pos(&self, code: u32) -> Option { + let mut si = raw::SCROLLINFO { + cbSize: std::mem::size_of::() as _, + fMask: raw::SIF_PAGE | raw::SIF_POS | raw::SIF_RANGE | raw::SIF_TRACKPOS, + nMin: 0, + nMax: 0, + nPage: 0, + nPos: 0, + nTrackPos: 0, + }; + unsafe { + Reaper::get().medium_reaper().low().CoolSB_GetScrollInfo( + self.view.require_window().raw() as _, + raw::SB_VERT as _, + &mut si as *mut raw::SCROLLINFO as _, + ); + } + let min_pos = si.nMin; + let max_pos = si.nMax - (si.nPage as i32 - 1); + let result = match code { + raw::SB_LINEUP => cmp::max(si.nPos - 1, min_pos), + raw::SB_LINEDOWN => cmp::min(si.nPos + 1, max_pos), + raw::SB_PAGEUP => cmp::max(si.nPos - si.nPage as i32, min_pos), + raw::SB_PAGEDOWN => cmp::min(si.nPos + si.nPage as i32, max_pos), + raw::SB_THUMBTRACK => si.nTrackPos, + raw::SB_TOP => min_pos, + raw::SB_BOTTOM => max_pos, + _ => return None, + }; + Some(result as _) + } + + pub fn filtered_mappings<'a>( + session: &'a UnitModel, + main_state: &'a MainState, + compartment: CompartmentKind, + ignore_group: bool, + ) -> impl Iterator { + let filter_is_active = main_state.filter_and_displayed_group_is_active(); + session.mappings(compartment).filter(move |m| { + if filter_is_active { + Self::mapping_matches_filter(session, main_state, m, ignore_group) + } else { + true + } + }) + } + + /// Let mapping rows reflect the correct mappings. + fn invalidate_mapping_rows(&self) { + let mut row_index = 0; + let shared_session = self.session(); + let session = shared_session.borrow(); + let main_state = self.main_state.borrow(); + let compartment = main_state.active_compartment.get(); + let filtered_mappings: Vec<_> = + Self::filtered_mappings(&session, &main_state, compartment, false).collect(); + let scroll_pos = self.scroll_position.get(); + if scroll_pos < filtered_mappings.len() { + for mapping in &filtered_mappings[scroll_pos..] { + if row_index >= self.rows.len() { + break; + } + self.rows + .get(row_index) + .expect("impossible") + .set_mapping(Some((*mapping).clone())); + row_index += 1; + } + } + // If there are unused rows, clear them + for i in row_index..self.rows.len() { + self.rows.get(i).expect("impossible").set_mapping(None); + } + self.invalidate_empty_group_controls( + &session, + &main_state, + compartment, + filtered_mappings.len(), + ); + } + + fn mapping_matches_filter( + session: &UnitModel, + main_state: &MainState, + mapping: &SharedMapping, + ignore_group: bool, + ) -> bool { + let mapping = mapping.borrow(); + if !ignore_group { + if let Some(group_filter) = main_state.displayed_group_for_active_compartment() { + if !group_filter.matches(&mapping) { + return false; + } + } + } + if let Some(source_filter) = main_state.source_filter.get_ref() { + let mapping_source = mapping.source_model.create_source(); + if !source_filter.matches(&mapping_source) { + return false; + } + } + if let Some(filter_target) = main_state.target_filter.get_ref() { + if !mapping + .with_context(session.extended_context()) + .has_target(filter_target) + { + return false; + } + } + let search_expression = main_state.search_expression.get_ref(); + if !search_expression.is_empty() + && !search_expression.matches(&mapping.effective_name()) + && !search_expression.matches_any_tag(mapping.tags()) + && !search_expression.matches_any_tag_in_group(&mapping, session) + { + return false; + } + true + } + + fn invalidate_all_controls(&self) { + self.invalidate_mapping_rows(); + self.panel_manager().borrow_mut().close_orphan_panels(); + self.invalidate_scroll_info(); + } + + fn invalidate_empty_group_controls( + &self, + session: &UnitModel, + main_state: &MainState, + compartment: CompartmentKind, + displayed_mapping_count: usize, + ) { + let (label_text, button_text) = if displayed_mapping_count == 0 { + use EmptyMappingListCase::*; + match self.determine_empty_mapping_list_case(session, main_state, compartment) { + CompartmentEmpty => (Some("There are no mappings in this compartment."), None), + FilterSet | GroupAndFilterSetFilterIsProblem => ( + Some("No mapping matching filter and search string."), + Some("Clear filter and search string".to_owned()), + ), + GroupSet => ( + Some("This group is empty."), + Some(format!( + "Show {} mappings in all groups", + session.mapping_count(compartment) + )), + ), + GroupAndFilterSetGroupIsProblem => ( + Some("There are matching mappings in other groups."), + Some("Show mappings in all groups".to_owned()), + ), + } + } else { + (None, None) + }; + self.view + .require_control(root::ID_GROUP_IS_EMPTY_TEXT) + .set_text_or_hide(label_text); + self.view + .require_control(root::ID_DISPLAY_ALL_GROUPS_BUTTON) + .set_text_or_hide(button_text); + } + + fn determine_empty_mapping_list_case( + &self, + session: &UnitModel, + main_state: &MainState, + compartment: CompartmentKind, + ) -> EmptyMappingListCase { + if session.mapping_count(compartment) == 0 { + EmptyMappingListCase::CompartmentEmpty + } else { + let filter_is_active = main_state.filter_is_active(); + let displayed_group = main_state.displayed_group[compartment].get(); + if !filter_is_active && displayed_group.is_some() { + EmptyMappingListCase::GroupSet + } else if filter_is_active && displayed_group.is_none() { + EmptyMappingListCase::FilterSet + } else { + // Both filter is active and displayed group. + // Try filter while ignoring group. + if Self::filtered_mappings(session, main_state, compartment, true).count() == 0 { + EmptyMappingListCase::GroupAndFilterSetFilterIsProblem + } else { + EmptyMappingListCase::GroupAndFilterSetGroupIsProblem + } + } + } + } + + fn register_listeners(self: SharedView) { + let shared_session = self.session(); + let session = shared_session.borrow(); + let main_state = self.main_state.borrow(); + self.when(session.everything_changed(), |view, _| { + view.invalidate_all_controls(); + }); + let main_state_clone = self.main_state.clone(); + self.when( + session.mapping_list_changed(), + move |view, (compartment, new_mapping_id)| { + if compartment == main_state_clone.borrow().active_compartment.get() { + view.invalidate_all_controls(); + if let Some(id) = new_mapping_id { + view.ensure_mapping_is_visible(compartment, id); + } + } + }, + ); + self.when( + main_state + .source_filter + .changed() + .merge(main_state.target_filter.changed()) + .merge(main_state.search_expression.changed()) + .merge(main_state.active_compartment.changed()) + .merge(main_state.displayed_group_for_any_compartment_changed()) + .merge(session.group_list_changed().map_to(())), + |view, _| { + if !view.scroll(0) { + // No scrolling was necessary. But that also means, the rows were not + // invalidated. Do it now! + view.invalidate_mapping_rows(); + } + view.invalidate_scroll_info(); + }, + ); + } + + fn fix_empty_mapping_list(&self) -> Result<(), &'static str> { + use EmptyMappingListCase::*; + let session = self.session(); + let session = session.borrow(); + let mut main_state = self.main_state.borrow_mut(); + let compartment = main_state.active_compartment.get(); + match self.determine_empty_mapping_list_case(&session, &main_state, compartment) { + CompartmentEmpty => return Err("can't be fixed"), + FilterSet | GroupAndFilterSetFilterIsProblem => main_state.clear_all_filters(), + GroupSet | GroupAndFilterSetGroupIsProblem => { + main_state.clear_displayed_group_for_active_compartment() + } + }; + Ok(()) + } + + fn open_context_menu(&self, location: Point) -> Result<(), &'static str> { + #[derive(Default)] + enum MenuAction { + #[default] + None, + PasteMappings(Vec), + } + let pure_menu = { + use swell_ui::menu_tree::*; + let data_object_from_clipboard = get_text_from_clipboard() + .and_then(|text| deserialize_data_object_from_json(&text).ok()); + let entries = vec![{ + let desc = match data_object_from_clipboard { + Some(DataObject::Mapping(Envelope { value: m, .. })) => Some(( + format!("Paste mapping \"{}\" (insert here)", &m.name), + vec![*m], + )), + Some(DataObject::Mappings(Envelope { value: vec, .. })) => { + Some((format!("Paste {} mappings (insert here)", vec.len()), vec)) + } + _ => None, + }; + if let Some((label, datas)) = desc { + item(label, MenuAction::PasteMappings(datas)) + } else { + disabled_item("Paste") + } + }]; + anonymous_menu(entries) + }; + let menu_action = self + .view + .require_window() + .open_popup_menu(pure_menu, location) + .ok_or("no entry selected")?; + match menu_action { + MenuAction::None => {} + MenuAction::PasteMappings(datas) => { + let shared_session = self.session(); + let main_state = self.main_state.borrow(); + let group_id = main_state + .displayed_group_for_active_compartment() + .map(|f| f.group_id()) + .unwrap_or_default(); + let compartment = main_state.active_compartment.get(); + let _ = paste_mappings( + Envelope::new(None, datas), + shared_session, + compartment, + None, + group_id, + ); + } + } + Ok(()) + } + + fn when( + self: &SharedView, + event: impl LocalObservable<'static, Item = I, Err = ()> + 'static, + reaction: impl Fn(SharedView, I) + 'static + Clone, + ) { + when(event.take_until(self.view.closed())) + .with(Rc::downgrade(self)) + .do_sync(reaction); + } +} + +impl View for MappingRowsPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_MAPPING_ROWS_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + #[cfg(target_family = "unix")] + unsafe { + let reaper = Reaper::get().medium_reaper().low(); + let hwnd = window.raw() as _; + reaper.InitializeCoolSB(hwnd); + #[cfg(target_os = "macos")] + if !Window::dark_mode_is_enabled() { + // If not in dark mode, using REAPER's dark scrollbar looks weird. + // Let's use the default bright toolbar of CoolSB. It looks ugly but + // at least it's brighter. + reaper.CoolSB_SetThemeIndex(hwnd, -1); + } + } + window.move_to_dialog_units(self.position); + self.open_mapping_rows(window); + self.invalidate_mapping_rows(); + self.invalidate_scroll_info(); + self.register_listeners(); + true + } + + #[allow(unused_variables)] + fn on_destroy(self: SharedView, window: Window) { + #[cfg(target_family = "unix")] + unsafe { + Reaper::get() + .medium_reaper() + .low() + .UninitializeCoolSB(window.raw() as _); + } + } + + fn scrolled_vertically(self: SharedView, code: u32) -> bool { + match self.scroll_pos(code) { + None => false, + Some(scroll_pos) => { + // TODO-low In the original ReaLearn we debounce this by 50ms. This is not yet + // possible with rxRust. It's possible to implement this without Rx though. But + // right now it doesn't seem to be even necessary. We could also just update + // a few controls when thumb tracking, not everything. Probably even better! + self.scroll(scroll_pos); + true + } + } + } + + fn mouse_wheel_turned(self: SharedView, distance: i32) -> bool { + let code = if distance < 0 { + raw::SB_LINEDOWN + } else { + raw::SB_LINEUP + }; + let new_scroll_pos = self.scroll_pos(code).expect("impossible"); + self.scroll(new_scroll_pos); + true + } + + fn context_menu_wanted(self: SharedView, location: Point) -> bool { + let _ = self.open_context_menu(location); + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + root::ID_DISPLAY_ALL_GROUPS_BUTTON => { + let _ = self.fix_empty_mapping_list(); + } + _ => {} + } + } +} + +impl Drop for MappingRowsPanel { + fn drop(&mut self) { + debug!("Dropping mapping rows panel..."); + } +} + +enum EmptyMappingListCase { + CompartmentEmpty, + FilterSet, + GroupSet, + GroupAndFilterSetFilterIsProblem, + GroupAndFilterSetGroupIsProblem, +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/menus.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/menus.rs new file mode 100644 index 0000000..e2c4ac5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/menus.rs @@ -0,0 +1,994 @@ +use crate::application::{UnitModel, WeakUnitModel}; +use crate::domain::{ + compartment_param_index_iter, probe_stream_deck_devices, CompartmentKind, + CompartmentParamIndex, CompartmentParams, ControlInput, FeedbackOutput, MappingId, + MidiControlInput, MidiDestination, OscDeviceId, ProbedStreamDeckDevice, ReaperTargetType, + StreamDeckDeviceId, TargetSection, +}; +use crate::infrastructure::data::{CommonPresetInfo, OscDevice}; +use crate::infrastructure::plugin::{ActionSection, BackboneShell, ACTION_DEFS}; +use crate::infrastructure::ui::Item; +use base::hash_util::NonCryptoIndexMap; +use camino::Utf8Path; +use derive_more::Display; +use helgobox_api::persistence::VirtualControlElementCharacter; +use indexmap::IndexMap; +use reaper_high::{FxChainContext, MidiInputDevice, MidiOutputDevice, Reaper}; +use reaper_medium::MidiInputDeviceId; +use std::ffi::CString; +use std::fmt::Display; +use std::hash::Hash; +use std::iter; +use strum::IntoEnumIterator; +use swell_ui::menu_tree::{ + anonymous_menu, item, item_with_opts, menu, separator, Entry, ItemOpts, Menu, +}; + +pub enum ControlInputMenuAction { + Nothing, + SelectControlInput(ControlInput), + ManageOsc(OscDeviceManagementAction), + ToggleWantsKeyboardInput, + SelectStreamDeckDevice(Option), +} + +pub fn midi_device_input_menu( + current_value: Option, + none_label: &str, +) -> Menu> { + let (open_midi_devs, closed_midi_devs) = get_open_and_closed_midi_input_devs(); + let unavailable_midi_input_devices = closed_midi_devs + .into_iter() + .map(|dev| build_midi_input_dev_menu_item(dev, current_value)) + .collect(); + let entries = iter::once(item_with_opts( + none_label, + ItemOpts { + enabled: true, + checked: current_value.is_none(), + }, + None, + )) + .chain( + open_midi_devs + .into_iter() + .map(|dev| build_midi_input_dev_menu_item(dev, current_value)), + ) + .chain([create_category_menu( + "Unavailable MIDI input devices", + unavailable_midi_input_devices, + )]); + anonymous_menu(entries.collect()) +} + +fn get_open_and_closed_midi_input_devs() -> (Vec, Vec) { + Reaper::get() + .midi_input_devices() + .filter(|d| d.is_available()) + .partition(|dev| dev.is_open()) +} + +pub fn control_input_menu( + current_value: ControlInput, + current_wants_keyboard_input: bool, + current_stream_deck_dev_id: Option, +) -> Menu { + let fx_input = ControlInput::Midi(MidiControlInput::FxInput); + let (open_midi_devs, closed_midi_devs) = get_open_and_closed_midi_input_devs(); + let osc_device_manager = BackboneShell::get().osc_device_manager(); + let osc_device_manager = osc_device_manager.borrow(); + let (open_osc_devs, closed_osc_devs): (Vec<_>, Vec<_>) = { + osc_device_manager + .devices() + .partition(|dev| dev.input_status().is_connected()) + }; + let stream_deck_devices = probe_stream_deck_devices().unwrap_or_default(); + let unavailable_midi_input_devs = closed_midi_devs + .into_iter() + .map(|dev| build_control_input_midi_input_dev_menu_item(dev, current_value)) + .collect(); + let unavailable_osc_devs = closed_osc_devs + .into_iter() + .map(|dev| build_osc_input_dev_menu_item(dev, current_value)) + .collect(); + let unavailable_stream_deck_devs = stream_deck_devices + .iter() + .filter(|d| !d.available) + .map(|dev| build_stream_deck_dev_menu_item(dev, current_stream_deck_dev_id)) + .collect(); + let available_stream_deck_devs = stream_deck_devices + .iter() + .filter(|d| d.available) + .map(|dev| build_stream_deck_dev_menu_item(dev, current_stream_deck_dev_id)); + let entries = [item_with_opts( + CONTROL_INPUT_MIDI_FX_INPUT_LABEL, + ItemOpts { + enabled: true, + checked: current_value == fx_input, + }, + ControlInputMenuAction::SelectControlInput(fx_input), + )] + .into_iter() + .chain( + open_midi_devs + .into_iter() + .map(|dev| build_control_input_midi_input_dev_menu_item(dev, current_value)), + ) + .chain([ + create_category_menu( + "Unavailable MIDI input devices", + unavailable_midi_input_devs, + ), + separator(), + ]) + .chain( + open_osc_devs + .into_iter() + .map(|dev| build_osc_input_dev_menu_item(dev, current_value)), + ) + .chain([ + create_category_menu("Unavailable OSC devices", unavailable_osc_devs), + menu( + "Manage OSC devices", + osc_device_management_menu_entries(ControlInputMenuAction::ManageOsc), + ), + separator(), + ]) + .chain([item_with_opts( + "Stream Deck: ", + ItemOpts { + enabled: true, + checked: current_stream_deck_dev_id.is_none(), + }, + ControlInputMenuAction::SelectStreamDeckDevice(None), + )]) + .chain(available_stream_deck_devs) + .chain([ + create_category_menu("Unavailable Stream Decks", unavailable_stream_deck_devs), + separator(), + ]) + .chain([item_with_opts( + CONTROL_INPUT_KEYBOARD_LABEL, + ItemOpts { + enabled: true, + checked: current_wants_keyboard_input, + }, + ControlInputMenuAction::ToggleWantsKeyboardInput, + )]); + let mut entries: Vec<_> = entries.collect(); + if Reaper::get() + .medium_reaper() + .low() + .pointers() + .IsWindowTextField + .is_none() + { + let tip_item = item_with_opts( + "Please update to at least REAPER 7.23 to temporarily disable keyboard control while entering text!", + ItemOpts { + enabled: false, + checked: false, + }, + ControlInputMenuAction::Nothing, + ); + entries.push(tip_item); + } + anonymous_menu(entries) +} + +pub enum FeedbackOutputMenuAction { + SelectFeedbackOutput(Option), + ManageOsc(OscDeviceManagementAction), +} + +pub fn feedback_output_menu( + current_value: Option, +) -> Menu { + let fx_output = Some(FeedbackOutput::Midi(MidiDestination::FxOutput)); + let (open_midi_devs, closed_midi_devs): (Vec<_>, Vec<_>) = Reaper::get() + .midi_output_devices() + .filter(|d| d.is_available()) + .partition(|dev| dev.is_open()); + let osc_device_manager = BackboneShell::get().osc_device_manager(); + let osc_device_manager = osc_device_manager.borrow(); + let (open_osc_devs, closed_osc_devs): (Vec<_>, Vec<_>) = { + osc_device_manager + .devices() + .partition(|dev| dev.input_status().is_connected()) + }; + let unsvailable_midi_devs = closed_midi_devs + .into_iter() + .map(|dev| build_midi_output_dev_menu_item(dev, current_value)) + .collect(); + let unavailable_osc_devs = closed_osc_devs + .into_iter() + .map(|dev| build_osc_output_dev_menu_item(dev, current_value)) + .collect(); + let entries = iter::once(item_with_opts( + FEEDBACK_OUTPUT_NONE_LABEL, + ItemOpts { + enabled: true, + checked: current_value.is_none(), + }, + FeedbackOutputMenuAction::SelectFeedbackOutput(None), + )) + .chain(iter::once(separator())) + .chain(iter::once(item_with_opts( + FEEDBACK_OUTPUT_MIDI_FX_OUTPUT, + ItemOpts { + enabled: true, + checked: current_value == fx_output, + }, + FeedbackOutputMenuAction::SelectFeedbackOutput(fx_output), + ))) + .chain( + open_midi_devs + .into_iter() + .map(|dev| build_midi_output_dev_menu_item(dev, current_value)), + ) + .chain([ + create_category_menu("Unavailable MIDI output devices", unsvailable_midi_devs), + separator(), + ]) + .chain( + open_osc_devs + .into_iter() + .map(|dev| build_osc_output_dev_menu_item(dev, current_value)), + ) + .chain([ + create_category_menu("Unavailable OSC devices", unavailable_osc_devs), + menu( + "Manage OSC devices", + osc_device_management_menu_entries(FeedbackOutputMenuAction::ManageOsc), + ), + separator(), + ]); + anonymous_menu(entries.collect()) +} + +pub const CONTROL_INPUT_MIDI_FX_INPUT_LABEL: &str = "MIDI: "; +pub const CONTROL_INPUT_KEYBOARD_LABEL: &str = "Computer keyboard"; +pub const FEEDBACK_OUTPUT_MIDI_FX_OUTPUT: &str = "MIDI: "; +pub const FEEDBACK_OUTPUT_NONE_LABEL: &str = ""; + +fn build_control_input_midi_input_dev_menu_item( + dev: MidiInputDevice, + current_value: ControlInput, +) -> Entry { + let control_input = ControlInput::Midi(MidiControlInput::Device(dev.id())); + item_with_opts( + get_midi_input_device_list_label(dev), + ItemOpts { + enabled: true, + checked: current_value == control_input, + }, + ControlInputMenuAction::SelectControlInput(control_input), + ) +} + +fn build_midi_input_dev_menu_item( + dev: MidiInputDevice, + current_value: Option, +) -> Entry> { + item_with_opts( + get_midi_input_device_list_label(dev), + ItemOpts { + enabled: true, + checked: current_value == Some(dev.id()), + }, + Some(dev.id()), + ) +} + +fn build_midi_output_dev_menu_item( + dev: MidiOutputDevice, + current_value: Option, +) -> Entry { + let feedback_output = Some(FeedbackOutput::Midi(MidiDestination::Device(dev.id()))); + item_with_opts( + get_midi_output_device_list_label(dev), + ItemOpts { + enabled: true, + checked: current_value == feedback_output, + }, + FeedbackOutputMenuAction::SelectFeedbackOutput(feedback_output), + ) +} + +fn build_osc_input_dev_menu_item( + dev: &OscDevice, + current_value: ControlInput, +) -> Entry { + let control_input = ControlInput::Osc(*dev.id()); + item_with_opts( + get_osc_device_list_label(dev, false), + ItemOpts { + enabled: true, + checked: current_value == control_input, + }, + ControlInputMenuAction::SelectControlInput(control_input), + ) +} + +fn build_osc_output_dev_menu_item( + dev: &OscDevice, + current_value: Option, +) -> Entry { + let feedback_output = Some(FeedbackOutput::Osc(*dev.id())); + item_with_opts( + get_osc_device_list_label(dev, true), + ItemOpts { + enabled: true, + checked: current_value == feedback_output, + }, + FeedbackOutputMenuAction::SelectFeedbackOutput(feedback_output), + ) +} + +fn build_stream_deck_dev_menu_item( + dev: &ProbedStreamDeckDevice, + current: Option, +) -> Entry { + item_with_opts( + format!("Stream Deck: {}", dev.dev.name), + ItemOpts { + enabled: true, + checked: current == Some(dev.dev.id), + }, + ControlInputMenuAction::SelectStreamDeckDevice(Some(dev.dev.id)), + ) +} + +pub fn get_osc_device_list_label(dev: &OscDevice, is_output: bool) -> String { + format!("OSC: {}", dev.get_list_label(is_output)) +} + +pub fn get_midi_input_device_list_label(dev: MidiInputDevice) -> String { + get_midi_device_list_label( + dev.name().unwrap_or_default(), + dev.id().get(), + MidiDeviceStatus::from_flags(dev.is_open(), dev.is_connected()), + ) +} + +pub fn get_midi_output_device_list_label(dev: MidiOutputDevice) -> String { + get_midi_device_list_label( + dev.name().unwrap_or_default(), + dev.id().get(), + MidiDeviceStatus::from_flags(dev.is_open(), dev.is_connected()), + ) +} + +fn get_midi_device_list_label(name: CString, raw_id: u8, status: MidiDeviceStatus) -> String { + format!("MIDI: [{}] {}{}", raw_id, name.to_string_lossy(), status) +} + +pub fn extension_menu() -> Menu<&'static str> { + anonymous_menu(vec![menu("Helgobox", helgbox_menu_entries().collect())]) +} + +fn helgbox_menu_entries() -> impl Iterator> { + ActionSection::iter() + .filter(|section| *section != ActionSection::General) + .filter_map(|section| section.build_menu()) + .chain(iter::once(separator())) + .chain( + ACTION_DEFS + .iter() + .filter(|def| def.section == ActionSection::General && def.should_appear_in_menu()) + .map(|def| def.build_menu_item()), + ) + .map(|mut entry| { + assign_command_ids_recursively(&mut entry); + entry + }) +} + +pub fn reaper_target_type_menu(current_value: ReaperTargetType) -> Menu { + let entries = TargetSection::iter().map(|section| { + let sub_entries = ReaperTargetType::iter() + .filter(|t| t.definition().section == section) + .map(|t| { + item_with_opts( + t.definition().name, + ItemOpts { + enabled: true, + checked: t == current_value, + }, + t, + ) + }); + menu(section.to_string(), sub_entries.collect()) + }); + anonymous_menu(entries.collect()) +} + +pub fn virtual_control_element_character_menu( + current_value: VirtualControlElementCharacter, +) -> Menu { + let entries = VirtualControlElementCharacter::iter().map(|t| { + item_with_opts( + t.to_string(), + ItemOpts { + enabled: true, + checked: t == current_value, + }, + t, + ) + }); + anonymous_menu(entries.collect()) +} + +pub fn menu_containing_realearn_params( + session: &WeakUnitModel, + compartment: CompartmentKind, + current_value: CompartmentParamIndex, +) -> Menu { + let session = session.upgrade().expect("session gone"); + let session = session.borrow(); + anonymous_menu( + compartment_param_index_iter() + .map(|i| { + let param_name = get_optional_param_name(&session, compartment, Some(i)); + item_with_opts( + param_name, + ItemOpts { + enabled: true, + checked: i == current_value, + }, + i, + ) + }) + .collect(), + ) +} + +pub fn menu_containing_realearn_params_optional( + session: &WeakUnitModel, + compartment: CompartmentKind, + current_value: Option, +) -> Menu> { + let session = session.upgrade().expect("session gone"); + let session = session.borrow(); + anonymous_menu( + iter::once(item_with_opts( + NONE, + ItemOpts { + enabled: true, + checked: current_value.is_none(), + }, + None, + )) + .chain(compartment_param_index_iter().map(|i| { + let value = Some(i); + let param_name = get_optional_param_name(&session, compartment, value); + item_with_opts( + param_name, + ItemOpts { + enabled: true, + checked: value == current_value, + }, + value, + ) + })) + .collect(), + ) +} + +pub fn menu_containing_mappings( + session: &WeakUnitModel, + compartment: CompartmentKind, + current_value: Option, +) -> Menu> { + let session = session.upgrade().expect("session gone"); + let session = session.borrow(); + let none_item = item_with_opts( + NONE, + ItemOpts { + enabled: true, + checked: current_value.is_none(), + }, + None, + ); + let group_items = session.groups_sorted(compartment).map(|group| { + let group = group.borrow(); + let group_id = group.id(); + menu( + group.effective_name(), + session + .mappings(compartment) + .filter_map(|mapping| { + // If borrowing fails, we know it's our own mapping + let mapping = mapping.try_borrow().ok()?; + if mapping.group_id() != group_id { + return None; + } + let mapping_id = mapping.id(); + let menu_item = item_with_opts( + mapping.effective_name(), + ItemOpts { + enabled: true, + checked: Some(mapping_id) == current_value, + }, + Some(mapping_id), + ); + Some(menu_item) + }) + .collect(), + ) + }); + anonymous_menu(iter::once(none_item).chain(group_items).collect()) +} + +pub fn menu_containing_sessions( + this_session: &UnitModel, + current_other_session_id: Option<&str>, +) -> Menu> { + let this_item = item_with_opts( + THIS, + ItemOpts { + enabled: true, + checked: current_other_session_id.is_none(), + }, + None, + ); + let items: Vec<_> = BackboneShell::get().with_unit_infos(|sessions| { + let instance_items = sessions.iter().filter_map(|session| { + let other_session = session.unit_model.upgrade()?; + let other_session = other_session.try_borrow().ok()?; + // Exclude certain sessions + if other_session.unit_id() == this_session.unit_id() { + // Don't include our own session. + return None; + } + let this_chain_context = this_session + .processor_context() + .containing_fx() + .chain() + .context(); + let other_chain_context = other_session + .processor_context() + .containing_fx() + .chain() + .context(); + match (this_chain_context, other_chain_context) { + // It's okay if this instance is on the monitoring FX chain and the other one + // as well (global => global). + (FxChainContext::Monitoring, FxChainContext::Monitoring) => {} + // It's okay if this instance is in a specific project and the other one on the + // monitoring FX chain (local => global). + (FxChainContext::Track { .. }, FxChainContext::Monitoring) => {} + // It's okay if this instance is in a specific project and the other one in the same + // project. + ( + FxChainContext::Track { + track: this_track, .. + }, + FxChainContext::Track { + track: other_track, .. + }, + ) if other_track.project() == this_track.project() => {} + // All other combinations are not allowed! + _ => return None, + } + // Build item + let other_session_id = other_session.unit_key().to_string(); + let item = item_with_opts( + other_session.to_string(), + ItemOpts { + enabled: true, + checked: Some(other_session_id.as_str()) == current_other_session_id, + }, + Some(other_session_id), + ); + Some(item) + }); + iter::once(this_item).chain(instance_items).collect() + }); + anonymous_menu(items) +} + +pub fn menu_containing_banks( + session: &WeakUnitModel, + compartment: CompartmentKind, + param_index: CompartmentParamIndex, + current_value: u32, +) -> Menu { + let session = session.upgrade().expect("session gone"); + let session = session.borrow(); + let bank_param = session + .params() + .compartment_params(compartment) + .at(param_index); + let menu_items = if let Some(discrete_values) = bank_param.setting().discrete_values() { + discrete_values + .enumerate() + // Don't block GUI if we come across a parameter that has insanely many + // discrete values (and is probably not intended to be used with banks). + .take(500) + .map(|(i, s)| bank_item(s.to_string(), i, current_value)) + .collect() + } else { + // For continuous parameters we just choose a default of 100 values. + let bank_count = 100; + (0..bank_count) + .map(|i| bank_item(i.to_string(), i, current_value)) + .collect() + }; + anonymous_menu(menu_items) +} + +pub fn get_optional_param_name( + session: &UnitModel, + compartment: CompartmentKind, + index: Option, +) -> String { + match index { + None => "".to_owned(), + Some(i) => { + let params = session.params().compartment_params(compartment); + get_param_name(params, i) + } + } +} + +pub fn get_param_name(params: &CompartmentParams, index: CompartmentParamIndex) -> String { + let param_name = params.get_parameter_name(index); + format!("{}. {}", index.get() + 1, param_name) +} + +pub fn get_bank_name( + session: &UnitModel, + item: &dyn Item, + bank_param_index: CompartmentParamIndex, + bank_index: u32, +) -> String { + let bank_param = session + .params() + .compartment_params(item.compartment()) + .at(bank_param_index); + if let Some(label) = bank_param.setting().find_label_for_value(bank_index) { + label.to_owned() + } else { + bank_index.to_string() + } +} + +fn bank_item(text: String, bank_index: usize, current_bank_index: u32) -> Entry { + item_with_opts( + text, + ItemOpts { + enabled: true, + checked: bank_index == current_bank_index as usize, + }, + bank_index as u32, + ) +} + +pub fn menu_containing_compartment_presets( + compartment: CompartmentKind, + current_value: Option<&str>, +) -> Menu> { + let preset_manager = BackboneShell::get().compartment_preset_manager(compartment); + let preset_manager = preset_manager.borrow(); + anonymous_menu( + iter::once(item_with_opts( + NONE, + ItemOpts { + enabled: true, + checked: current_value.is_none(), + }, + None, + )) + .chain(build_compartment_preset_menu_entries( + preset_manager.common_preset_infos(), + |info| Some(info.id.clone()), + current_value, + )) + .collect(), + ) +} + +trait Categorizer<'a> { + type Category: Display + Eq + Hash + Ord; + + fn extract_category(&self, info: &'a CommonPresetInfo) -> Self::Category; + + fn create_sub_entries( + &self, + infos: impl Iterator + 'a, + build_id: &impl Fn(&'a CommonPresetInfo) -> T, + current_id: Option<&'a str>, + ) -> Vec>; +} + +pub fn build_compartment_preset_menu_entries<'a, T>( + preset_infos: impl Iterator + 'a, + build_id: impl Fn(&'a CommonPresetInfo) -> T, + current_id: Option<&'a str>, +) -> Vec> +where + T: 'static, +{ + build_compartment_preset_menu_entries_flexible( + preset_infos, + &build_id, + current_id, + &OriginCategorizer, + ) +} + +pub fn build_enum_variants_menu( + variants: impl IntoIterator, + current_variant: V, +) -> Menu +where + V: Copy + Display + PartialEq, +{ + let entries = variants + .into_iter() + .map(|v| { + item_with_opts( + v.to_string(), + ItemOpts { + enabled: true, + checked: v == current_variant, + }, + v, + ) + }) + .collect(); + anonymous_menu(entries) +} + +fn build_compartment_preset_menu_entries_flexible<'a, T, C>( + preset_infos: impl Iterator + 'a, + build_id: &impl Fn(&'a CommonPresetInfo) -> T, + current_id: Option<&'a str>, + categorizer: &'a C, +) -> Vec> +where + T: 'static, + C: Categorizer<'a>, +{ + let mut categorized_infos: NonCryptoIndexMap> = + IndexMap::default(); + for info in preset_infos { + let category = categorizer.extract_category(info); + categorized_infos.entry(category).or_default().push(info); + } + categorized_infos.sort_keys(); + categorized_infos + .into_iter() + .map(move |(category, mut infos)| { + infos.sort_by_key(|info| &info.meta_data.name); + let entries = categorizer.create_sub_entries(infos.into_iter(), build_id, current_id); + create_category_menu(category, entries) + }) + .collect() +} + +fn build_compartment_preset_menu_entries_internal<'a, T: 'static>( + preset_infos: impl Iterator + 'a, + build_id: &impl Fn(&'a CommonPresetInfo) -> T, + current_id: Option<&'a str>, +) -> Vec> { + preset_infos + .map(move |info| { + let id = build_id(info); + item_with_opts( + info.meta_data.name.clone(), + ItemOpts { + enabled: true, + checked: current_id.is_some_and(|id| id == info.id), + }, + id, + ) + }) + .collect() +} + +pub enum OscDeviceManagementAction { + EditNewOscDevice, + EditExistingOscDevice(OscDeviceId), + RemoveOscDevice(OscDeviceId), + ToggleOscDeviceControl(OscDeviceId), + ToggleOscDeviceFeedback(OscDeviceId), + ToggleOscDeviceBundles(OscDeviceId), +} + +fn osc_device_management_menu_entries

ReaLearn Projection

+
+ Helgobox server QR code + {% if !server_is_running %} +
+ Server
+ not
+ running
+
+ {% endif %} +
+ +

Introduction

+

+ Welcome to ReaLearn's projection feature. This allows you to project a schematic representation of your currently + active controller onto a mobile device (e.g. a tablet computer). You can put this device close to your controller in + order to see immediately which control element is mapped to which parameter. +

+

First-time setup

+

+ In order to let your mobile device connect to ReaLearn, ReaLearn needs to act as a so-called web + server. By default, this server is switched off. In order to set it up correctly, please follow below + instructions. Don't worry, this needs to be done only once. After this initial effort, pairing your mobile device + with another ReaLearn instance is a matter of a few seconds (you just need to scan a QR code). +

+
    +
  1. + {% if server_is_running %} + Congratulations, the server is running. + {% else %} + Start the server! + {% endif %} + {% if server_is_running %} + 😃 +
      + {% if server_is_enabled %} +
    • + It will automatically be started again next time you start REAPER with ReaLearn. If you decide to not + use the projection feature anymore, disable the server in the context menu of ReaLearn's + header panel. +
    • + {% else %} +
    • + You have disabled it though. This will take effect on the next start of REAPER. You can enable it again + using the context menu of ReaLearn's header panel. +
    • + {% endif %} +
    + {% else %} +
      +
    • + Please open the context menu of ReaLearn's header panel (accessible via right-click on Windows and + Linux, control-click on macOS) and choose Server → Enabled. It's possible that your firewall will + ask you for permission to open a network port for external devices. In that case, say yes! +
    • +
    • + After you have done this, please refresh this page or click ReaLearn's Projection + button again. +
    • +
    + {% endif %} +
  2. +
  3. + Try to open the ReaLearn Companion web app on this + computer (requires internet connection and takes a while to load when using it the first time). +
      +
    • + If this works, you know the server is indeed running. That means you can already use projections right + here on this computer. +
    • +
    • + It's more interesting however to project the controller routing to another device. Let's set this up in + the next steps. +
    • +
    +
  4. +
  5. + Try to open ReaLearn's Companion app on your mobile device. +
      +
    • + At the moment there's an + Android app + and the web app. The iOS app has not been published yet. +
    • +
    • + If your device doesn't have a camera, point your mobile browser to the following address: + {{ companion_web_app_url }}. When + asked, enter the following connection details: +

( + build_payload: impl Fn(OscDeviceManagementAction) -> P, +) -> Vec> { + let dev_manager = BackboneShell::get().osc_device_manager(); + let dev_manager = dev_manager.borrow(); + iter::once(item( + "", + build_payload(OscDeviceManagementAction::EditNewOscDevice), + )) + .chain(dev_manager.devices().map(|dev| { + let dev_id = *dev.id(); + menu( + dev.name(), + vec![ + item( + "Edit...", + build_payload(OscDeviceManagementAction::EditExistingOscDevice(dev_id)), + ), + item( + "Remove", + build_payload(OscDeviceManagementAction::RemoveOscDevice(dev_id)), + ), + item_with_opts( + "Enabled for control", + ItemOpts { + enabled: true, + checked: dev.is_enabled_for_control(), + }, + build_payload(OscDeviceManagementAction::ToggleOscDeviceControl(dev_id)), + ), + item_with_opts( + "Enabled for feedback", + ItemOpts { + enabled: true, + checked: dev.is_enabled_for_feedback(), + }, + build_payload(OscDeviceManagementAction::ToggleOscDeviceFeedback(dev_id)), + ), + item_with_opts( + "Can deal with OSC bundles", + ItemOpts { + enabled: true, + checked: dev.can_deal_with_bundles(), + }, + build_payload(OscDeviceManagementAction::ToggleOscDeviceBundles(dev_id)), + ), + ], + ) + })) + .collect() +} + +pub const NONE: &str = ""; +pub const THIS: &str = ""; + +/// Interprets the item payloads as REAPER command names and uses them to lookup the corresponding REAPER command +/// IDs. +/// +/// This is useful for top-level menus. +pub fn assign_command_ids_recursively(entry: &mut Entry<&'static str>) { + match entry { + Entry::Menu(m) => { + m.id = 0; + for e in &mut m.entries { + assign_command_ids_recursively(e); + } + } + Entry::Item(i) => { + let command_id = Reaper::get() + .medium_reaper() + .named_command_lookup(format!("_{}", i.result)); + i.id = command_id.map(|id| id.get()).unwrap_or(0); + } + _ => {} + } +} + +#[derive(Display)] +enum MidiDeviceStatus { + #[display(fmt = " ")] + Disconnected, + #[display(fmt = " ")] + ConnectedButDisabled, + #[display(fmt = "")] + Connected, +} + +impl MidiDeviceStatus { + fn from_flags(open: bool, connected: bool) -> MidiDeviceStatus { + use MidiDeviceStatus::*; + match (open, connected) { + (false, false) => Disconnected, + (false, true) => ConnectedButDisabled, + // Shouldn't happen but cope with it. + (true, false) => Disconnected, + (true, true) => Connected, + } + } +} + +struct OriginCategorizer; + +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, derive_more::Display)] +enum OriginCategory<'a> { + #[display(fmt = "Factory")] + Factory, + #[display(fmt = "User ({_0})")] + UserSorted(&'a str), + #[display(fmt = "User (Unsorted)")] + UserUnsorted, +} +impl<'a> Categorizer<'a> for OriginCategorizer { + type Category = OriginCategory<'a>; + + fn extract_category(&self, info: &'a CommonPresetInfo) -> Self::Category { + let path = Utf8Path::new(&info.id); + if path.components().count() == 1 { + // Preset directly in user preset root = "Unsorted" + OriginCategory::UserUnsorted + } else { + // Preset in subdirectory (good) + let component = path + .components() + .next() + .expect("preset ID should have at least one component") + .as_str(); + if component == "factory" { + OriginCategory::Factory + } else { + OriginCategory::UserSorted(component) + } + } + } + + fn create_sub_entries( + &self, + infos: impl Iterator + 'a, + build_id: &impl Fn(&'a CommonPresetInfo) -> T, + current_id: Option<&'a str>, + ) -> Vec> { + build_compartment_preset_menu_entries_flexible( + infos.into_iter(), + build_id, + current_id, + &ManufacturerCategorizer, + ) + } +} + +struct ManufacturerCategorizer; + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, derive_more::Display)] +enum ManufacturerCategory<'a> { + #[display(fmt = "{_0}")] + Specific(&'a str), + #[display(fmt = "Unsorted")] + Generic, +} + +impl<'a> Categorizer<'a> for ManufacturerCategorizer { + type Category = ManufacturerCategory<'a>; + + fn extract_category(&self, info: &'a CommonPresetInfo) -> Self::Category { + info.meta_data + .device_manufacturer + .as_ref() + .map(|m| ManufacturerCategory::Specific(m.as_str())) + .unwrap_or(ManufacturerCategory::Generic) + } + + fn create_sub_entries( + &self, + infos: impl Iterator + 'a, + build_id: &impl Fn(&'a CommonPresetInfo) -> T, + current_id: Option<&'a str>, + ) -> Vec> { + build_compartment_preset_menu_entries_internal(infos.into_iter(), build_id, current_id) + } +} + +fn create_category_menu(category: impl Display, entries: Vec>) -> Entry { + const CURRENT_CATEGORY_SUFFIX: &str = " *"; + let contains_current_entry = entries.iter().any(|e| match e { + Entry::Menu(m) => m.text.ends_with(CURRENT_CATEGORY_SUFFIX), + Entry::Item(i) => i.opts.checked, + _ => false, + }); + let category_suffix = if contains_current_entry { + CURRENT_CATEGORY_SUFFIX + } else { + "" + }; + let category_label = format!("{category}{category_suffix}"); + menu(category_label, entries) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/message_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/message_panel.rs new file mode 100644 index 0000000..0908b92 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/message_panel.rs @@ -0,0 +1,88 @@ +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::util::fonts; +use derivative::Derivative; +use reaper_low::raw; +use std::cell::RefCell; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Debug, Default)] +pub struct MessagePanel { + view: ViewContext, + content: RefCell, +} + +#[derive(Derivative)] +#[derivative(Debug)] +struct MessagePanelContent { + title: String, + message: String, + #[derivative(Debug = "ignore")] + on_close: Box, +} + +impl Default for MessagePanelContent { + fn default() -> Self { + Self { + title: "".to_string(), + message: "".to_string(), + on_close: Box::new(|| ()), + } + } +} + +impl MessagePanel { + pub fn set_content(&self, title: String, message: String, on_close: impl FnOnce() + 'static) { + let prev_content = self.content.replace(MessagePanelContent { + title, + message, + on_close: Box::new(on_close), + }); + (prev_content.on_close)(); + if self.is_open() { + self.invalidate(); + } + } + + fn invalidate(&self) { + let content = self.content.borrow(); + self.view.require_window().set_text(content.title.as_str()); + self.view + .require_control(root::ID_MESSAGE_TEXT) + .set_text(content.message.as_str()); + } + + fn on_close(&self) { + let prev_content = self.content.replace(Default::default()); + (prev_content.on_close)(); + } +} + +impl View for MessagePanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_MESSAGE_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + self.view + .require_control(root::ID_MESSAGE_TEXT) + .set_cached_font(fonts::normal_font(window, 20)); + self.invalidate(); + true + } + + fn on_destroy(self: SharedView, _window: Window) { + self.on_close(); + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Escape key + raw::IDCANCEL => self.close(), + _ => {} + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/mod.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/mod.rs new file mode 100644 index 0000000..718afca --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/mod.rs @@ -0,0 +1,93 @@ +pub mod bindings; + +mod unit_panel; +pub use unit_panel::*; + +mod state; +pub use state::*; + +mod header_panel; +pub use header_panel::*; + +mod mapping_rows_panel; +pub use mapping_rows_panel::*; + +mod mapping_row_panel; +pub use mapping_row_panel::*; + +mod mapping_header_panel; +pub use mapping_header_panel::*; + +mod mapping_panel; +pub use mapping_panel::*; + +mod group_panel; +pub use group_panel::*; + +mod session_message_panel; +pub use session_message_panel::*; + +mod message_panel; +pub use message_panel::*; + +mod yaml_editor_panel; +pub use yaml_editor_panel::*; + +mod simple_script_editor_panel; +pub use simple_script_editor_panel::*; + +#[cfg(feature = "egui")] +mod advanced_script_editor_panel; +#[cfg(feature = "egui")] +pub use advanced_script_editor_panel::*; + +mod app; +pub use app::*; + +#[cfg(feature = "egui")] +mod target_filter_panel; +#[cfg(feature = "egui")] +pub use target_filter_panel::*; + +#[cfg(feature = "egui")] +mod pot_browser_panel; +#[cfg(feature = "egui")] +pub use pot_browser_panel::*; + +#[cfg(feature = "egui")] +mod control_transformation_templates; +#[cfg(feature = "egui")] +pub use control_transformation_templates::*; + +mod independent_panel_manager; +pub use independent_panel_manager::*; + +mod companion_app_presenter; +pub use companion_app_presenter::*; + +mod dialog_util; + +pub mod util; + +mod clipboard; +pub use clipboard::*; + +mod import; +pub use import::*; + +pub mod lua_serializer; + +#[cfg(feature = "egui")] +mod egui_views; + +pub mod menus; + +pub mod color_panel; +pub mod instance_panel; +pub mod welcome_panel; + +pub mod stream_deck_tool; + +mod ui_element_container; + +mod help; diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/framework.h b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/framework.h new file mode 100644 index 0000000..fbce5ca --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/framework.h @@ -0,0 +1,15 @@ +// header.h: Includedatei für Include-Standardsystemdateien +// oder projektspezifische Includedateien. +// + +#pragma once + +#include "targetver.h" +#define WIN32_LEAN_AND_MEAN // Selten verwendete Komponenten aus Windows-Headern ausschließen +// Windows-Headerdateien +#include +// C RunTime-Headerdateien +#include +#include +#include +#include diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.cpp b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.cpp new file mode 100644 index 0000000..d49020a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.cpp @@ -0,0 +1,180 @@ +// msvc.cpp : Definiert den Einstiegspunkt für die Anwendung. +// + +#include "framework.h" +#include "msvc.h" + +#define MAX_LOADSTRING 100 + +// Globale Variablen: +HINSTANCE hInst; // Aktuelle Instanz +WCHAR szTitle[MAX_LOADSTRING]; // Titelleistentext +WCHAR szWindowClass[MAX_LOADSTRING]; // Der Klassenname des Hauptfensters. + +// Vorwärtsdeklarationen der in diesem Codemodul enthaltenen Funktionen: +ATOM MyRegisterClass(HINSTANCE hInstance); +BOOL InitInstance(HINSTANCE, int); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + + // TODO: Hier Code einfügen. + + // Globale Zeichenfolgen initialisieren + LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); + LoadStringW(hInstance, IDC_MSVC, szWindowClass, MAX_LOADSTRING); + MyRegisterClass(hInstance); + + // Anwendungsinitialisierung ausführen: + if (!InitInstance (hInstance, nCmdShow)) + { + return FALSE; + } + + HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MSVC)); + + MSG msg; + + // Hauptnachrichtenschleife: + while (GetMessage(&msg, nullptr, 0, 0)) + { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return (int) msg.wParam; +} + + + +// +// FUNKTION: MyRegisterClass() +// +// ZWECK: Registriert die Fensterklasse. +// +ATOM MyRegisterClass(HINSTANCE hInstance) +{ + WNDCLASSEXW wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MSVC)); + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MSVC); + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); + + return RegisterClassExW(&wcex); +} + +// +// FUNKTION: InitInstance(HINSTANCE, int) +// +// ZWECK: Speichert das Instanzenhandle und erstellt das Hauptfenster. +// +// KOMMENTARE: +// +// In dieser Funktion wird das Instanzenhandle in einer globalen Variablen gespeichert, und das +// Hauptprogrammfenster wird erstellt und angezeigt. +// +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) +{ + hInst = hInstance; // Instanzenhandle in der globalen Variablen speichern + + HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); + + if (!hWnd) + { + return FALSE; + } + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + return TRUE; +} + +// +// FUNKTION: WndProc(HWND, UINT, WPARAM, LPARAM) +// +// ZWECK: Verarbeitet Meldungen für das Hauptfenster. +// +// WM_COMMAND - Verarbeiten des Anwendungsmenüs +// WM_PAINT - Darstellen des Hauptfensters +// WM_DESTROY - Ausgeben einer Beendenmeldung und zurückkehren +// +// +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_COMMAND: + { + int wmId = LOWORD(wParam); + // Menüauswahl analysieren: + switch (wmId) + { + case IDM_ABOUT: + DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); + break; + case IDM_EXIT: + DestroyWindow(hWnd); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + } + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + // TODO: Zeichencode, der hdc verwendet, hier einfügen... + EndPaint(hWnd, &ps); + } + break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +// Meldungshandler für Infofeld. +INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.h b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.h new file mode 100644 index 0000000..d00d47e --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.h @@ -0,0 +1,3 @@ +#pragma once + +#include "resource.h" diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.ico b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.ico new file mode 100644 index 0000000..b3ec03b Binary files /dev/null and b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.ico differ diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.sln b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.sln new file mode 100644 index 0000000..9cfab90 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30717.126 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msvc", "msvc.vcxproj", "{FA91FC9C-6A4A-4B03-BE29-73694817C7BA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Debug|x64.ActiveCfg = Debug|x64 + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Debug|x64.Build.0 = Debug|x64 + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Debug|x86.ActiveCfg = Debug|Win32 + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Debug|x86.Build.0 = Debug|Win32 + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Release|x64.ActiveCfg = Release|x64 + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Release|x64.Build.0 = Release|x64 + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Release|x86.ActiveCfg = Release|Win32 + {FA91FC9C-6A4A-4B03-BE29-73694817C7BA}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AA732071-CE98-4B69-9CC1-0924D5A2E1D1} + EndGlobalSection +EndGlobal diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.vcxproj b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.vcxproj new file mode 100644 index 0000000..919dda8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.vcxproj @@ -0,0 +1,160 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {fa91fc9c-6a4a-4b03-be29-73694817c7ba} + msvc + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.vcxproj.filters b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.vcxproj.filters new file mode 100644 index 0000000..c7cdc15 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/msvc.vcxproj.filters @@ -0,0 +1,49 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;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 + + + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + + + Quelldateien + + + + + Ressourcendateien + + + + + Ressourcendateien + + + Ressourcendateien + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/small.ico b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/small.ico new file mode 100644 index 0000000..b3ec03b Binary files /dev/null and b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/small.ico differ diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/targetver.h b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/targetver.h new file mode 100644 index 0000000..b522b5c --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/msvc/targetver.h @@ -0,0 +1,6 @@ +#pragma once + +// // Durch das Einschließen von "SDKDDKVer.h" wird die höchste verfügbare Windows-Plattform definiert. +// Wenn Sie Ihre Anwendung für eine frühere Windows-Plattform erstellen möchten, schließen Sie "WinSDKVer.h" ein, und +// legen Sie vor dem Einschließen von "SDKDDKVer.h" das _WIN32_WINNT-Makro auf die Plattform fest, die Sie unterstützen möchten. +#include diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/pot_browser_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/pot_browser_panel.rs new file mode 100644 index 0000000..6c0fb72 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/pot_browser_panel.rs @@ -0,0 +1,93 @@ +use crate::application::get_track_label; +use crate::domain::{AnyThreadBackboneState, Backbone}; +use crate::infrastructure::plugin::BackboneShell; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::egui_views; +use camino::Utf8Path; +use derivative::Derivative; +use pot::{CurrentPreset, PotFavorites, PotFilterExcludes, SharedRuntimePotUnit}; +use pot_browser::{run_ui, PotBrowserIntegration, State}; +use reaper_high::{Fx, Track}; +use reaper_low::raw; +use std::sync::RwLock; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct PotBrowserPanel { + view: ViewContext, + pot_unit: SharedRuntimePotUnit, +} + +impl PotBrowserPanel { + pub fn new(pot_unit: SharedRuntimePotUnit) -> Self { + Self { + view: Default::default(), + pot_unit, + } + } +} + +impl View for PotBrowserPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_EMPTY_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn wants_raw_keyboard_input(&self) -> bool { + true + } + + fn opened(self: SharedView, window: Window) -> bool { + window.size_and_center_on_screen(0.75, 0.75); + egui_views::open( + window, + "Pot Browser", + State::new(self.pot_unit.clone(), window), + |context, state| { + run_ui(context, state, &RealearnPotBrowserIntegration); + }, + ); + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Escape key + raw::IDCANCEL => self.close(), + _ => {} + } + } + + fn resized(self: SharedView) -> bool { + egui_views::on_parent_window_resize(self.view.require_window()) + } +} + +struct RealearnPotBrowserIntegration; + +impl PotBrowserIntegration for RealearnPotBrowserIntegration { + fn get_track_label(&self, track: &Track) -> String { + get_track_label(track) + } + + fn pot_preview_template_path(&self) -> Option<&'static Utf8Path> { + BackboneShell::realearn_pot_preview_template_path() + } + + fn pot_favorites(&self) -> &'static RwLock { + &AnyThreadBackboneState::get().pot_favorites + } + + fn with_current_fx_preset(&self, fx: &Fx, f: impl FnOnce(Option<&CurrentPreset>)) { + let target_state = Backbone::target_state().borrow(); + f(target_state.current_fx_preset(fx)); + } + + fn with_pot_filter_exclude_list(&self, f: impl FnOnce(&PotFilterExcludes)) { + f(&Backbone::get().pot_filter_exclude_list()); + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/session_message_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/session_message_panel.rs new file mode 100644 index 0000000..f6d2f8e --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/session_message_panel.rs @@ -0,0 +1,124 @@ +use crate::application::{LearnManySubState, SharedUnitModel, WeakUnitModel}; +use crate::base::when; +use crate::domain::CompartmentKind; +use crate::infrastructure::ui::bindings::root; +use helgobox_api::persistence::VirtualControlElementCharacter; +use reaper_low::raw; +use rxrust::prelude::*; +use std::rc::Rc; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Debug)] +pub struct SessionMessagePanel { + view: ViewContext, + session: WeakUnitModel, +} + +impl SessionMessagePanel { + pub fn new(session: WeakUnitModel) -> SessionMessagePanel { + SessionMessagePanel { + view: Default::default(), + session, + } + } + + fn invalidate_controls(&self) { + self.invalidate_message_and_title(); + } + + fn invalidate_message_and_title(&self) { + let session = self.session(); + let session = session.borrow(); + let (title_addition, msg) = if let Some(state) = session.learn_many_state() { + if let Some(mapping) = + session.find_mapping_by_id(state.compartment, state.current_mapping_id) + { + let mapping = mapping.borrow(); + let mapping_label = format!("mapping {}", mapping.effective_name()); + match state.sub_state { + LearnManySubState::LearningSource { + control_element_character, + } => { + let msg = match state.compartment { + CompartmentKind::Controller => match control_element_character { + VirtualControlElementCharacter::Multi => { + "Move a multi-like control element!" + } + VirtualControlElementCharacter::Button => { + "Press a button-like control element!" + } + }, + CompartmentKind::Main => "Touch a control element!", + }; + ( + format!("Learning source for {mapping_label}"), + msg.to_string(), + ) + } + LearnManySubState::LearningTarget => ( + format!("Learning target for {mapping_label}"), + "Now touch the target which you want to control!".to_string(), + ), + } + } else { + ("".to_string(), "".to_string()) + } + } else { + ("".to_string(), "".to_string()) + }; + self.view + .require_window() + .set_text(format!("ReaLearn - {title_addition}")); + self.view + .require_control(root::ID_MESSAGE_TEXT) + .set_text(msg); + } + + fn register_listeners(self: &SharedView) { + let session = self.session(); + let session = session.borrow(); + when( + session + .learn_many_state_changed() + .take_until(self.view.closed()), + ) + .with(Rc::downgrade(self)) + .do_async(|view, _| { + view.invalidate_message_and_title(); + }); + } + + fn session(&self) -> SharedUnitModel { + self.session.upgrade().expect("session gone") + } +} + +impl View for SessionMessagePanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_MESSAGE_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, _window: Window) -> bool { + self.invalidate_controls(); + self.register_listeners(); + true + } + + fn on_destroy(self: SharedView, _window: Window) { + if let Some(session) = self.session.upgrade() { + session.borrow_mut().stop_learning_many_mappings(); + } + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Escape key + raw::IDCANCEL => self.close(), + _ => {} + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/simple_script_editor_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/simple_script_editor_panel.rs new file mode 100644 index 0000000..ed69c96 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/simple_script_editor_panel.rs @@ -0,0 +1,342 @@ +use crate::domain::{ + EelMidiSourceScript, EelTransformation, LuaFeedbackScript, LuaMidiSourceScript, SafeLua, Script, +}; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::bindings::root::ID_YAML_HELP_BUTTON; +use crate::infrastructure::ui::util::{open_in_browser, open_in_text_editor}; +use derivative::Derivative; +use helgoboss_learn::{ + AbsoluteValue, FeedbackScript, FeedbackScriptInput, FeedbackStyle, FeedbackValue, + MidiSourceScript, NumericFeedbackValue, RawMidiEvent, RawMidiPattern, UnitValue, +}; +use reaper_low::raw; +use std::cell::RefCell; +use std::error::Error; +use swell_ui::{SharedView, View, ViewContext, Window}; + +pub trait ScriptEngine { + fn compile(&self, code: &str) -> Result, Box>; + + /// Must include the dot! + fn file_extension(&self) -> &'static str; +} + +pub struct LuaMidiScriptEngine { + lua: SafeLua, +} + +impl LuaMidiScriptEngine { + pub fn new() -> Self { + Self { + lua: SafeLua::new().unwrap(), + } + } +} + +impl ScriptEngine for LuaMidiScriptEngine { + fn compile(&self, code: &str) -> Result, Box> { + let script = LuaMidiSourceScript::compile(&self.lua, code)?; + script.execute(create_midi_script_test_feedback_value(), Default::default())?; + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".lua" + } +} + +pub struct LuaFeedbackScriptEngine { + lua: SafeLua, +} + +impl LuaFeedbackScriptEngine { + pub fn new() -> Self { + Self { + lua: SafeLua::new().unwrap(), + } + } +} + +impl ScriptEngine for LuaFeedbackScriptEngine { + fn compile(&self, code: &str) -> Result, Box> { + let script = LuaFeedbackScript::compile(&self.lua, code)?; + let test_input = FeedbackScriptInput { + prop_provider: &|_: &str| None, + }; + script.used_props()?; + script.feedback(test_input, Default::default())?; + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".lua" + } +} + +pub struct LuaCompartmentCommonScriptEngine { + lua: SafeLua, +} + +impl LuaCompartmentCommonScriptEngine { + pub fn new() -> Self { + Self { + lua: SafeLua::new().unwrap(), + } + } +} + +impl ScriptEngine for LuaCompartmentCommonScriptEngine { + fn compile(&self, code: &str) -> Result, Box> { + let env = self.lua.create_fresh_environment(false)?; + self.lua + .compile_as_function("Feedback script", code, env.clone())?; + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".lua" + } +} + +pub struct PlainTextEngine; + +impl ScriptEngine for PlainTextEngine { + fn compile(&self, _: &str) -> Result, Box> { + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".txt" + } +} + +pub struct EelMidiScriptEngine; + +impl ScriptEngine for EelMidiScriptEngine { + fn compile(&self, code: &str) -> Result, Box> { + let script = EelMidiSourceScript::compile(code)?; + script.execute(create_midi_script_test_feedback_value(), Default::default())?; + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".eel" + } +} + +pub struct RawMidiScriptEngine; + +impl ScriptEngine for RawMidiScriptEngine { + fn compile(&self, code: &str) -> Result, Box> { + let raw_midi_pattern: RawMidiPattern = code.parse()?; + if raw_midi_pattern.entries().len() > RawMidiEvent::MAX_LENGTH { + return Err(format!( + "Pattern exceeds maximum allowed MIDI message size of {} bytes", + RawMidiEvent::MAX_LENGTH + ) + .into()); + } + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".syx" + } +} + +pub struct OscFeedbackArgumentsEngine; + +impl ScriptEngine for OscFeedbackArgumentsEngine { + fn compile(&self, _: &str) -> Result, Box> { + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".txt" + } +} + +pub struct EelControlTransformationEngine; + +impl ScriptEngine for EelControlTransformationEngine { + fn compile(&self, code: &str) -> Result, Box> { + let transformation = EelTransformation::compile_for_control(code)?; + transformation.evaluate(Default::default())?; + Ok(Box::new(transformation)) + } + + fn file_extension(&self) -> &'static str { + ".eel" + } +} + +pub struct EelFeedbackTransformationEngine; + +impl ScriptEngine for EelFeedbackTransformationEngine { + fn compile(&self, code: &str) -> Result, Box> { + let transformation = EelTransformation::compile_for_feedback(code)?; + transformation.evaluate(Default::default())?; + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".eel" + } +} + +pub struct TextualFeedbackExpressionEngine; + +impl ScriptEngine for TextualFeedbackExpressionEngine { + fn compile(&self, _: &str) -> Result, Box> { + Ok(Box::new(())) + } + + fn file_extension(&self) -> &'static str { + ".mustache" + } +} + +fn create_midi_script_test_feedback_value() -> FeedbackValue<'static> { + FeedbackValue::Numeric(NumericFeedbackValue::new( + FeedbackStyle::default(), + AbsoluteValue::Continuous(UnitValue::new(0.0)), + )) +} + +pub struct ScriptEditorInput { + pub engine: Box, + pub help_url: &'static str, + pub initial_value: String, + pub set_value: A, +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct SimpleScriptEditorPanel { + view: ViewContext, + content: RefCell, + #[derivative(Debug = "ignore")] + apply: Box, + #[derivative(Debug = "ignore")] + engine: Box, + help_url: &'static str, +} + +impl SimpleScriptEditorPanel { + /// If the help URL is empty, the help button will be hidden and the info text (whether + /// compiled successfully) as well. + pub fn new(input: ScriptEditorInput) -> Self { + Self { + view: Default::default(), + content: RefCell::new(input.initial_value), + apply: Box::new(input.set_value), + engine: input.engine, + help_url: input.help_url, + } + } + + fn apply(&self) { + (self.apply)(self.content.borrow().clone()); + } + + fn invalidate_initial(&self) { + let initial_content = &self.content.borrow(); + self.set_text(initial_content); + self.invalidate_info(); + if self.help_url.is_empty() { + self.view.require_control(ID_YAML_HELP_BUTTON).hide(); + } + } + + fn update_content(&self) { + *self.content.borrow_mut() = self.text(); + if !self.help_url.is_empty() { + self.invalidate_info(); + } + } + + fn invalidate_info(&self) { + let info_text = if self.help_url.is_empty() { + "".to_string() + } else { + match self.engine.compile(&self.text()) { + Ok(_) => "Your script compiled successfully and seems to work.".to_string(), + Err(e) => e.to_string(), + } + }; + self.view + .require_control(root::ID_YAML_EDIT_INFO_TEXT) + .set_text(info_text); + } + + fn open_in_text_editor(&self) { + if let Ok(edited_text) = open_in_text_editor( + &self.text(), + self.view.require_window(), + self.engine.file_extension(), + ) { + self.set_text(&edited_text); + self.update_content(); + } + } + + fn text(&self) -> String { + self.view + .require_control(root::ID_YAML_EDIT_CONTROL) + .multi_line_text() + .unwrap_or_default() + } + + fn set_text(&self, text: &str) { + self.view + .require_control(root::ID_YAML_EDIT_CONTROL) + .set_multi_line_text(text); + } +} + +impl View for SimpleScriptEditorPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_YAML_EDITOR_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, _window: Window) -> bool { + self.invalidate_initial(); + true + } + + fn on_destroy(self: SharedView, _window: Window) { + self.apply(); + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Escape key + raw::IDCANCEL => self.close(), + root::ID_YAML_HELP_BUTTON => open_in_browser(self.help_url), + root::ID_YAML_TEXT_EDITOR_BUTTON => self.open_in_text_editor(), + _ => {} + } + } + + /// REAPER for macOS introduced support for edit_control_changed (EN_CHANGE) notifications around version 6.81/7.0. + /// Before that, we could rely on key-up events only (which wouldn't capture pasting text). We leave this here + /// as backup for older REAPER versions. We could also do a version check instead, but updating content is not that + /// expensive. + #[cfg(target_os = "macos")] + fn key_up(self: SharedView, _key_code: u8) -> bool { + self.update_content(); + true + } + + fn edit_control_changed(self: SharedView, resource_id: u32) -> bool { + match resource_id { + root::ID_YAML_EDIT_CONTROL => self.update_content(), + _ => return false, + }; + true + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/state.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/state.rs new file mode 100644 index 0000000..2cebec8 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/state.rs @@ -0,0 +1,229 @@ +use crate::base::{prop, Prop}; +use crate::domain::{ + CompartmentKind, CompoundMappingSource, GroupId, IncomingCompoundSourceValue, + MessageCaptureResult, ReaperTarget, Tag, VirtualSourceValue, +}; + +use crate::application::{MappingModel, UnitModel}; +use enum_map::{enum_map, EnumMap}; +use rxrust::prelude::*; +use std::cell::RefCell; +use std::fmt; +use std::rc::Rc; +use wildmatch::WildMatch; + +pub type SharedMainState = Rc>; + +#[derive(Debug)] +pub struct MainState { + pub target_filter: Prop>, + pub is_learning_target_filter: Prop, + pub source_filter: Prop>, + pub is_learning_source_filter: Prop, + pub active_compartment: Prop, + pub displayed_group: EnumMap>>, + pub search_expression: Prop, + pub scroll_status: Prop, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default)] +pub struct ScrollStatus { + pub from_pos: usize, + pub to_pos: usize, + pub item_count: usize, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct SourceFilter { + /// Only can match mappings with real sources. + pub message_capture_result: MessageCaptureResult, + /// If the incoming message was successfully virtualized + /// (can match main mappings with virtual sources). + pub virtual_source_value: Option, +} + +impl SourceFilter { + pub fn matches(&self, source: &CompoundMappingSource) -> bool { + // First try real source matching. + if source + .reacts_to_source_value_with(self.message_capture_result.message()) + .is_some() + { + return true; + } + // Then try virtual source matching (if the message was virtualized before). + if let Some(v) = self.virtual_source_value { + if source + .reacts_to_source_value_with(IncomingCompoundSourceValue::Virtual(&v)) + .is_some() + { + return true; + } + } + false + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct GroupFilter(pub GroupId); + +impl GroupFilter { + pub fn matches(&self, mapping: &MappingModel) -> bool { + mapping.group_id() == self.0 + } + + pub fn group_id(&self) -> GroupId { + self.0 + } +} + +impl Default for MainState { + fn default() -> Self { + MainState { + target_filter: prop(None), + is_learning_target_filter: prop(false), + source_filter: prop(None), + is_learning_source_filter: prop(false), + active_compartment: prop(CompartmentKind::Main), + displayed_group: enum_map! { + CompartmentKind::Controller => prop(Some(GroupFilter::default())), + CompartmentKind::Main => prop(Some(GroupFilter::default())), + }, + search_expression: Default::default(), + scroll_status: Default::default(), + } + } +} + +impl MainState { + pub fn clear_all_filters_and_displayed_group(&mut self) { + self.clear_all_filters(); + for c in CompartmentKind::enum_iter() { + self.clear_displayed_group(c); + } + } + + pub fn displayed_group_for_any_compartment_changed( + &self, + ) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.displayed_group[CompartmentKind::Controller] + .changed() + .merge(self.displayed_group[CompartmentKind::Main].changed()) + } + + pub fn displayed_group_for_active_compartment(&self) -> Option { + self.displayed_group[self.active_compartment.get()].get() + } + + pub fn set_displayed_group_for_active_compartment(&mut self, filter: Option) { + self.displayed_group[self.active_compartment.get()].set(filter); + } + + pub fn clear_all_filters(&mut self) { + self.clear_source_filter(); + self.clear_target_filter(); + self.clear_search_expression_filter(); + self.stop_filter_learning(); + } + + pub fn clear_displayed_group(&mut self, compartment: CompartmentKind) { + self.displayed_group[compartment].set(None); + } + + pub fn clear_displayed_group_for_active_compartment(&mut self) { + self.clear_displayed_group(self.active_compartment.get()); + } + + pub fn clear_search_expression_filter(&mut self) { + self.search_expression.set(Default::default()); + } + + pub fn clear_source_filter(&mut self) { + self.source_filter.set(None) + } + + pub fn clear_target_filter(&mut self) { + self.target_filter.set(None) + } + + pub fn filter_and_displayed_group_is_active(&self) -> bool { + self.displayed_group[self.active_compartment.get()] + .get_ref() + .is_some() + || self.filter_is_active() + } + + pub fn filter_is_active(&self) -> bool { + self.source_filter.get_ref().is_some() + || self.target_filter.get_ref().is_some() + || !self.search_expression.get_ref().is_empty() + } + + pub fn stop_filter_learning(&mut self) { + self.is_learning_source_filter.set(false); + self.is_learning_target_filter.set(false); + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct SearchExpression { + wild_match: WildMatch, + tag: Option, +} + +impl SearchExpression { + pub fn new(text: &str) -> SearchExpression { + let wild_match = if text.is_empty() { + WildMatch::default() + } else { + let modified_text = format!("*{}*", text.to_lowercase()); + WildMatch::new(&modified_text) + }; + fn extract_tag(text: &str) -> Option { + let tag_name = text.strip_prefix('#')?; + tag_name.parse().ok() + } + Self { + wild_match, + tag: extract_tag(text), + } + } + + pub fn matches(&self, text: &str) -> bool { + self.wild_match.matches(&text.to_lowercase()) + } + + pub fn matches_any_tag_in_group(&self, mapping: &MappingModel, session: &UnitModel) -> bool { + if self.tag.is_none() { + return false; + } + if let Some(group) = session + .find_group_by_id_including_default_group(mapping.compartment(), mapping.group_id()) + { + self.matches_any_tag(group.borrow().tags()) + } else { + false + } + } + + pub fn matches_any_tag(&self, tags: &[Tag]) -> bool { + if let Some(tag) = &self.tag { + tags.iter().any(|t| t == tag) + } else { + false + } + } + + pub fn is_empty(&self) -> bool { + self.to_string().is_empty() + } +} + +impl fmt::Display for SearchExpression { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = self.wild_match.to_string(); + let s = s.strip_prefix('*').unwrap_or(&s); + let s = s.strip_suffix('*').unwrap_or(s); + f.write_str(s) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/stream_deck_tool.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/stream_deck_tool.rs new file mode 100644 index 0000000..24567f0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/stream_deck_tool.rs @@ -0,0 +1,148 @@ +use helgobox_api::persistence::{ + AbsoluteMode, ActionInvocationKind, Compartment, Glue, Interval, Mapping, ReaperActionTarget, + ReaperCommand, Source, StreamDeckButtonBackground, StreamDeckButtonDesign, + StreamDeckButtonFadingImageForeground, StreamDeckButtonForeground, + StreamDeckButtonImageBackground, StreamDeckButtonSlidingImageForeground, StreamDeckSource, + Target, +}; +use reaper_high::{ActionCharacter, Reaper}; +use reaper_medium::{CommandItem, MenuOrToolbarItem}; + +pub struct StreamDeckToolbarOptions { + pub use_sliding_images: bool, +} + +pub fn create_stream_deck_compartment_reflecting_toolbar( + toolbar_name: &str, + opts: StreamDeckToolbarOptions, +) -> anyhow::Result { + let items = (0..) + .map(|pos| get_toolbar_cmd_item(toolbar_name, pos)) + .take_while(|item| item.is_some()) + .flatten() + .flatten(); + let reaper_resource_path = Reaper::get().resource_path(); + let button_mappings = items.enumerate().map(|(i, item)| { + let action = Reaper::get() + .main_section() + .action_by_command_id(item.command_id); + let action_character = action.character().unwrap_or(ActionCharacter::Trigger); + let button_icon_path = item + .icon_file_name + .as_ref() + .map(|icon| format!("Data/toolbar_icons/200/{icon}")) + // It's possible that the image doesn't exist (e.g. if it's an internal icon) + .filter(|path| reaper_resource_path.join(path).exists()); + let static_text = if item.icon_file_name.is_some() { + String::new() + } else { + item.label.replace(' ', "\n").clone() + }; + let button_design = match action_character { + ActionCharacter::Toggle => StreamDeckButtonDesign { + foreground: match button_icon_path { + None => StreamDeckButtonForeground::FadingColor(Default::default()), + Some(path) => { + if opts.use_sliding_images { + let fg = StreamDeckButtonSlidingImageForeground { path }; + StreamDeckButtonForeground::SlidingImage(fg) + } else { + let fg = StreamDeckButtonFadingImageForeground { path }; + StreamDeckButtonForeground::FadingImage(fg) + } + } + }, + static_text, + ..Default::default() + }, + ActionCharacter::Trigger => StreamDeckButtonDesign { + background: button_icon_path + .map(|path| { + let fg = StreamDeckButtonImageBackground { path }; + StreamDeckButtonBackground::Image(fg) + }) + .unwrap_or_default(), + static_text, + ..Default::default() + }, + }; + let source = StreamDeckSource { + button_index: i as u32, + button_design, + }; + let glue = match action_character { + ActionCharacter::Toggle => Glue { + source_interval: if !opts.use_sliding_images && item.icon_file_name.is_some() { + // Show icon dimmed when off and with full brightness when on + Some(Interval(0.5, 1.0)) + } else { + None + }, + absolute_mode: Some(AbsoluteMode::ToggleButton), + ..Default::default() + }, + ActionCharacter::Trigger => Glue::default(), + }; + let target = ReaperActionTarget { + command: { + let cmd = match action.command_name() { + // Built-in actions don't have a command name but a persistent command ID. + // Use command ID as string. + None => ReaperCommand::Id(item.command_id.get()), + // ReaScripts and custom actions have a command name as persistent identifier. + Some(name) => ReaperCommand::Name(name.into_string()), + }; + Some(cmd) + }, + invocation: { + let invocation = match action_character { + ActionCharacter::Toggle => ActionInvocationKind::Absolute7Bit, + ActionCharacter::Trigger => ActionInvocationKind::Trigger, + }; + Some(invocation) + }, + ..Default::default() + }; + Mapping { + name: Some(item.label), + source: Some(Source::StreamDeck(source)), + glue: Some(glue), + target: Some(Target::ReaperAction(target)), + ..Default::default() + } + }); + let set_brightness_to_max_mapping = Mapping { + name: Some("Set brightness to max".to_string()), + source: Some(Source::RealearnCompartmentLoaded), + target: Some(Target::StreamDeckBrightness(Default::default())), + ..Default::default() + }; + let all_mappings = [set_brightness_to_max_mapping] + .into_iter() + .chain(button_mappings); + let compartment = Compartment { + mappings: Some(all_mappings.collect()), + ..Default::default() + }; + Ok(compartment) +} + +fn get_toolbar_cmd_item( + toolbar_name: &str, + pos: u32, +) -> Option>> { + Reaper::get() + .medium_reaper() + .get_custom_menu_or_toolbar_item(toolbar_name, pos, |result| match result? { + MenuOrToolbarItem::Command(item) => { + let owned_command_item = CommandItem { + command_id: item.command_id, + toolbar_flags: item.toolbar_flags, + label: item.label.to_string(), + icon_file_name: item.icon_file_name.map(|n| n.to_string()), + }; + Some(Some(owned_command_item)) + } + _ => Some(None), + }) +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/target_filter_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/target_filter_panel.rs new file mode 100644 index 0000000..8c722e4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/target_filter_panel.rs @@ -0,0 +1,75 @@ +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::egui_views; +use crate::infrastructure::ui::egui_views::target_filter_panel::{run_ui, State, Value}; +use base::SenderToNormalThread; +use crossbeam_channel::Receiver; +use derivative::Derivative; +use reaper_low::raw; +use std::cell::RefCell; +use std::time::Duration; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct TargetFilterPanel { + view: ViewContext, + state: RefCell>, + value_receiver: Receiver, + #[derivative(Debug = "ignore")] + set_value: Box, +} + +const TIMER_ID: usize = 320; + +impl TargetFilterPanel { + pub fn new(initial_value: Value, set_value: impl Fn(Value) + 'static) -> Self { + let (value_sender, value_receiver) = + SenderToNormalThread::new_unbounded_channel("learnable targets"); + Self { + view: Default::default(), + state: RefCell::new(Some(State::new(initial_value, value_sender))), + value_receiver, + set_value: Box::new(set_value), + } + } +} + +impl View for TargetFilterPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_EMPTY_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + window.set_timer(TIMER_ID, Duration::from_millis(30)); + let state = self.state.take().expect("state already in use"); + egui_views::open(window, "Target filter", state, run_ui); + true + } + + fn wants_raw_keyboard_input(&self) -> bool { + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Escape key + raw::IDCANCEL => self.close(), + _ => {} + } + } + + fn timer(&self, _: usize) -> bool { + if let Some(v) = self.value_receiver.try_iter().last() { + (self.set_value)(v); + } + true + } + + fn resized(self: SharedView) -> bool { + egui_views::on_parent_window_resize(self.view.require_window()) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/ui_element_container.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/ui_element_container.rs new file mode 100644 index 0000000..c876582 --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/ui_element_container.rs @@ -0,0 +1,48 @@ +use swell_ui::{Point, Rect, Window}; + +#[derive(Debug, Default)] +pub struct UiElementContainer { + elements: Vec, +} + +#[derive(Debug)] +pub struct UiElement { + pub id: u32, + pub rect: Rect, + pub visible: bool, +} + +impl UiElementContainer { + pub fn fill_with_window_children(&mut self, window: Window) { + self.fill_with_window_children_internal(window, window) + } + + fn fill_with_window_children_internal(&mut self, ref_window: Window, current_window: Window) { + for child in current_window.children() { + let element = UiElement { + id: child.resource_id(), + rect: ref_window.screen_to_client_rect(&child.window_rect()), + visible: true, + }; + self.add_element(element); + self.fill_with_window_children_internal(ref_window, child) + } + } + + pub fn add_element(&mut self, element: UiElement) { + self.elements.push(element); + } + + pub fn set_visible(&mut self, id: u32, visible: bool) { + if let Some(el) = self.elements.iter_mut().find(|e| e.id == id) { + el.visible = visible; + } + } + + pub fn hit_test(&self, point: Point) -> impl Iterator + '_ { + self.elements + .iter() + .filter(move |e| e.visible && e.rect.contains(point)) + .map(|e| e.id) + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/unit_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/unit_panel.rs new file mode 100644 index 0000000..68645aa --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/unit_panel.rs @@ -0,0 +1,710 @@ +use crate::infrastructure::ui::{ + bindings::root, HeaderPanel, IndependentPanelManager, MappingRowsPanel, + SharedIndependentPanelManager, SharedMainState, +}; + +use reaper_high::Reaper; + +use crate::application::{ + get_virtual_fx_label, get_virtual_track_label, Affected, CompartmentProp, UnitCommand, + UnitModel, UnitProp, UnitUi, VirtualFxType, WeakUnitModel, +}; +use crate::base::when; +use crate::domain::ui_util::format_tags_as_csv; +use crate::domain::{ + CompartmentKind, InstanceId, InternalInfoEvent, MappingId, MappingMatchedEvent, + ProjectionFeedbackValue, QualifiedMappingId, SourceFeedbackEvent, TargetControlEvent, + TargetValueChangedEvent, +}; +use crate::infrastructure::plugin::{update_auto_units_async, BackboneShell}; +use crate::infrastructure::server::http::{ + send_projection_feedback_to_subscribed_clients, send_sessions_to_subscribed_clients, + send_updated_controller_routing, +}; +use crate::infrastructure::ui::instance_panel::InstancePanel; +use crate::infrastructure::ui::util::{header_panel_height, parse_tags_from_csv}; +use anyhow::{anyhow, bail, Context}; +use base::SoundPlayer; +use helgobox_allocator::undesired_allocation_count; +use helgobox_api::runtime::InstanceInfoEvent; +use rxrust::prelude::*; +use semver::Version; +use std::cell::RefCell; +use std::fmt; +use std::fmt::Write; +use std::rc::{Rc, Weak}; +use swell_ui::{DialogUnits, Point, SharedView, View, ViewContext, WeakView, Window}; + +/// Just the old term as alias for easier class search. +type _MainPanel = UnitPanel; + +/// The complete ReaLearn panel containing everything. +#[derive(Debug)] +pub struct UnitPanel { + instance_id: InstanceId, + view: ViewContext, + unit_model: WeakUnitModel, + instance_panel: WeakView, + header_panel: SharedView, + mapping_rows_panel: SharedView, + panel_manager: SharedIndependentPanelManager, + success_sound_player: Option, + state: SharedMainState, +} + +impl UnitPanel { + pub fn new( + instance_id: InstanceId, + session: WeakUnitModel, + instance_panel: WeakView, + ) -> SharedView { + let panel_manager = IndependentPanelManager::new(session.clone()); + let panel_manager = Rc::new(RefCell::new(panel_manager)); + let state = SharedMainState::default(); + let main_panel = Self { + instance_id, + view: Default::default(), + state: state.clone(), + unit_model: session.clone(), + instance_panel: instance_panel.clone(), + header_panel: HeaderPanel::new( + session.clone(), + state.clone(), + Rc::downgrade(&panel_manager), + instance_panel, + ) + .into(), + mapping_rows_panel: MappingRowsPanel::new( + session, + Rc::downgrade(&panel_manager), + state, + Point::new(DialogUnits(0), header_panel_height()), + ) + .into(), + panel_manager: panel_manager.clone(), + success_sound_player: { + let mut sound_player = SoundPlayer::new(); + if let Some(path_to_file) = BackboneShell::realearn_high_click_sound_path() { + if sound_player.load_file(path_to_file).is_ok() { + Some(sound_player) + } else { + None + } + } else { + None + } + }, + }; + let main_panel = Rc::new(main_panel); + panel_manager + .borrow_mut() + .set_main_panel(Rc::downgrade(&main_panel)); + // If the plug-in window is currently open, open the sub panels as well. Now we are talking! + if let Some(window) = main_panel.view.window() { + main_panel.open_sub_panels(window); + main_panel.invalidate_all_controls(); + main_panel.register_session_listeners(); + } + main_panel + } + + pub fn state(&self) -> &SharedMainState { + &self.state + } + + pub fn force_scroll_to_mapping(&self, id: QualifiedMappingId) { + self.mapping_rows_panel.force_scroll_to_mapping(id); + } + + pub fn edit_mapping(&self, compartment: CompartmentKind, mapping_id: MappingId) { + self.mapping_rows_panel + .edit_mapping(compartment, mapping_id); + } + + pub fn show_pot_browser(&self) { + self.header_panel.show_pot_browser(); + } + + pub fn header_panel(&self) -> SharedView { + self.header_panel.clone() + } + + fn open_sub_panels(&self, window: Window) { + self.header_panel.clone().open(window); + self.mapping_rows_panel.clone().open(window); + } + + fn invalidate_status_1_text(&self) { + use std::fmt::Write; + let _ = self.do_with_session(|unit_model| { + let state = self.state.borrow(); + let scroll_status = state.scroll_status.get_ref(); + let instance_model = unit_model.instance_model().borrow(); + let instance_tags = instance_model.tags(); + let instance_id = unit_model.instance_id(); + let unit_key = unit_model.unit_key(); + let mut text = format!( + "Showing mappings {} to {} of {}", + scroll_status.from_pos, scroll_status.to_pos, scroll_status.item_count, + ); + write!(&mut text, " | Instance ID: {instance_id}").expect("should never fail"); + if !instance_tags.is_empty() { + let _ = write!(&mut text, " [{}]", format_tags_as_csv(instance_tags)); + } + write!(&mut text, " | Unit key: {unit_key}").expect("should never fail"); + self.view + .require_control(root::ID_MAIN_PANEL_STATUS_1_TEXT) + .set_text(text.as_str()); + }); + } + + fn invalidate_status_2_text(&self) { + use std::fmt::Write; + let _ = self.do_with_session(|session| { + let instance_state = session.unit().borrow(); + let instance_track = instance_state.instance_track_descriptor(); + let compartment = CompartmentKind::Main; + let instance_track_label = get_virtual_track_label( + &instance_track.track, + compartment, + session.extended_context(), + ); + let instance_fx = instance_state.instance_fx_descriptor(); + let instance_fx_label = + get_virtual_fx_label(instance_fx, compartment, session.extended_context()); + let mut text = + format!("Track: {instance_track_label:.20} | FX: {instance_fx_label:.30}"); + let fx_type = VirtualFxType::from_virtual_fx(&instance_fx.fx); + if fx_type.requires_fx_chain() { + let instance_fx_track_label = get_virtual_track_label( + &instance_fx.track_descriptor.track, + compartment, + session.extended_context(), + ); + let _ = write!(&mut text, " (on track {instance_fx_track_label:.15})"); + } + let control_and_feedback_state = instance_state.global_control_and_feedback_state(); + if !control_and_feedback_state.control_active { + text.push_str(" | CONTROL off"); + } + if !control_and_feedback_state.feedback_active { + text.push_str(" | FEEDBACK off"); + } + if cfg!(debug_assertions) { + let _ = write!(&mut text, " | rt-allocs: {}", undesired_allocation_count()); + } + let label = self.view.require_control(root::ID_MAIN_PANEL_STATUS_2_TEXT); + label.disable(); + label.set_text(text.as_str()); + }); + } + + fn do_with_session(&self, f: impl FnOnce(&UnitModel) -> R) -> anyhow::Result { + match self.unit_model.upgrade() { + None => Err(anyhow!("session not available anymore")), + Some(session) => Ok(f(&session.borrow())), + } + } + + fn do_with_session_mut(&self, f: impl FnOnce(&mut UnitModel) -> R) -> anyhow::Result { + match self.unit_model.upgrade() { + None => Err(anyhow!("session not available anymore")), + Some(session) => Ok(f(&mut session.borrow_mut())), + } + } + + fn enable_or_disable_unit(&self) -> anyhow::Result<()> { + self.unit_model + .upgrade() + .context("upgrade unit model")? + .borrow_mut() + .change_with_notification(UnitCommand::ToggleEnabled, None, self.unit_model.clone()); + Ok(()) + } + + pub fn invalidate_instance_tags(&self) { + self.invalidate_status_1_text(); + } + + fn invalidate_all_controls(&self) { + let _ = self.invalidate_unit_enabled_checkbox(); + let _ = self.invalidate_unit_button(); + let _ = self.invalidate_version_text(); + self.invalidate_status_1_text(); + self.invalidate_status_2_text(); + } + + pub fn notify_units_changed(&self) { + if self.view.window().is_some() { + let _ = self.invalidate_unit_enabled_checkbox(); + let _ = self.invalidate_unit_button(); + } + } + + fn invalidate_unit_enabled_checkbox(&self) -> anyhow::Result<()> { + self.do_with_session(|session| { + self.view + .require_control(root::IDC_UNIT_ENABLED_CHECK_BOX) + .set_checked(session.is_enabled()); + })?; + Ok(()) + } + + fn invalidate_unit_button(&self) -> anyhow::Result<()> { + let instance_panel = self.instance_panel(); + let instance_shell = instance_panel.shell()?; + let unit_count = instance_shell.additional_unit_count() + 1; + let unit_id = instance_panel.displayed_unit_id(); + let (index, unit_model) = instance_shell + .find_unit_index_and_model_by_id(unit_id) + .context("unit not found")?; + let unit_model = unit_model.try_borrow().context("borrow unit model")?; + let label = build_unit_label(&unit_model, index, Some(unit_count)); + self.view + .require_control(root::IDC_UNIT_BUTTON) + .set_text(label); + Ok(()) + } + + fn invalidate_version_text(&self) -> anyhow::Result<()> { + let mut text = String::new(); + text.write_str("Helgobox ")?; + text.write_str(BackboneShell::detailed_version_label())?; + if let Some(remote_config) = BackboneShell::remote_config() { + if report_new_version( + BackboneShell::version(), + &remote_config.plugin.latest_version, + ) { + text.write_str(" [UPDATE AVAILABLE]")?; + } + } + self.view + .require_control(root::ID_MAIN_PANEL_VERSION_TEXT) + .set_text(text); + Ok(()) + } + + fn register_listeners(self: SharedView) { + let state = self.state.borrow(); + self.when(state.scroll_status.changed(), |view| { + view.invalidate_status_1_text(); + }); + self.register_session_listeners(); + } + + fn register_session_listeners(self: &SharedView) { + let _ = self.do_with_session(|session| { + self.when(session.everything_changed(), |view| { + view.invalidate_all_controls(); + }); + let instance_state = session.unit().borrow(); + self.when( + instance_state.global_control_and_feedback_state_changed(), + |view| { + view.invalidate_status_2_text(); + }, + ); + }); + } + + pub fn panel_manager(&self) -> &SharedIndependentPanelManager { + &self.panel_manager + } + + fn handle_changed_target_value(&self, event: TargetValueChangedEvent) { + self.panel_manager + .borrow() + .handle_changed_target_value(event); + } + + fn handle_matched_mapping(&self, event: MappingMatchedEvent) { + self.panel_manager.borrow().handle_matched_mapping(event); + if self.is_open() { + self.mapping_rows_panel.handle_matched_mapping(event); + } + } + + fn handle_target_control_event(&self, event: TargetControlEvent) { + self.panel_manager + .borrow() + .handle_target_control_event(event); + } + + fn handle_source_feedback_event(&self, event: SourceFeedbackEvent) { + self.panel_manager + .borrow() + .handle_source_feedback_event(event); + } + + fn handle_affected( + self: SharedView, + affected: Affected, + initiator: Option, + ) -> anyhow::Result<()> { + self.panel_manager + .borrow() + .handle_affected(&affected, initiator); + self.mapping_rows_panel + .handle_affected(&affected, initiator); + self.header_panel.handle_affected(&affected, initiator); + self.handle_affected_own(affected)?; + Ok(()) + } + + fn handle_internal_info_event(self: SharedView, event: &InternalInfoEvent) { + if !self.is_open() { + return; + } + match event { + InternalInfoEvent::UndesiredAllocationCountChanged => { + self.invalidate_status_2_text(); + } + } + } + + fn handle_external_info_event(self: SharedView, event: InstanceInfoEvent) { + BackboneShell::get() + .proto_hub() + .notify_about_instance_info_event(self.instance_id, event); + } + + fn handle_affected_own( + self: SharedView, + affected: Affected, + ) -> anyhow::Result<()> { + use Affected::*; + use UnitProp::*; + // Handle even if closed + match affected { + One(UnitName) => { + if let Ok(shell) = self.instance_panel().shell() { + BackboneShell::get() + .proto_hub() + .notify_instance_units_changed(&shell); + } + } + One(UnitKey) => { + // Actually, the instances are only affected if the main unit key is changed, but + // so what. + BackboneShell::get().proto_hub().notify_instances_changed(); + send_sessions_to_subscribed_clients(); + } + One(UnitTags) => self.invalidate_status_1_text(), + _ => {} + } + // Handle only if open + if !self.is_open() { + return Ok(()); + } + match affected { + One(InstanceTrack | InstanceFx) => { + self.invalidate_status_2_text(); + } + One(Enabled) => { + let _ = self.invalidate_unit_enabled_checkbox(); + let _ = self.invalidate_unit_button(); + } + One(UnitName) => { + let _ = self.invalidate_unit_button(); + } + One(UnitKey) => { + self.invalidate_status_1_text(); + } + Multiple => { + self.invalidate_all_controls(); + } + _ => {} + } + Ok(()) + } + + fn handle_changed_parameters(&self, session: &UnitModel) { + self.panel_manager + .borrow() + .handle_changed_parameters(session); + } + + fn celebrate_success(&self) { + if let Some(s) = &self.success_sound_player { + let _ = s.play(); + } + } + + fn handle_changed_midi_devices(&self) { + self.header_panel.handle_changed_midi_devices(); + } + + fn handle_changed_conditions(&self) { + self.panel_manager.borrow().handle_changed_conditions(); + if self.is_open() { + self.mapping_rows_panel.handle_changed_conditions(); + } + } + + fn open_unit_popup_menu(self: SharedView) { + self.instance_panel().open_unit_popup_menu(); + } + + fn instance_panel(&self) -> SharedView { + self.instance_panel + .upgrade() + .expect("instance panel doesn't exist anymore") + } + + fn edit_unit_data(&self) -> anyhow::Result<()> { + let (initial_key, initial_name, initial_tags_as_csv) = self.do_with_session(|session| { + ( + session.unit_key().to_owned(), + session.name().map(|n| n.to_string()).unwrap_or_default(), + format_tags_as_csv(session.tags()), + ) + })?; + let initial_csv = format!("{initial_key}|{initial_name}|{initial_tags_as_csv}"); + // Show UI + let csv_result = Reaper::get().medium_reaper().get_user_inputs( + "ReaLearn", + 3, + "Unit Key,Unit Name,Tags,separator=|,extrawidth=200", + initial_csv, + 1024, + ); + // Check if cancelled + let csv = match csv_result { + // Cancelled + None => return Ok(()), + Some(csv) => csv, + }; + // Parse result CSV + let split: Vec<_> = csv.to_str().split('|').collect(); + let (unit_key, unit_name, tags_as_csv) = match split.as_slice() { + [unit_key, unit_name, tags_as_csv] => (unit_key, unit_name, tags_as_csv), + _ => bail!("couldn't split result"), + }; + // Take care of tags + if tags_as_csv != &initial_tags_as_csv { + let tags = parse_tags_from_csv(tags_as_csv); + self.do_with_session_mut(|session| { + session.change_with_notification( + UnitCommand::SetUnitTags(tags), + None, + self.unit_model.clone(), + ); + })?; + } + // Take care of unit name + let unit_name = if unit_name.trim().is_empty() { + None + } else { + Some(unit_name.to_string()) + }; + self.do_with_session_mut(|session| { + session.change_with_notification( + UnitCommand::SetUnitName(unit_name), + None, + self.unit_model.clone(), + ); + })?; + // Take care of unit key + // TODO-low Introduce a SessionId newtype. + let new_unit_key = unit_key.replace(|ch| !nanoid::alphabet::SAFE.contains(&ch), ""); + if !new_unit_key.is_empty() && new_unit_key != initial_key { + if BackboneShell::get().has_unit_with_key(&new_unit_key) { + self.view.require_window().alert( + "ReaLearn", + "There's another open ReaLearn unit which already has this unit key!", + ); + return Ok(()); + } + self.do_with_session_mut(|session| { + session.change_with_notification( + UnitCommand::SetUnitKey(new_unit_key), + None, + self.unit_model.clone(), + ); + })?; + } + Ok(()) + } + + fn when( + self: &SharedView, + event: impl LocalObservable<'static, Item = (), Err = ()> + 'static, + reaction: impl Fn(SharedView) + 'static + Copy, + ) { + when(event.take_until(self.view.closed())) + .with(Rc::downgrade(self)) + .do_async(move |panel, _| reaction(panel)); + } +} + +impl View for UnitPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_MAIN_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + self.open_sub_panels(window); + self.invalidate_all_controls(); + self.register_listeners(); + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + root::IDC_UNIT_ENABLED_CHECK_BOX => { + let _ = self.enable_or_disable_unit(); + } + root::IDC_UNIT_BUTTON => { + // Yes, putting this button into the instance panel would make more sense logically + // but since the unit panel completely covers the instance panel, the button would + // be unusable. + self.open_unit_popup_menu(); + } + root::IDC_EDIT_TAGS_BUTTON => { + self.edit_unit_data().unwrap(); + } + _ => {} + } + } +} + +impl UnitUi for Weak { + fn show_mapping(&self, compartment: CompartmentKind, mapping_id: MappingId) { + upgrade_panel(self).edit_mapping(compartment, mapping_id); + } + + fn show_pot_browser(&self) { + upgrade_panel(self).show_pot_browser(); + } + + fn target_value_changed(&self, event: TargetValueChangedEvent) { + upgrade_panel(self).handle_changed_target_value(event); + } + + fn parameters_changed(&self, session: &UnitModel) { + upgrade_panel(self).handle_changed_parameters(session); + } + + fn midi_devices_changed(&self) { + upgrade_panel(self).handle_changed_midi_devices(); + } + + fn conditions_changed(&self) { + upgrade_panel(self).handle_changed_conditions(); + } + + fn celebrate_success(&self) { + upgrade_panel(self).celebrate_success(); + } + + fn send_projection_feedback(&self, session: &UnitModel, value: ProjectionFeedbackValue) { + let _ = send_projection_feedback_to_subscribed_clients(session.unit_key(), value); + } + + fn mapping_matched(&self, event: MappingMatchedEvent) { + upgrade_panel(self).handle_matched_mapping(event); + } + + fn handle_target_control(&self, event: TargetControlEvent) { + upgrade_panel(self).handle_target_control_event(event); + } + + fn handle_source_feedback(&self, event: SourceFeedbackEvent) { + upgrade_panel(self).handle_source_feedback_event(event); + } + + fn handle_affected( + &self, + session: &UnitModel, + affected: Affected, + initiator: Option, + ) { + // Update secondary GUIs (e.g. Projection) + use Affected::*; + use CompartmentProp::*; + use UnitProp::*; + match &affected { + One(InCompartment(_, One(InMapping(_, _)))) => { + let _ = send_updated_controller_routing(session); + } + _ => {} + } + // Update primary GUI + let _ = upgrade_panel(self).handle_affected(affected, initiator); + } + + fn handle_internal_info_event(&self, event: &InternalInfoEvent) { + upgrade_panel(self).handle_internal_info_event(event); + } + + fn handle_external_info_event(&self, event: InstanceInfoEvent) { + upgrade_panel(self).handle_external_info_event(event); + } + + fn handle_everything_changed(&self, unit_model: &UnitModel) { + BackboneShell::get() + .proto_hub() + .notify_everything_in_unit_has_changed(unit_model.instance_id(), unit_model.unit_id()); + } + + fn handle_global_control_and_feedback_state_changed(&self) { + update_auto_units_async(); + } +} + +fn upgrade_panel(panel: &Weak) -> Rc { + panel.upgrade().expect("unit panel not existing anymore") +} + +pub fn build_unit_label( + unit_model: &UnitModel, + index: Option, + count: Option, +) -> String { + build_unit_label_internal(unit_model, index, count).unwrap_or_default() +} + +/// A given count means that we build the button label, in which case the total number of units will be displayed, too. +fn build_unit_label_internal( + unit_model: &UnitModel, + index: Option, + count: Option, +) -> Result { + use std::fmt::Write; + let mut s = String::new(); + let pos = index.map(|i| i + 2).unwrap_or(1); + // Unit with position + write!(&mut s, "Unit {pos}")?; + // Total unit count, only if button label + if let Some(c) = count { + // This is for a button label. We want to display the total unit count. + write!(&mut s, "/{c}")?; + } + // Indicate which one is an auto unit + if unit_model.auto_unit().is_some() { + write!(&mut s, " (auto)")?; + } + let label = unit_model.name_or_key(); + write!(&mut s, ": {label}")?; + if !unit_model.is_enabled() { + write!(&mut s, " (disabled)")?; + } + if !unit_model.tags().is_empty() { + write!(&mut s, " [{}]", format_tags_as_csv(unit_model.tags()))?; + } + Ok(s) +} + +fn report_new_version(current: &Version, latest: &Version) -> bool { + if current >= latest { + return false; + } + if !latest.pre.is_empty() && current.pre.is_empty() { + return false; + } + true +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/util.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/util.rs new file mode 100644 index 0000000..29580df --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/util.rs @@ -0,0 +1,698 @@ +use crate::application::UnitModel; +use crate::domain::{compartment_param_index_iter, CompartmentKind, Tag}; +use crate::infrastructure::ui::bindings::root; +use helgobox_dialogs::constants; +use reaper_high::Reaper; +use std::cell::RefCell; +use std::path::Path; +use std::str::FromStr; +use swell_ui::{DialogScaling, DialogUnits, Dimensions, SharedView, View, Window}; + +/// The optimal size of the main panel in dialog units. +pub fn main_panel_dimensions() -> Dimensions { + Dimensions::new(main_panel_width(), main_panel_height()) +} + +pub fn main_panel_width() -> DialogUnits { + DialogUnits(constants::MAIN_PANEL_WIDTH).scale(GLOBAL_SCALING.width_scale) +} + +pub fn main_panel_height() -> DialogUnits { + header_panel_height() + mapping_rows_panel_height() + footer_panel_height() +} + +pub fn header_panel_height() -> DialogUnits { + DialogUnits(constants::HEADER_PANEL_HEIGHT).scale(HEADER_PANEL_SCALING.height_scale) +} + +pub fn mapping_row_panel_height() -> DialogUnits { + DialogUnits(constants::MAPPING_ROW_PANEL_HEIGHT).scale(GLOBAL_SCALING.height_scale) +} + +pub fn mapping_rows_panel_height() -> DialogUnits { + DialogUnits(constants::MAPPING_ROWS_PANEL_HEIGHT).scale(GLOBAL_SCALING.height_scale) +} + +pub fn footer_panel_height() -> DialogUnits { + DialogUnits(constants::FOOTER_PANEL_HEIGHT).scale(GLOBAL_SCALING.height_scale) +} + +pub mod symbols { + pub fn indicator_symbol() -> &'static str { + #[cfg(target_os = "windows")] + { + if pretty_symbols_are_supported() { + "●" + } else { + "*" + } + } + #[cfg(target_os = "macos")] + { + "●" + } + #[cfg(target_os = "linux")] + { + "*" + } + } + + pub fn arrow_up_symbol() -> &'static str { + #[cfg(target_os = "windows")] + { + if pretty_symbols_are_supported() { + "🡹" + } else { + "Up" + } + } + #[cfg(target_os = "macos")] + { + "⬆" + } + #[cfg(target_os = "linux")] + { + "Up" + } + } + + pub fn arrow_down_symbol() -> &'static str { + #[cfg(target_os = "windows")] + { + if pretty_symbols_are_supported() { + "🡻" + } else { + "Down" + } + } + #[cfg(target_os = "macos")] + { + "⬇" + } + #[cfg(target_os = "linux")] + { + "Down" + } + } + + pub fn arrow_left_symbol() -> &'static str { + #[cfg(target_os = "windows")] + { + if pretty_symbols_are_supported() { + "🡸" + } else { + "<=" + } + } + #[cfg(target_os = "macos")] + { + "⬅" + } + #[cfg(target_os = "linux")] + { + "<=" + } + } + + pub fn arrow_right_symbol() -> &'static str { + #[cfg(target_os = "windows")] + { + if pretty_symbols_are_supported() { + "🡺" + } else { + "=>" + } + } + #[cfg(target_os = "macos")] + { + "⮕" + } + #[cfg(target_os = "linux")] + { + "=>" + } + } + + #[cfg(target_os = "windows")] + fn pretty_symbols_are_supported() -> bool { + use once_cell::sync::Lazy; + static SOMETHING_LIKE_WINDOWS_10: Lazy = Lazy::new(|| { + let win_version = if let Ok(v) = sys_info::os_release() { + v + } else { + return true; + }; + win_version.as_str() >= "6.2" + }); + *SOMETHING_LIKE_WINDOWS_10 + } +} + +pub mod view { + use crate::infrastructure::ui::util::colors::ColorPair; + use reaper_medium::Hbrush; + use swell_ui::{ViewManager, Window}; + + pub fn get_brush_for_color_pair(color_pair: ColorPair) -> Option { + let color = if Window::dark_mode_is_enabled() { + color_pair.dark + } else { + color_pair.light + }; + ViewManager::get().get_solid_brush(color) + } +} + +pub mod fonts { + use swell_ui::{DialogUnits, Dimensions, FontDescriptor, Pixels, Window}; + + pub fn normal_font(window: Window, font_size: u32) -> FontDescriptor { + let font_dimensions: Dimensions = window.convert_to_pixels(Dimensions::new( + DialogUnits(font_size), + DialogUnits(font_size), + )); + FontDescriptor::new(normal_font_name(), font_dimensions.height.get()) + } + + const fn normal_font_name() -> &'static str { + #[cfg(target_os = "windows")] + { + "Verdana" + } + #[cfg(target_os = "macos")] + { + "Lucida Grande" + } + #[cfg(target_os = "linux")] + { + "DejaVu Sans" + } + } +} + +pub mod colors { + use palette::{Hsl, Lighten}; + use reaper_common_types::{color, RgbColor}; + + #[derive(Copy, Clone, Debug)] + pub struct ColorPair { + pub light: RgbColor, + pub dark: RgbColor, + } + + impl ColorPair { + pub fn generate_from_light(light: RgbColor) -> Self { + Self { + light, + dark: invert_lightness(light), + } + } + } + + fn invert_lightness(color: RgbColor) -> RgbColor { + let hsl = color.to_hsl(); + Hsl::new_const(hsl.hue, hsl.saturation, 1.0 - hsl.lightness).into() + } + + struct ColorPalette { + /// For the light theme, lightens all colors by this factor. + lighten_for_light_theme: f32, + /// For dark theme, lightens all colors by this factor *after* inverting their lightness. + lighten_for_dark_theme: f32, + colors: [RgbColor; 5], + } + + impl ColorPalette { + pub fn get(&self, index: usize) -> ColorPair { + let original_color = self.colors[index]; + let light_color = original_color + .to_linear_srgb() + .lighten(self.lighten_for_light_theme) + .into(); + ColorPair { + light: light_color, + dark: invert_lightness(original_color) + .to_linear_srgb() + .lighten(self.lighten_for_dark_theme) + .into(), + } + } + } + + pub fn instance_panel_background() -> ColorPair { + ColorPair::generate_from_light(tailwind::GRAY_200) + } + + pub fn show_background() -> ColorPair { + ColorPair::generate_from_light(tailwind::GRAY_300.to_linear_srgb().lighten(0.2).into()) + } + + pub fn mapping() -> ColorPair { + MAPPING_PANEL_COLOR_PALETTE.get(0) + } + + pub fn source() -> ColorPair { + MAPPING_PANEL_COLOR_PALETTE.get(1) + } + + pub fn target() -> ColorPair { + MAPPING_PANEL_COLOR_PALETTE.get(2) + } + + pub fn glue() -> ColorPair { + MAPPING_PANEL_COLOR_PALETTE.get(3) + } + + pub fn help() -> ColorPair { + MAPPING_PANEL_COLOR_PALETTE.get(4) + } + + /// Inspired by https://colorhunt.co/palette/96b6c5adc4ceeee0c9f1f0e8 + const MAPPING_PANEL_COLOR_PALETTE: ColorPalette = ColorPalette { + lighten_for_light_theme: 0.4, + lighten_for_dark_theme: 0.0, + colors: [ + tailwind::GRAY_200, + // The original color of the palette was ADC4CE, but that stands out too much. + // This one is 20% lighter. + color!("BDD0D8"), + color!("EEE0C9"), + color!("F1F0E8"), + tailwind::GRAY_200, + ], + }; + + // // 2. Nice pastel colors + // const MAPPING_PANEL_COLOR_PALETTE: ColorPalette = ColorPalette { + // lighten: 0.0, + // colors: [SLATE_200, SKY_100, ORANGE_100, SLATE_100, SLATE_200], + // }; + + // 24. Not too bad. But too much similarity between source and target color. + // const MAPPING_PANEL_COLOR_PALETTE: ColorPalette = ColorPalette { + // lighten: 0.5, + // colors: [ + // color!("C9D7DD"), + // color!("FFF3CF"), + // color!("E8C872"), + // color!("C9D7DD"), + // color!("637A9F"), + // ], + // }; + + // // Alternative + // const MAPPING_PANEL_COLOR_PALETTE: ColorPalette = ColorPalette { + // lighten: 0.5, + // colors: [ + // color!("637A9F"), + // color!("FFF3CF"), + // color!("E8C872"), + // color!("C9D7DD"), + // color!("637A9F"), + // ], + // }; + + pub mod tailwind { + use reaper_common_types::colors; + + colors! { + SLATE_50 = "f8fafc"; + SLATE_100 = "f1f5f9"; + SLATE_200 = "e2e8f0"; + SLATE_300 = "cbd5e1"; + SLATE_400 = "94a3b8"; + SLATE_500 = "64748b"; + SLATE_600 = "475569"; + SLATE_700 = "334155"; + SLATE_800 = "1e293b"; + SLATE_900 = "0f172a"; + SLATE_950 = "020617"; + GRAY_50 = "f9fafb"; + GRAY_100 = "f3f4f6"; + GRAY_200 = "e5e7eb"; + GRAY_300 = "d1d5db"; + GRAY_400 = "9ca3af"; + GRAY_500 = "6b7280"; + GRAY_600 = "4b5563"; + GRAY_700 = "374151"; + GRAY_800 = "1f2937"; + GRAY_900 = "111827"; + GRAY_950 = "030712"; + ZINC_50 = "fafafa"; + ZINC_100 = "f4f4f5"; + ZINC_200 = "e4e4e7"; + ZINC_300 = "d4d4d8"; + ZINC_400 = "a1a1aa"; + ZINC_500 = "71717a"; + ZINC_600 = "52525b"; + ZINC_700 = "3f3f46"; + ZINC_800 = "27272a"; + ZINC_900 = "18181b"; + ZINC_950 = "09090b"; + NEUTRAL_50 = "fafafa"; + NEUTRAL_100 = "f5f5f5"; + NEUTRAL_200 = "e5e5e5"; + NEUTRAL_300 = "d4d4d4"; + NEUTRAL_400 = "a3a3a3"; + NEUTRAL_500 = "737373"; + NEUTRAL_600 = "525252"; + NEUTRAL_700 = "404040"; + NEUTRAL_800 = "262626"; + NEUTRAL_900 = "171717"; + NEUTRAL_950 = "0a0a0a"; + STONE_50 = "fafaf9"; + STONE_100 = "f5f5f4"; + STONE_200 = "e7e5e4"; + STONE_300 = "d6d3d1"; + STONE_400 = "a8a29e"; + STONE_500 = "78716c"; + STONE_600 = "57534e"; + STONE_700 = "44403c"; + STONE_800 = "292524"; + STONE_900 = "1c1917"; + STONE_950 = "0c0a09"; + RED_50 = "fef2f2"; + RED_100 = "fee2e2"; + RED_200 = "fecaca"; + RED_300 = "fca5a5"; + RED_400 = "f87171"; + RED_500 = "ef4444"; + RED_600 = "dc2626"; + RED_700 = "b91c1c"; + RED_800 = "991b1b"; + RED_900 = "7f1d1d"; + RED_950 = "450a0a"; + ORANGE_50 = "fff7ed"; + ORANGE_100 = "ffedd5"; + ORANGE_200 = "fed7aa"; + ORANGE_300 = "fdba74"; + ORANGE_400 = "fb923c"; + ORANGE_500 = "f97316"; + ORANGE_600 = "ea580c"; + ORANGE_700 = "c2410c"; + ORANGE_800 = "9a3412"; + ORANGE_900 = "7c2d12"; + ORANGE_950 = "431407"; + AMBER_50 = "fffbeb"; + AMBER_100 = "fef3c7"; + AMBER_200 = "fde68a"; + AMBER_300 = "fcd34d"; + AMBER_400 = "fbbf24"; + AMBER_500 = "f59e0b"; + AMBER_600 = "d97706"; + AMBER_700 = "b45309"; + AMBER_800 = "92400e"; + AMBER_900 = "78350f"; + AMBER_950 = "451a03"; + YELLOW_50 = "fefce8"; + YELLOW_100 = "fef9c3"; + YELLOW_200 = "fef08a"; + YELLOW_300 = "fde047"; + YELLOW_400 = "facc15"; + YELLOW_500 = "eab308"; + YELLOW_600 = "ca8a04"; + YELLOW_700 = "a16207"; + YELLOW_800 = "854d0e"; + YELLOW_900 = "713f12"; + YELLOW_950 = "422006"; + LIME_50 = "f7fee7"; + LIME_100 = "ecfccb"; + LIME_200 = "d9f99d"; + LIME_300 = "bef264"; + LIME_400 = "a3e635"; + LIME_500 = "84cc16"; + LIME_600 = "65a30d"; + LIME_700 = "4d7c0f"; + LIME_800 = "3f6212"; + LIME_900 = "365314"; + LIME_950 = "1a2e05"; + GREEN_50 = "f0fdf4"; + GREEN_100 = "dcfce7"; + GREEN_200 = "bbf7d0"; + GREEN_300 = "86efac"; + GREEN_400 = "4ade80"; + GREEN_500 = "22c55e"; + GREEN_600 = "16a34a"; + GREEN_700 = "15803d"; + GREEN_800 = "166534"; + GREEN_900 = "14532d"; + GREEN_950 = "052e16"; + EMERALD_50 = "ecfdf5"; + EMERALD_100 = "d1fae5"; + EMERALD_200 = "a7f3d0"; + EMERALD_300 = "6ee7b7"; + EMERALD_400 = "34d399"; + EMERALD_500 = "10b981"; + EMERALD_600 = "059669"; + EMERALD_700 = "047857"; + EMERALD_800 = "065f46"; + EMERALD_900 = "064e3b"; + EMERALD_950 = "022c22"; + TEAL_50 = "f0fdfa"; + TEAL_100 = "ccfbf1"; + TEAL_200 = "99f6e4"; + TEAL_300 = "5eead4"; + TEAL_400 = "2dd4bf"; + TEAL_500 = "14b8a6"; + TEAL_600 = "0d9488"; + TEAL_700 = "0f766e"; + TEAL_800 = "115e59"; + TEAL_900 = "134e4a"; + TEAL_950 = "042f2e"; + CYAN_50 = "ecfeff"; + CYAN_100 = "cffafe"; + CYAN_200 = "a5f3fc"; + CYAN_300 = "67e8f9"; + CYAN_400 = "22d3ee"; + CYAN_500 = "06b6d4"; + CYAN_600 = "0891b2"; + CYAN_700 = "0e7490"; + CYAN_800 = "155e75"; + CYAN_900 = "164e63"; + CYAN_950 = "083344"; + SKY_50 = "f0f9ff"; + SKY_100 = "e0f2fe"; + SKY_200 = "bae6fd"; + SKY_300 = "7dd3fc"; + SKY_400 = "38bdf8"; + SKY_500 = "0ea5e9"; + SKY_600 = "0284c7"; + SKY_700 = "0369a1"; + SKY_800 = "075985"; + SKY_900 = "0c4a6e"; + SKY_950 = "082f49"; + BLUE_50 = "eff6ff"; + BLUE_100 = "dbeafe"; + BLUE_200 = "bfdbfe"; + BLUE_300 = "93c5fd"; + BLUE_400 = "60a5fa"; + BLUE_500 = "3b82f6"; + BLUE_600 = "2563eb"; + BLUE_700 = "1d4ed8"; + BLUE_800 = "1e40af"; + BLUE_900 = "1e3a8a"; + BLUE_950 = "172554"; + INDIGO_50 = "eef2ff"; + INDIGO_100 = "e0e7ff"; + INDIGO_200 = "c7d2fe"; + INDIGO_300 = "a5b4fc"; + INDIGO_400 = "818cf8"; + INDIGO_500 = "6366f1"; + INDIGO_600 = "4f46e5"; + INDIGO_700 = "4338ca"; + INDIGO_800 = "3730a3"; + INDIGO_900 = "312e81"; + INDIGO_950 = "1e1b4b"; + VIOLET_50 = "f5f3ff"; + VIOLET_100 = "ede9fe"; + VIOLET_200 = "ddd6fe"; + VIOLET_300 = "c4b5fd"; + VIOLET_400 = "a78bfa"; + VIOLET_500 = "8b5cf6"; + VIOLET_600 = "7c3aed"; + VIOLET_700 = "6d28d9"; + VIOLET_800 = "5b21b6"; + VIOLET_900 = "4c1d95"; + VIOLET_950 = "2e1065"; + PURPLE_50 = "faf5ff"; + PURPLE_100 = "f3e8ff"; + PURPLE_200 = "e9d5ff"; + PURPLE_300 = "d8b4fe"; + PURPLE_400 = "c084fc"; + PURPLE_500 = "a855f7"; + PURPLE_600 = "9333ea"; + PURPLE_700 = "7e22ce"; + PURPLE_800 = "6b21a8"; + PURPLE_900 = "581c87"; + PURPLE_950 = "3b0764"; + FUCHSIA_50 = "fdf4ff"; + FUCHSIA_100 = "fae8ff"; + FUCHSIA_200 = "f5d0fe"; + FUCHSIA_300 = "f0abfc"; + FUCHSIA_400 = "e879f9"; + FUCHSIA_500 = "d946ef"; + FUCHSIA_600 = "c026d3"; + FUCHSIA_700 = "a21caf"; + FUCHSIA_800 = "86198f"; + FUCHSIA_900 = "701a75"; + FUCHSIA_950 = "4a044e"; + PINK_50 = "fdf2f8"; + PINK_100 = "fce7f3"; + PINK_200 = "fbcfe8"; + PINK_300 = "f9a8d4"; + PINK_400 = "f472b6"; + PINK_500 = "ec4899"; + PINK_600 = "db2777"; + PINK_700 = "be185d"; + PINK_800 = "9d174d"; + PINK_900 = "831843"; + PINK_950 = "500724"; + ROSE_50 = "fff1f2"; + ROSE_100 = "ffe4e6"; + ROSE_200 = "fecdd3"; + ROSE_300 = "fda4af"; + ROSE_400 = "fb7185"; + ROSE_500 = "f43f5e"; + ROSE_600 = "e11d48"; + ROSE_700 = "be123c"; + ROSE_800 = "9f1239"; + ROSE_900 = "881337"; + ROSE_950 = "4c0519"; + } + } +} + +pub fn open_in_browser(url: &str) { + if webbrowser::open(url).is_err() { + Reaper::get().show_console_msg( + format!("Couldn't open browser. Please open the following address in your browser manually:\n\n{url}\n\n") + ); + } +} + +#[cfg(target_os = "windows")] +const FILE_MANAGER_CMD: &str = "explorer"; + +#[cfg(target_os = "macos")] +const FILE_MANAGER_CMD: &str = "open"; + +#[cfg(target_os = "linux")] +const FILE_MANAGER_CMD: &str = "xdg-open"; + +pub fn open_in_file_manager(path: &Path) -> Result<(), &'static str> { + let final_path = path + .canonicalize() + .map_err(|_| "couldn't canonicalize path")?; + std::process::Command::new(FILE_MANAGER_CMD) + .arg(final_path) + .spawn() + .map_err(|_| "couldn't execute command to open file manager")?; + Ok(()) +} + +pub fn open_in_text_editor( + text: &str, + parent_window: Window, + suffix: &str, +) -> Result { + edit::edit_with_builder(text, edit::Builder::new().prefix("realearn-").suffix(suffix)).map_err(|e| { + use std::io::ErrorKind::*; + let msg = match e.kind() { + NotFound => "Couldn't find text editor.".to_owned(), + InvalidData => { + "File is not properly UTF-8 encoded. Either avoid any special characters or make sure you use UTF-8 encoding!".to_owned() + } + _ => e.to_string() + }; + parent_window + .alert("Helgobox", format!("Couldn't obtain text:\n\n{msg}")); + "couldn't obtain text" + }) +} + +pub fn parse_tags_from_csv(text: &str) -> Vec { + text.split(',') + .filter_map(|item| Tag::from_str(item).ok()) + .collect() +} + +pub fn compartment_parameter_dropdown_contents( + session: &UnitModel, + compartment: CompartmentKind, +) -> impl Iterator + '_ { + compartment_param_index_iter().map(move |i| { + let param_name = session + .params() + .compartment_params(compartment) + .get_parameter_name(i); + (i.get() as isize, format!("{}. {}", i.get() + 1, param_name)) + }) +} + +pub const GLOBAL_SCALING: DialogScaling = DialogScaling { + x_scale: root::GLOBAL_X_SCALE, + y_scale: root::GLOBAL_Y_SCALE, + width_scale: root::GLOBAL_WIDTH_SCALE, + height_scale: root::GLOBAL_HEIGHT_SCALE, +}; + +pub const MAPPING_PANEL_SCALING: DialogScaling = DialogScaling { + x_scale: root::MAPPING_PANEL_X_SCALE, + y_scale: root::MAPPING_PANEL_Y_SCALE, + width_scale: root::MAPPING_PANEL_WIDTH_SCALE, + height_scale: root::MAPPING_PANEL_HEIGHT_SCALE, +}; + +pub const HEADER_PANEL_SCALING: DialogScaling = DialogScaling { + x_scale: root::HEADER_PANEL_X_SCALE, + y_scale: root::HEADER_PANEL_Y_SCALE, + width_scale: root::HEADER_PANEL_WIDTH_SCALE, + height_scale: root::HEADER_PANEL_HEIGHT_SCALE, +}; + +pub fn open_child_panel_dyn( + panel_slot: &RefCell>>, + panel: T, + parent_window: Window, +) { + let panel = SharedView::new(panel); + let panel_clone = panel.clone(); + if let Some(existing_panel) = panel_slot.replace(Some(panel)) { + existing_panel.close(); + }; + panel_clone.open(parent_window); +} + +pub fn open_child_panel( + panel_slot: &RefCell>>, + panel: T, + parent_window: Window, +) -> SharedView { + let panel = SharedView::new(panel); + let panel_clone = panel.clone(); + if let Some(existing_panel) = panel_slot.replace(Some(panel)) { + existing_panel.close(); + }; + panel_clone.clone().open(parent_window); + panel_clone +} + +pub fn close_child_panel_if_open(panel: &RefCell>>) { + if let Some(existing_panel) = panel.take() { + existing_panel.close(); + } +} + +#[allow(dead_code)] +pub fn alert_feature_not_available() { + // Made some progress with egui on Linux, but it's wonky. Flickering. Keyboard input + // needs window to be refocused to work at all. And the worst: baseview makes the + // window code run in a new thread. On macOS and Windows, the run_ui code all runs + // in the main thread. Which is very convenient because it allows us to call REAPER + // functions. I think we need to bypass baseview on Linux and write our own little + // egui integration. + crate::base::notification::alert( + "This feature is not available in this installation of ReaLearn, either because it's \ + not yet supported on this operating system or it was intentionally not included in the build.", + ); +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/welcome_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/welcome_panel.rs new file mode 100644 index 0000000..5dd489a --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/welcome_panel.rs @@ -0,0 +1,161 @@ +use crate::base::notification::notify_user_on_anyhow_error; +use crate::infrastructure::plugin::dynamic_toolbar::custom_toolbar_api_is_available; +use crate::infrastructure::plugin::{ + BackboneShell, ACTION_SHOW_HIDE_PLAYTIME_COMMAND_NAME, ACTION_SHOW_WELCOME_SCREEN_LABEL, +}; +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::util::{fonts, symbols}; +use reaper_low::raw; +use std::fmt::Debug; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Debug)] +pub struct WelcomePanel { + view: ViewContext, +} + +impl WelcomePanel { + pub fn new() -> Self { + Self { + view: Default::default(), + } + } + + fn toggle_toolbar_button(&self, command_name: &str) -> anyhow::Result<()> { + if !custom_toolbar_api_is_available() { + self.view.require_window().alert( + "Helgobox", + "To use this feature, please update REAPER to version 7.12 or later!", + ); + return Ok(()); + } + BackboneShell::get().toggle_toolbar_button_dynamically(command_name)?; + self.invalidate_controls(); + Ok(()) + } + + fn toggle_send_errors_to_dev(&self) { + let shell = BackboneShell::get(); + let value = shell.config().send_errors_to_dev(); + shell.set_send_errors_to_dev_persistently(!value); + self.invalidate_controls(); + } + + fn toggle_show_errors_in_console(&self) { + let shell = BackboneShell::get(); + let value = shell.config().show_errors_in_console(); + shell.set_show_errors_in_console_persistently(!value); + self.invalidate_controls(); + } + + fn toggle_notify_about_updates(&self) { + let shell = BackboneShell::get(); + let value = shell.config().notify_about_updates(); + shell.set_notify_about_updates_persistently(!value); + self.invalidate_controls(); + } + + fn invalidate_controls(&self) { + if custom_toolbar_api_is_available() { + self.invalidate_toolbar_checkboxes(); + } + let send_errors_to_dev = BackboneShell::get().config().send_errors_to_dev(); + let show_errors_in_console = BackboneShell::get().config().show_errors_in_console(); + let notify_about_updates = BackboneShell::get().config().notify_about_updates(); + let comment = match (send_errors_to_dev, show_errors_in_console) { + (false, false) => { + Some("Please consider checking at least one of the error checkboxes, as it helps to improve Helgobox!") + } + (false, true) => None, + (true, _) => Some("Errors are sent anonymously. Please see our privacy statement for details."), + }; + self.view + .require_control(root::ID_SETUP_SEND_ERRORS_TO_DEV) + .set_checked(send_errors_to_dev); + self.view + .require_control(root::ID_SETUP_SHOW_ERRORS_IN_CONSOLE) + .set_checked(show_errors_in_console); + self.view + .require_control(root::ID_SETUP_NOTIFY_ABOUT_UPDATES) + .set_checked(notify_about_updates); + self.view + .require_control(root::ID_SETUP_COMMENT) + .set_text_or_hide(comment); + self.invalidate_button(); + } + + fn invalidate_toolbar_checkboxes(&self) { + let bindings = [( + root::ID_SETUP_ADD_PLAYTIME_TOOLBAR_BUTTON, + ACTION_SHOW_HIDE_PLAYTIME_COMMAND_NAME, + )]; + for (control_id, action_name) in bindings { + let checked = BackboneShell::get() + .config() + .toolbar_button_is_enabled(action_name); + self.view.require_control(control_id).set_checked(checked); + } + } + + fn invalidate_button(&self) { + self.view + .require_control(root::ID_SETUP_PANEL_OK) + .set_text("Close"); + } +} + +impl View for WelcomePanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_SETUP_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, window: Window) -> bool { + window.center_on_screen(); + let large_font = fonts::normal_font(window, 18); + let medium_font = fonts::normal_font(window, 12); + // Text 1 + let text_1 = window.require_control(root::ID_SETUP_INTRO_TEXT_1); + text_1.set_cached_font(large_font); + text_1.set_text("Helgobox has been\nsuccessfully installed!"); + // Text 2 + let text_2 = window.require_control(root::ID_SETUP_INTRO_TEXT_2); + text_2.set_cached_font(medium_font); + text_2.set_text("Consider the following options to\noptimize your user experience:"); + // Text 3 + let text_3 = window.require_control(root::ID_SETUP_TIP_TEXT); + let arrow = symbols::arrow_right_symbol(); + text_3.set_text(format!("Tip: You can come back here at any time via\nExtensions {arrow} Helgobox {arrow} {ACTION_SHOW_WELCOME_SCREEN_LABEL}")); + // Checkboxes + self.invalidate_controls(); + true + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + root::ID_SETUP_ADD_PLAYTIME_TOOLBAR_BUTTON => { + notify_user_on_anyhow_error( + self.toggle_toolbar_button(ACTION_SHOW_HIDE_PLAYTIME_COMMAND_NAME), + ); + } + root::ID_SETUP_SEND_ERRORS_TO_DEV => { + self.toggle_send_errors_to_dev(); + } + root::ID_SETUP_SHOW_ERRORS_IN_CONSOLE => { + self.toggle_show_errors_in_console(); + } + root::ID_SETUP_NOTIFY_ABOUT_UPDATES => { + self.toggle_notify_about_updates(); + } + root::ID_SETUP_PANEL_OK => self.close(), + // IDCANCEL is escape button + raw::IDCANCEL => { + self.close(); + } + _ => {} + } + } +} diff --git a/plugin-reaper-realearn/main/src/infrastructure/ui/yaml_editor_panel.rs b/plugin-reaper-realearn/main/src/infrastructure/ui/yaml_editor_panel.rs new file mode 100644 index 0000000..b4993fe --- /dev/null +++ b/plugin-reaper-realearn/main/src/infrastructure/ui/yaml_editor_panel.rs @@ -0,0 +1,153 @@ +use crate::infrastructure::ui::bindings::root; +use crate::infrastructure::ui::util::{open_in_browser, open_in_text_editor}; +use derivative::Derivative; +use reaper_low::raw; +use serde_yaml::Mapping as YamlMapping; +use std::cell::RefCell; +use swell_ui::{SharedView, View, ViewContext, Window}; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct YamlEditorPanel { + view: ViewContext, + content: RefCell, serde_yaml::Error>>, + #[derivative(Debug = "ignore")] + apply: Box)>, +} + +impl YamlEditorPanel { + pub fn new( + initial_content: Option, + apply: impl Fn(Option) + 'static, + ) -> Self { + Self { + view: Default::default(), + content: RefCell::new(Ok(initial_content)), + apply: Box::new(apply), + } + } + + fn apply(&self) { + if let Ok(c) = self.content.borrow().as_ref() { + (self.apply)(c.clone()); + } + } + + fn parse_final_result(&self) -> Result, serde_yaml::Error> { + let text = self.text(); + let trimmed_text = text.trim(); + let res = if trimmed_text.is_empty() { + None + } else { + Some(serde_yaml::from_str(trimmed_text)?) + }; + Ok(res) + } + + fn invalidate_text_from_initial_content(&self) { + let text = if let Ok(Some(m)) = &self.content.borrow().as_ref() { + serde_yaml::to_string(m).unwrap() + } else { + "".to_owned() + }; + self.set_text(&text); + self.invalidate_info(); + } + + fn update_content(&self) { + *self.content.borrow_mut() = self.parse_final_result(); + self.invalidate_info(); + } + + fn invalidate_info(&self) { + let info_text = match self.content.borrow().as_ref() { + Ok(None) => "Okay! No properties defined.".to_owned(), + Ok(Some(m)) => format!( + "Okay! Defined {} properties. Close the window to apply them.", + m.len() + ), + Err(e) => e.to_string(), + }; + self.view + .require_control(root::ID_YAML_EDIT_INFO_TEXT) + .set_text(info_text); + } + + fn open_in_text_editor(&self) { + if let Ok(edited_text) = + open_in_text_editor(&self.text(), self.view.require_window(), ".yaml") + { + self.set_text(&edited_text); + self.update_content(); + } + } + + fn text(&self) -> String { + self.view + .require_control(root::ID_YAML_EDIT_CONTROL) + .multi_line_text() + .unwrap_or_default() + } + + fn set_text(&self, text: &str) { + self.view + .require_control(root::ID_YAML_EDIT_CONTROL) + .set_multi_line_text(text); + } +} + +impl View for YamlEditorPanel { + fn dialog_resource_id(&self) -> u32 { + root::ID_YAML_EDITOR_PANEL + } + + fn view_context(&self) -> &ViewContext { + &self.view + } + + fn opened(self: SharedView, _window: Window) -> bool { + self.invalidate_text_from_initial_content(); + true + } + + fn close_requested(self: SharedView) -> bool { + if self.parse_final_result().is_err() { + !self.view.require_window().confirm("ReaLearn", "Are you sure you want to close this window? The text you have entered is not valid YAML, so ReaLearn will discard it!") + } else { + false + } + } + + fn on_destroy(self: SharedView, _window: Window) { + self.apply(); + } + + fn button_clicked(self: SharedView, resource_id: u32) { + match resource_id { + // Escape key + raw::IDCANCEL => self.close(), + root::ID_YAML_HELP_BUTTON => help(), + root::ID_YAML_TEXT_EDITOR_BUTTON => self.open_in_text_editor(), + _ => {} + } + } + + #[cfg(target_os = "macos")] + fn key_up(self: SharedView, _key_code: u8) -> bool { + self.update_content(); + true + } + + #[cfg(not(target_os = "macos"))] + fn edit_control_changed(self: SharedView, resource_id: u32) -> bool { + match resource_id { + root::ID_YAML_EDIT_CONTROL => self.update_content(), + _ => return false, + }; + true + } +} + +fn help() { + open_in_browser("https://docs.helgoboss.org/realearn/goto#advanced-settings-dialog"); +} diff --git a/plugin-reaper-realearn/main/src/lib.rs b/plugin-reaper-realearn/main/src/lib.rs new file mode 100644 index 0000000..b4fb4a3 --- /dev/null +++ b/plugin-reaper-realearn/main/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(clippy::single_match)] +#![recursion_limit = "512"] +extern crate vst; + +#[macro_use] +mod base; +mod application; +mod domain; +mod infrastructure; diff --git a/plugin-reaper-realearn/main/templates/companion-app-setup.html b/plugin-reaper-realearn/main/templates/companion-app-setup.html new file mode 100644 index 0000000..f967ce2 --- /dev/null +++ b/plugin-reaper-realearn/main/templates/companion-app-setup.html @@ -0,0 +1,473 @@ +{% let activation_class -%} +{% if server_is_running -%} +{% let activation_class = "active" -%} +{% else -%} +{% let activation_class = "inactive" -%} +{% endif -%} + + + + + ReaLearn + + + + +

+ + + + + + + + + + + + + + + + +
Host:{{ server_host }}
HTTP port:{{ server_http_port }}
HTTPS port:{{ server_https_port }}
Session ID:{{ session_id }}
+ + + +

  • + The app might ask you to open ports in your firewall. If so, follow below instructions: +
      + {% if os == "windows" %} +
    • + Let ReaLearn add an exception to your Windows Firewall using the corresponding action in the context + menu of ReaLearn's header panel. +
    • + {% else if os == "macos" %} +
    • + Manually add a firewall rule that opens ports {{ server_http_port }} and + {{ server_https_port }} for incoming TCP connections. +
    • + {% else %} +
    • + Manually add a firewall rule that opens ports {{ server_http_port }} and + {{ server_https_port }} for incoming TCP connections. +
    • + {% endif %} +
    • + After that, retry connecting via mobile app. +
    • +
    • + Troubleshooting: +
        +
      • + Check the obvious: Make sure your mobile device is in the same network as your computer running + REAPER and ReaLearn (both should use Wi-Fi). +
      • +
      • + If your mobile device still cannot connect, you can temporarily switch off your firewall to see + if that's the issue. +
      • +
      • + If this helped, switch your Firewall on again and check if there's a firewall rule that blocks + REAPER. Remove it! If that still doesn't help, try to add a Firewall rule manually that opens + ports {{ server_http_port }} and {{ server_https_port }} for incoming TCP + connections. +
      • +
      +
    • +
    +
  • + +

    Can I use this on stage?

    +

    + At the moment this feature is optimized for usage in a studio environment, not for usage on stage. Before using it + in live scenarios, please consider the following advice: +

    +
      +
    • + In order to connect to ReaLearn, your mobile device needs to be in the same network as this computer. At home + this is usually a no-brainer because you have a wireless access point that connects everything for you. On stage + you would need to establish an ad-hoc wireless network connection between your computer and the mobile device. + This is possible and not really difficult but ReaLearn won't set it up for you. +
    • +
    • + ReaLearn's server is not password-protected or rate-limited. Therefore you should make sure that your ad-hoc + network uses a strong password. Otherwise a hacker with bad intentions could possibly find ways to mess up your + stage performance. +
    • +
    + + \ No newline at end of file diff --git a/plugin-reaper-realearn/playtime-api/Cargo.toml b/plugin-reaper-realearn/playtime-api/Cargo.toml new file mode 100644 index 0000000..561e7ba --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "playtime-api" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +serde.workspace = true +# For reusing common sound-related types that conveniently happen to be compatible with REAPER +reaper-common-types.workspace = true +# For exposing a runtime API within REAPER +reaper-low.workspace = true +# For being able to use the API macro +helgobox-macros.workspace = true +# For generating random IDs +nanoid.workspace = true +# For easier Display impl +derive_more.workspace = true +# For encoding/decoding a signed matrix value +rmp-serde.workspace = true +# For encoding/decoding a signed matrix value +base64.workspace = true +# For proper error types +thiserror.workspace = true +# For date/time persistence +chrono = { workspace = true, features = ["serde"] } +# For better error handling +anyhow.workspace = true +# For capturing and reporting unknown properties +serde_json.workspace = true +# For primitive enums +strum.workspace = true +# For primitive enums +num_enum.workspace = true +# For UTF-8 paths +camino = { workspace = true, features = ["serde1"] } + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/playtime-api/src/lib.rs b/plugin-reaper-realearn/playtime-api/src/lib.rs new file mode 100644 index 0000000..60024c2 --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/src/lib.rs @@ -0,0 +1,2 @@ +pub mod persistence; +pub mod runtime; diff --git a/plugin-reaper-realearn/playtime-api/src/persistence/mod.rs b/plugin-reaper-realearn/playtime-api/src/persistence/mod.rs new file mode 100644 index 0000000..9e21a5d --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/src/persistence/mod.rs @@ -0,0 +1,1873 @@ +//! This is the API for building clip engine presets. +//! +//! It is designed using the following conventions: +//! +//! - Fields are definitely optional if they have a totally natural default or are an optional +//! override of an otherwise inherited value. +//! - For fields that are added later, we must take care of backward compatibility. There are +//! two approaches to achieve that. One is `#[serde(default]`. This is the easiest approach and +//! very tempting to choose if the type has a natural default. However: This means this field +//! will *always* be present when serializing the state. And that means 1) there's no way of +//! leaving the value of that field open until the user actually sets it (often okay). +//! And 2) "Export as Lua without default values" will always contain those fields, even if they +//! are defaults (see `ConversionStyle`). If you don't want this, use the second approach: +//! An `Option` annotated with `#[serde(skip_serializing_if = "Option::is_none")]`. +//! - Fat enum variants are used to distinguish between multiple alternatives, but not as a general +//! rule. For UI purposes, it's sometimes desirable to save data even it's not actually in use. +//! In the processing layer this would be different. +//! - For the same reasons, boolean data types are allowed and not in general substituted by On/Off +//! enums. +//! - Only a subset of the possible Rust data structuring possibilities are used. The ones that +//! work well with ReaLearn Script (`lua_serializer.rs`) **and** our Rust-to-Dart converter. +//! For the latter, we need to avoid things like `#[serde(flatten)]` or type parameters! + +use std::cmp; +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter}; + +use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_ENGINE; +use base64::Engine; +use camino::Utf8PathBuf; +use chrono::NaiveDateTime; +use derive_more::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use reaper_common_types::{Bpm, Db, DurationInBeats, DurationInSeconds, Semitones}; +use serde::{Deserialize, Deserializer, Serialize}; +use strum::EnumIter; + +use crate::runtime::CellAddress; + +mod serialization; + +/// Global settings that apply to all Playtime instances. +/// +/// This is different from the app settings (which are also global) in that the app settings are only about GUI +/// aspects (less important) and managed by the app. Whereas the engine settings configure settings that +/// apply even without GUI, and they are managed by the engine. +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct PlaytimeSettings { + #[serde(skip_serializing_if = "Option::is_none")] + pub tempo_latency: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum TempoLatency { + #[default] + Auto, + Manual(ManualTempoLatency), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct ManualTempoLatency { + pub millis: u32, +} + +impl Default for ManualTempoLatency { + fn default() -> Self { + Self { millis: 200 } + } +} + +// TODO-high-playtime-after-release Add start time detection +// TODO-high-playtime-after-release Add legato + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Matrix { + /// All columns from left to right. + #[serde(skip_serializing_if = "Option::is_none")] + pub columns: Option>, + /// All rows from top to bottom. + #[serde(skip_serializing_if = "Option::is_none")] + pub rows: Option>, + #[serde(default)] + pub clip_play_settings: MatrixClipPlaySettings, + #[serde(default)] + pub clip_record_settings: MatrixClipRecordSettings, + #[serde(default)] + pub transport_sync_mode: TransportSyncMode, + #[serde(default)] + pub common_tempo_range: TempoRange, + /// Whether to automatically activate a slot when it's triggered. + /// + /// This might seem like a purely visual setting at first, but it's not! Activating + /// a slot happens on "server-side", so certain actions can be carried out on the + /// active slot and the active slot might even be persisted in future to recall a + /// specific setup. + #[serde(default)] + pub activate_slot_on_trigger: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub click_volume: Option, + /// Metronome output channel. + #[serde(skip_serializing_if = "Option::is_none")] + pub click_channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pre_roll_bars: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub color_palette: Option, + #[serde(default)] + pub content_quantization_settings: ContentQuantizationSettings, + #[serde(default)] + pub sequencer: MatrixSequencer, + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub unknown_props: Option>, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +#[repr(usize)] +pub enum TransportSyncMode { + /// Partial sync. + /// + /// - REAPER start => Playtime start + /// - REAPER stop => Playtime stop + /// - Playtime start => - + /// - Playtime stop => REAPER stop (feels weird otherwise) + #[default] + Partial, + /// Full sync. + /// + /// - REAPER start => Playtime start + /// - REAPER stop => Playtime stop + /// - Playtime start => REAPER start + /// - Playtime stop => REAPER stop (feels weird otherwise) + Full, +} + +// This default is only used to create the initial undo point. +impl Default for Matrix { + fn default() -> Self { + Self { + columns: None, + rows: None, + clip_play_settings: Default::default(), + clip_record_settings: Default::default(), + transport_sync_mode: Default::default(), + common_tempo_range: Default::default(), + activate_slot_on_trigger: ACTIVATE_SLOT_ON_TRIGGER_DEFAULT, + click_volume: None, + click_channel: None, + pre_roll_bars: None, + color_palette: None, + content_quantization_settings: Default::default(), + sequencer: Default::default(), + unknown_props: None, + } + } +} + +pub const ACTIVATE_SLOT_ON_TRIGGER_DEFAULT: bool = true; +pub const PRE_ROLL_BARS_DEFAULT: u32 = 2; + +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MatrixSequencer { + #[serde(default)] + pub sequences: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub active_sequence: Option, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixSequence { + pub id: MatrixSequenceId, + pub info: MatrixSequenceInfo, + pub data: MatrixSequenceData, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixSequenceInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + pub created_at: NaiveDateTime, +} + +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MatrixSequenceData { + pub ppq: u64, + pub count_in: u64, + pub events: Vec, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct MatrixSequenceEvent { + pub pulse_diff: u32, + pub message: MatrixSequenceMessage, +} + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub enum MatrixSequenceMessage { + PanicMatrix, + StopMatrix, + PanicColumn(MatrixSequenceColumnMessage), + StopColumn(MatrixSequenceColumnMessage), + StartScene(MatrixSequenceRowMessage), + PanicSlot(MatrixSequenceSlotMessage), + StartSlot(MatrixSequenceStartSlotMessage), + StopSlot(MatrixSequenceSlotMessage), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixSequenceColumnMessage { + pub index: u32, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixSequenceRowMessage { + pub index: u32, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixSequenceSlotMessage { + pub column_index: u32, + pub row_index: u32, +} + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixSequenceStartSlotMessage { + pub column_index: u32, + pub row_index: u32, + pub velocity: f64, +} + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct ContentQuantizationSettings { + pub quantization: EvenQuantization, +} + +impl Default for ContentQuantizationSettings { + fn default() -> Self { + Self { + quantization: EvenQuantization { + numerator: 1, + denominator: 16, + }, + } + } +} + +#[derive(Clone, PartialEq, Debug, Serialize)] +#[serde(untagged)] +pub enum FlexibleMatrix { + Unsigned(Box), + Signed(SignedMatrix), +} + +// We implement serializer on our own because #[serde(untagged)] produces error messages that are not helpful at all. +impl<'de> Deserialize<'de> for FlexibleMatrix { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Signed matrices are still an unused feature in March 2025. We can add support for its deserialization later. + // At the moment, deserializing the unsigned matrix directly (ignoring the possibility that it could be a + // signed matrix) is the fastest path to good error messages. + Ok(Self::Unsigned(Box::new(Matrix::deserialize(deserializer)?))) + } +} + +impl Default for FlexibleMatrix { + fn default() -> Self { + Self::Unsigned(Default::default()) + } +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct SignedMatrix { + pub matrix: String, + pub signature: String, +} + +impl SignedMatrix { + pub fn encode_value(matrix: &Matrix) -> anyhow::Result { + let bytes = rmp_serde::to_vec_named(matrix)?; + Ok(BASE64_ENGINE.encode(bytes)) + } + + pub fn decode_value(&self) -> anyhow::Result { + let bytes = BASE64_ENGINE.decode(self.matrix.as_bytes())?; + Ok(rmp_serde::from_slice(&bytes)?) + } +} + +/// This is redundant (already contained in [`Matrix`]) but used to transfer settings only, without all the content +/// (columns, rows, slots). +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixSettings { + pub clip_play_settings: MatrixClipPlaySettings, + pub clip_record_settings: MatrixClipRecordSettings, + pub common_tempo_range: TempoRange, + pub color_palette: ColorPalette, + pub content_quantization_settings: ContentQuantizationSettings, + pub activate_slot_on_trigger: bool, + pub transport_sync_mode: TransportSyncMode, + pub pre_roll_bars: u32, +} + +impl Matrix { + pub fn column_count(&self) -> usize { + let Some(columns) = &self.columns else { + return 0; + }; + columns.len() + } + + /// Calculates the effective number of rows, taking the rows vector *and* the slots in each + /// column into account. + pub fn necessary_row_count(&self) -> usize { + cmp::max( + self.necessary_row_count_of_columns(), + self.necessary_row_count_of_rows(), + ) + } + + fn necessary_row_count_of_columns(&self) -> usize { + let Some(columns) = self.columns.as_ref() else { + return 0; + }; + columns + .iter() + .map(|col| col.necessary_row_count()) + .max() + .unwrap_or(0) + } + + fn necessary_row_count_of_rows(&self) -> usize { + let Some(rows) = self.rows.as_ref() else { + return 0; + }; + rows.len() + } +} + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct TempoRange { + min: Bpm, + max: Bpm, +} + +impl TempoRange { + pub fn new(min: Bpm, max: Bpm) -> PlaytimeApiResult { + if min > max { + return Err("min must be <= max".into()); + } + Ok(Self { min, max }) + } + + pub const fn min(&self) -> Bpm { + self.min + } + + pub const fn max(&self) -> Bpm { + self.max + } +} + +impl Default for TempoRange { + fn default() -> Self { + Self { + min: Bpm::new_panic(60.0), + max: Bpm::new_panic(200.0), + } + } +} + +/// Matrix-global settings related to playing clips. +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MatrixClipPlaySettings { + #[serde(default)] + pub trigger_behavior: TriggerSlotBehavior, + #[serde(default)] + pub start_timing: ClipPlayStartTiming, + #[serde(default)] + pub stop_timing: ClipPlayStopTiming, + #[serde(default)] + pub audio_settings: MatrixClipPlayAudioSettings, + #[serde(default)] + pub velocity_sensitivity: f64, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MatrixClipPlayAudioSettings { + pub resample_mode: VirtualResampleMode, + pub time_stretch_mode: AudioTimeStretchMode, + pub cache_behavior: AudioCacheBehavior, +} + +/// Matrix-global settings related to recording clips. +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MatrixClipRecordSettings { + #[serde(default)] + pub start_timing: ClipRecordStartTiming, + #[serde(default)] + pub stop_timing: ClipRecordStopTiming, + #[serde(default)] + pub length_mode: RecordLengthMode, + /// If `true`, starting to record in stopped state when metronome off, does a tempo detection recording. + /// + /// Otherwise, it does a count-in recording without a metronome. + #[serde(default = "allow_tempo_detection_recording_default")] + pub allow_tempo_detection_recording: bool, + #[serde(default)] + pub custom_length: EvenQuantization, + #[serde(default)] + pub play_start_timing: ClipPlayStartTimingOverrideAfterRecording, + #[serde(default)] + pub play_stop_timing: ClipPlayStopTimingOverrideAfterRecording, + #[serde(default)] + pub time_base: ClipRecordTimeBase, + /// If `true`, starts playing the clip right after recording. + #[serde(default = "looped_default")] + pub looped: bool, + #[serde(default)] + pub midi_settings: MatrixClipRecordMidiSettings, + #[serde(default)] + pub audio_settings: MatrixClipRecordAudioSettings, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum RecordLengthMode { + /// Records open-ended until the user decides to stop. + #[display(fmt = "Open end")] + #[default] + OpenEnd = 0, + /// Records at a maximum as much material as defined by `custom_length`. + #[display(fmt = "Custom length")] + CustomLength = 1, +} + +impl MatrixClipRecordSettings { + pub fn downbeat_detection_enabled(&self, is_midi: bool) -> bool { + if is_midi { + self.midi_settings.detect_downbeat + } else { + self.audio_settings.detect_downbeat + } + } + + pub fn effective_play_start_timing( + &self, + initial_play_start_timing: ClipPlayStartTiming, + current_play_start_timing: ClipPlayStartTiming, + ) -> Option { + use ClipPlayStartTimingOverrideAfterRecording as T; + match self.play_start_timing { + T::Inherit => None, + T::Override(t) => Some(t.value), + T::DeriveFromRecordTiming => self + .start_timing + .derive_play_start_timing(initial_play_start_timing, current_play_start_timing), + } + } + + pub fn effective_play_stop_timing( + &self, + initial_play_start_timing: ClipPlayStartTiming, + current_play_start_timing: ClipPlayStartTiming, + ) -> Option { + use ClipPlayStopTimingOverrideAfterRecording as T; + match self.play_stop_timing { + T::Inherit => None, + T::Override(t) => Some(t.value), + T::DeriveFromRecordTiming => self.stop_timing.derive_play_stop_timing( + self.start_timing, + initial_play_start_timing, + current_play_start_timing, + ), + } + } + + pub fn effective_play_time_base( + &self, + initial_play_start_timing: ClipPlayStartTiming, + time_signature: TimeSignature, + downbeat: DurationInBeats, + ) -> ClipTimeBase { + use ClipRecordTimeBase as B; + let beat_based = match self.time_base { + B::DeriveFromRecordTiming => match self.start_timing { + ClipRecordStartTiming::LikeClipPlayStartTiming => match initial_play_start_timing { + ClipPlayStartTiming::Immediately => false, + ClipPlayStartTiming::Quantized(_) => true, + }, + ClipRecordStartTiming::Immediately => false, + ClipRecordStartTiming::Quantized(_) => true, + }, + B::Time => false, + B::Beat => true, + }; + if beat_based { + #[allow(deprecated)] + let beat_time_base = BeatTimeBase { + audio_tempo: None, + time_signature, + downbeat, + }; + ClipTimeBase::Beat(beat_time_base) + } else { + ClipTimeBase::Time + } + } +} + +impl Default for MatrixClipRecordSettings { + fn default() -> Self { + Self { + start_timing: Default::default(), + stop_timing: Default::default(), + length_mode: Default::default(), + custom_length: Default::default(), + play_start_timing: Default::default(), + play_stop_timing: Default::default(), + time_base: Default::default(), + looped: looped_default(), + midi_settings: Default::default(), + audio_settings: Default::default(), + allow_tempo_detection_recording: allow_tempo_detection_recording_default(), + } + } +} + +fn looped_default() -> bool { + true +} + +fn allow_tempo_detection_recording_default() -> bool { + true +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MatrixClipRecordMidiSettings { + #[serde(default)] + pub record_mode: MidiClipRecordMode, + /// If `true`, attempts to detect the actual start of the recorded MIDI material and derives + /// the downbeat position from that. + #[serde(default)] + pub detect_downbeat: bool, + /// Makes the global record button work for MIDI by allowing global input detection. + // TODO-high-playtime-after-release + #[serde(default)] + pub detect_input: bool, + /// Applies quantization while recording using the current quantization settings. + #[serde(default)] + pub auto_quantize: bool, + /// These are the MIDI settings each recorded and otherwise created clip will get. + #[serde(default)] + pub clip_settings: ClipMidiSettings, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MatrixClipRecordAudioSettings { + /// If `true`, attempts to detect the actual start of the recorded audio material and derives + /// the downbeat position from that. + // TODO-high-playtime-after-release + #[serde(default)] + pub detect_downbeat: bool, + /// Makes the global record button work for audio by allowing global input detection. + // TODO-high-playtime-after-release + #[serde(default)] + pub detect_input: bool, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipRecordTimeBase { + /// Derives the time base of the resulting clip from the clip start timing. + DeriveFromRecordTiming, + /// Sets the time base of the recorded clip to [`ClipTimeBase::Time`]. + Time, + /// Sets the time base of the recorded clip to [`ClipTimeBase::Beat`]. + Beat, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum TriggerSlotBehavior { + /// Press once = play, press again = stop. + #[default] + TogglePlayStop, + /// Press once = play, release = stop. + MomentaryPlayStop, + /// Press once = play, press again = retrigger. + Retrigger, +} + +impl Default for ClipRecordTimeBase { + fn default() -> Self { + Self::DeriveFromRecordTiming + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipRecordStartTiming { + /// Uses the inherited clip play start timing (from column or matrix). + LikeClipPlayStartTiming, + /// Starts recording immediately. + Immediately, + /// Starts recording according to the given quantization. + Quantized(EvenQuantization), +} + +impl Default for ClipRecordStartTiming { + fn default() -> Self { + Self::LikeClipPlayStartTiming + } +} + +impl ClipRecordStartTiming { + pub fn derive_play_start_timing( + &self, + initial_play_start_timing: ClipPlayStartTiming, + current_play_start_timing: ClipPlayStartTiming, + ) -> Option { + use ClipRecordStartTiming as T; + match self { + T::LikeClipPlayStartTiming => { + if initial_play_start_timing == current_play_start_timing { + None + } else { + Some(initial_play_start_timing) + } + } + T::Immediately => Some(ClipPlayStartTiming::Immediately), + T::Quantized(q) => Some(ClipPlayStartTiming::Quantized(*q)), + } + } + + pub fn suggests_beat_based_material(&self, play_start_timing: ClipPlayStartTiming) -> bool { + use ClipRecordStartTiming as T; + match self { + T::LikeClipPlayStartTiming => play_start_timing.suggests_beat_based_material(), + T::Immediately => false, + T::Quantized(_) => true, + } + } +} + +impl Default for ClipRecordStopTiming { + fn default() -> Self { + Self::LikeClipRecordStartTiming + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipRecordStopTiming { + /// Uses the record start timing. + LikeClipRecordStartTiming, + /// Stops recording immediately. + Immediately, + /// Stops recording according to the given quantization. + Quantized(EvenQuantization), +} + +impl ClipRecordStopTiming { + pub fn derive_play_stop_timing( + &self, + record_start_timing: ClipRecordStartTiming, + initial_play_start_timing: ClipPlayStartTiming, + current_play_start_timing: ClipPlayStartTiming, + ) -> Option { + use ClipRecordStopTiming as T; + match self { + T::LikeClipRecordStartTiming => record_start_timing + .derive_play_start_timing(initial_play_start_timing, current_play_start_timing) + .map(|play_start_timing| match play_start_timing { + ClipPlayStartTiming::Immediately => ClipPlayStopTiming::Immediately, + ClipPlayStartTiming::Quantized(q) => ClipPlayStopTiming::Quantized(q), + }), + T::Immediately => Some(ClipPlayStopTiming::Immediately), + T::Quantized(q) => Some(ClipPlayStopTiming::Quantized(*q)), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum MidiClipRecordMode { + /// Creates an empty clip and records MIDI material in it. + Normal, + /// Records more material onto an existing clip, leaving existing material in place. + Overdub, + /// Records more material onto an existing clip, overwriting existing material. + // TODO-high-playtime-after-release + Replace, +} + +impl Default for MidiClipRecordMode { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipPlayStartTiming { + /// Starts playing immediately. + Immediately, + /// Starts playing according to the given quantization. + Quantized(EvenQuantization), +} + +impl Default for ClipPlayStartTiming { + fn default() -> Self { + Self::Quantized(Default::default()) + } +} + +impl ClipPlayStartTiming { + pub fn suggests_beat_based_material(&self) -> bool { + use ClipPlayStartTiming as T; + match self { + T::Immediately => false, + T::Quantized(_) => true, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipPlayStopTiming { + /// Uses the play start timing. + LikeClipStartTiming, + /// Stops playing immediately. + Immediately, + /// Stops playing according to the given quantization. + Quantized(EvenQuantization), + /// Keeps playing until the end of the clip. + UntilEndOfClip, +} + +impl Default for ClipPlayStopTiming { + fn default() -> Self { + Self::LikeClipStartTiming + } +} + +impl ClipPlayStopTiming { + pub fn resolve(&self, start_timing: ClipPlayStartTiming) -> ResolvedClipPlayStopTiming { + use ClipPlayStopTiming as T; + match self { + T::LikeClipStartTiming => match start_timing { + ClipPlayStartTiming::Immediately => ResolvedClipPlayStopTiming::Immediately, + ClipPlayStartTiming::Quantized(q) => ResolvedClipPlayStopTiming::Quantized(q), + }, + T::Immediately => ResolvedClipPlayStopTiming::Immediately, + T::Quantized(q) => ResolvedClipPlayStopTiming::Quantized(*q), + T::UntilEndOfClip => ResolvedClipPlayStopTiming::UntilEndOfClip, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ResolvedClipPlayStopTiming { + Immediately, + /// Stops playing according to the given quantization. + Quantized(EvenQuantization), + /// Keeps playing until the end of the clip. + UntilEndOfClip, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipPlayStartTimingOverrideAfterRecording { + /// Doesn't apply any override. + #[default] + Inherit, + /// Overrides the setting with the given value. + Override(ClipPlayStartTimingOverride), + /// Derives the setting from the record timing. + /// + /// If the record timing is set to the global clip timing, that means it will not apply any + /// override. If it's set to something specific, it will apply the appropriate override. + DeriveFromRecordTiming, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipPlayStopTimingOverrideAfterRecording { + /// Doesn't apply any override. + #[default] + Inherit, + /// Overrides the setting with the given value. + Override(ClipPlayStopTimingOverride), + /// Derives the setting from the record timing. + /// + /// If the record timing is set to the global clip timing, that means it will not apply any + /// override. If it's set to something specific, it will apply the appropriate override. + DeriveFromRecordTiming, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct ClipPlayStartTimingOverride { + pub value: ClipPlayStartTiming, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct ClipPlayStopTimingOverride { + pub value: ClipPlayStopTiming, +} + +/// An even quantization. +/// +/// "Even" in the sense that it's not swing or dotted. +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(try_from = "RawEvenQuantization")] +pub struct EvenQuantization { + numerator: u32, + denominator: u32, +} + +impl Default for EvenQuantization { + fn default() -> Self { + Self { + numerator: 1, + denominator: 1, + } + } +} + +#[derive(Deserialize)] +struct RawEvenQuantization { + numerator: u32, + denominator: u32, +} + +impl TryFrom for EvenQuantization { + type Error = PlaytimeApiError; + + fn try_from(value: RawEvenQuantization) -> PlaytimeApiResult { + EvenQuantization::new(value.numerator, value.denominator) + } +} + +impl EvenQuantization { + pub const ONE_BAR: Self = EvenQuantization { + numerator: 1, + denominator: 1, + }; + + pub fn new(numerator: u32, denominator: u32) -> PlaytimeApiResult { + if numerator == 0 { + return Err("numerator must be > 0".into()); + } + if denominator == 0 { + return Err("denominator must be > 0".into()); + } + let q = Self { + numerator, + denominator, + }; + Ok(q) + } + + /// The number of bars. + /// + /// Must not be zero. + pub fn numerator(&self) -> u32 { + self.numerator + } + + /// Defines the fraction of a bar. + /// + /// Must not be zero. + /// + /// If the numerator is > 1, this must be 1. + pub fn denominator(&self) -> u32 { + self.denominator + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, derive_more::Display)] +pub struct ColumnId(String); + +impl ColumnId { + pub fn random() -> Self { + Default::default() + } +} + +impl Default for ColumnId { + fn default() -> Self { + Self(nanoid::nanoid!()) + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, derive_more::Display)] +pub struct RowId(String); + +impl RowId { + pub fn random() -> Self { + Default::default() + } +} + +impl Default for RowId { + fn default() -> Self { + Self(nanoid::nanoid!()) + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, derive_more::Display)] +pub struct SlotId(String); + +impl SlotId { + pub fn random() -> Self { + Default::default() + } +} + +impl Default for SlotId { + fn default() -> Self { + Self(nanoid::nanoid!()) + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, derive_more::Display)] +pub struct ClipId(String); + +impl ClipId { + pub fn random() -> Self { + Self(nanoid::nanoid!()) + } + + pub fn new(raw: String) -> Self { + Self(raw) + } +} + +impl Default for ClipId { + fn default() -> Self { + Self::random() + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, derive_more::Display)] +pub struct MatrixSequenceId(String); + +impl MatrixSequenceId { + pub fn new(raw: String) -> Self { + Self(raw) + } + + pub fn random() -> Self { + Default::default() + } +} + +impl Default for MatrixSequenceId { + fn default() -> Self { + Self(nanoid::nanoid!()) + } +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Column { + #[serde(default)] + pub id: ColumnId, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default)] + pub clip_play_settings: ColumnClipPlaySettings, + #[serde(default)] + pub clip_record_settings: ColumnClipRecordSettings, + /// Slots in this column. + /// + /// Only filled slots need to be mentioned here. + #[serde(skip_serializing_if = "Option::is_none")] + pub slots: Option>, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct ColumnSettings { + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + pub clip_play_settings: ColumnClipPlaySettings, + pub clip_record_settings: ColumnClipRecordSettings, +} + +impl Column { + /// Calculates the necessary number of rows in this column in order to keep all the defined + /// slots. + pub fn necessary_row_count(&self) -> usize { + let Some(slots) = self.slots.as_ref() else { + return 0; + }; + slots + .iter() + .map(|s| s.row) + .max() + .map(|max| max + 1) + .unwrap_or(0) + } +} + +pub fn default_follows_scene() -> bool { + true +} + +pub fn default_exclusive() -> bool { + true +} + +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct ColumnClipPlaySettings { + #[serde(default = "default_follows_scene")] + pub follows_scene: bool, + #[serde(default = "default_exclusive")] + pub exclusive: bool, + /// REAPER track used for playing back clips in this column. + /// + /// Usually, each column should have a play track. But events might occur that leave a column + /// in a "track-less" state, e.g. the deletion of a track. This column will be unusable until + /// the user sets a play track again. We still want to be able to save the matrix in such a + /// state, otherwise it could be really annoying. So we allow `None`. + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, + /// Start timing override. + /// + /// `None` means it uses the matrix-global start timing. + #[serde(skip_serializing_if = "Option::is_none")] + pub start_timing: Option, + /// Stop timing override. + /// + /// `None` means it uses the matrix-global stop timing. + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_timing: Option, + /// Trigger behavior override. + /// + /// `None` means it uses the matrix-global trigger behavior. + pub trigger_behavior: Option, + /// Velocity sensitivity override. + /// + /// `None` means it uses the matrix-global sensitivity. + #[serde(skip_serializing_if = "Option::is_none")] + pub velocity_sensitivity: Option, + #[serde(default)] + pub audio_settings: ColumnClipPlayAudioSettings, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct ColumnClipRecordSettings { + #[serde(default)] + pub origin: RecordOrigin, + /// By default, Playtime records from the play track but this settings allows to override that. + #[serde(skip_serializing_if = "Option::is_none")] + pub track: Option, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct ColumnClipPlayAudioSettings { + /// Overrides the matrix-global resample mode for clips in this column. + #[serde(skip_serializing_if = "Option::is_none")] + pub resample_mode: Option, + /// Overrides the matrix-global audio time stretch mode for clips in this column. + #[serde(skip_serializing_if = "Option::is_none")] + pub time_stretch_mode: Option, + /// Overrides the matrix-global audio cache behavior for clips in this column. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_behavior: Option, +} + +/// A row represents a complete row in a matrix. +/// +/// A scene is a very related concept and sometimes used interchangeably with row because there's +/// a one-to-one relationship between a row and a scene. +/// +/// The difference between row and scene is of conceptual nature: A scene represents a part of a +/// song that's played exclusively whereas a row is just a row in the Playtime matrix. This distinction +/// results in some practical differences: +/// +/// - A column can be configured to not follow scenes. The clips in that column are of +/// course still structured in rows, but they are not part of the scenes anymore. +/// - In practice, this means that whenever you launch the scene, the clips in that independent +/// column are not launched. Or when you clear the scene, the slots in that column are not +/// cleared. +/// - Whenever you read "Scene", it will only affect the columns that are configured to follow +/// scenes. Whenever you read "Row", it will affect the complete matrix row, no matter the +/// column type. +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Row { + #[serde(default)] + pub id: RowId, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// An optional tempo associated with this row. + #[serde(skip_serializing_if = "Option::is_none")] + pub tempo: Option, + /// An optional time signature associated with this row. + // TODO-high-playtime-after-release + #[serde(skip_serializing_if = "Option::is_none")] + pub time_signature: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum AudioTimeStretchMode { + /// Doesn't just stretch/squeeze the material but also changes the pitch. + /// + /// Comparatively fast. + VariSpeed, + /// Applies a real time-stretch algorithm to the material which keeps the pitch. + /// + /// Comparatively slow. + KeepingPitch(TimeStretchMode), +} + +impl AudioTimeStretchMode { + pub fn is_vari_speed(&self) -> bool { + matches!(self, Self::VariSpeed) + } +} + +impl Default for AudioTimeStretchMode { + fn default() -> Self { + Self::KeepingPitch(Default::default()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum VirtualResampleMode { + /// Uses the resample mode set as default for this REAPER project. + ProjectDefault, + /// Uses a specific resample mode. + ReaperMode(ReaperResampleMode), +} + +impl Default for VirtualResampleMode { + fn default() -> Self { + Self::ProjectDefault + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct ReaperResampleMode { + pub mode: u32, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct TimeStretchMode { + pub mode: VirtualTimeStretchMode, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum VirtualTimeStretchMode { + /// Uses the pitch shift mode set as default for this REAPER project. + ProjectDefault, + /// Uses a specific REAPER pitch shift mode. + ReaperMode(ReaperPitchShiftMode), +} + +impl Default for VirtualTimeStretchMode { + fn default() -> Self { + Self::ProjectDefault + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct ReaperPitchShiftMode { + pub mode: u32, + pub sub_mode: u32, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum RecordOrigin { + /// Records using the hardware input set for the track (MIDI or stereo). + TrackInput, + /// Captures audio from the output of the track. + TrackAudioOutput, + /// Records audio flowing into the FX input. + FxAudioInput(ChannelRange), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub enum SourceOrigin { + /// Normal source. + Normal, + /// Frozen source. + Frozen, +} + +impl Default for SourceOrigin { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct ChannelRange { + pub first_channel_index: u32, + pub channel_count: u32, +} + +impl Default for RecordOrigin { + fn default() -> Self { + Self::TrackInput + } +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Slot { + #[serde(default)] + pub id: SlotId, + /// Slot index within the column (= row), starting at zero. + pub row: usize, + /// Clip which currently lives in this slot. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "clip")] + pub clip_old: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub clips: Option>, + #[serde(default)] + pub ignited: bool, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Clip { + #[serde(default)] + pub id: ClipId, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Source of the audio/MIDI material of this clip. + #[serde(default)] + pub source: Source, + /// Source with effects "rendered in", usually audio. + #[serde(skip_serializing_if = "Option::is_none")] + pub frozen_source: Option, + /// Which of the sources is the active one. + #[serde(default)] + pub active_source: SourceOrigin, + /// Time base of the material provided by that source. + #[serde(default)] + pub time_base: ClipTimeBase, + /// Start timing override. + /// + /// `None` means it uses the column start timing. + #[serde(skip_serializing_if = "Option::is_none")] + pub start_timing: Option, + /// Stop timing override. + /// + /// `None` means it uses the column stop timing. + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_timing: Option, + /// Velocity sensitivity override. + /// + /// `None` means it uses the column sensitivity. + #[serde(skip_serializing_if = "Option::is_none")] + pub velocity_sensitivity: Option, + /// Whether the clip should be played repeatedly or as a single shot. + #[serde(default = "looped_default")] + pub looped: bool, + /// Relative volume adjustment of clip. + #[serde(default)] + pub volume: Db, + /// Color of the clip. + #[serde(default)] + pub color: ClipColor, + /// A more variable kind of section within the main section. + /// + /// Intended for playing with section bounds as a part of the performance *without* destroying + /// the original section. + #[serde(default)] + pub dynamic_section: Section, + /// Defines which portion of the original source should be played. + /// + /// This section is especially important for the way that Playtime records audio clips: It + /// records the count-in phase as well and more samples than actually necessary at the end. + /// It then sets this section to the portion that actually matters. Once set, this section + /// rarely changes. + #[serde(alias = "section")] + #[serde(default)] + pub fixed_section: Section, + #[serde(default)] + pub pitch_shift: Semitones, + #[serde(default)] + pub audio_settings: ClipAudioSettings, + #[serde(default)] + pub midi_settings: ClipMidiSettings, + // /// Defines the total amount of time this clip should consume and where within that range the + // /// portion of the original source is located. + // /// + // /// This allows one to insert silence at the beginning and the end + // /// as well as changing the source section without affecting beat + // /// alignment. + // /// + // /// `None` means the canvas will have the same size as the source + // /// section. + // canvas: Option, +} + +impl Default for Clip { + fn default() -> Self { + Self { + id: Default::default(), + name: None, + source: Default::default(), + frozen_source: None, + active_source: Default::default(), + time_base: Default::default(), + start_timing: None, + stop_timing: None, + velocity_sensitivity: None, + looped: looped_default(), + volume: Default::default(), + color: Default::default(), + dynamic_section: Default::default(), + fixed_section: Default::default(), + pitch_shift: Default::default(), + audio_settings: Default::default(), + midi_settings: Default::default(), + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct ClipAudioSettings { + /// Defines whether to apply automatic fades in order to fix potentially non-optimized source + /// material. + /// + /// ## `false` + /// + /// Doesn't apply automatic fades for fixing non-optimized source material. + /// + /// This only prevents fix fades at source level, that is fades fixing the source file itself or the source cut + /// (= static/fixed section). Fades that are not about fixing the source will still be applied if necessary in order + /// to ensure a smooth playback, such as: + /// + /// - Dynamic section fades (start fade-in, end fade-out) + /// - Interaction fades (resume-after-pause fade-in, immediate stop fade-out) + /// + /// Fades don't overlap. Here's the order of priority (for fade-in and fade-out separately): + /// + /// - Interaction fades + /// - Section fades + /// - Source-fix fades + /// + /// ## `true` + /// + /// Applies automatic fades to fix non-optimized source material, if necessary. + #[serde(default = "apply_source_fades_default")] + pub apply_source_fades: bool, + /// Defines how to adjust audio material. + /// + /// This is usually used with the beat time base to match the tempo of the clip to the global + /// tempo. + /// + /// `None` means it uses the column time stretch mode. + #[serde(skip_serializing_if = "Option::is_none")] + pub time_stretch_mode: Option, + /// Overrides the column resample mode for clips in this column. + #[serde(skip_serializing_if = "Option::is_none")] + pub resample_mode: Option, + /// Whether to cache audio in memory. + /// + /// `None` means it uses the column cache behavior. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_behavior: Option, + /// The clip's native tempo. + /// + /// This information is used by the clip engine to determine how much to speed up or + /// slow down the material depending on the current project tempo. + /// + /// The tempo is not kept in the [`ClipTimeBase`] variant `Beat` anymore because then it would + /// be too easy to get lost when temporarily switching to variant `Time` for fun. It could be + /// hard figuring out the original tempo later after loading the project again. One could + /// argue that the same is true for time signature, but that's quite easy to figure out again, + /// plus, it's not an audio-only setting. + /// + /// The tempo is also not kept as part of [`Source`] because it's just additional meta + /// information that's not strictly necessary to load the source. + #[serde(skip_serializing_if = "Option::is_none")] + pub original_tempo: Option, +} + +impl Default for ClipAudioSettings { + fn default() -> Self { + Self { + cache_behavior: Default::default(), + apply_source_fades: apply_source_fades_default(), + time_stretch_mode: None, + resample_mode: None, + original_tempo: None, + } + } +} + +fn apply_source_fades_default() -> bool { + true +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct ClipMidiSettings { + /// If set, all MIDI channel MIDI events will be remapped to this channel. + #[serde(skip_serializing_if = "Option::is_none")] + pub destination_channel: Option, + /// Reset settings. + #[serde(default)] + pub reset_settings: ClipMidiResetSettings, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct ClipMidiResetSettings { + /// For fine-tuning the start and the end of one particular "play" session of a clip. + /// + /// - The left interaction reset messages are sent when clip playback starts: + /// - For immediate starts: Immediately at trigger time (done in looper) + /// - For quantized starts that are triggered before the quantized position: + /// At the time the quantized position is reached (done in looper) + /// - For quantized starts that are triggered a tiny bit after the quantized position ("last minute triggering"): + /// Immediately at trigger time (done in interaction handler) + /// - The right interaction reset messages are sent when clip playback ends: + /// - For immediate stops/retriggers: Immediately at trigger time (done in interaction handler) + /// - For quantized stops/retriggers: At the time the quantized position is reached (done in interaction handler) + /// - For naturally ending one-shots or stop-triggered loops with "Until end of clip": + /// At the time when the clip ends naturally (done in looper) + pub interaction_reset_settings: MidiResetMessageRange, + /// For fine-tuning the section (done in section handler). + /// + /// - The left section reset messages are sent when the effective section start position > 0 and playback hits the + /// section start position. + /// - The right section reset messages are sent when a section length is defined and playback hits the section end + /// position. + /// - If the clip is looped, the messages will be sent at each loop cycle. + pub section_reset_settings: MidiResetMessageRange, + /// For fixing the source itself (done in start-end handler). + /// + /// - The left source reset messages are sent when the effective section start position == 0 and playback hits + /// the start of the source. + /// - The right source reset messages are sent when no section length is defined and playback hits the end of the + /// source. + /// - If the clip is looped, the messages will be sent at *each* loop cycle. + /// + /// This exists separately from the section reset settings because one might prefer using the original MIDI + /// sequence with less reset logic when playing without section. After all, the source itself might already + /// be perfect as it is. But as soon as we introduce a section, this is not guaranteed anymore and introducing + /// reset messages almost always makes sense. + pub source_reset_settings: MidiResetMessageRange, +} + +impl Default for ClipMidiResetSettings { + fn default() -> Self { + Self::RIGHT_LIGHT + } +} + +impl ClipMidiResetSettings { + pub const NONE: Self = Self { + interaction_reset_settings: MidiResetMessageRange::NONE, + section_reset_settings: MidiResetMessageRange::NONE, + source_reset_settings: MidiResetMessageRange::NONE, + }; + + pub const RIGHT_LIGHT: Self = Self { + interaction_reset_settings: MidiResetMessageRange::RIGHT_LIGHT, + section_reset_settings: MidiResetMessageRange::RIGHT_LIGHT, + source_reset_settings: MidiResetMessageRange::RIGHT_LIGHT, + }; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MidiResetMessageRange { + /// Which MIDI reset messages to apply at the beginning. + pub left: MidiResetMessages, + /// Which MIDI reset messages to apply at the end. + pub right: MidiResetMessages, +} + +impl MidiResetMessageRange { + pub const NONE: Self = Self { + left: MidiResetMessages::NONE, + right: MidiResetMessages::NONE, + }; + + pub const RIGHT_LIGHT: Self = Self { + left: MidiResetMessages::NONE, + right: MidiResetMessages::LIGHT, + }; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct MidiResetMessages { + /// Sends note-off events for all notes that are currently playing in this clip. + #[serde(default)] + pub on_notes_off: bool, + /// Sends MIDI CC 123 (all-notes-off). + pub all_notes_off: bool, + /// Sends MIDI CC 120 (all-sound-off). + pub all_sound_off: bool, + /// Sends MIDI CC121 (reset-all-controllers). + pub reset_all_controllers: bool, + /// Sends CC64 value 0 events (damper-pedal-off) for all damper pedals that are currently pressed in this clip. + #[serde(alias = "damper_pedal_off")] + pub on_damper_pedal_off: bool, +} + +impl MidiResetMessages { + pub const NONE: Self = Self { + on_notes_off: false, + all_notes_off: false, + all_sound_off: false, + reset_all_controllers: false, + on_damper_pedal_off: false, + }; + + pub const LIGHT: Self = Self { + on_notes_off: true, + all_notes_off: false, + all_sound_off: false, + reset_all_controllers: false, + on_damper_pedal_off: true, + }; + + pub fn at_least_one_enabled(&self) -> bool { + self.on_notes_off + || self.all_notes_off + || self.all_sound_off + || self.reset_all_controllers + || self.on_damper_pedal_off + } +} + +#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct Section { + /// Position in the source from which to start. + /// + /// If this is greater than zero, a fade-in will be used to avoid clicks. + #[serde(default)] + pub start_pos: DurationInSeconds, + /// Length of the material to be played, starting from `start_pos`. + /// + /// - `None` means until original source end. + /// - May exceed the end of the source. + /// - If this makes the section end be located before the original source end, a fade-out will + /// be used to avoid clicks. + #[serde(skip_serializing_if = "Option::is_none")] + pub length: Option, +} + +impl Section { + pub fn combine_with_inner(&self, inner: Section) -> Section { + Section { + start_pos: self.start_pos + inner.start_pos, + length: match (self.length, inner.length) { + (Some(l), None) => Some(l.saturating_sub(inner.start_pos)), + (_, l) => l, + }, + } + } + + pub fn effective_length(&self, source_length: DurationInSeconds) -> DurationInSeconds { + if let Some(length) = self.length { + length + } else { + source_length.saturating_sub(self.start_pos) + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum AudioCacheBehavior { + /// Loads directly from the disk. + /// + /// Might still pre-buffer some blocks but definitely won't put the complete audio data into + /// memory. + DirectFromDisk, + /// Loads the complete audio data into memory. + CacheInMemory, +} + +impl Default for AudioCacheBehavior { + fn default() -> Self { + Self::DirectFromDisk + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipColor { + /// Inherits the color of the column's play track. + #[default] + PlayTrackColor, + /// Assigns a very specific custom color. + CustomColor(CustomClipColor), + /// Uses a certain color from a palette. + PaletteColor(PaletteClipColor), +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct CustomClipColor { + pub value: RgbColor, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct PaletteClipColor { + pub index: u32, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum Source { + /// Takes content from a media file on the file system (audio). + File(FileSource), + /// Embedded MIDI data. + MidiChunk(MidiChunkSource), +} + +impl Default for Source { + fn default() -> Self { + Source::MidiChunk(Default::default()) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct FileSource { + /// Path to the media file. + /// + /// - If it's a relative path, it will be interpreted as relative to the REAPER project directory. + /// - This should use slash as path segment separator, even on Windows. + pub path: Utf8PathBuf, +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct MidiChunkSource { + /// MIDI data in the same format that REAPER uses for in-project MIDI. + pub chunk: String, +} + +/// Decides if the clip will be adjusted to the current tempo. +#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ClipTimeBase { + /// Material which doesn't need to be adjusted to the current tempo. + #[default] + Time, + /// Material which needs to be adjusted to the current tempo. + Beat(BeatTimeBase), +} + +impl ClipTimeBase { + pub fn is_time_based(&self) -> bool { + matches!(self, Self::Time) + } + + pub fn is_beat_based(&self) -> bool { + matches!(self, Self::Beat(_)) + } +} + +#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct BeatTimeBase { + #[deprecated(note = "moved to ClipAudioSettings#original_tempo")] + #[serde(skip_serializing)] + pub audio_tempo: Option, + /// The time signature of this clip. + /// + /// If provided, this information is used for certain aspects of the user interface. + pub time_signature: TimeSignature, + /// Defines which position (in beats) is the downbeat. + pub downbeat: DurationInBeats, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct ColorPalette { + pub entries: Vec, +} + +const TAILWIND_SLATE_500: RgbColor = RgbColor(0x64, 0x74, 0x8B); +const TAILWIND_GRAY_500: RgbColor = RgbColor(0x6b, 0x72, 0x80); +const TAILWIND_ZINC_500: RgbColor = RgbColor(0x71, 0x71, 0x7A); +const TAILWIND_NEUTRAL_500: RgbColor = RgbColor(0x73, 0x73, 0x73); +const TAILWIND_STONE_500: RgbColor = RgbColor(0x78, 0x71, 0x6c); +const TAILWIND_RED_500: RgbColor = RgbColor(0xef, 0x44, 0x44); +const TAILWIND_ORANGE_500: RgbColor = RgbColor(0xf9, 0x73, 0x16); +const TAILWIND_AMBER_500: RgbColor = RgbColor(0xf5, 0x9e, 0x0b); +const TAILWIND_YELLOW_500: RgbColor = RgbColor(0xea, 0xb3, 0x08); +const TAILWIND_LIME_500: RgbColor = RgbColor(0x84, 0xcc, 0x16); +const TAILWIND_GREEN_500: RgbColor = RgbColor(0x22, 0xc5, 0x5e); +const TAILWIND_EMERALD_500: RgbColor = RgbColor(0x10, 0xb9, 0x81); +const TAILWIND_TEAL_500: RgbColor = RgbColor(0x14, 0xb8, 0xa6); +const TAILWIND_CYAN_500: RgbColor = RgbColor(0x06, 0xb6, 0xd4); +const TAILWIND_SKY_500: RgbColor = RgbColor(0x0e, 0xa5, 0xe9); +const TAILWIND_BLUE_500: RgbColor = RgbColor(0x3b, 0x82, 0xf6); +const TAILWIND_INDIGO_500: RgbColor = RgbColor(0x63, 0x66, 0xf1); +const TAILWIND_VIOLET_500: RgbColor = RgbColor(0x8b, 0x5c, 0xf6); +const TAILWIND_PURPLE_500: RgbColor = RgbColor(0xa8, 0x55, 0xf7); +const TAILWIND_FUCHSIA_500: RgbColor = RgbColor(0xd9, 0x46, 0xef); +const TAILWIND_PINK_500: RgbColor = RgbColor(0xec, 0x48, 0x99); +const TAILWIND_ROSE_500: RgbColor = RgbColor(0xf4, 0x3f, 0x5e); + +impl Default for ColorPalette { + fn default() -> Self { + Self { + entries: [ + TAILWIND_SLATE_500, + TAILWIND_GRAY_500, + TAILWIND_ZINC_500, + TAILWIND_NEUTRAL_500, + TAILWIND_STONE_500, + TAILWIND_RED_500, + TAILWIND_ORANGE_500, + TAILWIND_AMBER_500, + TAILWIND_YELLOW_500, + TAILWIND_LIME_500, + TAILWIND_GREEN_500, + TAILWIND_EMERALD_500, + TAILWIND_TEAL_500, + TAILWIND_CYAN_500, + TAILWIND_SKY_500, + TAILWIND_BLUE_500, + TAILWIND_INDIGO_500, + TAILWIND_VIOLET_500, + TAILWIND_PURPLE_500, + TAILWIND_FUCHSIA_500, + TAILWIND_PINK_500, + TAILWIND_ROSE_500, + ] + .into_iter() + .map(|color| ColorPaletteEntry { color }) + .collect(), + } + } +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct ColorPaletteEntry { + pub color: RgbColor, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct TimeSignature { + pub numerator: u32, + pub denominator: u32, +} + +impl Default for TimeSignature { + fn default() -> Self { + Self { + numerator: 4, + denominator: 4, + } + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct TrackId(String); + +impl TrackId { + pub fn new(value: String) -> Self { + Self(value) + } + + pub fn get(&self) -> &str { + &self.0 + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct RgbColor(pub u8, pub u8, pub u8); + +type PlaytimeApiResult = Result; + +/// Important: Since some of these types are going to be used in real-time contexts, we don't +/// want heap-allocated types in here! +#[derive(thiserror::Error, Debug)] +#[error("{msg}")] +pub struct PlaytimeApiError { + msg: &'static str, +} + +impl From<&'static str> for PlaytimeApiError { + fn from(msg: &'static str) -> Self { + Self { msg } + } +} + +impl From for &'static str { + fn from(value: PlaytimeApiError) -> Self { + value.msg + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct ColumnAddress { + pub index: usize, +} + +impl Display for ColumnAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Column {}", self.index + 1) + } +} + +impl ColumnAddress { + pub fn new(index: usize) -> Self { + Self { index } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +pub struct RowAddress { + pub index: usize, +} + +impl Display for RowAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Row {}", self.index + 1) + } +} + +impl RowAddress { + pub fn new(index: usize) -> Self { + Self { index } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default, Serialize, Deserialize)] +pub struct SlotAddress { + pub column_index: usize, + pub row_index: usize, +} + +impl SlotAddress { + pub fn new(column: usize, row: usize) -> Self { + Self { + column_index: column, + row_index: row, + } + } + + pub fn column(&self) -> usize { + self.column_index + } + + pub fn row(&self) -> usize { + self.row_index + } + + pub fn to_cell_address(&self) -> CellAddress { + CellAddress::slot(self.column_index, self.row_index) + } +} + +impl Display for SlotAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Slot {}/{}", self.column_index + 1, self.row_index + 1) + } +} diff --git a/plugin-reaper-realearn/playtime-api/src/persistence/serialization.rs b/plugin-reaper-realearn/playtime-api/src/persistence/serialization.rs new file mode 100644 index 0000000..170d778 --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/src/persistence/serialization.rs @@ -0,0 +1,181 @@ +use crate::persistence::{ + MatrixSequenceColumnMessage, MatrixSequenceEvent, MatrixSequenceMessage, + MatrixSequenceRowMessage, MatrixSequenceSlotMessage, MatrixSequenceStartSlotMessage, +}; +use serde::de::{Error, SeqAccess, Visitor}; +use serde::ser::SerializeTuple; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::Formatter; + +/// A very compact representation of an event. +/// +/// The compactness is achieved using tuple serialization. +/// +/// Pro: +/// +/// - If used with a CSV serializer, the outcome can be made look as "noise-less" as +/// REAPER's MIDI sequence format (newline to separate events, space delimiter, unquoted strings). +/// - If used with JSON/Lua serializer, the outcome is valid JSON/Lua while still being compact. +/// No need for string embedding (which looks especially bad in JSON due to newline escaping). +/// - If used with a binary serializer (e.g. bincode or msgpack), one can achieve a *really* compact +/// serialization that also tops REAPER's MIDI sequence format. That will come in handy with +/// large undo histories or storage within RPP (in RPPs, we are base64-encoded, so the +/// human-readable-text advantage is not present anyway). +/// - TODO-high-ms3 Especially the last point could be desirable for MIDI sequences as well. +/// Use serde for them, too! +/// +/// Contra: +/// +/// - Not self-describing. However: If embedded in some `Serialize` wrapper that evaluates +/// `is_human_readable`, one could switch between this non-descriptive serialization style and a +/// (derived) descriptive serialization style. So we can have both if we want. +impl Serialize for MatrixSequenceEvent { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use MatrixSequenceMessage as M; + match self.message { + M::PanicMatrix => { + let mut seq = serializer.serialize_tuple(2)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&0u8)?; + seq.end() + } + M::StopMatrix => { + let mut seq = serializer.serialize_tuple(2)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&1u8)?; + seq.end() + } + M::PanicColumn(m) => { + let mut seq = serializer.serialize_tuple(3)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&2u8)?; + seq.serialize_element(&m.index)?; + seq.end() + } + M::StopColumn(m) => { + let mut seq = serializer.serialize_tuple(3)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&3u8)?; + seq.serialize_element(&m.index)?; + seq.end() + } + M::StartScene(m) => { + let mut seq = serializer.serialize_tuple(3)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&4u8)?; + seq.serialize_element(&m.index)?; + seq.end() + } + M::PanicSlot(m) => { + let mut seq = serializer.serialize_tuple(4)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&5u8)?; + seq.serialize_element(&m.column_index)?; + seq.serialize_element(&m.row_index)?; + seq.end() + } + M::StartSlot(m) => { + let mut seq = serializer.serialize_tuple(5)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&6u8)?; + seq.serialize_element(&m.column_index)?; + seq.serialize_element(&m.row_index)?; + seq.serialize_element(&m.velocity)?; + seq.end() + } + + M::StopSlot(m) => { + let mut seq = serializer.serialize_tuple(4)?; + seq.serialize_element(&self.pulse_diff)?; + seq.serialize_element(&7u8)?; + seq.serialize_element(&m.column_index)?; + seq.serialize_element(&m.row_index)?; + seq.end() + } + } + } +} + +struct MatrixSequenceEventVisitor; + +impl<'de> Visitor<'de> for MatrixSequenceEventVisitor { + type Value = MatrixSequenceEvent; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + write!(formatter, "a tuple") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + use MatrixSequenceMessage as M; + macro_rules! col(() => { + MatrixSequenceColumnMessage { + index: seq + .next_element()? + .ok_or(Error::custom("expected column index"))? + } + }); + macro_rules! row(() => { + MatrixSequenceRowMessage { + index: seq + .next_element()? + .ok_or(Error::custom("expected row index"))? + } + }); + macro_rules! slot(() => { + MatrixSequenceSlotMessage { + column_index: seq + .next_element()? + .ok_or(Error::custom("expected slot column index"))?, + row_index: seq + .next_element()? + .ok_or(Error::custom("expected slot row index"))?, + } + }); + let pulse_diff: u32 = seq + .next_element()? + .ok_or(Error::custom("expected pulse diff"))?; + let msg_type: u8 = seq + .next_element()? + .ok_or(Error::custom("expected message type"))?; + let message = match msg_type { + 0 => M::PanicMatrix, + 1 => M::StopMatrix, + 2 => M::PanicColumn(col!()), + 3 => M::StopColumn(col!()), + 4 => M::StartScene(row!()), + 5 => M::PanicSlot(slot!()), + 6 => M::StartSlot(MatrixSequenceStartSlotMessage { + column_index: seq + .next_element()? + .ok_or(Error::custom("expected slot column index"))?, + row_index: seq + .next_element()? + .ok_or(Error::custom("expected slot row index"))?, + // Full velocity by default + velocity: seq.next_element()?.unwrap_or(1.0), + }), + 7 => M::StopSlot(slot!()), + _ => return Err(Error::custom(format!("unknown message type {msg_type}"))), + }; + let event = MatrixSequenceEvent { + pulse_diff, + message, + }; + Ok(event) + } +} + +impl<'de> Deserialize<'de> for MatrixSequenceEvent { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(MatrixSequenceEventVisitor) + } +} diff --git a/plugin-reaper-realearn/playtime-api/src/runtime/app.rs b/plugin-reaper-realearn/playtime-api/src/runtime/app.rs new file mode 100644 index 0000000..9df550a --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/src/runtime/app.rs @@ -0,0 +1,120 @@ +//! Usually we use Protocol Buffers for the runtime app API but there are a few things that are +//! not performance-critical and better expressed in a Rust-first manner. +use crate::persistence::{ColumnAddress, RowAddress, SlotAddress}; +use serde::{Deserialize, Serialize}; + +// We don't really need a tagged enum here but it's an easy way to transmit the event as a +// JSON object (vs. just a string) ... which is better for some clients. Plus, we might want +// to deliver some additional payloads in the future. +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum InfoEvent { + Generic(GenericInfoEvent), + RecordedMatrixSequence, + DiscardedMatrixSequenceBecauseEmpty, + RemovedMatrixSequence, + WroteMatrixSequenceToArrangement, +} + +impl InfoEvent { + /// Creates an info event with a generic message. This is displayed as toast in the app. + pub fn generic(message: impl Into) -> Self { + Self::Generic(GenericInfoEvent { + message: message.into(), + }) + } + + /// Creates an info event that would ideally be treated like a warning on the app side. + pub fn warning(message: impl Into) -> Self { + // In the future, this could set a special error flag, so that it could be displayed + // in a different way in the app. + Self::generic(message) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct GenericInfoEvent { + pub message: String, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct SimpleMappingContainer { + pub mappings: Vec, +} + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct SimpleMapping { + pub source: SimpleSource, + pub target: SimpleMappingTarget, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum SimpleSource { + Note(NoteSource), + MoreComplicated, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct NoteSource { + pub channel: u8, + pub number: u8, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum SimpleMappingTarget { + TriggerMatrix, + TriggerColumn(ColumnAddress), + TriggerRow(RowAddress), + TriggerSlot(SlotAddress), + SmartRecord, + EnterSilenceModeOrPlayIgnited, + SequencerRecordOnOffState, + SequencerPlayOnOffState, + TapTempo, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct CellAddress { + pub column_index: Option, + pub row_index: Option, +} + +impl CellAddress { + pub fn new(column_index: Option, row_index: Option) -> Self { + Self { + column_index, + row_index, + } + } + + pub fn matrix() -> Self { + Self::new(None, None) + } + + pub fn column(column_index: usize) -> Self { + Self { + column_index: Some(column_index), + row_index: None, + } + } + + pub fn row(row_index: usize) -> Self { + Self { + column_index: None, + row_index: Some(row_index), + } + } + + pub fn slot(column_index: usize, row_index: usize) -> Self { + Self { + column_index: Some(column_index), + row_index: Some(row_index), + } + } + + pub fn to_slot_address(&self) -> Option { + Some(SlotAddress::new(self.column_index?, self.row_index?)) + } +} diff --git a/plugin-reaper-realearn/playtime-api/src/runtime/control_unit.rs b/plugin-reaper-realearn/playtime-api/src/runtime/control_unit.rs new file mode 100644 index 0000000..8fdd6c4 --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/src/runtime/control_unit.rs @@ -0,0 +1,54 @@ +use crate::persistence::SlotAddress; +use serde::Serialize; + +#[derive(Clone, PartialEq, Debug, Default, Serialize)] +pub struct ControlUnitConfig { + #[serde(default)] + pub control_units: Vec, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default, Serialize)] +pub struct ControlUnitId(u32); + +impl ControlUnitId { + pub fn new(raw: u32) -> Self { + Self(raw) + } + + pub fn get(&self) -> u32 { + self.0 + } +} + +/// A control unit represents a controller connected to Playtime. +/// +/// While definitely a part of the Playtime domain, control units are **not** managed/persisted by +/// Playtime. That's the responsibility of the software that integrates Playtime and provides the +/// controller integration (in our case Helgobox with ReaLearn). +#[derive(Clone, PartialEq, Debug, Serialize)] +pub struct ControlUnit { + /// Uniquely identifies the control unit at runtime. + /// + /// In our case (Helgobox/ReaLearn), it's equal to the ReaLearn unit ID. + pub id: ControlUnitId, + /// A display name which should indicate what connected device we are talking about. + pub name: String, + /// Color in which the control unit should be visualized in the Playtime matrix. + /// + /// In our case (Helgobox/ReaLearn), the color is in most usage scenarios dictated by the + /// global controller definition but should also work without using the "global controller" + /// feature (by setting `custom_data.playtime.control_unit.palette_color` in the main + /// compartment data). + #[serde(skip_serializing_if = "Option::is_none")] + pub palette_color: Option, + /// The top-left column/row which this control unit controls. + /// + /// This will change as the controller scrolls. So it must be changeable by ReaLearn's targets. + pub top_left_corner: SlotAddress, + /// Both column and row count are fixed. + /// + /// It should be dictated by the ReaLearn main compartment as it's a decision of the main preset + /// which area of the controller's grid will be used for slot control. + pub column_count: u32, + pub row_count: u32, +} diff --git a/plugin-reaper-realearn/playtime-api/src/runtime/mod.rs b/plugin-reaper-realearn/playtime-api/src/runtime/mod.rs new file mode 100644 index 0000000..642e19b --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/src/runtime/mod.rs @@ -0,0 +1,8 @@ +mod reaper; +pub use reaper::*; + +mod app; +pub use app::*; + +mod control_unit; +pub use control_unit::*; diff --git a/plugin-reaper-realearn/playtime-api/src/runtime/reaper.rs b/plugin-reaper-realearn/playtime-api/src/runtime/reaper.rs new file mode 100644 index 0000000..c98ba5c --- /dev/null +++ b/plugin-reaper-realearn/playtime-api/src/runtime/reaper.rs @@ -0,0 +1,23 @@ +#![allow(non_snake_case)] +use helgobox_macros::reaper_api; + +reaper_api![ + PlaytimeApi, PlaytimeApiPointers, PlaytimeApiSession, register_playtime_api + { + /// Finds the first Helgobox instance in the given project that contains a Playtime matrix. + /// + /// If the given project is `null`, it will look in the current project. + /// + /// Returns the instance ID or -1 if none exists. + HB_FindFirstPlaytimeHelgoboxInstanceInProject(project: *mut reaper_low::raw::ReaProject) -> std::ffi::c_int; + + /// Creates a new Playtime matrix in the given Helgobox instance. + HB_CreateClipMatrix(instance_id: std::ffi::c_int); + + /// Shows or hides the app for the given Helgobox instance and makes sure that the app displays + /// Playtime. + /// + /// If necessary, this will also start the app and create a Playtime matrix for the given instance. + HB_ShowOrHidePlaytime(instance_id: std::ffi::c_int); + } +]; diff --git a/plugin-reaper-realearn/playtime-clip-engine-placeholder/Cargo.toml b/plugin-reaper-realearn/playtime-clip-engine-placeholder/Cargo.toml new file mode 100644 index 0000000..3e95a26 --- /dev/null +++ b/plugin-reaper-realearn/playtime-clip-engine-placeholder/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "playtime-clip-engine" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[features] +licensing = [] \ No newline at end of file diff --git a/plugin-reaper-realearn/playtime-clip-engine-placeholder/src/lib.rs b/plugin-reaper-realearn/playtime-clip-engine-placeholder/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/plugin-reaper-realearn/pot-browser/Cargo.toml b/plugin-reaper-realearn/pot-browser/Cargo.toml new file mode 100644 index 0000000..dc00b2e --- /dev/null +++ b/plugin-reaper-realearn/pot-browser/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "pot-browser" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +# Own +pot.workspace = true +reaper-high.workspace = true +reaper-medium.workspace = true +base.workspace = true +helgobox-api.workspace = true + +# 3rd-party +egui.workspace = true +egui_extras.workspace = true +egui-toast.workspace = true +chrono.workspace = true +lru.workspace = true +pulldown-cmark.workspace = true +dirs.workspace = true +crossbeam-channel.workspace = true +strum.workspace = true +swell-ui.workspace = true +bytesize.workspace = true +camino.workspace = true +anyhow.workspace = true +url.workspace = true + +[target.'cfg(target_os = "macos")'.dependencies] +rfd.workspace = true + +[target.'cfg(not(all(target_os = "windows", target_arch = "x86")))'.dependencies] +# This doesn't work on win32 +opener = { workspace = true, features = ["reveal"] } + + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/pot-browser/src/lib.rs b/plugin-reaper-realearn/pot-browser/src/lib.rs new file mode 100644 index 0000000..9e8e94d --- /dev/null +++ b/plugin-reaper-realearn/pot-browser/src/lib.rs @@ -0,0 +1,2 @@ +mod pot_browser_panel; +pub use pot_browser_panel::*; diff --git a/plugin-reaper-realearn/pot-browser/src/pot_browser_panel.rs b/plugin-reaper-realearn/pot-browser/src/pot_browser_panel.rs new file mode 100644 index 0000000..295508c --- /dev/null +++ b/plugin-reaper-realearn/pot-browser/src/pot_browser_panel.rs @@ -0,0 +1,3518 @@ +use anyhow::anyhow; +use base::enigo::EnigoMouse; +use base::{ + blocking_lock, blocking_lock_arc, blocking_read_lock, NamedChannelSender, SenderToNormalThread, +}; +use base::{Mouse, MouseCursorPosition}; +use camino::{Utf8Path, Utf8PathBuf}; +use chrono::{DateTime, Local, Utc}; +use crossbeam_channel::Receiver; +use egui::collapsing_header::CollapsingState; +use egui::{ + popup_below_widget, vec2, Align, Align2, Button, CentralPanel, Color32, DragValue, Event, + FontFamily, FontId, Frame, InputState, Key, Label, Layout, RichText, ScrollArea, TextEdit, + TextStyle, TopBottomPanel, Ui, Visuals, Widget, WidgetText, +}; +use egui::{Context, SidePanel}; +use egui_extras::{Column, Size, StripBuilder, TableBuilder}; +use egui_toast::Toasts; +use helgobox_api::persistence::PotFilterKind; +use lru::LruCache; +use pot::preset_crawler::{ + crawl_presets, import_crawled_presets, CrawlPresetArgs, PresetCrawlerStopReason, + PresetCrawlingState, SharedPresetCrawlingState, +}; +use pot::preview_recorder::{ + prepare_preview_recording, record_previews, ExportPreviewOutputConfig, PreviewOutputConfig, + PreviewRecorderFailure, PreviewRecorderState, RecordPreviewsArgs, SharedPreviewRecorderState, +}; +use pot::providers::projects::{ProjectDatabase, ProjectDbConfig}; +use pot::{ + create_plugin_factory_preset, find_preview_file, pot_db, spawn_in_pot_worker, ChangeHint, + CurrentPreset, Debounce, DestinationTrackDescriptor, FiledBasedPotPresetKind, Filters, + LoadAudioSampleBehavior, LoadPresetError, LoadPresetOptions, LoadPresetWindowBehavior, + MacroParam, MainThreadDispatcher, MainThreadSpawner, OptFilter, PersistentDatabaseId, + PotFavorites, PotFilterExcludes, PotFxParamId, PotPreset, PotPresetKind, PotWorkerDispatcher, + PotWorkerSpawner, PresetWithId, RuntimePotUnit, SearchField, SharedRuntimePotUnit, + WorkerDispatcher, +}; +use pot::{FilterItemId, PresetId}; +use reaper_high::{Fx, FxParameter, Reaper, SliderVolume, Track}; +use reaper_medium::{ReaperNormalizedFxParamValue, ReaperVolumeValue}; +use std::borrow::Cow; +use std::error::Error; +use std::fs::File; +use std::num::NonZeroUsize; +use std::path::Path; +use std::sync::{Arc, MutexGuard, RwLock}; +use std::time::{Duration, Instant}; +use std::{fs, mem}; +use strum::IntoEnumIterator; +use swell_ui::Window; +use url::Url; + +pub trait PotBrowserIntegration { + fn get_track_label(&self, track: &Track) -> String; + fn pot_preview_template_path(&self) -> Option<&'static Utf8Path>; + fn pot_favorites(&self) -> &'static RwLock; + fn with_current_fx_preset(&self, fx: &Fx, f: impl FnOnce(Option<&pot::CurrentPreset>)); + fn with_pot_filter_exclude_list(&self, f: impl FnOnce(&PotFilterExcludes)); +} + +#[derive(Debug)] +pub struct State { + page: Page, + main_state: TopLevelMainState, +} + +impl State { + pub fn new(pot_unit: SharedRuntimePotUnit, os_window: Window) -> Self { + Self { + page: Default::default(), + main_state: TopLevelMainState::new(pot_unit, os_window), + } + } +} + +#[derive(Debug, Default)] +enum Page { + #[default] + Warning, + Main, +} + +#[derive(Debug)] +pub struct TopLevelMainState { + pot_worker_dispatcher: CustomPotWorkerDispatcher, + main_thread_dispatcher: CustomMainThreadDispatcher, + main_state: MainState, +} + +impl TopLevelMainState { + pub fn new(pot_unit: SharedRuntimePotUnit, os_window: Window) -> Self { + Self { + pot_worker_dispatcher: WorkerDispatcher::new(PotWorkerSpawner), + main_thread_dispatcher: WorkerDispatcher::new(MainThreadSpawner), + main_state: MainState::new(pot_unit, os_window), + } + } +} + +#[derive(Debug)] +pub struct MainState { + pot_unit: SharedRuntimePotUnit, + os_window: Window, + auto_preview: bool, + auto_hide_sub_filters: bool, + show_stats: bool, + paint_continuously: bool, + last_preset_id: Option, + last_filters: Filters, + bank_index: u32, + preset_cache: PresetCache, + dialog: Option, + mouse: EnigoMouse, + has_shown_legacy_vst3_scan_warning: bool, +} + +type CustomPotWorkerDispatcher = PotWorkerDispatcher; +type CustomMainThreadDispatcher = MainThreadDispatcher; + +#[derive(Debug)] +enum Dialog { + GeneralError { + title: Cow<'static, str>, + msg: Cow<'static, str>, + }, + AddProjectDatabase { + folder: String, + name: String, + }, + PresetCrawlerIntro, + PresetCrawlerBasics, + PresetCrawlerMouse { + creation_time: Instant, + }, + PresetCrawlerReady { + fx: Fx, + cursor_pos: MouseCursorPosition, + stop_if_destination_exists: bool, + never_stop_crawling: bool, + }, + PresetCrawlerFailure { + short_msg: Cow<'static, str>, + detail_msg: Cow<'static, str>, + }, + PresetCrawlerCrawling { + crawling_state: SharedPresetCrawlingState, + }, + PresetCrawlerStopped { + crawling_state: SharedPresetCrawlingState, + stop_reason: PresetCrawlerStopReason, + page: CrawlPresetsStoppedPage, + chunks_file: Option, + crawled_preset_count: u32, + }, + PresetCrawlerFinished { + stop_reason: PresetCrawlerStopReason, + crawled_preset_count: u32, + }, + PreviewRecorderIntro, + PreviewRecorderBasics, + PreviewRecorderPreparing, + PreviewRecorderReadyToRecord { + presets: Vec, + output_config: PreviewOutputConfig, + }, + PreviewRecorderRecording { + state: SharedPreviewRecorderState, + }, + PreviewRecorderDone { + state: SharedPreviewRecorderState, + page: PreviewRecorderDonePage, + output_config: PreviewOutputConfig, + }, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum CrawlPresetsStoppedPage { + Presets, + Duplicates, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum PreviewRecorderDonePage { + Todos, + Failures, +} + +impl Dialog { + fn general_error( + title: impl Into>, + msg: impl Into>, + ) -> Self { + Self::GeneralError { + title: title.into(), + msg: msg.into(), + } + } + + fn add_project_database(folder: String) -> Self { + let suggested_name = Path::new(&folder) + .file_name() + .and_then(|n| Some(n.to_str()?.to_string())) + .unwrap_or_default(); + Self::AddProjectDatabase { + folder, + name: suggested_name, + } + } + + fn preset_crawler_basics() -> Self { + Self::PresetCrawlerBasics + } + + fn preset_crawler_mouse() -> Self { + Self::PresetCrawlerMouse { + creation_time: Instant::now(), + } + } + + fn preset_crawler_ready(fx: Fx, cursor_pos: MouseCursorPosition) -> Self { + Self::PresetCrawlerReady { + fx, + cursor_pos, + stop_if_destination_exists: false, + never_stop_crawling: false, + } + } + + fn preset_crawler_failure( + short_msg: impl Into>, + detail_msg: impl Into>, + ) -> Self { + Self::PresetCrawlerFailure { + short_msg: short_msg.into(), + detail_msg: detail_msg.into(), + } + } + + fn preset_crawler_crawling(crawling_state: SharedPresetCrawlingState) -> Self { + Self::PresetCrawlerCrawling { crawling_state } + } + + fn preset_crawler_stopped( + crawling_state: SharedPresetCrawlingState, + stop_reason: PresetCrawlerStopReason, + chunks_file: File, + crawled_preset_count: u32, + ) -> Self { + Self::PresetCrawlerStopped { + crawling_state, + stop_reason, + page: CrawlPresetsStoppedPage::Presets, + chunks_file: Some(chunks_file), + crawled_preset_count, + } + } + + fn preset_crawler_finished( + crawled_preset_count: u32, + stop_reason: PresetCrawlerStopReason, + ) -> Self { + Self::PresetCrawlerFinished { + crawled_preset_count, + stop_reason, + } + } + + fn preview_recorder_preparing() -> Self { + Self::PreviewRecorderPreparing + } + + fn preview_recorder_ready_to_record( + presets: Vec, + output_config: PreviewOutputConfig, + ) -> Self { + Self::PreviewRecorderReadyToRecord { + presets, + output_config, + } + } + + fn preview_recorder_recording(state: SharedPreviewRecorderState) -> Self { + Self::PreviewRecorderRecording { state } + } + + fn preview_recorder_done( + state: SharedPreviewRecorderState, + output_config: PreviewOutputConfig, + ) -> Self { + Self::PreviewRecorderDone { + state, + page: PreviewRecorderDonePage::Todos, + output_config, + } + } +} + +struct PresetCacheMessage { + pot_db_revision: u8, + preset_id: PresetId, + preset_data: Option, +} + +#[derive(Debug)] +enum PresetCacheEntry { + Requested, + NotFound, + Found(Box), +} + +#[derive(Debug)] +struct PotPresetData { + preset: PotPreset, + preview_file: Option, +} + +pub fn run_ui(ctx: &Context, state: &mut State, integration: &I) { + match state.page { + Page::Warning => { + run_warning_ui(ctx, state); + } + Page::Main => run_main_ui(ctx, &mut state.main_state, integration), + } +} + +fn run_warning_ui(ctx: &Context, state: &mut State) { + CentralPanel::default().show(ctx, |_| { + egui::Window::new("A word of caution") + .resizable(false) + .collapsible(false) + .anchor(Align2::CENTER_CENTER, vec2(0.0, 0.0)) + .show(ctx, |ui| { + ui.style_mut().text_styles = { + use FontFamily as F; + use TextStyle as T; + [ + (T::Heading, FontId::new(30.0, F::Proportional)), + (T::Body, FontId::new(15.0, F::Proportional)), + (T::Monospace, FontId::new(14.0, F::Proportional)), + (T::Button, FontId::new(20.0, F::Proportional)), + (T::Small, FontId::new(10.0, F::Proportional)), + ] + .into() + }; + ui.vertical_centered(|ui| { + ui.label( + "Although being very useful already for daily preset browsing and preview \ + recording, Pot Browser is still in its infancy. It will not save any of \ + your settings!", + ); + ui.add_space(20.0); + ui.label( + RichText::new( + "Therefore, better don't spend much time yet creating the perfect \ + configuration! It will be gone after REAPER restarts.", + ) + .strong(), + ); + ui.add_space(20.0); + let button = Button::new("I understood. Really!"); + if ui.add(button).clicked() { + state.page = Page::Main; + } + }) + }); + }); +} + +const PRESET_CRAWLER_TITLE: &str = "Preset Crawler"; +const PREVIEW_RECORDER_TITLE: &str = "Preview Recorder"; +const PRESET_CRAWLER_COUNTDOWN_DURATION: Duration = Duration::from_secs(10); + +fn run_main_ui( + ctx: &Context, + state: &mut TopLevelMainState, + integration: &I, +) { + // Poll background task results + state.pot_worker_dispatcher.poll(&mut state.main_state); + state.main_thread_dispatcher.poll(&mut state.main_state); + // We need the pot unit throughout the complete UI building process + let pot_unit = &mut blocking_lock( + &*state.main_state.pot_unit, + "PotUnit from PotBrowserPanel run_ui 1", + ); + // Query commonly used stuff + let background_task_elapsed = pot_unit.background_task_elapsed(); + // Integrate cache worker results into local cache + state + .main_state + .preset_cache + .set_pot_db_revision(pot_db().revision()); + while let Ok(message) = state.main_state.preset_cache.receiver.try_recv() { + state.main_state.preset_cache.process_message(message); + } + // Prepare toasts + let toast_margin = 10.0; + let mut toasts = Toasts::new() + .anchor(ctx.screen_rect().max - vec2(toast_margin, toast_margin)) + .direction(egui::Direction::RightToLeft) + .align_to_end(true); + // Warning dialog + if !state.main_state.has_shown_legacy_vst3_scan_warning && pot_db().detected_legacy_vst3_scan() + { + state.main_state.has_shown_legacy_vst3_scan_warning = true; + state.main_state.dialog = Some(Dialog::general_error("Warning", LEGACY_VST3_SCAN_WARNING)); + } + // Process dialogs + let mut change_dialog = None; + if let Some(dialog) = state.main_state.dialog.as_mut() { + let input = ProcessDialogsInput { + shared_pot_unit: &state.main_state.pot_unit, + pot_unit, + dialog, + mouse: &state.main_state.mouse, + os_window: state.main_state.os_window, + change_dialog: &mut change_dialog, + pot_worker_dispatcher: &mut state.pot_worker_dispatcher, + main_thread_dispatcher: &mut state.main_thread_dispatcher, + integration, + }; + process_dialogs(input, ctx); + } + if let Some(d) = change_dialog { + state.main_state.dialog = d; + } + // Process keyboard + let key_action = + ctx.input_mut(|input| determine_key_action(input, &mut state.main_state.dialog)); + if let Some(key_action) = key_action { + let key_input = KeyInput { + auto_preview: state.main_state.auto_preview, + os_window: state.main_state.os_window, + pot_unit: state.main_state.pot_unit.clone(), + dialog: &mut state.main_state.dialog, + }; + execute_key_action(key_input, pot_unit, &mut toasts, key_action); + } + let current_fx = pot_unit + .resolve_destination() + .ok() + .and_then(|inst| inst.get_existing().and_then(|dest| dest.resolve())); + // UI + let panel_frame = Frame::central_panel(&ctx.style()); + // Upper panel (currently loaded preset with macro controls) + if let Some(fx) = ¤t_fx { + integration.with_current_fx_preset(fx, |current_preset| { + if let Some(current_preset) = current_preset { + // Macro params + TopBottomPanel::top("top-bottom-panel") + .frame(panel_frame) + .min_height(50.0) + .show(ctx, |ui| { + show_current_preset_panel( + &mut state.main_state.bank_index, + fx, + current_preset, + ui, + ); + }); + } + }); + } + // Main panel + CentralPanel::default() + .frame(Frame::none()) + .show(ctx, |ui| { + // Left pane + SidePanel::left("left-panel") + .frame(panel_frame) + .default_width(ctx.available_rect().width() * 0.5) + .show_inside(ui, |ui| { + ui.style_mut().text_styles.insert( + TextStyle::Heading, + FontId::new(15.0, FontFamily::Proportional), + ); + // Toolbar + ui.horizontal(|ui| { + left_right( + ui, + pot_unit, + TOOLBAR_HEIGHT_WITH_MARGIN, + 280.0, + // Left side: Toolbar + |ui, pot_unit| { + // Main options + let input = LeftOptionsDropdownInput { + pot_unit, + auto_hide_sub_filters: &mut state + .main_state + .auto_hide_sub_filters, + paint_continuously: &mut state.main_state.paint_continuously, + shared_pot_unit: &state.main_state.pot_unit, + }; + add_left_options_dropdown(input, ui); + // Refresh button + ui.add_enabled_ui(!pot_unit.is_refreshing(), |ui| { + if ui + .button(RichText::new("🔃").size(TOOLBAR_HEIGHT)) + .on_hover_text( + "Refreshes all databases (e.g. picks up new \ + files on disk)", + ) + .clicked() + { + pot_unit.refresh_pot(state.main_state.pot_unit.clone()); + } + }); + // Theme button + if ui + .button(RichText::new("🌙").size(TOOLBAR_HEIGHT)) + .on_hover_text("Switches between light and dark theme") + .clicked() + { + let mut style: egui::Style = (*ctx.style()).clone(); + style.visuals = if style.visuals.dark_mode { + Visuals::light() + } else { + Visuals::dark() + }; + ctx.set_style(style); + } + // Help button + add_help_button(ui); + // Spinner + if background_task_elapsed.is_some() { + ui.spinner(); + } + }, + // Right side: Mini filters + |ui, pot_unit| { + if pot_unit.filter_item_collections.are_filled_already() { + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + add_mini_filters(&state.main_state.pot_unit, pot_unit, ui); + }); + } + }, + ); + }); + // Filter panels + add_filter_panels( + &state.main_state.pot_unit, + pot_unit, + state.main_state.auto_hide_sub_filters, + ui, + &state.main_state.last_filters, + &mut state.main_state.dialog, + integration, + ); + }); + // Right pane + CentralPanel::default() + .frame(panel_frame) + .show_inside(ui, |ui| { + // Toolbar + ui.horizontal(|ui| { + left_right( + ui, + pot_unit, + TOOLBAR_HEIGHT_WITH_MARGIN, + 80.0, + // Left side: Toolbar + |ui, pot_unit| { + // Actions + ui.menu_button(RichText::new("Tools").size(TOOLBAR_HEIGHT), |ui| { + if ui.button(PRESET_CRAWLER_TITLE).clicked() { + state.main_state.dialog = Some(Dialog::PresetCrawlerIntro); + ui.close_menu(); + } + if ui.button(PREVIEW_RECORDER_TITLE).clicked() { + state.main_state.dialog = + Some(Dialog::PreviewRecorderIntro); + ui.close_menu(); + } + }); + // Options + let input = RightOptionsDropdownInput { + pot_unit, + shared_pot_unit: &state.main_state.pot_unit, + show_stats: &mut state.main_state.show_stats, + auto_preview: &mut state.main_state.auto_preview, + }; + add_right_options_dropdown(input, ui); + // Search field + let text_edit = TextEdit::singleline( + &mut pot_unit.runtime_state.search_expression, + ) + // .min_size(vec2(0.0, TOOLBAR_SIZE)) + .desired_width(140.0) + .clip_text(false) + .hint_text("Enter search text!") + .font(TextStyle::Monospace); + ui.add_enabled(false, text_edit).on_disabled_hover_text( + "Type anywhere to search!\nUse backspace to clear \ + the last character\nand (Ctrl+Alt)/(Cmd)+Backspace to clear all.", + ); + // Preset count + let preset_count = pot_unit.preset_count(); + ui.label(format!("➡ {preset_count} presets")); + }, + // Right side: Mini filters + |ui, pot_unit| { + add_filter_view_content_as_icons( + &state.main_state.pot_unit, + pot_unit, + PotFilterKind::HasPreview, + ui, + ); + }, + ); + }); + // Stats + if state.main_state.show_stats { + ui.separator(); + ui.horizontal(|ui| { + add_stats_panel(pot_unit, background_task_elapsed, ui); + }); + } + // Info about selected preset + let current_preset_id = pot_unit.preset_id(); + let current_preset_id_and_data = current_preset_id.and_then(|id| { + match state.main_state.preset_cache.find_preset(id) { + PresetCacheEntry::Requested => None, + PresetCacheEntry::NotFound => None, + PresetCacheEntry::Found(data) => Some((id, data)), + } + }); + ui.separator(); + let widget_id = ui.make_persistent_id("selected-preset"); + CollapsingState::load_with_default_open(ui.ctx(), widget_id, false) + .show_header(ui, |ui| { + left_right( + ui, + pot_unit, + ui.available_height(), + 50.0, + // Left side of preset info + |ui, _| { + ui.strong("Selected preset:"); + if let Some((preset_id, preset_data)) = + current_preset_id_and_data + { + ui.label(preset_data.preset.name()); + let _ = pot_db().try_with_db(preset_id.database_id, |db| { + ui.strong("from"); + ui.label(db.name()); + }); + if let Some(product_name) = + &preset_data.preset.common.product_name + { + ui.strong("for"); + ui.label(product_name); + } + } else { + ui.label("-"); + } + }, + // Right side of preset info + |ui, pot_unit| { + let Some((preset_id, preset_data)) = current_preset_id_and_data + else { + return; + }; + // Favorite button + let favorites = integration.pot_favorites(); + let toggle = if let Ok(favorites) = favorites.try_read() { + let mut is_favorite = favorites.is_favorite(preset_id); + let icon = if is_favorite { "★" } else { "☆" }; + ui.toggle_value(&mut is_favorite, icon).changed() + } else { + false + }; + if toggle { + show_info_toast( + "This feature is not available.", + &mut toasts, + ); + // pot_unit.toggle_favorite(preset_id, state.main_state.pot_unit.clone()); + } + // Preview button + let preview_button = Button::new("🔊"); + let preview_button_response = ui.add_enabled( + preset_data.preview_file.is_some(), + preview_button, + ); + if preview_button_response + .on_hover_text("Play preset preview") + .on_disabled_hover_text("Preset preview not available") + .clicked() + { + if let Err(e) = pot_unit.play_preview(preset_id) { + show_error_toast(e.to_string(), &mut toasts); + } + } + }, + ); + }) + .body(|ui| { + if let Some((_, preset_data)) = current_preset_id_and_data { + let metadata = &preset_data.preset.common.metadata; + ui.horizontal(|ui| { + ui.strong("Vendor:"); + ui.label(optional_string(metadata.vendor.as_deref())); + ui.strong("Author:"); + ui.label(optional_string(metadata.author.as_deref())); + ui.strong("Extension:"); + ui.label(optional_string( + preset_data.preset.kind.file_extension(), + )); + }); + ui.horizontal(|ui| { + ui.strong("File size:"); + let fmt_size = metadata + .file_size_in_bytes + .map(|s| bytesize::ByteSize(s).to_string()); + ui.label(optional_string(fmt_size.as_deref())); + ui.strong("Date modified:"); + let fmt_date = metadata.modification_date.and_then(|s| { + let utc = s.and_local_timezone(Utc).single()?; + let local: DateTime = utc.into(); + Some(local.format("%Y-%m-%d %H:%M:%S").to_string()) + }); + ui.label(optional_string(fmt_date.as_deref())); + }); + ui.horizontal(|ui| { + ui.strong("Comment:"); + // This is a fix of invalid usage of line breaks in some preset + // commons, e.g. in some u-he presets. + let text = + metadata.comment.as_ref().map(|c| c.replace("\\n", "")); + ui.label(optional_string(text.as_deref())); + }); + } + }); + // Destination info + ui.separator(); + ui.horizontal(|ui| { + left_right( + ui, + pot_unit, + ui.available_height(), + 75.0, + // Left side of destination info + |ui, pot_unit| { + add_destination_info_panel(ui, pot_unit, integration); + }, + // Right side of destination info + |ui, _| { + if let Some(fx) = ¤t_fx { + if ui + .small_button("Chain") + .on_hover_text("Shows the FX chain") + .clicked() + { + fx.show_in_chain().unwrap(); + } + if ui + .small_button("FX") + .on_hover_text("Shows the FX") + .clicked() + { + let _ = fx.show_in_floating_window(); + } + } + }, + ); + }); + // Preset table + ui.separator(); + let input = PresetTableInput { + pot_unit, + toasts: &mut toasts, + last_preset_id: state.main_state.last_preset_id, + auto_preview: state.main_state.auto_preview, + os_window: state.main_state.os_window, + dialog: &mut state.main_state.dialog, + }; + add_preset_table(input, ui, &mut state.main_state.preset_cache); + }); + }); + // Other stuff + toasts.show(ctx); + if state.main_state.paint_continuously { + // Necessary e.g. in order to not just repaint on clicks or so but also when controller + // changes pot stuff. But also for other things! + ctx.request_repaint(); + } + state.main_state.last_preset_id = pot_unit.preset_id(); + state.main_state.last_filters = *pot_unit.filters(); +} + +struct ProcessDialogsInput<'a, I: PotBrowserIntegration> { + shared_pot_unit: &'a SharedRuntimePotUnit, + pot_unit: &'a mut RuntimePotUnit, + dialog: &'a mut Dialog, + mouse: &'a EnigoMouse, + os_window: Window, + change_dialog: &'a mut Option>, + pot_worker_dispatcher: &'a mut CustomPotWorkerDispatcher, + main_thread_dispatcher: &'a mut CustomMainThreadDispatcher, + integration: &'a I, +} + +fn process_dialogs(input: ProcessDialogsInput, ctx: &Context) { + match input.dialog { + Dialog::GeneralError { title, msg } => show_dialog( + ctx, + title, + input.change_dialog, + |ui, _| { + add_markdown(ui, msg, DIALOG_CONTENT_MAX_HEIGHT); + }, + |ui, change_dialog| { + if ui.button("Ok").clicked() { + *change_dialog = Some(None); + }; + }, + ), + Dialog::AddProjectDatabase { folder, name } => { + show_dialog( + ctx, + "Add project database", + &mut (input.change_dialog, name, folder), + |ui, (_, name, folder)| { + ui.strong("Caution:"); + ui.label("Choosing a folder with lots of subdirectories can lead to *very* long refresh times!"); + ui.separator(); + ui.horizontal(|ui| { + ui.strong("Folder:"); + ui.text_edit_singleline(*folder); + }); + ui.horizontal(|ui| { + ui.strong("Name:"); + ui.text_edit_singleline(*name); + }); + }, + |ui, (change_dialog, name, folder)| { + if ui.button("Cancel").clicked() { + **change_dialog = Some(None); + }; + if ui.button("Add").clicked() { + let config = ProjectDbConfig { + persistent_id: PersistentDatabaseId::random(), + root_dir: Path::new(*folder).to_path_buf(), + name: name.clone(), + }; + match ProjectDatabase::open(config) { + Ok(db) => { + **change_dialog = Some(None); + pot_db().add_database(db); + input.pot_unit.refresh_pot(input.shared_pot_unit.clone()); + } + Err(e) => { + let error_dialog = Dialog::general_error(e.to_string(), ""); + **change_dialog = Some(Some(error_dialog)); + } + } + } + }, + ); + } + Dialog::PresetCrawlerIntro => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + input.change_dialog, + |ui, _| { + add_markdown(ui, PRESET_CRAWLER_INTRO_TEXT, DIALOG_CONTENT_MAX_HEIGHT); + }, + |ui, change_dialog| { + if ui.button("Cancel").clicked() { + *change_dialog = Some(None); + }; + if ui.button("Continue").clicked() { + *change_dialog = Some(Some(Dialog::preset_crawler_basics())); + } + }, + ), + Dialog::PresetCrawlerBasics => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + input.change_dialog, + |ui, _| { + add_markdown(ui, PRESET_CRAWLER_BASICS_TEXT, DIALOG_CONTENT_MAX_HEIGHT); + }, + |ui, change_dialog| { + if ui.button("Cancel").clicked() { + *change_dialog = Some(None); + }; + if ui.button("Continue").clicked() { + *change_dialog = Some(Some(Dialog::preset_crawler_mouse())); + } + }, + ), + Dialog::PresetCrawlerMouse { creation_time } => match input.mouse.cursor_position() { + // Capturing current cursor position successful + Ok(p) => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + input.change_dialog, + |ui, change_dialog| { + ui.add(Label::new(PRESET_CRAWLER_MOUSE_TEXT).wrap(true)); + let elapsed = creation_time.elapsed(); + ui.horizontal(|ui| { + ui.strong("Current mouse cursor position:"); + ui.label(format_mouse_cursor_pos(p)); + }); + ui.horizontal(|ui| { + ui.strong("Countdown:"); + let countdown = PRESET_CRAWLER_COUNTDOWN_DURATION.saturating_sub(elapsed); + ui.label(format!("{}s", countdown.as_secs())); + }); + if elapsed >= PRESET_CRAWLER_COUNTDOWN_DURATION { + // Countdown finished + input.os_window.focus_first_child(); + let next_dialog = if let Some(fx) = Reaper::get().focused_fx() { + if fx.fx.floating_window().is_some() { + Dialog::preset_crawler_ready(fx.fx, p) + } else { + Dialog::preset_crawler_failure( + format!("Identified FX \"{}\" but it's not open in a floating window.", fx.fx.name()), + "Please use the floating window to point the mouse to the \"Next preset\" button!", + ) + } + } else { + Dialog::preset_crawler_failure(PRESET_CRAWLER_MOUSE_FAILURE_TEXT, "") + }; + *change_dialog = Some(Some(next_dialog)); + } + }, + |ui, change_dialog| { + if ui.button("Cancel").clicked() { + *change_dialog = Some(None); + }; + if ui.button("Try again").clicked() { + *change_dialog = Some(Some(Dialog::preset_crawler_mouse())); + }; + }, + ), + // Capturing current cursor position failed + Err(e) => { + *input.change_dialog = Some(Some(Dialog::preset_crawler_failure( + "Sorry, capturing the mouse position failed.", + e, + ))); + } + }, + Dialog::PresetCrawlerFailure { + short_msg, + detail_msg, + } => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + input.change_dialog, + |ui, _| { + ui.label(&**short_msg); + if !detail_msg.is_empty() { + ui.add_space(3.0); + ui.label(&**detail_msg); + } + }, + |ui, change_dialog| { + if ui.button("Cancel").clicked() { + *change_dialog = Some(None); + }; + if ui.button("Try again").clicked() { + *change_dialog = Some(Some(Dialog::preset_crawler_mouse())); + }; + }, + ), + Dialog::PresetCrawlerReady { + fx, + cursor_pos, + stop_if_destination_exists, + never_stop_crawling, + } => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + &mut ( + input.change_dialog, + stop_if_destination_exists, + never_stop_crawling, + ), + |ui, (_, stop_if_destination_exists, never_stop_crawling)| { + add_markdown( + ui, + PRESET_CRAWLER_READY_TEXT, + DIALOG_CONTENT_MAX_HEIGHT - 100.0, + ); + ui.separator(); + ui.horizontal(|ui| { + ui.strong("Plug-in to be crawled:"); + ui.label(fx.name().to_str()); + }); + ui.horizontal(|ui| { + ui.strong("Mouse cursor position to be repeatedly clicked:"); + ui.label(format_mouse_cursor_pos(*cursor_pos)); + }); + ui.separator(); + ui.horizontal(|ui| { + ui.checkbox(stop_if_destination_exists, "Stop if destination exists") + .on_hover_text("By default, Preset Crawler crawls presets even if they already exist in your \"FXChains\" folder.\nIf you tick this checkbox, it will stop crawling as soon as it sees that a preset already exists."); + ui.checkbox(never_stop_crawling, "Never stop crawling") + .on_hover_text("By default, Preset Crawler stops when it guesses that the last preset has been crawled.\nSometimes, this guess is incorrect. By ticking this checkbox, you can make the crawling infinite.\nYou need to press \"Escape\" as soon as you think that all presets have been crawled.") + }); + }, + |ui, (change_dialog, stop_if_destination_exists, never_stop_crawling)| { + if ui.button("Cancel").clicked() { + **change_dialog = Some(None); + }; + if ui.button("Try again").clicked() { + **change_dialog = Some(Some(Dialog::preset_crawler_mouse())); + }; + if ui.button("Start crawling").clicked() { + let crawling_state = PresetCrawlingState::new(); + let os_window = input.os_window; + let args = CrawlPresetArgs { + fx: fx.clone(), + next_preset_cursor_pos: *cursor_pos, + state: crawling_state.clone(), + stop_if_destination_exists: **stop_if_destination_exists, + never_stop_crawling: **never_stop_crawling, + bring_focus_back_to_crawler: move || { + os_window.focus_first_child(); + }, + }; + **change_dialog = Some(Some(Dialog::preset_crawler_crawling( + crawling_state.clone(), + ))); + input.main_thread_dispatcher.do_in_background_and_then( + async move { crawl_presets(args).await }, + |context, result| { + let next_dialog = match result { + Ok(o) => { + let crawled_preset_count = blocking_lock_arc( + &crawling_state, + "crawling finished state", + ) + .preset_count(); + if crawled_preset_count == 0 { + Dialog::preset_crawler_finished( + crawled_preset_count, + o.reason, + ) + } else { + Dialog::preset_crawler_stopped( + crawling_state, + o.reason, + o.chunks_file, + crawled_preset_count, + ) + } + } + Err(e) => Dialog::preset_crawler_failure( + "Failure while crawling", + e.to_string(), + ), + }; + context.dialog = Some(next_dialog); + }, + ); + } + }, + ), + Dialog::PresetCrawlerCrawling { crawling_state } => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + input.change_dialog, + |ui, _| { + ui.heading("Crawling in process..."); + let state = blocking_lock_arc(crawling_state, "run_main_ui crawling state"); + ui.horizontal(|ui| { + ui.strong("Presets crawled so far:"); + ui.label(format_preset_count(&state)); + }); + ui.horizontal(|ui| { + ui.strong("Presets skipped so far (because duplicate name):"); + ui.label(state.duplicate_preset_name_count().to_string()); + }); + ui.horizontal(|ui| { + ui.strong("Last crawled preset:"); + let text = if let Some(p) = state.last_crawled_preset() { + p.name() + } else { + "-" + }; + ui.label(text); + }); + }, + |ui, change_dialog| { + if ui.button("Cancel").clicked() { + *change_dialog = Some(None); + }; + }, + ), + Dialog::PresetCrawlerStopped { + crawling_state, + stop_reason, + page, + chunks_file, + crawled_preset_count, + } => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + &mut (input.change_dialog, input.pot_worker_dispatcher), + |ui, _| { + let cs = blocking_lock_arc(crawling_state, "run_main_ui crawling state 2"); + add_crawl_presets_stopped_dialog_contents( + *stop_reason, + &cs, + page, + *crawled_preset_count, + ui, + ); + }, + |ui, (change_dialog, pot_worker_dispatcher)| { + if ui.button("Discard crawl results").clicked() { + **change_dialog = Some(None); + } + *chunks_file = if let Some(chunks_file) = chunks_file.take() { + if ui.button("Import").clicked() { + let cloned_crawling_state = crawling_state.clone(); + let crawled_preset_count = *crawled_preset_count; + let stop_reason = *stop_reason; + pot_worker_dispatcher.do_in_background_and_then( + async move { + import_crawled_presets(cloned_crawling_state, chunks_file).await + }, + move |context, output| { + let next_dialog = match output { + Ok(_) => { + let cloned_pot_unit = context.pot_unit.clone(); + let mut pot_unit = blocking_lock( + &*context.pot_unit, + "PotUnit from background result handler", + ); + pot_unit.refresh_pot(cloned_pot_unit); + Dialog::preset_crawler_finished( + crawled_preset_count, + stop_reason, + ) + } + Err(e) => Dialog::preset_crawler_failure( + "Sorry, preset import failed.", + e.to_string(), + ), + }; + context.dialog = Some(next_dialog); + }, + ); + None + } else { + Some(chunks_file) + } + } else { + None + }; + }, + ), + Dialog::PresetCrawlerFinished { + crawled_preset_count, + stop_reason, + } => show_dialog( + ctx, + PRESET_CRAWLER_TITLE, + input.change_dialog, + |ui, _| { + if *crawled_preset_count == 0 { + let markdown = + get_preset_crawler_stopped_markdown(stop_reason, *crawled_preset_count); + add_markdown(ui, markdown, DIALOG_CONTENT_MAX_HEIGHT - 30.0); + ui.heading("Nothing to import!"); + } else { + ui.strong(format!( + "Successfully imported {} presets!", + *crawled_preset_count + )); + } + }, + |ui, change_dialog| { + if ui.button("Close").clicked() { + *change_dialog = Some(None); + } + }, + ), + Dialog::PreviewRecorderIntro => show_dialog( + ctx, + PREVIEW_RECORDER_TITLE, + input.change_dialog, + |ui, _| { + add_markdown(ui, PREVIEW_RECORDER_INTRO_TEXT, DIALOG_CONTENT_MAX_HEIGHT); + }, + |ui, change_dialog| { + if ui.button("Cancel").clicked() { + *change_dialog = Some(None); + }; + if ui.button("Continue").clicked() { + *change_dialog = Some(Some(Dialog::PreviewRecorderBasics)); + } + }, + ), + Dialog::PreviewRecorderBasics => show_dialog( + ctx, + PREVIEW_RECORDER_TITLE, + &mut (input.change_dialog, input.pot_worker_dispatcher), + |ui, _| { + add_markdown(ui, PREVIEW_RECORDER_BASICS_TEXT, DIALOG_CONTENT_MAX_HEIGHT); + }, + |ui, (change_dialog, pot_worker_dispatcher)| { + if ui.button("Cancel").clicked() { + **change_dialog = Some(None); + }; + // Determine output config + let record_and_export_button = ui.button("Record and export"); + let record_for_pot_browser_button = + ui.button("Record for playback within Pot Browser"); + let output_config = if record_for_pot_browser_button.clicked() { + PreviewOutputConfig::ForPotBrowserPlayback + } else if record_and_export_button.clicked() { + let parent_dir = os_document_or_reaper_resource_dir(); + let dir_name = Local::now().format("%Y-%m-%d %H-%M-%S").to_string(); + let base_dir = parent_dir + .join("Helgobox/SoundPot/Preview Exports") + .join(dir_name); + let config = ExportPreviewOutputConfig { base_dir }; + PreviewOutputConfig::Export(config) + } else { + // No button clicked yet + return; + }; + // One of the continue buttons has been clicked + let build_input = input.pot_unit.create_build_input(); + let output_config_clone = output_config.clone(); + pot_worker_dispatcher.do_in_background_and_then( + async move { prepare_preview_recording(build_input, &output_config_clone) }, + |context, output| { + if matches!(context.dialog, Some(Dialog::PreviewRecorderPreparing)) { + context.dialog = Some(Dialog::preview_recorder_ready_to_record( + output, + output_config, + )); + } + }, + ); + **change_dialog = Some(Some(Dialog::preview_recorder_preparing())); + }, + ), + Dialog::PreviewRecorderPreparing => show_dialog( + ctx, + PREVIEW_RECORDER_TITLE, + input.change_dialog, + |ui, _| { + ui.label("Aggregating presets to be recorded (this may take a while)..."); + ui.spinner(); + }, + |ui, change_dialog| { + if ui.button("Cancel").clicked() { + *change_dialog = Some(None); + }; + }, + ), + Dialog::PreviewRecorderReadyToRecord { + presets, + output_config, + } => show_dialog( + ctx, + PREVIEW_RECORDER_TITLE, + &mut (input.change_dialog, presets, input.main_thread_dispatcher), + |ui, (_, presets, _)| { + add_markdown( + ui, + PREVIEW_RECORDER_READY_TEXT, + DIALOG_CONTENT_MAX_HEIGHT / 2.0, + ); + ui.separator(); + ui.label(format!("Ready to record {} presets:", presets.len())); + add_item_table(ui, presets, DIALOG_CONTENT_MAX_HEIGHT / 2.0); + }, + |ui, (change_dialog, presets, main_thread_dispatcher)| { + if ui.button("Cancel").clicked() { + **change_dialog = Some(None); + }; + if !ui.button("Continue").clicked() { + return; + } + let preview_rpp = get_preview_rpp_path( + input.integration.pot_preview_template_path(), + output_config, + ); + let preview_rpp = match preview_rpp { + Ok(f) => f, + Err(e) => { + **change_dialog = Some(Some(Dialog::general_error( + PREVIEW_RECORDER_TITLE, + e.to_string(), + ))); + return; + } + }; + let presets = mem::take(*presets); + let state = PreviewRecorderState::new(presets); + let state = Arc::new(RwLock::new(state)); + **change_dialog = Some(Some(Dialog::preview_recorder_recording(state.clone()))); + let cloned_state = state.clone(); + let shared_pot_unit = input.shared_pot_unit.clone(); + let output_config_clone_1 = output_config.clone(); + let output_config_clone_2 = output_config.clone(); + main_thread_dispatcher.do_in_background_and_then( + async move { + let args = RecordPreviewsArgs { + shared_pot_unit, + state, + preview_rpp: &preview_rpp, + config: output_config_clone_1, + }; + record_previews(args).await.map_err(|e| e.to_string())?; + Ok(()) + }, + |context, _: Result<(), String>| { + let cloned_pot_unit = context.pot_unit.clone(); + let mut pot_unit = blocking_lock( + &*context.pot_unit, + "PotUnit from preview background result handler", + ); + pot_unit.refresh_pot(cloned_pot_unit); + context.dialog = Some(Dialog::preview_recorder_done( + cloned_state, + output_config_clone_2, + )); + }, + ); + }, + ), + Dialog::PreviewRecorderRecording { state } => show_dialog( + ctx, + PREVIEW_RECORDER_TITLE, + input.change_dialog, + |ui, _| { + let state = blocking_read_lock(state, "preview recorder UI"); + ui.heading("Preview recording in process..."); + ui.horizontal(|ui| { + ui.strong("Presets still left to be recorded:"); + ui.label(state.todos.len().to_string()); + }); + ui.horizontal(|ui| { + ui.strong("Presets failed:"); + ui.label(state.failures.len().to_string()); + }); + add_item_table(ui, &state.todos, DIALOG_CONTENT_MAX_HEIGHT - 40.0); + }, + |_, _| {}, + ), + Dialog::PreviewRecorderDone { + state, + page, + output_config, + } => { + let output_config_clone = output_config.clone(); + show_dialog( + ctx, + PREVIEW_RECORDER_TITLE, + input.change_dialog, + |ui, _| { + let state = blocking_read_lock(state, "preview recorder UI"); + add_preview_recorder_done_dialog_contents(&state, page, output_config, ui); + }, + |ui, change_dialog| { + if ui.button("Close").clicked() { + if let PreviewOutputConfig::Export(c) = output_config_clone { + reveal_path(c.base_dir.as_std_path()); + } + *change_dialog = Some(None); + }; + }, + ) + } + } +} + +fn add_crawl_presets_stopped_dialog_contents( + stop_reason: PresetCrawlerStopReason, + cs: &PresetCrawlingState, + page: &mut CrawlPresetsStoppedPage, + crawled_preset_count: u32, + ui: &mut Ui, +) { + let preset_count = cs.preset_count(); + let markdown = get_preset_crawler_stopped_markdown(&stop_reason, crawled_preset_count); + add_markdown(ui, markdown, DIALOG_CONTENT_MAX_HEIGHT / 2.0 - 40.0); + ui.separator(); + ui.strong(PRESET_CRAWLER_IMPORT_OR_DISCARD); + ui.horizontal(|ui| { + ui.strong("Crawled presets ready for import:"); + ui.label(format_preset_count(cs)); + }); + ui.horizontal(|ui| { + ui.strong("Skipped presets (because duplicate name):"); + ui.label(cs.duplicate_preset_name_count().to_string()); + }); + ui.separator(); + ui.horizontal(|ui| { + ui.strong("Show:"); + ui.selectable_value(page, CrawlPresetsStoppedPage::Presets, "Presets"); + ui.selectable_value(page, CrawlPresetsStoppedPage::Duplicates, "Duplicates"); + }); + let table_height = DIALOG_CONTENT_MAX_HEIGHT; + match *page { + CrawlPresetsStoppedPage::Presets => { + let text_height = get_text_height(ui); + TableBuilder::new(ui) + .striped(true) + .resizable(true) + .min_scrolled_height(table_height) + .max_scroll_height(table_height) + .cell_layout(Layout::left_to_right(Align::Center)) + .column(Column::auto()) + .column(Column::initial(200.0).clip(true)) + .header(text_height, |mut header| { + header.col(|ui| { + ui.strong("Name"); + }); + header.col(|ui| { + ui.strong("Destination"); + }); + }) + .body(|body| { + body.rows(text_height, preset_count as usize, |row_index, mut row| { + let entry = cs.crawled_presets().get_index(row_index); + if let Some((_, preset)) = entry { + row.col(|ui| { + ui.label(preset.name()); + }); + row.col(|ui| { + let dest = preset.destination().as_str(); + ui.label(dest).on_hover_text(dest); + }); + } + }); + }); + } + CrawlPresetsStoppedPage::Duplicates => { + show_as_list(ui, cs.duplicate_preset_names(), table_height); + } + } +} + +fn get_preset_crawler_stopped_markdown( + stop_reason: &PresetCrawlerStopReason, + crawled_preset_count: u32, +) -> &'static str { + match stop_reason { + PresetCrawlerStopReason::Interrupted => PRESET_CRAWLER_INTERRUPTED, + PresetCrawlerStopReason::DestinationFileExists => PRESET_CRAWLER_DESTINATION_FILE_EXISTS, + PresetCrawlerStopReason::PresetNameNotChangingAnymore => { + if crawled_preset_count < 2 { + PRESET_CRAWLER_INCOMPATIBLE_PLUGIN_TEXT + } else { + PRESET_CRAWLER_PRESET_NAME_NOT_CHANGING + } + } + PresetCrawlerStopReason::PresetNameLikeBeginning => PRESET_CRAWLER_PRESET_NAME_LIKE_FIRST, + } +} + +fn add_preview_recorder_done_dialog_contents( + state: &PreviewRecorderState, + page: &mut PreviewRecorderDonePage, + output_config: &PreviewOutputConfig, + ui: &mut Ui, +) { + let markdown = match (output_config, state.todos.is_empty()) { + (PreviewOutputConfig::ForPotBrowserPlayback, false) => { + PREVIEW_RECORDER_DONE_INTERNAL_INCOMPLETE_TEXT + } + (PreviewOutputConfig::ForPotBrowserPlayback, true) => { + PREVIEW_RECORDER_DONE_INTERNAL_COMPLETE_TEXT + } + (PreviewOutputConfig::Export(_), false) => PREVIEW_RECORDER_DONE_EXPORT_INCOMPLETE_TEXT, + (PreviewOutputConfig::Export(_), true) => PREVIEW_RECORDER_DONE_EXPORT_COMPLETE_TEXT, + }; + add_markdown(ui, markdown, DIALOG_CONTENT_MAX_HEIGHT / 2.0 - 80.0); + if !state.failures.is_empty() { + ui.label("Some previews couldn't be recorded. See the list of failures below."); + } + ui.separator(); + ui.horizontal(|ui| { + ui.strong("Number of previews not yet recorded:"); + ui.label(state.todos.len().to_string()); + }); + ui.horizontal(|ui| { + ui.strong("Number of failures:"); + ui.label(state.failures.len().to_string()); + }); + ui.horizontal(|ui| { + ui.strong("Show:"); + ui.selectable_value(page, PreviewRecorderDonePage::Todos, "Remaining"); + ui.selectable_value(page, PreviewRecorderDonePage::Failures, "Failures"); + }); + match *page { + PreviewRecorderDonePage::Todos => { + add_item_table(ui, &state.todos, DIALOG_CONTENT_MAX_HEIGHT / 2.0); + } + PreviewRecorderDonePage::Failures => { + add_item_table(ui, &state.failures, DIALOG_CONTENT_MAX_HEIGHT / 2.0); + } + } +} + +struct PresetTableInput<'a> { + pot_unit: &'a mut RuntimePotUnit, + toasts: &'a mut Toasts, + last_preset_id: Option, + auto_preview: bool, + os_window: Window, + dialog: &'a mut Option, +} + +fn add_preset_table(mut input: PresetTableInput, ui: &mut Ui, preset_cache: &mut PresetCache) { + let text_height = get_text_height(ui); + let preset_count = input.pot_unit.preset_count(); + let mut table = TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + // Preset name + .column(Column::auto()) + // Plug-in or product + .column(Column::initial(200.0).clip(true).at_least(100.0)) + // Extension + .column(Column::initial(50.0)) + // Context + .column(Column::remainder()) + .min_scrolled_height(0.0); + if input.pot_unit.preset_id() != input.last_preset_id { + let scroll_index = match input.pot_unit.preset_id() { + None => 0, + Some(id) => input.pot_unit.find_index_of_preset(id).unwrap_or(0), + }; + table = table.scroll_to_row(scroll_index as usize, None); + } + table + .header(20.0, |mut header| { + header.col(|ui| { + ui.strong("Name"); + }); + header.col(|ui| { + ui.strong("Plug-in/product"); + }); + header.col(|ui| { + ui.strong("Extension"); + }); + header.col(|ui| { + ui.strong("Context"); + }); + }) + .body(|body| { + body.rows(text_height, preset_count as usize, |row_index, mut row| { + let preset_id = input + .pot_unit + .find_preset_id_at_index(row_index as u32) + .unwrap(); + let cache_entry = preset_cache.find_preset(preset_id); + // Name + row.col(|ui| { + // Decide about text to display + let text: Cow = match cache_entry { + PresetCacheEntry::Requested => "⏳".into(), + PresetCacheEntry::NotFound => "".into(), + PresetCacheEntry::Found(d) => shorten_preset_name(d.preset.name()), + }; + let mut button = Button::new(text).small().fill(Color32::TRANSPARENT); + if let PresetCacheEntry::Found(data) = cache_entry { + if data.preview_file.is_some() { + button = button.shortcut_text("🔊"); + } + }; + // Highlight currently selected preset + if Some(preset_id) == input.pot_unit.preset_id() { + button = button.fill(ui.style().visuals.selection.bg_fill); + } + let mut button = ui.add_sized(ui.available_size(), button); + // Only if preset found in cache + if let PresetCacheEntry::Found(data) = cache_entry { + // Hover text + button = button.on_hover_text(data.preset.name()); + // Context menu + button = button.context_menu(|ui| { + // Open in + if let Some(preview_file) = &data.preview_file { + if ui + .button("Pre-play") + .on_hover_text("Opens the preview file in RS5k sampler in a \"playable\" way") + .clicked() + { + let preset = PotPreset { + common: data.preset.common.clone(), + kind: PotPresetKind::FileBased(FiledBasedPotPresetKind { + path: preview_file.clone(), + file_ext: "ogg".to_string(), + }), + }; + load_preset_and_regain_focus( + &preset, + input.os_window, + input.pot_unit, + input.toasts, + LoadPresetOptions { + window_behavior_override: Some(LoadPresetWindowBehavior::NeverShow), + audio_sample_behavior: LoadAudioSampleBehavior { + // At the moment, previews are always C4. + root_pitch: Some(-60), + obey_note_off: true, + }, + }, + input.dialog, + ); + ui.close_menu(); + } + } + // Open plug-in + let has_associated_products = + !data.preset.common.product_ids.is_empty(); + ui.add_enabled_ui(has_associated_products, |ui| { + ui.menu_button("Associated products", |ui| { + create_product_plugin_menu(&mut input, data, ui); + }); + }); + // Reveal preset in file manager + #[cfg(any( + all(target_os = "windows", target_arch = "x86_64"), + target_os = "macos" + ))] + { + if let pot::PotPresetKind::FileBased(k) = &data.preset.kind { + if ui.button("Show preset in file manager").clicked() { + if k.path.exists() { + reveal_path(&k.path); + } else { + show_error_toast( + "Preset file doesn't exist", + input.toasts, + ); + } + ui.close_menu(); + } + } + if let Some(preview_file) = &data.preview_file { + if ui.button("Show preview in file manager").clicked() { + reveal_path(preview_file); + ui.close_menu(); + } + } + } + }); + // What to do when clicked + if button.clicked() { + if input.auto_preview { + let _ = input.pot_unit.play_preview(preset_id); + } + input.pot_unit.set_preset_id(Some(preset_id)); + } + // What to do when double-clicked + if button.double_clicked() { + load_preset_and_regain_focus( + &data.preset, + input.os_window, + input.pot_unit, + input.toasts, + LoadPresetOptions::default(), + input.dialog, + ); + } + } + }); + // Make other columns empty if preset info not available yet + let PresetCacheEntry::Found(data) = cache_entry else { + row.col(|_| {}); + row.col(|_| {}); + return; + }; + let preset = &data.preset; + // Product + row.col(|ui| { + if let Some(n) = preset.common.product_name.as_ref() { + ui.label(n).on_hover_text(n); + } + }); + // Extension + row.col(|ui| { + let text = preset.kind.file_extension().unwrap_or(""); + ui.label(text); + }); + // Context + row.col(|ui| { + let text = preset.common.context_name.as_deref().unwrap_or(""); + ui.label(text); + }); + }); + }); +} + +trait DisplayItem { + fn prop_count() -> u32; + fn prop_label(prop_index: u32) -> &'static str; + fn prop_value(&self, prop_index: u32) -> Option>; +} + +impl DisplayItem for PreviewRecorderFailure { + fn prop_count() -> u32 { + PresetWithId::prop_count() + 1 + } + + fn prop_label(prop_index: u32) -> &'static str { + match prop_index { + i if i < PresetWithId::prop_count() => PresetWithId::prop_label(i), + 4 => "Reason", + _ => "", + } + } + + fn prop_value(&self, prop_index: u32) -> Option> { + match prop_index { + i if i < PresetWithId::prop_count() => self.preset.prop_value(i), + 3 => Some(self.reason.as_str().into()), + _ => None, + } + } +} + +impl DisplayItem for PresetWithId { + fn prop_count() -> u32 { + 3 + } + + fn prop_label(prop_index: u32) -> &'static str { + match prop_index { + 0 => "Name", + 1 => "Plug-in/product", + 2 => "Extension", + _ => "", + } + } + + fn prop_value(&self, prop_index: u32) -> Option> { + match prop_index { + 0 => Some(shorten_preset_name(self.preset.name())), + 1 => self.preset.common.product_name.as_ref().map(|s| s.into()), + 2 => self.preset.kind.file_extension().map(|s| s.into()), + _ => None, + } + } +} + +fn add_item_table(ui: &mut Ui, items: &[T], max_height: f32) { + let text_height = get_text_height(ui); + let item_count = items.len(); + let mut table = TableBuilder::new(ui) + .striped(true) + .resizable(false) + .cell_layout(Layout::left_to_right(Align::Center)) + .min_scrolled_height(0.0) + .min_scrolled_height(max_height) + .max_scroll_height(max_height); + for i in 0..T::prop_count() { + let col = if i == 0 { + Column::auto() + } else { + Column::remainder() + }; + table = table.column(col); + } + table + .header(20.0, |mut header| { + for i in 0..T::prop_count() { + header.col(|ui| { + ui.strong(T::prop_label(i)); + }); + } + }) + .body(|body| { + body.rows(text_height, item_count, |row_index, mut row| { + let item = items.get(item_count - row_index - 1).unwrap(); + for i in 0..T::prop_count() { + row.col(|ui| { + if let Some(val) = item.prop_value(i) { + ui.label(val); + } + }); + } + }); + }); +} + +const DIALOG_CONTENT_MAX_HEIGHT: f32 = 300.0; + +fn create_product_plugin_menu(input: &mut PresetTableInput, data: &PotPresetData, ui: &mut Ui) { + pot_db().with_plugin_db(|db| { + for product_id in &data.preset.common.product_ids { + let Some(product) = db.find_product_by_id(product_id) else { + continue; + }; + ui.menu_button(&product.name, |ui| { + let product_plugins = db + .plugins() + .filter(|p| p.common.core.product_id == *product_id); + for plugin in product_plugins { + if ui.button(plugin.common.to_string()).clicked() { + let factory_preset = create_plugin_factory_preset( + &plugin.common, + data.preset.common.persistent_id.clone(), + data.preset.common.name.clone(), + ); + let options = LoadPresetOptions { + window_behavior_override: Some(LoadPresetWindowBehavior::AlwaysShow), + ..Default::default() + }; + if let Err(e) = input.pot_unit.load_preset(&factory_preset, options) { + process_error(&e, input.toasts); + } + ui.close_menu(); + } + } + }); + } + }); +} + +fn add_destination_info_panel( + ui: &mut Ui, + pot_unit: &mut RuntimePotUnit, + integration: &I, +) { + // Track descriptor + let current_project = Reaper::get().current_project(); + { + const SPECIAL_TRACK_COUNT: usize = 2; + ui.strong("Load into"); + let track_count = current_project.track_count(); + let old_track_code = match &mut pot_unit.destination_descriptor.track { + DestinationTrackDescriptor::SelectedTrack => 0usize, + DestinationTrackDescriptor::MasterTrack => 1usize, + DestinationTrackDescriptor::Track(i) => { + // If configured track index too high, set it to + // "new track at end of project". + *i = (*i).min(track_count); + *i as usize + SPECIAL_TRACK_COUNT + } + }; + let mut new_track_code = old_track_code; + egui::ComboBox::from_id_source("tracks").show_index( + ui, + &mut new_track_code, + SPECIAL_TRACK_COUNT + track_count as usize + 1, + |code| match code { + 0 => "".to_string(), + 1 => "".to_string(), + _ => { + if let Some(track) = + current_project.track_by_index(code as u32 - SPECIAL_TRACK_COUNT as u32) + { + integration.get_track_label(&track) + } else { + "".to_string() + } + } + }, + ); + if new_track_code != old_track_code { + let track_desc = match new_track_code { + 0 => DestinationTrackDescriptor::SelectedTrack, + 1 => DestinationTrackDescriptor::MasterTrack, + c => DestinationTrackDescriptor::Track(c as u32 - SPECIAL_TRACK_COUNT as u32), + }; + pot_unit.destination_descriptor.track = track_desc; + } + } + // Resolved track (if displaying it makes sense) + let resolved_track = pot_unit + .destination_descriptor + .track + .resolve(current_project); + if pot_unit.destination_descriptor.track.is_dynamic() { + ui.label("="); + let caption = match resolved_track.as_ref() { + Ok(t) => { + format!("\"{}\"", integration.get_track_label(t)) + } + Err(_) => "None (add new)".to_string(), + }; + let short_caption = shorten(caption.as_str().into(), 14); + ui.label(short_caption).on_hover_text(caption); + } + // FX descriptor + { + if let Ok(t) = resolved_track.as_ref() { + ui.label("at"); + let chain = t.normal_fx_chain(); + let fx_count = chain.fx_count(); + // If configured FX index too high, set it to "new FX at end of chain". + pot_unit.destination_descriptor.fx_index = + pot_unit.destination_descriptor.fx_index.min(fx_count); + let mut fx_code = pot_unit.destination_descriptor.fx_index as usize; + egui::ComboBox::from_id_source("fxs").show_index( + ui, + &mut fx_code, + fx_count as usize + 1, + |code| match chain.fx_by_index(code as _) { + None => "".to_string(), + Some(fx) => { + format!("{}. {}", code + 1, fx.name()) + } + }, + ); + pot_unit.destination_descriptor.fx_index = fx_code as _; + } + } +} + +fn add_stats_panel( + pot_unit: &mut RuntimePotUnit, + background_task_elapsed: Option, + ui: &mut Ui, +) { + ui.strong("Last query: "); + let total_duration = background_task_elapsed.unwrap_or(pot_unit.stats.total_query_duration()); + ui.label(format!("{}ms", total_duration.as_millis())); + if background_task_elapsed.is_none() { + ui.label("(= "); + ui.label(pot_unit.stats.refresh_duration.as_millis().to_string()) + .on_hover_text("Refresh databases"); + ui.label(" + "); + ui.label(pot_unit.stats.filter_query_duration.as_millis().to_string()) + .on_hover_text("Building filters"); + ui.label(" + "); + ui.label(pot_unit.stats.preset_query_duration.as_millis().to_string()) + .on_hover_text("Building presets"); + ui.label(" + "); + ui.label( + pot_unit + .stats + .preview_filter_duration + .as_millis() + .to_string(), + ) + .on_hover_text("Checking previews"); + ui.label(" + "); + ui.label(pot_unit.stats.sort_duration.as_millis().to_string()) + .on_hover_text("Sorting filters and presets"); + ui.label(" + "); + ui.label(pot_unit.stats.sort_duration.as_millis().to_string()) + .on_hover_text("Indexing presets"); + ui.label(")"); + } + ui.strong("Wasted runs/time: "); + ui.label(format!( + "{}/{}ms", + pot_unit.wasted_runs, + pot_unit.wasted_duration.as_millis() + )); +} + +struct RightOptionsDropdownInput<'a> { + pot_unit: &'a mut RuntimePotUnit, + shared_pot_unit: &'a SharedRuntimePotUnit, + show_stats: &'a mut bool, + auto_preview: &'a mut bool, +} + +fn add_right_options_dropdown(input: RightOptionsDropdownInput, ui: &mut Ui) { + ui.menu_button(RichText::new("Options").size(TOOLBAR_HEIGHT), |ui| { + // FX window behavior + ui.menu_button("FX window behavior", |ui| { + for behavior in LoadPresetWindowBehavior::iter() { + ui.selectable_value( + &mut input.pot_unit.default_load_preset_window_behavior, + behavior, + behavior.as_ref(), + ); + } + }) + .response + .on_hover_text("Under which conditions to show the FX window when loading a preset"); + // Search fields + ui.menu_button("Search fields", |ui| { + let search_fields = &mut input.pot_unit.runtime_state.search_options.search_fields; + for search_field in SearchField::iter() { + let mut checked = search_fields.contains(search_field); + ui.checkbox(&mut checked, search_field.as_ref()); + if checked { + search_fields.insert(search_field); + } else { + search_fields.remove(search_field); + } + } + }) + .response + .on_hover_text("Which columns to search"); + // Wildcards + let old_wildcard_setting = input.pot_unit.runtime_state.search_options.use_wildcards; + ui.checkbox( + &mut input.pot_unit.runtime_state.search_options.use_wildcards, + "Wildcards", + ) + .on_hover_text( + "Allows more accurate search by enabling wildcards: Use * to match any \ + string and ? to match any letter!", + ); + if input.pot_unit.runtime_state.search_options.use_wildcards != old_wildcard_setting { + input.pot_unit.rebuild_collections( + input.shared_pot_unit.clone(), + ChangeHint::SearchExpression, + Debounce::No, + ); + } + // Stats + ui.checkbox(input.show_stats, "Display stats") + .on_hover_text("Show query statistics"); + // Preview + ui.horizontal(|ui| { + ui.checkbox(input.auto_preview, "Auto-preview") + .on_hover_text( + "Automatically previews a sound when it's selected via mouse or keyboard", + ); + // Preview volume + let old_volume = input.pot_unit.preview_volume(); + let mut new_volume_raw = old_volume.get(); + egui::DragValue::new(&mut new_volume_raw) + .speed(0.01) + .custom_formatter(|v, _| { + // TODO-low It's useless to first convert into a slider volume + SliderVolume::from_reaper_value(ReaperVolumeValue::new_panic(v)).to_string() + }) + .clamp_range(0.0..=1.0) + .ui(ui) + .on_hover_text("Change volume of the sound previews"); + let new_volume = ReaperVolumeValue::new_panic(new_volume_raw); + if new_volume != old_volume { + input.pot_unit.set_preview_volume(new_volume); + } + }); + // Name track after preset + ui.checkbox( + &mut input.pot_unit.name_track_after_preset, + "Name track after preset", + ) + .on_hover_text( + "When enabled, Pot Browser will rename the track to reflect the name of \ + the preset.", + ); + }); +} + +fn add_filter_panels( + shared_unit: &SharedRuntimePotUnit, + pot_unit: &mut RuntimePotUnit, + auto_hide_sub_filters: bool, + ui: &mut Ui, + last_filters: &Filters, + dialog: &mut Option, + integration: &I, +) { + let heading_height = ui.text_style_height(&TextStyle::Heading); + // Database + ui.separator(); + ui.horizontal(|ui| { + ui.label(RichText::new("Database").heading().size(heading_height)); + ui.menu_button("➕", |ui| { + if ui.button("Project database...").clicked() { + ui.close_menu(); + let folder = { + // On macOS, the blocking file dialog works nicely. + #[cfg(target_os = "macos")] + { + rfd::FileDialog::new() + .pick_folder() + .and_then(|dir| Utf8PathBuf::from_path_buf(dir).ok()) + } + // On Windows, we run into the borrow_mut error because of RefCells combined + // with reentrancy in baseview. Tried async dialog as well with main thread + // dispatcher but that closes Pot Browser after choosing file. So we fall back to + // manual entry of project path. + // On some Linux distributions (especially those used in cross 2.5) we don't + // have an up-to-date glib but rfd uses glib-sys and this one needs a new glib. + #[cfg(any(target_os = "windows", target_os = "linux"))] + { + Some(os_document_or_reaper_resource_dir()) + } + }; + if let Some(folder) = folder { + *dialog = Some(Dialog::add_project_database(folder.to_string())); + } + } + }); + }); + add_filter_view_content( + shared_unit, + pot_unit, + PotFilterKind::Database, + ui, + true, + last_filters.get(PotFilterKind::Database), + integration, + ); + // Product type + ui.separator(); + ui.label(RichText::new("Product type").heading().size(heading_height)); + add_filter_view_content( + shared_unit, + pot_unit, + PotFilterKind::ProductKind, + ui, + false, + None, + integration, + ); + // Add dependent filter views + ui.separator(); + let show_projects = pot_unit.supports_filter_kind(PotFilterKind::Project); + let show_banks = pot_unit.supports_filter_kind(PotFilterKind::Bank); + let show_sub_banks = show_banks + && pot_unit.supports_filter_kind(PotFilterKind::SubBank) + && (!auto_hide_sub_filters + || (pot_unit + .filters() + .is_set_to_concrete_value(PotFilterKind::Bank) + || pot_unit.get_filter(PotFilterKind::SubBank).is_some())); + let show_categories = pot_unit.supports_filter_kind(PotFilterKind::Category); + let show_sub_categories = show_categories + && pot_unit.supports_filter_kind(PotFilterKind::SubCategory) + && (!auto_hide_sub_filters + || (pot_unit + .filters() + .is_set_to_concrete_value(PotFilterKind::Category) + || pot_unit.get_filter(PotFilterKind::SubCategory).is_some())); + let show_modes = pot_unit.supports_filter_kind(PotFilterKind::Mode); + let mut remaining_kind_count = 6; + if !show_projects { + remaining_kind_count -= 1; + } + if !show_banks { + remaining_kind_count -= 1; + } + if !show_sub_banks { + remaining_kind_count -= 1; + } + if !show_categories { + remaining_kind_count -= 1; + } + if !show_sub_categories { + remaining_kind_count -= 1; + } + if !show_modes { + remaining_kind_count -= 1; + } + let mut added_one_view_already = false; + let mut needs_separator = || { + if added_one_view_already { + true + } else { + added_one_view_already = true; + false + } + }; + if remaining_kind_count > 0 { + let filter_view_height = ui.available_height() / remaining_kind_count as f32; + if show_projects { + add_filter_view( + ui, + filter_view_height, + shared_unit, + pot_unit, + PotFilterKind::Project, + needs_separator(), + false, + last_filters.get(PotFilterKind::Project), + integration, + ); + } + if show_banks { + add_filter_view( + ui, + filter_view_height, + shared_unit, + pot_unit, + PotFilterKind::Bank, + needs_separator(), + false, + last_filters.get(PotFilterKind::Bank), + integration, + ); + } + if show_sub_banks { + add_filter_view( + ui, + filter_view_height, + shared_unit, + pot_unit, + PotFilterKind::SubBank, + needs_separator(), + true, + last_filters.get(PotFilterKind::SubBank), + integration, + ); + } + if show_categories { + add_filter_view( + ui, + filter_view_height, + shared_unit, + pot_unit, + PotFilterKind::Category, + needs_separator(), + false, + last_filters.get(PotFilterKind::Category), + integration, + ); + } + if show_sub_categories { + add_filter_view( + ui, + filter_view_height, + shared_unit, + pot_unit, + PotFilterKind::SubCategory, + needs_separator(), + true, + last_filters.get(PotFilterKind::SubCategory), + integration, + ); + } + if show_modes { + add_filter_view( + ui, + filter_view_height, + shared_unit, + pot_unit, + PotFilterKind::Mode, + needs_separator(), + false, + last_filters.get(PotFilterKind::Mode), + integration, + ); + } + } +} + +fn add_mini_filters( + shared_unit: &SharedRuntimePotUnit, + pot_unit: &mut MutexGuard, + ui: &mut Ui, +) { + add_filter_view_content_as_icons(shared_unit, pot_unit, PotFilterKind::IsUser, ui); + ui.separator(); + add_filter_view_content_as_icons(shared_unit, pot_unit, PotFilterKind::IsFavorite, ui); + ui.separator(); + add_filter_view_content_as_icons(shared_unit, pot_unit, PotFilterKind::IsSupported, ui); + ui.separator(); + add_filter_view_content_as_icons(shared_unit, pot_unit, PotFilterKind::IsAvailable, ui); +} + +fn add_help_button(ui: &mut Ui) { + let help_button = ui.button(RichText::new("❓").size(TOOLBAR_HEIGHT)); + let help_id = ui.make_persistent_id("help"); + if help_button.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(help_id)); + } + popup_below_widget(ui, help_id, &help_button, |ui| { + TableBuilder::new(ui) + .column(Column::auto().at_least(200.0)) + .column(Column::remainder()) + .cell_layout(Layout::left_to_right(Align::Center)) + .body(|mut body| { + for (interaction, reaction) in HELP.iter() { + body.row(30.0, |mut row| { + row.col(|ui| { + ui.strong(format!("{interaction}:")); + }); + row.col(|ui| { + ui.label(*reaction); + }); + }); + } + }); + }); +} + +struct LeftOptionsDropdownInput<'a> { + pot_unit: &'a mut RuntimePotUnit, + auto_hide_sub_filters: &'a mut bool, + paint_continuously: &'a mut bool, + shared_pot_unit: &'a SharedRuntimePotUnit, +} + +fn add_left_options_dropdown(input: LeftOptionsDropdownInput, ui: &mut Ui) { + ui.menu_button(RichText::new("Options").size(TOOLBAR_HEIGHT), |ui| { + ui.checkbox(input.auto_hide_sub_filters, "Auto-hide sub filters") + .on_hover_text( + "Makes sure you are not confronted with dozens of child filters if \ + the corresponding top-level filter is set to ", + ); + { + let old = input.pot_unit.show_excluded_filter_items(); + let mut new = input.pot_unit.show_excluded_filter_items(); + ui.checkbox(&mut new, "Show excluded filters") + .on_hover_text( + "Shows all previously excluded filters again (via right click on \ + filter item), so you can include them again if you want.", + ); + if new != old { + input + .pot_unit + .set_show_excluded_filter_items(new, input.shared_pot_unit.clone()); + } + } + ui.checkbox(input.paint_continuously, "Paint continuously (devs only)") + .on_hover_text("Leave this enabled. This option is only intended for developers."); + }); +} + +fn show_current_preset_panel( + bank_index: &mut u32, + fx: &Fx, + current_preset: &CurrentPreset, + ui: &mut Ui, +) { + ui.horizontal(|ui| { + ui.heading(current_preset.preset().name()); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if current_preset.has_params() { + // Bank picker + let mut new_bank_index = *bank_index as usize; + egui::ComboBox::from_id_source("banks").show_index( + ui, + &mut new_bank_index, + current_preset.macro_param_bank_count() as usize, + |i| { + if let Some(bank) = current_preset.find_macro_param_bank_at(i as _) { + format!("{}. {}", i + 1, bank.name()) + } else { + format!("Bank {} (doesn't exist)", i + 1) + } + }, + ); + let new_bank_index = new_bank_index as u32; + if new_bank_index != *bank_index { + *bank_index = new_bank_index; + } + // ui.strong("Parameter bank:"); + } + }) + }); + // Actual macro param display + if current_preset.has_params() { + show_macro_params(ui, fx, current_preset, *bank_index); + // Scroll handler. This must come at the end, otherwise ui_contains_pointer + // works with a zero-sized UI! + if ui.ui_contains_pointer() { + let vertical_scroll = ui.input(|i| { + i.events.iter().find_map(|e| match e { + Event::Scroll(s) if s.y != 0.0 => Some(s.y), + _ => None, + }) + }); + if let Some(s) = vertical_scroll { + let amount = -s.signum() as i32; + *bank_index = bank_index.saturating_add_signed(amount); + } + } + } +} + +struct KeyInput<'a> { + auto_preview: bool, + os_window: Window, + pot_unit: SharedRuntimePotUnit, + dialog: &'a mut Option, +} + +fn execute_key_action( + input: KeyInput, + pot_unit: &mut MutexGuard, + toasts: &mut Toasts, + key_action: KeyAction, +) { + match key_action { + KeyAction::NavigateWithinPresets(amount) => { + if let Some(next_preset_index) = pot_unit.find_next_preset_index(amount) { + if let Some(next_preset_id) = pot_unit.find_preset_id_at_index(next_preset_index) { + pot_unit.set_preset_id(Some(next_preset_id)); + if input.auto_preview { + let _ = pot_unit.play_preview(next_preset_id); + } + } + } + } + KeyAction::LoadPreset => { + if let Some((_, preset)) = pot_unit.preset_and_id() { + load_preset_and_regain_focus( + &preset, + input.os_window, + pot_unit, + toasts, + LoadPresetOptions::default(), + input.dialog, + ); + } + } + KeyAction::ClearLastSearchExpressionChar => { + pot_unit.runtime_state.search_expression.pop(); + pot_unit.rebuild_collections( + input.pot_unit, + ChangeHint::SearchExpression, + Debounce::No, + ); + } + KeyAction::ClearSearchExpression => { + pot_unit.runtime_state.search_expression.clear(); + pot_unit.rebuild_collections( + input.pot_unit, + ChangeHint::SearchExpression, + Debounce::No, + ); + } + KeyAction::ExtendSearchExpression(text) => { + pot_unit.runtime_state.search_expression.push_str(&text); + pot_unit.rebuild_collections( + input.pot_unit, + ChangeHint::SearchExpression, + Debounce::Yes, + ); + } + } +} + +fn determine_key_action(input: &mut InputState, dialog: &mut Option) -> Option { + let mut action = None; + input.events.retain_mut(|event| match event { + Event::Key { + key, + pressed, + modifiers, + .. + } => { + if *pressed { + match key { + Key::ArrowUp => action = Some(KeyAction::NavigateWithinPresets(-1)), + Key::ArrowDown => action = Some(KeyAction::NavigateWithinPresets(1)), + Key::Enter => action = Some(KeyAction::LoadPreset), + Key::Backspace if modifiers.command => { + action = Some(KeyAction::ClearSearchExpression) + } + Key::Backspace if !modifiers.command => { + action = Some(KeyAction::ClearLastSearchExpressionChar) + } + _ => {} + } + } + false + } + Event::Text(text) => { + if dialog.is_some() { + true + } else { + action = Some(KeyAction::ExtendSearchExpression(mem::take(text))); + false + } + } + _ => true, + }); + action +} + +fn show_macro_params(ui: &mut Ui, fx: &Fx, current_preset: &CurrentPreset, bank_index: u32) { + // Added this UI just to not get duplicate table IDs + ui.vertical(|ui| { + if let Some(bank) = current_preset.find_macro_param_bank_at(bank_index) { + let text_height = get_text_height(ui); + let table = TableBuilder::new(ui) + .striped(false) + .resizable(false) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .columns(Column::remainder(), bank.param_count() as _) + .vscroll(false); + struct CombinedParam<'a> { + macro_param: &'a MacroParam, + fx_param: Option, + param_id: PotFxParamId, + } + let slots: Vec<_> = bank + .params() + .iter() + .map(|macro_param| { + let fx_param = macro_param.fx_param?; + let param_index = fx_param.resolved_param_index; + let combined_param = CombinedParam { + fx_param: { + param_index.and_then(|i| { + let fx_param = fx.parameter_by_index(i); + if fx_param.is_available() { + Some(fx_param) + } else { + None + } + }) + }, + macro_param, + param_id: fx_param.param_id, + }; + Some(combined_param) + }) + .collect(); + table + .header(20.0, |mut header| { + for slot in &slots { + header.col(|ui| { + let Some(param) = slot else { + // Empty slot yields empty column + return; + }; + ui.vertical(|ui| { + ui.strong(param.macro_param.section.as_deref().unwrap_or_default()); + let resp = ui.label(¶m.macro_param.name); + resp.on_hover_ui(|ui| { + let hover_text = if let Some(fx_param) = ¶m.fx_param { + fx_param.name().map(|n| n.into_string()).unwrap_or_default() + } else { + format!( + "Mapped parameter {} doesn't exist in actual plug-in", + param.param_id + ) + }; + ui.label(hover_text); + }); + }); + }); + } + }) + .body(|mut body| { + body.row(text_height, |mut row| { + for slot in &slots { + row.col(|ui| { + let Some(param) = slot else { + // Empty slot yields empty column + return; + }; + if let Some(fx_param) = param.fx_param.as_ref() { + let old_param_value = fx_param.reaper_normalized_value(); + let mut new_param_value_raw = old_param_value.get(); + DragValue::new(&mut new_param_value_raw) + .speed(0.01) + .custom_formatter(|v, _| { + let v = ReaperNormalizedFxParamValue::new(v); + fx_param + .format_reaper_normalized_value(v) + .unwrap_or_default() + .into_string() + }) + .clamp_range(0.0..=1.0) + .ui(ui); + if new_param_value_raw != old_param_value.get() { + let _ = fx_param + .set_reaper_normalized_value(new_param_value_raw); + } + } + }); + } + }); + }); + } else { + ui.vertical_centered_justified(|ui| { + ui.heading(format!("Parameter bank {} doesn't exist", bank_index + 1)); + }); + }; + }); +} + +impl MainState { + pub fn new(pot_unit: SharedRuntimePotUnit, os_window: Window) -> Self { + Self { + pot_unit, + auto_preview: true, + auto_hide_sub_filters: true, + show_stats: false, + paint_continuously: true, + os_window, + last_preset_id: None, + last_filters: Default::default(), + bank_index: 0, + preset_cache: PresetCache::new(), + dialog: Default::default(), + mouse: EnigoMouse::new(), + has_shown_legacy_vst3_scan_warning: false, + } + } +} + +#[derive(Debug)] +struct PresetCache { + lru_cache: LruCache, + sender: SenderToNormalThread, + receiver: Receiver, + pot_db_revision: u8, +} + +impl PresetCache { + pub fn new() -> Self { + let (sender, receiver) = + SenderToNormalThread::new_unbounded_channel("pot browser preset cache"); + Self { + lru_cache: LruCache::new(NonZeroUsize::new(500).unwrap()), + sender, + receiver, + pot_db_revision: 0, + } + } + + /// This can be called very often in order to keep the cache informed about the revision + /// of the current data that it's supposed to cache. This call only does something + /// substantial when the revision changes, i.e. it invalidates the cache. + pub fn set_pot_db_revision(&mut self, revision: u8) { + if self.pot_db_revision != revision { + self.pot_db_revision = revision; + // Change of revision! We need to invalidate all results. + self.lru_cache.clear(); + } + } + + pub fn find_preset(&mut self, preset_id: PresetId) -> &PresetCacheEntry { + self.lru_cache.get_or_insert(preset_id, || { + let reaper_resource_dir = Reaper::get().resource_path(); + let sender = self.sender.clone(); + let pot_db_revision = self.pot_db_revision; + spawn_in_pot_worker(async move { + let pot_db = pot_db(); + let preset = pot_db.try_find_preset_by_id(preset_id)?; + let preset_data = preset.map(|p| { + let preview_file = + find_preview_file(&p, &reaper_resource_dir).map(|p| p.into_owned()); + PotPresetData { + preset: p, + preview_file, + } + }); + let message = PresetCacheMessage { + pot_db_revision, + preset_id, + preset_data, + }; + sender.send_complaining(message); + Ok(()) + }); + PresetCacheEntry::Requested + }) + } + + pub fn process_message(&mut self, message: PresetCacheMessage) { + if message.pot_db_revision != self.pot_db_revision { + // This worker result is based on a previous revision of the pot unit. + // Not valid anymore. + return; + } + let data = match message.preset_data { + None => PresetCacheEntry::NotFound, + Some(data) => PresetCacheEntry::Found(Box::new(data)), + }; + self.lru_cache.put(message.preset_id, data); + } +} + +#[allow(clippy::too_many_arguments)] +fn add_filter_view( + ui: &mut Ui, + max_height: f32, + shared_pot_unit: &SharedRuntimePotUnit, + pot_unit: &mut RuntimePotUnit, + kind: PotFilterKind, + add_separator: bool, + indent: bool, + last_filter: OptFilter, + integration: &I, +) { + let separator_height = if add_separator { + if indent { + let vertical_spacing = 6.0; + ui.add_space(vertical_spacing); + vertical_spacing + } else { + ui.separator().rect.height() + } + } else { + 0.0 + }; + let mut render = |ui: &mut Ui| { + // let mut panel = TopBottomPanel::top(kind) + // .resizable(false) + // .frame(Frame::none()); + let h1_style_height = ui.text_style_height(&TextStyle::Heading); + let heading_style_height = if indent { + h1_style_height * 0.9 + } else { + h1_style_height + }; + let heading_height = ui + .label( + RichText::new(kind.to_string()) + .text_style(TextStyle::Heading) + .size(heading_style_height), + ) + .rect + .height(); + // panel = panel.min_height(h).max_height(h); + // panel.show_inside(ui, |ui| { + ScrollArea::vertical() + .id_source(kind) + .max_height(max_height - heading_height - separator_height) + .auto_shrink([false, false]) + .show(ui, |ui| { + add_filter_view_content( + shared_pot_unit, + pot_unit, + kind, + ui, + true, + last_filter, + integration, + ); + }); + // }); + }; + if indent { + ui.horizontal_top(|ui| { + ui.add_space(15.0); + // ui.separator(); + ui.vertical(|ui| { + render(ui); + }); + }); + } else { + render(ui); + } +} + +fn add_filter_view_content( + shared_pot_unit: &SharedRuntimePotUnit, + pot_unit: &mut RuntimePotUnit, + kind: PotFilterKind, + ui: &mut Ui, + wrapped: bool, + last_filter: OptFilter, + integration: &I, +) { + enum UiAction { + InOrExcludeFilter(PotFilterKind, FilterItemId, bool), + } + let mut action = None; + let old_filter_item_id = pot_unit.get_filter(kind); + let mut new_filter_item_id = old_filter_item_id; + let render = |ui: &mut Ui| { + integration.with_pot_filter_exclude_list(|exclude_list| { + ui.selectable_value(&mut new_filter_item_id, None, ""); + for filter_item in pot_unit.filter_item_collections.get(kind) { + let mut text = RichText::new(filter_item.effective_leaf_name()); + if exclude_list.contains(kind, filter_item.id) { + text = text.weak(); + }; + let mut resp = + ui.selectable_value(&mut new_filter_item_id, Some(filter_item.id), text); + // Scroll to current if wrapped and changed from outside + if wrapped { + let is_currently_selected_item = || new_filter_item_id == Some(filter_item.id); + let changed_from_outside = || { + old_filter_item_id == new_filter_item_id + && Some(filter_item.id) != last_filter + }; + if is_currently_selected_item() && changed_from_outside() { + resp.scroll_to_me(None); + } + } + // Hover text + if let Some(more_info) = filter_item.more_info.as_ref() { + resp = resp.on_hover_text(more_info); + } else if let Some(parent_kind) = kind.parent() { + if let Some(parent_name) = filter_item.parent_name.as_ref() { + if !parent_name.is_empty() { + resp = resp.on_hover_ui(|ui| { + let tooltip = match &filter_item.name { + None => { + format!( + "{parent_name} (directly associated with {parent_kind})" + ) + } + Some(n) => format!("{parent_name} / {n}"), + }; + ui.label(tooltip); + }); + } + } + } + // Context menu + if kind.allows_excludes() { + resp.context_menu(|ui| { + let is_excluded = exclude_list.contains(kind, filter_item.id); + let (text, include) = if is_excluded { + ("Include again (globally)", true) + } else { + ("Exclude (globally)", false) + }; + if ui.button(text).clicked() { + action = + Some(UiAction::InOrExcludeFilter(kind, filter_item.id, include)); + ui.close_menu(); + } + }); + } + } + }); + }; + if wrapped { + ui.horizontal_wrapped(render); + } else { + ui.horizontal(render); + } + // Execute actions + if new_filter_item_id != old_filter_item_id { + pot_unit.set_filter( + kind, + new_filter_item_id, + shared_pot_unit.clone(), + Debounce::No, + ); + } + if let Some(act) = action { + match act { + UiAction::InOrExcludeFilter(kind, id, include) => { + pot_unit.include_filter_item(kind, id, include, shared_pot_unit.clone()); + } + } + } +} + +/// Returns true if at least one filter was displayed. +fn add_filter_view_content_as_icons( + shared_pot_unit: &SharedRuntimePotUnit, + pot_unit: &mut RuntimePotUnit, + kind: PotFilterKind, + ui: &mut Ui, +) { + let old_filter_item_id = pot_unit.get_filter(kind); + let mut new_filter_item_id = old_filter_item_id; + // Reverse because we use right-to-left order + for filter_item in pot_unit.filter_item_collections.get(kind).iter().rev() { + let currently_selected = old_filter_item_id == Some(filter_item.id); + let mut text = RichText::new(filter_item.icon.unwrap_or('-')).size(TOOLBAR_HEIGHT); + if !currently_selected { + text = text.weak(); + } + let resp = ui.button(text).on_hover_ui(|ui| { + let name = filter_item.effective_leaf_name(); + let tooltip: Cow = if let Some(more) = &filter_item.more_info { + format!("{name}: {more}").into() + } else { + name + }; + ui.label(tooltip); + }); + if resp.clicked() { + new_filter_item_id = if currently_selected { + None + } else { + Some(filter_item.id) + } + }; + } + if new_filter_item_id != old_filter_item_id { + pot_unit.set_filter( + kind, + new_filter_item_id, + shared_pot_unit.clone(), + Debounce::No, + ); + } +} + +fn load_preset_and_regain_focus( + preset: &PotPreset, + os_window: Window, + pot_unit: &mut RuntimePotUnit, + toasts: &mut Toasts, + options: LoadPresetOptions, + dialog: &mut Option, +) { + if let Err(e) = pot_unit.load_preset(preset, options) { + match e { + LoadPresetError::UnsupportedPresetFormat { + file_extension, + is_shim_preset, + } => { + if is_shim_preset { + let text = format!( + "Found shim preset for unsupported original format but even the shim + seems to have an unsupported format: {file_extension}", + ); + show_error_toast(&text, toasts); + } else { + let text = UNSUPPORTED_PRESET_FORMAT_TEXT; + *dialog = Some(Dialog::general_error("Can't open preset", text)); + } + } + _ => process_error(&e, toasts), + } + } + os_window.focus_first_child(); +} + +fn process_error(error: &dyn Error, toasts: &mut Toasts) { + show_error_toast(error.to_string(), toasts); +} + +fn show_error_toast(text: impl Into, toasts: &mut Toasts) { + toasts.error(text, Duration::from_secs(3)); +} + +fn show_info_toast(text: &str, toasts: &mut Toasts) { + toasts.info(text, Duration::from_secs(3)); +} + +const TOOLBAR_HEIGHT: f32 = 15.0; +const TOOLBAR_HEIGHT_WITH_MARGIN: f32 = TOOLBAR_HEIGHT + 5.0; + +const HELP: &[(&str, &str)] = &[ + ("Click preset", "Select it (and preview it if enabled)"), + ("Double-click preset", "Load it"), + ("Up/down arrays", "Navigate in preset list"), + ("Enter", "Load currently selected preset"), + ("Type letters", "Enter search text"), + ("(Ctrl+Alt)/(Cmd) + Backspace", "Clear search expression"), +]; + +fn shorten(text: Cow, max_len: usize) -> Cow { + if text.len() > max_len { + let mut s = text.into_owned(); + truncate_in_place(&mut s, max_len); + s.push('…'); + s.into() + } else { + text + } +} + +fn truncate_in_place(s: &mut String, max_chars: usize) { + let bytes = truncate(s, max_chars).len(); + s.truncate(bytes); +} + +fn truncate(s: &str, max_chars: usize) -> &str { + match s.char_indices().nth(max_chars) { + None => s, + Some((idx, _)) => &s[..idx], + } +} + +fn left_right( + ui: &mut Ui, + arg: &mut A, + height: f32, + right_width: f32, + left: impl FnOnce(&mut Ui, &mut A), + right: impl FnOnce(&mut Ui, &mut A), +) { + // At first, we need to add a vertical strip in order to not take infinity height. + StripBuilder::new(ui) + .size(Size::exact(height)) + .clip(true) + .vertical(|mut strip| { + strip.strip(|strip| { + strip + .size(Size::remainder()) + .size(Size::exact(right_width)) + .horizontal(|mut strip| { + // Left strip + strip.cell(|ui| { + ui.horizontal(|ui| { + left(ui, arg); + }); + }); + // Right strip + strip.cell(|ui| { + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + right(ui, arg); + }); + }); + }); + }); + }); +} + +enum KeyAction { + NavigateWithinPresets(i32), + LoadPreset, + ClearSearchExpression, + ClearLastSearchExpressionChar, + ExtendSearchExpression(String), +} + +fn show_dialog( + ctx: &Context, + title: &str, + value: &mut V, + content: impl FnOnce(&mut Ui, &mut V), + buttons: impl FnOnce(&mut Ui, &mut V), +) { + egui::Window::new(title) + .resizable(false) + .collapsible(false) + .pivot(Align2::CENTER_CENTER) + .default_pos(ctx.screen_rect().center()) + .show(ctx, |ui| { + ui.vertical_centered(|ui| { + ui.set_min_width(500.0); + // Content + content(ui, value); + // Space between content and buttons + ui.add_space(10.0); + // Buttons + ui.horizontal(|ui| { + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + // Right margin + ui.add_space(20.0); + // Buttons + buttons(ui, value); + }); + }); + // Bottom margin + ui.add_space(10.0); + }) + }); +} + +fn format_mouse_cursor_pos(pos: MouseCursorPosition) -> String { + format!("{}, {}", pos.x, pos.y) +} + +fn get_text_height(ui: &Ui) -> f32 { + TextStyle::Body.resolve(ui.style()).size +} + +fn show_as_list(ui: &mut Ui, entries: &[impl AsRef], height: f32) { + let text_height = get_text_height(ui); + TableBuilder::new(ui) + .striped(true) + .resizable(false) + .min_scrolled_height(height) + .max_scroll_height(height) + .column(Column::remainder()) + .body(|body| { + body.rows(text_height, entries.len(), |i, mut row| { + row.col(|ui| { + ui.label(entries[i].as_ref()); + }); + }); + }); +} + +fn shorten_preset_name(name: &str) -> Cow { + const MAX_PRESET_NAME_LEN: usize = 40; + shorten(name.into(), MAX_PRESET_NAME_LEN) +} + +fn add_markdown(ui: &mut Ui, markdown: &str, max_height: f32) { + if markdown.trim().is_empty() { + return; + } + use pulldown_cmark::*; + let parser = Parser::new(markdown); + let mut strong = false; + let mut heading_level: Option = None; + let mut list_item_index: Option = None; + let mut spans: Vec = vec![]; + let mut href: Option = None; + let insert_paragraph = |ui: &mut Ui, spans: &mut Vec| { + ui.horizontal_wrapped(|ui| { + for span in spans.drain(..) { + ui.label(span); + } + }); + ui.add_space(3.0); + }; + ScrollArea::vertical() + .id_source("markdown") + .max_height(max_height) + .show(ui, |ui| { + for event in parser { + match event { + Event::Start(Tag::Strong) => { + strong = true; + } + Event::End(Tag::Strong) => { + strong = false; + } + Event::Start(Tag::Link(_, url, _)) => { + href = Some(url.to_string()); + } + Event::End(Tag::Link(_, _, _)) => { + href = None; + } + Event::Start(Tag::Heading(level, ..)) => { + heading_level = Some(level); + } + Event::End(Tag::Heading(..)) => { + heading_level = None; + ui.add_space(3.0); + insert_paragraph(ui, &mut spans); + } + Event::Start(Tag::List(start_index)) => { + list_item_index = start_index; + } + Event::Start(Tag::Item) => { + let decoration = if let Some(i) = list_item_index { + format!("{i}.") + } else { + "•".to_string() + }; + spans.push(RichText::new(decoration).strong()); + } + Event::End(Tag::Item) => { + if let Some(i) = &mut list_item_index { + *i += 1; + } + insert_paragraph(ui, &mut spans); + } + Event::End(Tag::Paragraph) => { + insert_paragraph(ui, &mut spans); + } + Event::Text(text) => { + if let Some(href) = href.take() { + if ui.button(text.as_ref()).clicked() { + open_link(&href); + } + } else { + use HeadingLevel as L; + let (size, always_strong) = match heading_level { + Some(L::H1) => (20.0, true), + Some(L::H2) => (16.0, true), + _ => (14.0, false), + }; + let mut span = RichText::new(text.as_ref()).size(size); + if strong || always_strong { + span = span.strong(); + } + spans.push(span); + } + } + _ => {} + } + } + }); +} + +fn format_preset_count(state: &PresetCrawlingState) -> String { + let fmt_size = bytesize::ByteSize(state.bytes_crawled() as u64); + format!("{} ({fmt_size})", state.preset_count()) +} + +const PRESET_CRAWLER_INTRO_TEXT: &str = r#" +## Welcome to Pot Preset Crawler! + +You might have plug-in presets that don't show up in the browser because they are only accessible from within the plug-in itself. Wouldn't it be nice if you could browse them, too? + +One thing you could do is to manually load each preset from within the plug-in and save it, for example as a REAPER FX chain or REAPER FX preset. Then it will show up. But imagine doing that for hundreds of presets! What a tedious work! That's where Preset Crawler comes in. It tries to automate this process as far as possible. + +## Preconditions + +- The plug-in must have a button to navigate to the next preset. It must be accessible with just one click, not be buried in menus. +- The plug-in must correctly expose the name of the currently loaded internal preset. In my experience, this works with most VST2 plug-ins but unfortunately not with most VST3 plug-ins. Just check if REAPER's FX dropdown always shows the same preset name as the plug-in user interface. + +## How it works + +**Step 1:** You show preset crawler where's the "Next preset" button. + +**Step 2:** Preset crawler repeatedly clicks that button for you and memorizes the presets. + +**Step 3:** At the end, it shows you the list of memorized presets. If you then click "Import", it will save a REAPER FX chain for each of them. + +**Step 4:** That's it! Your newly crawled presets will show up in Pot Browser and even in REAPER's own FX browser. + +## Want to try it? + +Then press "Continue" and follow the instructions! +"#; + +const PREVIEW_RECORDER_INTRO_TEXT: &str = r#" +## Welcome to Pot Preview Recorder! + +Wouldn't it be nice if you could get a quick impression of how your instrument preset sounds without actually loading it? + +Preview Recorder has a feature for that: Previews. You know that a preset has a preview when it has the small 🔊 symbol right next to it. + +Some preset databases such as Komplete ship with pre-recorded previews and Pot Browser supports them! But even if you own Komplete, you will find that many previews are missing, especially those for your own user presets. + +Preview Recorder to the rescue! It can batch-record previews of your instrument presets, very conveniently, no matter where they come from. + +## Preconditions + +- The preset must be loadable by Pot Browser (Preview Recorder will simply skip presets that can't be loaded automatically). + +## How it works + +**Step 1:** You use Pot Browser's filter and search features in order to define the set of presets to be recorded. + +**Step 2:** Preview Recorder filters out unsuitable presets and creates an optimized recording plan, with as few plug-in reloads as possible. + +**Step 3:** You review the plan and if you like it, you start the recording process. + +**Step 4:** Preview Recorder opens a new project tab and renders the previews. + +**Step 5:** All newly recorded previews are automatically available! + +As an alternative, you can export the recorded previews to a directory of your choice. + +## Want to try it? + +Then press "Continue" and follow the instructions! +"#; + +const PREVIEW_RECORDER_BASICS_TEXT: &str = r#" +## Okay, let's do this! + +Have you used Pot Browser's filter and search features already to narrow down the set of presets? This step is optional but if you want to do it, now's the time. Simply press "Cancel" and reopen Preview Recorder when you are done filtering/searching. + +Creating previews for hundreds of presets can take long. But no worries, you can stop anytime without losing your already recorded previews. Next time, Preview Recorder will automatically continue where you left off. + +As soon as you press one of the record buttons, Preview Browser will narrow down your set of presets further and only include those that ... + +- ... don't have previews yet +- ... are instrument presets +- ... are available +- ... and are automatically loadable. + +## Ready? + +Then press one of the record buttons, depending on what you want to do! +"#; + +const PRESET_CRAWLER_BASICS_TEXT: &str = r#" +## Okay, let's do this! + +Do the following things while leaving the Pot Browser window open. You can move it to the side if it's getting in the way. + +1. At first, it's a good idea to save and close all open projects. Just in case. +2. Then open the plug-in whose presets you want to crawl - simply by adding it as FX to a track. +3. If you don't want to start crawling from the first preset, load the preset from which you want to start. +4. Make sure the plug-in window is visible, in particular its "Next preset" button. This is the button which makes the plug-in navigate to the next preset. + +After pressing "Continue", you will have to place the mouse cursor on top of the "Next preset" button of your plug-in. + +## Ready? + +Then press "Continue"! +"#; + +const PREVIEW_RECORDER_DONE_INTERNAL_COMPLETE_TEXT: &str = r#" +## Preview recording done! + +Preview Recorder has recorded all previews. They will be automatically available in Preset Browser. + +You may close the preview recording project tab (no need to save). +"#; + +const PREVIEW_RECORDER_DONE_EXPORT_INCOMPLETE_TEXT: &str = r#" +## Preview recording stopped! + +Preview Recorder has stopped recording previews. All the previews generated so far can be found in the +export directory (which will be opened in the next step). + +You may close the preview recording project tab (no need to save). +"#; + +const PREVIEW_RECORDER_DONE_EXPORT_COMPLETE_TEXT: &str = r#" +## Preview recording done! + +Preview Recorder has recorded all previews. They can be found in the export directory (which will be opened in the next step). + +You may close the preview recording project tab (no need to save). +"#; + +const PREVIEW_RECORDER_DONE_INTERNAL_INCOMPLETE_TEXT: &str = r#" +## Preview recording stopped! + +Preview Recorder has stopped recording previews. All the previews generated so far will be +automatically available in Preset Browser. + +You may close the preview recording project tab (no need to save). +"#; + +const PRESET_CRAWLER_MOUSE_TEXT: &str = r#" +Now you have 10 seconds to place the mouse cursor on top of the "Next preset" button. + +When it's there, simply wait, don't move the mouse. +"#; + +const PRESET_CRAWLER_MOUSE_FAILURE_TEXT: &str = r#" +Preset Crawler couldn't figure out which plug-in you want to crawl! + +Press "Try again" and make sure to give the plug-in focus before placing the mouse cursor, e.g. by clicking somewhere onto its surface. +"#; + +const PRESET_CRAWLER_READY_TEXT: &str = r#" +## Congratulations! + +Now, Preset Crawler knows which plug-in you want to crawl and where's the +"Next preset" button. + +## Next step + +Check if the plug-in and the mouse cursor position displayed below are correct. Make sure not to move the plug-in window anymore. You can still move the Pot Browser window, no problem. It's best to move it to the side so you can see this dialog. + +When you are ready, press "Start crawling". No worries, at this point, Preset Crawler will not yet save FX chains into your "FXChains" folder. As soon as the preset crawling is finished, you can choose to import or discard the results. + +## Important + +- As soon as you press "Start crawling", Preset Crawler will take over your mouse! That means you can't easily press "Cancel". Instead, just press the "Escape" key of your keyboard (the top-left key)! +- Try not to move the mouse during crawling! +"#; + +const PREVIEW_RECORDER_READY_TEXT: &str = r#" +## Ready! + +Preview Recorder is ready to record presets in the following order. + +## Next step + +- You might want to move the Pot Browser window to the side so you can see the progress, which would otherwise be covered by plug-in windows or REAPER's rendering dialog. +- When you are ready, press "Continue". + +## Possible issues + +- Some plug-ins have the bad habit to block the user interface by opening a dialog window. If this happens, it will pause the complete recording process. You will need to close the window in order to resume the recording process. + +## Important + +- If you want to cancel preview recording, press the "Escape" key of your keyboard (the top-left key)! +- It's best to not use the computer while previews are being generated. +"#; + +const PRESET_CRAWLER_INCOMPATIBLE_PLUGIN_TEXT: &str = r#" +## Bad news + +Preset Crawler detected that it's not possible to crawl your plug-in because it doesn't correctly update the current preset name in REAPER's preset menu. + +## Troubleshooting + +- Or maybe you didn't place the mouse cursor directly over the "Next preset" button? +- Did you try to crawl a VST3 plug-in? It seems that many VST3 plug-ins don't correctly expose the current preset name, in general. You might have more success with crawling the VST2 version of your plug-in. +"#; + +const PRESET_CRAWLER_PRESET_NAME_NOT_CHANGING: &str = r#" +## Crawling stopped + +Preset Crawler detected that the preset name wasn't changing anymore. That's usually a sign that the end of the preset list has been reached. + +## Troubleshooting + +It's possible that Preset Crawler stopped prematurely. This happens in the following cases: + +1. Multiple presets in a row had the same name (which made Preset Crawler guess that it's the last preset). +2. Multiple presets in a row had very similar names and only the ending was different, but the plug-in cropped the ending. + +If this happened, please tick the "Never stop crawling" checkbox next time. + +"#; + +const PRESET_CRAWLER_PRESET_NAME_LIKE_FIRST: &str = r#" +## Crawling stopped + +Preset Crawler detected that the preset names of the recently crawled presets are the same as the ones crawled at the beginning. That's usually a sign that the end of the preset list has been reached and we are back at its beginning. + +It can also mean that the plug-in navigates through its presets in a very non-linear fashion. If this happened, please tick the "Never stop crawling" checkbox next time. + +"#; + +const PRESET_CRAWLER_INTERRUPTED: &str = r#" +## Crawling interrupted + +It seems you have pressed the "Escape" key, so we have interrupted crawling. + +"#; + +const PRESET_CRAWLER_DESTINATION_FILE_EXISTS: &str = r#" +## Crawling stopped + +Preset Crawler detected that the destination file of the last-crawled preset already exists. You have chosen to stop crawling in that case, so here we are. + +"#; + +const LEGACY_VST3_SCAN_WARNING: &str = r#" +## Attention + +Pot Browser has detected that some of your VST3 plug-ins were scanned by a very old version of REAPER. The scan misses parts of information that are important for Pot Browser to work correctly. Therefore, Pot Browser will ignore those plug-ins for now! + +## What can I do? + +**In order to fix the issue, let REAPER do a full re-scan of your VST plug-ins!** + +Options ➡ Preferences ➡ Plug-ins ➡ VST ➡ Re-scan... ➡ Clear cache and re-scan VST paths for all plug-ins + +After this, press 🔃 in Pot Browser. Next time you start Pot Browser, this warning should not show up anymore. +"#; + +const UNSUPPORTED_PRESET_FORMAT_TEXT: &str = r#" +Unfortunately, Pot can't automatically open preset formats of Native Instrument's own plug-ins. + +**If you don't want such unsupported presets to show up in Pot Browser, enable the ✔ filter in the toolbar.** + +You have the following options to load the preset anyway. + +## Option A: Load the preset manually + +1. Load the plug-in, e.g. by right-clicking the preset and choosing "Associated products". +2. Then find the preset within the plug-in's user interface. If the plug-in supports drag'n'drop, e.g. Kontakt, you can alternatively reveal the preset file in the file explorer (available in right click menu as well) and drag it onto the plug-in. + +## Option B: Preset crawling + +Try to use Preset Crawler (menu "Tools") to crawl the presets of Native Instrument plug-ins. All presets that have been successfully crawled and imported should be loaded automatically in future. One issue is that crawling only seems to work with the VST2 versions of the NI plug-ins. + +In both cases, you will not be able to take advantage of the preset parameter banks. That's just how it is for now. +"#; + +const PRESET_CRAWLER_IMPORT_OR_DISCARD: &str = + r#"You can now choose to import the crawled presets or discard them!"#; + +fn optional_string(text: Option<&str>) -> &str { + text.unwrap_or("-") +} + +fn os_document_or_reaper_resource_dir() -> Utf8PathBuf { + dirs::document_dir() + .and_then(|dir| Utf8PathBuf::from_path_buf(dir).ok()) + .unwrap_or_else(|| Reaper::get().resource_path()) +} + +fn get_preview_rpp_path( + built_in_path: Option<&Utf8Path>, + output_config: &PreviewOutputConfig, +) -> anyhow::Result { + use anyhow::Context; + let built_in_template_path = + built_in_path.context("Built-in pot preview template not available")?; + match output_config { + PreviewOutputConfig::ForPotBrowserPlayback => Ok(built_in_template_path.to_path_buf()), + PreviewOutputConfig::Export(c) => { + let base_dir_parent = c.base_dir.parent().expect("base dir should have parent"); + let custom_template_path = base_dir_parent.join("pot-preview.RPP"); + if custom_template_path.exists() { + Ok(custom_template_path) + } else { + let custom_template_path_url = Url::from_file_path(&custom_template_path) + .map_err(|_| anyhow!("Couldn't turn base dir parent into URL"))?; + let _ = fs::create_dir_all(base_dir_parent); + fs::copy(built_in_template_path, &custom_template_path)?; + let msg = format!( + "You have chosen to render preset previews for export purposes. In order to give you full control about the audio format and other aspects of the preview rendering process, Pot Browser uses a user-customizable template RPP file to render the previews.\n\ + \n\ + Pot Browser copied the default template (export in OGG format) to the following location:\n\ + \n\ + [{custom_template_path}]({custom_template_path_url})\n\ + \n\ + Please review it and adjust it to your needs. Simply open it in REAPER, modify the render settings as desired, press \"Save Settings\" in the render panel and don't forget to save the project file itself after having made the adjustments. Then come back here again. You need to do this only once!\n\ + \n\ + Oh, and if you have messed things up, simply delete that file and Pot Browser will restore its default template ;)" + ); + Err(anyhow!(msg)) + } + } + } +} + +fn open_link(thing: &str) { + if thing.starts_with("file://") { + reveal_path(Path::new(thing)); + } else { + #[cfg(not(all(target_os = "windows", target_arch = "x86")))] + { + if opener::open_browser(thing).is_err() { + open_link_fallback(thing); + } + } + #[cfg(all(target_os = "windows", target_arch = "x86"))] + { + open_link_fallback(thing); + } + } +} + +fn open_link_fallback(link: &str) { + Reaper::get().show_console_msg(format!( + "Failed to open the following link in your browser. Please open it manually:\n\n{link}\n\n" + )); +} + +fn reveal_path(path: impl AsRef) { + let path = path.as_ref(); + #[cfg(not(all(target_os = "windows", target_arch = "x86")))] + { + if opener::reveal(path).is_err() { + reveal_path_fallback(path); + } + } + #[cfg(all(target_os = "windows", target_arch = "x86"))] + { + reveal_path_fallback(path); + } +} + +fn reveal_path_fallback(path: &Path) { + Reaper::get().show_console_msg( + format!("Failed to open the following path in your file manager. Please open it manually:\n\n{path:?}\n\n") + ); +} diff --git a/plugin-reaper-realearn/pot/Cargo.toml b/plugin-reaper-realearn/pot/Cargo.toml new file mode 100644 index 0000000..c7bb3ae --- /dev/null +++ b/plugin-reaper-realearn/pot/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pot" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +# Own +base.workspace = true +reaper-high.workspace = true +reaper-medium.workspace = true +helgobox-api.workspace = true +rppxml-parser.workspace = true + +# 3rd-party +enumset.workspace = true +itertools.workspace = true +wildmatch.workspace = true +serde.workspace = true +derive_more.workspace = true +tokio.workspace = true +enum-map.workspace = true +once_cell.workspace = true +lexical-sort.workspace = true +strum.workspace = true +splitty.workspace = true +rust-ini.workspace = true +walkdir.workspace = true +either.workspace = true +riff-io.workspace = true +rusqlite = { workspace = true, features = ["bundled"] } +rmp-serde.workspace = true +serde_json.workspace = true +dirs.workspace = true +futures.workspace = true +derivative.workspace = true +tempfile.workspace = true +sanitize-filename.workspace = true +tracing.workspace = true +regex.workspace = true +nanoid.workspace = true +chrono.workspace = true +anyhow.workspace = true +camino.workspace = true + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/pot/src/api.rs b/plugin-reaper-realearn/pot/src/api.rs new file mode 100644 index 0000000..9bccdb6 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/api.rs @@ -0,0 +1,710 @@ +use crate::plugins::ProductKind; +use crate::provider_database::{ + DatabaseId, FIL_HAS_PREVIEW_TRUE, FIL_IS_FAVORITE_TRUE, FIL_IS_USER_PRESET_FALSE, + FIL_IS_USER_PRESET_TRUE, +}; +use crate::{FilterItem, PotPreset}; +use enum_map::EnumMap; +use enumset::EnumSet; +use helgobox_api::persistence::PotFilterKind; +use once_cell::sync::Lazy; +use std::collections::HashSet; + +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use std::fmt; +use std::fmt::{Display, Formatter, Write}; +use std::str::FromStr; +use strum::IntoEnumIterator; + +/// An ID for uniquely identifying a preset along with its corresponding database. +/// +/// This ID is stable only at runtime and only until the database is refreshed. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct PresetId { + pub database_id: DatabaseId, + pub preset_id: InnerPresetId, +} + +impl PresetId { + pub fn new(database_id: DatabaseId, preset_id: InnerPresetId) -> Self { + Self { + database_id, + preset_id, + } + } +} + +/// An ID for uniquely identifying a preset within a certain database. +/// +/// This ID is stable only at runtime and only until the database is refreshed. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)] +pub struct InnerPresetId(pub u32); + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct FilterItemId(pub Option); + +impl FilterItemId { + pub const NONE: Self = Self(None); +} + +/// Filter value. +/// +/// These can be understood as possible types of a filter item kind. Not all types make sense +/// for a particular filter item kind. +/// +/// Many of these types are not suitable for persistence because their values are not stable. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum Fil { + /// A typical integer filter value to refer to a filter item in a specific Komplete database. + /// + /// Makes sense for all filter kinds supported by Komplete. + /// + /// This needs a pot filter item kind and a specific Komplete database to make full sense. + /// The integers are not suited for being persisted because different Komplete scans can yield + /// different integers! So they should only be used at runtime and translated to something + /// more stable for persistence. + Komplete(u32), + /// Refers to a specific pot database. + /// + /// Only makes sense for the pot filter kind "Database". + /// + /// Only valid at runtime, not suitable for persistence. + Database(DatabaseId), + /// Refers to something that can be true of false, e.g. "favorite" or "not favorite" + /// or "available" or "not available". + /// + /// Makes sense for all boolean filter kinds. + /// + /// Suitable for persistence. + Boolean(bool), + /// Refers to a kind of product. + /// + /// Only makes sense for the pot filter kind "Product kind". + ProductKind(ProductKind), + /// Refers to a product. + /// + /// Only makes sense for the pot filter kind "Product". + /// + /// Not suitable for persistence because product IDs are created at runtime. + Product(ProductId), + /// Refers to a project. + /// + /// Only makes sense for the pot filter kind "Project". + /// + /// Not suitable for persistence because project IDs are created at runtime. + Project(ProjectId), +} + +/// Runtime ID for a [`Product`]. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, derive_more::Display)] +pub struct ProductId(pub u32); + +/// Runtime ID for a REAPER project. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, derive_more::Display)] +pub struct ProjectId(pub u32); + +pub type FilterItemCollections = GenericFilterItemCollections; + +#[derive(Debug)] +pub struct GenericFilterItemCollections(EnumMap>); + +pub trait HasFilterItemId { + fn id(&self) -> FilterItemId; +} + +impl HasFilterItemId for FilterItem { + fn id(&self) -> FilterItemId { + self.id + } +} + +impl Default for GenericFilterItemCollections { + fn default() -> Self { + Self(enum_map::enum_map! { _ => vec![] }) + } +} + +impl GenericFilterItemCollections { + pub fn empty() -> Self { + Default::default() + } + + pub fn get(&self, kind: PotFilterKind) -> &[T] { + &self.0[kind] + } + + pub fn iter_mut(&mut self) -> impl Iterator)> { + self.0.iter_mut() + } + + pub fn set(&mut self, kind: PotFilterKind, items: Vec) { + self.0[kind] = items; + } + + pub fn extend(&mut self, kind: PotFilterKind, items: impl Iterator) { + self.0[kind].extend(items); + } + + pub fn are_filled_already(&self) -> bool { + // Just take any of of the constant filters that should be filled. + !self.get(PotFilterKind::IsFavorite).is_empty() + } +} + +impl IntoIterator for GenericFilterItemCollections { + type Item = (PotFilterKind, Vec); + type IntoIter = > as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl GenericFilterItemCollections { + pub fn narrow_down(&mut self, kind: PotFilterKind, includes: &NonCryptoHashSet) { + self.0[kind].retain(|item| includes.contains(&item.id())) + } +} + +/// `Some` means a filter is set (can also be the `` filter). +/// `None` means no filter is set (``). +pub type OptFilter = Option; + +#[derive(Copy, Clone, Debug, Default)] +pub struct Filters(EnumMap); + +impl Filters { + pub fn empty() -> Self { + Self::default() + } + + pub fn wants_preview(&self) -> Option { + if let Some(FilterItemId(Some(fil))) = self.get(PotFilterKind::HasPreview) { + Some(fil == FIL_HAS_PREVIEW_TRUE) + } else { + None + } + } + + pub fn database_matches(&self, db_id: DatabaseId) -> bool { + self.matches(PotFilterKind::Database, Fil::Database(db_id)) + } + + pub fn wants_user_presets_only(&self) -> bool { + self.wants_only(PotFilterKind::IsUser, FIL_IS_USER_PRESET_TRUE) + } + + pub fn wants_factory_presets_only(&self) -> bool { + self.wants_only(PotFilterKind::IsUser, FIL_IS_USER_PRESET_FALSE) + } + + pub fn wants_favorites_only(&self) -> bool { + self.wants_only(PotFilterKind::IsFavorite, FIL_IS_FAVORITE_TRUE) + } + + pub fn any_unsupported_filter_is_set_to_concrete_value( + &self, + supported_advanced_kinds: EnumSet, + ) -> bool { + let supported_kinds = supported_advanced_kinds.union(PotFilterKind::core_kinds()); + supported_kinds + .complement() + .iter() + .any(|k| self.is_set_to_concrete_value(k)) + } + + /// To be used with filter kinds where `` is **not** a valid filter value. In this case, + /// is considered an invalid value and it never matches (no reason to panic but almost). + pub fn matches(&self, kind: PotFilterKind, fil: Fil) -> bool { + match self.get(kind) { + None => true, + Some(FilterItemId(None)) => false, + Some(FilterItemId(Some(wanted_fil))) => fil == wanted_fil, + } + } + + pub fn favorite_matches( + &self, + favorites: &NonCryptoHashSet, + preset_id: InnerPresetId, + ) -> bool { + match self.get(PotFilterKind::IsFavorite) { + None => true, + Some(FilterItemId(None)) => false, + Some(FilterItemId(Some(fil))) => { + if fil == FIL_IS_FAVORITE_TRUE { + favorites.contains(&preset_id) + } else { + !favorites.contains(&preset_id) + } + } + } + } + + /// To be used with filter kinds where `` is a valid filter value. + pub fn matches_optional(&self, kind: PotFilterKind, fil: Option) -> bool { + match self.get(kind) { + // + None => true, + // or a specific value + Some(FilterItemId(wanted_fil)) => fil == wanted_fil, + } + } + + fn wants_only(&self, kind: PotFilterKind, fil: Fil) -> bool { + self.get(kind) == Some(FilterItemId(Some(fil))) + } + + /// Returns `false` if set to `None` + pub fn is_set_to_concrete_value(&self, kind: PotFilterKind) -> bool { + matches!(self.0[kind], Some(FilterItemId(Some(_)))) + } + + pub fn get(&self, kind: PotFilterKind) -> OptFilter { + self.0[kind] + } + + pub fn get_ref(&self, kind: PotFilterKind) -> &OptFilter { + &self.0[kind] + } + + pub fn set(&mut self, kind: PotFilterKind, value: OptFilter) { + self.0[kind] = value; + } + + pub fn effective_sub_bank(&self) -> &OptFilter { + self.effective_sub_item(PotFilterKind::Bank, PotFilterKind::SubBank) + } + + pub fn clear_excluded_ones(&mut self, exclude_list: &PotFilterExcludes) { + for kind in PotFilterKind::iter() { + if let Some(id) = self.0[kind] { + if exclude_list.contains(kind, id) { + self.0[kind] = None; + } + } + } + } + + pub fn clear_if_not_available_anymore( + &mut self, + affected_kinds: EnumSet, + collections: &FilterItemCollections, + ) { + for kind in affected_kinds { + if let Some(id) = self.0[kind] { + let valid_items = collections.get(kind); + if !valid_items.iter().any(|item| item.id == id) { + self.0[kind] = None; + } + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.0.into_iter() + } + + pub fn effective_sub_category(&self) -> &OptFilter { + self.effective_sub_item(PotFilterKind::Category, PotFilterKind::SubCategory) + } + + pub fn clear_this_and_dependent_filters(&mut self, kind: PotFilterKind) { + self.set(kind, None); + for dependent_kind in kind.dependent_kinds() { + self.set(dependent_kind, None); + } + } + + fn effective_sub_item( + &self, + parent_kind: PotFilterKind, + sub_kind: PotFilterKind, + ) -> &OptFilter { + let category = &self.0[parent_kind]; + if category == &Some(FilterItemId::NONE) { + category + } else { + &self.0[sub_kind] + } + } +} + +#[derive(Debug, Default)] +pub struct PotFavorites { + favorites: NonCryptoHashMap>, +} + +impl PotFavorites { + pub fn is_favorite(&self, preset_id: PresetId) -> bool { + if let Some(db_favorites) = self.favorites.get(&preset_id.database_id) { + db_favorites.contains(&preset_id.preset_id) + } else { + false + } + } + + pub fn toggle_favorite(&mut self, preset_id: PresetId) { + let db_favorites = self.favorites.entry(preset_id.database_id).or_default(); + if db_favorites.contains(&preset_id.preset_id) { + db_favorites.remove(&preset_id.preset_id); + } else { + db_favorites.insert(preset_id.preset_id); + } + } + + pub fn db_favorites(&self, db_id: DatabaseId) -> &NonCryptoHashSet { + static EMPTY_HASH_SET: Lazy> = Lazy::new(HashSet::default); + self.favorites.get(&db_id).unwrap_or(&EMPTY_HASH_SET) + } +} + +#[derive(Clone, Debug, Default)] +pub struct PotFilterExcludes { + exluded_items: EnumMap>, +} + +impl PotFilterExcludes { + pub fn contains(&self, kind: PotFilterKind, id: FilterItemId) -> bool { + self.exluded_items[kind].contains(&id) + } + + pub fn remove(&mut self, kind: PotFilterKind, id: FilterItemId) { + self.exluded_items[kind].remove(&id); + } + + pub fn add(&mut self, kind: PotFilterKind, id: FilterItemId) { + self.exluded_items[kind].insert(id); + } + + pub fn is_empty(&self, kind: PotFilterKind) -> bool { + self.exluded_items[kind].is_empty() + } + + pub fn contains_database(&self, db_id: DatabaseId) -> bool { + self.contains( + PotFilterKind::Database, + FilterItemId(Some(Fil::Database(db_id))), + ) + } + + pub fn contains_product(&self, product_id: Option) -> bool { + self.contains( + PotFilterKind::Bank, + FilterItemId(product_id.map(Fil::Product)), + ) + } + + pub fn normal_excludes_by_kind(&self, kind: PotFilterKind) -> impl Iterator + '_ { + self.exluded_items[kind] + .iter() + .filter_map(|id| id.0.as_ref()) + } + + pub fn contains_none(&self, kind: PotFilterKind) -> bool { + self.exluded_items[kind].contains(&FilterItemId::NONE) + } +} + +#[derive(Debug)] +pub struct CurrentPreset { + pub preset: PotPreset, + pub macro_param_banks: Vec, +} + +#[derive(Debug)] +pub struct MacroParamBank { + params: Vec, +} + +impl MacroParamBank { + pub fn new(params: Vec) -> Self { + Self { params } + } + + pub fn name(&self) -> String { + let mut name = String::with_capacity(32); + for p in &self.params { + if let Some(section) = &p.section { + name += " / "; + name += section; + } + } + name + } + + /// Returns the index of the section to which the parameter in the given slot belongs. + pub fn resolve_param_section_index(&self, slot_index: u32) -> Option { + self.params[0..=slot_index as usize] + .iter() + .filter(|param| param.section.is_some()) + .enumerate() + .map(|(i, _)| i as u32) + .last() + } + + /// Returns the section to which the parameter in the given slot belongs. + /// + /// The parameter itself only carries its section name if it's the beginning of a new section. + pub fn resolve_param_section(&self, slot_index: u32) -> Option<&str> { + // Search from right to left + self.params[0..=slot_index as usize] + .iter() + .rev() + .find_map(|param| Some(param.section.as_ref()?.as_str())) + } + + pub fn params(&self) -> &[MacroParam] { + &self.params + } + + pub fn params_mut(&mut self) -> &mut [MacroParam] { + &mut self.params + } + + pub fn find_macro_param_at(&self, slot_index: u32) -> Option<&MacroParam> { + self.params.get(slot_index as usize) + } + + /// Returns slot index and param. + pub fn find_first_param_with_fx_param_index(&self, index: u32) -> Option<(u32, &MacroParam)> { + self.params.iter().enumerate().find_map(|(i, p)| { + let resolved_index = p.fx_param?.resolved_param_index?; + if index == resolved_index { + Some((i as u32, p)) + } else { + None + } + }) + } + + pub fn param_count(&self) -> u32 { + self.params.len() as _ + } +} + +#[derive(Clone, Debug)] +pub struct MacroParam { + pub name: String, + /// The parameter itself only carries its section name if it's the beginning of a new section. + pub section: Option, + pub fx_param: Option, +} + +#[derive(Copy, Clone, Debug)] +pub struct PotFxParam { + pub param_id: PotFxParamId, + /// Only resolved on demand (when the FX is available). + pub resolved_param_index: Option, +} + +#[derive(Copy, Clone, Debug, derive_more::Display)] +pub enum PotFxParamId { + /// Positional/index ID. + /// + /// Some plug-in standards such as VST2 only support positional IDs. + Index(u32), + /// Real ID, independent of the position. + /// + /// Supported by VST3 plug-ins. + Id(u32), +} + +impl CurrentPreset { + pub fn preset(&self) -> &PotPreset { + &self.preset + } + + pub fn find_macro_param_bank_at(&self, bank_index: u32) -> Option<&MacroParamBank> { + self.macro_param_banks.get(bank_index as usize) + } + + /// Finds macro parameters across all banks, assuming a bank size of exactly 8. + pub fn find_macro_param_at(&self, slot_index: u32) -> Option<&MacroParam> { + let bank_index = slot_index / 8; + let bank_slot_index = slot_index % 8; + self.find_bank_macro_param_at(bank_index, bank_slot_index) + } + + pub fn find_bank_macro_param_at( + &self, + bank_index: u32, + bank_slot_index: u32, + ) -> Option<&MacroParam> { + self.macro_param_banks + .get(bank_index as usize)? + .find_macro_param_at(bank_slot_index) + } + + /// Returns bank, slot index and param. + pub fn find_first_macro_param_with_fx_param_index( + &self, + index: u32, + ) -> Option<(&MacroParamBank, u32, &MacroParam)> { + self.macro_param_banks.iter().find_map(|b| { + let (i, p) = b.find_first_param_with_fx_param_index(index)?; + Some((b, i, p)) + }) + } + + pub fn macro_param_bank_count(&self) -> u32 { + self.macro_param_banks.len() as _ + } + + pub fn has_params(&self) -> bool { + !self.macro_param_banks.is_empty() + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct PersistentDatabaseId(String); + +impl PersistentDatabaseId { + pub fn random() -> Self { + Self(nanoid::nanoid!()) + } + + pub const fn new(raw_id: String) -> Self { + PersistentDatabaseId(raw_id) + } + + pub fn get(&self) -> &str { + &self.0 + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct PersistentInnerPresetId(String); + +impl PersistentInnerPresetId { + pub fn new(raw_id: String) -> Self { + Self(raw_id) + } + + pub fn get(&self) -> &str { + &self.0 + } +} + +/// A preset ID that survives restarts and rescans. +/// +/// It's more expensive to clone, hash etc. than [`PresetId`]. That's why we should only use it +/// for purposes where persistence matters, e.g. when saving favorites, currently selected preset +/// or for associating preview files. +/// +/// The schema is `|`. The pipe character `|` can also be used within +/// the inner preset ID, so in order to extract the database ID, it's important to split at the +/// first pipe character and ignore the other ones. In general, it should be avoided to use +/// the pipe character in the database ID or inner preset ID, but it's still important to escape it. +/// +/// # Examples +/// +/// - `defaults|vst2|1967946098` +/// - `track-templates|Synths/Lead.RTrackTemplate` +/// - `fx-chains|Synths/Sun.RfxChain` +/// - `fx-presets|vst3-Surge XT.ini|My Preset` +/// - `komplete|77c5507f5d0b421ea93eeb4cee4b6f99` +/// - `n98h1f9unp92|maojiao/2023-02-03-ben/2023-02-03-ben.RPP|0FF9F738-7CF6-8A49-9AEA-A9AF26DF9C46` +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct PersistentPresetId { + db_id: PersistentDatabaseId, + inner_preset_id: PersistentInnerPresetId, +} + +impl PersistentPresetId { + pub fn new(db_id: PersistentDatabaseId, inner_preset_id: PersistentInnerPresetId) -> Self { + Self { + db_id, + inner_preset_id, + } + } +} + +impl Display for PersistentPresetId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // Database IDs ideally shouldn't contain pipe characters, but if they do, we escaped them. + let escaped_db_id = PipeEscaped(self.db_id.get()); + write!(f, "{escaped_db_id}|{}", self.inner_preset_id.get()) + } +} + +impl FromStr for PersistentPresetId { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let (escaped_db_id, inner_preset_id) = s + .split_once(unescaped_pipe_pattern()) + .ok_or("no | separator found in persistent preset ID")?; + // Unescaped pipe character (in the unlikely case that there was any) + let db_id = unescape_pipes(escaped_db_id); + let id = Self { + db_id: PersistentDatabaseId(db_id), + inner_preset_id: PersistentInnerPresetId(inner_preset_id.to_string()), + }; + Ok(id) + } +} + +pub fn unescaped_pipe_pattern() -> impl FnMut(char) -> bool { + unescaped_char_pattern('|') +} + +/// A Rust string matching pattern that matches the given character, but only if it's not preceded +/// by a backslash. +fn unescaped_char_pattern(needle: char) -> impl FnMut(char) -> bool { + let mut prev_char = None; + move |c: char| { + let matches = if c == needle { + prev_char != Some('\\') + } else { + false + }; + prev_char = Some(c); + matches + } +} + +/// Converts "hello\|fellow" to "hello|fellow" +pub fn unescape_pipes(escaped: &str) -> String { + escaped.replace(r"\|", "|") +} + +pub struct PipeEscaped<'a>(pub &'a str); + +impl Display for PipeEscaped<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for c in self.0.chars() { + if c == '|' { + f.write_str(r"\|")?; + } else { + f.write_char(c)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::{PersistentDatabaseId, PersistentInnerPresetId, PersistentPresetId}; + + #[test] + fn format_persistent_preset_id() { + let id = PersistentPresetId::new( + PersistentDatabaseId::new("test|hello".into()), + PersistentInnerPresetId::new("vst2|124135".into()), + ); + assert_eq!(id.to_string(), r#"test\|hello|vst2|124135"#); + } + + #[test] + fn parse_persistent_preset_id() { + let expression = r#"test\|hello|vst2|124135"#; + let parsed_id: PersistentPresetId = expression.parse().unwrap(); + let expected_id = PersistentPresetId::new( + PersistentDatabaseId::new("test|hello".into()), + PersistentInnerPresetId::new("vst2|124135".into()), + ); + assert_eq!(parsed_id, expected_id); + } +} diff --git a/plugin-reaper-realearn/pot/src/escape_catcher.rs b/plugin-reaper-realearn/pot/src/escape_catcher.rs new file mode 100644 index 0000000..0f48d1c --- /dev/null +++ b/plugin-reaper-realearn/pot/src/escape_catcher.rs @@ -0,0 +1,65 @@ +use reaper_high::Reaper; +use reaper_medium::{ + AccelMsgKind, AcceleratorBehavior, AcceleratorKeyCode, AcceleratorPosition, RegistrationHandle, + TranslateAccel, TranslateAccelArgs, TranslateAccelResult, +}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +pub struct EscapeCatcher { + accel_handle: RegistrationHandle, + escape_was_pressed: Arc, +} + +impl TranslateAccel for EscapeCatcherAccel { + fn call(&mut self, args: TranslateAccelArgs) -> TranslateAccelResult { + let is_escape = args.msg.behavior().contains(AcceleratorBehavior::VirtKey) + && args.msg.message() == AccelMsgKind::KeyDown + && args.msg.key() == AcceleratorKeyCode::new(0x1b); + if !is_escape { + return TranslateAccelResult::NotOurWindow; + } + self.escape_was_pressed.store(true, Ordering::Relaxed); + TranslateAccelResult::Eat + } +} + +struct EscapeCatcherAccel { + escape_was_pressed: Arc, +} + +impl Default for EscapeCatcher { + fn default() -> Self { + Self::new() + } +} + +impl EscapeCatcher { + pub fn new() -> Self { + let escape_was_pressed = Arc::new(AtomicBool::new(false)); + let accel = EscapeCatcherAccel { + escape_was_pressed: escape_was_pressed.clone(), + }; + let accel_handle = Reaper::get() + .medium_session() + .plugin_register_add_accelerator_register(Box::new(accel), AcceleratorPosition::Front) + .expect("couldn't register escape accelerator"); + Self { + accel_handle, + escape_was_pressed, + } + } + + pub fn escape_was_pressed(&self) -> bool { + self.escape_was_pressed.load(Ordering::Relaxed) + } +} + +impl Drop for EscapeCatcher { + fn drop(&mut self) { + Reaper::get() + .medium_session() + .plugin_register_remove_accelerator(self.accel_handle) + .expect("escape accelerator was not registered"); + } +} diff --git a/plugin-reaper-realearn/pot/src/lib.rs b/plugin-reaper-realearn/pot/src/lib.rs new file mode 100644 index 0000000..8f2fa64 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/lib.rs @@ -0,0 +1,1999 @@ +#![allow(clippy::single_match)] + +//! "Pot" is intended to be an abstraction over different preset databases. At the moment it only +//! supports Komplete/NKS. As soon as other database backends are supported, we need to add a few +//! abstractions. Care should be taken to not persist anything that's very specific to a particular +//! database backend. Or at least that existing persistent state can easily migrated to a future +//! state that has support for multiple database backends. + +use base::{blocking_lock, blocking_lock_arc, blocking_write_lock, hash_util, SoundPlayer}; + +use enumset::EnumSet; +use helgobox_api::persistence::PotFilterKind; +use reaper_high::{Chunk, Fx, FxChain, GroupingBehavior, Project, Reaper, Track}; +use reaper_medium::{ + reaper_str, FxPresetRef, GangBehavior, InputMonitoringMode, InsertMediaMode, + MasterTrackBehavior, ParamId, ReaperNormalizedFxParamValue, ReaperVolumeValue, RecordingInput, +}; +use std::borrow::Cow; +use std::cell::{Ref, RefMut}; +use std::error::Error; +use std::ffi::CString; +use std::fs; +use std::ops::Range; + +use anyhow::Context; +use camino::{Utf8Path, Utf8PathBuf}; +use chrono::NaiveDateTime; +use itertools::Itertools; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::{Duration, Instant}; +use wildmatch::WildMatch; + +mod api; +pub use api::*; +mod nks; +mod pot_database; +use crate::providers::komplete::NksFile; +pub use pot_database::*; + +mod plugin_id; +mod plugins; +use crate::plugins::{PluginCommon, PluginCore}; +use crate::provider_database::{DatabaseId, FIL_IS_AVAILABLE_FALSE, FIL_IS_AVAILABLE_TRUE}; +pub use plugin_id::*; + +mod provider_database; +pub mod providers; +mod worker; +pub use worker::*; +mod escape_catcher; +pub mod preset_crawler; +pub mod preview_recorder; +use crate::preset_crawler::get_shim_file_path; +use crate::preview_recorder::get_preview_file_path_from_hash; +use base::hash_util::{NonCryptoHashSet, NonCryptoIndexSet, PersistentHash}; +pub use escape_catcher::*; + +// - We have a global list of databases +// - A pot unit doesn't own those databases but it will access them. +// - That means for sure that this list of databases is *shared*. +// - Those databases can and will contain mutable state. Most queries are read-only, but there +// will be something like rescan() that modifies its state. And in future maybe also write access. +// - That means we need to have a Mutex or RWLock. +// - Question is if we need this RWLock around each database or around the complete list. +// - Having it around the list will probably be necessary in future in order to add more databases +// at runtime. +// - But having it (maybe additionally in future) around each database makes it possible to +// query multiple databases in parallel, also from different pot units. + +pub type SharedRuntimePotUnit = Arc>; + +#[derive(Debug)] +pub enum PotUnit { + Unloaded { + state: PersistentState, + previous_load_error: &'static str, + }, + Loaded(SharedRuntimePotUnit), +} + +impl Default for PotUnit { + fn default() -> Self { + Self::unloaded(Default::default()) + } +} + +impl PotUnit { + pub fn unloaded(state: PersistentState) -> Self { + Self::Unloaded { + state, + previous_load_error: "", + } + } + + pub fn loaded( + &mut self, + integration: BoxedPotIntegration, + ) -> Result { + match self { + PotUnit::Unloaded { + state, + previous_load_error, + } => { + if !previous_load_error.is_empty() { + return Err(previous_load_error); + } + match RuntimePotUnit::load(state, integration) { + Ok(u) => { + *self = Self::Loaded(u.clone()); + Ok(u) + } + Err(e) => { + *previous_load_error = e; + Err(e) + } + } + } + PotUnit::Loaded(p) => Ok(p.clone()), + } + } + + pub fn persistent_state(&self) -> PersistentState { + match self { + PotUnit::Unloaded { state, .. } => state.clone(), + PotUnit::Loaded(u) => { + blocking_lock(u, "PotUnit from persistence_state").persistent_state() + } + } + } +} + +/// This trait is not very good in terms of signatures. It's just a minimum-effort solution to get +/// inversion of control (in order to not let Pot depend on ReaLearn's main code). +pub trait PotIntegration { + // TODO-high-pot This will probably look different as soon as we seriously implement favorites. + fn favorites(&self) -> &RwLock; + fn set_current_fx_preset(&self, fx: Fx, preset: CurrentPreset); + fn exclude_list(&self) -> Ref; + fn exclude_list_mut(&self) -> RefMut; + fn notify_preset_changed(&self, id: Option); + fn notify_filter_changed(&self, kind: PotFilterKind, filter: OptFilter); + fn notify_indexes_rebuilt(&self); + /// Returns an FX instance which must not be removed (e.g. in the process of loading a preset). + /// + /// This should be the FX holding the ReaLearn instance which controls the pot. + fn protected_fx(&self) -> &Fx; +} + +#[derive(derivative::Derivative)] +#[derivative(Debug)] +pub struct RuntimePotUnit { + pub runtime_state: RuntimeState, + pub filter_item_collections: FilterItemCollections, + pub supported_filter_kinds: EnumSet, + pub preset_collection: PresetCollection, + pub wasted_runs: u32, + pub wasted_duration: Duration, + pub stats: Stats, + build_counter: u64, + sound_player: SoundPlayer, + preview_volume: ReaperVolumeValue, + pub default_load_preset_window_behavior: LoadPresetWindowBehavior, + pub destination_descriptor: DestinationDescriptor, + pub name_track_after_preset: bool, + show_excluded_filter_items: bool, + running_background_task: Option, + #[derivative(Debug = "ignore")] + integration: BoxedPotIntegration, +} + +#[derive(Debug)] +struct RunningBackgroundTask { + start_time: Instant, + change_hint: ChangeHint, +} + +impl RunningBackgroundTask { + pub fn new(change_hint: ChangeHint) -> Self { + Self { + start_time: Instant::now(), + change_hint, + } + } +} + +pub type BoxedPotIntegration = Box; + +#[derive(Copy, Clone, Debug, Default)] +pub struct DestinationDescriptor { + pub track: DestinationTrackDescriptor, + pub fx_index: u32, +} + +#[derive(Copy, Clone, Debug, Default)] +pub enum DestinationTrackDescriptor { + #[default] + SelectedTrack, + MasterTrack, + Track(u32), +} + +impl DestinationTrackDescriptor { + pub fn resolve(&self, project: Project) -> Result { + let track = match self { + DestinationTrackDescriptor::SelectedTrack => project + .first_selected_track(MasterTrackBehavior::IncludeMasterTrack) + .ok_or("No track selected")?, + DestinationTrackDescriptor::MasterTrack => project + .master_track() + .map_err(|_| "Couldn't get master track")?, + DestinationTrackDescriptor::Track(i) => project + .track_by_index(*i) + .ok_or("No track at that position")?, + }; + Ok(track) + } + + pub fn is_dynamic(&self) -> bool { + matches!(self, Self::SelectedTrack) + } +} + +pub enum DestinationInstruction { + Existing(Destination), + AddTrack, +} + +impl DestinationInstruction { + pub fn get_existing(&self) -> Option<&Destination> { + match self { + DestinationInstruction::Existing(d) => Some(d), + DestinationInstruction::AddTrack => None, + } + } +} + +impl DestinationDescriptor { + pub fn resolve_destination(&self) -> Result { + let project = Reaper::get().current_project(); + if let Ok(track) = self.track.resolve(project) { + let dest = Destination { + chain: track.normal_fx_chain(), + fx_index: self.fx_index, + }; + Ok(DestinationInstruction::Existing(dest)) + } else { + Ok(DestinationInstruction::AddTrack) + } + } +} + +#[derive(Debug, Default)] +pub struct Stats { + pub refresh_duration: Duration, + pub filter_query_duration: Duration, + pub preset_query_duration: Duration, + pub preview_filter_duration: Duration, + pub sort_duration: Duration, + pub index_duration: Duration, +} + +impl Stats { + pub fn total_query_duration(&self) -> Duration { + self.refresh_duration + + self.filter_query_duration + + self.preset_query_duration + + self.preview_filter_duration + + self.sort_duration + + self.index_duration + } +} + +#[derive(Clone)] +pub struct BuildInput { + pub filters: Filters, + pub filter_excludes: PotFilterExcludes, + pub search_evaluator: SearchEvaluator, +} + +#[derive(Copy, Clone)] +pub struct InnerBuildInput<'a> { + pub filter_input: FilterInput<'a>, + pub search_evaluator: &'a SearchEvaluator, +} + +impl<'a> InnerBuildInput<'a> { + pub fn new( + input: &'a BuildInput, + favorites: &'a PotFavorites, + db_id: DatabaseId, + ) -> InnerBuildInput<'a> { + InnerBuildInput { + filter_input: FilterInput { + filters: &input.filters, + excludes: &input.filter_excludes, + db_favorites: favorites.db_favorites(db_id), + }, + search_evaluator: &input.search_evaluator, + } + } +} + +#[derive(Copy, Clone)] +pub struct FilterInput<'a> { + pub filters: &'a Filters, + pub excludes: &'a PotFilterExcludes, + pub db_favorites: &'a NonCryptoHashSet, +} + +impl<'a> FilterInput<'a> { + /// This is a useful method for databases that filter in-memory and map their presets to + /// existing scanned REAPER plug-ins. The following kinds of filters are checked: + /// + /// - Availability + /// - Support (never matches if showing only unsupported presets) + /// - Product kind + /// - Product (also makes sure it's not excluded) + /// - Favorite + pub fn everything_matches( + &self, + plugin: Option<&PluginCore>, + preset_id: InnerPresetId, + ) -> bool { + let availability_matches = || { + let fil = if plugin.is_some() { + FIL_IS_AVAILABLE_TRUE + } else { + FIL_IS_AVAILABLE_FALSE + }; + self.filters.matches(PotFilterKind::IsAvailable, fil) + }; + let support_matches = || { + self.filters.get(PotFilterKind::IsSupported) + != Some(FilterItemId(Some(FIL_IS_AVAILABLE_FALSE))) + }; + let product_kind_matches = || { + // If we don't have plug-in info, we also don't have product kind info. + let fil = plugin.and_then(|p| p.product_kind).map(Fil::ProductKind); + self.filters + .matches_optional(PotFilterKind::ProductKind, fil) + }; + let product_matches = || { + // If we don't have plug-in info, we also don't have product info. + let fil = plugin.map(|p| Fil::Product(p.product_id)); + self.filters.matches_optional(PotFilterKind::Bank, fil) + }; + let product_is_included = || { + // If we don't have plug-in, we *can* be excluded via product ... if is excluded. + let product_id = plugin.map(|p| p.product_id); + !self.excludes.contains_product(product_id) + }; + let favorite_matches = || self.filters.favorite_matches(self.db_favorites, preset_id); + // Combine + availability_matches() + && support_matches() + && product_kind_matches() + && product_matches() + && favorite_matches() + && product_is_included() + } + + pub fn with_filters(&self, filters: &'a Filters) -> Self { + Self { + filters, + excludes: self.excludes, + db_favorites: self.db_favorites, + } + } +} + +#[derive(Clone, Default)] +pub struct SearchEvaluator { + processed_search_expression: String, + options: SearchOptions, + wild_match: Option, +} + +#[derive(Clone, Debug)] +pub struct SearchOptions { + pub use_wildcards: bool, + pub search_fields: EnumSet, +} + +impl Default for SearchOptions { + fn default() -> Self { + Self { + use_wildcards: false, + search_fields: EnumSet::all(), + } + } +} + +#[derive(Debug, enumset::EnumSetType, strum::AsRefStr, strum::EnumIter)] +pub enum SearchField { + #[strum(serialize = "Name")] + PresetName, + #[strum(serialize = "Plug-in/product")] + ProductName, + #[strum(serialize = "Extension")] + FileExtension, +} + +pub trait SearchInput { + fn preset_name(&self) -> &str; + fn product_name(&self) -> Option>; + fn file_extension(&self) -> Option<&str>; +} + +impl SearchEvaluator { + pub fn new(raw_search_expression: &str, options: SearchOptions) -> Self { + let processed_search_expression = raw_search_expression.trim().to_lowercase(); + Self { + wild_match: if options.use_wildcards { + Some(WildMatch::new(&processed_search_expression)) + } else { + None + }, + processed_search_expression, + options, + } + } + + pub fn processed_search_expression(&self) -> &str { + &self.processed_search_expression + } + + pub fn options(&self) -> &SearchOptions { + &self.options + } + + pub fn use_wildcards(&self) -> bool { + self.wild_match.is_some() + } + + pub fn matches(&self, input: impl SearchInput) -> bool { + if self.processed_search_expression.is_empty() { + return true; + } + self.options + .search_fields + .iter() + .any(|search_field| match search_field { + SearchField::PresetName => self.matches_internal(input.preset_name()), + SearchField::ProductName => { + if let Some(product_name) = input.product_name() { + self.matches_internal(&product_name) + } else { + false + } + } + SearchField::FileExtension => { + if let Some(ext) = input.file_extension() { + self.matches_internal(ext) + } else { + false + } + } + }) + } + + fn matches_internal(&self, text: &str) -> bool { + let lowercase_text = text.to_lowercase(); + match &self.wild_match { + None => lowercase_text.contains(&self.processed_search_expression), + Some(wild_match) => wild_match.matches(&lowercase_text), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ChangeHint { + /// Will refresh databases. Potentially expensive operation! + TotalRefresh, + Normal, + Filter(PotFilterKind), + SearchExpression, +} + +impl ChangeHint { + pub fn affected_kinds(&self) -> EnumSet { + use ChangeHint as H; + match self { + H::Normal | H::TotalRefresh => EnumSet::all(), + H::SearchExpression => EnumSet::empty(), + H::Filter(changed_kind) => changed_kind.dependent_kinds().collect(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct RuntimeState { + pub filters: Filters, + pub search_expression: String, + pub search_options: SearchOptions, + preset_id: Option, +} + +impl RuntimeState { + pub fn load(_persistent_state: &PersistentState) -> Result { + // with_preset_db(|db| { + // let filter_exclude_list = BackboneState::get().pot_filter_exclude_list(); + // let collections = db.build_filter_items( + // &Default::default(), + // EnumSet::all().into_iter(), + // &filter_exclude_list, + // ); + // let filter_settings = { + // let nks = &persistent_state.filter_settings.nks; + // let mut filters = Filters::empty(); + // let mut set_filter = |setting: &Option, kind: PotFilterItemKind| { + // let id = setting.as_ref().and_then(|persistent_id| { + // let item = collections + // .get(kind) + // .iter() + // .find(|item| &item.persistent_id == persistent_id)?; + // Some(item.id) + // }); + // filters.set(kind, id); + // }; + // set_filter(&nks.bank, PotFilterItemKind::NksBank); + // set_filter(&nks.sub_bank, PotFilterItemKind::NksSubBank); + // set_filter(&nks.category, PotFilterItemKind::NksCategory); + // set_filter(&nks.sub_category, PotFilterItemKind::NksSubCategory); + // set_filter(&nks.mode, PotFilterItemKind::NksMode); + // FilterSettings { nks: filters } + // }; + // let preset_id = persistent_state + // .preset_id + // .as_ref() + // .and_then(|persistent_id| db.find_preset_id_by_favorite_id(persistent_id)); + // Self { + // filter_settings, + // search_expression: "".to_string(), + // use_wildcard_search: false, + // preset_id, + // } + // }) + let state = Self { + filters: Default::default(), + search_expression: "".to_string(), + search_options: Default::default(), + preset_id: None, + }; + Ok(state) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct PersistentState { + filter_settings: PersistentFilterSettings, + preset_id: Option, +} + +type PresetCollection = NonCryptoIndexSet; + +#[derive(Clone, Eq, PartialEq, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct PersistentFilterSettings { + pub nks: nks::PersistentNksFilterSettings, +} + +impl RuntimePotUnit { + pub fn load( + state: &PersistentState, + integration: BoxedPotIntegration, + ) -> Result { + let sound_player = SoundPlayer::new(); + let unit = Self { + runtime_state: RuntimeState::load(state)?, + filter_item_collections: Default::default(), + supported_filter_kinds: Default::default(), + preset_collection: Default::default(), + wasted_runs: 0, + wasted_duration: Default::default(), + stats: Default::default(), + build_counter: 0, + preview_volume: sound_player.volume().unwrap_or_default(), + sound_player, + destination_descriptor: Default::default(), + name_track_after_preset: true, + show_excluded_filter_items: false, + running_background_task: None, + integration, + default_load_preset_window_behavior: Default::default(), + }; + let shared_unit = Arc::new(Mutex::new(unit)); + blocking_lock_arc(&shared_unit, "PotUnit from load").rebuild_collections( + shared_unit.clone(), + ChangeHint::TotalRefresh, + Debounce::No, + ); + Ok(shared_unit) + } + + pub fn preview_volume(&self) -> ReaperVolumeValue { + self.preview_volume + } + + pub fn protected_fx(&self) -> &Fx { + self.integration.protected_fx() + } + + pub fn set_preview_volume(&mut self, volume: ReaperVolumeValue) { + self.preview_volume = volume; + self.sound_player + .set_volume(volume) + .expect("changing preview volume value failed"); + } + + pub fn persistent_state(&self) -> PersistentState { + // let nks_settings = &self.runtime_state.filter_settings.nks; + // let nks_items = &self.collections.filter_item_collections.nks; + // let find_id = |kind: PotFilterItemKind| { + // nks_settings.get(kind).and_then(|id| { + // nks_items + // .get(kind) + // .iter() + // .find(|item| item.id == id) + // .map(|item| item.persistent_id.clone()) + // }) + // }; + // let filter_settings = PersistentFilterSettings { + // nks: PersistentNksFilterSettings { + // bank: find_id(PotFilterItemKind::NksBank), + // sub_bank: find_id(PotFilterItemKind::NksSubBank), + // category: find_id(PotFilterItemKind::NksCategory), + // sub_category: find_id(PotFilterItemKind::NksSubCategory), + // mode: find_id(PotFilterItemKind::NksMode), + // }, + // }; + // let preset_id = self.runtime_state.preset_id.and_then(|id| { + // with_preset_db(|db| Some(db.find_preset_by_id(id)?.favorite_id)) + // .ok() + // .flatten() + // }); + // PersistentState { + // filter_settings, + // preset_id, + // } + PersistentState::default() + } + + pub fn toggle_favorite(&mut self, preset_id: PresetId, shared_self: SharedRuntimePotUnit) { + let favorites = self.integration.favorites(); + blocking_write_lock(favorites, "favorite toggle").toggle_favorite(preset_id); + self.rebuild_collections( + shared_self, + ChangeHint::Filter(PotFilterKind::IsFavorite), + Debounce::No, + ); + } + + pub fn play_preview(&mut self, preset_id: PresetId) -> anyhow::Result<()> { + let preset = pot_db() + .find_preset_by_id(preset_id) + .context("couldn't find preset")?; + let reaper_resource_dir = Reaper::get().resource_path(); + let preview_file = find_preview_file(&preset, &reaper_resource_dir) + .context("couldn't find preview file")?; + self.sound_player.load_file(&preview_file)?; + self.sound_player.play()?; + Ok(()) + } + + pub fn stop_preview(&mut self) -> anyhow::Result<()> { + self.sound_player.stop() + } + + pub fn preset_and_id(&self) -> Option<(PresetId, PotPreset)> { + let preset_id = self.preset_id()?; + let preset = pot_db().find_preset_by_id(preset_id)?; + Some((preset_id, preset)) + } + + pub fn load_preset( + &mut self, + preset: &PotPreset, + options: LoadPresetOptions, + ) -> Result<(), LoadPresetError> { + let build_destination = |pot_unit: &mut Self| { + let dest = match pot_unit.resolve_destination()? { + DestinationInstruction::Existing(d) => d, + DestinationInstruction::AddTrack => { + let track = Reaper::get().current_project().add_track()?; + track.set_recording_input(Some(RecordingInput::Midi { + device_id: None, + channel: None, + })); + track.arm( + false, + GangBehavior::DenyGang, + GroupingBehavior::PreventGrouping, + ); + track.set_input_monitoring_mode( + InputMonitoringMode::Normal, + GangBehavior::DenyGang, + GroupingBehavior::PreventGrouping, + ); + // Reset FX back to first one for UI and next preset load. + pot_unit.destination_descriptor.fx_index = 0; + track.select_exclusively(); + Destination { + chain: track.normal_fx_chain(), + fx_index: 0, + } + } + }; + Ok(dest) + }; + let fx = self.load_preset_at(preset, options, &build_destination)?; + if self.name_track_after_preset { + if let Some(track) = fx.track() { + track.set_name(preset.name()); + } + } + Ok(()) + } + + pub fn resolve_destination(&self) -> Result { + self.destination_descriptor.resolve_destination() + } + + pub fn load_preset_at( + &mut self, + preset: &PotPreset, + options: LoadPresetOptions, + build_destination: &impl Fn(&mut RuntimePotUnit) -> Result, + ) -> Result { + let window_behavior = options + .window_behavior_override + .unwrap_or(self.default_load_preset_window_behavior); + match self.load_preset_at_internal( + preset, + window_behavior, + options.audio_sample_behavior, + build_destination, + ) { + Ok(fx) => Ok(fx), + Err(LoadPresetError::UnsupportedPresetFormat { file_extension, .. }) => { + // Unsupported format. But maybe we have a shim file? + let reaper_resource_dir = Reaper::get().resource_path(); + let shim_file_path = + get_shim_file_path(&reaper_resource_dir, &preset.common.persistent_id); + if !shim_file_path.exists() { + // Give up + return Err(LoadPresetError::UnsupportedPresetFormat { + file_extension, + is_shim_preset: false, + }); + } + let protected_fx = self.protected_fx().clone(); + let outcome = self.load_file_based_preset( + &shim_file_path, + build_destination, + window_behavior, + options.audio_sample_behavior, + true, + &protected_fx, + )?; + Ok(self.process_preset_load_outcome(preset, outcome)) + } + e => e, + } + } + + fn load_file_based_preset( + &mut self, + preset_file: &Utf8Path, + build_destination: impl Fn(&mut RuntimePotUnit) -> Result, + window_behavior: LoadPresetWindowBehavior, + audio_sample_behavior: LoadAudioSampleBehavior, + is_shim_preset: bool, + protected_fx: &Fx, + ) -> Result { + let ext = preset_file + .extension() + .ok_or(LoadPresetError::UnsupportedPresetFormat { + file_extension: "".to_string(), + is_shim_preset, + })?; + let outcome = match ext.to_lowercase().as_str() { + _ if is_audio_file_extension(ext) => { + let dest = build_destination(self)?; + load_audio_preset( + preset_file, + &dest, + window_behavior, + audio_sample_behavior, + protected_fx, + )? + } + "nksf" | "nksfx" => { + let dest = build_destination(self)?; + load_nks_preset( + preset_file.as_std_path(), + &dest, + window_behavior, + protected_fx, + )? + } + "rfxchain" => { + let dest = build_destination(self)?; + load_rfx_chain_preset_using_chunks( + preset_file.as_std_path(), + &dest, + window_behavior, + protected_fx, + )? + } + "rtracktemplate" => { + let dest = build_destination(self)?; + load_track_template_preset( + preset_file.as_std_path(), + &dest, + window_behavior, + protected_fx, + )? + } + x => { + return Err(LoadPresetError::UnsupportedPresetFormat { + file_extension: x.to_string(), + is_shim_preset, + }); + } + }; + Ok(outcome) + } + + /// Doesn't try to load a shim preset yet. + fn load_preset_at_internal( + &mut self, + preset: &PotPreset, + window_behavior: LoadPresetWindowBehavior, + audio_sample_behavior: LoadAudioSampleBehavior, + build_destination: &impl Fn(&mut RuntimePotUnit) -> Result, + ) -> Result { + let _ = self.sound_player.stop(); + let protected_fx = self.protected_fx().clone(); + let outcome = match &preset.kind { + PotPresetKind::FileBased(k) => self.load_file_based_preset( + &k.path, + build_destination, + window_behavior, + audio_sample_behavior, + false, + &protected_fx, + )?, + PotPresetKind::ProjectBased(k) => load_project_based_rfx_chain_preset( + self, + build_destination, + &k.path_to_rpp, + k.fx_chain_range.clone(), + window_behavior, + &protected_fx, + )?, + PotPresetKind::Internal(k) => { + if let Some(plugin_id) = k.plugin_id { + let dest = build_destination(self)?; + load_internal_preset( + plugin_id, + preset.name(), + &dest, + window_behavior, + &protected_fx, + ) + .map_err(LoadPresetError::Other)? + } else { + return Err(LoadPresetError::Other( + "Plug-in for internal preset not found".into(), + )); + } + } + PotPresetKind::DefaultFactory(plugin_id) => { + let dest = build_destination(self)?; + load_default_factory_preset(*plugin_id, &dest, window_behavior, &protected_fx) + .map_err(LoadPresetError::Other)? + } + }; + let fx = self.process_preset_load_outcome(preset, outcome); + Ok(fx) + } + + fn process_preset_load_outcome(&self, preset: &PotPreset, outcome: LoadPresetOutcome) -> Fx { + let current_preset = CurrentPreset { + preset: preset.clone(), + macro_param_banks: outcome.banks, + }; + self.integration + .set_current_fx_preset(outcome.fx.clone(), current_preset); + outcome.fx + } + + pub fn state(&self) -> &RuntimeState { + &self.runtime_state + } + + pub fn preset_id(&self) -> Option { + self.runtime_state.preset_id + } + + pub fn find_currently_selected_preset(&self) -> Option { + let preset_id = self.runtime_state.preset_id?; + pot_db().find_preset_by_id(preset_id) + } + + pub fn set_preset_id(&mut self, id: Option) { + self.runtime_state.preset_id = id; + self.integration.notify_preset_changed(id); + } + + pub fn filters(&self) -> &Filters { + &self.runtime_state.filters + } + + pub fn supports_filter_kind(&self, kind: PotFilterKind) -> bool { + self.supported_filter_kinds.contains(kind) + } + + pub fn get_filter(&self, kind: PotFilterKind) -> OptFilter { + self.runtime_state.filters.get(kind) + } + + pub fn show_excluded_filter_items(&self) -> bool { + self.show_excluded_filter_items + } + + pub fn set_show_excluded_filter_items( + &mut self, + show: bool, + shared_self: SharedRuntimePotUnit, + ) { + self.show_excluded_filter_items = show; + self.clear_invalid_filters(); + self.rebuild_collections(shared_self, ChangeHint::Normal, Debounce::No); + } + + pub fn include_filter_item( + &mut self, + kind: PotFilterKind, + id: FilterItemId, + include: bool, + shared_self: SharedRuntimePotUnit, + ) { + { + let mut list = self.integration.exclude_list_mut(); + if include { + list.remove(kind, id); + } else { + list.add(kind, id); + } + } + self.clear_invalid_filters(); + self.rebuild_collections(shared_self, ChangeHint::Normal, Debounce::No); + } + + fn clear_invalid_filters(&mut self) { + if !self.show_excluded_filter_items { + let excludes = self.integration.exclude_list(); + self.runtime_state.filters.clear_excluded_ones(&excludes); + } + } + + pub fn set_filter( + &mut self, + kind: PotFilterKind, + id: OptFilter, + shared_self: SharedRuntimePotUnit, + debounce: Debounce, + ) { + self.runtime_state.filters.set(kind, id); + self.integration.notify_filter_changed(kind, id); + self.rebuild_collections(shared_self, ChangeHint::Filter(kind), debounce); + } + + pub fn refresh_pot(&mut self, shared_self: SharedRuntimePotUnit) { + self.rebuild_collections(shared_self, ChangeHint::TotalRefresh, Debounce::No); + } + + pub fn create_build_input(&self) -> BuildInput { + BuildInput { + filters: self.runtime_state.filters, + search_evaluator: SearchEvaluator::new( + &self.runtime_state.search_expression, + self.runtime_state.search_options.clone(), + ), + filter_excludes: if self.show_excluded_filter_items { + PotFilterExcludes::default() + } else { + self.integration.exclude_list().clone() + }, + } + } + + pub fn rebuild_collections( + &mut self, + shared_self: SharedRuntimePotUnit, + change_hint: ChangeHint, + debounce: Debounce, + ) { + if self.is_refreshing() { + // If we are refreshing, we should let it finish first, otherwise the results will + // be wrong. + return; + } + let build_input = self.create_build_input(); + self.build_counter += 1; + let build_number = self.build_counter; + let affected_kinds = change_hint.affected_kinds(); + // Spawn new async task (don't block GUI thread, might take longer) + spawn_in_pot_worker(async move { + // Debounce (cheap) + // If we don't do this, the wasted runs will dramatically increase when quickly changing + // filters while last query still running. + { + if debounce == Debounce::Yes { + tokio::time::sleep(Duration::from_millis(50)).await; + } + let mut pot_unit = + blocking_lock_arc(&shared_self, "PotUnit from rebuild_collections 1"); + if pot_unit.build_counter == build_number { + // Okay, no new build was requested in the meantime. Start spinner. + pot_unit.running_background_task = + Some(RunningBackgroundTask::new(change_hint)); + } else { + // Oh, another build was requested already. Not worth to continue, the result + // will be discarded anyway. + return Ok(()); + } + } + // Refresh if desired (very expensive) + let refresh_start = Instant::now(); + if change_hint == ChangeHint::TotalRefresh { + pot_db().refresh(); + } + let refresh_duration = refresh_start.elapsed(); + // Build (expensive) + let build_output = pot_db().build_collections(build_input, affected_kinds); + // Set result (cheap) + // Only set result if no new build has been requested in the meantime. + // Prevents flickering and increment/decrement issues. + let mut pot_unit = + blocking_lock_arc(&shared_self, "PotUnit from rebuild_collections 2"); + if pot_unit.build_counter != build_number { + pot_unit.wasted_duration += build_output.stats.total_query_duration(); + pot_unit.wasted_runs += 1; + return Ok(()); + } + pot_unit.notify_build_outcome_ready(build_output, affected_kinds, refresh_duration); + Ok(()) + }); + } + + pub fn is_refreshing(&self) -> bool { + self.running_background_task + .as_ref() + .map(|t| t.change_hint == ChangeHint::TotalRefresh) + .unwrap_or(false) + } + + pub fn background_task_elapsed(&self) -> Option { + Some(self.running_background_task.as_ref()?.start_time.elapsed()) + } + + fn notify_build_outcome_ready( + &mut self, + build_output: BuildOutput, + affected_kinds: EnumSet, + refresh_duration: Duration, + ) { + self.running_background_task = None; + self.supported_filter_kinds = build_output.supported_filter_kinds; + self.preset_collection = build_output.preset_collection; + for (kind, collection) in build_output.filter_item_collections.into_iter() { + if affected_kinds.contains(kind) { + self.filter_item_collections.set(kind, collection); + } + } + self.runtime_state + .filters + .clear_if_not_available_anymore(affected_kinds, &self.filter_item_collections); + self.stats = build_output.stats; + self.stats.refresh_duration = refresh_duration; + self.integration.notify_indexes_rebuilt(); + } + + pub fn count_filter_items(&self, kind: PotFilterKind) -> u32 { + self.filter_item_collections.get(kind).len() as u32 + } + + pub fn preset_count(&self) -> u32 { + self.preset_collection.len() as u32 + } + + pub fn find_next_preset_index(&self, amount: i32) -> Option { + let preset_count = self.preset_count(); + if preset_count == 0 { + return None; + } + match self + .runtime_state + .preset_id + .and_then(|id| self.find_index_of_preset(id)) + { + None => { + if amount < 0 { + Some(preset_count - 1) + } else { + Some(0) + } + } + Some(current_index) => { + let next_index = current_index as i32 + amount; + if next_index < 0 { + Some(preset_count - 1) + } else if next_index as u32 >= preset_count { + Some(0) + } else { + Some(next_index as u32) + } + } + } + } + + pub fn find_index_of_preset(&self, id: PresetId) -> Option { + let index = self.preset_collection.get_index_of(&id)?; + Some(index as _) + } + + pub fn find_preset_id_at_index(&self, index: u32) -> Option { + self.preset_collection.get_index(index as _).copied() + } + + pub fn find_filter_item_id_at_index( + &self, + kind: PotFilterKind, + index: u32, + ) -> Option { + Some(self.find_filter_item_at_index(kind, index)?.id) + } + + pub fn find_filter_item_at_index( + &self, + kind: PotFilterKind, + index: u32, + ) -> Option<&FilterItem> { + self.filter_item_collections.get(kind).get(index as usize) + } + + pub fn find_index_of_filter_item(&self, kind: PotFilterKind, id: FilterItemId) -> Option { + Some(self.find_filter_item_and_index_by_id(kind, id)?.0) + } + + pub fn find_filter_item_by_id( + &self, + kind: PotFilterKind, + id: FilterItemId, + ) -> Option<&FilterItem> { + Some(self.find_filter_item_and_index_by_id(kind, id)?.1) + } + + fn find_filter_item_and_index_by_id( + &self, + kind: PotFilterKind, + id: FilterItemId, + ) -> Option<(u32, &FilterItem)> { + fn find(items: &[FilterItem], id: FilterItemId) -> Option<(u32, &FilterItem)> { + let (i, item) = items.iter().enumerate().find(|(_, item)| item.id == id)?; + Some((i as u32, item)) + } + find(self.filter_item_collections.get(kind), id) + } +} + +#[derive(Clone, Debug)] +pub struct FilterItem { + // TODO-high-pot Distinguish and in persistence + pub persistent_id: String, + /// `None` is also a valid filter item! It would match filter `` (e.g. no category + /// assigned at all) + pub id: FilterItemId, + /// Only set for sub filters. If not set, we know it's a top-level filter. + pub parent_name: Option, + /// If not set, parent name should be set. It's the most unspecific sub filter of a + /// top-level filter, so to say. + pub name: Option, + pub icon: Option, + pub more_info: Option, +} + +impl FilterItem { + pub fn none() -> Self { + Self { + // TODO-high-pot Persistence + persistent_id: "".to_string(), + id: FilterItemId(None), + parent_name: None, + name: Some("".to_string()), + icon: None, + more_info: None, + } + } + + pub fn simple(fil: Fil, name: &str, icon: char, more: &str) -> Self { + Self { + // TODO-high-pot Persistence + persistent_id: "".to_string(), + id: FilterItemId(Some(fil)), + parent_name: None, + name: Some(name.to_string()), + icon: Some(icon), + more_info: if more.is_empty() { + None + } else { + Some(more.to_string()) + }, + } + } + + pub fn effective_leaf_name(&self) -> Cow { + match &self.name { + None => match &self.parent_name { + None => "".into(), + Some(n) => format!("{n}*").into(), + }, + Some(n) => n.into(), + } + } + + pub fn sort_name(&self) -> &str { + match &self.name { + None => match &self.parent_name { + None => "", + Some(n) => n, + }, + Some(n) => n, + } + } +} + +#[derive(Debug)] +pub struct PresetWithId { + pub id: PresetId, + pub preset: PotPreset, +} + +impl PresetWithId { + pub fn new(id: PresetId, preset: PotPreset) -> Self { + Self { id, preset } + } +} + +impl AsRef for PresetWithId { + fn as_ref(&self) -> &PotPreset { + &self.preset + } +} + +/// Contains meta-data about a preset. +/// +/// Although the preset usually doesn't contain the preset chunk (the content to actually load the +/// preset), it's still a bit heavy-weight, containing much meta-data and stuff. So we should +/// not have a list of all presets in memory, for example. +#[derive(Clone, Debug)] +pub struct PotPreset { + pub common: PotPresetCommon, + pub kind: PotPresetKind, +} + +#[derive(Clone, Debug, Default)] +pub struct PotPresetMetaData { + pub author: Option, + pub vendor: Option, + pub comment: Option, + pub file_size_in_bytes: Option, + pub modification_date: Option, +} + +impl PotPreset { + pub fn new(common: PotPresetCommon, kind: PotPresetKind) -> Self { + Self { common, kind } + } + + pub fn name(&self) -> &str { + &self.common.name + } +} + +#[derive(Clone, Debug)] +pub struct PotPresetCommon { + /// ID of the preset that survives restarts and even rescans. + /// + /// It might be tempting to let the persistent ID be based on the contents of the preset + /// but that would be wrong. It also needs to survive modifications of the preset! + pub persistent_id: PersistentPresetId, + pub name: String, + /// Used e.g. for Project database, will contain the project name. + pub context_name: Option, + pub plugin_ids: Vec, + pub product_ids: Vec, + /// Meaning depends on the database. + /// + /// - In case of Komplete, this sometimes corresponds to the name of a plug-in (albeit not + /// necessarily the accurate name), e.g. FM8 as far as REAPER's plug-in scan is concerned. + /// At other times it corresponds to an instrument within a plug-in, e.g. in case + /// of "Abbey Road 50s Drummer" being an instrument within Kontakt. + /// - In case of other databases, this usually corresponds to the accurate plug-in name. + pub product_name: Option, + /// Optional xxh3 128-bit hash based on the contents of the preset. + /// + /// This will be used in the function that determines the Pot preview file location. If this is + /// `None`, the algorithm will take the persistent database and preset IDs, hash them via + /// xxh3 128-bit and turn this into a directory path. If `content_hash` is `Some`, the + /// algorithm will take this value directly and turn it into a directory path + /// (without re-hashing). + /// + /// It's recommended to set this hash if it's not too slow. It should really be based on the + /// content of the preset. Changing the content would change the hash and therefore invalidate + /// the preview, which is what we want. + /// + /// It's okay for a provider to switch from not setting this hash to setting it. But not the + /// other way around! Switching from `None` to `Some` is okay because the preview lookup + /// mechanism will try to find the preview file based on the persistent IDs if it couldn't find + /// the file based on the contents. + /// + /// Once a provider starts setting this hash, it's a sort of commitment! The provider should + /// make sure that the hash is provided in future as well and that the same preset contents lead + /// to the same hash. Otherwise, preview files can't be found anymore and need to be recreated. + pub content_hash: Option, + /// If the database provides its own preview file, this field should contain its hypothetical + /// path. It's not necessary or encouraged to already check for its existence of this file. This + /// will be checked by the consumer. + pub db_specific_preview_file: Option, + pub is_supported: bool, + pub is_available: bool, + pub metadata: PotPresetMetaData, +} + +impl PotPresetCommon { + /// Returns the hash that should be used to identify the location of the custom preview file. + pub fn content_or_id_hash(&self) -> PersistentHash { + self.content_hash.unwrap_or_else(|| { + // If there's none, we take the persistent ID. + hash_util::calculate_persistent_non_crypto_hash_one_shot( + self.persistent_id.to_string().as_bytes(), + ) + }) + } +} + +#[derive(Clone, Debug)] +pub enum PotPresetKind { + FileBased(FiledBasedPotPresetKind), + ProjectBased(ProjectBasedPotPresetKind), + Internal(InternalPotPresetKind), + DefaultFactory(PluginId), +} + +impl PotPresetKind { + pub fn file_extension(&self) -> Option<&str> { + match self { + PotPresetKind::FileBased(k) => Some(&k.file_ext), + PotPresetKind::Internal(_) => None, + PotPresetKind::DefaultFactory(_) => None, + PotPresetKind::ProjectBased(_) => None, + } + } +} + +/// The kind of preset that's saved in a separate file. +#[derive(Clone, Debug)] +pub struct FiledBasedPotPresetKind { + pub path: Utf8PathBuf, + pub file_ext: String, +} + +/// The kind of preset that's buried in a project file. +#[derive(Clone, Debug)] +pub struct ProjectBasedPotPresetKind { + pub path_to_rpp: PathBuf, + pub fx_chain_range: Range, +} + +/// The kind of preset that's saved together with the plug-in in REAPER's plug-in GUI, not exported +/// to a separate file. +#[derive(Clone, Debug)] +pub struct InternalPotPresetKind { + pub plugin_id: Option, +} + +#[derive(serde::Deserialize)] +#[allow(dead_code)] +struct ParamAssignment { + id: Option, + #[serde(default)] + section: Option, + #[serde(default)] + autoname: bool, + #[serde(default)] + name: String, + #[serde(default)] + vflag: bool, +} + +fn load_nks_preset( + path: &Path, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + let nks_file = NksFile::load(path)?; + let mut nks_content = nks_file.content()?; + load_preset_single_fx( + nks_content.plugin_id, + destination, + window_behavior, + protected_fx, + |fx| { + fx.set_vst_chunk(nks_content.vst_chunk)?; + resolve_macro_param_ids(&mut nks_content.macro_param_banks, fx); + let outcome = InternalLoadPresetOutcome { + banks: nks_content.macro_param_banks, + }; + Ok(outcome) + }, + ) +} + +fn resolve_macro_param_ids(banks: &mut [MacroParamBank], fx: &Fx) { + for bank in banks { + for param in bank.params_mut() { + if let Some(fx_param) = &mut param.fx_param { + fx_param.resolved_param_index = + resolve_macro_param_id_to_index(fx_param.param_id, fx); + } + } + } +} + +pub fn resolve_macro_param_id_to_index(param_id: PotFxParamId, fx: &Fx) -> Option { + match param_id { + PotFxParamId::Index(i) => Some(i), + PotFxParamId::Id(id) => { + let param = fx.parameter_by_id(ParamId::custom(format!(":{id}")))?; + Some(param.index()) + } + } +} + +/// Loads an RfxChain file using the "add fx" API. +/// +/// Pros: +/// - Easy API, no dealing with chunks +/// +/// Cons: +/// - Much more disruptive than it needs to be if the plug-in types stay the same +/// - RfxChain file needs to be in the "FXChains" folder +#[allow(dead_code)] +fn load_rfx_chain_preset_using_add_fx( + path: &Path, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + load_preset_multi_fx(destination, window_behavior, true, protected_fx, || { + let root_dir = Reaper::get().resource_path().join("FXChains"); + let relative_path = path.strip_prefix(&root_dir)?; + let relative_path = relative_path.to_string_lossy().to_string(); + let fx = destination + .chain + .add_fx_by_original_name(relative_path) + .ok_or("couldn't load FX chain file")?; + Ok(fx) + }) +} + +/// Loads an RfxChain file using chunk replacement. +/// +/// Pros: +/// - No need to remove existing FX (very fast and non-disruptive) +/// - RfxChain file can be at any location +/// +/// Cons: +/// - Needs to replace the complete track chunk. Modern REAPER API provides a way to make this +/// safe via dynamically resized buffers but we haven't incorporated this yet. +fn load_rfx_chain_preset_using_chunks( + path: &Path, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + let rppxml = + fs::read_to_string(path).map_err(|_| "couldn't read FX chain template as string")?; + load_rfx_chain_preset_using_chunks_from_string( + &rppxml, + destination, + window_behavior, + protected_fx, + ) +} + +fn load_rfx_chain_preset_using_chunks_from_string( + rppxml: &str, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + load_preset_multi_fx(destination, window_behavior, false, protected_fx, || { + // Our chunk stuff assumes we have a chunk without indentation. Time to use proper parsing... + let rppxml: String = rppxml.lines().map(|l| l.trim()).join("\n"); + let fx_chain_content_chunk = Chunk::new(rppxml); + let fx_chain_chunk = format!(""); + destination.chain.set_chunk(&fx_chain_chunk)?; + let first_fx = destination + .chain + .first_fx() + .ok_or("couldn't get hold of first FX on chain")?; + Ok(first_fx) + }) +} + +fn load_project_based_rfx_chain_preset( + pot_unit: &mut RuntimePotUnit, + build_destination: impl Fn(&mut RuntimePotUnit) -> Result, + path_to_rpp: &Path, + fx_chain_range: Range, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + let project_rppxml = + fs::read_to_string(path_to_rpp).map_err(|_| "couldn't read project as string")?; + let fx_chain_rppxml = &project_rppxml[fx_chain_range]; + let destination = &build_destination(pot_unit)?; + load_rfx_chain_preset_using_chunks_from_string( + fx_chain_rppxml, + destination, + window_behavior, + protected_fx, + ) +} + +fn load_track_template_preset( + path: &Path, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + load_preset_multi_fx(destination, window_behavior, false, protected_fx, || { + let rppxml = + fs::read_to_string(path).map_err(|_| "couldn't read track template as string")?; + // Our chunk stuff assumes we have a chunk without indentation. Time to use proper parsing... + let rppxml: String = rppxml.lines().map(|l| l.trim()).join("\n"); + let track_chunk = Chunk::new(rppxml); + let fx_chain_region = track_chunk + .region() + .find_first_tag_named(0, "FXCHAIN") + .ok_or("track template doesn't have FX chain")?; + destination.chain.set_chunk(&fx_chain_region.content())?; + let first_fx = destination + .chain + .first_fx() + .ok_or("couldn't get hold of first FX on chain")?; + Ok(first_fx) + }) +} + +fn load_internal_preset( + plugin_id: PluginId, + preset_name: &str, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + load_preset_single_fx( + plugin_id, + destination, + window_behavior, + protected_fx, + |fx| { + fx.activate_preset_by_name(preset_name)?; + Ok(Default::default()) + }, + ) +} + +fn load_default_factory_preset( + plugin_id: PluginId, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, +) -> Result> { + load_preset_single_fx( + plugin_id, + destination, + window_behavior, + protected_fx, + |fx| { + fx.activate_preset(FxPresetRef::FactoryPreset)?; + Ok(Default::default()) + }, + ) +} + +fn load_audio_preset( + path: &Utf8Path, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + audio_sample_behavior: LoadAudioSampleBehavior, + protected_fx: &Fx, +) -> Result> { + const RS5K_VST_ID: i32 = 1920167789; + let plugin_id = PluginId::Vst2 { + vst_magic_number: RS5K_VST_ID, + }; + load_preset_single_fx( + plugin_id, + destination, + window_behavior, + protected_fx, + |fx| { + // First try it the modern way ... + if load_media_in_specific_rs5k_modern(fx, path).is_err() { + // ... and if this didn't work, try it the old-school way. + // Make sure RS5k has focus + let window_is_open_now = fx.window_is_open(); + if window_is_open_now { + if !fx.window_has_focus() { + fx.hide_floating_window()?; + fx.show_in_floating_window()?; + } + } else { + fx.show_in_floating_window()?; + } + // Load into RS5k + load_media_in_last_focused_rs5k(path)?; + } + // Set RS5k options + let (mode, pitch_for_start_note) = + if let Some(root_pitch) = audio_sample_behavior.root_pitch { + // Value range -80 to 80, makes 161 discrete values. + let normalized_value = (root_pitch + 80).max(0) as f64 / 160.0; + ( + reaper_str!("2"), + ReaperNormalizedFxParamValue::new(normalized_value), + ) + } else { + (reaper_str!("1"), ReaperNormalizedFxParamValue::default()) + }; + unsafe { + let _ = fx.set_named_config_param("MODE", mode.as_c_str().as_ptr() as _); + } + let _ = fx + .parameter_by_index(5) + .set_reaper_normalized_value(pitch_for_start_note); + let obey_note_off_val = + ReaperNormalizedFxParamValue::new(audio_sample_behavior.obey_note_off.into()); + let _ = fx + .parameter_by_index(11) + .set_reaper_normalized_value(obey_note_off_val); + Ok(Default::default()) + }, + ) +} + +fn load_preset_single_fx( + plugin_id: PluginId, + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + protected_fx: &Fx, + f: impl FnOnce(&Fx) -> Result>, +) -> Result> { + let existing_fx = destination.resolve(); + let fx_was_open_before = existing_fx + .as_ref() + .map(|fx| fx.window_is_open()) + .unwrap_or(false); + let output = ensure_fx_has_correct_type(plugin_id, destination, existing_fx, protected_fx)?; + let outcome = f(&output.fx)?; + window_behavior.open_or_close(&output.fx, fx_was_open_before, output.op)?; + let outcome = LoadPresetOutcome { + fx: output.fx, + banks: outcome.banks, + }; + Ok(outcome) +} + +#[derive(Default)] +struct InternalLoadPresetOutcome { + banks: Vec, +} + +fn load_preset_multi_fx( + destination: &Destination, + window_behavior: LoadPresetWindowBehavior, + remove_existing_fx_manually: bool, + protected_fx: &Fx, + f: impl FnOnce() -> Result>, +) -> Result> { + let mut fx_was_open_before = false; + for fx in destination + .chain + .fxs() + .skip(destination.fx_index as _) + .rev() + { + if fx.window_is_open() { + fx_was_open_before = true; + } + if &fx == protected_fx { + return Err(CANT_REMOVE_PROTECTED_FX.into()); + } + if remove_existing_fx_manually { + destination.chain.remove_fx(&fx)?; + } + } + let fx = f()?; + for fx in destination.chain.fxs() { + window_behavior.open_or_close(&fx, fx_was_open_before, FxEnsureOp::Replaced)?; + } + let outcome = LoadPresetOutcome { fx, banks: vec![] }; + Ok(outcome) +} + +struct LoadPresetOutcome { + fx: Fx, + banks: Vec, +} + +pub struct FxEnsureOutput { + pub fx: Fx, + pub op: FxEnsureOp, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum FxEnsureOp { + Same, + Added, + Replaced, +} + +fn ensure_fx_has_correct_type( + plugin_id: PluginId, + destination: &Destination, + existing_fx: Option, + protected_fx: &Fx, +) -> Result> { + let output = match existing_fx { + None => { + let fx = insert_fx_by_plugin_id(plugin_id, destination)?; + FxEnsureOutput { + fx, + op: FxEnsureOp::Added, + } + } + Some(fx) => { + // No need to fall back to chunk-based FX info because Pot is experimental, and we can assume it's used + // with recent REAPER versions. + let fx_info = fx.info()?; + if fx_info.id == plugin_id.content_formatted_for_reaper() { + // This is the right plug-in type. Leave as is. + FxEnsureOutput { + fx, + op: FxEnsureOp::Same, + } + } else { + // We don't have the right plug-in type. Remove FX and insert correct one. + if &fx == protected_fx { + return Err(CANT_REMOVE_PROTECTED_FX.into()); + } + destination.chain.remove_fx(&fx)?; + let fx = insert_fx_by_plugin_id(plugin_id, destination)?; + FxEnsureOutput { + fx, + op: FxEnsureOp::Replaced, + } + } + } + }; + Ok(output) +} + +fn insert_fx_by_plugin_id( + plugin_id: PluginId, + destination: &Destination, +) -> Result> { + let kind = plugin_id.kind(); + let name = format!( + "{}{}{}", + kind.reaper_add_by_name_prefix_fix(), + kind.formatted_for_reaper(), + PluginIdContentInReaperFormat(&plugin_id) + ); + let fx = destination + .chain + .insert_fx_by_name(destination.fx_index, name.as_str()) + .ok_or_else(|| format!("Couldn't add FX via name \"{name}\""))?; + Ok(fx) +} + +fn load_media_in_specific_rs5k_modern(fx: &Fx, path: &Utf8Path) -> Result<(), Box> { + let path_c_string = CString::new(path.as_str())?; + unsafe { + fx.set_named_config_param("FILE", path_c_string.as_bytes_with_nul().as_ptr() as _)?; + } + Ok(()) +} + +fn load_media_in_last_focused_rs5k(path: &Utf8Path) -> Result<(), &'static str> { + Reaper::get().medium_reaper().insert_media( + path, + InsertMediaMode::CurrentReasamplomatic, + Default::default(), + )?; + Ok(()) +} + +#[derive(Clone, Debug)] +pub struct Destination { + pub chain: FxChain, + pub fx_index: u32, +} + +impl Destination { + pub fn resolve(&self) -> Option { + self.chain.fx_by_index(self.fx_index) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct LoadPresetOptions { + pub window_behavior_override: Option, + pub audio_sample_behavior: LoadAudioSampleBehavior, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct LoadAudioSampleBehavior { + /// If `None`, MIDI notes will be ignored. + /// + /// If `Some`: + /// - Sets RS5k parameter "Mode" (named parameter "MODE") to "Note (Semitone shifted)" (2). + /// - Sets RS5k parameter "Pitch for start note" (index 5), which has range -80 to +80. + pub root_pitch: Option, + /// Sets RS5k parameter "Obey note-offs" (index 11), which is boolean. + pub obey_note_off: bool, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, strum::EnumIter, strum::AsRefStr)] +pub enum LoadPresetWindowBehavior { + #[strum(serialize = "Never show")] + NeverShow, + #[strum(serialize = "Show only if previously shown")] + ShowOnlyIfPreviouslyShown, + #[strum(serialize = "Show only if previously shown or newly added")] + #[default] + ShowOnlyIfPreviouslyShownOrNewlyAdded, + #[strum(serialize = "Always show")] + AlwaysShow, +} + +impl LoadPresetWindowBehavior { + pub fn open_or_close( + &self, + fx: &Fx, + was_open_before: bool, + op: FxEnsureOp, + ) -> anyhow::Result<()> { + let now_is_open = fx.window_is_open(); + match self { + LoadPresetWindowBehavior::NeverShow => { + if now_is_open { + fx.hide_floating_window()?; + } + } + LoadPresetWindowBehavior::ShowOnlyIfPreviouslyShown => { + if !was_open_before && now_is_open { + fx.hide_floating_window()?; + } else if was_open_before && !now_is_open { + fx.show_in_floating_window()?; + } + } + LoadPresetWindowBehavior::AlwaysShow => { + if !now_is_open { + fx.show_in_floating_window()?; + } + } + LoadPresetWindowBehavior::ShowOnlyIfPreviouslyShownOrNewlyAdded => { + if op == FxEnsureOp::Added { + if !now_is_open { + fx.show_in_floating_window()?; + } + } else if !was_open_before && now_is_open { + fx.hide_floating_window()?; + } else if was_open_before && !now_is_open { + fx.show_in_floating_window()?; + } + } + } + Ok(()) + } +} + +#[derive(Debug, derive_more::Display)] +pub enum LoadPresetError { + #[display(fmt = "Unsupported preset format")] + UnsupportedPresetFormat { + file_extension: String, + /// `true` if a shim preset file was found but even its format is not supported. + /// That would be weird. + is_shim_preset: bool, + }, + Other(Box), +} + +impl From<&str> for LoadPresetError { + fn from(value: &str) -> Self { + Self::Other(value.into()) + } +} + +impl From> for LoadPresetError { + fn from(value: Box) -> Self { + Self::Other(value) + } +} + +impl Error for LoadPresetError {} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum Debounce { + No, + Yes, +} + +fn is_audio_file_extension(ext: &str) -> bool { + matches!(ext, "wav" | "aif" | "ogg" | "mp3") +} + +pub fn preview_exists(preset: &PotPreset, reaper_resource_dir: &Utf8Path) -> bool { + find_preview_file(preset, reaper_resource_dir).is_some() +} + +/// This looks up the preview file for the given preset, actually checking for the file's +/// existence. +/// +/// It also exposes audio file presets as preview files. +/// +/// It prefers custom previews over database-specific previews. +pub fn find_preview_file<'a>( + preset: &'a PotPreset, + reaper_resource_dir: &Utf8Path, +) -> Option> { + // If the preset is an audio file and it exists, return that + if let PotPresetKind::FileBased(kind) = &preset.kind { + if is_audio_file_extension(&kind.file_ext) { + return if kind.path.exists() { + Some(kind.path.as_path().into()) + } else { + None + }; + } + } + // If a custom preview file exists, return that + let hash = preset.common.content_or_id_hash(); + let preview_file_path = get_preview_file_path_from_hash(reaper_resource_dir, hash); + if preview_file_path.exists() { + return Some(preview_file_path.into()); + } + // If a database-specific preview file exists, return that + let db_specific_preview_file = preset.common.db_specific_preview_file.as_ref()?; + if db_specific_preview_file.exists() { + Some(Cow::Borrowed(db_specific_preview_file)) + } else { + None + } +} + +pub fn create_plugin_factory_preset( + plugin: &PluginCommon, + persistent_id: PersistentPresetId, + preset_name: String, +) -> PotPreset { + PotPreset { + common: PotPresetCommon { + persistent_id, + name: preset_name, + context_name: None, + plugin_ids: vec![plugin.core.id], + product_ids: vec![plugin.core.product_id], + product_name: Some(plugin.to_string()), + content_hash: None, + db_specific_preview_file: None, + is_supported: true, + is_available: true, + metadata: Default::default(), + }, + kind: PotPresetKind::DefaultFactory(plugin.core.id), + } +} + +const CANT_REMOVE_PROTECTED_FX: &str = "Can't replace ReaLearn itself. Either set \"Load into\" correctly or put ReaLearn on the monitoring FX chain!"; diff --git a/plugin-reaper-realearn/pot/src/nks.rs b/plugin-reaper-realearn/pot/src/nks.rs new file mode 100644 index 0000000..de653cd --- /dev/null +++ b/plugin-reaper-realearn/pot/src/nks.rs @@ -0,0 +1,35 @@ +use base::default_util::{deserialize_null_default, is_default}; + +#[derive(Clone, Eq, PartialEq, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct PersistentNksFilterSettings { + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub bank: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub sub_bank: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub category: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub sub_category: Option, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] + pub mode: Option, +} diff --git a/plugin-reaper-realearn/pot/src/plugin_id.rs b/plugin-reaper-realearn/pot/src/plugin_id.rs new file mode 100644 index 0000000..61692c7 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/plugin_id.rs @@ -0,0 +1,241 @@ +use base::LimitedAsciiString; +use std::fmt; +use std::fmt::{Display, Formatter}; + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum PluginId { + Vst2 { vst_magic_number: i32 }, + Vst3 { vst_uid: [u32; 4] }, + Clap { clap_id: LimitedAsciiString<100> }, + Js { js_id: LimitedAsciiString<100> }, +} + +impl PluginId { + pub fn vst2(vst_magic_number: i32) -> Self { + Self::Vst2 { vst_magic_number } + } + + pub fn vst3(vst_uid: [u32; 4]) -> Self { + Self::Vst3 { vst_uid } + } + + pub fn clap(id_expression: &str) -> Result { + let id = Self::Clap { + clap_id: LimitedAsciiString::try_from_str(id_expression)?, + }; + Ok(id) + } + + pub fn js(id_expression: &str) -> Result { + // It's better to normalize to lowercase. When loading the FX via TrackFX_AddByName, + // REAPER is case-insensitive anyway and we should be as well when doing comparisons. + let lowercase_id_expression = id_expression.to_lowercase(); + let id = Self::Js { + js_id: LimitedAsciiString::try_from_str(&lowercase_id_expression)?, + }; + Ok(id) + } + + pub fn parse_from_rxml_line(line: &str) -> Result { + let line = line.trim(); + let mut tokens = splitty::split_unquoted_whitespace(line).unwrap_quotes(true); + let tag_opener = tokens.next().ok_or("missing FX tag opener")?; + match tag_opener { + " { + // Examples: + // - "" + // - 5ch)" "Pianoteq 8.vst3" 0 "" 1031062328{565354507438717069616E6F74657120} "" + // Skip plug-in name, file, zero, custom name + for _ in 0..4 { + tokens.next(); + } + // Process ID expression + let id_expression = tokens.next().ok_or("missing VST ID expression")?; + if let Some((_, remainder)) = id_expression.split_once('{') { + // VST3 + let vst3_uid_string = remainder.strip_suffix('}').unwrap_or(remainder); + let uid = parse_vst3_uid(vst3_uid_string)?; + Ok(Self::vst3(uid)) + } else if let Some((magic_number_string, _)) = id_expression.split_once('<') { + // VST2 + let magic_number = parse_vst2_magic_number(magic_number_string)?; + Ok(Self::vst2(magic_number)) + } else { + Err("couldn't process VST ID expression") + } + } + " { + // Example: { + // Example: Err("unknown FX tag opener"), + } + } + + pub fn kind(&self) -> PluginKind { + match self { + PluginId::Vst2 { .. } => PluginKind::Vst2, + PluginId::Vst3 { .. } => PluginKind::Vst3, + PluginId::Clap { .. } => PluginKind::Clap, + PluginId::Js { .. } => PluginKind::Js, + } + } + + pub fn content_formatted_for_reaper(&self) -> String { + PluginIdContentInReaperFormat(self).to_string() + } +} + +/// Example: `1967946098` for a VST2 plug-in ID. +pub struct PluginIdContentInReaperFormat<'a>(pub &'a PluginId); + +impl Display for PluginIdContentInReaperFormat<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self.0 { + PluginId::Clap { clap_id } => clap_id.fmt(f), + PluginId::Js { js_id } => js_id.fmt(f), + PluginId::Vst2 { vst_magic_number } => vst_magic_number.fmt(f), + PluginId::Vst3 { vst_uid } => { + // D39D5B69 D6AF42FA 12345678 534D4433 + write!( + f, + "{:X}{:X}{:X}{:X}", + vst_uid[0], vst_uid[1], vst_uid[2], vst_uid[3], + ) + } + } + } +} + +/// Example: `vst|1967946098` for a VST2 plug-in ID. +pub struct PluginIdInPipeFormat<'a>(pub &'a PluginId); + +impl Display for PluginIdInPipeFormat<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let kind = self.0.kind(); + let kind = kind.as_ref(); + let content = PluginIdContentInReaperFormat(self.0); + write!(f, "{kind}|{content}") + } +} + +/// When adding a new variant, the serialization should correspond to the string which is used +/// as prefix for the ini file names in "REAPER_RESOURCE_PATH/presets". +#[derive(Copy, Clone, Eq, PartialEq, Debug, strum::AsRefStr, strum::EnumString)] +pub enum PluginKind { + #[strum(serialize = "vst")] + Vst2, + #[strum(serialize = "vst3")] + Vst3, + #[strum(serialize = "clap")] + Clap, + #[strum(serialize = "js")] + Js, +} + +impl PluginKind { + pub fn name(&self) -> &'static str { + match self { + Self::Vst2 => "VST", + Self::Vst3 => "VST3", + Self::Clap => "CLAP", + Self::Js => "JS", + } + } + + /// Need to put some random string in front of "<" due to bug in REAPER < 6.69, + /// otherwise loading by VST2 magic number doesn't work. + pub fn reaper_add_by_name_prefix_fix(&self) -> &'static str { + match self { + Self::Vst2 | Self::Vst3 => "i7zh34z", + Self::Clap | Self::Js => "", + } + } + + pub fn formatted_for_reaper(&self) -> &'static str { + match self { + Self::Vst2 => "<", + Self::Vst3 => "{", + Self::Clap | Self::Js => "", + } + } +} + +/// "1397572658" => 1397572658 +pub fn parse_vst2_magic_number(expression: &str) -> Result { + expression + .parse() + .map_err(|_| "couldn't parse VST2 magic number") +} + +/// "565354507438717069616E6F74657120" => [0x56535450, 0x74387170, 0x69616E6F, 0x74657120] +pub fn parse_vst3_uid(expression: &str) -> Result<[u32; 4], &'static str> { + fn parse_component(text: &str, i: usize) -> Result { + let from = i * 8; + let until = from + 8; + let parsed = u32::from_str_radix(&text[from..until], 16) + .map_err(|_| "couldn't parse VST3 uid component")?; + Ok(parsed) + } + let uid = [ + parse_component(expression, 0)?, + parse_component(expression, 1)?, + parse_component(expression, 2)?, + parse_component(expression, 3)?, + ]; + Ok(uid) +} + +#[cfg(test)] +mod tests { + use crate::PluginId; + + #[test] + pub fn vst2() { + assert_eq!( + PluginId::parse_from_rxml_line( + r#" """# + ), + Ok(PluginId::vst2(1397572658)) + ); + } + + #[test] + pub fn vst3() { + assert_eq!( + PluginId::parse_from_rxml_line( + r#"5ch)" "Pianoteq 8.vst3" 0 "" 1031062328{565354507438717069616E6F74657120} """# + ), + Ok(PluginId::vst3([ + 0x56535450, 0x74387170, 0x69616E6F, 0x74657120 + ])) + ); + } + + #[test] + pub fn clap() { + assert_eq!( + PluginId::parse_from_rxml_line( + r#", + products: Vec, + detected_legacy_vst3_scan: bool, +} + +/// Responsible for grouping similar plug-ins into products. +#[derive(Default)] +struct ProductAccumulator { + products: Vec, +} + +impl ProductAccumulator { + /// Adds a product with the given characteristics. + pub fn add_other_product(&mut self, name: String, kind: Option) -> ProductId { + let product = Product { name, kind }; + self.products.push(product); + ProductId(self.products.len() as u32 - 1) + } + + /// Tries to find an already added product matching the product name within the given plug-in + /// expression (and product kind) or creates a new product. + pub fn get_or_add_plugin_product( + &mut self, + name_expression: &str, + kind: Option, + ) -> ProductId { + // Extract product name + let name = if let Some(name) = ProductName::parse(name_expression) { + normalize_product_main_name(name.main) + } else { + name_expression + }; + // Find existing product + let existing_product = self + .products + .iter() + .enumerate() + .find(|(_, product)| product.name == name && product.kind == kind); + // Return existing or create new product + let i = match existing_product { + None => { + let new_product = Product { + name: name.to_string(), + kind, + }; + self.products.push(new_product); + self.products.len() - 1 + } + Some((i, _)) => i, + }; + ProductId(i as u32) + } + + pub fn into_products(self) -> Vec { + self.products + } +} + +impl PluginDatabase { + pub fn crawl(reaper_resource_dir: &Utf8Path) -> Self { + let mut product_accumulator = ProductAccumulator::default(); + let mut detected_legacy_vst3_scan = false; + let shared_library_plugins = crawl_shared_library_plugins( + &mut product_accumulator, + reaper_resource_dir, + &mut detected_legacy_vst3_scan, + ); + let js_root_dir = reaper_resource_dir.join("Effects"); + let js_plugins = crawl_js_plugins(&mut product_accumulator, &js_root_dir); + let plugin_map = shared_library_plugins + .into_iter() + .chain(js_plugins) + .map(|p| (p.common.core.id, p)) + .collect(); + Self { + plugins: plugin_map, + products: product_accumulator.into_products(), + detected_legacy_vst3_scan, + } + } + + pub fn detected_legacy_vst3_scan(&self) -> bool { + self.detected_legacy_vst3_scan + } + + pub fn plugins(&self) -> impl Iterator { + self.plugins.values() + } + + pub fn products(&self) -> impl Iterator { + self.products + .iter() + .enumerate() + .map(|(i, p)| (ProductId(i as _), p)) + } + + pub fn find_plugin_by_id(&self, plugin_id: &PluginId) -> Option<&Plugin> { + self.plugins.get(plugin_id) + } + + pub fn find_product_by_id(&self, product_id: &ProductId) -> Option<&Product> { + self.products.get(product_id.0 as usize) + } + + pub fn detect_plugin_from_rxml_line(&self, line: &str) -> Option<&Plugin> { + let is_fx_line = [", +} + +#[derive(Clone, Debug)] +pub struct Plugin { + /// Contains data relevant for all kinds of plug-ins. + pub common: PluginCommon, + /// Contains data specific to certain kinds of plug-ins. + pub kind: SuperPluginKind, +} + +#[derive(Clone, Debug)] +pub struct PluginCommon { + /// Full name of the plug-in without kind (for display purposes mainly). + /// + /// E.g. "Zebra2 (u-he)" + pub name: String, + pub core: PluginCore, +} + +#[derive(Copy, Clone, Debug)] +pub struct PluginCore { + /// Uniquely identifies the plug-in in a rather cheap way (copyable). + pub id: PluginId, + /// Whether we have an effect or an instrument or unknown. + pub product_kind: Option, + /// What product this plug-in belongs to. + pub product_id: ProductId, +} + +impl Display for PluginCommon { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str(self.core.id.kind().name())?; + if let Some(ProductKind::Instrument) = self.core.product_kind { + f.write_str("i")?; + } + write!(f, ": {}", &self.name)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum SuperPluginKind { + Vst(VstPlugin), + Clap(ClapPlugin), + Js(JsPlugin), +} + +#[derive(Clone, Debug)] +pub struct ClapPlugin { + /// Real file name, no characters replaced. + pub file_name: String, + /// According to Justin, this is a Win32 FILETIME timestamp (last write time and creation time, + /// concatenated). + pub filetime: String, + pub id: String, +} + +#[derive(Clone, Debug)] +pub struct JsPlugin { + /// Relative path from JS root dir. + /// + /// This is a runtime path and it's not normalized to lower-case! So it shouldn't be used + /// as an ID. + pub path: String, +} + +#[derive(Clone, Debug)] +pub struct VstPlugin { + /// Safe means: Each space and special character is replaced with an underscore. + pub safe_file_name: String, + /// A value which identifies the actual plug-in within a shell file. + /// + /// Some files are just shells. That means, they contain multiple actual plug-ins. + /// If this plug-in is part of a shell file, this value will be set. In practice, it's + /// equal to the magic number / uid_hash. + pub shell_qualifier: Option, + pub checksum: String, + pub kind: VstPluginKind, +} + +#[derive(Clone, Debug)] +pub enum VstPluginKind { + Vst2 { magic_number: String }, + Vst3 { uid_hash: String, uid: String }, +} + +impl VstPluginKind { + pub fn plugin_id(&self) -> Result { + let id = match self { + VstPluginKind::Vst2 { magic_number } => PluginId::Vst2 { + vst_magic_number: parse_vst2_magic_number(magic_number)?, + }, + VstPluginKind::Vst3 { uid, .. } => PluginId::Vst3 { + vst_uid: parse_vst3_uid(uid)?, + }, + }; + Ok(id) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, derive_more::Display)] +pub enum ProductKind { + Effect, + Instrument, + Loop, + OneShot, +} + +impl ProductKind { + pub fn komplete_id(&self) -> &'static u32 { + const INSTRUMENT: u32 = 1; + const EFFECT: u32 = 2; + const ONE_SHOT: u32 = 4; + const LOOP: u32 = 8; + use ProductKind as K; + match self { + K::Effect => &EFFECT, + K::Instrument => &INSTRUMENT, + K::Loop => &LOOP, + K::OneShot => &ONE_SHOT, + } + } +} +fn crawl_js_plugins( + product_accumulator: &mut ProductAccumulator, + js_root_dir: &Utf8Path, +) -> Vec { + WalkDir::new(js_root_dir) + .follow_links(true) + .into_iter() + .filter_entry(|e| !file_util::is_hidden(e.file_name())) + .filter_map(|entry| { + let entry = entry.ok()?; + if !entry.file_type().is_file() { + return None; + } + let relative_path = entry.path().strip_prefix(js_root_dir).ok()?; + let relative_path = relative_path.to_str()?; + let product_kind = Some(ProductKind::Effect); + let js_desc = read_js_desc_from_file(entry.path())?; + let plugin = Plugin { + common: PluginCommon { + name: js_desc.clone(), + core: PluginCore { + id: PluginId::js(relative_path).ok()?, + product_kind, + product_id: product_accumulator.add_other_product(js_desc, product_kind), + }, + }, + kind: SuperPluginKind::Js(JsPlugin { + path: relative_path.to_string(), + }), + }; + Some(plugin) + }) + .collect() +} + +fn crawl_shared_library_plugins( + product_accumulator: &mut ProductAccumulator, + reaper_resource_dir: &Utf8Path, + detected_legacy_vst3_scan: &mut bool, +) -> Vec { + WalkDir::new(reaper_resource_dir) + .max_depth(1) + .follow_links(false) + .into_iter() + .filter_map(|entry| { + let entry = entry.ok()?; + if !entry.file_type().is_file() { + return None; + } + let file_name = entry.file_name().to_str()?; + enum PlugType { + Vst, + Clap, + } + let plug_type = match file_name { + VST_CACHE_FILE => PlugType::Vst, + CLAP_CACHE_FILE => PlugType::Clap, + _ => return None, + }; + let ini = Ini::load_from_file(entry.path()).ok()?; + let plugins = match plug_type { + PlugType::Vst => crawl_vst_plugins_in_ini_file( + product_accumulator, + ini, + detected_legacy_vst3_scan, + ), + PlugType::Clap => crawl_clap_plugins_in_ini_file(product_accumulator, ini), + }; + Some(plugins) + }) + .flatten() + .collect() +} + +fn crawl_clap_plugins_in_ini_file( + product_accumulator: &mut ProductAccumulator, + ini: Ini, +) -> Vec { + ini.iter() + .filter_map(|(section, props)| { + let file_name = section?; + let checksum = props.get("_")?; + let plugins: Vec<_> = props + .iter() + .filter_map(|(key, value)| { + if key == "_" { + return None; + } + let (product_kind_id, plugin_name) = value.split_once('|')?; + let product_kind = match product_kind_id { + "0" => Some(ProductKind::Effect), + "1" => Some(ProductKind::Instrument), + _ => None, + }; + let plugin = Plugin { + common: PluginCommon { + core: PluginCore { + id: PluginId::clap(key).ok()?, + product_kind, + product_id: product_accumulator + .get_or_add_plugin_product(plugin_name, product_kind), + }, + name: plugin_name.to_string(), + }, + kind: SuperPluginKind::Clap(ClapPlugin { + file_name: file_name.to_string(), + filetime: checksum.to_string(), + id: key.to_string(), + }), + }; + Some(plugin) + }) + .collect(); + Some(plugins) + }) + .flatten() + .collect() +} + +fn crawl_vst_plugins_in_ini_file( + product_accumulator: &mut ProductAccumulator, + ini: Ini, + detected_legacy_vst3_scan: &mut bool, +) -> Vec { + let Some(section) = ini.section(Some("vstcache")) else { + return vec![]; + }; + section + .iter() + .filter_map(|(key, value)| { + let (safe_file_name, shell_qualifier) = match key.split_once('<') { + Some((first, second)) => (first.to_string(), Some(second.to_string())), + None => (key.to_string(), None), + }; + let mut value_iter = value.splitn(3, ','); + let checksum = value_iter.next()?.to_string(); + let plugin_id_expression = value_iter.next()?; + if plugin_id_expression == "0" { + // Must be a plug-in shell, not an actual plug-in. + return None; + } + let plugin_name_kind_expression = value_iter.next()?; + let vst_kind = match plugin_id_expression.split_once('{') { + None => { + if safe_file_name.ends_with(".vst3") { + // We skip VST3 plug-ins that have been scanned with old versions of + // REAPER (and therefore missing a UID) in order to avoid creating wrong + // persistent plug-in IDs). + *detected_legacy_vst3_scan = true; + return None; + } + VstPluginKind::Vst2 { + magic_number: plugin_id_expression.to_string(), + } + } + Some((left, right)) => VstPluginKind::Vst3 { + uid_hash: left.to_string(), + uid: right.to_string(), + }, + }; + let (name, product_kind) = match plugin_name_kind_expression.split_once("!!!") { + None => ( + plugin_name_kind_expression.to_string(), + Some(ProductKind::Effect), + ), + Some((left, right)) => { + let kind = match right { + "VSTi" => Some(ProductKind::Instrument), + _ => None, + }; + (left.to_string(), kind) + } + }; + let plugin = Plugin { + common: PluginCommon { + core: PluginCore { + id: vst_kind.plugin_id().ok()?, + product_id: product_accumulator + .get_or_add_plugin_product(name.as_str(), product_kind), + product_kind, + }, + name, + }, + kind: SuperPluginKind::Vst(VstPlugin { + safe_file_name, + shell_qualifier, + checksum, + kind: vst_kind, + }), + }; + Some(plugin) + }) + .collect() +} + +#[derive(Eq, PartialEq, Debug)] +struct ProductName<'a> { + main: &'a str, + arch: Option<&'a str>, + company: &'a str, + channels: Option<&'a str>, +} + +impl<'a> ProductName<'a> { + pub fn parse(name_expression: &'a str) -> Option { + let four_part_regex = base::regex!(r"(.*) \((.*)\) \((.*)\) \((.*)\)"); + let three_part_regex = base::regex!(r"(.*) \((.*)\) \((.*)\)"); + let two_part_regex = base::regex!(r"(.*) \((.*)\)"); + + if let Some(captures) = four_part_regex.captures(name_expression) { + return Some(ProductName { + main: s(captures.get(1)), + arch: Some(s(captures.get(2))), + company: s(captures.get(3)), + channels: Some(s(captures.get(4))), + }); + } + if let Some(captures) = three_part_regex.captures(name_expression) { + let name = if &captures[2] == "x86_64" { + ProductName { + main: s(captures.get(1)), + arch: Some(s(captures.get(2))), + company: s(captures.get(3)), + channels: None, + } + } else { + ProductName { + main: s(captures.get(1)), + arch: None, + company: s(captures.get(2)), + channels: Some(s(captures.get(3))), + } + }; + return Some(name); + } + if let Some(captures) = two_part_regex.captures(name_expression) { + return Some(ProductName { + main: s(captures.get(1)), + arch: None, + company: s(captures.get(2)), + channels: None, + }); + } + None + } +} + +fn s(m: Option) -> &str { + m.unwrap().as_str() +} + +fn read_js_desc_from_file(path: &Path) -> Option { + let file = File::open(path).ok()?; + let mut buffer = String::new(); + let mut reader = BufReader::new(&file); + while let Ok(count) = reader.read_line(&mut buffer) { + if count == 0 { + // EOF + break; + } + let line = buffer.trim(); + if let Some((left, right)) = line.split_once(':') { + if left == "desc" { + return Some(right.trim().to_string()); + } + } + buffer.clear(); + } + None +} + +/// Further normalizes the already extracted main name so it conforms to the way "Komplete" +/// displays the products. +/// +/// # Maybe crop version number +/// +/// Example: So we have successfully extracted "Kontakt 5" as main name from an expression +/// "VSTi: Kontakt 5 (x86_64) (Native Instruments GmbH) (64 out). So far a very neutral extraction. +/// But Komplete's product filter section doesn't distinguish between multiple versions of Kontakt. +/// So we want to normalize this to just "Kontakt". +/// +/// There are other NKS-ready products such as Pianoteq that distinguish between version numbers, +/// so as a general rule we distinguish between version numbers and just add a few exceptions here. +fn normalize_product_main_name(main_name: &str) -> &str { + maybe_crop_version_number(main_name) +} + +fn maybe_crop_version_number(main_name: &str) -> &str { + const PRODUCT_NAMES_WITHOUT_VERSION_NUMBER: &[&str] = + &["Kontakt", "Absynth", "Guitar Rig", "Reaktor", "Battery"]; + let Some((left, right)) = main_name.rsplit_once(' ') else { + // No right part + return main_name; + }; + if PRODUCT_NAMES_WITHOUT_VERSION_NUMBER.contains(&left) + && right.chars().all(|c| c.is_ascii_digit()) + { + // Conforms to pattern, e.g. "Kontakt 7" + left + } else { + // Doesn't conform to pattern + main_name + } +} + +const VST_CACHE_FILE: &str = { + #[cfg(target_os = "windows")] + { + #[cfg(target_arch = "x86_64")] + { + "reaper-vstplugins64.ini" + } + #[cfg(target_arch = "x86")] + { + "reaper-vstplugins.ini" + } + #[cfg(target_arch = "arm64ec")] + { + "reaper-vstplugins64arwmin.ini" + } + } + #[cfg(target_os = "linux")] + { + #[cfg(target_arch = "x86_64")] + { + "reaper-vstplugins64.ini" + } + #[cfg(target_arch = "x86")] + { + "reaper-vstplugins.ini" + } + #[cfg(target_arch = "aarch64")] + { + "reaper-vstplugins_arm64.ini" + } + #[cfg(target_arch = "arm")] + { + "reaper-vstplugins.ini" + } + } + #[cfg(target_os = "macos")] + { + #[cfg(target_arch = "x86_64")] + { + "reaper-vstplugins64.ini" + } + #[cfg(target_arch = "aarch64")] + { + "reaper-vstplugins_arm64.ini" + } + } +}; + +const CLAP_CACHE_FILE: &str = { + #[cfg(target_os = "windows")] + { + #[cfg(target_arch = "x86_64")] + { + "reaper-clap-win64" + } + #[cfg(target_arch = "x86")] + { + "reaper-clap-win32" + } + #[cfg(target_arch = "arm64ec")] + { + "reaper-clap-winarm64" + } + } + #[cfg(target_os = "linux")] + { + #[cfg(target_arch = "x86_64")] + { + "reaper-clap-linux-x86_64" + } + #[cfg(target_arch = "x86")] + { + "reaper-clap-linux-i386" + } + #[cfg(target_arch = "aarch64")] + { + "reaper-clap-linux-aarch64" + } + #[cfg(target_arch = "arm")] + { + "reaper-clap-linux-arm" + } + } + #[cfg(target_os = "macos")] + { + #[cfg(target_arch = "x86_64")] + { + "reaper-clap-macos-x86_64" + } + #[cfg(target_arch = "aarch64")] + { + "reaper-clap-macos-aarch64.ini" + } + } +}; + +#[cfg(test)] +mod tests { + use crate::plugins::ProductName; + + #[test] + pub fn product_name_parsing_2() { + assert_eq!( + ProductName::parse("TDR Nova (Tokyo Dawn Labs)"), + Some(ProductName { + main: "TDR Nova", + arch: None, + company: "Tokyo Dawn Labs", + channels: None, + }) + ); + } + + #[test] + pub fn product_name_parsing_3_arch() { + assert_eq!( + ProductName::parse("VC 76 (x86_64) (Native Instruments GmbH)"), + Some(ProductName { + main: "VC 76", + arch: Some("x86_64"), + company: "Native Instruments GmbH", + channels: None, + }) + ); + } + + #[test] + pub fn product_name_parsing_3_ch() { + assert_eq!( + ProductName::parse("Surge XT (Surge Synth Team) (2->6ch)"), + Some(ProductName { + main: "Surge XT", + arch: None, + company: "Surge Synth Team", + channels: Some("2->6ch"), + }) + ); + } + + #[test] + pub fn product_name_parsing_4() { + assert_eq!( + ProductName::parse("Sitala (x86_64) (Decomposer) (32 out)"), + Some(ProductName { + main: "Sitala", + arch: Some("x86_64"), + company: "Decomposer", + channels: Some("32 out"), + }) + ); + } +} diff --git a/plugin-reaper-realearn/pot/src/pot_database.rs b/plugin-reaper-realearn/pot/src/pot_database.rs new file mode 100644 index 0000000..f2f1f8a --- /dev/null +++ b/plugin-reaper-realearn/pot/src/pot_database.rs @@ -0,0 +1,554 @@ +use crate::provider_database::{ + Database, DatabaseId, InnerFilterItem, ProviderContext, SortablePresetId, + FIL_HAS_PREVIEW_FALSE, FIL_HAS_PREVIEW_TRUE, FIL_IS_AVAILABLE_FALSE, FIL_IS_AVAILABLE_TRUE, + FIL_IS_FAVORITE_FALSE, FIL_IS_FAVORITE_TRUE, FIL_IS_SUPPORTED_FALSE, FIL_IS_SUPPORTED_TRUE, + FIL_IS_USER_PRESET_FALSE, FIL_IS_USER_PRESET_TRUE, FIL_PRODUCT_KIND_EFFECT, + FIL_PRODUCT_KIND_INSTRUMENT, FIL_PRODUCT_KIND_LOOP, FIL_PRODUCT_KIND_ONE_SHOT, +}; +use crate::providers::directory::{DirectoryDatabase, DirectoryDbConfig}; +use crate::providers::komplete::KompleteDatabase; +use crate::{ + preview_exists, BuildInput, Fil, FilterItem, FilterItemCollections, FilterItemId, Filters, + InnerBuildInput, PersistentDatabaseId, PersistentPresetId, PluginId, PotFavorites, PotPreset, + PresetId, PresetWithId, Stats, +}; +use base::{blocking_read_lock, blocking_write_lock}; + +use crate::plugins::PluginDatabase; +use crate::providers::defaults::DefaultsDatabase; +use crate::providers::ini::IniDatabase; + +use enumset::{enum_set, EnumSet}; +use helgobox_api::persistence::PotFilterKind; +use reaper_high::Reaper; +use std::collections::{BTreeMap, HashSet}; +use std::error::Error; +use std::fmt::Debug; +use std::ops::Deref; + +use base::hash_util::NonCryptoIndexSet; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use std::sync::{RwLock, RwLockReadGuard}; +use std::time::{Duration, Instant}; + +pub fn pot_db() -> &'static PotDatabase { + use once_cell::sync::Lazy; + static POT_DB: Lazy = Lazy::new(PotDatabase::open); + &POT_DB +} + +type BoxedDatabase = Box; +type DatabaseOpeningResult = Result; + +// The pot database is thread-safe! We achieve this by using internal read-write locks. Making it +// thread-safe greatly simplifies usage of PotDatabase from a consumer perspective, because we can +// easily obtain a static reference to it and let the pot database internals decide how to deal with +// concurrency. +// +// But not just that, we can also improve performance. If the pot database wouldn't be thread-safe, +// we would have to expose it wrapped by a mutex or read-write lock - which means less fine-granular +// locking. Either the whole thing is locked or not at all. +// +// By having the RwLocks around the provider databases and not around the database collection, we +// can have "more" concurrent access. E.g. find_preset_by_id, a function which can be called very +// often by the GUI, only read-locks one particular database, not all. So if another database +// is currently written to, it doesn't matter. It's just more flexible. Also, in future we might +// want to use some fork-join approach to refresh/search concurrently multiple databases. +// This would require having a RwLock around the database itself +// because we would need to pass the database reference to the async code with an Arc, but an Arc +// alone doesn't allow mutation of its contents. That's true even if the async database access would +// be read-only. The synchronous refresh would still need mutable access but we wouldn't be able to +// get one directly within an Arc. +pub struct PotDatabase { + plugin_db: RwLock, + databases: RwLock, + revision: AtomicU8, + detected_legacy_vst3_scan: AtomicBool, +} + +type Databases = BTreeMap>; + +#[derive(Clone, Debug, derive_more::Display)] +pub struct PotDatabaseError(String); + +impl Error for PotDatabaseError {} + +fn box_db_result( + opening_result: Result>, +) -> DatabaseOpeningResult { + let db = opening_result.map_err(|e| PotDatabaseError(e.to_string()))?; + Ok(Box::new(db)) +} + +impl PotDatabase { + pub fn open() -> Self { + let resource_path = Reaper::get().resource_path(); + let komplete_db = KompleteDatabase::open(); + let rfx_chain_db = { + let config = DirectoryDbConfig { + persistent_id: PersistentDatabaseId::new("fx-chains".to_string()), + root_dir: resource_path.join("FXChains"), + valid_extensions: &["RfxChain"], + name: "FX chains", + description: "All the RfxChain files in your FXChains directory", + }; + DirectoryDatabase::open(config) + }; + let track_template_db = { + let config = DirectoryDbConfig { + persistent_id: PersistentDatabaseId::new("track-templates".to_string()), + root_dir: resource_path.join("TrackTemplates"), + valid_extensions: &["RTrackTemplate"], + name: "Track templates", + description: "All the RTrackTemplate files in your TrackTemplates directory.\n\ + Doesn't load the complete track, only its FX chain!", + }; + DirectoryDatabase::open(config) + }; + let ini_db = IniDatabase::open( + PersistentDatabaseId::new("fx-presets".to_string()), + resource_path.join("presets"), + ); + let defaults_db = DefaultsDatabase::open(); + let databases = [ + box_db_result(komplete_db), + box_db_result(rfx_chain_db), + box_db_result(track_template_db), + box_db_result(ini_db), + box_db_result(Ok(defaults_db)), + ]; + let databases = databases + .into_iter() + .flatten() + .enumerate() + .map(|(i, db)| (DatabaseId(i as _), RwLock::new(db))) + .collect(); + Self { + plugin_db: Default::default(), + databases: RwLock::new(databases), + revision: Default::default(), + detected_legacy_vst3_scan: Default::default(), + } + } + + /// Returns a number that will be increased with each database refresh. + pub fn revision(&self) -> u8 { + self.revision.load(Ordering::Relaxed) + } + + pub fn refresh(&self) { + // Build provider context + let resource_path = Reaper::get().resource_path(); + // Crawl plug-ins + let plugin_db = PluginDatabase::crawl(&resource_path); + // In order to be able to query the legacy-vst3-scan result without having to lock the + // plug-in DB (which could lead to unresponsive UI), we save it as atomic bool right here. + self.detected_legacy_vst3_scan + .store(plugin_db.detected_legacy_vst3_scan(), Ordering::Relaxed); + let provider_context = ProviderContext::new(&plugin_db); + // Refresh databases + for db in self.read_lock_databases().values() { + let mut db = blocking_write_lock(db, "pot db refresh provider db"); + let _ = db.refresh(&provider_context); + } + // Memorize plug-ins + *blocking_write_lock(&self.plugin_db, "pot db refresh plugin db") = plugin_db; + // Increment revision + self.revision.fetch_add(1, Ordering::Relaxed); + } + + pub fn detected_legacy_vst3_scan(&self) -> bool { + self.detected_legacy_vst3_scan.load(Ordering::Relaxed) + } + + fn read_lock_databases(&self) -> RwLockReadGuard { + blocking_read_lock(&self.databases, "read-lock pot-db databases") + } + + fn read_lock_plugin_db(&self) -> RwLockReadGuard { + blocking_read_lock(&self.plugin_db, "read-lock plug-in database") + } + + pub fn add_database(&self, db: impl Database + Send + Sync + 'static) -> DatabaseId { + let mut databases = blocking_write_lock(&self.databases, "add_database"); + let new_db_id = DatabaseId(databases.len() as u32); + databases.insert(new_db_id, RwLock::new(Box::new(db))); + new_db_id + } + + pub fn build_collections( + &self, + mut input: BuildInput, + affected_kinds: EnumSet, + ) -> BuildOutput { + // Preparation + // TODO-high-pot Implement correctly as soon as favorites writable + let favorites = PotFavorites::default(); + let plugin_db = self.read_lock_plugin_db(); + let provider_context = ProviderContext::new(&plugin_db); + // Build constant filter collections + let mut total_output = BuildOutput { + supported_filter_kinds: enum_set!( + PotFilterKind::Database + | PotFilterKind::IsUser + | PotFilterKind::IsFavorite + | PotFilterKind::ProductKind + ), + ..Default::default() + }; + measure_duration(&mut total_output.stats.filter_query_duration, || { + add_constant_filter_items(affected_kinds, &mut total_output.filter_item_collections); + // Let all databases build filter collections and accumulate them + let mut database_filter_items = Vec::new(); + let mut used_product_ids = HashSet::new(); + for (db_id, db) in self.read_lock_databases().deref() { + // If the database is on the exclude list, we don't even want it to appear in the + // database list. + if input.filter_excludes.contains_database(*db_id) { + continue; + } + // Acquire database access + let db = blocking_read_lock(db, "pot db build_collections 1"); + // Create database filter item + let filter_item = FilterItem { + persistent_id: "".to_string(), + id: FilterItemId(Some(Fil::Database(*db_id))), + parent_name: None, + name: Some(db.name().to_string()), + icon: None, + more_info: Some(db.description().to_string()), + }; + database_filter_items.push(filter_item); + // Don't continue if database doesn't match filter + // (but it should appear on the list) + if !input.filters.database_matches(*db_id) { + continue; + } + // Add supported filter kinds + total_output.supported_filter_kinds |= db.supported_advanced_filter_kinds(); + // Build and accumulate filters collections + let inner_input = InnerBuildInput::new(&input, &favorites, *db_id); + let Ok(filter_collections) = + db.query_filter_collections(&provider_context, inner_input, affected_kinds) + else { + continue; + }; + // Add unique filter items directly to the list of filters. Gather shared filter + // items so we can deduplicate them later. + for (kind, items) in filter_collections.into_iter() { + let final_filter_items = items.into_iter().filter_map(|i| match i { + InnerFilterItem::Unique(i) => Some(i), + InnerFilterItem::Product(pid) => { + used_product_ids.insert(pid); + None + } + }); + total_output + .filter_item_collections + .extend(kind, final_filter_items); + } + } + // Process shared filter items + let product_filter_items = used_product_ids.into_iter().filter_map(|pid| { + let product = plugin_db.find_product_by_id(&pid)?; + let filter_item = FilterItem { + persistent_id: "".to_string(), + id: FilterItemId(Some(Fil::Product(pid))), + parent_name: None, + name: Some(product.name.clone()), + icon: None, + more_info: product.kind.map(|k| k.to_string()), + }; + Some(filter_item) + }); + total_output + .filter_item_collections + .extend(PotFilterKind::Bank, product_filter_items); + // Add database filter items + if affected_kinds.contains(PotFilterKind::Database) { + total_output + .filter_item_collections + .set(PotFilterKind::Database, database_filter_items); + } + // Important: At this point, some previously selected filters might not exist anymore. + // So we should reset them and not let them influence the preset query anymore! + input.filters.clear_if_not_available_anymore( + affected_kinds, + &total_output.filter_item_collections, + ); + }); + // Finally build + let mut sortable_preset_ids: Vec<_> = + measure_duration(&mut total_output.stats.preset_query_duration, || { + self.gather_preset_ids_internal(&input, &provider_context, &favorites) + }); + // Apply "has preview" filter if necessary (expensive!) + measure_duration(&mut total_output.stats.preview_filter_duration, || { + self.apply_has_preview_filter(&input.filters, &mut sortable_preset_ids); + }); + // Sort filter items and presets + measure_duration(&mut total_output.stats.sort_duration, || { + for (kind, collection) in total_output.filter_item_collections.iter_mut() { + if kind.wants_sorting() { + collection.sort_by(|i1, i2| { + lexical_sort::lexical_cmp(i1.sort_name(), i2.sort_name()) + }); + } + } + sortable_preset_ids.sort_by(|(_, p1), (_, p2)| { + lexical_sort::lexical_cmp(&p1.preset_name, &p2.preset_name) + }); + }); + // Index presets. Because later, we look up the preset index by the preset ID and vice versa + // and we want that to happen without complexity O(n)! There can be tons of presets! + measure_duration(&mut total_output.stats.index_duration, || { + total_output.preset_collection = sortable_preset_ids + .into_iter() + .map(|(db_id, p)| PresetId::new(db_id, p.inner_preset_id)) + .collect(); + }); + total_output + } + + fn apply_has_preview_filter( + &self, + filters: &Filters, + sortable_preset_ids: &mut Vec<(DatabaseId, SortablePresetId)>, + ) { + if let Some(wants_preview) = filters.wants_preview() { + let reaper_resource_dir = Reaper::get().resource_path(); + sortable_preset_ids.retain(|(db_id, sortable_preset_id)| { + let preset_id = PresetId::new(*db_id, sortable_preset_id.inner_preset_id); + if let Some(preset) = self.find_preset_by_id(preset_id) { + preview_exists(&preset, &reaper_resource_dir) == wants_preview + } else { + // Preset doesn't exist? Shouldn't happen, but treat it like a missing preview. + !wants_preview + } + }); + } + } + + /// Gathers an unsorted list of preset respecting all pre-filters. + pub fn gather_presets(&self, input: BuildInput) -> Vec { + // TODO-high-pot Implement correctly as soon as favorites writable + let favorites = PotFavorites::default(); + let plugin_db = self.read_lock_plugin_db(); + let provider_context = ProviderContext::new(&plugin_db); + self.gather_preset_ids_internal(&input, &provider_context, &favorites) + .into_iter() + .filter_map(|(db_id, sortable_preset_id)| { + let preset_id = PresetId::new(db_id, sortable_preset_id.inner_preset_id); + let preset = self.find_preset_by_id(preset_id)?; + Some(PresetWithId::new(preset_id, preset)) + }) + .collect() + } + + fn gather_preset_ids_internal( + &self, + input: &BuildInput, + provider_context: &ProviderContext, + favorites: &PotFavorites, + ) -> Vec<(DatabaseId, SortablePresetId)> { + self.read_lock_databases() + .deref() + .iter() + .filter(|(db_id, _)| { + input.filters.database_matches(**db_id) + && !input.filter_excludes.contains_database(**db_id) + }) + .filter_map(|(db_id, db)| { + // Acquire database access + let db = blocking_read_lock(db, "pot db build_collections 2"); + // Don't even try to get presets if one filter is set which is not + // supported by database. + if input + .filters + .any_unsupported_filter_is_set_to_concrete_value( + db.supported_advanced_filter_kinds(), + ) + { + return None; + } + // Let database build presets + let inner_input = InnerBuildInput::new(input, favorites, *db_id); + let preset_ids = db.query_presets(provider_context, inner_input).ok()?; + Some((*db_id, preset_ids)) + }) + .flat_map(|(db_id, preset_ids)| preset_ids.into_iter().map(move |p| (db_id, p))) + .collect() + } + + pub fn find_preset_by_id(&self, preset_id: PresetId) -> Option { + let plugin_db = self.read_lock_plugin_db(); + let provider_context = ProviderContext::new(&plugin_db); + let databases = self.read_lock_databases(); + let db = databases.get(&preset_id.database_id)?; + let db = blocking_read_lock(db, "pot db find_preset_by_id 1"); + db.find_preset_by_id(&provider_context, preset_id.preset_id) + } + + pub fn with_plugin_db(&self, f: impl FnOnce(&PluginDatabase) -> R) -> R { + f(&self.read_lock_plugin_db()) + } + + pub fn try_with_db( + &self, + db_id: DatabaseId, + f: impl FnOnce(&dyn Database) -> R, + ) -> Result { + let databases = self.read_lock_databases(); + let db = databases.get(&db_id).ok_or("database not found")?; + let db = db + .try_read() + .map_err(|_| "couldn't acquire provider db lock")?; + let r = f(&**db); + Ok(r) + } + + pub fn try_find_preset_by_id( + &self, + preset_id: PresetId, + ) -> Result, &'static str> { + let plugin_db = self + .plugin_db + .try_read() + .map_err(|_| "couldn't acquire plugin db lock")?; + let provider_context = ProviderContext::new(&plugin_db); + let databases = self.read_lock_databases(); + let db = databases + .get(&preset_id.database_id) + .ok_or("database not found")?; + let db = db + .try_read() + .map_err(|_| "couldn't acquire provider db lock")?; + Ok(db.find_preset_by_id(&provider_context, preset_id.preset_id)) + } + + /// Ignores exclude lists. + pub fn find_unsupported_preset_matching( + &self, + plugin_id: &PluginId, + preset_name: &str, + ) -> Option { + let product_id = { + let plugin_db = self.read_lock_plugin_db(); + let plugin = plugin_db.find_plugin_by_id(plugin_id)?; + plugin.common.core.product_id + }; + self.read_lock_databases().values().find_map(|db| { + // Acquire database access + let db = blocking_read_lock(db, "pot db find_unsupported_preset_matching"); + // Find preset + let preset = db.find_unsupported_preset_matching(product_id, preset_name)?; + Some(preset.common.persistent_id) + }) + } +} + +#[derive(Default)] +pub struct BuildOutput { + pub supported_filter_kinds: EnumSet, + pub filter_item_collections: FilterItemCollections, + pub preset_collection: NonCryptoIndexSet, + pub stats: Stats, +} + +fn measure_duration(duration: &mut Duration, f: impl FnOnce() -> R) -> R { + let start = Instant::now(); + let r = f(); + *duration = start.elapsed(); + r +} + +fn add_constant_filter_items( + affected_kinds: EnumSet, + filter_item_collections: &mut FilterItemCollections, +) { + if affected_kinds.contains(PotFilterKind::IsAvailable) { + filter_item_collections.set( + PotFilterKind::IsAvailable, + create_filter_items_is_available(), + ); + } + if affected_kinds.contains(PotFilterKind::IsSupported) { + filter_item_collections.set( + PotFilterKind::IsSupported, + create_filter_items_is_supported(), + ); + } + if affected_kinds.contains(PotFilterKind::IsFavorite) { + filter_item_collections.set(PotFilterKind::IsFavorite, create_filter_items_is_favorite()); + } + if affected_kinds.contains(PotFilterKind::IsUser) { + filter_item_collections.set(PotFilterKind::IsUser, create_filter_items_is_user()); + } + if affected_kinds.contains(PotFilterKind::HasPreview) { + filter_item_collections.set(PotFilterKind::HasPreview, create_filter_items_has_preview()); + } + if affected_kinds.contains(PotFilterKind::ProductKind) { + filter_item_collections.set( + PotFilterKind::ProductKind, + create_filter_items_product_kind(), + ); + } +} + +fn create_filter_items_is_available() -> Vec { + vec![ + FilterItem::simple(FIL_IS_AVAILABLE_FALSE, "Not available", '❌', ""), + FilterItem::simple( + FIL_IS_AVAILABLE_TRUE, + "Available", + '✔', + "Usually means that the \ + corresponding plug-in has been scanned before by REAPER.\n\ + For Komplete, it means that the preset file itself is available.", + ), + ] +} + +fn create_filter_items_is_supported() -> Vec { + vec![ + FilterItem::simple(FIL_IS_SUPPORTED_FALSE, "Not supported", '☹', ""), + FilterItem::simple( + FIL_IS_SUPPORTED_TRUE, + "Supported", + '☺', + "Means that Pot Browser \ + can automatically load the preset into the corresponding plug-in.", + ), + ] +} + +fn create_filter_items_is_favorite() -> Vec { + vec![ + FilterItem::simple(FIL_IS_FAVORITE_FALSE, "Not favorite", '☆', ""), + FilterItem::simple(FIL_IS_FAVORITE_TRUE, "Favorite", '★', ""), + ] +} + +fn create_filter_items_product_kind() -> Vec { + vec![ + FilterItem::none(), + FilterItem::simple(FIL_PRODUCT_KIND_INSTRUMENT, "Instrument", '🎹', ""), + FilterItem::simple(FIL_PRODUCT_KIND_EFFECT, "Effect", '✨', ""), + FilterItem::simple(FIL_PRODUCT_KIND_LOOP, "Loop", '➿', ""), + FilterItem::simple(FIL_PRODUCT_KIND_ONE_SHOT, "One shot", '💥', ""), + ] +} + +fn create_filter_items_is_user() -> Vec { + vec![ + FilterItem::simple(FIL_IS_USER_PRESET_FALSE, "Factory preset", '🏭', ""), + FilterItem::simple(FIL_IS_USER_PRESET_TRUE, "User preset", '🕵', ""), + ] +} + +fn create_filter_items_has_preview() -> Vec { + vec![ + FilterItem::simple(FIL_HAS_PREVIEW_FALSE, "No preview", '🔇', "Display only presets that have no preview. This filter can take very long when operating on a large preset list because it checks whether the preview files actually exist!"), + FilterItem::simple(FIL_HAS_PREVIEW_TRUE, "Has preview", '🔊', "Display only presets that have a preview. This filter can take very long when operating on a large preset list because it checks whether the preview files actually exist!"), + ] +} diff --git a/plugin-reaper-realearn/pot/src/preset_crawler.rs b/plugin-reaper-realearn/pot/src/preset_crawler.rs new file mode 100644 index 0000000..e6ac127 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/preset_crawler.rs @@ -0,0 +1,393 @@ +use crate::{ + parse_vst2_magic_number, parse_vst3_uid, pot_db, EscapeCatcher, PersistentPresetId, PluginId, +}; +use base::enigo::EnigoMouse; +use base::future_util::millis; +use base::hash_util::NonCryptoIndexMap; +use base::{blocking_lock_arc, file_util, hash_util}; +use base::{Mouse, MouseCursorPosition}; +use camino::{Utf8Path, Utf8PathBuf}; +use helgobox_api::persistence::MouseButton; +use reaper_high::{Fx, FxInfo, Reaper}; +use std::error::Error; +use std::fs; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::sync::{Arc, Mutex}; + +pub type SharedPresetCrawlingState = Arc>; + +#[derive(Debug)] +pub struct PresetCrawlingState { + crawled_presets: NonCryptoIndexMap, + duplicate_preset_names: Vec, + same_preset_name_in_a_row: Option, + same_preset_name_in_a_row_attempts: u32, + same_preset_names_like_beginning: Vec, + same_preset_name_like_beginning_attempts: u32, + total_bytes_crawled: usize, +} + +#[derive(Debug)] +pub struct PresetCrawlingOutcome { + /// One temporary file that holds the chunks of all FXs when crawling finished. + /// Will be copied to separate destination files at a later stage. + /// If not set when stopped, this means at first it's a failure. Later we take the + /// file out of here for processing, in that case it's also `None`. + pub chunks_file: File, + pub reason: PresetCrawlerStopReason, +} + +impl PresetCrawlingOutcome { + pub fn new(chunks_file: File, reason: PresetCrawlerStopReason) -> Self { + Self { + chunks_file, + reason, + } + } +} + +impl PresetCrawlingState { + pub fn new() -> SharedPresetCrawlingState { + let state = Self { + crawled_presets: Default::default(), + duplicate_preset_names: Default::default(), + same_preset_name_in_a_row: None, + same_preset_name_in_a_row_attempts: 0, + same_preset_names_like_beginning: Default::default(), + same_preset_name_like_beginning_attempts: 0, + total_bytes_crawled: 0, + }; + Arc::new(Mutex::new(state)) + } + + pub fn last_crawled_preset(&self) -> Option<&CrawledPreset> { + let last = self.crawled_presets.last()?; + Some(last.1) + } + + pub fn pop_crawled_preset(&mut self) -> Option { + let last = self.crawled_presets.pop()?; + Some(last.1) + } + + pub fn bytes_crawled(&self) -> usize { + self.total_bytes_crawled + } + + pub fn crawled_presets(&self) -> &NonCryptoIndexMap { + &self.crawled_presets + } + + pub fn preset_count(&self) -> u32 { + self.crawled_presets.len() as _ + } + + pub fn duplicate_preset_name_count(&self) -> u32 { + self.duplicate_preset_names.len() as _ + } + + pub fn duplicate_preset_names(&self) -> &[String] { + &self.duplicate_preset_names + } + + fn add_preset(&mut self, preset: CrawledPreset, never_stop_crawling: bool) -> NextCrawlStep { + // Give stop signal if we reached the end of the list or are at its beginning again. + if !never_stop_crawling { + if let Some(step) = self.make_stop_check(&preset) { + return step; + } + } + // Reset "same preset name attempts" logic + self.same_preset_name_in_a_row_attempts = 0; + if let Some(last_same_preset_name) = self.same_preset_name_in_a_row.take() { + // Turns out that the last discovered same preset name was actually not the end + // of the preset list but just an intermediate duplicate. Treat it as such! + self.duplicate_preset_names.push(last_same_preset_name); + } + // Reset "same preset name like beginning" logic + self.same_preset_name_like_beginning_attempts = 0; + self.duplicate_preset_names + .append(&mut self.same_preset_names_like_beginning); + // Add or skip + if self.crawled_presets.contains_key(&preset.name) { + // Duplicate name. Skip preset! + self.duplicate_preset_names.push(preset.name); + } else { + // Add preset + self.total_bytes_crawled += preset.size_in_bytes; + self.crawled_presets.insert(preset.name.clone(), preset); + } + NextCrawlStep::Continue + } + + /// This executes a heuristic to check whether the end of the preset list has been reached and + /// crawling should therefore stop. + /// + /// It looks at the preset names only. I also tried to take the chunk into account but it's not + /// deterministic. Getting the chunk for one preset multiple times can yield different results! + fn make_stop_check(&mut self, preset: &CrawledPreset) -> Option { + // If we haven't crawled anything yet, there's nothing to check. + let (_, last_preset) = self.crawled_presets.last()?; + // Check if we get multiple equally named presets in a row. + if preset.name == last_preset.name { + // Same name like last crawled preset + if self.same_preset_name_in_a_row_attempts <= MAX_SAME_PRESET_NAME_IN_A_ROW_ATTEMPTS { + // Let's tolerate that right now and still continue crawling. + // It's possible that the plug-in crops the preset name and therefore + // presets that seemingly have the same name, in fact have different ones + // but have the same prefix. This happened with Zebra2 VSTi, for example. + self.same_preset_name_in_a_row_attempts += 1; + // Don't add it to the list of duplicates right away because it *might* really + // turn out to be the end of the preset list! If it turns out it isn't, we still add + // it to the list of duplicates later. + self.same_preset_name_in_a_row = Some(preset.name.clone()); + return Some(NextCrawlStep::Continue); + } else { + // More than max same preset names in a row! That either means the + // "Next preset" button doesn't work at all or we have reached the end of the + // preset list. + return Some(NextCrawlStep::Stop( + PresetCrawlerStopReason::PresetNameNotChangingAnymore, + )); + } + } + // Now check if the presets that we crawl are the same ones that we crawled in the beginning. + if let Some((_, reference_preset)) = self + .crawled_presets + .get_index(self.same_preset_name_like_beginning_attempts as usize) + { + if preset.name == reference_preset.name { + // This preset has the same name as the reference preset, which is one of the + // presets crawled right at the beginning. + if self.same_preset_name_like_beginning_attempts + <= MAX_SAME_PRESET_NAME_LIKE_BEGINNING_ATTEMPTS + { + // Let's tolerate that right now and still continue crawling. + // It's possible that the plug-in doesn't navigate through the preset list in + // a linear way. + self.same_preset_name_like_beginning_attempts += 1; + // Don't add it to the list of duplicates right away because it *might* really + // turn out to be the beginning of the preset list! If it turns out it isn't, + // we still add it to the list of duplicates later. + self.same_preset_names_like_beginning + .push(preset.name.clone()); + return Some(NextCrawlStep::Continue); + } else { + // More than max matches with the beginning! That either means the plug-in + // navigates in a *very* non-linear fashion through the preset list or we have + // reached the end of the preset list and restarted at its beginning. + return Some(NextCrawlStep::Stop( + PresetCrawlerStopReason::PresetNameLikeBeginning, + )); + } + } + } + None + } +} + +enum NextCrawlStep { + Continue, + Stop(PresetCrawlerStopReason), +} + +#[derive(Debug)] +pub struct CrawledPreset { + name: String, + offset: u64, + size_in_bytes: usize, + destination: Utf8PathBuf, +} + +impl CrawledPreset { + pub fn name(&self) -> &str { + &self.name + } + + pub fn destination(&self) -> &Utf8Path { + &self.destination + } +} + +pub struct CrawlPresetArgs { + pub fx: Fx, + pub next_preset_cursor_pos: MouseCursorPosition, + pub state: SharedPresetCrawlingState, + pub stop_if_destination_exists: bool, + pub never_stop_crawling: bool, + pub bring_focus_back_to_crawler: F, +} + +pub async fn crawl_presets( + args: CrawlPresetArgs, +) -> Result> +where + F: Fn() + 'static, +{ + let reaper_resource_dir = Reaper::get().resource_path(); + // No need to fall back to chunk-based FX info because Pot is experimental, and we can assume it's used + // with recent REAPER versions. + let fx_info = args.fx.info()?; + let plugin_id = get_plugin_id_from_fx_info(&fx_info); + let mut mouse = EnigoMouse::new(); + let escape_catcher = EscapeCatcher::new(); + let mut chunks_file = tempfile::tempfile()?; + let mut current_file_offset = 0u64; + loop { + // Check if escape has been pressed + if escape_catcher.escape_was_pressed() { + return Ok(PresetCrawlingOutcome::new( + chunks_file, + PresetCrawlerStopReason::Interrupted, + )); + } + // Get preset name + let name = args + .fx + .preset_name() + .ok_or("couldn't get preset name")? + .into_string(); + { + // Query chunk and save it in temporary file + let fx_chunk = args.fx.chunk()?; + let fx_chunk_content = fx_chunk.content(); + let fx_chunk_bytes = fx_chunk_content.as_bytes(); + chunks_file.write_all(fx_chunk_bytes)?; + chunks_file.flush()?; + // Determine where on the disk the RfxChain file should end up + let destination = determine_preset_file_destination( + &fx_info, + &reaper_resource_dir, + &name, + plugin_id.as_ref(), + ); + if args.stop_if_destination_exists && destination.exists() { + return Ok(PresetCrawlingOutcome::new( + chunks_file, + PresetCrawlerStopReason::DestinationFileExists, + )); + } + // Build crawled preset + let crawled_preset = CrawledPreset { + destination, + name, + offset: current_file_offset, + size_in_bytes: fx_chunk_bytes.len(), + }; + current_file_offset += fx_chunk_bytes.len() as u64; + let next_step = blocking_lock_arc(&args.state, "crawl_presets 3") + .add_preset(crawled_preset, args.never_stop_crawling); + match next_step { + NextCrawlStep::Stop(reason) => { + return Ok(PresetCrawlingOutcome::new(chunks_file, reason)); + } + NextCrawlStep::Continue => {} + } + } + // Click "Next preset" button + args.fx.show_in_floating_window()?; + mouse.set_cursor_position(args.next_preset_cursor_pos)?; + moment().await; + mouse.press(MouseButton::Left)?; + moment().await; + mouse.release(MouseButton::Left)?; + a_bit_longer().await; + } +} + +fn determine_preset_file_destination( + fx_info: &FxInfo, + reaper_resource_dir: &Utf8Path, + preset_name: &str, + plugin_id: Option<&PluginId>, +) -> Utf8PathBuf { + if let Some(persistent_preset_id) = find_shimmable_preset(plugin_id, preset_name) { + // Matched with existing unsupported preset. Create RfxChain file, a so called shim file, + // but not in the FX chain directory because we don't want it to show up in the FX chain + // database. Instead, we want the original preset (probably in the Komplete database) + // to become loadable. There's logic in our preset loading mechanism that looks for + // a shim file if it realizes that the preset can't be loaded. A kind of fallback! + get_shim_file_path(reaper_resource_dir, &persistent_preset_id) + } else { + // No match with existing unsupported preset + let sanitized_effect_name = sanitize_filename::sanitize(&fx_info.effect_name); + let file_name = format!("{}.RfxChain", &preset_name); + let sanitized_file_name = sanitize_filename::sanitize(file_name); + reaper_resource_dir + .join("FXChains/Pot") + .join(sanitized_effect_name) + .join(sanitized_file_name) + } +} + +/// Returns the file name of the original preset. +fn find_shimmable_preset( + plugin_id: Option<&PluginId>, + preset_name: &str, +) -> Option { + let plugin_id = plugin_id?; + pot_db().find_unsupported_preset_matching(plugin_id, preset_name) +} + +fn get_plugin_id_from_fx_info(fx_info: &FxInfo) -> Option { + let plugin_id = match fx_info.sub_type_expression.as_str() { + "VST" | "VSTi" => PluginId::vst2(parse_vst2_magic_number(&fx_info.id).ok()?), + "VST3" | "VST3i" => PluginId::vst3(parse_vst3_uid(&fx_info.id).ok()?), + // Komplete doesn't support CLAP or JS anyway, so not important right now. + _ => return None, + }; + Some(plugin_id) +} + +pub async fn import_crawled_presets( + state: SharedPresetCrawlingState, + mut chunks_file: File, +) -> Result<(), Box> { + loop { + let p = blocking_lock_arc(&state, "import_crawled_presets").pop_crawled_preset(); + let Some(p) = p else { + break; + }; + let dest_file_path = &p.destination; + let dest_dir_path = p.destination.parent().ok_or("destination without parent")?; + fs::create_dir_all(dest_dir_path)?; + chunks_file.seek(SeekFrom::Start(p.offset))?; + let mut buf = vec![0; p.size_in_bytes]; + chunks_file.read_exact(&mut buf)?; + fs::write(dest_file_path, buf)?; + } + Ok(()) +} + +async fn a_bit_longer() { + millis(100).await; +} + +async fn moment() { + millis(50).await; +} + +const MAX_SAME_PRESET_NAME_IN_A_ROW_ATTEMPTS: u32 = 10; +const MAX_SAME_PRESET_NAME_LIKE_BEGINNING_ATTEMPTS: u32 = 10; + +pub fn get_shim_file_path( + reaper_resource_dir: &Utf8Path, + preset_id: &PersistentPresetId, +) -> Utf8PathBuf { + // We don't need to + let hash = + hash_util::calculate_persistent_non_crypto_hash_one_shot(preset_id.to_string().as_bytes()); + let file_name = file_util::convert_hash_to_dir_structure(hash, ".RfxChain"); + reaper_resource_dir + .join("Helgoboss/Pot/shims") + .join(file_name) +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum PresetCrawlerStopReason { + Interrupted, + DestinationFileExists, + PresetNameNotChangingAnymore, + PresetNameLikeBeginning, +} diff --git a/plugin-reaper-realearn/pot/src/preview_recorder.rs b/plugin-reaper-realearn/pot/src/preview_recorder.rs new file mode 100644 index 0000000..0f65708 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/preview_recorder.rs @@ -0,0 +1,254 @@ +use crate::provider_database::{ + FIL_IS_AVAILABLE_TRUE, FIL_IS_SUPPORTED_TRUE, FIL_PRODUCT_KIND_INSTRUMENT, +}; +use crate::{ + pot_db, preview_exists, BuildInput, Destination, EscapeCatcher, FilterItemId, + LoadPresetOptions, LoadPresetWindowBehavior, PluginId, PotPreset, PotPresetKind, PresetWithId, + ProductId, SharedRuntimePotUnit, +}; +use base::future_util::millis; +use base::hash_util::PersistentHash; +use base::{blocking_lock_arc, blocking_write_lock, file_util}; +use camino::{Utf8Path, Utf8PathBuf}; +use helgobox_api::persistence::PotFilterKind; +use reaper_high::{Project, Reaper}; +use reaper_medium::{CommandId, OpenProjectBehavior, ProjectContext, ProjectInfoAttributeKey}; +use std::error::Error; +use std::sync::{Arc, RwLock}; + +pub type SharedPreviewRecorderState = Arc>; + +#[derive(Debug)] +pub struct PreviewRecorderState { + pub todos: Vec, + pub failures: Vec, +} + +impl PreviewRecorderState { + pub fn new(todos: Vec) -> Self { + Self { + todos, + failures: vec![], + } + } +} + +#[derive(Debug)] +pub struct PreviewRecorderFailure { + pub preset: PresetWithId, + pub reason: String, +} + +impl AsRef for PreviewRecorderFailure { + fn as_ref(&self) -> &PotPreset { + &self.preset.preset + } +} + +pub struct RecordPreviewsArgs<'a> { + pub shared_pot_unit: SharedRuntimePotUnit, + pub state: SharedPreviewRecorderState, + /// RPP file that is used to render the previews + pub preview_rpp: &'a Utf8Path, + pub config: PreviewOutputConfig, +} + +#[derive(Clone, Debug)] +pub enum PreviewOutputConfig { + /// For playback within Pot Browser. + ForPotBrowserPlayback, + /// For export to custom location. + Export(ExportPreviewOutputConfig), +} + +#[derive(Clone, Debug)] +pub struct ExportPreviewOutputConfig { + /// Where to put the preview files. + pub base_dir: Utf8PathBuf, +} + +pub async fn record_previews(args: RecordPreviewsArgs<'_>) -> Result<(), Box> { + let reaper = Reaper::get(); + let reaper_resource_dir = reaper.resource_path(); + // Open preview project template in new tab + let project = open_preview_project_in_new_tab(args.preview_rpp); + moment().await; + // Prepare destination (first track, first FX) + let first_track = project + .first_track() + .ok_or("preview must have at least one track")?; + let destination = Destination { + chain: first_track.normal_fx_chain(), + fx_index: 0, + }; + let cloned_state = args.state.clone(); + let report_failure = move |preset: PresetWithId, reason: String| { + let mut state = blocking_write_lock(&cloned_state, "record_previews state 2"); + let failure = PreviewRecorderFailure { preset, reason }; + state.failures.push(failure); + }; + let escape_catcher = EscapeCatcher::new(); + // Loop over the preset list + loop { + // Check if escape has been pressed + if escape_catcher.escape_was_pressed() { + break; + } + // Take new preset to be recorded + moment().await; + let Some(preset_with_id) = blocking_write_lock(&args.state, "record_previews state") + .todos + .pop() + else { + // Done! + break; + }; + // Determine destination file + let preset = &preset_with_id.preset; + let preview_file_path = match &args.config { + PreviewOutputConfig::ForPotBrowserPlayback => { + // Prefer creating preview file name based on preset content. That means whenever the + // content changes, we get a different preview file name. That is cool. + let hash = preset.common.content_or_id_hash(); + get_preview_file_path_from_hash(&reaper_resource_dir, hash) + } + PreviewOutputConfig::Export(c) => { + let product_dir_name = preset + .common + .product_name + .as_deref() + .unwrap_or("Unknown product"); + c.base_dir.join(product_dir_name).join(&preset.common.name) + } + }; + // Load preset + let options = LoadPresetOptions { + window_behavior_override: Some(LoadPresetWindowBehavior::AlwaysShow), + audio_sample_behavior: Default::default(), + }; + { + let load_result = blocking_lock_arc(&args.shared_pot_unit, "record_previews pot unit") + .load_preset_at(preset, options, &|_| Ok(destination.clone())); + if let Err(e) = load_result { + report_failure(preset_with_id, e.to_string()); + continue; + } + } + moment().await; + // Record preview + if let Err(e) = render_to_file(project, &preview_file_path) { + report_failure(preset_with_id, e.to_string()); + } + } + Ok(()) +} + +fn render_to_file(project: Project, full_path: &Utf8Path) -> Result<(), Box> { + let reaper = Reaper::get(); + let medium_reaper = reaper.medium_reaper(); + let dir = full_path.parent().ok_or("render path has not parent")?; + let file_name = full_path + .file_name() + .ok_or("render path has no file name")?; + medium_reaper.get_set_project_info_string_set( + ProjectContext::Proj(project.raw()), + ProjectInfoAttributeKey::RenderFile, + dir.as_str(), + )?; + medium_reaper.get_set_project_info_string_set( + ProjectContext::Proj(project.raw()), + ProjectInfoAttributeKey::RenderPattern, + file_name, + )?; + // "File: Render project, using the most recent render settings, auto-close render dialog" + reaper + .main_section() + .action_by_command_id(CommandId::new(42230)) + .invoke_as_trigger(Some(project), None)?; + Ok(()) +} + +fn open_preview_project_in_new_tab(preview_rpp: &Utf8Path) -> Project { + let reaper = Reaper::get(); + let project = reaper.create_empty_project_in_new_tab(); + let mut behavior = OpenProjectBehavior::default(); + behavior.prompt = false; + behavior.open_as_template = true; + reaper + .medium_reaper() + .main_open_project(preview_rpp, behavior); + project +} + +async fn moment() { + millis(200).await; +} + +pub fn get_preview_file_path_from_hash( + reaper_resource_dir: &Utf8Path, + hash: PersistentHash, +) -> Utf8PathBuf { + let file_name = file_util::convert_hash_to_dir_structure(hash, ".ogg"); + reaper_resource_dir + .join("Helgoboss/Pot/previews") + .join(file_name) +} + +/// Can take long. +pub fn prepare_preview_recording( + mut build_input: BuildInput, + output_config: &PreviewOutputConfig, +) -> Vec { + // We want only available and supported instruments + build_input.filters.set( + PotFilterKind::ProductKind, + Some(FilterItemId(Some(FIL_PRODUCT_KIND_INSTRUMENT))), + ); + build_input.filters.set( + PotFilterKind::IsAvailable, + Some(FilterItemId(Some(FIL_IS_AVAILABLE_TRUE))), + ); + build_input.filters.set( + PotFilterKind::IsSupported, + Some(FilterItemId(Some(FIL_IS_SUPPORTED_TRUE))), + ); + // Gather + let mut presets = pot_db().gather_presets(build_input); + if matches!(output_config, PreviewOutputConfig::ForPotBrowserPlayback) { + // Take only those that don't have a preview within Pot Browser yet + let reaper_resource_dir = Reaper::get().resource_path(); + presets.retain(|p| !preview_exists(&p.preset, &reaper_resource_dir)); + } + // Reverse, so we can efficiently pop from the front later on + presets.reverse(); + // Sort by plug-in + presets.sort_by(|left, right| bucket(left).cmp(&bucket(right))); + presets +} + +fn bucket(preset_with_id: &PresetWithId) -> BucketId { + let preset = &preset_with_id.preset; + if !preset.common.plugin_ids.is_empty() { + return BucketId::Plugin(&preset.common.plugin_ids); + } + if !preset.common.product_ids.is_empty() { + return BucketId::ProductId(&preset.common.product_ids); + } + if let PotPresetKind::FileBased(kind) = &preset.kind { + return BucketId::FileExtension(&kind.file_ext); + } + BucketId::Remaining +} + +#[derive(Eq, PartialEq, Ord, PartialOrd)] +enum BucketId<'a> { + /// The plug-in ID is certainly the best criteria here! We have that for all databases except + /// Komplete. For FX chains and track templates, we might have multiple plug-ins. The order of + /// these plug-ins is important because different orders will reload the plug-ins. + Plugin(&'a [PluginId]), + /// The next best bet is + ProductId(&'a [ProductId]), + /// And finally: File extension. + FileExtension(&'a str), + Remaining, +} diff --git a/plugin-reaper-realearn/pot/src/provider_database.rs b/plugin-reaper-realearn/pot/src/provider_database.rs new file mode 100644 index 0000000..84b1e10 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/provider_database.rs @@ -0,0 +1,132 @@ +use crate::plugins::{PluginDatabase, ProductKind}; +use crate::{ + Fil, FilterItem, FilterItemId, GenericFilterItemCollections, HasFilterItemId, InnerBuildInput, + InnerPresetId, PersistentDatabaseId, PotPreset, ProductId, +}; +use enumset::{enum_set, EnumSet}; +use helgobox_api::persistence::PotFilterKind; +use std::borrow::Cow; +use std::error::Error; + +/// A database ID that's only stable during the runtime of ReaLearn. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct DatabaseId(pub u32); + +pub trait Database { + fn persistent_id(&self) -> &PersistentDatabaseId; + + // TODO-medium-performace Maybe we should require this to be a reference. + fn name(&self) -> Cow; + + // TODO-medium-performace Maybe we should require this to be a reference. + fn description(&self) -> Cow; + + fn supported_advanced_filter_kinds(&self) -> EnumSet { + enum_set!() + } + + fn refresh(&mut self, context: &ProviderContext) -> Result<(), Box>; + + fn query_filter_collections( + &self, + context: &ProviderContext, + input: InnerBuildInput, + affected_kinds: EnumSet, + ) -> Result>; + + fn query_presets( + &self, + context: &ProviderContext, + input: InnerBuildInput, + ) -> Result, Box>; + + fn find_preset_by_id( + &self, + context: &ProviderContext, + preset_id: InnerPresetId, + ) -> Option; + + /// Tries to find a preset that belongs to the given product and has the given name *and* + /// most importantly a preset file format that can't be loaded by Pot Browser. + /// + /// This is used by the preset crawler to identify whether a crawled preset can be used to + /// make a preset with an unsupported format actually loadable. Only makes sense for Komplete + /// at the moment because this is the only database which exposes unsupported presets. + fn find_unsupported_preset_matching( + &self, + product_id: ProductId, + preset_name: &str, + ) -> Option { + let _ = (product_id, preset_name); + None + } +} + +pub type InnerFilterItemCollections = GenericFilterItemCollections; + +pub enum InnerFilterItem { + /// A unique final filter item. Only makes sense within a specific database and within the + /// context of a specific pot filter item kind. Not deduplicated. + Unique(FilterItem), + /// A filter item representing a particular product (product for which the preset is made). + /// + /// Will be deduplicated by the pot database! + Product(ProductId), +} + +impl HasFilterItemId for InnerFilterItem { + fn id(&self) -> FilterItemId { + match self { + InnerFilterItem::Unique(i) => i.id, + InnerFilterItem::Product(i) => FilterItemId(Some(Fil::Product(*i))), + } + } +} + +pub struct SortablePresetId { + pub inner_preset_id: InnerPresetId, + pub preset_name: String, +} + +impl SortablePresetId { + pub fn new(i: u32, preset_name: String) -> Self { + Self { + inner_preset_id: InnerPresetId(i), + preset_name, + } + } +} + +#[derive(Copy, Clone)] +pub struct ProviderContext<'a> { + pub plugin_db: &'a PluginDatabase, +} + +impl<'a> ProviderContext<'a> { + pub fn new(plugin_db: &'a PluginDatabase) -> Self { + Self { plugin_db } + } +} + +/// Komplete content path state ID = 1 +pub const FIL_IS_AVAILABLE_TRUE: Fil = Fil::Boolean(true); +/// Komplete content path state ID = 4 +pub const FIL_IS_AVAILABLE_FALSE: Fil = Fil::Boolean(false); +pub const FIL_IS_SUPPORTED_TRUE: Fil = Fil::Boolean(true); +pub const FIL_IS_SUPPORTED_FALSE: Fil = Fil::Boolean(false); +pub const FIL_IS_FAVORITE_TRUE: Fil = Fil::Boolean(true); +pub const FIL_IS_FAVORITE_FALSE: Fil = Fil::Boolean(false); +/// Komplete content type ID = 1 +pub const FIL_IS_USER_PRESET_TRUE: Fil = Fil::Boolean(true); +/// Komplete content type ID = 2 +pub const FIL_IS_USER_PRESET_FALSE: Fil = Fil::Boolean(false); +/// Komplete product type ID = 1 +pub const FIL_PRODUCT_KIND_INSTRUMENT: Fil = Fil::ProductKind(ProductKind::Instrument); +/// Komplete product type ID = 2 +pub const FIL_PRODUCT_KIND_EFFECT: Fil = Fil::ProductKind(ProductKind::Effect); +/// Komplete product type ID = 4 +pub const FIL_PRODUCT_KIND_LOOP: Fil = Fil::ProductKind(ProductKind::Loop); +/// Komplete product type ID = 8 +pub const FIL_PRODUCT_KIND_ONE_SHOT: Fil = Fil::ProductKind(ProductKind::OneShot); +pub const FIL_HAS_PREVIEW_TRUE: Fil = Fil::Boolean(true); +pub const FIL_HAS_PREVIEW_FALSE: Fil = Fil::Boolean(false); diff --git a/plugin-reaper-realearn/pot/src/providers/defaults.rs b/plugin-reaper-realearn/pot/src/providers/defaults.rs new file mode 100644 index 0000000..827382f --- /dev/null +++ b/plugin-reaper-realearn/pot/src/providers/defaults.rs @@ -0,0 +1,151 @@ +use crate::provider_database::{ + Database, InnerFilterItem, InnerFilterItemCollections, ProviderContext, SortablePresetId, +}; +use crate::{ + create_plugin_factory_preset, FilterInput, InnerBuildInput, InnerPresetId, + PersistentDatabaseId, PersistentInnerPresetId, PersistentPresetId, PluginId, + PluginIdInPipeFormat, PotPreset, SearchInput, +}; +use std::borrow::Cow; + +use crate::plugins::PluginCommon; +use either::Either; +use enumset::{enum_set, EnumSet}; +use helgobox_api::persistence::PotFilterKind; +use itertools::Itertools; +use std::error::Error; +use std::iter; + +pub struct DefaultsDatabase { + persistent_id: PersistentDatabaseId, + plugins: Vec, +} + +impl Default for DefaultsDatabase { + fn default() -> Self { + Self { + persistent_id: PersistentDatabaseId::new("fx-defaults".to_string()), + plugins: vec![], + } + } +} + +impl DefaultsDatabase { + pub fn open() -> Self { + Default::default() + } + + fn query_presets_internal<'a>( + &'a self, + filter_input: &'a FilterInput, + ) -> impl Iterator + 'a { + let matches = !filter_input.filters.wants_user_presets_only(); + if !matches { + return Either::Left(iter::empty()); + } + let iter = self.plugins.iter().enumerate().filter(|(i, p)| { + let id = InnerPresetId(*i as _); + filter_input.everything_matches(Some(&p.core), id) + }); + Either::Right(iter) + } +} + +impl Database for DefaultsDatabase { + fn persistent_id(&self) -> &PersistentDatabaseId { + &self.persistent_id + } + + fn name(&self) -> Cow { + "FX defaults".into() + } + + fn description(&self) -> Cow { + "Default factory presets for all of your plug-ins".into() + } + + fn supported_advanced_filter_kinds(&self) -> EnumSet { + enum_set!(PotFilterKind::Bank) + } + + fn refresh(&mut self, ctx: &ProviderContext) -> Result<(), Box> { + // We clone the plug-in list so we can create own own order and maintain stable IDs. + self.plugins = ctx.plugin_db.plugins().map(|p| p.common.clone()).collect(); + Ok(()) + } + + fn query_filter_collections( + &self, + _: &ProviderContext, + input: InnerBuildInput, + _: EnumSet, + ) -> Result> { + let mut new_filters = *input.filter_input.filters; + new_filters.clear_this_and_dependent_filters(PotFilterKind::Bank); + let product_items = self + .query_presets_internal(&input.filter_input.with_filters(&new_filters)) + .map(|(_, plugin)| plugin.core.product_id) + .unique() + .map(InnerFilterItem::Product) + .collect(); + let mut collections = InnerFilterItemCollections::empty(); + collections.set(PotFilterKind::Bank, product_items); + Ok(collections) + } + + fn query_presets( + &self, + _: &ProviderContext, + input: InnerBuildInput, + ) -> Result, Box> { + let preset_ids = self + .query_presets_internal(&input.filter_input) + .filter(|(_, entry)| { + let search_input = DefaultSearchInput { entry }; + input.search_evaluator.matches(search_input) + }) + .map(|(i, _)| SortablePresetId::new(i as _, PRESET_NAME.to_string())) + .collect(); + Ok(preset_ids) + } + + fn find_preset_by_id( + &self, + _: &ProviderContext, + preset_id: InnerPresetId, + ) -> Option { + let plugin = self.plugins.get(preset_id.0 as usize)?; + let persistent_id = PersistentPresetId::new( + self.persistent_id.clone(), + create_persistent_inner_id(&plugin.core.id), + ); + let preset = create_plugin_factory_preset(plugin, persistent_id, PRESET_NAME.to_string()); + Some(preset) + } +} + +const PRESET_NAME: &str = ""; + +/// Example: `vst2|1967946098` +fn create_persistent_inner_id(plugin_id: &PluginId) -> PersistentInnerPresetId { + let id = PluginIdInPipeFormat(plugin_id).to_string(); + PersistentInnerPresetId::new(id) +} + +struct DefaultSearchInput<'a> { + entry: &'a PluginCommon, +} + +impl SearchInput for DefaultSearchInput<'_> { + fn preset_name(&self) -> &str { + PRESET_NAME + } + + fn product_name(&self) -> Option> { + Some(self.entry.to_string().into()) + } + + fn file_extension(&self) -> Option<&str> { + None + } +} diff --git a/plugin-reaper-realearn/pot/src/providers/directory.rs b/plugin-reaper-realearn/pot/src/providers/directory.rs new file mode 100644 index 0000000..e2add67 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/providers/directory.rs @@ -0,0 +1,293 @@ +use crate::provider_database::{ + Database, InnerFilterItem, InnerFilterItemCollections, ProviderContext, SortablePresetId, +}; +use crate::{ + FiledBasedPotPresetKind, FilterInput, InnerBuildInput, InnerPresetId, PersistentDatabaseId, + PersistentInnerPresetId, PersistentPresetId, PipeEscaped, PluginId, PotPreset, PotPresetCommon, + PotPresetKind, SearchInput, +}; +use std::borrow::Cow; + +use crate::plugins::{PluginCore, PluginDatabase}; +use base::hash_util::{NonCryptoHashSet, NonCryptoIndexMap, PersistentHash, PersistentHasher}; +use camino::Utf8PathBuf; +use either::Either; +use enumset::{enum_set, EnumSet}; +use helgobox_api::persistence::PotFilterKind; +use itertools::Itertools; +use std::error::Error; +use std::ffi::OsStr; +use std::fs::File; +use std::hash::Hasher; +use std::io::{BufRead, BufReader}; +use std::iter; +use std::path::Path; +use walkdir::WalkDir; + +pub struct DirectoryDatabase { + persistent_id: PersistentDatabaseId, + root_dir: Utf8PathBuf, + valid_extensions: NonCryptoHashSet<&'static OsStr>, + name: &'static str, + description: &'static str, + entries: Vec, +} + +pub struct DirectoryDbConfig { + pub persistent_id: PersistentDatabaseId, + pub root_dir: Utf8PathBuf, + pub valid_extensions: &'static [&'static str], + pub name: &'static str, + pub description: &'static str, +} + +impl DirectoryDatabase { + pub fn open(config: DirectoryDbConfig) -> Result> { + if !config.root_dir.try_exists()? { + return Err("path to root directory doesn't exist".into()); + } + let db = Self { + persistent_id: config.persistent_id, + name: config.name, + entries: Default::default(), + root_dir: config.root_dir, + valid_extensions: config.valid_extensions.iter().map(OsStr::new).collect(), + description: config.description, + }; + Ok(db) + } + + fn query_presets_internal<'a>( + &'a self, + filter_input: &'a FilterInput, + ) -> impl Iterator + 'a { + let matches = !filter_input.filters.wants_factory_presets_only(); + if !matches { + return Either::Left(iter::empty()); + } + let iter = self.entries.iter().enumerate().filter(|(id, e)| { + let id = InnerPresetId(*id as _); + e.plugin_cores + .values() + .any(|core| filter_input.everything_matches(Some(core), id)) + }); + Either::Right(iter) + } +} + +struct PresetEntry { + preset_name: String, + relative_path: String, + plugin_cores: NonCryptoIndexMap, + content_hash: PersistentHash, +} + +impl Database for DirectoryDatabase { + fn persistent_id(&self) -> &PersistentDatabaseId { + &self.persistent_id + } + + fn name(&self) -> Cow { + self.name.into() + } + + fn description(&self) -> Cow { + self.description.into() + } + + fn supported_advanced_filter_kinds(&self) -> EnumSet { + enum_set!(PotFilterKind::Bank) + } + + fn refresh(&mut self, ctx: &ProviderContext) -> Result<(), Box> { + self.entries = WalkDir::new(&self.root_dir) + .follow_links(true) + .into_iter() + .filter_map(|entry| { + let entry = entry.ok()?; + if !entry.file_type().is_file() { + return None; + } + let extension = entry.path().extension()?; + if !self.valid_extensions.contains(extension) { + return None; + } + let relative_path = entry.path().strip_prefix(&self.root_dir).ok()?; + // Immediately exclude relative paths that can't be represented as valid UTF-8. + // Otherwise we will potentially open a can of worms (regarding persistence etc.). + let processing_output = process_file(entry.path(), ctx.plugin_db).ok()?; + let preset_entry = PresetEntry { + preset_name: entry.path().file_stem()?.to_str()?.to_string(), + relative_path: relative_path.to_str()?.to_string(), + plugin_cores: processing_output.used_plugins, + content_hash: processing_output.content_hash, + }; + Some(preset_entry) + }) + .collect(); + Ok(()) + } + + fn query_filter_collections( + &self, + _: &ProviderContext, + input: InnerBuildInput, + _: EnumSet, + ) -> Result> { + let mut new_filters = *input.filter_input.filters; + new_filters.clear_this_and_dependent_filters(PotFilterKind::Bank); + let product_items = self + .query_presets_internal(&input.filter_input.with_filters(&new_filters)) + .flat_map(|(_, entry)| entry.plugin_cores.values().map(|core| core.product_id)) + .unique() + .map(InnerFilterItem::Product) + .collect(); + let mut collections = InnerFilterItemCollections::empty(); + collections.set(PotFilterKind::Bank, product_items); + Ok(collections) + } + + fn query_presets( + &self, + ctx: &ProviderContext, + input: InnerBuildInput, + ) -> Result, Box> { + let preset_ids = self + .query_presets_internal(&input.filter_input) + .filter(|(_, entry)| { + let search_input = DirectorySearchInput { + ctx, + preset_entry: entry, + }; + input.search_evaluator.matches(search_input) + }) + .map(|(i, entry)| SortablePresetId::new(i as _, entry.preset_name.clone())) + .collect(); + Ok(preset_ids) + } + + fn find_preset_by_id( + &self, + ctx: &ProviderContext, + preset_id: InnerPresetId, + ) -> Option { + let preset_entry = self.entries.get(preset_id.0 as usize)?; + let preset = PotPreset { + common: PotPresetCommon { + persistent_id: PersistentPresetId::new( + self.persistent_id().clone(), + create_persistent_inner_id(preset_entry), + ), + name: preset_entry.preset_name.clone(), + context_name: Path::new(&preset_entry.relative_path) + .parent() + .and_then(|p| Some(p.to_str()?.to_string())), + plugin_ids: preset_entry.plugin_cores.values().map(|c| c.id).collect(), + product_ids: preset_entry + .plugin_cores + .values() + .map(|c| c.product_id) + .collect(), + product_name: build_product_name(ctx, preset_entry), + content_hash: Some(preset_entry.content_hash), + db_specific_preview_file: None, + is_supported: true, + is_available: !preset_entry.plugin_cores.is_empty(), + metadata: Default::default(), + }, + kind: PotPresetKind::FileBased(FiledBasedPotPresetKind { + file_ext: get_file_extension(&preset_entry.relative_path).to_string(), + path: self.root_dir.join(&preset_entry.relative_path), + }), + }; + Some(preset) + } +} + +struct FileProcessingOutput { + content_hash: PersistentHash, + used_plugins: NonCryptoIndexMap, +} + +/// Finds used plug-ins in a REAPER-XML-like text file (e.g. RPP, RfxChain, RTrackTemplate). +/// +/// Examples entries: +/// +/// ```text +/// "" +/// Result> { + let file = File::open(path)?; + let mut used_plugins = NonCryptoIndexMap::default(); + let mut buffer = String::new(); + let mut reader = BufReader::new(&file); + let mut hasher = PersistentHasher::new(); + while let Ok(count) = reader.read_line(&mut buffer) { + if count == 0 { + // EOF + break; + } + hasher.write(buffer.as_bytes()); + let line = buffer.trim(); + if let Some(plugin) = plugin_db.detect_plugin_from_rxml_line(line) { + used_plugins.insert(plugin.common.core.id, plugin.common.core); + } + buffer.clear(); + } + let output = FileProcessingOutput { + content_hash: hasher.digest_128(), + used_plugins, + }; + Ok(output) +} + +/// Example: `Synths/Lead.RTrackTemplate` +fn create_persistent_inner_id(preset_entry: &PresetEntry) -> PersistentInnerPresetId { + let escaped_path = PipeEscaped(preset_entry.relative_path.as_str()); + PersistentInnerPresetId::new(escaped_path.to_string()) +} + +struct DirectorySearchInput<'a> { + ctx: &'a ProviderContext<'a>, + preset_entry: &'a PresetEntry, +} + +impl SearchInput for DirectorySearchInput<'_> { + fn preset_name(&self) -> &str { + &self.preset_entry.preset_name + } + + fn product_name(&self) -> Option> { + let product_name = build_product_name(self.ctx, self.preset_entry)?; + Some(product_name.into()) + } + + fn file_extension(&self) -> Option<&str> { + Some(get_file_extension(&self.preset_entry.relative_path)) + } +} + +fn build_product_name(ctx: &ProviderContext, preset_entry: &PresetEntry) -> Option { + if preset_entry.plugin_cores.len() > 1 { + Some("".to_string()) + } else if let Some(first) = preset_entry.plugin_cores.values().next() { + ctx.plugin_db + .find_plugin_by_id(&first.id) + .map(|p| p.common.to_string()) + } else { + None + } +} + +fn get_file_extension(relative_path: &str) -> &str { + Path::new(relative_path) + .extension() + .unwrap() + .to_str() + .unwrap() +} diff --git a/plugin-reaper-realearn/pot/src/providers/ini.rs b/plugin-reaper-realearn/pot/src/providers/ini.rs new file mode 100644 index 0000000..573cad4 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/providers/ini.rs @@ -0,0 +1,331 @@ +use crate::provider_database::{ + Database, InnerFilterItem, InnerFilterItemCollections, ProviderContext, SortablePresetId, +}; +use crate::{ + FilterInput, InnerBuildInput, InnerPresetId, InternalPotPresetKind, PersistentDatabaseId, + PersistentInnerPresetId, PersistentPresetId, PipeEscaped, PluginKind, PotPreset, + PotPresetCommon, PotPresetKind, SearchInput, +}; +use std::borrow::Cow; + +use crate::plugins::{Plugin, PluginCore, SuperPluginKind}; +use base::hash_util::{PersistentHash, PersistentHasher}; +use camino::Utf8PathBuf; +use either::Either; +use enumset::{enum_set, EnumSet}; +use helgobox_api::persistence::PotFilterKind; +use ini::Ini; +use itertools::Itertools; +use std::error::Error; +use std::hash::Hasher; +use std::iter; +use std::str::FromStr; +use walkdir::WalkDir; + +pub struct IniDatabase { + persistent_id: PersistentDatabaseId, + root_dir: Utf8PathBuf, + entries: Vec, +} + +impl IniDatabase { + pub fn open( + persistent_id: PersistentDatabaseId, + root_dir: Utf8PathBuf, + ) -> Result> { + if !root_dir.try_exists()? { + return Err("path to presets root directory doesn't exist".into()); + } + let db = Self { + persistent_id, + entries: Default::default(), + root_dir, + }; + Ok(db) + } + + fn query_presets_internal<'a>( + &'a self, + filter_input: &'a FilterInput, + ) -> impl Iterator + 'a { + let matches = !filter_input.filters.wants_factory_presets_only(); + if !matches { + return Either::Left(iter::empty()); + } + let iter = self.entries.iter().enumerate().filter(|(i, e)| { + let id = InnerPresetId(*i as _); + filter_input.everything_matches(e.plugin.as_ref(), id) + }); + Either::Right(iter) + } +} + +struct PresetEntry { + preset_name: String, + plugin_kind: PluginKind, + /// Example: "Massive" + plugin_identifier: String, + /// If `None`, it means the corresponding plug-in is not installed/scanned. + plugin: Option, + content_hash: Option, +} + +impl Database for IniDatabase { + fn persistent_id(&self) -> &PersistentDatabaseId { + &self.persistent_id + } + + fn name(&self) -> Cow { + "FX presets".into() + } + + fn description(&self) -> Cow { + "All FX presets that you saved via \"Save preset...\" in REAPER's FX window.\n\".vstpreset\"-style presets are not yet supported!" + .into() + } + + fn supported_advanced_filter_kinds(&self) -> EnumSet { + enum_set!(PotFilterKind::Bank) + } + + fn refresh(&mut self, ctx: &ProviderContext) -> Result<(), Box> { + let file_name_regex = base::regex!(r#"(?i)(.*?)-(.*).ini"#); + self.entries = WalkDir::new(&self.root_dir) + .max_depth(1) + .follow_links(true) + .into_iter() + .filter_map(|entry| { + let entry = entry.ok()?; + if !entry.file_type().is_file() { + return None; + } + let file_name = entry.file_name().to_str()?; + // Example file names: + // - vst-Zebra2.ini + // - vst3-FM8-1168312232-builtin.ini + // - vst-TDR Nova-builtin.ini + // - vst-reacomp.ini + // - vst3-Massive.ini + // - clap-org_surge-synth-team_surge-xt.ini + // - js-analysis_hund.ini + let captures = file_name_regex.captures(file_name)?; + let plugin_kind_str = captures.get(1)?.as_str(); + let plugin_kind = PluginKind::from_str(plugin_kind_str).ok()?; + let plugin_identifier = captures.get(2)?.as_str(); + if plugin_identifier.ends_with("-builtin") { + return None; + } + let (main_plugin_identifier, shell_qualifier) = + match plugin_identifier.rsplit_once('-') { + // Example: vst3-Zebra2-959560201.ini + // (interpret the number behind the dash as shell qualifier) + Some((left, right)) + if right.len() >= 5 && right.chars().all(|ch| ch.is_ascii_digit()) => + { + (left, Some(right)) + } + // Examples: "vst-Tritik-Irid.ini", "vst-Zebra2.ini" + _ => (plugin_identifier, None), + }; + let plugin = ctx.plugin_db.plugins().find(|p| { + if p.common.core.id.kind() != plugin_kind { + return false; + } + match &p.kind { + SuperPluginKind::Vst(k) => { + let unsafe_char_regex = base::regex!(r#"[^a-zA-Z0-9_]"#); + let safe_main_plugin_identifier = + unsafe_char_regex.replace_all(main_plugin_identifier, "_"); + let file_name_prefix = format!("{safe_main_plugin_identifier}."); + tracing::trace!( + "Test VST '{}' should start with INI plug-in file name prefix '{file_name_prefix}'", + k.safe_file_name + ); + if !k.safe_file_name.starts_with(&file_name_prefix) { + return false; + } + let plugin_shell_qualifier = k.shell_qualifier.as_deref(); + if shell_qualifier != plugin_shell_qualifier { + return false; + } + true + } + SuperPluginKind::Clap(k) => { + let safe_plugin_id = k.id.replace('.', "_"); + if plugin_identifier != safe_plugin_id { + return false; + } + true + } + SuperPluginKind::Js(k) => { + let lowercase_safe_path = k.path.replace(['/', '\\', '.'], "_").to_lowercase(); + let lowercase_plugin_identifier = plugin_identifier.to_lowercase(); + tracing::trace!( + "Test JS '{lowercase_safe_path}' vs. INI plug-in identifier '{lowercase_plugin_identifier}'" + ); + if lowercase_plugin_identifier != lowercase_safe_path { + return false; + } + true + } + } + }); + let ini_file = Ini::load_from_file(entry.path()).ok()?; + let general_section = ini_file.section(Some("General"))?; + let nb_presets = general_section.get("NbPresets")?; + let preset_count: u32 = nb_presets.parse().ok()?; + let plugin_identifier = plugin_identifier.to_string(); + let iter = (0..preset_count).filter_map(move |i| { + let section_name = format!("Preset{i}"); + let section = ini_file.section(Some(section_name))?; + let name = section.get("Name")?; + // Calculate hash. At first add info about the plug-in. Without that info, + // the content could be ambiguous. + let mut hasher = PersistentHasher::new(); + let plugin_kind_str = plugin_kind.as_ref(); + let plugin_info = format!("{plugin_kind_str}-{plugin_identifier}"); + hasher.write(plugin_info.as_bytes()); + // Calculate hash out of data properties ("Data", "Data_1", "Data_2", ...) + let data = section.get("Data")?; + hasher.write(data.as_bytes()); + let mut i = 1; + while let Some(more_data) = section.get(format!("Data{i}")) { + hasher.write(more_data.as_bytes()); + i += 1; + } + // Build entry + let preset_entry = PresetEntry { + preset_name: name.to_string(), + plugin_kind, + plugin_identifier: plugin_identifier.clone(), + plugin: plugin.map(|p| p.common.core), + content_hash: Some(hasher.digest_128()), + }; + Some(preset_entry) + }); + Some(iter) + }) + .flatten() + .collect(); + Ok(()) + } + + fn query_filter_collections( + &self, + _: &ProviderContext, + input: InnerBuildInput, + _: EnumSet, + ) -> Result> { + let mut new_filters = *input.filter_input.filters; + new_filters.clear_this_and_dependent_filters(PotFilterKind::Bank); + let product_items = self + .query_presets_internal(&input.filter_input.with_filters(&new_filters)) + .filter_map(|(_, entry)| Some(entry.plugin.as_ref()?.product_id)) + .unique() + .map(InnerFilterItem::Product) + .collect(); + let mut collections = InnerFilterItemCollections::empty(); + collections.set(PotFilterKind::Bank, product_items); + Ok(collections) + } + + fn query_presets( + &self, + ctx: &ProviderContext, + input: InnerBuildInput, + ) -> Result, Box> { + let preset_ids = self + .query_presets_internal(&input.filter_input) + .filter(|(_, preset_entry)| { + let search_input = IniSearchInput { ctx, preset_entry }; + input.search_evaluator.matches(search_input) + }) + .map(|(i, entry)| SortablePresetId::new(i as _, entry.preset_name.clone())) + .collect(); + Ok(preset_ids) + } + + fn find_preset_by_id( + &self, + ctx: &ProviderContext, + preset_id: InnerPresetId, + ) -> Option { + let preset_entry = self.entries.get(preset_id.0 as usize)?; + let plugin = preset_entry + .plugin + .as_ref() + .and_then(|entry| ctx.plugin_db.find_plugin_by_id(&entry.id)); + let preset = PotPreset { + common: PotPresetCommon { + persistent_id: PersistentPresetId::new( + self.persistent_id().clone(), + create_persistent_inner_id(preset_entry), + ), + name: preset_entry.preset_name.clone(), + context_name: None, + plugin_ids: preset_entry + .plugin + .as_ref() + .map(|p| p.id) + .into_iter() + .collect(), + product_ids: preset_entry + .plugin + .as_ref() + .map(|p| p.product_id) + .into_iter() + .collect(), + product_name: Some(build_product_name(preset_entry, plugin).to_string()), + content_hash: preset_entry.content_hash, + db_specific_preview_file: None, + is_supported: true, + is_available: preset_entry.plugin.is_some(), + metadata: Default::default(), + }, + kind: PotPresetKind::Internal(InternalPotPresetKind { + plugin_id: preset_entry.plugin.as_ref().map(|p| p.id), + }), + }; + Some(preset) + } +} + +/// Example: `vst3-Surge XT.ini|My Preset` +fn create_persistent_inner_id(preset_entry: &PresetEntry) -> PersistentInnerPresetId { + let plugin_kind = preset_entry.plugin_kind.as_ref(); + let plugin_identifier = &preset_entry.plugin_identifier; + let escaped_preset_name = PipeEscaped(&preset_entry.preset_name); + let id = format!("{plugin_kind}-{plugin_identifier}.ini|{escaped_preset_name}"); + PersistentInnerPresetId::new(id) +} + +struct IniSearchInput<'a> { + ctx: &'a ProviderContext<'a>, + preset_entry: &'a PresetEntry, +} + +impl SearchInput for IniSearchInput<'_> { + fn preset_name(&self) -> &str { + &self.preset_entry.preset_name + } + + fn product_name(&self) -> Option> { + let plugin = self + .preset_entry + .plugin + .as_ref() + .and_then(|entry| self.ctx.plugin_db.find_plugin_by_id(&entry.id)); + Some(build_product_name(self.preset_entry, plugin)) + } + + fn file_extension(&self) -> Option<&str> { + None + } +} + +fn build_product_name<'a>(preset_entry: &'a PresetEntry, plugin: Option<&Plugin>) -> Cow<'a, str> { + match plugin { + None => preset_entry.plugin_identifier.as_str().into(), + Some(p) => p.common.to_string().into(), + } +} diff --git a/plugin-reaper-realearn/pot/src/providers/komplete.rs b/plugin-reaper-realearn/pot/src/providers/komplete.rs new file mode 100644 index 0000000..c11029c --- /dev/null +++ b/plugin-reaper-realearn/pot/src/providers/komplete.rs @@ -0,0 +1,1576 @@ +use crate::api::{OptFilter, PotFilterExcludes}; +use crate::provider_database::{ + Database, InnerFilterItem, InnerFilterItemCollections, ProviderContext, SortablePresetId, + FIL_IS_AVAILABLE_TRUE, FIL_IS_FAVORITE_TRUE, FIL_IS_SUPPORTED_FALSE, FIL_IS_SUPPORTED_TRUE, + FIL_IS_USER_PRESET_TRUE, +}; +use crate::{ + Fil, FiledBasedPotPresetKind, InnerBuildInput, InnerPresetId, MacroParamBank, + PersistentDatabaseId, PersistentInnerPresetId, PersistentPresetId, PluginKind, PotFxParam, + PotFxParamId, PotPreset, PotPresetCommon, PotPresetKind, PotPresetMetaData, ProductId, + SearchEvaluator, SearchField, SearchOptions, +}; +use crate::{FilterItem, FilterItemId, Filters, MacroParam, ParamAssignment, PluginId}; +use base::blocking_lock; +use enumset::{enum_set, EnumSet}; +use helgobox_api::persistence::PotFilterKind; + +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use camino::{Utf8Path, Utf8PathBuf}; +use chrono::NaiveDateTime; +use riff_io::{ChunkMeta, Entry, RiffFile}; +use rusqlite::{Connection, OpenFlags, Row, ToSql}; +use std::borrow::Cow; +use std::collections::BTreeSet; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::iter; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; +use strum::IntoEnumIterator; + +pub struct KompleteDatabase { + persistent_id: PersistentDatabaseId, + primary_preset_db: Mutex, + nks_filter_item_collections: NksFilterItemCollections, + nks_bank_id_by_product_id: NonCryptoHashMap, + nks_product_id_by_bank_id: NonCryptoHashMap, + nks_product_id_by_extension: NonCryptoHashMap, + /// This returns a second connection to the preset database. + /// + /// At the moment, the UI thread continuously queries the database for the currently visible rows. + /// This runs in parallel with expensive background queries. In order to not get UI freezes due + /// to mutex contention, we need a second connection to the same DB. + /// + /// This is probably temporary. Might be better performance-wise to keep the complete table data + /// (names and other fields to be shown) in-memory. + secondary_preset_db: Mutex, +} + +impl KompleteDatabase { + pub fn open() -> Result> { + let db = Self { + persistent_id: PersistentDatabaseId::new("komplete".to_string()), + primary_preset_db: PresetDb::open()?, + nks_filter_item_collections: Default::default(), + nks_bank_id_by_product_id: Default::default(), + nks_product_id_by_bank_id: Default::default(), + nks_product_id_by_extension: Default::default(), + secondary_preset_db: PresetDb::open()?, + }; + Ok(db) + } + + /// Translates all neutral (NKS-independent) filters into NKS filters. + /// + /// At the moment, this only affects the product filter. That means it translates any neutral + /// product filter (representing one of the installed plug-ins) into an NKS bank filter. + fn translate_neutral_filters_to_nks(&self, mut filters: Filters) -> Filters { + if let Some(FilterItemId(Some(fil))) = filters.get_ref(PotFilterKind::Bank) { + if let Some(translated_fil) = self.translate_neutral_product_filter_to_nks(fil) { + filters.set( + PotFilterKind::Bank, + Some(FilterItemId(Some(translated_fil))), + ); + } + } + filters + } + + /// Translates all neutral (NKS-independent) excludes into NKS excludes. + fn translate_neutral_excludes_to_nks(&self, excludes: &PotFilterExcludes) -> PotFilterExcludes { + let mut translated_excludes = excludes.clone(); + for fil in excludes.normal_excludes_by_kind(PotFilterKind::Bank) { + if let Some(translated_fil) = self.translate_neutral_product_filter_to_nks(fil) { + translated_excludes.remove(PotFilterKind::Bank, FilterItemId(Some(*fil))); + translated_excludes.add(PotFilterKind::Bank, FilterItemId(Some(translated_fil))); + } + } + translated_excludes + } + + /// Translates all NKS filter items into neutral ones. + fn translate_nks_filter_items_to_neutral(&self, collections: &mut InnerFilterItemCollections) { + for (kind, filter_items) in collections.iter_mut() { + for filter_item in filter_items { + if let InnerFilterItem::Unique(it) = filter_item { + if let FilterItemId(Some(Fil::Komplete(id))) = it.id { + if let Some(translated) = + self.translate_nks_filter_item_to_neutral(kind, id) + { + *filter_item = translated; + } + } + } + } + } + } + + fn translate_neutral_product_filter_to_nks(&self, fil: &Fil) -> Option { + if let Fil::Product(pid) = fil { + if let Some(bank_id) = self.nks_bank_id_by_product_id.get(pid) { + return Some(Fil::Komplete(*bank_id)); + } + } + None + } + + fn translate_nks_filter_item_to_neutral( + &self, + kind: PotFilterKind, + id: u32, + ) -> Option { + if kind == PotFilterKind::Bank { + let product_id = self.nks_product_id_by_bank_id.get(&id)?; + Some(InnerFilterItem::Product(*product_id)) + } else { + None + } + } + + fn find_preset_by_id_internal( + &self, + preset_db: &PresetDb, + id: InnerPresetId, + ) -> Option<(PotPresetCommon, FiledBasedPotPresetKind)> { + preset_db.find_preset_by_id(&self.persistent_id, id, |bank_id, extension| { + // Try to translate bank ID - a number representing either a plug-in product like + // "Zebra2" or a sub product like "Vintage Organs". If it represents a plug-in product, + // translating the bank ID can work (if we have that plug-in installed). + if let Some(bank_id) = bank_id { + if let Some(product_id) = self.nks_product_id_by_bank_id.get(&bank_id).copied() { + tracing::debug!("Looked up product {product_id} for bank {bank_id}."); + return Some(product_id); + } else { + tracing::debug!("Looking up product for bank {bank_id} not successful."); + } + } + // If that didn't work because we don't have a bank ID, we have sub product or the + // plug-in product is simply not installed, try at least to translate the extension. + self.nks_product_id_by_extension.get(extension).copied() + }) + } + + fn build_filter_item_collections( + &self, + affected_kinds: EnumSet, + non_empty_filters: &NonEmptyNksFilters, + excludes: &PotFilterExcludes, + filters: &Filters, + ) -> InnerFilterItemCollections { + let mut api_collections = InnerFilterItemCollections::empty(); + use PotFilterKind as K; + for kind in affected_kinds { + let items = match kind { + K::Bank => self.build_parent_filter_items( + &self + .nks_filter_item_collections + .bank_collections + .parent_items, + &non_empty_filters.banks_and_sub_banks, + excludes, + kind, + ), + K::SubBank => self.build_child_filter_items( + &self + .nks_filter_item_collections + .bank_collections + .child_items, + &non_empty_filters.banks_and_sub_banks, + excludes, + filters.get(K::Bank), + kind, + ), + K::Category => self.build_parent_filter_items( + &self + .nks_filter_item_collections + .category_collections + .parent_items, + &non_empty_filters.categories_and_sub_categories, + excludes, + kind, + ), + K::SubCategory => self.build_child_filter_items( + &self + .nks_filter_item_collections + .category_collections + .child_items, + &non_empty_filters.categories_and_sub_categories, + excludes, + filters.get(K::Category), + kind, + ), + K::Mode => self.build_simple_filter_items( + &self.nks_filter_item_collections.modes, + &non_empty_filters.modes, + ), + _ => continue, + }; + api_collections.set(kind, items) + } + api_collections + } + fn build_parent_filter_items( + &self, + nks_items: &[ParentNksFilterItem], + non_empty_filter: &NonEmptyNksFilter, + excludes: &PotFilterExcludes, + kind: PotFilterKind, + ) -> Vec { + let existing_filter_items = nks_items + .iter() + // Respect exclude list + .filter(|b| !excludes.contains(kind, FilterItemId(Some(Fil::Komplete(b.id))))) + // Narrow down to non-empty items + .filter(|b| { + non_empty_filter.non_empty_ids.contains(&b.id) + || b.child_ids + .intersection(&non_empty_filter.non_empty_ids) + .next() + .is_some() + }) + // Create actual filter item + .map(|item| FilterItem { + persistent_id: item.name.clone(), + id: FilterItemId(Some(Fil::Komplete(item.id))), + parent_name: None, + name: Some(item.name.clone()), + icon: None, + more_info: None, + }) + .map(InnerFilterItem::Unique); + if non_empty_filter.has_non_associated_presets { + iter::once(InnerFilterItem::Unique(FilterItem::none())) + .chain(existing_filter_items) + .collect() + } else { + existing_filter_items.collect() + } + } + + fn build_child_filter_items( + &self, + nks_items: &[ChildNksFilterItem], + non_empty_filter: &NonEmptyNksFilter, + excludes: &PotFilterExcludes, + parent_filter: OptFilter, + kind: PotFilterKind, + ) -> Vec { + nks_items + .iter() + // Respect parent filter + .filter(|b| { + if let Some(filter_item_id) = parent_filter { + // Filter set + if let Some(Fil::Komplete(parent_id)) = filter_item_id.0 { + b.parent_id == parent_id + } else { + // Filter set to or non-Komplete ID + false + } + } else { + // Filter not set (= ) + true + } + }) + // Respect exclude list + .filter(|b| !excludes.contains(kind, FilterItemId(Some(Fil::Komplete(b.id))))) + // Narrow down to non-empty items + .filter(|b| non_empty_filter.non_empty_ids.contains(&b.id)) + // Create actual filter item + .map(|item| FilterItem { + persistent_id: "".to_string(), + id: FilterItemId(Some(Fil::Komplete(item.id))), + parent_name: Some(item.parent_name.clone()), + name: item.name.clone(), + icon: None, + more_info: None, + }) + .map(InnerFilterItem::Unique) + .collect() + } + + fn build_simple_filter_items( + &self, + nks_items: &[SimpleNksFilterItem], + non_empty_filter: &NonEmptyNksFilter, + ) -> Vec { + nks_items + .iter() + // Narrow down to non-empty items + .filter(|b| non_empty_filter.non_empty_ids.contains(&b.id)) + // Create actual filter item + .map(|item| FilterItem { + persistent_id: "".to_string(), + id: FilterItemId(Some(Fil::Komplete(item.id))), + parent_name: None, + name: Some(item.name.clone()), + icon: None, + more_info: None, + }) + .map(InnerFilterItem::Unique) + .collect() + } +} + +impl Database for KompleteDatabase { + fn persistent_id(&self) -> &PersistentDatabaseId { + &self.persistent_id + } + + fn name(&self) -> Cow { + "Komplete".into() + } + + fn description(&self) -> Cow { + "All presets in your local Native Instruments Komplete database.\nPreset files only show up here after you have scanned them using the Komplete Kontrol software!".into() + } + + fn supported_advanced_filter_kinds(&self) -> EnumSet { + enum_set!( + PotFilterKind::Bank + | PotFilterKind::SubBank + | PotFilterKind::Category + | PotFilterKind::SubCategory + | PotFilterKind::Mode + ) + } + + fn refresh(&mut self, ctx: &ProviderContext) -> Result<(), Box> { + let preset_db = blocking_lock( + &self.primary_preset_db, + "Komplete DB query_filter_collections", + ); + self.nks_filter_item_collections = preset_db.build_nks_filter_item_collections()?; + // Obtain all NKS banks and find installed products (= groups of similar plug-ins) that + // match the bank name. They will be treated as the same in terms of filtering. + self.nks_bank_id_by_product_id = self + .nks_filter_item_collections + .bank_collections + .parent_items + .iter() + .filter_map(|bank| { + let product_id = ctx.plugin_db.products().find_map(|(product_id, product)| { + if bank.name == product.name { + tracing::debug!( + "Associated bank {} {} with product {} {}", + bank.id, + bank.name, + product_id.0, + &product.name, + ); + Some(product_id) + } else { + None + } + })?; + Some((product_id, bank.id)) + }) + .collect(); + // Make fast reverse lookup possible as well + self.nks_product_id_by_bank_id = self + .nks_bank_id_by_product_id + .iter() + .map(|(k, v)| (*v, *k)) + .collect(); + // And associate special extensions with products as well + self.nks_product_id_by_extension = EXTENSION_TO_PRODUCT_NAME_MAPPING + .iter() + .filter_map(|(ext, product_name)| { + let product_id = ctx.plugin_db.products().find_map(|(i, p)| { + if &p.name == product_name { + Some(i) + } else { + None + } + })?; + Some((ext.to_string(), product_id)) + }) + .collect(); + Ok(()) + } + + fn query_filter_collections( + &self, + _: &ProviderContext, + input: InnerBuildInput, + affected_kinds: EnumSet, + ) -> Result> { + // When a project filter is set, we know that Komplete filters don't matter. This + // could probably be generalized into Pot database somehow but at the moment, "Project" + // is the only unsupported foreign filter + if input + .filter_input + .filters + .is_set_to_concrete_value(PotFilterKind::Project) + { + return Ok(InnerFilterItemCollections::empty()); + } + // Translate possibly incoming "neutral" product filters to "NKS bank" product filters + let translated_filters = self.translate_neutral_filters_to_nks(*input.filter_input.filters); + let translated_excludes = + self.translate_neutral_excludes_to_nks(input.filter_input.excludes); + let banks_are_affected = affected_kinds.contains(PotFilterKind::Bank); + let sub_banks_are_affected = affected_kinds.contains(PotFilterKind::SubBank); + let categories_are_affected = affected_kinds.contains(PotFilterKind::Category); + let sub_categories_are_affected = affected_kinds.contains(PotFilterKind::SubCategory); + let modes_are_affected = affected_kinds.contains(PotFilterKind::Mode); + let mut preset_db = blocking_lock( + &self.primary_preset_db, + "Komplete DB query_filter_collections", + ); + let non_empty_filters = NonEmptyNksFilters { + banks_and_sub_banks: if banks_are_affected || sub_banks_are_affected { + NonEmptyNksFilter::from_vec( + preset_db.find_non_empty_banks(translated_filters, &translated_excludes)?, + ) + } else { + Default::default() + }, + categories_and_sub_categories: if categories_are_affected || sub_categories_are_affected + { + NonEmptyNksFilter::from_vec( + preset_db + .find_non_empty_categories(translated_filters, &translated_excludes)?, + ) + } else { + Default::default() + }, + modes: if modes_are_affected { + NonEmptyNksFilter::from_vec( + preset_db.find_non_empty_modes(translated_filters, &translated_excludes)?, + ) + } else { + Default::default() + }, + }; + let mut filter_item_collections = self.build_filter_item_collections( + affected_kinds, + &non_empty_filters, + &translated_excludes, + &translated_filters, + ); + // Translate some Komplete-specific filter items to shared filter items. It's important that + // we do this at the end, otherwise the narrow-down logic doesn't work correctly. + self.translate_nks_filter_items_to_neutral(&mut filter_item_collections); + Ok(filter_item_collections) + } + + fn query_presets( + &self, + _: &ProviderContext, + input: InnerBuildInput, + ) -> Result, Box> { + let translated_filters = self.translate_neutral_filters_to_nks(*input.filter_input.filters); + let translated_excludes = + self.translate_neutral_excludes_to_nks(input.filter_input.excludes); + let mut preset_db = blocking_lock(&self.primary_preset_db, "Komplete DB query_presets"); + preset_db.query_presets( + &translated_filters, + input.search_evaluator, + &translated_excludes, + ) + } + + fn find_preset_by_id( + &self, + _: &ProviderContext, + preset_id: InnerPresetId, + ) -> Option { + let preset_db = blocking_lock(&self.secondary_preset_db, "Komplete DB find_preset_by_id"); + let (common, kind) = self.find_preset_by_id_internal(&preset_db, preset_id)?; + Some(PotPreset::new(common, PotPresetKind::FileBased(kind))) + } + + fn find_unsupported_preset_matching( + &self, + product_id: ProductId, + preset_name: &str, + ) -> Option { + // Look for corresponding Komplete bank + let bank_id = self.nks_bank_id_by_product_id.get(&product_id)?; + // Make sure we only get results from that bank + let mut filters = Filters::empty(); + filters.set( + PotFilterKind::Bank, + Some(FilterItemId(Some(Fil::Komplete(*bank_id)))), + ); + // Make sure we only get unsupported presets + filters.set( + PotFilterKind::IsSupported, + Some(FilterItemId(Some(FIL_IS_SUPPORTED_FALSE))), + ); + // Look for exact preset name match + let search_evaluator = SearchEvaluator::new( + preset_name, + SearchOptions { + use_wildcards: true, + search_fields: enum_set!(SearchField::PresetName), + }, + ); + let mut preset_db = blocking_lock( + &self.primary_preset_db, + "Komplete DB find_unsupported_preset_matching", + ); + let preset_ids = preset_db + .query_presets(&filters, &search_evaluator, &Default::default()) + .ok()?; + let first_preset_id = preset_ids.first()?; + let (common, kind) = + self.find_preset_by_id_internal(&preset_db, first_preset_id.inner_preset_id)?; + Some(PotPreset::new(common, PotPresetKind::FileBased(kind))) + } +} + +struct PresetDb { + connection: Connection, + favorites_db_path: PathBuf, + attached_favorites_db: bool, +} + +pub struct NksFile { + file: RiffFile, +} + +#[derive(Debug)] +pub struct NksFileContent<'a> { + pub metadata: NisiChunkContent, + pub plugin_id: PluginId, + pub vst_chunk: &'a [u8], + pub macro_param_banks: Vec, +} + +impl NksFile { + pub fn load(path: &Path) -> Result { + let file = RiffFile::open(&path.to_string_lossy()) + .map_err(|_| "Couldn't fine preset file or doesn't have RIFF format")?; + Ok(Self { file }) + } + + pub fn content(&self) -> Result { + // Find relevant chunks + let entries = self + .file + .read_entries() + .map_err(|_| "couldn't read NKS file entries")?; + let mut plid_chunk = None; + let mut pchk_chunk = None; + let mut nica_chunk = None; + let mut nisi_chunk = None; + for entry in entries { + if let Entry::Chunk(chunk_meta) = entry { + // Log chunk contents if trace log level enabled + if tracing::enabled!(tracing::Level::TRACE) { + let bytes = self.relevant_bytes_of_chunk(&chunk_meta); + let value: Option = rmp_serde::from_slice(bytes).ok(); + if let Some(value) = value { + tracing::trace!( + "# Chunk {}{}{}{}\n{}\n", + chunk_meta.chunk_id[0] as char, + chunk_meta.chunk_id[1] as char, + chunk_meta.chunk_id[2] as char, + chunk_meta.chunk_id[3] as char, + serde_json::to_string_pretty(&value).unwrap_or_default() + ); + } + } + match &chunk_meta.chunk_id { + b"PLID" => plid_chunk = Some(chunk_meta), + b"NICA" => nica_chunk = Some(chunk_meta), + b"PCHK" => pchk_chunk = Some(chunk_meta), + b"NISI" => nisi_chunk = Some(chunk_meta), + _ => {} + } + } + } + let plid_chunk = plid_chunk.ok_or("couldn't find PLID chunk")?; + let pchk_chunk = pchk_chunk.ok_or("couldn't find PCHK chunk")?; + // Build content from relevant chunks + let plugin_id = { + let bytes = self.relevant_bytes_of_chunk(&plid_chunk); + let value: PlidChunkContent = + rmp_serde::from_slice(bytes).map_err(|_| "couldn't find VST magic number")?; + if let Some(vst3_uid) = value.vst3_uid { + PluginId::Vst3 { vst_uid: vst3_uid } + } else { + PluginId::Vst2 { + vst_magic_number: value.vst_magic as i32, + } + } + }; + let plugin_kind = plugin_id.kind(); + let content = NksFileContent { + metadata: nisi_chunk + .and_then(|ch| { + let bytes = self.relevant_bytes_of_chunk(&ch); + rmp_serde::from_slice(bytes).ok() + }) + .unwrap_or_default(), + plugin_id, + vst_chunk: self.relevant_bytes_of_chunk(&pchk_chunk), + macro_param_banks: { + nica_chunk + .and_then(|nica_chunk| { + let bytes = self.relevant_bytes_of_chunk(&nica_chunk); + let value: NicaChunkContent = rmp_serde::from_slice(bytes).ok()?; + Some(value.extract_macro_param_banks(plugin_kind)) + }) + .unwrap_or_default() + }, + }; + Ok(content) + } + + fn relevant_bytes_of_chunk(&self, chunk: &ChunkMeta) -> &[u8] { + let skip = 4; + let offset = chunk.data_offset + skip; + let size = chunk.chunk_size - skip; + let range = offset..(offset + size); + self.file.read_bytes(range) + } +} + +#[derive(Debug, Default, serde::Deserialize)] +#[serde(default)] +pub struct NisiChunkContent { + #[serde(rename = "author")] + pub author: Option, + #[serde(rename = "bankchain")] + pub bankchain: Vec, + #[serde(rename = "deviceType")] + pub device_type: Option, + #[serde(rename = "modes")] + pub modes: Vec, + #[serde(rename = "name")] + pub name: Option, + #[serde(rename = "types")] + pub types: Vec, + #[serde(rename = "vendor")] + pub vendor: Option, +} + +#[derive(serde::Deserialize)] +struct PlidChunkContent { + #[serde(rename = "VST.magic")] + vst_magic: u32, + // 4 * u32 (5 byte) = 128 bit (8 byte) + #[serde(rename = "VST3.uid")] + vst3_uid: Option<[u32; 4]>, +} + +#[derive(serde::Deserialize)] +struct NicaChunkContent { + ni8: Vec>, +} + +impl NicaChunkContent { + pub fn extract_macro_param_banks(self, plugin_kind: PluginKind) -> Vec { + self.ni8 + .into_iter() + .map(|params| { + let params = params + .into_iter() + .map(move |param| MacroParam { + name: param.name, + section: param.section, + fx_param: param.id.map(|id| PotFxParam { + param_id: match plugin_kind { + PluginKind::Vst2 => PotFxParamId::Index(id), + PluginKind::Vst3 => PotFxParamId::Id(id), + _ => unreachable!("NKS only supports VST2 and VST3"), + }, + // Can be resolved later on demand. + resolved_param_index: None, + }), + }) + .collect(); + MacroParamBank::new(params) + }) + .collect() + } +} + +impl PresetDb { + fn open() -> Result, Box> { + let (main_db_path, favorites_db_path) = path_to_main_and_favorites_db()?; + let connection = + Connection::open_with_flags(main_db_path, OpenFlags::SQLITE_OPEN_READ_ONLY)?; + let db = Self { + connection, + favorites_db_path, + attached_favorites_db: false, + }; + Ok(Mutex::new(db)) + } + + fn ensure_favorites_db_is_attached(&mut self) -> Result<(), Box> { + if self.attached_favorites_db { + return Ok(()); + } + let mut stmt = self + .connection + .prepare_cached("ATTACH DATABASE ? AS favorites_db")?; + let favorites_db_utf8_path = self + .favorites_db_path + .to_str() + .ok_or("non-UTF8 characters in favorite db path")?; + stmt.execute([favorites_db_utf8_path])?; + self.attached_favorites_db = true; + Ok(()) + } + + fn build_nks_filter_item_collections(&self) -> rusqlite::Result { + let collections = NksFilterItemCollections { + bank_collections: { + let rows = self.read_hierarchy_rows( + "SELECT id, entry1, entry2, entry3 FROM k_bank_chain ORDER BY entry1", + )?; + NksParentChildCollections::from_sorted_rows(rows) + }, + category_collections: { + let rows = self.read_hierarchy_rows( + "SELECT id, category, subcategory, subsubcategory FROM k_category ORDER BY category" + )?; + NksParentChildCollections::from_sorted_rows(rows) + }, + modes: { + let mut statement = self + .connection + .prepare_cached("SELECT id, name FROM k_mode ORDER BY name")?; + let rows = statement.query([])?; + let result: Result, _> = rows + .mapped(|row| { + Ok(SimpleNksFilterItem { + id: row.get(0)?, + name: row.get(1)?, + }) + }) + .collect(); + result? + }, + }; + Ok(collections) + } + + fn read_hierarchy_rows(&self, sql_query: &str) -> rusqlite::Result> { + let mut statement = self.connection.prepare_cached(sql_query)?; + let rows = statement.query([])?; + rows.mapped(|row| { + Ok(HierarchyRow { + id: row.get(0)?, + level1: row.get(1)?, + level2: row.get(2)?, + level3: row.get(3)?, + }) + }) + .collect() + } + + #[allow(dead_code)] + pub fn find_preset_id_by_favorite_id(&self, favorite_id: &str) -> Option { + self.connection + .query_row( + "SELECT id FROM k_sound_info WHERE favorite_id = ?", + [favorite_id], + |row| Ok(InnerPresetId(row.get(0)?)), + ) + .ok() + } + + pub fn find_preset_by_id( + &self, + persistent_db_id: &PersistentDatabaseId, + id: InnerPresetId, + translate_bank_or_ext_to_product_id: impl FnOnce(Option, &str) -> Option, + ) -> Option<(PotPresetCommon, FiledBasedPotPresetKind)> { + let sql = format!( + r#" + SELECT i.name, i.file_name, i.file_ext, i.favorite_id, bc.entry1, parent_bc.id, i.vendor, i.author, i.comment, i.file_size, i.mod_date, cp.state + FROM k_sound_info i + LEFT OUTER JOIN k_content_path cp ON cp.id = i.content_path_id + LEFT OUTER JOIN k_bank_chain bc ON i.bank_chain_id = bc.id + LEFT OUTER JOIN ({BANK_SQL_QUERY}) AS parent_bc ON bc.entry1 = parent_bc.entry1 + WHERE i.id = ? + "# + ); + self.connection + .query_row(&sql, [id.0], |row| { + let name: String = row.get(0)?; + let path: String = row.get(1)?; + let path: Utf8PathBuf = path.into(); + let file_ext: String = row.get(2)?; + let favorite_id: String = row.get(3)?; + let product_name: Option = row.get(4)?; + let bank_id: Option = row.get(5)?; + let product_id = translate_bank_or_ext_to_product_id(bank_id, &file_ext); + let preview_file = determine_preview_file(&path); + let content_path_state: Option = row.get(11)?; + let common = PotPresetCommon { + persistent_id: PersistentPresetId::new( + persistent_db_id.clone(), + PersistentInnerPresetId::new(favorite_id), + ), + name, + // In Komplete, "product" refers either to a top-level "plug-in product" + // (such as Zebra or Massive) or to a "product within a plug-in product" + // (e.g. "Abbey Road 60s Drums" within "Kontakt"). Only in the first case, + // the bank-to-product-ID translation will find something, because our + // plug-in database of course only knows products that represent plug-ins. + product_ids: product_id.into_iter().collect(), + plugin_ids: vec![], + product_name, + // We could make a hash of the file contents but since we would have to do that + // each time we look up the preset (not at refresh time), we don't do that for + // now. It probably would slow scrolling down quite a bit. + content_hash: None, + db_specific_preview_file: preview_file, + is_supported: SUPPORTED_FILE_EXTENSIONS.contains(&file_ext.as_str()), + is_available: content_path_state == Some(1), + metadata: PotPresetMetaData { + author: row.get(6).ok(), + vendor: row.get(7).ok(), + comment: row.get(8).ok(), + file_size_in_bytes: row.get(9)?, + modification_date: { + let nanos: Option = row.get(10).ok(); + nanos.and_then(|n| { + NaiveDateTime::from_timestamp_opt(n as i64 / 1_000_000_000, 0) + }) + }, + }, + context_name: None, + }; + let kind = FiledBasedPotPresetKind { path, file_ext }; + Ok((common, kind)) + }) + .ok() + } + + pub fn query_presets( + &mut self, + filters: &Filters, + search_evaluator: &SearchEvaluator, + exclude_list: &PotFilterExcludes, + ) -> Result, Box> { + let preset_collection = + self.build_preset_collection(filters, search_evaluator, exclude_list)?; + Ok(preset_collection) + } + + fn build_preset_collection( + &mut self, + filter_settings: &Filters, + search_evaluator: &SearchEvaluator, + exclude_list: &PotFilterExcludes, + ) -> Result, Box> { + tracing::trace!("build_preset_collection..."); + self.execute_preset_query( + filter_settings, + search_evaluator, + "DISTINCT i.id, i.name", + None, + exclude_list, + None, + |row| Ok(SortablePresetId::new(row.get(0)?, row.get(1)?)), + ) + } + + fn find_non_empty_banks( + &mut self, + mut filters: Filters, + exclude_list: &PotFilterExcludes, + ) -> Result>, Box> { + filters.clear_this_and_dependent_filters(PotFilterKind::Bank); + tracing::trace!("find_non_empty_banks..."); + self.execute_preset_query( + &filters, + &SearchEvaluator::default(), + "DISTINCT i.bank_chain_id", + None, + exclude_list, + None, + map_to_komplete_filter_id, + ) + } + + fn find_non_empty_categories( + &mut self, + mut filters: Filters, + exclude_list: &PotFilterExcludes, + ) -> Result>, Box> { + filters.clear_this_and_dependent_filters(PotFilterKind::Category); + tracing::trace!("find_non_empty_categories..."); + self.execute_preset_query( + &filters, + &SearchEvaluator::default(), + "DISTINCT ic.category_id", + Some(CATEGORY_JOIN), + exclude_list, + None, + map_to_komplete_filter_id, + ) + } + + fn find_non_empty_modes( + &mut self, + mut filters: Filters, + exclude_list: &PotFilterExcludes, + ) -> Result>, Box> { + filters.clear_this_and_dependent_filters(PotFilterKind::Mode); + tracing::trace!("find_non_empty_modes..."); + self.execute_preset_query( + &filters, + &SearchEvaluator::default(), + "DISTINCT im.mode_id", + Some(MODE_JOIN), + exclude_list, + None, + map_to_komplete_filter_id, + ) + } + + #[allow(clippy::too_many_arguments)] + fn execute_preset_query( + &mut self, + filter_settings: &Filters, + search_evaluator: &SearchEvaluator, + select_clause: &str, + from_more: Option<&str>, + exclude_list: &PotFilterExcludes, + order_by: Option<&str>, + row_mapper: impl Fn(&Row) -> Result, + ) -> Result> + where + C: FromIterator, + { + let mut sql = Sql::default(); + sql.select(select_clause); + sql.from("k_sound_info i"); + if let Some(v) = from_more { + sql.more_from(v); + } + if let Some(v) = order_by { + sql.order_by(v); + } + // Filter on state (= available or not) + if let Some(FilterItemId(Some(fil))) = filter_settings.get(PotFilterKind::IsAvailable) { + let state = if fil == FIL_IS_AVAILABLE_TRUE { + &ONE + } else { + &FOUR + }; + sql.more_from(CONTENT_PATH_JOIN); + sql.where_and_with_param("cp.state = ?", state); + } + // Filter on support (= supported by us to load or not) + if let Some(FilterItemId(Some(fil))) = filter_settings.get(PotFilterKind::IsSupported) { + let op = if fil == FIL_IS_SUPPORTED_TRUE { + "IN" + } else { + "NOT IN" + }; + let file_ext_csv = SUPPORTED_FILE_EXTENSIONS.join(r#"', '"#); + sql.where_and(format!("i.file_ext {op} ('{}')", file_ext_csv)); + } + // Filter on content type (= factory or user) + if let Some(FilterItemId(Some(fil))) = filter_settings.get(PotFilterKind::IsUser) { + let content_type = if fil == FIL_IS_USER_PRESET_TRUE { + &ONE + } else { + &TWO + }; + sql.more_from(CONTENT_PATH_JOIN); + sql.where_and_with_param("cp.content_type = ?", content_type); + } + // Filter on product/device type (= instrument, effect, loop or one shot) + if let Some(product_type) = filter_settings.get(PotFilterKind::ProductKind) { + // We chose the filter item IDs so they correspond to the device type flags. + let device_type_flags = match product_type.0.as_ref() { + None => Some(&ZERO), + Some(Fil::ProductKind(k)) => Some(k.komplete_id()), + _ => None, + }; + if let Some(flags) = device_type_flags { + sql.where_and_with_param("i.device_type_flags = ?", flags); + } else { + sql.where_and_false(); + } + }; + // Filter on favorite or not + if let Some(FilterItemId(Some(fil))) = filter_settings.get(PotFilterKind::IsFavorite) { + let is_favorite = fil == FIL_IS_FAVORITE_TRUE; + if self.ensure_favorites_db_is_attached().is_ok() { + if is_favorite { + // The IN query is vastly superior compared to the other two (EXISTS and JOIN)! + sql.where_and("i.favorite_id IN (SELECT id FROM favorites_db.favorites)"); + // sql.from_more(FAVORITES_JOIN); + // sql.where_and( + // "EXISTS (SELECT 1 FROM favorites_db.favorites f WHERE f.id = i.favorite_id)", + // ); + } else { + // NOT EXISTS is in the same ballpark ... takes long. Fortunately, this filter + // is not popular. + sql.where_and("i.favorite_id NOT IN (SELECT id FROM favorites_db.favorites)"); + } + } else if is_favorite { + // If the favorites database doesn't exist, it means we have no favorites! + sql.where_and("false"); + } + } + // Filter on bank and sub bank (= "Instrument" and "Bank") + if let Some(sub_bank_id) = filter_settings.effective_sub_bank() { + match &sub_bank_id.0 { + None => { + sql.where_and("i.bank_chain_id IS NULL"); + } + Some(Fil::Komplete(id)) => { + sql.where_and_with_param("i.bank_chain_id = ?", id); + } + _ => { + sql.where_and_false(); + } + } + } else if let Some(bank_id) = filter_settings.get_ref(PotFilterKind::Bank) { + match &bank_id.0 { + None => unreachable!("effective_sub_bank() should have prevented this"), + Some(Fil::Komplete(id)) => { + sql.where_and_with_param( + r#" + i.bank_chain_id IN ( + SELECT child.id FROM k_bank_chain child WHERE child.entry1 = ( + SELECT parent.entry1 FROM k_bank_chain parent WHERE parent.id = ? + ) + ) + "#, + id, + ); + } + _ => { + sql.where_and_false(); + } + } + } + // Filter on category and sub category (= "Type" and "Sub type") + if let Some(sub_category_id) = filter_settings.effective_sub_category() { + match &sub_category_id.0 { + None => { + sql.where_and("i.id NOT IN (SELECT sound_info_id FROM k_sound_info_category)") + } + Some(Fil::Komplete(id)) => { + sql.more_from(CATEGORY_JOIN); + sql.where_and_with_param("ic.category_id = ?", id); + } + _ => { + sql.where_and_false(); + } + } + } else if let Some(category_id) = filter_settings.get_ref(PotFilterKind::Category) { + match &category_id.0 { + None => unreachable!("effective_sub_category() should have prevented this"), + Some(Fil::Komplete(id)) => { + sql.more_from(CATEGORY_JOIN); + sql.where_and_with_param( + r#" + ic.category_id IN ( + SELECT child.id FROM k_category child WHERE child.category = ( + SELECT parent.category FROM k_category parent WHERE parent.id = ? + ) + ) + "#, + id, + ); + } + _ => { + sql.where_and_false(); + } + } + } + // Filter on mode (= "Character") + if let Some(mode_id) = filter_settings.get_ref(PotFilterKind::Mode) { + match &mode_id.0 { + None => sql.where_and("i.id NOT IN (SELECT sound_info_id FROM k_sound_info_mode)"), + Some(Fil::Komplete(id)) => { + sql.more_from(MODE_JOIN); + sql.where_and_with_param("im.mode_id = ?", id); + } + _ => { + sql.where_and_false(); + } + } + } + // Search expression + let search_expression = &search_evaluator.processed_search_expression; + let like_expression: String = if search_evaluator.options.use_wildcards { + search_expression + .chars() + .map(|x| match x { + '*' => '%', + '?' => '_', + _ => x, + }) + .collect() + } else { + format!("%{search_expression}%") + }; + if !search_expression.is_empty() { + if search_evaluator.options.search_fields.is_empty() { + sql.where_and_false(); + } else { + let mut conjunction = String::new(); + conjunction += "("; + for (i, field) in search_evaluator.options.search_fields.iter().enumerate() { + if i > 0 { + conjunction += " OR "; + } + match field { + SearchField::PresetName => { + conjunction += "i.name LIKE ?"; + sql.add_param(&like_expression); + } + SearchField::ProductName => { + sql.more_from(BANK_CHAIN_JOIN); + conjunction += "bc.entry1 LIKE ?"; + sql.add_param(&like_expression); + } + SearchField::FileExtension => { + conjunction += "i.file_ext LIKE ?"; + sql.add_param(search_expression); + } + } + } + conjunction += ")"; + sql.where_and(conjunction); + } + } + // Exclude filters + for kind in PotFilterKind::iter() { + if exclude_list.is_empty(kind) { + continue; + } + use PotFilterKind as K; + let selector = match kind { + K::Bank | K::SubBank => "i.bank_chain_id", + K::Category | K::SubCategory => { + sql.more_from(CATEGORY_JOIN); + "ic.category_id" + } + K::Mode => { + sql.more_from(MODE_JOIN); + "im.mode_id" + } + _ => continue, + }; + if exclude_list.contains_none(kind) { + sql.where_and(format!("{selector} IS NOT NULL")); + } + for exclude in exclude_list.normal_excludes_by_kind(kind) { + if let Fil::Komplete(id) = exclude { + sql.where_and_with_param(format!("{selector} <> ?"), id); + // For parent filter excludes such as banks and categories, we also need to + // exclude the child filters. + match kind { + K::Bank => { + sql.where_and_with_param( + r#" + i.bank_chain_id NOT IN ( + SELECT child.id FROM k_bank_chain child WHERE child.entry1 = ( + SELECT parent.entry1 FROM k_bank_chain parent WHERE parent.id = ? + ) + ) + "#, + id, + ); + } + K::Category => { + sql.where_and_with_param( + r#" + ic.category_id NOT IN ( + SELECT child.id FROM k_category child WHERE child.category = ( + SELECT parent.category FROM k_category parent WHERE parent.id = ? + ) + ) + "#, + id, + ); + } + _ => {} + } + } + } + } + // Put it all together + let sql_query = sql.to_string(); + tracing::trace!("{sql_query}"); + let mut statement = self.connection.prepare_cached(&sql_query)?; + let collection: Result = statement + .query(sql.params.as_slice())? + .mapped(|row| row_mapper(row)) + .collect(); + Ok(collection?) + } +} + +fn path_to_main_and_favorites_db() -> Result<(PathBuf, PathBuf), &'static str> { + let data_dir = dirs::data_local_dir().ok_or("couldn't identify data-local dir")?; + let main_db_path = data_dir.join("Native Instruments/Komplete Kontrol/komplete.db3"); + let favorites_db_path = data_dir.join("Native Instruments/Shared/favorites.db3"); + Ok((main_db_path, favorites_db_path)) +} + +fn map_to_komplete_filter_id(row: &Row) -> Result, rusqlite::Error> { + row.get(0) +} + +#[derive(Default)] +struct Sql<'a> { + select_clause: Cow<'a, str>, + from_main: Cow<'a, str>, + from_joins: BTreeSet>, + where_conjunctions: Vec>, + order_by_conditions: Vec>, + params: Vec<&'a dyn ToSql>, +} + +impl<'a> Sql<'a> { + pub fn select(&mut self, value: impl Into>) { + self.select_clause = value.into(); + } + + pub fn from(&mut self, value: impl Into>) { + self.from_main = value.into(); + } + + pub fn more_from(&mut self, value: impl Into>) { + self.from_joins.insert(value.into()); + } + + pub fn where_and_with_param(&mut self, value: impl Into>, param: &'a dyn ToSql) { + self.where_and(value); + self.params.push(param); + } + + pub fn add_param(&mut self, param: &'a dyn ToSql) { + self.params.push(param); + } + + pub fn where_and(&mut self, value: impl Into>) { + self.where_conjunctions.push(value.into()); + } + + pub fn where_and_false(&mut self) { + self.where_and("false"); + } + + pub fn order_by(&mut self, value: impl Into>) { + self.order_by_conditions.push(value.into()); + } +} + +impl Display for Sql<'_> { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + writeln!(f, "SELECT {}", &self.select_clause)?; + writeln!(f, "FROM {}", &self.from_main)?; + for join in &self.from_joins { + writeln!(f, " {}", join)?; + } + for (i, cond) in self.where_conjunctions.iter().enumerate() { + if i == 0 { + writeln!(f, "WHERE {}", cond)?; + } else { + writeln!(f, " AND {}", cond)?; + } + } + if !self.order_by_conditions.is_empty() { + write!(f, "ORDER BY ")?; + } + for (i, cond) in self.order_by_conditions.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + cond.fmt(f)?; + } + Ok(()) + } +} + +/// This picks one of the bank entries to serve as "canonical" parent bank. Which one is not +/// really important as long as the result is the same in all of our queries. It must be +/// deterministic! In our case, SQLite will always choose the one with the lowest ID. +/// +/// Note: If there would always be a row whose entry2/entry3 entries are NULL, we would pick +/// this one. But often, there's no such row (because no preset is mapped to the parent directly). +/// So at the end, it still doesn't matter which one we pick as "canonical" parent. +const BANK_SQL_QUERY: &str = + "SELECT id, '', entry1, '' FROM k_bank_chain GROUP BY entry1 ORDER BY entry1"; +const CONTENT_PATH_JOIN: &str = "JOIN k_content_path cp ON cp.id = i.content_path_id"; +const CATEGORY_JOIN: &str = "JOIN k_sound_info_category ic ON i.id = ic.sound_info_id"; +const MODE_JOIN: &str = "JOIN k_sound_info_mode im ON i.id = im.sound_info_id"; +const BANK_CHAIN_JOIN: &str = "JOIN k_bank_chain bc ON i.bank_chain_id = bc.id"; +const ZERO: u32 = 0; +const ONE: u32 = 1; +const TWO: u32 = 2; +const FOUR: u32 = 4; + +const SUPPORTED_FILE_EXTENSIONS: &[&str] = &["wav", "aif", "ogg", "mp3", "nksf", "nksfx"]; + +/// In Komplete, product (top-level bank) doesn't necessarily need be a plug-in product. It can be +/// a sub product *for* a plug-in product (e.g. "Vintage Organs" is a product *for* +/// plug-in product "Kontakt"). In the database, there's no association that indicates which sub +/// product belongs to which plug-in product. So we need another way to draw that association. +/// The most accurate way is to use the extension. +/// +/// This info is *not* used for loading the preset (not implemented anyway for below plug-ins) +/// but for presenting a list of associated plug-ins when right-clicking the preset. +const EXTENSION_TO_PRODUCT_NAME_MAPPING: &[(&str, &str)] = &[ + ("nki", "Kontakt"), + ("nksn", "Kontakt"), + ("ens", "Reaktor"), + ("nrkt", "Reaktor"), + ("nksr", "Reaktor"), + // Other associations are not necessary because above products seem to be the only ones which + // allow sub products. But for documentation purposes, we leave the other extensions here as + // well: + // ("nabs", "Absynth"), + // ("nbkt", "Battery"), + // ("nmsv", "Massive"), + // ("nfm8", "Fm8"), + // ("ngrr", "Guitar Rig"), +]; + +fn determine_preview_file(preset_file: &Utf8Path) -> Option { + let preview_dir = preset_file.parent()?.join(".previews"); + let pure_file_name = preset_file.file_name()?; + let preview_file_name = format!("{pure_file_name}.ogg"); + Some(preview_dir.join(preview_file_name)) +} + +#[derive(Default)] +struct NksFilterItemCollections { + bank_collections: NksParentChildCollections, + category_collections: NksParentChildCollections, + modes: Vec, +} + +#[derive(Default)] +struct NksParentChildCollections { + parent_items: Vec, + child_items: Vec, +} + +impl NksParentChildCollections { + /// Rows must be sorted by `entry1`, otherwise it won't work. + pub fn from_sorted_rows(rows: impl IntoIterator) -> Self { + let mut parent_items: Vec = Vec::new(); + let child_items = rows + .into_iter() + .map(|row| { + let parent_id = if let Some(last_parent_item) = parent_items.last_mut() { + if row.level1 == last_parent_item.name { + // We are still in the same bank. Add child ID to that bank. + last_parent_item.child_ids.insert(row.id); + last_parent_item.id + } else { + // New bank + parent_items.push(ParentNksFilterItem::from_hierarchy_row(&row)); + row.id + } + } else { + // No bank yet. Add first one. + parent_items.push(ParentNksFilterItem::from_hierarchy_row(&row)); + row.id + }; + ChildNksFilterItem { + id: row.id, + name: if let Some(entry2) = row.level2 { + if let Some(entry3) = row.level3 { + Some(format!("{entry2} / {entry3}")) + } else { + Some(entry2) + } + } else { + None + }, + parent_id, + parent_name: row.level1, + } + }) + .collect(); + Self { + parent_items, + child_items, + } + } +} + +struct NonEmptyNksFilters { + banks_and_sub_banks: NonEmptyNksFilter, + categories_and_sub_categories: NonEmptyNksFilter, + modes: NonEmptyNksFilter, +} + +#[derive(Default)] +struct NonEmptyNksFilter { + /// The IDs of all filter items of this kind that have presets. + non_empty_ids: NonCryptoHashSet, + /// Whether there are presets not associated to any filter item of this kind. + has_non_associated_presets: bool, +} + +impl NonEmptyNksFilter { + pub fn from_vec(vec: Vec>) -> Self { + let mut has_non_associated_presets = false; + let non_empty_ids = vec + .into_iter() + .filter_map(|id| { + if id.is_none() { + has_non_associated_presets = true; + } + id + }) + .collect(); + Self { + non_empty_ids, + has_non_associated_presets, + } + } +} + +/// For each parent filter item, there's also one child filter item with the same ID! The one that +/// represents direct association to the parent. +#[derive(Eq, PartialEq, Debug)] +struct ParentNksFilterItem { + id: u32, + name: String, + child_ids: NonCryptoHashSet, +} + +impl ParentNksFilterItem { + pub fn from_hierarchy_row(r: &HierarchyRow) -> Self { + Self { + id: r.id, + name: r.level1.clone(), + child_ids: [r.id].into_iter().collect(), + } + } +} + +#[derive(Eq, PartialEq, Debug)] +struct ChildNksFilterItem { + id: u32, + name: Option, + parent_id: u32, + parent_name: String, +} + +struct SimpleNksFilterItem { + id: u32, + name: String, +} + +struct HierarchyRow { + id: u32, + level1: String, + level2: Option, + level3: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + + impl ParentNksFilterItem { + pub fn new(id: u32, name: String, child_ids: NonCryptoHashSet) -> Self { + Self { + id, + name, + child_ids, + } + } + } + impl ChildNksFilterItem { + pub fn new(id: u32, name: Option, parent_id: u32, parent_name: String) -> Self { + Self { + id, + name, + parent_id, + parent_name, + } + } + } + + impl HierarchyRow { + pub fn new( + id: u32, + entry1: String, + entry2: Option, + entry3: Option, + ) -> Self { + Self { + id, + level1: entry1, + level2: entry2, + level3: entry3, + } + } + } + + #[test] + fn nks_bank_collections() { + // Give + let rows = vec![ + HierarchyRow::new(1, "a".to_string(), None, None), + HierarchyRow::new(2, "a".to_string(), Some("aa".to_string()), None), + HierarchyRow::new(3, "a".to_string(), Some("ab".to_string()), None), + HierarchyRow::new(4, "b".to_string(), Some("ba".to_string()), None), + HierarchyRow::new(5, "b".to_string(), Some("bb".to_string()), None), + HierarchyRow::new(6, "b".to_string(), Some("bc".to_string()), None), + HierarchyRow::new(7, "c".to_string(), Some("ca".to_string()), None), + HierarchyRow::new(8, "c".to_string(), None, None), + HierarchyRow::new(9, "d".to_string(), None, None), + HierarchyRow::new(10, "e".to_string(), Some("ea".to_string()), None), + HierarchyRow::new( + 11, + "f".to_string(), + Some("fa".to_string()), + Some("faa".to_string()), + ), + HierarchyRow::new( + 12, + "f".to_string(), + Some("fa".to_string()), + Some("fab".to_string()), + ), + ]; + // When + let bank_collections = NksParentChildCollections::from_sorted_rows(rows.into_iter()); + // Then + assert_eq!( + bank_collections.parent_items, + vec![ + ParentNksFilterItem::new(1, "a".to_string(), [1, 2, 3].into_iter().collect()), + ParentNksFilterItem::new(4, "b".to_string(), [4, 5, 6].into_iter().collect()), + ParentNksFilterItem::new(7, "c".to_string(), [7, 8].into_iter().collect()), + ParentNksFilterItem::new(9, "d".to_string(), [9].into_iter().collect()), + ParentNksFilterItem::new(10, "e".to_string(), [10].into_iter().collect()), + ParentNksFilterItem::new(11, "f".to_string(), [11, 12].into_iter().collect()) + ] + ); + assert_eq!( + bank_collections.child_items, + vec![ + ChildNksFilterItem::new(1, None, 1, "a".to_string()), + ChildNksFilterItem::new(2, Some("aa".to_string()), 1, "a".to_string()), + ChildNksFilterItem::new(3, Some("ab".to_string()), 1, "a".to_string()), + ChildNksFilterItem::new(4, Some("ba".to_string()), 4, "b".to_string()), + ChildNksFilterItem::new(5, Some("bb".to_string()), 4, "b".to_string()), + ChildNksFilterItem::new(6, Some("bc".to_string()), 4, "b".to_string()), + ChildNksFilterItem::new(7, Some("ca".to_string()), 7, "c".to_string()), + ChildNksFilterItem::new(8, None, 7, "c".to_string()), + ChildNksFilterItem::new(9, None, 9, "d".to_string()), + ChildNksFilterItem::new(10, Some("ea".to_string()), 10, "e".to_string()), + ChildNksFilterItem::new(11, Some("fa / faa".to_string()), 11, "f".to_string()), + ChildNksFilterItem::new(12, Some("fa / fab".to_string()), 11, "f".to_string()), + ] + ); + } +} diff --git a/plugin-reaper-realearn/pot/src/providers/mod.rs b/plugin-reaper-realearn/pot/src/providers/mod.rs new file mode 100644 index 0000000..be8629e --- /dev/null +++ b/plugin-reaper-realearn/pot/src/providers/mod.rs @@ -0,0 +1,5 @@ +pub mod defaults; +pub mod directory; +pub mod ini; +pub mod komplete; +pub mod projects; diff --git a/plugin-reaper-realearn/pot/src/providers/projects.rs b/plugin-reaper-realearn/pot/src/providers/projects.rs new file mode 100644 index 0000000..a807eef --- /dev/null +++ b/plugin-reaper-realearn/pot/src/providers/projects.rs @@ -0,0 +1,430 @@ +use crate::provider_database::{ + Database, InnerFilterItem, InnerFilterItemCollections, ProviderContext, SortablePresetId, +}; +use crate::{ + Fil, FilterInput, FilterItem, FilterItemId, InnerBuildInput, InnerPresetId, + PersistentDatabaseId, PersistentInnerPresetId, PersistentPresetId, PipeEscaped, PluginId, + PotPreset, PotPresetCommon, PotPresetKind, ProjectBasedPotPresetKind, ProjectId, SearchInput, +}; +use std::borrow::Cow; + +use crate::plugins::{PluginCore, PluginDatabase}; +use base::hash_util::{ + calculate_persistent_non_crypto_hash_one_shot, NonCryptoIndexMap, PersistentHash, +}; +use either::Either; +use enumset::{enum_set, EnumSet}; +use helgobox_api::persistence::PotFilterKind; +use itertools::Itertools; + +use std::error::Error; +use std::ffi::OsStr; + +use std::ops::Range; +use std::path::{Path, PathBuf}; +use std::{fs, iter}; +use walkdir::WalkDir; + +pub struct ProjectDatabase { + persistent_id: PersistentDatabaseId, + root_dir: PathBuf, + name: String, + description: String, + projects: Vec, + preset_entries: Vec, +} + +pub struct ProjectDbConfig { + pub persistent_id: PersistentDatabaseId, + pub root_dir: PathBuf, + pub name: String, +} + +impl ProjectDatabase { + pub fn open(config: ProjectDbConfig) -> Result> { + if !config.root_dir.try_exists()? { + return Err("path to projects root directory doesn't exist".into()); + } + let db = Self { + persistent_id: config.persistent_id, + name: config.name, + preset_entries: Default::default(), + description: format!("Projects in {}", config.root_dir.to_string_lossy()), + root_dir: config.root_dir, + projects: vec![], + }; + Ok(db) + } + + fn query_presets_internal<'a>( + &'a self, + filter_input: &'a FilterInput, + ) -> impl Iterator + 'a { + let matches = !filter_input.filters.wants_factory_presets_only(); + if !matches { + return Either::Left(iter::empty()); + } + let iter = self.preset_entries.iter().enumerate().filter(|(id, e)| { + if let Some(FilterItemId(Some(Fil::Project(id)))) = + filter_input.filters.get(PotFilterKind::Project) + { + if e.project_id != id { + return false; + } + } + let id = InnerPresetId(*id as _); + e.track_preset + .used_plugins + .values() + .any(|core| filter_input.everything_matches(Some(core), id)) + }); + Either::Right(iter) + } +} + +struct PresetEntry { + project_id: ProjectId, + track_preset: TrackPreset, +} + +struct Proj { + name: String, + relative_path_to_rpp: String, +} + +impl Database for ProjectDatabase { + fn persistent_id(&self) -> &PersistentDatabaseId { + &self.persistent_id + } + + fn name(&self) -> Cow { + self.name.as_str().into() + } + + fn description(&self) -> Cow { + self.description.as_str().into() + } + + fn supported_advanced_filter_kinds(&self) -> EnumSet { + enum_set!(PotFilterKind::Bank | PotFilterKind::Project) + } + + fn refresh(&mut self, ctx: &ProviderContext) -> Result<(), Box> { + self.preset_entries = WalkDir::new(&self.root_dir) + .follow_links(true) + .into_iter() + .filter_map(|entry| { + let entry = entry.ok()?; + if !entry.file_type().is_file() { + return None; + } + let extension = entry.path().extension()?; + if extension != OsStr::new("RPP") { + return None; + } + let relative_path = entry.path().strip_prefix(&self.root_dir).ok()?; + let stem = entry.path().file_stem()?; + // Immediately exclude relative paths that can't be represented as valid UTF-8. + // Otherwise we will potentially open a can of worms (regarding persistence etc.). + let project = Proj { + name: stem.to_str()?.to_string(), + relative_path_to_rpp: relative_path.to_str()?.to_string(), + }; + self.projects.push(project); + let project_id = ProjectId(self.projects.len() as u32 - 1); + process_file(entry.path(), ctx.plugin_db, project_id).ok() + }) + .flatten() + .collect(); + Ok(()) + } + + fn query_filter_collections( + &self, + _: &ProviderContext, + input: InnerBuildInput, + affected_kinds: EnumSet, + ) -> Result> { + let mut collections = InnerFilterItemCollections::empty(); + if affected_kinds.contains(PotFilterKind::Project) { + let mut new_filters = *input.filter_input.filters; + new_filters.clear_this_and_dependent_filters(PotFilterKind::Project); + let project_items = self + .query_presets_internal(&input.filter_input.with_filters(&new_filters)) + .map(|(_, entry)| entry.project_id) + .unique() + .filter_map(|project_id| { + let project = self.projects.get(project_id.0 as usize)?; + let item = FilterItem { + persistent_id: "".to_string(), + id: FilterItemId(Some(Fil::Project(project_id))), + parent_name: None, + name: Some(project.name.clone()), + icon: None, + more_info: Some(project.relative_path_to_rpp.to_string()), + }; + Some(InnerFilterItem::Unique(item)) + }) + .collect(); + collections.set(PotFilterKind::Project, project_items); + } + if affected_kinds.contains(PotFilterKind::Bank) { + let mut new_filters = *input.filter_input.filters; + new_filters.clear_this_and_dependent_filters(PotFilterKind::Bank); + let product_items = self + .query_presets_internal(&input.filter_input.with_filters(&new_filters)) + .flat_map(|(_, entry)| { + entry + .track_preset + .used_plugins + .values() + .map(|core| core.product_id) + }) + .unique() + .map(InnerFilterItem::Product) + .collect(); + collections.set(PotFilterKind::Bank, product_items); + } + Ok(collections) + } + + fn query_presets( + &self, + ctx: &ProviderContext, + input: InnerBuildInput, + ) -> Result, Box> { + let preset_ids = self + .query_presets_internal(&input.filter_input) + .filter(|(_, preset_entry)| { + let search_input = ProjectSearchInput { ctx, preset_entry }; + input.search_evaluator.matches(search_input) + }) + .map(|(i, entry)| SortablePresetId::new(i as _, entry.track_preset.preset_name.clone())) + .collect(); + Ok(preset_ids) + } + + fn find_preset_by_id( + &self, + ctx: &ProviderContext, + preset_id: InnerPresetId, + ) -> Option { + let preset_entry = self.preset_entries.get(preset_id.0 as usize)?; + let project = self.projects.get(preset_entry.project_id.0 as usize)?; + let relative_path = PathBuf::from(&project.relative_path_to_rpp); + let preset = PotPreset { + common: PotPresetCommon { + persistent_id: PersistentPresetId::new( + self.persistent_id().clone(), + create_persistent_inner_id(project, preset_entry), + ), + name: preset_entry.track_preset.preset_name.clone(), + context_name: Some(project.name.clone()), + plugin_ids: preset_entry + .track_preset + .used_plugins + .values() + .map(|c| c.id) + .collect(), + product_ids: preset_entry + .track_preset + .used_plugins + .values() + .map(|c| c.product_id) + .collect(), + product_name: build_product_name(ctx, preset_entry).map(|n| n.to_string()), + content_hash: Some(preset_entry.track_preset.content_hash), + db_specific_preview_file: None, + is_supported: true, + is_available: !preset_entry.track_preset.used_plugins.is_empty(), + metadata: Default::default(), + }, + kind: PotPresetKind::ProjectBased(ProjectBasedPotPresetKind { + path_to_rpp: self.root_dir.join(relative_path), + fx_chain_range: preset_entry.track_preset.fx_chain_range.clone(), + }), + }; + Some(preset) + } +} + +struct TrackPreset { + preset_name: String, + track_id: String, + fx_chain_range: Range, + used_plugins: NonCryptoIndexMap, + content_hash: PersistentHash, +} + +fn process_file( + path: &Path, + plugin_db: &PluginDatabase, + project_id: ProjectId, +) -> Result, Box> { + let rppxml = fs::read_to_string(path)?; + Ok(extract_presets(&rppxml, plugin_db, project_id)) +} + +/// Example: `maojiao/2023-02-03-ben/2023-02-03-ben.RPP|0FF9F738-7CF6-8A49-9AEA-A9AF26DF9C46` +fn create_persistent_inner_id( + project: &Proj, + preset_entry: &PresetEntry, +) -> PersistentInnerPresetId { + let escaped_path = PipeEscaped(project.relative_path_to_rpp.as_str()); + let id = format!("{escaped_path}|{}", preset_entry.track_preset.track_id); + PersistentInnerPresetId::new(id) +} + +fn extract_presets( + rppxml: &str, + plugin_db: &PluginDatabase, + project_id: ProjectId, +) -> Vec { + use rppxml_parser::*; + let parser = OneShotParser::new(rppxml); + #[derive(Debug, Default)] + struct P<'a> { + track_id: &'a str, + name: Option<&'a str>, + rfx_chain_start: Option, + rfx_chain_end: Option, + used_plugins: NonCryptoIndexMap, + } + impl<'a> P<'a> { + pub fn new(track_id: &'a str) -> Self { + Self { + track_id, + name: None, + rfx_chain_start: None, + rfx_chain_end: None, + used_plugins: Default::default(), + } + } + } + let mut stack: Vec<&str> = Vec::with_capacity(10); + let mut preset: Option

    = None; + let mut presets: Vec

    = vec![]; + for e in parser.events() { + let line = e.line(); + match e.item { + Item::StartTag(el) => { + stack.push(el.name()); + match *stack.as_slice() { + ["REAPER_PROJECT", "TRACK"] => { + let track_id = el.into_values().next().unwrap_or_default(); + preset = Some(P::new(track_id)); + } + ["REAPER_PROJECT", "TRACK", "FXCHAIN", _] => { + if let Some(p) = &mut preset { + if let Some(plugin) = + plugin_db.detect_plugin_from_rxml_line(line.trim()) + { + p.used_plugins + .insert(plugin.common.core.id, plugin.common.core); + } + } + } + _ => {} + } + } + Item::EndTag => { + match *stack.as_slice() { + ["REAPER_PROJECT", "TRACK"] => { + presets.extend(preset.take()); + } + ["REAPER_PROJECT", "TRACK", "FXCHAIN"] => { + if let Some(p) = &mut preset { + p.rfx_chain_end = Some(e.start); + } + } + _ => {} + } + stack.pop(); + } + Item::Attribute(el) => match *stack.as_slice() { + ["REAPER_PROJECT", "TRACK"] => match el.name() { + "NAME" => { + if let Some(p) = &mut preset { + let name = el.into_values().next().unwrap_or_default(); + p.name = Some(name) + } + } + _ => {} + }, + ["REAPER_PROJECT", "TRACK", "FXCHAIN"] => match el.name() { + "BYPASS" => { + if let Some(p) = &mut preset { + if p.rfx_chain_start.is_none() { + p.rfx_chain_start = Some(e.start); + } + } + } + _ => {} + }, + _ => {} + }, + Item::Content(_) => {} + Item::Empty => {} + } + } + presets + .into_iter() + .filter_map(|p| { + if p.used_plugins.is_empty() { + return None; + } + let fx_chain_range = p.rfx_chain_start?..p.rfx_chain_end?; + let track_id = p.track_id.strip_prefix('{')?.strip_suffix('}')?.to_string(); + let preset_name = p.name?.to_string(); + let content_hash = calculate_persistent_non_crypto_hash_one_shot( + rppxml[fx_chain_range.clone()].as_bytes(), + ); + let track_preset = TrackPreset { + preset_name, + track_id, + fx_chain_range, + used_plugins: p.used_plugins, + content_hash, + }; + let preset_entry = PresetEntry { + project_id, + track_preset, + }; + Some(preset_entry) + }) + .collect() +} + +struct ProjectSearchInput<'a> { + ctx: &'a ProviderContext<'a>, + preset_entry: &'a PresetEntry, +} + +impl SearchInput for ProjectSearchInput<'_> { + fn preset_name(&self) -> &str { + &self.preset_entry.track_preset.preset_name + } + + fn product_name(&self) -> Option> { + build_product_name(self.ctx, self.preset_entry) + } + + fn file_extension(&self) -> Option<&str> { + None + } +} + +fn build_product_name( + ctx: &ProviderContext, + preset_entry: &PresetEntry, +) -> Option> { + if preset_entry.track_preset.used_plugins.len() > 1 { + Some("".into()) + } else if let Some(first) = preset_entry.track_preset.used_plugins.values().next() { + ctx.plugin_db + .find_plugin_by_id(&first.id) + .map(|p| p.common.to_string().into()) + } else { + None + } +} diff --git a/plugin-reaper-realearn/pot/src/worker.rs b/plugin-reaper-realearn/pot/src/worker.rs new file mode 100644 index 0000000..5043108 --- /dev/null +++ b/plugin-reaper-realearn/pot/src/worker.rs @@ -0,0 +1,136 @@ +use base::Global; +use futures::channel::oneshot; +use once_cell::sync::Lazy; +use std::any::Any; +use std::collections::VecDeque; +use std::error::Error; +use std::future::Future; +use tokio::runtime::Runtime; + +pub type PotWorkerDispatcher = WorkerDispatcher; +pub type MainThreadDispatcher = WorkerDispatcher; + +type PotWorkerResult = Result>; + +/// Helper for easily dispatching background work from a non-asynchronous context and executing +/// code as soon as the result of the background work is available. +#[derive(Debug)] +pub struct WorkerDispatcher { + tasks: VecDeque>>, + spawner: S, +} + +impl WorkerDispatcher +where + S: Spawner, +{ + pub fn new(spawner: S) -> Self { + Self { + tasks: Default::default(), + spawner, + } + } + + /// Checks for each background task if a result is available and if yes, executes the + /// corresponding result handler. + /// + /// Must be called repeatedly from the non-asynchronous code. + pub fn poll(&mut self, state: &mut C) { + // Take next task from queue. Do nothing if no task enqueued. + let Some(mut task) = self.tasks.pop_front() else { + return; + }; + // Check if task has already produced some result. + match task.result_receiver.try_recv() { + // Result available. Handle it, done. + Ok(Some(result)) => { + (task.result_handler)(state, result); + } + // Result not yet available. Push task back on queue. + Ok(None) => { + self.tasks.push_back(task); + } + // Sender dropped. Discard task. + Err(_) => {} + } + } + + /// Schedules the given work for execution by the Pot worker and registers a handler that + /// will be executed as soon as the work has produced a result. + pub fn do_in_background_and_then( + &mut self, + work: impl Future + Send + 'static, + result_handler: impl FnOnce(&mut C, R) + Send + 'static, + ) where + R: Send + 'static, + { + // Create one-shot channel for transferring task result from background thread to + // polling thread. + let (sender, receiver) = oneshot::channel::>(); + // Combine this and the corresponding handler into a so-called task. + let task = Task { + result_receiver: receiver, + result_handler: Box::new(|context, result| { + if let Ok(result) = result.downcast::() { + result_handler(context, *result); + } + }), + }; + // Enqueue that task for repeated polling. + self.tasks.push_back(task); + // Schedule work to be done in background. + self.spawner.spawn(async move { + // Start executing work + let result = work.await; + // Work done. Send result. + let _ = sender.send(Box::new(result)); + Ok(()) + }); + } +} + +pub fn spawn_in_pot_worker(f: impl Future> + Send + 'static) { + POT_WORKER_RUNTIME.as_ref().unwrap().spawn(async { + f.await.unwrap(); + }); +} + +pub trait Spawner { + fn spawn(&self, f: impl Future> + Send + 'static); +} + +#[derive(Debug)] +pub struct PotWorkerSpawner; + +impl Spawner for PotWorkerSpawner { + fn spawn(&self, f: impl Future> + Send + 'static) { + spawn_in_pot_worker(f); + } +} + +#[derive(Debug)] +pub struct MainThreadSpawner; + +impl Spawner for MainThreadSpawner { + fn spawn(&self, f: impl Future> + Send + 'static) { + Global::future_support().spawn_in_main_thread_from_main_thread(f); + } +} + +#[derive(derivative::Derivative)] +#[derivative(Debug)] +struct Task { + result_receiver: oneshot::Receiver, + #[derivative(Debug = "ignore")] + result_handler: ResultHandler, +} + +type ResultHandler = Box; + +static POT_WORKER_RUNTIME: Lazy> = Lazy::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_time() + .thread_name("Helgobox Pot Worker") + .worker_threads(1) + .build() +}); diff --git a/plugin-reaper-realearn/reaboot.json b/plugin-reaper-realearn/reaboot.json new file mode 100644 index 0000000..1ce8815 --- /dev/null +++ b/plugin-reaper-realearn/reaboot.json @@ -0,0 +1,9 @@ +{ + "name": "Helgobox (ReaLearn & Playtime)", + "sub_title": "by Helgoboss Projects", + "required_packages": [ + "https://github.com/helgoboss/reaper-packages/raw/master/index.xml#p=Extensions/ReaLearn-x64&v=latest" + ], + "website": "https://www.helgoboss.org/projects/helgobox", + "skip_additional_packages": true +} \ No newline at end of file diff --git a/plugin-reaper-realearn/regenerate-doc-diagrams.sh b/plugin-reaper-realearn/regenerate-doc-diagrams.sh new file mode 100644 index 0000000..841af09 --- /dev/null +++ b/plugin-reaper-realearn/regenerate-doc-diagrams.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# This regenerates the D2 diagrams in the Antora documentations (requires d2 executable in path) +process_files() { + local DIR="$1" + for FILE in "$DIR"/*.d2; do + if [ -f "$FILE" ]; then + FILE_NAME=$(basename "$FILE") + echo "Processing $FILE_NAME" + FILE_NAME_NO_EXT="${FILE_NAME%.*}" + d2 --sketch "$FILE" "$DIR/target/$FILE_NAME_NO_EXT.svg" + fi + done +} + +process_files "doc/realearn/modules/ROOT/images/realearn/diagrams/control-flow" \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/api/luau/.stylua.toml b/plugin-reaper-realearn/resources/api/luau/.stylua.toml new file mode 100644 index 0000000..1d724ed --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/.stylua.toml @@ -0,0 +1,4 @@ +indent_type = "Spaces" +# This is important. We use Lua primarily for building large data structures and omitting +# the parentheses is what makes this very readable. +call_parentheses = "NoSingleTable" \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/api/luau/.vscode/extensions.json b/plugin-reaper-realearn/resources/api/luau/.vscode/extensions.json new file mode 100644 index 0000000..856ef3e --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "johnnymorganz.luau-lsp", + "johnnymorganz.stylua" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/api/luau/.vscode/settings.json b/plugin-reaper-realearn/resources/api/luau/.vscode/settings.json new file mode 100644 index 0000000..1759dbf --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "luau-lsp.types.roblox": false, + "luau-lsp.sourcemap.enabled": false +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/api/luau/README.md b/plugin-reaper-realearn/resources/api/luau/README.md new file mode 100644 index 0000000..be23d2a --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/README.md @@ -0,0 +1,192 @@ +# Compartment preset workspace + +This folder is a ReaLearn compartment preset workspace. It should be a direct sub folder of the +ReaLearn +controller or main preset folder. The purpose of this folder is to contain a set of ReaLearn +compartment presets +and provide you with a convenient workspace to develop those presets. + +## Choosing a good folder name + +If you have created this workspace via ReaLearn, it will have an auto-generated name. As explained +further below, +the name of the workspace folder becomes part of the preset ID! Therefore, make sure to do one of +the following +things: + +- **Either** rename this folder to a proper identifier of your choice (a name with only lowercase + letters and dashes, + e.g. `foo-bar`). A good default choice is to rename it to your operating system login name. If you + have already + saved presets from the ReaLearn user interface, that folder will probably already exist. +- **Or** copy all the files that you need from it to an already existing other workspace folder and + delete this one. + +## Sharing presets with other users + +Each workspace is completely self-contained, that means you can easily share it with other users +without having to worry about possible name conflicts or missing files: + +1. You pass the complete workspace folder to the other user (e.g. by zipping and mailing it or by + using cloud sharing). +2. The other user switches to the correct compartment and presses "Menu → Compartment presets → Open + compartment preset folder" and puts your workspace folder into it. +3. The other user presses "Menu → Compartment presets → Reload all compartment presets from disk". + +## Directory structure + +The meaning of each file in this folder is determined by its file extension. + +### Compartment presets + +Compartment preset files within this folder can be arbitrarily nested. But be aware that the +preset's file path relative +to the controller or main preset folder is at the same time its ID! If you change its location or +name, you will make +it a different preset as far as ReaLearn is concerned! + +**Example:** If the name of your workspace folder is `john` and you put your preset in +`john/foo/daw-control.json`, +the preset ID will be `john/foo/daw-control`. + +Tips: + +- Factory presets always begin with `factory`. This workspace name is reserved, so you shouldn't use + it. There's no way + to override factory presets. +- Presets can also reside directly in the main/controller preset folder, not in a workspace folder. + This was normal + in the past but is deprecated now. Such presets will show up as "Unsorted" in ReaLearn's preset + picker. + +Presets can be written in the following different formats. + +### Compartment presets in JSON format + +**File extension:** `.json` + +ReaLearn will read all files with this extension as compartment presets written +in [JSON](https://www.json.org/) format. + +A JSON preset is essentially a big data structure that describes the contents of one ReaLearn +compartment, such as +mappings and groups. JSON presets are simple and verbose. They don't contain any logic and can +become very +repetitive. + +It's the format in which ReaLearn saves presets whenever you use "Save as..." in the user +interface. For all other purposes, it's not recommended to use JSON! If you want to code your own +presets, +you should use the Luau format instead. + +### Compartment presets in Luau format + +**File extension:** `.preset.luau` or `.preset.lua` + +ReaLearn will read all files with this extension as compartment presets written +in [Luau](https://luau-lang.org/). + +A Luau preset is written in a real programming language. It contains a Luau program whose single +purpose is to return +a result. And that result should look very similar to the big data structure of JSON presets. + +In addition to the actual Luau code, Luau compartment presets contain a meta-data section right at +the top, +which contains the name of the preset and other data that can be scanned by ReaLearn before actually +loading the +preset by executing its code. + +If you have chosen to create this workspace with factory presets, you should find multiple Luau +presets in this folder +that can serve as starting point for building your own presets. You will realize that many of them +use Luau type +annotations and helper methods, which enables auto-completion during development and should make the +preset code +more understandable. If you want to learn more about this, read the section below. + +As an additional help, you can use "Export to clipboard → Export ... compartment as Lua" in +ReaLearn. This gives you +a chunk of text formatted as Luau. It doesn't contain any logic, it looks more like a configuration +file +but its valid Luau code. This chunk of text is not a ready-made preset (because it's meant to be +used with +"Import from clipboard"). But you will see that a big part of the chunk is exactly the same kind of +data structure that you need to generate as part of a Luau preset. + +### Reusable Luau modules + +**File extension:** `.luau` or `.lua` (without `.preset`) + +The workspace folder can contain Luau files that are not presets. ReaLearn ignores them when +scanning for +presets. But you can make use of them to make preset development more convenient. + +Let's assume you have a function that you need in more than one preset. Instead of pasting it into +each preset, you can +put that function into a module and require it in each preset. Luau presets can use the function +`require()` to include +Luau modules that are located in the same workspace. The path passed to `require()` is always +relative to the workspace +root folder. + +**Example:** `local my_module = require("my_module")` makes the contents of module `my_module.luau` +available in your +preset. + +When ReaLearn creates a workspace for you, it will put a few useful modules into your workspace +root. You are free +to use them for developing your own presets. The most important file is `realearn.lua`, which +contains Luau type +declarations and helper functions. Have a look at the factory presets to see how to make use of +them. + +Having said all that ... you don't need to use any type declarations, annotations or helper +functions, they are +completely optional. As long as the value that your script returns is a table and all the entries in +that table +meet ReaLearn's expectations, you are good. + +## Basic process of coding presets + +The basic process of coding presets goes like this: + +1. Open the preset in your text editor +2. Make modifications +3. In ReaLearn, choose "Menu → Compartment presets → Reload all compartment presets from disk" +4. Find the preset in the preset menu and (re)load it into your compartment + +If you code presets that reside in your *user workspace* (the folder that has the same name as your +operating-system +username), a welcome shortcut is available: + +1. Open the preset in your text editor +2. Make modifications +3. Copy the complete code to the clipboard +4. In ReaLearn, press "Import from clipboard" + +This doesn't "officially" load the preset but imports its contents, which has almost the same +effect. Please note that +ReaLearn can't detect from where you copied the code. That's why if the copied Luau code uses +`require()`, ReaLearn +will look for the required module **in the user workspace only**. + +## Development environment + +You can use *any* text editor to code ReaLearn presets. For basic auto-completion and other goodies, +it should +have special support for the [Luau](https://luau-lang.org/) language. + +For a good coding experience right out of the box, I can +recommend [Visual Studio Code](https://code.visualstudio.com/). +The workspace created by ReaLearn is pre-configured to play nice with it: + +1. Open Visual Studio Code +2. File → Open Folder... → Choose the workspace directory (in which this `README.md` file is + located) and press "Open" +3. View → Command Palette... → Search for "Extension: Show Recommended Extensions" +4. Install the recommended extensions + +Now you are good to go! Open one of the factory presets and enjoy all syntax highlighting, code +completion, +code formatting, etc. :) + diff --git a/plugin-reaper-realearn/resources/api/luau/feedback_script_runtime.luau b/plugin-reaper-realearn/resources/api/luau/feedback_script_runtime.luau new file mode 100644 index 0000000..5f4fea1 --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/feedback_script_runtime.luau @@ -0,0 +1,49 @@ +--!strict + +--- Contains types for building ReaLearn feedback scripts in Lua. +local module = {} + +--- Provides access to some data that might come in useful to create the feedback value. +export type Context = { + --- Queries arbitrary properties, e.g. target or mapping properties. + --- + --- Those properties are the very same properties that you can use in textual feedback. + prop: (key: string) -> any? +} + +--- You are supposed to return a value structured like that. +export type Output = { + --- Feedback event. + feedback_event: FeedbackEvent, +} + +--- Feedback event. +export type FeedbackEvent = { + --- Feedback value (the most important piece of information to return). + --- + --- Can either be ... + --- + --- - ... a string (ideal for display sources) + --- - ... a number (ideal for LEDs and motor faders) + --- - ... nil (which means "turn source off", e.g. turn off the LED, turn down the motor fader, clear the display text) + --- - ... or anything else: `true`, `false` or an arbitrary table. The latter is currently only useful for the MIDI script source + --- because other sources don't support arbitrary tables. + value: any?, + --- Main color. + --- + --- Either the default color (= `nil`) or an RGB color. + color: RgbColor?, + --- Background color. + --- + --- Either the default color (= `nil`) or an RGB color. + background_color: RgbColor?, +} + +--- Color in the RGB color system. +export type RgbColor = { + r: number, + g: number, + b: number, +} + +return module \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/api/luau/midi_script_source_runtime.luau b/plugin-reaper-realearn/resources/api/luau/midi_script_source_runtime.luau new file mode 100644 index 0000000..e6a24f9 --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/midi_script_source_runtime.luau @@ -0,0 +1,80 @@ +--!strict + +--- Contains types for building ReaLearn MIDI scripts in Lua. +local module = {} + +--- Type of the `y` variable in MIDI scripts, which represents the input value of the MIDI script. +--- +--- This input value is usually generated by the "Target" and/or the "Glue" section. +export type InputValue = OffInputValue | NumericInputValue | TextInputValue | ComplexInputValue + +--- Whenever the input value is `nil`, ReaLearn wants you to "switch off" feedback. +--- +--- Meaning of "switch off" depending on the type of feedback element: +--- +--- - LED: Switch LED off +--- - Motor fader: Tear it completely down +--- - Text display: Clear all characters +export type OffInputValue = nil + +--- A simple numeric value. +--- +--- ReaLearn sends this if the feedback type in the "Glue" section is set to "Numeric feedback". +--- +--- Currently, this should always be a value within the unit interval, that is a value between 0.0 (= 0%) +--- and 1.0 (= 100%). +export type NumericInputValue = number + +--- A chunk of text. +--- +--- ReaLearn sends this if the feedback type in the "Glue" section is set to "Textual feedback". +--- +--- This is obviously most useful for text displays. But it can also be used for LED. When you design +--- a virtual control scheme, you could for example declare that the text "blinking" should make the LED blink. +export type TextInputValue = string + +--- A value that could be anything, from a simple number to a complex table. +--- +--- ReaLearn sends this if the feedback type in the "Glue" section is set to "Dynamic feedback". +export type ComplexInputValue = any + +--- Provides access to some data that might come in useful to create the outgoing MIDI messages. +export type Context = { + feedback_event: FeedbackEvent +} + +--- More data about the feedback event which triggered the execution of this MIDI script. +export type FeedbackEvent = { + --- Color as set in the "Glue" section. + --- + --- Either the default color (= `nil`) or an RGB color. + color: RgbColor?, + --- Background color as set in the "Glue" section. + background_color: RgbColor?, +} + +--- Color in the RGB color system. +export type RgbColor = { + r: number, + g: number, + b: number, +} + +--- You are supposed to return a value structured like that. +export type Output = { + --- Feedback address. + --- + --- Each distinct feedback element on the controller (LED, motor fader, etc.) should get a unique feedback address. + --- If you get this value wrong or don't set it at all, you might experience weird feedback behavior, e.g. LEDs that + --- stay on or switch off when they shouldn't. + address: number?, + --- A list MIDI messages to be sent to the controller. + messages: { MidiMessage } +} + +--- Array containing the raw bytes of a MIDI message. +--- +--- You can express any kind of MIDI message with that. +export type MidiMessage = { number } + +return module \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/api/luau/playtime.luau b/plugin-reaper-realearn/resources/api/luau/playtime.luau new file mode 100644 index 0000000..404798f --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/playtime.luau @@ -0,0 +1,1328 @@ +--!strict + +--- Attention: This file is generated from Rust code! Don't modify it directly! + +--- Contains types and helper functions for building Playtime presets +local module = {} + +--- Global settings that apply to all Playtime instances. +--- +--- This is different from the app settings (which are also global) in that the app settings are only about GUI +--- aspects (less important) and managed by the app. Whereas the engine settings configure settings that +--- apply even without GUI, and they are managed by the engine. +export type PlaytimeSettings = { + tempo_latency: TempoLatency?, +} +--- Creates a PlaytimeSettings value. +--- Global settings that apply to all Playtime instances. +--- +--- This is different from the app settings (which are also global) in that the app settings are only about GUI +--- aspects (less important) and managed by the app. Whereas the engine settings configure settings that +--- apply even without GUI, and they are managed by the engine. +function module.PlaytimeSettings(value: PlaytimeSettings): PlaytimeSettings + return value +end + +export type TempoLatency_Auto = { kind: "Auto" } + +export type TempoLatency_Manual = { kind: "Manual", millis: number } +export type TempoLatency = TempoLatency_Auto | TempoLatency_Manual + +--- A type that represents all possible kinds of TempoLatency. +export type TempoLatencyKind = "Auto" | "Manual" + +--- Helper table to create TempoLatency values of different kinds. +module.TempoLatency = {} + +--- Creates a TempoLatency of kind Auto. +function module.TempoLatency.Auto(): TempoLatency_Auto + return { + kind = "Auto", + } +end + +--- Creates a TempoLatency of kind Manual. +function module.TempoLatency.Manual(value: ManualTempoLatency): TempoLatency_Manual + local t: any = table.clone(value) + t.kind = "Manual" + return t +end + +export type ManualTempoLatency = { + millis: number, +} +--- Creates a ManualTempoLatency value. +function module.ManualTempoLatency(value: ManualTempoLatency): ManualTempoLatency + return value +end + +export type Matrix = { + columns: { Column }?, + rows: { Row }?, + clip_play_settings: MatrixClipPlaySettings?, + clip_record_settings: MatrixClipRecordSettings?, + transport_sync_mode: TransportSyncMode?, + common_tempo_range: TempoRange?, + activate_slot_on_trigger: boolean?, + click_volume: number?, + pre_roll_bars: number?, + color_palette: ColorPalette?, + content_quantization_settings: ContentQuantizationSettings?, + sequencer: MatrixSequencer?, + unknown_props: BTreeMap?, +} +--- Creates a Matrix value. +function module.Matrix(value: Matrix): Matrix + return value +end + +export type TransportSyncMode = "Partial" | "Full" + +export type MatrixSequencer = { + sequences: { MatrixSequence }?, + active_sequence: MatrixSequenceId?, +} +--- Creates a MatrixSequencer value. +function module.MatrixSequencer(value: MatrixSequencer): MatrixSequencer + return value +end + +export type MatrixSequence = { + id: MatrixSequenceId, + info: MatrixSequenceInfo, + data: MatrixSequenceData, +} +--- Creates a MatrixSequence value. +function module.MatrixSequence(value: MatrixSequence): MatrixSequence + return value +end + +export type MatrixSequenceInfo = { + name: string?, + created_at: string, +} +--- Creates a MatrixSequenceInfo value. +function module.MatrixSequenceInfo(value: MatrixSequenceInfo): MatrixSequenceInfo + return value +end + +export type MatrixSequenceData = { + ppq: number, + count_in: number, + events: { MatrixSequenceEvent }, +} +--- Creates a MatrixSequenceData value. +function module.MatrixSequenceData(value: MatrixSequenceData): MatrixSequenceData + return value +end + +export type MatrixSequenceEvent = { + pulse_diff: number, + message: MatrixSequenceMessage, +} +--- Creates a MatrixSequenceEvent value. +function module.MatrixSequenceEvent(value: MatrixSequenceEvent): MatrixSequenceEvent + return value +end + +export type MatrixSequenceMessage = + "PanicMatrix" + | "StopMatrix" + | "PanicColumn" + | "StopColumn" + | "StartScene" + | "PanicSlot" + | "StartSlot" + | "StopSlot" + +export type MatrixSequenceColumnMessage = { + index: number, +} +--- Creates a MatrixSequenceColumnMessage value. +function module.MatrixSequenceColumnMessage(value: MatrixSequenceColumnMessage): MatrixSequenceColumnMessage + return value +end + +export type MatrixSequenceRowMessage = { + index: number, +} +--- Creates a MatrixSequenceRowMessage value. +function module.MatrixSequenceRowMessage(value: MatrixSequenceRowMessage): MatrixSequenceRowMessage + return value +end + +export type MatrixSequenceSlotMessage = { + column_index: number, + row_index: number, +} +--- Creates a MatrixSequenceSlotMessage value. +function module.MatrixSequenceSlotMessage(value: MatrixSequenceSlotMessage): MatrixSequenceSlotMessage + return value +end + +export type MatrixSequenceStartSlotMessage = { + column_index: number, + row_index: number, + velocity: number, +} +--- Creates a MatrixSequenceStartSlotMessage value. +function module.MatrixSequenceStartSlotMessage(value: MatrixSequenceStartSlotMessage): MatrixSequenceStartSlotMessage + return value +end + +export type ContentQuantizationSettings = { + quantization: EvenQuantization, +} +--- Creates a ContentQuantizationSettings value. +function module.ContentQuantizationSettings(value: ContentQuantizationSettings): ContentQuantizationSettings + return value +end + +export type SignedMatrix = { + matrix: string, + signature: string, +} +--- Creates a SignedMatrix value. +function module.SignedMatrix(value: SignedMatrix): SignedMatrix + return value +end + +--- This is redundant (already contained in [`Matrix`]) but used to transfer settings only, without all the content +--- (columns, rows, slots). +export type MatrixSettings = { + clip_play_settings: MatrixClipPlaySettings, + clip_record_settings: MatrixClipRecordSettings, + common_tempo_range: TempoRange, + color_palette: ColorPalette, + content_quantization_settings: ContentQuantizationSettings, + activate_slot_on_trigger: boolean, + transport_sync_mode: TransportSyncMode, + pre_roll_bars: number, +} +--- Creates a MatrixSettings value. +--- This is redundant (already contained in [`Matrix`]) but used to transfer settings only, without all the content +--- (columns, rows, slots). +function module.MatrixSettings(value: MatrixSettings): MatrixSettings + return value +end + +export type TempoRange = { + min: number, + max: number, +} +--- Creates a TempoRange value. +function module.TempoRange(value: TempoRange): TempoRange + return value +end + +--- Matrix-global settings related to playing clips. +export type MatrixClipPlaySettings = { + trigger_behavior: TriggerSlotBehavior?, + start_timing: ClipPlayStartTiming?, + stop_timing: ClipPlayStopTiming?, + audio_settings: MatrixClipPlayAudioSettings?, + velocity_sensitivity: number?, +} +--- Creates a MatrixClipPlaySettings value. +--- Matrix-global settings related to playing clips. +function module.MatrixClipPlaySettings(value: MatrixClipPlaySettings): MatrixClipPlaySettings + return value +end + +export type MatrixClipPlayAudioSettings = { + resample_mode: VirtualResampleMode, + time_stretch_mode: AudioTimeStretchMode, + cache_behavior: AudioCacheBehavior, +} +--- Creates a MatrixClipPlayAudioSettings value. +function module.MatrixClipPlayAudioSettings(value: MatrixClipPlayAudioSettings): MatrixClipPlayAudioSettings + return value +end + +--- Matrix-global settings related to recording clips. +export type MatrixClipRecordSettings = { + start_timing: ClipRecordStartTiming?, + stop_timing: ClipRecordStopTiming?, + length_mode: RecordLengthMode?, + allow_tempo_detection_recording: boolean?, + custom_length: EvenQuantization?, + play_start_timing: ClipPlayStartTimingOverrideAfterRecording?, + play_stop_timing: ClipPlayStopTimingOverrideAfterRecording?, + time_base: ClipRecordTimeBase?, + looped: boolean?, + midi_settings: MatrixClipRecordMidiSettings?, + audio_settings: MatrixClipRecordAudioSettings?, +} +--- Creates a MatrixClipRecordSettings value. +--- Matrix-global settings related to recording clips. +function module.MatrixClipRecordSettings(value: MatrixClipRecordSettings): MatrixClipRecordSettings + return value +end + +export type RecordLengthMode = "OpenEnd" | "CustomLength" + +export type MatrixClipRecordMidiSettings = { + record_mode: MidiClipRecordMode?, + detect_downbeat: boolean?, + detect_input: boolean?, + auto_quantize: boolean?, + clip_settings: ClipMidiSettings?, +} +--- Creates a MatrixClipRecordMidiSettings value. +function module.MatrixClipRecordMidiSettings(value: MatrixClipRecordMidiSettings): MatrixClipRecordMidiSettings + return value +end + +export type MatrixClipRecordAudioSettings = { + detect_downbeat: boolean?, + detect_input: boolean?, +} +--- Creates a MatrixClipRecordAudioSettings value. +function module.MatrixClipRecordAudioSettings(value: MatrixClipRecordAudioSettings): MatrixClipRecordAudioSettings + return value +end + +--- Derives the time base of the resulting clip from the clip start timing. +export type ClipRecordTimeBase_DeriveFromRecordTiming = { kind: "DeriveFromRecordTiming" } + +--- Sets the time base of the recorded clip to [`ClipTimeBase::Time`]. +export type ClipRecordTimeBase_Time = { kind: "Time" } + +--- Sets the time base of the recorded clip to [`ClipTimeBase::Beat`]. +export type ClipRecordTimeBase_Beat = { kind: "Beat" } +export type ClipRecordTimeBase = + ClipRecordTimeBase_DeriveFromRecordTiming + | ClipRecordTimeBase_Time + | ClipRecordTimeBase_Beat + +--- A type that represents all possible kinds of ClipRecordTimeBase. +export type ClipRecordTimeBaseKind = "DeriveFromRecordTiming" | "Time" | "Beat" + +--- Helper table to create ClipRecordTimeBase values of different kinds. +module.ClipRecordTimeBase = {} + +--- Creates a ClipRecordTimeBase of kind DeriveFromRecordTiming. +--- Derives the time base of the resulting clip from the clip start timing. +function module.ClipRecordTimeBase.DeriveFromRecordTiming(): ClipRecordTimeBase_DeriveFromRecordTiming + return { + kind = "DeriveFromRecordTiming", + } +end + +--- Creates a ClipRecordTimeBase of kind Time. +--- Sets the time base of the recorded clip to [`ClipTimeBase::Time`]. +function module.ClipRecordTimeBase.Time(): ClipRecordTimeBase_Time + return { + kind = "Time", + } +end + +--- Creates a ClipRecordTimeBase of kind Beat. +--- Sets the time base of the recorded clip to [`ClipTimeBase::Beat`]. +function module.ClipRecordTimeBase.Beat(): ClipRecordTimeBase_Beat + return { + kind = "Beat", + } +end + +--- Press once = play, press again = stop. +export type TriggerSlotBehavior_TogglePlayStop = { kind: "TogglePlayStop" } + +--- Press once = play, release = stop. +export type TriggerSlotBehavior_MomentaryPlayStop = { kind: "MomentaryPlayStop" } + +--- Press once = play, press again = retrigger. +export type TriggerSlotBehavior_Retrigger = { kind: "Retrigger" } +export type TriggerSlotBehavior = + TriggerSlotBehavior_TogglePlayStop + | TriggerSlotBehavior_MomentaryPlayStop + | TriggerSlotBehavior_Retrigger + +--- A type that represents all possible kinds of TriggerSlotBehavior. +export type TriggerSlotBehaviorKind = "TogglePlayStop" | "MomentaryPlayStop" | "Retrigger" + +--- Helper table to create TriggerSlotBehavior values of different kinds. +module.TriggerSlotBehavior = {} + +--- Creates a TriggerSlotBehavior of kind TogglePlayStop. +--- Press once = play, press again = stop. +function module.TriggerSlotBehavior.TogglePlayStop(): TriggerSlotBehavior_TogglePlayStop + return { + kind = "TogglePlayStop", + } +end + +--- Creates a TriggerSlotBehavior of kind MomentaryPlayStop. +--- Press once = play, release = stop. +function module.TriggerSlotBehavior.MomentaryPlayStop(): TriggerSlotBehavior_MomentaryPlayStop + return { + kind = "MomentaryPlayStop", + } +end + +--- Creates a TriggerSlotBehavior of kind Retrigger. +--- Press once = play, press again = retrigger. +function module.TriggerSlotBehavior.Retrigger(): TriggerSlotBehavior_Retrigger + return { + kind = "Retrigger", + } +end + +--- Uses the inherited clip play start timing (from column or matrix). +export type ClipRecordStartTiming_LikeClipPlayStartTiming = { kind: "LikeClipPlayStartTiming" } + +--- Starts recording immediately. +export type ClipRecordStartTiming_Immediately = { kind: "Immediately" } + +--- Starts recording according to the given quantization. +export type ClipRecordStartTiming_Quantized = { kind: "Quantized", numerator: number, denominator: number } +export type ClipRecordStartTiming = + ClipRecordStartTiming_LikeClipPlayStartTiming + | ClipRecordStartTiming_Immediately + | ClipRecordStartTiming_Quantized + +--- A type that represents all possible kinds of ClipRecordStartTiming. +export type ClipRecordStartTimingKind = "LikeClipPlayStartTiming" | "Immediately" | "Quantized" + +--- Helper table to create ClipRecordStartTiming values of different kinds. +module.ClipRecordStartTiming = {} + +--- Creates a ClipRecordStartTiming of kind LikeClipPlayStartTiming. +--- Uses the inherited clip play start timing (from column or matrix). +function module.ClipRecordStartTiming.LikeClipPlayStartTiming(): ClipRecordStartTiming_LikeClipPlayStartTiming + return { + kind = "LikeClipPlayStartTiming", + } +end + +--- Creates a ClipRecordStartTiming of kind Immediately. +--- Starts recording immediately. +function module.ClipRecordStartTiming.Immediately(): ClipRecordStartTiming_Immediately + return { + kind = "Immediately", + } +end + +--- Creates a ClipRecordStartTiming of kind Quantized. +--- Starts recording according to the given quantization. +function module.ClipRecordStartTiming.Quantized(value: EvenQuantization): ClipRecordStartTiming_Quantized + local t: any = table.clone(value) + t.kind = "Quantized" + return t +end + +--- Uses the record start timing. +export type ClipRecordStopTiming_LikeClipRecordStartTiming = { kind: "LikeClipRecordStartTiming" } + +--- Stops recording immediately. +export type ClipRecordStopTiming_Immediately = { kind: "Immediately" } + +--- Stops recording according to the given quantization. +export type ClipRecordStopTiming_Quantized = { kind: "Quantized", numerator: number, denominator: number } +export type ClipRecordStopTiming = + ClipRecordStopTiming_LikeClipRecordStartTiming + | ClipRecordStopTiming_Immediately + | ClipRecordStopTiming_Quantized + +--- A type that represents all possible kinds of ClipRecordStopTiming. +export type ClipRecordStopTimingKind = "LikeClipRecordStartTiming" | "Immediately" | "Quantized" + +--- Helper table to create ClipRecordStopTiming values of different kinds. +module.ClipRecordStopTiming = {} + +--- Creates a ClipRecordStopTiming of kind LikeClipRecordStartTiming. +--- Uses the record start timing. +function module.ClipRecordStopTiming.LikeClipRecordStartTiming(): ClipRecordStopTiming_LikeClipRecordStartTiming + return { + kind = "LikeClipRecordStartTiming", + } +end + +--- Creates a ClipRecordStopTiming of kind Immediately. +--- Stops recording immediately. +function module.ClipRecordStopTiming.Immediately(): ClipRecordStopTiming_Immediately + return { + kind = "Immediately", + } +end + +--- Creates a ClipRecordStopTiming of kind Quantized. +--- Stops recording according to the given quantization. +function module.ClipRecordStopTiming.Quantized(value: EvenQuantization): ClipRecordStopTiming_Quantized + local t: any = table.clone(value) + t.kind = "Quantized" + return t +end + +--- Creates an empty clip and records MIDI material in it. +export type MidiClipRecordMode_Normal = { kind: "Normal" } + +--- Records more material onto an existing clip, leaving existing material in place. +export type MidiClipRecordMode_Overdub = { kind: "Overdub" } + +--- Records more material onto an existing clip, overwriting existing material. +export type MidiClipRecordMode_Replace = { kind: "Replace" } +export type MidiClipRecordMode = MidiClipRecordMode_Normal | MidiClipRecordMode_Overdub | MidiClipRecordMode_Replace + +--- A type that represents all possible kinds of MidiClipRecordMode. +export type MidiClipRecordModeKind = "Normal" | "Overdub" | "Replace" + +--- Helper table to create MidiClipRecordMode values of different kinds. +module.MidiClipRecordMode = {} + +--- Creates a MidiClipRecordMode of kind Normal. +--- Creates an empty clip and records MIDI material in it. +function module.MidiClipRecordMode.Normal(): MidiClipRecordMode_Normal + return { + kind = "Normal", + } +end + +--- Creates a MidiClipRecordMode of kind Overdub. +--- Records more material onto an existing clip, leaving existing material in place. +function module.MidiClipRecordMode.Overdub(): MidiClipRecordMode_Overdub + return { + kind = "Overdub", + } +end + +--- Creates a MidiClipRecordMode of kind Replace. +--- Records more material onto an existing clip, overwriting existing material. +function module.MidiClipRecordMode.Replace(): MidiClipRecordMode_Replace + return { + kind = "Replace", + } +end + +--- Starts playing immediately. +export type ClipPlayStartTiming_Immediately = { kind: "Immediately" } + +--- Starts playing according to the given quantization. +export type ClipPlayStartTiming_Quantized = { kind: "Quantized", numerator: number, denominator: number } +export type ClipPlayStartTiming = ClipPlayStartTiming_Immediately | ClipPlayStartTiming_Quantized + +--- A type that represents all possible kinds of ClipPlayStartTiming. +export type ClipPlayStartTimingKind = "Immediately" | "Quantized" + +--- Helper table to create ClipPlayStartTiming values of different kinds. +module.ClipPlayStartTiming = {} + +--- Creates a ClipPlayStartTiming of kind Immediately. +--- Starts playing immediately. +function module.ClipPlayStartTiming.Immediately(): ClipPlayStartTiming_Immediately + return { + kind = "Immediately", + } +end + +--- Creates a ClipPlayStartTiming of kind Quantized. +--- Starts playing according to the given quantization. +function module.ClipPlayStartTiming.Quantized(value: EvenQuantization): ClipPlayStartTiming_Quantized + local t: any = table.clone(value) + t.kind = "Quantized" + return t +end + +--- Uses the play start timing. +export type ClipPlayStopTiming_LikeClipStartTiming = { kind: "LikeClipStartTiming" } + +--- Stops playing immediately. +export type ClipPlayStopTiming_Immediately = { kind: "Immediately" } + +--- Stops playing according to the given quantization. +export type ClipPlayStopTiming_Quantized = { kind: "Quantized", numerator: number, denominator: number } + +--- Keeps playing until the end of the clip. +export type ClipPlayStopTiming_UntilEndOfClip = { kind: "UntilEndOfClip" } +export type ClipPlayStopTiming = + ClipPlayStopTiming_LikeClipStartTiming + | ClipPlayStopTiming_Immediately + | ClipPlayStopTiming_Quantized + | ClipPlayStopTiming_UntilEndOfClip + +--- A type that represents all possible kinds of ClipPlayStopTiming. +export type ClipPlayStopTimingKind = "LikeClipStartTiming" | "Immediately" | "Quantized" | "UntilEndOfClip" + +--- Helper table to create ClipPlayStopTiming values of different kinds. +module.ClipPlayStopTiming = {} + +--- Creates a ClipPlayStopTiming of kind LikeClipStartTiming. +--- Uses the play start timing. +function module.ClipPlayStopTiming.LikeClipStartTiming(): ClipPlayStopTiming_LikeClipStartTiming + return { + kind = "LikeClipStartTiming", + } +end + +--- Creates a ClipPlayStopTiming of kind Immediately. +--- Stops playing immediately. +function module.ClipPlayStopTiming.Immediately(): ClipPlayStopTiming_Immediately + return { + kind = "Immediately", + } +end + +--- Creates a ClipPlayStopTiming of kind Quantized. +--- Stops playing according to the given quantization. +function module.ClipPlayStopTiming.Quantized(value: EvenQuantization): ClipPlayStopTiming_Quantized + local t: any = table.clone(value) + t.kind = "Quantized" + return t +end + +--- Creates a ClipPlayStopTiming of kind UntilEndOfClip. +--- Keeps playing until the end of the clip. +function module.ClipPlayStopTiming.UntilEndOfClip(): ClipPlayStopTiming_UntilEndOfClip + return { + kind = "UntilEndOfClip", + } +end + +export type ResolvedClipPlayStopTiming = "Immediately" | "Quantized" | "UntilEndOfClip" + +--- Doesn't apply any override. +export type ClipPlayStartTimingOverrideAfterRecording_Inherit = { kind: "Inherit" } + +--- Overrides the setting with the given value. +export type ClipPlayStartTimingOverrideAfterRecording_Override = { kind: "Override", value: ClipPlayStartTiming } + +--- Derives the setting from the record timing. +--- +--- If the record timing is set to the global clip timing, that means it will not apply any +--- override. If it's set to something specific, it will apply the appropriate override. +export type ClipPlayStartTimingOverrideAfterRecording_DeriveFromRecordTiming = { kind: "DeriveFromRecordTiming" } +export type ClipPlayStartTimingOverrideAfterRecording = + ClipPlayStartTimingOverrideAfterRecording_Inherit + | ClipPlayStartTimingOverrideAfterRecording_Override + | ClipPlayStartTimingOverrideAfterRecording_DeriveFromRecordTiming + +--- A type that represents all possible kinds of ClipPlayStartTimingOverrideAfterRecording. +export type ClipPlayStartTimingOverrideAfterRecordingKind = "Inherit" | "Override" | "DeriveFromRecordTiming" + +--- Helper table to create ClipPlayStartTimingOverrideAfterRecording values of different kinds. +module.ClipPlayStartTimingOverrideAfterRecording = {} + +--- Creates a ClipPlayStartTimingOverrideAfterRecording of kind Inherit. +--- Doesn't apply any override. +function module.ClipPlayStartTimingOverrideAfterRecording.Inherit(): ClipPlayStartTimingOverrideAfterRecording_Inherit + return { + kind = "Inherit", + } +end + +--- Creates a ClipPlayStartTimingOverrideAfterRecording of kind Override. +--- Overrides the setting with the given value. +function module.ClipPlayStartTimingOverrideAfterRecording.Override( + value: ClipPlayStartTimingOverride +): ClipPlayStartTimingOverrideAfterRecording_Override + local t: any = table.clone(value) + t.kind = "Override" + return t +end + +--- Creates a ClipPlayStartTimingOverrideAfterRecording of kind DeriveFromRecordTiming. +--- Derives the setting from the record timing. +--- +--- If the record timing is set to the global clip timing, that means it will not apply any +--- override. If it's set to something specific, it will apply the appropriate override. +function module.ClipPlayStartTimingOverrideAfterRecording.DeriveFromRecordTiming( +): ClipPlayStartTimingOverrideAfterRecording_DeriveFromRecordTiming + return { + kind = "DeriveFromRecordTiming", + } +end + +--- Doesn't apply any override. +export type ClipPlayStopTimingOverrideAfterRecording_Inherit = { kind: "Inherit" } + +--- Overrides the setting with the given value. +export type ClipPlayStopTimingOverrideAfterRecording_Override = { kind: "Override", value: ClipPlayStopTiming } + +--- Derives the setting from the record timing. +--- +--- If the record timing is set to the global clip timing, that means it will not apply any +--- override. If it's set to something specific, it will apply the appropriate override. +export type ClipPlayStopTimingOverrideAfterRecording_DeriveFromRecordTiming = { kind: "DeriveFromRecordTiming" } +export type ClipPlayStopTimingOverrideAfterRecording = + ClipPlayStopTimingOverrideAfterRecording_Inherit + | ClipPlayStopTimingOverrideAfterRecording_Override + | ClipPlayStopTimingOverrideAfterRecording_DeriveFromRecordTiming + +--- A type that represents all possible kinds of ClipPlayStopTimingOverrideAfterRecording. +export type ClipPlayStopTimingOverrideAfterRecordingKind = "Inherit" | "Override" | "DeriveFromRecordTiming" + +--- Helper table to create ClipPlayStopTimingOverrideAfterRecording values of different kinds. +module.ClipPlayStopTimingOverrideAfterRecording = {} + +--- Creates a ClipPlayStopTimingOverrideAfterRecording of kind Inherit. +--- Doesn't apply any override. +function module.ClipPlayStopTimingOverrideAfterRecording.Inherit(): ClipPlayStopTimingOverrideAfterRecording_Inherit + return { + kind = "Inherit", + } +end + +--- Creates a ClipPlayStopTimingOverrideAfterRecording of kind Override. +--- Overrides the setting with the given value. +function module.ClipPlayStopTimingOverrideAfterRecording.Override( + value: ClipPlayStopTimingOverride +): ClipPlayStopTimingOverrideAfterRecording_Override + local t: any = table.clone(value) + t.kind = "Override" + return t +end + +--- Creates a ClipPlayStopTimingOverrideAfterRecording of kind DeriveFromRecordTiming. +--- Derives the setting from the record timing. +--- +--- If the record timing is set to the global clip timing, that means it will not apply any +--- override. If it's set to something specific, it will apply the appropriate override. +function module.ClipPlayStopTimingOverrideAfterRecording.DeriveFromRecordTiming( +): ClipPlayStopTimingOverrideAfterRecording_DeriveFromRecordTiming + return { + kind = "DeriveFromRecordTiming", + } +end + +export type ClipPlayStartTimingOverride = { + value: ClipPlayStartTiming, +} +--- Creates a ClipPlayStartTimingOverride value. +function module.ClipPlayStartTimingOverride(value: ClipPlayStartTimingOverride): ClipPlayStartTimingOverride + return value +end + +export type ClipPlayStopTimingOverride = { + value: ClipPlayStopTiming, +} +--- Creates a ClipPlayStopTimingOverride value. +function module.ClipPlayStopTimingOverride(value: ClipPlayStopTimingOverride): ClipPlayStopTimingOverride + return value +end + +--- An even quantization. +--- +--- "Even" in the sense that it's not swing or dotted. +export type EvenQuantization = { + numerator: number, + denominator: number, +} +--- Creates a EvenQuantization value. +--- An even quantization. +--- +--- "Even" in the sense that it's not swing or dotted. +function module.EvenQuantization(value: EvenQuantization): EvenQuantization + return value +end + +export type ColumnId = string + +export type RowId = string + +export type SlotId = string + +export type ClipId = string + +export type MatrixSequenceId = string + +export type Column = { + id: ColumnId?, + name: string?, + clip_play_settings: ColumnClipPlaySettings?, + clip_record_settings: ColumnClipRecordSettings?, + slots: { Slot }?, +} +--- Creates a Column value. +function module.Column(value: Column): Column + return value +end + +export type ColumnSettings = { + name: string?, + clip_play_settings: ColumnClipPlaySettings, + clip_record_settings: ColumnClipRecordSettings, +} +--- Creates a ColumnSettings value. +function module.ColumnSettings(value: ColumnSettings): ColumnSettings + return value +end + +export type ColumnClipPlaySettings = { + follows_scene: boolean?, + exclusive: boolean?, + track: TrackId?, + start_timing: ClipPlayStartTiming?, + stop_timing: ClipPlayStopTiming?, + trigger_behavior: TriggerSlotBehavior?, + velocity_sensitivity: number?, + audio_settings: ColumnClipPlayAudioSettings?, +} +--- Creates a ColumnClipPlaySettings value. +function module.ColumnClipPlaySettings(value: ColumnClipPlaySettings): ColumnClipPlaySettings + return value +end + +export type ColumnClipRecordSettings = { + origin: RecordOrigin?, + track: TrackId?, +} +--- Creates a ColumnClipRecordSettings value. +function module.ColumnClipRecordSettings(value: ColumnClipRecordSettings): ColumnClipRecordSettings + return value +end + +export type ColumnClipPlayAudioSettings = { + resample_mode: VirtualResampleMode?, + time_stretch_mode: AudioTimeStretchMode?, + cache_behavior: AudioCacheBehavior?, +} +--- Creates a ColumnClipPlayAudioSettings value. +function module.ColumnClipPlayAudioSettings(value: ColumnClipPlayAudioSettings): ColumnClipPlayAudioSettings + return value +end + +--- A row represents a complete row in a matrix. +--- +--- A scene is a very related concept and sometimes used interchangeably with row because there's +--- a one-to-one relationship between a row and a scene. +--- +--- The difference between row and scene is of conceptual nature: A scene represents a part of a +--- song that's played exclusively whereas a row is just a row in the Playtime matrix. This distinction +--- results in some practical differences: +--- +--- - A column can be configured to not follow scenes. The clips in that column are of +--- course still structured in rows, but they are not part of the scenes anymore. +--- - In practice, this means that whenever you launch the scene, the clips in that independent +--- column are not launched. Or when you clear the scene, the slots in that column are not +--- cleared. +--- - Whenever you read "Scene", it will only affect the columns that are configured to follow +--- scenes. Whenever you read "Row", it will affect the complete matrix row, no matter the +--- column type. +export type Row = { + id: RowId?, + name: string?, + tempo: number?, + time_signature: TimeSignature?, +} +--- Creates a Row value. +--- A row represents a complete row in a matrix. +--- +--- A scene is a very related concept and sometimes used interchangeably with row because there's +--- a one-to-one relationship between a row and a scene. +--- +--- The difference between row and scene is of conceptual nature: A scene represents a part of a +--- song that's played exclusively whereas a row is just a row in the Playtime matrix. This distinction +--- results in some practical differences: +--- +--- - A column can be configured to not follow scenes. The clips in that column are of +--- course still structured in rows, but they are not part of the scenes anymore. +--- - In practice, this means that whenever you launch the scene, the clips in that independent +--- column are not launched. Or when you clear the scene, the slots in that column are not +--- cleared. +--- - Whenever you read "Scene", it will only affect the columns that are configured to follow +--- scenes. Whenever you read "Row", it will affect the complete matrix row, no matter the +--- column type. +function module.Row(value: Row): Row + return value +end + +--- Doesn't just stretch/squeeze the material but also changes the pitch. +--- +--- Comparatively fast. +export type AudioTimeStretchMode_VariSpeed = { kind: "VariSpeed" } + +--- Applies a real time-stretch algorithm to the material which keeps the pitch. +--- +--- Comparatively slow. +export type AudioTimeStretchMode_KeepingPitch = { kind: "KeepingPitch", mode: VirtualTimeStretchMode } +export type AudioTimeStretchMode = AudioTimeStretchMode_VariSpeed | AudioTimeStretchMode_KeepingPitch + +--- A type that represents all possible kinds of AudioTimeStretchMode. +export type AudioTimeStretchModeKind = "VariSpeed" | "KeepingPitch" + +--- Helper table to create AudioTimeStretchMode values of different kinds. +module.AudioTimeStretchMode = {} + +--- Creates a AudioTimeStretchMode of kind VariSpeed. +--- Doesn't just stretch/squeeze the material but also changes the pitch. +--- +--- Comparatively fast. +function module.AudioTimeStretchMode.VariSpeed(): AudioTimeStretchMode_VariSpeed + return { + kind = "VariSpeed", + } +end + +--- Creates a AudioTimeStretchMode of kind KeepingPitch. +--- Applies a real time-stretch algorithm to the material which keeps the pitch. +--- +--- Comparatively slow. +function module.AudioTimeStretchMode.KeepingPitch(value: TimeStretchMode): AudioTimeStretchMode_KeepingPitch + local t: any = table.clone(value) + t.kind = "KeepingPitch" + return t +end + +--- Uses the resample mode set as default for this REAPER project. +export type VirtualResampleMode_ProjectDefault = { kind: "ProjectDefault" } + +--- Uses a specific resample mode. +export type VirtualResampleMode_ReaperMode = { kind: "ReaperMode", mode: number } +export type VirtualResampleMode = VirtualResampleMode_ProjectDefault | VirtualResampleMode_ReaperMode + +--- A type that represents all possible kinds of VirtualResampleMode. +export type VirtualResampleModeKind = "ProjectDefault" | "ReaperMode" + +--- Helper table to create VirtualResampleMode values of different kinds. +module.VirtualResampleMode = {} + +--- Creates a VirtualResampleMode of kind ProjectDefault. +--- Uses the resample mode set as default for this REAPER project. +function module.VirtualResampleMode.ProjectDefault(): VirtualResampleMode_ProjectDefault + return { + kind = "ProjectDefault", + } +end + +--- Creates a VirtualResampleMode of kind ReaperMode. +--- Uses a specific resample mode. +function module.VirtualResampleMode.ReaperMode(value: ReaperResampleMode): VirtualResampleMode_ReaperMode + local t: any = table.clone(value) + t.kind = "ReaperMode" + return t +end + +export type ReaperResampleMode = { + mode: number, +} +--- Creates a ReaperResampleMode value. +function module.ReaperResampleMode(value: ReaperResampleMode): ReaperResampleMode + return value +end + +export type TimeStretchMode = { + mode: VirtualTimeStretchMode, +} +--- Creates a TimeStretchMode value. +function module.TimeStretchMode(value: TimeStretchMode): TimeStretchMode + return value +end + +--- Uses the pitch shift mode set as default for this REAPER project. +export type VirtualTimeStretchMode_ProjectDefault = { kind: "ProjectDefault" } + +--- Uses a specific REAPER pitch shift mode. +export type VirtualTimeStretchMode_ReaperMode = { kind: "ReaperMode", mode: number, sub_mode: number } +export type VirtualTimeStretchMode = VirtualTimeStretchMode_ProjectDefault | VirtualTimeStretchMode_ReaperMode + +--- A type that represents all possible kinds of VirtualTimeStretchMode. +export type VirtualTimeStretchModeKind = "ProjectDefault" | "ReaperMode" + +--- Helper table to create VirtualTimeStretchMode values of different kinds. +module.VirtualTimeStretchMode = {} + +--- Creates a VirtualTimeStretchMode of kind ProjectDefault. +--- Uses the pitch shift mode set as default for this REAPER project. +function module.VirtualTimeStretchMode.ProjectDefault(): VirtualTimeStretchMode_ProjectDefault + return { + kind = "ProjectDefault", + } +end + +--- Creates a VirtualTimeStretchMode of kind ReaperMode. +--- Uses a specific REAPER pitch shift mode. +function module.VirtualTimeStretchMode.ReaperMode(value: ReaperPitchShiftMode): VirtualTimeStretchMode_ReaperMode + local t: any = table.clone(value) + t.kind = "ReaperMode" + return t +end + +export type ReaperPitchShiftMode = { + mode: number, + sub_mode: number, +} +--- Creates a ReaperPitchShiftMode value. +function module.ReaperPitchShiftMode(value: ReaperPitchShiftMode): ReaperPitchShiftMode + return value +end + +--- Records using the hardware input set for the track (MIDI or stereo). +export type RecordOrigin_TrackInput = { kind: "TrackInput" } + +--- Captures audio from the output of the track. +export type RecordOrigin_TrackAudioOutput = { kind: "TrackAudioOutput" } + +--- Records audio flowing into the FX input. +export type RecordOrigin_FxAudioInput = { kind: "FxAudioInput", first_channel_index: number, channel_count: number } +export type RecordOrigin = RecordOrigin_TrackInput | RecordOrigin_TrackAudioOutput | RecordOrigin_FxAudioInput + +--- A type that represents all possible kinds of RecordOrigin. +export type RecordOriginKind = "TrackInput" | "TrackAudioOutput" | "FxAudioInput" + +--- Helper table to create RecordOrigin values of different kinds. +module.RecordOrigin = {} + +--- Creates a RecordOrigin of kind TrackInput. +--- Records using the hardware input set for the track (MIDI or stereo). +function module.RecordOrigin.TrackInput(): RecordOrigin_TrackInput + return { + kind = "TrackInput", + } +end + +--- Creates a RecordOrigin of kind TrackAudioOutput. +--- Captures audio from the output of the track. +function module.RecordOrigin.TrackAudioOutput(): RecordOrigin_TrackAudioOutput + return { + kind = "TrackAudioOutput", + } +end + +--- Creates a RecordOrigin of kind FxAudioInput. +--- Records audio flowing into the FX input. +function module.RecordOrigin.FxAudioInput(value: ChannelRange): RecordOrigin_FxAudioInput + local t: any = table.clone(value) + t.kind = "FxAudioInput" + return t +end + +export type SourceOrigin = "Normal" | "Frozen" + +export type ChannelRange = { + first_channel_index: number, + channel_count: number, +} +--- Creates a ChannelRange value. +function module.ChannelRange(value: ChannelRange): ChannelRange + return value +end + +export type Slot = { + id: SlotId?, + row: number, + clip_old: Clip?, + clips: { Clip }?, + ignited: boolean?, +} +--- Creates a Slot value. +function module.Slot(value: Slot): Slot + return value +end + +export type Clip = { + id: ClipId?, + name: string?, + source: Source?, + frozen_source: Source?, + active_source: SourceOrigin?, + time_base: ClipTimeBase?, + start_timing: ClipPlayStartTiming?, + stop_timing: ClipPlayStopTiming?, + velocity_sensitivity: number?, + looped: boolean?, + volume: number?, + color: ClipColor?, + dynamic_section: Section?, + fixed_section: Section?, + pitch_shift: Semitones?, + audio_settings: ClipAudioSettings?, + midi_settings: ClipMidiSettings?, +} +--- Creates a Clip value. +function module.Clip(value: Clip): Clip + return value +end + +export type ClipAudioSettings = { + apply_source_fades: boolean?, + time_stretch_mode: AudioTimeStretchMode?, + resample_mode: VirtualResampleMode?, + cache_behavior: AudioCacheBehavior?, + original_tempo: number?, +} +--- Creates a ClipAudioSettings value. +function module.ClipAudioSettings(value: ClipAudioSettings): ClipAudioSettings + return value +end + +export type ClipMidiSettings = { + destination_channel: number?, + reset_settings: ClipMidiResetSettings?, +} +--- Creates a ClipMidiSettings value. +function module.ClipMidiSettings(value: ClipMidiSettings): ClipMidiSettings + return value +end + +export type ClipMidiResetSettings = { + interaction_reset_settings: MidiResetMessageRange, + section_reset_settings: MidiResetMessageRange, + source_reset_settings: MidiResetMessageRange, +} +--- Creates a ClipMidiResetSettings value. +function module.ClipMidiResetSettings(value: ClipMidiResetSettings): ClipMidiResetSettings + return value +end + +export type MidiResetMessageRange = { + left: MidiResetMessages, + right: MidiResetMessages, +} +--- Creates a MidiResetMessageRange value. +function module.MidiResetMessageRange(value: MidiResetMessageRange): MidiResetMessageRange + return value +end + +export type MidiResetMessages = { + on_notes_off: boolean?, + all_notes_off: boolean, + all_sound_off: boolean, + reset_all_controllers: boolean, + on_damper_pedal_off: boolean, +} +--- Creates a MidiResetMessages value. +function module.MidiResetMessages(value: MidiResetMessages): MidiResetMessages + return value +end + +export type Section = { + start_pos: number?, + length: number?, +} +--- Creates a Section value. +function module.Section(value: Section): Section + return value +end + +--- Loads directly from the disk. +--- +--- Might still pre-buffer some blocks but definitely won't put the complete audio data into +--- memory. +export type AudioCacheBehavior_DirectFromDisk = { kind: "DirectFromDisk" } + +--- Loads the complete audio data into memory. +export type AudioCacheBehavior_CacheInMemory = { kind: "CacheInMemory" } +export type AudioCacheBehavior = AudioCacheBehavior_DirectFromDisk | AudioCacheBehavior_CacheInMemory + +--- A type that represents all possible kinds of AudioCacheBehavior. +export type AudioCacheBehaviorKind = "DirectFromDisk" | "CacheInMemory" + +--- Helper table to create AudioCacheBehavior values of different kinds. +module.AudioCacheBehavior = {} + +--- Creates a AudioCacheBehavior of kind DirectFromDisk. +--- Loads directly from the disk. +--- +--- Might still pre-buffer some blocks but definitely won't put the complete audio data into +--- memory. +function module.AudioCacheBehavior.DirectFromDisk(): AudioCacheBehavior_DirectFromDisk + return { + kind = "DirectFromDisk", + } +end + +--- Creates a AudioCacheBehavior of kind CacheInMemory. +--- Loads the complete audio data into memory. +function module.AudioCacheBehavior.CacheInMemory(): AudioCacheBehavior_CacheInMemory + return { + kind = "CacheInMemory", + } +end + +--- Inherits the color of the column's play track. +export type ClipColor_PlayTrackColor = { kind: "PlayTrackColor" } + +--- Assigns a very specific custom color. +export type ClipColor_CustomColor = { kind: "CustomColor", value: RgbColor } + +--- Uses a certain color from a palette. +export type ClipColor_PaletteColor = { kind: "PaletteColor", index: number } +export type ClipColor = ClipColor_PlayTrackColor | ClipColor_CustomColor | ClipColor_PaletteColor + +--- A type that represents all possible kinds of ClipColor. +export type ClipColorKind = "PlayTrackColor" | "CustomColor" | "PaletteColor" + +--- Helper table to create ClipColor values of different kinds. +module.ClipColor = {} + +--- Creates a ClipColor of kind PlayTrackColor. +--- Inherits the color of the column's play track. +function module.ClipColor.PlayTrackColor(): ClipColor_PlayTrackColor + return { + kind = "PlayTrackColor", + } +end + +--- Creates a ClipColor of kind CustomColor. +--- Assigns a very specific custom color. +function module.ClipColor.CustomColor(value: CustomClipColor): ClipColor_CustomColor + local t: any = table.clone(value) + t.kind = "CustomColor" + return t +end + +--- Creates a ClipColor of kind PaletteColor. +--- Uses a certain color from a palette. +function module.ClipColor.PaletteColor(value: PaletteClipColor): ClipColor_PaletteColor + local t: any = table.clone(value) + t.kind = "PaletteColor" + return t +end + +export type CustomClipColor = { + value: RgbColor, +} +--- Creates a CustomClipColor value. +function module.CustomClipColor(value: CustomClipColor): CustomClipColor + return value +end + +export type PaletteClipColor = { + index: number, +} +--- Creates a PaletteClipColor value. +function module.PaletteClipColor(value: PaletteClipColor): PaletteClipColor + return value +end + +--- Takes content from a media file on the file system (audio). +export type Source_File = { kind: "File", path: string } + +--- Embedded MIDI data. +export type Source_MidiChunk = { kind: "MidiChunk", chunk: string } +export type Source = Source_File | Source_MidiChunk + +--- A type that represents all possible kinds of Source. +export type SourceKind = "File" | "MidiChunk" + +--- Helper table to create Source values of different kinds. +module.Source = {} + +--- Creates a Source of kind File. +--- Takes content from a media file on the file system (audio). +function module.Source.File(value: FileSource): Source_File + local t: any = table.clone(value) + t.kind = "File" + return t +end + +--- Creates a Source of kind MidiChunk. +--- Embedded MIDI data. +function module.Source.MidiChunk(value: MidiChunkSource): Source_MidiChunk + local t: any = table.clone(value) + t.kind = "MidiChunk" + return t +end + +export type FileSource = { + path: string, +} +--- Creates a FileSource value. +function module.FileSource(value: FileSource): FileSource + return value +end + +export type MidiChunkSource = { + chunk: string, +} +--- Creates a MidiChunkSource value. +function module.MidiChunkSource(value: MidiChunkSource): MidiChunkSource + return value +end + +--- Material which doesn't need to be adjusted to the current tempo. +export type ClipTimeBase_Time = { kind: "Time" } + +--- Material which needs to be adjusted to the current tempo. +export type ClipTimeBase_Beat = { kind: "Beat", audio_tempo: number?, time_signature: TimeSignature, downbeat: number } +--- Decides if the clip will be adjusted to the current tempo. +export type ClipTimeBase = ClipTimeBase_Time | ClipTimeBase_Beat + +--- A type that represents all possible kinds of ClipTimeBase. +export type ClipTimeBaseKind = "Time" | "Beat" + +--- Helper table to create ClipTimeBase values of different kinds. +--- Decides if the clip will be adjusted to the current tempo. +module.ClipTimeBase = {} + +--- Creates a ClipTimeBase of kind Time. +--- Material which doesn't need to be adjusted to the current tempo. +function module.ClipTimeBase.Time(): ClipTimeBase_Time + return { + kind = "Time", + } +end + +--- Creates a ClipTimeBase of kind Beat. +--- Material which needs to be adjusted to the current tempo. +function module.ClipTimeBase.Beat(value: BeatTimeBase): ClipTimeBase_Beat + local t: any = table.clone(value) + t.kind = "Beat" + return t +end + +export type BeatTimeBase = { + audio_tempo: number?, + time_signature: TimeSignature, + downbeat: number, +} +--- Creates a BeatTimeBase value. +function module.BeatTimeBase(value: BeatTimeBase): BeatTimeBase + return value +end + +export type ColorPalette = { + entries: { ColorPaletteEntry }, +} +--- Creates a ColorPalette value. +function module.ColorPalette(value: ColorPalette): ColorPalette + return value +end + +export type ColorPaletteEntry = { + color: RgbColor, +} +--- Creates a ColorPaletteEntry value. +function module.ColorPaletteEntry(value: ColorPaletteEntry): ColorPaletteEntry + return value +end + +export type TimeSignature = { + numerator: number, + denominator: number, +} +--- Creates a TimeSignature value. +function module.TimeSignature(value: TimeSignature): TimeSignature + return value +end + +export type TrackId = string + +export type RgbColor = { number } + +export type ColumnAddress = { + index: number, +} +--- Creates a ColumnAddress value. +function module.ColumnAddress(value: ColumnAddress): ColumnAddress + return value +end + +export type RowAddress = { + index: number, +} +--- Creates a RowAddress value. +function module.RowAddress(value: RowAddress): RowAddress + return value +end + +export type SlotAddress = { + column_index: number, + row_index: number, +} +--- Creates a SlotAddress value. +function module.SlotAddress(value: SlotAddress): SlotAddress + return value +end + +return module diff --git a/plugin-reaper-realearn/resources/api/luau/preset_runtime.luau b/plugin-reaper-realearn/resources/api/luau/preset_runtime.luau new file mode 100644 index 0000000..b3e18ba --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/preset_runtime.luau @@ -0,0 +1,18 @@ +--!strict + +--- Contains types and function stubs for building presets. The actual function implementations are provided +--- by the runtime. +local module = {} + +--- Returns the content of the given file as string. +--- +--- The given path is always relative to the module root. +--- +--- This function is very useful when you build a preset in which the compartment contains commona Lua code. +--- Instead of writing the common Lua code as a Lua string, you can include it from a dedicated file. That way, +--- you can benefit from syntax highlighting and typechecking when building the common Lua code. +function module.include_str(path: string): string? + return nil +end + +return module \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/api/luau/realearn.luau b/plugin-reaper-realearn/resources/api/luau/realearn.luau new file mode 100644 index 0000000..7131d82 --- /dev/null +++ b/plugin-reaper-realearn/resources/api/luau/realearn.luau @@ -0,0 +1,4280 @@ +--!strict + +--- Attention: This file is generated from Rust code! Don't modify it directly! + +local playtime = require("playtime") + +--- Contains types and helper functions for building ReaLearn presets +local module = {} + +--- Complete content of a ReaLearn compartment, including mappings, groups, parameters etc. +export type Compartment = { + default_group: Group?, + parameters: { Parameter }?, + groups: { Group }?, + mappings: { Mapping }?, + common_lua: string?, + custom_data: { [string]: any }?, + notes: string?, + unknown_props: BTreeMap?, +} +--- Creates a Compartment value. +--- Complete content of a ReaLearn compartment, including mappings, groups, parameters etc. +function module.Compartment(value: Compartment): Compartment + return value +end + +export type Glue = { + absolute_mode: AbsoluteMode?, + source_interval: Interval?, + target_interval: Interval?, + reverse: boolean?, + out_of_range_behavior: OutOfRangeBehavior?, + target_value_sequence: string?, + round_target_value: boolean?, + wrap: boolean?, + jump_interval: Interval?, + takeover_mode: TakeoverMode?, + control_transformation: string?, + step_size_interval: Interval?, + step_factor_interval: Interval?, + button_filter: ButtonFilter?, + encoder_filter: EncoderFilter?, + relative_mode: RelativeMode?, + interaction: Interaction?, + fire_mode: FireMode?, + feedback: Feedback?, + feedback_value_table: FeedbackValueTable?, +} +--- Creates a Glue value. +function module.Glue(value: Glue): Glue + return value +end + +export type FeedbackValueTable_FromTextToDiscrete = { kind: "FromTextToDiscrete", value: { [string]: number } } + +export type FeedbackValueTable_FromTextToContinuous = { kind: "FromTextToContinuous", value: { [string]: number } } +export type FeedbackValueTable = FeedbackValueTable_FromTextToDiscrete | FeedbackValueTable_FromTextToContinuous + +--- A type that represents all possible kinds of FeedbackValueTable. +export type FeedbackValueTableKind = "FromTextToDiscrete" | "FromTextToContinuous" + +--- Helper table to create FeedbackValueTable values of different kinds. +module.FeedbackValueTable = {} + +--- Creates a FeedbackValueTable of kind FromTextToDiscrete. +function module.FeedbackValueTable.FromTextToDiscrete( + value: DiscreteFeedbackValueTableContent +): FeedbackValueTable_FromTextToDiscrete + local t: any = table.clone(value) + t.kind = "FromTextToDiscrete" + return t +end + +--- Creates a FeedbackValueTable of kind FromTextToContinuous. +function module.FeedbackValueTable.FromTextToContinuous( + value: ContinuousFeedbackValueTableContent +): FeedbackValueTable_FromTextToContinuous + local t: any = table.clone(value) + t.kind = "FromTextToContinuous" + return t +end + +export type DiscreteFeedbackValueTableContent = { + value: { [string]: number }, +} +--- Creates a DiscreteFeedbackValueTableContent value. +function module.DiscreteFeedbackValueTableContent( + value: DiscreteFeedbackValueTableContent +): DiscreteFeedbackValueTableContent + return value +end + +export type ContinuousFeedbackValueTableContent = { + value: { [string]: number }, +} +--- Creates a ContinuousFeedbackValueTableContent value. +function module.ContinuousFeedbackValueTableContent( + value: ContinuousFeedbackValueTableContent +): ContinuousFeedbackValueTableContent + return value +end + +export type AbsoluteMode = "Normal" | "IncrementalButton" | "ToggleButton" | "MakeRelative" | "PerformanceControl" + +export type RelativeMode = "Normal" | "MakeAbsolute" + +export type FireMode_Normal = { kind: "Normal", press_duration_interval: Interval? } + +export type FireMode_AfterTimeout = { kind: "AfterTimeout", timeout: number? } + +export type FireMode_AfterTimeoutKeepFiring = { kind: "AfterTimeoutKeepFiring", timeout: number?, rate: number? } + +export type FireMode_OnSinglePress = { kind: "OnSinglePress", max_duration: number? } + +export type FireMode_OnDoublePress = { kind: "OnDoublePress" } +export type FireMode = + FireMode_Normal + | FireMode_AfterTimeout + | FireMode_AfterTimeoutKeepFiring + | FireMode_OnSinglePress + | FireMode_OnDoublePress + +--- A type that represents all possible kinds of FireMode. +export type FireModeKind = "Normal" | "AfterTimeout" | "AfterTimeoutKeepFiring" | "OnSinglePress" | "OnDoublePress" + +--- Helper table to create FireMode values of different kinds. +module.FireMode = {} + +--- Creates a FireMode of kind Normal. +function module.FireMode.Normal(value: NormalFireMode): FireMode_Normal + local t: any = table.clone(value) + t.kind = "Normal" + return t +end + +--- Creates a FireMode of kind AfterTimeout. +function module.FireMode.AfterTimeout(value: AfterTimeoutFireMode): FireMode_AfterTimeout + local t: any = table.clone(value) + t.kind = "AfterTimeout" + return t +end + +--- Creates a FireMode of kind AfterTimeoutKeepFiring. +function module.FireMode.AfterTimeoutKeepFiring(value: AfterTimeoutKeepFiringFireMode): FireMode_AfterTimeoutKeepFiring + local t: any = table.clone(value) + t.kind = "AfterTimeoutKeepFiring" + return t +end + +--- Creates a FireMode of kind OnSinglePress. +function module.FireMode.OnSinglePress(value: OnSinglePressFireMode): FireMode_OnSinglePress + local t: any = table.clone(value) + t.kind = "OnSinglePress" + return t +end + +--- Creates a FireMode of kind OnDoublePress. +function module.FireMode.OnDoublePress(): FireMode_OnDoublePress + return { + kind = "OnDoublePress", + } +end + +export type NormalFireMode = { + press_duration_interval: Interval?, +} +--- Creates a NormalFireMode value. +function module.NormalFireMode(value: NormalFireMode): NormalFireMode + return value +end + +export type AfterTimeoutFireMode = { + timeout: number?, +} +--- Creates a AfterTimeoutFireMode value. +function module.AfterTimeoutFireMode(value: AfterTimeoutFireMode): AfterTimeoutFireMode + return value +end + +export type AfterTimeoutKeepFiringFireMode = { + timeout: number?, + rate: number?, +} +--- Creates a AfterTimeoutKeepFiringFireMode value. +function module.AfterTimeoutKeepFiringFireMode(value: AfterTimeoutKeepFiringFireMode): AfterTimeoutKeepFiringFireMode + return value +end + +export type OnSinglePressFireMode = { + max_duration: number?, +} +--- Creates a OnSinglePressFireMode value. +function module.OnSinglePressFireMode(value: OnSinglePressFireMode): OnSinglePressFireMode + return value +end + +export type VirtualColor_Rgb = RgbColor + +export type VirtualColor_Prop = PropColor +export type VirtualColor = VirtualColor_Rgb | VirtualColor_Prop + +--- Creates a VirtualColor value. +function module.VirtualColor(value: VirtualColor): VirtualColor + return value +end + +export type RgbColor = { number } + +export type PropColor = { + prop: string, +} +--- Creates a PropColor value. +function module.PropColor(value: PropColor): PropColor + return value +end + +export type OutOfRangeBehavior = "MinOrMax" | "Min" | "Ignore" + +export type TakeoverMode = "Off" | "PickUpTolerant" | "PickUp" | "LongTimeNoSee" | "Parallel" | "CatchUp" + +export type ButtonFilter = "PressOnly" | "ReleaseOnly" + +export type EncoderFilter = "IncrementOnly" | "DecrementOnly" + +export type Interaction = + "SameControl" + | "SameTargetValue" + | "InverseControl" + | "InverseTargetValue" + | "InverseTargetValueOnOnly" + | "InverseTargetValueOffOnly" + +export type FeedbackCommons = { + color: VirtualColor?, + background_color: VirtualColor?, +} +--- Creates a FeedbackCommons value. +function module.FeedbackCommons(value: FeedbackCommons): FeedbackCommons + return value +end + +export type Feedback_Numeric = { + kind: "Numeric", + color: VirtualColor?, + background_color: VirtualColor?, + transformation: string?, +} + +export type Feedback_Text = { + kind: "Text", + color: VirtualColor?, + background_color: VirtualColor?, + text_expression: string?, +} + +export type Feedback_Dynamic = { kind: "Dynamic", color: VirtualColor?, background_color: VirtualColor?, script: string? } +export type Feedback = Feedback_Numeric | Feedback_Text | Feedback_Dynamic + +--- A type that represents all possible kinds of Feedback. +export type FeedbackKind = "Numeric" | "Text" | "Dynamic" + +--- Helper table to create Feedback values of different kinds. +module.Feedback = {} + +--- Creates a Feedback of kind Numeric. +function module.Feedback.Numeric(value: NumericFeedback): Feedback_Numeric + local t: any = table.clone(value) + t.kind = "Numeric" + return t +end + +--- Creates a Feedback of kind Text. +function module.Feedback.Text(value: TextFeedback): Feedback_Text + local t: any = table.clone(value) + t.kind = "Text" + return t +end + +--- Creates a Feedback of kind Dynamic. +function module.Feedback.Dynamic(value: DynamicFeedback): Feedback_Dynamic + local t: any = table.clone(value) + t.kind = "Dynamic" + return t +end + +export type NumericFeedback = { + color: VirtualColor?, + background_color: VirtualColor?, + transformation: string?, +} +--- Creates a NumericFeedback value. +function module.NumericFeedback(value: NumericFeedback): NumericFeedback + return value +end + +export type TextFeedback = { + color: VirtualColor?, + background_color: VirtualColor?, + text_expression: string?, +} +--- Creates a TextFeedback value. +function module.TextFeedback(value: TextFeedback): TextFeedback + return value +end + +export type DynamicFeedback = { + color: VirtualColor?, + background_color: VirtualColor?, + script: string?, +} +--- Creates a DynamicFeedback value. +function module.DynamicFeedback(value: DynamicFeedback): DynamicFeedback + return value +end + +export type Interval = { T } + +export type Group = { + id: string?, + name: string?, + tags: { string }?, + control_enabled: boolean?, + feedback_enabled: boolean?, + activation_condition: ActivationCondition?, +} +--- Creates a Group value. +function module.Group(value: Group): Group + return value +end + +export type Mapping = { + id: string?, + name: string?, + tags: { string }?, + group: string?, + visible_in_projection: boolean?, + enabled: boolean?, + control_enabled: boolean?, + feedback_enabled: boolean?, + activation_condition: ActivationCondition?, + on_activate: LifecycleHook?, + on_deactivate: LifecycleHook?, + source: Source?, + glue: Glue?, + target: Target?, + success_audio_feedback: SuccessAudioFeedback?, + unprocessed: { [string]: any }?, +} +--- Creates a Mapping value. +function module.Mapping(value: Mapping): Mapping + return value +end + +export type LifecycleHook = { + send_midi_feedback: { SendMidiFeedbackAction }?, +} +--- Creates a LifecycleHook value. +function module.LifecycleHook(value: LifecycleHook): LifecycleHook + return value +end + +export type SendMidiFeedbackAction_Raw = { kind: "Raw", message: RawMidiMessage } +export type SendMidiFeedbackAction = SendMidiFeedbackAction_Raw + +--- A type that represents all possible kinds of SendMidiFeedbackAction. +export type SendMidiFeedbackActionKind = "Raw" + +--- Helper table to create SendMidiFeedbackAction values of different kinds. +module.SendMidiFeedbackAction = {} + +--- Creates a SendMidiFeedbackAction of kind Raw. +function module.SendMidiFeedbackAction.Raw(value: { message: RawMidiMessage }): SendMidiFeedbackAction_Raw + local t: any = table.clone(value) + t.kind = "Raw" + return t +end + +export type RawMidiMessage_HexString = string + +export type RawMidiMessage_ByteArray = { number } +export type RawMidiMessage = RawMidiMessage_HexString | RawMidiMessage_ByteArray + +--- Creates a RawMidiMessage value. +function module.RawMidiMessage(value: RawMidiMessage): RawMidiMessage + return value +end + +export type SuccessAudioFeedback_Simple = { kind: "Simple" } +export type SuccessAudioFeedback = SuccessAudioFeedback_Simple + +--- A type that represents all possible kinds of SuccessAudioFeedback. +export type SuccessAudioFeedbackKind = "Simple" + +--- Helper table to create SuccessAudioFeedback values of different kinds. +module.SuccessAudioFeedback = {} + +--- Creates a SuccessAudioFeedback of kind Simple. +function module.SuccessAudioFeedback.Simple(): SuccessAudioFeedback_Simple + return { + kind = "Simple", + } +end + +export type ActivationCondition_Modifier = { kind: "Modifier", modifiers: { ModifierState }? } + +export type ActivationCondition_Bank = { kind: "Bank", parameter: ParamRef, bank_index: number } + +export type ActivationCondition_Eel = { kind: "Eel", condition: string } + +export type ActivationCondition_Expression = { kind: "Expression", condition: string } + +export type ActivationCondition_TargetValue = { kind: "TargetValue", mapping: string?, condition: string } +export type ActivationCondition = + ActivationCondition_Modifier + | ActivationCondition_Bank + | ActivationCondition_Eel + | ActivationCondition_Expression + | ActivationCondition_TargetValue + +--- A type that represents all possible kinds of ActivationCondition. +export type ActivationConditionKind = "Modifier" | "Bank" | "Eel" | "Expression" | "TargetValue" + +--- Helper table to create ActivationCondition values of different kinds. +module.ActivationCondition = {} + +--- Creates a ActivationCondition of kind Modifier. +function module.ActivationCondition.Modifier(value: ModifierActivationCondition): ActivationCondition_Modifier + local t: any = table.clone(value) + t.kind = "Modifier" + return t +end + +--- Creates a ActivationCondition of kind Bank. +function module.ActivationCondition.Bank(value: BankActivationCondition): ActivationCondition_Bank + local t: any = table.clone(value) + t.kind = "Bank" + return t +end + +--- Creates a ActivationCondition of kind Eel. +function module.ActivationCondition.Eel(value: EelActivationCondition): ActivationCondition_Eel + local t: any = table.clone(value) + t.kind = "Eel" + return t +end + +--- Creates a ActivationCondition of kind Expression. +function module.ActivationCondition.Expression(value: ExpressionActivationCondition): ActivationCondition_Expression + local t: any = table.clone(value) + t.kind = "Expression" + return t +end + +--- Creates a ActivationCondition of kind TargetValue. +function module.ActivationCondition.TargetValue(value: TargetValueActivationCondition): ActivationCondition_TargetValue + local t: any = table.clone(value) + t.kind = "TargetValue" + return t +end + +export type ModifierActivationCondition = { + modifiers: { ModifierState }?, +} +--- Creates a ModifierActivationCondition value. +function module.ModifierActivationCondition(value: ModifierActivationCondition): ModifierActivationCondition + return value +end + +export type ModifierState = { + parameter: ParamRef, + on: boolean, +} +--- Creates a ModifierState value. +function module.ModifierState(value: ModifierState): ModifierState + return value +end + +export type BankActivationCondition = { + parameter: ParamRef, + bank_index: number, +} +--- Creates a BankActivationCondition value. +function module.BankActivationCondition(value: BankActivationCondition): BankActivationCondition + return value +end + +export type EelActivationCondition = { + condition: string, +} +--- Creates a EelActivationCondition value. +function module.EelActivationCondition(value: EelActivationCondition): EelActivationCondition + return value +end + +export type ExpressionActivationCondition = { + condition: string, +} +--- Creates a ExpressionActivationCondition value. +function module.ExpressionActivationCondition(value: ExpressionActivationCondition): ExpressionActivationCondition + return value +end + +export type TargetValueActivationCondition = { + mapping: string?, + condition: string, +} +--- Creates a TargetValueActivationCondition value. +function module.TargetValueActivationCondition(value: TargetValueActivationCondition): TargetValueActivationCondition + return value +end + +export type ParamRef_Index = number + +export type ParamRef_Key = string +export type ParamRef = ParamRef_Index | ParamRef_Key + +--- Creates a ParamRef value. +function module.ParamRef(value: ParamRef): ParamRef + return value +end + +export type VirtualControlElementId_Indexed = number + +export type VirtualControlElementId_Named = string +export type VirtualControlElementId = VirtualControlElementId_Indexed | VirtualControlElementId_Named + +--- Creates a VirtualControlElementId value. +function module.VirtualControlElementId(value: VirtualControlElementId): VirtualControlElementId + return value +end + +export type VirtualControlElementCharacter = "Multi" | "Button" + +export type OscArgument = { + index: number?, + arg_kind: OscArgKind?, + value_range: Interval?, +} +--- Creates a OscArgument value. +function module.OscArgument(value: OscArgument): OscArgument + return value +end + +export type OscArgKind = + "Float" + | "Double" + | "Bool" + | "Nil" + | "Inf" + | "Int" + | "String" + | "Blob" + | "Time" + | "Long" + | "Char" + | "Color" + | "Midi" + | "Array" + +export type Parameter = { + index: number, + id: string?, + name: string?, + value_count: number?, + value_labels: { string }?, +} +--- Creates a Parameter value. +function module.Parameter(value: Parameter): Parameter + return value +end + +export type Source_None = { kind: "None" } + +export type Source_MidiDeviceChanges = { kind: "MidiDeviceChanges" } + +export type Source_RealearnInstanceStart = { kind: "RealearnInstanceStart" } + +export type Source_RealearnCompartmentLoaded = { kind: "RealearnCompartmentLoaded" } + +export type Source_Timer = { kind: "Timer", duration: number } + +export type Source_RealearnParameter = { kind: "RealearnParameter", parameter_index: number } + +export type Source_Speech = { kind: "Speech" } + +export type Source_MidiNoteVelocity = { + kind: "MidiNoteVelocity", + feedback_behavior: FeedbackBehavior?, + channel: number?, + key_number: number?, +} + +export type Source_MidiNoteKeyNumber = { + kind: "MidiNoteKeyNumber", + feedback_behavior: FeedbackBehavior?, + channel: number?, +} + +export type Source_MidiPolyphonicKeyPressureAmount = { + kind: "MidiPolyphonicKeyPressureAmount", + feedback_behavior: FeedbackBehavior?, + channel: number?, + key_number: number?, +} + +export type Source_MidiControlChangeValue = { + kind: "MidiControlChangeValue", + feedback_behavior: FeedbackBehavior?, + channel: number?, + controller_number: number?, + character: SourceCharacter?, + fourteen_bit: boolean?, +} + +export type Source_MidiProgramChangeNumber = { + kind: "MidiProgramChangeNumber", + feedback_behavior: FeedbackBehavior?, + channel: number?, +} + +export type Source_MidiSpecificProgramChange = { + kind: "MidiSpecificProgramChange", + feedback_behavior: FeedbackBehavior?, + channel: number?, + program_number: number?, +} + +export type Source_MidiChannelPressureAmount = { + kind: "MidiChannelPressureAmount", + feedback_behavior: FeedbackBehavior?, + channel: number?, +} + +export type Source_MidiPitchBendChangeValue = { + kind: "MidiPitchBendChangeValue", + feedback_behavior: FeedbackBehavior?, + channel: number?, +} + +export type Source_MidiParameterNumberValue = { + kind: "MidiParameterNumberValue", + feedback_behavior: FeedbackBehavior?, + channel: number?, + number: number?, + fourteen_bit: boolean?, + registered: boolean?, + character: SourceCharacter?, +} + +export type Source_MidiClockTempo = { kind: "MidiClockTempo" } + +export type Source_MidiClockTransport = { kind: "MidiClockTransport", message: MidiClockTransportMessage? } + +export type Source_MidiRaw = { + kind: "MidiRaw", + feedback_behavior: FeedbackBehavior?, + pattern: string?, + character: SourceCharacter?, +} + +export type Source_MidiScript = { kind: "MidiScript", script_kind: MidiScriptKind?, script: string? } + +export type Source_MackieLcd = { kind: "MackieLcd", extender_index: number?, channel: number?, line: number? } + +export type Source_XTouchMackieLcd = { kind: "XTouchMackieLcd", extender_index: number?, channel: number?, line: number? } + +export type Source_MackieSevenSegmentDisplay = { + kind: "MackieSevenSegmentDisplay", + scope: MackieSevenSegmentDisplayScope?, +} + +export type Source_SlKeyboardDisplay = { kind: "SlKeyboardDisplay", section: number?, line: number? } + +export type Source_SiniConE24Display = { kind: "SiniConE24Display", cell_index: number?, item_index: number? } + +export type Source_LaunchpadProScrollingTextDisplay = { kind: "LaunchpadProScrollingTextDisplay" } + +export type Source_Osc = { + kind: "Osc", + feedback_behavior: FeedbackBehavior?, + address: string?, + argument: OscArgument?, + relative: boolean?, + feedback_arguments: { string }?, +} + +export type Source_Key = { kind: "Key", keystroke: Keystroke? } + +export type Source_StreamDeck = { kind: "StreamDeck", button_index: number, button_design: StreamDeckButtonDesign? } + +export type Source_Virtual = { kind: "Virtual", id: VirtualControlElementId, character: VirtualControlElementCharacter? } +export type Source = + Source_None + | Source_MidiDeviceChanges + | Source_RealearnInstanceStart + | Source_RealearnCompartmentLoaded + | Source_Timer + | Source_RealearnParameter + | Source_Speech + | Source_MidiNoteVelocity + | Source_MidiNoteKeyNumber + | Source_MidiPolyphonicKeyPressureAmount + | Source_MidiControlChangeValue + | Source_MidiProgramChangeNumber + | Source_MidiSpecificProgramChange + | Source_MidiChannelPressureAmount + | Source_MidiPitchBendChangeValue + | Source_MidiParameterNumberValue + | Source_MidiClockTempo + | Source_MidiClockTransport + | Source_MidiRaw + | Source_MidiScript + | Source_MackieLcd + | Source_XTouchMackieLcd + | Source_MackieSevenSegmentDisplay + | Source_SlKeyboardDisplay + | Source_SiniConE24Display + | Source_LaunchpadProScrollingTextDisplay + | Source_Osc + | Source_Key + | Source_StreamDeck + | Source_Virtual + +--- A type that represents all possible kinds of Source. +export type SourceKind = + "None" + | "MidiDeviceChanges" + | "RealearnInstanceStart" + | "RealearnCompartmentLoaded" + | "Timer" + | "RealearnParameter" + | "Speech" + | "MidiNoteVelocity" + | "MidiNoteKeyNumber" + | "MidiPolyphonicKeyPressureAmount" + | "MidiControlChangeValue" + | "MidiProgramChangeNumber" + | "MidiSpecificProgramChange" + | "MidiChannelPressureAmount" + | "MidiPitchBendChangeValue" + | "MidiParameterNumberValue" + | "MidiClockTempo" + | "MidiClockTransport" + | "MidiRaw" + | "MidiScript" + | "MackieLcd" + | "XTouchMackieLcd" + | "MackieSevenSegmentDisplay" + | "SlKeyboardDisplay" + | "SiniConE24Display" + | "LaunchpadProScrollingTextDisplay" + | "Osc" + | "Key" + | "StreamDeck" + | "Virtual" + +--- Helper table to create Source values of different kinds. +module.Source = {} + +--- Creates a Source of kind None. +function module.Source.None(): Source_None + return { + kind = "None", + } +end + +--- Creates a Source of kind MidiDeviceChanges. +function module.Source.MidiDeviceChanges(): Source_MidiDeviceChanges + return { + kind = "MidiDeviceChanges", + } +end + +--- Creates a Source of kind RealearnInstanceStart. +function module.Source.RealearnInstanceStart(): Source_RealearnInstanceStart + return { + kind = "RealearnInstanceStart", + } +end + +--- Creates a Source of kind RealearnCompartmentLoaded. +function module.Source.RealearnCompartmentLoaded(): Source_RealearnCompartmentLoaded + return { + kind = "RealearnCompartmentLoaded", + } +end + +--- Creates a Source of kind Timer. +function module.Source.Timer(value: TimerSource): Source_Timer + local t: any = table.clone(value) + t.kind = "Timer" + return t +end + +--- Creates a Source of kind RealearnParameter. +function module.Source.RealearnParameter(value: RealearnParameterSource): Source_RealearnParameter + local t: any = table.clone(value) + t.kind = "RealearnParameter" + return t +end + +--- Creates a Source of kind Speech. +function module.Source.Speech(): Source_Speech + return { + kind = "Speech", + } +end + +--- Creates a Source of kind MidiNoteVelocity. +function module.Source.MidiNoteVelocity(value: MidiNoteVelocitySource): Source_MidiNoteVelocity + local t: any = table.clone(value) + t.kind = "MidiNoteVelocity" + return t +end + +--- Creates a Source of kind MidiNoteKeyNumber. +function module.Source.MidiNoteKeyNumber(value: MidiNoteKeyNumberSource): Source_MidiNoteKeyNumber + local t: any = table.clone(value) + t.kind = "MidiNoteKeyNumber" + return t +end + +--- Creates a Source of kind MidiPolyphonicKeyPressureAmount. +function module.Source.MidiPolyphonicKeyPressureAmount( + value: MidiPolyphonicKeyPressureAmountSource +): Source_MidiPolyphonicKeyPressureAmount + local t: any = table.clone(value) + t.kind = "MidiPolyphonicKeyPressureAmount" + return t +end + +--- Creates a Source of kind MidiControlChangeValue. +function module.Source.MidiControlChangeValue(value: MidiControlChangeValueSource): Source_MidiControlChangeValue + local t: any = table.clone(value) + t.kind = "MidiControlChangeValue" + return t +end + +--- Creates a Source of kind MidiProgramChangeNumber. +function module.Source.MidiProgramChangeNumber(value: MidiProgramChangeNumberSource): Source_MidiProgramChangeNumber + local t: any = table.clone(value) + t.kind = "MidiProgramChangeNumber" + return t +end + +--- Creates a Source of kind MidiSpecificProgramChange. +function module.Source.MidiSpecificProgramChange( + value: MidiSpecificProgramChangeSource +): Source_MidiSpecificProgramChange + local t: any = table.clone(value) + t.kind = "MidiSpecificProgramChange" + return t +end + +--- Creates a Source of kind MidiChannelPressureAmount. +function module.Source.MidiChannelPressureAmount( + value: MidiChannelPressureAmountSource +): Source_MidiChannelPressureAmount + local t: any = table.clone(value) + t.kind = "MidiChannelPressureAmount" + return t +end + +--- Creates a Source of kind MidiPitchBendChangeValue. +function module.Source.MidiPitchBendChangeValue(value: MidiPitchBendChangeValueSource): Source_MidiPitchBendChangeValue + local t: any = table.clone(value) + t.kind = "MidiPitchBendChangeValue" + return t +end + +--- Creates a Source of kind MidiParameterNumberValue. +function module.Source.MidiParameterNumberValue(value: MidiParameterNumberValueSource): Source_MidiParameterNumberValue + local t: any = table.clone(value) + t.kind = "MidiParameterNumberValue" + return t +end + +--- Creates a Source of kind MidiClockTempo. +function module.Source.MidiClockTempo(): Source_MidiClockTempo + return { + kind = "MidiClockTempo", + } +end + +--- Creates a Source of kind MidiClockTransport. +function module.Source.MidiClockTransport(value: MidiClockTransportSource): Source_MidiClockTransport + local t: any = table.clone(value) + t.kind = "MidiClockTransport" + return t +end + +--- Creates a Source of kind MidiRaw. +function module.Source.MidiRaw(value: MidiRawSource): Source_MidiRaw + local t: any = table.clone(value) + t.kind = "MidiRaw" + return t +end + +--- Creates a Source of kind MidiScript. +function module.Source.MidiScript(value: MidiScriptSource): Source_MidiScript + local t: any = table.clone(value) + t.kind = "MidiScript" + return t +end + +--- Creates a Source of kind MackieLcd. +function module.Source.MackieLcd(value: MackieLcdSource): Source_MackieLcd + local t: any = table.clone(value) + t.kind = "MackieLcd" + return t +end + +--- Creates a Source of kind XTouchMackieLcd. +function module.Source.XTouchMackieLcd(value: XTouchMackieLcdSource): Source_XTouchMackieLcd + local t: any = table.clone(value) + t.kind = "XTouchMackieLcd" + return t +end + +--- Creates a Source of kind MackieSevenSegmentDisplay. +function module.Source.MackieSevenSegmentDisplay( + value: MackieSevenSegmentDisplaySource +): Source_MackieSevenSegmentDisplay + local t: any = table.clone(value) + t.kind = "MackieSevenSegmentDisplay" + return t +end + +--- Creates a Source of kind SlKeyboardDisplay. +function module.Source.SlKeyboardDisplay(value: SlKeyboardDisplaySource): Source_SlKeyboardDisplay + local t: any = table.clone(value) + t.kind = "SlKeyboardDisplay" + return t +end + +--- Creates a Source of kind SiniConE24Display. +function module.Source.SiniConE24Display(value: SiniConE24DisplaySource): Source_SiniConE24Display + local t: any = table.clone(value) + t.kind = "SiniConE24Display" + return t +end + +--- Creates a Source of kind LaunchpadProScrollingTextDisplay. +function module.Source.LaunchpadProScrollingTextDisplay(): Source_LaunchpadProScrollingTextDisplay + return { + kind = "LaunchpadProScrollingTextDisplay", + } +end + +--- Creates a Source of kind Osc. +function module.Source.Osc(value: OscSource): Source_Osc + local t: any = table.clone(value) + t.kind = "Osc" + return t +end + +--- Creates a Source of kind Key. +function module.Source.Key(value: KeySource): Source_Key + local t: any = table.clone(value) + t.kind = "Key" + return t +end + +--- Creates a Source of kind StreamDeck. +function module.Source.StreamDeck(value: StreamDeckSource): Source_StreamDeck + local t: any = table.clone(value) + t.kind = "StreamDeck" + return t +end + +--- Creates a Source of kind Virtual. +function module.Source.Virtual(value: VirtualSource): Source_Virtual + local t: any = table.clone(value) + t.kind = "Virtual" + return t +end + +export type FeedbackBehavior = "Normal" | "SendFeedbackAfterControl" | "PreventEchoFeedback" + +export type MidiNoteVelocitySource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, + key_number: number?, +} +--- Creates a MidiNoteVelocitySource value. +function module.MidiNoteVelocitySource(value: MidiNoteVelocitySource): MidiNoteVelocitySource + return value +end + +export type MidiNoteKeyNumberSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, +} +--- Creates a MidiNoteKeyNumberSource value. +function module.MidiNoteKeyNumberSource(value: MidiNoteKeyNumberSource): MidiNoteKeyNumberSource + return value +end + +export type MidiPolyphonicKeyPressureAmountSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, + key_number: number?, +} +--- Creates a MidiPolyphonicKeyPressureAmountSource value. +function module.MidiPolyphonicKeyPressureAmountSource( + value: MidiPolyphonicKeyPressureAmountSource +): MidiPolyphonicKeyPressureAmountSource + return value +end + +export type MidiControlChangeValueSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, + controller_number: number?, + character: SourceCharacter?, + fourteen_bit: boolean?, +} +--- Creates a MidiControlChangeValueSource value. +function module.MidiControlChangeValueSource(value: MidiControlChangeValueSource): MidiControlChangeValueSource + return value +end + +export type MidiProgramChangeNumberSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, +} +--- Creates a MidiProgramChangeNumberSource value. +function module.MidiProgramChangeNumberSource(value: MidiProgramChangeNumberSource): MidiProgramChangeNumberSource + return value +end + +export type MidiSpecificProgramChangeSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, + program_number: number?, +} +--- Creates a MidiSpecificProgramChangeSource value. +function module.MidiSpecificProgramChangeSource(value: MidiSpecificProgramChangeSource): MidiSpecificProgramChangeSource + return value +end + +export type MidiChannelPressureAmountSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, +} +--- Creates a MidiChannelPressureAmountSource value. +function module.MidiChannelPressureAmountSource(value: MidiChannelPressureAmountSource): MidiChannelPressureAmountSource + return value +end + +export type MidiPitchBendChangeValueSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, +} +--- Creates a MidiPitchBendChangeValueSource value. +function module.MidiPitchBendChangeValueSource(value: MidiPitchBendChangeValueSource): MidiPitchBendChangeValueSource + return value +end + +export type MidiParameterNumberValueSource = { + feedback_behavior: FeedbackBehavior?, + channel: number?, + number: number?, + fourteen_bit: boolean?, + registered: boolean?, + character: SourceCharacter?, +} +--- Creates a MidiParameterNumberValueSource value. +function module.MidiParameterNumberValueSource(value: MidiParameterNumberValueSource): MidiParameterNumberValueSource + return value +end + +export type MidiClockTransportSource = { + message: MidiClockTransportMessage?, +} +--- Creates a MidiClockTransportSource value. +function module.MidiClockTransportSource(value: MidiClockTransportSource): MidiClockTransportSource + return value +end + +export type MidiRawSource = { + feedback_behavior: FeedbackBehavior?, + pattern: string?, + character: SourceCharacter?, +} +--- Creates a MidiRawSource value. +function module.MidiRawSource(value: MidiRawSource): MidiRawSource + return value +end + +export type MidiScriptSource = { + script_kind: MidiScriptKind?, + script: string?, +} +--- Creates a MidiScriptSource value. +function module.MidiScriptSource(value: MidiScriptSource): MidiScriptSource + return value +end + +--- Kind of a MIDI script +export type MidiScriptKind = "Eel" | "Lua" + +export type SourceCharacter = "Range" | "Button" | "Relative1" | "Relative2" | "Relative3" | "StatefulButton" + +export type MidiClockTransportMessage = "Start" | "Continue" | "Stop" + +export type MackieLcdSource = { + extender_index: number?, + channel: number?, + line: number?, +} +--- Creates a MackieLcdSource value. +function module.MackieLcdSource(value: MackieLcdSource): MackieLcdSource + return value +end + +export type XTouchMackieLcdSource = { + extender_index: number?, + channel: number?, + line: number?, +} +--- Creates a XTouchMackieLcdSource value. +function module.XTouchMackieLcdSource(value: XTouchMackieLcdSource): XTouchMackieLcdSource + return value +end + +export type SlKeyboardDisplaySource = { + section: number?, + line: number?, +} +--- Creates a SlKeyboardDisplaySource value. +function module.SlKeyboardDisplaySource(value: SlKeyboardDisplaySource): SlKeyboardDisplaySource + return value +end + +export type MackieSevenSegmentDisplaySource = { + scope: MackieSevenSegmentDisplayScope?, +} +--- Creates a MackieSevenSegmentDisplaySource value. +function module.MackieSevenSegmentDisplaySource(value: MackieSevenSegmentDisplaySource): MackieSevenSegmentDisplaySource + return value +end + +export type MackieSevenSegmentDisplayScope = + "All" + | "Assignment" + | "Tc" + | "TcHoursBars" + | "TcMinutesBeats" + | "TcSecondsSub" + | "TcFramesTicks" + +export type SiniConE24DisplaySource = { + cell_index: number?, + item_index: number?, +} +--- Creates a SiniConE24DisplaySource value. +function module.SiniConE24DisplaySource(value: SiniConE24DisplaySource): SiniConE24DisplaySource + return value +end + +export type OscSource = { + feedback_behavior: FeedbackBehavior?, + address: string?, + argument: OscArgument?, + relative: boolean?, + feedback_arguments: { string }?, +} +--- Creates a OscSource value. +function module.OscSource(value: OscSource): OscSource + return value +end + +export type RealearnParameterSource = { + parameter_index: number, +} +--- Creates a RealearnParameterSource value. +function module.RealearnParameterSource(value: RealearnParameterSource): RealearnParameterSource + return value +end + +export type TimerSource = { + duration: number, +} +--- Creates a TimerSource value. +function module.TimerSource(value: TimerSource): TimerSource + return value +end + +export type KeySource = { + keystroke: Keystroke?, +} +--- Creates a KeySource value. +function module.KeySource(value: KeySource): KeySource + return value +end + +export type StreamDeckSource = { + button_index: number, + button_design: StreamDeckButtonDesign?, +} +--- Creates a StreamDeckSource value. +function module.StreamDeckSource(value: StreamDeckSource): StreamDeckSource + return value +end + +export type StreamDeckButtonDesign = { + background: StreamDeckButtonBackground?, + foreground: StreamDeckButtonForeground?, + static_text: string?, +} +--- Creates a StreamDeckButtonDesign value. +function module.StreamDeckButtonDesign(value: StreamDeckButtonDesign): StreamDeckButtonDesign + return value +end + +export type StreamDeckButtonForeground_None = { kind: "None" } + +export type StreamDeckButtonForeground_FadingColor = { kind: "FadingColor" } + +export type StreamDeckButtonForeground_FadingImage = { kind: "FadingImage", path: string } + +export type StreamDeckButtonForeground_SlidingImage = { kind: "SlidingImage", path: string } + +export type StreamDeckButtonForeground_FullBar = { kind: "FullBar" } + +export type StreamDeckButtonForeground_Knob = { kind: "Knob" } +export type StreamDeckButtonForeground = + StreamDeckButtonForeground_None + | StreamDeckButtonForeground_FadingColor + | StreamDeckButtonForeground_FadingImage + | StreamDeckButtonForeground_SlidingImage + | StreamDeckButtonForeground_FullBar + | StreamDeckButtonForeground_Knob + +--- A type that represents all possible kinds of StreamDeckButtonForeground. +export type StreamDeckButtonForegroundKind = "None" | "FadingColor" | "FadingImage" | "SlidingImage" | "FullBar" | "Knob" + +--- Helper table to create StreamDeckButtonForeground values of different kinds. +module.StreamDeckButtonForeground = {} + +--- Creates a StreamDeckButtonForeground of kind None. +function module.StreamDeckButtonForeground.None(): StreamDeckButtonForeground_None + return { + kind = "None", + } +end + +--- Creates a StreamDeckButtonForeground of kind FadingColor. +function module.StreamDeckButtonForeground.FadingColor( + value: StreamDeckButtonFadingColorForeground +): StreamDeckButtonForeground_FadingColor + local t: any = table.clone(value) + t.kind = "FadingColor" + return t +end + +--- Creates a StreamDeckButtonForeground of kind FadingImage. +function module.StreamDeckButtonForeground.FadingImage( + value: StreamDeckButtonFadingImageForeground +): StreamDeckButtonForeground_FadingImage + local t: any = table.clone(value) + t.kind = "FadingImage" + return t +end + +--- Creates a StreamDeckButtonForeground of kind SlidingImage. +function module.StreamDeckButtonForeground.SlidingImage( + value: StreamDeckButtonSlidingImageForeground +): StreamDeckButtonForeground_SlidingImage + local t: any = table.clone(value) + t.kind = "SlidingImage" + return t +end + +--- Creates a StreamDeckButtonForeground of kind FullBar. +function module.StreamDeckButtonForeground.FullBar( + value: StreamDeckButtonFullBarForeground +): StreamDeckButtonForeground_FullBar + local t: any = table.clone(value) + t.kind = "FullBar" + return t +end + +--- Creates a StreamDeckButtonForeground of kind Knob. +function module.StreamDeckButtonForeground.Knob(value: StreamDeckButtonKnobForeground): StreamDeckButtonForeground_Knob + local t: any = table.clone(value) + t.kind = "Knob" + return t +end + +export type StreamDeckButtonBackground_Color = { kind: "Color" } + +export type StreamDeckButtonBackground_Image = { kind: "Image", path: string } +export type StreamDeckButtonBackground = StreamDeckButtonBackground_Color | StreamDeckButtonBackground_Image + +--- A type that represents all possible kinds of StreamDeckButtonBackground. +export type StreamDeckButtonBackgroundKind = "Color" | "Image" + +--- Helper table to create StreamDeckButtonBackground values of different kinds. +module.StreamDeckButtonBackground = {} + +--- Creates a StreamDeckButtonBackground of kind Color. +function module.StreamDeckButtonBackground.Color( + value: StreamDeckButtonColorBackground +): StreamDeckButtonBackground_Color + local t: any = table.clone(value) + t.kind = "Color" + return t +end + +--- Creates a StreamDeckButtonBackground of kind Image. +function module.StreamDeckButtonBackground.Image( + value: StreamDeckButtonImageBackground +): StreamDeckButtonBackground_Image + local t: any = table.clone(value) + t.kind = "Image" + return t +end + +export type StreamDeckButtonFadingImageForeground = { + path: string, +} +--- Creates a StreamDeckButtonFadingImageForeground value. +function module.StreamDeckButtonFadingImageForeground( + value: StreamDeckButtonFadingImageForeground +): StreamDeckButtonFadingImageForeground + return value +end + +export type StreamDeckButtonSlidingImageForeground = { + path: string, +} +--- Creates a StreamDeckButtonSlidingImageForeground value. +function module.StreamDeckButtonSlidingImageForeground( + value: StreamDeckButtonSlidingImageForeground +): StreamDeckButtonSlidingImageForeground + return value +end + +export type StreamDeckButtonFadingColorForeground = {} +--- Creates a StreamDeckButtonFadingColorForeground value. +function module.StreamDeckButtonFadingColorForeground( + value: StreamDeckButtonFadingColorForeground +): StreamDeckButtonFadingColorForeground + return value +end + +export type StreamDeckButtonFullBarForeground = {} +--- Creates a StreamDeckButtonFullBarForeground value. +function module.StreamDeckButtonFullBarForeground( + value: StreamDeckButtonFullBarForeground +): StreamDeckButtonFullBarForeground + return value +end + +export type StreamDeckButtonKnobForeground = {} +--- Creates a StreamDeckButtonKnobForeground value. +function module.StreamDeckButtonKnobForeground(value: StreamDeckButtonKnobForeground): StreamDeckButtonKnobForeground + return value +end + +export type StreamDeckButtonImageBackground = { + path: string, +} +--- Creates a StreamDeckButtonImageBackground value. +function module.StreamDeckButtonImageBackground(value: StreamDeckButtonImageBackground): StreamDeckButtonImageBackground + return value +end + +export type StreamDeckButtonColorBackground = {} +--- Creates a StreamDeckButtonColorBackground value. +function module.StreamDeckButtonColorBackground(value: StreamDeckButtonColorBackground): StreamDeckButtonColorBackground + return value +end + +export type Keystroke = { + modifiers: number, + key: number, +} +--- Creates a Keystroke value. +function module.Keystroke(value: Keystroke): Keystroke + return value +end + +export type VirtualSource = { + id: VirtualControlElementId, + character: VirtualControlElementCharacter?, +} +--- Creates a VirtualSource value. +function module.VirtualSource(value: VirtualSource): VirtualSource + return value +end + +export type LearnableTargetKind = + "TrackVolume" + | "TrackPan" + | "RouteVolume" + | "RoutePan" + | "TrackArmState" + | "TrackMuteState" + | "TrackSoloState" + | "TrackSelectionState" + | "FxOnOffState" + | "FxParameterValue" + | "BrowseFxPresets" + | "PlayRate" + | "Tempo" + | "TrackAutomationMode" + | "TrackMonitoringMode" + | "AutomationModeOverride" + | "ReaperAction" + | "TransportAction" + +--- Which target invocations to observe, based on causality. E.g. only those not triggered by +--- ReaLearn (would pick up invocations triggered by mouse interaction with REAPER but not by +--- ReaLearn mapping control). +export type TargetTouchCause = "Any" | "Realearn" | "Reaper" + +export type Target_Mouse = { kind: "Mouse", unit: TargetUnit?, action: MouseAction } + +export type Target_LastTouched = { + kind: "LastTouched", + unit: TargetUnit?, + included_targets: { LearnableTargetKind }?, + touch_cause: TargetTouchCause?, +} + +export type Target_AutomationModeOverride = { + kind: "AutomationModeOverride", + unit: TargetUnit?, + override_value: AutomationModeOverride?, +} + +export type Target_ReaperAction = { + kind: "ReaperAction", + unit: TargetUnit?, + scope: ActionScope?, + command: ReaperCommand?, + invocation: ActionInvocationKind?, + track: TrackDescriptor?, +} + +export type Target_TransportAction = { kind: "TransportAction", unit: TargetUnit?, action: TransportAction } + +export type Target_AnyOn = { kind: "AnyOn", unit: TargetUnit?, parameter: AnyOnParameter } + +export type Target_BrowseTracks = { + kind: "BrowseTracks", + unit: TargetUnit?, + scroll_arrange_view: boolean?, + scroll_mixer: boolean?, + mode: BrowseTracksMode?, +} + +export type Target_Seek = { + kind: "Seek", + unit: TargetUnit?, + use_time_selection: boolean?, + use_loop_points: boolean?, + use_regions: boolean?, + use_project: boolean?, + move_view: boolean?, + seek_play: boolean?, + feedback_resolution: FeedbackResolution?, + behavior: SeekBehavior?, +} + +export type Target_PlayRate = { kind: "PlayRate", unit: TargetUnit? } + +export type Target_Tempo = { kind: "Tempo", unit: TargetUnit? } + +export type Target_GoToBookmark = { + kind: "GoToBookmark", + unit: TargetUnit?, + bookmark: BookmarkDescriptor, + set_time_selection: boolean?, + set_loop_points: boolean?, + seek_behavior: SeekBehavior?, +} + +export type Target_TrackArmState = { + kind: "TrackArmState", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} + +export type Target_TrackParentSendState = { + kind: "TrackParentSendState", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, +} + +export type Target_AllTrackFxOnOffState = { + kind: "AllTrackFxOnOffState", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + poll_for_feedback: boolean?, +} + +export type Target_TrackMuteState = { + kind: "TrackMuteState", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} + +export type Target_TrackPeak = { kind: "TrackPeak", unit: TargetUnit?, track: TrackDescriptor? } + +export type Target_TrackPhase = { + kind: "TrackPhase", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + poll_for_feedback: boolean?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} + +export type Target_TrackSelectionState = { + kind: "TrackSelectionState", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + scroll_arrange_view: boolean?, + scroll_mixer: boolean?, +} + +export type Target_TrackAutomationMode = { + kind: "TrackAutomationMode", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + mode: AutomationMode, +} + +export type Target_TrackMonitoringMode = { + kind: "TrackMonitoringMode", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + mode: MonitoringMode, + use_selection_ganging: boolean?, +} + +export type Target_TrackAutomationTouchState = { + kind: "TrackAutomationTouchState", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, + touched_parameter: TouchedTrackParameter, +} + +export type Target_TrackPan = { + kind: "TrackPan", + unit: TargetUnit?, + track: TrackDescriptor?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} + +export type Target_TrackWidth = { + kind: "TrackWidth", + unit: TargetUnit?, + track: TrackDescriptor?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} + +export type Target_TrackVolume = { + kind: "TrackVolume", + unit: TargetUnit?, + track: TrackDescriptor?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} + +export type Target_TrackTool = { + kind: "TrackTool", + unit: TargetUnit?, + track: TrackDescriptor?, + action: TrackToolAction?, + instance_tags: { string }?, +} + +export type Target_TrackVisibility = { + kind: "TrackVisibility", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + poll_for_feedback: boolean?, + area: TrackArea, +} + +export type Target_TrackSoloState = { + kind: "TrackSoloState", + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + behavior: SoloBehavior?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} + +export type Target_BrowseFxChain = { + kind: "BrowseFxChain", + unit: TargetUnit?, + chain: FxChainDescriptor, + display_kind: FxDisplayKind?, +} + +export type Target_FxOnOffState = { kind: "FxOnOffState", unit: TargetUnit?, fx: FxDescriptor? } + +export type Target_FxOnlineOfflineState = { kind: "FxOnlineOfflineState", unit: TargetUnit?, fx: FxDescriptor? } + +export type Target_LoadFxSnapshot = { + kind: "LoadFxSnapshot", + unit: TargetUnit?, + fx: FxDescriptor?, + snapshot: FxSnapshot?, +} + +export type Target_BrowseFxPresets = { kind: "BrowseFxPresets", unit: TargetUnit?, fx: FxDescriptor? } + +export type Target_FxTool = { + kind: "FxTool", + unit: TargetUnit?, + fx: FxDescriptor?, + action: FxToolAction?, + instance_tags: { string }?, +} + +export type Target_FxVisibility = { + kind: "FxVisibility", + unit: TargetUnit?, + fx: FxDescriptor?, + display_kind: FxDisplayKind?, +} + +export type Target_FxParameterValue = { + kind: "FxParameterValue", + unit: TargetUnit?, + parameter: FxParameterDescriptor, + poll_for_feedback: boolean?, + retrigger: boolean?, + real_time: boolean?, +} + +export type Target_FxParameterAutomationTouchState = { + kind: "FxParameterAutomationTouchState", + unit: TargetUnit?, + parameter: FxParameterDescriptor, +} + +export type Target_RouteAutomationMode = { + kind: "RouteAutomationMode", + unit: TargetUnit?, + route: RouteDescriptor, + mode: AutomationMode, + poll_for_feedback: boolean?, +} + +export type Target_RouteMonoState = { + kind: "RouteMonoState", + unit: TargetUnit?, + route: RouteDescriptor, + poll_for_feedback: boolean?, +} + +export type Target_RouteMuteState = { + kind: "RouteMuteState", + unit: TargetUnit?, + route: RouteDescriptor, + poll_for_feedback: boolean?, +} + +export type Target_RoutePhase = { + kind: "RoutePhase", + unit: TargetUnit?, + route: RouteDescriptor, + poll_for_feedback: boolean?, +} + +export type Target_RoutePan = { kind: "RoutePan", unit: TargetUnit?, route: RouteDescriptor } + +export type Target_RouteVolume = { kind: "RouteVolume", unit: TargetUnit?, route: RouteDescriptor } + +export type Target_RouteTouchState = { + kind: "RouteTouchState", + unit: TargetUnit?, + route: RouteDescriptor, + touched_parameter: TouchedRouteParameter, +} + +export type Target_PlaytimeSlotTransportAction = { + kind: "PlaytimeSlotTransportAction", + unit: TargetUnit?, + slot: PlaytimeSlotDescriptor, + action: PlaytimeSlotTransportAction, + stop_column_if_slot_empty: boolean?, +} + +export type Target_PlaytimeColumnAction = { + kind: "PlaytimeColumnAction", + unit: TargetUnit?, + column: PlaytimeColumnDescriptor, + action: PlaytimeColumnAction, +} + +export type Target_PlaytimeRowAction = { + kind: "PlaytimeRowAction", + unit: TargetUnit?, + row: PlaytimeRowDescriptor, + action: PlaytimeRowAction, +} + +export type Target_PlaytimeMatrixAction = { + kind: "PlaytimeMatrixAction", + unit: TargetUnit?, + action: PlaytimeMatrixAction, +} + +export type Target_PlaytimeControlUnitScroll = { kind: "PlaytimeControlUnitScroll", unit: TargetUnit?, axis: Axis } + +export type Target_PlaytimeBrowseCells = { kind: "PlaytimeBrowseCells", unit: TargetUnit?, axis: Axis } + +export type Target_PlaytimeSlotSeek = { + kind: "PlaytimeSlotSeek", + unit: TargetUnit?, + slot: PlaytimeSlotDescriptor, + feedback_resolution: FeedbackResolution?, +} + +export type Target_PlaytimeSlotVolume = { kind: "PlaytimeSlotVolume", unit: TargetUnit?, slot: PlaytimeSlotDescriptor } + +export type Target_PlaytimeSlotManagementAction = { + kind: "PlaytimeSlotManagementAction", + unit: TargetUnit?, + slot: PlaytimeSlotDescriptor, + action: PlaytimeSlotManagementAction, +} + +export type Target_SendMidi = { kind: "SendMidi", unit: TargetUnit?, message: string?, destination: SendMidiDestination? } + +export type Target_SendOsc = { + kind: "SendOsc", + unit: TargetUnit?, + address: string?, + argument: OscArgument?, + destination: OscDestination?, +} + +export type Target_Dummy = { kind: "Dummy", unit: TargetUnit? } + +export type Target_EnableInstances = { + kind: "EnableInstances", + unit: TargetUnit?, + tags: { string }?, + exclusivity: InstanceExclusivity?, +} + +export type Target_EnableMappings = { + kind: "EnableMappings", + unit: TargetUnit?, + tags: { string }?, + exclusivity: MappingExclusivity?, +} + +export type Target_ModifyMapping = { + kind: "ModifyMapping", + unit: TargetUnit?, + session: string?, + mapping: string?, + modification: MappingModification, +} + +export type Target_CompartmentParameterValue = { + kind: "CompartmentParameterValue", + unit: TargetUnit?, + parameter: CompartmentParameterDescriptor, +} + +export type Target_LoadMappingSnapshot = { + kind: "LoadMappingSnapshot", + unit: TargetUnit?, + tags: { string }?, + active_mappings_only: boolean?, + snapshot: MappingSnapshotDescForLoad?, + default_value: TargetValue?, +} + +export type Target_TakeMappingSnapshot = { + kind: "TakeMappingSnapshot", + unit: TargetUnit?, + tags: { string }?, + active_mappings_only: boolean?, + snapshot: BackwardCompatibleMappingSnapshotDescForTake, +} + +export type Target_BrowseGroupMappings = { + kind: "BrowseGroupMappings", + unit: TargetUnit?, + exclusivity: GroupMappingExclusivity?, + group: string?, +} + +export type Target_BrowsePotFilterItems = { kind: "BrowsePotFilterItems", unit: TargetUnit?, item_kind: PotFilterKind? } + +export type Target_BrowsePotPresets = { kind: "BrowsePotPresets", unit: TargetUnit? } + +export type Target_PreviewPotPreset = { kind: "PreviewPotPreset", unit: TargetUnit? } + +export type Target_LoadPotPreset = { kind: "LoadPotPreset", unit: TargetUnit?, fx: FxDescriptor? } + +export type Target_StreamDeckBrightness = { kind: "StreamDeckBrightness", unit: TargetUnit? } + +export type Target_Virtual = { + kind: "Virtual", + id: VirtualControlElementId, + character: VirtualControlElementCharacter?, + learnable: boolean?, +} +export type Target = + Target_Mouse + | Target_LastTouched + | Target_AutomationModeOverride + | Target_ReaperAction + | Target_TransportAction + | Target_AnyOn + | Target_BrowseTracks + | Target_Seek + | Target_PlayRate + | Target_Tempo + | Target_GoToBookmark + | Target_TrackArmState + | Target_TrackParentSendState + | Target_AllTrackFxOnOffState + | Target_TrackMuteState + | Target_TrackPeak + | Target_TrackPhase + | Target_TrackSelectionState + | Target_TrackAutomationMode + | Target_TrackMonitoringMode + | Target_TrackAutomationTouchState + | Target_TrackPan + | Target_TrackWidth + | Target_TrackVolume + | Target_TrackTool + | Target_TrackVisibility + | Target_TrackSoloState + | Target_BrowseFxChain + | Target_FxOnOffState + | Target_FxOnlineOfflineState + | Target_LoadFxSnapshot + | Target_BrowseFxPresets + | Target_FxTool + | Target_FxVisibility + | Target_FxParameterValue + | Target_FxParameterAutomationTouchState + | Target_RouteAutomationMode + | Target_RouteMonoState + | Target_RouteMuteState + | Target_RoutePhase + | Target_RoutePan + | Target_RouteVolume + | Target_RouteTouchState + | Target_PlaytimeSlotTransportAction + | Target_PlaytimeColumnAction + | Target_PlaytimeRowAction + | Target_PlaytimeMatrixAction + | Target_PlaytimeControlUnitScroll + | Target_PlaytimeBrowseCells + | Target_PlaytimeSlotSeek + | Target_PlaytimeSlotVolume + | Target_PlaytimeSlotManagementAction + | Target_SendMidi + | Target_SendOsc + | Target_Dummy + | Target_EnableInstances + | Target_EnableMappings + | Target_ModifyMapping + | Target_CompartmentParameterValue + | Target_LoadMappingSnapshot + | Target_TakeMappingSnapshot + | Target_BrowseGroupMappings + | Target_BrowsePotFilterItems + | Target_BrowsePotPresets + | Target_PreviewPotPreset + | Target_LoadPotPreset + | Target_StreamDeckBrightness + | Target_Virtual + +--- A type that represents all possible kinds of Target. +export type TargetKind = + "Mouse" + | "LastTouched" + | "AutomationModeOverride" + | "ReaperAction" + | "TransportAction" + | "AnyOn" + | "BrowseTracks" + | "Seek" + | "PlayRate" + | "Tempo" + | "GoToBookmark" + | "TrackArmState" + | "TrackParentSendState" + | "AllTrackFxOnOffState" + | "TrackMuteState" + | "TrackPeak" + | "TrackPhase" + | "TrackSelectionState" + | "TrackAutomationMode" + | "TrackMonitoringMode" + | "TrackAutomationTouchState" + | "TrackPan" + | "TrackWidth" + | "TrackVolume" + | "TrackTool" + | "TrackVisibility" + | "TrackSoloState" + | "BrowseFxChain" + | "FxOnOffState" + | "FxOnlineOfflineState" + | "LoadFxSnapshot" + | "BrowseFxPresets" + | "FxTool" + | "FxVisibility" + | "FxParameterValue" + | "FxParameterAutomationTouchState" + | "RouteAutomationMode" + | "RouteMonoState" + | "RouteMuteState" + | "RoutePhase" + | "RoutePan" + | "RouteVolume" + | "RouteTouchState" + | "PlaytimeSlotTransportAction" + | "PlaytimeColumnAction" + | "PlaytimeRowAction" + | "PlaytimeMatrixAction" + | "PlaytimeControlUnitScroll" + | "PlaytimeBrowseCells" + | "PlaytimeSlotSeek" + | "PlaytimeSlotVolume" + | "PlaytimeSlotManagementAction" + | "SendMidi" + | "SendOsc" + | "Dummy" + | "EnableInstances" + | "EnableMappings" + | "ModifyMapping" + | "CompartmentParameterValue" + | "LoadMappingSnapshot" + | "TakeMappingSnapshot" + | "BrowseGroupMappings" + | "BrowsePotFilterItems" + | "BrowsePotPresets" + | "PreviewPotPreset" + | "LoadPotPreset" + | "StreamDeckBrightness" + | "Virtual" + +--- Helper table to create Target values of different kinds. +module.Target = {} + +--- Creates a Target of kind Mouse. +function module.Target.Mouse(value: MouseTarget): Target_Mouse + local t: any = table.clone(value) + t.kind = "Mouse" + return t +end + +--- Creates a Target of kind LastTouched. +function module.Target.LastTouched(value: LastTouchedTarget): Target_LastTouched + local t: any = table.clone(value) + t.kind = "LastTouched" + return t +end + +--- Creates a Target of kind AutomationModeOverride. +function module.Target.AutomationModeOverride(value: AutomationModeOverrideTarget): Target_AutomationModeOverride + local t: any = table.clone(value) + t.kind = "AutomationModeOverride" + return t +end + +--- Creates a Target of kind ReaperAction. +function module.Target.ReaperAction(value: ReaperActionTarget): Target_ReaperAction + local t: any = table.clone(value) + t.kind = "ReaperAction" + return t +end + +--- Creates a Target of kind TransportAction. +function module.Target.TransportAction(value: TransportActionTarget): Target_TransportAction + local t: any = table.clone(value) + t.kind = "TransportAction" + return t +end + +--- Creates a Target of kind AnyOn. +function module.Target.AnyOn(value: AnyOnTarget): Target_AnyOn + local t: any = table.clone(value) + t.kind = "AnyOn" + return t +end + +--- Creates a Target of kind BrowseTracks. +function module.Target.BrowseTracks(value: BrowseTracksTarget): Target_BrowseTracks + local t: any = table.clone(value) + t.kind = "BrowseTracks" + return t +end + +--- Creates a Target of kind Seek. +function module.Target.Seek(value: SeekTarget): Target_Seek + local t: any = table.clone(value) + t.kind = "Seek" + return t +end + +--- Creates a Target of kind PlayRate. +function module.Target.PlayRate(value: PlayRateTarget): Target_PlayRate + local t: any = table.clone(value) + t.kind = "PlayRate" + return t +end + +--- Creates a Target of kind Tempo. +function module.Target.Tempo(value: TempoTarget): Target_Tempo + local t: any = table.clone(value) + t.kind = "Tempo" + return t +end + +--- Creates a Target of kind GoToBookmark. +function module.Target.GoToBookmark(value: GoToBookmarkTarget): Target_GoToBookmark + local t: any = table.clone(value) + t.kind = "GoToBookmark" + return t +end + +--- Creates a Target of kind TrackArmState. +function module.Target.TrackArmState(value: TrackArmStateTarget): Target_TrackArmState + local t: any = table.clone(value) + t.kind = "TrackArmState" + return t +end + +--- Creates a Target of kind TrackParentSendState. +function module.Target.TrackParentSendState(value: TrackParentSendStateTarget): Target_TrackParentSendState + local t: any = table.clone(value) + t.kind = "TrackParentSendState" + return t +end + +--- Creates a Target of kind AllTrackFxOnOffState. +function module.Target.AllTrackFxOnOffState(value: AllTrackFxOnOffStateTarget): Target_AllTrackFxOnOffState + local t: any = table.clone(value) + t.kind = "AllTrackFxOnOffState" + return t +end + +--- Creates a Target of kind TrackMuteState. +function module.Target.TrackMuteState(value: TrackMuteStateTarget): Target_TrackMuteState + local t: any = table.clone(value) + t.kind = "TrackMuteState" + return t +end + +--- Creates a Target of kind TrackPeak. +function module.Target.TrackPeak(value: TrackPeakTarget): Target_TrackPeak + local t: any = table.clone(value) + t.kind = "TrackPeak" + return t +end + +--- Creates a Target of kind TrackPhase. +function module.Target.TrackPhase(value: TrackPhaseTarget): Target_TrackPhase + local t: any = table.clone(value) + t.kind = "TrackPhase" + return t +end + +--- Creates a Target of kind TrackSelectionState. +function module.Target.TrackSelectionState(value: TrackSelectionStateTarget): Target_TrackSelectionState + local t: any = table.clone(value) + t.kind = "TrackSelectionState" + return t +end + +--- Creates a Target of kind TrackAutomationMode. +function module.Target.TrackAutomationMode(value: TrackAutomationModeTarget): Target_TrackAutomationMode + local t: any = table.clone(value) + t.kind = "TrackAutomationMode" + return t +end + +--- Creates a Target of kind TrackMonitoringMode. +function module.Target.TrackMonitoringMode(value: TrackMonitoringModeTarget): Target_TrackMonitoringMode + local t: any = table.clone(value) + t.kind = "TrackMonitoringMode" + return t +end + +--- Creates a Target of kind TrackAutomationTouchState. +function module.Target.TrackAutomationTouchState( + value: TrackAutomationTouchStateTarget +): Target_TrackAutomationTouchState + local t: any = table.clone(value) + t.kind = "TrackAutomationTouchState" + return t +end + +--- Creates a Target of kind TrackPan. +function module.Target.TrackPan(value: TrackPanTarget): Target_TrackPan + local t: any = table.clone(value) + t.kind = "TrackPan" + return t +end + +--- Creates a Target of kind TrackWidth. +function module.Target.TrackWidth(value: TrackWidthTarget): Target_TrackWidth + local t: any = table.clone(value) + t.kind = "TrackWidth" + return t +end + +--- Creates a Target of kind TrackVolume. +function module.Target.TrackVolume(value: TrackVolumeTarget): Target_TrackVolume + local t: any = table.clone(value) + t.kind = "TrackVolume" + return t +end + +--- Creates a Target of kind TrackTool. +function module.Target.TrackTool(value: TrackToolTarget): Target_TrackTool + local t: any = table.clone(value) + t.kind = "TrackTool" + return t +end + +--- Creates a Target of kind TrackVisibility. +function module.Target.TrackVisibility(value: TrackVisibilityTarget): Target_TrackVisibility + local t: any = table.clone(value) + t.kind = "TrackVisibility" + return t +end + +--- Creates a Target of kind TrackSoloState. +function module.Target.TrackSoloState(value: TrackSoloStateTarget): Target_TrackSoloState + local t: any = table.clone(value) + t.kind = "TrackSoloState" + return t +end + +--- Creates a Target of kind BrowseFxChain. +function module.Target.BrowseFxChain(value: BrowseFxChainTarget): Target_BrowseFxChain + local t: any = table.clone(value) + t.kind = "BrowseFxChain" + return t +end + +--- Creates a Target of kind FxOnOffState. +function module.Target.FxOnOffState(value: FxOnOffStateTarget): Target_FxOnOffState + local t: any = table.clone(value) + t.kind = "FxOnOffState" + return t +end + +--- Creates a Target of kind FxOnlineOfflineState. +function module.Target.FxOnlineOfflineState(value: FxOnlineOfflineStateTarget): Target_FxOnlineOfflineState + local t: any = table.clone(value) + t.kind = "FxOnlineOfflineState" + return t +end + +--- Creates a Target of kind LoadFxSnapshot. +function module.Target.LoadFxSnapshot(value: LoadFxSnapshotTarget): Target_LoadFxSnapshot + local t: any = table.clone(value) + t.kind = "LoadFxSnapshot" + return t +end + +--- Creates a Target of kind BrowseFxPresets. +function module.Target.BrowseFxPresets(value: BrowseFxPresetsTarget): Target_BrowseFxPresets + local t: any = table.clone(value) + t.kind = "BrowseFxPresets" + return t +end + +--- Creates a Target of kind FxTool. +function module.Target.FxTool(value: FxToolTarget): Target_FxTool + local t: any = table.clone(value) + t.kind = "FxTool" + return t +end + +--- Creates a Target of kind FxVisibility. +function module.Target.FxVisibility(value: FxVisibilityTarget): Target_FxVisibility + local t: any = table.clone(value) + t.kind = "FxVisibility" + return t +end + +--- Creates a Target of kind FxParameterValue. +function module.Target.FxParameterValue(value: FxParameterValueTarget): Target_FxParameterValue + local t: any = table.clone(value) + t.kind = "FxParameterValue" + return t +end + +--- Creates a Target of kind FxParameterAutomationTouchState. +function module.Target.FxParameterAutomationTouchState( + value: FxParameterAutomationTouchStateTarget +): Target_FxParameterAutomationTouchState + local t: any = table.clone(value) + t.kind = "FxParameterAutomationTouchState" + return t +end + +--- Creates a Target of kind RouteAutomationMode. +function module.Target.RouteAutomationMode(value: RouteAutomationModeTarget): Target_RouteAutomationMode + local t: any = table.clone(value) + t.kind = "RouteAutomationMode" + return t +end + +--- Creates a Target of kind RouteMonoState. +function module.Target.RouteMonoState(value: RouteMonoStateTarget): Target_RouteMonoState + local t: any = table.clone(value) + t.kind = "RouteMonoState" + return t +end + +--- Creates a Target of kind RouteMuteState. +function module.Target.RouteMuteState(value: RouteMuteStateTarget): Target_RouteMuteState + local t: any = table.clone(value) + t.kind = "RouteMuteState" + return t +end + +--- Creates a Target of kind RoutePhase. +function module.Target.RoutePhase(value: RoutePhaseTarget): Target_RoutePhase + local t: any = table.clone(value) + t.kind = "RoutePhase" + return t +end + +--- Creates a Target of kind RoutePan. +function module.Target.RoutePan(value: RoutePanTarget): Target_RoutePan + local t: any = table.clone(value) + t.kind = "RoutePan" + return t +end + +--- Creates a Target of kind RouteVolume. +function module.Target.RouteVolume(value: RouteVolumeTarget): Target_RouteVolume + local t: any = table.clone(value) + t.kind = "RouteVolume" + return t +end + +--- Creates a Target of kind RouteTouchState. +function module.Target.RouteTouchState(value: RouteTouchStateTarget): Target_RouteTouchState + local t: any = table.clone(value) + t.kind = "RouteTouchState" + return t +end + +--- Creates a Target of kind PlaytimeSlotTransportAction. +function module.Target.PlaytimeSlotTransportAction( + value: PlaytimeSlotTransportActionTarget +): Target_PlaytimeSlotTransportAction + local t: any = table.clone(value) + t.kind = "PlaytimeSlotTransportAction" + return t +end + +--- Creates a Target of kind PlaytimeColumnAction. +function module.Target.PlaytimeColumnAction(value: PlaytimeColumnActionTarget): Target_PlaytimeColumnAction + local t: any = table.clone(value) + t.kind = "PlaytimeColumnAction" + return t +end + +--- Creates a Target of kind PlaytimeRowAction. +function module.Target.PlaytimeRowAction(value: PlaytimeRowActionTarget): Target_PlaytimeRowAction + local t: any = table.clone(value) + t.kind = "PlaytimeRowAction" + return t +end + +--- Creates a Target of kind PlaytimeMatrixAction. +function module.Target.PlaytimeMatrixAction(value: PlaytimeMatrixActionTarget): Target_PlaytimeMatrixAction + local t: any = table.clone(value) + t.kind = "PlaytimeMatrixAction" + return t +end + +--- Creates a Target of kind PlaytimeControlUnitScroll. +function module.Target.PlaytimeControlUnitScroll( + value: PlaytimeControlUnitScrollTarget +): Target_PlaytimeControlUnitScroll + local t: any = table.clone(value) + t.kind = "PlaytimeControlUnitScroll" + return t +end + +--- Creates a Target of kind PlaytimeBrowseCells. +function module.Target.PlaytimeBrowseCells(value: PlaytimeBrowseCellsTarget): Target_PlaytimeBrowseCells + local t: any = table.clone(value) + t.kind = "PlaytimeBrowseCells" + return t +end + +--- Creates a Target of kind PlaytimeSlotSeek. +function module.Target.PlaytimeSlotSeek(value: PlaytimeSlotSeekTarget): Target_PlaytimeSlotSeek + local t: any = table.clone(value) + t.kind = "PlaytimeSlotSeek" + return t +end + +--- Creates a Target of kind PlaytimeSlotVolume. +function module.Target.PlaytimeSlotVolume(value: PlaytimeSlotVolumeTarget): Target_PlaytimeSlotVolume + local t: any = table.clone(value) + t.kind = "PlaytimeSlotVolume" + return t +end + +--- Creates a Target of kind PlaytimeSlotManagementAction. +function module.Target.PlaytimeSlotManagementAction( + value: PlaytimeSlotManagementActionTarget +): Target_PlaytimeSlotManagementAction + local t: any = table.clone(value) + t.kind = "PlaytimeSlotManagementAction" + return t +end + +--- Creates a Target of kind SendMidi. +function module.Target.SendMidi(value: SendMidiTarget): Target_SendMidi + local t: any = table.clone(value) + t.kind = "SendMidi" + return t +end + +--- Creates a Target of kind SendOsc. +function module.Target.SendOsc(value: SendOscTarget): Target_SendOsc + local t: any = table.clone(value) + t.kind = "SendOsc" + return t +end + +--- Creates a Target of kind Dummy. +function module.Target.Dummy(value: DummyTarget): Target_Dummy + local t: any = table.clone(value) + t.kind = "Dummy" + return t +end + +--- Creates a Target of kind EnableInstances. +function module.Target.EnableInstances(value: EnableInstancesTarget): Target_EnableInstances + local t: any = table.clone(value) + t.kind = "EnableInstances" + return t +end + +--- Creates a Target of kind EnableMappings. +function module.Target.EnableMappings(value: EnableMappingsTarget): Target_EnableMappings + local t: any = table.clone(value) + t.kind = "EnableMappings" + return t +end + +--- Creates a Target of kind ModifyMapping. +function module.Target.ModifyMapping(value: ModifyMappingTarget): Target_ModifyMapping + local t: any = table.clone(value) + t.kind = "ModifyMapping" + return t +end + +--- Creates a Target of kind CompartmentParameterValue. +function module.Target.CompartmentParameterValue( + value: CompartmentParameterValueTarget +): Target_CompartmentParameterValue + local t: any = table.clone(value) + t.kind = "CompartmentParameterValue" + return t +end + +--- Creates a Target of kind LoadMappingSnapshot. +function module.Target.LoadMappingSnapshot(value: LoadMappingSnapshotTarget): Target_LoadMappingSnapshot + local t: any = table.clone(value) + t.kind = "LoadMappingSnapshot" + return t +end + +--- Creates a Target of kind TakeMappingSnapshot. +function module.Target.TakeMappingSnapshot(value: TakeMappingSnapshotTarget): Target_TakeMappingSnapshot + local t: any = table.clone(value) + t.kind = "TakeMappingSnapshot" + return t +end + +--- Creates a Target of kind BrowseGroupMappings. +function module.Target.BrowseGroupMappings(value: BrowseGroupMappingsTarget): Target_BrowseGroupMappings + local t: any = table.clone(value) + t.kind = "BrowseGroupMappings" + return t +end + +--- Creates a Target of kind BrowsePotFilterItems. +function module.Target.BrowsePotFilterItems(value: BrowsePotFilterItemsTarget): Target_BrowsePotFilterItems + local t: any = table.clone(value) + t.kind = "BrowsePotFilterItems" + return t +end + +--- Creates a Target of kind BrowsePotPresets. +function module.Target.BrowsePotPresets(value: BrowsePotPresetsTarget): Target_BrowsePotPresets + local t: any = table.clone(value) + t.kind = "BrowsePotPresets" + return t +end + +--- Creates a Target of kind PreviewPotPreset. +function module.Target.PreviewPotPreset(value: PreviewPotPresetTarget): Target_PreviewPotPreset + local t: any = table.clone(value) + t.kind = "PreviewPotPreset" + return t +end + +--- Creates a Target of kind LoadPotPreset. +function module.Target.LoadPotPreset(value: LoadPotPresetTarget): Target_LoadPotPreset + local t: any = table.clone(value) + t.kind = "LoadPotPreset" + return t +end + +--- Creates a Target of kind StreamDeckBrightness. +function module.Target.StreamDeckBrightness(value: StreamDeckBrightnessTarget): Target_StreamDeckBrightness + local t: any = table.clone(value) + t.kind = "StreamDeckBrightness" + return t +end + +--- Creates a Target of kind Virtual. +function module.Target.Virtual(value: VirtualTarget): Target_Virtual + local t: any = table.clone(value) + t.kind = "Virtual" + return t +end + +export type TargetCommons = { + unit: TargetUnit?, +} +--- Creates a TargetCommons value. +function module.TargetCommons(value: TargetCommons): TargetCommons + return value +end + +export type TargetUnit = "Native" | "Percent" + +export type MouseTarget = { + unit: TargetUnit?, + action: MouseAction, +} +--- Creates a MouseTarget value. +function module.MouseTarget(value: MouseTarget): MouseTarget + return value +end + +export type LastTouchedTarget = { + unit: TargetUnit?, + included_targets: { LearnableTargetKind }?, + touch_cause: TargetTouchCause?, +} +--- Creates a LastTouchedTarget value. +function module.LastTouchedTarget(value: LastTouchedTarget): LastTouchedTarget + return value +end + +export type AutomationModeOverrideTarget = { + unit: TargetUnit?, + override_value: AutomationModeOverride?, +} +--- Creates a AutomationModeOverrideTarget value. +function module.AutomationModeOverrideTarget(value: AutomationModeOverrideTarget): AutomationModeOverrideTarget + return value +end + +export type ReaperActionTarget = { + unit: TargetUnit?, + scope: ActionScope?, + command: ReaperCommand?, + invocation: ActionInvocationKind?, + track: TrackDescriptor?, +} +--- Creates a ReaperActionTarget value. +function module.ReaperActionTarget(value: ReaperActionTarget): ReaperActionTarget + return value +end + +export type ActionScope = "Main" | "ActiveMidiEditor" | "ActiveMidiEventListEditor" | "MediaExplorer" + +export type TransportActionTarget = { + unit: TargetUnit?, + action: TransportAction, +} +--- Creates a TransportActionTarget value. +function module.TransportActionTarget(value: TransportActionTarget): TransportActionTarget + return value +end + +export type AnyOnTarget = { + unit: TargetUnit?, + parameter: AnyOnParameter, +} +--- Creates a AnyOnTarget value. +function module.AnyOnTarget(value: AnyOnTarget): AnyOnTarget + return value +end + +export type BrowseTracksTarget = { + unit: TargetUnit?, + scroll_arrange_view: boolean?, + scroll_mixer: boolean?, + mode: BrowseTracksMode?, +} +--- Creates a BrowseTracksTarget value. +function module.BrowseTracksTarget(value: BrowseTracksTarget): BrowseTracksTarget + return value +end + +export type SeekTarget = { + unit: TargetUnit?, + use_time_selection: boolean?, + use_loop_points: boolean?, + use_regions: boolean?, + use_project: boolean?, + move_view: boolean?, + seek_play: boolean?, + feedback_resolution: FeedbackResolution?, + behavior: SeekBehavior?, +} +--- Creates a SeekTarget value. +function module.SeekTarget(value: SeekTarget): SeekTarget + return value +end + +export type PlayRateTarget = { + unit: TargetUnit?, +} +--- Creates a PlayRateTarget value. +function module.PlayRateTarget(value: PlayRateTarget): PlayRateTarget + return value +end + +export type TempoTarget = { + unit: TargetUnit?, +} +--- Creates a TempoTarget value. +function module.TempoTarget(value: TempoTarget): TempoTarget + return value +end + +export type GoToBookmarkTarget = { + unit: TargetUnit?, + bookmark: BookmarkDescriptor, + set_time_selection: boolean?, + set_loop_points: boolean?, + seek_behavior: SeekBehavior?, +} +--- Creates a GoToBookmarkTarget value. +function module.GoToBookmarkTarget(value: GoToBookmarkTarget): GoToBookmarkTarget + return value +end + +export type TrackArmStateTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} +--- Creates a TrackArmStateTarget value. +function module.TrackArmStateTarget(value: TrackArmStateTarget): TrackArmStateTarget + return value +end + +export type TrackParentSendStateTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, +} +--- Creates a TrackParentSendStateTarget value. +function module.TrackParentSendStateTarget(value: TrackParentSendStateTarget): TrackParentSendStateTarget + return value +end + +export type AllTrackFxOnOffStateTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + poll_for_feedback: boolean?, +} +--- Creates a AllTrackFxOnOffStateTarget value. +function module.AllTrackFxOnOffStateTarget(value: AllTrackFxOnOffStateTarget): AllTrackFxOnOffStateTarget + return value +end + +export type TrackMuteStateTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} +--- Creates a TrackMuteStateTarget value. +function module.TrackMuteStateTarget(value: TrackMuteStateTarget): TrackMuteStateTarget + return value +end + +export type TrackPeakTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, +} +--- Creates a TrackPeakTarget value. +function module.TrackPeakTarget(value: TrackPeakTarget): TrackPeakTarget + return value +end + +export type TrackPhaseTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + poll_for_feedback: boolean?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} +--- Creates a TrackPhaseTarget value. +function module.TrackPhaseTarget(value: TrackPhaseTarget): TrackPhaseTarget + return value +end + +export type TrackSelectionStateTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + scroll_arrange_view: boolean?, + scroll_mixer: boolean?, +} +--- Creates a TrackSelectionStateTarget value. +function module.TrackSelectionStateTarget(value: TrackSelectionStateTarget): TrackSelectionStateTarget + return value +end + +export type TrackAutomationModeTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + mode: AutomationMode, +} +--- Creates a TrackAutomationModeTarget value. +function module.TrackAutomationModeTarget(value: TrackAutomationModeTarget): TrackAutomationModeTarget + return value +end + +export type TrackMonitoringModeTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + mode: MonitoringMode, + use_selection_ganging: boolean?, +} +--- Creates a TrackMonitoringModeTarget value. +function module.TrackMonitoringModeTarget(value: TrackMonitoringModeTarget): TrackMonitoringModeTarget + return value +end + +export type TrackAutomationTouchStateTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, + touched_parameter: TouchedTrackParameter, +} +--- Creates a TrackAutomationTouchStateTarget value. +function module.TrackAutomationTouchStateTarget(value: TrackAutomationTouchStateTarget): TrackAutomationTouchStateTarget + return value +end + +export type TrackPanTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} +--- Creates a TrackPanTarget value. +function module.TrackPanTarget(value: TrackPanTarget): TrackPanTarget + return value +end + +export type TrackWidthTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} +--- Creates a TrackWidthTarget value. +function module.TrackWidthTarget(value: TrackWidthTarget): TrackWidthTarget + return value +end + +export type TrackVolumeTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} +--- Creates a TrackVolumeTarget value. +function module.TrackVolumeTarget(value: TrackVolumeTarget): TrackVolumeTarget + return value +end + +export type TrackToolTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + action: TrackToolAction?, + instance_tags: { string }?, +} +--- Creates a TrackToolTarget value. +function module.TrackToolTarget(value: TrackToolTarget): TrackToolTarget + return value +end + +export type TrackToolAction = "DoNothing" | "SetAsUnitTrack" | "PinAsUnitTrack" + +--- Mouse position on the given axis. +--- +--- Control: +--- +--- - Move by (using relative control value) +--- - Move to (use absolute control value) +--- - Move to absolute coordinates before clicking (2 mappings for moving, 1 for clicking) +--- +--- Feedback: +--- +--- - Reflect position of the mouse cursor +--- +--- Future extension possibilities: +--- +--- - Canvas: Relative to all screens, current screen, REAPER window or focused window +--- - Pixel density: Take pixel density into account +export type MouseAction_MoveTo = { kind: "MoveTo", axis: Axis? } + +--- Like [`Self::Move`] but prefers relative control, so the glue section will feed it +--- with relative control values whenever possible. Could make a little difference. +export type MouseAction_MoveBy = { kind: "MoveBy", axis: Axis? } + +--- Button state. +--- +--- Control: +--- +--- - Press and release a mouse button +--- - Press a mouse button and keep it pressed (press-only filter) +--- - Just release a mouse button (release-only filter, e.g. for manual drag control) +--- +--- Feedback: +--- +--- - Whether the button is down or up +--- +--- Future extension possibilities: +--- +--- - Click or double-click a mouse button (press and immediate release, this could be a generic +--- "Glue" option because it could be useful for other on/off targets as well). +export type MouseAction_PressOrRelease = { kind: "PressOrRelease", button: MouseButton? } + +--- Scroll wheel. +--- +--- Control: +--- +--- - Invoke scroll wheel +--- +--- Feedback: None +export type MouseAction_Scroll = { kind: "Scroll", axis: Axis? } +export type MouseAction = MouseAction_MoveTo | MouseAction_MoveBy | MouseAction_PressOrRelease | MouseAction_Scroll + +--- A type that represents all possible kinds of MouseAction. +export type MouseActionKind = "MoveTo" | "MoveBy" | "PressOrRelease" | "Scroll" + +--- Helper table to create MouseAction values of different kinds. +module.MouseAction = {} + +--- Creates a MouseAction of kind MoveTo. +--- Mouse position on the given axis. +--- +--- Control: +--- +--- - Move by (using relative control value) +--- - Move to (use absolute control value) +--- - Move to absolute coordinates before clicking (2 mappings for moving, 1 for clicking) +--- +--- Feedback: +--- +--- - Reflect position of the mouse cursor +--- +--- Future extension possibilities: +--- +--- - Canvas: Relative to all screens, current screen, REAPER window or focused window +--- - Pixel density: Take pixel density into account +function module.MouseAction.MoveTo(value: { axis: Axis? }): MouseAction_MoveTo + local t: any = table.clone(value) + t.kind = "MoveTo" + return t +end + +--- Creates a MouseAction of kind MoveBy. +--- Like [`Self::Move`] but prefers relative control, so the glue section will feed it +--- with relative control values whenever possible. Could make a little difference. +function module.MouseAction.MoveBy(value: { axis: Axis? }): MouseAction_MoveBy + local t: any = table.clone(value) + t.kind = "MoveBy" + return t +end + +--- Creates a MouseAction of kind PressOrRelease. +--- Button state. +--- +--- Control: +--- +--- - Press and release a mouse button +--- - Press a mouse button and keep it pressed (press-only filter) +--- - Just release a mouse button (release-only filter, e.g. for manual drag control) +--- +--- Feedback: +--- +--- - Whether the button is down or up +--- +--- Future extension possibilities: +--- +--- - Click or double-click a mouse button (press and immediate release, this could be a generic +--- "Glue" option because it could be useful for other on/off targets as well). +function module.MouseAction.PressOrRelease(value: { button: MouseButton? }): MouseAction_PressOrRelease + local t: any = table.clone(value) + t.kind = "PressOrRelease" + return t +end + +--- Creates a MouseAction of kind Scroll. +--- Scroll wheel. +--- +--- Control: +--- +--- - Invoke scroll wheel +--- +--- Feedback: None +function module.MouseAction.Scroll(value: { axis: Axis? }): MouseAction_Scroll + local t: any = table.clone(value) + t.kind = "Scroll" + return t +end + +export type Axis = "X" | "Y" + +export type MouseButton = "Left" | "Middle" | "Right" + +export type TrackVisibilityTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + poll_for_feedback: boolean?, + area: TrackArea, +} +--- Creates a TrackVisibilityTarget value. +function module.TrackVisibilityTarget(value: TrackVisibilityTarget): TrackVisibilityTarget + return value +end + +export type TrackSoloStateTarget = { + unit: TargetUnit?, + track: TrackDescriptor?, + exclusivity: TrackExclusivity?, + behavior: SoloBehavior?, + use_track_grouping: boolean?, + use_selection_ganging: boolean?, +} +--- Creates a TrackSoloStateTarget value. +function module.TrackSoloStateTarget(value: TrackSoloStateTarget): TrackSoloStateTarget + return value +end + +export type BrowseFxChainTarget = { + unit: TargetUnit?, + chain: FxChainDescriptor, + display_kind: FxDisplayKind?, +} +--- Creates a BrowseFxChainTarget value. +function module.BrowseFxChainTarget(value: BrowseFxChainTarget): BrowseFxChainTarget + return value +end + +export type FxOnOffStateTarget = { + unit: TargetUnit?, + fx: FxDescriptor?, +} +--- Creates a FxOnOffStateTarget value. +function module.FxOnOffStateTarget(value: FxOnOffStateTarget): FxOnOffStateTarget + return value +end + +export type FxOnlineOfflineStateTarget = { + unit: TargetUnit?, + fx: FxDescriptor?, +} +--- Creates a FxOnlineOfflineStateTarget value. +function module.FxOnlineOfflineStateTarget(value: FxOnlineOfflineStateTarget): FxOnlineOfflineStateTarget + return value +end + +export type LoadFxSnapshotTarget = { + unit: TargetUnit?, + fx: FxDescriptor?, + snapshot: FxSnapshot?, +} +--- Creates a LoadFxSnapshotTarget value. +function module.LoadFxSnapshotTarget(value: LoadFxSnapshotTarget): LoadFxSnapshotTarget + return value +end + +export type BrowseFxPresetsTarget = { + unit: TargetUnit?, + fx: FxDescriptor?, +} +--- Creates a BrowseFxPresetsTarget value. +function module.BrowseFxPresetsTarget(value: BrowseFxPresetsTarget): BrowseFxPresetsTarget + return value +end + +export type FxToolTarget = { + unit: TargetUnit?, + fx: FxDescriptor?, + action: FxToolAction?, + instance_tags: { string }?, +} +--- Creates a FxToolTarget value. +function module.FxToolTarget(value: FxToolTarget): FxToolTarget + return value +end + +export type FxToolAction = "DoNothing" | "SetAsUnitFx" | "PinAsUnitFx" + +export type FxVisibilityTarget = { + unit: TargetUnit?, + fx: FxDescriptor?, + display_kind: FxDisplayKind?, +} +--- Creates a FxVisibilityTarget value. +function module.FxVisibilityTarget(value: FxVisibilityTarget): FxVisibilityTarget + return value +end + +export type FxParameterValueTarget = { + unit: TargetUnit?, + parameter: FxParameterDescriptor, + poll_for_feedback: boolean?, + retrigger: boolean?, + real_time: boolean?, +} +--- Creates a FxParameterValueTarget value. +function module.FxParameterValueTarget(value: FxParameterValueTarget): FxParameterValueTarget + return value +end + +export type CompartmentParameterValueTarget = { + unit: TargetUnit?, + parameter: CompartmentParameterDescriptor, +} +--- Creates a CompartmentParameterValueTarget value. +function module.CompartmentParameterValueTarget(value: CompartmentParameterValueTarget): CompartmentParameterValueTarget + return value +end + +export type FxParameterAutomationTouchStateTarget = { + unit: TargetUnit?, + parameter: FxParameterDescriptor, +} +--- Creates a FxParameterAutomationTouchStateTarget value. +function module.FxParameterAutomationTouchStateTarget( + value: FxParameterAutomationTouchStateTarget +): FxParameterAutomationTouchStateTarget + return value +end + +export type RouteAutomationModeTarget = { + unit: TargetUnit?, + route: RouteDescriptor, + mode: AutomationMode, + poll_for_feedback: boolean?, +} +--- Creates a RouteAutomationModeTarget value. +function module.RouteAutomationModeTarget(value: RouteAutomationModeTarget): RouteAutomationModeTarget + return value +end + +export type RouteMonoStateTarget = { + unit: TargetUnit?, + route: RouteDescriptor, + poll_for_feedback: boolean?, +} +--- Creates a RouteMonoStateTarget value. +function module.RouteMonoStateTarget(value: RouteMonoStateTarget): RouteMonoStateTarget + return value +end + +export type RouteMuteStateTarget = { + unit: TargetUnit?, + route: RouteDescriptor, + poll_for_feedback: boolean?, +} +--- Creates a RouteMuteStateTarget value. +function module.RouteMuteStateTarget(value: RouteMuteStateTarget): RouteMuteStateTarget + return value +end + +export type RoutePhaseTarget = { + unit: TargetUnit?, + route: RouteDescriptor, + poll_for_feedback: boolean?, +} +--- Creates a RoutePhaseTarget value. +function module.RoutePhaseTarget(value: RoutePhaseTarget): RoutePhaseTarget + return value +end + +export type RoutePanTarget = { + unit: TargetUnit?, + route: RouteDescriptor, +} +--- Creates a RoutePanTarget value. +function module.RoutePanTarget(value: RoutePanTarget): RoutePanTarget + return value +end + +export type RouteVolumeTarget = { + unit: TargetUnit?, + route: RouteDescriptor, +} +--- Creates a RouteVolumeTarget value. +function module.RouteVolumeTarget(value: RouteVolumeTarget): RouteVolumeTarget + return value +end + +export type RouteTouchStateTarget = { + unit: TargetUnit?, + route: RouteDescriptor, + touched_parameter: TouchedRouteParameter, +} +--- Creates a RouteTouchStateTarget value. +function module.RouteTouchStateTarget(value: RouteTouchStateTarget): RouteTouchStateTarget + return value +end + +export type PlaytimeSlotTransportActionTarget = { + unit: TargetUnit?, + slot: PlaytimeSlotDescriptor, + action: PlaytimeSlotTransportAction, + stop_column_if_slot_empty: boolean?, +} +--- Creates a PlaytimeSlotTransportActionTarget value. +function module.PlaytimeSlotTransportActionTarget( + value: PlaytimeSlotTransportActionTarget +): PlaytimeSlotTransportActionTarget + return value +end + +export type PlaytimeColumnActionTarget = { + unit: TargetUnit?, + column: PlaytimeColumnDescriptor, + action: PlaytimeColumnAction, +} +--- Creates a PlaytimeColumnActionTarget value. +function module.PlaytimeColumnActionTarget(value: PlaytimeColumnActionTarget): PlaytimeColumnActionTarget + return value +end + +export type PlaytimeRowActionTarget = { + unit: TargetUnit?, + row: PlaytimeRowDescriptor, + action: PlaytimeRowAction, +} +--- Creates a PlaytimeRowActionTarget value. +function module.PlaytimeRowActionTarget(value: PlaytimeRowActionTarget): PlaytimeRowActionTarget + return value +end + +export type PlaytimeMatrixActionTarget = { + unit: TargetUnit?, + action: PlaytimeMatrixAction, +} +--- Creates a PlaytimeMatrixActionTarget value. +function module.PlaytimeMatrixActionTarget(value: PlaytimeMatrixActionTarget): PlaytimeMatrixActionTarget + return value +end + +export type PlaytimeControlUnitScrollTarget = { + unit: TargetUnit?, + axis: Axis, +} +--- Creates a PlaytimeControlUnitScrollTarget value. +function module.PlaytimeControlUnitScrollTarget(value: PlaytimeControlUnitScrollTarget): PlaytimeControlUnitScrollTarget + return value +end + +export type PlaytimeBrowseCellsTarget = { + unit: TargetUnit?, + axis: Axis, +} +--- Creates a PlaytimeBrowseCellsTarget value. +function module.PlaytimeBrowseCellsTarget(value: PlaytimeBrowseCellsTarget): PlaytimeBrowseCellsTarget + return value +end + +export type PlaytimeSlotSeekTarget = { + unit: TargetUnit?, + slot: PlaytimeSlotDescriptor, + feedback_resolution: FeedbackResolution?, +} +--- Creates a PlaytimeSlotSeekTarget value. +function module.PlaytimeSlotSeekTarget(value: PlaytimeSlotSeekTarget): PlaytimeSlotSeekTarget + return value +end + +export type PlaytimeSlotVolumeTarget = { + unit: TargetUnit?, + slot: PlaytimeSlotDescriptor, +} +--- Creates a PlaytimeSlotVolumeTarget value. +function module.PlaytimeSlotVolumeTarget(value: PlaytimeSlotVolumeTarget): PlaytimeSlotVolumeTarget + return value +end + +export type PlaytimeSlotManagementActionTarget = { + unit: TargetUnit?, + slot: PlaytimeSlotDescriptor, + action: PlaytimeSlotManagementAction, +} +--- Creates a PlaytimeSlotManagementActionTarget value. +function module.PlaytimeSlotManagementActionTarget( + value: PlaytimeSlotManagementActionTarget +): PlaytimeSlotManagementActionTarget + return value +end + +export type PlaytimeSlotManagementAction = + "ClearSlot" + | "FillSlotWithSelectedItem" + | "EditClip" + | "CopyOrPasteClip" + | "DoubleClipSectionLength" + | "HalveClipSectionLength" + | "QuantizationOnOffState" + | "Duplicate" + | "Activate" + +export type AdjustClipSectionLengthAction = { + factor: number, +} +--- Creates a AdjustClipSectionLengthAction value. +function module.AdjustClipSectionLengthAction(value: AdjustClipSectionLengthAction): AdjustClipSectionLengthAction + return value +end + +export type SendMidiTarget = { + unit: TargetUnit?, + message: string?, + destination: SendMidiDestination?, +} +--- Creates a SendMidiTarget value. +function module.SendMidiTarget(value: SendMidiTarget): SendMidiTarget + return value +end + +export type DummyTarget = { + unit: TargetUnit?, +} +--- Creates a DummyTarget value. +function module.DummyTarget(value: DummyTarget): DummyTarget + return value +end + +export type SendOscTarget = { + unit: TargetUnit?, + address: string?, + argument: OscArgument?, + destination: OscDestination?, +} +--- Creates a SendOscTarget value. +function module.SendOscTarget(value: SendOscTarget): SendOscTarget + return value +end + +export type EnableInstancesTarget = { + unit: TargetUnit?, + tags: { string }?, + exclusivity: InstanceExclusivity?, +} +--- Creates a EnableInstancesTarget value. +function module.EnableInstancesTarget(value: EnableInstancesTarget): EnableInstancesTarget + return value +end + +export type EnableMappingsTarget = { + unit: TargetUnit?, + tags: { string }?, + exclusivity: MappingExclusivity?, +} +--- Creates a EnableMappingsTarget value. +function module.EnableMappingsTarget(value: EnableMappingsTarget): EnableMappingsTarget + return value +end + +export type ModifyMappingTarget = { + unit: TargetUnit?, + session: string?, + mapping: string?, + modification: MappingModification, +} +--- Creates a ModifyMappingTarget value. +function module.ModifyMappingTarget(value: ModifyMappingTarget): ModifyMappingTarget + return value +end + +export type MappingModification_LearnTarget = { + kind: "LearnTarget", + included_targets: { LearnableTargetKind }?, + touch_cause: TargetTouchCause?, +} + +export type MappingModification_SetTargetToLastTouched = { + kind: "SetTargetToLastTouched", + included_targets: { LearnableTargetKind }?, + touch_cause: TargetTouchCause?, +} +export type MappingModification = MappingModification_LearnTarget | MappingModification_SetTargetToLastTouched + +--- A type that represents all possible kinds of MappingModification. +export type MappingModificationKind = "LearnTarget" | "SetTargetToLastTouched" + +--- Helper table to create MappingModification values of different kinds. +module.MappingModification = {} + +--- Creates a MappingModification of kind LearnTarget. +function module.MappingModification.LearnTarget(value: LearnTargetMappingModification): MappingModification_LearnTarget + local t: any = table.clone(value) + t.kind = "LearnTarget" + return t +end + +--- Creates a MappingModification of kind SetTargetToLastTouched. +function module.MappingModification.SetTargetToLastTouched( + value: SetTargetToLastTouchedMappingModification +): MappingModification_SetTargetToLastTouched + local t: any = table.clone(value) + t.kind = "SetTargetToLastTouched" + return t +end + +export type LearnTargetMappingModification = { + included_targets: { LearnableTargetKind }?, + touch_cause: TargetTouchCause?, +} +--- Creates a LearnTargetMappingModification value. +function module.LearnTargetMappingModification(value: LearnTargetMappingModification): LearnTargetMappingModification + return value +end + +export type SetTargetToLastTouchedMappingModification = { + included_targets: { LearnableTargetKind }?, + touch_cause: TargetTouchCause?, +} +--- Creates a SetTargetToLastTouchedMappingModification value. +function module.SetTargetToLastTouchedMappingModification( + value: SetTargetToLastTouchedMappingModification +): SetTargetToLastTouchedMappingModification + return value +end + +export type LoadMappingSnapshotTarget = { + unit: TargetUnit?, + tags: { string }?, + active_mappings_only: boolean?, + snapshot: MappingSnapshotDescForLoad?, + default_value: TargetValue?, +} +--- Creates a LoadMappingSnapshotTarget value. +function module.LoadMappingSnapshotTarget(value: LoadMappingSnapshotTarget): LoadMappingSnapshotTarget + return value +end + +export type TakeMappingSnapshotTarget = { + unit: TargetUnit?, + tags: { string }?, + active_mappings_only: boolean?, + snapshot: BackwardCompatibleMappingSnapshotDescForTake, +} +--- Creates a TakeMappingSnapshotTarget value. +function module.TakeMappingSnapshotTarget(value: TakeMappingSnapshotTarget): TakeMappingSnapshotTarget + return value +end + +export type BackwardCompatibleMappingSnapshotDescForTake_Old = string + +export type BackwardCompatibleMappingSnapshotDescForTake_New = MappingSnapshotDescForTake +export type BackwardCompatibleMappingSnapshotDescForTake = + BackwardCompatibleMappingSnapshotDescForTake_Old + | BackwardCompatibleMappingSnapshotDescForTake_New + +--- Creates a BackwardCompatibleMappingSnapshotDescForTake value. +function module.BackwardCompatibleMappingSnapshotDescForTake( + value: BackwardCompatibleMappingSnapshotDescForTake +): BackwardCompatibleMappingSnapshotDescForTake + return value +end + +export type MappingSnapshotDescForLoad_Initial = { kind: "Initial" } + +export type MappingSnapshotDescForLoad_ById = { kind: "ById", id: string } +export type MappingSnapshotDescForLoad = MappingSnapshotDescForLoad_Initial | MappingSnapshotDescForLoad_ById + +--- A type that represents all possible kinds of MappingSnapshotDescForLoad. +export type MappingSnapshotDescForLoadKind = "Initial" | "ById" + +--- Helper table to create MappingSnapshotDescForLoad values of different kinds. +module.MappingSnapshotDescForLoad = {} + +--- Creates a MappingSnapshotDescForLoad of kind Initial. +function module.MappingSnapshotDescForLoad.Initial(): MappingSnapshotDescForLoad_Initial + return { + kind = "Initial", + } +end + +--- Creates a MappingSnapshotDescForLoad of kind ById. +function module.MappingSnapshotDescForLoad.ById(value: { id: string }): MappingSnapshotDescForLoad_ById + local t: any = table.clone(value) + t.kind = "ById" + return t +end + +export type MappingSnapshotDescForTake_LastLoaded = { kind: "LastLoaded" } + +export type MappingSnapshotDescForTake_ById = { kind: "ById", id: string } +export type MappingSnapshotDescForTake = MappingSnapshotDescForTake_LastLoaded | MappingSnapshotDescForTake_ById + +--- A type that represents all possible kinds of MappingSnapshotDescForTake. +export type MappingSnapshotDescForTakeKind = "LastLoaded" | "ById" + +--- Helper table to create MappingSnapshotDescForTake values of different kinds. +module.MappingSnapshotDescForTake = {} + +--- Creates a MappingSnapshotDescForTake of kind LastLoaded. +function module.MappingSnapshotDescForTake.LastLoaded(): MappingSnapshotDescForTake_LastLoaded + return { + kind = "LastLoaded", + } +end + +--- Creates a MappingSnapshotDescForTake of kind ById. +function module.MappingSnapshotDescForTake.ById(value: { id: string }): MappingSnapshotDescForTake_ById + local t: any = table.clone(value) + t.kind = "ById" + return t +end + +export type BrowseGroupMappingsTarget = { + unit: TargetUnit?, + exclusivity: GroupMappingExclusivity?, + group: string?, +} +--- Creates a BrowseGroupMappingsTarget value. +function module.BrowseGroupMappingsTarget(value: BrowseGroupMappingsTarget): BrowseGroupMappingsTarget + return value +end + +export type BrowsePotFilterItemsTarget = { + unit: TargetUnit?, + item_kind: PotFilterKind?, +} +--- Creates a BrowsePotFilterItemsTarget value. +function module.BrowsePotFilterItemsTarget(value: BrowsePotFilterItemsTarget): BrowsePotFilterItemsTarget + return value +end + +export type BrowsePotPresetsTarget = { + unit: TargetUnit?, +} +--- Creates a BrowsePotPresetsTarget value. +function module.BrowsePotPresetsTarget(value: BrowsePotPresetsTarget): BrowsePotPresetsTarget + return value +end + +export type PreviewPotPresetTarget = { + unit: TargetUnit?, +} +--- Creates a PreviewPotPresetTarget value. +function module.PreviewPotPresetTarget(value: PreviewPotPresetTarget): PreviewPotPresetTarget + return value +end + +export type LoadPotPresetTarget = { + unit: TargetUnit?, + fx: FxDescriptor?, +} +--- Creates a LoadPotPresetTarget value. +function module.LoadPotPresetTarget(value: LoadPotPresetTarget): LoadPotPresetTarget + return value +end + +export type StreamDeckBrightnessTarget = { + unit: TargetUnit?, +} +--- Creates a StreamDeckBrightnessTarget value. +function module.StreamDeckBrightnessTarget(value: StreamDeckBrightnessTarget): StreamDeckBrightnessTarget + return value +end + +export type PotFilterKind = + "Database" + | "IsAvailable" + | "IsSupported" + | "IsUser" + | "ProductKind" + | "IsFavorite" + | "Project" + | "Bank" + | "SubBank" + | "Category" + | "SubCategory" + | "Mode" + | "HasPreview" + +export type VirtualTarget = { + id: VirtualControlElementId, + character: VirtualControlElementCharacter?, + learnable: boolean?, +} +--- Creates a VirtualTarget value. +function module.VirtualTarget(value: VirtualTarget): VirtualTarget + return value +end + +export type AutomationModeOverride_Bypass = { kind: "Bypass" } + +export type AutomationModeOverride_Mode = { kind: "Mode", mode: AutomationMode } +export type AutomationModeOverride = AutomationModeOverride_Bypass | AutomationModeOverride_Mode + +--- A type that represents all possible kinds of AutomationModeOverride. +export type AutomationModeOverrideKind = "Bypass" | "Mode" + +--- Helper table to create AutomationModeOverride values of different kinds. +module.AutomationModeOverride = {} + +--- Creates a AutomationModeOverride of kind Bypass. +function module.AutomationModeOverride.Bypass(): AutomationModeOverride_Bypass + return { + kind = "Bypass", + } +end + +--- Creates a AutomationModeOverride of kind Mode. +function module.AutomationModeOverride.Mode(value: { mode: AutomationMode }): AutomationModeOverride_Mode + local t: any = table.clone(value) + t.kind = "Mode" + return t +end + +export type AutomationMode = "TrimRead" | "Read" | "Touch" | "Write" | "Latch" | "LatchPreview" + +export type MonitoringMode = "Off" | "Normal" | "TapeStyle" + +export type TransportAction = "PlayStop" | "PlayPause" | "Stop" | "Pause" | "Record" | "Repeat" + +export type PlaytimeSlotTransportAction = + "Trigger" + | "PlayStop" + | "PlayPause" + | "Stop" + | "Pause" + | "RecordStop" + | "RecordPlayStop" + | "OverdubPlay" + | "Looped" + +export type PlaytimeColumnAction = "Stop" | "ArmState" | "ArmStateExclusive" | "Activate" + +export type PlaytimeRowAction = "PlayScene" | "BuildScene" | "ClearScene" | "CopyOrPasteScene" | "Activate" + +export type PlaytimeMatrixAction = + "Stop" + | "Undo" + | "Redo" + | "BuildScene" + | "SetRecordLengthMode" + | "SetCustomRecordLengthInBars" + | "ClickOnOffState" + | "MidiAutoQuantizationOnOffState" + | "SmartRecord" + | "StartOrStopPlayback" + | "SilenceModeOnOffState" + | "Panic" + | "SequencerRecordOnOffState" + | "SequencerPlayOnOffState" + | "TapTempo" + +export type AnyOnParameter = "TrackSolo" | "TrackMute" | "TrackArm" | "TrackSelection" + +export type ActionInvocationKind = "Trigger" | "Absolute14Bit" | "Absolute7Bit" | "Relative" + +export type ReaperCommand_Id = number + +export type ReaperCommand_Name = string +export type ReaperCommand = ReaperCommand_Id | ReaperCommand_Name + +--- Creates a ReaperCommand value. +function module.ReaperCommand(value: ReaperCommand): ReaperCommand + return value +end + +--- Resolves to the track on which this ReaLearn instance is installed. +--- +--- Doesn't make sense if ReaLearn is installed on the monitoring FX chain. +export type TrackDescriptor_This = { address: "This", track_must_be_selected: boolean? } + +export type TrackDescriptor_Master = { address: "Master", track_must_be_selected: boolean? } + +export type TrackDescriptor_Instance = { address: "Instance", track_must_be_selected: boolean? } + +export type TrackDescriptor_Selected = { address: "Selected", allow_multiple: boolean? } + +export type TrackDescriptor_Dynamic = { + address: "Dynamic", + track_must_be_selected: boolean?, + expression: string, + scope: TrackScope?, +} + +export type TrackDescriptor_ById = { address: "ById", track_must_be_selected: boolean?, id: string? } + +export type TrackDescriptor_ByIndex = { + address: "ByIndex", + track_must_be_selected: boolean?, + index: number, + scope: TrackScope?, +} + +export type TrackDescriptor_ByName = { + address: "ByName", + track_must_be_selected: boolean?, + name: string, + allow_multiple: boolean?, +} + +export type TrackDescriptor_FromClipColumn = { + address: "FromClipColumn", + track_must_be_selected: boolean?, + column: PlaytimeColumnDescriptor, + context: ClipColumnTrackContext, +} +export type TrackDescriptor = + TrackDescriptor_This + | TrackDescriptor_Master + | TrackDescriptor_Instance + | TrackDescriptor_Selected + | TrackDescriptor_Dynamic + | TrackDescriptor_ById + | TrackDescriptor_ByIndex + | TrackDescriptor_ByName + | TrackDescriptor_FromClipColumn + +--- A type that represents all possible kinds of TrackDescriptor. +export type TrackDescriptorAddress = + "This" + | "Master" + | "Instance" + | "Selected" + | "Dynamic" + | "ById" + | "ByIndex" + | "ByName" + | "FromClipColumn" + +--- Helper table to create TrackDescriptor values of different kinds. +module.TrackDescriptor = {} + +--- Creates a TrackDescriptor of kind This. +--- Resolves to the track on which this ReaLearn instance is installed. +--- +--- Doesn't make sense if ReaLearn is installed on the monitoring FX chain. +function module.TrackDescriptor.This(value: { track_must_be_selected: boolean? }): TrackDescriptor_This + local t: any = table.clone(value) + t.address = "This" + return t +end + +--- Creates a TrackDescriptor of kind Master. +function module.TrackDescriptor.Master(value: { track_must_be_selected: boolean? }): TrackDescriptor_Master + local t: any = table.clone(value) + t.address = "Master" + return t +end + +--- Creates a TrackDescriptor of kind Instance. +function module.TrackDescriptor.Instance(value: { track_must_be_selected: boolean? }): TrackDescriptor_Instance + local t: any = table.clone(value) + t.address = "Instance" + return t +end + +--- Creates a TrackDescriptor of kind Selected. +function module.TrackDescriptor.Selected(value: { allow_multiple: boolean? }): TrackDescriptor_Selected + local t: any = table.clone(value) + t.address = "Selected" + return t +end + +--- Creates a TrackDescriptor of kind Dynamic. +function module.TrackDescriptor.Dynamic( + value: { track_must_be_selected: boolean?, expression: string, scope: TrackScope? } +): TrackDescriptor_Dynamic + local t: any = table.clone(value) + t.address = "Dynamic" + return t +end + +--- Creates a TrackDescriptor of kind ById. +function module.TrackDescriptor.ById(value: { track_must_be_selected: boolean?, id: string? }): TrackDescriptor_ById + local t: any = table.clone(value) + t.address = "ById" + return t +end + +--- Creates a TrackDescriptor of kind ByIndex. +function module.TrackDescriptor.ByIndex( + value: { track_must_be_selected: boolean?, index: number, scope: TrackScope? } +): TrackDescriptor_ByIndex + local t: any = table.clone(value) + t.address = "ByIndex" + return t +end + +--- Creates a TrackDescriptor of kind ByName. +function module.TrackDescriptor.ByName( + value: { track_must_be_selected: boolean?, name: string, allow_multiple: boolean? } +): TrackDescriptor_ByName + local t: any = table.clone(value) + t.address = "ByName" + return t +end + +--- Creates a TrackDescriptor of kind FromClipColumn. +function module.TrackDescriptor.FromClipColumn(value: { + track_must_be_selected: boolean?, + column: PlaytimeColumnDescriptor, + context: ClipColumnTrackContext, +}): TrackDescriptor_FromClipColumn + local t: any = table.clone(value) + t.address = "FromClipColumn" + return t +end + +export type ClipColumnTrackContext = "Playback" | "Recording" + +export type TrackDescriptorCommons = { + track_must_be_selected: boolean?, +} +--- Creates a TrackDescriptorCommons value. +function module.TrackDescriptorCommons(value: TrackDescriptorCommons): TrackDescriptorCommons + return value +end + +export type FeedbackResolution = "Beat" | "High" + +export type TrackExclusivity = "WithinProject" | "WithinFolder" | "WithinProjectOnOnly" | "WithinFolderOnOnly" + +export type InstanceExclusivity = "Exclusive" | "ExclusiveOnOnly" + +export type MappingExclusivity = "Exclusive" | "ExclusiveOnOnly" + +export type GroupMappingExclusivity = "Exclusive" + +export type TouchedTrackParameter = "Volume" | "Pan" | "Width" + +export type TouchedRouteParameter = "Volume" | "Pan" + +export type TrackArea = "Tcp" | "Mcp" + +export type SoloBehavior = "InPlace" | "IgnoreRouting" | "ReaperPreference" + +export type SeekBehavior = "Immediate" | "Smooth" | "ReaperPreference" + +export type BookmarkDescriptor_Marker = { kind: "Marker", id: number } | { kind: "Marker", index: number } + +export type BookmarkDescriptor_Region = { kind: "Region", id: number } | { kind: "Region", index: number } +export type BookmarkDescriptor = BookmarkDescriptor_Marker | BookmarkDescriptor_Region + +--- A type that represents all possible kinds of BookmarkDescriptor. +export type BookmarkDescriptorKind = "Marker" | "Region" + +--- Helper table to create BookmarkDescriptor values of different kinds. +module.BookmarkDescriptor = {} + +--- Creates a BookmarkDescriptor of kind Marker. +function module.BookmarkDescriptor.Marker(value: BookmarkRef): BookmarkDescriptor_Marker + local t: any = table.clone(value) + t.kind = "Marker" + return t +end + +--- Creates a BookmarkDescriptor of kind Region. +function module.BookmarkDescriptor.Region(value: BookmarkRef): BookmarkDescriptor_Region + local t: any = table.clone(value) + t.kind = "Region" + return t +end + +export type BookmarkRef_ById = { id: number } + +export type BookmarkRef_ByIndex = { index: number } +export type BookmarkRef = BookmarkRef_ById | BookmarkRef_ByIndex + +--- Creates a BookmarkRef value. +function module.BookmarkRef(value: BookmarkRef): BookmarkRef + return value +end + +export type FxDescriptorCommons = { + fx_must_have_focus: boolean?, +} +--- Creates a FxDescriptorCommons value. +function module.FxDescriptorCommons(value: FxDescriptorCommons): FxDescriptorCommons + return value +end + +export type FxDescriptor_This = { address: "This", fx_must_have_focus: boolean? } + +export type FxDescriptor_Focused = { address: "Focused" } + +export type FxDescriptor_Instance = { address: "Instance", fx_must_have_focus: boolean? } + +export type FxDescriptor_Dynamic = { + address: "Dynamic", + fx_must_have_focus: boolean?, + chain: FxChainDescriptor, + expression: string, +} + +export type FxDescriptor_ById = { address: "ById", fx_must_have_focus: boolean?, chain: FxChainDescriptor, id: string? } + +export type FxDescriptor_ByIndex = { + address: "ByIndex", + fx_must_have_focus: boolean?, + chain: FxChainDescriptor, + index: number, +} + +export type FxDescriptor_ByName = { + address: "ByName", + fx_must_have_focus: boolean?, + chain: FxChainDescriptor, + name: string, + allow_multiple: boolean?, +} +export type FxDescriptor = + FxDescriptor_This + | FxDescriptor_Focused + | FxDescriptor_Instance + | FxDescriptor_Dynamic + | FxDescriptor_ById + | FxDescriptor_ByIndex + | FxDescriptor_ByName + +--- A type that represents all possible kinds of FxDescriptor. +export type FxDescriptorAddress = "This" | "Focused" | "Instance" | "Dynamic" | "ById" | "ByIndex" | "ByName" + +--- Helper table to create FxDescriptor values of different kinds. +module.FxDescriptor = {} + +--- Creates a FxDescriptor of kind This. +function module.FxDescriptor.This(value: { fx_must_have_focus: boolean? }): FxDescriptor_This + local t: any = table.clone(value) + t.address = "This" + return t +end + +--- Creates a FxDescriptor of kind Focused. +function module.FxDescriptor.Focused(): FxDescriptor_Focused + return { + address = "Focused", + } +end + +--- Creates a FxDescriptor of kind Instance. +function module.FxDescriptor.Instance(value: { fx_must_have_focus: boolean? }): FxDescriptor_Instance + local t: any = table.clone(value) + t.address = "Instance" + return t +end + +--- Creates a FxDescriptor of kind Dynamic. +function module.FxDescriptor.Dynamic( + value: { fx_must_have_focus: boolean?, chain: FxChainDescriptor, expression: string } +): FxDescriptor_Dynamic + local t: any = table.clone(value) + t.address = "Dynamic" + return t +end + +--- Creates a FxDescriptor of kind ById. +function module.FxDescriptor.ById( + value: { fx_must_have_focus: boolean?, chain: FxChainDescriptor, id: string? } +): FxDescriptor_ById + local t: any = table.clone(value) + t.address = "ById" + return t +end + +--- Creates a FxDescriptor of kind ByIndex. +function module.FxDescriptor.ByIndex( + value: { fx_must_have_focus: boolean?, chain: FxChainDescriptor, index: number } +): FxDescriptor_ByIndex + local t: any = table.clone(value) + t.address = "ByIndex" + return t +end + +--- Creates a FxDescriptor of kind ByName. +function module.FxDescriptor.ByName(value: { + fx_must_have_focus: boolean?, + chain: FxChainDescriptor, + name: string, + allow_multiple: boolean?, +}): FxDescriptor_ByName + local t: any = table.clone(value) + t.address = "ByName" + return t +end + +export type FxChainDescriptor_Track = { address: "Track", track: TrackDescriptor?, chain: TrackFxChain? } +export type FxChainDescriptor = FxChainDescriptor_Track + +--- A type that represents all possible kinds of FxChainDescriptor. +export type FxChainDescriptorAddress = "Track" + +--- Helper table to create FxChainDescriptor values of different kinds. +module.FxChainDescriptor = {} + +--- Creates a FxChainDescriptor of kind Track. +function module.FxChainDescriptor.Track( + value: { track: TrackDescriptor?, chain: TrackFxChain? } +): FxChainDescriptor_Track + local t: any = table.clone(value) + t.address = "Track" + return t +end + +export type TrackFxChain = "Normal" | "Input" + +export type FxDisplayKind = "FloatingWindow" | "Chain" + +export type FxSnapshot = { + fx_kind: string?, + fx_name: string?, + preset_name: string?, + content: FxSnapshotContent, +} +--- Creates a FxSnapshot value. +function module.FxSnapshot(value: FxSnapshot): FxSnapshot + return value +end + +export type FxSnapshotContent_Chunk = { kind: "Chunk", chunk: string } +export type FxSnapshotContent = FxSnapshotContent_Chunk + +--- A type that represents all possible kinds of FxSnapshotContent. +export type FxSnapshotContentKind = "Chunk" + +--- Helper table to create FxSnapshotContent values of different kinds. +module.FxSnapshotContent = {} + +--- Creates a FxSnapshotContent of kind Chunk. +function module.FxSnapshotContent.Chunk(value: { chunk: string }): FxSnapshotContent_Chunk + local t: any = table.clone(value) + t.kind = "Chunk" + return t +end + +export type FxParameterDescriptor_Dynamic = { address: "Dynamic", fx: FxDescriptor?, expression: string } + +export type FxParameterDescriptor_ById = { address: "ById", fx: FxDescriptor?, index: number } + +export type FxParameterDescriptor_ByIndex = { address: "ByIndex", fx: FxDescriptor?, index: number } + +export type FxParameterDescriptor_ByName = { address: "ByName", fx: FxDescriptor?, name: string } +export type FxParameterDescriptor = + FxParameterDescriptor_Dynamic + | FxParameterDescriptor_ById + | FxParameterDescriptor_ByIndex + | FxParameterDescriptor_ByName + +--- A type that represents all possible kinds of FxParameterDescriptor. +export type FxParameterDescriptorAddress = "Dynamic" | "ById" | "ByIndex" | "ByName" + +--- Helper table to create FxParameterDescriptor values of different kinds. +module.FxParameterDescriptor = {} + +--- Creates a FxParameterDescriptor of kind Dynamic. +function module.FxParameterDescriptor.Dynamic( + value: { fx: FxDescriptor?, expression: string } +): FxParameterDescriptor_Dynamic + local t: any = table.clone(value) + t.address = "Dynamic" + return t +end + +--- Creates a FxParameterDescriptor of kind ById. +function module.FxParameterDescriptor.ById(value: { fx: FxDescriptor?, index: number }): FxParameterDescriptor_ById + local t: any = table.clone(value) + t.address = "ById" + return t +end + +--- Creates a FxParameterDescriptor of kind ByIndex. +function module.FxParameterDescriptor.ByIndex( + value: { fx: FxDescriptor?, index: number } +): FxParameterDescriptor_ByIndex + local t: any = table.clone(value) + t.address = "ByIndex" + return t +end + +--- Creates a FxParameterDescriptor of kind ByName. +function module.FxParameterDescriptor.ByName(value: { fx: FxDescriptor?, name: string }): FxParameterDescriptor_ByName + local t: any = table.clone(value) + t.address = "ByName" + return t +end + +export type CompartmentParameterDescriptor_ById = { address: "ById", index: number } +export type CompartmentParameterDescriptor = CompartmentParameterDescriptor_ById + +--- A type that represents all possible kinds of CompartmentParameterDescriptor. +export type CompartmentParameterDescriptorAddress = "ById" + +--- Helper table to create CompartmentParameterDescriptor values of different kinds. +module.CompartmentParameterDescriptor = {} + +--- Creates a CompartmentParameterDescriptor of kind ById. +function module.CompartmentParameterDescriptor.ById(value: { index: number }): CompartmentParameterDescriptor_ById + local t: any = table.clone(value) + t.address = "ById" + return t +end + +export type RouteDescriptorCommons = { + track: TrackDescriptor?, + route_kind: TrackRouteKind?, +} +--- Creates a RouteDescriptorCommons value. +function module.RouteDescriptorCommons(value: RouteDescriptorCommons): RouteDescriptorCommons + return value +end + +export type RouteDescriptor_Dynamic = { + address: "Dynamic", + track: TrackDescriptor?, + route_kind: TrackRouteKind?, + expression: string, +} + +export type RouteDescriptor_ById = { address: "ById", track: TrackDescriptor?, route_kind: TrackRouteKind?, id: string? } + +export type RouteDescriptor_ByIndex = { + address: "ByIndex", + track: TrackDescriptor?, + route_kind: TrackRouteKind?, + index: number, +} + +export type RouteDescriptor_ByName = { + address: "ByName", + track: TrackDescriptor?, + route_kind: TrackRouteKind?, + name: string, +} +export type RouteDescriptor = + RouteDescriptor_Dynamic + | RouteDescriptor_ById + | RouteDescriptor_ByIndex + | RouteDescriptor_ByName + +--- A type that represents all possible kinds of RouteDescriptor. +export type RouteDescriptorAddress = "Dynamic" | "ById" | "ByIndex" | "ByName" + +--- Helper table to create RouteDescriptor values of different kinds. +module.RouteDescriptor = {} + +--- Creates a RouteDescriptor of kind Dynamic. +function module.RouteDescriptor.Dynamic( + value: { track: TrackDescriptor?, route_kind: TrackRouteKind?, expression: string } +): RouteDescriptor_Dynamic + local t: any = table.clone(value) + t.address = "Dynamic" + return t +end + +--- Creates a RouteDescriptor of kind ById. +function module.RouteDescriptor.ById( + value: { track: TrackDescriptor?, route_kind: TrackRouteKind?, id: string? } +): RouteDescriptor_ById + local t: any = table.clone(value) + t.address = "ById" + return t +end + +--- Creates a RouteDescriptor of kind ByIndex. +function module.RouteDescriptor.ByIndex( + value: { track: TrackDescriptor?, route_kind: TrackRouteKind?, index: number } +): RouteDescriptor_ByIndex + local t: any = table.clone(value) + t.address = "ByIndex" + return t +end + +--- Creates a RouteDescriptor of kind ByName. +function module.RouteDescriptor.ByName( + value: { track: TrackDescriptor?, route_kind: TrackRouteKind?, name: string } +): RouteDescriptor_ByName + local t: any = table.clone(value) + t.address = "ByName" + return t +end + +export type TrackRouteKind = "Send" | "Receive" | "HardwareOutput" + +export type PlaytimeSlotDescriptor_Active = { address: "Active" } + +export type PlaytimeSlotDescriptor_ByIndex = { address: "ByIndex", column_index: number, row_index: number } + +export type PlaytimeSlotDescriptor_Dynamic = { address: "Dynamic", column_expression: string, row_expression: string } +export type PlaytimeSlotDescriptor = + PlaytimeSlotDescriptor_Active + | PlaytimeSlotDescriptor_ByIndex + | PlaytimeSlotDescriptor_Dynamic + +--- A type that represents all possible kinds of PlaytimeSlotDescriptor. +export type PlaytimeSlotDescriptorAddress = "Active" | "ByIndex" | "Dynamic" + +--- Helper table to create PlaytimeSlotDescriptor values of different kinds. +module.PlaytimeSlotDescriptor = {} + +--- Creates a PlaytimeSlotDescriptor of kind Active. +function module.PlaytimeSlotDescriptor.Active(): PlaytimeSlotDescriptor_Active + return { + address = "Active", + } +end + +--- Creates a PlaytimeSlotDescriptor of kind ByIndex. +function module.PlaytimeSlotDescriptor.ByIndex(value: playtime.SlotAddress): PlaytimeSlotDescriptor_ByIndex + local t: any = table.clone(value) + t.address = "ByIndex" + return t +end + +--- Creates a PlaytimeSlotDescriptor of kind Dynamic. +function module.PlaytimeSlotDescriptor.Dynamic( + value: { column_expression: string, row_expression: string } +): PlaytimeSlotDescriptor_Dynamic + local t: any = table.clone(value) + t.address = "Dynamic" + return t +end + +export type PlaytimeSlotDescriptorKind = "Active" | "ByIndex" | "Dynamic" + +export type PlaytimeColumnDescriptor_Active = { address: "Active" } + +export type PlaytimeColumnDescriptor_ByIndex = { address: "ByIndex", index: number } + +export type PlaytimeColumnDescriptor_Dynamic = { address: "Dynamic", expression: string } +export type PlaytimeColumnDescriptor = + PlaytimeColumnDescriptor_Active + | PlaytimeColumnDescriptor_ByIndex + | PlaytimeColumnDescriptor_Dynamic + +--- A type that represents all possible kinds of PlaytimeColumnDescriptor. +export type PlaytimeColumnDescriptorAddress = "Active" | "ByIndex" | "Dynamic" + +--- Helper table to create PlaytimeColumnDescriptor values of different kinds. +module.PlaytimeColumnDescriptor = {} + +--- Creates a PlaytimeColumnDescriptor of kind Active. +function module.PlaytimeColumnDescriptor.Active(): PlaytimeColumnDescriptor_Active + return { + address = "Active", + } +end + +--- Creates a PlaytimeColumnDescriptor of kind ByIndex. +function module.PlaytimeColumnDescriptor.ByIndex(value: playtime.ColumnAddress): PlaytimeColumnDescriptor_ByIndex + local t: any = table.clone(value) + t.address = "ByIndex" + return t +end + +--- Creates a PlaytimeColumnDescriptor of kind Dynamic. +function module.PlaytimeColumnDescriptor.Dynamic(value: { expression: string }): PlaytimeColumnDescriptor_Dynamic + local t: any = table.clone(value) + t.address = "Dynamic" + return t +end + +export type PlaytimeColumnDescriptorKind = "Active" | "ByIndex" | "Dynamic" + +export type PlaytimeRowDescriptor_Active = { address: "Active" } + +export type PlaytimeRowDescriptor_ByIndex = { address: "ByIndex", index: number } + +export type PlaytimeRowDescriptor_Dynamic = { address: "Dynamic", expression: string } +export type PlaytimeRowDescriptor = + PlaytimeRowDescriptor_Active + | PlaytimeRowDescriptor_ByIndex + | PlaytimeRowDescriptor_Dynamic + +--- A type that represents all possible kinds of PlaytimeRowDescriptor. +export type PlaytimeRowDescriptorAddress = "Active" | "ByIndex" | "Dynamic" + +--- Helper table to create PlaytimeRowDescriptor values of different kinds. +module.PlaytimeRowDescriptor = {} + +--- Creates a PlaytimeRowDescriptor of kind Active. +function module.PlaytimeRowDescriptor.Active(): PlaytimeRowDescriptor_Active + return { + address = "Active", + } +end + +--- Creates a PlaytimeRowDescriptor of kind ByIndex. +function module.PlaytimeRowDescriptor.ByIndex(value: playtime.RowAddress): PlaytimeRowDescriptor_ByIndex + local t: any = table.clone(value) + t.address = "ByIndex" + return t +end + +--- Creates a PlaytimeRowDescriptor of kind Dynamic. +function module.PlaytimeRowDescriptor.Dynamic(value: { expression: string }): PlaytimeRowDescriptor_Dynamic + local t: any = table.clone(value) + t.address = "Dynamic" + return t +end + +export type PlaytimeRowDescriptorKind = "Active" | "ByIndex" | "Dynamic" + +export type SendMidiDestination_FxOutput = { kind: "FxOutput" } + +export type SendMidiDestination_FeedbackOutput = { kind: "FeedbackOutput" } + +export type SendMidiDestination_InputDevice = { kind: "InputDevice", device_id: number? } +export type SendMidiDestination = + SendMidiDestination_FxOutput + | SendMidiDestination_FeedbackOutput + | SendMidiDestination_InputDevice + +--- A type that represents all possible kinds of SendMidiDestination. +export type SendMidiDestinationKind = "FxOutput" | "FeedbackOutput" | "InputDevice" + +--- Helper table to create SendMidiDestination values of different kinds. +module.SendMidiDestination = {} + +--- Creates a SendMidiDestination of kind FxOutput. +function module.SendMidiDestination.FxOutput(): SendMidiDestination_FxOutput + return { + kind = "FxOutput", + } +end + +--- Creates a SendMidiDestination of kind FeedbackOutput. +function module.SendMidiDestination.FeedbackOutput(): SendMidiDestination_FeedbackOutput + return { + kind = "FeedbackOutput", + } +end + +--- Creates a SendMidiDestination of kind InputDevice. +function module.SendMidiDestination.InputDevice(value: InputDeviceMidiDestination): SendMidiDestination_InputDevice + local t: any = table.clone(value) + t.kind = "InputDevice" + return t +end + +export type InputDeviceMidiDestination = { + device_id: number?, +} +--- Creates a InputDeviceMidiDestination value. +function module.InputDeviceMidiDestination(value: InputDeviceMidiDestination): InputDeviceMidiDestination + return value +end + +export type OscDestination_FeedbackOutput = { kind: "FeedbackOutput" } + +export type OscDestination_Device = { kind: "Device", id: string } +export type OscDestination = OscDestination_FeedbackOutput | OscDestination_Device + +--- A type that represents all possible kinds of OscDestination. +export type OscDestinationKind = "FeedbackOutput" | "Device" + +--- Helper table to create OscDestination values of different kinds. +module.OscDestination = {} + +--- Creates a OscDestination of kind FeedbackOutput. +function module.OscDestination.FeedbackOutput(): OscDestination_FeedbackOutput + return { + kind = "FeedbackOutput", + } +end + +--- Creates a OscDestination of kind Device. +function module.OscDestination.Device(value: { id: string }): OscDestination_Device + local t: any = table.clone(value) + t.kind = "Device" + return t +end + +export type TrackScope = "AllTracks" | "TracksVisibleInTcp" | "TracksVisibleInMcp" + +export type BrowseTracksMode = + "AllTracks" + | "TracksVisibleInTcp" + | "TracksVisibleInTcpAllowTwoSelections" + | "TracksVisibleInMcp" + | "TracksVisibleInMcpAllowTwoSelections" + +export type TargetValue_Unit = { kind: "Unit", value: number } + +export type TargetValue_Discrete = { kind: "Discrete", value: number } +export type TargetValue = TargetValue_Unit | TargetValue_Discrete + +--- A type that represents all possible kinds of TargetValue. +export type TargetValueKind = "Unit" | "Discrete" + +--- Helper table to create TargetValue values of different kinds. +module.TargetValue = {} + +--- Creates a TargetValue of kind Unit. +function module.TargetValue.Unit(value: { value: number }): TargetValue_Unit + local t: any = table.clone(value) + t.kind = "Unit" + return t +end + +--- Creates a TargetValue of kind Discrete. +function module.TargetValue.Discrete(value: { value: number }): TargetValue_Discrete + local t: any = table.clone(value) + t.kind = "Discrete" + return t +end + +return module diff --git a/plugin-reaper-realearn/resources/artwork/fonts/NotoSans-Bold.ttf b/plugin-reaper-realearn/resources/artwork/fonts/NotoSans-Bold.ttf new file mode 100644 index 0000000..506f7d8 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/fonts/NotoSans-Bold.ttf differ diff --git a/plugin-reaper-realearn/resources/artwork/playtime-logo.svg b/plugin-reaper-realearn/resources/artwork/playtime-logo.svg new file mode 100644 index 0000000..6dddc85 --- /dev/null +++ b/plugin-reaper-realearn/resources/artwork/playtime-logo.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TEXT_PLACEHOLDER + + + \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime.png new file mode 100644 index 0000000..c7004e5 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime_2.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime_2.png new file mode 100644 index 0000000..83cdae7 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime_2.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime_custom.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime_custom.png new file mode 100644 index 0000000..7069d17 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/150/toolbar_playtime_custom.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime.png new file mode 100644 index 0000000..13d06bf Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime_2.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime_2.png new file mode 100644 index 0000000..ba58e6e Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime_2.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime_custom.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime_custom.png new file mode 100644 index 0000000..15a1dc4 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/200/toolbar_playtime_custom.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/README.md b/plugin-reaper-realearn/resources/artwork/toolbar_icons/README.md new file mode 100644 index 0000000..5732441 --- /dev/null +++ b/plugin-reaper-realearn/resources/artwork/toolbar_icons/README.md @@ -0,0 +1,3 @@ +# Attention + +The files in this directory are generated! \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime.png new file mode 100644 index 0000000..32e9296 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime_2.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime_2.png new file mode 100644 index 0000000..f836bd7 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime_2.png differ diff --git a/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime_custom.png b/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime_custom.png new file mode 100644 index 0000000..8b85f61 Binary files /dev/null and b/plugin-reaper-realearn/resources/artwork/toolbar_icons/toolbar_playtime_custom.png differ diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/.stylua.toml b/plugin-reaper-realearn/resources/controller-presets/factory/.stylua.toml new file mode 120000 index 0000000..8210ed1 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/.stylua.toml @@ -0,0 +1 @@ +../../api/luau/.stylua.toml \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/.vscode b/plugin-reaper-realearn/resources/controller-presets/factory/.vscode new file mode 120000 index 0000000..e1e7088 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/.vscode @@ -0,0 +1 @@ +../../api/luau/.vscode \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/README.md b/plugin-reaper-realearn/resources/controller-presets/factory/README.md new file mode 120000 index 0000000..91575d6 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/README.md @@ -0,0 +1 @@ +../../api/luau/README.md \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-key-25-mk2.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-key-25-mk2.preset.luau new file mode 100644 index 0000000..799b420 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-key-25-mk2.preset.luau @@ -0,0 +1,1617 @@ +--- name: APC Key 25 mk2 +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC Key 25 mk2 +--- description: | +--- This controller preset exposes all control elements of the APC Key 25 mk2 in a neutral way. It supports +--- ReaLearn color feedback as well as Playtime slot status feedback. +--- midi_identity_pattern: F0 7E ? 06 02 47 4E 00 19 * F7 +--- # This device exposes 2 USB MIDI ports, one for "Keys" and one for "Control". We are +--- # interested in "Control". Both MIDI ports reply with the same device identity. If we +--- # don't provide a MIDI output device pattern, it's possible that ReaLearn's automatic +--- # controller detection feature takes the "Keys" port ... and things wouldn't work! +--- # On Windows, the naming is different, that's why there's also "MIDIOUT2". +--- midi_output_port_patterns: ["macos:*Control*", "windows:*MIDIOUT2*"] +--- provided_schemes: [akai/apc-key-25-mk2, grid] + +--!strict + +-- Configuration + +local resolve_shift = false + +-- Requires + +local preset_runtime = require("preset_runtime") +local realearn = require("realearn") + +-- Define MIDI scripts + +local common_lua = preset_runtime.include_str("akai/apc-mk2-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + +-- Parameters + +local shift_param = realearn.Parameter { + index = 0, + name = "Shift", +} +local parameters: { realearn.Parameter }? = if resolve_shift then { shift_param } else nil + +-- Single buttons + +local function simple_button(id: string, name: string, key_number: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local mappings = { + simple_button("stop-all-clips", "SAC", 81), + simple_button("play", "Play", 91), + simple_button("record", "Rec", 93), +} + +-- Shift +local no_shift_activation_condition: realearn.ActivationCondition? +if resolve_shift then + -- The activation condition reflecting the state that shift is not pressed. + no_shift_activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = false, + }, + }, + } + -- Mapping to make shift button switch to other set of virtual control elements + local shift_mapping = realearn.Mapping { + name = "Shift", + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 98, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = 0, + }, + }, + } + table.insert(mappings, shift_mapping) + -- Alternative set of virtual control elements + local alt_elements = { + { key = 64, id = "cursor-up" }, + { key = 65, id = "cursor-down" }, + { key = 66, id = "cursor-left" }, + { key = 67, id = "cursor-right" }, + { key = 68, id = "volume" }, + { key = 69, id = "pan" }, + { key = 70, id = "sends" }, + { key = 71, id = "device" }, + { key = 82, id = "stop-clip" }, + { key = 83, id = "solo" }, + { key = 84, id = "mute" }, + { key = 85, id = "record-arm" }, + { key = 86, id = "track-select" }, + } + for _, element in ipairs(alt_elements) do + local mapping = realearn.Mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = true, + }, + }, + }, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = element.key, + }, + target = realearn.Target.Virtual { + id = element.id, + character = "Button", + }, + } + table.insert(mappings, mapping) + end +else + no_shift_activation_condition = nil + local mapping = simple_button("shift", "Shift", 98) + table.insert(mappings, mapping) +end + +-- Knobs +for i = 0, 7 do + local human_i = i + 1 + local mapping = realearn.Mapping { + id = `k{human_i}`, + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 48 + i, + character = "Relative3", + }, + target = realearn.Target.Virtual { + id = i, + }, + } + table.insert(mappings, mapping) +end + +-- Clip launch buttons + +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 4 do + local human_row = row + 1 + local key_number_offset = (4 - row) * 8 + local id = `col{human_col}/row{human_row}/pad` + local control_mapping = realearn.Mapping { + id = id, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(key_number_offset + col), + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, control_mapping) + table.insert(mappings, feedback_mapping) + end +end + +-- Clip stop buttons +for col = 0, 7 do + local human_col = col + 1 + local id = `col{human_col}/stop` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 64 + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +-- Scene launch buttons +for row = 0, 4 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 82 + row, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +-- Controller layout + +local companion_data = { + controls = { + { + height = 32, + id = "0aa55949-ea14-404e-b3af-3c8a85ed50c7", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 0, + }, + { + height = 32, + id = "9b34bea6-9eeb-4a9a-850a-8a8294c772da", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 0, + }, + { + height = 32, + id = "bfb2db91-fb28-4b34-9bc4-6b25c18c4534", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 0, + }, + { + height = 32, + id = "817b84a8-675f-4edc-8229-67fc3e9a3400", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 0, + }, + { + height = 32, + id = "1d094852-f5b9-4aca-9972-71bf7b683b23", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 0, + }, + { + height = 32, + id = "7a2fcb32-1e24-4817-a20c-73dbbdd786fc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 0, + }, + { + height = 32, + id = "7ac2b4d1-16b8-43cd-9ebe-56bd375b813f", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 0, + }, + { + height = 32, + id = "42836480-6061-4625-81bf-87dba8d45d35", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 0, + }, + { + height = 32, + id = "fee5321d-d675-47ec-b4b6-d3e49f04ebda", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 64, + }, + { + height = 32, + id = "5760816d-bb92-457e-831c-6ba596d33a91", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 64, + }, + { + height = 32, + id = "cb55a16f-f589-4ee5-a977-a19c7ba5d2b2", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 64, + }, + { + height = 32, + id = "3b755e21-59c4-4766-9c14-2d20641b7bf4", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 64, + }, + { + height = 32, + id = "b6c14173-486f-49f7-87e7-c0fe52bf9898", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 64, + }, + { + height = 32, + id = "18b4dfd6-030e-4ddc-9a2a-c88e7270e950", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 64, + }, + { + height = 32, + id = "baa813e8-7d4b-4ea7-8610-74f124d3db97", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 64, + }, + { + height = 32, + id = "72edace4-890f-446a-b4e8-8d875c1841bc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 64, + }, + { + height = 32, + id = "998c9371-ff24-4954-945d-7c641916500e", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 128, + }, + { + height = 32, + id = "c2fdc571-347c-49da-9d9e-6caa4e8d9fcb", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 128, + }, + { + height = 32, + id = "6b25f0e2-4d79-40ab-be46-57d49cd8d579", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 128, + }, + { + height = 32, + id = "db014d76-4433-463a-ac6f-d38ddb7c364f", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 128, + }, + { + height = 32, + id = "8e39bc23-d51d-45c2-ad98-6a609e175584", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 128, + }, + { + height = 32, + id = "24cf1dbd-c01e-4a00-baac-282a5e4232af", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 128, + }, + { + height = 32, + id = "568c0177-4525-4eee-bfb9-e21ae7c202e0", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 128, + }, + { + height = 32, + id = "2ead47d1-ee75-429f-9cd4-3b5fe989c056", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 128, + }, + { + height = 32, + id = "941d996d-882c-44f3-abc4-3f6617cc04f9", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 192, + }, + { + height = 32, + id = "1ca9b0f7-cded-4f31-adec-3d3cff6c91ca", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 192, + }, + { + height = 32, + id = "a7d71b50-81b8-4261-ad8b-7c3229c09d78", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 192, + }, + { + height = 32, + id = "4a12fbbd-6ba1-43f2-bf5c-9650076bf376", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 192, + }, + { + height = 32, + id = "34b988c8-25e4-4936-8042-7140c7b28dad", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 192, + }, + { + height = 32, + id = "bbd4ceba-63d2-441f-8794-0263af223e5c", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 192, + }, + { + height = 32, + id = "57a49293-806b-4bff-8dfc-e8130aa8cc23", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 192, + }, + { + height = 32, + id = "bcefa1ae-60fe-4e52-b315-645a7b75931e", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 192, + }, + { + height = 32, + id = "a1fc5fa6-bfed-4b0a-8132-aed6a4b14bbc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 256, + }, + { + height = 32, + id = "74538f8c-d272-4ac4-a0e2-fcc6452311b5", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 256, + }, + { + height = 32, + id = "92bdce70-3a17-4d53-ae93-ec6fdfa4acb1", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 256, + }, + { + height = 32, + id = "05ac3407-9209-471e-9b0e-5029396de1cc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 256, + }, + { + height = 32, + id = "b898b127-15e3-4c57-8528-16f5ea7f6af6", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 256, + }, + { + height = 32, + id = "d91b3c22-ff5d-4fd0-b8c1-dacd88b17065", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 256, + }, + { + height = 32, + id = "99d82570-59ff-4109-aabb-01924bdf5067", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 256, + }, + { + height = 32, + id = "161ddee9-7fd6-420d-b9ba-a2714f17c724", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 256, + }, + { + height = 32, + id = "a393009f-655f-4425-9573-8351b151fd3a", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/stop", + }, + shape = "circle", + width = 32, + x = 32, + y = 320, + }, + { + height = 50, + id = "fb3a1723-7a69-457f-871d-5389c2637f4d", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/stop", + }, + shape = "circle", + width = 34, + x = 672, + y = 320, + }, + { + height = 50, + id = "44d09c91-a625-4b31-8e1e-e43d44470adc", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/stop", + }, + shape = "circle", + width = 34, + x = 160, + y = 320, + }, + { + height = 50, + id = "cad2df0b-319c-4c43-88dc-88042b2d6aed", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/stop", + }, + shape = "circle", + width = 34, + x = 288, + y = 320, + }, + { + height = 50, + id = "70fb4d30-33e8-482b-908f-7d8866d5fbec", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/stop", + }, + shape = "circle", + width = 34, + x = 416, + y = 320, + }, + { + height = 50, + id = "e92e54a4-96dd-4ed5-9580-e3a587ef86b0", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/stop", + }, + shape = "circle", + width = 34, + x = 544, + y = 320, + }, + { + height = 50, + id = "1a95adc6-8bb3-4f60-8dfd-7667550b202d", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/stop", + }, + shape = "circle", + width = 34, + x = 800, + y = 320, + }, + { + height = 50, + id = "ee30202c-0ebd-4abc-bd30-5a961480e5b6", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/stop", + }, + shape = "circle", + width = 34, + x = 928, + y = 320, + }, + { + height = 50, + id = "0bdeed18-4641-48bd-b7f1-102363c2d1f5", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row1/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 0, + }, + { + height = 50, + id = "39a81c68-dd64-47f0-9d2f-2c36e004b1d1", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row2/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 64, + }, + { + height = 50, + id = "65b0540d-7173-48b9-9b62-eaf833c73e3f", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row3/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 128, + }, + { + height = 50, + id = "4ceafe75-4521-4f87-aabd-cd84d83e46bf", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row5/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 256, + }, + { + height = 50, + id = "8a590659-5632-4227-832c-7bdcbf6caddd", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row4/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 192, + }, + { + height = 50, + id = "fe67392e-15ac-49d8-bcf2-129a3652effb", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "stop-all-clips", + }, + shape = "circle", + width = 34, + x = 1024, + y = 320, + }, + { + height = 50, + id = "cafb08e0-ba01-4b31-a175-62e4158a42f7", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k1", + }, + shape = "circle", + width = 98, + x = 1120, + y = 0, + }, + { + height = 50, + id = "f2b031ad-1879-4a75-984c-e906f69bd71e", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k2", + }, + shape = "circle", + width = 98, + x = 1280, + y = 0, + }, + { + height = 50, + id = "75667a1a-60c8-40e2-95ff-f1f90cc37e92", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k3", + }, + shape = "circle", + width = 98, + x = 1440, + y = 0, + }, + { + height = 50, + id = "edc1f799-cb0a-4f60-bf8c-b664ab57dc5a", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k4", + }, + shape = "circle", + width = 98, + x = 1600, + y = 0, + }, + { + height = 50, + id = "5573339f-1524-4ba3-9eb8-238702e437ad", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k5", + }, + shape = "circle", + width = 98, + x = 1120, + y = 128, + }, + { + height = 50, + id = "9a594e2e-d2b8-4f47-bddc-6872809a4089", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k6", + }, + shape = "circle", + width = 98, + x = 1280, + y = 128, + }, + { + height = 50, + id = "8b50a75c-c2cd-4f80-9c30-6d4bb9070035", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k7", + }, + shape = "circle", + width = 98, + x = 1440, + y = 128, + }, + { + height = 50, + id = "9301c8e0-1212-414f-a34e-76af972d68d0", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k8", + }, + shape = "circle", + width = 98, + x = 1600, + y = 128, + }, + { + height = 34, + id = "39506af3-b17e-45bb-a148-51ff39b4eeb5", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "play", + }, + shape = "rectangle", + width = 98, + x = 1472, + y = 256, + }, + { + height = 34, + id = "35aa5f43-6b36-46ad-b104-fbe84c37b96b", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "record", + }, + shape = "rectangle", + width = 98, + x = 1600, + y = 256, + }, + { + height = 34, + id = "d72b47cc-f0b4-46c6-9510-fd967f815965", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "shift", + }, + shape = "rectangle", + width = 98, + x = 1120, + y = 320, + }, + }, + ["gridDivisionCount"] = 2, + ["gridSize"] = 32, +} + +return realearn.Compartment { + parameters = parameters, + mappings = mappings, + common_lua = common_lua, + custom_data = { + companion = companion_data, + grid = { + column_count = 8, + row_count = 5, + }, + }, +} diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-key-25.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-key-25.preset.luau new file mode 100644 index 0000000..d6a1d22 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-key-25.preset.luau @@ -0,0 +1,1650 @@ +--- name: APC Key 25 +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC Key 25 +--- description: | +--- This controller preset exposes all control elements of the APC Key 25 mk1 in a neutral way. It supports +--- Playtime slot status feedback. +--- midi_identity_pattern: F0 7E ? 06 02 47 27 00 19 * F7 +--- provided_schemes: [akai/apc-key-25, grid] + +--!strict + +-- Configuration + +local resolve_shift = false + +-- Requires + +local realearn = require("realearn") + +-- Parameters + +local shift_param = realearn.Parameter { + index = 0, + name = "Shift", +} +local parameters: { realearn.Parameter }? = if resolve_shift then { shift_param } else nil + +-- Single buttons + +local function simple_button(id: string, name: string, key_number: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local mappings = { + simple_button("stop-all-clips", "SAC", 81), + simple_button("play", "Play", 91), + simple_button("record", "Rec", 93), + realearn.Mapping { + id = "sustain", + name = "Sustain", + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 1, + controller_number = 64, + character = "Button", + }, + target = realearn.Target.Virtual { + id = "sustain", + character = "Button", + }, + }, +} + +-- Shift +local no_shift_activation_condition: realearn.ActivationCondition? +if resolve_shift then + -- The activation condition reflecting the state that shift is not pressed. + no_shift_activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = false, + }, + }, + } + -- Mapping to make shift button switch to other set of virtual control elements + local shift_mapping = realearn.Mapping { + name = "Shift", + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 98, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = 0, + }, + }, + } + table.insert(mappings, shift_mapping) + -- Alternative set of virtual control elements + local alt_elements = { + { key = 64, id = "cursor-up" }, + { key = 65, id = "cursor-down" }, + { key = 66, id = "cursor-left" }, + { key = 67, id = "cursor-right" }, + { key = 68, id = "volume" }, + { key = 69, id = "pan" }, + { key = 70, id = "sends" }, + { key = 71, id = "device" }, + { key = 82, id = "stop-clip" }, + { key = 83, id = "solo" }, + { key = 84, id = "record-arm" }, + { key = 85, id = "mute" }, + { key = 86, id = "track-select" }, + } + for _, element in ipairs(alt_elements) do + local mapping = realearn.Mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = true, + }, + }, + }, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = element.key, + }, + target = realearn.Target.Virtual { + id = element.id, + character = "Button", + }, + } + table.insert(mappings, mapping) + end +else + no_shift_activation_condition = nil + local mapping = simple_button("shift", "Shift", 98) + table.insert(mappings, mapping) +end + +-- Knobs +for i = 0, 7 do + local human_i = i + 1 + local mapping = realearn.Mapping { + id = `k{human_i}`, + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 48 + i, + }, + target = realearn.Target.Virtual { + id = i, + }, + } + table.insert(mappings, mapping) +end + +-- Clip launch buttons +local feedback_value_table = realearn.FeedbackValueTable.FromTextToDiscrete { + value = { + -- Off + ["playtime.slot_state.empty"] = 0, + -- Red + ["playtime.slot_state.armed"] = 0, + -- Yellow + ["playtime.slot_state.stopped"] = 5, + -- Green blinking + ["playtime.slot_state.scheduled_for_play_start"] = 2, + -- Green + ["playtime.slot_state.playing"] = 1, + -- Yellow + ["playtime.slot_state.paused"] = 5, + -- Yellow blinking + ["playtime.slot_state.scheduled_for_play_stop"] = 6, + -- Yellow blinking + ["playtime.slot_state.scheduled_for_play_restart"] = 6, + -- Red blinking + ["playtime.slot_state.scheduled_for_record_start"] = 4, + -- Red + ["playtime.slot_state.recording"] = 3, + -- Yellow blinking + ["playtime.slot_state.scheduled_for_record_stop"] = 6, + }, +} + +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 4 do + local human_row = row + 1 + local key_number_offset = (4 - row) * 8 + local id = `col{human_col}/row{human_row}/pad` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + glue = { + feedback_value_table = feedback_value_table, + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, mapping) + end +end + +-- Clip stop buttons +for col = 0, 7 do + local human_col = col + 1 + local id = `col{human_col}/stop` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 64 + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +-- Scene launch buttons +for row = 0, 4 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 82 + row, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +-- Controller layout + +local companion_data = { + controls = { + { + height = 32, + id = "0aa55949-ea14-404e-b3af-3c8a85ed50c7", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 0, + }, + { + height = 32, + id = "9b34bea6-9eeb-4a9a-850a-8a8294c772da", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 0, + }, + { + height = 32, + id = "bfb2db91-fb28-4b34-9bc4-6b25c18c4534", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 0, + }, + { + height = 32, + id = "817b84a8-675f-4edc-8229-67fc3e9a3400", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 0, + }, + { + height = 32, + id = "1d094852-f5b9-4aca-9972-71bf7b683b23", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 0, + }, + { + height = 32, + id = "7a2fcb32-1e24-4817-a20c-73dbbdd786fc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 0, + }, + { + height = 32, + id = "7ac2b4d1-16b8-43cd-9ebe-56bd375b813f", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 0, + }, + { + height = 32, + id = "42836480-6061-4625-81bf-87dba8d45d35", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row1/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 0, + }, + { + height = 32, + id = "fee5321d-d675-47ec-b4b6-d3e49f04ebda", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 64, + }, + { + height = 32, + id = "5760816d-bb92-457e-831c-6ba596d33a91", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 64, + }, + { + height = 32, + id = "cb55a16f-f589-4ee5-a977-a19c7ba5d2b2", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 64, + }, + { + height = 32, + id = "3b755e21-59c4-4766-9c14-2d20641b7bf4", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 64, + }, + { + height = 32, + id = "b6c14173-486f-49f7-87e7-c0fe52bf9898", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 64, + }, + { + height = 32, + id = "18b4dfd6-030e-4ddc-9a2a-c88e7270e950", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 64, + }, + { + height = 32, + id = "baa813e8-7d4b-4ea7-8610-74f124d3db97", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 64, + }, + { + height = 32, + id = "72edace4-890f-446a-b4e8-8d875c1841bc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row2/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 64, + }, + { + height = 32, + id = "998c9371-ff24-4954-945d-7c641916500e", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 128, + }, + { + height = 32, + id = "c2fdc571-347c-49da-9d9e-6caa4e8d9fcb", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 128, + }, + { + height = 32, + id = "6b25f0e2-4d79-40ab-be46-57d49cd8d579", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 128, + }, + { + height = 32, + id = "db014d76-4433-463a-ac6f-d38ddb7c364f", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 128, + }, + { + height = 32, + id = "8e39bc23-d51d-45c2-ad98-6a609e175584", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 128, + }, + { + height = 32, + id = "24cf1dbd-c01e-4a00-baac-282a5e4232af", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 128, + }, + { + height = 32, + id = "568c0177-4525-4eee-bfb9-e21ae7c202e0", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 128, + }, + { + height = 32, + id = "2ead47d1-ee75-429f-9cd4-3b5fe989c056", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row3/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 128, + }, + { + height = 32, + id = "941d996d-882c-44f3-abc4-3f6617cc04f9", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 192, + }, + { + height = 32, + id = "1ca9b0f7-cded-4f31-adec-3d3cff6c91ca", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 192, + }, + { + height = 32, + id = "a7d71b50-81b8-4261-ad8b-7c3229c09d78", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 192, + }, + { + height = 32, + id = "4a12fbbd-6ba1-43f2-bf5c-9650076bf376", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 192, + }, + { + height = 32, + id = "34b988c8-25e4-4936-8042-7140c7b28dad", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 192, + }, + { + height = 32, + id = "bbd4ceba-63d2-441f-8794-0263af223e5c", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 192, + }, + { + height = 32, + id = "57a49293-806b-4bff-8dfc-e8130aa8cc23", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 192, + }, + { + height = 32, + id = "bcefa1ae-60fe-4e52-b315-645a7b75931e", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row4/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 192, + }, + { + height = 32, + id = "a1fc5fa6-bfed-4b0a-8132-aed6a4b14bbc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 0, + y = 256, + }, + { + height = 32, + id = "74538f8c-d272-4ac4-a0e2-fcc6452311b5", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 128, + y = 256, + }, + { + height = 32, + id = "92bdce70-3a17-4d53-ae93-ec6fdfa4acb1", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 256, + y = 256, + }, + { + height = 32, + id = "05ac3407-9209-471e-9b0e-5029396de1cc", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 384, + y = 256, + }, + { + height = 32, + id = "b898b127-15e3-4c57-8528-16f5ea7f6af6", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 512, + y = 256, + }, + { + height = 32, + id = "d91b3c22-ff5d-4fd0-b8c1-dacd88b17065", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 640, + y = 256, + }, + { + height = 32, + id = "99d82570-59ff-4109-aabb-01924bdf5067", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 768, + y = 256, + }, + { + height = 32, + id = "161ddee9-7fd6-420d-b9ba-a2714f17c724", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/row5/pad", + }, + shape = "rectangle", + width = 96, + x = 896, + y = 256, + }, + { + height = 32, + id = "a393009f-655f-4425-9573-8351b151fd3a", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/stop", + }, + shape = "circle", + width = 32, + x = 32, + y = 320, + }, + { + height = 50, + id = "fb3a1723-7a69-457f-871d-5389c2637f4d", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col6/stop", + }, + shape = "circle", + width = 34, + x = 672, + y = 320, + }, + { + height = 50, + id = "44d09c91-a625-4b31-8e1e-e43d44470adc", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/stop", + }, + shape = "circle", + width = 34, + x = 160, + y = 320, + }, + { + height = 50, + id = "cad2df0b-319c-4c43-88dc-88042b2d6aed", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/stop", + }, + shape = "circle", + width = 34, + x = 288, + y = 320, + }, + { + height = 50, + id = "70fb4d30-33e8-482b-908f-7d8866d5fbec", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/stop", + }, + shape = "circle", + width = 34, + x = 416, + y = 320, + }, + { + height = 50, + id = "e92e54a4-96dd-4ed5-9580-e3a587ef86b0", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col5/stop", + }, + shape = "circle", + width = 34, + x = 544, + y = 320, + }, + { + height = 50, + id = "1a95adc6-8bb3-4f60-8dfd-7667550b202d", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col7/stop", + }, + shape = "circle", + width = 34, + x = 800, + y = 320, + }, + { + height = 50, + id = "ee30202c-0ebd-4abc-bd30-5a961480e5b6", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "col8/stop", + }, + shape = "circle", + width = 34, + x = 928, + y = 320, + }, + { + height = 50, + id = "0bdeed18-4641-48bd-b7f1-102363c2d1f5", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row1/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 0, + }, + { + height = 50, + id = "39a81c68-dd64-47f0-9d2f-2c36e004b1d1", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row2/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 64, + }, + { + height = 50, + id = "65b0540d-7173-48b9-9b62-eaf833c73e3f", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row3/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 128, + }, + { + height = 50, + id = "4ceafe75-4521-4f87-aabd-cd84d83e46bf", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row5/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 256, + }, + { + height = 50, + id = "8a590659-5632-4227-832c-7bdcbf6caddd", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "row4/play", + }, + shape = "circle", + width = 34, + x = 1024, + y = 192, + }, + { + height = 50, + id = "fe67392e-15ac-49d8-bcf2-129a3652effb", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "stop-all-clips", + }, + shape = "circle", + width = 34, + x = 1024, + y = 320, + }, + { + height = 50, + id = "cafb08e0-ba01-4b31-a175-62e4158a42f7", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k1", + }, + shape = "circle", + width = 98, + x = 1120, + y = 0, + }, + { + height = 50, + id = "f2b031ad-1879-4a75-984c-e906f69bd71e", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k2", + }, + shape = "circle", + width = 98, + x = 1280, + y = 0, + }, + { + height = 50, + id = "75667a1a-60c8-40e2-95ff-f1f90cc37e92", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k3", + }, + shape = "circle", + width = 98, + x = 1440, + y = 0, + }, + { + height = 50, + id = "edc1f799-cb0a-4f60-bf8c-b664ab57dc5a", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k4", + }, + shape = "circle", + width = 98, + x = 1600, + y = 0, + }, + { + height = 50, + id = "5573339f-1524-4ba3-9eb8-238702e437ad", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k5", + }, + shape = "circle", + width = 98, + x = 1120, + y = 128, + }, + { + height = 50, + id = "9a594e2e-d2b8-4f47-bddc-6872809a4089", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k6", + }, + shape = "circle", + width = 98, + x = 1280, + y = 128, + }, + { + height = 50, + id = "8b50a75c-c2cd-4f80-9c30-6d4bb9070035", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k7", + }, + shape = "circle", + width = 98, + x = 1440, + y = 128, + }, + { + height = 50, + id = "9301c8e0-1212-414f-a34e-76af972d68d0", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "k8", + }, + shape = "circle", + width = 98, + x = 1600, + y = 128, + }, + { + height = 34, + id = "7935e4a0-512f-4e96-8746-b2c8f542c33d", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "sustain", + }, + shape = "rectangle", + width = 98, + x = 1120, + y = 256, + }, + { + height = 34, + id = "39506af3-b17e-45bb-a148-51ff39b4eeb5", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "play", + }, + shape = "rectangle", + width = 98, + x = 1472, + y = 256, + }, + { + height = 34, + id = "35aa5f43-6b36-46ad-b104-fbe84c37b96b", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "record", + }, + shape = "rectangle", + width = 98, + x = 1600, + y = 256, + }, + { + height = 34, + id = "d72b47cc-f0b4-46c6-9510-fd967f815965", + ["labelOne"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "shift", + }, + shape = "rectangle", + width = 98, + x = 1120, + y = 320, + }, + }, + ["gridDivisionCount"] = 2, + ["gridSize"] = 32, +} + +return realearn.Compartment { + parameters = parameters, + mappings = mappings, + custom_data = { + companion = companion_data, + grid = { + column_count = 8, + row_count = 5, + }, + }, +} diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mini-mk2.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mini-mk2.preset.luau new file mode 100644 index 0000000..6dce2d9 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mini-mk2.preset.luau @@ -0,0 +1,254 @@ +--- name: APC mini mk2 +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC mini mk2 +--- description: | +--- This controller preset exposes all control elements of the APC mini mk2 in a neutral way. It supports +--- ReaLearn color feedback as well as Playtime slot status feedback. +--- midi_identity_pattern: F0 7E ? 06 02 47 4F 00 19 * F7 +--- # This device exposes 2 USB MIDI ports, one for "Notes" and one for "Control". We are +--- # interested in "Control". Both MIDI ports reply with the same device identity. If we +--- # don't provide a MIDI output device pattern, it's possible that ReaLearn's automatic +--- # controller detection feature takes the "Notes" port ... and things wouldn't work! +--- # On Windows, the naming is different, that's why there's also "MIDIOUT2". +--- midi_output_port_patterns: ["macos:*Control*", "windows:APC mini mk2"] +--- provided_schemes: [akai/apc-mini-mk2, grid] + +--!strict + +-- Configuration + +local resolve_shift = false + +-- Requires + +local preset_runtime = require("preset_runtime") +local realearn = require("realearn") + +-- Define MIDI scripts + +local common_lua = preset_runtime.include_str("akai/apc-mk2-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + +-- Parameters + +local shift_param = realearn.Parameter { + index = 0, + name = "Shift", +} +local parameters: { realearn.Parameter }? = if resolve_shift then { shift_param } else nil + +-- Single buttons + +local function simple_button(id: string, name: string, key_number: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local mappings: { realearn.Mapping } = { + -- Main fader + { + id = "fm", + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 56, + character = "Range", + }, + target = realearn.Target.Virtual { + id = "main/fader", + }, + }, +} + +-- Shift +local no_shift_activation_condition: realearn.ActivationCondition? +if resolve_shift then + -- The activation condition reflecting the state that shift is not pressed. + no_shift_activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = false, + }, + }, + } + -- Mapping to make shift button switch to other set of virtual control elements + local shift_mapping = realearn.Mapping { + name = "Shift", + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 122, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = 0, + }, + }, + } + table.insert(mappings, shift_mapping) + -- Alternative set of virtual control elements + local alt_elements = { + { key = 104, id = "cursor-up" }, + { key = 105, id = "cursor-down" }, + { key = 106, id = "cursor-left" }, + { key = 107, id = "cursor-right" }, + { key = 100, id = "volume" }, + { key = 101, id = "pan" }, + { key = 102, id = "sends" }, + { key = 103, id = "device" }, + { key = 112, id = "stop-clip" }, + { key = 113, id = "solo" }, + { key = 114, id = "mute" }, + { key = 115, id = "record-arm" }, + { key = 116, id = "track-select" }, + { key = 117, id = "drum" }, + { key = 118, id = "note" }, + { key = 119, id = "stop-all-clips" }, + } + for _, element in ipairs(alt_elements) do + local mapping = realearn.Mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = true, + }, + }, + }, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = element.key, + }, + target = realearn.Target.Virtual { + id = element.id, + character = "Button", + }, + } + table.insert(mappings, mapping) + end +else + no_shift_activation_condition = nil + local mapping = simple_button("shift", "Shift", 122) + table.insert(mappings, mapping) +end + +-- Channel faders +for i = 0, 7 do + local human_i = i + 1 + local mapping = realearn.Mapping { + id = `f{human_i}`, + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 48 + i, + character = "Range", + }, + target = realearn.Target.Virtual { + id = i, + }, + } + table.insert(mappings, mapping) +end + +-- Clip launch buttons +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 7 do + local human_row = row + 1 + local key_number_offset = (7 - row) * 8 + local id = `col{human_col}/row{human_row}/pad` + local control_mapping = realearn.Mapping { + id = id, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(key_number_offset + col), + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, control_mapping) + table.insert(mappings, feedback_mapping) + end +end + +-- Clip stop buttons +for col = 0, 7 do + local human_col = col + 1 + local id = `col{human_col}/stop` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 100 + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +-- Scene launch buttons +for row = 0, 7 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 112 + row, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +return realearn.Compartment { + parameters = parameters, + mappings = mappings, + common_lua = common_lua, + custom_data = { + grid = { + column_count = 8, + row_count = 8, + }, + }, +} diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mini.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mini.preset.luau new file mode 100644 index 0000000..1ff5569 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mini.preset.luau @@ -0,0 +1,251 @@ +--- name: APC mini +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC mini +--- description: | +--- This controller preset exposes all control elements of the APC mini mk2 in a neutral way. It supports +--- Playtime slot status feedback. +--- midi_identity_pattern: F0 7E ? 06 02 47 28 00 19 * F7 +--- provided_schemes: [akai/apc-mini, grid] + +--!strict + +-- Configuration + +local resolve_shift = false + +-- Requires + +local realearn = require("realearn") + +-- Parameters + +local shift_param = realearn.Parameter { + index = 0, + name = "Shift", +} +local parameters: { realearn.Parameter }? = if resolve_shift then { shift_param } else nil + +-- Single buttons + +local function simple_button(id: string, name: string, key_number: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local mappings: { realearn.Mapping } = { + -- Main fader + { + id = "fm", + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 56, + character = "Range", + }, + target = realearn.Target.Virtual { + id = "main/fader", + }, + }, +} + +-- Shift +local no_shift_activation_condition: realearn.ActivationCondition? +if resolve_shift then + -- The activation condition reflecting the state that shift is not pressed. + no_shift_activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = false, + }, + }, + } + -- Mapping to make shift button switch to other set of virtual control elements + local shift_mapping = realearn.Mapping { + name = "Shift", + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 98, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = 0, + }, + }, + } + table.insert(mappings, shift_mapping) + -- Alternative set of virtual control elements + local alt_elements = { + { key = 64, id = "cursor-up" }, + { key = 65, id = "cursor-down" }, + { key = 66, id = "cursor-left" }, + { key = 67, id = "cursor-right" }, + { key = 68, id = "volume" }, + { key = 69, id = "pan" }, + { key = 70, id = "sends" }, + { key = 71, id = "device" }, + { key = 82, id = "stop-clip" }, + { key = 83, id = "solo" }, + { key = 84, id = "record-arm" }, + { key = 85, id = "mute" }, + { key = 86, id = "track-select" }, + { key = 89, id = "stop-all-clips" }, + } + for _, element in ipairs(alt_elements) do + local mapping = realearn.Mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 0, + on = true, + }, + }, + }, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = element.key, + }, + target = realearn.Target.Virtual { + id = element.id, + character = "Button", + }, + } + table.insert(mappings, mapping) + end +else + no_shift_activation_condition = nil + local mapping = simple_button("shift", "Shift", 98) + table.insert(mappings, mapping) +end + +-- Channel faders +for i = 0, 7 do + local human_i = i + 1 + local mapping = realearn.Mapping { + id = `f{human_i}`, + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 48 + i, + character = "Range", + }, + target = realearn.Target.Virtual { + id = i, + }, + } + table.insert(mappings, mapping) +end + +-- Clip launch buttons +local feedback_value_table = realearn.FeedbackValueTable.FromTextToDiscrete { + value = { + -- Off + ["playtime.slot_state.empty"] = 0, + -- Red + ["playtime.slot_state.armed"] = 0, + -- Yellow + ["playtime.slot_state.stopped"] = 5, + -- Green blinking + ["playtime.slot_state.scheduled_for_play_start"] = 2, + -- Green + ["playtime.slot_state.playing"] = 1, + -- Yellow + ["playtime.slot_state.paused"] = 5, + -- Yellow blinking + ["playtime.slot_state.scheduled_for_play_stop"] = 6, + -- Yellow blinking + ["playtime.slot_state.scheduled_for_play_restart"] = 6, + -- Red blinking + ["playtime.slot_state.scheduled_for_record_start"] = 4, + -- Red + ["playtime.slot_state.recording"] = 3, + -- Yellow blinking + ["playtime.slot_state.scheduled_for_record_stop"] = 6, + }, +} +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 7 do + local human_row = row + 1 + local key_number_offset = (7 - row) * 8 + local id = `col{human_col}/row{human_row}/pad` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + glue = { + feedback_value_table = feedback_value_table, + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, mapping) + end +end + +-- Clip stop buttons +for col = 0, 7 do + local human_col = col + 1 + local id = `col{human_col}/stop` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 64 + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +-- Scene launch buttons +for row = 0, 7 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + activation_condition = no_shift_activation_condition, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 82 + row, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +return realearn.Compartment { + parameters = parameters, + mappings = mappings, + custom_data = { + grid = { + column_count = 8, + row_count = 8, + }, + }, +} diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mk2-lib/compartment-common.luau b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mk2-lib/compartment-common.luau new file mode 100644 index 0000000..f2d87e3 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/akai/apc-mk2-lib/compartment-common.luau @@ -0,0 +1,293 @@ +--!strict + +local midi_script = require("midi_script_source_runtime") + +local module = {} + +type FeedbackEntry = { + behavior: number, + color: number?, +} +local dark = 0x91 +local medium = 0x93 +local bright = 0x96 +local pulsing = 0x9A +local blinking = 0x9B +local red_color = 6 +local default_color = 3 +local feedback_table: { [string]: FeedbackEntry? } = { + ["playtime.slot_state.empty"] = { + behavior = dark, + color = 0, + }, + ["playtime.slot_state.armed"] = { + behavior = dark, + color = red_color, + }, + ["playtime.slot_state.stopped"] = { + behavior = dark, + }, + ["playtime.slot_state.ignited"] = { + behavior = dark, + }, + ["playtime.slot_state.scheduled_for_play_start"] = { + behavior = medium, + }, + ["playtime.slot_state.playing"] = { + behavior = pulsing, + }, + ["playtime.slot_state.paused"] = { + behavior = medium, + }, + ["playtime.slot_state.scheduled_for_play_stop"] = { + behavior = blinking, + }, + ["playtime.slot_state.scheduled_for_play_restart"] = { + behavior = blinking, + }, + ["playtime.slot_state.scheduled_for_record_start"] = { + behavior = medium, + color = red_color, + }, + ["playtime.slot_state.recording"] = { + behavior = pulsing, + color = red_color, + }, + ["playtime.slot_state.scheduled_for_record_stop"] = { + behavior = blinking, + color = red_color, + }, +} + +--- Although I got the true RGB colors to work, it was impossible to combine them with blinking +--- or pulsing behavior. So back to good old color palette. +local color_palette: { midi_script.RgbColor } = { + {r = 0x00, g= 0x00, b = 0x00}, + {r = 0x1E, g= 0x1E, b = 0x1E}, + {r = 0x7F, g= 0x7F, b = 0x7F}, + {r = 0xFF, g= 0xFF, b = 0xFF}, + {r = 0xFF, g= 0x4C, b = 0x4C}, + {r = 0xFF, g= 0x00, b = 0x00}, + {r = 0x59, g= 0x00, b = 0x00}, + {r = 0x19, g= 0x00, b = 0x00}, + {r = 0xFF, g= 0xBD, b = 0x6C}, + {r = 0xFF, g= 0x54, b = 0x00}, + {r = 0x59, g= 0x1D, b = 0x00}, + {r = 0x27, g= 0x1B, b = 0x00}, + {r = 0xFF, g= 0xFF, b = 0x4C}, + {r = 0xFF, g= 0xFF, b = 0x00}, + {r = 0x59, g= 0x59, b = 0x00}, + {r = 0x19, g= 0x19, b = 0x00}, + {r = 0x88, g= 0xFF, b = 0x4C}, + {r = 0x54, g= 0xFF, b = 0x00}, + {r = 0x1D, g= 0x59, b = 0x00}, + {r = 0x14, g= 0x2B, b = 0x00}, + {r = 0x4C, g= 0xFF, b = 0x4C}, + {r = 0x00, g= 0xFF, b = 0x00}, + {r = 0x00, g= 0x59, b = 0x00}, + {r = 0x00, g= 0x19, b = 0x00}, + {r = 0x4C, g= 0xFF, b = 0x5E}, + {r = 0x00, g= 0xFF, b = 0x19}, + {r = 0x00, g= 0x59, b = 0x0D}, + {r = 0x00, g= 0x19, b = 0x02}, + {r = 0x4C, g= 0xFF, b = 0x88}, + {r = 0x00, g= 0xFF, b = 0x55}, + {r = 0x00, g= 0x59, b = 0x1D}, + {r = 0x00, g= 0x1F, b = 0x12}, + {r = 0x4C, g= 0xFF, b = 0xB7}, + {r = 0x00, g= 0xFF, b = 0x99}, + {r = 0x00, g= 0x59, b = 0x35}, + {r = 0x00, g= 0x19, b = 0x12}, + {r = 0x4C, g= 0xC3, b = 0xFF}, + {r = 0x00, g= 0xA9, b = 0xFF}, + {r = 0x00, g= 0x41, b = 0x52}, + {r = 0x00, g= 0x10, b = 0x19}, + {r = 0x4C, g= 0x88, b = 0xFF}, + {r = 0x00, g= 0x55, b = 0xFF}, + {r = 0x00, g= 0x1D, b = 0x59}, + {r = 0x00, g= 0x08, b = 0x19}, + {r = 0x4C, g= 0x4C, b = 0xFF}, + {r = 0x00, g= 0x00, b = 0xFF}, + {r = 0x00, g= 0x00, b = 0x59}, + {r = 0x00, g= 0x00, b = 0x19}, + {r = 0x87, g= 0x4C, b = 0xFF}, + {r = 0x54, g= 0x00, b = 0xFF}, + {r = 0x19, g= 0x00, b = 0x64}, + {r = 0x0F, g= 0x00, b = 0x30}, + {r = 0xFF, g= 0x4C, b = 0xFF}, + {r = 0xFF, g= 0x00, b = 0xFF}, + {r = 0x59, g= 0x00, b = 0x59}, + {r = 0x19, g= 0x00, b = 0x19}, + {r = 0xFF, g= 0x4C, b = 0x87}, + {r = 0xFF, g= 0x00, b = 0x54}, + {r = 0x59, g= 0x00, b = 0x1D}, + {r = 0x22, g= 0x00, b = 0x13}, + {r = 0xFF, g= 0x15, b = 0x00}, + {r = 0x99, g= 0x35, b = 0x00}, + {r = 0x79, g= 0x51, b = 0x00}, + {r = 0x43, g= 0x64, b = 0x00}, + {r = 0x03, g= 0x39, b = 0x00}, + {r = 0x00, g= 0x57, b = 0x35}, + {r = 0x00, g= 0x54, b = 0x7F}, + {r = 0x00, g= 0x00, b = 0xFF}, + {r = 0x00, g= 0x45, b = 0x4F}, + {r = 0x25, g= 0x00, b = 0xCC}, + {r = 0x7F, g= 0x7F, b = 0x7F}, + {r = 0x20, g= 0x20, b = 0x20}, + {r = 0xFF, g= 0x00, b = 0x00}, + {r = 0xBD, g= 0xFF, b = 0x2D}, + {r = 0xAF, g= 0xED, b = 0x06}, + {r = 0x64, g= 0xFF, b = 0x09}, + {r = 0x10, g= 0x8B, b = 0x00}, + {r = 0x00, g= 0xFF, b = 0x87}, + {r = 0x00, g= 0xA9, b = 0xFF}, + {r = 0x00, g= 0x2A, b = 0xFF}, + {r = 0x3F, g= 0x00, b = 0xFF}, + {r = 0x7A, g= 0x00, b = 0xFF}, + {r = 0xB2, g= 0x1A, b = 0x7D}, + {r = 0x40, g= 0x21, b = 0x00}, + {r = 0xFF, g= 0x4A, b = 0x00}, + {r = 0x88, g= 0xE1, b = 0x06}, + {r = 0x72, g= 0xFF, b = 0x15}, + {r = 0x00, g= 0xFF, b = 0x00}, + {r = 0x3B, g= 0xFF, b = 0x26}, + {r = 0x59, g= 0xFF, b = 0x71}, + {r = 0x38, g= 0xFF, b = 0xCC}, + {r = 0x5B, g= 0x8A, b = 0xFF}, + {r = 0x31, g= 0x51, b = 0xC6}, + {r = 0x87, g= 0x7F, b = 0xE9}, + {r = 0xD3, g= 0x1D, b = 0xFF}, + {r = 0xFF, g= 0x00, b = 0x5D}, + {r = 0xFF, g= 0x7F, b = 0x00}, + {r = 0xB9, g= 0xB0, b = 0x00}, + {r = 0x90, g= 0xFF, b = 0x00}, + {r = 0x83, g= 0x5D, b = 0x07}, + {r = 0x39, g= 0x2b, b = 0x00}, + {r = 0x14, g= 0x4C, b = 0x10}, + {r = 0x0D, g= 0x50, b = 0x38}, + {r = 0x15, g= 0x15, b = 0x2A}, + {r = 0x16, g= 0x20, b = 0x5A}, + {r = 0x69, g= 0x3C, b = 0x1C}, + {r = 0xA8, g= 0x00, b = 0x0A}, + {r = 0xDE, g= 0x51, b = 0x3D}, + {r = 0xD8, g= 0x6A, b = 0x1C}, + {r = 0xFF, g= 0xE1, b = 0x26}, + {r = 0x9E, g= 0xE1, b = 0x2F}, + {r = 0x67, g= 0xB5, b = 0x0F}, + {r = 0x1E, g= 0x1E, b = 0x30}, + {r = 0xDC, g= 0xFF, b = 0x6B}, + {r = 0x80, g= 0xFF, b = 0xBD}, + {r = 0x9A, g= 0x99, b = 0xFF}, + {r = 0x8E, g= 0x66, b = 0xFF}, + {r = 0x40, g= 0x40, b = 0x40}, + {r = 0x75, g= 0x75, b = 0x75}, + {r = 0xE0, g= 0xFF, b = 0xFF}, + {r = 0xA0, g= 0x00, b = 0x00}, + {r = 0x35, g= 0x00, b = 0x00}, + {r = 0x1A, g= 0xD0, b = 0x00}, + {r = 0x07, g= 0x42, b = 0x00}, + {r = 0xB9, g= 0xB0, b = 0x00}, + {r = 0x3F, g= 0x31, b = 0x00}, + {r = 0xB3, g= 0x5F, b = 0x00}, + {r = 0x4B, g= 0x15, b = 0x02}, + {r = 0x40, g= 0x40, b = 0x40}, + {r = 0x75, g= 0x75, b = 0x75}, + {r = 0xE0, g= 0xFF, b = 0xFF}, + {r = 0xA0, g= 0x00, b = 0x00}, + {r = 0x35, g= 0x00, b = 0x00}, + {r = 0x1A, g= 0xD0, b = 0x00}, + {r = 0x07, g= 0x42, b = 0x00}, + {r = 0xB9, g= 0xB0, b = 0x00}, + {r = 0x3F, g= 0x31, b = 0x00}, + {r = 0xB3, g= 0x5F, b = 0x00}, + {r = 0x4B, g= 0x15, b = 0x02}, +} + +--- Returns the 0-based index within the given palette array. +local function find_closest_color_in_palette(color: midi_script.RgbColor, palette: {midi_script.RgbColor}): number + local ifurthest = 0 + local furthest = 3 * math.pow(255, 2) + 1 + for i,c in palette do + if color.r == c.r and color.g == c.g and color.b == c.b then + return i - 1 + end + local distance = math.pow((color.r - c.r), 2) + math.pow((color.g - c.g), 2) + math.pow((color.b - c.b), 2) + if distance < furthest then + furthest = distance + ifurthest = i - 1 + end + end + return ifurthest +end + +function module.pad_script(pad_index: number, y: midi_script.InputValue, context: midi_script.Context): midi_script.Output + local address = pad_index + local off_color = 0x00 + local off_output = { + address = address, + messages = { + { bright, pad_index, off_color }, + }, + } + -- Handle "off" feedback + if y == nil then + return off_output + end + -- Handle numeric feedback + if type(y) == "number" then + -- Translate number to solid feedback color, just as with APC Key 25 mk1 + local color = math.floor(y * 127) + return { + address = address, + messages = { + { bright, pad_index, color }, + }, + } + end + -- Handle text feedback + if type(y) == "string" then + -- This is a grid controller, typically used for controlling a Playtime matrix, so we should be able to + -- handle text feedback that conforms to Playtime's clip state convention. + local entry = feedback_table[y] + if entry == nil then + -- Unknown texts switch the LED off + return off_output + end + local explicit_color = entry.color + if explicit_color ~= nil then + -- Switch LED to explicit color + return { + address = address, + messages = { + { entry.behavior, pad_index, explicit_color }, + }, + } + end + -- No explicit color means we should use the event color (which is set in the "Glue" section) + local event_color = context.feedback_event.color + if event_color == nil then + -- Event has default color / no specific color + return { + address = address, + messages = { + { entry.behavior, pad_index, default_color }, + }, + } + end + -- Find color available on the controller that's closest to the desired RGB color + local closest_color = find_closest_color_in_palette(event_color, color_palette) + return { + address = address, + messages = { + { entry.behavior, pad_index, closest_color }, + }, + } + end + -- Complex feedback is highly individual. It doesn't make sense to handle this in a general-purpose controller preset. + return { + address = address, + messages = {}, + } +end + +return module \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/behringer/x-touch-one.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/behringer/x-touch-one.preset.luau new file mode 100644 index 0000000..7ca1fc6 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/behringer/x-touch-one.preset.luau @@ -0,0 +1,26 @@ +--- name: X-Touch One +--- realearn_version: 2.16.0 +--- author: helgoboss +--- description: | +--- This controller preset implements support for the Behringer X-Touch One controller in MCU mode. +--- +--- The MC modes cover all buttons and work great with the "Mackie Control" preset. A potential +--- downside is that the device itself controls the "Master" button, so you can’t use that button in +--- ReaLearn to customize its logic. +--- setup_instructions: | +--- Select the correct mode by holding the stop button and pressing the encoder: +--- +--- - If you have firmware < 1.10 (the firmware is displayed at startup), use mode "MC Std" +--- - If you have at least firmware 1.10, use mode "MC user" +--- device_manufacturer: Behringer +--- device_name: X-Touch One +--- provided_schemes: [daw] + +--!strict + +local commons = require("mackie/control-universal-lib/preset-common") + +return commons.create_compartment { + companion_data = nil, + support_x_touch_colors = true, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-grid.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-grid.preset.luau new file mode 100644 index 0000000..e59cb4a --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-grid.preset.luau @@ -0,0 +1,35 @@ +--- name: Midi Fighter Twister - Grid +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: DJ TechTools +--- device_name: Midi Fighter Twister +--- description: | +--- This controller preset exposes the push encoders of the Midi Fighter Twister with the "grid" scheme, which means +--- it uses virtual element IDs such as "col1/row1/pad". This makes it compatible with main presets that are +--- built for generic grid controllers. +--- setup_instructions: | +--- The Midi Fighter Twister is a very customizable controller. Please follow the instructions at +--- https://github.com/helgoboss/helgobox/blob/master/doc/controllers.adoc#dj-techtools-midi-fighter-twister +--- to make it ready for ReaLearn. +--- midi_identity_pattern: F0 7E ? 06 02 00 01 79 05 00 01 00 * F7 +--- provided_schemes: [djtechtools/midi-fighter-twister/grid, grid] + +--!strict + +local realearn = require("realearn") +local commons = require("djtechtools/midi-fighter-twister-lib/preset-common") + +return commons.create_compartment { + create_push_target = function (col, row) + return realearn.Target.Virtual { + id = `col{col +1 }/row{row + 1}/pad`, + character = "Multi", + } + end, + create_turn_target = function (col, row) + return realearn.Target.Virtual { + id = `col{col +1 }/row{row + 1}/knob`, + character = "Multi", + } + end, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/compartment-common.luau b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/compartment-common.luau new file mode 100644 index 0000000..a186cbf --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/compartment-common.luau @@ -0,0 +1,268 @@ +--!strict + +local midi_script = require("midi_script_source_runtime") + +local module = {} + +local color_palette: { midi_script.RgbColor } = { + { r = 0, g = 0, b = 0 }, + { r = 0, g = 0, b = 255 }, + { r = 0, g = 21, b = 255 }, + { r = 0, g = 34, b = 255 }, + { r = 0, g = 46, b = 255 }, + { r = 0, g = 59, b = 255 }, + { r = 0, g = 68, b = 255 }, + { r = 0, g = 80, b = 255 }, + { r = 0, g = 93, b = 255 }, + { r = 0, g = 106, b = 255 }, + { r = 0, g = 119, b = 255 }, + { r = 0, g = 127, b = 255 }, + { r = 0, g = 140, b = 255 }, + { r = 0, g = 153, b = 255 }, + { r = 0, g = 165, b = 255 }, + { r = 0, g = 178, b = 255 }, + { r = 0, g = 191, b = 255 }, + { r = 0, g = 199, b = 255 }, + { r = 0, g = 212, b = 255 }, + { r = 0, g = 225, b = 255 }, + { r = 0, g = 238, b = 255 }, + { r = 0, g = 250, b = 255 }, + { r = 0, g = 255, b = 250 }, + { r = 0, g = 255, b = 237 }, + { r = 0, g = 255, b = 225 }, + { r = 0, g = 255, b = 212 }, + { r = 0, g = 255, b = 199 }, + { r = 0, g = 255, b = 191 }, + { r = 0, g = 255, b = 178 }, + { r = 0, g = 255, b = 165 }, + { r = 0, g = 255, b = 153 }, + { r = 0, g = 255, b = 140 }, + { r = 0, g = 255, b = 127 }, + { r = 0, g = 255, b = 119 }, + { r = 0, g = 255, b = 106 }, + { r = 0, g = 255, b = 93 }, + { r = 0, g = 255, b = 80 }, + { r = 0, g = 255, b = 67 }, + { r = 0, g = 255, b = 59 }, + { r = 0, g = 255, b = 46 }, + { r = 0, g = 255, b = 33 }, + { r = 0, g = 255, b = 21 }, + { r = 0, g = 255, b = 8 }, + { r = 0, g = 255, b = 0 }, + { r = 12, g = 255, b = 0 }, + { r = 25, g = 255, b = 0 }, + { r = 38, g = 255, b = 0 }, + { r = 51, g = 255, b = 0 }, + { r = 63, g = 255, b = 0 }, + { r = 72, g = 255, b = 0 }, + { r = 84, g = 255, b = 0 }, + { r = 97, g = 255, b = 0 }, + { r = 110, g = 255, b = 0 }, + { r = 123, g = 255, b = 0 }, + { r = 131, g = 255, b = 0 }, + { r = 144, g = 255, b = 0 }, + { r = 157, g = 255, b = 0 }, + { r = 170, g = 255, b = 0 }, + { r = 182, g = 255, b = 0 }, + { r = 191, g = 255, b = 0 }, + { r = 203, g = 255, b = 0 }, + { r = 216, g = 255, b = 0 }, + { r = 229, g = 255, b = 0 }, + { r = 242, g = 255, b = 0 }, + { r = 255, g = 255, b = 0 }, + { r = 255, g = 246, b = 0 }, + { r = 255, g = 233, b = 0 }, + { r = 255, g = 220, b = 0 }, + { r = 255, g = 208, b = 0 }, + { r = 255, g = 195, b = 0 }, + { r = 255, g = 187, b = 0 }, + { r = 255, g = 174, b = 0 }, + { r = 255, g = 161, b = 0 }, + { r = 255, g = 148, b = 0 }, + { r = 255, g = 135, b = 0 }, + { r = 255, g = 127, b = 0 }, + { r = 255, g = 114, b = 0 }, + { r = 255, g = 102, b = 0 }, + { r = 255, g = 89, b = 0 }, + { r = 255, g = 76, b = 0 }, + { r = 255, g = 63, b = 0 }, + { r = 255, g = 55, b = 0 }, + { r = 255, g = 42, b = 0 }, + { r = 255, g = 29, b = 0 }, + { r = 255, g = 16, b = 0 }, + { r = 255, g = 4, b = 0 }, + { r = 255, g = 0, b = 4 }, + { r = 255, g = 0, b = 16 }, + { r = 255, g = 0, b = 29 }, + { r = 255, g = 0, b = 42 }, + { r = 255, g = 0, b = 55 }, + { r = 255, g = 0, b = 63 }, + { r = 255, g = 0, b = 76 }, + { r = 255, g = 0, b = 89 }, + { r = 255, g = 0, b = 102 }, + { r = 255, g = 0, b = 114 }, + { r = 255, g = 0, b = 127 }, + { r = 255, g = 0, b = 135 }, + { r = 255, g = 0, b = 148 }, + { r = 255, g = 0, b = 161 }, + { r = 255, g = 0, b = 174 }, + { r = 255, g = 0, b = 186 }, + { r = 255, g = 0, b = 195 }, + { r = 255, g = 0, b = 208 }, + { r = 255, g = 0, b = 221 }, + { r = 255, g = 0, b = 233 }, + { r = 255, g = 0, b = 246 }, + { r = 255, g = 0, b = 255 }, + { r = 242, g = 0, b = 255 }, + { r = 229, g = 0, b = 255 }, + { r = 216, g = 0, b = 255 }, + { r = 204, g = 0, b = 255 }, + { r = 191, g = 0, b = 255 }, + { r = 182, g = 0, b = 255 }, + { r = 169, g = 0, b = 255 }, + { r = 157, g = 0, b = 255 }, + { r = 144, g = 0, b = 255 }, + { r = 131, g = 0, b = 255 }, + { r = 123, g = 0, b = 255 }, + { r = 110, g = 0, b = 255 }, + { r = 97, g = 0, b = 255 }, + { r = 85, g = 0, b = 255 }, + { r = 72, g = 0, b = 255 }, + { r = 63, g = 0, b = 255 }, + { r = 50, g = 0, b = 255 }, + { r = 38, g = 0, b = 255 }, + { r = 25, g = 0, b = 255 }, + { r = 240, g = 240, b = 225 }, +} + +type FeedbackEntry = { + behavior: number, + color: number?, +} +local dark = 30 +local medium = 40 +local bright = 47 +local pulsing = 12 +local blinking = 8 +local red_color = 83 +local default_color = 21 +local feedback_table: { [string]: FeedbackEntry? } = { + ["playtime.slot_state.empty"] = { + behavior = bright, + color = 0, + }, + ["playtime.slot_state.armed"] = { + behavior = dark, + color = red_color, + }, + ["playtime.slot_state.stopped"] = { + behavior = dark, + }, + ["playtime.slot_state.ignited"] = { + behavior = dark, + }, + ["playtime.slot_state.scheduled_for_play_start"] = { + behavior = medium, + }, + ["playtime.slot_state.playing"] = { + behavior = pulsing, + }, + ["playtime.slot_state.paused"] = { + behavior = dark, + }, + ["playtime.slot_state.scheduled_for_play_stop"] = { + behavior = blinking, + }, + ["playtime.slot_state.scheduled_for_play_restart"] = { + behavior = blinking, + }, + ["playtime.slot_state.scheduled_for_record_start"] = { + behavior = medium, + color = red_color, + }, + ["playtime.slot_state.recording"] = { + behavior = pulsing, + color = red_color, + }, + ["playtime.slot_state.scheduled_for_record_stop"] = { + behavior = blinking, + color = red_color, + }, +} + +--- Returns the 0-based index within the given palette array. +local function find_closest_color_in_palette(color: midi_script.RgbColor, palette: { midi_script.RgbColor }): number + local ifurthest = 0 + local furthest = 3 * math.pow(255, 2) + 1 + for i, c in palette do + if color.r == c.r and color.g == c.g and color.b == c.b then + return i - 1 + end + local distance = math.pow((color.r - c.r), 2) + math.pow((color.g - c.g), 2) + math.pow((color.b - c.b), 2) + if distance < furthest then + furthest = distance + ifurthest = i - 1 + end + end + return ifurthest +end + +local function create_output(pad_index: number, color_index: number, behavior: number): midi_script.Output + return { + address = pad_index, + messages = { + { 0xB1, pad_index, color_index }, + { 0xB2, pad_index, behavior }, + }, + } +end + +function module.pad_script( + pad_index: number, + y: midi_script.InputValue, + context: midi_script.Context +): midi_script.Output + local off_color = 0x00 + local off_output = create_output(pad_index, off_color, bright) + -- Handle "off" feedback + if y == nil then + return off_output + end + -- Handle numeric feedback + if type(y) == "number" then + -- Translate number to solid feedback color. + local color = math.floor(y * 127) + return create_output(pad_index, color, bright) + end + -- Handle text feedback + if type(y) == "string" then + -- This can be used as a grid controller for controlling a Playtime matrix, so we should be able to + -- handle text feedback that conforms to Playtime's clip state convention. + local entry = feedback_table[y] + if entry == nil then + -- Unknown texts switch the LED off + return off_output + end + local explicit_color = entry.color + if explicit_color ~= nil then + -- Switch LED to explicit color + return create_output(pad_index, explicit_color, entry.behavior) + end + -- No explicit color means we should use the event color (which is set in the "Glue" section) + local event_color = context.feedback_event.color + if event_color == nil then + -- Event has default color / no specific color + return create_output(pad_index, default_color, entry.behavior) + end + -- Find color available on the controller that's closest to the desired RGB color + local closest_color = find_closest_color_in_palette(event_color, color_palette) + return create_output(pad_index, closest_color, entry.behavior) + end + -- Complex feedback is highly individual. It doesn't make sense to handle this in a general-purpose controller preset. + return { + address = pad_index, + messages = {}, + } +end + +return module diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-common.luau b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-common.luau new file mode 100644 index 0000000..c95aca0 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-common.luau @@ -0,0 +1,605 @@ +--!strict + +local preset_runtime = require("preset_runtime") +local realearn = require("realearn") + +local module = {} + +export type MidiFighterTwisterPresetConfig = { + create_turn_target: (col: number, row: number) -> realearn.Target, + create_push_target: (col: number, row: number) -> realearn.Target, +} + +local common_lua = preset_runtime.include_str("djtechtools/midi-fighter-twister-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + +function module.create_compartment(config: MidiFighterTwisterPresetConfig): realearn.Compartment + -- Preparation + local function pad_control(col: number, row: number, cc_number: number): realearn.Mapping + return realearn.Mapping { + id = `col{col + 1}/row{row + 1}/pad`, + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 1, + controller_number = cc_number, + character = "Button", + }, + target = config.create_push_target(col, row), + } + end + + local function pad_feedback(col: number, row: number, cc_number: number): realearn.Mapping + return realearn.Mapping { + id = `col{col + 1}/row{row + 1}/pad/feedback`, + control_enabled = false, + visible_in_projection = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(cc_number), + }, + target = config.create_push_target(col, row) + } + end + + local function knob(col: number, row: number, cc_number: number): realearn.Mapping + return realearn.Mapping { + id = `col{col + 1}/row{row + 1}/knob`, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc_number, + character = "Relative2", + }, + glue = { + step_factor_interval = { 1, 100 } + }, + target = config.create_turn_target(col, row), + } + end + + local function side_button(id: string, cc_number: number): realearn.Mapping + return realearn.Mapping { + id = id, + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 3, + controller_number = cc_number, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + end + + local mappings: { realearn.Mapping } = { + -- Side buttons + side_button("bank-left", 8), + side_button("cursor-left", 9), + side_button("ch-left", 10), + side_button("bank-right", 11), + side_button("cursor-right", 12), + side_button("ch-right", 13), + } + + -- Grid + local column_count = 4 + local row_count = 4 + for col = 0, column_count - 1 do + for row = 0, row_count - 1 do + local cc_number = row * row_count + col + table.insert(mappings, pad_control(col, row, cc_number)) + table.insert(mappings, pad_feedback(col, row, cc_number)) + table.insert(mappings, knob(col, row, cc_number)) + end + end + + -- Companion data + + local companion_data = { + controls = { + { + height = 50, + id = "col4/row1/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row1/knob", + "col4/row1/pad", + }, + shape = "circle", + width = 50, + x = 400, + y = 0, + }, + { + height = 50, + id = "col1/row2/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row2/knob", + "col1/row2/pad", + }, + shape = "circle", + width = 50, + x = 100, + y = 100, + }, + { + height = 50, + id = "col4/row2/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row2/knob", + "col4/row2/pad", + }, + shape = "circle", + width = 50, + x = 400, + y = 100, + }, + { + height = 50, + id = "col1/row1/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row1/knob", + "col1/row1/pad", + }, + shape = "circle", + width = 50, + x = 100, + y = 0, + }, + { + height = 50, + id = "col3/row1/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row1/knob", + "col3/row1/pad", + }, + shape = "circle", + width = 50, + x = 300, + y = 0, + }, + { + height = 50, + id = "col2/row1/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row1/knob", + "col2/row1/pad", + }, + shape = "circle", + width = 50, + x = 200, + y = 0, + }, + { + height = 50, + id = "col2/row2/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row2/knob", + "col2/row2/pad", + }, + shape = "circle", + width = 50, + x = 200, + y = 100, + }, + { + height = 50, + id = "col3/row2/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row2/knob", + "col3/row2/pad", + }, + shape = "circle", + width = 50, + x = 300, + y = 100, + }, + { + height = 50, + id = "col2/row3/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row3/knob", + "col2/row3/pad", + }, + shape = "circle", + width = 50, + x = 200, + y = 200, + }, + { + height = 50, + id = "col3/row3/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row3/knob", + "col3/row3/pad", + }, + shape = "circle", + width = 50, + x = 300, + y = 200, + }, + { + height = 50, + id = "col4/row4/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row4/knob", + "col4/row4/pad", + }, + shape = "circle", + width = 50, + x = 400, + y = 300, + }, + { + height = 50, + id = "col1/row4/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row4/knob", + "col1/row4/pad", + }, + shape = "circle", + width = 50, + x = 100, + y = 300, + }, + { + height = 50, + id = "col2/row4/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col2/row4/knob", + "col2/row4/pad", + }, + shape = "circle", + width = 50, + x = 200, + y = 300, + }, + { + height = 50, + id = "col3/row4/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col3/row4/knob", + "col3/row4/pad", + }, + shape = "circle", + width = 50, + x = 300, + y = 300, + }, + { + height = 50, + id = "col1/row3/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col1/row3/knob", + "col1/row3/pad", + }, + shape = "circle", + width = 50, + x = 100, + y = 200, + }, + { + height = 50, + id = "col4/row3/knob", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "center", + ["sizeConstrained"] = true, + }, + mappings = { + "col4/row3/knob", + "col4/row3/pad", + }, + shape = "circle", + width = 50, + x = 400, + y = 200, + }, + { + height = 50, + id = "bank-right", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "bank-right", + }, + shape = "rectangle", + width = 50, + x = 500, + y = 50, + }, + { + height = 50, + id = "bank-left", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "bank-left", + }, + shape = "rectangle", + width = 50, + x = 0, + y = 50, + }, + { + height = 50, + id = "a78b277e-cfbf-4b2b-9cc6-1a550aeb87fd", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "ch-left", + }, + shape = "rectangle", + width = 50, + x = 0, + y = 250, + }, + { + height = 50, + id = "e312d2a2-ecf1-4189-95af-4174c43a750c", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "ch-right", + }, + shape = "rectangle", + width = 50, + x = 500, + y = 250, + }, + { + height = 50, + id = "cursor-left", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "cursor-left", + }, + shape = "rectangle", + width = 50, + x = 0, + y = 150, + }, + { + height = 50, + id = "cursor-right", + ["labelOne"] = { + angle = 0, + position = "aboveTop", + ["sizeConstrained"] = true, + }, + ["labelTwo"] = { + angle = 0, + position = "belowBottom", + ["sizeConstrained"] = true, + }, + mappings = { + "cursor-right", + }, + shape = "rectangle", + width = 50, + x = 500, + y = 150, + }, + }, + ["gridDivisionCount"] = 2, + ["gridSize"] = 50, + } + + -- Result + return realearn.Compartment { + mappings = mappings, + common_lua = common_lua, + custom_data = { + companion = companion_data, + numbered = { + multi_count = column_count * row_count, + button_count = column_count * row_count, + }, + grid = { + column_count = column_count, + row_count = row_count, + }, + }, + } +end + +return module diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-numbered.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-numbered.preset.luau new file mode 100644 index 0000000..ec7b068 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/djtechtools/midi-fighter-twister-numbered.preset.luau @@ -0,0 +1,35 @@ +--- name: Midi Fighter Twister - Numbered +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: DJ TechTools +--- device_name: Midi Fighter Twister +--- description: | +--- This controller preset exposes the push encoders of the Midi Fighter Twister with the "numbered" scheme, which means +--- it uses virtual element IDs such as button 5 or knob 3. This makes it compatible with main presets that are +--- built for generic controllers that have a row of buttons and knobs. +--- setup_instructions: | +--- The Midi Fighter Twister is a very customizable controller. Please follow the instructions at +--- https://github.com/helgoboss/helgobox/blob/master/doc/controllers.adoc#dj-techtools-midi-fighter-twister +--- to make it ready for ReaLearn. +--- midi_identity_pattern: F0 7E ? 06 02 00 01 79 05 00 01 00 * F7 +--- provided_schemes: [djtechtools/midi-fighter-twister/numbered, numbered] + +--!strict + +local realearn = require("realearn") +local commons = require("djtechtools/midi-fighter-twister-lib/preset-common") + +return commons.create_compartment { + create_push_target = function (col, row) + return realearn.Target.Virtual { + id = row * 4 + col, + character = "Button", + } + end, + create_turn_target = function (col, row) + return realearn.Target.Virtual { + id = row * 4 + col, + character = "Multi", + } + end, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/icon/platform-m.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/icon/platform-m.preset.luau new file mode 100644 index 0000000..5ca74d2 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/icon/platform-m.preset.luau @@ -0,0 +1,1397 @@ +--- name: Platform M+ +--- realearn_version: 2.16.0 +--- author: helgoboss +--- device_manufacturer: iCON +--- device_name: Platform M+ +--- description: | +--- This controller preset implements support for the iCON Platform M+ controller in MCU mode. +--- +--- Tested with firmware v2.12. +--- setup_instructions: | +--- Please start the device in "Mackie" control mode (channel 1 since firmware version 2). +--- +--- "Mackie" mode is preferred over "User defined" mode, even if "User defined" by default +--- transmits the same messages and therefore also just works. The significant difference is that "Mackie" +--- mode supports the complete 10-bit fader resolution (1024 different values) whereas "User defined" mode +--- uses only 7 bits (128 different values). It’s a complete mystery to me why they don’t use the full resolution +--- in "User defined" mode. But that’s no problem, just make sure you use "Mackie" mode. It provides the best +--- settings out of the box. Leave the customization to ReaLearn! +--- provided_schemes: [daw] + +--!strict + +local commons = require("mackie/control-universal-lib/preset-common") + +local companion_data = { + controls = { + { + height = 32, + id = "07226df9-370c-4495-8b58-172a3a50d7e6", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch1/v-pot/control", + "ch1/v-select", + }, + shape = "circle", + width = 32, + x = 0, + y = 0, + }, + { + height = 32, + id = "b718c836-0718-46a1-860d-9bf8c75c8018", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch2/v-pot/control", + "ch2/v-select", + }, + shape = "circle", + width = 32, + x = 128, + y = 0, + }, + { + height = 32, + id = "37ea8015-c89f-4d82-9ab9-7c9b81eb29ae", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch3/v-pot/control", + "ch3/v-select", + }, + shape = "circle", + width = 32, + x = 256, + y = 0, + }, + { + height = 32, + id = "1fa0bba0-80e7-4de3-b4ed-63ff6b3c2410", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch4/v-pot/control", + "ch4/v-select", + }, + shape = "circle", + width = 32, + x = 384, + y = 0, + }, + { + height = 32, + id = "7beac804-921e-4437-8a78-533427714619", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch5/v-pot/control", + "ch5/v-select", + }, + shape = "circle", + width = 32, + x = 512, + y = 0, + }, + { + height = 32, + id = "884daa2b-78cd-4f04-99c2-813fa21173ce", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch6/v-pot/control", + "ch6/v-select", + }, + shape = "circle", + width = 32, + x = 640, + y = 0, + }, + { + height = 32, + id = "f00ba6a6-ded6-4757-aee5-59098d475d0e", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch7/v-pot/control", + "ch7/v-select", + }, + shape = "circle", + width = 32, + x = 768, + y = 0, + }, + { + height = 32, + id = "4b76aaff-67c7-4e2d-a536-dad3642ee61f", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch8/v-pot/control", + "ch8/v-select", + }, + shape = "circle", + width = 32, + x = 896, + y = 0, + }, + { + height = 480, + id = "468dc034-8e4b-470b-9a51-2646906bb6f7", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch1/fader", + "ch1/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 0, + y = 64, + }, + { + height = 480, + id = "85b2477e-1ed1-45a6-b6b0-f5d6396a84ee", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch2/fader", + "ch2/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 128, + y = 64, + }, + { + height = 480, + id = "9cda0154-357d-4bf1-9ddc-64f97909e068", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch3/fader", + "ch3/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 256, + y = 64, + }, + { + height = 480, + id = "bdddbfc4-3a80-4397-bc43-a6eb92eb60c1", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch4/fader", + "ch4/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 384, + y = 64, + }, + { + height = 480, + id = "9fe0adef-8ae9-4a07-a8f7-11923bb2c42a", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch5/fader", + "ch5/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 512, + y = 64, + }, + { + height = 480, + id = "9d1fd347-e373-4af2-81e4-ada7e80f0f68", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch6/fader", + "ch6/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 640, + y = 64, + }, + { + height = 480, + id = "259f03c6-afe4-4a5d-9e12-66f6d6032627", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch7/fader", + "ch7/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 768, + y = 64, + }, + { + height = 480, + id = "a186f1a1-e78b-4320-b99d-22255dad0ae3", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "ch8/fader", + "ch8/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 896, + y = 64, + }, + { + height = 32, + id = "fe42b353-b491-4a1e-8438-b1cf31d3031f", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch1/select", + }, + shape = "rectangle", + width = 32, + x = 64, + y = 288, + }, + { + height = 32, + id = "fb2ced97-49e3-4ea2-8d13-1861ed41d32a", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch2/select", + }, + shape = "rectangle", + width = 32, + x = 192, + y = 288, + }, + { + height = 32, + id = "2305bc12-99f0-4760-b6e5-68d04b779607", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch3/select", + }, + shape = "rectangle", + width = 32, + x = 320, + y = 288, + }, + { + height = 32, + id = "053cb45b-901f-4bdb-af26-721cf50b4747", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch4/select", + }, + shape = "rectangle", + width = 32, + x = 448, + y = 288, + }, + { + height = 32, + id = "5422a6c5-4d4e-4662-8f62-43dcc222150e", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch5/select", + }, + shape = "rectangle", + width = 32, + x = 576, + y = 288, + }, + { + height = 32, + id = "e0c5bdac-5347-4c85-b332-f3929fc7d81a", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch6/select", + }, + shape = "rectangle", + width = 32, + x = 704, + y = 288, + }, + { + height = 32, + id = "425d54e3-4962-4785-a580-78370e5eaf25", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch7/select", + }, + shape = "rectangle", + width = 32, + x = 832, + y = 288, + }, + { + height = 32, + id = "f92d9b83-7ca8-4f5a-8aed-79d1f3d82e29", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch8/select", + }, + shape = "rectangle", + width = 32, + x = 960, + y = 288, + }, + { + height = 32, + id = "eb1d2f34-78b3-438a-af08-c4a0c49ffcac", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch1/mute", + }, + shape = "rectangle", + width = 32, + x = 64, + y = 352, + }, + { + height = 32, + id = "9a7eb5f1-c58f-42cd-a785-19e8d5a96a90", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch2/mute", + }, + shape = "rectangle", + width = 32, + x = 192, + y = 352, + }, + { + height = 32, + id = "35c77ce3-664c-4266-9f72-aed38b2f8bb9", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch3/mute", + }, + shape = "rectangle", + width = 32, + x = 320, + y = 352, + }, + { + height = 32, + id = "85dd9794-445f-4f46-be66-8637cec6ceee", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch4/mute", + }, + shape = "rectangle", + width = 32, + x = 448, + y = 352, + }, + { + height = 32, + id = "56cde00c-718b-4947-8465-a606cbfe1a28", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch5/mute", + }, + shape = "rectangle", + width = 32, + x = 576, + y = 352, + }, + { + height = 32, + id = "68ad5f8f-74d9-4872-af49-aa9bffcf01df", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch6/mute", + }, + shape = "rectangle", + width = 32, + x = 704, + y = 352, + }, + { + height = 32, + id = "0181ec93-43da-4311-a13d-8fcdd5fbab1e", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch7/mute", + }, + shape = "rectangle", + width = 32, + x = 832, + y = 352, + }, + { + height = 32, + id = "9a0ce8a7-5017-41b4-97c6-79b3c8bb2eae", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch8/mute", + }, + shape = "rectangle", + width = 32, + x = 960, + y = 352, + }, + { + height = 32, + id = "0ff5aee5-7793-4c7f-a204-10d58ef759a2", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch1/solo", + }, + shape = "rectangle", + width = 32, + x = 64, + y = 416, + }, + { + height = 32, + id = "3c4c2f4a-5e81-485b-a25a-302bd9df0a2c", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch2/solo", + }, + shape = "rectangle", + width = 32, + x = 192, + y = 416, + }, + { + height = 32, + id = "991df60d-6d72-42c0-acc1-89652572afd2", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch3/solo", + }, + shape = "rectangle", + width = 32, + x = 320, + y = 416, + }, + { + height = 32, + id = "9b200df7-a411-4c41-85ad-f6acc1a49d4e", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch4/solo", + }, + shape = "rectangle", + width = 32, + x = 448, + y = 416, + }, + { + height = 32, + id = "50a27c62-5dd3-463f-b2fa-0cc9c7514c2f", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch5/solo", + }, + shape = "rectangle", + width = 32, + x = 576, + y = 416, + }, + { + height = 32, + id = "16a1bf1a-e538-489f-84ce-2c55d89098bb", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch6/solo", + }, + shape = "rectangle", + width = 32, + x = 704, + y = 416, + }, + { + height = 32, + id = "92b6b927-e5b4-454a-af6d-7d6ea616ba82", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch7/solo", + }, + shape = "rectangle", + width = 32, + x = 832, + y = 416, + }, + { + height = 32, + id = "d7cc71ab-d73f-4aef-a644-7dfca7278a2e", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch8/solo", + }, + shape = "rectangle", + width = 32, + x = 960, + y = 416, + }, + { + height = 32, + id = "ddc518d6-ad09-485e-ad17-2305223011cd", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch1/record-ready", + }, + shape = "circle", + width = 32, + x = 64, + y = 480, + }, + { + height = 32, + id = "feb760d1-598d-4317-8f55-d1ec213eda02", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch2/record-ready", + }, + shape = "circle", + width = 32, + x = 192, + y = 480, + }, + { + height = 32, + id = "76ac7ba8-7987-4e89-9ff2-48da5e88d957", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch3/record-ready", + }, + shape = "circle", + width = 32, + x = 320, + y = 480, + }, + { + height = 32, + id = "a5bcfe29-b5f9-4936-8a82-3a30ef375d76", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch4/record-ready", + }, + shape = "circle", + width = 32, + x = 448, + y = 480, + }, + { + height = 32, + id = "94a388fd-d187-49dc-913b-d05a7e7c9054", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch5/record-ready", + }, + shape = "circle", + width = 32, + x = 576, + y = 480, + }, + { + height = 32, + id = "c4514b7c-ce78-44ca-a1f0-e047f3529223", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch6/record-ready", + }, + shape = "circle", + width = 32, + x = 704, + y = 480, + }, + { + height = 32, + id = "4ee345b9-06f7-4127-a8a0-8fdfd5839e80", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch7/record-ready", + }, + shape = "circle", + width = 32, + x = 832, + y = 480, + }, + { + height = 32, + id = "79d53c50-652c-4074-8446-40fbc0dc0c05", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch8/record-ready", + }, + shape = "circle", + width = 32, + x = 960, + y = 480, + }, + { + height = 480, + id = "d44fcac3-7dc1-48f7-b2c9-6e6bb2d4325f", + labelOne = { + angle = 270, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 270, + position = "rightOfRight", + sizeConstrained = true, + }, + mappings = { + "main/fader", + "main/fader/touch", + }, + shape = "rectangle", + width = 32, + x = 1024, + y = 64, + }, + { + height = 32, + id = "861c5a09-f033-49db-b913-b479bee77aee", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "marker", + }, + shape = "rectangle", + width = 32, + x = 1088, + y = 288, + }, + { + height = 32, + id = "63bc9c6f-e564-48c2-a3a1-9f4943f74aa2", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch-right", + }, + shape = "rectangle", + width = 32, + x = 1216, + y = 64, + }, + { + height = 32, + id = "dccbdaff-736e-42f3-8ded-96cd83a79ef8", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "read", + }, + shape = "rectangle", + width = 32, + x = 1088, + y = 352, + }, + { + height = 32, + id = "6bd70d81-77c7-4a8d-9327-36bfb50b6d3e", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "write", + }, + shape = "rectangle", + width = 32, + x = 1088, + y = 416, + }, + { + height = 32, + id = "439a498a-baea-4b2e-b105-c5102dad2717", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "ch-left", + }, + shape = "rectangle", + width = 32, + x = 1152, + y = 64, + }, + { + height = 32, + id = "1fd6fd2f-7951-46db-b223-4eeb018a95bb", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "bank-left", + }, + shape = "rectangle", + width = 32, + x = 1152, + y = 128, + }, + { + height = 32, + id = "5a5ab1d2-d3fd-4f0f-a479-4167ae7f7527", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "bank-right", + }, + shape = "rectangle", + width = 32, + x = 1216, + y = 128, + }, + { + height = 32, + id = "35ffd77c-f379-4a4e-b5c1-0f8a5ae46a18", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "rewind", + }, + shape = "rectangle", + width = 32, + x = 1152, + y = 224, + }, + { + height = 32, + id = "40313ac9-41dc-40d0-a95d-5df07fe57c9a", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "fast-fwd", + }, + shape = "rectangle", + width = 32, + x = 1216, + y = 224, + }, + { + height = 32, + id = "9a85c51b-8a74-4c06-b4ba-7dfa5f126a5d", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "play", + }, + shape = "rectangle", + width = 32, + x = 1152, + y = 288, + }, + { + height = 32, + id = "377c7fff-bcc6-4c4b-8efe-70795f110043", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "stop", + }, + shape = "rectangle", + width = 32, + x = 1216, + y = 288, + }, + { + height = 32, + id = "ebae16ac-5eaf-4919-ab6e-bf3aa667aaf8", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "record", + }, + shape = "rectangle", + width = 32, + x = 1152, + y = 352, + }, + { + height = 32, + id = "df79aada-345e-4458-9c46-4744cc1a2f06", + labelOne = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "cycle", + }, + shape = "rectangle", + width = 32, + x = 1216, + y = 352, + }, + { + height = 32, + id = "d9daf5d8-8384-4ea5-8f23-a6b528a78461", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "belowBottom", + sizeConstrained = true, + }, + mappings = { + "zoom", + }, + shape = "rectangle", + width = 96, + x = 1152, + y = 416, + }, + { + height = 32, + id = "903f3a94-5d67-4a9d-94ca-44ef67257aa6", + labelOne = { + angle = 0, + position = "aboveTop", + sizeConstrained = true, + }, + labelTwo = { + angle = 0, + position = "center", + sizeConstrained = true, + }, + mappings = { + "jog", + "scrub", + }, + shape = "circle", + width = 96, + x = 1152, + y = 480, + }, + }, + gridDivisionCount = 2, + gridSize = 32, +} + +return commons.create_compartment { + companion_data = companion_data, + support_x_touch_colors = false, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/mackie/control-universal-lib/preset-common.luau b/plugin-reaper-realearn/resources/controller-presets/factory/mackie/control-universal-lib/preset-common.luau new file mode 100644 index 0000000..66c3d97 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/mackie/control-universal-lib/preset-common.luau @@ -0,0 +1,729 @@ +--!strict + +local realearn = require("realearn") + +local module = {} + +export type McuPresetConfig = { + companion_data: any, + support_x_touch_colors: boolean, +} + +function module.create_compartment(config: McuPresetConfig): realearn.Compartment + local channel_count = 8; + + -- Replace with "XTouchMackieLcd" in order to be able to benefit from colors when using X-Touch. + local lcd_kind = "MackieLcd"; + + local binary_eight = { + [0] = "000", + [1] = "001", + [2] = "010", + [3] = "011", + [4] = "100", + [5] = "101", + [6] = "110", + [7] = "111", + } + + local groups: { realearn.Group } = { + { + id = "fader", + name = "Fader", + }, + { + id = "v-pot", + name = "V-Pot", + }, + { + id = "v-select", + name = "V-Select", + }, + { + id = "fader-touch", + name = "Fader Touch", + }, + { + id = "select", + name = "Select", + }, + { + id = "mute", + name = "Mute", + }, + { + id = "solo", + name = "Solo", + }, + { + id = "record-ready", + name = "Record-ready", + }, + { + id = "v-pot-leds", + name = "V-Pot LEDs", + }, + { + id = "lcd", + name = "LCD", + }, + { + id = "meter", + name = "Meter", + }, + } + + local mappings: { realearn.Mapping } = { + { + id = "main/fader", + group = "fader", + source = realearn.Source.MidiPitchBendChangeValue { + channel = 8, + }, + target = realearn.Target.Virtual { + id = "main/fader", + }, + }, + { + id = "jog", + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 60, + character = "Relative1", + fourteen_bit = false, + }, + target = realearn.Target.Virtual { + id = "jog", + }, + }, + { + id = "main/fader/touch", + group = "fader-touch", + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 112, + }, + target = realearn.Target.Virtual { + id = "main/fader/touch", + character = "Button", + learnable = false, + }, + }, + { + id = "marker", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 84, + }, + target = realearn.Target.Virtual { + id = "marker", + character = "Button", + }, + }, + { + id = "read", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 74, + }, + target = realearn.Target.Virtual { + id = "read", + character = "Button", + }, + }, + { + id = "write", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 75, + }, + target = realearn.Target.Virtual { + id = "write", + character = "Button", + }, + }, + { + id = "ch-left", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 48, + }, + target = realearn.Target.Virtual { + id = "ch-left", + character = "Button", + }, + }, + { + id = "ch-right", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 49, + }, + target = realearn.Target.Virtual { + id = "ch-right", + character = "Button", + }, + }, + { + id = "bank-left", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 46, + }, + target = realearn.Target.Virtual { + id = "bank-left", + character = "Button", + }, + }, + { + id = "bank-right", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 47, + }, + target = realearn.Target.Virtual { + id = "bank-right", + character = "Button", + }, + }, + { + id = "rewind", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 91, + }, + target = realearn.Target.Virtual { + id = "rewind", + character = "Button", + }, + }, + { + id = "fast-fwd", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 92, + }, + target = realearn.Target.Virtual { + id = "fast-fwd", + character = "Button", + }, + }, + { + id = "play", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 94, + }, + target = realearn.Target.Virtual { + id = "play", + character = "Button", + }, + }, + { + id = "stop", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 93, + }, + target = realearn.Target.Virtual { + id = "stop", + character = "Button", + }, + }, + { + id = "record", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 95, + }, + target = realearn.Target.Virtual { + id = "record", + character = "Button", + }, + }, + { + id = "cycle", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 86, + }, + target = realearn.Target.Virtual { + id = "cycle", + character = "Button", + }, + }, + { + id = "zoom", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 100, + }, + target = realearn.Target.Virtual { + id = "zoom", + character = "Button", + }, + }, + { + id = "scrub", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 101, + }, + target = realearn.Target.Virtual { + id = "scrub", + character = "Button", + }, + }, + { + id = "cursor-left", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 98, + }, + target = realearn.Target.Virtual { + id = "cursor-left", + character = "Button", + }, + }, + { + id = "cursor-right", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 99, + }, + target = realearn.Target.Virtual { + id = "cursor-right", + character = "Button", + }, + }, + { + id = "cursor-up", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 96, + }, + target = realearn.Target.Virtual { + id = "cursor-up", + character = "Button", + }, + }, + { + id = "cursor-down", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 97, + }, + target = realearn.Target.Virtual { + id = "cursor-down", + character = "Button", + }, + }, + { + id = "nudge", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 85, + }, + target = realearn.Target.Virtual { + id = "nudge", + character = "Button", + }, + }, + { + id = "drop", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 87, + }, + target = realearn.Target.Virtual { + id = "drop", + character = "Button", + }, + }, + { + id = "replace", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 88, + }, + target = realearn.Target.Virtual { + id = "replace", + character = "Button", + }, + }, + { + id = "click", + source = { + kind = "MidiNoteVelocity", + channel = 0, + key_number = 89, + }, + target = realearn.Target.Virtual { + id = "click", + character = "Button", + }, + }, + { + id = "solo", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 90, + }, + target = realearn.Target.Virtual { + id = "solo", + character = "Button", + }, + }, + { + id = "f1", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 54, + }, + target = realearn.Target.Virtual { + id = "f1", + character = "Button", + }, + }, + { + id = "f2", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 55, + }, + target = realearn.Target.Virtual { + id = "f2", + character = "Button", + }, + }, + { + id = "f3", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 56, + }, + target = realearn.Target.Virtual { + id = "f3", + character = "Button", + }, + }, + { + id = "f4", + source = { + kind = "MidiNoteVelocity", + channel = 0, + key_number = 57, + }, + target = realearn.Target.Virtual { + id = "f4", + character = "Button", + }, + }, + { + id = "f5", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 58, + }, + target = realearn.Target.Virtual { + id = "f5", + character = "Button", + }, + }, + { + id = "f6", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 59, + }, + target = realearn.Target.Virtual { + id = "f6", + character = "Button", + }, + }, + { + id = "smpte-beats", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 53, + }, + target = realearn.Target.Virtual { + id = "smpte-beats", + character = "Button", + }, + }, + { + id = "lcd/assignment", + group = "lcd", + control_enabled = false, + source = realearn.Source.MackieSevenSegmentDisplay { + }, + target = realearn.Target.Virtual { + id = "lcd/assignment", + }, + }, + { + id = "lcd/timecode", + group = "lcd", + control_enabled = false, + source = realearn.Source.MackieSevenSegmentDisplay { + scope = "Tc", + }, + target = realearn.Target.Virtual { + id = "lcd/timecode", + }, + }, + } + + -- For each channel + for ch = 0, channel_count - 1 do + local human_ch = ch + 1 + local prefix = "ch"..human_ch.."/" + local v_select = realearn.Mapping { + id = prefix.."v-select", + group = "v-select", + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 32 + ch, + }, + target = realearn.Target.Virtual { + id = prefix.."v-select", + character = "Button", + }, + } + local fader_touch = realearn.Mapping { + id = prefix.."fader/touch", + group = "fader-touch", + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 104 + ch, + }, + target = realearn.Target.Virtual { + id = prefix.."fader/touch", + character = "Button", + learnable = false, + }, + } + local select = realearn.Mapping { + id = prefix.."select", + group = "select", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 24 + ch, + }, + target = realearn.Target.Virtual { + id = prefix.."select", + character = "Button", + }, + } + local fader = realearn.Mapping { + id = prefix.."fader", + group = "fader", + source = realearn.Source.MidiPitchBendChangeValue { + channel = ch, + }, + target = realearn.Target.Virtual { + id = prefix.."fader", + }, + } + local v_pot_control = realearn.Mapping { + id = prefix.."v-pot/control", + group = "v-pot", + feedback_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 16 + ch, + character = "Relative3", + fourteen_bit = false, + }, + glue = { + step_factor_interval = {1, 100}, + }, + target = realearn.Target.Virtual { + id = prefix.."v-pot", + }, + } + local v_pot_feedback_default = realearn.Mapping { + id = prefix.."v-pot/feedback", + group = "v-pot-leds", + control_enabled = false, + source = realearn.Source.MidiRaw { + pattern = "B0 3"..ch.." [0000 dcba]", + }, + glue = { + source_interval = {0, 0.75}, + }, + target = realearn.Target.Virtual { + id = prefix.."v-pot", + }, + } + local v_pot_feedback_wrap = realearn.Mapping { + id = prefix.."v-pot/wrap", + group = "v-pot-leds", + control_enabled = false, + source = realearn.Source.MidiRaw { + pattern = "B0 3"..ch.." [0010 dcba]", + }, + glue = { + source_interval = {0, 0.75}, + }, + target = realearn.Target.Virtual { + id = prefix.."v-pot/wrap", + }, + } + local v_pot_feedback_boost_cut = realearn.Mapping { + id = prefix.."v-pot/boost-cut", + group = "v-pot-leds", + control_enabled = false, + source = realearn.Source.MidiRaw { + pattern = "B0 3"..ch.." [0001 dcba]", + }, + glue = { + source_interval = {0.05, 0.75}, + }, + target = realearn.Target.Virtual { + id = prefix.."v-pot/boost-cut", + }, + } + local v_pot_feedback_single = realearn.Mapping { + id = prefix.."v-pot/single", + group = "v-pot-leds", + control_enabled = false, + source = realearn.Source.MidiRaw { + pattern = "B0 3"..ch.." [0000 dcba]", + }, + glue = { + source_interval = {0, 0.75}, + }, + target = realearn.Target.Virtual { + id = prefix.."v-pot/single", + }, + } + local v_pot_feedback_spread = realearn.Mapping { + id = prefix.."v-pot/spread", + group = "v-pot-leds", + control_enabled = false, + source = realearn.Source.MidiRaw { + pattern = "B0 3"..ch.." [0011 dcba]", + }, + glue = { + source_interval = {0, 0.4}, + }, + target = realearn.Target.Virtual { + id = prefix.."v-pot/spread", + }, + } + local mute = realearn.Mapping { + id = prefix.."mute", + group = "mute", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 16 + ch, + }, + target = { + kind = "Virtual", + id = prefix.."mute", + character = "Button", + }, + } + local solo = realearn.Mapping { + id = prefix.."solo", + group = "solo", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 8 + ch, + }, + target = realearn.Target.Virtual { + id = prefix.."solo", + character = "Button", + }, + } + local record_ready = realearn.Mapping { + id = prefix.."record-ready", + group = "record-ready", + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 0 + ch, + }, + target = realearn.Target.Virtual { + id = prefix.."record-ready", + character = "Button", + }, + } + local normal_lcd_line_1 = realearn.Source.MackieLcd { + channel = ch, + line = 0, + } + local x_touch_lcd_line_1 = realearn.Source.XTouchMackieLcd { + channel = ch, + line = 0, + } + local lcd_line1 = realearn.Mapping { + id = prefix.."lcd/line1", + group = "lcd", + control_enabled = false, + source = if config.support_x_touch_colors then x_touch_lcd_line_1 else normal_lcd_line_1, + target = realearn.Target.Virtual { + id = prefix.."lcd/line1", + }, + } + local normal_lcd_line_2 = realearn.Source.MackieLcd { + channel = ch, + line = 1, + } + local x_touch_lcd_line_2 = realearn.Source.XTouchMackieLcd { + channel = ch, + line = 1, + } + local lcd_line2 = realearn.Mapping { + id = prefix.."lcd/line2", + group = "lcd", + control_enabled = false, + source = if config.support_x_touch_colors then x_touch_lcd_line_2 else normal_lcd_line_2, + target = realearn.Target.Virtual { + id = prefix.."lcd/line2", + }, + } + local meter = realearn.Mapping { + id = prefix.."meter/peak", + group = "meter", + control_enabled = false, + source = realearn.Source.MidiRaw { + pattern = "D0 [0"..binary_eight[ch].." dcba]", + }, + target = realearn.Target.Virtual { + id = prefix.."meter/peak", + }, + } + table.insert(mappings, v_select) + table.insert(mappings, fader_touch) + table.insert(mappings, select) + table.insert(mappings, fader) + table.insert(mappings, v_pot_control) + table.insert(mappings, v_pot_feedback_default) + table.insert(mappings, v_pot_feedback_wrap) + table.insert(mappings, v_pot_feedback_boost_cut) + table.insert(mappings, v_pot_feedback_single) + table.insert(mappings, v_pot_feedback_spread) + table.insert(mappings, mute) + table.insert(mappings, solo) + table.insert(mappings, record_ready) + table.insert(mappings, lcd_line1) + table.insert(mappings, lcd_line2) + table.insert(mappings, meter) + end + + return realearn.Compartment { + groups = groups, + mappings = mappings, + custom_data = { + companion = config.companion_data, + }, + } +end + +return module diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/mackie/control-universal.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/mackie/control-universal.preset.luau new file mode 100644 index 0000000..7fdce92 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/mackie/control-universal.preset.luau @@ -0,0 +1,21 @@ +--- name: Mackie Control Universal +--- realearn_version: 2.16.0 +--- author: helgoboss +--- device_manufacturer: Mackie +--- device_name: Control Universal +--- description: | +--- This controller preset implements the Mackie Control Universal (MCU) MIDI protocol. It is supported by many +--- DAW control surfaces out there. I have tested it with Behringer X-Touch One, Behringer X-Touch Compact and +--- iCON Platform M+. +--- setup_instructions: | +--- You probably must put your controller into MCU mode for using this controller preset. +--- provided_schemes: [daw] + +--!strict + +local commons = require("mackie/control-universal-lib/preset-common") + +return commons.create_compartment { + companion_data = nil, + support_x_touch_colors = false, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/midi_script_source_runtime.luau b/plugin-reaper-realearn/resources/controller-presets/factory/midi_script_source_runtime.luau new file mode 120000 index 0000000..2a8b541 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/midi_script_source_runtime.luau @@ -0,0 +1 @@ +../../api/luau/midi_script_source_runtime.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-lib/compartment-common.luau b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-lib/compartment-common.luau new file mode 100644 index 0000000..bcf3054 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-lib/compartment-common.luau @@ -0,0 +1,330 @@ +--!strict + +local midi_script = require("midi_script_source_runtime") + +local module = {} + +--- There's no way to put pads in a pulsing state with the true RGB sys-ex messages, so +--- we are back to an old-fashioned color palette. Whatever, close enough. +local color_palette: { midi_script.RgbColor } = { + { r = 0x61, g = 0x61, b = 0x61 }, + { r = 0xb3, g = 0xb3, b = 0xb3 }, + { r = 0xdd, g = 0xdd, b = 0xdd }, + { r = 0xff, g = 0xff, b = 0xff }, + { r = 0xfc, g = 0xb3, b = 0xb3 }, + { r = 0xfa, g = 0x61, b = 0x61 }, + { r = 0xdd, g = 0x61, b = 0x61 }, + { r = 0xb3, g = 0x61, b = 0x61 }, + { r = 0xfe, g = 0xf3, b = 0xd5 }, + { r = 0xfb, g = 0xb3, b = 0x61 }, + { r = 0xdd, g = 0x8c, b = 0x61 }, + { r = 0xb3, g = 0x76, b = 0x61 }, + { r = 0xfc, g = 0xee, b = 0xa1 }, + { r = 0xfb, g = 0xff, b = 0x61 }, + { r = 0xdd, g = 0xdd, b = 0x61 }, + { r = 0xb3, g = 0xb3, b = 0x61 }, + { r = 0xdd, g = 0xff, b = 0xa1 }, + { r = 0xc2, g = 0xff, b = 0x61 }, + { r = 0xa1, g = 0xdd, b = 0x61 }, + { r = 0x81, g = 0xb3, b = 0x61 }, + { r = 0xc2, g = 0xff, b = 0xb3 }, + { r = 0x61, g = 0xfe, b = 0x61 }, + { r = 0x61, g = 0xdd, b = 0x61 }, + { r = 0x61, g = 0xb3, b = 0x61 }, + { r = 0xc2, g = 0xff, b = 0xc2 }, + { r = 0x61, g = 0xfe, b = 0x8c }, + { r = 0x61, g = 0xdd, b = 0x76 }, + { r = 0x61, g = 0xb3, b = 0x6b }, + { r = 0xc2, g = 0xfe, b = 0xcc }, + { r = 0x61, g = 0xfe, b = 0xcc }, + { r = 0x61, g = 0xdd, b = 0xa1 }, + { r = 0x61, g = 0xb3, b = 0x81 }, + { r = 0xc2, g = 0xfe, b = 0xf3 }, + { r = 0x61, g = 0xfd, b = 0xe9 }, + { r = 0x61, g = 0xdd, b = 0xc2 }, + { r = 0x61, g = 0xb3, b = 0x96 }, + { r = 0xc2, g = 0xf3, b = 0xff }, + { r = 0x61, g = 0xee, b = 0xff }, + { r = 0x61, g = 0xc7, b = 0xdd }, + { r = 0x61, g = 0xa1, b = 0xb3 }, + { r = 0xc2, g = 0xdd, b = 0xff }, + { r = 0x61, g = 0xc7, b = 0xff }, + { r = 0x61, g = 0xa1, b = 0xdd }, + { r = 0x61, g = 0x81, b = 0xb3 }, + { r = 0xa1, g = 0x8c, b = 0xff }, + { r = 0x61, g = 0x61, b = 0xff }, + { r = 0x61, g = 0x61, b = 0xdd }, + { r = 0x61, g = 0x61, b = 0xb3 }, + { r = 0xcc, g = 0xb3, b = 0xff }, + { r = 0xa1, g = 0x61, b = 0xff }, + { r = 0x81, g = 0x61, b = 0xdd }, + { r = 0x76, g = 0x61, b = 0xb3 }, + { r = 0xfe, g = 0xb3, b = 0xff }, + { r = 0xfe, g = 0x61, b = 0xff }, + { r = 0xdd, g = 0x61, b = 0xdd }, + { r = 0xb3, g = 0x61, b = 0xb3 }, + { r = 0xfd, g = 0xb3, b = 0xd5 }, + { r = 0xfc, g = 0x61, b = 0xc2 }, + { r = 0xdd, g = 0x61, b = 0xa1 }, + { r = 0xb3, g = 0x61, b = 0x8c }, + { r = 0xfa, g = 0x76, b = 0x61 }, + { r = 0xe9, g = 0xb3, b = 0x61 }, + { r = 0xdd, g = 0xc2, b = 0x61 }, + { r = 0xa1, g = 0xa1, b = 0x61 }, + { r = 0x61, g = 0xb3, b = 0x61 }, + { r = 0x61, g = 0xb3, b = 0x8c }, + { r = 0x61, g = 0x8c, b = 0xd5 }, + { r = 0x61, g = 0x61, b = 0xff }, + { r = 0x61, g = 0xb3, b = 0xb3 }, + { r = 0x8c, g = 0x62, b = 0xf3 }, + { r = 0xcc, g = 0xb3, b = 0xc2 }, + { r = 0x8c, g = 0x76, b = 0x81 }, + { r = 0xfa, g = 0x61, b = 0x61 }, + { r = 0xf3, g = 0xff, b = 0xa1 }, + { r = 0xee, g = 0xfc, b = 0x61 }, + { r = 0xcc, g = 0xff, b = 0x60 }, + { r = 0x76, g = 0xdd, b = 0x61 }, + { r = 0x61, g = 0xfe, b = 0xcc }, + { r = 0x61, g = 0xe9, b = 0xff }, + { r = 0x61, g = 0xa1, b = 0xff }, + { r = 0x8c, g = 0x61, b = 0xff }, + { r = 0xcc, g = 0x61, b = 0xfc }, + { r = 0xee, g = 0x8c, b = 0xdd }, + { r = 0xa1, g = 0x76, b = 0x61 }, + { r = 0xfb, g = 0xa1, b = 0x61 }, + { r = 0xdd, g = 0xf9, b = 0x62 }, + { r = 0xd5, g = 0xff, b = 0x8c }, + { r = 0x61, g = 0xfe, b = 0x61 }, + { r = 0xb3, g = 0xff, b = 0xa1 }, + { r = 0xcc, g = 0xfc, b = 0xd5 }, + { r = 0xb3, g = 0xfe, b = 0xf6 }, + { r = 0xcc, g = 0xe4, b = 0xff }, + { r = 0xa1, g = 0xc2, b = 0xf6 }, + { r = 0xd5, g = 0xc2, b = 0xf9 }, + { r = 0xf9, g = 0x8c, b = 0xff }, + { r = 0xfc, g = 0x61, b = 0xcc }, + { r = 0xfb, g = 0xc2, b = 0x61 }, + { r = 0xf3, g = 0xee, b = 0x61 }, + { r = 0xe4, g = 0xff, b = 0x61 }, + { r = 0xdd, g = 0xcc, b = 0x61 }, + { r = 0xb3, g = 0xa1, b = 0x61 }, + { r = 0x61, g = 0xba, b = 0x76 }, + { r = 0x76, g = 0xc2, b = 0x8c }, + { r = 0x81, g = 0x81, b = 0xa1 }, + { r = 0x81, g = 0x8c, b = 0xcc }, + { r = 0xcc, g = 0xaa, b = 0x81 }, + { r = 0xdd, g = 0x61, b = 0x61 }, + { r = 0xf9, g = 0xb3, b = 0xa1 }, + { r = 0xf9, g = 0xba, b = 0x76 }, + { r = 0xfc, g = 0xf3, b = 0x8d }, + { r = 0xe9, g = 0xf9, b = 0xa1 }, + { r = 0xd5, g = 0xee, b = 0x76 }, + { r = 0x81, g = 0x81, b = 0xa1 }, + { r = 0xf9, g = 0xf9, b = 0xd5 }, + { r = 0xdd, g = 0xfc, b = 0xe4 }, + { r = 0xe9, g = 0xe9, b = 0xff }, + { r = 0xe4, g = 0xd5, b = 0xff }, + { r = 0xb3, g = 0xb3, b = 0xb3 }, + { r = 0xd5, g = 0xd5, b = 0xd5 }, + { r = 0xf9, g = 0xff, b = 0xff }, + { r = 0xe9, g = 0x61, b = 0x61 }, + { r = 0xaa, g = 0x62, b = 0x61 }, + { r = 0x81, g = 0xf6, b = 0x62 }, + { r = 0x61, g = 0xb3, b = 0x61 }, + { r = 0xf3, g = 0xee, b = 0x61 }, + { r = 0xb3, g = 0xa1, b = 0x61 }, + { r = 0xee, g = 0xc2, b = 0x61 }, + { r = 0xc2, g = 0x76, b = 0x61 }, +} + +--- Returns the 0-based index within the given palette array. +local function find_closest_color_in_palette(color: midi_script.RgbColor, palette: { midi_script.RgbColor }): number + local ifurthest = 0 + local furthest = 3 * math.pow(255, 2) + 1 + for i, c in palette do + if color.r == c.r and color.g == c.g and color.b == c.b then + return i - 1 + end + local distance = math.pow((color.r - c.r), 2) + math.pow((color.g - c.g), 2) + math.pow((color.b - c.b), 2) + if distance < furthest then + furthest = distance + ifurthest = i - 1 + end + end + return ifurthest +end + +type FeedbackEntry = { + behavior: number, + color: (number | midi_script.RgbColor)?, + brightness: number?, +} +local solid = 0x90 +local flashing = 0x91 +local pulsing = 0x92 +local black_color = 0 +local green_color = 0x15 +local white_color = { r = 255, g = 255, b = 255 } +local red_color = { r = 255, g = 0, b = 0 } + +local dark = 0.75 +local medium = 0.90 + +local function create_feedback_table(play_color: number?): { [string]: FeedbackEntry? } + return { + ["playtime.slot_state.empty"] = { + behavior = solid, + color = black_color, + }, + ["playtime.slot_state.armed"] = { + behavior = solid, + color = red_color, + brightness = dark, + }, + ["playtime.slot_state.stopped"] = { + behavior = solid, + brightness = dark, + }, + ["playtime.slot_state.ignited"] = { + behavior = solid, + brightness = dark, + }, + ["playtime.slot_state.scheduled_for_play_start"] = { + behavior = flashing, + color = play_color, + }, + ["playtime.slot_state.playing"] = { + behavior = pulsing, + color = play_color, + }, + ["playtime.slot_state.paused"] = { + behavior = solid, + brightness = medium, + }, + ["playtime.slot_state.scheduled_for_play_stop"] = { + behavior = flashing, + }, + ["playtime.slot_state.scheduled_for_play_restart"] = { + behavior = flashing, + color = play_color, + }, + ["playtime.slot_state.scheduled_for_record_start"] = { + behavior = solid, + color = red_color, + brightness = medium, + }, + ["playtime.slot_state.recording"] = { + behavior = pulsing, + color = red_color, + }, + ["playtime.slot_state.scheduled_for_record_stop"] = { + behavior = flashing, + color = red_color, + } + } +end + +-- Uses original color when playing (currently disabled, will probably be a configuration option in the future) +local colorful_feedback_table = create_feedback_table(nil) + +-- Uses green color when playing +local green_feedback_table = create_feedback_table(green_color) + +local function build_messages( + pad_index: number, + behavior: number, + color_index: number +): { midi_script.MidiMessage } + if behavior == pulsing then + return { + { 0x92, pad_index, color_index }, + } + elseif behavior == flashing then + -- Flashing between actual color and black + return { + { 0x90, pad_index, color_index }, + { 0x91, pad_index, black_color }, + } + else + -- Solid + return { + { 0x90, pad_index, color_index }, + } + end +end + +local function adjust_brightness(c: midi_script.RgbColor, factor: number): midi_script.RgbColor + return { + r = math.floor(c.r * factor), + g = math.floor(c.g * factor), + b = math.floor(c.b * factor), + } +end + +local function transform_and_find_closest_color_in_palette(original_color: midi_script.RgbColor, brightness: number?): number + local transformed_color = if brightness then adjust_brightness(original_color, brightness) else original_color + local index = find_closest_color_in_palette(transformed_color, color_palette) + if index == 0 then + -- Don't let it become black just because of the darkening + return find_closest_color_in_palette(original_color, color_palette) + end + return index +end + +local function get_palette_color_index(entry: FeedbackEntry, rgb_color: midi_script.RgbColor?): number + if type(entry.color) == "number" then + return entry.color + end + local final_rgb_color = entry.color or rgb_color or white_color + return transform_and_find_closest_color_in_palette(final_rgb_color, entry.brightness) +end + + +function module.pad_script( + pad_index: number, + y: midi_script.InputValue, + context: midi_script.Context +): midi_script.Output + local address = pad_index + local off_output = { + address = pad_index, + messages = build_messages(pad_index, solid, black_color), + } + -- Handle "off" feedback + if y == nil then + return off_output + end + -- Handle numeric feedback + if type(y) == "number" then + -- Translate number to solid feedback color + local color_index = math.floor(y * 127) + return { + address = address, + messages = { + { solid, pad_index, color_index }, + }, + } + end + -- Handle text feedback + if type(y) == "string" then + -- This is a grid controller, typically used for controlling a Playtime matrix, so we should be able to + -- handle text feedback that conforms to Playtime's clip state convention. + local entry = green_feedback_table[y] + if entry == nil then + -- Unknown texts switch the LED off + return off_output + end + local color_index = get_palette_color_index(entry, context.feedback_event.color) + return { + address = pad_index, + messages = build_messages(pad_index, entry.behavior, color_index), + } + end + -- Complex feedback is highly individual. It doesn't make sense to handle this in a general-purpose controller preset. + return { + address = address, + messages = {}, + } +end + +return module diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-mini-mk3.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-mini-mk3.preset.luau new file mode 100644 index 0000000..9b4f1cc --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-mini-mk3.preset.luau @@ -0,0 +1,152 @@ +--- name: Launchpad Mini mk3 - Live mode +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad Mini mk3 +--- description: | +--- This controller preset exposes all control elements of the Launchpad Mini mk3 in a neutral way. It supports +--- ReaLearn color feedback as well as Playtime slot status feedback. +--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 13 01 00 00 * F7 +--- midi_output_port_patterns: ["macos:*DAW*", "windows:LPMiniMK3 MIDI"] +--- provided_schemes: [novation/launchpad-mini-mk3/live, grid] + +--!strict + +-- Requires + +local preset_runtime = require("preset_runtime") +local realearn = require("realearn") + +-- Define MIDI scripts + +local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + +-- Single buttons + +local function simple_button(id: string, name: string, cc: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local init_mapping = realearn.Mapping { + name = "Enter DAW mode and select session layout", + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = [[ +local standalone_mode = 0x00 +local daw_mode = 0x01 +local session_layout = 0x00 +local mode = if y == nil then standalone_mode else daw_mode +local enter_or_leave_daw_mode_msg = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0D, 0x10, mode, 0xF7 } +local select_session_layout = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0D, 0x00, session_layout, 0xF7 } + +local messages = { + enter_or_leave_daw_mode_msg, +} +if y then + table.insert(messages, select_session_layout) +end + +return { + messages = messages +} + ]] + }, + target = realearn.Target.Dummy { + } +} + +local mappings = { + init_mapping, + + simple_button("cursor-up", "Up", 91), + simple_button("cursor-down", "Down", 92), + simple_button("cursor-left", "Left", 93), + simple_button("cursor-right", "Right", 94), + simple_button("session", "Session", 95), + simple_button("drums", "Drums", 96), + simple_button("keys", "Keys", 97), + simple_button("user", "User", 98), +} + +-- Clip launch buttons + +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 7 do + local human_row = row + 1 + local key_number_offset = 11 + (7 - row) * 10 + local id = `col{human_col}/row{human_row}/pad` + local control_mapping = realearn.Mapping { + id = id, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(key_number_offset + col), + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, control_mapping) + table.insert(mappings, feedback_mapping) + end +end + +-- Scene launch buttons +for row = 0, 7 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 19 + (7 - row) * 10, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +return realearn.Compartment { + common_lua = common_lua, + mappings = mappings, + custom_data = { + grid = { + column_count = 8, + row_count = 8, + }, + } +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-mk1.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-mk1.preset.luau new file mode 100644 index 0000000..92e9272 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-mk1.preset.luau @@ -0,0 +1,140 @@ +--- name: Launchpad mk1 - Live mode +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad mk1 +--- description: | +--- This controller preset exposes all control elements of the Launchpad mk1 in a neutral way. It supports Playtime slot status feedback. +--- +--- This device cannot be auto-detected because it doesn't support the MIDI device inquiry protocol. +--- provided_schemes: [novation/launchpad-mk1, grid] + +--!strict + +-- Requires + +local realearn = require("realearn") + +-- Single buttons + +local function simple_button(id: string, name: string, cc: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local init_mapping = realearn.Mapping { + name = "Enable flashing LEDs", + source = realearn.Source.MidiRaw { + pattern = "B0 00 28", + }, + target = realearn.Target.Dummy { + } +} + +local mappings = { + init_mapping, + simple_button("cursor-up", "Up", 104), + simple_button("cursor-down", "Down", 105), + simple_button("cursor-left", "Left", 106), + simple_button("cursor-right", "Right", 107), + simple_button("session", "session", 108), + simple_button("user-1", "user 1", 109), + simple_button("user-2", "user 2", 110), + simple_button("mixer", "mixer", 111), +} + +-- Clip launch buttons + +local leds = { + off = 12, + red_low = 13, + red_full = 15, + amber_low = 29, + amber_full = 63, + yellow_full = 62, + green_low = 28, + green_full = 60, + red_flashing = 11, + amber_flashing = 59, + yellow_flashing = 58, + green_flashing = 56, +} + +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 7 do + local human_row = row + 1 + local key_number_offset = row * 16 + local id = `col{human_col}/row{human_row}/pad` + local control_mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + glue = realearn.Glue { + feedback_value_table = { + kind = "FromTextToDiscrete", + value = { + ["playtime.slot_state.empty"] = leds.off, + ["playtime.slot_state.armed"] = leds.red_low, + ["playtime.slot_state.stopped"] = leds.amber_low, + ["playtime.slot_state.ignited"] = leds.amber_low, + ["playtime.slot_state.scheduled_for_play_start"] = leds.green_flashing, + ["playtime.slot_state.playing"] = leds.green_full, + ["playtime.slot_state.paused"] = leds.amber_low, + ["playtime.slot_state.scheduled_for_play_stop"] = leds.amber_flashing, + ["playtime.slot_state.scheduled_for_play_restart"] = leds.green_flashing, + ["playtime.slot_state.scheduled_for_record_start"] = leds.red_flashing, + ["playtime.slot_state.recording"] = leds.red_full, + ["playtime.slot_state.scheduled_for_record_stop"] = leds.green_flashing, + }, + } + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, control_mapping) + end +end + +-- Scene launch buttons +for row = 0, 7 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = 8 + row * 16, + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +return realearn.Compartment { + mappings = mappings, + custom_data = { + grid = { + column_count = 8, + row_count = 8, + }, + } +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-pro-mk2.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-pro-mk2.preset.luau new file mode 100644 index 0000000..fa1d934 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-pro-mk2.preset.luau @@ -0,0 +1,160 @@ +--- name: Launchpad Pro mk2 - Live mode +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad Pro mk2 +--- description: | +--- This controller preset exposes all control elements of the Launchpad Pro mk2 in a neutral way. It supports +--- ReaLearn color feedback as well as Playtime slot status feedback. +--- # I leave part of the family code dynamic. It's not always 00: https://github.com/helgoboss/helgobox/issues/939 +--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 51 00 ? 00 * F7 +--- midi_output_port_patterns: ["macos:*Live*", "windows:*Launchpad Pro"] +--- provided_schemes: [novation/launchpad-pro-mk2/live, grid] + +--!strict + +-- Requires + +local preset_runtime = require("preset_runtime") +local realearn = require("realearn") + +-- Define MIDI scripts + +local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + +-- Single buttons + +local function simple_button(id: string, name: string, cc: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local init_mapping = realearn.Mapping { + name = "Enter live mode", + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = [[ +local live_mode = 0x00 +local standalone_mode = 0x01 +local mode = if y == nil then standalone_mode else live_mode + +return { + messages = { + { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0x21, mode, 0xF7 } + } +} + ]] + }, + target = realearn.Target.Dummy { + } +} + +local mappings = { + init_mapping, + simple_button("cursor-up", "Up", 91), + simple_button("cursor-down", "Down", 92), + simple_button("cursor-left", "Left", 93), + simple_button("cursor-right", "Right", 94), + simple_button("session", "Session", 95), + simple_button("note", "Note", 96), + simple_button("device", "Device", 97), + simple_button("user", "User", 98), + simple_button("shift", "Shift", 80), + simple_button("click", "Click", 70), + simple_button("undo", "Undo", 60), + simple_button("delete", "Delete", 50), + simple_button("quantize", "Quantise", 40), + simple_button("duplicate", "Duplicate", 30), + simple_button("double", "Double", 20), + simple_button("record", "Record", 10), + simple_button("record-arm", "Record Arm", 1), + simple_button("track-select", "Track Select", 2), + simple_button("mute", "Mute", 3), + simple_button("solo", "Solo", 4), + simple_button("volume", "Volume", 5), + simple_button("pan", "Pan", 6), + simple_button("sends", "Sends", 7), + simple_button("stop-clip", "Stop Clip", 8), +} + +-- Clip launch buttons + +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 7 do + local human_row = row + 1 + local key_number_offset = 11 + (7 - row) * 10 + local id = `col{human_col}/row{human_row}/pad` + local control_mapping = realearn.Mapping { + id = id, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(key_number_offset + col), + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, control_mapping) + table.insert(mappings, feedback_mapping) + end +end + +-- Scene launch buttons +for row = 0, 7 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 19 + (7 - row) * 10, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +return realearn.Compartment { + common_lua = common_lua, + mappings = mappings, + custom_data = { + grid = { + column_count = 8, + row_count = 8, + }, + } +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-pro-mk3.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-pro-mk3.preset.luau new file mode 100644 index 0000000..a05eded --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-pro-mk3.preset.luau @@ -0,0 +1,185 @@ +--- name: Launchpad Pro mk3 - Live mode +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad Pro mk3 +--- description: | +--- This controller preset exposes all control elements of the Launchpad Pro mk3 in a neutral way. It supports +--- ReaLearn color feedback as well as Playtime slot status feedback. +--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 23 01 00 00 * F7 +--- midi_output_port_patterns: ["macos:*DAW*", "windows:*MIDIOUT3*"] +--- provided_schemes: [novation/launchpad-pro-mk3/live, grid] + +--!strict + +-- Requires + +local preset_runtime = require("preset_runtime") +local realearn = require("realearn") + +-- Define MIDI scripts + +local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + +-- Single buttons + +local function simple_button(id: string, name: string, cc: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local init_mapping = realearn.Mapping { + name = "Enter DAW mode and select session layout", + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = [[ +local standalone_mode = 0x00 +local daw_mode = 0x01 +local session_layout = 0x00 +local session_layout_page = 0x00 +local mode = if y == nil then standalone_mode else daw_mode +local enter_or_leave_daw_mode_msg = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0E, 0x10, mode, 0xF7 } +local select_session_layout = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0E, 0x00, session_layout, session_layout_page, 0x00, 0xF7 } + +local messages = { + enter_or_leave_daw_mode_msg, +} +if y then + table.insert(messages, select_session_layout) +end + +return { + messages = messages +} + ]] + }, + target = realearn.Target.Dummy { + } +} + +local mappings = { + init_mapping, + simple_button("shift", "Shift", 90), + simple_button("cursor-left", "Left", 91), + simple_button("cursor-right", "Right", 92), + simple_button("session", "Session", 93), + + simple_button("cursor-up", "Up", 80), + simple_button("cursor-down", "Down", 70), + simple_button("delete", "Clear", 60), + simple_button("duplicate", "Duplicate", 50), + simple_button("quantize", "Quantise", 40), + simple_button("fixed-length", "Fixed length", 30), + simple_button("play", "Play", 20), + simple_button("record", "Record", 10), + + simple_button("record-arm", "Record Arm", 1), + simple_button("mute", "Mute", 2), + simple_button("solo", "Solo", 3), + simple_button("volume", "Volume", 4), + simple_button("pan", "Pan", 5), + simple_button("sends", "Sends", 6), + simple_button("device", "Device", 7), + simple_button("stop-clip", "Stop Clip", 8), +} + +-- Clip launch buttons + +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 7 do + local human_row = row + 1 + local key_number_offset = 11 + (7 - row) * 10 + local id = `col{human_col}/row{human_row}/pad` + local control_mapping = realearn.Mapping { + id = id, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(key_number_offset + col), + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, control_mapping) + table.insert(mappings, feedback_mapping) + end +end + +-- Scene launch buttons +for row = 0, 7 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 19 + (7 - row) * 10, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +-- Column action buttons +for col = 0, 7 do + local human_col = col + 1 + local id = `col{human_col}/action` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 101 + col, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +return realearn.Compartment { + common_lua = common_lua, + mappings = mappings, + custom_data = { + grid = { + column_count = 8, + row_count = 8, + }, + } +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-x.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-x.preset.luau new file mode 100644 index 0000000..6749b7b --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/novation/launchpad-x.preset.luau @@ -0,0 +1,152 @@ +--- name: Launchpad X - Live mode +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad X +--- description: | +--- This controller preset exposes all control elements of the Launchpad X in a neutral way. It supports +--- ReaLearn color feedback as well as Playtime slot status feedback. +--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 03 01 00 00 * F7 +--- midi_output_port_patterns: ["macos:*DAW*", "windows:LPX MIDI"] +--- provided_schemes: [novation/launchpad-x/live, grid] + +--!strict + +-- Requires + +local preset_runtime = require("preset_runtime") +local realearn = require("realearn") + +-- Define MIDI scripts + +local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + +-- Single buttons + +local function simple_button(id: string, name: string, cc: number): realearn.Mapping + return realearn.Mapping { + id = id, + name = name, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } +end + +local init_mapping = realearn.Mapping { + name = "Enter DAW mode and select session layout", + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = [[ +local standalone_mode = 0x00 +local daw_mode = 0x01 +local session_layout = 0x00 +local mode = if y == nil then standalone_mode else daw_mode +local enter_or_leave_daw_mode_msg = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0C, 0x10, mode, 0xF7 } +local select_session_layout = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0C, 0x00, session_layout, 0xF7 } + +local messages = { + enter_or_leave_daw_mode_msg, +} +if y then + table.insert(messages, select_session_layout) +end + +return { + messages = messages +} + ]] + }, + target = realearn.Target.Dummy { + } +} + +local mappings = { + init_mapping, + + simple_button("cursor-up", "Up", 91), + simple_button("cursor-down", "Down", 92), + simple_button("cursor-left", "Left", 93), + simple_button("cursor-right", "Right", 94), + simple_button("session", "Session", 95), + simple_button("note", "Note", 96), + simple_button("custom", "Custom", 97), + simple_button("record", "Record", 98), +} + +-- Clip launch buttons + +for col = 0, 7 do + local human_col = col + 1 + for row = 0, 7 do + local human_row = row + 1 + local key_number_offset = 11 + (7 - row) * 10 + local id = `col{human_col}/row{human_row}/pad` + local control_mapping = realearn.Mapping { + id = id, + feedback_enabled = false, + source = realearn.Source.MidiNoteVelocity { + channel = 0, + key_number = key_number_offset + col, + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(key_number_offset + col), + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, control_mapping) + table.insert(mappings, feedback_mapping) + end +end + +-- Scene launch buttons +for row = 0, 7 do + local human_row = row + 1 + local id = `row{human_row}/play` + local mapping = realearn.Mapping { + id = id, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = 19 + (7 - row) * 10, + character = "Button", + }, + target = realearn.Target.Virtual { + id = id, + character = "Button", + }, + } + table.insert(mappings, mapping) +end + +return realearn.Compartment { + common_lua = common_lua, + mappings = mappings, + custom_data = { + grid = { + column_count = 8, + row_count = 8, + }, + } +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/playtime.luau b/plugin-reaper-realearn/resources/controller-presets/factory/playtime.luau new file mode 120000 index 0000000..7bb93c2 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/playtime.luau @@ -0,0 +1 @@ +../../api/luau/playtime.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/preset_runtime.luau b/plugin-reaper-realearn/resources/controller-presets/factory/preset_runtime.luau new file mode 120000 index 0000000..24f5fd6 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/preset_runtime.luau @@ -0,0 +1 @@ +../../api/luau/preset_runtime.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/realearn.luau b/plugin-reaper-realearn/resources/controller-presets/factory/realearn.luau new file mode 120000 index 0000000..fb4f211 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/realearn.luau @@ -0,0 +1 @@ +../../api/luau/realearn.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-lib/preset-common.luau b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-lib/preset-common.luau new file mode 100644 index 0000000..4b7699d --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-lib/preset-common.luau @@ -0,0 +1,722 @@ +--!strict + +local realearn = require("realearn") + +local module = {} + +export type PresetMode = "native" | "midi-absolute" | "midi-relative" + +export type PresetConfig = { + mode: PresetMode, + +} + +function module.create_compartment(config: PresetConfig): realearn.Compartment + -- Config + + -- With this, we can simulate true relative control. + -- + -- In MIDI mode, the encoders of the Console 1 send absolute control values, not relative ones. This is + -- not optimal because it means we can't define step sizes, wrap etc. However, as long as feedback is enabled, at least we don't + -- need to suffer from parameter jumps (a common disadvantage of absolute control). + -- This is because the Console 1 doesn't just use the feedback to set the LED ring, it also resets its internal encoder + -- value to the incoming feedback value. + -- + -- If we simulate relative control, we get the advantages of relative control. However, it doesn't + -- work so nicely with feedback :( The "reset-internal-encoder-value" mechanism mentioned above interferes. + -- Without further treatment, this makes the control stuck. We can make control work by not sending echo feedback + -- but then the indicated LED ring value is incorrect when we turn the encoder (however, it is correct when changing the parameter + -- in REAPER itself - as long as we don't disable feedback completely). + -- The technique we use right now is to compare with the previously sent feedback value - if available - and otherwise the previously + -- sent control value. This sort of works, but there can be glitches because it's not always sure that the last-sent feedback value + -- has actually been received by the controller. + local simulate_relative_control_in_midi_mode = config.mode == "midi-relative" + + local native_mode = config.mode == "native" + + -- Code + + local realearn = require("realearn") + + local sysex_to_relative_transformation = [[ +y_type = 1; +y = prev_timestamp == 0 ? ( + 1 +) : ( + // Implement encoder acceleration based on timestamp diffs + diff_millis = (realearn_timestamp - prev_timestamp) * 1000; + added = 10 / diff_millis; + // realearn_dbg(added); + 1 + added +); +prev_timestamp = realearn_timestamp; +]] + + local abs_to_rel_transformation = [[ +y_type = 1; +// If the last-sent feedback value is available, we use this one as reference (because the controller +// resets its internal value to the last-sent feedback value). If not (e.g. if feedback is disabled), +// we use the last-sent control value as reference. +ref = realearn_last_feedback_value == prev_last_feedback_value ? prev_x : realearn_last_feedback_value; +y = count == 0 ? ( + // On first invocation, we don't have any reference value to compare to + 0 +) : x > ref || x == 1 ? ( + // Controller sends higher number than reference value or encoder hit right boundary. Looks like an increment. + 1 +) : x < ref || x == 0 ? ( + // Controller sends lower number than reference value or encoder hit left boundary. Looks like a decrement. + -1 +) : ( + // No change + none +); +prev_last_feedback_value = last_feedback_value; +prev_x = x; +count += 1; +]] + + local mappings = {} + + local function button( + id: string, + target: realearn.Target, + name: string, + cc: number?, + on_sysex: string, + off_sysex: string + ) + local cc_source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Button", + } + if native_mode then + local on_mapping = realearn.Mapping { + id = `{id}-on`, + feedback_enabled = false, + name = name, + source = realearn.Source.MidiRaw { + pattern = on_sysex, + }, + target = target, + } + table.insert(mappings, on_mapping) + local off_mapping = realearn.Mapping { + id = `{id}-off`, + feedback_enabled = false, + name = name, + source = realearn.Source.MidiRaw { + pattern = off_sysex, + }, + glue = { + reverse = true, + }, + target = target, + } + table.insert(mappings, off_mapping) + if cc then + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + name = name, + source = cc_source, + target = target, + } + table.insert(mappings, feedback_mapping) + end + else + local mapping = realearn.Mapping { + id = id, + name = name, + source = cc_source, + target = target, + } + table.insert(mappings, mapping) + end + end + + local function named_button(id: string, name: string, cc: number?, on_sysex: string, off_sysex: string) + local target = realearn.Target.Virtual { + id = id, + character = "Button", + } + button(id, target, name, cc, on_sysex, off_sysex) + end + + local function numbered_button(index: number, name: string, cc: number, on_sysex: string, off_sysex: string) + local target = realearn.Target.Virtual { + id = index, + character = "Button", + } + button(tostring(index), target, name, cc, on_sysex, off_sysex) + end + + local function encoder(id: string, name: string, cc: number, cw_sysex: string, ccw_sysex: string) + local cc_source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Range", + -- Preventing echo feedback would make the LED ring show the incorrect value when we turn the encoder + -- (it would be correct when changing the parameter in REAPER itself). + -- feedback_behavior = "PreventEchoFeedback", + } + local target = realearn.Target.Virtual { + id = id, + character = "Multi", + } + if native_mode then + local cw_mapping = realearn.Mapping { + id = `{id}-cw`, + feedback_enabled = false, + name = name, + source = realearn.Source.MidiRaw { + pattern = cw_sysex, + character = "Button", + }, + glue = realearn.Glue { + control_transformation = sysex_to_relative_transformation, + step_factor_interval = {1, 100}, + }, + target = target, + } + local ccw_mapping = realearn.Mapping { + id = `{id}-ccw`, + feedback_enabled = false, + name = name, + source = realearn.Source.MidiRaw { + pattern = ccw_sysex, + character = "Button", + }, + glue = { + control_transformation = sysex_to_relative_transformation, + step_factor_interval = {1, 100}, + reverse = true, + }, + target = target, + } + local feedback_mapping = realearn.Mapping { + id = `{id}-feedback`, + control_enabled = false, + name = name, + source = cc_source, + target = target, + } + table.insert(mappings, cw_mapping) + table.insert(mappings, ccw_mapping) + table.insert(mappings, feedback_mapping) + else + local mapping = realearn.Mapping { + id = id, + name = name, + source = cc_source, + glue = realearn.Glue { + control_transformation = if simulate_relative_control_in_midi_mode then abs_to_rel_transformation else nil, + }, + target = target, + } + table.insert(mappings, mapping) + end + end + + local function meter(id: string, name: string, cc: number) + local m = realearn.Mapping { + id = id, + name = name, + control_enabled = false, + source = realearn.Source.MidiControlChangeValue { + channel = 0, + controller_number = cc, + character = "Range", + }, + target = realearn.Target.Virtual { + id = id, + character = "Multi", + }, + } + table.insert(mappings, m) + end + + -- Named buttons in row 1 + named_button("display-on", "Display: On", 102, "", "") + named_button( + "display/mode", + "Display: Mode", + 104, + "F0 7D 00 00 00 00 02 00 01 7F 01 02 55 0A 0C F7", + "F0 7D 00 00 00 00 02 00 01 00 41 38 39 27 01 F7" + ) + named_button( + "page/prev", + "Page: -", + 97, + "F0 7D 00 00 00 00 02 00 03 7F 33 34 37 08 0D F7", + "F0 7D 00 00 00 00 02 00 03 00 73 0E 5B 25 00 F7" + ) + named_button( + "page/next", + "Page: +", + 96, + "F0 7D 00 00 00 00 02 00 04 7F 7C 75 21 4F 0E F7", + "F0 7D 00 00 00 00 02 00 04 00 3C 4F 4D 62 03 F7" + ) + named_button( + "track/group", + "Track: Group", + 123, + "F0 7D 00 00 00 00 02 00 19 7F 03 19 4D 53 06 F7", + "F0 7D 00 00 00 00 02 00 19 00 43 23 21 7E 0B F7" + ) + named_button( + "track/copy", + "Track: Copy", + 120, + "F0 7D 00 00 00 00 02 00 1A 7F 28 34 1E 10 07 F7", + "F0 7D 00 00 00 00 02 00 1A 00 68 0E 72 3D 0A F7" + ) + named_button( + "order", + "Order", + 14, + "F0 7D 00 00 00 00 02 00 3A 7F 3D 30 3A 32 0A F7", + "F0 7D 00 00 00 00 02 00 3A 00 7D 0A 56 1F 07 F7" + ) + named_button( + "external-sidechain", + "External Sidechain", + 17, + "F0 7D 00 00 00 00 02 00 3B 7F 24 2B 0B 73 0A F7", + "F0 7D 00 00 00 00 02 00 3B 00 64 11 67 5E 07 F7" + ) + -- Numbered buttons in row 1 + numbered_button( + 0, + `Select track 1`, + 21, + "F0 7D 00 00 00 00 02 00 05 7F 65 6E 10 0E 0E F7", + "F0 7D 00 00 00 00 02 00 05 00 25 54 7C 23 03 F7" + ) + numbered_button( + 1, + `Select track 2`, + 22, + "F0 7D 00 00 00 00 02 00 06 7F 4E 43 43 4D 0F F7", + "F0 7D 00 00 00 00 02 00 06 00 0E 79 2F 60 02 F7" + ) + numbered_button( + 2, + `Select track 3`, + 23, + "F0 7D 00 00 00 00 02 00 07 7F 57 58 72 0C 0F F7", + "F0 7D 00 00 00 00 02 00 07 00 17 62 1E 21 02 F7" + ) + numbered_button( + 3, + `Select track 4`, + 24, + "F0 7D 00 00 00 00 02 00 08 7F 50 40 6E 43 02 F7", + "F0 7D 00 00 00 00 02 00 08 00 10 7A 02 6E 0F F7" + ) + numbered_button( + 4, + `Select track 5`, + 25, + "F0 7D 00 00 00 00 02 00 09 7F 49 5B 5F 02 02 F7", + "F0 7D 00 00 00 00 02 00 09 00 09 61 33 2F 0F F7" + ) + numbered_button( + 5, + `Select track 6`, + 26, + "F0 7D 00 00 00 00 02 00 0A 7F 62 76 0C 41 03 F7", + "F0 7D 00 00 00 00 02 00 0A 00 22 4C 60 6C 0E F7" + ) + numbered_button( + 6, + "Select track 7", + 27, + "F0 7D 00 00 00 00 02 00 0B 7F 7B 6D 3D 00 03 F7", + "F0 7D 00 00 00 00 02 00 0B 00 3B 57 51 2D 0E F7" + ) + numbered_button( + 7, + "Select track 8", + 28, + "F0 7D 00 00 00 00 02 00 0C 7F 34 2C 2B 47 00 F7", + "F0 7D 00 00 00 00 02 00 0C 00 74 16 47 6A 0D F7" + ) + numbered_button( + 8, + "Select track 9", + 29, + "F0 7D 00 00 00 00 02 00 0D 7F 2D 37 1A 06 00 F7", + "F0 7D 00 00 00 00 02 00 0D 00 6D 0D 76 2B 0D F7" + ) + numbered_button( + 9, + "Select track 10", + 30, + "F0 7D 00 00 00 00 02 00 0E 7F 06 1A 49 45 01 F7", + "F0 7D 00 00 00 00 02 00 0E 00 46 20 25 68 0C F7" + ) + numbered_button( + 10, + "Select track 11", + 31, + "F0 7D 00 00 00 00 02 00 0F 7F 1F 01 78 04 01 F7", + "F0 7D 00 00 00 00 02 00 0F 00 5F 3B 14 29 0C F7" + ) + numbered_button( + 11, + "Select track 12", + 32, + "F0 7D 00 00 00 00 02 00 10 7F 52 5B 76 1A 08 F7", + "F0 7D 00 00 00 00 02 00 10 00 12 61 1A 37 05 F7" + ) + numbered_button( + 12, + "Select track 13", + 33, + "F0 7D 00 00 00 00 02 00 11 7F 4B 40 47 5B 08 F7", + "F0 7D 00 00 00 00 02 00 11 00 0B 7A 2B 76 05 F7" + ) + numbered_button( + 13, + "Select track 14", + 34, + "F0 7D 00 00 00 00 02 00 12 7F 60 6D 14 18 09 F7", + "F0 7D 00 00 00 00 02 00 12 00 20 57 78 35 04 F7" + ) + numbered_button( + 14, + "Select track 15", + 35, + "F0 7D 00 00 00 00 02 00 13 7F 79 76 25 59 09 F7", + "F0 7D 00 00 00 00 02 00 13 00 39 4C 49 74 04 F7" + ) + numbered_button( + 15, + "Select track 16", + 36, + "F0 7D 00 00 00 00 02 00 14 7F 36 37 33 1E 0A F7", + "F0 7D 00 00 00 00 02 00 14 00 76 0D 5F 33 07 F7" + ) + numbered_button( + 16, + "Select track 17", + 37, + "F0 7D 00 00 00 00 02 00 15 7F 2F 2C 02 5F 0A F7", + "F0 7D 00 00 00 00 02 00 15 00 6F 16 6E 72 07 F7" + ) + numbered_button( + 17, + "Select track 18", + 38, + "F0 7D 00 00 00 00 02 00 16 7F 04 01 51 1C 0B F7", + "F0 7D 00 00 00 00 02 00 16 00 44 3B 3D 31 06 F7" + ) + numbered_button( + 18, + "Select track 19", + 39, + "F0 7D 00 00 00 00 02 00 17 7F 1D 1A 60 5D 0B F7", + "F0 7D 00 00 00 00 02 00 17 00 5D 20 0C 70 06 F7" + ) + numbered_button( + 19, + "Select track 20", + 40, + "F0 7D 00 00 00 00 02 00 18 7F 1A 02 7C 12 06 F7", + "F0 7D 00 00 00 00 02 00 18 00 5A 38 10 3F 0B F7" + ) + -- Buttons in row 2 + named_button( + "shape", + "Shape", + 53, + "F0 7D 00 00 00 00 02 00 21 7F 14 06 71 28 01 F7", + "F0 7D 00 00 00 00 02 00 21 00 54 3C 1D 05 0C F7" + ) + named_button( + "eq", + "Equalizer", + 80, + "F0 7D 00 00 00 00 02 00 27 7F 42 5C 56 2E 02 F7", + "F0 7D 00 00 00 00 02 00 27 00 02 66 3A 03 0F F7" + ) + named_button( + "comp", + "Compressor", + 46, + "F0 7D 00 00 00 00 02 00 34 7F 23 33 17 3C 07 F7", + "F0 7D 00 00 00 00 02 00 34 00 63 09 7B 11 0A F7" + ) + -- Buttons in row 3 + named_button( + "eq/low/type", + "EQ: Low type", + 93, + "F0 7D 00 00 00 00 02 00 28 7F 45 44 4A 61 0F F7", + "F0 7D 00 00 00 00 02 00 28 00 05 7E 26 4C 02 F7" + ) + named_button( + "eq/high/type", + "EQ: High type", + 65, + "F0 7D 00 00 00 00 02 00 31 7F 5E 44 63 79 05 F7", + "F0 7D 00 00 00 00 02 00 31 00 1E 7E 0F 54 08 F7" + ) + -- Buttons in row 4 + named_button( + "shape/hard-gate", + "Shape: Hard Gate", + 59, + "F0 7D 00 00 00 00 02 00 23 7F 26 30 13 2A 00 F7", + "F0 7D 00 00 00 00 02 00 23 00 66 0A 7F 07 0D F7" + ) + named_button( + "solo", + "Solo", + 13, + "F0 7D 00 00 00 00 02 00 3F 7F 40 47 4E 77 08 F7", + "F0 7D 00 00 00 00 02 00 3F 00 00 7D 22 5A 05 F7" + ) + named_button( + "mute", + "Mute", + 12, + "F0 7D 00 00 00 00 02 00 40 7F 68 60 2B 4E 04 F7", + "F0 7D 00 00 00 00 02 00 40 00 28 5A 47 63 09 F7" + ) + -- Remaining buttons + named_button( + "filters-to-compressor", + "Filters to Compressor", + 61, + "F0 7D 00 00 00 00 02 00 1E 7F 4C 58 5B 14 05 F7", + "F0 7D 00 00 00 00 02 00 1E 00 0C 62 37 39 08 F7" + ) + named_button( + "phase-inv", + "Phase Inv.", + 108, + "F0 7D 00 00 00 00 02 00 1F 7F 55 43 6A 55 05 F7", + "F0 7D 00 00 00 00 02 00 1F 00 15 79 06 78 08 F7" + ) + named_button( + "preset", + "Preset", + 58, + "F0 7D 00 00 00 00 02 00 20 7F 0D 1D 40 69 01 F7", + "F0 7D 00 00 00 00 02 00 20 00 4D 27 2C 44 0C F7" + ) + if native_mode then + named_button( + "fine-adjust", + "Fine Adjust", + nil, + "F0 7D 00 00 00 00 02 00 02 7F 2A 2F 06 49 0D F7", + "F0 7D 00 00 00 00 02 00 02 00 6A 15 6A 64 00 F7" + ) + end + -- Encoders in row 1 + encoder( + "high-cut", + "High Cut", + 105, + "F0 7D 00 00 00 00 02 00 1C 7F 7E 6E 39 16 04 F7", + "F0 7D 00 00 00 00 02 00 1C 00 3E 54 55 3B 09 F7" + ) + encoder( + "shape/gate-release", + "Shape: Gate Release", + 56, + "F0 7D 00 00 00 00 02 00 24 7F 69 71 05 6D 03 F7", + "F0 7D 00 00 00 00 02 00 24 00 29 4B 69 40 0E F7" + ) + encoder( + "eq/low-mid/type", + "EQ: Low Mid Type", + 90, + "F0 7D 00 00 00 00 02 00 2C 7F 21 28 0F 65 0D F7", + "F0 7D 00 00 00 00 02 00 2C 00 61 12 63 48 00 F7" + ) + encoder( + "eq/high-mid/type", + "EQ: High Mid Type", + 87, + "F0 7D 00 00 00 00 02 00 2F 7F 0A 05 5C 26 0C F7", + "F0 7D 00 00 00 00 02 00 2F 00 4A 3F 30 0B 01 F7" + ) + encoder( + "comp/attack", + "Comp: Attack", + 51, + "F0 7D 00 00 00 00 02 00 37 7F 08 1E 44 7F 06 F7", + "F0 7D 00 00 00 00 02 00 37 00 48 24 28 52 0B F7" + ) + encoder( + "drive", + "Drive", + 15, + "F0 7D 00 00 00 00 02 00 3C 7F 6B 6A 1D 34 09 F7", + "F0 7D 00 00 00 00 02 00 3C 00 2B 50 71 19 04 F7" + ) + -- Encoders in row 2 + encoder( + "shape/gate", + "Shape: Gate", + 54, + "F0 7D 00 00 00 00 02 00 22 7F 3F 2B 22 6B 00 F7", + "F0 7D 00 00 00 00 02 00 22 00 7F 11 4E 46 0D F7" + ) + encoder( + "comp/ratio", + "Comp: Ratio", + 49, + "F0 7D 00 00 00 00 02 00 35 7F 3A 28 26 7D 07 F7", + "F0 7D 00 00 00 00 02 00 35 00 7A 12 4A 50 0A F7" + ) + -- Encoders in row 3 + encoder( + "low-cut", + "Low Cut", + 103, + "F0 7D 00 00 00 00 02 00 1D 7F 67 75 08 57 04 F7", + "F0 7D 00 00 00 00 02 00 1D 00 27 4F 64 7A 09 F7" + ) + encoder( + "shape/sustain", + "Shape: Sustain", + 55, + "F0 7D 00 00 00 00 02 00 25 7F 70 6A 34 2C 03 F7", + "F0 7D 00 00 00 00 02 00 25 00 30 50 58 01 0E F7" + ) + encoder( + "eq/low/freq", + "EQ: Low Frequency", + 92, + "F0 7D 00 00 00 00 02 00 29 7F 5C 5F 7B 20 0F F7", + "F0 7D 00 00 00 00 02 00 29 00 1C 65 17 0D 02 F7" + ) + encoder( + "eq/low-mid/freq", + "EQ: Low Mid Frequency", + 89, + "F0 7D 00 00 00 00 02 00 2B 7F 6E 69 19 22 0E F7", + "F0 7D 00 00 00 00 02 00 2B 00 2E 53 75 0F 03 F7" + ) + encoder( + "eq/high-mid/freq", + "EQ: High Mid Frequency", + 86, + "F0 7D 00 00 00 00 02 00 2E 7F 13 1E 6D 67 0C F7", + "F0 7D 00 00 00 00 02 00 2E 00 53 24 01 4A 01 F7" + ) + encoder( + "eq/high/freq", + "EQ: High Frequency", + 83, + "F0 7D 00 00 00 00 02 00 32 7F 75 69 30 3A 04 F7", + "F0 7D 00 00 00 00 02 00 32 00 35 53 5C 17 09 F7" + ) + encoder( + "comp/release", + "Comp: Release", + 48, + "F0 7D 00 00 00 00 02 00 38 7F 0F 06 58 30 0B F7", + "F0 7D 00 00 00 00 02 00 38 00 4F 3C 34 1D 06 F7" + ) + encoder( + "character", + "Character", + 18, + "F0 7D 00 00 00 00 02 00 3D 7F 72 71 2C 75 09 F7", + "F0 7D 00 00 00 00 02 00 3D 00 32 4B 40 58 04 F7" + ) + -- Encoders in row 4 + encoder( + "input/gain", + "Input Gain", + 107, + "F0 7D 00 00 00 00 02 00 1B 7F 31 2F 2F 51 07 F7", + "F0 7D 00 00 00 00 02 00 1B 00 71 15 43 7C 0A F7" + ) + encoder( + "comp/parallel-dry-wet", + "Comp: Parallel Dry/Wet", + 50, + "F0 7D 00 00 00 00 02 00 36 7F 11 05 75 3E 06 F7", + "F0 7D 00 00 00 00 02 00 36 00 51 3F 19 13 0B F7" + ) + -- Encoders in row 5 + encoder( + "shape/punch", + "Shape: Punch", + 57, + "F0 7D 00 00 00 00 02 00 26 7F 5B 47 67 6F 02 F7", + "F0 7D 00 00 00 00 02 00 26 00 1B 7D 0B 42 0F F7" + ) + encoder( + "eq/low/gain", + "EQ: Low Gain", + 91, + "F0 7D 00 00 00 00 02 00 2A 7F 77 72 28 63 0E F7", + "F0 7D 00 00 00 00 02 00 2A 00 37 48 44 4E 03 F7" + ) + encoder( + "eq/low-mid/gain", + "EQ: Low Mid Gain", + 88, + "F0 7D 00 00 00 00 02 00 2D 7F 38 33 3E 24 0D F7", + "F0 7D 00 00 00 00 02 00 2D 00 78 09 52 09 00 F7" + ) + encoder( + "eq/high-mid/gain", + "EQ: High Mid Gain", + 85, + "F0 7D 00 00 00 00 02 00 30 7F 47 5F 52 38 05 F7", + "F0 7D 00 00 00 00 02 00 30 00 07 65 3E 15 08 F7" + ) + encoder( + "eq/high/gain", + "EQ: High Gain", + 82, + "F0 7D 00 00 00 00 02 00 33 7F 6C 72 01 7B 04 F7", + "F0 7D 00 00 00 00 02 00 33 00 2C 48 6D 56 09 F7" + ) + encoder( + "comp/threshold", + "Comp: Threshold", + 47, + "F0 7D 00 00 00 00 02 00 39 7F 16 1D 69 71 0B F7", + "F0 7D 00 00 00 00 02 00 39 00 56 27 05 5C 06 F7" + ) + encoder( + "pan", + "Pan", + 10, + "F0 7D 00 00 00 00 02 00 3E 7F 59 5C 7F 36 08 F7", + "F0 7D 00 00 00 00 02 00 3E 00 19 66 13 1B 05 F7" + ) + encoder( + "output/volume", + "Volume", + 7, + "F0 7D 00 00 00 00 02 00 41 7F 71 7B 1A 0F 04 F7", + "F0 7D 00 00 00 00 02 00 41 00 31 41 76 22 09 F7" + ) + -- Meters + meter("input/meter/left", "Input Meter: Left channel", 110) + meter("input/meter/right", "Input Meter: Right channel", 111) + meter("output/meter/left", "Output Meter: Left channel", 112) + meter("output/meter/right", "Output Meter: Right channel", 113) + meter("shape/meter", "Shape Meter", 114) + meter("comp/meter", "Compressor Meter", 115) + + return realearn.Compartment { + mappings = mappings, + } +end + +return module diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-midi-absolute.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-midi-absolute.preset.luau new file mode 100644 index 0000000..088f64b --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-midi-absolute.preset.luau @@ -0,0 +1,51 @@ +--- name: Console 1 mk2 - MIDI absolute mode +--- realearn_version: 2.16.12 +--- author: helgoboss +--- device_manufacturer: Softube +--- device_name: Console 1 mk2 +--- description: | +--- This controller preset implements support for the Softube Console 1 controller in MIDI mode, enabling absolute control. +--- +--- Pros: +--- * LED feedback without glitches +--- * Button "On" is available and can be customized +--- +--- Cons: +--- * Buttons can't be turned into momentary buttons (because Console1 doesn't send any message on release) +--- * True relative control is not possible (no user-defined step sizes and no wrap) +--- * Without pressing "Fine Adjust", encoder control is very coarse +--- * Button "Fine Adjust" can't be customized (it just switches to fine control on hardware side) +--- * In order to avoid parameter jumps with the encoders, feedback must be enabled (alternatively, you can use +--- ReaLearn's takeover modes) +--- +--- setup_instructions: | +--- 1. Connect the controller +--- 2. Ensure that the Console 1 On-Screen Display software is **not** running! +--- +--- provided_schemes: [softube/console1-mk2] + +--!strict + +-- Config + +-- With this, we can simulate true relative control. +-- +-- In MIDI mode, the encoders of the Console 1 send absolute control values, not relative ones. This is +-- not optimal because it means we can't define step sizes, wrap etc. However, as long as feedback is enabled, at least we don't +-- need to suffer from parameter jumps (a common disadvantage of absolute control). +-- This is because the Console 1 doesn't just use the feedback to set the LED ring, it also resets its internal encoder +-- value to the incoming feedback value. +-- +-- If we simulate relative control, we get the advantages of relative control. However, it doesn't +-- work so nicely with feedback :( The "reset-internal-encoder-value" mechanism mentioned above interferes. +-- Without further treatment, this makes the control stuck. We can make control work by not sending echo feedback +-- but then the indicated LED ring value is incorrect when we turn the encoder (however, it is correct when changing the parameter +-- in REAPER itself - as long as we don't disable feedback completely). +-- The technique we use right now is to compare with the previously sent feedback value - if available - and otherwise the previously +-- sent control value. This sort of works, but there can be glitches because it's not always sure that the last-sent feedback value +-- has actually been received by the controller. + +local commons = require("softube/console1-mk2-lib/preset-common") +return commons.create_compartment { + mode = "midi-absolute", +} diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-midi-relative.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-midi-relative.preset.luau new file mode 100644 index 0000000..f9c9921 --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-midi-relative.preset.luau @@ -0,0 +1,33 @@ +--- name: Console 1 mk2 - MIDI relative mode +--- realearn_version: 2.16.12 +--- author: helgoboss +--- device_manufacturer: Softube +--- device_name: Console 1 mk2 +--- description: | +--- This controller preset implements support for the Softube Console 1 controller in MIDI mode, enabling relative control. +--- +--- Pros: +--- * True relative control +--- * Button "On" is available and can be customized +--- +--- Cons: +--- * Buttons can't be turned into momentary buttons (because Console1 doesn't send any message on release) +--- * Occasional control and feedback glitches are possible (Console1 encoders send absolute messages in a way that makes +--- it hard to convert them into relative messages) +--- * Reacting to encoder increments (or decrements) only via encoder filter doesn't work +--- * Button "Fine Adjust" doesn't have any effect and can't be customized +--- +--- setup_instructions: | +--- 1. Connect the controller +--- 2. Ensure that the Console 1 On-Screen Display software is **not** running! +--- +--- provided_schemes: [softube/console1-mk2] + +--!strict + +-- Config + +local commons = require("softube/console1-mk2-lib/preset-common") +return commons.create_compartment { + mode = "midi-relative", +} diff --git a/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-native.preset.luau b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-native.preset.luau new file mode 100644 index 0000000..683acab --- /dev/null +++ b/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-native.preset.luau @@ -0,0 +1,31 @@ +--- name: Console 1 mk2 - Native mode +--- realearn_version: 2.16.12 +--- author: helgoboss +--- device_manufacturer: Softube +--- device_name: Console 1 mk2 +--- description: | +--- This controller preset implements support for the Softube Console 1 controller in native mode. +--- +--- Pros: +--- * True relative control without glitches +--- * True momentary buttons +--- * LED feedback without glitches +--- * Button "Fine Adjust" is available and can be customized +--- +--- Cons: +--- * Button "On" is not available +--- * Using Softube plug-ins that connect to the Console 1 is not possible (they would interfere) +--- +--- setup_instructions: | +--- 1. Connect the controller +--- 2. Start the Console 1 On-Screen Display software +--- 3. Make sure to not use any Softube plug-ins +--- +--- provided_schemes: [softube/console1-mk2] + +--!strict + +local commons = require("softube/console1-mk2-lib/preset-common") +return commons.create_compartment { + mode = "native", +} diff --git a/plugin-reaper-realearn/resources/main-presets/factory/.stylua.toml b/plugin-reaper-realearn/resources/main-presets/factory/.stylua.toml new file mode 120000 index 0000000..8210ed1 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/.stylua.toml @@ -0,0 +1 @@ +../../api/luau/.stylua.toml \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/.vscode b/plugin-reaper-realearn/resources/main-presets/factory/.vscode new file mode 120000 index 0000000..e1e7088 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/.vscode @@ -0,0 +1 @@ +../../api/luau/.vscode \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/README.md b/plugin-reaper-realearn/resources/main-presets/factory/README.md new file mode 120000 index 0000000..91575d6 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/README.md @@ -0,0 +1 @@ +../../api/luau/README.md \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-key-25-mk2/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-key-25-mk2/playtime.preset.luau new file mode 100644 index 0000000..643e808 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-key-25-mk2/playtime.preset.luau @@ -0,0 +1,34 @@ +--- name: APC Key 25 mk2 - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC Key 25 mk2 +--- description: | +--- This main preset turns the APC Key 25 mk2 into a capable device for controlling Playtime. Hold Shift +--- to access advanced functions. +--- used_schemes: [akai/apc-key-25-mk2] +--- required_features: [playtime] + +--!strict + + +local commons = require("akai/apc-lib/playtime-commons") + +return commons.create_compartment { + use_column_stop_buttons = true, + stop_column_if_slot_empty = true, + mute_track_button_id = "row3/play", + arm_track_button_id = "row4/play", + column_count = 8, + row_count = 5, + up_button_id = "col1/stop", + down_button_id = "col2/stop", + left_button_id = "col3/stop", + right_button_id = "col4/stop", + volume_button_id = "col5/stop", + pan_button_id = "col6/stop", + send_button_id = "col7/stop", + device_button_id = "col8/stop", + stop_all_clips_button_id = "stop-all-clips", + stop_all_clips_need_shift = false, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-key-25/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-key-25/playtime.preset.luau new file mode 100644 index 0000000..44149fa --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-key-25/playtime.preset.luau @@ -0,0 +1,33 @@ +--- name: APC Key 25 - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC Key 25 +--- description: | +--- This main preset turns the APC Key 25 mk1 into a capable device for controlling Playtime. Hold Shift or Sustain +--- to access advanced functions. +--- used_schemes: [akai/apc-key-25] +--- required_features: [playtime] + +--!strict + +local commons = require("akai/apc-lib/playtime-commons") + +return commons.create_compartment { + use_column_stop_buttons = true, + stop_column_if_slot_empty = true, + mute_track_button_id = "row4/play", + arm_track_button_id = "row3/play", + column_count = 8, + row_count = 5, + up_button_id = "col1/stop", + down_button_id = "col2/stop", + left_button_id = "col3/stop", + right_button_id = "col4/stop", + volume_button_id = "col5/stop", + pan_button_id = "col6/stop", + send_button_id = "col7/stop", + device_button_id = "col8/stop", + stop_all_clips_button_id = "stop-all-clips", + stop_all_clips_need_shift = false, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-lib/playtime-commons.luau b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-lib/playtime-commons.luau new file mode 100644 index 0000000..31a2537 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-lib/playtime-commons.luau @@ -0,0 +1,886 @@ +--!strict + +local util = require("util") +local realearn = require("realearn") +local playtime_util = require("playtime_util") + +local module = {} + +export type ApcPlaytimePresetConfig = { + mute_track_button_id: string, + arm_track_button_id: string, + use_column_stop_buttons: boolean, + stop_column_if_slot_empty: boolean, + column_count: number, + row_count: number, + up_button_id: string, + down_button_id: string, + left_button_id: string, + right_button_id: string, + volume_button_id: string, + pan_button_id: string, + send_button_id: string, + device_button_id: string, + stop_all_clips_button_id: string, + stop_all_clips_need_shift: boolean, +} + +function module.create_compartment(config: ApcPlaytimePresetConfig): realearn.Compartment + -- Aliases + + local partial_mapping = util.partial_mapping + local turbo = playtime_util.turbo + local scroll_vertically = playtime_util.scroll_vertically + local scroll_horizontally = playtime_util.scroll_horizontally + + -- Types + + type Mode = { + index: number, + label: string, + } + + type ModeMap = { [string]: Mode } + + type ParameterMap = { [string]: realearn.Parameter } + + type GroupMap = { [string]: realearn.Group } + + -- Constants + + local column_mode_count = 100 + local knob_mode_count = 100 + + -- Column modes + + local column_modes: ModeMap = { + stop = { + index = 0, + label = "Stop clip", + }, + solo = { + index = 1, + label = "Solo", + }, + record_arm = { + index = 2, + label = "Record arm", + }, + mute = { + index = 3, + label = "Mute", + }, + select = { + index = 4, + label = "Track select", + }, + } + local sorted_column_modes = util.sorted_by_index(column_modes) + + -- Knob modes + local knob_modes: ModeMap = { + volume = { + index = 0, + label = "Volume", + }, + pan = { + index = 1, + label = "Pan", + }, + sends = { + index = 2, + label = "Sends", + }, + device = { + index = 3, + label = "Device", + }, + } + local sorted_knob_modes = util.sorted_by_index(knob_modes) + + -- Parameters + + local params: ParameterMap = { + shift = { + index = 0, + name = "Shift modifier", + }, + column_mode = { + index = 1, + name = "Column mode", + value_count = column_mode_count, + value_labels = util.extract_labels(sorted_column_modes), + }, + knob_mode = { + index = 2, + name = "Knob mode", + value_count = knob_mode_count, + value_labels = util.extract_labels(sorted_knob_modes), + }, + send = { + index = 3, + name = "Send", + value_count = 2, + }, + sustain = { + index = 4, + name = "Sustain modifier", + }, + } + + -- Domain functions + + local function create_index_expression(variable_name: string, index: number): string + return `{variable_name} + {index}` + end + + local function create_col_expression(col: number): string + return create_index_expression("control_unit_column_index", col) + end + + local function create_row_expression(row: number): string + return create_index_expression("control_unit_row_index", row) + end + + local function create_slot_selector(col: number, row: number): realearn.PlaytimeSlotDescriptor + return realearn.PlaytimeSlotDescriptor.Dynamic { + column_expression = create_col_expression(col), + row_expression = create_row_expression(row), + } + end + + local function create_column_selector(col: number): realearn.PlaytimeColumnDescriptor + return realearn.PlaytimeColumnDescriptor.Dynamic { + expression = create_col_expression(col), + } + end + + local function create_row_selector(row: number): realearn.PlaytimeRowDescriptor + return realearn.PlaytimeRowDescriptor.Dynamic { + expression = create_row_expression(row), + } + end + + local function knob(index: number) + return partial_mapping { + source = realearn.Source.Virtual { + character = "Multi", + id = index, + }, + } + end + + local function button(id: string) + return partial_mapping { + source = realearn.Source.Virtual { + character = "Button", + id = id, + }, + } + end + + local function column_stop_button(col: number) + return button(`col{col + 1}/stop`) + end + + local function row_play_button(row: number) + return button(`row{row + 1}/play`) + end + + local function slot_button(col: number, row: number) + return partial_mapping { + source = realearn.Source.Virtual { + character = "Multi", + id = `col{col + 1}/row{row + 1}/pad`, + }, + } + end + + local function shift_pressed(on: boolean) + return partial_mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = params.shift.index, + on = on, + }, + { + parameter = params.sustain.index, + on = false, + }, + }, + }, + } + end + + local function sustain_pressed(on: boolean) + return partial_mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = params.sustain.index, + on = on, + }, + { + parameter = params.shift.index, + on = false, + }, + }, + }, + } + end + + local function shift_or_sustain_pressed() + return partial_mapping { + activation_condition = realearn.ActivationCondition.Expression { + condition = `p[{params.shift.index}] || p[{params.sustain.index}]`, + }, + } + end + + local function shift_and_sustain_pressed() + return partial_mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = params.sustain.index, + on = true, + }, + { + parameter = params.shift.index, + on = true, + }, + }, + }, + } + end + + local function fire_max(millis: number) + return partial_mapping { + glue = { + fire_mode = realearn.FireMode.Normal { + press_duration_interval = { 0, millis }, + }, + }, + } + end + + local function fire_after_timeout(millis: number) + return partial_mapping { + glue = { + fire_mode = realearn.FireMode.AfterTimeout { + timeout = millis, + }, + }, + } + end + + local function fire_on_single_press(max_duration: number) + return partial_mapping { + glue = { + fire_mode = realearn.FireMode.OnSinglePress { + max_duration = max_duration, + }, + }, + } + end + + local function fire_on_double_press() + return partial_mapping { + glue = { + fire_mode = realearn.FireMode.OnDoublePress(), + }, + } + end + + local no_mod = shift_pressed(false) + local shift = shift_pressed(true) + local sustain = sustain_pressed(true) + local shift_or_sustain = shift_or_sustain_pressed() + local short_press = fire_max(200) + local long_press = fire_after_timeout(1000) + local single_press = fire_on_single_press(200) + local double_press = fire_on_double_press() + + local function clip_matrix_action(action: realearn.PlaytimeMatrixAction) + return partial_mapping { + target = realearn.Target.PlaytimeMatrixAction { + action = action, + }, + } + end + + local function clip_column_action(col: number, action: realearn.PlaytimeColumnAction) + return partial_mapping { + target = realearn.Target.PlaytimeColumnAction { + column = create_column_selector(col), + action = action, + }, + } + end + + local function clip_row_action(row: number, action: realearn.PlaytimeRowAction) + return partial_mapping { + target = realearn.Target.PlaytimeRowAction { + row = create_row_selector(row), + action = action, + }, + } + end + + local function clip_column_track(col: number): realearn.TrackDescriptor + return realearn.TrackDescriptor.FromClipColumn { + column = create_column_selector(col), + context = "Playback", + } + end + + local function column_track_target(col: number, track_target_kind: realearn.TargetKind, exclusive: boolean?) + return partial_mapping { + target = { + kind = track_target_kind, + track = clip_column_track(col), + exclusivity = if exclusive then "WithinFolderOnOnly" else nil, + } :: any, + } + end + + local function route_target(col: number, route_target_kind: realearn.TargetKind) + return partial_mapping { + target = { + kind = route_target_kind, + route = realearn.RouteDescriptor.Dynamic { + track = clip_column_track(col), + expression = `p[{params.send.index}]`, + }, + } :: any, + } + end + + local function clip_transport_action(col: number, row: number, action: realearn.PlaytimeSlotTransportAction) + return partial_mapping { + target = realearn.Target.PlaytimeSlotTransportAction { + slot = create_slot_selector(col, row), + action = action, + stop_column_if_slot_empty = config.stop_column_if_slot_empty, + }, + } + end + + local function clip_management_action(col: number, row: number, action: realearn.PlaytimeSlotManagementAction) + return partial_mapping { + target = realearn.Target.PlaytimeSlotManagementAction { + slot = create_slot_selector(col, row), + action = action, + }, + } + end + + local function transport_action(action: realearn.TransportAction) + return partial_mapping { + target = realearn.Target.TransportAction { + action = action, + }, + } + end + + local function reaper_action(command: realearn.ReaperCommand) + return partial_mapping { + target = realearn.Target.ReaperAction { + command = command, + invocation = "Trigger", + }, + } + end + + local function toggle() + return partial_mapping { + glue = { + absolute_mode = "ToggleButton", + }, + } + end + + local function pick_up() + return partial_mapping { + glue = { + takeover_mode = "PickUpTolerant", + }, + } + end + + local function incremental() + return partial_mapping { + glue = { + absolute_mode = "IncrementalButton", + }, + } + end + + local function wrap() + return partial_mapping { + glue = { + wrap = true, + }, + } + end + + local function control_disabled() + return partial_mapping { + control_enabled = false, + visible_in_projection = false, + } + end + + local function feedback_disabled() + return partial_mapping { + feedback_enabled = false, + } + end + + local function set_param(index: number) + return partial_mapping { + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = index, + }, + }, + } + end + + local function name(n: string) + return partial_mapping { + name = n, + } + end + + local function group(g: realearn.Group) + return partial_mapping { + group = g.id, + } + end + + local function set_mode(mode: Mode, mode_count: number, mode_param_index: number) + local target_value = mode.index / (mode_count - 1) + return partial_mapping { + name = mode.label, + glue = { + target_interval = { target_value, target_value }, + out_of_range_behavior = "Min", + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = mode_param_index, + }, + }, + } + end + + local function set_column_mode(mode: Mode) + return set_mode(mode, column_mode_count, params.column_mode.index) + end + + local function set_knob_mode(mode: Mode) + return set_mode(mode, knob_mode_count, params.knob_mode.index) + end + + local function column_mode_is(column_mode: Mode) + return realearn.ActivationCondition.Bank { + parameter = params.column_mode.index, + bank_index = column_mode.index, + } + end + + local function knob_mode_is(knob_mode) + return realearn.ActivationCondition.Bank { + parameter = params.knob_mode.index, + bank_index = knob_mode.index, + } + end + + -- Groups + + local groups: GroupMap = { + slot_modes = { + name = "Slot modes", + }, + column_modes = { + name = "Column modes", + }, + record_settings = { + name = "Record settings", + }, + knob_modes = { + name = "Knob modes", + }, + slot_feedback = { + name = "Slot feedback", + }, + slot_play = { + name = "Slot play", + }, + slot_clear = { + name = "Slot clear", + }, + slot_quantize = { + name = "Slot quantize", + }, + slot_copy_or_paste = { + name = "Slot copy or paste", + }, + slot_double = { + name = "Slot double section", + }, + slot_halve = { + name = "Slot halve section", + }, + column_stop = { + name = "Column stop", + activation_condition = column_mode_is(column_modes.stop), + }, + row_play_scene = { + name = "Row play scene", + }, + row_build_scene = { + name = "Row build scene", + }, + row_copy_or_paste_scene = { + name = "Row copy or paste scene", + }, + row_clear_scene = { + name = "Row clear scene", + }, + column_solo = { + name = "Column solo", + activation_condition = column_mode_is(column_modes.solo), + }, + column_record_arm = { + name = "Column record arm", + activation_condition = column_mode_is(column_modes.record_arm), + }, + column_mute = { + name = "Column mute", + activation_condition = column_mode_is(column_modes.mute), + }, + column_select = { + name = "Column select", + activation_condition = column_mode_is(column_modes.select), + }, + knob_volume = { + name = "Knob volume", + activation_condition = knob_mode_is(knob_modes.volume), + }, + knob_pan = { + name = "Knob pan", + activation_condition = knob_mode_is(knob_modes.pan), + }, + knob_sends = { + name = "Knob sends", + activation_condition = knob_mode_is(knob_modes.sends), + }, + knob_device = { + name = "Knob device", + activation_condition = knob_mode_is(knob_modes.device), + }, + } + util.set_keys_as_ids(groups) + + -- Mappings + + local mappings = { + name("Play/stop") + no_mod + button("play") + toggle() + clip_matrix_action("PlayIgnitedOrEnterSilenceMode"), + name("Shift modifier") + button("shift") + set_param(params.shift.index), + name("Sustain modifier") + button("sustain") + set_param(params.sustain.index), + name("Undo") + shift + button("play") + clip_matrix_action("Undo"), + name("Record") + no_mod + button("record") + clip_matrix_action("SmartRecord"), + name("Redo") + shift + button("record") + clip_matrix_action("Redo"), + name("Build scene") + sustain + button("record") + clip_matrix_action("BuildScene"), + name("Switch send") + + group(groups.knob_sends) + + feedback_disabled() + + shift + + button("col7/stop") + + incremental() + + wrap() + + set_param(params.send.index), + name("Column stop mode") + + group(groups.column_modes) + + shift + + button("row1/play") + + set_column_mode(column_modes.stop), + name("Column solo mode") + + group(groups.column_modes) + + shift + + button("row2/play") + + set_column_mode(column_modes.solo), + name("Column arm mode") + + group(groups.column_modes) + + shift + + button(config.arm_track_button_id) + + set_column_mode(column_modes.record_arm), + name("Column mute mode") + + group(groups.column_modes) + + shift + + button(config.mute_track_button_id) + + set_column_mode(column_modes.mute), + name("Column select mode") + + group(groups.column_modes) + + shift + + button("row5/play") + + set_column_mode(column_modes.select), + } + + if config.stop_all_clips_need_shift then + -- We have a device that has a "Stop all clips" button but it needs shift to be pressed + table.insert( + mappings, + name("Stop all clips") + shift + button(config.stop_all_clips_button_id) + clip_matrix_action("Stop") + ) + else + -- We have a device with a dedicated "Stop all clips" button + table.insert( + mappings, + name("Stop all clips") + no_mod + button(config.stop_all_clips_button_id) + clip_matrix_action("Stop") + ) + table.insert( + mappings, + name("Click") + shift + button(config.stop_all_clips_button_id) + reaper_action(40364) + ) + end + + if config.use_column_stop_buttons then + -- Scrolling + table.insert( + mappings, + name("Scroll up") + shift_or_sustain + button(config.up_button_id) + turbo() + scroll_vertically(-1) + ) + table.insert( + mappings, + name("Scroll down") + shift_or_sustain + button(config.down_button_id) + turbo() + scroll_vertically(1) + ) + table.insert( + mappings, + name("Scroll left") + shift_or_sustain + button(config.left_button_id) + turbo() + scroll_horizontally(-1) + ) + table.insert( + mappings, + name("Scroll right") + shift_or_sustain + button(config.right_button_id) + turbo() + scroll_horizontally(1) + ) + -- Modes + table.insert( + mappings, + name("Knob volume mode") + + group(groups.knob_modes) + + shift + + button(config.volume_button_id) + + set_knob_mode(knob_modes.volume) + ) + table.insert( + mappings, + name("Knob pan mode") + + group(groups.knob_modes) + + shift + + button(config.pan_button_id) + + set_knob_mode(knob_modes.pan) + ) + table.insert( + mappings, + name("Knob send mode") + + group(groups.knob_modes) + + shift + + button(config.send_button_id) + + set_knob_mode(knob_modes.sends) + ) + table.insert( + mappings, + name("Knob device mode") + + group(groups.knob_modes) + + shift + + button(config.device_button_id) + + set_knob_mode(knob_modes.device) + ) + end + + -- For each column + for col = 0, config.column_count - 1 do + -- Column stop button functions + if config.use_column_stop_buttons then + table.insert( + mappings, + name("Stop column") + + group(groups.column_stop) + + no_mod + + column_stop_button(col) + + clip_column_action(col, "Stop") + ) + table.insert( + mappings, + name("Solo track") + + group(groups.column_solo) + + no_mod + + toggle() + + column_stop_button(col) + + column_track_target(col, "TrackSoloState") + ) + table.insert( + mappings, + name("Arm track") + + group(groups.column_record_arm) + + no_mod + + toggle() + + column_stop_button(col) + + clip_column_action(col, "ArmStateExclusive") + ) + table.insert( + mappings, + name("Mute track") + + group(groups.column_mute) + + no_mod + + toggle() + + column_stop_button(col) + + column_track_target(col, "TrackMuteState") + ) + table.insert( + mappings, + name("Select track") + + group(groups.column_select) + + no_mod + + toggle() + + column_stop_button(col) + + column_track_target(col, "TrackSelectionState", true) + ) + end + -- Knob functions + table.insert( + mappings, + name("Track volume") + group(groups.knob_volume) + knob(col) + pick_up() + column_track_target(col, "TrackVolume") + ) + table.insert( + mappings, + name("Track pan") + group(groups.knob_pan) + knob(col) + pick_up() + column_track_target(col, "TrackPan") + ) + table.insert( + mappings, + name("Track send volume") + group(groups.knob_sends) + knob(col) + pick_up() + route_target(col, "RouteVolume") + ) + end + + -- For each row + for row = 0, config.row_count - 1 do + table.insert( + mappings, + name("Play scene") + + group(groups.row_play_scene) + + feedback_disabled() + + no_mod + + row_play_button(row) + + clip_row_action(row, "PlayScene") + ) + table.insert( + mappings, + name("Copy or paste") + + group(groups.row_copy_or_paste_scene) + + sustain + + short_press + + row_play_button(row) + + clip_row_action(row, "CopyOrPasteScene") + ) + table.insert( + mappings, + name("Long = Clear") + + group(groups.row_clear_scene) + + feedback_disabled() + + sustain + + long_press + + row_play_button(row) + + clip_row_action(row, "ClearScene") + ) + end + + -- For each slot + for col = 0, config.column_count - 1 do + for row = 0, config.row_count - 1 do + -- Feedback + table.insert( + mappings, + name("Slot feedback") + + group(groups.slot_feedback) + + control_disabled() + + slot_button(col, row) + + playtime_util.slot_state_text_feedback() + + clip_transport_action(col, row, "Trigger") + ) + -- Control + table.insert( + mappings, + name("Slot trigger") + + group(groups.slot_play) + + feedback_disabled() + + no_mod + + slot_button(col, row) + + clip_transport_action(col, row, "Trigger") + ) + table.insert( + mappings, + name("Copy or paste") + + group(groups.slot_copy_or_paste) + + feedback_disabled() + + sustain + + single_press + + slot_button(col, row) + + toggle() + + clip_management_action(col, row, "CopyOrPasteClip") + ) + table.insert( + mappings, + name("Long = Delete") + + group(groups.slot_clear) + + feedback_disabled() + + sustain + + long_press + + slot_button(col, row) + + clip_management_action(col, row, "ClearSlot") + ) + table.insert( + mappings, + name("2x = Edit") + + group(groups.slot_quantize) + + feedback_disabled() + + sustain + + double_press + + slot_button(col, row) + + toggle() + + clip_management_action(col, row, "EditClip") + ) + --table.insert(mappings, name("Overdub clip") + group(groups.slot_play) + feedback_disabled() + shift + single_press + slot_button(col, row) + toggle() + clip_transport_action(col, row, "RecordStop", false)) + --table.insert(mappings, name("2x = Double section") + group(groups.slot_double) + feedback_disabled() + shift + double_press + slot_button(col, row) + adjust_clip_section_length_action(col, row, 2)) + --table.insert(mappings, name("1x = Halve section") + group(groups.slot_double) + feedback_disabled() + shift + single_press + slot_button(col, row) + adjust_clip_section_length_action(col, row, 0.5)) + table.insert( + mappings, + name("Fill slot") + + group(groups.slot_quantize) + + feedback_disabled() + + shift + + single_press + + slot_button(col, row) + + clip_management_action(col, row, "FillSlotWithSelectedItem") + ) + end + end + + return realearn.Compartment { + parameters = util.sorted_by_index(params), + groups = util.to_array(groups), + mappings = mappings :: any, + custom_data = { + playtime = { + control_unit = { + column_count = config.column_count, + row_count = config.row_count, + }, + }, + }, + } +end + +return module diff --git a/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-mini-mk2/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-mini-mk2/playtime.preset.luau new file mode 100644 index 0000000..2849fcc --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-mini-mk2/playtime.preset.luau @@ -0,0 +1,34 @@ +--- name: APC mini mk2 - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC mini mk2 +--- description: | +--- This main preset turns the APC mini mk2 into a capable device for controlling Playtime. Hold Shift +--- to access advanced functions. +--- used_schemes: [akai/apc-mini-mk2] +--- required_features: [playtime] + +--!strict + + +local commons = require("akai/apc-lib/playtime-commons") + +return commons.create_compartment { + use_column_stop_buttons = true, + stop_column_if_slot_empty = true, + mute_track_button_id = "row3/play", + arm_track_button_id = "row4/play", + column_count = 8, + row_count = 8, + up_button_id = "col5/stop", + down_button_id = "col6/stop", + left_button_id = "col7/stop", + right_button_id = "col8/stop", + volume_button_id = "col1/stop", + pan_button_id = "col2/stop", + send_button_id = "col3/stop", + device_button_id = "col4/stop", + stop_all_clips_button_id = "row8/play", + stop_all_clips_need_shift = true, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-mini/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-mini/playtime.preset.luau new file mode 100644 index 0000000..d17f312 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/akai/apc-mini/playtime.preset.luau @@ -0,0 +1,34 @@ +--- name: APC mini - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Akai +--- device_name: APC mini +--- description: | +--- This main preset turns the APC mini into a capable device for controlling Playtime. Hold Shift +--- to access advanced functions. +--- used_schemes: [akai/apc-mini] +--- required_features: [playtime] + +--!strict + + +local commons = require("akai/apc-lib/playtime-commons") + +return commons.create_compartment { + use_column_stop_buttons = true, + stop_column_if_slot_empty = true, + mute_track_button_id = "row4/play", + arm_track_button_id = "row3/play", + column_count = 8, + row_count = 8, + up_button_id = "col1/stop", + down_button_id = "col2/stop", + left_button_id = "col3/stop", + right_button_id = "col4/stop", + volume_button_id = "col5/stop", + pan_button_id = "col6/stop", + send_button_id = "col7/stop", + device_button_id = "col8/stop", + stop_all_clips_button_id = "row8/play", + stop_all_clips_need_shift = true, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/generic/daw/daw-control.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/generic/daw/daw-control.preset.luau new file mode 100644 index 0000000..2ee791e --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/generic/daw/daw-control.preset.luau @@ -0,0 +1,1174 @@ +--- name: DAW control +--- realearn_version: 2.16.0 +--- author: helgoboss +--- device_manufacturer: Generic +--- device_name: Generic +--- description: | +--- This main preset is built for controllers that offer DAW control elements. +--- +--- This preset is optimized for controllers with exactly 8 channels. At the moment, the number of channels +--- can only be changed by adjusting the preset source file. +--- used_schemes: [daw] + +--!strict + +local realearn = require("realearn") + +-- ### Configuration ### + +-- Number of channels on the surface +local channel_count = 8 + +-- Rates when pressing button a longer time +local bank_switch_rate = 100 +local track_switch_rate = 60 +local zoom_rate = 100 +local scroll_rate = 100 + +-- Set `follow_track_selection` to `true` in order to switch between tracks by selecting them. +local follow_track_selection = true +-- Setting this to false is supposed to prevent navigation to the master track when using `follow_track_selection` +-- but doesn't work 100% correctly. +local support_nav_to_master_track = true +local scroll_arrange_view = true +local scroll_mixer = true + +-- ### Content ### + +local parameters: { realearn.Parameter } = { + { + index = 1, + name = "Seek coarse/fine", + }, + { + index = 2, + name = "Zoom on/off", + }, +} + +if not follow_track_selection then + table.insert(parameters, { + index = 0, + name = "Track", + }) +end + +local groups: { realearn.Group } = { + { + id = "switching", + name = "Switching", + }, + { + id = "volume", + name = "Volume", + }, + { + id = "pan", + name = "Pan", + }, + { + id = "pan-reset", + name = "Pan reset", + }, + { + id = "selection", + name = "Selection", + }, + { + id = "mute", + name = "Mute", + }, + { + id = "solo", + name = "Solo", + }, + { + id = "arm", + name = "Arm", + }, + { + id = "touch", + name = "Touch", + }, + { + id = "transport", + name = "Transport", + }, + { + id = "zoom", + name = "Zoom", + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 2, + on = true, + }, + }, + }, + }, + { + id = "master", + name = "Master", + }, + { + id = "scroll", + name = "Scroll", + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 2, + on = false, + }, + }, + }, + }, + { + id = "lcd", + name = "LCD", + }, + { + id = "meter", + name = "Meter", + }, + { + id = "time-modes", + name = "Time modes", + }, +} + +local browse_tracks_target = realearn.Target.BrowseTracks { + scroll_arrange_view = scroll_arrange_view, + scroll_mixer = scroll_mixer, +} +local adjust_track_param_target = realearn.Target.FxParameterValue { + parameter = realearn.FxParameterDescriptor.ById { + index = 0, + }, +} + +local track_switching_target = if follow_track_selection then browse_tracks_target else adjust_track_param_target + +local track_bank_step_size_interval = not follow_track_selection and { 0.0008, 0.0008 } or nil +local track_bank_step_factor_interval = follow_track_selection and { 8, 8 } or nil +local track_switch_step_size_interval = not follow_track_selection and { 0.0001, 0.0001 } or nil +local track_switch_step_factor_interval = follow_track_selection and { 1, 1 } or nil + +local lcd_assignment_text_feedback = realearn.Feedback.Text { + text_expression = "{{ target.numeric_value }}", +} +local lcd_assignment_numeric_feedback = realearn.Feedback.Numeric { + transformation = "x = y + 0.01", +} +local lcd_assignment_browse_tracks_target = realearn.Target.BrowseTracks {} +local lcd_assignment_adjust_track_param_target = realearn.Target.FxParameterValue { + parameter = realearn.FxParameterDescriptor.ById { + index = 0, + }, + poll_for_feedback = false, +} + +local mappings: { realearn.Mapping } = { + { + id = "5cfd2ff0-85ce-4e70-98e8-eb53e5e94bb1", + name = "Bank -", + group = "switching", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "bank-left", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + step_size_interval = track_bank_step_size_interval, + step_factor_interval = track_bank_step_factor_interval, + }, + target = track_switching_target, + }, + { + id = "07b0202f-b8b8-48e4-a530-f228450864f0", + name = "Bank - faster", + group = "switching", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "bank-left", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + step_size_interval = track_bank_step_size_interval, + step_factor_interval = track_bank_step_factor_interval, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + timeout = 300, + rate = bank_switch_rate, + }, + }, + target = track_switching_target, + }, + { + id = "0baab91f-2c4e-43ae-8cac-dca727963b46", + feedback_enabled = false, + name = "Bank +", + group = "switching", + source = realearn.Source.Virtual { + id = "bank-right", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + step_size_interval = track_bank_step_size_interval, + step_factor_interval = track_bank_step_factor_interval, + }, + target = track_switching_target, + }, + { + id = "0a7e122c-3eda-4355-906a-a057eefa515b", + feedback_enabled = false, + name = "Bank + faster", + group = "switching", + source = realearn.Source.Virtual { + id = "bank-right", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + step_size_interval = track_bank_step_size_interval, + step_factor_interval = track_bank_step_factor_interval, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + timeout = 300, + rate = bank_switch_rate, + }, + }, + target = track_switching_target, + }, + { + id = "cf45689a-5537-465d-ad44-460dbebf4802", + name = "Track -", + group = "switching", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "ch-left", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + step_size_interval = track_switch_step_size_interval, + step_factor_interval = track_switch_step_factor_interval, + }, + target = track_switching_target, + }, + { + id = "76465b5f-c9b3-4aa6-ba77-b891021cf38e", + name = "Track - faster", + group = "switching", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "ch-left", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + step_size_interval = track_switch_step_size_interval, + step_factor_interval = track_switch_step_factor_interval, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + timeout = 300, + rate = track_switch_rate, + }, + }, + target = track_switching_target, + }, + { + id = "d40ffa44-a446-4499-b9d6-787c5ff8e188", + feedback_enabled = false, + name = "Track +", + group = "switching", + source = realearn.Source.Virtual { + id = "ch-right", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + step_size_interval = track_switch_step_size_interval, + step_factor_interval = track_switch_step_factor_interval, + }, + target = track_switching_target, + }, + { + id = "97db991c-a15b-4c27-aeb2-04b2ae3242c2", + name = "Track + faster", + group = "switching", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "ch-right", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + step_size_interval = track_switch_step_size_interval, + step_factor_interval = track_switch_step_factor_interval, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + timeout = 300, + rate = track_switch_rate, + }, + }, + target = track_switching_target, + }, + { + id = "afa35458-3160-4f49-bb90-279aae51779f", + name = "Play/pause", + group = "transport", + source = realearn.Source.Virtual { + id = "play", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + }, + target = realearn.Target.TransportAction { + action = "PlayPause", + }, + }, + { + id = "67b3a516-246f-43a6-b850-050eb415520a", + name = "Stop", + group = "transport", + source = realearn.Source.Virtual { + id = "stop", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + out_of_range_behavior = "Min", + }, + target = realearn.Target.TransportAction { + action = "Stop", + }, + }, + { + id = "f14ebd63-0127-496d-9acb-281b5a7bb3db", + name = "Repeat", + group = "transport", + source = realearn.Source.Virtual { + id = "cycle", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + out_of_range_behavior = "Min", + }, + target = realearn.Target.TransportAction { + action = "Repeat", + }, + }, + { + id = "00626498-80a6-41d4-bc9f-f273098e3dcf", + name = "Record", + group = "transport", + source = realearn.Source.Virtual { + id = "record", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + out_of_range_behavior = "Min", + }, + target = realearn.Target.TransportAction { + action = "Record", + }, + }, + { + id = "1f827b7b-db04-478a-a05b-36e898f6b245", + name = "Scrub coarse", + group = "transport", + feedback_enabled = false, + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 1, + on = false, + }, + }, + }, + source = realearn.Source.Virtual { + id = "jog", + }, + target = realearn.Target.ReaperAction { + command = 992, + invocation = "Relative", + }, + }, + { + id = "a259062e-dfde-4e6c-8e0a-014a078fc1be", + name = "Scrub fine", + group = "transport", + feedback_enabled = false, + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = 1, + on = true, + }, + }, + }, + source = realearn.Source.Virtual { + id = "jog", + }, + glue = { + step_factor_interval = { 0, 0 }, + }, + target = realearn.Target.ReaperAction { + command = 974, + invocation = "Relative", + }, + }, + { + id = "1e5c604f-9040-47c0-ab37-a05383e7be00", + name = "Seek coarse/fine", + group = "switching", + source = realearn.Source.Virtual { + id = "scrub", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + fire_mode = realearn.FireMode.Normal { + press_duration_interval = { 0, 250 }, + }, + }, + target = realearn.Target.FxParameterValue { + parameter = realearn.FxParameterDescriptor.ById { + index = 1, + }, + }, + }, + { + id = "ee81c405-d355-45e0-80b4-913633295193", + name = "Zoom on/off", + group = "switching", + source = realearn.Source.Virtual { + id = "zoom", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + fire_mode = realearn.FireMode.Normal { + press_duration_interval = { 0, 250 }, + }, + }, + target = realearn.Target.FxParameterValue { + parameter = realearn.FxParameterDescriptor.ById { + index = 2, + }, + }, + }, + { + id = "f13678b1-da81-46a5-b318-861a813d7183", + name = "Previous", + group = "transport", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "rewind", + character = "Button", + }, + target = realearn.Target.ReaperAction { + command = 40172, + invocation = "Trigger", + }, + }, + { + id = "previous-led", + name = "Previous LED", + group = "transport", + control_enabled = false, + source = realearn.Source.Virtual { + id = "rewind", + character = "Button", + }, + glue = { + target_interval = { 0, 0.00001 }, + }, + target = realearn.Target.Seek {}, + }, + { + id = "7f0d2cbb-0346-4229-89dd-db58cac3a460", + name = "Next", + group = "transport", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "fast-fwd", + character = "Button", + }, + target = realearn.Target.ReaperAction { + command = 40173, + invocation = "Trigger", + }, + }, + { + id = "next-led", + name = "Next LED", + group = "transport", + control_enabled = false, + source = realearn.Source.Virtual { + id = "fast-fwd", + character = "Button", + }, + glue = { + target_interval = { 0.99999, 1 }, + reverse = true, + }, + target = realearn.Target.Seek {}, + }, + { + id = "8b0ce5db-a371-4b4c-9fe8-ca086bc706bf", + name = "Zoom out horizontally", + group = "zoom", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-left", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = zoom_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 998, + invocation = "Relative", + }, + }, + { + id = "9b6fe0b8-d5f3-438b-9830-6bb782121c2d", + name = "Zoom in horizontally", + group = "zoom", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-right", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = zoom_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 998, + invocation = "Relative", + }, + }, + { + id = "c3c34680-0396-4b24-8714-e210062f46cd", + name = "Zoom out vertically", + group = "zoom", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-down", + character = "Button", + }, + glue = { + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = zoom_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 40112, + invocation = "Trigger", + }, + }, + { + id = "e93e8981-8127-446f-b5d9-34bb576fbc2b", + name = "Zoom in vertically", + group = "zoom", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-up", + character = "Button", + }, + glue = { + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = zoom_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 40111, + invocation = "Trigger", + }, + }, + { + id = "8b6d4cf5-d4f2-4701-af71-90d839da2318", + name = "Scroll down", + group = "scroll", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-down", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + step_factor_interval = { 8, 8 }, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = scroll_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 989, + invocation = "Relative", + }, + }, + { + id = "b633c2d1-c283-4021-a1fc-76b6abd407b9", + name = "Scroll up", + group = "scroll", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-up", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + step_size_interval = { 0.08, 0.08 }, + step_factor_interval = { 8, 8 }, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = scroll_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 989, + invocation = "Relative", + }, + }, + { + id = "b9fa3db6-8bed-4c8a-82fa-580d3d0da92c", + name = "Scroll left", + group = "scroll", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-left", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + step_factor_interval = { 8, 8 }, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = scroll_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 40140, + invocation = "Relative", + }, + }, + { + id = "344405d5-b50d-47d4-a22e-aa99a7454a25", + name = "Scroll right", + group = "scroll", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "cursor-right", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + step_factor_interval = { 8, 8 }, + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + rate = scroll_rate, + }, + }, + target = realearn.Target.ReaperAction { + command = 40141, + invocation = "Relative", + }, + }, + { + id = "261067ef-c2f0-41de-b10f-418cc718cf1c", + name = "Master touch", + group = "master", + source = realearn.Source.Virtual { + id = "main/fader/touch", + character = "Button", + }, + target = realearn.Target.TrackAutomationTouchState { + track = realearn.TrackDescriptor.Master {}, + touched_parameter = "Volume", + }, + }, + { + id = "2f45cdf6-bac9-4a97-8a3e-c296d2cc7e0f", + name = "Master volume", + group = "master", + source = realearn.Source.Virtual { + id = "main/fader", + }, + target = realearn.Target.TrackVolume { + track = realearn.TrackDescriptor.Master {}, + }, + }, + { + id = "970e04c9-e262-478e-85ce-9e84875fb5f5", + name = "Marker", + group = "master", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "marker", + character = "Button", + }, + glue = { + out_of_range_behavior = "Min", + button_filter = "PressOnly", + }, + target = realearn.Target.ReaperAction { + command = 40157, + invocation = "Trigger", + }, + }, + { + id = "c49238b9-189e-42d3-984a-955fe63c46e8", + name = "Global read", + group = "master", + source = realearn.Source.Virtual { + id = "read", + character = "Button", + }, + glue = { + target_interval = { 1, 1 }, + absolute_mode = "ToggleButton", + }, + target = realearn.Target.AutomationModeOverride { + override = realearn.AutomationModeOverride.Mode { + mode = "Read", + }, + }, + }, + { + id = "b805f628-16e1-42ca-ac36-3bb95133815f", + name = "Global write", + group = "master", + source = realearn.Source.Virtual { + id = "write", + character = "Button", + }, + glue = { + target_interval = { 1, 1 }, + absolute_mode = "ToggleButton", + }, + target = realearn.Target.AutomationModeOverride { + override = realearn.AutomationModeOverride.Mode { + mode = "Touch", + }, + }, + }, + { + id = "13d79d42-4d59-4930-a976-b4374e0a69b6", + name = "Click", + group = "master", + source = realearn.Source.Virtual { + id = "click", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + }, + target = realearn.Target.ReaperAction { + command = 40364, + }, + }, + { + id = "any-solo", + name = "Any solo", + group = "master", + source = realearn.Source.Virtual { + id = "solo", + character = "Button", + }, + target = realearn.Target.AnyOn { + parameter = "TrackSolo", + }, + }, + -- Done + { + id = "7b90c136-f89c-477e-b812-525b4a7da5ed", + name = "Assignment", + group = "lcd", + control_enabled = false, + source = realearn.Source.Virtual { + id = "lcd/assignment", + }, + glue = { + target_interval = if follow_track_selection then nil else { 0, 0.01 }, + feedback = if follow_track_selection then lcd_assignment_text_feedback else lcd_assignment_numeric_feedback, + }, + target = if follow_track_selection + then lcd_assignment_browse_tracks_target + else lcd_assignment_adjust_track_param_target, + }, + -- Done + { + id = "7GFNQfZp9uqovhV5zWtvh", + name = "Timecode", + group = "lcd", + control_enabled = false, + source = realearn.Source.Virtual { + id = "lcd/timecode", + }, + glue = { + feedback = realearn.Feedback.Text { + text_expression = "{{ target.position.project_default.mcu }}", + }, + }, + target = realearn.Target.Seek { + feedback_resolution = "High", + }, + }, + -- Time modes + { + id = "time-mode-measures-beats", + name = "Measures.Beats", + group = "time-modes", + control_enabled = false, + source = realearn.Source.Virtual { + id = "smpte-beats", + character = "Button", + }, + glue = { + source_interval = { 0, 0 }, + }, + target = realearn.Target.ReaperAction { + command = 40411, + invocation = "Trigger", + }, + }, + { + id = "time-mode-seconds", + name = "Seconds", + group = "time-modes", + control_enabled = false, + source = realearn.Source.Virtual { + id = "smpte-beats", + character = "Button", + }, + target = realearn.Target.ReaperAction { + command = 40412, + invocation = "Trigger", + }, + }, + { + id = "time-mode-samples", + name = "Samples", + group = "time-modes", + control_enabled = false, + source = realearn.Source.Virtual { + id = "smpte-beats", + character = "Button", + }, + target = realearn.Target.ReaperAction { + command = 40413, + invocation = "Trigger", + }, + }, + { + id = "time-mode-hmsf", + name = "Hours:Minutes:Seconds:Frames", + group = "time-modes", + control_enabled = false, + source = realearn.Source.Virtual { + id = "smpte-beats", + character = "Button", + }, + target = realearn.Target.ReaperAction { + command = 40414, + invocation = "Trigger", + }, + }, + { + id = "time-mode-absolute-frames", + name = "Absolute Frames", + group = "time-modes", + control_enabled = false, + source = realearn.Source.Virtual { + id = "smpte-beats", + character = "Button", + }, + target = realearn.Target.ReaperAction { + command = 41972, + invocation = "Trigger", + }, + }, + { + id = "time-mode-minutes-seconds", + name = "Minutes:Seconds", + group = "time-modes", + control_enabled = false, + source = realearn.Source.Virtual { + id = "smpte-beats", + character = "Button", + }, + target = realearn.Target.ReaperAction { + command = 40410, + invocation = "Trigger", + }, + }, + { + id = "cycle-time-modes", + name = "Cycle time modes", + group = "master", + feedback_enabled = false, + source = realearn.Source.Virtual { + id = "smpte-beats", + character = "Button", + }, + glue = { + absolute_mode = "IncrementalButton", + wrap = true, + }, + target = realearn.Target.BrowseGroupMappings { + group = "time-modes", + }, + }, +} + +-- For each channel +for ch = 0, channel_count - 1 do + local human_ch = ch + 1 + local prefix = "ch" .. human_ch .. "/" + local track_expression_incl_master_track = "((selected_track_index != none) && selected_track_index || 0) + " .. ch + local track_expression_excl_master_track = "max(selected_track_index, 0) + " .. ch + local track_expression_via_track_selection = if support_nav_to_master_track + then track_expression_incl_master_track + else track_expression_excl_master_track + local track_expression_via_parameter = "p[0] * 10000 + " .. ch + local track_expression = if follow_track_selection + then track_expression_via_track_selection + else track_expression_via_parameter + local track_volume = realearn.Mapping { + id = prefix .. "vol", + name = "Tr" .. human_ch .. " Vol", + group = "volume", + source = realearn.Source.Virtual { + id = prefix .. "fader", + }, + target = realearn.Target.TrackVolume { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + local track_pan_control = realearn.Mapping { + id = prefix .. "pan", + name = "Tr" .. human_ch .. " Pan", + group = "pan", + source = realearn.Source.Virtual { + id = prefix .. "v-pot", + }, + glue = { + step_size_interval = { 0.005, 1.0 }, + }, + target = realearn.Target.TrackPan { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + local track_pan_feedback = realearn.Mapping { + id = prefix .. "pan/feedback", + name = "Tr" .. human_ch .. " Pan Feedback", + control_enabled = false, + group = "pan", + source = realearn.Source.Virtual { + id = prefix .. "v-pot/boost-cut", + }, + target = realearn.Target.TrackPan { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + local track_pan_reset = realearn.Mapping { + id = prefix .. "pan/reset", + name = "Tr" .. human_ch .. " Pan Reset", + feedback_enabled = false, + group = "pan-reset", + source = realearn.Source.Virtual { + id = prefix .. "v-select", + character = "Button", + }, + glue = { + target_interval = { 0.5, 0.5 }, + }, + target = realearn.Target.TrackPan { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + local track_selection = realearn.Mapping { + id = prefix .. "selection", + name = "Tr" .. human_ch .. " Selection", + group = "selection", + source = realearn.Source.Virtual { + id = prefix .. "select", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + }, + target = realearn.Target.TrackSelectionState { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + scroll_mixer = true, + }, + } + local track_mute = realearn.Mapping { + id = prefix .. "mute", + name = "Tr" .. human_ch .. " Mute", + group = "mute", + source = realearn.Source.Virtual { + id = prefix .. "mute", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + }, + target = realearn.Target.TrackMuteState { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + local track_solo = realearn.Mapping { + id = prefix .. "solo", + name = "Tr" .. human_ch .. " Solo", + group = "solo", + source = { + kind = "Virtual", + id = prefix .. "solo", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + }, + target = realearn.Target.TrackSoloState { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + local track_arm = realearn.Mapping { + id = prefix .. "arm", + name = "Tr" .. human_ch .. " Arm", + group = "arm", + source = realearn.Source.Virtual { + id = prefix .. "record-ready", + character = "Button", + }, + glue = { + absolute_mode = "ToggleButton", + }, + target = realearn.Target.TrackArmState { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + local track_touch = realearn.Mapping { + id = prefix .. "touch", + name = "Tr" .. human_ch .. " Touch", + group = "touch", + source = realearn.Source.Virtual { + id = prefix .. "fader/touch", + character = "Button", + }, + target = realearn.Target.TrackAutomationTouchState { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + touched_parameter = "Volume", + }, + } + -- Done + local track_name_display = realearn.Mapping { + id = prefix .. "name", + name = "Tr" .. human_ch .. " Name", + group = "lcd", + control_enabled = false, + source = realearn.Source.Virtual { + id = prefix .. "lcd/line1", + }, + glue = { + feedback = realearn.Feedback.Text { + color = realearn.PropColor { + prop = "target.track.color", + }, + text_expression = "{{ target.track.name }}", + }, + }, + target = realearn.Target.TrackTool { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + -- Done + local track_pan_display = realearn.Mapping { + id = prefix .. "pan/lcd", + name = "Tr" .. human_ch .. " Pan LCD", + group = "lcd", + control_enabled = false, + source = realearn.Source.Virtual { + id = prefix .. "lcd/line2", + }, + glue = { + feedback = realearn.Feedback.Text { + kind = "Text", + text_expression = "{{ target.pan.mcu }}", + }, + }, + target = realearn.Target.TrackPan { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + -- Done + local track_peak = { + id = prefix .. "peak", + name = "Tr" .. human_ch .. " Peaks", + group = "meter", + control_enabled = false, + source = realearn.Source.Virtual { + id = prefix .. "meter/peak", + }, + target = realearn.Target.TrackPeak { + track = realearn.TrackDescriptor.Dynamic { + expression = track_expression, + }, + }, + } + table.insert(mappings, track_volume) + table.insert(mappings, track_pan_control) + table.insert(mappings, track_pan_feedback) + table.insert(mappings, track_pan_reset) + table.insert(mappings, track_selection) + table.insert(mappings, track_mute) + table.insert(mappings, track_solo) + table.insert(mappings, track_arm) + table.insert(mappings, track_touch) + table.insert(mappings, track_name_display) + table.insert(mappings, track_pan_display) + table.insert(mappings, track_peak) +end + +return realearn.Compartment { + parameters = parameters, + groups = groups, + mappings = mappings, +} diff --git a/plugin-reaper-realearn/resources/main-presets/factory/generic/grid/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/generic/grid/playtime.preset.luau new file mode 100644 index 0000000..1ba203a --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/generic/grid/playtime.preset.luau @@ -0,0 +1,75 @@ +--- name: Generic grid controller - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Generic +--- device_name: Generic +--- description: | +--- This main preset turns any grid-like controller into a device for doing very basic clip launching via Playtime. +--- +--- This generic preset is intended to be used as fallback if no device-specific preset exists. It lacks features because it can't take advantage +--- of device-specific features. +--- used_schemes: [grid] +--- required_features: [playtime] + +--!strict + +-- Configuration + +local stop_column_if_slot_empty = true + +-- Requires + +local realearn = require("realearn") + +-- Constants + +-- TODO This should be parameterized from the controller preset. One more reason to allow script parameterization :) + +local column_count = 4 +local row_count = 4 + +-- Mappings + +local mappings = {} + +-- For each slot +for col = 0, column_count - 1 do + for row = 0, row_count - 1 do + local mapping = realearn.Mapping { + name = `Trigger slot {col + 1}/{row + 1}`, + source = realearn.Source.Virtual { + character = "Multi", + id = `col{col + 1}/row{row + 1}/pad`, + }, + glue = { + feedback = realearn.Feedback.Text { + text_expression = "{{ target.slot_state.id }}", + color = realearn.VirtualColor { + prop = "target.slot.color", + }, + }, + }, + target = realearn.Target.PlaytimeSlotTransportAction { + slot = realearn.PlaytimeSlotDescriptor.Dynamic { + column_expression = `control_unit_column_index + {col}`, + row_expression = `control_unit_row_index + {row}`, + }, + action = "Trigger", + stop_column_if_slot_empty = stop_column_if_slot_empty, + }, + } + table.insert(mappings, mapping) + end +end + +return realearn.Compartment { + mappings = mappings :: any, + custom_data = { + playtime = { + control_unit = { + column_count = column_count, + row_count = row_count, + }, + }, + }, +} diff --git a/plugin-reaper-realearn/resources/main-presets/factory/generic/numbered/fx-parameters.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/generic/numbered/fx-parameters.preset.luau new file mode 100644 index 0000000..7f7e5ea --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/generic/numbered/fx-parameters.preset.luau @@ -0,0 +1,122 @@ +--- name: Numbered - FX parameters +--- realearn_version: 2.16.0-pre.9 +--- author: helgoboss +--- device_manufacturer: Generic +--- device_name: Generic +--- description: | +--- This main preset is built for controllers that offer a set of knobs and buttons. Each knob will control one parameter of +--- the unit FX (which by default resolves to the currently focused FX), starting with parameter 1. Each button will switch +--- to the next bank of parameters. If the controller has `bank-left` and `bank-right` buttons, they can be used to switch +--- between pages, making it possible to control even more parameters. +--- +--- At the moment, this preset assumes that the controller has exactly 16 knobs and buttons. In future, this will be variable. +--- used_schemes: [numbered] + +--!strict + +local realearn = require("realearn") + +--- Configuration + +local multi_count = 16 +local button_count = 16 + +-- Code + +local page_count = 16 +local bank_count = button_count + +local parameters: { realearn.Parameter } = { + { + index = 0, + name = "Page", + value_count = page_count, + }, + { + index = 1, + name = "Bank", + value_count = bank_count, + }, +} + +local mappings: { realearn.Mapping } = { + -- Side button left => Decrease page + realearn.Mapping { + name = "Page -", + source = realearn.Source.Virtual { + character = "Button", + id = "bank-left", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = true, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = 0, + }, + }, + }, + -- Side button right => Increase page + realearn.Mapping { + name = "Page +", + source = realearn.Source.Virtual { + character = "Button", + id = "bank-right", + }, + glue = { + absolute_mode = "IncrementalButton", + reverse = false, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = 0, + }, + }, + }, +} + +-- Encoder movement => Set FX parameter value +for i = 0, multi_count - 1 do + local m = realearn.Mapping { + name = `Set parameter value {i + 1}`, + source = realearn.Source.Virtual { + character = "Multi", + id = i, + }, + target = realearn.Target.FxParameterValue { + parameter = realearn.FxParameterDescriptor.Dynamic { + fx = realearn.FxDescriptor.Instance {}, + expression = `p[0] * {page_count} * {bank_count} + p[1] * {bank_count} + {i}`, + }, + }, + } + table.insert(mappings, m) +end + +-- Encoder push => Select bank +for i = 0, button_count - 1 do + local normalized_bank = i / (button_count - 1) + local m = realearn.Mapping { + name = `Switch to bank {i + 1}`, + source = realearn.Source.Virtual { + character = "Button", + id = i, + }, + glue = { + out_of_range_behavior = "Min", + target_interval = { normalized_bank, normalized_bank }, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = 1, + }, + }, + } + table.insert(mappings, m) +end + +return realearn.Compartment { + parameters = parameters, + mappings = mappings, +} diff --git a/plugin-reaper-realearn/resources/main-presets/factory/generic/unsorted/keyboard-to-midi.luau b/plugin-reaper-realearn/resources/main-presets/factory/generic/unsorted/keyboard-to-midi.luau new file mode 100644 index 0000000..c787e4e --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/generic/unsorted/keyboard-to-midi.luau @@ -0,0 +1,29 @@ +local mappings = {} + +for i = 0, 7 do + local m = { + name = tostring(i + 1), + source = { + kind = "Key", + keystroke = { + modifiers = 1, + key = 49 + i, + }, + }, + glue = { + button_filter = "PressOnly", + }, + target = { + kind = "SendMidi", + message = "B0 00 0" .. i, + }, + } + table.insert(mappings, m) +end + +return { + kind = "MainCompartment", + value = { + mappings = mappings, + } +} diff --git a/plugin-reaper-realearn/resources/main-presets/factory/midi_script_source_runtime.luau b/plugin-reaper-realearn/resources/main-presets/factory/midi_script_source_runtime.luau new file mode 120000 index 0000000..2a8b541 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/midi_script_source_runtime.luau @@ -0,0 +1 @@ +../../api/luau/midi_script_source_runtime.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-lib/playtime-commons.luau b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-lib/playtime-commons.luau new file mode 100644 index 0000000..90567bc --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-lib/playtime-commons.luau @@ -0,0 +1,1062 @@ +--!strict + +local partial = require("partial") +local util = require("util") +local realearn = require("realearn") +local playtime_util = require("playtime_util") + +local module = {} + +export type LaunchpadPlaytimePresetConfig = { + stop_column_if_slot_empty: boolean, + + --- If true, the last row of pads will not be used to switch between column modes. Instead, dedicated column + --- action buttons will be used for that purpose. + --- + --- This flag is tailored to the Launchpad Pro mk3 + has_dedicated_column_action_buttons: boolean, + has_shift_button: boolean, + has_delete_button: boolean, + has_quantize_button: boolean, + has_fixed_length_button: boolean, + has_duplicate_button: boolean, + has_play_button: boolean, + has_record_button: boolean, + + --- Button that switches to column mode "Stop clip" + stop_clip_button_id: string?, + --- Whether mixer mode needs to be active for the button to work + stop_clip_needs_mixer: boolean, + --- Button that switches to column mode "Mute" + mute_button_id: string?, + --- Whether mixer mode needs to be active for the button to work + mute_needs_mixer: boolean, + --- Button that switches to column mode "Solo" + solo_button_id: string?, + --- Whether mixer mode needs to be active for the button to work + solo_needs_mixer: boolean, + --- Button that switches to column mode "Record arm" + record_arm_button_id: string?, + --- Whether mixer mode needs to be active for the button to work + record_arm_needs_mixer: boolean, + --- Button that switches to column mode "Track select" + track_select_button_id: string?, + + undo_button_id: string?, + undo_needs_shift: boolean, + redo_button_id: string?, + redo_needs_shift: boolean, + click_button_id: string?, + click_needs_shift: boolean, + double_button_id: string?, + double_needs_shift: boolean, + tap_button_id: string?, + tap_needs_shift: boolean, + + --- If true, the last row play button switches between a few column modes according to the Launchpad Mini layout. + --- + --- This flag is tailored to the Launchpad Mini mk3. + use_last_row_play_button_for_column_modes: boolean, + + --- If set, pressing the mixer button will turn all row play buttons into column mode buttons according + --- to the Launchpad mk1 and Launchpad X layout. + --- + --- This flag is tailored to the Launchpad mk1 and Launchpad X. + mixer_button_id: string?, + + --- If true, normal buttons will use the color velocity values of the Launchpad mk1, which are different + --- from more recent Launchpad versions. + --- + --- This doesn't affect the slot play state stuff. This part is handled by the controller preset. + use_mk1_colors: boolean, +} + +function module.create_compartment(config: LaunchpadPlaytimePresetConfig): realearn.Compartment + -- Aliases + + local partial_mapping = util.partial_mapping + local turbo = playtime_util.turbo + local scroll_vertically = playtime_util.scroll_vertically + local scroll_horizontally = playtime_util.scroll_horizontally + + -- Types + + type Mode = { + index: number, + label: string, + } + + type ModeMap = { [string]: Mode } + + type ParameterMap = { [string]: realearn.Parameter } + + type GroupMap = { [string]: realearn.Group } + + -- Constants + + local column_count = 8 + local row_count = 8 + local slot_mode_count = 100 + local column_mode_count = 100 + local long_press_millis = 300 + + -- Slot modes + + local slot_modes: ModeMap = { + normal = { + index = 0, + label = "Normal", + }, + delete = { + index = 1, + label = "Delete", + }, + quantise = { + index = 2, + label = "Quantise", + }, + duplicate = { + index = 3, + label = "Duplicate", + }, + double = { + index = 4, + label = "Double", + }, + } + local sorted_slot_modes = util.sorted_by_index(slot_modes) + + -- Column modes + + -- Using a function has the advantage that the resulting table is sealed and rejects property accesses with different properties + local function create_column_modes() + return { + normal = { + index = 0, + label = "Normal", + }, + record_arm = { + index = 1, + label = "Record Arm", + }, + track_select = { + index = 2, + label = "Track Select", + }, + mute = { + index = 3, + label = "Mute", + }, + solo = { + index = 4, + label = "Solo", + }, + stop_clip = { + index = 5, + label = "Stop clip", + }, + set_record_length = { + index = 6, + label = "Set record length", + }, + } + end + local column_modes = create_column_modes() + local sorted_column_modes = util.sorted_by_index(column_modes) + + -- Parameters + + local params: ParameterMap = { + shift = { + index = 0, + name = "Shift modifier", + }, + slot_mode = { + index = 1, + name = "Slot mode", + value_count = slot_mode_count, + value_labels = util.extract_labels(sorted_slot_modes), + }, + column_mode = { + index = 2, + name = "Column mode", + value_count = column_mode_count, + value_labels = util.extract_labels(sorted_column_modes), + }, + mixer = { + index = 3, + name = "Mixer modifier", + }, + } + + -- Domain functions + + local function create_index_expression(variable_name: string, index: number): string + return `{variable_name} + {index}` + end + + local function create_col_expression(col: number): string + return create_index_expression("control_unit_column_index", col) + end + + local function create_row_expression(row: number): string + return create_index_expression("control_unit_row_index", row) + end + + local function create_slot_selector(col: number, row: number): realearn.PlaytimeSlotDescriptor + return realearn.PlaytimeSlotDescriptor.Dynamic { + column_expression = create_col_expression(col), + row_expression = create_row_expression(row), + } + end + + local function create_column_selector(col: number): realearn.PlaytimeColumnDescriptor + return realearn.PlaytimeColumnDescriptor.Dynamic { + expression = create_col_expression(col), + } + end + + local function create_row_selector(row: number): realearn.PlaytimeRowDescriptor + return realearn.PlaytimeRowDescriptor.Dynamic { + expression = create_row_expression(row), + } + end + + local function pad(id: string) + return partial_mapping { + source = realearn.Source.Virtual { + character = "Multi", + id = id, + }, + } + end + + local function button(id: string) + return partial_mapping { + source = realearn.Source.Virtual { + character = "Button", + id = id, + }, + } + end + + local function row_play_button(row: number) + return button(`row{row + 1}/play`) + end + + local function slot_button(col: number, row: number) + return pad(`col{col + 1}/row{row + 1}/pad`) + end + + local function column_action_button(col: number) + return button(`col{col + 1}/action`) + end + + local function clip_column_action(col: number, action: realearn.PlaytimeColumnAction) + return partial_mapping { + target = realearn.Target.PlaytimeColumnAction { + column = create_column_selector(col), + action = action, + }, + } + end + + local function clip_row_action(row: number, action: realearn.PlaytimeRowAction) + return partial_mapping { + target = realearn.Target.PlaytimeRowAction { + row = create_row_selector(row), + action = action, + }, + } + end + + local function clip_column_track(col: number): realearn.TrackDescriptor + return realearn.TrackDescriptor.FromClipColumn { + column = create_column_selector(col), + context = "Playback", + } + end + + local function column_track_target(col: number, track_target_kind: realearn.TargetKind, exclusive: boolean?) + return partial_mapping { + target = { + kind = track_target_kind, + track = clip_column_track(col), + exclusivity = if exclusive then "WithinFolderOnOnly" else nil, + } :: any, + } + end + + local function clip_transport_action(col: number, row: number, action: realearn.PlaytimeSlotTransportAction) + return partial_mapping { + target = realearn.Target.PlaytimeSlotTransportAction { + slot = create_slot_selector(col, row), + action = action, + stop_column_if_slot_empty = config.stop_column_if_slot_empty, + }, + } + end + + local function clip_management_action(col: number, row: number, action: realearn.PlaytimeSlotManagementAction) + return partial_mapping { + glue = { + button_filter = "PressOnly", + }, + target = realearn.Target.PlaytimeSlotManagementAction { + slot = create_slot_selector(col, row), + action = action, + }, + } + end + + local function clip_matrix_action(action: realearn.PlaytimeMatrixAction) + return partial_mapping { + target = realearn.Target.PlaytimeMatrixAction { + action = action, + }, + } + end + + local function toggle() + return partial_mapping { + glue = { + absolute_mode = "ToggleButton", + }, + } + end + + local function control_disabled() + return partial_mapping { + control_enabled = false, + visible_in_projection = false, + } + end + + local function feedback_disabled() + return partial_mapping { + feedback_enabled = false, + } + end + + local function set_param(index: number) + return partial_mapping { + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = index, + }, + }, + } + end + + local function name(n: string) + return partial_mapping { + name = n, + } + end + + --- Sets the source interval for the purpose of displaying different LED colors for off/on states. + --- + --- Both `off` and `on` should be integers between 0 and 127, representing the Launchpad colors. + --- + --- In feedback direction, source min is sent when the target is off, source max is sent when it's on. + --- For Launchpad buttons, those values influence the LED color. + --- + --- Attention: Since the source interval also influences the control direction, this should in most cases + --- be used only for feedback-only mappings. + local function color_pair(off: number, on: number) + if off > on then + error("on color must be >= off color") + end + return partial_mapping { + glue = realearn.Glue { + source_interval = { off / 127, on / 127 }, + }, + } + end + + + -- All color velocity values for Launchpad mk1 + local mk1 = { + off = 12, + red_low = 13, + red_full = 15, + amber_low = 29, + amber_full = 63, + yellow_full = 62, + green_low = 28, + green_full = 60, + red_flashing = 11, + amber_flashing = 59, + yellow_flashing = 58, + green_flashing = 56, + } + + -- Some color velocity values for Launchpads > mk1 + local mkn = { + off = 0, + green_low = 23, + green_full = 122, + gray = 1, + white = 3, + red_low = 7, + red_full = 72, + sky_low = 39, + sky_full = 78, + yellow_low = 15, + yellow_high = 74, + blue_low = 47, + blue_full = 66, + amber_low = 11, + amber_full = 84, + } + + local function set_mode_momentary(mode: Mode, mode_count: number, mode_param_index: number) + local target_value = mode.index / (mode_count - 1) + return partial_mapping { + name = mode.label, + glue = realearn.Glue { + target_value_sequence = `0, {target_value}`, + feedback = realearn.Feedback.Numeric { + transformation = `x = y == {target_value}`, + }, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = mode_param_index, + }, + }, + } + end + + local colors = if config.use_mk1_colors + then { + off_normal = color_pair(mk1.off, mk1.amber_full), + normal = color_pair(mk1.green_low, mk1.green_full), + shift = color_pair(mk1.amber_low, mk1.amber_full), + arm = color_pair(mk1.red_low, mk1.red_full), + select = color_pair(mk1.amber_low, mk1.amber_full), + mute = color_pair(mk1.amber_low, mk1.yellow_full), + solo = color_pair(mk1.amber_low, mk1.amber_full), + stop_clip = color_pair(mk1.amber_low, mk1.amber_full), + } + else { + off_normal = color_pair(mkn.off, mkn.green_low), + normal = color_pair(mkn.green_low, mkn.green_full), + shift = color_pair(mkn.gray, mkn.white), + arm = color_pair(mkn.red_low, mkn.red_full), + select = color_pair(mkn.sky_low, mkn.sky_full), + mute = color_pair(mkn.yellow_low, mkn.yellow_high), + solo = color_pair(mkn.blue_low, mkn.blue_full), + stop_clip = color_pair(mkn.amber_low, mkn.amber_full), + } + + + local function compartment_parameter_target(index: number) + return partial_mapping { + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = index, + }, + }, + } + end + + local function set_mode_toggle(mode: Mode, mode_count: number, mode_param_index: number) + local target_value = mode.index / (mode_count - 1) + return partial_mapping { + name = mode.label, + glue = realearn.Glue { + button_filter = "PressOnly", + control_transformation = `y = y == {target_value} ? 0 : {target_value}`, + feedback = realearn.Feedback.Numeric { + transformation = `x = y == {target_value}`, + }, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = mode_param_index, + }, + }, + } + end + + --- Sets the value back to zero if the button is pressed longer and then released. + --- + --- Can be used to turn toggle buttons into momentary buttons when pressed longer. + local function auto_release() + return partial_mapping { + glue = realearn.Glue { + target_interval = { 0.0, 0.0 }, + fire_mode = realearn.FireMode.Normal { + press_duration_interval = { long_press_millis, 10000 }, + }, + }, + } + end + + local function fire_if_pressed_less_than(millis: number) + return partial_mapping { + glue = realearn.Glue { + fire_mode = realearn.FireMode.Normal { + press_duration_interval = { 0, millis }, + }, + }, + } + end + + local function fire_after_timeout(millis: number) + return partial_mapping { + glue = realearn.Glue { + fire_mode = realearn.FireMode.AfterTimeout { + timeout = millis, + }, + }, + } + end + + local function set_slot_mode_momentary(mode: Mode) + return set_mode_momentary(mode, slot_mode_count, params.slot_mode.index) + end + + local function set_column_mode_toggle(mode: Mode) + return set_mode_toggle(mode, column_mode_count, params.column_mode.index) + end + + local function set_column_mode_momentary(mode: Mode) + return set_mode_momentary(mode, column_mode_count, params.column_mode.index) + end + + local function slot_mode_is(slot_mode: Mode) + return partial_mapping { + activation_condition = realearn.ActivationCondition.Expression { + condition = `p[{params.shift.index}] == 0.0 && p[{params.slot_mode.index}] == {slot_mode.index}`, + }, + } + end + + local function column_mode_is_normal_and_slot_mode_is(slot_mode: Mode) + return partial_mapping { + activation_condition = realearn.ActivationCondition.Expression { + condition = `p[{params.shift.index}] == 0.0 && p[{params.column_mode.index}] == {column_modes.normal.index} && p[{params.slot_mode.index}] == {slot_mode.index}`, + }, + } + end + + local function column_mode_is(column_mode: Mode) + return partial_mapping { + activation_condition = realearn.ActivationCondition.Bank { + parameter = params.column_mode.index, + bank_index = column_mode.index, + }, + } + end + + local function modifier_is(param: realearn.Parameter, on: boolean) + return partial_mapping { + activation_condition = realearn.ActivationCondition.Modifier { + modifiers = { + { + parameter = param.index, + on = on, + }, + }, + }, + } + end + + local function shift_is(on: boolean) + return modifier_is(params.shift, on) + end + + local function mixer_is(on: boolean) + return modifier_is(params.mixer, on) + end + + local function led_on_off() + return partial_mapping { + target = realearn.Target.Dummy {}, + } + end + + local function discrete_target_interval(min: number, max: number, count: number) + local max_val = count - 1 + return partial_mapping { + glue = realearn.Glue { + target_interval = { min / max_val, max / max_val }, + }, + } + end + + -- Mappings + + local function shift_config(needs_shift: boolean) + return shift_is(needs_shift) + (if needs_shift then colors.shift else colors.normal) + end + + local mappings = {} + + if config.has_shift_button then + table.insert(mappings, name("Shift") + button("shift") + colors.shift + set_param(params.shift.index)) + end + + if config.has_delete_button then + table.insert( + mappings, + name("Delete") + + button("delete") + + shift_is(false) + + colors.normal + + set_slot_mode_momentary(slot_modes.delete) + ) + end + + if config.has_fixed_length_button then + -- When short-pressing "Fixed length" button, toggle between open ended and custom-length mode + table.insert( + mappings, + name("Fixed length") + + button("fixed-length") + + shift_is(false) + + fire_if_pressed_less_than(long_press_millis) + + colors.normal + + toggle() + + discrete_target_interval(0, 1, 10) + + clip_matrix_action("SetRecordLengthMode") + ) + -- When long-pressing "Fixed length" button, allow user to configure custom length via column action buttons + table.insert( + mappings, + name("Fixed length - Long press 1") + + button("fixed-length") + + shift_is(false) + + fire_after_timeout(long_press_millis) + + feedback_disabled() + + set_column_mode_momentary(column_modes.set_record_length) + ) + -- When long-pressing "Fixed length" button, also enter custom-length mode + table.insert( + mappings, + name("Fixed length - Long press 2") + + button("fixed-length") + + shift_is(false) + + fire_after_timeout(long_press_millis) + + feedback_disabled() + + discrete_target_interval(1, 1, 10) + + clip_matrix_action("SetRecordLengthMode") + ) + end + + if config.has_duplicate_button then + table.insert( + mappings, + name("Duplicate") + + button("duplicate") + + shift_is(false) + + colors.normal + + set_slot_mode_momentary(slot_modes.duplicate) + ) + end + + if config.has_play_button then + table.insert( + mappings, + name("Play") + + button("play") + + shift_is(false) + + colors.normal + + toggle() + + clip_matrix_action("PlayIgnitedOrEnterSilenceMode") + ) + end + + if config.has_record_button then + table.insert( + mappings, + name("Record") + button("record") + shift_is(false) + colors.arm + clip_matrix_action("SmartRecord") + ) + end + + if config.has_quantize_button then + table.insert( + mappings, + name("Quantise") + + button("quantize") + + shift_is(false) + + colors.normal + + set_slot_mode_momentary(slot_modes.quantise) + ) + table.insert( + mappings, + name("Record Quantise") + + button("quantize") + + shift_is(true) + + colors.shift + + toggle() + + clip_matrix_action("MidiAutoQuantizationOnOffState") + ) + end + + if config.click_button_id then + table.insert( + mappings, + name("Click") + + button(config.click_button_id) + + shift_config(config.click_needs_shift) + + toggle() + + clip_matrix_action("ClickOnOffState") + ) + end + + if config.undo_button_id then + table.insert( + mappings, + name("Undo") + + button(config.undo_button_id) + + shift_is(config.undo_needs_shift) + + feedback_disabled() + + clip_matrix_action("Undo") + ) + table.insert( + mappings, + name("Undo LED") + button(config.undo_button_id) + shift_config(config.undo_needs_shift) + led_on_off() + ) + end + + if config.redo_button_id then + table.insert( + mappings, + name("Redo") + + button(config.redo_button_id) + + shift_is(config.redo_needs_shift) + + feedback_disabled() + + clip_matrix_action("Redo") + ) + table.insert( + mappings, + name("Redo LED") + button(config.redo_button_id) + shift_config(config.redo_needs_shift) + led_on_off() + ) + end + + if config.tap_button_id then + table.insert( + mappings, + name("Tap") + + button(config.tap_button_id) + + shift_is(config.tap_needs_shift) + + feedback_disabled() + + clip_matrix_action("TapTempo") + ) + table.insert( + mappings, + name("Tap LED") + button(config.tap_button_id) + shift_config(config.tap_needs_shift) + led_on_off() + ) + end + + if config.double_button_id then + table.insert( + mappings, + name("Double") + + button(config.double_button_id) + + shift_config(config.double_needs_shift) + + set_slot_mode_momentary(slot_modes.double) + ) + end + + if config.mixer_button_id then + table.insert( + mappings, + name("Toggle mixer mode") + + button(config.mixer_button_id) + + colors.shift + + toggle() + + set_param(params.mixer.index) + ) + end + + local function add_scroll_mappings(b: string, target: partial.Partial) + table.insert(mappings, name(b) + button(b) + feedback_disabled() + turbo() + target) + table.insert(mappings, name(b) + button(b) + control_disabled() + colors.off_normal + target) + end + + add_scroll_mappings("cursor-up", scroll_vertically(-1)) + add_scroll_mappings("cursor-down", scroll_vertically(1)) + add_scroll_mappings("cursor-left", scroll_horizontally(-1)) + add_scroll_mappings("cursor-right", scroll_horizontally(1)) + + local function add_column_mode_mappings( + label: string, + button_id: string, + needs_mixer_mode: boolean, + mode: Mode, + partial: partial.Partial + ) + local condition + if needs_mixer_mode then + condition = mixer_is(true) + else + condition = shift_is(false) + end + + table.insert(mappings, name(label) + button(button_id) + condition + partial + set_column_mode_toggle(mode)) + table.insert( + mappings, + name(`{label} auto-release`) + + feedback_disabled() + + button(button_id) + + condition + + auto_release() + + compartment_parameter_target(params.column_mode.index) + ) + end + + if config.record_arm_button_id then + add_column_mode_mappings( + "Record Arm", + config.record_arm_button_id, + config.record_arm_needs_mixer, + column_modes.record_arm, + colors.arm + ) + end + if config.track_select_button_id then + add_column_mode_mappings( + "Track Select", + config.track_select_button_id, + false, + column_modes.track_select, + colors.select + ) + end + if config.mute_button_id then + add_column_mode_mappings("Mute", config.mute_button_id, config.mute_needs_mixer, column_modes.mute, colors.mute) + end + if config.solo_button_id then + add_column_mode_mappings("Solo", config.solo_button_id, config.solo_needs_mixer, column_modes.solo, colors.solo) + end + if config.stop_clip_button_id then + add_column_mode_mappings( + "Stop clip", + config.stop_clip_button_id, + config.stop_clip_needs_mixer, + column_modes.stop_clip, + colors.stop_clip + ) + end + + -- For each row + for row = 0, row_count - 1 do + if config.use_last_row_play_button_for_column_modes and row == row_count - 1 then + local function val(mode: Mode): number + return mode.index / (column_mode_count - 1) + end + table.insert( + mappings, + name("Switch column mode") + + feedback_disabled() + + row_play_button(row) + + partial_mapping { + glue = realearn.Glue { + absolute_mode = "IncrementalButton", + target_value_sequence = `0, {val(column_modes.stop_clip)}, {val(column_modes.solo)}, {val( + column_modes.mute + )}`, + wrap = true, + }, + target = realearn.Target.CompartmentParameterValue { + parameter = realearn.CompartmentParameterDescriptor.ById { + index = params.column_mode.index, + }, + }, + } + ) + else + local condition = partial_mapping {} + if config.mixer_button_id then + condition = mixer_is(false) + end + table.insert( + mappings, + name("Play scene") + + condition + + feedback_disabled() + + row_play_button(row) + + clip_row_action(row, "PlayScene") + ) + table.insert(mappings, name("Play scene LED") + condition + row_play_button(row) + colors.off_normal + led_on_off()) + end + end + + -- For each slot + for col = 0, column_count - 1 do + for row = 0, row_count - 1 do + local function create_normal_control_condition(slot_mode: Mode) + if not config.has_dedicated_column_action_buttons and row == row_count - 1 then + -- The last row is special. If the column mode is not the normal one, it will carry out different functions. + return column_mode_is_normal_and_slot_mode_is(slot_mode) + end + return slot_mode_is(slot_mode) + end + local function create_normal_feedback_condition() + if not config.has_dedicated_column_action_buttons and row == row_count - 1 then + -- The last row is special. If the column mode is not the normal one, it will carry out different functions. + return column_mode_is(column_modes.normal) + end + return partial_mapping {} + end + -- Activate slot (shift) + table.insert( + mappings, + name("Activate slot") + + shift_is(true) + + feedback_disabled() + + slot_button(col, row) + + clip_management_action(col, row, "Activate") + ) + -- Control (normal) + table.insert( + mappings, + name("Trigger slot") + + create_normal_control_condition(slot_modes.normal) + + feedback_disabled() + + slot_button(col, row) + + clip_transport_action(col, row, "Trigger") + ) + -- Control (delete) + table.insert( + mappings, + name("Clear slot") + + create_normal_control_condition(slot_modes.delete) + + feedback_disabled() + + slot_button(col, row) + + clip_management_action(col, row, "ClearSlot") + ) + -- Control (quantize) + table.insert( + mappings, + name("Quantize slot") + + create_normal_control_condition(slot_modes.quantise) + + feedback_disabled() + + slot_button(col, row) + + clip_management_action(col, row, "QuantizationOnOffState") + ) + -- Control (duplicate) + table.insert( + mappings, + name("Duplicate slot") + + create_normal_control_condition(slot_modes.duplicate) + + feedback_disabled() + + slot_button(col, row) + + clip_management_action(col, row, "Duplicate") + ) + -- Control (double) + table.insert( + mappings, + name("Double slot") + + create_normal_control_condition(slot_modes.double) + + feedback_disabled() + + slot_button(col, row) + + clip_management_action(col, row, "DoubleClipSectionLength") + ) + -- Feedback + table.insert( + mappings, + name("Slot feedback") + + create_normal_feedback_condition() + + control_disabled() + + slot_button(col, row) + + playtime_util.slot_state_text_feedback() + + clip_transport_action(col, row, "Trigger") + ) + end + end + + -- For the last row of slots or - if available - the dedicated column action buttons + for col = 0, column_count - 1 do + if config.has_dedicated_column_action_buttons then + table.insert( + mappings, + name("Activate column") + + column_mode_is(column_modes.normal) + + column_action_button(col) + + colors.select + + clip_column_action(col, "Activate") + ) + table.insert( + mappings, + name(`Set custom record length to {col + 1} bars`) + + column_mode_is(column_modes.set_record_length) + + column_action_button(col) + + discrete_target_interval(col, col, 64) + + clip_matrix_action("SetCustomRecordLengthInBars") + ) + end + + local source = if config.has_dedicated_column_action_buttons + then column_action_button(col) + else slot_button(col, row_count - 1) + -- Record-arm track + table.insert( + mappings, + name("Record Arm") + + column_mode_is(column_modes.record_arm) + + source + + toggle() + + colors.arm + + clip_column_action(col, "ArmStateExclusive") + ) + -- Select track + table.insert( + mappings, + name("Track Select") + + column_mode_is(column_modes.track_select) + + source + + toggle() + + colors.select + + column_track_target(col, "TrackSelectionState", true) + ) + -- Mute track + table.insert( + mappings, + name("Mute") + + column_mode_is(column_modes.mute) + + source + + toggle() + + colors.mute + + column_track_target(col, "TrackMuteState") + ) + -- Solo track + table.insert( + mappings, + name("Solo") + + column_mode_is(column_modes.solo) + + source + + toggle() + + colors.solo + + column_track_target(col, "TrackSoloState") + ) + -- Stop column + table.insert( + mappings, + name("Stop column") + + column_mode_is(column_modes.stop_clip) + + source + + colors.stop_clip + + clip_column_action(col, "Stop") + ) + end + + return realearn.Compartment { + parameters = util.sorted_by_index(params), + mappings = mappings :: any, + custom_data = { + playtime = { + control_unit = { + column_count = column_count, + row_count = row_count, + }, + }, + }, + } +end + +return module diff --git a/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-mini-mk3/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-mini-mk3/playtime.preset.luau new file mode 100644 index 0000000..6a6093e --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-mini-mk3/playtime.preset.luau @@ -0,0 +1,48 @@ +--- name: Launchpad Mini mk3 - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad Mini mk3 +--- description: | +--- This main preset turns the Launchpad Mini mk3 into a capable device for controlling Playtime. +--- used_schemes: [novation/launchpad-mini-mk3/live] +--- required_features: [playtime] + +--!strict + +local commons = require("novation/launchpad-lib/playtime-commons") + +return commons.create_compartment { + stop_column_if_slot_empty = true, + has_dedicated_column_action_buttons = false, + has_shift_button = false, + has_delete_button = false, + has_quantize_button = false, + has_fixed_length_button = false, + has_duplicate_button = false, + has_play_button = false, + has_record_button = false, + + stop_clip_button_id = nil, + stop_clip_needs_mixer = false, + mute_button_id = nil, + mute_needs_mixer = false, + solo_button_id = nil, + solo_needs_mixer = false, + record_arm_button_id = nil, + record_arm_needs_mixer = false, + track_select_button_id = nil, + + undo_button_id = nil, + undo_needs_shift = true, + redo_button_id = nil, + redo_needs_shift = true, + click_button_id = nil, + click_needs_shift = true, + double_button_id = nil, + double_needs_shift = true, + tap_button_id = nil, + tap_needs_shift = true, + use_last_row_play_button_for_column_modes = true, + use_mk1_colors = false, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-mk1/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-mk1/playtime.preset.luau new file mode 100644 index 0000000..9c3745b --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-mk1/playtime.preset.luau @@ -0,0 +1,51 @@ +--- name: Launchpad mk1 - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad mk1 +--- description: | +--- This main preset turns the Launchpad mk1 into a capable device for controlling Playtime. +--- +--- The following features are not yet supported: vol, pan, snd A, snd B +--- used_schemes: [novation/launchpad-mk1] +--- required_features: [playtime] + +--!strict + +local commons = require("novation/launchpad-lib/playtime-commons") + +return commons.create_compartment { + stop_column_if_slot_empty = true, + has_dedicated_column_action_buttons = false, + has_shift_button = false, + has_delete_button = false, + has_quantize_button = false, + has_fixed_length_button = false, + has_duplicate_button = false, + has_play_button = false, + has_record_button = false, + + stop_clip_button_id = "row5/play", + stop_clip_needs_mixer = true, + mute_button_id = "row6/play", + mute_needs_mixer = true, + solo_button_id = "row7/play", + solo_needs_mixer = true, + record_arm_button_id = "row8/play", + record_arm_needs_mixer = true, + track_select_button_id = nil, + + undo_button_id = nil, + undo_needs_shift = false, + redo_button_id = nil, + redo_needs_shift = false, + click_button_id = nil, + click_needs_shift = false, + double_button_id = nil, + double_needs_shift = false, + tap_button_id = nil, + tap_needs_shift = false, + use_last_row_play_button_for_column_modes = false, + mixer_button_id = "mixer", + use_mk1_colors = true, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-pro-mk2/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-pro-mk2/playtime.preset.luau new file mode 100644 index 0000000..5f6a5ea --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-pro-mk2/playtime.preset.luau @@ -0,0 +1,51 @@ +--- name: Launchpad Pro mk2 - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad Pro mk2 +--- description: | +--- This main preset turns the Launchpad Pro mk2 into a capable device for controlling Playtime. +--- +--- The following features are not yet supported: Note, Device, User, Volume, Pan, Sends, Record, +--- Record Quantise. +--- used_schemes: [novation/launchpad-pro-mk2/live] +--- required_features: [playtime] + +--!strict + +local commons = require("novation/launchpad-lib/playtime-commons") + +return commons.create_compartment { + stop_column_if_slot_empty = true, + has_dedicated_column_action_buttons = false, + has_shift_button = true, + has_delete_button = true, + has_quantize_button = true, + has_fixed_length_button = false, + has_duplicate_button = true, + has_play_button = false, + has_record_button = true, + + stop_clip_button_id = "stop-clip", + stop_clip_needs_mixer = false, + mute_button_id = "mute", + mute_needs_mixer = false, + solo_button_id = "solo", + solo_needs_mixer = false, + record_arm_button_id = "record-arm", + record_arm_needs_mixer = false, + track_select_button_id = "track-select", + + undo_button_id = "undo", + undo_needs_shift = false, + redo_button_id = "undo", + redo_needs_shift = true, + click_button_id = "click", + click_needs_shift = false, + double_button_id = "double", + double_needs_shift = false, + tap_button_id = nil, + tap_needs_shift = false, + use_last_row_play_button_for_column_modes = false, + mixer_button_id = nil, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-pro-mk3/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-pro-mk3/playtime.preset.luau new file mode 100644 index 0000000..fe0701a --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-pro-mk3/playtime.preset.luau @@ -0,0 +1,54 @@ +--- name: Launchpad Pro mk3 - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad Pro mk3 +--- description: | +--- This main preset turns the Launchpad Pro mk3 into a capable device for controlling Playtime. +--- +--- The following features are not yet supported: Note, Chord, Custom, Sequencer, Projects, Volume, Pan, Sends, Device, +--- Swing, Tempo, Capture MIDI, Patterns, Steps, Pattern Settings, Velocity, Probability, +--- Mutation, Micro Step, Print to Clip. +--- used_schemes: [novation/launchpad-pro-mk3/live] +--- required_features: [playtime] + +--!strict + +local commons = require("novation/launchpad-lib/playtime-commons") + +return commons.create_compartment { + stop_column_if_slot_empty = true, + has_dedicated_column_action_buttons = true, + + has_shift_button = true, + has_delete_button = true, + has_quantize_button = true, + has_fixed_length_button = true, + has_duplicate_button = true, + has_play_button = true, + has_record_button = true, + + stop_clip_button_id = "stop-clip", + stop_clip_needs_mixer = false, + mute_button_id = "mute", + mute_needs_mixer = false, + solo_button_id = "solo", + solo_needs_mixer = false, + record_arm_button_id = "record-arm", + record_arm_needs_mixer = false, + track_select_button_id = "track-select", + + undo_button_id = "record-arm", + undo_needs_shift = true, + redo_button_id = "mute", + redo_needs_shift = true, + click_button_id = "solo", + click_needs_shift = true, + double_button_id = "duplicate", + double_needs_shift = true, + tap_button_id = "sends", + tap_needs_shift = true, + use_last_row_play_button_for_column_modes = false, + mixer_button_id = nil, + use_mk1_colors = false, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-x/playtime.preset.luau b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-x/playtime.preset.luau new file mode 100644 index 0000000..07511e7 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/novation/launchpad-x/playtime.preset.luau @@ -0,0 +1,51 @@ +--- name: Launchpad X - Playtime +--- realearn_version: 2.16.0-pre.8 +--- author: helgoboss +--- device_manufacturer: Novation +--- device_name: Launchpad X +--- description: | +--- This main preset turns the Launchpad X into a capable device for controlling Playtime. +--- +--- The following features are not yet supported: Note, Custom, Volume, Pan, Send A, Send B, Capture MIDI +--- used_schemes: [novation/launchpad-x/live] +--- required_features: [playtime] + +--!strict + +local commons = require("novation/launchpad-lib/playtime-commons") + +return commons.create_compartment { + stop_column_if_slot_empty = true, + has_dedicated_column_action_buttons = false, + has_shift_button = false, + has_delete_button = false, + has_quantize_button = false, + has_fixed_length_button = false, + has_duplicate_button = false, + has_play_button = false, + has_record_button = true, + + stop_clip_button_id = "row5/play", + stop_clip_needs_mixer = true, + mute_button_id = "row6/play", + mute_needs_mixer = true, + solo_button_id = "row7/play", + solo_needs_mixer = true, + record_arm_button_id = "row8/play", + record_arm_needs_mixer = true, + track_select_button_id = nil, + + undo_button_id = nil, + undo_needs_shift = false, + redo_button_id = nil, + redo_needs_shift = false, + click_button_id = nil, + click_needs_shift = false, + double_button_id = nil, + double_needs_shift = false, + tap_button_id = nil, + tap_needs_shift = false, + use_last_row_play_button_for_column_modes = false, + mixer_button_id = "session", + use_mk1_colors = false, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/partial.luau b/plugin-reaper-realearn/resources/main-presets/factory/partial.luau new file mode 100644 index 0000000..983d53d --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/partial.luau @@ -0,0 +1,51 @@ +--!strict + +type ArbitraryTable = { [any]: any } + +--- Returns a new table that is the result of merging t2 into t1. +--- +--- Values in t2 have precedence. +--- +--- The result will be mergeable as well. This is good for "modifier chaining". +--- +--- Typing not ideal because of https://github.com/luau-lang/luau/issues/392#issuecomment-1050344334 +function merged(t1: A, t2: B): A & B + if type(t1) ~= "table" or type(t2) ~= "table" then + return t1 :: any + end + local result = table.clone(t1) + for key, new_value in pairs(t2 :: ArbitraryTable) do + local old_value = result[key] + if old_value and type(old_value) == "table" and type(new_value) == "table" then + -- Merge table value as well + result[key] = merged(old_value, new_value) :: any + else + -- Simple use new value + result[key] = new_value + end + end + return result :: any +end + +type PartialImpl = { + new: (part: ArbitraryTable) -> Partial, + __index: PartialImpl, + __add: (self: Partial, part: Partial) -> Partial, +} + +export type Partial = typeof(setmetatable({}, {} :: PartialImpl)) + +local Partial: PartialImpl = {} :: PartialImpl +Partial.__index = Partial + +function Partial.new(part) + return setmetatable(part, Partial) +end + +function Partial:__add(part) + return Partial.new(merged(self, part) :: any) +end + +return { + Partial = Partial, +} \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/playtime.luau b/plugin-reaper-realearn/resources/main-presets/factory/playtime.luau new file mode 120000 index 0000000..7bb93c2 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/playtime.luau @@ -0,0 +1 @@ +../../api/luau/playtime.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/playtime_util.luau b/plugin-reaper-realearn/resources/main-presets/factory/playtime_util.luau new file mode 100644 index 0000000..d0c5ae0 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/playtime_util.luau @@ -0,0 +1,59 @@ +--!strict + +local realearn = require("realearn") +local util = require("util") + +local playtime_util = {} + +--- A partial mapping that enables turbo mode for buttons. +function playtime_util.turbo() + return util.partial_mapping { + glue = { + fire_mode = realearn.FireMode.AfterTimeoutKeepFiring { + timeout = 0, + rate = 100, + }, + }, + } +end + +local function scroll(axis: realearn.Axis, amount: number) + local abs_amount = math.abs(amount) + return util.partial_mapping { + glue = { + absolute_mode = "IncrementalButton", + step_factor_interval = { abs_amount, abs_amount }, + reverse = amount < 0, + --- This makes the LED switch off when no scrolling is possible in the given direction + feedback = realearn.Feedback.Numeric { + transformation = "x = y < 1", + }, + }, + target = realearn.Target.PlaytimeControlUnitScroll { + axis = axis, + }, + } +end + +function playtime_util.scroll_horizontally(amount: number) + return scroll("X", amount) +end + +function playtime_util.scroll_vertically(amount: number) + return scroll("Y", amount) +end + +function playtime_util.slot_state_text_feedback() + return util.partial_mapping { + glue = { + feedback = realearn.Feedback.Text { + text_expression = "{{ target.slot_state.id }}", + color = realearn.VirtualColor { + prop = "target.slot.color", + }, + }, + }, + } +end + +return playtime_util diff --git a/plugin-reaper-realearn/resources/main-presets/factory/preset_runtime.luau b/plugin-reaper-realearn/resources/main-presets/factory/preset_runtime.luau new file mode 120000 index 0000000..24f5fd6 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/preset_runtime.luau @@ -0,0 +1 @@ +../../api/luau/preset_runtime.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/realearn.luau b/plugin-reaper-realearn/resources/main-presets/factory/realearn.luau new file mode 120000 index 0000000..fb4f211 --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/realearn.luau @@ -0,0 +1 @@ +../../api/luau/realearn.luau \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/main-presets/factory/util.luau b/plugin-reaper-realearn/resources/main-presets/factory/util.luau new file mode 100644 index 0000000..96bb2de --- /dev/null +++ b/plugin-reaper-realearn/resources/main-presets/factory/util.luau @@ -0,0 +1,56 @@ +--!strict + +local realearn = require("realearn") +local partial = require("partial") + +local util = {} + +--- Creates a partial mapping. +--- +--- A partial mapping can be conveniently mixed with other partial mappings by using the + operator. +--- This enables a very intuitive but still flexible way of writing mappings. +--- +--- Ideally, we would return Partial & Mapping but Luau has still issues with that. In particular, it +--- can't resolve the + operator when used in an intersection. +function util.partial_mapping(m: realearn.Mapping): partial.Partial + return partial.Partial.new(m) +end + +--- Returns the values of the given key-value table as array. +function util.to_array(t: { [string]: V }): { V } + local array = {} + for _, v in t do + table.insert(array, v) + end + return array +end + +--- Returns the values of the given key-value table as array, sorted by the values' +--- `index` key. +function util.sorted_by_index(t: { [string]: V }): { V } + local sorted = util.to_array(t) + local compare_index = function(left: any, right: any): boolean + return left.index < right.index + end + table.sort(sorted, compare_index) + return sorted +end + +--- Takes a key-value table and adds a new attribute `id` to each value that +--- corresponds to the key. +function util.set_keys_as_ids(t: { [string]: { [string]: any } }) + for key, value in pairs(t) do + value.id = key + end +end + +--- Puts each `label` property value of the given array of tables into a new array. +function util.extract_labels(array: { { [string]: any } }): { string } + local labels = {} + for _, element in ipairs(array) do + table.insert(labels, element.label) + end + return labels +end + +return util diff --git a/plugin-reaper-realearn/resources/sounds/click-high.mp3 b/plugin-reaper-realearn/resources/sounds/click-high.mp3 new file mode 100644 index 0000000..5883975 Binary files /dev/null and b/plugin-reaper-realearn/resources/sounds/click-high.mp3 differ diff --git a/plugin-reaper-realearn/resources/template-projects/clip-engine-basic/.gitignore b/plugin-reaper-realearn/resources/template-projects/clip-engine-basic/.gitignore new file mode 100644 index 0000000..697e56f --- /dev/null +++ b/plugin-reaper-realearn/resources/template-projects/clip-engine-basic/.gitignore @@ -0,0 +1 @@ +*.wav \ No newline at end of file diff --git a/plugin-reaper-realearn/resources/template-projects/clip-engine-basic/clip-engine-basic.RPP b/plugin-reaper-realearn/resources/template-projects/clip-engine-basic/clip-engine-basic.RPP new file mode 100644 index 0000000..e2559ad --- /dev/null +++ b/plugin-reaper-realearn/resources/template-projects/clip-engine-basic/clip-engine-basic.RPP @@ -0,0 +1,1055 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 22 + ZOOM 4.24777993786314 0 0 + VZOOMEX 6 389 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.506024 0.5 0 -1 0 0 0 0 -1 -1 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAK3gAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNi4wLXByZS4xIiwiaWQiOiJjbGlwLWVuZ2luZSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6IkFsd2F5cyIsInNlbmRG + ZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwiY29udHJvbGxlckN1c3RvbURhdGEiOnsi + Y29tcGFuaW9uIjp7ImNvbnRyb2xzIjpbeyJoZWlnaHQiOjMyLCJpZCI6IjBhYTU1OTQ5LWVhMTQtNDA0ZS1iM2FmLTNjOGE4NWVkNTBjNyIsImxhYmVsT25lIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9t + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3cxL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MjU2LCJ5 + IjowfSx7ImhlaWdodCI6MzIsImlkIjoiOWIzNGJlYTYtOWVlYi00YTlhLTg1MGEtOGE4Mjk0Yzc3MmRhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + Y2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQi + OnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzEvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjB9LHsiaGVpZ2h0IjozMiwi + aWQiOiJiZmIyZGI5MS1mYjI4LTRiMzQtOWJjNC02YjI1YzE4YzQ1MzQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpb + ImNvbDEvcm93MS9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjAsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiI4MTdiODRhOC02NzVmLTRl + ZGMtODIyOS02N2ZjM2U5YTM0MDAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3 + byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93MS9wYWQiXSwic2hh + cGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6IjFkMDk0ODUyLWY1YjktNGFjYS05OTcyLTcxYmY3YjY4M2Iy + MyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNS9yb3cxL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndp + ZHRoIjo5NiwieCI6NTEyLCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiN2EyZmNiMzItMWUyNC00ODE3LWEyMGMtNzNkYmJkZDc4NmZjIiwibGFiZWxPbmUiOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20i + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w2L3JvdzEvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo2NDAsInki + OjB9LHsiaGVpZ2h0IjozMiwiaWQiOiI3YWMyYjRkMS0xNmI4LTQzY2QtOWViZS01NmJkMzc1YjgxM2YiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJj + ZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93MS9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwieSI6MH0seyJoZWlnaHQiOjMyLCJp + ZCI6IjQyODM2NDgwLTYwNjEtNDYyNS04MWJmLTg3ZGJhOGQ0NWQzNSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsi + Y29sOC9yb3cxL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6ODk2LCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiZmVlNTMyMWQtZDY3NS00 + N2VjLWI0YjYtZDNlNDlmMDRlYmRhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxU + d28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wxL3JvdzIvcGFkIl0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjowLCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjU3NjA4MTZkLWJiOTItNDU3ZS04MzFjLTZiYTU5NmQzM2E5 + MSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndp + ZHRoIjo5NiwieCI6MTI4LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6ImNiNTVhMTZmLWY1ODktNGVlNS1hOTc3LWExOWM3YmE1ZDJiMiIsImxhYmVsT25lIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9t + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MjU2LCJ5 + Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjNiNzU1ZTIxLTU5YzQtNDc2Ni05YzE0LTJkMjA2NDFiN2JmNCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibWFwcGluZ3MiOlsiY29sNC9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6Mzg0LCJ5Ijo2NH0seyJoZWlnaHQiOjMy + LCJpZCI6ImI2YzE0MTczLTQ4NmYtNDlmNy04N2U3LWMwZmU1MmJmOTg5OCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3Mi + OlsiY29sNS9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NTEyLCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjE4YjRkZmQ2LTAz + MGUtNGRkYy05YTJhLWM4OGU3MjcwZTk1MCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxh + YmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9yb3cyL3BhZCJd + LCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NjQwLCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6ImJhYTgxM2U4LTdkNGItNGVhNy04NjEwLTc0ZjEy + NGQzZGI5NyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjow + LCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNy9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3Rhbmds + ZSIsIndpZHRoIjo5NiwieCI6NzY4LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjcyZWRhY2U0LTg5MGYtNDQ2YS1iNGU4LThkODc1YzE4NDFiYyIsImxhYmVsT25l + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93 + Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sOC9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6 + ODk2LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6Ijk5OGM5MzcxLWZmMjQtNDk1NC05NDVkLTdjNjQxOTE2NTAwZSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3czL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MCwieSI6MTI4fSx7ImhlaWdo + dCI6MzIsImlkIjoiYzJmZGM1NzEtMzQ3Yy00OWRhLTlkOWUtNmNhYTRlOGQ5ZmNiIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6 + ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBw + aW5ncyI6WyJjb2wyL3JvdzMvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjEyOH0seyJoZWlnaHQiOjMyLCJpZCI6IjZiMjVm + MGUyLTRkNzktNDBhYi1iZTQ2LTU3ZDQ5Y2Q4ZDU3OSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3cz + L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MjU2LCJ5IjoxMjh9LHsiaGVpZ2h0IjozMiwiaWQiOiJkYjAxNGQ3Ni00NDMzLTQ2M2EtYWM2 + Zi1kMzhkZGI3YzM2NGYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93My9wYWQiXSwic2hhcGUiOiJy + ZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6MTI4fSx7ImhlaWdodCI6MzIsImlkIjoiOGUzOWJjMjMtZDUxZC00NWMyLWFkOTgtNmE2MDllMTc1NTg0Iiwi + bGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9u + IjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w1L3JvdzMvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgi + Ojk2LCJ4Ijo1MTIsInkiOjEyOH0seyJoZWlnaHQiOjMyLCJpZCI6IjI0Y2YxZGJkLWMwMWUtNGEwMC1iYWFjLTI4MmE1ZTQyMzJhZiIsImxhYmVsT25lIjp7ImFuZ2xl + IjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9yb3czL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NjQwLCJ5Ijox + Mjh9LHsiaGVpZ2h0IjozMiwiaWQiOiI1NjhjMDE3Ny00NTI1LTRlZWUtYmZiOS1lMjFhZTdjMjAyZTAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJj + ZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93My9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwieSI6MTI4fSx7ImhlaWdodCI6MzIs + ImlkIjoiMmVhZDQ3ZDEtZWU3NS00MjlmLTljZDQtM2I1ZmU5ODljMDU2IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6 + WyJjb2w4L3JvdzMvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo4OTYsInkiOjEyOH0seyJoZWlnaHQiOjMyLCJpZCI6Ijk0MWQ5OTZkLTg4 + MmMtNDRmMy1hYmM0LTNmNjYxN2NjMDRmOSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxh + YmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3c0L3BhZCJd + LCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MCwieSI6MTkyfSx7ImhlaWdodCI6MzIsImlkIjoiMWNhOWIwZjctY2RlZC00ZjMxLWFkZWMtM2QzY2Zm + NmM5MWNhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzQvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xl + Iiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjE5Mn0seyJoZWlnaHQiOjMyLCJpZCI6ImE3ZDcxYjUwLTgxYjgtNDI2MS1hZDhiLTdjMzIyOWMwOWQ3OCIsImxhYmVsT25l + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93 + Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3c0L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6 + MjU2LCJ5IjoxOTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI0YTEyZmJiZC02YmExLTQzZjItYmY1Yy05NjUwMDc2YmYzNzYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9z + aXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93NC9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6MTkyfSx7Imhl + aWdodCI6MzIsImlkIjoiMzRiOTg4YzgtMjVlNC00OTM2LTgwNDItNzE0MGM3YjI4ZGFkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJt + YXBwaW5ncyI6WyJjb2w1L3JvdzQvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo1MTIsInkiOjE5Mn0seyJoZWlnaHQiOjMyLCJpZCI6ImJi + ZDRjZWJhLTYzZDItNDQxZi04Nzk0LTAyNjNhZjIyM2U1YyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9y + b3c0L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NjQwLCJ5IjoxOTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI1N2E0OTI5My04MDZiLTRiZmYt + OGRmYy1lODEzMGFhOGNjMjMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6 + eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93NC9wYWQiXSwic2hhcGUi + OiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwieSI6MTkyfSx7ImhlaWdodCI6MzIsImlkIjoiYmNlZmExYWUtNjBmZS00ZTUyLWIzMTUtNjQ1YTdiNzU5MzFl + IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w4L3JvdzQvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lk + dGgiOjk2LCJ4Ijo4OTYsInkiOjE5Mn0seyJoZWlnaHQiOjMyLCJpZCI6ImExZmM1ZmE2LWJmZWQtNGIwYS04MTMyLWFlZDZhNGIxNGJiYyIsImxhYmVsT25lIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9t + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3c1L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MCwieSI6 + MjU2fSx7ImhlaWdodCI6MzIsImlkIjoiNzQ1MzhmOGMtZDI3Mi00YWM0LWEwZTItZmNjNjQ1MjMxMWI1IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + Y2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQi + OnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzUvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjI1Nn0seyJoZWlnaHQiOjMy + LCJpZCI6IjkyYmRjZTcwLTNhMTctNGQ1My1hZTkzLWVjNmZkZmE0YWNiMSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3Mi + OlsiY29sMy9yb3c1L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MjU2LCJ5IjoyNTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiIwNWFjMzQwNy05 + MjA5LTQ3MWUtOWIwZS01MDI5Mzk2ZGUxY2MiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93NS9wYWQi + XSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6MjU2fSx7ImhlaWdodCI6MzIsImlkIjoiYjg5OGIxMjctMTVlMy00YzU3LTg1MjgtMTZm + NWVhN2Y2YWY2IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w1L3JvdzUvcGFkIl0sInNoYXBlIjoicmVjdGFu + Z2xlIiwid2lkdGgiOjk2LCJ4Ijo1MTIsInkiOjI1Nn0seyJoZWlnaHQiOjMyLCJpZCI6ImQ5MWIzYzIyLWZmNWQtNGZkMC1iOGMxLWRhY2Q4OGIxNzA2NSIsImxhYmVs + T25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJl + bG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9yb3c1L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5Niwi + eCI6NjQwLCJ5IjoyNTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5OWQ4MjU3MC01OWZmLTQxMDktYWFiYi0wMTkyNGJkZjUwNjciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93NS9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwieSI6MjU2fSx7 + ImhlaWdodCI6MzIsImlkIjoiMTYxZGRlZTktN2ZkNi00MjBkLWI5YmEtYTI3MTRmMTdjNzI0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVy + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJtYXBwaW5ncyI6WyJjb2w4L3JvdzUvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo4OTYsInkiOjI1Nn0seyJoZWlnaHQiOjMyLCJpZCI6 + ImEzOTMwMDlmLTY1NWYtNDQyNS05NTczLTgzNTFiMTUxZmQzYSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJj + b2wxL3N0b3AiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjMyLCJ5IjozMjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJmYjNhMTcyMy03YTY5LTQ1N2YtODcx + ZC01Mzg5YzI2MzdmNGQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9zdG9wIl0sInNoYXBlIjoiY2ly + Y2xlIiwid2lkdGgiOjM0LCJ4Ijo2NzIsInkiOjMyMH0seyJoZWlnaHQiOjUwLCJpZCI6IjQ0ZDA5YzkxLWE2MjUtNGIzMS04ZTFlLWU0M2Q0NDQ3MGFkYyIsImxhYmVs + T25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + YmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3N0b3AiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjE2 + MCwieSI6MzIwfSx7ImhlaWdodCI6NTAsImlkIjoiY2FkMmRmMGItMzE5Yy00YzQzLTg4ZGMtODgwNDJiMmQ2YWVkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvc3RvcCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozNCwieCI6Mjg4LCJ5IjozMjB9LHsiaGVpZ2h0Ijo1 + MCwiaWQiOiI3MGZiNGQzMC0zM2U4LTQ4MmItOTA4Zi03ZDg4NjZkNWZiZWMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGlu + Z3MiOlsiY29sNC9zdG9wIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjM0LCJ4Ijo0MTYsInkiOjMyMH0seyJoZWlnaHQiOjUwLCJpZCI6ImU5MmU1NGE0LTk2ZGQt + NGVkNS05NTgwLWUzYTU4N2VmODZiMCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFi + ZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w1L3N0b3AiXSwic2hh + cGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjU0NCwieSI6MzIwfSx7ImhlaWdodCI6NTAsImlkIjoiMWE5NWFkYzYtOGJiMy00ZjYwLThkZmQtNzY2NzU1MGIyMDJk + IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9z + aXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvc3RvcCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjoz + NCwieCI6ODAwLCJ5IjozMjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJlZTMwMjAyYy0wZWJkLTRhYmMtYmQzMC01YTk2MTQ4MGU1YjYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6 + MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sOC9zdG9wIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjM0LCJ4Ijo5MjgsInkiOjMyMH0seyJo + ZWlnaHQiOjUwLCJpZCI6IjBiZGVlZDE4LTQ2NDEtNDhiZC1iN2YxLTEwMjM2M2MyZDFmNSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9w + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJtYXBwaW5ncyI6WyJyb3cxL3BsYXkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiIzOWE4MWM2 + OC1kZDY0LTQ3ZjAtOWQyZi0yYzM2ZTAwNGIxZDEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsicm93Mi9wbGF5 + Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjM0LCJ4IjoxMDI0LCJ5Ijo2NH0seyJoZWlnaHQiOjUwLCJpZCI6IjY1YjA1NDBkLTcxNzMtNDhiOS05YjYyLWVhZjgz + M2M3M2UzZiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyb3czL3BsYXkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3 + aWR0aCI6MzQsIngiOjEwMjQsInkiOjEyOH0seyJoZWlnaHQiOjUwLCJpZCI6IjRjZWFmZTc1LTQ1MjEtNGY4Ny1hYWJkLWNkODRkODNlNDZiZiIsImxhYmVsT25lIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dC + b3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyb3c1L3BsYXkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInki + OjI1Nn0seyJoZWlnaHQiOjUwLCJpZCI6IjhhNTkwNjU5LTU2MzItNDIyNy04MzJjLTdiZGNiZjZjYWRkZCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyb3c0L3BsYXkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInkiOjE5Mn0seyJoZWlnaHQiOjUwLCJp + ZCI6ImZlNjczOTJlLTE1YWMtNDlkOC1iY2YyLTEyOWEzNjUyZWZmYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6 + WyJhYzQ5Y2Q4YS1jZDk4LTRhY2QtODRhMC0yNzYzNzJhYThkMDUiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInkiOjMyMH0seyJoZWlnaHQi + OjUwLCJpZCI6ImNhZmIwOGUwLWJhMDEtNGIzMS1hMTc1LTYyZTQxNThhNDJmNyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGlu + Z3MiOlsiazEiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6OTgsIngiOjExMjAsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJmMmIwMzFhZC0xODc5LTRhNzUtOTg0 + Yy1lOTA2ZjY5YmQ3MWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImsyIl0sInNoYXBlIjoiY2lyY2xlIiwid2lk + dGgiOjk4LCJ4IjoxMjgwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiNzU2NjdhMWEtNjBjOC00MGUyLTk1ZmYtZjFmOTBjYzM3ZTkyIiwibGFiZWxPbmUiOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20i + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJrMyJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo5OCwieCI6MTQ0MCwieSI6MH0seyJoZWlnaHQi + OjUwLCJpZCI6ImVkYzFmNzk5LWNiMGEtNGY2MC1iZjhjLWI2NjRhYjU3ZGM1YSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGlu + Z3MiOlsiazQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6OTgsIngiOjE2MDAsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiI1NTczMzM5Zi0xNTI0LTRiYTMtOWVi + OC0yMzg3MDJlNDM3YWQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIms1Il0sInNoYXBlIjoiY2lyY2xlIiwid2lk + dGgiOjk4LCJ4IjoxMTIwLCJ5IjoxMjh9LHsiaGVpZ2h0Ijo1MCwiaWQiOiI5YTU5NGUyZS1kMmI4LTRmNDctYmRkYy02ODcyODA5YTQwODkiLCJsYWJlbE9uZSI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRv + bSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIms2Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjk4LCJ4IjoxMjgwLCJ5IjoxMjh9LHsiaGVp + Z2h0Ijo1MCwiaWQiOiI4YjUwYTc1Yy1jMmNkLTRmODAtOWMzMC02ZDRiYjkwNzAwMzUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJz + aXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1h + cHBpbmdzIjpbIms3Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjk4LCJ4IjoxNDQwLCJ5IjoxMjh9LHsiaGVpZ2h0Ijo1MCwiaWQiOiI5MzAxYzhlMC0xMjEyLTQx + NGYtYTM0ZS03NmFmOTcyZDY4ZDAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3 + byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIms4Il0sInNoYXBlIjoiY2lyY2xl + Iiwid2lkdGgiOjk4LCJ4IjoxNjAwLCJ5IjoxMjh9LHsiaGVpZ2h0IjozNCwiaWQiOiI3OTM1ZTRhMC01MTJmLTRlOTYtODc0Ni1iMmM4ZjU0MmMzM2QiLCJsYWJlbE9u + ZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxv + d0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIjIyMDUwMTgyLTQ4MGUtNDI2Ny1iMjAzLTlhZDY0MWE3MmI0NCJdLCJzaGFwZSI6InJl + Y3RhbmdsZSIsIndpZHRoIjo5OCwieCI6MTEyMCwieSI6MjU2fSx7ImhlaWdodCI6MzQsImlkIjoiMzk1MDZhZjMtYjE3ZS00NWJiLWExNDgtNTFmZjM5YjRlZWI1Iiwi + bGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9u + IjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiMDlkNjE2OS1hN2RmLTRkYmUtYjc2Yy03M2QzMGM4NjI1YzMiXSwic2hh + cGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTgsIngiOjE0NzIsInkiOjI1Nn0seyJoZWlnaHQiOjM0LCJpZCI6IjM1YWE1ZjQzLTZiMzYtNDZhZC1iMTA0LWZiZTg0YzM3 + Yjk2YiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiNmM0NzQ1YWMtMzliYi00ZWQ3LWIyOTAtMmNjNWFlZjQ5YmJi + Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk4LCJ4IjoxNjAwLCJ5IjoyNTZ9LHsiaGVpZ2h0IjozNCwiaWQiOiJkNzJiNDdjYy1mMGI0LTQ2YzYtOTUxMC1m + ZDk2N2Y4MTU5NjUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIjgzOGNjOWU2LTU4NTctNGRkMi05NTJiLTMzOWYz + Zjg4NmYzZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5OCwieCI6MTEyMCwieSI6MzIwfV0sImdyaWREaXZpc2lvbkNvdW50IjoyLCJncmlkU2l6ZSI6MzJ9 + fSwicGFyYW1ldGVycyI6eyI3Ijp7Im5hbWUiOiJTdXN0YWluIG1vZGlmaWVyIn0sIjIiOnsibmFtZSI6IlNoaWZ0IG1vZGlmaWVyIn0sIjYiOnsibmFtZSI6IlNlbmQi + LCJ2YWx1ZUNvdW50IjoyfSwiMCI6eyJuYW1lIjoiQ29sdW1uIG9mZnNldCIsInZhbHVlQ291bnQiOjEwMDAwfSwiMyI6eyJuYW1lIjoiQ29sdW1uIG1vZGUiLCJ2YWx1 + ZUNvdW50IjoxMDAsInZhbHVlTGFiZWxzIjpbIlN0b3AgY2xpcCIsIlNvbG8iLCJSZWNvcmQgYXJtIiwiTXV0ZSIsIlRyYWNrIHNlbGVjdCJdfSwiMSI6eyJuYW1lIjoi + Um93IG9mZnNldCIsInZhbHVlQ291bnQiOjEwMDAwfSwiNCI6eyJuYW1lIjoiS25vYiBtb2RlIiwidmFsdWVDb3VudCI6MTAwLCJ2YWx1ZUxhYmVscyI6WyJWb2x1bWUi + LCJQYW4iLCJTZW5kcyIsIkRldmljZSJdfX0sImNsaXBNYXRyaXgiOnsiY29sdW1ucyI6W3siY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1 + c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiRTBDNzY5OTAtMkIyOC05RjQ1LUE1NUMtNjg0RjRDMjA3NTgyIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9y + ZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W3sicm93IjowLCJjbGlwcyI6W3siaWQiOiIwMUgyQUJHOERUM0FZ + VlZHMDFQRFExQlQ2ViIsIm5hbWUiOiJDb2x1bW4gMSIsInNvdXJjZSI6eyJraW5kIjoiTWlkaUNodW5rIiwiY2h1bmsiOiJIQVNEQVRBIDEgOTYwIFFOXG5DQ0lOVEVS + UCAzMlxuUE9PTEVERVZUUyB7NTE2N0RCNTgtRDc2MC05OTQ0LUIzQUEtRDNGQTIzMDcwM0EwfVxuZSAwIDkxIDMwIDMxXG5FIDEgYjAgN2IgMDBcbmUgMjM5IDgxIDMw + IDAwIC05MFxuZSAyNDAgOTEgMzcgMjdcbmUgMjQwIDgxIDM3IDAwIC05MFxuZSAyNDAgOTEgM2MgM2RcbmUgMjQwIDgxIDNjIDAwIC05MFxuZSAyNDAgOTEgNDMgMzNc + bmUgMjQwIDgxIDQzIDAwIC05MFxuZSAyNDAgOTEgNDggM2RcbmUgMjQwIDgxIDQ4IDAwIC05MFxuZSAyNDAgOTEgNDMgMzFcbmUgMjQwIDgxIDQzIDAwIC05MFxuZSAy + NDAgOTEgM2MgNDNcbmUgMjQwIDgxIDNjIDAwIC05MFxuZSAyNDAgOTEgMzcgMzlcbmUgMjQwIDgxIDM3IDAwIC0xMjBcbkUgMjQwIGIwIDdiIDAwXG5DQ0lOVEVSUCAz + MlxuR1VJRCB7Mjk1RjE0N0UtMDlCMC02RjQyLTkzRDQtNzI3NTAyRjFFOTZBfVxuSUdOVEVNUE8gMSAxMjAgNCA0XG5TUkNDT0xPUiAzMDM3XG4ifSwiYWN0aXZlX3Nv + dXJjZSI6Ik5vcm1hbCIsInRpbWVfYmFzZSI6eyJraW5kIjoiQmVhdCIsInRpbWVfc2lnbmF0dXJlIjp7Im51bWVyYXRvciI6NCwiZGVub21pbmF0b3IiOjR9LCJkb3du + YmVhdCI6MC4wfSwibG9vcGVkIjp0cnVlLCJ2b2x1bWUiOjAuMCwiY29sb3IiOnsia2luZCI6IlBsYXlUcmFja0NvbG9yIn0sInNlY3Rpb24iOnsic3RhcnRfcG9zIjow + LjB9LCJhdWRpb19zZXR0aW5ncyI6eyJhcHBseV9zb3VyY2VfZmFkZXMiOnRydWV9LCJtaWRpX3NldHRpbmdzIjp7InNvdXJjZV9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0 + Ijp7Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxz + ZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpm + YWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwic2VjdGlvbl9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7 + Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwi + ZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxz + ZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwibG9vcF9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25v + dGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVy + X3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVz + ZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwiaW50ZXJhY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9u + b3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBl + cl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJl + c2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX19fV19LHsicm93IjozLCJjbGlwcyI6W3siaWQiOiIwMUgyQUJHOERWWU0y + MTM5VDhWUzVZR0VXNyIsIm5hbWUiOiJDb2x1bW4gMSIsInNvdXJjZSI6eyJraW5kIjoiTWlkaUNodW5rIiwiY2h1bmsiOiJIQVNEQVRBIDEgOTYwIFFOXG5DQ0lOVEVS + UCAzMlxuUE9PTEVERVZUUyB7RDZFNTEyN0YtOEU4Qi04NzQ4LUI3NzUtRjE2Q0M1MUQzMTZCfVxuZSAwIDkxIDI5IDUzXG5FIDEgYjAgN2IgMDBcbkUgNzY2OSA5MSAy + ZCA1MFxuZSAzMCA4MSAyOSAwMFxuRSA3NjA5IDgxIDJkIDAwXG5lIDUwIDkxIDI5IDRkXG5FIDU4MTcgOTEgMmIgM2RcbmUgMTg0IDgxIDI5IDAwXG5FIDUyMyA5MSAy + OSA0N1xuRSAxMTIgODEgMmIgMDBcbkUgNTUzIDkxIDI4IDQ5XG5FIDEyMyA4MSAyOSAwMFxuRSA3NzkzIDgxIDI4IDAwXG5FIDI1NiBiMCA3YiAwMFxuQ0NJTlRFUlAg + MzJcbkdVSUQgezQ3OTJCRjJDLTJDMzUtMjE0RC1BMTU4LTA0NzAwNUUzNUNDQX1cbklHTlRFTVBPIDEgMTIwIDQgNFxuU1JDQ09MT1IgMzAzOFxuIn0sImFjdGl2ZV9z + b3VyY2UiOiJOb3JtYWwiLCJ0aW1lX2Jhc2UiOnsia2luZCI6IkJlYXQiLCJ0aW1lX3NpZ25hdHVyZSI6eyJudW1lcmF0b3IiOjQsImRlbm9taW5hdG9yIjo0fSwiZG93 + bmJlYXQiOjAuMH0sImxvb3BlZCI6dHJ1ZSwidm9sdW1lIjowLjAsImNvbG9yIjp7ImtpbmQiOiJQbGF5VHJhY2tDb2xvciJ9LCJzZWN0aW9uIjp7InN0YXJ0X3BvcyI6 + MC4wfSwiYXVkaW9fc2V0dGluZ3MiOnsiYXBwbHlfc291cmNlX2ZhZGVzIjp0cnVlfSwibWlkaV9zZXR0aW5ncyI6eyJzb3VyY2VfcmVzZXRfc2V0dGluZ3MiOnsibGVm + dCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFs + c2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6 + ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sInNlY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6 + eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2Us + ImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFs + c2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImxvb3BfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9u + b3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBl + cl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJl + c2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImludGVyYWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQiOnsib25f + bm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1w + ZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJy + ZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19fX1dfV19LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2lu + ZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiMkVDNUJEMTUtRjREQy03NDQ1LTkwNDktMDk3QzgwRUY5QkRBIiwiYXVkaW9fc2V0dGluZ3MiOnt9 + fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W3sicm93IjowLCJjbGlwcyI6W3siaWQiOiIwMUgy + QUJHOERWRjJNODZIUEdQUFg2WVdTUSIsIm5hbWUiOiJDb2x1bW4gMiIsInNvdXJjZSI6eyJraW5kIjoiTWlkaUNodW5rIiwiY2h1bmsiOiJIQVNEQVRBIDEgOTYwIFFO + XG5DQ0lOVEVSUCAzMlxuUE9PTEVERVZUUyB7NTBFRDE4MzQtOTE4OS00NjRBLTlBMTgtQTk2NzRFN0ExMTJGfVxuRSAwIDkxIDE4IDQzXG5FIDEgYjAgN2IgMDBcbkUg + MjM5IDgxIDE4IDAwIC04NFxuRSAwIDkxIDI0IDMzIC0xN1xuRSAyNDAgODEgMjQgMDAgLTEyXG5FIDQ4MCA5MSAyNCAzYiAtMlxuRSAyNDAgODEgMjQgMDAgMjZcbkUg + MjQwIDkxIDE4IDM3IDlcbkUgMjQwIDgxIDE4IDAwIC03NVxuRSAwIDkxIDI0IDNmIDE0XG5FIDI0MCA4MSAyNCAwMCA2NFxuRSA0ODAgOTEgMjQgNGQgN1xuRSAyNDAg + ODEgMjQgMDAgNzlcbkUgMjQwIDkxIDE4IDRhIC0yN1xuRSAyNDAgODEgMTggMDAgLTExMVxuRSAwIDkxIDI0IDNkIC0yMlxuRSAyNDAgODEgMjQgMDAgLTYxXG5FIDAg + OTEgMzAgMzggLTYxXG5FIDI0MCA4MSAzMCAwMCAtNzhcbmUgMCA5MSAyNCAyZiAtNTZcbmUgMjQwIDgxIDI0IDAwIC0xMThcbkUgMCBiMCA3YiAwMFxuQ0NJTlRFUlAg + MzJcbkdVSUQgezFCNDk1Q0UwLTFBQjItNzc0OC05ODdDLUYzNkQyRkY0QjYzQX1cbklHTlRFTVBPIDEgMTIwIDQgNFxuU1JDQ09MT1IgMzAzOVxuIn0sImFjdGl2ZV9z + b3VyY2UiOiJOb3JtYWwiLCJ0aW1lX2Jhc2UiOnsia2luZCI6IkJlYXQiLCJ0aW1lX3NpZ25hdHVyZSI6eyJudW1lcmF0b3IiOjQsImRlbm9taW5hdG9yIjo0fSwiZG93 + bmJlYXQiOjAuMH0sImxvb3BlZCI6dHJ1ZSwidm9sdW1lIjowLjAsImNvbG9yIjp7ImtpbmQiOiJQbGF5VHJhY2tDb2xvciJ9LCJzZWN0aW9uIjp7InN0YXJ0X3BvcyI6 + MC4wfSwiYXVkaW9fc2V0dGluZ3MiOnsiYXBwbHlfc291cmNlX2ZhZGVzIjp0cnVlfSwibWlkaV9zZXR0aW5ncyI6eyJzb3VyY2VfcmVzZXRfc2V0dGluZ3MiOnsibGVm + dCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFs + c2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6 + ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sInNlY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6 + eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2Us + ImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFs + c2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImxvb3BfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9u + b3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBl + cl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJl + c2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImludGVyYWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQiOnsib25f + bm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1w + ZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJy + ZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19fX1dfSx7InJvdyI6MSwiY2xpcHMiOlt7ImlkIjoiMDFIMkFCRzhEVkhC + N0oyMTc1VlE1U1ZERzkiLCJuYW1lIjoiQ29sdW1uIDIiLCJzb3VyY2UiOnsia2luZCI6Ik1pZGlDaHVuayIsImNodW5rIjoiSEFTREFUQSAxIDk2MCBRTlxuQ0NJTlRF + UlAgMzJcblBPT0xFREVWVFMge0FCMjQ5MTA4LUFCMzEtRkM0NS05OEEyLTdCODhFMjY2NzE0Qn1cbkUgMCA5MSAxOCA3ZlxuRSAxIGIwIDdiIDAwXG5FIDIzOSA4MSAx + OCAwMFxuRSAyNDAgOTEgMTggMzZcbkUgMjQwIDgxIDE4IDAwXG5FIDI0MCA5MSAxOCAyM1xuRSAyNDAgODEgMTggMDBcbkUgMjQwIDkxIDE4IDEzXG5FIDI0MCA4MSAx + OCAwMFxuRSAyNDAgOTEgMTggN2ZcbkUgMjQwIDgxIDE4IDAwXG5FIDI0MCA5MSAxOCAzNlxuRSAyNDAgODEgMTggMDBcbkUgMjQwIDkxIDE4IDIzXG5FIDI0MCA4MSAx + OCAwMFxuRSAyNDAgOTEgMTggMTNcbkUgMjQwIDgxIDE4IDAwXG5FIDI0MCBiMCA3YiAwMFxuQ0NJTlRFUlAgMzJcbkdVSUQgezI3QTgzMzY2LUQyMkMtMUY0RC04OEQw + LTI3OUQ5N0Y2NTlCQ31cbklHTlRFTVBPIDEgMTIwIDQgNFxuU1JDQ09MT1IgMzA0MFxuIn0sImFjdGl2ZV9zb3VyY2UiOiJOb3JtYWwiLCJ0aW1lX2Jhc2UiOnsia2lu + ZCI6IkJlYXQiLCJ0aW1lX3NpZ25hdHVyZSI6eyJudW1lcmF0b3IiOjQsImRlbm9taW5hdG9yIjo0fSwiZG93bmJlYXQiOjAuMH0sImxvb3BlZCI6dHJ1ZSwidm9sdW1l + IjowLjAsImNvbG9yIjp7ImtpbmQiOiJQbGF5VHJhY2tDb2xvciJ9LCJzZWN0aW9uIjp7InN0YXJ0X3BvcyI6MC4wfSwiYXVkaW9fc2V0dGluZ3MiOnsiYXBwbHlfc291 + cmNlX2ZhZGVzIjp0cnVlfSwibWlkaV9zZXR0aW5ncyI6eyJzb3VyY2VfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90 + ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmln + aHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFs + c2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sInNlY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNf + b2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQi + Onsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2Us + ImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImxvb3BfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpm + YWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25f + bm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBl + cl9wZWRhbF9vZmYiOmZhbHNlfX0sImludGVyYWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQiOnsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6 + ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9u + X25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1w + ZXJfcGVkYWxfb2ZmIjpmYWxzZX19fX1dfSx7InJvdyI6MywiY2xpcHMiOlt7ImlkIjoiMDFIMkFCRzhEVlYzRUg1TUJLQkNTQU5BTU0iLCJuYW1lIjoiQ29sdW1uIDIi + LCJzb3VyY2UiOnsia2luZCI6Ik1pZGlDaHVuayIsImNodW5rIjoiSEFTREFUQSAxIDk2MCBRTlxuQ0NJTlRFUlAgMzJcblBPT0xFREVWVFMgezdCNTUzNTg5LTYyNDAt + NTc0MS04QkJFLTI2MDUzOURGNzRCOH1cbkUgMCA5MSAxMSA3ZiAzNFxuRSAxIGIwIDdiIDAwXG5FIDIzOSA4MSAxMSAwMFxuRSAyNDAgOTEgMTEgNDcgMjZcbkUgMjQw + IDgxIDExIDAwIC0xMFxuRSAyNDAgOTEgMTEgNDkgNzRcbkUgMjQwIDgxIDExIDAwIDIyXG5FIDI0MCA5MSAxMSAzOSA2OVxuRSAyNDAgODEgMTEgMDAgLTI4XG5FIDI0 + MCA5MSAxMSA3ZiA3MFxuRSAyNDAgODEgMTEgMDAgMzVcbkUgMjQwIDkxIDExIDQ1IDIwXG5FIDI0MCA4MSAxMSAwMCAtMjVcbkUgMjQwIDkxIDExIDQxIDMyXG5FIDI0 + MCA4MSAxMSAwMCAtMjRcbkUgMjQwIDkxIDExIDM5IDIzXG5FIDI0MCA4MSAxMSAwMCAtMzNcbkUgMjQwIDkxIDExIDY5IDM0XG5FIDI0MCA4MSAxMSAwMCAxMFxuRSAy + NDAgOTEgMTEgNDcgMTVcbkUgMjQwIDgxIDExIDAwIC00MFxuRSAyNDAgOTEgMTEgMzkgMTdcbkUgMjQwIDgxIDExIDAwIC0yOVxuRSAyNDAgOTEgMTEgM2YgMjhcbkUg + MjQwIDgxIDExIDAwIC00OFxuRSAyNDAgOTEgMTEgN2YgNDBcbkUgMjQwIDgxIDExIDAwIDRcbkUgMjQwIDkxIDExIDQzXG5FIDI0MCA4MSAxMSAwMCAtNjZcbkUgMjQw + IDkxIDExIDQ5IC0xOVxuRSAyNDAgODEgMTEgMDAgLTk1XG5FIDI0MCA5MSAxMSAzOSAtMjhcbkUgMjQwIDgxIDExIDAwIC05NFxuRSAyNDAgOTEgMTUgN2YgMTRcbkUg + MjQwIDgxIDE1IDAwIC0xXG5FIDI0MCA5MSAxNSA1MyAtMjZcbkUgMjQwIDgxIDE1IDAwIC01MVxuRSAyNDAgOTEgMTUgM2IgNlxuRSAyNDAgODEgMTUgMDAgLTM5XG5F + IDI0MCA5MSAxNSAzYiA1OVxuRSAyNDAgODEgMTUgMDAgM1xuRSAyNDAgOTEgMTUgN2YgNjBcbkUgMjQwIDgxIDE1IDAwIDQ1XG5FIDI0MCA5MSAxNSA0NyA0MVxuRSAy + NDAgODEgMTUgMDAgMTZcbkUgMjQwIDkxIDE1IDM3IDYzXG5FIDI0MCA4MSAxNSAwMCAzOFxuRSAyNDAgOTEgMTUgMzMgNTRcbkUgMjQwIDgxIDE1IDAwIC0yXG5FIDI0 + MCA5MSAxNSA2OSA2MVxuRSAyNDAgODEgMTUgMDAgNDBcbkUgMjQwIDkxIDE1IDRiIDQ2XG5FIDI0MCA4MSAxNSAwMCAyMVxuRSAyNDAgOTEgMTUgMzUgNzhcbkUgMjQw + IDgxIDE1IDAwIDMzXG5FIDI0MCA5MSAxNSAzOSA2OVxuRSAyNDAgODEgMTUgMDAgMjRcbkUgMjQwIDkxIDE1IDY1IDgxXG5FIDI0MCA4MSAxNSAwMCA2NlxuRSAyNDAg + OTEgMTUgNGIgNzJcbkUgMjQwIDgxIDE1IDAwIDQ3XG5FIDI0MCA5MSAxNSAzZiA3MFxuRSAyNDAgODEgMTUgMDAgMjdcbmUgMjQwIDkxIDE1IDMxIDY0XG5lIDI0MCA4 + MSAxNSAwMCA4XG5FIDI0MCA5MSAxMSA3ZiAxMDZcbkUgMjQwIDgxIDExIDAwIDYxXG5FIDI0MCA5MSAxMSA0OSA4N1xuRSAyNDAgODEgMTEgMDAgNDJcbkUgMjQwIDkx + IDExIDQzIDc4XG5FIDI0MCA4MSAxMSAwMCAzM1xuRSAyNDAgOTEgMTEgNGIgNTlcbkUgMjQwIDgxIDExIDAwIC03XG5FIDI0MCA5MSAxMSA3ZiA2MFxuRSAyNDAgODEg + MTEgMDAgNDVcbkUgMjQwIDkxIDExIDRiIDUxXG5FIDI0MCA4MSAxMSAwMCA2XG5FIDI0MCA5MSAxMSA0OSA0MlxuRSAyNDAgODEgMTEgMDAgLTNcbkUgMjQwIDkxIDEx + IDQ3IDIzXG5FIDI0MCA4MSAxMSAwMCAtMzNcbkUgMjQwIDkxIDExIDY5IDM0XG5FIDI0MCA4MSAxMSAwMCAzMFxuRSAyNDAgOTEgMTEgNDcgNDZcbkUgMjQwIDgxIDEx + IDAwIDIxXG5FIDI0MCA5MSAxMSA0NyA4OFxuRSAyNDAgODEgMTEgMDAgNTNcbkUgMjQwIDkxIDExIDQ3IDkwXG5FIDI0MCA4MSAxMSAwMCAyNFxuRSAyNDAgOTEgMTEg + NjkgMTAxXG5FIDI0MCA4MSAxMSAwMCA3NlxuRSAyNDAgOTEgMTEgNDcgNzJcbkUgMjQwIDgxIDExIDAwIDM2XG5FIDI0MCA5MSAxMSA0ZCA2M1xuRSAyNDAgODEgMTEg + MDAgMjdcbkUgMjQwIDkxIDExIDNkIDIzXG5FIDI0MCA4MSAxMSAwMCAtMjJcbkUgMjQwIDkxIDEwIDY5IDc1XG5FIDI0MCA4MSAxMCAwMCA0MFxuRSAyNDAgOTEgMTAg + NDUgNDZcbkUgMjQwIDgxIDEwIDAwIC0xMFxuRSAyNDAgOTEgMTAgNDEgNDdcbkUgMjQwIDgxIDEwIDAwIDIyXG5FIDI0MCA5MSAxMCA0MyAzOFxuRSAyNDAgODEgMTAg + MDAgLTdcbkUgMjQwIDkxIDEwIDY1IDQwXG5FIDI0MCA4MSAxMCAwMCAxNVxuRSAyNDAgOTEgMTAgNDUgMzFcbkUgMjQwIDgxIDEwIDAwIC0xNVxuRSAyNDAgOTEgMTAg + NGIgMzJcbkUgMjQwIDgxIDEwIDAwIC0zXG5FIDI0MCA5MSAxMCAzNyAzM1xuRSAyNDAgODEgMTAgMDAgLTJcbkUgMjQwIDkxIDEwIDY1IDY1XG5FIDI0MCA4MSAxMCAw + MCA2MVxuRSAyNDAgOTEgMTAgNDUgNTZcbkUgMjQwIDgxIDEwIDAwIDMxXG5FIDI0MCA5MSAxMCA0MyA0N1xuRSAyNDAgODEgMTAgMDAgMjJcbkUgMjQwIDkxIDEwIDM3 + IDM4XG5FIDI0MCA4MSAxMCAwMCAxM1xuRSAyNDAgOTEgMTAgNjUgNzBcbkUgMjQwIDgxIDEwIDAwIDU2XG5FIDI0MCA5MSAxMCA0NSA1MVxuRSAyNDAgODEgMTAgMDAg + MjZcbkUgMjQwIDkxIDEwIDQzIDQyXG5FIDI0MCA4MSAxMCAwMCAzOFxuRSAyNDAgOTEgMTAgMzUgNDNcbkUgMjQwIDgxIDEwIDAwIDE4XG5FIDI0MCBiMCA3YiAwMFxu + Q0NJTlRFUlAgMzJcbkdVSUQgezlGQURGQjdELTA4NjQtQjk0My1CNUFELUJFQUY4Rjg5NzQyRX1cbklHTlRFTVBPIDEgMTIwIDQgNFxuU1JDQ09MT1IgMzA0MVxuIn0s + ImFjdGl2ZV9zb3VyY2UiOiJOb3JtYWwiLCJ0aW1lX2Jhc2UiOnsia2luZCI6IkJlYXQiLCJ0aW1lX3NpZ25hdHVyZSI6eyJudW1lcmF0b3IiOjQsImRlbm9taW5hdG9y + Ijo0fSwiZG93bmJlYXQiOjAuMH0sImxvb3BlZCI6dHJ1ZSwidm9sdW1lIjowLjAsImNvbG9yIjp7ImtpbmQiOiJQbGF5VHJhY2tDb2xvciJ9LCJzZWN0aW9uIjp7InN0 + YXJ0X3BvcyI6MC4wfSwiYXVkaW9fc2V0dGluZ3MiOnsiYXBwbHlfc291cmNlX2ZhZGVzIjp0cnVlfSwibWlkaV9zZXR0aW5ncyI6eyJzb3VyY2VfcmVzZXRfc2V0dGlu + Z3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9s + bGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3Nv + dW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sInNlY3Rpb25fcmVzZXRfc2V0dGluZ3Mi + OnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVy + cyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5k + X29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImxvb3BfcmVzZXRfc2V0dGluZ3MiOnsibGVm + dCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFs + c2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6 + ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImludGVyYWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7Imxl + ZnQiOnsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZh + bHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYi + OmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19fX1dfV19LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1v + ZGUiOnsia2luZCI6Ik5vbkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiQkMyQUY5MjgtRUEyRC1EQTRELUFDMzgtRjhDMkQyOEJCRTU3IiwiYXVkaW9f + c2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W3sicm93IjowLCJjbGlwcyI6 + W3siaWQiOiIwMUgyQUJHOERWSEo1NzkzNlBUUEtDVDBBNSIsIm5hbWUiOiJDb2x1bW4gMyIsInNvdXJjZSI6eyJraW5kIjoiTWlkaUNodW5rIiwiY2h1bmsiOiJIQVNE + QVRBIDEgOTYwIFFOXG5DQ0lOVEVSUCAzMlxuUE9PTEVERVZUUyB7M0E5NEE0NjEtQzQxNC00MzRDLUIwM0UtRjQwQkU4MTBDRjc1fVxuRSAwIDkxIDM0IDNmXG5FIDAg + OTEgM2IgNDMgNDFcbkUgMSBiMCA3YiAwMFxuRSA0NzkgODEgMzQgMDAgMjlcbkUgMCA4MSAzYiAwMCA4M1xuRSAwIDkxIDM3IDM3IC0xOVxuRSAwIDkxIDNjIDQ5IDI5 + XG5FIDI0MCA4MSAzNyAwMCA2OFxuRSAwIDgxIDNjIDAwIDEyMFxuRSAwIDkxIDM5IDM5IDI4XG5FIDAgOTEgM2UgMzMgNTVcbkUgNDgwIDgxIDM5IDAwIDYwXG5FIDAg + ODEgM2UgMDAgLTIyXG5FIDAgOTEgNDMgM2QgNjBcbkUgMCA5MSAzYiAzMyA2MFxuRSA0ODAgODEgNDMgMDAgNDBcbkUgMCA4MSAzYiAwMCAxMFxuRSAwIDkxIDM0IDNk + IDYxXG5FIDAgOTEgM2IgNTYgODFcbkUgNDgwIDgxIDM0IDAwIDQyXG5FIDAgODEgM2IgMDAgNzJcbkUgMCA5MSAzNyAzNSAzMVxuRSAwIDkxIDNjIDM3IDUyXG5FIDI0 + MCA4MSAzNyAwMCA1OFxuRSAwIDgxIDNjIDAwIDk5XG5FIDAgOTEgMzkgMmIgMzdcbkUgMCA5MSAzZSAzNyA1OFxuRSAyNDAgODEgMzkgMDAgMTE1XG5FIDAgODEgM2Ug + MDAgNzRcbkUgMCA5MSA0MyAyYiA1M1xuRSAwIDkxIDNiIDNkIDg0XG5FIDI0MCA4MSA0MyAwMCAxNDFcbkUgMCA4MSAzYiAwMCAxNDFcbkUgMCA5MSAzZSA0NSAzOFxu + RSAwIDkxIDM5IDUzIDc4XG5FIDQ4MCA4MSAzZSAwMCAxMjJcbkUgMCA4MSAzOSAwMCA0MFxuRSAwIDkxIDM3IDNmIDI5XG5FIDAgOTEgM2MgNDUgNTBcbkUgNDgwIDgx + IDM3IDAwIDQ3XG5FIDAgODEgM2MgMDAgMzFcbkUgMCA5MSAzNCA0NSAyMFxuRSAwIDkxIDNiIDQzIDQxXG5FIDQ4MCA4MSAzNCAwMCAxMlxuRSAwIDgxIDNiIDAwIDYz + XG5FIDAgOTEgMzcgMmQgMTJcbkUgMCA5MSAzYyA0MSAzMlxuRSAyNDAgODEgMzcgMDAgNThcbkUgMCA4MSAzYyAwMCA5OVxuRSAwIDkxIDM5IDM1IDdcbkUgMCA5MSAz + ZSA0NSAyOFxuRSA0ODAgODEgMzkgMDAgNzBcbkUgMCA4MSAzZSAwMCA5MFxuRSAwIDkxIDNiIDM5IDQ5XG5FIDAgOTEgNDMgM2YgNDlcbkUgNDgwIDgxIDNiIDAwIC0x + XG5FIDAgODEgNDMgMDAgNjFcbkUgMCA5MSAzNCAzZiAxMFxuRSAwIDkxIDNiIDRkIDUxXG5FIDQ4MCA4MSAzNCAwMCAtMzBcbkUgMCA4MSAzYiAwMCAxNVxuRSAwIDkx + IDM3IDM3IC0zMFxuRSAwIDkxIDNjIDM5IDFcbkUgMjQwIDgxIDM3IDAwIC00XG5FIDAgODEgM2MgMDAgMjdcbkUgMCA5MSAzOSAyZCAtMjRcbkUgMCA5MSAzZSAzOSA2 + XG5FIDI0MCA4MSAzOSAwMCAzM1xuRSAwIDgxIDNlIDAwIDEyXG5FIDAgOTEgNDMgMzMgMjJcbkUgMCA5MSAzYiA0MSAyMlxuRSAyNDAgODEgNDMgMDAgMTEwXG5FIDAg + ODEgM2IgMDAgMThcbkUgMCA5MSAzOSA0ZCAtMTNcbkUgMCA5MSAzZSAzYiAyOFxuRSAyNDAgODEgMzkgMDAgODVcbkUgMCA4MSAzZSAwMCAxMjZcbkUgMCA5MSAzNyA0 + NSA1NFxuRSAwIDkxIDNjIDQ5IDc1XG5FIDI0MCA4MSAzNyAwMCA3MFxuRSAwIDgxIDNjIDAwIC0xXG5FIDAgOTEgM2IgNTkgMjlcbkUgMCA5MSAzNCAzNyAyOVxuRSAy + NDAgODEgM2IgMDAgNTZcbkUgMjQwIDgxIDM0IDAwIC04MlxuRSAwIDkxIDM1IDY1IC0zMVxuRSAwIDkxIDM5IDQ1IC0yMFxuRSA0ODAgODEgMzUgMDAgLTkxXG5FIDAg + ODEgMzkgMDAgLTlcbkUgMCA5MSAzNyAzZCAtNDBcbkUgMCA5MSAzYiAzNSAtMjlcbkUgMjQwIDgxIDM3IDAwIC04NVxuRSAwIDgxIDNiIDAwIC0zNFxuRSAwIDkxIDM5 + IDMzIC01NFxuRSAwIDkxIDNjIDM1IC01MlxuRSA0ODAgODEgMzkgMDAgN1xuRSAwIDgxIDNjIDAwIC00M1xuRSAwIDkxIDNlIDQ1IC0xMlxuRSAwIDkxIDNiIDQ3IDE5 + XG5FIDI0MCA4MSAzYiAwMCA0NVxuRSAyNDAgODEgM2UgMDAgLTcyXG5FIDAgOTEgMzkgNWYgMzBcbkUgMCA5MSAzNSA0YiA0MFxuRSA0ODAgODEgMzkgMDAgNTJcbkUg + MCA4MSAzNSAwMCAtNzFcbkUgMCA5MSAzNyA1MCAxXG5FIDAgOTEgM2IgNDcgMzFcbkUgMjQwIDgxIDM3IDAwIDZcbkUgMCA4MSAzYiAwMCAtMjRcbkUgMCA5MSAzOSA0 + MyA2XG5FIDAgOTEgM2MgNGIgMzdcbkUgMjQwIDgxIDM5IDAwIDUzXG5FIDAgODEgM2MgMDAgMzNcbkUgMCA5MSAzYiAxZiA1M1xuRSAwIDkxIDNlIDFmIDYzXG5FIDI0 + MCA4MSAzYiAwMCAyOFxuRSAwIDgxIDNlIDAwIDExMFxuRSAwIDkxIDM5IDU2IDhcbkUgMCA5MSAzYyAzZiAyOFxuRSA0ODAgODEgMzkgMDAgLTgzXG5FIDAgODEgM2Mg + MDAgMTAxXG5FIDAgOTEgMzcgNGIgLTFcbkUgMCA5MSAzYiA0OSAyOVxuRSA0ODAgODEgMzcgMDAgLTQxXG5FIDAgODEgM2IgMDAgLTIwXG5FIDAgOTEgMzUgNTZcbkUg + MCA5MSAzOSA1MyAzMVxuRSA0ODAgODEgMzUgMDAgLTI5XG5FIDAgODEgMzkgMDAgNjNcbkUgMCA5MSAzNyAzOSAyMlxuRSAwIDkxIDNiIDUwIDIyXG5FIDI0MCA4MSAz + NyAwMCAtMjRcbkUgMCA4MSAzYiAwMCAtMjRcbkUgMCA5MSAzOSAzZCAtMjRcbkUgMCA5MSAzYyA0MSAyOFxuRSAyNDAgODEgM2MgMDAgMTM2XG5FIDI0MCA4MSAzOSAw + MCAzOVxuRSAwIDkxIDNlIDNkIDE5XG5FIDAgOTEgM2IgNDkgNjBcbkUgMjQwIDgxIDNiIDAwIDEwNlxuRSAyNDAgODEgM2UgMDAgLTMxXG5FIDAgOTEgMzUgNGIgMjBc + bkUgMCA5MSAzOSA0NSA0MFxuRSAyNDAgODEgMzkgMDAgMTQ4XG5FIDI0MCA4MSAzNSAwMCAtOTJcbkUgMCA5MSAzNyA1MCAtNDBcbkUgMCA5MSAzYiAzZiAtMzBcbkUg + MjQwIDgxIDM3IDAwIC02NVxuRSAwIDgxIDNiIDAwIC0xMDZcbkUgMCA5MSAzOSAyMSAtNjVcbkUgMCA5MSAzYyA0NyAyXG5FIDI0MCA4MSAzOSAwMCAtMjlcbkUgMCA4 + MSAzYyAwMCAtMzlcbkUgMCA5MSAzYiA0NyAtMTlcbkUgMCA5MSAzZSAzMyAyMlxuRSAyNDAgODEgM2IgMDAgLTMyXG5FIDAgODEgM2UgMDAgOTBcbkUgMCA5MSAzOSA1 + MyAtMTNcbkUgMCA5MSAzYyA1YyAyOFxuRSAyNDAgODEgMzkgMDAgM1xuRSAwIDgxIDNjIDAwXG5FIDAgOTEgMzcgNDlcbkUgMCA5MSAzYiA0ZFxuRSAyNDAgODEgMzcg + MDBcbkUgMCA4MSAzYiAwMFxuZSAyNDAgOTEgMzUgMjdcbmUgMCA5MSAzOSA1MFxuZSAyNDAgODEgMzUgMDBcbmUgMCA4MSAzOSAwMFxuRSAwIGIwIDdiIDAwXG5DQ0lO + VEVSUCAzMlxuR1VJRCB7QzcwQUIwN0ItMjJFOC1GMTRBLUJBMjAtOTA0MUEzQjA2REIxfVxuSUdOVEVNUE8gMSAxMjAgNCA0XG5TUkNDT0xPUiAzMDQyXG4ifSwiYWN0 + aXZlX3NvdXJjZSI6Ik5vcm1hbCIsInRpbWVfYmFzZSI6eyJraW5kIjoiQmVhdCIsInRpbWVfc2lnbmF0dXJlIjp7Im51bWVyYXRvciI6NCwiZGVub21pbmF0b3IiOjR9 + LCJkb3duYmVhdCI6MC4wfSwibG9vcGVkIjp0cnVlLCJ2b2x1bWUiOjAuMCwiY29sb3IiOnsia2luZCI6IlBsYXlUcmFja0NvbG9yIn0sInNlY3Rpb24iOnsic3RhcnRf + cG9zIjowLjB9LCJhdWRpb19zZXR0aW5ncyI6eyJhcHBseV9zb3VyY2VfZmFkZXMiOnRydWV9LCJtaWRpX3NldHRpbmdzIjp7InNvdXJjZV9yZXNldF9zZXR0aW5ncyI6 + eyJsZWZ0Ijp7Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJz + IjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRf + b2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwic2VjdGlvbl9yZXNldF9zZXR0aW5ncyI6eyJs + ZWZ0Ijp7Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpm + YWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2Zm + IjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwibG9vcF9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7 + Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwi + ZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxz + ZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwiaW50ZXJhY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6 + eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2Us + ImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFs + c2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX19fV19LHsicm93IjoxLCJjbGlwcyI6W3siaWQiOiIwMUgyQUJH + OERWNVpQRzFBU0hOWDE5NDU5MSIsIm5hbWUiOiJDb2x1bW4gMyIsInNvdXJjZSI6eyJraW5kIjoiTWlkaUNodW5rIiwiY2h1bmsiOiJIQVNEQVRBIDEgOTYwIFFOXG5D + Q0lOVEVSUCAzMlxuUE9PTEVERVZUUyB7NDU3MzRBQzktMzc1Ni1ENDQ3LUEzMUEtMjYyQkM5OEE3NTk1fVxuRSAwIDkxIDM0IDNmXG5FIDAgOTEgM2IgNDMgNDFcbkUg + MSBiMCA3YiAwMFxuRSA0NzkgODEgMzQgMDAgMjlcbkUgMCA4MSAzYiAwMCA4M1xuRSAwIDkxIDM3IDM3IC0xOVxuRSAwIDkxIDNjIDQ5IDI5XG5FIDI0MCA4MSAzNyAw + MCA2OFxuRSAwIDgxIDNjIDAwIDEyMFxuRSAwIDkxIDM5IDM5IDI4XG5FIDAgOTEgM2UgMzMgNTVcbkUgNDgwIDgxIDM5IDAwIDYwXG5FIDAgODEgM2UgMDAgLTIyXG5F + IDAgOTEgNDMgM2QgNjBcbkUgMCA5MSAzYiAzMyA2MFxuRSA0ODAgODEgNDMgMDAgNDBcbkUgMCA4MSAzYiAwMCAxMFxuRSAwIDkxIDM0IDNkIDYxXG5FIDAgOTEgM2Ig + NTYgODFcbkUgNDgwIDgxIDM0IDAwIDQyXG5FIDAgODEgM2IgMDAgNzJcbkUgMCA5MSAzNyAzNSAzMVxuRSAwIDkxIDNjIDM3IDUyXG5FIDI0MCA4MSAzNyAwMCA1OFxu + RSAwIDgxIDNjIDAwIDk5XG5FIDAgOTEgMzkgMmIgMzdcbkUgMCA5MSAzZSAzNyA1OFxuRSAyNDAgODEgMzkgMDAgMTE1XG5FIDAgODEgM2UgMDAgNzRcbkUgMCA5MSA0 + MyAyYiA1M1xuRSAwIDkxIDNiIDNkIDg0XG5FIDI0MCA4MSA0MyAwMCAxNDFcbkUgMCA4MSAzYiAwMCAxNDFcbkUgMCA5MSAzZSA0NSAzOFxuRSAwIDkxIDM5IDUzIDc4 + XG5FIDQ4MCA4MSAzZSAwMCAxMjJcbkUgMCA4MSAzOSAwMCA0MFxuRSAwIDkxIDM3IDNmIDI5XG5FIDAgOTEgM2MgNDUgNTBcbkUgNDgwIDgxIDM3IDAwIDQ3XG5FIDAg + ODEgM2MgMDAgMzFcbkUgMCA5MSAzNCA0NSAyMFxuRSAwIDkxIDNiIDQzIDQxXG5FIDQ4MCA4MSAzNCAwMCAxMlxuRSAwIDgxIDNiIDAwIDYzXG5FIDAgOTEgMzcgMmQg + MTJcbkUgMCA5MSAzYyA0MSAzMlxuRSAyNDAgODEgMzcgMDAgNThcbkUgMCA4MSAzYyAwMCA5OVxuRSAwIDkxIDM5IDM1IDdcbkUgMCA5MSAzZSA0NSAyOFxuRSA0ODAg + ODEgMzkgMDAgNzBcbkUgMCA4MSAzZSAwMCA5MFxuRSAwIDkxIDNiIDM5IDQ5XG5FIDAgOTEgNDMgM2YgNDlcbkUgNDgwIDgxIDNiIDAwIC0xXG5FIDAgODEgNDMgMDAg + NjFcbkUgMCA5MSAzNCAzZiAxMFxuRSAwIDkxIDNiIDRkIDUxXG5FIDQ4MCA4MSAzNCAwMCAtMzBcbkUgMCA4MSAzYiAwMCAxNVxuRSAwIDkxIDM3IDM3IC0zMFxuRSAw + IDkxIDNjIDM5IDFcbkUgMjQwIDgxIDM3IDAwIC00XG5FIDAgODEgM2MgMDAgMjdcbkUgMCA5MSAzOSAyZCAtMjRcbkUgMCA5MSAzZSAzOSA2XG5FIDI0MCA4MSAzOSAw + MCAzM1xuRSAwIDgxIDNlIDAwIDEyXG5FIDAgOTEgNDMgMzMgMjJcbkUgMCA5MSAzYiA0MSAyMlxuRSAyNDAgODEgNDMgMDAgMTEwXG5FIDAgODEgM2IgMDAgMThcbkUg + MCA5MSAzOSA0ZCAtMTNcbkUgMCA5MSAzZSAzYiAyOFxuRSAyNDAgODEgMzkgMDAgODVcbkUgMCA4MSAzZSAwMCAxMjZcbkUgMCA5MSAzNyA0NSA1NFxuRSAwIDkxIDNj + IDQ5IDc1XG5FIDI0MCA4MSAzNyAwMCA3MFxuRSAwIDgxIDNjIDAwIC0xXG5FIDAgOTEgM2IgNTkgMjlcbkUgMCA5MSAzNCAzNyAyOVxuRSAyNDAgODEgM2IgMDAgNTZc + bkUgMjQwIDgxIDM0IDAwIC04MlxuRSAwIDkxIDM1IDY1IC0zMVxuRSAwIDkxIDM5IDQ1IC0yMFxuRSA0ODAgODEgMzUgMDAgLTkxXG5FIDAgODEgMzkgMDAgLTlcbkUg + MCA5MSAzNyAzZCAtNDBcbkUgMCA5MSAzYiAzNSAtMjlcbkUgMjQwIDgxIDM3IDAwIC04NVxuRSAwIDgxIDNiIDAwIC0zNFxuRSAwIDkxIDM5IDMzIC01NFxuRSAwIDkx + IDNjIDM1IC01MlxuRSA0ODAgODEgMzkgMDAgN1xuRSAwIDgxIDNjIDAwIC00M1xuRSAwIDkxIDNlIDQ1IC0xMlxuRSAwIDkxIDNiIDQ3IDE5XG5FIDI0MCA4MSAzYiAw + MCA0NVxuRSAyNDAgODEgM2UgMDAgLTcyXG5FIDAgOTEgMzkgNWYgMzBcbkUgMCA5MSAzNSA0YiA0MFxuRSA0ODAgODEgMzkgMDAgNTJcbkUgMCA4MSAzNSAwMCAtNzFc + bkUgMCA5MSAzNyA1MCAxXG5FIDAgOTEgM2IgNDcgMzFcbkUgMjQwIDgxIDM3IDAwIDZcbkUgMCA4MSAzYiAwMCAtMjRcbkUgMCA5MSAzOSA0MyA2XG5FIDAgOTEgM2Mg + NGIgMzdcbkUgMjQwIDgxIDM5IDAwIDUzXG5FIDAgODEgM2MgMDAgMzNcbkUgMCA5MSAzYiAxZiA1M1xuRSAwIDkxIDNlIDFmIDYzXG5FIDI0MCA4MSAzYiAwMCAyOFxu + RSAwIDgxIDNlIDAwIDExMFxuRSAwIDkxIDM5IDU2IDhcbkUgMCA5MSAzYyAzZiAyOFxuRSA0ODAgODEgMzkgMDAgLTgzXG5FIDAgODEgM2MgMDAgMTAxXG5FIDAgOTEg + MzcgNGIgLTFcbkUgMCA5MSAzYiA0OSAyOVxuRSA0ODAgODEgMzcgMDAgLTQxXG5FIDAgODEgM2IgMDAgLTIwXG5FIDAgOTEgMzUgNTZcbkUgMCA5MSAzOSA1MyAzMVxu + RSA0ODAgODEgMzUgMDAgLTI5XG5FIDAgODEgMzkgMDAgNjNcbkUgMCA5MSAzNyAzOSAyMlxuRSAwIDkxIDNiIDUwIDIyXG5FIDI0MCA4MSAzNyAwMCAtMjRcbkUgMCA4 + MSAzYiAwMCAtMjRcbkUgMCA5MSAzOSAzZCAtMjRcbkUgMCA5MSAzYyA0MSAyOFxuRSAyNDAgODEgM2MgMDAgMTM2XG5FIDI0MCA4MSAzOSAwMCAzOVxuRSAwIDkxIDNl + IDNkIDE5XG5FIDAgOTEgM2IgNDkgNjBcbkUgMjQwIDgxIDNiIDAwIDEwNlxuRSAyNDAgODEgM2UgMDAgLTMxXG5FIDAgOTEgMzUgNGIgMjBcbkUgMCA5MSAzOSA0NSA0 + MFxuRSAyNDAgODEgMzkgMDAgMTQ4XG5FIDI0MCA4MSAzNSAwMCAtOTJcbkUgMCA5MSAzNyA1MCAtNDBcbkUgMCA5MSAzYiAzZiAtMzBcbkUgMjQwIDgxIDM3IDAwIC02 + NVxuRSAwIDgxIDNiIDAwIC0xMDZcbkUgMCA5MSAzOSAyMSAtNjVcbkUgMCA5MSAzYyA0NyAyXG5FIDI0MCA4MSAzOSAwMCAtMjlcbkUgMCA4MSAzYyAwMCAtMzlcbkUg + MCA5MSAzYiA0NyAtMTlcbkUgMCA5MSAzZSAzMyAyMlxuRSAyNDAgODEgM2IgMDAgLTMyXG5FIDAgODEgM2UgMDAgOTBcbkUgMCA5MSAzOSA1MyAtMTNcbkUgMCA5MSAz + YyA1YyAyOFxuRSAyNDAgODEgMzkgMDAgM1xuRSAwIDgxIDNjIDAwXG5FIDAgOTEgMzcgNDlcbkUgMCA5MSAzYiA0ZFxuRSAyNDAgODEgMzcgMDBcbkUgMCA4MSAzYiAw + MFxuZSAyNDAgOTEgMzUgMjdcbmUgMCA5MSAzOSA1MFxuZSAyNDAgODEgMzUgMDBcbmUgMCA4MSAzOSAwMFxuRSAwIGIwIDdiIDAwXG5DQ0lOVEVSUCAzMlxuR1VJRCB7 + NkJDQzhGM0YtQThCQy01MjRGLUI3MjEtMTBGNDEyNTc4MERGfVxuSUdOVEVNUE8gMSAxMjAgNCA0XG5TUkNDT0xPUiAzMDQzXG4ifSwiYWN0aXZlX3NvdXJjZSI6Ik5v + cm1hbCIsInRpbWVfYmFzZSI6eyJraW5kIjoiQmVhdCIsInRpbWVfc2lnbmF0dXJlIjp7Im51bWVyYXRvciI6NCwiZGVub21pbmF0b3IiOjR9LCJkb3duYmVhdCI6MC4w + fSwibG9vcGVkIjp0cnVlLCJ2b2x1bWUiOjAuMCwiY29sb3IiOnsia2luZCI6IlBsYXlUcmFja0NvbG9yIn0sInNlY3Rpb24iOnsic3RhcnRfcG9zIjowLjB9LCJhdWRp + b19zZXR0aW5ncyI6eyJhcHBseV9zb3VyY2VfZmFkZXMiOnRydWV9LCJtaWRpX3NldHRpbmdzIjp7InNvdXJjZV9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25v + dGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVy + X3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVz + ZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwic2VjdGlvbl9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25vdGVz + X29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3Bl + ZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRf + YWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwibG9vcF9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25vdGVzX29mZiI6 + ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29m + ZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2Nv + bnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwiaW50ZXJhY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYi + OmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9v + ZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9j + b250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX19fV19LHsicm93IjoyLCJjbGlwcyI6W3siaWQiOiIwMUgyQUJHOERWNkE3S1M2WUhaV1c5 + SllINSIsIm5hbWUiOiJDb2x1bW4gMyIsInNvdXJjZSI6eyJraW5kIjoiTWlkaUNodW5rIiwiY2h1bmsiOiJIQVNEQVRBIDEgOTYwIFFOXG5DQ0lOVEVSUCAzMlxuUE9P + TEVERVZUUyB7ODYwRDVBNDItMDEwOS1ERTRELThCQTgtNzcyRkExQTAzMDNDfVxuRSAxIGIwIDdiIDAwXG5FIDI0IDkxIDQ3IDRiXG5FIDQxIDkxIDUzIDM5XG5FIDEz + MyA4MSA0NyAwMFxuRSA0MSA4MSA1MyAwMFxuRSA0OTIgOTEgNDMgNDNcbkUgMTAyIDkxIDRmIDU2XG5FIDUyIDgxIDQzIDAwXG5FIDExMiA4MSA0ZiAwMFxuRSA0NDEg + OTEgNDAgNTBcbkUgNDAgOTEgNGMgNWNcbkUgMTEzIDgxIDQwIDAwXG5FIDEwMyA4MSA0YyAwMFxuRSA0NzEgOTEgNDMgNWNcbkUgMTIyIDkxIDRmIDYyXG5FIDIxIDgx + IDQzIDAwXG5FIDEzMyA4MSA0ZiAwMFxuRSA0NjEgOTEgNDcgNWZcbkUgMzEgOTEgNTMgNGJcbkUgMTYzIDgxIDQ3IDAwXG5FIDUyIDgxIDUzIDAwXG5FIDQ1NTYgOTEg + NDggNTBcbkUgODIgOTEgNTQgNTBcbkUgNzIgODEgNDggMDBcbkUgMTIzIDgxIDU0IDAwXG5FIDM3OSA5MSA0NyA1NlxuRSAxMzMgODEgNDcgMDBcbkUgMCA5MSA1MyA1 + OVxuRSAxNzQgODEgNTMgMDBcbkUgNDEwIDkxIDQ1IDQxXG5FIDkyIDkxIDUxIDVjXG5FIDIwIDgxIDQ1IDAwXG5FIDEzMyA4MSA1MSAwMFxuRSAyNDYgOTEgNDMgNjVc + bkUgMTIzIDgxIDQzIDAwXG5FIDMxIDkxIDRmIDRiXG5FIDE2NCA4MSA0ZiAwMFxuRSA2NDUgOTEgNDAgNjJcbkUgNjEgOTEgNGMgMzVcbkUgNjIgODEgNDAgMDBcbkUg + MTEyIDgxIDRjIDAwXG5FIDIxNSA5MSAzZSA1ZlxuZSAxMjAgOTEgNGEgNDFcbkUgMTMgODEgM2UgMDBcbmUgMTk0IDgxIDRhIDAwXG5FIDQwNTEgYjAgN2IgMDBcbkND + SU5URVJQIDMyXG5HVUlEIHtFN0Y0Qjc5NS01QjZCLTBDNDktQjRERC1EMzYwQTM4RTc4REZ9XG5JR05URU1QTyAxIDEyMCA0IDRcblNSQ0NPTE9SIDMwNDRcbiJ9LCJh + Y3RpdmVfc291cmNlIjoiTm9ybWFsIiwidGltZV9iYXNlIjp7ImtpbmQiOiJCZWF0IiwidGltZV9zaWduYXR1cmUiOnsibnVtZXJhdG9yIjo0LCJkZW5vbWluYXRvciI6 + NH0sImRvd25iZWF0IjowLjB9LCJsb29wZWQiOnRydWUsInZvbHVtZSI6MC4wLCJjb2xvciI6eyJraW5kIjoiUGxheVRyYWNrQ29sb3IifSwic2VjdGlvbiI6eyJzdGFy + dF9wb3MiOjAuMH0sImF1ZGlvX3NldHRpbmdzIjp7ImFwcGx5X3NvdXJjZV9mYWRlcyI6dHJ1ZX0sIm1pZGlfc2V0dGluZ3MiOnsic291cmNlX3Jlc2V0X3NldHRpbmdz + Ijp7ImxlZnQiOnsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxl + cnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3Vu + ZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19LCJzZWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7 + ImxlZnQiOnsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMi + OmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9v + ZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19LCJsb29wX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQi + Onsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNl + LCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZh + bHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19LCJpbnRlcmFjdGlvbl9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0 + Ijp7Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxz + ZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpm + YWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fX19XX0seyJyb3ciOjMsImNsaXBzIjpbeyJpZCI6IjAxSDJB + Qkc4RFYxQUFHSEFDNU1OQlk0MTRFIiwibmFtZSI6IkNvbHVtbiAyIiwic291cmNlIjp7ImtpbmQiOiJNaWRpQ2h1bmsiLCJjaHVuayI6IkhBU0RBVEEgMSA5NjAgUU5c + bkNDSU5URVJQIDMyXG5QT09MRURFVlRTIHszMTRDNjlFOS0yMUE3LTBFNEQtQjdFNy00OUM4RjM3NDg1Nzh9XG5lIDAgOTEgMjQgNWFcbkUgMSBiMCA3YiAwMFxuZSAy + MzkgODEgMjQgMDAgMTZcbmUgMCA5MSAyOCA1YSAyNlxuZSAyNDAgODEgMjggMDAgMzJcbmUgMCA5MSAyOSA1YSAxXG5lIDI0MCA4MSAyOSAwMCAtNjVcbmUgMCA5MSAy + ZCA1YSA3XG5lIDI0MCA4MSAyZCAwMCAtMTAwXG5lIDAgOTEgMzAgNWEgLTM5XG5lIDI0MCA4MSAzMCAwMCAtNTNcbmUgMCA5MSAzNCA1YSAtNTNcbmUgMjQwIDgxIDM0 + IDAwIDRcbmUgMCA5MSAzNSA1YSAtMjdcbmUgMjQwIDgxIDM1IDAwIDUwXG5lIDAgOTEgMzkgNWEgLTExXG5lIDI0MCA4MSAzOSAwMCAtMTZcbmUgMCA5MSAzYyA1YSAt + MzZcbmUgMjQwIDgxIDNjIDAwIDIxXG5lIDAgOTEgMzkgNWEgLTUxXG5lIDI0MCA4MSAzOSAwMCAyN1xuZSAwIDkxIDM1IDVhIC0zNVxuZSAyNDAgODEgMzUgMDAgNDNc + bmUgMCA5MSAzNCA1YSAyXG5lIDI0MCA4MSAzNCAwMCA0XG5lIDAgOTEgMzAgNWEgLTQ0XG5lIDI0MCA4MSAzMCAwMCAtNThcbmUgMCA5MSAyZCA1YSAzXG5lIDI0MCA4 + MSAyZCAwMCAyOVxuZSAwIDkxIDI5IDVhIDI5XG5lIDI0MCA4MSAyOSAwMCAtMTZcbmUgMCA5MSAyOCA1YSAtMjZcbmUgMjQwIDgxIDI4IDAwIC04MlxuZSAwIDkxIDI0 + IDVhIC01MVxuZSAyNDAgODEgMjQgMDAgLTM1XG5lIDAgOTEgMjggNWEgLTU2XG5lIDI0MCA4MSAyOCAwMCAtNDBcbmUgMCA5MSAyOSA1YSAtNDBcbmUgMjQwIDgxIDI5 + IDAwIC01NVxuZSAwIDkxIDJkIDVhIC0zNFxuZSAyNDAgODEgMmQgMDAgLTkwXG5lIDAgOTEgMzAgNWEgLTQ5XG5lIDI0MCA4MSAzMCAwMCAtNzRcbmUgMCA5MSAzNCA1 + YSAtNTNcbmUgMjQwIDgxIDM0IDAwIC0yN1xuZSAwIDkxIDM1IDVhIC0yN1xuZSAyNDAgODEgMzUgMDAgNjFcbmUgMCA5MSAzOSA1YSAtMTFcbmUgMjQwIDgxIDM5IDAw + IDM2XG5lIDAgOTEgM2MgNWEgLTVcbmUgMjQwIDgxIDNjIDAwIDUyXG5lIDAgOTEgMzkgNWEgLTIwXG5lIDI0MCA4MSAzOSAwMCA1N1xuZSAwIDkxIDM1IDVhIC00XG5l + IDI0MCA4MSAzNSAwMCA3M1xuZSAwIDkxIDM0IDVhIDJcbmUgMjQwIDgxIDM0IDAwIDU5XG5lIDAgOTEgMzAgNWEgLTNcbmUgMjQwIDgxIDMwIDAwIC0xOFxuZSAwIDkx + IDJkIDVhIDQ0XG5lIDI0MCA4MSAyZCAwMCA1NlxuZSAwIDkxIDI5IDVhIDUwXG5lIDI0MCA4MSAyOSAwMCAxNFxuZSAwIDkxIDI4IDVhIDE0XG5lIDI0MCA4MSAyOCAw + MCAtMzFcbmUgMCA5MSAyNCA1YSAtMTBcbmUgMjQwIDgxIDI0IDAwIC0xNVxuZSAwIDkxIDI4IDVhIC0xNVxuZSAyNDAgODEgMjggMDAgLTE5XG5lIDAgOTEgMjkgNWEg + LTE5XG5lIDI0MCA4MSAyOSAwMCAtNDRcbmUgMCA5MSAyZCA1YSA3XG5lIDI0MCA4MSAyZCAwMCAtODBcbmUgMCA5MSAzMCA1YSAyXG5lIDI0MCA4MSAzMCAwMCAtMzNc + bmUgMCA5MSAzNCA1YSAtMlxuZSAyNDAgODEgMzQgMDAgLTM3XG5lIDAgOTEgMzUgNWEgMzRcbmUgMjQwIDgxIDM1IDAwIDkxXG5lIDAgOTEgMzkgNWEgMjNcbmUgMjQw + IDgxIDM5IDAwIDU2XG5lIDAgOTEgM2MgNWEgMTVcbmUgMjQwIDgxIDNjIDAwIDExM1xuZSAwIDkxIDM5IDVhIC0xMFxuZSAyNDAgODEgMzkgMDAgODhcbmUgMCA5MSAz + NSA1YSAxNlxuZSAyNDAgODEgMzUgMDAgODRcbmUgMCA5MSAzNCA1YSAzMlxuZSAyNDAgODEgMzQgMDAgNjlcbmUgMCA5MSAzMCA1YSAxOFxuZSAyNDAgODEgMzAgMDAg + LTI4XG5lIDAgOTEgMmQgNWEgMzRcbmUgMjQwIDgxIDJkIDAwIDM5XG5lIDAgOTEgMjkgNWEgNTBcbmUgMjQwIDgxIDI5IDAwIDE0XG5lIDAgOTEgMjggNWEgNFxuZSAy + NDAgODEgMjggMDAgLTQxXG5lIDAgOTEgMjQgNWEgLTEwXG5lIDI0MCA4MSAyNCAwMCAtNVxuZSAwIDkxIDI4IDVhIC0xNVxuZSAyNDAgODEgMjggMDAgLTMwXG5lIDAg + OTEgMjkgNWEgLTlcbmUgMjQwIDgxIDI5IDAwIC0zNFxuZSAwIDkxIDJkIDVhIDdcbmUgMjQwIDgxIDJkIDAwIC01OVxuZSAwIDkxIDMwIDVhIC0xOFxuZSAyNDAgODEg + MzAgMDAgLTUzXG5lIDAgOTEgMzQgNWEgLTEyXG5lIDI0MCA4MSAzNCAwMCA2M1xuZSAwIDkxIDM1IDVhIDI0XG5lIDI0MCA4MSAzNSAwMCAxMjJcbmUgMCA5MSAzOSA1 + YSA0MFxuZSAyNDAgODEgMzkgMDAgNDZcbmUgMCA5MSAzYyA1YSAzNlxuZSAyNDAgODEgM2MgMDAgOTNcbmUgMCA5MSAzOSA1YVxuZSAyNDAgODEgMzkgMDAgNjhcbmUg + MCA5MSAzNSA1YSAtNFxuZSAyNDAgODEgMzUgMDAgNzNcbmUgMCA5MSAzNCA1YSAxMlxuZSAyNDAgODEgMzQgMDAgNTlcbmUgMCA5MSAzMCA1YSAtNlxuZSAyNDAgODEg + MzAgMDAgLTE4XG5lIDAgOTEgMmQgNWEgMjNcbmUgMjQwIDgxIDJkIDAwIDE5XG5lIDAgOTEgMjkgNWEgMTlcbmUgMjQwIDgxIDI5IDAwIC02XG5lIDAgOTEgMjggNWEg + LTM3XG5lIDI0MCA4MSAyOCAwMCAtNDFcbkUgMCBiMCA3YiAwMFxuQ0NJTlRFUlAgMzJcbkdVSUQgezkwOUExMEQ1LTJFQkUtNTM0Ni1BMzJELTk4N0RCMDczQkRBOX1c + bklHTlRFTVBPIDEgMTIwIDQgNFxuU1JDQ09MT1IgMzA0NVxuIn0sImFjdGl2ZV9zb3VyY2UiOiJOb3JtYWwiLCJ0aW1lX2Jhc2UiOnsia2luZCI6IkJlYXQiLCJ0aW1l + X3NpZ25hdHVyZSI6eyJudW1lcmF0b3IiOjQsImRlbm9taW5hdG9yIjo0fSwiZG93bmJlYXQiOjAuMH0sImxvb3BlZCI6dHJ1ZSwidm9sdW1lIjowLjAsImNvbG9yIjp7 + ImtpbmQiOiJQbGF5VHJhY2tDb2xvciJ9LCJzZWN0aW9uIjp7InN0YXJ0X3BvcyI6MC4wfSwiYXVkaW9fc2V0dGluZ3MiOnsiYXBwbHlfc291cmNlX2ZhZGVzIjp0cnVl + fSwibWlkaV9zZXR0aW5ncyI6eyJzb3VyY2VfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwi + YWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNf + b2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRh + bF9vZmYiOmZhbHNlfX0sInNlY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxs + X3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2Zm + Ijp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9v + ZmYiOmZhbHNlfX0sImxvb3BfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5k + X29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVl + LCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZh + bHNlfX0sImludGVyYWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQiOnsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3Vu + ZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1 + ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpm + YWxzZX19fX1dfV19LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiQzRBOUNFNkMt + QzdEQS1CMzRFLThDNUMtREM2MjgyMjk3QkIyIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFj + a0lucHV0In19LCJzbG90cyI6W3sicm93IjowLCJjbGlwcyI6W3siaWQiOiIwMUgyQUJHOERWMDcyUlhLUUoyWFE0UTRKMCIsIm5hbWUiOiJDb2x1bW4gNCIsInNvdXJj + ZSI6eyJraW5kIjoiTWlkaUNodW5rIiwiY2h1bmsiOiJIQVNEQVRBIDEgOTYwIFFOXG5DQ0lOVEVSUCAzMlxuUE9PTEVERVZUUyB7OTYwREU1OEMtNTNFRC05RTQ5LUJE + NjEtMjkzRUIyRUE1RjYzfVxuZSAwIDkxIDQwIDRkXG5lIDAgOTEgNDMgNGRcbmUgMCA5MSAzYiA2MlxuRSAxIGIwIDdiIDAwXG5lIDY5NTkgODEgNDAgMDBcbmUgMCA4 + MSA0MyAwMFxuZSAwIDgxIDNiIDAwXG5FIDcyMCA5MSAzZSA0ZFxuRSAwIDkxIDM5IDQ5XG5FIDAgOTEgMzUgNGJcbkUgNzY4MCA5MSAzZSAwMFxuRSAwIDkxIDM5IDAw + XG5FIDAgOTEgMzUgMDBcbkUgMCBiMCA3YiAwMFxuQ0NJTlRFUlAgMzJcbkdVSUQgezUwOTk3RDAxLTRENjgtNzE0Mi04MTcyLUIxNDg4QTk1QTdFQX1cbklHTlRFTVBP + IDEgMTIwIDQgNFxuU1JDQ09MT1IgMzA0NlxuIn0sImFjdGl2ZV9zb3VyY2UiOiJOb3JtYWwiLCJ0aW1lX2Jhc2UiOnsia2luZCI6IkJlYXQiLCJ0aW1lX3NpZ25hdHVy + ZSI6eyJudW1lcmF0b3IiOjQsImRlbm9taW5hdG9yIjo0fSwiZG93bmJlYXQiOjAuMH0sImxvb3BlZCI6dHJ1ZSwidm9sdW1lIjowLjAsImNvbG9yIjp7ImtpbmQiOiJQ + bGF5VHJhY2tDb2xvciJ9LCJzZWN0aW9uIjp7InN0YXJ0X3BvcyI6MC4wfSwiYXVkaW9fc2V0dGluZ3MiOnsiYXBwbHlfc291cmNlX2ZhZGVzIjp0cnVlfSwibWlkaV9z + ZXR0aW5ncyI6eyJzb3VyY2VfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5k + X29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVl + LCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZh + bHNlfX0sInNlY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29m + ZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJh + bGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNl + fX0sImxvb3BfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFs + c2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90 + ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImlu + dGVyYWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQiOnsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZh + bHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25v + dGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19fX1d + fSx7InJvdyI6MywiY2xpcHMiOlt7ImlkIjoiMDFIMkFCRzhEVjAxSlAyVkNUM1RYM1hRTksiLCJuYW1lIjoiQ29sdW1uIDQiLCJzb3VyY2UiOnsia2luZCI6Ik1pZGlD + aHVuayIsImNodW5rIjoiSEFTREFUQSAxIDk2MCBRTlxuQ0NJTlRFUlAgMzJcblBPT0xFREVWVFMgezI0NTI4NTE4LUY3QzAtNzc0Qy04RTk1LTcyRDFERjg1NzNGMH1c + bmUgMCA5MSA0MSA2MlxuRSAxIGIwIDdiIDAwXG5lIDc2ODkgOTEgNDUgNDlcbmUgMTAzIDgxIDQxIDAwXG5lIDc1NjcgOTEgNDEgNDdcbmUgMTAzIDgxIDQ1IDAwXG5l + IDU2MjEgOTEgNDMgNTZcbmUgNjIgODEgNDEgMDBcbmUgNjU1IDkxIDQxIDRkXG5lIDEyMyA4MSA0MyAwMFxuZSA1MjIgOTEgNDAgNTNcbmUgODIgODEgNDEgMDBcbmUg + ODE5MiA4MSA0MCAwMFxuRSAwIGIwIDdiIDAwXG5DQ0lOVEVSUCAzMlxuR1VJRCB7OTA1ODM5NTQtNjRERC1DNDQ4LUJBQTAtQzY5MzAyMDU3RjYwfVxuSUdOVEVNUE8g + MSAxMjAgNCA0XG5TUkNDT0xPUiAzMDQ3XG4ifSwiYWN0aXZlX3NvdXJjZSI6Ik5vcm1hbCIsInRpbWVfYmFzZSI6eyJraW5kIjoiQmVhdCIsInRpbWVfc2lnbmF0dXJl + Ijp7Im51bWVyYXRvciI6NCwiZGVub21pbmF0b3IiOjR9LCJkb3duYmVhdCI6MC4wfSwibG9vcGVkIjp0cnVlLCJ2b2x1bWUiOjAuMCwiY29sb3IiOnsia2luZCI6IlBs + YXlUcmFja0NvbG9yIn0sInNlY3Rpb24iOnsic3RhcnRfcG9zIjowLjB9LCJhdWRpb19zZXR0aW5ncyI6eyJhcHBseV9zb3VyY2VfZmFkZXMiOnRydWV9LCJtaWRpX3Nl + dHRpbmdzIjp7InNvdXJjZV9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRf + b2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUs + ImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFs + c2V9fSwic2VjdGlvbl9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2Zm + IjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFs + bF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9 + fSwibG9vcF9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25vdGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxz + ZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rl + c19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fSwiaW50 + ZXJhY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFs + c2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90 + ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX19fV19 + XX0seyJjbGlwX3BsYXlfc2V0dGluZ3MiOnsibW9kZSI6eyJraW5kIjoiRXhjbHVzaXZlRm9sbG93aW5nU2NlbmUifSwidHJhY2siOiI5MkY5M0UzMS03MzQ4LTgzNEUt + QjEzOC02MEY2NTMxMDgyQkYiLCJhdWRpb19zZXR0aW5ncyI6e319LCJjbGlwX3JlY29yZF9zZXR0aW5ncyI6eyJvcmlnaW4iOnsia2luZCI6IlRyYWNrSW5wdXQifX0s + InNsb3RzIjpbXX0seyJjbGlwX3BsYXlfc2V0dGluZ3MiOnsibW9kZSI6eyJraW5kIjoiTm9uRXhjbHVzaXZlRm9sbG93aW5nU2NlbmUifSwidHJhY2siOiJENUY5OUNC + NS01RjQ4LTFBNDAtOEQzMS1CNDk4QUNBNjkxOEUiLCJhdWRpb19zZXR0aW5ncyI6e319LCJjbGlwX3JlY29yZF9zZXR0aW5ncyI6eyJvcmlnaW4iOnsia2luZCI6IlRy + YWNrSW5wdXQifX0sInNsb3RzIjpbeyJyb3ciOjAsImNsaXBzIjpbeyJpZCI6IjAxSDJBRjFSRDNGSDNYNkJFMDA0V0FEU1A2IiwibmFtZSI6IkNvbHVtbiA2Iiwic291 + cmNlIjp7ImtpbmQiOiJGaWxlIiwicGF0aCI6ImNsaXAtYXVkaW8tQlR6aHNPRW0ud2F2In0sImFjdGl2ZV9zb3VyY2UiOiJOb3JtYWwiLCJ0aW1lX2Jhc2UiOnsia2lu + ZCI6IkJlYXQiLCJhdWRpb190ZW1wbyI6MTIwLjAsInRpbWVfc2lnbmF0dXJlIjp7Im51bWVyYXRvciI6NCwiZGVub21pbmF0b3IiOjR9LCJkb3duYmVhdCI6MC4wfSwi + bG9vcGVkIjp0cnVlLCJ2b2x1bWUiOjAuMCwiY29sb3IiOnsia2luZCI6IlBsYXlUcmFja0NvbG9yIn0sInNlY3Rpb24iOnsic3RhcnRfcG9zIjoxLjMzNTQzNzUsImxl + bmd0aCI6Mi4wfSwiYXVkaW9fc2V0dGluZ3MiOnsiYXBwbHlfc291cmNlX2ZhZGVzIjp0cnVlfSwibWlkaV9zZXR0aW5ncyI6eyJzb3VyY2VfcmVzZXRfc2V0dGluZ3Mi + OnsibGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVy + cyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5k + X29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sInNlY3Rpb25fcmVzZXRfc2V0dGluZ3MiOnsi + bGVmdCI6eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6 + ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29m + ZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImxvb3BfcmVzZXRfc2V0dGluZ3MiOnsibGVmdCI6 + eyJvbl9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFsc2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2Us + ImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfSwicmlnaHQiOnsib25fbm90ZXNfb2ZmIjp0cnVlLCJhbGxfbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX3NvdW5kX29mZiI6ZmFs + c2UsInJlc2V0X2FsbF9jb250cm9sbGVycyI6ZmFsc2UsImRhbXBlcl9wZWRhbF9vZmYiOmZhbHNlfX0sImludGVyYWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQi + Onsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNl + LCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZh + bHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19fX1dfV19LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUi + Onsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiOEZFQjdDNTctN0JFQy03RjQ1LUE1RUMtQ0ExRkM5RDBBMEE4IiwiYXVkaW9fc2V0dGlu + Z3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W119LHsiY2xpcF9wbGF5X3NldHRpbmdz + Ijp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiRkFGNDczMDgtN0Q4OS00NTRGLUE1NzItQzJDQ0RBM0QyNkI1IiwiYXVk + aW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W119LHsiY2xpcF9wbGF5 + X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiMkVDNUJEMTUtRjREQy03NDQ1LTkwNDktMDk3QzgwRUY5 + QkRBIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W119LHsi + Y2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiMkVDNUJEMTUtRjREQy03NDQ1LTkwNDkt + MDk3QzgwRUY5QkRBIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90 + cyI6W119LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiMkVDNUJEMTUtRjREQy03 + NDQ1LTkwNDktMDk3QzgwRUY5QkRBIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0 + In19LCJzbG90cyI6W119LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNrIjoiMkVDNUJE + MTUtRjREQy03NDQ1LTkwNDktMDk3QzgwRUY5QkRBIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7ImtpbmQiOiJU + cmFja0lucHV0In19LCJzbG90cyI6W119LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5lIn0sInRyYWNr + IjoiRDVGOTlDQjUtNUY0OC0xQTQwLThEMzEtQjQ5OEFDQTY5MThFIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsib3JpZ2luIjp7 + ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W119LHsiY2xpcF9wbGF5X3NldHRpbmdzIjp7Im1vZGUiOnsia2luZCI6IkV4Y2x1c2l2ZUZvbGxvd2luZ1NjZW5l + In0sInRyYWNrIjoiRDVGOTlDQjUtNUY0OC0xQTQwLThEMzEtQjQ5OEFDQTY5MThFIiwiYXVkaW9fc2V0dGluZ3MiOnt9fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsi + b3JpZ2luIjp7ImtpbmQiOiJUcmFja0lucHV0In19LCJzbG90cyI6W119XSwicm93cyI6W3t9LHt9LHt9LHt9LHt9LHt9LHt9LHt9XSwiY2xpcF9wbGF5X3NldHRpbmdz + Ijp7InN0YXJ0X3RpbWluZyI6eyJraW5kIjoiUXVhbnRpemVkIiwibnVtZXJhdG9yIjoxLCJkZW5vbWluYXRvciI6MX0sInN0b3BfdGltaW5nIjp7ImtpbmQiOiJMaWtl + Q2xpcFN0YXJ0VGltaW5nIn0sImF1ZGlvX3NldHRpbmdzIjp7InJlc2FtcGxlX21vZGUiOnsia2luZCI6IlByb2plY3REZWZhdWx0In0sInRpbWVfc3RyZXRjaF9tb2Rl + Ijp7ImtpbmQiOiJWYXJpU3BlZWQifSwiY2FjaGVfYmVoYXZpb3IiOnsia2luZCI6IkRpcmVjdEZyb21EaXNrIn19fSwiY2xpcF9yZWNvcmRfc2V0dGluZ3MiOnsic3Rh + cnRfdGltaW5nIjp7ImtpbmQiOiJMaWtlQ2xpcFBsYXlTdGFydFRpbWluZyJ9LCJzdG9wX3RpbWluZyI6eyJraW5kIjoiTGlrZUNsaXBSZWNvcmRTdGFydFRpbWluZyJ9 + LCJkdXJhdGlvbiI6eyJraW5kIjoiUXVhbnRpemVkIiwibnVtZXJhdG9yIjoxLCJkZW5vbWluYXRvciI6MX0sInBsYXlfc3RhcnRfdGltaW5nIjp7ImtpbmQiOiJJbmhl + cml0In0sInBsYXlfc3RvcF90aW1pbmciOnsia2luZCI6IkluaGVyaXQifSwidGltZV9iYXNlIjp7ImtpbmQiOiJEZXJpdmVGcm9tUmVjb3JkVGltaW5nIn0sImxvb3Bl + ZCI6dHJ1ZSwibGVhZF90ZW1wbyI6ZmFsc2UsIm1pZGlfc2V0dGluZ3MiOnsicmVjb3JkX21vZGUiOnsia2luZCI6Ik92ZXJkdWIifSwiZGV0ZWN0X2Rvd25iZWF0Ijpm + YWxzZSwiZGV0ZWN0X2lucHV0IjpmYWxzZSwiYXV0b19xdWFudGl6ZSI6ZmFsc2UsImNsaXBfc2V0dGluZ3MiOnsic291cmNlX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQi + Onsib25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNl + LCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZh + bHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19LCJzZWN0aW9uX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQiOnsi + b25fbm90ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJk + YW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNl + LCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19LCJsb29wX3Jlc2V0X3NldHRpbmdzIjp7ImxlZnQiOnsib25fbm90 + ZXNfb2ZmIjpmYWxzZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNldF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJf + cGVkYWxfb2ZmIjpmYWxzZX0sInJpZ2h0Ijp7Im9uX25vdGVzX29mZiI6dHJ1ZSwiYWxsX25vdGVzX29mZiI6ZmFsc2UsImFsbF9zb3VuZF9vZmYiOmZhbHNlLCJyZXNl + dF9hbGxfY29udHJvbGxlcnMiOmZhbHNlLCJkYW1wZXJfcGVkYWxfb2ZmIjpmYWxzZX19LCJpbnRlcmFjdGlvbl9yZXNldF9zZXR0aW5ncyI6eyJsZWZ0Ijp7Im9uX25v + dGVzX29mZiI6ZmFsc2UsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVzZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVy + X3BlZGFsX29mZiI6ZmFsc2V9LCJyaWdodCI6eyJvbl9ub3Rlc19vZmYiOnRydWUsImFsbF9ub3Rlc19vZmYiOmZhbHNlLCJhbGxfc291bmRfb2ZmIjpmYWxzZSwicmVz + ZXRfYWxsX2NvbnRyb2xsZXJzIjpmYWxzZSwiZGFtcGVyX3BlZGFsX29mZiI6ZmFsc2V9fX19LCJhdWRpb19zZXR0aW5ncyI6eyJkZXRlY3RfZG93bmJlYXQiOmZhbHNl + LCJkZXRlY3RfaW5wdXQiOmZhbHNlfX0sImNvbW1vbl90ZW1wb19yYW5nZSI6eyJtaW4iOjgwLjAsIm1heCI6MjAwLjB9fSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoi + Rm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 315 50 752 735 + FXID {C173E1EB-8FC8-5049-9E38-C3C06683D64D} + WAK 0 0 + > + > + + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7JDutPAAAAAAAAAAAAAAAABk1kj6+cIc9AAAAAAAAQD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {D48B1A1D-A84E-794C-8B7D-739504D7AF07} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6ygy48e6cyOkw3ST8GgVU/AAAAADwDGT/88ls7uB4FPwAAQD8AAAAAAACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {9CB8C9CE-C3D4-1C48-B32F-DBAB67143802} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd4AAAAA3DcFPkw3ST93vl8/AAAAAJaxJD6PLkc8SOH6PgAAQD8AAAAAAACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {D2866135-B602-2C44-ABAC-A65B4B097672} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd7GwgNA2UgDPwAAAAAAAAAAAAAAAGA7Az7dTw4+ZmZmPgAAgD9nhBg+AAAAAAAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {6149E6C8-4A6D-0C41-A84C-705F8E0C4433} + WAK 0 0 + > + > + + > + + > + + > + + + > + > + +> diff --git a/plugin-reaper-realearn/resources/template-projects/pot-preview/pot-preview.RPP b/plugin-reaper-realearn/resources/template-projects/pot-preview/pot-preview.RPP new file mode 100644 index 0000000..f7c629c --- /dev/null +++ b/plugin-reaper-realearn/resources/template-projects/pot-preview/pot-preview.RPP @@ -0,0 +1,174 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 2 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 0 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN preview + RENDER_FMT 0 2 44100 + RENDER_1X 0 + RENDER_RANGE 1 0 0 16 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + RENDER_NORMALIZE 1024 0.199526 0.988553 0 0.01 1 1 + TIMELOCKMODE 0 + TEMPOENVLOCKMODE 0 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {EA473FFF-476B-E64C-8079-60BBA84EE352} + WAK 0 0 + > + + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/02-reasynth-recorded-midi-glued-k5P__kfQ.mid b/plugin-reaper-realearn/resources/test-projects/02-reasynth-recorded-midi-glued-k5P__kfQ.mid new file mode 100644 index 0000000..00891df Binary files /dev/null and b/plugin-reaper-realearn/resources/test-projects/02-reasynth-recorded-midi-glued-k5P__kfQ.mid differ diff --git a/plugin-reaper-realearn/resources/test-projects/02-reasynth-recorded-midi-glued-k5p-kfq-mid-ZSTEK6N1.mid b/plugin-reaper-realearn/resources/test-projects/02-reasynth-recorded-midi-glued-k5p-kfq-mid-ZSTEK6N1.mid new file mode 100644 index 0000000..435e1a0 Binary files /dev/null and b/plugin-reaper-realearn/resources/test-projects/02-reasynth-recorded-midi-glued-k5p-kfq-mid-ZSTEK6N1.mid differ diff --git a/plugin-reaper-realearn/resources/test-projects/Untitled.RPP b/plugin-reaper-realearn/resources/test-projects/Untitled.RPP new file mode 100644 index 0000000..fd63f9c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/Untitled.RPP @@ -0,0 +1,371 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 8 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.506024 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABcRgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS44IiwiaWQiOiJSeERyZHhpWCIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiYmFuay1sZWZ0Iiwic291cmNlIjp7ImNoYW5uZWwi + OjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJz + b3ItbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaC1sZWZ0Iiwic291cmNlIjp7ImNoYW5uZWwi + OjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmst + cmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291cmNlIjp7ImNoYW5u + ZWwiOjMsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNvbDEvcm93MS9wYWQiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwi + dmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJwbGF5aW5nIjo0MywicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInNj + aGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJyZWNvcmRpbmciOjg2LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NH19 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY29sMS9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cxL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjowLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY29sMS9yb3cxL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93Mi9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjo0LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsicGF1c2Vk + IjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJlbXB0eSI6MCwic2NoZWR1 + bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic3RvcHBlZCI6MX19fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cy + L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cyL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0 + ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cy + L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93My9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0 + ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRf + c3RhcnQiOjY0LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInN0b3BwZWQiOjEsInBh + dXNlZCI6MSwicGxheWluZyI6NDMsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwicmVjb3JkaW5nIjo4Nn19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3czL3BhZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3czL2tub2IiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6 + eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3Rv + cCI6NjQsInJlY29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJz + Y2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJwYXVzZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImNvbDEvcm93NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzQva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY29sMi9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1 + ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlf + c3RvcCI6NjQsImVtcHR5IjowLCJyZWNvcmRpbmciOjg2LCJwbGF5aW5nIjo0Mywic3RvcHBlZCI6MSwicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9w + Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wy + L3JvdzEva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29s + Mi9yb3cyL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQi + OiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic3RvcHBlZCI6MSwic2NoZWR1 + bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJw + bGF5aW5nIjo0MywicGF1c2VkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzIva25vYiIsInNv + dXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3czL3BhZCIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlz + Y3JldGUiLCJ2YWx1ZSI6eyJzdG9wcGVkIjoxLCJwYXVzZWQiOjEsInJlY29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9m + b3JfcmVjb3JkX3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsImVt + cHR5IjowfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjb2wyL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjb2wyL3JvdzMva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3c0L3BhZCIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUi + Onsic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVzZWQi + OjEsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NH19fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y29sMi9yb3c0L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3c0L2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjox + MywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNvbDIvcm93NC9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzEvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6 + MiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9m + b3JfcmVjb3JkX3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNj + aGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBhdXNlZCI6MSwicmVjb3JkaW5nIjo4Niwic3RvcHBlZCI6MSwiZW1wdHkiOjB9fX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93MS9wYWQi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93MS9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjoz + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93MS9rbm9i + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzIvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjox + fSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3Ai + OjY0LCJyZWNvcmRpbmciOjg2LCJwbGF5aW5nIjo0MywicGF1c2VkIjoxLCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9y + X3BsYXlfc3RhcnQiOjY0LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjR9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9wYWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93Mi9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9rbm9iIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzMvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVl + ZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2LCJwbGF5aW5nIjo0MywicGF1c2VkIjoxLCJl + bXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3Jf + cmVjb3JkX3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjb2wzL3JvdzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3czL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6ImNvbDMvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFi + bGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjQzLCJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQs + ImVtcHR5IjowLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2 + LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjR9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93 + NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzQva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9y + b3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJG + cm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwbGF5aW5nIjo0Mywic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJlbXB0eSI6MCwic2No + ZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInBhdXNlZCI6MSwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zv + cl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzEva25vYiIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cyL3BhZCIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3Jl + dGUiLCJ2YWx1ZSI6eyJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2 + NCwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwicGF1c2VkIjoxLCJzdG9wcGVk + IjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjb2w0L3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzIva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjcsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjb2w0L3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3czL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsi + cGxheWluZyI6NDMsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwicmVjb3JkaW5nIjo4 + Niwic3RvcHBlZCI6MSwicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NH19fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29s + NC9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwi + Y2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDQvcm93My9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUs + ImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwYXVzZWQiOjEsInJl + Y29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3Jf + cmVjb3JkX3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzQvcGFkIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjN9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3c0L2tub2Ii + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX1dLCJjb250cm9sbGVyQ3VzdG9tRGF0YSI6eyJjb21wYW5pb24iOnsiY29udHJvbHMiOlt7ImhlaWdodCI6NTAsImlkIjoi + Y29sNC9yb3cxL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93MS9rbm9iIiwiY29sNC9yb3cxL3Bh + ZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMS9yb3cyL2tub2IiLCJsYWJlbE9uZSI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDEvcm93Mi9rbm9iIiwiY29sMS9yb3cyL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1 + MCwieCI6MTAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2w0L3JvdzIva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9w + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFw + cGluZ3MiOlsiY29sNC9yb3cyL2tub2IiLCJjb2w0L3JvdzIvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4Ijo0MDAsInkiOjEwMH0seyJoZWlnaHQi + OjUwLCJpZCI6ImNvbDEvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wxL3JvdzEva25vYiIsImNv + bDEvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjEwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93MS9rbm9iIiwibGFi + ZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wzL3JvdzEva25vYiIsImNvbDMvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUi + LCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + YWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzEva25vYiIsImNvbDIvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MH0seyJo + ZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93Mi9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzIva25v + YiIsImNvbDIvcm93Mi9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MTAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMy9yb3cyL2tu + b2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvcm93Mi9rbm9iIiwiY29sMy9yb3cyL3BhZCJdLCJzaGFwZSI6 + ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wyL3JvdzMva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3czL2tub2IiLCJjb2wyL3JvdzMvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoyMDAs + InkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJj + b2wzL3JvdzMva25vYiIsImNvbDMvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MjAwfSx7ImhlaWdodCI6NTAsImlkIjoi + Y29sNC9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93NC9rbm9iIiwiY29sNC9yb3c0L3Bh + ZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wxL3JvdzQva25vYiIsImxhYmVsT25lIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVy + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3c0L2tub2IiLCJjb2wxL3JvdzQvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgi + OjUwLCJ4IjoxMDAsInkiOjMwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93NC9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVU + b3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJt + YXBwaW5ncyI6WyJjb2wyL3JvdzQva25vYiIsImNvbDIvcm93NC9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MzAwfSx7ImhlaWdo + dCI6NTAsImlkIjoiY29sMy9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + ImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvcm93NC9rbm9iIiwi + Y29sMy9yb3c0L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wxL3JvdzMva25vYiIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3czL2tub2IiLCJjb2wxL3JvdzMvcGFkIl0sInNoYXBlIjoiY2ly + Y2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDQvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w0L3JvdzMva25vYiIsImNvbDQvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjQwMCwieSI6 + MjAwfSx7ImhlaWdodCI6NTAsImlkIjoiYmFuay1yaWdodCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiYW5r + LXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjUwfSx7ImhlaWdodCI6NTAsImlkIjoiYmFuay1sZWZ0IiwibGFiZWxPbmUi + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxv + d0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImJhbmstbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6MCwi + eSI6NTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJhNzhiMjc3ZS1jZmJmLTRiMmItOWNjNi0xYTU1MGFlYjg3ZmQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gtbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6MCwieSI6MjUwfSx7ImhlaWdodCI6NTAsImlk + IjoiZTMxMmQyYTItZWNmMS00MTg5LTk1YWYtNDE3NGM0M2E3NTBjIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpb + ImNoLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjI1MH0seyJoZWlnaHQiOjUwLCJpZCI6ImN1cnNvci1sZWZ0IiwibGFi + ZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImN1cnNvci1sZWZ0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUw + LCJ4IjowLCJ5IjoxNTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjdXJzb3ItcmlnaHQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNp + emVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFw + cGluZ3MiOlsiY3Vyc29yLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjE1MH1dLCJncmlkRGl2aXNpb25Db3VudCI6Miwi + Z3JpZFNpemUiOjUwfX0sIm1haW5QcmVzZXRBdXRvTG9hZE1vZGUiOiJmb2N1c2VkLWZ4IiwiaW5zdGFuY2VQcmVzZXRMaW5rQ29uZmlnIjp7ImxpbmtzIjpbeyJmeCI6 + eyJuYW1lIjoiVlNUaTogUmVhU3ludGggKENvY2tvcykifSwicHJlc2V0SWQiOiJyZWFzeW50aCJ9XX19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 145 31 752 675 + FXID {AF3F9A1B-1535-6C4E-AFF7-A87A333F2EF2} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6Y9dw+w2qkPawcWj5Ei2w+3SSGPur5dD/s+eM9pptEPgaBFT/3dKo/SOH6PnNoET8AAAAAIzq+PAAAAAA= + AAAQAAAA + > + FLOATPOS 881 323 372 469 + FXID {314C4C58-4C0E-D442-9B7F-4D4351001B02} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AAAQAAAA + > + FLOATPOS 822 28 574 394 + FXID {30D2E5F4-E52D-B24F-AA62-6851192D0F3F} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/controller-conditional-activation.rpp b/plugin-reaper-realearn/resources/test-projects/controller-conditional-activation.rpp new file mode 100644 index 0000000..510f31f --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/controller-conditional-activation.rpp @@ -0,0 +1,404 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.39236500518455 -0.464 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAsBIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNSIsImlkIjoieWtoSlVLNlAiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI4OTJjMzlhOC0zODk5LTQ4YWYtODNmMy1kYTBiM2M3YzJmNzMiLCJu + YW1lIjoiVm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZX0sImFjdGl2YXRpb25UeXBlIjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjAsImlzT24iOmZhbHNlfX0seyJpZCI6ImU2ZmVj + MzY1LTU5YzQtNGJmNi04ODdiLWU2NzBlYTVlMWU5ZSIsIm5hbWUiOiJNYXN0ZXIgdm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwi + ZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImFjdGl2YXRpb25UeXBlIjoibW9kaWZpZXJzIiwi + bW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjAsImlzT24iOnRydWV9fSx7ImlkIjoiZmE2NDI0ODYtY2Y2MS00NTdkLWI3MTgtYjhhZTVmYjgyZmJmIiwi + bmFtZSI6IlBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjQsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJhY3RpdmF0aW9uVHlwZSI6Im1vZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjowLCJpc09u + IjpmYWxzZX19LHsiaWQiOiI4OGJkYjEwZi05NWQ5LTRlN2EtODU1ZC1hNjllZTlmYjg1NjgiLCJuYW1lIjoiTWFzdGVyIHBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWV9LCJhY3RpdmF0aW9uVHlwZSI6Im1vZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjowLCJpc09uIjp0cnVlfX0seyJp + ZCI6IjAyZWVlM2NlLWMxNWUtNDUyNi05OGY3LTQ2MDlmNzkzNzJjMiIsIm5hbWUiOiJUb2dnbGUgbWFpbiBwYXJhbSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJlY2U1OWU3Ni0xNTU1LTRlNmUtYjkxMy01ZjQ0MzdjZjgyYTkiLCJuYW1lIjoiNiIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfSwibW9kZSI6eyJ0eXBlIjoxfSwidGFyZ2V0 + Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fV0sImNvbnRyb2xsZXJNYXBw + aW5ncyI6W3siaWQiOiIzMDBjMDdhOC1jYjUwLTQ2OGItOTI0OS0wZTU5ODU5ZDI0NmEiLCJuYW1lIjoiRW5jb2RlciAxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJhY3RpdmF0 + aW9uVHlwZSI6ImVlbCIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjowLCJpc09uIjpmYWxzZX0sImVlbENvbmRpdGlvbiI6InkgPSBwMSA9PSAwIn0s + eyJpZCI6IjA5NDI5M2IwLTZmM2UtNDhiOC04NTNhLWNjMjRjZTU0YzE4NiIsIm5hbWUiOiJFbmNvZGVyIDEgc2hpZnRlZCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlfSwiYWN0aXZhdGlvblR5cGUiOiJtb2RpZmllcnMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsicGFyYW1JbmRleCI6MCwiaXNPbiI6dHJ1ZX0s + ImVlbENvbmRpdGlvbiI6InkgPSBwMSA+IDAifSx7ImlkIjoiNzkxNDQ2ODQtZWMwZi00Zjg2LWFhMzQtMDA3ZjFlZWRhY2E3IiwibmFtZSI6IkludGVybmFsIHNoaWZ0 + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5k + ZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInBhcmFtSW5kZXgiOjEwMCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjlmM2QwZTg2LTEwYTktNGIyNS05OTU4LTcwOWNmMmQ1ZmEwZSIsIm5h + bWUiOiJOb3JtYWwgYnV0dG9uIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiYjY4ZjM0NGQtYTZlYi00ZDY5LWIy + ODgtNDEzZmU5ZmRlMmZmIiwibmFtZSI6IlR1cmJvIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1pblByZXNzTWlsbGlzIjozMDAsIm1heFByZXNzTWlsbGlzIjo1MDAwLCJmaXJlTW9k + ZSI6InR1cmJvIn0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjhhNTk2MWEyLWVmYWEtNDJkYy05 + YTE4LTQ1NDliYzg5Y2I2YiIsIm5hbWUiOiJEeW5hIHRyYWNrIHZvbHVtZXIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjMsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9u + IjoicDIgKiA5IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZX19LHsiaWQiOiIwMjE1YWU3Mi0wMzk3LTQxMmItOGFlMS01ZjdiMzE4MmQ4NzQiLCJuYW1lIjoiRHluYSB0cmFjayBjaG9vc2VyIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInBhcmFtSW5kZXgiOjEwMSwiY29udHJvbEVsZW1lbnRJbmRleCI6MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlfX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJtb2JiaSIsImFjdGl2ZU1haW5QcmVzZXRJZCI6Im1haW5vIiwicGFyYW1ldGVycyI6 + eyIwIjp7Im5hbWUiOiJNYWluIGJhbmsifX0sImNvbnRyb2xsZXJQYXJhbWV0ZXJzIjp7IjIiOnsibmFtZSI6Ik1vYmJpLUZha3RvciJ9fX0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {C0288EB8-381F-4AB5-9DF8-F8C3FAB1526B} + PARM_TCP 0 100 101 + WAK 0 0 + > + > + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/ec4-advanced-test.RPP b/plugin-reaper-realearn/resources/test-projects/ec4-advanced-test.RPP new file mode 100644 index 0000000..1c58b9a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/ec4-advanced-test.RPP @@ -0,0 +1,204 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 65792 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 1.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1.69336888157165 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACiFwAAAQAAAAAAEAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4wIiwiaWQiOiJBMmNFQ04waiIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsImNvbnRyb2xEZXZpY2VJZCI6IjM2IiwiZmVlZGJhY2tE + ZXZpY2VJZCI6IjM0IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6InFlWE13d0RLbnVJdC1CTVJP + QVhvSiIsIm5hbWUiOiJMQ0QgLSBEaXNwbGF5IHRyYWNrIHZvbHVtZSIsInNvdXJjZSI6eyJ0eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJMdWEiLCJtaWRpU2NyaXB0 + IjoibG9jYWwgZWM0ID0gcmVxdWlyZShcImNvbXBhcnRtZW50XCIpLmVjNFxubG9jYWwgdGV4dCA9IHkgb3IgXCJcIlxucmV0dXJuIHtcblx0YWRkcmVzcyA9IDAsXG5c + dG1lc3NhZ2VzID0ge1xuICAgICAgICBlYzQud3JpdGVfdG90YWxfZGlzcGxheShlYzQuZGVmYXVsdF9kZXZpY2VfaWQsIDAsIDIwLCB0ZXh0KSxcbiAgICAgICAgZWM0 + LnNob3dfdG90YWxfZGlzcGxheShlYzQuZGVmYXVsdF9kZXZpY2VfaWQsIHRydWUpLFxuICAgIH1cbn1cblxuIiwiYnV0dG9uSW5kZXgiOjAsImJ1dHRvbkRlc2lnbiI6 + eyJiYWNrZ3JvdW5kIjp7ImtpbmQiOiJDb2xvciJ9LCJmb3JlZ3JvdW5kIjp7ImtpbmQiOiJOb25lIn0sInN0YXRpY190ZXh0IjoiIn19LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudGV4dF92YWx1ZSB9fSIsImZlZWRiYWNrVHlwZSI6 + MX0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6InRoaXMiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImxlYXJuYWJsZSI6ZmFsc2UsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImFjdGl2YXRpb25UeXBlIjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5k + ZXgiOjAsImlzT24iOnRydWV9fSx7ImlkIjoiQXBPdzNxYjUyRHZHc3NwTV81NlRxIiwibmFtZSI6IkxDRCAtIERpc3BsYXkgdHJhY2sgbmFtZSIsInNvdXJjZSI6eyJ0 + eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJMdWEiLCJtaWRpU2NyaXB0IjoibG9jYWwgZWM0ID0gcmVxdWlyZShcImNvbXBhcnRtZW50XCIpLmVjNFxubG9jYWwgdGV4 + dCA9IHkgb3IgXCJcIlxucmV0dXJuIHtcblx0YWRkcmVzcyA9IDAsXG5cdG1lc3NhZ2VzID0ge1xuICAgICAgICBlYzQud3JpdGVfdG90YWxfZGlzcGxheShlYzQuZGVm + YXVsdF9kZXZpY2VfaWQsIDAsIDIwLCB0ZXh0KSxcbiAgICAgICAgZWM0LnNob3dfdG90YWxfZGlzcGxheShlYzQuZGVmYXVsdF9kZXZpY2VfaWQsIHRydWUpLFxuICAg + IH1cbn1cblxuIiwiYnV0dG9uSW5kZXgiOjAsImJ1dHRvbkRlc2lnbiI6eyJiYWNrZ3JvdW5kIjp7ImtpbmQiOiJDb2xvciJ9LCJmb3JlZ3JvdW5kIjp7ImtpbmQiOiJO + b25lIn0sInN0YXRpY190ZXh0IjoiIn19LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24i + OiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo0NCwiZnhBbmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJsZWFybmFibGUiOmZhbHNlLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJhY3RpdmF0aW9uVHlwZSI6 + Im1vZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjowLCJpc09uIjpmYWxzZX19LHsiaWQiOiI0VG9jTk41cGNWNHcxaTE1NV8wRmQiLCJu + YW1lIjoiRW5jb2RlciAxLzEgLSBTZXQgdHJhY2sgdm9sdW1lIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiYnV0dG9uSW5kZXgiOjAsImJ1dHRvbkRlc2lnbiI6eyJiYWNrZ3JvdW5kIjp7ImtpbmQiOiJDb2xvciJ9LCJmb3Jl + Z3JvdW5kIjp7ImtpbmQiOiJOb25lIn0sInN0YXRpY190ZXh0IjoiIn19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoi + Yk1FNEsxT2F2T1phLXZfUWF1UXRxIiwibmFtZSI6IkVuY29kZXIgMS8xIC0gU2hvcnRseSBzaG93IHZhbHVlIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiYnV0dG9uSW5kZXgiOjAsImJ1dHRvbkRlc2lnbiI6eyJiYWNrZ3Jv + dW5kIjp7ImtpbmQiOiJDb2xvciJ9LCJmb3JlZ3JvdW5kIjp7ImtpbmQiOiJOb25lIn0sInN0YXRpY190ZXh0IjoiIn19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiIvLyBEZWJvdW5jZTogVHVybnMgdGFyZ2V0IG9uIGFz + IHNvb24gYXMgeW91IHN0YXJ0IG1vdmluZyB0aGUga25vYi9mYWRlci5UdXJucyBpdCBvZmYgc2hvcnRseSBhZnRlciB5b3Ugc3RvcCBtb3ZpbmcuIEdvb2QgZm9yIHRo + ZSBtb3VzZSB0YXJnZXQgdG8gc2ltdWxhdGUgYSBtb3VzZSBkcmFnIGJ1dCBhbHNvIGdvb2Qgd2l0aCBcIlRyYWNrOiBTZXQgYXV0b21hdGlvbiB0b3VjaCBzdGF0ZVwi + IHRhcmdldCB0byBpbXBsZW1lbnQgYXV0b21hdGljIHRvdWNoL3JlbGVhc2Ugd2hlbiB5b3UgZG9uJ3QgaGF2ZSBhIHRvdWNoLXNlbnNpdGl2ZSBmYWRlciBhdCBoYW5k + LlxuXG4vLyBQYXJhbWV0ZXJzXG5cbnRpbWVvdXRfaW5fbXMgPSA1MDA7XG5cbi8vIENvZGVcblxueSA9IHkgIT0gMSA/IChcbiAgICAxXG4pIDogKFxuICAgIHJlbF90 + aW1lIDwgdGltZW91dF9pbl9tcyA/IG5vbmUgOiBzdG9wKDApXG4pOyJ9LCJ0YXJnZXQiOnsidHlwZSI6NjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sIm1haW5Db21tb25MdWEiOiJsb2NhbCBtb2R1bGUgPSB7fVxuXG5tb2R1bGUuZWM0ID0ge31cblxubG9jYWwgZnVuY3Rp + b24gYXBwZW5kX2xpc3QodDEsIHQyKVxuXHRsb2NhbCBuMSA9ICN0MVxuXHRmb3IgaSA9IDEsICN0MiBkb1xuXHRcdHQxW24xICsgaV0gPSB0MltpXVxuXHRlbmRcbmVu + ZFxuXG5sb2NhbCBmdW5jdGlvbiBlbmNvZGVfYXNfdHdvX2J5dGVzKGJ5dGU6IG51bWJlcilcblx0bG9jYWwgbmliYmxlX2hpID0gYml0MzIucnNoaWZ0KGJ5dGUsIDQp + XG5cdGxvY2FsIG5pYmJsZV9sbyA9IGJpdDMyLmJhbmQoYnl0ZSwgMHgwRilcblx0cmV0dXJuIHsgMHgyMCArIG5pYmJsZV9oaSwgMHgxMCArIG5pYmJsZV9sbyB9XG5l + bmRcblxubG9jYWwgZnVuY3Rpb24gdG9fdGV4dF9kYXRhKHRleHQ6IHN0cmluZywgbnVtX2NoYXJzOiBudW1iZXIpXG5cdGxvY2FsIGJ5dGVzID0ge31cblx0aWYgdGV4 + dCB+PSBuaWwgdGhlblxuXHRcdGZvciBpID0gMSwgbnVtX2NoYXJzIGRvXG5cdFx0XHRsb2NhbCBjID0gc3RyaW5nLmJ5dGUodGV4dCwgaSlcblx0XHRcdC0tIEZvciBu + b3csIG9ubHkgYWNjZXB0IHRoZSBmb2xsb3dpbmcgaW5wdXQ6IFNQQUNFICgweDIwKSB0byBcIlpcIiAoMHg1QSkgYW5kIFwiYVwiICgweDYxKSB0byBcInpcIiAoMHg3 + QSlcblx0XHRcdGxvY2FsIGlzX3ZhbGlkID0gYyB+PSBuaWwgYW5kICgoYyA+PSAweDIwIGFuZCBjIDw9IDB4NUEpIG9yIChjID49IDB4NjEgYW5kIGMgPCAweDdBKSlc + blx0XHRcdGlmIG5vdCBpc192YWxpZCB0aGVuXG5cdFx0XHRcdC0tIFVzZSBzcGFjZSBhcyBmYWxsYmFjayBjaGFyYWN0ZXJcblx0XHRcdFx0YyA9IDB4MjBcblx0XHRc + dGVuZFxuICAgICAgICAgICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAweDREKVxuXHRcdFx0YXBwZW5kX2xpc3QoYnl0ZXMsIGVuY29kZV9hc190d29fYnl0ZXMoYykpXG5c + dFx0ZW5kXG5cdGVuZFxuXHRyZXR1cm4gYnl0ZXNcbmVuZFxuXG5sb2NhbCBwcmVmaXggPSB7IDB4RjAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4NEUsIDB4MkMgfVxubG9j + YWwgc3VmZml4ID0geyAweEY3IH1cblxuLS0tIFdyaXRlcyB0ZXh0IHRvIHNpbmdsZSBkaXNwbGF5cy5cbmZ1bmN0aW9uIG1vZHVsZS5lYzQud3JpdGVfY3RybF9kaXNw + bGF5KGRldmljZV9pZDogbnVtYmVyLCBwb3M6IG51bWJlciwgbGVuOiBudW1iZXIsIHRleHQ6IHN0cmluZylcblx0bG9jYWwgbXNnID0ge31cblx0YXBwZW5kX2xpc3Qo + bXNnLCBwcmVmaXgpXG5cdGFwcGVuZF9saXN0KG1zZywgeyAweDEwICsgZGV2aWNlX2lkLCAweDRFLCAweDIyLCAweDEwLCAweDRBIH0pXG4gICAgYXBwZW5kX2xpc3Qo + bXNnLCBlbmNvZGVfYXNfdHdvX2J5dGVzKHBvcykpXG5cdGFwcGVuZF9saXN0KG1zZywgdG9fdGV4dF9kYXRhKHRleHQsIGxlbikpXG5cdGFwcGVuZF9saXN0KG1zZywg + c3VmZml4KVxuXHRyZXR1cm4gbXNnXG5lbmRcblxuLS0tIFdyaXRlcyB0ZXh0IHRvIHRoZSB0b3RhbCBkaXNwbGF5LlxuZnVuY3Rpb24gbW9kdWxlLmVjNC53cml0ZV90 + b3RhbF9kaXNwbGF5KGRldmljZV9pZDogbnVtYmVyLCBwb3M6IG51bWJlciwgbGVuOiBudW1iZXIsIHRleHQ6IHN0cmluZylcblx0bG9jYWwgbXNnID0ge31cblx0YXBw + ZW5kX2xpc3QobXNnLCBwcmVmaXgpXG5cdGFwcGVuZF9saXN0KG1zZywgeyAweDEwICsgZGV2aWNlX2lkLCAweDRFLCAweDIyLCAweDEzLCAweDRBIH0pXG4gICAgYXBw + ZW5kX2xpc3QobXNnLCBlbmNvZGVfYXNfdHdvX2J5dGVzKHBvcykpXG5cdGFwcGVuZF9saXN0KG1zZywgdG9fdGV4dF9kYXRhKHRleHQsIGxlbikpXG5cdGFwcGVuZF9s + aXN0KG1zZywgc3VmZml4KVxuXHRyZXR1cm4gbXNnXG5lbmRcblxuLS0tIFNob3dzIG9yIGhpZGVzIHRoZSB0b3RhbCBkaXNwbGF5LlxuZnVuY3Rpb24gbW9kdWxlLmVj + NC5zaG93X3RvdGFsX2Rpc3BsYXkoZGV2aWNlX2lkOiBudW1iZXIsIHNob3c6IGJvb2xlYW4pXG5cdGxvY2FsIG1zZyA9IHt9XG5cdGFwcGVuZF9saXN0KG1zZywgcHJl + Zml4KVxuXHRhcHBlbmRfbGlzdChtc2csIHsgMHgxMCArIGRldmljZV9pZCwgMHg0RSwgMHgyMiwgaWYgc2hvdyB0aGVuIDB4MTQgZWxzZSAweDE1IH0pXG5cdGFwcGVu + ZF9saXN0KG1zZywgc3VmZml4KVxuXHRyZXR1cm4gbXNnXG5lbmRcblxubW9kdWxlLmVjNC5kZWZhdWx0X2RldmljZV9pZCA9IDB4MEJcblxucmV0dXJuIG1vZHVsZSIs + InBhcmFtZXRlcnMiOnsiMCI6eyJuYW1lIjoiU2hvdyB2YWx1ZSJ9fSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fSwiYWRkaXRpb25hbFVuaXRzIjpb + XX0= + AAAQAAAA + > + FLOAT 292 209 752 674 + FXID {A811AA96-45E5-AD4B-972E-34B4024A9545} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/faderfox-ec4-experiments.RPP b/plugin-reaper-realearn/resources/test-projects/faderfox-ec4-experiments.RPP new file mode 100644 index 0000000..22b36f7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/faderfox-ec4-experiments.RPP @@ -0,0 +1,150 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 65792 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 9 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACOAwAAAQAAAAAAEAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4wLXByZS4yIiwiaWQiOiJNbWJmX01aTiIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNr + Z3JvdW5kIjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsImNvbnRyb2xEZXZpY2VJZCI6IjM2IiwiZmVl + ZGJhY2tEZXZpY2VJZCI6IjM0IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IlFUeTQzZ1k1Vjd1 + a21yTVlzZy10LSIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjExLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwibWlkaVNjcmlwdEtpbmQi + OiJMdWEiLCJvc2NBcmdJbmRleCI6MCwiYnV0dG9uSW5kZXgiOjAsImJ1dHRvbkRlc2lnbiI6eyJiYWNrZ3JvdW5kIjp7ImtpbmQiOiJDb2xvciJ9LCJmb3JlZ3JvdW5k + Ijp7ImtpbmQiOiJOb25lIn0sInN0YXRpY190ZXh0IjoiIn19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7InR5cGUiOjUzLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4 + Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19LCJhZGRpdGlvbmFsVW5pdHMiOltdfQ== + AAAQAAAA + > + FLOAT 524 230 752 674 + FXID {18F0F3DC-443C-B14F-8F84-A4D4B61E92D6} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/faderport-classic.rpp b/plugin-reaper-realearn/resources/test-projects/faderport-classic.rpp new file mode 100644 index 0000000..9ca63ae --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/faderport-classic.rpp @@ -0,0 +1,240 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 16 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAIAAAAiAAAAAQAAAJAkAAAQAAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAQAAAAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAABAAAAAQAAAAMAAAAjAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {5340D3D0-F98A-4D1C-BBAE-0716991F3E70} + WAK 0 0 + > + + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAlQcAAAEAAAAAABAA + eyJsZXRNYXRjaGVkRXZlbnRzVGhyb3VnaCI6ZmFsc2UsImxldFVubWF0Y2hlZEV2ZW50c1Rocm91Z2giOnRydWUsImFsd2F5c0F1dG9EZXRlY3RNb2RlIjp0cnVlLCJz + ZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiY29udHJvbERldmljZUlkIjoiMSIsImZlZWRiYWNrRGV2aWNlSWQiOiIyIiwibWFwcGluZ3MiOlt7Im5hbWUiOiJD + b250cm9sIHRyYWNrIHZvbHVtZSB2aWEgZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MCwiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjAsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOnRydWUsIm1lc3NhZ2UiOjB9LCJtb2RlIjp7InR5cGUiOjAsIm1pblNvdXJjZVZhbHVlIjowLjAsIm1heFNvdXJjZVZhbHVlIjox + LjAsIm1pblRhcmdldFZhbHVlIjowLjAsIm1heFRhcmdldFZhbHVlIjoxLjAsIm1pblRhcmdldEp1bXAiOjAuMCwibWF4VGFyZ2V0SnVtcCI6MS4wLCJtaW5TdGVwU2l6 + ZSI6MC4wMSwibWF4U3RlcFNpemUiOjAuMDEsIm1pblByZXNzTWlsbGlzIjowLCJtYXhQcmVzc01pbGxpcyI6MCwiZWVsQ29udHJvbFRyYW5zZm9ybWF0aW9uIjoiIiwi + ZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6IiIsInJldmVyc2VJc0VuYWJsZWQiOmZhbHNlLCJpZ25vcmVPdXRPZlJhbmdlU291cmNlVmFsdWVzSXNFbmFibGVkIjpm + YWxzZSwicm91bmRUYXJnZXRWYWx1ZSI6ZmFsc2UsInNjYWxlTW9kZUVuYWJsZWQiOmZhbHNlLCJyb3RhdGVJc0VuYWJsZWQiOmZhbHNlfSwidGFyZ2V0Ijp7InR5cGUi + OjIsImNvbW1hbmROYW1lIjpudWxsLCJpbnZvY2F0aW9uVHlwZSI6MCwidHJhY2tHVUlEIjpudWxsLCJ0cmFja05hbWUiOm51bGwsImVuYWJsZU9ubHlJZlRyYWNrSXNT + ZWxlY3RlZCI6ZmFsc2UsImZ4SW5kZXgiOm51bGwsImlzSW5wdXRGeCI6ZmFsc2UsImVuYWJsZU9ubHlJZkZ4SGFzRm9jdXMiOmZhbHNlLCJzZW5kSW5kZXgiOm51bGws + InBhcmFtSW5kZXgiOjAsInNlbGVjdEV4Y2x1c2l2ZWx5IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJu + YW1lIjoiRmVlZGJhY2sgdHJhY2sgdm9sdW1lIHRvIGZhZGVyIiwic291cmNlIjp7InR5cGUiOjAsImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjowLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijp0cnVlLCJtZXNzYWdlIjowfSwibW9kZSI6eyJ0eXBlIjowLCJtaW5Tb3VyY2VWYWx1ZSI6MC4wLCJtYXhTb3VyY2VW + YWx1ZSI6MC4wNjI1MDM4MTQ5MzAxMTA0OCwibWluVGFyZ2V0VmFsdWUiOjAuMCwibWF4VGFyZ2V0VmFsdWUiOjEuMCwibWluVGFyZ2V0SnVtcCI6MC4wLCJtYXhUYXJn + ZXRKdW1wIjoxLjAsIm1pblN0ZXBTaXplIjowLjAxLCJtYXhTdGVwU2l6ZSI6MC4wMSwibWluUHJlc3NNaWxsaXMiOjAsIm1heFByZXNzTWlsbGlzIjowLCJlZWxDb250 + cm9sVHJhbnNmb3JtYXRpb24iOiIiLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiIiwicmV2ZXJzZUlzRW5hYmxlZCI6ZmFsc2UsImlnbm9yZU91dE9mUmFuZ2VT + b3VyY2VWYWx1ZXNJc0VuYWJsZWQiOmZhbHNlLCJyb3VuZFRhcmdldFZhbHVlIjpmYWxzZSwic2NhbGVNb2RlRW5hYmxlZCI6ZmFsc2UsInJvdGF0ZUlzRW5hYmxlZCI6 + ZmFsc2V9LCJ0YXJnZXQiOnsidHlwZSI6MiwiY29tbWFuZE5hbWUiOm51bGwsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0dVSUQiOm51bGwsInRyYWNrTmFtZSI6bnVs + bCwiZW5hYmxlT25seUlmVHJhY2tJc1NlbGVjdGVkIjpmYWxzZSwiZnhJbmRleCI6bnVsbCwiaXNJbnB1dEZ4IjpmYWxzZSwiZW5hYmxlT25seUlmRnhIYXNGb2N1cyI6 + ZmFsc2UsInNlbmRJbmRleCI6bnVsbCwicGFyYW1JbmRleCI6MCwic2VsZWN0RXhjbHVzaXZlbHkiOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2UsImZlZWRi + YWNrSXNFbmFibGVkIjp0cnVlfV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {7C9C41BE-731A-4191-8BD2-661E91BD89D8} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOAT 2228 513 1341 1163 + FXID {67786436-D98E-429A-8935-061C5F7DF9AA} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-11-parameters.rpp b/plugin-reaper-realearn/resources/test-projects/issue-11-parameters.rpp new file mode 100644 index 0000000..33c5c11 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-11-parameters.rpp @@ -0,0 +1,165 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAlg0AAAEAAAAAABAA + eyJsZXRNYXRjaGVkRXZlbnRzVGhyb3VnaCI6ZmFsc2UsImxldFVubWF0Y2hlZEV2ZW50c1Rocm91Z2giOnRydWUsImFsd2F5c0F1dG9EZXRlY3RNb2RlIjp0cnVlLCJz + ZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiY29udHJvbERldmljZUlkIjoiMTAiLCJmZWVkYmFja0RldmljZUlkIjoiMTEiLCJtYXBwaW5ncyI6W3sibmFtZSI6 + Ik9uL29mZiAxIiwic291cmNlIjp7InR5cGUiOjAsImNoYW5uZWwiOjEsIm51bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwibWVzc2FnZSI6MH0sIm1vZGUiOnsidHlwZSI6MiwibWluU291cmNlVmFsdWUiOjAuMCwibWF4U291cmNlVmFsdWUiOjEuMCwibWluVGFyZ2V0VmFsdWUi + OjAuMCwibWF4VGFyZ2V0VmFsdWUiOjEuMCwibWluVGFyZ2V0SnVtcCI6MC4wLCJtYXhUYXJnZXRKdW1wIjoxLjAsIm1pblN0ZXBTaXplIjowLjAxLCJtYXhTdGVwU2l6 + ZSI6MC4wMSwibWluUHJlc3NNaWxsaXMiOjAsIm1heFByZXNzTWlsbGlzIjowLCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiIiLCJlZWxGZWVkYmFja1RyYW5zZm9y + bWF0aW9uIjoiIiwicmV2ZXJzZUlzRW5hYmxlZCI6ZmFsc2UsImlnbm9yZU91dE9mUmFuZ2VTb3VyY2VWYWx1ZXNJc0VuYWJsZWQiOmZhbHNlLCJyb3VuZFRhcmdldFZh + bHVlIjpmYWxzZSwic2NhbGVNb2RlRW5hYmxlZCI6ZmFsc2UsInJvdGF0ZUlzRW5hYmxlZCI6ZmFsc2V9LCJ0YXJnZXQiOnsidHlwZSI6MSwiY29tbWFuZE5hbWUiOm51 + bGwsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0dVSUQiOm51bGwsInRyYWNrTmFtZSI6bnVsbCwiZW5hYmxlT25seUlmVHJhY2tJc1NlbGVjdGVkIjpmYWxzZSwiZnhJ + bmRleCI6MCwiaXNJbnB1dEZ4IjpmYWxzZSwiZW5hYmxlT25seUlmRnhIYXNGb2N1cyI6ZmFsc2UsInNlbmRJbmRleCI6bnVsbCwicGFyYW1JbmRleCI6MCwic2VsZWN0 + RXhjbHVzaXZlbHkiOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tJc0VuYWJsZWQiOnRydWUsInByZXZlbnRFY2hvRmVlZGJhY2siOmZhbHNl + LCJhY3RpdmF0aW9uVHlwZSI6ImFsd2F5cyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjowLCJpc09uIjpmYWxzZX0sIm1vZGlmaWVyQ29uZGl0aW9u + MiI6eyJwYXJhbUluZGV4IjpudWxsLCJpc09uIjpmYWxzZX0sInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4IjowfSwiZWVsQ29u + ZGl0aW9uIjoiIn0seyJuYW1lIjoiT24vb2ZmIDIiLCJzb3VyY2UiOnsidHlwZSI6MCwiY2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtZXNzYWdlIjowfSwibW9kZSI6eyJ0eXBlIjoyLCJtaW5Tb3VyY2VWYWx1ZSI6MC4wLCJtYXhTb3VyY2VWYWx1ZSI6 + MS4wLCJtaW5UYXJnZXRWYWx1ZSI6MC4wLCJtYXhUYXJnZXRWYWx1ZSI6MS4wLCJtaW5UYXJnZXRKdW1wIjowLjAsIm1heFRhcmdldEp1bXAiOjEuMCwibWluU3RlcFNp + emUiOjAuMDEsIm1heFN0ZXBTaXplIjowLjAxLCJtaW5QcmVzc01pbGxpcyI6MCwibWF4UHJlc3NNaWxsaXMiOjAsImVlbENvbnRyb2xUcmFuc2Zvcm1hdGlvbiI6IiIs + ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiIiLCJyZXZlcnNlSXNFbmFibGVkIjpmYWxzZSwiaWdub3JlT3V0T2ZSYW5nZVNvdXJjZVZhbHVlc0lzRW5hYmxlZCI6 + ZmFsc2UsInJvdW5kVGFyZ2V0VmFsdWUiOmZhbHNlLCJzY2FsZU1vZGVFbmFibGVkIjpmYWxzZSwicm90YXRlSXNFbmFibGVkIjpmYWxzZX0sInRhcmdldCI6eyJ0eXBl + IjoxLCJjb21tYW5kTmFtZSI6bnVsbCwiaW52b2NhdGlvblR5cGUiOjAsInRyYWNrR1VJRCI6bnVsbCwidHJhY2tOYW1lIjpudWxsLCJlbmFibGVPbmx5SWZUcmFja0lz + U2VsZWN0ZWQiOmZhbHNlLCJmeEluZGV4IjowLCJpc0lucHV0RngiOmZhbHNlLCJlbmFibGVPbmx5SWZGeEhhc0ZvY3VzIjpmYWxzZSwic2VuZEluZGV4IjpudWxsLCJw + YXJhbUluZGV4IjoxLCJzZWxlY3RFeGNsdXNpdmVseSI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0lzRW5hYmxlZCI6dHJ1ZSwicHJldmVu + dEVjaG9GZWVkYmFjayI6ZmFsc2UsImFjdGl2YXRpb25UeXBlIjoiYWx3YXlzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjAsImlzT24iOmZhbHNl + fSwibW9kaWZpZXJDb25kaXRpb24yIjp7InBhcmFtSW5kZXgiOm51bGwsImlzT24iOmZhbHNlfSwicHJvZ3JhbUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9n + cmFtSW5kZXgiOjB9LCJlZWxDb25kaXRpb24iOiIifSx7Im5hbWUiOiJWb2x1bWUiLCJzb3VyY2UiOnsidHlwZSI6MCwiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFy + YWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtZXNzYWdlIjowfSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5Tb3VyY2VWYWx1ZSI6MC4w + LCJtYXhTb3VyY2VWYWx1ZSI6MS4wLCJtaW5UYXJnZXRWYWx1ZSI6MC4wLCJtYXhUYXJnZXRWYWx1ZSI6MS4wLCJtaW5UYXJnZXRKdW1wIjowLjAsIm1heFRhcmdldEp1 + bXAiOjEuMCwibWluU3RlcFNpemUiOjAuMDEsIm1heFN0ZXBTaXplIjowLjAxLCJtaW5QcmVzc01pbGxpcyI6MCwibWF4UHJlc3NNaWxsaXMiOjAsImVlbENvbnRyb2xU + cmFuc2Zvcm1hdGlvbiI6IiIsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiIiLCJyZXZlcnNlSXNFbmFibGVkIjpmYWxzZSwiaWdub3JlT3V0T2ZSYW5nZVNvdXJj + ZVZhbHVlc0lzRW5hYmxlZCI6ZmFsc2UsInJvdW5kVGFyZ2V0VmFsdWUiOmZhbHNlLCJzY2FsZU1vZGVFbmFibGVkIjpmYWxzZSwicm90YXRlSXNFbmFibGVkIjpmYWxz + ZX0sInRhcmdldCI6eyJ0eXBlIjoyLCJjb21tYW5kTmFtZSI6bnVsbCwiaW52b2NhdGlvblR5cGUiOjAsInRyYWNrR1VJRCI6bnVsbCwidHJhY2tOYW1lIjpudWxsLCJl + bmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOmZhbHNlLCJmeEluZGV4IjowLCJpc0lucHV0RngiOmZhbHNlLCJlbmFibGVPbmx5SWZGeEhhc0ZvY3VzIjpmYWxzZSwi + c2VuZEluZGV4IjpudWxsLCJwYXJhbUluZGV4IjowLCJzZWxlY3RFeGNsdXNpdmVseSI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0lzRW5h + YmxlZCI6dHJ1ZSwicHJldmVudEVjaG9GZWVkYmFjayI6ZmFsc2UsImFjdGl2YXRpb25UeXBlIjoicHJvZ3JhbSIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUlu + ZGV4IjowLCJpc09uIjp0cnVlfSwibW9kaWZpZXJDb25kaXRpb24yIjp7InBhcmFtSW5kZXgiOjEsImlzT24iOnRydWV9LCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFt + SW5kZXgiOjUsInByb2dyYW1JbmRleCI6Mzl9LCJlZWxDb25kaXRpb24iOiJ5ID0gcDEgPiAwLjAgJiYgcDIgPT0gMC4wIn1dLCJwYXJhbWV0ZXJzIjp7IjAiOnsidmFs + dWUiOjEuMCwibmFtZSI6bnVsbH19fQ== + AAAQAAAA + > + FLOATPOS 1517 567 1373 1408 + FXID {EB254126-9E57-4367-9C99-07C19A39F32C} + PARM_TCP 0 1 + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-131-virtualize-main-mapping-sources.rpp b/plugin-reaper-realearn/resources/test-projects/issue-131-virtualize-main-mapping-sources.rpp new file mode 100644 index 0000000..5dfd4dd --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-131-virtualize-main-mapping-sources.rpp @@ -0,0 +1,714 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAGKUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xNCIsImlkIjoiTlpoT0pUQ3oiLCJjb250cm9sRGV2aWNlSWQiOiI3IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjgiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwiY29udHJvbGxlckdyb3VwcyI6W3siaWQiOiJmYWRlciIsIm5hbWUiOiJGYWRlciJ9LHsiaWQiOiJ2 + LXBvdCIsIm5hbWUiOiJWLVBvdCJ9LHsiaWQiOiJ2LXNlbGVjdCIsIm5hbWUiOiJWLVNlbGVjdCJ9LHsiaWQiOiJmYWRlci10b3VjaCIsIm5hbWUiOiJGYWRlciBUb3Vj + aCJ9LHsiaWQiOiJzZWxlY3QiLCJuYW1lIjoiU2VsZWN0In0seyJpZCI6Im11dGUiLCJuYW1lIjoiTXV0ZSJ9LHsiaWQiOiJzb2xvIiwibmFtZSI6IlNvbG8ifSx7Imlk + IjoicmVjb3JkLXJlYWR5IiwibmFtZSI6IlJlY29yZC1yZWFkeSJ9LHsiaWQiOiJ2LXBvdC1sZWRzIiwibmFtZSI6IlYtUG90IExFRHMifSx7ImlkIjoibGNkIiwibmFt + ZSI6IkxDRCJ9LHsiaWQiOiJtZXRlciIsIm5hbWUiOiJNZXRlciJ9XSwibWFwcGluZ3MiOlt7ImlkIjoiQXJ6UVRucGlrWDV6MXRndmdZZDF4IiwibmFtZSI6IjEiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDR9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6IkExMzkwQTMxLTBFNEYtNDM0MS1CMzAzLTg5RkYyQzlFMEVEMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IlE2dFB2VEcwY1JmTlk2Vmpvc09BSSIsIm5hbWUiOiIyIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA3fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiIwRjg2NEY2RC1CRTMwLTQxMUItODA3NS0yN0EwREVGMTE3OTciLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJRWmRoTHh1SHVMalBBSzU0V0JNVU4iLCJuYW1lIjoiMyIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM1fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJ0eXBlIjo4LCJ0cmFja0dVSUQiOiJGQTBFQ0EwRi1FNThFLTRDQzYtQkREMS1EOUNCRUIxNjc4RTciLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJDVE10NFVDYU03aTdxVUhsVGJIQ3MiLCJuYW1lIjoiNCIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkxfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJ0eXBlIjo3LCJ0cmFja0dVSUQiOiJEMzAwNjI1NS1BOUVELTQ3QjgtODkyQS0yNjEzNEY4NTg3NTEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6Im1haW4vZmFkZXIi + LCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImpvZyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1haW4vZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYWluL2ZhZGVyL3RvdWNoIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1hcmtlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjg0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2VyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJyZWFkIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWFkIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJ3cml0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + Ijoid3JpdGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoLWxlZnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OH0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoLXJpZ2h0Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiYmFuay1sZWZ0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJiYW5rLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo0N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJld2lu + ZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmV3aW5kIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmYXN0LWZ3ZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkyfSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiZmFzdC1md2QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InBsYXkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo5NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InN0b3AiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6InJlY29yZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoicmVjb3JkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjeWNsZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + Ojg2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Inpvb20iLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJzY3J1YiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + InNjcnViIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk4 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImN1cnNvci1yaWdodCIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0IiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjdXJzb3ItdXAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Nn0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci11cCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29yLWRvd24iLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJudWRnZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + bnVkZ2UiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImRyb3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4N30sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImRyb3AiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJlcGxhY2UiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo4OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlcGxhY2UiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImNsaWNrIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjbGljayIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkwfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4Ijoic29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo1NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMiIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMyIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1N30sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImY0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmNSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjU4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImY2Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoic21wdGUtYmVhdHMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + InNtcHRlLWJlYXRzIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJsY2QvYXNzaWdubWVudCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6 + MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC9hc3NpZ25tZW50IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoibGNkL3RpbWVjb2RlIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJk + aXNwbGF5SWQiOjJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1z + ZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMS92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvZmFkZXIvdG91 + Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3NlbGVj + dCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMS9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjoz + LCJjaGFubmVsIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBv + dCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Yt + cG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90 + LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92 + LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L2Jvb3N0LWN1dCIsImdyb3Vw + SWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFs + dWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoMS92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBb + MDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJCMCAzMCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9zb2xvIiwiZ3JvdXBJ + ZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NvbG8i + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6ImNoMS9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjB9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEv + bGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL2xjZC9saW5lMiIsImdyb3VwSWQiOiJs + Y2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRl + cm4iOiJEMCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoMi92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMzfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + Mi9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNX0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDIvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI1fSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gyL3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNl + Ijp7InR5cGUiOjMsImNoYW5uZWwiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi92LXBvdC9jb250cm9sIiwiZ3Jv + dXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBT + aXplIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3dyYXAiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZh + bHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gyL3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvYm9vc3Qt + Y3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJt + aW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVy + biI6IkIwIDMxIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdN + aWRpUGF0dGVybiI6IkIwIDMxIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3Nv + bG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDIvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3JlY29yZC1yZWFkeSIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MSwibGluZSI6 + MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMi9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbGNkL2xpbmUyIiwi + Z3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJy + YXdNaWRpUGF0dGVybiI6IkQwIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2gzL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzR9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2gzL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA2fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoMy9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvZmFkZXIiLCJncm91cElkIjoiZmFk + ZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3YtcG90L2Nv + bnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qv + d3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsi + bWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92 + LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMSBkY2JhXSJ9 + LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJh + d01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NwcmVhZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxOH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjaDMvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTB9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDMvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVh + ZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFk + eSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJ + ZCI6MiwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMv + bGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoyLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2xjZC9saW5lMiIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MzV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MTA3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + Mjd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvZmFkZXIiLCJn + cm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2g0L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiY2hhcmFjdGVyIjo0LCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDQvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDEwIGRjYmFd + In0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoNC92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBb + MDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9zaW5nbGUiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NwcmVh + ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiJjaDQvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTF9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3JlY29yZC1yZWFkeSIsImdyb3VwSWQi + OiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0 + L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6 + MTIsImRpc3BsYXlJZCI6MywibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDQvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9s + aW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVy + Iiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDExIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MzZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MTA4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6Mjh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJj + aDUvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2g1L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiY2hhcmFjdGVyIjo0 + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0 + IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRl + cm4iOiJCMCAzNCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMi + LCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9z + aW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYt + cG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40 + LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1 + L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tdXRlIiwiZ3JvdXBJZCI6Im11 + dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + MTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3JlY29yZC1yZWFk + eSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g1L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3Vy + Y2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NCwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo0LCJsaW5lIjoxfSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g1L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tZXRlci9wZWFrIiwiZ3Jv + dXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTAwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tZXRlci9wZWFrIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjaDYvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlciIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwi + Y2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0 + dGVybiI6IkIwIDM1IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9i + b29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6 + InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6 + MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNi92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvc3ByZWFkIiwi + Z3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3Vy + Y2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g2L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tdXRlIiwi + Z3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Ni9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MTN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2 + L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1fSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g2L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L2xjZC9saW5lMSIsImdyb3VwSWQi + OiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NSwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo1 + LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g2L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tZXRl + ci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTAxIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tZXRlci9w + ZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0 + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNo + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNo + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2VsZWN0IiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Nn0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRl + ciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjoyMiwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6 + eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6 + MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNy92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc2luZ2xl + IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhT + b3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoNy92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1w + b3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTEgZGNiYV0ifSwibW9k + ZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoNy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY2g3L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L2xjZC9saW5l + MSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwi + ZGlzcGxheUlkIjo2LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoNy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTEwIGRjYmFdIn0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNy9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Ytc2VsZWN0IiwiZ3JvdXBJ + ZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgv + di1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6 + ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTExfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4 + L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9zZWxlY3QiLCJncm91cElkIjoi + c2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc2VsZWN0 + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6N30s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoOC9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjoyMywiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvd3JhcCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVk + cyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhT + b3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgv + di1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwi + bW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiJjaDgvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTEg + ZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoOC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyM30sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2g4L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NywibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9sY2QvbGluZTEiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6 + eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo3LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoOC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTEx + IGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoOC9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfV0sImFjdGl2ZUNvbnRyb2xs + ZXJJZCI6Imljb24tcGxhdGZvcm0tbSJ9 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {52C036CB-388C-419E-9828-AA06A57DAEFC} + WAK 0 0 + > + > + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-1468-match-even-inactive-mappings.RPP b/plugin-reaper-realearn/resources/test-projects/issue-1468-match-even-inactive-mappings.RPP new file mode 100644 index 0000000..f057ab1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-1468-match-even-inactive-mappings.RPP @@ -0,0 +1,720 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 65792 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 0.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADOAwAAAQAAAAAAEAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4wLXByZS4yIiwiaWQiOiJsZHF4TG9uMiIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNr + Z3JvdW5kIjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsImNvbnRyb2xEZXZpY2VJZCI6IjE5IiwibWF0 + Y2hFdmVuSW5hY3RpdmVNYXBwaW5ncyI6dHJ1ZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6Ild4 + SjZzQmxwdzk3VmFRTjdQTHpGaiIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2OH0sImJ1dHRvbkluZGV4IjowLCJidXR0b25EZXNp + Z24iOnsiYmFja2dyb3VuZCI6eyJraW5kIjoiQ29sb3IifSwiZm9yZWdyb3VuZCI6eyJraW5kIjoiTm9uZSJ9LCJzdGF0aWNfdGV4dCI6IiJ9fSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImlzRW5hYmxlZCI6ZmFsc2V9XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fSwiYWRkaXRp + b25hbFVuaXRzIjpbXX0= + AAAQAAAA + > + FLOAT 256 96 752 674 + FXID {EF666678-DDCC-7449-A810-BF702E410C0F} + WAK 0 0 + BYPASS 0 0 0 + + PRESETNAME "Program 0" + FLOATPOS 581 13 1035 776 + FXID {199859E4-99A0-7B4E-AD9B-EB90894ECA8A} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-15-melda-vst3.rpp b/plugin-reaper-realearn/resources/test-projects/issue-15-melda-vst3.rpp new file mode 100644 index 0000000..6901e1d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-15-melda-vst3.rpp @@ -0,0 +1,186 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAARAQAAAEAAAAAABAA + eyJsZXRNYXRjaGVkRXZlbnRzVGhyb3VnaCI6ZmFsc2UsImxldFVubWF0Y2hlZEV2ZW50c1Rocm91Z2giOnRydWUsImFsd2F5c0F1dG9EZXRlY3RNb2RlIjp0cnVlLCJz + ZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiY29udHJvbERldmljZUlkIjoiMTAiLCJmZWVkYmFja0RldmljZUlkIjoiMTEiLCJtYXBwaW5ncyI6W3sibmFtZSI6 + IjEiLCJzb3VyY2UiOnsidHlwZSI6MCwiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJtZXNzYWdlIjowfSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5Tb3VyY2VWYWx1ZSI6MC4wLCJtYXhTb3VyY2VWYWx1ZSI6MS4wLCJtaW5UYXJnZXRWYWx1ZSI6MC4wLCJt + YXhUYXJnZXRWYWx1ZSI6MS4wLCJtaW5UYXJnZXRKdW1wIjowLjAsIm1heFRhcmdldEp1bXAiOjEuMCwibWluU3RlcFNpemUiOjAuMDQsIm1heFN0ZXBTaXplIjowLjA0 + LCJtaW5QcmVzc01pbGxpcyI6MCwibWF4UHJlc3NNaWxsaXMiOjAsImVlbENvbnRyb2xUcmFuc2Zvcm1hdGlvbiI6IiIsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24i + OiIiLCJyZXZlcnNlSXNFbmFibGVkIjpmYWxzZSwiaWdub3JlT3V0T2ZSYW5nZVNvdXJjZVZhbHVlc0lzRW5hYmxlZCI6ZmFsc2UsInJvdW5kVGFyZ2V0VmFsdWUiOmZh + bHNlLCJzY2FsZU1vZGVFbmFibGVkIjpmYWxzZSwicm90YXRlSXNFbmFibGVkIjpmYWxzZX0sInRhcmdldCI6eyJ0eXBlIjoxLCJjb21tYW5kTmFtZSI6bnVsbCwiaW52 + b2NhdGlvblR5cGUiOjAsInRyYWNrR1VJRCI6IjE0NzREQ0RGLTMzOUEtNEE4My04OTg5LUY3NTJENzY0MDZGNCIsInRyYWNrTmFtZSI6Ik1VdGlsaXR5IiwiZW5hYmxl + T25seUlmVHJhY2tJc1NlbGVjdGVkIjpmYWxzZSwiZnhJbmRleCI6MCwiaXNJbnB1dEZ4IjpmYWxzZSwiZW5hYmxlT25seUlmRnhIYXNGb2N1cyI6ZmFsc2UsInNlbmRJ + bmRleCI6bnVsbCwicGFyYW1JbmRleCI6MCwic2VsZWN0RXhjbHVzaXZlbHkiOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tJc0VuYWJsZWQi + OnRydWUsInByZXZlbnRFY2hvRmVlZGJhY2siOmZhbHNlfV19 + AAAQAAAA + > + FLOAT 292 549 1373 1408 + FXID {C40511EC-FCC8-45C5-8554-D2EFC48513EF} + WAK 0 0 + > + > + + PRESETNAME "Presets: 0" + FLOATPOS 1941 887 1239 883 + FXID {44191815-2B41-4C1B-A015-BB243BABB414} + PARM_TCP 0:0 + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-1545-takeover-broken.RPP b/plugin-reaper-realearn/resources/test-projects/issue-1545-takeover-broken.RPP new file mode 100644 index 0000000..73c4092 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-1545-takeover-broken.RPP @@ -0,0 +1,156 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 65792 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 2 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 16385 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.37042850573781 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAD4BQAAAQAAAAAAEAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4wIiwiaWQiOiJoN3NPUU9vaSIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsImNvbnRyb2xEZXZpY2VJZCI6IjAiLCJkZWZhdWx0R3Jv + dXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiV0YxelRUa1lLNnF4bG9USWlZaFZMIiwibmFtZSI6IjEiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJidXR0b25JbmRleCI6MCwi + YnV0dG9uRGVzaWduIjp7ImJhY2tncm91bmQiOnsia2luZCI6IkNvbG9yIn0sImZvcmVncm91bmQiOnsia2luZCI6Ik5vbmUifSwic3RhdGljX3RleHQiOiIifX0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsInRha2VvdmVyTW9kZSI6InBpY2t1cC10b2xlcmFudCJ9LCJ0 + YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19 + LHsiaWQiOiI0ZWJZZGF5dVJxTWpOMk9NR2doQVgiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImJ1dHRvbkluZGV4IjowLCJidXR0b25EZXNpZ24iOnsiYmFja2dyb3VuZCI6eyJraW5kIjoiQ29sb3IifSwi + Zm9yZWdyb3VuZCI6eyJraW5kIjoiTm9uZSJ9LCJzdGF0aWNfdGV4dCI6IiJ9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6NSwidGFrZW92ZXJNb2RlIjoicGlja3VwLXRvbGVyYW50In0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19LCJhZGRpdGlvbmFsVW5pdHMiOltdfQ== + AAAQAAAA + > + FLOAT 679 135 752 674 + FXID {4E167F62-6EEA-9148-AAD6-D14AB915DEE1} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-161-instance-track.RPP b/plugin-reaper-realearn/resources/test-projects/issue-161-instance-track.RPP new file mode 100644 index 0000000..0abe89d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-161-instance-track.RPP @@ -0,0 +1,471 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 1 + ZOOM 100 7 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.36279289742106 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACdUgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS45IiwiaWQiOiJsclJXZENVQiIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJwYXMydk1UT1pkS3luZzMtMGZVZDUiLCJuYW1lIjoiQWRqdXN0In0seyJpZCI6IjBic1drWTZ4NzZncDRLLUpmQ01BSSIs + Im5hbWUiOiJQaW4ifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJiVTlXR19vUmc5dk81TV9QMGVhY0ciLCJuYW1lIjoiUGlu + IHNlbGVjdGVkIHRyYWNrIGFzIGluc3RhbmNlIHRyYWNrIiwiZ3JvdXBJZCI6IjBic1drWTZ4NzZncDRLLUpmQ01BSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NDQsInRyYWNrR1VJRCI6InNlbGVj + dGVkIiwiZnhBbmNob3IiOiJpZCIsInRyYWNrVG9vbEFjdGlvbiI6IlBpbkFzSW5zdGFuY2VUcmFjayIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJz + ZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJJRXNDUUNsOU1WaGZfMC1ZWU9nVXMiLCJuYW1lIjoi + U2V0IHNlbGVjdGVkIHRyYWNrIGFzIGluc3RhbmNlIHRyYWNrIiwiZ3JvdXBJZCI6IjBic1drWTZ4NzZncDRLLUpmQ01BSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzIvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NDQsInRyYWNrR1VJRCI6InNl + bGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInRyYWNrVG9vbEFjdGlvbiI6IlNldEFzSW5zdGFuY2VUcmFjayIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ2a2xKdXZDcFVlOHl3THNLTDFpREQiLCJuYW1l + IjoiQ29udHJvbCBpbnN0YW5jZSB0cmFjayB2b2x1bWUiLCJncm91cElkIjoicGFzMnZNVE9aZEt5bmczLTBmVWQ1Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93NC9rbm9iIn0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiaW5zdGFuY2UiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJ5dzhEejdmSDd6UENGaWk5MmUwQ08iLCJuYW1lIjoi + UGluIGZvY3VzZWQgRlggYXMgaW5zdGFuY2UgRlgiLCJncm91cElkIjoiMGJzV2tZNng3NmdwNEstSmZDTUFJIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNvbDIvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo1NCwidHJhY2tHVUlEIjoic2VsZWN0ZWQi + LCJmeEFuY2hvciI6ImZvY3VzZWQiLCJ0cmFja1Rvb2xBY3Rpb24iOiJQaW5Bc0luc3RhbmNlVHJhY2siLCJmeFRvb2xBY3Rpb24iOiJQaW5Bc0luc3RhbmNlRngiLCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiQ3BtSko2MnB1UnJyUkxMbFQ5c1hFIiwibmFtZSI6IlNldCBmb2N1c2VkIEZYIGFzIGluc3RhbmNlIEZYIiwiZ3JvdXBJZCI6IjBic1drWTZ4NzZncDRLLUpmQ01B + SSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQi + OnsidHlwZSI6NTQsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJmb2N1c2VkIiwidHJhY2tUb29sQWN0aW9uIjoiUGluQXNJbnN0YW5jZVRyYWNrIiwi + ZnhUb29sQWN0aW9uIjoiU2V0QXNJbnN0YW5jZUZ4IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlVRYTlPRzV5dmlLOWF5X1FnaEcxUiIsIm5hbWUiOiJDb250cm9sIGZpcnN0IHBhcmFtIG9mIGluc3Rh + bmNlIEZYIiwiZ3JvdXBJZCI6InBhczJ2TVRPWmRLeW5nMy0wZlVkNSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzQva25vYiJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + fSwidGFyZ2V0Ijp7InRyYWNrR1VJRCI6IkJENTFGNUU4LTJENzItNTc0QS1COUY3LTk0OUIzRTZFMzc2RSIsImZ4QW5jaG9yIjoiZm9jdXNlZCIsInBhcmFtVHlwZSI6 + ImluZGV4LW1hbnVhbCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJN + YXBwaW5ncyI6W3siaWQiOiJiYW5rLWxlZnQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1s + ZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImN1cnNvci1sZWZ0Iiwic291cmNlIjp7ImNoYW5uZWwi + OjMsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoLWxlZnQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxl + ZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYmFuay1yaWdodCIsInNvdXJjZSI6eyJjaGFubmVsIjoz + LCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjdXJzb3ItcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1 + cnNvci1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaC1yaWdodCIsInNvdXJjZSI6eyJjaGFu + bmVsIjozLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY29sMS9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVl + ZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwYXVzZWQiOjEsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29y + ZF9zdGFydCI6NjQsInN0b3BwZWQiOjEsInJlY29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQi + OjY0LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjb2wxL3JvdzEva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiY29sMS9yb3cyL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxl + Ijp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9m + b3JfcGxheV9zdG9wIjo2NCwicGF1c2VkIjoxLCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic3RvcHBlZCI6MSwicmVjb3JkaW5nIjo4Niwi + c2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wxL3JvdzIv + a25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cz + L3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9t + VGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBsYXlpbmci + OjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsImVtcHR5IjowLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwicGF1c2Vk + IjoxLCJzdG9wcGVkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wxL3JvdzMva25vYiIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjgsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzMva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3c0L3BhZCIsInNvdXJjZSI6 + eyJjaGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRl + IiwidmFsdWUiOnsiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInBhdXNlZCI6MSwic2No + ZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwbGF5aW5nIjo0Mywic3RvcHBlZCI6 + MX19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY29sMS9yb3c0L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3c0L2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNvbDEvcm93NC9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzEvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEs + Im51bWJlciI6MSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InN0 + b3BwZWQiOjEsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJyZWNv + cmRpbmciOjg2LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjR9fX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIv + cm93MS9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93MS9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hh + cmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIv + cm93MS9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzIvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NSwiY2hh + cmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVj + b3JkX3N0b3AiOjY0LCJlbXB0eSI6MCwicmVjb3JkaW5nIjo4NiwicGxheWluZyI6NDMsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9w + bGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJzdG9wcGVkIjoxLCJwYXVzZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93Mi9wYWQiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93Mi9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93Mi9rbm9iIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzMvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxfSwibW9k + ZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7ImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3Rv + cCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJwbGF5aW5nIjo0MywicGF1c2VkIjoxLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVs + ZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjR9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93My9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6ImNvbDIvcm93My9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93My9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJjb2wyL3JvdzQvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tW + YWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwbGF5aW5nIjo0MywiZW1wdHkiOjAsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zv + cl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9w + bGF5X3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2LCJzdG9wcGVkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzQvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJj + b2wyL3JvdzQva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3c0L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + ImNvbDMvcm93MS9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJr + aW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInN0b3BwZWQiOjEsInBhdXNlZCI6MSwicmVjb3Jk + aW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9y + X3BsYXlfc3RhcnQiOjY0LCJlbXB0eSI6MH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMy9yb3cxL2tub2Ii + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3cxL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93Mi9wYWQi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRU + b0Rpc2NyZXRlIiwidmFsdWUiOnsiZW1wdHkiOjAsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFy + dCI6NjQsInBhdXNlZCI6MSwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcmVjb3Jk + X3N0YXJ0Ijo2NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMy9yb3cyL2tub2IiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3cyL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93My9wYWQiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZh + bHVlIjp7InNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQs + InBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJyZWNvcmRpbmciOjg2LCJzdG9wcGVkIjoxLCJwYXVzZWQiOjF9fX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNvbDMvcm93My9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93My9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MTAsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjb2wzL3JvdzMva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMy9yb3c0L3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1i + ZXIiOjE0LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1 + bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzdG9wcGVkIjoxLCJlbXB0eSI6MCwicGxheWluZyI6NDMsInNjaGVkdWxl + ZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwicGF1c2VkIjoxLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NH19fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3c0 + L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMy9yb3c0L2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFj + dGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93 + NC9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzEvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MywiY2hhcmFj + dGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjQzLCJzY2hlZHVs + ZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwicGF1c2VkIjoxLCJyZWNvcmRpbmciOjg2LCJlbXB0eSI6MCwic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3Ai + OjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjR9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93MS9wYWQiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvcm93MS9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93MS9rbm9iIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzIvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxfSwibW9kZSI6 + eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInN0b3Bw + ZWQiOjEsInJlY29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJw + YXVzZWQiOjEsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjR9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93Mi9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImNvbDQvcm93Mi9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93Mi9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjb2w0L3JvdzMvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1 + ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3Rh + cnQiOjY0LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwYXVzZWQiOjEsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFy + dCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0 + L3JvdzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3czL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNv + bDQvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2lu + ZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InJlY29yZGluZyI6ODYsInBsYXlpbmciOjQzLCJwYXVzZWQiOjEsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9y + X3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3Bs + YXlfc3RvcCI6NjQsInN0b3BwZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvcm93NC9rbm9iIiwi + c291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzQva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fV0sImNvbnRyb2xsZXJDdXN0b21EYXRh + Ijp7ImNvbXBhbmlvbiI6eyJjb250cm9scyI6W3siaGVpZ2h0Ijo1MCwiaWQiOiJjb2w0L3JvdzEva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibWFwcGluZ3MiOlsiY29sNC9yb3cxL2tub2IiLCJjb2w0L3JvdzEvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4Ijo0MDAsInkiOjB9LHsi + aGVpZ2h0Ijo1MCwiaWQiOiJjb2wxL3JvdzIva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3cyL2tu + b2IiLCJjb2wxL3JvdzIvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjEwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDQvcm93Mi9r + bm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w0L3JvdzIva25vYiIsImNvbDQvcm93Mi9wYWQiXSwic2hhcGUi + OiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjQwMCwieSI6MTAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMS9yb3cxL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDEvcm93MS9rbm9iIiwiY29sMS9yb3cxL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MTAw + LCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMy9yb3cxL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNv + bDMvcm93MS9rbm9iIiwiY29sMy9yb3cxL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiY29s + Mi9yb3cxL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDIvcm93MS9rbm9iIiwiY29sMi9yb3cxL3BhZCJd + LCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MjAwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMi9yb3cyL2tub2IiLCJsYWJlbE9uZSI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNp + emVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDIvcm93Mi9rbm9iIiwiY29sMi9yb3cyL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwi + eCI6MjAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wzL3JvdzIva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGlu + Z3MiOlsiY29sMy9yb3cyL2tub2IiLCJjb2wzL3JvdzIvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjozMDAsInkiOjEwMH0seyJoZWlnaHQiOjUw + LCJpZCI6ImNvbDIvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJl + bFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzMva25vYiIsImNvbDIv + cm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MjAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMy9yb3czL2tub2IiLCJsYWJl + bE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvcm93My9rbm9iIiwiY29sMy9yb3czL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIs + IndpZHRoIjo1MCwieCI6MzAwLCJ5IjoyMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2w0L3JvdzQva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibWFwcGluZ3MiOlsiY29sNC9yb3c0L2tub2IiLCJjb2w0L3JvdzQvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4Ijo0MDAsInkiOjMwMH0s + eyJoZWlnaHQiOjUwLCJpZCI6ImNvbDEvcm93NC9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQi + OnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wxL3JvdzQv + a25vYiIsImNvbDEvcm93NC9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjEwMCwieSI6MzAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMi9yb3c0 + L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjow + LCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDIvcm93NC9rbm9iIiwiY29sMi9yb3c0L3BhZCJdLCJzaGFw + ZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MjAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wzL3JvdzQva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjow + LCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNv + bnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3c0L2tub2IiLCJjb2wzL3JvdzQvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4Ijoz + MDAsInkiOjMwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDEvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXpl + Q29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6 + WyJjb2wxL3JvdzMva25vYiIsImNvbDEvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjEwMCwieSI6MjAwfSx7ImhlaWdodCI6NTAsImlk + IjoiY29sNC9yb3czL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdv + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93My9rbm9iIiwiY29sNC9yb3cz + L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjoyMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJiYW5rLXJpZ2h0IiwibGFiZWxPbmUiOnsi + YW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0Jv + dHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImJhbmstcmlnaHQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6NTAsIngiOjUwMCwi + eSI6NTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJiYW5rLWxlZnQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5l + ZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiYmFu + ay1sZWZ0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4IjowLCJ5Ijo1MH0seyJoZWlnaHQiOjUwLCJpZCI6ImE3OGIyNzdlLWNmYmYtNGIyYi05Y2M2 + LTFhNTUwYWViODdmZCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsi + YW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaC1sZWZ0Il0sInNoYXBlIjoicmVjdGFu + Z2xlIiwid2lkdGgiOjUwLCJ4IjowLCJ5IjoyNTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJlMzEyZDJhMi1lY2YxLTQxODktOTVhZi00MTc0YzQzYTc1MGMiLCJsYWJlbE9u + ZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJl + bG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gtcmlnaHQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6NTAsIngiOjUw + MCwieSI6MjUwfSx7ImhlaWdodCI6NTAsImlkIjoiY3Vyc29yLWxlZnQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3Mi + OlsiY3Vyc29yLWxlZnQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6NTAsIngiOjAsInkiOjE1MH0seyJoZWlnaHQiOjUwLCJpZCI6ImN1cnNvci1yaWdodCIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjdXJzb3ItcmlnaHQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0 + aCI6NTAsIngiOjUwMCwieSI6MTUwfV0sImdyaWREaXZpc2lvbkNvdW50IjoyLCJncmlkU2l6ZSI6NTB9fSwicGFyYW1ldGVycyI6eyIwIjp7InZhbHVlIjowLjI4NjAw + MTJ9LCIxIjp7InZhbHVlIjowLjE5NTk5OTk0fX0sInRhZ3MiOlsibWFpbiJdLCJpbnN0YW5jZVRyYWNrIjp7ImFkZHJlc3MiOiJCeUlkIiwiaWQiOiI5QUQ4OTkwRC0z + QUMyLTNDNDItOEI3OC1FQUYxNzc1Nzc3MTIifSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiQnlJZCIsImNoYWluIjp7ImFkZHJlc3MiOiJUcmFjayIsInRyYWNrIjp7 + ImFkZHJlc3MiOiJCeUlkIiwiaWQiOiJCRDUxRjVFOC0yRDcyLTU3NEEtQjlGNy05NDlCM0U2RTM3NkUifSwiY2hhaW4iOiJOb3JtYWwifSwiaWQiOiJFREJGRjZCNC00 + Mzc5LTZFNDEtQUFFMC1GNTBFMDA1NTE3QUUifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 465 58 752 675 + FXID {EDBFF6B4-4379-6E41-AAE0-F50E005517AE} + PARM_TCP 0:_Main_p1__Param_1 1:_Main_p2__Param_2 + WAK 0 0 + > + > + + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd7ANh5AF7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 105 269 372 469 + FXID {CEDF7363-C598-104C-8A59-BBCEBEA07986} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd4CAMA/F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 762 280 372 469 + FXID {50D55363-824E-3648-8401-893D8284842E} + WAK 0 0 + > + > + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {B52EA6B0-18BE-5241-919E-E1AE6419C4CC} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-1619-instance-tags.RPP b/plugin-reaper-realearn/resources/test-projects/issue-1619-instance-tags.RPP new file mode 100644 index 0000000..5508304 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-1619-instance-tags.RPP @@ -0,0 +1,247 @@ + + RIPPLE 0 0 + GROUPOVERRIDE 0 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + TCPUIFLAGS 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 65792 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 3.5 + ZOOM 120 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 0 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + RENDER_TRIM 0.000001 0.000001 0 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 0 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 1 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACpBgAAAQAAAAAAAAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiJ4Q0dNTjk1QiIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsIndhbnRzS2V5Ym9hcmRJbnB1dCI6dHJ1ZSwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkE0UDhYV1dCSFVkU1UwTnQtMzhVNiIsIm5hbWUiOiJUb2dnbGUg + aW5zdGFuY2UgYSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiYnV0dG9uSW5kZXgiOjAsImJ1dHRvbkRlc2lnbiI6eyJiYWNrZ3JvdW5kIjp7ImtpbmQiOiJDb2xv + ciJ9LCJmb3JlZ3JvdW5kIjp7ImtpbmQiOiJOb25lIn0sInN0YXRpY190ZXh0IjoiIn19LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjM4LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwiaW5zdGFuY2VUYWdLaW5kIjoiSW5zdGFuY2VUYWdzIiwi + dGFncyI6WyJhIl0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJWSmpoYUdMemFEeGM2Yzd2MUs2cnQiLCJuYW1lIjoi + VG9nZ2xlIGluc3RhbmNlIGIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2Nn0sImJ1dHRvbkluZGV4IjowLCJidXR0b25EZXNpZ24iOnsiYmFja2dyb3VuZCI6eyJraW5k + IjoiQ29sb3IifSwiZm9yZWdyb3VuZCI6eyJraW5kIjoiTm9uZSJ9LCJzdGF0aWNfdGV4dCI6IiJ9fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjozOCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sImluc3RhbmNlVGFnS2luZCI6Ikluc3RhbmNl + VGFncyIsInRhZ3MiOlsiYiJdLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImFjdGl2ZUluc3RhbmNlVGFncyI6WyJhMiIsImEx + IiwiYjEiLCJhIiwiYiJdLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19LCJhZGRpdGlvbmFsVW5pdHMiOltdfQ== + AAAAAAAA + > + FLOAT 1099 146 752 674 + FXID {B558CF04-95B7-E84C-BEDB-3E25771880BD} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAnAgAAAQAAAAAAAAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiI2ZTBhenpubCIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9s + bGVyR3JvdXAiOnt9LCJ0YWdzIjpbImExIiwiZ3VpdGFyIiwicmh5dGhtIiwiZHJ1bXMiLCJwaWFubyJdLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + LCJ0YWdzIjpbImEiXSwiYWRkaXRpb25hbFVuaXRzIjpbeyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiI3VmJzTHZROCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFj + a2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0SXNSdW5uaW5nIiwibGl2ZXNPblVwcGVyRmxvb3IiOmZhbHNlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0 + Q29udHJvbGxlckdyb3VwIjp7fSwidGFncyI6WyJhMiJdLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19XX0= + AFByb2dyYW0gMQAAAAAA + > + FLOATPOS 578 121 752 674 + FXID {4D97881F-E85B-3941-94BC-828D91B9E332} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAgAQAAAQAAAAAAAAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiJkZDEyZjNmIiwibmFtZSI6Ik1haW4iLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQi + OiJPbmx5SWZCYWNrZ3JvdW5kUHJvamVjdElzUnVubmluZyIsImxpdmVzT25VcHBlckZsb29yIjpmYWxzZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sInRhZ3MiOlsiYjEiXSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fSwidGFncyI6WyJiIl0sImFkZGl0aW9uYWxVbml0cyI6W119 + AFByb2dyYW0gMQAAAAAA + > + FLOATPOS 888 153 752 674 + FXID {8FE1C00F-91EF-F442-8D11-BD6ED457C006} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-1620-unit-tags.RPP b/plugin-reaper-realearn/resources/test-projects/issue-1620-unit-tags.RPP new file mode 100644 index 0000000..00eabb1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-1620-unit-tags.RPP @@ -0,0 +1,328 @@ + + RIPPLE 0 0 + GROUPOVERRIDE 0 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + TCPUIFLAGS 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 65792 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 0.5 + ZOOM 120 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 0 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + RENDER_TRIM 0.000001 0.000001 0 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 0 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 1 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACbEQAAAQAAAAAAAAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiJ4Q0dNTjk1QiIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsIndhbnRzS2V5Ym9hcmRJbnB1dCI6dHJ1ZSwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkE0UDhYV1dCSFVkU1UwTnQtMzhVNiIsIm5hbWUiOiJUb2dnbGUg + aW5pdGlhdGluZyB1bml0IChwcmV2ZW50ZWQpIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6Nzd9LCJidXR0b25JbmRleCI6MCwiYnV0dG9uRGVzaWduIjp7ImJhY2tncm91 + bmQiOnsia2luZCI6IkNvbG9yIn0sImZvcmVncm91bmQiOnsia2luZCI6Ik5vbmUifSwic3RhdGljX3RleHQiOiIifX0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNp + emUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjcsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJpbnN0YW5jZVRhZ0tpbmQi + OiJJbnN0YW5jZVRhZ3MiLCJ0YWdzIjpbIm1haW4iXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Ikl6ODllLWlaZlFy + UXNuazQzemRkVyIsIm5hbWUiOiJUb2dnbGUgdW5pdCBhIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJidXR0b25JbmRleCI6MCwiYnV0dG9uRGVzaWduIjp7ImJh + Y2tncm91bmQiOnsia2luZCI6IkNvbG9yIn0sImZvcmVncm91bmQiOnsia2luZCI6Ik5vbmUifSwic3RhdGljX3RleHQiOiIifX0sIm1vZGUiOnsidHlwZSI6MiwibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjcsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJpbnN0YW5jZVRh + Z0tpbmQiOiJJbnN0YW5jZVRhZ3MiLCJ0YWdzIjpbImEiXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlZKamhhR0x6 + YUR4YzZjN3YxSzZydCIsIm5hbWUiOiJUb2dnbGUgdW5pdCBiIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjZ9LCJidXR0b25JbmRleCI6MCwiYnV0dG9uRGVzaWduIjp7 + ImJhY2tncm91bmQiOnsia2luZCI6IkNvbG9yIn0sImZvcmVncm91bmQiOnsia2luZCI6Ik5vbmUifSwic3RhdGljX3RleHQiOiIifX0sIm1vZGUiOnsidHlwZSI6Miwi + bWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjcsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJpbnN0YW5j + ZVRhZ0tpbmQiOiJJbnN0YW5jZVRhZ3MiLCJ0YWdzIjpbImIiXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJ0YWdzIjpbIm1h + aW4iXSwiYWN0aXZlSW5zdGFuY2VUYWdzIjpbImEyIiwiYTEiLCJiMSIsImEiLCJiIl0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0sImFkZGl0aW9u + YWxVbml0cyI6W3sidmVyc2lvbiI6IjIuMTguMiIsImlkIjoiWnI0VjZnZGYiLCJuYW1lIjoiR3VpdGFyIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsIndhbnRzS2V5Ym9hcmRJbnB1dCI6dHJ1ZSwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6Im5RUGVHTkdXaGp2eHdFdUhERU1iYSIsIm5hbWUiOiJHdWl0YXIg + dm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtl + eXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODZ9LCJidXR0b25JbmRleCI6MCwiYnV0dG9uRGVzaWduIjp7ImJhY2tncm91bmQiOnsia2luZCI6IkNvbG9yIn0s + ImZvcmVncm91bmQiOnsia2luZCI6Ik5vbmUifSwic3RhdGljX3RleHQiOiIifX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiMkM1QTFBQUItQjE0Ny0wQjQ5LUFGNDEtQ0QzNTg0QzZCRDAwIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJY + In0sImluc3RhbmNlVGFnS2luZCI6Ikluc3RhbmNlVGFncyIsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwidGFncyI6WyJhIl0s + Imluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0seyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiI5WW9VNTNFYiIsIm5hbWUiOiJQaWFubyIsInN0YXlBY3Rp + dmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0SXNSdW5uaW5nIiwibGl2ZXNPblVwcGVyRmxvb3IiOmZhbHNlLCJ3YW50c0tl + eWJvYXJkSW5wdXQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJnYjQ0NjlidnlSMklG + N1F6QzM3LVAiLCJuYW1lIjoiUGlhbm8gdm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODZ9LCJidXR0b25JbmRleCI6MCwiYnV0dG9uRGVzaWduIjp7ImJhY2tn + cm91bmQiOnsia2luZCI6IkNvbG9yIn0sImZvcmVncm91bmQiOnsia2luZCI6Ik5vbmUifSwic3RhdGljX3RleHQiOiIifX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAu + MDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiOTRGNEE3NkQtNEY1MC0xMjRELTkyRjgt + MDgyODcxMERCRjEyIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic29sb0JlaGF2aW9y + IjoxLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4Ijow + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwiaW5zdGFuY2VUYWdLaW5kIjoiSW5zdGFuY2VUYWdzIiwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJ0YWdzIjpbImIiXSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fV0sImN1c3RvbURhdGEiOnsiYXBw + Ijp7ImZ1bGxTY3JlZW4iOmZhbHNlLCJ3aW5kb3dCb3VuZHMiOnsiaGVpZ2h0Ijo3OTIuMCwid2lkdGgiOjE0NDAuMCwieCI6MjQwLjAsInkiOjE1Ny4wfX19fQ== + AAAAAAAA + > + FLOAT 564 105 752 674 + FXID {B558CF04-95B7-E84C-BEDB-3E25771880BD} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAnAgAAAQAAAAAAAAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiI2ZTBhenpubCIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9s + bGVyR3JvdXAiOnt9LCJ0YWdzIjpbImExIiwiZ3VpdGFyIiwicmh5dGhtIiwiZHJ1bXMiLCJwaWFubyJdLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + LCJ0YWdzIjpbImEiXSwiYWRkaXRpb25hbFVuaXRzIjpbeyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiI3VmJzTHZROCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFj + a2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0SXNSdW5uaW5nIiwibGl2ZXNPblVwcGVyRmxvb3IiOmZhbHNlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0 + Q29udHJvbGxlckdyb3VwIjp7fSwidGFncyI6WyJhMiJdLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19XX0= + AFByb2dyYW0gMQAAAAAA + > + FLOATPOS 578 121 752 674 + FXID {4D97881F-E85B-3941-94BC-828D91B9E332} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAgAQAAAQAAAAAAAAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xOC4yIiwiaWQiOiJkZDEyZjNmIiwibmFtZSI6Ik1haW4iLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQi + OiJPbmx5SWZCYWNrZ3JvdW5kUHJvamVjdElzUnVubmluZyIsImxpdmVzT25VcHBlckZsb29yIjpmYWxzZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sInRhZ3MiOlsiYjEiXSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fSwidGFncyI6WyJiIl0sImFkZGl0aW9uYWxVbml0cyI6W119 + AFByb2dyYW0gMQAAAAAA + > + FLOATPOS 888 153 752 674 + FXID {8FE1C00F-91EF-F442-8D11-BD6ED457C006} + WAK 0 0 + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-178-action-track.rpp b/plugin-reaper-realearn/resources/test-projects/issue-178-action-track.rpp new file mode 100644 index 0000000..8d55703 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-178-action-track.rpp @@ -0,0 +1,302 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA+SQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4yIiwiaWQiOiI4djBsSHI0QiIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjUwN2Y5MDRiLTdiZGMtNDM0Ny1iNWVmLWU1ODNiOTQyOTE3YSIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJn + ZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0Iiwid2l0aFRyYWNrIjp0cnVlLCJ0cmFja0dVSUQiOiI3OTdERDc5Qi01NTg5LTRGODItOEZGOC1BREI2QkJEN0I5 + NEIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJv + bGxlck1hcHBpbmdzIjpbeyJpZCI6IjBjMDI5MzYzLTcxYzctNDExNS1iOGUwLTcyNDVhYzFiNmQ0YiIsIm5hbWUiOiJFbmNvZGVyIDEvMSIsInNvdXJjZSI6eyJjaGFu + bmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAxLzEiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifX0seyJpZCI6IjI3NTIxZWYwLTVjZjgtNDVl + OS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUiOiJFbmNvZGVyIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MywiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgxZDMtZDYwNy00MzI5LTlkOTEtYTZiODg4YjBmYWFmIiwibmFtZSI6IkJ1dHRvbiAxLzIiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6IjU0YTA2NmUy + LTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNoYXJhY3RlciI6 + MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYjgyZDc3NTItZDIyOS00NDhmLWJkNDItYWY2MWI0NTllNDgxIiwibmFtZSI6IkJ1dHRvbiAxLzMiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6 + ImEzYzI3YzAyLWM1MmQtNDcxYy1iNzA2LTI3YjYyZTJhNWRiZSIsIm5hbWUiOiJFbmNvZGVyIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoz + fX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQzNi05MTM0LTQ4OGE2ZmViMWZiZiIsIm5hbWUiOiJFbmNvZGVyIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNTBmM2Q1NmQtMGY2My00MDg2LTk1OTItYjNjZTkxM2RiMzgxIiwibmFtZSI6IkJ1 + dHRvbiAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiMDM5Zjc5OWItNTM3Zi00Mjc3LWFiYzEtMDNjNTA2ODI3MDgzIiwi + bmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjQ1YmJiZDVkLTM0ZjctNGFiOC1iNDk2LWVlYjljZjYwMmE3OCIsIm5hbWUiOiJFbmNvZGVyIDIvMyIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTctMjA1NTc2 + NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImQyODQ5ZWM3LWM4OTUtNDRlZS1iZmZhLTM0ZWVmNTBmOTc3YyIsIm5hbWUiOiJFbmNvZGVyIDIvNCIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiODAxOGMyNTktNmY3Ni00MTMzLTkw + NDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1dHRvbiAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNv + ZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiMTYwMTNjNDgtMmE0 + OS00NjcyLWJmM2EtOTVlYWUwNWVmM2E3IiwibmFtZSI6IkJ1dHRvbiAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjY0MGViMmE0LTczNWQtNGQ5MS1hZTViLTYzNjcxZGZhYjVkNyIsIm5h + bWUiOiJFbmNvZGVyIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiMmUx + ODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo5LCJjaGFyYWN0 + ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6ImM4NjVjNTAzLTI1ZmMtNGI4MS1iYmM4LTA3MzExMDVh + MjY1NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19 + LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzktOWI2NC1hOTA5ZTU1OTM1NGIiLCJuYW1lIjoiQnV0dG9uIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2It + OTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQxMzEtYmVkOS00YmE3LWE2MDQtNDM1Y2YwNDM2YzkxIiwibmFtZSI6IkJ1dHRvbiAzLzQiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiM2I1ZmMx + ZTgtNDJkYi00NTgxLTk0N2UtNWRmYTNkM2MwYmY0IiwibmFtZSI6IkVuY29kZXIgNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3Rl + ciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJmZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291 + cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0s + eyJpZCI6ImEzODQzNWMwLTNhYzQtNDE3NC1iMWIxLTc4MWVmZjc5ODhjNCIsIm5hbWUiOiJFbmNvZGVyIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjEzLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5MTQ1YjhhNy0wMjFlLTQ2ZTktYmMyNC01ODhiNWI3MGUzY2QiLCJuYW1lIjoiQnV0 + dG9uIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMxMjU4NGEz + IiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiNTVhYTIzYjAtYjgyMC00ZWJlLTliMTItOWZlNDA4NDJlZjI3IiwibmFtZSI6IkVuY29kZXIgNC80Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJhOWUzLTI4OGItNGY0Yi1hMGFl + LTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjI3YmYyMTUyLWU3ZjQtNDVlNS05Yjk4LWRjYWU4OTY1YjkxZiIsIm5hbWUiOiJMZWZ0 + IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxNn19LHsiaWQiOiJlZTlkZjU5MC02MWNiLTQ3NDYtYTMxMy03NzM1NmU1OGIzNWUiLCJuYW1lIjoiUmlnaHQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1i + ZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN319LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRm + NGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fX0seyJpZCI6ImE1N2EyNzg3LTM5MTktNDZmYi04ZmRkLTE3ZmI4M2Y0YmFmMSIsIm5hbWUiOiJS + aWdodCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjE5fX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJtaWRpLWZpZ2h0ZXItdHdpc3RlciJ9 + AAAQAAAA + > + FLOAT 1709 307 1436 1514 + FXID {510CB0FB-1503-4C41-8F27-A127DC6D7581} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-184-discrete-processing-2.rpp b/plugin-reaper-realearn/resources/test-projects/issue-184-discrete-processing-2.rpp new file mode 100644 index 0000000..7400607 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-184-discrete-processing-2.rpp @@ -0,0 +1,283 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA1AEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4zIiwiaWQiOiJsbV9Gb3czOCIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiYmI5NTdhYmQtNzQ5OC00MWY2LWJiZDgtZGE3MmY0YjBiNzE3IiwibmFtZSI6IjEiLCJzb3Vy + Y2UiOnsidHlwZSI6MiwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoxNCwidHJhY2tHVUlEIjoiQTE1NTYzNkYtNEUyRi00MkM1LTk4RjgtQUJFNUFERTIxMzNBIiwi + ZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {EA1B77AB-D097-4E83-A7AB-1470AA65CB28} + WAK 0 0 + > + > + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-184-discrete-processing.rpp b/plugin-reaper-realearn/resources/test-projects/issue-184-discrete-processing.rpp new file mode 100644 index 0000000..e823085 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-184-discrete-processing.rpp @@ -0,0 +1,260 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAAwIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4yIiwiaWQiOiJfRndiTGp3ZiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiNDI3MDUwZWEtNGQyMS00NzMxLTg4YTQtNTZkM2I2NWM0OTEyIiwibmFtZSI6IjEiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjMzMzMzMzMzMzMzMzMzMzMsIm1heFRhcmdldFZhbHVlIjowLjY2NjY2NjY2NjY2NjY2NjYsIm1pblN0ZXBTaXplIjot + MC4xMiwibWF4U3RlcFNpemUiOi0wLjEyfSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XX0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {8047CE23-89C3-475C-BE23-736C8864DF3F} + WAK 0 0 + > + > + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-188-auto-load-instance-fx.RPP b/plugin-reaper-realearn/resources/test-projects/issue-188-auto-load-instance-fx.RPP new file mode 100644 index 0000000..04f2029 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-188-auto-load-instance-fx.RPP @@ -0,0 +1,380 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 8 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.506024 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABcRgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS44IiwiaWQiOiJSeERyZHhpWCIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiYmFuay1sZWZ0Iiwic291cmNlIjp7ImNoYW5uZWwi + OjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJz + b3ItbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaC1sZWZ0Iiwic291cmNlIjp7ImNoYW5uZWwi + OjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmst + cmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291cmNlIjp7ImNoYW5u + ZWwiOjMsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNvbDEvcm93MS9wYWQiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwi + dmFsdWUiOnsic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicGF1c2VkIjoxLCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRf + c3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJlbXB0eSI6MCwicmVjb3JkaW5nIjo4Nn19 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY29sMS9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cxL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjowLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY29sMS9yb3cxL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93Mi9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjo0LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsicmVjb3Jk + aW5nIjo4NiwiZW1wdHkiOjAsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3Rv + cCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwic3RvcHBlZCI6MX19fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cy + L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cyL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0 + ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cy + L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93My9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0 + ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRf + c3RhcnQiOjY0LCJlbXB0eSI6MCwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJw + YXVzZWQiOjEsInBsYXlpbmciOjQzLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3czL3BhZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3czL2tub2IiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6 + eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJwYXVz + ZWQiOjEsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJyZWNvcmRp + bmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInN0b3BwZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImNvbDEvcm93NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzQva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY29sMi9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1 + ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwiZW1w + dHkiOjAsInN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInBh + dXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wy + L3JvdzEva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29s + Mi9yb3cyL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQi + OiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJw + bGF5aW5nIjo0MywicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVzZWQiOjEs + ImVtcHR5IjowLCJzdG9wcGVkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzIva25vYiIsInNv + dXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3czL3BhZCIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlz + Y3JldGUiLCJ2YWx1ZSI6eyJzdG9wcGVkIjoxLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJzY2hlZHVsZWRf + Zm9yX3JlY29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwbGF5aW5nIjo0MywicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3Rh + cnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjb2wyL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjb2wyL3JvdzMva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3c0L3BhZCIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUi + OnsicGxheWluZyI6NDMsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInJlY29yZGlu + ZyI6ODYsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NH19fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y29sMi9yb3c0L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3c0L2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjox + MywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNvbDIvcm93NC9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzEvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6 + MiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9m + b3JfcGxheV9zdGFydCI6NjQsImVtcHR5IjowLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwicGxheWluZyI6NDMsInN0b3BwZWQi + OjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwYXVzZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93MS9wYWQi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93MS9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjoz + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93MS9rbm9i + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzIvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjox + fSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0 + Ijo2NCwic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicGxheWluZyI6NDMsImVtcHR5IjowLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRf + Zm9yX3BsYXlfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJwYXVzZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9wYWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93Mi9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9rbm9iIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzMvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVl + ZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzdG9wcGVkIjoxLCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9w + bGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJyZWNvcmRpbmciOjg2LCJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3Jf + cmVjb3JkX3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjb2wzL3JvdzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3czL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6ImNvbDMvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFi + bGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7ImVtcHR5IjowLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQs + InBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9z + dG9wIjo2NCwic3RvcHBlZCI6MSwicGxheWluZyI6NDN9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93 + NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzQva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9y + b3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJG + cm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJlbXB0eSI6MCwic3RvcHBlZCI6MSwicGxheWluZyI6NDMsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJz + Y2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3Ai + OjY0LCJyZWNvcmRpbmciOjg2fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzEva25vYiIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cyL3BhZCIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3Jl + dGUiLCJ2YWx1ZSI6eyJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInBhdXNlZCI6 + MSwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsImVtcHR5IjowLCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3Ai + OjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjb2w0L3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzIva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjcsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjb2w0L3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3czL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsi + cmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVs + ZWRfZm9yX3BsYXlfc3RvcCI6NjQsInBhdXNlZCI6MSwiZW1wdHkiOjAsInN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NH19fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29s + NC9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwi + Y2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDQvcm93My9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUs + ImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2 + LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInN0b3BwZWQiOjEsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInBhdXNlZCI6 + MSwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzQvcGFkIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjN9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3c0L2tub2Ii + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX1dLCJjb250cm9sbGVyQ3VzdG9tRGF0YSI6eyJjb21wYW5pb24iOnsiY29udHJvbHMiOlt7ImhlaWdodCI6NTAsImlkIjoi + Y29sNC9yb3cxL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93MS9rbm9iIiwiY29sNC9yb3cxL3Bh + ZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMS9yb3cyL2tub2IiLCJsYWJlbE9uZSI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDEvcm93Mi9rbm9iIiwiY29sMS9yb3cyL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1 + MCwieCI6MTAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2w0L3JvdzIva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9w + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFw + cGluZ3MiOlsiY29sNC9yb3cyL2tub2IiLCJjb2w0L3JvdzIvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4Ijo0MDAsInkiOjEwMH0seyJoZWlnaHQi + OjUwLCJpZCI6ImNvbDEvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wxL3JvdzEva25vYiIsImNv + bDEvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjEwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93MS9rbm9iIiwibGFi + ZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wzL3JvdzEva25vYiIsImNvbDMvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUi + LCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + YWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzEva25vYiIsImNvbDIvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MH0seyJo + ZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93Mi9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzIva25v + YiIsImNvbDIvcm93Mi9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MTAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMy9yb3cyL2tu + b2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvcm93Mi9rbm9iIiwiY29sMy9yb3cyL3BhZCJdLCJzaGFwZSI6 + ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wyL3JvdzMva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3czL2tub2IiLCJjb2wyL3JvdzMvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoyMDAs + InkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJj + b2wzL3JvdzMva25vYiIsImNvbDMvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MjAwfSx7ImhlaWdodCI6NTAsImlkIjoi + Y29sNC9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93NC9rbm9iIiwiY29sNC9yb3c0L3Bh + ZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wxL3JvdzQva25vYiIsImxhYmVsT25lIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVy + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3c0L2tub2IiLCJjb2wxL3JvdzQvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgi + OjUwLCJ4IjoxMDAsInkiOjMwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93NC9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVU + b3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJt + YXBwaW5ncyI6WyJjb2wyL3JvdzQva25vYiIsImNvbDIvcm93NC9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MzAwfSx7ImhlaWdo + dCI6NTAsImlkIjoiY29sMy9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + ImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvcm93NC9rbm9iIiwi + Y29sMy9yb3c0L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wxL3JvdzMva25vYiIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3czL2tub2IiLCJjb2wxL3JvdzMvcGFkIl0sInNoYXBlIjoiY2ly + Y2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDQvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w0L3JvdzMva25vYiIsImNvbDQvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjQwMCwieSI6 + MjAwfSx7ImhlaWdodCI6NTAsImlkIjoiYmFuay1yaWdodCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiYW5r + LXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjUwfSx7ImhlaWdodCI6NTAsImlkIjoiYmFuay1sZWZ0IiwibGFiZWxPbmUi + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxv + d0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImJhbmstbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6MCwi + eSI6NTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJhNzhiMjc3ZS1jZmJmLTRiMmItOWNjNi0xYTU1MGFlYjg3ZmQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gtbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6MCwieSI6MjUwfSx7ImhlaWdodCI6NTAsImlk + IjoiZTMxMmQyYTItZWNmMS00MTg5LTk1YWYtNDE3NGM0M2E3NTBjIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpb + ImNoLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjI1MH0seyJoZWlnaHQiOjUwLCJpZCI6ImN1cnNvci1sZWZ0IiwibGFi + ZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImN1cnNvci1sZWZ0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUw + LCJ4IjowLCJ5IjoxNTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjdXJzb3ItcmlnaHQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNp + emVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFw + cGluZ3MiOlsiY3Vyc29yLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjE1MH1dLCJncmlkRGl2aXNpb25Db3VudCI6Miwi + Z3JpZFNpemUiOjUwfX0sIm1haW5QcmVzZXRBdXRvTG9hZE1vZGUiOiJmb2N1c2VkLWZ4IiwiaW5zdGFuY2VQcmVzZXRMaW5rQ29uZmlnIjp7ImxpbmtzIjpbeyJmeCI6 + eyJuYW1lIjoiVlNUaTogUmVhU3ludGggKENvY2tvcykifSwicHJlc2V0SWQiOiJyZWFzeW50aCJ9XX19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 524 123 752 675 + FXID {AF3F9A1B-1535-6C4E-AFF7-A87A333F2EF2} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6Y9dw+w2qkPawcWj5Ei2w+3SSGPur5dD/s+eM9pptEPgaBFT/3dKo/SOH6PnNoET8AAAAAIzq+PAAAAAA= + AAAQAAAA + > + FLOATPOS 176 204 372 469 + FXID {314C4C58-4C0E-D442-9B7F-4D4351001B02} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AAAQAAAA + > + FLOATPOS 822 28 574 394 + FXID {30D2E5F4-E52D-B24F-AA62-6851192D0F3F} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd4AAAAAT8k/P6wcWj5Ei2w+3SSGPgAAAAAAAAAApptEPgaBFT/3dKo/SOH6PgAAAAAAAAAAIzq+PAAAAAA= + AAAQAAAA + > + FLOATPOS 680 293 372 469 + FXID {EF642121-0072-8441-B8B5-F4489EF419AE} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-192-track-monitoring-target.rpp b/plugin-reaper-realearn/resources/test-projects/issue-192-track-monitoring-target.rpp new file mode 100644 index 0000000..cfc12ec --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-192-track-monitoring-target.rpp @@ -0,0 +1,138 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACyAQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJQQUYyU01IZCIsImNvbnRyb2xEZXZpY2VJZCI6IjIiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJv + bGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoibHREYjFaQl9Wbk9ZMURfZUxsNm9TIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoyNCwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBl + Ijo0OSwiZnhBbmNob3IiOiJpZCIsInRyYWNrTW9uaXRvcmluZ01vZGUiOiJUYXBlU3R5bGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJjbGlwU2xvdCI6eyJhZGRy + ZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {9E989A0D-4335-4E75-A66A-7D833EFCEA22} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-209-param-customization.rpp b/plugin-reaper-realearn/resources/test-projects/issue-209-param-customization.rpp new file mode 100644 index 0000000..eb7d5b1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-209-param-customization.rpp @@ -0,0 +1,151 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 3.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAYAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJrUmZkaGNseiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiRzlMMFBfbkJfWjhKMENnTHVjV3lsIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhcmFjdGVy + IjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjQ5LCJtaW5TdGVw + U2l6ZSI6MC4yNywibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19XSwicGFyYW1ldGVy + cyI6eyIwIjp7Im5hbWUiOiJCYW5rIiwidmFsdWVDb3VudCI6NTAwLCJ2YWx1ZSI6MC41NjV9fX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 32 28 799 815 + FXID {3FCFFFC1-2B8B-6D48-BF6F-2E1F45C56A1A} + + PARM_TCP 0:_Main_p1__Bank + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-219-play-preview.rpp b/plugin-reaper-realearn/resources/test-projects/issue-219-play-preview.rpp new file mode 100644 index 0000000..83c3c46 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-219-play-preview.rpp @@ -0,0 +1,626 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 261 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAXz0AAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcmMuNSIsImlkIjoiTGh4QVFvV0QiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJhOGI5ZWMzNy04MTdiLTRmMzQtODUzZC0xZTcxZmZkYzI2NTMiLCJu + YW1lIjoiU2xvdCAxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5uZWwiOjEwLCJudW1iZXIiOjYwLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfSwibW9k + ZSI6eyJ0eXBlIjoyLCJtaW5UYXJnZXRWYWx1ZSI6MS4wLCJtYXhTdGVwU2l6ZSI6MC4wNSwidHVyYm9SYXRlIjo1MDB9LCJ0YXJnZXQiOnsidHlwZSI6MzEsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6Ijc1OWEyNzY5LTYz + ZGItNDU1ZS04NjBiLTc1NTE5OWFmZWMzMCIsIm5hbWUiOiJTbG90IDEgcmVwZWF0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5u + ZWwiOjEwLCJudW1iZXIiOjYwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJ0dXJib1JhdGUiOjUwMH0sInRhcmdldCI6eyJ0 + eXBlIjozMSwiZnhBbmNob3IiOiJpZCIsInRyYW5zcG9ydEFjdGlvbiI6InJlcGVhdCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiOTRhNjk2NTAtMzQ4NS00MWE4LWFiZmMtOThmMWQ1N2Y4ZDgyIiwibmFtZSI6IlNsb3QgMSB2b2x1bWUiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MSwiY2hhbm5lbCI6MTAsIm51bWJlciI6NjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6OH0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDUsInR1cmJvUmF0ZSI6NTAwfSwi + dGFyZ2V0Ijp7InR5cGUiOjMzLCJmeEFuY2hvciI6ImlkIiwidHJhbnNwb3J0QWN0aW9uIjoicmVwZWF0IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiIwNGQ2ZDM3NS1iMDIyLTRkZWUtODM4OC0zMzJmMTNlYjEwMDkiLCJuYW1lIjoiU2xvdCAxIHNl + ZWsiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MzIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsImZlZWRiYWNrUmVzb2x1dGlvbiI6ImhpZ2giLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiIyNGZkM2Qz + My1mZDg4LTQ0YjUtYTcyNy0wZDFmNmNjMGE5M2YiLCJuYW1lIjoiU2xvdCAyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5uZWwi + OjEwLCJudW1iZXIiOjYyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjozMSwidHJhY2tHVUlE + IjoiOTg4NEJFNDUtNUMyMy00RDZDLUFGQTktMzhDMzNFODdFNUJGIiwiZnhBbmNob3IiOiJpZCIsInRyYW5zcG9ydEFjdGlvbiI6InBsYXlQYXVzZSIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInNsb3RJbmRleCI6MX19LHsiaWQiOiIwMGFhZDNhMi1lZGQ0LTQy + M2QtOGFiYy00OWExMTE3OGIyNGQiLCJuYW1lIjoiU2xvdCAyIHJlcGVhdCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjoxLCJjaGFubmVsIjox + MCwibnVtYmVyIjo2MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4Ijo5fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNSwidHVyYm9SYXRlIjo1MDB9LCJ0YXJnZXQiOnsidHlwZSI6 + MzEsImZ4QW5jaG9yIjoiaWQiLCJ0cmFuc3BvcnRBY3Rpb24iOiJyZXBlYXQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJzbG90SW5kZXgiOjF9fSx7ImlkIjoiMmQwNjQ1NTQtNjUyZC00OTRhLWFjNTUtMjkxMzY4ZjIzYmIzIiwibmFtZSI6IlNsb3QgMiB2b2x1 + bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MSwiY2hhbm5lbCI6MTAsIm51bWJlciI6NjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6OX0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDUsInR1cmJvUmF0 + ZSI6NTAwfSwidGFyZ2V0Ijp7InR5cGUiOjMzLCJmeEFuY2hvciI6ImlkIiwidHJhbnNwb3J0QWN0aW9uIjoicmVwZWF0IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwic2xvdEluZGV4IjoxfX0seyJpZCI6IjU3MzhlMmUyLTBmNzktNDNiNC05ZjM4LWJkZjBjMzE1 + YjIyNSIsIm5hbWUiOiJTbG90IDIgc2VlayIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwicm90YXRlSXNFbmFibGVkIjp0cnVlfSwidGFy + Z2V0Ijp7InR5cGUiOjMyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJmZWVkYmFja1Jlc29s + dXRpb24iOiJoaWdoIiwib3NjQXJnSW5kZXgiOjAsInNsb3RJbmRleCI6MX19LHsiaWQiOiI2YmE2YTIzMy1iN2FkLTQ4ODktYTdjOC00YWM3ZDNhZTZiNGQiLCJuYW1l + IjoiU2xvdCAzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9LCJtb2RlIjp7InR5cGUiOjIsIm1pblRhcmdldFZhbHVlIjoxLjAsIm1h + eFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjMxLCJ0cmFja0dVSUQiOiJFOEMxMDJDMy0zOUIyLTQyQkYtQUFBQy05N0YxQzdDMzZEMzUiLCJmeEFuY2hv + ciI6ImlkIiwidHJhbnNwb3J0QWN0aW9uIjoicGxheVBhdXNlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MCwic2xvdEluZGV4IjoyfX0seyJpZCI6ImMxZDZlYzU4LTQwMTgtNDk0Ny04MzMyLTk2YzI1MGMwMjUyZiIsIm5hbWUiOiJTbG90IDMgdm9sdW1lIiwic291 + cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5uZWwiOjEwLCJudW1iZXIiOjYwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNSwidHVyYm9SYXRlIjo1MDB9 + LCJ0YXJnZXQiOnsidHlwZSI6MzMsImZ4QW5jaG9yIjoiaWQiLCJ0cmFuc3BvcnRBY3Rpb24iOiJyZXBlYXQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJzbG90SW5kZXgiOjJ9fSx7ImlkIjoiMWE3NjE5MTktZmNhNy00YjVlLThjZmEtNjhmOTI0NjIxOTM0Iiwi + bmFtZSI6IlNsb3QgMyByZXBlYXQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MSwiY2hhbm5lbCI6MTAsIm51bWJlciI6NjAsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTB9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJ0dXJib1JhdGUiOjUwMH0sInRhcmdldCI6eyJ0eXBlIjozMSwiZnhBbmNob3IiOiJpZCIsInRy + YW5zcG9ydEFjdGlvbiI6InJlcGVhdCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInNsb3RJ + bmRleCI6Mn19LHsiaWQiOiI4MTk5MjY1YS05NDAwLTRjY2UtOWQ5ZS0xOTc3YWUwYThkMmEiLCJuYW1lIjoiU2xvdCAzIHNlZWsiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsInJvdGF0ZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjozMiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwiZmVlZGJhY2tSZXNvbHV0aW9uIjoiaGlnaCIsIm9zY0FyZ0luZGV4IjowLCJzbG90SW5kZXgiOjJ9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjM0NGNjMDFlLWM2ZWUtNGM5Ny05ODA2LTNlNmM1MzhjOGUxOCIsIm5hbWUiOiJTbG90IDQiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MzEsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJzbG90SW5kZXgiOjN9fSx7Imlk + IjoiMDNlN2FmMWItZWY3NS00YjkzLWI3OWItNWQzM2FlZGQyMGEzIiwibmFtZSI6IlNsb3QgNSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjMxLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MCwic2xvdEluZGV4Ijo0fX0seyJpZCI6IjQ0YTI3YmQyLTU3NTktNGM2NS05NjQ3LWQzYTJiYTRhYzI1NSIsIm5hbWUiOiJMYXN0IHRvdWNoZWQi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxl + bWVudEluZGV4IjozfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQx + MTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxfX0seyJpZCI6ImU1 + NTQzYWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MCwiY2hhcmFj + dGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVmMC01Y2Y4LTQ1ZTktOWM5MC00ZDQ5MDZjZjYzMDQiLCJuYW1lIjoiRW5jb2RlciAx + LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6ImRkMWE4MWQzLWQ2MDctNDMy + OS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2NTMtOWZiYi1lZjkwN2RiMDlkODciLCJuYW1lIjoi + RW5jb2RlciAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6ImI4MmQ3NzUy + LWQyMjktNDQ4Zi1iZDQyLWFmNjFiNDU5ZTQ4MSIsIm5hbWUiOiJCdXR0b24gMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MiwiY2hhcmFjdGVyIjox + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMtYjcwNi0yN2I2MmUyYTVkYmUi + LCJuYW1lIjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6 + IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MywiY2hh + cmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiI4MjhkNDY1YS05YmQzLTQ0MzYtOTEzNC00ODhh + NmZlYjFmYmYiLCJuYW1lIjoiRW5jb2RlciAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo0 + fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJl + ciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI3MTkwZTJhNi1lMDdjLTQ5MjIt + OWJiNC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo1fX0seyJpZCI6IjAzOWY3OTliLTUzN2YtNDI3Ny1hYmMxLTAzYzUwNjgyNzA4MyIsIm5hbWUiOiJCdXR0b24gMi8yIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6NSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiI0NWJiYmQ1ZC0z + NGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjMs + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJCdXR0b24gMi8zIiwic291cmNlIjp7 + ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJk + Mjg0OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2MiLCJuYW1lIjoiRW5jb2RlciAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3LCJjaGFy + YWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUiOiJCdXR0b24gMi80Iiwi + c291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N319 + LHsiaWQiOiIyNWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYiLCJuYW1lIjoiRW5jb2RlciAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + Ijo4LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjE2MDEzYzQ4LTJhNDktNDY3Mi1iZjNhLTk1ZWFlMDVlZjNhNyIsIm5hbWUiOiJCdXR0 + b24gMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5jb2RlciAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04Yjg5LWQ5NmI5NDUwMDAzMiIsIm5h + bWUiOiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJuYW1lIjoiRW5jb2RlciAzLzMiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdhOTMwNWQtOTRhYi00ZmM5LTliNjQtYTkwOWU1 + NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVlZC00NjNiLTk3OWQtYjMwOTE2ZTNiYjNhIiwibmFtZSI6IkVuY29kZXIgMy80 + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjZiOGFkMTMxLWJlZDktNGJh + Ny1hNjA0LTQzNWNmMDQzNmM5MSIsIm5hbWUiOiJCdXR0b24gMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEzZDNjMGJmNCIsIm5hbWUi + OiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiIxYzA5 + MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0 + ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiJhMzg0MzVjMC0zYWM0LTQxNzQtYjFiMS03ODFlZmY3 + OTg4YzQiLCJuYW1lIjoiRW5jb2RlciA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9 + fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + IjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTQwNjAzOGUtM2M0NC00MzJk + LThlY2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MywiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjE0fX0seyJpZCI6IjIzMmQ5ODlhLWUwZDctNDdmOS05NTg3LTY2OGFjMTI1ODRhMyIsIm5hbWUiOiJCdXR0b24gNC8zIiwic291cmNlIjp7ImNoYW5u + ZWwiOjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjU1YWEy + M2IwLWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0 + ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMwMDlkNGQiLCJuYW1lIjoiQnV0dG9uIDQvNCIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19 + LHsiaWQiOiIyN2JmMjE1Mi1lN2Y0LTQ1ZTUtOWI5OC1kY2FlODk2NWI5MWYiLCJuYW1lIjoiTGVmdCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6OCwi + Y2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5ZGY1OTAtNjFjYi00NzQ2LWEzMTMt + NzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9fSx7ImlkIjoiYWIwNTQ4ODAtMWM0My00ZjRhLWI0MTgtYzNlYTRkMzFiNTEwIiwibmFtZSI6IkxlZnQgMyIs + InNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijox + OH19LHsiaWQiOiJhNTdhMjc4Ny0zOTE5LTQ2ZmItOGZkZC0xN2ZiODNmNGJhZjEiLCJuYW1lIjoiUmlnaHQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIi + OjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOX19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWlk + aS1maWdodGVyLXR3aXN0ZXIiLCJjbGlwU2xvdHMiOlt7ImluZGV4IjowLCJ2b2x1bWUiOjAuNjU4NTk1NjEyODIxNDU1NCwiY29udGVudCI6eyJ0eXBlIjoiZmlsZSIs + ImZpbGUiOiJDOlxcUHJvZ3JhbSBGaWxlcyAoeDg2KVxcR09HIEdhbGF4eVxcd2ViXFxhdWRpb1xcZnJpZW5kX3JlcXVlc3Rfb3JfZ2FtZV9pbnYud2F2In19LHsiaW5k + ZXgiOjEsInZvbHVtZSI6MC4xNzg2NTMwOTY5OTQxMDA0NSwicmVwZWF0Ijp0cnVlLCJjb250ZW50Ijp7InR5cGUiOiJmaWxlIiwiZmlsZSI6IkM6XFxVc2Vyc1xcYmVu + amFcXERvY3VtZW50c1xcb3B0XFxmbHV0dGVyXFwucHViLWNhY2hlXFxob3N0ZWRcXHB1Yi5kYXJ0bGFuZy5vcmdcXGltcG9ydF9qc19saWJyYXJ5LTEuMC4yXFxleGFt + cGxlXFxhc3NldHNcXGFzdHJvbm9taWEubXAzIn19LHsiaW5kZXgiOjIsInZvbHVtZSI6MC4wODkwMDMzNTgzMjQzOTAzMiwiY29udGVudCI6eyJ0eXBlIjoiZmlsZSIs + ImZpbGUiOiIwMi1yZWFzeW50aC1yZWNvcmRlZC1taWRpLWdsdWVkLWs1cC1rZnEtbWlkLVpTVEVLNk4xLm1pZCJ9fSx7ImluZGV4IjozLCJ2b2x1bWUiOjEuMCwiY29u + dGVudCI6eyJ0eXBlIjoiZmlsZSIsImZpbGUiOiJDOlxcVXNlcnNcXGJlbmphXFxWaWRlb3NcXGV4YW1wbGUubWt2In19LHsiaW5kZXgiOjQsInZvbHVtZSI6MS4wLCJj + b250ZW50Ijp7InR5cGUiOiJmaWxlIiwiZmlsZSI6IjAxLVJlYUxlYXJuLTIxMDQyMF8wMTAxLnJwcCJ9fV19 + AAAQAAAA + > + FLOAT 545 309 1436 1514 + FXID {6816A302-349C-4D40-8989-A79EBDCD0ABE} + WAK 0 0 + > + + > + + > + > + + > + + > + > + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {85DC9B43-F05B-4096-9482-D37A638DFD1B} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROrbzHT8AAAAAAAAAACm0GT7pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 518 726 683 876 + FXID {7A9BF954-6726-4692-9A3F-0AB6C1E0688C} + WAK 0 0 + > + + > + + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-221-nks.RPP b/plugin-reaper-realearn/resources/test-projects/issue-221-nks.RPP new file mode 100644 index 0000000..3e18f17 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-221-nks.RPP @@ -0,0 +1,1228 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACaeAEAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoic0JZVV9nbWQiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsImNvbnRyb2xEZXZpY2VJZCI6IjE1IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjE1IiwiZGVmYXVsdEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJp + ZCI6IlJaUUxhb1FPUXdaRkJDUmFIOGtIQiIsIm5hbWUiOiJGaWx0ZXIgcGFnZXMifSx7ImlkIjoidFJVaFlhMFJHY3VwMVhJeVYzenZyIiwibmFtZSI6IlByZXNldCBi + cm93c2luZyJ9LHsiaWQiOiJDXzZBWTgwdGRvTTFDeTlGSGk3WE4iLCJuYW1lIjoiRmlsdGVyIHBhZ2UgXCJDaGFyYWN0ZXJcIiIsImFjdGl2YXRpb25UeXBlIjoicHJv + Z3JhbSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4Ijo0fX0seyJpZCI6IkVGcEYzRlc1THA2azZqS0JTaDZfSSIsIm5hbWUi + OiJGaWx0ZXIgcGFnZSBcIlN1YiB0eXBlXCIiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dyYW0iLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dy + YW1JbmRleCI6M319LHsiaWQiOiJscG5vWnlyWVVkU2FwcGN5U2wzOGQiLCJuYW1lIjoiRmlsdGVyIHBhZ2UgXCJUeXBlXCIiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dy + YW0iLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6Mn19LHsiaWQiOiI1QTZxYWpNcTZLRkZWczhzVkVJR1YiLCJuYW1lIjoi + RmlsdGVyIHBhZ2UgXCJCYW5rXCIiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dyYW0iLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRl + eCI6MX19LHsiaWQiOiJ6YlRUVXdrVS1ELXhzbFRRakdhM20iLCJuYW1lIjoiRmlsdGVyIHBhZ2UgXCJJbnN0cnVtZW50XCIiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dy + YW0ifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyR3JvdXBzIjpbeyJpZCI6ImZhZGVyIiwibmFtZSI6IkZhZGVyIn0seyJpZCI6InYtcG90 + IiwibmFtZSI6IlYtUG90In0seyJpZCI6InYtc2VsZWN0IiwibmFtZSI6IlYtU2VsZWN0In0seyJpZCI6ImZhZGVyLXRvdWNoIiwibmFtZSI6IkZhZGVyIFRvdWNoIn0s + eyJpZCI6InNlbGVjdCIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoibXV0ZSIsIm5hbWUiOiJNdXRlIn0seyJpZCI6InNvbG8iLCJuYW1lIjoiU29sbyJ9LHsiaWQiOiJy + ZWNvcmQtcmVhZHkiLCJuYW1lIjoiUmVjb3JkLXJlYWR5In0seyJpZCI6InYtcG90LWxlZHMiLCJuYW1lIjoiVi1Qb3QgTEVEcyJ9LHsiaWQiOiJsY2QiLCJuYW1lIjoi + TENEIn0seyJpZCI6Im1ldGVyIiwibmFtZSI6Ik1ldGVyIn1dLCJtYXBwaW5ncyI6W3siaWQiOiJNYU11VG9SLVFwOGRCMWl0LUVTUF8iLCJuYW1lIjoiQ2hvb3NlIGlu + c3RydW1lbnQiLCJncm91cElkIjoiUlpRTGFvUU9Rd1pGQkNSYUg4a0hCIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIn0sIm1vZGUi + OnsibWF4VGFyZ2V0VmFsdWUiOjAuMCwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsIm91dE9mUmFuZ2VCZWhhdmlv + ciI6Im1pbiIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoidGhpcyIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiI3c0laZldiaVNqWUdxWlI4ZWJFM20iLCJuYW1lIjoiQ2hvb3NlIGJhbmsiLCJncm91cElkIjoiUlpR + TGFvUU9Rd1pGQkNSYUg4a0hCIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYyIn0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuMTEx + MTExMTExMTExMTExMSwibWF4VGFyZ2V0VmFsdWUiOjAuMTExMTExMTExMTExMTExMSwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjUsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4 + QW5jaG9yIjoidGhpcyIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiIxWlN6Zi1CNHhUdXJBa1ZGa3NzM3kiLCJuYW1lIjoi + Q2hvb3NlIHR5cGUiLCJncm91cElkIjoiUlpRTGFvUU9Rd1pGQkNSYUg4a0hCIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYzIn0sIm1v + ZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuMjIyMjIyMjIyMjIyMjIyMiwibWF4VGFyZ2V0VmFsdWUiOjAuMjIyMjIyMjIyMjIyMjIyMiwibWF4U3RlcFNpemUiOjAuMDUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJn + ZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoidGhpcyIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJ0M2p3 + ZG54Z0xTZ053blloZlYzYlgiLCJuYW1lIjoiQ2hvb3NlIHN1YiB0eXBlIiwiZ3JvdXBJZCI6IlJaUUxhb1FPUXdaRkJDUmFIOGtIQiIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNCJ9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjMzMzMzMzMzMzMzMzMzMzMsIm1heFRhcmdldFZhbHVlIjowLjMzMzMz + MzMzMzMzMzMzMzMsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4iLCJi + dXR0b25Vc2FnZSI6InByZXNzLW9ubHkifSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6InRoaXMiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiWHJNam9LLWprc0tqQnZrSlI1NjBUIiwibmFtZSI6IkNob29zZSBjaGFyYWN0ZXIiLCJncm91cElkIjoiUlpRTGFvUU9R + d1pGQkNSYUg4a0hCIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY1In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNDQ0NDQ0NDQ0 + NDQ0NDQ0NCwibWF4VGFyZ2V0VmFsdWUiOjAuNDQ0NDQ0NDQ0NDQ0NDQ0NCwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjUsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoidGhpcyIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJHVzZrMnJlRnc1dVNOV2cyZ0ZHcVMiLCJuYW1lIjoiQnJvd3Nl + IiwiZ3JvdXBJZCI6InpiVFRVd2tVLUQteHNsVFFqR2EzbSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIn0sIm1vZGUi + OnsibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiTGFzdExvYWRlZCJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik5rc0JhbmsifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiblZUVWF4Z3JvT29f + WWZ1Z015azBYIiwibmFtZSI6IkJyb3dzZSIsImdyb3VwSWQiOiI1QTZxYWpNcTZLRkZWczhzVkVJR1YiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImpvZyJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAy + MDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NTdWJCYW5rIn0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZSwicHJvZ3JhbUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjF9fSx7ImlkIjoiTHJ5UXBJS0xLUk1Md0hBNk5mRFRCIiwibmFt + ZSI6IkJyb3dzZSIsImdyb3VwSWQiOiJscG5vWnlyWVVkU2FwcGN5U2wzOGQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6Impv + ZyJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NDYXRlZ29yeSJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInByb2dy + YW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4IjoyfX0seyJpZCI6InJLR1VSTUJHRFRMcHMtSERRR2J6LSIsIm5hbWUiOiJCcm93c2UiLCJn + cm91cElkIjoiRUZwRjNGVzVMcDZrNmpLQlNoNl9JIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJt + aW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzU3ViQ2F0ZWdvcnkifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJwcm9ncmFtQ29uZGl0aW9u + Ijp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6M319LHsiaWQiOiJCV0FGNVNiZUM1OEFCVWZnczE1VWwiLCJuYW1lIjoiQnJvd3NlIiwiZ3JvdXBJZCI6IkNf + NkFZODB0ZG9NMUN5OUZIaTdYTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIn0sIm1vZGUiOnsibWluU3RlcFNpemUi + OjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9z + Y0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRl + ZCJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik5rc01vZGUifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAs + InByb2dyYW1JbmRleCI6NH19LHsiaWQiOiJkMGF2ejJ1S2MxS2Mzam90NEZKRjciLCJuYW1lIjoiRGlzcGxheSIsImdyb3VwSWQiOiJDXzZBWTgwdGRvTTFDeTlGSGk3 + WE4iLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiIsImRpc3Bs + YXlJZCI6Miwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVjb2Rl + In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzTW9kZSJ9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6IlBHOTI2VVFxRmExdEtiWDdrbGFxMSIsIm5hbWUiOiJCcm93c2UgcHJlc2V0cyBjb2Fyc2UiLCJncm91cElkIjoidFJVaFlhMFJH + Y3VwMVhJeVYzenZyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6 + MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3Nj + QXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVk + In19fSx7ImlkIjoiT3BERHJwREcyaEF2NWdEall1YmZrIiwibmFtZSI6IkJyb3dzZSBwcmVzZXRzIGZpbmUiLCJncm91cElkIjoidFJVaFlhMFJHY3VwMVhJeVYzenZy + Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tl + Ijp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcx + MTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoi + aXdfbmc2XzJKQURaRVNsM1gyYXhpIiwibmFtZSI6IlByZXZpb3VzIHByZXNldCIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjox + LCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwi + bWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjEsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6NTgsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNnFvdnlsa0MzbjBKOURxa2VJaFRP + IiwibmFtZSI6Ik5leHQgcHJlc2V0IiwiZ3JvdXBJZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3 + MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsidHlwZSI6NTgsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4Ijow + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiZmVlZGJh + Y2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUkpERVR4eGlqZDR0SXYzNnp6djUxIiwibmFtZSI6IkRpc3BsYXkgcHJlc2V0IG5hbWUiLCJncm91cElkIjoidFJVaFlh + MFJHY3VwMVhJeVYzenZyIiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjowLCJvc2NBcmdJ + bmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUifSwibW9kZSI6eyJ0eXBl + IjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjU4LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOVh1dVEwVGwzMTVtNTYxSFB3ZHoxIiwi + bmFtZSI6IlNheSBwcmVzZXQgbmFtZSIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJyZWFwZXIiLCJ0eXBlIjox + MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlJZCI6MCwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEs + ImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVjb2RlIiwicmVhcGVyU291cmNlVHlwZSI6InNwZWVjaCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1p + blN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NTgsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiaXNFbmFibGVkIjpmYWxzZSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJLdzU3Mi1ET01qVzIt + bEFYLWxUa3MiLCJuYW1lIjoiUHJldmlldyBwcmVzZXQiLCJncm91cElkIjoidFJVaFlhMFJHY3VwMVhJeVYzenZyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVh + bCIsInR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjowLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1v + ZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5IiwicmVhcGVyU291cmNlVHlw + ZSI6InNwZWVjaCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0 + YXJnZXQiOnsidHlwZSI6NTksImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0lu + ZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYV9PYUtlMVVvX0ZBVGt1cG5IaTF6IiwibmFtZSI6IkxvYWQgcHJlc2V0IiwiZ3JvdXBJZCI6InRSVWhZYTBS + R2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRp + c3BsYXlJZCI6MCwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0IiwicmVhcGVyU291cmNlVHlwZSI6InNwZWVjaCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjAsImludm9jYXRpb25UeXBlIjozLCJ0cmFja0dV + SUQiOiJCNjNFOUY0My1DODQ3LUZBNDItQTVERC1FQUU5OThGNDJDNzgiLCJmeEFuY2hvciI6ImluZGV4IiwiZnhJbmRleCI6MSwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiTGFzdExvYWRlZCJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiSUVWRFNXdnVjbXAwYTYwajBOU2VHIiwibmFtZSI6IlNob3cgRlggb24g + bG9hZCIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXNlbGVjdCIsInJlYXBlclNvdXJjZVR5cGUiOiJzcGVlY2gifSwibW9kZSI6 + eyJtaW5UYXJnZXRWYWx1ZSI6MS4wLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tUeXBlIjoxfSwi + dGFyZ2V0Ijp7InR5cGUiOjI3LCJpbnZvY2F0aW9uVHlwZSI6MywidHJhY2tHVUlEIjoiQjYzRTlGNDMtQzg0Ny1GQTQyLUE1REQtRUFFOTk4RjQyQzc4IiwiZnhBbmNo + b3IiOiJpbmRleCIsImZ4SW5kZXgiOjEsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6Imt1VWg2MlpKWHBRYTlBU19zOVd2TSIsIm5hbWUiOiJEaXNwbGF5IHBhcmFtIG5hbWUiLCJncm91cElkIjoidFJVaFlhMFJHY3VwMVhJeVYzenZyIiwic291cmNl + Ijp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjowLCJsaW5lIjowLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQu + ZnhfcGFyYW1ldGVyLm5hbWUgfX0iLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MjAsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlZKamNDSFM0TDlYWEFFWmFnZ3dfdCIsIm5hbWUiOiJEaXNwbGF5IHBhcmFtIHZh + bHVlIiwiZ3JvdXBJZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + ImRpc3BsYXlJZCI6MCwibGluZSI6MSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MjAsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Ik9Dc015MVpITi1wTlZ0ckhQSFVxRCIsIm5hbWUiOiJEaXNwbGF5IiwiZ3JvdXBJZCI6IjVBNnFhak1xNktG + RlZzOHNWRUlHViIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVu + IiwiZGlzcGxheUlkIjoyLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2Qv + dGltZWNvZGUifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQw + MjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlw + ZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NTdWJCYW5rIn0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiWUNvSVpXb2pVZGxWNEhMR19ycEp1IiwibmFtZSI6IkRpc3BsYXkiLCJncm91cElkIjoiemJUVFV3a1UtRC14 + c2xUUWpHYTNtIiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4i + LCJkaXNwbGF5SWQiOjIsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90 + aW1lY29kZSJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAy + MDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImludm9jYXRpb25UeXBl + IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik5rc0JhbmsifSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJfTnpNbzkteFNGNUxVbWJsdkdMQkoiLCJuYW1lIjoiRGlzcGxheSIsImdyb3VwSWQiOiJFRnBGM0ZXNUxwNms2aktC + U2g2X0kiLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiIsImRp + c3BsYXlJZCI6Miwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVj + b2RlIn0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4 + NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzU3ViQ2F0ZWdvcnkifSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtOURBc3c5NERzY3JZMFRPMG4zRm4iLCJuYW1lIjoiRGlzcGxheSIsImdyb3VwSWQiOiJscG5vWnlyWVVkU2Fw + cGN5U2wzOGQiLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiIs + ImRpc3BsYXlJZCI6Miwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3Rp + bWVjb2RlIn0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIw + NjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzQ2F0ZWdvcnkifSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6Im1haW4vZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsi + dHlwZSI6MywiY2hhbm5lbCI6OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6Ilki + fSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiam9nIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6NjAsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2ciLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYWluL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3Vj + aCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1hcmtlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjg0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2VyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6 + InJlYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6InJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoid3JpdGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NX0sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoLWxlZnQiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQi + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OX0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJhbmstbGVmdCIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0Iiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0N30sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicmV3aW5kIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiZmFzdC1md2QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Mn0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InBsYXkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo5NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + fSx7ImlkIjoic3RvcCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijoic3RvcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyZWNvcmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5 + NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjeWNsZSIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y3ljbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiem9vbSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMH0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Inpvb20iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic2NydWIiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX19LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2Vs + ZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQi + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLXVwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTZ9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLWRvd24iLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1k + b3duIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Im51ZGdlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODV9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJudWRnZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJkcm9wIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6ODd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9wIiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fX0seyJpZCI6InJlcGxhY2UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6InJlcGxhY2UiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2xpY2siLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo4OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6 + InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6InNvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NH0sIm1vZGUiOnsi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImYyIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6NTV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMiIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX19LHsiaWQiOiJmMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiZjMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1N30sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImY1Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNSIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX19LHsiaWQiOiJmNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiZjYiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic21wdGUtYmVhdHMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo1M30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0s + eyJpZCI6ImxjZC9hc3NpZ25tZW50IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4ifSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudEluZGV4IjoibGNkL2Fzc2lnbm1lbnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImxjZC90aW1lY29kZSIs + Imdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoibGNkL3RpbWVjb2RlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1zZWxlY3QiLCJncm91cElkIjoi + di1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMS92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9mYWRlci90b3VjaCIsImdy + b3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9z + ZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNv + dXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS92LXBv + dC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9m + ZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAwIGRjYmFdIn0sIm1vZGUi + OnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoMS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTAgZGNi + YV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0 + ZXJuIjoiQjAgMzAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEv + di1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6 + InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6 + MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBv + dC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMSBkY2JhXSJ9LCJtb2Rl + Ijp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDEvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTZ9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDEvc29sbyIsImdyb3VwSWQiOiJz + b2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMS9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVh + ZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwi + bGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoMS9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDEvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJt + ZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbWV0ZXIvcGVh + ayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1z + ZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91 + Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvc2VsZWN0IiwiZ3JvdXBJZCI6 + InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gyL3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6Mywi + Y2hhbm5lbCI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvdi1wb3QvY29udHJvbCIsImdyb3Vw + SWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvZmVlZGJhY2siLCJncm91cElk + Ijoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVl + IjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvd3Jh + cCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4 + U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlv + blR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2gyL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAx + IGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L2Jvb3N0LWN1dCIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDIvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJ + ZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1 + ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL211 + dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gyL211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gyL3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fX0seyJpZCI6ImNoMi9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjB9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbGNkL2xpbmUyIiwi + Z3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjM0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3Rv + dWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zZWxl + Y3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjJ9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90Iiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMy92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1w + b3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9ib29z + dC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7 + Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3Npbmds + ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwi + c291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMy92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tdXRlIiwiZ3JvdXBJZCI6Im11 + dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMy9tdXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMy9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZv + Y2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zb2xvIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0s + eyJpZCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gz + L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MiwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + My9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwi + c291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJEMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + MzV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxMDd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlk + IjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjI3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX19LHsiaWQiOiJjaDQvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6M30sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNC9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3QvZmVlZGJhY2siLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjox + MCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90Iiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDQvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQi + OiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUi + OjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6 + IkIwIDMzIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6 + MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3Nw + cmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g0L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g0L3JlY29y + ZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozfSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvbGNkL2xpbmUxIiwiZ3Jv + dXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMSIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6 + MTIsImRpc3BsYXlJZCI6MywibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoNC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFsw + MDExIGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiJjaDUvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNn0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoNS9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOH0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + Mjh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNo + NS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImNo + YXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVy + biI6IkIwIDM0IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJh + d01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC93cmFwIiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNl + VmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDUvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDAgZGNi + YV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRl + cm4iOiJCMCAzNCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJjaDUvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDUvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJ + ZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDUvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291 + cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo0 + LCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g1L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMDAgZGNiYV0ifSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g1L21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXNlbGVj + dCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2Zh + ZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA5fSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g2L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOX0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L2ZhZGVyIiwiZ3JvdXBJ + ZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7 + ImlkIjoiY2g2L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjo0LCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9j + YXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g2L3YtcG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDAg + ZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJC + MCAzNSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjox + MCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNi92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zaW5n + bGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1h + eFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2g2L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDEx + IGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMX0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tdXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNi9zb2xv + IiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5 + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNi9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIs + ImRpc3BsYXlJZCI6NSwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoNi9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjF9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbWV0ZXIvcGVh + ayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDYvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYt + c2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDcvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvZmFkZXIvdG91Y2giLCJncm91 + cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvc2Vs + ZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3Vy + Y2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Nn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvdi1wb3Qv + Y29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3QvZmVl + ZGJhY2siLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7 + Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9j + YXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDcvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDEwIGRjYmFd + In0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVy + biI6IkIwIDM2IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3Yt + cG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2 + LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAu + NzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qv + c3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTEgZGNiYV0ifSwibW9kZSI6 + eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9j + YXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g3L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyfSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3NvbG8iLCJncm91cElkIjoic29s + byIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g3L3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFk + eSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo2LCJs + aW5lIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25U + eXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g3L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNy9sY2QvbGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1l + dGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTEwIGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tZXRlci9wZWFr + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXNl + bGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3Vj + aCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoOC9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9zZWxlY3QiLCJncm91cElkIjoi + c2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDgvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJj + aGFubmVsIjo3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC92LXBvdC9jb250cm9sIiwiZ3JvdXBJ + ZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoOC92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQi + OiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUi + OjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC93cmFw + IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhT + b3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDEg + ZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QvYm9vc3QtY3V0Iiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3Vy + Y2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoOC92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9zcHJlYWQiLCJncm91cElk + Ijoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVl + IjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbXV0 + ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fX0seyJpZCI6ImNoOC9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjB9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbGNkL2xpbmUyIiwi + Z3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo3LCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX1dLCJwYXJhbWV0ZXJzIjp7IjAiOnsibmFtZSI6IkZpbHRlciBiYW5rIiwidmFsdWVDb3VudCI6MTAsInZhbHVlIjow + LjExMTExMTExfX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifSwicG90U3RhdGUiOnsiZmlsdGVyX3NldHRpbmdzIjp7Im5rcyI6eyJiYW5rIjoiS29u + dGFrdCJ9fSwicHJlc2V0X2lkIjpudWxsfX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 508 110 752 675 + FXID {8AA22E92-CA21-F44E-86AD-447E6F1C2345} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABGDwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoiREZGTzA0dDUiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsImNvbnRyb2xEZXZpY2VJZCI6IjAiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3Mi + Olt7ImlkIjoiOTgwY3piSm5nZm5LOGl3eENRSW5hIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFu + Y2hvciI6ImluZGV4IiwiZnhJbmRleCI6MSwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbVR5cGUiOiJkeW5h + bWljIiwicGFyYW1FeHByZXNzaW9uIjoibWFwcGVkX2Z4X3BhcmFtZXRlcl9pbmRleGVzWzBdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJWQXA3MFFaTTYz + WjRQWVN2azd1MyIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NDksImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6 + MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpbmRleCIsImZ4SW5k + ZXgiOjEsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lv + biI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1sxXSIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJDOTg5YkpWMHpfSWxWV00wVjVOMTciLCJuYW1lIjoi + MyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUwLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaW5kZXgiLCJmeEluZGV4IjoxLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFtaWMiLCJwYXJhbUV4cHJlc3Npb24iOiJtYXBwZWRfZnhfcGFyYW1l + dGVyX2luZGV4ZXNbMl0iLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiN2JTdk5vcE9TdmlKSlRfSHNxYXFyIiwibmFtZSI6IjQiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjo1MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1 + fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImluZGV4IiwiZnhJbmRleCI6MSwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9uIjoibWFwcGVkX2Z4X3BhcmFtZXRlcl9pbmRleGVzWzNdIiwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkJUdEhNNGVERlNfTkR0SzhOVmg1TCIsIm5hbWUiOiI1Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTIsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpbmRleCIsImZ4SW5kZXgiOjEsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + cGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1s0XSIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJ1dDJvYkFfRVdQZ201WWZtRDhpMlkiLCJuYW1lIjoiNiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUzLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaW5kZXgiLCJmeEluZGV4IjoxLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFtaWMi + LCJwYXJhbUV4cHJlc3Npb24iOiJtYXBwZWRfZnhfcGFyYW1ldGVyX2luZGV4ZXNbNV0iLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQkNnekRkeUE4UElXdTN5 + WXItX1JQIiwibmFtZSI6IjciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImluZGV4IiwiZnhJbmRleCI6 + MSwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9uIjoi + bWFwcGVkX2Z4X3BhcmFtZXRlcl9pbmRleGVzWzZdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IndFeDRoMHZqQnBYSHk5dFRIRGl6QyIsIm5hbWUiOiI4Iiwi + c291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTUsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpbmRleCIsImZ4SW5kZXgiOjEsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJf + aW5kZXhlc1s3XSIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 316 110 752 675 + FXID {4CFB00DA-08DC-9444-B036-91FF39F66E64} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {98CB5B14-8CC6-1741-8AF3-1AF75475460C} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-221-pot-nks-demo.RPP b/plugin-reaper-realearn/resources/test-projects/issue-221-pot-nks-demo.RPP new file mode 100644 index 0000000..3ed2f5b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-221-pot-nks-demo.RPP @@ -0,0 +1,1558 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABUewEAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiJzQllVX2dtZCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMTUiLCJmZWVkYmFja0RldmljZUlkIjoiMTUiLCJkZWZhdWx0R3JvdXAiOnt9LCJncm91cHMiOlt7Imlk + IjoiUlpRTGFvUU9Rd1pGQkNSYUg4a0hCIiwibmFtZSI6IkZpbHRlciBwYWdlcyJ9LHsiaWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJuYW1lIjoiUHJlc2V0IGJy + b3dzaW5nIn0seyJpZCI6IkNfNkFZODB0ZG9NMUN5OUZIaTdYTiIsIm5hbWUiOiJGaWx0ZXIgcGFnZSBcIkNoYXJhY3RlclwiIiwiYWN0aXZhdGlvblR5cGUiOiJwcm9n + cmFtIiwicHJvZ3JhbUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjR9fSx7ImlkIjoiRUZwRjNGVzVMcDZrNmpLQlNoNl9JIiwibmFtZSI6 + IkZpbHRlciBwYWdlIFwiU3ViIHR5cGVcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3JhbSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3Jh + bUluZGV4IjozfX0seyJpZCI6Imxwbm9aeXJZVWRTYXBwY3lTbDM4ZCIsIm5hbWUiOiJGaWx0ZXIgcGFnZSBcIlR5cGVcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3Jh + bSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4IjoyfX0seyJpZCI6IjVBNnFhak1xNktGRlZzOHNWRUlHViIsIm5hbWUiOiJG + aWx0ZXIgcGFnZSBcIkJhbmtcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3JhbSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4 + IjoxfX0seyJpZCI6InpiVFRVd2tVLUQteHNsVFFqR2EzbSIsIm5hbWUiOiJGaWx0ZXIgcGFnZSBcIkluc3RydW1lbnRcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3Jh + bSJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiZmFkZXIiLCJuYW1lIjoiRmFkZXIifSx7ImlkIjoidi1wb3Qi + LCJuYW1lIjoiVi1Qb3QifSx7ImlkIjoidi1zZWxlY3QiLCJuYW1lIjoiVi1TZWxlY3QifSx7ImlkIjoiZmFkZXItdG91Y2giLCJuYW1lIjoiRmFkZXIgVG91Y2gifSx7 + ImlkIjoic2VsZWN0IiwibmFtZSI6IlNlbGVjdCJ9LHsiaWQiOiJtdXRlIiwibmFtZSI6Ik11dGUifSx7ImlkIjoic29sbyIsIm5hbWUiOiJTb2xvIn0seyJpZCI6InJl + Y29yZC1yZWFkeSIsIm5hbWUiOiJSZWNvcmQtcmVhZHkifSx7ImlkIjoidi1wb3QtbGVkcyIsIm5hbWUiOiJWLVBvdCBMRURzIn0seyJpZCI6ImxjZCIsIm5hbWUiOiJM + Q0QifSx7ImlkIjoibWV0ZXIiLCJuYW1lIjoiTWV0ZXIifV0sIm1hcHBpbmdzIjpbeyJpZCI6Ik1hTXVUb1ItUXA4ZEIxaXQtRVNQXyIsIm5hbWUiOiJDaG9vc2UgaW5z + dHJ1bWVudCIsImdyb3VwSWQiOiJSWlFMYW9RT1F3WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEifSwibW9kZSI6 + eyJtYXhUYXJnZXRWYWx1ZSI6MC4wLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwib3V0T2ZSYW5nZUJlaGF2aW9y + IjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IjdzSVpmV2JpU2pZR3FaUjhlYkUzbSIsIm5hbWUiOiJDaG9vc2UgYmFuayIsImdyb3VwSWQiOiJSWlFM + YW9RT1F3WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC4xMTEx + MTExMTExMTExMTExLCJtYXhUYXJnZXRWYWx1ZSI6MC4xMTExMTExMTExMTExMTExLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IjFaU3pmLUI0eFR1ckFrVkZrc3MzeSIsIm5hbWUiOiJD + aG9vc2UgdHlwZSIsImdyb3VwSWQiOiJSWlFMYW9RT1F3WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjMifSwibW9k + ZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC4yMjIyMjIyMjIyMjIyMjIyLCJtYXhUYXJnZXRWYWx1ZSI6MC4yMjIyMjIyMjIyMjIyMjIyLCJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdl + dCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6InQzandk + bnhnTFNnTnduWWhmVjNiWCIsIm5hbWUiOiJDaG9vc2Ugc3ViIHR5cGUiLCJncm91cElkIjoiUlpRTGFvUU9Rd1pGQkNSYUg4a0hCIiwic291cmNlIjp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImY0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuMzMzMzMzMzMzMzMzMzMzMywibWF4VGFyZ2V0VmFsdWUiOjAuMzMzMzMz + MzMzMzMzMzMzMywibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsImJ1 + dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoidGhpcyIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJYck1qb0stamtzS2pCdmtKUjU2MFQiLCJuYW1lIjoiQ2hvb3NlIGNoYXJhY3RlciIsImdyb3VwSWQiOiJSWlFMYW9RT1F3 + WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC40NDQ0NDQ0NDQ0 + NDQ0NDQ0LCJtYXhUYXJnZXRWYWx1ZSI6MC40NDQ0NDQ0NDQ0NDQ0NDQ0LCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IkdXNmsycmVGdzV1U05XZzJnRkdxUyIsIm5hbWUiOiJCcm93c2Ui + LCJncm91cElkIjoiemJUVFV3a1UtRC14c2xUUWpHYTNtIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6 + eyJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzQmFuayJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJuVlRVYXhncm9Pb19Z + ZnVnTXlrMFgiLCJuYW1lIjoiQnJvd3NlIiwiZ3JvdXBJZCI6IjVBNnFhak1xNktGRlZzOHNWRUlHViIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxl + bWVudEluZGV4Ijoiam9nIn0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIw + NjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik5rc1N1YkJhbmsifSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MX19LHsiaWQiOiJMcnlRcElLTEtSTUx3SEE2TmZEVEIiLCJuYW1l + IjoiQnJvd3NlIiwiZ3JvdXBJZCI6Imxwbm9aeXJZVWRTYXBwY3lTbDM4ZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9n + In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik5rc0NhdGVnb3J5In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwicHJvZ3Jh + bUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjJ9fSx7ImlkIjoicktHVVJNQkdEVExwcy1IRFFHYnotIiwibmFtZSI6IkJyb3dzZSIsImdy + b3VwSWQiOiJFRnBGM0ZXNUxwNms2aktCU2g2X0kiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImpvZyJ9LCJtb2RlIjp7Im1p + blN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + Ikxhc3RMb2FkZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NTdWJDYXRlZ29yeSJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInByb2dyYW1Db25kaXRpb24i + OnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4IjozfX0seyJpZCI6IkJXQUY1U2JlQzU4QUJVZmdzMTVVbCIsIm5hbWUiOiJCcm93c2UiLCJncm91cElkIjoiQ182 + QVk4MHRkb00xQ3k5RkhpN1hOIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6 + MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3Nj + QXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVk + In0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzTW9kZSJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwi + cHJvZ3JhbUluZGV4Ijo0fX0seyJpZCI6ImQwYXZ6MnVLYzFLYzNqb3Q0RkpGNyIsIm5hbWUiOiJEaXNwbGF5IiwiZ3JvdXBJZCI6IkNfNkFZODB0ZG9NMUN5OUZIaTdY + TiIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxh + eUlkIjoyLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUi + fSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NNb2RlIn0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiUEc5MjZVUXFGYTF0S2JYN2tsYXExIiwibmFtZSI6IkJyb3dzZSBwcmVzZXRzIGNvYXJzZSIsImdyb3VwSWQiOiJ0UlVoWWEwUkdj + dXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjow + LjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7InR5cGUiOjU4LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQi + fX19LHsiaWQiOiJPcEREcnBERzJoQXY1Z0RqWXViZmsiLCJuYW1lIjoiQnJvd3NlIHByZXNldHMgZmluZSIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2Ui + OnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzEx + MzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7 + InR5cGUiOjU4LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJp + d19uZzZfMkpBRFpFU2wzWDJheGkiLCJuYW1lIjoiUHJldmlvdXMgcHJlc2V0IiwiZ3JvdXBJZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEs + ImtleSI6NjV9LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQifSwibW9kZSI6eyJ0eXBlIjoxLCJt + aW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI2cW92eWxrQzNuMEo5RHFrZUloVE8i + LCJuYW1lIjoiTmV4dCBwcmVzZXQiLCJncm91cElkIjoidFJVaFlhMFJHY3VwMVhJeVYzenZyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcx + MTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJSSkRFVHh4aWpkNHRJdjM2enp2NTEiLCJuYW1lIjoiRGlzcGxheSBwcmVzZXQgbmFtZSIsImdyb3VwSWQiOiJ0UlVoWWEw + UkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsIm9zY0FyZ0lu + ZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90aW1lY29kZSJ9LCJtb2RlIjp7InR5cGUi + OjEsIm1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NTgsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5WHV1UTBUbDMxNW01NjFIUHdkejEiLCJu + YW1lIjoiU2F5IHByZXNldCBuYW1lIiwiZ3JvdXBJZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InJlYXBlciIsInR5cGUiOjEy + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjowLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwi + a2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUiLCJyZWFwZXJTb3VyY2VUeXBlIjoic3BlZWNoIn0sIm1vZGUiOnsidHlwZSI6MSwibWlu + U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJpc0VuYWJsZWQiOmZhbHNlLCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ikt3NTcyLURPTWpXMi1s + QVgtbFRrcyIsIm5hbWUiOiJQcmV2aWV3IHByZXNldCIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9k + aWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJyZWFwZXJTb3VyY2VUeXBl + Ijoic3BlZWNoIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRh + cmdldCI6eyJ0eXBlIjo1OSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhX09hS2UxVW9fRkFUa3VwbkhpMXoiLCJuYW1lIjoiTG9hZCBwcmVzZXQiLCJncm91cElkIjoidFJVaFlhMFJH + Y3VwMVhJeVYzenZyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlz + cGxheUlkIjowLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QiLCJyZWFwZXJTb3VyY2VUeXBlIjoic3BlZWNoIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MCwiaW52b2NhdGlvblR5cGUiOjMsInRyYWNrR1VJ + RCI6IkI2M0U5RjQzLUM4NDctRkE0Mi1BNURELUVBRTk5OEY0MkM3OCIsImZ4QW5jaG9yIjoiaW5kZXgiLCJmeEluZGV4IjoxLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJMYXN0TG9hZGVkIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJJRVZEU1d2dWNtcDBhNjBqME5TZUciLCJuYW1lIjoiU2hvdyBGWCBvbiBs + b2FkIiwiZ3JvdXBJZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlJZCI6MCwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0IiwicmVhcGVyU291cmNlVHlwZSI6InNwZWVjaCJ9LCJtb2RlIjp7 + Im1pblRhcmdldFZhbHVlIjoxLjAsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0 + YXJnZXQiOnsidHlwZSI6MjcsImludm9jYXRpb25UeXBlIjozLCJ0cmFja0dVSUQiOiJCNjNFOUY0My1DODQ3LUZBNDItQTVERC1FQUU5OThGNDJDNzgiLCJmeEFuY2hv + ciI6ImluZGV4IiwiZnhJbmRleCI6MSwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + Ijoia3VVaDYyWkpYcFFhOUFTX3M5V3ZNIiwibmFtZSI6IkRpc3BsYXkgcGFyYW0gbmFtZSIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJzb3VyY2Ui + OnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsImxpbmUiOjAsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5m + eF9wYXJhbWV0ZXIubmFtZSB9fSIsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJGeE9uT2ZmU3RhdGUiLCJUZW1wbyIsIlBsYXlSYXRlIiwiVHJh + Y2tTZWxlY3Rpb25TdGF0ZSIsIlJvdXRlVm9sdW1lIiwiUm91dGVQYW4iLCJUcmFja1NvbG9TdGF0ZSIsIlRyYWNrQXJtU3RhdGUiLCJBdXRvbWF0aW9uTW9kZU92ZXJy + aWRlIiwiVHJhY2tBdXRvbWF0aW9uTW9kZSIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJGeFBhcmFtZXRlclZhbHVlIiwiVHJhY2tNdXRlU3RhdGUiLCJCcm93c2VGeFBy + ZXNldHMiLCJUcmFja1ZvbHVtZSIsIlRyYWNrUGFuIl19fSx7ImlkIjoiVkpqY0NIUzRMOVhYQUVaYWdnd190IiwibmFtZSI6IkRpc3BsYXkgcGFyYW0gdmFsdWUiLCJn + cm91cElkIjoidFJVaFlhMFJHY3VwMVhJeVYzenZyIiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxh + eUlkIjowLCJsaW5lIjoxLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUs + ImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJMYXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJUcmFja0FybVN0YXRlIiwiVGVtcG8iLCJUcmFja1NvbG9TdGF0ZSIsIlBsYXlSYXRlIiwiQnJvd3Nl + RnhQcmVzZXRzIiwiRnhPbk9mZlN0YXRlIiwiQXV0b21hdGlvbk1vZGVPdmVycmlkZSIsIlRyYWNrUGFuIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlJvdXRlVm9sdW1l + IiwiVHJhY2tNdXRlU3RhdGUiLCJGeFBhcmFtZXRlclZhbHVlIiwiUm91dGVQYW4iLCJUcmFja01vbml0b3JpbmdNb2RlIiwiVHJhY2tBdXRvbWF0aW9uTW9kZSIsIlRy + YWNrVm9sdW1lIl19fSx7ImlkIjoiT0NzTXkxWkhOLXBOVnRySFBIVXFEIiwibmFtZSI6IkRpc3BsYXkiLCJncm91cElkIjoiNUE2cWFqTXE2S0ZGVnM4c1ZFSUdWIiwi + c291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQi + OjIsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90aW1lY29kZSJ9LCJt + b2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoie3sgdGFyZ2V0Lml0ZW0ubmFtZSB9fSIsImZlZWRiYWNrVHlw + ZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9h + ZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzU3ViQmFuayJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IllDb0laV29qVWRsVjRITEdfcnBKdSIs + Im5hbWUiOiJEaXNwbGF5IiwiZ3JvdXBJZCI6InpiVFRVd2tVLUQteHNsVFFqR2EzbSIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6 + MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0 + MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tUeXBl + IjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2Fk + ZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NCYW5rIn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiX056TW85LXhTRjVMVW1ibHZHTEJKIiwibmFt + ZSI6IkRpc3BsYXkiLCJncm91cElkIjoiRUZwRjNGVzVMcDZrNmpLQlNoNl9JIiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQiOjIsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJr + ZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90aW1lY29kZSJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2 + MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5z + Zm9ybWF0aW9uIjoie3sgdGFyZ2V0Lml0ZW0ubmFtZSB9fSIsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzU3ViQ2F0ZWdvcnkifSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtOURBc3c5NERzY3JZMFRPMG4zRm4iLCJuYW1lIjoiRGlzcGxheSIsImdyb3VwSWQiOiJscG5vWnlyWVVkU2FwcGN5 + U2wzOGQiLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiIsImRp + c3BsYXlJZCI6Miwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVj + b2RlIn0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4 + NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzQ2F0ZWdvcnkifSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6Im1haW4vZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlw + ZSI6MywiY2hhbm5lbCI6OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiam9nIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6NjAsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2ciLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYWluL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1hcmtlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjg0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25U + eXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2VyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJl + YWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6InJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoid3JpdGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NX0sIm1vZGUiOnsi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoLWxlZnQiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OX0sIm1vZGUiOnsibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJhbmstbGVmdCIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0N30sIm1vZGUiOnsibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicmV3aW5kIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQiLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19fSx7ImlkIjoiZmFzdC1md2QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Mn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InBsYXkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo5NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7 + ImlkIjoic3RvcCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4Ijoic3RvcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyZWNvcmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NX0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjeWNsZSIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3lj + bGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiem9vbSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMH0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Inpvb20iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic2NydWIiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxMDF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX19LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLXVwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTZ9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLWRvd24iLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3du + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Im51ZGdlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODV9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJudWRnZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJkcm9wIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6ODd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9wIiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fX0seyJpZCI6InJlcGxhY2UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6InJlcGxhY2UiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2xpY2siLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjo4OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InNv + bG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6InNvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NH0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImYyIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NTV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMiIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJmMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiZjMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1N30sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImY1Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNSIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX19LHsiaWQiOiJmNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiZjYiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic21wdGUtYmVhdHMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjo1M30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImxjZC9hc3NpZ25tZW50IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4ifSwibW9kZSI6eyJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudEluZGV4IjoibGNkL2Fzc2lnbm1lbnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImxjZC90aW1lY29kZSIsImdy + b3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoibGNkL3RpbWVjb2RlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1zZWxlY3QiLCJncm91cElkIjoidi1z + ZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMS92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9mYWRlci90b3VjaCIsImdyb3Vw + SWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9zZWxl + Y3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJj + ZSI6eyJ0eXBlIjozLCJjaGFubmVsIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS92LXBvdC9j + b250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9mZWVk + YmFjayIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsi + bWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoMS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTAgZGNiYV0i + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJu + IjoiQjAgMzAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1w + b3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYt + cG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43 + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9z + cHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7 + Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDEvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTZ9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDEvc29sbyIsImdyb3VwSWQiOiJzb2xv + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMS9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHki + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGlu + ZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlw + ZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + MS9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDEvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRl + ciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbWV0ZXIvcGVhayIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6 + ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxl + Y3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2gi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDIvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvc2VsZWN0IiwiZ3JvdXBJZCI6InNl + bGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gyL3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hh + bm5lbCI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvdi1wb3QvY29udHJvbCIsImdyb3VwSWQi + OiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDIvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvZmVlZGJhY2siLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvd3JhcCIs + Imdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291 + cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2gyL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAxIGRj + YmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L2Jvb3N0LWN1dCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDIvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6 + InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6 + MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL211dGUi + LCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gyL211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gyL3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX19LHsiaWQiOiJjaDIvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlv + blR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fX0seyJpZCI6ImNoMi9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjB9LCJtb2RlIjp7Im1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbGNkL2xpbmUyIiwiZ3Jv + dXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjM0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlu + dm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0IiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNo + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zZWxlY3Qi + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjJ9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDMvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3Vy + Y2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMy92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gzL3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9ib29zdC1j + dXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1p + blNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3 + TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NpbmdsZSIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMy92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMy9tdXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMy9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL2xj + ZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MiwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9s + Y2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291 + cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBh + dHRlcm4iOiJEMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9 + LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxMDd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjI3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlu + dm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX19LHsiaWQiOiJjaDQvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6M30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + NC9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3QvZmVlZGJhY2siLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQv + di1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2 + LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAu + MDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlu + dm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIw + IDMzIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NwcmVh + ZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiY2g0L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g0L3JlY29yZC1y + ZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2Vs + ZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvbGNkL2xpbmUxIiwiZ3JvdXBJ + ZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMSIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIs + ImRpc3BsYXlJZCI6MywibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoNC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDEx + IGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDUvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNn0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoNS9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOH0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjh9 + LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS9m + YWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fX0seyJpZCI6ImNoNS92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImNoYXJh + Y3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6 + IkIwIDM0IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01p + ZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC93cmFwIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3Vy + Y2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFs + dWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDUvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDAgZGNiYV0i + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJCMCAzNCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJjaDUvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDUvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6 + InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDUvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNl + Ijp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo0LCJs + aW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25U + eXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g1L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMDAgZGNiYV0ifSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g1L21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXNlbGVjdCIs + Imdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2ZhZGVy + L3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA5fSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2g2L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOX0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L2ZhZGVyIiwiZ3JvdXBJZCI6 + ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7Imlk + IjoiY2g2L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjo0LCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g2L3YtcG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDAgZGNi + YV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAz + NSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoNi92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zaW5nbGUi + LCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNv + dXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25U + eXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g2L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDExIGRj + YmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMX0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tdXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNi9zb2xvIiwi + Z3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoNi9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Ni9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRp + c3BsYXlJZCI6NSwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoNi9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjF9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbWV0ZXIvcGVhayIs + Imdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDYvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2Vs + ZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDcvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvZmFkZXIvdG91Y2giLCJncm91cElk + IjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvc2VsZWN0 + IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2Ui + OnsidHlwZSI6MywiY2hhbm5lbCI6Nn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvdi1wb3QvY29u + dHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3QvZmVlZGJh + Y2siLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1h + eFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDcvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDEwIGRjYmFdIn0s + Im1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6 + IkIwIDM2IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90 + L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBv + dC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc3By + ZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJt + YXhTb3VyY2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2g3L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyfSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3NvbG8iLCJncm91cElkIjoic29sbyIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g3L3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo2LCJsaW5l + IjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBl + IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3 + L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Ny9sY2QvbGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVy + Iiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTEwIGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tZXRlci9wZWFrIiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXNlbGVj + dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoOC9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9zZWxlY3QiLCJncm91cElkIjoic2Vs + ZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDgvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFu + bmVsIjo3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25U + eXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6 + InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoOC92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2 + LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAu + NzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC93cmFwIiwi + Z3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3Vy + Y2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlw + ZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDgvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDEgZGNi + YV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2Ui + OnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + OC92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9zcHJlYWQiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + LjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbXV0ZSIs + Imdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDgvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX19LHsiaWQiOiJjaDgvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlv + blR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fX0seyJpZCI6ImNoOC9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjB9LCJtb2RlIjp7Im1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbGNkL2xpbmUyIiwiZ3Jv + dXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo3LCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX1dLCJwYXJhbWV0ZXJzIjp7IjAiOnsibmFtZSI6IkZpbHRlciBiYW5rIiwidmFsdWVDb3VudCI6MTAsInZhbHVlIjowLjQ0 + NDQ0NDQ1fX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifSwicG90U3RhdGUiOnsiZmlsdGVyX3NldHRpbmdzIjp7Im5rcyI6eyJiYW5rIjoiWmVicmEy + In19LCJwcmVzZXRfaWQiOiIwOGM0YmU5NWZmNTllNDZjODhiNzYxZjUzZjZjMWJmNCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 392 51 752 675 + FXID {8AA22E92-CA21-F44E-86AD-447E6F1C2345} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABFDwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiJERkZPMDR0NSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6 + W3siaWQiOiI5ODBjemJKbmdmbks4aXd4Q1FJbmEiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaW5kZXgiLCJmeEluZGV4IjoxLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFt + aWMiLCJwYXJhbUV4cHJlc3Npb24iOiJtYXBwZWRfZnhfcGFyYW1ldGVyX2luZGV4ZXNbMF0iLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiYlZBcDcwUVpNNjNa + NFBZU3ZrN3UzIiwibmFtZSI6IjIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImluZGV4IiwiZnhJbmRl + eCI6MSwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9u + IjoibWFwcGVkX2Z4X3BhcmFtZXRlcl9pbmRleGVzWzFdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkM5ODliSlYwel9JbFZXTTBWNU4xNyIsIm5hbWUiOiIz + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTAsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpbmRleCIsImZ4SW5kZXgiOjEsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0 + ZXJfaW5kZXhlc1syXSIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI3YlN2Tm9wT1N2aUpKVF9Ic3FhcXIiLCJuYW1lIjoiNCIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjUxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9 + LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaW5kZXgiLCJmeEluZGV4IjoxLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFtaWMiLCJwYXJhbUV4cHJlc3Npb24iOiJtYXBwZWRfZnhfcGFyYW1ldGVyX2luZGV4ZXNbM10iLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQlR0SE00ZURGU19ORHRLOE5WaDVMIiwibmFtZSI6IjUiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1MiwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImluZGV4IiwiZnhJbmRleCI6MSwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJw + YXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9uIjoibWFwcGVkX2Z4X3BhcmFtZXRlcl9pbmRleGVzWzRdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6InV0Mm9iQV9FV1BnbTVZZm1EOGkyWSIsIm5hbWUiOiI2Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJpbmRleCIsImZ4SW5kZXgiOjEsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIs + InBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1s1XSIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJCQ2d6RGR5QThQSVd1M3lZ + ci1fUlAiLCJuYW1lIjoiNyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjU0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaW5kZXgiLCJmeEluZGV4Ijox + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFtaWMiLCJwYXJhbUV4cHJlc3Npb24iOiJt + YXBwZWRfZnhfcGFyYW1ldGVyX2luZGV4ZXNbNl0iLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoid0V4NGgwdmpCcFhIeTl0VEhEaXpDIiwibmFtZSI6IjgiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1NSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImluZGV4IiwiZnhJbmRleCI6MSwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9uIjoibWFwcGVkX2Z4X3BhcmFtZXRlcl9p + bmRleGVzWzddIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 316 110 752 675 + FXID {4CFB00DA-08DC-9444-B036-91FF39F66E64} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {98CB5B14-8CC6-1741-8AF3-1AF75475460C} + WAK 0 0 + > + > + "" + MkRNU+5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAAA8aQAAAQAAAAAAEAA= + I3BnbT1IUyBGb3VydGggQnJpZGdlCi8qQE1ldGEKCkJhbms6CidaZWJyYSBGYWN0b3J5IDIuNycKCkF1dGhvcjoKJ0hvd2FyZCBTY2FycicKClVzYWdlOgonTVcgPSB2 + aWJyYXRvXHJDdHJsIEEgPSBcInBoYXNlclwiJwoKQ2F0ZWdvcmllczoKJ0xlYWRzOkFuYWxvZ3VlLCBMZWFkczpQbHVja3MnCgpGZWF0dXJlczoKJ1BvbHksIENob3Jk + LCBQZXJjdXNzaXZlLCBHbGlkZScKCkNoYXJhY3RlcjoKJ0JyaWdodCwgUGhhdCcKCiovCgojQU09WmVicmEyCiNWZXJzPTIwNTAwCiNFbmRpYW49bGl0dGxlCiNubT0z + NAojbXM9bm9uZQojbXM9TW9kV2hsCiNtcz1QaXRjaFcKI21zPUN0cmxBCiNtcz1DdHJsQgojbXM9TGZvRzEKI21zPUxmb0cyCiNtcz1HYXRlCiNtcz1LZXlGb2wKI21z + PUtleUZvbDIKI21zPVZlbG9jaXR5CiNtcz1BVG91Y2gKI21zPUFycE1vZAojbXM9QXJwTWQyCiNtcz1FbnYxCiNtcz1FbnYyCiNtcz1FbnYzCiNtcz1FbnY0CiNtcz1N + U0VHMQojbXM9TVNFRzIKI21zPU1TRUczCiNtcz1NU0VHNAojbXM9TGZvMQojbXM9TGZvMgojbXM9TGZvMwojbXM9TGZvNAojbXM9TU1hcDEKI21zPU1NYXAyCiNtcz1N + TWFwMwojbXM9TU1hcDQKI21zPU1NaXgxCiNtcz1NTWl4MgojbXM9TU1peDMKI21zPU1NaXg0CiNudj01CiNtdj1HYXRlCiNtdj1FbnYxCiNtdj1FbnYyCiNtdj1FbnYz + CiNtdj1FbnY0CiNjbT1tYWluCkNjT3A9MTAwLjAwCiNMRk9HPTEKI0xGT0cyPTEKI2NtPVBDb3JlClhfMT0wLjAwCllfMT0wLjAwClhfMj0wLjAwCllfMj0wLjAwClhf + Mz0wLjAwCllfMz0wLjAwClhfND0wLjAwCllfND0wLjAwCk1UMTE9T1NDMTpXTnVtCk1MMTE9NC42NQpNUjExPS0xMC4xMgpNVDEyPU9TQzI6V051bQpNTDEyPTYuNzUK + TVIxMj0tOC4wNgpNVDEzPU9TQzM6V051bQpNTDEzPTguNTUKTVIxMz0tNS42MgpNVDE0PU9TQzQ6V051bQpNTDE0PTkuOTAKTVIxND0tMy45NApNVDE1PU9TQzE6UGFu + Ck1MMTU9LTEwMC4wMApNUjE1PTAuMDAKTVQxNj1PU0MyOlBhbgpNTDE2PTk4LjAwCk1SMTY9Mi4wMApNVDE3PW5vbmU6YXNzaWduZWQKTUwxNz04LjAwCk1SMTc9LTgu + MDAKTVQxOD1ub25lOmFzc2lnbmVkCk1MMTg9NTAuMDAKTVIxOD0tNTAuMDAKTVQyMT1PU0MxOlZvbApNTDIxPS02MC4wMApNUjIxPTIuMDAKTVQyMj1PU0MyOlZvbApN + TDIyPTAuMDAKTVIyMj0tODAuMDAKTVQyMz1PU0MzOlZvbApNTDIzPS0xMDIuMDAKTVIyMz0tMi4wMApNVDI0PU9TQzQ6Vm9sCk1MMjQ9MC4wMApNUjI0PS0xMDAuMDAK + TVQyNT1ub25lOmFzc2lnbmVkCk1MMjU9NTAuMDAKTVIyNT0tNTAuMDAKTVQyNj1ub25lOmFzc2lnbmVkCk1MMjY9NTAuMDAKTVIyNj0tNTAuMDAKTVQyNz1ub25lOmFz + c2lnbmVkCk1MMjc9NTAuMDAKTVIyNz0tNTAuMDAKTVQyOD1ub25lOmFzc2lnbmVkCk1MMjg9NTAuMDAKTVIyOD0tNTAuMDAKTVQzMT1YTUYxOkN1dApNTDMxPTUwLjAw + Ck1SMzE9LTI2LjI1Ck1UMzI9WE1GMTpGTTEKTUwzMj0wLjAwCk1SMzI9LTUwLjAwCk1UMzM9bm9uZTphc3NpZ25lZApNTDMzPTQ4LjAwCk1SMzM9LTQ4LjAwCk1UMzQ9 + bm9uZTphc3NpZ25lZApNTDM0PTUwLjAwCk1SMzQ9LTUwLjAwCk1UMzU9bm9uZTphc3NpZ25lZApNTDM1PTUwLjAwCk1SMzU9LTUwLjAwCk1UMzY9bm9uZTphc3NpZ25l + ZApNTDM2PTUwLjAwCk1SMzY9LTUwLjAwCk1UMzc9bm9uZTphc3NpZ25lZApNTDM3PTQ4LjAwCk1SMzc9LTQ4LjAwCk1UMzg9bm9uZTphc3NpZ25lZApNTDM4PTUwLjAw + Ck1SMzg9LTUwLjAwCk1UNDE9WE1GMTpSZXMKTUw0MT0xNS4wMApNUjQxPS0zLjc1Ck1UNDI9bm9uZTphc3NpZ25lZApNTDQyPTUwLjAwCk1SNDI9LTUwLjAwCk1UNDM9 + bm9uZTphc3NpZ25lZApNTDQzPTUwLjAwCk1SNDM9LTUwLjAwCk1UNDQ9bm9uZTphc3NpZ25lZApNTDQ0PTUwLjAwCk1SNDQ9LTUwLjAwCk1UNDU9bm9uZTphc3NpZ25l + ZApNTDQ1PTUwLjAwCk1SNDU9LTUwLjAwCk1UNDY9bm9uZTphc3NpZ25lZApNTDQ2PTUwLjAwCk1SNDY9LTUwLjAwCk1UNDc9bm9uZTphc3NpZ25lZApNTDQ3PTUwLjAw + Ck1SNDc9LTUwLjAwCk1UNDg9bm9uZTphc3NpZ25lZApNTDQ4PTQ4LjAwCk1SNDg9LTQ4LjAwCk1UNTE9RU5WMTpBdGsKTUw1MT00Ny4wMApNUjUxPTAuMDAKTVQ1Mj1F + TlYxOlN1cwpNTDUyPTAuMDAKTVI1Mj0tNTAuMDAKTVQ1Mz1FTlYxOkRlYwpNTDUzPTAuMDAKTVI1Mz0tMjguMDAKTVQ1ND1FTlYxOlJlbApNTDU0PTI3LjAwCk1SNTQ9 + LTIwLjAwCk1UNTU9RU5WMTpTdXMyCk1MNTU9MzAuMDAKTVI1NT0wLjAwCk1UNTY9bm9uZTphc3NpZ25lZApNTDU2PTUwLjAwCk1SNTY9LTUwLjAwCk1UNTc9bm9uZTph + c3NpZ25lZApNTDU3PTUwLjAwCk1SNTc9LTUwLjAwCk1UNTg9bm9uZTphc3NpZ25lZApNTDU4PTUwLjAwCk1SNTg9LTUwLjAwCk1UNjE9VkNDOlBvcnRhCk1MNjE9Mjgu + MDAKTVI2MT0tMy4wMApNVDYyPVZDQzpQb3J0YTIKTUw2Mj0tMjYuMDAKTVI2Mj0zMC4wMApNVDYzPU9TQzE6VE1EcHQKTUw2Mz0wLjAwCk1SNjM9LTQ2LjA4Ck1UNjQ9 + T1NDMjpUTURwdApNTDY0PTAuMDAKTVI2ND0tNDAuMzIKTVQ2NT1PU0MzOlRNRHB0Ck1MNjU9MC4wMApNUjY1PS0zMS42OApNVDY2PU9TQzQ6VE1EcHQKTUw2Nj03LjY4 + Ck1SNjY9LTI0LjAwCk1UNjc9bm9uZTphc3NpZ25lZApNTDY3PTUwLjAwCk1SNjc9LTUwLjAwCk1UNjg9bm9uZTphc3NpZ25lZApNTDY4PTUwLjAwCk1SNjg9LTUwLjAw + Ck1UNzE9RGVsYXkxOk1peApNTDcxPTQ5LjAwCk1SNzE9MjYuMDAKTVQ3Mj1EZWxheTE6VDAKTUw3Mj0wLjAwCk1SNzI9LTc1LjIwCk1UNzM9RGVsYXkxOlQxCk1MNzM9 + MC4wMApNUjczPS03NS4wMApNVDc0PW5vbmU6YXNzaWduZWQKTUw3ND01MC4wMApNUjc0PS01MC4wMApNVDc1PW5vbmU6YXNzaWduZWQKTUw3NT01MC4wMApNUjc1PS01 + MC4wMApNVDc2PW5vbmU6YXNzaWduZWQKTUw3Nj01MC4wMApNUjc2PS01MC4wMApNVDc3PW5vbmU6YXNzaWduZWQKTUw3Nz01MC4wMApNUjc3PS01MC4wMApNVDc4PW5v + bmU6YXNzaWduZWQKTUw3OD00OC4wMApNUjc4PS00OC4wMApNVDgxPVJldjE6RHB0Ck1MODE9MzMuMDAKTVI4MT0tMzEuMjUKTVQ4Mj1SZXYxOkZCCk1MODI9NDMuMDAK + TVI4Mj0tMTcuMDAKTVQ4Mz1SZXYxOldldApNTDgzPTE1LjAwCk1SODM9LTI0LjAwCk1UODQ9bm9uZTphc3NpZ25lZApNTDg0PTUwLjAwCk1SODQ9LTUwLjAwCk1UODU9 + bm9uZTphc3NpZ25lZApNTDg1PTUwLjAwCk1SODU9LTUwLjAwCk1UODY9bm9uZTphc3NpZ25lZApNTDg2PTUwLjAwCk1SODY9LTUwLjAwCk1UODc9bm9uZTphc3NpZ25l + ZApNTDg3PTUwLjAwCk1SODc9LTUwLjAwCk1UODg9bm9uZTphc3NpZ25lZApNTDg4PTUwLjAwCk1SODg9LTUwLjAwCk1NVDE9VkNGMTpEcnYKTU1TMT0xNQpNTUQxPTYu + MDAKTU1WUzE9MApNTVZEMT0wLjAwCk1NVDI9RVExOmZjMgpNTVMyPTE4Ck1NRDI9LTU4LjAwCk1NVlMyPTMKTU1WRDI9MTAwLjAwCk1NVDM9WE1GMTpPTG9hZApNTVMz + PTgKTU1EMz0tMjYuMDAKTU1WUzM9MApNTVZEMz0wLjAwCk1NVDQ9TVNFRzE6THB0Ck1NUzQ9MwpNTUQ0PTM0LjAwCk1NVlM0PTAKTU1WRDQ9MC4wMApNTVQ1PW5vbmU6 + YXNzaWduZWQKTU1TNT0wCk1NRDU9MC4wMApNTVZTNT0wCk1NVkQ1PTAuMDAKTU1UNj1ub25lOmFzc2lnbmVkCk1NUzY9MApNTUQ2PTAuMDAKTU1WUzY9MApNTVZENj0w + LjAwCk1NVDc9bm9uZTphc3NpZ25lZApNTVM3PTAKTU1ENz0wLjAwCk1NVlM3PTAKTU1WRDc9MC4wMApNTVQ4PW5vbmU6YXNzaWduZWQKTU1TOD0wCk1NRDg9MC4wMApN + TVZTOD0wCk1NVkQ4PTAuMDAKTU1UOT1ub25lOmFzc2lnbmVkCk1NUzk9MApNTUQ5PTAuMDAKTU1WUzk9MApNTVZEOT0wLjAwCk1NVDEwPW5vbmU6YXNzaWduZWQKTU1T + MTA9MApNTUQxMD0wLjAwCk1NVlMxMD0wCk1NVkQxMD0wLjAwCk1NVDExPW5vbmU6YXNzaWduZWQKTU1TMTE9MApNTUQxMT0wLjAwCk1NVlMxMT0wCk1NVkQxMT0wLjAw + Ck1NVDEyPW5vbmU6YXNzaWduZWQKTU1TMTI9MApNTUQxMj0wLjAwCk1NVlMxMj0wCk1NVkQxMj0wLjAwClNCYXNlPTMKU3dpbmc9MC4wMApTVHJpZz0xClBQcm9qPTIK + UEZvbGQ9MApQRmlsZT0xCkdGaWxlPTIKR1NjYWxlPTAKQ2hMYXk9MApTdXJyTz0wClJldj0xMjA5MgpMRUQ9MC4wMApQQUdFPTAKUGFnZXNPbj0wCkNvcmVOPTMKU2xp + Y2U9NApVSV9vcD0xCk1pZGlBPTUKTWlkaVA9NgpERm9sZD03CkN0cmxBPTIKQ3RybEI9MTEKI2NtPUxGT0cKU3luYz00ClRyaWc9MApXYXZlPTAKUGhzZT0wLjAwClJh + dGU9MTAwLjAwCkFtcD0xMDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz04ClVXdj0wCiNjbT1MRk9HMgpTeW5jPTQKVHJpZz0wCldhdmU9MApQaHNlPTAuMDAKUmF0ZT0x + MDAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTkKVVd2PTAKI2NtPVZDQwojTEZPMT0xCiNMRk8yPTEKI0xGTzM9MQojTEZPND0xClZvaWNlcz0xClZv + aWNpbmc9MApNb2RlPTAKUG9ydGE9NS4xMApQQj0yClBCRD0yCkFyU2M9MwpBck9yZD0wCkFyTHA9MApBck9jdD0xCkFyTEw9OApBclRyPTAKRHJmdD0xCk1UdW5TPTEK + TVR1bk49MTAKTVR1blQ9MTEKVHJzcD0wCkZUdW49MC4wMApQb3J0Umc9MjUuNTAKUG9ydGFNPTAKUG9ydGEyPTEwLjAwCkFndGUxPTIKQXRycDE9MApBdm9jMT0xCkFt + dWwxPTEKQW1vZDE9MApBTURwdDE9MC4wMApBTURwQjE9MC4wMApBZ3RlMj0yCkF0cnAyPTAKQXZvYzI9MQpBbXVsMj0xCkFtb2QyPTAKQU1EcHQyPTAuMDAKQU1EcEIy + PTAuMDAKQWd0ZTM9MgpBdHJwMz0wCkF2b2MzPTEKQW11bDM9MQpBbW9kMz0wCkFNRHB0Mz0wLjAwCkFNRHBCMz0wLjAwCkFndGU0PTIKQXRycDQ9MApBdm9jND0xCkFt + dWw0PTEKQW1vZDQ9MApBTURwdDQ9MC4wMApBTURwQjQ9MC4wMApBZ3RlNT0yCkF0cnA1PTAKQXZvYzU9MQpBbXVsNT0xCkFtb2Q1PTAKQU1EcHQ1PTAuMDAKQU1EcEI1 + PTAuMDAKQWd0ZTY9MgpBdHJwNj0wCkF2b2M2PTEKQW11bDY9MQpBbW9kNj0wCkFNRHB0Nj0wLjAwCkFNRHBCNj0wLjAwCkFndGU3PTIKQXRycDc9MApBdm9jNz0xCkFt + dWw3PTEKQW1vZDc9MApBTURwdDc9MC4wMApBTURwQjc9MC4wMApBZ3RlOD0yCkF0cnA4PTAKQXZvYzg9MQpBbXVsOD0xCkFtb2Q4PTAKQU1EcHQ4PTAuMDAKQU1EcEI4 + PTAuMDAKQWd0ZTk9MgpBdHJwOT0wCkF2b2M5PTEKQW11bDk9MQpBbW9kOT0wCkFNRHB0OT0wLjAwCkFNRHBCOT0wLjAwCkFndGUxMD0yCkF0cnAxMD0wCkF2b2MxMD0x + CkFtdWwxMD0xCkFtb2QxMD0wCkFNRHB0MTA9MC4wMApBTURwQjEwPTAuMDAKQWd0ZTExPTIKQXRycDExPTAKQXZvYzExPTEKQW11bDExPTEKQW1vZDExPTAKQU1EcHQx + MT0wLjAwCkFNRHBCMTE9MC4wMApBZ3RlMTI9MgpBdHJwMTI9MApBdm9jMTI9MQpBbXVsMTI9MQpBbW9kMTI9MApBTURwdDEyPTAuMDAKQU1EcEIxMj0wLjAwCkFndGUx + Mz0yCkF0cnAxMz0wCkF2b2MxMz0xCkFtdWwxMz0xCkFtb2QxMz0wCkFNRHB0MTM9MC4wMApBTURwQjEzPTAuMDAKQWd0ZTE0PTIKQXRycDE0PTAKQXZvYzE0PTEKQW11 + bDE0PTEKQW1vZDE0PTAKQU1EcHQxND0wLjAwCkFNRHBCMTQ9MC4wMApBZ3RlMTU9MgpBdHJwMTU9MApBdm9jMTU9MQpBbXVsMTU9MQpBbW9kMTU9MApBTURwdDE1PTAu + MDAKQU1EcEIxNT0wLjAwCkFndGUxNj0yCkF0cnAxNj0wCkF2b2MxNj0xCkFtdWwxNj0xCkFtb2QxNj0wCkFNRHB0MTY9MC4wMApBTURwQjE2PTAuMDAKI2NtPUVOVjEK + TW9kZT0wCmlNb2RlPTAKc01vZGU9NQppbml0PTAuMDAKQXRrPTAuMDAKRGVjPTUwLjAwClN1cz00My4wMApTdXNUPTAuMDAKU3VzMj0xMi4wMApSZWw9MzQuNTAKVmVs + PTAuMDAKVjJJPTAuMDAKVjJBPTAuMDAKVjJEPTAuMDAKVjJTPTAuMDAKVjJGUj0wLjAwClYyUzI9MC4wMApWMlI9MC4wMApLMkk9MC4wMApLMkE9MC4wMApLMkQ9MC4w + MApLMlM9MC4wMApLMkZSPTAuMDAKSzJTMj0wLjAwCksyUj0wLjAwClNsb3BlPTAuMDAKVEJhc2U9MAojY209RU5WMgpNb2RlPTAKaU1vZGU9MApzTW9kZT0wCmluaXQ9 + MC4wMApBdGs9MC4wMApEZWM9NDUuMDAKU3VzPTQ5LjUwClN1c1Q9MC4wMApTdXMyPTAuMDAKUmVsPTc4LjAwClZlbD0wLjAwClYyST0wLjAwClYyQT0wLjAwClYyRD0w + LjAwClYyUz0wLjAwClYyRlI9MC4wMApWMlMyPTAuMDAKVjJSPTAuMDAKSzJJPTAuMDAKSzJBPTAuMDAKSzJEPTAuMDAKSzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4w + MApLMlI9MC4wMApTbG9wZT0tMTAwLjAwClRCYXNlPTAKI2NtPUVOVjMKTW9kZT0yCmlNb2RlPTAKc01vZGU9MAppbml0PTAuMDAKQXRrPTAuMDAKRGVjPTUwLjAwClN1 + cz04MC4wMApTdXNUPTAuMDAKU3VzMj0wLjAwClJlbD0xNS4wMApWZWw9MC4wMApWMkk9MC4wMApWMkE9MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJT + Mj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksyQT0wLjAwCksyRD0wLjAwCksyUz0wLjAwCksyRlI9MC4wMApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9LTYwLjAwClRC + YXNlPTAKI2NtPUVOVjQKTW9kZT0yCmlNb2RlPTAKc01vZGU9MAppbml0PTAuMDAKQXRrPTAuMDAKRGVjPTUwLjAwClN1cz04MC4wMApTdXNUPTAuMDAKU3VzMj0wLjAw + ClJlbD0xNS4wMApWZWw9MC4wMApWMkk9MC4wMApWMkE9MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJTMj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksy + QT0wLjAwCksyRD0wLjAwCksyUz0wLjAwCksyRlI9MC4wMApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9LTYwLjAwClRCYXNlPTAKI2NtPU1TRUcxClRtVW49MwpFbnY9 + MTIKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAKVHJpZz0xCiNjbT1NU0VHMgpUbVVuPTEKRW52PTEzClZlbD0wLjAwCkF0az0wLjAwCkxwdD0wLjAw + ClJlbD0wLjAwClRyaWc9MAojY209TVNFRzMKVG1Vbj0xCkVudj0xNApWZWw9MC4wMApBdGs9MC4wMApMcHQ9MC4wMApSZWw9MC4wMApUcmlnPTAKI2NtPU1TRUc0ClRt + VW49MQpFbnY9MTUKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAKVHJpZz0wCiNjbT1MRk8xClN5bmM9LTIKVHJpZz0wCldhdmU9MApQaHNlPTAuMDAK + UmF0ZT0xNjAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTE2ClVXdj0wCkRseT0wLjAwCkRNUzE9MQpETUQxPTEwMC4wMApGTVMxPTgKRk1EMT0xOC4w + MAojY209TEZPMgpTeW5jPTQKVHJpZz0wCldhdmU9MApQaHNlPTAuMDAKUmF0ZT0xMDAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTE3ClVXdj0wCkRs + eT0wLjAwCkRNUzE9MApETUQxPTAuMDAKRk1TMT0wCkZNRDE9MC4wMAojY209TEZPMwpTeW5jPTQKVHJpZz0wCldhdmU9MApQaHNlPTAuMDAKUmF0ZT0xMDAuMDAKQW1w + PTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTE4ClVXdj0wCkRseT0wLjAwCkRNUzE9MApETUQxPTAuMDAKRk1TMT0wCkZNRDE9MC4wMAojY209TEZPNApTeW5jPTQK + VHJpZz0wCldhdmU9MApQaHNlPTAuMDAKUmF0ZT0xMDAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTE5ClVXdj0wCkRseT0wLjAwCkRNUzE9MApETUQx + PTAuMDAKRk1TMT0wCkZNRDE9MC4wMAojY209TU1hcDEKTW9kZT0zCk1TcmM9MApTdHBzPTIwCk51bT0xNwojY209TU1hcDIKTW9kZT0zCk1TcmM9MApTdHBzPTIxCk51 + bT0xNwojY209TU1hcDMKTW9kZT0zCk1TcmM9MApTdHBzPTIyCk51bT0xNwojY209TU1hcDQKTW9kZT0zCk1TcmM9MApTdHBzPTIzCk51bT0xNwojY209TU1peDEKVHlw + ZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1peDIKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1peDMKVHlw + ZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1peDQKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209R3JpZApHcmlk + PTI0CkdCeXA9MAojY209T1NDMQpXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MTQKVE1EcHQ9MC4wMApQaHNlPTUwLjAwClBoc01TcmM9MApQaHNN + RHB0PTAuMDAKV051bT0xMS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD0xMDAuMDAKQ3VydmU9MjUKUHJlYz01LjAwCkZYMVRwPTAKU0ZYMT0wLjAwCkZYMVNjPTAK + RlgxRHQ9MC4wMApGWDJUcD0wClNGWDI9MC4wMApGWDJTYz0wCkZYMkR0PTAuMDAKUG9seT0wCkR0dW49My4wMApLVnNjPTI2ClZvbD0xMDAuMDAKVm9sU2M9MApWb2xE + dD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApTeW5jPTAuMDAKU25jU2M9MApTbmNEdD0wLjAwClNuY09uPTAKUG9sVz01MC4wMApQd21Pbj0wCldhVGI9 + MjcKUmVQaHM9MApOb3JtPTE1LjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT0yOAojY209T1NDMgpXYXZlPTAKVHVuZT0tNS4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTE0 + ClRNRHB0PTAuMDAKUGhzZT0wLjAwClBoc01TcmM9MApQaHNNRHB0PTAuMDAKV051bT05LjAwCldQU3JjPTAKV1BEcHQ9MC4wMApWdG9EPTEwMC4wMApDdXJ2ZT0yOQpQ + cmVjPTUuMDAKRlgxVHA9MApTRlgxPTAuMDAKRlgxU2M9MApGWDFEdD0wLjAwCkZYMlRwPTAKU0ZYMj0wLjAwCkZYMlNjPTAKRlgyRHQ9MC4wMApQb2x5PTAKRHR1bj0t + My4wMApLVnNjPTMwClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApTeW5jPTAuMDAKU25jU2M9MApTbmNEdD0w + LjAwClNuY09uPTAKUG9sVz01MC4wMApQd21Pbj0wCldhVGI9MzEKUmVQaHM9MApOb3JtPTAuMDAKUmVuZD0wCkZtdHplPTAKV1ROYW1lPTMyCiNjbT1PU0MzCldhdmU9 + MApUdW5lPTE5LjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MTQKVE1EcHQ9MC4wMApQaHNlPTAuMDAKUGhzTVNyYz0wClBoc01EcHQ9MC4wMApXTnVtPTcuMDAKV1BTcmM9 + MApXUERwdD0wLjAwClZ0b0Q9MTAwLjAwCkN1cnZlPTMzClByZWM9NS4wMApGWDFUcD0wClNGWDE9MC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgyVHA9MApTRlgyPTAu + MDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MApEdHVuPS00LjUwCktWc2M9MzQKVm9sPTEwNC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQ + YW5EdD0wLjAwClN5bmM9MC4wMApTbmNTYz0wClNuY0R0PTAuMDAKU25jT249MApQb2xXPTUwLjAwClB3bU9uPTAKV2FUYj0zNQpSZVBocz0wCk5vcm09MC4wMApSZW5k + PTAKRm10emU9MApXVE5hbWU9MzYKI2NtPU9TQzQKV2F2ZT0wClR1bmU9MzYuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0xNApUTURwdD0wLjQ4ClBoc2U9NTAuMDAKUGhz + TVNyYz0wClBoc01EcHQ9MC4wMApXTnVtPTUuMDAKV1BTcmM9MApXUERwdD0wLjAwClZ0b0Q9MTAwLjAwCkN1cnZlPTM3ClByZWM9NS4wMApGWDFUcD0wClNGWDE9MC4w + MApGWDFTYz0wCkZYMUR0PTAuMDAKRlgyVHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MApEdHVuPTAuMDAKS1ZzYz0zOApWb2w9MTAwLjAwClZv + bFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBvbFc9NTAuMDAKUHdt + T249MApXYVRiPTM5ClJlUGhzPTAKTm9ybT0xNS4wMApSZW5kPTAKRm10emU9MApXVE5hbWU9NDAKI2NtPU5vaXNlMQpUeXBlPTAKRjE9MTAwLjAwCkYxU3JjPTAKRjFE + cHQ9MC4wMApGMj0wLjAwCkYyU3JjPTAKRjJEcHQ9MC4wMApLVnNjPTQxClZvbD0wLjc0ClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAu + MDAKUG9seT0wClBvbFc9NTAuMDAKI2NtPU5vaXNlMgpUeXBlPTAKRjE9MTAwLjAwCkYxU3JjPTAKRjFEcHQ9MC4wMApGMj0wLjAwCkYyU3JjPTAKRjJEcHQ9MC4wMApL + VnNjPTQyClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQb2x5PTAKUG9sVz01MC4wMAojY209VkNGMQpUeXA9 + MTgKQ3V0PTQwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9ODAuMDAKRlMxPTE1CkZNMj0wLjAwCkZTMj0wCktleVNjbD0zNC4wMAojY209VkNGMgpU + eXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209VkNGMwpU + eXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209VkNGNApU + eXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209Rk1PMQpX + YXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwClZ0b0Q9MC4wMApEdHVuPTAuMDAK + S1ZzYz00MwpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKR2V0cj0wCiNjbT1G + TU8yCldhdmU9MApUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKRk09MC4wMApGTVNyYz0wCkZNRHB0PTAuMDAKVnRvRD0wLjAwCkR0dW49 + MC4wMApLVnNjPTQ0ClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQb2x5PTAKUG9sVz01MC4wMApHZXRyPTAK + I2NtPUZNTzMKV2F2ZT0wClR1bmU9MC4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApGTT0wLjAwCkZNU3JjPTAKRk1EcHQ9MC4wMApWdG9EPTAuMDAK + RHR1bj0wLjAwCktWc2M9NDUKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCkdl + dHI9MAojY209Rk1PNApXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwClZ0b0Q9 + MC4wMApEdHVuPTAuMDAKS1ZzYz00NgpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAu + MDAKR2V0cj0wCiNjbT1Db21iMQpNb2RlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkRldG49MC4wMApWdG9EPTAuMDAKRkI9MC4w + MApGQlNyYz0wCkZCRHB0PTAuMDAKRGFtcD0wLjAwCkRtcFNyYz0wCkRtcERwdD0wLjAwCkV4Yz0wLjAwCkluaj0wLjAwCklualNyYz0wCkluakRwdD0wLjAwClRuZT01 + MC4wMApUbmVTcmM9MApUbmVEcHQ9MC4wMApTZWM9MC4wMApTZWNTcmM9MApTZWNEcHQ9MC4wMApEaXN0PTAuMDAKRHJ5PTAuMDAKVm9sPTEwMC4wMApWb2xTYz0wClZv + bER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCkZpbGw9MAojY209Q29tYjIKTW9kZT0wClR1bmU9MC4wMApLZXlTY2w9 + MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApEZXRuPTAuMDAKVnRvRD0wLjAwCkZCPTAuMDAKRkJTcmM9MApGQkRwdD0wLjAwCkRhbXA9MC4wMApEbXBTcmM9MApEbXBE + cHQ9MC4wMApFeGM9MC4wMApJbmo9MC4wMApJbmpTcmM9MApJbmpEcHQ9MC4wMApUbmU9NTAuMDAKVG5lU3JjPTAKVG5lRHB0PTAuMDAKU2VjPTAuMDAKU2VjU3JjPTAK + U2VjRHB0PTAuMDAKRGlzdD0wLjAwCkRyeT0wLjAwClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQb2x5PTAK + UG9sVz01MC4wMApGaWxsPTAKI2NtPVNoYXBlMQpUeXBlPTAKRGVwdGg9MC4wMApETVNyYz0wCkRNRHB0PTAuMDAKRWRnZT03NS4wMApFTVNyYz0wCkVNRHB0PTAuMDAK + SW5wdXQ9MC4wMApPdXRwdXQ9MC4wMApIaU91dD0wLjAwCiNjbT1TaGFwZTIKVHlwZT0wCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9NzUuMDAKRU1T + cmM9MApFTURwdD0wLjAwCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKSGlPdXQ9MC4wMAojY209TWl4MQpQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQ + bk1TPTAKI2NtPU1peDIKUGFuPTAuMDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1NaXgzClBhbj0wLjAwCk1peD01MC4wMApQbk1kPTAKUG5N + RD0wLjAwClBuTVM9MAojY209TWl4NApQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPVhNRjEKVHlwPTAKQ3V0PTI5LjAwClJlcz03 + LjUwCkZNMT0xMDIuMDAKRlMxPTE1CkZNMj0wLjAwCkZTMj0wCktleVNjbD0yMC4wMApGT2ZmPS0xLjUwCkZPTW9kPTAuMDAKRk9TcmM9MApYRk09MC4wMApYRk1EPTAu + MDAKWEZNUz0wCkJpYXM9MC4wMApPTG9hZD0xMi4wMApDbGljaz0wLjAwCkRydj0xClJvdXQ9MgpUeXAyPS0xCiNjbT1YTUYyClR5cD0wCkN1dD0xNTAuMDAKUmVzPTAu + MDAKRk0xPTAuMDAKRlMxPTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKRk9mZj0wLjAwCkZPTW9kPTAuMDAKRk9TcmM9MApYRk09MC4wMApYRk1EPTAuMDAKWEZN + Uz0wCkJpYXM9MC4wMApPTG9hZD0wLjAwCkNsaWNrPTAuMDAKRHJ2PTEKUm91dD0wClR5cDI9LTEKI2NtPVNCMQpSYW5nZT0wCkZyZXE9MC4wMApGTVNyYz0wCkZNRHB0 + PTAuMDAKT2Zmcz0wLjAwCk9NU3JjPTAKT01EcHQ9MC4wMApNaXg9NTAuMDAKTU1TcmM9MApNTURwdD0wLjAwCiNjbT1TQjIKUmFuZ2U9MApGcmVxPTAuMDAKRk1TcmM9 + MApGTURwdD0wLjAwCk9mZnM9MC4wMApPTVNyYz0wCk9NRHB0PTAuMDAKTWl4PTUwLjAwCk1NU3JjPTAKTU1EcHQ9MC4wMAojY209RGlzdDEKVHlwZT0wCklucHV0PTAu + MDAKT3V0cHV0PTAuMDAKUHJlVGlsdD0wLjAwClBzdFRpbHQ9MC4wMApDbnRGcmVxPTEwMC4wMApMb3c9MC4wMApIaWdoPTAuMDAKUG9zdEZsdD0wCiNjbT1EaXN0MgpU + eXBlPTAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMApQcmVUaWx0PTAuMDAKUHN0VGlsdD0wLjAwCkNudEZyZXE9MTAwLjAwCkxvdz0wLjAwCkhpZ2g9MC4wMApQb3N0Rmx0 + PTAKI2NtPUZvbGQxClJpcHBsZXM9NApGb2xkcz0xMC4wMApGTW9kPTAuMDAKRlNyYz0wCkJpYXM9MC4wMApCTW9kPTAuMDAKQlNyYz0wCkZSYXRpbz01MC4wMApGU2xv + cGU9MTAuMDAKQ0FuZ2w9NTAuMDAKTGl2ZT0wCiNjbT1Gb2xkMgpSaXBwbGVzPTQKRm9sZHM9MTAuMDAKRk1vZD0wLjAwCkZTcmM9MApCaWFzPTAuMDAKQk1vZD0wLjAw + CkJTcmM9MApGUmF0aW89NTAuMDAKRlNsb3BlPTEwLjAwCkNBbmdsPTUwLjAwCkxpdmU9MAojY209VkNBMQpQYW4xPTAuMDAKUGFuTVMxPTAKUGFuTUQxPTAuMDAKVm9s + MT0zOC4wMApWQ0ExPTEKTW9kU3JjMT0wCk1vZERwdDE9MC4wMApQYW4yPTAuMDAKUGFuTVMyPTAKUGFuTUQyPTAuMDAKVm9sMj01MC4wMApWQ0EyPTEKTW9kU3JjMj0w + Ck1vZERwdDI9MC4wMApQYW4zPTAuMDAKUGFuTVMzPTAKUGFuTUQzPTAuMDAKVm9sMz01MC4wMApWQ0EzPTEKTW9kU3JjMz0wCk1vZERwdDM9MC4wMApQYW40PTAuMDAK + UGFuTVM0PTAKUGFuTUQ0PTAuMDAKVm9sND01MC4wMApWQ0E0PTEKTW9kU3JjND0wCk1vZERwdDQ9MC4wMApNVDE9MApNVDI9MApNVDM9MApNVDQ9MApQQjE9MApQQjI9 + MApQQjM9MApQQjQ9MApCdXMxPTAKQnVzMj0wCkJ1czM9MApCdXM0PTAKU2VuZDE9MC4wMApTblNyYzE9MApTbkRwdDE9MC4wMApTZW5kMj0wLjAwClNuU3JjMj0wClNu + RHB0Mj0wLjAwCkF0dFM9MQojY209R3JpZEZYCkdyaWQ9NDcKR0J5cD0wCiNjbT1Nb2RGWDEKTW9kZT0wCkNlbnQ9MjAuMDAKU3BlZD01MC4wMApQaE9mZj01MC4wMApE + cHRoPTUwLjAwCkZlZUI9MC4wMApNaXg9NTAuMDAKTEN1dD0wLjAwCkhDdXQ9MTAwLjAwClF1YWQ9MC4wMApRcGhzPTI1LjAwCkxlcT0wLjAwCkhlcT0wLjAwClExPTAu + MDAKUTI9MC4wMApFUW9uPTEKI2NtPU1vZEZYMgpNb2RlPTAKQ2VudD0yMC4wMApTcGVkPTUwLjAwClBoT2ZmPTUwLjAwCkRwdGg9NTAuMDAKRmVlQj0wLjAwCk1peD01 + MC4wMApMQ3V0PTAuMDAKSEN1dD0xMDAuMDAKUXVhZD0wLjAwClFwaHM9MjUuMDAKTGVxPTAuMDAKSGVxPTAuMDAKUTE9MC4wMApRMj0wLjAwCkVRb249MQojY209RGVs + YXkxCk1vZGU9MApNaXg9MC4wMApGQj0xMC4wMApDQj0yNS4wMApMUD0xMDAuMDAKSFA9MC4wMApEcnY9MC4wMApTeW5jMT01ClN5bmMyPTYKU3luYzM9NApTeW5jND00 + ClQwPTEwMC4wMApUMT0xMDAuMDAKVDI9MTAwLjAwClQzPTEwMC4wMApQYW4xPS0xMDAuMDAKUGFuMj0xMDAuMDAKUGFuMz0tMTAwLjAwClBhbjQ9MTAwLjAwCiNjbT1E + ZWxheTIKTW9kZT0wCk1peD0wLjAwCkZCPTEwLjAwCkNCPTI1LjAwCkxQPTEwMC4wMApIUD0wLjAwCkRydj0wLjAwClN5bmMxPTQKU3luYzI9NApTeW5jMz00ClN5bmM0 + PTQKVDA9MTAwLjAwClQxPTEwMC4wMApUMj0xMDAuMDAKVDM9MTAwLjAwClBhbjE9LTEwMC4wMApQYW4yPTEwMC4wMApQYW4zPS0xMDAuMDAKUGFuND0xMDAuMDAKI2Nt + PVNoYXBlMwpUeXBlPTAKRGVwdGg9MC4wMApETVNyYz0wCkRNRHB0PTAuMDAKRWRnZT03NS4wMApFTVNyYz0wCkVNRHB0PTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4w + MApIaU91dD0wLjAwCiNjbT1TaGFwZTQKVHlwZT0wCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9NzUuMDAKRU1TcmM9MApFTURwdD0wLjAwCklucHV0 + PTAuMDAKT3V0cHV0PTAuMDAKSGlPdXQ9MC4wMAojY209TWl4NQpQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPU1peDYKUGFuPTAu + MDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1SZXYxCk1vZGU9MApEcnk9ODIuMDAKV2V0PTI1LjUwCkZCPTQwLjAwCkRhbXA9OC4wMApTaXpl + PTc1LjAwClNwZD0yOS4wMApEcHQ9MzQuMDAKREZCPTU4LjUwCkRTaXplPTUwLjAwCkVNaXg9MTAwLjAwCkRNb2Q9NzEuMDAKRFNwZD04NS41MApQcmU9MC4wMAojY209 + Q29tcDEKVHlwZT0xClJhdD0xMDAuMDAKVGhyZXM9LTIwLjAwCkF0dD0zMC4wMApSZWw9NTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMAojY209Q29tcDIKVHlwZT0x + ClJhdD0xMDAuMDAKVGhyZXM9LTIwLjAwCkF0dD0zMC4wMApSZWw9NTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMAojY209RVExCmZjMT0xOC41MApyZXMxPTI1LjAw + CmdhaW4xPTguNTAKZmMyPTEwMC4wMApyZXMyPTQzLjAwCmdhaW4yPS0yNC4wMApmYzM9NTAuMDAKcmVzMz03OC4wMApnYWluMz0tMS4wMApmYzQ9MTAwLjAwCnJlczQ9 + MjUuMDAKZ2FpbjQ9LTEzLjUwCiNjbT1FUTIKZmMxPTIwLjAwCnJlczE9MjUuMDAKZ2FpbjE9MC4wMApmYzI9NDAuMDAKcmVzMj0yNS4wMApnYWluMj0wLjAwCmZjMz02 + MC4wMApyZXMzPTI1LjAwCmdhaW4zPTAuMDAKZmM0PTgwLjAwCnJlczQ9MjUuMDAKZ2FpbjQ9MC4wMAojY209VkNGNQpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRy + dj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209VkNGNgpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRy + dj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209U0IzClJhbmdlPTAKRnJlcT0wLjAwCkZNU3JjPTAKRk1E + cHQ9MC4wMApPZmZzPTAuMDAKT01TcmM9MApPTURwdD0wLjAwCk1peD01MC4wMApNTVNyYz0wCk1NRHB0PTAuMDAKI2NtPVhNRjMKVHlwPTAKQ3V0PTE1MC4wMApSZXM9 + MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMApGT2ZmPTAuMDAKRk9Nb2Q9MC4wMApGT1NyYz0wClhGTT0wLjAwClhGTUQ9MC4wMApY + Rk1TPTAKQmlhcz0wLjAwCk9Mb2FkPTAuMDAKQ2xpY2s9MC4wMApEcnY9MQpSb3V0PTAKVHlwMj0tMQojY209RGlzdDMKVHlwZT0wCklucHV0PTAuMDAKT3V0cHV0PTAu + MDAKUHJlVGlsdD0wLjAwClBzdFRpbHQ9MC4wMApDbnRGcmVxPTEwMC4wMApMb3c9MC4wMApIaWdoPTAuMDAKUG9zdEZsdD0wCiNjbT1EaXN0NApUeXBlPTAKSW5wdXQ9 + MC4wMApPdXRwdXQ9MC4wMApQcmVUaWx0PTAuMDAKUHN0VGlsdD0wLjAwCkNudEZyZXE9MTAwLjAwCkxvdz0wLjAwCkhpZ2g9MC4wMApQb3N0Rmx0PTAKI2NtPU51UmV2 + MQpQcmU9MC4wMApEYW1wPTgwLjAwCkRlY2F5PTUwLjAwClNpemU9MTAwLjAwClRvbmU9MC4wMApXaWR0aD03NS4wMApNaXg9MjAuMDAKI2NtPVpNYXMKUmV0MT0wLjAw + ClJldDI9MC4wMApNYXN0PTI1LjUwClhZMUw9NDgKWFkyTD00OQpYWTNMPTUwClhZNEw9NTEKWFkxVD01MgpYWTJUPTUzClhZM1Q9NTQKWFk0VD01NQpPU0MxPTU2Ck9T + QzI9NTcKT1NDMz01OApPU0M0PTU5Ck1TRUcxPTYwCk1TRUcyPTYxCk1TRUczPTYyCk1TRUc0PTYzCk1TRUc1PTY0Ck1TRUc2PTY1Ck1TRUc3PTY2Ck1TRUc4PTY3ClJl + djE9NjgKUG4zPTAKUG40PTAKUG41PTAKUG42PTAKUG43PTAKUG44PTAKUG45PTAKUG4xMD0wClBuMTE9MApSYWNrMD02OQpSYWNrMT03MAojY209TGF5TWVtCkxheU1l + bTE9MApMYXlNZW0yPTAKTGF5TWVtMz0wCkxheU1lbTQ9MApMYXlNZW01PTAKCgoKCi8vIFNlY3Rpb24gZm9yIHVnbHkgY29tcHJlc3NlZCBiaW5hcnkgRGF0YQovLyBE + T04nVCBUT1VDSCBUSElTCgokJCQkNzczNDAKP2FhYWFpYWRwOmprampqamxvOmFhYWFpYWxwOmdkZ2NlbmViOnBucHBocGRwOnBtcHBocGRwOmNhYWFhYWFhOgpwbXBw + aHBscDphYWFhYWFlYTpwbnBwaHBscDphYTpkbzpkcDpsbzplYjplYzpscDpjYTpkbjpnZjplYTpnYjphCms6YmE6b2E6aWE6bG46ZGE6ZmQ6aGU6ZGM6YmI6bWE6aHA6 + cHA6YWMhdEFhYkEyNjV0S0ExMGVpU0hlZ2dwaGYKaGNUZ2lIRmhjZ2pnZWdoSmNvZ29nbGhkZ2dBNDJ0S0E3NHRLQTEwWm1ia2dkZWVsamNhZWVnTGxlZ2thaGtwawpk + aW9oa21jT21lZWxsaUloaGtscGFuY2libWJsYVFlZ2Nsb2Vjb2ZlZ21tZmRpVG5oZGRwZmFwZ29qa2ttcGtjCm1nY21qa2piaHBmY2NiaWNiY21vZ2xlb2lwbGZqZW1n + Z3RhZUExME9rZGFvQXRBYWJBOWVpZ3BoaExoY2dlSFMKZ2RMaGMxQTI0M3RQQTEzOHRBYWJBMjY1dFBBNnFBMTI3dFBBNnFBMTI3dEtBMTBoZ2dqZ2RUZ3BoY2dqTGdv + YwpvaGRnZGdtQTlPZ2Nia2tua2FsZmppQVpBMWFkYm1BMm5hbG1pZmpoY2JsbWlmamhnYWVpam9BNGxpbGRjbGttCnBpaG9wb1pBdEFaQTVxQTFhZGJtQTJuYWxtaWZq + aGNibG1pZmpoZ2FlaWpvQTRsaWxkY2xrbXBpaG9wb1pBSU0KWUtkZDFhcEVNbmhicEVJTWNwRUExS0VjamZtZXBFYmVrb2ZwRWJla29ncEVlaG9iaG9FQTFpaUVqajFp + cEVBMQpqaUVpcG1janBFamoxa2hFaWZvbGtwRWJvaWZsaEVBMVdFYmVrb21oRU1uaG1wRU1uaDFFa2VoYW5wRUExb2lFCmpqMW9wRUExcGlFaXBtY1lFbW5tbWFkRm1j + cGZhaEZpcG1jYWxGQTFORk1uaGJkRmlmb2xiaEZpZm9sYmxGZmMKbGlicEZBMWNlRm1ubW1jaEZBMWNtRmVob2JjcEZtbm1tZGRGbWNwZmRoRmlwbWNkbEZBMUtGTW5o + ZWRGaWZvbAplaEZpZm9sZWxGZmNsaWVwRkExZmVGbW0xZmhGQTFmbUZlaG9iZnBGbW0xZ2RGbWNwZmdoRmlwbWNnbEZBMWhhCkZNbmhoZEZpZm9saGhGaWZvbGhsRmZj + bGlYRkExaWNGZ2dvZ2lkRkExaWdGa2VwYWloRmdnb2dpakZvYnBraWwKRmVob2JpbkZBMWphRmlmb2xqYkZtY3BmamRGbWNwZmpmRmNqbm1qaEZBMWprRmdnb2dqbEZB + MWpvRmtlcGFqcApGZ2dvZ2tiRm9icGtrZEZlaG9ia2ZGQTFraUZpZm9sa2pGbWNwZmtsRm1jcGZrbkZjam5ta3BGQTFsY0ZnZ29nCmxkRkExbGdGa2VwYWxoRmdnb2ds + akZvYnBrbGxGZWhvYlFGQTFXRmlmb2xtYkZtY3BmbWRGbWNwZm1mRmNqbm0KbWhGQTFta0ZnZ29nbWxGQTFtb0ZrZXBhbXBGZ2dvZ25iRm9icGtuZEZlaG9ibmZGQTFu + aUZpZm9sbmpGbWNwZgpubEZtY3Bmbm5GY2pubW5wRkExb2NGZ2dvZ29kRkExb2dGa2VwYW9oRmdnb2dvakZvYnBrb2xGZWhvYm9uRkExCnBhRmlmb2xwYkZtY3BmcGRG + bWNwZjFGY2pubXBoRkExcGtGZ2dvZ3BsRkExcG9GdFJaQTVxQTNhZUE2YWVBMjIKZmYxbmZCaXBtY3BmbG1qajFmakNoa2xmZ2FCbGhnbmJsQ01uaGtkbG1KZ2dvZ0Ru + ZmNlaWJCcU1uaGNkbG1mbQppcEZDZWxuZW9vQmtsa2tja0NBM09oa2JlR0EyQ2ZmMW5mQkE3WTFHQ0ExMXBuWTFDQTdycUE3ckExSzFBN3JBCjFrYUtBN3JBMU9LQTdy + QTFnYUVBN3JBMWZhRUE3ckExS0VBN3JBMVJFQTdyQTFIRUE3ckExTkVBN3JBMWlpRUEKN3JBMWphRUE3ckExamlFQTdyQTFrYUVBN3JBMWtpRUE3ckExbGFFQTdyQTFs + aUVBN3JBMVdFQTdyQTFtaUVBNwpyQTFuYUVBN3JBMW5pRUE3ckExT0VBN3JBMW9pRUE3ckExcGFFQTdyQTFwaUVBN3JBMTV0UlpBNXFBM2FmQTJhCmJBMmFkQTJhYmFl + QTI5eUExM1BLQTEzV0tBMTRFQTEzSEVBN3JBMVdLQTExcUE3ckExSzFBN3JBMWthS0E3ckEKMU9LQTdyQTFnYUVBN3JBMWZhRUE3ckExS0VBN3JBMVJFQTdyQTFIRUE3 + ckExTkVBN3JBMWlpRUE3ckExamFFQQo3ckExamlFQTdyQTFrYUVBN3JBMWtpRUE3ckExbGFFQTdyQTFsaUVBN3JBMVdFQTdyQTFtaUVBN3JBMW5hRUE3CnJBMW5pRUE3 + ckExT0VBN3JBMW9pRUE3ckExcGFFQTdyQTFwaUVBN3JBMTV0UlpBNXFBM2FmQTJhYkEyYWRBMmEKYmFlQTI5eUExM1BLQTEzV0tBMTRFQTEzSEVBN3JBMVdLQTExcUE3 + ckExSzFBN3JBMWthS0E3ckExT0tBN3JBMQpnYUVBN3JBMWZhRUE3ckExS0VBN3JBMVJFQTdyQTFIRUE3ckExTkVBN3JBMWlpRUE3ckExamFFQTdyQTFqaUVBCjdyQTFr + YUVBN3JBMWtpRUE3ckExbGFFQTdyQTFsaUVBN3JBMVdFQTdyQTFtaUVBN3JBMW5hRUE3ckExbmlFQTcKckExT0VBN3JBMW9pRUE3ckExcGFFQTdyQTFwaUVBN3JBMTV0 + UlpBNXFBM2FmQTJhYkEyYWRBMmFiYWVBMjl5QQoxM1BLQTEzV0tBMTRFQTEzSEVBN3JBMVdLQTExcUE3ckExSzFBN3JBMWthS0E3ckExT0tBN3JBMWdhRUE3ckExCmZh + RUE3ckExS0VBN3JBMVJFQTdyQTFIRUE3ckExTkVBN3JBMWlpRUE3ckExamFFQTdyQTFqaUVBN3JBMWthRUEKN3JBMWtpRUE3ckExbGFFQTdyQTFsaUVBN3JBMVdFQTdy + QTFtaUVBN3JBMW5hRUE3ckExbmlFQTdyQTFPRUE3cgpBMW9pRUE3ckExcGFFQTdyQTFwaUVBN3JBMTV0UEE2cUExMjd0UEE2cUExMjd0UEE2cUExMjd0UEE2cUExMjd0 + CkFaQTVxT2NnY29EV3BhZWlJQWNtSWxsS2lsMVFnYWduYmpDSGZibXBJY2lhbW1vRFBmY2lkUU9wYmhvQmhpYmMKamxCYWlpa2tuQkFwaG9rbG1Pb2NtbkRrYW9namlJ + ZGllbW1oQmdhb2phbkNmaWVlTUdsYWVoZ2FCbW1haWdsRwpIcGdlZkRBZ2dwbUlobW1lYmNHZ2lJamRET2tubmJEY2lnbWNwR2hhbWtiakJnZW9pZGhDa2FSTkJPSm5k + QmVtCmFvYmlHaG1sbGFkQ1dwZWFlR0hwa2RkQm5hbmljZkRvbWdoZWVHbGFraXBrQmthamRiakNtaW1ubmlCZ2Flam8KZ0lLZGtKRGVpbWRkZUdOa2Flb0RPY25taVFP + U2FoRG5hWmZiQ2NpbmZwY0JPb2tvY0lXYWRta0RQY2JlblFmaQpuZGpiQm9paGFwZUJkaWFmYWVDQU1qYkJPZm5tbFFUb2ZIR2dhamdqbkRBYmlnY2RsV2ZmZWVJSG5u + aWJJUmptCmtjRGdhZ29pbkJiaWVkaW5EV2FsYWpJQWppamtJSGpwZW9CUlpla0JsYWJvZmlEUm9tbGpCUG5lZW5EZm1jbWUKaUduaWJncGhCbmFNY2tHUGJwZm5JSG1r + cGZRaG1qYk5HbWliZmRlQ1BIbGRJa21tcGNuR2hhQmNnQlBwZVZEYwppYWphZkNXbmdjY0lqaWRnb2tETmppa25ES3BnY2JDcGloYm5qRGppa2ZQQlBhbG1qUWtpWmJr + R2JtbGNFQ1BtCmRvY2xta2FkZ2JwQkFkamVtUVJibGJoRHBpampwZEJBcGVSUU5mYW5qQmdpcG5ZRGplb2JNQ2NpbGNkYkdQZmUKZGpCaGFrZ2dhRGZlYW5GR3Bha29h + bkNIYWhIRGhtb2RkbUNtZWdsYmxDTmFqa2ZCUGhubmdRY2lsbWttQmxpZApmcG9CV0hRRGdpZmhaQ2ZpbGtsZkJLbnBmb0llaWhmYWRHQWFtb2NRTmFoYm9CYmlsYW1u + QktmYmhoQldibWlsCklubWhnSENqYW1lSEJSY25vakRrYWhna2pEYmlqYWxnRGRtamtkZ0NQb25iaUd0QVpBNXExb2VJbGNEcG1raG8Kb0RXQWVkRGNtbmdwZ0JsaXBo + aGJHYmlwa2RmSWZhbmZGR3NpaWRscGhEc21lb2pjakdsaWltZGJDY2djZmdrQwpBb3BrZmxscGFtYmNtR2plb3BhakNhZ2lwU0diZW1tbWhEc2thaWRibEdqYWFsa2hR + c0hsZWFiRFJrbGJjRGhjCmZrSEdib3BtWkNtbW5maGZDSGJpYmdCbWtraE5Hb2VFb2NCS2ZlY25DUGdtbWlsbXFuYWtoa2ZRb2lkZWlnQnEKaGlsYmVrQmxlZmZtaEJo + YW5ob2tEcGFob2RnRHNmY25mTEdubWFkMUNuYWNpcG9EaW1sa29oQmhvRmJjR2thaApvcGlEZ21tZ2JnQ21tbWRwY0RrbVJucEJobWZvYW5HS2xiYmpDa2VoZm1jRGtt + Y2ltY0RnYWplb2lEZGVubGZmCkdITmJkSWFpZWthZ0RiZVBncEdLZWhtbkluZ1dnZENib2xqY2ZDV2VrYm1DT2toa2tJbmdrZGVpR25hMWlsUW4KaWtja2dES2JlbmtE + Z21nbHBlQmxrYmdia0NuYWhjaWtCTmNtZ2VHbmNqZmZjQ2lvR2JnQ3NkaW9nZGRHUmtsbAplSWRtYWprY0RrYWdvbWlCZ2Fhb2xtSWRrb3BMQ2RnQ2JlQ2VpY2tRQnNt + b2ZnTUNjaXBrY2tCc29pZ2FpakJsCm1PaW5EZm9oYmFtQ3BlcGNobkdrYWtiZGZCS2hiUWxtZ2VLbWpCT2JmZWxDbGliaGpsQm5paG1lcERiaUxja0IKamlrbUdEbGFr + amZlQ05ibEdRZGVmbWtsQk9jamhoRGhtZG1WR1ppY2JkR2Jjb2lnaEdtaWtibmNEa2FqbWRnRApSb21qaElCZWhiZkdOZWhjaUNUcGhhbUNuYWdjb29JamdnbmFpQ2hp + Z29uakRBYmRpbURtZWNvYmlDQWdqamhkCmxraVllZUdraWllb3BCcGluZ2VqRGFpb25oakRzMW5lcGhraERsaW1uYmlEcXRBWkE1cU9pbWJmRGdhZ2VwY0kKa21mYWVk + R2Jpa2doakNLa2FvYkRmbWxlZWVHY2lwbDFEUmVuSkNwaWNjSkNpaW5rUERobWlrYXBHQW9lcGJkbApPb2ZraERXbmtTUWdlb25ncEdIY2xkZUdmYWVlbGlCbmlmZ2pr + QmVtZGlnakdwaW9uZGlDS2toaGpDSGtpamhJCmppZ2xpakJoYXBoY2tHa2lvakVHbWlucFBEYm1mbGdnR2xhZGlubERoYVFnZENkZWlkZW5DTnBhY2hEUG5jamIKREhm + ZUZCZ2lsYm9rRFBnbmpjbG1jaWRtaGxHT2VqZmJHT2ZtaWhRa2lwZW9wRE9mYmlkQkhnZFJEcGFtZmhjQgpkbWFqZGtHaGlkamJpR0tjZmFiQm5pY3BpbkRoYWdoY21C + Z2FtY2FtR1JjaG5lRGNtZXBmaENwZWVlZ3BDamFnCmdsbURQYmllbkdoaW5lbWdEcGVlaWZsR2xhamRkaUdSa2pwbkJsaWZua2ZCbm1tZEJHamltY2hkR2thZm1qaUQK + cGVZZ3BDbWVlZ2hqR0Fka2dpQlBvZUFJYW1rY1RDa2lrYnBoQmppaGJmaENXZWtqbEJjZWlmY2NDY2lOT0RrYQplZ25lRHBha2lwZUJLcG1halFhaWVvamtCT2JraGtE + UGRlaWNJY21GZWVHYm1raWhsQ0tnZG5kRGZpUFVHa2FvCmlwZ1FtbWhvaGFHaWlpbWNsR1BsYnBkQk5GZnBCZW1mbGVtQ05oa2FiQ2NpbmVoYUdkaWJwb2hCbWlwbW9l + Qm4KaWJvVUdOYWJka0drYWRmbWVJTnBrZ2FEUFJqaEJwYWRra21EZ2lGbGNEY2lpbXBpQmdhaWZFQmthYmhDMWphbgpwYWlCZmluZ21kQmpha2ZhYkdlaW1kYW1HcGlm + YmliQk5FZGxCYWlnbWRkQ1BqZ2lmSWplaGRhb0NIbWZvalFQCm9kZGlEb2lmamZuQ2ttZ2dlZENiaW1kWURtaWxmZ2NHQWpvbmpEZWVpbGZwQ1RnaGhqR0FobmNiSUtv + cERRbGkKbmFtakJsaWhtaWhEV2ppaW9RUmtkaGdDVGxmZmpDTmRmYmxEQWdtampEdEFaQTVxT2xhZ2VEaW1hZkVDZmVvYwpiaENvaWtlZGtHT2VnZmJDZm1jZGVpR2xh + amZTQlBWamZJYWlhZ2hqR2xtbWRmZkNwaWhvcG5EZGlrcGhpR3BhCmNsYm1Ha2ltZ2xmQmRpZmxubkJIcG9nY0djaXBiZ2hDamVrbGRnR2xlRmVqQ2FpYmZmbUdBVWtq + SU5saWJkQm0KbWVpZmJDamFJYmdDa2lnY2VtR2RpY2NuZEJtaUNwbUJmaW1oYmVHamFiZGVwR1JtcGNjRGFpaWNubERBaXBjZwpCcGlkbGNjR2dpYnBraERqZWpoYmhD + bmllaWJoQ09la2FvR2dlaW5na0NmZW9oZWRHT2RsbmlCaGFmbW9uQmllCmFuY2lHZG1lb2NvR0hhb2NpQmdhb3BtakRmYWllYmNCY2VkZGRiR1dwa09EZ2luZGNpQ2Zp + Um1mQnBlb3BoY0MKaGFvZGxsQlBnb0pDUG5pcGNkbWJpZ2VQQmxhamdjZ0dnaWttZGVHT2hvam5JbGlqYmpjRFBuamJtUWZhbGhncApHYW1vcGRnR2hpamlsZUJBZ2hp + aUJsaWdraGRHYW1pZGNwQ25lY2xnaENBY25uZGxtQWpua29RY2VpZ2RkQ25pCm9ma2tCZmFjZ2drR2hpbmRSQ2RtTGdpQ1BHa2xsbVdlbm9lUWphbGZjZUNraWFuaW5C + Ym1hbFhDV2Zna2FCa2kKbm9FQ1dEQ0luYTFVQmhpcGlnbkNXbmhhaENobURlaEdlbWdnYWpDTmJoaGRDUGZjZW1RamFibmVpRGdlZWplaApDY2llb2JoQ05jY0JHbGVu + cGVrQ2llaWxTR0FoZlVCQWRib2xkbW1ta3BoZ0NiZWxiWkNraW5iZmdHS2tubmlCCm9lcGFhbENmYWdoWERLb2NjbERsaWhsRkNwbWdqYWlDbG1wZWZjR05pY25lQm5h + YWpBQ25pZGdqaERlaWdscGoKREtqaW1uQldkaW1qUWZpR2ljQmJpYm5pcERIaWRlcERBZmloa0NlZWNtQzFUZmVjZEdSZGxKR25hY25rYkRmbQpramZtQ2ZtaWRmZkdQ + am9nbElBY2RmaklUcG9naUNoaW9sbWJCQVhkZEN0YW1hZEE1cWVlZWpmY2VoYWVBMmFtCkEyZXBTZWRkYkFMaGpKYW5WQTFmZ2VkZWdVQTNaYmNBMWVnZW5lcGRlQTNh + b1ZBMWVkZ3BnbmdjVUEyTVZBMWUKcFNlZFVBM2FuVkExZmdlZGVnZGRBTGhqSlpiY0ExU0ZkYkE0TVZBMWVnZW5lcGRiQTNhb1ZBMWVwU2VkZGRBMwphblZBMWZnZWRl + Z2RlQTNaYmNBMWVuZ2poaWRkQTNNaGJBMWVvZ3BnamhkSlVBMWFvVkExZXBTZWRkZUFIZGJkCmlhblZBMWVkZ3BnbmdjZGJBMk1WQTFlZ2VuZXBVQUhVZGJhb1ZBMWVn + ZW5lcGRkQTNhb1ZBMWZpZW5lZ2RiQUwKaGpKYWpoYkExU2dpTGhhSlVBMU1iY0ExZW5namhpZGJBM01UQTFlbmdqaGlVQUhVZGVNaGlBMWVvZ3BnamhkSgpkYkExYW9W + QTFlbmdqaGlkZUEzTWhjQTFmY2dqZ29naGRiQTJNVEExZmNnamdvZ2hVQTJNaGlBMWZnZWRlZ2RiCkEzWlZBMWZpZW5lZ1VBM01oY0ExU2dpTGhhSmRiQUpNVkExU0ZV + QWhjSFVkaE1iaUExZWVnamhkVGRiQTJNVkEKMWVlZ2poZFRVQTJNYmNBMWVnZ3BnbWdlZGJBMk1iZUExZWdncGdtZ2VVQTJNYmlBNWVtTGhqSkEyOExoakpoYwpIZGRS + QTI4SGRkZGJBMjNlbUxoakpBMzF3QTI0M3RhZXdBMnlBM3BqU1lCcTJBMTExZ2dkZWprQlkxWENxMUExCjExZWtTbmVJcG9ZWENxMUExMTFub2NoY2NJWmttTkNLcGhO + Q3FBMTE4Q3BuZmZBQ3FBMTE1cG9TWUJwaVkxQnEKQTExNXBsa2hwb0JwaFNZQnFBMTE1cGFlbm9kQm9scGpvZEJxQTExNW9lcGRtaEJPanBtaUJxQTExNW5qamprbQpC + bmVlZmtuQnFBMTE1bW9DamJCbWlvbGpiQnFBMTE1aWVtbGdsQmhrY2RnbkJxQTExNUNrbGNqQmRmYWRjbEJxCkExMTVhbWhnbmVJcGdjZm5oSXFBMTE1V25lb2tkbWdh + amVwZmRtcUExMDh5UWdjZGxsYVpjbWRtamVSZXBkbXEKQTEwN3VzcGlZWENxMUExMDd1c3ZxMUExMDd1c3EyQTEwN3VzbGViZ2xjbGxlZ2lubWVkbHFBMTA3dXNBM1Vi + ZQpmZ2RtcUExMDd1c2duaGtuZERIcGVvYkJxQTEwN3VzenZxQTEwN3VzenZxQTEwN3VzenZxQTEwN3VzenZxQTEwCjd1c3p2cUExMDd1c3h2cUExMDd1c3h2cUExMDd1 + c3h2cUExMDd1c3h2cUExMDd1eHN2cUExMTFlbW9icGtEc0EKMTE5bmFtbWptRGllb2xmYkRBMTE5ZW1vYnBrUUExUERWa29laERBMTE1ZW1vYmhrUUEyREExNjUxbmVr + ZFJDTwpoa2plQkExMTlrYWhhYmZDamlqamRqQkExMTluaWhrcGVCbW5tbWFtQkExMTluaWhrVEJPaGtiZUlBMTY1NWFmCkEydE5BNnlmaWVtZG1jZVgzZ2FlaVJiaVgz + dEF3QTJ5QTgxOTF0d0E3ZmtVSGZoTGhnSlRMZ2NnbUpIZWZoaWgKYWdwaGNUQTEydGFld0EyeUEzcGpTWUJxMkExMTFnZ2RlamtCWTFYQ3ExQTExMWVrU25lSXBvWVhD + cTFBMTExbgpvY2hjY0laa21OQ0twaE5DcUExMThDcG5mZkFDcUExMTVwb1NZQnBpWTFCcUExMTVwbGtocG9CcGhTWUJxQTExCjVwYWVub2RCb2xwam9kQnFBMTE1b2Vw + ZG1oQk9qcG1pQnFBMTE1bmpqamttQm5lZWZrbkJxQTExNW1vQ2piQm0KaW9samJCcUExMTVpZW1sZ2xCaGtjZGduQnFBMTE1Q2tsY2pCZGZhZGNsQnFBMTE1YW1oZ25l + SXBnY2ZuaElxQQoxMTVXbmVva2RtZ2FqZXBmZG1xQTEwOHlRZ2NkbGxhWmNtZG1qZVJlcGRtcUExMDd1c3BpWVhDcTFBMTA3dXN2CnExQTEwN3VzcTJBMTA3dXNsZWJn + bGNsbGVnaW5tZWRscUExMDd1c0EzVWJlZmdkbXFBMTA3dXNnbmhrbmRESHAKZW9iQnFBMTA3dXN6dnFBMTA3dXN6dnFBMTA3dXN6dnFBMTA3dXN6dnFBMTA3dXN6dnFB + MTA3dXN4dnFBMTA3dQpzeHZxQTEwN3VzeHZxQTEwN3VzeHZxQTEwN3V4c3ZxQTExMWVtb2Jwa0RzQTExOW5hbW1qbURpZW9sZmJEQTExCjllbW9icGtRQTFQRFZrb2Vo + REExMTVlbW9iaGtRQTJEQTE2NTFuZWtkUkNPaGtqZUJBMTE5a2FoYWJmQ2ppamoKZGpCQTExOW5paGtwZUJtbm1tYW1CQTExOW5paGtUQk9oa2JlSUExNjU1YWZBMnRO + QTZ5ZmllbWRtY2VYM2dhZQppUmJpWDN0QXdBMnlBODE5MXR3QTdma1VIZmhMaGdKVExnY2dtSkhlZmhpaGFncGhjVEExMnRhZXdBMnlBM3BqClNZQnEyQTExMWdnZGVq + a0JZMVhDcTFBMTExZWtTbmVJcG9ZWENxMUExMTFub2NoY2NJWmttTkNLcGhOQ3FBMTEKOENwbmZmQUNxQTExNXBvU1lCcGlZMUJxQTExNXBsa2hwb0JwaFNZQnFBMTE1 + cGFlbm9kQm9scGpvZEJxQTExNQpvZXBkbWhCT2pwbWlCcUExMTVuampqa21CbmVlZmtuQnFBMTE1bW9DamJCbWlvbGpiQnFBMTE1aWVtbGdsQmhrCmNkZ25CcUExMTVD + a2xjakJkZmFkY2xCcUExMTVhbWhnbmVJcGdjZm5oSXFBMTE1V25lb2tkbWdhamVwZmRtcUEKMTA4eVFnY2RsbGFaY21kbWplUmVwZG1xQTEwN3VzcGlZWENxMUExMDd1 + c3ZxMUExMDd1c3EyQTEwN3VzbGViZwpsY2xsZWdpbm1lZGxxQTEwN3VzQTNVYmVmZ2RtcUExMDd1c2duaGtuZERIcGVvYkJxQTEwN3VzenZxQTEwN3VzCnp2cUExMDd1 + c3p2cUExMDd1c3p2cUExMDd1c3p2cUExMDd1c3h2cUExMDd1c3h2cUExMDd1c3h2cUExMDd1c3gKdnFBMTA3dXhzdnFBMTExZW1vYnBrRHNBMTE5bmFtbWptRGllb2xm + YkRBMTE5ZW1vYnBrUUExUERWa29laERBMQoxNWVtb2Joa1FBMkRBMTY1MW5la2RSQ09oa2plQkExMTlrYWhhYmZDamlqamRqQkExMTluaWhrcGVCbW5tbWFtCkJBMTE5 + bmloa1RCT2hrYmVJQTE2NTVhZkEydE5BNnlmaWVtZG1jZVgzZ2FlaVJiaVgzdEF3QTJ5QTgxOTF0d0EKN2ZrVUhmaExoZ0pUTGdjZ21KSGVmaGloYWdwaGNUQTEydGFl + d0EyeUEzcGpTWUJxMkExMTFsZ21vaWRCWTFYQwpxMUExMTFla1NuZUlwb1lYQ3ExQTExMW5vY2hjY0laa21OQ0twaE5DcUExMTRDcG5mZkFDcTFBMTE1cG9TWUJwCmlZ + MUJxQTExNXBsa2hwb0JwaFNZQnFBMTE1cGFlbm9kQm9scGpvZEJxQTExNWdnb3Bsa0JnYWpsbGxCcUExMTUKYmlmb2lpQmJkTWlqQnFBMTE1amZqamNsQmlscGJjbUJx + QTExNU1laWFnQkFrYWFoQnFBMTE1aGJqaGxkSWZsZQpobGdJcUExMTVpb2JkZmpJZ2RoZGZvSXFBMTE1V25lb2tkbWdhamVwZmRtcUExMDh5UWdjZGxsYVpjbWRtamVS + CmVwZG1xQTEwN3VzcGlZWENxMUExMDd1c3ZxMUExMDd1c3EyQTEwN3VzbGViZ2xjbGxlZ2lubWVkbHFBMTA3dUEKM1ViZWZnZG1zcUExMDd1c2duaGtuZERIcGVvYkJx + QTEwN3VzenZxQTEwN3VzenZxQTEwN3VzenZxQTEwN3VzeAp2cUExMDd1c3h2cUExMDd1c3h2cUExMDd1c3h2cUExMDd1c3h2cUExMDd1c3h2cUExMDd1eHN2cUExMTFl + bW9iCnBrRHNBMTE5bmFtbWptRGllb2xmYkRBMTE5ZW1vYnBrUUExUERWa29laERBMTE1ZW1vYmhrUUEyREExNjUxbmUKa2RSQ09oa2plQkExMTlrYWhhYmZDamlqamRq + QkExMTluaWhrcGVCbW5tbWFtQkExMTluaWhrVEJPaGtiZUlBMQo2NTVhZkEydE5BNnlmaWVtZG1jZVgzZ2FlaVJiaVgzdEF3QTJ5QTgxOTF0d0E3ZmtVSGZoTGhnSlRM + Z2NnbUpICmVmaGloYWdwaGNUQTEydE5BNnlmY2ZiZWZBWDRob0tDWDN0TkE2eWZjZmJlZkFYNGhvS0NYM3ROQTZ5ZmNmYmUKZkFYNGhvS0NYM3ROQTZ5ZmNmYmVmQVg0 + aG9LQ1gzdE5BNnlmY2ZiZWZBWDRob0tDWDN0TkE2eWZjZmJlZkFYNApob0tDWDN0YW1hZEE1cWVlZWpmY2VoYWRBMmFnQTJmY0poZ2RiQTNhalZOQWVkZ3BnbmhhZGJB + Mk1WQTFTZ2lMCmhhSmRkQTFNVkExZmllbmVnZGRBSFVBTWhjQTFlZmZiZGJBZW1MaGpKYWpWQTFTRmRkQTRNVkExZWVKZ21MaGoKVUExTVZBMWVlZ2poZFRkZEEyTWJl + QTFlZUpnbUxoamRiQTFhalZBMVNnaUxoYUpkZUExTVZBMWVuZ3BnZWVnZgppZGJBVU1WQTFlZWdqaGRUZGVBZGZBTWJpQTFlbmdwZ2VlZ2ZpVUExTVZBMWZnZWRlZ2Rm + QTNaVkExZmdlZGVnCmRnQTNaVkExM2VuZ2poaWRmQUxoakpNaGJBMWVuZ2poaWRnQTNNaGJBMWVmZmJVQTRNVkEyTGhqSmhjSGRpQTQKZWRncGduaGFVQTJNVkExZmNn + amdvZ2hkZEEyTWhiQTFlb2hmZmNKaGdkYkExTVZOQTE2ZW1MaGpKQTI4TGhqSgpoY0hkYjFBMjhIZGJVQTIzZW1MaGpKQTI4TGhqSmhjSGRiZGVBMjhIZGJkZkEyM2Vt + TGhqSkEyOExoakpoY0hkCmJkaEEyOEhkYmRpQTIzZW1MaGpKQTI4TGhqSmhjSFVSQTI4SFVkYkEyM2VtTGhqSkEyOExoakpoY0hVZGRBMjgKSFVkZUExOXR3QTdlcGhk + Z2RnamdtMUxUZ3BoY2hkQTIwdHdBN2VnZ2pnbVRKaGNBMjV0d0E3ZW5ncFRnamdwZwpvQTI1dHdBN2VnZmlBMjl0QWFiQTlmaUlmaExoZ0poZE1makllbmdqaGlUaGZo + Y0pBMjM4dEFhYkE5ZmlJZWRoCmZUZ3BnZzFNZmpJZmNKaGRncGdvTGdvZ2RKQTIzNXRBYWJBOWZpSWVmZ29oZ0pnbWdwaGFKTWZqSWZjZ2poZEoKQTIzOHRBYWJBOWZp + SWVlSmdtTGhqTWZqSWZjSmhnSmhjZ2NBMjM5dHdBN2VnZ2pnb2NuU0xoaGNuZmFoZmdtaApkSkE5YWRBMmFpQTN0d0E3ZWdnamdvY25TTGhoY25mYWhmZ21oZEpBOWFk + QTJhaUEzdHdBN2VnZ2pnb2NuU0xoCmhjbmZhaGZnbWhkSkE5YWRBMmFpQTN0d0E3ZWdnamdvY25TTGhoY25mYWdtaGRjblNoYmhmQTdrYWJuTmJoaGEKYWVqaGJpZ2F0 + d0E3Y28yQTRlaGNkY2pBM3BpbmVkaEdoZmluV0ZOZWhjZGNqQTNwaXR3QTdnZUpnZ0xoZmdtVApBMTZlZmdvaGdkZEEzdHdBN2dlSmdnTGhmZ21UQTE2ZWZnb2hnZGVB + M3R3QTdnZUpnZ0xoZmdtVEExNmVuU2VmCmVoZGJBMnR3QThKZ2dMaGZnbVRBMjR0d0E4SmdnTGhmZ21UQTI0dHdBOEpnZ0xoZmdtVEEyNHR3QThKZ2dMaGYKZ21UQTI0 + dHdBN2dlSmdnTGhmZ21UQTE2ZW5TZWZlaFVBMnRBYWJBOEZBMjU1dEFhYkE4RkEyNTU9OTIyODAxCgAA + AGluaXRpYWxpemUAEAAAAA== + > + PRESETNAME initialize + FLOATPOS 32 247 1035 776 + FXID {54326BE8-AAAC-5648-9010-EF528A4DA255} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-221-pot-nks-demo_1.RPP b/plugin-reaper-realearn/resources/test-projects/issue-221-pot-nks-demo_1.RPP new file mode 100644 index 0000000..07957bd --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-221-pot-nks-demo_1.RPP @@ -0,0 +1,1499 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABOewEAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiJzQllVX2dtZCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMTUiLCJmZWVkYmFja0RldmljZUlkIjoiMTUiLCJkZWZhdWx0R3JvdXAiOnt9LCJncm91cHMiOlt7Imlk + IjoiUlpRTGFvUU9Rd1pGQkNSYUg4a0hCIiwibmFtZSI6IkZpbHRlciBwYWdlcyJ9LHsiaWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJuYW1lIjoiUHJlc2V0IGJy + b3dzaW5nIn0seyJpZCI6IkNfNkFZODB0ZG9NMUN5OUZIaTdYTiIsIm5hbWUiOiJGaWx0ZXIgcGFnZSBcIkNoYXJhY3RlclwiIiwiYWN0aXZhdGlvblR5cGUiOiJwcm9n + cmFtIiwicHJvZ3JhbUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjR9fSx7ImlkIjoiRUZwRjNGVzVMcDZrNmpLQlNoNl9JIiwibmFtZSI6 + IkZpbHRlciBwYWdlIFwiU3ViIHR5cGVcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3JhbSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3Jh + bUluZGV4IjozfX0seyJpZCI6Imxwbm9aeXJZVWRTYXBwY3lTbDM4ZCIsIm5hbWUiOiJGaWx0ZXIgcGFnZSBcIlR5cGVcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3Jh + bSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4IjoyfX0seyJpZCI6IjVBNnFhak1xNktGRlZzOHNWRUlHViIsIm5hbWUiOiJG + aWx0ZXIgcGFnZSBcIkJhbmtcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3JhbSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4 + IjoxfX0seyJpZCI6InpiVFRVd2tVLUQteHNsVFFqR2EzbSIsIm5hbWUiOiJGaWx0ZXIgcGFnZSBcIkluc3RydW1lbnRcIiIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3Jh + bSJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiZmFkZXIiLCJuYW1lIjoiRmFkZXIifSx7ImlkIjoidi1wb3Qi + LCJuYW1lIjoiVi1Qb3QifSx7ImlkIjoidi1zZWxlY3QiLCJuYW1lIjoiVi1TZWxlY3QifSx7ImlkIjoiZmFkZXItdG91Y2giLCJuYW1lIjoiRmFkZXIgVG91Y2gifSx7 + ImlkIjoic2VsZWN0IiwibmFtZSI6IlNlbGVjdCJ9LHsiaWQiOiJtdXRlIiwibmFtZSI6Ik11dGUifSx7ImlkIjoic29sbyIsIm5hbWUiOiJTb2xvIn0seyJpZCI6InJl + Y29yZC1yZWFkeSIsIm5hbWUiOiJSZWNvcmQtcmVhZHkifSx7ImlkIjoidi1wb3QtbGVkcyIsIm5hbWUiOiJWLVBvdCBMRURzIn0seyJpZCI6ImxjZCIsIm5hbWUiOiJM + Q0QifSx7ImlkIjoibWV0ZXIiLCJuYW1lIjoiTWV0ZXIifV0sIm1hcHBpbmdzIjpbeyJpZCI6Ik1hTXVUb1ItUXA4ZEIxaXQtRVNQXyIsIm5hbWUiOiJDaG9vc2UgaW5z + dHJ1bWVudCIsImdyb3VwSWQiOiJSWlFMYW9RT1F3WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEifSwibW9kZSI6 + eyJtYXhUYXJnZXRWYWx1ZSI6MC4wLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwib3V0T2ZSYW5nZUJlaGF2aW9y + IjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IjdzSVpmV2JpU2pZR3FaUjhlYkUzbSIsIm5hbWUiOiJDaG9vc2UgYmFuayIsImdyb3VwSWQiOiJSWlFM + YW9RT1F3WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC4xMTEx + MTExMTExMTExMTExLCJtYXhUYXJnZXRWYWx1ZSI6MC4xMTExMTExMTExMTExMTExLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IjFaU3pmLUI0eFR1ckFrVkZrc3MzeSIsIm5hbWUiOiJD + aG9vc2UgdHlwZSIsImdyb3VwSWQiOiJSWlFMYW9RT1F3WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjMifSwibW9k + ZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC4yMjIyMjIyMjIyMjIyMjIyLCJtYXhUYXJnZXRWYWx1ZSI6MC4yMjIyMjIyMjIyMjIyMjIyLCJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdl + dCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6InQzandk + bnhnTFNnTnduWWhmVjNiWCIsIm5hbWUiOiJDaG9vc2Ugc3ViIHR5cGUiLCJncm91cElkIjoiUlpRTGFvUU9Rd1pGQkNSYUg4a0hCIiwic291cmNlIjp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImY0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuMzMzMzMzMzMzMzMzMzMzMywibWF4VGFyZ2V0VmFsdWUiOjAuMzMzMzMz + MzMzMzMzMzMzMywibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsImJ1 + dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoidGhpcyIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJYck1qb0stamtzS2pCdmtKUjU2MFQiLCJuYW1lIjoiQ2hvb3NlIGNoYXJhY3RlciIsImdyb3VwSWQiOiJSWlFMYW9RT1F3 + WkZCQ1JhSDhrSEIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC40NDQ0NDQ0NDQ0 + NDQ0NDQ0LCJtYXhUYXJnZXRWYWx1ZSI6MC40NDQ0NDQ0NDQ0NDQ0NDQ0LCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IkdXNmsycmVGdzV1U05XZzJnRkdxUyIsIm5hbWUiOiJCcm93c2Ui + LCJncm91cElkIjoiemJUVFV3a1UtRC14c2xUUWpHYTNtIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6 + eyJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzQmFuayJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJuVlRVYXhncm9Pb19Z + ZnVnTXlrMFgiLCJuYW1lIjoiQnJvd3NlIiwiZ3JvdXBJZCI6IjVBNnFhak1xNktGRlZzOHNWRUlHViIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxl + bWVudEluZGV4Ijoiam9nIn0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIw + NjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik5rc1N1YkJhbmsifSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MX19LHsiaWQiOiJMcnlRcElLTEtSTUx3SEE2TmZEVEIiLCJuYW1l + IjoiQnJvd3NlIiwiZ3JvdXBJZCI6Imxwbm9aeXJZVWRTYXBwY3lTbDM4ZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9n + In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik5rc0NhdGVnb3J5In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwicHJvZ3Jh + bUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjJ9fSx7ImlkIjoicktHVVJNQkdEVExwcy1IRFFHYnotIiwibmFtZSI6IkJyb3dzZSIsImdy + b3VwSWQiOiJFRnBGM0ZXNUxwNms2aktCU2g2X0kiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImpvZyJ9LCJtb2RlIjp7Im1p + blN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + Ikxhc3RMb2FkZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NTdWJDYXRlZ29yeSJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInByb2dyYW1Db25kaXRpb24i + OnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4IjozfX0seyJpZCI6IkJXQUY1U2JlQzU4QUJVZmdzMTVVbCIsIm5hbWUiOiJCcm93c2UiLCJncm91cElkIjoiQ182 + QVk4MHRkb00xQ3k5RkhpN1hOIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6 + MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3Nj + QXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVk + In0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzTW9kZSJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwi + cHJvZ3JhbUluZGV4Ijo0fX0seyJpZCI6ImQwYXZ6MnVLYzFLYzNqb3Q0RkpGNyIsIm5hbWUiOiJEaXNwbGF5IiwiZ3JvdXBJZCI6IkNfNkFZODB0ZG9NMUN5OUZIaTdY + TiIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxh + eUlkIjoyLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUi + fSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwicG90RmlsdGVySXRlbUtpbmQiOiJOa3NNb2RlIn0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiUEc5MjZVUXFGYTF0S2JYN2tsYXExIiwibmFtZSI6IkJyb3dzZSBwcmVzZXRzIGNvYXJzZSIsImdyb3VwSWQiOiJ0UlVoWWEwUkdj + dXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjow + LjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7InR5cGUiOjU4LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQi + fX19LHsiaWQiOiJPcEREcnBERzJoQXY1Z0RqWXViZmsiLCJuYW1lIjoiQnJvd3NlIHByZXNldHMgZmluZSIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2Ui + OnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzEx + MzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7 + InR5cGUiOjU4LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJp + d19uZzZfMkpBRFpFU2wzWDJheGkiLCJuYW1lIjoiUHJldmlvdXMgcHJlc2V0IiwiZ3JvdXBJZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEs + ImtleSI6NjV9LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQifSwibW9kZSI6eyJ0eXBlIjoxLCJt + aW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI2cW92eWxrQzNuMEo5RHFrZUloVE8i + LCJuYW1lIjoiTmV4dCBwcmVzZXQiLCJncm91cElkIjoidFJVaFlhMFJHY3VwMVhJeVYzenZyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcx + MTM0MDIwNjE4NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJSSkRFVHh4aWpkNHRJdjM2enp2NTEiLCJuYW1lIjoiRGlzcGxheSBwcmVzZXQgbmFtZSIsImdyb3VwSWQiOiJ0UlVoWWEw + UkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsIm9zY0FyZ0lu + ZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90aW1lY29kZSJ9LCJtb2RlIjp7InR5cGUi + OjEsIm1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NTgsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5WHV1UTBUbDMxNW01NjFIUHdkejEiLCJu + YW1lIjoiU2F5IHByZXNldCBuYW1lIiwiZ3JvdXBJZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InJlYXBlciIsInR5cGUiOjEy + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjowLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwi + a2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUiLCJyZWFwZXJTb3VyY2VUeXBlIjoic3BlZWNoIn0sIm1vZGUiOnsidHlwZSI6MSwibWlu + U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo1OCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJpc0VuYWJsZWQiOmZhbHNlLCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ikt3NTcyLURPTWpXMi1s + QVgtbFRrcyIsIm5hbWUiOiJQcmV2aWV3IHByZXNldCIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9k + aWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJyZWFwZXJTb3VyY2VUeXBl + Ijoic3BlZWNoIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRh + cmdldCI6eyJ0eXBlIjo1OSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhX09hS2UxVW9fRkFUa3VwbkhpMXoiLCJuYW1lIjoiTG9hZCBwcmVzZXQiLCJncm91cElkIjoidFJVaFlhMFJH + Y3VwMVhJeVYzenZyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlz + cGxheUlkIjowLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QiLCJyZWFwZXJTb3VyY2VUeXBlIjoic3BlZWNoIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MCwiaW52b2NhdGlvblR5cGUiOjMsInRyYWNrR1VJ + RCI6IjQ1OUU4RkNGLUREMzEtNUM0QS04NzU3LUIwNUYzNDFGOEVCNSIsImZ4QW5jaG9yIjoiaW5kZXgiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9h + ZGVkIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJJRVZEU1d2dWNtcDBhNjBqME5TZUciLCJuYW1lIjoiU2hvdyBGWCBvbiBsb2FkIiwiZ3JvdXBJ + ZCI6InRSVWhZYTBSR2N1cDFYSXlWM3p2ciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsImRpc3BsYXlJZCI6MCwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0IiwicmVhcGVyU291cmNlVHlwZSI6InNwZWVjaCJ9LCJtb2RlIjp7Im1pblRhcmdldFZh + bHVlIjoxLjAsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlw + ZSI6MjcsImludm9jYXRpb25UeXBlIjozLCJ0cmFja0dVSUQiOiI0NTlFOEZDRi1ERDMxLTVDNEEtODc1Ny1CMDVGMzQxRjhFQjUiLCJmeEFuY2hvciI6ImluZGV4Iiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiaXNFbmFibGVkIjpmYWxzZSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoia3VV + aDYyWkpYcFFhOUFTX3M5V3ZNIiwibmFtZSI6IkRpc3BsYXkgcGFyYW0gbmFtZSIsImdyb3VwSWQiOiJ0UlVoWWEwUkdjdXAxWEl5VjN6dnIiLCJzb3VyY2UiOnsidHlw + ZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsImxpbmUiOjAsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5meF9wYXJh + bWV0ZXIubmFtZSB9fSIsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJSb3V0ZVZvbHVtZSIsIkJyb3dzZUZ4UHJlc2V0cyIsIlRyYWNrQXJtU3Rh + dGUiLCJUcmFja0F1dG9tYXRpb25Nb2RlIiwiVHJhY2tNdXRlU3RhdGUiLCJGeFBhcmFtZXRlclZhbHVlIiwiUGxheVJhdGUiLCJUcmFja1NvbG9TdGF0ZSIsIlRyYWNr + UGFuIiwiQXV0b21hdGlvbk1vZGVPdmVycmlkZSIsIkZ4T25PZmZTdGF0ZSIsIlJvdXRlUGFuIiwiVHJhY2tWb2x1bWUiLCJUcmFja1NlbGVjdGlvblN0YXRlIiwiVHJh + Y2tNb25pdG9yaW5nTW9kZSIsIlRlbXBvIl19fSx7ImlkIjoiVkpqY0NIUzRMOVhYQUVaYWdnd190IiwibmFtZSI6IkRpc3BsYXkgcGFyYW0gdmFsdWUiLCJncm91cElk + IjoidFJVaFlhMFJHY3VwMVhJeVYzenZyIiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjow + LCJsaW5lIjoxLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImZlZWRi + YWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJM + YXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJGeFBhcmFtZXRlclZhbHVlIiwiVHJhY2tWb2x1bWUiLCJUZW1wbyIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJU + cmFja1NlbGVjdGlvblN0YXRlIiwiQnJvd3NlRnhQcmVzZXRzIiwiUGxheVJhdGUiLCJUcmFja0F1dG9tYXRpb25Nb2RlIiwiVHJhY2tQYW4iLCJSb3V0ZVBhbiIsIlJv + dXRlVm9sdW1lIiwiQXV0b21hdGlvbk1vZGVPdmVycmlkZSIsIlRyYWNrU29sb1N0YXRlIiwiVHJhY2tBcm1TdGF0ZSIsIkZ4T25PZmZTdGF0ZSIsIlRyYWNrTXV0ZVN0 + YXRlIl19fSx7ImlkIjoiT0NzTXkxWkhOLXBOVnRySFBIVXFEIiwibmFtZSI6IkRpc3BsYXkiLCJncm91cElkIjoiNUE2cWFqTXE2S0ZGVnM4c1ZFSUdWIiwic291cmNl + Ijp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQiOjIsIm9z + Y0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90aW1lY29kZSJ9LCJtb2RlIjp7 + InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoie3sgdGFyZ2V0Lml0ZW0ubmFtZSB9fSIsImZlZWRiYWNrVHlwZSI6MX0s + InRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJn + SW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0s + InBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzU3ViQmFuayJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IllDb0laV29qVWRsVjRITEdfcnBKdSIsIm5hbWUi + OiJEaXNwbGF5IiwiZ3JvdXBJZCI6InpiVFRVd2tVLUQteHNsVFFqR2EzbSIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5 + Ijo2NX0sImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4 + NTUsIm1heFN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tUeXBlIjoxfSwi + dGFyZ2V0Ijp7InR5cGUiOjYxLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwi + cG90RmlsdGVySXRlbUtpbmQiOiJOa3NCYW5rIn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiX056TW85LXhTRjVMVW1ibHZHTEJKIiwibmFtZSI6IkRp + c3BsYXkiLCJncm91cElkIjoiRUZwRjNGVzVMcDZrNmpLQlNoNl9JIiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQiOjIsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1 + fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90aW1lY29kZSJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDA0MTIzNzExMzQwMjA2MTg1NSwi + bWF4U3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0 + aW9uIjoie3sgdGFyZ2V0Lml0ZW0ubmFtZSB9fSIsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzU3ViQ2F0ZWdvcnkifSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtOURBc3c5NERzY3JZMFRPMG4zRm4iLCJuYW1lIjoiRGlzcGxheSIsImdyb3VwSWQiOiJscG5vWnlyWVVkU2FwcGN5U2wzOGQi + LCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiIsImRpc3BsYXlJ + ZCI6Miwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVjb2RlIn0s + Im1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMDQxMjM3MTEzNDAyMDYxODU1LCJtYXhTdGVwU2l6ZSI6MC4wMDAwNDEyMzcxMTM0MDIwNjE4NTUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiTmtzQ2F0ZWdvcnkifSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6Im1haW4vZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6Mywi + Y2hhbm5lbCI6OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiam9nIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6NjAsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2ciLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYWluL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1hcmtlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg0 + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjoz + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2VyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJlYWQiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJl + YWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoid3JpdGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NX0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoLWxlZnQiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19fSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJhbmstbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjQ2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicmV3aW5kIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6OTF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19fSx7ImlkIjoiZmFzdC1md2QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Mn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InBsYXkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo5NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoi + c3RvcCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4Ijoic3RvcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyZWNvcmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NX0sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjeWNsZSIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiem9vbSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMH0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Inpvb20iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic2NydWIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoxMDF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + aW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6OTl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLXVwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLWRvd24iLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6Im51ZGdlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJudWRnZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJkcm9wIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6ODd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9wIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6InJlcGxhY2UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6InJlcGxhY2UiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2xpY2siLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4 + OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InNvbG8iLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNv + bG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NH0sIm1vZGUiOnsibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImYyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6NTV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJN + b3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQi + OiJmMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiZjMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1N30sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImY1Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NTh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNSIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJmNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiZjYiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic21wdGUtYmVhdHMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1 + M30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Imxj + ZC9hc3NpZ25tZW50IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4ifSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoibGNkL2Fzc2lnbm1lbnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImxjZC90aW1lY29kZSIsImdyb3VwSWQi + OiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoibGNk + L3RpbWVjb2RlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3Qi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMS92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJm + YWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9zZWxlY3QiLCJn + cm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0 + eXBlIjozLCJjaGFubmVsIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS92LXBvdC9jb250cm9s + IiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9mZWVkYmFjayIs + Imdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291 + cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92 + LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTAgZGNiYV0ifSwibW9k + ZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAg + MzAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QvYm9v + c3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxl + ZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMS92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9zcHJlYWQi + LCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNv + dXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDEvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDEvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9z + b2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MH0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9sY2Qv + bGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNk + L2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNv + dXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbWV0ZXIvcGVhayIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxlY3QiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIv + ZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gyL3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6 + MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBv + dCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIv + di1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvZmVlZGJhY2siLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvd3JhcCIsImdyb3Vw + SWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFs + dWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Yt + cG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAxIGRjYmFdIn0s + Im1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5 + cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1w + b3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90 + LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL211dGUiLCJncm91 + cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gyL3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX19LHsiaWQiOiJjaDIvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImNoMi9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjB9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6 + ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjM0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MTA2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zZWxlY3QiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjJ9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDMvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MTAw + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92 + LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIs + InNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gzL3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9ib29zdC1jdXQiLCJn + cm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJj + ZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBh + dHRlcm4iOiJCMCAzMiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NpbmdsZSIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92 + LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9t + dXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMy9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoxMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlw + ZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNo + My9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL2xjZC9saW5l + MSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MiwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGlu + ZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7 + InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6 + ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJEMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + aW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g0L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDQvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoxMDd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjI3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJjaDQvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6M30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRl + ciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjE5LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3QvZmVlZGJhY2siLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJCMCAzMyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUi + OjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qv + d3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1h + eFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g0L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFsw + MDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6 + ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01p + ZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NwcmVhZCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjE5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUiLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlk + IjoiIn19fSx7ImlkIjoiY2g0L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwibW9kZSI6eyJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g0L3JlY29yZC1yZWFkeSIs + Imdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6Imxj + ZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMSIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3Bs + YXlJZCI6MywibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6Ilki + fSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoNC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDExIGRjYmFd + In0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUv + di1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNn0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoNS9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOH0sIm1vZGUiOnsi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoNS9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjh9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS9mYWRlciIs + Imdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fX0seyJpZCI6ImNoNS92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImNoYXJhY3RlciI6 + NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoNS92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0 + IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0 + ZXJuIjoiQjAgMzQgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC93cmFwIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAu + NzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1w + b3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDAgZGNiYV0ifSwibW9k + ZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoNS92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAz + NCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJj + aDUvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDUvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29y + ZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDUvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5 + cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo0LCJsaW5lIjox + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjoz + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L21l + dGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMDAgZGNiYV0ifSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g1L21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXNlbGVjdCIsImdyb3Vw + SWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2ZhZGVyL3RvdWNo + IiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g2L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOX0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVy + Iiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6 + ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2 + L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBl + IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Yt + cG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDAgZGNiYV0ifSwi + bW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g2L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAx + MCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJCMCAzNSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNi92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zaW5nbGUiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZh + bHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjoz + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2 + L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDExIGRjYmFdIn0s + Im1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoNi9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMX0sIm1vZGUiOnsi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tdXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNi9zb2xvIiwiZ3JvdXBJ + ZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNi9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9yZWNv + cmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJ + ZCI6NSwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZv + Y2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoNi9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDYvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbWV0ZXIvcGVhayIsImdyb3Vw + SWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbWV0 + ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDcvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFk + ZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvc2VsZWN0IiwiZ3Jv + dXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlw + ZSI6MywiY2hhbm5lbCI6Nn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvdi1wb3QvY29udHJvbCIs + Imdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDcvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3QvZmVlZGJhY2siLCJn + cm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJj + ZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBl + IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1w + b3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDEwIGRjYmFdIn0sIm1vZGUi + OnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2 + IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L2Jvb3N0 + LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRz + Iiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDcvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc3ByZWFkIiwi + Z3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3Vy + Y2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBl + IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g3L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3Nv + bG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX19LHsiaWQiOiJjaDcvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo2LCJsaW5lIjowfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L2xjZC9s + aW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9sY2Qv + bGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTEwIGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tZXRlci9wZWFrIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXNlbGVjdCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9m + YWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDgvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo3 + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjoz + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90 + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92 + LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC93cmFwIiwiZ3JvdXBJ + ZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1 + ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1w + b3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDEgZGNiYV0ifSwi + bW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBv + dC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbXV0ZSIsImdyb3Vw + SWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MTV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX19LHsiaWQiOiJjaDgvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + Ojd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImNoOC9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjB9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDgvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6 + ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo3LCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX1dLCJwYXJhbWV0ZXJzIjp7IjAiOnsibmFtZSI6IkZpbHRlciBiYW5rIiwidmFsdWVDb3VudCI6MTAsInZhbHVlIjowLjQ0NDQ0NDQ1 + fX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifSwicG90U3RhdGUiOnsiZmlsdGVyX3NldHRpbmdzIjp7Im5rcyI6eyJiYW5rIjoiWmVicmEyIn19LCJw + cmVzZXRfaWQiOiI4ZmYwZmFiNDY3YjQ5NTRmZmU2MzI4N2U1NTQ1ZTNkMSJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 392 51 752 675 + FXID {8AA22E92-CA21-F44E-86AD-447E6F1C2345} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAB9EAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiJERkZPMDR0NSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6 + W3siaWQiOiI5ODBjemJKbmdmbks4aXd4Q1FJbmEiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsInRyYWNr + R1VJRCI6IjQ1OUU4RkNGLUREMzEtNUM0QS04NzU3LUIwNUYzNDFGOEVCNSIsImZ4QW5jaG9yIjoiaW5kZXgiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFtaWMiLCJwYXJhbUV4cHJlc3Npb24iOiJtYXBwZWRfZnhfcGFyYW1ldGVyX2luZGV4ZXNbMF0iLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiYlZBcDcwUVpNNjNaNFBZU3ZrN3UzIiwibmFtZSI6IjIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9j + YXRpb25UeXBlIjozLCJ0cmFja0dVSUQiOiI0NTlFOEZDRi1ERDMxLTVDNEEtODc1Ny1CMDVGMzQxRjhFQjUiLCJmeEFuY2hvciI6ImluZGV4IiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9uIjoibWFwcGVkX2Z4X3BhcmFt + ZXRlcl9pbmRleGVzWzFdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkM5ODliSlYwel9JbFZXTTBWNU4xNyIsIm5hbWUiOiIzIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6NTAsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywidHJhY2tHVUlEIjoiNDU5RThGQ0YtREQzMS01QzRBLTg3NTctQjA1RjM0MUY4RUI1IiwiZnhBbmNob3IiOiJp + bmRleCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lv + biI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1syXSIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI3YlN2Tm9wT1N2aUpKVF9Ic3FhcXIiLCJuYW1lIjoi + NCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsInRyYWNrR1VJRCI6IjQ1OUU4RkNGLUREMzEtNUM0QS04NzU3LUIwNUYzNDFG + OEVCNSIsImZ4QW5jaG9yIjoiaW5kZXgiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFt + aWMiLCJwYXJhbUV4cHJlc3Npb24iOiJtYXBwZWRfZnhfcGFyYW1ldGVyX2luZGV4ZXNbM10iLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQlR0SE00ZURGU19O + RHRLOE5WaDVMIiwibmFtZSI6IjUiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJ0cmFja0dVSUQiOiI0NTlFOEZDRi1ERDMx + LTVDNEEtODc1Ny1CMDVGMzQxRjhFQjUiLCJmeEFuY2hvciI6ImluZGV4IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJwYXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9uIjoibWFwcGVkX2Z4X3BhcmFtZXRlcl9pbmRleGVzWzRdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0s + eyJpZCI6InV0Mm9iQV9FV1BnbTVZZm1EOGkyWSIsIm5hbWUiOiI2Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTMsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywidHJhY2tH + VUlEIjoiNDU5RThGQ0YtREQzMS01QzRBLTg3NTctQjA1RjM0MUY4RUI1IiwiZnhBbmNob3IiOiJpbmRleCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1s1XSIsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJCQ2d6RGR5QThQSVd1M3lZci1fUlAiLCJuYW1lIjoiNyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjU0LCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2Nh + dGlvblR5cGUiOjMsInRyYWNrR1VJRCI6IjQ1OUU4RkNGLUREMzEtNUM0QS04NzU3LUIwNUYzNDFGOEVCNSIsImZ4QW5jaG9yIjoiaW5kZXgiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtVHlwZSI6ImR5bmFtaWMiLCJwYXJhbUV4cHJlc3Npb24iOiJtYXBwZWRfZnhfcGFyYW1l + dGVyX2luZGV4ZXNbNl0iLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoid0V4NGgwdmpCcFhIeTl0VEhEaXpDIiwibmFtZSI6IjgiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjo1NSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1 + fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJ0cmFja0dVSUQiOiI0NTlFOEZDRi1ERDMxLTVDNEEtODc1Ny1CMDVGMzQxRjhFQjUiLCJmeEFuY2hvciI6Imlu + ZGV4IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbVR5cGUiOiJkeW5hbWljIiwicGFyYW1FeHByZXNzaW9u + IjoibWFwcGVkX2Z4X3BhcmFtZXRlcl9pbmRleGVzWzddIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 32 348 752 675 + FXID {452F6298-9A34-C442-BB90-0B2DB081B153} + WAK 0 0 + > + > + "" + MkRNU+5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABpYQAAAQAAAAAAEAA= + I3BnbT1IUyBFaWdodGllcyBQc2V1ZAovKkBNZXRhCgpCYW5rOgonWmVicmEgRmFjdG9yeSAyLjInCgpBdXRob3I6CidIb3dhcmQgU2NhcnInCgpVc2FnZToKJ01XID0g + dG9uZVxyQVQgPSBzbG93IHZpYnJhdG8nCgpDYXRlZ29yaWVzOgonQmFzczpEaWdpdGFsLCBCYXNzOlBsdWNrcycKCkZlYXR1cmVzOgonTW9ubywgRHJ5JwoKQ2hhcmFj + dGVyOgonRGFyaywgQ29uc3RhbnQsIFNvZnQsIFBoYXQnCgoqLwoKI0FNPVplYnJhMgojVmVycz0yMDUwMAojRW5kaWFuPWxpdHRsZQojbm09MzQKI21zPW5vbmUKI21z + PU1vZFdobAojbXM9UGl0Y2hXCiNtcz1DdHJsQQojbXM9Q3RybEIKI21zPUxmb0cxCiNtcz1MZm9HMgojbXM9R2F0ZQojbXM9S2V5Rm9sCiNtcz1LZXlGb2wyCiNtcz1W + ZWxvY2l0eQojbXM9QVRvdWNoCiNtcz1BcnBNb2QKI21zPUFycE1kMgojbXM9RW52MQojbXM9RW52MgojbXM9RW52MwojbXM9RW52NAojbXM9TVNFRzEKI21zPU1TRUcy + CiNtcz1NU0VHMwojbXM9TVNFRzQKI21zPUxmbzEKI21zPUxmbzIKI21zPUxmbzMKI21zPUxmbzQKI21zPU1NYXAxCiNtcz1NTWFwMgojbXM9TU1hcDMKI21zPU1NYXA0 + CiNtcz1NTWl4MQojbXM9TU1peDIKI21zPU1NaXgzCiNtcz1NTWl4NAojbnY9NQojbXY9R2F0ZQojbXY9RW52MQojbXY9RW52MgojbXY9RW52MwojbXY9RW52NAojY209 + bWFpbgpDY09wPTEwMC4wMAojTEZPRz0xCiNMRk9HMj0xCiNjbT1QQ29yZQpYXzE9MTAwLjAwCllfMT0wLjAwClhfMj0xMDAuMDAKWV8yPTEwMC4wMApYXzM9MTMuMzkK + WV8zPTEwMC4wMApYXzQ9MC4wMApZXzQ9MTAwLjAwCk1UMTE9T1NDMTpTRlgyCk1MMTE9LTE1Mi4wMApNUjExPTQ4LjAwCk1UMTI9bm9uZTphc3NpZ25lZApNTDEyPTE1 + LjAwCk1SMTI9LTE1LjAwCk1UMTM9bm9uZTphc3NpZ25lZApNTDEzPTUwLjAwCk1SMTM9LTUwLjAwCk1UMTQ9bm9uZTphc3NpZ25lZApNTDE0PTUwLjAwCk1SMTQ9LTUw + LjAwCk1UMTU9bm9uZTphc3NpZ25lZApNTDE1PTQ4LjAwCk1SMTU9LTQ4LjAwCk1UMTY9bm9uZTphc3NpZ25lZApNTDE2PTQ4LjAwCk1SMTY9LTQ4LjAwCk1UMTc9bm9u + ZTphc3NpZ25lZApNTDE3PTUwLjAwCk1SMTc9LTUwLjAwCk1UMTg9bm9uZTphc3NpZ25lZApNTDE4PTUwLjAwCk1SMTg9LTUwLjAwCk1UMjE9T1NDMTpTRlgxCk1MMjE9 + LTEzNC4wMApNUjIxPTY2LjAwCk1UMjI9bm9uZTphc3NpZ25lZApNTDIyPTQ4LjAwCk1SMjI9LTQ4LjAwCk1UMjM9bm9uZTphc3NpZ25lZApNTDIzPTQ4LjAwCk1SMjM9 + LTQ4LjAwCk1UMjQ9bm9uZTphc3NpZ25lZApNTDI0PTUwLjAwCk1SMjQ9LTUwLjAwCk1UMjU9bm9uZTphc3NpZ25lZApNTDI1PTUwLjAwCk1SMjU9LTUwLjAwCk1UMjY9 + bm9uZTphc3NpZ25lZApNTDI2PTUwLjAwCk1SMjY9LTUwLjAwCk1UMjc9bm9uZTphc3NpZ25lZApNTDI3PTUwLjAwCk1SMjc9LTUwLjAwCk1UMjg9bm9uZTphc3NpZ25l + ZApNTDI4PTUwLjAwCk1SMjg9LTUwLjAwCk1UMzE9VkNGMTpGTTEKTUwzMT0wLjAwCk1SMzE9LTcyLjAwCk1UMzI9VkNGMTpDdXQKTUwzMj00My41MApNUjMyPTAuMDAK + TVQzMz1ub25lOmFzc2lnbmVkCk1MMzM9MC4wMApNUjMzPTAuMDAKTVQzND1ub25lOmFzc2lnbmVkCk1MMzQ9MC4wMApNUjM0PTAuMDAKTVQzNT1ub25lOmFzc2lnbmVk + Ck1MMzU9Mi4wMApNUjM1PS0xMDAuMDAKTVQzNj1ub25lOmFzc2lnbmVkCk1MMzY9MC4wMApNUjM2PTAuMDAKTVQzNz1ub25lOmFzc2lnbmVkCk1MMzc9MC4wMApNUjM3 + PTAuMDAKTVQzOD1ub25lOmFzc2lnbmVkCk1MMzg9MC4wMApNUjM4PTAuMDAKTVQ0MT1WQ0YxOlJlcwpNTDQxPTQ3LjAwCk1SNDE9LTAuNTAKTVQ0Mj1NU0VHMTpBdGsK + TUw0Mj0xLjYwCk1SNDI9MC4wMApNVDQzPW5vbmU6YXNzaWduZWQKTUw0Mz0wLjAwCk1SNDM9MC4wMApNVDQ0PW5vbmU6YXNzaWduZWQKTUw0ND0wLjAwCk1SNDQ9MC4w + MApNVDQ1PW5vbmU6YXNzaWduZWQKTUw0NT0wLjAwCk1SNDU9MC4wMApNVDQ2PW5vbmU6YXNzaWduZWQKTUw0Nj0wLjAwCk1SNDY9MC4wMApNVDQ3PW5vbmU6YXNzaWdu + ZWQKTUw0Nz0wLjAwCk1SNDc9MC4wMApNVDQ4PW5vbmU6YXNzaWduZWQKTUw0OD0wLjAwCk1SNDg9MC4wMApNVDUxPUVOVjE6QXRrCk1MNTE9MzQuMDAKTVI1MT0wLjAw + Ck1UNTI9RU5WMTpEZWMKTUw1Mj0wLjAwCk1SNTI9LTM3LjAwCk1UNTM9TVNFRzE6VmVsCk1MNTM9MC4wMApNUjUzPS0zNi4wMApNVDU0PU1TRUcxOkF0awpNTDU0PS00 + LjAwCk1SNTQ9MS42OApNVDU1PW5vbmU6YXNzaWduZWQKTUw1NT01MC4wMApNUjU1PS01MC4wMApNVDU2PW5vbmU6YXNzaWduZWQKTUw1Nj01MC4wMApNUjU2PS01MC4w + MApNVDU3PW5vbmU6YXNzaWduZWQKTUw1Nz01MC4wMApNUjU3PS01MC4wMApNVDU4PW5vbmU6YXNzaWduZWQKTUw1OD01MC4wMApNUjU4PS01MC4wMApNVDYxPUxGTzI6 + QW1wCk1MNjE9OTkuMDAKTVI2MT0xMDAuMDAKTVQ2Mj1MRk8yOlJhdGUKTUw2Mj0yMC4wMApNUjYyPTAuMDAKTVQ2Mz1ub25lOmFzc2lnbmVkCk1MNjM9MC4wMApNUjYz + PS04LjAwCk1UNjQ9bm9uZTphc3NpZ25lZApNTDY0PTUwLjAwCk1SNjQ9LTUwLjAwCk1UNjU9bm9uZTphc3NpZ25lZApNTDY1PTUwLjAwCk1SNjU9LTUwLjAwCk1UNjY9 + bm9uZTphc3NpZ25lZApNTDY2PTguMDAKTVI2Nj0tOC4wMApNVDY3PW5vbmU6YXNzaWduZWQKTUw2Nz01MC4wMApNUjY3PS01MC4wMApNVDY4PW5vbmU6YXNzaWduZWQK + TUw2OD01MC4wMApNUjY4PS01MC4wMApNVDcxPURlbGF5MTpNaXgKTUw3MT00MC4wMApNUjcxPTMzLjAwCk1UNzI9RGVsYXkxOkZCCk1MNzI9MjUuMDAKTVI3Mj0tMTMu + MDAKTVQ3Mz1EZWxheTE6Q0IKTUw3Mz05LjAwCk1SNzM9LTIwLjAwCk1UNzQ9RGVsYXkxOlQwCk1MNzQ9NTAuMDAKTVI3ND0tNTAuMDAKTVQ3NT1EZWxheTE6VDEKTUw3 + NT01MC4wMApNUjc1PS00OC4wMApNVDc2PURlbGF5MTpQYW4xCk1MNzY9MC4wMApNUjc2PS00OC4wMApNVDc3PURlbGF5MTpQYW4yCk1MNzc9MC4wMApNUjc3PTQ2LjAw + Ck1UNzg9bm9uZTphc3NpZ25lZApNTDc4PTAuMDAKTVI3OD0wLjAwCk1UODE9TnVSZXYxOk1peApNTDgxPTk2LjAwCk1SODE9MC4wMApNVDgyPU1vZEZYMTpNaXgKTUw4 + Mj0wLjAwCk1SODI9NTAuMDAKTVQ4Mz1Nb2RGWDE6SGVxCk1MODM9MC4wMApNUjgzPTMuODQKTVQ4ND1Nb2RGWDE6TGVxCk1MODQ9MC43MgpNUjg0PTEyLjAwCk1UODU9 + bm9uZTphc3NpZ25lZApNTDg1PTUwLjAwCk1SODU9LTUwLjAwCk1UODY9bm9uZTphc3NpZ25lZApNTDg2PTUwLjAwCk1SODY9LTUwLjAwCk1UODc9bm9uZTphc3NpZ25l + ZApNTDg3PTUwLjAwCk1SODc9LTUwLjAwCk1UODg9bm9uZTphc3NpZ25lZApNTDg4PTUwLjAwCk1SODg9LTUwLjAwCk1NVDE9U2hhcGUxOklucHV0Ck1NUzE9MTgKTU1E + MT0xNC4wMApNTVZTMT0wCk1NVkQxPTAuMDAKTU1UMj1PU0MxOlZvbER0Ck1NUzI9MjMKTU1EMj01Mi4wMApNTVZTMj0wCk1NVkQyPTAuMDAKTU1UMz1ub25lOmFzc2ln + bmVkCk1NUzM9MApNTUQzPTAuMDAKTU1WUzM9MApNTVZEMz0wLjAwCk1NVDQ9bm9uZTphc3NpZ25lZApNTVM0PTAKTU1END0wLjAwCk1NVlM0PTAKTU1WRDQ9MC4wMApN + TVQ1PW5vbmU6YXNzaWduZWQKTU1TNT0wCk1NRDU9MC4wMApNTVZTNT0wCk1NVkQ1PTAuMDAKTU1UNj1ub25lOmFzc2lnbmVkCk1NUzY9MApNTUQ2PTAuMDAKTU1WUzY9 + MApNTVZENj0wLjAwCk1NVDc9bm9uZTphc3NpZ25lZApNTVM3PTAKTU1ENz0wLjAwCk1NVlM3PTAKTU1WRDc9MC4wMApNTVQ4PW5vbmU6YXNzaWduZWQKTU1TOD0wCk1N + RDg9MC4wMApNTVZTOD0wCk1NVkQ4PTAuMDAKTU1UOT1ub25lOmFzc2lnbmVkCk1NUzk9MApNTUQ5PTAuMDAKTU1WUzk9MApNTVZEOT0wLjAwCk1NVDEwPW5vbmU6YXNz + aWduZWQKTU1TMTA9MApNTUQxMD0wLjAwCk1NVlMxMD0wCk1NVkQxMD0wLjAwCk1NVDExPW5vbmU6YXNzaWduZWQKTU1TMTE9MApNTUQxMT0wLjAwCk1NVlMxMT0wCk1N + VkQxMT0wLjAwCk1NVDEyPW5vbmU6YXNzaWduZWQKTU1TMTI9MApNTUQxMj0wLjAwCk1NVlMxMj0wCk1NVkQxMj0wLjAwClNCYXNlPTQKU3dpbmc9MC4wMApTVHJpZz0x + ClBQcm9qPTQKUEZvbGQ9MApQRmlsZT0xCkdGaWxlPTIKR1NjYWxlPTAKQ2hMYXk9MApTdXJyTz0wClJldj0xMjA5MgpMRUQ9MC4wMApQQUdFPTAKUGFnZXNPbj0wCkNv + cmVOPTMKU2xpY2U9NApVSV9vcD00Ck1pZGlBPTUKTWlkaVA9NgpERm9sZD03CkN0cmxBPTIKQ3RybEI9MTEKI2NtPUxGT0cKU3luYz00ClRyaWc9MApXYXZlPTAKUGhz + ZT0wLjAwClJhdGU9MTAwLjAwCkFtcD0xMDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz04ClVXdj0wCiNjbT1MRk9HMgpTeW5jPTQKVHJpZz0wCldhdmU9MApQaHNlPTAu + MDAKUmF0ZT0xMDAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTkKVVd2PTAKI2NtPVZDQwojTEZPMT0xCiNMRk8yPTEKI0xGTzM9MQojTEZPND0xClZv + aWNlcz0wClZvaWNpbmc9MApNb2RlPTEKUG9ydGE9MC4wMApQQj0xMQpQQkQ9MTEKQXJTYz00CkFyT3JkPTAKQXJMcD0wCkFyT2N0PTEKQXJMTD0xNgpBclRyPTAKRHJm + dD0xCk1UdW5TPTAKTVR1bk49MTAKTVR1blQ9MTEKVHJzcD0wCkZUdW49MC4wMApQb3J0Umc9MTAwLjAwClBvcnRhTT0wClBvcnRhMj0wLjAwCkFndGUxPTIKQXRycDE9 + MApBdm9jMT0xCkFtdWwxPTEKQW1vZDE9MApBTURwdDE9MC4wMApBTURwQjE9MC4wMApBZ3RlMj0yCkF0cnAyPTAKQXZvYzI9MQpBbXVsMj0xCkFtb2QyPTAKQU1EcHQy + PTAuMDAKQU1EcEIyPTAuMDAKQWd0ZTM9MgpBdHJwMz0wCkF2b2MzPTEKQW11bDM9MQpBbW9kMz0wCkFNRHB0Mz0wLjAwCkFNRHBCMz0wLjAwCkFndGU0PTIKQXRycDQ9 + MApBdm9jND0xCkFtdWw0PTEKQW1vZDQ9MApBTURwdDQ9MC4wMApBTURwQjQ9MC4wMApBZ3RlNT0yCkF0cnA1PTAKQXZvYzU9MQpBbXVsNT0xCkFtb2Q1PTAKQU1EcHQ1 + PTAuMDAKQU1EcEI1PTAuMDAKQWd0ZTY9MgpBdHJwNj0wCkF2b2M2PTEKQW11bDY9MQpBbW9kNj0wCkFNRHB0Nj0wLjAwCkFNRHBCNj0wLjAwCkFndGU3PTIKQXRycDc9 + MApBdm9jNz0xCkFtdWw3PTEKQW1vZDc9MApBTURwdDc9MC4wMApBTURwQjc9MC4wMApBZ3RlOD0yCkF0cnA4PTAKQXZvYzg9MQpBbXVsOD0xCkFtb2Q4PTAKQU1EcHQ4 + PTAuMDAKQU1EcEI4PTAuMDAKQWd0ZTk9MgpBdHJwOT0wCkF2b2M5PTEKQW11bDk9MQpBbW9kOT0wCkFNRHB0OT0wLjAwCkFNRHBCOT0wLjAwCkFndGUxMD0yCkF0cnAx + MD0wCkF2b2MxMD0xCkFtdWwxMD0xCkFtb2QxMD0wCkFNRHB0MTA9MC4wMApBTURwQjEwPTAuMDAKQWd0ZTExPTIKQXRycDExPTAKQXZvYzExPTEKQW11bDExPTEKQW1v + ZDExPTAKQU1EcHQxMT0wLjAwCkFNRHBCMTE9MC4wMApBZ3RlMTI9MgpBdHJwMTI9MApBdm9jMTI9MQpBbXVsMTI9MQpBbW9kMTI9MApBTURwdDEyPTAuMDAKQU1EcEIx + Mj0wLjAwCkFndGUxMz0yCkF0cnAxMz0wCkF2b2MxMz0xCkFtdWwxMz0xCkFtb2QxMz0wCkFNRHB0MTM9MC4wMApBTURwQjEzPTAuMDAKQWd0ZTE0PTIKQXRycDE0PTAK + QXZvYzE0PTEKQW11bDE0PTEKQW1vZDE0PTAKQU1EcHQxND0wLjAwCkFNRHBCMTQ9MC4wMApBZ3RlMTU9MgpBdHJwMTU9MApBdm9jMTU9MQpBbXVsMTU9MQpBbW9kMTU9 + MApBTURwdDE1PTAuMDAKQU1EcEIxNT0wLjAwCkFndGUxNj0yCkF0cnAxNj0wCkF2b2MxNj0xCkFtdWwxNj0xCkFtb2QxNj0wCkFNRHB0MTY9MC4wMApBTURwQjE2PTAu + MDAKI2NtPUVOVjEKTW9kZT0yCmlNb2RlPTAKc01vZGU9MAppbml0PTAuMDAKQXRrPTAuMDAKRGVjPTgxLjUwClN1cz0xMC4wMApTdXNUPTAuMDEKU3VzMj0wLjAwClJl + bD0xNS4wMApWZWw9NzAuMDAKVjJJPTAuMDAKVjJBPTAuMDAKVjJEPTAuMDAKVjJTPTAuMDAKVjJGUj0wLjAwClYyUzI9MC4wMApWMlI9MC4wMApLMkk9MC4wMApLMkE9 + LTguMDAKSzJEPS0xMDAuMDAKSzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4wMApLMlI9MC4wMApTbG9wZT0tODAuMDAKVEJhc2U9MAojY209RU5WMgpNb2RlPTIKaU1v + ZGU9MApzTW9kZT0wCmluaXQ9MC4wMApBdGs9MC4wMApEZWM9ODAuMDAKU3VzPTM0LjUwClN1c1Q9MC4wMApTdXMyPTAuMDAKUmVsPTUwLjAwClZlbD03Mi4wMApWMkk9 + MC4wMApWMkE9MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJTMj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksyQT0wLjAwCksyRD0wLjAwCksyUz0wLjAw + CksyRlI9MC4wMApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9LTcwLjAwClRCYXNlPTAKI2NtPUVOVjMKTW9kZT0wCmlNb2RlPTAKc01vZGU9MAppbml0PTAuMDAKQXRr + PTAuMDAKRGVjPTUwLjAwClN1cz04MC4wMApTdXNUPTAuMDAKU3VzMj0wLjAwClJlbD0xNS4wMApWZWw9MzAuMDAKVjJJPTAuMDAKVjJBPTAuMDAKVjJEPTAuMDAKVjJT + PTAuMDAKVjJGUj0wLjAwClYyUzI9MC4wMApWMlI9MC4wMApLMkk9MC4wMApLMkE9MC4wMApLMkQ9MC4wMApLMlM9MC4wMApLMkZSPTAuMDAKSzJTMj0wLjAwCksyUj0w + LjAwClNsb3BlPTAuMDAKVEJhc2U9MAojY209RU5WNApNb2RlPTAKaU1vZGU9MApzTW9kZT0wCmluaXQ9MC4wMApBdGs9MC4wMApEZWM9NTAuMDAKU3VzPTgwLjAwClN1 + c1Q9MC4wMApTdXMyPTAuMDAKUmVsPTE1LjAwClZlbD0zMC4wMApWMkk9MC4wMApWMkE9MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJTMj0wLjAwClYy + Uj0wLjAwCksyST0wLjAwCksyQT0wLjAwCksyRD0wLjAwCksyUz0wLjAwCksyRlI9MC4wMApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9MC4wMApUQmFzZT0wCiNjbT1N + U0VHMQpUbVVuPTMKRW52PTEyClZlbD03NC4wMApBdGs9MC4wMApMcHQ9MC4wMApSZWw9NC4wMApUcmlnPTAKI2NtPU1TRUcyClRtVW49MQpFbnY9MTMKVmVsPTAuMDAK + QXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAKVHJpZz0wCiNjbT1NU0VHMwpUbVVuPTEKRW52PTE0ClZlbD0wLjAwCkF0az0wLjAwCkxwdD0wLjAwClJlbD0wLjAwClRy + aWc9MAojY209TVNFRzQKVG1Vbj0xCkVudj0xNQpWZWw9MC4wMApBdGs9MC4wMApMcHQ9MC4wMApSZWw9MC4wMApUcmlnPTAKI2NtPUxGTzEKU3luYz00ClRyaWc9MQpX + YXZlPTAKUGhzZT01MC4wMApSYXRlPTg4LjAwCkFtcD0xMDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz0xNgpVV3Y9MApEbHk9MC4wMApETVMxPTExCkRNRDE9MTAwLjAw + CkZNUzE9MTEKRk1EMT0xMi4wMAojY209TEZPMgpTeW5jPS0zClRyaWc9MApXYXZlPTAKUGhzZT02MC4wMApSYXRlPTcyLjAwCkFtcD0wLjAwClNsZXc9MQpOc3RwPTE2 + ClN0cHM9MTcKVVd2PTAKRGx5PTIwLjAwCkRNUzE9MApETUQxPTAuMDAKRk1TMT0wCkZNRDE9MC4wMAojY209TEZPMwpTeW5jPTQKVHJpZz0wCldhdmU9MApQaHNlPTAu + MDAKUmF0ZT0xMDAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTE4ClVXdj0wCkRseT0wLjAwCkRNUzE9MApETUQxPTAuMDAKRk1TMT0wCkZNRDE9MC4w + MAojY209TEZPNApTeW5jPTQKVHJpZz0wCldhdmU9MApQaHNlPTAuMDAKUmF0ZT0xMDAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTE5ClVXdj0wCkRs + eT0wLjAwCkRNUzE9MApETUQxPTAuMDAKRk1TMT0wCkZNRDE9MC4wMAojY209TU1hcDEKTW9kZT0zCk1TcmM9MApTdHBzPTIwCk51bT0xNwojY209TU1hcDIKTW9kZT0z + Ck1TcmM9MApTdHBzPTIxCk51bT0xNwojY209TU1hcDMKTW9kZT0zCk1TcmM9MApTdHBzPTIyCk51bT0xNwojY209TU1hcDQKTW9kZT0zCk1TcmM9MApTdHBzPTIzCk51 + bT0xNwojY209TU1peDEKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1peDIKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01 + MC4wMAojY209TU1peDMKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1peDQKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01 + MC4wMAojY209R3JpZApHcmlkPTI0CkdCeXA9MAojY209T1NDMQpXYXZlPTMKVHVuZT0tMjQuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0xOApUTURwdD0wLjAwClBoc2U9 + MzAuMDAKUGhzTVNyYz0yMgpQaHNNRHB0PTUuMDAKV051bT0xLjAwCldQU3JjPTEKV1BEcHQ9MS4wMApWdG9EPTAuMDAKQ3VydmU9MjUKUHJlYz01LjAwCkZYMVRwPTIK + U0ZYMT0zNC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgyVHA9MQpTRlgyPTUyLjAwCkZYMlNjPTAKRlgyRHQ9MC4wMApQb2x5PTAKRHR1bj0tNC4wMApLVnNjPTI2ClZv + bD0wLjAwClZvbFNjPTE4ClZvbER0PTEwMC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBv + bFc9NTAuMDAKUHdtT249MApXYVRiPTI3ClJlUGhzPTEKTm9ybT0wLjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT0yOAojY209T1NDMgpXYXZlPTAKVHVuZT0wLjAwCktl + eVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwClBoc2U9MC4wMApQaHNNU3JjPTAKUGhzTURwdD0wLjAwCldOdW09MS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRv + RD0wLjAwCkN1cnZlPTI5ClByZWM9NS4wMApGWDFUcD0wClNGWDE9MC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgyVHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0w + LjAwClBvbHk9MApEdHVuPTAuMDAKS1ZzYz0zMApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAw + ClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBvbFc9NTAuMDAKUHdtT249MApXYVRiPTMxClJlUGhzPTAKTm9ybT0wLjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT0z + MgojY209T1NDMwpXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwClBoc2U9MC4wMApQaHNNU3JjPTAKUGhzTURwdD0wLjAwCldO + dW09MS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD0wLjAwCkN1cnZlPTMzClByZWM9NS4wMApGWDFUcD0wClNGWDE9MC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgy + VHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MApEdHVuPTAuMDAKS1ZzYz0zNApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4w + MApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBvbFc9NTAuMDAKUHdtT249MApXYVRiPTM1ClJlUGhzPTAKTm9y + bT0wLjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT0zNgojY209T1NDNApXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwClBoc2U9 + MC4wMApQaHNNU3JjPTAKUGhzTURwdD0wLjAwCldOdW09MS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD0wLjAwCkN1cnZlPTM3ClByZWM9NS4wMApGWDFUcD0wClNG + WDE9MC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgyVHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MApEdHVuPTAuMDAKS1ZzYz0zOApWb2w9MTAw + LjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBvbFc9NTAu + MDAKUHdtT249MApXYVRiPTM5ClJlUGhzPTAKTm9ybT0wLjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT00MAojY209Tm9pc2UxClR5cGU9MApGMT0xMDAuMDAKRjFTcmM9 + MApGMURwdD0wLjAwCkYyPTAuMDAKRjJTcmM9MApGMkRwdD0wLjAwCktWc2M9NDEKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQ + YW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCiNjbT1Ob2lzZTIKVHlwZT0wCkYxPTEwMC4wMApGMVNyYz0wCkYxRHB0PTAuMDAKRjI9MC4wMApGMlNyYz0wCkYyRHB0 + PTAuMDAKS1ZzYz00MgpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKI2NtPVZD + RjEKVHlwPTAKQ3V0PTAuMDAKUmVzPTAuNTAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZNMT0xMzAuMDAKRlMxPTE4CkZNMj0wLjAwCkZTMj0wCktleVNjbD0yMC4wMAojY209 + VkNGMgpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209 + VkNGMwpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209 + VkNGNApUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209 + Rk1PMQpXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwClZ0b0Q9MC4wMApEdHVu + PTAuMDAKS1ZzYz00MwpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKR2V0cj0w + CiNjbT1GTU8yCldhdmU9MApUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKRk09MC4wMApGTVNyYz0wCkZNRHB0PTAuMDAKVnRvRD0wLjAw + CkR0dW49MC4wMApLVnNjPTQ0ClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQb2x5PTAKUG9sVz01MC4wMApH + ZXRyPTAKI2NtPUZNTzMKV2F2ZT0wClR1bmU9MC4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApGTT0wLjAwCkZNU3JjPTAKRk1EcHQ9MC4wMApWdG9E + PTAuMDAKRHR1bj0wLjAwCktWc2M9NDUKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUw + LjAwCkdldHI9MAojY209Rk1PNApXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0wLjAw + ClZ0b0Q9MC4wMApEdHVuPTAuMDAKS1ZzYz00NgpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBv + bFc9NTAuMDAKR2V0cj0wCiNjbT1Db21iMQpNb2RlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkRldG49MC4wMApWdG9EPTAuMDAK + RkI9MC4wMApGQlNyYz0wCkZCRHB0PTAuMDAKRGFtcD0wLjAwCkRtcFNyYz0wCkRtcERwdD0wLjAwCkV4Yz0wLjAwCkluaj0wLjAwCklualNyYz0wCkluakRwdD0wLjAw + ClRuZT01MC4wMApUbmVTcmM9MApUbmVEcHQ9MC4wMApTZWM9MC4wMApTZWNTcmM9MApTZWNEcHQ9MC4wMApEaXN0PTAuMDAKRHJ5PTAuMDAKVm9sPTEwMC4wMApWb2xT + Yz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCkZpbGw9MAojY209Q29tYjIKTW9kZT0wClR1bmU9MC4wMApL + ZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApEZXRuPTAuMDAKVnRvRD0wLjAwCkZCPTAuMDAKRkJTcmM9MApGQkRwdD0wLjAwCkRhbXA9MC4wMApEbXBTcmM9 + MApEbXBEcHQ9MC4wMApFeGM9MC4wMApJbmo9MC4wMApJbmpTcmM9MApJbmpEcHQ9MC4wMApUbmU9NTAuMDAKVG5lU3JjPTAKVG5lRHB0PTAuMDAKU2VjPTAuMDAKU2Vj + U3JjPTAKU2VjRHB0PTAuMDAKRGlzdD0wLjAwCkRyeT0wLjAwClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQ + b2x5PTAKUG9sVz01MC4wMApGaWxsPTAKI2NtPVNoYXBlMQpUeXBlPTAKRGVwdGg9MC4wMApETVNyYz0wCkRNRHB0PTAuMDAKRWRnZT0wLjAwCkVNU3JjPTEwCkVNRHB0 + PTEwMC4wMApJbnB1dD0wLjAwCk91dHB1dD0xNS4wMApIaU91dD0wLjI0CiNjbT1TaGFwZTIKVHlwZT0wCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9 + NzUuMDAKRU1TcmM9MApFTURwdD0wLjAwCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKSGlPdXQ9MC4wMAojY209TWl4MQpQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBu + TUQ9MC4wMApQbk1TPTAKI2NtPU1peDIKUGFuPTAuMDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1NaXgzClBhbj0wLjAwCk1peD01MC4wMApQ + bk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209TWl4NApQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPVhNRjEKVHlwPTAKQ3V0PTE1 + MC4wMApSZXM9MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMApGT2ZmPTAuMDAKRk9Nb2Q9MC4wMApGT1NyYz0wClhGTT0wLjAwClhG + TUQ9MC4wMApYRk1TPTAKQmlhcz0wLjAwCk9Mb2FkPTAuMDAKQ2xpY2s9MC4wMApEcnY9MApSb3V0PTAKVHlwMj0tMQojY209WE1GMgpUeXA9MApDdXQ9MTUwLjAwClJl + cz0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCkZPZmY9MC4wMApGT01vZD0wLjAwCkZPU3JjPTAKWEZNPTAuMDAKWEZNRD0wLjAw + ClhGTVM9MApCaWFzPTAuMDAKT0xvYWQ9MC4wMApDbGljaz0wLjAwCkRydj0wClJvdXQ9MApUeXAyPS0xCiNjbT1TQjEKUmFuZ2U9MApGcmVxPTAuMDAKRk1TcmM9MApG + TURwdD0wLjAwCk9mZnM9MC4wMApPTVNyYz0wCk9NRHB0PTAuMDAKTWl4PTUwLjAwCk1NU3JjPTAKTU1EcHQ9MC4wMAojY209U0IyClJhbmdlPTAKRnJlcT0wLjAwCkZN + U3JjPTAKRk1EcHQ9MC4wMApPZmZzPTAuMDAKT01TcmM9MApPTURwdD0wLjAwCk1peD01MC4wMApNTVNyYz0wCk1NRHB0PTAuMDAKI2NtPURpc3QxClR5cGU9MApJbnB1 + dD0wLjAwCk91dHB1dD0wLjAwClByZVRpbHQ9MC4wMApQc3RUaWx0PTAuMDAKQ250RnJlcT0xMDAuMDAKTG93PTAuMDAKSGlnaD0wLjAwClBvc3RGbHQ9MAojY209RGlz + dDIKVHlwZT0wCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKUHJlVGlsdD0wLjAwClBzdFRpbHQ9MC4wMApDbnRGcmVxPTEwMC4wMApMb3c9MC4wMApIaWdoPTAuMDAKUG9z + dEZsdD0wCiNjbT1Gb2xkMQpSaXBwbGVzPTQKRm9sZHM9MTAuMDAKRk1vZD0wLjAwCkZTcmM9MApCaWFzPTAuMDAKQk1vZD0wLjAwCkJTcmM9MApGUmF0aW89NTAuMDAK + RlNsb3BlPTEwLjAwCkNBbmdsPTUwLjAwCkxpdmU9MAojY209Rm9sZDIKUmlwcGxlcz00CkZvbGRzPTEwLjAwCkZNb2Q9MC4wMApGU3JjPTAKQmlhcz0wLjAwCkJNb2Q9 + MC4wMApCU3JjPTAKRlJhdGlvPTUwLjAwCkZTbG9wZT0xMC4wMApDQW5nbD01MC4wMApMaXZlPTAKI2NtPVZDQTEKUGFuMT0wLjAwClBhbk1TMT0wClBhbk1EMT0wLjAw + ClZvbDE9NTAuMDAKVkNBMT0xCk1vZFNyYzE9MApNb2REcHQxPTAuMDAKUGFuMj0wLjAwClBhbk1TMj0wClBhbk1EMj0wLjAwClZvbDI9MC4wMApWQ0EyPTEKTW9kU3Jj + Mj0wCk1vZERwdDI9MC4wMApQYW4zPTAuMDAKUGFuTVMzPTAKUGFuTUQzPTAuMDAKVm9sMz0wLjAwClZDQTM9MQpNb2RTcmMzPTAKTW9kRHB0Mz0wLjAwClBhbjQ9MC4w + MApQYW5NUzQ9MApQYW5NRDQ9MC4wMApWb2w0PTAuMDAKVkNBND0xCk1vZFNyYzQ9MApNb2REcHQ0PTAuMDAKTVQxPTAKTVQyPTAKTVQzPTAKTVQ0PTAKUEIxPTAKUEIy + PTAKUEIzPTAKUEI0PTAKQnVzMT0wCkJ1czI9MApCdXMzPTAKQnVzND0wClNlbmQxPTAuMDAKU25TcmMxPTAKU25EcHQxPTAuMDAKU2VuZDI9MC4wMApTblNyYzI9MApT + bkRwdDI9MC4wMApBdHRTPTEKI2NtPUdyaWRGWApHcmlkPTQ3CkdCeXA9MAojY209TW9kRlgxCk1vZGU9MApDZW50PTIwLjAwClNwZWQ9NTAuMDAKUGhPZmY9NTAuMDAK + RHB0aD01MC4wMApGZWVCPTAuMDAKTWl4PTAuMDAKTEN1dD0wLjAwCkhDdXQ9MTAwLjAwClF1YWQ9MC4wMApRcGhzPTI1LjAwCkxlcT0wLjAwCkhlcT0wLjAwClExPTAu + MDAKUTI9MC4wMApFUW9uPTEKI2NtPU1vZEZYMgpNb2RlPTAKQ2VudD0yMC4wMApTcGVkPTUwLjAwClBoT2ZmPTUwLjAwCkRwdGg9NTAuMDAKRmVlQj0wLjAwCk1peD0w + LjAwCkxDdXQ9MC4wMApIQ3V0PTEwMC4wMApRdWFkPTAuMDAKUXBocz0yNS4wMApMZXE9MC4wMApIZXE9MC4wMApRMT0wLjAwClEyPTAuMDAKRVFvbj0xCiNjbT1EZWxh + eTEKTW9kZT0zCk1peD0wLjAwCkZCPTE1LjUwCkNCPTE5LjUwCkxQPTYwLjAwCkhQPTExLjUwCkRydj0wLjAwClN5bmMxPTkKU3luYzI9OQpTeW5jMz00ClN5bmM0PTQK + VDA9NTguMDAKVDE9NTguMDAKVDI9MTAwLjAwClQzPTAuMDAKUGFuMT0tNTAuMDAKUGFuMj01NC4wMApQYW4zPS0yLjAwClBhbjQ9MC4wMAojY209RGVsYXkyCk1vZGU9 + MApNaXg9MC4wMApGQj0xMC4wMApDQj0yNS4wMApMUD0xMDAuMDAKSFA9MC4wMApEcnY9MC4wMApTeW5jMT00ClN5bmMyPTQKU3luYzM9NApTeW5jND00ClQwPTEwMC4w + MApUMT0xMDAuMDAKVDI9MTAwLjAwClQzPTEwMC4wMApQYW4xPS0xMDAuMDAKUGFuMj0xMDAuMDAKUGFuMz0tMTAwLjAwClBhbjQ9MTAwLjAwCiNjbT1TaGFwZTMKVHlw + ZT0wCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9NzUuMDAKRU1TcmM9MApFTURwdD0wLjAwCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKSGlPdXQ9MC4w + MAojY209U2hhcGU0ClR5cGU9MApEZXB0aD0wLjAwCkRNU3JjPTAKRE1EcHQ9MC4wMApFZGdlPTc1LjAwCkVNU3JjPTAKRU1EcHQ9MC4wMApJbnB1dD0wLjAwCk91dHB1 + dD0wLjAwCkhpT3V0PTAuMDAKI2NtPU1peDUKUGFuPTAuMDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1NaXg2ClBhbj0wLjAwCk1peD01MC4w + MApQbk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209UmV2MQpNb2RlPTAKRHJ5PTgyLjAwCldldD01Mi4wMApGQj00My4wMApEYW1wPTQuMDAKU2l6ZT03NS4wMApTcGQ9 + NTAuMDAKRHB0PTUwLjAwCkRGQj03MC4wMApEU2l6ZT01MC4wMApFTWl4PTEwMC4wMApETW9kPTUwLjAwCkRTcGQ9NTUuMDAKUHJlPTAuMDAKI2NtPUNvbXAxClR5cGU9 + MApSYXQ9ODguMDAKVGhyZXM9LTQ5LjUwCkF0dD00OC4wMApSZWw9NTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9Ni4wMAojY209Q29tcDIKVHlwZT0wClJhdD0xMDAuMDAK + VGhyZXM9LTIwLjAwCkF0dD0zMC4wMApSZWw9NTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMAojY209RVExCmZjMT0yMC4wMApyZXMxPTI1LjAwCmdhaW4xPTAuMDAK + ZmMyPTQwLjAwCnJlczI9MjUuMDAKZ2FpbjI9MC4wMApmYzM9NjAuMDAKcmVzMz0yNS4wMApnYWluMz0wLjAwCmZjND04MC4wMApyZXM0PTI1LjAwCmdhaW40PTAuMDAK + I2NtPUVRMgpmYzE9MjAuMDAKcmVzMT0yNS4wMApnYWluMT0wLjAwCmZjMj00MC4wMApyZXMyPTI1LjAwCmdhaW4yPTAuMDAKZmMzPTYwLjAwCnJlczM9MjUuMDAKZ2Fp + bjM9MC4wMApmYzQ9ODAuMDAKcmVzND0yNS4wMApnYWluND0wLjAwCiNjbT1WQ0Y1ClR5cD0wCkN1dD0xNTAuMDAKUmVzPTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZN + MT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCiNjbT1WQ0Y2ClR5cD0wCkN1dD0xNTAuMDAKUmVzPTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZN + MT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCiNjbT1TQjMKUmFuZ2U9MApGcmVxPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwCk9mZnM9MC4wMApP + TVNyYz0wCk9NRHB0PTAuMDAKTWl4PTUwLjAwCk1NU3JjPTAKTU1EcHQ9MC4wMAojY209WE1GMwpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkZNMT0wLjAwCkZTMT0w + CkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCkZPZmY9MC4wMApGT01vZD0wLjAwCkZPU3JjPTAKWEZNPTAuMDAKWEZNRD0wLjAwClhGTVM9MApCaWFzPTAuMDAKT0xv + YWQ9MC4wMApDbGljaz0wLjAwCkRydj0xClJvdXQ9MApUeXAyPS0xCiNjbT1EaXN0MwpUeXBlPTAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMApQcmVUaWx0PTAuMDAKUHN0 + VGlsdD0wLjAwCkNudEZyZXE9MTAwLjAwCkxvdz0wLjAwCkhpZ2g9MC4wMApQb3N0Rmx0PTAKI2NtPURpc3Q0ClR5cGU9MApJbnB1dD0wLjAwCk91dHB1dD0wLjAwClBy + ZVRpbHQ9MC4wMApQc3RUaWx0PTAuMDAKQ250RnJlcT0xMDAuMDAKTG93PTAuMDAKSGlnaD0wLjAwClBvc3RGbHQ9MAojY209TnVSZXYxClByZT01MC4wMApEYW1wPTg2 + LjAwCkRlY2F5PTUwLjAwClNpemU9MTQyLjAwClRvbmU9LTMwLjAwCldpZHRoPTgwLjAwCk1peD0wLjAwCiNjbT1aTWFzClJldDE9MC4wMApSZXQyPTAuMDAKTWFzdD0x + MDAuMDAKWFkxTD00OApYWTJMPTQ5ClhZM0w9NTAKWFk0TD01MQpYWTFUPTUyClhZMlQ9NTMKWFkzVD01NApYWTRUPTU1Ck9TQzE9NTYKT1NDMj01NwpPU0MzPTU4Ck9T + QzQ9NTkKTVNFRzE9NjAKTVNFRzI9NjEKTVNFRzM9NjIKTVNFRzQ9NjMKTVNFRzU9NjQKTVNFRzY9NjUKTVNFRzc9NjYKTVNFRzg9NjcKUmV2MT02OApQbjM9MApQbjQ9 + MApQbjU9MApQbjY9MApQbjc9MApQbjg9MApQbjk9MApQbjEwPTAKUG4xMT0wClJhY2swPTY5ClJhY2sxPTcwCiNjbT1MYXlNZW0KTGF5TWVtMT0wCkxheU1lbTI9MApM + YXlNZW0zPTAKTGF5TWVtND0wCkxheU1lbTU9MAoKCgoKLy8gU2VjdGlvbiBmb3IgdWdseSBjb21wcmVzc2VkIGJpbmFyeSBEYXRhCi8vIERPTidUIFRPVUNIIFRISVMK + CiQkJCQ3NzM0MAo/YWFhYWlhZHA6amtqampqbG86Z2RnY2VuZWI6YWFhYWlhbHA6YWFhYWFhZHA6YWFhYWFhZWE6Y2FhYWFhYWE6CmJhYWFhYWFhOmhmZ21oZWFhOmFl + YWFhYWFhOmFhOmRwOmxwOmViOmVjOmRvOmxvOmVhOmlhOmNhOmRuOmFrOmcKZjpoZTpsbjpnYjptYTpnYTpoYTprYTpncDpvYTpkYTpiaTplZzpnaiFzQWFiQTI2NXNI + QTEwZWlmZEplZlpnaApnaU5aTWhkSmZhaGRNaGZnZWNvZ29nbGhkZ2dBNDFzSEE3NHNIQTEwYWNtYmtnZGVlbGpjYWVZUGxlZ2thaGtwCmtkaW9oa21jVm1lZWxsaUto + aGtscGFuY2libWJsYU9ZY2xvZWNvZmVnbW1mZGlObmhkZHBmYXBnb2pra21wa2MKbWdjbWpramJocGZjY1hjYmNtb2dsZW9pcGxmamVtZ2dzekE3aWlsZ2NrQXNBYWJB + OWVpVWhoUGhjZ2VKZmRnZApQaGMxQTI0M3NJQTEzOHNBYWJBMjY1c0lBNnFBMTI3c0lBNnFBMTI3c0hBMTBnZU1nZ1BoZmdtTkpoZGdkUGdtCk1BMjJxdkExSDFBMUlI + QTFUSEExUUhBMVZIc0FhY0E1cUEzcXZBMUgxQTFJSEExVEhBMVFIQTFWSEEyREExYmEKREExSkRBMVdEQTFIREExZmFEQTFSREExU0RBMUlEQTFpaURBMWphREExamlE + QTFUREExa2lEQTFsYURBMWxpRApBMVFEQTFtaURBMW5hREExbmlEQTFWREExb2lEQTFwYURBMXBpREEyRUExYWVFQTFhaUVBMWFtRUExYmFFQTFiCmVFQTFYRUExYm1F + QTFKRUExY2VFQTFjaUVBMWNtRUExV0VBMWRlRUExZGlFQTFkbUVBMUhFQTFlZUVBMWVpRUEKMWVtRUExZmFFQTFmZUVBMWZpRUExZm1FQTFSRUExZ2VFQTFnaUVBMWdt + RUExU0VBMU5FQTFoaUVBMWhtRUExSQpFQTFpY0VBMWllRUExaWdFQTFpaUVBMWlrRUExaW1FQTFpb0VBMWphRUExamNFQTFqZUVBMWpnRUExamlFQTFqCmtFQTFqbUVB + MWpvRUExVEVBMWtjRUExa2VFQTFrZ0VBMWtpRUExa2tFQTFrbUVBMWtvRUExbGFFQTFsY0VBMWwKZUVBMWxnRUExbGlFQTFsa0VBMWxtRUExR0VBMVFFQTFtY0VBMW1l + RUExbWdFQTFtaUVBMW1rRUExbW1FQTFtbwpFQTFuYUVBMW5jRUExbmVFQTFuZ0VBMW5pRUExbmtFQTFubUVBMW5vRUExVkVBMW9jRUExb2VFQTFvZ0VBMW9pCkVBMW9r + RUExb21FQTFvb0VBMXBhRUExcGNFQTFwZUVBMXBnRUExcGlFQTFwa0VBMXBtRUExcG9Fc1dhY0E1cUEKM2FmQTJ6MWFiekExNHF0Ymtrb2FoQ0EzcVVLTENMbmhrZEtB + MkttZFdlbUJBM2plbWNwZkdpbmpuZG1CbXBwZApsbUZBOW5hQmtsa2sxRkEzckExbmVCQTFIQkEzckExbmVEZ29ubGRnRkEzcnFBN3JBMUgxQTdyQTFUSEE3ckExClZI + QTdyQTFSREE3ckExZmFEQTdyQTFIREE3ckExV0RBN3JBMUpEQTdyQTFiYURBN3JBMWlpREE3ckExamFEQTcKckExamlEQTdyQTFUREE3ckExa2lEQTdyQTFsYURBN3JB + MWxpREE3ckExUURBN3JBMW1pREE3ckExbmFEQTdyQQoxbmlEQTdyQTFWREE3ckExb2lEQTdyQTFwYURBN3JBMXBpREE3ckExNXNXYWNBNXFBM3h6YWlBMmFiekEyMnJx + CkE3cnZBN3JBMUgxQTdyQTFJSEE3ckExVEhBN3JBMVFIQTdyQTFWSEE3ckEyREE3ckExYmFEQTdyQTFKREE3ckEKMVdEQTdyQTFIREE3ckExZmFEQTdyQTFSREE3ckEx + U0RBN3JBMUlEQTdyQTFpaURBN3JBMWphREE3ckExamlEQQo3ckExVERBN3JBMWtpREE3ckExbGFEQTdyQTFsaURBN3JBMVFEQTdyQTFtaURBN3JBMW5hREE3ckExbmlE + QTdyCkExVkRBN3JBMW9pREE3ckExcGFEQTdyQTFwaURBN3JBMTVzV2FjQTVxQTN4emFpQTJhYnpBMjJycUE3cnZBN3IKQTFIMUE3ckExSUhBN3JBMVRIQTdyQTFRSEE3 + ckExVkhBN3JBMkRBN3JBMWJhREE3ckExSkRBN3JBMVdEQTdyQQoxSERBN3JBMWZhREE3ckExUkRBN3JBMVNEQTdyQTFJREE3ckExaWlEQTdyQTFqYURBN3JBMWppREE3 + ckExVERBCjdyQTFraURBN3JBMWxhREE3ckExbGlEQTdyQTFRREE3ckExbWlEQTdyQTFuYURBN3JBMW5pREE3ckExVkRBN3IKQTFvaURBN3JBMXBhREE3ckExcGlEQTdy + QTE1c1dhY0E1cUEzeHphaUEyYWJ6QTIycnFBN3J2QTdyQTFIMUE3cgpBMUlIQTdyQTFUSEE3ckExUUhBN3JBMVZIQTdyQTJEQTdyQTFiYURBN3JBMUpEQTdyQTFXREE3 + ckExSERBN3JBCjFmYURBN3JBMVJEQTdyQTFTREE3ckExSURBN3JBMWlpREE3ckExamFEQTdyQTFqaURBN3JBMVREQTdyQTFraUQKQTdyQTFsYURBN3JBMWxpREE3ckEx + UURBN3JBMW1pREE3ckExbmFEQTdyQTFuaURBN3JBMVZEQTdyQTFvaURBNwpyQTFwYURBN3JBMXBpREE3ckExNXNJQTZxQTEyN3NJQTZxQTEyN3NJQTZxQTEyN3NJQTZx + QTEyN3NBYWNBNXFTCmtuZGZGVm9lZWtDWG1jZ2lDUWtraG9LUmdtb2ZPU2Zta2NGQWNtYWpHaWlmb2lqRm9pWW9wR1JhaWxkR1dnbWwKZ0ZTcGJjYkZrZWZmYmdCSGFo + bmdLUm1vcGRGaW1nZ0xDZWVkamRlQ0FoaWdjRlZmaWdpR1hucG5oRmZpcHBrbwpHVGVsWkdWZ2VobEJrZWtvZWhCY2lvamtjR25hcGxlbkNiYXBhbnBHZWVnY2hkQ2ht + ZmpibUNqYW1mam9GSk5rCmxGa21oa2VmQ2ptcGxkaEJKYmVoY0NjaWlwYWNCZ21HZXBCUXBlY2pPYmFpZ2ltR0lhaWxiRkpBVEttbWJnY2sKQlFrYmNmR1Fma29uS29l + aWVGQ2JtRUhDUm5mY25HSm5sb2xHVGhibGFLYWVlbGFkQ0lnZXBtR2FpamticEJlbQpvY2JkQmdtY2NibkNuaUVlb0JWam1jbENnZWJoZGpCa21kZzFCSldvaUdtaWxk + YWZDSWlwa2lkbWhpUmtnR0ljCmhoYk9sYVBTQ29tZG1LQ2xpZWZrZkZJZmNORmdpcGJlZEJmaW5lbWNGcGVqZmJuQkllZUtPQWJtbWVPbWVkaWMKcEJYSmtkRmxhaWVn + bkNqaXBqY2xCbm1hamZvQmphZGVkZEZIb2RuYUtmYWNiZ2NDcGVkY2hvQm1lbm5haUNJbQpmZWlCbGlhbWVmQ1hka2loRmtpa2RtakZnaWZwZmpDUmNjQjFubW1oZXBD + b21RYWJCbGFwbGdsQkhsZWprT2JhCm5kZWhHYWVBWUJlaWlpWEJTZm1kZ0NuZWZhZmRCb2Vpb2VtQ0lqZmRpRkFqZmxhZG1vbWhmaGpCZGVua2FoQmkKaWNmZmRDSW5s + b2VGaWVrbGFvQm5hb29aR1FmbGJtR25pb2llaUJqbWhrYmRCam1JZW5DZmFKbm1GcGFkbGFlQgpqaWFtaW9HYWlnbG9wR0lqb29lRklhaGZjT2ppbWRqbEZsYWlkWkdR + RUdLUmZkQkNibW9mZGZDbGFiYWNpRkptCm5sZ0dBamNiZmxtYWVqZWZhQ2xtRmVpQ0hmaW5mS0lGbmJLamVjb05Cc0FhY0E1cWxpamFuakZKcG9CMVFhaW0KZE9UaGNs + Yk9OanBla0JkaXBqZWRCQWNlbmVHbG1oYmRqQ25hcGpIR1doYmVvQ29pWGVvQ1FkZmVuS0lmZWloZAptb2lkZGZnQlJJcG5GTkFFQlZqbnBvS0FkY2ttRmRpYW5iaENO + a2JNQ1JmZm9uT29pYnBkbENJZ2lnaEdmbUxnCmNDamVtaGNuQ1Jjb0dGcGFhYmlqRmVlZmxoZkNpZWZkY2hCbWlobmZtQkhtbWVpRlRjY3BlS2dlaHBma0NWYmcKZWpC + bGFtcHBiR2plZWtnZUNqaWVlb2tHcGlqaGZnQkhvZmhtQnBhZ2RoaUdnaWFpbWJHSmJma2hGSGpub25PUQprYmZqS2Jtam5ja0NmaWNjYWlDUWFuZWRGV2dsZmdHcGlj + ZHBmRkFibmdsR0ptbWRiRmxlR2VwQ2dtS2hiQ0FoCmdoZmRtSWtwZGtHbWltbXBrRmZhZmJVR1FjbGJvRm1paWNSQ2llY2Vlb0NWZmpvak9oaWtoYmRDUWNjY29Hb20K + bm5jaUNlaXBha2NHVGhoY2NCbmFsZW9iRm5lY2dnY0NwZWdrZ2RCZWlmYkhDQXBsb21GbWVDYWpCb2luZmltRwpSYWRwaUdXcGZtakZKaGptbE9qYW9paGtGamlpaWFv + Q1RJcG9HcGVqamNtQmhpYm9vZ0ZJbXBoYkNlaW1tZWZCCnBlb2xjZkNuZW1vWUJnaW1laGJDSWluSEZRYW5OT29tbWZhbEJRcGtnbkZoaWtrYWZCSmdvaWxPVmpoSU9l + aXAKZGVqQmphTkVCZmlBbmpHb2VvYkVDSGZwcGZHa2libmdpQm9tTmdoQmppRGlqR2lpY2NiZ0NBYmViYk9obWVuYwpkQ1RoZnBpT2JhbWVocEJuaURoZEJqYWpqaGtH + ZGlkaGtlR1RnbWZlRkpnZ25jR1RlZmlqS2llVmJoQ1FobmNlCkNiYURtaEZJYmpta2RtYmVOZWZCbGFZbWNGVGluYmhCVnBhbmxPZmFsbGRoR1NiZWZnQmxpa2NkZUJh + ZWJmZW8KQ2VlbWdhaEJhaWNvTkJBZ2lqZWRsZmFwZ2JrRnNBYWNBNXFmbU9nZUJKZmJhYkJYSWVqQ2ttZGlhZUJOaW1TQgpJZG1haE9BaWVkZEdlZWNmZWpCaW1ub2Ju + QldwZ2RjQ1hwYWZnQmtlbGZZQ2phZm9QRkhoaGpvS25hYWdobUNOCmpwZW9CZWVjcExDQWptZGpCbWlwZGZoQlFmaWVvS2ZhYWpMRlhsbGVvQlZnZ2JmQmZtZWRlaENu + aUxwYkZXYWMKYmRCSmppaGhHYmFoYmVqR2llVmRmQkptZGdjRmJhcGViaEJIZGZkbUtkaWpsbGVGSGRtcGZHbGFubm9mR25pZwppbm9GUm9rUU9WY2xnbkZRbGpiZ0Nw + bWRiTENqaW5ibGpGSmRsbWJGZmlPaG9CZGlvY29nRm9lQ0hCVGdjZGtGClFkYmVtRm1tbmRVQ2FlY2JoY0NBb2NucE9waWJsY2pDVGhucG5PZ21taWhoQ2NpZmhkZkNI + a2ttaEZKZ2xrbEsKa21saU1CVnBsVEZkaW1vZmhCcGFlamVwRmJhZWhjakZpaWdkaG5CZmFjaWNuQm9taG9laUNubWdtWEJSYm5nZwpDbmFvZ29oRmZpcGFtaUZhbVNl + a0NsaWZiZnBCQW9kUUdkbWlqZWZDcGVjbmFvQ2ZhbWphZkJqaWhkZGVCY2VTCkFDbm1raVJDUmxnYmpHZmFqaGFuQ1hib2xpR2Rtb21jbkJJamRiZ0JTamZuYUdwZWpt + YW9DUmtibGJPZ2lkamUKZENUbGRiZENlaW1tb21GbmljZmtvRmZhbWdjcENpaWFtZmdDaWlobm1oR3BtT2ZlQ2xpY2pkYkNqYW5mYW9CbgplZ2djakJvZWZlYnBDUmls + TkJmaWFiYm5Db21ubGNoQmRlZmVlZkJBZG1rbWRsQUdDS2htbm9oa0NlbWFnZmJCCnBhaWlhaUNuaWZrZGVCam1uaWZiQkljbXBuZG1IaGJrZktiYWFjaG9DbGVDZWhC + aWVvbGJsQ0hwbkpCaWloamQKaUJnaVZra0dvbWtvYW1DUWhja2VLSWNmY2dHbWlrYm5uRmtpaGRsbUdKZGJqbkdsaWpsYmxCcG1lamJtQmxtZgplYWNDYWlrYmhoQldu + Z2RoQ2plZWVkakJzQWFjQTVxZm1va0VCQWpwbmdPV2FvbGlHbGFwb2dlRlhubmtmR0FiCmpqYWxtQWlpZnBDbG1wZGZqQ0FvZGJjZG1BZ2dubGxtWGlqYW1CU2NmaGNG + a2VibWFoQlFXbXBPSWdvcGNPTm4Kb2RqQmhpY2xuakZBbnBobEJqYWhqQ0ZpbWNtZm9CZmFLTUZSWGVlRkpsYWhtQ2tlY2xnbUJqZWZuTENXa25maApCYWlPY3BDbG1o + Y2NqQmllYmZibEJTaGlsakdwYW5jZGpDSWVrbGVkbW1pamtsbUdJY21icEduaU9mZUNqZXBlCmdtQ1RLa2xLSUpibE9JaW9ucEZwYWtsaGlHV2ZoZWpGU2poZWVDa2lt + amdjQ1ZJb2xLSWdkcGhsbWZhaWhucEYKSmVmaWlHUm9nb21PcG1rZE1CUWhnZWZCcGFqZklHbGVoaWNwQ0FjZmFkZG1mYWxpb2xHQWVwampPZ2VwZWduQgprbWtmZWhC + Sm9vaXBHbWlwaWRnQ0hvaWZnT2RtWmVsQ2hpbmpja0NmYVphb0JiZWVpSkJjZVljcENSZ2NnZUNXCm9oaWpHY21jcGhmQmFtYmxoY0NTZmppY0ZBYXBqb0ZkbWlrZW1D + YW1maGRjQmxhZGRoY0NqaXBlYWZCbm1pb2YKaUJJaWVwbGRtSW5nZGpLbGFwYlpCb2lDbW5GZG1mamRtQkFYaGpPQWxpZmJsbGFtaWNnY0JuZVNjY0JnaWNvZQpkQ1Jt + ZmRmQm5tamtoaUNuaWNwbnBGUmhqb2VGaWlpbGNvQ21lQ2RjQ2JhYm1hcEZTZW5ub0dJZGVwZEtmaW1lCnBrR2phbnBoa0dSa29mbUJraWpwZmVCY2lwa2xiR21tbm9o + YkNiYWZqbm1HUXBpaGJCaWVsYWdtQm5hYmNTR1YKZWxUR1JjcGZqRm1pSm1uR0pwZG1pS25pR2FtQ2Jhb2JibENuYWprZmZGYmFoa2FjR0hwaWJwQkpMZ21Gamlobgpv + YkZuaWRkcGNHZmFobmppR0hVcGNGSkNvaUtjbWlsZmxCVk5vYkZsaWZtY2dCYWlvb2prR2hpYnBtbkdhaW1tCmJtQlRrZnBqRmplZmRoaEJzYW1hZEE1cWVlZWpmY2Vo + emFtQTJlcGZkZWRkYkEzYW5iYkExZXBmZGVkZGNBM2EKb2JjQTFlcGZkZWRkZEEzYW9iZUExZXBmZGVkZGVBM2FvWEExZmdlZFlkYkEzYWJiYkExZW9VWmhkTWRjQTFh + bwpiY0ExZW9VWmhkTWRiQTFhb2JiQTFmZ2VkWWRjQTNhY1hBMWZkZ2lQU01kYkExYWpiYkExZmdlZFlkZUEzYWNiCmNBMVllbmVwZGJBM2FvYmVBMVllbmVwZGNBM2Fv + WEExWWVuZXBkZEEzYW9iYkExWWVuZXBkZUEzYW9iY0ExZWQKVWduZ2NkYkEyTGJlQTFlZFVnbmdjZGNBMkxYQTFmZ2VkWWRkQTNhY2JiQTFmZGdpUFNNZGNBMUxiY0Ex + ZW5aaAppZGJBM0xOQTFlblpoaWRjQTNMaGlBMWVuWmhpZGRBM0xoYkExZW5aaGlkZUEzTGhjQTFmY1pnb2doZGJBMkxOCkExZmNaZ29naGRjQTJMaGlBMWZpZW5ZZGJB + M0xoYkExZmllbllkY0EzTGhjQTFmZEVkYkE0TGJlQTFmZEVkY0EKNExYQTFlZVpoZE5kYkEyTGJiQTFlZVpoZE5kY0EyTGJjQTFZVWdtZ2VkYkEyTGJlQTFZVWdtZ2Vk + Y0EyTFhBMwo4NXNhZXdBMnZBN3VxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExCjl1cUExMTl1cUExMTl1cUExMTl1cUEx + MTl1cUExMTl1cUExMTl1cUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUEKMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEz + cUExMTV0QTNxQQoxMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBNDIwN3pzeEEzdmZpZW1kbWNlaHAzUmVpCldYaHAzc0F3QTJ2cTJBMVNCQTFX + QkExVEZBMUlLQTYzam1ub05Lam1ub05GcGdrZ2xoRmptbm9ORmptbm9OS0EKNjVJS0ExSUZBMVFGQTFJRkExSUtBNTVva2VuVUtwZmtnamhGZGlPYmpCaGtuZGJsQk9v + ampuRkExSUtBMjM1cQoyQTFTQkExV0JBMVRGQTFJS0EzMXFBMzVxZWRiZ2xjbG1BMTQzb2tlblVLcGZrZ2poRmRpT2JqQmhrbmRibEJPCm9qam5GQTFJS0E3NDAzc3dB + N2ZrZGNKZmhQaGdNTlBnY2dtTUplZmhpU1VoY05BMTJzYWV3QTJ2QTd1cUExMTkKdXFBMTE5dXFBMTE5dXFBMTE5dXFBMTE5dXFBMTE5dXFBMTE5dXFBMTE5dXFBMTE5 + dXFBMTE5dXFBMTE5dXFBMQoxOXVxQTExOXVxQTExOXVxQTExOXVxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExCjE1dEEzcUExMTV0QTNx + QTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTEKMTV0QTNxQTExNXRBM3FBMTE1dEEzcUE0MjA3enN4QTN2ZmllbWRtY2VocDNS + ZWlXWGhwM3NBd0EydkE4MTkxcwp3QTdma2RjSmZoUGhnTU5QZ2NnbU1KZWZoaVNVaGNOQTEyc2Fld0EydkE3dXFBMTE5dXFBMTE5dXFBMTE5dXFBCjExOXVxQTExOXVx + QTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXVxQTExOXUKcUExMTl1cUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTEx + NXRBM3FBMTE1dEEzcUExMTV0QTNxQQoxMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBCjExNXRBM3FB + NDIwN3pzeEEzdmZpZW1kbWNlaHAzUmVpV1hocDNzQXdBMnZBODE5MXN3QTdma2RjSmZoUGhnTU4KUGdjZ21NSmVmaGlTVWhjTkExMnNhZXdBMnZBN3VxQTExOXVxQTEx + OXVxQTExOXVxQTExOXVxQTExOXVxQTExOQp1cUExMTl1cUExMTl1cUExMTl1cUExMTl1cUExMTl1cUExMTl1cUExMTl1cUExMTl1cUExMTl1cUExMTV0QTNxCkExMTV0 + QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3EKQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1 + dEEzcUExMTV0QTNxQTExNXRBM3FBNDIwN3pzeApBM3ZmaWVtZG1jZWhwM1JlaVdYaHAzc0F3QTJ2QTgxOTFzd0E3ZmtkY0pmaFBoZ01OUGdjZ21NSmVmaGlTVWhjCk5B + MTJzeEEzdmZjZmJlZkFocDRob0hCaHAzc3hBM3ZmY2ZiZWZBaHA0aG9IQmhwM3N4QTN2ZmNmYmVmQWhwNGgKb0hCaHAzc3hBM3ZmY2ZiZWZBaHA0aG9IQmhwM3N4QTN2 + ZmNmYmVmQWhwNGhvSEJocDNzeEEzdmZjZmJlZkFocAo0aG9IQmhwM3NhbWFkQTVxZWVlamZjZWhhZEEyYWdBMmVkVWduU2RiQTJhamJiQTFlblVnZVlmaWRjQTFMYmNB + CjFmY1pnb2doZGRBMkxoYkExZWVaaGROZGRBMkxiZUExZW5VZ2VZZmlkYkExYWpiYkExZmRnaVBTTWRkQTFMYmMKQTFmZGdpUFNNZGVBMUxiZUExZWVaaGROZGVBMkxY + QTFlZU1nbVBoamRiQTFhamJiQTFlblpoaWRnQTNMaGNBMQpmY01oZ2RiQTNMYmViYUFlblpoaWRmQTNMaGJBMWVvaGZmY01oZ2RiQTFhamJiYmFBZWVNZ21QaGpkY0Ex + TGJiCkExZWRVZ25TZGNBMkxiZUExM2VmZmJkYkE0TGJiQTFlZmZiZGNBNExiY0ExZmdlZFlkZkEzYWNiZUExM2ZnZWQKWWRnQTNhY2JiQTFmZEVkZEE0TGJiQTFmaWVu + WWRkQTNMaGNBNDkzc3dBN2VwaGRnZFpnbTFQTlVoY0EyMXN3QQo3WVpnbU5NaGNBMjVzd0E3ZW5VTlpVZ29BMjVzd0E3WWZpQTI5c0FhYkE5ZmlLRVVnZWhqTGZqS2Zk + aGJoZlBoCmNaZ2doakEyMzhzQWFiQTlmaUtlZGhmTlVnZzFMZmpLZmNNaGRVZ29QZ29nZE1BMjM1c0FhYkE5ZmlLRE4xUGcKZGdsTGZqS2ZlaGNNZ25VZ21VQTIzN3NB + YWJBOWZpS2VlTWdtUGhqTGZqS2VkZ2lVaGNoZmhkSmNwSmZjTWhnTQpoY2djQTIzMHN3QTdnZU1nZ1B5QTIzc3dBN2dlTWdnUHlBMjNzd0E3Z2VNZ2dQeUEyM3N3QTdn + ZU1nZ1B5QTIzCnN3QTdZTWhoSmZhVVpnb05oZEEyMXN3QTdnZU1nZ1B5QTIzc3dBN2dlTWdnUHlBMjNzd0E3Z2VNZ2dQeUEyM3MKd0E4TWdnUHlBMjNzd0E4TWdnUHlB + MjNzd0E4TWdnUHlBMjNzd0E4TWdnUHlBMjNzd0E3Z2VNZ2dQeUEyM3NBYQpiQThFQTI1NXNBYWJBOEVBMjU1PTcyMTQ4MQoAAA== + AEhTIEtsaWNreSBLbGF2ABAAAAA= + > + PRESETNAME "HS Klicky Klav" + FLOATPOS 32 247 1035 776 + FXID {54326BE8-AAAC-5648-9010-EF528A4DA255} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-227-cover-other-instances.rpp b/plugin-reaper-realearn/resources/test-projects/issue-227-cover-other-instances.rpp new file mode 100644 index 0000000..333832c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-227-cover-other-instances.rpp @@ -0,0 +1,498 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 2.17392233046799 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAbiQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoid1Y3MDA0bzciLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI1ODFkODA1MS1jN2Y0LTQwMzAtYTNmMi1lMjZhNTVjMmE5ODMiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjEyfSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4 + ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2Fk + OC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwi + c291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5 + MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29k + ZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5 + LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFt + ZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2 + ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIx + ZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsi + aWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQt + ZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00 + YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDll + YzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7Imlk + IjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMv + MSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + Ojh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoi + QnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0 + YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNv + dXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYw + NC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5j + b2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAt + OTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjox + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0 + IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJp + ZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNj + LTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1i + ODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoz + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7Imlk + IjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2 + ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7 + ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywi + Y2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmln + aHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {648BB3B7-BB63-499C-945F-B4B6D4FD9847} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAWSQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoid1Y3MDA0bzciLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI1ODFkODA1MS1jN2Y0LTQwMzAtYTNmMi1lMjZhNTVjMmE5ODMiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFt + ZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4 + MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1l + IjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0 + ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0x + OGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIg + Mi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQw + ODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6 + IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5 + Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4 + IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQi + OiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNo + YXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRl + ZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1i + ZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBh + LTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQt + NzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoz + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6 + eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoi + Yzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8z + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1l + IjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgx + ZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8y + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZl + OS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUi + OiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJk + OTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0 + ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0 + MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9 + fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + IjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1 + LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdo + dCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVt + YmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00 + NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {D05CC3E8-A919-4EE9-AF00-CF37949FDB58} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA+CMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoiU3hHMDh6QTciLCJsaXZlc09uVXBwZXJGbG9vciI6dHJ1ZSwiY29udHJvbERldmljZUlkIjoiMTAiLCJmZWVkYmFj + a0RldmljZUlkIjoiMTEiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6IjBjMDI5 + MzYzLTcxYzctNDExNS1iOGUwLTcyNDVhYzFiNmQ0YiIsIm5hbWUiOiJFbmNvZGVyIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3Rl + ciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9 + fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + IjowLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifX0seyJpZCI6IjI3NTIxZWYwLTVjZjgtNDVlOS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUi + OiJFbmNvZGVyIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgx + ZDMtZDYwNy00MzI5LTlkOTEtYTZiODg4YjBmYWFmIiwibmFtZSI6IkJ1dHRvbiAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIi + OjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4 + NyIsIm5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7Imlk + IjoiYjgyZDc3NTItZDIyOS00NDhmLWJkNDItYWY2MWI0NTllNDgxIiwibmFtZSI6IkJ1dHRvbiAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJj + aGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6ImEzYzI3YzAyLWM1MmQtNDcxYy1iNzA2LTI3 + YjYyZTJhNWRiZSIsIm5hbWUiOiJFbmNvZGVyIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQz + Ni05MTM0LTQ4OGE2ZmViMWZiZiIsIm5hbWUiOiJFbmNvZGVyIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MywiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjR9fSx7ImlkIjoiNTBmM2Q1NmQtMGY2My00MDg2LTk1OTItYjNjZTkxM2RiMzgxIiwibmFtZSI6IkJ1dHRvbiAyLzEiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2 + LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6 + MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiMDM5Zjc5OWItNTM3Zi00Mjc3LWFiYzEtMDNjNTA2ODI3MDgzIiwibmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6 + IjQ1YmJiZDVkLTM0ZjctNGFiOC1iNDk2LWVlYjljZjYwMmE3OCIsIm5hbWUiOiJFbmNvZGVyIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTctMjA1NTc2NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2 + fX0seyJpZCI6ImQyODQ5ZWM3LWM4OTUtNDRlZS1iZmZhLTM0ZWVmNTBmOTc3YyIsIm5hbWUiOiJFbmNvZGVyIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjcsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiODAxOGMyNTktNmY3Ni00MTMzLTkwNDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1 + dHRvbiAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo3fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiMTYwMTNjNDgtMmE0OS00NjcyLWJmM2EtOTVlYWUwNWVmM2E3Iiwi + bmFtZSI6IkJ1dHRvbiAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjY0MGViMmE0LTczNWQtNGQ5MS1hZTViLTYzNjcxZGZhYjVkNyIsIm5hbWUiOiJFbmNvZGVyIDMvMiIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0 + NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6ImM4NjVjNTAzLTI1ZmMtNGI4MS1iYmM4LTA3MzExMDVhMjY1NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzkt + OWI2NC1hOTA5ZTU1OTM1NGIiLCJuYW1lIjoiQnV0dG9uIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoi + RW5jb2RlciAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQx + MzEtYmVkOS00YmE3LWE2MDQtNDM1Y2YwNDM2YzkxIiwibmFtZSI6IkJ1dHRvbiAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVy + IjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiM2I1ZmMxZTgtNDJkYi00NTgxLTk0N2UtNWRmYTNkM2Mw + YmY0IiwibmFtZSI6IkVuY29kZXIgNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0s + eyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJmZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6 + MTIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6ImEzODQzNWMwLTNhYzQtNDE3NC1i + MWIxLTc4MWVmZjc5ODhjNCIsIm5hbWUiOiJFbmNvZGVyIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxM319LHsiaWQiOiI5MTQ1YjhhNy0wMjFlLTQ2ZTktYmMyNC01ODhiNWI3MGUzY2QiLCJuYW1lIjoiQnV0dG9uIDQvMiIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4 + ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMxMjU4NGEzIiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7 + ImlkIjoiNTVhYTIzYjAtYjgyMC00ZWJlLTliMTItOWZlNDA4NDJlZjI3IiwibmFtZSI6IkVuY29kZXIgNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + MTUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJhOWUzLTI4OGItNGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0 + b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjE1fX0seyJpZCI6IjI3YmYyMTUyLWU3ZjQtNDVlNS05Yjk4LWRjYWU4OTY1YjkxZiIsIm5hbWUiOiJMZWZ0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6Mywi + bnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNn19LHsiaWQiOiJlZTlkZjU5MC02MWNi + LTQ3NDYtYTMxMy03NzM1NmU1OGIzNWUiLCJuYW1lIjoiUmlnaHQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN319LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1l + IjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjE4fX0seyJpZCI6ImE1N2EyNzg3LTM5MTktNDZmYi04ZmRkLTE3ZmI4M2Y0YmFmMSIsIm5hbWUiOiJSaWdodCAzIiwic291cmNlIjp7ImNoYW5uZWwi + OjMsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE5fX1dLCJhY3RpdmVDb250cm9s + bGVySWQiOiJtaWRpLWZpZ2h0ZXItdHdpc3RlciIsIm1haW5QcmVzZXRBdXRvTG9hZE1vZGUiOiJmb2N1c2VkLWZ4IiwicGFyYW1ldGVycyI6eyI3Ijp7InZhbHVlIjow + LjI5MDAwMDAyfSwiMSI6eyJ2YWx1ZSI6MC40MTk5OTk5fSwiOSI6eyJ2YWx1ZSI6MC4zNjk5OTk5NX0sIjgiOnsidmFsdWUiOjMuNTAxNzcyOWUtOX19fQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {661379CC-F937-4096-840D-EFFD0DFEDEAE} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7zczMPQrXIzwAAAAAAAAAAIZsBT/pZ4g9Xo9CPkwKVz4wM7M/AACAP5iZGT8AAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 3133 1092 683 876 + FXID {D6D03B71-6068-4EE9-87DB-8E81AD41D27D} + WAK 0 0 + BYPASS 0 0 0 + "" + cnlMde5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA6AQAAAEAAAAAABAA + I3BnbT1pbml0aWFsaXplCiNBTT1Db2xvdXJDb3B5CiNWZXJzPTEwMDAwCiNFbmRpYW49bGl0dGxlCiNubT04CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwoj + bXM9Q3RybEEKI21zPUN0cmxCCiNtcz1FbnYKI21zPUxGTyBMCiNtcz1MRk8gUgojbnY9MAojY209UENvcmUKU0Jhc2U9NApTd2luZz0wLjAwClNUcmlnPTEKUFNvbmc9 + MApQRm9sZD0wClBGaWxlPTEKR0ZpbGU9MgpHU2NhbGU9MApDaExheT0wClN1cnJPPTEKUmV2PTc2NzUKTEVEPTAuMDAKUEFHRT0wClBhZ2VzT249MApDb3JlTj0zClNs + aWNlPTQKVUlfb3A9MzYKTWlkaUE9NQpNaWRpUD02CkRGb2xkPTcKQ3RybEE9MgpDdHJsQj0xMQojY209TEZPClJhdGU9NTAuMDAKU3RlcmVvPTI4LjgwCkZNb2Q9MC4w + MApXYXZlPTAKI2NtPURlbGF5ClRpbWVCPTgKRGx5UmF0ZT0wLjAwCk5vdGVDdHI9MApUYXBMPTEwMC4wMApUYXBSPTEwMC4wMApMRk9Ucmc9MApMRk9EcHQ9MC4wMApM + Rk9EeW49MC4wMApSZWdlbj01MC4wMApJbnZlcnQ9MApCcmlnaHQ9MC4wMApTYXQ9NTAuMDAKQ29sb3VyPTMuMDAKRmxvb3I9MQpEdWNrPTAKRHVja1RyZz0wCkR1Y2tU + aHI9MC4wMApSb3V0SU49MApSb3V0RkI9MApXaWR0aD0xMDAuMDAKTWl4PTUwLjAwCkluRz0wLjAwCk91dEc9MC4wMApQYW5pYz0wCkZyZWV6ZT0wCkJ5cGFzcz0wCkhR + PTAKI2NtPUxNZW0KTGF5TWVtMT0wCiNjbT1TdGlja2VyCnN0a3JUeHQ9OApiZ2lkeD05CnBvc3g9LTEuMDAKcG9zeT0tMS4wMAoKCgovLyBTZWN0aW9uIGZvciB1Z2x5 + IGNvbXByZXNzZWQgYmluYXJ5IERhdGEKLy8gRE9OJ1QgVE9VQ0ggVEhJUwoKJCQkJDEzMzMKP2dkZ2NlbmViOmFhYWJhYWFhOmVhYWFhYWFhOmdqZ29namhlOmdqZ2Jn + bWdqOmhrZ2Zjb2dpOmRjaGFhYWFhOgpoZGtrb2hhZDpjcGppYm9kYzpiaGJjbGdoaTphYTpiZjplYjplZDpuZDpvZzpwYzphZTphZzphaTphbDpiYTpiCmI6YmU6Y2E6 + Y2Y6Y2c6Y2k6Y2o6ZGY6ZHA6ZWE6ZWU6ZWk6ZWw6ZW4hZ2RnY1pDQWFiQTI2NWdkZ2NaQ1ZBMTAKZ2pnb2dqaGVnamdiZ21namhrZ2Zjb2dpZGNoYUE0OWdkZ2NaQ1ZB + NzRnZGdjWkNWQTEwaGRra29oYWRjcGppYgpvZGNiaGJjbGdoaW9lVGZwb2NrbW9rSWhha2lsY25lamFtbGpoUkVHUWpsSldCWVBvcERFZmxsYWhtaHBnbmliCmhsVWxq + R2dhU0xCZmdEQkZLbmpmZU5sbmlwRmdkZ2NaQ0hBMTBDWE9BZ2RnY1pDQWFiQTI2NWdkZ2NaQ2lhQTEKMzhnZGdjWkNBYWJBMjY1Z2RnY1pDTUEyN2dkZ2NaQ1ZBNzQ9 + MzgwODkKAAA= + AGluaXRpYWxpemUAEAAAAA== + > + PRESETNAME initialize + FLOATPOS 485 1003 2106 937 + FXID {22D267AA-A29B-4DFC-956A-680E3057F3AA} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-228-instance-fx-and-auto-load.RPP b/plugin-reaper-realearn/resources/test-projects/issue-228-instance-fx-and-auto-load.RPP new file mode 100644 index 0000000..b592ffc --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-228-instance-fx-and-auto-load.RPP @@ -0,0 +1,671 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 7 + ZOOM 100 7 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.36279289742106 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAC7SgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS45IiwiaWQiOiJINkY0VGZLeiIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJvNWVfWDdtS3FIV3g2eTRZVHJBTU4iLCJuYW1lIjoiUGluIGZpcnN0 + IGluIHNlbGVjdGVkIHRyYWNrIGFzIGluc3RhbmNlIEZYIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFj + dGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzQvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NTQsInRyYWNrR1VJRCI6InNl + bGVjdGVkIiwiZnhBbmNob3IiOiJpbmRleCIsInRyYWNrVG9vbEFjdGlvbiI6IlBpbkFzSW5zdGFuY2VUcmFjayIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsiYXV0byJdfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiV2xKWDZEaXlQ + U1NoakxSU0hTVzZzIiwibmFtZSI6IlNldCBmaXJzdCBpbiBzZWxlY3RlZCB0cmFjayBhcyBpbnN0YW5jZSBGWCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3c0L3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFy + Z2V0Ijp7InR5cGUiOjU0LCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaW5kZXgiLCJ0cmFja1Rvb2xBY3Rpb24iOiJQaW5Bc0luc3RhbmNlVHJhY2si + LCJmeFRvb2xBY3Rpb24iOiJTZXRBc0luc3RhbmNlRngiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowLCJ0YWdzIjpbImF1dG8iXX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiYmFuay1sZWZ0Iiwic291cmNl + Ijp7ImNoYW5uZWwiOjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjdXJzb3ItbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaC1sZWZ0Iiwic291cmNl + Ijp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRi + YWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImJhbmstcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNvbDEvcm93MS9wYWQi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRU + b0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInBsYXlpbmciOjQzLCJl + bXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInJlY29yZGluZyI6ODYsInN0b3BwZWQiOjEs + InBhdXNlZCI6MX19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cxL2tub2IiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93Mi9wYWQiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFs + dWUiOnsic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVzZWQiOjEsImVtcHR5 + IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic3RvcHBlZCI6MSwicGxheWluZyI6NDMsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NH19fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sMS9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cyL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + Ijo0LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sMS9yb3cyL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93My9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + Ijo4LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVk + X2Zvcl9yZWNvcmRfc3RvcCI6NjQsInN0b3BwZWQiOjEsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicGxheWluZyI6NDMsInNjaGVkdWxl + ZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJlbXB0eSI6MCwicmVjb3JkaW5nIjo4Nn19fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3czL3Bh + ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIi + OjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3czL2tu + b2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVy + IjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjQzLCJlbXB0eSI6MCwi + cmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRf + Zm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJwYXVzZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93NC9wYWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzQva25vYiIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsi + ZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVk + X2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNj + aGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwicGF1c2VkIjoxLCJzdG9wcGVkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJjb2wyL3JvdzEva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY29sMi9yb3cyL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRh + YmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJyZWNvcmRpbmci + Ojg2LCJlbXB0eSI6MCwicGF1c2VkIjoxLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2 + NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3Jv + dzIva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9y + b3czL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJG + cm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6 + NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInN0b3BwZWQiOjEsInJlY29yZGluZyI6ODYsInBsYXlpbmciOjQzLCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zv + cl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzMva25vYiIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzMva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3c0L3BhZCIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2Ny + ZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwicmVjb3JkaW5n + Ijo4NiwiZW1wdHkiOjAsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBhdXNl + ZCI6MX19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY29sMi9yb3c0L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3c0L2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNvbDIvcm93NC9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzEvcGFkIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6MiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7 + InN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4 + NiwicGxheWluZyI6NDMsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjR9fX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDMvcm93MS9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93MS9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6Miwi + Y2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDMvcm93MS9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzIvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6Niwi + Y2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3Jf + cGxheV9zdG9wIjo2NCwic3RvcHBlZCI6MSwicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJyZWNvcmRpbmciOjg2LCJlbXB0eSI6MCwic2No + ZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwicGxheWluZyI6NDN9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9wYWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93Mi9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9rbm9iIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzMvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0s + Im1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2 + NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInBsYXlpbmciOjQzLCJyZWNvcmRp + bmciOjg2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJlbXB0eSI6MCwicGF1c2VkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3czL2tub2IiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVk + YmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjQzLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3Jl + Y29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicGF1c2VkIjox + LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInJlY29yZGluZyI6ODZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImNvbDMvcm93NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzQva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiY29sNC9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxl + Ijp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzdG9wcGVkIjoxLCJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2No + ZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVk + X2Zvcl9yZWNvcmRfc3RvcCI6NjQsImVtcHR5IjowfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzEv + a25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cy + L3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9t + VGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzY2hlZHVs + ZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInN0b3BwZWQiOjEsInJlY29yZGluZyI6ODYsInBsYXlpbmciOjQzLCJwYXVz + ZWQiOjEsImVtcHR5IjowfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzIva25vYiIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjcsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3czL3BhZCIsInNvdXJjZSI6 + eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRl + IiwidmFsdWUiOnsicGxheWluZyI6NDMsInBhdXNlZCI6MSwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3Jk + X3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4Niwic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2 + NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY29sNC9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNvbDQvcm93My9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEs + Im51bWJlciI6MTUsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJz + dG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInBsYXlpbmciOjQzLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2 + NCwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0 + L3JvdzQvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJj + aGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29s + NC9yb3c0L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX1dLCJjb250cm9sbGVyQ3VzdG9tRGF0YSI6eyJjb21wYW5pb24iOnsiY29udHJvbHMiOlt7ImhlaWdo + dCI6NTAsImlkIjoiY29sNC9yb3cxL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + ImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93MS9rbm9iIiwi + Y29sNC9yb3cxL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMS9yb3cyL2tub2IiLCJs + YWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDEvcm93Mi9rbm9iIiwiY29sMS9yb3cyL3BhZCJdLCJzaGFwZSI6ImNpcmNs + ZSIsIndpZHRoIjo1MCwieCI6MTAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2w0L3JvdzIva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibWFwcGluZ3MiOlsiY29sNC9yb3cyL2tub2IiLCJjb2w0L3JvdzIvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4Ijo0MDAsInkiOjEw + MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDEvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wxL3Jv + dzEva25vYiIsImNvbDEvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjEwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93 + MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6 + MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wzL3JvdzEva25vYiIsImNvbDMvcm93MS9wYWQiXSwic2hh + cGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzEva25vYiIsImNvbDIvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIw + MCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93Mi9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJj + b2wyL3JvdzIva25vYiIsImNvbDIvcm93Mi9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MTAwfSx7ImhlaWdodCI6NTAsImlkIjoi + Y29sMy9yb3cyL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvcm93Mi9rbm9iIiwiY29sMy9yb3cyL3Bh + ZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wyL3JvdzMva25vYiIsImxhYmVsT25lIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVy + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3czL2tub2IiLCJjb2wyL3JvdzMvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgi + OjUwLCJ4IjoyMDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVU + b3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJt + YXBwaW5ncyI6WyJjb2wzL3JvdzMva25vYiIsImNvbDMvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MjAwfSx7ImhlaWdo + dCI6NTAsImlkIjoiY29sNC9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + ImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93NC9rbm9iIiwi + Y29sNC9yb3c0L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wxL3JvdzQva25vYiIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3c0L2tub2IiLCJjb2wxL3JvdzQvcGFkIl0sInNoYXBlIjoiY2ly + Y2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjMwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93NC9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzQva25vYiIsImNvbDIvcm93NC9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6 + MzAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMy9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMv + cm93NC9rbm9iIiwiY29sMy9yb3c0L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wx + L3JvdzMva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3czL2tub2IiLCJjb2wxL3JvdzMvcGFkIl0s + InNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDQvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJz + aXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w0L3JvdzMva25vYiIsImNvbDQvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAs + IngiOjQwMCwieSI6MjAwfSx7ImhlaWdodCI6NTAsImlkIjoiYmFuay1yaWdodCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6 + ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBw + aW5ncyI6WyJiYW5rLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjUwfSx7ImhlaWdodCI6NTAsImlkIjoiYmFuay1sZWZ0 + IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9z + aXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImJhbmstbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRo + Ijo1MCwieCI6MCwieSI6NTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJhNzhiMjc3ZS1jZmJmLTRiMmItOWNjNi0xYTU1MGFlYjg3ZmQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6 + MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gtbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6MCwieSI6MjUwfSx7Imhl + aWdodCI6NTAsImlkIjoiZTMxMmQyYTItZWNmMS00MTg5LTk1YWYtNDE3NGM0M2E3NTBjIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3Ai + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + Im1hcHBpbmdzIjpbImNoLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjI1MH0seyJoZWlnaHQiOjUwLCJpZCI6ImN1cnNv + ci1sZWZ0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6 + MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImN1cnNvci1sZWZ0Il0sInNoYXBlIjoicmVjdGFuZ2xl + Iiwid2lkdGgiOjUwLCJ4IjowLCJ5IjoxNTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjdXJzb3ItcmlnaHQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJh + Ym92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibWFwcGluZ3MiOlsiY3Vyc29yLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjE1MH1dLCJncmlkRGl2aXNp + b25Db3VudCI6MiwiZ3JpZFNpemUiOjUwfX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkJ5SWQiLCJjaGFpbiI6eyJhZGRyZXNzIjoiVHJhY2siLCJ0cmFjayI6eyJh + ZGRyZXNzIjoiQnlJZCIsImlkIjoiOUFEODk5MEQtM0FDMi0zQzQyLThCNzgtRUFGMTc3NTc3NzEyIn0sImNoYWluIjoiTm9ybWFsIn0sImlkIjoiQ0VERjczNjMtQzU5 + OC0xMDRDLThBNTktQkJDRUJFQTA3OTg2In19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 459 102 752 675 + FXID {B3D4A497-3763-C641-913F-734DCA30F728} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABuVwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS45IiwiaWQiOiJsclJXZENVQiIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJoNWlMVWd5RzJ3ZXlMZmJ1cGRDaXAiLCJuYW1lIjoiMSIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjb2wxL3JvdzEva25vYiJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoiaW5zdGFuY2UiLCJwYXJhbUluZGV4Ijo1 + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IllEZmY5NkxxVkNhWVcxWXdQNDg5 + TyIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93MS9rbm9iIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0 + YW5jZSIsInBhcmFtSW5kZXgiOjExLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6 + IndCZFhmdUtnYlZqa1VGN2IydDNzdiIsIm5hbWUiOiIzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93MS9rbm9iIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJn + ZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7 + ImlkIjoiSWFDN0w1aTA0emxnUjBIZ25aZEpwIiwibmFtZSI6IjQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3cxL2tub2IifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0s + InRhcmdldCI6eyJmeEFuY2hvciI6Imluc3RhbmNlIiwicGFyYW1JbmRleCI6NiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJMT2VDX2ZjN1NtSnY4M0JmYjJZUFQiLCJuYW1lIjoiNSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzIva25vYiJ9LCJtb2RlIjp7 + Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoiaW5zdGFuY2UiLCJwYXJhbUluZGV4Ijo5LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6InpPMEtEeGZ4VkFIU1BydUhEaWJsUCIsIm5hbWUiOiI2Iiwic291cmNlIjp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIv + cm93Mi9rbm9iIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInBhcmFtSW5kZXgiOjEsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoibjdzZkRURHd3dFhKRUdsV0VYbTRuIiwibmFtZSI6 + IjciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY29sMy9yb3cyL2tub2IifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJmeEFuY2hvciI6Imluc3RhbmNlIiwicGFy + YW1JbmRleCI6MTMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiczRhaXpiZGpX + MWlhNnEwRUtYUk9IIiwibmFtZSI6IjgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3cyL2tub2IifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJmeEFu + Y2hvciI6Imluc3RhbmNlIiwicGFyYW1JbmRleCI6MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MH19LHsiaWQiOiJYWS1NQ2pBTERSUDBiRlI3OHJKc08iLCJuYW1lIjoiOSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzMva25vYiJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1fSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoiaW5zdGFuY2UiLCJwYXJhbUluZGV4IjoxMCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJlbGpGcm5QQVMyQVk4akpuOTZCN3EiLCJuYW1lIjoiMTAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3czL2tub2IifSwi + bW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJmeEFuY2hvciI6Imluc3RhbmNlIiwicGFyYW1JbmRleCI6MywidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI0M1ZmaDV0TExiaGJWcjhYdm81Yy0iLCJuYW1lIjoiMTEiLCJzb3VyY2Ui + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sMy9yb3czL2tub2IifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJmeEFuY2hvciI6Imluc3RhbmNlIiwicGFyYW1JbmRleCI6NCwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJsSzM5MGp2TXNYcG5lYWZDbTJBXzci + LCJuYW1lIjoiMTIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3czL2tub2IifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJmeEFuY2hvciI6Imluc3Rh + bmNlIiwicGFyYW1JbmRleCI6NywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI3 + R0ZfS2xDX3Y5M0tvTWZtRENqSEkiLCJuYW1lIjoiMTMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3c0L2tub2IifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdl + dCI6eyJmeEFuY2hvciI6Imluc3RhbmNlIiwicGFyYW1JbmRleCI6OCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MH19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6ImJhbmstbGVmdCIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3Rl + ciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY3Vyc29y + LWxlZnQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gtbGVmdCIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0 + ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiYW5rLXJp + Z2h0Iiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImN1cnNvci1yaWdodCIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEyLCJjaGFy + YWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoLXJpZ2h0Iiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjb2wxL3JvdzEvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MCwi + Y2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBhdXNlZCI6MSwic2No + ZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwicGxheWluZyI6NDMsInNjaGVkdWxl + ZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwiZW1wdHkiOjAsInN0b3BwZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93MS9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9rbm9iIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wxL3JvdzIvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxfSwi + bW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQs + InN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0 + YXJ0Ijo2NCwicGF1c2VkIjoxLCJlbXB0eSI6MCwicGxheWluZyI6NDMsInJlY29yZGluZyI6ODZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93Mi9wYWQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6ImNvbDEvcm93Mi9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93Mi9rbm9iIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiJjb2wxL3JvdzMvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFj + a1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InJlY29yZGluZyI6ODYsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29y + ZF9zdGFydCI6NjQsInN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJwbGF5aW5nIjo0 + Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwYXVzZWQiOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93My9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + ImNvbDEvcm93My9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93My9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJjb2wxL3JvdzQvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7 + ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcmVj + b3JkX3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4NiwicGxheWluZyI6NDMsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBhdXNlZCI6MSwic3RvcHBlZCI6MSwi + c2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzQvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wxL3JvdzQva25v + YiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3c0L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93MS9w + YWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRl + eHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0 + LCJlbXB0eSI6MCwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwbGF5aW5nIjo0Mywic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9w + bGF5X3N0YXJ0Ijo2NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3cxL2tub2IiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3cxL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93Mi9wYWQiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwi + dmFsdWUiOnsiZW1wdHkiOjAsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0 + Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4Niwic3RvcHBlZCI6MX19 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY29sMi9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3cyL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjo1LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY29sMi9yb3cyL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93My9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjo5LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1 + bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJwbGF5aW5nIjo0 + Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4NiwiZW1wdHkiOjAsInBhdXNlZCI6MSwic3RvcHBlZCI6MX19fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3cz + L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFyYWN0 + ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3cz + L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFj + dGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3Jk + X3N0YXJ0Ijo2NCwic3RvcHBlZCI6MSwicmVjb3JkaW5nIjo4NiwiZW1wdHkiOjAsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVz + ZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjR9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93NC9wYWQiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6M30sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzQva25vYiIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMy9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MX0sIm1vZGUi + OnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwicmVj + b3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJz + Y2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVzZWQiOjEsInBsYXlpbmciOjQzfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjb2wzL3JvdzEva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY29sMy9yb3cyL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1 + ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwYXVzZWQiOjEsInN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6 + NjQsInJlY29yZGluZyI6ODYsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwbGF5aW5n + Ijo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wz + L3JvdzIva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29s + My9yb3czL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5k + IjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsicGF1c2VkIjoxLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJlbXB0eSI6MCwi + c2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9yZWNvcmRf + c3RhcnQiOjY0LCJwbGF5aW5nIjo0M319fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMy9yb3czL2tub2IiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93My9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzQvcGFkIiwi + c291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRv + RGlzY3JldGUiLCJ2YWx1ZSI6eyJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1 + bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJlbXB0eSI6MCwicGxheWluZyI6NDMsInN0b3BwZWQiOjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJyZWNv + cmRpbmciOjg2fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzQvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzQva25vYiIsInNvdXJjZSI6eyJjaGFu + bmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3c0L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvcm93MS9wYWQiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFs + dWUiOnsic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInJlY29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBs + YXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInBhdXNlZCI6MSwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NH19fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sNC9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cxL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + IjozLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sNC9yb3cxL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvcm93Mi9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + Ijo3LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVk + X2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicGF1c2VkIjoxLCJyZWNvcmRpbmciOjg2LCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVk + dWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJlbXB0eSI6MCwic3RvcHBlZCI6MX19fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3cyL3Bh + ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cyL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3LCJjaGFyYWN0ZXIi + OjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3cyL2tu + b2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvcm93My9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVy + IjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0 + b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQsInBsYXlpbmciOjQzLCJzdG9wcGVkIjoxLCJlbXB0 + eSI6MCwicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInJlY29yZGluZyI6ODZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93My9wYWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvcm93My9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzMva25vYiIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3c0L3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7 + ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsicGxheWluZyI6NDMsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zv + cl9wbGF5X3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9z + dGFydCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJlbXB0eSI6MH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3c0L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY29sNC9yb3c0L2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93NC9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19XSwiY29udHJvbGxlckN1c3RvbURhdGEiOnsiY29tcGFuaW9uIjp7ImNvbnRyb2xzIjpbeyJoZWlnaHQiOjUwLCJpZCI6ImNvbDQvcm93MS9rbm9iIiwibGFiZWxP + bmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJj + ZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w0L3JvdzEva25vYiIsImNvbDQvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3 + aWR0aCI6NTAsIngiOjQwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDEvcm93Mi9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJv + dmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJtYXBwaW5ncyI6WyJjb2wxL3JvdzIva25vYiIsImNvbDEvcm93Mi9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjEwMCwieSI6MTAwfSx7Imhl + aWdodCI6NTAsImlkIjoiY29sNC9yb3cyL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93Mi9rbm9i + IiwiY29sNC9yb3cyL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wxL3JvdzEva25v + YiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3cxL2tub2IiLCJjb2wxL3JvdzEvcGFkIl0sInNoYXBlIjoi + Y2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wzL3JvdzEva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3cxL2tub2IiLCJjb2wzL3JvdzEvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjozMDAsInki + OjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wyL3JvdzEva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9y + b3cxL2tub2IiLCJjb2wyL3JvdzEvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoyMDAsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wyL3Jv + dzIva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3cyL2tub2IiLCJjb2wyL3JvdzIvcGFkIl0sInNo + YXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoyMDAsInkiOjEwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93Mi9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXpl + Q29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wzL3JvdzIva25vYiIsImNvbDMvcm93Mi9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngi + OjMwMCwieSI6MTAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMi9yb3czL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNp + emVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdz + IjpbImNvbDIvcm93My9rbm9iIiwiY29sMi9yb3czL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MjAwLCJ5IjoyMDB9LHsiaGVpZ2h0Ijo1MCwi + aWQiOiJjb2wzL3JvdzMva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxU + d28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3czL2tub2IiLCJjb2wzL3Jv + dzMvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjozMDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDQvcm93NC9rbm9iIiwibGFiZWxP + bmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJj + ZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w0L3JvdzQva25vYiIsImNvbDQvcm93NC9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3 + aWR0aCI6NTAsIngiOjQwMCwieSI6MzAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMS9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJh + Ym92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sIm1hcHBpbmdzIjpbImNvbDEvcm93NC9rbm9iIiwiY29sMS9yb3c0L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MTAwLCJ5IjozMDB9LHsi + aGVpZ2h0Ijo1MCwiaWQiOiJjb2wyL3JvdzQva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3c0L2tu + b2IiLCJjb2wyL3JvdzQvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoyMDAsInkiOjMwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93NC9r + bm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wzL3JvdzQva25vYiIsImNvbDMvcm93NC9wYWQiXSwic2hhcGUi + OiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MzAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMS9yb3czL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDEvcm93My9rbm9iIiwiY29sMS9yb3czL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MTAw + LCJ5IjoyMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2w0L3JvdzMva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNv + bnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsi + Y29sNC9yb3czL2tub2IiLCJjb2w0L3JvdzMvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4Ijo0MDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6 + ImJhbmstcmlnaHQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiYmFuay1yaWdodCJdLCJzaGFwZSI6InJlY3Rh + bmdsZSIsIndpZHRoIjo1MCwieCI6NTAwLCJ5Ijo1MH0seyJoZWlnaHQiOjUwLCJpZCI6ImJhbmstbGVmdCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiYW5rLWxlZnQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6NTAsIngiOjAsInkiOjUwfSx7ImhlaWdodCI6NTAsImlk + IjoiYTc4YjI3N2UtY2ZiZi00YjJiLTljYzYtMWE1NTBhZWI4N2ZkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpb + ImNoLWxlZnQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6NTAsIngiOjAsInkiOjI1MH0seyJoZWlnaHQiOjUwLCJpZCI6ImUzMTJkMmEyLWVjZjEtNDE4OS05 + NWFmLTQxNzRjNDNhNzUwYyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28i + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaC1yaWdodCJdLCJzaGFwZSI6InJl + Y3RhbmdsZSIsIndpZHRoIjo1MCwieCI6NTAwLCJ5IjoyNTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjdXJzb3ItbGVmdCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjdXJzb3ItbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6MCwieSI6MTUwfSx7ImhlaWdo + dCI6NTAsImlkIjoiY3Vyc29yLXJpZ2h0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImN1cnNvci1yaWdodCJd + LCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6NTAwLCJ5IjoxNTB9XSwiZ3JpZERpdmlzaW9uQ291bnQiOjIsImdyaWRTaXplIjo1MH19LCJhY3RpdmVN + YWluUHJlc2V0SWQiOiJyZWFzeW50aCIsIm1haW5QcmVzZXRBdXRvTG9hZE1vZGUiOiJmb2N1c2VkLWZ4IiwidGFncyI6WyJhdXRvIl0sImluc3RhbmNlUHJlc2V0TGlu + a0NvbmZpZyI6eyJsaW5rcyI6W3siZngiOnsibmFtZSI6IlZTVGk6IFJlYVN5bnRoIChDb2Nrb3MpIn0sInByZXNldElkIjoicmVhc3ludGgifSx7ImZ4Ijp7Im5hbWUi + OiJWU1Q6IFJlYURlbGF5IChDb2Nrb3MpIn0sInByZXNldElkIjoicmVhZGVsYXkifV19LCJpbnN0YW5jZVRyYWNrIjp7ImFkZHJlc3MiOiJCeUlkIiwiaWQiOiJCRDUx + RjVFOC0yRDcyLTU3NEEtQjlGNy05NDlCM0U2RTM3NkUifSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImZ4X211c3RfaGF2ZV9mb2N1cyI6ZmFsc2Us + ImNoYWluIjp7ImFkZHJlc3MiOiJUcmFjayIsInRyYWNrIjp7ImFkZHJlc3MiOiJTZWxlY3RlZCIsImFsbG93X211bHRpcGxlIjpmYWxzZX0sImNoYWluIjoiTm9ybWFs + In0sImluZGV4IjowfX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 415 119 752 675 + FXID {EDBFF6B4-4379-6E41-AAE0-F50E005517AE} + PARM_TCP 0:_Main_p1__Param_1 1:_Main_p2__Param_2 + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6Lx+NA7Q0+PAAAAAAAAAAAj8L1PfYqwz7pZ4g9AAAAAAAAAD8AAIA/pHB9P1yPAj8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 754 186 372 469 + FXID {CEDF7363-C598-104C-8A59-BBCEBEA07986} + PARM_TCP 0:_Attack + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd4CAMA/F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 762 280 372 469 + FXID {50D55363-824E-3648-8401-893D8284842E} + WAK 0 0 + > + > + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAWo9CP0AK1z4AAAAAAACAPzUU6z4YZ3g/CtejPDxuPzwK1yM8ILCyPpyZmT5ACtc/ikEgPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 75 115 574 394 + FXID {B52EA6B0-18BE-5241-919E-E1AE6419C4CC} + PARM_TCP 0:_Wet + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-249-multi-instance-feedback-takeover.rpp b/plugin-reaper-realearn/resources/test-projects/issue-249-multi-instance-feedback-takeover.rpp new file mode 100644 index 0000000..555e747 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-249-multi-instance-feedback-takeover.rpp @@ -0,0 +1,245 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAzwEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoiNlNaM3poRUIiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJjMTllYTM5OS0wOTRlLTRlYjEtYmQyOS00MTBhOTc3NTIyYjAiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOnRydWUsImZ4QW5jaG9yIjoiaWQiLCJmeEluZGV4Ijox + LCJmeEdVSUQiOiI3Njg4QzE5MS00OTU4LTQxRUUtQTFDNS01RkE1ODZERDgzMDEifSwicHJldmVudEVjaG9GZWVkYmFjayI6dHJ1ZX1dfQ== + AAAQAAAA + > + FLOAT 975 504 1436 1439 + FXID {936610A8-0BE2-467A-90B7-E9646D5022A8} + WAK 0 0 + BYPASS 0 0 0 + + FLOATPOS 0 0 0 0 + FXID {7688C191-4958-41EE-A1C5-5FA586DD8301} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAzwEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoiNlNaM3poRUIiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJjMTllYTM5OS0wOTRlLTRlYjEtYmQyOS00MTBhOTc3NTIyYjAiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOnRydWUsImZ4QW5jaG9yIjoiaWQiLCJmeEluZGV4Ijox + LCJmeEdVSUQiOiJCNDQ1OURFOC0zNEQ5LTQwNTMtQUQzMy04NUQ2QzkzOTNCM0QifSwicHJldmVudEVjaG9GZWVkYmFjayI6dHJ1ZX1dfQ== + AAAQAAAA + > + FLOAT 2372 500 1436 1439 + FXID {01B6F0E0-8651-45CC-9C7C-DFE0DF15EB2C} + WAK 0 0 + BYPASS 0 0 0 + + FLOATPOS 0 0 0 0 + FXID {B4459DE8-34D9-4053-AD33-85D6C9393B3D} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAzwEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoiNlNaM3poRUIiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJjMTllYTM5OS0wOTRlLTRlYjEtYmQyOS00MTBhOTc3NTIyYjAiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOnRydWUsImZ4QW5jaG9yIjoiaWQiLCJmeEluZGV4Ijox + LCJmeEdVSUQiOiIxQzY0N0VDMy0yM0IwLTQ2M0YtOEM2MS1GMkQ1ODU3RDkxQTAifSwicHJldmVudEVjaG9GZWVkYmFjayI6dHJ1ZX1dfQ== + AAAQAAAA + > + FLOAT 3563 474 1436 1439 + FXID {C7711D03-6163-4823-B04E-9F392FEE6677} + WAK 0 0 + BYPASS 0 0 0 + + FLOATPOS 0 0 0 0 + FXID {1C647EC3-23B0-463F-8C61-F2D5857D91A0} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-263-radio-button-selection.rpp b/plugin-reaper-realearn/resources/test-projects/issue-263-radio-button-selection.rpp new file mode 100644 index 0000000..5ce0550 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-263-radio-button-selection.rpp @@ -0,0 +1,143 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAXQUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoieVZ5bkNQR2MiLCJjb250cm9sRGV2aWNlSWQiOiIzIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjQiLCJkZWZhdWx0R3Jv + dXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiZTM0Y2UwMmUtMDcyZS00MzFmLTg0YTEtMTZkZWYyZTE1YWRlIiwibmFt + ZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowfSwibW9kZSI6eyJtYXhUYXJnZXRWYWx1ZSI6MC4wLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4ifSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI0OGE0ZGI0MC1mOWViLTQyMzItYjg4Yy01NDQwYmFlNGUyNTIiLCJu + YW1lIjoiMiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjAxLCJtYXhUYXJnZXRWYWx1ZSI6MC4wMSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIn0sInRhcmdl + dCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiYmJhOTQ5ZWItNTFkOS00 + OGVmLTg5MGMtZDUxNGYwM2FmZTM4IiwibmFtZSI6IjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2NiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC4wMiwibWF4VGFyZ2V0VmFsdWUiOjAuMDIsIm91dE9mUmFu + Z2VCZWhhdmlvciI6Im1pbiJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + fX0seyJpZCI6ImM4MDA2MzQwLTVjMzItNGVjMy04NjU5LTJkYjc4NTIwMzRiNyIsIm5hbWUiOiIzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6NjcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuMDMsIm1heFRh + cmdldFZhbHVlIjowLjAzLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4ifSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19XX0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {CEFC7994-A97F-4E65-87EF-9A45F907E7C6} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-265-fx-chain-chunk-not-found.rpp b/plugin-reaper-realearn/resources/test-projects/issue-265-fx-chain-chunk-not-found.rpp new file mode 100644 index 0000000..512bc6f --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-265-fx-chain-chunk-not-found.rpp @@ -0,0 +1,169 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAmwAAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNiIsImlkIjoiM3hYWE91dWwiLCJzZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sIm1haW5QcmVzZXRBdXRvTG9hZE1vZGUiOiJmb2N1c2VkLWZ4In0= + AAAQAAAA + > + FLOATPOS 1480 295 1436 1439 + FXID {83F4E067-C619-44F6-A8F0-822B5D38184D} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 485 537 683 876 + FXID {1E445CD6-65E8-42C6-B79D-72FEDD53246C} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-27-out-of-range.rpp b/plugin-reaper-realearn/resources/test-projects/issue-27-out-of-range.rpp new file mode 100644 index 0000000..2bb3e60 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-27-out-of-range.rpp @@ -0,0 +1,177 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAeBIAAAEAAAAAABAA + eyJsZXRNYXRjaGVkRXZlbnRzVGhyb3VnaCI6ZmFsc2UsImxldFVubWF0Y2hlZEV2ZW50c1Rocm91Z2giOnRydWUsImFsd2F5c0F1dG9EZXRlY3RNb2RlIjp0cnVlLCJz + ZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiY29udHJvbERldmljZUlkIjoiMTAiLCJmZWVkYmFja0RldmljZUlkIjoiMTEiLCJtYXBwaW5ncyI6W3sibmFtZSI6 + IkJ1dHRvbiAxIiwic291cmNlIjp7InR5cGUiOjAsImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm1lc3NhZ2UiOjB9LCJtb2RlIjp7InR5cGUiOjIsIm1pblNvdXJjZVZhbHVlIjowLjAsIm1heFNvdXJjZVZhbHVlIjoxLjAsIm1pblRhcmdldFZhbHVl + IjowLjAsIm1heFRhcmdldFZhbHVlIjowLjAsIm1pblRhcmdldEp1bXAiOjAuMCwibWF4VGFyZ2V0SnVtcCI6MS4wLCJtaW5TdGVwU2l6ZSI6MC4wMSwibWF4U3RlcFNp + emUiOjAuMDEsIm1pblByZXNzTWlsbGlzIjowLCJtYXhQcmVzc01pbGxpcyI6MCwiZWVsQ29udHJvbFRyYW5zZm9ybWF0aW9uIjoiIiwiZWVsRmVlZGJhY2tUcmFuc2Zv + cm1hdGlvbiI6IiIsInJldmVyc2VJc0VuYWJsZWQiOmZhbHNlLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4iLCJyb3VuZFRhcmdldFZhbHVlIjpmYWxzZSwic2NhbGVN + b2RlRW5hYmxlZCI6ZmFsc2UsInJvdGF0ZUlzRW5hYmxlZCI6ZmFsc2V9LCJ0YXJnZXQiOnsidHlwZSI6MSwiY29tbWFuZE5hbWUiOm51bGwsImludm9jYXRpb25UeXBl + IjowLCJ0cmFja0dVSUQiOm51bGwsInRyYWNrTmFtZSI6bnVsbCwiZW5hYmxlT25seUlmVHJhY2tJc1NlbGVjdGVkIjpmYWxzZSwiZnhJbmRleCI6MCwiaXNJbnB1dEZ4 + IjpmYWxzZSwiZW5hYmxlT25seUlmRnhIYXNGb2N1cyI6ZmFsc2UsInNlbmRJbmRleCI6bnVsbCwicGFyYW1JbmRleCI6MCwic2VsZWN0RXhjbHVzaXZlbHkiOnRydWUs + InRyYW5zcG9ydEFjdGlvbiI6InBsYXlTdG9wIn0sImNvbnRyb2xJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrSXNFbmFibGVkIjp0cnVlLCJwcmV2ZW50RWNob0ZlZWRi + YWNrIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjpmYWxzZSwiYWN0aXZhdGlvblR5cGUiOiJhbHdheXMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsicGFy + YW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9LCJtb2RpZmllckNvbmRpdGlvbjIiOnsicGFyYW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9LCJwcm9ncmFtQ29uZGl0 + aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MH0sImVlbENvbmRpdGlvbiI6IiJ9LHsibmFtZSI6IkJ1dHRvbiAyIiwic291cmNlIjp7InR5cGUiOjAs + ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm1lc3NhZ2UiOjB9LCJtb2RlIjp7 + InR5cGUiOjAsIm1pblNvdXJjZVZhbHVlIjowLjAsIm1heFNvdXJjZVZhbHVlIjoxLjAsIm1pblRhcmdldFZhbHVlIjowLjAxLCJtYXhUYXJnZXRWYWx1ZSI6MC4wMSwi + bWluVGFyZ2V0SnVtcCI6MC4wLCJtYXhUYXJnZXRKdW1wIjoxLjAsIm1pblN0ZXBTaXplIjowLjAxLCJtYXhTdGVwU2l6ZSI6MC4wMSwibWluUHJlc3NNaWxsaXMiOjAs + Im1heFByZXNzTWlsbGlzIjowLCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiIiLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiIiwicmV2ZXJzZUlzRW5hYmxl + ZCI6ZmFsc2UsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsInJvdW5kVGFyZ2V0VmFsdWUiOmZhbHNlLCJzY2FsZU1vZGVFbmFibGVkIjpmYWxzZSwicm90YXRlSXNF + bmFibGVkIjpmYWxzZX0sInRhcmdldCI6eyJ0eXBlIjoxLCJjb21tYW5kTmFtZSI6bnVsbCwiaW52b2NhdGlvblR5cGUiOjAsInRyYWNrR1VJRCI6bnVsbCwidHJhY2tO + YW1lIjpudWxsLCJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOmZhbHNlLCJmeEluZGV4IjowLCJpc0lucHV0RngiOmZhbHNlLCJlbmFibGVPbmx5SWZGeEhhc0Zv + Y3VzIjpmYWxzZSwic2VuZEluZGV4IjpudWxsLCJwYXJhbUluZGV4IjowLCJzZWxlY3RFeGNsdXNpdmVseSI6dHJ1ZSwidHJhbnNwb3J0QWN0aW9uIjoicGxheVN0b3Ai + fSwiY29udHJvbElzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tJc0VuYWJsZWQiOnRydWUsInByZXZlbnRFY2hvRmVlZGJhY2siOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRl + ckNvbnRyb2wiOmZhbHNlLCJhY3RpdmF0aW9uVHlwZSI6ImFsd2F5cyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjpudWxsLCJpc09uIjpmYWxzZX0s + Im1vZGlmaWVyQ29uZGl0aW9uMiI6eyJwYXJhbUluZGV4IjpudWxsLCJpc09uIjpmYWxzZX0sInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3Jh + bUluZGV4IjowfSwiZWVsQ29uZGl0aW9uIjoiIn0seyJuYW1lIjoiQnV0dG9uIDMiLCJzb3VyY2UiOnsidHlwZSI6MCwiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hh + cmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwibWVzc2FnZSI6MH0sIm1vZGUiOnsidHlwZSI6MCwibWluU291cmNlVmFsdWUiOjAu + MCwibWF4U291cmNlVmFsdWUiOjEuMCwibWluVGFyZ2V0VmFsdWUiOjAuMDIsIm1heFRhcmdldFZhbHVlIjowLjAyLCJtaW5UYXJnZXRKdW1wIjowLjAsIm1heFRhcmdl + dEp1bXAiOjEuMCwibWluU3RlcFNpemUiOjAuMDEsIm1heFN0ZXBTaXplIjowLjAxLCJtaW5QcmVzc01pbGxpcyI6MCwibWF4UHJlc3NNaWxsaXMiOjAsImVlbENvbnRy + b2xUcmFuc2Zvcm1hdGlvbiI6IiIsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiIiLCJyZXZlcnNlSXNFbmFibGVkIjpmYWxzZSwib3V0T2ZSYW5nZUJlaGF2aW9y + IjoibWluIiwicm91bmRUYXJnZXRWYWx1ZSI6ZmFsc2UsInNjYWxlTW9kZUVuYWJsZWQiOmZhbHNlLCJyb3RhdGVJc0VuYWJsZWQiOmZhbHNlfSwidGFyZ2V0Ijp7InR5 + cGUiOjEsImNvbW1hbmROYW1lIjpudWxsLCJpbnZvY2F0aW9uVHlwZSI6MCwidHJhY2tHVUlEIjpudWxsLCJ0cmFja05hbWUiOm51bGwsImVuYWJsZU9ubHlJZlRyYWNr + SXNTZWxlY3RlZCI6ZmFsc2UsImZ4SW5kZXgiOjAsImlzSW5wdXRGeCI6ZmFsc2UsImVuYWJsZU9ubHlJZkZ4SGFzRm9jdXMiOmZhbHNlLCJzZW5kSW5kZXgiOm51bGws + InBhcmFtSW5kZXgiOjAsInNlbGVjdEV4Y2x1c2l2ZWx5Ijp0cnVlLCJ0cmFuc3BvcnRBY3Rpb24iOiJwbGF5U3RvcCJ9LCJjb250cm9sSXNFbmFibGVkIjp0cnVlLCJm + ZWVkYmFja0lzRW5hYmxlZCI6dHJ1ZSwicHJldmVudEVjaG9GZWVkYmFjayI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6ZmFsc2UsImFjdGl2YXRpb25U + eXBlIjoiYWx3YXlzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOm51bGwsImlzT24iOmZhbHNlfSwibW9kaWZpZXJDb25kaXRpb24yIjp7InBhcmFt + SW5kZXgiOm51bGwsImlzT24iOmZhbHNlfSwicHJvZ3JhbUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjB9LCJlZWxDb25kaXRpb24iOiIi + fSx7Im5hbWUiOiJFbmNvZGVyIiwic291cmNlIjp7InR5cGUiOjAsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm1lc3NhZ2UiOjB9LCJtb2RlIjp7InR5cGUiOjEsIm1pblNvdXJjZVZhbHVlIjowLjAsIm1heFNvdXJjZVZhbHVlIjoxLjAsIm1pblRh + cmdldFZhbHVlIjowLjMyLCJtYXhUYXJnZXRWYWx1ZSI6MC43MSwibWluVGFyZ2V0SnVtcCI6MC4wLCJtYXhUYXJnZXRKdW1wIjoxLjAsIm1pblN0ZXBTaXplIjowLjAx + LCJtYXhTdGVwU2l6ZSI6MC4wMSwibWluUHJlc3NNaWxsaXMiOjAsIm1heFByZXNzTWlsbGlzIjowLCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiIiLCJlZWxGZWVk + YmFja1RyYW5zZm9ybWF0aW9uIjoiIiwicmV2ZXJzZUlzRW5hYmxlZCI6ZmFsc2UsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsInJvdW5kVGFyZ2V0VmFsdWUiOmZh + bHNlLCJzY2FsZU1vZGVFbmFibGVkIjpmYWxzZSwicm90YXRlSXNFbmFibGVkIjpmYWxzZX0sInRhcmdldCI6eyJ0eXBlIjoyLCJjb21tYW5kTmFtZSI6bnVsbCwiaW52 + b2NhdGlvblR5cGUiOjAsInRyYWNrR1VJRCI6bnVsbCwidHJhY2tOYW1lIjpudWxsLCJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOmZhbHNlLCJmeEluZGV4Ijpu + dWxsLCJpc0lucHV0RngiOmZhbHNlLCJlbmFibGVPbmx5SWZGeEhhc0ZvY3VzIjpmYWxzZSwic2VuZEluZGV4IjpudWxsLCJwYXJhbUluZGV4IjowLCJzZWxlY3RFeGNs + dXNpdmVseSI6ZmFsc2UsInRyYW5zcG9ydEFjdGlvbiI6InBsYXlTdG9wIn0sImNvbnRyb2xJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrSXNFbmFibGVkIjp0cnVlLCJw + cmV2ZW50RWNob0ZlZWRiYWNrIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjpmYWxzZSwiYWN0aXZhdGlvblR5cGUiOiJhbHdheXMiLCJtb2RpZmllckNv + bmRpdGlvbjEiOnsicGFyYW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9LCJtb2RpZmllckNvbmRpdGlvbjIiOnsicGFyYW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9 + LCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MH0sImVlbENvbmRpdGlvbiI6IiJ9XSwicGFyYW1ldGVycyI6eyIwIjp7InZh + bHVlIjowLjAxLCJuYW1lIjpudWxsfX19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {0B44CC13-9685-41C7-9ABD-947C64C47423} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-270-double-press.rpp b/plugin-reaper-realearn/resources/test-projects/issue-270-double-press.rpp new file mode 100644 index 0000000..a92f7aa --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-270-double-press.rpp @@ -0,0 +1,140 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAQAQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlNyIsImlkIjoiMmJzN2tkWTAiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9s + bGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI1ZjE0ZjM3MC1mYzhkLTQ3ODMtYmRiYi05YWY0ZGE0ODNiN2UiLCJuYW1lIjoiU2luZ2xlIHByZXNzIiwic291 + cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhQcmVzc01pbGxpcyI6NDk5LCJmaXJlTW9kZSI6InNpbmdsZSJ9LCJ0YXJnZXQiOnsidHlwZSI6NywiZnhBbmNob3IiOiJpZCIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIwMjQ5MTNhZi1jMWRjLTRjOTYtODk0My0zZjFhYzExNzU3Mzci + LCJuYW1lIjoiRG91YmxlIHByZXNzIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhQcmVzc01pbGxpcyI6MjAwLCJmaXJlTW9kZSI6ImRvdWJsZSJ9LCJ0YXJnZXQi + OnsidHlwZSI6OCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIzNDAxNWE1ZC0z + ZDRhLTQxYjYtODQwNC02ZjlmNDZhMmUxYzAiLCJuYW1lIjoiTG9uZyBwcmVzcyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjEs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MiwibWluUHJlc3NNaWxsaXMiOjUwMCwibWF4 + UHJlc3NNaWxsaXMiOjEwMDAsImZpcmVNb2RlIjoidGltZW91dCJ9LCJ0YXJnZXQiOnsidHlwZSI6NSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19XX0= + AAAQAAAA + > + FLOAT 829 406 1436 1439 + FXID {35A7056C-A7B6-4DCC-B195-97FD1C14875B} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-278-button-encoder-usages.rpp b/plugin-reaper-realearn/resources/test-projects/issue-278-button-encoder-usages.rpp new file mode 100644 index 0000000..e62ff99 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-278-button-encoder-usages.rpp @@ -0,0 +1,136 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAzgIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlOSIsImlkIjoiY3hxVzd1azYiLCJzZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImU4ZjQ3MDk0LWFjZGEtNDU4OC1iZTQ5LWRmYWNlZGI4OTdlZSIsIm5hbWUiOiJQcmVzcyBvbmx5 + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5k + ZXgiOjB9LCJtb2RlIjp7ImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiZjljZGU0ZWMtYzg5Zi00Y2ZlLWEzNGItYjljNmZiOTdlOTFiIiwibmFtZSI6IkluY3JlbWVu + dCBvbmx5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjB9LCJtb2RlIjp7ImVuY29kZXJVc2FnZSI6ImluY3JlbWVudC1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX1dfQ== + AAAQAAAA + > + FLOAT 19 47 1436 1439 + FXID {713D17C4-DCA6-4CA3-A6AA-E5D4BDB6DAB4} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-279-dynamic-own-track-index.rpp b/plugin-reaper-realearn/resources/test-projects/issue-279-dynamic-own-track-index.rpp new file mode 100644 index 0000000..cb5902c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-279-dynamic-own-track-index.rpp @@ -0,0 +1,408 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAqgEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlOSIsImlkIjoicFZwYWxDbG0iLCJzZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImY0MDYyZGEyLWEyYzItNGQ4NS1hNmQ4LWZjYzhlMWMyZTBiMSIsIm5hbWUiOiIxIiwic291cmNl + Ijp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwi + bW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0V4cHJlc3Npb24iOiJ0aGlzX3RyYWNrX2luZGV4ICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {57222817-CB1D-41FF-A1A0-B915F65FA6A8} + WAK 0 0 + > + > + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-280-more-basic-targets.rpp b/plugin-reaper-realearn/resources/test-projects/issue-280-more-basic-targets.rpp new file mode 100644 index 0000000..7ebf4ec --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-280-more-basic-targets.rpp @@ -0,0 +1,269 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA3CoAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4yIiwiaWQiOiJvdVBRZkNaTyIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6Ijc0YTNhNjc4LTkwMWYtNGQ5Yi04OGFjLTY0MmM4MjJiNzI0NyIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsi + dHlwZSI6MzksImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJp + ZCI6ImE0ZmQ3NzgwLWI3MDAtNGExMS1hZjEzLTU4MDZiZmZkZDNjZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjEz + LCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NDAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQ + cm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjVhYzQzMjY3LWZiZjgtNGQ2Ni04OTkxLWIy + YWQxYzFjMjJkNSIsIm5hbWUiOiJTZW5kIHBoYXNlIChvdXRwdXQpIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjEsIm51bWJlciI6MTUs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6Mn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NDAsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQi + LCJyb3V0ZVNlbGVjdG9yVHlwZSI6ImlkIiwicm91dGVUeXBlIjoib3V0cHV0IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI3YzZmMjkxNy1mNGRlLTRmZGItODlmYS03YzRjMzY2YWM5YTUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + Y2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQx + LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI1NzEy + NGEwMS04ODQ3LTQxNTgtYjRlOC1iYzZlYzU0N2ViZjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFj + dGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjR9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQyLCJmeEFuY2hvciI6ImlkIiwicm91dGVTZWxlY3Rv + clR5cGUiOiJpZCIsInJvdXRlR3VpZCI6IjRDNDZDNjI1LUNBMEMtNDFFOS1BNEIzLUFCQjQzODk3QjY4QyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwidHJhY2tBdXRvbWF0aW9uTW9kZSI6MSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2 + My03MWM3LTQxMTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIi + OjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxfX0s + eyJpZCI6ImU1NTQzYWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6 + MCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVmMC01Y2Y4LTQ1ZTktOWM5MC00ZDQ5MDZjZjYzMDQiLCJuYW1lIjoi + RW5jb2RlciAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6ImRkMWE4MWQz + LWQ2MDctNDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFjdGVyIjox + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2NTMtOWZiYi1lZjkwN2RiMDlkODci + LCJuYW1lIjoiRW5jb2RlciAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6 + ImI4MmQ3NzUyLWQyMjktNDQ4Zi1iZDQyLWFmNjFiNDU5ZTQ4MSIsIm5hbWUiOiJCdXR0b24gMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MiwiY2hh + cmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMtYjcwNi0yN2I2 + MmUyYTVkYmUiLCJuYW1lIjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijoz + fX0seyJpZCI6IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJl + ciI6MywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiI4MjhkNDY1YS05YmQzLTQ0MzYt + OTEzNC00ODhhNmZlYjFmYmYiLCJuYW1lIjoiRW5jb2RlciAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo0fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI3MTkwZTJhNi1l + MDdjLTQ5MjItOWJiNC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjMs + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjAzOWY3OTliLTUzN2YtNDI3Ny1hYmMxLTAzYzUwNjgyNzA4MyIsIm5hbWUiOiJCdXR0b24gMi8yIiwic291cmNlIjp7 + ImNoYW5uZWwiOjEsIm51bWJlciI6NSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiI0 + NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2LCJjaGFy + YWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJCdXR0b24gMi8zIiwi + c291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19 + LHsiaWQiOiJkMjg0OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2MiLCJuYW1lIjoiRW5jb2RlciAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + Ijo3LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUiOiJCdXR0 + b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6N319LHsiaWQiOiIyNWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYiLCJuYW1lIjoiRW5jb2RlciAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjE2MDEzYzQ4LTJhNDktNDY3Mi1iZjNhLTk1ZWFlMDVlZjNhNyIsIm5h + bWUiOiJCdXR0b24gMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5jb2RlciAzLzIiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04Yjg5LWQ5NmI5NDUw + MDAzMiIsIm5hbWUiOiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJuYW1lIjoiRW5jb2RlciAzLzMiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdhOTMwNWQtOTRhYi00ZmM5LTli + NjQtYTkwOWU1NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVlZC00NjNiLTk3OWQtYjMwOTE2ZTNiYjNhIiwibmFtZSI6IkVu + Y29kZXIgMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjZiOGFkMTMx + LWJlZDktNGJhNy1hNjA0LTQzNWNmMDQzNmM5MSIsIm5hbWUiOiJCdXR0b24gMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTEsImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEzZDNjMGJm + NCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsi + aWQiOiIxYzA5MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEy + LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiJhMzg0MzVjMC0zYWM0LTQxNzQtYjFi + MS03ODFlZmY3OTg4YzQiLCJuYW1lIjoiRW5jb2RlciA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjozLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MTN9fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTQwNjAzOGUt + M2M0NC00MzJkLThlY2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6 + MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjIzMmQ5ODlhLWUwZDctNDdmOS05NTg3LTY2OGFjMTI1ODRhMyIsIm5hbWUiOiJCdXR0b24gNC8zIiwic291cmNl + Ijp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJp + ZCI6IjU1YWEyM2IwLWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1 + LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMwMDlkNGQiLCJuYW1lIjoiQnV0dG9u + IDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxNX19LHsiaWQiOiIyN2JmMjE1Mi1lN2Y0LTQ1ZTUtOWI5OC1kY2FlODk2NWI5MWYiLCJuYW1lIjoiTGVmdCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51 + bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5ZGY1OTAtNjFjYi00 + NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9fSx7ImlkIjoiYWIwNTQ4ODAtMWM0My00ZjRhLWI0MTgtYzNlYTRkMzFiNTEwIiwibmFtZSI6 + IkxlZnQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxOH19LHsiaWQiOiJhNTdhMjc4Ny0zOTE5LTQ2ZmItOGZkZC0xN2ZiODNmNGJhZjEiLCJuYW1lIjoiUmlnaHQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjoz + LCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOX19XSwiYWN0aXZlQ29udHJvbGxl + cklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIifQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {3A8684D4-410D-472B-86F0-355D3F65C0A0} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-283-other-project-tab.rpp b/plugin-reaper-realearn/resources/test-projects/issue-283-other-project-tab.rpp new file mode 100644 index 0000000..2efa283 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-283-other-project-tab.rpp @@ -0,0 +1,619 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 135.25999999999988 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + MARKER 1 85.16930356350743 "Organ check" 0 0 1 R {3C8284D4-7EFA-4994-A0BD-A73B56A49937} + MARKER 2 497.20267156772235 Speech 0 0 1 R {4B262F84-2EC3-48C7-AF9B-F5E02EAB1942} + MARKER 1 851.69303563507583 "Trombones 1" 1 0 1 R {D185ABBE-4CD5-4E12-ADE2-F73DE5D1DF05} + MARKER 1 965.25210705308291 "" 1 0 1 R + MARKER 2 993.64187490758627 Singing 1 0 1 R {EFD41653-89B9-4D06-B803-31B34CC3BC00} + MARKER 2 1192.370249889103 "" 1 0 1 R + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAHDEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlOSIsImlkIjoiS2Z5ckw5VE4iLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI2MDRlNGNlMC04N2VkLTQwZGYtOWJlMC1mN2M4MTRmNjkwYzYiLCJu + YW1lIjoiQXJtIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTMsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7 + InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tHVUlEIjoiNjIyRUMxMEQtMjQ2RC00OThFLUI3MkItOUNDMEFFMzBFRjE2IiwiZnhBbmNob3IiOiJpZCIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIzNDMzMWM1OC02ZWI3LTQ0ZDgtYmUyNC1mYzdlZGM0NmMyYTci + LCJuYW1lIjoiU3RlcmVvIHdpZHRoIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTMsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQi + OnsidHlwZSI6MTcsInRyYWNrR1VJRCI6IjYyMkVDMTBELTI0NkQtNDk4RS1CNzJCLTlDQzBBRTMwRUYxNiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiMGUxMjI5MWQtZmYwOC00M2FhLWFhYmUtOTE1NzQxNzM4ZDFjIiwibmFtZSI6IlNldCB0ZW1w + byIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjE0fSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoxMCwiY29tbWFuZE5hbWUiOiIzMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNzhmMjkwZDUtZTFhZS00ODg4LTliYjYtMTk4NTU2NmU5ODQzIiwibmFtZSI6IlNvbG8i + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6OCwiZnhBbmNob3IiOiJpZCIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI0ZGYwNzJhYi0wMTQ4LTRmY2UtYWFjMi1iYzYyNTFhZDcwNmYiLCJuYW1l + IjoiTWFya2VyIDEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyMiwiZnhBbmNob3Ii + OiJpZCIsImJvb2ttYXJrUmVmIjoxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiOGQwMmNhZjAtMDVjNi00 + ZDVjLTk5OWUtYzllMTg2MGE4ZmUwIiwibmFtZSI6Ik1hcmtlciAyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsidHlwZSI6MjIsImZ4QW5jaG9yIjoiaWQiLCJib29rbWFya1JlZiI6MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlfX0seyJpZCI6ImE4Mzk1MThiLTRmOWMtNDQ0Ni1hYzgyLTAyYjA2MjgzNGQxYyIsIm5hbWUiOiJSZWdpb24gMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjEwfSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyMiwiZnhBbmNob3IiOiJpZCIsImJvb2ttYXJrUmVmIjoxLCJib29rbWFya0lzUmVnaW9u + Ijp0cnVlLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiOTdhN2FhODgtZTM3Yy00M2YwLTgwZmItNzYwNzU5 + NjFmYWIzIiwibmFtZSI6IlJlZ2lvbiAyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUi + OjIyLCJmeEFuY2hvciI6ImlkIiwiYm9va21hcmtSZWYiOjIsImJvb2ttYXJrSXNSZWdpb24iOnRydWUsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJz + ZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIzZTI3YzMxZS1mY2Q2LTRiZmUtYjU0ZC0zOWE5YWUyOWM0ZDUiLCJuYW1lIjoiUGxheSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjR9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiN2NkMzUyOTMtZTE3YS00OTIyLThjMjEtNDExOTVlNWY5M2I3IiwibmFtZSI6IlNlZWsiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudElu + ZGV4Ijo1fSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyMywiZnhBbmNob3IiOiJpZCIsInVzZVJlZ2lvbnMiOnRydWUsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJjMTg2Nzk1Yi02NGQxLTQzYWYtYWFjZi05MTg4OWViNjFmMjkiLCJuYW1lIjoiVm9sdW1lIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4VGFyZ2V0 + VmFsdWUiOjAuNzE2fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWV9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJyb3RhdGVJc0VuYWJsZWQiOnRydWV9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0y + YzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJu + YW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1 + OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwi + c291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFk + NS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29k + ZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYz + LTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFt + ZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlm + Nzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAy + YTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsi + aWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEt + MzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00 + YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIy + YTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7Imlk + IjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAs + ImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24g + My8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJu + YW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5 + ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIg + NC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUt + NDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5h + bWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIy + MzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFy + YWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0 + MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00 + NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJS + aWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6Mywi + bnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkx + OS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOAT 1792 454 1436 1514 + FXID {05D15787-2AF5-41BC-88B4-F97F6D266274} + WAK 0 0 + > + + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAyAEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcHJlOSIsImlkIjoiOS0zbW1fLXQiLCJsZXRNYXRjaGVkRXZlbnRzVGhyb3VnaCI6dHJ1ZSwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQi + OnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJjN2U3NWE5OC1kZDk2LTRlY2QtYWUzOC1k + NGI4OGMyMDRhMDciLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjoxMCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC4zNCwibWF4VGFyZ2V0VmFsdWUiOjAuNzE2fSwidGFyZ2V0Ijp7InR5cGUi + OjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {D19C4056-4073-4F14-8DC0-5E0F32C928CE} + WAK 0 0 + BYPASS 0 0 0 + "" + MkRNU+5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAADrZwAAAQAAAAAAEAA= + I3BnbT1pbml0aWFsaXplCiNBTT1aZWJyYTIKI1ZlcnM9MjA1MDAKI0VuZGlhbj1saXR0bGUKI25tPTM0CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwojbXM9 + Q3RybEEKI21zPUN0cmxCCiNtcz1MZm9HMQojbXM9TGZvRzIKI21zPUdhdGUKI21zPUtleUZvbAojbXM9S2V5Rm9sMgojbXM9VmVsb2NpdHkKI21zPUFUb3VjaAojbXM9 + QXJwTW9kCiNtcz1BcnBNZDIKI21zPUVudjEKI21zPUVudjIKI21zPUVudjMKI21zPUVudjQKI21zPU1TRUcxCiNtcz1NU0VHMgojbXM9TVNFRzMKI21zPU1TRUc0CiNt + cz1MZm8xCiNtcz1MZm8yCiNtcz1MZm8zCiNtcz1MZm80CiNtcz1NTWFwMQojbXM9TU1hcDIKI21zPU1NYXAzCiNtcz1NTWFwNAojbXM9TU1peDEKI21zPU1NaXgyCiNt + cz1NTWl4MwojbXM9TU1peDQKI252PTUKI212PUdhdGUKI212PUVudjEKI212PUVudjIKI212PUVudjMKI212PUVudjQKI2NtPW1haW4KQ2NPcD04NC4wMAojTEZPRz0x + CiNMRk9HMj0xCiNjbT1QQ29yZQpYXzE9MC4wMApZXzE9MC4wMApYXzI9MC4wMApZXzI9MC4wMApYXzM9MC4wMApZXzM9MC4wMApYXzQ9MC4wMApZXzQ9MC4wMApNVDEx + PU9TQzE6VHVuZQpNTDExPTAuMjAKTVIxMT0tMC4yMApNVDEyPU9TQzE6RHR1bgpNTDEyPS05LjAwCk1SMTI9LTExLjAwCk1UMTM9T1NDMjpEdHVuCk1MMTM9Ny4wMApN + UjEzPTguMDAKTVQxND1ub25lOmFzc2lnbmVkCk1MMTQ9MC4wMApNUjE0PTAuMDAKTVQxNT1ub25lOmFzc2lnbmVkCk1MMTU9MC4wMApNUjE1PTAuMDAKTVQxNj1ub25l + OmFzc2lnbmVkCk1MMTY9MC4wMApNUjE2PTAuMDAKTVQxNz1ub25lOmFzc2lnbmVkCk1MMTc9MC4wMApNUjE3PTAuMDAKTVQxOD1ub25lOmFzc2lnbmVkCk1MMTg9MC4w + MApNUjE4PTAuMDAKTVQyMT1FTlYzOlN1cwpNTDIxPTYyLjAwCk1SMjE9LTM1LjAwCk1UMjI9bm9uZTphc3NpZ25lZApNTDIyPTAuMDAKTVIyMj0wLjAwCk1UMjM9bm9u + ZTphc3NpZ25lZApNTDIzPTAuMDAKTVIyMz0wLjAwCk1UMjQ9bm9uZTphc3NpZ25lZApNTDI0PTAuMDAKTVIyND0wLjAwCk1UMjU9bm9uZTphc3NpZ25lZApNTDI1PTAu + MDAKTVIyNT0wLjAwCk1UMjY9bm9uZTphc3NpZ25lZApNTDI2PTAuMDAKTVIyNj0wLjAwCk1UMjc9bm9uZTphc3NpZ25lZApNTDI3PTAuMDAKTVIyNz0wLjAwCk1UMjg9 + bm9uZTphc3NpZ25lZApNTDI4PTAuMDAKTVIyOD0wLjAwCk1UMzE9VkNGMTpGTTEKTUwzMT00OC4wMApNUjMxPS03Mi4wMApNVDMyPW5vbmU6YXNzaWduZWQKTUwzMj0w + LjAwCk1SMzI9MC4wMApNVDMzPW5vbmU6YXNzaWduZWQKTUwzMz0wLjAwCk1SMzM9MC4wMApNVDM0PW5vbmU6YXNzaWduZWQKTUwzND0wLjAwCk1SMzQ9MC4wMApNVDM1 + PW5vbmU6YXNzaWduZWQKTUwzNT0wLjAwCk1SMzU9MC4wMApNVDM2PW5vbmU6YXNzaWduZWQKTUwzNj0wLjAwCk1SMzY9MC4wMApNVDM3PW5vbmU6YXNzaWduZWQKTUwz + Nz0wLjAwCk1SMzc9MC4wMApNVDM4PW5vbmU6YXNzaWduZWQKTUwzOD0wLjAwCk1SMzg9MC4wMApNVDQxPVZDRjE6UmVzCk1MNDE9MzkuMDAKTVI0MT0yOC4wMApNVDQy + PVZDQTE6Vm9sMgpNTDQyPTAuMDAKTVI0Mj0zNS4wMApNVDQzPVZDQTE6Vm9sMQpNTDQzPTAuMDAKTVI0Mz0tMTcuMDAKTVQ0ND1ub25lOmFzc2lnbmVkCk1MNDQ9MC4w + MApNUjQ0PTAuMDAKTVQ0NT1ub25lOmFzc2lnbmVkCk1MNDU9MC4wMApNUjQ1PTAuMDAKTVQ0Nj1ub25lOmFzc2lnbmVkCk1MNDY9MC4wMApNUjQ2PTAuMDAKTVQ0Nz1u + b25lOmFzc2lnbmVkCk1MNDc9MC4wMApNUjQ3PTAuMDAKTVQ0OD1ub25lOmFzc2lnbmVkCk1MNDg9MC4wMApNUjQ4PTAuMDAKTVQ1MT1PU0M0OlZvbApNTDUxPTEwNC4w + MApNUjUxPTEwMC4wMApNVDUyPU9TQzQ6RHR1bgpNTDUyPTAuMDAKTVI1Mj0tMjQuMDAKTVQ1Mz1PU0M0OlNGWDEKTUw1Mz0wLjAwCk1SNTM9NDkuMDAKTVQ1ND1ub25l + OmFzc2lnbmVkCk1MNTQ9MC4wMApNUjU0PTAuMDAKTVQ1NT1ub25lOmFzc2lnbmVkCk1MNTU9MC4wMApNUjU1PTAuMDAKTVQ1Nj1ub25lOmFzc2lnbmVkCk1MNTY9MC4w + MApNUjU2PTAuMDAKTVQ1Nz1ub25lOmFzc2lnbmVkCk1MNTc9MC4wMApNUjU3PTAuMDAKTVQ1OD1ub25lOmFzc2lnbmVkCk1MNTg9MC4wMApNUjU4PTAuMDAKTVQ2MT1O + b2lzZTE6Vm9sCk1MNjE9MTAwLjAwCk1SNjE9MjAwLjAwCk1UNjI9T1NDMTpWb2wKTUw2Mj0wLjAwCk1SNjI9LTEwMC4wMApNVDYzPU9TQzI6Vm9sCk1MNjM9MC4wMApN + UjYzPS0xMDAuMDAKTVQ2ND1ub25lOmFzc2lnbmVkCk1MNjQ9MC4wMApNUjY0PTAuMDAKTVQ2NT1ub25lOmFzc2lnbmVkCk1MNjU9MC4wMApNUjY1PTAuMDAKTVQ2Nj1u + b25lOmFzc2lnbmVkCk1MNjY9MC4wMApNUjY2PTAuMDAKTVQ2Nz1ub25lOmFzc2lnbmVkCk1MNjc9MC4wMApNUjY3PTAuMDAKTVQ2OD1ub25lOmFzc2lnbmVkCk1MNjg9 + MC4wMApNUjY4PTAuMDAKTVQ3MT1FTlYyOkF0awpNTDcxPTMzLjAwCk1SNzE9LTEzLjAwCk1UNzI9RU5WMjpEZWMKTUw3Mj0xNS4wMApNUjcyPS0yNC4wMApNVDczPUVO + VjE6U3VzCk1MNzM9MS4wMApNUjczPS0zOS4wMApNVDc0PUVOVjE6RGVjCk1MNzQ9MC4wMApNUjc0PS01MC4wMApNVDc1PUVOVjE6UmVsCk1MNzU9MjcuMDAKTVI3NT0t + MTAuMDAKTVQ3Nj1ub25lOmFzc2lnbmVkCk1MNzY9MC4wMApNUjc2PTAuMDAKTVQ3Nz1ub25lOmFzc2lnbmVkCk1MNzc9MC4wMApNUjc3PTAuMDAKTVQ3OD1ub25lOmFz + c2lnbmVkCk1MNzg9MC4wMApNUjc4PTAuMDAKTVQ4MT1EZWxheTE6TWl4Ck1MODE9MC4wMApNUjgxPS01OC4wMApNVDgyPU51UmV2MTpNaXgKTUw4Mj04Mi4wMApNUjgy + PTc1LjAwCk1UODM9Wk1hczpNYXN0Ck1MODM9MjkuMDAKTVI4Mz0yMy4wMApNVDg0PU51UmV2MTpTaXplCk1MODQ9MC4wMApNUjg0PS0xMTcuNDEKTVQ4NT1OdVJldjE6 + RGVjYXkKTUw4NT0xLjAwCk1SODU9LTE3LjAwCk1UODY9TnVSZXYxOlRvbmUKTUw4Nj0wLjAwCk1SODY9MzIuMDAKTVQ4Nz1ub25lOmFzc2lnbmVkCk1MODc9MC4wMApN + Ujg3PTAuMDAKTVQ4OD1ub25lOmFzc2lnbmVkCk1MODg9MC4wMApNUjg4PTAuMDAKTU1UMT1ub25lOmFzc2lnbmVkCk1NUzE9MApNTUQxPTAuMDAKTU1WUzE9MApNTVZE + MT0wLjAwCk1NVDI9bm9uZTphc3NpZ25lZApNTVMyPTAKTU1EMj0wLjAwCk1NVlMyPTAKTU1WRDI9MC4wMApNTVQzPW5vbmU6YXNzaWduZWQKTU1TMz0wCk1NRDM9MC4w + MApNTVZTMz0wCk1NVkQzPTAuMDAKTU1UND1ub25lOmFzc2lnbmVkCk1NUzQ9MApNTUQ0PTAuMDAKTU1WUzQ9MApNTVZEND0wLjAwCk1NVDU9bm9uZTphc3NpZ25lZApN + TVM1PTAKTU1ENT0wLjAwCk1NVlM1PTAKTU1WRDU9MC4wMApNTVQ2PW5vbmU6YXNzaWduZWQKTU1TNj0wCk1NRDY9MC4wMApNTVZTNj0wCk1NVkQ2PTAuMDAKTU1UNz1u + b25lOmFzc2lnbmVkCk1NUzc9MApNTUQ3PTAuMDAKTU1WUzc9MApNTVZENz0wLjAwCk1NVDg9bm9uZTphc3NpZ25lZApNTVM4PTAKTU1EOD0wLjAwCk1NVlM4PTAKTU1W + RDg9MC4wMApNTVQ5PW5vbmU6YXNzaWduZWQKTU1TOT0wCk1NRDk9MC4wMApNTVZTOT0wCk1NVkQ5PTAuMDAKTU1UMTA9bm9uZTphc3NpZ25lZApNTVMxMD0wCk1NRDEw + PTAuMDAKTU1WUzEwPTAKTU1WRDEwPTAuMDAKTU1UMTE9bm9uZTphc3NpZ25lZApNTVMxMT0wCk1NRDExPTAuMDAKTU1WUzExPTAKTU1WRDExPTAuMDAKTU1UMTI9bm9u + ZTphc3NpZ25lZApNTVMxMj0wCk1NRDEyPTAuMDAKTU1WUzEyPTAKTU1WRDEyPTAuMDAKU0Jhc2U9MgpTd2luZz0wLjAwClNUcmlnPTEKUFByb2o9MTEKUEZvbGQ9MApQ + RmlsZT0xCkdGaWxlPTIKR1NjYWxlPTAKQ2hMYXk9MApTdXJyTz0xClJldj05NDM2CkxFRD0wLjAwClBBR0U9MApQYWdlc09uPTAKQ29yZU49MwpTbGljZT00ClVJX29w + PTQKTWlkaUE9NQpNaWRpUD02CkRGb2xkPTcKQ3RybEE9MgpDdHJsQj0xMQojY209TEZPRwpTeW5jPS0yClRyaWc9MApXYXZlPTEKUGhzZT0wLjAwClJhdGU9ODAuMDAK + QW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTgKVVd2PTAKI2NtPUxGT0cyClN5bmM9LTIKVHJpZz0wCldhdmU9MQpQaHNlPTAuMDAKUmF0ZT0xMDAuMDAKQW1w + PTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTkKVVd2PTAKI2NtPVZDQwojTEZPMT0xCiNMRk8yPTEKI0xGTzM9MQojTEZPND0xClZvaWNlcz0xClZvaWNpbmc9MApN + b2RlPTAKUG9ydGE9MC4wMApQQj0yClBCRD0yCkFyU2M9MgpBck9yZD0wCkFyTHA9MApBck9jdD0wCkFyTEw9MTYKQXJUcj0wCkRyZnQ9MQpNVHVuUz0wCk1UdW5OPTEw + Ck1UdW5UPTExClRyc3A9LTEyCkZUdW49MC4wMApQb3J0Umc9MTAwLjAwClBvcnRhTT0wClBvcnRhMj0xNi4wMApBZ3RlMT0yCkF0cnAxPTAKQXZvYzE9MQpBbXVsMT0x + CkFtb2QxPTAKQU1EcHQxPTAuMDAKQU1EcEIxPTAuMDAKQWd0ZTI9MgpBdHJwMj0wCkF2b2MyPTEKQW11bDI9MQpBbW9kMj0wCkFNRHB0Mj0wLjAwCkFNRHBCMj0wLjAw + CkFndGUzPTIKQXRycDM9MApBdm9jMz0xCkFtdWwzPTEKQW1vZDM9MApBTURwdDM9MC4wMApBTURwQjM9MC4wMApBZ3RlND0yCkF0cnA0PTAKQXZvYzQ9MQpBbXVsND0x + CkFtb2Q0PTAKQU1EcHQ0PTAuMDAKQU1EcEI0PTAuMDAKQWd0ZTU9MgpBdHJwNT0wCkF2b2M1PTEKQW11bDU9MQpBbW9kNT0wCkFNRHB0NT0wLjAwCkFNRHBCNT0wLjAw + CkFndGU2PTIKQXRycDY9MApBdm9jNj0xCkFtdWw2PTEKQW1vZDY9MApBTURwdDY9MC4wMApBTURwQjY9MC4wMApBZ3RlNz0yCkF0cnA3PTAKQXZvYzc9MQpBbXVsNz0x + CkFtb2Q3PTAKQU1EcHQ3PTAuMDAKQU1EcEI3PTAuMDAKQWd0ZTg9MgpBdHJwOD0wCkF2b2M4PTEKQW11bDg9MQpBbW9kOD0wCkFNRHB0OD0wLjAwCkFNRHBCOD0wLjAw + CkFndGU5PTIKQXRycDk9MApBdm9jOT0xCkFtdWw5PTEKQW1vZDk9MApBTURwdDk9MC4wMApBTURwQjk9MC4wMApBZ3RlMTA9MgpBdHJwMTA9MApBdm9jMTA9MQpBbXVs + MTA9MQpBbW9kMTA9MApBTURwdDEwPTAuMDAKQU1EcEIxMD0wLjAwCkFndGUxMT0yCkF0cnAxMT0wCkF2b2MxMT0xCkFtdWwxMT0xCkFtb2QxMT0wCkFNRHB0MTE9MC4w + MApBTURwQjExPTAuMDAKQWd0ZTEyPTIKQXRycDEyPTAKQXZvYzEyPTEKQW11bDEyPTEKQW1vZDEyPTAKQU1EcHQxMj0wLjAwCkFNRHBCMTI9MC4wMApBZ3RlMTM9MgpB + dHJwMTM9MApBdm9jMTM9MQpBbXVsMTM9MQpBbW9kMTM9MApBTURwdDEzPTAuMDAKQU1EcEIxMz0wLjAwCkFndGUxND0yCkF0cnAxND0wCkF2b2MxND0xCkFtdWwxND0x + CkFtb2QxND0wCkFNRHB0MTQ9MC4wMApBTURwQjE0PTAuMDAKQWd0ZTE1PTIKQXRycDE1PTAKQXZvYzE1PTEKQW11bDE1PTEKQW1vZDE1PTAKQU1EcHQxNT0wLjAwCkFN + RHBCMTU9MC4wMApBZ3RlMTY9MgpBdHJwMTY9MApBdm9jMTY9MQpBbXVsMTY9MQpBbW9kMTY9MApBTURwdDE2PTAuMDAKQU1EcEIxNj0wLjAwCiNjbT1FTlYxCk1vZGU9 + MAppTW9kZT0wCnNNb2RlPTAKaW5pdD0wLjAwCkF0az0wLjAwCkRlYz0xMDAuMDAKU3VzPTQwLjAwClN1c1Q9MC4wMApTdXMyPTAuMDAKUmVsPTMwLjAwClZlbD0wLjAw + ClYyST0wLjAwClYyQT0wLjAwClYyRD0wLjAwClYyUz0wLjAwClYyRlI9MC4wMApWMlMyPTAuMDAKVjJSPTAuMDAKSzJJPTAuMDAKSzJBPTAuMDAKSzJEPTAuMDAKSzJT + PTAuMDAKSzJGUj0wLjAwCksyUzI9MC4wMApLMlI9MC4wMApTbG9wZT0tNjAuMDAKVEJhc2U9MAojY209RU5WMgpNb2RlPTAKaU1vZGU9MApzTW9kZT0wCmluaXQ9MC4w + MApBdGs9MjAuMDAKRGVjPTYwLjAwClN1cz0wLjAwClN1c1Q9MC4wMApTdXMyPTAuMDAKUmVsPTUwLjAwClZlbD05MC4wMApWMkk9MC4wMApWMkE9LTI0LjAwClYyRD0w + LjAwClYyUz0wLjAwClYyRlI9MC4wMApWMlMyPTAuMDAKVjJSPTAuMDAKSzJJPTAuMDAKSzJBPTAuMDAKSzJEPTAuMDAKSzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4w + MApLMlI9MC4wMApTbG9wZT0tNjAuMDAKVEJhc2U9MAojY209RU5WMwpNb2RlPTAKaU1vZGU9MApzTW9kZT0wCmluaXQ9MC4wMApBdGs9MC4wMApEZWM9MC4wMApTdXM9 + NDkuNTAKU3VzVD0wLjAwClN1czI9MC4wMApSZWw9MTAwLjAwClZlbD0wLjAwClYyST0wLjAwClYyQT0wLjAwClYyRD0wLjAwClYyUz0wLjAwClYyRlI9MC4wMApWMlMy + PTAuMDAKVjJSPTAuMDAKSzJJPTAuMDAKSzJBPTAuMDAKSzJEPTAuMDAKSzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4wMApLMlI9MC4wMApTbG9wZT0tNjAuMDAKVEJh + c2U9MAojY209RU5WNApNb2RlPTAKaU1vZGU9MApzTW9kZT0wCmluaXQ9MC4wMApBdGs9MC4wMApEZWM9NTAuMDAKU3VzPTEwMC4wMApTdXNUPTAuMDAKU3VzMj0wLjAw + ClJlbD0xMC4wMApWZWw9MC4wMApWMkk9MC4wMApWMkE9MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJTMj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksy + QT0wLjAwCksyRD0wLjAwCksyUz0wLjAwCksyRlI9MC4wMApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9LTYwLjAwClRCYXNlPTAKI2NtPU1TRUcxClRtVW49MQpFbnY9 + MTIKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAKVHJpZz0wCiNjbT1NU0VHMgpUbVVuPTEKRW52PTEzClZlbD0wLjAwCkF0az0wLjAwCkxwdD0wLjAw + ClJlbD0wLjAwClRyaWc9MAojY209TVNFRzMKVG1Vbj0xCkVudj0xNApWZWw9MC4wMApBdGs9MC4wMApMcHQ9MC4wMApSZWw9MC4wMApUcmlnPTAKI2NtPU1TRUc0ClRt + VW49MQpFbnY9MTUKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAKVHJpZz0wCiNjbT1MRk8xClN5bmM9LTMKVHJpZz0xCldhdmU9MApQaHNlPTAuMDAK + UmF0ZT04MC4wMApBbXA9MTAwLjAwClNsZXc9MQpOc3RwPTE2ClN0cHM9MTYKVVd2PTAKRGx5PTAuMDAKRE1TMT0xCkRNRDE9MTAwLjAwCkZNUzE9MApGTUQxPTAuMDAK + I2NtPUxGTzIKU3luYz0tMgpUcmlnPTEKV2F2ZT0xClBoc2U9MC4wMApSYXRlPTEwMC4wMApBbXA9MTAwLjAwClNsZXc9MQpOc3RwPTE2ClN0cHM9MTcKVVd2PTAKRGx5 + PTAuMDAKRE1TMT0wCkRNRDE9MC4wMApGTVMxPTAKRk1EMT0wLjAwCiNjbT1MRk8zClN5bmM9LTIKVHJpZz0xCldhdmU9MQpQaHNlPTAuMDAKUmF0ZT0xMDAuMDAKQW1w + PTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTE4ClVXdj0wCkRseT0wLjAwCkRNUzE9MApETUQxPTAuMDAKRk1TMT0wCkZNRDE9MC4wMAojY209TEZPNApTeW5jPS0y + ClRyaWc9MQpXYXZlPTEKUGhzZT0wLjAwClJhdGU9MTAwLjAwCkFtcD0xMDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz0xOQpVV3Y9MApEbHk9MC4wMApETVMxPTAKRE1E + MT0wLjAwCkZNUzE9MApGTUQxPTAuMDAKI2NtPU1NYXAxCk1vZGU9MgpNU3JjPTE2ClN0cHM9MjAKTnVtPTMKI2NtPU1NYXAyCk1vZGU9MApNU3JjPTAKU3Rwcz0yMQpO + dW09MTcKI2NtPU1NYXAzCk1vZGU9MwpNU3JjPTAKU3Rwcz0yMgpOdW09MTcKI2NtPU1NYXA0Ck1vZGU9MwpNU3JjPTAKU3Rwcz0yMwpOdW09MTcKI2NtPU1NaXgxClR5 + cGU9MApNb2QxPTAKTW9kMj0wCk1vZDM9MApDc3Q9NTAuMDAKI2NtPU1NaXgyClR5cGU9MApNb2QxPTAKTW9kMj0wCk1vZDM9MApDc3Q9NTAuMDAKI2NtPU1NaXgzClR5 + cGU9MApNb2QxPTAKTW9kMj0wCk1vZDM9MApDc3Q9NTAuMDAKI2NtPU1NaXg0ClR5cGU9MApNb2QxPTAKTW9kMj0wCk1vZDM9MApDc3Q9NTAuMDAKI2NtPUdyaWQKR3Jp + ZD0yNApHQnlwPTAKI2NtPU9TQzEKV2F2ZT0wClR1bmU9MC4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApQaHNlPTUwLjAwClBoc01TcmM9MApQaHNN + RHB0PTAuMDAKV051bT01LjAwCldQU3JjPTAKV1BEcHQ9MC4wMApWdG9EPTY1LjAwCkN1cnZlPTI1ClByZWM9NS4wMApGWDFUcD0wClNGWDE9MC4wMApGWDFTYz0wCkZY + MUR0PTAuMDAKRlgyVHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MQpEdHVuPS04LjAwCktWc2M9MjYKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0 + PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClN5bmM9MC4wMApTbmNTYz0wClNuY0R0PTAuMDAKU25jT249MApQb2xXPTUwLjAwClB3bU9uPTAKV2FUYj0y + NwpSZVBocz0wCk5vcm09MTUuMDAKUmVuZD0wCkZtdHplPTAKV1ROYW1lPTI4CiNjbT1PU0MyCldhdmU9MApUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0yNgpU + TURwdD0xMi4wMApQaHNlPTUwLjAwClBoc01TcmM9NQpQaHNNRHB0PTIyLjAwCldOdW09NS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD01NS4wMApDdXJ2ZT0yOQpQ + cmVjPTUuMDAKRlgxVHA9MApTRlgxPTAuMDAKRlgxU2M9MApGWDFEdD0wLjAwCkZYMlRwPTAKU0ZYMj0wLjAwCkZYMlNjPTAKRlgyRHQ9MC4wMApQb2x5PTEKRHR1bj00 + LjAwCktWc2M9MzAKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClN5bmM9MC4wMApTbmNTYz0wClNuY0R0PTAu + MDAKU25jT249MApQb2xXPTEwMC4wMApQd21Pbj0xCldhVGI9MzEKUmVQaHM9MApOb3JtPTE1LjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT0zMgojY209T1NDMwpXYXZl + PTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwClBoc2U9NTAuMDAKUGhzTVNyYz0wClBoc01EcHQ9MC4wMApXTnVtPTUuMDAKV1BTcmM9 + MApXUERwdD0wLjAwClZ0b0Q9MC4wMApDdXJ2ZT0zMwpQcmVjPTUuMDAKRlgxVHA9MApTRlgxPTAuMDAKRlgxU2M9MApGWDFEdD0wLjAwCkZYMlRwPTAKU0ZYMj0wLjAw + CkZYMlNjPTAKRlgyRHQ9MC4wMApQb2x5PTAKRHR1bj0wLjAwCktWc2M9MzQKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5E + dD0wLjAwClN5bmM9MC4wMApTbmNTYz0wClNuY0R0PTAuMDAKU25jT249MApQb2xXPTUwLjAwClB3bU9uPTAKV2FUYj0zNQpSZVBocz0wCk5vcm09MTUuMDAKUmVuZD0w + CkZtdHplPTAKV1ROYW1lPTM2CiNjbT1PU0M0CldhdmU9MwpUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKUGhzZT0wLjAwClBoc01TcmM9 + MApQaHNNRHB0PTAuMDAKV051bT0xLjAwCldQU3JjPTAKV1BEcHQ9MTUuMDAKVnRvRD02NS4wMApDdXJ2ZT0zNwpQcmVjPTcuMDAKRlgxVHA9MTYKU0ZYMT0wLjAwCkZY + MVNjPTAKRlgxRHQ9MC4wMApGWDJUcD0wClNGWDI9MC4wMApGWDJTYz0wCkZYMkR0PTAuMDAKUG9seT0yCkR0dW49Mi4wMApLVnNjPTM4ClZvbD0wLjAwClZvbFNjPTAK + Vm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBvbFc9MTAwLjAwClB3bU9uPTAK + V2FUYj0zOQpSZVBocz0wCk5vcm09MjAuMDAKUmVuZD0wCkZtdHplPTAKV1ROYW1lPTQwCiNjbT1Ob2lzZTEKVHlwZT0wCkYxPTUwLjAwCkYxU3JjPTE1CkYxRHB0PTEw + MC4wMApGMj0wLjAwCkYyU3JjPTAKRjJEcHQ9MC4wMApLVnNjPTQxClZvbD0wLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAK + UG9seT0wClBvbFc9MTAwLjAwCiNjbT1Ob2lzZTIKVHlwZT0wCkYxPTEwMC4wMApGMVNyYz0wCkYxRHB0PTAuMDAKRjI9MC4wMApGMlNyYz0wCkYyRHB0PTAuMDAKS1Zz + Yz00MgpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9MTAwLjAwCiNjbT1WQ0YxClR5cD0w + CkN1dD0zNy4wMApSZXM9NS4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTEwMC4wMApGUzE9MTUKRk0yPTYwLjAwCkZTMj0xMQpLZXlTY2w9NDIuMDAKI2NtPVZDRjIK + VHlwPTE3CkN1dD03OC4wMApSZXM9MzUuMDAKRHJ2PTIwLjAwCkdhaW49MC4wMApGTTE9MzguMDAKRlMxPTUKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPVZD + RjMKVHlwPTE4CkN1dD0xNTAuMDAKUmVzPTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCiNjbT1W + Q0Y0ClR5cD0xOApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209 + Rk1PMQpXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwClZ0b0Q9MC4wMApEdHVu + PTAuMDAKS1ZzYz00MwpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9MTAwLjAwCkdldHI9 + MAojY209Rk1PMgpXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwClZ0b0Q9MC4w + MApEdHVuPTAuMDAKS1ZzYz00NApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9MTAwLjAw + CkdldHI9MAojY209Rk1PMwpXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwClZ0 + b0Q9MC4wMApEdHVuPTAuMDAKS1ZzYz00NQpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9 + MTAwLjAwCkdldHI9MAojY209Rk1PNApXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURwdD0w + LjAwClZ0b0Q9MC4wMApEdHVuPTAuMDAKS1ZzYz00NgpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0w + ClBvbFc9MTAwLjAwCkdldHI9MAojY209Q29tYjEKTW9kZT0wClR1bmU9MC4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApEZXRuPTAuMDAKVnRvRD0w + LjAwCkZCPTAuMDAKRkJTcmM9MApGQkRwdD0wLjAwCkRhbXA9MC4wMApEbXBTcmM9MApEbXBEcHQ9MC4wMApFeGM9MC4wMApJbmo9MTAwLjAwCklualNyYz0wCkluakRw + dD0wLjAwClRuZT01MC4wMApUbmVTcmM9MApUbmVEcHQ9MC4wMApTZWM9MC4wMApTZWNTcmM9MApTZWNEcHQ9MC4wMApEaXN0PTAuMDAKRHJ5PTAuMDAKVm9sPTIwMC4w + MApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTEwMC4wMApGaWxsPTAKI2NtPUNvbWIyCk1vZGU9MApUdW5l + PTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKRGV0bj0wLjAwClZ0b0Q9MC4wMApGQj0wLjAwCkZCU3JjPTAKRkJEcHQ9MC4wMApEYW1wPTAuMDAK + RG1wU3JjPTAKRG1wRHB0PTAuMDAKRXhjPTAuMDAKSW5qPTEwMC4wMApJbmpTcmM9MApJbmpEcHQ9MC4wMApUbmU9NTAuMDAKVG5lU3JjPTAKVG5lRHB0PTAuMDAKU2Vj + PTAuMDAKU2VjU3JjPTAKU2VjRHB0PTAuMDAKRGlzdD0wLjAwCkRyeT0wLjAwClZvbD0yMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFu + RHQ9MC4wMApQb2x5PTAKUG9sVz0xMDAuMDAKRmlsbD0wCiNjbT1TaGFwZTEKVHlwZT0zCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9MTAwLjAwCkVN + U3JjPTAKRU1EcHQ9MC4wMApJbnB1dD0wLjAwCk91dHB1dD0wLjAwCkhpT3V0PTAuMDAKI2NtPVNoYXBlMgpUeXBlPTMKRGVwdGg9MC4wMApETVNyYz0wCkRNRHB0PTAu + MDAKRWRnZT0xMDAuMDAKRU1TcmM9MApFTURwdD0wLjAwCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKSGlPdXQ9MC4wMAojY209TWl4MQpQYW49MC4wMApNaXg9NTAuMDAK + UG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPU1peDIKUGFuPTAuMDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1NaXgzClBhbj0wLjAwCk1p + eD01MC4wMApQbk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209TWl4NApQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPVhNRjEKVHlw + PTAKQ3V0PTE1MC4wMApSZXM9MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMApGT2ZmPTAuMDAKRk9Nb2Q9MC4wMApGT1NyYz0wClhG + TT0wLjAwClhGTUQ9MC4wMApYRk1TPTAKQmlhcz0wLjAwCk9Mb2FkPTAuMDAKQ2xpY2s9MC4wMApEcnY9MQpSb3V0PTAKVHlwMj0tMQojY209WE1GMgpUeXA9MApDdXQ9 + MTUwLjAwClJlcz0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCkZPZmY9MC4wMApGT01vZD0wLjAwCkZPU3JjPTAKWEZNPTAuMDAK + WEZNRD0wLjAwClhGTVM9MApCaWFzPTAuMDAKT0xvYWQ9MC4wMApDbGljaz0wLjAwCkRydj0xClJvdXQ9MApUeXAyPS0xCiNjbT1TQjEKUmFuZ2U9MApGcmVxPTAuMDAK + Rk1TcmM9MApGTURwdD0wLjAwCk9mZnM9MC4wMApPTVNyYz0wCk9NRHB0PTAuMDAKTWl4PTUwLjAwCk1NU3JjPTAKTU1EcHQ9MC4wMAojY209U0IyClJhbmdlPTAKRnJl + cT0wLjAwCkZNU3JjPTAKRk1EcHQ9MC4wMApPZmZzPTAuMDAKT01TcmM9MApPTURwdD0wLjAwCk1peD01MC4wMApNTVNyYz0wCk1NRHB0PTAuMDAKI2NtPURpc3QxClR5 + cGU9MApJbnB1dD0wLjAwCk91dHB1dD0wLjAwClByZVRpbHQ9MC4wMApQc3RUaWx0PTAuMDAKQ250RnJlcT0xMDAuMDAKTG93PTAuMDAKSGlnaD0wLjAwClBvc3RGbHQ9 + MAojY209RGlzdDIKVHlwZT0wCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKUHJlVGlsdD0wLjAwClBzdFRpbHQ9MC4wMApDbnRGcmVxPTEwMC4wMApMb3c9MC4wMApIaWdo + PTAuMDAKUG9zdEZsdD0wCiNjbT1Gb2xkMQpSaXBwbGVzPTQKRm9sZHM9MTAuMDAKRk1vZD0wLjAwCkZTcmM9MApCaWFzPTAuMDAKQk1vZD0wLjAwCkJTcmM9MApGUmF0 + aW89NTAuMDAKRlNsb3BlPTEwLjAwCkNBbmdsPTUwLjAwCkxpdmU9MAojY209Rm9sZDIKUmlwcGxlcz00CkZvbGRzPTEwLjAwCkZNb2Q9MC4wMApGU3JjPTAKQmlhcz0w + LjAwCkJNb2Q9MC4wMApCU3JjPTAKRlJhdGlvPTUwLjAwCkZTbG9wZT0xMC4wMApDQW5nbD01MC4wMApMaXZlPTAKI2NtPVZDQTEKUGFuMT0wLjAwClBhbk1TMT0wClBh + bk1EMT0wLjAwClZvbDE9NTAuMDAKVkNBMT0xCk1vZFNyYzE9MApNb2REcHQxPTAuMDAKUGFuMj0wLjAwClBhbk1TMj0wClBhbk1EMj0wLjAwClZvbDI9MC4wMApWQ0Ey + PTEKTW9kU3JjMj0wCk1vZERwdDI9MC4wMApQYW4zPTAuMDAKUGFuTVMzPTAKUGFuTUQzPTAuMDAKVm9sMz0yNS4wMApWQ0EzPTEKTW9kU3JjMz0wCk1vZERwdDM9MC4w + MApQYW40PTAuMDAKUGFuTVM0PTAKUGFuTUQ0PTAuMDAKVm9sND0wLjAwClZDQTQ9MQpNb2RTcmM0PTAKTW9kRHB0ND0wLjAwCk1UMT0wCk1UMj0wCk1UMz0wCk1UND0w + ClBCMT0wClBCMj0wClBCMz0wClBCND0wCkJ1czE9MApCdXMyPTAKQnVzMz0wCkJ1czQ9MApTZW5kMT0wLjAwClNuU3JjMT0wClNuRHB0MT0wLjAwClNlbmQyPTAuMDAK + U25TcmMyPTAKU25EcHQyPTAuMDAKQXR0Uz0xCiNjbT1HcmlkRlgKR3JpZD00NwpHQnlwPTAKI2NtPU1vZEZYMQpNb2RlPTAKQ2VudD0xNy41MApTcGVkPTUwLjAwClBo + T2ZmPTUwLjAwCkRwdGg9OTIuNTAKRmVlQj0wLjAwCk1peD0yNS4wMApMQ3V0PTAuMDAKSEN1dD0xMDAuMDAKUXVhZD02MC4wMApRcGhzPTI1LjAwCkxlcT0wLjAwCkhl + cT0wLjAwClExPTAuMDAKUTI9MC4wMApFUW9uPTAKI2NtPU1vZEZYMgpNb2RlPTIKQ2VudD00MC4wMApTcGVkPTUuMDAKUGhPZmY9NTAuMDAKRHB0aD00MC4wMApGZWVC + PTUwLjAwCk1peD01MC4wMApMQ3V0PTAuMDAKSEN1dD0xMDAuMDAKUXVhZD0wLjAwClFwaHM9MjUuMDAKTGVxPTAuMDAKSGVxPTAuMDAKUTE9MC4wMApRMj0wLjAwCkVR + b249MQojY209RGVsYXkxCk1vZGU9MgpNaXg9MzMuMDAKRkI9MC4wMApDQj02NS4wMApMUD03MC4wMApIUD01MC4wMApEcnY9MC4wMApTeW5jMT00ClN5bmMyPTkKU3lu + YzM9NApTeW5jND00ClQwPTEwMC4wMApUMT0xMDAuMDAKVDI9MTAwLjAwClQzPTEwMC4wMApQYW4xPS0xMDAuMDAKUGFuMj0xMDAuMDAKUGFuMz0tMTAwLjAwClBhbjQ9 + MTAwLjAwCiNjbT1EZWxheTIKTW9kZT0wCk1peD01MC4wMApGQj00MC4wMApDQj0wLjAwCkxQPTcwLjAwCkhQPTMwLjAwCkRydj0wLjAwClN5bmMxPS0xClN5bmMyPS0x + ClN5bmMzPS0xClN5bmM0PS0xClQwPTQ1LjAwClQxPTMwLjAwClQyPTEwMC4wMApUMz0xMDAuMDAKUGFuMT0tMTAwLjAwClBhbjI9MTAwLjAwClBhbjM9LTEwMC4wMApQ + YW40PTEwMC4wMAojY209U2hhcGUzClR5cGU9MwpEZXB0aD0wLjAwCkRNU3JjPTAKRE1EcHQ9MC4wMApFZGdlPTEwMC4wMApFTVNyYz0wCkVNRHB0PTAuMDAKSW5wdXQ9 + MC4wMApPdXRwdXQ9MC4wMApIaU91dD0wLjAwCiNjbT1TaGFwZTQKVHlwZT0zCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9MTAwLjAwCkVNU3JjPTAK + RU1EcHQ9MC4wMApJbnB1dD0wLjAwCk91dHB1dD0wLjAwCkhpT3V0PTAuMDAKI2NtPU1peDUKUGFuPTAuMDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0w + CiNjbT1NaXg2ClBhbj0wLjAwCk1peD01MC4wMApQbk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209UmV2MQpNb2RlPTAKRHJ5PTEwMC4wMApXZXQ9NTAuMDAKRkI9NzAu + NTAKRGFtcD0yMy4wMApTaXplPTc1LjAwClNwZD01MC4wMApEcHQ9NTUuMDAKREZCPTcwLjAwCkRTaXplPTczLjAwCkVNaXg9MTAwLjAwCkRNb2Q9NTUuMDAKRFNwZD01 + NS4wMApQcmU9MjAuMDAKI2NtPUNvbXAxClR5cGU9MQpSYXQ9NDAuMDAKVGhyZXM9MC4wMApBdHQ9MjAuMDAKUmVsPTI1LjAwCklucHV0PTAuMDAKT3V0cHV0PTAuMDAK + I2NtPUNvbXAyClR5cGU9MQpSYXQ9NDAuMDAKVGhyZXM9MC4wMApBdHQ9MjAuMDAKUmVsPTI1LjAwCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKI2NtPUVRMQpmYzE9MjAu + MDAKcmVzMT0yNS4wMApnYWluMT0wLjAwCmZjMj00MC4wMApyZXMyPTI1LjAwCmdhaW4yPTAuMDAKZmMzPTYwLjAwCnJlczM9MjUuMDAKZ2FpbjM9MC4wMApmYzQ9ODAu + MDAKcmVzND0yNS4wMApnYWluND0wLjAwCiNjbT1FUTIKZmMxPTIwLjAwCnJlczE9MjUuMDAKZ2FpbjE9MC4wMApmYzI9NDAuMDAKcmVzMj0yNS4wMApnYWluMj0wLjAw + CmZjMz02MC4wMApyZXMzPTI1LjAwCmdhaW4zPTAuMDAKZmM0PTgwLjAwCnJlczQ9MjUuMDAKZ2FpbjQ9MC4wMAojY209VkNGNQpUeXA9MTgKQ3V0PTE1MC4wMApSZXM9 + MC4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTAuMDAKRlMxPTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPVZDRjYKVHlwPTE4CkN1dD0xNTAuMDAKUmVz + PTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCiNjbT1TQjMKUmFuZ2U9MApGcmVxPTAuMDAKRk1T + cmM9MApGTURwdD0wLjAwCk9mZnM9MC4wMApPTVNyYz0wCk9NRHB0PTAuMDAKTWl4PTUwLjAwCk1NU3JjPTAKTU1EcHQ9MC4wMAojY209WE1GMwpUeXA9MApDdXQ9MTUw + LjAwClJlcz0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCkZPZmY9MC4wMApGT01vZD0wLjAwCkZPU3JjPTAKWEZNPTAuMDAKWEZN + RD0wLjAwClhGTVM9MApCaWFzPTAuMDAKT0xvYWQ9MC4wMApDbGljaz0wLjAwCkRydj0xClJvdXQ9MApUeXAyPS0xCiNjbT1EaXN0MwpUeXBlPTAKSW5wdXQ9MC4wMApP + dXRwdXQ9MC4wMApQcmVUaWx0PTAuMDAKUHN0VGlsdD0wLjAwCkNudEZyZXE9MTAwLjAwCkxvdz0wLjAwCkhpZ2g9MC4wMApQb3N0Rmx0PTAKI2NtPURpc3Q0ClR5cGU9 + MApJbnB1dD0wLjAwCk91dHB1dD0wLjAwClByZVRpbHQ9MC4wMApQc3RUaWx0PTAuMDAKQ250RnJlcT0xMDAuMDAKTG93PTAuMDAKSGlnaD0wLjAwClBvc3RGbHQ9MAoj + Y209TnVSZXYxClByZT0wLjAwCkRhbXA9ODEuNTAKRGVjYXk9NzMuNTAKU2l6ZT0xMzIuMDAKVG9uZT0tMzYuMDAKV2lkdGg9NjkuNTAKTWl4PTAuMDAKI2NtPVpNYXMK + UmV0MT0wLjAwClJldDI9MC4wMApNYXN0PTcwLjAwClhZMUw9NDgKWFkyTD00OQpYWTNMPTUwClhZNEw9NTEKWFkxVD01MgpYWTJUPTUzClhZM1Q9NTQKWFk0VD01NQpP + U0MxPTU2Ck9TQzI9NTcKT1NDMz01OApPU0M0PTU5Ck1TRUcxPTYwCk1TRUcyPTYxCk1TRUczPTYyCk1TRUc0PTYzCk1TRUc1PTY0Ck1TRUc2PTY1Ck1TRUc3PTY2Ck1T + RUc4PTY3ClJldjE9NjgKUG4zPTE5ClBuND0wClBuNT0wClBuNj0wClBuNz0wClBuOD0wClBuOT0wClBuMTA9MApQbjExPTAKUmFjazA9NjkKUmFjazE9NzAKI2NtPUxh + eU1lbQpMYXlNZW0xPTAKTGF5TWVtMj0wCkxheU1lbTM9MApMYXlNZW00PTAKTGF5TWVtNT0wCgoKLy8gU2VjdGlvbiBmb3IgdWdseSBjb21wcmVzc2VkIGJpbmFyeSBE + YXRhCi8vIERPTidUIFRPVUNIIFRISVMKCiQkJCQ3NzM0MAo/YWFhYWlhZHA6amtqampqbG86YWFhYWlhbHA6Z2RnY2VuZWI6cG5wcGhwZHA6cG1wcGhwZHA6a2tra2tr + bG86Cmtta2tra2RvOmFhYWFhYWVhOmNhYWFhYWFhOmFhOmRwOmxwOmRvOmViOmVjOmxvOmdmOmVhOmdiOmhwOmdwOmgKazpjYTpoZTpsbjpkbjpwcDpkYzphazpmZDpn + ajpiYTpkYTppYTplbiF0QWFiQTljcGVtTGdkSmdtY3BkZU5mYQpWSmdvTFZnZWhkQTIzOHRJQTEwVmdvVk9WSmdtVk1IY29naVNoYUFmYUxoZFZPVkxnb2NvZ2lTaGFB + MzZ0SUE3CjR0SUExMFZrYmdhaWhpbGljY2JhaWtoZGdhZnBkZGpobWtvYm1kbWhpaGhnbGFnbGxtZWtpbGJpcG9ta2hwZkUKZmlhb2dtbW9mYWRrcGhtYlJkZGNjbG1j + Zm5oZ2FYbGFhaWdoZGZwbWtpZ21la2dhY3BhaHBvamFwZ2Vma20xWgp0YWVBMTBnYWRla3BBdEFhYkE5ZWlMaGhKaGNnZU5VZ2RKaGMxQTI0M3RZQTEzOHRBYWJBMjY1 + dFlBNnFBMTI3CnRZQTZxQTEyN3RJQTEwZ2VIZ2dKaGZnbU9OaGRnZEpnbUhBMjJxeUExSTFBMVlJQTFrYUlBMW1hSUExb2FJdEEKYWNBNXFBM3F5QTFJMUExWUlBMWth + SUExbWFJQTFvYUlBMkVBMVdFQTFORUExWEVBMUlFQTFmYUVBMWdhRUExaAphRUExWUVBMWlpRUExamFFQTFqaUVBMWthRUExa2lFQTFsYUVBMWxpRUExbWFFQTFtaUVB + MW5hRUExbmlFQTFvCmFFQTFvaUVBMXBhRUExcGlFQTJGQTFhZUZBMWFpRkExYW1GQTFXRkExYmVGQTFiaUZBMWJtRkExTkZBMWNlRkEKMWNpRkExY21GQTFYRkExZGVG + QTFkaUZBMWRtRkExSUZBMWVlRkExZWlGQTFlbUZBMWZhRkExZmVGQTFmaUZBMQpmbUZBMWdhRkExZ2VGQTFnaUZBMWdtRkExaGFGQTFPRkExaGlGQTFobUZBMVlGQTFp + Y0ZBMWllRkExaWdGQTFpCmlGQTFpa0ZBMWltRkExaW9GQTFqYUZBMWpjRkExamVGQTFqZ0ZBMWppRkExamtGQTFqbUZBMWpvRkExa2FGQTEKa2NGQTFrZUZBMWtnRkEx + a2lGQTFra0ZBMWttRkExa29GQTFsYUZBMWxjRkExbGVGQTFsZ0ZBMWxpRkExbGtGQQoxbG1GQTFHRkExbWFGQTFtY0ZBMW1lRkExbWdGQTFtaUZBMW1rRkExbW1GQTFt + b0ZBMW5hRkExbmNGQTFuZUZBCjFuZ0ZBMW5pRkExbmtGQTFubUZBMW5vRkExb2FGQTFvY0ZBMW9lRkExb2dGQTFvaUZBMW9rRkExb21GQTFvb0YKQTFwYUZBMXBjRkEx + cGVGQTFwZ0ZBMXBpRkExcGtGQTFwbUZBMXBvRnRYYWNBNXFBM2FiQTJhYkEyYWJBM2FlQQoyNXJxQTdybmdqbGZvREE3ckExSTFBN3JBMVlJQTdyQTFrYUlBN3JBMW1h + SUE3ckExb2FJQTdyQTJFQTdyQTFXCkVBN3JBMU5FQTdyQTFYRUE3ckExSUVBN3JBMWZhRUE3ckExZ2FFQTdyQTFoYUVBN3JBMVlFQTdyQTFpaUVBN3IKQTFqYUVBN3JB + MWppRUE3ckExa2FFQTdyQTFraUVBN3JBMWxhRUE3ckExbGlFQTdyQTFtYUVBN3JBMW1pRUE3cgpBMW5hRUE3ckExbmlFQTdyQTFvYUVBN3JBMW9pRUE3ckExcGFFQTdy + QTFwaUVBN3JBMTV0WGFjQTVxQTNhYkEyCmFiQTJhYkEzYWVBMjVycUE3cnlBN3JBMUkxQTdyQTFZSUE3ckExa2FJQTdyQTFtYUlBN3JBMW9hSUE3ckEyRUEKN3JBMVdF + QTdyQTFORUE3ckExWEVBN3JBMUlFQTdyQTFmYUVBN3JBMWdhRUE3ckExaGFFQTdyQTFZRUE3ckExaQppRUE3ckExamFFQTdyQTFqaUVBN3JBMWthRUE3ckExa2lFQTdy + QTFsYUVBN3JBMWxpRUE3ckExbWFFQTdyQTFtCmlFQTdyQTFuYUVBN3JBMW5pRUE3ckExb2FFQTdyQTFvaUVBN3JBMXBhRUE3ckExcGlFQTdyQTE1dFhhY0E1cUEKM2Fi + QTJhYkEyYWJBM2FlQTI1cnFBN3J5QTdyQTFJMUE3ckExWUlBN3JBMWthSUE3ckExbWFJQTdyQTFvYUlBNwpyQTJFQTdyQTFXRUE3ckExTkVBN3JBMVhFQTdyQTFJRUE3 + ckExZmFFQTdyQTFnYUVBN3JBMWhhRUE3ckExWUVBCjdyZGpQaWJFQTdyQTFqYUVBN3JBMWppRUE3ckExa2FFQTdyQTFraUVBN3JBMWxhRUE3ckExbGlFQTdyQTFtYUUK + QTdyQTFtaUVBN3JBMW5hRUE3ckExbmlFQTdyQTFvYUVBN3JBMW9pRUE3ckExcGFFQTdyQTFwaUVBN3JBMTV0WAphY0E1cUEzYWJBMmFiQTJhYkEzYWVBMjVycUE3cnlB + N3JBMUkxQTdyQTFZSUE3ckExbWFJQTdyQTFORUE3ckExCldFQTdyQTJFQTdyQTFvYUlBN3JBMWthSUE3ckExWEVBN3JBMUlFQTdyQTFmYUVBN3JBMWdhRUE3ckExaGFF + QTcKckExWUVBN3JBMWlpRUE3ckExamFFQTdyQTFqaUVBN3JBMWthRUE3ckExa2lFQTdyQTFsYUVBN3JBMWxpRUE3cgpBMW1hRUE3ckExbWlFQTdyQTFuYUVBN3JBMW5p + RUE3ckExb2FFQTdyQTFvaUVBN3JBMXBhRUE3ckExcGlFQTdyCkExNXRZQTZxQTEyN3RZQTZxQTEyN3RZQTZxQTEyN3RZQTZxQTEyN3RBYWNBNXFzZmdmZm5mR0EzZmdm + ZmJmQnEKd3hxc3d4cXN3eHFzd3hxc3d4cXN3eHFzd3hxc3d4cXN3eHFzd3hxc3d4cXN3eHFzd3hxc3d4cXN3eHFzd3hxcwp3eHFzd3hxc3d4cXN3eHFzd3hxc3d4cXN3 + eHFzd3hxc3d4cXN3eHFzd3hxc3d4cXN3eHFzd3hxc3d4cXRBYWNBCjVxQTUxMXRBYWNBNXFZamFsZ0RwYWNwYWZHWGNsTURoYW9lZWdDa2lvYUxDQW1oZ2xkbWhhZGlm + YUdmYWZubm4KRE5uZGtkR1hkaGdhR3BtZ2lNQm1hamJlakJwYWtkamZHbGVpamVlQ25pam1tcEdjaWhqZ2tDb2FoaWJlQ21pZApnbGFEZmlmZm1rRFdiZWhmQ2RpSWVq + QmllbmhibENtZWtkYmxCaWlsYmdoQm1hZGdlaEdsYW9kZGtHbm1hZ2RiCkJZZ2NjbkRuYWVrcGlEa2ltZGtlR2Jpb21qYUdsaWNwYmlCWWdhYmpCZG1uZGFtQ2FtaWVn + ZEJsYWphZmJDWG4KbGtsRG5tcG5iakJsaXBuaW9HamlCYW1DQWttZ2VkbWJpZmNrZUdJam1iZlBZbmdnaENsaTFjaENiaW9jb2ZEZgphZmhXRGppbWVkZEJBZGRtYlFj + ZWJtYm5CWWdja3BsbUFhZmhnUGhpY25lZkJkbUxTQnBtbGxlaENlaWttYm1CCmhhZmlnZUNraVZub0RkaWdrbGtEa2FkZ2JnQ2Zpb2hnb0NmYWppZGVHbW1uYmJjQ1dr + ZGxoR21hbWJkaEJpZW8KcGJwQmdpa3Bsa0dwYWFjRENBbWtXbG1nZXBiY2xDaGFsY2dkR25pZ2xoYkNXZ2FaQ21hY2VnbVBhaWNkb2hHWQprZ2lpRG9hZGJjZkdYamho + b0RvbWxtRENraWlkYm9DQWFmb2JRbWFiYmVpUGZpamFHRFlqZW5iUFlubGNuUWFlCmthZGhDbG1pa2JsQ0lwaW5pUVljYm1uUGNpSGptRGphaW9RR1llaWVnRGJpbm5m + ZUNrbUhobEJiaWpqbGRHbWkKaWNmY0NBZm1ubkdsbVFoYUNPYmtibENrYWNmbGZEWGhnbW5Ea2VpaWhjQ2tlR2ZhQmJpZ2lhcENYYWVjbEJPYwplZGtCaWlmYWxlR0Fh + ZGJjQ0lsZ2lmUW5hcGZCR2tpYmNuZURuYWxobWtHQWxibmJQa2VpalpCZ21ubWVwQmthCm1qa29HYWlhbGZvQ2VlY2hhbUNpZWZoYm1CbWlKSEJtYWdkMUdmaWFwaWJH + ZGlpamtlREFoYW5uZGxjaWttYW4KQklWZGxQbWFvZWFoUHBpZWxlcEJ0QWFjQTVxSWtqRUJwaW1qbmtHZGVBZWVDaGFiYmFjQ2ZpamFib0JibWtvZgpoQm9hZWlsZVBs + YWptbW1Hb2Fhb21qRFhubGVwR3BpZWxqbURZZ2dsY0dwYXBibWxHZW1uZmNoQmNpY25qbURjCmliZ0xDTmhpU0JnZU1na0JBYmZkbWxtWWlpam5QZGljYmRpQnBpYm9Q + RGRtaGhnZUNpaWJtZGJCZmFibEhCbWEKam9lZVBqYTFibUdrYWpsZm9CZ2lvb2RrQm5lYmNlb0NwZWhvYWZCbGlpY2hkQkFuYXBobG1wYWxhYWpHYW1magpmbEJmYWxj + Y3BCZ2lKMUNubWJlY2tCY2Vib2ZhQklubGtmUW1hbW9lcERwZVBnaEJoaWNnb21EWWFlSkJOaWJjCmNEYmlsYWpqRGZpcGtvaUduYUltY0dnYWtoYW9CZWllZ2xjRGJl + bWdLQ2xhbWNhZEJoaXBvZmxCbWFnZGdrUU4KYWlwYVFsaWFpaGlDamFmaWVrQmNta21iZ0NibW9jY2VCZ2FjZVFCbGFibWtlR2JlbWhhZ0NrYWpqcGlRTnBrYgpiR1ll + Zm9mRHBpZmNsZEdnaWJmamhHV2NpY2JCbGFjbG1uRGdlamNobENnZXBpSUJuaUFjaUNwYWphZWRCZW1lCmRoZ0NwYW1uZGpEQW1va2JQaWltZWFiQlhVZmpEYmVmaWFk + QkllZmxpUEFwZGlnUG9lYWZla0JjbWhmRUJOZXAKbmZHZWljamRtQ1dpZWZtR2xtaWZmYkNwaVpmZkNBZ2lHZGxtYW9nU1BmbW1iYWVCWWplUURuYWNobm5Ea2lpZwpv + bEdYRGhvR2RpSWZmQkFtZEZCaGlab29HT2puZmNDWGdjYmZDYmllZUFCZm1qb2RlQmxpY2lwakdiaWFuYmpDCllqZ21pUGltZ21EQ2lpamdpb0dBZ2FlbUJtaW1oTUJv + aWltWUdrbW5qY2NDbWFwaW1qUUlna2thR2hhUGFsR2oKZWRnZmhDbmFtamdvQ21hTGphUU5Ga2RQYWljamxsRG1hbmppb1BZSWxsUWttbGljZ0NnZWVvYWdDWWZoZmNE + Zwphb2xlcEduaWZhcG1EWEhnZUdvYWxsY2lEdGFtYWRBNXFlZWVqZmNlaGFlQTJhbUEyZXBVZWRkYkFKaGpIYW5iCmJBMWZpWmVnZGJBSmhqSFRoYkExZXBVZWRkZUFO + ZGJkaWFuYmVBMVVnaUpoYUhTQTFUYmJBMWVwVWVkU0EzYW4KYmJBMVpWaGlkYkEzVGhjQTFmaVplZ1NBM1RoYkExZWdaZXBkZUEzYW9iYkExZW9MVmhkSGRiQTFhbmJi + QTFlbwpMVmhkSFNBMWFvYmNBMVVGZGJBNFRiY0ExZmdlZGVnZGRBSmhqSGFjYmJBMWZnZWRlZ2RiQTNhYmJiQTFlZ1plCnBkZEEzYW9iYkExVWdpSmhhSGRiQUhUYmNB + MVpWaGlkZUEzVGhjQTFmY1Znb2doZGJBMlRPQTFlZ1plcFNBTlMKZGJhb2JiQTFlZVZoZE9TQWhqSFRiY0ExWlZoaVNBTlNkZVRoaUExWlZoaWRkQTNUaGJBMWVkTGdu + Z2NkYkEyVApiY0ExZWRMZ25nY1NBMlRiY0ExZmNWZ29naFNBMlRoaUExZmdlZGVnZGVBM2FjYmJBMWZnZWRlZ1NBM2FiMUExCmVnWmVwZGJBM2FvYmJBMVVGU0FoY05T + ZGhUYmlBMWVnTGdtZ2VkYkEyVGJlQTFlZ0xnbWdlU0EyVGJpQTJOU2QKaUEyM2VtSmhqSEEyOEpoakhoY05kZFhBM2VwVWVkZGRBM2FvYmJBMTROZGRkYkExOWVlVmhk + T2RiQTJUYmJBMgo5ekEyNDN0YWV6QTJ5QTNwalVSRHEyQTExMWxnbW9pZERSMUtCcTFBMTExZWtVbmVRcG9SS0JxMUExMTFub2NoCmNjUWFja21XQklwaFdCcUExMThC + cG5mZkFCcUExMTVwb1VSRHBpUjFEcUExMTVwbGtocG9EcGhVUkRxQTExNXAKYVpvZERvbHBqb2REcUExMTVnZ29wbGtEZ2FqbGxsRHFBMTE1Ymlmb2lpRGJkVGlqRHFB + MTE1amZqamNsRGlscApiY21EcUExMTVUZWlhZ0RBa2FhaERxQTExNWhiamhsZFFmbGVobGdRcUExMTVpb2JkZmpRZ2RoZGZvUXFBMTE1Cm1hbmVva2RtZ2FqZXBmZG1x + QTEwOHlQZ2NkbGxhYWNjbWRtamVYZXBkbXFBMTA3dXNwaVJLQnExQTEwN3VzdnEKMUExMDd1c3EyQTEwN3VzbGViZ2xjbGxlZ2lubWVkbHFBMTA3dXNBM1NiZWZnZG1x + QTEwN3VzZ25NbmRHTnBlbwpiRHFBMTA3dXNwblJLQ3ZxQTEwN3VzcG5SS0N2cUExMDd1c3BuUktDdnFBMTA3dXNwbVJLQ3ZxQTEwN3VzcG1SCktDdnFBMTA3dXNwbVJL + Q3ZxQTEwN3VzcG1SS0N2cUExMDd1c3BtUktDdnFBMTA3dXNwbVJLQ3ZxQTEwN3VwbVIKS0NzdnFBMTExZW1vYnBrR3NBMTE5bmFtbWptR2llb2xmYkdBMTE5ZW1vYnBr + UEExWUdiYmtvZWhHQTExNWVtbwpiTVBBMkdBMTY1MW5la2RYQm9hTWplREExMTlrYWhhYmZCamlqamRqREExMTluaU1wZURtbm1tYW1EQTExOW5pCk1PRG9hTWJlUUEx + NjU1YWZBMnRXQTZ5ZmllbWRtY2VLM2dhZWlYYmlLM3RBekEyeUE4MTkxdHpBN2ZrU05maEoKaGdIT0pnY2dtSE5lZmhpaGFMaGNPQTEydGFlekEyeUEzcGpVUkRxMkEx + MTFsZ21vaWREUjFLQnExQTExMWVrVQpuZVFwb1JLQnExQTExMW5vY2hjY1FhY2ttV0JJcGhXQnFBMTE4QnBuZmZBQnFBMTE1cG9VUkRwaVIxRHFBMTE1CnBsa2hwb0Rw + aFVSRHFBMTE1cGFab2REb2xwam9kRHFBMTE1Z2dvcGxrRGdhamxsbERxQTExNWJpZm9paURiZFQKaWpEcUExMTVqZmpqY2xEaWxwYmNtRHFBMTE1VGVpYWdEQWthYWhE + cUExMTVoYmpobGRRZmxlaGxnUXFBMTE1aQpvYmRmalFnZGhkZm9RcUExMTVtYW5lb2tkbWdhamVwZmRtcUExMDh5UGdjZGxsYWFjY21kbWplWGVwZG1xQTEwCjd1c3Bp + UktCcTFBMTA3dXN2cTFBMTA3dXNxMkExMDd1c2xlYmdsY2xsZWdpbm1lZGxxQTEwN3VzQTNTYmVmZ2QKbXFBMTA3dXNnbk1uZEdOcGVvYkRxQTEwN3VzcG5SS0N2cUEx + MDd1c3BuUktDdnFBMTA3dXNwblJLQ3ZxQTEwNwp1c3BtUktDdnFBMTA3dXNwbVJLQ3ZxQTEwN3VzcG1SS0N2cUExMDd1c3BtUktDdnFBMTA3dXNwbVJLQ3ZxQTEwCjd1 + c3BtUktDdnFBMTA3dXBtUktDc3ZxQTExMWVtb2Jwa0dzQTExOW5hbW1qbUdpZW9sZmJHQTExOWVtb2Jwa1AKQTFZR2Jia29laEdBMTE1ZW1vYk1QQTJHQTE2NTFuZWtk + WEJvYU1qZURBMTE5a2FoYWJmQmppampkakRBMTE5bgppTXBlRG1ubW1hbURBMTE5bmlNT0RvYU1iZVFBMTY1NWFmQTJ0V0E2eWZpZW1kbWNlSzNnYWVpWGJpSzN0QXpB + CjJ5QTgxOTF0ekE3ZmtTTmZoSmhnSE9KZ2NnbUhOZWZoaWhhTGhjT0E0aGxXQTV0YWV6QTJ5QTNwalVSRHEyQTEKMTFsZ21vaWREUjFLQnExQTExMWVrVW5lUXBvUktC + cTFBMTExbm9jaGNjUWFja21XQklwaFdCcUExMThCcG5mZgpBQnFBMTE1cG9VUkRwaVIxRHFBMTE1cGxraHBvRHBoVVJEcUExMTVwYVpvZERvbHBqb2REcUExMTVnZ29w + bGtECmdhamxsbERxQTExNWJpZm9paURiZFRpakRxQTExNWpmampjbERpbHBiY21EcUExMTVUZWlhZ0RBa2FhaERxQTEKMTVoYmpobGRRZmxlaGxnUXFBMTE1aW9iZGZq + UWdkaGRmb1FxQTExNW1hbmVva2RtZ2FqZXBmZG1xQTEwOHlQZwpjZGxsYWFjY21kbWplWGVwZG1xQTEwN3VzcGlSS0JxMUExMDd1c3ZxMUExMDd1c3EyQTEwN3VzbGVi + Z2xjbGxlCmdpbm1lZGxxQTEwN3VzQTNTYmVmZ2RtcUExMDd1c2duTW5kR05wZW9iRHFBMTA3dXNwblJLQ3ZxQTEwN3VzcG4KUktDdnFBMTA3dXNwblJLQ3ZxQTEwN3Vz + cG1SS0N2cUExMDd1c3BtUktDdnFBMTA3dXNwbVJLQ3ZxQTEwN3VzcAptUktDdnFBMTA3dXNwbVJLQ3ZxQTEwN3VzcG1SS0N2cUExMDd1cG1SS0NzdnFBMTExZW1vYnBr + R3NBMTE5bmFtCm1qbUdpZW9sZmJHQTExOWVtb2Jwa1BBMVlHYmJrb2VoR0ExMTVlbW9iTVBBMkdBMTY1MW5la2RYQm9hTWplREEKMTE5a2FoYWJmQmppampkakRBMTE5 + bmlNcGVEbW5tbWFtREExMTluaU1PRG9hTWJlUUExNjU1YWZBMnRXQTZ5ZgppZW1kbWNlSzNnYWVpWGJpSzN0QXpBMnlBODE5MXR6QTdma1NOZmhKaGdIT0pnY2dtSE5l + ZmhpaGFMaGNPQTRrCmlibUE1dGFlekEyeUEzamxrY2NnREEyQnFBMTIyQnFBMTIyQnFBMTIyQnFBMTIyQnFBMTIyQnFBMTIyQnFBMTIKMkJxQTEyMkJxQTEyMkJxQTEy + MkJxQTEyMkJxQTEyMkJxQTEyMkJxQTEyMkJxQTEyMkJxQTExNWFmYmdsY2RtQQozcUExMTVzQTNxQTExNXNBM3FBMTE1c0EzcUExMTVzQTNxQTExNXNBM3FBMTE1c0Ez + cUExMTVzQTNxQTExNXNBCjNxQTExNXNBM3FBMTE1c0EzcUExMTVzQTNxQTExNXNBM3FBMTE1c0EzcUExMTVzQTNxQTExNXNBM3FBNDIwN2EKZUEydFdBNnlmaWVtZG1j + ZUszZ2FlaVhiaUszdEF6QTJ5QTNxQTdxQTE5bmRqbGZvQkExMU1uZGJsREEzUG9qWgpCQTExTE1VQm5kamxub1FBN29rWkxCQTdjYmFsZmpCQTI3cUEyN2ptbm9PQkE0 + N2xjamFhZkJsY2phaWZRZWRiCmdsY2xtQTM1cUEzMWJnbGNqYURBMTFlZGJnbGNRQTE1TW5kYmxEZWRiZ2xjZG1BMTYzUG9qWkNBN29rWkxDQTEKMWxjamFhZkdBM01u + ZGJsQ0ExOW1pRmJnQ0EyM2NiYWxmakNBMjJDQTE1TE1VQ0ExMUxNbmRHQTIzY2JhbGZqQwpBMjNsY2phaWZHQTExZ2VjYmFsQ0ExOWFsZmplaUNBMTVqbW5vT0NBMjdu + cHBla2dHQTE5YWxmam1pR0EzMWVkCmJnU0dBMTU5TE1VQkE3YmdsY1dCQTExZW9MTUJBMTVmam1pRkJBMzVhbGZqZWlCQTM1YWxmamVpQkEzNUxNVUIKQTMxa2hkaFFC + QTI3cGZrZ2RoQkEyNzVva1pMQ0ExMW5kamxmb0NBMTVpZ2NtZ2VDQTE1am1ub09DQTM5am1ubwpPQ0EyN2ptbm9wZUdBMjNNbmRqbEdBNTFNbmRibENBMTVmam1pRkNB + MjU4QkExMWJnbGNqYURBN0xNVUJBMjNuCmRqbGZvQkExNW5kamxmb0JBMzluZGpsZm9CQTExZGlQVkJBMzVmam1pRkJBMTFnZWNiYWxCQTMxam1ub09CQTIKNzlsY2ph + aWZHQTExZGlQVkNBMTFNbmRibENBMzVqYmlmY21DQTM1YmdsY1dDQTNiZ2xjV0NBN2JnbGNXQ0ExNQpva1pMQ0EzMENBMTFnZWNiYWxDQTExYmdsY2phR0E0N3Bma2dk + aENBMjM5a2hkaFBEQTNlZGJnU1FMTVVCQTE5Cm9rWkxCQTM5TW5kYmxCQTIzbWlGYmdCQTIzZW9MTUJBMjNNbmRibEJBMTlNbmRibEJBN25wcGVjZ0JBMTltaUYKYmdC + QTI4M25kamxmb0dkaVBvakdBMjNlZGJnU0NBMTlzQTExbnBwZWNnQ0ExMXNBMzVnZWNiYWxDQTNzQTdnZQpjYmFsQ0EzOWpiaWZjbUNBMzE1am1ub09EQTI3b2taTEJB + MTluZGpsbm9EQTNiZ2xjV0JBMTVjYmFsZmpCQTE5Cm5kamxub0RBMTlkaVBWQkExOWptbm9wZURBMTlva1pMQkExOW1pRmJnQkExOWRpUFZCQTI4M1BvalpDQTdsY2oK + YWlmR2ptbm9PR0Ezam1ub09DbGNqYWFmR0ExMWFsZmplaUNBMTVraGRoUUNBMjNQb2paQ0ExOWJnbGNXQ0ExNQphbGZqZWlDQTE1TE1uZEdBMTlQb2paQ0ExOWxjamFp + ZkdBMTFnZWNiYWxDQTE5YWxmamVpQ0ExNWptbm9PQ0EyCjducHBla2dHQTE5YWxmam1pR0EzMWVkYmdTR0ExNTFjbmdlY2JCQTNMTVVCQTdqbW5vT0JMTW5kREEzTW5k + amwKREExOWZqbWlGQkE3cUEyM2FsZmplaUJBN2dlY2JhbEJBMjNhbGZqZWlCQTM1TE1VQkEzMWtoZGhRQkEyN3BmawpnZGhCQTI2N21pRmJnQ0EyM2ptbm9wZUdBM2Rp + UFZDQTIzTW5kYmxDQTE5bmRqbGZvQ0ExNWdlY2JhbENBM3NBCjdsY2phYWZDQTdzQTdnZWNiYWxDQTM1NWptbm9PRExNVUJBN2JnbGNXQkEzZGlQVkJBM2VvTE1CQTE1 + ZmptaUYKQkEyN3FBNDNhbGZqZWlCQTdiZ2xjV0JBM2RpUG9qREExMXFBN21pRmJnQkEzTE1uZERBMTVraGRoUUJBMTlvawpaTEJBMjc1ZWRiZ2xjR0Ezb2taTENBMTFu + ZGpsZm9DQTE1aWdjbWdlQ0E3cGZrZ2RoQ0Ezam1ub09DQTExc0EyCjNqbW5vT0NBMjdqbW5vcGVHQTIzTW5kamxHQTUxTW5kYmxDQTE1ZmptaUZDQTI1OEJBMTVuZGps + Zm9CQTdxQTIKM3FBMTFuZGpsZm9CQTM5bmRqbGZvQkExMWRpUFZCQTI3am1ub09CQTE0QkExNWVvTE1CQTduZGpsZm9CQTdnZQpjYmFsQkE3amJpZmNtQkEyNzFqbW5v + T0NBN2VvTE1DQTdqbW5vT0NBN3NBN2VvTE1DbmRqbG5vUEEzc0ExMXNBCjExc0E3c0E3ZW9MTUNBMTVzQTNzQTE1c0ExMXNBMzVwZmtnZGhDQTQ3c0EyN2JnbGNXQ0Ex + OTl0ekE3ZmtTTmYKaEpoZ0hPSmdjZ21ITmVmaGloYUxoY09BNG1sYmZBNXRXQTZ5ZmNmYmVmQUs0aG9JQkszdFdBNnlmY2ZiZWZBSwo0aG9JQkszdFdBNnlnbmJmMks2 + QUszdFdBNnlnbmJmMks2QUszdFdBNnlnbmJmMks2QUszdFdBNnlnbmJmMks2CkFLM3RhbWFkQTVxZWVlamZjZWhhZEEyYWdBMmVlSGdtSmhqZGJBMWFqYmJBMWVkTGdu + aGFTQTJUYmNBMVpMZ2UKZWdmaWRiQVNUYmJBMWVlVmhkT2RkQVNBVGJlQTFlb2hmZmNIaGdkYkExYWpiYldBVWdpSmhhSGRlQTFUYmJBMQpVZ2lKaGFIZGRBMVRiYkEx + ZWVWaGRPZGVBMlRiaUExWlZoaWRmQUpoakhUaGJBMVpWaGlkZ0EzVGhjQTFlZUhnCm1KaGpTQTFUYmJBMWZjSGhnZGJBM1RiYldBZmNWZ29naGRkQTJUaGJBMVpMZ2Vl + Z2ZpU0ExVGJiQTFlZExnbmgKYWRiQTJUYmJBMTNmZ2VkZWdkZkEzYWNiYkExZWZmYlNBNFRiY0ExZWZmYmRiQWVtSmhqSFRiYkEySmhqSGhjTgpkaUE0ZmdlZGVnZGdB + M2FjYmJBMVVGZGRBNFRiY0ExZmlaZWdkZEEzVGhjQTE3ZW1KaGpIQTI4SmhqSGhjTmRiCjFBMjhOZGJTQTIzZW1KaGpIQTI4SmhqSGhjTmRiZGVBMjhOZGJkZkEyM2Vt + SmhqSEEyOEpoakhoY05kYmRoQTIKOE5kYmRpQTIzZW1KaGpIQTI4SmhqSGhjTlNYQTI4TlNkYkEyM2VtSmhqSEEyOEpoakhoY05TZGRBMjhOU2RlQQoxOXR6QTdlcGhk + Z2RWZ20xSk9MaGNoZEEyMHR6QTdlZ1ZnbU9IaGNBMjV0ekE3WlZoaU9oZmhjSEEyNHR6QTdlCmZoaU9oY0poZEEyNXRBYWJBOWZpUWVnVmdvSE5mZWhmZ29IVGZqUWVq + Z29PSGhjaGdKZ21BMjMzdEFhYkE5ZmkKUWVkaGZPTGdnMVRmalFmY0hoZExnb0pnb2dkSEEyMzV0QWFiQTlmaVFGSGdtMVRmalFlb0xWaGRIQTI0MXRBYQpiQTlmaVFl + ZmdvaGdIZ21MaGFIVGZqUVVoYUpnZEhoZEEyMzZ0ekE3ZWdWZ29jblVKaGhjbmZhZ21oZGNuVWhiCmhmQTdsYWhpY2xiaGFiQTJqbXR6QTdlZ1Znb2NuVUpoaGNuZmFn + bWhkY25VaGJoZkE3bGFoaWNsYmhhYkEyam0KdHpBN2VnVmdvY25VSmhoY25mYWdtaGRjblVoYmhmQTdsYWhpY2xiaGFiQTJqbXR6QTdGSGdtMWhkTmVnZ21WaAphMUho + Y0ExN2FidHpBN2dlSGdnSmhmZ21PQTE2ZWZnb2hnU0EzdHpBN2dlSGdnSmhmZ21PQTE2ZWZnb2hnZGRBCjN0ekE3Z2VIZ2dKaGZnbU9BMTZlZmdvaGdkZUEzdHpBN2dl + SGdnSmhmZ21PQTE2WlVlZmVoZGJBMnR6QThIZ2cKSmhmZ21PQWRkcGlnYUNsZ2hjZ2FDaG9vbmZwQ2lqZ2lmcENuam9kZm9DZ25mcGZvQ3R6QThIZ2dKaGZnbU9BbAps + aWZYQmlnaGRYQmZhSlhCYmllcFhCbm9kbVhCa2Nja1hCdHpBOEhnZ0poZmdtT0Flb2hiaWdCQ2lsaWdCZGZrCmdpZ0JsYm1haWdCU25saWdCbGpwZmlnQnR6QThIZ2dK + aGZnbU9BWHBsZWlQaGRib2VtUGtwRWVwUG9jZ2VmY1AKYW9paWZmUGRia2xmaVB0ekE3Z2VIZ2dKaGZnbU9BMTZaVWVmZWhTQTJ0QWFiQThGQTI1NXRBYWJBOEZBMjU1 + PQo4ODE2MzUKAAA= + AGluaXRpYWxpemUAEAAAAA== + > + PRESETNAME initialize + FLOATPOS 0 0 0 0 + FXID {13D06117-9DF0-481E-88D3-96C3E0D16731} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-29-controller-support.rpp b/plugin-reaper-realearn/resources/test-projects/issue-29-controller-support.rpp new file mode 100644 index 0000000..de85738 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-29-controller-support.rpp @@ -0,0 +1,195 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAXBUAAAEAAAAAABAA + eyJsZXRNYXRjaGVkRXZlbnRzVGhyb3VnaCI6ZmFsc2UsImxldFVubWF0Y2hlZEV2ZW50c1Rocm91Z2giOnRydWUsImFsd2F5c0F1dG9EZXRlY3RNb2RlIjp0cnVlLCJz + ZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiY29udHJvbERldmljZUlkIjoiMTAiLCJmZWVkYmFja0RldmljZUlkIjoiZngtb3V0cHV0IiwibWFwcGluZ3MiOlt7 + Im5hbWUiOiJQcmltYXJ5IG1hcHBpbmcgMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjowLCJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFy + YWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtZXNzYWdlIjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJjb250aW51b3VzIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MSwibWluU291cmNlVmFsdWUiOjAuMCwibWF4U291cmNlVmFsdWUiOjEuMCwibWluVGFyZ2V0VmFsdWUi + OjAuMCwibWF4VGFyZ2V0VmFsdWUiOjEuMCwibWluVGFyZ2V0SnVtcCI6MC4wLCJtYXhUYXJnZXRKdW1wIjoxLjAsIm1pblN0ZXBTaXplIjowLjAxLCJtYXhTdGVwU2l6 + ZSI6MS4wLCJtaW5QcmVzc01pbGxpcyI6MCwibWF4UHJlc3NNaWxsaXMiOjAsImVlbENvbnRyb2xUcmFuc2Zvcm1hdGlvbiI6IiIsImVlbEZlZWRiYWNrVHJhbnNmb3Jt + YXRpb24iOiIiLCJyZXZlcnNlSXNFbmFibGVkIjpmYWxzZSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluT3JNYXgiLCJyb3VuZFRhcmdldFZhbHVlIjpmYWxzZSwic2Nh + bGVNb2RlRW5hYmxlZCI6ZmFsc2UsInJvdGF0ZUlzRW5hYmxlZCI6ZmFsc2V9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJyZWFwZXIiLCJ0eXBlIjoyLCJjb21tYW5kTmFt + ZSI6bnVsbCwiaW52b2NhdGlvblR5cGUiOjAsInRyYWNrR1VJRCI6bnVsbCwidHJhY2tOYW1lIjpudWxsLCJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOmZhbHNl + LCJmeEluZGV4IjpudWxsLCJpc0lucHV0RngiOmZhbHNlLCJlbmFibGVPbmx5SWZGeEhhc0ZvY3VzIjpmYWxzZSwic2VuZEluZGV4IjpudWxsLCJwYXJhbUluZGV4Ijow + LCJzZWxlY3RFeGNsdXNpdmVseSI6ZmFsc2UsInRyYW5zcG9ydEFjdGlvbiI6InBsYXlTdG9wIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiY29udGludW91cyIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjB9LCJjb250cm9sSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0lzRW5hYmxlZCI6dHJ1ZSwicHJldmVudEVjaG9GZWVkYmFjayI6ZmFsc2Us + InNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6ZmFsc2UsImFjdGl2YXRpb25UeXBlIjoiYWx3YXlzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOm51 + bGwsImlzT24iOmZhbHNlfSwibW9kaWZpZXJDb25kaXRpb24yIjp7InBhcmFtSW5kZXgiOm51bGwsImlzT24iOmZhbHNlfSwicHJvZ3JhbUNvbmRpdGlvbiI6eyJwYXJh + bUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjB9LCJlZWxDb25kaXRpb24iOiIifV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3sibmFtZSI6IkVuY29kZXIgPT4gVmlydHVh + bCBNdWx0aSAxIiwic291cmNlIjp7ImNhdGVnb3J5IjoibWlkaSIsInR5cGUiOjAsImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwibWVzc2FnZSI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiY29udGludW91cyIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjB9 + LCJtb2RlIjp7InR5cGUiOjEsIm1pblNvdXJjZVZhbHVlIjowLjAsIm1heFNvdXJjZVZhbHVlIjoxLjAsIm1pblRhcmdldFZhbHVlIjowLjAsIm1heFRhcmdldFZhbHVl + IjoxLjAsIm1pblRhcmdldEp1bXAiOjAuMCwibWF4VGFyZ2V0SnVtcCI6MS4wLCJtaW5TdGVwU2l6ZSI6MC4wLCJtYXhTdGVwU2l6ZSI6MC4xNCwibWluUHJlc3NNaWxs + aXMiOjAsIm1heFByZXNzTWlsbGlzIjowLCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiIiLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiIiwicmV2ZXJzZUlz + RW5hYmxlZCI6ZmFsc2UsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbk9yTWF4Iiwicm91bmRUYXJnZXRWYWx1ZSI6ZmFsc2UsInNjYWxlTW9kZUVuYWJsZWQiOmZhbHNl + LCJyb3RhdGVJc0VuYWJsZWQiOmZhbHNlfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjIsImNvbW1hbmROYW1lIjpudWxsLCJpbnZvY2F0aW9u + VHlwZSI6MCwidHJhY2tHVUlEIjpudWxsLCJ0cmFja05hbWUiOm51bGwsImVuYWJsZU9ubHlJZlRyYWNrSXNTZWxlY3RlZCI6ZmFsc2UsImZ4SW5kZXgiOm51bGwsImlz + SW5wdXRGeCI6ZmFsc2UsImVuYWJsZU9ubHlJZkZ4SGFzRm9jdXMiOmZhbHNlLCJzZW5kSW5kZXgiOm51bGwsInBhcmFtSW5kZXgiOjAsInNlbGVjdEV4Y2x1c2l2ZWx5 + IjpmYWxzZSwidHJhbnNwb3J0QWN0aW9uIjoicGxheVN0b3AiLCJjb250cm9sRWxlbWVudFR5cGUiOiJjb250aW51b3VzIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MH0s + ImNvbnRyb2xJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrSXNFbmFibGVkIjp0cnVlLCJwcmV2ZW50RWNob0ZlZWRiYWNrIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJD + b250cm9sIjpmYWxzZSwiYWN0aXZhdGlvblR5cGUiOiJhbHdheXMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsicGFyYW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9LCJt + b2RpZmllckNvbmRpdGlvbjIiOnsicGFyYW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9LCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1J + bmRleCI6MH0sImVlbENvbmRpdGlvbiI6IiJ9LHsibmFtZSI6IkJ1dHRvbiArID0+IFZpcnR1YWwgTXVsdGkgMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im1pZGkiLCJ0 + eXBlIjowLCJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtZXNzYWdlIjowLCJj + b250cm9sRWxlbWVudFR5cGUiOiJjb250aW51b3VzIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MSwibWluU291cmNlVmFsdWUiOjAuMCwi + bWF4U291cmNlVmFsdWUiOjEuMCwibWluVGFyZ2V0VmFsdWUiOjAuMCwibWF4VGFyZ2V0VmFsdWUiOjEuMCwibWluVGFyZ2V0SnVtcCI6MC4wLCJtYXhUYXJnZXRKdW1w + IjoxLjAsIm1pblN0ZXBTaXplIjowLjA0MDAwMDAwMDAwMDAwMDAzLCJtYXhTdGVwU2l6ZSI6MC4wNDAwMDAwMDAwMDAwMDAwMywibWluUHJlc3NNaWxsaXMiOjAsIm1h + eFByZXNzTWlsbGlzIjowLCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiIiLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiIiwicmV2ZXJzZUlzRW5hYmxlZCI6 + ZmFsc2UsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbk9yTWF4Iiwicm91bmRUYXJnZXRWYWx1ZSI6ZmFsc2UsInNjYWxlTW9kZUVuYWJsZWQiOmZhbHNlLCJyb3RhdGVJ + c0VuYWJsZWQiOmZhbHNlfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjIsImNvbW1hbmROYW1lIjpudWxsLCJpbnZvY2F0aW9uVHlwZSI6MCwi + dHJhY2tHVUlEIjpudWxsLCJ0cmFja05hbWUiOm51bGwsImVuYWJsZU9ubHlJZlRyYWNrSXNTZWxlY3RlZCI6ZmFsc2UsImZ4SW5kZXgiOm51bGwsImlzSW5wdXRGeCI6 + ZmFsc2UsImVuYWJsZU9ubHlJZkZ4SGFzRm9jdXMiOmZhbHNlLCJzZW5kSW5kZXgiOm51bGwsInBhcmFtSW5kZXgiOjAsInNlbGVjdEV4Y2x1c2l2ZWx5IjpmYWxzZSwi + dHJhbnNwb3J0QWN0aW9uIjoicGxheVN0b3AiLCJjb250cm9sRWxlbWVudFR5cGUiOiJjb250aW51b3VzIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MH0sImNvbnRyb2xJ + c0VuYWJsZWQiOnRydWUsImZlZWRiYWNrSXNFbmFibGVkIjp0cnVlLCJwcmV2ZW50RWNob0ZlZWRiYWNrIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjpm + YWxzZSwiYWN0aXZhdGlvblR5cGUiOiJhbHdheXMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsicGFyYW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9LCJtb2RpZmllckNv + bmRpdGlvbjIiOnsicGFyYW1JbmRleCI6bnVsbCwiaXNPbiI6ZmFsc2V9LCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MH0s + ImVlbENvbmRpdGlvbiI6IiJ9LHsibmFtZSI6IkJ1dHRvbiAtID0+IFZpcnR1YWwgTXVsdGkgMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im1pZGkiLCJ0eXBlIjowLCJj + aGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtZXNzYWdlIjowLCJjb250cm9sRWxl + bWVudFR5cGUiOiJjb250aW51b3VzIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MSwibWluU291cmNlVmFsdWUiOjAuMCwibWF4U291cmNl + VmFsdWUiOjEuMCwibWluVGFyZ2V0VmFsdWUiOjAuMCwibWF4VGFyZ2V0VmFsdWUiOjEuMCwibWluVGFyZ2V0SnVtcCI6MC4wLCJtYXhUYXJnZXRKdW1wIjoxLjAsIm1p + blN0ZXBTaXplIjowLjA0LCJtYXhTdGVwU2l6ZSI6MC4wNCwibWluUHJlc3NNaWxsaXMiOjAsIm1heFByZXNzTWlsbGlzIjowLCJlZWxDb250cm9sVHJhbnNmb3JtYXRp + b24iOiIiLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiIiwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluT3JNYXgiLCJy + b3VuZFRhcmdldFZhbHVlIjpmYWxzZSwic2NhbGVNb2RlRW5hYmxlZCI6ZmFsc2UsInJvdGF0ZUlzRW5hYmxlZCI6ZmFsc2V9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwidHlwZSI6MiwiY29tbWFuZE5hbWUiOm51bGwsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0dVSUQiOm51bGwsInRyYWNrTmFtZSI6bnVsbCwiZW5hYmxl + T25seUlmVHJhY2tJc1NlbGVjdGVkIjpmYWxzZSwiZnhJbmRleCI6bnVsbCwiaXNJbnB1dEZ4IjpmYWxzZSwiZW5hYmxlT25seUlmRnhIYXNGb2N1cyI6ZmFsc2UsInNl + bmRJbmRleCI6bnVsbCwicGFyYW1JbmRleCI6MCwic2VsZWN0RXhjbHVzaXZlbHkiOmZhbHNlLCJ0cmFuc3BvcnRBY3Rpb24iOiJwbGF5U3RvcCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImNvbnRpbnVvdXMiLCJjb250cm9sRWxlbWVudEluZGV4IjowfSwiY29udHJvbElzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tJc0VuYWJsZWQiOnRydWUs + InByZXZlbnRFY2hvRmVlZGJhY2siOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOmZhbHNlLCJhY3RpdmF0aW9uVHlwZSI6ImFsd2F5cyIsIm1vZGlmaWVy + Q29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjpudWxsLCJpc09uIjpmYWxzZX0sIm1vZGlmaWVyQ29uZGl0aW9uMiI6eyJwYXJhbUluZGV4IjpudWxsLCJpc09uIjpmYWxz + ZX0sInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUluZGV4IjowfSwiZWVsQ29uZGl0aW9uIjoiIn1dLCJwYXJhbWV0ZXJzIjp7fX0= + AAAQAAAA + > + FLOAT 2210 420 1373 1408 + FXID {49534C93-C0CF-4495-B0D5-710AA3D4FCC8} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOAT 268 356 1341 1163 + FXID {2A1C3430-5364-4723-976E-D389C0B5279F} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-324-find-mapping-activation-state.rpp b/plugin-reaper-realearn/resources/test-projects/issue-324-find-mapping-activation-state.rpp new file mode 100644 index 0000000..ee3f1b9 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-324-find-mapping-activation-state.rpp @@ -0,0 +1,137 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAHQMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcmMuNSIsImlkIjoiU1Y1Nk02aUMiLCJzZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjYwOTA3ZWVmLTZjZDctNDA4My05Y2Q4LTdkMWZiZDZhYWE0ZiIsIm5hbWUiOiJJbmFjdGl2ZSIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRl + eCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiNjYwZDE4YTgtYjUzMy00YWNiLThjZjItZjI3NjVlNzY3M2Q5IiwibmFtZSI6IkFjdGl2ZSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFy + YWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJn + ZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9XX0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {5506F3D2-65A0-46B2-A079-71E5382923F8} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-325-navigate-within-group.rpp b/plugin-reaper-realearn/resources/test-projects/issue-325-navigate-within-group.rpp new file mode 100644 index 0000000..bae7a2a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-325-navigate-within-group.rpp @@ -0,0 +1,368 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAALTQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4zIiwiaWQiOiIyNlFXcnhkeSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6ImRkY2VmMGMzLTJmZjQtNDllYi05NjU3LWNmZWFlNGE3NThhNyIsIm5hbWUiOiJWb2x1bWVzIn0seyJpZCI6IjI3MjQ3 + MGIyLWQ0N2MtNDk1NC1iYzdkLWQwMDYwMTBkYzA0OSIsIm5hbWUiOiJQYWdlcyJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6 + IjZhMDhmNDM3LTc5YmMtNGYyMC04M2Q0LWU5NDJmODUxY2U5MSIsIm5hbWUiOiIxIiwiZ3JvdXBJZCI6ImRkY2VmMGMzLTJmZjQtNDllYi05NjU3LWNmZWFlNGE3NThh + NyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJt + YXhUYXJnZXRWYWx1ZSI6MC4xOSwibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiNTg1YTMxMTYtNTUzNi00MDI5LTg1YWEtYzA3OTBkODgyZGNjIiwibmFt + ZSI6IjIiLCJncm91cElkIjoiZGRjZWYwYzMtMmZmNC00OWViLTk2NTctY2ZlYWU0YTc1OGE3Iiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFRhcmdldFZhbHVlIjowLjMzLCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRh + cmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MH19LHsiaWQiOiI5NTUxODBiMy0xNWUwLTQwZjEtODNiYS0yZWY3MzNjNjBlZDEiLCJuYW1lIjoiMyIsImdyb3VwSWQiOiJkZGNlZjBjMy0yZmY0LTQ5ZWItOTY1Ny1j + ZmVhZTRhNzU4YTciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0s + Im1vZGUiOnsibWF4VGFyZ2V0VmFsdWUiOjAuNTksIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjM5OTFlMTcyLTU5MTctNDc5NS1iM2JkLTMwYjUwNDdj + YWJkMiIsIm5hbWUiOiI0IiwiZ3JvdXBJZCI6ImRkY2VmMGMzLTJmZjQtNDllYi05NjU3LWNmZWFlNGE3NThhNyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBl + IjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI4 + Mzk1YWNkNy1jOGJkLTRmZGYtYmE2Yy1hZmFlNTQ1NzUyODkiLCJuYW1lIjoiRmVlZGJhY2siLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0s + InRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYjY1OGMxYjktN2E4Ni00N2QxLTgxYmYtZGMzNzU0OGRkYjY4IiwibmFtZSI6Ik5hdiB3aXRoaW4g + dm9sdW1lcyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjEyfSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6LTAuMTIsIm1heFN0ZXBTaXplIjotMC4xMn0sInRhcmdldCI6eyJ0eXBlIjozNywiZnhB + bmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImdyb3VwSWQiOiJkZGNlZjBj + My0yZmY0LTQ5ZWItOTY1Ny1jZmVhZTRhNzU4YTcifX0seyJpZCI6IjljMDUxNTBhLTY1MmQtNDQ0ZC1hZTQ5LTU5Y2RiNTg4NjBjZCIsIm5hbWUiOiJOYXYgd2l0aGlu + IHBhZ2VzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTV9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjotMC4xMiwibWF4U3RlcFNpemUiOi0wLjEyfSwidGFyZ2V0Ijp7InR5cGUiOjM3LCJmeEFu + Y2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiZXhjbHVzaXZpdHkiOjEsImdy + b3VwSWQiOiIyNzI0NzBiMi1kNDdjLTQ5NTQtYmM3ZC1kMDA2MDEwZGMwNDkifX0seyJpZCI6ImQ0MWE0MjY0LTA3OTQtNDEwZC1iMmMzLWU5YmI1Y2ZlMDlhZCIsIm5h + bWUiOiIxIiwiZ3JvdXBJZCI6IjI3MjQ3MGIyLWQ0N2MtNDk1NC1iYzdkLWQwMDYwMTBkYzA0OSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhUYXJnZXRWYWx1ZSI6MC4xMywibWF4U3RlcFNpemUiOjAuMDV9LCJ0 + YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiRjRERDg4NTQtNUEyNS00OTY1LTg3M0UtRTkxNDYxMTE5QkYxIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3Qi + OnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiZTM4YzljNDItYzFmYi00YzkxLWE2NzEtZWVjMTU4MGNi + Y2IzIiwibmFtZSI6IjIiLCJncm91cElkIjoiMjcyNDcwYjItZDQ3Yy00OTU0LWJjN2QtZDAwNjAxMGRjMDQ5Iiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjEyLCJtYXhUYXJnZXRWYWx1 + ZSI6MC40MSwibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiN0Q0Rjk3OUMtQzE2QS00QkRELUE1RkMtNkZGNDQxRDAwNDU2 + IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiZjhi + ZGRiZDgtMGNhOS00ZjY3LWI5MTAtNTJiZTI1ZjYwM2Q4IiwibmFtZSI6IjMiLCJncm91cElkIjoiMjcyNDcwYjItZDQ3Yy00OTU0LWJjN2QtZDAwNjAxMGRjMDQ5Iiwi + c291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1pblRh + cmdldFZhbHVlIjowLjM4LCJtYXhUYXJnZXRWYWx1ZSI6MC42OSwibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiQ0UyQzZG + NTgtOURDQy00RTAxLUIyNjYtMUU3MTczRjZGNkU1IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiMDM3Mjk3ZjAtN2I0MC00ZWU1LThhY2YtMmYwMWIzOTViZjI0IiwibmFtZSI6IjQiLCJncm91cElkIjoiMjcyNDcwYjIt + ZDQ3Yy00OTU0LWJjN2QtZDAwNjAxMGRjMDQ5Iiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjY0LCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQi + OiI3REMzNTMzOS1FRThELTRCMkQtODAwMi1DQTQxMzYxNzk0RUYiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6IjBjMDI5MzYzLTcxYzctNDExNS1iOGUwLTcyNDVhYzFiNmQ0YiIs + Im5hbWUiOiJFbmNvZGVyIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00YmFmLWE4MmQt + MmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24ifX0seyJpZCI6IjI3NTIxZWYwLTVjZjgtNDVlOS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUiOiJFbmNvZGVyIDEvMiIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgxZDMtZDYwNy00MzI5LTlkOTEtYTZiODg4YjBmYWFmIiwi + bmFtZSI6IkJ1dHRvbiAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYjgyZDc3NTItZDIyOS00NDhmLWJkNDItYWY2MWI0 + NTllNDgxIiwibmFtZSI6IkJ1dHRvbiAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6ImEzYzI3YzAyLWM1MmQtNDcxYy1iNzA2LTI3YjYyZTJhNWRiZSIsIm5hbWUiOiJFbmNvZGVyIDEvNCIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkx + ZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQzNi05MTM0LTQ4OGE2ZmViMWZiZiIsIm5hbWUiOiJFbmNv + ZGVyIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNTBmM2Q1NmQtMGY2 + My00MDg2LTk1OTItYjNjZTkxM2RiMzgxIiwibmFtZSI6IkJ1dHRvbiAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjEsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5h + bWUiOiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiMDM5 + Zjc5OWItNTM3Zi00Mjc3LWFiYzEtMDNjNTA2ODI3MDgzIiwibmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0 + ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjQ1YmJiZDVkLTM0ZjctNGFiOC1iNDk2LWVlYjljZjYw + MmE3OCIsIm5hbWUiOiJFbmNvZGVyIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7 + ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTctMjA1NTc2NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2 + LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImQyODQ5ZWM3LWM4OTUtNDRlZS1iZmZh + LTM0ZWVmNTBmOTc3YyIsIm5hbWUiOiJFbmNvZGVyIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjd9fSx7ImlkIjoiODAxOGMyNTktNmY3Ni00MTMzLTkwNDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1dHRvbiAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwi + bnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEt + NGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MywiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiMTYwMTNjNDgtMmE0OS00NjcyLWJmM2EtOTVlYWUwNWVmM2E3IiwibmFtZSI6IkJ1dHRvbiAzLzEiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjY0MGVi + MmE0LTczNWQtNGQ5MS1hZTViLTYzNjcxZGZhYjVkNyIsIm5hbWUiOiJFbmNvZGVyIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3Rl + ciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJp + ZCI6ImM4NjVjNTAzLTI1ZmMtNGI4MS1iYmM4LTA3MzExMDVhMjY1NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEw + LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzktOWI2NC1hOTA5ZTU1OTM1NGIiLCJuYW1lIjoiQnV0dG9u + IDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQxMzEtYmVkOS00YmE3LWE2MDQtNDM1Y2YwNDM2YzkxIiwi + bmFtZSI6IkJ1dHRvbiAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiM2I1ZmMxZTgtNDJkYi00NTgxLTk0N2UtNWRmYTNkM2MwYmY0IiwibmFtZSI6IkVuY29kZXIgNC8xIiwic291cmNl + Ijp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJmZS04OTQyLTc1 + OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6ImEzODQzNWMwLTNhYzQtNDE3NC1iMWIxLTc4MWVmZjc5ODhjNCIsIm5hbWUiOiJFbmNvZGVy + IDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5MTQ1YjhhNy0wMjFl + LTQ2ZTktYmMyNC01ODhiNWI3MGUzY2QiLCJuYW1lIjoiQnV0dG9uIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJu + YW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoi + MjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMxMjU4NGEzIiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hh + cmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiNTVhYTIzYjAtYjgyMC00ZWJlLTliMTItOWZl + NDA4NDJlZjI3IiwibmFtZSI6IkVuY29kZXIgNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjE1fX0seyJpZCI6IjY3NzJhOWUzLTI4OGItNGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51 + bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjI3YmYyMTUyLWU3ZjQt + NDVlNS05Yjk4LWRjYWU4OTY1YjkxZiIsIm5hbWUiOiJMZWZ0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNn19LHsiaWQiOiJlZTlkZjU5MC02MWNiLTQ3NDYtYTMxMy03NzM1NmU1OGIzNWUiLCJuYW1lIjoi + UmlnaHQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxN319LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMs + Im51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fX0seyJpZCI6ImE1N2EyNzg3LTM5 + MTktNDZmYi04ZmRkLTE3ZmI4M2Y0YmFmMSIsIm5hbWUiOiJSaWdodCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE5fX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJtaWRpLWZpZ2h0ZXItdHdpc3RlciIsIm1h + aW4iOnsiYWN0aXZlTWFwcGluZ0J5R3JvdXAiOnsiMjcyNDcwYjItZDQ3Yy00OTU0LWJjN2QtZDAwNjAxMGRjMDQ5IjoiZDQxYTQyNjQtMDc5NC00MTBkLWIyYzMtZTli + YjVjZmUwOWFkIn19fQ== + AAAQAAAA + > + FLOAT 174 537 721 792 + FXID {07FA42FD-75AD-4AB3-8306-327DBB06504A} + WAK 0 0 + > + > + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-326-parameter-reset.rpp b/plugin-reaper-realearn/resources/test-projects/issue-326-parameter-reset.rpp new file mode 100644 index 0000000..2b388e2 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-326-parameter-reset.rpp @@ -0,0 +1,486 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA2oUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcmMuNSIsImlkIjoidWdXQllOdnYiLCJzZW5kRmVlZGJhY2tPbmx5SWZBcm1lZCI6dHJ1ZSwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwibmFtZSI6IkZh + ZGVyIn0seyJpZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsIm5hbWUiOiJWLVBvdCJ9LHsiaWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFj + Zi1hN2I4YmJiYTM5OTIiLCJuYW1lIjoiVi1TZWxlY3QifSx7ImlkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwibmFtZSI6IkZhZGVyIFRv + dWNoIn0seyJpZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1 + ZDMtMzMyNjFlYWI4MzYzIiwibmFtZSI6Ik11dGUifSx7ImlkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4IiwibmFtZSI6IlNvbG8ifSx7Imlk + IjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwibmFtZSI6IlJlY29yZC1yZWFkeSJ9LHsiaWQiOiIzMWJmODJmZC0xZmNhLTQ2ZTgtYTgwYS1i + YjZjOGI4OTE3ODgiLCJuYW1lIjoiVi1Qb3QgTEVEcyJ9LHsiaWQiOiI3YjYxMDA4OS02ZTE0LTQxMmItYWEyNi1mY2ViNmFjOGJlMjIiLCJuYW1lIjoiTENEIn1dLCJj + b250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiZTM4OTdlNDktYThmMi00YzVmLWFjNTUtYTQ2NGRjMGJkOWE1IiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1i + YWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXNlbGVjdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4N2ZjNmVhMi1lODdk + LTQ2ZmUtYmJmZS0wZWRjNzRhMmE1NzMiLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MzMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Yt + c2VsZWN0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNkMWFiM2NlLTE0MGQtNGZmYy1hYTI2LTFhNTYzMGQ2MDBjZiIsImdyb3VwSWQiOiI4MWMz + M2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNCwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1zZWxlY3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiN2U5OTYwNjktZmFjNy00ZDI0LWFiZmEtODM0ZWQ3MDU3YzMwIiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNC92LXNlbGVjdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhNDE1NGU5MS0wOGYzLTQwYWItYWQ3Yi02Yjk5MGU0NGQyODMi + LCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzYsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0In0sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImFmNWExOWRmLWYxZmYtNDI0MS1hYmU2LTFiMjk2OWY2MGUxOCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4 + YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYjliNTIzMjgtNzFlZC00ODJhLWEx + YmEtMWY5YjhhY2NhYjNlIiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjM4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXNlbGVjdCJ9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxYWYzYjYxMS1jZWQ4LTQyMDgtOTA5YS02NGI3M2U2YTUxOWIiLCJncm91cElkIjoiODFjMzNhMDItNmVk + OC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjMwMTdj + MGU0LWEwMzktNDA4ZC05YWM1LWUzYjQxMTdiODk2ZCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gxL2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjMxNzcyOGY1LTVlNDQtNDE1Ny05MTg2LTUwOTk4MjM2MjEwZCIsImdy + b3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDUsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6IjI5YTczZDRhLTkwZjMtNDJjMC1iNWJhLTdlMzM5MGI4MDlkMyIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVl + MTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjBkMTNmOGJlLTljZGQtNDEw + NS05ZTYyLTRmNzg1YjM5NWI3ZiIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoxMDcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVy + L3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjE4ZTZlNWQzLTUxMDktNGE1Mi04ZjRiLWIzZjk0NTRlMzAxOSIsImdyb3VwSWQiOiJjOTk5 + ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDgsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImQ1NWJhZjdiLWZkYmEtNGM0ZS1iZjFiLTExZTg0NzQ2MmI1NSIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJkNTY0OWFjLWQxZjAtNGZmOS1hNmEyLTQ2MDc1 + ZjY1ZDM5YyIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoxMTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNoIn0sImZl + ZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjAyZTA1YjAyLTk3YzEtNGVmMy1hNWMxLTY4MjFiM2EzYTI1NCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQx + NmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImM0NTlm + MzdhLTZjOGEtNDQ4My1iODQwLTc2ZmJhYjI4YTU0MCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDEvc2VsZWN0In19LHsiaWQiOiJhYTZhYTVmZi0zY2FjLTQ2MGItOGE1Zi03NThiNmI5NmZiZjciLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGIt + NDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NlbGVjdCJ9fSx7ImlkIjoiOGMzMmVkMTQtZWI0MC00NDBiLWE3NWEtM2Y4ZjIwNGE5YjkyIiwiZ3JvdXBJ + ZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI2LCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zZWxlY3QifX0seyJpZCI6Ijg3OTYyZWMyLWU2YjUtNGFi + OS04ZDI4LTQyZWI1ODc0YjljOSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoyNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0 + In19LHsiaWQiOiJkMGE2NmZiYi03ZTlmLTQxYjQtYWRhNS01NDJjZGNkMmE1Y2UiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZi + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g1L3NlbGVjdCJ9fSx7ImlkIjoiM2ZmNGE1M2UtMmZkOS00YzQ1LTk0YzktMGM3ODI4MzE1YWExIiwiZ3JvdXBJZCI6ImVkZGZmM2Y2 + LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zZWxlY3QifX0seyJpZCI6IjAyZjMyNGZjLWMwZDItNGQ0MS1iMmM0LTBjMGQ4 + OWJiMWFlOSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjozMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2VsZWN0In19LHsiaWQiOiIz + MGFiNWU4NS04NTMyLTQzNjctODA2ZS1lMTA0ZTM1NWY0ZWEiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g4L3NlbGVjdCJ9fSx7ImlkIjoiYjk2YjExYjMtNGEzMS00OGVjLThjMzAtZmFmMTYwNjZiYzM5IiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1h + OTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFk + ZXIifX0seyJpZCI6ImZiZjg0NDIwLTdkOTUtNDJjZC05ZDZiLTdiOWM2ZGMzMTM2MiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIi + OjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjE3NWJlYWE5LTgxNDgt + NGVlYS1iMTgxLTM4N2YwYjYzYTE5MyIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjoxMTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9m + YWRlci90b3VjaCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlNTE1ODVkZS0xYzMzLTQ4NDAtOTE5ZC1iYmZiNjVjZGEyNDMiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJtYXJrZXIifX0seyJpZCI6IjNlZTQwOGRhLTBmMjAtNDk4MC05YjUzLWU3ZTYyNjQyZmQzZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjc0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlYWQifX0seyJpZCI6IjU0NTRm + YzU0LTFjMjktNDZhNC05MDc2LTgyMGZjOWMwMTgzMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc1LCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIn19LHsiaWQiOiJhZTEzNjQ5My1kNDU2LTQ4ZDAtOTAwNS1mNjNhMTVl + MDNkZmEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0In19LHsiaWQiOiIzM2Y4MTk4Yy0yMDQ5LTRkYmMtYTRmMy0xMTkyZDM2ZWMzMmYiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aC1yaWdodCJ9fSx7ImlkIjoiN2IyNTlmNzYtYjAxZS00NmE0LWJmYWItNjhjNTM2YWJjZDUwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + NDYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0In19LHsiaWQiOiJlNmRh + OWM3My03ZmE4LTQ1MGUtODM1Zi00MDM0NDM1MTcyYmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0In19LHsiaWQiOiIzNDE3ODBjMy01OTM0LTRhMjQtOWY1My0x + NWQ0N2IzYzBiY2YiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQifX0seyJpZCI6ImY2YmFmZjFhLWMxY2EtNDA3OS1hMDU4LTFkZjJjNzYwYTYwZCIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImZhc3QtZndkIn19LHsiaWQiOiI3YTNmYTQ3ZC05YTVmLTRiNmUtYjk0YS1lN2E4MjQ1MjRmOTEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjo5NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5In19LHsiaWQiOiJjNmQ2 + ZTQ3ZC0zNjU4LTRkNjYtODZlYy0zMTA2ZjY3N2YzNTAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MywiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzdG9wIn19LHsiaWQiOiI3MWQ0MDcwMS1jZjA5LTQxZTUtYmY1Yy1kYTM0Zjcw + YzY2ZGMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNvcmQifX0seyJpZCI6ImJhYmE0ZGNmLTY1NDctNDY4MC1iYWI5LTNmMWNlNzRlZDFlMSIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjg2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN5 + Y2xlIn19LHsiaWQiOiJlZTM0Yzc2NS05N2I5LTQ3YzEtYjBhYy04OTYzNTcxMzAxMzciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDAs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoiem9vbSJ9fSx7ImlkIjoiYTAwOWE2M2QtZWJj + ZC00ZGRjLTk0ZGMtN2ZiZTJjYjczNmM5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNjcnViIn19LHsiaWQiOiI2OGM5MjRmZS1lY2QzLTRhOGMtYTNiMC0yMWY5MTE2NzUyOTAi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCJ9fSx7ImlkIjoiNDkzNzQyMGMtNWFhZS00Yzc3LWJiMDktY2E1MTg2NTc2OWFlIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6OTksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vy + c29yLXJpZ2h0In19LHsiaWQiOiI2YzM4MzQyMC02YzM1LTQ4NDktOGE0ZC0xYWI1ZWQ0OWE5NDYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo5NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAifX0seyJpZCI6Ijlj + OTgxZTYxLWYzMDAtNDM4Yy05YTJkLTRiMTliMGY0NGVlOSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3LCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIn19LHsiaWQiOiJjOTFmN2ZjNS04MDM5LTQ5MWYtOWQ3 + Ni1lNDMzMDk5OTQ1NDAiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyIn19LHsiaWQiOiI0OGIzYjIwNS1mMjFlLTQwZDgtOTYwMS03MWUzOGIwYTUyYWUi + LCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjEsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIn19LHsiaWQiOiJlMTljYzdjYy05ZWJhLTQzYzYtYjE1Yy0wMWRkZjI3NzIwYjkiLCJncm91cElkIjoiNzkx + NGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gzL2ZhZGVyIn19LHsiaWQiOiI5YTg1NmUyMC1kOWJlLTRiMzMtOGZhZi02MGViZWQ1MTc3NjYiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2 + LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2Zh + ZGVyIn19LHsiaWQiOiIzZDM0ZGZhMi1mNTZkLTQyNjgtODVmMS01ZWJkODNiYmY1MWIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0 + YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIn19LHsiaWQiOiIz + NDRmNTgwMS03MmJiLTQ3N2YtOGUxZC01NjI4YWFiNWY0YzEiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7 + InR5cGUiOjMsImNoYW5uZWwiOjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIn19LHsiaWQiOiJlMTQ0MjY3ZS04ZmQ2LTQy + MGItYjg4Zi1mMjkzNGU4NmEyYTQiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5u + ZWwiOjYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIn19LHsiaWQiOiI4NjZlZmI2MC1jYTRlLTRmNGQtOWM1Yy00MzIyOTQz + M2I1NjkiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjcsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyIn19LHsiaWQiOiIwMzQ1ZDRkNy0wNTkyLTQzOWEtODJiMS1hMjFmMTE2M2ZiMGUiLCJncm91cElk + IjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJhY3RlciI6MiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzVlOTJlY2MtMGZhOC00OTM5 + LWJlYzItZWU2ZGY0Y2VmOTM3IiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNv + dXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gxL3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNGFhYzgyOGMtZWRiMC00YmFmLTllOWQtYjIwYmJlNGZlZTUwIiwiZ3JvdXBJ + ZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjIsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjA4MGFhMWZlLWZiZWMtNGJi + MS1iMWRhLTIzNjFkMjgwNjc2MyIsImdyb3VwSWQiOiIzMWJmODJmZC0xZmNhLTQ2ZTgtYTgwYS1iYjZjOGI4OTE3ODgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjo0OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJtYXhT + b3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMi92LXBvdCJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNhMWZmOWRjLTI4NmMtNGUyOC1hNDkwLTNiOGE1Mjk3YTM3NyIsImdyb3Vw + SWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjoyLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlZWI5ODYwNS1lMGU4LTRi + NWEtOTFjMy0wMmYwMjhmODQzMDQiLCJncm91cElkIjoiMzFiZjgyZmQtMWZjYS00NmU4LWE4MGEtYmI2YzhiODkxNzg4Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6NTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4yNTk4NDI1MTk2ODUwMzkzNSwibWF4 + U291cmNlVmFsdWUiOjAuMzM4NTgyNjc3MTY1MzU0MzR9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDMvdi1wb3QifSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI1M2M2ZjJiNi1iZDRlLTQ5NzktODkwOS0yN2ZlZDU3MTg4YzIiLCJncm91 + cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTksImNoYXJhY3RlciI6MiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODdlYTdjMTYtOTEwMC00 + Njg1LWE0MGItMzk5ZTQ5NmI5ZTU0IiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjUxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1h + eFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g0L3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiM2U2YTE3MDItN2I0MC00N2M2LWE0NDktZDcxOGRlZWU1YTVkIiwiZ3Jv + dXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjQ3YjQwYzUyLWI4M2Yt + NGM5Yy1hYWM5LTZmM2ZiZGQwMDk1MyIsImdyb3VwSWQiOiIzMWJmODJmZC0xZmNhLTQ2ZTgtYTgwYS1iYjZjOGI4OTE3ODgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjo1MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJt + YXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNS92LXBvdCJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImZhYTUyYjdkLTVjNDQtNDAyOS1iMjVkLTdmZmE5Y2U0NjVlMCIsImdy + b3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjoyLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiM2YzMTJjYS04YjQ2 + LTQwMDEtOWE1My1lMGE2NzBlMzNkYTkiLCJncm91cElkIjoiMzFiZjgyZmQtMWZjYS00NmU4LWE4MGEtYmI2YzhiODkxNzg4Iiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NTMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4yNTk4NDI1MTk2ODUwMzkzNSwi + bWF4U291cmNlVmFsdWUiOjAuMzM4NTgyNjc3MTY1MzU0MzR9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDYvdi1wb3QifSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyMDIwYzQ3ZS01ZDczLTRlMDgtODEyYS1lNzNkZTg3MmUwN2IiLCJn + cm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjIsImNoYXJhY3RlciI6Miwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOTAzZTkzNWMtMjVh + Yi00ZmIwLTkwYTktZDllMWMxZTAzNDkwIiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjU0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUs + Im1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g3L3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZWEyMjg3NTUtMWJmYy00NDg1LWJkNTktMDVjZGQyMDc5ZTI4Iiwi + Z3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjE2ZjA1MTVkLWFk + NzctNDQzZC05N2RjLTZhM2ZjNzkyMzNhYSIsImdyb3VwSWQiOiIzMWJmODJmZC0xZmNhLTQ2ZTgtYTgwYS1iYjZjOGI4OTE3ODgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjo1NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1 + LCJtYXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoOC92LXBvdCJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhhYmQ1NTM1LTY2MWUtNGI2YS1hM2JlLTJhMjMyZDYxMTE4ZiIs + Imdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbXV0ZSJ9fSx7ImlkIjoiNDE3ZWQwN2QtY2I4 + OC00OTBjLWJlNzctMWU5NGQzMTBmZmI1IiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjE3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9t + dXRlIn19LHsiaWQiOiIyNzVhMjk1Ny05MWU3LTQ4ZTctYWFmNS0zZGQwNmZjYjY4MDIiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4 + MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gzL211dGUifX0seyJpZCI6ImY1ZmE0ZGM1LWViOTgtNGExYi05MWZlLWE2NDEzMTJmYWY0NyIsImdyb3VwSWQiOiI3MWI1Y2Rk + OC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbXV0ZSJ9fSx7ImlkIjoiYTEwNDlkOTgtNGNhZi00MmIzLWJkMjAtMDliYjRj + YmIxMTRiIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjIwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIn19LHsiaWQiOiI4Njkx + NTQ3MS1hNDViLTQzMTUtOTMwMS03MjI3ZGU3YzAwM2QiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g2L211dGUifX0seyJpZCI6ImI0NDI4YTcyLWE1YWItNDgxMi1iM2FkLTUxODhkNzgwYjY3YiIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0z + MzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbXV0ZSJ9fSx7ImlkIjoiN2FmOGMwNmMtODkzMi00NWM0LTlkOTQtZWE5ZjZhNzZlYTM2IiwiZ3JvdXBJZCI6 + IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tdXRlIn19LHsiaWQiOiJlNTBlZGFjNS0yNGNlLTQ4MTUtYmEz + My03NTM5MGUxYTIwOWIiLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyJ9fSx7Imlk + IjoiNTE1ZWYzYzItMTY2YS00ZTM1LTg3ZjgtZjk4NWMzMjlmZWM0IiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gyL3NvbG8ifX0seyJpZCI6IjhkZTIyYzJlLTI1MDAtNGM4Mi05YzdhLWQyOWJjZTI4YzUzOCIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUt + OTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc29sbyJ9fSx7ImlkIjoiZTc3ZjA5NWItYzY2MS00MGQyLTkwMmItOWI3YTBhYWMxMTM0IiwiZ3Jv + dXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zb2xvIn19LHsiaWQiOiIxMGZmZjM4MS1hNzM2LTQy + NDAtYTExOC0zYmY1MTgzZDIzYjQiLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NvbG8i + fX0seyJpZCI6ImNkMGE0YzU0LWYxMzEtNGY3Mi1iYzlkLWIzMDc4Njk0MjNiNiIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyJ9fSx7ImlkIjoiN2RlNWY5ZTYtODU3OC00ODY0LWI5MGYtZjkwYzZhMjk3NjY0IiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4 + MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zb2xvIn19LHsiaWQiOiI4MjQyOTE1NS02OGZjLTQ0OGMtODM0NC05MzJjODU0OTU2 + ZWYiLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTUs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NvbG8ifX0seyJpZCI6IjE1ZDI4MjQx + LTA5OTAtNDU0NS04Y2Q3LWJkMTkwZWJmMjc0ZiIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + MS9yZWNvcmQtcmVhZHkifX0seyJpZCI6Ijc3N2Q5ZDZlLWExODQtNGI1OC1hZTA4LWIzYjY1MjFkMmRlOSIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNk + NS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9yZWNvcmQtcmVhZHkifX0seyJpZCI6IjM0Mjg1YzIyLTM0OTEtNGY1MC04ZDZiLWJhYzJkZDI4NGIxNCIs + Imdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImFjYzFm + YWNkLTM5NTQtNGYxNC1hNzc0LWNlNDUwY2MxOWIzNiIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNC9yZWNvcmQtcmVhZHkifX0seyJpZCI6Ijg3YWVjOWIzLWU0MTAtNGQzYy05NzVmLWI1MmFjMTFmMGUwYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYt + OTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImNkMmRmZTY0LTMzYzItNGNjZC04YTdkLWMxNjZiNWZkNDZh + MyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImM5 + OTE1MWM5LWNhMDEtNDg4MC1hZDE3LTVhMWFhNjY4ZmJlMyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoNy9yZWNvcmQtcmVhZHkifX0seyJpZCI6IjI0NGQxMTk5LTgwY2EtNDNmZS1iNzdkLWRhYTNlZTU5NGU4YyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRi + NTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9yZWNvcmQtcmVhZHkifX0seyJpZCI6IjFkNjQzNjYzLWUwOGYtNGIzNi04NjBhLTVhMGQ2OGE1 + OWZiZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5k + ZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoibnVkZ2UiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiODg3ZDgzMTYt + MmZjYS00ZmE5LTlhYTYtYjMxZTE2N2Q1MTRhIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9wIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlfX0seyJpZCI6ImJkZjExMGNhLTg1NzAtNDIxNS1hNWE3LTQ4OGQyNDEwMzc0YSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg4LCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVwbGFjZSIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIwZWVhYjQyNC05MDQzLTQ4YzgtYjRmNi01NGVmYzVlODhkMmMiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo4OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNr + IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjNjZWJhNGQ0LWQ0ZTUtNDc0NS04NDQ1LThjNjU1ZjQwY2I0 + ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijoic29sbyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJlZjUzY2Y3MS0zNDRi + LTQ3OTktYTYxNi01OTY1MDgwZDNhYTMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0s + eyJpZCI6IjkwOWExNDUzLWQ2MzEtNDViMy04ODY3LTU1ZDY1ZGRhYmU2YSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU1LCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiMzBmMTM5YzctYTRlZi00Mjc5LWIyNmItMzY4MmQxNzc3NmM4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6NTYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMyIsInVzZVByb2plY3Qi + OnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI2MDcwMTczYy03YmVhLTQzMDAtOWQzYi02OTczNjI3NjNjMWMiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImY0IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImJkOWUzZjgxLTQ4NTItNDU0NC05ZDhlLTFiOTI0 + OGU2YzU5NiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiM2Y4MDg1ZWQt + NTcxZi00ZTU0LWE4MWYtMDdhMWIxZDc0N2Y0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNiIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZX19LHsiaWQiOiIzNmRjM2ZmNy1hYmMwLTQ1NmItOWM2NC1hOGZlMDY2YTQ3MDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1MywiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImNkZTZjYjQxLWViNmYtNGM1My1hOGIxLTE1YTdkNDU1ODg5NCIsImdyb3VwSWQiOiI3YjYxMDA4 + OS02ZTE0LTQxMmItYWEyNi1mY2ViNmFjOGJlMjIiLCJzb3VyY2UiOnsidHlwZSI6MTEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtaWRpU2Ny + aXB0IjoibnVtID0geSAqIDk5O1xuZDEgPSBudW0gJSAxMDtcbmQyID0gKG51bSAvIDEwKSAlIDEwO1xubXNnX3NpemUgPSA1O1xuMFtdID0gMHhiMDsgXG4xW10gPSAw + eDRiOyBcbjJbXSA9IDB4MzAgKyBkMjsgXG4zW10gPSAweDRhOyBcbjRbXSA9IDB4MzAgKyBkMTsiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNz + aWdubWVudCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWFja2llLWNvbnRy + b2wiLCJwYXJhbWV0ZXJzIjp7IjAiOnsidmFsdWUiOjAuNTY5MDAwMjR9fSwiY29udHJvbGxlclBhcmFtZXRlcnMiOnsiOTkiOnsidmFsdWUiOjAuNTU5fX19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {5E511124-703D-4124-9DAE-BAF5C7DC6D7F} + PARM_TCP 0 199 + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-330-group-interaction.rpp b/plugin-reaper-realearn/resources/test-projects/issue-330-group-interaction.rpp new file mode 100644 index 0000000..7e48bcd --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-330-group-interaction.rpp @@ -0,0 +1,467 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAASVkAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4yIiwiaWQiOiJSSVF3RUlpViIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6IjVkZTFhNzNhLWU0YWEtNGU5ZS04ZGUxLTE0MmIxNGMxNDBmYiIsIm5hbWUiOiJJbnZlcnNlIGNvbnRyb2wgMiJ9LHsi + aWQiOiI1NWRiZWIwZi1mZjM4LTRmYWQtOTUwMi1kNDI2MGFiYmU4OTIiLCJuYW1lIjoiSW52ZXJzZSBjb250cm9sIDEifSx7ImlkIjoiNGU3NjQ0ZWItMGVmNy00ZTNi + LWFhZDgtNzFlNmNlYTA2MWZlIiwibmFtZSI6IkludmVyc2UgdGFyZ2V0IHZhbHVlIn0seyJpZCI6IjRkYmFkNDVkLTc3NWItNDRjOS1iYWFjLTMyNjExNGE4NzNjNyIs + Im5hbWUiOiJTYW1lIGNvbnRyb2wgMSJ9LHsiaWQiOiJmMjFiODY5OS1mMDFiLTRkYzEtOTQ3Yi04YjZkOGYzZjkxOTciLCJuYW1lIjoiU2FtZSBjb250cm9sIDIifSx7 + ImlkIjoiY2YxNjM4MzUtMGQ0OC00ZDcyLTk2YmEtNjcwZTdjYTg4MjgxIiwibmFtZSI6IlNhbWUgdGFyZ2V0IHZhbHVlIn0seyJpZCI6IjBkOTkxMjI2LWZkNjItNDk1 + Yy05OTcxLTA4OTQwOGY4NTg4MSIsIm5hbWUiOiJJbnZlcnNlIHRhcmdldCB2YWx1ZSBhZnRlciB0aW1lb3V0In0seyJpZCI6IjVhZmNjYjZiLWNhYjktNDZjOS1iNWEw + LTc2NjMzOGNhYWYzZSIsIm5hbWUiOiJTYW1lIHRhcmdldCB2YWx1ZSBkb3VibGUgcHJlc3MifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6 + W3siaWQiOiIzMWQ1ZjY4Mi02NTcwLTQwMjktODU5Mi03ZDhiYmZiNGRiZDAiLCJuYW1lIjoiMSIsImdyb3VwSWQiOiI1NWRiZWIwZi1mZjM4LTRmYWQtOTUwMi1kNDI2 + MGFiYmU4OTIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4VGFyZ2V0VmFsdWUiOjAuMjYsIm1heFN0ZXBTaXplIjowLjA1 + LCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS1jb250cm9sIn0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiIyRTgxODY0NS02MDM4LTQyQzMtQjI4My1D + MTQyOEIyMzU3RkQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19 + LHsiaWQiOiI1YWIzYTJlOC00ODc0LTQ0YzAtYWIwNC05N2I2NmU1OWI2NjciLCJuYW1lIjoiMiIsImdyb3VwSWQiOiI1NWRiZWIwZi1mZjM4LTRmYWQtOTUwMi1kNDI2 + MGFiYmU4OTIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxM30sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNzIsIm1heFN0ZXBTaXplIjowLjA1 + LCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS1jb250cm9sIn0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiIxMTkzQTZEQS0wNzMzLTQ0NDYtOEQ4MS0z + NjdERUY2OENEQ0MiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19 + LHsiaWQiOiJmMWNhZWM3ZS00N2Y5LTQwNjQtYWY3Yy01NjY0Yjk2YWM5ODAiLCJuYW1lIjoiMyIsImdyb3VwSWQiOiI1NWRiZWIwZi1mZjM4LTRmYWQtOTUwMi1kNDI2 + MGFiYmU4OTIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImdyb3VwSW50ZXJhY3Rpb24iOiJp + bnZlcnNlLWNvbnRyb2wifSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6IjBFMUNDMzRCLTZBN0MtNDI1Mi04RjU0LThBOTRFMkZCNzFDMSIsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6ImY0MTgzYzgyLWZkNmYt + NGFiOS05OTRmLTNhYzExYzI1MDI4YSIsIm5hbWUiOiIzIiwiZ3JvdXBJZCI6IjVkZTFhNzNhLWU0YWEtNGU5ZS04ZGUxLTE0MmIxNGMxNDBmYiIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZ3JvdXBJbnRlcmFjdGlvbiI6ImludmVyc2UtY29udHJvbCJ9LCJ0YXJn + ZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiMEUxQ0MzNEItNkE3Qy00MjUyLThGNTQtOEE5NEUyRkI3MUMxIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiZDczMDI0YWUtZTVhZS00OGI5LTk0M2UtMTg3ODM0ZTU0MjAw + IiwibmFtZSI6IjQiLCJncm91cElkIjoiNWRlMWE3M2EtZTRhYS00ZTllLThkZTEtMTQyYjE0YzE0MGZiIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNo + YW5uZWwiOjAsIm51bWJlciI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTV9LCJtb2RlIjp7Im1heFRhcmdldFZhbHVlIjowLjQsIm1heFN0ZXBTaXplIjowLjA1LCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS1jb250cm9sIn0sInRhcmdl + dCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiIyOTRDMEZBMy1FN0JBLTRENDktQkMxRi05NzRFRkIzRTc3MUEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI2ZThiYTNmOS0yYjA4LTRhYTgtOGZjYS05MjE2MjRhOWFkZmUi + LCJuYW1lIjoiMSIsImdyb3VwSWQiOiI0ZTc2NDRlYi0wZWY3LTRlM2ItYWFkOC03MWU2Y2VhMDYxZmUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1heFByZXNzTWlsbGlzIjoyOTksImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSIsImdyb3VwSW50ZXJh + Y3Rpb24iOiJpbnZlcnNlLXRhcmdldC12YWx1ZSJ9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tHVUlEIjoiMkU4MTg2NDUtNjAzOC00MkMzLUIyODMtQzE0MjhCMjM1 + N0ZEIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoi + MjUxNGUwNTYtYjljNi00MTUyLWE3YWMtM2IyMGNkMjg1MTBiIiwibmFtZSI6IjIiLCJncm91cElkIjoiNGU3NjQ0ZWItMGVmNy00ZTNiLWFhZDgtNzFlNmNlYTA2MWZl + Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtYXhQcmVzc01pbGxpcyI6Mjk5LCJi + dXR0b25Vc2FnZSI6InByZXNzLW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10YXJnZXQtdmFsdWUifSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrR1VJ + RCI6IjExOTNBNkRBLTA3MzMtNDQ0Ni04RDgxLTM2N0RFRjY4Q0RDQyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6ImFmMDk4NDI2LTBjZDYtNGE5NC04NjRlLTcxOGE4ZWJmZWIxYyIsIm5hbWUiOiIzIiwiZ3JvdXBJZCI6 + IjRlNzY0NGViLTBlZjctNGUzYi1hYWQ4LTcxZTZjZWEwNjFmZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWF4UHJlc3NNaWxsaXMiOjI5OSwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5IiwiZ3JvdXBJbnRlcmFjdGlvbiI6ImludmVyc2UtdGFyZ2V0 + LXZhbHVlIn0sInRhcmdldCI6eyJ0eXBlIjo4LCJ0cmFja0dVSUQiOiIwRTFDQzM0Qi02QTdDLTQyNTItOEY1NC04QTk0RTJGQjcxQzEiLCJmeEFuY2hvciI6ImlkIiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI1NjY3ZTMyZC0zZDk2LTRjYzgtYTk5 + My1iNDU3Y2I2YTgwNmIiLCJuYW1lIjoiNCIsImdyb3VwSWQiOiI0ZTc2NDRlYi0wZWY3LTRlM2ItYWFkOC03MWU2Y2VhMDYxZmUiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoxNX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1heFByZXNzTWlsbGlzIjoyOTksImJ1dHRvblVzYWdlIjoicHJlc3Mtb25s + eSIsImdyb3VwSW50ZXJhY3Rpb24iOiJpbnZlcnNlLXRhcmdldC12YWx1ZSJ9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tHVUlEIjoiMjk0QzBGQTMtRTdCQS00RDQ5 + LUJDMUYtOTc0RUZCM0U3NzFBIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjB9fSx7ImlkIjoiZTAwZGViNjMtYTFjOC00ZjZlLWFhOWItZDIwYjM3Zjc3ZGE2IiwibmFtZSI6IjEiLCJncm91cElkIjoiNGRiYWQ0NWQtNzc1Yi00NGM5LWJh + YWMtMzI2MTE0YTg3M2M3Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6OH0sIm1vZGUiOnsibWF4VGFyZ2V0VmFsdWUiOjAuMjYsIm1heFN0ZXBTaXpl + IjowLjA1LCJncm91cEludGVyYWN0aW9uIjoic2FtZS1jb250cm9sIn0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiIyRTgxODY0NS02MDM4LTQyQzMtQjI4 + My1DMTQyOEIyMzU3RkQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MH19LHsiaWQiOiIzZTg4ZDNiOS0yZGI4LTQzZWUtYTQyZS1iMDk0MjJhN2MyMjEiLCJuYW1lIjoiMiIsImdyb3VwSWQiOiI0ZGJhZDQ1ZC03NzViLTQ0YzktYmFhYy0z + MjYxMTRhODczYzciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC43MiwibWF4U3RlcFNpemUiOjAu + MDUsImdyb3VwSW50ZXJhY3Rpb24iOiJzYW1lLWNvbnRyb2wifSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6IjExOTNBNkRBLTA3MzMtNDQ0Ni04RDgxLTM2 + N0RFRjY4Q0RDQyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0s + eyJpZCI6ImY3NDhhMDNlLWRmOGEtNGRjYy05MzNmLTgxZWY3M2NmYjliNCIsIm5hbWUiOiIzIiwiZ3JvdXBJZCI6IjRkYmFkNDVkLTc3NWItNDRjOS1iYWFjLTMyNjEx + NGE4NzNjNyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZ3JvdXBJbnRlcmFjdGlvbiI6InNh + bWUtY29udHJvbCJ9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiMEUxQ0MzNEItNkE3Qy00MjUyLThGNTQtOEE5NEUyRkI3MUMxIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiYmM4YzBkZTItYzIzZC00Yjc2 + LTgxZmEtNjcyN2QyZjViYTFkIiwibmFtZSI6IjMiLCJncm91cElkIjoiZjIxYjg2OTktZjAxYi00ZGMxLTk0N2ItOGI2ZDhmM2Y5MTk3Iiwic291cmNlIjp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJncm91cEludGVyYWN0aW9uIjoic2FtZS1jb250cm9sIn0sInRhcmdldCI6eyJ0 + eXBlIjoyLCJ0cmFja0dVSUQiOiIwRTFDQzM0Qi02QTdDLTQyNTItOEY1NC04QTk0RTJGQjcxQzEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJkMzhhMjk3ZS1kYjdkLTRjYmEtYjc4MC01NTljZTMzNDFkOWQiLCJuYW1l + IjoiNCIsImdyb3VwSWQiOiJmMjFiODY5OS1mMDFiLTRkYzEtOTQ3Yi04YjZkOGYzZjkxOTciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX0sIm1v + ZGUiOnsibWF4VGFyZ2V0VmFsdWUiOjAuNCwibWF4U3RlcFNpemUiOjAuMDUsImdyb3VwSW50ZXJhY3Rpb24iOiJzYW1lLWNvbnRyb2wifSwidGFyZ2V0Ijp7InR5cGUi + OjIsInRyYWNrR1VJRCI6IjI5NEMwRkEzLUU3QkEtNEQ0OS1CQzFGLTk3NEVGQjNFNzcxQSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjVhOWFiYWE2LTRlZjgtNGE4MS1iZmZhLTEzYTE4N2JkY2VhNyIsIm5hbWUiOiIx + IiwiZ3JvdXBJZCI6ImNmMTYzODM1LTBkNDgtNGQ3Mi05NmJhLTY3MGU3Y2E4ODI4MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9LCJt + b2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmaXJlTW9kZSI6InNpbmdsZSIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSIsImdyb3VwSW50ZXJhY3Rpb24iOiJzYW1l + LXRhcmdldC12YWx1ZSJ9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tHVUlEIjoiMkU4MTg2NDUtNjAzOC00MkMzLUIyODMtQzE0MjhCMjM1N0ZEIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiOTNjMGNlYmYtN2M1Ni00 + ZGQyLWIxN2EtYzUzZmE3YzViYWM1IiwibmFtZSI6IjIiLCJncm91cElkIjoiY2YxNjM4MzUtMGQ0OC00ZDcyLTk2YmEtNjcwZTdjYTg4MjgxIiwic291cmNlIjp7ImNh + dGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmlyZU1vZGUiOiJzaW5nbGUiLCJidXR0b25Vc2FnZSI6InByZXNz + LW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoic2FtZS10YXJnZXQtdmFsdWUifSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrR1VJRCI6IjExOTNBNkRBLTA3MzMtNDQ0 + Ni04RDgxLTM2N0RFRjY4Q0RDQyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0lu + ZGV4IjowfX0seyJpZCI6IjQ2YTFjMjBlLTRjNjYtNGY5Mi04OTk0LTNkMGU3MjU3ZDY2ZCIsIm5hbWUiOiIzIiwiZ3JvdXBJZCI6ImNmMTYzODM1LTBkNDgtNGQ3Mi05 + NmJhLTY3MGU3Y2E4ODI4MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxM30sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZpcmVNb2Rl + Ijoic2luZ2xlIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5IiwiZ3JvdXBJbnRlcmFjdGlvbiI6InNhbWUtdGFyZ2V0LXZhbHVlIn0sInRhcmdldCI6eyJ0eXBlIjo4 + LCJ0cmFja0dVSUQiOiIwRTFDQzM0Qi02QTdDLTQyNTItOEY1NC04QTk0RTJGQjcxQzEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiIyNTMxNjRlYy01NDMyLTRhNGMtYWE0MC01ZTIyMjIxNTUxMzAiLCJuYW1lIjoiNCIs + Imdyb3VwSWQiOiJjZjE2MzgzNS0wZDQ4LTRkNzItOTZiYS02NzBlN2NhODgyODEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJtb2Rl + Ijp7Im1heFN0ZXBTaXplIjowLjA1LCJmaXJlTW9kZSI6InNpbmdsZSIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSIsImdyb3VwSW50ZXJhY3Rpb24iOiJzYW1lLXRh + cmdldC12YWx1ZSJ9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tHVUlEIjoiMjk0QzBGQTMtRTdCQS00RDQ5LUJDMUYtOTc0RUZCM0U3NzFBIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiNTcyOWU3MjktMzAyZi00OWU1 + LWI2OGUtYTI1ZDI0MTI5ZmQ0IiwibmFtZSI6IjEiLCJncm91cElkIjoiMGQ5OTEyMjYtZmQ2Mi00OTVjLTk5NzEtMDg5NDA4Zjg1ODgxIiwic291cmNlIjp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5QcmVzc01pbGxpcyI6MzAwLCJtYXhQcmVzc01pbGxpcyI6MzAw + LCJmaXJlTW9kZSI6InRpbWVvdXQiLCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10YXJnZXQtdmFsdWUifSwidGFy + Z2V0Ijp7InR5cGUiOjcsInRyYWNrR1VJRCI6IjJFODE4NjQ1LTYwMzgtNDJDMy1CMjgzLUMxNDI4QjIzNTdGRCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYzI3ZGE5N2Mt + YmYzMC00MjU4LTkwNGQtNDVlZWY2MzIwOGViIiwibmFtZSI6IjIiLCJncm91cElkIjoiMGQ5OTEyMjYtZmQ2Mi00OTVjLTk5NzEtMDg5NDA4Zjg1ODgxIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5QcmVzc01pbGxpcyI6MzAwLCJtYXhQcmVzc01p + bGxpcyI6MzAwLCJmaXJlTW9kZSI6InRpbWVvdXQiLCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10YXJnZXQtdmFs + dWUifSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrR1VJRCI6IjExOTNBNkRBLTA3MzMtNDQ0Ni04RDgxLTM2N0RFRjY4Q0RDQyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQ + cm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + NWY0NjgwMWUtNmRiMS00MWUzLThmNjQtMWU2YTJjNmRhMjQ5IiwibmFtZSI6IjMiLCJncm91cElkIjoiMGQ5OTEyMjYtZmQ2Mi00OTVjLTk5NzEtMDg5NDA4Zjg1ODgx + Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5QcmVzc01pbGxpcyI6MzAwLCJt + YXhQcmVzc01pbGxpcyI6MzAwLCJmaXJlTW9kZSI6InRpbWVvdXQiLCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10 + YXJnZXQtdmFsdWUifSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrR1VJRCI6IjBFMUNDMzRCLTZBN0MtNDI1Mi04RjU0LThBOTRFMkZCNzFDMSIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiNGQ1ZDZkNmYtNjhlZS00OTgyLThiZDQtZTMzMzdmYWI5MzlhIiwibmFtZSI6IjQiLCJncm91cElkIjoiMGQ5OTEyMjYtZmQ2Mi00OTVjLTk5NzEtMDg5 + NDA4Zjg1ODgxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5QcmVzc01pbGxp + cyI6MzAwLCJtYXhQcmVzc01pbGxpcyI6MzAwLCJmaXJlTW9kZSI6InRpbWVvdXQiLCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoi + aW52ZXJzZS10YXJnZXQtdmFsdWUifSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrR1VJRCI6IjI5NEMwRkEzLUU3QkEtNEQ0OS1CQzFGLTk3NEVGQjNFNzcxQSIsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiYTVkMjU2NWMtNDhkYS00NjZhLTk4MGUtNzg0N2NhODk0NGNlIiwibmFtZSI6IjEiLCJncm91cElkIjoiNWFmY2NiNmItY2FiOS00NmM5 + LWI1YTAtNzY2MzM4Y2FhZjNlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZpcmVN + b2RlIjoiZG91YmxlIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5IiwiZ3JvdXBJbnRlcmFjdGlvbiI6InNhbWUtdGFyZ2V0LXZhbHVlIn0sInRhcmdldCI6eyJ0eXBl + Ijo3LCJ0cmFja0dVSUQiOiIyRTgxODY0NS02MDM4LTQyQzMtQjI4My1DMTQyOEIyMzU3RkQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI0ZDAwMTk2Ny0zNGJlLTQ5YjAtYmU5ZC1lYjMwMWRkNDg4MWUiLCJuYW1lIjoi + MiIsImdyb3VwSWQiOiI1YWZjY2I2Yi1jYWI5LTQ2YzktYjVhMC03NjYzMzhjYWFmM2UiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJt + b2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmaXJlTW9kZSI6ImRvdWJsZSIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSIsImdyb3VwSW50ZXJhY3Rpb24iOiJzYW1l + LXRhcmdldC12YWx1ZSJ9LCJ0YXJnZXQiOnsidHlwZSI6NywidHJhY2tHVUlEIjoiMTE5M0E2REEtMDczMy00NDQ2LThEODEtMzY3REVGNjhDRENDIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiMTQxNmRmNWItYzgzMC00 + OWE5LTg1MGMtNzk1YjdmZDg3ZWM0IiwibmFtZSI6IjMiLCJncm91cElkIjoiNWFmY2NiNmItY2FiOS00NmM5LWI1YTAtNzY2MzM4Y2FhZjNlIiwic291cmNlIjp7ImNh + dGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmlyZU1vZGUiOiJkb3VibGUiLCJidXR0b25Vc2FnZSI6InByZXNz + LW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoic2FtZS10YXJnZXQtdmFsdWUifSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrR1VJRCI6IjBFMUNDMzRCLTZBN0MtNDI1 + Mi04RjU0LThBOTRFMkZCNzFDMSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0lu + ZGV4IjowfX0seyJpZCI6IjJkNTFhMWI2LTA5OTQtNDM3Yy05YTJlLTk0ZjBiY2NmNGQwMyIsIm5hbWUiOiI0IiwiZ3JvdXBJZCI6IjVhZmNjYjZiLWNhYjktNDZjOS1i + NWEwLTc2NjMzOGNhYWYzZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxM30sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZpcmVNb2Rl + IjoiZG91YmxlIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5IiwiZ3JvdXBJbnRlcmFjdGlvbiI6InNhbWUtdGFyZ2V0LXZhbHVlIn0sInRhcmdldCI6eyJ0eXBlIjo3 + LCJ0cmFja0dVSUQiOiIyOTRDMEZBMy1FN0JBLTRENDktQkMxRi05NzRFRkIzRTc3MUEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6IjBjMDI5MzYzLTcxYzctNDExNS1iOGUwLTcy + NDVhYzFiNmQ0YiIsIm5hbWUiOiJFbmNvZGVyIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9fSx7ImlkIjoiZTU1NDNhZDgtOWU4 + MS00YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24ifX0seyJpZCI6IjI3NTIxZWYwLTVjZjgtNDVlOS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUiOiJFbmNvZGVyIDEvMiIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgxZDMtZDYwNy00MzI5LTlkOTEtYTZi + ODg4YjBmYWFmIiwibmFtZSI6IkJ1dHRvbiAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUiOiJFbmNvZGVyIDEv + MyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYjgyZDc3NTItZDIyOS00NDhm + LWJkNDItYWY2MWI0NTllNDgxIiwibmFtZSI6IkJ1dHRvbiAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6ImEzYzI3YzAyLWM1MmQtNDcxYy1iNzA2LTI3YjYyZTJhNWRiZSIsIm5hbWUiOiJF + bmNvZGVyIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiMWM4NmUxMzEt + YTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjEs + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQzNi05MTM0LTQ4OGE2ZmViMWZiZiIs + Im5hbWUiOiJFbmNvZGVyIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoi + NTBmM2Q1NmQtMGY2My00MDg2LTk1OTItYjNjZTkxM2RiMzgxIiwibmFtZSI6IkJ1dHRvbiAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFy + YWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJi + MDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9 + fSx7ImlkIjoiMDM5Zjc5OWItNTM3Zi00Mjc3LWFiYzEtMDNjNTA2ODI3MDgzIiwibmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + Ijo1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjQ1YmJiZDVkLTM0ZjctNGFiOC1i + NDk2LWVlYjljZjYwMmE3OCIsIm5hbWUiOiJFbmNvZGVyIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTctMjA1NTc2NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImQyODQ5ZWM3LWM4 + OTUtNDRlZS1iZmZhLTM0ZWVmNTBmOTc3YyIsIm5hbWUiOiJFbmNvZGVyIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcsImNoYXJhY3RlciI6Mywi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiODAxOGMyNTktNmY3Ni00MTMzLTkwNDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1dHRvbiAyLzQiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MSwibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjI1 + YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjgsImNoYXJh + Y3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiMTYwMTNjNDgtMmE0OS00NjcyLWJmM2EtOTVlYWUwNWVmM2E3IiwibmFtZSI6IkJ1dHRvbiAzLzEiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0s + eyJpZCI6IjY0MGViMmE0LTczNWQtNGQ5MS1hZTViLTYzNjcxZGZhYjVkNyIsIm5hbWUiOiJFbmNvZGVyIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjksImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRv + biAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4Ijo5fX0seyJpZCI6ImM4NjVjNTAzLTI1ZmMtNGI4MS1iYmM4LTA3MzExMDVhMjY1NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzktOWI2NC1hOTA5ZTU1OTM1NGIiLCJu + YW1lIjoiQnV0dG9uIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQxMzEtYmVkOS00YmE3LWE2MDQtNDM1 + Y2YwNDM2YzkxIiwibmFtZSI6IkJ1dHRvbiAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiM2I1ZmMxZTgtNDJkYi00NTgxLTk0N2UtNWRmYTNkM2MwYmY0IiwibmFtZSI6IkVuY29kZXIg + NC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkxNTgwLTk4NmYt + NDJmZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6ImEzODQzNWMwLTNhYzQtNDE3NC1iMWIxLTc4MWVmZjc5ODhjNCIsIm5h + bWUiOiJFbmNvZGVyIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5 + MTQ1YjhhNy0wMjFlLTQ2ZTktYmMyNC01ODhiNWI3MGUzY2QiLCJuYW1lIjoiQnV0dG9uIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFy + YWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQx + NTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMxMjU4NGEzIiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjoxNCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiNTVhYTIzYjAtYjgyMC00 + ZWJlLTliMTItOWZlNDA4NDJlZjI3IiwibmFtZSI6IkVuY29kZXIgNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MywiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJhOWUzLTI4OGItNGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0b24gNC80Iiwic291cmNlIjp7ImNo + YW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjI3 + YmYyMTUyLWU3ZjQtNDVlNS05Yjk4LWRjYWU4OTY1YjkxZiIsIm5hbWUiOiJMZWZ0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIi + OjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNn19LHsiaWQiOiJlZTlkZjU5MC02MWNiLTQ3NDYtYTMxMy03NzM1NmU1OGIz + NWUiLCJuYW1lIjoiUmlnaHQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoxN319LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7 + ImNoYW5uZWwiOjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fX0seyJpZCI6 + ImE1N2EyNzg3LTM5MTktNDZmYi04ZmRkLTE3ZmI4M2Y0YmFmMSIsIm5hbWUiOiJSaWdodCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTMsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE5fX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJtaWRpLWZpZ2h0ZXIt + dHdpc3RlciJ9 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {6F4E843F-E548-4AFB-AFC3-8DDEFAB7B8FD} + WAK 0 0 + > + > + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-331-last-touched.rpp b/plugin-reaper-realearn/resources/test-projects/issue-331-last-touched.rpp new file mode 100644 index 0000000..2dab40b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-331-last-touched.rpp @@ -0,0 +1,226 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.53061236608708 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAdCQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcmMuNSIsImlkIjoiMkNkSTBqNEQiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJmNzYxYjUyYi1iMDM4LTQwMDktYWU0OC02MzAyZjA2OTcxN2MiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00 + MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJl + NTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIg + MS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQz + MjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6 + IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1 + Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJl + IiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQi + OiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNo + YXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4 + YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1i + ZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIy + LTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQt + MzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoz + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6 + eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoi + ZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hh + cmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIs + InNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9 + fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0 + dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJu + YW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDll + NTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMv + NCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRi + YTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1l + IjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMw + OTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFj + dGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZm + Nzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEz + fX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJl + ciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMy + ZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFh + MjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFj + dGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9 + fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEz + LTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVy + IjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1p + ZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOAT 1297 397 1436 1514 + FXID {D95CDFE2-A95E-49D0-86F0-A860CDCF1310} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-347-reascript-button-toggle-feedback.rpp b/plugin-reaper-realearn/resources/test-projects/issue-347-reascript-button-toggle-feedback.rpp new file mode 100644 index 0000000..54446ee --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-347-reascript-button-toggle-feedback.rpp @@ -0,0 +1,227 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA6CQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi45LjAtcHJlLjYiLCJpZCI6Ik5pYzFacUZYIiwiY29udHJvbERldmljZUlkIjoiMTAiLCJmZWVkYmFja0RldmljZUlkIjoiMTEiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiYzlkYTU5MjQtNTkzNy00NGE3LWJkY2MtNmU2MzhiNDM0ZTc5Iiwi + bmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlw + ZSI6MCwiY29tbWFuZE5hbWUiOiJSU2I5ZmEwNGViMzAzMjkxYzdjYjJkMjhlZDBkMTUzNmQ1NDUwN2M2MzkiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3si + aWQiOiIwYzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjow + LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxfX0seyJpZCI6ImU1NTQzYWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVmMC01Y2Y4LTQ1ZTktOWM5MC00ZDQ5MDZjZjYz + MDQiLCJuYW1lIjoiRW5jb2RlciAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJp + ZCI6ImRkMWE4MWQzLWQ2MDctNDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwi + Y2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2NTMtOWZiYi1l + ZjkwN2RiMDlkODciLCJuYW1lIjoiRW5jb2RlciAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoyfX0seyJpZCI6ImI4MmQ3NzUyLWQyMjktNDQ4Zi1iZDQyLWFmNjFiNDU5ZTQ4MSIsIm5hbWUiOiJCdXR0b24gMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51 + bWJlciI6MiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3 + MWMtYjcwNi0yN2I2MmUyYTVkYmUiLCJuYW1lIjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudEluZGV4IjozfX0seyJpZCI6IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNlIjp7ImNoYW5u + ZWwiOjEsIm51bWJlciI6MywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiI4MjhkNDY1 + YS05YmQzLTQ0MzYtOTEzNC00ODhhNmZlYjFmYmYiLCJuYW1lIjoiRW5jb2RlciAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0ZXIi + OjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNl + Ijp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQi + OiI3MTkwZTJhNi1lMDdjLTQ5MjItOWJiNC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJj + aGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjAzOWY3OTliLTUzN2YtNDI3Ny1hYmMxLTAzYzUwNjgyNzA4MyIsIm5hbWUiOiJCdXR0b24gMi8y + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + NX19LHsiaWQiOiI0NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjo2LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJC + dXR0b24gMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6Nn19LHsiaWQiOiJkMjg0OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2MiLCJuYW1lIjoiRW5jb2RlciAyLzQiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIs + Im5hbWUiOiJCdXR0b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiIyNWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYiLCJuYW1lIjoiRW5jb2RlciAzLzEiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjE2MDEzYzQ4LTJhNDktNDY3Mi1iZjNhLTk1ZWFl + MDVlZjNhNyIsIm5hbWUiOiJCdXR0b24gMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5jb2RlciAzLzIi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04 + Yjg5LWQ5NmI5NDUwMDAzMiIsIm5hbWUiOiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJuYW1lIjoiRW5j + b2RlciAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdhOTMwNWQt + OTRhYi00ZmM5LTliNjQtYTkwOWU1NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjox + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVlZC00NjNiLTk3OWQtYjMwOTE2ZTNiYjNh + IiwibmFtZSI6IkVuY29kZXIgMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJp + ZCI6IjZiOGFkMTMxLWJlZDktNGJhNy1hNjA0LTQzNWNmMDQzNmM5MSIsIm5hbWUiOiJCdXR0b24gMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTEs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdl + LTVkZmEzZDNjMGJmNCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxMn19LHsiaWQiOiIxYzA5MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiJhMzg0MzVjMC0z + YWM0LTQxNzQtYjFiMS03ODFlZmY3OTg4YzQiLCJuYW1lIjoiRW5jb2RlciA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoz + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7Imlk + IjoiOTQwNjAzOGUtM2M0NC00MzJkLThlY2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQs + ImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjIzMmQ5ODlhLWUwZDctNDdmOS05NTg3LTY2OGFjMTI1ODRhMyIsIm5hbWUiOiJCdXR0b24g + NC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjE0fX0seyJpZCI6IjU1YWEyM2IwLWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMwMDlkNGQiLCJu + YW1lIjoiQnV0dG9uIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiIyN2JmMjE1Mi1lN2Y0LTQ1ZTUtOWI5OC1kY2FlODk2NWI5MWYiLCJuYW1lIjoiTGVmdCAxIiwic291cmNlIjp7ImNo + YW5uZWwiOjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5 + ZGY1OTAtNjFjYi00NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVy + IjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9fSx7ImlkIjoiYWIwNTQ4ODAtMWM0My00ZjRhLWI0MTgtYzNlYTRkMzFi + NTEwIiwibmFtZSI6IkxlZnQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoxOH19LHsiaWQiOiJhNTdhMjc4Ny0zOTE5LTQ2ZmItOGZkZC0xN2ZiODNmNGJhZjEiLCJuYW1lIjoiUmlnaHQgMyIsInNvdXJjZSI6 + eyJjaGFubmVsIjozLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOX19XSwiYWN0 + aXZlQ29udHJvbGxlcklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIifQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {D3F82F36-28DC-458C-856F-E04276E7FAEE} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-349-seek-viewport.rpp b/plugin-reaper-realearn/resources/test-projects/issue-349-seek-viewport.rpp new file mode 100644 index 0000000..e087bf8 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-349-seek-viewport.rpp @@ -0,0 +1,163 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 19.07948366460937 19.06145572716627 + SELECTION2 81.5 81.5 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAApwEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi45LjAtcHJlLjYiLCJpZCI6IkFMR1MtbzJJIiwiY29udHJvbERldmljZUlkIjoiMTAiLCJmZWVkYmFja0RldmljZUlkIjoiMTEiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiMTVkYzdiNzgtYzU3YS00ZGE5LWJjYWItMDI1NWZhZDkxYjE5Iiwi + bmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1ha2VBYnNvbHV0ZUVuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MjMsImZ4QW5jaG9yIjoiaWQiLCJmZWVkYmFj + a1Jlc29sdXRpb24iOiJoaWdoIiwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {CCA62C0F-FB99-44EF-8BD0-EDC7E72D020A} + WAK 0 0 + > + + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-350-fx-param-feedback-no-notify.rpp b/plugin-reaper-realearn/resources/test-projects/issue-350-fx-param-feedback-no-notify.rpp new file mode 100644 index 0000000..387f546 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-350-fx-param-feedback-no-notify.rpp @@ -0,0 +1,597 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAnCkAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4xIiwiaWQiOiI1Q2VRbkhnYyIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjAzMmYwNDIxLTQ4NDctNGE0MC1iYmU2LTU4MDY3MjNlZGVhNiIs + Im5hbWUiOiJGWCBQYXJhbWV0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0Fy + Z0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHJhY2tHVUlEIjoiODA2ODE3NUEt + NkM3Ny00QzQzLUE4RTQtM0VCQTU5NUJFRUZBIiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IjVGQzI5NDQ4LTI4QjctNDdFRi1COTMyLTNGMTdCOTJCQzEzOCIsInBh + cmFtSW5kZXgiOjEyNywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJiNjc3MWUx + OC1jZTk1LTQxNGItOGY4Ni01ZjIyNmZjNDcxY2UiLCJuYW1lIjoiQWxsIEZYIGVuYWJsZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEz + fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoxNSwidHJhY2tHVUlEIjoiODA2ODE3NUEtNkM3Ny00QzQzLUE4RTQt + M0VCQTU5NUJFRUZBIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9 + fSx7ImlkIjoiMzkzYjhmYjctYmZmNy00NDE5LWE3NTYtMmE4YzE2Njg4MjEwIiwibmFtZSI6IlNlbmQgbXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjE0fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoxOCwidHJhY2tHVUlEIjoiODA2ODE3NUEtNkM3 + Ny00QzQzLUE4RTQtM0VCQTU5NUJFRUZBIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3Nj + QXJnSW5kZXgiOjB9fSx7ImlkIjoiMTgwZTgyY2UtZjQ1MS00MzFhLWJkNzYtZDMwNzlhZmJiMjUxIiwibmFtZSI6IlRyYWNrIGhpZGUiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoxNX0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MjQsInRyYWNrR1VJRCI6 + Ijk1RjIwMzNELTIwRjAtNDEwMC04MDFELTI3QjZDMEQ0MERCNSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwi + bmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0y + YzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJu + YW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1 + OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwi + c291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFk + NS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29k + ZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYz + LTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFt + ZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlm + Nzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAy + YTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsi + aWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEt + MzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00 + YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIy + YTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7Imlk + IjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAs + ImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24g + My8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJu + YW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5 + ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIg + NC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUt + NDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5h + bWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIy + MzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFy + YWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0 + MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00 + NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJS + aWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6Mywi + bnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkx + OS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {ADB3BF01-1D56-46F9-88A2-BFDAD42EF8B2} + WAK 0 0 + > + > + "" + MkRNU+5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABrbQAAAQAAAAAAEAA= + I3BnbT1IUyBSZWNlbnRseQovKkBNZXRhCgpBdXRob3I6CidIb3dhcmQgU2NhcnInCgpVc2FnZToKJ01XID0gdmlicmF0bycKCkNhdGVnb3JpZXM6CidCYXNzOkFuYWxv + Z3VlLCBCYXNzOlN5bnRoJwoKRmVhdHVyZXM6CidNb25vLCBGTSwgRHJ5JwoKQ2hhcmFjdGVyOgonRGFyaywgU29mdCwgU3ludGhldGljJwoKKi8KCiNBTT1aZWJyYTIK + I1ZlcnM9MjA1MDAKI0VuZGlhbj1saXR0bGUKI25tPTM0CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwojbXM9Q3RybEEKI21zPUN0cmxCCiNtcz1MZm9HMQoj + bXM9TGZvRzIKI21zPUdhdGUKI21zPUtleUZvbAojbXM9S2V5Rm9sMgojbXM9VmVsb2NpdHkKI21zPUFUb3VjaAojbXM9QXJwTW9kCiNtcz1BcnBNZDIKI21zPUVudjEK + I21zPUVudjIKI21zPUVudjMKI21zPUVudjQKI21zPU1TRUcxCiNtcz1NU0VHMgojbXM9TVNFRzMKI21zPU1TRUc0CiNtcz1MZm8xCiNtcz1MZm8yCiNtcz1MZm8zCiNt + cz1MZm80CiNtcz1NTWFwMQojbXM9TU1hcDIKI21zPU1NYXAzCiNtcz1NTWFwNAojbXM9TU1peDEKI21zPU1NaXgyCiNtcz1NTWl4MwojbXM9TU1peDQKI252PTUKI212 + PUdhdGUKI212PUVudjEKI212PUVudjIKI212PUVudjMKI212PUVudjQKI2NtPW1haW4KQ2NPcD04MC4wMAojTEZPRz0xCiNMRk9HMj0xCiNjbT1QQ29yZQpYXzE9MC4w + MApZXzE9MC4wMApYXzI9MC4wMApZXzI9MC4wMApYXzM9MC4wMApZXzM9MC4wMApYXzQ9MC4wMApZXzQ9MC4wMApNVDExPUZNTzQ6VHVuZQpNTDExPTEyLjAwCk1SMTE9 + LTEyLjAwCk1UMTI9Rk1PMjpUdW5lCk1MMTI9LTEyLjAwCk1SMTI9MTIuMDAKTVQxMz1ub25lOmFzc2lnbmVkCk1MMTM9MC4wMApNUjEzPTAuMDAKTVQxND1ub25lOmFz + c2lnbmVkCk1MMTQ9MC4wMApNUjE0PTAuMDAKTVQxNT1ub25lOmFzc2lnbmVkCk1MMTU9MC4wMApNUjE1PTAuMDAKTVQxNj1ub25lOmFzc2lnbmVkCk1MMTY9MC4wMApN + UjE2PTAuMDAKTVQxNz1ub25lOmFzc2lnbmVkCk1MMTc9MC4wMApNUjE3PTAuMDAKTVQxOD1ub25lOmFzc2lnbmVkCk1MMTg9MC4wMApNUjE4PTAuMDAKTVQyMT1GTU8x + OlR1bmUKTUwyMT0yNC4wMApNUjIxPTEyLjAwCk1UMjI9bm9uZTphc3NpZ25lZApNTDIyPTAuMDAKTVIyMj0wLjAwCk1UMjM9bm9uZTphc3NpZ25lZApNTDIzPTAuMDAK + TVIyMz0wLjAwCk1UMjQ9bm9uZTphc3NpZ25lZApNTDI0PTAuMDAKTVIyND0wLjAwCk1UMjU9bm9uZTphc3NpZ25lZApNTDI1PTAuMDAKTVIyNT0wLjAwCk1UMjY9bm9u + ZTphc3NpZ25lZApNTDI2PTAuMDAKTVIyNj0wLjAwCk1UMjc9bm9uZTphc3NpZ25lZApNTDI3PTAuMDAKTVIyNz0wLjAwCk1UMjg9bm9uZTphc3NpZ25lZApNTDI4PTAu + MDAKTVIyOD0wLjAwCk1UMzE9Rk1PMTpGTQpNTDMxPTE1LjAwCk1SMzE9LTEwLjAwCk1UMzI9Rk1PMjpGTQpNTDMyPTE3LjUwCk1SMzI9LTcuNTAKTVQzMz1YTUYzOk9M + b2FkCk1MMzM9My43NQpNUjMzPS03LjUwCk1UMzQ9Wk1hczpNYXN0Ck1MMzQ9LTEyLjUwCk1SMzQ9Ni4yNQpNVDM1PW5vbmU6YXNzaWduZWQKTUwzNT0wLjAwCk1SMzU9 + MC4wMApNVDM2PW5vbmU6YXNzaWduZWQKTUwzNj0wLjAwCk1SMzY9MC4wMApNVDM3PW5vbmU6YXNzaWduZWQKTUwzNz0wLjAwCk1SMzc9MC4wMApNVDM4PW5vbmU6YXNz + aWduZWQKTUwzOD0wLjAwCk1SMzg9MC4wMApNVDQxPUVRMTpmYzQKTUw0MT0tNDcuNTAKTVI0MT0tNTMuNzUKTVQ0Mj1FUTE6Z2FpbjQKTUw0Mj01LjQwCk1SNDI9LTM3 + LjIwCk1UNDM9RVExOmdhaW4zCk1MNDM9OS4xMgpNUjQzPTAuMDAKTVQ0ND1FUTE6cmVzMwpNTDQ0PTY4LjAwCk1SNDQ9LTEuMDAKTVQ0NT1ub25lOmFzc2lnbmVkCk1M + NDU9MC4wMApNUjQ1PTAuMDAKTVQ0Nj1ub25lOmFzc2lnbmVkCk1MNDY9MC4wMApNUjQ2PTAuMDAKTVQ0Nz1ub25lOmFzc2lnbmVkCk1MNDc9MC4wMApNUjQ3PTAuMDAK + TVQ0OD1ub25lOmFzc2lnbmVkCk1MNDg9MC4wMApNUjQ4PTAuMDAKTVQ1MT1FTlYxOkRlYwpNTDUxPS0zMi41MApNUjUxPTE3LjUwCk1UNTI9RU5WMjpEZWMKTUw1Mj0t + NDAuMDAKTVI1Mj0yMS4yNQpNVDUzPUVOVjM6RGVjCk1MNTM9LTIyLjUwCk1SNTM9MzAuMDAKTVQ1ND1FTlYyOkF0awpNTDU0PTAuMDAKTVI1ND00MC4wMApNVDU1PW5v + bmU6YXNzaWduZWQKTUw1NT0wLjAwCk1SNTU9MC4wMApNVDU2PW5vbmU6YXNzaWduZWQKTUw1Nj0wLjAwCk1SNTY9MC4wMApNVDU3PW5vbmU6YXNzaWduZWQKTUw1Nz0w + LjAwCk1SNTc9MC4wMApNVDU4PW5vbmU6YXNzaWduZWQKTUw1OD0wLjAwCk1SNTg9MC4wMApNVDYxPUVOVjE6UmVsCk1MNjE9NDMuNzUKTVI2MT0wLjAwCk1UNjI9RU5W + MjpSZWwKTUw2Mj0zMS4yNQpNUjYyPS0xNy41MApNVDYzPUVOVjM6UmVsCk1MNjM9MzYuMjUKTVI2Mz0tMTIuNTAKTVQ2ND1ub25lOmFzc2lnbmVkCk1MNjQ9MC4wMApN + UjY0PTAuMDAKTVQ2NT1ub25lOmFzc2lnbmVkCk1MNjU9MC4wMApNUjY1PTAuMDAKTVQ2Nj1ub25lOmFzc2lnbmVkCk1MNjY9MC4wMApNUjY2PTAuMDAKTVQ2Nz1ub25l + OmFzc2lnbmVkCk1MNjc9MC4wMApNUjY3PTAuMDAKTVQ2OD1ub25lOmFzc2lnbmVkCk1MNjg9MC4wMApNUjY4PTAuMDAKTVQ3MT1aTWFzOlJldDIKTUw3MT0zOC4wMApN + UjcxPTMyLjAwCk1UNzI9RGVsYXkxOkZCCk1MNzI9MjAuMDAKTVI3Mj0tMTguMDAKTVQ3Mz1EZWxheTE6Q0IKTUw3Mz0xNS4wMApNUjczPS0xOS4wMApNVDc0PURlbGF5 + MTpIUApNTDc0PTUwLjAwCk1SNzQ9MC4wMApNVDc1PURlbGF5MTpUMApNTDc1PTAuMDAKTVI3NT0tNzguMDAKTVQ3Nj1EZWxheTE6VDEKTUw3Nj0yLjAwCk1SNzY9LTc4 + LjAwCk1UNzc9bm9uZTphc3NpZ25lZApNTDc3PTAuMDAKTVI3Nz0wLjAwCk1UNzg9bm9uZTphc3NpZ25lZApNTDc4PTAuMDAKTVI3OD0wLjAwCk1UODE9Wk1hczpSZXQx + Ck1MODE9MjkuMDAKTVI4MT0yOS4wMApNVDgyPU51UmV2MTpEZWNheQpNTDgyPTE4LjAwCk1SODI9LTIzLjAwCk1UODM9TnVSZXYxOlByZQpNTDgzPTIuNTAKTVI4Mz0t + MTAuMDAKTVQ4ND1OdVJldjE6U2l6ZQpNTDg0PTAuMDAKTVI4ND0tNjcuNjYKTVQ4NT1ub25lOmFzc2lnbmVkCk1MODU9MC4wMApNUjg1PTAuMDAKTVQ4Nj1ub25lOmFz + c2lnbmVkCk1MODY9MC4wMApNUjg2PTAuMDAKTVQ4Nz1ub25lOmFzc2lnbmVkCk1MODc9MC4wMApNUjg3PTAuMDAKTVQ4OD1ub25lOmFzc2lnbmVkCk1MODg9MC4wMApN + Ujg4PTAuMDAKTU1UMT1ub25lOmFzc2lnbmVkCk1NUzE9MApNTUQxPTAuMDAKTU1WUzE9MApNTVZEMT0wLjAwCk1NVDI9bm9uZTphc3NpZ25lZApNTVMyPTAKTU1EMj0w + LjAwCk1NVlMyPTAKTU1WRDI9MC4wMApNTVQzPW5vbmU6YXNzaWduZWQKTU1TMz0wCk1NRDM9MC4wMApNTVZTMz0wCk1NVkQzPTAuMDAKTU1UND1ub25lOmFzc2lnbmVk + Ck1NUzQ9MApNTUQ0PTAuMDAKTU1WUzQ9MApNTVZEND0wLjAwCk1NVDU9bm9uZTphc3NpZ25lZApNTVM1PTAKTU1ENT0wLjAwCk1NVlM1PTAKTU1WRDU9MC4wMApNTVQ2 + PW5vbmU6YXNzaWduZWQKTU1TNj0wCk1NRDY9MC4wMApNTVZTNj0wCk1NVkQ2PTAuMDAKTU1UNz1ub25lOmFzc2lnbmVkCk1NUzc9MApNTUQ3PTAuMDAKTU1WUzc9MApN + TVZENz0wLjAwCk1NVDg9bm9uZTphc3NpZ25lZApNTVM4PTAKTU1EOD0wLjAwCk1NVlM4PTAKTU1WRDg9MC4wMApNTVQ5PW5vbmU6YXNzaWduZWQKTU1TOT0wCk1NRDk9 + MC4wMApNTVZTOT0wCk1NVkQ5PTAuMDAKTU1UMTA9bm9uZTphc3NpZ25lZApNTVMxMD0wCk1NRDEwPTAuMDAKTU1WUzEwPTAKTU1WRDEwPTAuMDAKTU1UMTE9bm9uZTph + c3NpZ25lZApNTVMxMT0wCk1NRDExPTAuMDAKTU1WUzExPTAKTU1WRDExPTAuMDAKTU1UMTI9bm9uZTphc3NpZ25lZApNTVMxMj0wCk1NRDEyPTAuMDAKTU1WUzEyPTAK + TU1WRDEyPTAuMDAKU0Jhc2U9MwpTd2luZz0wLjAwClNUcmlnPTEKUFByb2o9MwpQRm9sZD0wClBGaWxlPTEKR0ZpbGU9MgpHU2NhbGU9MApDaExheT0wClN1cnJPPTEK + UmV2PTk0MzYKTEVEPTAuMDAKUEFHRT0wClBhZ2VzT249MApDb3JlTj0zClNsaWNlPTQKVUlfb3A9NApNaWRpQT01Ck1pZGlQPTYKREZvbGQ9NwpDdHJsQT0yCkN0cmxC + PTExCiNjbT1MRk9HClN5bmM9NApUcmlnPTAKV2F2ZT0wClBoc2U9MC4wMApSYXRlPTEwMC4wMApBbXA9MTAwLjAwClNsZXc9MQpOc3RwPTE2ClN0cHM9OApVV3Y9MAoj + Y209TEZPRzIKU3luYz00ClRyaWc9MApXYXZlPTAKUGhzZT0wLjAwClJhdGU9MTAwLjAwCkFtcD0xMDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz05ClVXdj0wCiNjbT1W + Q0MKI0xGTzE9MQojTEZPMj0xCiNMRk8zPTEKI0xGTzQ9MQpWb2ljZXM9MApWb2ljaW5nPTAKTW9kZT0xClBvcnRhPTAuMDAKUEI9MgpQQkQ9MgpBclNjPTMKQXJPcmQ9 + MApBckxwPTAKQXJPY3Q9MQpBckxMPTgKQXJUcj0wCkRyZnQ9MApNVHVuUz0wCk1UdW5OPTEwCk1UdW5UPTExClRyc3A9LTEyCkZUdW49MC4wMApQb3J0Umc9MTAwLjAw + ClBvcnRhTT0wClBvcnRhMj0wLjAwCkFndGUxPTIKQXRycDE9MApBdm9jMT0xCkFtdWwxPTEKQW1vZDE9MApBTURwdDE9MC4wMApBTURwQjE9MC4wMApBZ3RlMj0yCkF0 + cnAyPTAKQXZvYzI9MQpBbXVsMj0xCkFtb2QyPTAKQU1EcHQyPTAuMDAKQU1EcEIyPTAuMDAKQWd0ZTM9MgpBdHJwMz0wCkF2b2MzPTEKQW11bDM9MQpBbW9kMz0wCkFN + RHB0Mz0wLjAwCkFNRHBCMz0wLjAwCkFndGU0PTIKQXRycDQ9MApBdm9jND0xCkFtdWw0PTEKQW1vZDQ9MApBTURwdDQ9MC4wMApBTURwQjQ9MC4wMApBZ3RlNT0yCkF0 + cnA1PTAKQXZvYzU9MQpBbXVsNT0xCkFtb2Q1PTAKQU1EcHQ1PTAuMDAKQU1EcEI1PTAuMDAKQWd0ZTY9MgpBdHJwNj0wCkF2b2M2PTEKQW11bDY9MQpBbW9kNj0wCkFN + RHB0Nj0wLjAwCkFNRHBCNj0wLjAwCkFndGU3PTIKQXRycDc9MApBdm9jNz0xCkFtdWw3PTEKQW1vZDc9MApBTURwdDc9MC4wMApBTURwQjc9MC4wMApBZ3RlOD0yCkF0 + cnA4PTAKQXZvYzg9MQpBbXVsOD0xCkFtb2Q4PTAKQU1EcHQ4PTAuMDAKQU1EcEI4PTAuMDAKQWd0ZTk9MgpBdHJwOT0wCkF2b2M5PTEKQW11bDk9MQpBbW9kOT0wCkFN + RHB0OT0wLjAwCkFNRHBCOT0wLjAwCkFndGUxMD0yCkF0cnAxMD0wCkF2b2MxMD0xCkFtdWwxMD0xCkFtb2QxMD0wCkFNRHB0MTA9MC4wMApBTURwQjEwPTAuMDAKQWd0 + ZTExPTIKQXRycDExPTAKQXZvYzExPTEKQW11bDExPTEKQW1vZDExPTAKQU1EcHQxMT0wLjAwCkFNRHBCMTE9MC4wMApBZ3RlMTI9MgpBdHJwMTI9MApBdm9jMTI9MQpB + bXVsMTI9MQpBbW9kMTI9MApBTURwdDEyPTAuMDAKQU1EcEIxMj0wLjAwCkFndGUxMz0yCkF0cnAxMz0wCkF2b2MxMz0xCkFtdWwxMz0xCkFtb2QxMz0wCkFNRHB0MTM9 + MC4wMApBTURwQjEzPTAuMDAKQWd0ZTE0PTIKQXRycDE0PTAKQXZvYzE0PTEKQW11bDE0PTEKQW1vZDE0PTAKQU1EcHQxND0wLjAwCkFNRHBCMTQ9MC4wMApBZ3RlMTU9 + MgpBdHJwMTU9MApBdm9jMTU9MQpBbXVsMTU9MQpBbW9kMTU9MApBTURwdDE1PTAuMDAKQU1EcEIxNT0wLjAwCkFndGUxNj0yCkF0cnAxNj0wCkF2b2MxNj0xCkFtdWwx + Nj0xCkFtb2QxNj0wCkFNRHB0MTY9MC4wMApBTURwQjE2PTAuMDAKI2NtPUVOVjEKTW9kZT0yCmlNb2RlPTAKc01vZGU9MAppbml0PTAuMDAKQXRrPTAuMDAKRGVjPTcy + LjAwClN1cz0wLjAwClN1c1Q9LTQuMDAKU3VzMj0wLjAwClJlbD0xNS41MApWZWw9MC4wMApWMkk9MC4wMApWMkE9MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAu + MDAKVjJTMj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksyQT0wLjAwCksyRD0tMTIuMDAKSzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4wMApLMlI9MC4wMApTbG9wZT0t + NzAuMDAKVEJhc2U9MAojY209RU5WMgpNb2RlPTIKaU1vZGU9MApzTW9kZT0wCmluaXQ9MC4wMApBdGs9MC41MApEZWM9NjQuNTAKU3VzPTAuMDAKU3VzVD0wLjAwClN1 + czI9MC4wMApSZWw9MzIuNTAKVmVsPTkwLjAwClYyST0wLjAwClYyQT0wLjAwClYyRD0wLjAwClYyUz0wLjAwClYyRlI9MC4wMApWMlMyPTAuMDAKVjJSPTAuMDAKSzJJ + PTAuMDAKSzJBPTAuMDAKSzJEPS04LjAwCksyUz0wLjAwCksyRlI9MC4wMApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9LTcwLjAwClRCYXNlPTAKI2NtPUVOVjMKTW9k + ZT0yCmlNb2RlPTAKc01vZGU9MAppbml0PTAuMDAKQXRrPTIuMDAKRGVjPTUwLjAwClN1cz0wLjAwClN1c1Q9MC4wMApTdXMyPTQ2LjMzClJlbD0yNi41MApWZWw9MC4w + MApWMkk9MC4wMApWMkE9MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJTMj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksyQT0wLjAwCksyRD0tMTAuMDAK + SzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4wMApLMlI9MC4wMApTbG9wZT0tMTAwLjAwClRCYXNlPTAKI2NtPUVOVjQKTW9kZT0yCmlNb2RlPTAKc01vZGU9MAppbml0 + PTAuMDAKQXRrPTIuMDAKRGVjPTI5LjAwClN1cz0wLjAwClN1c1Q9MC4wMApTdXMyPTAuMDAKUmVsPTM3LjAwClZlbD0xMDAuMDAKVjJJPTAuMDAKVjJBPTAuMDAKVjJE + PTAuMDAKVjJTPTAuMDAKVjJGUj0wLjAwClYyUzI9MC4wMApWMlI9MC4wMApLMkk9MC4wMApLMkE9MC4wMApLMkQ9LTEyLjAwCksyUz0wLjAwCksyRlI9MC4wMApLMlMy + PTAuMDAKSzJSPTAuMDAKU2xvcGU9LTcwLjAwClRCYXNlPTAKI2NtPU1TRUcxClRtVW49MApFbnY9MTIKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAK + VHJpZz0wCiNjbT1NU0VHMgpUbVVuPTAKRW52PTEzClZlbD0wLjAwCkF0az0wLjAwCkxwdD0wLjAwClJlbD0wLjAwClRyaWc9MAojY209TVNFRzMKVG1Vbj0wCkVudj0x + NApWZWw9MC4wMApBdGs9MC4wMApMcHQ9MC4wMApSZWw9MC4wMApUcmlnPTAKI2NtPU1TRUc0ClRtVW49MApFbnY9MTUKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAK + UmVsPTAuMDAKVHJpZz0wCiNjbT1MRk8xClN5bmM9LTMKVHJpZz0wCldhdmU9MApQaHNlPTAuMDAKUmF0ZT04MC4wMApBbXA9MTAwLjAwClNsZXc9MQpOc3RwPTE2ClN0 + cHM9MTYKVVd2PTAKRGx5PTAuMDAKRE1TMT0xCkRNRDE9MTAwLjAwCkZNUzE9MApGTUQxPTAuMDAKI2NtPUxGTzIKU3luYz00ClRyaWc9MApXYXZlPTAKUGhzZT0wLjAw + ClJhdGU9MTAwLjAwCkFtcD0xMDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz0xNwpVV3Y9MApEbHk9MC4wMApETVMxPTAKRE1EMT0wLjAwCkZNUzE9MApGTUQxPTAuMDAK + I2NtPUxGTzMKU3luYz00ClRyaWc9MApXYXZlPTAKUGhzZT0wLjAwClJhdGU9MTAwLjAwCkFtcD0xMDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz0xOApVV3Y9MApEbHk9 + MC4wMApETVMxPTAKRE1EMT0wLjAwCkZNUzE9MApGTUQxPTAuMDAKI2NtPUxGTzQKU3luYz00ClRyaWc9MApXYXZlPTAKUGhzZT0wLjAwClJhdGU9MTAwLjAwCkFtcD0x + MDAuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz0xOQpVV3Y9MApEbHk9MC4wMApETVMxPTAKRE1EMT0wLjAwCkZNUzE9MApGTUQxPTAuMDAKI2NtPU1NYXAxCk1vZGU9MwpN + U3JjPTAKU3Rwcz0yMApOdW09MTcKI2NtPU1NYXAyCk1vZGU9MwpNU3JjPTAKU3Rwcz0yMQpOdW09NgojY209TU1hcDMKTW9kZT0zCk1TcmM9MApTdHBzPTIyCk51bT0x + NwojY209TU1hcDQKTW9kZT0zCk1TcmM9MApTdHBzPTIzCk51bT0xNwojY209TU1peDEKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1p + eDIKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1peDMKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209TU1p + eDQKVHlwZT0wCk1vZDE9MApNb2QyPTAKTW9kMz0wCkNzdD01MC4wMAojY209R3JpZApHcmlkPTI0CkdCeXA9MAojY209T1NDMQpXYXZlPTAKVHVuZT0wLjAwCktleVNj + bD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwClBoc2U9NTAuMDAKUGhzTVNyYz0wClBoc01EcHQ9MC4wMApXTnVtPTUuMDAKV1BTcmM9MApXUERwdD0wLjAwClZ0b0Q9 + MC4wMApDdXJ2ZT0yNQpQcmVjPTUuMDAKRlgxVHA9MApTRlgxPTAuMDAKRlgxU2M9MApGWDFEdD0wLjAwCkZYMlRwPTAKU0ZYMj0wLjAwCkZYMlNjPTAKRlgyRHQ9MC4w + MApQb2x5PTAKRHR1bj0wLjAwCktWc2M9MjYKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClN5bmM9MC4wMApT + bmNTYz0wClNuY0R0PTAuMDAKU25jT249MApQb2xXPTUwLjAwClB3bU9uPTAKV2FUYj0yNwpSZVBocz0wCk5vcm09MTUuMDAKUmVuZD0wCkZtdHplPTAKV1ROYW1lPTI4 + CiNjbT1PU0MyCldhdmU9MApUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKUGhzZT01MC4wMApQaHNNU3JjPTAKUGhzTURwdD0wLjAwCldO + dW09NS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD0wLjAwCkN1cnZlPTI5ClByZWM9NS4wMApGWDFUcD0wClNGWDE9MC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgy + VHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MApEdHVuPTAuMDAKS1ZzYz0zMApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4w + MApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBvbFc9NTAuMDAKUHdtT249MApXYVRiPTMxClJlUGhzPTAKTm9y + bT0xNS4wMApSZW5kPTAKRm10emU9MApXVE5hbWU9MzIKI2NtPU9TQzMKV2F2ZT0wClR1bmU9MC4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApQaHNl + PTUwLjAwClBoc01TcmM9MApQaHNNRHB0PTAuMDAKV051bT01LjAwCldQU3JjPTAKV1BEcHQ9MC4wMApWdG9EPTAuMDAKQ3VydmU9MzMKUHJlYz01LjAwCkZYMVRwPTAK + U0ZYMT0wLjAwCkZYMVNjPTAKRlgxRHQ9MC4wMApGWDJUcD0wClNGWDI9MC4wMApGWDJTYz0wCkZYMkR0PTAuMDAKUG9seT0wCkR0dW49MC4wMApLVnNjPTM0ClZvbD0x + MDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApTeW5jPTAuMDAKU25jU2M9MApTbmNEdD0wLjAwClNuY09uPTAKUG9sVz01 + MC4wMApQd21Pbj0wCldhVGI9MzUKUmVQaHM9MApOb3JtPTE1LjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT0zNgojY209T1NDNApXYXZlPTMKVHVuZT0wLjAwCktleVNj + bD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwClBoc2U9NTAuMDAKUGhzTVNyYz0wClBoc01EcHQ9MC4wMApXTnVtPTEuMDAKV1BTcmM9MApXUERwdD0xNS4wMApWdG9E + PTAuMDAKQ3VydmU9MzcKUHJlYz01LjAwCkZYMVRwPTAKU0ZYMT0wLjAwCkZYMVNjPTAKRlgxRHQ9MC4wMApGWDJUcD0wClNGWDI9MC4wMApGWDJTYz0wCkZYMkR0PTAu + MDAKUG9seT0wCkR0dW49MC4wMApLVnNjPTM4ClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApTeW5jPTAuMDAK + U25jU2M9MApTbmNEdD0wLjAwClNuY09uPTAKUG9sVz01MC4wMApQd21Pbj0wCldhVGI9MzkKUmVQaHM9MApOb3JtPTIwLjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT00 + MAojY209Tm9pc2UxClR5cGU9MApGMT0xMDAuMDAKRjFTcmM9MApGMURwdD0wLjAwCkYyPTAuMDAKRjJTcmM9MApGMkRwdD0wLjAwCktWc2M9NDEKVm9sPTEwMC4wMApW + b2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCiNjbT1Ob2lzZTIKVHlwZT0wCkYxPTEwMC4wMApGMVNy + Yz0wCkYxRHB0PTAuMDAKRjI9MC4wMApGMlNyYz0wCkYyRHB0PTAuMDAKS1ZzYz00MgpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0w + ClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKI2NtPVZDRjEKVHlwPTAKQ3V0PTE1MC4wMApSZXM9MC4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTAuMDAKRlMx + PTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPVZDRjIKVHlwPTAKQ3V0PTE1MC4wMApSZXM9MC4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTAuMDAKRlMx + PTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPVZDRjMKVHlwPTAKQ3V0PTE1MC4wMApSZXM9MC4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTAuMDAKRlMx + PTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPVZDRjQKVHlwPTAKQ3V0PTE1MC4wMApSZXM9MC4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTAuMDAKRlMx + PTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPUZNTzEKV2F2ZT0wClR1bmU9LTEyLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTI0 + LjAwCkZNU3JjPTAKRk1EcHQ9MC4wMApWdG9EPTEwMC4wMApEdHVuPTAuMDAKS1ZzYz00MwpWb2w9MjAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5T + Yz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKR2V0cj0wCiNjbT1GTU8yCldhdmU9MApUdW5lPS0xMi4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9 + MC4wMApGTT0xNC41MApGTVNyYz0xNQpGTURwdD0xMi4wMApWdG9EPTEwMC4wMApEdHVuPTAuMDAKS1ZzYz00NApWb2w9MC4wMApWb2xTYz0xNQpWb2xEdD00NC4wMApQ + YW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKR2V0cj0wCiNjbT1GTU8zCldhdmU9MApUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNy + Yz0wClRNRHB0PTAuMDAKRk09MC4wMApGTVNyYz0wCkZNRHB0PTAuMDAKVnRvRD0xMDAuMDAKRHR1bj0wLjAwCktWc2M9NDUKVm9sPTEwLjAwClZvbFNjPTE2ClZvbER0 + PTgwLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQb2x5PTAKUG9sVz01MC4wMApHZXRyPTMKI2NtPUZNTzQKV2F2ZT0xClR1bmU9MC4wMApLZXlTY2w9MTAw + LjAwClRNU3JjPTAKVE1EcHQ9MC4wMApGTT0xNS4wMApGTVNyYz0xNwpGTURwdD0yMC4wMApWdG9EPTEwMC4wMApEdHVuPTAuMDAKS1ZzYz00NgpWb2w9MTAuMDAKVm9s + U2M9MTcKVm9sRHQ9ODAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCkdldHI9MAojY209Q29tYjEKTW9kZT0wClR1bmU9MC4w + MApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApEZXRuPTAuMDAKVnRvRD0wLjAwCkZCPTUwLjAwCkZCU3JjPTAKRkJEcHQ9MC4wMApEYW1wPTEwLjAwCkRt + cFNyYz0wCkRtcERwdD0wLjAwCkV4Yz0xMDAuMDAKSW5qPTAuMDAKSW5qU3JjPTAKSW5qRHB0PTAuMDAKVG5lPTUwLjAwClRuZVNyYz0wClRuZURwdD0wLjAwClNlYz0w + LjAwClNlY1NyYz0wClNlY0RwdD0wLjAwCkRpc3Q9MC4wMApEcnk9MC4wMApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0 + PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKRmlsbD0wCiNjbT1Db21iMgpNb2RlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkRldG49 + MC4wMApWdG9EPTAuMDAKRkI9NTAuMDAKRkJTcmM9MApGQkRwdD0wLjAwCkRhbXA9MTAuMDAKRG1wU3JjPTAKRG1wRHB0PTAuMDAKRXhjPTEwMC4wMApJbmo9MC4wMApJ + bmpTcmM9MApJbmpEcHQ9MC4wMApUbmU9NTAuMDAKVG5lU3JjPTAKVG5lRHB0PTAuMDAKU2VjPTAuMDAKU2VjU3JjPTAKU2VjRHB0PTAuMDAKRGlzdD0wLjAwCkRyeT0w + LjAwClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQb2x5PTAKUG9sVz01MC4wMApGaWxsPTAKI2NtPVNoYXBl + MQpUeXBlPTAKRGVwdGg9MC4wMApETVNyYz0wCkRNRHB0PTAuMDAKRWRnZT03NS4wMApFTVNyYz0wCkVNRHB0PTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMApIaU91 + dD0wLjAwCiNjbT1TaGFwZTIKVHlwZT0wCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9NzUuMDAKRU1TcmM9MApFTURwdD0wLjAwCklucHV0PTAuMDAK + T3V0cHV0PTAuMDAKSGlPdXQ9MC4wMAojY209TWl4MQpQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPU1peDIKUGFuPTAuMDAKTWl4 + PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1NaXgzClBhbj0wLjAwCk1peD01MC4wMApQbk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209TWl4NApQYW49 + MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPVhNRjEKVHlwPTAKQ3V0PTE1MC4wMApSZXM9MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4w + MApGUzI9MApLZXlTY2w9MC4wMApGT2ZmPTAuMDAKRk9Nb2Q9MC4wMApGT1NyYz0wClhGTT0wLjAwClhGTUQ9MC4wMApYRk1TPTAKQmlhcz0wLjAwCk9Mb2FkPTAuMDAK + Q2xpY2s9MC4wMApEcnY9MQpSb3V0PTAKVHlwMj0tMQojY209WE1GMgpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0w + CktleVNjbD0wLjAwCkZPZmY9MC4wMApGT01vZD0wLjAwCkZPU3JjPTAKWEZNPTAuMDAKWEZNRD0wLjAwClhGTVM9MApCaWFzPTAuMDAKT0xvYWQ9MC4wMApDbGljaz0w + LjAwCkRydj0xClJvdXQ9MApUeXAyPS0xCiNjbT1TQjEKUmFuZ2U9MApGcmVxPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwCk9mZnM9MTAuMDAKT01TcmM9MApPTURwdD0w + LjAwCk1peD01MC4wMApNTVNyYz0wCk1NRHB0PTAuMDAKI2NtPVNCMgpSYW5nZT0wCkZyZXE9MC4wMApGTVNyYz0wCkZNRHB0PTAuMDAKT2Zmcz0wLjAwCk9NU3JjPTAK + T01EcHQ9MC4wMApNaXg9NTAuMDAKTU1TcmM9MApNTURwdD0wLjAwCiNjbT1EaXN0MQpUeXBlPTAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMApQcmVUaWx0PTAuMDAKUHN0 + VGlsdD0wLjAwCkNudEZyZXE9MTAwLjAwCkxvdz0wLjAwCkhpZ2g9MC4wMApQb3N0Rmx0PTAKI2NtPURpc3QyClR5cGU9MApJbnB1dD0wLjAwCk91dHB1dD0wLjAwClBy + ZVRpbHQ9MC4wMApQc3RUaWx0PTAuMDAKQ250RnJlcT0xMDAuMDAKTG93PTAuMDAKSGlnaD0wLjAwClBvc3RGbHQ9MAojY209Rm9sZDEKUmlwcGxlcz00CkZvbGRzPTEw + LjAwCkZNb2Q9MC4wMApGU3JjPTAKQmlhcz0wLjAwCkJNb2Q9MC4wMApCU3JjPTAKRlJhdGlvPTUwLjAwCkZTbG9wZT0xMC4wMApDQW5nbD01MC4wMApMaXZlPTAKI2Nt + PUZvbGQyClJpcHBsZXM9NApGb2xkcz0xMC4wMApGTW9kPTAuMDAKRlNyYz0wCkJpYXM9MC4wMApCTW9kPTAuMDAKQlNyYz0wCkZSYXRpbz01MC4wMApGU2xvcGU9MTAu + MDAKQ0FuZ2w9NTAuMDAKTGl2ZT0wCiNjbT1WQ0ExClBhbjE9MC4wMApQYW5NUzE9MApQYW5NRDE9MC4wMApWb2wxPTUwLjAwClZDQTE9MQpNb2RTcmMxPTAKTW9kRHB0 + MT0wLjAwClBhbjI9MC4wMApQYW5NUzI9MApQYW5NRDI9MC4wMApWb2wyPTAuMDAKVkNBMj0xCk1vZFNyYzI9MApNb2REcHQyPTAuMDAKUGFuMz0wLjAwClBhbk1TMz0w + ClBhbk1EMz0wLjAwClZvbDM9MC4wMApWQ0EzPTEKTW9kU3JjMz0wCk1vZERwdDM9MC4wMApQYW40PTAuMDAKUGFuTVM0PTAKUGFuTUQ0PTAuMDAKVm9sND0wLjAwClZD + QTQ9MQpNb2RTcmM0PTAKTW9kRHB0ND0wLjAwCk1UMT0wCk1UMj0wCk1UMz0wCk1UND0wClBCMT0wClBCMj0wClBCMz0wClBCND0wCkJ1czE9MApCdXMyPTAKQnVzMz0w + CkJ1czQ9MApTZW5kMT0wLjAwClNuU3JjMT0wClNuRHB0MT0wLjAwClNlbmQyPTAuMDAKU25TcmMyPTAKU25EcHQyPTAuMDAKQXR0Uz0xCiNjbT1HcmlkRlgKR3JpZD00 + NwpHQnlwPTAKI2NtPU1vZEZYMQpNb2RlPTAKQ2VudD01MC4wMApTcGVkPTUwLjAwClBoT2ZmPTUwLjAwCkRwdGg9NTAuMDAKRmVlQj0wLjAwCk1peD01MC4wMApMQ3V0 + PTAuMDAKSEN1dD0xMDAuMDAKUXVhZD0wLjAwClFwaHM9MjUuMDAKTGVxPTAuMDAKSGVxPTAuMDAKUTE9MC4wMApRMj0wLjAwCkVRb249MAojY209TW9kRlgyCk1vZGU9 + MApDZW50PTIwLjAwClNwZWQ9NTAuMDAKUGhPZmY9NTAuMDAKRHB0aD01MC4wMApGZWVCPTAuMDAKTWl4PTUwLjAwCkxDdXQ9MC4wMApIQ3V0PTEwMC4wMApRdWFkPTMw + LjAwClFwaHM9MjUuMDAKTGVxPTAuMDAKSGVxPTAuMDAKUTE9MC4wMApRMj0wLjAwCkVRb249MQojY209RGVsYXkxCk1vZGU9MApNaXg9MTAwLjAwCkZCPTE4LjAwCkNC + PTIxLjUwCkxQPTYyLjUwCkhQPTI5LjUwCkRydj0wLjAwClN5bmMxPTkKU3luYzI9MwpTeW5jMz00ClN5bmM0PTQKVDA9MTAwLjAwClQxPTEwMC4wMApUMj0xMDAuMDAK + VDM9MTAwLjAwClBhbjE9LTQwLjAwClBhbjI9NDAuMDAKUGFuMz0tMTAwLjAwClBhbjQ9MTAwLjAwCiNjbT1EZWxheTIKTW9kZT0wCk1peD0zMC4wMApGQj0xMC4wMApD + Qj0yNS4wMApMUD0xMDAuMDAKSFA9MC4wMApEcnY9MC4wMApTeW5jMT00ClN5bmMyPTQKU3luYzM9NApTeW5jND00ClQwPTEwMC4wMApUMT0xMDAuMDAKVDI9MTAwLjAw + ClQzPTEwMC4wMApQYW4xPS0xMDAuMDAKUGFuMj0xMDAuMDAKUGFuMz0tMTAwLjAwClBhbjQ9MTAwLjAwCiNjbT1TaGFwZTMKVHlwZT0zCkRlcHRoPTAuMDAKRE1TcmM9 + MApETURwdD0wLjAwCkVkZ2U9NzUuMDAKRU1TcmM9MApFTURwdD0wLjAwCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKSGlPdXQ9MC4wMAojY209U2hhcGU0ClR5cGU9MApE + ZXB0aD0wLjAwCkRNU3JjPTAKRE1EcHQ9MC4wMApFZGdlPTc1LjAwCkVNU3JjPTAKRU1EcHQ9MC4wMApJbnB1dD0wLjAwCk91dHB1dD0wLjAwCkhpT3V0PTAuMDAKI2Nt + PU1peDUKUGFuPTAuMDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1NaXg2ClBhbj0wLjAwCk1peD01MC4wMApQbk1kPTAKUG5NRD0wLjAwClBu + TVM9MAojY209UmV2MQpNb2RlPTAKRHJ5PTg1LjAwCldldD01MC4wMApGQj01MC4wMApEYW1wPTI3LjkwClNpemU9NTAuMDAKU3BkPTUwLjAwCkRwdD01MC4wMApERkI9 + NTAuMDAKRFNpemU9NTAuMDAKRU1peD04MC4wMApETW9kPTUwLjAwCkRTcGQ9MTAwLjAwClByZT0xMC4wMAojY209Q29tcDEKVHlwZT0xClJhdD0xMDAuMDAKVGhyZXM9 + LTIwLjAwCkF0dD0zMC4wMApSZWw9NTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9LTEwLjAwCiNjbT1Db21wMgpUeXBlPTEKUmF0PTEwMC4wMApUaHJlcz0tMjAuMDAKQXR0 + PTMwLjAwClJlbD01MC4wMApJbnB1dD0wLjAwCk91dHB1dD0tMTAuMDAKI2NtPUVRMQpmYzE9MTMuMDAKcmVzMT03OC4wMApnYWluMT0zLjAwCmZjMj0xMy4wMApyZXMy + PTEzLjAwCmdhaW4yPTUuNTAKZmMzPTY4LjAwCnJlczM9MC4wMApnYWluMz02LjAwCmZjND03OS4wMApyZXM0PTI1LjAwCmdhaW40PTE3LjAwCiNjbT1FUTIKZmMxPTIu + MDAKcmVzMT01NS4wMApnYWluMT0tMjQuMDAKZmMyPTQzLjAwCnJlczI9NS4wMApnYWluMj0xLjAwCmZjMz02MC4wMApyZXMzPTI1LjAwCmdhaW4zPTAuMDAKZmM0PTc4 + LjUwCnJlczQ9MjUuMDAKZ2FpbjQ9MS43NQojY209VkNGNQpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApG + TTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209VkNGNgpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkRydj0wLjAwCkdhaW49MC4wMApGTTE9MC4wMApGUzE9MApG + TTI9MC4wMApGUzI9MApLZXlTY2w9MC4wMAojY209U0IzClJhbmdlPTAKRnJlcT0wLjAwCkZNU3JjPTAKRk1EcHQ9MC4wMApPZmZzPTUuMDAKT01TcmM9MApPTURwdD0w + LjAwCk1peD01MC4wMApNTVNyYz0wCk1NRHB0PTAuMDAKI2NtPVhNRjMKVHlwPTcKQ3V0PTE1MC4wMApSZXM9MC4wMApGTTE9MC4wMApGUzE9MApGTTI9MC4wMApGUzI9 + MApLZXlTY2w9MC4wMApGT2ZmPTAuMDAKRk9Nb2Q9MC4wMApGT1NyYz0wClhGTT05LjUwClhGTUQ9MC4wMApYRk1TPTAKQmlhcz0wLjAwCk9Mb2FkPTUwLjAwCkNsaWNr + PTAuMDAKRHJ2PTEKUm91dD0wClR5cDI9LTEKI2NtPURpc3QzClR5cGU9MApJbnB1dD0wLjAwCk91dHB1dD0wLjAwClByZVRpbHQ9MC4wMApQc3RUaWx0PTAuMDAKQ250 + RnJlcT0xMDAuMDAKTG93PTAuMDAKSGlnaD0wLjAwClBvc3RGbHQ9MAojY209RGlzdDQKVHlwZT0wCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKUHJlVGlsdD0wLjAwClBz + dFRpbHQ9MC4wMApDbnRGcmVxPTEwMC4wMApMb3c9MC4wMApIaWdoPTAuMDAKUG9zdEZsdD0wCiNjbT1OdVJldjEKUHJlPTEyLjAwCkRhbXA9NDcuMDAKRGVjYXk9NDku + ODgKU2l6ZT0xMTIuMDAKVG9uZT0tMTguMDAKV2lkdGg9NzUuMDAKTWl4PTEwMC4wMAojY209Wk1hcwpSZXQxPTAuMDAKUmV0Mj0wLjAwCk1hc3Q9MzcuMDAKWFkxTD00 + OApYWTJMPTQ5ClhZM0w9NTAKWFk0TD01MQpYWTFUPTUyClhZMlQ9NTMKWFkzVD01NApYWTRUPTU1Ck9TQzE9NTYKT1NDMj01NwpPU0MzPTU4Ck9TQzQ9NTkKTVNFRzE9 + NjAKTVNFRzI9NjEKTVNFRzM9NjIKTVNFRzQ9NjMKTVNFRzU9NjQKTVNFRzY9NjUKTVNFRzc9NjYKTVNFRzg9NjcKUmV2MT02OApQbjM9MTYKUG40PTAKUG41PTAKUG42 + PTAKUG43PTAKUG44PTAKUG45PTAKUG4xMD0wClBuMTE9MApSYWNrMD02OQpSYWNrMT03MAojY209TGF5TWVtCkxheU1lbTE9MApMYXlNZW0yPTAKTGF5TWVtMz0wCkxh + eU1lbTQ9MApMYXlNZW01PTAKCgoKLy8gU2VjdGlvbiBmb3IgdWdseSBjb21wcmVzc2VkIGJpbmFyeSBEYXRhCi8vIERPTidUIFRPVUNIIFRISVMKCiQkJCQ3NzM0MAo/ + YWFhYWlhZHA6amtqampqbG86YWFhYWlhbHA6Z2RnY2VuZWI6cG5wcGhwZHA6cG1wcGhwZHA6YWFhYWFhZWE6CmNhYWFhYWFhOmFhYWFhYWRwOnBucHBocGxwOmFhOmRw + OmxwOmRvOmxvOmViOmVjOmNhOmVhOmdmOmRuOmhlOmcKYjpoazpiYTpsbjpncDpmZDpocDptYTpwcDpkYTpkYzpnbTppYTpuZCF0QWFiQTlmbWVtUWdkTVhmbWRiSEdN + aApkMWdqZ2RoZEEyMzl0SUExMGVpUkhmY0pnZEpnb0xYaGpjb2dpV2hhQTQ4dElBNzR0SUExMGdqa2JnYWloaWxpCmNjYmFpa2hkZ2FmcGRkamhta29ibWRtaGloaGds + YWdsbG1la2lsYmlwb21raHBmRmZpYW9YbW9mYWRrcGhtYlUKZGRjY2xtY2ZuaGdhVmxhYWlnaGRmcG1raVhla2dhY3BhaHBvamFwZ2Vma20xZW50YWVBMTFucGNiQXRB + YWJBOQplaVFoaE1oY2dlSFJnZE1oYzFBMjQzdFlBMTM4dEFhYkEyNjV0WUE2cUExMjd0WUE2cUExMjd0SUExMGdlSmdnCk1oZlhMSGhkZ2RNWEpBMjJxd0ExSTFBMVlJ + QTFrYUlBMVRJQTFvYUl0QWFjQTVxQTNxd0ExSTFBMVlJQTFrYUkKQTFUSUExb2FJQTJGQTFPRkExSEZBMVZGQTFJRkExZmFGQTFnYUZBMWhhRkExWUZBMWlpRkExamFG + QTFqaUZBMQprYUZBMWtpRkExbGFGQTFsaUZBMVRGQTFtaUZBMW5hRkExbmlGQTFvYUZBMW9pRkExcGFGQTFwaUZBMkdBMWFlCkdBMWFpR0ExYW1HQTFPR0ExYmVHQTFi + aUdBMWJtR0ExSEdBMWNlR0ExY2lHQTFjbUdBMVZHQTFkZUdBMWRpR0EKMWRtR0ExSUdBMWVlR0ExZWlHQTFlbUdBMWZhR0ExZmVHQTFmaUdBMWZtR0ExZ2FHQTFnZUdB + MWdpR0ExWEdBMQpoYUdBMUxHQTFoaUdBMWhtR0ExWUdBMWljR0ExaWVHQTFpZ0dBMWlpR0ExaWtHQTFpbUdBMWlvR0ExamFHQTFqCmNHQTFqZUdBMWpnR0ExamlHQTFq + a0dBMWptR0Exam9HQTFrYUdBMWtjR0Exa2VHQTFrZ0dBMWtpR0Exa2tHQTEKa21HQTFrb0dBMWxhR0ExbGNHQTFsZUdBMWxnR0ExbGlHQTFsa0dBMWxtR0ExRUdBMVRH + QTFtY0dBMW1lR0ExbQpnR0ExbWlHQTFta0dBMW1tR0ExbW9HQTFuYUdBMW5jR0ExbmVHQTFuZ0dBMW5pR0ExbmtHQTFubUdBMW5vR0ExCm9hR0Exb2NHQTFvZUdBMW9n + R0Exb2lHQTFva0dBMW9tR0Exb29HQTFwYUdBMXBjR0ExcGVHQTFwZ0dBMXBpR0EKMXBrR0ExcG1HQTFwb0d0VmFjQTVxQTNhZkEyYWJBMmFkQTJhYmFlQTI5d0ExM1lJ + QTEzVElBMTRGQTEzSEZBNwpyQTFUSUExMXFBN3JBMUkxQTdyQTFrYUlBN3JBMW9hSUE3ckExZ2FGQTdyQTFmYUZBN3JBMUlGQTdyQTFWRkE3CnJBMUhGQTdyQTFPRkE3 + ckExaWlGQTdyQTFqYUZBN3JBMWppRkE3ckExa2FGQTdyQTFraUZBN3JBMWxhRkE3ckEKMWxpRkE3ckExVEZBN3JBMW1pRkE3ckExbmFGQTdyQTFuaUZBN3JBMW9hRkE3 + ckExb2lGQTdyQTFwYUZBN3JBMQpwaUZBN3JBMTV0VmFjQTVxQTNhZkEyYWJBMmFkQTJhYmFlQTI5d0ExM1lJQTEzVElBMTRGQTEzSEZBN3JBMVRJCkExMXFBN3JBMUkx + QTdyQTFrYUlBN3JBMW9hSUE3ckExZ2FGQTdyQTFmYUZBN3JBMUlGQTdyQTFWRkE3ckExSEYKQTdyQTFPRkE3ckExaWlGQTdyQTFqYUZBN3JBMWppRkE3ckExa2FGQTdy + QTFraUZBN3JBMWxhRkE3ckExbGlGQQo3ckExVEZBN3JBMW1pRkE3ckExbmFGQTdyQTFuaUZBN3JBMW9hRkE3ckExb2lGQTdyQTFwYUZBN3JBMXBpRkE3CnJBMTV0VmFj + QTVxQTNhZkEyYWJBMmFkQTJhYmFlQTI5d0ExM1lJQTEzVElBMTRGQTEzSEZBN3JBMVRJQTExcUEKN3JBMUkxQTdyQTFrYUlBN3JBMW9hSUE3ckExZ2FGQTdyQTFmYUZB + N3JBMUlGQTdyQTFWRkE3ckExSEZBN3JBMQpPRkE3ckExaWlGQTdyQTFqYUZBN3JBMWppRkE3ckExa2FGQTdyQTFraUZBN3JBMWxhRkE3ckExbGlGQTdyQTFUCkZBN3JB + MW1pRkE3ckExbmFGQTdyQTFuaUZBN3JBMW9hRkE3ckExb2lGQTdyQTFwYUZBN3JBMXBpRkE3ckExNXQKVmFjQTVxQTNhZkEyYWJBMmFkQTJhYmFlQTI5d0ExM1lJQTEz + VElBMTRGQTEzSEZBN3JBMVRJQTExcUE3ckExSQoxQTdyQTFrYUlBN3JBMW9hSUE3ckExZ2FGQTdyQTFmYUZBN3JBMUlGQTdyQTFWRkE3ckExSEZBN3JBMU9GQTdyCkEx + aWlGQTdyQTFqYUZBN3JBMWppRkE3ckExa2FGQTdyQTFraUZBN3JBMWxhRkE3ckExbGlGQTdyQTFURkE3ckEKMW1pRkE3ckExbmFGQTdyQTFuaUZBN3JBMW9hRkE3ckEx + b2lGQTdyQTFwYUZBN3JBMXBpRkE3ckExNXRZQTZxQQoxMjd0WUE2cUExMjd0WUE2cUExMjd0WUE2cUExMjd0QWFjQTVxb2FjZ2NvRVRwYWVpS0FjbUtsbElpbDFQZ2Fn + Cm5iakJIZmJtcEtjaWFtbW9FWWZjaWRQb2FwYmhvRGhpYmNqbERhaWlra25EQXBob2tsbW9hb2NtbkVrYW9namkKS2RpZW1taERnYW9qYW5CZmllZWFrQ2xhZWhnYURt + bWFpZ2xDSHBnZWZFQWdncG1LaG1tZWJjQ2dpS2pkRW9hawpubmJFY2lYY3BDaGFta2JqRGdlb2lkaEJrYVZPRG9hSlpEZW1hb2JpQ2htbGxhZEJUcGVhZUNIcGtkZERu + YW5pCmNmRW9tZ2hlZUNsYWtpcGtEa2FqZGJqQm1pbW5uaURnYWVqb2dLSWRrSkVlaW1kZGVDT2thZW9Fb2Fjbm1pUG8KYVJhaEVuYWFjZmJCY2luZnBjRG9hb2tvY0tU + YWRta0VZY2JlblBmaVpqYkRvaWhhcGVEZGlhZmFlQkFha2piRApvYWZubWxQTG9mSENnYWpnam5FQWJpZ2NkbFRmZmVlS0hubmliS1ZqbWtjRWdhZ29pbkRiaWVkaW5F + VGFsYWpLCkFqaWprS0hqcGVvRFZhY2VrRGxhYm9maUVWb21sakRZbmVlbkVmbWNtZWlDbmliZ3BoRG5hYWtja0NZYnBmbksKSG1rcGZQaG1qYk9DbWliZmRlQllIbGRL + a21tcGNuQ2hhRGNnRFlwZWJiRWNpYWphZkJUbmdjY0tqaWRnb2tFTwpqaWtuRUlwZ2NiQnBpaGJuakVqaWtmWURZYWxtalBraWFjYmtDYm1sY0ZCWW1kb2NsbWthZGdi + cERBZGplbVBWCmJsYmhFcGlqanBkREFwZVZQT2ZhbmpEZ2lwblVFamVvYmFrQmNpbGNkYkNZZmVkakRoYWtnZ2FFZmVhbkdDcGEKa29hbkJIYWhIRWhtb2RkbUJtZWds + YmxCT2Fqa2ZEWWhubmdQY2lsbWttRGxpZGZwb0RUSFBFZ2lmaGFjQmZpbAprbGZESW5wZm9LZWloZmFkQ0FhbW9jUE9haGJvRGJpbGFtbkRJZmJoaERUYm1pbEtubWhn + SEJqYW1lSERWY25vCmpFa2FoZ2tqRWJpamFsZ0VkbWprZGdCWW9uYmlDdEFhY0E1cTFvZUtsY0VwbWtob29FVEFlZEVjbW5ncGdEbGkKcGhoYkNiaXBrZGZLZmFuZkdD + c2lpZGxwaEVzbWVvamNqQ2xpaW1kYkJjZ2NmZ2tCQW9wa2ZsbHBhbWJjbUNqZQpvcGFqQmFnaXBSQ2JlbW1taEVza2FpZGJsQ2phYWxraFBzSGxlYWJFVmtsYmNFaGNm + a0hDYm9wbWFjQm1tbmZoCmZCSGJpYmdEbWtraE9Db2VGb2NESWZlY25CWVhtaWxtcW5ha2hrZlBvaWRlaWdEcWhpbGJla0RsZWZmbWhEaGEKbmhva0VwYWhvZGdFc2Zj + bmZNQ25tYWQxQm5hY2lwb0VpbWxrb2hEaG9HYmNDa2Fob3BpRVhtZ2JnQm1tbWRwYwpFa21WbnBEaG1mb2FuQ0lsYmJqQmtlaGZtY0VrbWNpbWNFZ2FqZW9pRWRlbmxm + ZkNIT2JkS2FpZWthZ0ViZVlRCkNJZWhtbktuZ1RnZEJib2xqY2ZCVGVrYm1Cb2FraGtrS25na2RlaUNuYTFpbFBuaWtja2dFSWJlbmtFWGdscGUKRGxrYmdia0JuYWhj + aWtET2NtZ2VDbmNqZmZjQmlvQ2JnQnNkaW9nZGRDVmtsbGVLZG1hamtjRWthZ29taURnYQphb2xtS2Rrb3BNQmRnQmJlQmVpY2tQRHNtb2ZnYWtCY2lwa2NrRHNvaWdh + aWpEbG1vYWluRWZvaGJhbUJwZXBjCmhuQ2tha2JkZkRJaGJQbG1nZUltakRvYWJmZWxCbGliaGpsRG5paG1lcEViaU1ja0RqaWttQ0VsYWtqZmVCT2IKbENQZGVmbWts + RG9hY2poaEVobWRtYmJDYWNpY2JkQ2Jjb2lnaENtaWtibmNFa2FqbWRnRVZvbWpoS0RlaGJmQwpPZWhjaUJMcGhhbUJuYWdjb29LamdnbmFpQmhpZ29uakVBYmRpbUVt + ZWNvYmlCQWdqamhkbGtpVWVlQ2tpaWVvCnBEcGluZ2VqRWFpb25oakVzMW5lcGhraEVsaW1uYmlFcXRBYWNBNXFtaWZhYm1Cb2lib2llRWttbm1haENZa2IKaGFLSXBp + aWtFQW9qY2RkbWplbmpia0NuYWtvYmxDb2FubFNESWplaG5QaWVhamFiQkhicGNrREhjY25qRGZpWgpvZkVqYUpnaEVvYW9pZmhCY2llZ0dCY2lrYm9uRWVtZmVSQ2Fp + RGJkQ0lnbGFoQmFla2pkZ0JnaWFtcGxFaGFlCmRiaENJa25qZFBvZW9sZGVDZGlvb2liRWtpbGhlbkJoYW5iTkJWb2RobkVhaVZsZ0VBZ2xhcERwaWxnYWlDZ2kKY2ps + ZkVqZUhXQm5pZ2hiaEJUaWxuZkVqbWhoYW9Da2FsY2xsUE9vZGVkQ21pb2ViZkNwaWdrbm5EaWlsZmpsRApoaUpkZ0NsYWJjZ2FDbGFlcGVtRWNlaWNjZkNUcG1pY0Vn + aUlLQmNtamRiaUJiaWVvbnBFZWllcGJuQ0FqY2ZiCkVkbW9nV0NvaWtqa2ZFZmFwYWNoQlZmYnBjRGNlb25mZ0NjZWhhZGRCamlqb2dsQ2xhYWZiZ0JwZWRkZ29CSGIK + Z3BqUEFubGpsRWphbWVsZERnYVZwZEtuZUxkbEJZa2doYVBBY21vYmRtbm1jZ2VvQ29tYmxobEJrYWJvbWtFaQppa2hjaUNIZm9qblBwbWNlZm9DbGlhY2ZhQ1lmZWtm + S2thaW9uYUtibW9taGZCVG9lbm1Ea2loZEIxa2FtaWxoClBUa21nbFBoaWJoZmVCVGZtUkJwaWpnbGJFbGVnYWdrQ29hbWRsa0VuaWNwZWlCZ2VpbWhkQ2phbWpjbURn + YW8KbGFmRXBhaWNjb0JrYW5mbWlLaG1jZkhCQWlwaGFFQWZvaG9sbW1tb2NmZUJiZWZrZWlCZmFoZlpFZ2FjZWVmQwpibWJlYWdDY21rcGJmQmhhcG9lbEJIcG5ibUVP + YW1naEVlZWNlYWtCSEFjZERrYW9ubWxEWGdpYWVDa2VkZWJpCkNZb21ka0RnYWlpZGhFa21ha09CbmFtaldEaGFqbW1oREFnbE9DbG1WY2tDYmloamxmRElRZ2FEYmli + Z2RlQlQKbWZjaktlaWdsb3BEYmlqcGFjQ25hZGlib0NuYW9mYWNEaWlsY2loRUFwaWVpRHRBYWNBNXFBYW9iZ2xtb2llbQpuaUVraWFtaWJEa2FpZGVpRVlrbGVvRGZt + bXBlcENqZW5naGNDQWltZGZkbUhlaWRtRWhpUXBsRHBhY25MRUhiCmRhZERwYWxoZ2NDa21qaWVuQ0hoZFVQSG1rYmRDZ2FnbGRlRWplbGhjcENqaWhja2hFcGltb2Nj + QlluZW5wRHAKbWZuZ2tDbW1vbWZqQmhhRGFoQ2ZpZGJkZUJibWJmZWtCVG1iYmhQbGFrZ2llRW9hVG1jRFZCYWJFcGlvZm9pRApZaGFrbUVwYVFqYUVlbWdlYm1CamVp + bGNiQmNpbGRJQ0hqamRkQmptam9obkNmaWxqbmxEb2FpZG9sRG1pam5jCmhDaWVnamNpQ09uaGNtRHBhVm1uRUlkYmJqRG9tZGtkZUNlaWZvb21Fa2FmZ2NnQm5hbW9s + Z0RuZVpmZ0NwZWkKZGJmQmVpaWtnZENBa2ZVRGlpamZvcERwZWlqZGZDbGFoY2VnQ2tha2Jna0VjZVhiZUNsaWNqa3BFZ2lwZ2VmQgpsYW9taGxCVmttZm5FaWlqcGpl + RUFqb2duRGhhZ25uYUVUaGFYS2NtbGFibkNnaWpmY3BDVElsa0RlaW5laWVECmJlbGpTQ2xha25ibUJpaWZpWENoaWdrRURhZWRnYWZCaGFnbG9tRWhhb2diZ0NnYWNq + b2pQb2VuYWVrQ2thbmEKYmxDZmFpZm5wRG5pbnBqcERtbWZoZGZDaWlhZGZvQ1lpaW5lRWhtb2tmbENsZWFmZGJDT2hsT0JuaWlvVkJnZQpsZGJlQ2dlam5OQm5pam5i + akNwYW9iY21CbGVwZGZiQlRpaGhoS0FlZmFvRGlpS2ZjQm1taGRibkJvbWxrZGtDCmZpbWNlaEJnYWNmZnBDSGNoWktsYWdpY2tEaGFlbGZmQmxpa3BibUJhZWdjQkNl + ZVVEQmFpcGZSQkFvaUVkbEkKaWpmaktmbWVvTEJJZ2FvcERvaWltZXBCZmljaGllRExiZmJiQmphbGppZ0VBY2lwZ0VpaVltcERuYUZhZEVrYQpmYm9jRG5haWZsbUVl + aWlma2dFa2VmYWJlQm9paG5iZ0JuYW1qT0NMaWNnYUJlZWhnZW5DQWVnbGdEdGFtYWRBCjVxZWVlamZjZWhhZUEyYW1BMmVnZW5lcGRlQTNhbmJiQTFlZ2VuZXBkZEEz + YW5iY0ExZXBSZWRkYkFNaGpKYW8KYmJBMWVkUWduZ2NXQTJha2JiQTFSZ2lNaGFKZGJBSmFrYmJBMWZnZWRlZ2RkQU1oakphY2JjQTFSR2RiQTRhawpiYkExZXBSZWRk + ZUFIZGJkaWFvYmJBMWVuZ2poaWRiQTNhamxiQTFmZ2VkZWdkZUEzYWNiY0ExZW9RZ2poZEpkCmJBMWFvYmJBMWVvUWdqaGRKV0ExYW9iYkExZWdlbmVwV0FIV2RiYW5i + YkExZWRRZ25nY2RiQTJha2JiQTFlcFIKZWRXQTNhb2JiQTFlcFJlZGRkQTNhb2JiQTFlZ2VuZXBkYkEzYW5iYkExUmdpTWhhSldBMWFrYmNBMWZnZWRlZwpXQTNhY2Ji + QTFlbmdqaGlXQUhXZGVha2hpQTFlbmdqaGlkZEEzYWtoYkExZW5namhpZGVBM2FraGNBMWZjZ2pnCm9naGRiQTJha0xBMWZjZ2pnb2doV0EyYWtoaUExZmllbmVnZGJB + TWhqSmFraGJBMWZpZW5lZ1dBM2FraGNBMWYKZ2VkZWdkYkEzYWNiYkExUkdXQWhjSFdkaGFrYmlBMWVlZ2poZExkYkEyYWtiYkExZWVnamhkTFdBMmFrYmNBMQplZ1FY + Z2VkYkEyYWtiZUExZWdRWGdlV0EyYWtiaUE4SkEzNFZBMjhIZGRkYkEyM2VtTWhqSkEzMXhBMjQzdGFlCnhBMndBM3BqUlVEcTJBMTExZ2dkZWprRFUxU0JxMUExMTFl + a1JuZUtwb1VTQnExQTExMW5vY2hjY0thY2ttT0IKSXBoT0JxQTExNXlwbmZmQUJxQTExNXBvUlVEcGlVMURxQTExNXBsa2hwb0RwaFJVRHFBMTE1cGFlbm9kRG9scApq + b2REcUExMTVvZXBkbWhEb2FqcG1pRHFBMTE1bmpqamttRG5lZWZrbkRxQTExNW1vQmpiRG1pb2xqYkRxQTExCjVpZW1sZ2xETmNkZ25EcUExMTJ5a2xjakRkZmFkY2xE + cUExMTVhbWhnbmVLcGdjZm5oS3FBMTE1VG5lb2tkbWcKYWplcGZkbXFBMTA4d1BnY2RsbGFhY2NtZG1qZVZlcGRtcUExMDd1c3BpVVNCcTFBMTA3dXN2cTFBMTA3dXNx + MgpBMTA3dXNsZWJnbGNsbGVnaW5tZWRscUExMDd1c0EzV2JlZmdkbXFBMTA3dXNnbk5aRUhwZW9iRHFBMTA3dXN6CnZxQTEwN3VzenZxQTEwN3VzenZxQTEwN3VzenZx + QTEwN3VzenZxQTEwN3VzcG1VU0N2cUExMDd1c3BtVVNDdnEKQTEwN3VzcG1VU0N2cUExMDd1c3BtVVNDdnFBMTA3dXBtVVNDc3ZxQTExMWVtb2Jwa0VzQTExOW5hbW1q + bUVpZQpvbGZiRUExMTllbW9icGtQQTFZRWJia29laEVBMTE1ZW1vYk5QQTJFQTE2NTFuZWtkVkJvYU5qZURBMTE5a2FoCmFiZkJqaWpqZGpEQTExOW5pTnBlRG1ubW1h + bURBMTE5bmlOTERvYU5iZUtBMTY1NWFmQTJ0T0E2d2ZpZW1kbWMKZVMzZ2FlaVZiaVMzdEF4QTJ3QTgxOTF0eEE3ZmtXSGZoTWhnSkxNZ2NYSkhlZmhpaGFRaGNMQTRZ + cG5VNXRhZQp4QTJ3QTNwalJVRHEyQTExMWdnZGVqa0RVMVNCcTFBMTExZWtSbmVLcG9VU0JxMUExMTFla1JmZUthY2ttT0JJCnBoT0JxQTExNXlwbmZmQUJxQTExNXBv + UlVEcGlVMURxQTExNXBsa2hwb0RwaFJVRHFBMTE1cGFlbm9kRG9scGoKb2REcUExMTVvZXBkbWhEb2FqcG1pRHFBMTE1bmpqamttRG5lZWZrbkRxQTExNW1vQmpiRG1p + b2xqYkRxQTExNQppZW1sZ2xETmNkZ25EcUExMTJ5a2xjakRkZmFkY2xEcUExMTVhbWhnbmVLcGdjZm5oS3FBMTE1VG5lb2tkbWdhCmplcGZkbXFBMTA4d1BnY2RsbGFh + Y2NtZG1qZVZlcGRtcUExMDd1c3BpVVNCcTFBMTA3dXN2cTFBMTA3dXNxMkEKMTA3dXNsZWJnbGNsbGVnaW5tZWRscUExMDd1c0EzV2JlZmdkbXFBMTA3dXNnbk5aRUhw + ZW9iRHFBMTA3dXN6dgpxQTEwN3VzenZxQTEwN3VzenZxQTEwN3VzenZxQTEwN3VzenZxQTEwN3VzcG1VU0N2cUExMDd1c3BtVVNDdnFBCjEwN3VzcG1VU0N2cUExMDd1 + c3BtVVNDdnFBMTA3dXBtVVNDc3ZxQTExMWVtb2Jwa0VzQTExOW5hbW1qbUVpZW8KbGZiRUExMTllbW9icGtQQTFZRWJia29laEVBMTE1ZW1vYk5QQTJFQTE2NTFuZWtk + VkJvYU5qZURBMTE5a2FoYQpiZkJqaWpqZGpEQTExOW5pTnBlRG1ubW1hbURBMTE5bmlOTERvYU5iZUtBMTY1NWFmQTJ0T0E2d2ZpZW1kbWNlClMzZ2FlaVZiaVMzdEF4 + QTJ3QTgxOTF0eEE3ZmtXSGZoTWhnSkxNZ2NYSkhlZmhpaGFRaGNMQTRwYXBoVTV0YWUKeEEyd0EzcGpSVURxMkExMTFnZ2RlamtEVTFTQnExQTExMWVrUm5lS3BvVVNC + cTFBMTExZWtSZmVLYWNrbU9CSQpwaE9CcUExMTV5cG5mZkFCcUExMTVwb1JVRHBpVTFEcUExMTVwbGtocG9EcGhSVURxQTExNXBhZW5vZERvbHBqCm9kRHFBMTE1b2Vw + ZG1oRG9hanBtaURxQTExNW5qamprbURuZWVma25EcUExMTVtb0JqYkRtaW9samJEcUExMTUKaWVtbGdsRE5jZGduRHFBMTEyeWtsY2pEZGZhZGNsRHFBMTE1YW1oZ25l + S3BnY2ZuaEtxQTExNVRuZW9rZG1nYQpqZXBmZG1xQTEwOHdQZ2NkbGxhYWNjbWRtamVWZXBkbXFBMTA3dXNwaVVTQnExQTEwN3VzdnExQTEwN3VzcTJBCjEwN3VzbGVi + Z2xjbGxlZ2lubWVkbHFBMTA3dXNBM1diZWZnZG1xQTEwN3VzZ25OWkVIcGVvYkRxQTEwN3VzenYKcUExMDd1c3p2cUExMDd1c3p2cUExMDd1c3p2cUExMDd1c3p2cUEx + MDd1c3BtVVNDdnFBMTA3dXNwbVVTQ3ZxQQoxMDd1c3BtVVNDdnFBMTA3dXNwbVVTQ3ZxQTEwN3VwbVVTQ3N2cUExMTFlbW9icGtFc0ExMTluYW1tam1FaWVvCmxmYkVB + MTE5ZW1vYnBrUEExWUViYmtvZWhFQTExNWVtb2JOUEEyRUExNjUxbmVrZFZCb2FOamVEQTExOWthaGEKYmZCamlqamRqREExMTluaU5wZURtbm1tYW1EQTExOW5pTkxE + b2FOYmVLQTE2NTVhZkEydE9BNndmaWVtZG1jZQpTM2dhZWlWYmlTM3RBeEEyd0E4MTkxdHhBN2ZrV0hmaE1oZ0pMTWdjWEpIZWZoaWhhUWhjTEE0VmFnQTV0YWV4CkEy + d0E3eXFBMTE5eXFBMTE5eXFBMTE5eXFBMTE5eXFBMTE5eXFBMTE5eXFBMTE5eXFBMTE5eXFBMTE5eXFBMTEKOXlxQTExOXlxQTExOXlxQTExOXlxQTExOXlxQTExOXlx + QTExNXNBM3FBMTE1c0EzcUExMTVzQTNxQTExNXNBMwpxQTExNXNBM3FBMTE1c0EzcUExMTVzQTNxQTExNXNBM3FBMTE1c0EzcUExMTVzQTNxQTExNXNBM3FBMTE1c0Ez + CnFBMTE1c0EzcUExMTVzQTNxQTExNXNBM3FBMTE1c0EzcUE0MjA3YWVBMnRPQTZ3ZmllbWRtY2VTM2dhZWlWYmkKUzN0QXhBMndBM3FBN3FBMTlaamxmb0JBMTFOWmJs + REEzUG9qZW5CQTExUU5SQlpqbG5vS0E3b2tlblFCQTdjYgphbGZqQkEyN3FBMjdqbW5vTEJBNDdsY2phYWZCbGNqYWlmS2VkYmdsY2xtQTM1cUEzMWJnbGNqYURBMTFl + ZGJnCmxjS0ExNU5aYmxEZWRiZ2xjZG1BMTYzUG9qZW5DQTdva2VuUUNBMTFsY2phYWZFQTNOWmJsQ0ExOW1pR2JnQ0EKMjNjYmFsZmpDQTIyQ0ExNVFOUkNBMTFRTlpF + QTIzY2JhbGZqQ0EyM2xjamFpZkVBMTFnZWNiYWxDQTE5YWxmagplaUNBMTVqbW5vTENBMjducHBla2dFQTE5YWxmam1pRUEzMWVkYmdXRUExNTlRTlJCQTdiZ2xjT0JB + MTFlb1FOCkJBMTVmam1pR0JBMzVhbGZqZWlCQTM1YWxmamVpQkEzNVFOUkJBMzFraGRoS0JBMjdwZmtnZGhCQTI3NW9rZW4KUUNBMTFaamxmb0NBMTVpZ2NtZ2VDQTE1 + am1ub0xDQTM5am1ub0xDQTI3am1ub3BlRUEyM05aamxFQTUxTlpibApDQTE1ZmptaUdDQTI1NXlBMTFiZ2xjamFEQTdRTlJCQTIzWmpsZm9CQTE1WmpsZm9CQTM5Wmps + Zm9CQTExZGlQCmdqQkEzNWZqbWlHQkExMWdlY2JhbEJBMzFqbW5vTEJBMjc5bGNqYWlmRUExMWRpUGdqQ0ExMU5aYmxDQTM1amIKaWZjbUNBMzViZ2xjT0NBM2JnbGNP + Q0E3YmdsY09DQTE1b2tlblFDQTMwQ0ExMWdlY2JhbENBMTFiZ2xjamFFQQo0N3Bma2dkaENBMjM5a2hkaFBEQTNlZGJnV0tRTlJCQTE5b2tlblFCQTM5TlpibEJBMjNt + aUdiZ0JBMjNlb1FOCkJBMjNOWmJsQkExOU5aYmxCQTducHBlY2dCQTE5bWlHYmdCQTI4M1pqbGZvRWRpUG9qRUEyM2VkYmdXQ0ExOXMKQTExbnBwZWNnQ0ExMXNBMzVn + ZWNiYWxDQTNzQTdnZWNiYWxDQTM5amJpZmNtQ0EzMTVqbW5vTERBMjdva2VuUQpCQTE5Wmpsbm9EQTNiZ2xjT0JBMTVjYmFsZmpCQTE5Wmpsbm9EQTE5ZGlQZ2pCQTE5 + am1ub3BlREExOW9rZW5RCkJBMTltaUdiZ0JBMTlkaVBnakJBMjgzUG9qZW5DQTdsY2phaWZFam1ub0xFQTNqbW5vTENsY2phYWZFQTExYWwKZmplaUNBMTVraGRoS0NB + MjNQb2plbkNBMTliZ2xjT0NBMTVhbGZqZWlDQTE1UU5aRUExOVBvamVuQ0ExOWxjagphaWZFQTExZ2VjYmFsQ0ExOWFsZmplaUNBMTVqbW5vTENBMjducHBla2dFQTE5 + YWxmam1pRUEzMWVkYmdXRUExCjUxY25nZWNiQkEzUU5SQkE3am1ub0xCUU5aREEzTlpqbERBMTlmam1pR0JBN3FBMjNhbGZqZWlCQTdnZWNiYWwKQkEyM2FsZmplaUJB + MzVRTlJCQTMxa2hkaEtCQTI3cGZrZ2RoQkEyNjdtaUdiZ0NBMjNqbW5vcGVFQTNkaVBnagpDQTIzTlpibENBMTlaamxmb0NBMTVnZWNiYWxDQTNzQTdsY2phYWZDQTdz + QTdnZWNiYWxDQTM1NWptbm9MRFFOClJCQTdiZ2xjT0JBM2RpUGdqQkEzZW9RTkJBMTVmam1pR0JBMjdxQTQzYWxmamVpQkE3YmdsY09CQTNkaVBvakQKQTExcUE3bWlH + YmdCQTNRTlpEQTE1a2hkaEtCQTE5b2tlblFCQTI3NWVkYmdsY0VBM29rZW5RQ0ExMVpqbGZvQwpBMTVpZ2NtZ2VDQTdwZmtnZGhDQTNqbW5vTENBMTFzQTIzam1ub0xD + QTI3am1ub3BlRUEyM05aamxFQTUxTlpiCmxDQTE1ZmptaUdDQTI1NXlBMTVaamxmb0JBN3FBMjNxQTExWmpsZm9CQTM5WmpsZm9CQTExZGlQZ2pCQTI3am0Kbm9MQkEx + MXlBMTVlb1FOQkE3WmpsZm9CQTdnZWNiYWxCQTdqYmlmY21CQTI3MWptbm9MQ0E3ZW9RTkNBN2ptbgpvTENBN3NBN2VvUU5DWmpsbm9QQTNzQTExc0ExMXNBN3NBN2Vv + UU5DQTE1c0Ezc0ExNXNBMTFzQTM1cGZrZ2RoCkNBNDdzQTI3YmdsY09DQTE5OXR4QTdma1dIZmhNaGdKTE1nY1hKSGVmaGloYVFoY0xBNGJsYmVBNXRPQTZ3ZmMKZmJl + ZkFTNGhvSUJTM3RPQTZ3ZmNmYmVmQVM0aG9JQlMzdE9BNndnbjFkZUEyUzJob0lCUzN0T0E2d2ZjZmJlZgpBUzRob0lCUzN0T0E2d2ZjZmJlZkFTNGhvSUJTM3RPQTZ3 + ZmNmYmVmQVM0aG9JQlMzdGFtYWRBNXFlZWVqZmNlCmhhZEEyYWdBMmVmZmJkYkFlbU1oakphamJiQTFmZ2VkZWdkZkEzYWNiYkExUmdpTWhhSmRkQTFha2JiQTFlZFEK + Z25oYVdBMmFrYmJBMWZpZW5lZ2RkQUhXQWFqaGJBMWZnZWRlZ2RnQTNhY2JiQTFlZUpYTWhqV0ExYWtiYkExZQplZ2poZExkZUEyYWtiaUExZW5RZ2VlZ2ZpZGJBV2Fr + YmVBMVJnaU1oYUpkZUExYWtiYkExZW5namhpZGdBM2FrCmhiQTFlZFFnbmhhZGJBMmFrYmJBMWVuUWdlZWdmaVdBMWFrYmJBMWVvaGZmY0poZ2RiQTFhamFiT0FlZUpY + TWgKamRiQTFhamFiQTEzZW5namhpZGZBTWhqSmFraGJBMWVmZmJXQTRhamJjQTFSR2RkQTRhamJlQTJNaGpKaGNIZAppQTRmY0poZ2RiQTNha2JiT0FmY2dqZ29naGRk + QTJha2hiQTFlZWdqaGRMZGRBMmFrYmVBMTdlbU1oakpBMjhNCmhqSmhjSGRiMUEyOEhkYldBMjNlbU1oakpBMjhNaGpKaGNIZGJkZUEyOEhkYmRmQTIzZW1NaGpKQTI4 + TWhqSmgKY0hkYmRoQTI4SGRiZGlBMjNlbU1oakpBMjhNaGpKaGNIV1ZBMjhIV2RiQTIzZW1NaGpKQTI4TWhqSmhjSFdkZApBMjhIV2RlQTE5dHhBN2VwaGRnZGdqWDFN + TFFoY2hkQTIwdHhBN2VnZ2pYTEpoY2hkQTE2ZWhNTEpBM3R4QTdlCmZnb2hnSlhRaGFKaGRBMjJ0eEE3ZWdmaUEyMWZnSlhRZ2RnakxoanRBYWJBOWZpS2ZjSkxoZmdv + SkhkYmFrZmoKS2ZjSkxoZmdvSkhXQTIzNHRBYWJBOWZpS2VnZW5IZWVKaGFMZ2lha2ZqS2VpZ2pnaGdpaGRBMjM3dEFhYkE5ZgppS1JMTWdkMU1MUWFrZmpLZmNKWEpN + aGRKQTIzNXRBYWJBOWZpS2VlSlhNaGpha2ZqS2ZjSmhnSmhjZ2NBMjM5CnR4QTdlZ2dqZ29jblJNaGhjbmZhaGZYaGRKQTlUa2lpZWthaW1haEFhYmFkdHhBN2VnZ2pn + b2NuUk1oaGNuZmEKaGZYaGRKQTlUZ25na2JpcGFqa2JpYmtjaXR4QTdlZ2dqZ29jblJNaGhjbmZhaGZYaGRKQTlUZ25na2JpcGFqawpiaWJrY2l0eEE3R0pYMWhkSGVn + WGdqaGExSmhjQTEwd2pnZmpjZVV0eEE3Y28yQTRlaGNkY2pBM3BpbmVkaENoCmZpblRHT2VoY2RjakEzcGl0eEE3Z2VKZ2dNaGZYTEExNmVmZ29oZ2RkQTN0eEE3Z2VK + Z2dNaGZYTEExNmVmZ28KaGdkZUEzdHhBN2dlSmdnTWhmWExBMTZlblJlZmVoZGJBMnR4QThKZ2dNaGZYTEEyNHR4QThKZ2dNaGZYTEEyNAp0eEE4SmdnTWhmWExBMjR0 + eEE4SmdnTWhmWExBMjR0eEE3Z2VKZ2dNaGZYTEExNmVuUmVmZWhXQTJ0QWFiQThHCkEyNTV0QWFiQThHQTI1NT0xMDAxMjY3CgAA + AEhTIFJlY2VudGx5ABAAAAA= + > + PRESETNAME "HS Recently" + FLOATPOS 0 0 0 0 + FXID {5FC29448-28B7-47EF-B932-3F17B92BC138} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-350-less-feedback.rpp b/plugin-reaper-realearn/resources/test-projects/issue-350-less-feedback.rpp new file mode 100644 index 0000000..3f701fe --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-350-less-feedback.rpp @@ -0,0 +1,174 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 16 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAywEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4xIiwiaWQiOiJhRG5ldkV5YSIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJmZWVkYmFja0RldmljZUlkIjoiZngt + b3V0cHV0IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjBlZjBkODA0LTE5MGEtNGVlZC1iNzQ1 + LWZkZjdhYzI2ZWY4OSIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {1D91D40C-047E-40FD-8F6C-3DE04E723A20} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {CB4189B4-360F-4597-8C79-02F1B41F990E} + WAK 0 0 + > + + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-350-nervous-faders.rpp b/plugin-reaper-realearn/resources/test-projects/issue-350-nervous-faders.rpp new file mode 100644 index 0000000..e1e716a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-350-nervous-faders.rpp @@ -0,0 +1,929 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAoisBAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi45LjAiLCJpZCI6IkdHT0tJb0lWIiwiY29udHJvbERldmljZUlkIjoiMTUiLCJmZWVkYmFja0RldmljZUlkIjoiMTYiLCJkZWZhdWx0R3JvdXAi + Ont9LCJncm91cHMiOlt7ImlkIjoiNDYwYzgxNjQtZWJmNi00NWIwLWIyMjEtYTJmZTc0ZTg4ODg3IiwibmFtZSI6IkNvbmRpdGlvbmFsIHN3aXRjaGVzIn0seyJpZCI6 + ImJlNzI3ZmRjLWI0ZjktNGY3MS05OTQ0LTAyMmZlNGRiYWRhYyIsIm5hbWUiOiJWb2x1bWUifSx7ImlkIjoiOWI1NGNmNDQtZDIxNC00ZGI3LWFiMDctNjg0ZWI0ODE0 + NTcwIiwibmFtZSI6IlBhbiJ9LHsiaWQiOiJmM2RkNGViOS0xZjZlLTRiNzYtODlmNi02YzQxNzg3M2QwZDQiLCJuYW1lIjoiUGFuIHJlc2V0In0seyJpZCI6ImI2NGVl + NTQ2LTY2YzktNDdjMC04Mjc2LTMwYWYyYWM1OGE1MSIsIm5hbWUiOiJTZWxlY3Rpb24ifSx7ImlkIjoiYTAyMmE4NTQtM2M4Yy00YjU3LWJhNGQtNmY0YzQyMmNmZmRm + IiwibmFtZSI6Ik11dGUifSx7ImlkIjoiNTEzYjcxZmItNzI4Ni00OGI0LTk0NDctZjRmZWM0NTM3ZjRhIiwibmFtZSI6IlNvbG8ifSx7ImlkIjoiZDJiNjlkNmUtOTk5 + Zi00Nzc3LWFlMzktNGIwYTEwOGEzZjA0IiwibmFtZSI6IkFybSJ9LHsiaWQiOiI0Y2I2N2I0ZC1mMzk1LTQyZWMtYjFiNy1iYjE0ZWMzYzM4NDgiLCJuYW1lIjoiVG91 + Y2gifSx7ImlkIjoiMzY4OGFlNzMtYjM5ZS00ODM2LWJjMDItZDA5YjI2YjkyZTNkIiwibmFtZSI6IlRyYW5zcG9ydCJ9LHsiaWQiOiJkNmFiNWE3ZS1mMWFkLTQ4ZjQt + OGY1MS0zZDYzZTI2ZjAwOTYiLCJuYW1lIjoiWm9vbSIsImFjdGl2YXRpb25UeXBlIjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgi + OjIsImlzT24iOnRydWV9fSx7ImlkIjoiN2NhYzIwNjYtZjA5ZS00YzY5LWJiYzMtOGVhODZiZDM4MDM2IiwibmFtZSI6Ik1hc3RlciJ9LHsiaWQiOiJkMGQ4ODk1Mi1j + YTcwLTQ5OWUtOGM2ZS0zYjk0ZWFlZmJiMmEiLCJuYW1lIjoiU2Nyb2xsIiwiYWN0aXZhdGlvblR5cGUiOiJtb2RpZmllcnMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsi + cGFyYW1JbmRleCI6MiwiaXNPbiI6ZmFsc2V9fSx7ImlkIjoiYjUyZmYzZTgtN2NiOC00ZWJkLWIyNzYtM2U5YjQ5MGUwMTM4IiwibmFtZSI6IkxDRCJ9XSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwibmFtZSI6IkZh + ZGVyIn0seyJpZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsIm5hbWUiOiJWLVBvdCJ9LHsiaWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFj + Zi1hN2I4YmJiYTM5OTIiLCJuYW1lIjoiVi1TZWxlY3QifSx7ImlkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwibmFtZSI6IkZhZGVyIFRv + dWNoIn0seyJpZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1 + ZDMtMzMyNjFlYWI4MzYzIiwibmFtZSI6Ik11dGUifSx7ImlkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4IiwibmFtZSI6IlNvbG8ifSx7Imlk + IjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwibmFtZSI6IlJlY29yZC1yZWFkeSJ9LHsiaWQiOiIzMWJmODJmZC0xZmNhLTQ2ZTgtYTgwYS1i + YjZjOGI4OTE3ODgiLCJuYW1lIjoiVi1Qb3QgTEVEcyJ9LHsiaWQiOiI3YjYxMDA4OS02ZTE0LTQxMmItYWEyNi1mY2ViNmFjOGJlMjIiLCJuYW1lIjoiTENEIn1dLCJt + YXBwaW5ncyI6W3siaWQiOiI1Y2ZkMmZmMC04NWNlLTRlNzAtOThlOC1lYjUzZTVlOTRiYjEiLCJuYW1lIjoiQmFuayAtIiwiZ3JvdXBJZCI6IjQ2MGM4MTY0LWViZjYt + NDViMC1iMjIxLWEyZmU3NGU4ODg4NyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLWxlZnQifSwibW9kZSI6eyJ0eXBlIjoxLCJt + aW5TdGVwU2l6ZSI6MC4wMDA4LCJtYXhTdGVwU2l6ZSI6MC4wMDA4LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjA3YjAyMDJmLWI4Yjgt + NDhlNC1hNTMwLWYyMjg0NTA4NjRmMCIsIm5hbWUiOiJCYW5rIC0gZmFzdGVyIiwiZ3JvdXBJZCI6IjQ2MGM4MTY0LWViZjYtNDViMC1iMjIxLWEyZmU3NGU4ODg4NyIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLWxlZnQifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDA4LCJtYXhT + dGVwU2l6ZSI6MC4wMDA4LCJtaW5QcmVzc01pbGxpcyI6MzAwLCJtYXhQcmVzc01pbGxpcyI6MzAwLCJ0dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1 + ZSwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMGJhYWI5MWYtMmM0ZS00M2FlLThjYWMtZGNhNzI3OTYzYjQ2IiwibmFtZSI6IkJhbmsgKyIsImdy + b3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRlODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1yaWdo + dCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDgsIm1heFN0ZXBTaXplIjowLjAwMDh9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjBhN2UxMjJjLTNlZGEtNDM1NS05MDZhLWEwNTdlZWZhNTE1YiIsIm5h + bWUiOiJCYW5rICsgZmFzdGVyIiwiZ3JvdXBJZCI6IjQ2MGM4MTY0LWViZjYtNDViMC1iMjIxLWEyZmU3NGU4ODg4NyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwOCwibWF4U3RlcFNpemUiOjAuMDAwOCwibWluUHJlc3NN + aWxsaXMiOjMwMCwibWF4UHJlc3NNaWxsaXMiOjMwMCwidHVyYm9SYXRlIjoxMDAsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJjZjQ1Njg5YS01NTM3LTQ2NWQtYWQ0NC00NjBkYmViZjQ4MDIi + LCJuYW1lIjoiVHJhY2sgLSIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRlODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gtbGVmdCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDEsIm1heFN0ZXBTaXplIjowLjAwMDEsInJldmVyc2VJc0VuYWJs + ZWQiOnRydWV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJh + Y2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNzY0NjViNWYtYzliMy00YWE2LWJhNzctYjg5MTAyMWNmMzhlIiwibmFtZSI6IlRyYWNrIC0gZmFzdGVyIiwiZ3JvdXBJ + ZCI6IjQ2MGM4MTY0LWViZjYtNDViMC1iMjIxLWEyZmU3NGU4ODg4NyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0In0sIm1v + ZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMSwibWF4U3RlcFNpemUiOjAuMDAwMSwibWluUHJlc3NNaWxsaXMiOjMwMCwibWF4UHJlc3NNaWxsaXMiOjMw + MCwidHVyYm9SYXRlIjo2MCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZDQwZmZhNDQtYTQ0Ni00NDk5 + LWI5ZDYtNzg3YzVmZjhlMTg4IiwibmFtZSI6IlRyYWNrICsiLCJncm91cElkIjoiNDYwYzgxNjQtZWJmNi00NWIwLWIyMjEtYTJmZTc0ZTg4ODg3Iiwic291cmNlIjp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwMSwibWF4U3RlcFNpemUiOjAu + MDAwMX0sInRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiOTdk + Yjk5MWMtYTE1Yi00YzI3LWFlYjItMDRiMmFlMzI0MmMyIiwibmFtZSI6IlRyYWNrICsgZmFzdGVyIiwiZ3JvdXBJZCI6IjQ2MGM4MTY0LWViZjYtNDViMC1iMjIxLWEy + ZmU3NGU4ODg4NyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjow + LjAwMDEsIm1heFN0ZXBTaXplIjowLjAwMDEsIm1pblByZXNzTWlsbGlzIjozMDAsIm1heFByZXNzTWlsbGlzIjozMDAsInR1cmJvUmF0ZSI6NjAsImZpcmVNb2RlIjoi + dHVyYm8ifSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjQ5ZDhiMDMwLWYyYjctNDZjNi04NzcyLTI2OGFlNDFhZjQwMyIsIm5hbWUiOiJUcjEgVm9sIiwiZ3JvdXBJZCI6ImJlNzI3 + ZmRjLWI0ZjktNGY3MS05OTQ0LTAyMmZlNGRiYWRhYyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIifSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0V4cHJl + c3Npb24iOiJwMSAqIDEwMDAwICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7Imlk + IjoiYjZlYzNmYmYtOWFlYi00ODhkLTljNzItMGJlYzMwMzBiMGIxIiwibmFtZSI6IlRyMiBWb2wiLCJncm91cElkIjoiYmU3MjdmZGMtYjRmOS00ZjcxLTk5NDQtMDIy + ZmU0ZGJhZGFjIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAx + IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJkZDU4YzEzNC00MjE0LTRmMjQt + OWNjYy0xNWM4YTM1NjFjMDEiLCJuYW1lIjoiVHIzIFZvbCIsImdyb3VwSWQiOiJiZTcyN2ZkYy1iNGY5LTRmNzEtOTk0NC0wMjJmZTRkYmFkYWMiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gzL2ZhZGVyIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDIiLCJmeEFuY2hvciI6ImlkIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjYzZGZkMjU2LTE3MDUtNDk0MS05ZDM1LTNlNDQ3NmJhM2VlMiIsIm5h + bWUiOiJUcjQgVm9sIiwiZ3JvdXBJZCI6ImJlNzI3ZmRjLWI0ZjktNGY3MS05OTQ0LTAyMmZlNGRiYWRhYyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIifSwibW9kZSI6e30s + InRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNTM4YTM0MDgtN2Q0My00NWM1LTk0YmYtMDIwZDczODJjOTRmIiwibmFtZSI6IlRyNSBWb2wiLCJncm91cElk + IjoiYmU3MjdmZGMtYjRmOS00ZjcxLTk5NDQtMDIyZmU0ZGJhZGFjIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRy + YWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZX19LHsiaWQiOiJmMDA0OWUwMS1kYjVlLTQzMzEtOWMyZC04YzFjMTk4MTY4OTEiLCJuYW1lIjoiVHI2IFZvbCIsImdyb3VwSWQiOiJiZTcyN2ZkYy1iNGY5LTRmNzEt + OTk0NC0wMjJmZTRkYmFkYWMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAx + MDAwMCArIDUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImE5ODBlZDY1LTJj + YmMtNGVkNC04MGZhLWEyNDY4ZmY4YWVmZSIsIm5hbWUiOiJUcjcgVm9sIiwiZ3JvdXBJZCI6ImJlNzI3ZmRjLWI0ZjktNGY3MS05OTQ0LTAyMmZlNGRiYWRhYyIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDcvZmFkZXIifSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNiIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNTQ5YTBmM2EtOWJlNC00Y2I4LTk2OWYtNTExYjVkYTgz + MGM3IiwibmFtZSI6IlRyOCBWb2wiLCJncm91cElkIjoiYmU3MjdmZGMtYjRmOS00ZjcxLTk5NDQtMDIyZmU0ZGJhZGFjIiwic291cmNlIjp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlciJ9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA3IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJiZjY2ZjdkNS03NGJlLTQwOTgtYmQzMy03NWY1MzYzNzE1YjciLCJuYW1lIjoiVHIxIFBhbiIs + Imdyb3VwSWQiOiI5YjU0Y2Y0NC1kMjE0LTRkYjctYWIwNy02ODRlYjQ4MTQ1NzAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAu + MDUsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAwIiwiZnhBbmNob3IiOiJpZCIsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI0ZTdiOWY5ZS0yOGM1LTQ2ZDgtYWU4ZS03OTFiYTY3NDViNjQiLCJu + YW1lIjoiVHIyIFBhbiIsImdyb3VwSWQiOiI5YjU0Y2Y0NC1kMjE0LTRkYjctYWIwNy02ODRlYjQ4MTQ1NzAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90In0sIm1vZGUiOnsi + bWluU3RlcFNpemUiOjAuMDUsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAxIiwiZnhB + bmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJlOWEwYWVmMC0zM2YxLTRlODUtYWU2ZC1m + YWQ5ZWY4MjBlYzkiLCJuYW1lIjoiVHIzIFBhbiIsImdyb3VwSWQiOiI5YjU0Y2Y0NC1kMjE0LTRkYjctYWIwNy02ODRlYjQ4MTQ1NzAiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Yt + cG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDUsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICog + MTAwMDAgKyAyIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJmOWE2MWM0Ny01 + ODNjLTQwOTQtOGFiMC0zYjA1NTU0MDczM2QiLCJuYW1lIjoiVHI0IFBhbiIsImdyb3VwSWQiOiI5YjU0Y2Y0NC1kMjE0LTRkYjctYWIwNy02ODRlYjQ4MTQ1NzAiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g0L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDUsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhw + cmVzc2lvbiI6InAxICogMTAwMDAgKyAzIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsi + aWQiOiI4YTU3Y2VmZS0wZmI5LTQxZWItYmU0Zi00ZGMzYjUzYTEwM2YiLCJuYW1lIjoiVHI1IFBhbiIsImdyb3VwSWQiOiI5YjU0Y2Y0NC1kMjE0LTRkYjctYWIwNy02 + ODRlYjQ4MTQ1NzAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDUsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5 + cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZX19LHsiaWQiOiIwYjMzYTFjMC1hNTU0LTQ2ZDUtOTE1YS0yODMwOTE2ZjY1ZmQiLCJuYW1lIjoiVHI2IFBhbiIsImdyb3VwSWQiOiI5YjU0Y2Y0NC1k + MjE0LTRkYjctYWIwNy02ODRlYjQ4MTQ1NzAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDUsIm1heFN0ZXBTaXplIjowLjA1 + fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA1IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI3ZGMzMTI5Yy1jNjQ3LTRmYjgtYjI0YS1mMzYwMmNkNDg1NzIiLCJuYW1lIjoiVHI3IFBhbiIsImdyb3Vw + SWQiOiI5YjU0Y2Y0NC1kMjE0LTRkYjctYWIwNy02ODRlYjQ4MTQ1NzAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDUsIm1h + eFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA2IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJlNmZjMDQwZi1lZjgyLTQ4MTgtYmIyZS03MDU4NjYwNWFlOGEiLCJuYW1lIjoi + VHI4IFBhbiIsImdyb3VwSWQiOiI5YjU0Y2Y0NC1kMjE0LTRkYjctYWIwNy02ODRlYjQ4MTQ1NzAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90In0sIm1vZGUiOnsibWluU3Rl + cFNpemUiOjAuMDUsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA3IiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIzNTVkZmZmNy1mZGEzLTQwN2QtOGY0MC00ZmJhZTU1 + MTFkYzUiLCJuYW1lIjoiVHIxIFBhbiBSZXNldCIsImdyb3VwSWQiOiJmM2RkNGViOS0xZjZlLTRiNzYtODlmNi02YzQxNzg3M2QwZDQiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNX0sInRhcmdl + dCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI2MjkzNmQ4OS1lNTJiLTRmMWYtYWUzYS01MzVlNGRlNmJmODciLCJuYW1l + IjoiVHIyIFBhbiBSZXNldCIsImdyb3VwSWQiOiJmM2RkNGViOS0xZjZlLTRiNzYtODlmNi02YzQxNzg3M2QwZDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjo0 + LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJkY2Y2MmM2Zi1kMTQ4LTQxMjItODNjYS1jMTZmNzg4MTZjZGQiLCJuYW1lIjoiVHIzIFBhbiBS + ZXNldCIsImdyb3VwSWQiOiJmM2RkNGViOS0xZjZlLTRiNzYtODlmNi02YzQxNzg3M2QwZDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gzL3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJl + c3Npb24iOiJwMSAqIDEwMDAwICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5NWRjNTc5NC1lMTcxLTRiNjEtODUzMy0zYTMzMTA3M2JjYmQiLCJuYW1lIjoiVHI0IFBhbiBSZXNldCIsImdyb3Vw + SWQiOiJmM2RkNGViOS0xZjZlLTRiNzYtODlmNi02YzQxNzg3M2QwZDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3Ytc2VsZWN0 + In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAq + IDEwMDAwICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJhOGI2ZGJmMS1hZGY1LTQ4ZDQtYTU5OS05OGYwMTQwMGIzY2YiLCJuYW1lIjoiVHI1IFBhbiBSZXNldCIsImdyb3VwSWQiOiJmM2RkNGVi + OS0xZjZlLTRiNzYtODlmNi02YzQxNzg3M2QwZDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0In0sIm1vZGUiOnsi + bWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNCIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiI3OGEwZDVlMC1jNjlkLTQ1YjQtYWE3Mi1mMGM2YTM1NjZmODAiLCJuYW1lIjoiVHI2IFBhbiBSZXNldCIsImdyb3VwSWQiOiJmM2RkNGViOS0xZjZlLTRiNzYt + ODlmNi02YzQxNzg3M2QwZDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFs + dWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNSIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIzN2I3Nzhh + Mi1jMjQzLTQ3NzYtOWEzYy05MzczMDkyN2VkMzMiLCJuYW1lIjoiVHI3IFBhbiBSZXNldCIsImdyb3VwSWQiOiJmM2RkNGViOS0xZjZlLTRiNzYtODlmNi02YzQxNzg3 + M2QwZDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4 + VGFyZ2V0VmFsdWUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiZWY0NjI2NC1hNDlhLTQ2Nzgt + ODhmMi1hNzM2OTExNWU2ZTQiLCJuYW1lIjoiVHI4IFBhbiBSZXNldCIsImdyb3VwSWQiOiJmM2RkNGViOS0xZjZlLTRiNzYtODlmNi02YzQxNzg3M2QwZDQiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUi + OjAuNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIwZjAwNGMwZi00M2ZiLTQ3N2EtYmE2Yi1kYzY2Mzc5 + MzFiYTUiLCJuYW1lIjoiVHIxIFNlbGVjdGlvbiIsImdyb3VwSWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoicDEg + KiAxMDAwMCArIDAiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1 + ZX19LHsiaWQiOiJkMDU5ZDMyZS1jYzczLTRiYjMtOWU5OS0wMjBhMTA5ZGQwYzEiLCJuYW1lIjoiVHIyIFNlbGVjdGlvbiIsImdyb3VwSWQiOiJiNjRlZTU0Ni02NmM5 + LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjJ9 + LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZX19LHsiaWQiOiJlNjE0MDFmZi04NjZlLTRlOGEtYjQ5OC1jM2UxNWU3MTQ1YzUiLCJuYW1l + IjoiVHIzIFNlbGVjdGlvbiIsImdyb3VwSWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gzL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDIi + LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZX19LHsiaWQiOiI0 + YzRjNWI5NC1hODUyLTQ5YzYtOWE5Yi05YTAyNjBiMjRmMTgiLCJuYW1lIjoiVHI0IFNlbGVjdGlvbiIsImdyb3VwSWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0z + MGFmMmFjNThhNTEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsi + dHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDMiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNl + ZWtQbGF5Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZX19LHsiaWQiOiIwNzA0YzUwZC05YTI5LTRlODgtYWU0OC1iOTU2ZTNjMmU2NjIiLCJuYW1lIjoiVHI1IFNlbGVj + dGlvbiIsImdyb3VwSWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g1L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDQiLCJmeEFuY2hvciI6 + ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZX19LHsiaWQiOiJhYzYyZDVlNi05OTk4 + LTRmYTItOGQzYi1hY2M0ODJjNzk4MTkiLCJuYW1lIjoiVHI2IFNlbGVjdGlvbiIsImdyb3VwSWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJh + Y2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJzY3JvbGxNaXhlciI6dHJ1ZX19LHsiaWQiOiI3NDQwZjM5My1jMGVjLTQzY2MtOGMwNS0yZDIyMDQ3N2MzY2QiLCJuYW1lIjoiVHI3IFNlbGVjdGlvbiIsImdyb3Vw + SWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCJ9 + LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDYiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZX19LHsiaWQiOiI3MjQ3MDg1My1jZDZkLTRjMzgtYTNjNi00 + NjYxOWZmNTRmNzciLCJuYW1lIjoiVHI4IFNlbGVjdGlvbiIsImdyb3VwSWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9u + IjoicDEgKiAxMDAwMCArIDciLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzY3JvbGxNaXhl + ciI6dHJ1ZX19LHsiaWQiOiJkMGVlMTlkOS0xNDY4LTQ0NzQtOTBiZS1jMDYwM2UwOWU0OGUiLCJuYW1lIjoiVHIxIE11dGUiLCJncm91cElkIjoiYTAyMmE4NTQtM2M4 + Yy00YjU3LWJhNGQtNmY0YzQyMmNmZmRmIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIn0sIm1vZGUiOnsidHlwZSI6Mn0s + InRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiYTIyNDhlZmYtMmQwYy00YWY2LTlkMDktNTNjYjJmMDFiYWM5IiwibmFtZSI6IlRyMiBNdXRlIiwiZ3JvdXBJ + ZCI6ImEwMjJhODU0LTNjOGMtNGI1Ny1iYTRkLTZmNGM0MjJjZmZkZiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbXV0ZSJ9LCJt + b2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NywidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjY1NDEzY2RjLWIwNDItNDNiMC05ZmI4LTQ4YzgxZDE1NjVhZSIsIm5hbWUiOiJU + cjMgTXV0ZSIsImdyb3VwSWQiOiJhMDIyYTg1NC0zYzhjLTRiNTctYmE0ZC02ZjRjNDIyY2ZmZGYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gzL211dGUifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAyIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIzMDQ3ZDVhYi1kODI1LTRkZWEtOWVjZS0yZTFiMGQ1 + NzFmMGEiLCJuYW1lIjoiVHI0IE11dGUiLCJncm91cElkIjoiYTAyMmE4NTQtM2M4Yy00YjU3LWJhNGQtNmY0YzQyMmNmZmRmIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIn0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAw + ICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiZDE3MDk2OGItMTA3ZS00 + MmM1LTkxNWQtMzE0MDVmYzY1MWQ3IiwibmFtZSI6IlRyNSBNdXRlIiwiZ3JvdXBJZCI6ImEwMjJhODU0LTNjOGMtNGI1Ny1iYTRkLTZmNGM0MjJjZmZkZiIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NywidHJhY2tFeHByZXNz + aW9uIjoicDEgKiAxMDAwMCArIDQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6 + IjQ1NGEzNWU1LWZiZGEtNGUxMy04ZWNkLWFmNWQyMjQ4Y2ZmNCIsIm5hbWUiOiJUcjYgTXV0ZSIsImdyb3VwSWQiOiJhMDIyYTg1NC0zYzhjLTRiNTctYmE0ZC02ZjRj + NDIyY2ZmZGYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L211dGUifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUi + OjcsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA1IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZX19LHsiaWQiOiIzYWI0ZDM3ZC01NDEwLTQwNDAtYjU1Ny1kZTNkNWM5OThkM2YiLCJuYW1lIjoiVHI3IE11dGUiLCJncm91cElkIjoiYTAyMmE4NTQtM2M4 + Yy00YjU3LWJhNGQtNmY0YzQyMmNmZmRmIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tdXRlIn0sIm1vZGUiOnsidHlwZSI6Mn0s + InRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiOWM4MmMyZDItNGVhNC00M2Q5LWI5MWMtOTA0N2QxYTQ3NTJmIiwibmFtZSI6IlRyOCBNdXRlIiwiZ3JvdXBJ + ZCI6ImEwMjJhODU0LTNjOGMtNGI1Ny1iYTRkLTZmNGM0MjJjZmZkZiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbXV0ZSJ9LCJt + b2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NywidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDciLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImQzNmVjY2Q0LWY1ZTgtNDRmYi1iMWVhLTUzOThiMjJjZmQ3NSIsIm5hbWUiOiJU + cjEgU29sbyIsImdyb3VwSWQiOiI1MTNiNzFmYi03Mjg2LTQ4YjQtOTQ0Ny1mNGZlYzQ1MzdmNGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gxL3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAwIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJhNWNkNmE4Mi1iOTY2LTRjM2QtODNiYy0yNzFmY2Ji + MDZjNGIiLCJuYW1lIjoiVHIyIFNvbG8iLCJncm91cElkIjoiNTEzYjcxZmItNzI4Ni00OGI0LTk0NDctZjRmZWM0NTM3ZjRhIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIn0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjo4LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAw + ICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiOTg5YTQyZTMtMWE4Ny00 + NDIzLWI1MTItOGM2ZDQyOGNkOTNjIiwibmFtZSI6IlRyMyBTb2xvIiwiZ3JvdXBJZCI6IjUxM2I3MWZiLTcyODYtNDhiNC05NDQ3LWY0ZmVjNDUzN2Y0YSIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNz + aW9uIjoicDEgKiAxMDAwMCArIDIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6 + ImQ4Njg1YzljLTU0MTUtNGNhZS1iZjFiLTdjYjBjMzJhMTk4ZCIsIm5hbWUiOiJUcjQgU29sbyIsImdyb3VwSWQiOiI1MTNiNzFmYi03Mjg2LTQ4YjQtOTQ0Ny1mNGZl + YzQ1MzdmNGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUi + OjgsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAzIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZX19LHsiaWQiOiIzMmI5N2EzZS00ZTcxLTRmZDEtOGM4MC1lNjRiNjU1YjY2OTMiLCJuYW1lIjoiVHI1IFNvbG8iLCJncm91cElkIjoiNTEzYjcxZmItNzI4 + Ni00OGI0LTk0NDctZjRmZWM0NTM3ZjRhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zb2xvIn0sIm1vZGUiOnsidHlwZSI6Mn0s + InRhcmdldCI6eyJ0eXBlIjo4LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiODRhNzg1NWYtZDFlNC00YjI2LThjMzMtNzYyOGY1MTAyN2RmIiwibmFtZSI6IlRyNiBTb2xvIiwiZ3JvdXBJ + ZCI6IjUxM2I3MWZiLTcyODYtNDhiNC05NDQ3LWY0ZmVjNDUzN2Y0YSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyJ9LCJt + b2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjM4OTJiM2FlLThlMjYtNGNlYy1hODhiLWYwNTNmNzc5ZmNmMCIsIm5hbWUiOiJU + cjcgU29sbyIsImdyb3VwSWQiOiI1MTNiNzFmYi03Mjg2LTQ4YjQtOTQ0Ny1mNGZlYzQ1MzdmNGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g3L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA2IiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJiYzdlMGJhMi0zZWFjLTQ5YmMtOTYwNi1jNTlkMzE2 + NWZlZjAiLCJuYW1lIjoiVHI4IFNvbG8iLCJncm91cElkIjoiNTEzYjcxZmItNzI4Ni00OGI0LTk0NDctZjRmZWM0NTM3ZjRhIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIn0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjo4LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAw + ICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiOGQ3YzkzMDMtYTg0OC00 + Y2JlLTk1OGEtN2U5MTE5YjY0YjYwIiwibmFtZSI6IlRyMSBBcm0iLCJncm91cElkIjoiZDJiNjlkNmUtOTk5Zi00Nzc3LWFlMzktNGIwYTEwOGEzZjA0Iiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjUsInRyYWNr + RXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAwIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19 + LHsiaWQiOiIzZWE1MmQ1Ny1mODk5LTRlMTEtYTZhNS0yMTY2NWNhZGE3NzQiLCJuYW1lIjoiVHIyIEFybSIsImdyb3VwSWQiOiJkMmI2OWQ2ZS05OTlmLTQ3NzctYWUz + OS00YjBhMTA4YTNmMDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0 + YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImE4MTBiYTkyLTUyZTItNDViNi1hMDc1LTVjOGM2NGFkOThjYSIsIm5hbWUiOiJUcjMgQXJtIiwiZ3JvdXBJZCI6 + ImQyYjY5ZDZlLTk5OWYtNDc3Ny1hZTM5LTRiMGExMDhhM2YwNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvcmVjb3JkLXJlYWR5 + In0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQ + cm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiZGUzNmI1MWEtY2Q4MC00OWNkLThmMWItMmZlYmEwYjAxMWM1IiwibmFt + ZSI6IlRyNCBBcm0iLCJncm91cElkIjoiZDJiNjlkNmUtOTk5Zi00Nzc3LWFlMzktNGIwYTEwOGEzZjA0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoNC9yZWNvcmQtcmVhZHkifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjUsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAz + IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJmOTlhMmJiNy0zYzk5LTRkMDkt + OTIyMi03OGNkNjdmYmY0NTQiLCJuYW1lIjoiVHI1IEFybSIsImdyb3VwSWQiOiJkMmI2OWQ2ZS05OTlmLTQ3NzctYWUzOS00YjBhMTA4YTNmMDQiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHBy + ZXNzaW9uIjoicDEgKiAxMDAwMCArIDQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJp + ZCI6IjYxMWFjM2Q0LWU1MTMtNDgyMi1iODhhLTg5ZDdhNjMxYmY2ZiIsIm5hbWUiOiJUcjYgQXJtIiwiZ3JvdXBJZCI6ImQyYjY5ZDZlLTk5OWYtNDc3Ny1hZTM5LTRi + MGExMDhhM2YwNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvcmVjb3JkLXJlYWR5In0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdl + dCI6eyJ0eXBlIjo1LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNGZhMTExM2ItMzFlNy00MjljLWFlODQtOGU0YmYyOWQyMWU1IiwibmFtZSI6IlRyNyBBcm0iLCJncm91cElkIjoiZDJi + NjlkNmUtOTk5Zi00Nzc3LWFlMzktNGIwYTEwOGEzZjA0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9yZWNvcmQtcmVhZHkifSwi + bW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjUsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA2IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI5YTZhOWZmMC1kOGYzLTRhODctYWU3OS01MjhkMDdmZWU2NjYiLCJuYW1lIjoi + VHI4IEFybSIsImdyb3VwSWQiOiJkMmI2OWQ2ZS05OTlmLTQ3NzctYWUzOS00YjBhMTA4YTNmMDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g4L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDciLCJm + eEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6Ijk5Y2FiZTU0LTJhYzMtNDE0NC05MjY4 + LWQ1MmVmZWJkMDU5NSIsIm5hbWUiOiJUcjEgVG91Y2giLCJncm91cElkIjoiNGNiNjdiNGQtZjM5NS00MmVjLWIxYjctYmIxNGVjM2MzODQ4Iiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3VjaCJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiJw + MSAqIDEwMDAwICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNmExOTZk + NGItNWZmMC00MzRhLThjYmUtYjFlNjZkNjc1MzY2IiwibmFtZSI6IlRyMiBUb3VjaCIsImdyb3VwSWQiOiI0Y2I2N2I0ZC1mMzk1LTQyZWMtYjFiNy1iYjE0ZWMzYzM4 + NDgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MjEsInRy + YWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAxIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZX19LHsiaWQiOiJiODExZDRlNy03Y2JjLTQwNTItOWFkYi1iZGI1ZTg1NjQ5MDUiLCJuYW1lIjoiVHIzIFRvdWNoIiwiZ3JvdXBJZCI6IjRjYjY3YjRkLWYzOTUtNDJl + Yy1iMWI3LWJiMTRlYzNjMzg0OCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2gifSwibW9kZSI6e30sInRhcmdl + dCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjZlZjU0MzhhLWQyZWYtNGQ3Ni1iMTk5LWRjMjhkYjNmZDUzYSIsIm5hbWUiOiJUcjQgVG91Y2giLCJncm91cElkIjoi + NGNiNjdiNGQtZjM5NS00MmVjLWIxYjctYmIxNGVjM2MzODQ4Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlci90b3VjaCJ9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiZmZhMTI2MzItZmRiZi00ZDE5LTgyMjAtYmVkMDA2MjUyYmRhIiwibmFtZSI6IlRyNSBU + b3VjaCIsImdyb3VwSWQiOiI0Y2I2N2I0ZC1mMzk1LTQyZWMtYjFiNy1iYjE0ZWMzYzM4NDgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g1L2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MjEsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA0IiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIzYmYyODNkNi02MzYyLTRmYTItYTFlNS1hODc3OTRmNjJj + ZjAiLCJuYW1lIjoiVHI2IFRvdWNoIiwiZ3JvdXBJZCI6IjRjYjY3YjRkLWYzOTUtNDJlYy1iMWI3LWJiMTRlYzNjMzg0OCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIvdG91Y2gifSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCAr + IDUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjkzZmZjYTAxLWI4ZDQtNDE2 + Yy1hNzYzLTkyODgzN2ZhYzJjMSIsIm5hbWUiOiJUcjcgVG91Y2giLCJncm91cElkIjoiNGNiNjdiNGQtZjM5NS00MmVjLWIxYjctYmIxNGVjM2MzODQ4Iiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlci90b3VjaCJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Np + b24iOiJwMSAqIDEwMDAwICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoi + Y2RmMDYyMzgtZmU4ZC00ODNiLWIyYzEtMGMzMGFkN2Y4NTNlIiwibmFtZSI6IlRyOCBUb3VjaCIsImdyb3VwSWQiOiI0Y2I2N2I0ZC1mMzk1LTQyZWMtYjFiNy1iYjE0 + ZWMzYzM4NDgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6 + MjEsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA3IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZX19LHsiaWQiOiJhZmEzNTQ1OC0zMTYwLTRmNDktYmI5MC0yNzlhYWU1MTc3OWYiLCJuYW1lIjoiUGxheS9wYXVzZSIsImdyb3VwSWQiOiIzNjg4YWU3My1i + MzllLTQ4MzYtYmMwMi1kMDliMjZiOTJlM2QiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicGxheSJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0 + YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ0cmFuc3BvcnRBY3Rpb24iOiJwbGF5UGF1c2UiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNjdiM2E1MTYtMjQ2Zi00M2E2LWI4NTAtMDUwZWI0MTU1MjBhIiwibmFtZSI6IlN0b3AiLCJncm91cElkIjoiMzY4OGFl + NzMtYjM5ZS00ODM2LWJjMDItZDA5YjI2YjkyZTNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AifSwibW9kZSI6eyJ0eXBlIjoy + LCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4ifSwidGFyZ2V0Ijp7InR5cGUiOjE2LCJmeEFuY2hvciI6ImlkIiwidHJhbnNwb3J0QWN0aW9uIjoic3RvcCIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJmMTRlYmQ2My0wMTI3LTQ5NmQtOWFjYi0yODFiNWE3YmIzZGIiLCJuYW1l + IjoiUmVwZWF0IiwiZ3JvdXBJZCI6IjM2ODhhZTczLWIzOWUtNDgzNi1iYzAyLWQwOWIyNmI5MmUzZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjeWNsZSJ9LCJtb2RlIjp7InR5cGUiOjIsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiJ9LCJ0YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ0 + cmFuc3BvcnRBY3Rpb24iOiJyZXBlYXQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiMDA2MjY0OTgtODBh + Ni00MWQ0LWJjOWYtZjI3MzA5OGUzZGNmIiwibmFtZSI6IlJlY29yZCIsImdyb3VwSWQiOiIzNjg4YWU3My1iMzllLTQ4MzYtYmMwMi1kMDliMjZiOTJlM2QiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVjb3JkIn0sIm1vZGUiOnsidHlwZSI6Miwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIn0sInRhcmdl + dCI6eyJ0eXBlIjoxNiwiZnhBbmNob3IiOiJpZCIsInRyYW5zcG9ydEFjdGlvbiI6InJlY29yZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZX19LHsiaWQiOiIxZjgyN2I3Yi1kYjA0LTQ3OGEtYTA1Yi0zNmU4OThmNmIyNDUiLCJuYW1lIjoiU2NydWIgY29hcnNlIiwiZ3JvdXBJZCI6IjM2ODhh + ZTczLWIzOWUtNDgzNi1iYzAyLWQwOWIyNmI5MmUzZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6Ijk5 + MiIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJh + Y2tJc0VuYWJsZWQiOmZhbHNlLCJhY3RpdmF0aW9uVHlwZSI6Im1vZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjoxLCJpc09uIjpmYWxz + ZX19LHsiaWQiOiJhMjU5MDYyZS1kZmRlLTRlNmMtOGUwYS0wMTRhMDc4ZmMxYmUiLCJuYW1lIjoiU2NydWIgZmluZSIsImdyb3VwSWQiOiIzNjg4YWU3My1iMzllLTQ4 + MzYtYmMwMi1kMDliMjZiOTJlM2QiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0Fy + Z0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIn0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAxLCJtYXhTdGVwU2l6ZSI6MC4wMDF9LCJ0YXJnZXQi + OnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI5NzQiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwiYWN0aXZhdGlvblR5cGUiOiJtb2RpZmllcnMiLCJtb2RpZmllckNvbmRpdGlvbjEi + OnsicGFyYW1JbmRleCI6MSwiaXNPbiI6dHJ1ZX19LHsiaWQiOiIxZTVjNjA0Zi05MDQwLTQ3YzAtYWIzNy1hMDUzODNlN2JlMDAiLCJuYW1lIjoiU2VlayBjb2Fyc2Uv + ZmluZSIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRlODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + c2NydWIifSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhQcmVzc01pbGxpcyI6MjUwfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInBhcmFtSW5kZXgiOjEsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJlZTgxYzQwNS1kMzU1LTQ1ZTAtODBiNC05MTM2MzMyOTUxOTMiLCJuYW1l + IjoiWm9vbSBvbi9vZmYiLCJncm91cElkIjoiNDYwYzgxNjQtZWJmNi00NWIwLWIyMjEtYTJmZTc0ZTg4ODg3Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6Inpvb20ifSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhQcmVzc01pbGxpcyI6MjUwfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInBhcmFtSW5kZXgi + OjIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJmMTM2NzhiMS1kYTgxLTQ2YTUtYjMxOC04NjFhODEzZDcx + ODMiLCJuYW1lIjoiUHJldmlvdXMiLCJncm91cElkIjoiMzY4OGFlNzMtYjM5ZS00ODM2LWJjMDItZDA5YjI2YjkyZTNkIiwic291cmNlIjp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6InJld2luZCJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxNzIiLCJpbnZvY2F0aW9uVHlwZSI6MCwi + ZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6IjdmMGQyY2JiLTAzNDYtNDIyOS04OWRkLWRiNThjYWMzYTQ2MCIsIm5hbWUiOiJOZXh0IiwiZ3JvdXBJZCI6IjM2ODhhZTczLWIzOWUtNDgzNi1iYzAyLWQwOWIy + NmI5MmUzZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmYXN0LWZ3ZCJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1h + bmROYW1lIjoiNDAxNzMiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhiMGNlNWRiLWEzNzEtNGI0Yy05ZmU4LWNhMDg2YmM3MDZiZiIsIm5hbWUiOiJab29tIG91dCBo + b3Jpem9udGFsbHkiLCJncm91cElkIjoiZDZhYjVhN2UtZjFhZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImN1cnNvci1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwibWF4UHJlc3NNaWxsaXMiOjgwMCwidHVyYm9SYXRlIjoxMDAsInJldmVyc2VJc0VuYWJsZWQiOnRy + dWUsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTk4IiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5YjZmZTBiOC1k + NWYzLTQzOGItOTgzMC02YmI3ODIxMjFjMmQiLCJuYW1lIjoiWm9vbSBpbiBob3Jpem9udGFsbHkiLCJncm91cElkIjoiZDZhYjVhN2UtZjFhZC00OGY0LThmNTEtM2Q2 + M2UyNmYwMDk2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1yaWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1heFByZXNzTWls + bGlzIjozNTAsInR1cmJvUmF0ZSI6MTAwLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6Ijk5OCIsImludm9jYXRpb25U + eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiYzNjMzQ2ODAtMDM5Ni00YjI0LTg3MTQtZTIxMDA2MmY0NmNkIiwibmFtZSI6Ilpvb20gb3V0IHZlcnRpY2FsbHkiLCJncm91cElkIjoiZDZhYjVh + N2UtZjFhZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIn0sIm1vZGUiOnsi + bWF4UHJlc3NNaWxsaXMiOjM1MCwidHVyYm9SYXRlIjoxMDAsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxMTIi + LCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImU5M2U4OTgxLTgxMjctNDQ2Zi1iNWQ5LTM0YmI1NzZmYmMyYiIsIm5hbWUiOiJab29tIGluIHZlcnRpY2FsbHkiLCJncm91 + cElkIjoiZDZhYjVhN2UtZjFhZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci11cCJ9 + LCJtb2RlIjp7Im1heFByZXNzTWlsbGlzIjozNTAsInR1cmJvUmF0ZSI6MTAwLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFt + ZSI6IjQwMTExIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4YjZkNGNmNS1kNGYyLTQ3MDEtYWY3MS05MGQ4MzlkYTIzMTgiLCJuYW1lIjoiU2Nyb2xsIGRvd24iLCJn + cm91cElkIjoiZDBkODg5NTItY2E3MC00OTllLThjNmUtM2I5NGVhZWZiYjJhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1k + b3duIn0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgwMDAwMDAwMDAwMDAwMDcsIm1heFN0ZXBTaXplIjowLjA4MDAwMDAwMDAwMDAwMDA3LCJ0dXJi + b1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI5ODkiLCJp + bnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImI2MzNjMmQxLWMyODMtNDAyMS1hMWZjLTc2YjZhYmQ0MDdiOSIsIm5hbWUiOiJTY3JvbGwgdXAiLCJncm91cElkIjoiZDBkODg5 + NTItY2E3MC00OTllLThjNmUtM2I5NGVhZWZiYjJhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci11cCJ9LCJtb2RlIjp7InR5 + cGUiOjEsIm1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVwU2l6ZSI6MC4wOCwidHVyYm9SYXRlIjoxMDAsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUi + OjAsImNvbW1hbmROYW1lIjoiOTg5IiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiOWZhM2RiNi04YmVkLTRjOGEtODJmYS01ODBkM2QwZGE5MmMiLCJuYW1lIjoiU2Ny + b2xsIGxlZnQiLCJncm91cElkIjoiZDBkODg5NTItY2E3MC00OTllLThjNmUtM2I5NGVhZWZiYjJhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImN1cnNvci1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgwMDAwMDAwMDAwMDAwMDcsIm1heFN0ZXBTaXplIjowLjA4MDAwMDAwMDAw + MDAwMDA3LCJ0dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5h + bWUiOiI0MDE0MCIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzQ0NDA1ZDUtYjUwZC00N2Q0LWEyMmUtYWE5OWE3NDU0YTI1IiwibmFtZSI6IlNjcm9sbCByaWdodCIs + Imdyb3VwSWQiOiJkMGQ4ODk1Mi1jYTcwLTQ5OWUtOGM2ZS0zYjk0ZWFlZmJiMmEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29y + LXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgwMDAwMDAwMDAwMDAwMDcsIm1heFN0ZXBTaXplIjowLjA4MDAwMDAwMDAwMDAwMDA3LCJ0 + dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE0 + MSIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJh + Y2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMjYxMDY3ZWYtYzJmMC00MWRlLWIxMGYtNDE4Y2M3MThjZjFjIiwibmFtZSI6Ik1hc3RlciB0b3VjaCIsImdyb3VwSWQi + OiI3Y2FjMjA2Ni1mMDllLTRjNjktYmJjMy04ZWE4NmJkMzgwMzYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3Vj + aCJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMmY0NWNkZjYtYmFjOS00YTk3LThhM2UtYzI5NmQyY2M3ZTBm + IiwibmFtZSI6Ik1hc3RlciB2b2x1bWUiLCJncm91cElkIjoiN2NhYzIwNjYtZjA5ZS00YzY5LWJiYzMtOGVhODZiZDM4MDM2Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIi + fSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlfSwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjMsImlzT24iOmZhbHNlfX0seyJpZCI6Ijk3MGUwNGM5LWUyNjIt + NDc4ZS04NWNlLTllODQ4NzVmYjVmNSIsIm5hbWUiOiJNYXJrZXIiLCJncm91cElkIjoiN2NhYzIwNjYtZjA5ZS00YzY5LWJiYzMtOGVhODZiZDM4MDM2Iiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1hcmtlciJ9LCJtb2RlIjp7Im91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsImJ1dHRvblVzYWdlIjoicHJl + c3Mtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE1NyIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJhdXRvbWF0aW9uTW9kZU92ZXJyaWRlVHlwZSI6Im5vbmUifSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiYzQ5MjM4YjktMTg5ZS00MmQzLTk4NGEtOTU1ZmU2M2M0NmU4IiwibmFtZSI6Ikdsb2JhbCByZWFkIiwiZ3JvdXBJZCI6IjdjYWMyMDY2LWYw + OWUtNGM2OS1iYmMzLThlYTg2YmQzODAzNiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWFkIn0sIm1vZGUiOnsibWluVGFyZ2V0VmFs + dWUiOjEuMH0sInRhcmdldCI6eyJ0eXBlIjoyNiwiY29tbWFuZE5hbWUiOiI0MDg3OSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsInRyYWNrQXV0b21hdGlvbk1vZGUiOjEsImF1dG9tYXRpb25Nb2RlT3ZlcnJpZGVUeXBlIjoib3ZlcnJpZGUifX0seyJpZCI6ImI4 + MDVmNjI4LTE2ZTEtNDJjYS1hYzM2LTNiYjk1MTMzODE1ZiIsIm5hbWUiOiJHbG9iYWwgdG91Y2giLCJncm91cElkIjoiN2NhYzIwNjYtZjA5ZS00YzY5LWJiYzMtOGVh + ODZiZDM4MDM2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIn0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjEuMH0sInRhcmdl + dCI6eyJ0eXBlIjoyNiwiY29tbWFuZE5hbWUiOiI0MDg3OSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsInRyYWNrQXV0b21hdGlvbk1vZGUiOjIsImF1dG9tYXRpb25Nb2RlT3ZlcnJpZGVUeXBlIjoib3ZlcnJpZGUifX0seyJpZCI6IjEzZDc5ZDQyLTRkNTktNDkz + MC1hOTc2LWI0Mzc0ZTBhNjliNiIsIm5hbWUiOiJDbGljayIsImdyb3VwSWQiOiI3Y2FjMjA2Ni1mMDllLTRjNjktYmJjMy04ZWE4NmJkMzgwMzYiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2xpY2sifSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAzNjQi + LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjdiOTBjMTM2LWY4OWMtNDc3ZS1i + ODEyLTUyNWI0YTdkYTVlZCIsIm5hbWUiOiJUcmFjayBMQ0QiLCJncm91cElkIjoiYjUyZmYzZTgtN2NiOC00ZWJkLWIyNzYtM2U5YjQ5MGUwMTM4Iiwic291cmNlIjp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImxjZC9hc3NpZ25tZW50In0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDIsIm1heFRhcmdldFZhbHVlIjowLjAwOTh9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0 + aGlzIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiZTM4OTdlNDkt + YThmMi00YzVmLWFjNTUtYTQ2NGRjMGJkOWE1IiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjMyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + MS92LXNlbGVjdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4N2ZjNmVhMi1lODdkLTQ2ZmUtYmJmZS0wZWRjNzRhMmE1NzMiLCJncm91cElkIjoi + ODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzMsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IjNkMWFiM2NlLTE0MGQtNGZmYy1hYTI2LTFhNTYzMGQ2MDBjZiIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDMvdi1zZWxlY3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiN2U5OTYwNjktZmFjNy00ZDI0LWFiZmEtODM0ZWQ3MDU3 + YzMwIiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM1 + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXNlbGVjdCJ9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhNDE1NGU5MS0wOGYzLTQwYWItYWQ3Yi02Yjk5MGU0NGQyODMiLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2Yt + YTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImFmNWExOWRmLWYxZmYtNDI0 + MS1hYmU2LTFiMjk2OWY2MGUxOCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjozNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxl + Y3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYjliNTIzMjgtNzFlZC00ODJhLWExYmEtMWY5YjhhY2NhYjNlIiwiZ3JvdXBJZCI6IjgxYzMzYTAy + LTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXNlbGVjdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIx + YWYzYjYxMS1jZWQ4LTQyMDgtOTA5YS02NGI3M2U2YTUxOWIiLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g4L3Ytc2VsZWN0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjMwMTdjMGU0LWEwMzktNDA4ZC05YWM1LWUzYjQxMTdiODk2ZCIsImdy + b3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDQsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6IjMxNzcyOGY1LTVlNDQtNDE1Ny05MTg2LTUwOTk4MjM2MjEwZCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVl + MTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjI5YTczZDRhLTkwZjMtNDJj + MC1iNWJhLTdlMzM5MGI4MDlkMyIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoxMDYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVy + L3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjBkMTNmOGJlLTljZGQtNDEwNS05ZTYyLTRmNzg1YjM5NWI3ZiIsImdyb3VwSWQiOiJjOTk5 + ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDcsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IjE4ZTZlNWQzLTUxMDktNGE1Mi04ZjRiLWIzZjk0NTRlMzAxOSIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImQ1NWJhZjdiLWZkYmEtNGM0ZS1iZjFiLTExZTg0 + NzQ2MmI1NSIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoxMDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIn0sImZl + ZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJkNTY0OWFjLWQxZjAtNGZmOS1hNmEyLTQ2MDc1ZjY1ZDM5YyIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQx + NmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjAyZTA1 + YjAyLTk3YzEtNGVmMy1hNWMxLTY4MjFiM2EzYTI1NCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g4L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImM0NTlmMzdhLTZjOGEtNDQ4My1iODQwLTc2ZmJhYjI4YTU0MCIsImdy + b3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0In19LHsiaWQiOiJhYTZhYTVmZi0zY2Fj + LTQ2MGItOGE1Zi03NThiNmI5NmZiZjciLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Nl + bGVjdCJ9fSx7ImlkIjoiOGMzMmVkMTQtZWI0MC00NDBiLWE3NWEtM2Y4ZjIwNGE5YjkyIiwiZ3JvdXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNm + MzJmYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zZWxlY3QifX0seyJpZCI6Ijg3OTYyZWMyLWU2YjUtNGFiOS04ZDI4LTQyZWI1ODc0YjljOSIsImdyb3VwSWQiOiJlZGRm + ZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNywiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0In19LHsiaWQiOiJkMGE2NmZiYi03ZTlmLTQxYjQtYWRhNS01 + NDJjZGNkMmE1Y2UiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MjgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NlbGVjdCJ9fSx7Imlk + IjoiM2ZmNGE1M2UtMmZkOS00YzQ1LTk0YzktMGM3ODI4MzE1YWExIiwiZ3JvdXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNi9zZWxlY3QifX0seyJpZCI6IjAyZjMyNGZjLWMwZDItNGQ0MS1iMmM0LTBjMGQ4OWJiMWFlOSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQz + YTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2VsZWN0In19LHsiaWQiOiIzMGFiNWU4NS04NTMyLTQzNjctODA2ZS1lMTA0ZTM1NWY0ZWEi + LCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzEsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NlbGVjdCJ9fSx7ImlkIjoiYjk2YjExYjMt + NGEzMS00OGVjLThjMzAtZmFmMTYwNjZiYzM5IiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjoz + LCJjaGFubmVsIjo4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIifX0seyJpZCI6ImZiZjg0NDIwLTdkOTUtNDJjZC05ZDZi + LTdiOWM2ZGMzMTM2MiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4Ijoiam9nIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjE3NWJlYWE5LTgxNDgtNGVlYS1iMTgxLTM4N2YwYjYzYTE5MyIsImdyb3VwSWQiOiJj + OTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJlNTE1ODVkZS0xYzMzLTQ4NDAtOTE5ZC1iYmZiNjVjZGEyNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NCwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYXJrZXIifX0seyJpZCI6IjNlZTQwOGRhLTBmMjAt + NDk4MC05YjUzLWU3ZTYyNjQyZmQzZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlYWQifX0seyJpZCI6IjU0NTRmYzU0LTFjMjktNDZhNC05MDc2LTgyMGZjOWMwMTgzMSIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6IndyaXRlIn19LHsiaWQiOiJhZTEzNjQ5My1kNDU2LTQ4ZDAtOTAwNS1mNjNhMTVlMDNkZmEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0In19LHsi + aWQiOiIzM2Y4MTk4Yy0yMDQ5LTRkYmMtYTRmMy0xMTkyZDM2ZWMzMmYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OSwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCJ9fSx7ImlkIjoiN2IyNTlmNzYtYjAxZS00NmE0 + LWJmYWItNjhjNTM2YWJjZDUwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0In19LHsiaWQiOiJlNmRhOWM3My03ZmE4LTQ1MGUtODM1Zi00MDM0NDM1MTcyYmIiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0In19LHsiaWQiOiIzNDE3ODBjMy01OTM0LTRhMjQtOWY1My0xNWQ0N2IzYzBiY2YiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo5MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQi + fX0seyJpZCI6ImY2YmFmZjFhLWMxY2EtNDA3OS1hMDU4LTFkZjJjNzYwYTYwZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkyLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIn19LHsiaWQiOiI3YTNmYTQ3ZC05YTVm + LTRiNmUtYjk0YS1lN2E4MjQ1MjRmOTEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5In19LHsiaWQiOiJjNmQ2ZTQ3ZC0zNjU4LTRkNjYtODZlYy0zMTA2ZjY3N2YzNTAiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJzdG9wIn19LHsiaWQiOiI3MWQ0MDcwMS1jZjA5LTQxZTUtYmY1Yy1kYTM0ZjcwYzY2ZGMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo5NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNvcmQifX0seyJp + ZCI6ImJhYmE0ZGNmLTY1NDctNDY4MC1iYWI5LTNmMWNlNzRlZDFlMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg2LCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN5Y2xlIn19LHsiaWQiOiJlZTM0Yzc2NS05N2I5LTQ3YzEtYjBh + Yy04OTYzNTcxMzAxMzciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoiem9vbSJ9fSx7ImlkIjoiYTAwOWE2M2QtZWJjZC00ZGRjLTk0ZGMtN2ZiZTJjYjczNmM5Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6InNjcnViIn19LHsiaWQiOiI2OGM5MjRmZS1lY2QzLTRhOGMtYTNiMC0yMWY5MTE2NzUyOTAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjo5OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCJ9fSx7Imlk + IjoiNDkzNzQyMGMtNWFhZS00Yzc3LWJiMDktY2E1MTg2NTc2OWFlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTksImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0In19LHsiaWQiOiI2YzM4MzQyMC02YzM1LTQ4 + NDktOGE0ZC0xYWI1ZWQ0OWE5NDYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAifX0seyJpZCI6IjljOTgxZTYxLWYzMDAtNDM4Yy05YTJkLTRiMTliMGY0NGVlOSIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIn19LHsiaWQiOiJjOTFmN2ZjNS04MDM5LTQ5MWYtOWQ3Ni1lNDMzMDk5OTQ1NDAiLCJncm91cElkIjoiNzkxNGZiNDkt + N2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gxL2ZhZGVyIn19LHsiaWQiOiI0OGIzYjIwNS1mMjFlLTQwZDgtOTYwMS03MWUzOGIwYTUyYWUiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5Njgt + YmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIn19 + LHsiaWQiOiJlMTljYzdjYy05ZWJhLTQzYzYtYjE1Yy0wMWRkZjI3NzIwYjkiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwi + c291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyIn19LHsiaWQiOiI5YTg1NmUy + MC1kOWJlLTRiMzMtOGZhZi02MGViZWQ1MTc3NjYiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUi + OjMsImNoYW5uZWwiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyIn19LHsiaWQiOiIzZDM0ZGZhMi1mNTZkLTQyNjgtODVm + MS01ZWJkODNiYmY1MWIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjQs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIn19LHsiaWQiOiIzNDRmNTgwMS03MmJiLTQ3N2YtOGUxZC01NjI4YWFiNWY0YzEi + LCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjUsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIn19LHsiaWQiOiJlMTQ0MjY3ZS04ZmQ2LTQyMGItYjg4Zi1mMjkzNGU4NmEyYTQiLCJncm91cElkIjoiNzkx + NGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g3L2ZhZGVyIn19LHsiaWQiOiI4NjZlZmI2MC1jYTRlLTRmNGQtOWM1Yy00MzIyOTQzM2I1NjkiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2 + LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2Zh + ZGVyIn19LHsiaWQiOiIwMzQ1ZDRkNy0wNTkyLTQzOWEtODJiMS1hMjFmMTE2M2ZiMGUiLCJncm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVj + NmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEv + di1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzVlOTJlY2MtMGZhOC00OTM5LWJlYzItZWU2ZGY0Y2VmOTM3IiwiZ3JvdXBJZCI6IjMxYmY4 + MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiNGFhYzgyOGMtZWRiMC00YmFmLTllOWQtYjIwYmJlNGZlZTUwIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFl + YzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gy + L3YtcG90In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjA4MGFhMWZlLWZiZWMtNGJiMS1iMWRhLTIzNjFkMjgwNjc2MyIsImdyb3VwSWQiOiIzMWJm + ODJmZC0xZmNhLTQ2ZTgtYTgwYS1iYjZjOGI4OTE3ODgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJtYXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdCJ9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNhMWZmOWRjLTI4NmMtNGUyOC1hNDkwLTNiOGE1Mjk3YTM3NyIsImdyb3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMx + ZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + My92LXBvdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlZWI5ODYwNS1lMGU4LTRiNWEtOTFjMy0wMmYwMjhmODQzMDQiLCJncm91cElkIjoiMzFi + ZjgyZmQtMWZjYS00NmU4LWE4MGEtYmI2YzhiODkxNzg4Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4yNTk4NDI1MTk2ODUwMzkzNSwibWF4U291cmNlVmFsdWUiOjAuMzM4NTgyNjc3MTY1MzU0MzR9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QifSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiI1M2M2ZjJiNi1iZDRlLTQ5NzktODkwOS0yN2ZlZDU3MTg4YzIiLCJncm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYz + MWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTksImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDQvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODdlYTdjMTYtOTEwMC00Njg1LWE0MGItMzk5ZTQ5NmI5ZTU0IiwiZ3JvdXBJZCI6IjMx + YmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90In0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiM2U2YTE3MDItN2I0MC00N2M2LWE0NDktZDcxOGRlZWU1YTVkIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2 + MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g1L3YtcG90In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjQ3YjQwYzUyLWI4M2YtNGM5Yy1hYWM5LTZmM2ZiZGQwMDk1MyIsImdyb3VwSWQiOiIz + MWJmODJmZC0xZmNhLTQ2ZTgtYTgwYS1iYjZjOGI4OTE3ODgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJtYXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCJ9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImZhYTUyYjdkLTVjNDQtNDAyOS1iMjVkLTdmZmE5Y2U0NjVlMCIsImdyb3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVj + NjMxZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNi92LXBvdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiM2YzMTJjYS04YjQ2LTQwMDEtOWE1My1lMGE2NzBlMzNkYTkiLCJncm91cElkIjoi + MzFiZjgyZmQtMWZjYS00NmU4LWE4MGEtYmI2YzhiODkxNzg4Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4yNTk4NDI1MTk2ODUwMzkzNSwibWF4U291cmNlVmFsdWUiOjAuMzM4NTgyNjc3MTY1MzU0MzR9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QifSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiIyMDIwYzQ3ZS01ZDczLTRlMDgtODEyYS1lNzNkZTg3MmUwN2IiLCJncm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZl + YzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjIsImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDcvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOTAzZTkzNWMtMjVhYi00ZmIwLTkwYTktZDllMWMxZTAzNDkwIiwiZ3JvdXBJZCI6 + IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjU0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90In0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiZWEyMjg3NTUtMWJmYy00NDg1LWJkNTktMDVjZGQyMDc5ZTI4IiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVm + ZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g4L3YtcG90In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjE2ZjA1MTVkLWFkNzctNDQzZC05N2RjLTZhM2ZjNzkyMzNhYSIsImdyb3VwSWQi + OiIzMWJmODJmZC0xZmNhLTQ2ZTgtYTgwYS1iYjZjOGI4OTE3ODgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJtYXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQz + NH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdCJ9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6IjhhYmQ1NTM1LTY2MWUtNGI2YS1hM2JlLTJhMjMyZDYxMTE4ZiIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0z + MzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbXV0ZSJ9fSx7ImlkIjoiNDE3ZWQwN2QtY2I4OC00OTBjLWJlNzctMWU5NGQzMTBmZmI1IiwiZ3JvdXBJZCI6 + IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIn19LHsiaWQiOiIyNzVhMjk1Ny05MWU3LTQ4ZTctYWFm + NS0zZGQwNmZjYjY4MDIiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL211dGUifX0seyJp + ZCI6ImY1ZmE0ZGM1LWViOTgtNGExYi05MWZlLWE2NDEzMTJmYWY0NyIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDQvbXV0ZSJ9fSx7ImlkIjoiYTEwNDlkOTgtNGNhZi00MmIzLWJkMjAtMDliYjRjYmIxMTRiIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhm + Yi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIn19LHsiaWQiOiI4NjkxNTQ3MS1hNDViLTQzMTUtOTMwMS03MjI3ZGU3YzAwM2QiLCJn + cm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjEsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L211dGUifX0seyJpZCI6ImI0NDI4YTcyLWE1YWIt + NDgxMi1iM2FkLTUxODhkNzgwYjY3YiIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjoyMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbXV0 + ZSJ9fSx7ImlkIjoiN2FmOGMwNmMtODkzMi00NWM0LTlkOTQtZWE5ZjZhNzZlYTM2IiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2 + MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tdXRlIn19LHsiaWQiOiJlNTBlZGFjNS0yNGNlLTQ4MTUtYmEzMy03NTM5MGUxYTIwOWIiLCJncm91cElkIjoiNzk4YWUzMzIt + MDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyJ9fSx7ImlkIjoiNTE1ZWYzYzItMTY2YS00ZTM1LTg3ZjgtZjk4NWMzMjlm + ZWM0IiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjks + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NvbG8ifX0seyJpZCI6IjhkZTIyYzJl + LTI1MDAtNGM4Mi05YzdhLWQyOWJjZTI4YzUzOCIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDMvc29sbyJ9fSx7ImlkIjoiZTc3ZjA5NWItYzY2MS00MGQyLTkwMmItOWI3YTBhYWMxMTM0IiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhl + ODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zb2xvIn19LHsiaWQiOiIxMGZmZjM4MS1hNzM2LTQyNDAtYTExOC0zYmY1MTgzZDIzYjQiLCJncm91cElkIjoiNzk4 + YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NvbG8ifX0seyJpZCI6ImNkMGE0YzU0LWYxMzEtNGY3Mi1iYzlkLWIz + MDc4Njk0MjNiNiIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjoxMywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyJ9fSx7ImlkIjoi + N2RlNWY5ZTYtODU3OC00ODY0LWI5MGYtZjkwYzZhMjk3NjY0IiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoNy9zb2xvIn19LHsiaWQiOiI4MjQyOTE1NS02OGZjLTQ0OGMtODM0NC05MzJjODU0OTU2ZWYiLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3 + YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NvbG8ifX0seyJpZCI6IjE1ZDI4MjQxLTA5OTAtNDU0NS04Y2Q3LWJkMTkwZWJmMjc0ZiIsImdyb3Vw + SWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkifX0seyJpZCI6Ijc3N2Q5ZDZlLWEx + ODQtNGI1OC1hZTA4LWIzYjY1MjFkMmRlOSIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9y + ZWNvcmQtcmVhZHkifX0seyJpZCI6IjM0Mjg1YzIyLTM0OTEtNGY1MC04ZDZiLWJhYzJkZDI4NGIxNCIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0y + MjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImFjYzFmYWNkLTM5NTQtNGYxNC1hNzc0LWNlNDUwY2MxOWIzNiIsImdy + b3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9yZWNvcmQtcmVhZHkifX0seyJpZCI6Ijg3YWVjOWIz + LWU0MTAtNGQzYy05NzVmLWI1MmFjMTFmMGUwYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + NS9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImNkMmRmZTY0LTMzYzItNGNjZC04YTdkLWMxNjZiNWZkNDZhMyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNk + NS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImM5OTE1MWM5LWNhMDEtNDg4MC1hZDE3LTVhMWFhNjY4ZmJlMyIs + Imdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2LCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9yZWNvcmQtcmVhZHkifX0seyJpZCI6IjI0NGQx + MTk5LTgwY2EtNDNmZS1iNzdkLWRhYTNlZTU5NGU4YyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoOC9yZWNvcmQtcmVhZHkifX0seyJpZCI6IjFkNjQzNjYzLWUwOGYtNGIzNi04NjBhLTVhMGQ2OGE1OWZiZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjg1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibnVkZ2UiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiODg3ZDgzMTYtMmZjYS00ZmE5LTlhYTYtYjMxZTE2N2Q1MTRhIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJkcm9wIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImJkZjExMGNhLTg1NzAtNDIxNS1hNWE3 + LTQ4OGQyNDEwMzc0YSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVwbGFjZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQi + OiIwZWVhYjQyNC05MDQzLTQ4YzgtYjRmNi01NGVmYzVlODhkMmMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4OSwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjNjZWJhNGQ0LWQ0ZTUtNDc0NS04NDQ1LThjNjU1ZjQwY2I0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjkwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic29sbyIsInVzZVByb2plY3Qi + OnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJlZjUzY2Y3MS0zNDRiLTQ3OTktYTYxNi01OTY1MDgwZDNhYTMiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImYxIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjkwOWExNDUzLWQ2MzEtNDViMy04ODY3LTU1ZDY1 + ZGRhYmU2YSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiMzBmMTM5Yzct + YTRlZi00Mjc5LWIyNmItMzY4MmQxNzc3NmM4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZX19LHsiaWQiOiI2MDcwMTczYy03YmVhLTQzMDAtOWQzYi02OTczNjI3NjNjMWMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NywiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY0IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImJkOWUzZjgxLTQ4NTItNDU0NC05ZDhlLTFiOTI0OGU2YzU5NiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjU4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiM2Y4MDg1ZWQtNTcxZi00ZTU0LWE4MWYtMDdhMWIxZDc0N2Y0Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJmNiIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIzNmRjM2ZmNy1hYmMwLTQ1NmItOWM2NC1h + OGZlMDY2YTQ3MDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJp + ZCI6ImNkZTZjYjQxLWViNmYtNGM1My1hOGIxLTE1YTdkNDU1ODg5NCIsImdyb3VwSWQiOiI3YjYxMDA4OS02ZTE0LTQxMmItYWEyNi1mY2ViNmFjOGJlMjIiLCJzb3Vy + Y2UiOnsidHlwZSI6MTEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtaWRpU2NyaXB0IjoibnVtID0geSAqIDk5O1xuZDEgPSBudW0gJSAxMDtc + bmQyID0gKG51bSAvIDEwKSAlIDEwO1xubXNnX3NpemUgPSA1O1xuMFtdID0gMHhiMDsgXG4xW10gPSAweDRiOyBcbjJbXSA9IDB4MzAgKyBkMjsgXG4zW10gPSAweDRh + OyBcbjRbXSA9IDB4MzAgKyBkMTsiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MiwidHJhY2tH + VUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdubWVudCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWFja2llLWNvbnRyb2wiLCJhY3RpdmVNYWluUHJlc2V0SWQiOiJkYXctY29udHJv + bCIsInBhcmFtZXRlcnMiOnsiMiI6eyJuYW1lIjoiWm9vbSBvbi9vZmYifSwiMCI6eyJuYW1lIjoiVHJhY2sifSwiMSI6eyJuYW1lIjoiU2VlayBjb2Fyc2UvZmluZSJ9 + fX0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {82B0643C-0C3F-4599-B25C-90582A07176E} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-358-key-source-table.rpp b/plugin-reaper-realearn/resources/test-projects/issue-358-key-source-table.rpp new file mode 100644 index 0000000..5cf6581 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-358-key-source-table.rpp @@ -0,0 +1,518 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAB5iAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJqSmpSZVRodyIsImxldFVubWF0Y2hlZEV2ZW50c1Rocm91Z2giOmZhbHNlLCJjb250cm9sRGV2aWNlSWQiOiJr + ZXlib2FyZCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiOXRIbF8xaHVtWTRj + cUdnQkQzeklOIiwibmFtZSI6ImAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MCwia2V5Ijo5Nn19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiI3SGhq + RDVMY0k4eW9BZjBsclJZSXQiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjQ5fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjEsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFk + ZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6InVfMlBZWmV5TnBLalRyZml4NEhuWCIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NTB9fSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiUnF6d21ZTlI2TXB5dm9nVHhwYk8tIiwibmFtZSI6IjMiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7 + Im1vZGlmaWVycyI6MSwia2V5Ijo1MX19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjozLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJEY2dU + NWpXNENkNE1EbXMzSmFzbW8iLCJuYW1lIjoiNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjUyfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFk + ZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IjdhS0VaRjNhRDBEZXRWaGNXQVZ6cCIsIm5hbWUiOiI1Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NTN9fSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiTEtSMFMtVlBXcmVaOTZ0WjlLb3ZWIiwibmFtZSI6IjYiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7 + Im1vZGlmaWVycyI6MSwia2V5Ijo1NH19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2LCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJhdTEw + RW15UkN3VW41QkoydDNUNTgiLCJuYW1lIjoiNyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjU1fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjcsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFk + ZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IjN3UjA4dVk2am84R1VmXy04UnVZWSIsIm5hbWUiOiI4Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NTZ9fSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiQVdTZzN0Y3daYVRNR2d0QWdtWXBhIiwibmFtZSI6IjkiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7 + Im1vZGlmaWVycyI6MSwia2V5Ijo1N319LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5LCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiIyNElx + MnA1M0xScEc1OGhNSG5VaFkiLCJuYW1lIjoiMCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjQ4fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjEwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJh + ZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJLMjV5a1ZsRS1wb1VmWnFYbnh6SlUiLCJuYW1lIjoiLSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjowLCJrZXkiOjQ1fX0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJJTTh1eDRHMi15REw2Vkl5aXVaTW8iLCJuYW1lIjoiPSIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2Ui + OnsibW9kaWZpZXJzIjowLCJrZXkiOjYxfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJB + OG1fbnh0cGFkd1g4X1dvQkJfR1EiLCJuYW1lIjoiQmFja3NwYWNlIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6OH19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoxMywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xp + cFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiT2szRzNraHIzM1RoNHEzeTFhdVVsIiwibmFtZSI6IlRhYiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXki + Ojl9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJz + ZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IlJIellPZ0ppVDVVd3VaWWJpbEdhciIs + Im5hbWUiOiJRIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODF9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTUsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9 + fX0seyJpZCI6Im9kakRtWFdlenRzWHc0X0tCZ2RsbCIsIm5hbWUiOiJXIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODd9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4w + NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTYsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAs + ImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IkYzWXhZSVRydzZlM01Kd2FRdjVqQSIsIm5hbWUiOiJFIiwic291cmNlIjp7ImNhdGVnb3J5 + Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtl + eSI6Njl9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTcsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IldzQlBrNHZuTS0wX2Q4TEZGRnlk + cCIsIm5hbWUiOiJSIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODJ9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTgs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3Rl + ZCJ9fX0seyJpZCI6ImJjOGpLSU4tSEZxTGlMdzhCUTZVQiIsIm5hbWUiOiJUIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODR9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6 + MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTksInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgi + OjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6InV3T29HMHhXSE1fV25mQ1c2X1lXaCIsIm5hbWUiOiJZIiwic291cmNlIjp7ImNhdGVn + b3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEs + ImtleSI6ODl9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IlZQUC1xcjkyanlyWFdfZzlf + OXVBMCIsIm5hbWUiOiJVIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5k + ZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODV9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MjEsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxl + Y3RlZCJ9fX0seyJpZCI6IjZPLVY2MEpkdWljQlFjaFFkT2w2ZSIsIm5hbWUiOiJJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NzN9fSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6ImpEblo2dWFiS0h5TzhUS2p5Sm9acyIsIm5hbWUiOiJPIiwic291cmNlIjp7ImNh + dGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMi + OjEsImtleSI6Nzl9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6InB0N2lDb25QT2pqZ1dQ + TEd5dmZzTyIsIm5hbWUiOiJQIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODB9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6MjQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJT + ZWxlY3RlZCJ9fX0seyJpZCI6IktGTV9Fb255c2VhdVZ3Qjl6eHRSTSIsIm5hbWUiOiJbIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjAsImtleSI6OTF9fSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjUsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJn + SW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6Im4waGwwazU1UEdrcFcwSDlxX2xIMyIsIm5hbWUiOiJdIiwic291cmNlIjp7 + ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmll + cnMiOjAsImtleSI6OTN9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjYsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IkJlbUE5YjY5UVNS + VDFxeUJIYWJYWSIsIm5hbWUiOiJFbnRlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjEzfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjI3LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRy + ZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJfM0RaalZTTG41eW9fejVrSW82Q28iLCJuYW1lIjoiQSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fX0sIm1vZGUiOnsi + bWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI4LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJhMUZRV2hkZUttRXpldGFFcWlyRUoiLCJuYW1lIjoiUyIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsi + bW9kaWZpZXJzIjoxLCJrZXkiOjgzfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI5LCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJaeGtV + bHNaNndKbk1JeVhoLUlQaEYiLCJuYW1lIjoiRCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY4fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjMwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJh + ZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJPVEVRX1lZLTRNaXVBeVpyV0ZNbEwiLCJuYW1lIjoiRiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjcwfX0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiI4clNRdWxONGotX2dpdjJFWmg1c2EiLCJuYW1lIjoiRyIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2Ui + OnsibW9kaWZpZXJzIjoxLCJrZXkiOjcxfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMyLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJ0 + eVNnQ0syR3JHd29wRmtFdFFiZzciLCJuYW1lIjoiSCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjcyfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjMzLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6 + eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJhakZ6Q2pLODE1QnlaV0Y0Z1hPSXgiLCJuYW1lIjoiSiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJk + IiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjc0fX0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM0LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJOd0V1VzlEcXJHcU5EdUVMb3FJLS0iLCJuYW1lIjoi + SyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJv + a2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjc1fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM1LCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQi + OiJGaWFwRDdNTU5UdHNHNU5zMnBrN2EiLCJuYW1lIjoiTCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjc2fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjM2LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xv + dCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJObGdPbEFJVEJTT1hqU1FCTXFmZlYiLCJuYW1lIjoiOyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJv + YXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjowLCJrZXkiOjU5fX0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM3LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJsYTJkYS0zbEJPSXBFVGsyVDJCUVoiLCJuYW1l + IjoiJyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlz + dHJva2UiOnsibW9kaWZpZXJzIjowLCJrZXkiOjM5fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM4LCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsi + aWQiOiI2d3MzcXdkY1lNSE5zR0tkd3lQNG8iLCJuYW1lIjoiXFwiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MCwia2V5Ijo5Mn19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjozOSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xp + cFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoidE45Rjc0UUxpTi1MMmpaUnJmSFliIiwibmFtZSI6IlNoaWZ0Iiwic291cmNlIjp7ImNhdGVnb3J5 + Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtl + eSI6MTZ9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IkZIa1BBaFM5NjJLN0VVeHVtQUdM + dCIsIm5hbWUiOiJaIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6OTB9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDEs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3Rl + ZCJ9fX0seyJpZCI6Ik9NalhucXU0YjlDTlphV3h2WF9RaiIsIm5hbWUiOiJYIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODh9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6 + MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgi + OjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6InZJb044Y21ZMTVrV1lsWUJYTFRQeSIsIm5hbWUiOiJDIiwic291cmNlIjp7ImNhdGVn + b3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEs + ImtleSI6Njd9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6Imw2ZDR6am1Xc2I5bWdCRDVG + d0g5SyIsIm5hbWUiOiJWIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5k + ZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODZ9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + NDQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxl + Y3RlZCJ9fX0seyJpZCI6Ik1nenN4YTJSblZHYzI0dncxbWxuXyIsIm5hbWUiOiJCIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjZ9fSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDUsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IkVPVGdTeXFlTm9SMzZzamFWaG8ycyIsIm5hbWUiOiJOIiwic291cmNlIjp7ImNh + dGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMi + OjEsImtleSI6Nzh9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDYsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6Ik8yYjBUSjdVQnUwZ1Vj + NU9HS0M3MyIsIm5hbWUiOiJNIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6Nzd9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6NDcsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJT + ZWxlY3RlZCJ9fX0seyJpZCI6ImRyd2tNYVdyLXJmV0JOX3gzdnJrVyIsIm5hbWUiOiIsIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjAsImtleSI6NDR9fSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDgsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJn + SW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IlhCUS0wMEpBc1Y4cTVNck5uYzFzZSIsIm5hbWUiOiIuIiwic291cmNlIjp7 + ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmll + cnMiOjAsImtleSI6NDZ9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDksInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IldqaEs1UHJOWjd2 + Y1hUbXlmNWFHdCIsIm5hbWUiOiIvIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjAsImtleSI6NDd9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6NTAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3Mi + OiJTZWxlY3RlZCJ9fX0seyJpZCI6InVCa09UN3NuNnBqTTVNRG1nb0gzNSIsIm5hbWUiOiJDdHJsL0NtZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjE3fX0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjUxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJoYl9HTWM3RlJOWEZvWDk0aFdydmMiLCJuYW1lIjoiV2lu + L14iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ry + b2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo5MX19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1MiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7Imlk + IjoiZFF2MkpOX3lXM3IwUnp3SXhfVy1GIiwibmFtZSI6IkFsdC9PcHQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5IjoxOH19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1MywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwi + Y2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiaWJ0N2pXMC0xTlh1NElQaFo5cTZEIiwibmFtZSI6IlNwYWNlIiwic291cmNlIjp7ImNhdGVn + b3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEs + ImtleSI6MzJ9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NTQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IkxMeHpkdlNLeXVxUG9OX2g0 + TW1KNCIsIm5hbWUiOiJOdW1QYWQgVXAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5IjozOH19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo1NSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVz + cyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiYWttb09sVXhCYzB6U3E0RW54RjhpIiwibmFtZSI6Ik51bVBhZCBMZWZ0Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9h + cmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6Mzd9fSwi + bW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NTYsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6ImdwMlBMUG9NeFFKbE1YTkhmMnRCUyIsIm5hbWUi + OiJOdW1QYWQgRG93biIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjQwfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjU3 + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0 + ZWQifX19LHsiaWQiOiJGVXBrU1dTNHBRc3otY0o1V2dERTgiLCJuYW1lIjoiTnVtUGFkIFJpZ2h0Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6Mzl9fSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NTgsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IlRXeGNpNUhHZVdidzU2WXRjRkNfZyIsIm5hbWUiOiJFU0MiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7 + Im1vZGlmaWVycyI6MSwia2V5IjoyN319LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1OSwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiejRE + aGFQNlNqWktUTk1zb0xiTlhKIiwibmFtZSI6IkYxIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6MTEyfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjYwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6 + eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJTc2E4Z3JLZGt2Qlg2S05VaXRBb1ciLCJuYW1lIjoiRjIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2Fy + ZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5IjoxMTN9fSwi + bW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NjEsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6InY5OU1vbnlRUWNjcndpTVhIVmpMZCIsIm5hbWUi + OiJGMyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlz + dHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjExNH19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2MiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7 + ImlkIjoiZjBiTHpiRmNqcE9XQ2MzWWZNWFVkIiwibmFtZSI6IkY0Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6MTE1fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUs + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjYzLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJj + bGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJ1VlN0OXU1alZlYXVCTHYwYmpGOTUiLCJuYW1lIjoiRjUiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5 + IjoxMTZ9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NjQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IkdRTVNzdUVucjlrM3NPbE9xNFZt + diIsIm5hbWUiOiJGNiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjExN319LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2 + NSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVj + dGVkIn19fSx7ImlkIjoieGdSYXI2b0lZQ1VEV3BQRVlFV1pGIiwibmFtZSI6IkY3Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6MTE4fX0sIm1vZGUiOnsibWF4U3RlcFNp + emUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjY2LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0lu + ZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJTY2dHUVNlX29ZVDB1NjBxY01pTU0iLCJuYW1lIjoiRjgiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVy + cyI6MSwia2V5IjoxMTl9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NjcsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6InFLVko0dnlVeENK + eWJVSWJMYkxQcSIsIm5hbWUiOiJGOSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjEyMH19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo2OCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVz + cyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiTFlRYmJCWUZHNmJmNGdIRUgxRE0xIiwibmFtZSI6IkYxMCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjEyMX19LCJtb2RlIjp7 + Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2OSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoidW95UXJzTVVIUkpzUHFXajZYUzEyIiwibmFtZSI6IkYxMSIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2Ui + OnsibW9kaWZpZXJzIjoxLCJrZXkiOjEyMn19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3MCwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoi + dGdLb3FON25LT25ZdEJzVm1nVDRQIiwibmFtZSI6IkYxMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjEyM319LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4Ijo3MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNs + b3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiMUQzeHJycF9CNkpsTkFZVGdIdFF4IiwibmFtZSI6Ik51bVBhZCBJbnNlcnQiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6 + MSwia2V5Ijo0NX19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiUTZFU05RdmlPNmJMQ3Bi + UWswbzZmIiwibmFtZSI6Ik51bVBhZCBEZWxldGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo0Nn19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijo3MywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsi + YWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiaEFJTGRqY09LdUtIVm96OXhtQmtxIiwibmFtZSI6IlBhdXNlIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9h + cmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6MTQ0fX0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjc0LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJXZ1NzVnoteTlqc19SY1ZJOTFPa3giLCJuYW1l + IjoiKiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlz + dHJva2UiOnsibW9kaWZpZXJzIjowLCJrZXkiOjQyfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjc1LCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsi + aWQiOiJBRDVMVHRhbC1JVExVVWdLRm53V3QiLCJuYW1lIjoiTnVtUGFkIEhvbWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5IjozNn19LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3NiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fSx7ImlkIjoiOW9hc3V6a3dfdjVyRG1Dd3VBMnhKIiwibmFtZSI6Ik51bVBhZCBQYWdlIFVwIiwi + c291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6 + eyJtb2RpZmllcnMiOjEsImtleSI6MzN9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NzcsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IjdR + Y3duMFZHTXZia3JGSFVMejl0SiIsIm5hbWUiOiIrIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjAsImtleSI6NDN9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6NzgsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7 + ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6IlljVWcxZ3lsdzBJaGhRbUdnNEwwNiIsIm5hbWUiOiJOdW0gNSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJv + YXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjEyfX0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjc5LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiIwN1FvRnNhdE1LQ01EbkFPTVRZTXAiLCJuYW1l + IjoiTnVtUGFkIEVuZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjM1fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjgw + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0 + ZWQifX19LHsiaWQiOiJqNkR3dWxTekZoR1hqREZRbTVpSDIiLCJuYW1lIjoiTnVtUGFkIFBhZ2UgRG93biIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjM0fX0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjgxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJBWExmRldTNnJuUjQzTlhYX2czNm4iLCJuYW1lIjoiMCIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2Ui + OnsibW9kaWZpZXJzIjowLCJrZXkiOjQ4fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjgyLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1234 354 1436 1514 + FXID {8FACB5B9-EFA4-4490-85F7-961A2E23451B} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOAT 2205 645 1341 1163 + FXID {0DAAB291-4545-4268-9AB5-BC71A6A57082} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7lgUpOrByKD8AAAAAAAAAAM5NAD95aBU7AAAAAAAAAD9iRA8+AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 1266 386 683 876 + FXID {752D5DBE-1D28-4BDD-A4FD-A75529A6DBAA} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-358-key-source.rpp b/plugin-reaper-realearn/resources/test-projects/issue-358-key-source.rpp new file mode 100644 index 0000000..b50e395 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-358-key-source.rpp @@ -0,0 +1,167 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADcBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJqSmpSZVRodyIsImNvbnRyb2xEZXZpY2VJZCI6ImtleWJvYXJkIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6InBEcGJUQWZMbDdWblVJajZLNHE3dyIsIm5hbWUiOiJLZXkgdG8gbm90ZSAtIGZpcmluZyIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2OH19LCJtb2RlIjp7InR1cmJvUmF0ZSI6NTAsImZlZWRi + YWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwicmF3TWlkaVBhdHRlcm4iOiI5MCA0MCBb + MGdmZSBkY2JhXSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgi + OjB9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidEpmc05SczMwU2E2Mnpod0lLUmZtIiwibmFtZSI6IktleSB0byBub3RlIC0gcmVsZWFzZSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2OH19LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGws + ImJ1dHRvblVzYWdlIjoicmVsZWFzZS1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInJhd01pZGlQYXR0ZXJuIjoiOTAgNDAgWzBnZmUg + ZGNiYV0iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX0s + ImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImVuQTdRTl9yWFY3cTRMSDdwVDduOSIsIm5hbWUiOiJLZXkgdG8gbm90ZSAtIG1vbWVudGFyeSIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo3MH19LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJyYXdNaWRpUGF0dGVybiI6IjkwIDQwIFswZ2ZlIGRjYmFdIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + Y2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1234 354 1436 1514 + FXID {8FACB5B9-EFA4-4490-85F7-961A2E23451B} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOAT 2205 645 1341 1163 + FXID {0DAAB291-4545-4268-9AB5-BC71A6A57082} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7lgUpOrByKD8AAAAAAAAAAM5NAD95aBU7AAAAAAAAAD9iRA8+AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 1266 386 683 876 + FXID {752D5DBE-1D28-4BDD-A4FD-A75529A6DBAA} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-363-track-all-by-name.rpp b/plugin-reaper-realearn/resources/test-projects/issue-363-track-all-by-name.rpp new file mode 100644 index 0000000..126cae7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-363-track-all-by-name.rpp @@ -0,0 +1,302 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA8SQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4xIiwiaWQiOiJSS1k3bHNmQiIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6Ijk2N2IwYjVkLWMwMDUtNGE4NS04NWMyLTY4OTY0MjNiMzVjZCIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJn + ZXQiOnsidHlwZSI6MiwiY29tbWFuZE5hbWUiOiI0MDAwMSIsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0dVSUQiOiJuYW1lKiIsInRyYWNrTmFtZSI6ImEqIiwiZnhB + bmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBw + aW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxfX0seyJpZCI6ImU1NTQzYWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwic291cmNlIjp7 + ImNoYW5uZWwiOjEsIm51bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVmMC01Y2Y4LTQ1ZTktOWM5MC00 + ZDQ5MDZjZjYzMDQiLCJuYW1lIjoiRW5jb2RlciAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxfX0seyJpZCI6ImRkMWE4MWQzLWQ2MDctNDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51 + bWJlciI6MSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2 + NTMtOWZiYi1lZjkwN2RiMDlkODciLCJuYW1lIjoiRW5jb2RlciAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjMsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudEluZGV4IjoyfX0seyJpZCI6ImI4MmQ3NzUyLWQyMjktNDQ4Zi1iZDQyLWFmNjFiNDU5ZTQ4MSIsIm5hbWUiOiJCdXR0b24gMS8zIiwic291cmNlIjp7ImNoYW5u + ZWwiOjEsIm51bWJlciI6MiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2Mw + Mi1jNTJkLTQ3MWMtYjcwNi0yN2I2MmUyYTVkYmUiLCJuYW1lIjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIi + OjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNl + Ijp7ImNoYW5uZWwiOjEsIm51bWJlciI6MywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQi + OiI4MjhkNDY1YS05YmQzLTQ0MzYtOTEzNC00ODhhNmZlYjFmYmYiLCJuYW1lIjoiRW5jb2RlciAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJj + aGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24gMi8x + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + NH19LHsiaWQiOiI3MTkwZTJhNi1lMDdjLTQ5MjItOWJiNC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjo1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjAzOWY3OTliLTUzN2YtNDI3Ny1hYmMxLTAzYzUwNjgyNzA4MyIsIm5hbWUiOiJC + dXR0b24gMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6NX19LHsiaWQiOiI0NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIs + Im5hbWUiOiJCdXR0b24gMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJkMjg0OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2MiLCJuYW1lIjoiRW5jb2RlciAyLzQiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3 + ODE4OTZhZCIsIm5hbWUiOiJCdXR0b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiIyNWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYiLCJuYW1lIjoiRW5jb2RlciAzLzEi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjE2MDEzYzQ4LTJhNDktNDY3Mi1i + ZjNhLTk1ZWFlMDVlZjNhNyIsIm5hbWUiOiJCdXR0b24gMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5j + b2RlciAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZh + ZDAtNDdlNC04Yjg5LWQ5NmI5NDUwMDAzMiIsIm5hbWUiOiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJu + YW1lIjoiRW5jb2RlciAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoi + ZjdhOTMwNWQtOTRhYi00ZmM5LTliNjQtYTkwOWU1NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hh + cmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVlZC00NjNiLTk3OWQtYjMw + OTE2ZTNiYjNhIiwibmFtZSI6IkVuY29kZXIgMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjExfX0seyJpZCI6IjZiOGFkMTMxLWJlZDktNGJhNy1hNjA0LTQzNWNmMDQzNmM5MSIsIm5hbWUiOiJCdXR0b24gMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51 + bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGIt + NDU4MS05NDdlLTVkZmEzZDNjMGJmNCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiIxYzA5MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiJh + Mzg0MzVjMC0zYWM0LTQxNzQtYjFiMS03ODFlZmY3OTg4YzQiLCJuYW1lIjoiRW5jb2RlciA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiY2hh + cmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTN9fSx7ImlkIjoiOTQwNjAzOGUtM2M0NC00MzJkLThlY2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjIzMmQ5ODlhLWUwZDctNDdmOS05NTg3LTY2OGFjMTI1ODRhMyIsIm5hbWUi + OiJCdXR0b24gNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjU1YWEyM2IwLWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJj + aGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMw + MDlkNGQiLCJuYW1lIjoiQnV0dG9uIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiIyN2JmMjE1Mi1lN2Y0LTQ1ZTUtOWI5OC1kY2FlODk2NWI5MWYiLCJuYW1lIjoiTGVmdCAxIiwic291 + cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9fSx7 + ImlkIjoiZWU5ZGY1OTAtNjFjYi00NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwi + Y2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9fSx7ImlkIjoiYWIwNTQ4ODAtMWM0My00ZjRhLWI0MTgt + YzNlYTRkMzFiNTEwIiwibmFtZSI6IkxlZnQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOH19LHsiaWQiOiJhNTdhMjc4Ny0zOTE5LTQ2ZmItOGZkZC0xN2ZiODNmNGJhZjEiLCJuYW1lIjoiUmlnaHQgMyIs + InNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijox + OX19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIifQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {10BC5330-A45C-4CE7-A581-A11135A4C571} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-365-exclusive-on-only.rpp b/plugin-reaper-realearn/resources/test-projects/issue-365-exclusive-on-only.rpp new file mode 100644 index 0000000..2bf74ae --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-365-exclusive-on-only.rpp @@ -0,0 +1,357 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAfjkAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4yIiwiaWQiOiJUWGo5X2p6ayIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6ImFkN2JhOTVjLTI4ZDQtNDMwMS1hZTEzLTI0YWJmMjk4MDI2OCIsIm5hbWUiOiJUcmFjayBleGNsdXNpdml0eSJ9LHsi + aWQiOiJiODc0NzFhZS1mNjVhLTQ4MGItYmM4Ny0yOGQzNjQ1NmJlOTIiLCJuYW1lIjoiR3JvdXAgaW50ZXJhY3Rpb24ifSx7ImlkIjoiZDI1MmE0ODUtYmNhOC00YzA4 + LTgxYTMtYzk1MTEzOThiYmQwIiwibmFtZSI6IkVuYWJsZS9kaXNhYmxlIG1hcHBpbmdzIn0seyJpZCI6Ijc0MTIzNDg0LTRjMTItNGFmMS04YTBlLTZmMDcwMzVlZWYy + YiIsIm5hbWUiOiJOYXZpZ2F0ZSB3aXRoaW4gZ3JvdXAgLSBMZWFkIn0seyJpZCI6IjUzZjBjYzM0LTQ0ZWQtNDY5My1hNzMzLWI3M2JkZDgxZjdlMiIsIm5hbWUiOiJO + YXZpZ2F0ZSB3aXRoaW4gZ3JvdXAgLSBGb2xsb3cifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJkOTQ5NjdjNi03NWUxLTRi + OWEtOTJlYy0yZWI3MzE1Nzk4NjciLCJuYW1lIjoiMSIsImdyb3VwSWQiOiJhZDdiYTk1Yy0yOGQ0LTQzMDEtYWUxMy0yNGFiZjI5ODAyNjgiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdl + dCI6eyJ0eXBlIjo3LCJ0cmFja0dVSUQiOiI1OTAxMzg5MS01RTkxLTRBNDctQThENC0wOUIzOTU3NDAzMEEiLCJmeEFuY2hvciI6ImlkIiwidHJhY2tFeGNsdXNpdml0 + eSI6NCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJ0cmFja0F1dG9tYXRpb25Nb2RlIjozLCJvc2NBcmdJbmRleCI6MH19 + LHsiaWQiOiI0NzUwZTFjMS03ODYxLTQxMTYtYjY3OC0xN2QzZGI3NWM1YzQiLCJuYW1lIjoiMiIsImdyb3VwSWQiOiJhZDdiYTk1Yy0yOGQ0LTQzMDEtYWUxMy0yNGFi + ZjI5ODAyNjgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6eyJ0eXBlIjoyLCJt + YXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0dVSUQiOiJFQUFCNTU1Mi1CRjZELTQwQjgtODdBNi0yMTlGODNCNUIwNjAiLCJmeEFuY2hv + ciI6ImlkIiwidHJhY2tFeGNsdXNpdml0eSI6NCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJ0cmFja0F1dG9tYXRpb25N + b2RlIjozLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI5NGIxOTE2NC1hZWIwLTQ5Y2QtOTk4MC1hNjU5ZDY1MGFlY2QiLCJuYW1lIjoiMSIsImdyb3VwSWQiOiJiODc0 + NzFhZS1mNjVhLTQ4MGItYmM4Ny0yOGQzNjQ1NmJlOTIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNSwiZ3JvdXBJ + bnRlcmFjdGlvbiI6ImludmVyc2UtdGFyZ2V0LXZhbHVlLW9uLW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrR1VJRCI6IjU5MDEzODkxLTVFOTEtNEE0Ny1B + OEQ0LTA5QjM5NTc0MDMwQSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowfX0seyJpZCI6ImUzODViOGYwLTQzNTYtNGU1OS1hMDM1LWQwMTA2OTAxZDlkZiIsIm5hbWUiOiIyIiwiZ3JvdXBJZCI6ImI4NzQ3MWFlLWY2NWEtNDgwYi1iYzg3 + LTI4ZDM2NDU2YmU5MiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJn + cm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10YXJnZXQtdmFsdWUtb24tb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6NywidHJhY2tHVUlEIjoiRUFBQjU1NTItQkY2RC00 + MEI4LTg3QTYtMjE5RjgzQjVCMDYwIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJn + SW5kZXgiOjB9fSx7ImlkIjoiOTJlOGRkNWEtMGU1YS00NGE1LTlkZjYtNGFhMGE0NDk3MDU3IiwibmFtZSI6IjEiLCJncm91cElkIjoiZDI1MmE0ODUtYmNhOC00YzA4 + LTgxYTMtYzk1MTEzOThiYmQwIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAu + MDV9LCJ0YXJnZXQiOnsidHlwZSI6MzYsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0Fy + Z0luZGV4IjowLCJ0YWdzIjpbInQxIl0sImV4Y2x1c2l2aXR5IjoyfX0seyJpZCI6IjAzODkyYTliLTM0Y2MtNDk3ZS04YzAyLTYzYzg4ZDkwMTdjNSIsIm5hbWUiOiIy + IiwiZ3JvdXBJZCI6ImQyNTJhNDg1LWJjYTgtNGMwOC04MWEzLWM5NTExMzk4YmJkMCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9LCJt + b2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjM2LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJ0MiJdLCJleGNsdXNpdml0eSI6Mn19LHsiaWQiOiI1YWVhYTI5NC01N2ZhLTQ2 + YzUtODQ0MC1kNGM5YjMwNmMyZDkiLCJuYW1lIjoidDEiLCJ0YWdzIjpbInQxIl0sImdyb3VwSWQiOiJkMjUyYTQ4NS1iY2E4LTRjMDgtODFhMy1jOTUxMTM5OGJiZDAi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxl + bWVudEluZGV4Ijo4fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiI1OTAxMzg5MS01RTkxLTRBNDctQThE + NC0wOUIzOTU3NDAzMEEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MH0sImlzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjNzkzMWEwMS0wNWI5LTQ1MDAtYjg1OC00YTFjYWVjNGJkZWMiLCJuYW1lIjoidDIiLCJ0YWdzIjpbInQyIl0sImdy + b3VwSWQiOiJkMjUyYTQ4NS1iY2E4LTRjMDgtODFhMy1jOTUxMTM5OGJiZDAiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6 + eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJFQUFCNTU1Mi1CRjZELTQwQjgtODdBNi0yMTlGODNCNUIwNjAiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImlzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5MDYzN2I4Ny1mMjEzLTQyY2MtYWIw + Yi1iMmI5MTgxOWVhZjUiLCJuYW1lIjoiTmF2aWdhdGUiLCJncm91cElkIjoiNzQxMjM0ODQtNGMxMi00YWYxLThhMGUtNmYwNzAzNWVlZjJiIiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9 + LCJtb2RlIjp7Im1pblN0ZXBTaXplIjotMC4xOTk5OTk5OTk5OTk5OTk5NiwibWF4U3RlcFNpemUiOi0wLjE5OTk5OTk5OTk5OTk5OTk2fSwidGFyZ2V0Ijp7InR5cGUi + OjM3LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiZXhjbHVzaXZp + dHkiOjEsImdyb3VwSWQiOiI1M2YwY2MzNC00NGVkLTQ2OTMtYTczMy1iNzNiZGQ4MWY3ZTIifX0seyJpZCI6IjA5YjRkZmFlLWJiY2ItNGM2OS1hMjU3LTE2ZTA2YTJm + MTBiYSIsIm5hbWUiOiIxIiwiZ3JvdXBJZCI6IjUzZjBjYzM0LTQ0ZWQtNDY5My1hNzMzLWI3M2JkZDgxZjdlMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJjaGFubmVsIjoxLCJudW1iZXIiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjcsInRy + YWNrR1VJRCI6IjU5MDEzODkxLTVFOTEtNEE0Ny1BOEQ0LTA5QjM5NTc0MDMwQSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsInRyYWNrQXV0b21hdGlvbk1vZGUiOjMsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjQ4MTY0NmQ4LWE2ZWMtNDIyMS1iMGRhLTc4NDc1 + OTI5YTkxNCIsIm5hbWUiOiIyIiwiZ3JvdXBJZCI6IjUzZjBjYzM0LTQ0ZWQtNDY5My1hNzMzLWI3M2JkZDgxZjdlMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjcs + InRyYWNrR1VJRCI6IkVBQUI1NTUyLUJGNkQtNDBCOC04N0E2LTIxOUY4M0I1QjA2MCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsInRyYWNrQXV0b21hdGlvbk1vZGUiOjMsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMw + MjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFj + dGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1i + ZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFt + ZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFh + ODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5 + ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsi + aWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYt + MjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00 + NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUy + YTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7Imlk + IjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6Niwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIv + MyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoi + QnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTci + LCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNl + Ijp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZi + OTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8z + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZj + OS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUi + OiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2Yjhh + ZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0 + ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2Qz + YzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9 + fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + IjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0 + LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5u + ZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYw + MzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0 + ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19 + LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + IjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1 + dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjoz + LCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYx + Y2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5h + bWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRy + b2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIiwibWFpbiI6eyJhY3RpdmVNYXBwaW5nQnlHcm91cCI6eyI1M2YwY2MzNC00NGVkLTQ2OTMtYTczMy1iNzNiZGQ4 + MWY3ZTIiOiIwOWI0ZGZhZS1iYmNiLTRjNjktYTI1Ny0xNmUwNmEyZjEwYmEiLCJhZDdiYTk1Yy0yOGQ0LTQzMDEtYWUxMy0yNGFiZjI5ODAyNjgiOiJkOTQ5NjdjNi03 + NWUxLTRiOWEtOTJlYy0yZWI3MzE1Nzk4NjcifX19 + AAAQAAAA + > + FLOAT 1229 366 1436 1514 + FXID {7894ACB5-CACA-4743-9560-3D26677F2BCF} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-376-sysex-control.rpp b/plugin-reaper-realearn/resources/test-projects/issue-376-sysex-control.rpp new file mode 100644 index 0000000..a073f52 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-376-sysex-control.rpp @@ -0,0 +1,144 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA1wEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xIiwiaWQiOiJMVU5LbVExTSIsImNvbnRyb2xEZXZpY2VJZCI6IjIiLCJmZWVkYmFja0RldmljZUlkIjoiMyIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJhYjA4ZjcxZS1kNjJhLTRkMzAtOWU2My0yY2I2YjIwNDRlNGEiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIg + N0YgNDIgMDIgMDAgMDAgMkYgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6 + MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {2804C184-967F-4375-8893-E01E11BCF958} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAAZAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {CA4CBBD7-AE15-495E-812D-803F21F35029} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-378-global-midi-filtering.rpp b/plugin-reaper-realearn/resources/test-projects/issue-378-global-midi-filtering.rpp new file mode 100644 index 0000000..cb49167 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-378-global-midi-filtering.rpp @@ -0,0 +1,309 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.19791652277306 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAApCQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4xMCIsImlkIjoiRFIxYnRKai0iLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1 + bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJlMWU0NjVmZC01ZDVlLTQ2ZDktOGVkOC00YTE1MzZmY2JlYTIi + LCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFy + Z2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4Ijow + fX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0 + dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFl + ZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7Imlk + IjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6Miwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEv + MyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoi + QnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEi + LCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNl + Ijp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1 + MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8z + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAt + OTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVu + Y29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02 + Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwi + bmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIx + NjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFk + ZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19 + LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJi + YzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgw + LWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIi + OjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsi + aWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjox + MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRv + biA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIs + Im5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02 + NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2Rl + ciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4 + Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwi + bmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5u + ZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0 + ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjox + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYx + IiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {C8D5DDE5-2ED8-4157-8509-FAD28A0670C5} + WAK 0 0 + > + > + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 139 594 676 616 + FXID {8AB5B311-03E5-4E77-9DFA-CAFE63066358} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAywEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4xMCIsImlkIjoiWWoyRlQxZFgiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1 + bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJkODE3ZDQxMi1lNzI5LTQ4YmEtYWFjOS0zZWFjMTY3NTQyMDAi + LCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {0E140AD3-BF35-43CA-A2B0-7BE67DDC101A} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-387-midi.rpp b/plugin-reaper-realearn/resources/test-projects/issue-387-midi.rpp new file mode 100644 index 0000000..674f3bb --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-387-midi.rpp @@ -0,0 +1,9546 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAxQEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS40IiwiaWQiOiJTcTk1eFhuNiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiOGNkNTZmZjAtMjJjNy00ZDJjLWFhM2YtZjk5MjhjYWY0NzNjIiwibmFtZSI6IjEiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJyYXdNaWRpUGF0dGVybiI6IkUwIFswZ2ZlIGRjYmFdIFswbm1sIGtqaWhdIiwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {953A1944-2EF4-4EE1-B8E8-25B69E69F2C8} + WAK 0 0 + BYPASS 1 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAOYAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAIA/AACAIwAAmQwAAAAAAAA1AAAAQzpcVXNlcnNcYmVuamFcQXBwRGF0YVxSb2FtaW5nXFJFQVBFUlxEYXRhXEdNLnJl + YWJhbmsAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAABNYWpvcgANAAAAMTAyMDM0MDUwNjA3 + AAEAAAAAAAAAAAAAAAAKAAAADQAAAAEAAAAAAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 1189 683 739 1163 + FXID {ACC704AF-E80C-4EBD-B1BF-684087EA1455} + WAK 0 0 + > + + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-387-osc.rpp b/plugin-reaper-realearn/resources/test-projects/issue-387-osc.rpp new file mode 100644 index 0000000..d7d91fd --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-387-osc.rpp @@ -0,0 +1,9548 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAeAIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS40IiwiaWQiOiJTcTk1eFhuNiIsImNvbnRyb2xEZXZpY2VJZCI6IjJhZTNjYTFjLWM3ZDMtNDI1Ni05NGIzLWU2NjRmZGY2MGU0 + YyIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI4Y2Q1NmZmMC0yMmM3LTRkMmMtYWEzZi1mOTky + OGNhZjQ3M2MiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImNoYW5uZWwiOjAsIm51bWJlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzIvcHVzaDEiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsInJldmVyc2VJ + c0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MzAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsInJhd01pZGlQYXR0ZXJuIjoiRTAgWzBnZmUgZGNiYV0gWzBubWwga2ppaF0iLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8yL3B1c2gzIiwib3NjQXJnSW5kZXgi + OjAsIm9zY0RldklkIjoiMmFlM2NhMWMtYzdkMy00MjU2LTk0YjMtZTY2NGZkZjYwZTRjIn19XX0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {953A1944-2EF4-4EE1-B8E8-25B69E69F2C8} + WAK 0 0 + BYPASS 1 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAOYAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAIA/AACAIwAAmQwAAAAAAAA1AAAAQzpcVXNlcnNcYmVuamFcQXBwRGF0YVxSb2FtaW5nXFJFQVBFUlxEYXRhXEdNLnJl + YWJhbmsAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAABNYWpvcgANAAAAMTAyMDM0MDUwNjA3 + AAEAAAAAAAAAAAAAAAAKAAAADQAAAAEAAAAAAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 1189 683 739 1163 + FXID {ACC704AF-E80C-4EBD-B1BF-684087EA1455} + WAK 0 0 + > + + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-391-target-value-sequences.rpp b/plugin-reaper-realearn/resources/test-projects/issue-391-target-value-sequences.rpp new file mode 100644 index 0000000..0d49d8e --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-391-target-value-sequences.rpp @@ -0,0 +1,236 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAVSgAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS41IiwiaWQiOiJoX3oyWkRlXyIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImMyYmRhNzU1LTZjZmItNGU5ZS04NjU0LWY0NDVkNDZhM2ViNSIs + Im5hbWUiOiJFbmNvZGVyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWFrZUFic29sdXRlRW5hYmxl + ZCI6dHJ1ZSwidGFyZ2V0VmFsdWVTZXF1ZW5jZSI6IjAuMjUgLSAwLjUsIDAuNzUsIDAuNSwgMC4xIn0sInRhcmdldCI6eyJ1bml0IjoicGVyY2VudCIsInR5cGUiOjIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6Ijk1NDBm + ZjZiLTc1Y2EtNDMyNC1iYzU1LWY2YTU0NWRiODUwNCIsIm5hbWUiOiJCdXR0b24gLSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjoxLCJu + dW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJtb2RlIjp7InR5cGUiOjEsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsInRhcmdldFZhbHVlU2VxdWVu + Y2UiOiIwLjI1IC0gMC41ICgwLjAyKSwgMC43NSwgMC41LCAwLjEifSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjA5NzdiMTVjLWU3MDAtNDQ0ZS1hN2Y4LWZiNjc0NWU3ZjRlZiIsIm5h + bWUiOiJCdXR0b24gKyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9 + LCJtb2RlIjp7InR5cGUiOjEsInRhcmdldFZhbHVlU2VxdWVuY2UiOiIwLjI1IC0gMC41ICgwLjAyKSwgMC43NSwgMC41LCAwLjEifSwidGFyZ2V0Ijp7InR5cGUiOjIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVy + TWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTlj + OTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhl + My00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNj + MjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFj + dGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7 + ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9u + IDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1l + IjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFl + ZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02 + NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIg + My8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2 + NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6 + IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2Ex + ZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2 + IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJp + ZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05Nzlk + LWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00 + MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoz + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7Imlk + IjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMs + ImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24g + NC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJu + YW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2Jj + ZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIs + InNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2 + fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6 + MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1i + NDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0 + IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {EB598F6A-203C-4C29-8CA0-C5C80D141049} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-392-combination-with-rel-time.RPP b/plugin-reaper-realearn/resources/test-projects/issue-392-combination-with-rel-time.RPP new file mode 100644 index 0000000..149fdcb --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-392-combination-with-rel-time.RPP @@ -0,0 +1,150 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 543 + ZOOM 100 58015 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABgBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS44IiwiaWQiOiI2aFVYejFONCIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJzMHZVXzVnSGV3bEFCczNCb3J5bFgiLCJuYW1lIjoiQ29uZGl0aW9ucyJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6 + e30sIm1hcHBpbmdzIjpbeyJpZCI6IlZ5STVGa1A2Y0tQMjFWTmx3N2lqaSIsIm5hbWUiOiJNb2R1bGF0aW9uIiwic291cmNlIjp7ImNhdGVnb3J5IjoicmVhcGVyIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJyZWFwZXJTb3VyY2VUeXBlIjoidGltZXIiLCJ0aW1lck1pbGxpcyI6MX0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImVlbENvbnRyb2xUcmFuc2Zvcm1hdGlvbiI6InkgPSAoc2luKHJlbF90aW1lIC8gNTAwKSArIDEpIC8gMiJ9LCJ0YXJn + ZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsImFjdGl2YXRpb25UeXBlIjoidGFyZ2V0LXZhbHVlIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjUs + ImlzT24iOmZhbHNlfSwibW9kaWZpZXJDb25kaXRpb24yIjp7InBhcmFtSW5kZXgiOjYsImlzT24iOmZhbHNlfSwiZWVsQ29uZGl0aW9uIjoieSA+IDAiLCJtYXBwaW5n + S2V5IjoiQ0pmT2dGSHQwUEo3QThRTUFxeHhkIn0seyJpZCI6IkNKZk9nRkh0MFBKN0E4UU1BcXh4ZCIsIm5hbWUiOiJQcm9qZWN0IHBsYXlpbmciLCJncm91cElkIjoi + czB2VV81Z0hld2xBQnMzQm9yeWxYIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjE2LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJQUWgzMV9JMmdKUzZIRzhFUWEtcE0iLCJuYW1lIjoiMyIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNl + ZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 588 159 752 660 + FXID {8A28E574-39E7-F14E-9B07-A1F87C119D58} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-392-target-based-conditional-activation.RPP b/plugin-reaper-realearn/resources/test-projects/issue-392-target-based-conditional-activation.RPP new file mode 100644 index 0000000..5c01315 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-392-target-based-conditional-activation.RPP @@ -0,0 +1,217 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.506024 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABnDAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS44IiwiaWQiOiIyLTJfZXdpMiIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJHSkEyMm5jcGVRSm9qTDA3QTFWQ2UiLCJuYW1lIjoiQmxhIn0seyJpZCI6ImEyUm9palVkRXktVEJxN19ZeHctbSIsIm5h + bWUiOiJGb28ifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJSNU80OXpnNkdURGtNcTlfWHZiVVgiLCJuYW1lIjoiMSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZSwib3NjQXJnSW5kZXgiOjB9LCJhY3RpdmF0aW9uVHlwZSI6InRhcmdldC12YWx1ZSIsImVlbENvbmRpdGlvbiI6InkgPiAwIiwibWFwcGluZ0tleSI6IjhDV3I0RzlR + UVN3bFJKUzZ5eUZyTiJ9LHsiaWQiOiJydEhWaTZDX1RGTTdsbzJLdHdJVGYiLCJuYW1lIjoiMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhB + bmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJhY3RpdmF0aW9uVHlwZSI6 + InRhcmdldC12YWx1ZSIsImVlbENvbmRpdGlvbiI6InkgPiAwIiwibWFwcGluZ0tleSI6IkZSYUJHNUlVeXN0Vzd0NVU3dGtsRSJ9LHsiaWQiOiI1dnROTXk0QjdzUE9r + RWJBeXY0SEMiLCJuYW1lIjoiMyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiZHg2V3Y0NTZnWHg1eV9jdm1QMWRxIiwibmFtZSI6IjQiLCJncm91cElkIjoi + R0pBMjJuY3BlUUpvakwwN0ExVkNlIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjUzLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJpbktWcWVBRDJEeU9oQlJPWTk5V3MiLCJuYW1lIjoiNSIsImdyb3VwSWQi + OiJHSkEyMm5jcGVRSm9qTDA3QTFWQ2UiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IkxmZTRfRVlZV3B2X21PNXZhS1BkcyIsIm5hbWUiOiI2IiwiZ3JvdXBJ + ZCI6IkdKQTIybmNwZVFKb2pMMDdBMVZDZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoibkJqbTNKOEF0VWlHbVFwbFI2dzRLIiwibmFtZSI6IjciLCJncm91 + cElkIjoiYTJSb2lqVWRFeS1UQnE3X1l4dy1tIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjUzLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJGUmFCRzVJVXlzdFc3dDVVN3RrbEUiLCJuYW1lIjoiOCIsImdy + b3VwSWQiOiJhMlJvaWpVZEV5LVRCcTdfWXh3LW0iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tHVUlEIjoiQjFBNTk2MDgtQjUyMy1EQTQ0 + LTk4ODUtRDhGRTNGNTQ1NjkwIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjB9LCJpc0VuYWJsZWQiOmZhbHNlLCJjb250cm9sSXNFbmFibGVkIjpmYWxzZSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOENXcjRHOVFRU3ds + UkpTNnl5RnJOIiwibmFtZSI6IjkiLCJncm91cElkIjoiYTJSb2lqVWRFeS1UQnE3X1l4dy1tIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNr + R1VJRCI6IjdCMjVCNjE0LUZFRjgtQkE0Mi05NjlCLUFGM0JEM0U5N0RCRCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwi + c2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiaXNFbmFibGVkIjpmYWxzZSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2UsImZlZWRiYWNrSXNFbmFibGVkIjpm + YWxzZX1dfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 423 131 752 675 + FXID {AD524079-C4F6-DA46-AC23-835E772C0473} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-394-save-mapping-snapshot.RPP b/plugin-reaper-realearn/resources/test-projects/issue-394-save-mapping-snapshot.RPP new file mode 100644 index 0000000..47a4521 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-394-save-mapping-snapshot.RPP @@ -0,0 +1,796 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 7 + ZOOM 100 0 0 + VZOOMEX 6 72 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADoygAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4xMSIsImlkIjoibTJrMlBpd3MiLCJjb250cm9sRGV2aWNlSWQiOiIwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjAiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJncm91cHMiOlt7ImlkIjoiT1pDeGlIQnVrd1kwRGVxb05TZ182IiwibmFtZSI6IkFybSIsInRhZ3MiOlsiYXJtIl19LHsiaWQiOiJnVi1GZWVsWHBP + c3habGZHZHAxRlYiLCJuYW1lIjoiTG9hZCBzbmFwc2hvdCJ9LHsiaWQiOiJnUzVTY3dfSmpsZjFKUU9RaUtoUVUiLCJuYW1lIjoiU2F2ZSBzbmFwc2hvdCJ9XSwiZGVm + YXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6InQ4WFdkenNDdks4LWRzc3hoYV9ZRyIsIm5hbWUiOiIxIiwiZ3JvdXBJZCI6Ik9aQ3hpSEJ1 + a3dZMERlcW9OU2dfNiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3N0b3AifSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6 + ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiIwNUI4ODQ2NC0wNjU5LTJDNDEt + OEQ1RC0zODU3QkVCNUQ3MUIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MH19LHsiaWQiOiJFcDFrMkx6dkN5UU1SSmprMW1nV24iLCJuYW1lIjoiMiIsImdyb3VwSWQiOiJPWkN4aUhCdWt3WTBEZXFvTlNnXzYiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9zdG9wIn0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tHVUlEIjoiRTU4MzEzMTUtNkE1QS1ERTQ0LTlBQjAtNDY1NEFBMUFDNThEIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiRXh4WWVkYk8xcFM0aTFn + aHlldlVuIiwibmFtZSI6IjMiLCJncm91cElkIjoiT1pDeGlIQnVrd1kwRGVxb05TZ182Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDMvc3RvcCJ9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5 + cGUiOjUsInRyYWNrR1VJRCI6IjFGQUQwRUUxLTQzQjUtRjA0QS1CMzZGLTFCMzc3QTZGQUE2RSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3Zl + VmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IkF1TkZTcDQ2bmRWeGYySGVWY2M0MSIsIm5hbWUiOiI0IiwiZ3JvdXBJZCI6 + Ik9aQ3hpSEJ1a3dZMERlcW9OU2dfNiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3N0b3AifSwibW9kZSI6eyJ0eXBlIjoyLCJt + YXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJCMTQzODAxRS1G + QkY5LUE4NDEtQkE0NC00NjY1RUJDRUVDRjkiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MH19LHsiaWQiOiItQWJ5SF9uUGIzZEs2VTVtcVdpSGgiLCJuYW1lIjoiNSAobmV3KSIsImdyb3VwSWQiOiJPWkN4aUhCdWt3WTBEZXFvTlNnXzYi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9zdG9wIn0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tHVUlEIjoiRUU0RUM3NDYtRTkxOS05MjQ4LTk2NDAtNzk0NDVGQkQ5 + QzBEIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoi + ZWZ4aWpWb24zTDk2ODY1eGc1X1I5IiwibmFtZSI6IkxvYWQgc25hcHNob3QgMSIsImdyb3VwSWQiOiJnVi1GZWVsWHBPc3habGZHZHAxRlYiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoicm93MS9wbGF5In0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MzUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9z + Y0FyZ0luZGV4IjowLCJ0YWdzIjpbImFybSJdLCJtYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6ImFybV9zdGF0ZV8xIn0sIm1hcHBpbmdTbmFwc2hv + dERlZmF1bHRWYWx1ZSI6eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjowLjB9fX0seyJpZCI6InlDb1ZIYm1ESV9yZzlrM2FzMlhMbyIsIm5hbWUiOiJMb2FkIHNu + YXBzaG90IDIiLCJncm91cElkIjoiZ1YtRmVlbFhwT3N4WmxmR2RwMUZWIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJvdzIvcGxheSJ9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjM1LCJmeEFuY2hvciI6 + ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJhcm0iXSwibWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiJhcm1fc3RhdGVfMiJ9LCJtYXBwaW5nU25hcHNob3REZWZhdWx0VmFsdWUiOnsia2luZCI6Ik5vcm1hbGl6ZWQiLCJ2 + YWx1ZSI6MC4wfX19LHsiaWQiOiI0Ylg0djlwRm4zYW5iLUVWNWpiUXoiLCJuYW1lIjoiTG9hZCBzbmFwc2hvdCAzIiwiZ3JvdXBJZCI6ImdWLUZlZWxYcE9zeFpsZkdk + cDFGViIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyb3czL3BsYXkifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjozNSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsiYXJtIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiYXJtX3N0YXRl + XzMifSwibWFwcGluZ1NuYXBzaG90RGVmYXVsdFZhbHVlIjp7ImtpbmQiOiJOb3JtYWxpemVkIiwidmFsdWUiOjAuMH19fSx7ImlkIjoib01Ja2RYVkM4c2JxMVhZUElC + Q3Q4IiwibmFtZSI6IkxvYWQgc25hcHNob3QgNCIsImdyb3VwSWQiOiJnVi1GZWVsWHBPc3habGZHZHAxRlYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijoicm93NC9wbGF5In0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsi + dHlwZSI6MzUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdz + IjpbImFybSJdLCJtYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6ImFybV9zdGF0ZV80In0sIm1hcHBpbmdTbmFwc2hvdERlZmF1bHRWYWx1ZSI6eyJr + aW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjowLjB9fX0seyJpZCI6IktfQWNwZXd6V2M2Tlg4V1pSS1UtUSIsIm5hbWUiOiJTYXZlIHNuYXBzaG90IDEiLCJncm91cElk + IjoiZ1M1U2N3X0pqbGYxSlFPUWlLaFFVIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDgvcm93MS9wYWQifSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1NSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsiYXJtIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiYXJtX3N0YXRlXzEifX19LHsiaWQiOiJKdW5vOGphb2dQQkw2SkdIMUZiRG0iLCJuYW1lIjoiU2F2ZSBzbmFwc2hvdCAyIiwiZ3JvdXBJZCI6 + ImdTNVNjd19KamxmMUpRT1FpS2hRVSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w4L3JvdzIvcGFkIn0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbImFybSJdLCJtYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6ImFybV9zdGF0ZV8yIn19fSx7ImlkIjoiZWVwU09wOVZMQno4YzFfYkk3Z0xLIiwibmFtZSI6IlNhdmUgc25hcHNob3QgMyIsImdyb3VwSWQiOiJn + UzVTY3dfSmpsZjFKUU9RaUtoUVUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0Fy + Z0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sOC9yb3czL3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBT + aXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJhcm0iXSwibWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiJhcm1fc3RhdGVfMyJ9fX0seyJpZCI6IlJaajl3SC1SRGlHYlk4Qmg4YlppdCIsIm5hbWUiOiJTYXZlIHNuYXBzaG90IDQiLCJncm91cElkIjoiZ1M1 + U2N3X0pqbGYxSlFPUWlLaFFVIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDgvcm93NC9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1NSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsiYXJtIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiYXJtX3N0YXRlXzQifX19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6ImFjNDljZDhhLWNkOTgtNGFjZC04NGEwLTI3NjM3MmFhOGQwNSIsIm5h + bWUiOiJTQUMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6InN0b3AtYWxsLWNsaXBzIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIyMjA1MDE4Mi00ODBlLTQyNjctYjIwMy05YWQ2NDFhNzJiNDQiLCJu + YW1lIjoiU3VzdGFpbiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjY0LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJzdXN0YWluIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImIwOWQ2MTY5LWE3 + ZGYtNGRiZS1iNzZjLTczZDMwYzg2MjVjMyIsIm5hbWUiOiJQbGF5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTF9LCJtb2RlIjp7Im1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI2YzQ3NDVhYy0zOWJiLTRlZDct + YjI5MC0yY2M1YWVmNDliYmIiLCJuYW1lIjoiUmVjIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNvcmQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + ODM4Y2M5ZTYtNTg1Ny00ZGQyLTk1MmItMzM5ZjNmODg2ZjNkIiwibmFtZSI6IlNoaWZ0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTh9 + LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzaGlmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJrMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiazIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImszIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTB9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJrNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjUxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjozLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiazUiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1Mn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NCwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6Ims2Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJrNyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjU0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijo2LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiazgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1NX0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6NywicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNvbDEvcm93MS9wYWQiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1 + ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2LCJzY2hlZHVsZWRfZm9yX3BsYXlf + c3RvcCI6Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJwYXVzZWQiOjUsImVtcHR5IjowLCJzdG9wcGVkIjo1LCJwbGF5aW5nIjoxLCJyZWNvcmRpbmciOjMs + InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wxL3JvdzIv + cGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZl + ZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic3RvcHBlZCI6NSwicGF1c2VkIjo1LCJwbGF5aW5nIjoxLCJlbXB0 + eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6MiwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9yX3JlY29y + ZF9zdG9wIjo2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y29sMS9yb3czL3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0 + LCJwbGF5aW5nIjoxLCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJyZWNvcmRpbmciOjMsInN0b3BwZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVj + b3JkX3N0b3AiOjYsInBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93My9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImNvbDEvcm93NC9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3Jk + X3N0YXJ0Ijo0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjIsInBhdXNlZCI6NSwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInJlY29y + ZGluZyI6Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInBsYXlpbmciOjEsInN0b3BwZWQiOjV9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93NS9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjowfSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjEs + InBhdXNlZCI6NSwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjIsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJzY2hlZHVsZWRf + Zm9yX3JlY29yZF9zdG9wIjo2LCJzdG9wcGVkIjo1LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93NS9wYWQiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93MS9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozM30sIm1vZGUiOnsi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJl + bXB0eSI6MCwic3RvcHBlZCI6NSwicmVjb3JkaW5nIjozLCJwYXVzZWQiOjUsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2LCJwbGF5aW5nIjoxLCJzY2hlZHVsZWRf + Zm9yX3JlY29yZF9zdG9wIjo2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyfX19LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEv + cGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzIvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjV9 + LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwi + dmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJyZWNvcmRpbmciOjMsInBhdXNlZCI6NSwicGxh + eWluZyI6MSwic3RvcHBlZCI6NSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NiwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6Mn19fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y29sMi9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3czL3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjE3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9E + aXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2LCJzdG9wcGVkIjo1LCJwYXVzZWQiOjUsInJlY29yZGluZyI6Mywi + c2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6Niwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6MiwiZW1wdHki + OjB9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNvbDIvcm93My9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93NC9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZy + b21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7ImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6Niwic3RvcHBlZCI6NSwicGxheWluZyI6MSwic2NoZWR1 + bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInBhdXNlZCI6NSwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9yX3Bs + YXlfc3RhcnQiOjJ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDIvcm93NS9wYWQiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsi + a2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7ImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NCwicmVjb3JkaW5nIjozLCJzdG9w + cGVkIjo1LCJwbGF5aW5nIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjIsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2LCJwYXVzZWQiOjUsInNjaGVkdWxl + ZF9mb3JfcmVjb3JkX3N0b3AiOjZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93NS9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93MS9wYWQiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1 + ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjMsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFy + dCI6NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInN0b3BwZWQiOjUsInBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJwbGF5aW5nIjox + LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzIv + cGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZl + ZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic3RvcHBlZCI6NSwicGxheWluZyI6MSwic2NoZWR1bGVkX2Zvcl9w + bGF5X3N0b3AiOjYsInBhdXNlZCI6NSwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJyZWNvcmRpbmciOjMsInNjaGVkdWxlZF9mb3JfcGxh + eV9zdGFydCI6Miwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6Nn19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y29sMy9yb3czL3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9yZWNv + cmRfc3RhcnQiOjQsInBsYXlpbmciOjEsInN0b3BwZWQiOjUsInJlY29yZGluZyI6Mywic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsImVtcHR5IjowLCJzY2hlZHVs + ZWRfZm9yX3BsYXlfc3RhcnQiOjIsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93My9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImNvbDMvcm93NC9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3BsYXlf + c3RhcnQiOjIsInBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJlbXB0eSI6MCwicGxheWlu + ZyI6MSwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2LCJzdG9wcGVkIjo1fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzQvcGFkIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzUvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1vZGUiOnsibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwbGF5aW5nIjox + LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6Niwic3RvcHBlZCI6NSwicmVjb3JkaW5nIjozLCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJw + YXVzZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzUvcGFkIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzEvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsi + c2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQi + OjQsInBsYXlpbmciOjEsImVtcHR5IjowLCJzdG9wcGVkIjo1LCJyZWNvcmRpbmciOjMsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6Mn19fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3cx + L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cyL3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI3 + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIs + InZhbHVlIjp7InN0b3BwZWQiOjUsImVtcHR5IjowLCJwbGF5aW5nIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6Niwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3Rh + cnQiOjQsInBhdXNlZCI6NSwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjJ9fX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNvbDQvcm93Mi9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvcm93My9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRv + RGlzY3JldGUiLCJ2YWx1ZSI6eyJlbXB0eSI6MCwicGF1c2VkIjo1LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjIsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3Ai + OjYsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2LCJzdG9wcGVkIjo1LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NCwicmVjb3JkaW5nIjozLCJwbGF5aW5n + IjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjb2w0L3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MTF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoi + RnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInBsYXlp + bmciOjEsInBhdXNlZCI6NSwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NiwiZW1wdHkiOjAsInN0b3BwZWQiOjUsInNjaGVkdWxlZF9mb3Jf + cGxheV9zdGFydCI6Mn19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3c0L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3c1L3BhZCIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6 + eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic3RvcHBlZCI6NSwicGF1c2VkIjo1LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0 + IjoyLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NiwicGxheWluZyI6MSwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2LCJzY2hlZHVs + ZWRfZm9yX3JlY29yZF9zdGFydCI6NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3c1L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNS9yb3cxL3BhZCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1Zh + bHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2LCJyZWNvcmRp + bmciOjMsInN0b3BwZWQiOjUsImVtcHR5IjowLCJwYXVzZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQi + OjIsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDUvcm93MS9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDUvcm93 + Mi9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwi + ZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NiwiZW1wdHkiOjAs + InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6Miwic3RvcHBlZCI6NSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3Rh + cnQiOjQsInJlY29yZGluZyI6MywicGF1c2VkIjo1LCJwbGF5aW5nIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w1L3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJjb2w1L3JvdzMvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9y + X3BsYXlfc3RhcnQiOjIsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2LCJwYXVzZWQiOjUsInBsYXlpbmciOjEs + ImVtcHR5IjowLCJzdG9wcGVkIjo1LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNS9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY29sNS9yb3c0L3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVj + b3JkX3N0b3AiOjYsImVtcHR5IjowLCJwYXVzZWQiOjUsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6Miwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsInJl + Y29yZGluZyI6Mywic3RvcHBlZCI6NSwicGxheWluZyI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDUvcm93NC9wYWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDUvcm93NS9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0fSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxl + ZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJwYXVzZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsImVtcHR5IjowLCJyZWNvcmRpbmciOjMsInNjaGVkdWxl + ZF9mb3JfcGxheV9zdGFydCI6Miwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInBsYXlpbmciOjEsInN0b3BwZWQiOjV9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDUvcm93NS9wYWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDYvcm93MS9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozN30sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6 + eyJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NiwicGF1c2VkIjo1LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6Niwic2NoZWR1bGVkX2Zvcl9y + ZWNvcmRfc3RhcnQiOjQsInN0b3BwZWQiOjUsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6MiwicGxheWluZyI6MSwicmVjb3JkaW5nIjozfX19LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w2L3Jv + dzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w2L3JvdzIvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + Mjl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRl + IiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NCwiZW1wdHkiOjAsInBsYXlpbmciOjEsInNj + aGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2LCJyZWNvcmRpbmciOjMsInBhdXNlZCI6NSwic3RvcHBlZCI6NSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6Nn19fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sNi9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNi9yb3czL3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjIxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0 + VG9EaXNjcmV0ZSIsInZhbHVlIjp7InJlY29yZGluZyI6Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsInBhdXNlZCI6NSwic3RvcHBlZCI6NSwicGxheWlu + ZyI6MSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NiwiZW1w + dHkiOjB9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNvbDYvcm93My9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDYvcm93NC9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQi + OiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjMsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJlbXB0eSI6MCwic2NoZWR1bGVk + X2Zvcl9wbGF5X3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6Miwic3RvcHBlZCI6NSwicGxheWlu + ZyI6MSwicGF1c2VkIjo1fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w2L3JvdzQvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w2L3JvdzUvcGFkIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxl + Ijp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzdG9wcGVkIjo1LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsInNj + aGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInJlY29yZGluZyI6MywicGxheWluZyI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInNjaGVkdWxlZF9mb3Jf + cGxheV9zdGFydCI6MiwicGF1c2VkIjo1fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w2L3JvdzUvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w3L3JvdzEvcGFkIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNr + VmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NiwiZW1wdHkiOjAsInNjaGVk + dWxlZF9mb3JfcGxheV9zdGFydCI6Miwic3RvcHBlZCI6NSwicmVjb3JkaW5nIjozLCJwYXVzZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJzY2hl + ZHVsZWRfZm9yX3BsYXlfc3RvcCI6NiwicGxheWluZyI6MX19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNy9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNy9y + b3cyL3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + LCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmciOjEsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9y + X3BsYXlfc3RvcCI6NiwicGF1c2VkIjo1LCJyZWNvcmRpbmciOjMsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQi + OjIsInN0b3BwZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjZ9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDcvcm93Mi9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImNvbDcvcm93My9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwYXVzZWQiOjUsInJlY29yZGluZyI6Mywi + c2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJzdG9wcGVkIjo1LCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInBsYXlpbmciOjEsInNjaGVk + dWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w3L3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJjb2w3L3JvdzQvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9y + ZWNvcmRfc3RvcCI6NiwicGxheWluZyI6MSwicmVjb3JkaW5nIjozLCJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJwYXVzZWQiOjUsInNjaGVk + dWxlZF9mb3JfcGxheV9zdG9wIjo2LCJzdG9wcGVkIjo1LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNy9yb3c0L3BhZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNy9yb3c1L3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjZ9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic3RvcHBl + ZCI6NSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NiwicmVjb3JkaW5nIjozLCJlbXB0eSI6 + MCwicGxheWluZyI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNy9yb3c1L3BhZCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sOC9yb3cxL3BhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM5fSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVl + Ijp7InJlY29yZGluZyI6Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6Niwic3RvcHBlZCI6NSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInBsYXlpbmci + OjEsInBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6MiwiZW1wdHkiOjB9fX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDgv + cm93MS9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDgvcm93Mi9wYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjozMX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3Jl + dGUiLCJ2YWx1ZSI6eyJwYXVzZWQiOjUsInN0b3BwZWQiOjUsInBsYXlpbmciOjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcGxh + eV9zdG9wIjo2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjIsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0LCJlbXB0eSI6MCwicmVjb3JkaW5nIjozfX19 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjb2w4L3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w4L3JvdzMvcGFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MjN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRl + eHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsiZW1wdHkiOjAsInBhdXNlZCI6NSwicGxheWluZyI6MSwic3RvcHBlZCI6NSwicmVjb3JkaW5nIjozLCJzY2hlZHVsZWRfZm9y + X3BsYXlfc3RvcCI6Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0IjoyLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRf + c3RvcCI6Nn19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY29sOC9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sOC9yb3c0L3BhZCIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjE1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2lu + ZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBhdXNlZCI6NSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFy + dCI6Miwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjQsImVtcHR5IjowLCJyZWNvcmRpbmciOjMsInN0b3BwZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0 + b3AiOjYsInBsYXlpbmciOjF9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDgvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDgvcm93NS9wYWQiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJmZWVkYmFja1ZhbHVlVGFi + bGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjYsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9w + Ijo2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjIsInBsYXlpbmciOjEsImVtcHR5IjowLCJwYXVzZWQiOjUsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0YXJ0Ijo0 + LCJzdG9wcGVkIjo1LCJyZWNvcmRpbmciOjN9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDgvcm93NS9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvc3RvcCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9z + dG9wIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3N0b3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2NX0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvc3RvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMy9z + dG9wIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjb2wzL3N0b3AiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDQvc3RvcCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjY3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9zdG9wIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJjb2w1L3N0b3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNvbDUvc3RvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNi9zdG9wIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6Njl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w2L3N0b3AiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImNvbDcvc3RvcCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjcwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sNy9zdG9wIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w4L3N0b3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo3MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDgvc3RvcCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9fSx7ImlkIjoicm93MS9wbGF5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyb3cxL3BsYXkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJvdzIvcGxheSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjgzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoicm93Mi9wbGF5IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJyb3czL3BsYXkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NH0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJvdzMvcGxheSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicm93NC9wbGF5Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyb3c0L3BsYXki + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJvdzUvcGxheSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg2fSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoicm93NS9wbGF5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19XSwiY29udHJvbGxlckN1c3Rv + bURhdGEiOnsiY29tcGFuaW9uIjp7ImNvbnRyb2xzIjpbeyJoZWlnaHQiOjMyLCJpZCI6IjBhYTU1OTQ5LWVhMTQtNDA0ZS1iM2FmLTNjOGE4NWVkNTBjNyIsImxhYmVs + T25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJl + bG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3cxL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5Niwi + eCI6MjU2LCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiOWIzNGJlYTYtOWVlYi00YTlhLTg1MGEtOGE4Mjk0Yzc3MmRhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzEvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjB9LHsiaGVp + Z2h0IjozMiwiaWQiOiJiZmIyZGI5MS1mYjI4LTRiMzQtOWJjNC02YjI1YzE4YzQ1MzQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJz + aXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1h + cHBpbmdzIjpbImNvbDEvcm93MS9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjAsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiI4MTdiODRh + OC02NzVmLTRlZGMtODIyOS02N2ZjM2U5YTM0MDAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93MS9w + YWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6IjFkMDk0ODUyLWY1YjktNGFjYS05OTcyLTcx + YmY3YjY4M2IyMyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xl + IjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNS9yb3cxL3BhZCJdLCJzaGFwZSI6InJlY3Rh + bmdsZSIsIndpZHRoIjo5NiwieCI6NTEyLCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiN2EyZmNiMzItMWUyNC00ODE3LWEyMGMtNzNkYmJkZDc4NmZjIiwibGFiZWxP + bmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVs + b3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w2L3JvdzEvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4 + Ijo2NDAsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiI3YWMyYjRkMS0xNmI4LTQzY2QtOWViZS01NmJkMzc1YjgxM2YiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9z + aXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93MS9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwieSI6MH0seyJoZWln + aHQiOjMyLCJpZCI6IjQyODM2NDgwLTYwNjEtNDYyNS04MWJmLTg3ZGJhOGQ0NWQzNSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNp + emVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFw + cGluZ3MiOlsiY29sOC9yb3cxL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6ODk2LCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiZmVlNTMy + MWQtZDY3NS00N2VjLWI0YjYtZDNlNDlmMDRlYmRhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVl + fSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wxL3JvdzIv + cGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjowLCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjU3NjA4MTZkLWJiOTItNDU3ZS04MzFjLTZi + YTU5NmQzM2E5MSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xl + IjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3Rh + bmdsZSIsIndpZHRoIjo5NiwieCI6MTI4LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6ImNiNTVhMTZmLWY1ODktNGVlNS1hOTc3LWExOWM3YmE1ZDJiMiIsImxhYmVs + T25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJl + bG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5Niwi + eCI6MjU2LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjNiNzU1ZTIxLTU5YzQtNDc2Ni05YzE0LTJkMjA2NDFiN2JmNCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNv + bnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNC9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6Mzg0LCJ5Ijo2NH0seyJo + ZWlnaHQiOjMyLCJpZCI6ImI2YzE0MTczLTQ4NmYtNDlmNy04N2U3LWMwZmU1MmJmOTg5OCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwi + bWFwcGluZ3MiOlsiY29sNS9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NTEyLCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjE4 + YjRkZmQ2LTAzMGUtNGRkYy05YTJhLWM4OGU3MjcwZTk1MCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9y + b3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NjQwLCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6ImJhYTgxM2U4LTdkNGItNGVhNy04 + NjEwLTc0ZjEyNGQzZGI5NyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNy9yb3cyL3BhZCJdLCJzaGFwZSI6 + InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NzY4LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjcyZWRhY2U0LTg5MGYtNDQ2YS1iNGU4LThkODc1YzE4NDFiYyIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sOC9yb3cyL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRo + Ijo5NiwieCI6ODk2LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6Ijk5OGM5MzcxLWZmMjQtNDk1NC05NDVkLTdjNjQxOTE2NTAwZSIsImxhYmVsT25lIjp7ImFuZ2xl + IjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3czL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MCwieSI6MTI4 + fSx7ImhlaWdodCI6MzIsImlkIjoiYzJmZGM1NzEtMzQ3Yy00OWRhLTlkOWUtNmNhYTRlOGQ5ZmNiIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2Vu + dGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzMvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjEyOH0seyJoZWlnaHQiOjMyLCJp + ZCI6IjZiMjVmMGUyLTRkNzktNDBhYi1iZTQ2LTU3ZDQ5Y2Q4ZDU3OSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsi + Y29sMy9yb3czL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MjU2LCJ5IjoxMjh9LHsiaGVpZ2h0IjozMiwiaWQiOiJkYjAxNGQ3Ni00NDMz + LTQ2M2EtYWM2Zi1kMzhkZGI3YzM2NGYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJl + bFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93My9wYWQiXSwi + c2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6MTI4fSx7ImhlaWdodCI6MzIsImlkIjoiOGUzOWJjMjMtZDUxZC00NWMyLWFkOTgtNmE2MDll + MTc1NTg0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w1L3JvdzMvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xl + Iiwid2lkdGgiOjk2LCJ4Ijo1MTIsInkiOjEyOH0seyJoZWlnaHQiOjMyLCJpZCI6IjI0Y2YxZGJkLWMwMWUtNGEwMC1iYWFjLTI4MmE1ZTQyMzJhZiIsImxhYmVsT25l + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93 + Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9yb3czL3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6 + NjQwLCJ5IjoxMjh9LHsiaGVpZ2h0IjozMiwiaWQiOiI1NjhjMDE3Ny00NTI1LTRlZWUtYmZiOS1lMjFhZTdjMjAyZTAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9z + aXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93My9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwieSI6MTI4fSx7Imhl + aWdodCI6MzIsImlkIjoiMmVhZDQ3ZDEtZWU3NS00MjlmLTljZDQtM2I1ZmU5ODljMDU2IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJt + YXBwaW5ncyI6WyJjb2w4L3JvdzMvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo4OTYsInkiOjEyOH0seyJoZWlnaHQiOjMyLCJpZCI6Ijk0 + MWQ5OTZkLTg4MmMtNDRmMy1hYmM0LTNmNjYxN2NjMDRmOSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9y + b3c0L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MCwieSI6MTkyfSx7ImhlaWdodCI6MzIsImlkIjoiMWNhOWIwZjctY2RlZC00ZjMxLWFk + ZWMtM2QzY2ZmNmM5MWNhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsi + YW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzQvcGFkIl0sInNoYXBlIjoi + cmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjE5Mn0seyJoZWlnaHQiOjMyLCJpZCI6ImE3ZDcxYjUwLTgxYjgtNDI2MS1hZDhiLTdjMzIyOWMwOWQ3OCIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMy9yb3c0L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRo + Ijo5NiwieCI6MjU2LCJ5IjoxOTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI0YTEyZmJiZC02YmExLTQzZjItYmY1Yy05NjUwMDc2YmYzNzYiLCJsYWJlbE9uZSI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQvcm93NC9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6 + MTkyfSx7ImhlaWdodCI6MzIsImlkIjoiMzRiOTg4YzgtMjVlNC00OTM2LTgwNDItNzE0MGM3YjI4ZGFkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + Y2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQi + OnRydWV9LCJtYXBwaW5ncyI6WyJjb2w1L3JvdzQvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo1MTIsInkiOjE5Mn0seyJoZWlnaHQiOjMy + LCJpZCI6ImJiZDRjZWJhLTYzZDItNDQxZi04Nzk0LTAyNjNhZjIyM2U1YyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3Mi + OlsiY29sNi9yb3c0L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6NjQwLCJ5IjoxOTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI1N2E0OTI5My04 + MDZiLTRiZmYtOGRmYy1lODEzMGFhOGNjMjMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93NC9wYWQi + XSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwieSI6MTkyfSx7ImhlaWdodCI6MzIsImlkIjoiYmNlZmExYWUtNjBmZS00ZTUyLWIzMTUtNjQ1 + YTdiNzU5MzFlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w4L3JvdzQvcGFkIl0sInNoYXBlIjoicmVjdGFu + Z2xlIiwid2lkdGgiOjk2LCJ4Ijo4OTYsInkiOjE5Mn0seyJoZWlnaHQiOjMyLCJpZCI6ImExZmM1ZmE2LWJmZWQtNGIwYS04MTMyLWFlZDZhNGIxNGJiYyIsImxhYmVs + T25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJl + bG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3c1L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5Niwi + eCI6MCwieSI6MjU2fSx7ImhlaWdodCI6MzIsImlkIjoiNzQ1MzhmOGMtZDI3Mi00YWM0LWEwZTItZmNjNjQ1MjMxMWI1IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzUvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMjgsInkiOjI1Nn0seyJo + ZWlnaHQiOjMyLCJpZCI6IjkyYmRjZTcwLTNhMTctNGQ1My1hZTkzLWVjNmZkZmE0YWNiMSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwi + bWFwcGluZ3MiOlsiY29sMy9yb3c1L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MjU2LCJ5IjoyNTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiIw + NWFjMzQwNy05MjA5LTQ3MWUtOWIwZS01MDI5Mzk2ZGUxY2MiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQi + OnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDQv + cm93NS9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjM4NCwieSI6MjU2fSx7ImhlaWdodCI6MzIsImlkIjoiYjg5OGIxMjctMTVlMy00YzU3 + LTg1MjgtMTZmNWVhN2Y2YWY2IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28i + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w1L3JvdzUvcGFkIl0sInNoYXBl + IjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo1MTIsInkiOjI1Nn0seyJoZWlnaHQiOjMyLCJpZCI6ImQ5MWIzYzIyLWZmNWQtNGZkMC1iOGMxLWRhY2Q4OGIxNzA2 + NSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9yb3c1L3BhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndp + ZHRoIjo5NiwieCI6NjQwLCJ5IjoyNTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5OWQ4MjU3MC01OWZmLTQxMDktYWFiYi0wMTkyNGJkZjUwNjciLCJsYWJlbE9uZSI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRv + bSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvcm93NS9wYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTYsIngiOjc2OCwi + eSI6MjU2fSx7ImhlaWdodCI6MzIsImlkIjoiMTYxZGRlZTktN2ZkNi00MjBkLWI5YmEtYTI3MTRmMTdjNzI0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9u + IjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w4L3JvdzUvcGFkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4Ijo4OTYsInkiOjI1Nn0seyJoZWlnaHQi + OjMyLCJpZCI6ImEzOTMwMDlmLTY1NWYtNDQyNS05NTczLTgzNTFiMTUxZmQzYSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6 + ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBw + aW5ncyI6WyJjb2wxL3N0b3AiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjMyLCJ5IjozMjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJmYjNhMTcyMy03YTY5 + LTQ1N2YtODcxZC01Mzg5YzI2MzdmNGQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxh + YmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNi9zdG9wIl0sInNo + YXBlIjoiY2lyY2xlIiwid2lkdGgiOjM0LCJ4Ijo2NzIsInkiOjMyMH0seyJoZWlnaHQiOjUwLCJpZCI6IjQ0ZDA5YzkxLWE2MjUtNGIzMS04ZTFlLWU0M2Q0NDQ3MGFk + YyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3N0b3AiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6 + MzQsIngiOjE2MCwieSI6MzIwfSx7ImhlaWdodCI6NTAsImlkIjoiY2FkMmRmMGItMzE5Yy00YzQzLTg4ZGMtODgwNDJiMmQ2YWVkIiwibGFiZWxPbmUiOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvc3RvcCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozNCwieCI6Mjg4LCJ5IjozMjB9LHsi + aGVpZ2h0Ijo1MCwiaWQiOiI3MGZiNGQzMC0zM2U4LTQ4MmItOTA4Zi03ZDg4NjZkNWZiZWMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRv + cCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVl + fSwibWFwcGluZ3MiOlsiY29sNC9zdG9wIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjM0LCJ4Ijo0MTYsInkiOjMyMH0seyJoZWlnaHQiOjUwLCJpZCI6ImU5MmU1 + NGE0LTk2ZGQtNGVkNS05NTgwLWUzYTU4N2VmODZiMCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w1L3N0 + b3AiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjU0NCwieSI6MzIwfSx7ImhlaWdodCI6NTAsImlkIjoiMWE5NWFkYzYtOGJiMy00ZjYwLThkZmQtNzY2 + NzU1MGIyMDJkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDcvc3RvcCJdLCJzaGFwZSI6ImNpcmNsZSIs + IndpZHRoIjozNCwieCI6ODAwLCJ5IjozMjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJlZTMwMjAyYy0wZWJkLTRhYmMtYmQzMC01YTk2MTQ4MGU1YjYiLCJsYWJlbE9uZSI6 + eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93 + Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sOC9zdG9wIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjM0LCJ4Ijo5MjgsInki + OjMyMH0seyJoZWlnaHQiOjUwLCJpZCI6IjBiZGVlZDE4LTQ2NDEtNDhiZC1iN2YxLTEwMjM2M2MyZDFmNSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyb3cxL3BsYXkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQi + OiIzOWE4MWM2OC1kZDY0LTQ3ZjAtOWQyZi0yYzM2ZTAwNGIxZDEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsi + cm93Mi9wbGF5Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjM0LCJ4IjoxMDI0LCJ5Ijo2NH0seyJoZWlnaHQiOjUwLCJpZCI6IjY1YjA1NDBkLTcxNzMtNDhiOS05 + YjYyLWVhZjgzM2M3M2UzZiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28i + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyb3czL3BsYXkiXSwic2hhcGUiOiJj + aXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInkiOjEyOH0seyJoZWlnaHQiOjUwLCJpZCI6IjRjZWFmZTc1LTQ1MjEtNGY4Ny1hYWJkLWNkODRkODNlNDZiZiIsImxh + YmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9u + IjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyb3c1L3BsYXkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngi + OjEwMjQsInkiOjI1Nn0seyJoZWlnaHQiOjUwLCJpZCI6IjhhNTkwNjU5LTU2MzItNDIyNy04MzJjLTdiZGNiZjZjYWRkZCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXpl + Q29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyb3c0L3BsYXkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInkiOjE5Mn0seyJoZWln + aHQiOjUwLCJpZCI6ImZlNjczOTJlLTE1YWMtNDlkOC1iY2YyLTEyOWEzNjUyZWZmYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJt + YXBwaW5ncyI6WyJhYzQ5Y2Q4YS1jZDk4LTRhY2QtODRhMC0yNzYzNzJhYThkMDUiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzQsIngiOjEwMjQsInkiOjMyMH0s + eyJoZWlnaHQiOjUwLCJpZCI6ImNhZmIwOGUwLWJhMDEtNGIzMS1hMTc1LTYyZTQxNThhNDJmNyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRl + ciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVl + fSwibWFwcGluZ3MiOlsiazEiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6OTgsIngiOjExMjAsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJmMmIwMzFhZC0xODc5 + LTRhNzUtOTg0Yy1lOTA2ZjY5YmQ3MWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJl + bFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImsyIl0sInNoYXBlIjoiY2ly + Y2xlIiwid2lkdGgiOjk4LCJ4IjoxMjgwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiNzU2NjdhMWEtNjBjOC00MGUyLTk1ZmYtZjFmOTBjYzM3ZTkyIiwibGFiZWxP + bmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVs + b3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJrMyJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo5OCwieCI6MTQ0MCwieSI6MH0s + eyJoZWlnaHQiOjUwLCJpZCI6ImVkYzFmNzk5LWNiMGEtNGY2MC1iZjhjLWI2NjRhYjU3ZGM1YSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRl + ciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVl + fSwibWFwcGluZ3MiOlsiazQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6OTgsIngiOjE2MDAsInkiOjB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiI1NTczMzM5Zi0xNTI0 + LTRiYTMtOWViOC0yMzg3MDJlNDM3YWQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJl + bFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIms1Il0sInNoYXBlIjoiY2ly + Y2xlIiwid2lkdGgiOjk4LCJ4IjoxMTIwLCJ5IjoxMjh9LHsiaGVpZ2h0Ijo1MCwiaWQiOiI5YTU5NGUyZS1kMmI4LTRmNDctYmRkYy02ODcyODA5YTQwODkiLCJsYWJl + bE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJi + ZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIms2Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjk4LCJ4IjoxMjgwLCJ5Ijox + Mjh9LHsiaGVpZ2h0Ijo1MCwiaWQiOiI4YjUwYTc1Yy1jMmNkLTRmODAtOWMzMC02ZDRiYjkwNzAwMzUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJj + ZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sIm1hcHBpbmdzIjpbIms3Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjk4LCJ4IjoxNDQwLCJ5IjoxMjh9LHsiaGVpZ2h0Ijo1MCwiaWQiOiI5MzAxYzhl + MC0xMjEyLTQxNGYtYTM0ZS03NmFmOTcyZDY4ZDAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIms4Il0sInNoYXBl + IjoiY2lyY2xlIiwid2lkdGgiOjk4LCJ4IjoxNjAwLCJ5IjoxMjh9LHsiaGVpZ2h0IjozNCwiaWQiOiI3OTM1ZTRhMC01MTJmLTRlOTYtODc0Ni1iMmM4ZjU0MmMzM2Qi + LCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRp + b24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIjIyMDUwMTgyLTQ4MGUtNDI2Ny1iMjAzLTlhZDY0MWE3MmI0NCJdLCJz + aGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5OCwieCI6MTEyMCwieSI6MjU2fSx7ImhlaWdodCI6MzQsImlkIjoiMzk1MDZhZjMtYjE3ZS00NWJiLWExNDgtNTFmZjM5 + YjRlZWI1IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiMDlkNjE2OS1hN2RmLTRkYmUtYjc2Yy03M2QzMGM4NjI1 + YzMiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6OTgsIngiOjE0NzIsInkiOjI1Nn0seyJoZWlnaHQiOjM0LCJpZCI6IjM1YWE1ZjQzLTZiMzYtNDZhZC1iMTA0 + LWZiZTg0YzM3Yjk2YiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiNmM0NzQ1YWMtMzliYi00ZWQ3LWIyOTAtMmNj + NWFlZjQ5YmJiIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk4LCJ4IjoxNjAwLCJ5IjoyNTZ9LHsiaGVpZ2h0IjozNCwiaWQiOiJkNzJiNDdjYy1mMGI0LTQ2 + YzYtOTUxMC1mZDk2N2Y4MTU5NjUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3 + byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIjgzOGNjOWU2LTU4NTctNGRkMi05 + NTJiLTMzOWYzZjg4NmYzZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5OCwieCI6MTEyMCwieSI6MzIwfV0sImdyaWREaXZpc2lvbkNvdW50IjoyLCJncmlk + U2l6ZSI6MzJ9fSwibWFwcGluZ1NuYXBzaG90cyI6W3siaWQiOiJhcm1fc3RhdGVfMiIsIm1hcHBpbmdzIjpbeyJpZCI6IkF1TkZTcDQ2bmRWeGYySGVWY2M0MSIsInRh + cmdldF92YWx1ZSI6eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjoxLjB9fSx7ImlkIjoiRXAxazJMenZDeVFNUkpqazFtZ1duIiwidGFyZ2V0X3ZhbHVlIjp7Imtp + bmQiOiJOb3JtYWxpemVkIiwidmFsdWUiOjAuMH19LHsiaWQiOiJFeHhZZWRiTzFwUzRpMWdoeWV2VW4iLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6Ik5vcm1hbGl6ZWQi + LCJ2YWx1ZSI6MS4wfX0seyJpZCI6InQ4WFdkenNDdks4LWRzc3hoYV9ZRyIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjoxLjB9fV19 + LHsiaWQiOiJhcm1fc3RhdGVfNCIsIm1hcHBpbmdzIjpbeyJpZCI6IkVwMWsyTHp2Q3lRTVJKamsxbWdXbiIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiTm9ybWFsaXpl + ZCIsInZhbHVlIjowLjB9fSx7ImlkIjoiQXVORlNwNDZuZFZ4ZjJIZVZjYzQxIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQiOiJOb3JtYWxpemVkIiwidmFsdWUiOjEuMH19 + LHsiaWQiOiJ0OFhXZHpzQ3ZLOC1kc3N4aGFfWUciLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6Ik5vcm1hbGl6ZWQiLCJ2YWx1ZSI6MC4wfX0seyJpZCI6IkV4eFllZGJP + MXBTNGkxZ2h5ZXZVbiIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjowLjB9fV19LHsiaWQiOiJhcm1fc3RhdGVfMSIsIm1hcHBpbmdz + IjpbeyJpZCI6IkV4eFllZGJPMXBTNGkxZ2h5ZXZVbiIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjowLjB9fSx7ImlkIjoidDhYV2R6 + c0N2SzgtZHNzeGhhX1lHIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQiOiJOb3JtYWxpemVkIiwidmFsdWUiOjEuMH19LHsiaWQiOiJFcDFrMkx6dkN5UU1SSmprMW1nV24i + LCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6Ik5vcm1hbGl6ZWQiLCJ2YWx1ZSI6MS4wfX0seyJpZCI6IkF1TkZTcDQ2bmRWeGYySGVWY2M0MSIsInRhcmdldF92YWx1ZSI6 + eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjowLjB9fV19LHsiaWQiOiJhcm1fc3RhdGVfMyIsIm1hcHBpbmdzIjpbeyJpZCI6IkF1TkZTcDQ2bmRWeGYySGVWY2M0 + MSIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjowLjB9fSx7ImlkIjoidDhYV2R6c0N2SzgtZHNzeGhhX1lHIiwidGFyZ2V0X3ZhbHVl + Ijp7ImtpbmQiOiJOb3JtYWxpemVkIiwidmFsdWUiOjAuMH19LHsiaWQiOiJFeHhZZWRiTzFwUzRpMWdoeWV2VW4iLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6Ik5vcm1h + bGl6ZWQiLCJ2YWx1ZSI6MC4wfX0seyJpZCI6IkVwMWsyTHp2Q3lRTVJKamsxbWdXbiIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiTm9ybWFsaXplZCIsInZhbHVlIjow + LjB9fV19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 412 126 752 675 + FXID {EE3BB328-D901-CD4B-9702-6FD758C60D29} + WAK 0 0 + > + > + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-394-tags.rpp b/plugin-reaper-realearn/resources/test-projects/issue-394-tags.rpp new file mode 100644 index 0000000..a367037 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-394-tags.rpp @@ -0,0 +1,143 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAnQUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wIiwiaWQiOiI3MjAwUnJuZSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVsdEdyb3Vw + Ijp7InRhZ3MiOlsiYXV0b3N0YXJ0IiwidGVzdCJdfSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjVmZTBiNGIxLWE3OWQtNGVj + Ny1hNjgxLWUxZmYwMmRiNGNjYyIsIm5hbWUiOiIxIiwidGFncyI6WyJuaWNlIiwic3BlY2lhbCJdLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hh + cmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFy + Z2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4Ijow + fSwiaXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImMxYmY4OWQ3LTBlYTMtNDk2My1iOTI1LWNhZTNhYjViZjQ4YyIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjozNiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIxZDg2MTdkYy02MzQzLTQ2YTUtOTdiYi05ZDE5MmMxYTdmOTMiLCJuYW1l + IjoiMSIsInRhZ3MiOlsibmljZSIsInNwZWNpYWwiXSwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiaXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImQ5YmRmNzZjLTZlMzAtNDRiMC1iMjViLWIxNDdhNWVjZTZiZSIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6 + MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0 + eXBlIjozNiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {F1C3984F-8488-4848-AB4C-140230ED85F8} + WAK 1 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-395-processing-order.rpp b/plugin-reaper-realearn/resources/test-projects/issue-395-processing-order.rpp new file mode 100644 index 0000000..3fdb5ca --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-395-processing-order.rpp @@ -0,0 +1,253 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAKwkAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS40IiwiaWQiOiJfOG56eTdqZSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJp + ZCI6ImQyNzQ3OWZmLWEwMTYtNGEyMS04NjVlLWJiODVlMWVmMDc1ZCIsIm5hbWUiOiJOb3JtYWwifSx7ImlkIjoiZWRkY2YzMjgtZTgxZS00NDQ5LWEyZjctMzJjNGQz + MmRlNzhhIiwibmFtZSI6Ikdyb3VwIGludGVyYWN0aW9uIn1dLCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiMDQzZGYzMGMtNTg3 + ZS00NjY1LWEyNzYtYTU2YjZlZDE5ZGI5IiwibmFtZSI6IjEiLCJncm91cElkIjoiZDI3NDc5ZmYtYTAxNi00YTIxLTg2NWUtYmI4NWUxZWYwNzVkIiwic291cmNlIjp7 + ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0dVSUQiOiJBNzAwRTQ1RS03NjZF + LTRGOTYtOUQwMi01ODFGNTk4QjY0MjMiLCJmeEFuY2hvciI6ImlkIiwidHJhY2tFeGNsdXNpdml0eSI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiIwYzc5MzUwMS0wODQxLTQ4ZWQtOGI5MS01NGVmM2JjOWQ4MGIiLCJuYW1lIjoiMiIsImdyb3Vw + SWQiOiJkMjc0NzlmZi1hMDE2LTRhMjEtODY1ZS1iYjg1ZTFlZjA3NWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJidXR0b25Vc2Fn + ZSI6InByZXNzLW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiNjBiZWM1YjUtNzY4ZS00YWViLTgwNDMtNjlkNTZjOGJmOWY3IiwibmFt + ZSI6IlRyaWdnZXIiLCJncm91cElkIjoiZWRkY2YzMjgtZTgxZS00NDQ5LWEyZjctMzJjNGQzMmRlNzhhIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMs + ImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + Z3JvdXBJbnRlcmFjdGlvbiI6InNhbWUtY29udHJvbCJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI3NjE2ZjczMy1hZjk2LTRiMjgtODc0ZC05MWU2ZmQ0OWM0NDgiLCJuYW1lIjoiMSIsImdyb3Vw + SWQiOiJlZGRjZjMyOC1lODFlLTQ0NDktYTJmNy0zMmM0ZDMyZGU3OGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImNoYW5uZWwiOjEsIm51bWJlciI6MTIs + ImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + YnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0dVSUQiOiJBNzAwRTQ1RS03NjZFLTRGOTYtOUQwMi01ODFGNTk4QjY0MjMi + LCJmeEFuY2hvciI6ImlkIiwidHJhY2tFeGNsdXNpdml0eSI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MH19LHsiaWQiOiJlNzI1NDExOS1lNjgyLTQxZTEtOTQ4ZC03MjZjYmRiODU2ZTMiLCJuYW1lIjoiMiIsImdyb3VwSWQiOiJlZGRjZjMyOC1lODFlLTQ0NDkt + YTJmNy0zMmM0ZDMyZGU3OGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNSwiYnV0dG9uVXNhZ2UiOiJw + cmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dfQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {D56B04E3-5C51-4A11-A853-98ADF522A8D0} + WAK 0 0 + > + > + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-4-multi-targets.rpp b/plugin-reaper-realearn/resources/test-projects/issue-4-multi-targets.rpp new file mode 100644 index 0000000..3da404d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-4-multi-targets.rpp @@ -0,0 +1,976 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAuSQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcmMuNCIsImlkIjoiRjBJN3FlT2wiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJhMzM4ZGE2Yy00ZWU5LTRiNjYtYmVmYy02MDNlYTFjYWM1MDIiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0 + Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6InNlbGVjdGVkKiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFt + ZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4 + MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1l + IjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0 + ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0x + OGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIg + Mi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQw + ODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6 + IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5 + Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4 + IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQi + OiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNo + YXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRl + ZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1i + ZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBh + LTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQt + NzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoz + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6 + eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoi + Yzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8z + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1l + IjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgx + ZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8y + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZl + OS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUi + OiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJk + OTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0 + ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0 + MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9 + fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + IjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1 + LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdo + dCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVt + YmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00 + NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {2781B733-C6DB-4E0B-A91E-9A82380FF078} + WAK 0 0 + > + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-403-osc-value-range.rpp b/plugin-reaper-realearn/resources/test-projects/issue-403-osc-value-range.rpp new file mode 100644 index 0000000..e1c4e9a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-403-osc-value-range.rpp @@ -0,0 +1,140 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACRAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJqYUd4QTgzdyIsImNvbnRyb2xEZXZpY2VJZCI6IjJhZTNjYTFjLWM3ZDMtNDI1Ni05NGIzLWU2NjRmZGY2MGU0 + YyIsImZlZWRiYWNrRGV2aWNlSWQiOiIyYWUzY2ExYy1jN2QzLTQyNTYtOTRiMy1lNjY0ZmRmNjBlNGMiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxl + ckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiMGh4UUtSV1dMMDVLb05Fb3IwVEw1IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJvc2NB + ZGRyZXNzUGF0dGVybiI6Ii8xL2ZhZGVyMSIsIm9zY0FyZ0luZGV4IjowLCJvc2NBcmdWYWx1ZVJhbmdlIjp7Im1pbiI6LTMuMCwibWF4IjoyLjB9LCJrZXlzdHJva2Ui + Om51bGx9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjMwLCJmeEFuY2hvciI6ImlkIiwib3Nj + QWRkcmVzc1BhdHRlcm4iOiIvMS9mYWRlcjIiLCJvc2NBcmdJbmRleCI6MCwib3NjQXJnVmFsdWVSYW5nZSI6eyJtaW4iOi0xLjAsIm1heCI6MS4wfSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {B03C2E69-11EC-4490-AAA2-45648296087A} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-405-fx-output-lag.rpp b/plugin-reaper-realearn/resources/test-projects/issue-405-fx-output-lag.rpp new file mode 100644 index 0000000..941fe8d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-405-fx-output-lag.rpp @@ -0,0 +1,9546 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAxQEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4zIiwiaWQiOiJTcTk1eFhuNiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiOGNkNTZmZjAtMjJjNy00ZDJjLWFhM2YtZjk5MjhjYWY0NzNjIiwibmFtZSI6IjEiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJyYXdNaWRpUGF0dGVybiI6IkUwIFswZ2ZlIGRjYmFdIFswbm1sIGtqaWhdIiwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {953A1944-2EF4-4EE1-B8E8-25B69E69F2C8} + WAK 0 0 + BYPASS 1 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAOYAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAIA/AACAIwAAmQwAAAAAAAA1AAAAQzpcVXNlcnNcYmVuamFcQXBwRGF0YVxSb2FtaW5nXFJFQVBFUlxEYXRhXEdNLnJl + YWJhbmsAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAABNYWpvcgANAAAAMTAyMDM0MDUwNjA3 + AAEAAAAAAAAAAAAAAAAKAAAADQAAAAEAAAAAAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 1189 683 739 1163 + FXID {ACC704AF-E80C-4EBD-B1BF-684087EA1455} + WAK 0 0 + > + + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-406-dynamic-for-non-dynamic-track-targets.rpp b/plugin-reaper-realearn/resources/test-projects/issue-406-dynamic-for-non-dynamic-track-targets.rpp new file mode 100644 index 0000000..ad2aae0 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-406-dynamic-for-non-dynamic-track-targets.rpp @@ -0,0 +1,213 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAHQIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS4zIiwiaWQiOiIwdUQ0bHNhSyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiYzZiZjU0ZGYtNTExYi00ZDI3LTg0ZDAtZjFmOTQxZjA4ZWI4IiwibmFtZSI6IjEiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InRyYWNrR1VJRCI6IkJFNjQwM0Y4LTZEMjktNERGOC05NDcwLURFQjQwRUY1RTdDMyIsImZ4QW5j + aG9yIjoiZHluYW1pYyIsImZ4RXhwcmVzc2lvbiI6InAxICogMSIsInBhcmFtSW5kZXgiOjI0LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJwYXJhbWV0ZXJzIjp7IjAiOnsidmFsdWUiOjEuMH19fQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {536B2E49-2780-43F0-9641-41CD3612EC81} + PARM_TCP 0 + WAK 0 0 + > + > + "" + cnlMde5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA6AQAAAEAAAAAABAA + I3BnbT1pbml0aWFsaXplCiNBTT1Db2xvdXJDb3B5CiNWZXJzPTEwMDAwCiNFbmRpYW49bGl0dGxlCiNubT04CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwoj + bXM9Q3RybEEKI21zPUN0cmxCCiNtcz1FbnYKI21zPUxGTyBMCiNtcz1MRk8gUgojbnY9MAojY209UENvcmUKU0Jhc2U9NApTd2luZz0wLjAwClNUcmlnPTEKUFNvbmc9 + MApQRm9sZD0wClBGaWxlPTEKR0ZpbGU9MgpHU2NhbGU9MApDaExheT0wClN1cnJPPTEKUmV2PTc2NzUKTEVEPTAuMDAKUEFHRT0wClBhZ2VzT249MApDb3JlTj0zClNs + aWNlPTQKVUlfb3A9MgpNaWRpQT01Ck1pZGlQPTYKREZvbGQ9NwpDdHJsQT0yCkN0cmxCPTExCiNjbT1MRk8KUmF0ZT01MC4wMApTdGVyZW89OTAuMDAKRk1vZD0wLjAw + CldhdmU9MAojY209RGVsYXkKVGltZUI9OApEbHlSYXRlPTAuMDAKTm90ZUN0cj0wClRhcEw9MTAwLjAwClRhcFI9MTAwLjAwCkxGT1RyZz0wCkxGT0RwdD0wLjAwCkxG + T0R5bj0wLjAwClJlZ2VuPTUwLjAwCkludmVydD0wCkJyaWdodD0wLjAwClNhdD01MC4wMApDb2xvdXI9My4wMApGbG9vcj0xCkR1Y2s9MApEdWNrVHJnPTAKRHVja1Ro + cj0wLjAwClJvdXRJTj0wClJvdXRGQj0wCldpZHRoPTEwMC4wMApNaXg9NTAuMDAKSW5HPTYuNzIKT3V0Rz0wLjAwClBhbmljPTAKRnJlZXplPTAKQnlwYXNzPTAKSFE9 + MAojY209TE1lbQpMYXlNZW0xPTAKI2NtPVN0aWNrZXIKc3RrclR4dD04CmJnaWR4PTkKcG9zeD0tMS4wMApwb3N5PS0xLjAwCgoKCgovLyBTZWN0aW9uIGZvciB1Z2x5 + IGNvbXByZXNzZWQgYmluYXJ5IERhdGEKLy8gRE9OJ1QgVE9VQ0ggVEhJUwoKJCQkJDEzMzMKP2dkZ2NlbmViOmFhYWJhYWFhOmVhYWFhYWFhOmdqZ29namhlOmdqZ2Jn + bWdqOmhrZ2Zjb2dpOmRjaGFhYWFhOgpoZGtrb2hhZDpjcGppYm9kYzpiaGJjbGdoaTphYTpiZjphaTplZDpuZDpvZzpwYzphZTphZzphbDpiYTpiYjpiCmU6Y2Y6Y2c6 + Y2k6Y2o6ZGY6ZHA6ZWE6ZWI6ZWU6ZWs6ZWw6ZW46ZmUhZ2RnY1lVQWFiQTI2NWdkZ2NZVVRBMTAKZ2pnb2dqaGVnamdiZ21namhrZ2Zjb2dpZGNoYUE0OWdkZ2NZVVRB + NzRnZGdjWVVUQTEwaGRra29oYWRjcGppYgpvZGNiaGJjbGdoaW9lUmZwb2NrbW9rSWhha2lsY25lamFtbGpoUEVHT2psQ1ZCWE5vcERFZmxsYWhtaHBnbmliCmhsU2xq + R2dhUUtCZmdEQkZKbmpaTWxuaXBGZ2RnY1lVSEExMGtiV0NBZ2RnY1lVQWFiQTI2NWdkZ2NZVWlhQTEKMzhnZGdjWVVBYWJBMjY1Z2RnY1lVTEEyN2dkZ2NZVVRBNzQ9 + MzgyNDcKAAA= + AGluaXRpYWxpemUAEAAAAA== + > + PRESETNAME initialize + FLOAT 3990 764 2106 937 + FXID {DD8A31D2-0431-4399-8EC8-2FB05DDFC471} + WAK 0 0 + BYPASS 0 0 0 + "" + cnlMde5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA6AQAAAEAAAAAABAA + I3BnbT1pbml0aWFsaXplCiNBTT1Db2xvdXJDb3B5CiNWZXJzPTEwMDAwCiNFbmRpYW49bGl0dGxlCiNubT04CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwoj + bXM9Q3RybEEKI21zPUN0cmxCCiNtcz1FbnYKI21zPUxGTyBMCiNtcz1MRk8gUgojbnY9MAojY209UENvcmUKU0Jhc2U9NApTd2luZz0wLjAwClNUcmlnPTEKUFNvbmc9 + MApQRm9sZD0wClBGaWxlPTEKR0ZpbGU9MgpHU2NhbGU9MApDaExheT0wClN1cnJPPTEKUmV2PTc2NzUKTEVEPTAuMDAKUEFHRT0wClBhZ2VzT249MApDb3JlTj0zClNs + aWNlPTQKVUlfb3A9MgpNaWRpQT01Ck1pZGlQPTYKREZvbGQ9NwpDdHJsQT0yCkN0cmxCPTExCiNjbT1MRk8KUmF0ZT01MC4wMApTdGVyZW89OTAuMDAKRk1vZD0wLjAw + CldhdmU9MAojY209RGVsYXkKVGltZUI9OApEbHlSYXRlPTAuMDAKTm90ZUN0cj0wClRhcEw9MTAwLjAwClRhcFI9MTAwLjAwCkxGT1RyZz0wCkxGT0RwdD0wLjAwCkxG + T0R5bj0wLjAwClJlZ2VuPTUwLjAwCkludmVydD0wCkJyaWdodD0wLjAwClNhdD01MC4wMApDb2xvdXI9My4wMApGbG9vcj0xCkR1Y2s9MApEdWNrVHJnPTAKRHVja1Ro + cj0wLjAwClJvdXRJTj0wClJvdXRGQj0wCldpZHRoPTEwMC4wMApNaXg9NTAuMDAKSW5HPTAuNjAKT3V0Rz0wLjAwClBhbmljPTAKRnJlZXplPTAKQnlwYXNzPTAKSFE9 + MAojY209TE1lbQpMYXlNZW0xPTAKI2NtPVN0aWNrZXIKc3RrclR4dD04CmJnaWR4PTkKcG9zeD0tMS4wMApwb3N5PS0xLjAwCgoKCgovLyBTZWN0aW9uIGZvciB1Z2x5 + IGNvbXByZXNzZWQgYmluYXJ5IERhdGEKLy8gRE9OJ1QgVE9VQ0ggVEhJUwoKJCQkJDEzMzMKP2dkZ2NlbmViOmFhYWJhYWFhOmVhYWFhYWFhOmdqZ29namhlOmdqZ2Jn + bWdqOmhrZ2Zjb2dpOmRjaGFhYWFhOgpoZGtrb2hhZDpjcGppYm9kYzpiaGJjbGdoaTphYTpiZjphaTplZDpuZDpvZzpwYzphZTphZzphbDpiYTpiYjpiCmU6Y2Y6Y2c6 + Y2g6Y2k6Y2o6ZGY6ZHA6ZWE6ZWI6ZWU6ZWw6ZW46ZmUhZ2RnY1lWQWFiQTI2NWdkZ2NZVlVBMTAKZ2pnb2dqaGVnamdiZ21namhrZ2Zjb2dpZGNoYUE0OWdkZ2NZVlVB + NzRnZGdjWVZVQTEwaGRra29oYWRjcGppYgpvZGNiaGJjbGdoaW9lU2Zwb2NrbW9rSWhha2lsY25lamFtbGpoUUVHT2psQ1dCWE5vcERFZmxsYWhtaHBnbmliCmhsVGxq + R2dhUktCZmdEQkZKbmpaTWxuaXBGZ2RnY1lWSEExMGtiUENBZ2RnY1lWQWFiQTI2NWdkZ2NZVmlhQTEKMzhnZGdjWVZBYWJBMjY1Z2RnY1lWTEEyN2dkZ2NZVlVBNzQ9 + MzgyNTQKAAA= + AGluaXRpYWxpemUAEAAAAA== + > + PRESETNAME initialize + FLOAT 674 964 2106 937 + FXID {0038A536-FDF3-4328-960A-F373C8C43028} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-408-auto-load-fallback.RPP b/plugin-reaper-realearn/resources/test-projects/issue-408-auto-load-fallback.RPP new file mode 100644 index 0000000..5d2f60d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-408-auto-load-fallback.RPP @@ -0,0 +1,180 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAA2AwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS45IiwiaWQiOiJaY2JGYWNaciIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjNmMVktZi1xUy13TUxZZWtWZ01sNyIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUi + OjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tHVUlEIjoiMDg3MzM4NDItQkEwQi1FMjRELTg0 + NjItOTI3MTc2MzIzNDEwIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwibWFpblByZXNldEF1dG9Mb2Fk + TW9kZSI6ImZvY3VzZWQtZngiLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 805 173 752 675 + FXID {D3C9D9AC-7F5F-E446-A677-F6CE2B1558EB} + WAK 0 0 + > + > + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAmRSOPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AAAQAAAA + > + FLOATPOS 191 250 574 394 + FXID {02BD6C52-9C1F-8743-98E4-A376DBF8B003} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-414-projection-feedback.rpp b/plugin-reaper-realearn/resources/test-projects/issue-414-projection-feedback.rpp new file mode 100644 index 0000000..83866be --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-414-projection-feedback.rpp @@ -0,0 +1,324 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAMCUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJEUjFidEpqLSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImUxZTQ2NWZkLTVkNWUtNDZkOS04ZWQ4LTRhMTUzNmZjYmVhMiIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJn + ZXQiOnsidHJhY2tHVUlEIjoiNEFGNzk0NjktNDY3Qy00QzYxLTk0OTItMUIyMzc2RDQ1NjBEIiwiZW5hYmxlT25seUlmVHJhY2tJc1NlbGVjdGVkIjp0cnVlLCJmeEFu + Y2hvciI6ImlkIiwiZnhHVUlEIjoiOEFCNUIzMTEtMDNFNS00RTc3LTlERkEtQ0FGRTYzMDY2MzU4IiwicGFyYW1JbmRleCI6MTAsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQxMTUt + YjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxfX0seyJpZCI6ImU1NTQz + YWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MCwiY2hhcmFjdGVy + IjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVmMC01Y2Y4LTQ1ZTktOWM5MC00ZDQ5MDZjZjYzMDQiLCJuYW1lIjoiRW5jb2RlciAxLzIi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6ImRkMWE4MWQzLWQ2MDctNDMyOS05 + ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2NTMtOWZiYi1lZjkwN2RiMDlkODciLCJuYW1lIjoiRW5j + b2RlciAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6ImI4MmQ3NzUyLWQy + MjktNDQ4Zi1iZDQyLWFmNjFiNDU5ZTQ4MSIsIm5hbWUiOiJCdXR0b24gMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MiwiY2hhcmFjdGVyIjoxLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMtYjcwNi0yN2I2MmUyYTVkYmUiLCJu + YW1lIjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjFj + ODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MywiY2hhcmFj + dGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiI4MjhkNDY1YS05YmQzLTQ0MzYtOTEzNC00ODhhNmZl + YjFmYmYiLCJuYW1lIjoiRW5jb2RlciAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0s + eyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6 + NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI3MTkwZTJhNi1lMDdjLTQ5MjItOWJi + NC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4Ijo1fX0seyJpZCI6IjAzOWY3OTliLTUzN2YtNDI3Ny1hYmMxLTAzYzUwNjgyNzA4MyIsIm5hbWUiOiJCdXR0b24gMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEs + Im51bWJlciI6NSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiI0NWJiYmQ1ZC0zNGY3 + LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjMsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJCdXR0b24gMi8zIiwic291cmNlIjp7ImNo + YW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJkMjg0 + OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2MiLCJuYW1lIjoiRW5jb2RlciAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3LCJjaGFyYWN0 + ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUiOiJCdXR0b24gMi80Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsi + aWQiOiIyNWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYiLCJuYW1lIjoiRW5jb2RlciAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4 + LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjE2MDEzYzQ4LTJhNDktNDY3Mi1iZjNhLTk1ZWFlMDVlZjNhNyIsIm5hbWUiOiJCdXR0b24g + My8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5jb2RlciAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04Yjg5LWQ5NmI5NDUwMDAzMiIsIm5hbWUi + OiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJuYW1lIjoiRW5jb2RlciAzLzMiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdhOTMwNWQtOTRhYi00ZmM5LTliNjQtYTkwOWU1NTkz + NTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVlZC00NjNiLTk3OWQtYjMwOTE2ZTNiYjNhIiwibmFtZSI6IkVuY29kZXIgMy80Iiwi + c291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjZiOGFkMTMxLWJlZDktNGJhNy1h + NjA0LTQzNWNmMDQzNmM5MSIsIm5hbWUiOiJCdXR0b24gMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEzZDNjMGJmNCIsIm5hbWUiOiJF + bmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiIxYzA5MTU4 + MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIi + OjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiJhMzg0MzVjMC0zYWM0LTQxNzQtYjFiMS03ODFlZmY3OTg4 + YzQiLCJuYW1lIjoiRW5jb2RlciA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7 + ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjox + MywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTQwNjAzOGUtM2M0NC00MzJkLThl + Y2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjE0fX0seyJpZCI6IjIzMmQ5ODlhLWUwZDctNDdmOS05NTg3LTY2OGFjMTI1ODRhMyIsIm5hbWUiOiJCdXR0b24gNC8zIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjU1YWEyM2Iw + LWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIi + OjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMwMDlkNGQiLCJuYW1lIjoiQnV0dG9uIDQvNCIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsi + aWQiOiIyN2JmMjE1Mi1lN2Y0LTQ1ZTUtOWI5OC1kY2FlODk2NWI5MWYiLCJuYW1lIjoiTGVmdCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6OCwiY2hh + cmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5ZGY1OTAtNjFjYi00NzQ2LWEzMTMtNzcz + NTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9fSx7ImlkIjoiYWIwNTQ4ODAtMWM0My00ZjRhLWI0MTgtYzNlYTRkMzFiNTEwIiwibmFtZSI6IkxlZnQgMyIsInNv + dXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOH19 + LHsiaWQiOiJhNTdhMjc4Ny0zOTE5LTQ2ZmItOGZkZC0xN2ZiODNmNGJhZjEiLCJuYW1lIjoiUmlnaHQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEz + LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOX19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWlkaS1m + aWdodGVyLXR3aXN0ZXIifQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {C8D5DDE5-2ED8-4157-8509-FAD28A0670C5} + WAK 0 0 + > + > + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAMg2AAAyIwAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAQAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 139 594 1341 1163 + FXID {8AB5B311-03E5-4E77-9DFA-CAFE63066358} + WAK 0 0 + BYPASS 0 0 0 + "" + Y2Flcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAcBgAAAQAAAAAAEAA= + AAAAAAAA8D8Aa8QaAAAAAFABxBoAAAAATkdQUk9GSUxFPVVTQ0hJAFBST0NFU1NPUl9MRVZFTD02AE5WTV9TWU1MSU5LPUM6XFByb2dyYW0gRmlsZXNcbm9kZWpzAFNF + U1NJT05OQU1FPUNvbnNvbGUAQUxMVVNFUlNQUk9GSUxFPUM6XFByb2dyYW1EYXRhAFBST0NFU1NPUl9BUkNISVRFQ1RVUkU9QU1ENjQAUFNNb2R1bGVQYXRoPUM6XFVz + ZXJzXGJlbmphXERvY3VtZW50c1xXaW5kb3dzUG93ZXJTaGVsbFxNb2R1bGVzO0M6XFVzZXJzXGJlbmphXEFwcERhdGFcTG9jAGxcR29vZ2xlXENsb3VkIFNES1xnb29n + bGUtY2xvdWQtc2RrXHBsYXRmb3JtXFBvd2VyU2hlbGwAU3lzdGVtRHJpdmU9QzoAVVNFUk5BTUU9YmVuamEAUHJvZ3JhbUZpbGVzKHg4Nik9QzpcUHJvZ3JhbSBGaWxl + cyAoeDg2KQBGUFNfQlJPV1NFUl9VU0VSX1BST0ZJTEVfU1RSSU5HPURlZmF1bHQAT0FOT0NBQ0hFPTEAUEFUSEVYVD0uQ09NOy5FWEU7LkJBVDsuQ01EOy5WQlM7LlZC + RTsuSlM7LkpTRTsuV1NGOy5XU0g7Lk1TQwBEcml2ZXJEYXRhPUM6XABpbmRvd3NcU3lzdGVtMzJcRHJpdmVyc1xEcml2ZXJEYXRhAE9uZURyaXZlQ29uc3VtZXI9Qzpc + VXNlcnNcYmVuamFcT25lRHJpdmUAX05UX0FMVF9TWU1CT0xfUEFUSD1DOlxVc2Vyc1xiZW5qYVxEb2N1bWVudHNcb3B0XHBkYgBQcm9ncmFtRGF0YT1DOlxQcm9ncmFt + RGF0YQBQcm9ncmFtVzY0MzI9QzpcUHJvZ3JhbSBGaWxlcwBDT01NQU5ERVJfUEFUSD1DOlxQcm9ncmFtIEZpbGVzXHRvdGFsY21kAEhPTUVQQVRIPVxVc2Vyc1xiZW5q + YQBQUk9DRVMAT1JfSURFTlRJRklFUj1JbnRlbDY0IEZhbWlseSA2IE1vZGVsIDE1OCBTdGVwcGluZyAxMCwgR2VudWluZUludGVsAFByb2dyYW1GaWxlcz1DOlxQcm9n + cmFtIEZpbGVzAFBVQkxJQz1DOlxVc2Vyc1xQdWJsaWMAd2luZGlyPUM6XFdJTkRPV1MAQ0xJTktfRElSPUM6XFByb2dyYW0gRmlsZXMgKHg4NilcY2xpbmtcMC40LjkA + PTo6PTo6XABMT0NBTEFQUERBVEE9QzpcVXNlcnNcYmVuamFcQXBwRGF0YVxMb2NhbABDaG9jb2xhdGV5TGFzdFBhdGhVcGRhAAAAAABkIEphbiAyMiAxODoyODozMSAy + MDIwAFVTRVJET01BSU49VVNDSEkATE9HT05TRVJWRVI9XFxVU0NISQBGUFNfQlJPV1NFUl9BUFBfUFJPRklMRV9TVFJJTkc9SW50ZXJuZXQgRXhwbG9yZXIASkFWQV9I + T01FPUM6XFByb2dyYW0gRmlsZXNcQWRvcHRPcGVuSkRLXGpyZS0xMy4wLjEuOS1ob3RzcG90XABfTlRfU1lNQk9MX1BBVEg9c3ltc3J2KnN5bXNydi5kbGwqQzpcV0lO + RE9XU1xTeW1ib2xzKmh0dHA6Ly9tc2RsLm1pY3Jvc29mdC5jb20vZG93bmwAYWQvc3ltYm9scwBPbmVEcml2ZT1DOlxVc2Vyc1xiZW5qYVxPbmVEcml2ZQBBUFBEQVRB + PUM6XFVzZXJzXGJlbmphXEFwcERhdGFcUm9hbWluZwBSRUFMRUFSTl9MT0c9ZGVidWcsdnN0PWluZm8saHlwZXI9dHJhY2UAQ2hvY29sYXRleUluc3RhbGw9QzpcUHJv + Z3JhbURhdGFcY2hvY29sYXRleQBDb21tb25Qcm9ncmFtRmlsZXM9QzpcUHJvZ3JhbSBGaWxlc1xDb21tb24gRmlsZXMAUGF0aD1DOlxQcm9ncmFtIEZpbGVzXEFkb3B0 + T3BlbkpES1xqcmUtRKwAAAIAAACAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {329F1251-E63F-48DA-835D-2C80C97D8B6C} + WAK 0 0 + > + "" + cnlMde5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA6AQAAAEAAAAAABAA + I3BnbT1pbml0aWFsaXplCiNBTT1Db2xvdXJDb3B5CiNWZXJzPTEwMDAwCiNFbmRpYW49bGl0dGxlCiNubT04CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwoj + bXM9Q3RybEEKI21zPUN0cmxCCiNtcz1FbnYKI21zPUxGTyBMCiNtcz1MRk8gUgojbnY9MAojY209UENvcmUKU0Jhc2U9NApTd2luZz0wLjAwClNUcmlnPTEKUFNvbmc9 + MApQRm9sZD0wClBGaWxlPTEKR0ZpbGU9MgpHU2NhbGU9MApDaExheT0wClN1cnJPPTEKUmV2PTc2NzUKTEVEPTAuMDAKUEFHRT0wClBhZ2VzT249MApDb3JlTj0zClNs + aWNlPTQKVUlfb3A9MwpNaWRpQT01Ck1pZGlQPTYKREZvbGQ9NwpDdHJsQT0yCkN0cmxCPTExCiNjbT1MRk8KUmF0ZT01MC4wMApTdGVyZW89OTAuMDAKRk1vZD0wLjAw + CldhdmU9MAojY209RGVsYXkKVGltZUI9OApEbHlSYXRlPTAuMDAKTm90ZUN0cj0wClRhcEw9MTAwLjAwClRhcFI9MTAwLjAwCkxGT1RyZz0wCkxGT0RwdD0wLjAwCkxG + T0R5bj0wLjAwClJlZ2VuPTUwLjAwCkludmVydD0wCkJyaWdodD0wLjAwClNhdD01MC4wMApDb2xvdXI9My4wMApGbG9vcj0xCkR1Y2s9MApEdWNrVHJnPTAKRHVja1Ro + cj0wLjAwClJvdXRJTj0wClJvdXRGQj0wCldpZHRoPTEwMC4wMApNaXg9NTAuMDAKSW5HPTAuMDAKT3V0Rz0wLjAwClBhbmljPTAKRnJlZXplPTAKQnlwYXNzPTAKSFE9 + MAojY209TE1lbQpMYXlNZW0xPTAKI2NtPVN0aWNrZXIKc3RrclR4dD04CmJnaWR4PTkKcG9zeD0tMS4wMApwb3N5PS0xLjAwCgoKCgovLyBTZWN0aW9uIGZvciB1Z2x5 + IGNvbXByZXNzZWQgYmluYXJ5IERhdGEKLy8gRE9OJ1QgVE9VQ0ggVEhJUwoKJCQkJDEzMzMKP2dkZ2NlbmViOmFhYWJhYWFhOmVhYWFhYWFhOmdqZ29namhlOmdqZ2Jn + bWdqOmhrZ2Zjb2dpOmRjaGFhYWFhOgpoZGtrb2hhZDpjcGppYm9kYzpiaGJjbGdoaTphYTpiZjpiYjplZDpuZDpvZzpwYzphZTphZzphaTphbDpiYTpiCmU6Y2Y6Y2c6 + Y2k6Y2o6ZGY6ZHA6ZWE6ZWI6ZWU6ZWw6ZW46ZmU6ZmchZ2RnY1hVQWFiQTI2NWdkZ2NYVVRBMTAKZ2pnb2dqaGVnamdiZ21namhrZ2Zjb2dpZGNoYUE0OWdkZ2NYVVRB + NzRnZGdjWFVUQTEwaGRra29oYWRjcGppYgpvZGNiaGJjbGdoaW9lUmZwb2NrbW9rSWhha2lsY25lamFtbGpoUEVHT2psSlZCV05vcERFZmxsYWhtaHBnbmliCmhsU2xq + R2dhUUxCWkRCRktuallNbG5pcEZnZGdjWFVIQTEwa2JuaUNBZ2RnY1hVQWFiQTI2NWdkZ2NYVWlhQTEKMzhnZGdjWFVBYWJBMjY1Z2RnY1hVQ0EyN2dkZ2NYVVRBNzQ9 + MzgyNDUKAAA= + AGluaXRpYWxpemUAEAAAAA== + > + PRESETNAME initialize + FLOATPOS 0 0 0 0 + FXID {4E992428-8419-4358-883F-00C21662982E} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-415-takeover-target-range.RPP b/plugin-reaper-realearn/resources/test-projects/issue-415-takeover-target-range.RPP new file mode 100644 index 0000000..36e6c63 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-415-takeover-target-range.RPP @@ -0,0 +1,133 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAHwIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJGMWtKM0RXSiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiOGQ2ZjBmZDYtYzA2OC00YWI2LTk1MTUtNmNkYzYyYzc0NDY2IiwibmFtZSI6IjEiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI5LCJtYXhTb3VyY2VWYWx1ZSI6MC41NiwibWF4VGFyZ2V0SnVtcCI6MC4wMiwibWF4U3RlcFNpemUiOjAuMDUsInRh + a2VvdmVyTW9kZSI6InZhbHVlU2NhbGluZyIsIm1ha2VBYnNvbHV0ZUVuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {4627C00E-2825-9F4C-9815-C032EED70412} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-416-automation-last-touched.RPP b/plugin-reaper-realearn/resources/test-projects/issue-416-automation-last-touched.RPP new file mode 100644 index 0000000..c973358 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-416-automation-last-touched.RPP @@ -0,0 +1,318 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 8191 + + GLOBAL_AUTO 0 + TEMPO 110 4 4 + PLAYRATE 1 1 0.25 4 + SELECTION 10.36363636363636 12.54545454545455 + SELECTION2 10.36363636363636 12.54545454545455 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 1 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + MARKER 1 0 "" 0 0 1 R {42ED4156-9EB3-4507-940F-7B8DAF311FA5} + + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd5uMGBAw/eLPrBy6D5/ahw/AAAAAM5NAD9FIpE+AAAAAAAAAD8AAAAABoHVPgbX4z4AAAAABoPBPg== + AAAQAAAA + > + WET 0.909972 + FLOAT 567 595 345 461 + FXID {266494C0-D2A2-4F16-BEFB-A4A7D7F3545C} + + WAK 0 0 + > + + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAhwMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJpSG9Nd1l2NiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJmZWVkYmFja0RldmljZUlkIjoiMCIs + ImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI0NjFhY2Y1My00ODJhLTQwMWEtYTVjOC1iNmNkOWNl + N2U3M2EiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidW5pdCI6InBlcmNlbnQiLCJjb21tYW5kTmFtZSI6IjQwMDQ0IiwiaW52b2NhdGlv + blR5cGUiOjAsInRyYWNrR1VJRCI6IjI3QTNEMkE3LUEwQTgtNDY1RS1BQTBGLTYzNkY1M0QwQzc0OCIsImZ4QW5jaG9yIjoiaWQiLCJmeEdVSUQiOiIyNjY0OTRDMC1E + MkEyLTRGMTYtQkVGQi1BNEE3RDdGMzU0NUMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2UsImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjUxNTVjMTY0LTEyNTQtNDdlMS1hMTY1LTFjZjlmZGZhNDU2YSIs + Im5hbWUiOiIyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {86477398-7967-7C4F-90FE-302A5575B80F} + WAK 0 0 + > + > + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAA1VHDPvtYvz4AAAAAAACAPwjXozwAAAAACYnePdZqtT69N4Y+AACAP7ByqD4AAIA/AAAAPw== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {461BE01C-0B5B-1345-990B-620B0F4DB8C6} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-437-autostart.rpp b/plugin-reaper-realearn/resources/test-projects/issue-437-autostart.rpp new file mode 100644 index 0000000..1f039a1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-437-autostart.rpp @@ -0,0 +1,226 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.21260972541623 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAnyQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wIiwiaWQiOiJvMzY0WUpzRyIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVsdEdyb3Vw + Ijp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjY2OTM4OTVmLTIxZDgtNGI4Yy1hNzE2LWQ2Y2VmMjc2YzlmZCIsIm5hbWUi + OiIxIiwic291cmNlIjp7ImNhdGVnb3J5IjoicmVhcGVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJyZWFwZXJT + b3VyY2VUeXBlIjoicmVhbGVhcm4taW5zdGFuY2Utc3RhcnQifSwibW9kZSI6eyJtYXhUYXJnZXRWYWx1ZSI6MC4wLCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6 + eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwi + Y29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6IjBjMDI5MzYzLTcxYzctNDExNS1iOGUwLTcyNDVhYzFiNmQ0YiIsIm5hbWUiOiJFbmNvZGVyIDEvMSIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAx + LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifX0seyJpZCI6IjI3NTIxZWYwLTVj + ZjgtNDVlOS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUiOiJFbmNvZGVyIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6Mywi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgxZDMtZDYwNy00MzI5LTlkOTEtYTZiODg4YjBmYWFmIiwibmFtZSI6IkJ1dHRvbiAxLzIiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6IjU0 + YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNoYXJh + Y3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYjgyZDc3NTItZDIyOS00NDhmLWJkNDItYWY2MWI0NTllNDgxIiwibmFtZSI6IkJ1dHRvbiAxLzMiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0s + eyJpZCI6ImEzYzI3YzAyLWM1MmQtNDcxYy1iNzA2LTI3YjYyZTJhNWRiZSIsIm5hbWUiOiJFbmNvZGVyIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRv + biAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjozfX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQzNi05MTM0LTQ4OGE2ZmViMWZiZiIsIm5hbWUiOiJFbmNvZGVyIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNTBmM2Q1NmQtMGY2My00MDg2LTk1OTItYjNjZTkxM2RiMzgxIiwibmFt + ZSI6IkJ1dHRvbiAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJj + aGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiMDM5Zjc5OWItNTM3Zi00Mjc3LWFiYzEtMDNjNTA2ODI3 + MDgzIiwibmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjQ1YmJiZDVkLTM0ZjctNGFiOC1iNDk2LWVlYjljZjYwMmE3OCIsIm5hbWUiOiJFbmNvZGVyIDIvMyIsInNv + dXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTct + MjA1NTc2NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImQyODQ5ZWM3LWM4OTUtNDRlZS1iZmZhLTM0ZWVmNTBmOTc3YyIsIm5hbWUiOiJFbmNvZGVy + IDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiODAxOGMyNTktNmY3Ni00 + MTMzLTkwNDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1dHRvbiAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjEsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUi + OiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiMTYwMTNj + NDgtMmE0OS00NjcyLWJmM2EtOTVlYWUwNWVmM2E3IiwibmFtZSI6IkJ1dHRvbiAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIi + OjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjY0MGViMmE0LTczNWQtNGQ5MS1hZTViLTYzNjcxZGZhYjVk + NyIsIm5hbWUiOiJFbmNvZGVyIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7Imlk + IjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo5LCJj + aGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6ImM4NjVjNTAzLTI1ZmMtNGI4MS1iYmM4LTA3 + MzExMDVhMjY1NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxMH19LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzktOWI2NC1hOTA5ZTU1OTM1NGIiLCJuYW1lIjoiQnV0dG9uIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVk + LTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQxMzEtYmVkOS00YmE3LWE2MDQtNDM1Y2YwNDM2YzkxIiwibmFtZSI6IkJ1dHRvbiAzLzQiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoi + M2I1ZmMxZTgtNDJkYi00NTgxLTk0N2UtNWRmYTNkM2MwYmY0IiwibmFtZSI6IkVuY29kZXIgNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJmZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8x + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjEyfX0seyJpZCI6ImEzODQzNWMwLTNhYzQtNDE3NC1iMWIxLTc4MWVmZjc5ODhjNCIsIm5hbWUiOiJFbmNvZGVyIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5MTQ1YjhhNy0wMjFlLTQ2ZTktYmMyNC01ODhiNWI3MGUzY2QiLCJuYW1l + IjoiQnV0dG9uIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMx + MjU4NGEzIiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiNTVhYTIzYjAtYjgyMC00ZWJlLTliMTItOWZlNDA4NDJlZjI3IiwibmFtZSI6IkVuY29kZXIgNC80 + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJhOWUzLTI4OGItNGY0 + Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjI3YmYyMTUyLWU3ZjQtNDVlNS05Yjk4LWRjYWU4OTY1YjkxZiIsIm5hbWUi + OiJMZWZ0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxNn19LHsiaWQiOiJlZTlkZjU5MC02MWNiLTQ3NDYtYTMxMy03NzM1NmU1OGIzNWUiLCJuYW1lIjoiUmlnaHQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjoz + LCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN319LHsiaWQiOiJhYjA1NDg4MC0x + YzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fX0seyJpZCI6ImE1N2EyNzg3LTM5MTktNDZmYi04ZmRkLTE3ZmI4M2Y0YmFmMSIsIm5h + bWUiOiJSaWdodCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjE5fX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJtaWRpLWZpZ2h0ZXItdHdpc3RlciJ9 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {836CB72D-531F-4F08-9392-9AF627064AFA} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-438-enable-mappings.rpp b/plugin-reaper-realearn/resources/test-projects/issue-438-enable-mappings.rpp new file mode 100644 index 0000000..52f0f26 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-438-enable-mappings.rpp @@ -0,0 +1,386 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.37204449018951 -0.584 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAAjsAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wIiwiaWQiOiJDR2Y2T1BOcSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVsdEdyb3Vw + Ijp7fSwiZ3JvdXBzIjpbeyJpZCI6ImVjOTQ4NGRkLTBkM2EtNGY4OS05MDllLTQ1ZmZmMTQyYWQxOCIsIm5hbWUiOiJWb2x1bWVzIiwidGFncyI6WyJ2b2wiXX0seyJp + ZCI6IjQ4ZjYwYjZmLWI3YzAtNDk4Ni1iYmUzLTQwZGEyY2Y5NTU4NCIsIm5hbWUiOiJQYW4iLCJ0YWdzIjpbInBhbiJdfSx7ImlkIjoiODRmMzhjZjAtN2MxMC00ZTNm + LWFjOTEtMWJmY2IwNDVmNjRhIiwibmFtZSI6Ik1haW4iLCJ0YWdzIjpbIm1haW4iXX0seyJpZCI6IjEzNjA1ZDM1LTMwMGMtNGIzMC1hZTUzLTI0M2ExM2VjY2Y4OSIs + Im5hbWUiOiJab25lcyJ9LHsiaWQiOiI4OTFlNzJkMS02ZTU3LTQ4NWItODc1NC0yOTdmNmQ1MGQxMDgiLCJuYW1lIjoiR3JvdXAgY3ljbGUifV0sImRlZmF1bHRDb250 + cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI5ZWE0ZGQxMC1kNzc5LTRmY2ItOWVjMC03MDk3OGJhY2FjNTkiLCJuYW1lIjoiMSIsImdyb3VwSWQiOiJl + Yzk0ODRkZC0wZDNhLTRmODktOTA5ZS00NWZmZjE0MmFkMTgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6 + MiwidHJhY2tHVUlEIjoiQjQ1NUI4N0YtRDgzOS00Q0U1LUE5NUMtQTBDNURBNzQ0N0E0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJpc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZTZjYTY4MzYtNDY5NS00MzM5LWFlNGItNDQyNGUz + YTA2MmEyIiwibmFtZSI6IjEiLCJncm91cElkIjoiZWM5NDg0ZGQtMGQzYS00Zjg5LTkwOWUtNDVmZmYxNDJhZDE4Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6IjA1MDIwNDRBLUNDMUQtNEYyMC05QjI1LUQ1OUI1Mzk5NjU3MyIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiaXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + IjYzOWQ3MzVlLTFjN2QtNDllNy04MzY4LTZlNTgxNWJlNjlhNSIsIm5hbWUiOiIxIiwiZ3JvdXBJZCI6ImVjOTQ4NGRkLTBkM2EtNGY4OS05MDllLTQ1ZmZmMTQyYWQx + OCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjE0fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiI5NjQyNzlFQy03NzY3LTRGRUMt + OENBRS0yOUM3MzdBRDUyMEQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MH0sImlzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI1M2FhMzI3ZS02YWFiLTRhOWItODQyZi0zMTQwZDg1MTE4Y2EiLCJuYW1lIjoiMSIsImdyb3VwSWQiOiJlYzk0 + ODRkZC0wZDNhLTRmODktOTA5ZS00NWZmZjE0MmFkMTgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6Miwi + dHJhY2tHVUlEIjoiMDAzMEJDMTctREQxMS00QTIzLUE5MUUtQUJBNDVCRjQ4MkYwIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJpc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNmRmYWE1MTAtYjgwZi00MjlkLTkyNzEtY2NkYjhjMWNj + NWNkIiwibmFtZSI6IjUiLCJncm91cElkIjoiNDhmNjBiNmYtYjdjMC00OTg2LWJiZTMtNDBkYTJjZjk1NTg0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7Im1heFN0ZXBT + aXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrR1VJRCI6IkI0NTVCODdGLUQ4MzktNENFNS1BOTVDLUEwQzVEQTc0NDdBNCIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6ImNlMjQ5NTkyLWU3ZmMtNGE0MS1h + MDRkLTcxMDExMTdlMjc1NiIsIm5hbWUiOiI1IiwiZ3JvdXBJZCI6IjQ4ZjYwYjZmLWI3YzAtNDk4Ni1iYmUzLTQwZGEyY2Y5NTU4NCIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0dVSUQiOiIwNTAyMDQ0QS1DQzFELTRGMjAtOUIyNS1ENTlCNTM5OTY1NzMiLCJm + eEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI2NjFiZGU2 + ZS0yMzk3LTRkNWMtOGYxNy05NTMxNjNkMmI3OTIiLCJuYW1lIjoiNSIsImdyb3VwSWQiOiI0OGY2MGI2Zi1iN2MwLTQ5ODYtYmJlMy00MGRhMmNmOTU1ODQiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxNH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tHVUlEIjoiOTY0Mjc5RUMtNzc2Ny00RkVDLThDQUUtMjlD + NzM3QUQ1MjBEIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7 + ImlkIjoiMjE5ODUzNGQtY2Q5MC00YWVlLThhNTgtNzg3YmE1ZjA4MTE4IiwibmFtZSI6IjUiLCJncm91cElkIjoiNDhmNjBiNmYtYjdjMC00OTg2LWJiZTMtNDBkYTJj + Zjk1NTg0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTV9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrR1VJRCI6IjAwMzBCQzE3LUREMTEt + NEEyMy1BOTFFLUFCQTQ1QkY0ODJGMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0Fy + Z0luZGV4IjowfX0seyJpZCI6IjdlNWY4NTA0LTYzYTAtNDlkMS05YzBiLTk3YTdkODcwZDNmNiIsIm5hbWUiOiI5IiwiZ3JvdXBJZCI6Ijg0ZjM4Y2YwLTdjMTAtNGUz + Zi1hYzkxLTFiZmNiMDQ1ZjY0YSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0dVSUQiOiJt + YXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQi + OiI2ODY5NmY1MC1hZDg5LTRkZmUtOTkxNy0wN2ViNmNlNjIyMDYiLCJuYW1lIjoiOSIsImdyb3VwSWQiOiI4NGYzOGNmMC03YzEwLTRlM2YtYWM5MS0xYmZjYjA0NWY2 + NGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiMmY0YzE3MmYtYWI5MC00 + MTE2LWJhN2QtZDQyYjZkYzliM2ViIiwibmFtZSI6IlZvbCIsImdyb3VwSWQiOiIxMzYwNWQzNS0zMDBjLTRiMzAtYWU1My0yNDNhMTNlY2NmODkiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24ifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjozNiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsidm9sIl0sImV4Y2x1c2l2aXR5 + IjoxfX0seyJpZCI6ImVlMWI2MmJiLTMwOTItNDYwNS04YTg0LWVjNTUxNGE4NjQyZiIsIm5hbWUiOiJQYW4gKyBNYWluIiwiZ3JvdXBJZCI6IjEzNjA1ZDM1LTMwMGMt + NGIzMC1hZTUzLTI0M2ExM2VjY2Y4OSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJi + dXR0b25Vc2FnZSI6InByZXNzLW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjM2LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJwYW4iLCJtYWluIl0sImV4Y2x1c2l2aXR5IjoxfX0seyJpZCI6IjhkNTlkNmMzLWFkYWItNDIy + Yy1iYWEyLTBiNWMxMjI0M2I5ZSIsIm5hbWUiOiJOb25lIiwiZ3JvdXBJZCI6IjEzNjA1ZDM1LTMwMGMtNGIzMC1hZTUzLTI0M2ExM2VjY2Y4OSIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkifSwidGFyZ2V0Ijp7 + InR5cGUiOjM2LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiZXhj + bHVzaXZpdHkiOjF9fSx7ImlkIjoiN2M1NDE2NzEtN2EzNS00ZGZmLWFmNDktYzNjNzZlMDM5ZWRmIiwiZ3JvdXBJZCI6Ijg5MWU3MmQxLTZlNTctNDg1Yi04NzU0LTI5 + N2Y2ZDUwZDEwOCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjotMC4xOTk5OTk5OTk5OTk5OTk5NiwibWF4U3RlcFNpemUiOi0wLjE4MDAwMDAwMDAw + MDAwMDA1fSwidGFyZ2V0Ijp7InR5cGUiOjM3LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MCwiZ3JvdXBJZCI6IjEzNjA1ZDM1LTMwMGMtNGIzMC1hZTUzLTI0M2ExM2VjY2Y4OSJ9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIw + YzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFy + YWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxfX0seyJpZCI6ImU1NTQzYWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51 + bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVmMC01Y2Y4LTQ1ZTktOWM5MC00ZDQ5MDZjZjYzMDQiLCJu + YW1lIjoiRW5jb2RlciAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6ImRk + MWE4MWQzLWQ2MDctNDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFj + dGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2NTMtOWZiYi1lZjkwN2Ri + MDlkODciLCJuYW1lIjoiRW5jb2RlciAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0s + eyJpZCI6ImI4MmQ3NzUyLWQyMjktNDQ4Zi1iZDQyLWFmNjFiNDU5ZTQ4MSIsIm5hbWUiOiJCdXR0b24gMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6 + MiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMtYjcw + Ni0yN2I2MmUyYTVkYmUiLCJuYW1lIjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4IjozfX0seyJpZCI6IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEs + Im51bWJlciI6MywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiI4MjhkNDY1YS05YmQz + LTQ0MzYtOTEzNC00ODhhNmZlYjFmYmYiLCJuYW1lIjoiRW5jb2RlciAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjMsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNlIjp7ImNo + YW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI3MTkw + ZTJhNi1lMDdjLTQ5MjItOWJiNC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJjaGFyYWN0 + ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjAzOWY3OTliLTUzN2YtNDI3Ny1hYmMxLTAzYzUwNjgyNzA4MyIsIm5hbWUiOiJCdXR0b24gMi8yIiwic291 + cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsi + aWQiOiI0NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2 + LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJCdXR0b24g + Mi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6Nn19LHsiaWQiOiJkMjg0OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2MiLCJuYW1lIjoiRW5jb2RlciAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUi + OiJCdXR0b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6N319LHsiaWQiOiIyNWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYiLCJuYW1lIjoiRW5jb2RlciAzLzEiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjE2MDEzYzQ4LTJhNDktNDY3Mi1iZjNhLTk1ZWFlMDVlZjNh + NyIsIm5hbWUiOiJCdXR0b24gMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5jb2RlciAzLzIiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04Yjg5LWQ5 + NmI5NDUwMDAzMiIsIm5hbWUiOiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJuYW1lIjoiRW5jb2RlciAz + LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdhOTMwNWQtOTRhYi00 + ZmM5LTliNjQtYTkwOWU1NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVlZC00NjNiLTk3OWQtYjMwOTE2ZTNiYjNhIiwibmFt + ZSI6IkVuY29kZXIgMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjZi + OGFkMTMxLWJlZDktNGJhNy1hNjA0LTQzNWNmMDQzNmM5MSIsIm5hbWUiOiJCdXR0b24gMy80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTEsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEz + ZDNjMGJmNCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijox + Mn19LHsiaWQiOiIxYzA5MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1i + ZXIiOjEyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiJhMzg0MzVjMC0zYWM0LTQx + NzQtYjFiMS03ODFlZmY3OTg4YzQiLCJuYW1lIjoiRW5jb2RlciA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTQw + NjAzOGUtM2M0NC00MzJkLThlY2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJh + Y3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjIzMmQ5ODlhLWUwZDctNDdmOS05NTg3LTY2OGFjMTI1ODRhMyIsIm5hbWUiOiJCdXR0b24gNC8zIiwi + c291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0 + fX0seyJpZCI6IjU1YWEyM2IwLWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjE1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMwMDlkNGQiLCJuYW1lIjoi + QnV0dG9uIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoxNX19LHsiaWQiOiIyN2JmMjE1Mi1lN2Y0LTQ1ZTUtOWI5OC1kY2FlODk2NWI5MWYiLCJuYW1lIjoiTGVmdCAxIiwic291cmNlIjp7ImNoYW5uZWwi + OjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5ZGY1OTAt + NjFjYi00NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9fSx7ImlkIjoiYWIwNTQ4ODAtMWM0My00ZjRhLWI0MTgtYzNlYTRkMzFiNTEwIiwi + bmFtZSI6IkxlZnQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoxOH19LHsiaWQiOiJhNTdhMjc4Ny0zOTE5LTQ2ZmItOGZkZC0xN2ZiODNmNGJhZjEiLCJuYW1lIjoiUmlnaHQgMyIsInNvdXJjZSI6eyJjaGFu + bmVsIjozLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOX19XSwiYWN0aXZlQ29u + dHJvbGxlcklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIifQ== + AAAQAAAA + > + FLOAT 1393 319 1436 1514 + FXID {B2BA5493-4B8C-4CFD-9465-E51302303640} + WAK 0 0 + > + > + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-439-superior-mappings.rpp b/plugin-reaper-realearn/resources/test-projects/issue-439-superior-mappings.rpp new file mode 100644 index 0000000..667efff --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-439-superior-mappings.rpp @@ -0,0 +1,236 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.13771568931343 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAMSgAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wIiwiaWQiOiI5NmhfWmNKWCIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVsdEdyb3Vw + Ijp7fSwiZ3JvdXBzIjpbeyJpZCI6IjhiMzM3MjdhLWU2MzQtNGQxNy05OTYxLWVkYmNkM2VkYjUzZSIsIm5hbWUiOiJTdXBlciBsb25nIGdyb3VwIG5hbWUgMSwgMiwg + MywgNCwgNSJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjQyMTVjZjNjLWExNWEtNGFhZC04MzgxLTljYjU2Y2RiMTk5ZSIs + Im5hbWUiOiJTaGlmdCAxLCAyLCAzLCBzdXBlciBsb25nIG1hcHBpbmcgbmFtZSIsImdyb3VwSWQiOiI4YjMzNzI3YS1lNjM0LTRkMTctOTk2MS1lZGJjZDNlZGI1M2Ui + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MzYsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbImIiXX19LHsi + aWQiOiJiMWQ3MDY4NC02MjViLTRjY2UtYTg4Yi04YjBmNWRlNTdmMjUiLCJuYW1lIjoiVHJhY2sgdm9sIiwidGFncyI6WyJhIl0sImdyb3VwSWQiOiI4YjMzNzI3YS1l + NjM0LTRkMTctOTk2MS1lZGJjZDNlZGI1M2UiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI3ZjRlY2QwNS1iMDEyLTRmOTMtYTk2ZS1mOTUyMDgwZmUzN2Qi + LCJuYW1lIjoiTWFpbiB2b2wiLCJ0YWdzIjpbImIiXSwiZ3JvdXBJZCI6IjhiMzM3MjdhLWU2MzQtNGQxNy05OTYxLWVkYmNkM2VkYjUzZSIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiaXNFbmFibGVkIjpmYWxzZX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1 + LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0 + M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8y + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjkt + OWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVu + Y29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1k + MjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwi + bmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIx + Yzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZm + ZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19 + LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTli + YjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRm + Ny00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4 + NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFj + dGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7 + ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9u + IDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1l + IjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5 + MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTct + YTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoi + RW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1 + ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVy + IjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4 + OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0s + eyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6 + MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04 + ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNi + MC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7 + ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNo + YXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3 + MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9 + fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjox + MywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGkt + ZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {858AF73A-F811-477F-B2C3-FA984F545401} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-448-daw-control.rpp b/plugin-reaper-realearn/resources/test-projects/issue-448-daw-control.rpp new file mode 100644 index 0000000..683a5da --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-448-daw-control.rpp @@ -0,0 +1,4742 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 1 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.094228982387 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 1 + + + MARKER 2 44.88668749999847 "" 0 0 1 R {0F0BA177-259C-4D73-9522-EB0AE16C7D7D} + MARKER 1 53.02766666666647 "" 0 0 1 R {76B3246B-D26E-4AFA-88A9-E70CEC60B873} + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAt1kBAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xNCIsImlkIjoiV3VxNUllT3ciLCJjb250cm9sRGV2aWNlSWQiOiIxMSIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMiIsImRlZmF1 + bHRHcm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJzd2l0Y2hpbmciLCJuYW1lIjoiU3dpdGNoaW5nIn0seyJpZCI6InZvbHVtZSIsIm5hbWUiOiJWb2x1bWUifSx7Imlk + IjoicGFuIiwibmFtZSI6IlBhbiJ9LHsiaWQiOiJwYW4tcmVzZXQiLCJuYW1lIjoiUGFuIHJlc2V0In0seyJpZCI6InNlbGVjdGlvbiIsIm5hbWUiOiJTZWxlY3Rpb24i + fSx7ImlkIjoibXV0ZSIsIm5hbWUiOiJNdXRlIn0seyJpZCI6InNvbG8iLCJuYW1lIjoiU29sbyJ9LHsiaWQiOiJhcm0iLCJuYW1lIjoiQXJtIn0seyJpZCI6InRvdWNo + IiwibmFtZSI6IlRvdWNoIn0seyJpZCI6InRyYW5zcG9ydCIsIm5hbWUiOiJUcmFuc3BvcnQifSx7ImlkIjoiem9vbSIsIm5hbWUiOiJab29tIiwiYWN0aXZhdGlvblR5 + cGUiOiJtb2RpZmllcnMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsicGFyYW1JbmRleCI6MiwiaXNPbiI6dHJ1ZX19LHsiaWQiOiJtYXN0ZXIiLCJuYW1lIjoiTWFzdGVy + In0seyJpZCI6InNjcm9sbCIsIm5hbWUiOiJTY3JvbGwiLCJhY3RpdmF0aW9uVHlwZSI6Im1vZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4 + IjoyLCJpc09uIjpmYWxzZX19LHsiaWQiOiJsY2QiLCJuYW1lIjoiTENEIn0seyJpZCI6Im1ldGVyIiwibmFtZSI6Ik1ldGVyIn0seyJpZCI6InRpbWUtbW9kZXMiLCJu + YW1lIjoiVGltZSBtb2RlcyJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiZmFkZXIiLCJuYW1lIjoiRmFkZXIi + fSx7ImlkIjoidi1wb3QiLCJuYW1lIjoiVi1Qb3QifSx7ImlkIjoidi1zZWxlY3QiLCJuYW1lIjoiVi1TZWxlY3QifSx7ImlkIjoiZmFkZXItdG91Y2giLCJuYW1lIjoi + RmFkZXIgVG91Y2gifSx7ImlkIjoic2VsZWN0IiwibmFtZSI6IlNlbGVjdCJ9LHsiaWQiOiJtdXRlIiwibmFtZSI6Ik11dGUifSx7ImlkIjoic29sbyIsIm5hbWUiOiJT + b2xvIn0seyJpZCI6InJlY29yZC1yZWFkeSIsIm5hbWUiOiJSZWNvcmQtcmVhZHkifSx7ImlkIjoidi1wb3QtbGVkcyIsIm5hbWUiOiJWLVBvdCBMRURzIn0seyJpZCI6 + ImxjZCIsIm5hbWUiOiJMQ0QifSx7ImlkIjoibWV0ZXIiLCJuYW1lIjoiTWV0ZXIifV0sIm1hcHBpbmdzIjpbeyJpZCI6IjVjZmQyZmYwLTg1Y2UtNGU3MC05OGU4LWVi + NTNlNWU5NGJiMSIsIm5hbWUiOiJCYW5rIC0iLCJncm91cElkIjoic3dpdGNoaW5nIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLWxlZnQifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wOCwibWF4U3RlcFNp + emUiOjAuMDgsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MTQsImZ4QW5jaG9yIjoiaWQiLCJzY3Jv + bGxBcnJhbmdlVmlldyI6dHJ1ZSwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiIwN2IwMjAyZi1iOGI4LTQ4ZTQtYTUzMC1mMjI4NDUwODY0ZjAiLCJuYW1lIjoiQmFuayAtIGZhc3RlciIsImdyb3VwSWQiOiJzd2l0Y2hpbmciLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCJ9LCJtb2RlIjp7InR5 + cGUiOjEsIm1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVwU2l6ZSI6MC4wOCwibWluUHJlc3NNaWxsaXMiOjMwMCwibWF4UHJlc3NNaWxsaXMiOjMwMCwidHVyYm9SYXRl + IjoxMDAsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFu + Y2hvciI6ImlkIiwic2Nyb2xsQXJyYW5nZVZpZXciOnRydWUsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiMGJhYWI5MWYtMmM0ZS00M2FlLThjYWMtZGNhNzI3OTYzYjQ2IiwibmFtZSI6IkJhbmsgKyIsImdyb3VwSWQiOiJzd2l0Y2hpbmciLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQifSwi + bW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wOCwibWF4U3RlcFNpemUiOjAuMDgsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MTQs + ImZ4QW5jaG9yIjoiaWQiLCJzY3JvbGxBcnJhbmdlVmlldyI6dHJ1ZSwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIwYTdlMTIyYy0zZWRhLTQzNTUtOTA2YS1hMDU3ZWVmYTUxNWIiLCJuYW1lIjoiQmFuayArIGZhc3RlciIsImdyb3VwSWQiOiJz + d2l0Y2hpbmciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJh + bmstcmlnaHQifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wOCwibWF4U3RlcFNpemUiOjAuMDgsIm1pblByZXNzTWlsbGlzIjozMDAsIm1heFByZXNz + TWlsbGlzIjozMDAsInR1cmJvUmF0ZSI6MTAwLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjoxNCwiZnhBbmNo + b3IiOiJpZCIsInNjcm9sbEFycmFuZ2VWaWV3Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNmNDU2ODlhLTU1MzctNDY1ZC1hZDQ0LTQ2MGRiZWJmNDgwMiIsIm5hbWUiOiJUcmFjayAtIiwiZ3JvdXBJZCI6InN3aXRjaGluZyIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCJ9LCJtb2Rl + Ijp7InR5cGUiOjEsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MTQsImZ4QW5jaG9yIjoiaWQiLCJz + Y3JvbGxBcnJhbmdlVmlldyI6dHJ1ZSwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiI3NjQ2NWI1Zi1jOWIzLTRhYTYtYmE3Ny1iODkxMDIxY2YzOGUiLCJuYW1lIjoiVHJhY2sgLSBmYXN0ZXIiLCJncm91cElkIjoic3dpdGNoaW5nIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0In0sIm1vZGUiOnsi + dHlwZSI6MSwibWluUHJlc3NNaWxsaXMiOjMwMCwibWF4UHJlc3NNaWxsaXMiOjMwMCwidHVyYm9SYXRlIjo2MCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJh + Y2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MTQsImZ4QW5jaG9yIjoiaWQiLCJzY3JvbGxBcnJhbmdlVmlldyI6dHJ1ZSwi + c2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJkNDBmZmE0NC1hNDQ2LTQ0OTkt + YjlkNi03ODdjNWZmOGUxODgiLCJuYW1lIjoiVHJhY2sgKyIsImdyb3VwSWQiOiJzd2l0Y2hpbmciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJ0eXBlIjoxNCwiZnhBbmNob3IiOiJpZCIsInNjcm9sbEFycmFuZ2VWaWV3Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ijk3ZGI5OTFjLWExNWItNGMyNy1hZWIyLTA0YjJhZTMyNDJjMiIsIm5hbWUiOiJUcmFjayAr + IGZhc3RlciIsImdyb3VwSWQiOiJzd2l0Y2hpbmciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWluUHJlc3NNaWxsaXMiOjMwMCwibWF4UHJlc3NNaWxsaXMiOjMwMCwidHVyYm9S + YXRlIjo2MCwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MTQsImZ4QW5jaG9yIjoiaWQiLCJzY3JvbGxBcnJh + bmdlVmlldyI6dHJ1ZSwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhZmEz + NTQ1OC0zMTYwLTRmNDktYmI5MC0yNzlhYWU1MTc3OWYiLCJuYW1lIjoiUGxheS9wYXVzZSIsImdyb3VwSWQiOiJ0cmFuc3BvcnQiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjE2LCJmeEFuY2hvciI6ImlkIiwidHJhbnNwb3J0QWN0aW9uIjoicGxheVBhdXNlIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiI2N2IzYTUxNi0yNDZmLTQzYTYtYjg1MC0wNTBlYjQxNTUyMGEiLCJuYW1lIjoiU3RvcCIsImdyb3VwSWQiOiJ0cmFuc3BvcnQiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AifSwibW9kZSI6eyJ0 + eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4ifSwidGFyZ2V0Ijp7InR5cGUiOjE2LCJmeEFuY2hvciI6ImlkIiwidHJh + bnNwb3J0QWN0aW9uIjoic3RvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjE0ZWJkNjMtMDEyNy00OTZkLTlhY2ItMjgxYjVhN2JiM2RiIiwibmFt + ZSI6IlJlcGVhdCIsImdyb3VwSWQiOiJ0cmFuc3BvcnQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImN5Y2xlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbCwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIn0s + InRhcmdldCI6eyJ0eXBlIjoxNiwiZnhBbmNob3IiOiJpZCIsInRyYW5zcG9ydEFjdGlvbiI6InJlcGVhdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + MDA2MjY0OTgtODBhNi00MWQ0LWJjOWYtZjI3MzA5OGUzZGNmIiwibmFtZSI6IlJlY29yZCIsImdyb3VwSWQiOiJ0cmFuc3BvcnQiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRi + YWNrQ29sb3IiOm51bGwsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiJ9LCJ0YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ0cmFuc3BvcnRBY3Rpb24i + OiJyZWNvcmQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjFmODI3YjdiLWRiMDQtNDc4YS1hMDViLTM2ZTg5OGY2YjI0NSIsIm5hbWUiOiJTY3J1YiBj + b2Fyc2UiLCJncm91cElkIjoidHJhbnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTkyIiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJhY3RpdmF0aW9uVHlwZSI6Im1vZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0 + aW9uMSI6eyJwYXJhbUluZGV4IjoxLCJpc09uIjpmYWxzZX19LHsiaWQiOiJhMjU5MDYyZS1kZmRlLTRlNmMtOGUwYS0wMTRhMDc4ZmMxYmUiLCJuYW1lIjoiU2NydWIg + ZmluZSIsImdyb3VwSWQiOiJ0cmFuc3BvcnQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImpvZyJ9LCJtb2RlIjp7 + Im1pblN0ZXBTaXplIjowLjAsIm1heFN0ZXBTaXplIjowLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI5NzQi + LCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsImFjdGl2YXRp + b25UeXBlIjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjEsImlzT24iOnRydWV9fSx7ImlkIjoiMWU1YzYwNGYtOTA0MC00N2Mw + LWFiMzctYTA1MzgzZTdiZTAwIiwibmFtZSI6IlNlZWsgY29hcnNlL2ZpbmUiLCJncm91cElkIjoic3dpdGNoaW5nIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiJ9LCJtb2RlIjp7InR5cGUiOjIsIm1heFByZXNzTWlsbGlz + IjoyNTAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwicGFyYW1JbmRleCI6MSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJlZTgxYzQwNS1kMzU1LTQ1ZTAtODBiNC05MTM2MzMyOTUxOTMiLCJuYW1lIjoiWm9vbSBvbi9vZmYiLCJncm91cElkIjoic3dpdGNoaW5nIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIn0sIm1vZGUiOnsidHlw + ZSI6MiwibWF4UHJlc3NNaWxsaXMiOjI1MCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJwYXJhbUluZGV4IjoyLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImYxMzY3OGIxLWRhODEtNDZhNS1iMzE4LTg2MWE4MTNkNzE4MyIsIm5hbWUiOiJQcmV2aW91cyIsImdyb3VwSWQiOiJ0 + cmFuc3BvcnQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJl + d2luZCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE3MiIsImludm9jYXRpb25UeXBlIjow + LCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InByZXZpb3VzLWxlZCIsIm5hbWUi + OiJQcmV2aW91cyBMRUQiLCJncm91cElkIjoidHJhbnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQifSwibW9kZSI6eyJtYXhUYXJnZXRWYWx1ZSI6MC4wMDAwMSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJ0eXBlIjoyMywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiN2YwZDJjYmItMDM0Ni00MjI5LTg5ZGQtZGI1OGNhYzNhNDYwIiwibmFtZSI6Ik5leHQiLCJn + cm91cElkIjoidHJhbnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJmYXN0LWZ3ZCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE3MyIsImludm9j + YXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im5leHQtbGVk + IiwibmFtZSI6Ik5leHQgTEVEIiwiZ3JvdXBJZCI6InRyYW5zcG9ydCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZmFzdC1md2QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC45OTk5OSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1 + ZSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJz + ZWVrUGxheSI6dHJ1ZSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOGIwY2U1ZGItYTM3MS00YjRjLTlmZTgt + Y2EwODZiYzcwNmJmIiwibmFtZSI6Ilpvb20gb3V0IGhvcml6b250YWxseSIsImdyb3VwSWQiOiJ6b29tIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCJ9LCJtb2RlIjp7InR5cGUiOjEsInR1cmJvUmF0ZSI6MTAw + LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFt + ZSI6Ijk5OCIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IjliNmZlMGI4LWQ1ZjMtNDM4Yi05ODMwLTZiYjc4MjEyMWMyZCIsIm5hbWUiOiJab29tIGluIGhvcml6b250YWxseSIsImdyb3VwSWQiOiJ6b29tIiwic291 + cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQifSwi + bW9kZSI6eyJ0eXBlIjoxLCJ0dXJib1JhdGUiOjEwMCwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29t + bWFuZE5hbWUiOiI5OTgiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJjM2MzNDY4MC0wMzk2LTRiMjQtODcxNC1lMjEwMDYyZjQ2Y2QiLCJuYW1lIjoiWm9vbSBvdXQgdmVydGljYWxseSIsImdyb3VwSWQiOiJ6b29t + Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItZG93 + biJ9LCJtb2RlIjp7InR1cmJvUmF0ZSI6MTAwLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5k + TmFtZSI6IjQwMTEyIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiZTkzZTg5ODEtODEyNy00NDZmLWI1ZDktMzRiYjU3NmZiYzJiIiwibmFtZSI6Ilpvb20gaW4gdmVydGljYWxseSIsImdyb3VwSWQiOiJ6b29tIiwi + c291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAifSwi + bW9kZSI6eyJ0dXJib1JhdGUiOjEwMCwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUi + OiI0MDExMSIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IjhiNmQ0Y2Y1LWQ0ZjItNDcwMS1hZjcxLTkwZDgzOWRhMjMxOCIsIm5hbWUiOiJTY3JvbGwgZG93biIsImdyb3VwSWQiOiJzY3JvbGwiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIn0sIm1vZGUiOnsi + dHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgsIm1heFN0ZXBTaXplIjowLjA4LCJ0dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tD + b2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI5ODkiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNo + b3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiNjMzYzJkMS1jMjgzLTQwMjEtYTFmYy03NmI2 + YWJkNDA3YjkiLCJuYW1lIjoiU2Nyb2xsIHVwIiwiZ3JvdXBJZCI6InNjcm9sbCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXVwIn0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgsIm1heFN0ZXBTaXpl + IjowLjA4LCJ0dXJib1JhdGUiOjEwMCwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUi + OiI5ODkiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiJiOWZhM2RiNi04YmVkLTRjOGEtODJmYS01ODBkM2QwZGE5MmMiLCJuYW1lIjoiU2Nyb2xsIGxlZnQiLCJncm91cElkIjoic2Nyb2xsIiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCJ9LCJtb2RlIjp7InR5 + cGUiOjEsIm1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVwU2l6ZSI6MC4wOCwidHVyYm9SYXRlIjoxMDAsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29s + b3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxNDAiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNo + b3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIzNDQ0MDVkNS1iNTBkLTQ3ZDQtYTIyZS1hYTk5 + YTc0NTRhMjUiLCJuYW1lIjoiU2Nyb2xsIHJpZ2h0IiwiZ3JvdXBJZCI6InNjcm9sbCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgsIm1heFN0 + ZXBTaXplIjowLjA4LCJ0dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0 + YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE0MSIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjI2MTA2N2VmLWMyZjAtNDFkZS1iMTBmLTQxOGNjNzE4Y2YxYyIsIm5hbWUiOiJNYXN0ZXIgdG91Y2gi + LCJncm91cElkIjoibWFzdGVyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJtYWluL2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tHVUlEIjoibWFzdGVy + IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiMmY0NWNkZjYtYmFjOS00YTk3LThhM2UtYzI5NmQyY2M3ZTBmIiwibmFtZSI6 + Ik1hc3RlciB2b2x1bWUiLCJncm91cElkIjoibWFzdGVyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYWluL2Zh + ZGVyIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI5NzBlMDRjOS1lMjYyLTQ3OGUtODVjZS05ZTg0ODc1ZmI1ZjUiLCJuYW1lIjoiTWFya2VyIiwiZ3JvdXBJZCI6Im1h + c3RlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2Vy + In0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbCwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6 + eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQwMTU3IiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYzQ5MjM4YjktMTg5ZS00MmQzLTk4NGEtOTU1ZmU2M2M0NmU4IiwibmFtZSI6Ikdsb2JhbCByZWFkIiwiZ3JvdXBJ + ZCI6Im1hc3RlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + cmVhZCJ9LCJtb2RlIjp7InR5cGUiOjIsIm1pblRhcmdldFZhbHVlIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MjYsImZ4QW5jaG9y + IjoiaWQiLCJ0cmFja0F1dG9tYXRpb25Nb2RlIjoxLCJhdXRvbWF0aW9uTW9kZU92ZXJyaWRlVHlwZSI6Im92ZXJyaWRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJiODA1ZjYyOC0xNmUxLTQyY2EtYWMzNi0zYmI5NTEzMzgxNWYiLCJuYW1lIjoiR2xvYmFsIHdyaXRlIiwiZ3JvdXBJZCI6Im1hc3RlciIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoid3JpdGUifSwibW9kZSI6eyJ0eXBl + IjoyLCJtaW5UYXJnZXRWYWx1ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjI2LCJmeEFuY2hvciI6ImlkIiwidHJhY2tBdXRvbWF0 + aW9uTW9kZSI6MiwiYXV0b21hdGlvbk1vZGVPdmVycmlkZVR5cGUiOiJvdmVycmlkZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiMTNkNzlkNDItNGQ1 + OS00OTMwLWE5NzYtYjQzNzRlMGE2OWI2IiwibmFtZSI6IkNsaWNrIiwiZ3JvdXBJZCI6Im1hc3RlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2xpY2sifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAzNjQiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJhbnktc29s + byIsIm5hbWUiOiJBbnkgc29sbyIsImdyb3VwSWQiOiJtYXN0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNvbG8ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQzLCJmeEFuY2hvciI6 + ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI3YjkwYzEzNi1mODljLTQ3N2UtYjgxMi01MjViNGE3ZGE1ZWQiLCJuYW1lIjoiQXNzaWdubWVudCIs + Imdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC9hc3NpZ25tZW50In0sIm1vZGUiOnsi + ZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5udW1lcmljX3ZhbHVlIH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwi + dGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiN0dG + TlFmWnA5dXFvdmhWNXpXdHZoIiwibmFtZSI6IlRpbWVjb2RlIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUt + c2V2ZW4iLCJkaXNwbGF5SWQiOjJ9LCJtb2RlIjp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQucG9zaXRpb24ucHJvamVjdF9kZWZhdWx0Lm1j + dSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjoyMywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwiZmVlZGJhY2tSZXNvbHV0aW9uIjoiaGlnaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InRpbWUtbW9kZS1tZWFzdXJlcy1iZWF0cyIsIm5hbWUiOiJNZWFzdXJlcy5CZWF0cyIsImdyb3VwSWQiOiJ0aW1lLW1vZGVz + Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzbXB0ZS1iZWF0 + cyJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDQxMSIs + Imludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidGlt + ZS1tb2RlLXNlY29uZHMiLCJuYW1lIjoiU2Vjb25kcyIsImdyb3VwSWQiOiJ0aW1lLW1vZGVzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzbXB0ZS1iZWF0cyJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDQxMiIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidGltZS1tb2RlLXNhbXBsZXMiLCJuYW1lIjoiU2FtcGxlcyIsImdyb3VwSWQiOiJ0aW1lLW1vZGVzIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzbXB0ZS1iZWF0cyJ9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDQxMyIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6 + ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidGltZS1tb2RlLWhtc2YiLCJuYW1lIjoiSG91cnM6TWlu + dXRlczpTZWNvbmRzOkZyYW1lcyIsImdyb3VwSWQiOiJ0aW1lLW1vZGVzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzbXB0ZS1iZWF0cyJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MCwi + Y29tbWFuZE5hbWUiOiI0MDQxNCIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoidGltZS1tb2RlLWFic29sdXRlLWZyYW1lcyIsIm5hbWUiOiJBYnNvbHV0ZSBGcmFtZXMiLCJncm91cElkIjoidGltZS1tb2RlcyIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMifSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDE5NzIiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNo + b3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InRpbWUtbW9kZS1taW51dGVzLXNlY29uZHMiLCJu + YW1lIjoiTWludXRlczpTZWNvbmRzIiwiZ3JvdXBJZCI6InRpbWUtbW9kZXMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjow + LCJjb21tYW5kTmFtZSI6IjQwNDEwIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjeWNsZS10aW1lLW1vZGVzIiwibmFtZSI6IkN5Y2xlIHRpbWUgbW9kZXMiLCJncm91cElkIjoibWFzdGVyIiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzbXB0ZS1iZWF0cyJ9LCJtb2RlIjp7InR5 + cGUiOjEsImZlZWRiYWNrQ29sb3IiOm51bGwsInJvdGF0ZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjozNywiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsImdyb3VwSWQiOiJ0aW1lLW1vZGVzIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92b2wiLCJuYW1lIjoiVHIxIFZv + bCIsImdyb3VwSWQiOiJ2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciJ9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAwIiwiZnhBbmNob3Ii + OiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL3BhbiIsIm5hbWUiOiJUcjEgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4w + LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMCIsImZ4QW5jaG9y + IjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9wYW4vZmVlZGJhY2siLCJuYW1lIjoiVHIxIFBhbiBGZWVkYmFjayIsImdyb3VwSWQiOiJw + YW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9ib29zdC1jdXQifSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvcGFuL3Jlc2V0IiwibmFtZSI6IlRyMSBQYW4gUmVzZXQiLCJn + cm91cElkIjoicGFuLXJlc2V0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDEvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3NlbGVjdGlvbiIsIm5hbWUiOiJUcjEgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVj + dGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Nl + bGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJh + Y2tfaW5kZXggKyAwIiwiZnhBbmNob3IiOiJpZCIsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9tdXRlIiwibmFt + ZSI6IlRyMSBNdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4 + cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDAiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEvc29sbyIs + Im5hbWUiOiJUcjEgU29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJh + Y2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL2Fy + bSIsIm5hbWUiOiJUcjEgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlw + ZSI6NSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7Imlk + IjoiY2gxL3RvdWNoIiwibmFtZSI6IlRyMSBUb3VjaCIsImdyb3VwSWQiOiJ0b3VjaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY2gxL25hbWUiLCJuYW1lIjoiVHIxIE5hbWUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUxIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC50cmFjay5uYW1lIH19IiwiZmVlZGJh + Y2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDAi + LCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3Bhbi9sY2QiLCJuYW1lIjoi + VHIxIFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUy + In0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBl + IjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvcGVhayIsIm5hbWUiOiJUcjEgUGVha3MiLCJncm91cElkIjoibWV0ZXIiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tZXRlci9wZWFrIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92b2wiLCJuYW1lIjoiVHIyIFZvbCIsImdyb3VwSWQiOiJ2b2x1bWUiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2gyL3BhbiIsIm5hbWUiOiJUcjIgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6ImNoMi9wYW4vZmVlZGJhY2siLCJuYW1lIjoiVHIyIFBhbiBGZWVkYmFjayIsImdyb3VwSWQiOiJwYW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9ib29zdC1jdXQifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5 + cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvcGFuL3Jlc2V0IiwibmFtZSI6IlRyMiBQYW4gUmVzZXQiLCJncm91cElkIjoicGFuLXJlc2V0Iiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxlY3QifSwibW9k + ZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhw + cmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2gyL3NlbGVjdGlvbiIsIm5hbWUiOiJUcjIgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAxIiwiZnhBbmNob3IiOiJp + ZCIsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi9tdXRlIiwibmFtZSI6IlRyMiBNdXRlIiwiZ3JvdXBJZCI6Im11 + dGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRl + In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19p + bmRleCArIDEiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDIvc29sbyIsIm5hbWUiOiJUcjIgU29sbyIsImdyb3VwSWQi + OiJzb2xvIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIv + c29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJh + Y2tfaW5kZXggKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL2FybSIsIm5hbWUiOiJUcjIgQXJtIiwiZ3JvdXBJ + ZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gy + L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoic2Vs + ZWN0ZWRfdHJhY2tfaW5kZXggKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3RvdWNoIiwibmFtZSI6IlRyMiBU + b3VjaCIsImdyb3VwSWQiOiJ0b3VjaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9u + Ijoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL25hbWUiLCJuYW1lIjoi + VHIyIE5hbWUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUxIn0s + Im1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC50cmFjay5uYW1lIH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBl + IjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDEiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Bhbi9sY2QiLCJuYW1lIjoiVHIyIFBhbiBMQ0QiLCJncm91cElkIjoibGNk + Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFu + c2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRy + YWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvcGVhayIsIm5hbWUiOiJUcjIgUGVha3MiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tZXRlci9wZWFrIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJh + Y2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoMy92b2wiLCJuYW1lIjoiVHIzIFZvbCIsImdyb3VwSWQiOiJ2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNz + aW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAyIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3BhbiIsIm5hbWUi + OiJUcjMgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90In0sIm1v + ZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVz + c2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9wYW4vZmVlZGJh + Y2siLCJuYW1lIjoiVHIzIFBhbiBGZWVkYmFjayIsImdyb3VwSWQiOiJwYW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMy92LXBvdC9ib29zdC1jdXQifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNl + bGVjdGVkX3RyYWNrX2luZGV4ICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDMvcGFuL3Jlc2V0IiwibmFtZSI6IlRyMyBQYW4gUmVzZXQiLCJncm91cElkIjoicGFuLXJlc2V0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJt + YXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2lu + ZGV4ICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3NlbGVjdGlv + biIsIm5hbWUiOiJUcjMgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAyIiwiZnhBbmNob3IiOiJpZCIsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9tdXRlIiwibmFtZSI6IlRyMyBNdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDIiLCJmeEFuY2hvciI6ImlkIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvc29sbyIsIm5hbWUiOiJUcjMgU29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAyIiwiZnhBbmNob3IiOiJp + ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL2FybSIsIm5hbWUiOiJUcjMgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5 + cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAyIiwiZnhB + bmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3RvdWNoIiwibmFtZSI6IlRyMyBUb3VjaCIsImdyb3VwSWQiOiJ0b3VjaCIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNo + In0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAy + IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL25hbWUiLCJuYW1lIjoiVHIzIE5hbWUiLCJncm91cElkIjoibGNkIiwi + c291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUxIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zv + cm1hdGlvbiI6Int7IHRhcmdldC50cmFjay5uYW1lIH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0 + cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDIiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3Bhbi9sY2QiLCJuYW1lIjoiVHIzIFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4u + bWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3Ry + YWNrX2luZGV4ICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvcGVh + ayIsIm5hbWUiOiJUcjMgUGVha3MiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + My9tZXRlci9wZWFrIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJh + Y2tfaW5kZXggKyAyIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92b2wi + LCJuYW1lIjoiVHI0IFZvbCIsImdyb3VwSWQiOiJ2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9m + YWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXgg + KyAzIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3BhbiIsIm5hbWUiOiJUcjQgUGFuIiwiZ3JvdXBJZCI6InBhbiIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJt + YXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4 + ICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNC9wYW4vZmVlZGJhY2siLCJuYW1lIjoiVHI0IFBhbiBGZWVkYmFj + ayIsImdyb3VwSWQiOiJwYW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9ib29zdC1jdXQifSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMyIsImZ4 + QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvcGFuL3Jlc2V0IiwibmFtZSI6IlRy + NCBQYW4gUmVzZXQiLCJncm91cElkIjoicGFuLXJlc2V0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjQgU2VsZWN0aW9uIiwi + Z3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g0L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9u + Ijoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAzIiwiZnhBbmNob3IiOiJpZCIsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + ImNoNC9tdXRlIiwibmFtZSI6IlRyNCBNdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0 + eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDMiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjaDQvc29sbyIsIm5hbWUiOiJUcjQgU29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAzIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY2g0L2FybSIsIm5hbWUiOiJUcjQgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAzIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3RvdWNoIiwibmFtZSI6IlRyNCBUb3VjaCIsImdyb3VwSWQiOiJ0b3VjaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAzIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L25hbWUiLCJuYW1lIjoiVHI0IE5hbWUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUxIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC50cmFjay5u + YW1lIH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90 + cmFja19pbmRleCArIDMiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Bh + bi9sY2QiLCJuYW1lIjoiVHI0IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDQvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVs + bCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgMyIsImZ4QW5jaG9yIjoi + aWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvcGVhayIsIm5hbWUiOiJUcjQgUGVha3MiLCJncm91 + cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tZXRlci9wZWFrIn0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyAzIiwiZnhBbmNob3IiOiJp + ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92b2wiLCJuYW1lIjoiVHI1IFZvbCIsImdyb3VwSWQi + OiJ2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3BhbiIsIm5hbWUiOiJUcjUgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9wYW4vZmVlZGJhY2siLCJuYW1lIjoiVHI1IFBhbiBGZWVkYmFjayIsImdyb3VwSWQiOiJwYW4iLCJzb3VyY2Ui + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9ib29zdC1jdXQifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvcGFuL3Jlc2V0IiwibmFtZSI6IlRyNSBQYW4gUmVzZXQiLCJncm91cElkIjoicGFu + LXJlc2V0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUv + di1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5 + cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjUgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NlbGVjdCJ9LCJtb2Rl + Ijp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA0 + IiwiZnhBbmNob3IiOiJpZCIsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9tdXRlIiwibmFtZSI6IlRyNSBNdXRl + IiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoNS9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJz + ZWxlY3RlZF90cmFja19pbmRleCArIDQiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUvc29sbyIsIm5hbWUiOiJUcjUg + U29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDUvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9u + Ijoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L2FybSIsIm5hbWUiOiJU + cjUgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g1L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tF + eHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3RvdWNo + IiwibmFtZSI6IlRyNSBUb3VjaCIsImdyb3VwSWQiOiJ0b3VjaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwi + dHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1 + L25hbWUiLCJuYW1lIjoiVHI1IE5hbWUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDUvbGNkL2xpbmUxIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC50cmFjay5uYW1lIH19IiwiZmVlZGJhY2tDb2xvciI6bnVs + bCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDQiLCJmeEFuY2hvciI6 + ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3Bhbi9sY2QiLCJuYW1lIjoiVHI1IFBhbiBMQ0Qi + LCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUyIn0sIm1vZGUiOnsi + ZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0 + Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvcGVhayIsIm5hbWUiOiJUcjUgUGVha3MiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tZXRlci9wZWFrIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJ0eXBlIjozNCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92b2wiLCJuYW1lIjoiVHI2IFZvbCIsImdyb3VwSWQiOiJ2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6 + MiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2g2L3BhbiIsIm5hbWUiOiJUcjYgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g2L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUi + OjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + ImNoNi9wYW4vZmVlZGJhY2siLCJuYW1lIjoiVHI2IFBhbiBGZWVkYmFjayIsImdyb3VwSWQiOiJwYW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9ib29zdC1jdXQifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNr + RXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDYvcGFuL3Jlc2V0IiwibmFtZSI6IlRyNiBQYW4gUmVzZXQiLCJncm91cElkIjoicGFuLXJlc2V0Iiwic291cmNlIjp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJn + ZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNl + bGVjdGVkX3RyYWNrX2luZGV4ICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g2L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjYgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA1IiwiZnhBbmNob3IiOiJpZCIsInNjcm9sbE1p + eGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNi9tdXRlIiwibmFtZSI6IlRyNiBNdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2Ui + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tdXRlIn0sIm1vZGUiOnsi + dHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDUiLCJm + eEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvc29sbyIsIm5hbWUiOiJUcjYgU29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291 + cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyJ9LCJtb2Rl + Ijp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA1 + IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L2FybSIsIm5hbWUiOiJUcjYgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3JlY29yZC1yZWFk + eSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tf + aW5kZXggKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L3RvdWNoIiwibmFtZSI6IlRyNiBUb3VjaCIsImdyb3Vw + SWQiOiJ0b3VjaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g2L2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRf + dHJhY2tfaW5kZXggKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L25hbWUiLCJuYW1lIjoiVHI2IE5hbWUiLCJn + cm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUxIn0sIm1vZGUiOnsiZWVs + RmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC50cmFjay5uYW1lIH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0 + Ijp7InR5cGUiOjQ0LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDUiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Bhbi9sY2QiLCJuYW1lIjoiVHI2IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6 + Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lv + biI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDYvcGVhayIsIm5hbWUiOiJUcjYgUGVha3MiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoNi9tZXRlci9wZWFrIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tFeHByZXNzaW9u + Ijoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoNy92b2wiLCJuYW1lIjoiVHI3IFZvbCIsImdyb3VwSWQiOiJ2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNy9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0 + ZWRfdHJhY2tfaW5kZXggKyA2IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3BhbiIsIm5hbWUiOiJUcjcgUGFuIiwi + Z3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90In0sIm1vZGUiOnsibWluU3Rl + cFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVj + dGVkX3RyYWNrX2luZGV4ICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNy9wYW4vZmVlZGJhY2siLCJuYW1lIjoi + VHI3IFBhbiBGZWVkYmFjayIsImdyb3VwSWQiOiJwYW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBv + dC9ib29zdC1jdXQifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNr + X2luZGV4ICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvcGFuL3Jl + c2V0IiwibmFtZSI6IlRyNyBQYW4gUmVzZXQiLCJncm91cElkIjoicGFuLXJlc2V0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1 + ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNiIsImZ4 + QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3NlbGVjdGlvbiIsIm5hbWUiOiJU + cjcgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6Niwi + dHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA2IiwiZnhBbmNob3IiOiJpZCIsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6ImNoNy9tdXRlIiwibmFtZSI6IlRyNyBNdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDYiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvc29sbyIsIm5hbWUiOiJUcjcgU29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA2IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L2FybSIsIm5hbWUiOiJUcjcgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA2IiwiZnhBbmNob3IiOiJpZCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3RvdWNoIiwibmFtZSI6IlRyNyBUb3VjaCIsImdyb3VwSWQiOiJ0b3VjaCIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA2IiwiZnhBbmNob3Ii + OiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L25hbWUiLCJuYW1lIjoiVHI3IE5hbWUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUxIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7 + IHRhcmdldC50cmFjay5uYW1lIH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFja0V4cHJlc3Np + b24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDYiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g3L3Bhbi9sY2QiLCJuYW1lIjoiVHI3IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVl + ZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsg + NiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvcGVhayIsIm5hbWUiOiJU + cjcgUGVha3MiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tZXRlci9wZWFr + In0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA2 + IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92b2wiLCJuYW1lIjoiVHI4 + IFZvbCIsImdyb3VwSWQiOiJ2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlciJ9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA3IiwiZnhBbmNo + b3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3BhbiIsIm5hbWUiOiJUcjggUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6 + MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNyIsImZ4QW5j + aG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9wYW4vZmVlZGJhY2siLCJuYW1lIjoiVHI4IFBhbiBGZWVkYmFjayIsImdyb3VwSWQi + OiJwYW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9ib29zdC1jdXQifSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNyIsImZ4QW5jaG9yIjoiaWQi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvcGFuL3Jlc2V0IiwibmFtZSI6IlRyOCBQYW4gUmVzZXQi + LCJncm91cElkIjoicGFuLXJlc2V0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjggU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNl + bGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4 + L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRf + dHJhY2tfaW5kZXggKyA3IiwiZnhBbmNob3IiOiJpZCIsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9tdXRlIiwi + bmFtZSI6IlRyOCBNdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFj + a0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCArIDciLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvc29s + byIsIm5hbWUiOiJUcjggU29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwi + dHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4 + L2FybSIsIm5hbWUiOiJUcjggQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + dHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiY2g4L3RvdWNoIiwibmFtZSI6IlRyOCBUb3VjaCIsImdyb3VwSWQiOiJ0b3VjaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2g4L25hbWUiLCJuYW1lIjoiVHI4IE5hbWUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUxIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC50cmFjay5uYW1lIH19IiwiZmVl + ZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleCAr + IDciLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Bhbi9sY2QiLCJuYW1l + IjoiVHI4IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xp + bmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tU + eXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InNlbGVjdGVkX3RyYWNrX2luZGV4ICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvcGVhayIsIm5hbWUiOiJUcjggUGVha3MiLCJncm91cElkIjoibWV0ZXIi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tZXRlci9wZWFrIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tFeHByZXNzaW9uIjoic2VsZWN0ZWRfdHJhY2tfaW5kZXggKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoibWFpbi9mYWRlciIsImdyb3VwSWQiOiJm + YWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiam9nIiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NjAsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2ciLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibWFpbi9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjExMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibWFya2VyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODR9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYXJrZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJlYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo3NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + IndyaXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ3cml0ZSIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gtbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ4fSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo0OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJiYW5rLWxlZnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmst + bGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiYmFuay1yaWdodCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ3fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicmV3aW5kIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6ImZhc3QtZndkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJmYXN0LWZ3ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicGxheSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk0 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicGxheSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic3RvcCIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic3RvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoicmVjb3JkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNv + cmQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImN5Y2xlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODZ9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjeWNsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiem9vbSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjEwMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Inpvb20iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InNj + cnViIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic2NydWIiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImN1cnNvci1sZWZ0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTh9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6ImN1cnNvci11cCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY3Vyc29yLXVwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjdXJzb3ItZG93biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjk3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWRvd24iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Im51 + ZGdlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJudWRnZSIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZHJvcCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg3fSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiZHJvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicmVwbGFjZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjg4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVwbGFjZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2xpY2siLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJzb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU0fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImYyIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjMi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYzIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJmNCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiZjQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImY1Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTh9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo1OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY2IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJzbXB0ZS1i + ZWF0cyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImxjZC9hc3NpZ25tZW50IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5 + cGUiOiJtYWNraWUtc2V2ZW4ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL2Fzc2lnbm1lbnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJsY2QvdGltZWNvZGUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiIsImRpc3BsYXlJZCI6Mn0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImxjZC90aW1lY29kZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXNlbGVjdCIsImdyb3Vw + SWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gx + L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9mYWRlci90b3VjaCIsImdyb3VwSWQi + OiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + MS9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvc2VsZWN0IiwiZ3JvdXBJZCI6 + InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NlbGVj + dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjB9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDEvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3dyYXAiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxl + ZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4 + U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gx + L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAwIGRjYmFdIn0s + Im1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2gxL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDEx + IGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJjaDEvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTZ9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL3NvbG8iLCJncm91cElkIjoic29sbyIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2gxL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTEiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6 + eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjowLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoMS9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDAw + IGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMS9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Ytc2Vs + ZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDIvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL2ZhZGVyL3RvdWNo + IiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9zZWxlY3Qi + LCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDIvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDIvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6Mywi + Y2hhbm5lbCI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3Qi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNywiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBv + dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1w + b3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9ib29zdC1jdXQiLCJncm91cElk + Ijoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVl + IjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDIvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAw + MDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoi + QjAgMzEgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxN30s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDIvc29sbyIsImdyb3VwSWQi + OiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDIvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjaDIvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjowfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xj + ZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9sY2QvbGluZTIiLCJncm91cElkIjoibGNk + Iiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJu + IjoiRDAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDMvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNH0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMv + ZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDZ9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2gzL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMy9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6 + eyJ0eXBlIjozLCJjaGFubmVsIjoyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvdi1wb3QvY29udHJvbCIsImdyb3Vw + SWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gzL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC93cmFwIiwiZ3JvdXBJ + ZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1 + ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMy92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L2Jvb3N0LWN1 + dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWlu + U291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJCMCAzMiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJCMCAzMiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjE4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9zb2xv + IiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMy9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoyLCJsaW5lIjow + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gzL2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9sY2QvbGluZTIiLCJn + cm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJh + d01pZGlQYXR0ZXJuIjoiRDAgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJjaDQvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNX0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDQvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDd9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g0L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyN30sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNC9mYWRlciIsImdyb3VwSWQiOiJmYWRl + ciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjozfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvdi1wb3QvY29u + dHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g0L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC93 + cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJt + YXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Yt + cG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAxIGRjYmFdIn0s + Im1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3 + TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBl + IjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjE5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImNoNC9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoNC9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFk + eSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvcmVjb3JkLXJlYWR5 + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlk + IjozLCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9s + Y2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjMsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUyIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjozNn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjoxMDh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoy + OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9mYWRlciIsImdy + b3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJj + aDUvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoNS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMTAgZGNiYV0i + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2g1L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFsw + MDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0 + eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3NpbmdsZSIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNv + dXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvc3ByZWFk + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L211dGUiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6ImNoNS9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMn0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6 + InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUv + cmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjox + MiwiZGlzcGxheUlkIjo0LCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoNS9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xp + bmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIi + LCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMDAgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjozN30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjoxMDl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoyOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNo + Ni9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjaDYvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIxLCJjaGFyYWN0ZXIiOjQs + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoNi92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUg + WzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVy + biI6IkIwIDM1IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L2Jvb3N0LWN1dCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIs + InNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3Np + bmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1w + b3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQs + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYv + di1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L211dGUiLCJncm91cElkIjoibXV0 + ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L211dGUiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNi9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjox + M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvcmVjb3JkLXJlYWR5 + IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDYvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJj + ZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo1LCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoNi9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjF9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDYvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L21ldGVyL3BlYWsiLCJncm91 + cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMDEgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L21ldGVyL3BlYWsiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImNoNy9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJj + aGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0 + ZXJuIjoiQjAgMzYgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJy + YXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L2Jv + b3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9zaW5nbGUiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g3L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9zcHJlYWQiLCJn + cm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJj + ZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDcvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L211dGUiLCJn + cm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3 + L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNy9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxNH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcv + cmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjZ9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6 + ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo2LCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjYs + ImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L21ldGVy + L3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTAgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L21ldGVyL3Bl + YWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3Qi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXNlbGVjdCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2gi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIvdG91Y2gi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zZWxlY3QiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo3fSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVy + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjow + Ljc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g4L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9zaW5nbGUi + LCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNv + dXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g4L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBv + dC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMSBkY2JhXSJ9LCJtb2Rl + Ijp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g4L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g4L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJjaDgvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjd9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvbGNkL2xpbmUx + IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo3LCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJk + aXNwbGF5SWQiOjcsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g4L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTEgZGNiYV0ifSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g4L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWFja2ll + LWNvbnRyb2wiLCJhY3RpdmVNYWluUHJlc2V0SWQiOiJkYXctY29udHJvbCIsInBhcmFtZXRlcnMiOnsiMiI6eyJuYW1lIjoiWm9vbSBvbi9vZmYifSwiMSI6eyJuYW1l + IjoiU2VlayBjb2Fyc2UvZmluZSJ9fSwibWFpbiI6eyJhY3RpdmVNYXBwaW5nQnlHcm91cCI6eyJmMmU1MjZhYy04ZWJmLTRiMDktYWU5Zi0wYzMxMzQ5YmJiZGYiOiI4 + OTVjZDVjMi0xZmM4LTQ2ZDItOWJjYi0xYzQ4MWRlY2Y1Y2UiLCJjNTcwMmY3OS1hMzIwLTQyNzctOGI1Mi03ZWM0NmJiNTJiYzIiOiJkMTQ5YTA1YS1lNzAyLTRjNWEt + ODgyOC1jNGZmNzIzMTk0ZGIiLCIwMDM4ZTc4My01MTA2LTRkNWUtYTYwMS04N2Y4NWMzMzQ4NjkiOiI4NGQyZGFkZC1hNjU2LTQ1ZTgtYTRiNS01NDk2ZjJkNjRkMjEi + LCIxZTAyNDczNi00MmVkLTQ5ZTQtYmM4ZS1hZDExZDFiYmQyYWIiOiJmODU3ZjQ3YS02NDgyLTQ0YjAtYThmMS1mZDQ1NmY0MjlmODciLCI4NTIzNDQ5OS1hMzdjLTQ5 + NWMtYTI1OS1kZGQ4OWVjZmQ1NWUiOiIwZmVjY2E3Zi1iZDJkLTRjNDYtYjFjMi1hNmRkM2EwNzEwNGQiLCJhMjEzMGE4ZC1lMGIyLTQyOWMtYWNhZC00N2RiYTRhYWEz + MTMiOiJmMGMzOTczNS03NjZhLTQ2MjAtYjU4Yy03ZmU5YTNhYWI3YjEiLCJmNWNiMjBlZC1kZDg2LTRhM2EtOGExOC1mN2EyNTQ5YjM2ODciOiJiNTFiMDhlNS01ODQz + LTRhMDItOTFmOC0zNjAzZDE4NDUyM2MiLCI5NjZlNjIzYy05OWZjLTRlYzMtOTlhYi1hODNlNmFkNGNkNTAiOiJhMjNjYjZmMi1hODZlLTQ4OGQtYjA4Ny02MDk5OTQ4 + YzczMTEiLCI3MGZjYmIxZS03Y2E1LTRjOTctYTQyOC0yOTVjNDYxM2FjOWUiOiJiNTAyMTJkZC04ZjUwLTQ4NDgtODg1My01N2FmNjRmZTdhMWIifX19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 668 322 1436 1514 + FXID {B8DF9E06-D43A-4AB1-8053-16BAC2AF996A} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA02oBAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xNCIsImlkIjoiSW5DWWRYZHUiLCJjb250cm9sRGV2aWNlSWQiOiI3IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjgiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJncm91cHMiOlt7ImlkIjoic3dpdGNoaW5nIiwibmFtZSI6IlN3aXRjaGluZyJ9LHsiaWQiOiJ2b2x1bWUiLCJuYW1lIjoiVm9sdW1lIn0seyJpZCI6 + InBhbiIsIm5hbWUiOiJQYW4ifSx7ImlkIjoicGFuLXJlc2V0IiwibmFtZSI6IlBhbiByZXNldCJ9LHsiaWQiOiJzZWxlY3Rpb24iLCJuYW1lIjoiU2VsZWN0aW9uIn0s + eyJpZCI6Im11dGUiLCJuYW1lIjoiTXV0ZSJ9LHsiaWQiOiJzb2xvIiwibmFtZSI6IlNvbG8ifSx7ImlkIjoiYXJtIiwibmFtZSI6IkFybSJ9LHsiaWQiOiJ0b3VjaCIs + Im5hbWUiOiJUb3VjaCJ9LHsiaWQiOiJ0cmFuc3BvcnQiLCJuYW1lIjoiVHJhbnNwb3J0In0seyJpZCI6Inpvb20iLCJuYW1lIjoiWm9vbSIsImFjdGl2YXRpb25UeXBl + IjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjIsImlzT24iOnRydWV9fSx7ImlkIjoibWFzdGVyIiwibmFtZSI6Ik1hc3RlciJ9 + LHsiaWQiOiJzY3JvbGwiLCJuYW1lIjoiU2Nyb2xsIiwiYWN0aXZhdGlvblR5cGUiOiJtb2RpZmllcnMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsicGFyYW1JbmRleCI6 + MiwiaXNPbiI6ZmFsc2V9fSx7ImlkIjoibGNkIiwibmFtZSI6IkxDRCJ9LHsiaWQiOiJtZXRlciIsIm5hbWUiOiJNZXRlciJ9LHsiaWQiOiJ0aW1lLW1vZGVzIiwibmFt + ZSI6IlRpbWUgbW9kZXMifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyR3JvdXBzIjpbeyJpZCI6ImZhZGVyIiwibmFtZSI6IkZhZGVyIn0s + eyJpZCI6InYtcG90IiwibmFtZSI6IlYtUG90In0seyJpZCI6InYtc2VsZWN0IiwibmFtZSI6IlYtU2VsZWN0In0seyJpZCI6ImZhZGVyLXRvdWNoIiwibmFtZSI6IkZh + ZGVyIFRvdWNoIn0seyJpZCI6InNlbGVjdCIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoibXV0ZSIsIm5hbWUiOiJNdXRlIn0seyJpZCI6InNvbG8iLCJuYW1lIjoiU29s + byJ9LHsiaWQiOiJyZWNvcmQtcmVhZHkiLCJuYW1lIjoiUmVjb3JkLXJlYWR5In0seyJpZCI6InYtcG90LWxlZHMiLCJuYW1lIjoiVi1Qb3QgTEVEcyJ9LHsiaWQiOiJs + Y2QiLCJuYW1lIjoiTENEIn0seyJpZCI6Im1ldGVyIiwibmFtZSI6Ik1ldGVyIn1dLCJtYXBwaW5ncyI6W3siaWQiOiI1Y2ZkMmZmMC04NWNlLTRlNzAtOThlOC1lYjUz + ZTVlOTRiYjEiLCJuYW1lIjoiQmFuayAtIiwiZ3JvdXBJZCI6InN3aXRjaGluZyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgsIm1heFN0ZXBTaXpl + IjowLjA4LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwic2Nyb2xs + QXJyYW5nZVZpZXciOnRydWUsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + MDdiMDIwMmYtYjhiOC00OGU0LWE1MzAtZjIyODQ1MDg2NGYwIiwibmFtZSI6IkJhbmsgLSBmYXN0ZXIiLCJncm91cElkIjoic3dpdGNoaW5nIiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLWxlZnQifSwibW9kZSI6eyJ0eXBl + IjoxLCJtaW5TdGVwU2l6ZSI6MC4wOCwibWF4U3RlcFNpemUiOjAuMDgsIm1pblByZXNzTWlsbGlzIjozMDAsIm1heFByZXNzTWlsbGlzIjozMDAsInR1cmJvUmF0ZSI6 + MTAwLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjoxNCwiZnhBbmNo + b3IiOiJpZCIsInNjcm9sbEFycmFuZ2VWaWV3Ijp0cnVlLCJzY3JvbGxNaXhlciI6dHJ1ZSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6IjBiYWFiOTFmLTJjNGUtNDNhZS04Y2FjLWRjYTcyNzk2M2I0NiIsIm5hbWUiOiJCYW5rICsiLCJncm91cElkIjoic3dpdGNoaW5nIiwic291 + cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0In0sIm1v + ZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgsIm1heFN0ZXBTaXplIjowLjA4LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJm + eEFuY2hvciI6ImlkIiwic2Nyb2xsQXJyYW5nZVZpZXciOnRydWUsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiMGE3ZTEyMmMtM2VkYS00MzU1LTkwNmEtYTA1N2VlZmE1MTViIiwibmFtZSI6IkJhbmsgKyBmYXN0ZXIiLCJncm91cElkIjoic3dp + dGNoaW5nIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5r + LXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgsIm1heFN0ZXBTaXplIjowLjA4LCJtaW5QcmVzc01pbGxpcyI6MzAwLCJtYXhQcmVzc01p + bGxpcyI6MzAwLCJ0dXJib1JhdGUiOjEwMCwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MTQsImZ4QW5jaG9y + IjoiaWQiLCJzY3JvbGxBcnJhbmdlVmlldyI6dHJ1ZSwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJjZjQ1Njg5YS01NTM3LTQ2NWQtYWQ0NC00NjBkYmViZjQ4MDIiLCJuYW1lIjoiVHJhY2sgLSIsImdyb3VwSWQiOiJzd2l0Y2hpbmciLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQifSwibW9kZSI6 + eyJ0eXBlIjoxLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwic2Ny + b2xsQXJyYW5nZVZpZXciOnRydWUsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiNzY0NjViNWYtYzliMy00YWE2LWJhNzctYjg5MTAyMWNmMzhlIiwibmFtZSI6IlRyYWNrIC0gZmFzdGVyIiwiZ3JvdXBJZCI6InN3aXRjaGluZyIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCJ9LCJtb2RlIjp7InR5 + cGUiOjEsIm1pblByZXNzTWlsbGlzIjozMDAsIm1heFByZXNzTWlsbGlzIjozMDAsInR1cmJvUmF0ZSI6NjAsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNr + Q29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsQXJyYW5nZVZpZXciOnRydWUsInNj + cm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZDQwZmZhNDQtYTQ0Ni00NDk5LWI5 + ZDYtNzg3YzVmZjhlMTg4IiwibmFtZSI6IlRyYWNrICsiLCJncm91cElkIjoic3dpdGNoaW5nIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsidHlwZSI6MTQsImZ4QW5jaG9yIjoiaWQiLCJzY3JvbGxBcnJhbmdlVmlldyI6dHJ1ZSwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5N2RiOTkxYy1hMTViLTRjMjctYWViMi0wNGIyYWUzMjQyYzIiLCJuYW1lIjoiVHJhY2sgKyBm + YXN0ZXIiLCJncm91cElkIjoic3dpdGNoaW5nIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblByZXNzTWlsbGlzIjozMDAsIm1heFByZXNzTWlsbGlzIjozMDAsInR1cmJvUmF0 + ZSI6NjAsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsQXJyYW5n + ZVZpZXciOnRydWUsInNjcm9sbE1peGVyIjp0cnVlLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYWZhMzU0 + NTgtMzE2MC00ZjQ5LWJiOTAtMjc5YWFlNTE3NzlmIiwibmFtZSI6IlBsYXkvcGF1c2UiLCJncm91cElkIjoidHJhbnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoxNiwiZnhBbmNob3IiOiJpZCIsInRyYW5zcG9ydEFjdGlvbiI6InBsYXlQYXVzZSIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiNjdiM2E1MTYtMjQ2Zi00M2E2LWI4NTAtMDUwZWI0MTU1MjBhIiwibmFtZSI6IlN0b3AiLCJncm91cElkIjoidHJhbnNwb3J0Iiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzdG9wIn0sIm1vZGUiOnsidHlw + ZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbCwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIn0sInRhcmdldCI6eyJ0eXBlIjoxNiwiZnhBbmNob3IiOiJpZCIsInRyYW5z + cG9ydEFjdGlvbiI6InN0b3AiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImYxNGViZDYzLTAxMjctNDk2ZC05YWNiLTI4MWI1YTdiYjNkYiIsIm5hbWUi + OiJSZXBlYXQiLCJncm91cElkIjoidHJhbnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjeWNsZSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGwsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiJ9LCJ0 + YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ0cmFuc3BvcnRBY3Rpb24iOiJyZXBlYXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjAw + NjI2NDk4LTgwYTYtNDFkNC1iYzlmLWYyNzMwOThlM2RjZiIsIm5hbWUiOiJSZWNvcmQiLCJncm91cElkIjoidHJhbnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNvcmQifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFj + a0NvbG9yIjpudWxsLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4ifSwidGFyZ2V0Ijp7InR5cGUiOjE2LCJmeEFuY2hvciI6ImlkIiwidHJhbnNwb3J0QWN0aW9uIjoi + cmVjb3JkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIxZjgyN2I3Yi1kYjA0LTQ3OGEtYTA1Yi0zNmU4OThmNmIyNDUiLCJuYW1lIjoiU2NydWIgY29h + cnNlIiwiZ3JvdXBJZCI6InRyYW5zcG9ydCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIn0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6Ijk5MiIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwiYWN0aXZhdGlvblR5cGUiOiJtb2RpZmllcnMiLCJtb2RpZmllckNvbmRpdGlv + bjEiOnsicGFyYW1JbmRleCI6MSwiaXNPbiI6ZmFsc2V9fSx7ImlkIjoiYTI1OTA2MmUtZGZkZS00ZTZjLThlMGEtMDE0YTA3OGZjMWJlIiwibmFtZSI6IlNjcnViIGZp + bmUiLCJncm91cElkIjoidHJhbnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJt + aW5TdGVwU2l6ZSI6MC4wLCJtYXhTdGVwU2l6ZSI6MC4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTc0Iiwi + aW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJhY3RpdmF0aW9u + VHlwZSI6Im1vZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjoxLCJpc09uIjp0cnVlfX0seyJpZCI6IjFlNWM2MDRmLTkwNDAtNDdjMC1h + YjM3LWEwNTM4M2U3YmUwMCIsIm5hbWUiOiJTZWVrIGNvYXJzZS9maW5lIiwiZ3JvdXBJZCI6InN3aXRjaGluZyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic2NydWIifSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhQcmVzc01pbGxpcyI6 + MjUwLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInBhcmFtSW5kZXgiOjEsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiZWU4MWM0MDUtZDM1NS00NWUwLTgwYjQtOTEzNjMzMjk1MTkzIiwibmFtZSI6Ilpvb20gb24vb2ZmIiwiZ3JvdXBJZCI6InN3aXRjaGluZyIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoiem9vbSJ9LCJtb2RlIjp7InR5cGUi + OjIsIm1heFByZXNzTWlsbGlzIjoyNTAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwicGFyYW1JbmRleCI6MiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMTM2NzhiMS1kYTgxLTQ2YTUtYjMxOC04NjFhODEzZDcxODMiLCJuYW1lIjoiUHJldmlvdXMiLCJncm91cElkIjoidHJh + bnNwb3J0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdp + bmQifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxNzIiLCJpbnZvY2F0aW9uVHlwZSI6MCwi + ZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJwcmV2aW91cy1sZWQiLCJuYW1lIjoi + UHJldmlvdXMgTEVEIiwiZ3JvdXBJZCI6InRyYW5zcG9ydCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoicmV3aW5kIn0sIm1vZGUiOnsibWF4VGFyZ2V0VmFsdWUiOjAuMDAwMDEsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6MjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjdmMGQyY2JiLTAzNDYtNDIyOS04OWRkLWRiNThjYWMzYTQ2MCIsIm5hbWUiOiJOZXh0IiwiZ3Jv + dXBJZCI6InRyYW5zcG9ydCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiZmFzdC1md2QifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxNzMiLCJpbnZvY2F0 + aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJuZXh0LWxlZCIs + Im5hbWUiOiJOZXh0IExFRCIsImdyb3VwSWQiOiJ0cmFuc3BvcnQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIn0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuOTk5OTksInJldmVyc2VJc0VuYWJsZWQiOnRydWUs + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhiMGNlNWRiLWEzNzEtNGI0Yy05ZmU4LWNh + MDg2YmM3MDZiZiIsIm5hbWUiOiJab29tIG91dCBob3Jpem9udGFsbHkiLCJncm91cElkIjoiem9vbSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQifSwibW9kZSI6eyJ0eXBlIjoxLCJ0dXJib1JhdGUiOjEwMCwi + cmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUi + OiI5OTgiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiI5YjZmZTBiOC1kNWYzLTQzOGItOTgzMC02YmI3ODIxMjFjMmQiLCJuYW1lIjoiWm9vbSBpbiBob3Jpem9udGFsbHkiLCJncm91cElkIjoiem9vbSIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0In0sIm1v + ZGUiOnsidHlwZSI6MSwidHVyYm9SYXRlIjoxMDAsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1h + bmROYW1lIjoiOTk4IiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiYzNjMzQ2ODAtMDM5Ni00YjI0LTg3MTQtZTIxMDA2MmY0NmNkIiwibmFtZSI6Ilpvb20gb3V0IHZlcnRpY2FsbHkiLCJncm91cElkIjoiem9vbSIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWRvd24i + fSwibW9kZSI6eyJ0dXJib1JhdGUiOjEwMCwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5h + bWUiOiI0MDExMiIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImU5M2U4OTgxLTgxMjctNDQ2Zi1iNWQ5LTM0YmI1NzZmYmMyYiIsIm5hbWUiOiJab29tIGluIHZlcnRpY2FsbHkiLCJncm91cElkIjoiem9vbSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXVwIn0sIm1v + ZGUiOnsidHVyYm9SYXRlIjoxMDAsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoi + NDAxMTEiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiI4YjZkNGNmNS1kNGYyLTQ3MDEtYWY3MS05MGQ4MzlkYTIzMTgiLCJuYW1lIjoiU2Nyb2xsIGRvd24iLCJncm91cElkIjoic2Nyb2xsIiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItZG93biJ9LCJtb2RlIjp7InR5 + cGUiOjEsIm1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVwU2l6ZSI6MC4wOCwidHVyYm9SYXRlIjoxMDAsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29s + b3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTg5IiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9y + IjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYjYzM2MyZDEtYzI4My00MDIxLWExZmMtNzZiNmFi + ZDQwN2I5IiwibmFtZSI6IlNjcm9sbCB1cCIsImdyb3VwSWQiOiJzY3JvbGwiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci11cCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVwU2l6ZSI6 + MC4wOCwidHVyYm9SYXRlIjoxMDAsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoi + OTg5IiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiYjlmYTNkYjYtOGJlZC00YzhhLTgyZmEtNTgwZDNkMGRhOTJjIiwibmFtZSI6IlNjcm9sbCBsZWZ0IiwiZ3JvdXBJZCI6InNjcm9sbCIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQifSwibW9kZSI6eyJ0eXBl + IjoxLCJtaW5TdGVwU2l6ZSI6MC4wOCwibWF4U3RlcFNpemUiOjAuMDgsInR1cmJvUmF0ZSI6MTAwLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9y + IjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQwMTQwIiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9y + IjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzQ0NDA1ZDUtYjUwZC00N2Q0LWEyMmUtYWE5OWE3 + NDU0YTI1IiwibmFtZSI6IlNjcm9sbCByaWdodCIsImdyb3VwSWQiOiJzY3JvbGwiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1yaWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVw + U2l6ZSI6MC4wOCwidHVyYm9SYXRlIjoxMDAsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFy + Z2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxNDEiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyNjEwNjdlZi1jMmYwLTQxZGUtYjEwZi00MThjYzcxOGNmMWMiLCJuYW1lIjoiTWFzdGVyIHRvdWNoIiwi + Z3JvdXBJZCI6Im1hc3RlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoibWFpbi9mYWRlci90b3VjaCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MjEsInRyYWNrR1VJRCI6Im1hc3RlciIs + ImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjJmNDVjZGY2LWJhYzktNGE5Ny04YTNlLWMyOTZkMmNjN2UwZiIsIm5hbWUiOiJN + YXN0ZXIgdm9sdW1lIiwiZ3JvdXBJZCI6Im1hc3RlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRl + ciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiOTcwZTA0YzktZTI2Mi00NzhlLTg1Y2UtOWU4NDg3NWZiNWY1IiwibmFtZSI6Ik1hcmtlciIsImdyb3VwSWQiOiJtYXN0 + ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1hcmtlciJ9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGwsIm91dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsi + dHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE1NyIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRi + YWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImM0OTIzOGI5LTE4OWUtNDJkMy05ODRhLTk1NWZlNjNjNDZlOCIsIm5hbWUiOiJHbG9iYWwgcmVhZCIsImdyb3VwSWQi + OiJtYXN0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJl + YWQifSwibW9kZSI6eyJ0eXBlIjoyLCJtaW5UYXJnZXRWYWx1ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjI2LCJmeEFuY2hvciI6 + ImlkIiwidHJhY2tBdXRvbWF0aW9uTW9kZSI6MSwiYXV0b21hdGlvbk1vZGVPdmVycmlkZVR5cGUiOiJvdmVycmlkZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiYjgwNWY2MjgtMTZlMS00MmNhLWFjMzYtM2JiOTUxMzM4MTVmIiwibmFtZSI6Ikdsb2JhbCB3cml0ZSIsImdyb3VwSWQiOiJtYXN0ZXIiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIn0sIm1vZGUiOnsidHlwZSI6 + MiwibWluVGFyZ2V0VmFsdWUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyNiwiZnhBbmNob3IiOiJpZCIsInRyYWNrQXV0b21hdGlv + bk1vZGUiOjIsImF1dG9tYXRpb25Nb2RlT3ZlcnJpZGVUeXBlIjoib3ZlcnJpZGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjEzZDc5ZDQyLTRkNTkt + NDkzMC1hOTc2LWI0Mzc0ZTBhNjliNiIsIm5hbWUiOiJDbGljayIsImdyb3VwSWQiOiJtYXN0ZXIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQwMzY0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiYW55LXNvbG8i + LCJuYW1lIjoiQW55IHNvbG8iLCJncm91cElkIjoibWFzdGVyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzb2xvIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0MywiZnhBbmNob3IiOiJp + ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiN2I5MGMxMzYtZjg5Yy00NzdlLWI4MTItNTI1YjRhN2RhNWVkIiwibmFtZSI6IkFzc2lnbm1lbnQiLCJn + cm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdubWVudCJ9LCJtb2RlIjp7ImVl + bEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQubnVtZXJpY192YWx1ZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRh + cmdldCI6eyJ0eXBlIjoxNCwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjdHRk5R + ZlpwOXVxb3ZoVjV6V3R2aCIsIm5hbWUiOiJUaW1lY29kZSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNl + dmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6eyJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoie3sgdGFyZ2V0LnBvc2l0aW9uLnByb2plY3RfZGVmYXVsdC5tY3Ug + fX0iLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsImZlZWRiYWNrUmVzb2x1dGlvbiI6ImhpZ2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ0aW1lLW1vZGUtbWVhc3VyZXMtYmVhdHMiLCJuYW1lIjoiTWVhc3VyZXMuQmVhdHMiLCJncm91cElkIjoidGltZS1tb2RlcyIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMi + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDA0MTEiLCJp + bnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InRpbWUt + bW9kZS1zZWNvbmRzIiwibmFtZSI6IlNlY29uZHMiLCJncm91cElkIjoidGltZS1tb2RlcyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDA0MTIiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InRpbWUtbW9kZS1zYW1wbGVzIiwibmFtZSI6IlNhbXBsZXMiLCJncm91cElkIjoidGltZS1tb2RlcyIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMifSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDA0MTMiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJp + ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InRpbWUtbW9kZS1obXNmIiwibmFtZSI6IkhvdXJzOk1pbnV0 + ZXM6U2Vjb25kczpGcmFtZXMiLCJncm91cElkIjoidGltZS1tb2RlcyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNv + bW1hbmROYW1lIjoiNDA0MTQiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6InRpbWUtbW9kZS1hYnNvbHV0ZS1mcmFtZXMiLCJuYW1lIjoiQWJzb2x1dGUgRnJhbWVzIiwiZ3JvdXBJZCI6InRpbWUtbW9kZXMiLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIn0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQxOTcyIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9y + IjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ0aW1lLW1vZGUtbWludXRlcy1zZWNvbmRzIiwibmFt + ZSI6Ik1pbnV0ZXM6U2Vjb25kcyIsImdyb3VwSWQiOiJ0aW1lLW1vZGVzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzbXB0ZS1iZWF0cyJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MCwi + Y29tbWFuZE5hbWUiOiI0MDQxMCIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY3ljbGUtdGltZS1tb2RlcyIsIm5hbWUiOiJDeWNsZSB0aW1lIG1vZGVzIiwiZ3JvdXBJZCI6Im1hc3RlciIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMifSwibW9kZSI6eyJ0eXBl + IjoxLCJmZWVkYmFja0NvbG9yIjpudWxsLCJyb3RhdGVJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MzcsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJncm91cElkIjoidGltZS1tb2RlcyJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdm9sIiwibmFtZSI6IlRyMSBWb2wi + LCJncm91cElkIjoidm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIifSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2Vs + ZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL3BhbiIsIm5hbWUiOiJU + cjEgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90In0sIm1vZGUi + OnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lv + biI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL3Bhbi9mZWVkYmFjayIsIm5hbWUiOiJUcjEgUGFuIEZlZWRiYWNrIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L2Jvb3N0LWN1dCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAw + KSArIDAiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3Bhbi9yZXNldCIs + Im5hbWUiOiJUcjEgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6InBhbi1yZXNldCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAu + NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYm + IHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2gxL3NlbGVjdGlvbiIsIm5hbWUiOiJUcjEgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3Rl + ZF90cmFja19pbmRleCB8fCAwKSArIDAiLCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gx + L211dGUiLCJuYW1lIjoiVHIxIE11dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUi + OjcsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAwIiwiZnhBbmNo + b3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL3NvbG8iLCJuYW1lIjoiVHIxIFNvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NvbG8ifSwibW9kZSI6eyJ0 + eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9u + ZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL2FybSIs + Im5hbWUiOiJUcjEgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gxL3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6 + NSwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDAiLCJmeEFuY2hv + ciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEvdG91Y2giLCJuYW1lIjoiVHIxIFRvdWNoIiwiZ3JvdXBJZCI6InRvdWNoIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2gifSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5v + bmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9uYW1l + IiwibmFtZSI6IlRyMSBOYW1lIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xj + ZC9saW5lMSJ9LCJtb2RlIjp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZl + ZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo0NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3Rl + ZF90cmFja19pbmRleCB8fCAwKSArIDAiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2gxL3Bhbi9sY2QiLCJuYW1lIjoiVHIxIFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tD + b2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9u + ZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAwIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoMS9wZWFrIiwibmFtZSI6IlRyMSBQZWFrcyIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gxL21ldGVyL3BlYWsifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja0V4 + cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdm9sIiwibmFtZSI6IlRyMiBWb2wiLCJncm91cElkIjoidm9s + dW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5k + ZXggfHwgMCkgKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3BhbiIsIm5hbWUiOiJUcjIgUGFuIiwiZ3JvdXBJ + ZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUi + OjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRf + dHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY2gyL3Bhbi9mZWVkYmFjayIsIm5hbWUiOiJUcjIgUGFuIEZlZWRiYWNrIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L2Jvb3N0LWN1dCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6 + NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDEiLCJmeEFuY2hv + ciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Bhbi9yZXNldCIsIm5hbWUiOiJUcjIgUGFu + IFJlc2V0IiwiZ3JvdXBJZCI6InBhbi1yZXNldCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNSwiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNr + X2luZGV4IHx8IDApICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gy + L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjIgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8 + fCAwKSArIDEiLCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL211dGUiLCJuYW1lIjoi + VHIyIE11dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gyL211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVz + c2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3NvbG8iLCJuYW1lIjoiVHIyIFNvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRf + dHJhY2tfaW5kZXggfHwgMCkgKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL2FybSIsIm5hbWUiOiJUcjIgQXJt + IiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gyL3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNz + aW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDEiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDIvdG91Y2giLCJuYW1lIjoiVHIyIFRvdWNoIiwiZ3JvdXBJZCI6InRvdWNoIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVk + X3RyYWNrX2luZGV4IHx8IDApICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi9uYW1lIiwibmFtZSI6IlRyMiBO + YW1lIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMSJ9LCJtb2Rl + Ijp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0s + InRhcmdldCI6eyJ0eXBlIjo0NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8 + fCAwKSArIDEiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3Bhbi9sY2Qi + LCJuYW1lIjoiVHIyIFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIv + bGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVl + ZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRf + dHJhY2tfaW5kZXggfHwgMCkgKyAxIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoMi9wZWFrIiwibmFtZSI6IlRyMiBQZWFrcyIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gyL21ldGVyL3BlYWsifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNl + bGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdm9sIiwibmFtZSI6IlRyMyBWb2wiLCJncm91cElkIjoidm9sdW1lIiwic291cmNlIjp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAyIiwi + ZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3BhbiIsIm5hbWUiOiJUcjMgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVw + U2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0g + bm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAyIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3Bh + bi9mZWVkYmFjayIsIm5hbWUiOiJUcjMgUGFuIEZlZWRiYWNrIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gzL3YtcG90L2Jvb3N0LWN1dCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNz + aW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDIiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3Bhbi9yZXNldCIsIm5hbWUiOiJUcjMgUGFuIFJlc2V0IiwiZ3JvdXBJ + ZCI6InBhbi1yZXNldCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gzL3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsg + MiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3NlbGVjdGlvbiIsIm5h + bWUiOiJUcjMgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlw + ZSI6NiwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDIiLCJmeEFu + Y2hvciI6ImlkIiwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL211dGUiLCJuYW1lIjoiVHIzIE11dGUiLCJncm91 + cElkIjoibXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gzL211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0 + ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAyIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2gzL3NvbG8iLCJuYW1lIjoiVHIzIFNvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwg + MCkgKyAyIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL2FybSIsIm5hbWUiOiJUcjMgQXJtIiwiZ3JvdXBJZCI6ImFy + bSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29y + ZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3Rl + ZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDIiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjaDMvdG91Y2giLCJuYW1lIjoiVHIzIFRvdWNoIiwiZ3JvdXBJZCI6InRvdWNoIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8 + IDApICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9uYW1lIiwibmFtZSI6IlRyMyBOYW1lIiwiZ3JvdXBJZCI6 + ImxjZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2xjZC9saW5lMSJ9LCJtb2RlIjp7ImVlbEZlZWRiYWNr + VHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBl + Ijo0NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDIiLCJmeEFu + Y2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3Bhbi9sY2QiLCJuYW1lIjoiVHIzIFBh + biBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIn0sIm1v + ZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwi + dGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwg + MCkgKyAyIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9wZWFrIiwibmFt + ZSI6IlRyMyBQZWFrcyIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL21ldGVy + L3BlYWsifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2lu + ZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdm9sIiwibmFtZSI6IlRyNCBWb2wiLCJncm91cElkIjoidm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNr + RXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAzIiwiZnhBbmNob3IiOiJpZCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3BhbiIsIm5hbWUiOiJUcjQgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0 + ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAzIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3Bhbi9mZWVkYmFjayIsIm5h + bWUiOiJUcjQgUGFuIEZlZWRiYWNrIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0 + L3YtcG90L2Jvb3N0LWN1dCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3Rl + ZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDMiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Bhbi9yZXNldCIsIm5hbWUiOiJUcjQgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6InBhbi1yZXNldCIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3Ytc2VsZWN0 + In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0 + cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMyIsImZ4QW5jaG9yIjoi + aWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjQgU2VsZWN0 + aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHBy + ZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDMiLCJmeEFuY2hvciI6ImlkIiwic2Ny + b2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L211dGUiLCJuYW1lIjoiVHI0IE11dGUiLCJncm91cElkIjoibXV0ZSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUifSwibW9k + ZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXgg + IT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAzIiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0 + L3NvbG8iLCJuYW1lIjoiVHI0IFNvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUi + OjgsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAzIiwiZnhBbmNo + b3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L2FybSIsIm5hbWUiOiJUcjQgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSJ9LCJtb2Rl + Ijp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAh + PSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDMiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQv + dG91Y2giLCJuYW1lIjoiVHI0IFRvdWNoIiwiZ3JvdXBJZCI6InRvdWNoIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUi + OjIxLCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMyIsImZ4QW5j + aG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNC9uYW1lIiwibmFtZSI6IlRyNCBOYW1lIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMSJ9LCJtb2RlIjp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24i + OiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo0NCwidHJhY2tFeHBy + ZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDMiLCJmeEFuY2hvciI6ImlkIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Bhbi9sY2QiLCJuYW1lIjoiVHI0IFBhbiBMQ0QiLCJncm91cElk + IjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJh + Y2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUi + OjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyAzIiwiZnhBbmNo + b3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9wZWFrIiwibmFtZSI6IlRyNCBQZWFrcyIs + Imdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L21ldGVyL3BlYWsifSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYm + IHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDUvdm9sIiwibmFtZSI6IlRyNSBWb2wiLCJncm91cElkIjoidm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDUvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6Iigo + c2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3BhbiIsIm5hbWUiOiJUcjUgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXgg + fHwgMCkgKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3Bhbi9mZWVkYmFjayIsIm5hbWUiOiJUcjUgUGFuIEZl + ZWRiYWNrIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L2Jvb3N0LWN1 + dCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAh + PSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDQiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3Bhbi9yZXNldCIsIm5hbWUiOiJUcjUgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6InBhbi1yZXNldCIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0In0sIm1vZGUiOnsibWlu + VGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24i + OiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjUgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6 + InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g1L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxl + Y3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDQiLCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsTWl4ZXIiOnRydWUs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L211dGUiLCJuYW1lIjoiVHI1IE11dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2Vs + ZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3NvbG8iLCJuYW1lIjoi + VHI1IFNvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g1L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVz + c2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L2FybSIsIm5hbWUiOiJUcjUgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxl + Y3RlZF90cmFja19pbmRleCB8fCAwKSArIDQiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUvdG91Y2giLCJuYW1lIjoi + VHI1IFRvdWNoIiwiZ3JvdXBJZCI6InRvdWNoIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJl + c3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9uYW1lIiwibmFtZSI6IlRyNSBOYW1lIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMSJ9LCJtb2RlIjp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudHJh + Y2submFtZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo0NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxl + Y3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDQiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3Bhbi9sY2QiLCJuYW1lIjoiVHI1IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlv + biI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVz + c2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA0IiwiZnhBbmNob3IiOiJpZCIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9wZWFrIiwibmFtZSI6IlRyNSBQZWFrcyIsImdyb3VwSWQiOiJtZXRl + ciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L21ldGVyL3BlYWsifSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNr + X2luZGV4IHx8IDApICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYv + dm9sIiwibmFtZSI6IlRyNiBWb2wiLCJncm91cElkIjoidm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDYvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tf + aW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7Imlk + IjoiY2g2L3BhbiIsIm5hbWUiOiJUcjYgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g2L3YtcG90In0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5 + cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA1IiwiZnhB + bmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L3Bhbi9mZWVkYmFjayIsIm5hbWUiOiJUcjYgUGFuIEZlZWRiYWNrIiwiZ3JvdXBJ + ZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L2Jvb3N0LWN1dCJ9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxl + Y3RlZF90cmFja19pbmRleCB8fCAwKSArIDUiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2g2L3Bhbi9yZXNldCIsIm5hbWUiOiJUcjYgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6InBhbi1yZXNldCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAu + NSwibWF4VGFyZ2V0VmFsdWUiOjAuNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3Ry + YWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjYgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NlbGVjdCJ9LCJt + b2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRl + eCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDUiLCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9fSx7ImlkIjoiY2g2L211dGUiLCJuYW1lIjoiVHI2IE11dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5k + ZXggfHwgMCkgKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L3NvbG8iLCJuYW1lIjoiVHI2IFNvbG8iLCJncm91 + cElkIjoic29sbyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g2L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0 + ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2g2L2FybSIsIm5hbWUiOiJUcjYgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRl + eCB8fCAwKSArIDUiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvdG91Y2giLCJuYW1lIjoiVHI2IFRvdWNoIiwiZ3Jv + dXBJZCI6InRvdWNoIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDYvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVj + dGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6ImNoNi9uYW1lIiwibmFtZSI6IlRyNiBOYW1lIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g2L2xjZC9saW5lMSJ9LCJtb2RlIjp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZl + ZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo0NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRl + eCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDUiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Bhbi9sY2QiLCJuYW1lIjoiVHI2IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5w + YW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0 + ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA1IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9wZWFrIiwibmFtZSI6IlRyNiBQZWFrcyIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L21ldGVyL3BlYWsifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7InR5cGUiOjM0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsg + NSIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdm9sIiwibmFtZSI6IlRy + NyBWb2wiLCJncm91cElkIjoidm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIifSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkg + JiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA2IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3BhbiIsIm5h + bWUiOiJUcjcgUGFuIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90In0s + Im1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhw + cmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA2IiwiZnhBbmNob3IiOiJpZCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3Bhbi9mZWVkYmFjayIsIm5hbWUiOiJUcjcgUGFuIEZlZWRiYWNrIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L2Jvb3N0LWN1dCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRl + eCB8fCAwKSArIDYiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3Bhbi9y + ZXNldCIsIm5hbWUiOiJUcjcgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6InBhbi1yZXNldCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFs + dWUiOjAuNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5v + bmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjcgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIs + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBz + ZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDYiLCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7Imlk + IjoiY2g3L211dGUiLCJuYW1lIjoiVHI3IE11dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA2Iiwi + ZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3NvbG8iLCJuYW1lIjoiVHI3IFNvbG8iLCJncm91cElkIjoic29sbyIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NvbG8ifSwibW9k + ZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXgg + IT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA2IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3 + L2FybSIsIm5hbWUiOiJUcjcgQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + dHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDYiLCJm + eEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvdG91Y2giLCJuYW1lIjoiVHI3IFRvdWNoIiwiZ3JvdXBJZCI6InRvdWNoIiwi + c291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91 + Y2gifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4 + ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNo + Ny9uYW1lIiwibmFtZSI6IlRyNyBOYW1lIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g3L2xjZC9saW5lMSJ9LCJtb2RlIjp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51 + bGwsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo0NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBz + ZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDYiLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g3L3Bhbi9sY2QiLCJuYW1lIjoiVHI3IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVl + ZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXgg + IT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA2IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9wZWFrIiwibmFtZSI6IlRyNyBQZWFrcyIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L21ldGVyL3BlYWsifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0 + cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNiIsImZ4QW5jaG9yIjoi + aWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdm9sIiwibmFtZSI6IlRyOCBWb2wiLCJncm91cElk + Ijoidm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJh + Y2tfaW5kZXggfHwgMCkgKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3BhbiIsIm5hbWUiOiJUcjggUGFuIiwi + Z3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90In0sIm1vZGUiOnsibWluU3Rl + cFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2Vs + ZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiY2g4L3Bhbi9mZWVkYmFjayIsIm5hbWUiOiJUcjggUGFuIEZlZWRiYWNrIiwiZ3JvdXBJZCI6InBhbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L2Jvb3N0LWN1dCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + dHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDciLCJm + eEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Bhbi9yZXNldCIsIm5hbWUiOiJU + cjggUGFuIFJlc2V0IiwiZ3JvdXBJZCI6InBhbi1yZXNldCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0In0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNSwibWF4VGFyZ2V0VmFsdWUiOjAuNSwiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVk + X3RyYWNrX2luZGV4IHx8IDApICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g4L3NlbGVjdGlvbiIsIm5hbWUiOiJUcjggU2VsZWN0aW9uIiwiZ3JvdXBJZCI6InNlbGVjdGlvbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19p + bmRleCB8fCAwKSArIDciLCJmeEFuY2hvciI6ImlkIiwic2Nyb2xsTWl4ZXIiOnRydWUsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L211dGUiLCJu + YW1lIjoiVHI4IE11dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g4L211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNr + RXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2VsZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA3IiwiZnhBbmNob3IiOiJpZCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3NvbG8iLCJuYW1lIjoiVHI4IFNvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2Vs + ZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L2FybSIsIm5hbWUiOiJU + cjggQXJtIiwiZ3JvdXBJZCI6ImFybSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g4L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tF + eHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19pbmRleCB8fCAwKSArIDciLCJmeEFuY2hvciI6ImlkIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvdG91Y2giLCJuYW1lIjoiVHI4IFRvdWNoIiwiZ3JvdXBJZCI6InRvdWNoIiwic291cmNlIjp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNl + bGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9uYW1lIiwibmFtZSI6 + IlRyOCBOYW1lIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMSJ9 + LCJtb2RlIjp7ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQudHJhY2submFtZSB9fSIsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlw + ZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo0NCwidHJhY2tFeHByZXNzaW9uIjoiKChzZWxlY3RlZF90cmFja19pbmRleCAhPSBub25lKSAmJiBzZWxlY3RlZF90cmFja19p + bmRleCB8fCAwKSArIDciLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Bh + bi9sY2QiLCJuYW1lIjoiVHI4IFBhbiBMQ0QiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDgvbGNkL2xpbmUyIn0sIm1vZGUiOnsiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6bnVs + bCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6Iigoc2VsZWN0ZWRfdHJhY2tfaW5kZXggIT0gbm9uZSkgJiYgc2Vs + ZWN0ZWRfdHJhY2tfaW5kZXggfHwgMCkgKyA3IiwiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoOC9wZWFrIiwibmFtZSI6IlRyOCBQZWFrcyIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g4L21ldGVyL3BlYWsifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja0V4cHJlc3Npb24i + OiIoKHNlbGVjdGVkX3RyYWNrX2luZGV4ICE9IG5vbmUpICYmIHNlbGVjdGVkX3RyYWNrX2luZGV4IHx8IDApICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6InNuMXA2dVdrd3BXOU4zWVY2dTdleiIsImdy + b3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + RWhFV25Pbm9vY21GX1RGUXVsOTNjIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NjAsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2ci + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiSXJtcDYtNW1wM0ZGM1hjRWxINHJaIiwiZ3JvdXBJZCI6ImZh + ZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9m + YWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ0a2hSUHdFUHdkbllSMVBweVRWLUEiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1hcmtlciIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9fSx7ImlkIjoidmVCV3JsOUpzc3VMbWd2Ry1vcXhoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzR9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJvLVdORERvRFFDUG1IUVVFTUhvM3kiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJIaVppekkzMURyUXZ6c0dnWFRIbHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OH0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IkFBNDBOTGpLREduMFpXYTQ5UE1EeiIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6Inpwajg0dC1vSF9iM0lHQ2lEdVpGMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ2fSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJZN0VPNDRxaG40a09NR3I1YWQ5cTYiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6InFDREE2cmpUY2lOODlKQ3EzYUhJXyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkxfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoicmV3aW5kIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiItWElVR01vanhsN19RTU9URk1JcUciLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJDcmo5ZDZCVUl3UE5RN1BjZ0ozRjciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NH0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Ii15cXlMREFsMjBxejdKaGo3YUh2NyIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic3RvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiNGlyejdzcDVPbk5LRG42QmNFSTR1Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJyZWNvcmQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IlJVWWplRTlJbzUwWjh2Y21rWXUyZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjg2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + Im5QSnNXN1Z2aVhwaVVFMkJvZ0hwMCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6Inpvb20iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IkdGQTZSN2xwMWhGZG41TndmM3YwLSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjEwMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNjcnViIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI2RTlM + U1J4amtuTkVQR2lnUHNUcjciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImN1cnNvci1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJHVE94VkhmVU15N0JPdkpZcTVaYVAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo5OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7Imlk + IjoieVdPX2ltMzNDSzRFdnpELUVGRTFvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjdXJzb3ItdXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IlBZSDZ6XzlZNmw0Rk9KWlFxbXBwbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjk3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWRvd24iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6IlZKTGhCdUlSQmVPc3J6dDBGdndGTSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoibnVkZ2UiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IklSZnMtU0FwX3RNUElaejVDcnNadCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjg3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZHJvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Vml2aFNDNEVZWENaV3o5SFpIQXlYIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJyZXBsYWNlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJYY2RDcG5PUmVjT3FYZWZ1c2I4ajYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo4OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJrYTF2 + bVBpQnhHVlRmLTBNZElUdkoiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + InNvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InYzYkFHUmRDak9kSjNIOWtud3JEbCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjU0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjNOZ2RjaUl3YkNDdmsy + NURGT3B1VCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjhITFY2UVU5MHdZZU41dV9nUWZMciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU2fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjMiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IlFqV2ZtY1hCTHZVTmZZR3pGOWN5XyIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6InZ1Tkw3V1VPNWgzYWNpLW1xaG03ViIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU4fSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Ik8ydERCaFkzT3FCdE5HeWwxb0pYUCIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjYiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6Im82VkJsejVTQjdONzFudTBReHpINyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijoic21wdGUtYmVhdHMiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IkFUaEhwa3pSdUw4b2ZJNWFtN2dVSSIsImdyb3VwSWQiOiJsY2QiLCJz + b3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC9hc3NpZ25tZW50IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUUVWVnhYUWdoVHA3ZGptenU0VFZPIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxh + eVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQiOjJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJNVTFOM0ZSVlZJT2JaRGp1R2lzeWsiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjozMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiIyRHY2c2RhLTEyQlRSN2NJNU1PMFMiLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxMDR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiU0N4Qm5EX1A4SHdLSTFqRERzajFtIiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjI0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7Imlk + IjoiWHZ4VmFySkNvbG5mUTAwRWdYa19wIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjZYZFlOZ3FwT1ZyT3lKMGoyNWpoOSIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjE2LCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImJKU0VhNGZYeVIwVE1LZWt6Q2prSCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVy + biI6IkIwIDMwIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJIVEEwMEF3dTlWNlhhNTRWdWZHRGciLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJCMCAzMCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9ib29zdC1j + dXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJLR1N1UW9kVzVMZ0tQMWxZR0hJZFoiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gxL3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ikp4VnhLY2YxcFc2c1lyRWpvczRh + SCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4 + U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMS92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJYZm9oOVRn + eF9WN3NDb3hjRDJEcDMiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2fSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gxL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InRWemNtbW9CLUxEMkQ0TkRoNkVnMiIsImdyb3VwSWQiOiJzb2xv + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zb2xvIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJ4VlpLRzcxYVRzLVd3eVRKTGJOUzEiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6ImMyUFdjeElKOVBfamJ3YUxVellRUCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MH0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMS9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ1SER2MGx3OUFMVWxYZlhyTER6SjAi + LCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNnd4VzdIMzlvXzE1RE9wdU0xNWZRIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5 + cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNk11Y3JYU2Fld24yOXpwcDFqOEhoIiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MzN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJh + Y2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiRzNGSnEySjV4OUp3cTNNSTNKMllNIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MTA1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InVfWlV0b08yU1ZyLWdWdEpyRzJrZCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6IkR4MGh4ZENMU1c2X1EwNzA2alB1NCIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoxfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVy + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJQempPMC1YVEpXS0dZMk4ydXZKNFYiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxNywiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI1V09iQzFDR0s5SXpoZkR1Q3U2MjQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJCMCAzMSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiTU40YW9fcVVDRjhsZWVRcWxKTkFVIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qv + Ym9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMDRCd2xlTjdJbl81Z1lnR2VEdlJZIiwiZ3Jv + dXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VW + YWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMi92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ6S3puMWQ2Wm0wZjJ5 + TGNMUHZZVjkiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMSBkY2JhXSJ9LCJtb2Rl + Ijp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + OVRGMFZ2eGt3bTJvZE95TnZqXzhxIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxN30sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJUY2pTYkhvNnZpZ2ptUDlNQ2dUaUIiLCJncm91cElk + Ijoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiYWloMWZ4eVo4TjN4Qi1Ra0Vpc2dpIiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiJKR2tORnF1dFpnWk9oRGc4VTAycHkiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUi + OjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDIvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYmZLYnBheGozMmpscGZK + V051MGk2IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IkpPVFFrZTJEanVDWWlNd3gydjE2aSIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IkFybWJEUW9ZVE1pdjBzTFN4QXdhQyIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjM0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0s + ImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjVxekdXRm8tNEhJWjNPX3dwX19fdyIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ3QmpDRmxBV3oyeGRGWVhRcUdtemMiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiJzMVl4SXBHRnZLQ0Z5bHVrcVpEakIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Mn0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + My9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiYThranJpM2tFUEN2bWprTlNJMU9rIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6MTgsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUU5MR3BpTHNZQ05FNkoyMVRsOENLIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Il9QUVp4dG1IN05kTHNRa3k5Z1Q3NyIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5 + cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1 + LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gz + L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im9tQUhfTUlzV190Uy1XODQ5OEhr + VyIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4 + U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDMvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZ0huczVI + RkpiYnpXa2xlOWg0OGpJIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTEgZGNiYV0i + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6Im4xSlgtaDN5LXBQOWdrR3hEa0llbyIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTh9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic01aZTVMcnhseHpYT0pBVmlaVndyIiwi + Z3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + My9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIza3k5YjI3ZVZHVUJBLWx0UXl0YXQiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNPQ2FtNUlBekhPTzZuVVBjNFY2OSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6 + MiwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJqYndVaXcw + R3B4Mmd0RkpvTmZJMk0iLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiTWVsWG1kZ1VUaVBmVWVEU3JmSXVNIiwiZ3JvdXBJZCI6Im1ldGVy + Iiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiR2ltRE5Zdm5BMy15M0M4aWM1ZXFXIiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNzBNV182UF9YRUVleEtJeHJhdWtxIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InhGbmsxVFNya1c0T0pQTHpmdVFwQyIsImdyb3VwSWQiOiJzZWxlY3QiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyN30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zZWxlY3QiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImpIODBNR1lpV0R4NWFRaElaNTZDcCIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoz + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g0L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIyUFlRVE8tTFNwcHRydm4xckwyaUEiLCJncm91cElkIjoidi1wb3QiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJXeFZBUXB6MnRzSWZ0S0FpYjhRejciLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0 + eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90IiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoieEFlSUlDZDd3RGNCOE5tNzJnWFRyIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3Vy + Y2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFs + dWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDQvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiS0k2SUQ3bC1XVXVD + VkJwT05aa3MxIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDAgZGNiYV0ifSwibW9k + ZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJmTWZGLUZZWDdoNnkwd1hJZXNTLXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAx + MSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiaUdPSlEyS0xNNjRCWGU5U2h4SDJPIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjox + OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJnczBPdnFjS3BVb3Rqam51 + Y3JUajUiLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g0L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjVaVnpqdkRaMy10OThldGo1Y3M0NiIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHki + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZEFWOGlnRHRrN1Z3RC00ekJ5VTl0IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlz + cGxheUlkIjozLCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + IkZKSXdXN29YOG9XdE1DcF9YWjV0QyIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MywibGluZSI6MX0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2Qv + bGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ0MnJxSGI3a3FXOTVDS1YxTG5HNnMiLCJncm91cElk + IjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI1S0xBTGgxYWxQNzB1SktFaFJ3M1QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXNlbGVjdCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJwb1R5emh6TVVWai15SUFRUWZ6QnYiLCJncm91cElkIjoiZmFkZXItdG91Y2gi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIvdG91Y2gi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYlA2azRLYmd6dVhlM1RteFFSWkluIiwiZ3JvdXBJZCI6InNl + bGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NlbGVjdCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoibkVsZHIzUzVSSHNLNW9TYTBuUS1aIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNo + YW5uZWwiOjR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDUvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InR6OHRlczdsM3N1OWUxRkJUYkdfWCIsImdyb3VwSWQiOiJ2LXBv + dCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90IiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IklfUzcwS1NEaHZmV19nRFZqTzgzMiIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJaSGxtNEs0ZGRPM1Q2ZzZlMGhWNzIiLCJncm91cElkIjoidi1wb3QtbGVk + cyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhT + b3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI1S2xS + RWpZU0tiUnNBaEROM3FXRjMiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAwMCBkY2Jh + XSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6IlRIWGVzdWhxQ0NjU1lpc0VuRlhqMiIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIw + IDM0IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJQVWlkd0xaWlY4WllUaHFkTExwM0MiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjIwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IkVLUVA4cllk + a1hzRHRiNC12Y2FXWSIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDUvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoidy05TE0zakEzMDc0aGZnWVRSRXJKIiwiZ3JvdXBJZCI6InJlY29y + ZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvcmVjb3Jk + LXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJFeEQtSUx1RDNPR2dNXzNkdzRLemwiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUi + OjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiUl8wUElMalhHQXNFdEpUeXo1WGpZIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo0LCJsaW5lIjoxfSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g1L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlJDZWc5ZFlPa1EzMGJNNjlyakEwRCIs + Imdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEwMCBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbWV0ZXIvcGVhayIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjY4bHNNOUpyQUdBdjZMTFRtOTZFZiIsImdyb3VwSWQiOiJ2LXNlbGVj + dCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0Iiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjZ5SnFTUk9CYjVRSV9DUmY2T21CbiIsImdyb3VwSWQiOiJmYWRl + ci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRl + ci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ3MUZvQXFsc1pVTWdFTEhMMUxnd08iLCJncm91 + cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYv + c2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmb2g5TXFmbmR0eFRSRERUWGNBUWIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlw + ZSI6MywiY2hhbm5lbCI6NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZHFBNXlISXM2bVo5MVdXMklyMk5lIiwiZ3JvdXBJ + ZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjEsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUVo0WFN2Sk9yUTB4ZHZNbGdlQVV2IiwiZ3JvdXBJZCI6InYtcG90LWxl + ZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBv + dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlF0Ung0UnhpeHFoYkpQQWdvT3gxdyIsImdyb3VwSWQiOiJ2 + LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAu + MDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImhoanNybkhYbHFBN2xXaUJTY3NSTiIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFsw + MDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiWXF0OWNIOUVoN1lyZ2k0NWJhS0Z3IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0 + ZXJuIjoiQjAgMzUgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlBoMW5fMlR0X2UtZUx5SmxhcGdKcyIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + bkJibElZaWxwNVdNY2sxeVJETklnIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJKZlh6dGtPWTFzNE5BaFhadlFpVF8iLCJncm91cElk + IjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Ni9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Ik5uY3A3RDVBbWViUGZBV2NaNGhxXyIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2Ui + OnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NSwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJEZ1E3SHFhYnlJd3JzRDBLYllkVEoiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUi + OjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDYvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoieHp6RThuUDQ5RHZOZm51 + R1kwZjVkIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTAxIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tZXRlci9w + ZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYzE5Y1lLS2pQRm1uSmVpbGxLdTlrIiwiZ3JvdXBJZCI6 + InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1z + ZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiSWZiLUdlME00OGhsTUE3SnZqd1dIIiwiZ3JvdXBJ + ZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g3L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IllxUXhaSlB4ZnhjeWZvX2NuVjJf + WSIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoNy9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Imc5Ni0wc05Xd1EyckQ4d3dCTkFUbCIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJj + ZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJsSng5QzlwN2hYT1Ztd0tuQ3JPdHUi + LCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMiwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBv + dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJRWjRLVDRlNUJkN013R0RWSldnNDgiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g3L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiVnFXTFYybW80cHVZcGdFZUNfdG1jIiwiZ3Jv + dXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VW + YWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiTVZNS2REUTREWGhqY1lKSF9RZVlDIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoi + QjAgMzYgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJVWnZ3UDhCV3N3NmxuakRZLXcyVUQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3 + TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODUyU3g2bHhEU2FIWmNWUVJrNEloIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJZUUY1RWVvZmRqZnhZMXB3RnN5LWwiLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjFkUHhCZmhZOUYzSnVYblZuRTNwXyIs + Imdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiWjlVLTk1OEVqYlNjRE4yTjBVa29EIiwiZ3JvdXBJZCI6ImxjZCIs + InNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo2LCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IklLd0VIZ0R5SVBIc1h0RXlqVF80TyIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6 + NiwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoNy9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJYN1JGWTJ3 + ZGF4ZDVDWHczTXRSMFoiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTAgZGNiYV0ifSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3 + L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJzUE5nMmZ4d0RuLW1DdXFSX3NMeV8iLCJn + cm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoOC92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ4bEVqYVp4ZUVjbld4VU5fallWWWki + LCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDgvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMUlNTjhMb2NCbENM + X0VCRENEZjFlIiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g4L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiakMzSXQ5SlBDU1RpTE9PdGtGTEd0IiwiZ3JvdXBJZCI6ImZhZGVy + Iiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Ink1aDRUbjBiSVZGQmZE + SDA5ZEJ3dCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g4L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImFHRU9KV1lPVHFBZHBRUGJVVndkWiIsImdy + b3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNl + VmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDgvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJsQm1fQWxhV1dMVlY5TGZhMkxH + MVEiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1p + blNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJUWmRMSlFycVNrVVllNVN1QnUtOXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBh + dHRlcm4iOiJCMCAzNyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IkdSRlZSWnJXSXloc2JaMVJKeDAyWCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUi + OjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjN1ZMNVZwWWNnME5yUkliTGxBaXMiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L211dGUiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6ImhYSW1CN21MTHByZDZTbUVnVTE4cyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + MTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiVmFjS3lmUFl3MWlvTXBX + bFE0OXgyIiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjd9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI5dk1FTXBiNGFFXzBGM3R3cklWNlciLCJncm91cElk + IjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidnJfNXFqQlRUcWdnQko1MWlpdVlSIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlz + cGxheUlkIjo3LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + Im5jaUFwMnlkSUxjaS0tQm1xc3VtLSIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDExMSBkY2JhXSJ9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDgvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX1dLCJwYXJhbWV0ZXJzIjp7IjIiOnsibmFt + ZSI6Ilpvb20gb24vb2ZmIn0sIjEiOnsibmFtZSI6IlNlZWsgY29hcnNlL2ZpbmUifX19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1460 281 1436 1514 + FXID {54A34603-ACA3-48E5-85A3-39B643437F0F} + WAK 0 0 + > + + > + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-448-display-colors.rpp b/plugin-reaper-realearn/resources/test-projects/issue-448-display-colors.rpp new file mode 100644 index 0000000..798f968 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-448-display-colors.rpp @@ -0,0 +1,489 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 5 9 + SELECTION2 5 9 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.11521806013581 -0.112 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + MARKER 1 1 blue 1 33488896 1 R {5E150CF6-2054-482F-B9FB-171FC8E8682A} + MARKER 1 3.5 "" 1 33488896 1 R + MARKER 2 5 White 1 33554431 1 R {1752E908-F1B5-4568-BDF2-30F9753523C6} + MARKER 2 9 "" 1 33554431 1 R + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAyXIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS44IiwiaWQiOiJnbE1nam11cyIsImNvbnRyb2xEZXZpY2VJZCI6IjExIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjEyIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1 + MGQ0YzUzIiwibmFtZSI6IkZhZGVycyJ9LHsiaWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJuYW1lIjoiRW5jb2RlcnMifSx7ImlkIjoi + ODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwibmFtZSI6IkVuY29kZXIgcHVzaCJ9LHsiaWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVl + MTM3NDFkNDMiLCJuYW1lIjoiRmFkZXIgdG91Y2gifSx7ImlkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwibmFtZSI6IlNlbGVjdCJ9LHsi + aWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJuYW1lIjoiTXV0ZSJ9LHsiaWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4 + N2RjNTgiLCJuYW1lIjoiU29sbyJ9LHsiaWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJuYW1lIjoiUmVjb3JkIn1dLCJtYXBwaW5ncyI6 + W3siaWQiOiIzOWRiYTAyZi04YzcxLTRkNjctYjIwOS1iZWRmMzIzZGNjNGEiLCJuYW1lIjoiU2VsZWN0ZWQgdHJhY2sgdm9sdW1lIiwic291cmNlIjp7InR5cGUiOjEy + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheVR5cGUiOiJzaW5pY29uLWUyNCIsImRpc3BsYXlJZCI6MCwibGluZSI6MCwib3NjQXJn + SW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6IkEi + LCJmZWVkYmFja0NvbG9yIjp7InByb3AiOiJ0YXJnZXQudHJhY2suY29sb3IifSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6 + InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7 + ImlkIjoiYmY0MzhmODEtNGU0Yy00YzZhLThlNzYtM2MyZmE2ZWM4NTdmIiwibmFtZSI6IkJsdWUgcmVnaW9uIiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheVR5cGUiOiJzaW5pY29uLWUyNCIsImRpc3BsYXlJZCI6MCwibGluZSI6MSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6IkIiLCJmZWVkYmFja0Nv + bG9yIjp7InByb3AiOiJ0YXJnZXQuYm9va21hcmsuY29sb3IifSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjIyLCJ0cmFja0dVSUQiOiJzZWxlY3Rl + ZCIsImZ4QW5jaG9yIjoiaWQiLCJib29rbWFya1JlZiI6MSwiYm9va21hcmtJc1JlZ2lvbiI6dHJ1ZSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNl + ZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJmYjY0Zjg1NS1jNzBiLTRjNjYtODJmNi05NzRhYzcwMTI2YzAiLCJuYW1lIjoiV2hpdGUgcmVnaW9u + Iiwic291cmNlIjp7InR5cGUiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheVR5cGUiOiJzaW5pY29uLWUyNCIsImRpc3BsYXlJ + ZCI6MCwibGluZSI6Miwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsRmVlZGJh + Y2tUcmFuc2Zvcm1hdGlvbiI6IlciLCJmZWVkYmFja0NvbG9yIjp7InByb3AiOiJ0YXJnZXQuYm9va21hcmsuY29sb3IifSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0 + Ijp7InR5cGUiOjIyLCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJib29rbWFya1JlZiI6MiwiYm9va21hcmtJc1JlZ2lvbiI6dHJ1ZSwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6ImUz + ODk3ZTQ5LWE4ZjItNGM1Zi1hYzU1LWE0NjRkYzBiZDlhNSIsIm5hbWUiOiJDaDEgUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJi + YTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QifX0seyJpZCI6Ijg3ZmM2ZWEyLWU4N2QtNDZmZS1iYmZlLTBlZGM3NGEyYTU3MyIsIm5hbWUiOiJDaDIg + UHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoz + MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxlY3QifX0seyJpZCI6IjNk + MWFiM2NlLTE0MGQtNGZmYy1hYTI2LTFhNTYzMGQ2MDBjZiIsIm5hbWUiOiJDaDMgUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJi + YTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1zZWxlY3QifX0seyJpZCI6IjdlOTk2MDY5LWZhYzctNGQyNC1hYmZhLTgzNGVkNzA1N2MzMCIsIm5hbWUiOiJDaDQg + UHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoz + NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QifX0seyJpZCI6ImE0 + MTU0ZTkxLTA4ZjMtNDBhYi1hZDdiLTZiOTkwZTQ0ZDI4MyIsIm5hbWUiOiJDaDUgUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJi + YTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1zZWxlY3QifX0seyJpZCI6ImFmNWExOWRmLWYxZmYtNDI0MS1hYmU2LTFiMjk2OWY2MGUxOCIsIm5hbWUiOiJDaDYg + UHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoz + NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QifX0seyJpZCI6ImI5 + YjUyMzI4LTcxZWQtNDgyYS1hMWJhLTFmOWI4YWNjYWIzZSIsIm5hbWUiOiJDaDcgUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJi + YTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QifX0seyJpZCI6IjFhZjNiNjExLWNlZDgtNDIwOC05MDlhLTY0YjczZTZhNTE5YiIsIm5hbWUiOiJDaDgg + UHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoz + OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1zZWxlY3QifX0seyJpZCI6IjMw + MTdjMGU0LWEwMzktNDA4ZC05YWM1LWUzYjQxMTdiODk2ZCIsIm5hbWUiOiJDaDEgVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEz + NzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3VjaCJ9fSx7ImlkIjoiMzE3NzI4ZjUtNWU0NC00MTU3LTkxODYtNTA5OTgyMzYyMTBkIiwibmFtZSI6 + IkNoMiBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjoxMDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIn19 + LHsiaWQiOiIyOWE3M2Q0YS05MGYzLTQyYzAtYjViYS03ZTMzOTBiODA5ZDMiLCJuYW1lIjoiQ2gzIFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05 + MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2gifX0seyJpZCI6IjBkMTNmOGJlLTljZGQtNDEwNS05ZTYyLTRmNzg1YjM5NWI3 + ZiIsIm5hbWUiOiJDaDQgVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTA3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRl + ci90b3VjaCJ9fSx7ImlkIjoiMThlNmU1ZDMtNTEwOS00YTUyLThmNGItYjNmOTQ1NGUzMDE5IiwibmFtZSI6IkNoNSBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1m + OTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIn19LHsiaWQiOiJkNTViYWY3Yi1mZGJhLTRjNGUtYmYxYi0x + MWU4NDc0NjJiNTUiLCJuYW1lIjoiQ2g2IFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDYvZmFkZXIvdG91Y2gifX0seyJpZCI6ImJkNTY0OWFjLWQxZjAtNGZmOS1hNmEyLTQ2MDc1ZjY1ZDM5YyIsIm5hbWUiOiJDaDcgVG91Y2giLCJncm91cElkIjoi + Yzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEwLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlci90b3VjaCJ9fSx7ImlkIjoiMDJlMDViMDItOTdjMS00 + ZWYzLWE1YzEtNjgyMWIzYTNhMjU0IiwibmFtZSI6IkNoOCBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIn19LHsiaWQiOiJjNDU5ZjM3YS02YzhhLTQ0ODMtYjg0MC03NmZiYWIyOGE1NDAiLCJuYW1lIjoiQ2gxIFNlbCIsImdy + b3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0In19LHsiaWQiOiJhYTZhYTVmZi0zY2Fj + LTQ2MGItOGE1Zi03NThiNmI5NmZiZjciLCJuYW1lIjoiQ2gyIFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDIvc2VsZWN0In19LHsiaWQiOiI4YzMyZWQxNC1lYjQwLTQ0MGItYTc1YS0zZjhmMjA0YTliOTIiLCJuYW1lIjoiQ2gzIFNlbCIsImdyb3VwSWQi + OiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0In19LHsiaWQiOiI4Nzk2MmVjMi1lNmI1LTRhYjkt + OGQyOC00MmViNTg3NGI5YzkiLCJuYW1lIjoiQ2g0IFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDQvc2VsZWN0In19LHsiaWQiOiJkMGE2NmZiYi03ZTlmLTQxYjQtYWRhNS01NDJjZGNkMmE1Y2UiLCJuYW1lIjoiQ2g1IFNlbCIsImdyb3VwSWQiOiJlZGRm + ZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOCwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0In19LHsiaWQiOiIzZmY0YTUzZS0yZmQ5LTRjNDUtOTRjOS0w + Yzc4MjgzMTVhYTEiLCJuYW1lIjoiQ2g2IFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDYvc2VsZWN0In19LHsiaWQiOiIwMmYzMjRmYy1jMGQyLTRkNDEtYjJjNC0wYzBkODliYjFhZTkiLCJuYW1lIjoiQ2g3IFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03 + MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2VsZWN0In19LHsiaWQiOiIzMGFiNWU4NS04NTMyLTQzNjctODA2ZS1lMTA0ZTM1 + NWY0ZWEiLCJuYW1lIjoiQ2g4IFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjozMSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc2Vs + ZWN0In19LHsiaWQiOiJiOTZiMTFiMy00YTMxLTQ4ZWMtOGMzMC1mYWYxNjA2NmJjMzkiLCJuYW1lIjoiTWFzdGVyIEZhZGVyIiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdk + YjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + Im1haW4vZmFkZXIifX0seyJpZCI6ImZiZjg0NDIwLTdkOTUtNDJjZC05ZDZiLTdiOWM2ZGMzMTM2MiIsIm5hbWUiOiJKb2cgV2hlZWwiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImpvZyJ9fSx7ImlkIjoiMTc1YmVhYTktODE0 + OC00ZWVhLWIxODEtMzg3ZjBiNjNhMTkzIiwibmFtZSI6Ik1hc3RlciBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCJ9fSx7ImlkIjoiZTUxNTg1ZGUtMWMzMy00ODQwLTkxOWQtYmJmYjY1Y2RhMjQzIiwibmFtZSI6Ik1peGVy + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoibWFya2VyIn19LHsiaWQiOiIzZWU0MDhkYS0wZjIwLTQ5ODAtOWI1My1lN2U2MjY0MmZkM2QiLCJuYW1lIjoiUmVhZCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6InJlYWQifX0seyJpZCI6IjU0NTRmYzU0LTFjMjktNDZhNC05MDc2LTgyMGZjOWMwMTgzMSIsIm5hbWUiOiJXcml0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjc1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRl + In19LHsiaWQiOiJhZTEzNjQ5My1kNDU2LTQ4ZDAtOTAwNS1mNjNhMTVlMDNkZmEiLCJuYW1lIjoiUHJldiBDaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQifX0seyJp + ZCI6IjMzZjgxOThjLTIwNDktNGRiYy1hNGYzLTExOTJkMzZlYzMyZiIsIm5hbWUiOiJOZXh0IENoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6NDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmlnaHQifX0seyJpZCI6Ijdi + MjU5Zjc2LWIwMWUtNDZhNC1iZmFiLTY4YzUzNmFiY2Q1MCIsIm5hbWUiOiJQcmV2IEJhbmsiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0 + NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLWxlZnQifX0seyJpZCI6ImU2ZGE5 + YzczLTdmYTgtNDUwZS04MzVmLTQwMzQ0MzUxNzJiYiIsIm5hbWUiOiJOZXh0IEJhbmsiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0Nywi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0In19LHsiaWQiOiIzNDE3ODBj + My01OTM0LTRhMjQtOWY1My0xNWQ0N2IzYzBiY2YiLCJuYW1lIjoiVHJhbnMgbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkxLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJld2luZCJ9fSx7ImlkIjoiZjZiYWZmMWEtYzFj + YS00MDc5LWEwNTgtMWRmMmM3NjBhNjBkIiwibmFtZSI6IlRyYW5zIHJpZ2h0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTIsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZmFzdC1md2QifX0seyJpZCI6IjdhM2ZhNDdkLTlhNWYt + NGI2ZS1iOTRhLWU3YTgyNDUyNGY5MSIsIm5hbWUiOiJQbGF5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTQsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicGxheSJ9fSx7ImlkIjoiYzZkNmU0N2QtMzY1OC00ZDY2LTg2ZWMtMzEw + NmY2NzdmMzUwIiwibmFtZSI6IlN0b3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzdG9wIn19LHsiaWQiOiI3MWQ0MDcwMS1jZjA5LTQxZTUtYmY1Yy1kYTM0ZjcwYzY2ZGMiLCJu + YW1lIjoiUmVjb3JkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVjb3JkIn19LHsiaWQiOiJiYWJhNGRjZi02NTQ3LTQ2ODAtYmFiOS0zZjFjZTc0ZWQxZTEiLCJuYW1lIjoiQ3lj + bGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjeWNsZSJ9fSx7ImlkIjoiZWUzNGM3NjUtOTdiOS00N2MxLWIwYWMtODk2MzU3MTMwMTM3IiwibmFtZSI6Ilpvb20iLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijoiem9vbSJ9fSx7ImlkIjoiYTAwOWE2M2QtZWJjZC00ZGRjLTk0ZGMtN2ZiZTJjYjczNmM5IiwibmFtZSI6IkpvZyBQdXNoIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + InNjcnViIn19LHsiaWQiOiI2OGM5MjRmZS1lY2QzLTRhOGMtYTNiMC0yMWY5MTE2NzUyOTAiLCJuYW1lIjoiWm9vbSBvdXQgaG9yaXoiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + dXJzb3ItbGVmdCJ9fSx7ImlkIjoiNDkzNzQyMGMtNWFhZS00Yzc3LWJiMDktY2E1MTg2NTc2OWFlIiwibmFtZSI6Ilpvb20gaW4gaG9yaXoiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjdXJzb3ItcmlnaHQifX0seyJpZCI6IjZjMzgzNDIwLTZjMzUtNDg0OS04YTRkLTFhYjVlZDQ5YTk0NiIsIm5hbWUiOiJab29tIG91dCB2ZXJ0Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY3Vyc29yLXVwIn19LHsiaWQiOiI5Yzk4MWU2MS1mMzAwLTQzOGMtOWEyZC00YjE5YjBmNDRlZTkiLCJuYW1lIjoiWm9vbSBpbiB2ZXJ0Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY3Vyc29yLWRvd24ifX0seyJpZCI6IjdhZTdjMzlkLThmOWQtNGFhOC04NDViLWQ5NzdiY2IzZjkzYSIsIm5hbWUiOiJDaDEgRmFkZXIiLCJncm91cElkIjoi + NzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gxL2ZhZGVyIn19LHsiaWQiOiIzMDQ3NDljMi1hNjQ0LTRkZDUtOWRmMC02M2I5MjFiNDkyNWIiLCJuYW1lIjoiQ2gyIEZhZGVyIiwiZ3JvdXBJ + ZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciJ9fSx7ImlkIjoiMjIwZjc1NDAtYzU0MS00NjUzLThiZTMtODM3ZWFjODRkYmE3IiwibmFtZSI6IkNoMyBGYWRlciIsImdy + b3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIifX0seyJpZCI6ImNiZWU3ZGFlLTA5NDEtNDYwMi1iMjc5LTMzNjFiNmIwMDU0MSIsIm5hbWUiOiJDaDQgRmFkZXIi + LCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjMsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyIn19LHsiaWQiOiJhZjhhOTEyZi1jNDExLTRjZGItODFmOC01NGVjOWI5MmE0YjgiLCJuYW1lIjoiQ2g1IEZh + ZGVyIiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo0LCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciJ9fSx7ImlkIjoiZDY2MDQyOGQtNjcwMi00ZTYzLTg4YjgtMDcyNjdlMjI5ZTkyIiwibmFtZSI6IkNo + NiBGYWRlciIsImdyb3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NSwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIifX0seyJpZCI6ImY4NDEzZTkyLWEzYzgtNDcwOS1iZmJkLWVlMTkyY2IyMmJjMyIsIm5hbWUi + OiJDaDcgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjYsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIn19LHsiaWQiOiI2NmI5NjA2NC03YzJjLTQyMzEtOWMwMC0zOWM1OTZmYjM1NmMiLCJu + YW1lIjoiQ2g4IEZhZGVyIiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo3 + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlciJ9fSx7ImlkIjoiZjY2ZDFlN2QtMDQ5Mi00MDcxLThkNjYtMjc5YzM4M2Q1Yzk1 + IiwibmFtZSI6IkNoMSBFbmMiLCJncm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MTYsImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QifX0seyJpZCI6IjExNWJjODg0LWJlMjYtNDVk + ZS1hOTAxLTNiY2JmMTQ1OWJlYSIsIm5hbWUiOiJDaDIgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90In19LHsiaWQi + OiIxZTAyM2Q4YS1lZDFkLTQwNDctYTNjYi1lNjZiOGI1OGNiNDUiLCJuYW1lIjoiQ2gzIEVuYyIsImdyb3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVj + NjMxZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMy92LXBvdCJ9fSx7ImlkIjoiMWEzZDA0OWUtMTRmYS00YzI4LWI5NTMtMDJhYjU1ZjNmOTFmIiwibmFtZSI6IkNoNCBFbmMiLCJncm91cElkIjoiOTdkOGRjODAt + MGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTksImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QifX0seyJpZCI6ImEzYTIwZjM3LWM5ZmEtNGI4Ni1iMDFmLTM3OWIyMjk1N2MwOCIsIm5hbWUiOiJDaDUgRW5jIiwi + Z3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90In19LHsiaWQiOiI2ZDZiOTkxOC1lMWFjLTRmYTctYjZjMC0yMDVmYWFiODQ1MzYi + LCJuYW1lIjoiQ2g2IEVuYyIsImdyb3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + IjoyMSwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCJ9fSx7ImlkIjoiYWE2ZjczN2ItZjA3OS00ZTBm + LWFlNDYtNmE5NzkzNTQwYTI4IiwibmFtZSI6IkNoNyBFbmMiLCJncm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MjIsImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QifX0seyJpZCI6 + IjhlZjA3MTk5LWNmYmEtNDdiMi05NmI0LTIzNzk0MTY3NmM3NSIsIm5hbWUiOiJDaDggRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2 + MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g4L3YtcG90In19LHsiaWQiOiI5ZjdmODMxMi0wNTU2LTRmODEtOWYwNy1lOTYzZWM3NjY5OWYiLCJuYW1lIjoiQ2gxIE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgt + ZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUifX0seyJpZCI6IjIwNGFjYjcwLTJjZGItNGJmMC04NmNiLWMwMWZkMzc2 + MGZiMiIsIm5hbWUiOiJDaDIgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjoxNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbXV0 + ZSJ9fSx7ImlkIjoiZjlmNjJjM2MtYTIyNC00ZjQwLWJiYTEtMmYxZWZjZmM4ZjE1IiwibmFtZSI6IkNoMyBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhm + Yi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tdXRlIn19LHsiaWQiOiI1MmI5YmM5YS1jZGM5LTRlYzUtOTQ5Ny1iNDFkZTMyYTYzYTUiLCJu + YW1lIjoiQ2g0IE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUifX0seyJp + ZCI6ImZlZmY4NWNiLTc4ZDYtNDUxOS04NDVmLWNiNDNhZGQ1MGQ0YSIsIm5hbWUiOiJDaDUgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0z + MzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSJ9fSx7ImlkIjoiODE1N2Y4ZDUtODJmYi00MjkxLWFjOGEtMmQ3Mjc1MjEzYzAzIiwibmFtZSI6IkNo + NiBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjIxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tdXRlIn19LHsiaWQiOiJjNTQ1 + NzE5Mi00MmI3LTQ3M2ItYmE3NS02YjY3NTkxNTdmNTEiLCJuYW1lIjoiQ2g3IE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4 + MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUifX0seyJpZCI6Ijg2Y2ExYmY1LTBmMzktNGI2MS05YzRjLTg3NTliOWE4ODU4NyIsIm5hbWUiOiJDaDggTXV0ZSIs + Imdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMywiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbXV0ZSJ9fSx7ImlkIjoiMmYzNTFhNTUtMTRl + NS00Njk2LTkxMjgtNGI2NmMxNjhmZDZiIiwibmFtZSI6IkNoMSBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gxL3NvbG8ifX0seyJpZCI6ImRjZjY4MjRlLTc1OWMtNGM2ZS04MjE1LWM1MzU3MzQzYzE5ZiIsIm5hbWUiOiJDaDIgU29sbyIsImdyb3VwSWQi + OiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIn19LHsiaWQiOiIzMjlhMTlkMC05YTUyLTQyYzgtOGU0 + OC0wNjkyZThkZDRlZWIiLCJuYW1lIjoiQ2gzIFNvbG8iLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gzL3NvbG8ifX0seyJpZCI6ImFhNzY3NzZlLTEwNzgtNDdjYS1iMjMwLTMwNzhhYTJjMTc1YSIsIm5hbWUiOiJDaDQgU29sbyIsImdyb3VwSWQiOiI3OThhZTMz + Mi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc29sbyJ9fSx7ImlkIjoiNDM0NjFmYWEtNWY3MS00YjFjLWE5ZWItZjk4Yzhj + ZmIyNTRmIiwibmFtZSI6IkNoNSBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9z + b2xvIn19LHsiaWQiOiIxNTI3Y2RkOC0yMThhLTQwY2EtOTc2Mi0zNTU3ZTA0ZTgzNmYiLCJuYW1lIjoiQ2g2IFNvbG8iLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00 + ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NvbG8ifX0seyJpZCI6IjEyZDk2YjhjLWE4YmQtNGVhYy1iMzdlLWI0MjhjMTQ3NmFjNyIs + Im5hbWUiOiJDaDcgU29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc29sbyJ9fSx7 + ImlkIjoiODYxZTI3Y2YtYmIyMS00NTVmLWIwZDUtYjM2ZjMyYTk0ZDgzIiwibmFtZSI6IkNoOCBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2 + LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIn19LHsiaWQiOiIzMThjZDViOC1lNDJkLTQ1NjgtODlkZC0xMzdhNzFjZjIxNmIiLCJuYW1lIjoi + Q2gxIFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkifX0seyJp + ZCI6ImYxOTU4NTVkLTljZjUtNGMxNi1hOWU5LTBiY2Q3MTQzNDNlMyIsIm5hbWUiOiJDaDIgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIy + MTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3JlY29yZC1yZWFkeSJ9fSx7ImlkIjoiOWZkZmVhY2UtZDVhNi00ZmVlLWJkMmYtYWUxMzRhMmVhZDM3IiwibmFt + ZSI6IkNoMyBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvcmVjb3JkLXJlYWR5In19 + LHsiaWQiOiJhYjlhNmYwNi1kZTMwLTQ0ZGQtYmE5Ny02ODU5Y2Q3Nzc1OGQiLCJuYW1lIjoiQ2g0IFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNk + NS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImZkOTlkODJjLWRkZDYtNGI3Zi1iNDQ4LWY3NWY3ZmFmNmE5MCIs + Im5hbWUiOiJDaDUgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3JlY29yZC1yZWFk + eSJ9fSx7ImlkIjoiMjA3NzRmMTEtNzQ0Ni00MmU4LWEyNDQtYzlkYWQ5OTkxMDJiIiwibmFtZSI6IkNoNiBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2 + LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvcmVjb3JkLXJlYWR5In19LHsiaWQiOiI3MTk2NDlmYS03NjZhLTQ2YjYtYWI5Yy1mZjFmNzEwNTU1 + M2IiLCJuYW1lIjoiQ2g3IFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9yZWNvcmQt + cmVhZHkifX0seyJpZCI6ImZkNjkwOTg2LTdjM2EtNGNlMS05NjdhLTgwYjNlNzMyODI4MCIsIm5hbWUiOiJDaDggUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMt + NGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3JlY29yZC1yZWFkeSJ9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6ImJlaHJpbmdlci14LXRv + dWNoLW9uZSJ9 + AAAQAAAA + > + FLOATPOS 24 52 721 792 + FXID {4D875CBE-BE59-4F24-8CD8-12E082686DB3} + WAK 0 0 + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-448-display-duplicate-feedback.rpp b/plugin-reaper-realearn/resources/test-projects/issue-448-display-duplicate-feedback.rpp new file mode 100644 index 0000000..89405d9 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-448-display-duplicate-feedback.rpp @@ -0,0 +1,430 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.11521806013581 -0.112 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA7XAAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS44IiwiaWQiOiJnbE1nam11cyIsImNvbnRyb2xEZXZpY2VJZCI6IjExIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjEyIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1 + MGQ0YzUzIiwibmFtZSI6IkZhZGVycyJ9LHsiaWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJuYW1lIjoiRW5jb2RlcnMifSx7ImlkIjoi + ODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwibmFtZSI6IkVuY29kZXIgcHVzaCJ9LHsiaWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVl + MTM3NDFkNDMiLCJuYW1lIjoiRmFkZXIgdG91Y2gifSx7ImlkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwibmFtZSI6IlNlbGVjdCJ9LHsi + aWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJuYW1lIjoiTXV0ZSJ9LHsiaWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4 + N2RjNTgiLCJuYW1lIjoiU29sbyJ9LHsiaWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJuYW1lIjoiUmVjb3JkIn1dLCJtYXBwaW5ncyI6 + W3siaWQiOiIwNWY2YzM5Mi0wYzIyLTRjNTEtYjc5Ni03NDI1ZGYyNzUwZWYiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6 + MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJiMGM0NWQ0Zi1hN2M1LTQyODUtOGM1Ny02OTJmMmJhMDIwYzEiLCJuYW1lIjoiMSIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJqb2cifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiIzOWRiYTAyZi04YzcxLTRkNjctYjIw + OS1iZWRmMzIzZGNjNGEiLCJuYW1lIjoiIDEiLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQi + OjAsImxpbmUiOjAsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImVlbEZlZWRiYWNr + VHJhbnNmb3JtYXRpb24iOiJIZXkiLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQi + OiJlMzg5N2U0OS1hOGYyLTRjNWYtYWM1NS1hNDY0ZGMwYmQ5YTUiLCJuYW1lIjoiQ2gxIFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdi + OGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0In19LHsiaWQiOiI4N2ZjNmVhMi1lODdkLTQ2ZmUtYmJmZS0wZWRjNzRhMmE1NzMiLCJuYW1lIjoi + Q2gyIFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MzMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0In19LHsiaWQi + OiIzZDFhYjNjZS0xNDBkLTRmZmMtYWEyNi0xYTU2MzBkNjAwY2YiLCJuYW1lIjoiQ2gzIFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdi + OGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0In19LHsiaWQiOiI3ZTk5NjA2OS1mYWM3LTRkMjQtYWJmYS04MzRlZDcwNTdjMzAiLCJuYW1lIjoi + Q2g0IFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MzUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3Ytc2VsZWN0In19LHsiaWQi + OiJhNDE1NGU5MS0wOGYzLTQwYWItYWQ3Yi02Yjk5MGU0NGQyODMiLCJuYW1lIjoiQ2g1IFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdi + OGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0In19LHsiaWQiOiJhZjVhMTlkZi1mMWZmLTQyNDEtYWJlNi0xYjI5NjlmNjBlMTgiLCJuYW1lIjoi + Q2g2IFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MzcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0In19LHsiaWQi + OiJiOWI1MjMyOC03MWVkLTQ4MmEtYTFiYS0xZjliOGFjY2FiM2UiLCJuYW1lIjoiQ2g3IFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdi + OGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3Ytc2VsZWN0In19LHsiaWQiOiIxYWYzYjYxMS1jZWQ4LTQyMDgtOTA5YS02NGI3M2U2YTUxOWIiLCJuYW1lIjoi + Q2g4IFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MzksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0In19LHsiaWQi + OiIzMDE3YzBlNC1hMDM5LTQwOGQtOWFjNS1lM2I0MTE3Yjg5NmQiLCJuYW1lIjoiQ2gxIFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQz + NWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2gifX0seyJpZCI6IjMxNzcyOGY1LTVlNDQtNDE1Ny05MTg2LTUwOTk4MjM2MjEwZCIsIm5h + bWUiOiJDaDIgVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTA1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlci90b3Vj + aCJ9fSx7ImlkIjoiMjlhNzNkNGEtOTBmMy00MmMwLWI1YmEtN2UzMzkwYjgwOWQzIiwibmFtZSI6IkNoMyBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQx + NmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIn19LHsiaWQiOiIwZDEzZjhiZS05Y2RkLTQxMDUtOWU2Mi00Zjc4NWIz + OTViN2YiLCJuYW1lIjoiQ2g0IFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjEwNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQv + ZmFkZXIvdG91Y2gifX0seyJpZCI6IjE4ZTZlNWQzLTUxMDktNGE1Mi04ZjRiLWIzZjk0NTRlMzAxOSIsIm5hbWUiOiJDaDUgVG91Y2giLCJncm91cElkIjoiYzk5OWRl + YzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA4LCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlci90b3VjaCJ9fSx7ImlkIjoiZDU1YmFmN2ItZmRiYS00YzRlLWJm + MWItMTFlODQ3NDYyYjU1IiwibmFtZSI6IkNoNiBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIn19LHsiaWQiOiJiZDU2NDlhYy1kMWYwLTRmZjktYTZhMi00NjA3NWY2NWQzOWMiLCJuYW1lIjoiQ2g3IFRvdWNoIiwiZ3JvdXBJ + ZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMCwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2gifX0seyJpZCI6IjAyZTA1YjAyLTk3 + YzEtNGVmMy1hNWMxLTY4MjFiM2EzYTI1NCIsIm5hbWUiOiJDaDggVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTExLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlci90b3VjaCJ9fSx7ImlkIjoiYzQ1OWYzN2EtNmM4YS00NDgzLWI4NDAtNzZmYmFiMjhhNTQwIiwibmFtZSI6IkNoMSBTZWwi + LCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjQsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NlbGVjdCJ9fSx7ImlkIjoiYWE2YWE1ZmYt + M2NhYy00NjBiLThhNWYtNzU4YjZiOTZmYmY3IiwibmFtZSI6IkNoMiBTZWwiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gyL3NlbGVjdCJ9fSx7ImlkIjoiOGMzMmVkMTQtZWI0MC00NDBiLWE3NWEtM2Y4ZjIwNGE5YjkyIiwibmFtZSI6IkNoMyBTZWwiLCJncm91 + cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjYsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NlbGVjdCJ9fSx7ImlkIjoiODc5NjJlYzItZTZiNS00 + YWI5LThkMjgtNDJlYjU4NzRiOWM5IiwibmFtZSI6IkNoNCBTZWwiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g0L3NlbGVjdCJ9fSx7ImlkIjoiZDBhNjZmYmItN2U5Zi00MWI0LWFkYTUtNTQyY2RjZDJhNWNlIiwibmFtZSI6IkNoNSBTZWwiLCJncm91cElkIjoi + ZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjgsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NlbGVjdCJ9fSx7ImlkIjoiM2ZmNGE1M2UtMmZkOS00YzQ1LTk0 + YzktMGM3ODI4MzE1YWExIiwibmFtZSI6IkNoNiBTZWwiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g2L3NlbGVjdCJ9fSx7ImlkIjoiMDJmMzI0ZmMtYzBkMi00ZDQxLWIyYzQtMGMwZDg5YmIxYWU5IiwibmFtZSI6IkNoNyBTZWwiLCJncm91cElkIjoiZWRkZmYz + ZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzAsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCJ9fSx7ImlkIjoiMzBhYjVlODUtODUzMi00MzY3LTgwNmUtZTEw + NGUzNTVmNGVhIiwibmFtZSI6IkNoOCBTZWwiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MzEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4 + L3NlbGVjdCJ9fSx7ImlkIjoiYjk2YjExYjMtNGEzMS00OGVjLThjMzAtZmFmMTYwNjZiYzM5IiwibmFtZSI6Ik1hc3RlciBGYWRlciIsImdyb3VwSWQiOiI3OTE0ZmI0 + OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJtYWluL2ZhZGVyIn19LHsiaWQiOiJmYmY4NDQyMC03ZDk1LTQyY2QtOWQ2Yi03YjljNmRjMzEzNjIiLCJuYW1lIjoiSm9nIFdoZWVsIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6NjAsImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifX0seyJpZCI6IjE3NWJlYWE5 + LTgxNDgtNGVlYS1iMTgxLTM4N2YwYjYzYTE5MyIsIm5hbWUiOiJNYXN0ZXIgVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQx + ZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIvdG91Y2gifX0seyJpZCI6ImU1MTU4NWRlLTFjMzMtNDg0MC05MTlkLWJiZmI2NWNkYTI0MyIsIm5hbWUiOiJN + aXhlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6Im1hcmtlciJ9fSx7ImlkIjoiM2VlNDA4ZGEtMGYyMC00OTgwLTliNTMtZTdlNjI2NDJmZDNkIiwibmFtZSI6IlJlYWQiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJyZWFkIn19LHsiaWQiOiI1NDU0ZmM1NC0xYzI5LTQ2YTQtOTA3Ni04MjBmYzljMDE4MzEiLCJuYW1lIjoiV3JpdGUiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ3 + cml0ZSJ9fSx7ImlkIjoiYWUxMzY0OTMtZDQ1Ni00OGQwLTkwMDUtZjYzYTE1ZTAzZGZhIiwibmFtZSI6IlByZXYgQ2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0In19 + LHsiaWQiOiIzM2Y4MTk4Yy0yMDQ5LTRkYmMtYTRmMy0xMTkyZDM2ZWMzMmYiLCJuYW1lIjoiTmV4dCBDaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjQ5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0In19LHsiaWQi + OiI3YjI1OWY3Ni1iMDFlLTQ2YTQtYmZhYi02OGM1MzZhYmNkNTAiLCJuYW1lIjoiUHJldiBCYW5rIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6NDYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0In19LHsiaWQiOiJl + NmRhOWM3My03ZmE4LTQ1MGUtODM1Zi00MDM0NDM1MTcyYmIiLCJuYW1lIjoiTmV4dCBCYW5rIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + NDcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1yaWdodCJ9fSx7ImlkIjoiMzQx + NzgwYzMtNTkzNC00YTI0LTlmNTMtMTVkNDdiM2MwYmNmIiwibmFtZSI6IlRyYW5zIGxlZnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5 + MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQifX0seyJpZCI6ImY2YmFmZjFh + LWMxY2EtNDA3OS1hMDU4LTFkZjJjNzYwYTYwZCIsIm5hbWUiOiJUcmFucyByaWdodCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkyLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIn19LHsiaWQiOiI3YTNmYTQ3ZC05 + YTVmLTRiNmUtYjk0YS1lN2E4MjQ1MjRmOTEiLCJuYW1lIjoiUGxheSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk0LCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkifX0seyJpZCI6ImM2ZDZlNDdkLTM2NTgtNGQ2Ni04NmVj + LTMxMDZmNjc3ZjM1MCIsIm5hbWUiOiJTdG9wIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic3RvcCJ9fSx7ImlkIjoiNzFkNDA3MDEtY2YwOS00MWU1LWJmNWMtZGEzNGY3MGM2NmRj + IiwibmFtZSI6IlJlY29yZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCJ9fSx7ImlkIjoiYmFiYTRkY2YtNjU0Ny00NjgwLWJhYjktM2YxY2U3NGVkMWUxIiwibmFtZSI6 + IkN5Y2xlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUifX0seyJpZCI6ImVlMzRjNzY1LTk3YjktNDdjMS1iMGFjLTg5NjM1NzEzMDEzNyIsIm5hbWUiOiJab29tIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6Inpvb20ifX0seyJpZCI6ImEwMDlhNjNkLWViY2QtNGRkYy05NGRjLTdmYmUyY2I3MzZjOSIsIm5hbWUiOiJKb2cgUHVzaCIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJzY3J1YiJ9fSx7ImlkIjoiNjhjOTI0ZmUtZWNkMy00YThjLWEzYjAtMjFmOTExNjc1MjkwIiwibmFtZSI6Ilpvb20gb3V0IGhvcml6Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY3Vyc29yLWxlZnQifX0seyJpZCI6IjQ5Mzc0MjBjLTVhYWUtNGM3Ny1iYjA5LWNhNTE4NjU3NjlhZSIsIm5hbWUiOiJab29tIGluIGhvcml6Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY3Vyc29yLXJpZ2h0In19LHsiaWQiOiI2YzM4MzQyMC02YzM1LTQ4NDktOGE0ZC0xYWI1ZWQ0OWE5NDYiLCJuYW1lIjoiWm9vbSBvdXQgdmVydCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImN1cnNvci11cCJ9fSx7ImlkIjoiOWM5ODFlNjEtZjMwMC00MzhjLTlhMmQtNGIxOWIwZjQ0ZWU5IiwibmFtZSI6Ilpvb20gaW4gdmVydCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImN1cnNvci1kb3duIn19LHsiaWQiOiI3YWU3YzM5ZC04ZjlkLTRhYTgtODQ1Yi1kOTc3YmNiM2Y5M2EiLCJuYW1lIjoiQ2gxIEZhZGVyIiwiZ3JvdXBJ + ZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciJ9fSx7ImlkIjoiMzA0NzQ5YzItYTY0NC00ZGQ1LTlkZjAtNjNiOTIxYjQ5MjViIiwibmFtZSI6IkNoMiBGYWRlciIsImdy + b3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MSwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIifX0seyJpZCI6IjIyMGY3NTQwLWM1NDEtNDY1My04YmUzLTgzN2VhYzg0ZGJhNyIsIm5hbWUiOiJDaDMgRmFkZXIi + LCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyIn19LHsiaWQiOiJjYmVlN2RhZS0wOTQxLTQ2MDItYjI3OS0zMzYxYjZiMDA1NDEiLCJuYW1lIjoiQ2g0IEZh + ZGVyIiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjozLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlciJ9fSx7ImlkIjoiYWY4YTkxMmYtYzQxMS00Y2RiLTgxZjgtNTRlYzliOTJhNGI4IiwibmFtZSI6IkNo + NSBGYWRlciIsImdyb3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NCwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIifX0seyJpZCI6ImQ2NjA0MjhkLTY3MDItNGU2My04OGI4LTA3MjY3ZTIyOWU5MiIsIm5hbWUi + OiJDaDYgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjUsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIn19LHsiaWQiOiJmODQxM2U5Mi1hM2M4LTQ3MDktYmZiZC1lZTE5MmNiMjJiYzMiLCJu + YW1lIjoiQ2g3IEZhZGVyIiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo2 + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciJ9fSx7ImlkIjoiNjZiOTYwNjQtN2MyYy00MjMxLTljMDAtMzljNTk2ZmIzNTZj + IiwibmFtZSI6IkNoOCBGYWRlciIsImdyb3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5l + bCI6NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIifX0seyJpZCI6ImY2NmQxZTdkLTA0OTItNDA3MS04ZDY2LTI3OWMzODNk + NWM5NSIsIm5hbWUiOiJDaDEgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjE2LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90In19LHsiaWQiOiIxMTViYzg4NC1iZTI2 + LTQ1ZGUtYTkwMS0zYmNiZjE0NTliZWEiLCJuYW1lIjoiQ2gyIEVuYyIsImdyb3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNywiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdCJ9fSx7 + ImlkIjoiMWUwMjNkOGEtZWQxZC00MDQ3LWEzY2ItZTY2YjhiNThjYjQ1IiwibmFtZSI6IkNoMyBFbmMiLCJncm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjct + ZWZlYzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTgsImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDMvdi1wb3QifX0seyJpZCI6IjFhM2QwNDllLTE0ZmEtNGMyOC1iOTUzLTAyYWI1NWYzZjkxZiIsIm5hbWUiOiJDaDQgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhk + YzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90In19LHsiaWQiOiJhM2EyMGYzNy1jOWZhLTRiODYtYjAxZi0zNzliMjI5NTdjMDgiLCJuYW1lIjoiQ2g1IEVu + YyIsImdyb3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiY2hhcmFjdGVy + IjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCJ9fSx7ImlkIjoiNmQ2Yjk5MTgtZTFhYy00ZmE3LWI2YzAtMjA1ZmFhYjg0 + NTM2IiwibmFtZSI6IkNoNiBFbmMiLCJncm91cElkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MjEsImNoYXJhY3RlciI6MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QifX0seyJpZCI6ImFhNmY3MzdiLWYwNzkt + NGUwZi1hZTQ2LTZhOTc5MzU0MGEyOCIsIm5hbWUiOiJDaDcgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90In19LHsi + aWQiOiI4ZWYwNzE5OS1jZmJhLTQ3YjItOTZiNC0yMzc5NDE2NzZjNzUiLCJuYW1lIjoiQ2g4IEVuYyIsImdyb3VwSWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1l + ZmVjNjMxZWM2YzAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMywiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoOC92LXBvdCJ9fSx7ImlkIjoiOWY3ZjgzMTItMDU1Ni00ZjgxLTlmMDctZTk2M2VjNzY2OTlmIiwibmFtZSI6IkNoMSBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVj + ZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIn19LHsiaWQiOiIyMDRhY2I3MC0yY2RiLTRiZjAtODZjYi1jMDFm + ZDM3NjBmYjIiLCJuYW1lIjoiQ2gyIE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gy + L211dGUifX0seyJpZCI6ImY5ZjYyYzNjLWEyMjQtNGY0MC1iYmExLTJmMWVmY2ZjOGYxNSIsIm5hbWUiOiJDaDMgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3 + LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbXV0ZSJ9fSx7ImlkIjoiNTJiOWJjOWEtY2RjOS00ZWM1LTk0OTctYjQxZGUzMmE2M2E1 + IiwibmFtZSI6IkNoNCBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjE5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIn19 + LHsiaWQiOiJmZWZmODVjYi03OGQ2LTQ1MTktODQ1Zi1jYjQzYWRkNTBkNGEiLCJuYW1lIjoiQ2g1IE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1 + ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L211dGUifX0seyJpZCI6IjgxNTdmOGQ1LTgyZmItNDI5MS1hYzhhLTJkNzI3NTIxM2MwMyIsIm5hbWUi + OiJDaDYgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjoyMSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbXV0ZSJ9fSx7ImlkIjoi + YzU0NTcxOTItNDJiNy00NzNiLWJhNzUtNmI2NzU5MTU3ZjUxIiwibmFtZSI6IkNoNyBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYx + ZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tdXRlIn19LHsiaWQiOiI4NmNhMWJmNS0wZjM5LTRiNjEtOWM0Yy04NzU5YjlhODg1ODciLCJuYW1lIjoiQ2g4IE11 + dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjMs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L211dGUifX0seyJpZCI6IjJmMzUxYTU1 + LTE0ZTUtNDY5Ni05MTI4LTRiNjZjMTY4ZmQ2YiIsIm5hbWUiOiJDaDEgU29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS9zb2xvIn19LHsiaWQiOiJkY2Y2ODI0ZS03NTljLTRjNmUtODIxNS1jNTM1NzM0M2MxOWYiLCJuYW1lIjoiQ2gyIFNvbG8iLCJncm91 + cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OSwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyJ9fSx7ImlkIjoiMzI5YTE5ZDAtOWE1Mi00MmM4 + LThlNDgtMDY5MmU4ZGQ0ZWViIiwibmFtZSI6IkNoMyBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMy9zb2xvIn19LHsiaWQiOiJhYTc2Nzc2ZS0xMDc4LTQ3Y2EtYjIzMC0zMDc4YWEyYzE3NWEiLCJuYW1lIjoiQ2g0IFNvbG8iLCJncm91cElkIjoiNzk4 + YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8ifX0seyJpZCI6IjQzNDYxZmFhLTVmNzEtNGIxYy1hOWViLWY5 + OGM4Y2ZiMjU0ZiIsIm5hbWUiOiJDaDUgU29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDUvc29sbyJ9fSx7ImlkIjoiMTUyN2NkZDgtMjE4YS00MGNhLTk3NjItMzU1N2UwNGU4MzZmIiwibmFtZSI6IkNoNiBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4 + MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zb2xvIn19LHsiaWQiOiIxMmQ5NmI4Yy1hOGJkLTRlYWMtYjM3ZS1iNDI4YzE0NzZh + YzciLCJuYW1lIjoiQ2g3IFNvbG8iLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NvbG8i + fX0seyJpZCI6Ijg2MWUyN2NmLWJiMjEtNDU1Zi1iMGQ1LWIzNmYzMmE5NGQ4MyIsIm5hbWUiOiJDaDggU29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUt + OTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyJ9fSx7ImlkIjoiMzE4Y2Q1YjgtZTQyZC00NTY4LTg5ZGQtMTM3YTcxY2YyMTZiIiwibmFt + ZSI6IkNoMSBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvcmVjb3JkLXJlYWR5In19 + LHsiaWQiOiJmMTk1ODU1ZC05Y2Y1LTRjMTYtYTllOS0wYmNkNzE0MzQzZTMiLCJuYW1lIjoiQ2gyIFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNk + NS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9yZWNvcmQtcmVhZHkifX0seyJpZCI6IjlmZGZlYWNlLWQ1YTYtNGZlZS1iZDJmLWFlMTM0YTJlYWQzNyIs + Im5hbWUiOiJDaDMgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFk + eSJ9fSx7ImlkIjoiYWI5YTZmMDYtZGUzMC00NGRkLWJhOTctNjg1OWNkNzc3NThkIiwibmFtZSI6IkNoNCBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2 + LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvcmVjb3JkLXJlYWR5In19LHsiaWQiOiJmZDk5ZDgyYy1kZGQ2LTRiN2YtYjQ0OC1mNzVmN2ZhZjZh + OTAiLCJuYW1lIjoiQ2g1IFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQt + cmVhZHkifX0seyJpZCI6IjIwNzc0ZjExLTc0NDYtNDJlOC1hMjQ0LWM5ZGFkOTk5MTAyYiIsIm5hbWUiOiJDaDYgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMt + NGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3JlY29yZC1yZWFkeSJ9fSx7ImlkIjoiNzE5NjQ5ZmEtNzY2YS00NmI2LWFiOWMtZmYxZjcx + MDU1NTNiIiwibmFtZSI6IkNoNyBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvcmVj + b3JkLXJlYWR5In19LHsiaWQiOiJmZDY5MDk4Ni03YzNhLTRjZTEtOTY3YS04MGIzZTczMjgyODAiLCJuYW1lIjoiQ2g4IFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0y + NWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9yZWNvcmQtcmVhZHkifX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJiZWhyaW5nZXIt + eC10b3VjaC1vbmUifQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {4D875CBE-BE59-4F24-8CD8-12E082686DB3} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-448-display-superior-test.rpp b/plugin-reaper-realearn/resources/test-projects/issue-448-display-superior-test.rpp new file mode 100644 index 0000000..714dcac --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-448-display-superior-test.rpp @@ -0,0 +1,642 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 17 19.5 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.69677491433128 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAtYAAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS45IiwiaWQiOiJnVzhPZGpLUyIsImNvbnRyb2xEZXZpY2VJZCI6IjExIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjEyIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6IjVkZTNkZjc5LTI3Y2UtNDJlNi1hYjI3LWMxYzcyMzQ2MzU4YSIsIm5hbWUiOiJQYWdlcyJ9LHsiaWQiOiJhY2NiMjlj + NC00OTE0LTRkYWUtOTA5NS0wNjI4Nzc3ZWY5NzQiLCJuYW1lIjoiQ29udHJvbHMifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyR3JvdXBz + IjpbeyJpZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsIm5hbWUiOiJGYWRlcnMifSx7ImlkIjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjct + ZWZlYzYzMWVjNmMwIiwibmFtZSI6IkVuY29kZXJzIn0seyJpZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsIm5hbWUiOiJFbmNvZGVyIHB1 + c2gifSx7ImlkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwibmFtZSI6IkZhZGVyIHRvdWNoIn0seyJpZCI6ImVkZGZmM2Y2LTczMWEtNDNh + MC1iYmRiLTQ2NWVhNmNmMzJmYiIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwibmFtZSI6Ik11dGUi + fSx7ImlkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4IiwibmFtZSI6IlNvbG8ifSx7ImlkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIx + NmRlN2MzYmFkIiwibmFtZSI6IlJlY29yZCJ9XSwibWFwcGluZ3MiOlt7ImlkIjoiZWQyNWY2M2UtMWU1NC00MDZmLThkNGYtZTFkN2Y2YzBjOWMyIiwibmFtZSI6IkRp + c3BsYXkgc291cmNlIiwic291cmNlIjp7InR5cGUiOjEyLCJjaGFubmVsIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjow + LCJsaW5lIjowLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRh + cmdldCI6eyJ0eXBlIjoyMywidHJhY2tHVUlEIjoiNzJDMEE3NjgtNDgyNy00MzBDLUJBOTctQjIwMzNFMjAwRjdDIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3Qi + OnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwiZmVlZGJhY2tSZXNvbHV0aW9uIjoiaGlnaCIsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbImFzZCJd + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIwZmViNTMxZS0wZDIwLTRkOGItYmFhOS02MjQwZGZiNWZhZDkiLCJuYW1lIjoiT25lIiwiZ3JvdXBJZCI6 + IjVkZTNkZjc5LTI3Y2UtNDJlNi1hYjI3LWMxYzcyMzQ2MzU4YSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjg0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJtYXJrZXIifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbCwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1v + bmx5IiwiZ3JvdXBJbnRlcmFjdGlvbiI6ImludmVyc2UtdGFyZ2V0LXZhbHVlIn0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJDMjM3NTgyQy1DRDA0LTQ1 + MzEtQTlENC02RUZGQTc4REE5M0QiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MH19LHsiaWQiOiI1ZjcyZDk5Ni1jY2YzLTRkZjEtODYzZS1mY2RjMzc3NWZkNDMiLCJuYW1lIjoiVHdvIiwiZ3JvdXBJZCI6IjVkZTNkZjc5LTI3Y2UtNDJl + Ni1hYjI3LWMxYzcyMzQ2MzU4YSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsLCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHki + LCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10YXJnZXQtdmFsdWUifSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6IjNFQUMxQzAwLTBBNEItNDBBRi04 + NTk4LTE2MzRBN0I1OUJDRSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowfX0seyJpZCI6ImY2OTQxYjQ1LWI1NzQtNDIyZi05NWQyLWE1NjZlNGNjMmNkZSIsIm5hbWUiOiJUaHJlZSIsImdyb3VwSWQiOiI1ZGUzZGY3OS0yN2NlLTQyZTYt + YWIyNy1jMWM3MjM0NjM1OGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3lj + bGUifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbCwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5IiwiZ3JvdXBJbnRlcmFjdGlv + biI6ImludmVyc2UtdGFyZ2V0LXZhbHVlIn0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJCQ0MzN0Y1MS1BQjQ3LTQyNzAtOEZEMi0yOTI4QUZDM0FGODgi + LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI3MDA2 + YjcxMi1kMTE0LTQ2MjgtYjIzMC05ZTg2Zjk0ZjBmZDkiLCJuYW1lIjoiUGFnZSBjeWNsZSIsImdyb3VwSWQiOiJhY2NiMjljNC00OTE0LTRkYWUtOTA5NS0wNjI4Nzc3 + ZWY5NzQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImV4Y2x1c2l2aXR5IjoxLCJncm91cElkIjoiNWRlM2RmNzktMjdjZS00MmU2LWFiMjctYzFjNzIzNDYzNThhIn0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjBlYzU5ODRlLWNjYWItNDU2Ny05YTVhLTRiNGYwOWVmOTMwNSIsIm5hbWUiOiJQYWdlIGN5Y2xlIGZlZWRiYWNrIiwiZ3Jv + dXBJZCI6ImFjY2IyOWM0LTQ5MTQtNGRhZS05MDk1LTA2Mjg3NzdlZjk3NCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFj + dGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjowLCJsaW5lIjoxLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImpvZyJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlw + ZSI6MzcsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJncm91cElk + IjoiNWRlM2RmNzktMjdjZS00MmU2LWFiMjctYzFjNzIzNDYzNThhIn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQi + OiJlMzg5N2U0OS1hOGYyLTRjNWYtYWM1NS1hNDY0ZGMwYmQ5YTUiLCJuYW1lIjoiQ2gxIFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdi + OGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QifX0seyJpZCI6Ijg3ZmM2ZWEyLWU4N2QtNDZmZS1iYmZlLTBl + ZGM3NGEyYTU3MyIsIm5hbWUiOiJDaDIgUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXNlbGVjdCJ9fSx7ImlkIjoiM2QxYWIzY2UtMTQwZC00ZmZjLWFhMjYtMWE1NjMwZDYwMGNmIiwibmFtZSI6IkNoMyBQdXNo + IiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM0LCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2Vs + ZWN0In19LHsiaWQiOiI3ZTk5NjA2OS1mYWM3LTRkMjQtYWJmYS04MzRlZDcwNTdjMzAiLCJuYW1lIjoiQ2g0IFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00 + MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QifX0seyJpZCI6ImE0MTU0ZTkxLTA4ZjMt + NDBhYi1hZDdiLTZiOTkwZTQ0ZDI4MyIsIm5hbWUiOiJDaDUgUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXNlbGVjdCJ9fSx7ImlkIjoiYWY1YTE5ZGYtZjFmZi00MjQxLWFiZTYtMWIyOTY5ZjYwZTE4IiwibmFt + ZSI6IkNoNiBQdXNoIiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjM3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g2L3Ytc2VsZWN0In19LHsiaWQiOiJiOWI1MjMyOC03MWVkLTQ4MmEtYTFiYS0xZjliOGFjY2FiM2UiLCJuYW1lIjoiQ2g3IFB1c2giLCJncm91cElkIjoiODFj + MzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzgsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QifX0seyJpZCI6IjFh + ZjNiNjExLWNlZDgtNDIwOC05MDlhLTY0YjczZTZhNTE5YiIsIm5hbWUiOiJDaDggUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJi + YTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXNlbGVjdCJ9fSx7ImlkIjoiMzAxN2MwZTQtYTAzOS00MDhkLTlhYzUtZTNiNDEx + N2I4OTZkIiwibmFtZSI6IkNoMSBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxMDQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2gifX0seyJpZCI6IjMxNzcyOGY1LTVlNDQtNDE1Ny05MTg2LTUwOTk4MjM2MjEwZCIsIm5hbWUiOiJDaDIgVG91 + Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA1 + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2Zh + ZGVyL3RvdWNoIn19LHsiaWQiOiIyOWE3M2Q0YS05MGYzLTQyYzAtYjViYS03ZTMzOTBiODA5ZDMiLCJuYW1lIjoiQ2gzIFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMy + LWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlci90b3VjaCJ9fSx7ImlkIjoiMGQx + M2Y4YmUtOWNkZC00MTA1LTllNjItNGY3ODViMzk1YjdmIiwibmFtZSI6IkNoNCBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3 + NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91Y2gifX0seyJpZCI6IjE4ZTZlNWQzLTUxMDktNGE1Mi04ZjRiLWIz + Zjk0NTRlMzAxOSIsIm5hbWUiOiJDaDUgVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIn19LHsiaWQiOiJkNTViYWY3Yi1mZGJhLTRjNGUtYmYxYi0xMWU4NDc0NjJiNTUiLCJuYW1lIjoiQ2g2 + IFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjEwOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Ni9mYWRlci90b3VjaCJ9fSx7ImlkIjoiYmQ1NjQ5YWMtZDFmMC00ZmY5LWE2YTItNDYwNzVmNjVkMzljIiwibmFtZSI6IkNoNyBUb3VjaCIsImdyb3VwSWQiOiJjOTk5 + ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTAsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2gifX0seyJpZCI6 + IjAyZTA1YjAyLTk3YzEtNGVmMy1hNWMxLTY4MjFiM2EzYTI1NCIsIm5hbWUiOiJDaDggVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1 + ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTExLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIn19LHsiaWQiOiJjNDU5ZjM3YS02YzhhLTQ0ODMtYjg0 + MC03NmZiYWIyOGE1NDAiLCJuYW1lIjoiQ2gxIFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zZWxlY3QifX0seyJpZCI6ImFhNmFhNWZmLTNjYWMtNDYwYi04YTVmLTc1OGI2Yjk2ZmJmNyIsIm5hbWUiOiJDaDIgU2Vs + IiwiZ3JvdXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI1LCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NlbGVj + dCJ9fSx7ImlkIjoiOGMzMmVkMTQtZWI0MC00NDBiLWE3NWEtM2Y4ZjIwNGE5YjkyIiwibmFtZSI6IkNoMyBTZWwiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2Ew + LWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0In19LHsiaWQiOiI4Nzk2MmVjMi1lNmI1LTRhYjkt + OGQyOC00MmViNTg3NGI5YzkiLCJuYW1lIjoiQ2g0IFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zZWxlY3QifX0seyJpZCI6ImQwYTY2ZmJiLTdlOWYtNDFiNC1hZGE1LTU0MmNkY2QyYTVjZSIsIm5hbWUiOiJDaDUg + U2VsIiwiZ3JvdXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI4 + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Nl + bGVjdCJ9fSx7ImlkIjoiM2ZmNGE1M2UtMmZkOS00YzQ1LTk0YzktMGM3ODI4MzE1YWExIiwibmFtZSI6IkNoNiBTZWwiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00 + M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0In19LHsiaWQiOiIwMmYzMjRmYy1jMGQyLTRk + NDEtYjJjNC0wYzBkODliYjFhZTkiLCJuYW1lIjoiQ2g3IFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zZWxlY3QifX0seyJpZCI6IjMwYWI1ZTg1LTg1MzItNDM2Ny04MDZlLWUxMDRlMzU1ZjRlYSIsIm5hbWUiOiJD + aDggU2VsIiwiZ3JvdXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjMxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4 + L3NlbGVjdCJ9fSx7ImlkIjoiYjk2YjExYjMtNGEzMS00OGVjLThjMzAtZmFmMTYwNjZiYzM5IiwibmFtZSI6Ik1hc3RlciBGYWRlciIsImdyb3VwSWQiOiI3OTE0ZmI0 + OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIifX0seyJpZCI6ImZiZjg0NDIwLTdkOTUtNDJjZC05ZDZiLTdiOWM2ZGMzMTM2MiIsIm5hbWUiOiJKb2cg + V2hlZWwiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudEluZGV4Ijoiam9nIn19LHsiaWQiOiIxNzViZWFhOS04MTQ4LTRlZWEtYjE4MS0zODdmMGI2M2ExOTMiLCJuYW1lIjoiTWFzdGVyIFRvdWNoIiwiZ3Jv + dXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIvdG91 + Y2gifX0seyJpZCI6ImU1MTU4NWRlLTFjMzMtNDg0MC05MTlkLWJiZmI2NWNkYTI0MyIsIm5hbWUiOiJNaXhlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjg0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoibWFya2VyIn19LHsiaWQiOiIzZWU0MDhkYS0wZjIwLTQ5ODAtOWI1My1lN2U2MjY0MmZkM2QiLCJuYW1lIjoiUmVhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjc0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoicmVhZCJ9fSx7ImlkIjoiNTQ1NGZjNTQtMWMyOS00NmE0LTkwNzYtODIwZmM5YzAxODMxIiwibmFtZSI6IldyaXRlIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ3cml0ZSJ9fSx7ImlkIjoiYWUxMzY0OTMtZDQ1Ni00OGQwLTkwMDUtZjYzYTE1ZTAzZGZhIiwibmFtZSI6IlByZXYgQ2giLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQifX0seyJpZCI6IjMzZjgxOThjLTIwNDktNGRiYy1hNGYzLTExOTJkMzZlYzMyZiIsIm5hbWUi + OiJOZXh0IENoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCJ9fSx7ImlkIjoiN2IyNTlmNzYtYjAxZS00NmE0LWJmYWItNjhjNTM2 + YWJjZDUwIiwibmFtZSI6IlByZXYgQmFuayIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0In19LHsiaWQiOiJlNmRhOWM3My03ZmE4 + LTQ1MGUtODM1Zi00MDM0NDM1MTcyYmIiLCJuYW1lIjoiTmV4dCBCYW5rIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDcsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0In19LHsi + aWQiOiIzNDE3ODBjMy01OTM0LTRhMjQtOWY1My0xNWQ0N2IzYzBiY2YiLCJuYW1lIjoiVHJhbnMgbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjkxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoicmV3aW5kIn19LHsiaWQiOiJmNmJhZmYxYS1jMWNhLTQwNzktYTA1OC0xZGYyYzc2MGE2MGQiLCJuYW1lIjoiVHJhbnMgcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIn19LHsiaWQiOiI3YTNmYTQ3ZC05YTVmLTRiNmUtYjk0YS1lN2E4MjQ1MjRmOTEiLCJuYW1lIjoiUGxheSIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicGxheSJ9fSx7ImlkIjoiYzZkNmU0N2QtMzY1OC00ZDY2LTg2ZWMtMzEwNmY2NzdmMzUwIiwibmFtZSI6IlN0b3Ai + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AifX0seyJpZCI6IjcxZDQwNzAxLWNmMDktNDFlNS1iZjVjLWRhMzRmNzBjNjZkYyIsIm5hbWUi + OiJSZWNvcmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCJ9fSx7ImlkIjoiYmFiYTRkY2YtNjU0Ny00NjgwLWJhYjktM2YxY2U3NGVk + MWUxIiwibmFtZSI6IkN5Y2xlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjeWNsZSJ9fSx7ImlkIjoiZWUzNGM3NjUtOTdiOS00N2MxLWIwYWMt + ODk2MzU3MTMwMTM3IiwibmFtZSI6Ilpvb20iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIn19LHsiaWQiOiJhMDA5YTYzZC1lYmNkLTRk + ZGMtOTRkYy03ZmJlMmNiNzM2YzkiLCJuYW1lIjoiSm9nIFB1c2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDEsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiJ9fSx7ImlkIjoiNjhj + OTI0ZmUtZWNkMy00YThjLWEzYjAtMjFmOTExNjc1MjkwIiwibmFtZSI6Ilpvb20gb3V0IGhvcml6Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6OTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + dXJzb3ItbGVmdCJ9fSx7ImlkIjoiNDkzNzQyMGMtNWFhZS00Yzc3LWJiMDktY2E1MTg2NTc2OWFlIiwibmFtZSI6Ilpvb20gaW4gaG9yaXoiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1yaWdodCJ9fSx7ImlkIjoiNmMzODM0MjAtNmMzNS00ODQ5LThhNGQtMWFiNWVkNDlhOTQ2IiwibmFtZSI6Ilpvb20g + b3V0IHZlcnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci11cCJ9fSx7ImlkIjoiOWM5ODFlNjEtZjMwMC00MzhjLTlhMmQtNGIxOWIw + ZjQ0ZWU5IiwibmFtZSI6Ilpvb20gaW4gdmVydCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWRvd24ifX0seyJpZCI6IjdhZTdjMzlk + LThmOWQtNGFhOC04NDViLWQ5NzdiY2IzZjkzYSIsIm5hbWUiOiJDaDEgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUz + Iiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEv + ZmFkZXIifX0seyJpZCI6IjMwNDc0OWMyLWE2NDQtNGRkNS05ZGYwLTYzYjkyMWI0OTI1YiIsIm5hbWUiOiJDaDIgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2Ri + MS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIifX0seyJpZCI6IjIyMGY3NTQwLWM1NDEtNDY1My04YmUzLTgzN2VhYzg0ZGJhNyIsIm5hbWUiOiJDaDMgRmFkZXIi + LCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIifX0seyJpZCI6ImNiZWU3ZGFlLTA5NDEtNDYwMi1iMjc5LTMzNjFiNmIw + MDU0MSIsIm5hbWUiOiJDaDQgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNo + YW5uZWwiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIifX0seyJpZCI6ImFmOGE5MTJm + LWM0MTEtNGNkYi04MWY4LTU0ZWM5YjkyYTRiOCIsIm5hbWUiOiJDaDUgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUz + Iiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUv + ZmFkZXIifX0seyJpZCI6ImQ2NjA0MjhkLTY3MDItNGU2My04OGI4LTA3MjY3ZTIyOWU5MiIsIm5hbWUiOiJDaDYgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2Ri + MS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIifX0seyJpZCI6ImY4NDEzZTkyLWEzYzgtNDcwOS1iZmJkLWVlMTkyY2IyMmJjMyIsIm5hbWUiOiJDaDcgRmFkZXIi + LCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjYsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIifX0seyJpZCI6IjY2Yjk2MDY0LTdjMmMtNDIzMS05YzAwLTM5YzU5NmZi + MzU2YyIsIm5hbWUiOiJDaDggRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNo + YW5uZWwiOjcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIifX0seyJpZCI6ImY2NmQxZTdk + LTA0OTItNDA3MS04ZDY2LTI3OWMzODNkNWM5NSIsIm5hbWUiOiJDaDEgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDEvdi1wb3QifX0seyJpZCI6IjExNWJjODg0LWJlMjYtNDVkZS1hOTAxLTNiY2JmMTQ1OWJlYSIsIm5hbWUiOiJDaDIgRW5jIiwiZ3JvdXBJZCI6 + Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QifX0seyJpZCI6IjFlMDIzZDhhLWVkMWQtNDA0Ny1hM2NiLWU2 + NmI4YjU4Y2I0NSIsIm5hbWUiOiJDaDMgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1w + b3QifX0seyJpZCI6IjFhM2QwNDllLTE0ZmEtNGMyOC1iOTUzLTAyYWI1NWYzZjkxZiIsIm5hbWUiOiJDaDQgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVl + Mi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QifX0seyJpZCI6ImEzYTIwZjM3LWM5ZmEtNGI4Ni1iMDFmLTM3OWIyMjk1N2MwOCIsIm5hbWUi + OiJDaDUgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJj + aGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QifX0seyJpZCI6IjZkNmI5 + OTE4LWUxYWMtNGZhNy1iNmMwLTIwNWZhYWI4NDUzNiIsIm5hbWUiOiJDaDYgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZj + MCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIxLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDYvdi1wb3QifX0seyJpZCI6ImFhNmY3MzdiLWYwNzktNGUwZi1hZTQ2LTZhOTc5MzU0MGEyOCIsIm5hbWUiOiJDaDcgRW5jIiwiZ3JvdXBJ + ZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjIsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QifX0seyJpZCI6IjhlZjA3MTk5LWNmYmEtNDdiMi05NmI0 + LTIzNzk0MTY3NmM3NSIsIm5hbWUiOiJDaDggRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFu + bmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgv + di1wb3QifX0seyJpZCI6IjlmN2Y4MzEyLTA1NTYtNGY4MS05ZjA3LWU5NjNlYzc2Njk5ZiIsIm5hbWUiOiJDaDEgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3 + LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIn19LHsiaWQiOiIyMDRhY2I3MC0yY2RiLTRi + ZjAtODZjYi1jMDFmZDM3NjBmYjIiLCJuYW1lIjoiQ2gyIE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbXV0ZSJ9fSx7ImlkIjoiZjlmNjJjM2MtYTIyNC00ZjQwLWJiYTEtMmYxZWZjZmM4ZjE1IiwibmFtZSI6IkNo + MyBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjE4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gz + L211dGUifX0seyJpZCI6IjUyYjliYzlhLWNkYzktNGVjNS05NDk3LWI0MWRlMzJhNjNhNSIsIm5hbWUiOiJDaDQgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3 + LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIn19LHsiaWQiOiJmZWZmODVjYi03OGQ2LTQ1 + MTktODQ1Zi1jYjQzYWRkNTBkNGEiLCJuYW1lIjoiQ2g1IE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSJ9fSx7ImlkIjoiODE1N2Y4ZDUtODJmYi00MjkxLWFjOGEtMmQ3Mjc1MjEzYzAzIiwibmFtZSI6IkNo + NiBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjIxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2 + L211dGUifX0seyJpZCI6ImM1NDU3MTkyLTQyYjctNDczYi1iYTc1LTZiNjc1OTE1N2Y1MSIsIm5hbWUiOiJDaDcgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3 + LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tdXRlIn19LHsiaWQiOiI4NmNhMWJmNS0wZjM5LTRi + NjEtOWM0Yy04NzU5YjlhODg1ODciLCJuYW1lIjoiQ2g4IE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbXV0ZSJ9fSx7ImlkIjoiMmYzNTFhNTUtMTRlNS00Njk2LTkxMjgtNGI2NmMxNjhmZDZiIiwibmFtZSI6IkNo + MSBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEv + c29sbyJ9fSx7ImlkIjoiZGNmNjgyNGUtNzU5Yy00YzZlLTgyMTUtYzUzNTczNDNjMTlmIiwibmFtZSI6IkNoMiBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEt + NGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyJ9fSx7ImlkIjoiMzI5YTE5ZDAtOWE1Mi00MmM4 + LThlNDgtMDY5MmU4ZGQ0ZWViIiwibmFtZSI6IkNoMyBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NvbG8ifX0seyJpZCI6ImFhNzY3NzZlLTEwNzgtNDdjYS1iMjMwLTMwNzhhYTJjMTc1YSIsIm5hbWUiOiJDaDQg + U29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjox + MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9z + b2xvIn19LHsiaWQiOiI0MzQ2MWZhYS01ZjcxLTRiMWMtYTllYi1mOThjOGNmYjI1NGYiLCJuYW1lIjoiQ2g1IFNvbG8iLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00 + ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyJ9fSx7ImlkIjoiMTUyN2NkZDgtMjE4YS00MGNh + LTk3NjItMzU1N2UwNGU4MzZmIiwibmFtZSI6IkNoNiBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NvbG8ifX0seyJpZCI6IjEyZDk2YjhjLWE4YmQtNGVhYy1iMzdlLWI0MjhjMTQ3NmFjNyIsIm5hbWUiOiJDaDcg + U29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjox + NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9z + b2xvIn19LHsiaWQiOiI4NjFlMjdjZi1iYjIxLTQ1NWYtYjBkNS1iMzZmMzJhOTRkODMiLCJuYW1lIjoiQ2g4IFNvbG8iLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00 + ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyJ9fSx7ImlkIjoiMzE4Y2Q1YjgtZTQyZC00NTY4 + LTg5ZGQtMTM3YTcxY2YyMTZiIiwibmFtZSI6IkNoMSBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImYxOTU4NTVkLTljZjUtNGMxNi1hOWU5LTBiY2Q3MTQzNDNlMyIsIm5hbWUi + OiJDaDIgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDIvcmVjb3JkLXJlYWR5In19LHsiaWQiOiI5ZmRmZWFjZS1kNWE2LTRmZWUtYmQyZi1hZTEzNGEyZWFkMzciLCJuYW1lIjoiQ2gzIFJlYyIsImdyb3VwSWQiOiJiYmZk + YmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFkeSJ9fSx7ImlkIjoi + YWI5YTZmMDYtZGUzMC00NGRkLWJhOTctNjg1OWNkNzc3NThkIiwibmFtZSI6IkNoNCBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRl + N2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImZkOTlkODJjLWRkZDYtNGI3Zi1iNDQ4LWY3 + NWY3ZmFmNmE5MCIsIm5hbWUiOiJDaDUgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDUvcmVjb3JkLXJlYWR5In19LHsiaWQiOiIyMDc3NGYxMS03NDQ2LTQyZTgtYTI0NC1jOWRhZDk5OTEwMmIiLCJuYW1lIjoiQ2g2IFJl + YyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3JlY29y + ZC1yZWFkeSJ9fSx7ImlkIjoiNzE5NjQ5ZmEtNzY2YS00NmI2LWFiOWMtZmYxZjcxMDU1NTNiIiwibmFtZSI6IkNoNyBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjVi + My00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9yZWNvcmQtcmVhZHkifX0seyJpZCI6ImZkNjkwOTg2 + LTdjM2EtNGNlMS05NjdhLTgwYjNlNzMyODI4MCIsIm5hbWUiOiJDaDggUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5In19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoiYmVocmluZ2VyLXgtdG91Y2gt + b25lIiwibWFpbiI6eyJhY3RpdmVNYXBwaW5nQnlHcm91cCI6eyI1ZGUzZGY3OS0yN2NlLTQyZTYtYWIyNy1jMWM3MjM0NjM1OGEiOiIwZmViNTMxZS0wZDIwLTRkOGIt + YmFhOS02MjQwZGZiNWZhZDkifX19 + AAAQAAAA + > + FLOATPOS 1287 385 1436 1514 + FXID {19CF6B93-C6EE-4DF7-AF93-45C8F07169B4} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAgQMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS45IiwiaWQiOiIxQkE0TjBMTCIsImxpdmVzT25VcHBlckZsb29yIjp0cnVlLCJjb250cm9sRGV2aWNlSWQiOiIxMSIsImZlZWRi + YWNrRGV2aWNlSWQiOiIxMiIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiIyMTM3NTEyMi0wMzgw + LTRhNzQtYmNkZi0yMzk1MzQyZTllNDAiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiY2hhbm5lbCI6MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsImRpc3BsYXlJZCI6MCwibGluZSI6MCwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJlZWxGZWVkYmFja1RyYW5zZm9y + bWF0aW9uIjoiU3VwZXJpb3IiLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVy + IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiYTBl + Zjc3M2MtYjYyYi00ZjJjLTg5OGItM2Q4ZGI0YzFiMjUzIiwibmFtZSI6IjIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NSwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {4D5DA25E-5D19-4FDE-B989-7E84B49C9E1E} + WAK 0 0 + > + > + "" + ZG1jcu5e7f4AAAAAAAAAAOYAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAA1AAAAQzpcVXNlcnNcYmVuamFcQXBwRGF0YVxSb2FtaW5nXFJFQVBFUlxEYXRhXEdNLnJl + YWJhbmsAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAABNYWpvcgANAAAAMTAyMDM0MDUwNjA3 + AAEAAAAAAAAAAAAAAAAKAAAADQAAAAEAAAAAAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 55 622 676 616 + FXID {CA574B24-91E9-4585-85BF-430CE251B492} + WAK 0 0 + > + + > + > + + + + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-448-display-timecode.rpp b/plugin-reaper-realearn/resources/test-projects/issue-448-display-timecode.rpp new file mode 100644 index 0000000..7a65d66 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-448-display-timecode.rpp @@ -0,0 +1,748 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAl7sAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMyIsImlkIjoiV3VxNUllT3ciLCJjb250cm9sRGV2aWNlSWQiOiIxMSIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMiIsImRlZmF1 + bHRHcm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJocEd6VEdOLUJMajVTSjVsZWhvMlMiLCJuYW1lIjoiVGltZSB1bml0cyJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91 + cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiZmFkZXIiLCJuYW1lIjoiRmFkZXIifSx7ImlkIjoidi1wb3QiLCJuYW1lIjoiVi1Qb3QifSx7ImlkIjoidi1z + ZWxlY3QiLCJuYW1lIjoiVi1TZWxlY3QifSx7ImlkIjoiZmFkZXItdG91Y2giLCJuYW1lIjoiRmFkZXIgVG91Y2gifSx7ImlkIjoic2VsZWN0IiwibmFtZSI6IlNlbGVj + dCJ9LHsiaWQiOiJtdXRlIiwibmFtZSI6Ik11dGUifSx7ImlkIjoic29sbyIsIm5hbWUiOiJTb2xvIn0seyJpZCI6InJlY29yZC1yZWFkeSIsIm5hbWUiOiJSZWNvcmQt + cmVhZHkifSx7ImlkIjoidi1wb3QtbGVkcyIsIm5hbWUiOiJWLVBvdCBMRURzIn0seyJpZCI6ImxjZCIsIm5hbWUiOiJMQ0QifSx7ImlkIjoibWV0ZXIiLCJuYW1lIjoi + TWV0ZXIifV0sIm1hcHBpbmdzIjpbeyJpZCI6IjdHRk5RZlpwOXVxb3ZoVjV6V3R2aCIsIm5hbWUiOiJUaW1lY29kZSIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQucG9zaXRpb24ucHJvamVjdF9kZWZhdWx0Lm1jdSB9fSIsImZl + ZWRiYWNrQ29sb3IiOm51bGwsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjoyMywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsImZlZWRi + YWNrUmVzb2x1dGlvbiI6ImhpZ2giLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUUpEdlhnU3F4cDFEbThBVGtzTmp2Iiwi + bmFtZSI6IkxDRDogVHJhY2sgbmFtZSIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlJZCI6MCwi + bGluZSI6MCwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoie3sgdGFyZ2V0LnRyYWNr + Lm5hbWUgfX0iLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoic2VsZWN0ZWQiLCJmeEFu + Y2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwiZmVlZGJhY2tSZXNvbHV0aW9uIjoiaGlnaCIsIm9zY0FyZ0luZGV4IjowfSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjSmw3WGlmUmxQQ0UtV2RlMWdqSFEiLCJuYW1lIjoiTENEOiBUcmFjayBuYW1lIiwic291cmNlIjp7InR5cGUiOjEyLCJjaGFubmVsIjowLCJudW1i + ZXIiOjE2LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5SWQiOjAsImxpbmUiOjEsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6Int7IHRhcmdldC5wYW4ubWN1IH19IiwiZmVlZGJhY2tDb2xvciI6 + bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRy + dWUsImZlZWRiYWNrUmVzb2x1dGlvbiI6ImhpZ2giLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidVVtSXpmaHA3TEdvWmxI + TVlxUWRoIiwibmFtZSI6IlNldCBwYW4iLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiY2hhcmFjdGVyIjo0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwiZGlzcGxheUlkIjowLCJsaW5lIjoxLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDA1LCJtYXhTdGVwU2l6ZSI6 + MS4wLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoie3sgdGFyZ2V0LnRleHRfdmFsdWUgfX0iLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmZWVkYmFja1R5cGUiOjF9 + LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tHVUlEIjoic2VsZWN0ZWQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwiZmVlZGJhY2tSZXNvbHV0aW9u + IjoiaGlnaCIsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY29KanI2M0NRcVphY05fMkRPZDdqIiwiZ3JvdXBJZCI6Imhw + R3pUR04tQkxqNVNKNWxlaG8yUyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjAsIm1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsLCJidXR0 + b25Vc2FnZSI6InByZXNzLW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDA0MTEiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IjZOcWNWczVrNVltTlp0TGdFQ0QyZyIsImdyb3VwSWQiOiJocEd6VEdOLUJMajVTSjVsZWhvMlMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo1MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVl + ZGJhY2tDb2xvciI6bnVsbCwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQwNDEwIiwiaW52b2NhdGlv + blR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJUcS1PYXBjLTYwRW10ZkYzME9OM2YiLCJncm91cElkIjoiaHBHelRHTi1CTGo1U0o1bGVobzJTIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsi + bWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGwsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5h + bWUiOiI0MDQxMyIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoieDJwZWIxbm1IWEVHcHhDMGpVNkx2IiwiZ3JvdXBJZCI6ImhwR3pUR04tQkxq + NVNKNWxlaG8yUyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsLCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkifSwidGFyZ2V0 + Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDA0MTIiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ilp4cWlPZjRncVc1eDRfekFUSmhwViIs + Imdyb3VwSWQiOiJocEd6VEdOLUJMajVTSjVsZWhvMlMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1MywiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbCwiYnV0dG9uVXNhZ2Ui + OiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQxOTcyIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQ + cm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJf + VWtpSlRKeXhBV01DZEJRemJpeVAiLCJncm91cElkIjoiaHBHelRHTi1CTGo1U0o1bGVobzJTIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + NTMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29s + b3IiOm51bGwsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDQxNCIsImludm9jYXRpb25UeXBlIjow + LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiMlpLdU5nbVA4ejJORDZ0blJDQmhkIiwibmFtZSI6IkN5Y2xlIHRpbWUgdW5pdHMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo1MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjoxLCJmZWVkYmFj + a0NvbG9yIjpudWxsLCJyb3RhdGVJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MzcsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3Zl + VmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJncm91cElkIjoiaHBHelRHTi1CTGo1U0o1bGVobzJTIn0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6IlBEN2NmWDM2VnpXWTNKVHJYVDIycCIsIm5hbWUiOiJQZWFrcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6MCwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMDAgZGNiYV0iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNp + emUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MzQsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJRMXRsWWFBelU3TjhxUVROOHoydi0iLCJu + YW1lIjoiMTMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3NwcmVhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7InVuaXQiOiJwZXJjZW50IiwidHlwZSI6MiwidHJhY2tHVUlEIjoiODJDMTFFQUMtRDMzOS00OTUyLUJBMUMtMTQxODhCQzIxRDU4IiwiZnhBbmNob3IiOiJpZCIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQi + OiJNTHdlZko2Y0JqTDBjTXpXZGVocDMiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Il9sOWRIeDBNdHBsSkttN3ljTXFWRCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIi + OjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjJZNnUtTUk3 + bG95M2UyUDFqLTdfSSIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMn0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiNEJyQWh2ZnlfLTExWHFvV1Z3MERlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJtYXJrZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImpqMDc3ZXcyTlBuWExCUGw2Y1EwUiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjc0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZ0E4 + RVdYakM4YUNfMklJNF96bEotIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJ3cml0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicTJYcDgxUHBFc0M2bVY2M2NqMkxwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6NDh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJPNVBzbW1P + OUpjZzh0RzQ2SExrYXgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + LXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJ0cGxrTW1xQmU1R3F3R2tFWWpQQnAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo0Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiTVhGay1Q + QjB6aEdaZjZGY2k0SW5JIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJi + YW5rLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIydnBxQUJkWlBjV2ZyVzhzTnFmSnEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo5MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJld2luZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiTjdOSUFk + Z1VaWU5ncUE1cm1oSmZhIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJm + YXN0LWZ3ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiU1pDOVdoR2RkckxUWjdTay13djBJIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6OTR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJuNF81bV9Kd25s + UUYwbXNESklOeDkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3Ai + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Imw2emx2NVZ1aGxNb3JlQWEtU01fZyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + Ojk1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVjb3JkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJHdjVqTUZIdVdZRHNxRzVv + QXFxcXgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN5Y2xlIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJtTGxfRENoZGlVLUU5RU5SMVJDNC0iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDB9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJTZHZVZmZpdUV1MFU4bGtxbE1FQ0ci + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicEJYMnI0TkpLVGtWTjRPb296Z3hPIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTh9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiWDkzR2pYTlVfRFZTYVdGbURKcFgt + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImF6SXNqQng2RUkzNkZ1dE0tUDNJMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk2 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXVwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJWOFBKdGFBNFgtNU5TWEZO + dS1HYjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3du + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJNV3RMMGNMbjJNTnMxTzNxbHlyODYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo4NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im51ZGdlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJQT3RYdUk2bERjNno4eWVw + VkVOdkwiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImRyb3AiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Ik5mWTBaTkVSNVQ4S3hjY3RSbEFxQyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg4fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVwbGFjZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoibFhIcmpvdzFYZEI1Q0VRd3NQcjVN + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjbGljayIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoidDZ5VnU3aHBhelFJOXBnVU9EV19OIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTB9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJoQ0RFbk1ZcDNtSjl5MDFwb0JrZkgiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiIzaTh5aTNCcjJyYzZGTzFTZFBCdV8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NX0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImYyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI2clI1NHFra3A3cVlISmdydm44TEQiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYzIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJRaHI3RzNTenpGeHk0My1MdHRNU18iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImY0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI3UU9aT2xHMjkyWnJkOUhDTk9PS2oiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo1OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY1IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJNZC1KV29oM3c0 + WVR6alQ4bUNzVWIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY2Iiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJ2aWFzOVNLWHBWaFhfZGwtX1owZzgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1 + M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJiTFRIRlNyczZ3RUph + ZGl2dTdrNmoiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdubWVudCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlJMWDRsWU14bDNTR2xRSWZDOFJ2QyIsImdyb3VwSWQiOiJsY2Qi + LCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVjb2RlIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiRzVNY1pvOXFjY3Vwam5ScEZkcjVwIiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiVnNJOWJEc1czU2V6Q3daNUJkVWZSIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyL3RvdWNoIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlFmLVlEUlRwZk9CX05DZXNJZTR5dyIsImdyb3VwSWQiOiJzZWxl + Y3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zZWxlY3QiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IlI2cS02SkFuNVoydUxZako5OVRYYiIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFu + bmVsIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gxL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIteE8zemFKVWdiS2Q1SkVVUzUtRXkiLCJncm91cElkIjoidi1wb3Qi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ5clBpQm1PQlVzMUtMQllKb19yVXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3dyYXAiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJLX1ZQeWRSNEl1RnJzcmR1S3lXdWkiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJt + YXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4 + RTRJMWhxUkFRWXV2aFQtUkJvb3AiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMCBk + Y2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6Il9XYUlsWVpSQmhnNmxsVXd5QUZVYSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6 + IkIwIDMwIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ3dU5RcWNtYXhNVHc4UFNoZGQtcjkiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjE2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImQtVmdh + OXAxNG1SQ1VmR2ZuZk1KZSIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OH0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJWQ0ZPbHNyOFJBMFcxWkZ0cEsyMHEiLCJncm91cElkIjoicmVj + b3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNv + cmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IkJKRS13X1VsYXZFNVBLLW9kYnU0ViIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlw + ZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiIzUXEzN1RaLTUxUm9vbHhUYWE1NGoiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjF9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDEvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoib2J1RWYxTUtZM0xCWHltMDZKSTEx + IiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tZXRlci9wZWFrIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOXprQVdVTUcxTnpzenBraUh2aWJKIiwiZ3JvdXBJZCI6InYtc2Vs + ZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxlY3Qi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiS1QzUWRrb0lBelYxRWtBQklBRW1yIiwiZ3JvdXBJZCI6ImZh + ZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2Zh + ZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImVaMzdwYm4xUjJmREstOV9kR1N1WCIsImdy + b3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Mi9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IkphWlNGaWRIOTkybk9tRDE0anZCUSIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0 + eXBlIjozLCJjaGFubmVsIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJucGFJTUNRRTYwUWRhaTU2Ty0wZTUiLCJncm91 + cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNywiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJHcUM3dmJscG1IbTNTV1VNQzhSclkiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Yt + cG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJDSmZZVnJ6WFNoZ3ZMUm1IRUN2UXEiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZh + bHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJCcElTMzluODlFYlJHNDJLekF3UmMiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJC + MCAzMSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IkxLcG5JZHpyX3cyV2g0SjhJRGZMZiIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdN + aWRpUGF0dGVybiI6IkIwIDMxIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvc3ByZWFk + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYkpwX2FiTVJic3VKaWlHS2Y5TmJHIiwiZ3JvdXBJZCI6Im11 + dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxN30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI0NktYQjl5bUl6U3FmLVhaZTczdksiLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiWkFv + RmtaNUNJTkZWRjNGdU44RGl0IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjF9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJ5SzJfS1hGWUJDQjBJYnNfUlQ5 + cHoiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZ0VlWFh0NDBWQTdyR1BTUGV4c2JiIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0 + eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6IkQ5MWZsWnl0QmhtSlZCTHRoSE1zdiIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBb + MDAwMSBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDIvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InFNU3Zp + eTZWVVhOUG1DQnRuM1BZRCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM0fSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Imx4 + UjI5UE83YUlnLXQ3LVk3T0FJeCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNn0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJSWl9nbkdBZDNKVzdIWGVGYTRaaUUiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIyVHpBZnpSS2swTkl2amtOanFp + SU0iLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiRmVWaWdqMWFzSXRzc1U3M1BHSC0yIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTgsImNoYXJhY3RlciI6Miwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiT0pZV2wt + ZEs2eVB4OTRvX2otbDFiIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTAgZGNiYV0i + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiR29pOFVIdm02Q0VQSi1XZkdIWmRMIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIg + WzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiS3Nfc1NSUlVPNnh4cUVtN1dtcmdYIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3Vy + Y2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9zaW5nbGUi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ3V2tPbnl2cTVzZDdTejV3c2dXS2QiLCJncm91cElkIjoidi1w + b3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1 + LCJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlRl + SjFYc0JRS3cxR3NiSUNaODE1VyIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTh9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiaWlpRzFDcVprQVVpOTVfM21IbGRTIiwiZ3JvdXBJZCI6 + InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zb2xvIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJBNGZMTUZvT2dJQTRPRld4eTV4a3QiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6IkxVam5KcENUaElkcHpBNjJQdUZmeCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MiwibGluZSI6 + MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJTMU12Mi04S3pCalgxdUwx + S29QSDgiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUyIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiVWtTYUVkenE3MWZLNTRJMkV1VjhuIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidzItTnVTZENPUkt2RWRzQWdkWDZNIiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MzV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoia1hrM1Babk9vVU1DdkRUWEtLWXFTIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IngyWlVxZjJvNDlPdW84WWZtMGFSRCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyN30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6IkNBNklJcWJuRGctWHdfMUVDY0x2RiIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjozfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0 + L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJQR0tjWU9pM0Z5RzFBcDl3MW5lTVgiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjoxOSwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJsWU9IMzZYSjVXeEJnV3BMc2pFcWsiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJNb0ltWTRkR2JnRXM0TC15ZFJVZXgiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6 + eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6 + MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNC92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5YVNQYWVWM0ZJZjFiWGRp + T3hJejMiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7 + Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IkpS + SG1Hay1qM0M5OHVwM2hyS3ZBLSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDExIGRj + YmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOHpmRXlpNzlPSUhHSTFiQjR3MGVTIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjoxOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJVMzBCWlZXYmptZ3BEMTdsNWVxMUwiLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImE5Ymd3NW41Rm5kc2FQY1RhcU1tMyIsImdyb3Vw + SWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g0L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiNVhHd2N5Ukw3ZWIzZFhNWmM0RUt0IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJj + ZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6Ikpua05PMHNtUmdDRVlQWUltejB5MCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MywibGlu + ZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNC9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJUWUxQUUZvTGRGckpq + aGhEbUdXRG8iLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L21ldGVy + L3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJyRnBpcTY4cElIQzJzdWxlcVBvaDUiLCJncm91cElk + Ijoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92 + LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxZVBqLUN2MXE3YXU5T0Q3REJZeEMiLCJncm91 + cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDUvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUnVIYW1yTnZ5Q3FzejB0OVNm + UXYtIiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g1L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiTF9XZGVfLThRbjVEck9CcEpQLWpoIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291 + cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IlR5V2I5TWhjUzE4ZTVQV1E1ZjFG + dyIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Yt + cG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im5JcDJCeDk2OENvODl0OUIzdkZWcyIsImdyb3VwSWQi + OiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUi + OjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDUvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IktPbmlESF9RU2xOTG5wN0xiVER5 + UCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWlu + U291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6IlJ1WnFwSnVXeWU1QXdGN09hS21KZyIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0 + dGVybiI6IkIwIDM0IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoialNTVWx4RWtKWlFoOUFtVlk3MzFNIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6 + MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNCwiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBv + dC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJNU0hoXzd4U2ZmeEtGYlBDNlh2YV8iLCJncm91 + cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L211 + dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Im9aR1NYTDJOTnd2eGV3R0hqcmZEbiIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiNU5KeEZWcm5hRThHeTVZd0NiYjlqIiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjR9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI4QmZ5elJTb3E5 + NXhWZ3RvUGVua00iLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUxIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiajhkaUc5ZzVUSmRvV3VYTmJDb0ZiIiwiZ3JvdXBJZCI6ImxjZCIsInNv + dXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo0LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6IlNvek0xRkhQNjhmazBLTWh6eTZ0SyIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRl + cm4iOiJEMCBbMDEwMCBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6InM1LWJ6RDU4R251MTgtcGZrTEVFQyIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3fSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6InpsMmNSOFlZbUpmNlBwanhtazNoaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEw + OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJRSEc0b0JqSGpoMHo2Z0JfVlVWZ3AiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6Mjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJaMXRNSXV3NGFR + cm9RYkZTcjhHR1kiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiRFhleUtLYXQ5TENheGljeW5fUzhFIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjEsImNoYXJh + Y3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiM29kTXdZOGliTUZGNjZmV25uX180IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAw + MTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoibzlSWmVCeUZHTE55Yk5NOU50YkhZIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJu + IjoiQjAgMzUgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QvYm9vc3QtY3V0IiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODAyQ05pMWJfNmJlWVVEVWR2Tm91IiwiZ3JvdXBJZCI6InYtcG90LWxl + ZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBv + dC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJSSWNiWWNmc3FTSUx3S3VoNU5WNUUiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZh + bHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IkhNRmFCMFZCSjdDLWwwRjhzcGpmXyIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjF9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiRVdTeTVEcTk1UmhXWTJieUtlVHZUIiwi + Z3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Ni9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIzd2VrUFZtTnpNaFlUemYxZ2htSmkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IlYzVy1oUkx6RUJUOHp2OGt1bl80NiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6 + NSwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJTR3VzUzAz + aE5PU2EyaHhUemNHdjQiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUyIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiQ2V2NEFoTFl5OERjcE56UGtzN3VuIiwiZ3JvdXBJZCI6Im1ldGVy + Iiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTAxIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiVXprWmkxblF0M0FRNDBVQzhJT0ZNIiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiQUtYMHlPZVN6WnRYRTY0QTBXQVR3IiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNoIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjdIQXZaOWZPQzFCMVVlV0oxNmhsYiIsImdyb3VwSWQiOiJzZWxlY3QiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zZWxlY3QiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IkhuN2JjWUljM2djdmZLU2NfdVBQNyIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo2 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g3L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJLVnlEODZlaVhmUFV5am1wUHpJMTYiLCJncm91cElkIjoidi1wb3QiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMiwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJKc1BVNDNuVWpQLWNBSTVlYTNDUXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0 + eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3dyYXAiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI0ZUMyUkFheGg5Q0FycWF1ak40LWMiLCJncm91cElkIjoidi1wb3QtbGVkcyIs + InNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3Vy + Y2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNy92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJQaGRJNGFq + RUstSDF6cFN1LVFUZTkiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMCBkY2JhXSJ9 + LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IktYemU0QnlZSzB1MkdDaklzb3RZZiIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2 + IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMjBMLUNYVmxieTJpTTdIeEJKWWVMIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJtZmlUMHdiSlQ4QWt0bDM1cE55eVUiLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjFpekExV1A0TkdfX2dETlVBRnJL + TiIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoib09WQzVHR3FTUHJ6N19WcEFIb2FXIiwiZ3JvdXBJZCI6Imxj + ZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo2LCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InNIeERQWS05YmVPY2V1aEVBcWkwYyIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJ + ZCI6NiwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNy9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjdlNJ + Ym9HaGtLY0k3R2hUQW9oRHkiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTAgZGNiYV0ifSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g3L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5T0JsRXEtcmpselA5enZ5MnZnQmki + LCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoOC92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJBYzBMSU52YTB2VnRQZVFRWWVf + cEsiLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZWZFNHBwVW1N + QU0zYzhBazFGaG8wIiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMxfSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g4L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicmpHUFByb2R6dGl6UW5mUGNUY1dVIiwiZ3JvdXBJZCI6ImZh + ZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Im44YWozeUJYU2Jz + VmxSeUNpNHhuTSIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g4L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IllVQUtLUkZGMmFueEhfQzVkMDQwNCIs + Imdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291 + cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlVEZzh3SUpmcmda + eVpud0dXOHN5dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAxIGRjYmFdIn0sIm1v + ZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJvWENaQ2ltcTlQeDNaOVJMNU9qMyIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJy + YXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUVBpQ1k2cEFuamVqQ0RvdTBXWlBBIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2Ui + OnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUi + OjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoOC92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJSNHU4YVN3dHBMNngzUzZ5aGth + WnIiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g4L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImxaazRIVVJzaU55RktSQzl0TTFaZyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoic0E5Y0VneUJiZjN6dW43NlFMS1BSIiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJl + Z2Znek9FaVZSSm5VdWNFVFMtQ28iLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xp + bmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiTm5KTmpvWi1tOHREU1V3cWplTnlUIiwiZ3JvdXBJZCI6 + ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo3LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IkppSHgyc3BVc2xVWUs5ekktcDN0UiIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3 + TWlkaVBhdHRlcm4iOiJEMCBbMDExMSBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX1dLCJtYWluIjp7ImFjdGl2ZU1hcHBpbmdCeUdyb3VwIjp7ImY1Y2IyMGVkLWRkODYtNGEzYS04YTE4LWY3YTI1NDliMzY4NyI6ImI1MWIwOGU1LTU4NDMtNGEw + Mi05MWY4LTM2MDNkMTg0NTIzYyJ9fX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 1052 508 1436 1514 + FXID {B8DF9E06-D43A-4AB1-8053-16BAC2AF996A} + WAK 0 0 + > + + > + > + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-448-launchpad-scrolling-text.rpp b/plugin-reaper-realearn/resources/test-projects/issue-448-launchpad-scrolling-text.rpp new file mode 100644 index 0000000..b4998f7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-448-launchpad-scrolling-text.rpp @@ -0,0 +1,134 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAEwIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS45IiwiaWQiOiJBWVdPQ3NESSIsImNvbnRyb2xEZXZpY2VJZCI6IjUiLCJmZWVkYmFja0RldmljZUlkIjoiNiIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJmZWJlZDE1NC01ODE2LTQ5ZjAtODhlNi1iY2JjMDkwMTY3ZGIiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImRpc3BsYXlUeXBlIjoibGF1bmNocGFkLXByby1z + Y3JvbGxpbmctdGV4dCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6IjEyMyIsImZl + ZWRiYWNrQ29sb3IiOlsyNTUsMCwyNTVdLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {B26D774B-FCAF-4D85-847B-F43C5FFBA696} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-449-enable-instances.rpp b/plugin-reaper-realearn/resources/test-projects/issue-449-enable-instances.rpp new file mode 100644 index 0000000..a37539b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-449-enable-instances.rpp @@ -0,0 +1,417 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.00577825462898 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAciwAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xIiwiaWQiOiJKeTQ0X0xIeSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6IjkzZDEyZTA0LTI4MGEtNDM5Mi05YjdmLTc0Y2Y5NTFlMDk0ZCIsIm5hbWUiOiJQYWdlcyJ9XSwiZGVmYXVsdENvbnRy + b2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImI4ODk1M2M0LTc4NDYtNDA5MS1iZjhlLTZkZDIzOTkyYjhhMyIsIm5hbWUiOiJhbGwiLCJncm91cElkIjoi + OTNkMTJlMDQtMjgwYS00MzkyLTliN2YtNzRjZjk1MWUwOTRkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MCwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjozOCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsibWl4IiwibWFzdGVyIiwiZngiXSwiZXhjbHVzaXZpdHkiOjF9fSx7Imlk + IjoiYjY0ZDE5YzEtMDY2YS00ZjU5LThkNDEtY2YyNWFmNWI5MzliIiwibmFtZSI6Im1peCIsImdyb3VwSWQiOiI5M2QxMmUwNC0yODBhLTQzOTItOWI3Zi03NGNmOTUx + ZTA5NGQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjM4LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJtaXgiXSwiZXhjbHVzaXZpdHkiOjF9fSx7ImlkIjoiNTkwODU1MmItMDFlNy00Y2Rm + LWI0ZGYtN2EzNzI1NjRhMGVhIiwibmFtZSI6Im1hc3RlciwgZngiLCJncm91cElkIjoiOTNkMTJlMDQtMjgwYS00MzkyLTliN2YtNzRjZjk1MWUwOTRkIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjAsIm51bWJlciI6MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiYnV0dG9uVXNh + Z2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjozOCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsibWFzdGVyIiwiZngiXSwiZXhjbHVzaXZpdHkiOjF9fSx7ImlkIjoiNzNiNzAxYjEtMDY0Mi00Y2I5LTg4NzYt + YTIzYWRkMTVjMGJkIiwibmFtZSI6IjQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjozfSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6LTAuMTYwMDAwMDAwMDAwMDAwMDMsIm1heFN0ZXBTaXplIjot + MC4xNjAwMDAwMDAwMDAwMDAwM30sInRhcmdldCI6eyJ0eXBlIjozNywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImdyb3VwSWQiOiI5M2QxMmUwNC0yODBhLTQzOTItOWI3Zi03NGNmOTUxZTA5NGQifX0seyJpZCI6IjRjYjEyN2JhLWQ5 + NTktNGJmOC05NWY1LTQ3ZjE1NzMxMGQwNCIsIm5hbWUiOiI1Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MzgsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbInRvcCJdfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFj + MWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRi + YWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhi + MGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwi + c291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0 + Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29k + ZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5 + LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFt + ZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYz + ZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJk + ODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsi + aWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYt + ZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00 + NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNl + YzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7Imlk + IjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMv + MiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + Ojl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUi + OiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJj + aGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0 + MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZl + LTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6 + IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDVi + OGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBh + ZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19 + LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUt + OWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIx + NTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIs + Im5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3 + YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVy + IjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lz + dGVyIiwibWFpbiI6eyJhY3RpdmVNYXBwaW5nQnlHcm91cCI6eyI5M2QxMmUwNC0yODBhLTQzOTItOWI3Zi03NGNmOTUxZTA5NGQiOiJiNjRkMTljMS0wNjZhLTRmNTkt + OGQ0MS1jZjI1YWY1YjkzOWIifX0sImFjdGl2ZUluc3RhbmNlVGFncyI6WyJtaXgiXX0= + AAAQAAAA + > + FLOAT 1502 267 1436 1514 + FXID {4C2FBB30-12B4-4B94-8DDB-5F4F089F95B2} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAhgAAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xIiwiaWQiOiJSeW1pcm91dCIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwidGFncyI6WyJtaXgiXX0= + AAAQAAAA + > + FLOATPOS 19 47 1436 1514 + FXID {8B58F4CD-85FD-48E4-85A1-E74D6E9D504C} + WAK 0 0 + BYPASS 1 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAApCQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xIiwiaWQiOiJSeW1pcm91dCIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjkyNDI0NzY2LTc0ZDgtNGMyNC04NzA2LTVmYzkyOTViZTRjOCIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFw + cGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6 + eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAt + NGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00 + NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdj + MDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVy + IjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJj + ZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7Imlk + IjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIv + MSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoi + QnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEi + LCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNl + Ijp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEw + NzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8x + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzIt + YmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVu + Y29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1m + YWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2Iiwi + bmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6 + ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNo + YXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIz + MDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRi + LTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoi + YTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8y + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1l + IjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgz + MDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNv + dXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0s + eyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4 + LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIiwidGFncyI6WyJtYXN0ZXIiXX0= + AAAQAAAA + > + FLOATPOS 1466 301 1436 1514 + FXID {2F26DE2A-A883-45B1-819B-DB8D69955144} + WAK 0 0 + BYPASS 1 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAhQAAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xIiwiaWQiOiJSeW1pcm91dCIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwidGFncyI6WyJmeCJdfQ== + AAAQAAAA + > + FLOATPOS 837 373 1436 1514 + FXID {4FDEE787-376B-40A7-8DA5-5E93C6A64219} + WAK 0 0 + BYPASS 1 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAxAEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xIiwiaWQiOiJSeW1pcm91dCIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjdlM2EyMGNjLTNiYTYtNGY0Ni04MzEzLTVlZDk0OTJhNDk1ZCIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwidGFncyI6WyJ0b3AiXX0= + AAAQAAAA + > + FLOATPOS 1868 416 1436 1514 + FXID {BF368CBD-647B-4733-A070-4DC0D195DB24} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-45-grouping-vca-test.RPP b/plugin-reaper-realearn/resources/test-projects/issue-45-grouping-vca-test.RPP new file mode 100644 index 0000000..8555e0b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-45-grouping-vca-test.RPP @@ -0,0 +1,3877 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 2 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 554.5 + ZOOM 30.62418687242224 17304 0 + VZOOMEX 5 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 2 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 344 + SELECTION2 0 344 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACdCAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS45IiwiaWQiOiJFRnRWdC1NTiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6 + W3siaWQiOiJTdkJyRmdNTTdtQlp2TUd1WlN0OWoiLCJuYW1lIjoiR3JvdXBpbmcgdGVzdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6MTAzNH19LCJtb2RlIjp7Im1h + eFRhcmdldFZhbHVlIjowLjcxNiwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsInJvdGF0ZUlzRW5hYmxlZCI6dHJ1 + ZX0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6dHJ1ZSwidXNlVHJhY2tHcm91cGluZyI6dHJ1ZSwic29sb0Jl + aGF2aW9yIjoyLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInRyYWNrQXV0 + b21hdGlvbk1vZGUiOjMsInRyYWNrTW9uaXRvcmluZ01vZGUiOiJUYXBlU3R5bGUiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJnVzU4blRlbXVPQmYzYzJHRDRzTEYiLCJuYW1lIjoi + QXV0b21hdGlvbiB0b3VjaCBzdGF0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjoxLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6MTAzNH19LCJtb2RlIjp7Im1heFRhcmdldFZhbHVlIjowLjcxNiwi + bWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsInJvdGF0ZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjoy + MSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOnRydWUsInVzZVRyYWNrR3JvdXBpbmciOnRydWUsInNvbG9CZWhhdmlvciI6Miwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJ0cmFja0F1dG9tYXRpb25Nb2RlIjozLCJ0cmFj + a01vbml0b3JpbmdNb2RlIjoiVGFwZVN0eWxlIiwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiTmdzNlBhWVp3ak1BTm1iMGdkUm1kIiwibmFtZSI6IlZDQSB0ZXN0Iiwic291cmNlIjp7 + ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmll + cnMiOjEsImtleSI6MTAzNH19LCJtb2RlIjp7InR5cGUiOjEsIm1heFRhcmdldFZhbHVlIjowLjcxNiwibWluU3RlcFNpemUiOjAuMDcsIm1heFN0ZXBTaXplIjowLjA3 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJyb3RhdGVJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiQTdG + QjgyNTgtNkEyNy05RTQ0LUI2NjUtOTJBOUExQUFCQkVEIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic29sb0JlaGF2aW9yIjoyLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsInRyYWNrQXV0b21hdGlvbk1vZGUiOjMsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiaXNFbmFibGVkIjpmYWxzZX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 601 73 752 675 + FXID {232F08FA-1F29-9C4E-9B17-903F504E9BB4} + WAK 0 0 + > + > + + > + + + + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-452-exclusive-toggle-buttons.rpp b/plugin-reaper-realearn/resources/test-projects/issue-452-exclusive-toggle-buttons.rpp new file mode 100644 index 0000000..a687262 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-452-exclusive-toggle-buttons.rpp @@ -0,0 +1,236 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAgCgAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4yIiwiaWQiOiJ6dE5DSnEwWiIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjUzYTUwOWQxLTU5ZGMtNGM2Ny04ZGQyLTgxMGUyN2MzZWEzNyIs + Im5hbWUiOiJUb2dnbGUgYmFuayAxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn0sIm1vZGUiOnsidHlwZSI6MiwibWluVGFyZ2V0VmFsdWUiOjAuMDEsIm1heFRhcmdldFZhbHVl + IjowLjAxLCJtYXhTdGVwU2l6ZSI6MC4wNSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJmeEFu + Y2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6Ijg5NjQ0OGE1 + LWU2NzctNDkwNi1hNWRhLTRlNDljZjMxNDc4ZiIsIm5hbWUiOiJUb2dnbGUgYmFuayAyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX0s + Im1vZGUiOnsidHlwZSI6MiwibWluVGFyZ2V0VmFsdWUiOjAuMDIsIm1heFRhcmdldFZhbHVlIjowLjAyLCJtYXhTdGVwU2l6ZSI6MC4wNSwib3V0T2ZSYW5nZUJlaGF2 + aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6ImQzMWY0NDBkLWU2ZTItNDI1Ny05NGU3LTc2ZTc2ZjY3YTM3ZSIsIm5hbWUiOiJUb2dn + bGUgYmFuayAzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn0sIm1vZGUiOnsidHlwZSI6MiwibWluVGFyZ2V0VmFsdWUiOjAuMDMsIm1h + eFRhcmdldFZhbHVlIjowLjAzLCJtYXhTdGVwU2l6ZSI6MC4wNSwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRh + cmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJj + b250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEv + MSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNm + OC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRh + MDY2ZTItNDhlMy00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFj + dGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7 + ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9u + IDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1l + IjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4Mjcw + ODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0y + MDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIg + Mi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQx + MzMtOTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6 + IkVuY29kZXIgMy8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0 + OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3 + IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQi + OiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNo + YXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDcz + MTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51 + bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQt + NDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIz + YjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hh + cmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUi + OiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJj + aGFubmVsIjowLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEy + NTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRi + LWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6 + IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMs + Im51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFj + NDMtNGY0YS1iNDE4LWMzZWE0ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFt + ZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIiwicGFyYW1ldGVycyI6eyIwIjp7InZhbHVlIjowLjAxfX19 + AAAQAAAA + > + FLOATPOS 19 47 1436 1514 + FXID {FA2D3DE4-10C3-40B8-8354-7838ED496A0B} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-457-performance-control.rpp b/plugin-reaper-realearn/resources/test-projects/issue-457-performance-control.rpp new file mode 100644 index 0000000..db76eda --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-457-performance-control.rpp @@ -0,0 +1,138 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADWAQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJSQTlGTlhTViIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiT1ZwUXl0dEh2UnFHR0tLd0NXZnhZIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6Mywi + Y2hhbm5lbCI6MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7InR5cGUi + OjQsIm1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {5717AD4B-C838-4433-9880-5DE475A8CBE8} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-457-y-last.rpp b/plugin-reaper-realearn/resources/test-projects/issue-457-y-last.rpp new file mode 100644 index 0000000..31a5e74 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-457-y-last.rpp @@ -0,0 +1,289 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA9iwAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xNCIsImlkIjoidWFmU19vbHgiLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250 + cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJUZ3NfaDRRalVfX29sMWlPVENZdHMiLCJuYW1lIjoiU2V0IHZvbHVtZSIsInRhZ3MiOlsidm9sdW1lIl0s + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjEyfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsQ29udHJvbFRyYW5zZm9ybWF0aW9uIjoieSA9IHlfbGFzdCArIHggKiAoMSAtIHlfbGFz + dCkiLCJmZWVkYmFja0NvbG9yIjpudWxsLCJtYWtlQWJzb2x1dGVFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZWhz + U2ZrQ2trMUl2ZmR3QXg1T29fIiwibmFtZSI6IlJlc2V0IHZvbHVtZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9LCJtb2RlIjp7Im1h + eFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjM1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJ2b2x1bWUiXX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + Im43RTZzOWJDeWxiUjFMRmJEM2ZjbyIsIm5hbWUiOiJTZXQgRlggcGFyYW0iLCJ0YWdzIjpbInBhcmFtIl0sInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MC4wNSwiZWVsQ29udHJvbFRyYW5zZm9ybWF0aW9uIjoieSA9IHlfbGFzdCArIHggKiAoMSAtIHlfbGFzdCkiLCJmZWVkYmFja0NvbG9yIjpudWxsLCJtYWtlQWJz + b2x1dGVFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InRyYWNrR1VJRCI6IjIxODAyRTcyLUUxMDgtNDNDNy04RUY4LUYyOEE1MEVGNkM0NCIsImZ4QW5jaG9yIjoiaWQi + LCJmeEdVSUQiOiI4MkJBQkM1MS1DNDFFLTRCMUYtQkU0Qy0xNUJGNTRGQTc1NEMiLCJwYXJhbUluZGV4IjoxMywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ikt1T3F2Z2FNc0Jud2dQNTdwS3UzTiIsIm5h + bWUiOiJSZXNldCBGWCBwYXJhbSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsicGFyYW0iXX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7Imlk + IjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxfX0seyJpZCI6ImU1NTQzYWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwi + c291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifX0seyJp + ZCI6IjI3NTIxZWYwLTVjZjgtNDVlOS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUiOiJFbmNvZGVyIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEs + ImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZh + YWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2NTMtOWZiYi1lZjkwN2RiMDlkODciLCJuYW1l + IjoiRW5jb2RlciAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjJ9fSx7ImlkIjoiYjgyZDc3NTItZDIyOS00NDhmLWJkNDItYWY2MWI0NTllNDgxIiwibmFtZSI6IkJ1dHRvbiAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwi + bnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7 + ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + MywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0 + Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQzNi05MTM0LTQ4OGE2ZmViMWZiZiIsIm5h + bWUiOiJFbmNvZGVyIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19 + LHsiaWQiOiI3MTkwZTJhNi1lMDdjLTQ5MjItOWJiNC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + Ijo1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiMDM5Zjc5OWItNTM3Zi00Mjc3LWFiYzEtMDNjNTA2 + ODI3MDgzIiwibmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4Iiwi + bmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJCdXR0b24gMi8zIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2 + fX0seyJpZCI6ImQyODQ5ZWM3LWM4OTUtNDRlZS1iZmZhLTM0ZWVmNTBmOTc3YyIsIm5hbWUiOiJFbmNvZGVyIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjcsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEw + NzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiIyNWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYi + LCJuYW1lIjoiRW5jb2RlciAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjh9fSx7ImlkIjoiMTYwMTNjNDgtMmE0OS00NjcyLWJmM2EtOTVlYWUwNWVmM2E3IiwibmFtZSI6IkJ1dHRvbiAzLzEiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + Ojh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04Yjg5LWQ5 + NmI5NDUwMDAzMiIsIm5hbWUiOiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6ImM4NjVjNTAzLTI1ZmMtNGI4MS1iYmM4LTA3MzExMDVhMjY1 + NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNo + YW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVlZC00NjNiLTk3OWQtYjMwOTE2ZTNiYjNhIiwibmFtZSI6IkVuY29kZXIgMy80Iiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQxMzEtYmVkOS00YmE3 + LWE2MDQtNDM1Y2YwNDM2YzkxIiwibmFtZSI6IkJ1dHRvbiAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01 + ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiIxYzA5MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6ImEzODQzNWMwLTNhYzQtNDE3NC1iMWIxLTc4MWVmZjc5ODhjNCIsIm5hbWUiOiJFbmNvZGVyIDQvMiIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3 + LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6 + MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTQwNjAzOGUtM2M0NC00 + MzJkLThlY2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MywiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMxMjU4NGEzIiwibmFtZSI6IkJ1dHRv + biA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQi + OiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMwMDlkNGQiLCJuYW1lIjoiQnV0dG9uIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJj + aGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjI3YmYy + MTUyLWU3ZjQtNDVlNS05Yjk4LWRjYWU4OTY1YjkxZiIsIm5hbWUiOiJMZWZ0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEs + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0 + Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9fSx7ImlkIjoiYWIwNTQ4ODAtMWM0My00ZjRhLWI0MTgtYzNl + YTRkMzFiNTEwIiwibmFtZSI6IkxlZnQgMyIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fX0seyJpZCI6ImE1N2EyNzg3LTM5MTktNDZmYi04ZmRkLTE3ZmI4M2Y0YmFmMSIs + Im5hbWUiOiJSaWdodCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {3F0FAD6B-CFD1-4664-B165-61E7E8A5B354} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAKa9kjbpZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAA7pgCPw== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {82BABC51-C41E-4B1F-BE4C-15BF54FA754C} + PARM_TCP 13 + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-458-reset-on-play-mft.rpp b/plugin-reaper-realearn/resources/test-projects/issue-458-reset-on-play-mft.rpp new file mode 100644 index 0000000..99a398e --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-458-reset-on-play-mft.rpp @@ -0,0 +1,948 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 2 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.13283129311351 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAYikAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMSIsImlkIjoiaGg4Z1pyeGEiLCJsZXRVbm1hdGNoZWRFdmVudHNUaHJvdWdoIjpmYWxzZSwiY29udHJvbERldmljZUlkIjoi + MTAiLCJmZWVkYmFja0RldmljZUlkIjoiMTEiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiNmU2 + NjliMTctZDRhZi00ZWMxLWE0OTctYTA5ZWQyYjVjOTIwIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MTcsInRyYWNrR1VJRCI6IjQ1QTg4NDhELUQxQjctNEI1Qy05RUZELTY0RDU3NzNCRTFFMyIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjE1YmU1ZDNmLTdiZWEtNGI2 + NS04ZjBlLWI0NGYzYWE5ZmYwNiIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0dVSUQiOiI0NUE4ODQ4RC1EMUI3LTRCNUMtOUVGRC02NEQ1NzczQkUx + RTMiLCJmeEFuY2hvciI6ImlkIiwidG91Y2hlZFBhcmFtZXRlclR5cGUiOjIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5j + b2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00 + YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVu + Y29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijox + fX0seyJpZCI6ImRkMWE4MWQzLWQ2MDctNDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJl + ciI6MSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6 + IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEi + LCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMtYjcwNi0yN2I2MmUyYTVkYmUiLCJuYW1lIjoi + RW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7Imlk + IjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4 + MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUi + OiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsi + aWQiOiI0NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2 + LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTctMjA1NTc2NTMx + ZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFt + ZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUiOiJCdXR0b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEs + Im51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0s + eyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjgsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1 + ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJu + YW1lIjoiRW5jb2RlciAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjl9fSx7ImlkIjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MSwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9 + fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdhOTMwNWQtOTRhYi00ZmM5LTliNjQtYTkw + OWU1NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2Ji + M2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEzZDNjMGJmNCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJm + ZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEt + NzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5 + YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIi + OjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjU1YWEyM2IwLWI4MjAt + NGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjMsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJhOWUzLTI4OGItNGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0 + b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNv + dXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5ZGY1OTAtNjFjYi00NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxN319LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51 + bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7 + ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywi + Y2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOX19XSwiYWN0aXZlQ29u + dHJvbGxlcklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIifQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 527 476 1436 1514 + FXID {2F4D7110-8C38-48FB-8AD1-6E201289AF09} + WAK 0 0 + > + > + + + + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-458-reset-on-play.rpp b/plugin-reaper-realearn/resources/test-projects/issue-458-reset-on-play.rpp new file mode 100644 index 0000000..3aea8f3 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-458-reset-on-play.rpp @@ -0,0 +1,1269 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 0 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.13283129311351 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAADjwBAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMSIsImlkIjoiaGg4Z1pyeGEiLCJsZXRVbm1hdGNoZWRFdmVudHNUaHJvdWdoIjpmYWxzZSwiY29udHJvbERldmljZUlkIjoi + NyIsImZlZWRiYWNrRGV2aWNlSWQiOiI4IiwiZGVmYXVsdEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6IjQ2MGM4MTY0LWViZjYtNDViMC1iMjIxLWEyZmU3NGU4ODg4 + NyIsIm5hbWUiOiJDb25kaXRpb25hbCBzd2l0Y2hlcyJ9LHsiaWQiOiJiZTcyN2ZkYy1iNGY5LTRmNzEtOTk0NC0wMjJmZTRkYmFkYWMiLCJuYW1lIjoiVm9sdW1lIn0s + eyJpZCI6IjliNTRjZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGViNDgxNDU3MCIsIm5hbWUiOiJQYW4ifSx7ImlkIjoiZjNkZDRlYjktMWY2ZS00Yjc2LTg5ZjYtNmM0MTc4 + NzNkMGQ0IiwibmFtZSI6IlBhbiByZXNldCJ9LHsiaWQiOiJiNjRlZTU0Ni02NmM5LTQ3YzAtODI3Ni0zMGFmMmFjNThhNTEiLCJuYW1lIjoiU2VsZWN0aW9uIn0seyJp + ZCI6ImEwMjJhODU0LTNjOGMtNGI1Ny1iYTRkLTZmNGM0MjJjZmZkZiIsIm5hbWUiOiJNdXRlIn0seyJpZCI6IjUxM2I3MWZiLTcyODYtNDhiNC05NDQ3LWY0ZmVjNDUz + N2Y0YSIsIm5hbWUiOiJTb2xvIn0seyJpZCI6ImQyYjY5ZDZlLTk5OWYtNDc3Ny1hZTM5LTRiMGExMDhhM2YwNCIsIm5hbWUiOiJBcm0ifSx7ImlkIjoiNGNiNjdiNGQt + ZjM5NS00MmVjLWIxYjctYmIxNGVjM2MzODQ4IiwibmFtZSI6IlRvdWNoIn0seyJpZCI6IjM2ODhhZTczLWIzOWUtNDgzNi1iYzAyLWQwOWIyNmI5MmUzZCIsIm5hbWUi + OiJUcmFuc3BvcnQifSx7ImlkIjoiZDZhYjVhN2UtZjFhZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2IiwibmFtZSI6Ilpvb20iLCJhY3RpdmF0aW9uVHlwZSI6Im1vZGlm + aWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjoyLCJpc09uIjp0cnVlfX0seyJpZCI6IjdjYWMyMDY2LWYwOWUtNGM2OS1iYmMzLThlYTg2YmQz + ODAzNiIsIm5hbWUiOiJNYXN0ZXIifSx7ImlkIjoiZDBkODg5NTItY2E3MC00OTllLThjNmUtM2I5NGVhZWZiYjJhIiwibmFtZSI6IlNjcm9sbCIsImFjdGl2YXRpb25U + eXBlIjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjIsImlzT24iOmZhbHNlfX0seyJpZCI6ImI1MmZmM2U4LTdjYjgtNGViZC1i + Mjc2LTNlOWI0OTBlMDEzOCIsIm5hbWUiOiJMQ0QifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyR3JvdXBzIjpbeyJpZCI6Ijc5MTRmYjQ5 + LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsIm5hbWUiOiJGYWRlciJ9LHsiaWQiOiI5N2Q4ZGM4MC0wYzg1LTQ1ZTItOTI2Ny1lZmVjNjMxZWM2YzAiLCJuYW1l + IjoiVi1Qb3QifSx7ImlkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwibmFtZSI6IlYtU2VsZWN0In0seyJpZCI6ImM5OTlkZWMyLWY5OTUt + NDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsIm5hbWUiOiJGYWRlciBUb3VjaCJ9LHsiaWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJuYW1l + IjoiU2VsZWN0In0seyJpZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsIm5hbWUiOiJNdXRlIn0seyJpZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4 + ZS05N2E2LWUwZDhlODg3ZGM1OCIsIm5hbWUiOiJTb2xvIn0seyJpZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsIm5hbWUiOiJSZWNvcmQt + cmVhZHkifSx7ImlkIjoiMzFiZjgyZmQtMWZjYS00NmU4LWE4MGEtYmI2YzhiODkxNzg4IiwibmFtZSI6IlYtUG90IExFRHMifSx7ImlkIjoiN2I2MTAwODktNmUxNC00 + MTJiLWFhMjYtZmNlYjZhYzhiZTIyIiwibmFtZSI6IkxDRCJ9XSwibWFwcGluZ3MiOlt7ImlkIjoiNWNmZDJmZjAtODVjZS00ZTcwLTk4ZTgtZWI1M2U1ZTk0YmIxIiwi + bmFtZSI6IkJhbmsgLSIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRlODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiYmFuay1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwOCwibWF4U3RlcFNpemUiOjAuMDAwOCwicmV2ZXJzZUlzRW5hYmxl + ZCI6dHJ1ZSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIwN2IwMjAyZi1iOGI4LTQ4ZTQtYTUzMC1mMjI4NDUwODY0ZjAiLCJuYW1lIjoiQmFu + ayAtIGZhc3RlciIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRlODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiYmFuay1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwOCwibWF4U3RlcFNpemUiOjAuMDAwOCwibWluUHJlc3NNaWxsaXMiOjMw + MCwibWF4UHJlc3NNaWxsaXMiOjMwMCwidHVyYm9SYXRlIjoxMDAsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoi + dHVyYm8ifSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjBiYWFiOTFmLTJjNGUtNDNhZS04Y2FjLWRjYTcyNzk2M2I0NiIsIm5hbWUiOiJCYW5rICsiLCJncm91cElkIjoiNDYwYzgx + NjQtZWJmNi00NWIwLWIyMjEtYTJmZTc0ZTg4ODg3Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQifSwibW9kZSI6eyJ0 + eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDA4LCJtYXhTdGVwU2l6ZSI6MC4wMDA4LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhp + cyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiIwYTdlMTIyYy0zZWRhLTQzNTUtOTA2YS1hMDU3ZWVmYTUx + NWIiLCJuYW1lIjoiQmFuayArIGZhc3RlciIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRlODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiYmFuay1yaWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDgsIm1heFN0ZXBTaXplIjowLjAwMDgsIm1p + blByZXNzTWlsbGlzIjozMDAsIm1heFByZXNzTWlsbGlzIjozMDAsInR1cmJvUmF0ZSI6MTAwLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0s + InRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiY2Y0NTY4OWEt + NTUzNy00NjVkLWFkNDQtNDYwZGJlYmY0ODAyIiwibmFtZSI6IlRyYWNrIC0iLCJncm91cElkIjoiNDYwYzgxNjQtZWJmNi00NWIwLWIyMjEtYTJmZTc0ZTg4ODg3Iiwi + c291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAxLCJtYXhTdGVw + U2l6ZSI6MC4wMDAxLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ijc2NDY1YjVmLWM5YjMtNGFhNi1i + YTc3LWI4OTEwMjFjZjM4ZSIsIm5hbWUiOiJUcmFjayAtIGZhc3RlciIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRlODg4ODciLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDEsIm1heFN0ZXBTaXpl + IjowLjAwMDEsIm1pblByZXNzTWlsbGlzIjozMDAsIm1heFByZXNzTWlsbGlzIjozMDAsInR1cmJvUmF0ZSI6NjAsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRi + YWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImQ0MGZmYTQ0LWE0NDYtNDQ5OS1iOWQ2LTc4N2M1ZmY4ZTE4OCIsIm5hbWUi + OiJUcmFjayArIiwiZ3JvdXBJZCI6IjQ2MGM4MTY0LWViZjYtNDViMC1iMjIxLWEyZmU3NGU4ODg4NyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaC1yaWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDEsIm1heFN0ZXBTaXplIjowLjAwMDEsImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6Ijk3ZGI5OTFj + LWExNWItNGMyNy1hZWIyLTA0YjJhZTMyNDJjMiIsIm5hbWUiOiJUcmFjayArIGZhc3RlciIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAtYjIyMS1hMmZlNzRl + ODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmlnaHQifSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wMDAx + LCJtYXhTdGVwU2l6ZSI6MC4wMDAxLCJtaW5QcmVzc01pbGxpcyI6MzAwLCJtYXhQcmVzc01pbGxpcyI6MzAwLCJ0dXJib1JhdGUiOjYwLCJmZWVkYmFja0NvbG9yIjpu + dWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI0OWQ4YjAzMC1mMmI3LTQ2YzYtODc3Mi0yNjhhZTQxYWY0MDMiLCJuYW1lIjoiVHIxIFZvbCIs + Imdyb3VwSWQiOiJiZTcyN2ZkYy1iNGY5LTRmNzEtOTk0NC0wMjJmZTRkYmFkYWMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiYjZlYzNmYmYtOWFlYi00ODhkLTljNzItMGJlYzMwMzBiMGIxIiwibmFtZSI6IlRyMiBWb2wiLCJn + cm91cElkIjoiYmU3MjdmZGMtYjRmOS00ZjcxLTk5NDQtMDIyZmU0ZGJhZGFjIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImRkNThjMTM0LTQyMTQtNGYyNC05Y2NjLTE1YzhhMzU2MWMwMSIsIm5hbWUiOiJUcjMgVm9sIiwiZ3Jv + dXBJZCI6ImJlNzI3ZmRjLWI0ZjktNGY3MS05OTQ0LTAyMmZlNGRiYWRhYyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAyIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI2M2RmZDI1Ni0xNzA1LTQ5NDEtOWQzNS0zZTQ0NzZiYTNlZTIiLCJuYW1lIjoiVHI0IFZvbCIsImdyb3Vw + SWQiOiJiZTcyN2ZkYy1iNGY5LTRmNzEtOTk0NC0wMjJmZTRkYmFkYWMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNTM4YTM0MDgtN2Q0My00NWM1LTk0YmYtMDIwZDczODJjOTRmIiwibmFtZSI6IlRyNSBWb2wiLCJncm91cElk + IjoiYmU3MjdmZGMtYjRmOS00ZjcxLTk5NDQtMDIyZmU0ZGJhZGFjIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImYwMDQ5ZTAxLWRiNWUtNDMzMS05YzJkLThjMWMxOTgxNjg5MSIsIm5hbWUiOiJUcjYgVm9sIiwiZ3JvdXBJZCI6 + ImJlNzI3ZmRjLWI0ZjktNGY3MS05OTQ0LTAyMmZlNGRiYWRhYyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7InR5cGUiOjIsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA1IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJhOTgwZWQ2NS0yY2JjLTRlZDQtODBmYS1hMjQ2OGZmOGFlZmUiLCJuYW1lIjoiVHI3IFZvbCIsImdyb3VwSWQiOiJi + ZTcyN2ZkYy1iNGY5LTRmNzEtOTk0NC0wMjJmZTRkYmFkYWMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJ0eXBlIjoyLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNTQ5YTBmM2EtOWJlNC00Y2I4LTk2OWYtNTExYjVkYTgzMGM3IiwibmFtZSI6IlRyOCBWb2wiLCJncm91cElkIjoiYmU3 + MjdmZGMtYjRmOS00ZjcxLTk5NDQtMDIyZmU0ZGJhZGFjIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6MiwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDciLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImJmNjZmN2Q1LTc0YmUtNDA5OC1iZDMzLTc1ZjUzNjM3MTViNyIsIm5hbWUiOiJUcjEgUGFuIiwiZ3JvdXBJZCI6IjliNTRj + ZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGViNDgxNDU3MCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wNSwibWF4U3RlcFNpemUi + OjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDAiLCJmeEFuY2hvciI6Imlk + IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjRlN2I5ZjllLTI4YzUtNDZkOC1hZThlLTc5MWJhNjc0NWI2 + NCIsIm5hbWUiOiJUcjIgUGFuIiwiZ3JvdXBJZCI6IjliNTRjZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGViNDgxNDU3MCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QifSwibW9k + ZSI6eyJtaW5TdGVwU2l6ZSI6MC4wNSwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNz + aW9uIjoicDEgKiAxMDAwMCArIDEiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6 + ImU5YTBhZWYwLTMzZjEtNGU4NS1hZTZkLWZhZDllZjgyMGVjOSIsIm5hbWUiOiJUcjMgUGFuIiwiZ3JvdXBJZCI6IjliNTRjZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGVi + NDgxNDU3MCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wNSwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImY5YTYxYzQ3LTU4M2MtNDA5NC04YWIwLTNiMDU1NTQwNzMzZCIsIm5hbWUiOiJUcjQgUGFuIiwiZ3Jv + dXBJZCI6IjliNTRjZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGViNDgxNDU3MCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wNSwi + bWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDMiLCJm + eEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjhhNTdjZWZlLTBmYjktNDFlYi1iZTRm + LTRkYzNiNTNhMTAzZiIsIm5hbWUiOiJUcjUgUGFuIiwiZ3JvdXBJZCI6IjliNTRjZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGViNDgxNDU3MCIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUv + di1wb3QifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wNSwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwi + dHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlfX0seyJpZCI6IjBiMzNhMWMwLWE1NTQtNDZkNS05MTVhLTI4MzA5MTZmNjVmZCIsIm5hbWUiOiJUcjYgUGFuIiwiZ3JvdXBJZCI6IjliNTRjZjQ0LWQyMTQtNGRi + Ny1hYjA3LTY4NGViNDgxNDU3MCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wNSwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjdkYzMxMjljLWM2NDctNGZiOC1iMjRhLWYzNjAyY2Q0ODU3MiIsIm5hbWUiOiJU + cjcgUGFuIiwiZ3JvdXBJZCI6IjliNTRjZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGViNDgxNDU3MCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QifSwibW9kZSI6eyJtaW5TdGVw + U2l6ZSI6MC4wNSwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAx + MDAwMCArIDYiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImU2ZmMwNDBmLWVm + ODItNDgxOC1iYjJlLTcwNTg2NjA1YWU4YSIsIm5hbWUiOiJUcjggUGFuIiwiZ3JvdXBJZCI6IjliNTRjZjQ0LWQyMTQtNGRiNy1hYjA3LTY4NGViNDgxNDU3MCIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDgvdi1wb3QifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wNSwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6NCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDciLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjM1NWRmZmY3LWZkYTMtNDA3ZC04ZjQwLTRmYmFlNTUxMWRjNSIsIm5hbWUiOiJUcjEgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6 + ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODczZDBkNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QifSwi + bW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNr + RXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAwIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0s + ImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjYyOTM2ZDg5LWU1MmItNGYxZi1hZTNhLTUzNWU0ZGU2YmY4NyIsIm5hbWUiOiJUcjIgUGFuIFJlc2V0Iiwi + Z3JvdXBJZCI6ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODczZDBkNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1z + ZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUi + OjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAxIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImRjZjYyYzZmLWQxNDgtNDEyMi04M2NhLWMxNmY3ODgxNmNkZCIsIm5hbWUiOiJUcjMgUGFu + IFJlc2V0IiwiZ3JvdXBJZCI6ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODczZDBkNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDMvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAyIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ijk1ZGM1Nzk0LWUxNzEtNGI2MS04NTMzLTNhMzMxMDczYmNiZCIsIm5hbWUi + OiJUcjQgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODczZDBkNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAzIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImE4YjZkYmYxLWFkZjUtNDhkNC1hNTk5LTk4ZjAxNDAwYjNj + ZiIsIm5hbWUiOiJUcjUgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODczZDBkNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ijc4YTBkNWUwLWM2OWQtNDViNC1hYTcyLWYw + YzZhMzU2NmY4MCIsIm5hbWUiOiJUcjYgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODczZDBkNCIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1ZSI6MC41LCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA1IiwiZnhBbmNob3IiOiJpZCIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjM3Yjc3OGEyLWMyNDMtNDc3 + Ni05YTNjLTkzNzMwOTI3ZWQzMyIsIm5hbWUiOiJUcjcgUGFuIFJlc2V0IiwiZ3JvdXBJZCI6ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODczZDBkNCIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhUYXJnZXRWYWx1 + ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA2IiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJlZjQ2MjY0 + LWE0OWEtNDY3OC04OGYyLWE3MzY5MTE1ZTZlNCIsIm5hbWUiOiJUcjggUGFuIFJlc2V0IiwiZ3JvdXBJZCI6ImYzZGQ0ZWI5LTFmNmUtNGI3Ni04OWY2LTZjNDE3ODcz + ZDBkNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1zZWxlY3QifSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MC41LCJtYXhU + YXJnZXRWYWx1ZSI6MC41LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjQsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA3IiwiZnhB + bmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + IjBmMDA0YzBmLTQzZmItNDc3YS1iYTZiLWRjNjYzNzkzMWJhNSIsIm5hbWUiOiJUcjEgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2 + LTMwYWYyYWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNjcm9sbE1peGVyIjp0cnVlfX0seyJpZCI6ImQwNTlkMzJlLWNjNzMtNGJiMy05ZTk5LTAyMGExMDlk + ZDBjMSIsIm5hbWUiOiJUcjIgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2LTMwYWYyYWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc2VsZWN0In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0 + cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsInNjcm9sbE1peGVyIjp0cnVlfX0seyJpZCI6ImU2MTQwMWZmLTg2NmUtNGU4YS1iNDk4LWMzZTE1ZTcxNDVjNSIsIm5hbWUiOiJUcjMgU2VsZWN0aW9uIiwiZ3Jv + dXBJZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2LTMwYWYyYWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0 + In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMiIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNjcm9sbE1peGVyIjp0cnVlfX0seyJpZCI6IjRj + NGM1Yjk0LWE4NTItNDljNi05YTliLTlhMDI2MGIyNGYxOCIsIm5hbWUiOiJUcjQgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2LTMw + YWYyYWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNjcm9sbE1peGVyIjp0cnVlfX0seyJpZCI6IjA3MDRjNTBkLTlhMjktNGU4OC1hZTQ4LWI5NTZlM2MyZTY2 + MiIsIm5hbWUiOiJUcjUgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2LTMwYWYyYWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFj + a0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + InNjcm9sbE1peGVyIjp0cnVlfX0seyJpZCI6ImFjNjJkNWU2LTk5OTgtNGZhMi04ZDNiLWFjYzQ4MmM3OTgxOSIsIm5hbWUiOiJUcjYgU2VsZWN0aW9uIiwiZ3JvdXBJ + ZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2LTMwYWYyYWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0In0s + Im1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNSIsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNjcm9sbE1peGVyIjp0cnVlfX0seyJpZCI6Ijc0NDBm + MzkzLWMwZWMtNDNjYy04YzA1LTJkMjIwNDc3YzNjZCIsIm5hbWUiOiJUcjcgU2VsZWN0aW9uIiwiZ3JvdXBJZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2LTMwYWYy + YWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2VsZWN0In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNjcm9sbE1peGVyIjp0cnVlfX0seyJpZCI6IjcyNDcwODUzLWNkNmQtNGMzOC1hM2M2LTQ2NjE5ZmY1NGY3NyIs + Im5hbWUiOiJUcjggU2VsZWN0aW9uIiwiZ3JvdXBJZCI6ImI2NGVlNTQ2LTY2YzktNDdjMC04Mjc2LTMwYWYyYWM1OGE1MSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDgvc2VsZWN0In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo2LCJ0cmFja0V4 + cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNj + cm9sbE1peGVyIjp0cnVlfX0seyJpZCI6ImQwZWUxOWQ5LTE0NjgtNDQ3NC05MGJlLWMwNjAzZTA5ZTQ4ZSIsIm5hbWUiOiJUcjEgTXV0ZSIsImdyb3VwSWQiOiJhMDIy + YTg1NC0zYzhjLTRiNTctYmE0ZC02ZjRjNDIyY2ZmZGYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUifSwibW9kZSI6eyJ0 + eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAwIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJhMjI0OGVmZi0yZDBjLTRhZjYtOWQwOS01M2NiMmYwMWJh + YzkiLCJuYW1lIjoiVHIyIE11dGUiLCJncm91cElkIjoiYTAyMmE4NTQtM2M4Yy00YjU3LWJhNGQtNmY0YzQyMmNmZmRmIiwic291cmNlIjp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJl + c3Npb24iOiJwMSAqIDEwMDAwICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7Imlk + IjoiNjU0MTNjZGMtYjA0Mi00M2IwLTlmYjgtNDhjODFkMTU2NWFlIiwibmFtZSI6IlRyMyBNdXRlIiwiZ3JvdXBJZCI6ImEwMjJhODU0LTNjOGMtNGI1Ny1iYTRkLTZm + NGM0MjJjZmZkZiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbXV0ZSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NywidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjMwNDdkNWFiLWQ4MjUtNGRlYS05ZWNlLTJlMWIwZDU3MWYwYSIsIm5hbWUiOiJUcjQgTXV0ZSIs + Imdyb3VwSWQiOiJhMDIyYTg1NC0zYzhjLTRiNTctYmE0ZC02ZjRjNDIyY2ZmZGYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211 + dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAz + IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJkMTcwOTY4Yi0xMDdlLTQyYzUt + OTE1ZC0zMTQwNWZjNjUxZDciLCJuYW1lIjoiVHI1IE11dGUiLCJncm91cElkIjoiYTAyMmE4NTQtM2M4Yy00YjU3LWJhNGQtNmY0YzQyMmNmZmRmIiwic291cmNlIjp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBl + Ijo3LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWV9fSx7ImlkIjoiNDU0YTM1ZTUtZmJkYS00ZTEzLThlY2QtYWY1ZDIyNDhjZmY0IiwibmFtZSI6IlRyNiBNdXRlIiwiZ3JvdXBJZCI6ImEwMjJhODU0LTNj + OGMtNGI1Ny1iYTRkLTZmNGM0MjJjZmZkZiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbXV0ZSJ9LCJtb2RlIjp7InR5cGUiOjIs + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NywidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDUiLCJmeEFuY2hvciI6ImlkIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjNhYjRkMzdkLTU0MTAtNDA0MC1iNTU3LWRlM2Q1Yzk5OGQzZiIsIm5h + bWUiOiJUcjcgTXV0ZSIsImdyb3VwSWQiOiJhMDIyYTg1NC0zYzhjLTRiNTctYmE0ZC02ZjRjNDIyY2ZmZGYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g3L211dGUifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrRXhwcmVzc2lvbiI6 + InAxICogMTAwMDAgKyA2IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI5Yzgy + YzJkMi00ZWE0LTQzZDktYjkxYy05MDQ3ZDFhNDc1MmYiLCJuYW1lIjoiVHI4IE11dGUiLCJncm91cElkIjoiYTAyMmE4NTQtM2M4Yy00YjU3LWJhNGQtNmY0YzQyMmNm + ZmRmIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tdXRlIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJ0eXBlIjo3LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiZDM2ZWNjZDQtZjVlOC00NGZiLWIxZWEtNTM5OGIyMmNmZDc1IiwibmFtZSI6IlRyMSBTb2xvIiwiZ3JvdXBJ + ZCI6IjUxM2I3MWZiLTcyODYtNDhiNC05NDQ3LWY0ZmVjNDUzN2Y0YSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyJ9LCJt + b2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDAiLCJmeEFu + Y2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImE1Y2Q2YTgyLWI5NjYtNGMzZC04M2JjLTI3 + MWZjYmIwNmM0YiIsIm5hbWUiOiJUcjIgU29sbyIsImdyb3VwSWQiOiI1MTNiNzFmYi03Mjg2LTQ4YjQtOTQ0Ny1mNGZlYzQ1MzdmNGEiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRy + YWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAxIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZX19LHsiaWQiOiI5ODlhNDJlMy0xYTg3LTQ0MjMtYjUxMi04YzZkNDI4Y2Q5M2MiLCJuYW1lIjoiVHIzIFNvbG8iLCJncm91cElkIjoiNTEzYjcxZmItNzI4Ni00OGI0 + LTk0NDctZjRmZWM0NTM3ZjRhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zb2xvIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo4LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiZDg2ODVjOWMtNTQxNS00Y2FlLWJmMWItN2NiMGMzMmExOThkIiwibmFtZSI6IlRy + NCBTb2xvIiwiZ3JvdXBJZCI6IjUxM2I3MWZiLTcyODYtNDhiNC05NDQ3LWY0ZmVjNDUzN2Y0YSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDQvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAx + MDAwMCArIDMiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjMyYjk3YTNlLTRl + NzEtNGZkMS04YzgwLWU2NGI2NTViNjY5MyIsIm5hbWUiOiJUcjUgU29sbyIsImdyb3VwSWQiOiI1MTNiNzFmYi03Mjg2LTQ4YjQtOTQ0Ny1mNGZlYzQ1MzdmNGEiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI4NGE3ODU1Zi1kMWU0LTRiMjYtOGMzMy03NjI4ZjUxMDI3ZGYiLCJuYW1lIjoiVHI2IFNvbG8iLCJncm91cElkIjoiNTEz + YjcxZmItNzI4Ni00OGI0LTk0NDctZjRmZWM0NTM3ZjRhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zb2xvIn0sIm1vZGUiOnsi + dHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo4LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNSIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiMzg5MmIzYWUtOGUyNi00Y2VjLWE4OGItZjA1M2Y3Nzlm + Y2YwIiwibmFtZSI6IlRyNyBTb2xvIiwiZ3JvdXBJZCI6IjUxM2I3MWZiLTcyODYtNDhiNC05NDQ3LWY0ZmVjNDUzN2Y0YSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDcvc29sbyJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6OCwidHJhY2tFeHBy + ZXNzaW9uIjoicDEgKiAxMDAwMCArIDYiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJp + ZCI6ImJjN2UwYmEyLTNlYWMtNDliYy05NjA2LWM1OWQzMTY1ZmVmMCIsIm5hbWUiOiJUcjggU29sbyIsImdyb3VwSWQiOiI1MTNiNzFmYi03Mjg2LTQ4YjQtOTQ0Ny1m + NGZlYzQ1MzdmNGEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NvbG8ifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjgsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA3IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI4ZDdjOTMwMy1hODQ4LTRjYmUtOTU4YS03ZTkxMTliNjRiNjAiLCJuYW1lIjoiVHIxIEFybSIs + Imdyb3VwSWQiOiJkMmI2OWQ2ZS05OTlmLTQ3NzctYWUzOS00YjBhMTA4YTNmMDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Jl + Y29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAx + MDAwMCArIDAiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjNlYTUyZDU3LWY4 + OTktNGUxMS1hNmE1LTIxNjY1Y2FkYTc3NCIsIm5hbWUiOiJUcjIgQXJtIiwiZ3JvdXBJZCI6ImQyYjY5ZDZlLTk5OWYtNDc3Ny1hZTM5LTRiMGExMDhhM2YwNCIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvcmVjb3JkLXJlYWR5In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiYTgxMGJhOTItNTJlMi00NWI2LWEwNzUtNWM4YzY0YWQ5OGNhIiwibmFtZSI6IlRyMyBBcm0iLCJncm91cElk + IjoiZDJiNjlkNmUtOTk5Zi00Nzc3LWFlMzktNGIwYTEwOGEzZjA0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVh + ZHkifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjUsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyAy + IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJkZTM2YjUxYS1jZDgwLTQ5Y2Qt + OGYxYi0yZmViYTBiMDExYzUiLCJuYW1lIjoiVHI0IEFybSIsImdyb3VwSWQiOiJkMmI2OWQ2ZS05OTlmLTQ3NzctYWUzOS00YjBhMTA4YTNmMDQiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDMiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImY5OWEyYmI3LTNjOTktNGQwOS05MjIyLTc4Y2Q2N2ZiZjQ1NCIsIm5hbWUiOiJUcjUgQXJtIiwiZ3JvdXBJZCI6ImQyYjY5 + ZDZlLTk5OWYtNDc3Ny1hZTM5LTRiMGExMDhhM2YwNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvcmVjb3JkLXJlYWR5In0sIm1v + ZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNCIsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNjExYWMzZDQtZTUxMy00ODIyLWI4OGEtODlk + N2E2MzFiZjZmIiwibmFtZSI6IlRyNiBBcm0iLCJncm91cElkIjoiZDJiNjlkNmUtOTk5Zi00Nzc3LWFlMzktNGIwYTEwOGEzZjA0Iiwic291cmNlIjp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9yZWNvcmQtcmVhZHkifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUi + OjUsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA1IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZX19LHsiaWQiOiI0ZmExMTEzYi0zMWU3LTQyOWMtYWU4NC04ZTRiZjI5ZDIxZTUiLCJuYW1lIjoiVHI3IEFybSIsImdyb3VwSWQiOiJkMmI2OWQ2ZS05OTlm + LTQ3NzctYWUzOS00YjBhMTA4YTNmMDQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSJ9LCJtb2RlIjp7InR5 + cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDYiLCJmeEFuY2hvciI6Imlk + IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjlhNmE5ZmYwLWQ4ZjMtNGE4Ny1hZTc5LTUyOGQwN2ZlZTY2 + NiIsIm5hbWUiOiJUcjggQXJtIiwiZ3JvdXBJZCI6ImQyYjY5ZDZlLTk5OWYtNDc3Ny1hZTM5LTRiMGExMDhhM2YwNCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDgvcmVjb3JkLXJlYWR5In0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFj + a0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9 + fSx7ImlkIjoiOTljYWJlNTQtMmFjMy00MTQ0LTkyNjgtZDUyZWZlYmQwNTk1IiwibmFtZSI6IlRyMSBUb3VjaCIsImdyb3VwSWQiOiI0Y2I2N2I0ZC1mMzk1LTQyZWMt + YjFiNy1iYjE0ZWMzYzM4NDgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDAiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjZhMTk2ZDRiLTVmZjAtNDM0YS04Y2JlLWIxZTY2ZDY3NTM2NiIsIm5hbWUiOiJUcjIg + VG91Y2giLCJncm91cElkIjoiNGNiNjdiNGQtZjM5NS00MmVjLWIxYjctYmIxNGVjM2MzODQ4Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMi9mYWRlci90b3VjaCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MjEsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAw + MDAgKyAxIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJiODExZDRlNy03Y2Jj + LTQwNTItOWFkYi1iZGI1ZTg1NjQ5MDUiLCJuYW1lIjoiVHIzIFRvdWNoIiwiZ3JvdXBJZCI6IjRjYjY3YjRkLWYzOTUtNDJlYy1iMWI3LWJiMTRlYzNjMzg0OCIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgMiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwi + c2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNmVmNTQzOGEtZDJlZi00ZDc2LWIxOTktZGMyOGRiM2ZkNTNhIiwibmFtZSI6IlRyNCBUb3VjaCIsImdyb3VwSWQiOiI0Y2I2 + N2I0ZC1mMzk1LTQyZWMtYjFiNy1iYjE0ZWMzYzM4NDgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIn0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDMiLCJmeEFuY2hvciI6Imlk + IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImZmYTEyNjMyLWZkYmYtNGQxOS04MjIwLWJlZDAwNjI1MmJk + YSIsIm5hbWUiOiJUcjUgVG91Y2giLCJncm91cElkIjoiNGNiNjdiNGQtZjM5NS00MmVjLWIxYjctYmIxNGVjM2MzODQ4Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlci90b3VjaCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MjEsInRyYWNrRXhwcmVz + c2lvbiI6InAxICogMTAwMDAgKyA0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQi + OiIzYmYyODNkNi02MzYyLTRmYTItYTFlNS1hODc3OTRmNjJjZjAiLCJuYW1lIjoiVHI2IFRvdWNoIiwiZ3JvdXBJZCI6IjRjYjY3YjRkLWYzOTUtNDJlYy1iMWI3LWJi + MTRlYzNjMzg0OCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0V4cHJlc3Npb24iOiJwMSAqIDEwMDAwICsgNSIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiOTNmZmNhMDEtYjhkNC00MTZjLWE3NjMtOTI4ODM3ZmFjMmMxIiwibmFtZSI6IlRyNyBUb3VjaCIs + Imdyb3VwSWQiOiI0Y2I2N2I0ZC1mMzk1LTQyZWMtYjFiNy1iYjE0ZWMzYzM4NDgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2Zh + ZGVyL3RvdWNoIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyMSwidHJhY2tFeHByZXNzaW9uIjoicDEgKiAxMDAwMCArIDYi + LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImNkZjA2MjM4LWZlOGQtNDgzYi1i + MmMxLTBjMzBhZDdmODUzZSIsIm5hbWUiOiJUcjggVG91Y2giLCJncm91cElkIjoiNGNiNjdiNGQtZjM5NS00MmVjLWIxYjctYmIxNGVjM2MzODQ4Iiwic291cmNlIjp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlci90b3VjaCJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6 + MjEsInRyYWNrRXhwcmVzc2lvbiI6InAxICogMTAwMDAgKyA3IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZX19LHsiaWQiOiJhZmEzNTQ1OC0zMTYwLTRmNDktYmI5MC0yNzlhYWU1MTc3OWYiLCJuYW1lIjoiUGxheS9wYXVzZSIsImdyb3VwSWQiOiIzNjg4YWU3My1i + MzllLTQ4MzYtYmMwMi1kMDliMjZiOTJlM2QiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicGxheSJ9LCJtb2RlIjp7InR5cGUiOjIsImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ0cmFuc3BvcnRBY3Rpb24iOiJwbGF5UGF1c2UiLCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiNjdiM2E1MTYtMjQ2Zi00M2E2LWI4NTAtMDUwZWI0MTU1MjBhIiwibmFtZSI6IlN0 + b3AiLCJncm91cElkIjoiMzY4OGFlNzMtYjM5ZS00ODM2LWJjMDItZDA5YjI2YjkyZTNkIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0 + b3AifSwibW9kZSI6eyJ0eXBlIjoyLCJmZWVkYmFja0NvbG9yIjpudWxsLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4ifSwidGFyZ2V0Ijp7InR5cGUiOjE2LCJmeEFu + Y2hvciI6ImlkIiwidHJhbnNwb3J0QWN0aW9uIjoic3RvcCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJm + MTRlYmQ2My0wMTI3LTQ5NmQtOWFjYi0yODFiNWE3YmIzZGIiLCJuYW1lIjoiUmVwZWF0IiwiZ3JvdXBJZCI6IjM2ODhhZTczLWIzOWUtNDgzNi1iYzAyLWQwOWIyNmI5 + MmUzZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjeWNsZSJ9LCJtb2RlIjp7InR5cGUiOjIsImZlZWRiYWNrQ29sb3IiOm51bGwsIm91 + dE9mUmFuZ2VCZWhhdmlvciI6Im1pbiJ9LCJ0YXJnZXQiOnsidHlwZSI6MTYsImZ4QW5jaG9yIjoiaWQiLCJ0cmFuc3BvcnRBY3Rpb24iOiJyZXBlYXQiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiMDA2MjY0OTgtODBhNi00MWQ0LWJjOWYtZjI3MzA5OGUzZGNmIiwibmFtZSI6 + IlJlY29yZCIsImdyb3VwSWQiOiIzNjg4YWU3My1iMzllLTQ4MzYtYmMwMi1kMDliMjZiOTJlM2QiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoicmVjb3JkIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbCwib3V0T2ZSYW5nZUJlaGF2aW9yIjoibWluIn0sInRhcmdldCI6eyJ0eXBlIjox + NiwiZnhBbmNob3IiOiJpZCIsInRyYW5zcG9ydEFjdGlvbiI6InJlY29yZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19 + LHsiaWQiOiIxZjgyN2I3Yi1kYjA0LTQ3OGEtYTA1Yi0zNmU4OThmNmIyNDUiLCJuYW1lIjoiU2NydWIgY29hcnNlIiwiZ3JvdXBJZCI6IjM2ODhhZTczLWIzOWUtNDgz + Ni1iYzAyLWQwOWIyNmI5MmUzZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmRO + YW1lIjoiOTkyIiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsImFjdGl2YXRpb25UeXBlIjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgiOjEsImlz + T24iOmZhbHNlfX0seyJpZCI6ImEyNTkwNjJlLWRmZGUtNGU2Yy04ZTBhLTAxNGEwNzhmYzFiZSIsIm5hbWUiOiJTY3J1YiBmaW5lIiwiZ3JvdXBJZCI6IjM2ODhhZTcz + LWIzOWUtNDgzNi1iYzAyLWQwOWIyNmI5MmUzZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wMDEsIm1heFN0ZXBTaXplIjowLjAwMSwi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6Ijk3NCIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJhY3RpdmF0aW9uVHlwZSI6Im1v + ZGlmaWVycyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjoxLCJpc09uIjp0cnVlfX0seyJpZCI6IjFlNWM2MDRmLTkwNDAtNDdjMC1hYjM3LWEwNTM4 + M2U3YmUwMCIsIm5hbWUiOiJTZWVrIGNvYXJzZS9maW5lIiwiZ3JvdXBJZCI6IjQ2MGM4MTY0LWViZjYtNDViMC1iMjIxLWEyZmU3NGU4ODg4NyIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiJ9LCJtb2RlIjp7InR5cGUiOjIsIm1heFByZXNzTWlsbGlzIjoyNTAsImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwicGFyYW1JbmRleCI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0s + eyJpZCI6ImVlODFjNDA1LWQzNTUtNDVlMC04MGI0LTkxMzYzMzI5NTE5MyIsIm5hbWUiOiJab29tIG9uL29mZiIsImdyb3VwSWQiOiI0NjBjODE2NC1lYmY2LTQ1YjAt + YjIyMS1hMmZlNzRlODg4ODciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoiem9vbSJ9LCJtb2RlIjp7InR5cGUiOjIsIm1heFByZXNzTWls + bGlzIjoyNTAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwicGFyYW1JbmRleCI6MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImYxMzY3OGIxLWRhODEtNDZhNS1iMzE4LTg2MWE4MTNkNzE4MyIsIm5hbWUiOiJQcmV2aW91cyIsImdy + b3VwSWQiOiIzNjg4YWU3My1iMzllLTQ4MzYtYmMwMi1kMDliMjZiOTJlM2QiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmV3aW5kIn0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQwMTcyIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3 + ZjBkMmNiYi0wMzQ2LTQyMjktODlkZC1kYjU4Y2FjM2E0NjAiLCJuYW1lIjoiTmV4dCIsImdyb3VwSWQiOiIzNjg4YWU3My1iMzllLTQ4MzYtYmMwMi1kMDliMjZiOTJl + M2QiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZmFzdC1md2QifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxNzMiLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhiMGNlNWRiLWEzNzEtNGI0Yy05ZmU4LWNhMDg2YmM3MDZiZiIsIm5h + bWUiOiJab29tIG91dCBob3Jpem9udGFsbHkiLCJncm91cElkIjoiZDZhYjVhN2UtZjFhZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2Iiwic291cmNlIjp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwibWF4UHJlc3NNaWxsaXMiOjgwMCwidHVyYm9SYXRlIjoxMDAsInJldmVy + c2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTk4 + IiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5YjZmZTBiOC1kNWYzLTQzOGItOTgzMC02YmI3ODIxMjFjMmQiLCJuYW1lIjoiWm9vbSBpbiBob3Jpem9udGFsbHkiLCJn + cm91cElkIjoiZDZhYjVhN2UtZjFhZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1y + aWdodCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1heFByZXNzTWlsbGlzIjozNTAsInR1cmJvUmF0ZSI6MTAwLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1 + cmJvIn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6Ijk5OCIsImludm9jYXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYzNjMzQ2ODAtMDM5Ni00YjI0LTg3MTQtZTIx + MDA2MmY0NmNkIiwibmFtZSI6Ilpvb20gb3V0IHZlcnRpY2FsbHkiLCJncm91cElkIjoiZDZhYjVhN2UtZjFhZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2Iiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIn0sIm1vZGUiOnsibWF4UHJlc3NNaWxsaXMiOjM1MCwidHVyYm9SYXRlIjoxMDAsImZl + ZWRiYWNrQ29sb3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAxMTIiLCJpbnZvY2F0aW9uVHlwZSI6 + MCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImU5M2U4OTgxLTgxMjctNDQ2Zi1iNWQ5LTM0YmI1NzZmYmMyYiIsIm5hbWUiOiJab29tIGluIHZlcnRpY2FsbHkiLCJncm91cElkIjoiZDZhYjVhN2UtZjFh + ZC00OGY0LThmNTEtM2Q2M2UyNmYwMDk2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci11cCJ9LCJtb2RlIjp7Im1heFByZXNz + TWlsbGlzIjozNTAsInR1cmJvUmF0ZSI6MTAwLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5k + TmFtZSI6IjQwMTExIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4YjZkNGNmNS1kNGYyLTQ3MDEtYWY3MS05MGQ4MzlkYTIzMTgiLCJuYW1lIjoiU2Nyb2xsIGRvd24i + LCJncm91cElkIjoiZDBkODg5NTItY2E3MC00OTllLThjNmUtM2I5NGVhZWZiYjJhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNv + ci1kb3duIn0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDgwMDAwMDAwMDAwMDAwMDcsIm1heFN0ZXBTaXplIjowLjA4MDAwMDAwMDAwMDAwMDA3LCJ0 + dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6 + MCwiY29tbWFuZE5hbWUiOiI5ODkiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImI2MzNjMmQxLWMyODMtNDAyMS1hMWZjLTc2YjZhYmQ0MDdiOSIsIm5hbWUiOiJTY3Jv + bGwgdXAiLCJncm91cElkIjoiZDBkODg5NTItY2E3MC00OTllLThjNmUtM2I5NGVhZWZiYjJhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImN1cnNvci11cCJ9LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVwU2l6ZSI6MC4wOCwidHVyYm9SYXRlIjoxMDAsImZlZWRiYWNrQ29s + b3IiOm51bGwsImZpcmVNb2RlIjoidHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTg5IiwiaW52b2NhdGlvblR5cGUiOjIsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiOWZh + M2RiNi04YmVkLTRjOGEtODJmYS01ODBkM2QwZGE5MmMiLCJuYW1lIjoiU2Nyb2xsIGxlZnQiLCJncm91cElkIjoiZDBkODg5NTItY2E3MC00OTllLThjNmUtM2I5NGVh + ZWZiYjJhIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0In0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAu + MDgwMDAwMDAwMDAwMDAwMDcsIm1heFN0ZXBTaXplIjowLjA4MDAwMDAwMDAwMDAwMDA3LCJ0dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmVl + ZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE0MCIsImludm9jYXRpb25UeXBlIjoy + LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiMzQ0NDA1ZDUtYjUwZC00N2Q0LWEyMmUtYWE5OWE3NDU0YTI1IiwibmFtZSI6IlNjcm9sbCByaWdodCIsImdyb3VwSWQiOiJkMGQ4ODk1Mi1jYTcwLTQ5OWUt + OGM2ZS0zYjk0ZWFlZmJiMmEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0In0sIm1vZGUiOnsidHlwZSI6MSwibWlu + U3RlcFNpemUiOjAuMDgwMDAwMDAwMDAwMDAwMDcsIm1heFN0ZXBTaXplIjowLjA4MDAwMDAwMDAwMDAwMDA3LCJ0dXJib1JhdGUiOjEwMCwicmV2ZXJzZUlzRW5hYmxl + ZCI6dHJ1ZSwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDE0MSIsImludm9j + YXRpb25UeXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiMjYxMDY3ZWYtYzJmMC00MWRlLWIxMGYtNDE4Y2M3MThjZjFjIiwibmFtZSI6Ik1hc3RlciB0b3VjaCIsImdyb3VwSWQiOiI3Y2FjMjA2 + Ni1mMDllLTRjNjktYmJjMy04ZWE4NmJkMzgwMzYiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCJ9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MjEsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyZjQ1Y2RmNi1iYWM5LTRhOTctOGEzZS1j + Mjk2ZDJjYzdlMGYiLCJuYW1lIjoiTWFzdGVyIHZvbHVtZSIsImdyb3VwSWQiOiI3Y2FjMjA2Ni1mMDllLTRjNjktYmJjMy04ZWE4NmJkMzgwMzYiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + bWFpbi9mYWRlciJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX0sIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjozLCJpc09u + IjpmYWxzZX19LHsiaWQiOiI5NzBlMDRjOS1lMjYyLTQ3OGUtODVjZS05ZTg0ODc1ZmI1ZjUiLCJuYW1lIjoiTWFya2VyIiwiZ3JvdXBJZCI6IjdjYWMyMDY2LWYwOWUt + NGM2OS1iYmMzLThlYTg2YmQzODAzNiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYXJrZXIifSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4iLCJidXR0b25Vc2FnZSI6InByZXNzLW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoi + NDAxNTciLCJpbnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwiYXV0 + b21hdGlvbk1vZGVPdmVycmlkZVR5cGUiOiJub25lIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImM0OTIzOGI5LTE4OWUtNDJkMy05ODRhLTk1NWZl + NjNjNDZlOCIsIm5hbWUiOiJHbG9iYWwgcmVhZCIsImdyb3VwSWQiOiI3Y2FjMjA2Ni1mMDllLTRjNjktYmJjMy04ZWE4NmJkMzgwMzYiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoicmVhZCJ9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlw + ZSI6MjYsImNvbW1hbmROYW1lIjoiNDA4NzkiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJ0 + cmFja0F1dG9tYXRpb25Nb2RlIjoxLCJhdXRvbWF0aW9uTW9kZU92ZXJyaWRlVHlwZSI6Im92ZXJyaWRlIn19LHsiaWQiOiJiODA1ZjYyOC0xNmUxLTQyY2EtYWMzNi0z + YmI5NTEzMzgxNWYiLCJuYW1lIjoiR2xvYmFsIHRvdWNoIiwiZ3JvdXBJZCI6IjdjYWMyMDY2LWYwOWUtNGM2OS1iYmMzLThlYTg2YmQzODAzNiIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ3cml0ZSJ9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6MjYsImNvbW1hbmROYW1lIjoiNDA4NzkiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJ0cmFja0F1dG9tYXRpb25Nb2RlIjoyLCJhdXRvbWF0aW9uTW9kZU92ZXJyaWRlVHlwZSI6Im92ZXJyaWRlIn19LHsiaWQiOiIxM2Q3OWQ0Mi00ZDU5LTQ5MzAt + YTk3Ni1iNDM3NGUwYTY5YjYiLCJuYW1lIjoiQ2xpY2siLCJncm91cElkIjoiN2NhYzIwNjYtZjA5ZS00YzY5LWJiYzMtOGVhODZiZDM4MDM2Iiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNsaWNrIn0sIm1vZGUiOnsidHlwZSI6MiwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjowLCJj + b21tYW5kTmFtZSI6IjQwMzY0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI3 + YjkwYzEzNi1mODljLTQ3N2UtYjgxMi01MjViNGE3ZGE1ZWQiLCJuYW1lIjoiVHJhY2sgTENEIiwiZ3JvdXBJZCI6ImI1MmZmM2U4LTdjYjgtNGViZC1iMjc2LTNlOWI0 + OTBlMDEzOCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdubWVudCJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjAyLCJtYXhUYXJnZXRWYWx1ZSI6MC4wMDk4LCJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19 + XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6ImUzODk3ZTQ5LWE4ZjItNGM1Zi1hYzU1LWE0NjRkYzBiZDlhNSIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQx + ODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXNlbGVjdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiI4N2ZjNmVhMi1lODdkLTQ2ZmUtYmJmZS0wZWRjNzRhMmE1NzMiLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTky + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxlY3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiM2QxYWIzY2Ut + MTQwZC00ZmZjLWFhMjYtMWE1NjMwZDYwMGNmIiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjM0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjdlOTk2MDY5LWZhYzctNGQyNC1hYmZhLTgzNGVk + NzA1N2MzMCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjozNSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + NC92LXNlbGVjdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhNDE1NGU5MS0wOGYzLTQwYWItYWQ3Yi02Yjk5MGU0NGQyODMiLCJncm91cElkIjoi + ODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1zZWxlY3QifSwiZmVlZGJh + Y2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYWY1YTE5ZGYtZjFmZi00MjQxLWFiZTYtMWIyOTY5ZjYwZTE4IiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1i + YWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImI5YjUyMzI4LTcxZWQtNDgyYS1hMWJhLTFmOWI4YWNjYWIzZSIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXNlbGVjdCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxYWYzYjYxMS1jZWQ4 + LTQyMDgtOTA5YS02NGI3M2U2YTUxOWIiLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MzksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDgvdi1zZWxlY3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzAxN2MwZTQtYTAzOS00MDhkLTlhYzUtZTNiNDExN2I4 + OTZkIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEw + NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9m + YWRlci90b3VjaCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIzMTc3MjhmNS01ZTQ0LTQxNTctOTE4Ni01MDk5ODIzNjIxMGQiLCJncm91cElkIjoi + Yzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA1LCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIn0sImZl + ZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjI5YTczZDRhLTkwZjMtNDJjMC1iNWJhLTdlMzM5MGI4MDlkMyIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQx + NmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2gifSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiMGQxM2Y4YmUtOWNkZC00MTA1LTllNjItNGY3ODViMzk1YjdmIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0 + MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlci90b3VjaCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIx + OGU2ZTVkMy01MTA5LTRhNTItOGY0Yi1iM2Y5NDU0ZTMwMTkiLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImQ1NWJhZjdiLWZkYmEtNGM0 + ZS1iZjFiLTExZTg0NzQ2MmI1NSIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoxMDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDYvZmFkZXIvdG91Y2gifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYmQ1NjQ5YWMtZDFmMC00ZmY5LWE2YTItNDYwNzVmNjVk + MzljIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEx + MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9m + YWRlci90b3VjaCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIwMmUwNWIwMi05N2MxLTRlZjMtYTVjMS02ODIxYjNhM2EyNTQiLCJncm91cElkIjoi + Yzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTExLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIn0sImZl + ZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImM0NTlmMzdhLTZjOGEtNDQ4My1iODQwLTc2ZmJhYjI4YTU0MCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQz + YTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zZWxlY3QifX0seyJpZCI6ImFhNmFhNWZmLTNjYWMtNDYw + Yi04YTVmLTc1OGI2Yjk2ZmJmNyIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoyNSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMi9zZWxlY3QifX0seyJpZCI6IjhjMzJlZDE0LWViNDAtNDQwYi1hNzVhLTNmOGYyMDRhOWI5MiIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQz + YTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9zZWxlY3QifX0seyJpZCI6Ijg3OTYyZWMyLWU2YjUtNGFi + OS04ZDI4LTQyZWI1ODc0YjljOSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoyNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNC9zZWxlY3QifX0seyJpZCI6ImQwYTY2ZmJiLTdlOWYtNDFiNC1hZGE1LTU0MmNkY2QyYTVjZSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQz + YTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zZWxlY3QifX0seyJpZCI6IjNmZjRhNTNlLTJmZDktNGM0 + NS05NGM5LTBjNzgyODMxNWFhMSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoyOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNi9zZWxlY3QifX0seyJpZCI6IjAyZjMyNGZjLWMwZDItNGQ0MS1iMmM0LTBjMGQ4OWJiMWFlOSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQz + YTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zZWxlY3QifX0seyJpZCI6IjMwYWI1ZTg1LTg1MzItNDM2 + Ny04MDZlLWUxMDRlMzU1ZjRlYSIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjozMSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoOC9zZWxlY3QifX0seyJpZCI6ImI5NmIxMWIzLTRhMzEtNDhlYy04YzMwLWZhZjE2MDY2YmMzOSIsImdyb3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRh + NDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIifX0seyJpZCI6ImZiZjg0NDIwLTdkOTUtNDJjZC05ZDZiLTdiOWM2ZGMzMTM2MiIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2cifSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMTc1YmVhYTktODE0OC00ZWVhLWIxODEtMzg3ZjBiNjNhMTkzIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2 + ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIvdG91Y2gifSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiZTUxNTg1ZGUtMWMzMy00ODQwLTkxOWQtYmJmYjY1Y2RhMjQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODQs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYXJrZXIi + fX0seyJpZCI6IjNlZTQwOGRhLTBmMjAtNDk4MC05YjUzLWU3ZTYyNjQyZmQzZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0LCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVhZCJ9fSx7Imlk + IjoiNTQ1NGZjNTQtMWMyOS00NmE0LTkwNzYtODIwZmM5YzAxODMxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzUsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ3cml0ZSJ9fSx7ImlkIjoiYWUx + MzY0OTMtZDQ1Ni00OGQwLTkwMDUtZjYzYTE1ZTAzZGZhIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0In19LHsiaWQiOiIzM2Y4MTk4 + Yy0yMDQ5LTRkYmMtYTRmMy0xMTkyZDM2ZWMzMmYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0In19LHsiaWQiOiI3YjI1OWY3Ni1i + MDFlLTQ2YTQtYmZhYi02OGM1MzZhYmNkNTAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCJ9fSx7ImlkIjoiZTZkYTljNzMtN2Zh + OC00NTBlLTgzNWYtNDAzNDQzNTE3MmJiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0In19LHsiaWQiOiIzNDE3ODBjMy01OTM0 + LTRhMjQtOWY1My0xNWQ0N2IzYzBiY2YiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJld2luZCJ9fSx7ImlkIjoiZjZiYWZmMWEtYzFjYS00MDc5 + LWEwNTgtMWRmMmM3NjBhNjBkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmYXN0LWZ3ZCJ9fSx7ImlkIjoiN2EzZmE0N2QtOWE1Zi00YjZlLWI5 + NGEtZTdhODI0NTI0ZjkxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5In19LHsiaWQiOiJjNmQ2ZTQ3ZC0zNjU4LTRkNjYtODZlYy0zMTA2 + ZjY3N2YzNTAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AifX0seyJpZCI6IjcxZDQwNzAxLWNmMDktNDFlNS1iZjVjLWRhMzRmNzBjNjZk + YyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVjb3JkIn19LHsiaWQiOiJiYWJhNGRjZi02NTQ3LTQ2ODAtYmFiOS0zZjFjZTc0ZWQxZTEiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN5Y2xlIn19LHsiaWQiOiJlZTM0Yzc2NS05N2I5LTQ3YzEtYjBhYy04OTYzNTcxMzAxMzciLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIn19LHsiaWQiOiJhMDA5YTYzZC1lYmNkLTRkZGMtOTRkYy03ZmJlMmNiNzM2YzkiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiJ9fSx7ImlkIjoiNjhjOTI0ZmUtZWNkMy00YThjLWEzYjAtMjFmOTExNjc1MjkwIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6OTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCJ9fSx7ImlkIjoiNDkzNzQyMGMtNWFhZS00Yzc3LWJiMDktY2E1MTg2NTc2OWFlIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6OTksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQifX0seyJpZCI6IjZjMzgzNDIwLTZjMzUtNDg0OS04YTRkLTFhYjVlZDQ5YTk0NiIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjk2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXVwIn19LHsiaWQiOiI5Yzk4MWU2MS1mMzAwLTQzOGMtOWEyZC00YjE5YjBmNDRlZTkiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo5NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIn19LHsiaWQiOiJjOTFmN2ZjNS04MDM5LTQ5MWYtOWQ3Ni1lNDMzMDk5OTQ1NDAiLCJncm91cElkIjoiNzkxNGZiNDkt + N2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIifX0seyJpZCI6IjQ4YjNiMjA1LWYyMWUtNDBkOC05NjAxLTcxZTM4YjBhNTJhZSIsImdyb3VwSWQiOiI3OTE0 + ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciJ9fSx7ImlkIjoiZTE5Y2M3Y2MtOWViYS00M2M2LWIxNWMtMDFkZGYyNzcyMGI5IiwiZ3JvdXBJZCI6 + Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyIn19LHsiaWQiOiI5YTg1NmUyMC1kOWJlLTRiMzMtOGZhZi02MGViZWQ1MTc3NjYiLCJncm91 + cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIifX0seyJpZCI6IjNkMzRkZmEyLWY1NmQtNDI2OC04NWYxLTVlYmQ4M2JiZjUxYiIs + Imdyb3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NCwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciJ9fSx7ImlkIjoiMzQ0ZjU4MDEtNzJiYi00NzdmLThlMWQtNTYyOGFhYjVm + NGMxIiwiZ3JvdXBJZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo1LCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIn19LHsiaWQiOiJlMTQ0MjY3ZS04ZmQ2LTQyMGItYjg4Zi1mMjkz + NGU4NmEyYTQiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjYsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIifX0seyJpZCI6Ijg2NmVmYjYwLWNhNGUtNGY0ZC05YzVj + LTQzMjI5NDMzYjU2OSIsImdyb3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Nywi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlciJ9fSx7ImlkIjoiMDM0NWQ0ZDctMDU5Mi00Mzlh + LTgyYjEtYTIxZjExNjNmYjBlIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjE2LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QifSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzVlOTJlY2MtMGZhOC00OTM5LWJlYzItZWU2ZGY0Y2VmOTM3IiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZl + OC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90In0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNGFhYzgyOGMtZWRiMC00YmFmLTllOWQtYjIwYmJlNGZlZTUwIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3 + LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMDgwYWExZmUtZmJlYy00YmIxLWIxZGEt + MjM2MWQyODA2NzYzIiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ5 + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZh + bHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2ExZmY5ZGMtMjg2Yy00ZTI4LWE0OTAtM2I4YTUy + OTdhMzc3IiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFy + YWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiZWViOTg2MDUtZTBlOC00YjVhLTkxYzMtMDJmMDI4Zjg0MzA0IiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4 + Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291 + cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiNTNjNmYyYjYtYmQ0ZS00OTc5LTg5MDktMjdmZWQ1NzE4OGMyIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZj + MCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDQvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODdlYTdjMTYtOTEwMC00Njg1LWE0MGItMzk5ZTQ5NmI5ZTU0 + IiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUxLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4 + MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g0L3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiM2U2YTE3MDItN2I0MC00N2M2LWE0NDktZDcxOGRlZWU1YTVkIiwiZ3Jv + dXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiNDdiNDBjNTItYjgzZi00YzljLWFhYzktNmYzZmJkZDAwOTUzIiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNv + dXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAu + MjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + ZmFhNTJiN2QtNWM0NC00MDI5LWIyNWQtN2ZmYTljZTQ2NWUwIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjIxLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDYvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYjNmMzEyY2EtOGI0Ni00MDAxLTlhNTMtZTBhNjcwZTMzZGE5IiwiZ3JvdXBJZCI6 + IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0 + LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2 + L3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMjAyMGM0N2UtNWQ3My00ZTA4LTgxMmEtZTczZGU4NzJlMDdiIiwiZ3JvdXBJZCI6Ijk3ZDhk + YzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOTAzZTkz + NWMtMjVhYi00ZmIwLTkwYTktZDllMWMxZTAzNDkwIiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFu + bmVsIjowLCJudW1iZXIiOjU0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1 + MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90In0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZWEyMjg3NTUtMWJm + Yy00NDg1LWJkNTktMDVjZGQyMDc5ZTI4IiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qi + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMTZmMDUxNWQtYWQ3Ny00NDNkLTk3ZGMtNmEzZmM3OTIzM2FhIiwiZ3JvdXBJZCI6IjMxYmY4MmZkLTFm + Y2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4OCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjU1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90In0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOGFiZDU1MzUtNjYxZS00YjZhLWEzYmUtMmEyMzJkNjExMThmIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhm + Yi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUifX0seyJpZCI6IjQxN2VkMDdkLWNiODgtNDkwYy1i + ZTc3LTFlOTRkMzEwZmZiNSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMi9tdXRlIn19LHsiaWQiOiIyNzVhMjk1Ny05MWU3LTQ4ZTctYWFmNS0zZGQwNmZjYjY4MDIiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1 + ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbXV0ZSJ9fSx7ImlkIjoiZjVmYTRkYzUtZWI5OC00YTFiLTkxZmUt + YTY0MTMxMmZhZjQ3IiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjE5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g0L211dGUifX0seyJpZCI6ImExMDQ5ZDk4LTRjYWYtNDJiMy1iZDIwLTA5YmI0Y2JiMTE0YiIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0z + MzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIn19LHsiaWQiOiI4NjkxNTQ3MS1hNDViLTQzMTUtOTMwMS03MjI3 + ZGU3YzAwM2QiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDYvbXV0ZSJ9fSx7ImlkIjoiYjQ0MjhhNzItYTVhYi00ODEyLWIzYWQtNTE4OGQ3ODBiNjdiIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYx + ZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUifX0seyJpZCI6IjdhZjhjMDZjLTg5MzItNDVjNC05ZDk0LWVhOWY2YTc2 + ZWEzNiIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoy + MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9t + dXRlIn19LHsiaWQiOiJlNTBlZGFjNS0yNGNlLTQ4MTUtYmEzMy03NTM5MGUxYTIwOWIiLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdk + YzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zb2xvIn19LHsiaWQiOiI1MTVlZjNjMi0xNjZhLTRlMzUtODdmOC1mOTg1YzMyOWZlYzQi + LCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OSwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIn19 + LHsiaWQiOiI4ZGUyMmMyZS0yNTAwLTRjODItOWM3YS1kMjliY2UyOGM1MzgiLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc29sbyJ9fSx7ImlkIjoiZTc3ZjA5NWItYzY2MS00MGQyLTkwMmItOWI3YTBhYWMxMTM0IiwiZ3Jv + dXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8ifX0seyJp + ZCI6IjEwZmZmMzgxLWE3MzYtNDI0MC1hMTE4LTNiZjUxODNkMjNiNCIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zb2xvIn19LHsiaWQiOiJjZDBhNGM1NC1mMTMxLTRmNzItYmM5ZC1iMzA3ODY5NDIzYjYiLCJncm91cElk + IjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyJ9fSx7ImlkIjoi + N2RlNWY5ZTYtODU3OC00ODY0LWI5MGYtZjkwYzZhMjk3NjY0IiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NvbG8ifX0seyJpZCI6IjgyNDI5MTU1LTY4ZmMtNDQ4Yy04MzQ0LTkzMmM4NTQ5NTZlZiIsImdyb3VwSWQiOiI3 + OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNSwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIn19LHsiaWQiOiIxNWQy + ODI0MS0wOTkwLTQ1NDUtOGNkNy1iZDE5MGViZjI3NGYiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkifX0seyJpZCI6Ijc3N2Q5ZDZlLWExODQtNGI1OC1hZTA4LWIzYjY1MjFkMmRlOSIsImdyb3VwSWQi + OiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3JlY29yZC1yZWFkeSJ9fSx7 + ImlkIjoiMzQyODVjMjItMzQ5MS00ZjUwLThkNmItYmFjMmRkMjg0YjE0IiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvcmVjb3JkLXJlYWR5In19LHsiaWQiOiJhY2MxZmFjZC0zOTU0LTRmMTQtYTc3NC1jZTQ1MGNjMTliMzYi + LCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MywiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9yZWNvcmQt + cmVhZHkifX0seyJpZCI6Ijg3YWVjOWIzLWU0MTAtNGQzYy05NzVmLWI1MmFjMTFmMGUwYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3 + YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3JlY29yZC1yZWFkeSJ9fSx7ImlkIjoiY2QyZGZlNjQtMzNjMi00Y2NkLThhN2QtYzE2 + NmI1ZmQ0NmEzIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDYvcmVjb3JkLXJlYWR5In19LHsiaWQiOiJjOTkxNTFjOS1jYTAxLTQ4ODAtYWQxNy01YTFhYTY2OGZiZTMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkz + ZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9yZWNvcmQtcmVhZHkifX0seyJpZCI6IjI0NGQxMTk5LTgwY2EtNDNm + ZS1iNzdkLWRhYTNlZTU5NGU4YyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g4L3JlY29yZC1yZWFkeSJ9fSx7ImlkIjoiMWQ2NDM2NjMtZTA4Zi00YjM2LTg2MGEtNWEwZDY4YTU5ZmJlIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6ODUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6Im51ZGdlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6Ijg4N2Q4MzE2LTJmY2EtNGZh + OS05YWE2LWIzMWUxNjdkNTE0YSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9wIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6ImJkZjExMGNhLTg1NzAtNDIxNS1hNWE3LTQ4OGQyNDEwMzc0YSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjg4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJyZXBsYWNlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjBlZWFiNDI0LTkwNDMtNDhjOC1iNGY2LTU0 + ZWZjNWU4OGQyYyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjbGljayIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZX19LHsiaWQiOiIzY2ViYTRkNC1kNGU1LTQ3NDUtODQ0NS04YzY1NWY0MGNiNGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5 + MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic29sbyIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiJlZjUzY2Y3MS0zNDRiLTQ3OTktYTYxNi01OTY1MDgwZDNhYTMi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7Imlk + IjoiOTA5YTE0NTMtZDYzMS00NWIzLTg4NjctNTVkNjVkZGFiZTZhIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTUsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYyIiwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjMwZjEzOWM3LWE0ZWYtNDI3OS1iMjZiLTM2ODJkMTc3NzZjOCIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJmMyIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZX19LHsiaWQiOiI2MDcwMTczYy03YmVh + LTQzMDAtOWQzYi02OTczNjI3NjNjMWMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiYmQ5ZTNmODEtNDg1Mi00NTQ0LTlkOGUtMWI5MjQ4ZTZjNTk2Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6NTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImY1IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlfX0seyJpZCI6IjNmODA4NWVkLTU3MWYtNGU1NC1hODFmLTA3YTFi + MWQ3NDdmNCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNiIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZX19LHsiaWQiOiIzNmRjM2ZmNy1hYmMwLTQ1NmItOWM2NC1hOGZlMDY2YTQ3MDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1MywiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMi + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWV9fSx7ImlkIjoiY2RlNmNiNDEtZWI2Zi00YzUzLWE4YjEtMTVhN2Q0NTU4ODk0 + IiwiZ3JvdXBJZCI6IjdiNjEwMDg5LTZlMTQtNDEyYi1hYTI2LWZjZWI2YWM4YmUyMiIsInNvdXJjZSI6eyJ0eXBlIjoxMSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm1pZGlTY3JpcHQiOiJudW0gPSB5ICogOTk7XG5kMSA9IG51bSAlIDEwO1xuZDIgPSAobnVtIC8gMTApICUgMTA7XG5tc2dfc2l6ZSA9IDU7XG4w + W10gPSAweGIwOyBcbjFbXSA9IDB4NGI7IFxuMltdID0gMHgzMCArIGQyOyBcbjNbXSA9IDB4NGE7IFxuNFtdID0gMHgzMCArIGQxOyIsIm9zY0FyZ0luZGV4IjowfSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjIsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL2Fzc2lnbm1lbnQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWV9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1hY2tpZS1jb250cm9sIiwiYWN0aXZlTWFpblByZXNldElkIjoiZGF3LWNvbnRyb2wiLCJwYXJhbWV0ZXJzIjp7IjAi + OnsibmFtZSI6IlRyYWNrIn0sIjEiOnsibmFtZSI6IlNlZWsgY29hcnNlL2ZpbmUifSwiMiI6eyJuYW1lIjoiWm9vbSBvbi9vZmYifX19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 175 552 721 792 + FXID {2F4D7110-8C38-48FB-8AD1-6E201289AF09} + WAK 0 0 + > + + > + + > + + > + > + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-459-touch-automation-reset.rpp b/plugin-reaper-realearn/resources/test-projects/issue-459-touch-automation-reset.rpp new file mode 100644 index 0000000..1b43c58 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-459-touch-automation-reset.rpp @@ -0,0 +1,948 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 2 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.13283129311351 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAYikAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMSIsImlkIjoiaGg4Z1pyeGEiLCJsZXRVbm1hdGNoZWRFdmVudHNUaHJvdWdoIjpmYWxzZSwiY29udHJvbERldmljZUlkIjoi + MTAiLCJmZWVkYmFja0RldmljZUlkIjoiMTEiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiNmU2 + NjliMTctZDRhZi00ZWMxLWE0OTctYTA5ZWQyYjVjOTIwIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MTcsInRyYWNrR1VJRCI6IjQ1QTg4NDhELUQxQjctNEI1Qy05RUZELTY0RDU3NzNCRTFFMyIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjE1YmU1ZDNmLTdiZWEtNGI2 + NS04ZjBlLWI0NGYzYWE5ZmYwNiIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIxLCJ0cmFja0dVSUQiOiI0NUE4ODQ4RC1EMUI3LTRCNUMtOUVGRC02NEQ1NzczQkUx + RTMiLCJmeEFuY2hvciI6ImlkIiwidG91Y2hlZFBhcmFtZXRlclR5cGUiOjIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1YWMxYjZkNGIiLCJuYW1lIjoiRW5j + b2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00 + YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVu + Y29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijox + fX0seyJpZCI6ImRkMWE4MWQzLWQ2MDctNDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJl + ciI6MSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6 + IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEi + LCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMtYjcwNi0yN2I2MmUyYTVkYmUiLCJuYW1lIjoi + RW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVt + YmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7Imlk + IjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwi + Y2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4 + MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUi + OiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRl + eCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJu + dW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsi + aWQiOiI0NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2 + LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTctMjA1NTc2NTMx + ZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFt + ZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4Ijo3fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUiOiJCdXR0b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEs + Im51bWJlciI6NywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0s + eyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjgsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1 + ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJu + YW1lIjoiRW5jb2RlciAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjl9fSx7ImlkIjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MSwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9 + fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdhOTMwNWQtOTRhYi00ZmM5LTliNjQtYTkw + OWU1NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2Ji + M2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEzZDNjMGJmNCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJm + ZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEt + NzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5 + YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIi + OjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0fX0seyJpZCI6IjU1YWEyM2IwLWI4MjAt + NGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjMsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJhOWUzLTI4OGItNGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0 + b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNv + dXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5ZGY1OTAtNjFjYi00NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxN319LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51 + bWJlciI6MTAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7 + ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywi + Y2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOX19XSwiYWN0aXZlQ29u + dHJvbGxlcklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIifQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 527 476 1436 1514 + FXID {2F4D7110-8C38-48FB-8AD1-6E201289AF09} + WAK 0 0 + > + > + + + + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-466-midi-routing-bug.rpp b/plugin-reaper-realearn/resources/test-projects/issue-466-midi-routing-bug.rpp new file mode 100644 index 0000000..bcd79c4 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-466-midi-routing-bug.rpp @@ -0,0 +1,143 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.20516320079249 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAiAAAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMSIsImlkIjoiRXYwYXlxZl8iLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1 + bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9fQ== + AAAQAAAA + > + FLOATPOS 198 466 721 792 + FXID {D794F5BE-0E68-4EF1-8FBD-A72045A159B7} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA3gEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMSIsImlkIjoiUUVVSnNydHIiLCJjb250cm9sRGV2aWNlSWQiOiIyIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjMiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiOTlkY2I5ZTEtZjcxMC00NzJlLTk3NzYtOTM5MTEyZWFlZTVlIiwi + bmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6 + Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dfQ== + AAAQAAAA + > + FLOAT 166 434 721 792 + FXID {5160C614-E8C3-46C7-A2B1-EEEAD0A4A2A3} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-468-jump.RPP b/plugin-reaper-realearn/resources/test-projects/issue-468-jump.RPP new file mode 100644 index 0000000..13dc786 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-468-jump.RPP @@ -0,0 +1,148 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACTBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS42IiwiaWQiOiI4VU9mY1FobSIsImFsd2F5c0F1dG9EZXRlY3RNb2RlIjpmYWxzZSwiY29udHJvbERldmljZUlkIjoiMCIsImZl + ZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkZUcHN3SmYwWGNG + QXNOREY3MnhTcyIsIm5hbWUiOiJNYWtlIHJlbGF0aXZlIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MywibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJp + ZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJhY3RpdmF0aW9uVHlwZSI6Im1vZGlmaWVy + cyIsIm1vZGlmaWVyQ29uZGl0aW9uMSI6eyJwYXJhbUluZGV4IjowLCJpc09uIjpmYWxzZX19LHsiaWQiOiJnSHFLVGxsMFJRZ19zNHI4NGduZUciLCJuYW1lIjoiQWJz + b2x1dGUiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwi + bW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImFjdGl2YXRpb25UeXBlIjoibW9kaWZpZXJzIiwibW9kaWZpZXJDb25kaXRpb24xIjp7InBhcmFtSW5kZXgi + OjAsImlzT24iOnRydWV9fSx7ImlkIjoid05JQ1VNa0lKaVFaZGxRVGg2YmlsIiwibmFtZSI6IlNoaWZ0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6OTgsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjoyLCJt + YXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowfX1dfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 305 68 752 735 + FXID {97C3202B-883C-CE41-957E-F5BCFC5D12C0} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-468-make-relative.rpp b/plugin-reaper-realearn/resources/test-projects/issue-468-make-relative.rpp new file mode 100644 index 0000000..0bb0fe1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-468-make-relative.rpp @@ -0,0 +1,2543 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABlAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJqTmtfRlBCZiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiV05PTFFqcnhJN25LaVlEVnkxWXRqIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTIsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7InR5cGUiOjMsInJldmVyc2VJc0VuYWJsZWQiOnRydWUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsidHlwZSI6MTQsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJj + bGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6ImtQQ2ZGUHpHSFpGYlJSZ0xxeGI4bSIsIm5hbWUiOiIx + Iiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tl + IjpudWxsfSwibW9kZSI6eyJ0eXBlIjozLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQi + fX19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 1675 409 1436 1514 + FXID {46E642BD-845F-4507-A974-DB9967ECC53E} + WAK 0 0 + > + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-470-selected-track-indexes.rpp b/plugin-reaper-realearn/resources/test-projects/issue-470-selected-track-indexes.rpp new file mode 100644 index 0000000..46111f8 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-470-selected-track-indexes.rpp @@ -0,0 +1,786 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.73270941974115 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAQ6UAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xNCIsImlkIjoiNklpZ0E3MHIiLCJjb250cm9sRGV2aWNlSWQiOiI3IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjgiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwiY29udHJvbGxlckdyb3VwcyI6W3siaWQiOiJmYWRlciIsIm5hbWUiOiJGYWRlciJ9LHsiaWQiOiJ2 + LXBvdCIsIm5hbWUiOiJWLVBvdCJ9LHsiaWQiOiJ2LXNlbGVjdCIsIm5hbWUiOiJWLVNlbGVjdCJ9LHsiaWQiOiJmYWRlci10b3VjaCIsIm5hbWUiOiJGYWRlciBUb3Vj + aCJ9LHsiaWQiOiJzZWxlY3QiLCJuYW1lIjoiU2VsZWN0In0seyJpZCI6Im11dGUiLCJuYW1lIjoiTXV0ZSJ9LHsiaWQiOiJzb2xvIiwibmFtZSI6IlNvbG8ifSx7Imlk + IjoicmVjb3JkLXJlYWR5IiwibmFtZSI6IlJlY29yZC1yZWFkeSJ9LHsiaWQiOiJ2LXBvdC1sZWRzIiwibmFtZSI6IlYtUG90IExFRHMifSx7ImlkIjoibGNkIiwibmFt + ZSI6IkxDRCJ9LHsiaWQiOiJtZXRlciIsIm5hbWUiOiJNZXRlciJ9XSwibWFwcGluZ3MiOlt7ImlkIjoiRzE0TllUc2ZEVUhEaXFsbzFDTllfIiwibmFtZSI6IjEiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gxL2ZhZGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwiY29tbWFu + ZE5hbWUiOiI0MDA0NCIsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleGVzWzBdIiwiZnhBbmNob3IiOiJpZCIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoicHhJQU9rUU0zZnhrdUlRVWNRMUdl + IiwibmFtZSI6IjIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + dHlwZSI6MiwiY29tbWFuZE5hbWUiOiI0MDA0NCIsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleGVzWzFdIiwi + ZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiM2xudUFB + aDRnQmVJM3QwWHZEbm5PIiwibmFtZSI6IjMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwiY29tbWFuZE5hbWUiOiI0MDA0NCIsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFj + a19pbmRleGVzWzJdIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9 + fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiJtYWluL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjh9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJtYWluL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJqb2ciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVy + IjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImpvZyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYWluL2Zh + ZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEyfSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJt + YXJrZXIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1hcmtlciIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicmVhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0fSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoicmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoid3JpdGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo3NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaC1sZWZ0Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0IiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaC1yaWdodCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ5fSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gtcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImJhbmstbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjQ2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJi + YW5rLXJpZ2h0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJp + Z2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJyZXdpbmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MX0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJld2luZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZmFzdC1md2QiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJwbGF5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5 + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJzdG9wIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTN9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzdG9wIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJyZWNvcmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo5NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3lj + bGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN5Y2xlIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJ6b29tIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAwfSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijoiem9vbSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic2NydWIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjoxMDF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29yLWxlZnQi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0IiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjdXJzb3ItcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OX0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29yLXVwIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6ImN1cnNvci1kb3duIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjdXJzb3ItZG93biIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoibnVkZ2UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjo4NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im51ZGdlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJkcm9wIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9wIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJyZXBsYWNlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJyZXBsYWNlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjbGljayIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg5 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2xpY2siLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InNvbG8iLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6ImYxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMSIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NX0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImYyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjU2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjMiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImY0Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7Imlk + IjoiZjUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY1IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiZjYiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InNtcHRlLWJlYXRzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6NTN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzbXB0ZS1iZWF0cyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoibGNkL2Fz + c2lnbm1lbnQiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdubWVudCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImxjZC90aW1lY29kZSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2Ui + OnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVjb2RlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MzJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTA0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0s + ImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MjR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEv + ZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY2gxL3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiY2hhcmFjdGVyIjo0LCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDEvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFsw + MDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJCMCAzMCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9ib29zdC1jdXQiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9zaW5n + bGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90 + LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Yt + cG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9tdXRlIiwiZ3JvdXBJZCI6Im11dGUi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OH0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEvcmVjb3JkLXJlYWR5Iiwi + Z3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDEvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6 + eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjowLCJsaW5lIjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoMS9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjF9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDEvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL21ldGVyL3BlYWsiLCJncm91cElk + IjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL21ldGVyL3BlYWsiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozM30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoyNX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6ImNoMi9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJjaDIvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFy + YWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJu + IjoiQjAgMzEgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdN + aWRpUGF0dGVybiI6IkIwIDMxIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L2Jvb3N0 + LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1w + b3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1 + LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gy + L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9zcHJlYWQiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZh + bHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL211dGUiLCJncm91 + cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL211 + dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi9yZWNv + cmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi9sY2QvbGluZTEiLCJncm91cElkIjoibGNk + Iiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MSwibGlu + ZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMi9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbWV0ZXIvcGVh + ayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbWV0ZXIvcGVhayIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Ytc2VsZWN0IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlci90b3VjaCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NlbGVjdCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9fSx7ImlkIjoiY2gzL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjJ9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MTgsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3 + TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUs + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMv + di1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NpbmdsZSIsImdy + b3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNl + VmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDMvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3Nw + cmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDExIGRjYmFdIn0sIm1vZGUiOnsi + bWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMv + bXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDMvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjEwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9sY2QvbGluZTEiLCJn + cm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3Bs + YXlJZCI6MiwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDMvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMv + bWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXNlbGVjdCIsImdyb3VwSWQiOiJ2 + LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3Ytc2Vs + ZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRl + ci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwN30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRl + ci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVj + dCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjN9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDQvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNC92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6MTksImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0 + eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3dyYXAiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNl + VmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDQvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90 + L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAwIGRjYmFdIn0sIm1vZGUi + OnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g0L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDExIGRjYmFd + In0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDQvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTl9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDQvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6ImNoNC9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNC9s + Y2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjMsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUxIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlw + ZSI6MTIsImRpc3BsYXlJZCI6MywibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDQvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAxMSBkY2Jh + XSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDQvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXNlbGVjdCIs + Imdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g1L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9mYWRlci90b3VjaCIsImdy + b3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoNS9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvc2VsZWN0IiwiZ3Jv + dXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1 + L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5u + ZWwiOjR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDUvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIs + InNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3dy + YXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYt + cG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4w + NSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g1L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDAwIGRj + YmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g1L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0 + IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjB9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3NvbG8iLCJncm91cElkIjoic29s + byIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NvbG8iLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6ImNoNS9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xp + bmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJz + b3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NCwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJE + MCBbMDEwMCBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + Ni92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3fSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9mYWRl + ci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOX0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYv + c2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g2L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5 + cGUiOjMsImNoYW5uZWwiOjV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNi92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6 + InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjEsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjox + LjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDYvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3dyYXAiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g2L3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3QvYm9vc3QtY3V0Iiwi + Z3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3Vy + Y2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIw + IDM1IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0 + dGVybiI6IkIwIDM1IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L3NvbG8iLCJn + cm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2 + L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImNoNi9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjB9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDYvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2xjZC9saW5lMiIsImdyb3Vw + SWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NSwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJEMCBbMDEwMSBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoNy92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM4fSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoNy9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMH0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDcvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMwfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwi + c291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNy92LXBvdC9jb250cm9s + IiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjIsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1h + eFN0ZXBTaXplIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDcvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L3dyYXAi + LCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNv + dXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g3L3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qv + Ym9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDEgZGNiYV0ifSwibW9k + ZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRp + UGF0dGVybiI6IkIwIDM2IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MjJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2g3L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g3L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNy9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9yZWNvcmQtcmVhZHkiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNy9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjYs + ImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L2xjZC9s + aW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9sY2QvbGluZTIiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBl + IjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDExMCBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjM5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoOC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjExMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMxfSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L2ZhZGVyIiwiZ3JvdXBJ + ZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC92 + LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4 + L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMCBkY2JhXSJ9LCJt + b2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDEg + ZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUi + OjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zcHJlYWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2g4L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE1fSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVj + b3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9yZWNv + cmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJk + aXNwbGF5SWQiOjcsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2g4L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NywibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9sY2QvbGluZTIi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNv + dXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDExMSBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJpY29uLXBsYXRmb3JtLW0ifQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {D7518B4F-5471-4EA4-B95C-8897584028D7} + WAK 0 0 + > + > + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-474-fx-param-and-send-automation-touch.rpp b/plugin-reaper-realearn/resources/test-projects/issue-474-fx-param-and-send-automation-touch.rpp new file mode 100644 index 0000000..d7766b1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-474-fx-param-and-send-automation-touch.rpp @@ -0,0 +1,1942 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 2 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADnmwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJFYjlDMVVQZSIsImNvbnRyb2xEZXZpY2VJZCI6IjExIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjEyIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6IkJudl9MQ0lzQkhlY3dNZUpQQWRuQiIsIm5hbWUiOiJTd2l0Y2gifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9 + LCJjb250cm9sbGVyR3JvdXBzIjpbeyJpZCI6Ijc5MTRmYjQ5LTdkYjEtNGE0Ni1hOTY4LWJhMWViNTBkNGM1MyIsIm5hbWUiOiJGYWRlcnMifSx7ImlkIjoiOTdkOGRj + ODAtMGM4NS00NWUyLTkyNjctZWZlYzYzMWVjNmMwIiwibmFtZSI6IkVuY29kZXJzIn0seyJpZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIs + Im5hbWUiOiJFbmNvZGVyIHB1c2gifSx7ImlkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwibmFtZSI6IkZhZGVyIHRvdWNoIn0seyJpZCI6 + ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4 + MzYzIiwibmFtZSI6Ik11dGUifSx7ImlkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4IiwibmFtZSI6IlNvbG8ifSx7ImlkIjoiYmJmZGJkNDEt + MjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwibmFtZSI6IlJlY29yZCJ9XSwibWFwcGluZ3MiOlt7ImlkIjoiMnkyNE9yZ1dYNlNQeFA3NzFuNk5JIiwibmFtZSI6 + IlN3aXRjaCBGWCBvbiIsImdyb3VwSWQiOiJCbnZfTENJc0JIZWN3TWVKUEFkbkIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImtleXN0cm9rZSI6bnVsbH0s + Im1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MzYsImZ4QW5jaG9yIjoiaWQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWdzIjpbImZ4Il0sImV4Y2x1c2l2aXR5IjoyLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRl + eCI6MCwicm93X2luZGV4IjowfX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Il8yNDgxc3hXZGZfUFowZUxPZHh2MyIsIm5hbWUiOiJTd2l0Y2ggc2Vu + ZCBvbiIsImdyb3VwSWQiOiJCbnZfTENJc0JIZWN3TWVKUEFkbkIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsi + dHlwZSI6MiwibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MzYsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWdzIjpbInNlbmQiXSwiZXhjbHVzaXZpdHkiOjIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJy + b3dfaW5kZXgiOjB9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiRTExMWFwanRFQlpCU1lTY2luZXJlIiwibmFtZSI6IlN3aXRjaCBiZXR3ZWVuIEZY + IGFuZCBzZW5kIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImtleXN0cm9rZSI6bnVsbCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS92LXNlbGVjdCJ9LCJtb2RlIjp7InR5cGUiOjEsImZlZWRiYWNrQ29sb3IiOm51bGwsInJvdGF0ZUlzRW5hYmxlZCI6dHJ1ZX0sInRh + cmdldCI6eyJ0eXBlIjozNywiZnhBbmNob3IiOiJpZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsImdyb3VwSWQiOiJCbnZfTENJc0JIZWN3TWVKUEFkbkIiLCJjbGlw + U2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJQLXlBZWNBOThYUWRucXJxQWtIM2oiLCJuYW1l + IjoiU2V0IEZYIHBhcmFtIiwidGFncyI6WyJmeCJdLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwia2V5c3Ryb2tlIjpudWxsLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gxL2ZhZGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHJhY2tHVUlEIjoiNDMxQUY4 + REYtOThGRS00OUY5LUIzREItN0Q4ODQzMzRDNjMzIiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IjM4M0U5OUZCLTM4MjktNEY2OC1CRkE2LTgyMDUzQUI1NDRCNiIs + InBhcmFtSW5kZXgiOjEsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fSwiaXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6Ikk2R1NTVDh5ZTJWdGVJVlFjMTI1QyIsIm5hbWUiOiJSZWxlYXNlIEZYIHBhcmFtIiwidGFncyI6WyJmeCJdLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwia2V5c3Ryb2tlIjpudWxsLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyL3RvdWNoIn0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6NDcsInRyYWNrR1VJRCI6IjQzMUFGOERGLTk4RkUt + NDlGOS1CM0RCLTdEODg0MzM0QzYzMyIsImZ4QW5jaG9yIjoiaWQiLCJmeEdVSUQiOiIzODNFOTlGQi0zODI5LTRGNjgtQkZBNi04MjA1M0FCNTQ0QjYiLCJwYXJhbUlu + ZGV4IjoxLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX0s + ImlzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJfTjVUc2Q5S1Jqc0FiREw0LTIxNzMiLCJuYW1lIjoiU2V0IHNlbmQgdm9sIiwidGFncyI6WyJzZW5kIl0sInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJrZXlzdHJva2UiOm51bGwsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6 + MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozLCJmeEFuY2hvciI6ImlkIiwicm91dGVTZWxlY3RvclR5cGUiOiJpZCIsInJvdXRlR3Vp + ZCI6IjQzMUFGOERGLTk4RkUtNDlGOS1CM0RCLTdEODg0MzM0QzYzMyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4 + IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6Ik9TdWstN0JxWXZDSjhMRWZGZnFWbyIsIm5hbWUiOiJSZWxlYXNlIHNlbmQgdm9sIiwidGFn + cyI6WyJzZW5kIl0sInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJrZXlzdHJva2UiOm51bGwsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2gifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0 + eXBlIjo0OCwidHJhY2tHVUlEIjoiNDMxQUY4REYtOThGRS00OUY5LUIzREItN0Q4ODQzMzRDNjMzIiwiZnhBbmNob3IiOiJpZCIsInJvdXRlU2VsZWN0b3JUeXBlIjoi + aWQiLCJyb3V0ZVR5cGUiOiJyZWNlaXZlIiwicm91dGVHdWlkIjoiMjc5NTJENUYtRTU4OC00MEY1LTgwRTMtNkE0QURDOEI5QkQ4IiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3si + aWQiOiJlMzg5N2U0OS1hOGYyLTRjNWYtYWM1NS1hNDY0ZGMwYmQ5YTUiLCJuYW1lIjoiQ2gxIFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2Yt + YTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJr + ZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXNlbGVjdCIsImNsaXBTbG90Ijp7ImFk + ZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6Ijg3ZmM2ZWEyLWU4N2QtNDZmZS1iYmZlLTBlZGM3NGEyYTU3MyIs + Im5hbWUiOiJDaDIgUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjozMywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRl + eCI6MH19fSx7ImlkIjoiM2QxYWIzY2UtMTQwZC00ZmZjLWFhMjYtMWE1NjMwZDYwMGNmIiwibmFtZSI6IkNoMyBQdXNoIiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgt + NDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1zZWxlY3QiLCJjbGlw + U2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI3ZTk5NjA2OS1mYWM3LTRkMjQtYWJmYS04MzRl + ZDcwNTdjMzAiLCJuYW1lIjoiQ2g0IFB1c2giLCJncm91cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MzUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXNlbGVjdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4Ijow + LCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImE0MTU0ZTkxLTA4ZjMtNDBhYi1hZDdiLTZiOTkwZTQ0ZDI4MyIsIm5hbWUiOiJDaDUgUHVzaCIsImdyb3VwSWQiOiI4MWMz + M2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2Vs + ZWN0IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYWY1YTE5ZGYtZjFmZi00MjQx + LWFiZTYtMWIyOTY5ZjYwZTE4IiwibmFtZSI6IkNoNiBQdXNoIiwiZ3JvdXBJZCI6IjgxYzMzYTAyLTZlZDgtNDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVt + bl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJiOWI1MjMyOC03MWVkLTQ4MmEtYTFiYS0xZjliOGFjY2FiM2UiLCJuYW1lIjoiQ2g3IFB1c2giLCJncm91 + cElkIjoiODFjMzNhMDItNmVkOC00MTg0LWJhY2YtYTdiOGJiYmEzOTkyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzgsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNy92LXNlbGVjdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjFhZjNiNjEx + LWNlZDgtNDIwOC05MDlhLTY0YjczZTZhNTE5YiIsIm5hbWUiOiJDaDggUHVzaCIsImdyb3VwSWQiOiI4MWMzM2EwMi02ZWQ4LTQxODQtYmFjZi1hN2I4YmJiYTM5OTIi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVs + bH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5k + ZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMzAxN2MwZTQtYTAzOS00MDhkLTlhYzUtZTNiNDExN2I4OTZkIiwibmFtZSI6IkNoMSBU + b3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjox + MDQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3VjaCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0s + eyJpZCI6IjMxNzcyOGY1LTVlNDQtNDE1Ny05MTg2LTUwOTk4MjM2MjEwZCIsIm5hbWUiOiJDaDIgVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkw + MmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIvdG91Y2giLCJjbGlwU2xv + dCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIyOWE3M2Q0YS05MGYzLTQyYzAtYjViYS03ZTMzOTBi + ODA5ZDMiLCJuYW1lIjoiQ2gzIFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjEwNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgi + OjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMGQxM2Y4YmUtOWNkZC00MTA1LTllNjItNGY3ODViMzk1YjdmIiwibmFtZSI6IkNoNCBUb3VjaCIsImdyb3VwSWQiOiJj + OTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDcsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9m + YWRlci90b3VjaCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjE4ZTZlNWQzLTUx + MDktNGE1Mi04ZjRiLWIzZjk0NTRlMzAxOSIsIm5hbWUiOiJDaDUgVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxs + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIvdG91Y2giLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJ + bmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJkNTViYWY3Yi1mZGJhLTRjNGUtYmYxYi0xMWU4NDc0NjJiNTUiLCJuYW1lIjoiQ2g2 + IFRvdWNoIiwiZ3JvdXBJZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjEwOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19 + fSx7ImlkIjoiYmQ1NjQ5YWMtZDFmMC00ZmY5LWE2YTItNDYwNzVmNjVkMzljIiwibmFtZSI6IkNoNyBUb3VjaCIsImdyb3VwSWQiOiJjOTk5ZGVjMi1mOTk1LTQxNmUt + OTAyYy00MzVlMTM3NDFkNDMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlci90b3VjaCIsImNsaXBT + bG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjAyZTA1YjAyLTk3YzEtNGVmMy1hNWMxLTY4MjFi + M2EzYTI1NCIsIm5hbWUiOiJDaDggVG91Y2giLCJncm91cElkIjoiYzk5OWRlYzItZjk5NS00MTZlLTkwMmMtNDM1ZTEzNzQxZDQzIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTExLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIvdG91Y2giLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRl + eCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJjNDU5ZjM3YS02YzhhLTQ0ODMtYjg0MC03NmZiYWIyOGE1NDAiLCJuYW1lIjoiQ2gxIFNlbCIsImdyb3VwSWQiOiJl + ZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Nl + bGVjdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImFhNmFhNWZmLTNjYWMtNDYw + Yi04YTVmLTc1OGI2Yjk2ZmJmNyIsIm5hbWUiOiJDaDIgU2VsIiwiZ3JvdXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc2VsZWN0IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5f + aW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiOGMzMmVkMTQtZWI0MC00NDBiLWE3NWEtM2Y4ZjIwNGE5YjkyIiwibmFtZSI6IkNoMyBTZWwiLCJncm91cElk + IjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjYsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + My9zZWxlY3QiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI4Nzk2MmVjMi1lNmI1 + LTRhYjktOGQyOC00MmViNTg3NGI5YzkiLCJuYW1lIjoiQ2g0IFNlbCIsImdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1v + ZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29s + dW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImQwYTY2ZmJiLTdlOWYtNDFiNC1hZGE1LTU0MmNkY2QyYTVjZSIsIm5hbWUiOiJDaDUgU2VsIiwiZ3Jv + dXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJmYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI4LCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDUvc2VsZWN0IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiM2ZmNGE1M2Ut + MmZkOS00YzQ1LTk0YzktMGM3ODI4MzE1YWExIiwibmFtZSI6IkNoNiBTZWwiLCJncm91cElkIjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zZWxlY3QiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIs + ImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIwMmYzMjRmYy1jMGQyLTRkNDEtYjJjNC0wYzBkODliYjFhZTkiLCJuYW1lIjoiQ2g3IFNlbCIs + Imdyb3VwSWQiOiJlZGRmZjNmNi03MzFhLTQzYTAtYmJkYi00NjVlYTZjZjMyZmIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMCwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g3L3NlbGVjdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjMwYWI1 + ZTg1LTg1MzItNDM2Ny04MDZlLWUxMDRlMzU1ZjRlYSIsIm5hbWUiOiJDaDggU2VsIiwiZ3JvdXBJZCI6ImVkZGZmM2Y2LTczMWEtNDNhMC1iYmRiLTQ2NWVhNmNmMzJm + YiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpu + dWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc2VsZWN0IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5k + ZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYjk2YjExYjMtNGEzMS00OGVjLThjMzAtZmFmMTYwNjZiYzM5IiwibmFtZSI6Ik1hc3Rl + ciBGYWRlciIsImdyb3VwSWQiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRjNTMiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6OCwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlciIsImNsaXBTbG90Ijp7ImFk + ZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImZiZjg0NDIwLTdkOTUtNDJjZC05ZDZiLTdiOWM2ZGMzMTM2MiIs + Im5hbWUiOiJKb2cgV2hlZWwiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2ciLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRl + eCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIxNzViZWFhOS04MTQ4LTRlZWEtYjE4MS0zODdmMGI2M2ExOTMiLCJuYW1lIjoiTWFzdGVyIFRvdWNoIiwiZ3JvdXBJ + ZCI6ImM5OTlkZWMyLWY5OTUtNDE2ZS05MDJjLTQzNWUxMzc0MWQ0MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + bWFpbi9mYWRlci90b3VjaCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImU1MTU4 + NWRlLTFjMzMtNDg0MC05MTlkLWJiZmI2NWNkYTI0MyIsIm5hbWUiOiJNaXhlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg0LCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJtYXJrZXIiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIzZWU0MDhkYS0w + ZjIwLTQ5ODAtOWI1My1lN2U2MjY0MmZkM2QiLCJuYW1lIjoiUmVhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0LCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJy + ZWFkIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiNTQ1NGZjNTQtMWMyOS00NmE0 + LTkwNzYtODIwZmM5YzAxODMxIiwibmFtZSI6IldyaXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzUsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwi + Y2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYWUxMzY0OTMtZDQ1Ni00OGQwLTkwMDUt + ZjYzYTE1ZTAzZGZhIiwibmFtZSI6IlByZXYgQ2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCIsImNs + aXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjMzZjgxOThjLTIwNDktNGRiYy1hNGYzLTEx + OTJkMzZlYzMyZiIsIm5hbWUiOiJOZXh0IENoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0IiwiY2xp + cFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiN2IyNTlmNzYtYjAxZS00NmE0LWJmYWItNjhj + NTM2YWJjZDUwIiwibmFtZSI6IlByZXYgQmFuayIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLWxlZnQiLCJj + bGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJlNmRhOWM3My03ZmE4LTQ1MGUtODM1Zi00 + MDM0NDM1MTcyYmIiLCJuYW1lIjoiTmV4dCBCYW5rIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDcsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQi + LCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIzNDE3ODBjMy01OTM0LTRhMjQtOWY1 + My0xNWQ0N2IzYzBiY2YiLCJuYW1lIjoiVHJhbnMgbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkxLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQi + LCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJmNmJhZmYxYS1jMWNhLTQwNzktYTA1 + OC0xZGYyYzc2MGE2MGQiLCJuYW1lIjoiVHJhbnMgcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZmFzdC1m + d2QiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI3YTNmYTQ3ZC05YTVmLTRiNmUt + Yjk0YS1lN2E4MjQ1MjRmOTEiLCJuYW1lIjoiUGxheSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk0LCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5IiwiY2xp + cFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYzZkNmU0N2QtMzY1OC00ZDY2LTg2ZWMtMzEw + NmY2NzdmMzUwIiwibmFtZSI6IlN0b3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic3RvcCIsImNsaXBTbG90Ijp7 + ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjcxZDQwNzAxLWNmMDktNDFlNS1iZjVjLWRhMzRmNzBjNjZk + YyIsIm5hbWUiOiJSZWNvcmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVjb3JkIiwiY2xpcFNsb3QiOnsiYWRk + cmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYmFiYTRkY2YtNjU0Ny00NjgwLWJhYjktM2YxY2U3NGVkMWUxIiwi + bmFtZSI6IkN5Y2xlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJr + ZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN5Y2xlIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6 + IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiZWUzNGM3NjUtOTdiOS00N2MxLWIwYWMtODk2MzU3MTMwMTM3IiwibmFtZSI6 + Ilpvb20iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJv + a2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Inpvb20iLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRl + eCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJhMDA5YTYzZC1lYmNkLTRkZGMtOTRkYy03ZmJlMmNiNzM2YzkiLCJuYW1lIjoiSm9nIFB1 + c2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2Ui + Om51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNjcnViIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgi + LCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiNjhjOTI0ZmUtZWNkMy00YThjLWEzYjAtMjFmOTExNjc1MjkwIiwibmFtZSI6Ilpvb20gb3V0 + IGhvcml6Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJv + a2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhh + dmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6 + IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiNDkzNzQyMGMtNWFhZS00Yzc3LWJiMDktY2E1MTg2NTc2OWFlIiwibmFtZSI6 + Ilpvb20gaW4gaG9yaXoiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + ImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0IiwiY2xpcFNsb3QiOnsi + YWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiNmMzODM0MjAtNmMzNS00ODQ5LThhNGQtMWFiNWVkNDlhOTQ2 + IiwibmFtZSI6Ilpvb20gb3V0IHZlcnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXVwIiwiY2xpcFNs + b3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiOWM5ODFlNjEtZjMwMC00MzhjLTlhMmQtNGIxOWIw + ZjQ0ZWU5IiwibmFtZSI6Ilpvb20gaW4gdmVydCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItZG93biIs + ImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjdhZTdjMzlkLThmOWQtNGFhOC04NDVi + LWQ5NzdiY2IzZjkzYSIsIm5hbWUiOiJDaDEgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5 + cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + MS9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjMwNDc0OWMyLWE2NDQt + NGRkNS05ZGYwLTYzYjkyMWI0OTI1YiIsIm5hbWUiOiJDaDIgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291 + cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMi9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjIyMGY3 + NTQwLWM1NDEtNDY1My04YmUzLTgzN2VhYzg0ZGJhNyIsIm5hbWUiOiJDaDMgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0 + YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJp + ZCI6ImNiZWU3ZGFlLTA5NDEtNDYwMi1iMjc5LTMzNjFiNmIwMDU0MSIsIm5hbWUiOiJDaDQgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5Njgt + YmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51 + bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgi + OjB9fX0seyJpZCI6ImFmOGE5MTJmLWM0MTEtNGNkYi04MWY4LTU0ZWM5YjkyYTRiOCIsIm5hbWUiOiJDaDUgRmFkZXIiLCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00 + YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlz + dHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJy + b3dfaW5kZXgiOjB9fX0seyJpZCI6ImQ2NjA0MjhkLTY3MDItNGU2My04OGI4LTA3MjY3ZTIyOWU5MiIsIm5hbWUiOiJDaDYgRmFkZXIiLCJncm91cElkIjoiNzkxNGZi + NDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2lu + ZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImY4NDEzZTkyLWEzYzgtNDcwOS1iZmJkLWVlMTkyY2IyMmJjMyIsIm5hbWUiOiJDaDcgRmFkZXIiLCJncm91cElk + IjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4Iiwi + Y29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjY2Yjk2MDY0LTdjMmMtNDIzMS05YzAwLTM5YzU5NmZiMzU2YyIsIm5hbWUiOiJDaDggRmFkZXIi + LCJncm91cElkIjoiNzkxNGZiNDktN2RiMS00YTQ2LWE5NjgtYmExZWI1MGQ0YzUzIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjcsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlciIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJC + eUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImY2NmQxZTdkLTA0OTItNDA3MS04ZDY2LTI3OWMzODNkNWM5NSIsIm5hbWUiOiJD + aDEgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJjaGFy + YWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCIs + ImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjExNWJjODg0LWJlMjYtNDVkZS1hOTAx + LTNiY2JmMTQ1OWJlYSIsIm5hbWUiOiJDaDIgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFu + bmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMi92LXBvdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6 + IjFlMDIzZDhhLWVkMWQtNDA0Ny1hM2NiLWU2NmI4YjU4Y2I0NSIsIm5hbWUiOiJDaDMgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2 + MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJr + ZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4Ijow + LCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjFhM2QwNDllLTE0ZmEtNGMyOC1iOTUzLTAyYWI1NWYzZjkxZiIsIm5hbWUiOiJDaDQgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhk + YzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJC + eUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImEzYTIwZjM3LWM5ZmEtNGI4Ni1iMDFmLTM3OWIyMjk1N2MwOCIsIm5hbWUiOiJD + aDUgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFy + YWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIs + ImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjZkNmI5OTE4LWUxYWMtNGZhNy1iNmMw + LTIwNWZhYWI4NDUzNiIsIm5hbWUiOiJDaDYgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFu + bmVsIjowLCJudW1iZXIiOjIxLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7 + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoNi92LXBvdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6 + ImFhNmY3MzdiLWYwNzktNGUwZi1hZTQ2LTZhOTc5MzU0MGEyOCIsIm5hbWUiOiJDaDcgRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhkYzgwLTBjODUtNDVlMi05MjY3LWVmZWM2 + MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJr + ZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4Ijow + LCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjhlZjA3MTk5LWNmYmEtNDdiMi05NmI0LTIzNzk0MTY3NmM3NSIsIm5hbWUiOiJDaDggRW5jIiwiZ3JvdXBJZCI6Ijk3ZDhk + YzgwLTBjODUtNDVlMi05MjY3LWVmZWM2MzFlYzZjMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdCIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJC + eUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjlmN2Y4MzEyLTA1NTYtNGY4MS05ZjA3LWU5NjNlYzc2Njk5ZiIsIm5hbWUiOiJD + aDEgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoxNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gxL211dGUiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQi + OiIyMDRhY2I3MC0yY2RiLTRiZjAtODZjYi1jMDFmZDM3NjBmYjIiLCJuYW1lIjoiQ2gyIE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMy + NjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlz + dHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6 + IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiZjlmNjJjM2MtYTIyNC00ZjQwLWJiYTEtMmYxZWZjZmM4ZjE1IiwibmFtZSI6 + IkNoMyBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQzLTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjE4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDMvbXV0ZSIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJp + ZCI6IjUyYjliYzlhLWNkYzktNGVjNS05NDk3LWI0MWRlMzJhNjNhNSIsIm5hbWUiOiJDaDQgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0z + MzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtl + eXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUiLCJjbGlwU2xvdCI6eyJhZGRyZXNz + IjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJmZWZmODVjYi03OGQ2LTQ1MTktODQ1Zi1jYjQzYWRkNTBkNGEiLCJuYW1l + IjoiQ2g1IE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7 + ImlkIjoiODE1N2Y4ZDUtODJmYi00MjkxLWFjOGEtMmQ3Mjc1MjEzYzAzIiwibmFtZSI6IkNoNiBNdXRlIiwiZ3JvdXBJZCI6IjcxYjVjZGQ4LWZjNTctNDhmYi04NWQz + LTMzMjYxZWFiODM2MyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + a2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbXV0ZSIsImNsaXBTbG90Ijp7ImFkZHJl + c3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImM1NDU3MTkyLTQyYjctNDczYi1iYTc1LTZiNjc1OTE1N2Y1MSIsIm5h + bWUiOiJDaDcgTXV0ZSIsImdyb3VwSWQiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjoyMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19 + LHsiaWQiOiI4NmNhMWJmNS0wZjM5LTRiNjEtOWM0Yy04NzU5YjlhODg1ODciLCJuYW1lIjoiQ2g4IE11dGUiLCJncm91cElkIjoiNzFiNWNkZDgtZmM1Ny00OGZiLTg1 + ZDMtMzMyNjFlYWI4MzYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tdXRlIiwiY2xpcFNsb3QiOnsiYWRk + cmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMmYzNTFhNTUtMTRlNS00Njk2LTkxMjgtNGI2NmMxNjhmZDZiIiwi + bmFtZSI6IkNoMSBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zb2xvIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19 + fSx7ImlkIjoiZGNmNjgyNGUtNzU5Yy00YzZlLTgyMTUtYzUzNTczNDNjMTlmIiwibmFtZSI6IkNoMiBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05 + N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIiwiY2xpcFNsb3QiOnsiYWRk + cmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMzI5YTE5ZDAtOWE1Mi00MmM4LThlNDgtMDY5MmU4ZGQ0ZWViIiwi + bmFtZSI6IkNoMyBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjEwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc29sbyIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9 + fX0seyJpZCI6ImFhNzY3NzZlLTEwNzgtNDdjYS1iMjMwLTMwNzhhYTJjMTc1YSIsIm5hbWUiOiJDaDQgU29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUt + OTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8iLCJjbGlwU2xvdCI6eyJh + ZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI0MzQ2MWZhYS01ZjcxLTRiMWMtYTllYi1mOThjOGNmYjI1NGYi + LCJuYW1lIjoiQ2g1IFNvbG8iLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MTIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51 + bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zb2xvIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6 + MH19fSx7ImlkIjoiMTUyN2NkZDgtMjE4YS00MGNhLTk3NjItMzU1N2UwNGU4MzZmIiwibmFtZSI6IkNoNiBTb2xvIiwiZ3JvdXBJZCI6Ijc5OGFlMzMyLTA4MzEtNGQ4 + ZS05N2E2LWUwZDhlODg3ZGM1OCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyIsImNsaXBTbG90Ijp7 + ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjEyZDk2YjhjLWE4YmQtNGVhYy1iMzdlLWI0MjhjMTQ3NmFj + NyIsIm5hbWUiOiJDaDcgU29sbyIsImdyb3VwSWQiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjoxNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NvbG8iLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4 + IjowfX19LHsiaWQiOiI4NjFlMjdjZi1iYjIxLTQ1NWYtYjBkNS1iMzZmMzJhOTRkODMiLCJuYW1lIjoiQ2g4IFNvbG8iLCJncm91cElkIjoiNzk4YWUzMzItMDgzMS00 + ZDhlLTk3YTYtZTBkOGU4ODdkYzU4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIiwiY2xpcFNsb3Qi + OnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMzE4Y2Q1YjgtZTQyZC00NTY4LTg5ZGQtMTM3YTcxY2Yy + MTZiIiwibmFtZSI6IkNoMSBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3JlY29yZC1yZWFkeSIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJy + b3dfaW5kZXgiOjB9fX0seyJpZCI6ImYxOTU4NTVkLTljZjUtNGMxNi1hOWU5LTBiY2Q3MTQzNDNlMyIsIm5hbWUiOiJDaDIgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQx + LTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9yZWNvcmQtcmVh + ZHkiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI5ZmRmZWFjZS1kNWE2LTRmZWUt + YmQyZi1hZTEzNGEyZWFkMzciLCJuYW1lIjoiQ2gzIFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvcmVjb3JkLXJlYWR5IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1 + bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYWI5YTZmMDYtZGUzMC00NGRkLWJhOTctNjg1OWNkNzc3NThkIiwibmFtZSI6IkNoNCBSZWMiLCJncm91 + cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkzZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MywiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g0L3JlY29yZC1yZWFkeSIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImZkOTlk + ODJjLWRkZDYtNGI3Zi1iNDQ4LWY3NWY3ZmFmNmE5MCIsIm5hbWUiOiJDaDUgUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2Jh + ZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51 + bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoi + QnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIyMDc3NGYxMS03NDQ2LTQyZTgtYTI0NC1jOWRhZDk5OTEwMmIiLCJuYW1lIjoi + Q2g2IFJlYyIsImdyb3VwSWQiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo1LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDYvcmVjb3JkLXJlYWR5IiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19 + fSx7ImlkIjoiNzE5NjQ5ZmEtNzY2YS00NmI2LWFiOWMtZmYxZjcxMDU1NTNiIiwibmFtZSI6IkNoNyBSZWMiLCJncm91cElkIjoiYmJmZGJkNDEtMjViMy00YjU2LTkz + ZDUtMjIxNmRlN2MzYmFkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + ImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIsImNsaXBTbG90 + Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImZkNjkwOTg2LTdjM2EtNGNlMS05NjdhLTgwYjNlNzMy + ODI4MCIsIm5hbWUiOiJDaDggUmVjIiwiZ3JvdXBJZCI6ImJiZmRiZDQxLTI1YjMtNGI1Ni05M2Q1LTIyMTZkZTdjM2JhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9yZWNvcmQtcmVhZHkiLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwi + cm93X2luZGV4IjowfX19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoiYmVocmluZ2VyLXgtdG91Y2gtb25lIiwibWFpbiI6eyJhY3RpdmVNYXBwaW5nQnlHcm91cCI6eyJm + OWM0YzBlYi1iMTMxLTRiNGItYThiMC0yODQ1NjdmMzBkMzciOiJiNzQxMTlmOS04YzQzLTRiZTktOTFiNS01ZWM5NzVlY2YyNzIiLCI1NTI0YjE0Yi00ZmVkLTQyMzgt + YjI5Ny02OTMxYTg3MGNkNDAiOiI1MGVjNDJiNi1kNWZiLTRiZWItOTNlOS01OTU5Y2QwYjQ2ODYiLCI2NWYwYjg3ZS01OGYxLTQ3MjAtOWYzMC1lN2Q3ZGIxYzJmZjYi + OiJhOWQ4NjJkNC1mZDgxLTQ3Y2ItODdlNy00OWRhZWUwNjRjNDMifSwiYWN0aXZlTWFwcGluZ1RhZ3MiOlsic2VuZCJdfX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 141 446 1436 1514 + FXID {0C0F5C6C-DBB4-453B-B324-3AEDF6677B1B} + WAK 0 0 + > + > + + MIDIOUT -1 + MAINSEND 1 0 + + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAIHCNITH4FZAAAAAQHiX6z8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAPkDAACdAgAAAgAAAA== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {383E99FB-3829-4F68-BFA6-82053AB544B6} + + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-479-convert-to-by-id.rpp b/plugin-reaper-realearn/resources/test-projects/issue-479-convert-to-by-id.rpp new file mode 100644 index 0000000..a2f371b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-479-convert-to-by-id.rpp @@ -0,0 +1,803 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.73270941974115 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAb6UAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xNCIsImlkIjoiNklpZ0E3MHIiLCJjb250cm9sRGV2aWNlSWQiOiI3IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjgiLCJkZWZhdWx0 + R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwiY29udHJvbGxlckdyb3VwcyI6W3siaWQiOiJmYWRlciIsIm5hbWUiOiJGYWRlciJ9LHsiaWQiOiJ2 + LXBvdCIsIm5hbWUiOiJWLVBvdCJ9LHsiaWQiOiJ2LXNlbGVjdCIsIm5hbWUiOiJWLVNlbGVjdCJ9LHsiaWQiOiJmYWRlci10b3VjaCIsIm5hbWUiOiJGYWRlciBUb3Vj + aCJ9LHsiaWQiOiJzZWxlY3QiLCJuYW1lIjoiU2VsZWN0In0seyJpZCI6Im11dGUiLCJuYW1lIjoiTXV0ZSJ9LHsiaWQiOiJzb2xvIiwibmFtZSI6IlNvbG8ifSx7Imlk + IjoicmVjb3JkLXJlYWR5IiwibmFtZSI6IlJlY29yZC1yZWFkeSJ9LHsiaWQiOiJ2LXBvdC1sZWRzIiwibmFtZSI6IlYtUG90IExFRHMifSx7ImlkIjoibGNkIiwibmFt + ZSI6IkxDRCJ9LHsiaWQiOiJtZXRlciIsIm5hbWUiOiJNZXRlciJ9XSwibWFwcGluZ3MiOlt7ImlkIjoiRzE0TllUc2ZEVUhEaXFsbzFDTllfIiwibmFtZSI6IjEiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gxL2ZhZGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwiY29tbWFu + ZE5hbWUiOiI0MDA0NCIsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0V4cHJlc3Npb24iOiJzZWxlY3RlZF90cmFja19pbmRleGVzWzBdIiwiZnhBbmNob3IiOiJpZCIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoicHhJQU9rUU0zZnhrdUlRVWNRMUdl + IiwibmFtZSI6IjIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y29tbWFuZE5hbWUiOiI0MDA0NCIsImludm9jYXRpb25UeXBlIjowLCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaW5kZXgiLCJwYXJhbVR5cGUiOiJp + bmRleC1tYW51YWwiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjNsbnVBQWg0 + Z0JlSTN0MFh2RG5uTyIsIm5hbWUiOiIzIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlciJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJjb21tYW5kTmFtZSI6IjQwMDQ0IiwiaW52b2NhdGlvblR5cGUiOjAsInRyYWNrSW5kZXgiOjMsImZ4QW5jaG9yIjoiaWQiLCJy + b3V0ZVNlbGVjdG9yVHlwZSI6ImlkIiwicm91dGVHdWlkIjoiRkEyM0JCOUUtM0QxNS00MkY2LTlFNTctMjJEN0Y1RTQ5MzQ3IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6Im1haW4vZmFkZXIiLCJncm91cElk + IjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImpvZyIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1haW4vZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYWluL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1hcmtlciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg0fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2VyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJyZWFkIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJ3cml0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoid3JpdGUi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoLWxlZnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OH0sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoLXJpZ2h0Iiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6NDl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiYmFuay1sZWZ0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJi + YW5rLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0 + N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJld2luZCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmV3aW5kIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiJmYXN0LWZ3ZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiZmFzdC1md2QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InBsYXkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo5NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InN0b3AiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6InJlY29yZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + cmVjb3JkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjeWNsZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg2fSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Inpvb20iLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxMDB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJzY3J1YiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNjcnViIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk4fSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImN1cnNvci1yaWdodCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0IiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJjdXJzb3ItdXAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImN1cnNvci11cCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29yLWRvd24iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo5N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJudWRnZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibnVkZ2UiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImRyb3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4N30sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImRyb3AiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJlcGxhY2UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo4OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlcGxhY2UiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNsaWNr + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjbGljayIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4Ijoic29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1 + NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMiIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjU1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + ImYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMyIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImY0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmNSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU4fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImY2Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NTl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic21w + dGUtYmVhdHMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJl + YXRzIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJsY2QvYXNzaWdubWVudCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3Bs + YXlUeXBlIjoibWFja2llLXNldmVuIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC9hc3NpZ25tZW50IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoibGNkL3RpbWVjb2RlIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQi + OjJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJsY2QvdGltZWNvZGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1zZWxlY3QiLCJn + cm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMS92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvZmFkZXIvdG91Y2giLCJncm91 + cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDEvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3NlbGVjdCIsImdyb3Vw + SWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9z + ZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVs + IjowfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gxL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDEvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90IiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC93cmFw + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBv + dC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUs + Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoMS92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMCBkY2Jh + XSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoMS92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBb + MDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2gxL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2fSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9zb2xvIiwiZ3JvdXBJZCI6InNvbG8i + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NvbG8iLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImNoMS9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjB9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUx + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3Vy + Y2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBb + MDAwMCBkY2JhXSJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDEvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92 + LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9mYWRlci90 + b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMi9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvc2Vs + ZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gyL3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUi + OjMsImNoYW5uZWwiOjF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYt + cG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAs + ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIv + di1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1w + b3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1 + LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gy + L3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvYm9vc3QtY3V0IiwiZ3Jv + dXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VW + YWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2gyL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMx + IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVy + biI6IkIwIDMxIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + MTd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3NvbG8iLCJncm91 + cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29s + byIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiY2gyL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MSwibGluZSI6MH0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + Mi9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6 + ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoxLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0 + dGVybiI6IkQwIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2gzL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzR9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2gzL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA2fSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoMy9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3Vy + Y2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3YtcG90L2NvbnRyb2wiLCJn + cm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMy92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvd3JhcCIsImdy + b3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNl + VmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDMvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9ib29z + dC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7 + Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0 + ZXJuIjoiQjAgMzIgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJh + d01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxOH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMv + c29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDMvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFkeSIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gzL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MiwibGlu + ZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvbGNkL2xpbmUy + IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoyLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g0L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA3 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoNC9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjd9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvZmFkZXIiLCJncm91cElkIjoi + ZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3YtcG90 + L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1w + b3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDEwIGRjYmFdIn0sIm1vZGUi + OnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + NC92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMSBkY2Jh + XSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NwcmVhZCIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJjaDQvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTF9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDQvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQt + cmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1y + ZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3Bs + YXlJZCI6MywibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDQvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMiIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDExIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MzZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTA4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRi + YWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6Mjh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUvZmFkZXIi + LCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7Imlk + IjoiY2g1L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiJjaDUvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDEwIGRj + YmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAz + NCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2Ui + OnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zaW5nbGUiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMi + LCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3Nw + cmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJjaDUvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2Rl + Ijp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3JlY29yZC1yZWFkeSIsImdyb3Vw + SWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g1L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlw + ZSI6MTIsImRpc3BsYXlJZCI6NCwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDUvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo0LCJsaW5lIjoxfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xj + ZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1l + dGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTAwIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6Mzd9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTA5fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6Mjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJjaDYvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiY2g2L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVy + Ijo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIw + IDM1IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBh + dHRlcm4iOiJCMCAzNSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9ib29zdC1jdXQi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxl + ZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBv + dC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6 + InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6 + MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g2L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tdXRlIiwiZ3JvdXBJZCI6 + Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tdXRlIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MTN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L3JlY29yZC1y + ZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g2L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJz + b3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NSwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo1LCJsaW5lIjox + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g2L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tZXRlci9wZWFrIiwi + Z3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTAxIGRjYmFdIn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tZXRlci9wZWFrIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNoIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJjaDcvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoy + MiwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRp + UGF0dGVybiI6IkIwIDM2IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjox + MCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBv + dC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJ + ZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1 + ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoNy92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc3ByZWFk + IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhT + b3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g3L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9tdXRl + IiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2g3L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2fSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L2xjZC9saW5lMSIsImdyb3Vw + SWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlk + Ijo2LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9t + ZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTEwIGRjYmFdIn0sIm1vZGUiOnsiZmVl + ZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tZXRl + ci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2Vs + ZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1zZWxlY3Qi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRv + dWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTExfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3Rv + dWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc2VsZWN0IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6N30sIm1vZGUiOnsi + ZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9m + YWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjoyMywiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUi + OjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1 + ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvc2lu + Z2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJt + YXhTb3VyY2VWYWx1ZSI6MC43NSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgv + di1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTEgZGNiYV0ifSwi + bW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoOC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyM30sIm1vZGUiOnsiZmVlZGJhY2tDb2xv + ciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoOC9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2g4L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L2xjZC9s + aW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NywibGluZSI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9sY2QvbGluZTEiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjox + MiwiZGlzcGxheUlkIjo3LCJsaW5lIjoxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoOC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTExIGRjYmFdIn0s + Im1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoOC9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Imlj + b24tcGxhdGZvcm0tbSJ9 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1261 305 1436 1514 + FXID {D7518B4F-5471-4EA4-B95C-8897584028D7} + WAK 0 0 + > + > + + + + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {0401AD2E-A852-478E-868C-55C8B80B3886} + WAK 0 0 + > + > + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-485-jump-takeover-improvements.RPP b/plugin-reaper-realearn/resources/test-projects/issue-485-jump-takeover-improvements.RPP new file mode 100644 index 0000000..fafd8ed --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-485-jump-takeover-improvements.RPP @@ -0,0 +1,166 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 11 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.29479771273295 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADoAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMCIsImlkIjoicjMwdnlrOXgiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoiWlZmUlJjcm9DZUpkTWFJenkyWDV3IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NSwidGFrZW92ZXJNb2RlIjoicGlja3VwLXRvbGVyYW50In0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlU2Vs + ZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3Zl + VmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 546 251 752 675 + FXID {2C5280E6-BB28-4147-BBBD-2B5D15A3433B} + WAK 0 0 + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-493-target-last-touched-options.RPP b/plugin-reaper-realearn/resources/test-projects/issue-493-target-last-touched-options.RPP new file mode 100644 index 0000000..b307924 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-493-target-last-touched-options.RPP @@ -0,0 +1,144 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 3 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 0.82 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.28937191492725 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADEAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJUQXl4NEhQWSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3Mi + Olt7ImlkIjoiM2ZJYVBoQ1IxaXJUSWo3MXlhRnBmIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX19LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIwLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiTGFzdExvYWRlZCJ9LCJpbmNsdWRlZFRhcmdldHMiOlsiRnhQYXJhbWV0ZXJWYWx1ZSIsIlJlYXBlckFjdGlvbiIsIlRyYWNrU29sb1N0YXRlIiwiRnhPbk9m + ZlN0YXRlIiwiVHJhY2tWb2x1bWUiLCJUcmFja0FybVN0YXRlIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlJvdXRlVm9sdW1lIiwiVHJhY2tNb25pdG9yaW5nTW9kZSIs + IkF1dG9tYXRpb25Nb2RlT3ZlcnJpZGUiLCJUcmFja011dGVTdGF0ZSIsIlRlbXBvIiwiUGxheVJhdGUiXX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNl + ZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 32 168 752 675 + FXID {498E549B-52F6-D347-B4F7-BBDE52797B33} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-498-preset-navigation-bug.rpp b/plugin-reaper-realearn/resources/test-projects/issue-498-preset-navigation-bug.rpp new file mode 100644 index 0000000..a1ec5e7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-498-preset-navigation-bug.rpp @@ -0,0 +1,185 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAdgUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMi4wLXByZS4zIiwiaWQiOiJDMmhOd0NmWSIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiUEhJTWtrckpVSXNZd0FENHRPZktVIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo5MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjoxLCJy + ZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjEzLCJ0cmFja0dVSUQiOiJFMTNEQjZBOC0wNkI5LTQ4ODEt + QTBFNi00OEM3MDNERDQ5MkEiLCJmeEFuY2hvciI6ImlkIiwiZnhHVUlEIjoiQ0RFRjM3RDktMzI1Qi00OEM0LUIzOUEtMjVGN0RGQUE4QTQ4IiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJXMkRRU3Y4RTJ1LVM5d2E5SS1JVTciLCJuYW1lIjoiMSIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7InR5cGUiOjEsInR1cmJvUmF0ZSI6MjUwLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjoxMywi + dHJhY2tHVUlEIjoiRTEzREI2QTgtMDZCOS00ODgxLUEwRTYtNDhDNzAzREQ0OTJBIiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IkNERUYzN0Q5LTMyNUItNDhDNC1C + MzlBLTI1RjdERkFBOEE0OCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiOHpa + Z2FVQ2psUnY3T2F6M3d3N3pHIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7InR5cGUiOjEsInR1cmJvUmF0ZSI6MjUwLCJmZWVkYmFja0NvbG9yIjpudWxsLCJmaXJl + TW9kZSI6InR1cmJvIiwibWFrZUFic29sdXRlRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjoxMywidHJhY2tHVUlEIjoiRTEzREI2QTgtMDZCOS00ODgxLUEw + RTYtNDhDNzAzREQ0OTJBIiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IkNERUYzN0Q5LTMyNUItNDhDNC1CMzlBLTI1RjdERkFBOEE0OCIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {911432C0-FE04-43CB-AA04-043A2B9069F9} + WAK 0 0 + > + > + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAAAAA + IQAAAAQAAAAEAAAAAQAAAAAAAAAAwGJAAAAAAAAA8D8AAAAAAAAAQAACAAAAAQAAAAAAAAAAsHNAWEIzz7kJ4D9/arx0kxjgPwACAAAAAQAAAAAAAAAAAIRAAAAAAAAA + 8D8AAAAAAADwPwACAAAAAQAAAAAAAAAAcMdA2Qph6RoE8z8AAAAAAAAAQAABAAAAAQAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAgAAAA== + AHN0b2NrIC0gVHJhY2sgRGVmYXVsdAAAAAAA + > + PRESETNAME "stock - Track Default" + FLOAT 1272 368 1043 786 + FXID {CDEF37D9-325B-48C4-B39A-25F7DFAA8A48} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-498-vst3-preset-navigation.rpp b/plugin-reaper-realearn/resources/test-projects/issue-498-vst3-preset-navigation.rpp new file mode 100644 index 0000000..932a59f --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-498-vst3-preset-navigation.rpp @@ -0,0 +1,157 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAARgMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMi4wLXByZS4yIiwiaWQiOiJ0Z3EwQk94biIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiaTEyNDBacG56SktxN3VwSUY0Tkt6IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo5MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjoxLCJy + ZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjEzLCJmeEFuY2hvciI6ImlkIiwiZnhJbmRleCI6MywiZnhH + VUlEIjoiREMwQjE0RTAtQ0Q5NS00MjAyLUExQTUtODZEM0I1Q0Y4MDY4IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MH19LHsiaWQiOiJ4MjQ4akxoWDVJQ3pQT1BjOVI4UjAiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjkyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7InR5cGUiOjEsImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsidHlwZSI6MTMsImZ4QW5jaG9yIjoiaWQiLCJmeEluZGV4IjoxLCJmeEdVSUQiOiJEQzBCMTRFMC1DRDk1LTQyMDItQTFBNS04NkQzQjVDRjgwNjgi + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 450 303 1436 1514 + FXID {A959713A-A6EA-45B6-9879-B250E3459FFF} + WAK 0 0 + BYPASS 0 0 0 + + FLOAT 1928 895 1496 1037 + FXID {DC0B14E0-CD95-4202-A1A5-86D3B5CF8068} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-509-touchosc-color-support.rpp b/plugin-reaper-realearn/resources/test-projects/issue-509-touchosc-color-support.rpp new file mode 100644 index 0000000..8abe9fa --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-509-touchosc-color-support.rpp @@ -0,0 +1,2666 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA4AMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMi4wLXByZS4zIiwiaWQiOiJRVmhvbU13XyIsImNvbnRyb2xEZXZpY2VJZCI6IjFlNDc5ZjdkLTMzYjEtNDE1ZC04NjZmLTA4OTBhOTJhNzdj + YSIsImZlZWRiYWNrRGV2aWNlSWQiOiIxZTQ3OWY3ZC0zM2IxLTQxNWQtODY2Zi0wODkwYTkyYTc3Y2EiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxl + ckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiYmRnWVoxMC1OaFd3SEZSaE1GUHpkIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMS9mYWRlcjEiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoic2VsZWN0ZWQiLCJmeEFuY2hvciI6ImlkIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJSR1V4Uzl2MzhBRERTUjBHaEdrVWkiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8x + L2ZhZGVyMS9jb2xvciIsIm9zY0ZlZWRiYWNrQXJncyI6WyJzdHlsZS5jb2xvci5ycmdnYmIiXX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29s + b3IiOnsicHJvcCI6InRhcmdldC50cmFjay5jb2xvciJ9LCJmZWVkYmFja0JhY2tncm91bmRDb2xvciI6WzAsMCwyNTVdfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFj + a0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {B753F868-ACBA-4704-A9CD-07203660BDD9} + WAK 0 0 + > + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-513-send-automation-feedback-jump.rpp b/plugin-reaper-realearn/resources/test-projects/issue-513-send-automation-feedback-jump.rpp new file mode 100644 index 0000000..4245ff7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-513-send-automation-feedback-jump.rpp @@ -0,0 +1,416 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 3 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA9gEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMi4wLXByZS4zIiwiaWQiOiJ1NlFicVdXZCIsImNvbnRyb2xEZXZpY2VJZCI6IjExIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjEyIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkRBQjBEbHBvVkdOSVB5aUVIZm9PSiIsIm5hbWUiOiIxIiwic291 + cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInJvdXRlU2VsZWN0b3JUeXBlIjoiaWQiLCJy + b3V0ZUd1aWQiOiIyOUEyOTQ3Ni05NEU1LTREM0UtQjM2Qi03MENERTRCQ0E4MDgiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowfX1dfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {C0881FBB-67C6-4232-BE62-AA4F56368660} + WAK 0 0 + > + > + + MIDIOUT -1 + MAINSEND 1 0 + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-518-timer-source.rpp b/plugin-reaper-realearn/resources/test-projects/issue-518-timer-source.rpp new file mode 100644 index 0000000..4960128 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-518-timer-source.rpp @@ -0,0 +1,140 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAA8AgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4xIiwiaWQiOiJvQVN3WUY0bSIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiSGxlNmxmc2JjVjg1azAzdWc4cE84IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJyZWFwZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsInJlYXBlclNvdXJjZVR5cGUiOiJ0aW1lciIsInRpbWVy + TWlsbGlzIjoxMH0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDMsIm1heFN0ZXBTaXplIjoxLjAsImZlZWRiYWNrQ29sb3IiOm51bGwsInJvdGF0ZUlz + RW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjoyLCJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOnRydWUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {8906ECC3-D02A-4955-A5DF-79B1FC24486F} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-526-dev-midi-to-fx-output-midi.rpp b/plugin-reaper-realearn/resources/test-projects/issue-526-dev-midi-to-fx-output-midi.rpp new file mode 100644 index 0000000..be40047 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-526-dev-midi-to-fx-output-midi.rpp @@ -0,0 +1,160 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAhAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4xIiwiaWQiOiJqSmpSZVRodyIsImNvbnRyb2xEZXZpY2VJZCI6IjIiLCJmZWVkYmFja0RldmljZUlkIjoiMyIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJwRHBiVEFmTGw3Vm5VSWo2SzRxN3ciLCJuYW1lIjoiT1NDIHBhZCB0 + byBub3RlIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzIv + cHVzaDEzIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VQ + cm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInJhd01pZGlQYXR0ZXJuIjoiOTAgNDAgWzBnZmUgZGNiYV0iLCJvc2NBcmdJbmRleCI6 + MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1161 340 1436 1514 + FXID {8FACB5B9-EFA4-4490-85F7-961A2E23451B} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {0DAAB291-4545-4268-9AB5-BC71A6A57082} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7gOPKOrByKD8AAAAAAAAAAM5NAD/u5YE8AAAAAAAAAD8AAAAAAACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {752D5DBE-1D28-4BDD-A4FD-A75529A6DBAA} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-526-osc-to-fx-output-midi.rpp b/plugin-reaper-realearn/resources/test-projects/issue-526-osc-to-fx-output-midi.rpp new file mode 100644 index 0000000..f546f88 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-526-osc-to-fx-output-midi.rpp @@ -0,0 +1,161 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABnAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4xIiwiaWQiOiJqSmpSZVRodyIsImNvbnRyb2xEZXZpY2VJZCI6IjJhZTNjYTFjLWM3ZDMtNDI1Ni05NGIzLWU2NjRmZGY2MGU0 + YyIsImZlZWRiYWNrRGV2aWNlSWQiOiIyYWUzY2ExYy1jN2QzLTQyNTYtOTRiMy1lNjY0ZmRmNjBlNGMiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxl + ckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoicERwYlRBZkxsN1ZuVUlqNks0cTd3IiwibmFtZSI6Ik9TQyBwYWQgdG8gbm90ZSIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8yL3B1c2gxMyIsIm9zY0FyZ0luZGV4IjowfSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJyYXdNaWRpUGF0dGVybiI6IjkwIDQwIFswZ2ZlIGRjYmFdIiwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJT + ZWxlY3RlZCJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1110 342 1436 1514 + FXID {8FACB5B9-EFA4-4490-85F7-961A2E23451B} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {0DAAB291-4545-4268-9AB5-BC71A6A57082} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7gOPKOrByKD8AAAAAAAAAAM5NAD/u5YE8AAAAAAAAAD8AAAAAAACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {752D5DBE-1D28-4BDD-A4FD-A75529A6DBAA} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-529-follow-tcp-mcp-visibility.RPP b/plugin-reaper-realearn/resources/test-projects/issue-529-follow-tcp-mcp-visibility.RPP new file mode 100644 index 0000000..8dbd594 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-529-follow-tcp-mcp-visibility.RPP @@ -0,0 +1,746 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.5 + ZOOM 1540.7021574586365 14 0 + VZOOMEX 0 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0.00778865658226 0.00778865658226 + SELECTION2 0.00778865658226 0.00778865658226 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + + + + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADBHwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS44IiwiaWQiOiJtdVJ3MXNrbiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJncm91cHMiOlt7ImlkIjoiWUxicmt0akpKWTZrZll1 + Mm1TYS1oIiwibmFtZSI6Ik5hdmlnYXRlIHdpdGhpbiB0cmFja3MifSx7ImlkIjoic0FwUm9TX1Y3MTg1dWRVcU9YVDBFIiwibmFtZSI6IkR5bmFtaWMifSx7ImlkIjoi + Mkd2NGhsWU9QSXROaHFuUTQ5UDFuIiwibmFtZSI6IkJ5IHBvc2l0aW9uIn1dLCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoibEZo + b3hIaHZ5XzJCTmZfaG95NzY1IiwibmFtZSI6Ikdsb2JhbCB0cmFjayAtIiwiZ3JvdXBJZCI6IllMYnJrdGpKSlk2a2ZZdTJtU2EtaCIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6ImtleWJvYXJkIiwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo0OX19LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjA0NzYxOTA0NzYx + OTA0NzYxNiwibWF4U3RlcFNpemUiOjAuMDQ3NjE5MDQ3NjE5MDQ3NjE2LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJyZXZlcnNlSXNFbmFibGVk + Ijp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibDZHVlVKY2F4QWgxTmJsc0NqV1QzIiwibmFtZSI6Ikdsb2JhbCB0cmFj + ayArIiwiZ3JvdXBJZCI6IllMYnJrdGpKSlk2a2ZZdTJtU2EtaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6 + MSwia2V5Ijo4MX19LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjA0NzYxOTA0NzYxOTA0NzYxNiwibWF4U3RlcFNpemUiOjAuMDQ3NjE5MDQ3NjE5MDQ3 + NjE2LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoieVJSMTVnRkZW + Tnh5VXFaaHlCd3ZCIiwibmFtZSI6IlRDUCB0cmFjayAtIiwiZ3JvdXBJZCI6IllMYnJrdGpKSlk2a2ZZdTJtU2EtaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJv + YXJkIiwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo1MH19LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjA0NzYxOTA0NzYxOTA0NzYxNiwi + bWF4U3RlcFNpemUiOjAuMDQ3NjE5MDQ3NjE5MDQ3NjE2LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwi + dGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJicm93c2VUcmFja3NNb2RlIjoiVHJhY2tzVmlzaWJsZUluVGNwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlZtYUxweEtiblB2 + d21xbnJLMmR0QiIsIm5hbWUiOiJUQ1AgdHJhY2sgKyIsImdyb3VwSWQiOiJZTGJya3RqSkpZNmtmWXUybVNhLWgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2Fy + ZCIsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODd9fSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wNDc2MTkwNDc2MTkwNDc2MTYsIm1h + eFN0ZXBTaXplIjowLjA0NzYxOTA0NzYxOTA0NzYxNiwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjoxNCwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiYnJvd3Nl + VHJhY2tzTW9kZSI6IlRyYWNrc1Zpc2libGVJblRjcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJzczFuTUdfTWl5X2UwS25LbWlqQ20iLCJuYW1lIjoiTUNQIHRy + YWNrIC0iLCJncm91cElkIjoiWUxicmt0akpKWTZrZll1Mm1TYS1oIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJrZXlzdHJva2UiOnsibW9kaWZpZXJz + IjoxLCJrZXkiOjUxfX0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDQ3NjE5MDQ3NjE5MDQ3NjE2LCJtYXhTdGVwU2l6ZSI6MC4wNDc2MTkwNDc2MTkw + NDc2MTYsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MTQsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImJyb3dzZVRy + YWNrc01vZGUiOiJUcmFja3NWaXNpYmxlSW5NY3BBbGxvd1R3b1NlbGVjdGlvbnMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiNWJESnlqNDZxUVFBbjM0UmIwcFBy + IiwibmFtZSI6Ik1DUCB0cmFjayArIiwiZ3JvdXBJZCI6IllMYnJrdGpKSlk2a2ZZdTJtU2EtaCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwia2V5c3Ry + b2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2OX19LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjA0NzYxOTA0NzYxOTA0NzYxNiwibWF4U3RlcFNpemUi + OjAuMDQ3NjE5MDQ3NjE5MDQ3NjE2LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjE0LCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJicm93c2VUcmFja3NNb2Rl + IjoiVHJhY2tzVmlzaWJsZUluTWNwQWxsb3dUd29TZWxlY3Rpb25zIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlZsNC1FRXdrUG92OTNMWU1LbFhfcCIsIm5hbWUi + OiJUQ1AgdHJhY2sgbXVsdGkgLSIsImdyb3VwSWQiOiJZTGJya3RqSkpZNmtmWXUybVNhLWgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImtleXN0cm9r + ZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NTJ9fSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4wNDc2MTkwNDc2MTkwNDc2MTYsIm1heFN0ZXBTaXplIjow + LjA0NzYxOTA0NzYxOTA0NzYxNiwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBl + IjoxNCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiYnJvd3NlVHJhY2tzTW9kZSI6IlRyYWNrc1Zpc2libGVJblRjcEFsbG93VHdvU2VsZWN0aW9ucyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJOZXpwaEdv + NFVuX0dSLXBaclJxWGMiLCJuYW1lIjoiVENQIG11bHRpIHRyYWNrICsiLCJncm91cElkIjoiWUxicmt0akpKWTZrZll1Mm1TYS1oIiwic291cmNlIjp7ImNhdGVnb3J5 + Ijoia2V5Ym9hcmQiLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjgyfX0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDQ3NjE5MDQ3NjE5 + MDQ3NjE2LCJtYXhTdGVwU2l6ZSI6MC4wNDc2MTkwNDc2MTkwNDc2MTYsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6 + MTQsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImJyb3dzZVRyYWNrc01vZGUiOiJUcmFja3NWaXNpYmxlSW5UY3BBbGxvd1R3b1NlbGVjdGlvbnMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiNDRJRVpHVVB1 + UXM3b2RSYmdWY0twIiwibmFtZSI6IlRyYWNrIC0iLCJncm91cElkIjoic0FwUm9TX1Y3MTg1dWRVcU9YVDBFIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQi + LCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fX0sIm1vZGUiOnsidHlwZSI6MSwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjEsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZWE0OEFkY1otemEwMEM2QS1WOHA2IiwibmFtZSI6IlRyYWNrICsiLCJn + cm91cElkIjoic0FwUm9TX1Y3MTg1dWRVcU9YVDBFIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXki + Ojg5fX0sIm1vZGUiOnsidHlwZSI6MSwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiZnhBbmNo + b3IiOiJ0aGlzIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiV2kt + LUZfdGdHNWFvTUdmYnhmUHZ0IiwibmFtZSI6IlZvbHVtZSAtIiwiZ3JvdXBJZCI6InNBcFJvU19WNzE4NXVkVXFPWFQwRSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Imtl + eWJvYXJkIiwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo0MH19LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjEsIm1heFN0ZXBTaXplIjow + LjEsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoi + ZHluYW1pY19tY3AiLCJ0cmFja0V4cHJlc3Npb24iOiJwWzBdIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ikt4YV9JS252Q2VtcUxvMjBCN1hnQSIsIm5hbWUiOiJW + b2x1bWUgKyIsImdyb3VwSWQiOiJzQXBSb1NfVjcxODV1ZFVxT1hUMEUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImtleXN0cm9rZSI6eyJtb2RpZmll + cnMiOjEsImtleSI6Mzh9fSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5TdGVwU2l6ZSI6MC4xLCJtYXhTdGVwU2l6ZSI6MC4xLCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6ImR5bmFtaWNfbWNwIiwidHJhY2tFeHByZXNzaW9uIjoicFswXSIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX19LHsiaWQiOiJycDNZb2dIOG9YUG5kbkp1WF9yQWoiLCJuYW1lIjoiU2V0IHZvbHVtZSBvZiAxc3QgTUNQIHRyYWNrIiwiZ3JvdXBJZCI6IjJHdjRobFlPUEl0Tmhx + blE0OVAxbiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJr + ZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjczfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoiaW5kZXhfbWNwIiwidHJhY2tJbmRleCI6MCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJLTE5XLWpmb0F5U2lLQ2o2RHRJT3giLCJuYW1lIjoiVG9nZ2xlIHZpc2liaWxpdHkgb2YgdHJhY2sgMiIsImdy + b3VwSWQiOiIyR3Y0aGxZT1BJdE5ocW5RNDlQMW4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MCwia2V5Ijo0Nn19LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjI0LCJ0cmFja0luZGV4IjoxLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInRyYWNrQXJlYSI6Im1jcCIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJwYXJhbWV0ZXJzIjp7IjAiOnsibmFtZSI6IlRyYWNrIiwidmFsdWVDb3Vu + dCI6MTAwMDAsInZhbHVlIjowLjAwMDEwMDAxfX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 619 160 752 675 + FXID {378DC0ED-A6E3-814E-99C5-B09E66B05739} + WAK 0 0 + > + > + + + + + + + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-529-instance_track_tcp_index.RPP b/plugin-reaper-realearn/resources/test-projects/issue-529-instance_track_tcp_index.RPP new file mode 100644 index 0000000..cf4d01e --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-529-instance_track_tcp_index.RPP @@ -0,0 +1,166 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 10.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAFAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoiS0NqcnNUakYiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoieVIyelBIaGtDUmVzUmJqeHNUTklIIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJkeW5hbWljX3RjcCIsInRyYWNrRXhwcmVzc2lvbiI6Imluc3RhbmNlX3RyYWNrX3RjcF9p + bmRleCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJN + b3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3Vz + ZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 583 267 752 675 + FXID {AB61C737-AB38-7044-A303-C9A7AE0A84AA} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-548-instance-wide-preset-links.rpp b/plugin-reaper-realearn/resources/test-projects/issue-548-instance-wide-preset-links.rpp new file mode 100644 index 0000000..1bad46f --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-548-instance-wide-preset-links.rpp @@ -0,0 +1,316 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 1 12.5 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAApMgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJ5Qk5Fc01rUyIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1 + YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlLCJr + ZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MSwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiZTU1NDNhZDgt + OWU4MS00YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEs + ImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJj + b2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8y + Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjEsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6ImRkMWE4MWQzLWQ2MDct + NDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFjdGVyIjoxLCJpczE0 + Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEsImNsaXBTbG90Ijp7ImFk + ZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIs + Im5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6 + bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoyLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsi + aWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoyLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMt + YjcwNi0yN2I2MmUyYTVkYmUiLCJuYW1lIjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQi + OmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MywiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAs + InJvd19pbmRleCI6MH19fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MywiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoi + ODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hh + cmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4 + IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24g + Mi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3df + aW5kZXgiOjB9fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVyIDIvMiIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo1LCJjbGlwU2xvdCI6 + eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4Mjcw + ODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9r + ZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1LCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNv + bHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI0NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6NiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00 + YWIwLTk4MTctMjA1NTc2NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlzMTRC + aXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NiwiY2xpcFNsb3QiOnsiYWRk + cmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwi + bmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpu + dWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjcsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJp + ZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUiOiJCdXR0b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6Nywi + Y2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjcsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04 + NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6 + ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo4LCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwi + cm93X2luZGV4IjowfX19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4Ijo4LCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI2 + NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5jb2RlciAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo5LCJjaGFy + YWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OSwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgi + LCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAz + LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OSwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19p + bmRleCI6MH19fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMCwiY2xpcFNsb3Qi + OnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiZjdhOTMwNWQtOTRhYi00ZmM5LTliNjQtYTkwOWU1NTkz + NTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ry + b2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVo + YXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIs + ImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAz + LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjExLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiI2YjhhZDEzMS1i + ZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEs + ImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTEsImNsaXBTbG90 + Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEzZDNj + MGJmNCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlLCJrZXlz + dHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTIsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgi + OjB9fX0seyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJmZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51 + bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoxMiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYTM4NDM1YzAt + M2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6 + MywiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMywiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1 + bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiOTE0NWI4YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFj + a0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4 + IjowfX19LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0LCJjbGlwU2xvdCI6eyJh + ZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMi + LCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2Ui + Om51bGx9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTQsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29s + dW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjU1YWEyM2IwLWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlLCJrZXlzdHJva2UiOm51bGx9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MTUsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJCeUluZGV4IiwiY29sdW1uX2luZGV4IjowLCJyb3dfaW5kZXgiOjB9fX0seyJpZCI6IjY3NzJhOWUzLTI4OGIt + NGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MSwiaXMx + NEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNSwiY2xpcFNsb3QiOnsi + YWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFm + IiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVs + bH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNiwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5f + aW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiZWU5ZGY1OTAtNjFjYi00NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3LCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19 + LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTAs + ImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2UsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxOCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJjb2x1bW5faW5kZXgiOjAsInJvd19pbmRleCI6MH19fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZi + LThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0Ijpm + YWxzZSwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE5LCJjbGlwU2xvdCI6eyJhZGRyZXNz + IjoiQnlJbmRleCIsImNvbHVtbl9pbmRleCI6MCwicm93X2luZGV4IjowfX19XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIiLCJtYWlu + UHJlc2V0QXV0b0xvYWRNb2RlIjoiZm9jdXNlZC1meCIsImluc3RhbmNlUHJlc2V0TGlua0NvbmZpZyI6eyJsaW5rcyI6W3siZngiOnsibmFtZSI6IlZTVDogUmVhRVEg + KENvY2tvcykifSwicHJlc2V0SWQiOiJyZWFlcS1nYWlucyJ9XX0sInVzZUluc3RhbmNlUHJlc2V0TGlua3NPbmx5Ijp0cnVlfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1294 238 1436 1514 + FXID {55BD7690-59FE-46E3-B83E-469EBC203817} + WAK 0 0 + > + > + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAAAAAAAAAFlAAAAAAIXr8T8AAAAAAAAAQAEIAAAAAQAAAEDJzasrz3FA+v///49Y5D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAPkDAACdAgAAAgAAAA== + AAAQAAAA + > + FLOAT 247 930 1043 786 + FXID {596479F9-B991-461A-A2EC-449870743221} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOAT 494 824 683 876 + FXID {FCCD9B80-E747-48DC-BF0E-5763476A585E} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-549-multiplying-selectors.rpp b/plugin-reaper-realearn/resources/test-projects/issue-549-multiplying-selectors.rpp new file mode 100644 index 0000000..b48c191 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-549-multiplying-selectors.rpp @@ -0,0 +1,457 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACCBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4yIiwiaWQiOiJtak11b29HWiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiRUFVSVFJMW85eXQ5Z2lFMXpLRzl5IiwibmFtZSI6IlRyYWNrICogRlg6IEVuYWJsZS9kaXNh + YmxlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRl + eCI6MCwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoxMiwidHJh + Y2tHVUlEIjoibmFtZSoiLCJ0cmFja05hbWUiOiJSRUFQRVIgKiIsImZ4QW5jaG9yIjoiQWxsQnlOYW1lIiwiZnhOYW1lIjoiKiIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsImNsaXBTbG90Ijp7ImFkZHJlc3MiOiJTZWxlY3RlZCJ9fX0seyJpZCI6ImxOV2o5ejZI + QjZ1cTI2VGZNdXNpaiIsIm5hbWUiOiJUcmFjayAqIEZYICogcGFyYW1ldGVyOiBTZXQgdmFsdWUiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsidHJhY2tHVUlEIjoibmFtZSoiLCJ0cmFja05hbWUiOiJNeSBwYXJhbWV0ZXJzICoiLCJmeEFuY2hvciI6IkFsbEJ5 + TmFtZSIsImZ4TmFtZSI6IioiLCJwYXJhbVR5cGUiOiJpbmRleC1tYW51YWwiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJjbGlwU2xvdCI6eyJhZGRyZXNzIjoiU2VsZWN0ZWQifX19LHsiaWQiOiJiZTNWZ1M4ZzNNWmt5eklROVVCdU0iLCJuYW1lIjoiVHJhY2sg + KiBzZW5kOiBTZXQgdm9sdW1lIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MCwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozLCJ0 + cmFja0dVSUQiOiJuYW1lKiIsInRyYWNrTmFtZSI6Ik15IHNlbmRzICoiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNl + ZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 1578 222 1436 1514 + FXID {4B482461-7BE2-4AD8-A834-FD741BE9B8CB} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {84935C06-8968-4B5E-96B0-ECCCF789E082} + WAK 0 0 + BYPASS 1 0 0 + "" + bW9zcu5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAAANAQAAAQAAAAAAEAA= + AAAAAAAAAPA/AAAAAAAA4D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D+amZmZmZmxP83MzMzMzOs/AAAAAAAAAAAcx3Ecx3HcP/yp8dJNYkA//Knx0k1iQD8AAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAOA/AQAAAAAAAAAAAAAAAAAAAAAA8D9AAAAAVVVVVVVVxT//////CAQCgUAggD8AAAAAAADwPwAAAAAAAPA/AAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOpCEhGmWQPwAAAAAAAPA//Knx0k1iMD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {734ED650-70FC-4DB7-927D-748CC24B2272} + WAK 0 0 + > + > + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {49885680-9145-4647-9D90-D8A63CA9C406} + WAK 0 0 + BYPASS 1 0 0 + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAAAAAAAAAFlAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAPkDAACdAgAAAgAAAA== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {A41FDF0A-28A6-4ABF-9062-C97020021ABE} + WAK 0 0 + > + > + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAOG0sd24s3hAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAPkDAACdAgAAAgAAAA== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {44014A1D-DBB7-4EDC-B300-58C9BB38E07A} + PARM_TCP 0:_Freq_Low_Shelf + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6lUik/ppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {54B19494-BA43-4C32-AB1A-23D173DFB61D} + PARM_TCP 0:_Threshold + WAK 0 0 + > + > + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAOG0sd24s3hAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAPkDAACdAgAAAgAAAA== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {F77C3C43-3ED9-414E-A3EA-2DEE0FA3D368} + PARM_TCP 0:_Freq_Low_Shelf + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6lUik/ppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {5DDA250C-2ECF-487F-B785-5D92D3AF6DCD} + PARM_TCP 0:_Threshold + WAK 0 0 + > + > + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-553-preset-slowdown.RPP b/plugin-reaper-realearn/resources/test-projects/issue-553-preset-slowdown.RPP new file mode 100644 index 0000000..03dcb7c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-553-preset-slowdown.RPP @@ -0,0 +1,24432 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 110592 + ZOOM 0.00941385630454 0 0 + VZOOMEX 7 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1.48763943758377 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADPbwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS43IiwiaWQiOiJQWUF2azVncSIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJmZWVkYmFja0RldmljZUlkIjoiZngt + b3V0cHV0IiwiZGVmYXVsdEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6IjUxYzcxNDIxLTQ3NzAtNGFlYy05ZDc2LWI4MzhmYTIzMDcxZSIsIm5hbWUiOiJTZWxlY3Qg + TG9vcGVyIiwiYWN0aXZhdGlvblR5cGUiOiJwcm9ncmFtIiwicHJvZ3JhbUNvbmRpdGlvbiI6eyJwYXJhbUluZGV4IjowLCJwcm9ncmFtSW5kZXgiOjF9fSx7ImlkIjoi + ZWU5NDI4MzMtMjUxNy00MjE4LWE1YjItOTVlYTU3ZjA0YjY5IiwibmFtZSI6IlNlbGVjdCBMb29wZXIgUGVha3MiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dyYW0iLCJt + b2RpZmllckNvbmRpdGlvbjEiOnsicGFyYW1JbmRleCI6MCwiaXNPbiI6ZmFsc2V9LCJtb2RpZmllckNvbmRpdGlvbjIiOnsicGFyYW1JbmRleCI6MCwiaXNPbiI6ZmFs + c2V9LCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MX19LHsiaWQiOiIwYzdmZjFiNS0xNDU3LTRhOTctYjQwMC1iMTU1NzQ2 + NzBiNTQiLCJuYW1lIjoiU2VsZWN0IExvb3BlciBWb2x1bWUiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dyYW0iLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgi + OjAsInByb2dyYW1JbmRleCI6Mn19LHsiaWQiOiI1YmUxNTZjYi00MWQzLTRmZmUtYjEwNi01ZjNlMjczMGVmM2YiLCJuYW1lIjoiTG9vcGVyIEZYIn0seyJpZCI6Ijk5 + YTY0OTgxLTBhYzMtNDdiNC1hYjBiLWFkMjc5YmVkZmU5ZCIsIm5hbWUiOiJMb29wZXIgRlggUmVzZXQifSx7ImlkIjoiMGZmMzZlNDEtZmI4Yi00NjhkLWFhZTgtZDAy + ZGIxMTMzZGIxIiwibmFtZSI6IlN1c3RhaW4gUGVkYWwgRmVlZGJhY2sifSx7ImlkIjoiVkRUX3Azb1ZMNEYwN1p0c1ZESnVVIiwibmFtZSI6IkhpaGF0IFNlcXVlbmNl + ciJ9LHsiaWQiOiI5YXJfUDBCTUppMWhrLUhuOG5hRy0iLCJuYW1lIjoiSGloYXQgU2VxdWVuY2VyIFJlc2V0In0seyJpZCI6IkU3VktaNUxnQUpweVhpU0ZxRlJETCIs + Im5hbWUiOiJLaWNrIEZYIn0seyJpZCI6IjB5WWhoWllhaU50LU82aWdkT0VsUiIsIm5hbWUiOiJCYXNzICYgS2ljayJ9LHsiaWQiOiItM0daT2RnUWZqaWdEQTgzNllu + bEwiLCJuYW1lIjoiRGVsYXkgRlgifSx7ImlkIjoiOWdnaHFKTERqVW9uenpLWGNhYll4IiwibmFtZSI6IkRlbGF5IEZYIFJlc2V0In1dLCJkZWZhdWx0Q29udHJvbGxl + ckdyb3VwIjp7fSwiY29udHJvbGxlckdyb3VwcyI6W3siaWQiOiIxYjBhZTJiNy1kOTA1LTQ1NmMtODYxZC1mYTNmMmM4ODY3OTUiLCJuYW1lIjoiU2lkZSBidXR0b25z + In0seyJpZCI6ImMzNzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsIm5hbWUiOiJQdXNoIGVuY29kZXJzIn1dLCJtYXBwaW5ncyI6W3siaWQiOiI3MDA5 + OGIyMy0wZjFhLTRiYTUtYWQ0NS1jOTI2YWY0YWQ0NGYiLCJuYW1lIjoiMyIsImdyb3VwSWQiOiI1MWM3MTQyMS00NzcwLTRhZWMtOWQ3Ni1iODM4ZmEyMzA3MWUiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24ifSwibW9kZSI6eyJ0eXBlIjoyLCJtaW5Tb3VyY2VWYWx1ZSI6MC41LCJtYXhTb3VyY2VWYWx1ZSI6MC42NywibWF4U3RlcFNpemUiOjAuMDUs + InJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MTgsIndpdGhUcmFjayI6dHJ1ZSwidHJhY2tHVUlEIjoic2VsZWN0ZWQiLCJmeEFuY2hvciI6 + ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiYzJmNzJiOGQtNzUxMC00ZWU0LWJhMDktNDk4YmMyMmI4ZjAzIiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlkNzYtYjgz + OGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX0sIm1vZGUiOnsidHlwZSI6MiwibWluU291cmNlVmFsdWUiOjAuNSwibWF4 + U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJ3aXRoVHJhY2siOnRy + dWUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInNlbmRJbmRleCI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYmUwZjUzN2MtOGRkZi00NTNiLTgxZDYtMTE2YTUyMDdmNTZi + IiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6Mn0sIm1vZGUiOnsidHlwZSI6MiwibWluU291cmNlVmFsdWUiOjAuNSwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNl + SXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJ3aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInNl + bmRJbmRleCI6MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2Y0ODliN2QtY2ZiNy00ODY0LTg5ODYtNGRkMDhkMGI5NzZhIiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlk + NzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRl + eCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M30sIm1vZGUiOnsidHlwZSI6MiwibWluU291cmNlVmFsdWUiOjAu + NSwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJ3aXRoVHJh + Y2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInNlbmRJbmRleCI6MywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZjY1ZTUzZTktZDkyYy00NDg0LWIxNTMtZDY5MjQ1 + NTJjNzlkIiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6NH0sIm1vZGUiOnsidHlwZSI6MiwibWluU291cmNlVmFsdWUiOjAuNSwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJy + ZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJ3aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJp + ZCIsInNlbmRJbmRleCI6NCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiMDhiYzVjYzMtZjE3Yi00NDdmLTgzM2ItYmU4NzVkN2ZlZjQ3IiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00 + YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX0sIm1vZGUiOnsidHlwZSI6MiwibWluU291cmNlVmFs + dWUiOjAuNSwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJ3 + aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInNlbmRJbmRleCI6NSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiM2I3NzA2ZTQtYmJmOC00Mjc1LThiODgt + ZjdkMzFmZTQ1Nzg1IiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6Nn0sIm1vZGUiOnsidHlwZSI6MiwibWluU291cmNlVmFsdWUiOjAuNSwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjow + LjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJ3aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNo + b3IiOiJpZCIsInNlbmRJbmRleCI6NiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOTQ2YmExNDktYWM4NS00MDhiLTg2NjktM2U3YmIwY2IzZTFlIiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEt + NDc3MC00YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N30sIm1vZGUiOnsidHlwZSI6MiwibWluU291 + cmNlVmFsdWUiOjAuNSwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUi + OjE4LCJ3aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInNlbmRJbmRleCI6NywidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZWIzMzMyYjEtMTY1Ni00NzYy + LTllNGYtMjEwYjI2NTRmMTMyIiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + In0sIm1vZGUiOnsidHlwZSI6MiwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7 + InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNTQ4OThhZDc4MGY1Njc0M2E3OGFlYjczN2Y4MDBkZDciLCJ3aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVjdGVk + IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhNWU1NDhmMy1hMDRjLTQ1NjktYTk4Mi01ZTU0MmRmYThkNjQiLCJuYW1lIjoiMyIsImdyb3VwSWQiOiI1MWM3MTQyMS00Nzcw + LTRhZWMtOWQ3Ni1iODM4ZmEyMzA3MWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTb3VyY2VW + YWx1ZSI6MC42NywibWF4U3RlcFNpemUiOjAuMDUsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiJjMzE4ZmFk + ZWY1MGI0OTQzYTMzZTBjZjdlOGU1NWU2ZiIsIndpdGhUcmFjayI6dHJ1ZSwidHJhY2tHVUlEIjoic2VsZWN0ZWQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjU2YjQxNTVk + LTdmNjUtNGI2Ni04NGEwLThhMzE3ZWFkN2RiNSIsIm5hbWUiOiIzIiwiZ3JvdXBJZCI6IjUxYzcxNDIxLTQ3NzAtNGFlYy05ZDc2LWI4MzhmYTIzMDcxZSIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9LCJtb2RlIjp7InR5cGUiOjIsIm1heFNvdXJjZVZhbHVlIjowLjY3LCJtYXhTdGVwU2l6ZSI6MC4wNSwi + cmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6ImQzMGI2ODBhZTZkY2ExNDJhYmM3MGFlMDk4YTQ1OGUwIiwid2l0 + aFRyYWNrIjp0cnVlLCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOTFhNTMyZjEtNDZhMC00OGI2LTk1ZmYtYTg0NWY5YWExZGIzIiwi + bmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6M30sIm1vZGUiOnsidHlwZSI6MiwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0 + Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTI4MjJmMjc1MzI2ZDY0MGE2ZDdlZTcyMTA3MjllY2EiLCJ3aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNlbGVj + dGVkIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiYmViZTg3ZS00ZGEwLTRmYWItOGNkOC05NDYxNjQxYmIxMWEiLCJuYW1lIjoiMyIsImdyb3VwSWQiOiI1MWM3MTQyMS00 + NzcwLTRhZWMtOWQ3Ni1iODM4ZmEyMzA3MWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTb3Vy + Y2VWYWx1ZSI6MC42NywibWF4U3RlcFNpemUiOjAuMDUsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiJlYWEw + MTFhZjE4MzgxMTQ4OWU2OWM0NWNjN2EyOTg0OSIsIndpdGhUcmFjayI6dHJ1ZSwidHJhY2tHVUlEIjoic2VsZWN0ZWQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjcyODE0 + YjM3LTM1NWItNDBiMi05YTE3LTgwMDcxZWM5NjA5MiIsIm5hbWUiOiIzIiwiZ3JvdXBJZCI6IjUxYzcxNDIxLTQ3NzAtNGFlYy05ZDc2LWI4MzhmYTIzMDcxZSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9LCJtb2RlIjp7InR5cGUiOjIsIm1heFNvdXJjZVZhbHVlIjowLjY3LCJtYXhTdGVwU2l6ZSI6MC4w + NSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjYyNDQ3NzAzNDYzMDM0NGU4YmM4YjhjMDgwNWU3YjRkIiwi + d2l0aFRyYWNrIjp0cnVlLCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMjI2YWQ4MmYtMzhjMy00OTY5LTkwMzEtODA0OTZhNDFiODRk + IiwibmFtZSI6IjMiLCJncm91cElkIjoiNTFjNzE0MjEtNDc3MC00YWVjLTlkNzYtYjgzOGZhMjMwNzFlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6Nn0sIm1vZGUiOnsidHlwZSI6MiwibWF4U291cmNlVmFsdWUiOjAuNjcsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFy + Z2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNjQ4NDkzNjNkODk4ZDU0ZTk4MjI5NzI0ZTczYWIzN2UiLCJ3aXRoVHJhY2siOnRydWUsInRyYWNrR1VJRCI6InNl + bGVjdGVkIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiNjliM2EwYi02Yzg4LTQ5N2MtOWNjYy0yYWQ2ZjIxY2JkN2MiLCJuYW1lIjoiMyIsImdyb3VwSWQiOiI1MWM3MTQy + MS00NzcwLTRhZWMtOWQ3Ni1iODM4ZmEyMzA3MWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhT + b3VyY2VWYWx1ZSI6MC42NywibWF4U3RlcFNpemUiOjAuMDUsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI1 + ZmVkNmE5MTllNDAyZjRmYWFmZGYxYzkxNGMxYzc5ZSIsIndpdGhUcmFjayI6dHJ1ZSwidHJhY2tHVUlEIjoic2VsZWN0ZWQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImQz + NWFhZDY3LWViNTMtNDUxYy1iODU0LTU3NTcxMWNhMTQ0OSIsIm5hbWUiOiIxOCIsImdyb3VwSWQiOiJlZTk0MjgzMy0yNTE3LTQyMTgtYTViMi05NWVhNTdmMDRiNjki + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tOYW1lIjoiTG9vcGVyIE91dCAxIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiOWZhODE4YTUtYjA2MC00ZjdiLWI5NWYtZDFjMTlmZTU4NGU1 + IiwibmFtZSI6IjE4IiwiZ3JvdXBJZCI6ImVlOTQyODMzLTI1MTctNDIxOC1hNWIyLTk1ZWE1N2YwNGI2OSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja05hbWUiOiJMb29wZXIgT3V0IDIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJmMGM4MWIwYS1iOTc3LTQyMGUtOTM0MC1lMWNiNmMwYmNiNjEiLCJuYW1lIjoi + MTgiLCJncm91cElkIjoiZWU5NDI4MzMtMjUxNy00MjE4LWE1YjItOTVlYTU3ZjA0YjY5Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0 + YXJnZXQiOnsidHlwZSI6MzQsInRyYWNrTmFtZSI6Ikxvb3BlciBPdXQgMyIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwi + c2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjJkY2NmYTBlLThhZGMtNDZhZi05ODBmLTcwZDZkZjdiZTJmYiIsIm5hbWUiOiIxOCIsImdyb3Vw + SWQiOiJlZTk0MjgzMy0yNTE3LTQyMTgtYTViMi05NWVhNTdmMDRiNjkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjozfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0 + eXBlIjozNCwidHJhY2tOYW1lIjoiTG9vcGVyIE91dCA0IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiOWQ3YjU4ZTctYjRmYi00ZWJhLWJiNzEtNDIzNzkwOTY1OTc2IiwibmFtZSI6IjE4IiwiZ3JvdXBJZCI6ImVlOTQy + ODMzLTI1MTctNDIxOC1hNWIyLTk1ZWE1N2YwNGI2OSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0 + cmFja05hbWUiOiJMb29wZXIgT3V0IDUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MH19LHsiaWQiOiJhOTRmZjcyMS0yNWMwLTQxYmMtYjBiMi0wNzliMzY5MjYxNTkiLCJuYW1lIjoiMTgiLCJncm91cElkIjoiZWU5NDI4MzMtMjUxNy00 + MjE4LWE1YjItOTVlYTU3ZjA0YjY5Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6NX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MzQsInRyYWNrTmFtZSI6 + Ikxvb3BlciBPdXQgNiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4Ijow + fX0seyJpZCI6ImE4ZWRlMzk5LWU4ZjctNDY5YS05YzlkLTJlYWU1NTgzMzBiMCIsIm5hbWUiOiIxOCIsImdyb3VwSWQiOiJlZTk0MjgzMy0yNTE3LTQyMTgtYTViMi05 + NWVhNTdmMDRiNjkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + LCJjb250cm9sRWxlbWVudEluZGV4Ijo2fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tOYW1lIjoiTG9vcGVyIE91 + dCA3IiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoi + YzAzNjEzZjEtZmJhYi00MWVhLTk3OWUtYjdhOGYyZThjZmUxIiwibmFtZSI6IjE4IiwiZ3JvdXBJZCI6ImVlOTQyODMzLTI1MTctNDIxOC1hNWIyLTk1ZWE1N2YwNGI2 + OSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjd9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJ0cmFja05hbWUiOiJMb29wZXIgT3V0IDgiLCJmeEFu + Y2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJhNTYzNDhkYy1k + ODkzLTRlNDgtODZmMC01NGNmNjlhODU5ZmMiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYxYjUtMTQ1Ny00YTk3LWI0MDAtYjE1NTc0NjcwYjU0Iiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWluU3RlcFNp + emUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja05hbWUiOiJMb29wZXIgT3V0IDEiLCJmeEFuY2hvciI6ImlkIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI3OWE0NDAwMC05NjE3LTQ2OGQtODhlZC1j + NmU5OWJmMzcwODMiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYxYjUtMTQ1Ny00YTk3LWI0MDAtYjE1NTc0NjcwYjU0Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6MX0sIm1vZGUiOnsi + bWluU3RlcFNpemUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja05hbWUiOiJMb29wZXIgT3V0IDIiLCJmeEFuY2hvciI6 + ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJlMDJkZDRkOC1lODYzLTRj + ZjgtYTY3MS01YzZkMjkzOWE5OTkiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYxYjUtMTQ1Ny00YTk3LWI0MDAtYjE1NTc0NjcwYjU0Iiwic291cmNlIjp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn0s + Im1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja05hbWUiOiJMb29wZXIgT3V0IDMiLCJm + eEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI2ZWIzZTZk + MC01YTNjLTQwNGYtOWE5Ny1hM2E4MDUxMGQ3YjEiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYxYjUtMTQ1Ny00YTk3LWI0MDAtYjE1NTc0NjcwYjU0Iiwic291 + cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJ + bmRleCI6M30sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja05hbWUiOiJMb29wZXIg + T3V0IDQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQi + OiJhYjQzYjI2Ni0yN2FiLTQ0YTgtYmExYy02MmRlYTYwODM2NGYiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYxYjUtMTQ1Ny00YTk3LWI0MDAtYjE1NTc0Njcw + YjU0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJv + bEVsZW1lbnRJbmRleCI6NH0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja05hbWUi + OiJMb29wZXIgT3V0IDUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MH19LHsiaWQiOiI2NmFjYzQyYi0zODQwLTQxYjEtYTc1OS1lYWZiMjg3YmJiYjAiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYxYjUtMTQ1Ny00YTk3LWI0MDAt + YjE1NTc0NjcwYjU0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MCwiY29udHJvbEVsZW1lbnRJbmRleCI6NX0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0 + cmFja05hbWUiOiJMb29wZXIgT3V0IDYiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MH19LHsiaWQiOiI0ZjM5OWIwYy03ZDg5LTQ5MzItYWU3Mi00YzcyYjYxNGU2ZjkiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYxYjUtMTQ1Ny00 + YTk3LWI0MDAtYjE1NTc0NjcwYjU0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRhcmdldCI6eyJ0 + eXBlIjoyLCJ0cmFja05hbWUiOiJMb29wZXIgT3V0IDciLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI5ZmIyMmVkNC02Nzc0LTQ2ZTctODFjMi0wMDE5OWEwNDAxOGUiLCJuYW1lIjoiMjYiLCJncm91cElkIjoiMGM3ZmYx + YjUtMTQ1Ny00YTk3LWI0MDAtYjE1NTc0NjcwYjU0Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6N30sIm1vZGUiOnsibWluU3RlcFNpemUiOjAuMDAyNSwibWF4U3RlcFNpemUiOjAuNX0sInRh + cmdldCI6eyJ0eXBlIjoyLCJ0cmFja05hbWUiOiJMb29wZXIgT3V0IDgiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNl + ZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJwdGhZTHVLaVpRY01uRHZDVktnWUciLCJuYW1lIjoiVHJhY2sgVm9sdW1lIiwiZ3JvdXBJZCI6IlZE + VF9wM29WTDRGMDdadHNWREp1VSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjYxOTQxMTc2NDcwNTg4MjMsIm1pblN0ZXBTaXplIjowLjAwMSwibWF4U3RlcFNpemUiOjAuNH0sInRh + cmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJuYW1lKiIsInRyYWNrTmFtZSI6IkhpaGF0IFNlcXVlbmNlciIsImVuYWJsZU9ubHlJZlRyYWNrSXNTZWxlY3RlZCI6 + dHJ1ZSwiZnhBbmNob3IiOiJuYW1lIiwiZnhOYW1lIjoiU2hpZnQiLCJwYXJhbUluZGV4IjoxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6InpOdlRxbURsSmF4N3VqbllCbHQzWCIsIm5hbWUiOiJWb2x1bWUiLCJncm91cElkIjoiRTdWS1o1TGdBSnB5 + WGlTRnFGUkRMIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0s + Im1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNjIsIm1pblN0ZXBTaXplIjowLjAwMiwibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tH + VUlEIjoibmFtZSoiLCJ0cmFja05hbWUiOiJLaWNrIEZYIiwiZW5hYmxlT25seUlmVHJhY2tJc1NlbGVjdGVkIjp0cnVlLCJmeEFuY2hvciI6IkFsbEJ5TmFtZSIsImZ4 + TmFtZSI6IlZTVDM6IFByby1RIDMgKEZhYkZpbHRlcikiLCJwYXJhbUluZGV4IjozLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IlVJRkZaNGZlbm15Z2ZmNndSNnpnWCIsIm5hbWUiOiJWb2x1bWUgUmVzZXQiLCJncm91cElkIjoiRTdWS1o1TGdBSnB5 + WGlTRnFGUkRMIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluVGFyZ2V0VmFsdWUiOjAuODEsIm1heFRhcmdldFZhbHVl + IjowLjgxLCJtaW5TdGVwU2l6ZSI6MC4wMDIsIm1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJtaW4i + fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6Im5hbWUqIiwidHJhY2tOYW1lIjoiS2ljayBGWCIsImVuYWJsZU9ubHlJZlRyYWNrSXNTZWxlY3RlZCI6dHJ1 + ZSwiZnhBbmNob3IiOiJBbGxCeU5hbWUiLCJmeE5hbWUiOiJWU1QzOiBQcm8tUSAzIChGYWJGaWx0ZXIpIiwicGFyYW1JbmRleCI6MywidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJjQUxTSlJUYzFTV3ZYOE9TNnVQWDUiLCJuYW1lIjoiMTMzIiwiZ3Jv + dXBJZCI6IjB5WWhoWllhaU50LU82aWdkT0VsUiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1pblRhcmdldFZhbHVlIjowLjcxNDcwNTg4MjM1Mjk0MTIsIm1heFRhcmdldFZhbHVlIjowLjkwNSwibWF4U3RlcFNp + emUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tOYW1lIjoiQmFzcyAmIEtpY2siLCJlbmFibGVPbmx5SWZUcmFja0lzU2VsZWN0ZWQiOnRydWUsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGlu + Z3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwiZ3JvdXBJZCI6ImMzNzY2MmVmLTYzMWUt + NGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6M30sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEu + MH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxfX0seyJpZCI6ImU1NTQzYWQ4LTllODEtNGJhZi1h + ODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwiZ3JvdXBJZCI6ImMzNzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6 + eyJjaGFubmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVmMC01Y2Y4LTQ1ZTktOWM5MC00ZDQ5MDZjZjYzMDQiLCJu + YW1lIjoiRW5jb2RlciAxLzIiLCJncm91cElkIjoiYzM3NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MSwiY2hhcmFjdGVyIjozfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgxZDMtZDYwNy00MzI5LTlkOTEtYTZiODg4YjBmYWFmIiwibmFtZSI6IkJ1 + dHRvbiAxLzIiLCJncm91cElkIjoiYzM3NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hh + cmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUi + OiJFbmNvZGVyIDEvMyIsImdyb3VwSWQiOiJjMzc2NjJlZi02MzFlLTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoy + LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9u + IDEvMyIsImdyb3VwSWQiOiJjMzc2NjJlZi02MzFlLTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJjaGFyYWN0 + ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVu + Y29kZXIgMS80IiwiZ3JvdXBJZCI6ImMzNzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNo + YXJhY3RlciI6M30sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2 + aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80 + IiwiZ3JvdXBJZCI6ImMzNzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6 + MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiI4MjhkNDY1YS05YmQzLTQ0MzYtOTEzNC00ODhhNmZlYjFmYmYiLCJuYW1lIjoiRW5jb2Rl + ciAyLzEiLCJncm91cElkIjoiYzM3NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFj + dGVyIjozfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNTBmM2Q1NmQtMGY2My00MDg2LTk1OTItYjNjZTkxM2RiMzgxIiwibmFtZSI6IkJ1dHRvbiAyLzEiLCJn + cm91cElkIjoiYzM3NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVyIDIv + MiIsImdyb3VwSWQiOiJjMzc2NjJlZi02MzFlLTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1LCJjaGFyYWN0ZXIi + OjN9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsImdyb3Vw + SWQiOiJjMzc2NjJlZi02MzFlLTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjF9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwi + Z3JvdXBJZCI6ImMzNzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNoYXJhY3RlciI6M30s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5YjctNGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJCdXR0b24gMi8zIiwiZ3JvdXBJZCI6 + ImMzNzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJkMjg0OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2MiLCJuYW1lIjoiRW5jb2RlciAyLzQiLCJncm91 + cElkIjoiYzM3NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiODAxOGMyNTktNmY3Ni00MTMzLTkwNDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1dHRvbiAyLzQiLCJncm91cElkIjoiYzM3 + NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NywiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsImdyb3VwSWQi + OiJjMzc2NjJlZi02MzFlLTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7 + Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsImdyb3VwSWQiOiJjMzc2NjJl + Zi02MzFlLTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwiZ3JvdXBJZCI6ImMz + NzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6M30sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudElu + ZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04Yjg5LWQ5NmI5NDUwMDAzMiIsIm5hbWUiOiJCdXR0b24gMy8yIiwiZ3JvdXBJZCI6ImMzNzY2MmVmLTYz + MWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJuYW1lIjoiRW5jb2RlciAzLzMiLCJncm91cElkIjoiYzM3NjYy + ZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6M30sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxMH19LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzktOWI2NC1hOTA5ZTU1OTM1NGIiLCJuYW1lIjoiQnV0dG9uIDMvMyIsImdyb3VwSWQiOiJjMzc2NjJlZi02MzFl + LTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJncm91cElkIjoiYzM3NjYy + ZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTEsImNoYXJhY3RlciI6M30sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxMX19LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsImdyb3VwSWQiOiJjMzc2NjJlZi02MzFl + LTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJncm91cElkIjoiYzM3NjYy + ZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6M30sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxMn19LHsiaWQiOiIxYzA5MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsImdyb3VwSWQiOiJjMzc2NjJlZi02MzFl + LTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxMn19LHsiaWQiOiJhMzg0MzVjMC0zYWM0LTQxNzQtYjFiMS03ODFlZmY3OTg4YzQiLCJuYW1lIjoiRW5jb2RlciA0LzIiLCJncm91cElkIjoiYzM3NjYy + ZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3RlciI6M30sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxM319LHsiaWQiOiI5MTQ1YjhhNy0wMjFlLTQ2ZTktYmMyNC01ODhiNWI3MGUzY2QiLCJuYW1lIjoiQnV0dG9uIDQvMiIsImdyb3VwSWQiOiJjMzc2NjJlZi02MzFl + LTRjY2YtYTYyYi0wOGJhYWIyMTY3ZjkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJncm91cElkIjoiYzM3NjYy + ZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMxMjU4NGEzIiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJncm91cElk + IjoiYzM3NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiNTVhYTIzYjAtYjgyMC00ZWJlLTliMTItOWZlNDA4NDJlZjI3IiwibmFtZSI6IkVuY29kZXIgNC80Iiwi + Z3JvdXBJZCI6ImMzNzY2MmVmLTYzMWUtNGNjZi1hNjJiLTA4YmFhYjIxNjdmOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjN9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJncm91cElk + IjoiYzM3NjYyZWYtNjMxZS00Y2NmLWE2MmItMDhiYWFiMjE2N2Y5Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IlVwcGVyIGxlZnQiLCJn + cm91cElkIjoiMWIwYWUyYjctZDkwNS00NTZjLTg2MWQtZmEzZjJjODg2Nzk1Iiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImVlOWRmNTkwLTYxY2ItNDc0Ni1h + MzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJVcHBlciByaWdodCIsImdyb3VwSWQiOiIxYjBhZTJiNy1kOTA1LTQ1NmMtODYxZC1mYTNmMmM4ODY3OTUiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1yaWdodCJ9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTG93ZXIgbGVmdCIsImdyb3VwSWQiOiIxYjBh + ZTJiNy1kOTA1LTQ1NmMtODYxZC1mYTNmMmM4ODY3OTUiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxz + ZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZi + LThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6Ikxvd2VyIHJpZ2h0IiwiZ3JvdXBJZCI6IjFiMGFlMmI3LWQ5MDUtNDU2Yy04NjFkLWZhM2YyYzg4Njc5NSIsInNvdXJj + ZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmln + aHQifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIiwicGFyYW1ldGVycyI6eyIwIjp7 + InZhbHVlIjowLjA1fX19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {B52ECA18-0816-E342-B8E5-6CCBA359A579} + WAK 0 0 + > + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-558-midi-script-text-value.RPP b/plugin-reaper-realearn/resources/test-projects/issue-558-midi-script-text-value.RPP new file mode 100644 index 0000000..5d5d6c7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-558-midi-script-text-value.RPP @@ -0,0 +1,145 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 8.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABlBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS4zIiwiaWQiOiJIQVZ3elRVeSIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJmZWVkYmFja0RldmljZUlkIjoiMCIs + ImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJuUnJQYXpkNmVtMWczVTFzMFNRX1kiLCJuYW1lIjoi + RUVMIiwic291cmNlIjp7InR5cGUiOjExLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwibWlkaVNjcmlwdCI6Ii8vIGRhdGFfMiA9IHN0cmNtcCh5 + LCBcImFcIik7XG5kYXRhXzIgPSAwO1xuXG5tc2dfc2l6ZSA9IDM7XG4wW10gPSAweGIwO1xuMVtdID0gMHg0YjtcbjJbXSA9IGRhdGFfMiArIDEwO1xuIiwib3NjQXJn + SW5kZXgiOjAsImtleXN0cm9rZSI6bnVsbH0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjEuMCwibWF4U3RlcFNpemUiOjAuMDUsImVlbEZlZWRiYWNrVHJhbnNmb3Jt + YXRpb24iOiJkIiwiZmVlZGJhY2tDb2xvciI6bnVsbCwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJzbDBiY0txVG43Q0oxekZfMGFYdDQiLCJuYW1lIjoiTHVhIiwic291cmNlIjp7InR5cGUiOjExLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwibWlkaVNjcmlwdEtpbmQiOiJsdWEiLCJtaWRpU2NyaXB0IjoicHJpbnQoeSlcblxucmV0dXJuIHtcbiAgICB7MHhiMCwg + MHg0YiwgeSAqIDEyN31cbn0iLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjpudWxsfSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1ZSI6MS4wLCJtYXhTdGVwU2l6ZSI6 + MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwiY2xpcFNsb3QiOnsiYWRkcmVzcyI6IlNlbGVjdGVkIn19fV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 355 74 752 735 + FXID {ADCB3E21-D71E-3B4B-B0FC-382F890A3276} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-575-slow-action-invocation.RPP b/plugin-reaper-realearn/resources/test-projects/issue-575-slow-action-invocation.RPP new file mode 100644 index 0000000..9df1236 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-575-slow-action-invocation.RPP @@ -0,0 +1,565 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 26.5811149081894 + ZOOM 18.57464612959071 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACGlQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS41IiwiaWQiOiJxZ09YVEZWUiIsImNvbnRyb2xEZXZpY2VJZCI6IjkiLCJmZWVkYmFja0RldmljZUlkIjoiOSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyR3JvdXBzIjpbeyJpZCI6ImZhZGVyIiwibmFtZSI6IkZhZGVyIn0seyJpZCI6InYt + cG90IiwibmFtZSI6IlYtUG90In0seyJpZCI6InYtc2VsZWN0IiwibmFtZSI6IlYtU2VsZWN0In0seyJpZCI6ImZhZGVyLXRvdWNoIiwibmFtZSI6IkZhZGVyIFRvdWNo + In0seyJpZCI6InNlbGVjdCIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoibXV0ZSIsIm5hbWUiOiJNdXRlIn0seyJpZCI6InNvbG8iLCJuYW1lIjoiU29sbyJ9LHsiaWQi + OiJyZWNvcmQtcmVhZHkiLCJuYW1lIjoiUmVjb3JkLXJlYWR5In0seyJpZCI6InYtcG90LWxlZHMiLCJuYW1lIjoiVi1Qb3QgTEVEcyJ9LHsiaWQiOiJsY2QiLCJuYW1l + IjoiTENEIn0seyJpZCI6Im1ldGVyIiwibmFtZSI6Ik1ldGVyIn1dLCJtYXBwaW5ncyI6W3siaWQiOiJsSGZCZUVrZk5BbmIxaGNiQlFxaTEiLCJuYW1lIjoiMSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIiOjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjowLjA4LCJtYXhTdGVwU2l6 + ZSI6MC4xMn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6Ijk5NiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoibWFpbi9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIs + InNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImpvZyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjYwLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImpvZyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYWluL2ZhZGVyL3Rv + dWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEyfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFk + ZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibWFya2VyIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6ODR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2VyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJyZWFkIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoid3JpdGUiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ3cml0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2gtbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6ImNoLXJpZ2h0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmlnaHQiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImJhbmstbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ2fSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImJhbmstbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiYmFuay1yaWdodCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjQ3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJld2luZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjkxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJld2luZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZmFzdC1md2QiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Mn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmYXN0LWZ3ZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicGxheSIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk0fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + InN0b3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzdG9wIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJyZWNvcmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNvcmQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImN5Y2xlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6Inpvb20iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDB9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoiem9v + bSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic2NydWIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDF9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4Ijoic2NydWIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImN1cnNvci1sZWZ0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6OTh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImN1cnNvci1yaWdodCIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk5fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29y + LXVwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXVwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjdXJzb3ItZG93biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJudWRnZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1fSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + Im51ZGdlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJkcm9wIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODd9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiZHJvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicmVwbGFjZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + Ojg4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6InJlcGxhY2UiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNsaWNrIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6ODl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2xpY2siLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo5MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU0fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMiIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMyIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU2fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYzIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmNCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmNSIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY1IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmNiIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY2IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJz + bXB0ZS1iZWF0cyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNtcHRlLWJlYXRzIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJsY2QvYXNzaWdubWVudCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNl + dmVuIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdu + bWVudCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImxjZC90aW1lY29kZSIsImdyb3VwSWQiOiJsY2QiLCJz + b3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC90aW1lY29kZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjMyfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEv + ZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDR9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gxL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9zZWxlY3QiLCJncm91cElk + Ijoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NlbGVjdCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9fSx7ImlkIjoiY2gxL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjB9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjaDEvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJjaGFyYWN0ZXIiOjQs + ImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBv + dC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTAgZGNiYV0ifSwibW9kZSI6 + eyJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + MS92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L2Jvb3N0LWN1dCIsImdy + b3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNl + VmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gxL3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBv + dC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMCBkY2JhXSJ9LCJtb2Rl + Ijp7Im1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gxL3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9zcHJlYWQiLCJn + cm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJj + ZVZhbHVlIjowLjR9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc3By + ZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJj + aDEvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9sY2QvbGlu + ZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUi + OjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xjZC9saW5l + MiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwi + c291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoMi92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMzfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMi92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvZmFkZXIv + dG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDV9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2Zh + ZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9zZWxlY3QiLCJncm91cElkIjoic2Vs + ZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjV9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiY2gyL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjaDIvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjQsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC93cmFw + IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhT + b3VyY2VWYWx1ZSI6MC43NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBv + dC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQi + OiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUi + OjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gyL3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9zaW5n + bGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1h + eFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Yt + cG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9zcHJlYWQiLCJncm91cElk + Ijoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVl + IjowLjR9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvc3ByZWFkIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDIvc29s + byIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi9sY2QvbGluZTEiLCJn + cm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoMi9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjF9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDAxIGRjYmFdIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoMy92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM0fSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMy92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvZmFkZXIvdG91Y2gi + LCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3Rv + dWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiY2gzL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJj + aDMvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gzL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC93cmFwIiwiZ3Jv + dXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VW + YWx1ZSI6MC43NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC93cmFw + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBv + dC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUs + Im1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gz + L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zaW5nbGUiLCJn + cm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJj + ZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3Np + bmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1w + b3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjR9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3Qvc3ByZWFkIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjE4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvc29sbyIsImdy + b3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NvbG8iLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6Mn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvbGNkL2xpbmUxIiwiZ3JvdXBJ + ZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoyLCJsaW5lIjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJjaDMvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoyLCJsaW5lIjoxfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGluZTIiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0 + eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiJjaDQvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDQvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L2ZhZGVyL3RvdWNoIiwiZ3Jv + dXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlci90b3VjaCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + ImNoNC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3Yt + cG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNC92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvd3JhcCIsImdyb3VwSWQi + OiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUi + OjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvd3JhcCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVk + cyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhT + b3VyY2VWYWx1ZSI6MC43NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBv + dC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJ + ZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1 + ZSI6MC43NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9zaW5nbGUi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxl + ZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjoxOX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L3NvbG8iLCJncm91cElk + Ijoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX19LHsiaWQiOiJjaDQvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L2xjZC9saW5lMSIsImdyb3VwSWQiOiJs + Y2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MywibGluZSI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g0L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MywibGluZSI6MX0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6 + MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMTEgZGNiYV0ifSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g1L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzZ9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1 + L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9mYWRlci90b3VjaCIsImdyb3VwSWQi + OiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIvdG91Y2giLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUv + ZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS92LXBvdC9j + b250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUv + di1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1w + b3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3dyYXAiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNl + VmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QvYm9v + c3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2 + LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAu + NzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvc2luZ2xlIiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwi + c291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNH0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9zb2xvIiwiZ3JvdXBJZCI6InNv + bG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY2g1L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0fSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwi + c291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoNS9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjF9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJy + YXdNaWRpUGF0dGVybiI6IkQwIFswMTAwIGRjYmFdIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDUvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92 + LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM3fSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXNl + bGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFk + ZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L2ZhZGVy + IiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjV9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvdi1wb3QvY29udHJv + bCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIxLCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90 + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxl + ZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC93cmFwIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVl + IjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L2Jvb3N0LWN1 + dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NpbmdsZSIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjR9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjIxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJp + ZCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDYvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJj + ZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo1LCJsaW5lIjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDYvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo1LCJsaW5lIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJEMCBbMDEwMSBkY2JhXSJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g2L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1zZWxl + Y3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3Qi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRv + dWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEwfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjMwfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNy9mYWRlciIsImdy + b3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo2fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3YtcG90L2NvbnRyb2wiLCJn + cm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMiwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwi + c291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0 + eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43 + NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC9ib29zdC1jdXQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMi + LCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoyMn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJj + aDcvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjZ9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g3L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsi + dHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L2xj + ZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0 + ZXJuIjoiRDAgWzAxMTAgZGNiYV0ifSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoNy9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Ytc2VsZWN0Iiwi + Z3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0IiwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjozMX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvZmFkZXIiLCJncm91cElk + IjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6N30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC92LXBvdC9jb250cm9sIiwiZ3JvdXBJ + ZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6 + MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUi + OjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjN9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g4L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3Jl + Y29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3fSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + OC9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUi + OjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g4L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9sY2QvbGlu + ZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6 + IkQwIFswMTExIGRjYmFdIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDgvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX1dfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {1A299894-166B-0C4C-8F03-5527DB333A37} + WAK 0 0 + > + + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-577-target-max-exceeded.RPP b/plugin-reaper-realearn/resources/test-projects/issue-577-target-max-exceeded.RPP new file mode 100644 index 0000000..cbd6000 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-577-target-max-exceeded.RPP @@ -0,0 +1,164 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADNAQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS41IiwiaWQiOiJvdDU2SEZYRCIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiWGdCVWZyU0VyTTJjS3dFWWp3R0ktIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFRh + cmdldFZhbHVlIjowLjcxNn0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiIyNURCQjA1Ri0zNzIwLTA5NEQtOTM2MC1ENjY1OTAwQTBGNjgiLCJmeEFuY2hv + ciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {6916CEE7-0D73-2A4A-9B99-705EE346B128} + WAK 0 0 + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-579-realearn-parameter-source.rpp b/plugin-reaper-realearn/resources/test-projects/issue-579-realearn-parameter-source.rpp new file mode 100644 index 0000000..2eb3aa3 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-579-realearn-parameter-source.rpp @@ -0,0 +1,339 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 0 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAD4BAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS43IiwiaWQiOiIyaGdnSml6RCIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoia29oUFZNWTN2T1d4Tk9JMlJVR1BNIiwibmFtZSI6IlAxID0+IFAyIiwic291cmNlIjp7ImNh + dGVnb3J5IjoicmVhcGVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJyZWFwZXJTb3VyY2VUeXBlIjoicmVhbGVh + cm4tcGFyYW1ldGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwi + cGFyYW1JbmRleCI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiJqRzB6YlR5 + TUFGeWZVRHZkSzZIc2oiLCJuYW1lIjoiUDIgPT4gUDMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJyZWFwZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQXJnSW5kZXgiOjAsInJlYXBlclNvdXJjZVR5cGUiOiJyZWFsZWFybi1wYXJhbWV0ZXIiLCJwYXJhbWV0ZXJJbmRleCI6MX0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDUsInJldmVyc2VJc0VuYWJsZWQiOnRydWV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwicGFyYW1JbmRleCI6MiwidXNlUHJvamVjdCI6dHJ1 + ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiI3UjU5by1kNFBueXh4MEhsdVZadDUiLCJuYW1lIjoiUDMgPT4g + Vm9sIiwic291cmNlIjp7ImNhdGVnb3J5IjoicmVhcGVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJyZWFwZXJT + b3VyY2VUeXBlIjoicmVhbGVhcm4tcGFyYW1ldGVyIiwicGFyYW1ldGVySW5kZXgiOjJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJyZXZlcnNlSXNFbmFibGVk + Ijp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoidGhpcyIsInBhcmFtSW5kZXgiOjIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJz + ZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sInBhcmFtZXRlcnMiOnsiMCI6eyJ2YWx1ZSI6MC40NzYwMTc5fSwiMSI6eyJ2YWx1ZSI6MC41MjM5ODIxfSwi + MiI6eyJ2YWx1ZSI6MC40NzYwMTc5fX19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {F7261A58-AF12-46F0-8D12-C10B0E107B22} + + PARM_TCP 0:_Main_p1__Param_1 1:_Main_p2__Param_2 2:_Main_p3__Param_3 + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-583-bookmark-by-position-ui.RPP b/plugin-reaper-realearn/resources/test-projects/issue-583-bookmark-by-position-ui.RPP new file mode 100644 index 0000000..4950702 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-583-bookmark-by-position-ui.RPP @@ -0,0 +1,144 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6 + ZOOM 100 13 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 6 7.5 + SELECTION2 6 7.5 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + MARKER 5 6 check 0 0 1 B {429AC4AE-9B9B-AC4E-85F3-EDFEBD388F3E} + MARKER 2 7.5 "" 0 0 1 B {F61EA17D-568C-AD4B-B87B-C88F901B9671} + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACLAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS41IiwiaWQiOiJHQ1FHT0RQOCIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiUnJ4NnF5aEIyWjBsS0VDdG5kX0dyIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyMiwidHJh + Y2tJbmRleCI6MCwiZnhBbmNob3IiOiJpZCIsImJvb2ttYXJrUmVmIjo1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9z + Y0FyZ0luZGV4IjowfX0seyJpZCI6Im1QcEQzcDRROUMyaFo1Wl8wam15ciIsIm5hbWUiOiIyIiwic291cmNlIjp7ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6MCwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {F73E68CD-1BF4-E14B-A0B9-38FB83F09236} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-588-text-feedback-target-fx-name.RPP b/plugin-reaper-realearn/resources/test-projects/issue-588-text-feedback-target-fx-name.RPP new file mode 100644 index 0000000..d2fe585 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-588-text-feedback-target-fx-name.RPP @@ -0,0 +1,766 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACM2gAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS41IiwiaWQiOiJzQWtNVFRKUSIsImNvbnRyb2xEZXZpY2VJZCI6IjkiLCJmZWVkYmFja0RldmljZUlkIjoiOSIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJjb250cm9sbGVyR3JvdXBzIjpbeyJpZCI6ImZhZGVyIiwibmFtZSI6IkZhZGVyIn0seyJpZCI6InYt + cG90IiwibmFtZSI6IlYtUG90In0seyJpZCI6InYtc2VsZWN0IiwibmFtZSI6IlYtU2VsZWN0In0seyJpZCI6ImZhZGVyLXRvdWNoIiwibmFtZSI6IkZhZGVyIFRvdWNo + In0seyJpZCI6InNlbGVjdCIsIm5hbWUiOiJTZWxlY3QifSx7ImlkIjoibXV0ZSIsIm5hbWUiOiJNdXRlIn0seyJpZCI6InNvbG8iLCJuYW1lIjoiU29sbyJ9LHsiaWQi + OiJyZWNvcmQtcmVhZHkiLCJuYW1lIjoiUmVjb3JkLXJlYWR5In0seyJpZCI6InYtcG90LWxlZHMiLCJuYW1lIjoiVi1Qb3QgTEVEcyJ9LHsiaWQiOiJsY2QiLCJuYW1l + IjoiTENEIn0seyJpZCI6Im1ldGVyIiwibmFtZSI6Ik1ldGVyIn1dLCJtYXBwaW5ncyI6W3siaWQiOiI1dUpKRjdzNUkwS3hBbHFHc0dUZUoiLCJuYW1lIjoiMSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDEvbGNkL2xpbmUxIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQuZnguaW5k + ZXggfX0iLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MTIsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQiLCJmeEdVSUQiOiJENTZC + MzIzOC1CNERELUI0NDgtQTcwMS04MjEwM0U3M0M1MTIiLCJpc0lucHV0RngiOnRydWUsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiJtYWluL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5 + cGUiOjMsImNoYW5uZWwiOjh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoibWFpbi9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiam9nIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NjAsImNoYXJhY3Rl + ciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudElu + ZGV4Ijoiam9nIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1haW4vZmFkZXIvdG91Y2giLCJncm91cElk + IjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCIsInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYXJrZXIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo4NH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYXJrZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJlYWQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo3NH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJ3cml0ZSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaC1sZWZ0Iiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoi + Y2gtcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiYmFuay1sZWZ0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0Iiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJiYW5rLXJpZ2h0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDd9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiYmFuay1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoicmV3aW5kIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6OTF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoicmV3aW5kIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmYXN0LWZ3ZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjkyfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJwbGF5Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicGxheSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic3RvcCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InJlY29y + ZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiY3ljbGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4Nn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjeWNsZSIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9fSx7ImlkIjoiem9vbSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJzY3J1YiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29yLWxlZnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OH0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjdXJzb3ItbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY3Vyc29yLXJpZ2h0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6OTl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjdXJzb3ItdXAiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Nn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImN1 + cnNvci1kb3duIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWRvd24iLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6Im51ZGdlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODV9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibnVkZ2UiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImRyb3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4N30sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9w + IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJyZXBsYWNlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODh9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoicmVwbGFjZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2xpY2siLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4 + OX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjbGljayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjkwfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImYxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6NTR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImYyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6NTV9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImYzIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NTZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjMiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImY0Iiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6NTd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImY1Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImY2Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjYiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6InNtcHRlLWJlYXRzIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6ImxjZC9hc3NpZ25tZW50IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4ifSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC9hc3NpZ25tZW50IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibGNkL3RpbWVjb2RlIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBl + IjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQiOjJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVjb2RlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2gxL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzJ9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gxL3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9mYWRlci90b3VjaCIs + Imdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91 + Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjaDEvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNo + MS92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDEvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L3dyYXAiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZh + bHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3dyYXAi + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90 + LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwi + bWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEv + di1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L3NpbmdsZSIsImdy + b3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNl + VmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qvc2lu + Z2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBv + dC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9zcHJlYWQiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMS9zb2xvIiwiZ3Jv + dXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9zb2xvIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX19LHsiaWQiOiJjaDEvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2gxL3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gxL2xjZC9saW5lMSIsImdyb3VwSWQi + OiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2gxL2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUyIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMDAgZGNiYV0ifSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2gyL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2gyL3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9mYWRlci90b3VjaCIsImdyb3Vw + SWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIvdG91Y2giLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJj + aDIvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi92LXBv + dC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDIvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3dyYXAiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L3dyYXAiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMi + LCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291 + cmNlVmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qv + Ym9vc3QtY3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NpbmdsZSIsImdyb3VwSWQi + OiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUi + OjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvc2luZ2xlIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRz + Iiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMi9zb2xvIiwiZ3JvdXBJZCI6 + InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiJjaDIvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjF9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2gyL3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2gyL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2Qi + LCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MSwibGluZSI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2gyL2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MSwibGluZSI6MX0sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMDEgZGNiYV0ifSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gz + L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3Yt + c2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJm + YWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2giLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDMvZmFk + ZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Mn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy92LXBvdC9jb250 + cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTgsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + Im1heFN0ZXBTaXplIjoxLjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1w + b3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3dyYXAiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3Vy + Y2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFs + dWUiOjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QvYm9vc3Qt + Y3V0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBv + dC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzV9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3Qvc2luZ2xlIiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNH0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9zb2xvIiwiZ3JvdXBJZCI6InNvbG8i + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiY2gzL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoMy9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291 + cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoMy9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdN + aWRpUGF0dGVybiI6IkQwIFswMDEwIGRjYmFdIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDMvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXNl + bGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXNlbGVj + dCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXIt + dG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6Mjd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g0L2ZhZGVyIiwi + Z3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvdi1wb3QvY29udHJvbCIs + Imdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE5LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MS4wfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90Iiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMi + LCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjow + Ljc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIs + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVk + cyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6 + eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjR9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjE5fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + ImNoNC9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6M30sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDQvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDQvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6 + eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQv + bGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBh + dHRlcm4iOiJEMCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g0L21ldGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1zZWxlY3Qi + LCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1zZWxlY3QiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNo + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjI4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNS9mYWRlciIsImdyb3Vw + SWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo0fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3YtcG90L2NvbnRyb2wiLCJncm91 + cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNp + emUiOjEuMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBl + IjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoy + MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEyfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDUv + cmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjR9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g1L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g1L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlw + ZSI6MTIsImRpc3BsYXlJZCI6NCwibGluZSI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L2xjZC9s + aW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NCwibGluZSI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJu + IjoiRDAgWzAxMDAgZGNiYV0ifSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoNS9tZXRlci9wZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Ytc2VsZWN0IiwiZ3Jv + dXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoyOX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDYvZmFkZXIiLCJncm91cElkIjoi + ZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNi92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6 + InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjEsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjox + LjB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QiLCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6 + eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3dyYXAiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QvYm9vc3QtY3V0IiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvc2luZ2xlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9zcHJlYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjF9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g2L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNi9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g2L3JlY29y + ZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1fSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9y + ZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNi9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEy + LCJkaXNwbGF5SWQiOjUsImxpbmUiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g2L2xjZC9saW5lMSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9sY2QvbGluZTIi + LCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2xjZC9saW5lMiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQw + IFswMTAxIGRjYmFdIn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDYvbWV0ZXIvcGVhayIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXNlbGVjdCIsImdyb3VwSWQi + OiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXNlbGVjdCIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZl + ZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MzB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g3L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVy + Iiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBv + dCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90IiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlw + ZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC93cmFwIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdN + aWRpUGF0dGVybiI6IkIwIDM2IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0 + eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NpbmdsZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3 + TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjR9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvc3ByZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2g3L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoNy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoNy9yZWNvcmQtcmVh + ZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Nn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvcmVjb3Jk + LXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDcvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlz + cGxheUlkIjo2LCJsaW5lIjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoNy9sY2QvbGluZTEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbGNkL2xpbmUyIiwiZ3Jv + dXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo2LCJsaW5lIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9sY2QvbGluZTIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEx + MCBkY2JhXSJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L21l + dGVyL3BlYWsiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1zZWxlY3QiLCJncm91cElkIjoidi1z + ZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTExfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjMxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNoOC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNv + dXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoOC9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMywiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvd3JhcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBh + dHRlcm4iOiJCMCAzNyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6 + MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zaW5nbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQ + YXR0ZXJuIjoiQjAgMzcgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3NwcmVhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoOC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyM30sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDgvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjE1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjaDgvcmVjb3JkLXJlYWR5Iiwi + Z3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3JlY29yZC1yZWFk + eSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY2g4L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJ + ZCI6NywibGluZSI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDgvbGNkL2xpbmUxIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L2xjZC9saW5lMiIsImdyb3VwSWQi + OiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NywibGluZSI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g4L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTEgZGNi + YV0ifSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tZXRlci9w + ZWFrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfV0sImNvbnRyb2xsZXJDdXN0b21EYXRhIjp7ImNvbXBhbmlvbiI6eyJj + b250cm9scyI6W3siaGVpZ2h0IjozMiwiaWQiOiIwNzIyNmRmOS0zNzBjLTQ0OTUtOGI1OC0xNzJhM2E1MGQ3ZTYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRp + b24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gxL3YtcG90L2NvbnRyb2wiLCJjaDEvdi1zZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjAs + InkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiJiNzE4YzgzNi0wNzE4LTQ2YTEtODYwZC05YmY4Yzc1YzgwMTgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gyL3YtcG90L2NvbnRyb2wiLCJjaDIvdi1zZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjEyOCwi + eSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6IjM3ZWE4MDE1LWM4OWYtNGQ4Mi05YWI5LTdjOWI4MWViMjlhZSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDMvdi1wb3QvY29udHJvbCIsImNoMy92LXNlbGVjdCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6MjU2LCJ5 + IjowfSx7ImhlaWdodCI6MzIsImlkIjoiMWZhMGJiYTAtODBlNy00ZGUzLWI0ZWQtNjNmZjZiM2MyNDEwIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + YWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5l + ZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC92LXBvdC9jb250cm9sIiwiY2g0L3Ytc2VsZWN0Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4IjozODQsInki + OjB9LHsiaGVpZ2h0IjozMiwiaWQiOiI3YmVhYzgwNC05MjFlLTQ0MzctOGE3OC01MzM0Mjc3MTQ2MTkiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJh + Ym92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibWFwcGluZ3MiOlsiY2g1L3YtcG90L2NvbnRyb2wiLCJjaDUvdi1zZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjUxMiwieSI6 + MH0seyJoZWlnaHQiOjMyLCJpZCI6Ijg4NGRhYTJiLTc4Y2QtNGYwNC05OWMyLTgxM2ZhMjExNzNjZSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFi + b3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQi + OnRydWV9LCJtYXBwaW5ncyI6WyJjaDYvdi1wb3QvY29udHJvbCIsImNoNi92LXNlbGVjdCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6NjQwLCJ5Ijow + fSx7ImhlaWdodCI6MzIsImlkIjoiZjAwYmE2YTYtZGVkNi00NzU3LWFlZTUtNTkwOThkNDc1ZDBlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJv + dmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy92LXBvdC9jb250cm9sIiwiY2g3L3Ytc2VsZWN0Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4Ijo3NjgsInkiOjB9 + LHsiaGVpZ2h0IjozMiwiaWQiOiI0Yjc2YWFmZi02N2M3LTRlMmQtYTUzNi1kYWQzNjQyZWU2MWYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92 + ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibWFwcGluZ3MiOlsiY2g4L3YtcG90L2NvbnRyb2wiLCJjaDgvdi1zZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjg5NiwieSI6MH0s + eyJoZWlnaHQiOjQ4MCwiaWQiOiI0NjhkYzAzNC04ZTRiLTQ3MGItOWE1MS0yNjQ2OTA2YmI2ZjciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNl + bnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibWFwcGluZ3MiOlsiY2gxL2ZhZGVyIiwiY2gxL2ZhZGVyL3RvdWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjowLCJ5Ijo2NH0s + eyJoZWlnaHQiOjQ4MCwiaWQiOiI4NWIyNDc3ZS0xZWQxLTQ1YTYtYjZiMC1mNWQ2Mzk2YTg0ZWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNl + bnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibWFwcGluZ3MiOlsiY2gyL2ZhZGVyIiwiY2gyL2ZhZGVyL3RvdWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjgsInkiOjY0 + fSx7ImhlaWdodCI6NDgwLCJpZCI6IjljZGEwMTU0LTM1N2QtNGJmMS05ZGRjLTY0Zjk3OTA5ZTA2OCIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoi + Y2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDMvZmFkZXIiLCJjaDMvZmFkZXIvdG91Y2giXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjI1NiwieSI6 + NjR9LHsiaGVpZ2h0Ijo0ODAsImlkIjoiYmRkZGJmYzQtM2E4MC00Mzk3LWJjNDMtYTZlYjkyZWI2MGMxIiwibGFiZWxPbmUiOnsiYW5nbGUiOjI3MCwicG9zaXRpb24i + OiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6InJpZ2h0T2ZSaWdodCIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC9mYWRlciIsImNoNC9mYWRlci90b3VjaCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6Mzg0LCJ5 + Ijo2NH0seyJoZWlnaHQiOjQ4MCwiaWQiOiI5ZmUwYWRlZi04YWU5LTRhMDctYThmNy0xMTkyM2JiMmM0MmEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlv + biI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g1L2ZhZGVyIiwiY2g1L2ZhZGVyL3RvdWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo1MTIs + InkiOjY0fSx7ImhlaWdodCI6NDgwLCJpZCI6IjlkMWZkMzQ3LWUzNzMtNGFmMi04MWU0LWFkYTdlODBmMGY2OCIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0 + aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDYvZmFkZXIiLCJjaDYvZmFkZXIvdG91Y2giXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjY0 + MCwieSI6NjR9LHsiaGVpZ2h0Ijo0ODAsImlkIjoiMjU5ZjAzYzYtYWZlNC00YTVkLTllMTItNjZmNmQ2MDMyNjI3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjI3MCwicG9z + aXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6InJpZ2h0T2ZSaWdodCIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy9mYWRlciIsImNoNy9mYWRlci90b3VjaCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6 + NzY4LCJ5Ijo2NH0seyJoZWlnaHQiOjQ4MCwiaWQiOiJhMTg2ZjFhMS1lNzhiLTQzMjAtYjk5ZC0yMjI1NWRhZDBhZTMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJw + b3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6 + ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g4L2ZhZGVyIiwiY2g4L2ZhZGVyL3RvdWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4 + Ijo4OTYsInkiOjY0fSx7ImhlaWdodCI6MzIsImlkIjoiZmU0MmIzNTMtYjQ5MS00YTFlLTg0MzgtYjFjZjMxZDMwMzFmIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDEvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo2NCwieSI6Mjg4fSx7ImhlaWdo + dCI6MzIsImlkIjoiZmIyY2VkOTctNDllMy00ZWEyLThkMTMtMTg2MWVkNDFkMzJhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6 + ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBw + aW5ncyI6WyJjaDIvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxOTIsInkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6IjIzMDViYzEy + LTk5ZjAtNDc2MC1iNmU1LTY4ZDA0Yjc3OTYwNyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + ImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gzL3NlbGVjdCJd + LCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MzIwLCJ5IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiIwNTNjYjQ1Yi05MDFmLTRiZGItYWYyNi03MjFj + ZjUwYjQ3NDciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6 + MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC9zZWxlY3QiXSwic2hhcGUiOiJyZWN0YW5nbGUi + LCJ3aWR0aCI6MzIsIngiOjQ0OCwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiNTQyMmE2YzUtNGQ0ZS00NjYyLThmNjItNDNkY2MyMjIxNTBlIiwibGFiZWxPbmUi + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dC + b3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDUvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo1NzYs + InkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6ImUwYzViZGFjLTUzNDctNGM4NS1iMzMyLWYzOTI5ZmM3ZDgxYSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g2L3NlbGVjdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NzA0LCJ5IjoyODh9LHsiaGVpZ2h0Ijoz + MiwiaWQiOiI0MjVkNTRlMy00OTYyLTQ3ODUtYTU4MC03ODM3MGU1ZWFmMjUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdz + IjpbImNoNy9zZWxlY3QiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjgzMiwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiZjkyZDliODMtN2Nh + OC00ZjVhLThhZWQtNzlkMWYzZDgyZTI5IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFi + ZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDgvc2VsZWN0Il0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo5NjAsInkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6ImViMWQyZjM0LTc4YjMtNDM4YS1hZjA4LWM0YTBjNDlm + ZmNhYyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gxL211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0 + aCI6MzIsIngiOjY0LCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5YTdlYjVmMS1jNThmLTQyY2QtYTc4NS0xOWU4ZDVhOTZhOTAiLCJsYWJlbE9uZSI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMi9tdXRlIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxOTIsInkiOjM1Mn0s + eyJoZWlnaHQiOjMyLCJpZCI6IjM1Yzc3Y2UzLTY2NGMtNDI2Ni05ZjcyLWFlZDM4YjJmOGJiOSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRl + ciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVl + fSwibWFwcGluZ3MiOlsiY2gzL211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjMyMCwieSI6MzUyfSx7ImhlaWdodCI6MzIsImlkIjoiODVk + ZDk3OTQtNDQ1Zi00ZjQ2LWJlNjYtODYzN2NlYzZjZWVlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDQvbXV0 + ZSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NDQ4LCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI1NmNkZTAwYy03MThiLTQ5NDctODQ2NS1h + NjA2Y2JmZTFhMjgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNS9tdXRlIl0sInNoYXBlIjoicmVjdGFuZ2xl + Iiwid2lkdGgiOjMyLCJ4Ijo1NzYsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6IjY4YWQ1ZjhmLTc0ZDktNDg3Mi1hZjQ5LWFhOWJmZmNmMDFkZiIsImxhYmVsT25l + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93 + Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g2L211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjcwNCwi + eSI6MzUyfSx7ImhlaWdodCI6MzIsImlkIjoiMDE4MWVjOTMtNDNkYS00MzExLWExM2QtOGZjZGQ1ZmJhYjFlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9u + IjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDcvbXV0ZSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6ODMyLCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwi + aWQiOiI5YTBjZThhNy01MDE3LTQxYjQtOTdjNi03OWIzYzhiYjJlYWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpb + ImNoOC9tdXRlIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo5NjAsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6IjBmZjVhZWU1LTc3OTMtNGM3 + Zi1hMjA0LTEwZDU4ZWY3NTlhMiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdv + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gxL3NvbG8iXSwic2hhcGUiOiJy + ZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjY0LCJ5Ijo0MTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiIzYzRjMmY0YS01ZTgxLTQ4NWItYTI1YS0zMDJiZDlkZjBhMmMiLCJs + YWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMi9zb2xvIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4 + IjoxOTIsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6Ijk5MWRmNjBkLTZkNzItNDJjMC1hY2MxLTg5NjUyNTcyYWZkMiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNv + bnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gzL3NvbG8iXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjMyMCwieSI6NDE2fSx7ImhlaWdo + dCI6MzIsImlkIjoiOWIyMDBkZjctYTQxMS00YzQxLTg1YWQtZjZhY2MxYTQ5ZDRlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6 + ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBw + aW5ncyI6WyJjaDQvc29sbyJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NDQ4LCJ5Ijo0MTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiI1MGEyN2M2Mi01 + ZGQzLTQ2M2YtYjJmYS0wY2M5Yzc1MTRjMmYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNS9zb2xvIl0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo1NzYsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6IjE2YTFiZjFhLWU1MzgtNDg5Zi04NGNlLTJjNTVkODkw + OThiYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g2L3NvbG8iXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0 + aCI6MzIsIngiOjcwNCwieSI6NDE2fSx7ImhlaWdodCI6MzIsImlkIjoiOTJiNmI5MjctZTViNC00NTRhLWFmNmQtN2Q2ZWE2MTZiYTgyIiwibGFiZWxPbmUiOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20i + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDcvc29sbyJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6ODMyLCJ5Ijo0MTZ9 + LHsiaGVpZ2h0IjozMiwiaWQiOiJkN2NjNzFhYi1kNzNmLTRhZWYtYTY0NC03ZGZjYTcyNzhhMmUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50 + ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sIm1hcHBpbmdzIjpbImNoOC9zb2xvIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo5NjAsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6ImRk + YzUxOGQ2LWFkMDktNDg1ZS1hZDE3LTIzMDUyMjMwMTFjZCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVk + Ijp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDEv + cmVjb3JkLXJlYWR5Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4Ijo2NCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiZmViNzYwZDEtNTk4ZC00MzE3 + LThmNTUtZDFlYzIxM2VkYTAyIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3 + byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMi9yZWNvcmQtcmVhZHkiXSwi + c2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjE5MiwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiNzZhYzdiYTgtNzk4Ny00ZTg5LTlmZjItNDhkYTVlODhk + OTU3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMy9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUi + LCJ3aWR0aCI6MzIsIngiOjMyMCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiYTViY2ZlMjktYjVmOS00OTM2LThhODItM2EzMGVmMzc1ZDc2IiwibGFiZWxPbmUi + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxv + d0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngi + OjQ0OCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiOTRhMzg4ZmQtZDE4Ny00OWRjLTkxM2ItZDA1YTdlN2M5MDU0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNS9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjU3NiwieSI6NDgwfSx7 + ImhlaWdodCI6MzIsImlkIjoiYzQ1MTRiN2MtY2U3OC00NGNhLWExZjAtZTA0N2YzNTI5MjIzIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVU + b3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sIm1hcHBpbmdzIjpbImNoNi9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjcwNCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlk + IjoiNGVlMzQ1YjktMDZmNy00MTI3LWE4YTAtOGZkZmQ1ODM5ZTgwIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpb + ImNoNy9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjgzMiwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiNzlkNTNjNTAtNjUy + Yy00MDc0LTg0NDYtNDBmYmMwZGMwYzA1IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoOC9yZWNvcmQtcmVh + ZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjk2MCwieSI6NDgwfSx7ImhlaWdodCI6NDgwLCJpZCI6ImQ0NGZjYWMzLTdkYzEtNDhmNy1iMmM5LTZl + NmJiMmQ0MzI1ZiIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5n + bGUiOjI3MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJtYWluL2ZhZGVyIiwibWFpbi9mYWRlci90 + b3VjaCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTAyNCwieSI6NjR9LHsiaGVpZ2h0IjozMiwiaWQiOiI4NjFjNWEwOS1mMDMzLTQ5ZGItYjkx + My1iNDc5YmVlNzdhZWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIm1hcmtlciJdLCJzaGFwZSI6InJlY3Rhbmds + ZSIsIndpZHRoIjozMiwieCI6MTA4OCwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiNjNiYzljNmYtZTU2NC00OGMyLWEzYTEtOWY0OTQzZjc0YWEyIiwibGFiZWxP + bmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVs + b3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaC1yaWdodCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTIx + NiwieSI6NjR9LHsiaGVpZ2h0IjozMiwiaWQiOiJkY2NiZGFmZi03MzZlLTQyZjMtOGRlZC05NmNkODNhNzllZjgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRp + b24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbInJlYWQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEwODgsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJp + ZCI6IjZiZDcwZDgxLTc3YzctNGE4ZC05MzI3LTM2YmZiNTBiNmQzZSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsi + d3JpdGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEwODgsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6IjQzOWE0OThhLWJhZWEtNGIyZS1i + MTA1LWM1MTAyZGFkMjcxNyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gtbGVmdCJdLCJzaGFwZSI6InJlY3Rh + bmdsZSIsIndpZHRoIjozMiwieCI6MTE1MiwieSI6NjR9LHsiaGVpZ2h0IjozMiwiaWQiOiIxZmQ2ZmQyZi03OTUxLTQ2ZGItYjIyMy00ZWViMDE4YTk1YmIiLCJsYWJl + bE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJi + ZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImJhbmstbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6 + MTE1MiwieSI6MTI4fSx7ImhlaWdodCI6MzIsImlkIjoiNWE1YWIxZDItZDNmZC00ZjBmLWE0NzktNDE2N2FlN2Y3NTI3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiYW5rLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjE2LCJ5IjoxMjh9LHsiaGVp + Z2h0IjozMiwiaWQiOiIzNWZmZDc3Yy1mMzc5LTRhNGUtYjVjMS0wZjhhNWFlNDZhMTgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJz + aXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1h + cHBpbmdzIjpbInJld2luZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTE1MiwieSI6MjI0fSx7ImhlaWdodCI6MzIsImlkIjoiNDAzMTNhYzkt + NDFkYy00MGQwLWE5NWQtNWRmMDdmZTU3YzlhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwi + bGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJmYXN0LWZ3ZCJdLCJz + aGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTIxNiwieSI6MjI0fSx7ImhlaWdodCI6MzIsImlkIjoiOWE4NWM1MWItOGE3NC00YzA2LWI0YmEtN2RmYTVm + MTI2YTVkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJwbGF5Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgi + OjMyLCJ4IjoxMTUyLCJ5IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiIzNzdjN2ZmZi1iY2M2LTRjNGItOGVmZS03MDc5NWYxMTAwNDMiLCJsYWJlbE9uZSI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbInN0b3AiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEyMTYsInkiOjI4OH0seyJo + ZWlnaHQiOjMyLCJpZCI6ImViYWUxNmFjLTVlYWYtNDkxOS1hYjZlLWJmM2FhNjY3YWFmOCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwi + bWFwcGluZ3MiOlsicmVjb3JkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMTUyLCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiJkZjc5YWFk + YS0zNDVlLTQ0NTgtOWM0Ni00NzQ0Y2MxYTJmMDYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImN5Y2xlIl0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjE2LCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiJkOWRhZjVkOC04Mzg0LTRlYTUtOGYyMy1hNmI1Mjhh + Nzg0NjEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjow + LCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiem9vbSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRo + Ijo5NiwieCI6MTE1MiwieSI6NDE2fSx7ImhlaWdodCI6MzIsImlkIjoiOTAzZjNhOTQtNWQ2Ny00YTlkLTk0Y2EtNDRlZjY3MjU3YWE2IiwibGFiZWxPbmUiOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJz + aXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJqb2ciLCJzY3J1YiJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo5NiwieCI6MTE1MiwieSI6NDgwfV0s + ImdyaWREaXZpc2lvbkNvdW50IjoyLCJncmlkU2l6ZSI6MzJ9fSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWFja2llLWNvbnRyb2wifQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 94 0 752 735 + FXID {C2696F78-CED0-A640-AF0A-62E398AB957E} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {400E1B70-8443-E742-975D-E7CDB3E88B71} + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {5703593C-1D68-4F47-9069-A5F1498198E2} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-591-broken-projection-save.RPP b/plugin-reaper-realearn/resources/test-projects/issue-591-broken-projection-save.RPP new file mode 100644 index 0000000..feca0a8 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-591-broken-projection-save.RPP @@ -0,0 +1,140 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADEAQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS41IiwiaWQiOiJLUmxZNV9uRiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6Im5hVWJIUEFaWmdYcEhORDVDcHgzeSIsIm5hbWUiOiIxIiwic291cmNlIjp7 + ImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC42 + MywibWF4U3RlcFNpemUiOjAuMDV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im15In0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {B6B6E5CB-F70B-CC45-A63A-07728D28CF5B} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-593-control-transformation-time-domain.RPP b/plugin-reaper-realearn/resources/test-projects/issue-593-control-transformation-time-domain.RPP new file mode 100644 index 0000000..d1ee29b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-593-control-transformation-time-domain.RPP @@ -0,0 +1,160 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3455 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 5.73 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABCCQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS41IiwiaWQiOiJNVTJ2SGticSIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiYTdPRk1lNi1WcXBRWnM2cGVxR3BMIiwibmFtZSI6IlJhbmRvbSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjoxLCJudW1iZXIiOjYwLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsInJlYXBlclNvdXJjZVR5 + cGUiOiJ0aW1lciIsInRpbWVyTWlsbGlzIjoxMDAwMDAwMH0sIm1vZGUiOnsibWF4VGFyZ2V0VmFsdWUiOjAuMCwibWF4U3RlcFNpemUiOjAuMDUsImVlbENvbnRyb2xU + cmFuc2Zvcm1hdGlvbiI6InkgPSByZWxfdGltZSA8IDIwMDAgPyByYW5kKDEpIDogc3RvcCJ9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI2 + QmxOZGp6Rmh2N2JTZGM1Rjh2QWMiLCJuYW1lIjoiVHJhbnNpdGlvbiBmcm9tIDAgdG8geCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjoxLCJudW1iZXIiOjYy + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJlZWxDb250cm9sVHJh + bnNmb3JtYXRpb24iOiJ5ID0gYWJzKHggLSB5KSA8IDAuMDUgPyBzdG9wIDogeCAqIG1pbihyZWxfdGltZSAvIDUwMCwgMSkifSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6Indqb3Vwc2h2 + ZjJKWC1zWDZTajJoaCIsIm5hbWUiOiJUcmFuc2l0aW9uIGZyb20geSB0byB4Iiwic291cmNlIjp7InR5cGUiOjIsImNoYW5uZWwiOjEsIm51bWJlciI6NjQsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMzUsIm1heFNvdXJjZVZhbHVlIjow + LjYzLCJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsQ29udHJvbFRyYW5zZm9ybWF0aW9uIjoicmVsX3RpbWU7IHkgPSBhYnMoeCAtIHkpIDwgMC4wNSA/IHN0b3AgOiB5ICsg + MC4xICogKHggLSB5KSIsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiUTBZMDJRV1ZjY0d0d2t1U2xILTZ2IiwibmFtZSI6IlNpbnVzIiwi + c291cmNlIjp7ImNhdGVnb3J5IjoicmVhcGVyIiwidHlwZSI6MSwiY2hhbm5lbCI6MSwibnVtYmVyIjo2NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJyZWFwZXJTb3VyY2VUeXBlIjoidGltZXIiLCJ0aW1lck1pbGxpcyI6MTAwMH0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMzUs + Im1heFNvdXJjZVZhbHVlIjowLjYzLCJtYXhTdGVwU2l6ZSI6MC4wNSwiZWVsQ29udHJvbFRyYW5zZm9ybWF0aW9uIjoieSA9IChzaW4ocmVsX3RpbWUgLyA1MDApICsg + MSkgLyAyIn0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MH0sImlzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ4LTg3SVJSd3RnZFJnWVV0aVFEUXgiLCJuYW1lIjoiU3RlcHBlZCBzaW51cyIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InJlYXBlciIsInR5cGUiOjEsImNoYW5uZWwiOjEsIm51bWJlciI6NjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MCwicmVhcGVyU291cmNlVHlwZSI6InRpbWVyIiwidGltZXJNaWxsaXMiOjEwMDAwMDB9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjM1LCJtYXhT + b3VyY2VWYWx1ZSI6MC42MywibWF4U3RlcFNpemUiOjAuMDUsImVlbENvbnRyb2xUcmFuc2Zvcm1hdGlvbiI6InkgPSAocmVsX3RpbWUgLyAxMDApICUgMTAgPT0gMCA/ + IChzaW4ocmVsX3RpbWUgLyA1MDApICsgMSkgLyAyIDogbm9uZSJ9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9LCJpc0VuYWJsZWQiOmZhbHNlfV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 318 84 752 735 + FXID {114917DB-650E-884E-91C0-5FC995BE5371} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-599-this-fx-index.RPP b/plugin-reaper-realearn/resources/test-projects/issue-599-this-fx-index.RPP new file mode 100644 index 0000000..316e650 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-599-this-fx-index.RPP @@ -0,0 +1,149 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADbAQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS43IiwiaWQiOiJWTVNQd2VZMiIsImNvbnRyb2xEZXZpY2VJZCI6ImtleWJvYXJkIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVs + dENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkF2ekdCMVQ4dDl5LWRBWm01OGUzaiIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + a2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6 + NjV9fSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoxMiwiZnhBbmNob3IiOiJkeW5hbWljIiwiZnhFeHByZXNzaW9u + IjoidGhpc19meF9pbmRleCArIDEiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {A280567A-30ED-1945-969A-53D2DD5F5CB6} + WAK 0 0 + BYPASS 1 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {27CB341F-08E6-4540-94C3-9A797EE56638} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-6-send-midi-target.rpp b/plugin-reaper-realearn/resources/test-projects/issue-6-send-midi-target.rpp new file mode 100644 index 0000000..6739ed1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-6-send-midi-target.rpp @@ -0,0 +1,150 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA7wMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi44LjAtcmMuNCIsImlkIjoiQ2FtUl85RkwiLCJsZXRVbm1hdGNoZWRFdmVudHNUaHJvdWdoIjpmYWxzZSwic2VuZEZlZWRiYWNrT25seUlmQXJt + ZWQiOnRydWUsImZlZWRiYWNrRGV2aWNlSWQiOiI4IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6 + ImExZTkxNzI1LTJlZmQtNDQ2NC1iNmE1LTVkNTAxOTUzMzAzOCIsIm5hbWUiOiJTZW5kIE1JREkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hh + cmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1ha2VBYnNvbHV0ZUVuYWJsZWQiOnRy + dWV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInJhd01p + ZGlQYXR0ZXJuIjoiRTAgWzBnZmUgZGNiYV0gWzBubWwga2ppaF0ifX0seyJpZCI6IjIzMTNkNGIxLTU2OWYtNGFiZi04Y2Q1LTM5YTYzMTNlYzBmZCIsIm5hbWUiOiJT + ZW5kIE9TQyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBZGRyZXNzUGF0dGVybiI6Ii8xL2ZhZGVyMSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wOCwibWFrZUFic29sdXRlRW5hYmxlZCI6 + dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjozMCwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwicmF3 + TWlkaVBhdHRlcm4iOiJFMCBbMGdmZSBkY2JhXSBbMG5tbCBramloXSIsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEvZmFkZXIxIiwib3NjQXJnSW5kZXgiOjAsIm9zY0Rl + dklkIjoiMmFlM2NhMWMtYzdkMy00MjU2LTk0YjMtZTY2NGZkZjYwZTRjIn19XX0= + AAAQAAAA + > + FLOAT 455 282 1436 1514 + FXID {0A1F6C3A-CBC9-490F-B988-82DA9E9248A0} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOAT 2204 459 1341 1163 + FXID {2C55FD5A-F575-4928-937A-26E562A905E9} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-612-instance-track-navigation.RPP b/plugin-reaper-realearn/resources/test-projects/issue-612-instance-track-navigation.RPP new file mode 100644 index 0000000..354dcb3 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-612-instance-track-navigation.RPP @@ -0,0 +1,886 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 7 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAA5TwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xMy4wLXByZS45IiwiaWQiOiJMTnZ1YnhUaCIsImNvbnRyb2xEZXZpY2VJZCI6IjQiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiJ3a0dlaEZkTHExSzM4N0FKeUFrUHUiLCJuYW1lIjoiVHJhY2sgLSIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjb2wxL3JvdzEva25vYiJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjotMC4wNjAwMDAwMDAwMDAwMDAwNSwibWF4U3RlcFNpemUiOi0wLjA2MDAwMDAw + MDAwMDAwMDA1LCJlbmNvZGVyVXNhZ2UiOiJkZWNyZW1lbnQtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6NDQsInRyYWNrRXhwcmVzc2lvbiI6Imluc3RhbmNlX3RyYWNr + X2luZGV4ID09IG5vbmUgJiYgMCB8fCBpbnN0YW5jZV90cmFja19pbmRleCAtIDEiLCJmeEFuY2hvciI6ImlkIiwidHJhY2tUb29sQWN0aW9uIjoiUGluQXNJbnN0YW5j + ZVRyYWNrIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19LHsiaWQiOiItVTk1MVBTRlFpUW8y + SXFfVWJYOGQiLCJuYW1lIjoiVHJhY2sgKyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEva25vYiJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjotMC4wNjAwMDAwMDAwMDAwMDAw + NSwibWF4U3RlcFNpemUiOi0wLjA2MDAwMDAwMDAwMDAwMDA1LCJlbmNvZGVyVXNhZ2UiOiJpbmNyZW1lbnQtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6NDQsInRyYWNr + RXhwcmVzc2lvbiI6Imluc3RhbmNlX3RyYWNrX2luZGV4ID09IG5vbmUgJiYgMCB8fCBpbnN0YW5jZV90cmFja19pbmRleCArIDEiLCJmeEFuY2hvciI6ImlkIiwidHJh + Y2tUb29sQWN0aW9uIjoiUGluQXNJbnN0YW5jZVRyYWNrIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MH19LHsiaWQiOiJ1TkFMUmtYenpROGxYZWZmZEVhZEIiLCJuYW1lIjoiRlggLSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEva25vYiJ9LCJtb2RlIjp7Im1pblN0ZXBT + aXplIjotMC4wNjAwMDAwMDAwMDAwMDAwNSwibWF4U3RlcFNpemUiOi0wLjA2MDAwMDAwMDAwMDAwMDA1LCJlbmNvZGVyVXNhZ2UiOiJkZWNyZW1lbnQtb25seSJ9LCJ0 + YXJnZXQiOnsidHlwZSI6NTQsInRyYWNrR1VJRCI6Imluc3RhbmNlIiwiZnhBbmNob3IiOiJkeW5hbWljIiwiZnhFeHByZXNzaW9uIjoiaW5zdGFuY2VfZnhfaW5kZXgg + PT0gbm9uZSAmJiAwIHx8IGluc3RhbmNlX2Z4X2luZGV4IC0gMSIsInRyYWNrVG9vbEFjdGlvbiI6IlBpbkFzSW5zdGFuY2VUcmFjayIsImZ4VG9vbEFjdGlvbiI6IlBp + bkFzSW5zdGFuY2VGeCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiNlVNZG5w + YjBaWmZHME1qYzdWd2pQIiwibmFtZSI6IkZYICsiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3cxL2tub2IifSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6LTAuMDYwMDAwMDAwMDAw + MDAwMDUsIm1heFN0ZXBTaXplIjotMC4wNjAwMDAwMDAwMDAwMDAwNSwiZW5jb2RlclVzYWdlIjoiaW5jcmVtZW50LW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjU0LCJ0 + cmFja0dVSUQiOiJpbnN0YW5jZSIsImZ4QW5jaG9yIjoiZHluYW1pYyIsImZ4RXhwcmVzc2lvbiI6Imluc3RhbmNlX2Z4X2luZGV4ID09IG5vbmUgJiYgMCB8fCBpbnN0 + YW5jZV9meF9pbmRleCArIDEiLCJ0cmFja1Rvb2xBY3Rpb24iOiJQaW5Bc0luc3RhbmNlVHJhY2siLCJmeFRvb2xBY3Rpb24iOiJQaW5Bc0luc3RhbmNlRngiLCJ1c2VQ + cm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiYmFu + ay1sZWZ0Iiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjdXJzb3ItbGVmdCIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjksImNoYXJh + Y3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItbGVmdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aC1sZWZ0Iiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0IiwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJhbmstcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFj + dGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY3Vy + c29yLXJpZ2h0Iiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gtcmlnaHQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywi + Y2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNvbDEvcm93MS9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJr + aW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJwbGF5aW5nIjo0Mywic3RvcHBlZCI6MSwiZW1w + dHkiOjAsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic2No + ZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cxL2tub2Ii + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93Mi9wYWQi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRU + b0Rpc2NyZXRlIiwidmFsdWUiOnsic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJwbGF5aW5nIjo0Mywic3RvcHBlZCI6MSwicGF1c2VkIjoxLCJzY2hlZHVsZWRf + Zm9yX3JlY29yZF9zdGFydCI6NjQsInJlY29yZGluZyI6ODYsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29y + ZF9zdG9wIjo2NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cyL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3cyL2tub2IiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cyL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93My9wYWQiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJvbVRleHRUb0Rpc2NyZXRlIiwidmFs + dWUiOnsicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzdG9wcGVkIjoxLCJwbGF5aW5nIjo0Mywic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3Rv + cCI6NjQsImVtcHR5IjowLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInJlY29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NH19fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sMS9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMS9yb3czL2tub2IiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVy + Ijo4LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sMS9yb3czL2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVy + IjoxMiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7InBsYXlpbmci + OjQzLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInJlY29yZGluZyI6ODYsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwiZW1w + dHkiOjAsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVzZWQiOjF9fX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93NC9w + YWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDEvcm93NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3Rl + ciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzQv + a25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEsImNoYXJhY3Rl + ciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2LCJzY2hlZHVs + ZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsImVtcHR5IjowLCJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0LCJwbGF5aW5nIjo0Mywic2NoZWR1 + bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzdG9wcGVkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEvcGFkIiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3JvdzEva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEva25vYiIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3cyL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsi + ZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwbGF5aW5n + Ijo0MywiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcmVjb3Jk + X3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4Niwic3RvcHBlZCI6MSwicGF1c2VkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19 + LHsiaWQiOiJjb2wyL3JvdzIva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiY29sMi9yb3czL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRh + YmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJwbGF5aW5nIjo0Mywic3RvcHBlZCI6MSwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zv + cl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwiZW1wdHki + OjAsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzMvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wyL3Jv + dzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzMva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9y + b3c0L3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoi + RnJvbVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsicmVjb3JkaW5nIjo4Niwic3RvcHBlZCI6MSwiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0 + LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVzZWQiOjEsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInNjaGVkdWxlZF9m + b3JfcmVjb3JkX3N0YXJ0Ijo2NH19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3c0L3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sMi9yb3c0L2tub2IiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDIvcm93NC9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzEvcGFkIiwic291 + cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNj + cmV0ZSIsInZhbHVlIjp7InNjaGVkdWxlZF9mb3JfcGxheV9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9z + dGFydCI6NjQsInN0b3BwZWQiOjEsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJyZWNvcmRpbmciOjg2LCJwbGF5aW5nIjo0MywiZW1w + dHkiOjB9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNvbDMvcm93MS9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93MS9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNvbDMvcm93MS9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzIvcGFkIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7 + ImVtcHR5IjowLCJwYXVzZWQiOjEsInBsYXlpbmciOjQzLCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3Jl + Y29yZF9zdG9wIjo2NCwic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjR9fX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDMvcm93Mi9wYWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93Mi9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6Niwi + Y2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDMvcm93Mi9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzMvcGFkIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAs + ImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9y + X3JlY29yZF9zdGFydCI6NjQsInBhdXNlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0 + b3AiOjY0LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwicGxheWluZyI6NDMsImVtcHR5IjowLCJzdG9wcGVkIjoxfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzMvcGFkIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2wzL3JvdzMva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjN9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3czL2tub2Ii + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93NC9wYWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjox + fSwibW9kZSI6eyJmZWVkYmFja1ZhbHVlVGFibGUiOnsia2luZCI6IkZyb21UZXh0VG9EaXNjcmV0ZSIsInZhbHVlIjp7ImVtcHR5IjowLCJwbGF5aW5nIjo0Mywic2No + ZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwicGF1c2VkIjoxLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9w + bGF5X3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2LCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjR9fX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93NC9wYWQiLCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlfX0seyJpZCI6ImNvbDMvcm93NC9rbm9iIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTQsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wzL3JvdzQva25vYiIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cxL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVl + ZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQsInNjaGVkdWxl + ZF9mb3JfcGxheV9zdG9wIjo2NCwicGF1c2VkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJyZWNvcmRpbmciOjg2LCJzdG9wcGVkIjoxLCJwbGF5aW5n + Ijo0MywiZW1wdHkiOjAsInNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiJjb2w0L3JvdzEva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzEva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7 + ImlkIjoiY29sNC9yb3cyL3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxl + Ijp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3JldGUiLCJ2YWx1ZSI6eyJyZWNvcmRpbmciOjg2LCJwYXVzZWQiOjEsInNjaGVkdWxlZF9mb3JfcGxheV9zdGFydCI6NjQs + ImVtcHR5IjowLCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RvcCI6NjQsInBsYXlpbmciOjQzLCJzY2hlZHVsZWRfZm9yX3JlY29yZF9zdGFydCI6NjQs + InNjaGVkdWxlZF9mb3JfcmVjb3JkX3N0b3AiOjY0fX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzIvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzIv + a25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2w0L3JvdzIva25vYiIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3cz + L3BhZCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7ImZlZWRiYWNrVmFsdWVUYWJsZSI6eyJraW5kIjoiRnJv + bVRleHRUb0Rpc2NyZXRlIiwidmFsdWUiOnsicmVjb3JkaW5nIjo4Niwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RvcCI6NjQsInNjaGVkdWxlZF9mb3JfcGxheV9zdG9w + Ijo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJwbGF5aW5nIjo0Mywic3RvcHBlZCI6MSwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0YXJ0Ijo2NCwiZW1w + dHkiOjAsInBhdXNlZCI6MX19fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sNC9yb3czL3BhZCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiY29sNC9yb3czL2tub2IiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDQvcm93My9rbm9iIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQvcGFkIiwic291cmNl + Ijp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tWYWx1ZVRhYmxlIjp7ImtpbmQiOiJGcm9tVGV4dFRvRGlzY3Jl + dGUiLCJ2YWx1ZSI6eyJlbXB0eSI6MCwic2NoZWR1bGVkX2Zvcl9wbGF5X3N0b3AiOjY0LCJyZWNvcmRpbmciOjg2LCJzdG9wcGVkIjoxLCJzY2hlZHVsZWRfZm9yX3Jl + Y29yZF9zdG9wIjo2NCwic2NoZWR1bGVkX2Zvcl9yZWNvcmRfc3RhcnQiOjY0LCJzY2hlZHVsZWRfZm9yX3BsYXlfc3RhcnQiOjY0LCJwYXVzZWQiOjEsInBsYXlpbmci + OjQzfX19LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjb2w0L3JvdzQvcGFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjb2w0L3JvdzQva25vYiIsInNvdXJjZSI6eyJjaGFubmVsIjow + LCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY29sNC9yb3c0L2tub2IiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX1dLCJjb250cm9sbGVyQ3VzdG9tRGF0YSI6eyJjb21wYW5pb24iOnsiY29u + dHJvbHMiOlt7ImhlaWdodCI6NTAsImlkIjoiY29sNC9yb3cxL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNv + bDQvcm93MS9rbm9iIiwiY29sNC9yb3cxL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjowfSx7ImhlaWdodCI6NTAsImlkIjoiY29s + MS9yb3cyL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDEvcm93Mi9rbm9iIiwiY29sMS9yb3cyL3BhZCJd + LCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MTAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2w0L3JvdzIva25vYiIsImxhYmVsT25lIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sNC9yb3cyL2tub2IiLCJjb2w0L3JvdzIvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUw + LCJ4Ijo0MDAsInkiOjEwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDEvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3Ai + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBw + aW5ncyI6WyJjb2wxL3JvdzEva25vYiIsImNvbDEvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjEwMCwieSI6MH0seyJoZWlnaHQiOjUw + LCJpZCI6ImNvbDMvcm93MS9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJl + bFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wzL3JvdzEva25vYiIsImNvbDMv + cm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjMwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93MS9rbm9iIiwibGFiZWxP + bmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJj + ZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzEva25vYiIsImNvbDIvcm93MS9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3 + aWR0aCI6NTAsIngiOjIwMCwieSI6MH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93Mi9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJv + dmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzIva25vYiIsImNvbDIvcm93Mi9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjIwMCwieSI6MTAwfSx7Imhl + aWdodCI6NTAsImlkIjoiY29sMy9yb3cyL2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNvbDMvcm93Mi9rbm9i + IiwiY29sMy9yb3cyL3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjoxMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjb2wyL3JvdzMva25v + YiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMi9yb3czL2tub2IiLCJjb2wyL3JvdzMvcGFkIl0sInNoYXBlIjoi + Y2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoyMDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDMvcm93My9rbm9iIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wzL3JvdzMva25vYiIsImNvbDMvcm93My9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6NTAsIngiOjMwMCwi + eSI6MjAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sNC9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNv + bDQvcm93NC9rbm9iIiwiY29sNC9yb3c0L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6NDAwLCJ5IjozMDB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJj + b2wxL3JvdzQva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsi + YW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3c0L2tub2IiLCJjb2wxL3JvdzQvcGFk + Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjMwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDIvcm93NC9rbm9iIiwibGFiZWxPbmUiOnsi + YW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIi + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2wyL3JvdzQva25vYiIsImNvbDIvcm93NC9wYWQiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6 + NTAsIngiOjIwMCwieSI6MzAwfSx7ImhlaWdodCI6NTAsImlkIjoiY29sMy9yb3c0L2tub2IiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRv + cCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1h + cHBpbmdzIjpbImNvbDMvcm93NC9rbm9iIiwiY29sMy9yb3c0L3BhZCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo1MCwieCI6MzAwLCJ5IjozMDB9LHsiaGVpZ2h0 + Ijo1MCwiaWQiOiJjb2wxL3JvdzMva25vYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwi + bGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY29sMS9yb3czL2tub2IiLCJj + b2wxL3JvdzMvcGFkIl0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjUwLCJ4IjoxMDAsInkiOjIwMH0seyJoZWlnaHQiOjUwLCJpZCI6ImNvbDQvcm93My9rbm9iIiwi + bGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRp + b24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjb2w0L3JvdzMva25vYiIsImNvbDQvcm93My9wYWQiXSwic2hhcGUiOiJjaXJj + bGUiLCJ3aWR0aCI6NTAsIngiOjQwMCwieSI6MjAwfSx7ImhlaWdodCI6NTAsImlkIjoiYmFuay1yaWdodCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWlu + ZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiYW5rLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjUwfSx7ImhlaWdodCI6NTAs + ImlkIjoiYmFuay1sZWZ0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6 + eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImJhbmstbGVmdCJdLCJzaGFwZSI6InJl + Y3RhbmdsZSIsIndpZHRoIjo1MCwieCI6MCwieSI6NTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJhNzhiMjc3ZS1jZmJmLTRiMmItOWNjNi0xYTU1MGFlYjg3ZmQiLCJsYWJl + bE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gtbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo1MCwieCI6 + MCwieSI6MjUwfSx7ImhlaWdodCI6NTAsImlkIjoiZTMxMmQyYTItZWNmMS00MTg5LTk1YWYtNDE3NGM0M2E3NTBjIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjI1MH0seyJoZWlnaHQi + OjUwLCJpZCI6ImN1cnNvci1sZWZ0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJl + bFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImN1cnNvci1sZWZ0Il0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4IjowLCJ5IjoxNTB9LHsiaGVpZ2h0Ijo1MCwiaWQiOiJjdXJzb3ItcmlnaHQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6 + MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwi + c2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY3Vyc29yLXJpZ2h0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjUwLCJ4Ijo1MDAsInkiOjE1 + MH1dLCJncmlkRGl2aXNpb25Db3VudCI6MiwiZ3JpZFNpemUiOjUwfX0sImluc3RhbmNlVHJhY2siOnsiYWRkcmVzcyI6IkJ5SWQiLCJpZCI6IjI2RUU4NzdDLTlGMUEt + Njg0OC05MDgwLUQ1RDc4QkVDMDI1MiJ9LCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJCeUlkIiwiY2hhaW4iOnsiYWRkcmVzcyI6IlRyYWNrIiwidHJhY2siOnsiYWRk + cmVzcyI6IkJ5SWQiLCJpZCI6IjI2RUU4NzdDLTlGMUEtNjg0OC05MDgwLUQ1RDc4QkVDMDI1MiJ9LCJjaGFpbiI6Ik5vcm1hbCJ9LCJpZCI6IjkzNzMwNTZGLTlDRkYt + QTU0OS04RjM0LTU1QUI5NzREN0UyRCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 664 103 752 675 + FXID {6D056AE5-BA17-DE4E-B09F-19D26FA122B1} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {35578942-A924-344C-9B28-0CB9FD7FF8B2} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {40293ADF-FFF3-444B-BC7B-8D2488B5EA61} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOATPOS 64 417 559 394 + FXID {DAA6BC6E-F71B-5C43-A9F6-55FB961C6926} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {C1694AF9-E23E-2847-8956-FD498606C0E2} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {C4072016-D4B8-BB49-B13A-384E3D65925F} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {0147EA6B-EAF3-E142-ABE1-9E099680C85B} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {9373056F-9CFF-A549-8F34-55AB974D7E2D} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {3AF83E6C-F1B0-AA4F-A243-61B094CF7D4D} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {E02A91C1-9A37-4B4E-8985-07135C5D112D} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {96881C7D-2181-EA4F-9133-D9F8BF360839} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {CF108EE7-D8AB-B145-BC0C-938ACFD39553} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {87D431F7-F950-1F40-A4AA-72C59CD69AF5} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {1DAF6555-9F94-EF41-B130-2B1BB1B22446} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {7778C30E-9691-2F47-92EE-9AA0594A8F45} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {F6A9E363-9CC5-1F42-B802-DCBC62E6C003} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {534DB607-CEB1-0447-82A0-732DE903416C} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {AF63AF4C-9805-3047-BC01-3F14DB22DF7E} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {17868512-5473-FE48-A5C2-5F8378B346D3} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {E7604548-CFBB-DE4A-B889-DBF0126B2C78} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {372B5E15-95AB-704F-AE22-EC42A9E1ABCC} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {CD2879A0-D785-8940-A66D-77C0DF6DF191} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {126A2206-41C6-6D45-8DE7-CD649672559C} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {4F6D0FB6-DF54-2047-B01D-6FB193E8E8EA} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {C1791003-865E-7944-AA48-981691BB6B2E} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 347 309 372 469 + FXID {670B141D-6281-7B4E-AA52-411663F26077} + WAK 0 0 + BYPASS 0 0 0 + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 574 394 + FXID {0E3D8D7E-D71B-5544-B050-4518A7C27916} + WAK 0 0 + BYPASS 0 0 0 + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd6c0QczppvEOwrXozwAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACc0QczAACAP5zRBzMAAIA/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAAAAAAAAAAAAA= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 64 417 559 394 + FXID {208D455F-6B9D-B445-8EB0-3E25FDDB18A1} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-62-api.rpp b/plugin-reaper-realearn/resources/test-projects/issue-62-api.rpp new file mode 100644 index 0000000..27c42e7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-62-api.rpp @@ -0,0 +1,539 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 16 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAqIMAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMiIsImlkIjoiYURuZXZFeWEiLCJsZXRNYXRjaGVkRXZlbnRzVGhyb3VnaCI6dHJ1ZSwic2VuZEZlZWRiYWNrT25seUlmQXJt + ZWQiOnRydWUsImZlZWRiYWNrRGV2aWNlSWQiOiJmeC1vdXRwdXQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwiY29udHJvbGxl + ckdyb3VwcyI6W3siaWQiOiIwOGU0YzNmYi0wMzc0LTRhYTQtOTkxYS03ZWMzZGM4ODY5YjAiLCJrZXkiOiI3OTE0ZmI0OS03ZGIxLTRhNDYtYTk2OC1iYTFlYjUwZDRj + NTMiLCJuYW1lIjoiRmFkZXIifSx7ImlkIjoiYzJkYTJiZDItYTQzZS00MjdhLThkZjAtYTk2NjRlOTRkNTc2Iiwia2V5IjoiOTdkOGRjODAtMGM4NS00NWUyLTkyNjct + ZWZlYzYzMWVjNmMwIiwibmFtZSI6IlYtUG90In0seyJpZCI6IjgxMmE2MTU4LWY0NGMtNDY2NC05NmVlLWY2MDgyOTllMTBkOSIsImtleSI6IjgxYzMzYTAyLTZlZDgt + NDE4NC1iYWNmLWE3YjhiYmJhMzk5MiIsIm5hbWUiOiJWLVNlbGVjdCJ9LHsiaWQiOiJiZTA5YzdlNy03MGRlLTQyZGUtOTk3MC1hNjczZWY3MmFlNzQiLCJrZXkiOiJj + OTk5ZGVjMi1mOTk1LTQxNmUtOTAyYy00MzVlMTM3NDFkNDMiLCJuYW1lIjoiRmFkZXIgVG91Y2gifSx7ImlkIjoiOWMzZTdlZGQtZmNkNy00Zjg1LTk3YzEtZDA0ZTk4 + ZmY1MzI0Iiwia2V5IjoiZWRkZmYzZjYtNzMxYS00M2EwLWJiZGItNDY1ZWE2Y2YzMmZiIiwibmFtZSI6IlNlbGVjdCJ9LHsiaWQiOiJmNmZjMzNiZi02MjJlLTRjOTIt + YThiMi04ZmRlMmJhMTI5ZmYiLCJrZXkiOiI3MWI1Y2RkOC1mYzU3LTQ4ZmItODVkMy0zMzI2MWVhYjgzNjMiLCJuYW1lIjoiTXV0ZSJ9LHsiaWQiOiI2MGRlMjhjZC0z + ZjBmLTQ0MGItOThhYi02MGE0ZTBmYmFhNjEiLCJrZXkiOiI3OThhZTMzMi0wODMxLTRkOGUtOTdhNi1lMGQ4ZTg4N2RjNTgiLCJuYW1lIjoiU29sbyJ9LHsiaWQiOiJm + NjEwOTIzYi05NzVhLTQwMTItOWMzNi04MzgzZGViZjMxM2EiLCJrZXkiOiJiYmZkYmQ0MS0yNWIzLTRiNTYtOTNkNS0yMjE2ZGU3YzNiYWQiLCJuYW1lIjoiUmVjb3Jk + LXJlYWR5In0seyJpZCI6IjRhZDRhMjUyLTU0MWItNGQxOS05OTY1LTIzZjE5ZDgxZTZiNyIsImtleSI6IjMxYmY4MmZkLTFmY2EtNDZlOC1hODBhLWJiNmM4Yjg5MTc4 + OCIsIm5hbWUiOiJWLVBvdCBMRURzIn0seyJpZCI6IjU5NzA3ZmNhLTA3MDItNDQxOS1hMmY3LTBkYWM3ZTdhZjg4YiIsImtleSI6IjdiNjEwMDg5LTZlMTQtNDEyYi1h + YTI2LWZjZWI2YWM4YmUyMiIsIm5hbWUiOiJMQ0QifV0sIm1hcHBpbmdzIjpbeyJpZCI6IjBlZjBkODA0LTE5MGEtNGVlZC1iNzQ1LWZkZjdhYzI2ZWY4OSIsIm5hbWUi + OiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJ0eXBlIjozNCwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6 + W3siaWQiOiIxMDhhZjM4OC0zZTdjLTQwZWYtOTQ5NS0xZDFkNWNlODkwOTAiLCJncm91cElkIjoiODEyYTYxNTgtZjQ0Yy00NjY0LTk2ZWUtZjYwODI5OWUxMGQ5Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiN2NhZGNmMjUtYWM0MS00MDJlLTk5YWItMjhmZDcyMzhjZDVmIiwiZ3Jv + dXBJZCI6IjgxMmE2MTU4LWY0NGMtNDY2NC05NmVlLWY2MDgyOTllMTBkOSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMzfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6IjEyYzZhYmQxLWJiODYtNDJlYy04NWM1LTdlNGUyMGMxNDczZSIsImdyb3VwSWQiOiI4MTJhNjE1OC1mNDRjLTQ2NjQtOTZlZS1mNjA4Mjk5ZTEwZDkiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXNlbGVjdCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI1NjQ1MmMwYS1iNzYyLTQxY2MtYjhmYS0zMmRmMTMyZGMzOGUiLCJncm91cElk + IjoiODEyYTYxNTgtZjQ0Yy00NjY0LTk2ZWUtZjYwODI5OWUxMGQ5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + MTdmNGM3MTgtODdlYi00NzQ4LWIyNzktMTMwOTU1NDc1MjgyIiwiZ3JvdXBJZCI6IjgxMmE2MTU4LWY0NGMtNDY2NC05NmVlLWY2MDgyOTllMTBkOSIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImYzNWI1ZTBiLWFkZDQtNDkzMC04Njc0LTdlYTgyYThhM2MyMiIsImdyb3VwSWQiOiI4 + MTJhNjE1OC1mNDRjLTQ2NjQtOTZlZS1mNjA4Mjk5ZTEwZDkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozN30sIm1vZGUiOnsiZmVlZGJh + Y2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXNlbGVjdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5NDMx + N2U5Mi0wYWViLTQxNTYtYTBjNS00Yzk3YzNjOWU4ODAiLCJncm91cElkIjoiODEyYTYxNTgtZjQ0Yy00NjY0LTk2ZWUtZjYwODI5OWUxMGQ5Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1zZWxlY3QiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNzhjMjQwYTAtZDQwMS00NzMwLTg3MmQtNTRhYTJhY2E4NmZlIiwiZ3JvdXBJZCI6IjgxMmE2 + MTU4LWY0NGMtNDY2NC05NmVlLWY2MDgyOTllMTBkOSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM5fSwibW9kZSI6eyJmZWVkYmFja0Nv + bG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjY1ZDE4MGU4 + LTM5NDUtNGM5Yy1iZDVhLWFmYmMwNTA0Y2YyMSIsImdyb3VwSWQiOiJiZTA5YzdlNy03MGRlLTQyZGUtOTk3MC1hNjczZWY3MmFlNzQiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZGI5YjFlMzQtZDNiOS00ZTA0LWJlMzctOWJiZDlkYWU3NTU3IiwiZ3JvdXBJZCI6ImJlMDlj + N2U3LTcwZGUtNDJkZS05OTcwLWE2NzNlZjcyYWU3NCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNX0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxZmI4 + NmY3Yi1jZDljLTQ2ZGMtOTg0OS0yYjNjNjE0M2E3MjIiLCJncm91cElkIjoiYmUwOWM3ZTctNzBkZS00MmRlLTk5NzAtYTY3M2VmNzJhZTc0Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ijk5NWIzNTExLWM0MDEtNDYyZS04NTgyLTY1M2NkYTU1ZGRiYyIsImdyb3VwSWQiOiJi + ZTA5YzdlNy03MGRlLTQyZGUtOTk3MC1hNjczZWY3MmFlNzQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDd9LCJtb2RlIjp7ImZlZWRi + YWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91Y2giLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + N2Q0ZGVmY2YtYmNjMC00ZDY3LTk4ZDItNzA5YjE1NGY3ODQyIiwiZ3JvdXBJZCI6ImJlMDljN2U3LTcwZGUtNDJkZS05OTcwLWE2NzNlZjcyYWU3NCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9mYWRlci90b3VjaCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhNzAxMGVhZS0xNmY4LTRhNzMtYTc1My05ZjI1Nzg2MzFjNDMiLCJncm91cElk + IjoiYmUwOWM3ZTctNzBkZS00MmRlLTk5NzAtYTY3M2VmNzJhZTc0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA5fSwibW9kZSI6eyJm + ZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6IjcxMTA1YzliLWMzMjItNDRhYi04YWEyLTI1MGFkNjE3OWFkZSIsImdyb3VwSWQiOiJiZTA5YzdlNy03MGRlLTQyZGUtOTk3MC1hNjczZWY3MmFlNzQiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2giLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYzU2Y2EzZWQtZGIzNC00ZGJlLWEwMmEtNmI4N2NjMzRmYzkzIiwiZ3Jv + dXBJZCI6ImJlMDljN2U3LTcwZGUtNDJkZS05OTcwLWE2NzNlZjcyYWU3NCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMX0sIm1vZGUi + OnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9mYWRlci90b3VjaCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJkZjY1YTE5ZC04N2U4LTQ4YWMtYTkwMC0zZGM4MWU3MzIzODciLCJncm91cElkIjoiOWMzZTdlZGQtZmNkNy00Zjg1LTk3YzEtZDA0ZTk4ZmY1MzI0Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjODgyN2MzYS0wZDJlLTQ5NDUtYmM1OC1jMGU1ODNhOGU2NGUiLCJncm91cElkIjoiOWMzZTdlZGQtZmNkNy00Zjg1LTk3 + YzEtZDA0ZTk4ZmY1MzI0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDIvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI5MzQ4M2JlNS00ODE1LTRiNTUtYmEyYi1hZDQ4MTU5Y2Q5ZDUiLCJncm91cElkIjoiOWMz + ZTdlZGQtZmNkNy00Zjg1LTk3YzEtZDA0ZTk4ZmY1MzI0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIwODc3NjlmMi1hYTZkLTRkNzYtYmQwOS02ZmNiMDdiYWVj + ZjQiLCJncm91cElkIjoiOWMzZTdlZGQtZmNkNy00Zjg1LTk3YzEtZDA0ZTk4ZmY1MzI0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjd9 + LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI1MTRkMzdlMi1kZDY0LTQw + YmYtOGFmNC1lY2ZhZDBjYTY1ZDAiLCJncm91cElkIjoiOWMzZTdlZGQtZmNkNy00Zjg1LTk3YzEtZDA0ZTk4ZmY1MzI0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6Mjh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQi + OiJmMmZmODQ5OS1jNjcxLTQxMDgtYmFhNy04NTFlZWU4OWQyMWMiLCJncm91cElkIjoiOWMzZTdlZGQtZmNkNy00Zjg1LTk3YzEtZDA0ZTk4ZmY1MzI0Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0IiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiJiNjY3MTBkYS0wNTUzLTQ3ZGUtODIxYS01MTkwNTU3MGU3ZDAiLCJncm91cElkIjoiOWMzZTdlZGQtZmNkNy00Zjg1LTk3YzEtZDA0 + ZTk4ZmY1MzI0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2Vs + ZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJiYjZlMmU4OC0zYWJhLTQ2OTktODVlZi1iNDM4MmUxZWIwYTEiLCJncm91cElkIjoiOWMzZTdlZGQt + ZmNkNy00Zjg1LTk3YzEtZDA0ZTk4ZmY1MzI0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzF9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3Ii + Om51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvc2VsZWN0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI3MTU2NzA4OS1lODFkLTQxMjgtOWU5Yy1lYWFiOGU0OTMzNjkiLCJn + cm91cElkIjoiMDhlNGMzZmItMDM3NC00YWE0LTk5MWEtN2VjM2RjODg2OWIwIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjh9LCJtb2RlIjp7ImZlZWRiYWNr + Q29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYWluL2ZhZGVyIiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI0Y2MxMWQ1NC0zNjU0LTQxOTktODM3MC0xMTQ1ZWJiMjAyZjAiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImpvZyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJiNjFiYjIwNy1iNThlLTRjYjItYWMzNC00ZWE5ZmMwODE2NDAiLCJncm91cElkIjoiYmUwOWM3ZTctNzBkZS00MmRlLTk5NzAtYTY3M2VmNzJhZTc0 + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEyfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3Vj + aCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxNDI0ZTQ0OC1hYzA4LTQwZGMtYWZlMi0wZGY0NjM3MWQx + OTciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1hcmtlciIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZWJkN2FlM2UtM2U1Ni00MmUyLWE3OWQtOGU3MTc4Yzc5ZWQyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6NzR9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWFkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJlNDVkOTE1ZS05 + ZmZiLTQ3NzMtOTNhNS04ODllZmE3NDgxZWIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6IndyaXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI2ZDFlNzgyZS0wOGQwLTQ0YjMtYjUwYi0zYWY4Nzc5MTlhNTYiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6IjMwOWY3ODBiLTZkNWEtNDc5OC04ZWY0LTRhZDAzMzdiZTBkNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ5 + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjZmNTg1MjEwLTlmMTYtNDY5 + MS1hMzRjLWFmZTljMjA0MjRlMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiYmFuay1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI1ZDFlNDVlMy00ZDNiLTQyMmUtYTQ3OS03NzdhMmZlNDMzZmIiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6IjEzNmQ5OGY3LWYwYzYtNGYxOS1iMTUzLTAxYjdmY2NjZDRjYSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkx + fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmV3aW5kIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIwYWYyMTgwYi1jOTc1LTQyN2Mt + OGI4NS1jNjFkMmEyNmYyZDgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Mn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImZhc3QtZndkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmYTMwNzU3Ny0zMmE0LTQ5YzYtYTRhZS1kMjZjMDEwZWNiZGIiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InBsYXkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0s + eyJpZCI6ImM0ZTNlYjlmLWZiZGYtNDFjMi1iMjcyLWRkNGRmYzQ1OTBkZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic3RvcCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiOWJhMWE5ZDQtOTNjZC00OGFkLWJiOGEtN2VkNTE2 + MmVmY2M0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNvcmQiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjcxOTUwY2I3LWZkZTYtNDY0ZS05N2RlLWFjNWMyZGVkNzE4YyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjg2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjJjMGUz + ZjgzLTA2ZDItNDU2ZC1iOTEzLWI5ZDE0Zjc0YzYzYyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMH0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6Inpvb20iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjY1ZjczNDIyLTg0MjMtNDRkZS04NGVjLWM2NGNiZDE2NTgyOCIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNjcnViIiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX19LHsiaWQiOiI4OGNjMzljMi1lZWIzLTRmODUtYmQyYy0yMzk3ODdkOTk1ZGYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo5OH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmMTRjMjBhZC02 + MTY5LTQ4YmQtYjU2Yy1iMDcyMWY0ZmY1MGMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImN1cnNvci1yaWdodCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiOThmMjc4YzUtNmRhOS00YjRiLWExYzktMjE5MjcxNmViMjhiIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTZ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItdXAiLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjFhMzFiZmFjLTJmZjQtNGM0Mi1iNDFmLTA0MzJmZjAzNGYzNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjk3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWRvd24iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjRhZDI3 + NTQwLTc4YzUtNGM3YS1iMjRhLTJjYzI2MTg5NDQxNSIsImdyb3VwSWQiOiIwOGU0YzNmYi0wMzc0LTRhYTQtOTkxYS03ZWMzZGM4ODY5YjAiLCJzb3VyY2UiOnsidHlw + ZSI6MywiY2hhbm5lbCI6MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiOThiYTIwMTItNTA2ZC00MDUxLWI4YmEtZDEyYzIx + ZjhmZTFlIiwiZ3JvdXBJZCI6IjA4ZTRjM2ZiLTAzNzQtNGFhNC05OTFhLTdlYzNkYzg4NjliMCIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoxfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gy + L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI3YmMzYTg3MS0yYTcwLTQ2NDctYWI1ZC01Njg3YjNiZDQ5MDkiLCJncm91cElkIjoiMDhlNGMz + ZmItMDM3NC00YWE0LTk5MWEtN2VjM2RjODg2OWIwIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIiLCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlfX0seyJpZCI6ImZhNTg1ZDk3LTk4YjMtNGEyNy1hODBjLTRjOTgwYTBlMjVjMyIsImdyb3VwSWQiOiIwOGU0YzNmYi0wMzc0LTRhYTQtOTkxYS03ZWMzZGM4 + ODY5YjAiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6M30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiYzBmYWFiZDAt + MWRhOC00OTA2LTkzNjEtYmY5MWQ3NmQ5MzkxIiwiZ3JvdXBJZCI6IjA4ZTRjM2ZiLTAzNzQtNGFhNC05OTFhLTdlYzNkYzg4NjliMCIsInNvdXJjZSI6eyJ0eXBlIjoz + LCJjaGFubmVsIjo0fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI1OTcxNjI3NS1lMmI1LTQ2ZjItYmEyNi05YTNlM2VlMWQw + MmYiLCJncm91cElkIjoiMDhlNGMzZmItMDM3NC00YWE0LTk5MWEtN2VjM2RjODg2OWIwIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjV9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFk + ZXIiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImVjMmFiMzI0LWYwNTctNGQ0ZC05OTAxLWMzM2IwNzJiN2FiMyIsImdyb3VwSWQiOiIwOGU0YzNmYi0w + Mzc0LTRhYTQtOTkxYS03ZWMzZGM4ODY5YjAiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Nn0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlciIsInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2V9fSx7ImlkIjoiZGY5YzEzOGMtOTFmOS00Y2JiLWI3OTQtZThlN2E5MDkwYzRjIiwiZ3JvdXBJZCI6IjA4ZTRjM2ZiLTAzNzQtNGFhNC05OTFhLTdlYzNkYzg4Njli + MCIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJkZDFjY2MzNy1iM2Yw + LTRiMmQtOTQ2OS03ZjQwN2IyYTI1NmQiLCJncm91cElkIjoiYzJkYTJiZDItYTQzZS00MjdhLThkZjAtYTk2NjRlOTRkNTc2Iiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6MTYsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiNWRlZDNkMTAtMTRmZS00ZDAwLWJmNGUtMjk5OWY0OGQ0MTFkIiwiZ3JvdXBJZCI6IjRhZDRhMjUyLTU0MWItNGQxOS05OTY1LTIz + ZjE5ZDgxZTZiNyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQy + NTE5Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiMDMzNjM2MDctMDcyYi00Y2M5LWI5MmUtOGNmNWNhNjExNTAzIiwiZ3JvdXBJZCI6ImMyZGEyYmQyLWE0M2UtNDI3YS04ZGYwLWE5NjY0 + ZTk0ZDU3NiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90IiwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImQ0OGJlNjI0LWJkNTctNGEzNS04MDI1LWE3ZTQxMjY4ZjYzMSIsImdyb3Vw + SWQiOiI0YWQ0YTI1Mi01NDFiLTRkMTktOTk2NS0yM2YxOWQ4MWU2YjciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OSwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJtYXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNCwiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjQyMWVjYzc1LWNiODItNDYyMS1hMWUwLWJmNTI1YjNhYTdhMCIsImdyb3VwSWQi + OiJjMmRhMmJkMi1hNDNlLTQyN2EtOGRmMC1hOTY2NGU5NGQ1NzYiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOCwiY2hhcmFjdGVyIjoyLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMy92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI2YWUzMzM5Zi01NTBl + LTRiZTItYTlhMy0xNDhhMDU0ODQ3NzMiLCJncm91cElkIjoiNGFkNGEyNTItNTQxYi00ZDE5LTk5NjUtMjNmMTlkODFlNmI3Iiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NTAsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4yNTk4NDI1MTk2ODUwMzkzNSwibWF4U291cmNlVmFsdWUiOjAuMzM4 + NTgyNjc3MTY1MzU0MzQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDMvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJmZTBmY2QzYy00YTkxLTRk + YjctYWI3Zi1kZjcyZDdlNWY2MDAiLCJncm91cElkIjoiYzJkYTJiZDItYTQzZS00MjdhLThkZjAtYTk2NjRlOTRkNTc2Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTksImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiYjc5NWI1OGQtMGM5Zi00MmQyLWFkZmYtOTNlNGRjZjkwMGFhIiwiZ3JvdXBJZCI6IjRhZDRhMjUyLTU0MWItNGQxOS05OTY1LTIzZjE5 + ZDgxZTZiNyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5 + Njg1MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiZGRlY2EzMGUtOWJhYy00MmJmLWEwN2EtNzVjMWI0OTQ5YzViIiwiZ3JvdXBJZCI6ImMyZGEyYmQyLWE0M2UtNDI3YS04ZGYwLWE5NjY0ZTk0 + ZDU3NiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIwLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpu + dWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90IiwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjdiYjgzMzgzLTAzMmEtNDg2Yi04ZWI3LTU3MmI4MGY5YWViYiIsImdyb3VwSWQi + OiI0YWQ0YTI1Mi01NDFiLTRkMTktOTk2NS0yM2YxOWQ4MWU2YjciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJtYXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNCwiZmVlZGJhY2tDb2xvciI6bnVs + bH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjE4MzNjNWI2LTM5ZDEtNGVlMS1hYjNjLTc1ZmVmMTQ2ZjM0ZCIsImdyb3VwSWQiOiJj + MmRhMmJkMi1hNDNlLTQyN2EtOGRmMC1hOTY2NGU5NGQ1NzYiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjoyLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoNi92LXBvdCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJmMTFlN2E0Yi0yZmU0LTRh + NjItOTUyYS1iY2Y2MGZjM2Y4N2IiLCJncm91cElkIjoiNGFkNGEyNTItNTQxYi00ZDE5LTk5NjUtMjNmMTlkODFlNmI3Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6NTMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4yNTk4NDI1MTk2ODUwMzkzNSwibWF4U291cmNlVmFsdWUiOjAuMzM4NTgy + Njc3MTY1MzU0MzQsImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDYvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5Njg1ZDVhZi1lYzUxLTQ2ZGYt + OTM1My1lNzMyY2ViMTkyZTgiLCJncm91cElkIjoiYzJkYTJiZDItYTQzZS00MjdhLThkZjAtYTk2NjRlOTRkNTc2Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MjIsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiNzYzNDUyMGUtN2FmNS00NTNlLWIwNTItMmFhYjQyNmVlOTYxIiwiZ3JvdXBJZCI6IjRhZDRhMjUyLTU0MWItNGQxOS05OTY1LTIzZjE5ZDgx + ZTZiNyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjU0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMjU5ODQyNTE5Njg1 + MDM5MzUsIm1heFNvdXJjZVZhbHVlIjowLjMzODU4MjY3NzE2NTM1NDM0LCJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiM2Y4OTk0NTgtOWYwYS00YjVjLWIxZGUtNmRmZjQxMDk3MzAzIiwiZ3JvdXBJZCI6ImMyZGEyYmQyLWE0M2UtNDI3YS04ZGYwLWE5NjY0ZTk0ZDU3 + NiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxs + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90IiwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjgxOWFjNjc5LWUyZDItNGZlZS04NmYyLTU4OWQ2NDAzZmYyYiIsImdyb3VwSWQiOiI0 + YWQ0YTI1Mi01NDFiLTRkMTktOTk2NS0yM2YxOWQ4MWU2YjciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1NSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + Im1pblNvdXJjZVZhbHVlIjowLjI1OTg0MjUxOTY4NTAzOTM1LCJtYXhTb3VyY2VWYWx1ZSI6MC4zMzg1ODI2NzcxNjUzNTQzNCwiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdCIsInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2V9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImI2NDVmMWJjLWQ5OWQtNDRjMC04YmM5LTFiOWVmMTdmYjQ0YyIsImdyb3VwSWQiOiJmNmZj + MzNiZi02MjJlLTRjOTItYThiMi04ZmRlMmJhMTI5ZmYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNn0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI2MjJkZmY0My00ZTUxLTQ3NDUtOWVlZi0xMTNkOTUzZDUzYjYi + LCJncm91cElkIjoiZjZmYzMzYmYtNjIyZS00YzkyLWE4YjItOGZkZTJiYTEyOWZmIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTd9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiODZhN2YzNzktZmRkYi00ZDE0LWI0 + OTItMzdjN2I3N2ZlZWYzIiwiZ3JvdXBJZCI6ImY2ZmMzM2JmLTYyMmUtNGM5Mi1hOGIyLThmZGUyYmExMjlmZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjE4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjljMmYx + MjJkLTZlMjMtNGUwMi05ZTZmLTU1ODBjYWNhOWFlMCIsImdyb3VwSWQiOiJmNmZjMzNiZi02MjJlLTRjOTItYThiMi04ZmRlMmJhMTI5ZmYiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZX19LHsiaWQiOiIxN2VlMWI3ZC05NmM3LTQ0N2MtYWEyZC0wMzhhMTJkYWQ0MTIiLCJncm91cElkIjoiZjZmYzMzYmYtNjIyZS00YzkyLWE4YjItOGZkZTJiYTEyOWZm + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSIsInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiOTY4ZjU2MjgtZmYyOC00ZDYzLWJhZGQtMmFhZGY1MTQxYmMzIiwiZ3JvdXBJZCI6ImY2ZmMzM2JmLTYyMmUtNGM5Mi1h + OGIyLThmZGUyYmExMjlmZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIxfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g2L211dGUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjFkZWI2NmEyLWE0M2YtNGE2ZC1hYjNjLTdmN2NlNWU2YjY3OCIsImdyb3VwSWQiOiJmNmZj + MzNiZi02MjJlLTRjOTItYThiMi04ZmRlMmJhMTI5ZmYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMn0sIm1vZGUiOnsiZmVlZGJhY2tD + b2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoNy9tdXRlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJmYjU5YTQ2MS00MmUwLTRmNjItYTJhZS1iNjdlMTE0Njg3NTci + LCJncm91cElkIjoiZjZmYzMzYmYtNjIyZS00YzkyLWE4YjItOGZkZTJiYTEyOWZmIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjN9LCJt + b2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbXV0ZSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZWNmMjcwZGItN2Y0MS00NWJjLWFh + YjgtNjY3YWY0NTA3ODk4IiwiZ3JvdXBJZCI6IjYwZGUyOGNkLTNmMGYtNDQwYi05OGFiLTYwYTRlMGZiYWE2MSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiZjc5OWJi + YjAtN2ZhZC00YzgzLTk5MmQtMTI2MjA2ZTBjMjA0IiwiZ3JvdXBJZCI6IjYwZGUyOGNkLTNmMGYtNDQwYi05OGFiLTYwYTRlMGZiYWE2MSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiNzNmMDEwNWQtMGI3Yi00ZmI3LWI1NTItNzAyYTZkYWM1ZDFkIiwiZ3JvdXBJZCI6IjYwZGUyOGNkLTNmMGYtNDQwYi05OGFiLTYwYTRlMGZiYWE2MSIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NvbG8iLCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjM1NTQ4YTZhLWFmNDgtNGE2My05ZGM5LTE5ZTRiMTBmMjhlNiIsImdyb3VwSWQiOiI2MGRlMjhjZC0zZjBmLTQ0MGItOThh + Yi02MGE0ZTBmYmFhNjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNo + NC9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIzYWQzNDhiNy03MjUzLTQ3YjUtYWEwMS1iYWFmM2RmMTY5MWYiLCJncm91cElkIjoiNjBkZTI4 + Y2QtM2YwZi00NDBiLTk4YWItNjBhNGUwZmJhYTYxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29s + b3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDUvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiMjZmNDNiNTQtOWYwMy00YWRiLTgxMDktZWMwY2Y4YjM2ZjUxIiwi + Z3JvdXBJZCI6IjYwZGUyOGNkLTNmMGYtNDQwYi05OGFiLTYwYTRlMGZiYWE2MSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEzfSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImJkMWJkZWU1LWIxOWUtNDIyZC05ZjM2 + LWI3M2ZjYjAwZmI5YyIsImdyb3VwSWQiOiI2MGRlMjhjZC0zZjBmLTQ0MGItOThhYi02MGE0ZTBmYmFhNjEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxNH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zb2xvIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJjNTVhNjYw + NC02N2M5LTQwNjktODA1Yy1lNDkxZDI5MmFiNmYiLCJncm91cElkIjoiNjBkZTI4Y2QtM2YwZi00NDBiLTk4YWItNjBhNGUwZmJhYTYxIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTV9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9 + fSx7ImlkIjoiNDkwYzM2YTgtMjllYy00M2YxLThiZGUtZDkzNzA1YzZmNjg4IiwiZ3JvdXBJZCI6ImY2MTA5MjNiLTk3NWEtNDAxMi05YzM2LTgzODNkZWJmMzEzYSIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjB9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvcmVjb3JkLXJlYWR5Iiwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI1NTVjMzc5Zi1kZTc5LTQyMDktOWFhZS03MTU4NTRmMjNmYzciLCJncm91cElkIjoiZjYxMDkyM2ItOTc1YS00 + MDEyLTljMzYtODM4M2RlYmYzMTNhIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMi9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjYxMDNkOTZmLTllYmMtNDAxNy04MDc3LTkwOWRhNDljOGU0ZSIsImdy + b3VwSWQiOiJmNjEwOTIzYi05NzVhLTQwMTItOWMzNi04MzgzZGViZjMxM2EiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyfSwibW9kZSI6 + eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiODBiYjUxYzUtNmU1ZS00MGFi + LTk2ODctMzI0MGQxMjFhMzNkIiwiZ3JvdXBJZCI6ImY2MTA5MjNiLTk3NWEtNDAxMi05YzM2LTgzODNkZWJmMzEzYSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjN9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsi + aWQiOiIyMGExODUwMC0yMzU0LTQ1NmItYjJjNi05YjhkYjhjZGQxZmYiLCJncm91cElkIjoiZjYxMDkyM2ItOTc1YS00MDEyLTljMzYtODM4M2RlYmYzMTNhIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjljMGM2ZjRlLTY1NTUtNGNmOS1iYWYxLWM5MTMyNDRlMGYwMyIsImdyb3VwSWQiOiJmNjEwOTIzYi05NzVhLTQwMTIt + OWMzNi04MzgzZGViZjMxM2EiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g2L3JlY29yZC1yZWFkeSIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiYjQwMDI2NzEtMmRiYi00YjkzLWI2YWUtMjY3ODM2YzBiY2Q5IiwiZ3JvdXBJ + ZCI6ImY2MTA5MjNiLTk3NWEtNDAxMi05YzM2LTgzODNkZWJmMzEzYSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjZ9LCJtb2RlIjp7ImZl + ZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvcmVjb3JkLXJlYWR5IiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJhNTIyMzZkNi04N2VkLTQ3MmItOTJi + Zi01ZDhkOGMzODdhNGYiLCJncm91cElkIjoiZjYxMDkyM2ItOTc1YS00MDEyLTljMzYtODM4M2RlYmYzMTNhIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6N30sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9yZWNvcmQtcmVhZHkiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6 + IjFlNmIyYTgyLTJjYWUtNDNmNS1hYjgzLTQxMmE1ZDAwZThkMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1fSwibW9kZSI6eyJmZWVk + YmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoibnVkZ2UiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImExM2E5N2UwLTlhOTEtNDA0OC1hMjA4LTYzMjI4Zjg1OTlh + NCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZHJvcCIsInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2V9fSx7ImlkIjoiODY0YTNhNTctYTNjYS00ZDRiLTgwZDItZDIwYzdmOGY0MjVkIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6ODh9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXBsYWNlIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiIzYzc5ZTM2YS02 + YTBiLTQ4MWMtOWZiZS04ZWUyYmE3MDgyMWEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4OX0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6 + bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNsaWNrIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiI0YjllZTU5ZS0yOTY2LTQ5MTQtYjYzMi1mMmIwYTI3MmY2YjQiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MH0sIm1vZGUiOnsiZmVlZGJhY2tDb2xvciI6bnVsbH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNvbG8iLCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlfX0seyJpZCI6IjkwZmRkMDI5LTZkMTYtNDBhYi05MThkLTc2ZjllMDg2ZjdlNyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU0fSwi + bW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjI2MzU1ODQ2LTg4YzgtNDMyYy05NmQ1LWQx + MjEyN2E1M2I1ZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU1fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjIzZGU0YWEyLTMyYzItNDExYS05YWY5LWIwMzQyMzc5NmFmZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjU2fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjMiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjlkY2MzZjA3 + LWY5NDctNDAzNi1hYmIyLTBiMDZkNjM5YWRlYSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU3fSwibW9kZSI6eyJmZWVkYmFja0NvbG9y + IjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiZjQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjllNmZjMGM0LTQ0YmEtNDg2Zi1iNzc5LTEzYTY0NzFkNTQ0YiIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU4fSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + fX0seyJpZCI6IjczOGEwZDU4LTJhODgtNGZiYS05NzYwLTQ2ZWMxMzA5OWY1YiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU5fSwibW9k + ZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjYiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6ImM3YjU4YTIzLWE4YjEtNDVkMC1iYjM0LTc4NGI3 + ZTlmYWM2OSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzfSwibW9kZSI6eyJmZWVkYmFja0NvbG9yIjpudWxsfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVh + dHMiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX0seyJpZCI6IjU0ZGFmMWM1LTUwYjEtNGFmMC05ZTA1LTJhMDBiM2YwYjc5NiIsImdyb3VwSWQiOiI1OTcwN2ZjYS0w + NzAyLTQ0MTktYTJmNy0wZGFjN2U3YWY4OGIiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHQiOiJudW0gPSB5ICogOTk7XG5kMSA9IG51bSAlIDEwO1xuZDIg + PSAobnVtIC8gMTApICUgMTA7XG5tc2dfc2l6ZSA9IDU7XG4wW10gPSAweGIwOyBcbjFbXSA9IDB4NGI7IFxuMltdID0gMHgzMCArIGQyOyBcbjNbXSA9IDB4NGE7IFxu + NFtdID0gMHgzMCArIGQxOyJ9LCJtb2RlIjp7ImZlZWRiYWNrQ29sb3IiOm51bGx9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdubWVudCIsInBvbGxGb3JGZWVkYmFjayI6ZmFsc2V9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1hY2tpZS1j + b250cm9sIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {1D91D40C-047E-40FD-8F6C-3DE04E723A20} + WAK 0 0 + > + + > + + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-62-color-sync.rpp b/plugin-reaper-realearn/resources/test-projects/issue-62-color-sync.rpp new file mode 100644 index 0000000..eda9851 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-62-color-sync.rpp @@ -0,0 +1,256 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATQIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMyIsImlkIjoiMmFmdUNVR3kiLCJjb250cm9sRGV2aWNlSWQiOiIyYWUzY2ExYy1jN2QzLTQyNTYtOTRiMy1lNjY0ZmRmNjBl + NGMiLCJmZWVkYmFja0RldmljZUlkIjoiMmFlM2NhMWMtYzdkMy00MjU2LTk0YjMtZTY2NGZkZjYwZTRjIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6InhPT29pNzNRdnVmN0ZuUkc3YkRtbCIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL3Rlc3QiLCJvc2NBcmdJbmRleCI6MCwib3NjQXJnVHlwZSI6ImNvbG9y + In0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImZlZWRiYWNrQ29sb3IiOnsicHJvcCI6InRhcmdldC50cmFjay5jb2xvciJ9fSwidGFyZ2V0Ijp7InR5cGUiOjQ0 + LCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0Fy + Z0luZGV4IjowfX1dfQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {68D0492F-27E3-4666-BC6F-906FC6AD33B6} + WAK 0 0 + > + > + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-62-text-sync.rpp b/plugin-reaper-realearn/resources/test-projects/issue-62-text-sync.rpp new file mode 100644 index 0000000..6f763ad --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-62-text-sync.rpp @@ -0,0 +1,256 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAARQIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMS4wLXByZS4xMyIsImlkIjoiMmFmdUNVR3kiLCJjb250cm9sRGV2aWNlSWQiOiIyYWUzY2ExYy1jN2QzLTQyNTYtOTRiMy1lNjY0ZmRmNjBl + NGMiLCJmZWVkYmFja0RldmljZUlkIjoiMmFlM2NhMWMtYzdkMy00MjU2LTk0YjMtZTY2NGZkZjYwZTRjIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6InhPT29pNzNRdnVmN0ZuUkc3YkRtbCIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL3Rlc3QiLCJvc2NBcmdJbmRleCI6MCwib3NjQXJnVHlwZSI6InN0cmlu + ZyJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJmZWVkYmFja0NvbG9yIjpudWxsLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tH + VUlEIjoic2VsZWN0ZWQiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MH19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {68D0492F-27E3-4666-BC6F-906FC6AD33B6} + WAK 0 0 + > + > + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-621-offline-rendering-errors.RPP b/plugin-reaper-realearn/resources/test-projects/issue-621-offline-rendering-errors.RPP new file mode 100644 index 0000000..9e760d5 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-621-offline-rendering-errors.RPP @@ -0,0 +1,2241 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3455 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.93250403337995 + ZOOM 102.30518752204635 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN issue-621-offline-rendering-errors + RENDER_FMT 0 2 44100 + RENDER_1X 1 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 0 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 6.83516130000666 0 + SELECTION2 6.83516130000666 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.4989857770359 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAuAwAAAQAAAAAAEIA= + eyJ2ZXJzaW9uIjoiMi4xMy4wIiwiaWQiOiIzeUZXMGt5XyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29u + dHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiV3RuSGhlSDUta1JDZnlUVC1CYU9wIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IkljWjVkR3NTQnk4MG1uVS1ma3ZsNSIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNhdGVnb3J5 + IjoicmVhcGVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJyZWFwZXJTb3VyY2VUeXBlIjoicmVhbGVhcm4tcGFy + YW1ldGVyIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJh + Y2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgi + OjB9fV0sInBhcmFtZXRlcnMiOnsiMCI6eyJ2YWx1ZSI6MC41OTIxMDY2NH19fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 585 0 752 883 + FXID {4720282A-75E1-2344-8BEF-9BBB4F84503A} + + WAK 0 0 + > + + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-621-offline-rendering-errors.wav b/plugin-reaper-realearn/resources/test-projects/issue-621-offline-rendering-errors.wav new file mode 100644 index 0000000..380faee Binary files /dev/null and b/plugin-reaper-realearn/resources/test-projects/issue-621-offline-rendering-errors.wav differ diff --git a/plugin-reaper-realearn/resources/test-projects/issue-626-learn-realearn-parameter-source.RPP b/plugin-reaper-realearn/resources/test-projects/issue-626-learn-realearn-parameter-source.RPP new file mode 100644 index 0000000..84c945c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-626-learn-realearn-parameter-source.RPP @@ -0,0 +1,141 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABhAgAAAQAAAAAAEIA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJQRTByVUtWSSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJYVUQ4U0ttWGRlTloxamoyaXlfOXMiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3Nj + QXJnSW5kZXgiOjAsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwicGFyYW1ldGVycyI6eyIwIjp7InZhbHVlIjowLjE5Mn19LCJp + bnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 32 140 752 703 + FXID {67363FEC-709B-A643-81A0-CF3E8CD9E403} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-627-beep-on-success.RPP b/plugin-reaper-realearn/resources/test-projects/issue-627-beep-on-success.RPP new file mode 100644 index 0000000..c482c34 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-627-beep-on-success.RPP @@ -0,0 +1,304 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 51 + ZOOM 100 4572 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABcGwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJFRTNJV0pFSyIsImNvbnRyb2xEZXZpY2VJZCI6IjAiLCJmZWVkYmFja0RldmljZUlkIjoiMCIsImRlZmF1bHRH + cm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJKTFlCYUtKek1ZNXI4VnJCYy1VWEEiLCJuYW1lIjoiU29uZ3MiLCJ0YWdzIjpbInNvbmciXX0seyJpZCI6IlZfa3diMTJ1 + bm1YTUV6YnQ2X1pKeSIsIm5hbWUiOiJTb3VuZHMiLCJ0YWdzIjpbInNvdW5kIl19LHsiaWQiOiJMYzNSNHl6YURDVXItLUtsa1l1MmIiLCJuYW1lIjoiTG9hZCBzbmFw + c2hvdHMifSx7ImlkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwibmFtZSI6IlRha2Ugc25hcHNob3RzIn1dLCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiNmJORFRHeFFlcXJCdEFQVmEzUzhnIiwiZ3JvdXBJZCI6IkxjM1I0eXphRENVci0tS2xrWXUyYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjMyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjM1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb25nIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoic29uZ18xIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJGd0c4V1lzNUV3VDNXZ1gyeFFsZ2QiLCJncm91 + cElkIjoiTGMzUjR5emFEQ1VyLS1LbGtZdTJiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsidHlwZSI6MzUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowLCJ0YWdzIjpbInNvbmciXSwibWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiJzb25nXzIifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlVSaTVZdXZlckVoazJWcXJRMmEzWSIsImdyb3VwSWQiOiJMYzNSNHl6YURDVXItLUtsa1l1MmIiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjozNSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsic291bmQiXSwibWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiJzb3VuZF8xIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJsV2lySzRQOWJHb1RJSnh2 + ZTZTclMiLCJncm91cElkIjoiTGMzUjR5emFEQ1VyLS1LbGtZdTJiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjUsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MzUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvdW5kIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoic291bmRfMiJ9LCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiNjd2OHdEVWoyMzJvSjB5STZIMlBqIiwiZ3JvdXBJZCI6IkpMWUJhS0p6TVk1cjhWckJjLVVYQSIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJFOUQxOUU4MS01M0Yy + LTc2NDEtQkZGQS1BRTVBQjU2NEM0NDAiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQVQtM003d0xYUjFQeXRFeGFzRm4zIiwiZ3JvdXBJ + ZCI6IkpMWUJhS0p6TVk1cjhWckJjLVVYQSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1 + LCJ0cmFja0dVSUQiOiIzRUVGMUE4OS0yNUJFLTRGNDYtQUU0NC0wMEIwM0YzQzBGQ0IiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiLWVM + bXRmVTI1dGI3X2Z3SVh2Xy1EIiwiZ3JvdXBJZCI6IlZfa3diMTJ1bm1YTUV6YnQ2X1pKeSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJBRTM2M0Y0NS04NDE0LUYwNDUtQkIyOS00RTQ5ODcwQzA1MzIiLCJmeEFuY2hvciI6ImlkIiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiMmNZUWJKT1I1ZFR3OHpTaEhwSVFvIiwiZ3JvdXBJZCI6IlZfa3diMTJ1bm1YTUV6YnQ2X1pKeSIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJBQTg0NjZFNy1FMTZDLTdCNEQtQUE1My1DNjZD + NTcwRDcwQTUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiYlE3Vm1lTTVqMlNadkllSU5xOVh5IiwibmFtZSI6IlRha2UgbGFzdCBsb2Fk + ZWQgc29uZyBzbmFwc2hvdCIsImdyb3VwSWQiOiI0elB3RjdfbkxQQ3pUemw1aEhlYkgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9z + Y0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvbmciXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6ImVmZWRHU29CazRCTm9z + VVBzSTNGcSIsIm5hbWUiOiJUYWtlIGxhc3QgbG9hZGVkIHNvdW5kIHNuYXBzaG90IiwiZ3JvdXBJZCI6IjR6UHdGN19uTFBDelR6bDVoSGViSCIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4w + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1NSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsic291bmQiXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFz + dExvYWRlZCJ9fX0seyJpZCI6IjVzazJsLWJSc0J4ZENELUM0STF6UiIsImdyb3VwSWQiOiI0elB3RjdfbkxQQ3pUemw1aEhlYkgiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvbmciXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + c29uZ18xIn19fSx7ImlkIjoiT3pqRlBselpRa0g1R1c0Qkl0eVRqIiwiZ3JvdXBJZCI6IjR6UHdGN19uTFBDelR6bDVoSGViSCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1NSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsic29uZyJdLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiJz + b25nXzIifX19LHsiaWQiOiJuVElrMnhCMzRGZXM4dkREOS01b0MiLCJncm91cElkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + bmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb3VuZCJdLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiJz + b3VuZF8xIn19fSx7ImlkIjoidDlvVUdiNXlCTXBET0phalRRTG0tIiwibmFtZSI6IjkiLCJncm91cElkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwic291cmNlIjp7 + ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb3VuZCJdLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiJzb3VuZF8yIn19fV0sIm1haW4iOnsiYWN0aXZlTWFwcGluZ1NuYXBzaG90cyI6eyJzb25nIjoic29uZ18xIiwic291bmQiOiJzb3VuZF8xIn19LCJp + bnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn0sIm1hcHBpbmdTbmFwc2hvdHMiOlt7ImlkIjoic29uZ18yIiwibWFwcGluZ3MiOlt7ImlkIjoiQVQtM003d0xY + UjFQeXRFeGFzRm4zIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQiOiJVbml0IiwidmFsdWUiOjEuMH19LHsiaWQiOiI2N3Y4d0RVajIzMm9KMHlJNkgyUGoiLCJ0YXJnZXRf + dmFsdWUiOnsia2luZCI6IlVuaXQiLCJ2YWx1ZSI6MC4wfX1dfSx7ImlkIjoic291bmRfMiIsIm1hcHBpbmdzIjpbeyJpZCI6IjJjWVFiSk9SNWRUdzh6U2hIcElRbyIs + InRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjoxLjB9fSx7ImlkIjoiLWVMbXRmVTI1dGI3X2Z3SVh2Xy1EIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQi + OiJVbml0IiwidmFsdWUiOjAuMH19XX0seyJpZCI6InNvdW5kXzEiLCJtYXBwaW5ncyI6W3siaWQiOiItZUxtdGZVMjV0YjdfZndJWHZfLUQiLCJ0YXJnZXRfdmFsdWUi + Onsia2luZCI6IlVuaXQiLCJ2YWx1ZSI6MS4wfX0seyJpZCI6IjJjWVFiSk9SNWRUdzh6U2hIcElRbyIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVl + IjowLjB9fV19LHsiaWQiOiJzb25nXzEiLCJtYXBwaW5ncyI6W3siaWQiOiI2N3Y4d0RVajIzMm9KMHlJNkgyUGoiLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6IlVuaXQi + LCJ2YWx1ZSI6MS4wfX0seyJpZCI6IkFULTNNN3dMWFIxUHl0RXhhc0ZuMyIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjowLjB9fV19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 681 100 752 675 + FXID {427FC72C-3C9F-0947-90A8-9F9E24742336} + WAK 0 0 + > + > + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-628-mapping-snapshot-feedback.RPP b/plugin-reaper-realearn/resources/test-projects/issue-628-mapping-snapshot-feedback.RPP new file mode 100644 index 0000000..5b7060c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-628-mapping-snapshot-feedback.RPP @@ -0,0 +1,291 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADFFgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJFRTNJV0pFSyIsImNvbnRyb2xEZXZpY2VJZCI6IjAiLCJmZWVkYmFja0RldmljZUlkIjoiMCIsImRlZmF1bHRH + cm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJKTFlCYUtKek1ZNXI4VnJCYy1VWEEiLCJuYW1lIjoiU29uZ3MiLCJ0YWdzIjpbInNvbmciXX0seyJpZCI6IlZfa3diMTJ1 + bm1YTUV6YnQ2X1pKeSIsIm5hbWUiOiJTb3VuZHMiLCJ0YWdzIjpbInNvdW5kIl19LHsiaWQiOiJMYzNSNHl6YURDVXItLUtsa1l1MmIiLCJuYW1lIjoiTG9hZCBzbmFw + c2hvdHMifSx7ImlkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwibmFtZSI6IlRha2Ugc25hcHNob3RzIn1dLCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiNmJORFRHeFFlcXJCdEFQVmEzUzhnIiwiZ3JvdXBJZCI6IkxjM1I0eXphRENVci0tS2xrWXUyYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjMyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjM1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb25nIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoic29uZ18xIn19fSx7ImlkIjoiRndHOFdZczVFd1QzV2dYMnhRbGdkIiwiZ3JvdXBJZCI6IkxjM1I0eXphRENVci0tS2xrWXUyYiIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMzLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjM1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb25nIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoic29uZ18yIn19fSx7ImlkIjoiVVJpNVl1dmVyRWhrMlZxclEyYTNZIiwiZ3JvdXBJZCI6IkxjM1I0eXphRENVci0tS2xrWXUyYiIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2Rl + Ijp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjM1LCJmeEFuY2hvciI6ImlkIiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb3VuZCJdLCJtYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6InNvdW5kXzEifX19LHsiaWQiOiJsV2lySzRQOWJHb1RJSnh2ZTZTclMiLCJncm91cElkIjoiTGMzUjR5emFEQ1VyLS1LbGtZ + dTJiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRl + eCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MzUsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvdW5kIl0sIm1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoic291bmRfMiJ9fX0seyJpZCI6IjY3djh3RFVqMjMyb0oweUk2SDJQaiIsImdyb3VwSWQiOiJKTFlCYUtK + ek1ZNXI4VnJCYy1VWEEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tHVUlE + IjoiRTlEMTlFODEtNTNGMi03NjQxLUJGRkEtQUU1QUI1NjRDNDQwIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiQVQtM003d0xYUjFQeXRFeGFzRm4zIiwiZ3JvdXBJZCI6IkpMWUJhS0p6TVk1cjhWckJjLVVYQSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiIzRUVGMUE4OS0yNUJFLTRG + NDYtQUU0NC0wMEIwM0YzQzBGQ0IiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MH19LHsiaWQiOiItZUxtdGZVMjV0YjdfZndJWHZfLUQiLCJncm91cElkIjoiVl9rd2IxMnVubVhNRXpidDZfWkp5Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + bmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjUsInRyYWNrR1VJRCI6IkFFMzYzRjQ1LTg0MTQtRjA0NS1CQjI5LTRFNDk4NzBDMDUz + MiIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjJj + WVFiSk9SNWRUdzh6U2hIcElRbyIsImdyb3VwSWQiOiJWX2t3YjEydW5tWE1FemJ0Nl9aSnkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tHVUlEIjoiQUE4NDY2RTctRTE2Qy03QjRELUFBNTMtQzY2QzU3MEQ3MEE1IiwiZnhBbmNob3IiOiJpZCIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjB9fSx7ImlkIjoiNXNrMmwtYlJzQnhkQ0QtQzRJMXpS + IiwibmFtZSI6IjkiLCJncm91cElkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1 + fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MCwidGFncyI6WyJzb25nIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoic29uZ18xIn19fSx7ImlkIjoiT3pqRlBselpRa0g1R1c0 + Qkl0eVRqIiwibmFtZSI6IjkiLCJncm91cElkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MCwidGFncyI6WyJzb25nIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoic29uZ18yIn19fSx7ImlkIjoiblRJazJ4QjM0 + RmVzOHZERDktNW9DIiwibmFtZSI6IjkiLCJncm91cElkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb3VuZCJdLCJtYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6InNvdW5kXzEifX19LHsiaWQiOiJ0 + OW9VR2I1eUJNcERPSmFqVFFMbS0iLCJuYW1lIjoiOSIsImdyb3VwSWQiOiI0elB3RjdfbkxQQ3pUemw1aEhlYkgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvdW5kIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoic291bmRfMiJ9fX1d + LCJtYWluIjp7ImFjdGl2ZU1hcHBpbmdTbmFwc2hvdHMiOnsic29uZyI6InNvbmdfMSIsInNvdW5kIjoic291bmRfMiJ9fSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoi + Rm9jdXNlZCJ9LCJtYXBwaW5nU25hcHNob3RzIjpbeyJpZCI6InNvbmdfMiIsIm1hcHBpbmdzIjpbeyJpZCI6IkFULTNNN3dMWFIxUHl0RXhhc0ZuMyIsInRhcmdldF92 + YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjoxLjB9fSx7ImlkIjoiNjd2OHdEVWoyMzJvSjB5STZIMlBqIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQiOiJVbml0Iiwi + dmFsdWUiOjAuMH19XX0seyJpZCI6InNvdW5kXzEiLCJtYXBwaW5ncyI6W3siaWQiOiItZUxtdGZVMjV0YjdfZndJWHZfLUQiLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6 + IlVuaXQiLCJ2YWx1ZSI6MS4wfX0seyJpZCI6IjJjWVFiSk9SNWRUdzh6U2hIcElRbyIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjowLjB9fV19 + LHsiaWQiOiJzb25nXzEiLCJtYXBwaW5ncyI6W3siaWQiOiI2N3Y4d0RVajIzMm9KMHlJNkgyUGoiLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6IlVuaXQiLCJ2YWx1ZSI6 + MS4wfX0seyJpZCI6IkFULTNNN3dMWFIxUHl0RXhhc0ZuMyIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjowLjB9fV19LHsiaWQiOiJzb3VuZF8y + IiwibWFwcGluZ3MiOlt7ImlkIjoiLWVMbXRmVTI1dGI3X2Z3SVh2Xy1EIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQiOiJVbml0IiwidmFsdWUiOjAuMH19LHsiaWQiOiIy + Y1lRYkpPUjVkVHc4elNoSHBJUW8iLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6IlVuaXQiLCJ2YWx1ZSI6MS4wfX1dfV19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 681 100 752 675 + FXID {427FC72C-3C9F-0947-90A8-9F9E24742336} + WAK 0 0 + > + > + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-635-rel-time-control-and-stop.RPP b/plugin-reaper-realearn/resources/test-projects/issue-635-rel-time-control-and-stop.RPP new file mode 100644 index 0000000..05555d4 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-635-rel-time-control-and-stop.RPP @@ -0,0 +1,143 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 5.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.27991910429543 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAC8AgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJOUFp3TmYzbCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiUGs0OXlJdVNoRTkyckxXcFZoY1A1IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX19LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiJ5ID0gcmVsX3RpbWUgPCAyMDAwID8gbm9u + ZSA6IHN0b3AoMC41KSJ9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFu + Y2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 231 100 752 675 + FXID {84984E1E-9DA2-9F46-B857-AB3A672557D8} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-640-take-last-loaded-mapping-snapshot.RPP b/plugin-reaper-realearn/resources/test-projects/issue-640-take-last-loaded-mapping-snapshot.RPP new file mode 100644 index 0000000..f917f47 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-640-take-last-loaded-mapping-snapshot.RPP @@ -0,0 +1,304 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 51 + ZOOM 100 4572 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABcGwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJFRTNJV0pFSyIsImNvbnRyb2xEZXZpY2VJZCI6IjAiLCJmZWVkYmFja0RldmljZUlkIjoiMCIsImRlZmF1bHRH + cm91cCI6e30sImdyb3VwcyI6W3siaWQiOiJKTFlCYUtKek1ZNXI4VnJCYy1VWEEiLCJuYW1lIjoiU29uZ3MiLCJ0YWdzIjpbInNvbmciXX0seyJpZCI6IlZfa3diMTJ1 + bm1YTUV6YnQ2X1pKeSIsIm5hbWUiOiJTb3VuZHMiLCJ0YWdzIjpbInNvdW5kIl19LHsiaWQiOiJMYzNSNHl6YURDVXItLUtsa1l1MmIiLCJuYW1lIjoiTG9hZCBzbmFw + c2hvdHMifSx7ImlkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwibmFtZSI6IlRha2Ugc25hcHNob3RzIn1dLCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiNmJORFRHeFFlcXJCdEFQVmEzUzhnIiwiZ3JvdXBJZCI6IkxjM1I0eXphRENVci0tS2xrWXUyYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjMyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjM1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb25nIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoic29uZ18xIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJGd0c4V1lzNUV3VDNXZ1gyeFFsZ2QiLCJncm91 + cElkIjoiTGMzUjR5emFEQ1VyLS1LbGtZdTJiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsidHlwZSI6MzUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowLCJ0YWdzIjpbInNvbmciXSwibWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiJzb25nXzIifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlVSaTVZdXZlckVoazJWcXJRMmEzWSIsImdyb3VwSWQiOiJMYzNSNHl6YURDVXItLUtsa1l1MmIiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjozNSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsic291bmQiXSwibWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiJzb3VuZF8xIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJsV2lySzRQOWJHb1RJSnh2 + ZTZTclMiLCJncm91cElkIjoiTGMzUjR5emFEQ1VyLS1LbGtZdTJiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjUsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MzUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvdW5kIl0sIm1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoic291bmRfMiJ9LCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiNjd2OHdEVWoyMzJvSjB5STZIMlBqIiwiZ3JvdXBJZCI6IkpMWUJhS0p6TVk1cjhWckJjLVVYQSIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJFOUQxOUU4MS01M0Yy + LTc2NDEtQkZGQS1BRTVBQjU2NEM0NDAiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQVQtM003d0xYUjFQeXRFeGFzRm4zIiwiZ3JvdXBJ + ZCI6IkpMWUJhS0p6TVk1cjhWckJjLVVYQSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1 + LCJ0cmFja0dVSUQiOiIzRUVGMUE4OS0yNUJFLTRGNDYtQUU0NC0wMEIwM0YzQzBGQ0IiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiLWVM + bXRmVTI1dGI3X2Z3SVh2Xy1EIiwiZ3JvdXBJZCI6IlZfa3diMTJ1bm1YTUV6YnQ2X1pKeSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJBRTM2M0Y0NS04NDE0LUYwNDUtQkIyOS00RTQ5ODcwQzA1MzIiLCJmeEFuY2hvciI6ImlkIiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiMmNZUWJKT1I1ZFR3OHpTaEhwSVFvIiwiZ3JvdXBJZCI6IlZfa3diMTJ1bm1YTUV6YnQ2X1pKeSIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1LCJ0cmFja0dVSUQiOiJBQTg0NjZFNy1FMTZDLTdCNEQtQUE1My1DNjZD + NTcwRDcwQTUiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiYlE3Vm1lTTVqMlNadkllSU5xOVh5IiwibmFtZSI6IlRha2UgbGFzdCBsb2Fk + ZWQgc29uZyBzbmFwc2hvdCIsImdyb3VwSWQiOiI0elB3RjdfbkxQQ3pUemw1aEhlYkgiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9z + Y0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvbmciXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6ImVmZWRHU29CazRCTm9z + VVBzSTNGcSIsIm5hbWUiOiJUYWtlIGxhc3QgbG9hZGVkIHNvdW5kIHNuYXBzaG90IiwiZ3JvdXBJZCI6IjR6UHdGN19uTFBDelR6bDVoSGViSCIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4w + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1NSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsic291bmQiXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFz + dExvYWRlZCJ9fX0seyJpZCI6IjVzazJsLWJSc0J4ZENELUM0STF6UiIsImdyb3VwSWQiOiI0elB3RjdfbkxQQ3pUemw1aEhlYkgiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTUsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWdzIjpbInNvbmciXSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + c29uZ18xIn19fSx7ImlkIjoiT3pqRlBselpRa0g1R1c0Qkl0eVRqIiwiZ3JvdXBJZCI6IjR6UHdGN19uTFBDelR6bDVoSGViSCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1NSwiZnhBbmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRhZ3MiOlsic29uZyJdLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiJz + b25nXzIifX19LHsiaWQiOiJuVElrMnhCMzRGZXM4dkREOS01b0MiLCJncm91cElkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwic291cmNlIjp7ImNhdGVnb3J5Ijoi + bmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb3VuZCJdLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiJz + b3VuZF8xIn19fSx7ImlkIjoidDlvVUdiNXlCTXBET0phalRRTG0tIiwibmFtZSI6IjkiLCJncm91cElkIjoiNHpQd0Y3X25MUEN6VHpsNWhIZWJIIiwic291cmNlIjp7 + ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjU1LCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFncyI6WyJzb3VuZCJdLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiJzb3VuZF8yIn19fV0sIm1haW4iOnsiYWN0aXZlTWFwcGluZ1NuYXBzaG90cyI6eyJzb3VuZCI6InNvdW5kXzEiLCJzb25nIjoic29uZ18xIn19LCJp + bnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn0sIm1hcHBpbmdTbmFwc2hvdHMiOlt7ImlkIjoic291bmRfMSIsIm1hcHBpbmdzIjpbeyJpZCI6IjJjWVFiSk9S + NWRUdzh6U2hIcElRbyIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjowLjB9fSx7ImlkIjoiLWVMbXRmVTI1dGI3X2Z3SVh2Xy1EIiwidGFyZ2V0 + X3ZhbHVlIjp7ImtpbmQiOiJVbml0IiwidmFsdWUiOjEuMH19XX0seyJpZCI6InNvbmdfMiIsIm1hcHBpbmdzIjpbeyJpZCI6IkFULTNNN3dMWFIxUHl0RXhhc0ZuMyIs + InRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjoxLjB9fSx7ImlkIjoiNjd2OHdEVWoyMzJvSjB5STZIMlBqIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQi + OiJVbml0IiwidmFsdWUiOjAuMH19XX0seyJpZCI6InNvbmdfMSIsIm1hcHBpbmdzIjpbeyJpZCI6IjY3djh3RFVqMjMyb0oweUk2SDJQaiIsInRhcmdldF92YWx1ZSI6 + eyJraW5kIjoiVW5pdCIsInZhbHVlIjoxLjB9fSx7ImlkIjoiQVQtM003d0xYUjFQeXRFeGFzRm4zIiwidGFyZ2V0X3ZhbHVlIjp7ImtpbmQiOiJVbml0IiwidmFsdWUi + OjAuMH19XX0seyJpZCI6InNvdW5kXzIiLCJtYXBwaW5ncyI6W3siaWQiOiIyY1lRYkpPUjVkVHc4elNoSHBJUW8iLCJ0YXJnZXRfdmFsdWUiOnsia2luZCI6IlVuaXQi + LCJ2YWx1ZSI6MS4wfX0seyJpZCI6Ii1lTG10ZlUyNXRiN19md0lYdl8tRCIsInRhcmdldF92YWx1ZSI6eyJraW5kIjoiVW5pdCIsInZhbHVlIjowLjB9fV19XX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 681 100 752 675 + FXID {427FC72C-3C9F-0947-90A8-9F9E24742336} + WAK 0 0 + > + > + + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-644-relative-control-of-trigger-actions.RPP b/plugin-reaper-realearn/resources/test-projects/issue-644-relative-control-of-trigger-actions.RPP new file mode 100644 index 0000000..ffe7e2a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-644-relative-control-of-trigger-actions.RPP @@ -0,0 +1,143 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 280.00000000154063 + ZOOM 100 27149 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAADAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJyYkJPYnhuQiIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiaFZoczhhcWx5aE9zMDdLODlnNVcxIiwibmFtZSI6IkJhciAtIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7InR5cGUiOjMsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxLCJlbmNvZGVyVXNhZ2UiOiJkZWNyZW1lbnQtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MTA0MyIsImludm9jYXRpb25U + eXBlIjowLCJmeEFuY2hvciI6ImlkIiwicG9sbEZvckZlZWRiYWNrIjpmYWxzZX19LHsiaWQiOiJYYjFUMFJMMC1kTE93a3htZ0RBR0ciLCJuYW1lIjoiQmFyICsiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsidHlwZSI6MywibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVuY29kZXJVc2FnZSI6ImluY3JlbWVudC1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQxMDQy + IiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlfX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2Vk + In19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 387 75 752 675 + FXID {047CAEA0-55A3-7940-9362-312C8B391C03} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-648-track-parent-send.RPP b/plugin-reaper-realearn/resources/test-projects/issue-648-track-parent-send.RPP new file mode 100644 index 0000000..4d9338c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-648-track-parent-send.RPP @@ -0,0 +1,164 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAD+AQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJhLXNSWnZ0diIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiNjNabEJ2TUtOeDBCSUpqck9KYzdlIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTYsInRyYWNrR1VJRCI6IjUxREQyRjY3LUMwRDctOEI0NS1CNTU2LUVCOEI2MURE + QkI2RCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJpbnN0 + YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 613 107 752 675 + FXID {D5437BC1-DB5A-ED4B-ACFA-1AAA868C6026} + WAK 0 0 + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-657-background-project-1.RPP b/plugin-reaper-realearn/resources/test-projects/issue-657-background-project-1.RPP new file mode 100644 index 0000000..450a7ba --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-657-background-project-1.RPP @@ -0,0 +1,147 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABMBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiI0eWtyb1JCaCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJmZWVkYmFja0RldmljZUlkIjoiZngtb3V0cHV0IiwiZGVmYXVsdEdyb3VwIjp7fSwi + ZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImZvSnl3ZVhqM0x4Z1hSVjBtMF94UCIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVn + b3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEs + ImtleSI6NjV9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjowLCJj + b21tYW5kTmFtZSI6IjQwMzY0IiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiNG9PdnlMWFpSVmV5eFBIVjQz + aC1KIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5k + ZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjQwMzY0IiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9h + ZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 402 162 752 675 + FXID {0812A20C-65C9-FD46-802C-45C71EEF6D0A} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-658-vice-versa-control.RPP b/plugin-reaper-realearn/resources/test-projects/issue-658-vice-versa-control.RPP new file mode 100644 index 0000000..1c15dba --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-658-vice-versa-control.RPP @@ -0,0 +1,141 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABCAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiIweU9ydTZhRyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJyZXNldEZlZWRiYWNrV2hlblJlbGVh + c2luZ1NvdXJjZSI6ZmFsc2UsImZlZWRiYWNrRGV2aWNlSWQiOiJmeC1vdXRwdXQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoiSkdzNFR6cnhfNUhaNUNUbVlwUjU2IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQi + fX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 104 86 752 675 + FXID {A41E6AEF-29FD-9C48-8206-6AA35D8DE5E3} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-659-automation-bypass-has-no-effect.RPP b/plugin-reaper-realearn/resources/test-projects/issue-659-automation-bypass-has-no-effect.RPP new file mode 100644 index 0000000..28e8296 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-659-automation-bypass-has-no-effect.RPP @@ -0,0 +1,200 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 2.5 + ZOOM 25.47655226259523 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABGAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4yIiwiaWQiOiJlZWNFRkRsdCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6IkFsd2F5cyIsInNlbmRGZWVk + YmFja09ubHlJZkFybWVkIjp0cnVlLCJmZWVkYmFja0RldmljZUlkIjoiZngtb3V0cHV0IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6 + e30sIm1hcHBpbmdzIjpbeyJpZCI6IlJfaFZ6NnRqeU1UdzliT0hGOVhFeiIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1 + c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 410 3 752 675 + FXID {D4FC8287-77C3-DF43-9555-3E5DDBB230AF} + + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACOAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4yIiwiaWQiOiJtUWhBRUlWZCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6IkFsd2F5cyIsInNlbmRGZWVk + YmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoieDFOSnRDRl9V + eXVaZEtxNVFsb0I5IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHJhY2tHVUlE + IjoiMzM1NTlGRDAtOENGRS04OTRFLUIwRjItMUJGRTcwNUIyMDVBIiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IkQ0RkM4Mjg3LTc3QzMtREY0My05NTU1LTNFNURE + QkIyMzBBRiIsInBhcmFtSW5kZXgiOjIwMCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 287 65 752 675 + FXID {5CC0780B-0D81-6341-BD92-160A1EF2B6E5} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-660-x-touch-color-lcd.RPP b/plugin-reaper-realearn/resources/test-projects/issue-660-x-touch-color-lcd.RPP new file mode 100644 index 0000000..1930377 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-660-x-touch-color-lcd.RPP @@ -0,0 +1,143 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADOAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJFSzMtcTRNYiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMTUiLCJmZWVkYmFja0RldmljZUlkIjoiMTUiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJv + bGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoib0NQYUIwN181WjlhR1N2azNGYTVUIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MTIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJkaXNwbGF5VHlwZSI6IngtdG91Y2gtbWFja2llLWxjZCIsImRpc3BsYXlJZCI6MCwibGluZSI6MCwib3NjQXJnSW5k + ZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9u + IjoiVGVzdCIsImZlZWRiYWNrQ29sb3IiOlsxNywxMjgsMl0sImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RM + b2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 321 -50 752 675 + FXID {8DB42F1B-180D-9846-9021-A6159C099D6C} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-661-advanced-settings-on-activate.RPP b/plugin-reaper-realearn/resources/test-projects/issue-661-advanced-settings-on-activate.RPP new file mode 100644 index 0000000..284d216 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-661-advanced-settings-on-activate.RPP @@ -0,0 +1,153 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 2 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 3 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAB6AgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJobk03dng3biIsImZlZWRiYWNrRGV2aWNlSWQiOiJmeC1vdXRwdXQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiY09XaXZwYUJVZ0JPbFNrTFY5WFE5IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJhZHZhbmNlZCI6eyJv + bl9hY3RpdmF0ZSI6eyJzZW5kX21pZGlfZmVlZGJhY2siOlt7InJhdyI6IkYwIDAxIEY3In1dfSwib25fZGVhY3RpdmF0ZSI6eyJzZW5kX21pZGlfZmVlZGJhY2siOlt7 + InJhdyI6IkYwIDAwIEY3In1dfX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {501D125B-944D-1742-A003-0DB6F31B5107} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAPQAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAABDAAAAL1VzZXJzL2hlbGdvYm9zcy9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvUkVB + UEVSL0RhdGEvR00ucmVhYmFuawAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0A + AAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAAAAAAAAAAAAAAAA== + AAAQAAAA + > + FLOAT 668 87 760 646 + FXID {AA062559-062F-014E-A602-C1855D6BCE2F} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-663-go-to-bookmark-feedback-bug.RPP b/plugin-reaper-realearn/resources/test-projects/issue-663-go-to-bookmark-feedback-bug.RPP new file mode 100644 index 0000000..2a455a7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-663-go-to-bookmark-feedback-bug.RPP @@ -0,0 +1,151 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + MARKER 1 0 "" 0 0 1 B {7B8D49A5-5C72-9A4A-8556-7B6FC6A9516F} + MARKER 2 4 "" 0 0 1 B {BA8CDC9D-7612-7A46-A65E-44CE9E5F816A} + MARKER 3 6.5 "" 0 0 1 B {566386F1-57CF-2949-A208-06A413EC1B5C} + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAcBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiI1by03amc0SyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJmZWVkYmFja0RldmljZUlkIjoiZngt + b3V0cHV0IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IlFHcVBNR3VxMmt5SkxITzE3UXNHZyIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MCwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUi + OjIyLCJmeEFuY2hvciI6ImlkIiwiYm9va21hcmtSZWYiOjEsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjAsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJhRjI1S0ZtNDU1SVFxWEFqbVhEQ0UiLCJuYW1lIjoiMiIsInNv + dXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyMiwiZnhBbmNob3Ii + OiJpZCIsImJvb2ttYXJrUmVmIjoyLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiNk9EZVJwNldLSzZiQ2ZWTlpFWEVmIiwibmFtZSI6IjMiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjIsImZ4QW5jaG9yIjoiaWQiLCJib29rbWFy + a1JlZiI6MywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 321 9 752 675 + FXID {853234D9-33A5-BC42-96BF-EF89B24FA819} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-666-source-parameter-row-label.RPP b/plugin-reaper-realearn/resources/test-projects/issue-666-source-parameter-row-label.RPP new file mode 100644 index 0000000..17393f4 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-666-source-parameter-row-label.RPP @@ -0,0 +1,145 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADCAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xIiwiaWQiOiJFZUJoY0VmRCIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoieE1jN1hVNHZvbE9mM2hhSlhqV3A4IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJyZWFwZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsInJlYXBlclNvdXJjZVR5cGUiOiJyZWFsZWFybi1wYXJh + bWV0ZXIifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhB + bmNob3IiOiJpZCIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJtU2VXaTcwWVF4SkRiMUxDWC1qR3oiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InJlYXBl + ciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwicmVhcGVyU291cmNlVHlwZSI6InRpbWVyIiwidGltZXJNaWxsaXMi + OjUwMDB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjUzLCJmeEFu + Y2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJwYXJhbWV0ZXJzIjp7IjAiOnsibmFtZSI6IlNvbmcifX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQi + fX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 233 115 752 675 + FXID {0D1AC42D-67F6-B346-8CE2-42787887BB0A} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-679-multi-line-eel-editing.RPP b/plugin-reaper-realearn/resources/test-projects/issue-679-multi-line-eel-editing.RPP new file mode 100644 index 0000000..068998e --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-679-multi-line-eel-editing.RPP @@ -0,0 +1,144 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAQAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4yIiwiaWQiOiJMU20xY3FWbSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJvZVl1NHFfR1ZXVWVPZF9DcTVOdXciLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1LCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiJ5ID0geCArIDQ7XG5cbmhhbGxvID0gNSJ9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQi + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImNvbnRyb2xsZXJOb3RlcyI6IkNvbnRyb2xsZXIgY29tcGFydG1lbnQgaGFzIGRpZmZl + cmVudCBub3RlcyEiLCJtYWluTm90ZXMiOiIjIFRlc3RcblxuVGhlc2UgYXJlIHNvbWUgdGVzdCBub3Rlcy5cblxuw7bDpMOfXsKwIiwiaW5zdGFuY2VGeCI6eyJhZGRy + ZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 102 154 752 675 + FXID {7776C38E-FC75-304A-9580-3534023AD1FF} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-680-marker-smooth-seek.RPP b/plugin-reaper-realearn/resources/test-projects/issue-680-marker-smooth-seek.RPP new file mode 100644 index 0000000..5adc50d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-680-marker-smooth-seek.RPP @@ -0,0 +1,159 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 90 + ZOOM 7.613158991167 0 0 + VZOOMEX 4 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 90 98 + SELECTION2 90 98 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + MARKER 1 4 "" 0 0 1 B {DD8E4538-3C38-514F-A929-ECFF37AF8661} + MARKER 2 16 "" 0 0 1 B {89EABF11-32A1-6041-813B-FBCC6ABA2C4B} + MARKER 3 49 "" 0 0 1 B {CB73D486-857F-4940-B148-00101B63A858} + MARKER 1 60 "" 1 0 1 B {E5750614-C898-EB48-AC80-CB542F31FAC9} + MARKER 1 74 "" 1 0 1 B + MARKER 2 90 "" 1 0 1 B {36FF7F8F-460B-7343-A72F-D4D22CD13FB6} + MARKER 2 98 "" 1 0 1 B + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAHBgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4yIiwiaWQiOiJReEpLdGpoUCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiaTBVSG90dmVoRFRiNS1aSm1TcjY0IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX19LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIyLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiYm9va21hcmtSZWYiOjEsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJYXy00bUJ2Q0NTal9CbFc5VGNNcFgiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJz + IjoxLCJrZXkiOjY2fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6 + MjIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJib29rbWFya1JlZiI6MiwiYm9va21hcmtJc1JlZ2lvbiI6dHJ1ZSwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRl + ZCJ9fX0seyJpZCI6IlNISTBPWmFrcU42aWJSY1MwMFd5dSIsIm5hbWUiOiIzIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6Njd9fSwibW9kZSI6eyJtaW5UYXJnZXRWYWx1 + ZSI6MC4zLCJtYXhUYXJnZXRWYWx1ZSI6MC4zLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0 + eXBlIjoyMywiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjAsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9j + dXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 369 17 752 675 + FXID {29138BBD-1DAF-3640-B0E9-B86CE4F0B960} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-681-x-touch-color-mismatch.RPP b/plugin-reaper-realearn/resources/test-projects/issue-681-x-touch-color-mismatch.RPP new file mode 100644 index 0000000..6e1acbf --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-681-x-touch-color-mismatch.RPP @@ -0,0 +1,142 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3455 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 7.28 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAC7AgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4yIiwiaWQiOiJabnFlU0RCNiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJqQTNhMzV3dlEtUERFVFdqejYwbEsiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1LCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoidGVzdCIsImZlZWRiYWNrQ29sb3IiOnsicHJvcCI6InRhcmdldC50cmFjay5jb2xvciJ9LCJmZWVkYmFj + a1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NDQsImZ4QW5jaG9yIjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX1dLCJpbnN0YW5j + ZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 500 101 752 675 + FXID {986E2876-6E0E-5D48-8668-38BB410E8F0E} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-686-mouse-target.RPP b/plugin-reaper-realearn/resources/test-projects/issue-686-mouse-target.RPP new file mode 100644 index 0000000..9828088 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-686-mouse-target.RPP @@ -0,0 +1,1033 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 9 + ZOOM 9.3463878987179 0 0 + VZOOMEX 7 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1.768 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.67745977029598 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + Mouse" 1751282284<5653546862726C7265616C6561726E00> "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABBHQEAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4zIiwiaWQiOiJjckVGLVNyLSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMTUiLCJkZWZhdWx0R3JvdXAiOnt9LCJncm91cHMiOlt7ImlkIjoiczlVTmYwcnVlY25uOExNeE1qdUdk + IiwibmFtZSI6IlRvdWNoLXNlbnNpdGl2ZSBmYWRlciBkcmFncyJ9LHsiaWQiOiJ0Rlc4V085UkNuMFlmbkFNUGt6SVIiLCJuYW1lIjoiSm9nIHdoZWVsICsgc2NydWIg + YnV0dG9uIGRyYWcifSx7ImlkIjoiM1N3Zm1JV1dLSTBEak1ZS0k4dVFUIiwibmFtZSI6IkZhZGVyIHNjcm9sbHMifSx7ImlkIjoiUHFpMVlQM3Jhb012OHAycVZfRkpz + IiwibmFtZSI6IkVuY29kZXIgc2Nyb2xscyJ9LHsiaWQiOiJ1cm50RW42cVktek5GcFhRQWVUbFgiLCJuYW1lIjoiQ3Vyc29yIGJ1dHRvbnMgc2Nyb2xsIn0seyJpZCI6 + IjQ1aTNGOGFmODdMTGRDbVh3NXMzVCIsIm5hbWUiOiJFbmNvZGVyICsgcHVzaCBkcmFncyJ9LHsiaWQiOiJBcFJWaGYxOG9SWk1qVDM0bWhqTWIiLCJuYW1lIjoiRW5j + b2RlciBkcmFncyJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiZmFkZXIiLCJuYW1lIjoiRmFkZXIifSx7Imlk + Ijoidi1wb3QiLCJuYW1lIjoiVi1Qb3QifSx7ImlkIjoidi1zZWxlY3QiLCJuYW1lIjoiVi1TZWxlY3QifSx7ImlkIjoiZmFkZXItdG91Y2giLCJuYW1lIjoiRmFkZXIg + VG91Y2gifSx7ImlkIjoic2VsZWN0IiwibmFtZSI6IlNlbGVjdCJ9LHsiaWQiOiJtdXRlIiwibmFtZSI6Ik11dGUifSx7ImlkIjoic29sbyIsIm5hbWUiOiJTb2xvIn0s + eyJpZCI6InJlY29yZC1yZWFkeSIsIm5hbWUiOiJSZWNvcmQtcmVhZHkifSx7ImlkIjoidi1wb3QtbGVkcyIsIm5hbWUiOiJWLVBvdCBMRURzIn0seyJpZCI6ImxjZCIs + Im5hbWUiOiJMQ0QifSx7ImlkIjoibWV0ZXIiLCJuYW1lIjoiTWV0ZXIifV0sIm1hcHBpbmdzIjpbeyJpZCI6InJzNXpnR3hYMGRHd1lsb3JMT29YMCIsIm5hbWUiOiJG + YWRlciB0b3VjaCA9PiBQcmVzcyBvciByZWxlYXNlIiwiZ3JvdXBJZCI6InM5VU5mMHJ1ZWNubjhMTXhNanVHZCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJv + a2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3Vj + aCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjU3LCJmeEFuY2hv + ciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6IlByZXNzT3JSZWxlYXNlIiwiYnV0dG9uIjoiTGVmdCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0 + TG9hZGVkIn19LCJpc0VuYWJsZWQiOmZhbHNlLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInN1Y2Nlc3NBdWRpb0ZlZWRiYWNrIjp7ImtpbmQiOiJTaW1wbGUifX0s + eyJpZCI6InJnTGVhTW56ZGZFSzd1M1M4dE5PcCIsIm5hbWUiOiJGYWRlciA9PiBNb3ZlIiwiZ3JvdXBJZCI6InM5VU5mMHJ1ZWNubjhMTXhNanVHZCIsInNvdXJjZSI6 + eyJ0eXBlIjozLCJjaGFubmVsIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmll + cnMiOjEsImtleSI6NjV9fSwibW9kZSI6eyJ0eXBlIjozLCJtaW5TdGVwU2l6ZSI6MC4wMDExMTIzNDcwNTIyODAzMTE0LCJtYXhTdGVwU2l6ZSI6MC4wMDExMTIzNDcw + NTIyODAzMTE0LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjU3LCJmeEFu + Y2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVCeSIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0s + ImlzRW5hYmxlZCI6ZmFsc2UsImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ik1SMzA3VzQ0a3M4TWd4UHFKMFJRbCIsIm5hbWUiOiJKb2cgd2hlZWwgPT4g + TW92ZSIsImdyb3VwSWQiOiJ0Rlc4V085UkNuMFlmbkFNUGt6SVIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MywiY2hhbm5lbCI6MCwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImpvZyJ9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjowLjAwMTExMjM0NzA1MjI4MDMxMTQsIm1heFN0ZXBTaXplIjowLjAwMTExMjM0NzA1MjI4MDMx + MTQsIm1pblN0ZXBGYWN0b3IiOjUsIm1heFN0ZXBGYWN0b3IiOjEwMCwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjo1NywiZnhBbmNob3Ii + OiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgi + OjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJpc0Vu + YWJsZWQiOmZhbHNlLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ4clF4TjIwdW9SX1dEX19lWExKQ1giLCJuYW1lIjoiU2NydWIgYnV0dG9uID0+IFBy + ZXNzIG9yIHJlbGVhc2UiLCJncm91cElkIjoidEZXOFdPOVJDbjBZZm5BTVBreklSIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTA0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMi + OjEsImtleSI6NjV9LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic2NydWIifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6 + MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo1NywiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJQcmVzc09yUmVsZWFzZSIsImJ1dHRvbiI6IkxlZnQifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiaXNFbmFibGVkIjpmYWxz + ZSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzdWNjZXNzQXVkaW9GZWVkYmFjayI6eyJraW5kIjoiU2ltcGxlIn19LHsiaWQiOiIzMlNiQWpmQ1NUZVdyX1RBc19C + bVYiLCJuYW1lIjoiRmFkZXIgPT4gU2Nyb2xsIiwiZ3JvdXBJZCI6IjNTd2ZtSVdXS0kwRGpNWUtJOHVRVCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0 + eXBlIjozLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2Ui + OnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciJ9LCJtb2RlIjp7InR5cGUiOjMsIm1heFN0ZXBTaXplIjowLjA1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjU3LCJmeEFuY2hvciI6Imlk + Iiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwi + bW91c2VBY3Rpb24iOnsia2luZCI6IlNjcm9sbCIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImlzRW5hYmxl + ZCI6ZmFsc2UsImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ilk4SV9uWHVjZmEtcGNSX0dxU2hSSCIsIm5hbWUiOiJFbmNvZGVyID0+IFNjcm9sbCIsImdy + b3VwSWQiOiJQcWkxWVAzcmFvTXY4cDJxVl9GSnMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6MywiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDQs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX0sImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MTAwfSwi + dGFyZ2V0Ijp7InR5cGUiOjU3LCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6IlNjcm9sbCIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6Ikxhc3RMb2FkZWQifX0sImlzRW5hYmxlZCI6ZmFsc2UsImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im5UVFgzblZjZ3NSVVJfUkRTSVhv + TSIsIm5hbWUiOiJDdXJzb3IgdXAgPT4gU2Nyb2xsIHVwIiwiZ3JvdXBJZCI6InVybnRFbjZxWS16TkZwWFFBZVRsWCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJ0eXBlIjozLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlz + dHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3du + In0sIm1vZGUiOnsidHlwZSI6MSwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsIm1heFByZXNzTWlsbGlzIjo0MDAs + InR1cmJvUmF0ZSI6MTAwLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjo1NywiZnhBbmNob3IiOiJpZCIs + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJTY3JvbGwiLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJpc0VuYWJsZWQi + OmZhbHNlLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ0UEpVVGotYk9kVjBMUVhhQkh3QlQiLCJuYW1lIjoiQ3Vyc29yIGRvd24gPT4gU2Nyb2xsIGRv + d24iLCJncm91cElkIjoidXJudEVuNnFZLXpORnBYUUFlVGxYIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjMsImNoYW5uZWwiOjAsIm51bWJl + ciI6MTA0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9 + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXVwIn0sIm1vZGUiOnsidHlwZSI6MSwibWF4U3RlcFNpemUi + OjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsIm1heFByZXNzTWlsbGlzIjoyNTAsInR1cmJvUmF0ZSI6MTAwLCJmaXJlTW9kZSI6InR1cmJv + In0sInRhcmdldCI6eyJ0eXBlIjo1NywiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJTY3JvbGwiLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJpc0VuYWJsZWQiOmZhbHNlLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI2aTZhZFRGRGc5RERVY1B5 + T1EzWjgiLCJuYW1lIjoiRW5jb2RlciA9PiBNb3ZlIiwiZ3JvdXBJZCI6IjQ1aTNGOGFmODdMTGRDbVh3NXMzVCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QifSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBl + Ijo1NywiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0 + TG9hZGVkIn19LCJpc0VuYWJsZWQiOmZhbHNlLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJDOEt6RDVOaTAxZ3UzM2ZPZ2xLZ2wiLCJuYW1lIjoiRW5j + b2RlciBwdXNoID0+IFByZXNzIG9yIHJlbGVhc2UiLCJncm91cElkIjoiNDVpM0Y4YWY4N0xMZENtWHc1czNUIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMS92LXNlbGVjdCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0 + Ijp7InR5cGUiOjU3LCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6IlByZXNzT3JSZWxlYXNlIiwiYnV0dG9uIjoiTGVmdCJ9LCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJpc0VuYWJsZWQiOmZhbHNlLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJTM3FQZ0k5dWhrNnhu + T0pQVmVWMksiLCJuYW1lIjoiRW5jb2RlciBtb3ZlID0+IFByZXNzIC4uLiByZWxlYXNlIiwiZ3JvdXBJZCI6IkFwUlZoZjE4b1JaTWpUMzRtaGpNYiIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDEvdi1wb3QifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZWVsQ29udHJvbFRyYW5zZm9y + bWF0aW9uIjoieSA9IHkgPT0gMCA/IDEgOiAocmVsX3RpbWUgPCAyMDAgPyBub25lIDogc3RvcCgwKSkiLCJtYWtlQWJzb2x1dGVFbmFibGVkIjp0cnVlfSwidGFyZ2V0 + Ijp7InR5cGUiOjU3LCJmeEFuY2hvciI6InRoaXMiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiUHJlc3NPclJlbGVhc2UiLCJidXR0b24iOiJMZWZ0In0sInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InpGRjRGbkVHSm85bGNSSDB3eXBIWiIsIm5hbWUi + OiJFbmNvZGVyID0+IE1vdmUiLCJncm91cElkIjoiQXBSVmhmMThvUlpNalQzNG1oak1iIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7InR5cGUiOjU3LCJmeEFuY2hv + ciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImZl + ZWRiYWNrSXNFbmFibGVkIjpmYWxzZX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoibWFpbi9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0 + eXBlIjozLCJjaGFubmVsIjo4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlciIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJqb2ciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImpvZyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1haW4vZmFkZXIv + dG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYWluL2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibWFya2VyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODR9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYXJrZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoi + cmVhZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ3cml0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVs + IjowLCJudW1iZXIiOjc1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoid3Jp + dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gtbGVmdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ4fSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtbGVmdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJN + b3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQi + OiJjaC1yaWdodCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gtcmlnaHQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiYmFuay1sZWZ0Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJiYW5rLWxlZnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiYmFuay1yaWdodCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjQ3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1yaWdodCIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyZXdpbmQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MX0sIm1vZGUiOnsibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJld2luZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJmYXN0 + LWZ3ZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZmFzdC1md2QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicGxheSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjk0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + cGxheSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJzdG9wIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTN9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzdG9wIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJl + Y29yZCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVjb3JkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImN5Y2xlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6ODZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + eWNsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ6b29tIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAwfSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoiem9vbSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJz + Y3J1YiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNjcnViIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImN1cnNvci1sZWZ0Iiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6OTh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjdXJzb3ItbGVmdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjdXJzb3ItcmlnaHQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo5OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1yaWdodCIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjdXJzb3ItdXAiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Nn0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci11cCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJN + b3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQi + OiJjdXJzb3ItZG93biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWRvd24iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibnVkZ2UiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6Im51ZGdlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImRyb3AiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4N30s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImRyb3AiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19fSx7ImlkIjoicmVwbGFjZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoicmVwbGFjZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjbGljayIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2xpY2siLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkw + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic29sbyIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX19LHsiaWQiOiJmMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjIiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo1NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImYyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImYzIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTZ9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJmNCIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiZjQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo1OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY1IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fX0seyJpZCI6ImY2Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJzbXB0ZS1iZWF0cyIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibGNkL2Fzc2lnbm1lbnQiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7 + InR5cGUiOjEyLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvYXNzaWdu + bWVudCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibGNkL3RpbWVjb2RlIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6 + eyJ0eXBlIjoxMiwiZGlzcGxheVR5cGUiOiJtYWNraWUtc2V2ZW4iLCJkaXNwbGF5SWQiOjJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJsY2QvdGltZWNvZGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXNlbGVjdCIsImdyb3VwSWQiOiJ2 + LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2gxL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA0fSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjoyNH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMS9zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwi + OjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL3YtcG90 + L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJY + In0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2gxL3YtcG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAg + MzAgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdCIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92 + LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMSBkY2JhXSJ9 + LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92 + LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9zaW5nbGUiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIw + IDMwIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC9z + cHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxNn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoMS9tdXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gxL3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gxL3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDEvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0 + eXBlIjoxMiwiZGlzcGxheUlkIjowLCJsaW5lIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xjZC9saW5lMSIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6 + MTIsImRpc3BsYXlJZCI6MCwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTIiLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tZXRlci9wZWFr + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozM30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9mYWRlci90b3Vj + aCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + MjV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc2VsZWN0IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMi9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoxfSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMi92LXBvdC9jb250cm9sIiwiZ3JvdXBJ + ZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92 + LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAwIGRjYmFdIn0s + Im1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJh + d01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMi92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvYm9vc3QtY3V0Iiwi + Z3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3Vy + Y2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QvYm9vc3QtY3V0Iiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3Vy + Y2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + Mi92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMSBkY2JhXSJ9 + LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6MTd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIv + bXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9yZWNv + cmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gyL2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJ + ZCI6MSwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEs + ImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJEMCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MzR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDMvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFk + ZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDMvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI2fSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJjaDMvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Mn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMy9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDMvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDMvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3QvZmVlZGJhY2siLCJn + cm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJj + ZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIw + IDMyIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3Qv + d3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1h + eFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJy + YXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDMvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvc3ByZWFkIiwi + Z3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3Vy + Y2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE4fSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL211dGUiLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlk + IjoiIn19fSx7ImlkIjoiY2gzL3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwfSwibW9kZSI6eyJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7 + ImlkIjoiY2gzL3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyfSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFkeSIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX19LHsiaWQiOiJjaDMvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjoyLCJsaW5lIjowfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL2xjZC9saW5lMSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2gzL2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MiwibGluZSI6MX0sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6Ilgi + fSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoMy9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDEwIGRjYmFd + In0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoz + NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXNlbGVjdCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwN30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNC9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9zZWxl + Y3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNC9mYWRlciIs + Imdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjozfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g0L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNC92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6MTksImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92 + LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoNC92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMTAgZGNiYV0i + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC93cmFwIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAu + NzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoi + QjAgMzMgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBv + dC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDQvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTl9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJj + aDQvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvcmVjb3Jk + LXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImNoNC9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjMsImxpbmUiOjB9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDQvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjozLCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L21l + dGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6ImNoNS92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM2fSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MTA4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g1L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3NlbGVjdCIsImdyb3VwSWQiOiJz + ZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g1L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVy + Iiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiY2g1L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiY2hh + cmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsi + dHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3dyYXAi + LCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNv + dXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJCMCAzNCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoNS92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAwMCBkY2Jh + XSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3NpbmdsZSIsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tdXRl + IiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tdXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS9zb2xvIiwiZ3JvdXBJ + ZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJncm91cElk + IjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g1L2xjZC9saW5l + MSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NCwibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6ImNoNS9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9sY2QvbGluZTIiLCJn + cm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDUvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvbWV0ZXIvcGVhayIsImdyb3Vw + SWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEwMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDUvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Ytc2VsZWN0 + IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJjaDYvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoxMDl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIvdG91 + Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g2L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDYvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlw + ZSI6MywiY2hhbm5lbCI6NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJjaDYvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjIxLCJjaGFyYWN0ZXIiOjQsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxMDB9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3QvZmVlZGJhY2siLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlk + aVBhdHRlcm4iOiJCMCAzNSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g2L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBv + dC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g2L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1 + IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g2L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3Npbmds + ZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4 + U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1wb3Qvc3ByZWFkIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01p + ZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjox + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g2L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L211dGUiLCJncm91cElkIjoibXV0 + ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g2L3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L3JlY29yZC1yZWFkeSIsImdyb3VwSWQiOiJyZWNvcmQtcmVhZHki + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDYvbGNkL2xpbmUxIiwiZ3JvdXBJZCI6Imxj + ZCIsInNvdXJjZSI6eyJ0eXBlIjoxMiwiZGlzcGxheUlkIjo1LCJsaW5lIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2 + L2xjZC9saW5lMSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2xjZC9saW5lMiIsImdyb3VwSWQiOiJsY2QiLCJz + b3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NSwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9sY2Qv + bGluZTIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291 + cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMTAxIGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoNi9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1zZWxlY3QiLCJncm91cElkIjoidi1z + ZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoNy9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExMH0sIm1vZGUiOnsi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MzB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDcvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNy9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo2 + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNy92LXBvdC9j + b250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjIsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEwMH0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoNy92LXBvdC9mZWVkYmFjayIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2 + IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2Ui + OnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC93cmFwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1w + b3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDEgZGNiYV0ifSwi + bW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1w + b3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYt + cG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43 + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoNy92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAz + NiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3Qvc3By + ZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbXV0ZSIsImdyb3VwSWQiOiJtdXRlIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDcvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MTR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OiJjaDcvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDcvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNy9sY2QvbGluZTEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5 + cGUiOjEyLCJkaXNwbGF5SWQiOjYsImxpbmUiOjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUxIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvbGNkL2xpbmUyIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjox + MiwiZGlzcGxheUlkIjo2LCJsaW5lIjoxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMiIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTAgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L21ldGVyL3BlYWsi + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjM5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L2ZhZGVyL3RvdWNo + IiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTExfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoz + MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zZWxlY3QiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiY2g4L2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwiOjd9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g4L3YtcG90L2NvbnRyb2wiLCJncm91cElk + Ijoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMywiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6MTAwfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Yt + cG90L2ZlZWRiYWNrIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwi + bW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdCIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3 + TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g4L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJn + cm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJj + ZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9zaW5nbGUiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4 + L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDExIGRjYmFdIn0s + Im1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlk + IjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjoyM30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9t + dXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + IjoxNX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9yZWNv + cmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g4L2xjZC9saW5lMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJ + ZCI6NywibGluZSI6MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlgifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9sY2QvbGluZTIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjcs + ImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJYIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJEMCBbMDExMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 330 93 752 675 + FXID {56928D63-4C84-7E45-9701-E4407998B257} + WAK 0 0 + BYPASS 1 0 0 + Mouse (wonky)" 1751282284<5653546862726C7265616C6561726E00> "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACQCwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4zIiwiaWQiOiJNU2JyWFlWeSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiYXRhdlU4QVdOSHRsQXJESEhZcjl5IiwibmFtZSI6IlVwIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6ODd9fSwibW9kZSI6eyJ0eXBlIjoxLCJt + aW5TdGVwU2l6ZSI6MC4wMDA2OTQ5MjcwMzI2NjE1NzA1LCJtYXhTdGVwU2l6ZSI6MC4wMDA2OTQ5MjcwMzI2NjE1NzA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjoxLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6eyJ0eXBlIjo1NywiZnhBbmNob3IiOiJpZCIsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiZkt2a2FpdUk1 + cXRGRzZ1TmFrTjRqIiwibmFtZSI6IkRvd24iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo4M319LCJtb2RlIjp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAwMDY5NDky + NzAzMjY2MTU3MDUsIm1heFN0ZXBTaXplIjowLjAwMDY5NDkyNzAzMjY2MTU3MDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImZpcmVNb2RlIjoi + dHVyYm8ifSwidGFyZ2V0Ijp7InR5cGUiOjU3LCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJyUXFtSVoxLWlUbVoxeXFCM3BUekIiLCJuYW1lIjoiTGVmdCIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJr + ZXkiOjY1fX0sIm1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDAwNjk0OTI3MDMyNjYxNTcwNSwibWF4U3RlcFNpemUiOjAuMDAwNjk0OTI3MDMyNjYxNTcw + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZSwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlw + ZSI6NTcsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFz + dExvYWRlZCJ9fX0seyJpZCI6IjlDdFU1bTZ1UDQ0Q0J1MjRudnk3SiIsIm5hbWUiOiJSaWdodCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY4fX0sIm1vZGUiOnsidHlw + ZSI6MSwibWluU3RlcFNpemUiOjAuMDAwNjk0OTI3MDMyNjYxNTcwNSwibWF4U3RlcFNpemUiOjAuMDAwNjk0OTI3MDMyNjYxNTcwNSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6MSwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsidHlwZSI6NTcsImZ4QW5jaG9yIjoiaWQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6ImpvbWo1SUVxNU16N3U5NS1wS2h4UyIsIm5h + bWUiOiJDbGljayIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0 + ZXJuIjoiLzEvdG9nZ2xlMSIsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjE2fSwicmVhcGVyU291cmNlVHlwZSI6InJlYWxl + YXJuLXBhcmFtZXRlciJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUi + OjU3LCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6IlByZXNzT3JSZWxlYXNlIiwiYnV0dG9uIjoiTGVmdCJ9LCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 183 112 752 675 + FXID {A31E6845-8695-4541-B321-4913D0770B92} + PARM_TCP 0:_Main_p1__Param_1 + WAK 0 0 + BYPASS 1 0 0 + Mouse" 1751282284<5653546862726C7265616C6561726E00> "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACfBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4zIiwiaWQiOiJNU2JyWFlWeSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiYmRmMTQ5NDUtZDMxNC00YTkxLWIxZDItYWI1NTFkOGU5MWRkIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVm + YXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IktqWXM1bVFGTHJraHY3SGNuWlJ4aSIsIm5hbWUiOiJNb3ZlIiwic291cmNlIjp7ImNhdGVn + b3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEvZmFkZXIxIiwib3NjQXJnSW5kZXgiOjAs + ImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NDl9LCJyZWFwZXJTb3VyY2VUeXBlIjoicmVhbGVhcm4tcGFyYW1ldGVyIn0sIm1vZGUiOnsidHlwZSI6Mywi + bWluU3RlcFNpemUiOjAuMDAxMTEyMzQ3MDUyMjgwMzExNCwibWF4U3RlcFNpemUiOjAuMDAxMTEyMzQ3MDUyMjgwMzExNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6MSwicmV2ZXJzZUlzRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjo1NywidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiam9tajVJ + RXE1TXo3dTk1LXBLaHhTIiwibmFtZSI6IkNsaWNrIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEvdG9nZ2xlMSIsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjQ5fSwicmVhcGVyU291 + cmNlVHlwZSI6InJlYWxlYXJuLXBhcmFtZXRlciJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwi + dGFyZ2V0Ijp7InR5cGUiOjU3LCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6IlByZXNzT3JSZWxlYXNlIiwiYnV0 + dG9uIjoiTGVmdCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 0 0 0 0 + FXID {12671238-2659-284B-8FE5-3BF001572D48} + PARM_TCP 0:_Main_p1__Param_1 + WAK 0 0 + BYPASS 0 0 0 + "" + cnlMde5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA5wQAAAEAAAAAABAA + I3BnbT1pbml0aWFsaXplCiNBTT1Db2xvdXJDb3B5CiNWZXJzPTEwMDAxCiNFbmRpYW49bGl0dGxlCiNubT04CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwoj + bXM9Q3RybEEKI21zPUN0cmxCCiNtcz1FbnYKI21zPUxGTyBMCiNtcz1MRk8gUgojbnY9MAojY209UENvcmUKU0Jhc2U9NApTd2luZz0wLjAwClNUcmlnPTEKUFByb2o9 + MApQRm9sZD0wClBGaWxlPTEKR0ZpbGU9MgpHU2NhbGU9MApDaExheT0wClN1cnJPPTAKUmV2PTEyMDkyCkxFRD0wLjAwClBBR0U9MApQYWdlc09uPTAKQ29yZU49MwpT + bGljZT00ClVJX29wPTIwCk1pZGlBPTUKTWlkaVA9NgpERm9sZD03CkN0cmxBPTIKQ3RybEI9MTEKI2NtPUxGTwpSYXRlPTUwLjAwClN0ZXJlbz05MC4wMApGTW9kPTAu + MDAKV2F2ZT0wCiNjbT1EZWxheQpUaW1lQj04CkRseVJhdGU9MzYuMDAKTm90ZUN0cj0wClRhcEw9MTAwLjAwClRhcFI9MTAwLjAwCkxGT1RyZz0wCkxGT0RwdD0wLjAw + CkxGT0R5bj0wLjAwClJlZ2VuPTUwLjAwCkludmVydD0wCkJyaWdodD0wLjAwClNhdD01MC4wMApDb2xvdXI9My4wMApGbG9vcj0xCkR1Y2s9MApEdWNrVHJnPTAKRHVj + a1Rocj0wLjAwClJvdXRJTj0wClJvdXRGQj0wCldpZHRoPTcxLjAwCk1peD0zNi4wMApJbkc9LTYuMDAKT3V0Rz0wLjgwClBhbmljPTAKRnJlZXplPTAKQnlwYXNzPTAK + SFE9MAojY209TE1lbQpMYXlNZW0xPTAKI2NtPVN0aWNrZXIKc3RrclR4dD04CmJnaWR4PTkKcG9zeD0tMS4wMApwb3N5PS0xLjAwCgoKCgoKLy8gU2VjdGlvbiBmb3Ig + dWdseSBjb21wcmVzc2VkIGJpbmFyeSBEYXRhCi8vIERPTidUIFRPVUNIIFRISVMKCiQkJCQxMzMzCj9nZGdjZW5lYjphYWFiYWFhYTplYWFhYWFhYTpnamdvZ2poZTpn + amdiZ21najpoa2dmY29naTpkY2hhYWFhYToKamxpaGtnbmI6Y2NjYmdwbGY6bGNva2NrYmU6YWE6ZnA6YWU6Y2g6ZWg6ZW46aWE6bmI6b2o6YWI6YXA6YmI6YwpkOmNm + OmNnOmNtOmRiOmRkOmRoOmRtOmRwOmVhOmViOmVkOmZtOmZvIWdkZ2NGV0FKQTI2NWdkZ2NGV1ZBMTBnCmpnb2dqaGVnamdiZ21namhrZ2Zjb2dpZGNoYUE0OWdkZ2NG + V1ZBNzRnZGdjRldWQTEwamxpaGtnSGNjY2JncGwKZmxjb2tja2JlamNQWG1wb2RwbWpoQm9ua2psak1oYU9rbURaWUVOSFRtZmhjQlFtY2tmcGpHaGJGVUVTbGRJSApq + amthQ25kQmlrS0RJUmxlZ2pna2xpZ2RnY0ZXQ0ExMGlicGVuZUpnZGdjRldBSkEyNjVnZGdjRldHQTEzOGdkCmdjRldBSkEyNjVnZGdjRldMQTI3Z2RnY0ZXVkE3ND0z + NzUxMgoAAA== + AGluaXRpYWxpemUAEAAAAA== + > + PRESETNAME initialize + FLOATPOS 314 248 1040 466 + FXID {47EAA534-886D-E247-9C93-2EE7C0F2E304} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-692-when-target-value-met-active-inactive.RPP b/plugin-reaper-realearn/resources/test-projects/issue-692-when-target-value-met-active-inactive.RPP new file mode 100644 index 0000000..f8d4b95 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-692-when-target-value-met-active-inactive.RPP @@ -0,0 +1,185 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 19.5 + ZOOM 100 1523 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADECQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS41IiwiaWQiOiJ5N0pvOW94aCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiI0dVRmMlJwMVg2cFJhbXVKZl9IUlkiLCJuYW1lIjoiTGVhZCBtYXBwaW5nIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJM + YXN0TG9hZGVkIn19fSx7ImlkIjoiVWZLRkx0ZHhncFk1djQ4d3RyblZhIiwibmFtZSI6IkFjdGl2ZSBpZiB0YXJnZXQgb2YgbGVhZCBtYXBwaW5nIGluYWN0aXZlIiwi + c291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjUzLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiYWN0aXZhdGlvblR5cGUiOiJ0YXJnZXQtdmFsdWUiLCJlZWxDb25kaXRpb24iOiJ5ID09IG5vbmUiLCJtYXBw + aW5nS2V5IjoiNHVUZjJScDFYNnBSYW11SmZfSFJZIn0seyJpZCI6IkY0OS1lVTB4R1VEclhLY21rLTNCNCIsIm5hbWUiOiJBY3RpdmUgaWYgdGFyZ2V0IG9mIGxlYWQg + bWFwcGluZyBhY3RpdmUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJhY3RpdmF0aW9uVHlwZSI6InRhcmdldC12YWx1ZSIsImVlbENvbmRpdGlvbiI6 + InkgIT0gbm9uZSIsIm1hcHBpbmdLZXkiOiI0dVRmMlJwMVg2cFJhbXVKZl9IUlkifSx7ImlkIjoieW43MGR5QnlDRDBkMTNVcDVmTG1FIiwibmFtZSI6IkFjdGl2ZSBp + ZiB0YXJnZXQgb2YgbGVhZCBtYXBwaW5nID4gMC41Iiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5 + cGUiOjUzLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiYWN0aXZhdGlvblR5cGUiOiJ0YXJnZXQtdmFsdWUi + LCJlZWxDb25kaXRpb24iOiJ5ID4gMC41IiwibWFwcGluZ0tleSI6IjR1VGYyUnAxWDZwUmFtdUpmX0hSWSJ9XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNl + ZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 520 60 752 675 + FXID {9BF11B3B-9E7F-4E43-89BE-B2BF92C44346} + WAK 0 0 + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-695-dummy-target-slider.RPP b/plugin-reaper-realearn/resources/test-projects/issue-695-dummy-target-slider.RPP new file mode 100644 index 0000000..b5f35de --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-695-dummy-target-slider.RPP @@ -0,0 +1,142 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADxAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS41IiwiaWQiOiJEeFZMbmdHOCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJSbmw0LXZOV0ZGNmR5THoxSlNzOHMiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIi + OjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo4M319LCJt + b2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjUzLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 32 168 752 675 + FXID {E3DBF4A2-FC21-5847-A59D-2DF6D889B721} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-696-relative-retrigger.RPP b/plugin-reaper-realearn/resources/test-projects/issue-696-relative-retrigger.RPP new file mode 100644 index 0000000..8b6557a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-696-relative-retrigger.RPP @@ -0,0 +1,143 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADxAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS41IiwiaWQiOiIxRDc1bTFhZiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJJWjNJbFFoTGgwZDZNbXVMQ0hLOFIiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIi + OjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAwOCBbMGdmZSBkY2JhXSIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 32 168 752 675 + FXID {E02E966A-7803-E041-9894-BE3464457F72} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-698-control-transformation-plotting.RPP b/plugin-reaper-realearn/resources/test-projects/issue-698-control-transformation-plotting.RPP new file mode 100644 index 0000000..7ca74f6 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-698-control-transformation-plotting.RPP @@ -0,0 +1,154 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.00000003162278 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADfBgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS41IiwiaWQiOiJwQVhzRTZNZyIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJXSFJYei1URWo0UGZ1clVYZkZ1bXciLCJuYW1lIjoiZGRkZGRkIiwidGFncyI6WyJ3d2QiXSwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWluVGFyZ2V0VmFsdWUiOjAuNDUsIm1h + eFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiIvLyBEZWJvdW5jZTogVHVy + bnMgdGFyZ2V0IG9uIGFzIHNvb24gYXMgeW91IHN0YXJ0IG1vdmluZyB0aGUga25vYi9mYWRlci5UdXJucyBpdCBvZmYgc2hvcnRseSBhZnRlciB5b3Ugc3RvcCBtb3Zp + bmcuIEdvb2QgZm9yIHRoZSBtb3VzZSB0YXJnZXQgdG8gc2ltdWxhdGUgYSBtb3VzZSBkcmFnIGJ1dCBhbHNvIGdvb2Qgd2l0aCBcIlRyYWNrOiBTZXQgYXV0b21hdGlv + biB0b3VjaCBzdGF0ZVwiIHRhcmdldCB0byBpbXBsZW1lbnQgYXV0b21hdGljIHRvdWNoL3JlbGVhc2Ugd2hlbiB5b3UgZG9uJ3QgaGF2ZSBhIHRvdWNoLXNlbnNpdGl2 + ZSBmYWRlciBhdCBoYW5kLlxuXG4vLyBQYXJhbWV0ZXJzXG5cbnRpbWVvdXRfaW5fbXMgPSAyMDA7XG5cbi8vIENvZGVcblxueSA9IHkgIT0gMSA/IChcbiAgICAxXG4p + IDogKFxuICAgIHJlbF90aW1lIDwgdGltZW91dF9pbl9tcyA/IG5vbmUgOiBzdG9wKDApXG4pOyIsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJhYWEiLCJmZWVk + YmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJGQVhaaUZWUWxJZURRYWFRdEVfWGoiLCJuYW1lIjoiMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5z + dGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 529 9 752 675 + FXID {6F438474-5F03-6045-9CE0-C8572C4F4492} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-706-syntien-osc.RPP b/plugin-reaper-realearn/resources/test-projects/issue-706-syntien-osc.RPP new file mode 100644 index 0000000..0fcebf9 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-706-syntien-osc.RPP @@ -0,0 +1,1425 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6.5625 + ZOOM 55.4635961486199 1548 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO 1 + TEMPO 64 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 37.5 0 + SELECTION2 37.5 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.97259984631529 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAcDQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS42IiwiaWQiOiJwQVhzRTZNZyIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiNzVkNTVkY2QtZGQwMi00YjQwLWJiN2YtY2NlMTI3YjczOWU5IiwiZmVlZGJhY2tEZXZpY2VJZCI6Ijc1 + ZDU1ZGNkLWRkMDItNGI0MC1iYjdmLWNjZTEyN2I3MzllOSIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3si + aWQiOiJXSFJYei1URWo0UGZ1clVYZkZ1bXciLCJuYW1lIjoiRmFkZXIgMSA9PiBWb2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJjaGFubmVsIjowLCJu + dW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvc3ludGllbi9iYXNpYy8xL3NsaWRlcjEiLCJv + c2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6 + MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sInByZXZlbnRFY2hvRmVlZGJhY2siOnRydWV9LHsiaWQiOiJs + T3d4b1JRcnI0N00xMWpneVQ3Z1UiLCJuYW1lIjoiVm9sdW1lID0+IEZhZGVyIDEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJjaGFubmVsIjowLCJudW1iZXIi + OjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMS9mYWRlcjEiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6MSwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlLCJwcmV2ZW50RWNob0ZlZWRi + YWNrIjp0cnVlfSx7ImlkIjoiRkJqclExb0dOR29mcmU1UlFWLW5QIiwibmFtZSI6IlZvbHVtZSA9PiBGYWRlciAyIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL3N5bnRpZW4vYmFzaWMv + MS9idXR0b243Iiwib3NjQXJnSW5kZXgiOjAsIm9zY0FyZ1R5cGUiOiJpbnQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0luZGV4IjoyLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFz + dExvYWRlZCJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2UsInByZXZlbnRFY2hvRmVlZGJhY2siOnRydWV9LHsiaWQiOiJXUDhGY0FrUjhMcGpheEFXSl9pVHciLCJu + YW1lIjoiVm9sdW1lID0+IEZhZGVyIDMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvc3ludGllbi9iYXNpYy8xL2J1dHRvbjExIiwib3NjQXJnSW5kZXgiOjAsIm9zY0FyZ1R5cGUiOiJp + bnQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0lu + ZGV4IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2UsInByZXZl + bnRFY2hvRmVlZGJhY2siOnRydWV9LHsiaWQiOiJad09ZUGVWTXUwTEZyakUwcVlrRGwiLCJuYW1lIjoiVm9sdW1lID0+IEZhZGVyIDQiLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJvc2MiLCJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvc3lu + dGllbi9iYXNpYy8xL3NsaWRlcjIiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6NCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQi + fX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlLCJwcmV2ZW50RWNob0ZlZWRiYWNrIjp0cnVlfV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 500 120 752 675 + FXID {6F438474-5F03-6045-9CE0-C8572C4F4492} + WAK 0 0 + > + > + + > + + > + + > + + > + + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-707-rel-time-target-min-max.RPP b/plugin-reaper-realearn/resources/test-projects/issue-707-rel-time-target-min-max.RPP new file mode 100644 index 0000000..65a279e --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-707-rel-time-target-min-max.RPP @@ -0,0 +1,152 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 3.4020618556701 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 194 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.00000003162278 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACWBgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS42IiwiaWQiOiJiamFLYnpLdSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiM2VSMHUxTmRHYVd5cDVUWU5ibFdqIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2NX19LCJtb2RlIjp7Im1pblRhcmdldFZh + bHVlIjowLjI1LCJtYXhUYXJnZXRWYWx1ZSI6MC43MiwibWluU3RlcFNpemUiOjAuMDAxMDQyNzUyODY3NTcwMzg1OCwibWF4U3RlcFNpemUiOjAuMDAxMDQyNzUyODY3 + NTcwMzg1OCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZWVsQ29udHJvbFRyYW5zZm9ybWF0aW9uIjoiLy8gU2ludXMgTEZPOiBNb2R1bGF0ZSBp + biBzaW51cyBzaGFwZSB3aGlsZSBidXR0b24gcHJlc3NlZFxuXG4vLyBQYXJhbWV0ZXJzXG5cbnBlcmlvZCA9IDI7XG5cbi8vIENvZGVcblxueSA9IHggPiAwID8gKFxu + XHRzZWNzID0gcmVsX3RpbWUgLyAxMDAwO1xuICAgIHNjYWxlZCA9IHNlY3MgKiAoJHBpICogMikgLyBwZXJpb2Q7XG4gICAgKHNpbihzY2FsZWQgLSAkcGkgLyAyKSAr + IDEpIC8gMlxuKSA6IChcbiAgICBzdG9wXG4pOyIsInJvdW5kVGFyZ2V0VmFsdWUiOnRydWUsImdyb3VwSW50ZXJhY3Rpb24iOiJpbnZlcnNlLXRhcmdldC12YWx1ZSJ9 + LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJhdXRvbWF0aW9uTW9kZU92ZXJyaWRlVHlw + ZSI6Im5vbmUiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJQNWdZQVhyYWFuZVFwb24zTUs5MUkiLCJuYW1lIjoiMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 257 125 752 675 + FXID {F7D97A2F-4E70-D846-8B7E-F807BD6E332C} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-713-speech-source.RPP b/plugin-reaper-realearn/resources/test-projects/issue-713-speech-source.RPP new file mode 100644 index 0000000..875c0c3 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-713-speech-source.RPP @@ -0,0 +1,142 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1.84255291671712 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAD7AgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS44IiwiaWQiOiJDci1mSEJXcCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJWVmlGZV83TVk0OXhTVVRQS1d5YWoiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InJlYXBlciIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwicmVhcGVyU291cmNlVHlwZSI6InNwZWVjaCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOAT 246 137 752 675 + FXID {C4670185-9BCB-AD47-9174-FA046BAAD124} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-716-novation-sl-mk3.RPP b/plugin-reaper-realearn/resources/test-projects/issue-716-novation-sl-mk3.RPP new file mode 100644 index 0000000..d96375a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-716-novation-sl-mk3.RPP @@ -0,0 +1,165 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACWCwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wIiwiaWQiOiJBNWlUSWdLTSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMjIiLCJmZWVkYmFja0RldmljZUlkIjoiMjIiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdy + b3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiTDgzQTlyd2hZMjl4LWttOFRMM2YzIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MTEsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJtaWRpU2NyaXB0S2luZCI6Imx1YSIsIm1pZGlTY3JpcHQiOiItLSBDb25maWd1cmF0aW9uXG5cbi0tIDB4MDAgKDApIOKAkyBF + bXB0eSBMYXlvdXRcbi0tIDB4MDEgKDEpIOKAkyBLbm9iIExheW91dFxuLS0gMHgwMiAoMikg4oCTIEJveCBMYXlvdXRcbmxvY2FsIGxheW91dF9pbmRleCA9IDJcblxu + LS0gVGhlIGNvbHVtbiBpbmRleCBmb3IgdGhlIHZhbHVlOlxuLS0gMHgwMCAoMCkgdG8gMHgwNyAoNykgLSBDb2x1bW5zIDEtOFxuLS0gMHgwOCAoOCkgLSBDZW50cmUg + c2NyZWVuXG5sb2NhbCBjb2x1bW5faW5kZXggPSAwXG5cbi0tIFRoZSBpbmRleCBvZiB0aGUgb2JqZWN0IHRvIGNoYW5nZS4gVGhpcyBpcyBzcGVjaWZpY1xuLS0gdG8g + dGhlIGxheW91dC4gU2VlIHRoZSBsYXlvdXQgZGV0YWlscyBmb3IgdGhlXG4tLSB2YWx1ZXMuXG5sb2NhbCBvYmplY3RfaW5kZXggPSAwXG5cbi0tIFRoZSB0ZXh0IChi + eSBkZWZhdWx0IHRoZSB0YXJnZXQgdmFsdWUpXG5sb2NhbCB0ZXh0ID0geVxuXG4tLSBGdW5jdGlvbnNcblxuZnVuY3Rpb24gdG9fYXNjaWkodGV4dClcbiAgICBsb2Nh + bCBieXRlcyA9IHt9XG4gICAgZm9yIGk9MSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICBieXRlc1tpXSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgZW5k + XG4gICAgcmV0dXJuIGJ5dGVzXG5lbmRcblxuZnVuY3Rpb24gY29uY2F0X3RhYmxlKHQxLCB0MilcbiAgICBmb3IgaT0xLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxKzFd + ID0gdDJbaV1cbiAgICBlbmRcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9tc2coY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHRleHQpXG4gICAgbG9jYWwg + bXNnID0ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiVGV4dFwiXG4gICAg + ICAgIDB4MDEsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgfVxuICAgIC0tIEFTQ0lJLWVuY29kZWQgdGV4dFxuICAg + IGNvbmNhdF90YWJsZShtc2csIHRvX2FzY2lpKHRleHQpKVxuICAgIC0tIG51bGwgdGVybWluYXRvclxuICAgIHRhYmxlLmluc2VydChtc2csIDB4MDApXG4gICAgcmV0 + dXJuIG1zZ1xuZW5kXG5cbi0tIENvZGVcblxubG9jYWwgY2hhbmdlX2xheW91dCA9IHtcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICAweGYwLCAweDAwLCAweDIwLCAw + eDI5LCAweDAyLCAweDBhLCAweDAxLFxuICAgIC0tIFNldCBTY3JlZW4gTGF5b3V0IENvbW1hbmQgSURcbiAgICAweDAxLFxuICAgIC0tIExheW91dCBJbmRleFxuICAg + IGxheW91dF9pbmRleCxcbiAgICAtLSBFbmQgb2YgU3lzRXhcbiAgICAweGY3LFxufVxubG9jYWwgY2hhbmdlX3Byb3BlcnRpZXMgPSB7XG4gICAgLS0gU3lzRXggSGVh + ZGVyXG4gICAgMHhmMCwgMHgwMCwgMHgyMCwgMHgyOSwgMHgwMiwgMHgwYSwgMHgwMSxcbiAgICAtLSBTZXQgU2NyZWVuIFByb3BlcnRpZXMgQ29tbWFuZFxuICAgIDB4 + MDIsXG59XG5jb25jYXRfdGFibGUoY2hhbmdlX3Byb3BlcnRpZXMsIGNyZWF0ZV90ZXh0X21zZyhjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dCkpXG50YWJs + ZS5pbnNlcnQoY2hhbmdlX3Byb3BlcnRpZXMsIDB4ZjcpXG5cbnJldHVybiB7XG4gICAgbWVzc2FnZXMgPSB7XG4gICAgICAgIGNoYW5nZV9sYXlvdXQsXG4gICAgICAg + IGNoYW5nZV9wcm9wZXJ0aWVzLFxuICAgIH1cbn0gIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1LCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiSGFsbG8iLCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJRU2JFZU9fUTRNOFBXcTBGdXh5djUiLCJuYW1lIjoiMiIsInNv + dXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjoyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 32 168 752 675 + FXID {93353443-C049-A943-AEB2-425F7D85FFA2} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-716-novation-sl-mkiii-pot-browser.RPP b/plugin-reaper-realearn/resources/test-projects/issue-716-novation-sl-mkiii-pot-browser.RPP new file mode 100644 index 0000000..d44c6e9 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-716-novation-sl-mkiii-pot-browser.RPP @@ -0,0 +1,5159 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAANJgMAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNi4wLXByZS4xIiwiaWQiOiJaRmdLMW5uNCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMjIiLCJmZWVkYmFja0RldmljZUlkIjoiMjIiLCJkZWZhdWx0R3JvdXAiOnt9LCJncm91cHMiOlt7Imlk + IjoibW9kZXMiLCJuYW1lIjoiTW9kZXMifSx7ImlkIjoibWFjcm8tYmFua3MiLCJuYW1lIjoiTWFjcm8gYmFua3MiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dyYW0iLCJw + cm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MX19LHsiaWQiOiJtYWNyby1wYXJhbWV0ZXJzIiwibmFtZSI6Ik1hY3JvIHBhcmFt + ZXRlcnMiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dyYW0iLCJwcm9ncmFtQ29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MX19LHsiaWQiOiJt + YWNyby1yZXNvbHZlZC1wYXJhbWV0ZXJzIiwibmFtZSI6Ik1hY3JvIHJlc29sdmVkIHBhcmFtZXRlcnMiLCJhY3RpdmF0aW9uVHlwZSI6InByb2dyYW0iLCJwcm9ncmFt + Q29uZGl0aW9uIjp7InBhcmFtSW5kZXgiOjAsInByb2dyYW1JbmRleCI6MX19LHsiaWQiOiJicm93c2UtY29sdW1ucyIsIm5hbWUiOiJCcm93c2UgY29sdW1ucyIsImFj + dGl2YXRpb25UeXBlIjoicHJvZ3JhbSJ9LHsiaWQiOiJicm93c2UtYWN0aW9ucyIsIm5hbWUiOiJCcm93c2UgYWN0aW9ucyIsImFjdGl2YXRpb25UeXBlIjoicHJvZ3Jh + bSJ9LHsiaWQiOiJtYW51YWwiLCJuYW1lIjoiTWFudWFsIn1dLCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiMVNqTEp0U0pLdHBN + UXpQUUpVN0RjIiwibmFtZSI6Ik1vZGUgc3dpdGNoIiwiZ3JvdXBJZCI6Im1vZGVzIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjkwLCJjaGFyYWN0ZXIi + OjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJ0eXBlIjoxLCJtaW5Tb3VyY2VWYWx1ZSI6MC4xNiwibWF4VGFyZ2V0VmFsdWUiOjAuMTExMTExMTExMTExMTExMSwi + bWluU3RlcFNpemUiOjAuMTExMTExMTExMTExMTExMSwibWF4U3RlcFNpemUiOjAuMTExMTExMTExMTExMTExMSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6MSwicm90YXRlSXNFbmFibGVkIjp0cnVlfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IjBOaUF1bzhPTzRnYTVLZDZBY0FEMSIsIm5hbWUiOiJJbml0IG1hY3JvIG1vZGUiLCJncm91 + cElkIjoibW9kZXMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2Nh + bCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcg + PSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19h + c2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBk + b1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAg + ICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAg + dGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAj + dDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNFeCBI + ZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRl + bnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJu + IG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVy + IGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4g + ICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3Rp + ZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5l + MiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29u + dGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWko + bGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3Jl + ZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9w + cm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNb + aV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4g + Y3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51 + bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRp + c3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5f + aW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgs + XG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRl + eCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFs + dWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUg + dG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGlu + Z3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXgg + bnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8g + Y2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9j + aGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5f + aW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmpl + Y3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0 + eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBj + b2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2gg + bG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBh + bmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09 + IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9p + bmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBv + YmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9y + LmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhl + IHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVk + X2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0g + Zmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAo + dGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAg + aWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtc + biAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAg + ICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVf + bGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVu + XG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAj + dGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChm + cmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAgICAg + ICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291 + bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRf + b2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMg + Q29kZSAjI1xucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gMTAwMCxcbiAgICBtZXNzYWdlcyA9IHtcbiAgICAgICAgY3JlYXRlX3NjcmVlbl9sYXlvdXRfbXNnKDEpLFxu + ICAgIH1cbn0gIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2UsImFjdGl2YXRpb25UeXBlIjoicHJvZ3JhbSIsInByb2dyYW1Db25kaXRpb24iOnsicGFyYW1JbmRleCI6MCwicHJvZ3JhbUlu + ZGV4IjoxfX0seyJpZCI6InF0clQ1M0VqTmZ1d21rSGVhSERoVCIsIm5hbWUiOiJJbml0IGJyb3dzZSBtb2RlIiwiZ3JvdXBJZCI6Im1vZGVzIiwic291cmNlIjp7InR5 + cGUiOjExLCJtaWRpU2NyaXB0S2luZCI6Imx1YSIsIm1pZGlTY3JpcHQiOiItLSAjIyBDb25zdGFudHMgIyNcblxubG9jYWwgY29sdW1uX2tub2JfYWRkcmVzc19vZmZz + ZXQgPSA1MDAwXG5sb2NhbCBsZWRfYWRkcmVzc19vZmZzZXQgPSAxMDAwMFxubG9jYWwgYmxhY2sgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxubG9jYWwgd2hpdGUg + PSB7IHIgPSAyNTUsIGcgPSAyNTUsIGIgPSAyNTUgfVxuXG4tLSAjIyBGdW5jdGlvbnMgIyNcblxuZnVuY3Rpb24gdG9fYXNjaWkodGV4dClcbiAgICBsb2NhbCBieXRl + cyA9IHt9XG4gICAgaWYgdGV4dCB+PSBuaWwgdGhlblxuICAgICAgICBmb3IgaSA9IDEsIHN0cmluZy5sZW4odGV4dCkgZG9cbiAgICAgICAgICAgIGxvY2FsIGJ5dGUg + PSBzdHJpbmcuYnl0ZSh0ZXh0LCBpKVxuICAgICAgICAgICAgaWYgYnl0ZSA8IDEyOCB0aGVuXG4gICAgICAgICAgICAgICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCBieXRl + KVxuICAgICAgICAgICAgZW5kXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIC0tIE51bGwgdGVybWluYXRvclxuICAgIHRhYmxlLmluc2VydChieXRlcywgMClcbiAg + ICByZXR1cm4gYnl0ZXNcbmVuZFxuXG5mdW5jdGlvbiBjb25jYXRfdGFibGUodDEsIHQyKVxuICAgIGZvciBpID0gMSwgI3QyIGRvXG4gICAgICAgIHQxWyN0MSArIDFd + ID0gdDJbaV1cbiAgICBlbmRcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbXNnKGNvbnRlbnQpXG4gICAgLS0gU3lzRXggSGVhZGVyXG4gICAgbG9jYWwgbXNnID0ge1xu + ICAgICAgICAweGYwLCAweDAwLCAweDIwLCAweDI5LCAweDAyLCAweDBhLCAweDAxLFxuICAgIH1cbiAgICAtLSBDb250ZW50XG4gICAgY29uY2F0X3RhYmxlKG1zZywg + Y29udGVudClcbiAgICAtLSBFbmQgb2YgU3lzRXhcbiAgICB0YWJsZS5pbnNlcnQobXNnLCAweGY3KVxuICAgIHJldHVybiBtc2dcbmVuZFxuXG4tLS0gQ3JlYXRlcyBh + IG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBsYXlvdXQuXG4tLS1cbi0tLSBAcGFyYW0gbGF5b3V0X2luZGV4IG51bWJlciBsYXlvdXQgaW5kZXggKDAgPSBlbXB0eSwg + MSA9IGtub2IsIDIgPSBib3gpXG5mdW5jdGlvbiBjcmVhdGVfc2NyZWVuX2xheW91dF9tc2cobGF5b3V0X2luZGV4KVxuICAgIHJldHVybiBjcmVhdGVfbXNnKHsgMHgw + MSwgbGF5b3V0X2luZGV4IH0pXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBkaXNwbGF5aW5nIGEgbm90aWZpY2F0aW9uIG9uIHRoZSBjZW50ZXIgc2Ny + ZWVuLlxuLS0tXG4tLS0gQHBhcmFtIGxpbmUxIHN0cmluZyBmaXJzdCBsaW5lIG9mIHRleHRcbi0tLSBAcGFyYW0gbGluZTIgc3RyaW5nIHNlY29uZCBsaW5lIG9mIHRl + eHRcbmZ1bmN0aW9uIGNyZWF0ZV9ub3RpZmljYXRpb25fdGV4dF9tc2cobGluZTEsIGxpbmUyKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDQgfVxuICAgIGNvbmNh + dF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMSkpXG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUyKSlcbiAgICByZXR1cm4gY3JlYXRl + X21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgbXVsdGlwbGUgc2NyZWVuIHByb3BlcnRpZXMuXG4tLS1cbi0tLSBA + cGFyYW0gY2hhbmdlcyB0YWJsZSBsaXN0IG9mIHByb3BlcnR5IGNoYW5nZXNcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fcHJvcHNfbXNnKGNoYW5nZXMpXG4gICAgbG9j + YWwgY29udGVudCA9IHsgMHgwMiB9XG4gICAgZm9yIGkgPSAxLCAjY2hhbmdlcyBkb1xuICAgICAgICBpZiBjaGFuZ2VzW2ldIH49IG5pbCB0aGVuXG4gICAgICAgICAg + ICBjb25jYXRfdGFibGUoY29udGVudCwgY2hhbmdlc1tpXSlcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxu + XG4tLS0gQ3JlYXRlcyBhIHRleHQgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGRp + c3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFt + IHRleHQgc3RyaW5nIHRoZSBhY3R1YWwgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHRleHQp + XG4gICAgbG9jYWwgY2hhbmdlID0ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBl + IFwiVGV4dFwiXG4gICAgICAgIDB4MDEsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgfVxuICAgIGNvbmNhdF90YWJs + ZShjaGFuZ2UsIHRvX2FzY2lpKHRleHQpKVxuICAgIHJldHVybiBjaGFuZ2VcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHZhbHVlIHByb3BlcnR5IGNoYW5nZS5cbi0tLVxu + LS0tIElmIGl0J3MgYWJvdXQgc2V0dGluZyB0aGUga25vYiB2YWx1ZSwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIG5vdFxuLS0tIGRvIHRoaXMgdmlhIHNj + cmlwdCBidXQgYnkgZW5hYmxpbmcgZmVlZGJhY2sgb24gdGhlIGNvcnJlc3BvbmRpbmcgZW5jb2RlclxuLS0tIG1hcHBpbmdzLCB0aGVuIHlvdSBuZWVkIGp1c3Qgb25l + IG1hcHBpbmcgZm9yIGJvdGggY29udHJvbCBhbmQgZmVlZGJhY2suXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8g + Y2hhbmdlIHRoZSB2YWx1ZVxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIHRoZSBraW5kIG9mIHZhbHVlIHRvIGNoYW5nZSAoc2VlIHByb2dyYW1tZXIncyBn + dWlkZSlcbi0tLSBAcGFyYW0gdmFsdWUgbnVtYmVyIHRoZSB2YWx1ZVxuZnVuY3Rpb24gY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0 + X2luZGV4LCB2YWx1ZSlcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0 + eSBUeXBlIFwiVmFsdWVcIlxuICAgICAgICAweDAzLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgICAgICAtLSBDb2xv + ciBieXRlc1xuICAgICAgICB2YWx1ZVxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhbiBSR0IgY29sb3IgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgdGhl + IGdpdmVuIGNvbG9yIGlzIGBuaWxgLCB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYG5pbGAuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGlj + aCBjb2x1bW4gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGNoYW5nZSB0aGUgY29s + b3Jcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfcmdi + X2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBudWxsIHRoZW5cbiAgICAgICAgcmV0dXJu + IG5pbFxuICAgIGVuZFxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5 + IFR5cGUgXCJSR0IgY29sb3JcIlxuICAgICAgICAweDA0LFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgICAgICAtLSBD + b2xvciBieXRlc1xuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxv + b3IoY29sb3IuYiAvIDIpLFxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBzdGF0ZSBvZiBhbiBMRUQuXG4tLS1cbi0t + LSBQYXNzaW5nIGEgYG5pbGAgY29sb3Igd2lsbCBtYWtlIHRoZSBMRUQgZ28gb2ZmLlxuLS0tXG4tLS0gQHBhcmFtIGxlZF9pbmRleCBudW1iZXIgd2hpY2ggTEVEIHRv + IHRhbGsgdG9cbi0tLSBAcGFyYW0gbGVkX2JlaGF2aW9yIG51bWJlciBMRUQgYmVoYXZpb3IgKDEgPSBzb2xpZCwgMiA9IGZsYXNoaW5nIHdpdGggcHJldmlvdXNseSBz + ZXQgc29saWQgY29sb3IsIDMgPSBwdWxzYXRpbmcpXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGllcyByLCBn + IGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX2xlZF9tc2cobGVkX2luZGV4LCBsZWRfYmVoYXZpb3IsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG5pbCB0aGVuXG4gICAg + ICAgIGNvbG9yID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1cbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7XG4gICAgICAgIDB4MDMsXG4gICAgICAgIGxl + ZF9pbmRleCxcbiAgICAgICAgbGVkX2JlaGF2aW9yLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8g + MiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH0pXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX2xlZnRfcmlnaHRfYW5pbWF0aW9uKGdsb2Jh + bF9taWxsaXMsIG1heF9jaGFyX2NvdW50LCBmcmFtZV9sZW5ndGgsIHRleHQpXG4gICAgaWYgdGV4dCA9PSBuaWwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAg + ZW5kXG4gICAgaWYgI3RleHQgPiBtYXhfY2hhcl9jb3VudCB0aGVuXG4gICAgICAgIGxvY2FsIGZyYW1lX2NvdW50ID0gI3RleHQgLSBtYXhfY2hhcl9jb3VudFxuICAg + ICAgICBsb2NhbCBmcmFtZV9pbmRleCA9IG1hdGguZmxvb3IoZ2xvYmFsX21pbGxpcyAvIGZyYW1lX2xlbmd0aCkgJSAoZnJhbWVfY291bnQgKiAyKVxuICAgICAgICBs + b2NhbCB0ZXh0X29mZnNldFxuICAgICAgICBpZiBmcmFtZV9pbmRleCA8IGZyYW1lX2NvdW50IHRoZW5cbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfaW5k + ZXhcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgbG9jYWwgZGlzdGFuY2UgPSBmcmFtZV9pbmRleCAtIGZyYW1lX2NvdW50XG4gICAgICAgICAgICB0ZXh0X29mZnNl + dCA9IGZyYW1lX2NvdW50IC0gZGlzdGFuY2VcbiAgICAgICAgZW5kXG4gICAgICAgIHJldHVybiB0ZXh0OnN1Yih0ZXh0X29mZnNldCArIDEsIHRleHRfb2Zmc2V0ICsg + bWF4X2NoYXJfY291bnQpXG4gICAgZWxzZVxuICAgICAgICByZXR1cm4gdGV4dFxuICAgIGVuZFxuZW5kXG5cbi0tICMjIENvZGUgIyNcbnJldHVybiB7XG4gICAgYWRk + cmVzcyA9IDEwMDAsXG4gICAgbWVzc2FnZXMgPSB7XG4gICAgICAgIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZygyKSxcbiAgICB9XG59ICJ9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlLCJh + Y3RpdmF0aW9uVHlwZSI6InByb2dyYW0ifSx7ImlkIjoiZW5MRjdSMmMzM0p2QnRpM0ROY0tVIiwibmFtZSI6IlByZXNldCBpbmZvIiwiZ3JvdXBJZCI6Im1vZGVzIiwi + c291cmNlIjp7InR5cGUiOjExLCJtaWRpU2NyaXB0S2luZCI6Imx1YSIsIm1pZGlTY3JpcHQiOiItLSAjIyBDb25zdGFudHMgIyNcblxubG9jYWwgY29sdW1uX2tub2Jf + YWRkcmVzc19vZmZzZXQgPSA1MDAwXG5sb2NhbCBsZWRfYWRkcmVzc19vZmZzZXQgPSAxMDAwMFxubG9jYWwgYmxhY2sgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxu + bG9jYWwgd2hpdGUgPSB7IHIgPSAyNTUsIGcgPSAyNTUsIGIgPSAyNTUgfVxuXG4tLSAjIyBGdW5jdGlvbnMgIyNcblxuZnVuY3Rpb24gdG9fYXNjaWkodGV4dClcbiAg + ICBsb2NhbCBieXRlcyA9IHt9XG4gICAgaWYgdGV4dCB+PSBuaWwgdGhlblxuICAgICAgICBmb3IgaSA9IDEsIHN0cmluZy5sZW4odGV4dCkgZG9cbiAgICAgICAgICAg + IGxvY2FsIGJ5dGUgPSBzdHJpbmcuYnl0ZSh0ZXh0LCBpKVxuICAgICAgICAgICAgaWYgYnl0ZSA8IDEyOCB0aGVuXG4gICAgICAgICAgICAgICAgdGFibGUuaW5zZXJ0 + KGJ5dGVzLCBieXRlKVxuICAgICAgICAgICAgZW5kXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIC0tIE51bGwgdGVybWluYXRvclxuICAgIHRhYmxlLmluc2VydChi + eXRlcywgMClcbiAgICByZXR1cm4gYnl0ZXNcbmVuZFxuXG5mdW5jdGlvbiBjb25jYXRfdGFibGUodDEsIHQyKVxuICAgIGZvciBpID0gMSwgI3QyIGRvXG4gICAgICAg + IHQxWyN0MSArIDFdID0gdDJbaV1cbiAgICBlbmRcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbXNnKGNvbnRlbnQpXG4gICAgLS0gU3lzRXggSGVhZGVyXG4gICAgbG9j + YWwgbXNnID0ge1xuICAgICAgICAweGYwLCAweDAwLCAweDIwLCAweDI5LCAweDAyLCAweDBhLCAweDAxLFxuICAgIH1cbiAgICAtLSBDb250ZW50XG4gICAgY29uY2F0 + X3RhYmxlKG1zZywgY29udGVudClcbiAgICAtLSBFbmQgb2YgU3lzRXhcbiAgICB0YWJsZS5pbnNlcnQobXNnLCAweGY3KVxuICAgIHJldHVybiBtc2dcbmVuZFxuXG4t + LS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBsYXlvdXQuXG4tLS1cbi0tLSBAcGFyYW0gbGF5b3V0X2luZGV4IG51bWJlciBsYXlvdXQgaW5kZXgg + KDAgPSBlbXB0eSwgMSA9IGtub2IsIDIgPSBib3gpXG5mdW5jdGlvbiBjcmVhdGVfc2NyZWVuX2xheW91dF9tc2cobGF5b3V0X2luZGV4KVxuICAgIHJldHVybiBjcmVh + dGVfbXNnKHsgMHgwMSwgbGF5b3V0X2luZGV4IH0pXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBkaXNwbGF5aW5nIGEgbm90aWZpY2F0aW9uIG9uIHRo + ZSBjZW50ZXIgc2NyZWVuLlxuLS0tXG4tLS0gQHBhcmFtIGxpbmUxIHN0cmluZyBmaXJzdCBsaW5lIG9mIHRleHRcbi0tLSBAcGFyYW0gbGluZTIgc3RyaW5nIHNlY29u + ZCBsaW5lIG9mIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV9ub3RpZmljYXRpb25fdGV4dF9tc2cobGluZTEsIGxpbmUyKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDQg + fVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMSkpXG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUyKSlcbiAgICBy + ZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgbXVsdGlwbGUgc2NyZWVuIHByb3BlcnRpZXMu + XG4tLS1cbi0tLSBAcGFyYW0gY2hhbmdlcyB0YWJsZSBsaXN0IG9mIHByb3BlcnR5IGNoYW5nZXNcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fcHJvcHNfbXNnKGNoYW5n + ZXMpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwMiB9XG4gICAgZm9yIGkgPSAxLCAjY2hhbmdlcyBkb1xuICAgICAgICBpZiBjaGFuZ2VzW2ldIH49IG5pbCB0aGVu + XG4gICAgICAgICAgICBjb25jYXRfdGFibGUoY29udGVudCwgY2hhbmdlc1tpXSlcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29u + dGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHRleHQgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2gg + Y29sdW1uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBkaXNwbGF5IHRoZSB0ZXh0 + XG4tLS0gQHBhcmFtIHRleHQgc3RyaW5nIHRoZSBhY3R1YWwgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3Rf + aW5kZXgsIHRleHQpXG4gICAgbG9jYWwgY2hhbmdlID0ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQ + cm9wZXJ0eSBUeXBlIFwiVGV4dFwiXG4gICAgICAgIDB4MDEsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgfVxuICAg + IGNvbmNhdF90YWJsZShjaGFuZ2UsIHRvX2FzY2lpKHRleHQpKVxuICAgIHJldHVybiBjaGFuZ2VcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHZhbHVlIHByb3BlcnR5IGNo + YW5nZS5cbi0tLVxuLS0tIElmIGl0J3MgYWJvdXQgc2V0dGluZyB0aGUga25vYiB2YWx1ZSwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIG5vdFxuLS0tIGRv + IHRoaXMgdmlhIHNjcmlwdCBidXQgYnkgZW5hYmxpbmcgZmVlZGJhY2sgb24gdGhlIGNvcnJlc3BvbmRpbmcgZW5jb2RlclxuLS0tIG1hcHBpbmdzLCB0aGVuIHlvdSBu + ZWVkIGp1c3Qgb25lIG1hcHBpbmcgZm9yIGJvdGggY29udHJvbCBhbmQgZmVlZGJhY2suXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGlj + aCBjb2x1bW4gdG8gY2hhbmdlIHRoZSB2YWx1ZVxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIHRoZSBraW5kIG9mIHZhbHVlIHRvIGNoYW5nZSAoc2VlIHBy + b2dyYW1tZXIncyBndWlkZSlcbi0tLSBAcGFyYW0gdmFsdWUgbnVtYmVyIHRoZSB2YWx1ZVxuZnVuY3Rpb24gY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbl9p + bmRleCwgb2JqZWN0X2luZGV4LCB2YWx1ZSlcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAg + ICAtLSBQcm9wZXJ0eSBUeXBlIFwiVmFsdWVcIlxuICAgICAgICAweDAzLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAg + ICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICB2YWx1ZVxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhbiBSR0IgY29sb3IgcHJvcGVydHkgY2hhbmdlLlxuLS0t + XG4tLS0gSWYgdGhlIGdpdmVuIGNvbG9yIGlzIGBuaWxgLCB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYG5pbGAuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51 + bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGNo + YW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlv + biBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBudWxsIHRoZW5cbiAg + ICAgICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAg + IC0tIFByb3BlcnR5IFR5cGUgXCJSR0IgY29sb3JcIlxuICAgICAgICAweDA0LFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxu + ICAgICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAg + ICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBzdGF0ZSBvZiBhbiBM + RUQuXG4tLS1cbi0tLSBQYXNzaW5nIGEgYG5pbGAgY29sb3Igd2lsbCBtYWtlIHRoZSBMRUQgZ28gb2ZmLlxuLS0tXG4tLS0gQHBhcmFtIGxlZF9pbmRleCBudW1iZXIg + d2hpY2ggTEVEIHRvIHRhbGsgdG9cbi0tLSBAcGFyYW0gbGVkX2JlaGF2aW9yIG51bWJlciBMRUQgYmVoYXZpb3IgKDEgPSBzb2xpZCwgMiA9IGZsYXNoaW5nIHdpdGgg + cHJldmlvdXNseSBzZXQgc29saWQgY29sb3IsIDMgPSBwdWxzYXRpbmcpXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJv + cGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX2xlZF9tc2cobGVkX2luZGV4LCBsZWRfYmVoYXZpb3IsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG5p + bCB0aGVuXG4gICAgICAgIGNvbG9yID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1cbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7XG4gICAgICAgIDB4MDMs + XG4gICAgICAgIGxlZF9pbmRleCxcbiAgICAgICAgbGVkX2JlaGF2aW9yLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9v + cihjb2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH0pXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX2xlZnRfcmlnaHRfYW5p + bWF0aW9uKGdsb2JhbF9taWxsaXMsIG1heF9jaGFyX2NvdW50LCBmcmFtZV9sZW5ndGgsIHRleHQpXG4gICAgaWYgdGV4dCA9PSBuaWwgdGhlblxuICAgICAgICByZXR1 + cm4gbmlsXG4gICAgZW5kXG4gICAgaWYgI3RleHQgPiBtYXhfY2hhcl9jb3VudCB0aGVuXG4gICAgICAgIGxvY2FsIGZyYW1lX2NvdW50ID0gI3RleHQgLSBtYXhfY2hh + cl9jb3VudFxuICAgICAgICBsb2NhbCBmcmFtZV9pbmRleCA9IG1hdGguZmxvb3IoZ2xvYmFsX21pbGxpcyAvIGZyYW1lX2xlbmd0aCkgJSAoZnJhbWVfY291bnQgKiAy + KVxuICAgICAgICBsb2NhbCB0ZXh0X29mZnNldFxuICAgICAgICBpZiBmcmFtZV9pbmRleCA8IGZyYW1lX2NvdW50IHRoZW5cbiAgICAgICAgICAgIHRleHRfb2Zmc2V0 + ID0gZnJhbWVfaW5kZXhcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgbG9jYWwgZGlzdGFuY2UgPSBmcmFtZV9pbmRleCAtIGZyYW1lX2NvdW50XG4gICAgICAgICAg + ICB0ZXh0X29mZnNldCA9IGZyYW1lX2NvdW50IC0gZGlzdGFuY2VcbiAgICAgICAgZW5kXG4gICAgICAgIHJldHVybiB0ZXh0OnN1Yih0ZXh0X29mZnNldCArIDEsIHRl + eHRfb2Zmc2V0ICsgbWF4X2NoYXJfY291bnQpXG4gICAgZWxzZVxuICAgICAgICByZXR1cm4gdGV4dFxuICAgIGVuZFxuZW5kXG5cbi0tICMjIENvZGUgIyNcbmxvY2Fs + IGNvbHVtbiA9IDhcbmxvY2FsIHByZXNldF9uYW1lID0geSBhbmQgeS5wcmVzZXRfbmFtZSBvciBcIlwiXG5sb2NhbCBtYXhfY2hhcl9jb3VudCA9IDlcbmxvY2FsIGZy + YW1lX2xlbmd0aCA9IDE1MFxubG9jYWwgcHJlc2V0X25hbWVfYW5pbWF0aW9uID0geSBhbmQgY3JlYXRlX2xlZnRfcmlnaHRfYW5pbWF0aW9uKHkubWlsbGlzLCBtYXhf + Y2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB5LnByZXNldF9uYW1lKSBvciBcIlwiXG5sb2NhbCBwcm9kdWN0X25hbWVfYW5pbWF0aW9uID0geSBhbmQgY3JlYXRlX2xl + ZnRfcmlnaHRfYW5pbWF0aW9uKHkubWlsbGlzLCBtYXhfY2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB5LnByb2R1Y3RfbmFtZSkgb3IgXCJcIlxucmV0dXJuIHtcbiAg + ICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfdGV4 + dF9wcm9wX2NoYW5nZShjb2x1bW4sIDAsIHByZXNldF9uYW1lX2FuaW1hdGlvbiksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDEs + IHByb2R1Y3RfbmFtZV9hbmltYXRpb24pLFxuICAgICAgICB9KSxcbiAgICB9XG59ICJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEs + ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJsb2NhbCBtaWxsaXMgPSBjb250ZXh0LnByb3AoXCJnbG9iYWwucmVhbGVhcm4udGltZVwiKVxubG9jYWwgcHJlc2V0 + X25hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQucHJlc2V0Lm5hbWVcIilcbmxvY2FsIHByb2R1Y3RfbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5wcmVzZXQu + cHJvZHVjdC5uYW1lXCIpXG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIHByZXNldF9uYW1lID0g + cHJlc2V0X25hbWUsXG4gICAgICAgICAgICBwcm9kdWN0X25hbWUgPSBwcm9kdWN0X25hbWUsXG4gICAgICAgICAgICBtaWxsaXMgPSBtaWxsaXMsXG4gICAgICAgIH1c + biAgICB9LFxufVxuIiwiZmVlZGJhY2tUeXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjYwLCJmeEFuY2hvciI6Imluc3RhbmNlIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6IlNReW9qMHRLVXROOC1jZndSZ283YSIsIm5hbWUiOiJTZXQgaW5zdGFuY2UgRlgiLCJncm91cElkIjoibWFudWFsIiwic291cmNlIjp7ImNhdGVn + b3J5IjoibmV2ZXIifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo1 + NCwidHJhY2tHVUlEIjoiOEM2RjlEREMtQTVFNC1EQzQ5LTgwRTctQzc5QTM3M0ZBNTFCIiwiZnhBbmNob3IiOiJpbmRleCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiZnhUb29sQWN0aW9uIjoiU2V0QXNJbnN0YW5jZUZ4IiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiUkNuamtiOFc1cjl5a1BHNFhzblFZIiwibmFtZSI6IlVwIC0gUHJldmlvdXMgbWFjcm8g + YmFuayIsImdyb3VwSWQiOiJtYWNyby1iYW5rcyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjo4MSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnsidHlwZSI6MSwibWluU3RlcFNpemUiOjAuMDEwMTAxMDEwMTAxMDEwMTAyLCJtYXhTdGVwU2l6ZSI6MC4wMTAxMDEwMTAxMDEwMTAxMDIsIm1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsInR1cmJvUmF0ZSI6MTUwLCJyZXZlcnNlSXNFbmFibGVkIjp0cnVlLCJmaXJlTW9kZSI6InR1cmJvIn0sInRhcmdldCI6 + eyJmeEFuY2hvciI6InRoaXMiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtSW5kZXgiOjEsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlR5MW9vMzVFRnZBZ2xxR2VlSVJjUyIsIm5hbWUiOiJVcCBMRUQgLSBQcmV2aW91cyBt + YWNybyBiYW5rIiwiZ3JvdXBJZCI6Im1hY3JvLWJhbmtzIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjgxLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC45NCwibWF4VGFyZ2V0VmFsdWUiOjAuMDEwMTAxMDEwMTAxMDEwMTAyLCJtaW5TdGVwU2l6ZSI6MC4wMTAxMDEw + MTAxMDEwMTAxMDIsIm1heFN0ZXBTaXplIjowLjAxMDEwMTAxMDEwMTAxMDEwMiwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJm + eEFuY2hvciI6InRoaXMiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtSW5kZXgiOjEsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMHNHVUFEclhsRmhtSDdqNjBoZllJIiwibmFtZSI6IkRvd24gLSBOZXh0IG1hY3JvIGJhbmsi + LCJncm91cElkIjoibWFjcm8tYmFua3MiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MTUsIm51bWJlciI6ODIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7InR5cGUiOjEsIm1pblN0ZXBTaXplIjowLjAxMDEwMTAxMDEwMTAxMDEwMiwibWF4U3RlcFNpemUiOjAuMDEwMTAxMDEwMTAxMDEwMTAyLCJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJ0dXJib1JhdGUiOjE1MCwiZmlyZU1vZGUiOiJ0dXJibyJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJ0aGlzIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbUluZGV4IjoxLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJaajJjMzF2dnJOTDFvWlVzVHd0cl8iLCJuYW1lIjoiTWFjcm8gYmFuayBkaXNwbGF5IiwiZ3JvdXBJZCI6Im1hY3JvLWJhbmtzIiwic291 + cmNlIjp7InR5cGUiOjExLCJtaWRpU2NyaXB0S2luZCI6Imx1YSIsIm1pZGlTY3JpcHQiOiItLSAjIyBDb25zdGFudHMgIyNcblxubG9jYWwgY29sdW1uX2tub2JfYWRk + cmVzc19vZmZzZXQgPSA1MDAwXG5sb2NhbCBsZWRfYWRkcmVzc19vZmZzZXQgPSAxMDAwMFxubG9jYWwgYmxhY2sgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxubG9j + YWwgd2hpdGUgPSB7IHIgPSAyNTUsIGcgPSAyNTUsIGIgPSAyNTUgfVxuXG4tLSAjIyBGdW5jdGlvbnMgIyNcblxuZnVuY3Rpb24gdG9fYXNjaWkodGV4dClcbiAgICBs + b2NhbCBieXRlcyA9IHt9XG4gICAgaWYgdGV4dCB+PSBuaWwgdGhlblxuICAgICAgICBmb3IgaSA9IDEsIHN0cmluZy5sZW4odGV4dCkgZG9cbiAgICAgICAgICAgIGxv + Y2FsIGJ5dGUgPSBzdHJpbmcuYnl0ZSh0ZXh0LCBpKVxuICAgICAgICAgICAgaWYgYnl0ZSA8IDEyOCB0aGVuXG4gICAgICAgICAgICAgICAgdGFibGUuaW5zZXJ0KGJ5 + dGVzLCBieXRlKVxuICAgICAgICAgICAgZW5kXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIC0tIE51bGwgdGVybWluYXRvclxuICAgIHRhYmxlLmluc2VydChieXRl + cywgMClcbiAgICByZXR1cm4gYnl0ZXNcbmVuZFxuXG5mdW5jdGlvbiBjb25jYXRfdGFibGUodDEsIHQyKVxuICAgIGZvciBpID0gMSwgI3QyIGRvXG4gICAgICAgIHQx + WyN0MSArIDFdID0gdDJbaV1cbiAgICBlbmRcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbXNnKGNvbnRlbnQpXG4gICAgLS0gU3lzRXggSGVhZGVyXG4gICAgbG9jYWwg + bXNnID0ge1xuICAgICAgICAweGYwLCAweDAwLCAweDIwLCAweDI5LCAweDAyLCAweDBhLCAweDAxLFxuICAgIH1cbiAgICAtLSBDb250ZW50XG4gICAgY29uY2F0X3Rh + YmxlKG1zZywgY29udGVudClcbiAgICAtLSBFbmQgb2YgU3lzRXhcbiAgICB0YWJsZS5pbnNlcnQobXNnLCAweGY3KVxuICAgIHJldHVybiBtc2dcbmVuZFxuXG4tLS0g + Q3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBsYXlvdXQuXG4tLS1cbi0tLSBAcGFyYW0gbGF5b3V0X2luZGV4IG51bWJlciBsYXlvdXQgaW5kZXggKDAg + PSBlbXB0eSwgMSA9IGtub2IsIDIgPSBib3gpXG5mdW5jdGlvbiBjcmVhdGVfc2NyZWVuX2xheW91dF9tc2cobGF5b3V0X2luZGV4KVxuICAgIHJldHVybiBjcmVhdGVf + bXNnKHsgMHgwMSwgbGF5b3V0X2luZGV4IH0pXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBkaXNwbGF5aW5nIGEgbm90aWZpY2F0aW9uIG9uIHRoZSBj + ZW50ZXIgc2NyZWVuLlxuLS0tXG4tLS0gQHBhcmFtIGxpbmUxIHN0cmluZyBmaXJzdCBsaW5lIG9mIHRleHRcbi0tLSBAcGFyYW0gbGluZTIgc3RyaW5nIHNlY29uZCBs + aW5lIG9mIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV9ub3RpZmljYXRpb25fdGV4dF9tc2cobGluZTEsIGxpbmUyKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDQgfVxu + ICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMSkpXG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUyKSlcbiAgICByZXR1 + cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgbXVsdGlwbGUgc2NyZWVuIHByb3BlcnRpZXMuXG4t + LS1cbi0tLSBAcGFyYW0gY2hhbmdlcyB0YWJsZSBsaXN0IG9mIHByb3BlcnR5IGNoYW5nZXNcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fcHJvcHNfbXNnKGNoYW5nZXMp + XG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwMiB9XG4gICAgZm9yIGkgPSAxLCAjY2hhbmdlcyBkb1xuICAgICAgICBpZiBjaGFuZ2VzW2ldIH49IG5pbCB0aGVuXG4g + ICAgICAgICAgICBjb25jYXRfdGFibGUoY29udGVudCwgY2hhbmdlc1tpXSlcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29udGVu + dClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHRleHQgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29s + dW1uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4t + LS0gQHBhcmFtIHRleHQgc3RyaW5nIHRoZSBhY3R1YWwgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5k + ZXgsIHRleHQpXG4gICAgbG9jYWwgY2hhbmdlID0ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9w + ZXJ0eSBUeXBlIFwiVGV4dFwiXG4gICAgICAgIDB4MDEsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgfVxuICAgIGNv + bmNhdF90YWJsZShjaGFuZ2UsIHRvX2FzY2lpKHRleHQpKVxuICAgIHJldHVybiBjaGFuZ2VcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHZhbHVlIHByb3BlcnR5IGNoYW5n + ZS5cbi0tLVxuLS0tIElmIGl0J3MgYWJvdXQgc2V0dGluZyB0aGUga25vYiB2YWx1ZSwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIG5vdFxuLS0tIGRvIHRo + aXMgdmlhIHNjcmlwdCBidXQgYnkgZW5hYmxpbmcgZmVlZGJhY2sgb24gdGhlIGNvcnJlc3BvbmRpbmcgZW5jb2RlclxuLS0tIG1hcHBpbmdzLCB0aGVuIHlvdSBuZWVk + IGp1c3Qgb25lIG1hcHBpbmcgZm9yIGJvdGggY29udHJvbCBhbmQgZmVlZGJhY2suXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBj + b2x1bW4gdG8gY2hhbmdlIHRoZSB2YWx1ZVxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIHRoZSBraW5kIG9mIHZhbHVlIHRvIGNoYW5nZSAoc2VlIHByb2dy + YW1tZXIncyBndWlkZSlcbi0tLSBAcGFyYW0gdmFsdWUgbnVtYmVyIHRoZSB2YWx1ZVxuZnVuY3Rpb24gY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRl + eCwgb2JqZWN0X2luZGV4LCB2YWx1ZSlcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAt + LSBQcm9wZXJ0eSBUeXBlIFwiVmFsdWVcIlxuICAgICAgICAweDAzLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgICAg + ICAtLSBDb2xvciBieXRlc1xuICAgICAgICB2YWx1ZVxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhbiBSR0IgY29sb3IgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4t + LS0gSWYgdGhlIGdpdmVuIGNvbG9yIGlzIGBuaWxgLCB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYG5pbGAuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJl + ciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGNoYW5n + ZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBj + cmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBudWxsIHRoZW5cbiAgICAg + ICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0t + IFByb3BlcnR5IFR5cGUgXCJSR0IgY29sb3JcIlxuICAgICAgICAweDA0LFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAg + ICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAgICAg + IG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBzdGF0ZSBvZiBhbiBMRUQu + XG4tLS1cbi0tLSBQYXNzaW5nIGEgYG5pbGAgY29sb3Igd2lsbCBtYWtlIHRoZSBMRUQgZ28gb2ZmLlxuLS0tXG4tLS0gQHBhcmFtIGxlZF9pbmRleCBudW1iZXIgd2hp + Y2ggTEVEIHRvIHRhbGsgdG9cbi0tLSBAcGFyYW0gbGVkX2JlaGF2aW9yIG51bWJlciBMRUQgYmVoYXZpb3IgKDEgPSBzb2xpZCwgMiA9IGZsYXNoaW5nIHdpdGggcHJl + dmlvdXNseSBzZXQgc29saWQgY29sb3IsIDMgPSBwdWxzYXRpbmcpXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVy + dGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX2xlZF9tc2cobGVkX2luZGV4LCBsZWRfYmVoYXZpb3IsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG5pbCB0 + aGVuXG4gICAgICAgIGNvbG9yID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1cbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7XG4gICAgICAgIDB4MDMsXG4g + ICAgICAgIGxlZF9pbmRleCxcbiAgICAgICAgbGVkX2JlaGF2aW9yLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihj + b2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH0pXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX2xlZnRfcmlnaHRfYW5pbWF0 + aW9uKGdsb2JhbF9taWxsaXMsIG1heF9jaGFyX2NvdW50LCBmcmFtZV9sZW5ndGgsIHRleHQpXG4gICAgaWYgdGV4dCA9PSBuaWwgdGhlblxuICAgICAgICByZXR1cm4g + bmlsXG4gICAgZW5kXG4gICAgaWYgI3RleHQgPiBtYXhfY2hhcl9jb3VudCB0aGVuXG4gICAgICAgIGxvY2FsIGZyYW1lX2NvdW50ID0gI3RleHQgLSBtYXhfY2hhcl9j + b3VudFxuICAgICAgICBsb2NhbCBmcmFtZV9pbmRleCA9IG1hdGguZmxvb3IoZ2xvYmFsX21pbGxpcyAvIGZyYW1lX2xlbmd0aCkgJSAoZnJhbWVfY291bnQgKiAyKVxu + ICAgICAgICBsb2NhbCB0ZXh0X29mZnNldFxuICAgICAgICBpZiBmcmFtZV9pbmRleCA8IGZyYW1lX2NvdW50IHRoZW5cbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0g + ZnJhbWVfaW5kZXhcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgbG9jYWwgZGlzdGFuY2UgPSBmcmFtZV9pbmRleCAtIGZyYW1lX2NvdW50XG4gICAgICAgICAgICB0 + ZXh0X29mZnNldCA9IGZyYW1lX2NvdW50IC0gZGlzdGFuY2VcbiAgICAgICAgZW5kXG4gICAgICAgIHJldHVybiB0ZXh0OnN1Yih0ZXh0X29mZnNldCArIDEsIHRleHRf + b2Zmc2V0ICsgbWF4X2NoYXJfY291bnQpXG4gICAgZWxzZVxuICAgICAgICByZXR1cm4gdGV4dFxuICAgIGVuZFxuZW5kXG5cbi0tICMjIENvZGUgIyNcbmxvY2FsIGNv + bHVtbiA9IDhcbnJldHVybiB7XG4gICAgYWRkcmVzcyA9IGNvbHVtbiArIDEsXG4gICAgbWVzc2FnZXMgPSB7XG4gICAgICAgIGNyZWF0ZV9zY3JlZW5fcHJvcHNfbXNn + KHtcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgeSksXG4gICAgICAgIH0pLFxuICAgIH1cbn0gIn0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6ImxvY2FsIGJhbmsgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQudGV4 + dF92YWx1ZVwiKVxucmV0dXJuIHtcbiAgICBmZWVkYmFja19ldmVudCA9IHtcbiAgICAgICAgdmFsdWUgPSBcIkJhbms6IFwiIC4uIChiYW5rICsgMSlcbiAgICB9LFxu + fVxuIiwiZmVlZGJhY2tUeXBlIjoyfSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwicGFyYW1JbmRleCI6MSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImFTdmd4ZkZUcjRSR2F1YjBUQTdERCIsIm5hbWUiOiJFbmNvZGVyIDEgLSBE + YXRhYmFzZSIsImdyb3VwSWQiOiJicm93c2UtY29sdW1ucyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjoyLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6LTEwLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6ImlkIiwidXNlU2Vs + ZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ6djBidHNjYzZwMTFobmtYRklxSGIiLCJuYW1lIjoiU2NyZWVuIDEgLSBCcm93c2UgcHJvZHVjdHMiLCJncm91cElk + IjoiYnJvd3NlLWNvbHVtbnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xu + XG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9 + IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlv + biB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0 + ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAg + ICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9y + XG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkg + PSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBT + eXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0t + IENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAg + cmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXgg + bnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5k + ZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcg + YSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJh + bSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9j + YWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9f + YXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBs + ZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3Nj + cmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNo + YW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICBy + ZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2lu + ZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9u + IHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShj + b2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5f + aW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVj + dF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVz + IGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1 + aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0g + bWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5f + aW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFs + dWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVf + cHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBj + b2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAg + ICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBw + cm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBw + YXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4g + d2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMg + ciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNv + bG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNv + bHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAg + ICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29y + KGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdp + bmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFy + YW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlk + LCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBj + b2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3Ip + XG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVf + bXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIp + LFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBj + cmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5p + bCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291 + bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3Ro + KSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAg + ICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJh + bWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3Vi + KHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxu + LS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gMFxubG9jYWwgYWN0aW9uID0geyBbXCJuYW1lXCJdID0gXCJBdmFpbGFibGVcIiwgW1wiY29sb3JcIl0gPSB7ICBb + XCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDI1NSwgIFtcImdcIl0gPSAyNTUsIH0sIFtcInRhcmdldFwiXSA9IHsgIFtcIml0ZW1fa2luZFwiXSA9IFwiSXNBdmFpbGFi + bGVcIiwgIFtcImtpbmRcIl0gPSBcIkJyb3dzZVBvdEZpbHRlckl0ZW1zXCIsIH0sIFtcImNvbnRyb2xfa2luZFwiXSA9IFwidG9nZ2xlXCIsfVxubG9jYWwgYXZhaWxh + YmxlID0geSBhbmQgeS5hdmFpbGFibGUgb3IgZmFsc2VcbmxvY2FsIGxhYmVsID0gYXZhaWxhYmxlIGFuZCB5LmxhYmVsIG9yIFwiXCJcbmxvY2FsIG5hbWVfMSA9IGF2 + YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEsIDkpIG9yIFwiXCJcbmxvY2FsIG5hbWVfMiA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEw + LCAxOCkgb3IgXCJcIlxubG9jYWwgbmFtZV8zID0gYXZhaWxhYmxlIGFuZCBzdHJpbmcuc3ViKHkubmFtZSwgMTksIDI3KSBvciBcIlwiXG5sb2NhbCBwcm9ncmVzcyA9 + IChhdmFpbGFibGUgYW5kIHkuaW5kZXggYW5kIHkuY291bnQpIGFuZCAoXCJcIiAuLiAoeS5pbmRleCArIDEpIC4uIFwiL1wiIC4uIHkuY291bnQpIG9yIFwiXCJcbmxv + Y2FsIHNlY29uZGFyeV9wcm9wID0gYXZhaWxhYmxlIGFuZCB5LnNlY29uZGFyeV9wcm9wIG9yIFwiXCJcbmxvY2FsIGNvbG9yID0gYXZhaWxhYmxlIGFuZCAoY29udGV4 + dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIGFjdGlvbl9uYW1lID0gYXZhaWxhYmxlIGFuZCBhY3Rpb24gYW5kIGFjdGlvbi5u + YW1lIG9yIG5pbFxubG9jYWwgYWN0aW9uX2NvbG9yID0gYXZhaWxhYmxlIGFuZCAoYWN0aW9uIGFuZCBhY3Rpb24uY29sb3Igb3IgbmlsKSBvciBibGFja1xucmV0dXJu + IHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICAtLSBI + ZWFkZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBjb2xvciksXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJv + cF9jaGFuZ2UoY29sdW1uLCAwLCAxKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgbGFiZWwpLFxuICAgICAgICAgICAgY3Jl + YXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBwcm9ncmVzcyksXG4gICAgICAgICAgICAtLSBDb250ZW50XG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9w + X2NoYW5nZShjb2x1bW4sIDIsIG5hbWVfMSksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDMsIG5hbWVfMiksXG4gICAgICAgICAg + ICAtLSBGb290ZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAyLCBhY3Rpb25fY29sb3IpLFxuICAgICAgICAgICAgY3Jl + YXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgMCksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDQsIFwiXCIpLFxuICAg + ICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCA1LCBhY3Rpb25fbmFtZSBvciBzZWNvbmRhcnlfcHJvcCksXG4gICAgICAgIH0pLFxuICAgIH1c + bn1cbiJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJsb2NhbCBpbmRleCA9IGNv + bnRleHQucHJvcChcInRhcmdldC5kaXNjcmV0ZV92YWx1ZVwiKVxubG9jYWwgY291bnQgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZGlzY3JldGVfdmFsdWVfY291bnRc + IilcbmxvY2FsIG5hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQudGV4dF92YWx1ZVwiKSBvciBcIi1cIlxubG9jYWwgc2Vjb25kYXJ5X3Byb3Bfa2V5ID0gbmlsXG5s + b2NhbCBzZWNvbmRhcnlfcHJvcF92YWx1ZSA9IHNlY29uZGFyeV9wcm9wX2tleSBhbmQgY29udGV4dC5wcm9wKHNlY29uZGFyeV9wcm9wX2tleSkgb3IgXCJcIlxubG9j + YWwgYXZhaWxhYmxlID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LmF2YWlsYWJsZVwiKVxucmV0dXJuIHtcbiAgICBmZWVkYmFja19ldmVudCA9IHtcbiAgICAgICAgY29s + b3IgPSB7IFtcInJcIl0gPSAzMywgW1wiYlwiXSA9IDI0MywgW1wiZ1wiXSA9IDE1MCx9LFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIGxhYmVsID0gXCJE + YXRhYmFzZVwiLFxuICAgICAgICAgICAgbmFtZSA9IG5hbWUsXG4gICAgICAgICAgICBzZWNvbmRhcnlfcHJvcCA9IHNlY29uZGFyeV9wcm9wX3ZhbHVlLFxuICAgICAg + ICAgICAgYXZhaWxhYmxlID0gYXZhaWxhYmxlLFxuICAgICAgICAgICAgaW5kZXggPSBpbmRleCxcbiAgICAgICAgICAgIGNvdW50ID0gY291bnQsXG4gICAgICAgIH1c + biAgICB9LFxufVxuIiwiZmVlZGJhY2tUeXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6IjloZll4dmRNd2xhVWtCcWhwNkI3ayIsIm5hbWUiOiJBdmFpbGFibGUgKGNvbnRyb2wpIiwiZ3JvdXBJZCI6ImJyb3dzZS1hY3Rpb25zIiwic291cmNlIjp7 + ImNoYW5uZWwiOjE1LCJudW1iZXIiOjUxLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJ0eXBlIjoyLCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiSXNBdmFpbGFibGUifSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoic0tEU3pmWk1iUUtMbWNVT0JKODktIiwibmFtZSI6IkF2YWlsYWJsZSAoZmVlZGJhY2spIiwiZ3JvdXBJZCI6ImJyb3dzZS1h + Y3Rpb25zIiwic291cmNlIjp7InR5cGUiOjExLCJtaWRpU2NyaXB0S2luZCI6Imx1YSIsIm1pZGlTY3JpcHQiOiItLSAjIyBDb25zdGFudHMgIyNcblxubG9jYWwgY29s + dW1uX2tub2JfYWRkcmVzc19vZmZzZXQgPSA1MDAwXG5sb2NhbCBsZWRfYWRkcmVzc19vZmZzZXQgPSAxMDAwMFxubG9jYWwgYmxhY2sgPSB7IHIgPSAwLCBnID0gMCwg + YiA9IDAgfVxubG9jYWwgd2hpdGUgPSB7IHIgPSAyNTUsIGcgPSAyNTUsIGIgPSAyNTUgfVxuXG4tLSAjIyBGdW5jdGlvbnMgIyNcblxuZnVuY3Rpb24gdG9fYXNjaWko + dGV4dClcbiAgICBsb2NhbCBieXRlcyA9IHt9XG4gICAgaWYgdGV4dCB+PSBuaWwgdGhlblxuICAgICAgICBmb3IgaSA9IDEsIHN0cmluZy5sZW4odGV4dCkgZG9cbiAg + ICAgICAgICAgIGxvY2FsIGJ5dGUgPSBzdHJpbmcuYnl0ZSh0ZXh0LCBpKVxuICAgICAgICAgICAgaWYgYnl0ZSA8IDEyOCB0aGVuXG4gICAgICAgICAgICAgICAgdGFi + bGUuaW5zZXJ0KGJ5dGVzLCBieXRlKVxuICAgICAgICAgICAgZW5kXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIC0tIE51bGwgdGVybWluYXRvclxuICAgIHRhYmxl + Lmluc2VydChieXRlcywgMClcbiAgICByZXR1cm4gYnl0ZXNcbmVuZFxuXG5mdW5jdGlvbiBjb25jYXRfdGFibGUodDEsIHQyKVxuICAgIGZvciBpID0gMSwgI3QyIGRv + XG4gICAgICAgIHQxWyN0MSArIDFdID0gdDJbaV1cbiAgICBlbmRcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbXNnKGNvbnRlbnQpXG4gICAgLS0gU3lzRXggSGVhZGVy + XG4gICAgbG9jYWwgbXNnID0ge1xuICAgICAgICAweGYwLCAweDAwLCAweDIwLCAweDI5LCAweDAyLCAweDBhLCAweDAxLFxuICAgIH1cbiAgICAtLSBDb250ZW50XG4g + ICAgY29uY2F0X3RhYmxlKG1zZywgY29udGVudClcbiAgICAtLSBFbmQgb2YgU3lzRXhcbiAgICB0YWJsZS5pbnNlcnQobXNnLCAweGY3KVxuICAgIHJldHVybiBtc2dc + bmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBsYXlvdXQuXG4tLS1cbi0tLSBAcGFyYW0gbGF5b3V0X2luZGV4IG51bWJlciBsYXlv + dXQgaW5kZXggKDAgPSBlbXB0eSwgMSA9IGtub2IsIDIgPSBib3gpXG5mdW5jdGlvbiBjcmVhdGVfc2NyZWVuX2xheW91dF9tc2cobGF5b3V0X2luZGV4KVxuICAgIHJl + dHVybiBjcmVhdGVfbXNnKHsgMHgwMSwgbGF5b3V0X2luZGV4IH0pXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBkaXNwbGF5aW5nIGEgbm90aWZpY2F0 + aW9uIG9uIHRoZSBjZW50ZXIgc2NyZWVuLlxuLS0tXG4tLS0gQHBhcmFtIGxpbmUxIHN0cmluZyBmaXJzdCBsaW5lIG9mIHRleHRcbi0tLSBAcGFyYW0gbGluZTIgc3Ry + aW5nIHNlY29uZCBsaW5lIG9mIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV9ub3RpZmljYXRpb25fdGV4dF9tc2cobGluZTEsIGxpbmUyKVxuICAgIGxvY2FsIGNvbnRlbnQg + PSB7IDB4MDQgfVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMSkpXG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUy + KSlcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgbXVsdGlwbGUgc2NyZWVuIHBy + b3BlcnRpZXMuXG4tLS1cbi0tLSBAcGFyYW0gY2hhbmdlcyB0YWJsZSBsaXN0IG9mIHByb3BlcnR5IGNoYW5nZXNcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fcHJvcHNf + bXNnKGNoYW5nZXMpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwMiB9XG4gICAgZm9yIGkgPSAxLCAjY2hhbmdlcyBkb1xuICAgICAgICBpZiBjaGFuZ2VzW2ldIH49 + IG5pbCB0aGVuXG4gICAgICAgICAgICBjb25jYXRfdGFibGUoY29udGVudCwgY2hhbmdlc1tpXSlcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0 + ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHRleHQgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIg + aW4gd2hpY2ggY29sdW1uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBkaXNwbGF5 + IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIHRleHQgc3RyaW5nIHRoZSBhY3R1YWwgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4 + LCBvYmplY3RfaW5kZXgsIHRleHQpXG4gICAgbG9jYWwgY2hhbmdlID0ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAg + ICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiVGV4dFwiXG4gICAgICAgIDB4MDEsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4g + ICAgfVxuICAgIGNvbmNhdF90YWJsZShjaGFuZ2UsIHRvX2FzY2lpKHRleHQpKVxuICAgIHJldHVybiBjaGFuZ2VcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHZhbHVlIHBy + b3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIElmIGl0J3MgYWJvdXQgc2V0dGluZyB0aGUga25vYiB2YWx1ZSwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIG5v + dFxuLS0tIGRvIHRoaXMgdmlhIHNjcmlwdCBidXQgYnkgZW5hYmxpbmcgZmVlZGJhY2sgb24gdGhlIGNvcnJlc3BvbmRpbmcgZW5jb2RlclxuLS0tIG1hcHBpbmdzLCB0 + aGVuIHlvdSBuZWVkIGp1c3Qgb25lIG1hcHBpbmcgZm9yIGJvdGggY29udHJvbCBhbmQgZmVlZGJhY2suXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJl + ciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSB2YWx1ZVxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIHRoZSBraW5kIG9mIHZhbHVlIHRvIGNoYW5n + ZSAoc2VlIHByb2dyYW1tZXIncyBndWlkZSlcbi0tLSBAcGFyYW0gdmFsdWUgbnVtYmVyIHRoZSB2YWx1ZVxuZnVuY3Rpb24gY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdl + KGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCB2YWx1ZSlcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4 + LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiVmFsdWVcIlxuICAgICAgICAweDAzLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2lu + ZGV4LFxuICAgICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICB2YWx1ZVxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhbiBSR0IgY29sb3IgcHJvcGVydHkgY2hh + bmdlLlxuLS0tXG4tLS0gSWYgdGhlIGdpdmVuIGNvbG9yIGlzIGBuaWxgLCB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYG5pbGAuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1u + X2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0 + aW9uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIp + XG5mdW5jdGlvbiBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBudWxs + IHRoZW5cbiAgICAgICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgs + XG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJSR0IgY29sb3JcIlxuICAgICAgICAweDA0LFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0 + X2luZGV4LFxuICAgICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8g + MiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBzdGF0 + ZSBvZiBhbiBMRUQuXG4tLS1cbi0tLSBQYXNzaW5nIGEgYG5pbGAgY29sb3Igd2lsbCBtYWtlIHRoZSBMRUQgZ28gb2ZmLlxuLS0tXG4tLS0gQHBhcmFtIGxlZF9pbmRl + eCBudW1iZXIgd2hpY2ggTEVEIHRvIHRhbGsgdG9cbi0tLSBAcGFyYW0gbGVkX2JlaGF2aW9yIG51bWJlciBMRUQgYmVoYXZpb3IgKDEgPSBzb2xpZCwgMiA9IGZsYXNo + aW5nIHdpdGggcHJldmlvdXNseSBzZXQgc29saWQgY29sb3IsIDMgPSBwdWxzYXRpbmcpXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxl + IHdpdGggcHJvcGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX2xlZF9tc2cobGVkX2luZGV4LCBsZWRfYmVoYXZpb3IsIGNvbG9yKVxuICAgIGlmIGNv + bG9yID09IG5pbCB0aGVuXG4gICAgICAgIGNvbG9yID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1cbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7XG4gICAg + ICAgIDB4MDMsXG4gICAgICAgIGxlZF9pbmRleCxcbiAgICAgICAgbGVkX2JlaGF2aW9yLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAg + bWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH0pXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX2xlZnRf + cmlnaHRfYW5pbWF0aW9uKGdsb2JhbF9taWxsaXMsIG1heF9jaGFyX2NvdW50LCBmcmFtZV9sZW5ndGgsIHRleHQpXG4gICAgaWYgdGV4dCA9PSBuaWwgdGhlblxuICAg + ICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgaWYgI3RleHQgPiBtYXhfY2hhcl9jb3VudCB0aGVuXG4gICAgICAgIGxvY2FsIGZyYW1lX2NvdW50ID0gI3RleHQg + LSBtYXhfY2hhcl9jb3VudFxuICAgICAgICBsb2NhbCBmcmFtZV9pbmRleCA9IG1hdGguZmxvb3IoZ2xvYmFsX21pbGxpcyAvIGZyYW1lX2xlbmd0aCkgJSAoZnJhbWVf + Y291bnQgKiAyKVxuICAgICAgICBsb2NhbCB0ZXh0X29mZnNldFxuICAgICAgICBpZiBmcmFtZV9pbmRleCA8IGZyYW1lX2NvdW50IHRoZW5cbiAgICAgICAgICAgIHRl + eHRfb2Zmc2V0ID0gZnJhbWVfaW5kZXhcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgbG9jYWwgZGlzdGFuY2UgPSBmcmFtZV9pbmRleCAtIGZyYW1lX2NvdW50XG4g + ICAgICAgICAgICB0ZXh0X29mZnNldCA9IGZyYW1lX2NvdW50IC0gZGlzdGFuY2VcbiAgICAgICAgZW5kXG4gICAgICAgIHJldHVybiB0ZXh0OnN1Yih0ZXh0X29mZnNl + dCArIDEsIHRleHRfb2Zmc2V0ICsgbWF4X2NoYXJfY291bnQpXG4gICAgZWxzZVxuICAgICAgICByZXR1cm4gdGV4dFxuICAgIGVuZFxuZW5kXG5cbi0tICMjIENvZGUg + IyNcbmxvY2FsIGNvbHVtbiA9IDBcbmxvY2FsIGNvbG9yID0gKHkgYW5kIHkgfj0gMCkgYW5kIHsgW1wiclwiXSA9IDI1NSwgW1wiYlwiXSA9IDI1NSwgW1wiZ1wiXSA9 + IDI1NSx9IG9yIG5pbFxuXG5sb2NhbCBsZWRfaW5kZXggPSA0ICsgY29sdW1uXG5yZXR1cm4ge1xuICAgIGFkZHJlc3MgPSBsZWRfYWRkcmVzc19vZmZzZXQgKyBsZWRf + aW5kZXgsXG4gICAgbWVzc2FnZXMgPSB7XG4gICAgICAgIGNyZWF0ZV9sZWRfbXNnKGxlZF9pbmRleCwgMSwgY29sb3IpLFxuICAgIH1cbn1cbiJ9LCJtb2RlIjp7Im1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtpbmQiOiJJc0F2 + YWlsYWJsZSJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlBPUXh2MVBpRzhTTm9wT0oySU82QyIsIm5hbWUiOiJFbmNvZGVyIDIgLSBLaW5kIiwiZ3Jv + dXBJZCI6ImJyb3dzZS1jb2x1bW5zIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjIyLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjotMTAsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtpbmQi + OiJQcm9kdWN0S2luZCJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJfV093a3N4MGx0aVZMQmVSVjVrVF8iLCJuYW1lIjoiU2NyZWVuIDIgLSBCcm93 + c2UgcHJvZHVjdHMiLCJncm91cElkIjoiYnJvd3NlLWNvbHVtbnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6 + Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAw + XG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1 + bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZv + ciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRl + IDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4g + ICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJs + ZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9t + c2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEs + IDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmlu + c2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0t + IEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5f + bGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1l + c3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxp + bmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhs + aW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25j + YXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdl + IGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdl + c1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFu + Z2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAg + ICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1c + bi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVt + YmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVh + dGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJ + bmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0 + IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5n + ZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBp + dCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVz + cG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0t + LVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBu + dW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5m + dW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENv + bHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0t + IE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBD + cmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJu + cyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9i + amVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0 + YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5k + ZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29s + dW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAg + IC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIp + LFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVz + IGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBn + byBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExF + RCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0g + Y29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgs + IGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVu + ZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1h + dGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAg + fSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4 + dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAg + ICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxf + bWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwg + ZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5j + ZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAg + ICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0 + ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gMVxubG9jYWwgYWN0aW9uID0geyBbXCJuYW1lXCJdID0gXCJTdXBwb3J0ZWRc + IiwgW1wiY29sb3JcIl0gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDI1NSwgIFtcImdcIl0gPSAyNTUsIH0sIFtcInRhcmdldFwiXSA9IHsgIFtcIml0ZW1f + a2luZFwiXSA9IFwiSXNTdXBwb3J0ZWRcIiwgIFtcImtpbmRcIl0gPSBcIkJyb3dzZVBvdEZpbHRlckl0ZW1zXCIsIH0sIFtcImNvbnRyb2xfa2luZFwiXSA9IFwidG9n + Z2xlXCIsfVxubG9jYWwgYXZhaWxhYmxlID0geSBhbmQgeS5hdmFpbGFibGUgb3IgZmFsc2VcbmxvY2FsIGxhYmVsID0gYXZhaWxhYmxlIGFuZCB5LmxhYmVsIG9yIFwi + XCJcbmxvY2FsIG5hbWVfMSA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEsIDkpIG9yIFwiXCJcbmxvY2FsIG5hbWVfMiA9IGF2YWlsYWJsZSBhbmQg + c3RyaW5nLnN1Yih5Lm5hbWUsIDEwLCAxOCkgb3IgXCJcIlxubG9jYWwgbmFtZV8zID0gYXZhaWxhYmxlIGFuZCBzdHJpbmcuc3ViKHkubmFtZSwgMTksIDI3KSBvciBc + IlwiXG5sb2NhbCBwcm9ncmVzcyA9IChhdmFpbGFibGUgYW5kIHkuaW5kZXggYW5kIHkuY291bnQpIGFuZCAoXCJcIiAuLiAoeS5pbmRleCArIDEpIC4uIFwiL1wiIC4u + IHkuY291bnQpIG9yIFwiXCJcbmxvY2FsIHNlY29uZGFyeV9wcm9wID0gYXZhaWxhYmxlIGFuZCB5LnNlY29uZGFyeV9wcm9wIG9yIFwiXCJcbmxvY2FsIGNvbG9yID0g + YXZhaWxhYmxlIGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIGFjdGlvbl9uYW1lID0gYXZhaWxhYmxlIGFu + ZCBhY3Rpb24gYW5kIGFjdGlvbi5uYW1lIG9yIG5pbFxubG9jYWwgYWN0aW9uX2NvbG9yID0gYXZhaWxhYmxlIGFuZCAoYWN0aW9uIGFuZCBhY3Rpb24uY29sb3Igb3Ig + bmlsKSBvciBibGFja1xucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21z + Zyh7XG4gICAgICAgICAgICAtLSBIZWFkZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBjb2xvciksXG4gICAgICAg + ICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCAxKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgbGFi + ZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBwcm9ncmVzcyksXG4gICAgICAgICAgICAtLSBDb250ZW50XG4gICAgICAg + ICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDIsIG5hbWVfMSksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDMs + IG5hbWVfMiksXG4gICAgICAgICAgICAtLSBGb290ZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAyLCBhY3Rpb25fY29s + b3IpLFxuICAgICAgICAgICAgY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgMCksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShj + b2x1bW4sIDQsIFwiXCIpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCA1LCBhY3Rpb25fbmFtZSBvciBzZWNvbmRhcnlfcHJvcCks + XG4gICAgICAgIH0pLFxuICAgIH1cbn1cbiJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRp + b24iOiJsb2NhbCBpbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5kaXNjcmV0ZV92YWx1ZVwiKVxubG9jYWwgY291bnQgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQu + ZGlzY3JldGVfdmFsdWVfY291bnRcIilcbmxvY2FsIG5hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQudGV4dF92YWx1ZVwiKSBvciBcIi1cIlxubG9jYWwgc2Vjb25k + YXJ5X3Byb3Bfa2V5ID0gbmlsXG5sb2NhbCBzZWNvbmRhcnlfcHJvcF92YWx1ZSA9IHNlY29uZGFyeV9wcm9wX2tleSBhbmQgY29udGV4dC5wcm9wKHNlY29uZGFyeV9w + cm9wX2tleSkgb3IgXCJcIlxubG9jYWwgYXZhaWxhYmxlID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LmF2YWlsYWJsZVwiKVxucmV0dXJuIHtcbiAgICBmZWVkYmFja19l + dmVudCA9IHtcbiAgICAgICAgY29sb3IgPSB7IFtcInJcIl0gPSAzMywgW1wiYlwiXSA9IDI0MywgW1wiZ1wiXSA9IDE1MCx9LFxuICAgICAgICB2YWx1ZSA9IHtcbiAg + ICAgICAgICAgIGxhYmVsID0gXCJLaW5kXCIsXG4gICAgICAgICAgICBuYW1lID0gbmFtZSxcbiAgICAgICAgICAgIHNlY29uZGFyeV9wcm9wID0gc2Vjb25kYXJ5X3By + b3BfdmFsdWUsXG4gICAgICAgICAgICBhdmFpbGFibGUgPSBhdmFpbGFibGUsXG4gICAgICAgICAgICBpbmRleCA9IGluZGV4LFxuICAgICAgICAgICAgY291bnQgPSBj + b3VudCxcbiAgICAgICAgfVxuICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVy + SXRlbUtpbmQiOiJQcm9kdWN0S2luZCJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlNNY1lSYXVxenMxMGo0UURvbVBaWSIsIm5hbWUiOiJTdXBwb3J0 + ZWQgKGNvbnRyb2wpIiwiZ3JvdXBJZCI6ImJyb3dzZS1hY3Rpb25zIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjUyLCJjaGFyYWN0ZXIiOjEsImlzMTRC + aXQiOmZhbHNlfSwibW9kZSI6eyJ0eXBlIjoyLCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlk + IjoiIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiSXNTdXBwb3J0ZWQifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiakdza2Z5eFVyekQwbHlLdGc2d1h2 + IiwibmFtZSI6IlN1cHBvcnRlZCAoZmVlZGJhY2spIiwiZ3JvdXBJZCI6ImJyb3dzZS1hY3Rpb25zIiwic291cmNlIjp7InR5cGUiOjExLCJtaWRpU2NyaXB0S2luZCI6 + Imx1YSIsIm1pZGlTY3JpcHQiOiItLSAjIyBDb25zdGFudHMgIyNcblxubG9jYWwgY29sdW1uX2tub2JfYWRkcmVzc19vZmZzZXQgPSA1MDAwXG5sb2NhbCBsZWRfYWRk + cmVzc19vZmZzZXQgPSAxMDAwMFxubG9jYWwgYmxhY2sgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxubG9jYWwgd2hpdGUgPSB7IHIgPSAyNTUsIGcgPSAyNTUsIGIg + PSAyNTUgfVxuXG4tLSAjIyBGdW5jdGlvbnMgIyNcblxuZnVuY3Rpb24gdG9fYXNjaWkodGV4dClcbiAgICBsb2NhbCBieXRlcyA9IHt9XG4gICAgaWYgdGV4dCB+PSBu + aWwgdGhlblxuICAgICAgICBmb3IgaSA9IDEsIHN0cmluZy5sZW4odGV4dCkgZG9cbiAgICAgICAgICAgIGxvY2FsIGJ5dGUgPSBzdHJpbmcuYnl0ZSh0ZXh0LCBpKVxu + ICAgICAgICAgICAgaWYgYnl0ZSA8IDEyOCB0aGVuXG4gICAgICAgICAgICAgICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCBieXRlKVxuICAgICAgICAgICAgZW5kXG4gICAg + ICAgIGVuZFxuICAgIGVuZFxuICAgIC0tIE51bGwgdGVybWluYXRvclxuICAgIHRhYmxlLmluc2VydChieXRlcywgMClcbiAgICByZXR1cm4gYnl0ZXNcbmVuZFxuXG5m + dW5jdGlvbiBjb25jYXRfdGFibGUodDEsIHQyKVxuICAgIGZvciBpID0gMSwgI3QyIGRvXG4gICAgICAgIHQxWyN0MSArIDFdID0gdDJbaV1cbiAgICBlbmRcbmVuZFxu + XG5mdW5jdGlvbiBjcmVhdGVfbXNnKGNvbnRlbnQpXG4gICAgLS0gU3lzRXggSGVhZGVyXG4gICAgbG9jYWwgbXNnID0ge1xuICAgICAgICAweGYwLCAweDAwLCAweDIw + LCAweDI5LCAweDAyLCAweDBhLCAweDAxLFxuICAgIH1cbiAgICAtLSBDb250ZW50XG4gICAgY29uY2F0X3RhYmxlKG1zZywgY29udGVudClcbiAgICAtLSBFbmQgb2Yg + U3lzRXhcbiAgICB0YWJsZS5pbnNlcnQobXNnLCAweGY3KVxuICAgIHJldHVybiBtc2dcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRo + ZSBsYXlvdXQuXG4tLS1cbi0tLSBAcGFyYW0gbGF5b3V0X2luZGV4IG51bWJlciBsYXlvdXQgaW5kZXggKDAgPSBlbXB0eSwgMSA9IGtub2IsIDIgPSBib3gpXG5mdW5j + dGlvbiBjcmVhdGVfc2NyZWVuX2xheW91dF9tc2cobGF5b3V0X2luZGV4KVxuICAgIHJldHVybiBjcmVhdGVfbXNnKHsgMHgwMSwgbGF5b3V0X2luZGV4IH0pXG5lbmRc + blxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBkaXNwbGF5aW5nIGEgbm90aWZpY2F0aW9uIG9uIHRoZSBjZW50ZXIgc2NyZWVuLlxuLS0tXG4tLS0gQHBhcmFtIGxp + bmUxIHN0cmluZyBmaXJzdCBsaW5lIG9mIHRleHRcbi0tLSBAcGFyYW0gbGluZTIgc3RyaW5nIHNlY29uZCBsaW5lIG9mIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV9ub3Rp + ZmljYXRpb25fdGV4dF9tc2cobGluZTEsIGxpbmUyKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDQgfVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2Np + aShsaW5lMSkpXG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUyKSlcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0t + LSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgbXVsdGlwbGUgc2NyZWVuIHByb3BlcnRpZXMuXG4tLS1cbi0tLSBAcGFyYW0gY2hhbmdlcyB0YWJsZSBsaXN0 + IG9mIHByb3BlcnR5IGNoYW5nZXNcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fcHJvcHNfbXNnKGNoYW5nZXMpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwMiB9XG4g + ICAgZm9yIGkgPSAxLCAjY2hhbmdlcyBkb1xuICAgICAgICBpZiBjaGFuZ2VzW2ldIH49IG5pbCB0aGVuXG4gICAgICAgICAgICBjb25jYXRfdGFibGUoY29udGVudCwg + Y2hhbmdlc1tpXSlcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHRleHQgcHJv + cGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFy + YW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIHRleHQgc3RyaW5nIHRoZSBhY3R1YWwg + dGV4dFxuZnVuY3Rpb24gY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHRleHQpXG4gICAgbG9jYWwgY2hhbmdlID0ge1xu + ICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiVGV4dFwiXG4gICAgICAgIDB4MDEs + XG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgfVxuICAgIGNvbmNhdF90YWJsZShjaGFuZ2UsIHRvX2FzY2lpKHRleHQp + KVxuICAgIHJldHVybiBjaGFuZ2VcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHZhbHVlIHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIElmIGl0J3MgYWJvdXQgc2V0dGlu + ZyB0aGUga25vYiB2YWx1ZSwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIG5vdFxuLS0tIGRvIHRoaXMgdmlhIHNjcmlwdCBidXQgYnkgZW5hYmxpbmcgZmVl + ZGJhY2sgb24gdGhlIGNvcnJlc3BvbmRpbmcgZW5jb2RlclxuLS0tIG1hcHBpbmdzLCB0aGVuIHlvdSBuZWVkIGp1c3Qgb25lIG1hcHBpbmcgZm9yIGJvdGggY29udHJv + bCBhbmQgZmVlZGJhY2suXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSB2YWx1ZVxuLS0tIEBw + YXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIHRoZSBraW5kIG9mIHZhbHVlIHRvIGNoYW5nZSAoc2VlIHByb2dyYW1tZXIncyBndWlkZSlcbi0tLSBAcGFyYW0gdmFsdWUg + bnVtYmVyIHRoZSB2YWx1ZVxuZnVuY3Rpb24gY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCB2YWx1ZSlcbiAgICByZXR1 + cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiVmFsdWVcIlxuICAgICAg + ICAweDAzLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICB2YWx1ZVxu + ICAgIH1cbmVuZFxuXG4tLS0gQ3JlYXRlcyBhbiBSR0IgY29sb3IgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgdGhlIGdpdmVuIGNvbG9yIGlzIGBuaWxgLCB0 + aGlzIGZ1bmN0aW9uIHJldHVybnMgYG5pbGAuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSBj + b2xvclxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gY29sb3IgdGFi + bGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVt + bl9pbmRleCwgb2JqZWN0X2luZGV4LCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBudWxsIHRoZW5cbiAgICAgICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIHJldHVy + biB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJSR0IgY29sb3JcIlxuICAg + ICAgICAweDA0LFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICBtYXRo + LmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH1c + bmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBzdGF0ZSBvZiBhbiBMRUQuXG4tLS1cbi0tLSBQYXNzaW5nIGEgYG5pbGAgY29sb3Ig + d2lsbCBtYWtlIHRoZSBMRUQgZ28gb2ZmLlxuLS0tXG4tLS0gQHBhcmFtIGxlZF9pbmRleCBudW1iZXIgd2hpY2ggTEVEIHRvIHRhbGsgdG9cbi0tLSBAcGFyYW0gbGVk + X2JlaGF2aW9yIG51bWJlciBMRUQgYmVoYXZpb3IgKDEgPSBzb2xpZCwgMiA9IGZsYXNoaW5nIHdpdGggcHJldmlvdXNseSBzZXQgc29saWQgY29sb3IsIDMgPSBwdWxz + YXRpbmcpXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRl + X2xlZF9tc2cobGVkX2luZGV4LCBsZWRfYmVoYXZpb3IsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG5pbCB0aGVuXG4gICAgICAgIGNvbG9yID0geyByID0gMCwgZyA9 + IDAsIGIgPSAwIH1cbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7XG4gICAgICAgIDB4MDMsXG4gICAgICAgIGxlZF9pbmRleCxcbiAgICAgICAgbGVkX2Jl + aGF2aW9yLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3Io + Y29sb3IuYiAvIDIpLFxuICAgIH0pXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX2xlZnRfcmlnaHRfYW5pbWF0aW9uKGdsb2JhbF9taWxsaXMsIG1heF9jaGFyX2NvdW50 + LCBmcmFtZV9sZW5ndGgsIHRleHQpXG4gICAgaWYgdGV4dCA9PSBuaWwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgaWYgI3RleHQgPiBtYXhf + Y2hhcl9jb3VudCB0aGVuXG4gICAgICAgIGxvY2FsIGZyYW1lX2NvdW50ID0gI3RleHQgLSBtYXhfY2hhcl9jb3VudFxuICAgICAgICBsb2NhbCBmcmFtZV9pbmRleCA9 + IG1hdGguZmxvb3IoZ2xvYmFsX21pbGxpcyAvIGZyYW1lX2xlbmd0aCkgJSAoZnJhbWVfY291bnQgKiAyKVxuICAgICAgICBsb2NhbCB0ZXh0X29mZnNldFxuICAgICAg + ICBpZiBmcmFtZV9pbmRleCA8IGZyYW1lX2NvdW50IHRoZW5cbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfaW5kZXhcbiAgICAgICAgZWxzZVxuICAgICAg + ICAgICAgbG9jYWwgZGlzdGFuY2UgPSBmcmFtZV9pbmRleCAtIGZyYW1lX2NvdW50XG4gICAgICAgICAgICB0ZXh0X29mZnNldCA9IGZyYW1lX2NvdW50IC0gZGlzdGFu + Y2VcbiAgICAgICAgZW5kXG4gICAgICAgIHJldHVybiB0ZXh0OnN1Yih0ZXh0X29mZnNldCArIDEsIHRleHRfb2Zmc2V0ICsgbWF4X2NoYXJfY291bnQpXG4gICAgZWxz + ZVxuICAgICAgICByZXR1cm4gdGV4dFxuICAgIGVuZFxuZW5kXG5cbi0tICMjIENvZGUgIyNcbmxvY2FsIGNvbHVtbiA9IDFcbmxvY2FsIGNvbG9yID0gKHkgYW5kIHkg + fj0gMCkgYW5kIHsgW1wiclwiXSA9IDI1NSwgW1wiYlwiXSA9IDI1NSwgW1wiZ1wiXSA9IDI1NSx9IG9yIG5pbFxuXG5sb2NhbCBsZWRfaW5kZXggPSA0ICsgY29sdW1u + XG5yZXR1cm4ge1xuICAgIGFkZHJlc3MgPSBsZWRfYWRkcmVzc19vZmZzZXQgKyBsZWRfaW5kZXgsXG4gICAgbWVzc2FnZXMgPSB7XG4gICAgICAgIGNyZWF0ZV9sZWRf + bXNnKGxlZF9pbmRleCwgMSwgY29sb3IpLFxuICAgIH1cbn1cbiJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsi + dHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtpbmQiOiJJc1N1cHBvcnRlZCJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Impp + bW1YeTIyOW5wbXUtajBianhKQyIsIm5hbWUiOiJFbmNvZGVyIDMgLSBQcm9kdWN0IiwiZ3JvdXBJZCI6ImJyb3dzZS1jb2x1bW5zIiwic291cmNlIjp7ImNoYW5uZWwi + OjE1LCJudW1iZXIiOjIzLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjotMTAsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtpbmQiOiJCYW5rIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + Inluc1E4MU5xTkZfME5LNUZ4QWliRyIsIm5hbWUiOiJTY3JlZW4gMyAtIEJyb3dzZSBwcm9kdWN0cyIsImdyb3VwSWQiOiJicm93c2UtY29sdW1ucyIsInNvdXJjZSI6 + eyJ0eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJsdWEiLCJtaWRpU2NyaXB0IjoiLS0gIyMgQ29uc3RhbnRzICMjXG5cbmxvY2FsIGNvbHVtbl9rbm9iX2FkZHJlc3Nf + b2Zmc2V0ID0gNTAwMFxubG9jYWwgbGVkX2FkZHJlc3Nfb2Zmc2V0ID0gMTAwMDBcbmxvY2FsIGJsYWNrID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1cbmxvY2FsIHdo + aXRlID0geyByID0gMjU1LCBnID0gMjU1LCBiID0gMjU1IH1cblxuLS0gIyMgRnVuY3Rpb25zICMjXG5cbmZ1bmN0aW9uIHRvX2FzY2lpKHRleHQpXG4gICAgbG9jYWwg + Ynl0ZXMgPSB7fVxuICAgIGlmIHRleHQgfj0gbmlsIHRoZW5cbiAgICAgICAgZm9yIGkgPSAxLCBzdHJpbmcubGVuKHRleHQpIGRvXG4gICAgICAgICAgICBsb2NhbCBi + eXRlID0gc3RyaW5nLmJ5dGUodGV4dCwgaSlcbiAgICAgICAgICAgIGlmIGJ5dGUgPCAxMjggdGhlblxuICAgICAgICAgICAgICAgIHRhYmxlLmluc2VydChieXRlcywg + Ynl0ZSlcbiAgICAgICAgICAgIGVuZFxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICAtLSBOdWxsIHRlcm1pbmF0b3JcbiAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIDAp + XG4gICAgcmV0dXJuIGJ5dGVzXG5lbmRcblxuZnVuY3Rpb24gY29uY2F0X3RhYmxlKHQxLCB0MilcbiAgICBmb3IgaSA9IDEsICN0MiBkb1xuICAgICAgICB0MVsjdDEg + KyAxXSA9IHQyW2ldXG4gICAgZW5kXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX21zZyhjb250ZW50KVxuICAgIC0tIFN5c0V4IEhlYWRlclxuICAgIGxvY2FsIG1zZyA9 + IHtcbiAgICAgICAgMHhmMCwgMHgwMCwgMHgyMCwgMHgyOSwgMHgwMiwgMHgwYSwgMHgwMSxcbiAgICB9XG4gICAgLS0gQ29udGVudFxuICAgIGNvbmNhdF90YWJsZSht + c2csIGNvbnRlbnQpXG4gICAgLS0gRW5kIG9mIFN5c0V4XG4gICAgdGFibGUuaW5zZXJ0KG1zZywgMHhmNylcbiAgICByZXR1cm4gbXNnXG5lbmRcblxuLS0tIENyZWF0 + ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgbGF5b3V0LlxuLS0tXG4tLS0gQHBhcmFtIGxheW91dF9pbmRleCBudW1iZXIgbGF5b3V0IGluZGV4ICgwID0gZW1w + dHksIDEgPSBrbm9iLCAyID0gYm94KVxuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9sYXlvdXRfbXNnKGxheW91dF9pbmRleClcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7 + IDB4MDEsIGxheW91dF9pbmRleCB9KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgZGlzcGxheWluZyBhIG5vdGlmaWNhdGlvbiBvbiB0aGUgY2VudGVy + IHNjcmVlbi5cbi0tLVxuLS0tIEBwYXJhbSBsaW5lMSBzdHJpbmcgZmlyc3QgbGluZSBvZiB0ZXh0XG4tLS0gQHBhcmFtIGxpbmUyIHN0cmluZyBzZWNvbmQgbGluZSBv + ZiB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfbm90aWZpY2F0aW9uX3RleHRfbXNnKGxpbmUxLCBsaW5lMilcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDA0IH1cbiAgICBj + b25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTEpKVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMikpXG4gICAgcmV0dXJuIGNy + ZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIG11bHRpcGxlIHNjcmVlbiBwcm9wZXJ0aWVzLlxuLS0tXG4t + LS0gQHBhcmFtIGNoYW5nZXMgdGFibGUgbGlzdCBvZiBwcm9wZXJ0eSBjaGFuZ2VzXG5mdW5jdGlvbiBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyhjaGFuZ2VzKVxuICAg + IGxvY2FsIGNvbnRlbnQgPSB7IDB4MDIgfVxuICAgIGZvciBpID0gMSwgI2NoYW5nZXMgZG9cbiAgICAgICAgaWYgY2hhbmdlc1tpXSB+PSBuaWwgdGhlblxuICAgICAg + ICAgICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIGNoYW5nZXNbaV0pXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5l + bmRcblxuLS0tIENyZWF0ZXMgYSB0ZXh0IHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0 + byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBw + YXJhbSB0ZXh0IHN0cmluZyB0aGUgYWN0dWFsIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCB0 + ZXh0KVxuICAgIGxvY2FsIGNoYW5nZSA9IHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkg + VHlwZSBcIlRleHRcIlxuICAgICAgICAweDAxLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgIH1cbiAgICBjb25jYXRf + dGFibGUoY2hhbmdlLCB0b19hc2NpaSh0ZXh0KSlcbiAgICByZXR1cm4gY2hhbmdlXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB2YWx1ZSBwcm9wZXJ0eSBjaGFuZ2UuXG4t + LS1cbi0tLSBJZiBpdCdzIGFib3V0IHNldHRpbmcgdGhlIGtub2IgdmFsdWUsIGl0J3MgdXN1YWxseSBtb3JlIGludHVpdGl2ZSB0byBub3Rcbi0tLSBkbyB0aGlzIHZp + YSBzY3JpcHQgYnV0IGJ5IGVuYWJsaW5nIGZlZWRiYWNrIG9uIHRoZSBjb3JyZXNwb25kaW5nIGVuY29kZXJcbi0tLSBtYXBwaW5ncywgdGhlbiB5b3UgbmVlZCBqdXN0 + IG9uZSBtYXBwaW5nIGZvciBib3RoIGNvbnRyb2wgYW5kIGZlZWRiYWNrLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1u + IHRvIGNoYW5nZSB0aGUgdmFsdWVcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciB0aGUga2luZCBvZiB2YWx1ZSB0byBjaGFuZ2UgKHNlZSBwcm9ncmFtbWVy + J3MgZ3VpZGUpXG4tLS0gQHBhcmFtIHZhbHVlIG51bWJlciB0aGUgdmFsdWVcbmZ1bmN0aW9uIGNyZWF0ZV92YWx1ZV9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9i + amVjdF9pbmRleCwgdmFsdWUpXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJv + cGVydHkgVHlwZSBcIlZhbHVlXCJcbiAgICAgICAgMHgwMyxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICAgICAgLS0g + Q29sb3IgYnl0ZXNcbiAgICAgICAgdmFsdWVcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYW4gUkdCIGNvbG9yIHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIElm + IHRoZSBnaXZlbiBjb2xvciBpcyBgbmlsYCwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGBuaWxgLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4g + d2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBjaGFuZ2UgdGhl + IGNvbG9yXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRl + X3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbnVsbCB0aGVuXG4gICAgICAgIHJl + dHVybiBuaWxcbiAgICBlbmRcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9w + ZXJ0eSBUeXBlIFwiUkdCIGNvbG9yXCJcbiAgICAgICAgMHgwNCxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICAgICAg + LS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRo + LmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgc3RhdGUgb2YgYW4gTEVELlxuLS0t + XG4tLS0gUGFzc2luZyBhIGBuaWxgIGNvbG9yIHdpbGwgbWFrZSB0aGUgTEVEIGdvIG9mZi5cbi0tLVxuLS0tIEBwYXJhbSBsZWRfaW5kZXggbnVtYmVyIHdoaWNoIExF + RCB0byB0YWxrIHRvXG4tLS0gQHBhcmFtIGxlZF9iZWhhdmlvciBudW1iZXIgTEVEIGJlaGF2aW9yICgxID0gc29saWQsIDIgPSBmbGFzaGluZyB3aXRoIHByZXZpb3Vz + bHkgc2V0IHNvbGlkIGNvbG9yLCAzID0gcHVsc2F0aW5nKVxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMg + ciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9sZWRfbXNnKGxlZF9pbmRleCwgbGVkX2JlaGF2aW9yLCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBuaWwgdGhlblxu + ICAgICAgICBjb2xvciA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coe1xuICAgICAgICAweDAzLFxuICAgICAg + ICBsZWRfaW5kZXgsXG4gICAgICAgIGxlZF9iZWhhdmlvcixcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3Iu + ZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9KVxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9sZWZ0X3JpZ2h0X2FuaW1hdGlvbihn + bG9iYWxfbWlsbGlzLCBtYXhfY2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB0ZXh0KVxuICAgIGlmIHRleHQgPT0gbmlsIHRoZW5cbiAgICAgICAgcmV0dXJuIG5pbFxu + ICAgIGVuZFxuICAgIGlmICN0ZXh0ID4gbWF4X2NoYXJfY291bnQgdGhlblxuICAgICAgICBsb2NhbCBmcmFtZV9jb3VudCA9ICN0ZXh0IC0gbWF4X2NoYXJfY291bnRc + biAgICAgICAgbG9jYWwgZnJhbWVfaW5kZXggPSBtYXRoLmZsb29yKGdsb2JhbF9taWxsaXMgLyBmcmFtZV9sZW5ndGgpICUgKGZyYW1lX2NvdW50ICogMilcbiAgICAg + ICAgbG9jYWwgdGV4dF9vZmZzZXRcbiAgICAgICAgaWYgZnJhbWVfaW5kZXggPCBmcmFtZV9jb3VudCB0aGVuXG4gICAgICAgICAgICB0ZXh0X29mZnNldCA9IGZyYW1l + X2luZGV4XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGxvY2FsIGRpc3RhbmNlID0gZnJhbWVfaW5kZXggLSBmcmFtZV9jb3VudFxuICAgICAgICAgICAgdGV4dF9v + ZmZzZXQgPSBmcmFtZV9jb3VudCAtIGRpc3RhbmNlXG4gICAgICAgIGVuZFxuICAgICAgICByZXR1cm4gdGV4dDpzdWIodGV4dF9vZmZzZXQgKyAxLCB0ZXh0X29mZnNl + dCArIG1heF9jaGFyX2NvdW50KVxuICAgIGVsc2VcbiAgICAgICAgcmV0dXJuIHRleHRcbiAgICBlbmRcbmVuZFxuXG4tLSAjIyBDb2RlICMjXG5sb2NhbCBjb2x1bW4g + PSAyXG5sb2NhbCBhY3Rpb24gPSB7IFtcIm5hbWVcIl0gPSBcIkZhdm9yaXRlXCIsIFtcImNvbG9yXCJdID0geyAgW1wiclwiXSA9IDI1NSwgIFtcImJcIl0gPSAyNTUs + ICBbXCJnXCJdID0gMjU1LCB9LCBbXCJ0YXJnZXRcIl0gPSB7ICBbXCJpdGVtX2tpbmRcIl0gPSBcIklzRmF2b3JpdGVcIiwgIFtcImtpbmRcIl0gPSBcIkJyb3dzZVBv + dEZpbHRlckl0ZW1zXCIsIH0sIFtcImNvbnRyb2xfa2luZFwiXSA9IFwidG9nZ2xlXCIsfVxubG9jYWwgYXZhaWxhYmxlID0geSBhbmQgeS5hdmFpbGFibGUgb3IgZmFs + c2VcbmxvY2FsIGxhYmVsID0gYXZhaWxhYmxlIGFuZCB5LmxhYmVsIG9yIFwiXCJcbmxvY2FsIG5hbWVfMSA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUs + IDEsIDkpIG9yIFwiXCJcbmxvY2FsIG5hbWVfMiA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEwLCAxOCkgb3IgXCJcIlxubG9jYWwgbmFtZV8zID0g + YXZhaWxhYmxlIGFuZCBzdHJpbmcuc3ViKHkubmFtZSwgMTksIDI3KSBvciBcIlwiXG5sb2NhbCBwcm9ncmVzcyA9IChhdmFpbGFibGUgYW5kIHkuaW5kZXggYW5kIHku + Y291bnQpIGFuZCAoXCJcIiAuLiAoeS5pbmRleCArIDEpIC4uIFwiL1wiIC4uIHkuY291bnQpIG9yIFwiXCJcbmxvY2FsIHNlY29uZGFyeV9wcm9wID0gYXZhaWxhYmxl + IGFuZCB5LnNlY29uZGFyeV9wcm9wIG9yIFwiXCJcbmxvY2FsIGNvbG9yID0gYXZhaWxhYmxlIGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0 + ZSkgb3IgYmxhY2tcbmxvY2FsIGFjdGlvbl9uYW1lID0gYXZhaWxhYmxlIGFuZCBhY3Rpb24gYW5kIGFjdGlvbi5uYW1lIG9yIG5pbFxubG9jYWwgYWN0aW9uX2NvbG9y + ID0gYXZhaWxhYmxlIGFuZCAoYWN0aW9uIGFuZCBhY3Rpb24uY29sb3Igb3IgbmlsKSBvciBibGFja1xucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAg + IG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICAtLSBIZWFkZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2Jf + Y29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBjb2xvciksXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCAxKSxcbiAgICAg + ICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAx + LCBwcm9ncmVzcyksXG4gICAgICAgICAgICAtLSBDb250ZW50XG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDIsIG5hbWVfMSksXG4g + ICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDMsIG5hbWVfMiksXG4gICAgICAgICAgICAtLSBGb290ZXJcbiAgICAgICAgICAgIGNyZWF0 + ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAyLCBhY3Rpb25fY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbiwg + MiwgMCksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDQsIFwiXCIpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFu + Z2UoY29sdW1uLCA1LCBhY3Rpb25fbmFtZSBvciBzZWNvbmRhcnlfcHJvcCksXG4gICAgICAgIH0pLFxuICAgIH1cbn1cbiJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJsb2NhbCBpbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5kaXNjcmV0ZV92 + YWx1ZVwiKVxubG9jYWwgY291bnQgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZGlzY3JldGVfdmFsdWVfY291bnRcIilcbmxvY2FsIG5hbWUgPSBjb250ZXh0LnByb3Ao + XCJ0YXJnZXQudGV4dF92YWx1ZVwiKSBvciBcIi1cIlxubG9jYWwgc2Vjb25kYXJ5X3Byb3Bfa2V5ID0gbmlsXG5sb2NhbCBzZWNvbmRhcnlfcHJvcF92YWx1ZSA9IHNl + Y29uZGFyeV9wcm9wX2tleSBhbmQgY29udGV4dC5wcm9wKHNlY29uZGFyeV9wcm9wX2tleSkgb3IgXCJcIlxubG9jYWwgYXZhaWxhYmxlID0gY29udGV4dC5wcm9wKFwi + dGFyZ2V0LmF2YWlsYWJsZVwiKVxucmV0dXJuIHtcbiAgICBmZWVkYmFja19ldmVudCA9IHtcbiAgICAgICAgY29sb3IgPSB7IFtcInJcIl0gPSAxMjEsIFtcImJcIl0g + PSA3MiwgW1wiZ1wiXSA9IDg1LH0sXG4gICAgICAgIHZhbHVlID0ge1xuICAgICAgICAgICAgbGFiZWwgPSBcIlByb2R1Y3RcIixcbiAgICAgICAgICAgIG5hbWUgPSBu + YW1lLFxuICAgICAgICAgICAgc2Vjb25kYXJ5X3Byb3AgPSBzZWNvbmRhcnlfcHJvcF92YWx1ZSxcbiAgICAgICAgICAgIGF2YWlsYWJsZSA9IGF2YWlsYWJsZSxcbiAg + ICAgICAgICAgIGluZGV4ID0gaW5kZXgsXG4gICAgICAgICAgICBjb3VudCA9IGNvdW50LFxuICAgICAgICB9XG4gICAgfSxcbn1cbiIsImZlZWRiYWNrVHlwZSI6Mn0s + InRhcmdldCI6eyJ0eXBlIjo2MSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6IkJhbmsifSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJWLWhzNF9PbmRJNlRoVXVkZEl1eWwiLCJuYW1lIjoiRmF2b3JpdGUgKGNvbnRyb2wpIiwiZ3JvdXBJZCI6ImJyb3dzZS1hY3Rpb25zIiwic291cmNlIjp7ImNoYW5u + ZWwiOjE1LCJudW1iZXIiOjUzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJ0eXBlIjoyLCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiSXNGYXZvcml0ZSJ9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJFSElJOWJ1MHR5V1ZHTGVRS1RDSXgiLCJuYW1lIjoiRmF2b3JpdGUgKGZlZWRiYWNrKSIsImdyb3VwSWQiOiJicm93c2UtYWN0aW9ucyIs + InNvdXJjZSI6eyJ0eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJsdWEiLCJtaWRpU2NyaXB0IjoiLS0gIyMgQ29uc3RhbnRzICMjXG5cbmxvY2FsIGNvbHVtbl9rbm9i + X2FkZHJlc3Nfb2Zmc2V0ID0gNTAwMFxubG9jYWwgbGVkX2FkZHJlc3Nfb2Zmc2V0ID0gMTAwMDBcbmxvY2FsIGJsYWNrID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1c + bmxvY2FsIHdoaXRlID0geyByID0gMjU1LCBnID0gMjU1LCBiID0gMjU1IH1cblxuLS0gIyMgRnVuY3Rpb25zICMjXG5cbmZ1bmN0aW9uIHRvX2FzY2lpKHRleHQpXG4g + ICAgbG9jYWwgYnl0ZXMgPSB7fVxuICAgIGlmIHRleHQgfj0gbmlsIHRoZW5cbiAgICAgICAgZm9yIGkgPSAxLCBzdHJpbmcubGVuKHRleHQpIGRvXG4gICAgICAgICAg + ICBsb2NhbCBieXRlID0gc3RyaW5nLmJ5dGUodGV4dCwgaSlcbiAgICAgICAgICAgIGlmIGJ5dGUgPCAxMjggdGhlblxuICAgICAgICAgICAgICAgIHRhYmxlLmluc2Vy + dChieXRlcywgYnl0ZSlcbiAgICAgICAgICAgIGVuZFxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICAtLSBOdWxsIHRlcm1pbmF0b3JcbiAgICB0YWJsZS5pbnNlcnQo + Ynl0ZXMsIDApXG4gICAgcmV0dXJuIGJ5dGVzXG5lbmRcblxuZnVuY3Rpb24gY29uY2F0X3RhYmxlKHQxLCB0MilcbiAgICBmb3IgaSA9IDEsICN0MiBkb1xuICAgICAg + ICB0MVsjdDEgKyAxXSA9IHQyW2ldXG4gICAgZW5kXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX21zZyhjb250ZW50KVxuICAgIC0tIFN5c0V4IEhlYWRlclxuICAgIGxv + Y2FsIG1zZyA9IHtcbiAgICAgICAgMHhmMCwgMHgwMCwgMHgyMCwgMHgyOSwgMHgwMiwgMHgwYSwgMHgwMSxcbiAgICB9XG4gICAgLS0gQ29udGVudFxuICAgIGNvbmNh + dF90YWJsZShtc2csIGNvbnRlbnQpXG4gICAgLS0gRW5kIG9mIFN5c0V4XG4gICAgdGFibGUuaW5zZXJ0KG1zZywgMHhmNylcbiAgICByZXR1cm4gbXNnXG5lbmRcblxu + LS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgbGF5b3V0LlxuLS0tXG4tLS0gQHBhcmFtIGxheW91dF9pbmRleCBudW1iZXIgbGF5b3V0IGluZGV4 + ICgwID0gZW1wdHksIDEgPSBrbm9iLCAyID0gYm94KVxuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9sYXlvdXRfbXNnKGxheW91dF9pbmRleClcbiAgICByZXR1cm4gY3Jl + YXRlX21zZyh7IDB4MDEsIGxheW91dF9pbmRleCB9KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgZGlzcGxheWluZyBhIG5vdGlmaWNhdGlvbiBvbiB0 + aGUgY2VudGVyIHNjcmVlbi5cbi0tLVxuLS0tIEBwYXJhbSBsaW5lMSBzdHJpbmcgZmlyc3QgbGluZSBvZiB0ZXh0XG4tLS0gQHBhcmFtIGxpbmUyIHN0cmluZyBzZWNv + bmQgbGluZSBvZiB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfbm90aWZpY2F0aW9uX3RleHRfbXNnKGxpbmUxLCBsaW5lMilcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDA0 + IH1cbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTEpKVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMikpXG4gICAg + cmV0dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIG11bHRpcGxlIHNjcmVlbiBwcm9wZXJ0aWVz + LlxuLS0tXG4tLS0gQHBhcmFtIGNoYW5nZXMgdGFibGUgbGlzdCBvZiBwcm9wZXJ0eSBjaGFuZ2VzXG5mdW5jdGlvbiBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyhjaGFu + Z2VzKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDIgfVxuICAgIGZvciBpID0gMSwgI2NoYW5nZXMgZG9cbiAgICAgICAgaWYgY2hhbmdlc1tpXSB+PSBuaWwgdGhl + blxuICAgICAgICAgICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIGNoYW5nZXNbaV0pXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNv + bnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB0ZXh0IHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNo + IGNvbHVtbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gZGlzcGxheSB0aGUgdGV4 + dFxuLS0tIEBwYXJhbSB0ZXh0IHN0cmluZyB0aGUgYWN0dWFsIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0 + X2luZGV4LCB0ZXh0KVxuICAgIGxvY2FsIGNoYW5nZSA9IHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0g + UHJvcGVydHkgVHlwZSBcIlRleHRcIlxuICAgICAgICAweDAxLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgIH1cbiAg + ICBjb25jYXRfdGFibGUoY2hhbmdlLCB0b19hc2NpaSh0ZXh0KSlcbiAgICByZXR1cm4gY2hhbmdlXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB2YWx1ZSBwcm9wZXJ0eSBj + aGFuZ2UuXG4tLS1cbi0tLSBJZiBpdCdzIGFib3V0IHNldHRpbmcgdGhlIGtub2IgdmFsdWUsIGl0J3MgdXN1YWxseSBtb3JlIGludHVpdGl2ZSB0byBub3Rcbi0tLSBk + byB0aGlzIHZpYSBzY3JpcHQgYnV0IGJ5IGVuYWJsaW5nIGZlZWRiYWNrIG9uIHRoZSBjb3JyZXNwb25kaW5nIGVuY29kZXJcbi0tLSBtYXBwaW5ncywgdGhlbiB5b3Ug + bmVlZCBqdXN0IG9uZSBtYXBwaW5nIGZvciBib3RoIGNvbnRyb2wgYW5kIGZlZWRiYWNrLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hp + Y2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgdmFsdWVcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciB0aGUga2luZCBvZiB2YWx1ZSB0byBjaGFuZ2UgKHNlZSBw + cm9ncmFtbWVyJ3MgZ3VpZGUpXG4tLS0gQHBhcmFtIHZhbHVlIG51bWJlciB0aGUgdmFsdWVcbmZ1bmN0aW9uIGNyZWF0ZV92YWx1ZV9wcm9wX2NoYW5nZShjb2x1bW5f + aW5kZXgsIG9iamVjdF9pbmRleCwgdmFsdWUpXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAg + ICAgLS0gUHJvcGVydHkgVHlwZSBcIlZhbHVlXCJcbiAgICAgICAgMHgwMyxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAg + ICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgdmFsdWVcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYW4gUkdCIGNvbG9yIHByb3BlcnR5IGNoYW5nZS5cbi0t + LVxuLS0tIElmIHRoZSBnaXZlbiBjb2xvciBpcyBgbmlsYCwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGBuaWxgLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBu + dW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBj + aGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rp + b24gY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbnVsbCB0aGVuXG4g + ICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAg + ICAtLSBQcm9wZXJ0eSBUeXBlIFwiUkdCIGNvbG9yXCJcbiAgICAgICAgMHgwNCxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxc + biAgICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuZyAvIDIpLFxuICAg + ICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgc3RhdGUgb2YgYW4g + TEVELlxuLS0tXG4tLS0gUGFzc2luZyBhIGBuaWxgIGNvbG9yIHdpbGwgbWFrZSB0aGUgTEVEIGdvIG9mZi5cbi0tLVxuLS0tIEBwYXJhbSBsZWRfaW5kZXggbnVtYmVy + IHdoaWNoIExFRCB0byB0YWxrIHRvXG4tLS0gQHBhcmFtIGxlZF9iZWhhdmlvciBudW1iZXIgTEVEIGJlaGF2aW9yICgxID0gc29saWQsIDIgPSBmbGFzaGluZyB3aXRo + IHByZXZpb3VzbHkgc2V0IHNvbGlkIGNvbG9yLCAzID0gcHVsc2F0aW5nKVxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHBy + b3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9sZWRfbXNnKGxlZF9pbmRleCwgbGVkX2JlaGF2aW9yLCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBu + aWwgdGhlblxuICAgICAgICBjb2xvciA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coe1xuICAgICAgICAweDAz + LFxuICAgICAgICBsZWRfaW5kZXgsXG4gICAgICAgIGxlZF9iZWhhdmlvcixcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxv + b3IoY29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9KVxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9sZWZ0X3JpZ2h0X2Fu + aW1hdGlvbihnbG9iYWxfbWlsbGlzLCBtYXhfY2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB0ZXh0KVxuICAgIGlmIHRleHQgPT0gbmlsIHRoZW5cbiAgICAgICAgcmV0 + dXJuIG5pbFxuICAgIGVuZFxuICAgIGlmICN0ZXh0ID4gbWF4X2NoYXJfY291bnQgdGhlblxuICAgICAgICBsb2NhbCBmcmFtZV9jb3VudCA9ICN0ZXh0IC0gbWF4X2No + YXJfY291bnRcbiAgICAgICAgbG9jYWwgZnJhbWVfaW5kZXggPSBtYXRoLmZsb29yKGdsb2JhbF9taWxsaXMgLyBmcmFtZV9sZW5ndGgpICUgKGZyYW1lX2NvdW50ICog + MilcbiAgICAgICAgbG9jYWwgdGV4dF9vZmZzZXRcbiAgICAgICAgaWYgZnJhbWVfaW5kZXggPCBmcmFtZV9jb3VudCB0aGVuXG4gICAgICAgICAgICB0ZXh0X29mZnNl + dCA9IGZyYW1lX2luZGV4XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGxvY2FsIGRpc3RhbmNlID0gZnJhbWVfaW5kZXggLSBmcmFtZV9jb3VudFxuICAgICAgICAg + ICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9jb3VudCAtIGRpc3RhbmNlXG4gICAgICAgIGVuZFxuICAgICAgICByZXR1cm4gdGV4dDpzdWIodGV4dF9vZmZzZXQgKyAxLCB0 + ZXh0X29mZnNldCArIG1heF9jaGFyX2NvdW50KVxuICAgIGVsc2VcbiAgICAgICAgcmV0dXJuIHRleHRcbiAgICBlbmRcbmVuZFxuXG4tLSAjIyBDb2RlICMjXG5sb2Nh + bCBjb2x1bW4gPSAyXG5sb2NhbCBjb2xvciA9ICh5IGFuZCB5IH49IDApIGFuZCB7IFtcInJcIl0gPSAyNTUsIFtcImJcIl0gPSAyNTUsIFtcImdcIl0gPSAyNTUsfSBv + ciBuaWxcblxubG9jYWwgbGVkX2luZGV4ID0gNCArIGNvbHVtblxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gbGVkX2FkZHJlc3Nfb2Zmc2V0ICsgbGVkX2luZGV4LFxu + ICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIDEsIGNvbG9yKSxcbiAgICB9XG59XG4ifSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiSXNGYXZvcml0ZSJ9 + LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IkZzTm12UUNaOUIzRXgtcUJISVpnNiIsIm5hbWUiOiJFbmNvZGVyIDQgLSBCYW5rIiwiZ3JvdXBJZCI6ImJy + b3dzZS1jb2x1bW5zIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjI0LCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjotMTAsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtpbmQiOiJTdWJCYW5r + In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlZ2bjhQQTlBdHVURnhpLUNfSFZ6SiIsIm5hbWUiOiJTY3JlZW4gNCAtIEJyb3dzZSBwcm9kdWN0cyIs + Imdyb3VwSWQiOiJicm93c2UtY29sdW1ucyIsInNvdXJjZSI6eyJ0eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJsdWEiLCJtaWRpU2NyaXB0IjoiLS0gIyMgQ29uc3Rh + bnRzICMjXG5cbmxvY2FsIGNvbHVtbl9rbm9iX2FkZHJlc3Nfb2Zmc2V0ID0gNTAwMFxubG9jYWwgbGVkX2FkZHJlc3Nfb2Zmc2V0ID0gMTAwMDBcbmxvY2FsIGJsYWNr + ID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1cbmxvY2FsIHdoaXRlID0geyByID0gMjU1LCBnID0gMjU1LCBiID0gMjU1IH1cblxuLS0gIyMgRnVuY3Rpb25zICMjXG5c + bmZ1bmN0aW9uIHRvX2FzY2lpKHRleHQpXG4gICAgbG9jYWwgYnl0ZXMgPSB7fVxuICAgIGlmIHRleHQgfj0gbmlsIHRoZW5cbiAgICAgICAgZm9yIGkgPSAxLCBzdHJp + bmcubGVuKHRleHQpIGRvXG4gICAgICAgICAgICBsb2NhbCBieXRlID0gc3RyaW5nLmJ5dGUodGV4dCwgaSlcbiAgICAgICAgICAgIGlmIGJ5dGUgPCAxMjggdGhlblxu + ICAgICAgICAgICAgICAgIHRhYmxlLmluc2VydChieXRlcywgYnl0ZSlcbiAgICAgICAgICAgIGVuZFxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICAtLSBOdWxsIHRl + cm1pbmF0b3JcbiAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIDApXG4gICAgcmV0dXJuIGJ5dGVzXG5lbmRcblxuZnVuY3Rpb24gY29uY2F0X3RhYmxlKHQxLCB0MilcbiAg + ICBmb3IgaSA9IDEsICN0MiBkb1xuICAgICAgICB0MVsjdDEgKyAxXSA9IHQyW2ldXG4gICAgZW5kXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX21zZyhjb250ZW50KVxu + ICAgIC0tIFN5c0V4IEhlYWRlclxuICAgIGxvY2FsIG1zZyA9IHtcbiAgICAgICAgMHhmMCwgMHgwMCwgMHgyMCwgMHgyOSwgMHgwMiwgMHgwYSwgMHgwMSxcbiAgICB9 + XG4gICAgLS0gQ29udGVudFxuICAgIGNvbmNhdF90YWJsZShtc2csIGNvbnRlbnQpXG4gICAgLS0gRW5kIG9mIFN5c0V4XG4gICAgdGFibGUuaW5zZXJ0KG1zZywgMHhm + NylcbiAgICByZXR1cm4gbXNnXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgbGF5b3V0LlxuLS0tXG4tLS0gQHBhcmFtIGxheW91 + dF9pbmRleCBudW1iZXIgbGF5b3V0IGluZGV4ICgwID0gZW1wdHksIDEgPSBrbm9iLCAyID0gYm94KVxuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9sYXlvdXRfbXNnKGxh + eW91dF9pbmRleClcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7IDB4MDEsIGxheW91dF9pbmRleCB9KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgZGlz + cGxheWluZyBhIG5vdGlmaWNhdGlvbiBvbiB0aGUgY2VudGVyIHNjcmVlbi5cbi0tLVxuLS0tIEBwYXJhbSBsaW5lMSBzdHJpbmcgZmlyc3QgbGluZSBvZiB0ZXh0XG4t + LS0gQHBhcmFtIGxpbmUyIHN0cmluZyBzZWNvbmQgbGluZSBvZiB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfbm90aWZpY2F0aW9uX3RleHRfbXNnKGxpbmUxLCBsaW5lMilc + biAgICBsb2NhbCBjb250ZW50ID0geyAweDA0IH1cbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTEpKVxuICAgIGNvbmNhdF90YWJsZShjb250 + ZW50LCB0b19hc2NpaShsaW5lMikpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5n + IG11bHRpcGxlIHNjcmVlbiBwcm9wZXJ0aWVzLlxuLS0tXG4tLS0gQHBhcmFtIGNoYW5nZXMgdGFibGUgbGlzdCBvZiBwcm9wZXJ0eSBjaGFuZ2VzXG5mdW5jdGlvbiBj + cmVhdGVfc2NyZWVuX3Byb3BzX21zZyhjaGFuZ2VzKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDIgfVxuICAgIGZvciBpID0gMSwgI2NoYW5nZXMgZG9cbiAgICAg + ICAgaWYgY2hhbmdlc1tpXSB+PSBuaWwgdGhlblxuICAgICAgICAgICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIGNoYW5nZXNbaV0pXG4gICAgICAgIGVuZFxuICAgIGVu + ZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB0ZXh0IHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIEBwYXJhbSBj + b2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2gg + bG9jYXRpb24gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSB0ZXh0IHN0cmluZyB0aGUgYWN0dWFsIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV90ZXh0X3Byb3Bf + Y2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCB0ZXh0KVxuICAgIGxvY2FsIGNoYW5nZSA9IHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAg + IGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlRleHRcIlxuICAgICAgICAweDAxLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAg + ICAgb2JqZWN0X2luZGV4LFxuICAgIH1cbiAgICBjb25jYXRfdGFibGUoY2hhbmdlLCB0b19hc2NpaSh0ZXh0KSlcbiAgICByZXR1cm4gY2hhbmdlXG5lbmRcblxuLS0t + IENyZWF0ZXMgYSB2YWx1ZSBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiBpdCdzIGFib3V0IHNldHRpbmcgdGhlIGtub2IgdmFsdWUsIGl0J3MgdXN1YWxseSBt + b3JlIGludHVpdGl2ZSB0byBub3Rcbi0tLSBkbyB0aGlzIHZpYSBzY3JpcHQgYnV0IGJ5IGVuYWJsaW5nIGZlZWRiYWNrIG9uIHRoZSBjb3JyZXNwb25kaW5nIGVuY29k + ZXJcbi0tLSBtYXBwaW5ncywgdGhlbiB5b3UgbmVlZCBqdXN0IG9uZSBtYXBwaW5nIGZvciBib3RoIGNvbnRyb2wgYW5kIGZlZWRiYWNrLlxuLS0tXG4tLS0gQHBhcmFt + IGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgdmFsdWVcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciB0aGUga2lu + ZCBvZiB2YWx1ZSB0byBjaGFuZ2UgKHNlZSBwcm9ncmFtbWVyJ3MgZ3VpZGUpXG4tLS0gQHBhcmFtIHZhbHVlIG51bWJlciB0aGUgdmFsdWVcbmZ1bmN0aW9uIGNyZWF0 + ZV92YWx1ZV9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdmFsdWUpXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4g + ICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlZhbHVlXCJcbiAgICAgICAgMHgwMyxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4 + XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgdmFsdWVcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYW4gUkdC + IGNvbG9yIHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIElmIHRoZSBnaXZlbiBjb2xvciBpcyBgbmlsYCwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGBuaWxgLlxuLS0t + XG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51 + bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJv + cGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgY29sb3IpXG4g + ICAgaWYgY29sb3IgPT0gbnVsbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAg + ICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiUkdCIGNvbG9yXCJcbiAgICAgICAgMHgwNCxcbiAgICAgICAgLS0gT2JqZWN0IElu + ZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1h + dGguZmxvb3IoY29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZv + ciBjaGFuZ2luZyB0aGUgc3RhdGUgb2YgYW4gTEVELlxuLS0tXG4tLS0gUGFzc2luZyBhIGBuaWxgIGNvbG9yIHdpbGwgbWFrZSB0aGUgTEVEIGdvIG9mZi5cbi0tLVxu + LS0tIEBwYXJhbSBsZWRfaW5kZXggbnVtYmVyIHdoaWNoIExFRCB0byB0YWxrIHRvXG4tLS0gQHBhcmFtIGxlZF9iZWhhdmlvciBudW1iZXIgTEVEIGJlaGF2aW9yICgx + ID0gc29saWQsIDIgPSBmbGFzaGluZyB3aXRoIHByZXZpb3VzbHkgc2V0IHNvbGlkIGNvbG9yLCAzID0gcHVsc2F0aW5nKVxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0 + aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9sZWRfbXNnKGxlZF9pbmRleCwgbGVkX2JlaGF2aW9y + LCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBuaWwgdGhlblxuICAgICAgICBjb2xvciA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG4gICAgZW5kXG4gICAgcmV0dXJu + IGNyZWF0ZV9tc2coe1xuICAgICAgICAweDAzLFxuICAgICAgICBsZWRfaW5kZXgsXG4gICAgICAgIGxlZF9iZWhhdmlvcixcbiAgICAgICAgbWF0aC5mbG9vcihjb2xv + ci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9KVxuZW5kXG5cbmZ1 + bmN0aW9uIGNyZWF0ZV9sZWZ0X3JpZ2h0X2FuaW1hdGlvbihnbG9iYWxfbWlsbGlzLCBtYXhfY2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB0ZXh0KVxuICAgIGlmIHRl + eHQgPT0gbmlsIHRoZW5cbiAgICAgICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIGlmICN0ZXh0ID4gbWF4X2NoYXJfY291bnQgdGhlblxuICAgICAgICBsb2NhbCBm + cmFtZV9jb3VudCA9ICN0ZXh0IC0gbWF4X2NoYXJfY291bnRcbiAgICAgICAgbG9jYWwgZnJhbWVfaW5kZXggPSBtYXRoLmZsb29yKGdsb2JhbF9taWxsaXMgLyBmcmFt + ZV9sZW5ndGgpICUgKGZyYW1lX2NvdW50ICogMilcbiAgICAgICAgbG9jYWwgdGV4dF9vZmZzZXRcbiAgICAgICAgaWYgZnJhbWVfaW5kZXggPCBmcmFtZV9jb3VudCB0 + aGVuXG4gICAgICAgICAgICB0ZXh0X29mZnNldCA9IGZyYW1lX2luZGV4XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGxvY2FsIGRpc3RhbmNlID0gZnJhbWVfaW5k + ZXggLSBmcmFtZV9jb3VudFxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9jb3VudCAtIGRpc3RhbmNlXG4gICAgICAgIGVuZFxuICAgICAgICByZXR1cm4g + dGV4dDpzdWIodGV4dF9vZmZzZXQgKyAxLCB0ZXh0X29mZnNldCArIG1heF9jaGFyX2NvdW50KVxuICAgIGVsc2VcbiAgICAgICAgcmV0dXJuIHRleHRcbiAgICBlbmRc + bmVuZFxuXG4tLSAjIyBDb2RlICMjXG5sb2NhbCBjb2x1bW4gPSAzXG5sb2NhbCBhY3Rpb24gPSB7IFtcIm5hbWVcIl0gPSBcIlVzZXJcIiwgW1wiY29sb3JcIl0gPSB7 + ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDI1NSwgIFtcImdcIl0gPSAyNTUsIH0sIFtcInRhcmdldFwiXSA9IHsgIFtcIml0ZW1fa2luZFwiXSA9IFwiSXNVc2Vy + XCIsICBbXCJraW5kXCJdID0gXCJCcm93c2VQb3RGaWx0ZXJJdGVtc1wiLCB9LCBbXCJjb250cm9sX2tpbmRcIl0gPSBcInRvZ2dsZVwiLH1cbmxvY2FsIGF2YWlsYWJs + ZSA9IHkgYW5kIHkuYXZhaWxhYmxlIG9yIGZhbHNlXG5sb2NhbCBsYWJlbCA9IGF2YWlsYWJsZSBhbmQgeS5sYWJlbCBvciBcIlwiXG5sb2NhbCBuYW1lXzEgPSBhdmFp + bGFibGUgYW5kIHN0cmluZy5zdWIoeS5uYW1lLCAxLCA5KSBvciBcIlwiXG5sb2NhbCBuYW1lXzIgPSBhdmFpbGFibGUgYW5kIHN0cmluZy5zdWIoeS5uYW1lLCAxMCwg + MTgpIG9yIFwiXCJcbmxvY2FsIG5hbWVfMyA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDE5LCAyNykgb3IgXCJcIlxubG9jYWwgcHJvZ3Jlc3MgPSAo + YXZhaWxhYmxlIGFuZCB5LmluZGV4IGFuZCB5LmNvdW50KSBhbmQgKFwiXCIgLi4gKHkuaW5kZXggKyAxKSAuLiBcIi9cIiAuLiB5LmNvdW50KSBvciBcIlwiXG5sb2Nh + bCBzZWNvbmRhcnlfcHJvcCA9IGF2YWlsYWJsZSBhbmQgeS5zZWNvbmRhcnlfcHJvcCBvciBcIlwiXG5sb2NhbCBjb2xvciA9IGF2YWlsYWJsZSBhbmQgKGNvbnRleHQu + ZmVlZGJhY2tfZXZlbnQuY29sb3Igb3Igd2hpdGUpIG9yIGJsYWNrXG5sb2NhbCBhY3Rpb25fbmFtZSA9IGF2YWlsYWJsZSBhbmQgYWN0aW9uIGFuZCBhY3Rpb24ubmFt + ZSBvciBuaWxcbmxvY2FsIGFjdGlvbl9jb2xvciA9IGF2YWlsYWJsZSBhbmQgKGFjdGlvbiBhbmQgYWN0aW9uLmNvbG9yIG9yIG5pbCkgb3IgYmxhY2tcbnJldHVybiB7 + XG4gICAgYWRkcmVzcyA9IGNvbHVtbixcbiAgICBtZXNzYWdlcyA9IHtcbiAgICAgICAgY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coe1xuICAgICAgICAgICAgLS0gSGVh + ZGVyXG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3ZhbHVlX3Byb3Bf + Y2hhbmdlKGNvbHVtbiwgMCwgMSksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDAsIGxhYmVsKSxcbiAgICAgICAgICAgIGNyZWF0 + ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMSwgcHJvZ3Jlc3MpLFxuICAgICAgICAgICAgLS0gQ29udGVudFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9j + aGFuZ2UoY29sdW1uLCAyLCBuYW1lXzEpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBuYW1lXzIpLFxuICAgICAgICAgICAg + LS0gRm9vdGVyXG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgYWN0aW9uX2NvbG9yKSxcbiAgICAgICAgICAgIGNyZWF0 + ZV92YWx1ZV9wcm9wX2NoYW5nZShjb2x1bW4sIDIsIDApLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCA0LCBcIlwiKSxcbiAgICAg + ICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgNSwgYWN0aW9uX25hbWUgb3Igc2Vjb25kYXJ5X3Byb3ApLFxuICAgICAgICB9KSxcbiAgICB9XG59 + XG4ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoibG9jYWwgaW5kZXggPSBjb250 + ZXh0LnByb3AoXCJ0YXJnZXQuZGlzY3JldGVfdmFsdWVcIilcbmxvY2FsIGNvdW50ID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LmRpc2NyZXRlX3ZhbHVlX2NvdW50XCIp + XG5sb2NhbCBuYW1lID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LnRleHRfdmFsdWVcIikgb3IgXCItXCJcbmxvY2FsIHNlY29uZGFyeV9wcm9wX2tleSA9IG5pbFxubG9j + YWwgc2Vjb25kYXJ5X3Byb3BfdmFsdWUgPSBzZWNvbmRhcnlfcHJvcF9rZXkgYW5kIGNvbnRleHQucHJvcChzZWNvbmRhcnlfcHJvcF9rZXkpIG9yIFwiXCJcbmxvY2Fs + IGF2YWlsYWJsZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5hdmFpbGFibGVcIilcbnJldHVybiB7XG4gICAgZmVlZGJhY2tfZXZlbnQgPSB7XG4gICAgICAgIGNvbG9y + ID0geyBbXCJyXCJdID0gMTIxLCBbXCJiXCJdID0gNzIsIFtcImdcIl0gPSA4NSx9LFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIGxhYmVsID0gXCJCYW5r + XCIsXG4gICAgICAgICAgICBuYW1lID0gbmFtZSxcbiAgICAgICAgICAgIHNlY29uZGFyeV9wcm9wID0gc2Vjb25kYXJ5X3Byb3BfdmFsdWUsXG4gICAgICAgICAgICBh + dmFpbGFibGUgPSBhdmFpbGFibGUsXG4gICAgICAgICAgICBpbmRleCA9IGluZGV4LFxuICAgICAgICAgICAgY291bnQgPSBjb3VudCxcbiAgICAgICAgfVxuICAgIH0s + XG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtpbmQiOiJTdWJCYW5rIn0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidE52UHRjMHd4a3lYbklRZ080c18yIiwibmFtZSI6IlVzZXIgKGNvbnRyb2wpIiwiZ3JvdXBJZCI6ImJyb3dzZS1h + Y3Rpb25zIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjU0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJ0eXBlIjoyLCJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiSXNVc2Vy + In0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ii1SWnFKOEdjYUJDNmZjdllUc0dvMSIsIm5hbWUiOiJVc2VyIChmZWVkYmFjaykiLCJncm91cElkIjoi + YnJvd3NlLWFjdGlvbnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5s + b2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAs + IGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0 + b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0 + KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAg + ICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4g + ICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAx + LCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNF + eCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENv + bnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0 + dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVt + YmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgp + XG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBu + b3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBs + aW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwg + Y29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNj + aWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBz + Y3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVl + bl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5n + ZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1 + cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4 + IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRv + IGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1 + bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5k + ZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9p + bmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEg + dmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRp + dmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFw + cGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5k + ZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUg + dG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJv + cF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1 + bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBv + YmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9w + ZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJh + bSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hp + Y2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwg + ZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9y + ID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVt + bl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAg + ICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNv + bG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcg + dGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0g + bGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAy + ID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xv + ciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4g + ICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNn + KHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxu + ICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVh + dGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0 + aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQg + PSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAl + IChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAg + ICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVf + Y291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRl + eHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0g + IyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gM1xubG9jYWwgY29sb3IgPSAoeSBhbmQgeSB+PSAwKSBhbmQgeyBbXCJyXCJdID0gMjU1LCBbXCJiXCJdID0gMjU1LCBb + XCJnXCJdID0gMjU1LH0gb3IgbmlsXG5cbmxvY2FsIGxlZF9pbmRleCA9IDQgKyBjb2x1bW5cbnJldHVybiB7XG4gICAgYWRkcmVzcyA9IGxlZF9hZGRyZXNzX29mZnNl + dCArIGxlZF9pbmRleCxcbiAgICBtZXNzYWdlcyA9IHtcbiAgICAgICAgY3JlYXRlX2xlZF9tc2cobGVkX2luZGV4LCAxLCBjb2xvciksXG4gICAgfVxufVxuIn0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9LCJwb3RGaWx0ZXJJdGVtS2lu + ZCI6IklzVXNlciJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjdxUF9MMkFXUDRwMFgwc2t2MjRHSiIsIm5hbWUiOiJFbmNvZGVyIDUgLSBUeXBlIiwi + Z3JvdXBJZCI6ImJyb3dzZS1jb2x1bW5zIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjI1LCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjotMTAsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtp + bmQiOiJDYXRlZ29yeSJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJueE1GNDlUNEc5WXZkcnVaV05pNVEiLCJuYW1lIjoiU2NyZWVuIDUgLSBCcm93 + c2UgcHJvZHVjdHMiLCJncm91cElkIjoiYnJvd3NlLWNvbHVtbnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6 + Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAw + XG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1 + bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZv + ciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRl + IDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4g + ICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJs + ZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9t + c2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEs + IDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmlu + c2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0t + IEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5f + bGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1l + c3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxp + bmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhs + aW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25j + YXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdl + IGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdl + c1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFu + Z2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAg + ICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1c + bi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVt + YmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVh + dGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJ + bmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0 + IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5n + ZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBp + dCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVz + cG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0t + LVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBu + dW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5m + dW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENv + bHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0t + IE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBD + cmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJu + cyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9i + amVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0 + YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5k + ZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29s + dW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAg + IC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIp + LFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVz + IGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBn + byBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExF + RCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0g + Y29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgs + IGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVu + ZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1h + dGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAg + fSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4 + dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAg + ICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxf + bWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwg + ZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5j + ZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAg + ICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0 + ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gNFxubG9jYWwgYWN0aW9uID0geyBbXCJuYW1lXCJdID0gXCJQcmV2aWV3XCIs + IFtcImNvbG9yXCJdID0geyAgW1wiclwiXSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwgIFtcImdcIl0gPSA3OSwgfSwgW1widGFyZ2V0XCJdID0geyAgW1wia2luZFwiXSA9 + IFwiUHJldmlld1BvdFByZXNldFwiLCB9LCBbXCJjb250cm9sX2tpbmRcIl0gPSBcInRyaWdnZXJcIix9XG5sb2NhbCBhdmFpbGFibGUgPSB5IGFuZCB5LmF2YWlsYWJs + ZSBvciBmYWxzZVxubG9jYWwgbGFiZWwgPSBhdmFpbGFibGUgYW5kIHkubGFiZWwgb3IgXCJcIlxubG9jYWwgbmFtZV8xID0gYXZhaWxhYmxlIGFuZCBzdHJpbmcuc3Vi + KHkubmFtZSwgMSwgOSkgb3IgXCJcIlxubG9jYWwgbmFtZV8yID0gYXZhaWxhYmxlIGFuZCBzdHJpbmcuc3ViKHkubmFtZSwgMTAsIDE4KSBvciBcIlwiXG5sb2NhbCBu + YW1lXzMgPSBhdmFpbGFibGUgYW5kIHN0cmluZy5zdWIoeS5uYW1lLCAxOSwgMjcpIG9yIFwiXCJcbmxvY2FsIHByb2dyZXNzID0gKGF2YWlsYWJsZSBhbmQgeS5pbmRl + eCBhbmQgeS5jb3VudCkgYW5kIChcIlwiIC4uICh5LmluZGV4ICsgMSkgLi4gXCIvXCIgLi4geS5jb3VudCkgb3IgXCJcIlxubG9jYWwgc2Vjb25kYXJ5X3Byb3AgPSBh + dmFpbGFibGUgYW5kIHkuc2Vjb25kYXJ5X3Byb3Agb3IgXCJcIlxubG9jYWwgY29sb3IgPSBhdmFpbGFibGUgYW5kIChjb250ZXh0LmZlZWRiYWNrX2V2ZW50LmNvbG9y + IG9yIHdoaXRlKSBvciBibGFja1xubG9jYWwgYWN0aW9uX25hbWUgPSBhdmFpbGFibGUgYW5kIGFjdGlvbiBhbmQgYWN0aW9uLm5hbWUgb3IgbmlsXG5sb2NhbCBhY3Rp + b25fY29sb3IgPSBhdmFpbGFibGUgYW5kIChhY3Rpb24gYW5kIGFjdGlvbi5jb2xvciBvciBuaWwpIG9yIGJsYWNrXG5yZXR1cm4ge1xuICAgIGFkZHJlc3MgPSBjb2x1 + bW4sXG4gICAgbWVzc2FnZXMgPSB7XG4gICAgICAgIGNyZWF0ZV9zY3JlZW5fcHJvcHNfbXNnKHtcbiAgICAgICAgICAgIC0tIEhlYWRlclxuICAgICAgICAgICAgY3Jl + YXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDAsIGNvbG9yKSxcbiAgICAgICAgICAgIGNyZWF0ZV92YWx1ZV9wcm9wX2NoYW5nZShjb2x1bW4sIDAsIDEp + LFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBsYWJlbCksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShj + b2x1bW4sIDEsIHByb2dyZXNzKSxcbiAgICAgICAgICAgIC0tIENvbnRlbnRcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgbmFt + ZV8xKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMywgbmFtZV8yKSxcbiAgICAgICAgICAgIC0tIEZvb3RlclxuICAgICAgICAg + ICAgY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDIsIGFjdGlvbl9jb2xvciksXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2Uo + Y29sdW1uLCAyLCAwKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgNCwgXCJcIiksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9w + cm9wX2NoYW5nZShjb2x1bW4sIDUsIGFjdGlvbl9uYW1lIG9yIHNlY29uZGFyeV9wcm9wKSxcbiAgICAgICAgfSksXG4gICAgfVxufVxuIn0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6ImxvY2FsIGluZGV4ID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LmRp + c2NyZXRlX3ZhbHVlXCIpXG5sb2NhbCBjb3VudCA9IGNvbnRleHQucHJvcChcInRhcmdldC5kaXNjcmV0ZV92YWx1ZV9jb3VudFwiKVxubG9jYWwgbmFtZSA9IGNvbnRl + eHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIpIG9yIFwiLVwiXG5sb2NhbCBzZWNvbmRhcnlfcHJvcF9rZXkgPSBuaWxcbmxvY2FsIHNlY29uZGFyeV9wcm9wX3Zh + bHVlID0gc2Vjb25kYXJ5X3Byb3Bfa2V5IGFuZCBjb250ZXh0LnByb3Aoc2Vjb25kYXJ5X3Byb3Bfa2V5KSBvciBcIlwiXG5sb2NhbCBhdmFpbGFibGUgPSBjb250ZXh0 + LnByb3AoXCJ0YXJnZXQuYXZhaWxhYmxlXCIpXG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICBjb2xvciA9IHsgW1wiclwiXSA9IDI1NSwg + W1wiYlwiXSA9IDM0LCBbXCJnXCJdID0gODcsfSxcbiAgICAgICAgdmFsdWUgPSB7XG4gICAgICAgICAgICBsYWJlbCA9IFwiVHlwZVwiLFxuICAgICAgICAgICAgbmFt + ZSA9IG5hbWUsXG4gICAgICAgICAgICBzZWNvbmRhcnlfcHJvcCA9IHNlY29uZGFyeV9wcm9wX3ZhbHVlLFxuICAgICAgICAgICAgYXZhaWxhYmxlID0gYXZhaWxhYmxl + LFxuICAgICAgICAgICAgaW5kZXggPSBpbmRleCxcbiAgICAgICAgICAgIGNvdW50ID0gY291bnQsXG4gICAgICAgIH1cbiAgICB9LFxufVxuIiwiZmVlZGJhY2tUeXBl + IjoyfSwidGFyZ2V0Ijp7InR5cGUiOjYxLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn0sInBvdEZpbHRlckl0ZW1LaW5kIjoiQ2F0ZWdvcnkifSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiI5VHFOdGlfZ0dJY2NTV3ZIeWJJbFkiLCJuYW1lIjoiUHJldmlldyAoY29udHJvbCkiLCJncm91cElkIjoiYnJvd3NlLWFjdGlvbnMiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MTUsIm51bWJlciI6NTUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjEsImJ1dHRvblVzYWdlIjoicHJlc3Mtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6NTksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImI3T0Y4SjFYb0VBWWxhcE5oRk1NcSIsIm5hbWUiOiJQcmV2aWV3IChmZWVkYmFjaykiLCJncm91cElkIjoiYnJvd3NlLWFjdGlvbnMiLCJzb3VyY2Ui + OnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNz + X29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3 + aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2Fs + IGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwg + Ynl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMs + IGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAw + KVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3Qx + ICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cg + PSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUo + bXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVh + dGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVt + cHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2co + eyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRl + ciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUg + b2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAg + Y29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBj + cmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxu + LS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAg + ICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAg + ICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxu + ZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4g + dG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBA + cGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwg + dGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5 + IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0 + X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxu + LS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2 + aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVz + dCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVt + biB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1l + cidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBv + YmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFBy + b3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0t + IENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJ + ZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGlu + IHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRo + ZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0 + ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICBy + ZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJv + cGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAg + IC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0 + aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0t + LVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBM + RUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91 + c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVz + IHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5c + biAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAg + ICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9y + LmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24o + Z2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxc + biAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50 + XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAg + ICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFt + ZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRf + b2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZz + ZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1u + ID0gNFxubG9jYWwgY29sb3IgPSAoeSBhbmQgeS5hdmFpbGFibGUpIGFuZCB7IFtcInJcIl0gPSA0OCwgW1wiYlwiXSA9IDI1NCwgW1wiZ1wiXSA9IDc5LH0gb3Igbmls + XG5cbmxvY2FsIGxlZF9pbmRleCA9IDQgKyBjb2x1bW5cbnJldHVybiB7XG4gICAgYWRkcmVzcyA9IGxlZF9hZGRyZXNzX29mZnNldCArIGxlZF9pbmRleCxcbiAgICBt + ZXNzYWdlcyA9IHtcbiAgICAgICAgY3JlYXRlX2xlZF9tc2cobGVkX2luZGV4LCAxLCBjb2xvciksXG4gICAgfVxufVxuIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6ImxvY2FsIGF2YWlsYWJsZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5hdmFpbGFi + bGVcIilcbnJldHVybiB7XG4gICAgZmVlZGJhY2tfZXZlbnQgPSB7XG4gICAgICAgIHZhbHVlID0ge1xuICAgICAgICAgICAgYXZhaWxhYmxlID0gYXZhaWxhYmxlLFxu + ICAgICAgICB9XG4gICAgfSxcbn1cbiIsImZlZWRiYWNrVHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjo1OSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJ0M1k5OXUtRmFKbzBkOF9VV3l0aVYiLCJuYW1lIjoiRW5jb2RlciA2IC0gU3ViIHR5cGUiLCJncm91cElkIjoiYnJvd3NlLWNvbHVtbnMi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MTUsIm51bWJlciI6MjYsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOi0xMCwi + bWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6IlN1YkNhdGVnb3J5In0sImZlZWRi + YWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjdkYUYyc1dLQjQwNVBLSDctbHNkcSIsIm5hbWUiOiJTY3JlZW4gNiAtIEJyb3dzZSBwcm9kdWN0cyIsImdyb3VwSWQi + OiJicm93c2UtY29sdW1ucyIsInNvdXJjZSI6eyJ0eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJsdWEiLCJtaWRpU2NyaXB0IjoiLS0gIyMgQ29uc3RhbnRzICMjXG5c + bmxvY2FsIGNvbHVtbl9rbm9iX2FkZHJlc3Nfb2Zmc2V0ID0gNTAwMFxubG9jYWwgbGVkX2FkZHJlc3Nfb2Zmc2V0ID0gMTAwMDBcbmxvY2FsIGJsYWNrID0geyByID0g + MCwgZyA9IDAsIGIgPSAwIH1cbmxvY2FsIHdoaXRlID0geyByID0gMjU1LCBnID0gMjU1LCBiID0gMjU1IH1cblxuLS0gIyMgRnVuY3Rpb25zICMjXG5cbmZ1bmN0aW9u + IHRvX2FzY2lpKHRleHQpXG4gICAgbG9jYWwgYnl0ZXMgPSB7fVxuICAgIGlmIHRleHQgfj0gbmlsIHRoZW5cbiAgICAgICAgZm9yIGkgPSAxLCBzdHJpbmcubGVuKHRl + eHQpIGRvXG4gICAgICAgICAgICBsb2NhbCBieXRlID0gc3RyaW5nLmJ5dGUodGV4dCwgaSlcbiAgICAgICAgICAgIGlmIGJ5dGUgPCAxMjggdGhlblxuICAgICAgICAg + ICAgICAgIHRhYmxlLmluc2VydChieXRlcywgYnl0ZSlcbiAgICAgICAgICAgIGVuZFxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICAtLSBOdWxsIHRlcm1pbmF0b3Jc + biAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIDApXG4gICAgcmV0dXJuIGJ5dGVzXG5lbmRcblxuZnVuY3Rpb24gY29uY2F0X3RhYmxlKHQxLCB0MilcbiAgICBmb3IgaSA9 + IDEsICN0MiBkb1xuICAgICAgICB0MVsjdDEgKyAxXSA9IHQyW2ldXG4gICAgZW5kXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX21zZyhjb250ZW50KVxuICAgIC0tIFN5 + c0V4IEhlYWRlclxuICAgIGxvY2FsIG1zZyA9IHtcbiAgICAgICAgMHhmMCwgMHgwMCwgMHgyMCwgMHgyOSwgMHgwMiwgMHgwYSwgMHgwMSxcbiAgICB9XG4gICAgLS0g + Q29udGVudFxuICAgIGNvbmNhdF90YWJsZShtc2csIGNvbnRlbnQpXG4gICAgLS0gRW5kIG9mIFN5c0V4XG4gICAgdGFibGUuaW5zZXJ0KG1zZywgMHhmNylcbiAgICBy + ZXR1cm4gbXNnXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgbGF5b3V0LlxuLS0tXG4tLS0gQHBhcmFtIGxheW91dF9pbmRleCBu + dW1iZXIgbGF5b3V0IGluZGV4ICgwID0gZW1wdHksIDEgPSBrbm9iLCAyID0gYm94KVxuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9sYXlvdXRfbXNnKGxheW91dF9pbmRl + eClcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7IDB4MDEsIGxheW91dF9pbmRleCB9KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgZGlzcGxheWluZyBh + IG5vdGlmaWNhdGlvbiBvbiB0aGUgY2VudGVyIHNjcmVlbi5cbi0tLVxuLS0tIEBwYXJhbSBsaW5lMSBzdHJpbmcgZmlyc3QgbGluZSBvZiB0ZXh0XG4tLS0gQHBhcmFt + IGxpbmUyIHN0cmluZyBzZWNvbmQgbGluZSBvZiB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfbm90aWZpY2F0aW9uX3RleHRfbXNnKGxpbmUxLCBsaW5lMilcbiAgICBsb2Nh + bCBjb250ZW50ID0geyAweDA0IH1cbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTEpKVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19h + c2NpaShsaW5lMikpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIG11bHRpcGxl + IHNjcmVlbiBwcm9wZXJ0aWVzLlxuLS0tXG4tLS0gQHBhcmFtIGNoYW5nZXMgdGFibGUgbGlzdCBvZiBwcm9wZXJ0eSBjaGFuZ2VzXG5mdW5jdGlvbiBjcmVhdGVfc2Ny + ZWVuX3Byb3BzX21zZyhjaGFuZ2VzKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDIgfVxuICAgIGZvciBpID0gMSwgI2NoYW5nZXMgZG9cbiAgICAgICAgaWYgY2hh + bmdlc1tpXSB+PSBuaWwgdGhlblxuICAgICAgICAgICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIGNoYW5nZXNbaV0pXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIHJl + dHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB0ZXh0IHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5k + ZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24g + dG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSB0ZXh0IHN0cmluZyB0aGUgYWN0dWFsIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNv + bHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCB0ZXh0KVxuICAgIGxvY2FsIGNoYW5nZSA9IHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9p + bmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlRleHRcIlxuICAgICAgICAweDAxLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0 + X2luZGV4LFxuICAgIH1cbiAgICBjb25jYXRfdGFibGUoY2hhbmdlLCB0b19hc2NpaSh0ZXh0KSlcbiAgICByZXR1cm4gY2hhbmdlXG5lbmRcblxuLS0tIENyZWF0ZXMg + YSB2YWx1ZSBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiBpdCdzIGFib3V0IHNldHRpbmcgdGhlIGtub2IgdmFsdWUsIGl0J3MgdXN1YWxseSBtb3JlIGludHVp + dGl2ZSB0byBub3Rcbi0tLSBkbyB0aGlzIHZpYSBzY3JpcHQgYnV0IGJ5IGVuYWJsaW5nIGZlZWRiYWNrIG9uIHRoZSBjb3JyZXNwb25kaW5nIGVuY29kZXJcbi0tLSBt + YXBwaW5ncywgdGhlbiB5b3UgbmVlZCBqdXN0IG9uZSBtYXBwaW5nIGZvciBib3RoIGNvbnRyb2wgYW5kIGZlZWRiYWNrLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9p + bmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgdmFsdWVcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciB0aGUga2luZCBvZiB2YWx1 + ZSB0byBjaGFuZ2UgKHNlZSBwcm9ncmFtbWVyJ3MgZ3VpZGUpXG4tLS0gQHBhcmFtIHZhbHVlIG51bWJlciB0aGUgdmFsdWVcbmZ1bmN0aW9uIGNyZWF0ZV92YWx1ZV9w + cm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdmFsdWUpXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNv + bHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlZhbHVlXCJcbiAgICAgICAgMHgwMyxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAg + IG9iamVjdF9pbmRleCxcbiAgICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgdmFsdWVcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYW4gUkdCIGNvbG9yIHBy + b3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIElmIHRoZSBnaXZlbiBjb2xvciBpcyBgbmlsYCwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGBuaWxgLlxuLS0tXG4tLS0gQHBh + cmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3 + aGljaCBsb2NhdGlvbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGllcyBy + LCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgY29sb3IpXG4gICAgaWYgY29s + b3IgPT0gbnVsbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29s + dW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiUkdCIGNvbG9yXCJcbiAgICAgICAgMHgwNCxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAg + ICAgIG9iamVjdF9pbmRleCxcbiAgICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3Io + Y29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2lu + ZyB0aGUgc3RhdGUgb2YgYW4gTEVELlxuLS0tXG4tLS0gUGFzc2luZyBhIGBuaWxgIGNvbG9yIHdpbGwgbWFrZSB0aGUgTEVEIGdvIG9mZi5cbi0tLVxuLS0tIEBwYXJh + bSBsZWRfaW5kZXggbnVtYmVyIHdoaWNoIExFRCB0byB0YWxrIHRvXG4tLS0gQHBhcmFtIGxlZF9iZWhhdmlvciBudW1iZXIgTEVEIGJlaGF2aW9yICgxID0gc29saWQs + IDIgPSBmbGFzaGluZyB3aXRoIHByZXZpb3VzbHkgc2V0IHNvbGlkIGNvbG9yLCAzID0gcHVsc2F0aW5nKVxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNv + bG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9sZWRfbXNnKGxlZF9pbmRleCwgbGVkX2JlaGF2aW9yLCBjb2xvcilc + biAgICBpZiBjb2xvciA9PSBuaWwgdGhlblxuICAgICAgICBjb2xvciA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9t + c2coe1xuICAgICAgICAweDAzLFxuICAgICAgICBsZWRfaW5kZXgsXG4gICAgICAgIGxlZF9iZWhhdmlvcixcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiks + XG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9KVxuZW5kXG5cbmZ1bmN0aW9uIGNy + ZWF0ZV9sZWZ0X3JpZ2h0X2FuaW1hdGlvbihnbG9iYWxfbWlsbGlzLCBtYXhfY2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB0ZXh0KVxuICAgIGlmIHRleHQgPT0gbmls + IHRoZW5cbiAgICAgICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIGlmICN0ZXh0ID4gbWF4X2NoYXJfY291bnQgdGhlblxuICAgICAgICBsb2NhbCBmcmFtZV9jb3Vu + dCA9ICN0ZXh0IC0gbWF4X2NoYXJfY291bnRcbiAgICAgICAgbG9jYWwgZnJhbWVfaW5kZXggPSBtYXRoLmZsb29yKGdsb2JhbF9taWxsaXMgLyBmcmFtZV9sZW5ndGgp + ICUgKGZyYW1lX2NvdW50ICogMilcbiAgICAgICAgbG9jYWwgdGV4dF9vZmZzZXRcbiAgICAgICAgaWYgZnJhbWVfaW5kZXggPCBmcmFtZV9jb3VudCB0aGVuXG4gICAg + ICAgICAgICB0ZXh0X29mZnNldCA9IGZyYW1lX2luZGV4XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGxvY2FsIGRpc3RhbmNlID0gZnJhbWVfaW5kZXggLSBmcmFt + ZV9jb3VudFxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9jb3VudCAtIGRpc3RhbmNlXG4gICAgICAgIGVuZFxuICAgICAgICByZXR1cm4gdGV4dDpzdWIo + dGV4dF9vZmZzZXQgKyAxLCB0ZXh0X29mZnNldCArIG1heF9jaGFyX2NvdW50KVxuICAgIGVsc2VcbiAgICAgICAgcmV0dXJuIHRleHRcbiAgICBlbmRcbmVuZFxuXG4t + LSAjIyBDb2RlICMjXG5sb2NhbCBjb2x1bW4gPSA1XG5sb2NhbCBhY3Rpb24gPSB7IFtcIm5hbWVcIl0gPSBcIkxvYWRcIiwgW1wiY29sb3JcIl0gPSB7ICBbXCJyXCJd + ID0gMTgzLCAgW1wiYlwiXSA9IDI4LCAgW1wiZ1wiXSA9IDI4LCB9LCBbXCJ0YXJnZXRcIl0gPSB7ICBbXCJmeFwiXSA9IHsgICBbXCJhZGRyZXNzXCJdID0gXCJJbnN0 + YW5jZVwiLCAgfSwgIFtcImtpbmRcIl0gPSBcIkxvYWRQb3RQcmVzZXRcIiwgfSwgW1wiY29udHJvbF9raW5kXCJdID0gXCJ0cmlnZ2VyXCIsfVxubG9jYWwgYXZhaWxh + YmxlID0geSBhbmQgeS5hdmFpbGFibGUgb3IgZmFsc2VcbmxvY2FsIGxhYmVsID0gYXZhaWxhYmxlIGFuZCB5LmxhYmVsIG9yIFwiXCJcbmxvY2FsIG5hbWVfMSA9IGF2 + YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEsIDkpIG9yIFwiXCJcbmxvY2FsIG5hbWVfMiA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEw + LCAxOCkgb3IgXCJcIlxubG9jYWwgbmFtZV8zID0gYXZhaWxhYmxlIGFuZCBzdHJpbmcuc3ViKHkubmFtZSwgMTksIDI3KSBvciBcIlwiXG5sb2NhbCBwcm9ncmVzcyA9 + IChhdmFpbGFibGUgYW5kIHkuaW5kZXggYW5kIHkuY291bnQpIGFuZCAoXCJcIiAuLiAoeS5pbmRleCArIDEpIC4uIFwiL1wiIC4uIHkuY291bnQpIG9yIFwiXCJcbmxv + Y2FsIHNlY29uZGFyeV9wcm9wID0gYXZhaWxhYmxlIGFuZCB5LnNlY29uZGFyeV9wcm9wIG9yIFwiXCJcbmxvY2FsIGNvbG9yID0gYXZhaWxhYmxlIGFuZCAoY29udGV4 + dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIGFjdGlvbl9uYW1lID0gYXZhaWxhYmxlIGFuZCBhY3Rpb24gYW5kIGFjdGlvbi5u + YW1lIG9yIG5pbFxubG9jYWwgYWN0aW9uX2NvbG9yID0gYXZhaWxhYmxlIGFuZCAoYWN0aW9uIGFuZCBhY3Rpb24uY29sb3Igb3IgbmlsKSBvciBibGFja1xucmV0dXJu + IHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICAtLSBI + ZWFkZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBjb2xvciksXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJv + cF9jaGFuZ2UoY29sdW1uLCAwLCAxKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgbGFiZWwpLFxuICAgICAgICAgICAgY3Jl + YXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBwcm9ncmVzcyksXG4gICAgICAgICAgICAtLSBDb250ZW50XG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9w + X2NoYW5nZShjb2x1bW4sIDIsIG5hbWVfMSksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDMsIG5hbWVfMiksXG4gICAgICAgICAg + ICAtLSBGb290ZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAyLCBhY3Rpb25fY29sb3IpLFxuICAgICAgICAgICAgY3Jl + YXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgMCksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDQsIFwiXCIpLFxuICAg + ICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCA1LCBhY3Rpb25fbmFtZSBvciBzZWNvbmRhcnlfcHJvcCksXG4gICAgICAgIH0pLFxuICAgIH1c + bn1cbiJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJsb2NhbCBpbmRleCA9IGNv + bnRleHQucHJvcChcInRhcmdldC5kaXNjcmV0ZV92YWx1ZVwiKVxubG9jYWwgY291bnQgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZGlzY3JldGVfdmFsdWVfY291bnRc + IilcbmxvY2FsIG5hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQudGV4dF92YWx1ZVwiKSBvciBcIi1cIlxubG9jYWwgc2Vjb25kYXJ5X3Byb3Bfa2V5ID0gbmlsXG5s + b2NhbCBzZWNvbmRhcnlfcHJvcF92YWx1ZSA9IHNlY29uZGFyeV9wcm9wX2tleSBhbmQgY29udGV4dC5wcm9wKHNlY29uZGFyeV9wcm9wX2tleSkgb3IgXCJcIlxubG9j + YWwgYXZhaWxhYmxlID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LmF2YWlsYWJsZVwiKVxucmV0dXJuIHtcbiAgICBmZWVkYmFja19ldmVudCA9IHtcbiAgICAgICAgY29s + b3IgPSB7IFtcInJcIl0gPSAyNTUsIFtcImJcIl0gPSAzNCwgW1wiZ1wiXSA9IDg3LH0sXG4gICAgICAgIHZhbHVlID0ge1xuICAgICAgICAgICAgbGFiZWwgPSBcIlN1 + YiB0eXBlXCIsXG4gICAgICAgICAgICBuYW1lID0gbmFtZSxcbiAgICAgICAgICAgIHNlY29uZGFyeV9wcm9wID0gc2Vjb25kYXJ5X3Byb3BfdmFsdWUsXG4gICAgICAg + ICAgICBhdmFpbGFibGUgPSBhdmFpbGFibGUsXG4gICAgICAgICAgICBpbmRleCA9IGluZGV4LFxuICAgICAgICAgICAgY291bnQgPSBjb3VudCxcbiAgICAgICAgfVxu + ICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NjEsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifSwicG90RmlsdGVySXRlbUtpbmQiOiJTdWJDYXRl + Z29yeSJ9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlFJV3RqTDRsQjB5VTVzaGttcHVvbSIsIm5hbWUiOiJMb2FkIChjb250cm9sKSIsImdyb3VwSWQi + OiJicm93c2UtYWN0aW9ucyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjo1NiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MSwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjo2MCwiZnhBbmNob3IiOiJpbnN0 + YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoidmZtQWItM1hFV2stXzRGaUVRcWtpIiwibmFtZSI6IkxvYWQgKGZlZWRiYWNrKSIsImdyb3Vw + SWQiOiJicm93c2UtYWN0aW9ucyIsInNvdXJjZSI6eyJ0eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJsdWEiLCJtaWRpU2NyaXB0IjoiLS0gIyMgQ29uc3RhbnRzICMj + XG5cbmxvY2FsIGNvbHVtbl9rbm9iX2FkZHJlc3Nfb2Zmc2V0ID0gNTAwMFxubG9jYWwgbGVkX2FkZHJlc3Nfb2Zmc2V0ID0gMTAwMDBcbmxvY2FsIGJsYWNrID0geyBy + ID0gMCwgZyA9IDAsIGIgPSAwIH1cbmxvY2FsIHdoaXRlID0geyByID0gMjU1LCBnID0gMjU1LCBiID0gMjU1IH1cblxuLS0gIyMgRnVuY3Rpb25zICMjXG5cbmZ1bmN0 + aW9uIHRvX2FzY2lpKHRleHQpXG4gICAgbG9jYWwgYnl0ZXMgPSB7fVxuICAgIGlmIHRleHQgfj0gbmlsIHRoZW5cbiAgICAgICAgZm9yIGkgPSAxLCBzdHJpbmcubGVu + KHRleHQpIGRvXG4gICAgICAgICAgICBsb2NhbCBieXRlID0gc3RyaW5nLmJ5dGUodGV4dCwgaSlcbiAgICAgICAgICAgIGlmIGJ5dGUgPCAxMjggdGhlblxuICAgICAg + ICAgICAgICAgIHRhYmxlLmluc2VydChieXRlcywgYnl0ZSlcbiAgICAgICAgICAgIGVuZFxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICAtLSBOdWxsIHRlcm1pbmF0 + b3JcbiAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIDApXG4gICAgcmV0dXJuIGJ5dGVzXG5lbmRcblxuZnVuY3Rpb24gY29uY2F0X3RhYmxlKHQxLCB0MilcbiAgICBmb3Ig + aSA9IDEsICN0MiBkb1xuICAgICAgICB0MVsjdDEgKyAxXSA9IHQyW2ldXG4gICAgZW5kXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX21zZyhjb250ZW50KVxuICAgIC0t + IFN5c0V4IEhlYWRlclxuICAgIGxvY2FsIG1zZyA9IHtcbiAgICAgICAgMHhmMCwgMHgwMCwgMHgyMCwgMHgyOSwgMHgwMiwgMHgwYSwgMHgwMSxcbiAgICB9XG4gICAg + LS0gQ29udGVudFxuICAgIGNvbmNhdF90YWJsZShtc2csIGNvbnRlbnQpXG4gICAgLS0gRW5kIG9mIFN5c0V4XG4gICAgdGFibGUuaW5zZXJ0KG1zZywgMHhmNylcbiAg + ICByZXR1cm4gbXNnXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgbGF5b3V0LlxuLS0tXG4tLS0gQHBhcmFtIGxheW91dF9pbmRl + eCBudW1iZXIgbGF5b3V0IGluZGV4ICgwID0gZW1wdHksIDEgPSBrbm9iLCAyID0gYm94KVxuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9sYXlvdXRfbXNnKGxheW91dF9p + bmRleClcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7IDB4MDEsIGxheW91dF9pbmRleCB9KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgZGlzcGxheWlu + ZyBhIG5vdGlmaWNhdGlvbiBvbiB0aGUgY2VudGVyIHNjcmVlbi5cbi0tLVxuLS0tIEBwYXJhbSBsaW5lMSBzdHJpbmcgZmlyc3QgbGluZSBvZiB0ZXh0XG4tLS0gQHBh + cmFtIGxpbmUyIHN0cmluZyBzZWNvbmQgbGluZSBvZiB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfbm90aWZpY2F0aW9uX3RleHRfbXNnKGxpbmUxLCBsaW5lMilcbiAgICBs + b2NhbCBjb250ZW50ID0geyAweDA0IH1cbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTEpKVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0 + b19hc2NpaShsaW5lMikpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIG11bHRp + cGxlIHNjcmVlbiBwcm9wZXJ0aWVzLlxuLS0tXG4tLS0gQHBhcmFtIGNoYW5nZXMgdGFibGUgbGlzdCBvZiBwcm9wZXJ0eSBjaGFuZ2VzXG5mdW5jdGlvbiBjcmVhdGVf + c2NyZWVuX3Byb3BzX21zZyhjaGFuZ2VzKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDIgfVxuICAgIGZvciBpID0gMSwgI2NoYW5nZXMgZG9cbiAgICAgICAgaWYg + Y2hhbmdlc1tpXSB+PSBuaWwgdGhlblxuICAgICAgICAgICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIGNoYW5nZXNbaV0pXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAg + IHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB0ZXh0IHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5f + aW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRp + b24gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSB0ZXh0IHN0cmluZyB0aGUgYWN0dWFsIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdl + KGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCB0ZXh0KVxuICAgIGxvY2FsIGNoYW5nZSA9IHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVt + bl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlRleHRcIlxuICAgICAgICAweDAxLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2Jq + ZWN0X2luZGV4LFxuICAgIH1cbiAgICBjb25jYXRfdGFibGUoY2hhbmdlLCB0b19hc2NpaSh0ZXh0KSlcbiAgICByZXR1cm4gY2hhbmdlXG5lbmRcblxuLS0tIENyZWF0 + ZXMgYSB2YWx1ZSBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiBpdCdzIGFib3V0IHNldHRpbmcgdGhlIGtub2IgdmFsdWUsIGl0J3MgdXN1YWxseSBtb3JlIGlu + dHVpdGl2ZSB0byBub3Rcbi0tLSBkbyB0aGlzIHZpYSBzY3JpcHQgYnV0IGJ5IGVuYWJsaW5nIGZlZWRiYWNrIG9uIHRoZSBjb3JyZXNwb25kaW5nIGVuY29kZXJcbi0t + LSBtYXBwaW5ncywgdGhlbiB5b3UgbmVlZCBqdXN0IG9uZSBtYXBwaW5nIGZvciBib3RoIGNvbnRyb2wgYW5kIGZlZWRiYWNrLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVt + bl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgdmFsdWVcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciB0aGUga2luZCBvZiB2 + YWx1ZSB0byBjaGFuZ2UgKHNlZSBwcm9ncmFtbWVyJ3MgZ3VpZGUpXG4tLS0gQHBhcmFtIHZhbHVlIG51bWJlciB0aGUgdmFsdWVcbmZ1bmN0aW9uIGNyZWF0ZV92YWx1 + ZV9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdmFsdWUpXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAg + IGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlZhbHVlXCJcbiAgICAgICAgMHgwMyxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAg + ICAgIG9iamVjdF9pbmRleCxcbiAgICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgdmFsdWVcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYW4gUkdCIGNvbG9y + IHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIElmIHRoZSBnaXZlbiBjb2xvciBpcyBgbmlsYCwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGBuaWxgLlxuLS0tXG4tLS0g + QHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBp + biB3aGljaCBsb2NhdGlvbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGll + cyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgY29sb3IpXG4gICAgaWYg + Y29sb3IgPT0gbnVsbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAg + Y29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiUkdCIGNvbG9yXCJcbiAgICAgICAgMHgwNCxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4g + ICAgICAgIG9iamVjdF9pbmRleCxcbiAgICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxv + b3IoY29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFu + Z2luZyB0aGUgc3RhdGUgb2YgYW4gTEVELlxuLS0tXG4tLS0gUGFzc2luZyBhIGBuaWxgIGNvbG9yIHdpbGwgbWFrZSB0aGUgTEVEIGdvIG9mZi5cbi0tLVxuLS0tIEBw + YXJhbSBsZWRfaW5kZXggbnVtYmVyIHdoaWNoIExFRCB0byB0YWxrIHRvXG4tLS0gQHBhcmFtIGxlZF9iZWhhdmlvciBudW1iZXIgTEVEIGJlaGF2aW9yICgxID0gc29s + aWQsIDIgPSBmbGFzaGluZyB3aXRoIHByZXZpb3VzbHkgc2V0IHNvbGlkIGNvbG9yLCAzID0gcHVsc2F0aW5nKVxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdC + IGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9sZWRfbXNnKGxlZF9pbmRleCwgbGVkX2JlaGF2aW9yLCBjb2xv + cilcbiAgICBpZiBjb2xvciA9PSBuaWwgdGhlblxuICAgICAgICBjb2xvciA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0 + ZV9tc2coe1xuICAgICAgICAweDAzLFxuICAgICAgICBsZWRfaW5kZXgsXG4gICAgICAgIGxlZF9iZWhhdmlvcixcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8g + MiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9KVxuZW5kXG5cbmZ1bmN0aW9u + IGNyZWF0ZV9sZWZ0X3JpZ2h0X2FuaW1hdGlvbihnbG9iYWxfbWlsbGlzLCBtYXhfY2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB0ZXh0KVxuICAgIGlmIHRleHQgPT0g + bmlsIHRoZW5cbiAgICAgICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIGlmICN0ZXh0ID4gbWF4X2NoYXJfY291bnQgdGhlblxuICAgICAgICBsb2NhbCBmcmFtZV9j + b3VudCA9ICN0ZXh0IC0gbWF4X2NoYXJfY291bnRcbiAgICAgICAgbG9jYWwgZnJhbWVfaW5kZXggPSBtYXRoLmZsb29yKGdsb2JhbF9taWxsaXMgLyBmcmFtZV9sZW5n + dGgpICUgKGZyYW1lX2NvdW50ICogMilcbiAgICAgICAgbG9jYWwgdGV4dF9vZmZzZXRcbiAgICAgICAgaWYgZnJhbWVfaW5kZXggPCBmcmFtZV9jb3VudCB0aGVuXG4g + ICAgICAgICAgICB0ZXh0X29mZnNldCA9IGZyYW1lX2luZGV4XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGxvY2FsIGRpc3RhbmNlID0gZnJhbWVfaW5kZXggLSBm + cmFtZV9jb3VudFxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9jb3VudCAtIGRpc3RhbmNlXG4gICAgICAgIGVuZFxuICAgICAgICByZXR1cm4gdGV4dDpz + dWIodGV4dF9vZmZzZXQgKyAxLCB0ZXh0X29mZnNldCArIG1heF9jaGFyX2NvdW50KVxuICAgIGVsc2VcbiAgICAgICAgcmV0dXJuIHRleHRcbiAgICBlbmRcbmVuZFxu + XG4tLSAjIyBDb2RlICMjXG5sb2NhbCBjb2x1bW4gPSA1XG5sb2NhbCBjb2xvciA9ICh5IGFuZCB5LmF2YWlsYWJsZSkgYW5kIHsgW1wiclwiXSA9IDE4MywgW1wiYlwi + XSA9IDI4LCBbXCJnXCJdID0gMjgsfSBvciBuaWxcblxubG9jYWwgbGVkX2luZGV4ID0gNCArIGNvbHVtblxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gbGVkX2FkZHJl + c3Nfb2Zmc2V0ICsgbGVkX2luZGV4LFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIDEsIGNvbG9yKSxcbiAgICB9XG59 + XG4ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoibG9jYWwgYXZhaWxhYmxlID0g + Y29udGV4dC5wcm9wKFwidGFyZ2V0LmF2YWlsYWJsZVwiKVxucmV0dXJuIHtcbiAgICBmZWVkYmFja19ldmVudCA9IHtcbiAgICAgICAgdmFsdWUgPSB7XG4gICAgICAg + ICAgICBhdmFpbGFibGUgPSBhdmFpbGFibGUsXG4gICAgICAgIH1cbiAgICB9LFxufVxuIiwiZmVlZGJhY2tUeXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjYwLCJmeEFu + Y2hvciI6Imluc3RhbmNlIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IlhvN2RER0ZYcTZrS2xWRGI3cmd4QSIsIm5hbWUiOiJFbmNvZGVyIDcgLSBD + aGFyYWN0ZXIiLCJncm91cElkIjoiYnJvd3NlLWNvbHVtbnMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MTUsIm51bWJlciI6MjcsImNoYXJhY3RlciI6MiwiaXMxNEJpdCI6 + ZmFsc2V9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOi0xMCwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo2MSwiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9LCJwb3RG + aWx0ZXJJdGVtS2luZCI6Ik1vZGUifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNlRqUkw2SkpwaE1xQThyOG1TalJZIiwibmFtZSI6IlNjcmVlbiA3 + IC0gQnJvd3NlIHByb2R1Y3RzIiwiZ3JvdXBJZCI6ImJyb3dzZS1jb2x1bW5zIiwic291cmNlIjp7InR5cGUiOjExLCJtaWRpU2NyaXB0S2luZCI6Imx1YSIsIm1pZGlT + Y3JpcHQiOiItLSAjIyBDb25zdGFudHMgIyNcblxubG9jYWwgY29sdW1uX2tub2JfYWRkcmVzc19vZmZzZXQgPSA1MDAwXG5sb2NhbCBsZWRfYWRkcmVzc19vZmZzZXQg + PSAxMDAwMFxubG9jYWwgYmxhY2sgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxubG9jYWwgd2hpdGUgPSB7IHIgPSAyNTUsIGcgPSAyNTUsIGIgPSAyNTUgfVxuXG4t + LSAjIyBGdW5jdGlvbnMgIyNcblxuZnVuY3Rpb24gdG9fYXNjaWkodGV4dClcbiAgICBsb2NhbCBieXRlcyA9IHt9XG4gICAgaWYgdGV4dCB+PSBuaWwgdGhlblxuICAg + ICAgICBmb3IgaSA9IDEsIHN0cmluZy5sZW4odGV4dCkgZG9cbiAgICAgICAgICAgIGxvY2FsIGJ5dGUgPSBzdHJpbmcuYnl0ZSh0ZXh0LCBpKVxuICAgICAgICAgICAg + aWYgYnl0ZSA8IDEyOCB0aGVuXG4gICAgICAgICAgICAgICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCBieXRlKVxuICAgICAgICAgICAgZW5kXG4gICAgICAgIGVuZFxuICAg + IGVuZFxuICAgIC0tIE51bGwgdGVybWluYXRvclxuICAgIHRhYmxlLmluc2VydChieXRlcywgMClcbiAgICByZXR1cm4gYnl0ZXNcbmVuZFxuXG5mdW5jdGlvbiBjb25j + YXRfdGFibGUodDEsIHQyKVxuICAgIGZvciBpID0gMSwgI3QyIGRvXG4gICAgICAgIHQxWyN0MSArIDFdID0gdDJbaV1cbiAgICBlbmRcbmVuZFxuXG5mdW5jdGlvbiBj + cmVhdGVfbXNnKGNvbnRlbnQpXG4gICAgLS0gU3lzRXggSGVhZGVyXG4gICAgbG9jYWwgbXNnID0ge1xuICAgICAgICAweGYwLCAweDAwLCAweDIwLCAweDI5LCAweDAy + LCAweDBhLCAweDAxLFxuICAgIH1cbiAgICAtLSBDb250ZW50XG4gICAgY29uY2F0X3RhYmxlKG1zZywgY29udGVudClcbiAgICAtLSBFbmQgb2YgU3lzRXhcbiAgICB0 + YWJsZS5pbnNlcnQobXNnLCAweGY3KVxuICAgIHJldHVybiBtc2dcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBsYXlvdXQuXG4t + LS1cbi0tLSBAcGFyYW0gbGF5b3V0X2luZGV4IG51bWJlciBsYXlvdXQgaW5kZXggKDAgPSBlbXB0eSwgMSA9IGtub2IsIDIgPSBib3gpXG5mdW5jdGlvbiBjcmVhdGVf + c2NyZWVuX2xheW91dF9tc2cobGF5b3V0X2luZGV4KVxuICAgIHJldHVybiBjcmVhdGVfbXNnKHsgMHgwMSwgbGF5b3V0X2luZGV4IH0pXG5lbmRcblxuLS0tIENyZWF0 + ZXMgYSBtZXNzYWdlIGZvciBkaXNwbGF5aW5nIGEgbm90aWZpY2F0aW9uIG9uIHRoZSBjZW50ZXIgc2NyZWVuLlxuLS0tXG4tLS0gQHBhcmFtIGxpbmUxIHN0cmluZyBm + aXJzdCBsaW5lIG9mIHRleHRcbi0tLSBAcGFyYW0gbGluZTIgc3RyaW5nIHNlY29uZCBsaW5lIG9mIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV9ub3RpZmljYXRpb25fdGV4 + dF9tc2cobGluZTEsIGxpbmUyKVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDQgfVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMSkpXG4g + ICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUyKSlcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEg + bWVzc2FnZSBmb3IgY2hhbmdpbmcgbXVsdGlwbGUgc2NyZWVuIHByb3BlcnRpZXMuXG4tLS1cbi0tLSBAcGFyYW0gY2hhbmdlcyB0YWJsZSBsaXN0IG9mIHByb3BlcnR5 + IGNoYW5nZXNcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fcHJvcHNfbXNnKGNoYW5nZXMpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwMiB9XG4gICAgZm9yIGkgPSAx + LCAjY2hhbmdlcyBkb1xuICAgICAgICBpZiBjaGFuZ2VzW2ldIH49IG5pbCB0aGVuXG4gICAgICAgICAgICBjb25jYXRfdGFibGUoY29udGVudCwgY2hhbmdlc1tpXSlc + biAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHRleHQgcHJvcGVydHkgY2hhbmdl + LlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2ggY29sdW1uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gb2JqZWN0X2lu + ZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIHRleHQgc3RyaW5nIHRoZSBhY3R1YWwgdGV4dFxuZnVuY3Rp + b24gY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHRleHQpXG4gICAgbG9jYWwgY2hhbmdlID0ge1xuICAgICAgICAtLSBD + b2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiVGV4dFwiXG4gICAgICAgIDB4MDEsXG4gICAgICAgIC0t + IE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgfVxuICAgIGNvbmNhdF90YWJsZShjaGFuZ2UsIHRvX2FzY2lpKHRleHQpKVxuICAgIHJldHVy + biBjaGFuZ2VcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIHZhbHVlIHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIElmIGl0J3MgYWJvdXQgc2V0dGluZyB0aGUga25vYiB2 + YWx1ZSwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIG5vdFxuLS0tIGRvIHRoaXMgdmlhIHNjcmlwdCBidXQgYnkgZW5hYmxpbmcgZmVlZGJhY2sgb24gdGhl + IGNvcnJlc3BvbmRpbmcgZW5jb2RlclxuLS0tIG1hcHBpbmdzLCB0aGVuIHlvdSBuZWVkIGp1c3Qgb25lIG1hcHBpbmcgZm9yIGJvdGggY29udHJvbCBhbmQgZmVlZGJh + Y2suXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSB2YWx1ZVxuLS0tIEBwYXJhbSBvYmplY3Rf + aW5kZXggbnVtYmVyIHRoZSBraW5kIG9mIHZhbHVlIHRvIGNoYW5nZSAoc2VlIHByb2dyYW1tZXIncyBndWlkZSlcbi0tLSBAcGFyYW0gdmFsdWUgbnVtYmVyIHRoZSB2 + YWx1ZVxuZnVuY3Rpb24gY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2luZGV4LCB2YWx1ZSlcbiAgICByZXR1cm4ge1xuICAgICAg + ICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAtLSBQcm9wZXJ0eSBUeXBlIFwiVmFsdWVcIlxuICAgICAgICAweDAzLFxuICAg + ICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICB2YWx1ZVxuICAgIH1cbmVuZFxu + XG4tLS0gQ3JlYXRlcyBhbiBSR0IgY29sb3IgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgdGhlIGdpdmVuIGNvbG9yIGlzIGBuaWxgLCB0aGlzIGZ1bmN0aW9u + IHJldHVybnMgYG5pbGAuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBw + YXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBj + b2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2Jq + ZWN0X2luZGV4LCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBudWxsIHRoZW5cbiAgICAgICAgcmV0dXJuIG5pbFxuICAgIGVuZFxuICAgIHJldHVybiB7XG4gICAgICAg + IC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJSR0IgY29sb3JcIlxuICAgICAgICAweDA0LFxu + ICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgICAgICAtLSBDb2xvciBieXRlc1xuICAgICAgICBtYXRoLmZsb29yKGNvbG9y + LnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIpLFxuICAgIH1cbmVuZFxuXG4tLS0g + Q3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIHRoZSBzdGF0ZSBvZiBhbiBMRUQuXG4tLS1cbi0tLSBQYXNzaW5nIGEgYG5pbGAgY29sb3Igd2lsbCBtYWtlIHRo + ZSBMRUQgZ28gb2ZmLlxuLS0tXG4tLS0gQHBhcmFtIGxlZF9pbmRleCBudW1iZXIgd2hpY2ggTEVEIHRvIHRhbGsgdG9cbi0tLSBAcGFyYW0gbGVkX2JlaGF2aW9yIG51 + bWJlciBMRUQgYmVoYXZpb3IgKDEgPSBzb2xpZCwgMiA9IGZsYXNoaW5nIHdpdGggcHJldmlvdXNseSBzZXQgc29saWQgY29sb3IsIDMgPSBwdWxzYXRpbmcpXG4tLS0g + QHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24gY3JlYXRlX2xlZF9tc2cobGVk + X2luZGV4LCBsZWRfYmVoYXZpb3IsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG5pbCB0aGVuXG4gICAgICAgIGNvbG9yID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1c + biAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyh7XG4gICAgICAgIDB4MDMsXG4gICAgICAgIGxlZF9pbmRleCxcbiAgICAgICAgbGVkX2JlaGF2aW9yLFxuICAg + ICAgICBtYXRoLmZsb29yKGNvbG9yLnIgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5nIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuYiAvIDIp + LFxuICAgIH0pXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX2xlZnRfcmlnaHRfYW5pbWF0aW9uKGdsb2JhbF9taWxsaXMsIG1heF9jaGFyX2NvdW50LCBmcmFtZV9sZW5n + dGgsIHRleHQpXG4gICAgaWYgdGV4dCA9PSBuaWwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgaWYgI3RleHQgPiBtYXhfY2hhcl9jb3VudCB0 + aGVuXG4gICAgICAgIGxvY2FsIGZyYW1lX2NvdW50ID0gI3RleHQgLSBtYXhfY2hhcl9jb3VudFxuICAgICAgICBsb2NhbCBmcmFtZV9pbmRleCA9IG1hdGguZmxvb3Io + Z2xvYmFsX21pbGxpcyAvIGZyYW1lX2xlbmd0aCkgJSAoZnJhbWVfY291bnQgKiAyKVxuICAgICAgICBsb2NhbCB0ZXh0X29mZnNldFxuICAgICAgICBpZiBmcmFtZV9p + bmRleCA8IGZyYW1lX2NvdW50IHRoZW5cbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfaW5kZXhcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgbG9jYWwg + ZGlzdGFuY2UgPSBmcmFtZV9pbmRleCAtIGZyYW1lX2NvdW50XG4gICAgICAgICAgICB0ZXh0X29mZnNldCA9IGZyYW1lX2NvdW50IC0gZGlzdGFuY2VcbiAgICAgICAg + ZW5kXG4gICAgICAgIHJldHVybiB0ZXh0OnN1Yih0ZXh0X29mZnNldCArIDEsIHRleHRfb2Zmc2V0ICsgbWF4X2NoYXJfY291bnQpXG4gICAgZWxzZVxuICAgICAgICBy + ZXR1cm4gdGV4dFxuICAgIGVuZFxuZW5kXG5cbi0tICMjIENvZGUgIyNcbmxvY2FsIGNvbHVtbiA9IDZcbmxvY2FsIGFjdGlvbiA9IG5pbFxubG9jYWwgYXZhaWxhYmxl + ID0geSBhbmQgeS5hdmFpbGFibGUgb3IgZmFsc2VcbmxvY2FsIGxhYmVsID0gYXZhaWxhYmxlIGFuZCB5LmxhYmVsIG9yIFwiXCJcbmxvY2FsIG5hbWVfMSA9IGF2YWls + YWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEsIDkpIG9yIFwiXCJcbmxvY2FsIG5hbWVfMiA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1Yih5Lm5hbWUsIDEwLCAx + OCkgb3IgXCJcIlxubG9jYWwgbmFtZV8zID0gYXZhaWxhYmxlIGFuZCBzdHJpbmcuc3ViKHkubmFtZSwgMTksIDI3KSBvciBcIlwiXG5sb2NhbCBwcm9ncmVzcyA9IChh + dmFpbGFibGUgYW5kIHkuaW5kZXggYW5kIHkuY291bnQpIGFuZCAoXCJcIiAuLiAoeS5pbmRleCArIDEpIC4uIFwiL1wiIC4uIHkuY291bnQpIG9yIFwiXCJcbmxvY2Fs + IHNlY29uZGFyeV9wcm9wID0gYXZhaWxhYmxlIGFuZCB5LnNlY29uZGFyeV9wcm9wIG9yIFwiXCJcbmxvY2FsIGNvbG9yID0gYXZhaWxhYmxlIGFuZCAoY29udGV4dC5m + ZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIGFjdGlvbl9uYW1lID0gYXZhaWxhYmxlIGFuZCBhY3Rpb24gYW5kIGFjdGlvbi5uYW1l + IG9yIG5pbFxubG9jYWwgYWN0aW9uX2NvbG9yID0gYXZhaWxhYmxlIGFuZCAoYWN0aW9uIGFuZCBhY3Rpb24uY29sb3Igb3IgbmlsKSBvciBibGFja1xucmV0dXJuIHtc + biAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICAtLSBIZWFk + ZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBjb2xvciksXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9j + aGFuZ2UoY29sdW1uLCAwLCAxKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRl + X3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBwcm9ncmVzcyksXG4gICAgICAgICAgICAtLSBDb250ZW50XG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2No + YW5nZShjb2x1bW4sIDIsIG5hbWVfMSksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDMsIG5hbWVfMiksXG4gICAgICAgICAgICAt + LSBGb290ZXJcbiAgICAgICAgICAgIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uLCAyLCBhY3Rpb25fY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRl + X3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgMCksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW4sIDQsIFwiXCIpLFxuICAgICAg + ICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCA1LCBhY3Rpb25fbmFtZSBvciBzZWNvbmRhcnlfcHJvcCksXG4gICAgICAgIH0pLFxuICAgIH1cbn1c + biJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJsb2NhbCBpbmRleCA9IGNvbnRl + eHQucHJvcChcInRhcmdldC5kaXNjcmV0ZV92YWx1ZVwiKVxubG9jYWwgY291bnQgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZGlzY3JldGVfdmFsdWVfY291bnRcIilc + bmxvY2FsIG5hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQudGV4dF92YWx1ZVwiKSBvciBcIi1cIlxubG9jYWwgc2Vjb25kYXJ5X3Byb3Bfa2V5ID0gbmlsXG5sb2Nh + bCBzZWNvbmRhcnlfcHJvcF92YWx1ZSA9IHNlY29uZGFyeV9wcm9wX2tleSBhbmQgY29udGV4dC5wcm9wKHNlY29uZGFyeV9wcm9wX2tleSkgb3IgXCJcIlxubG9jYWwg + YXZhaWxhYmxlID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LmF2YWlsYWJsZVwiKVxucmV0dXJuIHtcbiAgICBmZWVkYmFja19ldmVudCA9IHtcbiAgICAgICAgY29sb3Ig + PSB7IFtcInJcIl0gPSAyNTUsIFtcImJcIl0gPSA1OSwgW1wiZ1wiXSA9IDIzNSx9LFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIGxhYmVsID0gXCJDaGFy + YWN0ZXJcIixcbiAgICAgICAgICAgIG5hbWUgPSBuYW1lLFxuICAgICAgICAgICAgc2Vjb25kYXJ5X3Byb3AgPSBzZWNvbmRhcnlfcHJvcF92YWx1ZSxcbiAgICAgICAg + ICAgIGF2YWlsYWJsZSA9IGF2YWlsYWJsZSxcbiAgICAgICAgICAgIGluZGV4ID0gaW5kZXgsXG4gICAgICAgICAgICBjb3VudCA9IGNvdW50LFxuICAgICAgICB9XG4g + ICAgfSxcbn1cbiIsImZlZWRiYWNrVHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjo2MSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9LCJwb3RGaWx0ZXJJdGVtS2luZCI6Ik1vZGUifSwi + Y29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJWajlLc1N2ejE0XzE0cnh0MXEycVQiLCJuYW1lIjoiRW5jb2RlciA4IC0gUHJlc2V0IiwiZ3JvdXBJZCI6ImJy + b3dzZS1jb2x1bW5zIiwic291cmNlIjp7ImNoYW5uZWwiOjE1LCJudW1iZXIiOjI4LCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjotMTAsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTgsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6InhkQmNGTTUyVEJyZTd1SEtfc3FrdiIsIm5hbWUiOiJTY3JlZW4gOCAtIEJyb3dzZSBwcm9kdWN0cyIsImdyb3VwSWQiOiJicm93c2UtY29sdW1ucyIsInNv + dXJjZSI6eyJ0eXBlIjoxMSwibWlkaVNjcmlwdEtpbmQiOiJsdWEiLCJtaWRpU2NyaXB0IjoiLS0gIyMgQ29uc3RhbnRzICMjXG5cbmxvY2FsIGNvbHVtbl9rbm9iX2Fk + ZHJlc3Nfb2Zmc2V0ID0gNTAwMFxubG9jYWwgbGVkX2FkZHJlc3Nfb2Zmc2V0ID0gMTAwMDBcbmxvY2FsIGJsYWNrID0geyByID0gMCwgZyA9IDAsIGIgPSAwIH1cbmxv + Y2FsIHdoaXRlID0geyByID0gMjU1LCBnID0gMjU1LCBiID0gMjU1IH1cblxuLS0gIyMgRnVuY3Rpb25zICMjXG5cbmZ1bmN0aW9uIHRvX2FzY2lpKHRleHQpXG4gICAg + bG9jYWwgYnl0ZXMgPSB7fVxuICAgIGlmIHRleHQgfj0gbmlsIHRoZW5cbiAgICAgICAgZm9yIGkgPSAxLCBzdHJpbmcubGVuKHRleHQpIGRvXG4gICAgICAgICAgICBs + b2NhbCBieXRlID0gc3RyaW5nLmJ5dGUodGV4dCwgaSlcbiAgICAgICAgICAgIGlmIGJ5dGUgPCAxMjggdGhlblxuICAgICAgICAgICAgICAgIHRhYmxlLmluc2VydChi + eXRlcywgYnl0ZSlcbiAgICAgICAgICAgIGVuZFxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICAtLSBOdWxsIHRlcm1pbmF0b3JcbiAgICB0YWJsZS5pbnNlcnQoYnl0 + ZXMsIDApXG4gICAgcmV0dXJuIGJ5dGVzXG5lbmRcblxuZnVuY3Rpb24gY29uY2F0X3RhYmxlKHQxLCB0MilcbiAgICBmb3IgaSA9IDEsICN0MiBkb1xuICAgICAgICB0 + MVsjdDEgKyAxXSA9IHQyW2ldXG4gICAgZW5kXG5lbmRcblxuZnVuY3Rpb24gY3JlYXRlX21zZyhjb250ZW50KVxuICAgIC0tIFN5c0V4IEhlYWRlclxuICAgIGxvY2Fs + IG1zZyA9IHtcbiAgICAgICAgMHhmMCwgMHgwMCwgMHgyMCwgMHgyOSwgMHgwMiwgMHgwYSwgMHgwMSxcbiAgICB9XG4gICAgLS0gQ29udGVudFxuICAgIGNvbmNhdF90 + YWJsZShtc2csIGNvbnRlbnQpXG4gICAgLS0gRW5kIG9mIFN5c0V4XG4gICAgdGFibGUuaW5zZXJ0KG1zZywgMHhmNylcbiAgICByZXR1cm4gbXNnXG5lbmRcblxuLS0t + IENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgbGF5b3V0LlxuLS0tXG4tLS0gQHBhcmFtIGxheW91dF9pbmRleCBudW1iZXIgbGF5b3V0IGluZGV4ICgw + ID0gZW1wdHksIDEgPSBrbm9iLCAyID0gYm94KVxuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9sYXlvdXRfbXNnKGxheW91dF9pbmRleClcbiAgICByZXR1cm4gY3JlYXRl + X21zZyh7IDB4MDEsIGxheW91dF9pbmRleCB9KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgZGlzcGxheWluZyBhIG5vdGlmaWNhdGlvbiBvbiB0aGUg + Y2VudGVyIHNjcmVlbi5cbi0tLVxuLS0tIEBwYXJhbSBsaW5lMSBzdHJpbmcgZmlyc3QgbGluZSBvZiB0ZXh0XG4tLS0gQHBhcmFtIGxpbmUyIHN0cmluZyBzZWNvbmQg + bGluZSBvZiB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfbm90aWZpY2F0aW9uX3RleHRfbXNnKGxpbmUxLCBsaW5lMilcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDA0IH1c + biAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTEpKVxuICAgIGNvbmNhdF90YWJsZShjb250ZW50LCB0b19hc2NpaShsaW5lMikpXG4gICAgcmV0 + dXJuIGNyZWF0ZV9tc2coY29udGVudClcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGNoYW5naW5nIG11bHRpcGxlIHNjcmVlbiBwcm9wZXJ0aWVzLlxu + LS0tXG4tLS0gQHBhcmFtIGNoYW5nZXMgdGFibGUgbGlzdCBvZiBwcm9wZXJ0eSBjaGFuZ2VzXG5mdW5jdGlvbiBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyhjaGFuZ2Vz + KVxuICAgIGxvY2FsIGNvbnRlbnQgPSB7IDB4MDIgfVxuICAgIGZvciBpID0gMSwgI2NoYW5nZXMgZG9cbiAgICAgICAgaWYgY2hhbmdlc1tpXSB+PSBuaWwgdGhlblxu + ICAgICAgICAgICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIGNoYW5nZXNbaV0pXG4gICAgICAgIGVuZFxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRl + bnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB0ZXh0IHByb3BlcnR5IGNoYW5nZS5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNv + bHVtbiB0byBkaXNwbGF5IHRoZSB0ZXh0XG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gZGlzcGxheSB0aGUgdGV4dFxu + LS0tIEBwYXJhbSB0ZXh0IHN0cmluZyB0aGUgYWN0dWFsIHRleHRcbmZ1bmN0aW9uIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbl9pbmRleCwgb2JqZWN0X2lu + ZGV4LCB0ZXh0KVxuICAgIGxvY2FsIGNoYW5nZSA9IHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJv + cGVydHkgVHlwZSBcIlRleHRcIlxuICAgICAgICAweDAxLFxuICAgICAgICAtLSBPYmplY3QgSW5kZXhcbiAgICAgICAgb2JqZWN0X2luZGV4LFxuICAgIH1cbiAgICBj + b25jYXRfdGFibGUoY2hhbmdlLCB0b19hc2NpaSh0ZXh0KSlcbiAgICByZXR1cm4gY2hhbmdlXG5lbmRcblxuLS0tIENyZWF0ZXMgYSB2YWx1ZSBwcm9wZXJ0eSBjaGFu + Z2UuXG4tLS1cbi0tLSBJZiBpdCdzIGFib3V0IHNldHRpbmcgdGhlIGtub2IgdmFsdWUsIGl0J3MgdXN1YWxseSBtb3JlIGludHVpdGl2ZSB0byBub3Rcbi0tLSBkbyB0 + aGlzIHZpYSBzY3JpcHQgYnV0IGJ5IGVuYWJsaW5nIGZlZWRiYWNrIG9uIHRoZSBjb3JyZXNwb25kaW5nIGVuY29kZXJcbi0tLSBtYXBwaW5ncywgdGhlbiB5b3UgbmVl + ZCBqdXN0IG9uZSBtYXBwaW5nIGZvciBib3RoIGNvbnRyb2wgYW5kIGZlZWRiYWNrLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1iZXIgaW4gd2hpY2gg + Y29sdW1uIHRvIGNoYW5nZSB0aGUgdmFsdWVcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciB0aGUga2luZCBvZiB2YWx1ZSB0byBjaGFuZ2UgKHNlZSBwcm9n + cmFtbWVyJ3MgZ3VpZGUpXG4tLS0gQHBhcmFtIHZhbHVlIG51bWJlciB0aGUgdmFsdWVcbmZ1bmN0aW9uIGNyZWF0ZV92YWx1ZV9wcm9wX2NoYW5nZShjb2x1bW5faW5k + ZXgsIG9iamVjdF9pbmRleCwgdmFsdWUpXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAg + LS0gUHJvcGVydHkgVHlwZSBcIlZhbHVlXCJcbiAgICAgICAgMHgwMyxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICAg + ICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgdmFsdWVcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYW4gUkdCIGNvbG9yIHByb3BlcnR5IGNoYW5nZS5cbi0tLVxu + LS0tIElmIHRoZSBnaXZlbiBjb2xvciBpcyBgbmlsYCwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGBuaWxgLlxuLS0tXG4tLS0gQHBhcmFtIGNvbHVtbl9pbmRleCBudW1i + ZXIgaW4gd2hpY2ggY29sdW1uIHRvIGNoYW5nZSB0aGUgY29sb3Jcbi0tLSBAcGFyYW0gb2JqZWN0X2luZGV4IG51bWJlciBpbiB3aGljaCBsb2NhdGlvbiB0byBjaGFu + Z2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIGNvbG9yIHRhYmxlIHRoZSBSR0IgY29sb3IgKHRhYmxlIHdpdGggcHJvcGVydGllcyByLCBnIGFuZCBiKVxuZnVuY3Rpb24g + Y3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbnVsbCB0aGVuXG4gICAg + ICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICByZXR1cm4ge1xuICAgICAgICAtLSBDb2x1bW4gSW5kZXhcbiAgICAgICAgY29sdW1uX2luZGV4LFxuICAgICAgICAt + LSBQcm9wZXJ0eSBUeXBlIFwiUkdCIGNvbG9yXCJcbiAgICAgICAgMHgwNCxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAg + ICAgICAgLS0gQ29sb3IgYnl0ZXNcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuZyAvIDIpLFxuICAgICAg + ICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9XG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyB0aGUgc3RhdGUgb2YgYW4gTEVE + LlxuLS0tXG4tLS0gUGFzc2luZyBhIGBuaWxgIGNvbG9yIHdpbGwgbWFrZSB0aGUgTEVEIGdvIG9mZi5cbi0tLVxuLS0tIEBwYXJhbSBsZWRfaW5kZXggbnVtYmVyIHdo + aWNoIExFRCB0byB0YWxrIHRvXG4tLS0gQHBhcmFtIGxlZF9iZWhhdmlvciBudW1iZXIgTEVEIGJlaGF2aW9yICgxID0gc29saWQsIDIgPSBmbGFzaGluZyB3aXRoIHBy + ZXZpb3VzbHkgc2V0IHNvbGlkIGNvbG9yLCAzID0gcHVsc2F0aW5nKVxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3Bl + cnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9sZWRfbXNnKGxlZF9pbmRleCwgbGVkX2JlaGF2aW9yLCBjb2xvcilcbiAgICBpZiBjb2xvciA9PSBuaWwg + dGhlblxuICAgICAgICBjb2xvciA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG4gICAgZW5kXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coe1xuICAgICAgICAweDAzLFxu + ICAgICAgICBsZWRfaW5kZXgsXG4gICAgICAgIGxlZF9iZWhhdmlvcixcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5yIC8gMiksXG4gICAgICAgIG1hdGguZmxvb3Io + Y29sb3IuZyAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmIgLyAyKSxcbiAgICB9KVxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9sZWZ0X3JpZ2h0X2FuaW1h + dGlvbihnbG9iYWxfbWlsbGlzLCBtYXhfY2hhcl9jb3VudCwgZnJhbWVfbGVuZ3RoLCB0ZXh0KVxuICAgIGlmIHRleHQgPT0gbmlsIHRoZW5cbiAgICAgICAgcmV0dXJu + IG5pbFxuICAgIGVuZFxuICAgIGlmICN0ZXh0ID4gbWF4X2NoYXJfY291bnQgdGhlblxuICAgICAgICBsb2NhbCBmcmFtZV9jb3VudCA9ICN0ZXh0IC0gbWF4X2NoYXJf + Y291bnRcbiAgICAgICAgbG9jYWwgZnJhbWVfaW5kZXggPSBtYXRoLmZsb29yKGdsb2JhbF9taWxsaXMgLyBmcmFtZV9sZW5ndGgpICUgKGZyYW1lX2NvdW50ICogMilc + biAgICAgICAgbG9jYWwgdGV4dF9vZmZzZXRcbiAgICAgICAgaWYgZnJhbWVfaW5kZXggPCBmcmFtZV9jb3VudCB0aGVuXG4gICAgICAgICAgICB0ZXh0X29mZnNldCA9 + IGZyYW1lX2luZGV4XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGxvY2FsIGRpc3RhbmNlID0gZnJhbWVfaW5kZXggLSBmcmFtZV9jb3VudFxuICAgICAgICAgICAg + dGV4dF9vZmZzZXQgPSBmcmFtZV9jb3VudCAtIGRpc3RhbmNlXG4gICAgICAgIGVuZFxuICAgICAgICByZXR1cm4gdGV4dDpzdWIodGV4dF9vZmZzZXQgKyAxLCB0ZXh0 + X29mZnNldCArIG1heF9jaGFyX2NvdW50KVxuICAgIGVsc2VcbiAgICAgICAgcmV0dXJuIHRleHRcbiAgICBlbmRcbmVuZFxuXG4tLSAjIyBDb2RlICMjXG5sb2NhbCBj + b2x1bW4gPSA3XG5sb2NhbCBhY3Rpb24gPSBuaWxcbmxvY2FsIGF2YWlsYWJsZSA9IHkgYW5kIHkuYXZhaWxhYmxlIG9yIGZhbHNlXG5sb2NhbCBsYWJlbCA9IGF2YWls + YWJsZSBhbmQgeS5sYWJlbCBvciBcIlwiXG5sb2NhbCBuYW1lXzEgPSBhdmFpbGFibGUgYW5kIHN0cmluZy5zdWIoeS5uYW1lLCAxLCA5KSBvciBcIlwiXG5sb2NhbCBu + YW1lXzIgPSBhdmFpbGFibGUgYW5kIHN0cmluZy5zdWIoeS5uYW1lLCAxMCwgMTgpIG9yIFwiXCJcbmxvY2FsIG5hbWVfMyA9IGF2YWlsYWJsZSBhbmQgc3RyaW5nLnN1 + Yih5Lm5hbWUsIDE5LCAyNykgb3IgXCJcIlxubG9jYWwgcHJvZ3Jlc3MgPSAoYXZhaWxhYmxlIGFuZCB5LmluZGV4IGFuZCB5LmNvdW50KSBhbmQgKFwiXCIgLi4gKHku + aW5kZXggKyAxKSAuLiBcIi9cIiAuLiB5LmNvdW50KSBvciBcIlwiXG5sb2NhbCBzZWNvbmRhcnlfcHJvcCA9IGF2YWlsYWJsZSBhbmQgeS5zZWNvbmRhcnlfcHJvcCBv + ciBcIlwiXG5sb2NhbCBjb2xvciA9IGF2YWlsYWJsZSBhbmQgKGNvbnRleHQuZmVlZGJhY2tfZXZlbnQuY29sb3Igb3Igd2hpdGUpIG9yIGJsYWNrXG5sb2NhbCBhY3Rp + b25fbmFtZSA9IGF2YWlsYWJsZSBhbmQgYWN0aW9uIGFuZCBhY3Rpb24ubmFtZSBvciBuaWxcbmxvY2FsIGFjdGlvbl9jb2xvciA9IGF2YWlsYWJsZSBhbmQgKGFjdGlv + biBhbmQgYWN0aW9uLmNvbG9yIG9yIG5pbCkgb3IgYmxhY2tcbnJldHVybiB7XG4gICAgYWRkcmVzcyA9IGNvbHVtbixcbiAgICBtZXNzYWdlcyA9IHtcbiAgICAgICAg + Y3JlYXRlX3NjcmVlbl9wcm9wc19tc2coe1xuICAgICAgICAgICAgLS0gSGVhZGVyXG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVt + biwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3ZhbHVlX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgMSksXG4gICAgICAgICAgICBjcmVhdGVfdGV4dF9wcm9w + X2NoYW5nZShjb2x1bW4sIDAsIGxhYmVsKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMSwgcHJvZ3Jlc3MpLFxuICAgICAgICAg + ICAgLS0gQ29udGVudFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAyLCBuYW1lXzEpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRf + cHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBuYW1lXzIpLFxuICAgICAgICAgICAgLS0gRm9vdGVyXG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdl + KGNvbHVtbiwgMiwgYWN0aW9uX2NvbG9yKSxcbiAgICAgICAgICAgIGNyZWF0ZV92YWx1ZV9wcm9wX2NoYW5nZShjb2x1bW4sIDIsIDApLFxuICAgICAgICAgICAgY3Jl + YXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCA0LCBcIlwiKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgNSwgYWN0aW9uX25h + bWUgb3Igc2Vjb25kYXJ5X3Byb3ApLFxuICAgICAgICB9KSxcbiAgICB9XG59XG4ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJl + ZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoibG9jYWwgaW5kZXggPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZGlzY3JldGVfdmFsdWVcIilcbmxvY2FsIGNvdW50ID0g + Y29udGV4dC5wcm9wKFwidGFyZ2V0LmRpc2NyZXRlX3ZhbHVlX2NvdW50XCIpXG5sb2NhbCBuYW1lID0gY29udGV4dC5wcm9wKFwidGFyZ2V0LnRleHRfdmFsdWVcIikg + b3IgXCItXCJcbmxvY2FsIHNlY29uZGFyeV9wcm9wX2tleSA9IFwidGFyZ2V0LnByZXNldC5wcm9kdWN0Lm5hbWVcIlxubG9jYWwgc2Vjb25kYXJ5X3Byb3BfdmFsdWUg + PSBzZWNvbmRhcnlfcHJvcF9rZXkgYW5kIGNvbnRleHQucHJvcChzZWNvbmRhcnlfcHJvcF9rZXkpIG9yIFwiXCJcbmxvY2FsIGF2YWlsYWJsZSA9IGNvbnRleHQucHJv + cChcInRhcmdldC5hdmFpbGFibGVcIilcbnJldHVybiB7XG4gICAgZmVlZGJhY2tfZXZlbnQgPSB7XG4gICAgICAgIGNvbG9yID0geyBbXCJyXCJdID0gNzYsIFtcImJc + Il0gPSA4MCwgW1wiZ1wiXSA9IDE3NSx9LFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIGxhYmVsID0gXCJQcmVzZXRcIixcbiAgICAgICAgICAgIG5hbWUg + PSBuYW1lLFxuICAgICAgICAgICAgc2Vjb25kYXJ5X3Byb3AgPSBzZWNvbmRhcnlfcHJvcF92YWx1ZSxcbiAgICAgICAgICAgIGF2YWlsYWJsZSA9IGF2YWlsYWJsZSxc + biAgICAgICAgICAgIGluZGV4ID0gaW5kZXgsXG4gICAgICAgICAgICBjb3VudCA9IGNvdW50LFxuICAgICAgICB9XG4gICAgfSxcbn1cbiIsImZlZWRiYWNrVHlwZSI6 + Mn0sInRhcmdldCI6eyJ0eXBlIjo1OCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJqYjBtSU9nVkJXVU5iS28tLS1xN3Ei + LCJuYW1lIjoiRW5jb2RlciAxOiBNYWNybyBjb250cm9sIDEiLCJncm91cElkIjoibWFjcm8tcGFyYW1ldGVycyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVy + IjoyMSwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1U + eXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDBdIiwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoieGo4dkIxYWJjVDhHZlpZX0JCa0JPIiwibmFtZSI6IlNjcmVlbiAxIiwiZ3JvdXBJZCI6Im1hY3JvLXBh + cmFtZXRlcnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBj + b2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAw + LCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2Np + aSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xu + ICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0 + YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFi + bGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIg + ZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFk + ZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRc + biAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1z + Z1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxh + eW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAg + cmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmlj + YXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBz + dHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVu + dCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGlu + ZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4g + cHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9w + c19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0g + fj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3Jl + YXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJl + ciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3Bs + YXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5k + ZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4g + ICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxc + biAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUg + cHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8g + bm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3Ms + IHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVt + YmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hh + bmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFu + Z2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5k + ZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3Rf + aW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBj + aGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1 + bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9j + YXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQg + YilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51 + bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRl + eCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmpl + Y3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcg + LyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0 + YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2lu + ZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxh + c2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFi + bGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYg + Y29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAg + ICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAg + ICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVm + dF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4g + ICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4 + dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFt + ZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAg + dGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRc + biAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zm + c2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29k + ZSAjI1xubG9jYWwgY29sdW1uID0gMFxubG9jYWwgY29sb3IgPSB5IGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxv + Y2FsIHNlY3Rpb25fbmFtZSA9IHkgYW5kIHkuc2VjdGlvbl9uYW1lIG9yIFwiXCJcbmxvY2FsIG1hY3JvX25hbWUgPSB5IGFuZCB5Lm1hY3JvX25hbWUgb3IgXCJcIlxu + bG9jYWwgbm9ybWFsaXplZF9wYXJhbV92YWx1ZSA9IHkgYW5kIHkucGFyYW1fdmFsdWUgb3IgMC4wXG5sb2NhbCBtaWRpX3BhcmFtX3ZhbHVlID0gbWF0aC5mbG9vcihu + b3JtYWxpemVkX3BhcmFtX3ZhbHVlICogMTI3KVxubG9jYWwgcGFyYW1fbmFtZSA9IHkgYW5kIHkucGFyYW1fbmFtZSBvciBcIlwiXG5sb2NhbCBwYXJhbV92YWx1ZV9s + YWJlbCA9IHkgYW5kIHkucGFyYW1fdmFsdWVfbGFiZWwgb3IgXCJcIlxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAg + ICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgY29sb3IpLFxuICAg + ICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBzZWN0aW9uX25hbWUpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2Uo + Y29sdW1uLCAxLCBtYWNyb19uYW1lKSxcbiAgICAgICAgICAgIC0tIE1ha2UgdGhlIGtub2IgdmlzaWJsZSAoYnkgbWFraW5nIGl0IHdoaXRlKVxuICAgICAgICAgICAg + Y3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDEsIGNvbG9yKSxcbiAgICAgICAgICAgIC0tIFJvdGF0ZSB0aGUga25vYiBzbyBpdCByZWZsZWN0cyB0 + aGUgcGFyYW1ldGVyIHZhbHVlXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBtaWRpX3BhcmFtX3ZhbHVlKSxcbiAgICAgICAg + ICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgcGFyYW1fdmFsdWVfbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2Uo + Y29sdW1uLCAzLCBwYXJhbV9uYW1lKSxcbiAgICAgICAgfSksXG4gICAgfVxufSAifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJl + ZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiXG5sb2NhbCBtYWNyb19jb2xvcnMgPSB7IFsxXSA9IHsgIFtcInJcIl0gPSAzMywgIFtcImJcIl0gPSAyNDMsICBbXCJn + XCJdID0gMTUwLCB9LCBbMl0gPSB7ICBbXCJyXCJdID0gMTIxLCAgW1wiYlwiXSA9IDcyLCAgW1wiZ1wiXSA9IDg1LCB9LCBbM10gPSB7ICBbXCJyXCJdID0gMjU1LCAg + W1wiYlwiXSA9IDM0LCAgW1wiZ1wiXSA9IDg3LCB9LCBbNF0gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDU5LCAgW1wiZ1wiXSA9IDIzNSwgfSwgWzVdID0g + eyAgW1wiclwiXSA9IDc2LCAgW1wiYlwiXSA9IDgwLCAgW1wiZ1wiXSA9IDE3NSwgfSwgWzZdID0geyAgW1wiclwiXSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwgIFtcImdc + Il0gPSA3OSwgfSwgWzddID0geyAgW1wiclwiXSA9IDE4MywgIFtcImJcIl0gPSAyOCwgIFtcImdcIl0gPSAyOCwgfSwgWzhdID0geyAgW1wiclwiXSA9IDE1NiwgIFtc + ImJcIl0gPSAxNzYsICBbXCJnXCJdID0gMzksIH0sfVxubG9jYWwgc2VjdGlvbl9pbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8u + c2VjdGlvbi5pbmRleFwiKVxubG9jYWwgY29sb3IgPSBzZWN0aW9uX2luZGV4IGFuZCBtYWNyb19jb2xvcnNbc2VjdGlvbl9pbmRleCArIDFdIG9yIG5pbFxuXG5yZXR1 + cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICBjb2xvciA9IGNvbG9yLFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIHNlY3Rpb25fbmFt + ZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8ubmV3X3NlY3Rpb24ubmFtZVwiKSxcbiAgICAgICAgICAgIG1hY3JvX25hbWUgPSBjb250 + ZXh0LnByb3AoXCJ0YXJnZXQuZnhfcGFyYW1ldGVyLm1hY3JvLm5hbWVcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZSA9IGNvbnRleHQucHJvcChcInlcIiksXG4g + ICAgICAgICAgICBwYXJhbV92YWx1ZV9sYWJlbCA9IGNvbnRleHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIpLFxuICAgICAgICAgICAgcGFyYW1fbmFtZSA9IGNv + bnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubmFtZVwiKSxcbiAgICAgICAgfVxuICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsi + ZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIs + InBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDBdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJzOW0yckswVnctNVJ1SWR3eUR3R1QiLCJuYW1lIjoiRW5jb2RlciAyOiBNYWNybyBjb250cm9sIDIiLCJncm91cElkIjoibWFjcm8t + cGFyYW1ldGVycyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjoyMiwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNp + emUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJf + aW5kZXhlc1twWzFdICogOCArIDFdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibVg3Y2tMaWw2T2ItNTd4X0ZS + SENrIiwibmFtZSI6IlNjcmVlbiAyIiwiZ3JvdXBJZCI6Im1hY3JvLXBhcmFtZXRlcnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwi + bWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29m + ZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9 + XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVu + XG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAg + ICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5k + XG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9u + IGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0 + aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4Mjks + IDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxu + ICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91 + dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNy + ZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0g + Q3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3Ry + aW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlv + bl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUx + KSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0 + ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJv + cGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3Ig + aSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2Vz + W2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBj + aGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmpl + Y3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5m + dW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAg + IC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAg + ICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAg + cmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBr + bm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBv + biB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBm + ZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9i + amVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIg + dGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4g + ICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMs + XG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxu + ZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVu + Y3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4t + LS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUg + UkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4 + LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAg + ICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4 + MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3Io + Y29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5c + bi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1h + a2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZp + b3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylc + bi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21z + ZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9 + IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3Is + XG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5i + IC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1l + X2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2Nv + dW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5m + bG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZy + YW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBs + b2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAg + ICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAg + ICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gMVxubG9jYWwgY29sb3IgPSB5IGFuZCAoY29udGV4dC5m + ZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIHNlY3Rpb25fbmFtZSA9IHkgYW5kIHkuc2VjdGlvbl9uYW1lIG9yIFwiXCJcbmxvY2Fs + IG1hY3JvX25hbWUgPSB5IGFuZCB5Lm1hY3JvX25hbWUgb3IgXCJcIlxubG9jYWwgbm9ybWFsaXplZF9wYXJhbV92YWx1ZSA9IHkgYW5kIHkucGFyYW1fdmFsdWUgb3Ig + MC4wXG5sb2NhbCBtaWRpX3BhcmFtX3ZhbHVlID0gbWF0aC5mbG9vcihub3JtYWxpemVkX3BhcmFtX3ZhbHVlICogMTI3KVxubG9jYWwgcGFyYW1fbmFtZSA9IHkgYW5k + IHkucGFyYW1fbmFtZSBvciBcIlwiXG5sb2NhbCBwYXJhbV92YWx1ZV9sYWJlbCA9IHkgYW5kIHkucGFyYW1fdmFsdWVfbGFiZWwgb3IgXCJcIlxucmV0dXJuIHtcbiAg + ICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfcmdi + X2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBzZWN0aW9uX25h + bWUpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBtYWNyb19uYW1lKSxcbiAgICAgICAgICAgIC0tIE1ha2UgdGhlIGtub2Ig + dmlzaWJsZSAoYnkgbWFraW5nIGl0IHdoaXRlKVxuICAgICAgICAgICAgY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDEsIGNvbG9yKSxcbiAgICAg + ICAgICAgIC0tIFJvdGF0ZSB0aGUga25vYiBzbyBpdCByZWZsZWN0cyB0aGUgcGFyYW1ldGVyIHZhbHVlXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFu + Z2UoY29sdW1uLCAwLCBtaWRpX3BhcmFtX3ZhbHVlKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgcGFyYW1fdmFsdWVfbGFi + ZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBwYXJhbV9uYW1lKSxcbiAgICAgICAgfSksXG4gICAgfVxufSAifSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiXG5sb2NhbCBtYWNyb19jb2xvcnMgPSB7IFsx + XSA9IHsgIFtcInJcIl0gPSAzMywgIFtcImJcIl0gPSAyNDMsICBbXCJnXCJdID0gMTUwLCB9LCBbMl0gPSB7ICBbXCJyXCJdID0gMTIxLCAgW1wiYlwiXSA9IDcyLCAg + W1wiZ1wiXSA9IDg1LCB9LCBbM10gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDM0LCAgW1wiZ1wiXSA9IDg3LCB9LCBbNF0gPSB7ICBbXCJyXCJdID0gMjU1 + LCAgW1wiYlwiXSA9IDU5LCAgW1wiZ1wiXSA9IDIzNSwgfSwgWzVdID0geyAgW1wiclwiXSA9IDc2LCAgW1wiYlwiXSA9IDgwLCAgW1wiZ1wiXSA9IDE3NSwgfSwgWzZd + ID0geyAgW1wiclwiXSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwgIFtcImdcIl0gPSA3OSwgfSwgWzddID0geyAgW1wiclwiXSA9IDE4MywgIFtcImJcIl0gPSAyOCwgIFtc + ImdcIl0gPSAyOCwgfSwgWzhdID0geyAgW1wiclwiXSA9IDE1NiwgIFtcImJcIl0gPSAxNzYsICBbXCJnXCJdID0gMzksIH0sfVxubG9jYWwgc2VjdGlvbl9pbmRleCA9 + IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8uc2VjdGlvbi5pbmRleFwiKVxubG9jYWwgY29sb3IgPSBzZWN0aW9uX2luZGV4IGFuZCBtYWNy + b19jb2xvcnNbc2VjdGlvbl9pbmRleCArIDFdIG9yIG5pbFxuXG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICBjb2xvciA9IGNvbG9yLFxu + ICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIHNlY3Rpb25fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8ubmV3X3NlY3Rp + b24ubmFtZVwiKSxcbiAgICAgICAgICAgIG1hY3JvX25hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZnhfcGFyYW1ldGVyLm1hY3JvLm5hbWVcIiksXG4gICAgICAg + ICAgICBwYXJhbV92YWx1ZSA9IGNvbnRleHQucHJvcChcInlcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZV9sYWJlbCA9IGNvbnRleHQucHJvcChcInRhcmdldC50 + ZXh0X3ZhbHVlXCIpLFxuICAgICAgICAgICAgcGFyYW1fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubmFtZVwiKSxcbiAgICAgICAgfVxu + ICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICog + OCArIDFdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJDVW0yZDZpRzN3SzJHTjl5c3NRT2YiLCJuYW1lIjoiRW5j + b2RlciAzOiBNYWNybyBjb250cm9sIDMiLCJncm91cElkIjoibWFjcm8tcGFyYW1ldGVycyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjoyMywiY2hhcmFj + dGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQi + OnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1p + YyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDJdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiS1llMWFEWHEwR2xhV09lY3Y2NG9CIiwibmFtZSI6IlNjcmVlbiAzIiwiZ3JvdXBJZCI6Im1hY3JvLXBhcmFtZXRlcnMiLCJz + b3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9h + ZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5s + b2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAg + IGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAg + bG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQo + Ynl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5 + dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAg + dDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2Nh + bCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRf + dGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0t + LSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAo + MCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0 + ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhl + IGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25k + IGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9 + XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJl + dHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5c + bi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdl + cylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5c + biAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250 + ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBj + b2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRc + bi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9p + bmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFBy + b3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAg + Y29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hh + bmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8g + dGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5l + ZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNo + IGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJv + Z3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2lu + ZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAg + IC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAg + ICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1c + bi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVt + YmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hh + bmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9u + IGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAg + ICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAg + LS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4g + ICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAg + ICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExF + RC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3 + aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBw + cmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9w + ZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmls + IHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxc + biAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29y + KGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmlt + YXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVy + biBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFy + X2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIp + XG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQg + PSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAg + IHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4 + dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwg + Y29sdW1uID0gMlxubG9jYWwgY29sb3IgPSB5IGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIHNlY3Rpb25f + bmFtZSA9IHkgYW5kIHkuc2VjdGlvbl9uYW1lIG9yIFwiXCJcbmxvY2FsIG1hY3JvX25hbWUgPSB5IGFuZCB5Lm1hY3JvX25hbWUgb3IgXCJcIlxubG9jYWwgbm9ybWFs + aXplZF9wYXJhbV92YWx1ZSA9IHkgYW5kIHkucGFyYW1fdmFsdWUgb3IgMC4wXG5sb2NhbCBtaWRpX3BhcmFtX3ZhbHVlID0gbWF0aC5mbG9vcihub3JtYWxpemVkX3Bh + cmFtX3ZhbHVlICogMTI3KVxubG9jYWwgcGFyYW1fbmFtZSA9IHkgYW5kIHkucGFyYW1fbmFtZSBvciBcIlwiXG5sb2NhbCBwYXJhbV92YWx1ZV9sYWJlbCA9IHkgYW5k + IHkucGFyYW1fdmFsdWVfbGFiZWwgb3IgXCJcIlxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVf + c2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3Jl + YXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBzZWN0aW9uX25hbWUpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBt + YWNyb19uYW1lKSxcbiAgICAgICAgICAgIC0tIE1ha2UgdGhlIGtub2IgdmlzaWJsZSAoYnkgbWFraW5nIGl0IHdoaXRlKVxuICAgICAgICAgICAgY3JlYXRlX3JnYl9j + b2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDEsIGNvbG9yKSxcbiAgICAgICAgICAgIC0tIFJvdGF0ZSB0aGUga25vYiBzbyBpdCByZWZsZWN0cyB0aGUgcGFyYW1ldGVy + IHZhbHVlXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBtaWRpX3BhcmFtX3ZhbHVlKSxcbiAgICAgICAgICAgIGNyZWF0ZV90 + ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgcGFyYW1fdmFsdWVfbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBw + YXJhbV9uYW1lKSxcbiAgICAgICAgfSksXG4gICAgfVxufSAifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1Ry + YW5zZm9ybWF0aW9uIjoiXG5sb2NhbCBtYWNyb19jb2xvcnMgPSB7IFsxXSA9IHsgIFtcInJcIl0gPSAzMywgIFtcImJcIl0gPSAyNDMsICBbXCJnXCJdID0gMTUwLCB9 + LCBbMl0gPSB7ICBbXCJyXCJdID0gMTIxLCAgW1wiYlwiXSA9IDcyLCAgW1wiZ1wiXSA9IDg1LCB9LCBbM10gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDM0 + LCAgW1wiZ1wiXSA9IDg3LCB9LCBbNF0gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDU5LCAgW1wiZ1wiXSA9IDIzNSwgfSwgWzVdID0geyAgW1wiclwiXSA9 + IDc2LCAgW1wiYlwiXSA9IDgwLCAgW1wiZ1wiXSA9IDE3NSwgfSwgWzZdID0geyAgW1wiclwiXSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwgIFtcImdcIl0gPSA3OSwgfSwg + WzddID0geyAgW1wiclwiXSA9IDE4MywgIFtcImJcIl0gPSAyOCwgIFtcImdcIl0gPSAyOCwgfSwgWzhdID0geyAgW1wiclwiXSA9IDE1NiwgIFtcImJcIl0gPSAxNzYs + ICBbXCJnXCJdID0gMzksIH0sfVxubG9jYWwgc2VjdGlvbl9pbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8uc2VjdGlvbi5pbmRl + eFwiKVxubG9jYWwgY29sb3IgPSBzZWN0aW9uX2luZGV4IGFuZCBtYWNyb19jb2xvcnNbc2VjdGlvbl9pbmRleCArIDFdIG9yIG5pbFxuXG5yZXR1cm4ge1xuICAgIGZl + ZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICBjb2xvciA9IGNvbG9yLFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIHNlY3Rpb25fbmFtZSA9IGNvbnRleHQu + cHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8ubmV3X3NlY3Rpb24ubmFtZVwiKSxcbiAgICAgICAgICAgIG1hY3JvX25hbWUgPSBjb250ZXh0LnByb3AoXCJ0 + YXJnZXQuZnhfcGFyYW1ldGVyLm1hY3JvLm5hbWVcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZSA9IGNvbnRleHQucHJvcChcInlcIiksXG4gICAgICAgICAgICBw + YXJhbV92YWx1ZV9sYWJlbCA9IGNvbnRleHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIpLFxuICAgICAgICAgICAgcGFyYW1fbmFtZSA9IGNvbnRleHQucHJvcChc + InRhcmdldC5meF9wYXJhbWV0ZXIubmFtZVwiKSxcbiAgICAgICAgfVxuICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJp + bnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVz + c2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDJdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJObWw0SE1oVDl6M1ZyWXFnSGRWQ3UiLCJuYW1lIjoiRW5jb2RlciA0OiBNYWNybyBjb250cm9sIDQiLCJncm91cElkIjoibWFjcm8tcGFyYW1ldGVycyIs + InNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjoyNCwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFd + ICogOCArIDNdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoialBtTFhubFJYVk4tcHFNM0Z6SjZCIiwibmFtZSI6 + IlNjcmVlbiA0IiwiZ3JvdXBJZCI6Im1hY3JvLXBhcmFtZXRlcnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6 + Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAw + XG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1 + bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZv + ciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRl + IDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4g + ICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJs + ZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9t + c2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEs + IDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmlu + c2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0t + IEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5f + bGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1l + c3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxp + bmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhs + aW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25j + YXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdl + IGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdl + c1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFu + Z2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAg + ICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1c + bi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVt + YmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVh + dGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJ + bmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0 + IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5n + ZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBp + dCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVz + cG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0t + LVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBu + dW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5m + dW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENv + bHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0t + IE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBD + cmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJu + cyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9i + amVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0 + YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5k + ZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29s + dW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAg + IC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIp + LFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVz + IGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBn + byBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExF + RCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0g + Y29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgs + IGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVu + ZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1h + dGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAg + fSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4 + dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAg + ICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxf + bWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwg + ZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5j + ZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAg + ICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0 + ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gM1xubG9jYWwgY29sb3IgPSB5IGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVu + dC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIHNlY3Rpb25fbmFtZSA9IHkgYW5kIHkuc2VjdGlvbl9uYW1lIG9yIFwiXCJcbmxvY2FsIG1hY3JvX25hbWUg + PSB5IGFuZCB5Lm1hY3JvX25hbWUgb3IgXCJcIlxubG9jYWwgbm9ybWFsaXplZF9wYXJhbV92YWx1ZSA9IHkgYW5kIHkucGFyYW1fdmFsdWUgb3IgMC4wXG5sb2NhbCBt + aWRpX3BhcmFtX3ZhbHVlID0gbWF0aC5mbG9vcihub3JtYWxpemVkX3BhcmFtX3ZhbHVlICogMTI3KVxubG9jYWwgcGFyYW1fbmFtZSA9IHkgYW5kIHkucGFyYW1fbmFt + ZSBvciBcIlwiXG5sb2NhbCBwYXJhbV92YWx1ZV9sYWJlbCA9IHkgYW5kIHkucGFyYW1fdmFsdWVfbGFiZWwgb3IgXCJcIlxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0g + Y29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3Bf + Y2hhbmdlKGNvbHVtbiwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBzZWN0aW9uX25hbWUpLFxuICAgICAg + ICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBtYWNyb19uYW1lKSxcbiAgICAgICAgICAgIC0tIE1ha2UgdGhlIGtub2IgdmlzaWJsZSAoYnkg + bWFraW5nIGl0IHdoaXRlKVxuICAgICAgICAgICAgY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDEsIGNvbG9yKSxcbiAgICAgICAgICAgIC0tIFJv + dGF0ZSB0aGUga25vYiBzbyBpdCByZWZsZWN0cyB0aGUgcGFyYW1ldGVyIHZhbHVlXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAw + LCBtaWRpX3BhcmFtX3ZhbHVlKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgcGFyYW1fdmFsdWVfbGFiZWwpLFxuICAgICAg + ICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBwYXJhbV9uYW1lKSxcbiAgICAgICAgfSksXG4gICAgfVxufSAifSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiXG5sb2NhbCBtYWNyb19jb2xvcnMgPSB7IFsxXSA9IHsgIFtcInJc + Il0gPSAzMywgIFtcImJcIl0gPSAyNDMsICBbXCJnXCJdID0gMTUwLCB9LCBbMl0gPSB7ICBbXCJyXCJdID0gMTIxLCAgW1wiYlwiXSA9IDcyLCAgW1wiZ1wiXSA9IDg1 + LCB9LCBbM10gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDM0LCAgW1wiZ1wiXSA9IDg3LCB9LCBbNF0gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9 + IDU5LCAgW1wiZ1wiXSA9IDIzNSwgfSwgWzVdID0geyAgW1wiclwiXSA9IDc2LCAgW1wiYlwiXSA9IDgwLCAgW1wiZ1wiXSA9IDE3NSwgfSwgWzZdID0geyAgW1wiclwi + XSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwgIFtcImdcIl0gPSA3OSwgfSwgWzddID0geyAgW1wiclwiXSA9IDE4MywgIFtcImJcIl0gPSAyOCwgIFtcImdcIl0gPSAyOCwg + fSwgWzhdID0geyAgW1wiclwiXSA9IDE1NiwgIFtcImJcIl0gPSAxNzYsICBbXCJnXCJdID0gMzksIH0sfVxubG9jYWwgc2VjdGlvbl9pbmRleCA9IGNvbnRleHQucHJv + cChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8uc2VjdGlvbi5pbmRleFwiKVxubG9jYWwgY29sb3IgPSBzZWN0aW9uX2luZGV4IGFuZCBtYWNyb19jb2xvcnNbc2Vj + dGlvbl9pbmRleCArIDFdIG9yIG5pbFxuXG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICBjb2xvciA9IGNvbG9yLFxuICAgICAgICB2YWx1 + ZSA9IHtcbiAgICAgICAgICAgIHNlY3Rpb25fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8ubmV3X3NlY3Rpb24ubmFtZVwiKSxc + biAgICAgICAgICAgIG1hY3JvX25hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZnhfcGFyYW1ldGVyLm1hY3JvLm5hbWVcIiksXG4gICAgICAgICAgICBwYXJhbV92 + YWx1ZSA9IGNvbnRleHQucHJvcChcInlcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZV9sYWJlbCA9IGNvbnRleHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIp + LFxuICAgICAgICAgICAgcGFyYW1fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubmFtZVwiKSxcbiAgICAgICAgfVxuICAgIH0sXG59XG4i + LCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDNdIiwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI0MXJUc3Z1cGQ5ZkFmWU5PVUVLcUUiLCJuYW1lIjoiRW5jb2RlciA1OiBNYWNy + byBjb250cm9sIDUiLCJncm91cElkIjoibWFjcm8tcGFyYW1ldGVycyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjoyNSwiY2hhcmFjdGVyIjoyLCJpczE0 + Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiZnhBbmNob3Ii + OiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhw + cmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDRdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoidzl0aU1kSGU2SmdFOVRFd1N0UTY3IiwibmFtZSI6IlNjcmVlbiA1IiwiZ3JvdXBJZCI6Im1hY3JvLXBhcmFtZXRlcnMiLCJzb3VyY2UiOnsidHlw + ZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNl + dCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9 + IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVz + ID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9 + IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUp + XG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAg + IHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0g + PSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4g + ICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBj + b250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEg + bWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAx + ID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAx + LCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3Jl + ZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4 + dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0 + X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVf + bXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBw + YXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2Nh + bCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAg + IGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5c + bi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlz + cGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0g + dGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClc + biAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUg + XCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxl + KGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4t + LS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2Ny + aXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUg + bWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBj + aGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1 + aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3Rf + aW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5 + IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9y + IGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUg + Z2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNo + IGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xv + clxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2Jf + Y29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4g + bmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkg + VHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENv + bG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9v + cihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0t + IFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8g + dGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNl + dCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcg + YW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAg + ICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVk + X2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAy + KSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFs + X21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBl + bmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAg + ICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxv + Y2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRl + eFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0 + ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBt + YXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gNFxu + bG9jYWwgY29sb3IgPSB5IGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIHNlY3Rpb25fbmFtZSA9IHkgYW5k + IHkuc2VjdGlvbl9uYW1lIG9yIFwiXCJcbmxvY2FsIG1hY3JvX25hbWUgPSB5IGFuZCB5Lm1hY3JvX25hbWUgb3IgXCJcIlxubG9jYWwgbm9ybWFsaXplZF9wYXJhbV92 + YWx1ZSA9IHkgYW5kIHkucGFyYW1fdmFsdWUgb3IgMC4wXG5sb2NhbCBtaWRpX3BhcmFtX3ZhbHVlID0gbWF0aC5mbG9vcihub3JtYWxpemVkX3BhcmFtX3ZhbHVlICog + MTI3KVxubG9jYWwgcGFyYW1fbmFtZSA9IHkgYW5kIHkucGFyYW1fbmFtZSBvciBcIlwiXG5sb2NhbCBwYXJhbV92YWx1ZV9sYWJlbCA9IHkgYW5kIHkucGFyYW1fdmFs + dWVfbGFiZWwgb3IgXCJcIlxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3Bz + X21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJv + cF9jaGFuZ2UoY29sdW1uLCAwLCBzZWN0aW9uX25hbWUpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBtYWNyb19uYW1lKSxc + biAgICAgICAgICAgIC0tIE1ha2UgdGhlIGtub2IgdmlzaWJsZSAoYnkgbWFraW5nIGl0IHdoaXRlKVxuICAgICAgICAgICAgY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2No + YW5nZShjb2x1bW4sIDEsIGNvbG9yKSxcbiAgICAgICAgICAgIC0tIFJvdGF0ZSB0aGUga25vYiBzbyBpdCByZWZsZWN0cyB0aGUgcGFyYW1ldGVyIHZhbHVlXG4gICAg + ICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBtaWRpX3BhcmFtX3ZhbHVlKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hh + bmdlKGNvbHVtbiwgMiwgcGFyYW1fdmFsdWVfbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBwYXJhbV9uYW1lKSxc + biAgICAgICAgfSksXG4gICAgfVxufSAifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9u + IjoiXG5sb2NhbCBtYWNyb19jb2xvcnMgPSB7IFsxXSA9IHsgIFtcInJcIl0gPSAzMywgIFtcImJcIl0gPSAyNDMsICBbXCJnXCJdID0gMTUwLCB9LCBbMl0gPSB7ICBb + XCJyXCJdID0gMTIxLCAgW1wiYlwiXSA9IDcyLCAgW1wiZ1wiXSA9IDg1LCB9LCBbM10gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDM0LCAgW1wiZ1wiXSA9 + IDg3LCB9LCBbNF0gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDU5LCAgW1wiZ1wiXSA9IDIzNSwgfSwgWzVdID0geyAgW1wiclwiXSA9IDc2LCAgW1wiYlwi + XSA9IDgwLCAgW1wiZ1wiXSA9IDE3NSwgfSwgWzZdID0geyAgW1wiclwiXSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwgIFtcImdcIl0gPSA3OSwgfSwgWzddID0geyAgW1wi + clwiXSA9IDE4MywgIFtcImJcIl0gPSAyOCwgIFtcImdcIl0gPSAyOCwgfSwgWzhdID0geyAgW1wiclwiXSA9IDE1NiwgIFtcImJcIl0gPSAxNzYsICBbXCJnXCJdID0g + MzksIH0sfVxubG9jYWwgc2VjdGlvbl9pbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8uc2VjdGlvbi5pbmRleFwiKVxubG9jYWwg + Y29sb3IgPSBzZWN0aW9uX2luZGV4IGFuZCBtYWNyb19jb2xvcnNbc2VjdGlvbl9pbmRleCArIDFdIG9yIG5pbFxuXG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50 + ID0ge1xuICAgICAgICBjb2xvciA9IGNvbG9yLFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIHNlY3Rpb25fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdl + dC5meF9wYXJhbWV0ZXIubWFjcm8ubmV3X3NlY3Rpb24ubmFtZVwiKSxcbiAgICAgICAgICAgIG1hY3JvX25hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZnhfcGFy + YW1ldGVyLm1hY3JvLm5hbWVcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZSA9IGNvbnRleHQucHJvcChcInlcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZV9s + YWJlbCA9IGNvbnRleHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIpLFxuICAgICAgICAgICAgcGFyYW1fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9w + YXJhbWV0ZXIubmFtZVwiKSxcbiAgICAgICAgfVxuICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBl + ZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDRdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJQS2NW + WWpXZ1c4QlRYLUZROG8wbE4iLCJuYW1lIjoiRW5jb2RlciA2OiBNYWNybyBjb250cm9sIDYiLCJncm91cElkIjoibWFjcm8tcGFyYW1ldGVycyIsInNvdXJjZSI6eyJj + aGFubmVsIjoxNSwibnVtYmVyIjoyNiwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDVdIiwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiRkV3RHJienlHRjRZcmdUQkk2RHVJIiwibmFtZSI6IlNjcmVlbiA2Iiwi + Z3JvdXBJZCI6Im1hY3JvLXBhcmFtZXRlcnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0 + YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFj + ayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xu + XG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3Ry + aW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5c + biAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0 + ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4g + ICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClc + biAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAg + fVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4 + ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlv + dXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhs + YXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRp + c3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxu + LS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIp + XG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29u + dGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2lu + ZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24g + Y3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAg + ICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBl + bmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0g + Y29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNo + IGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9w + X2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAg + ICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAg + ICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0t + LSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkg + bW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNv + ZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJh + bSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtp + bmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVh + dGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxu + ICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRl + eFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJH + QiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0t + LVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBu + dW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHBy + b3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxu + ICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4g + ICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJ + bmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBt + YXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBm + b3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1c + bi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAo + MSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUg + dGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlv + ciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVy + biBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29s + b3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5m + dW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0 + ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwg + ZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJh + bWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQg + dGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2lu + ZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJu + IHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5k + XG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gNVxubG9jYWwgY29sb3IgPSB5IGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3 + aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIHNlY3Rpb25fbmFtZSA9IHkgYW5kIHkuc2VjdGlvbl9uYW1lIG9yIFwiXCJcbmxvY2FsIG1hY3JvX25hbWUgPSB5IGFuZCB5Lm1h + Y3JvX25hbWUgb3IgXCJcIlxubG9jYWwgbm9ybWFsaXplZF9wYXJhbV92YWx1ZSA9IHkgYW5kIHkucGFyYW1fdmFsdWUgb3IgMC4wXG5sb2NhbCBtaWRpX3BhcmFtX3Zh + bHVlID0gbWF0aC5mbG9vcihub3JtYWxpemVkX3BhcmFtX3ZhbHVlICogMTI3KVxubG9jYWwgcGFyYW1fbmFtZSA9IHkgYW5kIHkucGFyYW1fbmFtZSBvciBcIlwiXG5s + b2NhbCBwYXJhbV92YWx1ZV9sYWJlbCA9IHkgYW5kIHkucGFyYW1fdmFsdWVfbGFiZWwgb3IgXCJcIlxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAg + IG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVt + biwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBzZWN0aW9uX25hbWUpLFxuICAgICAgICAgICAgY3JlYXRl + X3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBtYWNyb19uYW1lKSxcbiAgICAgICAgICAgIC0tIE1ha2UgdGhlIGtub2IgdmlzaWJsZSAoYnkgbWFraW5nIGl0IHdo + aXRlKVxuICAgICAgICAgICAgY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDEsIGNvbG9yKSxcbiAgICAgICAgICAgIC0tIFJvdGF0ZSB0aGUga25v + YiBzbyBpdCByZWZsZWN0cyB0aGUgcGFyYW1ldGVyIHZhbHVlXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBtaWRpX3BhcmFt + X3ZhbHVlKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgcGFyYW1fdmFsdWVfbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRl + X3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBwYXJhbV9uYW1lKSxcbiAgICAgICAgfSksXG4gICAgfVxufSAifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiXG5sb2NhbCBtYWNyb19jb2xvcnMgPSB7IFsxXSA9IHsgIFtcInJcIl0gPSAzMywgIFtc + ImJcIl0gPSAyNDMsICBbXCJnXCJdID0gMTUwLCB9LCBbMl0gPSB7ICBbXCJyXCJdID0gMTIxLCAgW1wiYlwiXSA9IDcyLCAgW1wiZ1wiXSA9IDg1LCB9LCBbM10gPSB7 + ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDM0LCAgW1wiZ1wiXSA9IDg3LCB9LCBbNF0gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDU5LCAgW1wiZ1wi + XSA9IDIzNSwgfSwgWzVdID0geyAgW1wiclwiXSA9IDc2LCAgW1wiYlwiXSA9IDgwLCAgW1wiZ1wiXSA9IDE3NSwgfSwgWzZdID0geyAgW1wiclwiXSA9IDQ4LCAgW1wi + YlwiXSA9IDI1NCwgIFtcImdcIl0gPSA3OSwgfSwgWzddID0geyAgW1wiclwiXSA9IDE4MywgIFtcImJcIl0gPSAyOCwgIFtcImdcIl0gPSAyOCwgfSwgWzhdID0geyAg + W1wiclwiXSA9IDE1NiwgIFtcImJcIl0gPSAxNzYsICBbXCJnXCJdID0gMzksIH0sfVxubG9jYWwgc2VjdGlvbl9pbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5m + eF9wYXJhbWV0ZXIubWFjcm8uc2VjdGlvbi5pbmRleFwiKVxubG9jYWwgY29sb3IgPSBzZWN0aW9uX2luZGV4IGFuZCBtYWNyb19jb2xvcnNbc2VjdGlvbl9pbmRleCAr + IDFdIG9yIG5pbFxuXG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICBjb2xvciA9IGNvbG9yLFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAg + ICAgICAgIHNlY3Rpb25fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8ubmV3X3NlY3Rpb24ubmFtZVwiKSxcbiAgICAgICAgICAg + IG1hY3JvX25hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZnhfcGFyYW1ldGVyLm1hY3JvLm5hbWVcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZSA9IGNvbnRl + eHQucHJvcChcInlcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZV9sYWJlbCA9IGNvbnRleHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIpLFxuICAgICAgICAg + ICAgcGFyYW1fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubmFtZVwiKSxcbiAgICAgICAgfVxuICAgIH0sXG59XG4iLCJmZWVkYmFja1R5 + cGUiOjJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFy + YW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDVdIiwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYlRKTXhBVHFmQ3BSRFp6U0hwN0kiLCJuYW1lIjoiRW5jb2RlciA3OiBNYWNybyBjb250cm9sIDci + LCJncm91cElkIjoibWFjcm8tcGFyYW1ldGVycyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwibnVtYmVyIjoyNywiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1h + cHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDZdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + ZTNwWnFIcUhTRTYtbXNXbzFaWWNOIiwibmFtZSI6IlNjcmVlbiA3IiwiZ3JvdXBJZCI6Im1hY3JvLXBhcmFtZXRlcnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlT + Y3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5sb2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxv + Y2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAsIGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwg + ZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBp + ZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRl + KHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAgICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAg + ICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4gICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRl + c1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAxLCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAg + IGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNFeCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAs + IDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENvbnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAg + IC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3Ig + Y2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVtYmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9 + IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgpXG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5k + ZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBub3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0t + LSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBsaW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24g + Y3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwgY29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRl + bnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNjaWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQp + XG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBzY3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2Vz + IHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVlbl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0g + eyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5nZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJs + ZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVz + IGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4 + dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRvIGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcg + dGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBj + aGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAg + ICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9pbmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9f + YXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBh + Ym91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRpdmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBl + bmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFwcGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3Ig + Ym90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZh + bHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUgdG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBw + YXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVl + KVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1 + ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAg + ICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3Ig + aXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBj + aGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hpY2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJh + bSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwgZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9j + aGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9yID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5k + XG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVtbl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBj + b2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4g + ICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8g + MiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBg + bmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0gbGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0t + IEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAyID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xv + ciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xvciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5j + dGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4gICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7 + IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNnKHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAg + ICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAg + bWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVhdGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4 + X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAj + dGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQgPSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZy + YW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAlIChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zm + c2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAgICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBl + bHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVfY291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291 + bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRleHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3Vu + dClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0gIyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gNlxubG9jYWwgY29sb3Ig + PSB5IGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxhY2tcbmxvY2FsIHNlY3Rpb25fbmFtZSA9IHkgYW5kIHkuc2VjdGlvbl9u + YW1lIG9yIFwiXCJcbmxvY2FsIG1hY3JvX25hbWUgPSB5IGFuZCB5Lm1hY3JvX25hbWUgb3IgXCJcIlxubG9jYWwgbm9ybWFsaXplZF9wYXJhbV92YWx1ZSA9IHkgYW5k + IHkucGFyYW1fdmFsdWUgb3IgMC4wXG5sb2NhbCBtaWRpX3BhcmFtX3ZhbHVlID0gbWF0aC5mbG9vcihub3JtYWxpemVkX3BhcmFtX3ZhbHVlICogMTI3KVxubG9jYWwg + cGFyYW1fbmFtZSA9IHkgYW5kIHkucGFyYW1fbmFtZSBvciBcIlwiXG5sb2NhbCBwYXJhbV92YWx1ZV9sYWJlbCA9IHkgYW5kIHkucGFyYW1fdmFsdWVfbGFiZWwgb3Ig + XCJcIlxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0ge1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAg + ICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgY29sb3IpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29s + dW1uLCAwLCBzZWN0aW9uX25hbWUpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAxLCBtYWNyb19uYW1lKSxcbiAgICAgICAgICAg + IC0tIE1ha2UgdGhlIGtub2IgdmlzaWJsZSAoYnkgbWFraW5nIGl0IHdoaXRlKVxuICAgICAgICAgICAgY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4s + IDEsIGNvbG9yKSxcbiAgICAgICAgICAgIC0tIFJvdGF0ZSB0aGUga25vYiBzbyBpdCByZWZsZWN0cyB0aGUgcGFyYW1ldGVyIHZhbHVlXG4gICAgICAgICAgICBjcmVh + dGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBtaWRpX3BhcmFtX3ZhbHVlKSxcbiAgICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwg + MiwgcGFyYW1fdmFsdWVfbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAzLCBwYXJhbV9uYW1lKSxcbiAgICAgICAgfSks + XG4gICAgfVxufSAifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiXG5sb2NhbCBt + YWNyb19jb2xvcnMgPSB7IFsxXSA9IHsgIFtcInJcIl0gPSAzMywgIFtcImJcIl0gPSAyNDMsICBbXCJnXCJdID0gMTUwLCB9LCBbMl0gPSB7ICBbXCJyXCJdID0gMTIx + LCAgW1wiYlwiXSA9IDcyLCAgW1wiZ1wiXSA9IDg1LCB9LCBbM10gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDM0LCAgW1wiZ1wiXSA9IDg3LCB9LCBbNF0g + PSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDU5LCAgW1wiZ1wiXSA9IDIzNSwgfSwgWzVdID0geyAgW1wiclwiXSA9IDc2LCAgW1wiYlwiXSA9IDgwLCAgW1wi + Z1wiXSA9IDE3NSwgfSwgWzZdID0geyAgW1wiclwiXSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwgIFtcImdcIl0gPSA3OSwgfSwgWzddID0geyAgW1wiclwiXSA9IDE4Mywg + IFtcImJcIl0gPSAyOCwgIFtcImdcIl0gPSAyOCwgfSwgWzhdID0geyAgW1wiclwiXSA9IDE1NiwgIFtcImJcIl0gPSAxNzYsICBbXCJnXCJdID0gMzksIH0sfVxubG9j + YWwgc2VjdGlvbl9pbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8uc2VjdGlvbi5pbmRleFwiKVxubG9jYWwgY29sb3IgPSBzZWN0 + aW9uX2luZGV4IGFuZCBtYWNyb19jb2xvcnNbc2VjdGlvbl9pbmRleCArIDFdIG9yIG5pbFxuXG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAg + ICBjb2xvciA9IGNvbG9yLFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIHNlY3Rpb25fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0 + ZXIubWFjcm8ubmV3X3NlY3Rpb24ubmFtZVwiKSxcbiAgICAgICAgICAgIG1hY3JvX25hbWUgPSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZnhfcGFyYW1ldGVyLm1hY3Jv + Lm5hbWVcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZSA9IGNvbnRleHQucHJvcChcInlcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZV9sYWJlbCA9IGNvbnRl + eHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIpLFxuICAgICAgICAgICAgcGFyYW1fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubmFt + ZVwiKSxcbiAgICAgICAgfVxuICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0 + ZXJfaW5kZXhlc1twWzFdICogOCArIDZdIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJDaW9JdExmWG9idzJscjNl + TVB6YmQiLCJuYW1lIjoiRW5jb2RlciA4OiBNYWNybyBjb250cm9sIDgiLCJncm91cElkIjoibWFjcm8tcGFyYW1ldGVycyIsInNvdXJjZSI6eyJjaGFubmVsIjoxNSwi + bnVtYmVyIjoyOCwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + cGFyYW1UeXBlIjoiZHluYW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDddIiwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiM2cyNk5sSDRUVjZPdUphTl9nUnFDIiwibmFtZSI6IlNjcmVlbiA4IiwiZ3JvdXBJZCI6Im1h + Y3JvLXBhcmFtZXRlcnMiLCJzb3VyY2UiOnsidHlwZSI6MTEsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6Ii0tICMjIENvbnN0YW50cyAjI1xuXG5s + b2NhbCBjb2x1bW5fa25vYl9hZGRyZXNzX29mZnNldCA9IDUwMDBcbmxvY2FsIGxlZF9hZGRyZXNzX29mZnNldCA9IDEwMDAwXG5sb2NhbCBibGFjayA9IHsgciA9IDAs + IGcgPSAwLCBiID0gMCB9XG5sb2NhbCB3aGl0ZSA9IHsgciA9IDI1NSwgZyA9IDI1NSwgYiA9IDI1NSB9XG5cbi0tICMjIEZ1bmN0aW9ucyAjI1xuXG5mdW5jdGlvbiB0 + b19hc2NpaSh0ZXh0KVxuICAgIGxvY2FsIGJ5dGVzID0ge31cbiAgICBpZiB0ZXh0IH49IG5pbCB0aGVuXG4gICAgICAgIGZvciBpID0gMSwgc3RyaW5nLmxlbih0ZXh0 + KSBkb1xuICAgICAgICAgICAgbG9jYWwgYnl0ZSA9IHN0cmluZy5ieXRlKHRleHQsIGkpXG4gICAgICAgICAgICBpZiBieXRlIDwgMTI4IHRoZW5cbiAgICAgICAgICAg + ICAgICB0YWJsZS5pbnNlcnQoYnl0ZXMsIGJ5dGUpXG4gICAgICAgICAgICBlbmRcbiAgICAgICAgZW5kXG4gICAgZW5kXG4gICAgLS0gTnVsbCB0ZXJtaW5hdG9yXG4g + ICAgdGFibGUuaW5zZXJ0KGJ5dGVzLCAwKVxuICAgIHJldHVybiBieXRlc1xuZW5kXG5cbmZ1bmN0aW9uIGNvbmNhdF90YWJsZSh0MSwgdDIpXG4gICAgZm9yIGkgPSAx + LCAjdDIgZG9cbiAgICAgICAgdDFbI3QxICsgMV0gPSB0MltpXVxuICAgIGVuZFxuZW5kXG5cbmZ1bmN0aW9uIGNyZWF0ZV9tc2coY29udGVudClcbiAgICAtLSBTeXNF + eCBIZWFkZXJcbiAgICBsb2NhbCBtc2cgPSB7XG4gICAgICAgIDB4ZjAsIDB4MDAsIDB4MjAsIDB4MjksIDB4MDIsIDB4MGEsIDB4MDEsXG4gICAgfVxuICAgIC0tIENv + bnRlbnRcbiAgICBjb25jYXRfdGFibGUobXNnLCBjb250ZW50KVxuICAgIC0tIEVuZCBvZiBTeXNFeFxuICAgIHRhYmxlLmluc2VydChtc2csIDB4ZjcpXG4gICAgcmV0 + dXJuIG1zZ1xuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcgdGhlIGxheW91dC5cbi0tLVxuLS0tIEBwYXJhbSBsYXlvdXRfaW5kZXggbnVt + YmVyIGxheW91dCBpbmRleCAoMCA9IGVtcHR5LCAxID0ga25vYiwgMiA9IGJveClcbmZ1bmN0aW9uIGNyZWF0ZV9zY3JlZW5fbGF5b3V0X21zZyhsYXlvdXRfaW5kZXgp + XG4gICAgcmV0dXJuIGNyZWF0ZV9tc2coeyAweDAxLCBsYXlvdXRfaW5kZXggfSlcbmVuZFxuXG4tLS0gQ3JlYXRlcyBhIG1lc3NhZ2UgZm9yIGRpc3BsYXlpbmcgYSBu + b3RpZmljYXRpb24gb24gdGhlIGNlbnRlciBzY3JlZW4uXG4tLS1cbi0tLSBAcGFyYW0gbGluZTEgc3RyaW5nIGZpcnN0IGxpbmUgb2YgdGV4dFxuLS0tIEBwYXJhbSBs + aW5lMiBzdHJpbmcgc2Vjb25kIGxpbmUgb2YgdGV4dFxuZnVuY3Rpb24gY3JlYXRlX25vdGlmaWNhdGlvbl90ZXh0X21zZyhsaW5lMSwgbGluZTIpXG4gICAgbG9jYWwg + Y29udGVudCA9IHsgMHgwNCB9XG4gICAgY29uY2F0X3RhYmxlKGNvbnRlbnQsIHRvX2FzY2lpKGxpbmUxKSlcbiAgICBjb25jYXRfdGFibGUoY29udGVudCwgdG9fYXNj + aWkobGluZTIpKVxuICAgIHJldHVybiBjcmVhdGVfbXNnKGNvbnRlbnQpXG5lbmRcblxuLS0tIENyZWF0ZXMgYSBtZXNzYWdlIGZvciBjaGFuZ2luZyBtdWx0aXBsZSBz + Y3JlZW4gcHJvcGVydGllcy5cbi0tLVxuLS0tIEBwYXJhbSBjaGFuZ2VzIHRhYmxlIGxpc3Qgb2YgcHJvcGVydHkgY2hhbmdlc1xuZnVuY3Rpb24gY3JlYXRlX3NjcmVl + bl9wcm9wc19tc2coY2hhbmdlcylcbiAgICBsb2NhbCBjb250ZW50ID0geyAweDAyIH1cbiAgICBmb3IgaSA9IDEsICNjaGFuZ2VzIGRvXG4gICAgICAgIGlmIGNoYW5n + ZXNbaV0gfj0gbmlsIHRoZW5cbiAgICAgICAgICAgIGNvbmNhdF90YWJsZShjb250ZW50LCBjaGFuZ2VzW2ldKVxuICAgICAgICBlbmRcbiAgICBlbmRcbiAgICByZXR1 + cm4gY3JlYXRlX21zZyhjb250ZW50KVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgdGV4dCBwcm9wZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBAcGFyYW0gY29sdW1uX2luZGV4 + IG51bWJlciBpbiB3aGljaCBjb2x1bW4gdG8gZGlzcGxheSB0aGUgdGV4dFxuLS0tIEBwYXJhbSBvYmplY3RfaW5kZXggbnVtYmVyIGluIHdoaWNoIGxvY2F0aW9uIHRv + IGRpc3BsYXkgdGhlIHRleHRcbi0tLSBAcGFyYW0gdGV4dCBzdHJpbmcgdGhlIGFjdHVhbCB0ZXh0XG5mdW5jdGlvbiBjcmVhdGVfdGV4dF9wcm9wX2NoYW5nZShjb2x1 + bW5faW5kZXgsIG9iamVjdF9pbmRleCwgdGV4dClcbiAgICBsb2NhbCBjaGFuZ2UgPSB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1bW5faW5k + ZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJUZXh0XCJcbiAgICAgICAgMHgwMSxcbiAgICAgICAgLS0gT2JqZWN0IEluZGV4XG4gICAgICAgIG9iamVjdF9p + bmRleCxcbiAgICB9XG4gICAgY29uY2F0X3RhYmxlKGNoYW5nZSwgdG9fYXNjaWkodGV4dCkpXG4gICAgcmV0dXJuIGNoYW5nZVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEg + dmFsdWUgcHJvcGVydHkgY2hhbmdlLlxuLS0tXG4tLS0gSWYgaXQncyBhYm91dCBzZXR0aW5nIHRoZSBrbm9iIHZhbHVlLCBpdCdzIHVzdWFsbHkgbW9yZSBpbnR1aXRp + dmUgdG8gbm90XG4tLS0gZG8gdGhpcyB2aWEgc2NyaXB0IGJ1dCBieSBlbmFibGluZyBmZWVkYmFjayBvbiB0aGUgY29ycmVzcG9uZGluZyBlbmNvZGVyXG4tLS0gbWFw + cGluZ3MsIHRoZW4geW91IG5lZWQganVzdCBvbmUgbWFwcGluZyBmb3IgYm90aCBjb250cm9sIGFuZCBmZWVkYmFjay5cbi0tLVxuLS0tIEBwYXJhbSBjb2x1bW5faW5k + ZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIHZhbHVlXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgdGhlIGtpbmQgb2YgdmFsdWUg + dG8gY2hhbmdlIChzZWUgcHJvZ3JhbW1lcidzIGd1aWRlKVxuLS0tIEBwYXJhbSB2YWx1ZSBudW1iZXIgdGhlIHZhbHVlXG5mdW5jdGlvbiBjcmVhdGVfdmFsdWVfcHJv + cF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIHZhbHVlKVxuICAgIHJldHVybiB7XG4gICAgICAgIC0tIENvbHVtbiBJbmRleFxuICAgICAgICBjb2x1 + bW5faW5kZXgsXG4gICAgICAgIC0tIFByb3BlcnR5IFR5cGUgXCJWYWx1ZVwiXG4gICAgICAgIDB4MDMsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAgICBv + YmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIHZhbHVlXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGFuIFJHQiBjb2xvciBwcm9w + ZXJ0eSBjaGFuZ2UuXG4tLS1cbi0tLSBJZiB0aGUgZ2l2ZW4gY29sb3IgaXMgYG5pbGAsIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgbmlsYC5cbi0tLVxuLS0tIEBwYXJh + bSBjb2x1bW5faW5kZXggbnVtYmVyIGluIHdoaWNoIGNvbHVtbiB0byBjaGFuZ2UgdGhlIGNvbG9yXG4tLS0gQHBhcmFtIG9iamVjdF9pbmRleCBudW1iZXIgaW4gd2hp + Y2ggbG9jYXRpb24gdG8gY2hhbmdlIHRoZSBjb2xvclxuLS0tIEBwYXJhbSBjb2xvciB0YWJsZSB0aGUgUkdCIGNvbG9yICh0YWJsZSB3aXRoIHByb3BlcnRpZXMgciwg + ZyBhbmQgYilcbmZ1bmN0aW9uIGNyZWF0ZV9yZ2JfY29sb3JfcHJvcF9jaGFuZ2UoY29sdW1uX2luZGV4LCBvYmplY3RfaW5kZXgsIGNvbG9yKVxuICAgIGlmIGNvbG9y + ID09IG51bGwgdGhlblxuICAgICAgICByZXR1cm4gbmlsXG4gICAgZW5kXG4gICAgcmV0dXJuIHtcbiAgICAgICAgLS0gQ29sdW1uIEluZGV4XG4gICAgICAgIGNvbHVt + bl9pbmRleCxcbiAgICAgICAgLS0gUHJvcGVydHkgVHlwZSBcIlJHQiBjb2xvclwiXG4gICAgICAgIDB4MDQsXG4gICAgICAgIC0tIE9iamVjdCBJbmRleFxuICAgICAg + ICBvYmplY3RfaW5kZXgsXG4gICAgICAgIC0tIENvbG9yIGJ5dGVzXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxuICAgICAgICBtYXRoLmZsb29yKGNv + bG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfVxuZW5kXG5cbi0tLSBDcmVhdGVzIGEgbWVzc2FnZSBmb3IgY2hhbmdpbmcg + dGhlIHN0YXRlIG9mIGFuIExFRC5cbi0tLVxuLS0tIFBhc3NpbmcgYSBgbmlsYCBjb2xvciB3aWxsIG1ha2UgdGhlIExFRCBnbyBvZmYuXG4tLS1cbi0tLSBAcGFyYW0g + bGVkX2luZGV4IG51bWJlciB3aGljaCBMRUQgdG8gdGFsayB0b1xuLS0tIEBwYXJhbSBsZWRfYmVoYXZpb3IgbnVtYmVyIExFRCBiZWhhdmlvciAoMSA9IHNvbGlkLCAy + ID0gZmxhc2hpbmcgd2l0aCBwcmV2aW91c2x5IHNldCBzb2xpZCBjb2xvciwgMyA9IHB1bHNhdGluZylcbi0tLSBAcGFyYW0gY29sb3IgdGFibGUgdGhlIFJHQiBjb2xv + ciAodGFibGUgd2l0aCBwcm9wZXJ0aWVzIHIsIGcgYW5kIGIpXG5mdW5jdGlvbiBjcmVhdGVfbGVkX21zZyhsZWRfaW5kZXgsIGxlZF9iZWhhdmlvciwgY29sb3IpXG4g + ICAgaWYgY29sb3IgPT0gbmlsIHRoZW5cbiAgICAgICAgY29sb3IgPSB7IHIgPSAwLCBnID0gMCwgYiA9IDAgfVxuICAgIGVuZFxuICAgIHJldHVybiBjcmVhdGVfbXNn + KHtcbiAgICAgICAgMHgwMyxcbiAgICAgICAgbGVkX2luZGV4LFxuICAgICAgICBsZWRfYmVoYXZpb3IsXG4gICAgICAgIG1hdGguZmxvb3IoY29sb3IuciAvIDIpLFxu + ICAgICAgICBtYXRoLmZsb29yKGNvbG9yLmcgLyAyKSxcbiAgICAgICAgbWF0aC5mbG9vcihjb2xvci5iIC8gMiksXG4gICAgfSlcbmVuZFxuXG5mdW5jdGlvbiBjcmVh + dGVfbGVmdF9yaWdodF9hbmltYXRpb24oZ2xvYmFsX21pbGxpcywgbWF4X2NoYXJfY291bnQsIGZyYW1lX2xlbmd0aCwgdGV4dClcbiAgICBpZiB0ZXh0ID09IG5pbCB0 + aGVuXG4gICAgICAgIHJldHVybiBuaWxcbiAgICBlbmRcbiAgICBpZiAjdGV4dCA+IG1heF9jaGFyX2NvdW50IHRoZW5cbiAgICAgICAgbG9jYWwgZnJhbWVfY291bnQg + PSAjdGV4dCAtIG1heF9jaGFyX2NvdW50XG4gICAgICAgIGxvY2FsIGZyYW1lX2luZGV4ID0gbWF0aC5mbG9vcihnbG9iYWxfbWlsbGlzIC8gZnJhbWVfbGVuZ3RoKSAl + IChmcmFtZV9jb3VudCAqIDIpXG4gICAgICAgIGxvY2FsIHRleHRfb2Zmc2V0XG4gICAgICAgIGlmIGZyYW1lX2luZGV4IDwgZnJhbWVfY291bnQgdGhlblxuICAgICAg + ICAgICAgdGV4dF9vZmZzZXQgPSBmcmFtZV9pbmRleFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBsb2NhbCBkaXN0YW5jZSA9IGZyYW1lX2luZGV4IC0gZnJhbWVf + Y291bnRcbiAgICAgICAgICAgIHRleHRfb2Zmc2V0ID0gZnJhbWVfY291bnQgLSBkaXN0YW5jZVxuICAgICAgICBlbmRcbiAgICAgICAgcmV0dXJuIHRleHQ6c3ViKHRl + eHRfb2Zmc2V0ICsgMSwgdGV4dF9vZmZzZXQgKyBtYXhfY2hhcl9jb3VudClcbiAgICBlbHNlXG4gICAgICAgIHJldHVybiB0ZXh0XG4gICAgZW5kXG5lbmRcblxuLS0g + IyMgQ29kZSAjI1xubG9jYWwgY29sdW1uID0gN1xubG9jYWwgY29sb3IgPSB5IGFuZCAoY29udGV4dC5mZWVkYmFja19ldmVudC5jb2xvciBvciB3aGl0ZSkgb3IgYmxh + Y2tcbmxvY2FsIHNlY3Rpb25fbmFtZSA9IHkgYW5kIHkuc2VjdGlvbl9uYW1lIG9yIFwiXCJcbmxvY2FsIG1hY3JvX25hbWUgPSB5IGFuZCB5Lm1hY3JvX25hbWUgb3Ig + XCJcIlxubG9jYWwgbm9ybWFsaXplZF9wYXJhbV92YWx1ZSA9IHkgYW5kIHkucGFyYW1fdmFsdWUgb3IgMC4wXG5sb2NhbCBtaWRpX3BhcmFtX3ZhbHVlID0gbWF0aC5m + bG9vcihub3JtYWxpemVkX3BhcmFtX3ZhbHVlICogMTI3KVxubG9jYWwgcGFyYW1fbmFtZSA9IHkgYW5kIHkucGFyYW1fbmFtZSBvciBcIlwiXG5sb2NhbCBwYXJhbV92 + YWx1ZV9sYWJlbCA9IHkgYW5kIHkucGFyYW1fdmFsdWVfbGFiZWwgb3IgXCJcIlxucmV0dXJuIHtcbiAgICBhZGRyZXNzID0gY29sdW1uLFxuICAgIG1lc3NhZ2VzID0g + e1xuICAgICAgICBjcmVhdGVfc2NyZWVuX3Byb3BzX21zZyh7XG4gICAgICAgICAgICBjcmVhdGVfcmdiX2NvbG9yX3Byb3BfY2hhbmdlKGNvbHVtbiwgMCwgY29sb3Ip + LFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBzZWN0aW9uX25hbWUpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9j + aGFuZ2UoY29sdW1uLCAxLCBtYWNyb19uYW1lKSxcbiAgICAgICAgICAgIC0tIE1ha2UgdGhlIGtub2IgdmlzaWJsZSAoYnkgbWFraW5nIGl0IHdoaXRlKVxuICAgICAg + ICAgICAgY3JlYXRlX3JnYl9jb2xvcl9wcm9wX2NoYW5nZShjb2x1bW4sIDEsIGNvbG9yKSxcbiAgICAgICAgICAgIC0tIFJvdGF0ZSB0aGUga25vYiBzbyBpdCByZWZs + ZWN0cyB0aGUgcGFyYW1ldGVyIHZhbHVlXG4gICAgICAgICAgICBjcmVhdGVfdmFsdWVfcHJvcF9jaGFuZ2UoY29sdW1uLCAwLCBtaWRpX3BhcmFtX3ZhbHVlKSxcbiAg + ICAgICAgICAgIGNyZWF0ZV90ZXh0X3Byb3BfY2hhbmdlKGNvbHVtbiwgMiwgcGFyYW1fdmFsdWVfbGFiZWwpLFxuICAgICAgICAgICAgY3JlYXRlX3RleHRfcHJvcF9j + aGFuZ2UoY29sdW1uLCAzLCBwYXJhbV9uYW1lKSxcbiAgICAgICAgfSksXG4gICAgfVxufSAifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + IjoxLCJlZWxGZWVkYmFja1RyYW5zZm9ybWF0aW9uIjoiXG5sb2NhbCBtYWNyb19jb2xvcnMgPSB7IFsxXSA9IHsgIFtcInJcIl0gPSAzMywgIFtcImJcIl0gPSAyNDMs + ICBbXCJnXCJdID0gMTUwLCB9LCBbMl0gPSB7ICBbXCJyXCJdID0gMTIxLCAgW1wiYlwiXSA9IDcyLCAgW1wiZ1wiXSA9IDg1LCB9LCBbM10gPSB7ICBbXCJyXCJdID0g + MjU1LCAgW1wiYlwiXSA9IDM0LCAgW1wiZ1wiXSA9IDg3LCB9LCBbNF0gPSB7ICBbXCJyXCJdID0gMjU1LCAgW1wiYlwiXSA9IDU5LCAgW1wiZ1wiXSA9IDIzNSwgfSwg + WzVdID0geyAgW1wiclwiXSA9IDc2LCAgW1wiYlwiXSA9IDgwLCAgW1wiZ1wiXSA9IDE3NSwgfSwgWzZdID0geyAgW1wiclwiXSA9IDQ4LCAgW1wiYlwiXSA9IDI1NCwg + IFtcImdcIl0gPSA3OSwgfSwgWzddID0geyAgW1wiclwiXSA9IDE4MywgIFtcImJcIl0gPSAyOCwgIFtcImdcIl0gPSAyOCwgfSwgWzhdID0geyAgW1wiclwiXSA9IDE1 + NiwgIFtcImJcIl0gPSAxNzYsICBbXCJnXCJdID0gMzksIH0sfVxubG9jYWwgc2VjdGlvbl9pbmRleCA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIu + bWFjcm8uc2VjdGlvbi5pbmRleFwiKVxubG9jYWwgY29sb3IgPSBzZWN0aW9uX2luZGV4IGFuZCBtYWNyb19jb2xvcnNbc2VjdGlvbl9pbmRleCArIDFdIG9yIG5pbFxu + XG5yZXR1cm4ge1xuICAgIGZlZWRiYWNrX2V2ZW50ID0ge1xuICAgICAgICBjb2xvciA9IGNvbG9yLFxuICAgICAgICB2YWx1ZSA9IHtcbiAgICAgICAgICAgIHNlY3Rp + b25fbmFtZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubWFjcm8ubmV3X3NlY3Rpb24ubmFtZVwiKSxcbiAgICAgICAgICAgIG1hY3JvX25hbWUg + PSBjb250ZXh0LnByb3AoXCJ0YXJnZXQuZnhfcGFyYW1ldGVyLm1hY3JvLm5hbWVcIiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZSA9IGNvbnRleHQucHJvcChcInlc + IiksXG4gICAgICAgICAgICBwYXJhbV92YWx1ZV9sYWJlbCA9IGNvbnRleHQucHJvcChcInRhcmdldC50ZXh0X3ZhbHVlXCIpLFxuICAgICAgICAgICAgcGFyYW1fbmFt + ZSA9IGNvbnRleHQucHJvcChcInRhcmdldC5meF9wYXJhbWV0ZXIubmFtZVwiKSxcbiAgICAgICAgfVxuICAgIH0sXG59XG4iLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJn + ZXQiOnsiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1UeXBlIjoiZHlu + YW1pYyIsInBhcmFtRXhwcmVzc2lvbiI6Im1hcHBlZF9meF9wYXJhbWV0ZXJfaW5kZXhlc1twWzFdICogOCArIDddIiwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9XSwicGFyYW1ldGVycyI6eyIwIjp7Im5hbWUiOiJNb2RlIiwidmFsdWVDb3VudCI6MTB9LCIxIjp7Im5hbWUiOiJNYWNybyBiYW5rIiwi + dmFsdWVDb3VudCI6MTAwfX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkJ5SW5kZXgiLCJmeF9tdXN0X2hhdmVfZm9jdXMiOmZhbHNlLCJjaGFpbiI6eyJhZGRyZXNz + IjoiVHJhY2siLCJ0cmFjayI6eyJhZGRyZXNzIjoiQnlJZCIsInRyYWNrX211c3RfYmVfc2VsZWN0ZWQiOmZhbHNlLCJpZCI6IjhDNkY5RERDLUE1RTQtREM0OS04MEU3 + LUM3OUEzNzNGQTUxQiJ9LCJjaGFpbiI6Ik5vcm1hbCJ9LCJpbmRleCI6MH19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 355 67 752 675 + FXID {E1CC5C41-6731-D041-97BC-C848EF71FD9A} + WAK 0 0 + > + > + "" + cTh0UO5e7f4AAAAABQAAAAEAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAgAAAAAAAAAEAAAAAAAAADaJwQAAQAAAAAAEAA= + oJgT/gIAAABIOBIAyicEACAAAAAROFY4DBEAAFByVksmAAACWEZUUPwQAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8yMDIyMTExN8mSeRiiyFn+nHNS + NFttw9ZkbGxhuxAAAHRkdE2WAwAAEgAAAE1hcmltYmEgYSBsYSBwaWFubwAAAAAHAAAATW9kYXJ0dMIAAABIZXJlLCBsaWtlIGluIHRoZSBwaWFubywgZGFtcGVycyBs + aWZ0IGFsbCB0b2dldGhlciB3aGVuIGRlcHJlc3NpbmcgdGhlIHN1c3RhaW4gcGVkYWwgYW5kIGluZGl2aWR1YWxseSB3aGVuIHByZXNzaW5nIGEga2V5IGRvd24uIFJl + Y29tbWVuZGVkIGZvciBNSURJIGZpbGVzIHRoYXQgd2VyZSBub3QgcmVjb3JkZWQgZm9yIHRoZSBNYXJpbWJhLgAAAABEAQAAEgAAAE1hcmltYmEgYSBsYSBwaWFubwAA + AACFAgAAR1JVVQAAAAYAAAABA8BNVHJrAAACbwD/Ag8gQmxheiBKdXJqZXZjaWMAgEYAgiAyAIF6kEogPLBAACeASgApkEg2gQCASAAAkEY6gTOARgAdkEVYgUqARQCC + XJBGbYF0gEYAgSCQSGuCA4BIAIFFkElrgXuASQCCKZBIbYF+gEgAOZBGVIFngEYAVZBFPoEegEUAgReQSHAvNByBM4BIAIFhkEZagRE6LluARgCBLJBFYYFdgEUAapBD + KoEggEMAdZBBIIEegEEAgQOQQipMgDoAc0IAFZBFWGqANABCRQBzkEQygSyARABCkEI8gT2AQgBSkEMceoBDAEWQRUCBJIBFAF+QRjSBQoBGADCQSEaBJoBIAB6QLR1b + SVCBWzcWCoBJAIE9kEUmgSeARQCCEZBIcIFwgEgAapBGX4FKgEYANpBFSoE1gEUARJBDLoEAgEMAXJBCQoEkgEIAWJBBKoEmgEEATZA/IVuAPwB+kEAqaYBAAC+QQzyB + MkFECYBDAIERQQB7kDkXXoA5AFCQPTKBMUAgCoA9AIEiQAALkD8ygSSAPwAmkD0mUoA9AG6QPjwbgDcATD4AVJA5OIELgDkASJA8PIEigDwARZA+PoEAgD4Aa5A7QIEQ + gDsAHZA8NmWAPABbkDs2gRqAOwBDkDcqgViANwBCkDg+gUqAOABpkDkoSoAtAIFlOQA7kEBCgx+AQAA3kD06hVgmIoExsEB/g12QLRuDFzkihDI1F4Z+NBqGM4AmQAAt + QAAyQAA0QAA1QAA3QAA4QAA5QAA6QAA7QAA8QAA9QAA+QAA/QABAQABBQABCQABDQABEQABFQABGQABIQABJQABKQACwQAAA/y8AUHJzVRUNAABHAAAABwAAAG1hcmlt + YmEAAAAABwAAAE1hcmltYmEAAAAAAAAAAHByb21YAAAAAQAAAAEAAAABAAAABwAAAG1hcmltYmEAAAAABwAAAE1hcmltYmEAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MRAAAAAQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAA + AAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOA + AABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAAAAggMAAAABAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAAA/ + AAAAAAEAAAAAAAAAAAAAPwOAAABgAgAAAAAAqEEAAIxCAgAAAAAAgD+BAQAAAAAAAAAAAAAAAACgwAAAoEAAAABDAAAAAAOAAABQAABwQgEAAAAAAAAAAAAAAAAAAAAA + AAAAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAAAAAAA + AAAAAAAAgAAAAD8AAIA/A4AAAFAAAHBCAQAAAAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAgAAAAEAAABxAAAA + RmxhdCB0ZW1wZXJhbWVudApBbiBlcXVhbCB0ZW1wZXJhbWVudCB3aGVyZSB0aGUgb2N0YXZlIHJhdGlvIGlzIGV4YWN0bHkgMiB3aGVuIHRoZSBvY3RhdmUgc3RyZXRj + aGluZyBpcyBzZXQgdG8gMS4MAAAAAADIQgAASEMAAJZDAADIQwAA+kMAABZEAAAvRAAASEQAAGFEAAB6RACAiUQAAJZEAAAAAAAAAAAEAAAAAADcQzwAAABFAAAARgAA + AAB/AAAAAAAAAAcAAABEZWZhdWx0AAAAAAAAAAACAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAAAAAAAAAAAAwD8BAAAAAQAAAAEAAAAYAAAAA4AAAEgBAAAA + AACAPwAAAAAAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAA + gD8AAAAAA4AAAFAAAHBCAQAAAAAAAAAAAIA/A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOAAABAAwAA + AAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAASEIDgAAAYAIAAAAA + AKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACA + PwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAA + zcxMPgAAAAAAAAAAAAAAAAAAAD/NzEw/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAAAAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAABAAAA + DAAAAENsZWFuIHN0dWRpbwsAAABjbGVhbnN0dWRpbwAAEEEAAEBBAACAP83MzD4AAAAAAAAAAAAAAAAAAAAAAAAAAO/u7r8AAKpDAACAPwAAgD8BAAAABgAAAAAAgD8A + AIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAQAA + AAAAAAABAAAAAAAAPwAAAEADAAAAAACAPwAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AA + AEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAAgD8HAAAAAACAP5qZmT7NzEw/MzOzPwAAgD4IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD + gAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMA + AAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAAAQAAAAAAcEIDgAAAUAAAAAABAAAAAAAAAMommz4DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQEAAAAAAAAAAQAAADMz + Mz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAAA + 8EIAAAAADgAAAAAAAD/NzMw9zcxMPgAAAD8CAAAAAAAAAAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAACAPwQAAAACAAAACAAAAAcAAAABAAAAAQAAAAAAgD8AAIA/ + AQAAAAEAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAA + AwAAAAEAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAgEAAAABBAAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAHAAAA + AAAAAAAAAAAOAAAAEAAAAAAAAAAAAIA+AAAAAAAAAADNzEy+AAA0Q5qZGT4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAAAAAA + DgAAABAAAAAAAAAAAACAPwAAAEDNzEw/AABwwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAACbIBo/AABAwQAAAAADAAAA + AwAAAAAAAAABAAAABQAAAAQAAAABAAAAAwAAADMzMz+amZm+MzOzPwAAAAAAAAAAAAAAAAUAAAAAAAAAAADAwgAAwMIAAMDCAADAwgUAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAmpmZP5qZmb4zM7M/AAAAAAAAAAAAAAAABQAAAAAAwMIAAAAAAADAwgAAwMIAAMDC + BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAACPwvU/PQpXvx+Faz8AALRCAAAAAAAAAAAFAAAA + AABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAADMzM79cj8I+ + rkdhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA + BAAAAAAAAAADAAAAuB7FPz0KVz+uR+E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAADAAAAMzNzP5qZmb4zM7M/AACQQQAAAAAAAAAAAAAAAAEAAAAAAAAAEThWOCM/AABQclZLJgAAAlhGVFATPwAA + CgAAADIwMjMvMDYvMDYXAAAAUGlhbm90ZXEgOC4wLjAvMjAyMjExMTernU5rhOY+HbKdCnQUYNBDZGxsYdI+AAB0ZHRNDRUAABcAAABIQiBTdGVpbndheSBEIENsYXNz + aWNhbAAAAAAHAAAATW9kYXJ0dEIAAABUaGlzIHByZXNldCBvZmZlcnMgYSBCQSBtaWNpbmcgcGVyc3BlY3RpdmUgd2l0aCBhIGNsYXNzaWNhbCBzb3VuZC4AAAAARAEA + ABcAAABIQiBTdGVpbndheSBEIENsYXNzaWNhbAAAAAByFAAAR1JVVQAAAAYAAAABA8BNVHJrAAAUXAD/AhogSGF5ZG4gLSBTb25hdGEgaW4gRyBtYWpvckOwQ3qCM4Ay + OR2gRxwYkEdBcrBAIyRANSdAPSdAPydAQSZARABDfCZASCZATABDfiZATyVAUCdAUSdAUiVAUydAVIEWQFYqQFcRoEgcFbBAVwaQSDIfsEBXGqBKHAuwQFMIkEpCAoBH + Jx2wQEkoQDsDgEg9H7BAIw+gQxwAgEo/BqA7HAU3HASQQ0YGOzYCsEAABJA3ODWAOzwAQ0UENz2DDKA7HAY3HA+QOzoAoEMcApA3NQ9DQT2AO0IAQ0MIN0GBRqBFHBWQ + RUJMgEVAEKBHHBCQR0dUoDIcAEUcBjwcD5AyNgJFSQA8Oh+AR0AWPEEGMj2BaUU/HKBDHBCQQ0RlgEM3B6AyHAo8HA2QMjcHPDUGoEUcE5BFQjKAMkEAPEKCO6BHHAqA + RTsIkEdBgSCARz4doDccCkMcD5A3JwCgOxwEkEM8FDswR7BAKiCAO0AGsEA+EYA3PRKwQEIOgENAF7BARydATCdAUSdAVSRAWTtAW4IHQFkmQFQoQE8mQE4qQE0gQEwn + QEklQEMlQDooQComQAAmQ1ACoEgcAEwcC5BISARMURWwQwAOgEhFghWgThwXkE4/AIBMOSygTxwXkE9JMKBHHBSQR0AAoEocEJBKRwCAT0ECTj8kR0MXSkWDB6BIHAJF + HAyQSEACRUIQsEMRHoBFQi6wQxUmQxgoQxtfQx4noEocALBDIBSQSjYCgEg3DLBDJChDKQKgTBwVkExDEbBDKyCASj0GsEMsEaBDHA9HHACATEEAkENDBrBDLQmQR0UZ + sEMxBoBDQyGwQzgDgEdAIrBDPCdDPihDPkpDPyZDQSRDQyZDRChDRA+gRxwSkEdLBrBDRiCgQBwFsENMAqA8HAiQQEYNPDcOsENVE4BHPQ+gRRwFsENeDpBFQhewQ2UP + oEccEIBFQACgRwAARxwHsENuEZBHQhewQ3cmQ38/gDwnBqBFHA+QRVAJgEdBXqBDHACARUATkENME6A9HBOQPUINgEBAOD1BgwhDNg2gShwYkEo9A6BCHAQ+HBGQPjkG + QjBqgD42gw+wQCQcoDIcCLBAMQKAQjIUkDIoELBAOSZAPBGAMjkXsEA+JkBBJkBDJkBHJkBKJUBMJ0BPEoBKOhWwQFUAQ3okQF8AQ2IkQGEAQ0ImQE4AQxgIoEocCz4c + BJBKWQCgQhwIkD5RA0JOBLBAMgBDACdAABSAPkQAQkaDK6BCHAg+HASQQk4FPkg1gEJGED5DgTlKOwegTBwQkExKM6BOHAiATEMHkE5Na6BMHAmQTFobgE5ACqA+HABD + HA+QPkAWQ0ISgEw/HENDGT5AgwOgQxwMPhwATBwDkENLCj5IAExbNYBDRQhMQws+QoMioEMcBUkcC5BDTgBJWAagQBwIPhwHkEBDCD5BG4BDRxxAQwSwQDwJgD4/HbBA + UCJAWSdAXYEYQF4mQGEsgEksRrBAXiWgQxwCsEBZD5BDPQagQBwDPhwEShwJsEBTBpBAMAI+NQBKPx+wQE8NgENCDEA9C7BASweAPj8fsEBHKkBDBaBMHAqASjsKkExD + DbBAPyhAOwSgThwTkE5IC7BAOidAOAygTxwSkE9NGYBMPwSwQDEnQAAboFEcD5BRVQaATkEIoD4cAkIcDZBCSgOAT0QAkD48DIBRSSxCRRM+QIJsoD4cCEIcAFEcD5A+ + PQBRWgJCQSuAUUQIQkETPkCDEqA3HAqQN0kCoD4cAFMcDZA+QQBTVy+AN0QAPkAUsEAdJUA4J0A/JkBBJkBFJkBLJkBTJ0BXJUBYSYBTPQKwQFUioFgcBrBASgKgPhwG + NxwBkFhVBj5HAjdFFbBAPBmAN0AHPkEGsEApIkAAG6BWHA+QVlEjsEMVJkMZD6BVHBWQVUgIsEMdLYBYPi6gUxwRkFNLHYBWPyCwQyAegFU/CLBDIyZDKQCAU0AXoFEc + DbBDLgKgPhwGkFFADz46ELBAIgBDMSWAPj4AsEAzAEMxJ0A7KEA/AEMyJkBDAEM0JkBGAEM5JkBKAEM6JEBMJkBNTkBMAEM7I0BKAEM8E6BVHAeAUT0IkFVHBLBARgBD + PwSgPhwPkD4/E7BAQgBDQheQNyYPsEA8AENGE4A+QQKgUxwRkFNIALBANwBDSRuANyILsEA1AENLJkA1AENLBIBVPQigURwRkFFGCbBAMwBDTR2AU0AIsEAwAENRJEAt + AENVCaBPHBWQT00AgFE/CLBAKQBDWihAJwBDXiZAJgBDYx+AT0IHsEAjAENrF6A+HAI5HA2wQB8AQ3MCkD4/Bjk8BKBOHBWQTkQGsEAAAEN7JUN/FYBOQoMJoE4cF5BO + QwagPRwJgD4zCJA9PzOATkMKoEwcEZBMRz2ATEQ3oE4cD5BOVG+gTBwQkExMGYBOPUs5PRw9PghMQASgShwTkEpLB6A+HBOQPjg7gD49g0KgMhwbkDIsO4BKNoUKMiQ5 + oEocE5BKOoE5sEAiJUAvPEA4JkA8IkA+JkBAJkBDLEBGJkBJJkBMJ0BNI0BPJ0BRJ0BRTYBKFjWgTBwZkEw3SbBAUAegTRwZkE06B7BATiNASyZARiKATD0EsEA+BIBN + PxOgRBwNsEAwApBETySwQCAOoD4cBzQcAoBERQqQPi8FNC0CsEAAPIA+Ngk0NoJToEQcBDQcBz4cBpBETA80NAA+OkyAPj8CNEAfRD6BKKBFHBaQRUVAoEccB4BFQA2Q + R0dWoDQcAEUcCzwcCpBFTQI0Nwg8N0yAR0EAPD4JNDmBbkU9FKBEHBGQRE9yoDQcCzscEJA7PgM0KgygRRwUkEVGBIBEOyc7Qgk0PoFjRTw1oEccEZBHRzmAR0ZLoDkc + HJA5LRegRxwUkEdCg1iARzszoC0cCkgcFZAtJAZILxGwQCsPgDkwFbBANyZAPShAQCZAQyZARyGASC8BsEBLJkBNOkBPJ0BQJ0BQJEBNHoAtKQiwQEMfoEgcB7BAMASg + ORwEkEhCDzk8D7BAAIFngEghMKBMHBSQTEJnoEUcEIBMPgOQRUcAgDk0IqA2HBSQNjaBK4BFAACQRRJroEgcD5BIRACARTtwNjsfoEccD5BHVgqgNxwTgEg9AZA3OIF8 + gEc+GaBKHA+QSkmBA6BDHA6QQ04IgEo+DaAvHBaQLzQKgDcwghywQ3oMoEccAoBDPhCQR0cKsENpJkNbJENPJ0NBF6BFHA+QRUoAsEMwFIBHPwygMBwGsEMiD5AwOBWw + QxwcgC8kMrBDICZDIiZDJCZDJieART0AsEMoGqBIHA2wQyoGkEhHIbBDLyVDOCVDQRmgQBwCgDA/C7BDSgKQQEwHgEg5BqAxHBeQMTkAsENSJkNXKENdJ0NmJUNyJ0N9 + JEN/UKBFHBWQRUYPgEA+ZzE2M6AyHA5DHA+QMjACQ0QAgEU8g14yJA9DPhmgQhwPJhwEkEI0FyYoRLBANhuAJjYLsEBAJ0BFAENuKUBKAENPI0BPAEMyJkBUAEMcJkBY + AEMAJkBbJkBfJUBjJ0BqJ0B/UqA5HA2QOVIEgEJBDbBAVSZANChAAIFlQCUmQDAmQDkoQD4mQEEmQEYkQE0oQFYmQF0mQGIlQGkmQH9VgDk+CqA7HA+QO0ZDoDwcEZA8 + SzKAOzwWsEBRJ0AkB6AyHAhOHASQMl0JTmALsEAACIA8QjIyRYM4oDIcCZAyWzWAMkiCEaBPHBGQT0osgE48C6BRHBKQUU9QgE9CIKBCHAZIHAiQQmIHSFIVgFFBP0hA + gy6gSBwOkEhQLYBISIFroEMcD5BDSBGAQjwloEUcD5BFURqAQz9MoDwcDlQcBJA8TwdUZgSARUGCeVQ9AKBYHA+QWE2BCYBYQgygURwPkFFMghqAUT0ioFQcF5BURB2w + QCYmQDcjQEAcoE4cAoBUPwewQEcIkE5NH7BATg2APDoesEBUJ0BbAEMRJUBnAEMUJ0B/D4BOAACgThxhTn8AsEMXHaBRHBOQUU0bsEMaJUBfHaBOAAqwQE0AQx0mQEQF + oEocDpBKTRGwQEIAQyIGgFE9IrBARQBDJyZASABDLSZASwBDMSZATgBDNCRAUABDNSdAUgBDNyZAUwBDOihAVABDPg2gThwPkE5OCrBAVABDRCRATgBDTBuASjwLsEBA + AENYF6BPHA87HACwQCgAQ2kEkE9OCztDKbBAAABDfRyATjsLsEN/DIA7QYF9T0AIoEocEZBKR1uASjkloDwcFUwcApA8Nw1MTCyAPEGBZEw+G6BIHBGQSEN0gEg+FaBH + HBKQR1EeoD4cD5A+PwKAR0mDOKBHHA6QR1UYoDIcEYA+PAKQMkA/oEUcCIBHPwmQRUAwgEVBJKBHHBeQR0RfoEUcEZBFTCWAR0BhoDccDZA3RQKgQxwRkENUAIBFOggy + Piw3RIQJoCscH4BDQASQKySFX4ArNCygShwXkEozZbBAKCJANyhAPSdAPyVAQSdARCRARyZATCZATzlAUiZAVSZAVoE/QFcooEwcFYBKKASQTDhKoE0cFpBNORuwQFYn + gEw3ALBAUSdARgKATT8XoEQcCrBAOgaQREQOoD4cAjQcEJA+NAKwQCcFkDQuHYBEQwSwQAAWgD46DDQ3gn2gRBwENBwEPhwJkEREDzQzAD42PoBEQgc+Pgg0P4E5oEUc + FZBFOjeARUACoEccE5BHQ2egRRwENBwLPBwBkEVaBjQ7Djw+HoBHPhQ8Qgo0QIFaRTw5oEQcEZBETHqgRRwTkEVOAKA0HAQ7HACARD8RkDQ5ADs+M4A7QBk0QIFJRTcz + oEccFZBHSDeAR0WBEaBHHAc5HBaQRzgXORKDVIBHOSagSBwNLRwQkEgwB7BAJQyQLSUbsEAwJ0A5GYA5KQ6wQD0mQEAmQEMmQEcmQEkmQEwmQE8agEgqDLBAUSZAUyZA + USdASgmgSBwQgC0pBZBIPAmgORwAsEA+LpA5KwqwQDEmQC8kQDImQDUmQDcoQDgmQDgXoEocDrBAOAeQSj8fsEA0DYBINxGgTBwIsEAsDZBMSwKASj8XsEAeJkAACqBF + HAWAOTQIoDYcB5BFSQyATD8EkDY1RLBAICdAIyVAJCdAJidAJzaARTEVsEAmGqBIHAywQCQIkEhJHrBAISZAAACANjMooEccAjccEZBHUAQ3OwiASECCJaBIHBWQSEAH + gEcePKBKHBGQSkkFgEg7SaAvHAJDHBOQQ0sALz0IgDc6F0o+f7BAHiJAIG2gRxwHsEAdBIBDOQiQR0gbsEAAZaAwHAJFHCaQMEAARVAXgEc/GS8wggpFOAugSBwTkEhG + UoAwPiGgMRwJQBwPkDE4AEBFAoBIPYJdoEUcD4BAMQiQRT5MgDE7JqAyHAhDHACARToPkDI3AENHgyeAMi1UoEIcDYBDPAiQQjwLoCYcFZAmLx2wQCsmQD0HgCY7H7BA + QSpARSZASihAUwBDYSRAWgBDNyVAYABDADtAZCdAaSdAcCNAf4E0oDkcDZA5YQqAQkEZsEBbJUA7J0AAg21AICZAIiZAJCZAJydAKSVAKydALheAOT4HoDscCbBAMwaQ + O0cfsEA6JkA/GaA8HA2wQEMCkDxMJLBARgKAO0AksEBEIaAyHAawQCoHkDJbAKBOHA+QTmAPsEAAAoA8QjAyQoNEoDIcD5AyTD+AMkSCB6BPHBeQT0QtgE49AqBRHBGQ + UVJWgE9BGaBCHABIHA+QQmMCSFoegFFBKEhBgzOgSBwNkEhWVIBIPoEYoEMcEpBDRluAQj0PoEUcD5BFSyGAQ0E3oFQcBjwcB5BUXgg8SSaART+CVVRBD6BYHBCQWE2B + BaBRHACAWEIPkFFNVLBALyRAOihAPyZAQSRARCeAUTcAsEBFHqBUHAmwQEMGkFRMH7BAOiZAIShAAABDER+gThwHsEAqBpBOVh6AVD8CsEA8AEMAJUBDJUBJAEMRJ0BP + J0BYJ0BgIkBlAEMUJkBaBoBONSKwQEQAQxcCoFEcEZBRSxOwQCcnQAAjQCcnQDkAQxkEoEocDpBKThSwQEEHgDw8BFE+GbBARwBDHCZATyZAWChAZSZAciRAf1SgTBwP + kExIOaBOHBCQTk4FgEo8DrBAYABDIyZAQwBDLwmATD8AoE8cEDscAZBPVww7RACwQB0AQ0AmgE5BALBAAABDTiCAO0QGsENZJ0NkJ0NwOEN5JkN/W6BKHBOQSkNegEo7 + CE82JaA8HBKQPEIAoEwcFZBMRC2APEWBKkxARqBIHBOQSD8/gEhFPaBHHA+QR0shoD4cD5A+PgCAR0eDQaBHHBCQR0sJoDIcFJAyQS2gRRwCgD43FZBFPxiARwAAoEcc + IYBFQh2QRyR0oEUcDZBFTwKAR0GBA0U/DqBDHA2QQ1MEoDccDJA3QReAMj8tN0OEPaArHBmQKy4CgENAhUcrPFSgTxwakE86gVewQCQnQC8mQDkmQD0oQD8kQEImQEUm + QEomQE84QFInQFUlQFcmQFkmQFomQFtLQFsnQFyBEaBRHB+ATy0GkFEkUKBSHA2AUTcKkFI9FrBAWiRAUyZASSeAUj0AsEA+F6BKHAQ3HAywQDECkEpFCDcsHbBAHiCA + SkEHsEAAhRugRhwEShwRkEY3Ako+gQ+wQCYmQDIoQDonQD0lQD8lQEE7QEMigDcgBLBARShARyZASSRAShegMhwTkDI4IrBARyNAQCZAMChAHyZAKCZANiRAPSZAQSdA + RCtARyZASjCgSBwIgEo+CZBIQAiwQEcEgEYhCaBFHBmwQEkDkEUvF4BIPgywQE8bgEU7C7BAWCZAXidAYyVAZ0ugSBwBsEBkDpBIQhewQFogoEocCLBASwWASEMIkEo+ + GbBANyZAACyAMjURoEgcEZBISQSgMxwERRwLgEo/CJAzMwdFNgCwQB8oQDYmQEEnQEoUgEU+ErBAVQBDeCZAZABDZiRAfwBDWCZDRyhDLiZDAAD/LwBQcnNVtSkAAEcA + AAACAAAAZDQAAAAAGgAAAEdyYW5kIFN0ZWlud2F5IEQgKEhhbWJ1cmcpAAAAAAAAAABwcm9tZgAAAAEAAAABAAAAAQAAAAIAAABkNAAAAAAaAAAAR3JhbmQgU3RlaW53 + YXkgRCAoSGFtYnVyZykAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MRAAAA + AQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAA + AAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOAAABgaQAAAAAAEEEAAIA/aQAAANBT3b9ooHxLv6BIz75AA1a+ADJOPSBSiL7g3AO/AATzvUCe9L5Y + XhvAADkbvUAgsD6Ahcs+MCoXvwCuBb4wXo+//EHZv1CaUb8o9SA/5L6hPwAofb6AjTi+4AFUv4B3q75Ismk/wn8KQJTGgD/g0ZQ+MF8qPwDJ8j1ARQK/AIhivpouF0A4 + EcU/yCa2v2xHhz9Ylu4/KBXqPypUIkBUhsY/vCp0QIBUYz+gp7k/NNaUP15WgEC8ZBlAgHOFPWA+qz6APAu+lJrPP/j8ij/Qc7o/ADbmP5bGHEBSPz5AaOgjQMAKKkB4 + mmQ/gC7IvnA++r/Aui8+slsRQAjQ3r+8WNU/hlsFQHAMlT8oV2Y/8Jtqv3L5U0C8N5M/XHgRwIBXur2+yg5AStIuQIqOBUAA3de90FOJPzpGW0CUmus/YO3cPgB4Ob9w + TKi/lIE+wEzPvL+QLbu/zMi6vwStsz+aCGJAUKmJv+AroL+kgoQ/akgiQALkVkBUwZI/iCAmQGAkZD9AZku+sOY/QBiiMUDx1pNAqmNTQPwWNEAEw6Q/sDIJPyDZCkAD + AAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAACAPwAAAAABAAAAAAAAAOV/Ij4DgAAAYGkAAAAAABBB + AACAP2kAAABVpkk/jcgzTD+D/dFOP4KYgVE/D01DVD/TF1c/7f9ZP2z8XD8nDmA/AzZjP/V0Zj/7y2k/AzZjP2z8XD9NQ1Q/7f9ZPycOYD8DNmM/9XRmP/V0Zj8C+8tp + Pyk8bT8pPG0/ApvGcD+HbHQ/h2x0PwIvL3g/6w98P+sPfD8NFgiAP7Y6hD/kboY/MbaIPzOCjT8ZCZA/hKeSP8RelT99HZs/GyieP95RoT+vnKQ/nAqoP5wKqD8N552r + P/xYrz+FPrM/ZVG3PyEMwD9Bu8Q/VabJP/3Rzj9NQ9Q/7f/ZPycO4D/1dOY/KTztPyk87T8Bh2z0P4ds9D8B6w/8P+sP/D+CuxgCQIPkbgZAkwEAAAAAAAAAAAAAAAAA + oMAAAKBAAAC2QgAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AA + AGACAAAAAACoQQAArkICAAAAAACAP4GBAAAAAAAAANIEAAAAAAAAAAAAAAAAAIAAAAA/AACAPwOAAABQAABwQgEAAAAAAAAAA4AAAGBpAAAAAAAQQQAAgD9pAAAAAAAA + wAIAAIC/AABAwAAAQMAEAABAQAAAAEAAAMBAAACAPwAAgD8GAADAQAAAQEAAAIBAAAAAwAAAQMAAAIBAAACAQAgAAKBAAADAQAAAgEAAAAAAAACAPwAAAMAAAIA/AABA + wAAAQMADAABAQAAAgEAAAKBAAACgQAYAAEDAAACgQAAAAAAAAABAAABAwAAAoEAAAKBAFQAAQEAAAIBAAAAAQAAAAMAAAIA/AACAQAAAAEAAAKBAAABAQAAAQMAAAAAA + AACAQAAAQMAAAAAAAACAPwAAgL8AAEDAAACgQAAAQMAAAADAAAAAAAAAAAAFAACgQAAAQMAAAADAAACAvwAAQMAAAEDAAwAAoEAAAADAAABAwAAAQMAPAACAQAAAgD8A + AEDAAACAQAAAAAAAAIBAAACAvwAAAEAAAKBAAABAwAAAgL8AAADAAADAQAAAoEAAAEDAAABAwAMAAADAAACAPwAAQMAAAEDAAQAAgL8AAIC/BAAAwEAAAIBAAABAwAAA + AAAAAAAABwAAgD8AAEDAAAAAwAAAwEAAAEDAAADAQAAAQMAAAEDAgQAAAEADgAAAUAAAcEIBAAAAAACAPwIAAAABAAAAVQAAAEVxdWFsIHRlbXBlcmFtZW50ClRoZSBz + dGFuZGFyZCB0dW5pbmcsIHdoZXJlIHRoZSBvY3RhdmUgaXMgZGl2aWRlZCBpbiAxMiBlcXVhbCBzdGVwcy4MAAAAAADIQgAASEMAAJZDAADIQwAA+kMAABZEAAAvRAAA + SEQAAGFEAAB6RACAiUQAAJZEAQAAAAAAAAAEAAAAAADcQzwAAABFAAAARgAAAAB/AAAAAAAAAAcAAABEZWZhdWx0AAAAAAAAAAACAAAAA4AAAGBpAAAAAAAQQQAAgD9p + AAAAAAAAAOgAAAAAAAAAAAAAgD8BAAAAAQAAAAEAAAAMAAAAA4AAAEALAAAAAACAPwAAAEIAAARCAAAgQgAAJEIAAEBCAABEQgAAcEIAAHRCAADEQgAAxkILAAAAAACg + QIEAAIBAgQAAQECBAAAAQIEAAIA/gQAAAEAAAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGBpAAAAAAAQQQAAgD9pAAAAQ/SOP2ga24M/R7t5P7LC + kD+0wWY/8NxvP6orgz+zKJI/fBuKP9TYgz+vcIA/m41lP6opkz9o7ZE/Dx5zP/GChD97DGo/zk9cPwJMfT/z35o/36qQP7rLdj/OCWU/YeNrP3WwiD/tUIQ/1rpaP0Md + dz9Q/34/A3N2P7G7Wj+VKWc/Ur5SP6a8iD8/a3I/avxPP8Y0lD99UFs/0FttPymaWT+084E/F0FkP8+1kD8TcWk/zVN4PyVCfj/U/IQ/Ho9fP53Aaz82jYM/SXyDP4Gj + kD8FkXk/tfOCP+tYej+KaoQ/3w91Pze3gD8kUIw/hSSAPxEFbD/eaHU/z0ltP/vIaT/ASmI/C6qGPxpNiT8rsGo/8MSFPwqXaz9unIo/6MgdPy4Lhz8/xGQ/q7ZyP11e + Zz+wx4Y/diJ2P1XfYT9XnoA/Kqx1P2jkUz8JsH0/b3eGPypueT/u+Gg/75tyP9Cdgz9rVWg/5+9sPxZDaj/8aYk/pWSEP+TSaT9co1k/J9iCP1PcQj+bwI0/uMVxPwlN + ij+56kc/SyKNP3TsXD90x5g/6kuRPwOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAAAAAAAAAAAIA/A4AAAFAAAHBCAQAAAAAA + AAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACu + QgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAIEIDgAAAQAgAAAAAAKhBAACQQgAAqEIAALRCAADAQgAAzEIAANhCAADiQggAAAAAAAAAggAA + gD8EAABAQAAAwEAAACBBAABAQQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAYAIAAAAAANxCAADcQgIAAAAAAADAAQAAAAABAAAAA4AAAGACAAAA + AAAAAAAAgD8CAAAAAAAAAAEAAIA/A4AAAGACAAAAAAAAAAAAgD8CAAAAAACAP4EDgAAAQAQAAAAAAAAADJNJPvZ+Pz8AAIA/BAAAAAAAAACBAACAP4EDgAAAYAIAAAAA + AAAAAACAPwIAAAAAAAAAgQAAAADNzEw+AAAAAAAAAAAAAAAAAAAAP83MLEADgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gc3MLEAAAAA/A4AAAEgBAAAAAACAPwEAAAAB + AAAAAQAAAAoAAABTbWFsbCBIYWxsCwAAAG1lZGl1bWhhbGwxAACAQAAAoEEAAIA/zcyMPwAAAADNzEw9AAAAAM3MTD4AAAAAMzOzPwAAr0MAAIA/AACAPwEAAAAGAAAA + AACAPwAAgD8DgAAAYGkAAAAAABBBAACAP2kAAAAAAIA/k3E9ij8QheuRP/YonD/sUZg/j8KVP3E9ij+amZk/j8KVP3sUjj+PwpU/mpmZP7gepT/NzIw/CtejP1K4nj+a + mZk/16OQP9ejkD8VuB6lPylcjz/hepQ/PQqXP3sUjj+PwpU/SOGaP3sUrj+uR6E/AACAPylcjz9SuJ4/AACAP+F6lD+PwpU/H4WLP4/ClT+F65E/16OQP83MjD/hepQ/ + 4XqUPyd7FI4/cT2KP4/ClT/D9Yg/7FGYP83MjD8Uroc/H4WLP+xRmD9SuJ4/exSOP65HoT8pXI8/mpmZP1yPoj8K16M/ZmamP6RwnT8zM5M/j8K1P1K4nj+amZk/mpm5 + P7gepT8AAKA/zcysP5qZuT+kcJ0/SOGaP83MrD89Cpc/KVyPP8P1qD89Cpc/pHCdP5qZmT9xPao/SOGaP1K4nj9SuJ4/BUjhmj97FK4/4XqUP/YonD+kcJ0/A4AAAEAD + AAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgXQAAAAAAqEEAAIA/XQAAAAAAAACHMZrlvFRI4Ui9F1UHvUAZYTzQQni9X9hJPbA2Sb3eOeK52FWqvS64a704 + IRM612OAvBbbhL1EUgU9qImPPIKXsLruvKo9kuK1vbXxuj3abEk9Qy+hPeQWq7u9CG+9BH+gvdfcrb3bFZ09h4NDPbwFlb0yNcY82XuxvVHhrryMjPI8dP/BPVvmNrtm + Yo+6ECm6Pdxnsz0gLpo9pbQSvc7fhbyNybY8UkeoPEo0uj1y5Ge9uRqAPf81rb3UZZ09iR1dPDaOWz2uXJC9OuldvWBgoTx8h0Y9EWcKPY5ZAz0b/NY7xtSxvFcYez0B + GGW9HXzCPDOaJL1PMFK9SW86vfdOTj1s07Y9ybYnvRVLpT09Xp09RS16vT6keD2V5d+8HeGXvCXBjD1+Stk5ZvfHPVpZIT2VS848Aw+cvdqaa71swZI9KDPwPCW/s73t + o189RvyOPbD5QL0BAAAAAAAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/AACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA + 2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/AACAPwcAAAAAAIA/mpmZPs3MTD/NzMw/mpmZPggAAAAAAAAAAAAAAAAAAAAAAAAAgJmZ + PgAAAAAAAAAAAAAAAAOAAABgaQAAAAAAEEEAAIA/aQAAAAmgpT9o0B6SPwTdjz9f+JI/tQ2HP+kYgD+c+YE/f9R7Pwfcaz99cY0/O1RnP/RWYD/9rG8/lld4P+xrdT+2 + BbQ/sk+qP4t4iz+RoXQ/Zsp0PzRzmT8yJ5Y/ZyaFP7yskD/VSow/NDNwP7BZgT8cYIo/E7KMPwDgkj/j26A/9kKaP3BlZz/9xYQ/ikhfP3f3dj8BwFE/vbsmP6xqPj+I + XYs/iUglP1+GgT+aoJ4/isKnP8ZfQz9ACXg/4lmwP877nD+/Po8/iLB4Pz52kz9A2oc/4UV4P96ycz9h64U/0il8Px5/eT/vOpU/vcWvP7ijvj8VXpk/Azd6P2cmoj82 + ooQ/vfeCPx3uhj+fN5I/Z6aTP5qZRz9mppM/zYzQP2cmoj/NbIQ/mhlWP11kfD9nZsY/AQCYP8s0Uz97Tlc/4hGOPyNNvj/JuMY/AQDSP81MrD/NDKU/AcCtP/99bT/H + 8lk/IdmzP4Xrrj9APJU/DhCAP8eOZD/keI0/illoP7BGjz9B45Q/HVZ7P1jTZj/Zkls/FNViPwJfdT8b3Yk/KJqaP6xLgT8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAA + AACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAAaQAAAAAAEEEDgAAAYAwBAAAAAAAAAACAPwwB + AAAAAAAA/wAAAAD/AAAAAIsAACBBA4AAAGACAQAAAAAAAAAAgD8CAQAAAAAAAP8AAAAA/wAAAACBAAAwQQOAAABg+AAAAAAAAAAAAIA/+AAAAAAAAAD/AAAAAPcAAEBB + A4AAAGDvAAAAAAAAAAAAgD/vAAAAAAAAAP8AAAAA7gAAUEEDgAAAYOsAAAAAAAAAAACAP+sAAAAAAAAA/wAAAADqAABgQQOAAABg4wAAAAAAAAAAAIA/4wAAAAAAAAD/ + AAAAAOIAAHBBA4AAAGDZAAAAAAAAAAAAgD/ZAAAAAAAAAP8AAAAA2AAAgEEDgAAAYNMAAAAAAAAAAACAP9MAAAAAAAAA/wAAAADSAACIQQOAAABg0AAAAAAAAAAAAIA/ + 0AAAAAAAAAD/AAAAAM8AAJBBA4AAAGDOAAAAAAAAAAAAgD/OAAAAAAAAAP8AAAAAzQAAmEEDgAAAYMsAAAAAAAAAAACAP8sAAAAAAAAA/wAAAADKAACgQQOAAABgygAA + AAAAAAAAAIA/ygAAAAAAAAD/AAAAAMkAAKhBA4AAAGDiAAAAAAAAAAAAgD/iAAAAAAAAAP8AAAAA4QAAsEEDgAAAYNoAAAAAAAAAAACAP9oAAAAAAAAA/wAAAADZAAC4 + QQOAAABg0QAAAAAAAAAAAIA/0QAAAAAAAAD/AAAAANAAAMBBA4AAAGD8AAAAAAAAAAAAgD/8AAAAAAAAAP8AAAAA+wAAyEEDgAAAYOgAAAAAAAAAAACAP+gAAAAAAAAA + /wAAAADnAADQQQOAAABg2QAAAAAAAAAAAIA/2QAAAAAAAAD/AAAAANgAANhBA4AAAGDJAAAAAAAAAAAAgD/JAAAAAAAAAP8AAAAAyAAA4EEDgAAAYNsAAAAAAAAAAACA + P9sAAAAAAAAA/wAAAADaAADoQQOAAABg5gAAAAAAAAAAAIA/5gAAAAAAAAD/AAAAAOUAAPBBA4AAAGDWAAAAAAAAAAAAgD/WAAAAAAAAAP8AAAAA1QAA+EEDgAAAYMoA + AAAAAAAAAACAP8oAAAAAAAAA/wAAAADJAAAAQgOAAABgwwAAAAAAAAAAAIA/wwAAAAAAAAD/AAAAAMIAAARCA4AAAGDMAAAAAAAAAAAAgD/MAAAAAADAvwEAACBAAAAg + QAEAAMA/AADAP5kzM7M/CWZmpj+amZk/AACAPzMzMz8AAAA/mpmZPs3MTD7NzMw9AAAAAAAAAAD/AAAAAKMAAAhCA4AAAGDSAAAAAAAAAAAAgD/SAAAAAACgwAEAAAAA + AAAAAAEAAADAAAAAwIEAAEDAAQAAAAAAAAAA/wAAAADJAAAMQgOAAABgxgAAAAAAAAAAAIA/xgAAAAAAAAD/AAAAAMUAABBCA4AAAGDEAAAAAAAAAAAAgD/EAAAAAABA + wAMAAAAAAABAwAAAAAAAAAAA/wAAAAC/AAAUQgOAAABgxAAAAAAAAAAAAIA/xAAAAAAAAACBAABAwAEAAAAAAAAAAP8AAAAAvwAAGEIDgAAAYMQAAAAAAAAAAACAP8QA + AAAAAAAA/wAAAADDAAAcQgOAAABgqwAAAAAAAAAAAIA/qwAAAGZmhr8CAABAwAAAAAAAAAAA/wAAAACnAAAgQgOAAABgqwAAAAAAAAAAAIA/qwAAAAAAAAD/AAAAAKoA + ACRCA4AAAGCpAAAAAAAAAAAAgD+pAAAAAAAAAP8AAAAAqAAAKEIDgAAAYJMAAAAAAAAAAACAP5MAAAAAAAAA/wAAAACSAAAsQgOAAABgkwAAAAAAAAAAAIA/kwAAAAAA + AAACAACAwAAAAAAAAAAAAgAAQMAAAAAAAAAAAI4AAEDAgQAAAADtAADoQQEAAAAAAAAAAIsAADBCA4AAAGCTAAAAAAAAAAAAgD+TAAAAAAAAAP8AAAAAkgAANEIDgAAA + YIAAAAAAAAAAAACAP4AAAAAAAAAA/wAAOEIDgAAAYIAAAAAAAAAAAACAP4AAAAAAAEBAAQAAAAAAAAAAiwAAQMABAAAAAAAAAACMAABAwAEAAAAAAAAAAOAAADxCA4AA + AGCAAAAAAAAAAAAAgD+AAAAAAABAQAMAAAAAAABAwAAAAAAAAAAA+wAAQEIDgAAAYGwAAAAAAAAAAACAP2wAAAAAAIBABQAAAEAAAADAAACAPwAAgL8AAIA/AACAPwEA + AAAAAAAAAIIAAADAAQAAAAAAAAAA3gAAREIDgAAAYGwAAAAAAAAAAACAP2wAAABmZoa/AQAAAAAAAAAAgjMzA8ABAAAAAAAAAACHAABAwAEAAAAAAAAAANoAAEhCA4AA + AGBsAAAAAAAAAAAAgD9sAAAAAACAvwEAAAAAAAAAAIEAAEDAAQAAAAAAAAAAggAAQMABAAAAAAAAAADgAABMQgOAAABgZgAAAAAAAAAAAIA/ZgAAAAAAAAAFAABAQAAA + QMAAAAAAAACgwAAAAAAAAAAAggAAgMABAAAAAAAAAADaAABQQgOAAABgVwAAAAAAAAAAAIA/VwAAAAAAQEAFMzMDQAAAkMAAAAAAAABAwAAAAAAAAAAA0AAAVEIDgAAA + YFcAAAAAAAAAAACAP1cAAAAAAMA/gQAAAACCAABAwAEAAAAAAAAAAAIAAEDAAAAAAAAAAADMAABYQgOAAABgVgAAAAAAAAAAAIA/VgAAAAAAgD8FAAAAAGZmxj8AAEDA + ZmbGPwAAAAAAAAAAAgAAQMAAAAAAAAAAAIIAAIDAAQAAAAAAAAAAxwAAXEIDgAAAYEkAAAAAAAAAAACAP0kAAAAAAIC/gQAAQEACZGaGv2Zmhj9mZoY/AwAAQEAzMwPA + AAAAAAAAAAC/AABgQgOAAABgSQAAAAAAAAAAAIA/SQAAAAAAwL8EAABAwAAAwD8AAMC/AAAAAAAAAADDAABkQgOAAABgSAAAAAAAAAAAAIA/SAAAAAAAAAABAADAvwAA + wL+BAAAAAIEAAIDAAQAAAAAAAAAAvwAAaEIDgAAAYEQAAAAAAAAAAACAP0QAAABmZoY/AzMzA8AAAIA/AAAAAAAAAACHAABAwAMAAAAAAABAwAAAAAAAAAAAiAAAQMCC + AAAAAKcAAGxCA4AAAGA+AAAAAAAAAAAAgD8+AAAAAAAAAAIAAEDAAAAAAAAAAAC6AABwQgOAAABgOQAAAAAAAAAAAIA/OQAAAAAAwD8BAACAvwAAgL+BAAAAAAMAAEDA + AACAvwAAAAAAAAAAsAAAdEIDgAAAYDkAAAAAAAAAAACAPzkAAABmZoY/gQIAQMACAACAv2Zmhj9mZoY/AofNTL0AAAAAAAAAALAAAHhCA4AAAGA2AAAAAAAAAAAAgD82 + AAAAAAAAAIIAAIA/AwAAAAAAAIDAAAAAAAAAAACDAABAwAIAAABAAAAAAAAAAACnAAB8QgOAAABgMQAAAAAAAAAAAIA/MQAAAAAAgD8BAAAAAAAAAACBAABAwIEAAAAA + qgAAgEIDgAAAYDAAAAAAAAAAAACAPzAAAAAAAAAArwAAgkIDgAAAYC0AAAAAAAAAAACAPy0AAAAAAIA/BQAAAAAAAIC/AAAAAAAAgL8AAAAAAAAAAIQAAEDAAQAAAAAA + AAAAnwAAhEIDgAAAYCsAAAAAAAAAAACAPysAAAAAAIA/gQAAAMABAACAvwAAgL8BAAAAAAAAAACFAABAwIEAAAAAAgAAQMAAAAAAAAAAAJkAAIZCA4AAAGAnAAAAAAAA + AAAAgD8nAAAAAAAAAKYAAIhCA4AAAGAmAAAAAAAAAAAAgD8mAAAAZmaGv4EAAEBAgwAAAACFAABAwAEAAAAAAAAAAJcAAIpCA4AAAGAkAAAAAAAAAAAAgD8kAAAAAACA + vwEAAIA/AACAPwICAEDAAAAAAAAAAACeAACMQgOAAABgIgAAAAAAAAAAAIA/IgAAAPv//78F//9/QDIzA0AAAAAAMzMDwAAAAAAAAAAAmwAAjkIDgAAAYCAAAAAAAAAA + AACAPyAAAAACAEDAAQAAAEAAAABABGZmhj9mZoa/AAAAQAAAAAAAAAAAmAAAkEIDgAAAYB0AAAAAAAAAAACAPx0AAAAAAAAAnAAAkkIDgAAAYBwAAAAAAAAAAACAPxwA + AAAAAAAAmwAAlEIDgAAAYBsAAAAAAAAAAACAPxsAAAAAAAAAmgAAlkIDgAAAYBkAAAAAAAAAAACAPxkAAAAAAAAAmAAAmEIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAIC/ + AQAAAAAAAAAAAs3MjD8AAAAAAAAAAJIAAJpCA4AAAGAWAAAAAAAAAAAAgD8WAAAAZmaGPwMAAABAAgBAQDIzA8AyMwPAAQAAAAAAAAAAjwAAnEIDgAAAYBUAAAAAAAAA + AACAPxUAAAAAAAAAAgAAgD8AAAAAAAAAAJEAAJ5CA4AAAGAUAAAAAAAAAAAAgD8UAAAAAACAv4EAAEDAAQAAAAAAAAAAjwAAoEIDgAAAYBMAAAAAAAAAAACAPxMAAAAA + AADAAQAAAAAAAAAAgwAAAMABAAAAAAAAAACKAACiQgOAAABgEgAAAAAAAAAAAIA/EgAAAKDAPkCDAADAvwEAAAAAAAAAAIsAAKRCA4AAAGARAAAAAAAAAAAAgD8RAAAA + ZmaGPwYAAIC/ZmaGPwAAAEAAAAAAAACAPwAAAAAAAAAAiQAApkIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAABAB2Zmhj+ambm/AAAAAAAAwL8AAAAAAADAvwAAAAAAAAAA + hwAAqEIDgAAAYA8AAAAAAAAAAACAPw8AAAAAAAAAjgAAqkIDgAAAYA4AAAAAAAAAAACAPw4AAAAAAAAAjQAArEIDgAAAYA0AAAAAAAAAAACAPw0AAAAAAAAAjAAArkID + gAAAYAwAAAAAAAAAAACAPwwAAAAAAAAAiwAAsEIDgAAAYAwAAAAAAAAAAACAPwwAAAAAAAAAiwAAskIDgAAAYAsAAAAAAAAAAACAPwsAAAAAAAAAigAAtEIDgAAAYAoA + AAAAAAAAAACAPwoAAAAAAAAAiQAAtkIDgAAAYAoAAAAAAAAAAACAPwoAAAAAAAAAiQAAuEIDgAAAYAkAAAAAAAAAAACAPwkAAAAAAAAAiAAAukIDgAAAYAkAAAAAAAAA + AACAPwkAAAAAAAAAiAAAvEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAvkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAwEIDgAAAYAgAAAAAAAAAAACAPwgA + AAAAAAAAhwAAwkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAxEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAxkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAA + hwAAyEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAykIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAzEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAzkID + gAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA0EIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA0kIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA1EIDgAAAYAgA + AAAAAAAAAACAPwgAAAAAAAAAhwAA1kIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA2EIDgAAAYAQAAAAAAAAAAACAPwQAAAAAAAAAgwAA2kIDgAAAYAQAAAAAAAAA + AACAPwQAAAAAAAAAgwAA3EIDgAAAYAQAAAAAAAAAAACAPwQAAAAAAAAAgwAA3kIDgAAAYAQAAAAAAAAAAACAPwQAAAAAAAAAgwAA4EIDgAAAYAQAAAAAAAAAAACAPwQA + AAAAAAAAgwAA4kIDgAAAYAQAAAAAAAAAAACAPwQAAAAAAAAAg9y93D0DgAAAQBYAAAAAABBBAACoQQAAwEEAAOBBAADoQQAAAEIAAAhCAAAYQgAAJEIAADBCAABAQgAA + TEIAAGBCAABwQgAAgEIAAIhCAACQQgAAmEIAAKBCAACoQgAAwEIAANhCFgAAAG8GIT8EtOWMP6RTjz8015E/amqRP2pqkT+C7YGOPwFqapE/amqRPwTtgY4/mraLP+2B + jj+atos/mraLP4Gv61c/gVvtRT8C56YpP0GD7T4BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAABAAAAAQAAAMP1aD/NzIw/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAA + AAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOA + AABIAQAAAAAAgD8EAAAAAgAAAAMAAAAEAAAAAQAAAAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AAAEgBAAAAAAAA + AAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMAAAAHAAAAAQAAAAAAAAAOAAAAEAAAAAAAAACPwnU9AACAPwAAAAAAAAC/AABwQgAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAAEAAAAAAAAADgAAABAAAAAAAAAAAADIQgAAAAAAAAA/AAAAQAAAlkMAAIC/AAAAPwAAgD8A + QBxFAACAPwAAAD8AAEBAAAAAAAAAAAAAAAAAAwAAAAoAAAABAAAAAAAAAA4AAAAQAAAAAAAAAAAAyEIAAAAAAACAPwAAAEAAAPpDAAAAAAAAgD8AAIA/AKCMRQAAAEAz + MzM/AABAQAAAAAAAAAAAAAAAAAEAAAABAAAAAACAPwAAQMEAAAAAAwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAAD2KBxArUfhPwAAwD8AAGHDAAAAAAMAAAAF + AAAAAACAPwAAcMIAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAAAAIEDK + zEw/AADAPwAANEIAAAAAAwAAAAUAAAAAAHDCAACAPwAAwMIAAMDCAADAwgUAAAAAAAAAF7dROQAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAA + AAAABAAAAAEAAAADAAAA0FG4Ph2Fqz8pXI8/OF3jQl/0h0MFAAAABQAAAAAAIMEAAMDBAABwwgAAcMIAAHDCBQAAABe3UTkXt1E5AAAAAAAAAAAAAAAABQAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAAAfhes+UriePylcjz8AAOJCAACywgUAAAAFAAAAAADAwQAAIMEAAHDCAABwwgAAcMIFAAAAAAAAAG8SAzoA + AAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAHDCAABwwgAAcMIA + AHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAwAAAFK4Hj/sUbi+ZmamPwAAkEEA + AAAAAAAAAAAAAAABAAAAAAAAABE4VjjufAAAUHJWSyYAAAJYRlRQ3nwAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3QpCCL2iVLxk/HdHa + tNu0LmRsbGGdfAAAdGR0Ta9SAAAHAAAASzIgV2FybQAAAAAHAAAATW9kYXJ0dCMAAABBIHByZXNldCBwcm92aWRpbmcgYSB3YXJtIGFtYmlhbmNlLgAAAABEAQAABwAA + AEsyIFdhcm0AAAAAU1IAAEdSVVUAAAAGAAAAAQPATVRyawAAUj0A/wIQRGF2aXMgLSBFeG9yZGl1bQCQMSEisEAGBkAHCkAIAEAJAEAKBEALAEAMAkANBkAOAEAPAkAQ + CEARAEASAEATAkAUAEAVAkAWAEAXBEAYAkAZAEAaDkAbAEAcAEAdAEAeAEAfAEAgAEAhBEAiAkAjAEAkAkAlAkAmAkAoCkApAEAqAEArAEAsAEAtBEAuAEAvAEAwBpBB + DQCwQDECQDIMQDMAQDQAQDUAQDYAQDcAQDgAQDkEQDoCQDsCQDwAQD0OQD4AQD8AQEAAQEEAQEIAQEMEQEQCQEUAQEYEQEcAQEgMQEkAQEoAQEsAQEwCQE0AQE4AQE8C + QFAGQFEAQFICQFMKQFQAQFUAQFYCQFcAkDgrALBAWARAWQBAWgKQRCYAsEBbDkBcAEBdAEBeAEBfAEBgAEBhAEBiBEBjAkBkEEBlAEBmAEBnAEBoAEBpAEBqBEBrAEBs + AkBtBEBuAEBvDEBwAEBxAEByAkBzAEB0BIAxQASwQHUCQHYIgEFABLBAdwBAeABAeQBAegRAewBAfBJAfQBAfgBAfx6QSCVigERACjhALJBBHxRNOUCASECBZE1AhkRB + QFSQQiMSTi2BSIBCQChOQIFykEQZAlA4gQCAREA0UECCYLBAfQJAfAJAegJAeQJAdwBAdgBAdAhAcghAcQBAbwBAbQBAawBAagBAaARAZgBAZAJAYgJAYQJAXwJAXQBA + WwpAWQBAVwBAVQBAUwBAUgJAUAJATgRATAJASgJASAJARgpARABAQgBAQABAPgBAPABAOgRAOABANgJANABAMgZAMApALgBALABAKgBAKABAJgBAJQRAIwBAIQBAHwRA + HgBAHAJAGwKQRxwMsEAZAEAYAEAXAEAVAEAUAEATAEASBkAQAEAPAkAOAEANDEAMAEALAEAKAEAJAEAIBEAHAkAGAEAFCJAxIQqwQAQCkEoqHFItgUCwQAUAQAYCQAcA + QAgEQAkEQAoMQAsAQAwAQA0AQA4AQA8AQBACQBEGQBIAQBMCQBQCQBUAQBYKQBcAQBgAQBkAQBoCQBsCQBwAQB0AQB4IQB8AQCAAQCEKQCIAQCMAQCQAQCYCQCcCQCgA + QCkCQCoCQCsCQCwCQC0MQC4AQC8AQDAAQDEAQDIAQDMCQDQEQDUAQDYCQDcCQDgCQDkKQDoAQDsAQDwAQD0AQD4CQD8CQEAAQEEGQEICQEMKQEQAQEUAQEYCQEcCQEgA + QEkEQEoCQEsAQEwMQE0AQE4AQE8AQFAAQFEGQFICQFMEQFQKQFUAQFYAQFcGQFgAQFkGQFoKQFsAQFwCQF0CQF4GQF9YQGg4gDFAJLBAboJkkDsaerBAdBKAO0CHTLBA + ei6QQQ+IOrBAfxyAQUCCSlJAgUJKQFpHQFyQTTWDVoBNQIhgkFAigQqAUEAQkFIpgQZQJnCAUkBckE8uFoBQQIFGsEB9BkB8AkB6CkB4AEB3AEB1AEBzAEBxAkBwAkBu + AEBsBEBqAkBoAEBmAkBkCkBiAEBgAEBeAEBcAkBaAEBYAEBWAoBPQAawQFQAQFIAQFAOkFAfALBATQBASwBASQBARwBARQBAQwRAQQJAPgBAPAJAOgJAOAJANQBAMwpA + MQBALwBALABAKgJAKABAJgJAJQZAIwBAIQJAHwpAHQBAHABAGgBAGQBAFwJAFgBAFABAEwRAEgJAEQBADwJADg5ADQBADABACwBACgBACQBACARABwJABgRABQCQMSEM + sEAEgVJABQpABgBABwBACABACQJACgJACwZADABADQJADgBADwxAEABAEgBAEwBAFABAFQJAFgBAGARAGQBAGgJAGwRAHQpAHgBAIABAIQBAIgBAJABAJQZAJgBAKABA + KQJAKwJALAJALQpALwBAMABAMQBAMwBANAJANgBANwJAOAZAOgBAOwBAPAJAPgpAPwBAQABAQgBAQwJARAJARgBARwRASABASQBASxBATABATQBATgBATwBAUQBAUgBA + UwJAVAJAVQJAVwBAWARAWQBAWgJAWwpAXABAXgBAXwBAYAJAYQBAYgJAZARAZQJAZgJAZwhAaABAaQBAagBAawJAbABAbQBAbgRAbwJAcAJAcQBAcg5AcwBAdABAdQBA + dgBAdwRAeAJAeQRAegJAewpAfABAfQRAfgZAf4FokDooIIAxQIQMOkAwkD8mgTqAP0CDIJBCIQhJIoQOgEJAgXJJQIEgkEkWiU6ASUCDOrBAfQBAfABAewBAegZAeQBA + dwBAdgRAdQJAcwJAcghAcABAbwBAbgBAbAJAawJAaQBAaAZAZgJAZQBAZABAYgpAYQBAXwBAXgBAXAJAWwBAWQRAWAJAVgBAVQBAVAJAUgxAUQBAUABATgBATQBASwBA + SgZASQBARwJARgJARQRAQwpAQgBAQQBAQABAPwBAPQBAPARAOwJAOgBAOQJANwBANgJANQJANApAMwBAMgBAMABALwJALgBALQJALARAKwJAKgBAKQ5AKABAJwBAJgBA + JQBAJABAIwBAIgRAIQJAIABAHxBAHgBAHQBAHABAGwJAGgJAGQZAGABAF0KQQg4OSBYILBMusEAhDEAiAEAjAEAkCEAlAEAmAEAnDkAoAEApAEAqAEArBEAsAEAtEkAu + AEAvAEAwAEAxAEAyBEAzAEA0AkA1BEA2AEA3DEA4AEA5AEA6AEA7BEA8AkA9AkA+AkA/DEBAAEBBAEBCAEBDAkBEBkBFAEBGEEBHAEBIAEBJAEBKAEBLBEBMAEBNBkBO + AEBPDEBQAEBRAEBSAkBTAEBUBkBVAEBWAkBXCkBYAEBZAEBaAkBbAkBcBEBdAkBeDEBfAEBgAEBhAEBiAEBjBEBkBEBlBkBmCEBnAEBoAEBpBkBqAEBrBEBsAEBtDEBu + AEBvAkBwCEBxAIAsQACwQHIOQHMAQHQAQHVIQH+EXpA2HSY/DxQ8DGqAUEAaP0AGNkAOPEAASEA2QkCHZLBAfQBAfAJAewBAeQJAeAZAdgBAdQJAcwBAcgpAcABAbgBA + bQBAawJAaQBAaAJAZgJAZQJAYwBAYQZAYApAXgBAXQBAWwBAWQBAVwBAVgRAVABAUgJAUQBATwRATQJATABASgpASABARwBARQBARAJAQgJAQQJAPwJAPQBAPAJAOg5A + OQBANwBANgBANABAMwBAMQBAMARALgJALQBAKwJAKg5AKQBAJwBAJgBAJQBAIwBAIgBAIQJAIAJAHgJAHQRAHABAGwJAGgpAGQBAFwBAFgBAFQJAFABAEwBAEgJAEQRA + EABADw5ADgBADQBADABACwBACgBACQhACACQLCYAsEAHBEAGDJA1Ih4lGDiwQAcEQAgAQAkGQAoCQAsKQAwAQA0AQA4CQA8GQBACQBEAQBIOQBMAQBQAQBUAQBYAQBcC + QBgCQBkAQBoIQBsKQBwAQB0AQB4AQB8AQCACQCECQCIAQCMEQCQCQCUAQCYCQCcMQCgAQCkAQCoAQCsAQCwAQC0CQC4CQC8CQDACQDICQDMMQDQAQDUAQDYAQDcAQDgA + QDkEQDoCQDsAQD0CQD4CQEACQEEKQEIAQEMAQEQCQEUCQEYAQEgEQEkCQEoAQEsMQEwAQE0AQE8AQFACQFEAQFIEQFMCQFQAQFUOQFYAQFcAQFgAQFkAQFoAQFsAQFwG + QF0AQF4CQF8CQGACQGEIQGIAQGMAQGQAQGUAQGYCQGcCQGgCQGkAQGoGQGsCQGwGgCxABLBAbQBAbgBAbwJAcAJAcQBAcgRAcwBAdA5AdQBAdgBAdwBAeABAeQSAJUAC + sEB6AkB7BEB8CkB9AEB+BEB/EIA1QIhmkDUWIjgcIjovFEEdLkQqDj0lMEkagQZNO0aAQUAiREAsSUBCTUCCbJBCIRpOLIFqgEJARE5ALj1AHjVADjpAHDhAgQyQUD0k + Rg6BIoBGQBBQQIFIsEB+AEB8AEB7AEB5AEB4AkB2AEB1BkBzAEByAkBwAkBuAEBtCkBrAEBpAEBoAEBmAEBkAkBjBEBhAEBfAkBeBEBcAEBaDEBYAEBXAEBVAEBTAEBR + AkBPAEBOAkBMAEBKBkBIAEBGAEBFDEBDAEBBAEBAAEA+AEA8AEA7AEA5BkA3AkA2AkA0AEAyDEAxAEAvAEAtAEAsAEAqAEApAEAnBkAlAEAkAEAiBkAhDEAgAEAeAEAd + AEAbAEAaAEAZAkAXAEAWAkAVAkAUAEATAkASAkARDEAQAEAPAEAOAEANAEAMAEALBkAKAEAJAkAIDkAHAEAGAEAFWEAGBEAHAJA0JAKwQAgGQAkAQAoMkEckAEwyALBA + CwBADABADQJADgCQUzIAsEAPBEAQAEAREkASAEATAEAUAEAVAEAWAEAXAEAYAkAZAkAaAEAbEEAcAEAdAEAeAEAfAEAgAEAhAEAiAkAjAkAkAEAlBkAmAEAnAkAoDEAp + AEArAEAsAEAtAEAuAEAvAkAwAEAxBEAyAEAzAkA0DEA2AEA3AEA4AEA5AEA6AEA7AEA8BkA9AkA/AJAqDQCwQEAQQEEAQEIAQEMAQEQAQEUAQEYAQEcAQEgCQEkCQEoA + QEsGQEwAQE0MQE8AQFAAQFEAQFIAQFMAQFQAQFUCQFYCQFcEQFgAQFkAQFoOQFsAQFwAQF0AQF4AQF8AQGAAQGEGQGIAQGQAQGUEQGYKQGcAQGgAQGkAQGoAQGsAQGwE + QG0AQG4CQG8CQHAEQHEAQHIMQHMAQHQAQHUCQHYAQHcAQHgGQHkAQHoKgFNABLBAewBAfABAfQJAfgRAfxKATEAsR0CCNpA5LkJHMiI7Hg5AK4MUgDtAZkBAIDRAgQCw + QH4AQH0AQHsAQHoAQHgAQHcEQHUCQHQAQHIAQHAEQG8CQG0MQGsAQGoAQGgAQGYAQGUAQGMAQGECQGAEQF4CQFwCQFoAQFgMQFYAQFUAQFMAQFEAQE8AQE0CQEwCQEoC + QEgAQEYSQEUAQEMAQEEAQD8AQD0AQDwAQDoCQDgAQDYCQDUAQDMCQDEEQC8AQC4MQCwAQCoAQCkAQCcAQCUAQCQAQCIIQCEAQB8AQB4CQBwMQBsAQBoAQBgAQBcAQBYA + QBUAQBQGQBMAQBICQBAOQA8AQA4AQA0AQAwAkDMZALBACwBACgJACQJACABABwZABg6QPx0AsEAFAJA7FQCwQASBAkAFAEAGAEAHAEAIBEAJAEAKAEALAkAMBEANAEAO + DkAPAEAQAEARAEASAEATAEAUAkAVBEAWAkAXAEAZAEAaDkAbAEAcAEAdAEAeAEAgAEAhAEAiBEAjAEAlAEAmCEAnCkAoAEApAEArAEAsAEAtAEAuAkAvAkAwAEAyAkAz + AkA0AEA1AkA2DEA3AEA5AEA6AEA7AEA8AEA9AkA/BEBAAkBBAkBCAEBDDkBFAEBGAEBHAEBIAEBJAEBLAEBMAkBNAkBOAkBPBEBQDEBRAEBSAEBTAEBUAEBVAEBWAkBY + AEBZAEBaAkBbBkBcDEBdAEBeAEBfAEBgAEBhAEBiAEBjAkBkAEBlBEBmAkBnAEBoDEBpAEBqAEBrAEBsAEBtAEBuAkBvBkBwAEBxAkByDEBzAEB0AEB1AEB2AEB3BEB4 + AkB5BkB6AEB7AkB8CkB9AEB+AkB/aIA7QFw/QDZHQId4kEcYcFMzgUSAOUAYU0AER0CBCipAWDNAg0aQRCIgsEB+AEB9AEB7AkB6AkB4AJBQIgSwQHcCQHUAQHMCQHIM + QHAAQG4AQG0AQGsAQGkAQGgCQGYEQGQAQGICQGECQF8CQF0KQFsAQFoAQFgAQFYAQFQAQFMCQFECQE8CQE0AQEwEQEoCQEgMQEcAQEUAQEQAQEIAQEAAQD8CQD0AQDwG + QDoCQDkAQDcAQDYMQDUAQDMAQDIAQDEAQC8CQC4AQC0EQCwAQCsAQCoQQCkAQCgAQCcAQCYAQCUAQCQEQCMAQCICQCEEQCAOQB8AQB40QB8OQCAAQCEAQCIIQCMCQCQM + QCUAQCYAQCcAQCgGQCkAQCoQQCsAQCwAQC0AQC4AQC8EQDACQDEAQDIEQDMMQDQAQDUAQDYAQDcAQDgCQDkAQDoIQDsAQDwMQD0AQD4AQD8AQEAAQEEAQEICQEMAQEQE + QEUAQEYCQEcOQEgAQEkAQEoAQEsAQEwAQE0AQE4EQE8EQFAAQFIEQFMMQFQAQFUAQFYAQFgAQFkAQFoAQFsCQFwAQF4AQF8GQGAAQGECQGIKQGQAQGUAQGYAQGcCQGgA + QGkCQGsCQGwCQG0CQG4MQG8AQHEAQHIAQHMAQHQAQHUAQHYGQHcAQHgCQHkCQHoMQHsAQHwAQH0AQH4AQH+BNEB+AEB8AkB7BEB6AEB4AEB3AkB1DEB0AEByAEBwAEBv + AEBtAEBsAkBqBEBpAkBnAEBmBEBkAEBiAkBhCkBfAEBeAEBcAEBaAkBZAEBXAkBWAEBUBEBSAkBRAkBPCkBOAEBMAEBLAEBJAEBIAkBGAkBFAkBDAkBCAkBADkA/AEA9 + AEA8AEA7AEA5AEA4AEA3BEA1AEA0AkAzAEAyAkAwDkAvAEAuAEAtAEAsAEArAEApAkAoAkAnAEAmBEAlAEAkAkAjDEAiAEAhAEAgAEAfCEAeAEAdDpAnLSywQB4AQB8K + QCAAQCEOQCIAQCMAQCQAQCUCQCYCQCcCQCgQkDYNALBAKQBAKgBAKwBALABALQBALgJALwJAMARAMQBAMgJAMwxANABANQBANgBANwBAOABAOQBAOgZAOwJAPABAPQJA + PgiAJ0AEsEA/AEBAAEBBAEBCAEBDAEBEBEBFAEBGAkBHAkBIBEBJCkBLAEBMAEBNAEBOAEBPAEBQBEBSAEBTAEBUAkBVBEBWAEBYDEBZAEBaAEBbAEBcAEBdAEBeAkBf + AEBgBIBEQASwQGEAQGIAQGQIgDZABLBAZQBAZgBAZwBAaAJAaQBAawRAbABAbQBAbgJAbw5AcABAcQBAcgBAcwBAdABAdQBAdgRAdwJAeABAeQJAegJAewJAfApAfQBA + fgBAf4MwkDYcAjocgXKAOkBONkAEkDYLgV49IgREKRQ6IABJLVSAPUAaOkAwNkBSREAuSUCBYrBAfgZAfQBAewBAegJAeAJAdwJAdQpAcwBAcgBAcABAbwBAbQJAawBA + agJAaAZAZgBAZQJAYwpAYQBAYABAXgBAXAJAWgBAWQJAVwBAVQRAUwBAUgBAUBBATgBATABASwBASQBARwBARQBARAJAQgJAQAJAPwBAPRBAOwBAOgBAOABANgBANQBA + MwBAMgRAMABALwJALQRALABAKgJAKQBAKApAJgBAJQBAIwCQQiMAsEAiAkAhApAsMASwQCACQB4AQB0CQBwMQBsAQBoAQBkAQBcAQBYAQBUAQBQEQBMAQBICkEgrELBA + EQBAEABADwBADgBADQBADABACwBACgJACQJACARABwBABlBABQBABgBABwJACAhACQBACgxACwBADABADQBADgBADwZAEABAERBAEgBAEwBAFABAFQBAFgBAFwRAGABA + GQJAGgRAGwJAHABAHQxAHgBAHwBAIABAIQBAIgBAIwJAJARAJQJAJgBAJwJAKAxAKQBAKgBAKwBALABALQBALgJALwJAMABAMgJAMxBANABANQBANgBANwBAOABAOQBA + OgRAOwBAPQBAPgZAQAxAQQBAQgBAQwBARABARQJARgJASARASQBASgJASwBATAxATQBATgBATwBAUABAUQJAUgCALEAAsEBTBkBUAkBVDkBWAEBXAEBYAEBZAEBaAEBb + AEBcAkBdAkBeAEBfBkBgAEBhCkBiAEBjAEBkAEBlAkBmAkBnAEBoBEBpAEBqDkBrAEBsAEBtAEBuAkBvBkBwAEBxAkByDEBzAEB0AEB1BEB2AkB3EEB4AEB5AEB6BEB7 + REB/hmqQNh4UPBkAPxWBVIBQQIEESEBEP0AMQkBSPEAUNkCJOLBAfgRAfAJAewJAeQBAeApAdgBAdQBAcwBAcgBAcAJAbwBAbQZAbAJAagBAaAJAZwJAZQpAZABAYgBA + YABAXwBAXQJAXAJAWgJAWQBAVwRAVQJAVABAUgpAUABATwBATQBATAJASgJASQBARwRARQBARAJAQgBAQQ5AQABAPgBAPQBAOwBAOgBAOABANwZANgBANAJAMwJAMQJA + MApALwBALQBALABAKwBAKQJAKABAJwJAJgBAJQZAIwJAIgpAIQBAIABAHwBAHQBAHAJAGwJAGgBAGQRAGABAFwBAFgJAFQxAFABAEwBAEgBAEQBAEAJADwRADgJADQSQ + NUMMsEAMAEALAEAKAEAJAJAsMBwlHSiwQAoIQAsAQAwMQA0AQA4CQA8AQBAGQBEOQBIAQBMAQBQAQBUAQBYEQBcCQBgCQBkOQBoAQBsAQBwAQB0AQB4GQB8AQCACQCEC + QCICQCMMQCQAQCUAQCYAQCcAQCgCQCkAQCoCQCsEQCwAQC0CQC4MQC8AQDAAQDEAQDIAQDMAQDQCQDUCQDYCQDcAQDgCQDkOQDoAQDsAQDwAQD0AQD4AQD8AQEAEQEEC + QEIAQEMEQEQAQEUMQEYAQEcAQEgAQEkAQEoAQEsCQEwAQE0CQE4GQE8AQFAMQFIAQFMAQFQAQFUAQFYAQFgCQFkAQFoEQFsAQFwCQF0CQF4MQF8AQGAAQGEAQGIAQGQA + QGUCQGYEQGcAQGgCQGkCQGsCQGwKQG0AQG4AQG8AQHAAQHECQHICQHMAQHQGQHUCQHYAQHcKQHgAQHkAQHoAQHsEQHwAQH0CQH4CQH+BFoAlQBA1QHIsQIggkD8xEE0+ + BjwqAkQuBEk0DDUfADgrAkEtgUCAP0AENUAITUAAPEA4SUAGQUASREAIOECGYLBAfgRAfQJAewJAeg5AeABAdwBAdQBAdABAcgBAcABAbwRAbQJAbABAagRAaQJAZwpA + ZQBAZABAYgBAYQBAXwJAXgJAXAJAWwBAWQRAVwBAVgBAVAxAUwBAUQBAUABATgBATQBATAJASgZASQBARwJARgJARAxAQwBAQgBAQABAPwBAPgBAPABAOwJAOgBAOQJA + OAZANgBANQxANABAMwBAMgBAMQBAMABALwJALgJALQBALARAKwBAKg5AKQBAKABAJwBAJgCQNU4CLT8EsEAlEpA5QAJBMhQlKQA9OBywQCYAQCcQQCgAQCkAQCoAQCsE + QCwAQC0GQC4AQC8MQDAAQDEAQDIAQDMCQDQCQDUEQDYAQDcCQDgOQDkAQDoAQDsAQDwAQD0AQD4AQD8EQEACQEEAQEIMgCVABLBAQwBARABARQBARgBARwBASABASQJA + SwBATAJATQZATgiANUAEsEBPAEBQAEBSAEBTAEBUAkBVAEBXAEBYAkBZAkBaAkBbAEBdAEBeDkBfAEBhAEBiAEBkAEBlAEBnAkBoBEBqAEBrAkBsAEBuDkBvAEBxAEBy + AEB0AEB1AEB2AEB4BEB5AkB6AkB7AEB9CoAtQAA5QASwQH4AQH8ugD1APEFAhVqQRUMKPCsAPzMATUgCSUQCQTCBQIBBQBg/QEpFQA48QBpNQBJJQIZokEY3ArBAfghA + fQBAewBAeQ5AeACQPCQAsEB2AEB0AEBzAEBxAEBvBEBuAEBsApBNMgI/HwSwQGoAQGgMQGYAQGUAkEgtALBAYwBAYQBAXwJAXgJAXABAWgRAWABAVgBAVQJAUwxAUQBA + TwBATgCQQRkAsEBMAkBKAkBIAkBGAkBFAEBDAEBBEEBAAEA+AEA8AEA7AEA5AEA3AEA2BEA0AEAzAEAxAkAwAkAuDEAtAEArAEAqAEAoAEAnAEAmAkAkAEAjAkAiBkAh + AEAgAEAeDkAdAEAcAEAbAEAaAEAZAEAXAEAWAkAVAkAUAkATAEASAkARDkAQAEAPAEAOAEANAEAMAEALAEAKBEAJBkAIDEAHMkAIEEAJAEAKAEALAkAMAkANAEAOBkAP + AEAQDEARAEASAEATAkAUBEAVAkAWAEAXAkAYDEAZAEAaAEAbAEAcAEAdAkAeBEAgAEAhBkAiDEAjAEAkAEAlAEAmAEAnAEAoAkApAkAqAEArBEAsAkAtDEAuAEAvAEAw + AEAyAEAzAkA0AEA1AEA2BEA3AkA4AEA5DkA6AEA7AEA9AEA+AEBAAEBBAEBCBkBDAEBEEEBFAEBGAEBIAEBJAEBKAEBLAEBMBEBNAEBOAkBPAkBQAkBRDEBSAEBTAEBU + AEBVAEBWAEBXAEBYBEBZAEBaBEBbAkBcAkBeCkBfAEBgAEBhAEBiAEBjAkBkAEBlBEBmAEBnAkBoAEBpDkBqAEBrAEBsAEBtAEBuAEBvBkBwAEBxAEByBEBzAEB0AkB1 + CkB2AEB3AkB4AEB5AkB6BkB7AkB8CkB9AEB+AEB/ghKAP0B6PECBIpA8EBY/FoJggD9AGjxAgw5NQGRBQAxIQEpGQBKQPxkMPBOCLksuKkgdDEESgVCAQUAASEBIS0AS + P0AakEkyVIA8QIFosEB+BkB9AEB7DEB6AEB5AEB4AEB3AEB2AEB0BEBzAEByBEBxAkBwAEBvAkBuCkBtAEBsAEBrAEBpAEBoAkBnBEBmAEBlAkBkAkBjDEBiAEBhAEBg + AEBeAEBdAEBcBkBbAEBaAEBZBEBYAkBXAEBWDEBVAEBUAEBTAEBSAkBRBIBJQAKwQFACQE8MQE4AQE0AQEwAQEtkkD0fDLBASgCQRCkssEBTBEBUFEBVAEBWAEBXAEBY + AkBZBEBaDkBbAEBcAEBdAkBeCEBfDEBgAEBhAEBiAEBjBEBkAkBlBkBmCkBnAEBoAEBpBEBqAEBrAoA9QASwQGwCQG0AQG4MQG8AQHAAQHEAQHIIQHMOQHQAQHUAQHYE + QHcAQHgSQHkAQHoAQHsCQHxWQH+CRJBBHQiAREB0QUCDBLBAfgBAfQxAewBAegBAeABAdwJAdQBAcwBAcgJAcARAbwJAbQBAawxAaQBAaABAZgBAZABAYgBAYQJAXwRA + XQJAWwBAWQJAVwxAVgBAVABAUgBAUABATgBATABASgRASQJARwJARQRAQwxAQQBAPwBAPQBAPABAOgBAOABANgBANAJAMwZAMQBALwBALQ5ALABAKgBAKABAJgBAJQBA + IwBAIgBAIARAHgJAHQJAGw5AGgBAGABAFwBAFgBAFQBAFABAEgRAEQBAEAJADwBADgRADQCQKhYOsEAMAJA5IACwQAsAQAoAQAkAQAgAkEcpALBABwJABgZABQ5ABACQ + NBgEOxUSQB+BGrBABQJABgRABwBACARACQBACg5ACwBADABADQBADgBADwJAEARAEQJAEgJAEwBAFApAFQBAFgBAFwBAGABAGQBAGgJAGwRAHAJAHQBAHgRAIAxAIQBA + IgBAIwBAJABAJQBAJgJAJwJAKABAKQBAKwRALAJALQxALwBAMABAMQBAMgBAMwBANAJANQZANgBANwBAOQ5AOgBAOwBAPABAPQBAPgJAPwRAQABAQQJAQg5AQwBARABA + RQBARgBARwBASARASQBASgJASwRATABATQ5ATgBATwBAUABAUQBAUgJAUwRAVAJAVQJAVgpAVwBAWABAWQJAWgRAWwJAXA5AXQBAXgBAXwBAYABAYQRAYgZAYwJAZABA + ZQpAZgJAZwJAaAZAaQBAagxAawBAbABAbUxAd1BAfhyAOUA+sEB/PoA0QIEKkDkkGDQahCyANEBYQEAYsEB+AEB8AEB7AEB5AEB4AEB2AEB1BkBzAEByAEBwAkBvAkBt + AkBsCkBqAEBoAEBnAEBlAEBkAkBiAkBgAEBfBkBdAkBbAEBaCkBYAEBWAEBVAEBTAkBSAEBQAkBOAEBNBEBLAEBJAEBIEEBGAEBEAEBDAEBBAEBAAEA+AEA9BEA7AkA5 + AEA4AkA2AkA1DEAzAEAyAEAwAEAvAEAuAEAsAkArAEApAkAoBkAmAEAlDEAkAEAiAEAhAEAgAEAeAEAdAkAcAEAbAkAaAkAZAkAXAEAWDkAVAEAUAEATAEASAEARAEAQ + AEAPBEAOAkANAkAMDkALAEAKAJAzIgCwQAkAQAgAQAcEQAYGQAUOkD8hXrBABhBABwBACABACQBACgBACwRADAJADQZADgpADwBAEABAEQBAEgBAEwJAFAJAFQBAFgZA + FwBAGAxAGQBAGgBAGwBAHABAHQJAHgJAIAJAIQJAIgJAIw5AJABAJQBAJgBAKABAKQBAKgRAKwBALAJALQBALgRALwBAMAxAMQBAMgBAMwBANABANQBANgJANwJAOARA + OQJAOgBAOwBAPAxAPQBAPgBAPwBAQABAQQJAQgBAQwRARABARQJARhBARwBASABASQBASgBASwBATAJATQJATgJATwJAUAJAUQpAUgBAUwBAVARAVQBAVgJAVwRAWABA + WQxAWgBAWwBAXABAXQJAXgZAXwJAYABAYQxAYgBAYwBAZABAZQZAZgZAZwpAaABAaQBAagZAa1xAdFZAelRAf4MMgDlAgQYzQEqQORsuMxeBFIA/QGA7QIM2R0CEEJBJ + LIE6gDlAgUgzQAYqQIJsSUBukEcpJLBAfgZAfQBAewBAegJAeQRAeAJAdwBAdQpAdABAcwBAcQBAcAJAbgBAbQJAawJAagJAaAJAZwBAZQxAZABAYgBAYQBAXwBAXQJA + XABAWgRAWQJAVwBAVQRAVApAUgBAUABATwBATQBASwBASgRASABARgJARAJAQwRAQQBAPwxAPQBAOwBAOgBAOABANgJANABAMwBAMQRALwJALQJAKwBAKgxAKABAJgBA + JABAIwCQRi0AsEAhAkAfBEAeAkAcAEAbBEAaAEAYDEAXAJApKgCwQBYAQBQAQBMAQBICQBAAQA8AQA4GQA0AQAwKgEdABLBACwBACgBACQBACABABwBABgJABQRABIMU + QAUAQAYAQAcKQAgAQAkCQAoCQAsAQAwCQA0CQA4CQA8CQBAKQBIAQBMAQBQAQBUAQBYAQBcCQBkGQBoAQBsCQBwEQB0KQB4AQCAAQCEAQCIAQCMAQCQCQCYCQCcAQCgC + QCkEQCoCQCwKQC0AQC4AQC8AQDAAQDICQDMCQDQAQDUAQDYGQDcAQDgAQDkMQDoAQDsAQDwAQD0AQD4EQD8AQEACQEECQEIOQEMAQEQAQEUAQEYAQEcAQEgGQEkAQEoA + QEsEQEwEQE0KQE4AQE8AQFAAQFECQFIGQFMCQFQMQFUAQFYAQFcCQFgEQFkAQFoOQFsAQFwAQF0IQF4EQF82QGhQQHBMQHhQQH8okDgugzKAKUAkkD8mDjodgQ6AOEAQ + OkB+P0CCQJAuGQiwQH4MgEZAAJA+HwCwQHwAQHsAQHoCQHgAQHcEQHUCQHQCQHIAQHAOQG8AQG0AkDgZALBAbABAagBAaQCQPBUGsEBnAEBlAEBkAkBiAkBgDEBfAEBd + AEBbAEBaAEBYAEBWAkBVAEBTAEBRAkBQBkBOAEBMAkBLCkBJAEBHAEBFAEBEAEBCAkBAAkA/AEA9BEA7AEA5AEA4DkA2AEA0AEAyAEAxAEAvAEAtAEAsAkAqBEAoAkAn + AEAlAkAkAkAiAEAgDEAfAEAdAEAcAEAbAEAZAkAYAEAXAkAVBEAUAEATAkASDEAQAEAPAEAOAEANAEAMAEALAEAKAkAJAkAIBEAHDkAGAEAFAEAEgUZABQJABgJABwRA + CABACRBACgBACwBADABADQBADgBADwBAEARAEQBAEgJAEwJAFQRAFgBAFwhAGABAGQBAGwBAHABAHQRAHgJAHwBAIQZAIgJAIwBAJABAJQpAJgBAKABAKQBAKgJAKwJA + LABALgJALwJAMAJAMQxAMgBANABANQBANgBANwBAOABAOgZAOwBAPAJAPQJAPgJAPwxAQABAQQBAQgBAQwBARQBARgBARwJASAJASQRASwBATAJATQxATgBATwBAUABA + UgBAUwBAVAJAVQRAVgBAVwBAWAJAWQ5AWgBAWwBAXABAXQBAXgBAYABAYQRAYgJAYwRAZABAZQJAZghAZwBAaABAaQBAagRAawBAbAJAbQRAbgJAbwJAcAhAcQBAcgBA + cwJAdAJAdQRAdgJAdwxAeABAeQBAegBAewhAfARAfVpAf4IYgDhAgmyQOBKBGIA4QGYuQIZEsEB+AEB9BEB7AkB6CEB5AEB3AEB2AEB0AEBzBEBxAkBvAEBuAkBsBEBr + AkBpCkBoAEBmAEBkAEBjAEBhAkBgAEBeAkBcBEBaAkBZAkBXAEBVCkBUAEBSAEBQAEBOAkBNAEBLAEBJBEBIAkBGAEBEDkBCAEBBAEA/AEA9AEA7AEA5AEA4BkA2AkA0 + AEAyAEAwBEAvAEAtAkArCkAqAEAoAEAmAEAlAkAjAEAiAkAgBEAfAkAdAEAcDEAaAEAZAEAYAEAWAEAVAEAUAkATAkASAkAQAkAPAEAODkANAEAMAEALAEAKAEAJBEAI + AkAHBEAGAEAFFpAnJig2GySAPEAysEAGBIA+QAawQAcAQAgKQAkAQAoAQAsCQAwCQA0EQA4AQA8AQBACQBEMQBIAQBMAQBQAQBYAQBcGQBgAQBkCQBoAQBsGQBwKQB0A + QB4AQCAAQCEAQCIAQCMEQCUAQCYAQCcEQCgCQCkAQCsAQCwMQC0AQC4AQC8AQDAAQDIAQDMCQDQIQDUAQDYAQDgMQDkAQDoAQDsAQDwAQD4AQD8CQEAEQEEAQEIAQEQC + QEUKgCdABLBARgBARwBASABASgBASwBATAKANkACsEBOAkBPAkBQAkBSAEBTAkBUCkBVAEBXAEBYAEBZAEBaAkBcAkBdBEBeAEBgAkBhAkBiCkBkAEBlAEBmAEBnAEBo + AEBqAkBrAkBsAkBtAkBuAEBvDkBxAEByAEBzAEB0AEB1AEB2AEB3BEB4AkB5AEB6AEB7BEB8AEB9AkB+CkB/hTZAfgZAfQJAewBAegpAeQBAeABAdgBAdQJAdABAcgJA + cQRAbwKQOkQCsEBuAEBtDEBrAEBqAJA2PgA9QACwQGkCQGcAkD83ArBAZgKQMzQAsEBkApBCOQawQGMKQGIAQGAAQF8AQF4AQF0AQFsCQFoCQFkAQFgAQFcGQFUAQFQA + QFMMQFIAQFEAQFAAQE8AQE0CQEwAQEsCQEoCQEkCQEgAQEcOQEYAQEUAQEQAQEMAQEIAQEEAQEAAQD8GQD4AQD0GQDwMQDsAQDpAQDsEQDwIQD0CQD4KQD8AQEAAQEEI + QEIAQEMOQEQAQEUAQEYGQEcAQEgAQEkEQEoMQEsAQEwAQE0CQE4CQE8GQFAAQFEMQFIAQFMAQFQCQFUAQFYCQFcEQFgAQFkAQFoOQFsAQFwAQF0AQF4AQF8AQGAGQGEC + QGIAQGMEQGQAQGUKQGYAQGcAQGgAQGkAQGoEQGsAQGwCQG0AQG4EQG8CQHAKgDpAALBAcQBAcgBAcwBAdAJAdQBAdgZAdwJAeABAeQBAegxAewBAfABAfQJAfgKAP0AA + sEB/EIA9QBYzQBZCQIFKNkCBCLBAfgBAfQBAewJAegBAeQBAeAJAdwZAdQBAdAJAcwpAcQBAcABAbgBAbQJAbABAagBAaQRAZwBAZgJAZABAYw5AYQBAYABAXwBAXQBA + XABAWgBAWQZAVwJAVgBAVQRAUwBAUgBAUQxATwBATgBATQBATABASwJASQBASABARwZARgBARQJARAxAQgBAQQBAQABAPwBAPgBAPQJAPAJAOwJAOgJAOQ5AOACQOjsA + sEA3AEA2AEA1AEA0AEAzBJBENgKwQDIEQDEAQDAOkDg3AD0wALBALwCQNS4sQSIWsEAwAEAxAkAyBkAzDkA0AEA1AEA2AEA3CEA4AEA5AEA6EEA7AEA8AEA9AEA+AkA/ + AEBAAkBBEkBCAEBDAEBEAEBFAEBGAkBHAEBIBkBJAEBKAEBLDkBMAEBNAEBOAEBPAEBQBkBRAkBSAEBTDkBUAEBVAEBWAEBXAEBYAEBZAkBaAkBbAEBcEkBdAEBeAEBf + AEBgAEBhAEBiBEBjAEBkBEBlAEBmAkBnAEBoDEBpAEBqAEBrAEBsAEBtCEBuAEBvDkBwAEBxAEByAEBzAEB0BkB1AEB2EEB3AEB4AEB5BEB6AEB7PoBEQAqwQH9MgDhA + QjpAQjVAgWJBQIF2kEY8GIA9QACQNiUEPRUaOjYQsEB+AEB9BEB8ApBBHAKwQHsCQHoMQHkAQHgAQHcAQHYAQHUAQHQCQHMCQHIAQHEEQHACQG8AQG4MQGwAQGsAQGoA + QGkAkEQMArBAaABAZgBAZQRAZAJAYwJAYgBAYQxAXwBAXgBAXQBAXABAWgBAWQBAWAZAVgBAVQJAVAJAUwxAUQBAUABATwBATQBATABASgBASQRASABARgJARQRAQwJA + QgBAQAxAPwBAPQBAPABAOgBAOQBANwJANgBANAZAMwBAMQBAMA5ALgBALABAKwBAKQBAKABAJgBAJQRAIwJAIgJAIABAHw5AHQBAHABAGgBAGQBAGABAFgBAFQJAFABA + EwRAEgJAEAJADwJADgpADQBADABACwBACgJACQRACAJABwBABg5ABQBABIE8QAUMQAYAQAcAQAgCQAkAQAoEQAsAQAwCQA0CQA8MQBAAQBEAQBIAQBMAQBUAQBYAQBcG + QBkAQBoCQBsEQB0AQB4MQCAAQCEAQCMAQCQAQCYAQCcCQCkAQCoAQCwGQC0CQC8AQDEMQDIAQDQAQDYAQDcAQDkAQDoCQDwEQD4AQD8CQEEAQEMOQEQAQEYAQEgAQEkA + QEsAQE0AQE8EQFEAQFICQFQEQFYMQFgAQFkAQFsAQF0AQF8AQGACQGIAQGQCQGUAQGcGQGkCQGoKQGwAQG0AQG8AQHAAQHIAQHMCQHUCQHYCQHcAQHkCQHoCQHsIgERA + BLBAfABAfgBAf1qAQUAoPUAuRkCBOrBAfgBAfRBAfABAewBAegBAeQRAeAJAdwRAdgBAdQJAdApAcwBAcgJAcQJAcAZAbwJAbgpAbQBAbABAawZAagJAaRqQSS0CPSZo + sEByAkBzCoA6QASwQHQAQHUAQHYGQHcCQHgEQHkMQHoAQHsAQHwIQH0MQH4AQH8qgDZAgV49QIEmSUCCdLBAfgBAfABAewBAeQJAeABAdgRAdQJAcwBAcgRAcAJAbwpA + bQBAawBAagBAaABAZgJAZAJAYwBAYQRAXwBAXgJAXAJAWgpAWABAVwBAVQBAUwBAUQBATwJATgZATABASgBASARARgxARQCQSScAsEBDAEBBAEA/AEA+AEA8ApA9FwKw + QDoCQDgEkDkiArBANgBANQxAMwBAMQBAMABALgCQQh0AsEAsAkAqAEApBJA/GwKwQCcAQCUAQCQOkCoUALBAIgBAIQBAHwBAHgCQRRoAsEAcBEAbAEAZAkAYAkAXDkAV + AEAUAEATAEASAEAQAEAPAEAOBEANAEAMAEALAkAKAkAJAkAIDEAHAEAGAEAFgiBABgBABwBACAJACQBACghACwBADAJADQBADgxADwBAEABAEQBAEwBAFAJAFQBAFgRA + FwBAGQBAGhBAGwBAHABAHQBAHwBAIABAIQBAIgRAIwBAJQJAJgJAJwJAKABAKQxAKwBALABALQBALgBALwBAMAJAMgJAMwRANAJANQBANgxANwBAOQBAOgBAOwBAPABA + PQJAPwJAQAJAQQBAQgRAQw5ARABARQBARgBARwBASABASQBASgJASwJATABATQRATgBATwJAUApAUQBAUgBAUwJAVABAVQBAVghAVwBAWAJAWQxAWgBAWwBAXABAXQRA + XgJAXw5AYABAYQBAYgBAY4E0QG1YQHOBMkB6gR5Af4EwgEVACD1AIElAJEJALD9Ah345QCaQSDoIPC9OgCpAWjxATEhAhUawQH4GQHwCQHsAQHkMQHgAQHYAQHUAQHMA + QHECQHAAQG4EQGwCQGoAQGgCQGcCQGUKQGMAQGEAQGAAQF4AQFwCQFoCQFgCQFYAQFUGQFMAQFEKQE8AQE0AQEwAQEoAQEgEQEYAQEQAQEICQEAEQD8CQD0AQDsMQDkA + QDcAQDYAQDQAQDIAQDAAQC8GQC0AQCsCQCkAQCgOkCAyALBAJgBAJABAIwBAIQBAHwBAHgRAHABAGwJAGgJAGQRAFwBAFgxAFQBAFABAEwBAEgBAEABADwKQPysAsEAO + AEANBEAMAkALAEAKDkAJAJBLKQCwQAgAQAcAkCwoALBABgCQRCMEsEAFFJBIHUCAIEAULEAasEAGAEAHBkAIAEAJEEAKAEALAEAMAEANAEAOAEAPAEAQBEARAEASAEAT + BkAUDEAWAEAXAEAYAEAZAEAaAEAbAkAcAEAdAkAeBkAgAEAhAkAiDEAjAEAlAEAmAEAnAEAoAEApAkArAEAsBEAtAEAuAEAvEEAwAEAyAEAzAEA0AEA1AEA3AEA4BEA5 + AEA7AkA8AEA9BEA/AEBADEBBAEBCAEBEAEBFAEBGAkBHAEBIAkBKBEBLAkBMDEBOAEBPAEBRAEBSAEBTAEBVAEBWAkBXAEBZBEBaAEBcAkBdDkBeAEBgAEBhAEBiAEBk + AEBlAEBmBEBoAkBpAEBrBEBsAEBtCkBuAEBvAEBxAEByAEBzBEB0AEB1AEB3AkB4BEB5AkB6AEB7CkB8AEB9AEB+AkB/gjKAP0CCVJA/LRY4MCw1FYF8gD9AinSQPSqC + bIBEQINukEQxDj8qhCiAP0CBCj1Agj6QPRZUgERAgmQ9QIEEkEQXKoBIQDA1QBQ4QDSQQyICgERAgXRDQCqQRDGBRLBAfgBAfAJAewJAeQJAdwJAdgBAdARAcgJAcQBA + bwaAS0AEsEBtAEBrAEBpAkBnAEBlAkBjAEBhBEBfAkBdAEBbAkBZDEBXAEBVAEBTAEBRAEBPAEBMAEBKBkBIAEBGAkBEAkBCDEA/AEA9AEA7AEA5AEA3AEA0AEAyAkAw + AkAuAEAsAoBEQASwQCoMQCgAQCYAQCUAkDlIALBAIwBAIQJAHwJAHgBAHARAGgBAGQBAFwJAFg5AFQBAEwBAEgBAEQCQRTwAsEAQAEAPBEAOAEANAkAMAEALBEAKDEAJ + AEAIAEAHAEAGAEAFAkAEgWRABQJABgxABwBACABACQBACgJACwBADABADQRADgBADwJAEAJAEQxAEwBAFABAFQBAFgBAFwBAGQBAGgZAGwBAHQJAHgJAHwJAIQxAIgBA + JABAJQBAJgBAKABAKQJAKwBALABALQZALwJAMAxAMgBAMwBANABANgBANwBAOQJAOgBAOwRAPQBAPgBAQAJAQQ5AQgBARABARQBARwBASABASgBASwRATABATgJATwRA + UQxAUgBAVABAVQBAVwBAWABAWQBAWwJAXAJAXQBAXwZAYAJAYghAYwBAZABAZgBAZwBAaAJAaQJAagBAbARAbQJAbgBAbwJAcApAcQBAcgBAcwBAdABAdQJAdgBAdwZA + eABAeQBAegRAewpAfABAfQBAfgBAf4EgkDxGgRaAOUB0RUCBdJBCQQI/NwZLNRBFLQpIK4MYgDxAZpA8R3iAS0AASEAiRUASQkA8P0AIPECCYLBAfgBAfABAewKQLkcA + sEB5BEB3AkB2AkB0AkByAkBwCkBuAEBsAEBrAEBpAEBnAEBlAkBjAkBhAEBfBEBdAkBbDEBZAEBXAEBVAEBTAEBRAEBPAkBNAEBLAkBJBEBHAEBFAkBDAEBBDkA/AEA9 + AEA6AEA4AEA2AEA0AEAyBEAwAEAuAkAsEEAqAEAoAEAmAEAlAEAjAEAhAEAfBEAeAEAcAkAbBEAZAEAYDEAWAEAVAEAUAEATAEARAEAQAEAPAkAOAEANBkAMAkALDEAK + AEAJAEAIAEAHAEAGBEAFAkAEgUJABQJABgBABwJACAJACQJACg5ACwBADABADQBADwBAEABAEQBAEgRAEwBAFQRAFgBAFw5AGABAGgBAGwBAHABAHQBAHwBAIARAIQJA + IwBAJARAJgBAJwBAKA5AKgBAKwBALQBALgBAMABAMQBAMwJANAZANgBANwBAOQ5AOgBAOwBAPQBAPgBAQABAQQBAQwRARAJARgBARwJASQ5ASgBATABATQBATwBAUABA + UgBAUwJAVQJAVgJAWAJAWgBAWwJAXQxAXgBAXwBAYQBAYgBAYwJAZQBAZgJAaARAaQJAagBAawpAbQBAbgBAbwBAcAJAcQJAcwBAdARAdQBAdgJAdwBAeA5AeQBAegBA + ewBAfABAfQBAfgBAf4F0kD1TGElABDhYEkE6AEQxBEYrgRSAPUBCRkAEQUAuREA+SUCBeDhAgQqQRj4CPy8CPT0QSzsWOBuBWoA/QDpGQGI9QE5LQIFukE1PFkE3SEYV + EIA4QCouQHBGQEJBQFpNQIIysEB+CkB8AEB7AEB5AEB4AkB2AkB1AEBzAEBxBEBwAkBuAkBsCkBrAEBpAEBnAEBlAEBkAkBiAEBgBkBeAEBdAkBbAEBZDEBXAEBVAEBT + AEBSAEBQAEBOAkBMBEBLAEBJAEBHBkBFDkBDAEBBAEA/AEA+AEA8AEA6AEA4AEA2AkA1AEAzBEAxAkAvAEAtDEAsAEAqAEApAEAnAEAlAEAkAkAiBkAhAEAgAEAeAkAd + DkAbAEAaAEAZAJBNOgCwQBcAkBtFALBAFgJAFQBAFAJAEwZAEgBAEAxADwBADgBADQBADABACwBACgBACQJACAZABwBABjxABwBACAhACQBACgxACwBADABADQJADgBA + DwZAEABAEQ5AEgBAEwBAFABAFQBAFgBAFwRAGAJAGQJAGgBAGw5AHQBAHgBAHwBAIABAIQBAIgBAIwJAJAJAJgJAJwRAKABAKQJAKgiAG0AEsEAsAEAtAEAuAEAvAEAw + AEAyBEAzAEA0BEA1AEA3AEA4DkA5AEA7AEA8AEA+AEA/AEBAAEBCBkBDAEBFAkBGAEBIDkBJAEBLAEBMAEBOAEBPAEBRAEBSAkBUAEBWAkBXBkBZAEBaAEBcDEBeAEBf + AEBhAEBiAEBkAkBmAEBnAkBpAkBqAkBsAEBtDkBvAEBwAEByAEBzAEB1AEB2AkB4BEB5AEB6AkB7BEB9CkB+AEB/QoBNQIRGsEB9CEB8AkB6CEB5AEB3AEB2AEB0AEBz + BEBxAEBvAEBuBEBsAkBrAEBpAkBnDEBmAEBkAEBiAEBhAEBfAEBeAEBcBkBaAkBZAEBXDkBWAEBUAEBTAEBRAEBPAEBOAEBMBEBLAkBJAkBIAkBGAEBFAkBDDEBCAEBB + AEA/AJBFNQCwQD4CQD0AQDsCQDoEQDkAQDcCkE1CArBANgxANQCQSD0AsEA0AEAzAEAyAEAwAEAvAkAuAkAtAkAsAEArEJA3JQA9MACwQCoAQCkAQCgAkEErALBAJwJA + JgJAJQBAJABAIwRAIgBAIQJAIAxAHwBAHgBAHTRAHg5AHwBAIABAIQpAIgBAIwxAJABAJQBAJgBAJwBAKAZAKQBAKgBAKxBALABALQBALgBALwBAMABAMQRAMgJAMwJA + NAJANQJANgBANwxAOABAOQBAOgBAOwBAPABAPQJAPgRAQAJAQQJAQgxAQwBARABARQBARgBARwBASAJASQJASgBASwJATBBATQBATgBATwBAUABAUQBAUgBAUwRAVABA + VQBAVgJAVwJAWA5AWQBAWgBAWwBAXABAXgBAXwBAYAJAYQBAYgZAYwJAZABAZQBAZgpAZwBAaABAaQBAagBAawJAbAJAbQRAbgJAbwJAcAxAcQBAcgBAcwBAdABAdQBA + dgRAdwJAeABAeQZAegpAewBAfABAfQRAfgBAf4U2gEVAQLBAfgBAfAhAewBAeQhAdwBAdgBAdABAcwBAcQRAbwJAbQBAbARAagBAaAJAZwBAZQpAYwBAYQBAYABAXgJA + XAJAWgBAWARAVwJAVQJAUwxAUQBATwBATgBATABASgBASABARgRARQJAQwBAQQBAPwZAPgpAPABAOgBAOABANwBANQBAMwRAMQCQRjMAsEAwBEAuAkAsAkArAEApCIBI + QASwQCcAQCYAQCQAQCIAQCEAQB8GQB4AkEMpALBAHAJAGw5AGgBAGABAFwBAFgBAFQBAEwBAEgRAEQBAEARADwBADgRADQBADApACwBACgBACQBACABABwJABgZABQJA + BH5ABQZABgJABwpACABACQBACgJACwJADABADQRADgJADwJAEAxAEQBAEgBAEwBAFABAFgBAFwBAGAZAGQJAGgBAGwRAHApAHQBAHgBAIABAIQBAIgBAIwRAJABAJgBA + JwJAKARAKQJAKgpALABALQBALgBALwBAMAJAMgJAMwJANAJANQJANgBANwBAOAxAOQBAOgBAOwBAPQBAPgBAPwJAQARAQQJAQgJAQwJARAxARQBARgBARwBASABASQBA + SgRASwJATABATQBATgRATwBAUAxAUQBAUgBAUwBAVABAVQJAVgBAVwBAWAhAWQBAWgBAWwxAXABAXQBAXgBAXwJAYABAYQRAYgBAYwJAZAxAZQBAZgBAZwBAaABAaQBA + agZAawJAbARAbQJAbghAbwBAcABAcQRAcgJAcwZAdABAdQpAdgRAdwRAeABAeQyAQ0BGsEB/KoBGQEiQSDIURSiDUoBFQHCwQH4CQHwEQHsCQHkCQHgAQHYEQHUAQHMA + QHIKgE1AALBAcABAbwBAbQBAbAJAagJAaARAZwJAZQJAZABAYgiASEAEsEBgAEBfAEBdAEBbAkBaAEBYBkBWAEBVAkBTAEBSDEBQAEBPAEBNAEBLAEBKAEBIAEBHBEBF + AEBEAkBCAkBADkA/AEA9AEA8AEA6AEA5AEA3AEA2BEA0AEAzAkAxAEAwBEAvAEAtAEAsDkAqAEApAEAoAEAmAEAlAJBLGgCwQCQAQCIGQCECQCAAkEYbDrBAHgBAHQBA + HABAGwBAGgBAGQBAFwJAFgJAFQBAFAJAEwRAEgxAEQBAEABADwBADgBADQBADAJACwBACgBACQZACA5ABwBABgaQQw1SsEAHBEAIAkAJBkAKCkALAEAMAEANAEAOBEAP + AkAQAEARAEASBEATAkAUCkAVAEAWAEAXAEAYAkAZAEAaAkAbBEAcAkAdAEAeDkAgAEAhAEAiAEAjAEAkAEAmAEAnBkAoAEApAEAqBEAsDEAtAEAuAEAvAEAwAEAyAEAz + AEA0AkA1AkA2AkA3BEA5AkA6CkA7AEA8AEA9AEA+AEBAAkBBAEBCAkBDAEBEBEBFAEBHAkBIDEBJAEBKAEBLAEBNAEBOAEBPAEBQBkBRAkBTAEBUAkBVAkBWDEBXAEBY + AEBZAEBaAEBbAEBdAkBeAkBfAEBgBEBhAkBiCkBkAEBlAEBmAEBnAEBoAkBpAEBrAkBsAkBtBEBuAEBvAEBwDEBxAEByAEBzAEB0AEB1AkB2AEB3BkB4AkB5AEB6AkB7 + CkB8AEB9AEB+AkB/gQaAQUBSN0AMPUBwkEkmDoBLQIFEQ0BeRkAwSUCCKrBAfgBAfQpAewBAegBAeQRAdwJAdQBAdABAcgZAcQBAbwBAbgpAbABAagBAaQJAZwJAZgBA + ZAJAYgJAYQJAXwBAXQ5AXABAWgBAWQBAVwBAVQBAVABAUgZAUABATwJATQBATARASgBASQBARwpARgBARABAQwBAQQJAQAJAPgJAPQRAPABAOgJAOQxAOABANgBANQBA + NABAMwBAMgJAMABALwZALgJALQCQJyMMsEAsAJBJHQCwQCsAkDYmAEYfALBAKQJAKABAJwJAJgJAJQBAJAaQQRcMTRsAsEAjAEAiAEAhAEAgAEAfAEAeAkAdAEAcAkAb + VEAcBkAdEEAeAEAfAEAgAEAhBEAiAkAjBkAkCkAlAEAmAEAnAEAoBEApAEAqAkArAkAsAkAtAkAuCkAvAEAwAEAxAEAyAkAzAEA0AEA1BkA2AEA3AkA4DkA5AEA6AEA7 + AEA8AEA9AEA+AEA/BEBAAEBBAkBCEEBDAEBEAEBFAEBGAEBHAEBIAEBJAkBKAkBLAkBMAEBNAkBOAEBPDkBQAEBRAEBSAEBTAEBUAEBWAkBXBkBZAEBaAkBbAEBcDEBd + AEBeAEBfAEBgAEBhAEBiAkBjAkBkAkBlAkBmAkBnCkBoAEBpAEBqAEBrAEBsAkBtBEBuAEBvAkBwDkBxAEByAEBzAEB0AEB1AEB2AEB3BEB4AkB5BkB6AEB7AEB8DEB9 + AEB+AEB/LIAnQGw2QIJSkDo0XIBGQIIaTUA6kEY3CoBJQF5BQEhGQINOkD0YFjYZBkskEkYnALBAfgKQPxYAsEB9AJBCIwawQHsCQHoAQHgMQHcAQHUAQHQAQHIAQHAA + QG8CQG0AQGwEQGoAQGgCQGcEQGUKQGMAQGIAQGAAQF4AQF0AQFsAQFoEQFgAQFYCQFUCQFMCQFICQFAKQE4AQE0AQEsAQEoAQEgCQEcAQEUCQEQEQEICQEEAQD8CQD4M + QDwAQDsAQDoAQDgAQDcAQDYCQDQCQDMCQDIAQDAQQC8AQC4AQC0AQCwAQCoAQCkAQCgCQCcCQCYAQCUCQCMCQCICQCEAQCAMQB8AQB4AQB0AQBwAQBsAQBoCQBkGQBgA + QBcCQBYAQBUMQBQAQBMAQBIAQBEAQBAEQA8SQA4AQA0AQAxCQA0EQA4IQA8KQBAAQBEAQBIEQBMGQBQAQBUMQBYAQBcCQBgAQBkAQBoIQBsAQBwOQB0AQB4AQB8AQCAA + QCEEQCICQCMAQCQQQCUAQCYAQCcAQCgAQCkAQCoCQCsAQCwCQC0CQC4EQC8AQDAAQDEMQDIAQDMAQDQAQDUAQDYCQDcAQDgGQDkAQDoAQDsOQDwAQD0AQD4AQD8AQEAA + QEECQEICQEMCQEQSQEUAQEYAQEcAQEgAQEkAQEoCQEsAQEwCQE0EQE4CQE8AQFAMQFEAQFIAQFMAQFQCQFUEQFYCQFcAQFgAQFkOQFoAQFsAQFwAQF0AQF4EQF8AQGAI + QGEIQGIAQGMAQGQAQGUAQGYGQGcAQGgAQGkEQGoCQGsKQGwAQG0AQG4CQG8AQHAIQHEAQHIMQHMAQHQAQHUCQHYGQHcAQHgQQHkAQHoAQHsEQHwGQH0CQH4KQH+CEoA9 + QIEQOkCDBpA6FQY9DoJAgD1AgV5LQBZGQDJCQCo/QIMUOkAqkD0/Akk9AkJKAIA2QBSQRkAssEB+AEB9AEB8AEB7CEB6AEB5AEB4DkB3AEB2AEB1AEB0AEBzAEByBkBx + AEBwAEBvEEBuAEBtAEBsAEBrAEBqAkBpAkBoAkBnBEBmAEBlDEBkAEBjAEBiAEBhAkBgBkBfAkBeDkBdAEBcAEBbBEBaAkBZBEBYghZAX4IUQGWBVkBpgWCQLD8EsEBv + PkB2REB9WEB/gQiASUAELEACRkAAPUAMQkCBEpBITIMwgEhAgRKwQH4CQHwAQHsEQHkIQHcAQHYAQHQAQHMAQHEEQG8CQG0AQGwCQGoEQGgCQGYIQGQAQGMAQGEAQF8A + QF0EQFsCQFkAQFgCQFYAQFQCQFICQFAMQE8AQE0AQEsAQEkAQEcAQEUAQEMGQEECQEAAQD4EQDwMQDoAQDgAQDYAQDQAQDMAQDECQC8CQC0AQCwAkEg3BrBAKgxAKABA + JgBAJQBAIwCQPDMAsEAhAEAgAkAeAJA1TwKwQB0CQBsCQBoAQBkQQBcAkEEyALBAFgBAFQBAFABAEwBAEgCQRS8EsEAQAEAPAkAOEEANAEAMAJArQACwQAsAQAoAQAkA + QAgAQAcEQAYAQAUAQASBMkAFAkAGDkAHAEAIAEAJAEAKAEALAEAMBkANAkAOBEAPCkAQAEARAEASAEATAEAUAEAVBEAWAEAXAEAYAkAZBEAaAEAbAkAcDEAdAEAeAEAg + AEAhAEAiAkAjAEAkBEAlAEAmAkAnAEAoDkApAEAqAEAsAEAtAEAuAEAvAEAwBEAxBEAyAEAzEEA0AEA1AEA2AEA3AEA4AEA5AEA6AkA7AEA8AkA9AEA+BEA/AEBADEBB + AEBCAEBDAEBEAkBFAEBGCEBHAEBIAEBJDEBKAEBLAEBMAEBNAEBOAkBPBEBQAEBREEBSAEBTAEBUAEBVAEBWBEBXAkBYBkBZAEBaCkBbAEBcAkBdAkBeBEBfBEBgCkBh + AEBiAkBjAEBkBEBlAkBmDkBnAEBoAEBpBkBqBEBrAkBsCkBtAkBuAkBvBkBwREB5TkB/TpA6P4IcgDVAQkFADkVASjxAUEhAIjpAELBAfQBAfAJAegJAeQBAdwRAdQJA + cwJAcgxAcABAbgBAbABAawBAaQBAZwBAZQRAYwBAYQJAXwJAXQRAWwpAWQBAVwBAVQBAUwBAUQBATwRATQBASwBASQRARwBARQJARA5AQQBAQABAPgBAPABAOgBAOABA + NgJANARAMgJAMABALg5ALABAKgBAKABAJwBAJQBAIwBAIQJAIAJAHgBAHQZAGwxAGgCQNDAAsEAYAEAXAEAWAEAUAEATAkASAkAQAEAPAkAOAkANApBIMgCwQAwKgCtA + BLBACwCQQDcAsEAKAEAJAJA6NgCwQAgEQAcAQAYCQAUCkEQvDjwiALBABIEkQAUMQAYAQAcAQAgCQAkAQAoCQAsCQAwCQA0AQA8OQBAAQBEAQBIAQBMAQBQAQBUAQBYG + QBcCQBkAQBoCQBsCQBwMQB0AQB4AQCAAQCEAQCIAQCMCQCQAQCYCQCcGQCgAQCkAQCoMQCwAQC0AQC4AQC8AQDACQDEAQDICQDMEQDQAQDUAQDYOQDcAQDgAQDkAQDoA + QDsAQDwAQD0GQD4CQD8AQEACQEEMQEIAQEMAQEQAQEUAQEYEQEcAQEgGQEkAQEoMQEsAQEwAQE0CQE4AQE8CQFAEQFECQFIMQFMAQFQAQFUAQFYIQFcOQFgAQFkAQFoE + QFsAQFxWQGU4QGw2QHQGQHUCQHYEQHcKQHgAQHkEQHoIQHsKQHwCQH0GQH4CQH+CbIA6QASQKhsAOgaCaIA6QBI8QExAQAI0QABEQHhIQAwqQIU4kCksRLBAfgpAfABA + ewBAeQBAeARAdgBAdABAcwZAcQJAcApAbgBAbABAagBAaQBAZwJAZQJAZABAYgBAYARAXgBAXQJAWwxAWQBAVwBAVQBAUwBAUgBAUAJATgRATAKQMB8CsEBKAEBIDkBG + AEBFAEBDAEBBAEA/AEA9AEA7AEA5BEA4AEA2AEA0BkAyAEAwDEAvAEAtAEArAEApAEAoAkAmAEAkAEAiAkAhAkAfAkAeAEAcEEAbAEAaAEAYAEAXAEAWAJBDEgCwQBQE + QBMAQBIAQBECQBAQQA8AkDklALBADgBADQBADABACwBACgBACQJACABABwZABgJABQxABACQSC5ITClUUTU0sEAFDkAGAEAHAEAIAEAJAoBDQACwQAoCQAsEQAwAQA0O + QA4AQA8AQBAAQBEAQBIAQBMAQBQEQBUCQBYCQBcCQBgCQBkKQBoAQBsAQBwAQB0AQB4AQCACQCECQCIAQCMGQCQAQCYAQCcOQCgAQCkAQCoAQCwAQC0AQC4AQC8EQDAA + QDICQDMCQDQOQDUAQDYAQDcAQDgAQDkAQDoAQDsEQDwCQD0AQD4EQD8AQEAMQEEAQEIAQEMAQEQAQEUAQEYCQEcAQEgAQEkGQEoCQEsMQEwAQE0AQE4AQE8AQFAAQFIA + QFMGQFQAQFUCQFYOQFcAQFgAQFkAQFoAQFsAQFwAQF0EQF4AQF8GQGAMQGEAQGIAQGMAQGQAQGUCQGYAQGcCQGgGQGkAQGoMQGsAQGwAQG0AQG4CQG8GQHACQHEAQHIM + QHMAQHQAQHUGQHYCQHcOQHgAQHkGQHoAQHtAQH8agFFADExAAEhAgjY5QINgsEB+AkB9AEB8BEB7CEB6AEB5AEB4AEB3AEB2AkB1BEB0AkBzAEByAEBxBkBwCkBuAEBt + AEBsAEBrAEBqAkBoAkBnAEBmAkBlAkBkAkBjAEBiDEBhAEBgAEBfAEBeAEBdAEBcAkBbBkBaAEBZAkBYiHyAKUCDZjBAlxqwQABmQACBTEAAaEAAZkAAgjJAAIN+QABm + QACBTEAAZkAAZkAAgUxAAGZAAGZAAGZAAGZAAIFMQACBTEAAgUxAAGZAAGZAAIFMQABmQABmQACBTEAAZkAAAP8vAFByc1XeKQAARwAAAAIAAABrMgAAAAAIAAAAR3Jh + bmQgSzIAAAAAAAAAAHByb21UAAAAAQAAAAEAAAABAAAAAgAAAGsyAAAAAAgAAABHcmFuZCBLMgAAgD8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAA + AAAAAAAAAAAAcnlhbBAAAAACAAAAAAAAAAAAAAD/////b2xvcxEAAAABAAAAAQAAAEYAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAA + AAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4AAAGBpAAAAAAAQQQAAgD9p + AAAA0P1hv2jY9B8/qF8VP+BzG7/oe5W/KAqOv1S/kz/ItDA/gJ9Av4wt/7+A5Nq9kP/jv2AFrj6A76U9hFcQwLBqHj90xbM/gF3iPiBAREB+F5pAwLIYPggK6b9gUSY/ + AJ+2PSCxqL5wzn8/0NGfP6B/3j/SljlAAHoGQAIKQUBAvoA+lCjHP/CXgb9ARH++ZCyMP9gePj9w8Rs/VHahP+inPUBKpURAmPh2QNI2cUC2mS1AgMAVQABjJT+wLQE/ + CPo9P/Qemz/gtaG+gLGtPYiZGT8IPkk/wic0QO2BxUD8WjtA6EdKQPD2jD/u1kBAoGd7QODwX0BqJnBAAsgGQDqtXkCs2c0/ABaBvwjHWD/8KM8/QIHBvkBRhr70ldo/ + JN0HwGBOoT4sxAFA+BDhPyJuGMCqfpHA+L9FwFR3gT9ccz1AACNnPiXztEB0+HJALJq1vwC+eL0AhWq/hGAOwFC+uL/KLS5AAMHzvjJ/WsBoiBdA0A7gP/wgQMCVXpDA + 3IKgwCDOCsBA+PU+fTqKwMD2NMDa+iXAzNMXwDCmLsAuTKfAeBAoPwMAAAABAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAA + AQAAAAEAAAAAAIA/AAAAAAEAAAAAAAAA3H8iPgOAAABACgAAAAAAqEEAAOhBAAAYQgAAIEIAACRCAAAsQgAAYEIAAJJCAACUQgAAtkIKAAAAYaZJPwcI0k4/CcxpP3j8 + XD9ZQ1Q/Mw5gPx0IgD+PPrM/jz6zP4EBAAAAAAAAAAAAAAAAAKDAAACgQAAAtkIAAAAAA4AAAFAAAHBCAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAKDA + AACgQAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BgAAAAAAAAADSBAAAAAAAAAAAAAAAAACAAAAAPwAAgD8DgAAA + UAAAcEIBAAAAAAAAAAOAAABgaQAAAAAAEEEAAIA/aQAAAAAAAMACAACAvwAAQMAAAEDABAAAQEAAAABAAADAQAAAgD8AAIA/BgAAwEAAAEBAAACAQAAAAMAAAEDAAACA + QAAAgEAIAACgQAAAwEAAAIBAAAAAgAAAgD8AAADAAACAPwAAQMAAAEDAAwAAQEAAAIBAAACgQAAAoEAGAABAwAAAoEAAAACAAAAAQAAAQMAAAKBAAACgQBUAAEBAAACA + QAAAAEAAAADAAACAPwAAgEAAAABAAACgQAAAQEAAAEDAAAAAgAAAgEAAAEDAAAAAgAAAgD8AAIC/AABAwAAAoEAAAEDAAAAAwAAAAIAAAACABQAAoEAAAEDAAAAAwAAA + gL8AAEDAAABAwAMAAKBAAAAAwAAAQMAAAEDADwAAgEAAAIA/AABAwAAAgEAAAAAAAACAQAAAgL8AAABAAACgQAAAQMAAAIC/AAAAwAAAwEAAAKBAAABAwAAAQMADAAAA + wAAAgD8AAEDAAABAwAEAAIC/AACAvwQAAMBAAACAQAAAQMAAAAAAAAAAAAcAAIA/AABAwAAAAMAAAMBAAABAwAAAwEAAAEDAAABAwIEAAABAA4AAAGACAAAAAACoQQAA + rkICAAAAAACAP4ECAAAAAQAAAFUAAABFcXVhbCB0ZW1wZXJhbWVudApUaGUgc3RhbmRhcmQgdHVuaW5nLCB3aGVyZSB0aGUgb2N0YXZlIGlzIGRpdmlkZWQgaW4gMTIg + ZXF1YWwgc3RlcHMuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAEAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAA + AAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOAAABgaQAAAAAAEEEAAIA/aQAAAAAASMIOAAAswgAAEMIAAODBAADIwQAAqMEAAJDBAABgwQAAQMEAACDBAAAQwQAA + AMEAAODAAADAwAAAoMAAAKDAAQAAgMAAAIDAgQAAQMCCAAAAwIMAAIC/hwAAAADGAAAAAAAAAAAAAIA/AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACA + PwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQ + AABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA + 2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAABBCA4AAAEAHAAAAAACoQQAAkEIAAKhC + AAC0QgAAwEIAAMhCAADYQgcAAAAAAAAAgQAAQEAEAACgQAAA4EAAAABBAAAQQQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAA + AAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+B + A4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAzcxMPgAAAAAAAAAAAAAAAAAAAD89CgdAA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAA + AAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAABAAAACwAAAEphenogc3R1ZGlvCgAAAGphenpzdHVkaW8AAAAALDObQQAAgD9mZuY/AAAAAMCntTzw//+/cKRKPgAAAACa + mRlAAACqQwAAgD8AAIA/AQAAAAYAAAAAAIA/AACAPwOAAABgaQAAAAAAEEEAAIA/aQAAAAAAgD+TIlmWP1RxWZA/x0qfPxqYkz8vJok/KIKBP8eokD8mUpg/ZIGTP+Gm + nz+6HZc/euWbP06Khz/7ep4/oMSeP2jykD/ea44/rsmUP8RwlT9kkos/OgmbP0K1hj+WbZE/RDOWP7JQij8Ur6I/b3uhP7j4YT/A+HY/azSRPznmaz/9spY/RPKSP7D7 + kD9Ul48/REqPP+QVkD8dmIc/7J2KP9YDZT/oJ4w/NKaIPzTBaD8uMZE/GJuZP1ZAgD/Vypg/oliKPwg6jz9BDJg/pb2LPz4UlT8ygpM/Xk+ePxqJmD/ebK8/PQWsP6OE + pj/2qZo/DIDAP68Dqj+67o4/u13AP+x/mz+sKp0/7PS3P14Rtz9qMpM/an6SP6Rzpz9EzpA/CuuXPw7osj+ZIJg/K2uePzLkpT9snq0/CqijP3b9kz82/6Q/3jGWP3C1 + sD+Sy4k/gl+dPznbqD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGBdAAAAAACoQQAAgD9dAAAAAAAAAIestZG9VCQAwr1C6cc6VYhTva+Uhrys + cn88vkkbPDekrDxFGGu9aS3IvRqGijlXYIw9iQ/aPP9bQD3KPuM77xpJPaZJRzx/3Ik9xXXavERQmb2b1Za9sdA3PRunvr0oSjm9jokoPatSXTvBHX+9c3IaPRUYDb2G + ZEK9U543vSgUpz2ylsA9SoFMPZd3tr2PrL86CkXlO4/CDryhmoU9EsAKPR3imzuD3nK99nOdPc09BD1mgaU8x9yPurj7mD3qlK09QZeCPP/5i72ROJ29kUqkvaRkGz1c + +J87v6GrurbzrT2UwY290pvhvGi3Kj3jfa48y5pTO5qPkb07Fu+8JJCTPVQnMb3AdeS8QmSVvbf4gr34iGa7mgcnvW6Fs7xsxI68/72nPcs/fz1v1my9FfGKPYsXFTya + u+e8C+vnO2Pnqj04hBu97dRGvRfSHT3yMpg9ccLvPAEAAAAAAAAAAQAAAAAAAD8AAABAAwAAAAAAgD8AAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ + ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD+amZk+zcxMPwAAwD+amZk+CAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAGBpAAAAAAAQQQAAgD9pAAAA8TMKQGhMtt0/p9O+P0zWxD/twcI/C/a2P6sWjD/6IIc/D8OQP4V4mj+m + R30/lHCcP4eteT8kBoM/dayuPyesmD88VWo/e1mDPxcPKT/I4h8/BkeJP9PRrD80s2E/+pZ9P9wUkz9CUH0/EAB3P8RFaj/PSU4/f6JjP7s2Tj+qrY4/sit3Pz8xpz+I + IZo/oOSEPzpNiz9vYo4/qfKEP/UCXD8wwEU/ZSFLPybbXj++6Y0/ljtrP7CRmT81oo8/HPaKP7rkgj88LJs/n8aXP6I5pT8XspY//ERiP3qXGj/KhTk/6dchPygfij+t + b2k/RQJEP7kVTD+g0EU/G8poP5rCVz/eDVs/AUCfP2dmaj94DpA/+VagPwbSnj+O+3s/4cK3P5bpiT+aGVY/HTBjP4NWtj8g5co/mljDP0qqdT+vZU0/0jCbP5WNGT/E + 6B8//wiiP3eXaT/ATYw/YNycP7y9iD/rXSM/TTNrPylvmz/VziI/RGQsPwb7kj+fqqs/EX+yP4JqfT9+7zg/SgucPwiwgT/EDX4/gER5P4lTgj/igKw/vag2PwOAAABA + AwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAABpAAAAAAAQ + QQOAAABgDAEAAAAAAAAAAIA/DAEAAAAAAAD/AAAAAP8AAAAAiwAAIEEDgAAAYAIBAAAAAAAAAACAPwIBAAAAAAAA/wAAAAD/AAAAAIEAADBBA4AAAGD1AAAAAAAAAAAA + gD/1AAAAAAAAAP8AAAAA9AAAQEEDgAAAYO4AAAAAAAAAAACAP+4AAAAAAAAA/wAAAADtAABQQQOAAABg6QAAAAAAAAAAAIA/6QAAAAAAAAD/AAAAAOgAAGBBA4AAAGDZ + AAAAAAAAAAAAgD/ZAAAAAAAAAP8AAAAA2AAAcEEDgAAAYNMAAAAAAAAAAACAP9MAAAAAAAAA/wAAAADSAACAQQOAAABgzgAAAAAAAAAAAIA/zgAAAAAAAAD/AAAAAM0A + AIhBA4AAAGDLAAAAAAAAAAAAgD/LAAAAAAAAAP8AAAAAygAAkEEDgAAAYMkAAAAAAAAAAACAP8kAAAAAAAAA/wAAAADIAACYQQOAAABgyAAAAAAAAAAAAIA/yAAAAAAA + AAD/AAAAAMcAAKBBA4AAAGDDAAAAAAAAAAAAgD/DAAAAAAAAAP8AAAAAwgAAqEEDgAAAYOAAAAAAAAAAAACAP+AAAAAAAAAA/wAAAADfAACwQQOAAABg0gAAAAAAAAAA + AIA/0gAAAAAAAAD/AAAAANEAALhBA4AAAGDOAAAAAAAAAAAAgD/OAAAAAAAAAIEAAEDAAgAAgL8AAAAAAAAAAP8AAAAAyAAAwEEDgAAAYO4AAAAAAAAAAACAP+4AAAAA + AAAA/wAAAADtAADIQQOAAABg2QAAAAAAAAAAAIA/2QAAAAAAAACBAACAwAEAAAAAAAAAAP8AAAAA1AAA0EEDgAAAYMkAAAAAAAAAAACAP8kAAAAAAAAA/wAAAADIAADY + QQOAAABgvAAAAAAAAAAAAIA/vAAAAAAAAAD/AAAAALsAAOBBA4AAAGDTAAAAAAAAAAAAgD/TAAAAAAAAAAMAAIC/AADAwAAAAAAAAAAA/wAAAADOAADoQQOAAABg3QAA + AAAAAAAAAIA/3QAAAAAAAAACAAAAwQAAAAAAAAAA/wAAAADZAADwQQOAAABg0QAAAAAAAAAAAIA/0QAAAAAAAAAEAABAwAAAgD8AAADAAAAAAAAAAAD/AAAAAMsAAPhB + A4AAAGDHAAAAAAAAAAAAgD/HAAAAAAAAAJQAAEDAAQAAAAAAAAAA/wAAAACvAAAAQgOAAABgwgAAAAAAAAAAAIA/wgAAAAAAAAADAAAAwDMzA0AAAAAAAAAAAP8AAAAA + vQAABEIDgAAAYMoAAAAAAAAAAACAP8oAAAAAAAAAgQAAQECCAAAAwAEAAAAAAAAAAP8AAAAAwgAACEIDgAAAYNIAAAAAAAAAAACAP9IAAAAAAAAA/wAAAADRAAAMQgOA + AABgxgAAAAAAAAAAAIA/xgAAAAAAAAD/AAAAAMUAABBCA4AAAGDEAAAAAAAAAAAAgD/EAAAAAAAAAIEAAKDAAwAAAAAAAEDAAAAAAAAAAAD/AAAAAL0AABRCA4AAAGDE + AAAAAAAAAAAAgD/EAAAAAAAAAP8AAAAAwwAAGEIDgAAAYMQAAAAAAAAAAACAP8QAAAAAAADAAQAAAAAAAAAAlwAAQMAEAAAAAAAAAEAAAIBAAAAAAAAAAAD/AAAAAKQA + ABxCA4AAAGCrAAAAAAAAAAAAgD+rAAAAAAAAAIwAAIDAAQAAAAAAAAAA/wAAAACbAAAgQgOAAABgqwAAAAAAAAAAAIA/qwAAAAAAAACLAADAvwEAAAAAAAAAAP8AAAAA + nAAAJEIDgAAAYKsAAAAAAAAAAACAP6sAAAAAAIDAAgAAAEAAAAAAAAAAAIcAAJDAAQAAAAAAAAAAhwAAQMABAAAAAAAAAACCAABAwIEAAAAA/wAAAACOAAAoQgOAAABg + kwAAAAAAAAAAAIA/kwAAAAAAgMACAAAAAGZmxj9mZsY/AQAAAAAAAAAAhQAAwMABAAAAAAAAAACGMzMDwAIAAKDAAAAAAAAAAAD7AAAsQgOAAABgkwAAAAAAAAAAAIA/ + kwAAAAAAQMABAAAAAAAAAACOAABAwAIAAAAAAABAwAAAQMABAAAAAAAAAAD8AAAwQgOAAABgkwAAAAAAAAAAAIA/kwAAAAAAQMABAAAAAAAAAACDAABAwAEAAAAAAAAA + AIIAAIC/AjMzw8AAAAAAAAAAAIMAAEDAAQAAAAAAAAAAgQAAQMABAAAAAAAAAAD6AAA0QgOAAABggAAAAAAAAAAAAIA/gAAAAAAAgL+CAAAAAIIAAJDABgAAAAAAAEDA + AACAwAAAAAAAAEDAAAAAAAAAAACCAABAwIIAAAAA7AAAOEIDgAAAYIAAAAAAAAAAAACAP4AAAAAAAEDAAQAAAAAAAAAAgwAAwL8DAAAAAAAAQMAAAAAAAAAAAJwAABDB + AQAAAAAAAAAA1gAAPEIDgAAAYIAAAAAAAAAAAACAP4AAAAAAAEDAAQAAAAAAAAAAggAAQMABAAAAAAAAAACKAADAwAEAAAAAAAAAAOsAAEBCA4AAAGBsAAAAAAAAAAAA + gD9sAAAAAACAvwIAAIA/AAAAAAAAAACCAAAAwAEAAAAAAAAAAOMAAERCA4AAAGBsAAAAAAAAAAAAgD9sAAAAmpn5vwIAAIA/AAAAAAAAAACBAACAwAIAAADAAAAAAAAA + AADjAABIQgOAAABgbAAAAAAAAAAAAIA/bAAAAAAAAMACAABAQAAAwL8AAMC/AgAAQMAAAAAAAAAAAIYAAIDAAQAAAAAAAAAA3AAATEIDgAAAYGYAAAAAAAAAAACAP2YA + AAAAAAAAgwAAQMADAAAAAAAAQMAAAAAAAAAAAN0AAFBCA4AAAGBXAAAAAAAAAAAAgD9XAAAAAAAAAIMAAEDAAwAAAAAAAIC/AAAAAAAAAACCAACAvwEAAAAAAAAAAIQA + AIC/AgAAAMAAAAAAAAAAAMEAAFRCA4AAAGBXAAAAAAAAAAAAgD9XAAAAAACAvwYAAABAAAAAAAAAAMAAAAAAAAAAwAAAAAAAAAAAkwAAEMEBAAAAAAAAAAC5AABYQgOA + AABgVgAAAAAAAAAAAIA/VgAAAAAAoMANAACAP5qZ+T8AAKDAAAAAAAAAAMAAAIA/AAAAAAAAAEBmZoY/AABAwAAAQEAAACDBAAAAQAAAAEABAAAAAAAAAACDAABAwAEA + AAAAAAAAAL8AAFxCA4AAAGBJAAAAAAAAAAAAgD9JAAAAAABAwAEAAAAAAAAAAIKambm/AQAAAAAAAAAAgQAAQMABAAAAAAAAAAC9AABgQgOAAABgSQAAAAAAAAAAAIA/ + SQAAAAAAwL8EAAAAADMzA0AAAADAAAAAAAAAAACDAACAwAMAAAAAAABAwAAAAAAAAAAAjAAAwMABAAAAAAAAAACsAABkQgOAAABgSAAAAAAAAAAAAIA/SAAAAAAAwL8B + AAAAAAAAAACBAADAwAEAAAAAAAAAAMEAAGhCA4AAAGBEAAAAAAAAAAAAgD9EAAAAAAAAAAhmZoY/9/9/vwAAgL9mZoY/AQAAwP//n8AAAIA/AAAAAAAAAACBAABAwAEA + AAAAAAAAAIv////AAQAAAAAAAAAAqAAAbEIDgAAAYEAAAAAAAAAAAACAP0AAAAAAACDABgAAAEAAAADAAAAAQAAAAAAAAKBAAAAAAAAAAACBAABAwAEAAAAAAAAAALQA + AHBCA4AAAGA5AAAAAAAAAAAAgD85AAAAmpn5PwWamfm/AABAwAAAAAAAAEBAAAAAAAAAAACFAABAQAMAAAAAAACAwAAAAAAAAAAAqAAAdEIDgAAAYDkAAAAAAAAAAACA + PzkAAAAAAAAAgQAAoMABAAAAAAAAAAC0AAB4QgOAAABgNgAAAAAAAAAAAIA/NgAAAAAAgD+BAABAwAEAAAAAAAAAAIEAAEDAAQAAAAAAAAAArQAAfEIDgAAAYDEAAAAA + AAAAAACAPzEAAAAAAAAAsAAAgEIDgAAAYDAAAAAAAAAAAACAPzAAAAAAAAAAgpqZ+T8Bmpn5v5qZ+b8CZmaGPwAAAAAAAAAApwAAgkIDgAAAYC0AAAAAAAAAAACAPy0A + AAAAAMA/AQAAAAAAAAAAgQAAwL8BAACAwAAAgMABAAAAAAAAAACkAACEQgOAAABgKwAAAAAAAAAAAIA/KwAAAAAAAACBAADAwAEAAAAAAAAAAIMAAEDAAQAAAAAAAAAA + oAAAhkIDgAAAYCcAAAAAAAAAAACAPycAAABmZoY/gQAAgL8FAABAQAAAoMAAAEDAAAAAAAAAQMAAAEDAAQAAAAAAAAAABgAAgD+amfm/AACAPwAAAAAAAADAAAAAAAAA + AACVAACIQgOAAABgJgAAAAAAAAAAAIA/JgAAAAAAgD+CAADAwAIAAIDAAAAAAAAAAAACAAAAwAAAAAAAAAAAgQAAQMCBAAAAAJgAAIpCA4AAAGAkAAAAAAAAAAAAgD8k + AAAAAAAAAIQAAEDAAQAAAAAAAAAAnAAAjEIDgAAAYCIAAAAAAAAAAACAPyIAAAAAAIA/gQAAAACBAACAwAIAAMC/AAAAAAAAAACDAADAv4EAAAAAlAAAjkIDgAAAYCAA + AAAAAAAAAACAPyAAAAAAAAAAAQAAwD8AAMA/BAAAQMAAAAAAAABAwAAAAAAAAAAAmAAAkEIDgAAAYB0AAAAAAAAAAACAPx0AAAAAAAAAgQAAAEAGAAAAAAAAQMAAAIC/ + AAAAAAAAAMAAAAAAAAAAAJMAAJJCA4AAAGAcAAAAAAAAAAAAgD8cAAAAAAAAAIQAAEDAAQAAAAAAAAAAlAAAlEIDgAAAYBsAAAAAAAAAAACAPxsAAAAAAIC/AQAAAAAA + AAAAmAAAlkIDgAAAYBkAAAAAAAAAAACAPxkAAAAAAIC/AQAAQEAAAEBAAgAAwMAAAABAAAAAQAMAAEBAZmaGvwAAAAAAAAAAjwAAmEIDgAAAYBgAAAAAAAAAAACAPxgA + AAAAAAAAAwAAQEAAAIC/AAAAAAAAAACCmpm5vwEAAAAAAAAAAI4AAJpCA4AAAGAWAAAAAAAAAAAAgD8WAAAAAAAAAIIAAABAgQAAAMABAAAAAAAAAACOAACcQgOAAABg + FQAAAAAAAAAAAIA/FQAAAAAAAAABAABAQAAAQECEAAAAwAEAAAAAAAAAAIsAAJ5CA4AAAGAUAAAAAAAAAAAAgD8UAAAAAACAvwEAAIA/AACAPwMAAEDAAADAvwAAAAAA + AAAAjQAAoEIDgAAAYBMAAAAAAAAAAACAPxMAAAAAAAAAgZqZ+b8BAAAAAAAAAACOAACiQgOAAABgEgAAAAAAAAAAAIA/EgAAAAAAAACRAACkQgOAAABgEQAAAAAAAAAA + AIA/EQAAAAAAAACQAACmQgOAAABgEAAAAAAAAAAAAIA/EAAAAAAAgEABAAAAQAAAAEABAAAAAAAAAACLAACoQgOAAABgDwAAAAAAAAAAAIA/DwAAAGZmhj8Cmpn5PwAA + AAAAAAAAAgAAQMAAAAAAAAAAAIgAAKpCA4AAAGAOAAAAAAAAAAAAgD8OAAAAAACAvwEAAAAAAAAAAIsAAKxCA4AAAGANAAAAAAAAAAAAgD8NAAAAAAAAAIwAAK5CA4AA + AGAMAAAAAAAAAAAAgD8MAAAAAAAAAAIAAADAAAAAAAAAAACIAACwQgOAAABgDAAAAAAAAAAAAIA/DAAAAAAAQEAEAAAAQAAAQMBmZoa/AAAAAAAAAACGAACyQgOAAABg + CwAAAAAAAAAAAIA/CwAAAAAAAACKAAC0QgOAAABgCgAAAAAAAAAAAIA/CgAAAAAAAEAEAACAvwAAAAAAAMDAAAAAAAAAAACEAAC2QgOAAABgCgAAAAAAAAAAAIA/CgAA + AAgZ4L8EkOqCv2TjtT+Q6oK/AAAAAAAAAACEAAC4QgOAAABgCQAAAAAAAAAAAIA/CQAAAAAAAACBAABAwAEAAAAAAAAAAIQAALpCA4AAAGAJAAAAAAAAAAAAgD8JAAAA + AABAQAMAAAAAAABAwAAAAAAAAAAAhAAAvEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAvkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAwEIDgAAAYAgAAAAA + AAAAAACAPwgAAAAAAAAAhwAAwkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAxEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAxkIDgAAAYAgAAAAAAAAAAACA + PwgAAAAAAAAAhwAAyEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAAgAAQMAAAAAAAAAAAIQAAMpCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMxCA4AAAGAIAAAA + AAAAAAAAgD8IAAAAAAAAAIcAAM5CA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANBCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANJCA4AAAGAIAAAAAAAAAAAA + gD8IAAAAAAAAAIcAANRCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANZCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANhCA4AAAGAEAAAAAAAAAAAAgD8EAAAA + AAAAAIMAANpCA4AAAGAEAAAAAAAAAAAAgD8EAAAAAAAAAIMAANxCA4AAAGAEAAAAAAAAAAAAgD8EAAAAAAAAAIMAAN5CA4AAAGAEAAAAAAAAAAAAgD8EAAAAAAAAAIMA + AOBCA4AAAGAEAAAAAAAAAAAAgD8EAAAAAAAAAIMAAOJCA4AAAGAEAAAAAAAAAAAAgD8EAAAAAAAAAIOJE9k9A4AAAEAWAAAAAAAQQQAAqEEAAMBBAADgQQAA6EEAAABC + AAAIQgAAGEIAACRCAAAwQgAAQEIAAExCAABgQgAAcEIAAIBCAACIQgAAkEIAAJhCAACgQgAAqEIAAMBCAADYQhYAAABSHjQ/BKpGjz8Ov5E/ok2UP/Lekz/y3pM/gtnp + kD8B8t6TP/Lekz8E2emQP3ASjj/Z6ZA/cBKOP3ASjj+B3JBbP4HKREk/AhuELD+/hfE+AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAQAAAAEAAABmZmY/zcyMPwEAAAAA + AAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAA + AAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAADAAAABAAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACA + QAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAABwAAAAEAAAAAAAAADgAAABAAAAAAAAAAzczM + PQAAgD8AAAAAUGZmPvzvoUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAAABAAAAAAAAAA4AAAAQAAAAAAAAAAAAAkMAAAAAAACA + PwAAAEAA0ARGAAAAQM3MzD4AAIA/AIA7RQAAAACamRk/AABAQAAAAAAAAAAAAAAAAAMAAAAJAAAAAQAAAAAAAAAOAAAAEAAAAAAAAAC1SUw+AAAIQAQAoEAgAIDAqQVd + QLtLyUIAAKBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAgD8AAEDBAAAAAAMAAAADAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAA9igc + QK1H4T8AAMA/AABhwwAAAAAEAAAABQAAAAAAQMAAAHDCAADAwgAAwMIAAMDCBQAAABe30TgAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA + AAAAAAAEAAAAAQAAAAMAAAAAACBAysxMPwAAwD8AADRCAAAAAAQAAAAFAAAAAABwwgAAQMAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAANBRuD4dhas/XI+CPzhd40Jf9IdDBQAAAAUAAAAAAEDBAADAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAH4XrPlK4nj9cj4I/AADiQgAAssIFAAAABQAAAAAAwMEAAEDBAABw + wgAAcMIAAHDCBQAAAAAAAABSSZ05AAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAA + AAAAAAAFAAAAAABwwgAAcMIAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAA + AAMAAADNzEw/j8J1vgAAwD8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4FSsAAFByVksmAAACWEZUUAUrAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAu + MC8yMDIyMTExN49al13smqWCtwk6s9mRfZ9kbGxhxCoAAHRkdE1WBAAACgAAAE1LSUkgU3BhcmsAAAAABwAAAE1vZGFydHRHAAAASW50cm9kdWN0b3J5IHByZXNldCBw + cm92aWRpbmcgdGhlIGVzc2VudGlhbCBmbGF2b3VyIG9mIHRoZSBmYW1vdXMgTUtJSS4AAAAARAEAAAoAAABNS0lJIFNwYXJrAAAAANADAABHUlVVAAAABgAAAAEDwE1U + cmsAAAO6AP8CDU1LSUkgQkogQmx1ZXODYJBPfIJOTwCDBk1ugk5NAH5KYoFfSgBFSDmBUUgAgXtFNYEnRQAcSE+CQUgAgRlKb4E1SgBhRimCCUYAcEdcgXpDPQ5HAIJd + QwA3PjoqJjeBGT4AHCYAAEE7OCBCgjNBAIE1IACBCx8lDkNPiTwrNxwfAEUrAIEMIyCIFUMAUz9aDiMAgQwkJhs/AIEaQCCCFkAAgSdDPmJDAIQtRl0cJABiJS8cRgCD + Z0A4gRlAAIQ8Q0cbJQBiJiMcQwCEEUFWgSdBAINaPA+BNSYARiciHD09UzwAgW09AIEZQF6BUUAAYj8dgVE/AIELPTtwJwA3PQBUJh8APk6CXD4AgW0mAFQgLDhFOoJO + RQBhSG6BKCAANx80VEgAgRlHM4FDHwAcJh8ARwBiJgA3IzxUQ0OCJUMAgScjAA5GYH0kPINMJABFHE6BGkYAb0FIgwZBABxGSHBBSg1GAIEaPjUqQQCDBj4AGz9ZYhwA + Kh0xHD8ARUBCgxRAADg8IX08AGJBSoMwQQAcPz1THQAOPwBUHi9GQEqCTkAAUz9jggk/ACo+O4FtPgAqHgBTPU8cJC2BJz0ADiQAHB4rRjwKgXo8AFQ6S2IeAFM6AA4f + N0Y7NIJqOwBGHwAcPiZvPgA4QkwqHxmCeEIAU0VoRh8AfSQrh15FAG8kAA5CQQ4iREUiAA5CAFQjKRxDSIJ4QwCBCz5OgTU+AIJrIwApKEWFHT43VCgAKj4ARSIeHEMz + cEMAU0ZHfkYAYUhuVCIAgQtIAAAhMH1GWoIzRgCBX0NUghZDAIFRQSYAJyIcIQBUJwAqKCMcQQBvQzKBe0MAfSEsAEFWDigAgRknLiohABxBAG8+I4F7JwAAPgBUPA0c + JiGBQzwAgSc9BYFfPQAqQGAOJgBFKDeBX0AADj8ugSc/AIEMPUOBJygAHCk4Gz0ARj4/glw+ABxBNH5BAAApACkqNGJFMYFtRQCBXyoAHEhHYSsscEgAgyJGZ4F6RgAq + SE2CJUgARSc9OCsAHCcAVCgjb0YugkBGAHBIP2FIAIEaSTWBX0kAgXpKEyooAEZKACohERtPS4IJTwCBNUhiDk9sgVFIAABJQRxPAIFDSDIAJj44IQCBC0kAKkgAHENJ + gRlDACpIWIJqSABiQx0AJgAOKyB9QwAqIDgARj8OKwCCMkYAgScgAA5NTwAfQw5JTw5FPwBGQIl0HwAcK1xvKwCEZSY9cCYANys1hg1NAGFFAFRGAH1JAIFtKwAA/y8A + UHJzVV4mAABHAAAABAAAAG1raWkAAAAAEgAAAFZpbnRhZ2UgVGluZXMgTUtJSQAAAAAAAAAAcHJvbWAAAAABAAAAAQAAAAEAAAAEAAAAbWtpaQAAAAASAAAAVmludGFn + ZSBUaW5lcyBNS0lJAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEA + AAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA + AAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAgMADgAAAYFgAAAAAAKhBAACAP1gAAADsxI5AUq2ikkCsp4xA7VqYQGmrlEBA7o9AGCuZQBWTnkC9pppAGK6K + QB4Po0C8sopA4A+GQL/QkUDMvY5ABgCQQG7Yi0DWlIZAnrCGQIjVfECIs2ZAnAprQHjLW0AI1EVAiBM4QIDhPUA6kilACABAQPr1JkCM4h5AXGggQDDz8j8Y/Oc/tETS + PzBHyj+49MM/8KvHP3DPij9gu0s/AG/TPmixCT9ADsY+aF8DPwATVL4AcPM6gGw0PlA2tL7AlIK9KNEKP8BF2T1Aywe+QPk2voDQCb/YrnG/gCWAvwC9CL+gYwq/sN55 + v0Aiz77AbiG/yKMavwDTyr4IDkC/EK6jv5B/l79wDKa/cDO7v8B8Fb8ggku/QC8Av6B+F78Ang29oAasvrDD5L4A4K68ANa8PoC49b1wXQ2/mOhUvwCCdb/gdtC/PGjP + v4LjKMCC4yjAAlpVFcCC4yjAguMowIEDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAzcxMPwAAAAAB + AAAAAAAAAM3MzD0DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAADYQgAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAABAwQEA + AAAAAAAAAAAAAAAAoMAAAKBAAAAgQQOAAABgWAAAAAAAqEEAAIA/WAAAADxwz8EBCFPKwQhTysGBoG3BwT1szMjBcJe5wRg4tMEsQbHBiBqlwYi6q8H0PaHBYDGYwZi3 + ocHEbZbBoFuRwXyjk8HU8pLBCIyQwYSOmsE4qJTBrNuWwezXk8EsL5vBMFWnwdA3k8Gss4fBvAWDwXTDhsEcuojBMO11wYDOc8HI9WLBMCVbwXDmWcGIHlfBAKlhwWhk + XMEw8VTBoFdUwTBnOcEYHjzBELcvwUhzO8HYxynBsMscwfBHEcEI8AXBIFz9wOCV8cAw5uXAEDPawCDBx8Bgm8LAMB3EwFDPwMDgE8PA4JXEwKBBxcBQ8sfA8M3NwKCD + 2MCAKO7AEA4CwUAY+cBAmgfBQJoHwYEQDgLBE8iyBcEIwh3BGG0PwbBSDcHY9xbB4BsQwVikHMH48CnBSF4+wagSS8EQHE7ByHZvwZC+b8H8wIPBMNpowZAulsE0+qLB + aI6twSRrqsEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAgAAAAD/NzEw+A4AAAGBYAAAAAACoQQAAgD9YAAAA5OqGwVeglYjB + xLaHwTgPksGosX7BoCeBwTBojcGIG3jB8HVbwfhCacGIGVrBoH5hwVDbbMHQ2lbBgCdDwQAjP8EQcz3BQNMwwRAANMFo+T7BgP0uwRh4JcGYUh7BcOknwYCXJcFgSiPB + GAIhwYC+HsFIxwfBOC0YweB3C8GAGx3BWMYVwcCRGME4nBjB4FMKwYh5AsFASurAwLH4wJD648DQQ+DAYG3TwABf9MDgzOnA8AvUwDAlwcCAW8nAANDLwBClv8DA9bDA + MJeowABrscBg5KrAgOyywNDnq8BACLDA0Fq2wMDju8AQKL7AIHKzwDB1usDwCsTAsGbGwLBwwsAwL7rAYK+1wBA9wMDwjcnA4E3FwED+vcDQeMTAYHjVwLBx3cCQW+DA + cOXbwIBR4sDw1+rAYM3qwGBB8MA4UAXBeNEZwShRI8GgyDTBwMJJwUA2ccHc4Y3BaAavwdx5ysEDgAAAYFgAAAAAAKhBAACAP1gAAACi5vy/Vwcgjr+YsaXA18dxwEIA + I0AS4ss/4na7QPXkaj/VHi4/qaOyQNCCTUC6Mm9AO3MAwLadlcDYPI9AFe5kQHsxpUDdZr1AYmpqQEOYur586Ew/8b8ewFzvtT9HXHzAuviPwAFmQ0D3AWFA+8SdQPLa + p0DzvEXAI1ipQFLJ376q9Nc/fgdxwHFqr0DbpJhAcqYjQL8YhED8ock/nXbQv2gxIz+zPIFAgn/BP3+BqkAdzCdAPaGLwF1V0b0slHRAaXGjwLvIF72NPxI/J+KMv8b3 + XcA7q5dAllKewMNF579cmIC++CCkvnjLrEBRnbPAtlwPwN4Zq7/6ArzAPg+5wFuImkAVcei/MLqRwOVVksCMk39Aih2/P0yFJsAEcoxAaHilPcdqZEBLywO/miDQP3Tv + nUBl71TABoZGv7uAGMB+C7JAvtaVQM4Ef8D7BWLAUF0NwKVRQj/NUyTAf3VWwAOAAABgWAAAAAAAqEEAAIA/WAAAAGozgj9XD2hsP8uNbD+xcoA/ilZhP1DjVj8AKGI/ + btGNP8YqcT+uE5Q/V1ScP4pXbT/JMXc/2UGyPzgXaz/cu40/FEB9P3QBdz8BS54/6TCXP/I6sz+90j4/rxFVP48RRT/IPl8/YkRLP+sigT+rRkk/+hSLP6QylD/5KqU/ + DcaFP2MtTT833lI/S7ioP+Hvtz+o7mk/iapHP6S8hD8/W4c/8VyRP036rD+jAao/hv6IP6xTdT8zVbw/qUCOP4nAXj8Rs34/yHVfP6Lfqj/XtGA/5N9nP5djdT9dYGY/ + qF5aPxawhD8p1Jo/dBlVP9lXZz9dfVs/d8lZP+GYjT9xzDE/iCB4P+aykD+tcmY/9cGfP+QgUz/ZD38/HGd9P750hT+Q0XM/0gJbPxk2lD86ao8/ef6HP15RVD/cs4M/ + Z89OP2YPWj86mYg/NDyKPyt+Uz87qVQ/GgyTPwqsij9P3Gc/AgAAAAEAAAB+AAAARmxhdCB0ZW1wZXJhbWVudApBbiBlcXVhbCB0ZW1wZXJhbWVudCB3aXRoIGhhcm1v + bmljIHN0cmV0Y2hpbmc6IHNldHRpbmcgdGhlIG9jdGF2ZSBzdHJldGNoaW5nIHRvIDEgd2lsbCBkaXNhYmxlIGFsbCBzdHJldGNoaW5nDAAAAAAAyEIAAEhDAACWQwAA + yEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOA + AABgWAAAAAAAqEEAAIA/WAAAAADsWT1XALslPgC41b4AcEg9AEk/vgCe9T2AdbE+ADBSvACJCD8AmR0+AImCvgBgZL0AbMk+ALonvsBdM78AOII+ADPsPoB+kL4AhEw9 + APmoPgD+or7AVxC/gJXMPgAhBL8A5Be+gO7PPgCAxz1AlBa/AFabvQBPDD6ApaI+AGtTPoCSwT6A986+APZSPgBo/DwA2t++AMVovgDRBr4Amwk+AN6TPYD64D4A3YA+ + AOnVPgCy3T3AGw0/gDyOvgCU4z4A0xG+ANifvQA02T0ASnu/AKCbvYAslL4A7sY+wBtePwDWQD+AC7Q+AKZJPgDWIz4AzJ8+ABofPgCq9T2A8di+AE4wP4DPOj8ASO0+ + QDSbPwBw3j5gM7c/QJGGP6CD1j8AYvk/EO1VQBCfWkDQS0FAgLtBQPB8YEC4DKBAeNC0QDgPs0DAa9lAIGfwQNDSGkFciyNBIEFBQbSRWUGM44pBAAAAAAAAAAB6NpM/ + AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGBYAAAAAACoQQAAgD9YAAAAAAAAANcDgAAAUAAA + ekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABgWAAAAAAAqEEAAIA/WAAAAAAAAAADXzypO4pJHz0AAAAAAAAAAIF4kOw8BgAAAAB3sHI9a7wVPC2PJz0X + M1w8AAAAAAAAAAAKsH94O3GpiTwIXWI9c4UmPAAAAACvWhw9AAAAADLXujxT71U7AAAAAAAAAACCng/pPCNK2sU8AAAAAAKbkjxy1jM8AAAAAIH5fjyicB099HiXPAAA + AACnFB49AAAAAJehNz0AAAAAEsX/PCpeuzwAAAAAfrwdPQAAAAAwLeU7oiKkPBEqWj2RiSs9aSZHPQ1AlTwAAAAArtmKPGd0DDyj7mQ8AAAAAFw+hDs4gdQ7AAAAAEoh + tDyEYzM5AAAAAAAAAACBXOYGPBYAAAAAIh8IPJbA3jxkymY7AAAAABfruTsAAAAA0K4NPbZKKzwAAAAABtAvPFZHkD0AAAAA0HGKPTx+Az21Vrs8AAAAADmFBD2wz9Y8 + L/hCOwAAAAC4mq08AACAPwOAAABgWAAAAAAAqEEAAIA/WAAAAADzOj1XMJbhPWC2WD0gYQM9QOtPvQCURrxgsBA94ISdvFAhhj2AsRM8gA1PPMB9pTxwTQq9AB5SPQBD + RbtAjWC8MBccvQDMmzwAyWK9gLEfvUCCNz0AOGU8AEY/O2CJHb2AsMM9IN4rPQDZ9jygyIw94BO8vWAlnbzwLWO9YB0zPYDvLjzosIm9kA0tvXBmIb0AlhU9QB6rvMCi + DD2w1Se9ADQ1vACthDvAJfS8wDOFPYBxwLxg4nY9QDa+vPDBHL0AyPq6wNIQPUDrCT3wmmC9QCCsvMBMYT3AcTM9AKN3PYAhDLyQCjq9QOqRPRAiS70AR8G8QCGpPICf + NzzA0sm8AMssPCCEGj0gk5O8AMu0u6CoMD3AIYQ8QII9PYBt2DwAr3o8oFrjPYDuOjyYH9G94D7wvGD0wbzge7C8oDxrvQDHvzwg4Lg9AAK+PICSzrzgPYe9cDKzvaAv + Yj0AgKU6AQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4JggZg+A4AAAGBYAAAAAACo + QQAAgD9YAAAAoA17Pyc4zHQ/CwlgP9TLfj8zJ3I/sGt2P6ilej/gOHA/kNZ2PwzTcT8C+XI/1RB4Pw8QeD/uv3U/3v6EP5oagT9gtHg/M5x/P/gqgz8h54Q/pT6HP0wV + iT+dRYo/HtSLP6efjj+SNZM/67WWPwqDlj+MRpg/UxKePzoAoj8VLKI/SaOmP0SuqT/GtKg/q3+qP5h5rj+jlrE/qrOyPwGDuT8Bg7k/AXSOwD90jsA/EZvnwj+Y8MU/ + kcLLPxluyz8OA8g/20LJP664yT8OFMw/NEnOPySW0T8mctM/gAjWPweF2j+8yts/TvrZPzQ13D+PbuE/j27hPwfdIOY/xNLmP4uM5D/03Ok/VpDlP46G5D8r0+o/K9Pq + PxN3TuU/eLThPwR66D/wTeM/RerlP1As7T8l+ek/DCfmP1Lm6D8DJeI/3RntPwTh6z8r0+o/KzPqPwR66D8r0+o/0dfqPwut6z9QLO0/PSVIPwOAAABgWAAAAAAAqEEA + AIA/WAAAAPZ4WD9XZoZZP3fhVj8yvmU/NPBdP9riWT+wSV4/+GheP01oZD8+pmg/ymx0PyUkdz8ia4E//WmAP2Muhj/HaYs/auuNP/2XjT+pupE/oLOVP5SQlz+JbZk/ + xciaP0b5mj/EF5w/qmycP/J5nT/4HJw/0CidP0LznD+BfZ0/BnOcP7JCnT9APp8/avubP5F5nz9B2J4/Q56dP0+qoD8bOKA/QoyeP6UIoD/SA6I/aAGjP9mJnz9ykaI/ + b/edP786nz8aBKU/anGgP6m/pD/TvaM/t3GgP3rMpT/EnJ8/5F2kPzJ6oz8Wz6E/6MukPyHnoz+Ko6A/At6nP3EfpD+fjqQ/KhaiPw7ooj+9YKg/9k+fP7Rkoz8lG54/ + oMegPxJ2pj/SgqI/+UahPz6LpT9OQaE/EQOePwF4oT9VtqM/eeCgP4MbqD/Kh50/bj+eP75ZnD+4nZ4/jHWdP2z4oT9vRJ8/AAAgQgOAAABgWAAAAAAAqEEAAIA/WAAA + AH6GBcFX6CjnwGAHzMDysh3BkPPHwGodAcE4E9bAKOcTwbJADMHQkfHAeBLHwAA97MB+lQfBmBLUwChzFcFgtdTACGzXwLAG68AQZfbAaA/hwGD15cAId6jAWBHHwLDq + asDAcLDAgGZtwMjviMDQ13nAwGL5vwBGUcAA+yq/wCxCv4DM2r8QjQPAAAmuv2CWAcAAgNe6AAitvsD5JL/AQMo/QFi6PwACa74AIYA+AF4EP8B/GUDAaoU/gNnUvsB8 + ID+wzQBAAKDRvkDL3T8ALJ0+gLmQPwDa5T6AoLE/AEsePwDRN0AgJrM/wKAmQGAMWkAQ6VtAoHFSQLAOYEDQIUxAQNsFQFCUmUDwDrdAeCOIQCD9sUBoRdhAAA76QBgF + 40CkSRtBVBEGQURQN0EAPUZBfHdeQQxZVEG4IVlB6DhkQfycYkHsdnpBGDlnQfK9iUG2p4JBin6FQTCJgUFAV29BAAAAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAA + AIA/ggOAAABIAQAAAAAAAAABAAAAA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAAEAAIA/A4AAAGACAAAAAAAAAAAAgD8CAAAAAACAP4EDgAAAQAQAAAAAAAAAJpNJPv5+ + Pz8AAIA/BAAAAAAAAACBAACAP4EDgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAgQAAAADNzEw+AAAAAAAAAAAAAAAAAAAAPwAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAA + AIA/gQAAgL8AAAA/A4AAAGACAAAAAADAQQAAkEICAAAAAACAP4EBAAAAAQAAAAEAAAAKAAAATG9uZyBQbGF0ZQkAAABsb25ncGxhdGUAAEDAAADAQQAAgD8AAAA/AAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqQ96VWDkAAIA/AQAAAAYAAACGg2Y9AACAPwOAAABgWAAAAAAAqEEAAIA/WAAAAE6QkTyBJ/SiPAFOkJE8TpCRPAN81ts8+Hen + PE6QkTxOkJE8BUU9mTxOkJE85viSPGSCpDxOkJE8TpCRPAOk1Z08o5CnPE6QkTxOkJE8g93LnDwDTpCRPDDpqzxOkJE8TpCRPIII66I8BAmPrzw5kaY8ejvVPE6QkTxO + kJE8DVTOujyIrug8qvnmPCqhtDyu9L88QITCPE6QkTxGWtg8aZ7EPGhbyTxSRaM8R1WsPE6QkTxOkJE8hBhbrzwBTpCRPE6QkTyDmsn/PAI8EeM8TpCRPE6QkTwDlRf8 + PAqslTxOkJE8TpCRPIHuA5U9Asr6pTxOkJE8TpCRPAgAoqA8QV/tPagHzzy8spI8TpCRPAEHojyZsWs9TpCRPE6QkTyBy5vCPAX9q5Q8Yg/dPEgFwTxGBc08SzMaPQOA + AABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAAQAAAAAAAD8AAABAAwAAAP//fz8AAIA/AACAPwOA + AABgWAAAAAAAqEEAAIA/WAAAAEoNkT9X5jeOP68+kj+6UY4/C9WNP42UkT8yfo8/LoSNP+/ejD8o/4s/kJqRP1AmjT+UzJU/TciUPxoukz8VTYw/RYCXP8cKlj/4NJE/ + q6SZP991mD9ZLJY/nTqaP0jRoT9lM6M/Rh+dP8DfpD/0e54/92GgPw1Hpz/BZa8/+tevP8qWrz/+K7k/AhiyP2DPsj+PRr8/QA60P6Vysz+S9K8/hcGvP5x3qT+//a0/ + H5qYP7AOmj9BapM/NrqNPxOHjj8X9Ik/6jeHP/lWhT+AEYA/eiV4PzQsfD9ouXo/j6WCPzXNhz/xToQ/FPN4P0lOfT/loYE/mCd6Py4sdj8L030/zoWHP3BDej+DuoQ/ + wDiAP87nez/lYn0/4/R4PzIYej+smHY/cHZ9PxCVcz/bWIA/4D6EPwxadT8mPnU/9yKBPx4Dhz/WBnA/zxJ+P8I+hT/RSYU/oMF4P3TfgD8kLnw/A4AAAGBYAAAAAACo + QQAAgD9YAAAAM3R0P1edw4A/0CF+P+6+hD+CeYA/5Tx4P/O3fD/bjoY/1zhzP/yAgj+TbXk/hapxP3uagz9wuIE/lk19P8yFfD89aIg/ArZ7P612fT+noH0/VCqCP5HY + hj+5/4A/Tv97P18CgD8ANoM/AuB4P1KDhT+bh30/gGR/P2aheD8aDnU/vOhzPxMdgj8Tn4Y/loF+Pyjmej8Mw3o/s7GBP9aHfj8/0XA/vLxyP1cSgT8PnH8/MZ6HPzWD + ej8XO4c/OnCIPxxdhD/edXo/nM2EPz5Sgj9Z3oI/37iCPyZKfj8coYI/r4KBPwAUgT9bQIY/89B5P7Xjgz+kxII/Fr6EP2XWfj+PsYM/qZOFPynQhD/ylYY/SFWDP2sW + hT8Mg3c/yjB/P/oUfz84d3Y/vt1/PztFgD/z834/B8mDP/d0fj+2RIM/Wa2DP4s5gT8D43s/fJVtP2difj/K3IM/WuGAP0yqfz8DgAAAQAMAAAAAAKhBAABwQgAA2EID + AAAAAACAP4IAAIA/AACAPwcAAACli64/4noUPwAAgD/BDrw/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABgWAAAAAAAqEEAAIA/WAAA + AH7jdD9OZyyGP+IYbz9rKHo/QGNzP0oNcD+y/24/VQ9kP6hGZD/rc28/eax8PxUHcD+puW0/NvBuP+V9XD+9NmQ/EuhsPzqEdD9c9F4/KoB2P8yUeD+o04E/hFaQP2UR + jT/vmJY/gACQPwfLjD8dRIo/XbGVPzLijz/WMJI/yh2WPzamkD9eYJc/28qMP5w+kz8uwZc/yt6XP8Agkz/b5pE/dKKRP6Cskz9yypQ/h8yPP4q5nD/RJZU/2kKXPwqU + lj9y65k/2LuWP4hclD/IL5Y/5NeYP8gvlj9DjZo/R9aZP98wmD/IL5Y/7+6XP1Bglz9XT5o/EeGXP0fWmT+P754/GxmaP4Clmz9mHaE/QXCoP78WrD+zAqk/RtGrP4BD + qj9o8Kc/0KmrP/rssD8+va8/vWOzP36QsT88Crc/PAq3Pwh63bg/urC6P/iDvD+6sLo/dyrAP8tBxD/20MM/dXfHPwOAAABgWAAAAAAAqEEAAIA/WAAAAEWU6j+CCfPl + P1TzZ+Q/tcbfP+ZX0D8DXr8/u6+wPx4hqT/Dc6E/xaGdP3gQmT9W4JM/QzGRPw9bhj95F4M/95OGP7PlhD+4Dog/B2OKP4wsiT9WzIY/8tCJP8j3hD+Sl4A/vvGFP0OD + hD81IXY/EB6LP4AJgT9yDYg/oFZ3P71AhT+EAYY/Qv1kPy/6ij+l3YE/72V4P45Acz8kpIc/fsxpPwDbjD8XD4g/9kSRPyByhz99MnI/V5CFPy0Pij+2kYE/ejNqP6dY + hD8yzG0/fPZuPwQqkj+3Pnk/nAqBPzCWbj82WYE/9VmJPwiabj+Qv3s/RJCDP+Haej/ZLIM/fDyQP087hj/QdII/nj50P+LRgj+EhoM/LZpvPzKbYz91xHo/XzKAP721 + ez+4Y4Q/o2aXP81Oij8rxHc/cQOPP7+PbD8484k/0UtwP/U/hj9DBWg/RvR0P7Hyij8DgAAAYFgAAAAAAKhBAACAP1gAAADRGqM/V6C8pD8Fs6Q/IDmmP7rooj+VA6I/ + s6ScP9FFlz/KAZE/NG2NP57YiT+JuYQ/LO2BP9tLgj/DJoE/jeyAP/n/gT9mE4M/8VF+P62ggz9PjII/A8p9Pw4UiD+kToM/W02GPwF1gj938IA/lFOBP6ZfhT89KH0/ + VumKPyJOgz/bBHU/FdyEP0ZuiT+GYXs/qbGHP72lfT+suYM/6BJyPxQVhz8KY3c/OEdwPwhXgj/s7oQ/LnWFP9Oldj9+83A/jWxxPxBmgT/A9Yc/NbSCP7nEcj/z84E/ + 3Vp4P6I+gj/Uvog/tt14P1vgdj8Ghns/zpqGP3TXdj/Dnns/wdWCP6X+dj9Ix3k/EUGBP/v6dD+sKoY/EuCBPyqxgD/AoYE/2C1zP6PTej/dT3s/FD9/Pwo5gz+Ts4k/ + y8NmP5sqcD95zXg/bJGEP6pTfj+V/IE/+sJ7PywdgT8YRH8/XMmAPwOAAABgWAAAAAAAqEEAAIA/WAAAAD33jz9Xul2TP72skD/5mpE/Sc2UP0ykiz87944/o1CDPx5z + jD/GAIY/LM6FPwCTiD8PbIM/gHSCP7f/gj/qe4Y/i8h8P5Wqgz/fqXY/mmxzP1RYfz8htoQ/yWWEP2OEgD/D2YM/y/+EPwxOhj8YWYw/VyqIP25ahD/FDJQ/5l6IPwkW + iz9OW4w/6kGIP1aQlD+fA44/+TCPP/DXkT+6ZpU/5e6SP/7lkj81QZU/3iaQP5mJkj8L6pk/4/yQP9Abkj9SiJA/rj6RP3izkj/8KpM/oniUP1bJlj+1C40/F1KRP19/ + kz9/a5A/vNCSP1k3kT/4mJI/pc2WP1bblz8qTJY/kWeQPxl3jz/K45Q/sYqSP9Zfkz8qU48/v5qZP41bkT86IJI/h0CRP2fCmD+uS44/XlSaP1xQjT9j85E/DwaUPxfi + jj9LVJI/afSPP0omkT9mSZU/L92NP/3Okz8rx5Q/AQAAAAcAAAAAALhBA4AAAGC0AAAAAAAAAAAAgD+0AAAAAADAwAMAAIDAAAAAwAAAAAAAAAAA/wAAAACvAADgQQOA + AABgtAAAAAAAAAAAAIA/tAAAAAAAgMACAAAAwAAAAAAAAAAA/wAAAACwAAAQQgOAAABgdwAAAAAAAAAAAIA/dwAAAAAAAMABAAAAAAAAAAD0AAA0QgOAAABgQgAAAAAA + AAAAAIA/QgAAAAAAAADBAABAQgOAAABgQwAAAAAAAAAAAIA/QwAAAAAAAMABAAAAAAAAAADAAABQQgOAAABgNQAAAAAAAAAAAIA/NQAAAAAAAAC0AABwQgOAAABgJAAA + AAAAAAAAAIA/JAAAAAAAAACjAAAAPgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAA + AQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAAAAAA + A4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAADAAAABAAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACAQAAA + tEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAAAQAAAAEAAAAAAAAADgAAABAAAAAAAAAAAAAAQAAA + AEAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAABAAAAAAAAAA4AAAAQAAAAAAAAAM3MzD3D9ag+Z2YWP5qZ + mT4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAAQAAAAAAAAAOAAAAEAAAAAAAAAAAABZDAAAAwM3MTD4AAABAAABhRQAA + wECamZk+AACAPwBAtUUAAIBBmpkZPwAAQEAAAAAAAAAAAAAAAAABAAAAAQAAALPvMj8AABDBAAAAAAMAAAABAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAAPArXPvYo + XD8AAIA/AAA0wwAAAAAAAAAABQAAAAAAAAAAAPDBAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEA + AAAEAAAAAQAAAAMAAAATroc/9ihcPwAAgD8AADTDAAAAAAAAAAAFAAAAAADwwQAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAABAAAAAQAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4VrPwAAtEIAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAA + cMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAAAAAA + AAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAABAAAAAQAAAAMA + AABSuB4/AACAPwAAgD8AAMBBAAAAAAAA8EEAAAAAAQAAAAAAAAAROFY4mxEAAFByVksmAAACWEZUUIsRAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8y + MDIyMTExNykbHhnkfoOsmILVrJdR9m5kbGxhShEAAHRkdE0fBAAACQAAAE1LSSBUcmVtbwAAAAAHAAAATW9kYXJ0dC4AAABUaGUgVmludGFnZSBUaW5lcyBNS0kgd2l0 + aCBpdHMgc3RlcmVvIHRyZW1vbG8uAAAAAEQBAAAJAAAATUtJIFRyZW1vAAAAALQDAABHUlVVAAAABgAAAAEDwE1UcmsAAAOeAP8CDyBCbGF6IEp1cmpldmNpYwCwQAAA + QwAAQgAARQBNkDQoB7BAf4VykDomhCc+NINbQkCERYBCAC+QRT6QKLBAADKARQApkEIhglKAQgBIkEMsgmyAQwAEkEVSgTGAPgCBaToAF0UAEpBGVEeANACCK0YAE5BI + XoFsgEgAgRmQSEqCOoBIAASQSUSFES0kgTGwQH+CX4AtAF2QNyCCTYA3AIFnkD0dgzCASQCBVJBBE4Q6SG1ssEAAgSKASABlkEZAgU6ARgCBGpBFHYEVgEUAgUiQRjyC + OoBGAGmQQx6DbYBDAEOQRCyFCIBEAEiQRUCHHoBFAAaQRlyCKYBGAEWQRDSBK4BBADtEAHOQRT6CXIBFAAc9AACQQCKCIIBAAFyQQ0iDDoBDAFyQQTqFZ4BBAA+QQ0KB + FDIdgTaAQwAakEQ+hV2wQH9zkDgqhnk7GIZtQCiMAEcygnOARACJFDsADTIATDgAakcAAEAAhECQSFaELYBIAAmwQAA5kDIyBkVIgkiwQH+BU5BMVCA6LIR9gEUARJA8 + JAZBOJJfgDwAAEwAJ5BKRjqAMgA5OgAPQQCBB0oATJBIToJAgEgAfpBKXAqwQAAYkDM2gjOwQH+BH5A6NIJ+PjqCZYBKABiQQ0CBBoA+AIFuOgAMMwAUkEhhPYBDAIFn + SABbkEZWhDCARgAukEhSDbBAAB2QNCiCU7BAf4ErkDougxxDOoErgEgAgmFDACCQRkCDboBGAC80ABCQRVRpgDoAhAOwQAAhgEUALJA1MgNGRoJZsEB/glKQP0YzOhgy + gEYATD8AhleQSkwqPyyIQoA/AIJVOgBJNQCGA0oAD5BLQIMxgEsAL5BMaRcuKAawQACCL4BMAHiQT1ZXOS52gE8AgTSQTT6CDIBNAFmQSkaBUIBKAHiQRiF2gEYAhBKQ + SlCBcIBKAG6QS0CEOoBLADaQSjyKLEZcEYBKAIIQRgA9kEU4ggWARQBUkEhUggmASABBkEY4glmARgAJOQBHkEQsggyALgCBQEQAS5BFMIEULRaCRbBALApAf4RZkDcY + hRs+HIdssEAAYoA+AEmQPReBO7BAf4kdgD0AgWYtAIQjNwCDEi1AAC5AADJAADNAADRAADVAADdAADhAADlAADpAADtAADxAAD1AAD5AAD9AAEBAAEFAAEJAAENAAERA + AEVAAEZAAEdAAEhAAElAAEpAAEtAAExAAE1AAE9AALBAAAD/LwBQcnNVGw0AAEcAAAACAAAAcjIAAAAAEQAAAFZpbnRhZ2UgVGluZXMgTUtJAAAAAAAAAABwcm9tXQAA + AAEAAAABAAAAAQAAAAIAAAByMgAAAAARAAAAVmludGFnZSBUaW5lcyBNS0kAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5 + YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MRAAAAAQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAA + AAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAAAA + ggMAAAABAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAEAAAAAAAAAzczMPQOAAABgAgAAAAAA + qEEAAIxCAgAAAAAAgD+BAQAAAAAAAAAAAAAAAACgwAAAoEAAANhCAAAAAAOAAABQAABwQgEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAA + A4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAgAAAAD8AAIA/A4AAAFAAAHBCAQAA + AAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAgAAAAEAAABxAAAARmxhdCB0ZW1wZXJhbWVudApBbiBlcXVhbCB0 + ZW1wZXJhbWVudCB3aGVyZSB0aGUgb2N0YXZlIHJhdGlvIGlzIGV4YWN0bHkgMiB3aGVuIHRoZSBvY3RhdmUgc3RyZXRjaGluZyBpcyBzZXQgdG8gMS4MAAAAAADIQgAA + SEMAAJZDAADIQwAA+kMAABZEAAAvRAAASEQAAGFEAAB6RACAiUQAAJZEAAAAAAAAAAAEAAAAAADcQzwAAABFAAAARgAAAAB/AAAAAAAAAAcAAABEZWZhdWx0AAAAAAAA + AAACAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAAAAAAAAAHo2kz8BAAAAAQAAAAEAAAAYAAAAA4AAAEgBAAAAAACAPwAAAAAAAIA/A4AAAGACAAAAAACoQQAA + rkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAAAAAAAAAAAIA/ + A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAAD + gAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAIEIDgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAAD8DgAAAQAMA + AAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+B + A4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAzcxMPgAAAAAAAAAAAAAAAAAAAD8AAIA/A4AA + AGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAAAAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAABAAAACgAAAExvbmcgUGxhdGUJAAAAbG9uZ3BsYXRl + AADAwAAAwEEAAIA/MzMzPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPwAAqkMAAAAAAACAPwEAAAAGAAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACA + P4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/AACAPwAA + gD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/ + AACAPwcAAAAAAIA/mpmZPs3MTD8zM7M/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOA + AABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAAABAAAA + AABwQgOAAABQAAAAAAEAAAAAAAAAAAAAPgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAA + AAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAA + AAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAADAAAABAAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACA + QAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAAAQAAAAEAAAAAAAAADgAAABAAAAAAAAAAAAAA + QAAAgEAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAcAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAM3MzD0AAIA/AAAA + AAAAAAAAAHBCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIA/AAAAAAAAoEAAAAA/AAAA + PwAAgD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAALPvMj8AABDBAAAAAAMAAAABAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAAmpmZ + PgAAgD8AAIA/AAA0wwAAAAAAAAAABQAAAAAAAAAAAPDBAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA + AAAAAAAEAAAAAQAAAAMAAADXo3A/AACAPwAAgD8AADTDAAAAAAAAAAAFAAAAAADwwQAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4VrPwAAtEIAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABw + wgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAA + AAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAA + AAMAAABSuB4/AACAPwAAgD8AAMBBAAAAAAAA8EEAAAAAAQAAAAAAAAAROFY4pBMAAFByVksmAAACWEZUUJQTAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAu + MC8yMDIyMTExNxOr78opGWxdLxyiQPfMqppkbGxhUxMAAHRkdE2GBAAADQAAAFcyIEJhc2ljIE1vbm8AAAAABwAAAE1vZGFydHQ4AAAAVGhlIHByZXNldCBjbG9zZXN0 + IHRvIHRoZSBvcmlnaW5hbCBXIDIwMEEsIG1vbm8gdmVyc2lvbi4AAAAARAEAAA0AAABXMiBCYXNpYyBNb25vAAAAAAkEAABHUlVVAAAABgAAAAEDwE1UcmsAAAPzAP8C + CCBNb2RhcnR0AIAyAAJHAAtAAAtDAFCQJkk3sEBnCUBXCUBHBUA3BkAnBkAXBkAALpBFbQlCbAIyWwc+aIFcgEUAC0IACD4AgXqQQieBcIBCAIEYkEVJBj5ACUJEgWKA + RQAFPgAJQgCBN5BFWAc+UwdCUoFigEUABz4ABkIAgWeQRT4FQjoIPjiBYYBFAAdCAAg+AIFFkEVODEJFAz5EgWCARQANQgADPgCBDLBAFwpAJwVANwlARwtAVwKQRVIC + Qk0RsEBnBUB/BJA+On+AJgBURQACQgAbPgBrMgAUkEVCKyY6gUWARQCBD7BAZwpAVxFARwVANwdAJwZAFwhAADCQRUsLMjwAPkYGQj2BXoBFAAw+AAZCAIEckEU0F0If + HD4igTyARQAYQgAcPgCBHJBFRAxCOgo+O4FZgEUADUIACz4AgT+QQkYCRUgCPkeBboBCAABFAAI+AIF4kEUjKT4cgUmARQApPgB0kEVAC0I0Hj4rgUeARQAMQgAdPgCB + IpA+PgFCOwRFPYFrgD4AAUIABEUAgUkmAAiQQigIRSsKPiOBLoAyADBCAAhFAAo+ACSQJjWBDkNIADI7DjxEKUAngTmAQwAOPAApQACBP5BDJoFwgEMAgTaQQy4YPCKB + WYBDABc8AIEhkEBLA0NOAjxLgWuAQAACQwAEPACBSZBDMgo8M4FmgEMACTwAgTyQQ08PQEINPESBMbBAFwlAJwtANwhARwWAQwAJsEBXCIBAAAuwQGcCgDwARbBAf12Q + Q1QNPE8EQEeBVoAmAAhDAA48AARAAIETkENJC4AyAAqQQDQNPD5VJkR5gEMAFUAADTwAZLBAZw1AVw1ARwRANwhAJwVAFwhAADCQQF8DMksIR2cGQ2OBX4BAAAtHAAZD + AIFHkEdGBkNGFEAygVeARwAFQwAUQACBMJBHVgFDUg9ATYFggEcAAUMAD0AAgSiQQ2gCR2UNQFuBYYBDAAJHAAtAAIFukENPBEdNDUBGgV+AQwAERwAMQACBBZBHVAJD + UgZATYFogEcAAkMABkAAgU2QR1IIQ08DQE1BsEAXCkAnCUA3BUBHCEBXB0BnCkB/c4BHAAhDAANAABEmAIEKkEM9BUc3C0AsgSImRBKAMgAsQwAFRwAMQABksEBnC0BX + CkBHB0A3BkAnCUAXBUAASZBFWwtCXAIyUAw+VoFXgEUACkIADz4AgU2QQigMPiMERR6BYIBCAA4+AAJFAIEHkEVCA0I4CT5BgWSARQADQgAJPgCBVZBCbQRFbQk+Z4Fj + gEIABEUACT4AgSImAEwyAIJVJkAAMkAAPEAAPkAAQEAAQkAAQ0AARUAAR0AA/y8AUHJzVb0OAABHAAAAAgAAAHcxAAAAAA0AAABWaW50YWdlIFJlZWRzAAAAAAAAAABw + cm9tWQAAAAEAAAABAAAAAQAAAAIAAAB3MQAAAAANAAAAVmludGFnZSBSZWVkcwAAgD8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAAAAAAAAAAAAAA + cnlhbBAAAAACAAAAAAAAAAAAAAD/////b2xvcxEAAAABAAAAAQAAAEYAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAA + AAAAAAABAAAACgAAAExvbmcgUGxhdGUAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOAAABgQAAAAAAABEIAAIA/ + QAAAAAAAAAC/AwAAAAEAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAQAAAAAAAAAqVqE+A4AA + AEAEAAAAAAAQQgAAQEIAAHBCAACoQgQAAABNJkY/AwLLVT/QCIM/iG7BPwEAAAAAAAAAAAAAAAAAoMAAAKBAAAC2QgAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAA + AAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAAAAAAA + AAAAAAAAAIAAAAA/AACAPwOAAABQAABwQgEAAAAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIAAAABAAAAfgAA + AEZsYXQgdGVtcGVyYW1lbnQKQW4gZXF1YWwgdGVtcGVyYW1lbnQgd2l0aCBoYXJtb25pYyBzdHJldGNoaW5nOiBzZXR0aW5nIHRoZSBvY3RhdmUgc3RyZXRjaGluZyB0 + byAxIHdpbGwgZGlzYWJsZSBhbGwgc3RyZXRjaGluZwwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQAAAAAAAAAAAQAAAAA + ANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAAAAAAAAIAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAMzOTPwEAAAABAAAAAQAA + ABgAAAADgAAASAEAAAAAAIA/AAAAAAAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAFAAAHpEAQAAAAAAAAAD + gAAAUAAAekQBAAAAAACAPwAAgD8DgAAAYAQAAAAAABBCAABAQQQAAAAAAAAAgwAAgD8DgAAAQAUAAAAAABBCAAAsQgAAQEIAAHBCAACQQgUAAACuR2G/As3MTL4AAAAA + AAAAAIEBAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhB + AACuQgIAAAAAAIA/gQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAIEIDgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAAD8DgAAAQAMAAAAAAKhBAABwQgAA + 2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAA + ACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAzcxMPgAAAAAAAAAAAAAAAAAAAD8AAIA/A4AAAGACAAAAAACoQQAA + rkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAAAAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAABAAAACgAAAExvbmcgUGxhdGUJAAAAbG9uZ3BsYXRlAADAwQAAoEEAAIA/ + mpkZPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPwAAqkMAAAAAAACAPwEAAAAGAAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAA + AKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAQAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/AACAPwAAgD8DgAAAQAMAAAAA + AKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/AACAPwcAAAAAAIA/ + zcxMPpqZGT+amZk/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABgWAAAAAAAqEEAAIA/WAAAAIj2TT9XDSZjP41mlD/akXo/yD53P3C6 + Vz+ifqY/Z1VSP6AymT9KRp4/A2mIP3ROgT9SDYI//4ZdP9WAZD81nYg/uO6IP65scD+ra2M/eFhWP1q3hz81v5Q/lqKYP7Pibz8GRms/j0prP4jygj+A8pM/T4mGP0OF + gz/cQVY/cHtgP5imij/b74I/vN57Pw04jD+t31E/Z7CSP+nshT9lXYQ/xOqSP8igfT/ignY/u8peP+vPdD8Rgn4/l393P6OicT+RwHE/fg+VP6/LfT9M0YU/xQ6AP0If + cT8UcHY/zkuDPy50gz+pWn0/vROEPy0udD8ZEGs/FcKEP+93kj+qz4w/RtCOP7tzhz9cD2w/37NqP53Yhz+24I4/tdKNPxsIiz+vpmY/Ev5bP1OciT/PKJo/8JCBP7wN + dD9Yt3I/pviJPzJlgz81oXE/6wBxPy0afz/z42k/vPVzP+/UYD9LU2E/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAA + AAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAQAAAAEAAAAAAHBCA4AAAFAAAAAAAQAAAAAAAAAAAAA+A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EB + AAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9m + ZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAAgD8EAAAAAgAAAAMAAAAEAAAAAQAA + AAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAA + SAEAAAAAAAAAAAAAAAMAAAAHAAAAAAAAAAAAAAAOAAAAEAAAAAAAAACPwnU9AACAPwAAAAAAAAC/AAA0QgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAADAAAACgAAAAAAAAAAAAAADgAAABAAAAAAAAAAAAAWQwAAAMCamRk/AAAAQAAA+kQAAABAmpmZPgAAgD8AQJxFAADAQAAAAD8AAEBAAAAAAAAAAAAAAAAAAwAA + AAEAAAABAAAAAAAAAA4AAAAQAAAAAAAAAAAAoEAAAIA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAACA + PwAAQMEAAAAAAwAAAAIAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAACamZk+zcxMPs3MzD8AADTDAAAAAAAAAAAFAAAAAAAAAAAA8MEAAMDCAADAwgAAwMIFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAArXYz/NzEw+zczMPwAANMMAAAAAAAAAAAUAAAAAAPDBAAAA + AAAAwMIAAMDCAADAwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAj8L1Pz0KV78fhWs/AAC0 + QgAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAA + AAMAAAAzMzO/XI/CPq5HYT8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAwAAAOxRGD/NzEw+zczMPwAAwEEAAAAAAAAAAAAAAAABAAAAAAAAABE4VjjqFgAAUHJW + SyYAAAJYRlRQ2hYAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3tgoOV7Ytlb/sRjgVHmWFSWRsbGGZFgAAdGR0TbEGAAAOAAAAUGlhbmV0 + IE4gQmFzaWMAAAAABwAAAE1vZGFydHRIAAAAVGhlIGRlZmF1bHQgcHJlc2V0IGZvciB0aGUgUGlhbmV0IE4sIGNsb3Nlc3QgdG8gdGhlIG9yaWdpbmFsIGluc3RydW1l + bnQuAAAAAEQBAAAOAAAAUGlhbmV0IE4gQmFzaWMAAAAAIgYAAEdSVVUAAAAGAAAAAQPATVRyawAABgwA/wIeSy5QLlJhdXNjaCAtIFBpYW5ldCBOIDgwcyBCYW5kAJAm + WAs+VQBBXgBFWYEXgEFAEj40BkU3gnmQQWUARVyDID5SgVWAQRMOkC09gRSARQEnPgyCAZAyWIFIRUUAQWYAPlqCAoAyIWU+CgVFEgkmNWVBLRItQGiQMDgAJFsNRVUP + QFkEPFmBGIBAPBk8NQBFOYMhkEVmAEBogjyAMCc4kDxJgV8rQYEIgEAZHzwzC0UggWGQMFOBTEBjAEVfCDxcaoAwQIESPCsYJEBLQDA7KyARRTiBHpAuRgkiXBxGXgBB + XAA+TYERgD47BUE9HUY8gw2QQWkARmiCdYAuHYFHkD5TWykxgiuAQQEykC5kIYBGLTM+LoFUkEFmAEZjED5YUIAuQCVBQABGQAw+OGkpPBEiOoIikCszEB9FAjpmAD5m + ADdegxeANxR5Oi9IkDdjHDpZYYAfMYFGKzkANyqBAzoiaD5AB5AhXA8tRx08YwBAWQY5XoMXgDkrgUmQOV81gCEugWUtQIEWORg6PD4AQDZ+kCZUCDJNHzlbAD5mAEFl + gUOAPi4AOS4GQTODAJA+agBBZ4JaOT5ZgDIfgRc+EhCQLU+BF4BBBTI5IYFWkDJUgUE5VQA+XwdBWYEigCYmgRo5MBYyM0tBMhY+OW4tQCGQJlYRMkYWO1EAQ2MCPl+B + K4A+Mgc7LwBDMYMMkD5oAENlgjY7XCKAPhGBGzIuYJAtTIEWgEMCHDsbgUiQMlmCF0NaBT5eAjtZghyAJiYAQzARPiYUOyUXMjOBFi1APpAmXBsyY2pBR2qAQSuDEZBB + YwY+ZgA5ZIIcgDIxgVSQLUWDAIA5AgY+DAZBCAuQMlSBXz5pADlfBUFBgjiAJipZMioZOQFiPi4RQTwTLUAqkCZXCDJUFkNXCTtcAD5lgSKAOzYAPjMpQzmDBZBDZQQ+ + Zj47OTeAOzKBTzIwIZA7WYFDLTyBaoA+ASc7FRZDIYEikDJbgT5DWgU+YQs7V0mAMjhqQ0ACPjkLJkAqOzIkLS2CFZAiSwguTjY+ZgA6YwBBYwU3VIESgDoyDzcmDD4n + DUEognOQQWMIOmAEPmWBM4AuJXmQN1GCJylLX4A6Egs+EixBGBc3G4ERkC5TgXpBWwA6WgA3VgI+XIIigD4UATcKAzoVHkExfi4tgSeQOmAGQVsAPl4bN0RUgDc5HDpA + ET5AB0E+gyaQQWYHPmYJOlwHQFAsgEBAgVOQN0eBZYBBCFM6JxE+KiKQLlgWgDctgj6QQWMAOmAAN1kBPmODRoAiLAkpKh8uQFQ3NSI6MwtBNAc+QIF7kEBnBDljBT1o + MiFTLS0QgRiAOTEPQCUHPTyCPJA9awBAZR45SV2AOTeBZJA5WVSALSeBSpAoQSKAPQF5QBIcOSuBQ5AtRoFTQGEAPWsLOVSBPoA5KxE9PBBAOYMBkD1sAEBmBjlfh3uA + OT+BUpA5XymAPS6CYZA7V4EigEAgMCFADZA9Zh+AKD9wLUCCAjk1ADstPT07dJAySwsmY4R7QV4AOWUCPmN5gD5ABUFADzlAg0uQPmsAQWeCODlRgVuAMiwrkC1SgnGA + OSwMQQFMPhaBOJBAWwA8ZAs3XIlMgDcwCZAyaD+APDFzQAGBfZA3aIFoPFmCPoAmOREyMoERLTc5NzMykCZhADJeFoA8P4Q5kEFbAD5jBTlceYA+QBI5QAJBP4MbkEFm + AD5sgl85WG6APjyBOJAtS4EGgDk4HUEjgxWQQ2QEPmUCO1iGOYAyNINFkDJoaIAtQINlkC1RggaAJi2BDTI8gSctNjaQJloaMl4CgEMuLD4+Ijszg3CQQVoBPl4AOViB + BoA+QAg5QABBPoM/kEFmAD5pgks5ToEKgDIxSJAtToMCgDkqLEEGJj4kP5AyRlJAUAA8XBE3U4J8gEAdGzcKATwjgSuQPFcAQFECN0SBf4A3K4ELPCwnQDWBJ5BBWQ05 + Uw0+TYMrgD4KdZBAUU2AQR+BHEA5ADkuTiY1HTIygU0tLl6QJlkGMkwAPlwGOWECNVjLZYA1QBw+KwYyQBU5QDImPQD/LwBQcnNV2A8AAEcAAAAHAAAAcGlhbmV0bgAA + AAAIAAAAUGlhbmV0IE4AAAAAAAAAAHByb21ZAAAAAQAAAAEAAAABAAAABwAAAHBpYW5ldG4AAAAACAAAAFBpYW5ldCBOAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAA + AAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAA + QAMAAAAAAKhBAABwQgAA2EIDAAAAAAAAAIIDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAACAPwAA + AAABAAAAAAAAAM3MzD0DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAADYQgAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAA + AAEAAAABAAAAAAAAAAAAQMEAAEBBAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAABAAAAAAAA + AAIAAAAAAAA/AACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGBYAAAAAACoQQAAgD9YAAAAAAAAQFcjLPc/yz0NvtQISz9HWG5AWO5pQDXCskB8GiFAjbAc + QE8jrEBY7olAYbmHQKgRlj+x3NM9fBqBQAnLfUCNsJxAlnuaQD6NcECfRtg/wnLPP8s9DT+E5R5AqBEWv2G5J7+5p1FAyz1NQN3TSED3NIJAAACAvxKWe0CNsFw/aoTl + P3NPo79Y7mlAaoQlQPc0wj+NsBxAPo2wPz6NML8jLHc+1AgLQMs9jT/3NEJAIyx3P+aeBsA+jTC+fBrhP7HcU8D3NMK+aoTlvvc0wr9qhCXATyOsP0dYLsBqhOW/jbBc + v0dYbr8AAABA9zSCwN3TCMDLPQ3A3dOIwNQIi8CoERY/hOUewHNPY8BhuWfAhOWePvc0Qr8s9zTAsdzTPRKW+7/LPQ295p4GwKgRlr8jLHe+WO6JwJ9GGMCNsFzAIyz3 + PqgRFr8s95TAIyyXwDXCcsAjLDfAEpZ7wAAAoMADgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIAAAABAAAAcQAAAEZsYXQgdGVtcGVyYW1lbnQKQW4gZXF1YWwgdGVt + cGVyYW1lbnQgd2hlcmUgdGhlIG9jdGF2ZSByYXRpbyBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRvIDEuDAAAAAAAyEIAAEhD + AACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAA + AAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAQAAAAAAAAB6NpM/AQAAAAEAAAABAAAAGAAAAAOAAABQAACAPwEAAAAAAIA/AAAAAAAAgD8DgAAAYAIAAAAAAKhB + AACuQgIAAAAAAIA/gQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAFAAAHpEAQAAAAAAAAADgAAAUAAAekQBAAAAAACAPwAAAAADgAAAUAAAcEIBAAAAAAAAAAAA + gD8DgAAAUAAAcEIBAAAAAAAAAAEAAAAAAAAAAAAAAM3MzL3NzMw9AQAAAAAAAAAAAAAAAAAAvwAAAD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CzczM + PQOAAABgWAAAAAAAqEEAAIA/WAAAAHs0KkBXepIyQKj0MkDRvC5AkcUuQDx3JEBnMSRAti0yQB/zKkDZPTJAdWInQIZSJEB0WBpAySUZQMMCG0DoNu8/2ADlP8QX4j9Z + +NA/CK8XQH70xz+2r7Y/KvuiP9rGoD9MBJI/oSqZP1gWfz/9FZw/ksiiP+RYmj9wIZo/d26GP42+nz8kMpY/V5GhP3KPrj+FW8A/uSmiPznLkT9Ff44/faaZP91fhT/o + qow/0AeEP+xDmz/6FIg/d4ylP1Ncgz9BDpQ/e8CbP0XuoD9pQ6Q/GS2gP47GkT+rMqI/1uODP7TWnD8gppw/CZacP5g+nz8KHYA/nOKLP7ppjz8qCqM/Pm+iP7kwiz99 + YIY//7qBP4k+kD9DHaU/ufadP7gmlD9/ZZs/zbWGP1CWpT+04p0/Xp6MPyLelj8k5J0/HmGiPzf1iz+6XIs/5oucP1WTnj/AOIo/vXSkPzpAiD+onIs/AACAPwOAAABg + AgAAAAAAqEEAAK5CAgAAAAAAgD+BAADAQQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAAAAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABIAQAAAAAA + AAABAAAAA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAAEAAIA/A4AAAGACAAAAAAAAAAAAgD8CAAAAAACAP4EDgAAAQAQAAAAAAAAAJpNJPv5+Pz8AAIA/BAAAAAAAAACB + AACAP4EDgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAgL8AAAA/A4AA + AGACAAAAAADAQQAAkEICAAAAAACAP4EBAAAAAQAAAAEAAAAKAAAATG9uZyBQbGF0ZQkAAABsb25ncGxhdGUAABDBAADAQQAAgD+amRk/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAMA/AACqQwAAAAAAAIA/AQAAAAYAAAAAAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAA + AAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAAQAAAAAAAD8AAIBAAwAAAAAAgD8AAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOA + AABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD+amZk+zcxMPzMzsz8AAIA+CAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEAD + AAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAQAAAAEAAAAAAHBCA4AAAFAAAAAAAQAAAAAAAAAAAAA+A4AAAGACAAAA + AACoQQAArkICAAAAAACAP4EBAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/Zmam + PwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAAgD8E + AAAAAgAAAAMAAAAEAAAAAQAAAAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAASAEA + AAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAAAAAAOAAAAEAAAAAAAAAAAALBAAABAQAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAACwAAAAAAAAAAAAAADgAAABAAAAAAAAAAmpkZPwAAAD8AAIA/AACAPwAAAEEAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAwAAAAgAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAJqZGT8AAAAAAACgQJqZmb7NzMy9AABAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAEAAAABAAAAs+8yPwAAEMEAAAAAAwAAAAEAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAACamZk+AACAPwAAgD8AADTDAAAAAAAAAAAFAAAAAAAAAAAA8MEA + AMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAANejcD8AAIA/AACAPwAANMMA + AAAAAAAAAAUAAAAAAPDBAAAAAAAAwMIAAMDCAADAwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAAD + AAAAj8L1Pz0KV78fhWs/AAC0QgAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAQAAAAAAAAAEAAAAAAAAAAMAAAAzMzO/XI/CPq5HYT8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAwAAAFK4Hj8AAIA/AACAPwAAwEEAAAAAAAAAAAAAAAAB + AAAAAAAAABE4VjjKFgAAUHJWSyYAAAJYRlRQuhYAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3vuE6vMbD60otS5CXTcgaZGRsbGF5FgAA + dGR0TeUHAAAOAAAAUGlhbmV0IFQgQmFzaWMAAAAABwAAAE1vZGFydHRIAAAAVGhlIGRlZmF1bHQgcHJlc2V0IGZvciB0aGUgUGlhbmV0IFQsIGNsb3Nlc3QgdG8gdGhl + IG9yaWdpbmFsIGluc3RydW1lbnQuAAAAAEQBAAAOAAAAUGlhbmV0IFQgQmFzaWMAAAAAVgcAAEdSVVUAAAAGAAAAAQPATVRyawAAB0AA/wIeSy5QLlJhdXNjaCAtIFBp + YW5ldCBUIENvbmNyZXRlAJA/QQUpQAI4Qgs8TQI6R4ErgDgPGzoOJzwUCT8RgkuQOEUHPzsOPD8COkCBAoA6Ejg4CwU8DzA/EYJSkDU9gQaANR+BRJA4OAY/PgI6NwU8 + Q4EogDgNDjoPKDwRAD8SgkKQOFwNOkkAP0QaPESBC4A4DyQ6FxA8Dxw/EIJWkDh/gSSAOCGBJJA1DTGANRWCBZAwSFuAMA2BRpA6YQA4UAU/VIIrgDgTgUYpKjmQOFAQ + gDoqGz8gajgngR6QPxaBA4A/DoFWkB8+AjtTCz83ADVKASspgUyANRAdOx0APxqCZZA/RgA7VgA1UoEYgDsWDDUROD8Sgm6QJlo+gCYqgj+QO1MAP0AANU2BC4A7GCY/ + FgY1D4MNkD9LCjVPCjtMa4A7IBs1GhY/GoMKkD9TCDVjADtbc4A7IA41GxU/HoMSkCZPNYAmIoEokD9OBztXADVPgimANQYrPw0LOwxwHx9nkD86GzsjK4A7LwIrKj0/ + HoNZkDpCBCRBCUE5BTAsAD5VcIA6GHE+Dl2QOhUSPz4fQzoKgEEVbToVEEMTGT8cgnCQOksOPzYGQz5/gDoTIEMSFj8Sgh0wD0+QOmaBH4A6IWmQKzuCPDBJK4AwN4FL + kDpQAENHAj9GgTSAOgtBQxcfPxKCJZA6QhI/MwBDOoE9gDoRMj8VCEMVgmiQPGYHRVUAQUxugDwZEEEdJUUYgyiQRVwAQVYAPG12gDwePEUTKkEMCyQiOysigiGQJGoV + PFUAP1UBQ0uCAIA8CIFIPws6QxY8kDxbgSWAPA9QkDAzYYAwDoF2kCtHQYArFoIJkDxaAENQBD9RgQqAPBMIPxU4QxmDAJA/bAJDVghYWGOAPBoePyE4QxuCYpA8coEm + gDwSM5AwPIEBgDAlgXWQKzSCCj9mAENWAzxjgR6APA+CYiQVG0MlAT8xL5A8VQ6AKy8tPCGEGJA4YwYpHgI/SQY6TA08RQs1Hgs3K4ELgDcYPToSADgNJD8SFTwXghyQ + OF0QOkkAP0cTPEqBQ4A6FhA4DhY8DTM/DDs1FIFLkDBIgmY1RFKANSaEJpA6WwQ4XAs/SQs8RnCAOA8xOhAQPA8CPw+CcJA4f4FGgDgVaDARIZA1S3CANSWBNpAwSYJL + OHOCA4AwIoFkKSA7kDpJgRyAOBtwOg6CV5AfRAsrMBA7TgI/QA41TIEOgDUVSzsVAD8YglyQP0oTO00ANVCBeYA1Bhk7CzA/D4FLkCZCgTeAJg2BJpBBVQI7Twg1TYFH + gDUJFkEUCjsHgmeQNVECO1YEP1CBQIA1Cg07Cxs/EoJCkD5fBTVcEztSgi+AOwiCdTUBAD4EgVGQNQsaPEWEGIArHxs1BACQPlU8gB8aADwdTT4phBKQLDkGIEcDODQd + P0oKPE6CENABEAEFAQgCDQIGAQ0AhFiAIBgpLBYgPw0AOBcgPAyBIZA8VgI4Twk/QCAsTQIgXIEGgDwLIzgHgQEgHRA/EDssFYQIkCJQAjpTCEFDAC43Cz5HgQGAOhM7 + PhUbQRaCTpA6ZBM+VQNBSYYpgEEEC5A8SQo/QxSAPg2CXCIQgSYuGVg6LSM8GWY/F4NTkCQ5AjApED86CDxDBEM7iV4rJyWAPBFrQwcbPw2BHJA8VIFhsEB/gneQP0WE + H4A/DyOQQ0mBB4A8DYN9kD88NYBDCoEHJBYPKx44PxMekDxBRoAwGIN0kD86eoA8DBA/KYJxkCkuK7BAADaQMDBbPD4ANTMVQTgbRUB2sEB/EoA8EIFKQRRCRR+GepBI + PIFUgEgKgyOQTTOEbYBNCzOQTzCBFIApFgIwEnY1FIVITxaHRZA4D2k8KhM/MhOwQAAXkEMqNEQoNkg/Kk9JYbBAfxCARB9SSBx0TyOGC5BeOYEFgF4mhWWQSzQEVzeB + AoBLHElXDoU7kFk/FE0da4BNEgJZHYYiQw5VkEg3IVQiXIBIHDtUF4M8kEMSgkhXMxtLI0aAQwwjVyY2SxWIIpBEJg5QNIF8gEQHgmdQEE0/Dik4DwA8DJN0sEAAgV9A + fzuQPx0AQyoANSICOycUOBAQTyiCQ4BDEYFfTxKCaZBHKAlTN4ERgEcRgRFTDoVpkEo0EFYzdIBKFVdWDogbkE8mCVsufYBPGm9bGIsVkFMiCV85gSSAUx0rXxqBej8Q + KjsMgQA1Bww4B49BkCQmK7BAAII7QH9EkCsfgwcwJ4JdOy6DDTwwI4A7DIJukEAuAIA8EoMiQBUbkEMpeYBDEIIOkEg5gzhKMxOASAqDHZBMLUuASgVvTBOBYpBPMoF7 + gE8RghqQVDKCB4BUE4FGkFYsgleAVgSBApBYH4FQgFgTgmqQXQuBGYBdEYMakGA0hDWAYAk2kF8nhDiAXwxmkFsqhiCAWwOCK5BWFokOgFYHgV+QWB2LV4BYCB8wCQwk + BxorEZ8BsEAAAP8vAFByc1WEDgAARwAAAAcAAABwaWFuZXR0AAAAAAgAAABQaWFuZXQgVAAAAAAAAAAAcHJvbVkAAAABAAAAAQAAAAEAAAAHAAAAcGlhbmV0dAAAAAAI + AAAAUGlhbmV0IFQAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MRAAAAAQAA + AAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAA + AAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAAAAggMAAAABAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAA + AwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAEAAAAAAAAAzczMPQOAAABgAgAAAAAAqEEAAIxCAgAAAAAAgD+BAQAAAAAAAAAAAAAAAACgwAAA + oEAAANhCAAAAAAOAAABQAABwQgEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAABAwQAAQEEAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIA + AAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAEAAAAAAAAAAgAAAAAAAD8AAIA/A4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYFgAAAAAAKhBAACAP1gA + AAAAAIC/gQAAQMACAAAAwAAAgD8AAIA/AgAAQEAAAAAAAAAAAAIAAEBAAAAAQAAAAEADAACAvwAAAMAAAABAAAAAQAEAAEBAAABAQAIAAABAAAAAgAAAAAADAACAvwAA + gD8AAADAAAAAwAEAAABAAAAAQIEAAEBAFQAAAMAAAEBAAAAAgAAAgD8AAADAAABAQAAAAEAAAIA/AAAAQAAAgD8AAIC/AAAAAAAAAEAAAIA/AABAQAAAgD8AAADAAAAA + gAAAAEAAAEDAAAAAgAAAAAAGAACAvwAAAMAAAABAAAAAwAAAgL8AAACAAAAAgAMAAEBAAABAwAAAgL8AAIC/AQAAQMAAAEDAAwAAAEAAAIC/AAAAwAAAAMAPAAAAQAAA + gD8AAIC/AAAAQAAAAAAAAABAAAAAgAAAgD8AAABAAAAAwAAAAIAAAIC/AABAQAAAAEAAAADAAAAAwAQAAIC/AAAAAAAAgL8AAADAA4AAAGACAAAAAACoQQAArkICAAAA + AACAP4ECAAAAAQAAAHEAAABGbGF0IHRlbXBlcmFtZW50CkFuIGVxdWFsIHRlbXBlcmFtZW50IHdoZXJlIHRoZSBvY3RhdmUgcmF0aW8gaXMgZXhhY3RseSAyIHdoZW4g + dGhlIG9jdGF2ZSBzdHJldGNoaW5nIGlzIHNldCB0byAxLgwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQAAAAAAAAAAAQA + AAAAgNxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAAAAAAAAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAejaTPwEAAAABAAAA + AQAAABgAAAADgAAAUAAAgD8BAAAAAACAPwAAAAAAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABQAAB6RAEA + AAAAAAAAA4AAAFAAAHpEAQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAAAAAAAAAAAIA/A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAA + AAAAAL8AAAA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAgD8DgAAAYAIAAAAAAKhBAACuQgIA + AAAAAIA/gQAAwEEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAAD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAA + AAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAA + AAAAAAAAgD8CAAAAAAAAAIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAAAAAAwEEAAJBC + AgAAAAAAgD+BAQAAAAEAAAABAAAACgAAAExvbmcgUGxhdGUJAAAAbG9uZ3BsYXRlAAAQwQAAwEEAAIA/mpkZPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPwAAqkMAAAAA + AACAPwEAAAAGAAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACo + QQAArkICAAAAAAAAAIEBAAAAAAAAAAEAAAAAAAA/AACAQAMAAAAAAIA/AACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABw + QgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/AACAPwcAAAAAAIA/mpmZPs3MTD8zM7M/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADY + QgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAAABAAAAAABwQgOAAABQAAAAAAEAAAAAAAAAAAAAPgOAAABgAgAAAAAAqEEAAK5CAgAAAAAA + gD+BAQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAz + MzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAAAAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAADAAAABAAA + AAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACAQAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAA + A4AAAEgBAAAAAAAAAAAAAAADAAAAAQAAAAEAAAAAAAAADgAAABAAAAAAAAAAAACAPwAAAEAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAwAAAAsAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAJqZGT8AAAA/AACAPwAAgD8AAABBAAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAMAAAAIAAAAAAAAAAAAAAAOAAAAEAAAAAAAAACamRk/AAAAAAAAoECamZm+zczMvQAAQD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAA + ALPvMj8AABDBAAAAAAMAAAABAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAAmpmZPgAAgD8AAIA/AAA0wwAAAAAAAAAABQAAAAAAAAAAAPDBAADAwgAAwMIAAMDCBQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAADXo3A/AACAPwAAgD8AADTDAAAAAAAAAAAFAAAAAADw + wQAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4Vr + PwAAtEIAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAA + AAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAMAAABSuB4/AACAPwAAgD8AAMBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4xhcA + AFByVksmAAACWEZUULYXAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8yMDIyMTExN0wr3t3BuGNP8ISnEmlVocFkbGxhdRcAAHRkdE22CAAADQAAAEVs + ZWN0cmEgQmFzaWMAAAAABwAAAE1vZGFydHRNAAAAVGhlIGRlZmF1bHQgcHJlc2V0IGZvciB0aGUgRWxlY3RyYS1QaWFubywgY2xvc2VzdCB0byB0aGUgb3JpZ2luYWwg + aW5zdHJ1bWVudC4AAAAARAEAAA0AAABFbGVjdHJhIEJhc2ljAAAAACQIAABHUlVVAAAABgAAAAEDwE1UcmsAAAgOAP8CI0suUC5SYXVzY2ggLSBFbGVjdHJhIENhbG1h + IFBhY2lmaWNhALBAfxSQRzYHLSyEfIBHBjuQPDGCYTkrgk9ALoVXRzuCIYA5DYZEPANBQBBbkDkhgQKARxGEWJBIQ4FAgDkOggMtH4FvkEcsIIBID4RAsEAAQ4BHFiuQ + RTUQKSmBR7BAf4NHgEUQhiaQPEGGAjU6iACANQ2BXDwJa5A1LoQKgDUQgSSQPCB9gCkVJzwKgTaQPjyBDoA+FYEmkEA4gTiAQBBvkEEzgmiwQAAdkENHBSY1AIBBEIJU + sEB/gjCAQxAskEEmgkKAQRUbkEAsgmoyIYQVgEAIYpA+MkeAMgqFMZAyMoEygD4KhFGQPENYgDwQhDyQPlyBHYAmCoEgkDw2EoA+DkYyC4FlPBUKkDtJhRKwQAA2kCgx + giywQH+DDZA8Qw2AOxKCLzwaAZA7RIJ2NDCEFoA7E4EWNA0NkDktgT6AORKBHpA7O4Is0AEJAQsBBwEJAQeQNDSIa4AoCxs7D3E0EYkcsEAAOpA1MhZFOwApNAZBMIMb + sEB/ZoBFEhxBBXGQRxUkQxSBIYBDDABHEAE1CoE9kEgtBEUggms1MYMTgEUHCEgEgiGQRTIgQScYgDULikGQNR8MgEEOHUUVhQCQRUQVQSpSgEEVD0UfhCCQRUkbQSk5 + gEEbGkUmECkUcTUXgkywQABwkCs6C0dSDENCgmawQH9ugEMFbkcQPpBIIgNFGoFIgEUMJEgQgQGQSioERyuHL4BKARZHBxGQNyeFUkNDhBuAQwmBBpBPUINYgE8SgRQr + EjCQTzpigDcShBxPCA2QTTiENrBAAHiQTDYDgE0QD5AtM4FqsEB/gweATBJTkEgighGASBEmkEVCgkc5OoI5gEUJgl+QQzCBJ4BDGIFJkEhhgTmASBWDXTkKgQGQRSWC + QoBFFTSQQDSCEjk7hgw+NkCAQAuCZC0Ufj4FfJBDQQCAOQ4SkC04hFOAQwtLkEAkgnqAQBMOkD4/gTaAPhGBCJA5MIJAgDkSglyQOUGJBoA5FIEekDlBgTSAORqEE5A5 + Y4FbgDkQgSwtC4FzkDc+foA3H4NSsEAAdpApKwsdOxY5UARAOxE8RQA1PIEIgDUPGTkUEDwTRkASCh0PCCkLjDiQKTcDHT4QQCgLPDAANTgAOTaDMrBAf4o5gB0LgQM1 + Dw0pIhY5GT48GhVAHYotsEAABZA0KxAoOgA4KRU8MwA+MRFBLoEAgDwHADgSFT4NCzQPCCgVA0EUjXSQNCUQKCwKOC4bPDgCQSkAPi+BSrBAf4gggDQNgXY4DwI8DQMo + Gx1BEgQ+DYotkCEtArBAAA2QKClXLUEJRz4EQEAMPEkAQzuBE4A8EwMoGTZAEjVDEyZHDYEBLQ4lIRGBV5BDJAVAIwJHIgA8IW6APBEtQBIkQw8ARw2DP5BDNgNHPQBA + PxA8TIFegDwOLkAWFkcSFUMWgmKQPCIAQxwCQCEJRyFlgDwPI0AIE0cIEEMIg1iQR1EAQE8AQ0kFPGaBWIA8BiRABytDFwBHFYJtkEMkAUcpBEApAjwuWYA8FytAEgBH + ChJDC4N7kEBkBENYA0dbADxxgU+APAY9QBULRxEJQxaCZ5BAPgI8QwVDMwRHN2uAPAkQQwkSRw0FQBODUZA+RgJAPghFQwAmQgZCNoEdgD4KDkAKcUULBEIRgnCQRSEF + QCAAQhuBFoBACS5FDBBCCIMtkEIrC0UzAEAuBz4pdIA+DCZAD0BCDw1FEIM1kEIlB0UtD0AqAj4kgT+wQH+FQ4BFBRs+BxBACiZCCYJckDI6gmOAMg2CIJA+RVaAJg6B + Y5BFPoIwQj6BDYA+Bi5FEBdCFYJrsEAAaJAhQxBDQANAQQ08QBtHK3mAPAY7RwULQAwwQxqDeJBDDoEBgEMQg1KQQFAAQ0kAR0sLPGCBB4A8DzFADSNHEgtDGoNCkEMs + EkAnCTwbC0ccK4A8FxBAFwVHFgJDE4QRkENQAkBRA0dQADxlgQGAPBI1QA8oQw8FRxGDG5BDJwtAJwtHHAU8GTWAPBQLRxcXQxAJQA6ED5BAQARDPAA8UANHPnaAPBkb + QBoGIRAXQxYARxEQsEB/g1SQQDgMPDMWQx0ugDwZD0McI0AigkKwQACCKpBFNw9CJwA+MgBAMQAmJgoyGoEdsEB/gwSAQAUSPgE2QgYiRQWHD5A5J4EfPi+BCUI5gQOA + ORkUPg+Bb0ILCZBFRoQDgEUegSSQRzSCJIAmEoEQMg+BdpBFNR2ARwdCRSiIbrBAAIFFkCkvIDUvAEAwADwtBDk2gUOwQH+Cb4A1C4FZORc8PAlKQA+DO5A1LYRgQTOC + KIBBDYJzkExlgi2ATAsTkEpBggeASgYwkEhJgjCASBwZkEc1giOARxdGkEU+gQmARSKJf5A+PoEwgDUKiUuQNRiBXYA+BYkAkDwgR4A1CWQpCooxkD4aPYA8B4UfPhGH + e5AfLgqwQAAQkDlAEz40dzsdGoA5D4EysEB/hVKAPgUOOxGMCpA+IAFHHQVDIYEsgD4IHUcMDUMNglgfD58ekCEiVrBAAFuQLR5FNxZOPCIDORsQQC2BO7BAf4FOgDcL + gSQ5DGs8BDZADIYskEgzgmqASAdwkEoqg22ASgMAkEwkgUeATBGCHJBPIYFhgE8QgjeQVCeBa4BUDYIXkFY0gROAVg+DMJBYLoEDgFgPg2uQWyljgFsOhE2QYDRegGAO + hAgtChshENU8sEAAAP8vAFByc1WvDgAARwAAAAcAAABlbGVjdHJhAAAAAA0AAABFbGVjdHJhLVBpYW5vAAAAAAAAAABwcm9tXgAAAAEAAAABAAAAAQAAAAcAAABlbGVj + dHJhAAAAAA0AAABFbGVjdHJhLVBpYW5vAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP// + //9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA + AAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAAAAAIIDAAAAAQAAAAAAAAAAAAAAAwAA + AAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAACAPwAAAAABAAAAAAAAAM3MzD0DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEAAAAA + AAAAAAAAAAAAoMAAAKBAAADYQgAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAQMEAAEBBAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAA + AAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAABAAAAAAAAAAIAAAAAAAA/AACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGBY + AAAAAACoQQAAgD9YAAAAAACAv4EAAEDAAgAAAMAAAIA/AACAPwIAAEBAAAAAAAAAAAACAABAQAAAAEAAAABAAwAAgL8AAADAAAAAQAAAAEABAABAQAAAQEACAAAAQAAA + AIAAAAAAAwAAgL8AAIA/AAAAwAAAAMABAAAAQAAAAECBAABAQBUAAADAAABAQAAAAIAAAIA/AAAAwAAAQEAAAABAAACAPwAAAEAAAIA/AACAvwAAAAAAAABAAACAPwAA + QEAAAIA/AAAAwAAAAIAAAABAAABAwAAAAIAAAAAABgAAgL8AAADAAAAAQAAAAMAAAIC/AAAAgAAAAIADAABAQAAAQMAAAIC/AACAvwEAAEDAAABAwAMAAABAAACAvwAA + AMAAAADADwAAAEAAAIA/AACAvwAAAEAAAAAAAAAAQAAAAIAAAIA/AAAAQAAAAMAAAACAAACAvwAAQEAAAABAAAAAwAAAAMAEAACAvwAAAAAAAIC/AAAAwAOAAABgAgAA + AAAAqEEAAK5CAgAAAAAAgD+BAgAAAAEAAABxAAAARmxhdCB0ZW1wZXJhbWVudApBbiBlcXVhbCB0ZW1wZXJhbWVudCB3aGVyZSB0aGUgb2N0YXZlIHJhdGlvIGlzIGV4 + YWN0bHkgMiB3aGVuIHRoZSBvY3RhdmUgc3RyZXRjaGluZyBpcyBzZXQgdG8gMS4MAAAAAADIQgAASEMAAJZDAADIQwAA+kMAABZEAAAvRAAASEQAAGFEAAB6RACAiUQA + AJZEAAAAAAAAAAAEAAAAAADbQzwAAABFAAAARgAAAAB/AAAAAAAAAAcAAABEZWZhdWx0AAAAAAAAAAAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAA + AHo2kz8BAAAAAQAAAAEAAAAYAAAAA4AAAFAAAIA/AQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACA + P4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3M + zD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIA/A4AAAGAC + AAAAAACoQQAArkICAAAAAACAP4EAAPBBA4AAAEAGAAAAAACoQQAAcEIAAJBCAACoQgAAwEIAANhCBgAAAAAAAACBAAAAQAMAAKBAAAAgQQAAQEEAAAA/A4AAAEADAAAA + AACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/gQOA + AABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AACAPwOAAABg + AgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAoAAABMb25nIFBsYXRlCQAAAGxvbmdwbGF0ZQAA + EMEAAMBBAACAPwAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD8AAKpDAAAAAAAAgD8BAAAABgAAAAAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+C + A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAQAAAAAAAAABAAAAAAAAPwAAgEADAAAAAACAPwAAgD8AAIA/ + A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAA + gD8HAAAAAACAP5qZmT7NzEw/MzOzPwAAgD4IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAA + QAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAAAQAAAAAA + cEIDgAAAUAAAAAABAAAAAAAAAAAAAD4DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAA + AAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAAA8EIAAAAADgAAAAAAAD/NzMw9zcxMPgAAAD8CAAAAAAAA + AAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAACAPwQAAAACAAAAAwAAAAQAAAABAAAAAQAAAAAAgD8AAIA/AQAAAAEAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAAAgEAA + ALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAAAAAA4AAAAQAAAAAAAAAAAAgEAA + AIA/AAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAALAAAAAAAAAAAAAAAOAAAAEAAAAAAAAACamRk/AAAAPwAAgD8A + AIA/AAAAQQAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAACAAAAAAAAAAAAAAADgAAABAAAAAAAAAAmpkZPwAAAAAAAKBAAAAAvwAAAL8A + AEA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAACz7zI/AAAQwQAAAAADAAAAAQAAAAAAAAABAAAABQAAAAQAAAABAAAAAwAAAJqZmT4A + AIA/AACAPwAANMMAAAAAAAAAAAUAAAAAAAAAAADwwQAAwMIAAMDCAADAwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAA + AAAABAAAAAEAAAADAAAA16NwPwAAgD8AAIA/AAA0wwAAAAAAAAAABQAAAAAA8MEAAAAAAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAACPwvU/PQpXvx+Faz8AALRCAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAADMzM79cj8I+rkdhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIA + AHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAuB7FPz0KVz+uR+E/AAAAAAAAAAAA + AAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAD + AAAAUrgePwAAgD8AAIA/AADAQQAAAAAAAAAAAAAAAAEAAAAAAAAAEThWOGMYAABQclZLJgAAAlhGVFBTGAAACgAAADIwMjMvMDYvMDYXAAAAUGlhbm90ZXEgOC4wLjAv + MjAyMjExMTfPNnIYralhZ+MJMhdaWENsZGxsYRIYAAB0ZHRNJggAABEAAABDbGF2aW5ldCBENiBCYXNpYwAAAAAHAAAATW9kYXJ0dFAAAABUaGUgZGVmYXVsdCBzZXR0 + aW5nIGZvciB0aGUgQ2xhdmluZXQgRDYgZmVhdHVyaW5nIGEgY2xlYW4gYW5kIHVucHJvY2Vzc2VkIHNvdW5kLgAAAABEAQAAEQAAAENsYXZpbmV0IEQ2IEJhc2ljAAAA + AIkHAABHUlVVAAAABgAAAAEDwE1UcmsAAAdzAP8CCyBLLlAuUmF1c2NoAJArXgofXj2AK2ItH2ODDJA6VQBFZwQ+YoENgD5CEDokb5A+ShE6GoFMgEUvEzoiFj5DWJAm + bC2AJmZDkD5oC0NkETo0c4A6BxM+OhZDRYMAkCtMHR9bgSGAK0whHzyCKpBBcgk6YgY3Z4MjJm8DgEEyJDckBDpCDiZZgRmQOmENQGoAN2GDTCtHC4A6IQY3HhKQHz8P + gEAtWh9DGCtFY5A6XQk3VAlBb4F/gEE3Kzo0Ojcyf5ArYwcfY4JYgB87DCtHU5A6Xhc3XQU+YU2ANz4HOkEnPk49kDVRVIA1SoE+kCZnK4AmZYE4kDxiBEBwCTlngQWA + OU0AQEYLPFMukDdOXIA3TIELkB9EACtaUIAfYQArWIEtkDlrBkFyBjpjCj5iTYA6QwU+KxNBRAY5SoJHkDlsAEFsBjpbBD5kToA6Qw1BSwA+VAs5TYJTkEBrADxkCzdm + ADlngVqAORoWN0QNPEgEQDaCDJArYQQfaHE9KhGAPUMWK1wMH1GCCpBFYAc+YQQ6T4N8JnAXgDocAEVBFCZlEz5JgTiQOl0APmIAQ2tsgD5LADo8HkNOg0iQK1cLH1Nb + gB9lFSthgxKQQXIHN2YAOmCDNoBBPhE3MQY6RQWQJmongCZngUCQOlwEQGgDN1qDSx9RFitOBYA3HR46IgBANzgfZAorT4ERkDpdBUFxBjdagheAQTgaOjNWNzRMkCtu + CB9nglSAHzUVKztqkDprDT5pADdiXYA6LBE3SCs+USiQNVVSgDVYgSKQJnE4gCZjgTiQPGYGQG4AOWl/gDlLDUA+CTxRI5A3U1KAN1mBCJArUwYfUCCAK2AFH2WBX5BB + bAA6YQA+ZwA5bFSAOjAWPjVvkDpABT5AYYA6RgBBOgQ+Qws5OnKQJm03gCZWgTyQOWsGQGwAPF8FN2WDAoBALQU3LAY5MBE8QCGQH10AK1dDgB9iJStHd5A8YABAaQA5 + ZgQ3XoEogDcpDTkwKzxBEkAygTiQJGEEMGSDJIAwOgMkLD6QP2AFOmd7gDpPBj8/PZA3PkqAN0iBXpArZiyAK2aBDZA8ZQBBbIEcgDxUJUFBOJA5T3CAOUt1kCRBCzBG + SYAwWRwkRnWQOlMGP1CBGoA6Py8/KgOQN0VhgDdDgSCQK2Q9gCtRgQeQQWUFPFyBLIA8TRhBPjKQOUxdgDlGOJAwRykkLzCAMEQIJBh3kDpZAD9YgVw3UkaAOik1N1Ve + PyZEkCRrADBngwqAJDULMEVQkEtjBEZsaIBLRg1GWBOQQ1NbgENKgUWQK24xgCtUgQyQRmMLSmyBGoBGQgBKTFyQQUtigEFNcJAwUwAkU4ENgCQ/ADBIWJBIXhFDS4Eh + gEhBOZA/RjeAQ1lEP011kCtie4ArT26QP14HQ2uBQoBDSieQPFsQgD8rbjxRgRmQJFsGMFaBCYAwUh4kPyWQOmUGN2wNP2FugDo+Ez8YGzcqgjWQJG0IMGeDQoAkLAgw + Qg+QOmUAP18pN0x1gDpVCj8/azdVgS+QK2YvgCtjgReQQXEFPG0+OTclgDknPTxWBkFCX5A5Ji2AOTaBAJAkSBwwPkmAMD4LJDdzkDpZAD9VgUCAOkIAkDdLEoA/KGk3 + RHOQK2RfgCtRgQGQPGELQWeBNoBBMQg8RhCQOVB2gDlKXZAkRQYwN0KAMCk5JCMjkDpNKz8/ajdYQ4A6QT03TQA/NoIFkCRjADBhgxhDYQU/TQY6SACAMDwWJCduOjwA + PzcaQ0iCR5A6OgA+UARBTIFRgDoiKz5AGEFAgV6QK2xegCtggzKQOlIGP1MAQ2SBMoA/Hh06JRtDOoFIkDBjByRcX4AwXQskX4E4kDpAAEFVAD5VggWAOh44kCtnEYA+ + JSFBOxorWIEokD9OETpUGENqgUeAQ0gjP0dCOgiBOJAfcAYrYYJ7gB9EACtLXpA6VRI3XQk+W1+AOkMAN2EAPjgykDVda4A1XYEnkCZxOIAmXIEikEBsADllBDxfgQ2A + OU8GQEELPEomkDdKRIA3UYEPkCtQBh9OPoArWBwfU4EwkEFyADphBj5ngQiAPkQNQUMAOjo9kDlHPoA5Q4EjkCZoRIAmTYElkDleADxWBkBfgR6APEUAQDgAOUchkDdN + b4A3S4EAkCtHJx8kOIAfORErQDKQOlQGPmIYN1CBBIA3MiU+NAg6HEGQNTthgDVDgQCQH2gEK2qDAoAfNxArSBeQOnQKN3QGNWsRPlcngDowBjc3Cz5WEjU/e5A1PgQ6 + QRA3Tww+PjKAN1QAPjYAOkUANUeBEZAmaz6AJk2BJZA6cgA3cgw9VQA+Zgc1YBKAPVYkOioXNzwANTQuPiA0kDUtDTdQFjo+RIA6QQs3VBA1PmeQH0gNK0dbgCtRAB9d + gQ+QOl8AN2kAPmcANV+CLoA+GBg1HQ86CQA3JoEGkCZySYAmRYEXkEBpADlkBTdhADxcgV6AQBVQPBUUNw8AOQKBHZArUwcfVGOAH2AcK0w3kDpgDD5rBzdkADVaYYA3 + Sw06PA01QxQ+SwD/LwBQcnNV3A8AAEcAAAADAAAAY2wxAAAAAAsAAABDbGF2aW5ldCBENgAAAAAAAAAAcHJvbVgAAAABAAAAAQAAAAEAAAADAAAAY2wxAAAAAAsAAABD + bGF2aW5ldCBENgAAgD8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAAAAAAAAAAAAAAcnlhbBAAAAACAAAAAAAAAAAAAAD/////b2xvcxEAAAABAAAA + AQAAAEYAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAA + AAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAAACCAwAAAAEAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAD + AAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAAAAEAAAAAAAQAAAMGowMDihgE9A4AAAGACAAAAAACoQQAAfEICAAAAm+ghPwHCYso/AQAAAAAAAAAAAAAAAACg + wAAAoEAAANhCAAAAAAOAAABQAABwQgEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAA + YAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAEAAAAAAAAAAgAAAAAAAD8AAIA/A4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYFgAAAAAAKhBAACA + P1gAAAAAAAAAV4mIiL3v7u4/zcxMPyIior+rqqq+zczMvu/u7r4iIiLAzczMv6uqqj/e3d2/mpmZP+/u7r+8uzvAAABAwAAAQL4AALC/AADgPgAA4L8AADzAAABIwAAA + oL4AAAC/AABswAAAeMAAAETAAABQwAAAXMAAAGjAAADovwAAwMAAAMbAAACswAAAZMAAAJjAAADewAAAxMAAAOrAAACQwAAA9sAAAPzAAADCwAAA6MAAAM7AAAC0wAAA + +sAAABDBAACmwAAArMAAAPLAAAD4wAAA3sAAABLBAAAVwQAA8MAAACvBAAD8wAAA4sAAAATBAAAHwQAAGsEAAC3BAAAgwQAANMEAADjBAAAcwQAAEMEAACTBAAAowQAA + PMEAADDBAABUwQAAOMEAAGzBAABAwQAAdMEAAEjBAABcwQAAYMEAAGTBAAB4wQAAXMEAAHDBAAB0wQAAWMEAAHzBAABwwQOAAABgWAAAAAAAqEEAAIA/WAAAALs+Zj9X + cSVeP2aedT8bGWo/Vg8/PxmbQj9EvyQ/ws5BP1nEQD8H1R4/63IrPyuFJT+auUc/Qz5WP8scGT+XThw/8pkPPxwzCD8VFBQ/3UorP/1GIT8qnjI/Lf0VPySqND/VkDQ/ + D5MAP6rp8z4Cfto+OXXWPuWSID+/3NU+65gPP5EAAj8m5yQ/Bm/TPtyK3D623vg+58LkPue3Aj8hORc/XbgIP7jn5T4GIAM/zWXVPmEK+D7ZuSg/RHQNPyqv6D4VfS0/ + gwkNP9AkCT8c2BM/Lf8iP7Xu3D7rdiw/F10YP05oDj8D2g4/anvUPhC5MD/fIhs/5VoVP/9mMj/ZzzE/dcnbPg5sGD8K8ik/LhEqPxd85j6GPgM/u3MdP/5r4T5sSAw/ + perrPkgYED/FZAI/BW3aPvEXIj9awxE/3wwcP85h0j4aqt0+SE0mP/9mIz+77xo/KfEHP5U7HT/zPiI/AgAAAAEAAABxAAAARmxhdCB0ZW1wZXJhbWVudApBbiBlcXVh + bCB0ZW1wZXJhbWVudCB3aGVyZSB0aGUgb2N0YXZlIHJhdGlvIGlzIGV4YWN0bHkgMiB3aGVuIHRoZSBvY3RhdmUgc3RyZXRjaGluZyBpcyBzZXQgdG8gMS4MAAAAAADI + QgAASEMAAJZDAADIQwAA+kMAABZEAAAvRAAASEQAAGFEAAB6RACAiUQAAJZEAAAAAAAAAAAEAAAAAADcQzwAAABFAAAARgAAAAB/AAAAAAAAAAcAAABEZWZhdWx0AAAA + AAAAAAAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAAAM3MjD8BAAAAAQAAAAEAAAAfAAAAA4AAAFAAAIA/AQAAAAAAgD8AAAAAAACAPwOAAABgAgAA + AAAAqEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAA + AAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACA + P4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAACBCA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAAA/ + A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIA + AAAAAIA/gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ + AACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAwAAABTdXBlciBTcHJpbmcJAAAA + dnNwcmluZ2NuAABwwQAA8EEAAIA/AACAPwAAAAAK1yM8AACgwQAAAAAAAAAAZmamPwAAqkMAAIA/AACAPwEAAAAGAAAAAAAAAAAAgD8DgAAAQAMAAAAAAKhBAABwQgAA + 2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAAAAEAAAAAAAA/AACAQAMAAAAA + AIA/AACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAA + AACAP4IAAIA/AACAPwcAAAAAAIA/AAAAPwAAgD8AAMA/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMA + AAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ + ggEAAAABAAAAAABwQgOAAABQAAAAAAEAAAAAAAAAAAAAPgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ + ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+ + AAAAPwIAAAAAAAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAcAAAAIAAAAAwAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAA + gD8AAAAAAACAQAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAAAAAADgAAABAA + AAAAAAAAAACAPwAAAEDNzEw/AABwwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAQAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAA + AD/NzEw+AABAPwAAgD8AAKBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIA/AAAAAAAA + cEEAAIA/AACAPwAAAL8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAJsgGj8AAEDBAAAAAAMAAAABAAAAAAAAAAEAAAAFAAAABAAAAAEA + AAADAAAAAACAPs3MTD4zM7M/AAC0wgAAtMIAAAAABQAAAAAAAAAAAPDBAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAABmZmY/zcxMPjMzsz8AALTCAAC0wgAAAAAFAAAAAADwwQAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4VrPwAAtEIAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAA + QMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H + 4T8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEA + AAAAAAAAAQAAAAMAAAAzMxM/zcxMPjMzsz8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY41g8AAFByVksmAAACWEZUUMYPAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFu + b3RlcSA4LjAuMC8yMDIyMTExN+9v8Jh/QugDbiQ0EmVE+dFkbGxhhQ8AAHRkdE1iAgAADQAAAENlbGVzdGEgVHJlbW8AAAAABwAAAE1vZGFydHQgAAAAVGhlIENlbGVz + dGEgd2l0aCBhIG5pY2UgdHJlbW9sby4AAAAARAEAAA0AAABDZWxlc3RhIFRyZW1vAAAAAP0BAABHUlVVAAAABgAAAAEDwE1UcmsAAAHnAP8CDyBCbGF6IEp1cmpldmNp + YwCwQH+CQIA1AIE3OgAoPgAdkDVKgQs6Uw2ANQCBG5BBVQCAOgCBC5A6WFOAQQAdkDw/KIA6ADmQNUWBCTpaD4A1AGM8AACQQU0AgDoAgQpBAGOQQVBhgEEADZBGV4Eb + TUYagEYAcJBGTACATQAOkEhAgQuARgA3kEExRUZJKoBBAGNGAA+QTVYAgEgAgRhNAH2QTSVGUlkNgE0AcJBZUxuAUgCBDZBUMziAWQApVACFLJBeT4EJZUoAgF4AgRuQ + YFo3gGUAgmpgAIwzJACOB5BFPWFGV2KARQA2RgAskEhhDbBAAIRzgEgADZBGTIJ6gEYARJBEOoMWgEQAG5BDRYJ3gEMAY5BBKotuNUSFcTw6gRqwQH9FgEEAgneQPUSE + PkQ2g3OARABwkEUyhDtMPoNMgEUAD0wAAJBNSYNmgE0AD5BUSXyAVACEBJBVTIFSgFUAgz6QXE+BRIBcAIRxkF0XgRqAXQCHT5BkMYFtgGQAjVqQZTSUS4A8ADc9AHw1 + AEZlAJVMJEAANUAAOkAAPEAAPUAAPkAAQUAAQ0AAREAARUAARkAASEAATEAATUAAUkAAVEAAVUAAWUAAXEAAXUAAXkAAYEAAZEAAZUAAsEAAAP8vAFByc1UTDQAARwAA + AAcAAABjZWxlc3RhAAAAAAcAAABDZWxlc3RhAAAAAAAAAABwcm9tWAAAAAEAAAABAAAAAQAAAAcAAABjZWxlc3RhAAAAAAcAAABDZWxlc3RhAACAPwYAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAA + AAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAA + AAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAAAAAIIDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEA + AAABAAAAAACAPwAAAAABAAAAAAAAAAAAAD8DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAADYQgAAAAADgAAAUAAAcEIBAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAA + ANIEAAAAAAAAAAAAAAAAAIAAAAA/AACAPwOAAABQAABwQgEAAAAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIA + AAABAAAAcQAAAEZsYXQgdGVtcGVyYW1lbnQKQW4gZXF1YWwgdGVtcGVyYW1lbnQgd2hlcmUgdGhlIG9jdGF2ZSByYXRpbyBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0 + YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRvIDEuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8 + AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAAAAAAAAAAAAAMA/AQAAAAEAAAABAAAAGAAA + AAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQ + AAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAA + gD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAHBC + A4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIA + AAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAA + AAAAAACBAAAAAM3MTD4AAAAAAAAAAAAAAAAAAAA/mpkZPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEA + AAABAAAAAQAAAAoAAABTbWFsbCBIYWxsCwAAAG1lZGl1bWhhbGwxAABAQAAAoEEAAIA/zcxMPwAAAACPwvU8AAAAAAAAAAAAAAAAAAAAAAAAqkMAAIA/AACAPwEAAAAG + AAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAA + AAAAAIEBAAAAAAAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/AACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAA + AACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/AACAPwcAAAAAAIA/mpmZPs3MTD8zM7M/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ + ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAAABAAAAAABwQgOAAABQAAAAAAEAAAAAAAAAq6qqPgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAQAAAAAA + AAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEA + AAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAAAAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAAIAAAABwAAAAEAAAABAAAA + AACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACAQAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAA + AAAAAAAAAAADAAAAAQAAAAEAAAAAAAAADgAAABAAAAAAAAAAAACgQAAAgEAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AwAAAAAAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAA + AAAAAAAAAAAOAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAJsgGj8AAEDB + AAAAAAMAAAADAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAAmpmZPpqZmb4zM7M/AAAAAAAAAAAAAAAABQAAAAAAAAAAAMDCAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAABmZmY/mpmZvjMzsz8AAAAAAAAAAAAAAAAFAAAAAADAwgAAAAAAAMDC + AADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4VrPwAAtEIAAAAA + AAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAA + MzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAMAAACamRk/mpmZvjMzsz8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4whQAAFByVksmAAAC + WEZUULIUAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8yMDIyMTExN8K8Y21jYwHxWLn3wNVPYWFkbGxhcRQAAHRkdE1CBwAAFgAAAEdsb2NrZW5zcGll + bCBIdW1hbml6ZWQAAAAABwAAAE1vZGFydHSBAAAAVGhlIHNhbWUgYXMgR2xvY2tlbnNwaWVsIE9yaWdpbmFsLCBidXQgd2l0aCB0aGUgc3RyaWtlIHBvaW50IHZhcnlp + bmcgcmFuZG9tbHkgYXMgcGxheWVkIGJ5IGEgcmVhbCBtdXNpY2lhbi4gUmVjb21tZW5kZWQgZm9yIHNvbG8uAAAAAEQBAAAWAAAAR2xvY2tlbnNwaWVsIEh1bWFuaXpl + ZAAAAABqBgAAR1JVVQAAAAYAAAABA8BNVHJrAAAGVAD/Ah9CYWNoIC0gUHJlbHVkZSBmcm9tIFBhcnRpdGEgTm8zAJBkToJAgGRAAJBjR4IjgGNAAJBkToIagGRAgiaQ + X0KCLYBfQII4kFxHgkSAXECCRpBfToJKgF9AglOQWFiCVYBYQACQWiiCT4BaQACQWF2CRIBYQACQV1KCNoBXQACQWFmCIIBYQIILkFNMghaAU0CCIZBQToIxgFBAgjmQ + U1aCRIBTQIJLkExegWmATEBjkFMmgWaAU0BikE41gV6ATkBckFMqgVWAU0BbkFA6gUWAUEBSkFMugTyAU0BQkFE+gUOAUUBVkFMugU6AU0BZkFBBgVqAUEBakFMpgVyA + U0BikE5AgWKATkBhkFMggWWAU0BdkEw7gV+ATEBfkFgygVmAWEBckFcugVCAV0BWkFUwgUSAVUBTkFM7gTuAU0BOkFhOgUGAWEBUkFdHgUaAV0BXkFVGgUyAVUBdkFNI + gViAU0BZkFFOgV2AUUBgkFBUgWCAUEBikE5dgWKATkBfkExmgTmwQ38sgExAXZBTHoFdgFNAXpBOMoFSgE5AW5BTI4FIgFNAUpBQOIE5gFBATpBTKIE9gFNAUZBRPIFE + gFFAVpBTKoFMgFNAWpBQQIFVgFBAW5BTKIFdgFNAYJBOPoFggE5AYJBTHoFigFNAYZBMOIFjgExAXJBYRIFbgFhAW5BXR4FSgFdAWJBVQ4FDgFVAUJBTOYE1gFNAUJBY + P4E5gFhAVpBXOYFBgFdAVpBVT4FHgFVAWJBTRYFRgFNAWJBRNYFWgFFAWZBQQYFXgFBAYpBOQYEKsEMAUoBOQGCQTEWBXYBMQFuQTj6BXoBOQGCQUEWBVoBQQFuQUTGB + VIBRQFeQU06BTIBTQFaQVUmBQoBVQFCQV1CBOYBXQEqQWEKBN4BYQFKQWl2BQoBaQFiQXEOBTIBcQF6QXWeBVYBdQFmQWk6BW4BaQF6QXE+BXIBcQGKQXzKBW4BfQF2Q + WDiBWoBYQFmQWkmBU4BaQFiQXFiBTIBcQFKQXTWBQoBdQFSQX02BN4BfQE6QYUmBM4BhQFCQY1yBQoBjQFSQZDuBTIBkQFiQYW6BU4BhQFqQY2GBV4BjQF2QZHGBVoBk + QF+QXzSBVYBfQFqQXVSBT4BdQFqQXziBSIBfQFSQXE6BQIBcQFKQXzqBN4BfQEyQXU6BN4BdQFKQXzSBP4BfQFKQXFOBR4BcQFaQXyqBSoBfQFaQWlqBUIBaQFeQXx2B + UoBfQF2QWGSBUIBYQFuQZD6BUIBkQFiQY1CBTIBjQFSQYTGBQ4BhQFSQX0SBPYBfQEyQWCmBNYBYQEuQYT6BN4BhQE6QWC+BPoBYQFSQXzyBQIBfQFiQWC+BSIBYQFeQ + XUGBToBdQFiQWCSBUYBYQFqQXEiBVIBcQFeQXxyBUIBfQFmQWkKBSoBaQFKQXyCBPYBfQEyQXDuBN4BcQFCQXyOBP4BfQFCQXTqBQoBdQFOQXyaBSYBfQFeQXDqBUoBc + QFuQXySBUoBfQFuQWjyBWIBaQF2QXxyBXYBfQFuQWEKBXYBYQGGQZC6BWIBkQFeQYzaBUIBjQFSQYTGBQIBhQE6QXy2BPYBfQE6QWB2BP4BYQFWQYTaBSIBhQFSQWCKB + SoBYQF6QXymBUYBfQFqQWCKBU4BYQF2QXSaBWIBdQF2QWBoesEN/gT+AWEBekFxBgVyAXEBgkFgigVuAWEBdkFhHgVeAWEBekFgpgVCAWEBZkFdNgUyAV0BVkFgugUeA + WEBQkFhQgTyAWEBNkFgvgT6AWEBQkFpSgUiAWkBYkFgqgU+AWEBakFdQgVSAV0BekFgigVqAWEBdkFhIgV2AWEBgkFg2gVmAWEBbkFxSgVOAXEBYkFgxgUiAWEBQkFpc + gTuAWkBUkFgwgUCAWEBSkFxJgUaAXEBYkFg7gUuAWEBYkF1NgU6AXUBckFhCgVOAWEBakFpQgVWAWkBbkFg2gViAWEBfkFxFgVCAXEBdkFgvgVGAWEBYkFg1gUyAWEBU + kFgqgUKAWEBQkFc7gUGAV0BRkFgrgUSAWEBakFhAgUqAWEBYkFglgVGAWEBckFpCgVeAWkBZkFghgV6AWEBfkFdBgV2AV0BjkFgqgWOAWEBfkFg2gWOAWEBdkFgXgV2A + WEBdkFwogVOAXEBWkFgfAP8vAFByc1UfDQAARwAAAAcAAABnbG9ja2VuAAAAAAwAAABHbG9ja2Vuc3BpZWwAAAAAAAAAAHByb21dAAAAAQAAAAEAAAABAAAABwAAAGds + b2NrZW4AAAAADAAAAEdsb2NrZW5zcGllbAAAgD8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAAAAAAAAAAAAAAcnlhbBAAAAACAAAAAAAAAAAAAAD/ + ////b2xvcxEAAAABAAAAAQAAAEYAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAA + AAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAAACCAwAAAAEAAAAAAAAAAAAAAAMA + AAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAQAAAAAAAAAAAAA/A4AAAGACAAAAAACoQQAAjEICAAAAAACAP4EBAAAA + AAAAAAAAAAAAAKDAAACgQAAAAEMAAAAAA4AAAFAAAHBCAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAKDAAACgQAAAAAADgAAAYAIAAAAAAKhBAACuQgIA + AAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BgAAAAAAAAADSBAAAAAAAAAAAAAAAAACAAAAAPwAAgD8DgAAAUAAAcEIBAAAAAAAAAAOAAABgAgAAAAAAqEEA + AK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4ECAAAAAQAAAHEAAABGbGF0IHRlbXBlcmFtZW50CkFuIGVxdWFsIHRlbXBlcmFtZW50IHdoZXJlIHRo + ZSBvY3RhdmUgcmF0aW8gaXMgZXhhY3RseSAyIHdoZW4gdGhlIG9jdGF2ZSBzdHJldGNoaW5nIGlzIHNldCB0byAxLgwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQA + AC9EAABIRAAAYUQAAHpEAICJRAAAlkQAAAAAAAAAAAQAAAAAANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAAAAAAAAIAAAADgAAAYAIAAAAAAKhB + AACuQgIAAAAAAAAAgQAAAAAAAAAAAADAPwEAAAABAAAAAQAAABgAAAADgAAASAEAAAAAAIA/AAAAAAAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABgAgAA + AAAAqEEAAK5CAgAAAAAAgD+BA4AAAFAAAHpEAQAAAAAAAAADgAAAUAAAekQBAAAAAACAPwAAAAADgAAAUAAAcEIBAAAAAAAAAAAAgD8DgAAAUAAAcEIBAAAAAAAAAAEA + AAAAAAAAAAAAAM3MzL3NzMw9AQAAAAAAAAAAAAAAAAAAvwAAAD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAA + AAAAAACBAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAAAgQgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAA + AIA/ggOAAABIAQAAAAAAAAABAAAAA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAAEAAIA/A4AAAGACAAAAAAAAAAAAgD8CAAAAAACAP4EDgAAAQAQAAAAAAAAAJpNJPv5+ + Pz8AAIA/BAAAAAAAAACBAACAP4EDgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAgQAAAADNzEw+AAAAAAEAAAAAAAAAAAAAPwAAAD8DgAAAYAIAAAAAAKhBAACuQgIAAAAA + AIA/gQAAgL8AAAA/A4AAAGACAAAAAADAQQAAkEICAAAAAACAP4EBAAAAAQAAAAEAAAAMAAAAQ2xlYW4gc3R1ZGlvCwAAAGNsZWFuc3R1ZGlvAABAQAAAQEEAAIA/zczM + PgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqkMAAIA/AACAPwEAAAAGAAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhB + AABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/AACAPwAAgD8DgAAAQAMAAAAAAKhB + AABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/AACAPwcAAAAAAIA/zcxM + PgAAAD8AAIA/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBC + AADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAAABAAAAAABwQgOAAABQAAAAAAEA + AAAAAAAAq6qqPgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAQAAAAEAAAABAAAAq6oqPgAAAD8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/ + AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAAAAAAA4AAAEgBAAAAAACA + PwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAAIAAAABwAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACAQAAAtEJGAAAAAAOAAABI + AQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAAAQAAAAAAAAAAAAAADgAAABAAAAAAAAAAAACAQAAAAEEAAAAAAAAAPwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAcAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAgD4AAAAAAAAAAM3MTL4AADRDmpkZPgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIA/AAAAQM3MTD8AAHDBAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAJsgGj8AAEDBAAAAAAMAAAADAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAAmpkZP5qZmb4zM7M/AAAAAAAA + AAAAAAAABQAAAAAAAAAAAMDCAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMA + AACamZk/mpmZvjMzsz8AAAAAAAAAAAAAAAAFAAAAAADAwgAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAABAAAAAAAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4VrPwAAtEIAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAAAAAAAAAFAAAAAABAwQAA + QMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAMAAABmZmY/mpmZvjMz + sz8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4ShEAAFByVksmAAACWEZUUDoRAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8yMDIyMTExNyDyrjVa + 6DVBp+yN8Qj6DzJkbGxh+RAAAHRkdE18AwAAFAAAAFRveSBQaWFubyBmb3IgQW1lbGllAAAAAAcAAABNb2RhcnR0aAAAAFRoZSBUb3kgUGlhbm8gd2l0aCA0IG1pY3Ms + IGEgdG91Y2ggb2YgZGVsYXksIHRyZW1vbG8gYW5kIGNvbXByZXNzb3IsIGFuZCBhIHNsaWdodGx5IGluY3JlYXNlZCBpbXBlZGFuY2UuAAAAAEQBAAAUAAAAVG95IFBp + YW5vIGZvciBBbWVsaWUAAAAAwQIAAEdSVVUAAAAGAAAAAQPATVRyawAAAqsA/wIZSkZlbGljZSAtIEFuIEVhcmxpZXIgVGltZZ5SkDwziU5MPwZHMZJOgEdAApBBLjSA + TEBEPECIRJBMLi5FLZIsPDdEgEFAHExABkVAiQqQRzQCTD6SAoBMQAxHQCaQQTBegDxAiEKQTD0gRSmJeIBFQDBMQIgekE9CAoBBQCKQPDSJDkw/AEc/NoBPQIkQkFFM + hjqAPECCTJBPSDKAR0AEkEExCoBMQDRRQIgskEU8GEw4jgCAT0CFEkxAHEVAJJBPRQQ8KoESgEFAiCSQRzQKTDVOgE9AiSCQUT6IZoBHQASQQS8CgExAAjxAIpBPRXKA + UUCHbJBFPjRMPyyAQUCPTkxAWEVABJA8NYRagE9AhCCQRzoITEyIIk1RSoA8QIcSTEBUTUAGkEA4CE9IIIBHQIgEkEcpGkoqCE1KgQCAT0CHMJBMTw6ATUCCMEBAhVBM + QAxHQA6QTUcKgEpACJA+PIhaSDUOSk8ERS0ggE1AjxQ+QIFWSEAckEMuPIBFQIhYkEcpkQyASkAUR0AIkE1JED4xLIBDQIkCkEgpAko+GEUkQIBNQIdqkE9HiAqASkAM + kE1HFkMtFIBIQBxPQBJFQDY+QIg0kEoZNEcOixyATUCFPpBNTgaASkASQ0AGkD44FIBHQIhakEUuAkpDDkguMoBNQIgGkE8/iBiASkAWkE1OBIBIQABFQBaQQzpggE9A + CD5Ah3SQSjoGRyuQIIBHQBKQPjUUgEpAKkNAWk1Ah0aQSkUKRSsCSC+IFkxFHoBKQIM+SECEGkVAJJBDPTCATEAEkE1BeIA+QIZWkEc0EExJeoBNQIdUkEpEDoBMQINa + Q0CDZEpADJBMSiKAR0AEkDwsiBZIR0yATECHPpBDISiAPECIXJA8LT6AQ0CRPkhABjxAAP8vAFByc1VtDQAARwAAAAgAAAB0b3lwaWFubwAAAAAJAAAAVG95IFBpYW5v + AAAAAAAAAABwcm9tWwAAAAEAAAABAAAAAQAAAAgAAAB0b3lwaWFubwAAAAAJAAAAVG95IFBpYW5vAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAA + AAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAA + AAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABw + QgAA2EIDAAAAAAAAAIIDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAACAPwAAAAABAAAAAAAAAAAA + AD8DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAQwAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAA + AAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAAAAAAAAAAAAAAAAIAAAAA/AACA + PwOAAABQAABwQgEAAAAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIAAAABAAAAcQAAAEZsYXQgdGVtcGVyYW1l + bnQKQW4gZXF1YWwgdGVtcGVyYW1lbnQgd2hlcmUgdGhlIG9jdGF2ZSByYXRpbyBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRv + IDEuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAA + RGVmYXVsdAAAAAAAAAAAAgAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAQAAAAAAAAAAAMA/AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAmpkZPwOA + AABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABw + QgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EID + AAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAABBCA4AAAGACAAAAAACoQQAArkICAAAAAAAA + AIEAAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAA + AACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAAAQAAAAAA + AAAAAAA/AAAAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAASAEAAAAAAIA/AQAAAAEAAAABAAAADAAAAENsZWFuIHN0dWRpbwsAAABjbGVhbnN0 + dWRpbwAAcEEAAEBBAACAPwAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAJqZWcAAAKpDAACAPwAAgD8BAAAABgAAAAAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAA + AAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAQAAAAAAAAABAAAAAAAAPwAAAEADAAAAmpmZPwAA + gD9mZmY/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+C + AACAPwAAgD8HAAAAAACAP83MTD8AAIA/mpmZP83MzD0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACA + P4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAA + AwAAAAAAcEIDgAAAYD0AAAAAAAAAAACAPz0AAAAAAAAAvAAAukIDgAAAYAkAAAAAAAAAAACAPwkAAAAAAAAAiAAAwEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAgQAA + QMABAAAAAAAAAACDn6qqPgOAAABABQAAAAAAcEIAAIZCAACQQgAAnkIAAKhCBQAAAAAAgD+EAQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEA + AAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIA + AAAAAAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAAIAAAABwAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAA + AACAQAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAABwAAAAEAAAAAAAAADgAAABAAAAAAAAAA + mpkZPgAAAAAAAAAA4XoUvwAASEIAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAAAAAA4AAAAQAAAAAAAAAAAAAEAAAABA + AAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAJAAAAAQAAAAAAAAAOAAAAEAAAAAAAAAAnMQA/AABAQAAAwEAAAKDB + AAAgQQAAyEMAAKBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAJsgGj8AAEDBAAAAAAMAAAADAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAA + mpkZPs3MzL4AAAA/AAAAAAAAAAACAAAABQAAAAAAAAAAAMDCAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AQAAAAAAAAAEAAAAAQAAAAMAAABmZuY+zczMvgAAAD8AAAAAAAAAAAIAAAAFAAAAAADAwgAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAHsULr97FK4/exQOQCeRH8MAAAAABAAAAAUAAAAAAAAAAABwwgAAcMIAAHDCAABwwgUAAACPwvU8 + AAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAA9ijcP3sUrj/NzAxA+h4iQwAAAAAEAAAABQAAAAAAcMIAAAAA + AABwwgAAcMIAAHDCBQAAAAAAAACPwvU8AAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAA + AAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAA + AQAAAAMAAABcj8I+zczMvgAAAD8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4nhYAAFByVksmAAACWEZUUI4WAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4 + LjAuMC8yMDIyMTExN5hcuMQeeHNV+5cma5Nf2eJkbGxhTRYAAHRkdE0oAgAAEAAAAEthbGltYmEgT3JpZ2luYWwAAAAABwAAAE1vZGFydHRFAAAAVGhlIGRlZmF1bHQg + cHJlc2V0IGZvciB0aGUgS2FsaW1iYSwgY2xvc2UgdG8gdGhlIG9yaWdpbmFsIGluc3RydW1lbnQuAAAAAEQBAAAQAAAAS2FsaW1iYSBPcmlnaW5hbAAAAACYAQAAR1JV + VQAAAAYAAAABA8BNVHJrAAABggD/AhFrYWxpbWJhLW1pbmktZGVtb4M3kDk5BEU5g2yARUAVkEgqgWqASEARkEc9gWiAR0AOkEhOg3WASEBZkEVHg1yARUAlkEg7F0A2 + hCSASEAfkEcsgRuAR0BGkEg2gS6ASEA4kEdAhBqAR0AbOUAMkEg9gWqAQECBSUhAW5A5QAJFPYQOSCYAgEVAgVFIQBuQRzWBcEhDEYBHQINxSEBCkEUsgzKARUBykEFJ + Ako8hFiASkAKkEclYYBHQHaQSDaBNIBIQDiQRzqEYkhABIBHQBE5QIIeQUCBFUhARpBFOww5PYRYSB0ugEVAgQlIQBuQRyyBcEhGMoBHQINPSEAukEUsg2KARUAKkEAv + GUgxhCSASEAZkEcigQaAR0BPkEg/gUuASEAOkEdDhA6AOUBVR0ACkEhAglyAQEBfSEBIkEU0Bjk3hDNIITqARUAuSEBEkEcygWZIPDCAR0CDO0hAY5BFOYMYgEVAgWSQ + QTICTDeLLoA5QIQgQUBZTEAA/y8AUHJzVRUUAABHAAAABwAAAGthbGltYmEAAAAABwAAAEthbGltYmEAAAAAAAAAAHByb21YAAAAAQAAAAEAAAABAAAABwAAAGthbGlt + YmEAAAAABwAAAEthbGltYmEAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MR + AAAAAQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA + AQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAAAAggMAAAABAAAAAAAAAAAAAAADAAAAAAAAAAAA + AAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAEAAAAAAAAA9yTNPQOAAABgWAAAAAAAqEEAAIA/WAAAAHPzfD9XD3Z9P0Eagj/6r4Q/ + EzqBP2Lpcz9mwoM/ic9/P1U+dD80woI//x+APzJCeT9BMXU/+VB3P+vwgz+2mIE/cmSEP3+qfD9+iYE/jId7P40/hT8Gh4M/rmmBP3FAgz8+soI/w36CP31rgz8/JYE/ + 8UKCP/QQez+pvXQ/noKAP/90hD+eQ4E/WfWCP8qTcz+vUnU/on2APwypgT+b43o/nGGBP/p5cz9m6HY/DP+AP5EdhD9VYnQ/Am+FP4GGfj/ipIE/RaN3P9g+gD/RpYI/ + HYSFP3rKgD+wCIQ/EqqBP1Dpdj9VhIA/4CyCPxlzdj/SWXc/Mx6FP704ez+E7H0/mGV1P7U6eT+dsIA/512FP0dWeD9Oz4E/esZ4P/TfeT9yi3c/8NyFPyv4gj+5Znw/ + 3yuCP82vej9ByoE/PEmDP986dT/NE4Y/YwZzPzhrhD8Qx4E/Dgd3P50GgT+w9oQ/AQAAAAAAAAAAAAAAAACgwAAAoEAAAOJCAAAAAAOAAABQAABwQgEAAAAAAAAAAAAA + AAAAAAAAAAAAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQA + AAEAAAAAAAAAAgAAAAAAAD8AAIA/A4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYFgAAAAAAKhBAACAP1gAAAAAAIC/gQAAQMACAAAAwAAAgD8AAIA/AgAAQEAA + AAAAAAAAAAIAAEBAAAAAQAAAAEADAACAvwAAAMAAAABAAAAAQAEAAEBAAABAQAIAAABAAAAAgAAAAAADAACAvwAAgD8AAADAAAAAwAEAAABAAAAAQIEAAEBAFQAAAMAA + AEBAAAAAgAAAgD8AAADAAABAQAAAAEAAAIA/AAAAQAAAgD8AAIC/AAAAAAAAAEAAAIA/AABAQAAAgD8AAADAAAAAgAAAAEAAAEDAAAAAgAAAAAAGAACAvwAAAMAAAABA + AAAAwAAAgL8AAACAAAAAgAMAAEBAAABAwAAAgL8AAIC/AQAAQMAAAEDAAwAAAEAAAIC/AAAAwAAAAMAPAAAAQAAAgD8AAIC/AAAAQAAAAAAAAABAAAAAgAAAgD8AAABA + AAAAwAAAAIAAAIC/AABAQAAAAEAAAADAAAAAwAQAAIC/AAAAAAAAgL8AAADAA4AAAGBYAAAAAACoQQAAgD9YAAAAup98P1cBy30/atmLP+RIiD9+3nQ/WG+AP1VNgD/V + QII/71FsP2XQdj9cT4w/2/pvP5DHjD9gX20/zqlqP5gcaD+jSYQ/V2l8PxSXiz9eSIE/oLBtP1Yncz/zQ4w/i7OKP07Cdz/6sHU/VGaBP4l2fz9ST34/Ks+CP4rEij+K + RWo/EmlmP8QAbT8PSIM/wHF8PxZRZz/aOnI/aJ1sPww+hT901Gs/sgRqP/Eqgj+QvHM/q/B7P3Cvhj8Kj3I/RCxoP2cdjD9Ufoo/GFOBP9q4fD+V1IM/b+NwP3ENdj+4 + voY/96ZoP1l+hT88vIk/PPuFP7fOgz+c83s/AAV4P9raeT/bQnk/WO5tPxRQhT/fNoo/IXGHPzs9iD8u63k/8nqDP914cD+uqYY/7TBsPyiMiD9wf2c/IIuGP6HFfj8T + pHk/X0iBP07+cD8Ld4Q/QIt8P774fz9P9os/UGR7P1Ybhz8CAAAAAQAAAHEAAABGbGF0IHRlbXBlcmFtZW50CkFuIGVxdWFsIHRlbXBlcmFtZW50IHdoZXJlIHRoZSBv + Y3RhdmUgcmF0aW8gaXMgZXhhY3RseSAyIHdoZW4gdGhlIG9jdGF2ZSBzdHJldGNoaW5nIGlzIHNldCB0byAxLgwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9E + AABIRAAAYUQAAHpEAICJRAAAlkQAAAAAAAAAAAQAAAAAANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAAAAAAAAAAAAADgAAAYAIAAAAAAKhBAACu + QgIAAAAAAAAAgQEAAAAAAAAAmpmZPwEAAAABAAAAAQAAABgAAAADgAAAUAAAgD8BAAAAAACAPwAAAAAAAIA/A4AAAEAHAAAAAACoQQAAQEIAAHBCAACQQgAAqEIAAMBC + AADYQgcAAAAAAIA/gs3MTD8DAAAAP5qZGT9mZmY/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOA + AABQAABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABw + QgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAACBCA4AAAGACAAAAAACoQQAArkIC + AAAAAAAAAIEAAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIA + AAAAAAAAAACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAACAP4EAAAAAgQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAA/mpmZPgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAsAAABKYXp6 + IHN0dWRpbwoAAABqYXp6c3R1ZGlvAADAwAAAoEEAAIA/mpkZPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPwAAqkMAAIA/AACAPwEAAAAGAAAAAACAPwAAgD8DgAAAYFgA + AAAAAKhBAACAP1gAAADN6mo/V1xfaj9Cx3A/Jq1oP5wRhT+hzHQ/bad3P2KPdj+pKXo/gkGJPxiTgz9HAYI/b6l9PxgXiD88cYA/NSR+P7Mifz9+0YI/AeF+PySfgz/W + PoU/T5B5Pz0waT/VLmo/p0tnP3S3iD86Knc/pqFpP/UMhz+eRnQ/n3OBP4SAiT/hxWo/Fwp4P337iz/rnmY/GLaMPzTacD9HZXc/aCaAP7mBgj//VIU/NHBwP3MrhT92 + ioM/+I2CP1BrbT+mFmc/5DKKP8sqbT/oBoc/wqd1P34fdj/7Z4Y/QMZvP75Vdz+KUos/L1t2P8XHgj/NTIQ/TrGBP7yeiT//62o/ufVzP17vhT8zloY/Kw5oP9Xfbz/5 + O3g/n1l7P0eydz/qEoA/x7+AP9oeiz9VLno/kI9oP65jej9Egm4/vTV+PyVeaj+K/ok/ccKBP+VMgD+o9G0/aeF1P8DMbD8Yy4I/6miAPwOAAABAAwAAAAAAqEEAAHBC + AADYQgMAAAAAAIA/ggAAAAADgAAAYFgAAAAAAKhBAACAP1gAAAC3Log+V4UTgr6uVZk+JnjzPSM6oj29ppG9BWC0PfL5ZD6I/Gm+B2MZvsRkDL79SVs+gww8PojVJL4F + DR68ilLJvSBM+b3J+JI+33Q6PZ6/9T1GuCK7h7r8vVZVCj5vJZA97q11vfmdcT7DGxW+fQ9pPh9MIT68sA2+aG+WPifiHj516cQ9KxOavUfKD77lc3K+PuiJPZIAjj7S + nZe+ODkuPiJ/5zwjvEo+GpuCPsnqfj3yiI29nmdZu/9OVr4ZS0Y+CYvRPQw7jbw99ZY+BmRMPiQvEr5Ztm6+ohz2O5gscL7c4mY+zhcCPiT/ZD2Ddnc+8WN1PTm/0zxx + 1Cw+Ubs+PlnmSb6YrHk9tC/7vR8pFr40FU697FaZPi2ghT6tbFU+MK2IPj5x8L009NO8/mUpPvCUcr6HQYG+i+A/Pm/smj3BGGI+6GYaO6fvVj5JffC9rlI1vnrjFz5W + qoO+E4gQvQEAAAAAAAAAAQAAAAAAAD8AAIBAAwAAAAAAgD8AAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMA + AAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD+amZk+ZmZmP83MzD/NzEw+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAA + gD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAQAAAAEAAAAAAHBCA4AAAFAAAAAAAQAAAAAAAAAAAIA9A4AAAGACAAAAAABUQgAADEICAAAAAACAP4EBAAAA + AAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/ + AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAAgD8EAAAAAgAAAAgAAAAHAAAAAQAAAAEA + AAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAASAEA + AAAAAAAAAAAAAAMAAAAHAAAAAAAAAAAAAAAOAAAAEAAAAAAAAADNzMw9AACAPwAAAAAAAAC/AABIQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADAAAAAAAAAAAAAAAAAAAADgAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAA + AAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAACAPwAA + QMEAAAAAAwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAAAAAAAAzczMvgAAwD8AADTCAAAAAAAAAAAFAAAAAAAAAAAAQMEAAMDCAADAwgAAwMIFAAAAAAAAAI/C + 9TwAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAM3MzD7NzMy+AADAPwAANEIAAAAAAAAAAAUAAAAAAEDBAAAAAAAA + wMIAAMDCAADAwgUAAACPwvU8AAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAj8L1Pz0KV78fhWs/AAC0QgAA + AAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMA + AAAzMzO/XI/CPq5HYT8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAEAAAABAAAAAwAAAAAAwD8AAIA/AADAPwAAkEEAAPBCAAAAAAAAAAABAAAAAAAAABE4VjiuEAAAUHJWSyYA + AAJYRlRQnhAAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3WLpc7mP5mZrC474hT08NA2RsbGFdEAAAdGR0TTgDAAARAAAATWFyaW1iYSBI + dW1hbml6ZWQAAAAABwAAAE1vZGFydHRmAAAAVGhlIHNhbWUgYXMgTWFyaW1iYSBPcmlnaW5hbCwgYnV0IHdpdGggdGhlIHN0cmlrZSBwb2ludCB2YXJ5aW5nIHJhbmRv + bWx5IGFzIHBsYXllZCBieSBhIHJlYWwgbXVzaWNpYW4uAAAAAEQBAAARAAAATWFyaW1iYSBIdW1hbml6ZWQAAAAAhQIAAEdSVVUAAAAGAAAAAQPATVRyawAAAm8A/wIP + IEJsYXogSnVyamV2Y2ljAIBGAIIgMgCBepBKIDywQAAngEoAKZBINoEAgEgAAJBGOoEzgEYAHZBFWIFKgEUAglyQRm2BdIBGAIEgkEhrggOASACBRZBJa4F7gEkAgimQ + SG2BfoBIADmQRlSBZ4BGAFWQRT6BHoBFAIEXkEhwLzQcgTOASACBYZBGWoEROi5bgEYAgSyQRWGBXYBFAGqQQyqBIIBDAHWQQSCBHoBBAIEDkEIqTIA6AHNCABWQRVhq + gDQAQkUAc5BEMoEsgEQAQpBCPIE9gEIAUpBDHHqAQwBFkEVAgSSARQBfkEY0gUKARgAwkEhGgSaASAAekC0dW0lQgVs3FgqASQCBPZBFJoEngEUAghGQSHCBcIBIAGqQ + Rl+BSoBGADaQRUqBNYBFAESQQy6BAIBDAFyQQkKBJIBCAFiQQSqBJoBBAE2QPyFbgD8AfpBAKmmAQAAvkEM8gTJBRAmAQwCBEUEAe5A5F16AOQBQkD0ygTFAIAqAPQCB + IkAAC5A/MoEkgD8AJpA9JlKAPQBukD48G4A3AEw+AFSQOTiBC4A5AEiQPDyBIoA8AEWQPj6BAIA+AGuQO0CBEIA7AB2QPDZlgDwAW5A7NoEagDsAQ5A3KoFYgDcAQpA4 + PoFKgDgAaZA5KEqALQCBZTkAO5BAQoMfgEAAN5A9OoVYJiKBMbBAf4NdkC0bgxc5IoQyNReGfjQahjOAJkAALUAAMkAANEAANUAAN0AAOEAAOUAAOkAAO0AAPEAAPUAA + PkAAP0AAQEAAQUAAQkAAQ0AAREAARUAARkAASEAASUAASkAAsEAAAP8vAFByc1UVDQAARwAAAAcAAABtYXJpbWJhAAAAAAcAAABNYXJpbWJhAAAAAAAAAABwcm9tWAAA + AAEAAAABAAAAAQAAAAcAAABtYXJpbWJhAAAAAAcAAABNYXJpbWJhAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAA + AAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEA + AAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAAAAAIIDAAAA + AQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAACAPwAAAAABAAAAAAAAAAAAAD8DgAAAYAIAAAAAAKhBAACM + QgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAQwAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABg + AgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAAAAAAAAAAAAAAAAIAAAAA/AACAPwOAAABQAABwQgEAAAAAAAAA + A4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIAAAABAAAAcQAAAEZsYXQgdGVtcGVyYW1lbnQKQW4gZXF1YWwgdGVtcGVy + YW1lbnQgd2hlcmUgdGhlIG9jdGF2ZSByYXRpbyBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRvIDEuDAAAAAAAyEIAAEhDAACW + QwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAA + AAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAAAAAAAAAAAAAMA/AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAA + AAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAAAACAPwOAAABQ + AABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGAC + AAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAEhCA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAEADAAAAAACo + QQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/gQOAAABA + BAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAAAQAAAAAAAAAAAAA/zcxMPwOAAABgAgAA + AAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAwAAABDbGVhbiBzdHVkaW8LAAAAY2xlYW5zdHVkaW8A + ABBBAABAQQAAgD/NzMw+AAAAAAAAAAAAAAAAAAAAAAAAAADv7u6/AACqQwAAgD8AAIA/AQAAAAYAAAAAAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ + ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAAQAAAAAAAD8AAABAAwAAAAAAgD8AAIA/AACA + PwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8A + AIA/BwAAAAAAgD+amZk+zcxMPzMzsz8AAIA+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AA + AEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAQAAAAEAAAAA + AHBCA4AAAFAAAAAAAQAAAAAAAADPJps+A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EBAAAAAQAAAAEAAAD5//89LDPTPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAA + AAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAA + AAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAAgD8EAAAAAgAAAAgAAAAHAAAAAQAAAAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBA + AAC0QkYAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMAAAABAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIBA + AAAAQQAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAABwAAAAAAAAAAAAAADgAAABAAAAAAAAAAAACAPgAAAAAAAAAA + zcxMvgAANEOamRk+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAIAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAgD8AAABAzcxMPwAAcMEAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAmyAaPwAAQMEAAAAAAwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAAAzMzM/ + mpmZvjMzsz8AAAAAAAAAAAAAAAAFAAAAAAAAAAAAwMIAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA + AAAAAAQAAAABAAAAAwAAAJqZmT+amZm+MzOzPwAAAAAAAAAAAAAAAAUAAAAAAMDCAAAAAAAAwMIAAMDCAADAwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAj8L1Pz0KV78fhWs/AAC0QgAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAAzMzO/XI/CPq5HYT8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDC + AABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAA + AAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAA + AwAAADMzcz+amZm+MzOzPwAAkEEAAAAAAAAAAAAAAAABAAAAAAAAABE4VjgMEAAAUHJWSyYAAAJYRlRQ/A8AAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4w + LzIwMjIxMTE36UVsJdLulIej86jGBXwuE2RsbGG7DwAAdGR0TZgCAAATAAAAWHlsb3Bob25lIEh1bWFuaXplZAAAAAAHAAAATW9kYXJ0dGgAAABUaGUgc2FtZSBhcyBY + eWxvcGhvbmUgT3JpZ2luYWwsIGJ1dCB3aXRoIHRoZSBzdHJpa2UgcG9pbnQgdmFyeWluZyByYW5kb21seSBhcyBwbGF5ZWQgYnkgYSByZWFsIG11c2ljaWFuLgAAAABE + AQAAEwAAAFh5bG9waG9uZSBIdW1hbml6ZWQAAAAA3wEAAEdSVVUAAAAGAAAAAQPATVRyawAAAckA/wIPIEJsYXogSnVyamV2Y2ljAIBVAFZXAACQIUUkT2tqsEAAYoAh + AAtPABmQGl0XJk8vWV6BBoAaAAsmAIFZWQBrkChoABxoGFtlgiyAWwALkFddMoAcADsoAIE1VwCBGpApWgAdTg1Ya4F8gFgASJBURg6AHQBSKQAkkFhQL4BUAIEqkFQ/ + O4BYACSQHmYZKmkwT1ovgFQAgSpPAGuQVGMLTmFIgCoAMh4AgUpOAGGQH2ELK1cMTWgwgFQAgVlNAEYfADCQS0gagCsAgXBLAGuQIU4MLVIZUWgMTWCBb4BNABhRADAh + AAwtABiQS2GCCYBLAHeQLmMMIlIjTG8AT26BQYBPAC9MAD+QSDxfT0QwTE8LgC4AMEgAMU8ARyIAGZBISSSATACBTJBDUQCASACBQEMAO5AjWQwvSg1Ha4EDgCMAPS8A + giBHAGuQJGkYMHQASHaBfoAkADBIACIwAIFeGkAAHEAAHUAAHkAAH0AAIUAAIkAAI0AAJEAAJkAAKEAAKUAAKkAAK0AALUAALkAAL0AAMEAAQ0AAR0AASEAAS0AATEAA + TUAATkAAT0AAUUAAVEAAVUAAV0AAWEAAWUAAW0AA/y8AUHJzVRMNAABHAAAABAAAAHh5bG8AAAAACQAAAFh5bG9waG9uZQAAAAAAAAAAcHJvbVcAAAABAAAAAQAAAAEA + AAAEAAAAeHlsbwAAAAAJAAAAWHlsb3Bob25lAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAA + AP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEA + AAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAAAAAIIDAAAAAQAAAAAAAAAAAAAA + AwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAACAPwAAAAABAAAAAAAAAAAAAD8DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEA + AAAAAAAAAAAAAAAAoMAAAKBAAAAAQwAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5C + AgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAAAAAAAAAAAAAAAAIAAAAA/AACAPwOAAABQAABwQgEAAAAAAAAAA4AAAGACAAAAAACo + QQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIAAAABAAAAcQAAAEZsYXQgdGVtcGVyYW1lbnQKQW4gZXF1YWwgdGVtcGVyYW1lbnQgd2hlcmUg + dGhlIG9jdGF2ZSByYXRpbyBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRvIDEuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAW + RAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOAAABgAgAAAAAA + qEEAAK5CAgAAAAAAAACBAAAAAAAAAAAAAMA/AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGAC + AAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAA + AQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkIC + AAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAEhCA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAA + AAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAmk0k+ + /n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAAAQAAAAAAAAAAAAA/mpkZPwOAAABgAgAAAAAAqEEAAK5CAgAA + AAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAwAAABDbGVhbiBzdHVkaW8LAAAAY2xlYW5zdHVkaW8AABBBAABAQQAAgD/N + zMw+AAAAAAAAAAAAAKDBzcxMvgAAAADv7u6/AACqQwAAgD8AAIA/AQAAAAYAAAAAAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAA + qEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAAQAAAAAAAD8AAABAAwAAAAAAgD8AAIA/AACAPwOAAABAAwAAAAAA + qEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD8z + MzM/AACAP5qZmT8AAIA+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAA + cEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAQAAAAEAAAAAAHBCA4AAAFAAAAAA + AQAAAAAAAACuqqo+A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EBAAAAAQAAAAEAAAD//38+/P+/PwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zm + pj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAA + AIA/AAAAAAOAAABIAQAAAAAAgD8EAAAAAgAAAAgAAAAHAAAAAQAAAAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AA + AEgBAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMAAAABAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIBAAAAAQQAAAAAAAAA/ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAABwAAAAAAAAAAAAAADgAAABAAAAAAAAAAAACAPgAAAAAAAAAAzcxMvgAANEOamRk+ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAIAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAgD8AAABAzcxMPwAAcMEAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAmyAaPwAAQMEAAAAAAwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAACamZk+zcxMvjMzsz8AAAAA + AAAAAAAAAAAFAAAAAAAAAAAAwMIAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAA + AwAAAGZmZj+amZm+MzOzPwAAAAAAAAAAAAAAAAUAAAAAAMDCAAAAAAAAwMIAAMDCAADAwgUAAAAAAAAAF7dROQAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAEAAAAAAAAABAAAAAAAAAADAAAAj8L1Pz0KV78fhWs/AAC0QgAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAAzMzO/XI/CPq5HYT8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDB + AABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAwAAAJqZGT8AAIC+ + MzOzPwAAkEGsZRfBAAAAAAAAAAABAAAAAAAAABE4VjgALwAAUHJWSyYAAAJYRlRQ8C4AAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3hWVe + u/d61j5c1C1qVuyJDGRsbGGvLgAAdGR0TSsEAAARAAAAU3RlaW53YXkgQiBJbXByb3YAAAAABwAAAE1vZGFydHQqAAAAQSBwcmVzZXQgdGhhdCBlbmNvdXJhZ2VzIHlv + dSB0byBpbXByb3Zpc2UuAAAAAEQBAAARAAAAU3RlaW53YXkgQiBJbXByb3YAAAAAtAMAAEdSVVUAAAAGAAAAAQPATVRyawAAA54A/wIPIEJsYXogSnVyamV2Y2ljALBA + AABDAABCAABFAE2QNCgHsEB/hXKQOiaEJz40g1tCQIRFgEIAL5BFPpAosEAAMoBFACmQQiGCUoBCAEiQQyyCbIBDAASQRVKBMYA+AIFpOgAXRQASkEZUR4A0AIIrRgAT + kEhegWyASACBGZBISoI6gEgABJBJRIURLSSBMbBAf4JfgC0AXZA3IIJNgDcAgWeQPR2DMIBJAIFUkEEThDpIbWywQACBIoBIAGWQRkCBToBGAIEakEUdgRWARQCBSJBG + PII6gEYAaZBDHoNtgEMAQ5BELIUIgEQASJBFQIcegEUABpBGXIIpgEYARZBENIErgEEAO0QAc5BFPoJcgEUABz0AAJBAIoIggEAAXJBDSIMOgEMAXJBBOoVngEEAD5BD + QoEUMh2BNoBDABqQRD6FXbBAf3OQOCqGeTsYhm1AKIwARzKCc4BEAIkUOwANMgBMOABqRwAAQACEQJBIVoQtgEgACbBAADmQMjIGRUiCSLBAf4FTkExUIDoshH2ARQBE + kDwkBkE4kl+APAAATAAnkEpGOoAyADk6AA9BAIEHSgBMkEhOgkCASAB+kEpcCrBAABiQMzaCM7BAf4EfkDo0gn4+OoJlgEoAGJBDQIEGgD4AgW46AAwzABSQSGE9gEMA + gWdIAFuQRlaEMIBGAC6QSFINsEAAHZA0KIJTsEB/gSuQOi6DHEM6gSuASACCYUMAIJBGQINugEYALzQAEJBFVGmAOgCEA7BAACGARQAskDUyA0ZGglmwQH+CUpA/RjM6 + GDKARgBMPwCGV5BKTCo/LIhCgD8AglU6AEk1AIYDSgAPkEtAgzGASwAvkExpFy4oBrBAAIIvgEwAeJBPVlc5LnaATwCBNJBNPoIMgE0AWZBKRoFQgEoAeJBGIXaARgCE + EpBKUIFwgEoAbpBLQIQ6gEsANpBKPIosRlwRgEoAghBGAD2QRTiCBYBFAFSQSFSCCYBIAEGQRjiCWYBGAAk5AEeQRCyCDIAuAIFARABLkEUwgRQtFoJFsEAsCkB/hFmQ + NxiFGz4ch2ywQABigD4ASZA9F4E7sEB/iR2APQCBZi0AhCM3AIMSLUAALkAAMkAAM0AANEAANUAAN0AAOEAAOUAAOkAAO0AAPEAAPUAAPkAAP0AAQEAAQUAAQkAAQ0AA + REAARUAARkAAR0AASEAASUAASkAAS0AATEAATUAAT0AAsEAAAP8vAFByc1V0KgAARwAAAAYAAABtb2RlbGIAAAAAEAAAAEdyYW5kIFN0ZWlud2F5IEIAAAAAAAAAAHBy + b21gAAAAAQAAAAEAAAABAAAABgAAAG1vZGVsYgAAAAAQAAAAR3JhbmQgU3RlaW53YXkgQgAAgD8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAAAAAA + AAAAAAAAcnlhbBAAAAACAAAAAAAAAAAAAAD/////b2xvcxEAAAABAAAAAQAAAEYAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAB + AAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAACAAAADUwMDAtMy0xCgAAAE15IFByZXNldHMAAIC1A4AA + AGBYAAAAAACoQQAAgD9YAAAAgLRmP1eYihw/8PsavwyYpD9QdHo/gHa1PQD9ET0A14M+AB5QvmBgLT8As4A+/HrgPwCpuz9s+Zo/APPlPc5OLEAQAQ1AwIpWvhh6OT9Q + LhE/MOkGv6DHHL+Awf69pv5KQEaEB0DAqqq+gPz7PSA5yT7AhU++IPOrvgB4xjwAhNM9QIG9PnhwWD94gnE/tpUQQGic4z9YTRs/6HFbP2Azsz/AC1I/ODuGP4BwuT5o + W4g/GAuMP+jGJECs288/oPWvv0DqWT+ANgK/KMyvv044BEAAB1u94IKAvgCaYr5UVxvASEfDv6DX+L8gp5W/AJ1VvbA9PD8EQa+/gH76vsBlr78QV1i/kD51PyD2tL5g + 1qa/IGBkv/ACqb/AaRg+OBaYvwCcBbyg4YQ+AM1SPdTNA8B49E8/ADG4v+iLtL+Qr26/2J3Hv/QS27+AzO+90FlWv3xPSMBAjR0+eMWhv4D52j0DAAAAAQAAAAAAAAAA + AAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAADAPwAAAAABAAAAAAAAACzSKT4DgAAAYFgAAAAAAKhBAACAP1gAAADONyQ/ + gtj7JT8BtsknP7bJJz8Fl3EtPyhqLz9RbjE/FJs1P3x+Mz98fjM/ghSbNT+Bi8Q3PwUUmzU/UW4xPz2EKz/vkj4/xfRAP8X0QD8GUnlIP+TRTT9ydVM/VWpZP523Xz/Z + AWM/2QFjP4Hg4mk/AQd8bT8HfG0/BfYxcT8HfG0/9jFxPw8GdT/E+Xg/xPl4PwGmDn0/pg59P4Euo4A/AVbRgj9W0YI/Ab0ShT+9EoU/AWZohz9maIc/AmjTiT/tVIw/ + 7VSMP4Ez7o4/gpWgkT+DhG2UP4GRVpc/gmtdmj+VAQAAAAAAAAAAAAAAAACgwAAAoEAAALBCAAAAAAOAAABQAABwQgEAAAAAAAAAAAAAAAAAQMAAAAAAAQAAAAEAAAAA + AAAAAADAwAAAQEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAgAAAAD8A + AIA/A4AAAFAAAHBCAQAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAAAAAIC/BAAAAAAAAADAAACAvwAAgD8AAIA/AgAAAEAAAAAAAAAAAAIAAABAAACAPwAAgD8DAACA + vwAAAMAAAIA/AACAPwEAAABAAAAAQAYAAIA/AAAAAAAAgL8AAADAAACAvwAAAMAAAADAAQAAAAAAAAAAAQAAgD8AAIA/BgAAAMAAAIA/AACAvwAAAAAAAADAAACAPwAA + gD8BAAAAAAAAAACBAAAAwAIAAIC/AAAAAAAAAAAHAACAPwAAgL8AAEDAAAAAwAAAgL8AAIDAAAAAwAAAAMCBAABAwAQAAAAAAACAwAAAQMAAAADAAAAAwAUAAAAAAACA + wAAAQMAAAADAAACAwAAAgMARAAAAAAAAQMAAAIDAAACgwAAAAMAAAEDAAACAwAAAAMAAAEDAAAAAwAAAQMAAAADAAACAvwAAgMAAAEDAAACAwAAAgL8AAIC/AQAAgMAA + AIDAgQAAQMABAACAwAAAgMADgAAAYFgAAAAAAKhBAACAP1gAAAC6n3w/VwHLfT9q2Ys/5EiIP37edD9Yb4A/VU2AP9VAgj/vUWw/ZdB2P1xPjD/b+m8/kMeMP2BfbT/O + qWo/mBxoP6NJhD9XaXw/FJeLP15IgT+gsG0/VidzP/NDjD+Ls4o/TsJ3P/qwdT9UZoE/iXZ/P1JPfj8qz4I/isSKP4pFaj8SaWY/xABtPw9Igz/AcXw/FlFnP9o6cj9o + nWw/DD6FP3TUaz+yBGo/8SqCP5C8cz+r8Hs/cK+GPwqPcj9ELGg/Zx2MP1R+ij8YU4E/2rh8P5XUgz9v43A/cQ12P7i+hj/3pmg/WX6FPzy8iT88+4U/t86DP5zzez8A + BXg/2tp5P9tCeT9Y7m0/FFCFP982ij8hcYc/Oz2IPy7reT/yeoM/3XhwP66phj/tMGw/KIyIP3B/Zz8gi4Y/ocV+PxOkeT9fSIE/Tv5wPwt3hD9Ai3w/vvh/P0/2iz9Q + ZHs/VhuHPwIAAAABAAAAVQAAAEVxdWFsIHRlbXBlcmFtZW50ClRoZSBzdGFuZGFyZCB0dW5pbmcsIHdoZXJlIHRoZSBvY3RhdmUgaXMgZGl2aWRlZCBpbiAxMiBlcXVh + bCBzdGVwcy4MAAAAAADIQgAASEMAAJZDAADIQwAA+kMAABZEAAAvRAAASEQAAGFEAAB6RACAiUQAAJZEAQAAAAAAAAAEAAAAAADcQzwAAABFAAAARgAAAAB/AAAAAAAA + AAcAAABEZWZhdWx0AAAAAAAAAAACAAAAA4AAAEAFAAAAAADAQQAAEEIAAEBCAABwQgAA2EIFAAAAAADAwAMAAEDAAACAvwAAAAAAAAAAAAAAAAAAAADNzIw/AQAAAAEA + AAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgWAAAAAAAqEEAAIA/WAAAAFzCdz9XmWN7PwZjgz8z2YU/zvuAP83UfD8Ni4A/CA2KP65FfT+fdXo/ooGG + P/CWgz8HgH8/VWF2PzzbgD/FkIQ/kdOGP/HNdj/UJn4/+493P+tjeD9Bg3Y/st58P9bXZj+8i28/2z1yP/gJez/y3oI/+X56PwQFhD9qo2o/VRCFPwWujj8zjoQ/B9CF + P4ylgj8Mpng/k5KJPzP+gz81rHE/YMGEP7+rgD/d7IQ/MySCP9hChj9etnU/iXR6P47zfD/IwoA/mC9xPyZpaD88bGw/NIaDPxg8cD+ZzIM/U/iBP+5thD9JwnA/iPw0 + Pw2Ffj9IloU/X8mEP/xHij8MvIc/ms2PP/Uvgz+kQX0/K3CAP5AMWz/SG3Q/3vqDP3EBgz9oqXA/YAJ2P1K5dT/3pog/nlKBP0xkej9xJX0/Nsx1PxeOhj90unE/c1Z9 + P1gsgz/H0H0/Dw+JP4xzgT9OlYI/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEA + AAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAA + AACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAACBCA4AAAEAPAAAAAACoQQAA0EEAAPhBAAAQQgAA + QEIAAHBCAACQQgAAqEIAALRCAADAQgAAyEIAANBCAADUQgAA1kIAANhCDwAAAAAAwMAEAABAwAAAAMAAAIC/AAAAAAAAAAAJAACAPwAAQEAAAKBAAADgQAAAIEEAAGBB + AACYQQAAwEEAAPBBAACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABIAQAAAAAAAAABAAAAA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAAEAAIA/A4AA + AGACAAAAAAAAAAAAgD8CAAAAAACAP4EDgAAAQAQAAAAAAAAAJpNJPv5+Pz8AAIA/BAAAAAAAAACBAACAP4EDgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAgQAAAADNzEw+ + AAAAAAAAAAAAAAAAAAAAPz0KB0ADgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAgL8AAAA/A4AAAGACAAAAAADAQQAAkEICAAAAAACAP4EBAAAAAQAAAAEAAAALAAAA + SmF6eiBzdHVkaW8KAAAAamF6enN0dWRpbwAAQMAAAJBBAACAPzMzsz8AAAAAj8L1PAAAAAAAAAAAAAAAADMzMz9xnalDAACAPwAAgD8BAAAABgAAAAAAgD8AAIA/A4AA + AGBYAAAAAACoQQAAgD9YAAAAw/WIPwMfhYs/j8J1P9ejcD/Xo3A/AbgehT+4HoU/FKRwfT/D9Yg/MzNzP0jhej8fhYs/SOF6P3sUbj+4HoU/4XpUP2Zmhj/NzIw/7FF4 + P65HgT/sUTg/rkeBP2Zmhj+uR4E/4XqUPx+Faz+uR4E/rkeBPzzD9Wg/ZmaGP83MjD9mZoY/exRuP0jhej9cj6I/GwqYP2Zmhj8fhYs/exRuP1yPgj/sUXg/7FGYPwrX + gz/sUZg/ZmaGP8P1aD8zM5M/7FF4P65HgT+kcH0/SOF6P9ejcD/D9Yg/PQpXPwrXgz+PwnU/uB5FP8P1iD+amVk/zcxMPwrXgz8zM3M/uB6FPwrXgz8AAIA/ZmZmP6Rw + fT/Xo3A/H4WLPx+Faz9mZoY/AACAP7gehT/sUXg/exRuP65HgT+4HoU/AACAP2Zmhj8Uroc/cT2KPwAAgD9xPYo/MzNzP83MjD8K14M/7FF4P8P1aD8DgAAAYFgAAAAA + AKhBAACAP1gAAACDhIA/V2q3fD+HeH4/rSV+P2otgD+kZIA/ngJ6P9DPej9yFIA/aqOCP63qej9Wcnk/W6+AP08LeD+9gYE/YN+CP3CFgD8WvIE/xj+BP5nKgT8fJjo/ + jehAP3N6dD8rzHo/T9WDP6C3gD8Iz34/4CJ4P5fUdz8YQ4M/8I2AP0sJgj8B0YI/77SDP4EThj9E7nU/aRCDP1gxej+RBoE/RGx5P7sUdz9leIM/2AB9P1Aqfz96S3g/ + DOKGP3rogz93Y4E/boGBP3Ljez8X+nU/lSp7P5Gygj9ysIE/Rj+APxkthz/erXw/smKEP0BUfj96cXo/m+J8PxOhfz/hzHs/Y7p1P+89gT9WDYE/IDd8PyWGdT82KIM/ + cQODP+2BfT+qw4A/NQ18P+T2fj9uw38/nm16P7uFhD9WzYA/OE+BP+g3fj9YsXk/HaGAP9AGfz8bY3s/hreBP61Xgz+YaHQ/JRGGPwAAAAADgAAAYFgAAAAAAKhBAACA + P1gAAACUZh4+V3YVYj5T1z+++yVovljggL6nnV49Nmg7Pavm0b0/piA+6k1fvgQi6L33uFc+QJ4Cvn9Og751DDk9Gye2PeoOoj1YBGo+EOUgvnJUEL1wtAw+q59ZPus9 + GD4L6RA+hVoFPppboL6ypL28KT67vKMXo7514qw98BZoPodHrj0z44e+Jnz6vWkaUj7Zdq6+yVXEPckqWz6zwI++Uc0LPGj0Gr4hIV4+fMnjPOYiDj4WkNA956+tvtoc + l76cVhq+mpmZvn1Ro74Wydy9718AvpqP+b1vdaa9KVwPvnggP756SFM+TDwYPh1rrD3Lp1O+e1i/PH9RZL4fq0k9RucIPRMPVb1oo7K+wObBvSDHcr6EU2I+ouygvvUn + xz0ayYW9V+VqPUjeGr5RYZG+SqXAvLfbiT1zOYm9x07OPQkp9j0iz0I+ecN0vcuVLD5uT1++7mB0PnKrLT1R7Aq+MK6nvgEAAAAAAAAAAQAAAAAAAD8AAABAAwAAAAAA + gD8AAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAA + AIA/ggAAgD8AAIA/BwAAAAAAgD+amZk+zcxMPwAAwD/NzEw+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJxEPQAAAAAAAAAAA4AAAGBYAAAAAACoQQAAgD9YAAAAw/Zo + P1dA3nU/D9qNPxxRWz+sRnI/ZECGPxnziD8IUYI/tMF9P7IUgz/FdIo/tQxjP9zDgz8NFWs/KWCUP1OpVD88uEw/8OeBPyTvhz/uDY8/6HTBP/bviD8k74c/mvw4P/a5 + ZD9IfXU/NEWBPyTvhz9Su4k/hW+GP8H/oT/GOY4/l22MPwm2dT/oHIA/ruprP4mSQj/MJ08/VoxUP5DMfD9poYo/o9eDPzC5lz89UIE/4KRhP2A2TT/lIIw/akWbP2PA + gD8OJ3c/jUeDP+1TZz9tqok/ZymAP/u4fj/LbZ8/QBeLP8yYjz8XLpE/BoqKPyo0dz/aOIY/EeaIP9tTkD8mga8/BAqIP/8Yhj8NqZY/Rk2RPxpAkz9OkoY/hDmUP9kN + ij/Cn4M/CnaHP4orvD9YGpQ/9ZijP9Kxnj+nupE/juehP84mmD+mwn4/fZyNPzFuvD+If3w/wYOLP1lhbT8DgAAAYFgAAAAAAKhBAACAP1gAAAAAAIA/j2nQfz8o0KB/ + Pzhxfz+hQX8/CBJ/P3Hifj/Zsn4/QIN+P6lTfj8SJH4/efR9P+HEfT9KlX0/sWV9Pxo2fT+CBn0/6dZ8P1KnfD+7d3w/Ikh8P4oYfD/z6Hs/Wrl7P8OJez9nOHo/Dud4 + P7KVdz9aRHY//fJ0P6Whcz9KUHI/8P5wP5Wtbz89XG4/4AptP4i5az/+CWs/d1pqP+uqaT9k+2g/2UtoP9lLaD+eA4AAAGBYAAAAAACoQQAAgD9YAAAAAACAP6nNzHw/ + g5mZeT+EZmZ2P4QzM3M/gzRfaz8BL7poPy+6aD8BAABwPwAAcD+CYkVpPwEAAHA/AABwP5IDgAAAYFgAAAAAAKhBAACAP1gAAAAAAIA/qRhLfj+DMJZ8P4NkdH0/BrCd + fD/8xns/SPB6P5QZej/fQnk/ED54PxA+eD+B52t1PwZYjGs/L7poPztFbj9h4n0/OBB7PxA+eD8QPng/A+drdT8hFHk/eHd3P3h3dz+RAQAAAFgAAAAAAKhBA4AAAGCH + AAAAAAAAAAAAgD+HAAAAAAAAAP8AAAAAhgAAsEEDgAAAYIcAAAAAAAAAAACAP4cAAAAAAAAA/wAAAACGAAC4QQOAAABghwAAAAAAAAAAAIA/hwAAAAAAAAD/AAAAAIYA + AMBBA4AAAGCHAAAAAAAAAAAAgD+HAAAAAAAAAP8AAAAAhgAAyEEDgAAAYIcAAAAAAAAAAACAP4cAAAAAAAAA/wAAAACGAADQQQOAAABghwAAAAAAAAAAAIA/hwAAAAAA + AAD/AAAAAIYAANhBA4AAAGCHAAAAAAAAAAAAgD+HAAAAAAAAAP8AAAAAhgAA4EEDgAAAYIcAAAAAAAAAAACAP4cAAAAAAAAA/wAAAACGAADoQQOAAABghwAAAAAAAAAA + AIA/hwAAAAAAAACBAAAAwAEAAAAAAAAAAP8AAAAAggAA8EEDgAAAYIcAAAAAAAAAAACAP4cAAAAAAAAAmwAAQMABAAAAAAAAAACVAAAQwQEAAAAAAAAAANAAAPhBA4AA + AGCHAAAAAAAAAAAAgD+HAAAAAAAAAP8AAAAAhgAAAEIDgAAAYIcAAAAAAAAAAACAP4cAAAAAAAAA/wAAAACGAAAEQgOAAABghwAAAAAAAAAAAIA/hwAAAAAAAAABAABA + wAAAQMABAAAAAAAAAAD/AAAAAIIAAAhCA4AAAGCHAAAAAAAAAAAAgD+HAAAAAAAAwAEAAEDAAABAwAEAAAAAAAAAAP8AAAAAggAADEIDgAAAYIcAAAAAAAAAAACAP4cA + AAAAAAAAAgAAgMAAAAAAAAAAAP8AAAAAgwAAEEIDgAAAYIcAAAAAAAAAAACAP4cAAAAAAIDAAgAAAMAAAAAAAAAAAP8AAAAAgwAAFEIDgAAAYIcAAAAAAAAAAACAP4cA + AAAAAADAAQAAAAAAAAAAgQAAQMABAAAAAAAAAAD/AAAAAAAAGEIDgAAAYIcAAAAAAAAAAACAP4cAAAAAAIDABgAAgD8AAEBAAABAwAAAgMAAAADAAAAAAAAAAAD/AAAc + QgOAAABghwAAAAAAAAAAAIA/hwAAAAAAQMADAAAAwAAAgD8AAAAAAAAAAIEAAADAAQAAAAAAAAAAkAAAQMABAAAAAAAAAADrAAAgQgOAAABghwAAAAAAAAAAAIA/hwAA + AAAAQMAGAAAAwAAAAAAAAADAAAAAAAAAQMAAAAAAAAAAAP8AACRCA4AAAGCGAAAAAAAAAAAAgD+GAAAAAACAP4RmZkZAgQAAQEAJMzMzQM3MLEAzMxNAZmYGQAAAAEBm + ZuY/AADAP5qZmT8AAIA/AACAPwlmZmY/MzMzP5qZGT8AAAA/zczMPpqZmT7NzEw+zczMPQAAAAAAAAAA6gAAKEIDgAAAYIYAAAAAAAAAAACAP4YAAABmZoY/gQAAAAAW + mpmZPwAAAMAAAABAtJYBQMlx7j8Dttk/LCnKPzPgpT8b9Ys/RrZZP1I+MD9IJBE/fBTkPlxYzz5o4KU+/CORPlDQeD7Q3yU+UND4PQAAgL8AAABAAAAAAAAAAACIAABA + wAEAAAAAAAAAAOEAACxCA4AAAGCGAAAAAAAAAAAAgD+GAAAAAAAAAIEAAIDABQAAAAAAAKDAAAAAAAAAQEAAAIBAAACAQAYAAABAAAAAAAAAQEBmZua/AACAPwAAAAAA + AAAAhQAAAEACAACAvwAAAAAAAAAA7QAAMEIDgAAAYIYAAAAAAAAAAACAP4YAAAAAAAAA/wAAAACFAAA0QgOAAABgdgAAAAAAAAAAAIA/dgAAAAAAAMABAAAAAAAAAADz + AAA4QgOAAABgdgAAAAAAAAAAAIA/dgAAAAAAQMADZmbGPwAAoMAAAAAAAAAAAPEAADxCA4AAAGB2AAAAAAAAAAAAgD92AAAAAAAAAIIAAGDAAQAAAAAAAAAAhQAAQMAB + AAAAAAAAAADoAABAQgOAAABgawAAAAAAAAAAAIA/awAAAAAAgL8EAAAAQAAAAAAAAEDAAAAAAAAAAADlAABEQgOAAABgawAAAAAAAAAAAIA/awAAAAAAgD+BAABAwAIA + AIDAAAAAAAAAAADlAABIQgOAAABgawAAAAAAAAAAAIA/awAAAAAAAAADAAAAQAAAAMAAAAAAAAAAAOYAAExCA4AAAGBbAAAAAAAAAAAAgD9bAAAAAAAAAIEAAADAAgAA + gD8AAAAAAAAAANUAAFBCA4AAAGBbAAAAAAAAAAAAgD9bAAAAAAAAQAYAAADAAACAvwAAAEAAAEBAAAAAwJ9HiT+fR4k/AQAAAAAAAAAA0QAAVEIDgAAAYFsAAAAAAAAA + AACAP1sAAAAAAIC/gQAAAAADAAAAwAAAgD8AAAAAAAAAANQAAFhCA4AAAGBWAAAAAAAAAAAAgD9WAAAAAAAAQIEAAAAA0wAAXEIDgAAAYFEAAAAAAAAAAACAP1EAAAAA + AAAAgwAAAMCBAAAAAMoAAGBCA4AAAGBIAAAAAAAAAAAAgD9IAAAAAABAQAEAAADAAAAAwAKamZm/AAAAAAAAAAAFAABAwAAAgL8AAAAAAAAAwAAAAAAAAAAAjgAAAMAB + AAAAAAAAAACrAABkQgOAAABgSAAAAAAAAAAAAIA/SAAAAAAAAACBAACAvwEAAAAAAAAAAIEAAIA/AQAAAAAAAAAAvwAAaEIDgAAAYEQAAAAAAAAAAACAP0QAAAAAAMA/ + BAAAAMAAAAAAAAAAwAAAAAAAAAAAvgAAbEIDgAAAYEAAAAAAAAAAAACAP0AAAAAAAABAAQAAAMAAAADAAQAAAAAAAAAAAgAAAMAAAAAAAAAAALgAAHBCA4AAAGA5AAAA + AAAAAAAAgD85AAAAM4ACQAIAAIC/AAAAAAAAAAACAAAAwAAAAAAAAAAAsgAAdEIDgAAAYDkAAAAAAAAAAACAPzkAAAAAAAAAAgAAQMAAAAAAAAAAALUAAHhCA4AAAGA2 + AAAAAAAAAAAAgD82AAAAAAAAAAIAAEDAAAAAAAAAAACBoA6BPwIAAEDAAAAAAAAAAACtAAB8QgOAAABgLwAAAAAAAAAAAIA/LwAAAAAAAACuAACAQgOAAABgLwAAAAAA + AAAAAIA/LwAAAAAAAAADAACAvwAAAMAAAAAAAAAAAKoAAIJCA4AAAGAtAAAAAAAAAAAAgD8tAAAAAACAvwMAAAAAAAAAwAAAAAAAAAAAqAAAhEIDgAAAYCsAAAAAAAAA + AACAPysAAAAAAAAAqgAAhkIDgAAAYCcAAAAAAAAAAACAPycAAAAAAIA/gQAAAACCAACAwAEAAAAAAAAAAJ8AAIhCA4AAAGAmAAAAAAAAAAAAgD8mAAAAAACAvwIAAIA/ + AAAAAAAAAAACAACAPwAAAAAAAAAAnwAAikIDgAAAYCQAAAAAAAAAAACAPyQAAAAo1wJAhQAAAACdAACMQgOAAABgIgAAAAAAAAAAAIA/IgAAAAAAAAABAAAAQAAAAEAB + AAAAAAAAAACdAACOQgOAAABgIAAAAAAAAAAAAIA/IAAAAAAAAMABAAAAAAAAAACBAABAwAMAAKDAAABAQAAAAAAAAAAAlwAAkEIDgAAAYB0AAAAAAAAAAACAPx0AAAAA + AABAAwAAAAAAAABAAAAAAAAAAACYAACSQgOAAABgHAAAAAAAAAAAAIA/HAAAAAAAAACbAACUQgOAAABgGwAAAAAAAAAAAIA/GwAAAGZmhj8BAAAAAAAAAAAEAACAvwAA + AAAAAIDAAAAAAAAAAACTAACWQgOAAABgGQAAAAAAAAAAAIA/GQAAAAAAAACHAAAAwAEAAAAAAAAAAI4AAJhCA4AAAGAYAAAAAAAAAAAAgD8YAAAAAAAAAJcAAJpCA4AA + AGAWAAAAAAAAAAAAgD8WAAAAAAAAQAEAAAAAAAAAAJMAAJxCA4AAAGAVAAAAAAAAAAAAgD8VAAAAAAAAAJQAAJ5CA4AAAGAUAAAAAAAAAAAAgD8UAAAAAAAAAIYAAIC/ + AgAAAMAAAAAAAAAAAIkAAKBCA4AAAGATAAAAAAAAAAAAgD8TAAAAAAAAAJIAAKJCA4AAAGASAAAAAAAAAAAAgD8SAAAAAAAAAIJmZoa/AgAAAAAAAIC/AACAv4EAAAAA + iQAApEIDgAAAYBEAAAAAAAAAAACAPxEAAAAAAAAAkAAApkIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAAAAjwAAqEIDgAAAYA8AAAAAAAAAAACAPw8AAAAAAAAAjgAAqkID + gAAAYA4AAAAAAAAAAACAPw4AAAAAAAAAjQAArEIDgAAAYA0AAAAAAAAAAACAPw0AAAAzMwNAAQAAAAAAAAAAAgAAAMAAAAAAAAAAAIcAAK5CA4AAAGAMAAAAAAAAAAAA + gD8MAAAAAABAQAMAAAAAAADAPwAAAAAAAAAAhwAAsEIDgAAAYAwAAAAAAAAAAACAPwwAAAAAAIA/AQAAAAAAAAAAiQAAskIDgAAAYAsAAAAAAAAAAACAPwsAAAAAAAAA + AgAAQMAAAAAAAAAAAIcAALRCA4AAAGAKAAAAAAAAAAAAgD8KAAAAAAAAAIkAALZCA4AAAGAKAAAAAAAAAAAAgD8KAAAAAAAAAIkAALhCA4AAAGAJAAAAAAAAAAAAgD8J + AAAAAAAAAIgAALpCA4AAAGAJAAAAAAAAAAAAgD8JAAAAAAAAAAIAAIDAAAAAAAAAAACFAAC8QgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAAC+QgOAAABgCAAAAAAA + AAAAAIA/CAAAAAAAAACHAADAQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADCQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADEQgOAAABgCAAAAAAAAAAAAIA/ + CAAAAAAAAACHAADGQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADIQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADKQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAA + AACHAADMQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADOQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADQQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADS + QgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADUQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADWQgOAAABgBAAAAAAAAAAAAIA/BAAAAAAAAACDAADYQgOAAABg + BAAAAAAAAAAAAIA/BAAAAAAAAACDR2ziPQOAAABAEQAAAAAAsEEAAOBBAADoQQAACEIAACBCAAAkQgAANEIAAEBCAABMQgAAYEIAAHBCAAB8QgAAhkIAAJBCAACoQgAA + wEIAANhCEQAAAOmjgD8EfhOFPxQ1iD8w1Ik/FDWIPxQ1iD8BMNSJPzDUiT+BFDWIPwegn4Y/XJCDPxpVdj9NvXM/cI1nP5SAUj/d9UA/AQAAAAAAAAABAAAAMzMzP2Zm + pj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAA + AAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAAAAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAADAAAABAAAAAEAAAABAAAAAACAPwAAgD8BAAAA + AQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACAQAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAA + BwAAAAEAAAAAAAAADgAAABAAAAAAAAAAj8J1PQAAgD8AAAAAAAAAvwAAIEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAAABAAAA + AAAAAA4AAAAQAAAAAAAAAAAAyEIAAIA/zcxMPgAAAEAAAPpEAAAAQJqZmT4AAIA/AIA7RgAAAAAAAIA/AABAQAAAAAAAAAAAAAAAAAMAAAAKAAAAAQAAAAAAAAAOAAAA + EAAAAAAAAAAAAHBCAAAAAAAAAD8AAABAAAACQwAAwEAAABBBAACAPwAAekUAAIBAmpkZPwAAQEAAAAAAAAAAAAAAAAABAAAAAQAAAAAAgD8AAKDAAAAAAAMAAAADAAAA + AAAAAAEAAAAFAAAABAAAAAEAAAADAAAA/ppPP8r7HUAAAMA/AABhwwAAAAAEAAAABQAAAAAAgL8AAMDBAADAwgAAwMIAAMDCBQAAAFJJnbmPwvU8AAAAAAAAAAAAAAAA + BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAADSCTpA4IwzPQAAwD8AADRCAAAAAAQAAAAFAAAAAADAwQAAgL8AAMDCAADAwgAAwMIFAAAA + j8L1PG8SgzoAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAKBtukAG1wNA//8fQDpd40JgbI/BAAAAAAUAAAAAAIDB + AACAwQAAcMIAAHDCAABwwgUAAAAK16M8CtejPAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/ + AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAA + AAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAMAAADNzEw/j8J1vgAAwD8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4+DEAAFByVksmAAACWEZUUOgxAAAKAAAA + MjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8yMDIyMTExNyP3YehCsMUcgzu4u9jicQRkbGxhpzEAAHRkdE10BAAAFwAAAEMuIEJlY2hzdGVpbiBERyBQcmVsdWRl + AAAAAAcAAABNb2RhcnR0YQAAAEEgcHJlc2V0IHRoYXQgbGV0cyB5b3UgZGlzY292ZXIgYSBjb2xvcmZ1bCB2YXJpYW50IG9mIHRoZSBDLiBCZWNoc3RlaW4gRCAyODIg + Q29uY2VydCBncmFuZCBwaWFuby4AAAAARAEAABcAAABDLiBCZWNoc3RlaW4gREcgUHJlbHVkZQAAAAC6AwAAR1JVVQAAAAYAAAABA8BNVHJrAAADpAD/AiJCbGF6IEp1 + cmpldmNpYyAtIFpyZWxvIGplIHppdG8gLSAygiyQPEAwLRmHZLBAf4E0kDQohVwtAIMwPACCODQAYC0ghHA8RiSwQAB4kDUoDDwADDtKhkiwQH8kkDsAJDlUhmA6Gzw5 + AGCwQAAMkDoAGDswbDUAPDYkDDsADDw+hjCwQH+HEJAtAIMwPACDeDYAMC0mhgA8SAywQAB4kDcdADtCDDwAhCiwQH94kDsAGDlcgXw3AIFYLQAwOQAkNDyFLDQAGLBA + ADCQLUaCCLBAf4hQQAAwkDcyDD1CAD5AAEVGAEJCglCwQH+DMJBCADw+AABFAAw9AIkMsEAAYJA3AAA9LgBEOAw8LAA2MhhBPII4sEB/g2yQPAAwRAAAQQAwPQCHWDYA + ALBAAEiQRTYMNywMPS4APigMQjCCFLBAf4E0kEUAPD0AAEIAJD4AgUwtAAw3AIkAsEAAJJBATCQ7Rgw1KAAtJDw4IIIgsEB/hDSQOACBHEAAgnQ7AIEoNQCJALBAABiQ + QB4wPC5IOx8MNBgkOR2BNLBAf4ZUkDsADDQAADkAVDwAGC0ADEAAjwAoHgxDSACwQAAMkDpCDD5GJDcqgWSwQH+DYJBGOgwoAAw+AEg6AAxDAAA3AIEQRgCIXEhEDD88 + ALBAAAyQNyAAPToYLSqDGLBAf5AlkEY8g1ctAIEoSABdRgA/NwAkPQCBKD8Agww+Emw/Jhg+AIEoQEQMPwB4sEAAJJBAACQyNgBBXIQ0sEB/ggiQOTyEQD5QPEEAgxgy + AIEcPgAAQUCCCDkAhgxAUDxBABiwQAAMkDIOGD06ADguhFiwQH+GYJA+VDxAADw9AIF8MgCENDgAhyhBSgAyKhg+ACSwQAAMkDcuJDw2gmiwQH+TWJA8AIRMQFIkQQCC + dDcAVDIAhARAAAxBPIEQQQAAQFCBHEAADD5ahCg0Rgw+AIVENAAkRV8YLUYksEAAgmhAf4JQkDQ6gRxFAIccNAAYRVIMQEwMPjYANzgksEAAglxAf4JEkEAAgiBFAEg+ + AIYYNwAYPTYARDgAPzgANigMsEAAgwxAf4QEkEQAgQQ9AIE0PwCGSDYAGEUwALBAAACQQDIMPigYNySCILBAf4MwkEUADEAAYD4AeC0ADDcAhhhASAwtNjCwQACCOEB/ + glCQNDI8QACHBDQAMDw6ADcsDDs4ALBAAIFwQH+GPJBANJskOwCBWDwAVEAAgTQ3AIFwLQAA/y8AUHJzVSMtAABHAAAACwAAAGJlY2hzdGVpbmRnAAAAABUAAABHcmFu + ZCBDLiBCZWNoc3RlaW4gREcAAAAAAAAAAHByb21qAAAAAQAAAAEAAAABAAAACwAAAGJlY2hzdGVpbmRnAAAAABUAAABHcmFuZCBDLiBCZWNoc3RlaW4gREcAAIA/BgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MRAAAAAQAAAAEAAABGAAAAAAAAAAABAAAA + AQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA + AQAAAAAAAAAAAAAAAACAtQOAAABgWAAAAAAAqEEAAIA/WAAAAMDHQ75X4Ja8v8CCjb4AKOi9MJhMvwCdhD5A9ju+IF+RP9j2zL8ANLu+4OqnvwB64DygvRM/AFzuPSiV + OD/4XrG/LJlBwKAA8r6Y86W/5ETtvyD8Sb8Qxqm/GOOiv1Dler+AnCK/AF26vsCZjj5wPAQ/oAKgv9TttL+AfU8+KIJwP4CBTL4gPMO+wFMCv6D1Oz/4en4/eClGPxjs + 7z/oTpM/AA2+PuCUC7+A5vC9sL9jv2xy879A0nu+1G6mP8yPij98fsc/PkaFQJdVpEA0a1pApF62PwDlkD+COwRAgPOPvgD5xz0A5uu9QKpzPrBzQT8ASEo9sBIcv3Br + ar98A8i/8JAnv2DLQr9gvdU+sJpGvxDUXr9ccu2/IEnlPizbwz8AyKY+QNPcPgDNYz1gbp6+IBrWPuDHXr86RQpAkLBPv3Add7+gbfi+AMYbPgDNmb2A+uK9wDp3vshL + AcAAES29AwAAAAEAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAGZmpj8AAAAAAQAAAAAAAACL7yM+A4AAAGBY + AAAAAACoQQAAgD9YAAAAEKGAP1dosYA/c4mBPxAvgj+tJYE/TlB+P5lwgT8BToA/jl99P3qtgD8ksn8/3pp9PwI/fD/Bb3w/5h6APwe6fj+uy38/oG58P5e0fT+YfHs/ + 7eZ+P9C1fT89Unw/Rel8P3xNfD8c33s//wB8P/SIej/0xXo/DRZ4P8gudj+J73g/Rpd6PyKseD88M3k/N0p0Pw9odD8FAXc/40R3P6vWdD8gz3Y/vvtyP5fXcz/KnXY/ + fS14P+U1cz9l1ng/pr91P83wdj9cBnQ/lT12P2hxdz9Plno/Lu55PxdDfT/NyH0/k2J8Pz1QgD8qlYE/hbKAPxKqgT+74YQ/bNuDP6oMhT+A+4M/RnaEP197hT8Gp4Y/ + sFmEPyDDhT+6Z4Q/84qEP05AhD/RxoY/bA2GP+PbhD9L2oU/9qSEP9zBhT+2IYY/J/aDP4zUhj+Er4M/SWqGPxDBhT+9L4Q/5pCFPzGNhj8BAAAAAAAAAAAAAAAAAKDA + AACgQAAAskIAAAAAA4AAAFAAAHBCAQAAAAAAAAAAAAAAAAAAAAAAQMABAAAAAQAAAAAAAAAAAMDAAABAQAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQOAAABg + AgAAAAAAqEEAAK5CAgAAAAAAgD+BgAAAAAAAAADSBAAAAAAAAAAAAAAAAACAAAAAPwAAgD8DgAAAUAAAcEIBAAAAAAAAAAOAAABgWAAAAAAAqEEAAIA/WAAAAIdZhb9X + EMduvUDSBMAX5Yu/iY5jP5W/Xj+u4+w/MW8tvm6uQr4V6+Q/tydEPy5HPj+q5qO/7oUTwA1mKz8OqiQ/LNnOP4A+yz8lCA8/GFzxvk+nAL+icsS/aGkRv4Qazb+epNG/ + SLKmPhhjkz7k358/AsKaP5CA6r+3GJA/t+Jqv3lJHD16dQDAH1FyP6Q9Zj8aLxi+0RdKvpisfL5F/RLAgm6sv6C3y74N1OW+AAAAP/oVDb8MiSbAfpHTv+sKNL82NXDA + Br3mvx367L/SHvO/SJQ8wF706juTOIHAUkdFwFsMCMC4vwrAFBBWvvn3h8BjbFLASdYUwNmWi8BfuYzAMS3dvq/GXcAW64/AP+qQwBOEx79AnyXAlmxnwAdV0r+D2SrA + I/TYv6sMLsCXI9+/tydEv4t1csAZ1TPADSl1wFzHWb+Vv16/ouN4wHUNesDALXvA5EQ8wD1TfcAjWX7AA4AAAGBYAAAAAACoQQAAgD9YAAAAup98P1cBy30/atmLP+RI + iD9+3nQ/WG+AP1VNgD/VQII/71FsP2XQdj9cT4w/2/pvP5DHjD9gX20/zqlqP5gcaD+jSYQ/V2l8PxSXiz9eSIE/oLBtP1Yncz/zQ4w/i7OKP07Cdz/6sHU/VGaBP4l2 + fz9ST34/Ks+CP4rEij+KRWo/EmlmP8QAbT8PSIM/wHF8PxZRZz/aOnI/aJ1sPww+hT901Gs/sgRqP/Eqgj+QvHM/q/B7P3Cvhj8Kj3I/RCxoP2cdjD9Ufoo/GFOBP9q4 + fD+V1IM/b+NwP3ENdj+4voY/96ZoP1l+hT88vIk/PPuFP7fOgz+c83s/AAV4P9raeT/bQnk/WO5tPxRQhT/fNoo/IXGHPzs9iD8u63k/8nqDP914cD+uqYY/7TBsPyiM + iD9wf2c/IIuGP6HFfj8TpHk/X0iBP07+cD8Ld4Q/QIt8P774fz9P9os/UGR7P1Ybhz8CAAAAAQAAAFUAAABFcXVhbCB0ZW1wZXJhbWVudApUaGUgc3RhbmRhcmQgdHVu + aW5nLCB3aGVyZSB0aGUgb2N0YXZlIGlzIGRpdmlkZWQgaW4gMTIgZXF1YWwgc3RlcHMuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQA + gIlEAACWRAEAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOAAABACgAAAAAAqEEAAMBBAADYQQAA8EEAABBC + AAAoQgAAQEIAAFhCAABwQgAA2EIKAAAAAAAAwQhmZsbAZmaGwM3MTMAzM/O/mpmZvwrXI7+PwnW+AAAAAAAAAAAAAAAAAAAAAAAAgD8BAAAAAQAAAAEAAAAfAAAAA4AA + AEgBAAAAAACAPwAAAAAAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpE + AQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAAAAAAAAAAAIA/A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOA + AABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAIEIDgAAA + QA8AAAAAAKhBAADQQQAA+EEAABBCAABAQgAAcEIAAJBCAACoQgAAtEIAAMBCAADIQgAA0EIAANRCAADWQgAA2EIPAAAAAAAAAIcAAIA/BgAAQEAAAKBAAAAQQQAAQEEA + AGBBAACIQQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAYAAAAAAPBCAAB6QwCAfEQAgDtFACCyRQAA+kUGAAAAAACAvwEAAAAAAAAAAAMAAEBA + AABAwAAAAAABAAAAA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAAEAAIA/A4AAAGACAAAAAAAAAAAAgD8CAAAAAACAP4EDgAAAQAQAAAAAAAAAJpNJPv5+Pz8AAIA/BAAA + AAAAAACBAACAP4EDgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAgQAAAADNzEw+AAAAAAAAAAAAAAAAAAAAP+F6NEADgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAgL8A + AAA/A4AAAGACAAAAAADAQQAAkEICAAAAAACAP4EBAAAAAQAAAAEAAAAKAAAAU21hbGwgSGFsbAsAAABtZWRpdW1oYWxsMQAAQEE2lINBAACAPwAAgD8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAMAAAKpDAACAPwAAgD8BAAAABgAAAAAAgD8AAIA/A4AAAGBYAAAAAACoQQAAgD9YAAAA16NwPwIfhYs/7FF4P+xReD8BFK6HPxSuhz8TH4WLPxSu + hz+4HoU/exRuP65HgT8zM3M/cT2KP1yPgj9I4Xo/CteDP6RwfT9mZoY/MzNzPx+Fiz/D9Yg/H4WLP3sUbj9xPYo/H4WLPx+Fiz8Xw/VoP0jhej9xPYo/j8J1P8P1iD9c + j4I/16NwPwAAgD/sUXg/AACAP1yPgj9mZoY/H4WLPx+Faz/D9Yg/XI+CP0jhej/Xo3A/zcyMP7gehT8fhYs/XI+CP8P1aD/D9Wg/DwAAgD/D9Wg/rkeBP2Zmhj9cj4I/ + 7FF4PwAAgD/D9Yg/H4WLPxSuhz8zM3M/ZmZmP6RwfT+PwnU/w/WIP8P1iD8WcT2KP2Zmhj8AAIA/H4WLPwAAgD8K14M/uB6FPxSuhz9mZoY/H4WLP6RwfT8K14M/7FF4 + P0jhej97FG4/pHB9Px+Fiz9cj4I/rkeBP3E9ij8zM3M/zcyMPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYFgAAAAAAKhBAACAP1gAAABhA448 + Vy3xcj6TCnw9ew5zPXACPz7ZWUQ+z/91PspbPD4C3SU+ihwjO0u88j3HHwQ9FFZfPjtABj5Xqo49spwZPmKarj0wIzM+gOWdPAIIdz7JEFU+Qgl1Ph292rtDYWU+HcRt + Pmrkaj5NpyC9mleVPaC0Yz4OIi093RlMPumxAz70WY87UkHUPQfaUz0KhdM91HMKPvYgOT4fDXg+Anl+vOePUj5ZXgQ+fdyHPZKrMTxANX0++zIfPtR3dj6naQI+exQu + PilcDz6cvtU9XI9CPo/CdT0pXI89oTH7Pf7kYj2g3cQ9xjtSPlqXbD7jS0E+U82YPIrESb39lbE9edwGPe/WTz6KQFM+b4NiPmdPLj7lEtI91tR0Pnqjxj2Z8A0+m9Un + Pnf3Pz4ACDU+84N2PsGRsT1ZFhA+y+ZSPcOemD2py8W7IomfPelUaz6+jAA+HFL0PYeZXT4iPqA83495PgEAAAAAAAAAAQAAAAAAAD8AAABAAwAAAAAAgD8AAIA/AACA + PwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8A + AIA/BwAAAAAAgD+amZk+AABAPwAAwD+amZk+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAGBYAAAAAACoQQAAgD9YAAAAbFVfP1cg3Uw/rd5H + P6YCLz9qZCo/SKEePxQ1LT942Og+SzciPxXHKD+3cTk/P3oDP9axFD+swT8/hAYwP389ED9w92I/0losP6YLLD+jLks/mO4gPx1CLT/I7WI/R6pVP8IUSj8kJnA/ta5L + P5s+Qj8iHGg/EJ5sPxEZYj/EYVA/4/dLP0LDST+yJFg/7FpLP0b9NT9RcQ4/PYldP1Cekz9/Hmo/xLN2PzlPcT/t02M/jZCFPzmlXz+66FA/OrhcP2MfTD8tjiE/AH0S + P9GtKj/0wFM/PTddP0UEIz8Hzos/4B5zPw27fT/xAno/QtdiPyq/ej9sE44/0EiaPwOVrj8mWoM/H9yRPyGSWj+BHmo/AS1lP2T+fD8qiDs/hcsxP2WjUj/biWo/wRKC + P2yvmT+3bYc/leOLP2rOVz9BfJI/Kd2eP6xelj8Hd4s/4z2NPwYwlj+6AZk/lim7P5gspz8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAYFgAAAAAAKhB + AACAP1gAAAAAAIA/1wOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAABYAAAAAACoQQOAAABgzgAAAAAAAAAAAIA/zgAAAAAAQMCDAAAAAJcAAIDAAQAAAAAA + AAAAhAAAQMABAAAAAAAAAAD/AAAAAKgAALBBA4AAAGDCAAAAAAAAAAAAgD/CAAAAAAAAAIKrqio/AQAAAAAAAAAA/wAAAAC8AAC4QQOAAABguQAAAAAAAAAAAIA/uQAA + AAAAAACCq6qqPwEAAAAAAAAAAP8AAAAAswAAwEEDgAAAYM8AAAAAAAAAAACAP88AAAAAAAAAggAAAEABAAAAAAAAAACVAACgwAEAAAAAAAAAAP8AAAAAsQAAyEEDgAAA + YLkAAAAAAAAAAACAP7kAAAAAAAAABAAAgL8AAAAAAABAPwAAAAAAAAAAjgAAwD8BAAAAAAAAAACEAABwwAEAAAAAAAAAAP8AAAAAmwAA0EEDgAAAYKgAAAAAAAAAAACA + P6gAAAAAAAAABAAAAMAAAAAAAAAAvwAAAAAAAAAAjgAAQEABAAAAAAAAAACEAAAgwAEAAAAAAAAAAP8AAAAAigAA2EEDgAAAYLoAAAAAAAAAAACAP7oAAAAAAAAABAAA + QMAAAAAAAADgvwAAAAAAAAAAjgAAkEABAAAAAAAAAACEAACgvwEAAAAAAAAAAP8AAAAAnAAA4EEDgAAAYLcAAAAAAAAAAACAP7cAAAAAAAAABAAAgMAAAAAAAABAwAAA + AAAAAAAAjgAAwEABAAAAAAAAAAD/AAAAAKAAAOhBA4AAAGC1AAAAAAAAAAAAgD+1AAAAAAAAAARVVVXAAAAAAAAAgL8AAMC/AADAvwEAAAAAAAAAAIIAAMA/ggAAAACG + AACgQAEAAAAAAAAAAP8AAAAAngAA8EEDgAAAYM8AAAAAAAAAAACAP88AAAAAAAAABKqqKsAAAAAAAACAPwAAQMAAAEDAAQAAAAAAAAAAggAAQECCAAAAAIb//39AAQAA + AAAAAAAA/wAAAAC4AAD4QQOAAABgtwAAAAAAAAAAAIA/twAAAAAAAAAE////vwAAAAAAAADAAAAAAAAAAACEAAAQQIIAAAAAhv//P0ABAAAAAAAAAAD/AAAAAKAAAABC + A4AAAGCoAAAAAAAAAAAAgD+oAAAAAAAAAAUAAADAAAAAAKqqqr8AAIDAAAAAAAAAAACD//+/P4IAAAAAhv7//z8BAAAAAAAAAAD/AAAAAJEAAARCA4AAAGCbAAAAAAAA + AAAAgD+bAAAAAAAAAAUAAIC/AAAAAKqqKr8AAADAAAAAAAAAAACD//8/P4IAAAAAhv7/fz8BAAAAAAAAAAD/AAAAAIQAAAhCA4AAAGCrAAAAAAAAAAAAgD+rAAAAAAAA + AIMAAEDAAQAAAAAAAAAAhQAAgMABAAAAAAAAAAD/AAAAAJwAAAxCA4AAAGC5AAAAAAAAAAAAgD+5AAAAAAAAwAUAAIA/AAAAAAAAwMAAAAAAZmYmwGZmJsABAAAAAAAA + AAD/AAAAALAAABBCA4AAAGCXAAAAAAAAAAAAgD+XAAAAAABAwIMAAIA/AgAAQMAAAAAAAAAAAIgAAEBAAQAAAAAAAAAAgYAy/z8BAAAAAAAAAAD/AAAAAAAAFEIDgAAA + YJ4AAAAAAAAAAACAP54AAAAAAADAAgAAoMAAAAAAAAAAAAQAAIDAAACAPwAAAEAAAAAAAAAAAP8AAAAAlQAAGEIDgAAAYJEAAAAAAAAAAACAP5EAAAAAAIDABgAAgD8A + AAAAAAAAwAAAAAAAAABAAAAAAAAAAAADAAAAwAAAgD8AAAAAAAAAAIEAAIDAAgAAQEAAAAAAAAAAAP8AAAAAAAAcQgOAAABgjAAAAAAAAAAAAIA/jAAAAAAAgMCBAAAA + AAcAAEDAAACAvwAAgEAAAIA/AAAAAAAAQMAAAAAAAAAAAIEAAEDAAwAAAEAAAEDAAAAAAAAAAACEAAAAQAEAAAAAAAAAAIUAAEDAAQAAAAAAAAAA7AAAIEIDgAAAYIMA + AAAAAAAAAACAP4MAAAAAAMDAAwAAAMAAAIDAAAAAAAAAAAD+AAAkQgOAAABgfwAAAAAAAAAAAIA/fwAAAAAAQMADAACAvwAAQMAAAAAAAAAAAAEAAABAAAAAQAMAAAAA + AACAwAAAAEAAAABABAAAgMAAAADAAABAwAAAAAAAAAAAgQAAAEADAACQQAAA4EAAAAAAAAAAAIMAAEDAAQAAQEAAAEBAAwAAQMAAAEBAAAAAAAAAAADfAAAoQgOAAABg + fAAAAAAAAAAAAIA/fAAAAAAAwL8DAAAAAAAAgL8AAAAAAAAAAPcAACxCA4AAAGCEAAAAAAAAAAAAgD+EAAAAAABAwIEAAIC/AQAAAAAAAAAAgQAAAMABAAAAAAAAAACE + AAAAwIEAAAAA9AAAMEIDgAAAYIAAAAAAAAAAAACAP4AAAAAAAEDAAwAAwMAAAEDAAAAAAAAAAAAEAAAAQNrTgMAAAAAAAAAAwAAAAMAEAAAAQAAAAAAAAEDAAAAAAAAA + AADxAAA0QgOAAABgdQAAAAAAAAAAAIA/dQAAAAAAwMAEAABAwAAAAAAAAIC/AAAAAAAAAACCAACAwAMAAABAAAAAAAAAgD8AAIA/AQAAAAAAAAAAiwAAAECCAAAAANcA + ADhCA4AAAGBjAAAAAAAAAAAAgD9jAAAAAACAwIIAAAAA3wAAPEIDgAAAYF0AAAAAAAAAAACAP10AAAAAAABAAgAAoMAAAAAAAAAAANkAAEBCA4AAAGBnAAAAAAAAAAAA + gD9nAAAAAAAAAAIAAEDAAAAAAAAAAADjAABEQgOAAABgYQAAAAAAAAAAAIA/YQAAAAAAwMACAAAAwAAAAAAAAAAAgQAAAMADAAAAAAAAAEAAAAAAAAAAAIQAAEDAAQAA + AAAAAAAA0AAASEIDgAAAYFkAAAAAAAAAAACAP1kAAAAAAADAAwAAQMAAAIC/AAAAAAAAAACBAABAwAEAAAAAAAAAAIMAAABAAgAAwMAAAAAAAAAAAMkAAExCA4AAAGBS + AAAAAAAAAAAAgD9SAAAAAACgwAcAAMDAAACAQAAAAAAAAADAAACgwAAAoEAAAAAAAAAAAIIAAEBABAAAAAAAAIBAAABAwAAAAAAAAAAAwQAAUEIDgAAAYFgAAAAAAAAA + AACAP1gAAAAAAIC/gQAAQEAEAACAvwAAgD8AAADAAAAAAAAAAADQAABUQgOAAABgVwAAAAAAAAAAAIA/VwAAAAAAAMADAADAwAAAQEAAAAAAAAAAAAIAAKDAAAAAAAAA + AAAEAAAAQAAAAAAAAABAAAAAAAAAAADKAABYQgOAAABgTAAAAAAAAAAAAIA/TAAAAAAAgMAHAAAAwAAAwD8AACDAAACQwAAAAAAAAIA/AAAAAAAAAACEAACAwAEAAAAA + AAAAAIMAAEDAAwAAAAAAAEDAAAAAAAAAAAC0AABcQgOAAABgTAAAAAAAAAAAAIA/TAAAAAAAQMADAACAvwAAgEAAAIC/AACAvwEAAAAAAAAAAMUAAGBCA4AAAGBMAAAA + AAAAAAAAgD9MAAAAAABAwAdmZoY/AAAAQAAAgL8AAEDAAAAAAAAAgL8AAAAAAAAAAAMAAMDAAACAwAAAAAAAAAAAAwAAoMAAAEDAAAAAAAAAAAC7AABkQgOAAABgSAAA + AAAAAAAAAIA/SAAAAAAAgL8CAACAPwAAAAAAAAAAigAAwL8BAAAAAAAAAAC3AABoQgOAAABgOgAAAAAAAAAAAIA/OgAAAAAAAMABAAAAQAAAAEADAACAwAAAQMAAAAAA + AAAAALMAAGxCA4AAAGA2AAAAAAAAAAAAgD82AAAAAABAwAcAAEBAAACAPwAAgL8AAADAAABAwAAAAMAAAAAAAAAAAIX3OSLAAQAAAAAAAAAApQAAcEIDgAAAYDkAAAAA + AAAAAACAPzkAAAAAAEDAAQAAgD8AAIA/AgAAQMAAAIA/AACAP4EAAAAAhQAAAMACAACAwAAAAAAAAAAAqAAAdEIDgAAAYDcAAAAAAAAAAACAPzcAAAAAAADABQAAAAAA + AIC/AACAQAAAgMAAAAAAAAAAAIeqqirAAQAAAAAAAAAApgAAeEIDgAAAYC8AAAAAAAAAAACAPy8AAAAAAEDABAAAAAAAAADAAABAwAAAAAAAAAAABgAAwMAAAAAAAAAA + QAAAAAAAAEDAAAAAAAAAAACBqqqqvwEAAAAAAAAAAJ4AAHxCA4AAAGAwAAAAAAAAAAAAgD8wAAAAAAAAAAQAAIBAAACgwAAAQEAAAAAAAAAAAKoAAIBCA4AAAGAwAAAA + AAAAAAAAgD8wAAAAAADAPwoAAMC+0o8owAAAAAAAAKBAAAAAAAAAoMAAAAAAAACAPwAAgL8AAAAAAAAAAKQAAIJCA4AAAGApAAAAAAAAAAAAgD8pAAAAAAAAAAMAAIC/ + AAAgwAAAAAAAAAAAhAAAIMACAABAQAAAAAAAAAAAnAAAhEIDgAAAYCQAAAAAAAAAAACAPyQAAAAAAAAABAAAoL8AAIA/AAAAAAAAwD8AAMA/BgAAAMAAAAAAAADAPwAA + cMAAAMA/AAAAAAAAAACXAACGQgOAAABgJwAAAAAAAAAAAIA/JwAAAAAAAAAHAAAAwAAAgD8AAAAAAAAAQAAAAMAAAEBAAAAAAAAAAAACAACgwAAAAAAAAAAAmwAAiEID + gAAAYCYAAAAAAAAAAACAPyYAAAAAAABABwAAAMAAAIC/AAAAQAAAAMAAAAAAAACAwAAAAAAAAAAAAgAAgMAAAAAAAAAAAJoAAIpCA4AAAGAhAAAAAAAAAAAAgD8hAAAA + ZmZmPwNmZma/AABAwAAAAAAAAAAAgQAAgL8BAAAAAAAAAAACAABAwAAAAAAAAAAAlQAAjEIDgAAAYBsAAAAAAAAAAACAPxsAAAAAAIBABQAAAMAAAIA/AAAAAAAAgL8A + AAAAAAAAAIL///+/AQAAAAAAAAAAjwAAjkIDgAAAYBoAAAAAAAAAAACAPxoAAAAAAABAAgAAAAAAAADAAAAAwAcAAEDAAACAvwAAAAAAAKDAAAAAAAAAgL8AAAAAAAAA + AI4AAJBCA4AAAGAdAAAAAAAAAAAAgD8dAAAA+///PwYAAIC/ZmaGPwAAgL8BAADAAACAvwAAAAAAAAAAlQAAkkIDgAAAYBwAAAAAAAAAAACAPxwAAAAAAAAAgwAAAMCC + AAAAAJQAAJRCA4AAAGAbAAAAAAAAAAAAgD8bAAAAFtr+PwMttH0/AABAQAAAAMAAAADAAQAAAAAAAAAAggAAgD8CAAAAwAAAAAAAAAAAjgAAlkIDgAAAYBkAAAAAAAAA + AACAPxkAAAAAAABAAiFH/j8AAAAAAAAAAAYAAEDAAADAvwAAgL8AAAAAAABAQAAAAAAAAAAAjgAAmEIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAABAAwAAAAAAAIC/AAAA + AAAAAACTAACaQgOAAABgFgAAAAAAAAAAAIA/FgAAAAAAAAAIZmaGPwAAAMBmZoa/AAAAAGZmhr8AAAAAZmaGvwAAAAAAAAAAjAAAnEIDgAAAYBUAAAAAAAAAAACAPxUA + AAAA8sO7BQAAAAAceH4/AAAAAAAAAEAAAADAAAAAwAEAAAAAAAAAAIwAAJ5CA4AAAGAUAAAAAAAAAAAAgD8UAAAAAACAvwQAAAAAAACAwAAAgL8AAAAAAAAAAI4AAKBC + A4AAAGATAAAAAAAAAAAAgD8TAAAAAAAAwAMAAABAAAAAAAAAAMAAAADAAwAAAAAAAEDAAAAAAAAAAACKAACiQgOAAABgEgAAAAAAAAAAAIA/EgAAAGZmZr8GAAAAAAAA + QMAAAAAAAACAwAAAQEAAAAAAAAAAAIoAAKRCA4AAAGARAAAAAAAAAAAAgD8RAAAAAACAP4EAAIC/gQAAQMADAAAAAAAAAMAAAAAAAAAAAIgAAKZCA4AAAGAQAAAAAAAA + AAAAgD8QAAAAAAAAQAQAAAAAqqoqv6qqKj8AAAAAAAAAAIoAAKhCA4AAAGAPAAAAAAAAAAAAgD8PAAAAAAAAQAIAAAAAAAAAwAAAAMABAAAAAAAAAACJAACqQgOAAABg + DgAAAAAAAAAAAIA/DgAAAAAAAEABAAAAAAAAAACLAACsQgOAAABgDQAAAAAAAAAAAIA/DQAAAAAAAEABAAAAAAAAAACKAACuQgOAAABgDAAAAAAAAAAAAIA/DAAAAAAA + AACLAACwQgOAAABgDAAAAAAAAAAAAIA/DAAAAAAAAACLAACyQgOAAABgCwAAAAAAAAAAAIA/CwAAAAAAAACKAAC0QgOAAABgCgAAAAAAAAAAAIA/CgAAAAAAAACJAAC2 + QgOAAABgCgAAAAAAAAAAAIA/CgAAAAAAgL8DAAAAAAAAwL8AAAAAAAAAAIUAALhCA4AAAGAJAAAAAAAAAAAAgD8JAAAAAAAAwAMAAABAAACAPwAAAAAAAAAAhAAAukID + gAAAYAkAAAAAAAAAAACAPwkAAAAAAAAAAQAAAMAAAADAAQAAAAAAAAAAhAAAvEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAEA/AwAAIMAAAOC/AAAAAAAAAACDAAC+QgOA + AABgCAAAAAAAAAAAAIA/CAAAAAAAwD8DAABAwAAAwL8AAAAAAAAAAIMAAMBCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMJCA4AAAGAHAAAAAAAAAAAAgD8HAAAA + AAAAQAMAAAAAAADAvwAAAAAAAAAAggAAxEIDgAAAYAcAAAAAAAAAAACAPwcAAAAAAEDAAgAAwL8AAAAAAAAAAIMAAMZCA4AAAGAHAAAAAAAAAAAAgD8HAAAAAAAAAIYA + AMhCA4AAAGAHAAAAAAAAAAAAgD8HAAAAmpkZwAL//z+/AAAAAAAAAACDAADKQgOAAABgBwAAAAAAAAAAAIA/BwAAAGdmBsAC//+/vgAAAAAAAAAAgwAAzEIDgAAAYAcA + AAAAAAAAAACAPwcAAABnZua/AQAAAAAAAAAAhAAAzkIDgAAAYAcAAAAAAAAAAACAPwcAAAAAAAAAAgAAQMAAAAAAAAAAAIMAANBCA4AAAGAHAAAAAAAAAAAAgD8HAAAA + AAAAAAIAAADAAAAAAAAAAACDAADSQgOAAABgBwAAAAAAAAAAAIA/BwAAAAAAAAACAACAvwAAAAAAAAAAgwAA1EIDgAAAYAcAAAAAAAAAAACAPwcAAAAAAAAAhgAA1kID + gAAAYAcAAAAAAAAAAACAPwcAAAAAAAAAhgAA2EIDgAAAYAQAAAAAAAAAAACAPwQAAAAAAAAAg6WZ9D0DgAAAQBQAAAAAAKhBAADAQQAA2EEAAPBBAAAIQgAADEIAABRC + AAAYQgAALEIAAEBCAABQQgAAXEIAAHBCAAB8QgAAhkIAAJBCAACeQgAArEIAAMJCAADYQhQAAADw54E/gi/3hT8Bt7KCP7eygj+BL/eFP4Nhm4A/gi/3hT8FpCt8Py/3 + hT84KW4/UKBhP1QjTD8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEA + AAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAAAAAEAAAA + AgAAAAMAAAAEAAAAAQAAAAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAASAEAAAAA + AAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMAAAAHAAAAAQAAAAAAAAAOAAAAEAAAAAAAAACPwvU9AACAPwAAAAAAAAAAAADwQQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAAEAAAAAAAAADgAAABAAAAAAAAAAAABcQwAAgD8AAAA/AAAAQACAO0UAAADAAACAPwAAgD8AgLtFAACgQDMzMz8AAEBA + AAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAEAAAABAAAAAACAPwAAoMAAAAAAAwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAACgR2E+AACAPhSuhz+YKT3C/DatQwQAAAAFAAAAAAAQwQAAEMIAAMDC + AADAwgAAwMIFAAAAAAAAAKabRDwAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAFyPgj+amZk+FK6HP2jWKkLNw6xD + BAAAAAUAAAAAABDCAAAAwQAAwMIAAMDCAADAwgUAAABCYGU8bxKDOgAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAA + UrgeP4TrEUBG4bo/ZvYeQwAAAAAEAAAABQAAAAAAMMEAADDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AQAAAAAAAAAEAAAAAAAAAAMAAABgzMw9mpk5QK5HYT+8eh3DAAAAAAQAAAAFAAAAAACAPwAAcMIAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAwAAAM3MTD+PwnW+AADAPwAAkEEAAAAAAAAAAAAAAAABAAAA + AAAAABE4VjiFMgAAUHJWSyYAAAJYRlRQdTIAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3Atwsim+ioUZyMG+pbLVooGRsbGE0MgAAdGR0 + TaIGAAAQAAAAQW50LiBQZXRyb2YgV2FybQAAAAAHAAAATW9kYXJ0dCQAAABQcmVzZXQgd2l0aCBhIHdhcm0gYW5kIG1lbGxvdyBzb3VuZC4AAAAARAEAABAAAABBbnQu + IFBldHJvZiBXYXJtAAAAADMGAABHUlVVAAAABgAAAAEDwE1UcmsAAAYdAP8CL0NyYWlnIFJpY2hhcmRzIC0gTGVhdmluZyBBZ2FpbiAtIENyYWlnIFJpY2hhcmRzhGKw + QAIYQBgWQDQYQFAWQGwYQH+GSJBHRIEyRwCFGEhcgSZIAIYKsEBqGEA2FkAKGEAABpBHKxpDLgRAKwo+LxwwJIF4sEAEFkAuGEBWFkB2GEB/gwKQQAA6PgAgRwA2QwAG + NyaHDEMmJD4mhyo7GEw+AI1KQwCLEkdWCj5EgQg+ABpHAIUmMAAQOwBWSEQMQERKNwAwQAAYSACGcrBAWhRAJBhAAByQRzcMQzkUPjIGMCqDIrBAKhZAVhhAf4J0kDcp + Aj4AIkcAGkMAhiJDKCA+J4cEOxdwPgCJYjAAPjsALjcADkMAjHpCKwywQEgEkDwmFLBADAyQOSEKsEAAFJAyIIE8sEAsFkBdGEB/gmaQQgAcOQA0PAASMgCJcEMnBjsg + ArBAbBZAOgyQKx4MsEAGFEAAhCxAFBZAPhhAWhZAdhhAf4EckDIch3w3HCI7AIF2QwCEKjcAUCsAZDIAhxiwQG4CkEMhEDwgBrBAPBRACAqQKxsQsEAACJA0GINEsEAI + FkAmFkBEGEBqFkB/jQqQPAAkQwCFPENACjxEFCsACjQAgi5DAB48AIR8QyQMMh0EsEBGDJA7JAqwQAwKkCsdELBAAIM0QBwWQEAYQGUWQH+JaJA7AIEANxeJakMAlmhH + ToFiNwCCOjIAAisAgi5HAC5ISoFKSACGFrBAYhRAHBhAAAaQRygWQzcUQDwGPi0iJClKsEA6FkB4GEB/ghqQJACCRj4APEAAEkcAKEMAfjQxh05DNBI8LoYOQwAEPABu + NyGINEAfBjsZiAg7ADJAAIguNwAaR02BIjQAVkcAhQpIVoFuSACFCrBAeBhAJhZAAAKQR0sWPkMsJC52sEBMFkB/giSQJAAMMDYSPgAMRwCCDjAAgSg0KoMUNz2ENENC + Ejw4hSI3AIFKPACBAkMAhjo3JIY8NwCBMjQAiF6wQGcWQCoEkEIrBD41ELBAAAKQOSMuNhoMKiiBFLBAChRAMhhATBhAcRZAf4Q8kDkACioAQDYACEIAED4ASEUjg0hF + AIRUQygUOyEIsEBgFkAsFkAACJA3FQYrGYJ6sEAiGkBKFEB4GEB/gwqQMiRCOwAWQwCHID4khRY+ADg3ADAyAEgrAIh0OzQAQzsINywQsEB9FEBBApAoJg4vKwiwQAoG + kDQjELBAAIF+QBYYQDgWQFgWQH+DMJBDAIIMQD0WNwAQOwBMQAByNACFakBQDDc2IDQph2g3ABYoAERAABYvAGg0AIcSQzIUPDwEsEBiApA5LhSwQCoSkDAwAiknBLBA + ABSQNRmCXLBABhhAKBZASBhAdBZAf4N4kDwAVEMAgwI5AIRgQC0ePCiBPDUAgSQwAE4pAIMAQABKPAAOOShmOQCGSLBAZw6QPCwCNCoIsEAsFkAAJpAwGwokIoMesEAi + FkBMFkB0GEB/ghyQNAAUMAAOJABMKxqHRDQxejwAgQI0AIEON0lYKwCDJDcABjxkhDY8AIMWOTkwMi8KsEBkCpArKQqwQBwYQACBYJA5AA47M4F2sEA8FkB/glaQNy8m + OwCHPkMoFDspiTg3AD5DAA47AEgyACQrAJUIR0iHOEg4AkcAhBawQH+CHpBIAIECsEAAUJBHKQxAMhA+Hh4wI4IesEB+hHCQQAAQPgAKNy0gRwCHXkM0Fj4qk3g+AIRI + QwCJKkdTgWQwAIRORwBoSDkINwCBQEgAhRIwGmawQEIUQAQYQAAQkDsaBDcWUkcyIj8rgwywQA4UQEEYQHgWQH+CPJBHAIEKPwCJIkMtghhDAI1yPyEoRSiBXD8AKEUA + hSQ7AIRiNwCBJjAAgzY+MoN0PgAmOzSEODsAMjlDhFY5ADgyIoQgMgCCbCshggqwQEoWQAoCkDIdFrBAAIEEkDkzCj4vgkw5ACw7L4YSNyc2sEAiFkBkFkB/g2aQNwCB + fisABDsACD4AgTYyAJ5YNyiIfD4jgRI3AF5HHFBPEUZWIl4+AExfHyxPAExWACxHAIoAXwDoILBAXRZANhhAGBZAAAD/LwBQcnNVgisAAEcAAAAKAAAAYW50LnBldHJv + ZgAAAAARAAAAR3JhbmQgQW50LiBQZXRyb2YAAAAAAAAAAHByb21lAAAAAQAAAAEAAAABAAAACgAAAGFudC5wZXRyb2YAAAAAEQAAAEdyYW5kIEFudC4gUGV0cm9mAACA + PwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAA + AQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAA + AAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAACQHNC/VyD1Yb8AJYq/IAMdv8DcLr+sdaK/4EMAvwDipr5YZU0/oG7+vgBfzj2AaRa/UGVsv+im + z7/gowe/UKDdv8DQhL5g57c+ANLWPeCwaT8AVOG8wNGav2DSP79AAAG/wIy3vwDPkr5wam2/wMRkv1A+Kr9AaOi+TOgMQACOdL286ta/sHVUvwBrnz0AIZe+TBnDv0AY + IT8ADRe/AM16vfLQAkBAeKA/+NnIP/bNDcCA/ru+HMTJvygQBMBgPtk+0Fp+vyBNmr4AYRq+EHN4vx0egkCMzJM/4LPoPoBOzD5A2D++uOarPzDbrr8gx4O/sBd+v+Yx + YMAwOrk/AO5tvtARi78siyvAYNuePsCYSr9QxKM/MNghv/45ZcBAae0+hl8jwFjjvr9AVZS/EEb/v4jlGcDsKlTAlFD4PywhA8AQDJu/LLkzwNDLUr+5X47AvC2fv6gT + ur9wtbq/QLrjvwMAAAABAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAEAAAAAAAAAutDGPQOA + AABgWAAAAAAAqEEAAIA/WAAAAFIvhT+BPIuGP4JSL4U/Ac1YiT/NWIk/BDyLhj9S7oc/Ui+FP1/agz9f2oM/gTyLhj8DUi+FPzyLhj9SL4U/Ui+FP4HNWIk/A+rKij/N + WIk/6sqKP+rKij+CzViJPwhS7oc/Ui+FP1/agz9SL4U/PIuGP1IvhT88i4Y/X9qDP1/agz8BUi+FP1IvhT+BUu6HPwI8i4Y/Uu6HP1Luhz8DPIuGPyuMgj9S7oc/Uu6H + PwHqyoo/6sqKPwcGx40/t+SQP4hRjz/oRIw/6sqKP1Luhz9f2oM/X9qDPwUrjII/CiZ7P37HeD8KJns/n+5xP5/ucT8BlbtvP5W7bz+Bn+5xPwOpc2s/r5JtPz9eaT8/ + Xmk/BDFSZz+pc2s/P15pP0FPZT9BT2U/Ccx6Xz/HY2E/zHpfP1jwWT8Jml0/iKpUP1jwWT8nZVY/XkpRP15KUT8BiKpUPwEAAAAAAAAAAAAAAAAAoMAAAKBAAACyQgAA + AAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAEDAAABAwAEAAAABAAAAAAAAAAAAwMAAAEBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAA + rkICAAAAAACAP4GAAAAAAAAAANIEAAAAAAAAAAAAAAAAAIAAAAA/AACAPwOAAABQAABwQgEAAAAAAAAAA4AAAGBYAAAAAACoQQAAgD9YAAAAh1mFv1cQx269QNIEwBfl + i7+JjmM/lb9eP67j7D8xby2+bq5CvhXr5D+3J0Q/Lkc+P6rmo7/uhRPADWYrPw6qJD8s2c4/gD7LPyUIDz8YXPG+T6cAv6JyxL9oaRG/hBrNv56k0b9IsqY+GGOTPuTf + nz8Cwpo/kIDqv7cYkD+34mq/eUkcPXp1AMAfUXI/pD1mPxovGL7RF0q+mKx8vkX9EsCCbqy/oLfLvg3U5b4AAAA/+hUNvwyJJsB+kdO/6wo0vzY1cMAGvea/Hfrsv9Ie + 879IlDzAXvTqO5M4gcBSR0XAWwwIwLi/CsAUEFa++feHwGNsUsBJ1hTA2ZaLwF+5jMAxLd2+r8ZdwBbrj8A/6pDAE4THv0CfJcCWbGfAB1XSv4PZKsAj9Ni/qwwuwJcj + 37+3J0S/i3VywBnVM8ANKXXAXMdZv5W/Xr+i43jAdQ16wMAte8DkRDzAPVN9wCNZfsADgAAAYFgAAAAAAKhBAACAP1gAAAC6n3w/VwHLfT9q2Ys/5EiIP37edD9Yb4A/ + VU2AP9VAgj/vUWw/ZdB2P1xPjD/b+m8/kMeMP2BfbT/OqWo/mBxoP6NJhD9XaXw/FJeLP15IgT+gsG0/VidzP/NDjD+Ls4o/TsJ3P/qwdT9UZoE/iXZ/P1JPfj8qz4I/ + isSKP4pFaj8SaWY/xABtPw9Igz/AcXw/FlFnP9o6cj9onWw/DD6FP3TUaz+yBGo/8SqCP5C8cz+r8Hs/cK+GPwqPcj9ELGg/Zx2MP1R+ij8YU4E/2rh8P5XUgz9v43A/ + cQ12P7i+hj/3pmg/WX6FPzy8iT88+4U/t86DP5zzez8ABXg/2tp5P9tCeT9Y7m0/FFCFP982ij8hcYc/Oz2IPy7reT/yeoM/3XhwP66phj/tMGw/KIyIP3B/Zz8gi4Y/ + ocV+PxOkeT9fSIE/Tv5wPwt3hD9Ai3w/vvh/P0/2iz9QZHs/VhuHPwIAAAABAAAAVQAAAEVxdWFsIHRlbXBlcmFtZW50ClRoZSBzdGFuZGFyZCB0dW5pbmcsIHdoZXJl + IHRoZSBvY3RhdmUgaXMgZGl2aWRlZCBpbiAxMiBlcXVhbCBzdGVwcy4MAAAAAADIQgAASEMAAJZDAADIQwAA+kMAABZEAAAvRAAASEQAAGFEAAB6RACAiUQAAJZEAQAA + AAAAAAAEAAAAAADcQzwAAABFAAAARgAAAAB/AAAAAAAAAAcAAABEZWZhdWx0AAAAAAAAAAACAAAAA4AAAEAKAAAAAACoQQAAwEEAANhBAADwQQAAEEIAAChCAABAQgAA + WEIAAHBCAADYQgoAAABmZgbBCDMz08AzM5PAMzNTwAAAAMBmZqa/MzMzv5qZmb4AAAAAAAAAAAAAAAAAAAAAAACAPwEAAAABAAAAAQAAAB8AAAADgAAASAEAAAAAAIA/ + AAAAAAAAgD8DgAAAYFgAAAAAAKhBAACAP1gAAAAAAIA/1wOAAABgWAAAAAAAqEEAAIA/WAAAAI2pkj9XUWOTPxtwkD9jBIk/FRF2P+g0mD8kHJY/MFaRP5RRhz9qq3I/ + WiBsP+2NgT8hHoc/axWCP4QjhT9f0YI/PLeOP011gj9FUHc/3yGTP7/Shj9o44g/a4qNP5HkeD/tpo4/BRCSP2ZVdz8gP4c/VDRnP5EZjD+gloc/R0+FPz2HZj/i3o8/ + LA6SP8xubT8jZo0/SlVWP18ZlD8FbWQ/b8F0P0Gihz+p+3U/f0uIP2oiZT+hiIo/ksyJP8btfT+4oYM/KWp+P9+FWj9pq3Q/dd1fPz7Mkz9NknY/VSlzP/bkXz+xeos/ + ASSLP/Q5gz9ryXk/G7JxP095gD/lyn0/8zF0P96vZD/v93U/PDlvP29BbD8inWQ/2k1uPyIeXT+n7Vg/jVk6P5jQOj8ZyDM/VgEgP9SbOT8g+jY/ecgzP9pxLj/rEio/ + DSAcP2KuFj+ODQo/oa0jP9H7Jz/qLSU/A4AAAFAAAHpEAQAAAAAAAAADgAAAUAAAekQBAAAAAACAPwAAAAADgAAAUAAAcEIBAAAAAAAAAAAAgD8DgAAAUAAAcEIBAAAA + AAAAAAEAAAAAAAAAAAAAAM3MzL3NzMw9AQAAAAAAAAAAAAAAAAAAvwAAAD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEA + AK5CAgAAAAAAAACBAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAAAoQgOAAABADwAAAAAAqEEAANBBAAD4QQAAEEIAAEBCAABwQgAAkEIAAKhCAAC0QgAAwEIA + AMhCAADQQgAA1EIAANZCAADYQg8AAAAAAAAAhgAAgD8HAABAQAAAoEAAAOBAAAAgQQAAQEEAAFBBAABwQQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4ID + gAAAIAIAAAAAAHpDAAB6QwIAAAABAAAAAgAAAAAAAMABAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/ + gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAAAAAAAAAAAAAAAAA/exQuQAOA + AABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAsAAABKYXp6IHN0dWRpbwoAAABqYXp6c3R1 + ZGlvAABAQCwzm0EAAIA/MzPzPwAAAADAp7U88P//v3CkSj4AAAAAmpmZPhr0n0PNzIw/AACAPwEAAAAGAAAAAACAPwAAgD8DgAAAYFgAAAAAAKhBAACAP1gAAADTYYo/ + Vy+lgz87nog/gWWHP7xahj9yh4A/jRBtPyCZdj+OUoU/7jp6P724iD/bsns/EEFqPwtLdD89kG4/QNiEPxb0gD/+8H0/3G6IPxv9iz/Xr3E/YqyKP8CZgj+/5n0/rs1w + P2RsZj+pv4E/l4mKPwakjD+r1mk/eJR6P79gfT90y4c/T8x3P9qWZj8Wvnw/OkRxP9I7gT8Sa2w/1JqGP+sneT/Cw3k/T96GPxNsej/2Zmc/qIyLP3lddj8OX4c/HxmJ + P/1OiT/nJ2g/iON9P40Efj91SGc/VSNwP2s/hz8iIWg/jFt5PxvAij8xdIs/vLyIP9vVhj8LRm4/8JmCP3E9ij/J84M/miV9P31giz+9qn0/S5eMP9ErfD/s4oU/dzp6 + P2b9iD9cO3w/luVyP1vpfz/t6YA/j3GDP5/ocz9+PnU/7QmJP2hAZz/Go2w/4jmCP0Fsdz+TZnM/iTqHPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAAD + gAAAYFgAAAAAAKhBAACAP1gAAABUBAa+V4GNjz5j/+Y9Zew6vho7Ib7PED4+OAB2OzOpnL25joo+xN3sPSLEZz4xBbm9tBJtvluZSr04J5C+LjGVPsY+Zr4Slpe9fNOO + PZC3lT6eKr69XhCLvaLyjD53SYG+JEQUvuRkcb6DxgS+uz3/vfPKPz6SUww9yObUPYkQ3bpMbZg++Ml/O7eIkD5jgQk+KfBFvWDdBz4w6z8+19BAvpOJAr4vk4W+7LN8 + vY8rib61h689TcI0vkxTJb73txU+QDNTPfmmWT75Ooy+QWYLPqJQSL6TV6K8r+invWz0870REFC+FxmTvrj2OrtLv6+9SrGQvvYvsr3NE269+edMvki56L2g9YW+bKuD + PlxIIL7ZNNu9ca3YPYVj8jwfm4++ByWWPiq4Cb0EX5W+vizTPYSkmj2zdX4+8oFUPuD7aTyUBIE9fvcSvkE+HD4ZF5A+fGl7vSWGJb5QiTK+2cHSvQEAAAAAAAAAAQAA + AAAAAD8AAABAAwAAADCacD9diY8/ZxyOPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAA + qEEAAHBCAADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD+hZ5A+ZmYmP2Zmpj+amZk+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAGBYAAAA + AACoQQAAgD9YAAAAmmmEP1e7v3o/ekh9PyqTbz9YMWo/1P2BPxOfaj8+u2c/+01NPyOXbT+DqVo/16hsP2u/fT86QoY/O3pxPwL+hz/p/l8/ObhXPzhdWz9qkkU/sUVg + P61Ugj8GkG4/7ElxPygogz999Go/Qx92P8a0fD/IpGY/JDdqP9CqKz/Th2c/x9uGPxwpeD8AE14/cApJP9cZiD+LQ1A/9x5xPyRRWD8efSA/o+4+P+SrND/h5W4/HjVS + P1tHgj8j/Yg/nC5KP3HjUT+fB2Y/HfNxPz0WgD+nzyU/+vNQP8rYZj/GBWw/Xt6BP/P5Wz/U24o/UruJP5ZQlj/VyKw/CoNvP8dWhD8nBJ0/e+jIPy7Ykj/f17Q/W66C + PycEnT9nZsY/4zyUPyOfvT/9mrg/e+jIP9icrT97ELE/tWrDP5R6aT8WQcM/78ujP475sz++loc/gLG9P0ipgz/W54U/l22MPyC1mz8DgAAAQAMAAAAAAKhBAABwQgAA + 2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAYFgAAAAAAKhBAACAP1gAAAAAAIA/qJ86gD+CAACAP4Q4OXg/AQAAgD8AAIA/BL6KdT8e + 33Q/mY13PwAAgD8AAIA/nwEAAABYAAAAAACoQQOAAABgAwEAAAAAAAAAAIA/AwEAAAAAAAD/AAAAAP8AAAAAggAAsEEDgAAAYAMBAAAAAAAAAACAPwMBAAAAAAAA/wAA + AAD/AAAAAIIAALhBA4AAAGDcAAAAAAAAAAAAgD/cAAAAAAAAAP8AAAAA2wAAwEEDgAAAYPkAAAAAAAAAAACAP/kAAAAAAAAA/wAAAAD4AADIQQOAAABg+QAAAAAAAAAA + AIA/+QAAAAAAAACBAABAwAEAAAAAAAAAAP8AAAAA9AAA0EEDgAAAYOUAAAAAAAAAAACAP+UAAAAAAAAAgQAAAMACAAAAQAAAAAAAAAAA0gAAwMABAAAAAAAAAAD/AAAA + AIoAANhBA4AAAGDSAAAAAAAAAAAAgD/SAAAAAAAAAP8AAAAA0QAA4EEDgAAAYPIAAAAAAAAAAACAP/IAAAAAAAAA/wAAAADxAADoQQOAAABg8gAAAAAAAAAAAIA/8gAA + AAAAAAD/AAAAAPEAAPBBA4AAAGDMAAAAAAAAAAAAgD/MAAAAAAAAAP8AAAAAywAA+EEDgAAAYM4AAAAAAAAAAACAP84AAAAAAAAA/wAAAADNAAAAQgOAAABgzgAAAAAA + AAAAAIA/zgAAAAAAAAAEAABAwAAAAAAAAEDAAAAAAAAAAAD/AAAAAMgAAARCA4AAAGDOAAAAAAAAAAAAgD/OAAAAAAAAAAIAAKDAAAAAQAAAAECBAAAAAP8AAAAAyAAA + CEIDgAAAYMkAAAAAAAAAAACAP8kAAAAAAAAAgQAAQMABAAAAAAAAAAD/AAAAAMQAAAxCA4AAAGCsAAAAAAAAAAAAgD+sAAAAAACAPwQAAABAAAAAwAAAgD8AAAAAAAAA + AP8AAAAApgAAEEIDgAAAYK0AAAAAAAAAAACAP60AAAAAAAAA/wAAAACsAAAUQgOAAABgrQAAAAAAAAAAAIA/rQAAAAAAAAADAAAAwAAAgMAAAAAAAAAAAP8AAAAAqAAA + GEIDgAAAYK0AAAAAAAAAAACAP60AAAAAAIDAAQAAAAAAAAAA/wAAAACqAAAcQgOAAABgrQAAAAAAAAAAAIA/rQAAAAAAAAAEAAAAQAAAAMAAAIA/AAAAAAAAAAD/AAAA + AKcAACBCA4AAAGCqAAAAAAAAAAAAgD+qAAAAAAAAAP8AAAAAqQAAJEIDgAAAYKoAAAAAAAAAAACAP6oAAAAAAEDAAwAAAAAAAADAAAAAAAAAAACEMzMDwAEAAAAAAAAA + AP8AAAAAngAAKEIDgAAAYJUAAAAAAAAAAACAP5UAAAAAAAAAgQAAAEABAAAAAAAAAACHAABAwIEAAAAAggAAAEABAAAAAAAAAAD/AAAAAIEAACxCA4AAAGCVAAAAAAAA + AAAAgD+VAAAAAAAAAIIAAEDAAQAAAAAAAAAAj2Zmhr8CMzMDwAAAAAAAAAAA/AAAMEIDgAAAYJUAAAAAAAAAAACAP5UAAADvuT7AAgAAgMAAAAAAAAAAAJEAAEDAgQAA + AAD9AAA0QgOAAABgkQAAAAAAAAAAAIA/kQAAAAAAAMABAAAAAAAAAAD/AAAAAI4AADhCA4AAAGCJAAAAAAAAAAAAgD+JAAAAAAAAAAMAAIA/AACAvwAAAAAAAAAAgQAA + QMABAAAAAAAAAAACAAAAwAAAAAAAAAAA/QAAPEIDgAAAYHYAAAAAAAAAAACAP3YAAAAAAIA/gQAAAMABAAAAAAAAAACBAAAAwAEAAAAAAAAAAO0AAEBCA4AAAGB2AAAA + AAAAAAAAgD92AAAAAACAPwQAAAAAAABAwAAAAEAAAAAAAAAAAI8AAADAAQAAAAAAAAAA3gAAREIDgAAAYHMAAAAAAAAAAACAP3MAAAAAAAAAhAAAQMABAAAAAAAAAACG + AADAwAEAAAAAAAAAAOIAAEhCA4AAAGBtAAAAAAAAAAAAgD9tAAAAAAAAAIMAAMC/AgAAgL8AAAAAAAAAAIYAAIDAAgAAAMAAAAAAAAAAANsAAExCA4AAAGBcAAAAAAAA + AAAAgD9cAAAAAABAQIEAAADABQAAAAAAAADAAAAAAAAAAMAAAAAAAAAAAIUzM+PAAQHy7r8B8u6/lmsI27+FAfLuv4RrCNu/hOgex78BrRPRv60T0b+F6B7Hv5wAAFBC + A4AAAGBcAAAAAAAAAAAAgD9cAAAAAACAPwEAAAAAAAAAAAUAAIA/ZmaGvwAAAABmZoa/AAAAAAAAAACNAABAwAEAAAAAAAAAAMMAAFRCA4AAAGBbAAAAAAAAAAAAgD9b + AAAAAAAAANoAAFhCA4AAAGBWAAAAAAAAAAAAgD9WAAAAAAAAAIMAAEDAAwAAAAAAAIC/AAAAAAAAAADNAABcQgOAAABgSgAAAAAAAAAAAIA/SgAAAAAAAAACAACAv5qZ + mT+amZk/BAAAQMAAAKDAAACAPwAAAAAAAAAAggAAQMABAAAAAAAAAAC8AABgQgOAAABgSgAAAAAAAAAAAIA/SgAAAAAAwMABAAAAQAAAAEACAAAAAAAAoEAAAKBABGZm + lkAAAGBAZmbmPwAAAAAAAAAAgQAAQMABAAAAAAAAAAC7AABkQgOAAABgSAAAAAAAAAAAAIA/SAAAAAAAAACBAAAAwAEAAAAAAAAAAIEAAADAAQAAAAAAAAAAkQAAQMAB + AAAAAAAAAACrAABoQgOAAABgRAAAAAAAAAAAAIA/RAAAAAAAAACBAACAPwIAAEDAAAAAAAAAAACDAABAwAEAAAAAAAAAAIIzMwPAAQAAAAAAAAAAswAAbEIDgAAAYDsA + AAAAAAAAAACAPzsAAAAAAAAAgQAAQMABAAAAAAAAAAC2AABwQgOAAABgOwAAAAAAAAAAAIA/OwAAAAAAAAC6AAB0QgOAAABgOQAAAAAAAAAAAIA/OQAAAAAAAAACAACA + vwAAQMAAAEDAAf/gqj//4Ko/gS4OTT8BAAAAAAAAAACEMzMDwAEAAAAAAAAAAIIAAADAAQAAAAAAAAAAowAAeEIDgAAAYDYAAAAAAAAAAACAPzYAAABmZoa/gTMzA8AB + AACgwAAAoMACAAAAwAAAAAAAAAAAggAAEMEBAAAAAAAAAAACAAAQwQAAQEAAAEBAggAAYEADAACQQAAA8EAAAEBAAABAQA+amTlAAAAwQAAAIEAzMxNAMzMDQGZm5j9m + ZsY/rkehP2Zmhj9mZmY/j8I1PwAAAD8zM7M+zcxMPgAAAAAAAAAAiQrXo7wBAAAAAAAAAACDAAB8QgOAAABgLwAAAAAAAAAAAIA/LwAAAAAAAACBMzNzwAUAAAAAMzMT + wTMzA8CcmdnAAAAAAAAAAACBAABAwAEAAAAAAAAAAAEAAABAAAAAQAEAAAAAAAAAAIUAAEBAAQAAAAAAAAAAlgAAgEIDgAAAYC8AAAAAAAAAAACAPy8AAAAAAAAAA/// + /8AAAMDAycwMwMnMDMCBAADAwAEAAAAAAAAAAIZmZkZAiAAAQEABAAAAAAAAAACUAACCQgOAAABgLQAAAAAAAAAAAIA/LQAAAAAAQMCCAAAAAIIAAADAAQAAAAAAAAAA + pAAAhEIDgAAAYCgAAAAAAAAAAACAPygAAAAAAAAAAwAAgD8AAADAAAAAAAAAAACjAACGQgOAAABgKAAAAAAAAAAAAIA/KAAAAAAAAACJAADAQAEAAAAAAAAAAJsAAIhC + A4AAAGAmAAAAAAAAAAAAgD8mAAAANjNzvwMAAEDAMzMDwAAAAAAAAAAAoQAAikIDgAAAYCQAAAAAAAAAAACAPyQAAAAAAAAAAgAAQMAAAAAAAAAAAKAAAIxCA4AAAGAi + AAAAAAAAAAAAgD8iAAAAAAAAAAYAAEDAAABAQAAAAAAAAEBAAADAQAAAAAAAAAAAmgAAjkIDgAAAYCAAAAAAAAAAAACAPyAAAAAAAAAAhQAAQMACAABAQAAAAAAAAAAA + AgAAgEAAAAAAAAAAAJMAAJBCA4AAAGAdAAAAAAAAAAAAgD8dAAAAZmaGvwUAAAAAAADAPwAAAMAAAEDAAAAAAAAAAACWAACSQgOAAABgHAAAAAAAAAAAAIA/HAAAADMz + A0AEAACAPwAAAAAAACDAAAAAAAAAAAAEAABAwAAAAAAAACDAAAAAAAAAAACRAACUQgOAAABgGwAAAAAAAAAAAIA/GwAAAAAAAACCAACAv4EAAAAABAAAgL8AAAAAAABA + wAAAAAAAAAAAkAAAlkIDgAAAYBkAAAAAAAAAAACAPxkAAAAAAAAAmAAAmEIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAIC/AQAAAAAAAAAAggAAAEABAAAAAAAAAACQAACa + QgOAAABgFgAAAAAAAAAAAIA/FgAAAAAAAAAB3AoCQNwKAkABAAAAAAAAAAACMzMDwAAAAAAAAAAAjgAAnEIDgAAAYBUAAAAAAAAAAACAPxUAAAAAAEBABgAAAEAAAIA/ + AAAAAAAAgL8AAAAAAABAwAAAQMABAAAAAAAAAACLAACeQgOAAABgFAAAAAAAAAAAAIA/FAAAAAAAQD8GAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAAAAAIwAAKBCA4AA + AGATAAAAAAAAAAAAgD8TAAAAAACQPwEAAAAAAAAAAAMAAABAMzMDwAAAAAAAAAAAjAAAokIDgAAAYBIAAAAAAAAAAACAPxIAAAAAAAAAgQAAAMACAADAwAAAAAAAAAAA + jAAApEIDgAAAYBEAAAAAAAAAAACAPxEAAABmZoY/BAAAQEAAAIA/AABAwAAAAAAAAAAAiwAApkIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAIBAAwAAAEAAAIA/AADAvwAA + wL+CAAAAAIgAAKhCA4AAAGAPAAAAAAAAAAAAgD8PAAAAAABAQAMAAAAAAAAAQAAAoMAAAKDAAjMzA8AAAAAAAAAAAIcAAKpCA4AAAGAOAAAAAAAAAAAAgD8OAAAAAAAA + QAM0M7O9AAAAQAAAAAAAAAAAiQAArEIDgAAAYA0AAAAAAAAAAACAPw0AAAAAAABAAwAAAAAAAEDAAAAAAAAAAACIAACuQgOAAABgDAAAAAAAAAAAAIA/DAAAAAAAAEAF + AACAPwAAAAAAAIC/AAAAwAAAAAAAAAAAhQAAsEIDgAAAYAwAAAAAAAAAAACAPwwAAAAAAABAA83MTL05jmO9AAAAAAAAAACHAACyQgOAAABgCwAAAAAAAAAAAIA/CwAA + AAAAQEADmpkZvWELNr0AAAAAAAAAAIYAALRCA4AAAGAKAAAAAAAAAAAAgD8KAAAAAAAAQAPMzMy8iIgIvQAAAAAAAAAAhQAAtkIDgAAAYAoAAAAAAAAAAACAPwoAAAAA + AABAA83MTLxgC7a8AAAAAAAAAACFAAC4QgOAAABgCQAAAAAAAAAAAIA/CQAAAAAAQEADAAAAAAAAAMAAAAAAAAAAAIQAALpCA4AAAGAJAAAAAAAAAAAAgD8JAAAAAACA + QAIAAADAAAAAAAAAAACFAAC8QgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAgEACAAAAwAAAAAAAAAAAhAAAvkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAABAAQAAAAAAAAAA + hQAAwEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAABAAgAAwL8AAAAAAAAAAIQAAMJCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAQAEAAAAAAAAAAIUAAMRCA4AAAGAIAAAA + AAAAAAAAgD8IAAAAAAAAQAEAAAAAAAAAAIUAAMZCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAQAIzMwPAAAAAAAAAAACEAADIQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAA + AEABAAAAAAAAAACFAADKQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAEABAAAAAAAAAACFAADMQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAEABAAAAAAAAAACFAADOQgOA + AABgCAAAAAAAAAAAAIA/CAAAAAAAAEABAAAAAAAAAACFAADQQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAEABAAAAAAAAAACFAADSQgOAAABgCAAAAAAAAAAAAIA/CAAA + AAAAAEABAAAAAAAAAACFAADUQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAEABAAAAAAAAAACFAADWQgOAAABgBAAAAAAAAAAAAIA/BAAAAAAAAEABAAAAAAAAAACBAADY + QgOAAABgBAAAAAAAAAAAAIA/BAAAAAAAAEABAAAAAAAAAACBmTD0PQOAAABAEwAAAAAAqEEAAMBBAADgQQAA+EEAABBCAAAUQgAAIEIAAChCAAA8QgAATEIAAFxCAABs + QgAAfEIAAIRCAACQQgAAnkIAAKZCAAC+QgAA1kITAAAA0h+CP4HRMIY/AdIfgj/SH4I/gtEwhj+B0h+CP4LRMIY/BrTSgD9qBow/H5h8P6yPbj9fAWI/JXtMPwEAAAAA + AAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8B + AAAAAAAAAAAA8EIAAAAADgAAAAAAAD/NzMw9zcxMPgAAAD8CAAAAAAAAAAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAAAAAAQAAAACAAAAAwAAAAQAAAABAAAAAQAA + AAAAgD8AAIA/AQAAAAEAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAAAAOAAABIAQAA + AAAAAAAAAAAAAwAAAAcAAAABAAAAAAAAAA4AAAAQAAAAAAAAAM3MzD0AAIA/AAAAAFBmZj7876FBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAMAAAAKAAAAAQAAAAAAAAAOAAAAEAAAAAAAAAAAANxCAAAAADMzMz8AAABAANAERgAAgEDNzEw+AACAPwCAO0UAAIA/mpkZPwAAQEAAAAAAAAAAAAAAAAADAAAACQAA + AAEAAAAAAAAADgAAABAAAAAAAAAAtUlMPgAACEAEAKBAIACAwKkFXUC7S8lCAACgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAIA/AACg + wAAAAAADAAAAAwAAAAAAAAABAAAABQAAAAQAAAABAAAAAwAAAPYoHECtR+E/AADAPwAAYcMAAAAABAAAAAUAAAAAAEBAAABwwgAAwMIAAMDCAADAwgUAAAAXt9E4Gy/d + PAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAAAAgQMrMTD8AAMA/AAA0QgAAAAAEAAAABQAAAAAAcMIAAEBAAADA + wgAAwMIAAMDCBQAAABsv3TwAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAADQUbg+HYWrP1yPgj84XeNCX/SH + QwUAAAAFAAAAAABAwQAAwMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAA + AB+F6z5SuJ4/XI+CPwAA4kIAALLCBQAAAAUAAAAAAMDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAUkmdOQAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAEAAAAAAAAABAAAAAAAAAADAAAAuB7FPz0KVz+uR+E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAADAAAAzcxMP4/Cdb4AAMA/AACQQQAAAAAAAAAAAAAAAAEAAAAAAAAAEThWOGY1AABQclZLJgAA + AlhGVFBWNQAACgAAADIwMjMvMDYvMDYXAAAAUGlhbm90ZXEgOC4wLjAvMjAyMjExMTfxDRLbCw5Ug6TEmqQGnLCaZGxsYRU1AAB0ZHRNqAYAABMAAABQZXRyb2YgTWlz + dHJhbCBXYXJtAAAAAAcAAABNb2RhcnR0JAAAAFByZXNldCB3aXRoIGEgd2FybSBhbmQgbWVsbG93IHNvdW5kLgAAAABEAQAAEwAAAFBldHJvZiBNaXN0cmFsIFdhcm0A + AAAAMwYAAEdSVVUAAAAGAAAAAQPATVRyawAABh0A/wIvQ3JhaWcgUmljaGFyZHMgLSBMZWF2aW5nIEFnYWluIC0gQ3JhaWcgUmljaGFyZHOEYrBAAhhAGBZANBhAUBZA + bBhAf4ZIkEdEgTJHAIUYSFyBJkgAhgqwQGoYQDYWQAoYQAAGkEcrGkMuBEArCj4vHDAkgXiwQAQWQC4YQFYWQHYYQH+DApBAADo+ACBHADZDAAY3JocMQyYkPiaHKjsY + TD4AjUpDAIsSR1YKPkSBCD4AGkcAhSYwABA7AFZIRAxAREo3ADBAABhIAIZysEBaFEAkGEAAHJBHNwxDORQ+MgYwKoMisEAqFkBWGEB/gnSQNykCPgAiRwAaQwCGIkMo + ID4nhwQ7F3A+AIliMAA+OwAuNwAOQwCMekIrDLBASASQPCYUsEAMDJA5IQqwQAAUkDIggTywQCwWQF0YQH+CZpBCABw5ADQ8ABIyAIlwQycGOyACsEBsFkA6DJArHgyw + QAYUQACELEAUFkA+GEBaFkB2GEB/gRyQMhyHfDccIjsAgXZDAIQqNwBQKwBkMgCHGLBAbgKQQyEQPCAGsEA8FEAICpArGxCwQAAIkDQYg0SwQAgWQCYWQEQYQGoWQH+N + CpA8ACRDAIU8Q0AKPEQUKwAKNACCLkMAHjwAhHxDJAwyHQSwQEYMkDskCrBADAqQKx0QsEAAgzRAHBZAQBhAZRZAf4lokDsAgQA3F4lqQwCWaEdOgWI3AII6MgACKwCC + LkcALkhKgUpIAIYWsEBiFEAcGEAABpBHKBZDNxRAPAY+LSIkKUqwQDoWQHgYQH+CGpAkAIJGPgA8QAASRwAoQwB+NDGHTkM0Ejwuhg5DAAQ8AG43IYg0QB8GOxmICDsA + MkAAiC43ABpHTYEiNABWRwCFCkhWgW5IAIUKsEB4GEAmFkAAApBHSxY+QywkLnawQEwWQH+CJJAkAAwwNhI+AAxHAIIOMACBKDQqgxQ3PYQ0Q0ISPDiFIjcAgUo8AIEC + QwCGOjckhjw3AIEyNACIXrBAZxZAKgSQQisEPjUQsEAAApA5Iy42GgwqKIEUsEAKFEAyGEBMGEBxFkB/hDyQOQAKKgBANgAIQgAQPgBIRSODSEUAhFRDKBQ7IQiwQGAW + QCwWQAAIkDcVBisZgnqwQCIaQEoUQHgYQH+DCpAyJEI7ABZDAIcgPiSFFj4AODcAMDIASCsAiHQ7NABDOwg3LBCwQH0UQEECkCgmDi8rCLBACgaQNCMQsEAAgX5AFhhA + OBZAWBZAf4MwkEMAggxAPRY3ABA7AExAAHI0AIVqQFAMNzYgNCmHaDcAFigAREAAFi8AaDQAhxJDMhQ8PASwQGICkDkuFLBAKhKQMDACKScEsEAAFJA1GYJcsEAGGEAo + FkBIGEB0FkB/g3iQPABUQwCDAjkAhGBALR48KIE8NQCBJDAATikAgwBAAEo8AA45KGY5AIZIsEBnDpA8LAI0KgiwQCwWQAAmkDAbCiQigx6wQCIWQEwWQHQYQH+CHJA0 + ABQwAA4kAEwrGodENDF6PACBAjQAgQ43SVgrAIMkNwAGPGSENjwAgxY5OTAyLwqwQGQKkCspCrBAHBhAAIFgkDkADjszgXawQDwWQH+CVpA3LyY7AIc+QygUOymJODcA + PkMADjsASDIAJCsAlQhHSIc4SDgCRwCEFrBAf4IekEgAgQKwQABQkEcpDEAyED4eHjAjgh6wQH6EcJBAABA+AAo3LSBHAIdeQzQWPiqTeD4AhEhDAIkqR1OBZDAAhE5H + AGhIOQg3AIFASACFEjAaZrBAQhRABBhAABCQOxoENxZSRzIiPyuDDLBADhRAQRhAeBZAf4I8kEcAgQo/AIkiQy2CGEMAjXI/IShFKIFcPwAoRQCFJDsAhGI3AIEmMACD + Nj4yg3Q+ACY7NIQ4OwAyOUOEVjkAODIihCAyAIJsKyGCCrBAShZACgKQMh0WsEAAgQSQOTMKPi+CTDkALDsvhhI3JzawQCIWQGQWQH+DZpA3AIF+KwAEOwAIPgCBNjIA + nlg3KIh8PiOBEjcAXkccUE8RRlYiXj4ATF8fLE8ATFYALEcAigBfAOggsEBdFkA2GEAYFkAAAP8vAFByc1VdLgAARwAAAAsAAABtaXN0LnBldHJvZgAAAAAUAAAAR3Jh + bmQgUGV0cm9mIE1pc3RyYWwAAAAAAAAAAHByb21pAAAAAQAAAAEAAAABAAAACwAAAG1pc3QucGV0cm9mAAAAABQAAABHcmFuZCBQZXRyb2YgTWlzdHJhbAAAgD8GAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAAAAAAAAAAAAAAcnlhbBAAAAACAAAAAAAAAAAAAAD/////b2xvcxEAAAABAAAAAQAAAEYAAAAAAAAAAAEAAAAB + AAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAB + AAAAAAAAAAAAAAAAAAAAA4AAAGBYAAAAAACoQQAAgD9YAAAAANFsPlcADbq+AGawvQTM+T8wLAs/gLDFPnhJlD+4ceA/sKuLPxD5ZD/AR2i/3CjRvwA2p79AWhG/pG9Z + QMRO7D9A8CU/KDc5QL3FhUDoBGs/4G+uP2yqb0BMDPs/bE7OvxCYCsDA5pM+gDu9vsgfI0A6AQtAuAgiQDxlGkCQSnA/gB7yP6AuUECYSfY//Lfrv8QRnz+AsXw+ANFU + vwhoVj+QJBxAUDmrQN+bmEAA/A5ACBATQNT1HkCG+VJAlH5+QP5OAEDM8M0/wCBAvwBZxb0Alss8JRzAQEw1hEBGSjFAgJvMP/zknD+AnBs/MBNyPzD6NL8gbqQ+QJLg + vqNloEBgRRk/oCCsvhgyw78SuQ9AdNb4P1xixT8g9J6+KJM+P4fCmECAZzk+MRKFQEinCD94yJy//HCtv0T1OUAkFK8/Q8u9QFq4V0CQz6BAlHI3QEr1fUCKwe9AIUe8 + QMZzAEEDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAX89/PwAAAAABAAAAwGNXwDk4wT0DgAAAYFgA + AAAAAKhBAACAP1gAAAAcS4g/V+hfiD/ccok/oEWKP+MgiT9xxYY/QdqJP26ViD/ty4Y/Q5qJP5vRiD8drIc/4v2GPwZMhz/15ok/VB+JP00Dij8z7Ig/cciKP9Nrij8p + uIw/+hWMP3BPiz9Fy4s/3YOLP1tZiz+kios/yrCKP2Lrij9uSYk/JSeIP/LwiT/uB4s/wNiJPxA4ij/iDIc/kxSHP2qwiD9N0Ig/bKuGP0wfhz/14oM/uqGDP7lghD8j + WoQ/DiyAPxhEgz9R0IA/5RWBP558fT9OVX8/3eN/PzKogD+U5n0/NZB/P4mmfT/KSnk/7xx8PyTEfD/2T3Y/wZJ0P/GPeD/8wHE/r5RxP8DUbT+cBG4/jpRvP5+GcT/t + oWo/QzFtP2G0aD9lBWg/wj5mP7Ojaz8rw2g/UnFkP7ewZT82VWE/OOJiPwmOYj/Nv1s/7cRhP7J7WD8lJl4/Qi9bP5rpVT9aJFg/hqZaPwEAAAAAAAAAAAAAAAAAoMAA + AKBAAACyQgAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGAC + AAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAAAAAAAAAAAAAAAAIAAAAA/AACAPwOAAABgWAAAAAAAqEEAAIA/WAAAAMC88T9XABKQPoDnhj8g+xZAAHTyPmDs + E0DAqow/AE5yPwDe0D4AI8k+APRWP4AbAT+Ap40/gHMYPwDpaz8AmI09AHl9PwBN8T4AIYm+wBH2P4BYUz/AKbI/gGkUP8BhHkCA7OA/AG1EPwBJjj+Az7k/wC/6PwC0 + RD8APhU/AIf9P4BbTj8AN/g/ALAtQAAW174AKGK+gL58PwCVpj8AWE0/AFRbPlDnlMCAj9A/AOJ9PwCDAT8AxF4/AEu0PoCECr+AWBU/AMdQPwCkDz4AWsw/AP6nP4Cu + lz+An88/gMduP0Dhkz8AeiA/AIjDPYAmYD9A7Lc/gKG5P0BShz9AT7Y/gD/BP4BXlT/AHco/QMKdPwCZ6j8AVNk9gPMMP0Be+j8ADqQ+ABaVPqBYBUAA6xs/AAplP8CZ + 1T8Ago++gECCv4Bkdj+ALX8/wDDiP4CJLUBA/Z4/AJDDvAA3vD9ARY4/A4AAAGBYAAAAAACoQQAAgD9YAAAAAACAv4EAAEDAAgAAAMAAAIA/AACAPwIAAEBAAAAAAAAA + AAACAABAQAAAAEAAAABAAwAAgL8AAADAAAAAQAAAAEABAABAQAAAQEACAAAAQAAAAIAAAAAAAwAAgL8AAIA/AAAAwAAAAMABAAAAQAAAAECBAABAQBUAAADAAABAQAAA + AIAAAIA/AAAAwAAAQEAAAABAAACAPwAAAEAAAIA/AACAvwAAAAAAAABAAACAPwAAQEAAAIA/AAAAwAAAAIAAAABAAABAwAAAAIAAAAAABgAAgL8AAADAAAAAQAAAAMAA + AIC/AAAAgAAAAIADAABAQAAAQMAAAIC/AACAvwEAAEDAAABAwAMAAABAAACAvwAAAMAAAADADwAAAEAAAIA/AACAvwAAAEAAAAAAAAAAQAAAAIAAAIA/AAAAQAAAAMAA + AACAAACAvwAAQEAAAABAAAAAwAAAAMAEAACAvwAAAAAAAIC/AAAAwAOAAABgWAAAAAAAqEEAAIA/WAAAALqffD9XAct9P2rZiz/kSIg/ft50P1hvgD9VTYA/1UCCP+9R + bD9l0HY/XE+MP9v6bz+Qx4w/YF9tP86paj+YHGg/o0mEP1dpfD8Ul4s/XkiBP6CwbT9WJ3M/80OMP4uzij9Ownc/+rB1P1RmgT+Jdn8/Uk9+PyrPgj+KxIo/ikVqPxJp + Zj/EAG0/D0iDP8BxfD8WUWc/2jpyP2idbD8MPoU/dNRrP7IEaj/xKoI/kLxzP6vwez9wr4Y/Co9yP0QsaD9nHYw/VH6KPxhTgT/auHw/ldSDP2/jcD9xDXY/uL6GP/em + aD9ZfoU/PLyJPzz7hT+3zoM/nPN7PwAFeD/a2nk/20J5P1jubT8UUIU/3zaKPyFxhz87PYg/Lut5P/J6gz/deHA/rqmGP+0wbD8ojIg/cH9nPyCLhj+hxX4/E6R5P19I + gT9O/nA/C3eEP0CLfD+++H8/T/aLP1Bkez9WG4c/AgAAAAEAAABVAAAARXF1YWwgdGVtcGVyYW1lbnQKVGhlIHN0YW5kYXJkIHR1bmluZywgd2hlcmUgdGhlIG9jdGF2 + ZSBpcyBkaXZpZGVkIGluIDEyIGVxdWFsIHN0ZXBzLgwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQBAAAAAAAAAAQAAAAA + ANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAAAAAAAAIAAAADgAAAYFgAAAAAAKhBAACAP1gAAAAAAODAAgAAwMAAAKDAAACgwAEAAIDAAACAwIEA + AEDAggAAAMCDAACAv4cAAAAAwQAAAAAAAAAAAACAPwEAAAABAAAAAQAAAB8AAAADgAAASAEAAAAAAIA/AAAAAAAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOA + AABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAFAAAHpEAQAAAAAAAAADgAAAUAAAekQBAAAAAACAPwAAAAADgAAAUAAAcEIBAAAAAAAAAAAAgD8DgAAAUAAAcEIBAAAA + AAAAAAEAAAAAAAAAAAAAAM3MzL3NzMw9AQAAAAAAAAAAAAAAAAAAvwAAAD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEA + AK5CAgAAAAAAAACBAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAAAgQgOAAABADwAAAAAAqEEAANBBAAD4QQAAEEIAAEBCAABwQgAAkEIAAKhCAAC0QgAAwEIA + AMhCAADQQgAA1EIAANZCAADYQg8AAAAAAAAAhgAAgD8HAABAQAAAoEAAAOBAAAAwQQAAYEEAAIBBAACYQQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4ID + gAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACA + PwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAzcxMPgAAAAAAAAAAAAAAAAAAAD+PwjVAA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EA + AIC/AAAAPwOAAABgAgAAAAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAABAAAACwAAAEphenogc3R1ZGlvCgAAAGphenpzdHVkaW8AAEBAAADIQQAAgD8zM/M/AAAAAMCn + tTzw//+/mpkZPgAAAAAAAAAAAACqQwAAgD8AAIA/AQAAAAYAAAAAAIA/AACAPwOAAABgWAAAAAAAqEEAAIA/WAAAAPgoiT9X7PqLPxwidT85xnE//6NvPwHhhD8dg4Q/ + x198P/RAiT/lgnI/o3J7P2aMiz8zPHo/TjxvP9N8hD/XWoY/rO+FP4pPjD9Mtnc/ew6BPzZsiD+uoIs/RueIPxKZiD89Clc/yWRqP4GSgT+0lYE/HfBpP2kphj/7Oow/ + 2jCGP9p4bj/hrno/dlCLP+kKaD97poY/JbGLP0UpbT+Q7II/BTV4P77Qiz8Tv4M/e3uIP7Lnhj8SLGg/P+9rPytCeD+KWYw/eeZpP63rez8PbHo/vbh6Pykvfj/A64c/ + 9iicPwxdiz8154g/7SaGP2d7cz99joM/7hdyPySphD9w/IM/NFeAP9JYZz9wCn0/duNwP4L9iz+dTGo/h7WGP62Lfz/AAYU/3TZ4P9bjbD+AjoE/m26FP/5mfz+r24Y/ + N7CHP1Otij+oAoA/RcCJP8WCcj8Rvow/e16EPweLeT9bLGk/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgWAAAAAAAqEEAAIA/WAAAAN6tXT1X + TEUNPuvqqb2zsRg+XgE4PbtqPj5QdKC9URt0vqcWZj5kJck9l7+CvU75PL7GLHi+4O8WPTQPlj5pjlg+z7CWvs92AL4OhEY+VWNFPkFDAD4xvS++08mrvfycHLwb7Q6+ + smoJvqtfAT6d6iY9c9kVvpDcjT7lNK27l9uLPgEjM77Bus+7Bs2MPlomh73Z8AM+hbNdPRXIOz6EVjO96cJ8vs2tFb5AW2u5rcuWPfWFmT7xxNM9zoh6PvscW77QaLo9 + JouQPikaib66jwM+FlQWPgvjVT5bClO+UGl7vupT7T0ADok9J/kivsSDYD5Wdnc+/RF6vmw5fz4CHse99KYovZHzer48sxS+6ZktvnMwR741LA6+a6TTvVgBP752IIG+ + 2D4HvrVwxL3xXS2+AsmUvmWhM73uYKw9X0+dPPVkTT1epAM+w9a2va55tL3Nr3Y+i1GUPWSADDz0SYI+AQAAAAAAAAABAAAAAAAAPwAAAEADAAAAAACAPwAAgD8AAIA/ + A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAA + gD8HAAAAAACAP5qZmT7NzEw/AADAP5qZmT4IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAABm/4M/GztMij9/s3o/ + q39RPx34fj/G84I/yz6BP7T3Yj+Ox2o/PEWAP9nikD9jVrM/7he1P+FRoD9cRz0/yeNxPytUXz+kTmo/8Vg1P9H7ij/AFnE/H1ZCP9wqXz+j7bM/eADEP7eOmj8aQJM/ + U6lUP1OpVD87IdJNP0DJUT95/nc/yQ54P0/mVT/QQ3g/v3i3P4gzhT/sh48/eaKXP4r3bj87DFU/q7MrP9IiRT/YMIE/9amAPybDcj+tWV4/HqJSP1wXgj/KuIE/nqSn + P2oblD/fcqk/ZNQxPxL5XD+ImXQ/g2qNP1G0mT+f9Jc/I1OUP2vtqz9KbaU/9a2sP4VYVj//h6Q/oHPDP/4X0D+7opU/oOuRP0sFiz/SKbg/spqRP+dbSD+Tjqg/t0ZI + P1Lupj9RK9I/lwPZP8PzQj8U9pU/CwIrP4iyaT8ZbDc/vQp6P4PjXT8jVOw+H6BGP7tPDj8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhB + AABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAAWAAAAAAAqEEDgAAAYNAAAAAAAAAAAACAP9AAAAAAAAAAkDMzA8AEzcxswDMz + k7/NzEw9AAAAAAAAAADUAADAwIEAAAAA4gAAsEEDgAAAYMMAAAAAAAAAAACAP8MAAAAAAAAAggrXIz4BAAAAAAAAAACBzcxMPYEAAAAAhzMzA8ABAAAAAAAAAACDmpkZ + PgEAAAAAAAAAAKvNzEw9ATMzQ8AzM0PAAs3MzL0AAAAAAAAAAPgAALhBA4AAAGC+AAAAAAAAAAAAgD++AAAAAAAAAIeamRk+AQAAAAAAAAAAg83MzD0IAAAAADMzA8Ca + mRk+zcxMvZqZGT4AAAAAzczMvZqZGT6amRk+AQAAAAAAAAAAgc3MTL0CAACAwAAAAAAAAAAA/wAAAACfAADAQQOAAABgywAAAAAAAAAAAIA/ywAAAAAAAACBAACgwAEA + AAAAAAAAAJ8AAADAAc3MTD3NzEw9AQAAAAAAAAAA/wAAAACiAADIQQOAAABgxQAAAAAAAAAAAIA/xQAAAAAAAACBmpmhwAEAAAAAAAAAAP8AAAAAwAAA0EEDgAAAYLwA + AAAAAAAAAACAP7wAAAAAAAAAgZqZ+b8BAAAAAAAAAACPmpn5vwLNzEy9AAAAAAAAAAD/AAAAAKQAANhBA4AAAGC2AAAAAAAAAAAAgD+2AAAAAAAAAIyamfm/AQAAAAAA + AAAA4c3MTD0B4Xr0v+F69L+Bmpn5v4UAAADAgZqZ+b+JAAAAAK4AAOBBA4AAAGDKAAAAAAAAAAAAgD/KAAAAAAAAAAPNzDxAzczMvQAAAAAAAAAAh2ZmnsABAAAAAAAA + AACuZmZ2wAEAAAAAAAAAAKTNzEy9gwAAAACBzcxMvYHNzEw9AgAAAAAzMzPAMzMzwIkAADDAAjMzM8BmZjbAZmY2wIMzMzPAgQAAMMABMzMzwDMzM8CCAAAAAMIAAOhB + A4AAAGCwAAAAAAAAAAAAgD+wAAAAAAAAALoAAADBgc3MzL0BAAAAAAAAAACNzczMPQLNzEw9AAAAAAAAAACGAACgwAEAAAAAAAAAAIHNzEw9g83MzD0DMzO7wM3MzL3N + zEw9zcxMPYHNzMy9AQAAAAAAAAAABM3MTD3NzMw9mpkZPs3MTD3NzEw9gQAAiMADmpkZvs3MzL0AAAAAAAAAAL0AAPBBA4AAAGC9AAAAAAAAAAAAgD+9AAAAAAAAALwA + ACDBAQAAAAAAAAAA/QAA+EEDgAAAYKwAAAAAAAAAAACAP6wAAAAAAAAAvAAAwMABAAAAAAAAAADsAAAAQgOAAABgoAAAAAAAAAAAAIA/oAAAAAAAAAACzcwMwAAAAAAA + AAAAgTMzA8ABAAAAAAAAAAC1AACgwAEAAAAAAAAAAOAAAARCA4AAAGCZAAAAAAAAAAAAgD+ZAAAAAAAAAAIAAEDAAAAAAAAAAACimpmBwAOF64HAzczMvQAAAAAAAAAA + 7gAACEIDgAAAYJQAAAAAAAAAAACAP5QAAAAAAAAAAgAAgL8AAAAAAAAAAP8AAAAAkAAADEIDgAAAYI4AAAAAAAAAAACAP44AAAAAAABABAAAgD+amfk/AAAAQAAAAAAA + AAAAAmZmBsAAAAAAAAAAAJozM4PAggAAAACHAADAwIEAAAAAm6RwPcABAAAAAAAAAACFAACwwALsUbDAAAAAAAAAAAC2AAAQQgOAAABgmQAAAAAAAAAAAIA/mQAAAJqZ + ScABheuBwIXrgcABAAAAAAAAAAD/AAAAAJQAABRCA4AAAGCdAAAAAAAAAAAAgD+dAAAAZmb+wIEAAAAABM3MTL0AAAAAAACAwAAAAAAAAAAAggAAQMABAAAAAAAAAAD/ + AAAAAJAAABhCA4AAAGCeAAAAAAAAAAAAgD+eAAAAMzObwAMAAAAAMzOTQAAAAAAAAAAA/wAAAACZAAAcQgOAAABgigAAAAAAAAAAAIA/igAAAAAAgMACMzPzPwAAAAAA + AAAA/wAAAACGAAAgQgOAAABgfwAAAAAAAAAAAIA/fwAAAKRwPcABAAAAAAAAAACBAAAAwAEAAAAAAAAAAJMAAIDAggAAAADhAAAkQgOAAABghAAAAAAAAAAAAIA/hAAA + AAAAAACB4Xr0vwLNzEw9AAAAAAAAAACUAACAwIEAAAAA5wAAKEIDgAAAYHwAAAAAAAAAAACAP3wAAAAAAIDAApqZ+T8AAAAAAAAAAIQAAEDAgQAAAACJAABAwAEAAAAA + AAAAAOUAACxCA4AAAGB1AAAAAAAAAAAAgD91AAAAUri+wAIAAADAAAAAAAAAAADxAAAwQgOAAABgcAAAAAAAAAAAAIA/cAAAAAAAQMAHAACgwM3MTD0AAADAAAAAAAAA + gEAAAKBAAAAAAAAAAADnAAA0QgOAAABgdAAAAAAAAAAAAIA/dAAAAAAA4MAEAAAAAOF69L8AAAAAAAAAQAAAAEACAABAQAAAAAAAAAAAAgAAAMAAAAAAAAAAAAQAAKBA + AAAAAAAAgMAAAAAAAAAAAIOamfm/AgAAAMEAAAAAAAAAANwAADhCA4AAAGBrAAAAAAAAAAAAgD9rAAAAzcy8wALNzMy9AAAAAAAAAACQAACAwAEAAAAAAAAAANQAADxC + A4AAAGBlAAAAAAAAAAAAgD9lAAAAAAAAwATNzDzAzcxMvQAAgD8AAAAAAAAAAN8AAEBCA4AAAGBpAAAAAAAAAAAAgD9pAAAAAACgwAIAAIC/AAAAAAAAAACBAABAwAIA + AMDAAAAAAAAAAACDAAAAwAEAAAAAAAAAANoAAERCA4AAAGBnAAAAAAAAAAAAgD9nAAAAAACgwAMAAMA/AAAgwAAAAAAAAAAAgXAI678EAAAAAAAAQMAAAIBAAAAAAAAA + AADbAABIQgOAAABgZQAAAAAAAAAAAIA/ZQAAAAAAgD8DAAAAAAAAAMAAAAAAAAAAAImkcD3AAQAAAAAAAAAA1AAATEIDgAAAYF4AAAAAAAAAAACAP14AAAAAAMA/BGZm + BsAAAODAAAAAAAAAAMAAAADAAQAAAAAAAAAAAgAAQMAAAAAAAAAAAAUAAEDAzcxMvQAAAAAAAEDAAAAAAAAAAADNAABQQgOAAABgVwAAAAAAAAAAAIA/VwAAAAAAAACC + zcxMPQEAAAAAAAAAAAIAAADAAAAAAAAAAADOAABUQgOAAABgTwAAAAAAAAAAAIA/TwAAADMzA8AEzcxMPc3MfMAAAMA/AAAAAAAAAAACAAAAwQAAAAAAAAAAgwAAQMAC + AACgwAAAAAAAAAAAhAAAQECBAAAAALgAAFhCA4AAAGBPAAAAAAAAAAAAgD9PAAAAMzNDQAKamfk/AAAAAAAAAACBMzMDwAIAAABAAAAAAAAAAACCAABAwAEAAAAAAAAA + AAIAAEDAAAAAAAAAAAC+AABcQgOAAABgTwAAAAAAAAAAAIA/TwAAADMzA8ACMzMDQAAAAAAAAAAABAAAoMAAAEDAAACAvwAAAAAAAAAAxgAAYEIDgAAAYE0AAAAAAAAA + AACAP00AAAAAAADAAgAAAAAAAIA/AACAP4cAAEDAAQAAAAAAAAAAAs3MTD0AAAAAAAAAAIXNzEw9AQAAAAAAAAAAtAAAZEIDgAAAYEIAAAAAAAAAAACAP0IAAAAAAEDA + BDMzA0BmZkbAAABAwAAAAAAAAAAAvAAAaEIDgAAAYEIAAAAAAAAAAACAP0IAAAAAAAAABJqZGcDNzMy9zcxMPQAAAAAAAAAABQAAgMDNzEy9AAAAADMzA8AAAAAAAAAA + AALNzEw9AAAAAAAAAACzAABsQgOAAABgQAAAAAAAAAAAAIA/QAAAAAAAAAC/AABwQgOAAABgOQAAAAAAAAAAAIA/OQAAAAAAAACBAAAAwAEAAEBAAABAQAUAAAAAAABA + QAAAAAAAAKDAAAAAAAAAAACuAAB0QgOAAABgOQAAAAAAAAAAAIA/OQAAAAAAAAAHAACAvwAAAEAAAEBAAAAAAAAAAMAAAIA/AAAAAAAAAACDAACgwAEAAAAAAAAAAIoA + AKDAAQAAAAAAAAAAnQAAeEIDgAAAYDYAAAAAAAAAAACAPzYAAAAAAIA/gU7e3b+BAACAPwONaQFAAACAPwAAAAAAAAAAgU7e3b8GjWkBQAAAAACRd3u/AAAAAE7e3b8A + AAAAAAAAAKQAAHxCA4AAAGAwAAAAAAAAAAAAgD8wAAAAAAAAAAQAAABAAAAAAAAAoMAAAAAAAAAAAIQAAEDAAwAAAAAAAMC/AAAAAAAAAAACpHA9wAAAAAAAAAAAAwAA + QMDNzEw9AAAAAAAAAACaAACAQgOAAABgMAAAAAAAAAAAAIA/MAAAAAAAAAAKZmaGP/v//z8AAIBAAAAQwQAAoEAAAEDAAAAAQAAAgL8AAIBAAAAAAAAAAACkAACCQgOA + AABgLQAAAAAAAAAAAIA/LQAAAAAAAACsAACEQgOAAABgKwAAAAAAAAAAAIA/KwAAAAAAAAAFmpn5vwAAAAAAAIC/AAAAwAAAAAAAAAAApAAAhkIDgAAAYCcAAAAAAAAA + AACAPycAAAAAAAAAAQAAwD8AAMA/hDMzQ8ABAAAAAAAAAACdAACIQgOAAABgJgAAAAAAAAAAAIA/JgAAAAAAAAAEMzPzPwAAQMAAAKDAAAAAAAAAAAABAAAAwAAAAMAB + AAAAAAAAAACBAABAwILNzMy9AQAAAAAAAAAAlQAAikIDgAAAYCQAAAAAAAAAAACAPyQAAAAAAAAAgQAAQEADMzNDwGZmRsAAAAAAAAAAAIUzM/O/BJqZ+b8AAAAAzcy8 + wAAAAAAAAAAAkgAAjEIDgAAAYCIAAAAAAAAAAACAPyIAAAAAAAAABAAAgL8AAIBAAAAAwAAAgD8AAIA/gQAAAMABAACAPwAAgD8CAACgwAAAgL8AAIC/AQAAAAAAAAAA + kwAAjkIDgAAAYCAAAAAAAAAAAACAPyAAAAAAAAAAis3MPMADpHA9wAAAwMAAAAAAAAAAAJAAAJBCA4AAAGAdAAAAAAAAAAAAgD8dAAAAAAAAwAEAAIA/AACAPwYAAAAA + MzNDwAAAQMAAAAAAAABAwAAAAAAAAAAAAgAAQMAAAMDAAADAwAHNzEy9zcxMvYHNzEw9AQAAAAAAAAAAigAAkkIDgAAAYBwAAAAAAAAAAACAPxwAAAAAAAAAAoXrgcAA + AAAAAAAAAIKkcD3AAwAAAACkcD3AAAAAAAAAAACRAACUQgOAAABgGwAAAAAAAAAAAIA/GwAAAAAAAACCAABAwAJmZgbAzcxMPc3MTD0CzcxMvQAAAAAAAAAAkQAAlkID + gAAAYBkAAAAAAAAAAACAPxkAAAAAAAAAA6RwPcAAAADAAAAAAAAAAAADZmYGwAAAQMAAAAAAAAAAAJAAAJhCA4AAAGAYAAAAAAAAAAAAgD8YAAAAAAAAAAYAAEBAAAAA + AAAAwEAAAAAAAACAQAAAQMAAAEDAAgAAwMAAAIC/AACAvwEAAAAAAAAAAIsAAJpCA4AAAGAWAAAAAAAAAAAAgD8WAAAAZmaGP4EAAEBAA2Zmhj8AAKDAAAAAAAAAAACP + AACcQgOAAABgFQAAAAAAAAAAAIA/FQAAAAAAAACUAACeQgOAAABgFAAAAAAAAAAAAIA/FAAAAAAAAACCAAAAwAEAAAAAAAAAAI4AAKBCA4AAAGATAAAAAAAAAAAAgD8T + AAAAAAAAAAMAAEBAAABAwAAAAAAAAAAAAs3MzD0zM8PAMzPDwIIAAAAAiAAAokIDgAAAYBIAAAAAAAAAAACAPxIAAAAAAAAAAQAAAEAAAABAAwAAAAAAAEDAAAAAAAAA + AACLAACkQgOAAABgEQAAAAAAAAAAAIA/EQAAAAAAgD8GAAAAQGZmhr8AAADAAAAAAJqZ+b8AAAAAAAAAAIkAAKZCA4AAAGAQAAAAAAAAAAAAgD8QAAAAAAAAAIMAAIDA + gQAAAACJAACoQgOAAABgDwAAAAAAAAAAAIA/DwAAAAAAAEEFAAAAQHR5AsBmZoa/ANekOwAAAAAAAAAAiAAAqkIDgAAAYA4AAAAAAAAAAACAPw4AAAAAAEDABgAAgMBm + ZoY/AAAAAAAAAEAAAMDAAAAAAAAAAACGAACsQgOAAABgDQAAAAAAAAAAAIA/DQAAAAAAAACEZma+wAEAAAAAAAAAAIUAAK5CA4AAAGAMAAAAAAAAAAAAgD8MAAAAAAAA + AIMAAADAAQAAAAAAAAAAhQAAsEIDgAAAYAwAAAAAAAAAAACAPwwAAAAAAAAAggAAQMABAAAAAAAAAACGAACyQgOAAABgCwAAAAAAAAAAAIA/CwAAAAAAQEACMzMDwAAA + AAAAAAAAhwAAtEIDgAAAYAoAAAAAAAAAAACAPwoAAAAAAABAAgAAgL8AAAAAAAAAAIYAALZCA4AAAGAKAAAAAAAAAAAAgD8KAAAAAAAAAIkAALhCA4AAAGAJAAAAAAAA + AAAAgD8JAAAAAAAAQAEAAAAAAAAAAIYAALpCA4AAAGAJAAAAAAAAAAAAgD8JAAAAZmaGPwIAAEDAAAAAAAAAAACFAAC8QgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACH + AAC+QgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAEABAAAAAAAAAACFAADAQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADCQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAA + oEABAAAAAAAAAACFAADEQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADGQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAAADMzPLwGZmdsAAAAAAAAAAAIMAAMhCA4AA + AGAIAAAAAAAAAAAAgD8IAAAAZma+QAEAAAAAAAAAAIUAAMpCA4AAAGAIAAAAAAAAAAAAgD8IAAAAMzPDQAEAAAAAAAAAAIUAAMxCA4AAAGAIAAAAAAAAAAAAgD8IAAAA + heuBQAEAAAAAAAAAAIUAAM5CA4AAAGAIAAAAAAAAAAAAgD8IAAAAzcy8QAEAAAAAAAAAAIUAANBCA4AAAGAIAAAAAAAAAAAAgD8IAAAAZmZGQAEAAAAAAAAAAIUAANJC + A4AAAGAIAAAAAAAAAAAAgD8IAAAAmpnBQAEAAAAAAAAAAIUAANRCA4AAAGAEAAAAAAAAAAAAgD8EAAAAMzNjQAMAAFDAmplJwAAAAAAAANZCA4AAAGAEAAAAAAAAAAAA + gD8EAAAAmpnBQANSuL7AAADAwAAAAAAAANhCA4AAAGAEAAAAAAAAAAAAgD8EAAAAZma+QAMAAIjAZmaGwAAAAACQiO49A4AAAEATAAAAAACoQQAAwEEAAOBBAAD4QQAA + EEIAABRCAAAgQgAAKEIAADxCAABMQgAAXEIAAGxCAAB8QgAAhEIAAJBCAACeQgAApkIAAL5CAADWQhMAAABH44Q/gTG6hD8CR+OEPzG6hD8xuoQ/DRkthz+VAoc/rIOG + P7AFhj8jX4U/UWiEP8JKgT+7o3w/WPZ2P+A3dD8aV2w/NkhgP3dUUT8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMz + P2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEA + AAAAAIA/AAAAAAOAAABIAQAAAAAAAAAEAAAAAgAAAAMAAAAEAAAAAQAAAAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAA + A4AAAEgBAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMAAAAHAAAAAQAAAAAAAAAOAAAAEAAAAAAAAADNzMw9AACAPwAAAABQ + ZmY+/O+hQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAAEAAAAAAAAADgAAABAAAAAAAAAAAADcQgAAAAAzMzM/AAAAQABg6kUA + AABAzczMPgAAgD8AgDtFAAAAAJqZGT8AAEBAAAAAAAAAAAAAAAAAAwAAAAkAAAAAAAAAAAAAAA4AAAAQAAAAAAAAALVJTD4AAAhABACgQCAAgMCpBV1Au0vJQgAAoEAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAACAPwAAoMAAAAAAAwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAAD2KBxArUfhPwAAwD8A + AGHDAAAAAAQAAAAFAAAAAABAQAAAcMIAAMDCAADAwgAAwMIFAAAAF7fROBsv3TwAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAB + AAAAAwAAAAAAIEDKzEw/AADAPwAANEIAAAAABAAAAAUAAAAAAHDCAABAQAAAwMIAAMDCAADAwgUAAAAbL908AAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAA0FG4Ph2Fqz9cj4I/OF3jQl/0h0MFAAAABQAAAAAAQMEAAMDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAAAfhes+UrieP1yPgj8AAOJCAACywgUAAAAFAAAAAADAwQAAQMEAAHDCAABwwgAAcMIF + AAAAAAAAAFJJnTkAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAA + AEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAwAAAM3MTD+P + wnW+AADAPwAAkEEAAAAAAAAAAAAAAAABAAAAAAAAABE4VjgJKAAAUHJWSyYAAAJYRlRQ+ScAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3 + xyAc3ClsIyT4APzy/X0zwGRsbGG4JwAAdGR0TZ0GAAAVAAAAQ2xhc3NpY2FsIEd1aXRhciBXYXJtAAAAAAcAAABNb2RhcnR0fQAAAFRoaXMgcHJlc2V0IHVzZXMgUjg0 + IHJpYmJvbiBtaWNyb3Bob25lcyBrbm93biBmb3IgdGhlaXIgc3Ryb25nIGxvdy1lbmQgcHJvdmlkaW5nIGEgcm91bmRlciBzb3VuZCBpbiBhIEJsdW1sZWluIGNvbmZp + Z3VyYXRpb24uAAAAAEQBAAAVAAAAQ2xhc3NpY2FsIEd1aXRhciBXYXJtAAAAAMsFAABHUlVVAAAABgAAAAEDwE1UcmsAAAW1AP8CDmd1aXRfam92dXJjaGlvh0CQWFJk + Gn8ULVsUgBpAgVxYQII2kBx/FDlJFIAcQII2kB1/FDxSCoAdQIFmkBh/FChPFIA8QAAYQIIYkB9/CkBKFIAfQIJ8QECBApAffxRBRxSAH0BkKEB4LUAeQUAykBp/Ci1U + FIAaQIIYkB9/FEBBFIAfQIJyOUCBFpAcfwo5QhSAHECBAkBAgT45QBSQKE8AQ1ODOIBDQIEqkB9/FEFHFIAfQIE0QUCCBChAKC1AgQKQL1gUPlgAQU0KOESEEoA4QFBB + QAqQHH8UOEcUgBxAPC9AADhAeD5AgQKQH38UQEcUgB9AgVKQGH8UKEwUgBhAglSQHX8UPlcUgB1Agy5AQIECkB9/FEA/FIAfQDwoQIEWQEBGkBh/FC9MFIAYQIFwPkBG + kB1/FD5UChx/CoAdQAqQOEwUgBxAgzg4QFqQHH8UOEYUgBxAZC9Abj5AHpAYfxQoVBSAGEAekB1/FD5ZFIAdQINWOEBQkBx/FDhMFIAcQG4+QFA4QFAoQIEMkFtKgQww + WBRBSxQ3Q4M4gEFAgRaQH38UQEwUgB9AHltAgVIwQCg3QIE+QECBIJArTgBASgo7UoM4gEBAgSCQH38KPj8UgB9AgjYrQAA+QEY7QIFIkDBUFEFPHjdFgkqAQUCCDpAf + fxRARBSAH0CBZkBARpBARQo7T4FmGH8UK0wUgBhAgT5AQIECkB9/FD5SFIAfQIJoPkCBFpAffxRAOxSAH0AoK0AAMEAAO0BQQEAoN0CBApBYSh4cfxQ1WBSAHECCIlhA + AJAdfxQ5VBSAHUCDapAffxQ+QQqAH0CCSpAYfwCAPkAUkClUFIAYQIIikB9/FDxKFIAfQIQIPEA8kB9/FD5GFIAfQIFSNUBGkBx/FDVXFIAcQIIOOUBkkB1/FDlPFIAd + QIJoNUBakBx/FDVOFIAcQIE0KUAAPkAoOUBkkBh/FClPCoAYQAqQHX8UOVsUgB1Agy41QG6QHH8UNUwUgBxAeDlAeClAgRY1QG6QGn8UL2cUgBpAhDCQHX8UOVoUgB1A + giyQH38UPlIUgB9AHi9AgRY5QDKQGH8UKFwUgBhAgjaQIX8UQGQKgCFAgxpAQCg+QDyQIX8UQVwUgCFAgiJBQACQGn8UL2IUgBpAghiQIX8UQGMUgCFAhAiQHX8UOFkU + gB1AgWYoQEaQGH8KgDhAAJAoWxQffwCAGEAUkD5ZFIAfQAovQIMkQEB4PkAAkCF/FEBjFIAhQIJ8KEBQQECBApAtXAo+ZBQ0VoZwgD5ARpAffxQ8UwqAH0CCBJAYfxQo + TRSAGECBUjxAbpAffwo7WRSAH0CECDtAMpAffxQ8TxSAH0CBKihAKC1ARpAafxQtYhSAGkCCSpAdfwo3YhSAHUCBNDxAgWY0QFCQHH8UNE0UgBxAgWY3QDyQGH8UKFgK + gBhAHpAdfxQ3YBSAHUCDajRAUJAcfxQ0UhSAHECDaihAKDRAPJAYfxQpZxSAGECCaDdAgg6QHH8UNE0UgBxAAC1AgiyQHX8UOVYUgB1AhCaQH38UPE8UgB9Agxo8QHiQ + H38KO0UUgB9AgXApQEaQGH8UKFkUgBhAADRAgg45QG6QHX8UOVgUgB1Ag2CQHH8UMk8UgBxAgQw5QIFIkB1/FDhlCoAdQIJ8O0CBDJAffxQ7WBSAH0CDEDhARihAgRaQ + LWUUOWWDQoAyQG47QDKQHH8UNGUUgBxAgTQ5QGSQHX8UOWUKgB1AgT6QGH8UKFYUgBhAgkCQH38UO2AUgB9AhFiQIX8KQFwUgCFAgWY0QGSQHH8UNGAUgBxAgUg7QHiQ + H38KO1QUgB9Agl45QIEgkB1/FDhaFIAdQIE+KEAeO0BQkBh/FChYFIAYQACQH38UO00UgB9AgyQ4QIECkB1/FDhmFIAdQIF6KEAALUAANEAAOEAAO0AAQEAA/y8AUHJz + VQshAABHAAAACAAAAGFsaGFtYnJhAAAAABAAAABDbGFzc2ljYWwgR3VpdGFyAAAAAAAAAABwcm9tYgAAAAEAAAABAAAAAQAAAAgAAABhbGhhbWJyYQAAAAAQAAAAQ2xh + c3NpY2FsIEd1aXRhcgAAgD8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAAAAAAAAAAAAAAcnlhbBAAAAACAAAAAAAAAAAAAAD/////b2xvcxEAAAAB + AAAAAQAAAEYAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAA + AAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4AAAGAtAAAAAAAgQgAAgD8tAAAAAACAtYH8Pqq/AwAAgLVwgI6/AACAtQAAgLWD1EvMPwqovAbAAACA + tQAY8rysX5y/IEkSv5Dc07+sX5y/aKGAvxAK6D9ooYC/aKGAvwUAAIC14rZtwDCb77+sX5y/AACAtQAAgLWRAwAAAAEAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAD + AAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAQAAAAAAEEGavqk+A4AAAGBYAAAAAACoQQAAgD9YAAAAoEYyP1fzCTE/V/QwP1OdMj8uwDM/g6gw + P0ZgMT81fi4/DKYzP1ulLz8bEzE/fsYzP1SvLT9+/C4/z+YtP26LMD9g2S4/6QgxP2hOMz/hWi4/ACU4P3W9RD8Yyk4/6lBVP+UBND8rAUw/6s1FPy/XST+WeE4/N041 + P9eWOz93uEk/C31SP1i6MD/JkTA/HvI4PwyqUz+lEFE/s30xP/NTNT+9vzs/ag45P0S4Zj8mlig/bh41P1bmPT8jc1s/68heP/Nbbz80MHI/0pp8P+6YiT+lxZE/Czie + P4FvoD+/AKU/LHeqP5rarz94F7Y/u/O4P+ELwT8V8cU/09DKP5bhzD8pScs/CXXLP+g2yz+C1so/yXjKP5qcyj8LL80/Wn3KP0DOzD+E98s/q7fNP0z7zD84+cw/T/bL + Pys4zT/Be8o/FaPLP6dyyz/8e8w/yKLLP/ElzT85gM0/1/jLP4jcyj8BAAAAAQAAAAAAAAAAAFDBAACgQAAA2EIAAAAAA4AAAFAAAHBCAQAAAAAAAAAAAAAAAAAAAAAA + AAABAAAAAAAAAAAAAAAAAKDAAACgQAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BgAAAAAAAAADSBAAAAQAAAAAA + AAAAAACAAAAAPwAAgD8DgAAAYFgAAAAAAKhBAACAP1gAAAAxGb2/V6ZHIkAPvgDAcK6Lv634iD+CX8K/WXCfv+f9Oz8mmJG+R0obQMV2GL51u7y/Y4oMQIutLcCwKW2/ + Px+OP3MHPUCUFLC/27QAQF7lLT5r2km/+YQKv8j29z/BxFE/FaEOwH4kNsBDKRxAmf85QGmK+z5umT/AC9Hiv+4w6b8LbB3AfNjiv9UOBMAJN8e/uDS+v5gXO79hADNA + 1g8zwJjOszpO8fu+Hh/uv6181b/q0O8/HwmIP2JnPT9DjyHAf2hmv+QG3b9v+w/AafvgvnZxPUAVCo2/TEcMv6PgNcDCyWA/mswnwJ3brD6bVYe+/VcZv9MrJr/lTgNA + 4mcfvye4YD4gYi8/oBc4wCYWIMAlYoq+c5fsv7upGsDN4tS/6bksQEPL/j/1CyRAsPUbP3JmR78buDZAVHQ+QLDh/z519R5AJHTKPtnxjj+7g4U/kQF3PQNMH0Do+gC/ + 9QLuPwOAAABgWAAAAAAAqEEAAIA/WAAAAAAAAEAHAACAPwAAAEAAAIA/AACAvwAAAAAAAADAAAAAQAAAAEAHAACAPwAAAEAAAIC/AABAQAAAgL8AAABAAACAPwAAgD8F + AACAvwAAAMAAAACAAABAwAAAAEAAAABAgQAAQMACAAAAQAAAAMAAAADABwAAAEAAAADAAABAQAAAgD8AAEBAAAAAQAAAgD8AAIA/CQAAAAAAAIC/AAAAgAAAgD8AAEBA + AAAAQAAAQMAAAACAAACAvwAAgL8FAAAAQAAAgD8AAIC/AAAAQAAAAMAAAADADAAAgL8AAIA/AAAAgAAAgD8AAADAAABAwAAAAMAAAEBAAAAAQAAAAMAAAIA/AAAAAAAA + AIAFAABAQAAAAEAAAADAAAAAgAAAgD8AAIA/BwAAAAAAAIC/AAAAwAAAQMAAAEBAAAAAgAAAAMAAAADACQAAgL8AAEBAAAAAAAAAgD8AAAAAAACAvwAAAIAAAIC/AAAA + QAOAAABgWAAAAAAAqEEAAIA/WAAAAG+wgT9X4mlpPznpfj9yN34/gfWGPwF6cz9lcHU/tR2CP8p+ij9ryYU/BpZ/Pw4/gz+Xxog/Hh+DP2fRgT+7rYA/eU15P/WNfj+i + ooI/4Zp3PyMFaj8kLHQ/Nd9oPxVvez8pcYU/TBtrP1NYjD8RUYc/639tPxdCfj+q3Wo/MhSBPwqHdz8dDX0/4+qGPy95Zj/moHU/TEdwP73QgT/RjX8/ZBRqP3qTZj/9 + I4Q/Em+MP86Mhj8Ccoc/EhZ5P1mKaj8/j4g/C06GP2angT9ndWs/93ZpPzBmiD8P5mg/u911P+hniD9HhYo//y9rP0W/jD8jJnc/8DFsPzO4fT9lGHY/j2eKPynucT8S + DIE/r6VoPwVThj/O44Q/DrqMP/Eraz/XOXg/1fKIP/7qZj9IOYc/s3qCP5SAgz9T6Wo/pZxxPwT9iz8j5H4/cUxrP1lieD9RZYY/cqN5P0HogD/dUIE/AgAAAAEAAABV + AAAARXF1YWwgdGVtcGVyYW1lbnQKVGhlIHN0YW5kYXJkIHR1bmluZywgd2hlcmUgdGhlIG9jdGF2ZSBpcyBkaXZpZGVkIGluIDEyIGVxdWFsIHN0ZXBzLgwAAAAAAMhC + AABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQBAAAAAAAAAAQAAAAAANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAA + AAAAAAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAACAAAAAACAPwEAAAAAAAAAAQAAAB8AAAADgAAAUAAAgD8BAAAAAACAPwAAAAAAAIA/A4AAAGACAAAA + AACoQQAArkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAAAAAA + AAAAAIA/A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ + ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAIEIDgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8D + gAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAA + AAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9m + ZiY/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/FrH9PgOAAABACAAAAAAAIEIAADRCAABIQgAAWEIAAFxCAABsQgAAgEIAAJhCCAAAAPe+fz+DLDJ+P4ElKoE/ + gQEAAAABAAAAAQAAAAoAAABTbWFsbCBIYWxsCwAAAG1lZGl1bWhhbGwxAAAAAAAAoEEAAIA/zcxMPwAAAADNzEw9kiQJwc3MzD0AAAAAgML1vgAAqkMAAIA/AACAPwEA + AAAGAAAAAACAPwAAgD8DgAAAYFgAAAAAAKhBAACAP1gAAAD4KIk/V+z6iz8cInU/OcZxP/+jbz8B4YQ/HYOEP8dffD/0QIk/5YJyP6Nyez9mjIs/Mzx6P048bz/TfIQ/ + 11qGP6zvhT+KT4w/TLZ3P3sOgT82bIg/rqCLP0bniD8SmYg/zB2IP8lkaj+BkoE/tJWBPx3waT9pKYY/+zqMP9owhj/aeG4/4a56P3ZQiz/pCmg/e6aGPyWxiz9FKW0/ + kOyCPwU1eD++0Is/E7+DP3t7iD+y54Y/EixoPz/vaz8rQng/ilmMP3nmaT+t63s/D2x6P724ej8pL34/wOuHP1kxdT8MXYs/NeeIP+0mhj9ne3M/fY6DP+4Xcj8kqYQ/ + cPyDPzRXgD/SWGc/cAp9P3bjcD+C/Ys/nUxqP4e1hj+ti38/wAGFP902eD/W42w/gI6BP5tuhT/+Zn8/q9uGPzewhz9TrYo/qAKAP0XAiT/FgnI/Eb6MP3tehD8Hi3k/ + WyxpPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYFgAAAAAAKhBAACAP1gAAADerV09V0xFDT7r6qm9s7EYPl4BOD27aj4+UHSgvVEbdL6nFmY+ + ZCXJPZe/gr1O+Ty+xix4vuDvFj00D5Y+aY5YPs+wlr7PdgC+DoRGPlVjRT5BQwA+Mb0vvtPJq738nBy8G+0OvrJqCb6rXwE+neomPXPZFb6Q3I0+5TStu5fbiz4BIzO+ + wbrPuwbNjD5aJoe92fADPoWzXT0VyDs+hFYzvenCfL7NrRW+QFtrua3Llj31hZk+8cTTPc6Iej77HFu+0Gi6PSaLkD4pGom+uo8DPhZUFj4L41U+WwpTvlBpe77qU+09 + AA6JPSf5Ir7Eg2A+VnZ3Pv0Rer5sOX8+Ah7HvfSmKL2R83q+PLMUvumZLb5zMEe+NSwOvmuk071YAT++diCBvtg+B761cMS98V0tvgLJlL5loTO97mCsPV9PnTz1ZE09 + XqQDPsPWtr2uebS9za92PotRlD1kgAw89EmCPgEAAAAAAAAAAQAAAAAAAD8AAIBAAwAAAAAAgD8AAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOA + AABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD+amZk+zcxMPwAAgD/NzEw+CAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAGAtAAAAAAAgQgAAgD8tAAAAAACAP4k0M1M/AQAAgD8AAIA/oAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAA + AIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAAAtAAAAAAAgQgOAAABghwAAAAAAAAAAAIA/hwAA + AAAAAAD/AAAAAIYAACRCA4AAAGCHAAAAAAAAAAAAgD+HAAAAAAAAAASambnAAAAAAM3MzD0AAAAAAAAAAIWambnAgc3MvMACzczMvQAAAAAAAAAAgwAAwMADMzPDwM3M + vMAAAAAAAAAAAITNzEzAgs3MzD0BAAAAAAAAAADkAAAoQgOAAABghwAAAAAAAAAAAIA/hwAAAAAAAAD/AAAAAIYAACxCA4AAAGCHAAAAAAAAAAAAgD+HAAAAAAAAAI0A + AJDAAQAAAAAAAAAA9gAAMEIDgAAAYIcAAAAAAAAAAACAP4cAAAAAAAAAiGZmxsABAAAAAAAAAACBzcy8wAEAAAAAAAAAAPcAADRCA4AAAGCHAAAAAAAAAAAAgD+HAAAA + AAAAAP8AAAAAhgAAOEIDgAAAYIcAAAAAAAAAAACAP4cAAAAAAAAAhc3MTMABAAAAAAAAAAD+AAA8QgOAAABggQAAAAAAAAAAAIA/gQAAAAAAAACTzcxMwAEAAAAAAAAA + AAMAAIDAzczMPQAAAAAAAAAA5gAAQEIDgAAAYHoAAAAAAAAAAACAP3oAAAAAAADAAjMzU8AAAAAAAAAAAI9mZobAAQAAAAAAAAAA5AAAREIDgAAAYHMAAAAAAAAAAACA + P3MAAAAAAADAAwAAQMDNzGzAAAAAAAAAAAADZmaGwM3MzL0AAAAAAAAAAOoAAEhCA4AAAGBtAAAAAAAAAAAAgD9tAAAAAAAAAI8zM6PAAQAAAAAAAAAA2gAATEIDgAAA + YGYAAAAAAAAAAACAP2YAAAAAAAAAAmZmpsAAAAAAAAAAAIrNzMy9BmZmRsDNzEzAzcxMvgAAAADNzMw9AAAAAAAAAADQAABQQgOAAABgYQAAAAAAAAAAAIA/YQAAAAAA + AACIMzMzwIWamTnAAQAAAAAAAAAAzwAAVEIDgAAAYFsAAAAAAAAAAACAP1sAAAAAAMDAAQAAAAAAAAAAgs3MTMACAABAwAAAAAAAAAAA0gAAWEIDgAAAYFYAAAAAAAAA + AACAP1YAAABmZobAAQAAAAAAAAAABmZmhsAAAAAAZmamwAAAAAAzM7PAAAAAAAAAAADMAABcQgOAAABgUQAAAAAAAAAAAIA/UQAAAGZmhsAEAAAAAJqZWcDNzMw9AAAA + AAAAAADLAABgQgOAAABgTQAAAAAAAAAAAIA/TQAAAAAAQMACMzNTwAAAAAAAAAAAgjMzU8ABAAAAAAAAAAAIzczMPZqZWcAAAEDAzcxMwM3MzL0AAAAAmpmJwAAAAAAA + AAAAuwAAZEIDgAAAYEgAAAAAAAAAAACAP0gAAADNzJzAAQAAAAAAAAAAApqZicAAAAAAAAAAAMIAAGhCA4AAAGBEAAAAAAAAAAAAgD9EAAAAAABgwAEAAAAAAAAAAITN + zMw9gTMzc8ACMzMLwQAAAAAAAAAAtwAAbEIDgAAAYEAAAAAAAAAAAACAP0AAAAAAAAAAgs3MTL4DAAAAAAAA0MAAAEDAAABAwAEAAAAAAAAAAIYzMzPABM3MjMBmZibA + MzMzwAAAAAAAAAAAqgAAcEIDgAAAYD0AAAAAAAAAAACAPz0AAAAzMzPAAQAAAAAAAAAAugAAdEIDgAAAYDkAAAAAAAAAAACAPzkAAAAAAEDABc3MzL0zM1PAzcwswM3M + zL0AAAAAAAAAAIPNzMw9AQAAAAAAAAAArAAAeEIDgAAAYDYAAAAAAAAAAACAPzYAAAAAAAAABmZmBkDNzAxAZmYGQGZm1sCamXnAAAAAAAAAAACCZmamwAHNzEzAzcxM + wIIAAEDAATMzM8AzMzPAgQAAAACJzczMvQEAAAAAAAAAAJYAAHxCA4AAAGAzAAAAAAAAAAAAgD8zAAAAmpnJwAEAAAAAAAAAAIgAALDAAwAAAACambnAAAAAAAAAAACj + AACAQgOAAABgMAAAAAAAAAAAAIA/MAAAADMzU8ABAAAAAAAAAACtAACCQgOAAABgLQAAAAAAAAAAAIA/LQAAAAAAAACsAACEQgOAAABgKwAAAAAAAAAAAIA/KwAAAAAA + AACCzcycwIEAAEDAAZqZOcCamTnAgwAAAACfAACGQgOAAABgKAAAAAAAAAAAAIA/KAAAAAAAAACCzcwswAKamYnAAAAAAAAAAAChAACIQgOAAABgJgAAAAAAAAAAAIA/ + JgAAAAAAAAClAACKQgOAAABgJAAAAAAAAAAAAIA/JAAAAAAAAACIMzODwAEAAAAAAAAAAJgAAIxCA4AAAGAiAAAAAAAAAAAAgD8iAAAAAAAAAILNzEzAApqZmcAAAAAA + AAAAAJsAAI5CA4AAAGAgAAAAAAAAAAAAgD8gAAAAAAAAAIHNzCzABAAAQMCamTnAAACgwAAAAAAAAAAAApqZ+cDNzAzAzcwMwIFmZgbAAQAAAAAAAAAAkQAAkEIDgAAA + YB4AAAAAAAAAAACAPx4AAAAAAAAAAwAAQMDNzEw+AAAAAAAAAACZAACSQgOAAABgHAAAAAAAAAAAAIA/HAAAAAAAAAACZmZGwAAAAAAAAAAAmAAAlEIDgAAAYBsAAAAA + AAAAAACAPxsAAAAAAAAAgTMzo8AFAAAAADMzw8AAAAAAzcyMwAAAAAAAAAAAgQAAsMACMzOTwAAAAAAAAAAAjQAAlkIDgAAAYBkAAAAAAAAAAACAPxkAAAAAAAAAgpqZ + OcABAAAAAAAAAACTAACYQgOAAABgGAAAAAAAAAAAAIA/GAAAAAAAAACXAACaQgOAAABgFgAAAAAAAAAAAIA/FgAAAAAAAACVAACcQgOAAABgFQAAAAAAAAAAAIA/FQAA + AGZmtsAHMzOzwAAAQMDNzPzAZmZGwAAAQMCamTnAAAAAAAAAAACMAACeQgOAAABgFAAAAAAAAAAAAIA/FAAAAAAAAACTAACgQgOAAABgEwAAAAAAAAAAAIA/EwAAAAAA + AACSAACiQgOAAABgEgAAAAAAAAAAAIA/EgAAAAAAAACRAACkQgOAAABgEQAAAAAAAAAAAIA/EQAAAAAAAACQAACmQgOAAABgEAAAAAAAAAAAAIA/EAAAAAAAAACPAACo + QgOAAABgDwAAAAAAAAAAAIA/DwAAAAAAAACO7quHPgOAAABAKQAAAAAAIEIAAChCAAAsQgAAMEIAADRCAAA4QgAAPEIAAEBCAABEQgAASEIAAFBCAABUQgAAWEIAAFxC + AABgQgAAZEIAAGhCAABsQgAAcEIAAHRCAAB4QgAAfEIAAIBCAACCQgAAhEIAAIZCAACIQgAAikIAAIxCAACOQgAAkEIAAJJCAACUQgAAmEIAAJpCAACcQgAAnkIAAKBC + AACiQgAApEIAAKZCKQAAAJXNPT8MPkRVP3ySZj8QIm8/GcdCP9oRWz9QkVs/Ah5fP/6UXT8yOEE/UJFbP119aj9cgTo/XIE6Pxs0RUk/a9pjPyg8aD9/Ezk/MjhBP11u + Tj9DiUo/wY2CPy0/Mj+YYEY/7zVQP+iKbj/nlHQ/ZUODPxewhz+f1Iw/e9KbP0dPqD/K6rQ/n6+/P4f4xT+Drsw/bgXSP5Ol1z+BrOA/KDzoPxAi7z8BAAAAAQAAAAEA + AACPwnU/CteDPwEAAAABAAAAAQAAABSuRz/2KJw/AQAAAAEAAAABAAAA+n4qP+Oluz8BAAAAAQAAAAEAAAAAAEA/AACgPwEAAAABAAAAAQAAAJqZWT+amZk/AQAAAAAA + AAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAAgD8EAAAACgAAAAkAAAAHAAAAAQAAAAEAAAAAAIA/ + AACAPwEAAAABAAAAAQAAAAEAAAABAAAAH4VrP3sULj8AAIBAAAAWQ0YAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAA + AAAAAAMAAAAHAAAAAAAAAAAAAAAOAAAAEAAAAAAAAADNzMw9AACAPwAAAAAAAAAAAABIQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAA + AAAAAAAAAAAAAAAADgAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAA + AAAAAA4AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAACAPwAAQMEAAAAA + AwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAADA/409UtUJv1C0VT5r2VnCAAAAABEAAAAFAAAAAAAAANi2zcEAAMDCAADAwgAAwMIFAAAAAAAAAAB8TLgAAAAA + AAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAQAAAABAAAAAwAAAGD8jz1C5Qm/ULRVPrn87kEAAAAAEQAAAAUAAADmYqrBAAAAAAAAwMIAAMDC + AADAwgUAAAAAhEy4AAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABAAAAAEAAAADAAAATyWBP9qF1j/41Ek+oXUGQwAAAAACAAAA + BQAAALZR5cDf443BAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAACbT6A/ + PBXxv+g2Jj59E5tBgJkSwAIAAAAFAAAABh/iwXUz6MAAAHDCAABwwgAAcMIFAAAAAAAAAAAAADMAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA + AAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAwAAAAAAIEAzM7M/6lH4PwAAkEH9mexCAAAAAAAAAAABAAAABAAAAAEAAAAMAAAAQ29udHJvbGxlciAx + IQAAAHtTZXRQYXJhbWV0ZXJ8M3xuZnhMZm9SdHwwOjAuNTI1fQEAAAAMAAAAQ29udHJvbGxlciAxIgAAAHtTZXRQYXJhbWV0ZXJ8M3xuZnhPbnNldHwwLjI6MC4wNH0B + AAAADAAAAENvbnRyb2xsZXIgMR4AAAB7U2V0UGFyYW1ldGVyfDN8bmZ4VmliQ3wwOjAuNn0BAAAADAAAAENvbnRyb2xsZXIgMSMAAAB7U2V0UGFyYW1ldGVyfDN8bmZ4 + VHJtRHwwOjAuMjk3NjY1fRE4VjgAMwAAUHJWSyYAAAJYRlRQ8DIAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE33gCFGBtY8Rpjejdn1A38 + o2RsbGGvMgAAdGR0TaQGAAARAAAAU3RlaW5ncmFlYmVyIFdhcm0AAAAABwAAAE1vZGFydHQkAAAAUHJlc2V0IHdpdGggYSB3YXJtIGFuZCBtZWxsb3cgc291bmQuAAAA + AEQBAAARAAAAU3RlaW5ncmFlYmVyIFdhcm0AAAAAMwYAAEdSVVUAAAAGAAAAAQPATVRyawAABh0A/wIvQ3JhaWcgUmljaGFyZHMgLSBMZWF2aW5nIEFnYWluIC0gQ3Jh + aWcgUmljaGFyZHOEYrBAAhhAGBZANBhAUBZAbBhAf4ZIkEdEgTJHAIUYSFyBJkgAhgqwQGoYQDYWQAoYQAAGkEcrGkMuBEArCj4vHDAkgXiwQAQWQC4YQFYWQHYYQH+D + ApBAADo+ACBHADZDAAY3JocMQyYkPiaHKjsYTD4AjUpDAIsSR1YKPkSBCD4AGkcAhSYwABA7AFZIRAxAREo3ADBAABhIAIZysEBaFEAkGEAAHJBHNwxDORQ+MgYwKoMi + sEAqFkBWGEB/gnSQNykCPgAiRwAaQwCGIkMoID4nhwQ7F3A+AIliMAA+OwAuNwAOQwCMekIrDLBASASQPCYUsEAMDJA5IQqwQAAUkDIggTywQCwWQF0YQH+CZpBCABw5 + ADQ8ABIyAIlwQycGOyACsEBsFkA6DJArHgywQAYUQACELEAUFkA+GEBaFkB2GEB/gRyQMhyHfDccIjsAgXZDAIQqNwBQKwBkMgCHGLBAbgKQQyEQPCAGsEA8FEAICpAr + GxCwQAAIkDQYg0SwQAgWQCYWQEQYQGoWQH+NCpA8ACRDAIU8Q0AKPEQUKwAKNACCLkMAHjwAhHxDJAwyHQSwQEYMkDskCrBADAqQKx0QsEAAgzRAHBZAQBhAZRZAf4lo + kDsAgQA3F4lqQwCWaEdOgWI3AII6MgACKwCCLkcALkhKgUpIAIYWsEBiFEAcGEAABpBHKBZDNxRAPAY+LSIkKUqwQDoWQHgYQH+CGpAkAIJGPgA8QAASRwAoQwB+NDGH + TkM0Ejwuhg5DAAQ8AG43IYg0QB8GOxmICDsAMkAAiC43ABpHTYEiNABWRwCFCkhWgW5IAIUKsEB4GEAmFkAAApBHSxY+QywkLnawQEwWQH+CJJAkAAwwNhI+AAxHAIIO + MACBKDQqgxQ3PYQ0Q0ISPDiFIjcAgUo8AIECQwCGOjckhjw3AIEyNACIXrBAZxZAKgSQQisEPjUQsEAAApA5Iy42GgwqKIEUsEAKFEAyGEBMGEBxFkB/hDyQOQAKKgBA + NgAIQgAQPgBIRSODSEUAhFRDKBQ7IQiwQGAWQCwWQAAIkDcVBisZgnqwQCIaQEoUQHgYQH+DCpAyJEI7ABZDAIcgPiSFFj4AODcAMDIASCsAiHQ7NABDOwg3LBCwQH0U + QEECkCgmDi8rCLBACgaQNCMQsEAAgX5AFhhAOBZAWBZAf4MwkEMAggxAPRY3ABA7AExAAHI0AIVqQFAMNzYgNCmHaDcAFigAREAAFi8AaDQAhxJDMhQ8PASwQGICkDku + FLBAKhKQMDACKScEsEAAFJA1GYJcsEAGGEAoFkBIGEB0FkB/g3iQPABUQwCDAjkAhGBALR48KIE8NQCBJDAATikAgwBAAEo8AA45KGY5AIZIsEBnDpA8LAI0KgiwQCwW + QAAmkDAbCiQigx6wQCIWQEwWQHQYQH+CHJA0ABQwAA4kAEwrGodENDF6PACBAjQAgQ43SVgrAIMkNwAGPGSENjwAgxY5OTAyLwqwQGQKkCspCrBAHBhAAIFgkDkADjsz + gXawQDwWQH+CVpA3LyY7AIc+QygUOymJODcAPkMADjsASDIAJCsAlQhHSIc4SDgCRwCEFrBAf4IekEgAgQKwQABQkEcpDEAyED4eHjAjgh6wQH6EcJBAABA+AAo3LSBH + AIdeQzQWPiqTeD4AhEhDAIkqR1OBZDAAhE5HAGhIOQg3AIFASACFEjAaZrBAQhRABBhAABCQOxoENxZSRzIiPyuDDLBADhRAQRhAeBZAf4I8kEcAgQo/AIkiQy2CGEMA + jXI/IShFKIFcPwAoRQCFJDsAhGI3AIEmMACDNj4yg3Q+ACY7NIQ4OwAyOUOEVjkAODIihCAyAIJsKyGCCrBAShZACgKQMh0WsEAAgQSQOTMKPi+CTDkALDsvhhI3Jzaw + QCIWQGQWQH+DZpA3AIF+KwAEOwAIPgCBNjIAnlg3KIh8PiOBEjcAXkccUE8RRlYiXj4ATF8fLE8ATFYALEcAigBfAOggsEBdFkA2GEAYFkAAAP8vAFByc1X7KwAARwAA + AAwAAABzdGVpbmdyYWViZXIAAAAAEgAAAEdyYW5kIFN0ZWluZ3JhZWJlcgAAAAAAAAAAcHJvbWgAAAABAAAAAQAAAAEAAAAMAAAAc3RlaW5ncmFlYmVyAAAAABIAAABH + cmFuZCBTdGVpbmdyYWViZXIAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MR + AAAAAQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA + AQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOAAABgWAAAAAAAqEEAAIA/WAAAALq9XcBXoIL0v2jHi7/ghFO/2IotwLiecsBA3fC/dPQHwBAx + s7/gRJw+gO4zvtDwlb/orIS/kFkGv6Dntr8QGB6/CNmAv0AMLb5wN0I/ygQoQFDnzj/guZ4+kKEhv4zi178QGkq/EGwOv5DjML+gB/G/kGm0P8D1IT7S5xNAqDsfPyBJ + qT5ocSo/4F+vPsjzpL/Yn6G/+I+wPwQKpj+CxQhAIogWQEB6Ij6g71A/QKfWvmAw3j7Aq62+wFZ0v1QSvD/4gBtAUEbeP/gHyT+ARaC9ySaNQAAj4T4AaRi/wGQnvkBm + fL4A/s+9lIIgwLjWH8CQVHa/CCsgwJCGBUCgCm4/LMHyv7TrS8BAVmC/9Eu5vwBRdr3oyrO/UBY2wADYgDtAieO+OKaDPwA+gT7A+KS+2MRtwOi6iMDkemHAHNyVwPho + n7+yd63AfCx4wND10cAfwLnABWH4wJo6vsAk8urAAwAAAAEAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAAA + gD8AAAAAAQAAAAAAAAAaXRU+A4AAAGBYAAAAAACoQQAAgD9YAAAAb0WBP1ehU4I/NgaEP0iPhT+C2YU/xlqFPx9wiD9yMoc/AI+FPxrkhj/K5oU/A7yEP1/egz/4o4M/ + psyLP/9Qiz9z5Is/bE6JP/SgiD//2Yc/6Q+KP2Fgij8UnIo/SqiLP/+Kiz9kgIs/KbGLPzU5iz8RdIs/sHqKP+PTiT+0F4s/3OeLP3c/iz+i7oo/02CIP6vkhz/sbYg/ + awGIP//ehj+Maoc/X5OFP8iphT/hyoU/LmyFP5ldgj9Hq4M/z3WBP2UDgT9SwH0/ObN9P2LHgD+8zYI/mkODP7ZdhT8G7IM/3K+BPxPGgT/TKIE/EYt9PyvRez9PJH0/ + lHt3Pzp5dT+iJnE/Vy5wP8YZcD+uRHA/AbhqP6FIaz9FSmc/1sFlP4WEYz956mU/0/ZiP7ldYD96XmE/Ib9ePxbyXz/O7V8/FblbPxDQXz+eAFo/89xdP0QkXD937Vg/ + zZBaPyowXD8BAAAAAAAAAAAAAAAAAKDAAACgQAAAtkIAAAAAA4AAAFAAAHBCAQAAAAAAAAAAAAAAAAAAAAAmpD4BAAAAAQAAAAAAAAAAAMDAAABAQAAAAAADgAAAYAIA + AAAAAKhBAACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BgQAAAKlHYT7SBAAAAAAAAAAAAAAAAACAAAAAP4QOMj8DgAAAUAAAcEIBAAAAAAAAAAOA + AABgWAAAAAAAqEEAAIA/WAAAAIdZhb9XEMduvUDSBMAX5Yu/iY5jP5W/Xj+u4+w/MW8tvm6uQr4V6+Q/tydEPy5HPj+q5qO/7oUTwA1mKz8OqiQ/LNnOP4A+yz8lCA8/ + GFzxvk+nAL+icsS/aGkRv4Qazb+epNG/SLKmPhhjkz7k358/AsKaP5CA6r+3GJA/t+Jqv3lJHD16dQDAH1FyP6Q9Zj8aLxi+0RdKvpisfL5F/RLAgm6sv6C3y74N1OW+ + AAAAP/oVDb8MiSbAfpHTv+sKNL82NXDABr3mvx367L/SHvO/SJQ8wF706juTOIHAUkdFwFsMCMC4vwrAFBBWvvn3h8BjbFLASdYUwNmWi8BfuYzAMS3dvq/GXcAW64/A + P+qQwBOEx79AnyXAlmxnwAdV0r+D2SrAI/TYv6sMLsCXI9+/tydEv4t1csAZ1TPADSl1wFzHWb+Vv16/ouN4wHUNesDALXvA5EQ8wD1TfcAjWX7AA4AAAGBYAAAAAACo + QQAAgD9YAAAAup98P1cBy30/atmLP+RIiD9+3nQ/WG+AP1VNgD/VQII/71FsP2XQdj9cT4w/2/pvP5DHjD9gX20/zqlqP5gcaD+jSYQ/V2l8PxSXiz9eSIE/oLBtP1Yn + cz/zQ4w/i7OKP07Cdz/6sHU/VGaBP4l2fz9ST34/Ks+CP4rEij+KRWo/EmlmP8QAbT8PSIM/wHF8PxZRZz/aOnI/aJ1sPww+hT901Gs/sgRqP/Eqgj+QvHM/q/B7P3Cv + hj8Kj3I/RCxoP2cdjD9Ufoo/GFOBP9q4fD+V1IM/b+NwP3ENdj+4voY/96ZoP1l+hT88vIk/PPuFP7fOgz+c83s/AAV4P9raeT/bQnk/WO5tPxRQhT/fNoo/IXGHPzs9 + iD8u63k/8nqDP914cD+uqYY/7TBsPyiMiD9wf2c/IIuGP6HFfj8TpHk/X0iBP07+cD8Ld4Q/QIt8P774fz9P9os/UGR7P1Ybhz8CAAAAAQAAAFUAAABFcXVhbCB0ZW1w + ZXJhbWVudApUaGUgc3RhbmRhcmQgdHVuaW5nLCB3aGVyZSB0aGUgb2N0YXZlIGlzIGRpdmlkZWQgaW4gMTIgZXF1YWwgc3RlcHMuDAAAAAAAyEIAAEhDAACWQwAAyEMA + APpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAEAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOAAABA + CgAAAAAAqEEAAMBBAADYQQAA8EEAABBCAAAoQgAAQEIAAFhCAABwQgAA2EIKAAAAZmYGwQgzM9PAMzOTwDMzU8AAAADAZmamvzMzM7+amZm+AAAAAAAAAAAAAAAAAAAA + ABethj8BAAAAAQAAAAEAAAAYAAAAA4AAAEgBAAAAAACAPwAAAADNzIw/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOA + AABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAAAAAAAAAAAIA/A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEA + AAAAAAAAAAAAAAAAAL8AAAA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAA + AKhBAACuQgIAAAAAAIA/gQAAKEIDgAAAQA8AAAAAAKhBAADQQQAA+EEAABBCAABAQgAAcEIAAJBCAACoQgAAtEIAAMBCAADIQgAA0EIAANRCAADWQgAA2EIPAAAAAAAA + AIYAAIA/BwAAQEAAAKBAAADgQAAAIEEAAEBBAABQQQAAcEEAAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAGACAAAAAKCMRQAA+kQCAAAAAAAAAAEA + AEBAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAA + gQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAzcxMPgAAAAAAAAAAAAAAAAAAAD97FC5AA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOA + AABgAgAAAAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAABAAAACwAAAEphenogc3R1ZGlvCgAAAGphenpzdHVkaW8AAEBALDObQQAAgD8zM/M/AAAAAMCntTzw//+/cKRK + PgAAAAAAAAAAGvSfQ83MjD8AAIA/AQAAAAYAAADQxYw/C5aTPwOAAABgWAAAAAAAqEEAAIA/WAAAAK5HgT8TCteDP7gehT8Uroc/MzNzPwAAgD/D9Yg/MzNzP9ejcD8K + 14M/o3B9P9ejcD8fhYs/AACAPwrXgz9mZmY/H4VrP1yPgj+QwnU/MzNzPzMzcz8GZmZmP6NwfT+4HoU/7FF4PwAAgD9cj4I/XI+CPwNmZmY/w/VoP9ejcD/Xo3A/DTMz + cz9J4Xo/uB6FP65HgT8AAIA/cT2KPzMzcz/sUXg/16NwPwrXgz/NzIw/o3B9Px+Fiz8fhYs/DJDCdT8K14M/kU0kPx+Faz9mZoY/AACAP8P1iD/sUXg/uB6FPwAAgD/D + 9Yg/cT2KP3E9ij8dH4WLP9ejcD8Uroc/7FF4PwrXgz/sUXg/kMJ1P2Zmhj+F65E/cT2KP1K4nj+4HoU/w/VoP2Zmhj8fhYs/uB6FP1yPgj9xPYo/zcyMPx+Fiz8Uroc/ + kMJ1Px+Faz8Uroc/w/VoPx+Fiz8AAIA/mpmZP1yPgj8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGBYAAAAAACoQQAAgD9YAAAAj8J1PSSuR+G9 + cT2KPgrXIz4K1yO8XI9Cvs3MTL6PwnW+4XqUvgrXI72PwnW+CtcjPgrXo7wAAIA+H4VrPo/CdT1xPYo+4XqUvpqZGb4K1yM+4XqUPuxRuD1cj0K+j8J1vnE9ir4AAIA+ + CtejPHE9ij4fhWs+KVyPvgAAgL64HoW+KVyPPVyPQr4K16O9j8J1vXE9ir5xPYq+Lc3MTD4K1yM8PQpXvrgeBT7sUbi9uB6FPo/CdT2PwvU9j8J1Pc3MTD6PwvU9mpkZ + Ps3MTL17FC6+Ctejvc3MzD2amRk+rkdhPgAAgD6PwnU+AACAPilcD757FC4+PQpXPq5HYT6PwvU9zcxMPj0KVz6PwvU9j8L1PHsULj6PwvU8CtcjPilcDz6PwvU9uB4F + PuxROD4pXA8+7FE4PpqZGT57FC4+uB4FPs3MzD2amRk+rkfhPa5H4T0EzczMPVyPQj6PwvU87FG4PQEAAAAAAAAAAQAAAAAAAD8AAABAAwAAADCacD9diY8/ZxyOPwOA + AABgWAAAAAAAqEEAAIA/WAAAAF7sgj9XowuAPzrGgT/M9YM/1ACBPwA6fT/804A/J1WCPzZDej+NK34/8Qt5P+Ffgj9afYI/0EZ/P5ehgT/f54E/HSGCPyDWfT+6AIM/ + RIKDPzsmgj8pZH4/cKuBP42Cfz/ZToA/6B2BPzgggT9RNH8/MxV7P0SCgz8H8oA/kzGAP3A/gD8vx38/tw2CP57LgD+cYoA/RZx9PwZUgT8obXc/c6OCPw4nfT+qkXs/ + GBh+Pwj6dz8303o/vlaAPwcCeD9STmo/9gqAP2aMgT9ZmYM/ev2BP4/FeD+aCXk/5Jt9P6jRgT+8knw/AdKBP7TggT8bAX8/5b53P0iGfz/L330/PG93P+KGeD+Pn4A/ + Qc97P9Hifj9LFoA/ryODPxeWfj9YDYI/UhmBP73Ofj8YeYA/0Ip+P+KGeD8/f4A/pI2DP8PGgz+7jH8/2cWDP7j/gT91D4Q/LtWCPz5sgz8C+4A/A4AAAEADAAAAAACo + QQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAAgD8HAAAAAACAP6FnkD5mZiY/ZmamP83MTD4IAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAADz2KE/Vy1hcj/sl1g/9tdrPzyObj/AJ4c/PFNzP77djz/soGs/6XhbP27TbD9wDFc/cE1UP+OS + Yj89/ow/afKCP/cNWj+CgF8/pf6MP3XTSj99oU4/r/twP1hlhj+xz3M/d7J4P/L1bz8J6XA/YAKIP5qePj9Qg2c/wCM7PwZDfz/VQHk/HH1dPwoycz81EIA/o0VqP+/W + Tj/MUGk/L5c7P6CCND+eo4M/aWZZP0sLbD/fMlI/vN2OP0NrrT/fooQ/H+JrP1DoYz8kiGc/zSZ2P+tbFD8MrlM/36KEP/nmeT9yrYA/B6x2P/uNoz86tpQ/dFOjP9cE + nD8Xc0c/unlHP2Tjmz9ujqQ/I3p6PwMYkz/wZYI/+TOQPy48tD8hwYc/3XWPP4GHiz+BQaI/WIaZP0vswz/ZQdY/4Qa5PwgNyT/EHJg/oHzVPzMPyj8B4Og/8kTNP36d + 1T9AGcI/h6LRPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABgWAAAAAAAqEEAAIA/WAAAAGypgT9XJDN5P1a0gT/T54I/qE1+P5hDgj9oaYM/STOCP6n5 + gD+78X0/Ha5/P0wagj8nCIE/tNKBP0mkgT98XH8/ca+CP2bRgz+8eYQ/rWmAPyblgD8foXo/n5eAP2+4gD+20n8/bdh4Pzh/dT/RoIA/fu+CP3t6gD9xr4I/FKCDP2rq + ej+jw4M/iNeCP5xYfj9acH0/O056P72teD+GjIA/WBB5Px+hej9zf3o/n0Z4P84CgD+K/H0/1yCCPz3lfz/ORIA/BXODP2xcgD86T4E/tAGCP2olgT+1NX4/+quCP3Gv + gj+AAIE/jS6AP/oqej80WIE/klh5P85tfD/zmX8/6Fl3P0fjez+JDHc/ixN0P963cj+vJXA/+y1xPy66aD/OQGU/U59gP3RbaD/8JmQ/GIZpPyf+ZD/7SGk/zNdrPwYw + cz9B9m0/JKhwP7umcz9qTH0/6+KBPwBwfD9QRYQ/A4AAAGBYAAAAAACoQQAAgD9YAAAAJTyBP1evLYA/AnSDPxICfz9Q93c/90uDP3BrgD+Fin8/2Xl/P/6YgD/QnH8/ + iNl7P3kkgT+ysHs/ISB4P9CUhT8yA4A/hg17P/xgfT8SgYE/sPeAP62wfD+5AX4/9VF8PxdteT/+lXw/nU2BP8XGgD9dGXo/v0d6P6m2ej/CHYA/nluDP5oHfj+L6oE/ + RfODP4fzfT+Y8Hw/3wN7PzfIfj/OCoA/FvaAPz1Sfz/eVYA/6Bh/PyL8ez9W7IE/AiJ7P0pNfj+P+3w/Ox2CP7eRgj/MNoE/ThyBP7R6gD+m23g/PDl8PxPBfz/Q/3o/ + XXCBP7bFeT/BsYM/SJh+PxPxeT8+9IE/kwGAP1HlfT+JRnM/MLpoPyJ+Yz8tnGA/4CJTP1EEQT+84y8/dxExPzVDOj+FADM/HjMsP9tTOT/Rry4/8vI2Px+6MD/FTiw/ + zLc8P7gPQz+xak8/yZBPP4zuUz8BAAAAWAAAAAAAqEEDgAAAYN0AAAAAAAAAAACAP90AAAAAAAAA/wAAAADcAACwQQOAAABg0AAAAAAAAAAAAIA/0AAAAAAAAAD/AAAA + AM8AALhBA4AAAGDEAAAAAAAAAAAAgD/EAAAAAAAAAP8AAAAAwwAAwEEDgAAAYLsAAAAAAAAAAACAP7sAAAAAAAAA/wAAAAC6AADIQQOAAABgswAAAAAAAAAAAIA/swAA + AAAAAACBAABAwAEAAAAAAAAAAP8AAAAArgAA0EEDgAAAYMsAAAAAAAAAAACAP8sAAAAAAAAAAwAAAMAAAEDAAAAAAAAAAAD/AAAAAMYAANhBA4AAAGDRAAAAAAAAAAAA + gD/RAAAAAAAAAAIAAEBAAAAAAAAAAAD/AAAAAM0AAOBBA4AAAGDIAAAAAAAAAAAAgD/IAAAAAAAAAP8AAAAAxwAA6EEDgAAAYLsAAAAAAAAAAACAP7sAAAAAAAAABAAA + AMAAAAAAAAAAwAAAAAAAAAAA/wAAAAC1AADwQQOAAABgqgAAAAAAAAAAAIA/qgAAAAAAAACBAAAAwAEAAAAAAAAAAP8AAAAApQAA+EEDgAAAYKIAAAAAAAAAAACAP6IA + AAAAAAAAAgAAQMAAAAAAAAAAAP8AAAAAngAAAEIDgAAAYJwAAAAAAAAAAACAP5wAAAAAAAAAAwAAgMAAAAAAAADAPwAAwD8BAAAAAAAAAACGAACAwAEAAAAAAAAAAP8A + AAAAjAAABEIDgAAAYJcAAAAAAAAAAACAP5cAAAAAAAAAAgAAgMAAAAAAAAAAAIkAAEDAAQAAAAAAAAAA/wAAAACHAAAIQgOAAABgpgAAAAAAAAAAAIA/pgAAAAAAAACI + AABAwAEAAAAAAAAAAIEAAEDAAQAAAAAAAAAA/wAAAACWAAAMQgOAAABguwAAAAAAAAAAAIA/uwAAAAAAAACBZmaGPwQAAAAAAABAQAAAoMAAAAAAAAAAAAQAAIA/AAAA + wAAAQEAAAAAAAAAAAIEAAADAgQAAAAACAADAwAAAAAAAAAAAjwAAEMEBAAAAAAAAAAD/AAAAAJUAABBCA4AAAGDhAAAAAAAAAAAAgD/hAAAAAAAAAAMzMwPAAACAvwAA + AAAAAAAAhwAAAMCBAAAAAP8AAAAA0gAAFEIDgAAAYOcAAAAAAAAAAACAP+cAAAABAADAA///n8D//3/AAACAPwAAgD+H//+fwAIBAADAAACAPwAAgD+F/v8/wAIqM0NA + ZmaGP2Zmhj+E/v8/wAEAAAAAAAAAAP8AAAAAxwAAGEIDgAAAYMsAAAAAAAAAAACAP8sAAAAAAADAgQAAAAD/AAAAAMgAABxCA4AAAGDBAAAAAAAAAAAAgD/BAAAAAAAA + QAEAAAAAAAAAAAIAAABAAAAAAAAAAAD/AAAAALsAACBCA4AAAGCpAAAAAAAAAAAAgD+pAAAAAAAAwAIAAABAAAAAAAAAAAD/AAAAAKUAACRCA4AAAGCpAAAAAAAAAAAA + gD+pAAAAAAAAAAJmZoa/AAAAAAAAAAD/AAAAAKUAAChCA4AAAGCpAAAAAAAAAAAAgD+pAAAAAAAAAIMAAEBAAwAAAAAAAIDAAAAAAAAAAAACAABAQAAAAAAAAAAA/wAA + AACdAAAsQgOAAABgngAAAAAAAAAAAIA/ngAAAAAAAACNAACAwAEAAAAAAAAAAP8AAAAAjQAAMEIDgAAAYIEAAAAAAAAAAACAP4EAAAABAADAgQAAgD+CAACAv4EAAIA/ + gv//f8ACAACAvwAAgD8AAIA/A///f8AAAIA/AAAAAAAAAACDAAAAwAEAAAAAAAAAAIQAAADAAQAAAAAAAAAA4gAANEIDgAAAYIEAAAAAAAAAAACAP4EAAAAAAADAAQAA + AAAAAAAA/gAAOEIDgAAAYIEAAAAAAAAAAACAP4EAAAAAAIC/gQAAAACDAAAAwAEAAAAAAAAAAPgAADxCA4AAAGBuAAAAAAAAAAAAgD9uAAAAAAAAQAEAAAAAAAAAAOsA + AEBCA4AAAGBuAAAAAAAAAAAAgD9uAAAAAACAvwIAAEDAAAAAAAAAAACCAACAwAEAAAAAAAAAAOUAAERCA4AAAGBuAAAAAAAAAAAAgD9uAAAAAACAwAEAAABAAAAAQAQA + AIA/AAAAAAAAoMAAAAAAAAAAAIYAAEDAAQAAAAAAAAAA3QAASEIDgAAAYG0AAAAAAAAAAACAP20AAAAAAAAAAQAAAEAAAABAgQAAgL+BAAAAAOYAAExCA4AAAGBhAAAA + AAAAAAAAgD9hAAAAAACAPwEAAEBAAABAQAEAAAAAAAAAAAIAAEDAAAAAAAAAAACFAAAAwIEAAAAA0QAAUEIDgAAAYGEAAAAAAAAAAACAP2EAAAAAAAAAgwAAQMABAAAA + AAAAAACGMzMDwAEAAAAAAAAAANEAAFRCA4AAAGBbAAAAAAAAAAAAgD9bAAAAAAAAwIEAAAAAAgAAQMAAAAAAAAAAANUAAFhCA4AAAGBSAAAAAAAAAAAAgD9SAAAAAAAA + AIMAAEDAAQAAAAAAAAAAywAAXEIDgAAAYFEAAAAAAAAAAACAP1EAAAAAAAAAigAAAMABAAAAAAAAAADDAABgQgOAAABgRwAAAAAAAAAAAIA/RwAAAAAAgMABAAAAQAAA + AEACAAAAwAAAAEAAAABAAQAAAAAAAAAABgAAQEAAAAAAAACAwAAAAEAAAEDAAAAAAAAAAAC4AABkQgOAAABgRwAAAAAAAAAAAIA/RwAAAAAAAACCAABAwAEAAAAAAAAA + AAIAAEDAAAAAAAAAAAC+AABoQgOAAABgRAAAAAAAAAAAAIA/RAAAAAAAAACCAACgwAEAAAAAAAAAAL4AAGxCA4AAAGA8AAAAAAAAAAAAgD88AAAAAACAvwEAAAAAAAAA + AIgAAADAAQAAAAAAAAAArgAAcEIDgAAAYDwAAAAAAAAAAACAPzwAAAAAAMA/ATnkQkA55EJAggAAAAC2AAB0QgOAAABgOQAAAAAAAAAAAIA/OQAAAAAAAACBAABAwIEA + AAAAtAAAeEIDgAAAYDYAAAAAAAAAAACAPzYAAAAAAADABAAAAAAAAIC/AABAwAAAAAAAAAAAggAAAMABAAAAAAAAAACrAAB8QgOAAABgLwAAAAAAAAAAAIA/LwAAAAAA + gD8FAAAAAAAAQMAAAAAAAACgwAAAAAAAAAAAhAAAgL8BAAAAAAAAAAChAACAQgOAAABgLwAAAAAAAAAAAIA/LwAAAAAAAACuAACCQgOAAABgLQAAAAAAAAAAAIA/LQAA + AAAAQMACAAAAAAAAAEAAAABAAgAAAMAAAAAAAAAAAKYAAIRCA4AAAGArAAAAAAAAAAAAgD8rAAAAAAAAwIEAAAAAAgAAAMAAAAAAAAAAAKUAAIZCA4AAAGAmAAAAAAAA + AAAAgD8mAAAAAAAAAIEAAEDAAQAAAAAAAAAAAgAAgD8AAAAAAAAAAJ4AAIhCA4AAAGAmAAAAAAAAAAAAgD8mAAAA6iaEv4UAAAAAAgAA4MAAAAAAAAAAAJwAAIpCA4AA + AGAkAAAAAAAAAAAAgD8kAAAAAAAAAIMAAADAAQAAAAAAAAAAnQAAjEIDgAAAYCIAAAAAAAAAAACAPyIAAAAAAAAAAgAAIMAAAAAAAAAAAJ4AAI5CA4AAAGAfAAAAAAAA + AAAAgD8fAAAAuD0AQIIAAAAAmwAAkEIDgAAAYB4AAAAAAAAAAACAPx4AAAAAAAAAnQAAkkIDgAAAYBwAAAAAAAAAAACAPxwAAAAAAIA/BAAAAMAAAAAAAABAwAAAAAAA + AAAAgQAAgL8BAAAAAAAAAACSAACUQgOAAABgGwAAAAAAAAAAAIA/GwAAAAAAAECCAACAvwIAAEDAAAAAAAAAAACUAACWQgOAAABgGQAAAAAAAAAAAIA/GQAAAAAAAAAI + AAAAQAAAgEAAAABAAAAAAAAAAMAAAAAAAAAAwAAAAAAAAAAAjwAAmEIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAAAAgwAAAMABAAAAAAAAAACRAACaQgOAAABgFgAAAAAA + AAAAAIA/FgAAAAAAgECBAAAAQAEAAAAAAAAAAJEAAJxCA4AAAGAVAAAAAAAAAAAAgD8VAAAAAACAPwMAAEBAAACAvwAAAAAAAAAAkAAAnkIDgAAAYBQAAAAAAAAAAACA + PxQAAAAAAAAAAwAAAMAAAKDAAAAAAAAAAACCAABAwAEAAAAAAAAAAIoAAKBCA4AAAGATAAAAAAAAAAAAgD8TAAAAAAAAAJIAAKJCA4AAAGASAAAAAAAAAAAAgD8SAAAA + AACAP4EAAAAAAgAAAMAAAAAAAAAAAIwAAKRCA4AAAGARAAAAAAAAAAAAgD8RAAAAAAAAwAEAAAAAAAAAAI4AAKZCA4AAAGAQAAAAAAAAAAAAgD8QAAAAAACAvwMAAIA/ + AACAvwAAAAAAAAAAiwAAqEIDgAAAYA8AAAAAAAAAAACAPw8AAAAAAIA/AQAAAAAAAAAAjAAAqkIDgAAAYA4AAAAAAAAAAACAPw4AAAAAAAAAjQAArEIDgAAAYA0AAAAA + AAAAAACAPw0AAAAAAAAAjAAArkIDgAAAYAwAAAAAAAAAAACAPwwAAAAAAAAAiwAAsEIDgAAAYAwAAAAAAAAAAACAPwwAAAAAAAAAiwAAskIDgAAAYAsAAAAAAAAAAACA + PwsAAAAAAAAAigAAtEIDgAAAYAoAAAAAAAAAAACAPwoAAAAAAAAAiQAAtkIDgAAAYAoAAAAAAAAAAACAPwoAAAAAAAAAiQAAuEIDgAAAYAkAAAAAAAAAAACAPwkAAAAA + AAAAgQAAQMABAAAAAAAAAACEAAC6QgOAAABgCQAAAAAAAAAAAIA/CQAAAAAAAAACAACgwAAAAAAAAAAAhQAAvEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAABAAgAAgMAA + AAAAAAAAAIQAAL5CA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMBCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMJCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAA + AIcAAMRCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMZCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMhCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMpC + A4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMxCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAM5CA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANBCA4AAAGAI + AAAAAAAAAAAAgD8IAAAAAAAAAIcAANJCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANRCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANZCA4AAAGAEAAAAAAAA + AAAAgD8EAAAAAAAAAIMAANhCA4AAAGAEAAAAAAAAAAAAgD8EAAAAAAAAAIOMkO49A4AAAEAVAAAAAACoQQAA0EEAANhBAAAIQgAADEIAABRCAAAcQgAAIEIAADBCAAA8 + QgAATEIAAFhCAABsQgAAfEIAAIZCAACOQgAAlkIAAKJCAACqQgAAvkIAANZCFQAAAMApez8EUdyDP0oxhT9uRoE/1VqJP9VaiT8BMwGGPzMBhj8B1VqJP9VaiT+CSjGF + PwhR3IM//5N9P7MvdD9uRoE/sy90P5xVZz91TVE/8MlHPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEA + AAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAAAIEEAAAAADgAAAPlROD/kRTw98qUbPvzSzT4CAAAAAAAAAAOAAABIAQAAAAAAgD8A + AAAAA4AAAEgBAAAAAACAPwQAAAACAAAACAAAAAsAAAABAAAAAQAAAAAAgD8AAIA/AQAAAAEAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEA + AAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAwAAAAcAAAABAAAAAAAAAA4AAAAQAAAAAAAAAM3MzD0AAIA/AAAAAFBmZj7876FB + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAAQAAAAAAAAAOAAAAEAAAAAAAAAAAAAJDAACAPwAAgD8AAABAANAERgAAoEDNzEw+ + AACAPwCAO0UAAABAmpkZPwAAQEAAAAAAAAAAAAAAAAADAAAACQAAAAEAAAAAAAAADgAAABAAAAAAAAAAtUlMPgAACEAEAKBAIACAwKkFXUC7S8lCAACgQAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAIA/AACgwAAAAAADAAAAAwAAAAAAAAABAAAABQAAAAQAAAABAAAAAwAAAPYoHECtR+E/AADAPwAAYcMAAAAA + BAAAAAUAAAAAAEBAAABwwgAAwMIAAMDCAADAwgUAAAAXt9E4Gy/dPAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAA + AAAgQMrMTD8AAMA/AAA0QgAAAAAEAAAABQAAAAAAcMIAAEBAAADAwgAAwMIAAMDCBQAAABsv3TwAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AQAAAAAAAAAEAAAAAQAAAAMAAADQUbg+HYWrP1yPgj84XeNCX/SHQwUAAAAFAAAAAABAwQAAwMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAB+F6z5SuJ4/XI+CPwAA4kIAALLCBQAAAAUAAAAAAMDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAA + UkmdOQAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAuB7FPz0KVz+uR+E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDB + AABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAADAAAAzcxMP4/Cdb4AAMA/ + AACQQQAAAAAAAAAAAAAAAAEAAAAAAAAAEThWOJMxAABQclZLJgAAAlhGVFCDMQAACgAAADIwMjMvMDYvMDYXAAAAUGlhbm90ZXEgOC4wLjAvMjAyMjExMTclCG+SxwF+ + yP5ahRluLjDgZGxsYUIxAAB0ZHRNugUAABAAAABHcm90cmlhbiBQcmVsdWRlAAAAAAcAAABNb2RhcnR0kwAAAEEgcHJlc2V0IHRoYXQgbGV0cyB5b3UgZGlzY292ZXIg + YSBjb2xvcmZ1bCB2YXJpYW50IG9mIHRoZSBHcm90cmlhbiBDb25jZXJ0IFJveWFsIEdyYW5kLiBUaGlzIHByZXNldCBpcyBvcmllbnRlZCB0b3dhcmQgUG9wL0phenov + Qmx1ZXMvTW9kZXJuIG11c2ljLgAAAABEAQAAEAAAAEdyb3RyaWFuIFByZWx1ZGUAAAAA3AQAAEdSVVUAAAAGAAAAAQPATVRyawAABMYA/wIiUmljaGFyZHMgLSBOb3cg + YW5kIHRoZW4gLSBSaWNoYXJkcwCwQH+EKJA1Imw1AIMEJS2DDDglg0Q9LIM6QScMSSWBXkkAVkEAgXI9AIE0OABoSU4EQUAIJQCDIEkAFkEAgxBJRgBBO4NGJz4GsEBn + FkAwFkAAgQSQSQAIsEAYFkBAGEBsFkB/gQKQSUgUQleBKkEAgUgnABQzMhRCAAhJAIEkMwCBPEJTBElQgwA2N4EqSQCCEkIADjo3hD46AIJIOi9EOgAENgCDaCoycrBA + YBZAJhZAAIEKQBoYQEoWQH8kkDYyghIqAIFASS8EQjFoQgBASQCBbkJmAklegxJCAAg6QBBJAIF6NgBWSWsCQnOCYjoATEkAIEIACDZCggY2AFRCdQJJbINWLE0IsEB0 + FkAyGEAAcJBCABqwQAQSkEkABLBAOBZAf4EUkElBFkRUgwRJAAgzOR5EAIJySVsKRFuCIEQAFkkAcDg2gSYsAIF2SVAGQlt0OACBMkkAJkIAgQQzACQ4M2Q4AIF2SUQa + QTaDTiU9NrBAdhZAPBhAAIEiQCQWQFYWQH9ekDg4gy5JABI9QTRBAIJCSVQWQUOBbkkAGEEAg2Y9AB5JcwhBW4FyJQCBEkkALj1KGEEAWD0AEDgAgiBJZxJBWYJosEBk + FEAgGEAAHJAnS4FKsEAKCJBJABCwQH0WQH+BFJBJUxJCXoEcQQCBDLBAahZAKhZABBhAACqQM0KBPLBAZBZAfyKQQgAgSQB4sEBuFkBQGEAuBpBJQwpCRQiwQBYIkCcA + JrBAOhZAZRhAf4MokDMAJDYph3I2AFxJAAJCAIIQOi5COgCDBCo0PLBAYhZAEhZAAIF+QCQWQHgYQH8WkDYbgTAqAIFkQjUESTaBIkIAGkkAgX5CaQpJZYIqQgAeSQBk + OkOBZDYAbklnBEJvgh5JAEZCABQ6AFA2OIEONgCCBElUBkJXgmywQG4YQCYWQABEkCw9gRxCAEywQEgQkEkACLBAf4E4kEkpAkQwghxEAARJAIEQMzKDIkk8CkQ8gyg4 + KBYsAIEsMwCBWDgAPkImfkkAPEQAiUglNgqwQHYWQDoYQAwWQACBIkAUFkAuGEBSFkB/fJA4L4IqQgCBHD04gzJJYRBBVINuSQCBTkEAgQBJYQhBYYEaPQCCAiUAQkkA + PD0aIEEAEjgALj0AgVxJPhhBL4FWSQBYQQCBCLBAbBZAIhKQPToEMzoEsEAADJA6RIMYSUEMQjiBIkkAEkIAhUpJKwZCOIIusEBWFkB/hg6QPQCCUDMAUjoAhmAqLxRJ + AEhCAIJkPSuDZEIoBkknKLBAeBZAQhhAEBZAABSQQgAQSQCDGElKBkJJgS6wQCoWQH+EDJBJABxCAIJASTICQjNEKgBsPQBKQgAYSQCFZrBAXQSQQi4GSSkMsEAuDJA4 + Lwg/MgSwQAgWQACDNkAOFkA6GEBkFkB/gnaQSQBqQgCBJEksHEQ6gVpEAFhJAIZySSEARDeMUEkAFkIogXJEABY/AIIIOACCZkIAkQYxH244JCCwQHsYQEwWQA4SkEEo + BrBAAEyQRDgAPRwQsEAGFkA2GEBlGEB/LJBBAAxEAC5JREJJALoAPQAUMQAsOAAA/y8AUHJzVXgrAABHAAAACAAAAGdyb3RyaWFuAAAAAA4AAABHcmFuZCBHcm90cmlh + bgAAAAAAAAAAcHJvbWAAAAABAAAAAQAAAAEAAAAIAAAAZ3JvdHJpYW4AAAAADgAAAEdyYW5kIEdyb3RyaWFuAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG//// + /wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEA + AAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAYFgAAAAA + AKhBAACAP1gAAABgT+4+Vzg9CD8ArE+9eLAJPwDkhz0AuZ8/AFyPvuBzqz7g85e+2MFiPzDlKj8AGRS/wC/ovjAqVL9Q1TY/0IyKP5hXYj8Atmg/2IV5P+CE9b4A/vY9 + AHGUPtT92T9ACKy+4HIIP5yxkD8IFmc/AOj7PMDkVD4ACKQ9IO/0PgCx0T3AZzbA8KcePwAiir4s/JE/+ARMPxZgXkBAsRW/eAQKQLyb/T9Q1iw/SK5WP/yeIUDwvK2/ + 7I8AQABuJD3Q5VK/eP2MP8Cwrj9NDo9AUORdP3JxNkDqTx5ArmWgQGhs4T8g2l4/jLykv1R/rD/wA5G/oLaUvwBgRzxggJi/MFUcwKj9pL8gAdy/gHC/vkBHBT+A9We+ + 4FrzvwBle76AXPG+AGk5v6BNuL4QxsG/CJ7kv0g07b8Mk+y/AMz6v7gdC8CUM2vAmD6DwJBqBsBgPBXAyAr6v2i3G8BW+zvAuMgowAMAAAABAAAAAAAAAAAAAAADAAAA + AAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAABmZqY/AAAAAAEAAAAAAAAAVRrMPQOAAABgWAAAAAAAqEEAAIA/WAAAAATenj9XtPGeP//1 + nz+GvaA/ZbKfPyKBnT/jdaA/Y0yfP/GNnT+jKKA/V12fP4BPnj+Qsp0/igSeP+qDoD/vzp8/wKagPwTTnj9Yyp8/ftCMP5Pvjz8nSJA/VoKQP7PtkT88oJI/IG6TP+uS + lD/FwJQ/VPSVP8BklT8ITpU/YwWYP3YTmj9p+pk/m1qbP1eslz9HBZc/0NyXP6hMlz/LHJU/mJmVP4eYkj/gaZI/nmKTP1Cgkz84748/5J+SP3YQkD8zFZA/daCNPxJD + jj9sSY4/ZPaNPzRZiz8YI4s/vDuJPwkshj9HgoY/2dGFP+KIgj85e4E/qiODP3Zffz8qz30/SRp0P3Yfbj/ycWk/UyFlP5hyWD/atlQ/GU9KP0+BQz/Iqjs/9Z06P5G8 + MT+uuSc/Xo0mP6wSID/XLx8/eIUcP3a5Ez+7ERc/luwLPwXvDj/txQk/kG0CPzQvAj/O/QA/AQAAAAAAAAAAAAAAAACgwAAAoEAAALZCAAAAAAOAAABQAABwQgEAAAAA + AAAAAAAAAAAAQMAAAAAAAQAAAAEAAAAAAAAAAADAwAAAQEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAA + AAAA0gQAAAAAAAAAAAAAAAAAgAAAAD9mZmY/A4AAAFAAAHBCAQAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAACHWYW/VxDHbr1A0gTAF+WLv4mOYz+Vv14/ruPsPzFv + Lb5urkK+FevkP7cnRD8uRz4/quajv+6FE8ANZis/DqokPyzZzj+APss/JQgPPxhc8b5PpwC/onLEv2hpEb+EGs2/nqTRv0iypj4YY5M+5N+fPwLCmj+QgOq/txiQP7fi + ar95SRw9enUAwB9Rcj+kPWY/Gi8YvtEXSr6YrHy+Rf0SwIJurL+gt8u+DdTlvgAAAD/6FQ2/DIkmwH6R07/rCjS/NjVwwAa95r8d+uy/0h7zv0iUPMBe9Oo7kziBwFJH + RcBbDAjAuL8KwBQQVr7594fAY2xSwEnWFMDZlovAX7mMwDEt3b6vxl3AFuuPwD/qkMAThMe/QJ8lwJZsZ8AHVdK/g9kqwCP02L+rDC7AlyPfv7cnRL+LdXLAGdUzwA0p + dcBcx1m/lb9ev6LjeMB1DXrAwC17wOREPMA9U33AI1l+wAOAAABgWAAAAAAAqEEAAIA/WAAAALqffD9XAct9P2rZiz/kSIg/ft50P1hvgD9VTYA/1UCCP+9RbD9l0HY/ + XE+MP9v6bz+Qx4w/YF9tP86paj+YHGg/o0mEP1dpfD8Ul4s/XkiBP6CwbT9WJ3M/80OMP4uzij9Ownc/+rB1P1RmgT+Jdn8/Uk9+PyrPgj+KxIo/ikVqPxJpZj/EAG0/ + D0iDP8BxfD8WUWc/2jpyP2idbD8MPoU/dNRrP7IEaj/xKoI/kLxzP6vwez9wr4Y/Co9yP0QsaD9nHYw/VH6KPxhTgT/auHw/ldSDP2/jcD9xDXY/uL6GP/emaD9ZfoU/ + PLyJPzz7hT+3zoM/nPN7PwAFeD/a2nk/20J5P1jubT8UUIU/3zaKPyFxhz87PYg/Lut5P/J6gz/deHA/rqmGP+0wbD8ojIg/cH9nPyCLhj+hxX4/E6R5P19IgT9O/nA/ + C3eEP0CLfD+++H8/T/aLP1Bkez9WG4c/AgAAAAEAAABVAAAARXF1YWwgdGVtcGVyYW1lbnQKVGhlIHN0YW5kYXJkIHR1bmluZywgd2hlcmUgdGhlIG9jdGF2ZSBpcyBk + aXZpZGVkIGluIDEyIGVxdWFsIHN0ZXBzLgwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQBAAAAAAAAAAQAAAAAANxDPAAA + AEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAAAAAAAAIAAAADgAAAQAQAAAAAAMBBAAAQQgAAQEIAANhCBAAAAAAAAMACAACAvwAAAAAAAAAAAAAAAAAAAAAA + AIA/AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAA + UAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAA + AAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACo + QQAArkICAAAAAACAP4EAACBCA4AAAEAPAAAAAACoQQAA0EEAAPhBAAAQQgAAQEIAAHBCAACQQgAAqEIAALRCAADAQgAAyEIAANBCAADUQgAA1kIAANhCDwAAAAAAgMAE + AABAwAAAAMAAAIC/AAAAAAAAAACBAAAAQAcAAIBAAADAQAAAIEEAAIBBAACgQQAAyEEAAABCAACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAAAgAgAA + AAAAekMAgDtEAwAAAAIAAAACAAAAAAAAwAEAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAE + AAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAzcxMPgAAAAAAAAAAAAAAAAAAAD+uRzFAA4AAAGACAAAA + AACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAAAAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAABAAAACgAAAFNtYWxsIEhhbGwLAAAAbWVkaXVtaGFsbDEAADBB + AACQQQAAgD/NzEw/AAAAAArXIz0AAAAAzcxMPgAAAAAzMzO/AACqQwAAgD8AAIA/AQAAAAYAAACamZk/mpmZPwOAAABgWAAAAAAAqEEAAIA/WAAAAOdygT9XiA98P47W + dT+edGo/E0x9P/dqfT+xrYU/CgSCPww3Zz+nj4Y/+vRyP/eGij/zrX0/Zcx2P8N2bj++4Yo/N3qFP9gQaD+RroQ/g0+HP+UOdj8iUIo/buOGP4eDiD/2QYc/8tdtP+KF + iz+BlHI/OVt7PyyKiz8lrIg/8CVlP8RLiz+laG8/NipwP58wTz/wJWU/3GWEPz8xZz8aboU/bNRpP8cmgT8XdHQ/2HRxPzovWj9Yhmw/pUxyP2jHZD/GP3U/rJ16Pyaa + dz/9kIA/Xvp3P9/hhT+qb20/YdlgP7S2bT/W6mE/irN5P/hWgT+FFHA/+HNiPx0Shz+QwGs/m+eHP2D5hT9pwXE/UtRwP7TKgj/rzYc/bt2HPz6iiD/Iums/HlaBPxEV + fT9OcGo/OBdtP5ZFdD+kvn8/szuKP3gtez/vjYw/yDZ1P6U0Zz8Zwn4/1GqJP5Txaz9QAG4/A4AAAGBYAAAAAACoQQAAgD9YAAAAqd96P1f3knA/mxODP2sZgj9HxIQ/ + g+ZzP1EugD8vjIQ/tsuGP4RTcz9rcn4/Tcx9P7GUeT8+XoI/i7qAPyv3fj8SQ4Q/p02BPynRdj+0j30/6AR7P5Vyhz8X5oU/ZKR+P7PHdj8zjYM/a4CBP2S7ez8jw4E/ + 1KByP5uBfz+dsoE/hcl2PxdPiD99UIA/00aBP6MXgT+fqoE/tFR/PyTtgj+4cHw/KROCP73BgD+zYnI/l26eP/RfdD85enc/pVN1P3mLhj8hqoE/Zbt/P9MWgj/GSIM/ + sBWDP3Ekfz9vR3g/UbeWP6pyhT8sSnY/J1NvP/Aqbz9Rt5Y/cT+IP3GRgj+h8nw/jfuBP8ExfD9bzn4/OVN9P0VJhD8VjXw/G8qBP2WWdz83b3o/WjuFP0vnez8oOno/ + DeWDP4jRfD8zfIU/6Tx6P1yNgz+7Yns/h1d/P3OLgz8zzIE/FbyDPzMOiD8AAAAAA4AAAGBYAAAAAACoQQAAgD9YAAAAlemKvjflLuo9nOKAvl0QHL4nWFU+5qNVvmzr + hz5iHZK+S7qUPp8smb4itEE+llw+vqKh570iwXY9BcFxPsP9pD3M/2a+jDtWvqmHc74+Kw6+VUGuva5+eD3cvwc+bTd2PriMQr4rFKs9BYWEve75Jr6kbEQ9DnV8vhJd + 471G7pW+1E8rvkZQwL1yQ4++fteou12g6D0IpI09ZkLou1znX71wDiC9vudzPjfstr1+irO9OVcSPof8FD6+umq+yV02vpFQlD4sRbm88RbjPEI3XD7JR2i+MFt2vXzY + lj6wqiq9sKoqvR+oUt47UZyVPrJrVz0HR9M+Hw64PkXsyD6w/44+S9YwvXeDzT6G6ZQ+q3SfPqmrrz1FlPQ+oFV7PfNppT52PuA+iQLGPghAoz04x+s+YgFXPRNk4z6n + IMQ+0dNsvYylxz71FkQ+UsUcPvu9Tj7pR+C8wW9/Pk29Jb1wsdk+AQAAAAAAAAABAAAAAAAAPwAAAEADAAAAAACAPwAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhC + AwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAAgD8HAAAAzcxMP4/CdT6F61E/AADA + P83MTD4IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAACCTp8/V2x8mz/4uaE/SOeUPwGtlD+CqHw/IRCOP8zkgD85 + S4c/kcVhP39nez+bLnY/UTqEP8J9jD/v62s/3bNsPwr7Zz+0GWE/T51gPxcdhD+jFoA/EGp8PySVVj/f84Q/oAdtP8Q4bj80O2Y/m6t+P0E8bz+qwoA/LklrP9P9hT/p + Pqk/AiiBPyy/jj+eUH4/TdqYP1DLOD/9ops/XQp8P6Occj+efIM/iXR9P7wRdD+WjIc/lWtqP/BChz8SNHw/2s1yP9n0eT80rTo/LDOBP5I1Yj8xWWc/as4vP2/FfD8I + AYo/K1miP4TFcT+ucpM/rUqXP3B4kD/msZU/y/efP2kFkz96bKU/oKKHP8V9fj/X7Ik/BRyjP7lljD/H9pA/Bf2SPzgrjT8zlZo/JR+eP6Ognj+n15w/roGcP+9NnT8/ + srI/auq5PwrCmT8RQZw/PgOXPx+Wmj+pBKE/M/SaPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABg + WAAAAAAAqEEAAIA/WAAAAIexfz9XqNh+Pwkafj9Yf34/mkuAP49QgD/Xy38/VIB/P2fsfz8v334/TJx/P/TPfj8zEX4/FTp+PwRtfj857H8/kRiAP8Xhfz/FYn8/CSp/ + PwRMfj9HSn4/pBt+P+hXfj/p3X4/9Ql/P72Jfj+bh30/UH19PwijfD/V63w/J9d9Pz9Mfj9X6H0/bPh8P0XjfD/ppnw/gDZ9P9lvfT930X0/g4R9P91rfD9wjXs/tEt8 + P+v/fD+J9Ho/2Nt6P7/Oez8/fHw/HCJ8P1wsez/7rHs/DHJ8P58jfT8vQHw/3Dx8Px3Uez/8NHw/fZZ8P/S0fD9g7Xw/AMt8P7s+fT/0IX0/JUx8Pzw7fD98t3w/dAt9 + PxNkfj9ng34/PWR/P8Hafj8HO34/cIp9Py9Ffj8fB34/TW99Pw8Ufz9bgn8/NAWAP2hTfz/XEIA/VdF/P6Oqfj+sdX4/969/P4e+fz/kk4A/AQAAAFgAAAAAAKhBA4AA + AGCyAAAAAAAAAAAAgD+yAAAAAAAAAP8AAAAAsQAAsEEDgAAAYLIAAAAAAAAAAACAP7IAAAAAAAAA/wAAAACxAAC4QQOAAABgsgAAAAAAAAAAAIA/sgAAAAAAAAD/AAAA + ALEAAMBBA4AAAGDTAAAAAAAAAAAAgD/TAAAAAAAAAP8AAAAA0gAAyEEDgAAAYMkAAAAAAAAAAACAP8kAAAAAAAAAgQAAQMABAAAAAAAAAAD/AAAAAMQAANBBA4AAAGDH + AAAAAAAAAAAAgD/HAAAAAAAAAIEAACDAAQAAAAAAAAAA/wAAAADCAADYQQOAAABgxAAAAAAAAAAAAIA/xAAAAAAAAAAEAABAwAAAQEAAAEDAAAAAAAAAAAD/AAAAAL4A + AOBBA4AAAGDjAAAAAAAAAAAAgD/jAAAAAAAAAAQAAADAMO9BvQAAAABmZoY/ZmaGP4EAAAAA/wAAAADbAADoQQOAAABgxgAAAAAAAAAAAIA/xgAAAAAAAAAEtqxGwKiB + m0C2rEbAAAAAAAAAAAC4AAAAwQEAAAAAAAAAAP8AAAAAhQAA8EEDgAAAYMUAAAAAAAAAAACAP8UAAAAAAAAAAwAAwMAAAEBAAAAAAAAAAAD/AAAAAMAAAPhBA4AAAGDA + AAAAAAAAAAAAgD/AAAAAAAAAAAQAAADAAABAQAAAgL8AAAAAAAAAAP8AAAAAugAAAEIDgAAAYLQAAAAAAAAAAACAP7QAAAAAAAAAAgAAwMAAAAAAAAAAAP8AAAAAsAAA + BEIDgAAAYLAAAAAAAAAAAACAP7AAAAAAAAAAAQAAAMAAAADAAQAAQEAAAEBAAQAAAAAAAAAA/wAAAACpAAAIQgOAAABgpwAAAAAAAAAAAIA/pwAAAAAAgD+EAAAAAP8A + AAAAoQAADEIDgAAAYLkAAAAAAAAAAACAP7kAAABmZmZAA83MLEAAAGBAMzODQDMzg0ABAAAgQAAAIECEmpkZQIHNzAxAA2ZmBkAzM/M/ZmbmP2Zm5j8FmpnZPwAAwD8z + M7M/ZmamP5qZmT+amZk/gc3MjD8CAACAP2ZmZj9mZmY/gc3MTD8DMzMzP5qZGT8AAAA/AAAAP4HNzMw+AZqZmT6amZk+gs3MTD4BAAAAAAAAAAD/AAAAAI0AABBCA4AA + AGCuAAAAAAAAAAAAgD+uAAAAMzMDwAMAAAAAAACAvwAAgD8AAIA/AQAAAAAAAAAAhgAAQMABAAAAAAAAAACsAAAAwQEAAAAAAAAAAO8AABRCA4AAAGCXAAAAAAAAAAAA + gD+XAAAAAAAAAAIzM0PAAAAAAAAAAAD/AAAAAJMAABhCA4AAAGCMAAAAAAAAAAAAgD+MAAAAmpn5vwEAAAAAAAAAAP8AAAAAiQAAHEIDgAAAYJYAAAAAAAAAAACAP5YA + AACRlX4/As3MfMD2urQ/9rq0PwGRlX4/kZV+P5n9fXk/grVmdD8L3SBlP9rxWj/8k0Y/RDYyP9LAGD88egk/bNrVPtCRjj5ATTc+IPAiPQAAAAAAAAAA5wAAIEIDgAAA + YJkAAAAAAAAAAACAP5kAAABmZoa/BQAAAAAAAEBAAZkBQAAAgL8AAAAAAAAAAIMzM0PABgAAgMAAAAAAMzNDwAAAoMAzM0PAAAAAAAAAAACBAAAAwAEAAAAAAAAAAP8A + AAAAgwAAJEIDgAAAYJYAAAAAAAAAAACAP5YAAAAAAADAAmZmhr8AAAAAAAAAAAIzM0PAAAAAAAAAAACeMzMDQIIAAAAA7QAAKEIDgAAAYI0AAAAAAAAAAACAP40AAAAA + AEDAAgEAAMAAAAAAAAAAAIIAAABAAQAAAAAAAAAAAwEAAMACAEDAAACAPwAAgD+B///fwAEAAAAAAAAAAPwAACxCA4AAAGCTAAAAAAAAAAAAgD+TAAAAAACgwAEAAIC/ + AACAvwQAAIA/AAAAQAAAQECamYlAmpmJQAMAAAAAAABAwAAAAAAAAAAAgQAAQMCBAAAAAP8AAAAAgwAAMEIDgAAAYI4AAAAAAAAAAACAP44AAAAAAADAgZqZ+T8BAAAA + AAAAAACHAAAAwIEAAAAA/wAANEIDgAAAYIsAAAAAAAAAAACAP4sAAAAAAAAA/wAAAACKAAA4QgOAAABggwAAAAAAAAAAAIA/gwAAAN9m+j+BL3p6PwHHZL0/x2S9P4HC + sLo/DBuUsj+zwqc/daWfP9mHlz+OtYw/gZeEP3cgbj9UEVM/xPIcPxTVwj5QLy0+IC6tvCAurbyBAAAAAAKamfm/AAAAAAAAAADqAAA8QgOAAABgeQAAAAAAAAAAAIA/ + eQAAAAAAAEABmpn5P5qZ+T+CAACAv4EvG7c/Cxh7qj/J2p0/hYqXP9jpij9vmYQ/X/FvP5SvVj8ALCQ/6A7KPgiLFz4AAAAAAAAAAOUAAEBCA4AAAGBoAAAAAAAAAAAA + gD9oAAAAAAAAAAEAAMA/AADAP4IAAEBABAAAAMAzM7M/ZmamP5qZmT+amZk/CM3MjD8AAIA/ZmZmP83MTD+amRk/zczMPs3MzD0AAAAAAAAAANQAAERCA4AAAGBoAAAA + AAAAAAAAgD9oAAAAMzNDwAYAAAAAAAAAQAAAAAAAAABAAAAAwAAAAAAAAAAAgwAAAEAEAACgQAAAgEAAAADAAAAAAAAAAACCAABAQAMAAMBAAABAQAAAAAAAAAAA0AAA + SEIDgAAAYGgAAAAAAAAAAACAP2gAAAAAAAAAAyRcjz8AAABAAAAAAAAAAACBJFyPPwNxzYU/fX14Pz9gZT8/YGU/CNpCUj+cJT8/OAgsP9TqGD8YYOU+1OqYPiDrmD0A + AAAAAAAAAIIAAADAAQAAAAAAAAAAzwAATEIDgAAAYGYAAAAAAAAAAACAP2YAAAAAAMDAAQAAAAAAAAAA4wAAUEIDgAAAYFQAAAAAAAAAAACAP1QAAAAAAAAABwAAgD8A + AABAAAAAAAAAAEAAAAAAAACAvwAAAAAAAAAAywAAVEIDgAAAYFQAAAAAAAAAAACAP1QAAAAAAADAApqZ+b8AAAAAAAAAANAAAFhCA4AAAGBUAAAAAAAAAAAAgD9UAAAA + AAAAAI0AAEDAAQAAAAAAAAAAwwAAXEIDgAAAYEoAAAAAAAAAAACAP0oAAAAAAIA/hAAAQMABAAAAAAAAAACGAAAAwAEAAAAAAAAAALkAAGBCA4AAAGBKAAAAAAAAAAAA + gD9KAAAAAAAAAMkAAGRCA4AAAGBIAAAAAAAAAAAAgD9IAAAAAAAAAMcAAGhCA4AAAGBEAAAAAAAAAAAAgD9EAAAAAAAAAAGambk/mpm5PwIAAADAAAAAAAAAAACHAAAA + wAEAAAAAAAAAALQAAGxCA4AAAGBAAAAAAAAAAAAAgD9AAAAAAAAAAIMAAADAAQAAAAAAAAAAggAAgD8DAAAAAAAAAMAAAAAAAAAAAIQAAEBAAwAAoEAAAEBAAAAAAAAA + AACpAABwQgOAAABgOAAAAAAAAAAAAIA/OAAAAAAAAAC3AAB0QgOAAABgOAAAAAAAAAAAAIA/OAAAAAAAAAC3AAB4QgOAAABgNgAAAAAAAAAAAIA/NgAAAAAAAECBAACA + P4EAAIC/AwAAAAAAAADAAAAAAAAAAAADAADAQAAAgEAAAABAAAAAQAEAAAAAAAAAAKcAAHxCA4AAAGAzAAAAAAAAAAAAgD8zAAAAAAAAAAJxrv8/ZmaGv2Zmhr8BAAAA + AAAAAACtAACAQgOAAABgLgAAAAAAAAAAAIA/LgAAAAAAAEAGmpnZPwAAAACamZk/zcxMPwAAAD8AAAAAAAAAAKYAAIJCA4AAAGAtAAAAAAAAAAAAgD8tAAAAAAAAAIQA + AIA/AgAAAEAAAAAAAAAAAAIAAABAAAAAAAAAAAChAACEQgOAAABgKwAAAAAAAAAAAIA/KwAAAAAAwD8BAAAAAAAAAAABAADAPwAAwD+BAAAAAKQAAIZCA4AAAGAnAAAA + AAAAAAAAgD8nAAAAAACAPwEAAAAAAAAAAIEAAIC/BAAAAMAAAEDAAAAAwAAAAAAAAAAAnQAAiEIDgAAAYCYAAAAAAAAAAACAPyYAAAAAAAAAgwAAQMABAAAAAAAAAACf + AACKQgOAAABgJAAAAAAAAAAAAIA/JAAAAAAAAAACAACAvwAAAAAAAAAAoAAAjEIDgAAAYCIAAAAAAAAAAACAPyIAAAAAAAAAAgAAAMAAAAAAAAAAAJ4AAI5CA4AAAGAg + AAAAAAAAAAAAgD8gAAAAAAAAQAIAAIC/AAAAAAAAAAADAACAvzMzA8AAAAAAAAAAAJgAAJBCA4AAAGAeAAAAAAAAAAAAgD8eAAAAAAAAAAIAAEDAAAAAAAAAAACHAAAA + wIEAAAAAkAAAkkIDgAAAYBwAAAAAAAAAAACAPxwAAAAAAIA/AwAAgL8AAADAAACAvwAAgL8DAAAAAAAAgL8AAAAAAAAAAJMAAJRCA4AAAGAbAAAAAAAAAAAAgD8bAAAA + AACAP4IzMwPAgQAAAACVAACWQgOAAABgGQAAAAAAAAAAAIA/GQAAAAAAwD8CZmbGvwAAAAAAAAAAlQAAmEIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAABAgQAAAACDAABA + QAEAAAAAAAAAAI8AAJpCA4AAAGAWAAAAAAAAAAAAgD8WAAAAAAAAAJUAAJxCA4AAAGAVAAAAAAAAAAAAgD8VAAAAAAAAAIUAAADAgQAAAACMAACeQgOAAABgFAAAAAAA + AAAAAIA/FAAAAAAAgD+BAAAAAIQAAADAAQAAAAAAAAAAigAAoEIDgAAAYBMAAAAAAAAAAACAPxMAAAAAAAAAkgAAokIDgAAAYBIAAAAAAAAAAACAPxIAAAAAAIDAAs3M + PMAAAAAAAAAAAI4AAKRCA4AAAGARAAAAAAAAAAAAgD8RAAAAAADAvwEAAAAAAAAAAI4AAKZCA4AAAGAQAAAAAAAAAAAAgD8QAAAAAAAAAI8AAKhCA4AAAGAPAAAAAAAA + AAAAgD8PAAAAAAAAAI4AAKpCA4AAAGAOAAAAAAAAAAAAgD8OAAAAAAAAAI0AAKxCA4AAAGANAAAAAAAAAAAAgD8NAAAAAAAAAIwAAK5CA4AAAGAMAAAAAAAAAAAAgD8M + AAAAAAAAAIsAALBCA4AAAGAMAAAAAAAAAAAAgD8MAAAAAAAAAIsAALJCA4AAAGALAAAAAAAAAAAAgD8LAAAAAAAAAIoAALRCA4AAAGAKAAAAAAAAAAAAgD8KAAAAAAAA + AIkAALZCA4AAAGAKAAAAAAAAAAAAgD8KAAAAAAAAAIkAALhCA4AAAGAJAAAAAAAAAAAAgD8JAAAAAAAAAIgAALpCA4AAAGAJAAAAAAAAAAAAgD8JAAAAAAAAAIgAALxC + A4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAL5CA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMBCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMJCA4AAAGAI + AAAAAAAAAAAAgD8IAAAAAAAAAIcAAMRCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMZCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMhCA4AAAGAIAAAAAAAA + AAAAgD8IAAAAAAAAAIcAAMpCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAMxCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAAM5CA4AAAGAIAAAAAAAAAAAAgD8I + AAAAAAAAAIcAANBCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANJCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANRCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAA + AIcAANZCA4AAAGAIAAAAAAAAAAAAgD8IAAAAAAAAAIcAANhCA4AAAGAEAAAAAAAAAAAAgD8EAAAAAAAAAIPwmtM9A4AAAEALAAAAAADAQQAA4EEAAOhBAAAcQgAAIEIA + AFxCAABwQgAAkEIAAKhCAADAQgAA2EILAAAAutqaP4P6pYk/Bm4plj/NvpE/+qWJP1zEdz+TLSU/XMT3PgEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMz + P2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAAA8EIAAAAADgAAAAAAAD/NzMw9zcxM + PgAAAD8CAAAAAAAAAAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAACAPwQAAAACAAAAAwAAAAQAAAABAAAAAQAAAAAAgD8AAIA/AQAAAAEAAAABAAAAAQAAAAEAAAAA + AIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAwAAAAcAAAABAAAAAAAAAA4AAAAQ + AAAAAAAAAJqZGT4AAIA/AAAAAAAAAL8AAMBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAAQAAAAAAAAAOAAAAEAAAAAAAAAAA + AMhCAACAvwAAAD8AAABAAADhQwAAAMAAAIA/AACAPwAA+kUAAADAMzMzPwAAQEAAAAAAAAAAAAAAAAADAAAACgAAAAEAAAAAAAAADgAAABAAAAAAAAAAAADIQgAAAMDN + zEw/AAAAQAAAlkMAAADAAACAPwAAgD8AQBxFAABAQAAAAD8AAEBAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAIA/AACgwAAAAAADAAAAAwAAAAAAAAABAAAABQAAAAQAAAAB + AAAAAwAAAKBHYT7IzEw+FK6HP5gpPcL8Nq1DBAAAAAUAAAAAACDBAAAYwgAAwMIAAMDCAADAwgUAAAAAAAAASHs1PAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAXI+CP8jMTD4Uroc/aNYqQs3DrEMEAAAABQAAAAAAGMIAACDBAADAwgAAwMIAAMDCBQAAAODkZzwAAAAAAAAAAAAAAAAA + AAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAAD2KBxArkfhPwAAwD8AAAdDAAAAAAQAAAAFAAAAAADAwAAAcMIAAHDCAABwwgAAcMIF + AAAAbxKDOgAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAAAAIEDNzEw/AADAPwAANEIAAAAABAAAAAUAAAAA + AHDCAADAwAAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAuB7FPz0KVz+u + R+E/AAAAAAAAAAAAAAAABQAAAAAAcMIAAHDCAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA + AAAAAAAAAAEAAAADAAAAzcxMP4/Cdb4AAMA/AACQQQAAAAAAAAAAAAAAAAEAAAAAAAAAEThWOKQsAABQclZLJgAAAlhGVFCULAAACgAAADIwMjMvMDYvMDYXAAAAUGlh + bm90ZXEgOC4wLjAvMjAyMjExMTcYjEVHyzymDvwmmkiaLNWaZGxsYVMsAAB0ZHRN6goAABAAAABCbMO8dGhuZXIgSW1wcm92AAAAAAcAAABNb2RhcnR0rAAAAFRoaXMg + cHJlc2V0IG9mZmVycyBhIG5pY2UgbXVzaWNhbCBwcmVzZW5jZS4gWW91IG1heSBsaWtlIHRvIHVzZSBpdCBmb3IgZGFpbHkgcHJhY3RpY2Ugb3Igc29tZSByZWxheGVk + IGltcHJvdmlzYXRpb24uIEl0IHVzZXMgVTg3LWZpZzggbWljcyB0aGF0IGluY3JlYXNlIHRoZSBzZW5zZSBvZiBzcGFjZS4AAAAARAEAABAAAABCbMO8dGhuZXIgSW1w + cm92AAAAAPMJAABHUlVVAAAABgAAAAEDwE1UcmsAAAndAP8CHyBMaXN6dCAtIFVuIFNvc3Bpcm8gLSBQaGlsIEJlc3QAsEM8AENwAEN/ggOQJRxXsEANVkAdAEAtAEA8 + AEBMAEBcAEBtAEB+VkB/ggOAJVtXkCweglkxKACALGmBLZA1KYIDODCCAz0vAIA4W4EtPWMAkEEwAIAxG1Y1MIEtkERAAIBBYIEtkEEuAIBEVYIDkD0yAIBBZoEtPVUA + kDgrggM1IwCAOEqCAzVjAJAxK4IDgDFmAJAsJoMwgCxmAJAlKIIDgCVvAJAsL4IDMTYAgCxpgS2QNTeBLTg9ggM9PgCANU8AOGNWMTBXkEFEAIA9bIEtkERNVoBBaYEt + kEFGAIBEY4EskD1CAIBBaYEtkDg4V4A9RYEskDUzV4A4R1aQMT9XgDVmgSyQLD1XgDFpggOQJUoAgCxsggOQLENWgCVvgS2QMUlWsEM/AEMLV0MAAIAsbACQNT+BLThC + ggM9PgCAOGMANUeBLDE9AJBBQACAPWyCBJBERwCAQWmBLJBBMwCARGCCA7BDIACAQWkAsENfAJA9MACwQ3+BLYA9YACQODKCAzUxAIA4UoEtNWAAkDEzggOAMWkAkCwv + glqALGYAkCU1ggOAJW8AkCwtggMxNFaALGNXkDUwVoAxW1eQOCeBLIA1R1eQPScAgDhYggOQQSoAgD1pgS1BTwCQRDaCA4BEYACQQSGBLIBBXVeQPSZWgD0uV5A4GoEs + gDgrV5A1G4IDgDVYAJAxI4IDgDFdVpAsJ4MwgCxFV5AlJ4IDgCVsAJAsKYIDgCxjAJAxLIEtNSgAgDFbVjVmVpA4MYEtPS5XgDhpVpBBMlaAPViBLUE0AJBES4EtgERs + VpBBJIEtgEFmAJA9K4EtgD04VpA4F1aAOFtXkDUigS2ANWBWkDErgS2AMWMAkCwpglmALFsAkCUogS1GT4EtgEZvACVsVpAsMIEtMTAAgCxpgS2QNTFWgDFmADVpVpA4 + P4IEPTUAgDhsgSyQQTYAgD1bgS1BXQCQSVuBLYBJb1aQQSmBLYBBZgCQPTOBLYA9XVaQOCdWgDhPV5A1LoEtgDVgAJAxMIIDgDFpAJAsKYIDgCxpVpAlPlewQHkAQGEA + QEkAkEtjALBAMgBAGwBABABAAFZABlZAGgBALwBASABAYwBAf1eAJW8AkC4yAIBLcoEtkDFAVoAuZlaQNk1XgDFmVjZsVpA6RYEtPUUAgDpsgS2QQkkAgD1pgS2QTVxW + gEJmVk1sAJBCMIIEPS9WgEJbVpA6L1eAPUpWOkpWkDYrgS2ANmMAkDEzggMuNwCAMWmCBC5gAJAlOYEssEBwAEBcAJBLVwCwQEkAQDkAQCpXQBsAQBEAQAoAQAYAQAQA + QApWQBUAQCIAQDAAQEAAQFQAQGoAQH9WgCVvAJAuJgCAS2+CBJAxMgCALmaBLJA2K1eAMWNWNmkAkDopggOAOmMAkD0fgS2APSsAkEImVoBCQIEtkEY5VoBGb1eQQiuB + LYBCD1aQPRyBLYA9NgCQOiSBLIA6O1eQNhmBLYA2WFaQMSKCA4AxUgCQLiOBLYAuWIJZkCUiV7BAdABAWwBAQVaQSUMAsEAkAEAGAEAAgS1ABgBAIABAOQBAUwBAbQBA + f1aQLCsAgCVsAElvggOQMSkAgCxpgS2QNS6BLTg2gS2ANVUAMT0AkD0wVoA4aVY9Y1eQQTiBLURGAIBBaYEskEE1AIBEY4EtkD1DVoBBZoEtkDg1AIA9SoEtkDVDAIA4 + VYEtNWYAkDFHggMsQgCAMWyCA5AlSlaALGwAsEM/AEMLV0MAgS2AJW8AkCxOgSwxSVeALGlWkDVBgS04UIEtPU1WgDhvADVgVjE4V5BBQwCAPWyBLJBERgCAQWyBLbBD + HgBDXgCQQTRXsEN/AIBEY4EskD07AIBBaYEtkDg0VoA9XVeQNTdWgDhVgS01ZgCQMTuCAywsAIAxaYJakCVJVoAsbIIDJWwAkCw0ggMxLACALGyBLZA1KwCAMWNWNW9X + kDgygS2AOGYAkD0zgSyAPWNXkEEiVoBBK1aQRFZXgERsVpBBJYEtgEFmVpA9K1eAPTJWkDggVoA4VVeQNSeBLYA1OFaQMS2BLYAxYwCQLCmBLIAsYFeQJSwARlqCA4Al + bwCQLDUAgEZvgS0sYACQMTSBLTU5VoAxZgA1YIEtkDhAgSw9NgCAOGyBLT1jAJBBLoEtgEEjAJBJX4EtgElvVpBBLoEtgEFpAJA9MYIDgD1jAJA4KlaAOFJXkDUrgSyA + NVUAkDEuggSAMWkAkCwlglmALGkAkCU+VrBAcgBAVgBAPABAHgBAAACQS1+BLbBAAgBAHQBAOVdAVwBAeABAfwCAJWxWkC4xAIBLb4IDkDE1AIAuZoEtkDY8AIAxZlY2 + ZleQOkOBLIA6bACQPS6BLUI1V4A9XYEskE1VAIBCT1dNbFaQQiSCAz0rAIBCT4EtPT0AkDovVoA6RVeQNi2CA4A2YACQMTCCAy4yAIAxZoIDLlIAkCUugS2wQHQAkEtQ + ALBAXABARABALQBAFABAAIEtQAgAQB0AQDMAQE0AQGgAQH8AgCVvVpAuKACAS2+BLZAxKQCALmyCA5A2LACAMV0ANmyBLZA6NoEsgDpjAJA9J4EtQisAgD0ogS1CHFaQ + RjaBLYBGbFaQQiiBLYBCOACQPSCBLYA9F1aQOhpXgDpbVpA2IoIDgDZjAJAxI4IDgDFgV5AuJ1aALl2CWpAlJVawQGwAQFAAQDUAQBcAQAAAkElFgS2wQA9WQCoAQEcA + QGUAQH9WgCVsAJAsJwCASWyBLZAxLACALGOBLZA1KoEtgDFKVpA4MVeANVVWOGAAkD0sgS2APSgAkEEwggOAQVIAkEQ6gS2ARGAAkEEjgSw9LwCAQWmBLT0YAJA4KoEt + NSdWgDgpVzVbVpAxN4EtgDFpAJAsNIIDJUAAgCxsgS2QLCoAgCVvggOQMTlWgCxpVpA1OIEtODCBLYAxPQA1TVY4WwCQPSeBLYA9J1eQQSNWgEE2VpBEMYEtgEQuVpBB + I4EtgEEkV5A9GFaAPWCBLZA4FlaAODJWkDUZggSANU0AkDEnggOAMWkAkCwyggOALG+CA7BAewBAYQBARgCQJEMAsEAoVkAIAEAAV0AGAEAgAEA9VkBcAEB9AEB/AIAk + bFaQLTCBLYAtZleQMz6BLDU/AIAzbAA1b4EtkDk7gS0/RIEtQU1WgD9pADkkVpBFSFeAQWBWkE5mAIBFaYEtTnIAkEU6ggNBPwCARWCBLZA/PgCAQWaBLT9pAJA5QFaA + OV1WkDVAgS0zPFaANTBXkC1CVoAzbAAtW4JasEB9AEBgVkBCAEAgAJAhWQCwQABWkE1mALBAEQBALgD/LwBQcnNVWSEAAEcAAAAJAAAAYmx1ZXRobmVyAAAAAA8AAABH + cmFuZCBCbMO8dGhuZXIAAAAAAAAAAHByb21iAAAAAQAAAAEAAAABAAAACQAAAGJsdWV0aG5lcgAAAAAPAAAAR3JhbmQgQmzDvHRobmVyAACAPwYAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAA + AAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAA + AAAAAMADgAAAYFgAAAAAAKhBAACAP1gAAACwS2A/VxARcz8AOA090HOXv9B61r9o2za/eBN+v1BQkT/Y9Z0/OCkSP1CwWL8sz62/aD+Gv5Cmq78AxP07+PvEP8Cbl77A + bEY/SJR3v9CRUL/Axe8+ALc2PcB99T4AtdW+kBLuv8hrq79Aymc/0ObfPkCwPb4QFhu/4Bu9PgCtuT9A7QM+ABzpPoCiJ73cgca/7M2Wvwx5ir9AQP8+AODzvfoDLEBI + 1Qs/AB+tP6D6xr4Axb6+QGm/PeAnDz4MYYm/wDMSv8gMA0Aw2Gk/ANi8PTDuMT9AyAQ/ACeLvQBDrr7gvga/AKO6vuxSvb8IU4i/fODVv/ifqj/If0c/cAEPP8gVar/Q + 6Xq/4PyKvsDXqj4QhZ6+yKPqP8BMWj4ASVc9oH1dvjB/hb8w/CI/QHzevpBruL6w+YG/YJuoPoAwYj9Y1KQ/gHq4PfDhAEAggwE/yKOavyg0mj8QacA/AOb5PQMAAAAB + AAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAEBAAAAAAAEAAAAAAAAA32AfPgOAAABACwAAAAAAqEEAAPBB + AAD4QQAAEEIAABRCAAAgQgAAQEIAAHBCAACQQgAAqEIAALBCCwAAAEeZTT8B2t5SP9reUj8GF2BuP05QYT90a1g/bHFkP/SJgj8jwbY/I8G2P4EBAAAAAAAAAAAAAAAA + AKDAAACgQAAAsEIAAAAAA4AAAFAAAHBCAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAKDAAACgQAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQOA + AABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BgAAAAAAAAADSBAAAAAAAAAAAAAAAAACAAAAAPwAAgD8DgAAAUAAAcEIBAAAAAAAAAAOAAABgWAAAAAAAqEEAAIA/WAAAAAAA + AMACAACAvwAAQMAAAEDABAAAQEAAAABAAADAQAAAgD8AAIA/BgAAwEAAAEBAAACAQAAAAMAAAEDAAACAQAAAgEAIAACgQAAAwEAAAIBAAAAAgAAAgD8AAADAAACAPwAA + QMAAAEDAAwAAQEAAAIBAAACgQAAAoEAGAABAwAAAoEAAAACAAAAAQAAAQMAAAKBAAACgQBUAAEBAAACAQAAAAEAAAADAAACAPwAAgEAAAABAAACgQAAAQEAAAEDAAAAA + gAAAgEAAAEDAAAAAgAAAgD8AAIC/AABAwAAAoEAAAEDAAAAAwAAAAIAAAACABQAAoEAAAEDAAAAAwAAAgL8AAEDAAABAwAMAAKBAAAAAwAAAQMAAAEDADwAAgEAAAIA/ + AABAwAAAgEAAAAAAAACAQAAAgL8AAABAAACgQAAAQMAAAIC/AAAAwAAAwEAAAKBAAABAwAAAQMADAAAAwAAAgD8AAEDAAABAwAOAAABgAgAAAAAAqEEAAK5CAgAAAAAA + gD+BAgAAAAEAAABVAAAARXF1YWwgdGVtcGVyYW1lbnQKVGhlIHN0YW5kYXJkIHR1bmluZywgd2hlcmUgdGhlIG9jdGF2ZSBpcyBkaXZpZGVkIGluIDEyIGVxdWFsIHN0 + ZXBzLgwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQBAAAAAAAAAAQAAAAAANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAA + AERlZmF1bHQAAAAAAAAAAAIAAAADgAAAQAQAAAAAAMBBAAAQQgAAQEIAANhCBAAAAAAAgMACAAAAwAAAAAAAAAAAAAAAAAAAAAAAAIA/AQAAAAEAAAABAAAAGAAAAAOA + AABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6 + RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8D + gAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAACBCA4AA + AEAHAAAAAACoQQAAkEIAAKhCAAC0QgAAwEIAAMhCAADYQgcAAAAAAAAAgQAAoEAEAAAAQQAAMEEAAEBBAABQQQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACA + P4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/ + AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEAAAAAzcxMPgAAAAAAAAAAAAAAAAAAAD8zMzNAA4AAAGACAAAAAACoQQAArkICAAAAAACA + P4EzMzNAAAAAPwOAAABIAQAAAAAAgD8BAAAAAQAAAAEAAAAKAAAAU21hbGwgSGFsbAsAAABtZWRpdW1oYWxsMQAAgEAAAKBBAACAP83MTD8AAAAACtcjPQAAAADNzMw9 + AAAAAJqZmb4AAKpDAACAPwAAgD8BAAAABgAAAAAAgD8AAIA/A4AAAGBYAAAAAACoQQAAgD9YAAAAAACAP4kfhYs/M0jhej+F69E/w/VoP+xRmD/D9Yg/XI+CP2Zmhj8A + AIA/CteDP4/CdT/D9Yg/KVyPP2Zmhj8fhYs/j8J1P65HgT/Xo5A/zcyMPwrXgz/D9Yg/16OQPxSuhz9xPYo/w/VoP1yPgj+kcH0/4XqUPwAAgD/D9Yg/7FGYPx+Fiz/X + o5A/exSOP+xROD+PwnU/ZmaGP3sUjj89Cpc/exSOP1yPgj8AAIA/SOF6PwrXgz/Xo3A/ZmZmP65HYT+amVk/cT2KP7gehT8Uroc/cT2KP3E9ij8MMzOTP8P1iD8K14M/ + w/VoP2ZmZj+kcH0/ZmZmP65HgT/NzIw/H4VrP8P1aD9xPYo/cT2KPwFmZmY/ZmZmPwkzM3M/w/WIP8P1aD+uR4E/cT2KP3sUjj/NzIw/H4VrPwrXgz8K14M/A4AAAEAD + AAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgWAAAAAAAqEEAAIA/WAAAAAAAAACJ+ZqVPU0Bft08Eo9BPe5ax70+OY49x/78PD2wyT0OvFk9pnPJvdGlrzzk + lKu92culO3bzYr2meoS9bmeCvYivmbyAM1A9amf3ux6K2jwZuZa9duzLvJdsNj3AZ2m9RGQZvHVHub1ZyDw9ivm0PRYz5zwFa3m9+FU5u6tREL1MnL29k+8bveHAYL1b + Nfy8KZ7jPEPZXj2RDpw9tkPFve1Llb2caME9+B8WvWfCwD0uUti8FY4QPfHkI72sZb+7rUtKPSx5NLyXo5e6KGVrvSK/cb3ynTi8xQ6MvZT5vTxcjrq9IU+lPZc7o7v5 + 7HQ8qITOPMn0dT1YGjG9Zh19PRWtLL3Taay8SNGsvRc5qr2+1bG9YK61vS4Uuz2W7se96O9NPYFjBL0Lu2A9LwtsPPmGmTtItIY9kqxRPQEAAAAAAAAAAQAAAAAAAD8A + AABAAwAAAAAAgD8AAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBC + AADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD+amZk+zcxMPwAAwD/NzEw+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAGBYAAAAAACoQQAA + gD9YAAAA8othP1eUpl8/IH94P3byjj/qSJc/LG+HP6LXiz/06lo/E3FYP/icaT+6gok/xdmRP4bAjD9YkZE/NUN5P/35UD8ENYE/Rq1kP2Piiz9ymYk/sqltP87PeT8w + 3m0/SAOEP9MHnD8DHJM/onVjPxFQcD8oOYE/qMeHP2H7cj/ExVY/Zld6P8ddcT/d1H8/hF2YP38fkj+lqpA/+3VxP1nEgT+G9Do/O6ZmP/kcSD8hRYE/WgiEPz/efz/U + YoI/XeWUP0OQiz8KDU0/LgtoP/lLfT8zUGw/EylxP1oBgT9/HYU/Ff+HP3V8hT89w5c/orKQP8kmmz8dlFs/NwhqP+INcD8/aI4/IrCPPyidhD/JuHc/Q5GFPxgGUT+R + Onw/gqWAP/TfhD9AWJI/5I9xP9bSiD912Ic/Z7GSP4Cnez/ibGw/KP1hPzjhgT95cVA/4014Pylylz/qvmU/8UpeP9V7gj8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAA + AACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAAWAAAAAAAqEEDgAAAYMoAAAAAAAAAAACAP8oA + AAAAAAAA/wAAAADJAACwQQOAAABgxgAAAAAAAAAAAIA/xgAAAAAAAAD/AAAAAMUAALhBA4AAAGDEAAAAAAAAAAAAgD/EAAAAAAAAAP8AAAAAwwAAwEEDgAAAYMEAAAAA + AAAAAACAP8EAAAAAAAAA/wAAAADAAADIQQOAAABgzAAAAAAAAAAAAIA/zAAAAAAAAAD/AAAAAMsAANBBA4AAAGDGAAAAAAAAAAAAgD/GAAAAAAAAAP8AAAAAxQAA2EED + gAAAYLoAAAAAAAAAAACAP7oAAAAAAAAA/wAAAAC5AADgQQOAAABgswAAAAAAAAAAAIA/swAAAAAAAAD/AAAAALIAAOhBA4AAAGCsAAAAAAAAAAAAgD+sAAAAAAAAAP8A + AAAAqwAA8EEDgAAAYLQAAAAAAAAAAACAP7QAAAAAAAAA/wAAAACzAAD4QQOAAABgtwAAAAAAAAAAAIA/twAAAAAAAAD/AAAAALYAAABCA4AAAGCuAAAAAAAAAAAAgD+u + AAAAAAAAAIFmZuY/BDMzU8AAAAAAmpkZQAAAAAAAAAAAgc3MLEACzczMvQAAAAAAAAAA/wAAAAChAAAEQgOAAABgqQAAAAAAAAAAAIA/qQAAAAAAAAD/AAAAAKgAAAhC + A4AAAGCjAAAAAAAAAAAAgD+jAAAAAAAAAP8AAAAAogAADEIDgAAAYK4AAAAAAAAAAACAP64AAAAAAAAA/wAAAACtAAAQQgOAAABgqgAAAAAAAAAAAIA/qgAAAAAAAAD/ + AAAAAKkAABRCA4AAAGCjAAAAAAAAAAAAgD+jAAAAAAAAAP8AAAAAogAAGEIDgAAAYJcAAAAAAAAAAACAP5cAAAAAAAAA/wAAAACWAAAcQgOAAABgiwAAAAAAAAAAAIA/ + iwAAAAAAAAD/AAAAAIoAACBCA4AAAGCEAAAAAAAAAAAAgD+EAAAAAAAAAP8AAAAAgwAAJEIDgAAAYIYAAAAAAAAAAACAP4YAAAAAAAAA/wAAAACFAAAoQgOAAABgjAAA + AAAAAAAAAIA/jAAAAAAAAAD/AAAAAIsAACxCA4AAAGCLAAAAAAAAAAAAgD+LAAAAAAAAAP8AAAAAigAAMEIDgAAAYIgAAAAAAAAAAACAP4gAAAAAAAAA/wAAAACHAAA0 + QgOAAABghAAAAAAAAAAAAIA/hAAAAAAAAAD/AAAAAIMAADhCA4AAAGB+AAAAAAAAAAAAgD9+AAAAAAAAAP0AADxCA4AAAGB3AAAAAAAAAAAAgD93AAAAAAAAAPYAAEBC + A4AAAGBoAAAAAAAAAAAAgD9oAAAAAAAAAOcAAERCA4AAAGBoAAAAAAAAAAAAgD9oAAAAAAAAAOcAAEhCA4AAAGBoAAAAAAAAAAAAgD9oAAAAAAAAAOcAAExCA4AAAGBZ + AAAAAAAAAAAAgD9ZAAAAAAAAANgAAFBCA4AAAGBZAAAAAAAAAAAAgD9ZAAAAAAAAANgAAFRCA4AAAGBZAAAAAAAAAAAAgD9ZAAAAAAAAANgAAFhCA4AAAGBWAAAAAAAA + AAAAgD9WAAAAAAAAANUAAFxCA4AAAGBJAAAAAAAAAAAAgD9JAAAAAAAAAMgAAGBCA4AAAGBJAAAAAAAAAAAAgD9JAAAAAAAAAMgAAGRCA4AAAGBIAAAAAAAAAAAAgD9I + AAAAAAAAAMcAAGhCA4AAAGBEAAAAAAAAAAAAgD9EAAAAAAAAAMMAAGxCA4AAAGBAAAAAAAAAAAAAgD9AAAAAAAAAAL8AAHBCA4AAAGA3AAAAAAAAAAAAgD83AAAAAAAA + ALYAAHRCA4AAAGA3AAAAAAAAAAAAgD83AAAAZmZmP4GamZk+ATMzMz8zMzM/AwAAAABmZqY/mpmZPpqZmT6EAAAAAKkAAHhCA4AAAGA2AAAAAAAAAAAAgD82AAAAMzPz + P4EzMzM/AWZmpj9mZqY/AwAAAABmZiZAMzMzPzMzMz+EAAAAAKgAAHxCA4AAAGAvAAAAAAAAAAAAgD8vAAAAMzMzQIEAAIA/AQAAAEAAAABAAwAAAAAAAIBAAACAPwAA + gD+EAAAAAKEAAIBCA4AAAGAvAAAAAAAAAAAAgD8vAAAAMzMTQIHNzEw/Ac3MzD/NzMw/AwAAAADNzExAzcxMP83MTD8CAACAP5qZmT+amZk/A83MTD/NzEw+AAAAAAAA + AACfAACCQgOAAABgLQAAAAAAAAAAAIA/LQAAAJqZ2T+BmpkZPwGamZk/mpmZPwMAAAAAmpkZQJqZGT+amRk/AgAAgD8zM7M/MzOzPwOamRk/zczMPgAAAAAAAAAAnQAA + hEIDgAAAYCsAAAAAAAAAAACAPysAAADNzIw/gc3MzD4BzcxMP83MTD8DAAAAAM3MzD/NzMw+zczMPgIAAIA/zczMP83MzD8DzczMPpqZGT8AAAAAAAAAAJsAAIZCA4AA + AGAoAAAAAAAAAAAAgD8oAAAAmpkZP4HNzEw+Ac3MzD7NzMw+AwAAAADNzEw/zcxMPs3MTD4CAACAP2Zm5j9mZuY/A83MTD7NzEw/AAAAAAAAAACYAACIQgOAAABgJQAA + AAAAAAAAAIA/JQAAAAAAAACIZmZmPwEAAABAAAAAQAMAAAAAAACAPwAAAAAAAAAAlQAAikIDgAAAYCQAAAAAAAAAAACAPyQAAAAAAAAAAwAAQMAAAABAAAAAAAAAAACC + AACgwAMAAAAAMzMzPwAAwD8AAMA/AwAAAADNzEw/AAAAAAAAAACUAACMQgOAAABgIgAAAAAAAAAAAIA/IgAAAAAAAAADAAAAwGZmpj8AAAAAAAAAAIIzM1PAAwAAAAAA + AAA/AACAPwAAgD8DAAAAAAAAAD8AAAAAAAAAAJIAAI5CA4AAAGAgAAAAAAAAAAAAgD8gAAAAAAAAAAMAAIC/MzMzPwAAAAAAAAAAgpqZ2b8DAAAAAM3MTD4AAAA/AAAA + PwMAAAAAzcxMPgAAAAAAAAAAkAAAkEIDgAAAYB4AAAAAAAAAAACAPx4AAAAAAAAAnQAAkkIDgAAAYBwAAAAAAAAAAACAPxwAAAAAAAAAmwAAlEIDgAAAYBsAAAAAAAAA + AACAPxsAAAAAAAAAmgAAlkIDgAAAYBkAAAAAAAAAAACAPxkAAAAAAAAAmAAAmEIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAAAAlwAAmkIDgAAAYBYAAAAAAAAAAACAPxYA + AAAAAAAAlQAAnEIDgAAAYBUAAAAAAAAAAACAPxUAAAAAAABAhQAAAACOAACeQgOAAABgFAAAAAAAAAAAAIA/FAAAAAAAAACTAACgQgOAAABgEwAAAAAAAAAAAIA/EwAA + AAAAAACSAACiQgOAAABgEgAAAAAAAAAAAIA/EgAAAAAAAACRAACkQgOAAABgEQAAAAAAAAAAAIA/EQAAAAAAAACQAACmQgOAAABgEAAAAAAAAAAAAIA/EAAAAAAAAACP + AACoQgOAAABgDwAAAAAAAAAAAIA/DwAAAAAAAACOAACqQgOAAABgDgAAAAAAAAAAAIA/DgAAAAAAAACNAACsQgOAAABgDQAAAAAAAAAAAIA/DQAAAAAAAACMAACuQgOA + AABgDAAAAAAAAAAAAIA/DAAAAAAAAACLAACwQgOAAABgDAAAAAAAAAAAAIA/DAAAAAAAAACLAACyQgOAAABgCwAAAAAAAAAAAIA/CwAAAAAAAACKAAC0QgOAAABgCgAA + AAAAAAAAAIA/CgAAAAAAAACJAAC2QgOAAABgCgAAAAAAAAAAAIA/CgAAAAAAAACJAAC4QgOAAABgCQAAAAAAAAAAAIA/CQAAAAAAAACIAAC6QgOAAABgCQAAAAAAAAAA + AIA/CQAAAAAAAACIAAC8QgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAAC+QgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADAQgOAAABgCAAAAAAAAAAAAIA/CAAA + AAAAAACHAADCQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADEQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADGQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACH + AADIQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADKQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADMQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADOQgOA + AABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADQQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADSQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADUQgOAAABgCAAA + AAAAAAAAAIA/CAAAAAAAAACHAADWQgOAAABgCAAAAAAAAAAAAIA/CAAAAAAAAACHAADYQgOAAABgBAAAAAAAAAAAAIA/BAAAAAAAAACDqJbIPQOAAABACQAAAAAAqEEA + APBBAAD4QQAAEEIAACRCAAAoQgAAgkIAAKpCAADYQgkAAABP9pc/AbTykz+08pM/BnGRjj+zBYw/L9OcP1I1kT9Esjo//a8CPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAA + AAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAAA8EIAAAAADgAA + AAAAAD/NzMw9zcxMPgAAAD8CAAAAAAAAAAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAAAAAAQAAAACAAAAAwAAAAQAAAABAAAAAQAAAAAAgD8AAIA/AQAAAAEAAAAB + AAAAAQAAAAEAAAAAAIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAwAAAAcAAAAB + AAAAAAAAAA4AAAAQAAAAAAAAAM3MTD0AAIA/AAAAAAAAAL8AAHBCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAO + AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAADgAAABAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAIA/AADAwAAAAAADAAAAAwAAAAAAAAAB + AAAABQAAAAQAAAABAAAAAwAAAAAAwD8AAKA/AADAPwAAYcMAAAAABAAAAAUAAAAAAAAAAACQwQAAwMIAAMDCAADAwgUAAAAXt9E4j8J1PQAAAAAAAAAAAAAAAAUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAAADAPwAAQD8AAMA/AAA0QgAAAAAEAAAABQAAAAAAkMEAAAAAAADAwgAAwMIAAMDCBQAAAI/CdT0X + t9E4AAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAACPwvU/PQpXvx+Faz8AALRCAAAAAAAAAAAFAAAAAABAwQAAQMEA + AHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAADMzM79cj8I+rkdhPwAAAAAA + AAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAAD + AAAAuB7FPz0KVz+uR+E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAQAAAAAAAAABAAAAAQAAAAEAAAADAAAAAABAP83MTL5mZqY/AACQQQAAAAAAAAAAAAAAAAEAAAAAAAAAEThWOCwmAABQclZLJgAAAlhGVFAcJgAACgAAADIwMjMv + MDYvMDYXAAAAUGlhbm90ZXEgOC4wLjAvMjAyMjExMTcNKJ0oFgikCbVRP8zNVBCcZGxsYdslAAB0ZHRNHQQAAAoAAABZQzUgSW1wcm92AAAAAAcAAABNb2RhcnR0KgAA + AEEgcHJlc2V0IHRoYXQgZW5jb3VyYWdlcyB5b3UgdG8gaW1wcm92aXNlLgAAAABEAQAACgAAAFlDNSBJbXByb3YAAAAAtAMAAEdSVVUAAAAGAAAAAQPATVRyawAAA54A + /wIPIEJsYXogSnVyamV2Y2ljALBAAABDAABCAABFAE2QNCgHsEB/hXKQOiaEJz40g1tCQIRFgEIAL5BFPpAosEAAMoBFACmQQiGCUoBCAEiQQyyCbIBDAASQRVKBMYA+ + AIFpOgAXRQASkEZUR4A0AIIrRgATkEhegWyASACBGZBISoI6gEgABJBJRIURLSSBMbBAf4JfgC0AXZA3IIJNgDcAgWeQPR2DMIBJAIFUkEEThDpIbWywQACBIoBIAGWQ + RkCBToBGAIEakEUdgRWARQCBSJBGPII6gEYAaZBDHoNtgEMAQ5BELIUIgEQASJBFQIcegEUABpBGXIIpgEYARZBENIErgEEAO0QAc5BFPoJcgEUABz0AAJBAIoIggEAA + XJBDSIMOgEMAXJBBOoVngEEAD5BDQoEUMh2BNoBDABqQRD6FXbBAf3OQOCqGeTsYhm1AKIwARzKCc4BEAIkUOwANMgBMOABqRwAAQACEQJBIVoQtgEgACbBAADmQMjIG + RUiCSLBAf4FTkExUIDoshH2ARQBEkDwkBkE4kl+APAAATAAnkEpGOoAyADk6AA9BAIEHSgBMkEhOgkCASAB+kEpcCrBAABiQMzaCM7BAf4EfkDo0gn4+OoJlgEoAGJBD + QIEGgD4AgW46AAwzABSQSGE9gEMAgWdIAFuQRlaEMIBGAC6QSFINsEAAHZA0KIJTsEB/gSuQOi6DHEM6gSuASACCYUMAIJBGQINugEYALzQAEJBFVGmAOgCEA7BAACGA + RQAskDUyA0ZGglmwQH+CUpA/RjM6GDKARgBMPwCGV5BKTCo/LIhCgD8AglU6AEk1AIYDSgAPkEtAgzGASwAvkExpFy4oBrBAAIIvgEwAeJBPVlc5LnaATwCBNJBNPoIM + gE0AWZBKRoFQgEoAeJBGIXaARgCEEpBKUIFwgEoAbpBLQIQ6gEsANpBKPIosRlwRgEoAghBGAD2QRTiCBYBFAFSQSFSCCYBIAEGQRjiCWYBGAAk5AEeQRCyCDIAuAIFA + RABLkEUwgRQtFoJFsEAsCkB/hFmQNxiFGz4ch2ywQABigD4ASZA9F4E7sEB/iR2APQCBZi0AhCM3AIMSLUAALkAAMkAAM0AANEAANUAAN0AAOEAAOUAAOkAAO0AAPEAA + PUAAPkAAP0AAQEAAQUAAQkAAQ0AAREAARUAARkAAR0AASEAASUAASkAAS0AATEAATUAAT0AAsEAAAP8vAFByc1WuIQAARwAAAAMAAAB5YzUAAAAACQAAAEdyYW5kIFlD + NQAAAAAAAAAAcHJvbVYAAAABAAAAAQAAAAEAAAADAAAAeWM1AAAAAAkAAABHcmFuZCBZQzUAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAA + AAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MRAAAAAQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA + AQAAAAAAAAAAAAAAAQAAAAwAAABDb25jZXJ0IEhhbGwAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAOAAABgWAAA + AAAAqEEAAIA/WAAAADQHuD9X/HH2PwhPNT9wIgg/fDOmP2DYuT9A+EY/5DqRPwjrZz8I+uw/QDaBPsC5rL8AKT++yHcWP4jrID/ghYc+3OzDP/BILz+A8oc+wFyOP8Cw + 3D9ccKc/wBH4vgCgjDwA3Es9wPTHPsCTez6AuNI+iLYdP6g0Yz80T7U/4JIPvwBoijyAwfg+IKgGv4Cf0T6A6uE+ANE4vegpRz8AXqc8WAd5Pwh+Hj8M8PU/aGXPv/A1 + CT+gJ64+AKdLPsANVz4Aj5c9QFVVv8RO7D9I7MS/MKtWPwABpz4gdmJAgH4wP6DWgz8Y0qC/MIgTP0CrMr4Q8Aa/QMJRvth87r+QY3S/QFVWviiZtL/A5U/AQO4MvxBp + uD8A8Ge8yG9pP5g0yD/wlg/AAgCQwE51DMA4B8e/IA3SvtiTkL+QBum/CDCwv9C3Hr9wXHi/4JQxwLirKsBQ9Qm/TFEewKAO9T74+n8/AwAAAAEAAAAAAAAAAAAAAAMA + AAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAAAwD8AAAAAAQAAAAAAAAAutwU+A4AAAGBYAAAAAACoQQAAgD9YAAAA5fVjP4FzpGY/ + glxjaT+CNTNsP4OfFG8/gjgIcj+Cug51P4LWKHg/glBXez+B1ih4P4FQV3s/gvCafj+CR/qAP4OEsoI/gql2hD+CMUeGP4KfJIg/g4IPij+BagiMP4H3D44/AcsmkD/L + JpA/ApRNkj8KhZQ/CoWUPwL6zZY/NCmZPzQpmT8BkpebP5KXmz8BABqePwAanj8BhbGgP4WxoD8BJl+jPyZfoz+QAQAAAAAAAAAAAAAAAACgwAAAoEAAALJCAAAAAAOA + AABQAABwQgEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIA + AAAAAIA/gYAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAgAAAAD8AAIA/A4AAAFAAAHBCAQAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAAAAAEDAG3d3N8Dv7k7AZmZGwN7d + HcBVVRXAmpnZv0REBMB3d/e/Zmamv1VVlb9ERIS/MzOzvyIi4r8iIiK/AAAAv1VV1b6rqqq+AACAvquqKr9VVRW/AAAAv1VV1b5VVVW/AABAv6uqqj5VVdU+AAAAPwAA + AD8DAAAAvwAAAD8AAAAAAAAAAAIAAAC/AAAAPwAAAD8DAAAAAAAAAD8AAAAAAAAAAIEAAAA/CAAAAAAAAAA/AAAAAAAAAL8AAAAAAAAAPwAAAL8AAAAAAAAAAIEAAAC/ + AwAAAD8AAAC/AAAAAAAAAACBAAAAPwIAAAC/AAAAAAAAAAABAAAAvwAAAL8DAAAAPwAAAAAAAAC/AAAAvwIAAAA/AAAAAAAAAAAEAAAAPwAAAAAAAAA/AAAAAAAAAAAD + AAAAPwAAAL8AAAAAAAAAAAEAAAA/AAAAPwEAAAC/AAAAvwEAAAAAAAAAAIEAAAC/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4ECAAAAAQAAAFUAAABFcXVhbCB0ZW1w + ZXJhbWVudApUaGUgc3RhbmRhcmQgdHVuaW5nLCB3aGVyZSB0aGUgb2N0YXZlIGlzIGRpdmlkZWQgaW4gMTIgZXF1YWwgc3RlcHMuDAAAAAAAyEIAAEhDAACWQwAAyEMA + APpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAEAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOAAABA + BAAAAAAAwEEAABBCAABAQgAA2EIEAAAAAADAwAIAAIC/AAAAAAAAAAAAAAAAAAAAAAAAgD8BAAAAAQAAAAEAAAAfAAAAA4AAAEgBAAAAAACAPwAAAAAAAIA/A4AAAGAC + AAAAAACoQQAArkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAA + AAAAAAAAAIA/A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAA + AIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAIEIDgAAAQA8AAAAAAKhBAADQQQAA+EEAABBCAABA + QgAAcEIAAJBCAACoQgAAtEIAAMBCAADIQgAA0EIAANRCAADWQgAA2EIPAAAAAACAwAQAAEDAAAAAwAAAgL8AAAAAAAAAAIEAAABABwAAgEAAAMBAAAAgQQAAgEEAAKBB + AADIQQAAAEIAAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIA + AAAAAAAAAACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAA + AAAAAAAAAAAAAAA/AAAAQAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAAAAQAAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAwAAABDb25j + ZXJ0IEhhbGwGAAAAYmNoYWxsAACAPwAAcEEAAIA/mpmZPwAAAAAK1yM8AAAAAAAAAAAAAAAAzcyMPwAAqkMAAIA/AACAPwEAAAAGAAAAAACAPwAAgD8DgAAAYFgAAAAA + AKhBAACAP1gAAAAAAIA/iaRwfT8ScT2KPwAAgD/NzIw/H4WLP6RwfT9cj4I/ZmZmP7gehT8fhWs/MzOTP6RwfT9xPYo/4XpUP3E9ij+uR2E/exRuPzMzcz+4HoU/uB6F + P4GamVk/F7gehT+kcH0/w/VoP1yPgj9I4Xo/MzNzPxSuhz9mZmY/rkeBP9ejcD+kcH0/exRuP4/CdT8fhYs/pHB9P3E9ij9mZmY/zcxMP8P1aD8zM3M/w/VoP1yPgj8f + hWs/H4VrPwGkcH0/pHB9Pw6PwnU/16NwPwAAgD/NzIw/rkeBP2ZmZj8K14M/H4VrP6RwfT+4HoU/cT2KP+xReD97FG4/CteDPwrXgz8Dw/WIPwAAgD9mZmY/ZmZmPwWu + R4E/7FF4P3E9ij9cj4I/exRuP3sUbj8FAACAPzMzcz8K14M/w/VoPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGBYAAAAAACoQQAAgD9Y + AAAAAAAAAIlePP68TRK7MD2Mda09ongXPQh5WT0JyPk88BjMvTF7yL11C1C80bC2PcXNOLyTSKw9dgA8u6euxD21p7k9EUYAvOX7kDw518O9IxMyPYRTqL1E+zk9B/EQ + vIk+rT2Cwuo88++qPWfKvj01wJS90S26PYb1HD2QpiA9rSUZPS8TSD3oI4m8e7G3uzDyvb0s3p08j+DAvFeMjzoVz3c9kcrLvbXXqzv2+Xq9ezqwuxxCK7z/7DS9XgPC + PRB4bbxKPak9XtjJvZFtpz2dPbC9RhVvPXAut70ft2A9SKuevahNuLxDGae7zQ/zu1Tmdz2N5369doubOiYNxz0+mjw8cz/HveAr6zxceKO9q7PcuykYLj2plKA9EKjs + vPWzhr3H5tQ81bbbPJushz0J6i47l0HIvV1Oxr3NkUM8AQAAAAAAAAABAAAAAAAAPwAAgEADAAAAAACAPwAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAA + gD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAAgD8HAAAAAACAP5qZmT5mZmY/AADAP5qZmT4I + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAADxjoA/V4uZcj8bjos/mEeEP0WJfz/+tHY/k6aFP/bngD+7438/A1Zn + PzGViT9FTpY/jYaNP9yZfz8ZKXg/3kqAP+WLYD9snns/Y16DP/JJbz//+Hs/MLJQP0dBkT8+Sok/qg+JPzkSgz/tMV4/AamCP59iej9Zn3M/K2xiP83pjz86m4Q/tWRl + P4X/oD/eing/kSN6P36tdj+ZGko/d9uDP/vvbT8U2Hg/rSdXP6akoj9poYo/TmpnP8eyhT+ZwIc/Ua+JP8njcT988Ys/t46aP+NpgT+ywYo/2h9DPyHugz+Wz3o/eaKX + P85+hD8YsY8/wwKUP04JkT+l26s/zfObP99WjD/nHp8/MRisP/jPij8VvVE/e7mAPwsfaT8Qbl0/t46aPyzJtz8ql5U/9TuKP6G2gT9SSIs/nWeNP65TkT/clYU/76qK + P3e7rD/K56w/aUyKP6mXqj/PEn8/Hpx0PwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAA + qEEAAHBCAADYQgMAAAAAAIA/ggEAAABYAAAAAACoQQOAAABgxgAAAAAAAAAAAIA/xgAAAAAAAAD/AAAAAMUAALBBA4AAAGC/AAAAAAAAAAAAgD+/AAAAAAAAAP8AAAAA + vgAAuEEDgAAAYLMAAAAAAAAAAACAP7MAAAAAAAAA/wAAAACyAADAQQOAAABgrAAAAAAAAAAAAIA/rAAAAAAAAAD/AAAAAKsAAMhBA4AAAGDGAAAAAAAAAAAAgD/GAAAA + AAAAAP8AAAAAxQAA0EEDgAAAYL8AAAAAAAAAAACAP78AAAAAAAAA/wAAAAC+AADYQQOAAABguwAAAAAAAAAAAIA/uwAAAAAAAAD/AAAAALoAAOBBA4AAAGC3AAAAAAAA + AAAAgD+3AAAAAAAAAP8AAAAAtgAA6EEDgAAAYLQAAAAAAAAAAACAP7QAAAAAAAAA/wAAAACzAADwQQOAAABg1AAAAAAAAAAAAIA/1AAAAAAAAAD/AAAAANMAAPhBA4AA + AGDMAAAAAAAAAAAAgD/MAAAAAAAAAP8AAAAAywAAAEIDgAAAYMYAAAAAAAAAAACAP8YAAAAAAAAA/wAAAADFAAAEQgOAAABguwAAAAAAAAAAAIA/uwAAAAAAAACBAACA + wAEAAAAAAAAAAP8AAAAAtgAACEIDgAAAYK8AAAAAAAAAAACAP68AAAAAAAAAgQAAQEADAACAPwAAAMAAAAAAAAAAAP8AAAAAqAAADEIDgAAAYKkAAAAAAAAAAACAP6kA + AAAAAAAAgc3MDMABAAAAAAAAAAD/AAAAAKQAABBCA4AAAGCvAAAAAAAAAAAAgD+vAAAAAAAAAIEAAEDAAQAAAAAAAAAA/wAAAACqAAAUQgOAAABgrwAAAAAAAAAAAIA/ + rwAAAAAAAAD/AAAAAK4AABhCA4AAAGCkAAAAAAAAAAAAgD+kAAAAAACAvwEAAAAAAAAAAP8AAAAAoQAAHEIDgAAAYJ0AAAAAAAAAAACAP50AAAAAAAAA/wAAAACcAAAg + QgOAAABgpQAAAAAAAAAAAIA/pQAAAAAAgD8CZmaGvwAAAAAAAAAA/wAAAAChAAAkQgOAAABgpQAAAAAAAAAAAIA/pQAAAAAAAAD/AAAAAKQAAChCA4AAAGCWAAAAAAAA + AAAAgD+WAAAAAAAAAAIAAADAAAAAAAAAAAACAAAAwAAAAAAAAAAA/wAAAACPAAAsQgOAAABglgAAAAAAAAAAAIA/lgAAAAAAAAD/AAAAAJUAADBCA4AAAGCWAAAAAAAA + AAAAgD+WAAAAAAAAAAIAAADAAAAAAAAAAACaAACAwAEAAAAAAAAAAIkAAIDAAQAAAAAAAAAA6QAANEIDgAAAYJEAAAAAAAAAAACAP5EAAAAAAAAA/wAAAACQAAA4QgOA + AABgiQAAAAAAAAAAAIA/iQAAAAAAAAD/AAAAAIgAADxCA4AAAGB0AAAAAAAAAAAAgD90AAAAAAAAAAIAAEDAAAAAAAAAAADwAABAQgOAAABgdAAAAAAAAAAAAIA/dAAA + AAAAAADzAABEQgOAAABgcwAAAAAAAAAAAIA/cwAAAAAAAACBAACAwAMAAAAAAAAAwAAAAAAAAAAA7AAASEIDgAAAYGMAAAAAAAAAAACAP2MAAAAAAIA/AgAAAMAAAAAA + AAAAAN8AAExCA4AAAGBjAAAAAAAAAAAAgD9jAAAAAADAv4IAAIA/AgAAwL8AAAAAAAAAANwAAFBCA4AAAGBhAAAAAAAAAAAAgD9hAAAAAAAAAOAAAFRCA4AAAGBbAAAA + AAAAAAAAgD9bAAAAAAAAANoAAFhCA4AAAGBOAAAAAAAAAAAAgD9OAAAAAABAwAEAAAAAAAAAAMsAAFxCA4AAAGBOAAAAAAAAAAAAgD9OAAAAZmaGvwEAAAAAAAAAAAIA + AADAAAAAAAAAAADIAABgQgOAAABgTQAAAAAAAAAAAIA/TQAAAJqZ+b8BAAAAAAAAAADKAABkQgOAAABgSAAAAAAAAAAAAIA/SAAAAAAAAMADAACAPwAAAAAAAMA/AADA + PwEAAAAAAAAAAMEAAGhCA4AAAGBEAAAAAAAAAAAAgD9EAAAAAAAAAMMAAGxCA4AAAGBAAAAAAAAAAAAAgD9AAAAAAAAAwAEAAAAAAAAAAL0AAHBCA4AAAGA6AAAAAAAA + AAAAgD86AAAAAAAAALkAAHRCA4AAAGA5AAAAAAAAAAAAgD85AAAAAAAAALgAAHhCA4AAAGA2AAAAAAAAAAAAgD82AAAAAAAAAIYAAEDAggAAAACrAAB8QgOAAABgMwAA + AAAAAAAAAIA/MwAAAAAAAACLZmaGvwEAAAAAAAAAAKQAAIBCA4AAAGAwAAAAAAAAAAAAgD8wAAAAAAAAAK8AAIJCA4AAAGAsAAAAAAAAAAAAgD8sAAAAAAAAAKsAAIRC + A4AAAGArAAAAAAAAAAAAgD8rAAAAAAAAAKoAAIZCA4AAAGAoAAAAAAAAAAAAgD8oAAAAAAAAAKcAAIhCA4AAAGAmAAAAAAAAAAAAgD8mAAAAAAAAAKUAAIpCA4AAAGAk + AAAAAAAAAAAAgD8kAAAAAAAAAKMAAIxCA4AAAGAiAAAAAAAAAAAAgD8iAAAAAACAP4EAAABAAgAAgL8AAAAAAAAAAJwAAI5CA4AAAGAgAAAAAAAAAAAAgD8gAAAAAAAA + AJ8AAJBCA4AAAGAeAAAAAAAAAAAAgD8eAAAAAAAAAJ0AAJJCA4AAAGAcAAAAAAAAAAAAgD8cAAAAAAAAAJsAAJRCA4AAAGAbAAAAAAAAAAAAgD8bAAAAAAAAAJoAAJZC + A4AAAGAZAAAAAAAAAAAAgD8ZAAAAAAAAAJgAAJhCA4AAAGAYAAAAAAAAAAAAgD8YAAAAAAAAAIEAAIA/AmZmhr8AAAAAAAAAAJIAAJpCA4AAAGAWAAAAAAAAAAAAgD8W + AAAAAAAAAIQAAEDAAQAAAAAAAAAAjgAAnEIDgAAAYBUAAAAAAAAAAACAPxUAAAAAAAAAlAAAnkIDgAAAYBQAAAAAAAAAAACAPxQAAAAAAAAAkwAAoEIDgAAAYBMAAAAA + AAAAAACAPxMAAAAAAAAAgwAAQMABAAAAAAAAAACMAACiQgOAAABgEgAAAAAAAAAAAIA/EgAAAAAAAACRAACkQgOAAABgEQAAAAAAAAAAAIA/EQAAAAAAAACCZmbGvwEA + AAAAAAAAAIsAAKZCA4AAAGAQAAAAAAAAAAAAgD8QAAAAAAAAAI8AAKhCA4AAAGAPAAAAAAAAAAAAgD8PAAAAAAAAAI4AAKpCA4AAAGAOAAAAAAAAAAAAgD8OAAAAAAAA + AIMAAIC/AgAAAMAAAAAAAAAAAIYAAKxCA4AAAGANAAAAAAAAAAAAgD8NAAAAAAAAAIwAAK5CA4AAAGAMAAAAAAAAAAAAgD8MAAAAAAAAAIsAALBCA4AAAGAMAAAAAAAA + AAAAgD8MAAAAAAAAAIJmZoa/AQAAAAAAAAAAhgAAskIDgAAAYAsAAAAAAAAAAACAPwsAAAAAAAAAigAAtEIDgAAAYAoAAAAAAAAAAACAPwoAAAAAAAAAiQAAtkIDgAAA + YAoAAAAAAAAAAACAPwoAAAAAAAAAiQAAuEIDgAAAYAkAAAAAAAAAAACAPwkAAAAAAIA/AQAAAAAAAAAAAQAAAMAAAADAAQAAAAAAAAAAggAAukIDgAAAYAkAAAAAAAAA + AACAPwkAAAAAAAAAiAAAvEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAvkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAwEIDgAAAYAgAAAAAAAAAAACAPwgA + AAAAAAAAhwAAwkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAxEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAxkIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAA + hwAAyEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAykIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAzEIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAAzkID + gAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA0EIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA0kIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA1EIDgAAAYAgA + AAAAAAAAAACAPwgAAAAAAAAAhwAA1kIDgAAAYAgAAAAAAAAAAACAPwgAAAAAAAAAhwAA2EIDgAAAYAQAAAAAAAAAAACAPwQAAAAAAAAAg81B6T0DgAAAQBQAAAAAAKhB + AADIQQAA8EEAAPhBAAAQQgAAIEIAAChCAAA4QgAAPEIAAEhCAABYQgAAcEIAAIJCAACGQgAAjkIAAJpCAACmQgAAsEIAAMBCAADYQhQAAABr3IY/BGrKhT8nOYg/qDCG + Pyc5iD8nOYg/A6gwhj/lrYI/JzmIPyc5iD8CasqFP3fYlT932JU/gvB6jD8EHL55P7PEYD+VTjs/s8TgPgEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMz + P2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAAA8EIAAAAADgAAAAAAAD/NzMw9zcxM + PgAAAD8CAAAAAAAAAAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAACAPwQAAAACAAAAAwAAAAQAAAABAAAAAQAAAAAAgD8AAIA/AQAAAAEAAAABAAAAAQAAAAEAAAAA + AIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAwAAAAcAAAABAAAAAAAAAA4AAAAQ + AAAAAAAAAI/C9T0AAIA/AAAAAAAAAL8AAMBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAA + AEhDAAAAAM3MTD8AAABAAAB6RAAAAAAAAIA/AACAPwAAekUAAABAMzMzPwAAQEAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAADgAAABAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAIA/AABAwQAAAAADAAAAAwAAAAAAAAABAAAABQAAAAQAAAAB + AAAAAwAAALByaD6PwnU+MQiMPwR+CMH0ATPBCgAAAAUAAAAAAIC/AABQwQAAwMIAAMDCAADAwgUAAAAAAAAAI9v5PAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAj8J1P83MTD5okY0/BH4IQfQBM8EKAAAABQAAAAAAUMEAAIC/AADAwgAAwMIAAMDCBQAAAI/C9Tymm8Q6AAAAAAAAAAAA + AAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAADD9UhAPQoXQJqZGUAAALRCAACgwQAAAAAFAAAAAADAwAAAcMIAAHDCAABwwgAAcMIF + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAArXY0DsUbg/7FEYQAAAtEIAAKDBAAAAAAUAAABj + J27CAADAwAAAcMIAAHDCAABwwgUAAAAAAAAANIA3OgAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAuB7FPz0KVz+u + R+E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAB + AAAAAAAAAAEAAAADAAAAAABAP83MTL4AAMA/AACQQQAAAAAAAAAAAAAAAAEAAAAAAAAAEThWOPAwAABQclZLJgAAAlhGVFDgMAAACgAAADIwMjMvMDYvMDYXAAAAUGlh + bm90ZXEgOC4wLjAvMjAyMjExMTduloqil6Uld4rUqJakEoGPZGxsYZ8wAAB0ZHRNlgQAAAcAAABVNCBUYWxsAAAAAAcAAABNb2RhcnR0cAAAAFRhbGxlciBzaXplIG1v + ZGVsOiBzdHJpbmdzIGFyZSBsb25nZXIgYW5kIGhhdmUgYSBsb3dlciBpbmhhcm1vbmljaXR5LCBwYXJ0aWN1bGFybHkgbm90aWNlYWJsZSBpbiB0aGUgYmFzcyByYW5n + ZS4AAAAARAEAAAcAAABVNCBUYWxsAAAAAO0DAABHUlVVAAAABgAAAAEDwE1UcmsAAAPXAP8CDyBCbGF6IEp1cmpldmNpYwCwQACGYZA0LEewQH+EWZBMTgxEVxc7IHiA + TAAARACDa5BLUBlCTjCAOwAxNAAjSwAAQgCDMLBAAIEckDgwDEdUADE2C0BMAD9EbbBAf0iAQAAARwAkPwAjOAAxMQCDPLBAAG2QREoAO0IALTwNPUoJNDZtsEB/YYBE + AEc9AAA0ADs7AEotAIQ0sEAAC5BAVSUqPhc7VAsxRD05Skg4NkmwQH+CK4BAADsxABo7ACMqAA05ADs4AIR8kD1MJbBAACSQJj4LOU4XLTwkNkolNECDVLBAf4YkgC0A + JDYAJTQAXyYAADkACz0AhHGQQFkMsEAADZA9SgsjMjAvPA05Rhc4OIIgsEB/h3GAIwAAOABgOQAXLwALPQCBKkAAilaQIygZREoLQUQxsEAAJZA8QAszKkk5PoI2sEB/ + hmGAIwBIMwCBYzkADTwAVEQAgl1BAI87kCgXDEU8C0I8MC8mJLBAAACQPkAaNygwOziDMLBAf4VngCgAgRBCAC8vACVFACU3ADA7AIFXPgCEBZBCMDstEi8/NCWwQAA8 + kD0yDDcabTokggawQH+FRYA6AIEcPQCBBD8AMDcAbEIAeJA9CFSAPQANkD4ogQKAPgA+LQAAkD8+UrBAAD6APwA7kEA+hnewQH+CFZAmSINggCYAL5AtLIFagEAAgQMt + AC+QNR6BKjsoF4A1AIEoOwA8kD4geYA+ACKQOyiBEYA7AA2QPDBHgDwAO5A+Nj6APgB3kEBGLzIcYUE0GYBAADtBAFSQOSQMQxqBS4BDACWQQSqBS4BBAFaQQxxrgEMA + AJBBIIEQgEEAgTSQQSZIgEEAgQ+QQCQ9gEAAJJBBJj2AQQBSkEMTMEUYGYBDADFFAC+QR0qBEEg0DIBHAElIADuQSjR4TEAZgEoAX5BILiaATABFSAAmkEpGX0dEMIBK + AHiQSDANgEcAMEgAIpBKPkmASgBIkExSgQNNSBmATABhTQAjkE8iSIBPABeQUSY9gFEAGJBTSnlUSiKAUwCBBVQAAJBWSIEPWEYZgFYAbJBUPBeAWABtVAAMkFZMbFMw + JIBWAGuQVEQmgFMARlQAMJBWPkmAVgBVkFhOdllAGYBYAFRZAD2QWx5SgFsADZBdLj2AXQBUkF8wRoBfABmQYEZhgGAAUpBiRoE1gGIAMJBkNFSAZAA8kGAya4BgAIEq + kGI8gWSAYgA8kGdAgUCAZwBUkGIugWKAYgAlkGQohFiAZACGeJBiGIEcZBaBA2s4lyCAawBWYgAkZABHOQAvMgCPDLBAAAD/LwBQcnNV+SsAAEcAAAAFAAAAc3UxMTgA + AAAACgAAAFVwcmlnaHQgVTQAAAAAAAAAAHByb21ZAAAAAQAAAAEAAAABAAAABQAAAHN1MTE4AAAAAAoAAABVcHJpZ2h0IFU0AACAPwYAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAA + AAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAD + gAAAYFgAAAAAAKhBAACAP1gAAAAyswpAV3CV3D+CBwxAcKXHP1QVvD/y0R9AFHj/P1QswT8Keh9AQFLzP3JqIkAwbX8/KH10P7ihnz+65wdAwB8fQMjpYz/g1Og/OELc + P5ImJ0BShB1A/F0XQFpFCEBY1aQ/8kstQKRqxT/iLgNAroAJQHQL3T9QS20/slQqQHjPzT/AZ4E+qBIeP8A7ED6AyeM9QChSPmCayj5Qn4I/QFq1P+DwkD/gqvE+zHpI + QDCEyD9oa0I/YNLdPwB8Mr0UPNA/QAW8PwBD6r3gFY0/9C6OP7Tp2D/w91s/xPScPwBanj8Ag6k/kgVRQDpYTkCigj1AQGTBP1Sf8D+IdpA/dAzSPwBY/j4Ui/w/gGQz + vqSoBUA8MPU/MA2SP2Tghj+OZgZAeFGFP0D/qj/A9Us+IA3SvsCbkD6ApGq+2Kd0PzjliD/8ZvU/0FYDP/CaIb8ATak90P+PP5gNID/ADBY/iFZPQAMAAAABAAAAAAAA + AAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAAA/AAAAAAEAAAAAAAAA/lL9PQOAAABgWAAAAAAAqEEAAIA/WAAAAKTY + lT9XscmcP0mjjD9OKZ8/i5WQP5N9kT/9tJQ/SD2dP9W+nz8bD5g/E8GRP1IRkj88z58//nCiP8qFnD9inKA/LRqSPyvJjj+Muo0/tqKUP+ahkj/sDZQ/97WKPx7Ykj81 + D40//72IP7/Gjz+Xu44/MeqJPziCiz9inYc/1oWCP1vEez+aknM/ZWF+P5DAhj9fJYE/NDN+P4Dygz/rcYU/TRV3PwzPiz+ZroM/EtWEP3JOjz8EZJk/yCWEPwoWkz/Y + yow/paiQPztRjT9cY5Q/HlyTP3WKmT9sBJc/ImWkP/THmz8Rn5c/b/GeP7FChz/D1Ys/P2GRP2Ipgz+6EoE/2K59P+0wez9v3nw/rhaAPxFmbj/K5m0/ivpfPy+vYz8i + xmE/n2tbP6JJRD9861A/jONAPwlnRT/x2UM/xGspP7i8Kj+gCSk/fzgnPyMtKT92NRk/pasPP13SCj/5VAc/AQAAAAAAAAAAAAAAAACgwAAAoEAAALZCAAAAAAOAAABQ + AABwQgEAAAAAAAAAAAAAAAAAQEAAAABAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAA + AIA/gYAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAgAAAAD8AAIA/A4AAAFAAAHBCAQAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAAA64JFAV3wlQj+cC2FAaKWQvjrZF0Dg + 1lVAwITWQJq/pkAfVgXBwBCGQMCE1kAolppAwITWQLAebEBGxaXA85bpwFi0ykAT8XvAhBviP7AebEB0FvLAM+qVQIdQDMHMESJAfOWVwBLTEUA8KbVA5F6Zvzyvuj8l + 6pVAWLTKQL6cpUCwHmxANaX3v5iQxECzAAvAGk62wJiQxEC+loZASB7AwHSHpkCA/ZQ/vsI7wDSPY0BKzr5AX72ev3R5w0D6Bw7AL+PdwIz2ycAcYrVAeuUxQIz2ycDA + hNZAcF/5PyoqtkCZ+IbA/iErQMwYa0BKzr5AVAZSQFi0ykBsLaPAEBdPQKj/YL8z6pVAcUkDwawQhkDOzTVAmOD0P/4hK0C4nKVAvn+ywPjxyEAv493APCm1QDLKS0C8 + 56LAL+PdwEqGaMAyyktAXifWvyAYtr+YkMRA6KI4wNIZdMD7xJe/sZY2wQOAAABgWAAAAAAAqEEAAIA/WAAAAB6F3D9XKi/gP4lm8D/LGdM/x/nuPxOX2D9SI9w/2czE + PyVN5T9HNrw/bSrUP+b3wz9zXa0/5vfDP2KJsD8QpKA/QZeWP4rtqz9T6L8/w9S6P6MmuT+SoKE/NM2WP97qlz95YZ8/+0SqP/rErj8Ryqk/lguXP0dfoz9lY48/52Sp + P9uhjj/qCJ8/WAeXP41Fhz8Lrp4/dyCUPz8Pqj/lK4g/LkaWP9nbmD+JEJg/oJyLP+cFjz/aLZE/jNqEP2x9oT95Y5A/1YGMP0Wfoj+FBZU/0LaWP/S2kD9quJk/1UyY + P27ukz9mFX4/hVylP1AplD9L14Q/PkmYP0PBhT9fdJ0/2wiIP9Kqgz9UGY0/lpWFP2FCgz+qy34//lJnP2ZXiD9kGIs/AxOHP31MkD8EQWk/1fKDPx1QWT+42Xg/Vf5t + P+XGhD8Rj4I/LflyP5fieT+0e3M/mHNbP5Q7aD+qMYc/AgAAAAEAAABVAAAARXF1YWwgdGVtcGVyYW1lbnQKVGhlIHN0YW5kYXJkIHR1bmluZywgd2hlcmUgdGhlIG9j + dGF2ZSBpcyBkaXZpZGVkIGluIDEyIGVxdWFsIHN0ZXBzLgwAAAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQBAAAAAAAAAAQA + AAAAANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1bHQAAAAAAAAAAAIAAAADgAAAYFgAAAAAAKhBAACAP1gAAACojcvAMPjMp8A4jpvAMA/CwDgErMAwva/A + uJHEwMAY3MAIy5/ASGaowLgKi8AIZ7/ASLOZwGgPocCYYZLAYPsRwHATEMBg42HAkIMiwGAw4L+Agzm/QHUmvwDg8z0AumE/AKO4vgClAj8AdFg/gJ3CvoD9Mb8AYsm+ + AKwPvgCNhT6AprW+4Cmfv4C+X7+gmZK/IHSVPwDpSr6AsZw/wCUwPwA40T0AluM9QNpvv8Dcar+AJTC/wNzqv8A3uL+AcOg+AAAAAAAAAAAmYDmxPwAkYT9AVNo/ANSQ + vuDJkj/A3Oo/wIOfP4Adcr8APYu/QImmPwCxFj8ANIi9AJ8tvwAC2j8gkbC/wAPzvyCWlr8Afhg+QAGovwDoi71gQdI/wLSKP8Dc6r8AYA0+gP2aPzAcBEAAYFK8wNzq + v8Dc6j9gK4E/ANIjvqC4qL+g9Zu/AENSvkBqFb/AEqc/QGMnQEC9I78AAAAAAAAAAAAAgD8BAAAAAQAAAAEAAAAYAAAAA4AAAEgBAAAAAACAPwAAAACamZk/A4AAAGBY + AAAAAACoQQAAgD9YAAAAOVfWP1dZU+Y/WA/4Pxpi9j8sjwFAMt4OQG0CE0CjiBJAOr4VQOpaG0B0gxtADloVQEHRFEBnhBdAtC8UQGJFC0AYJxFAt4QJQNRpA0Ah8fk/ + mMHyP5I68T+TSe0/Cr/nP23B4z+tEew/dJLbP+s92D8WP9M/3gbPP+PyvT++Xa8/o5i0PyIouT/pbrc/WN6xP5FHqj/oraU/Gm+mP0JEnD+/kJw/BUKeP7frnz8RmZU/ + Z3+bPx2BpT+hZY8/086XPyuEjj+xZJU/jQScPyMJjz+lAqc/hG2XPzAInT92Yps/kUiZP+konD+xbo0/43KVP6CAjD/AJow/oZCSP6L7jj8vZZs/RbuQPzOTkD/3554/ + A4iXP7oAjz/XVpQ/7M6WPwy9kD/EcIw/eMaNP4BgmT++/ZE/Y0mjPxt9mT9+Fak/ApyjPy1lsz+oCa4/26S3PyWXvD8Dtrc/n3u+P0RXwD8DgAAAYFgAAAAAAKhBAACA + P1gAAAA5V9Y/V1lT5j9YD/g/GmL2PyyPAUAy3g5AbQITQKOIEkA6vhVA6lobQHSDG0AOWhVAQdEUQGeEF0C0LxRAYkULQBgnEUC3hAlA1GkDQCHx+T+YwfI/kjrxP5NJ + 7T8Kv+c/bcHjP60R7D90kts/6z3YPxY/0z/eBs8/4/K9P75drz+jmLQ/Iii5P+lutz9Y3rE/kUeqP+itpT8ab6Y/QkScP7+QnD8FQp4/t+ufPxGZlT9nf5s/HYGlP6Fl + jz/Tzpc/K4SOP7FklT+NBJw/IwmPP6UCpz+EbZc/MAidP3Zimz+RSJk/6SicP7FujT/jcpU/oICMP8AmjD+hkJI/ovuOPy9lmz9Fu5A/M5OQP/fnnj8DiJc/ugCPP9dW + lD/szpY/DL2QP8RwjD94xo0/gGCZP779kT9jSaM/G32ZP34VqT8CnKM/LWWzP6gJrj/bpLc/JZe8PwO2tz+fe74/RFfAPwOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpE + AQAAAAAAgD8AAAAAA4AAAFAAAHBCAQAAAAAAAAAAAIA/A4AAAFAAAHBCAQAAAAAAAAABAAAAAAAAAAAAAADNzMy9zczMPQEAAAAAAAAAAAAAAAAAAL8AAAA/AACAPwOA + AABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAANEIDgAAA + YAIAAAAAAKhBAAB8QgIAAAAAAAAAgQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAAAQAAAAAAHBCAAB6RAAA+kQAoIxFAQAAAAMAAAACAAAABAAA + AAQAAAAAAABAAwAAgD8AAABAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAm + k0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAAAAAAAAAAAAAAAAA/uB6lPwOAAABgAgAAAAAAqEEAAK5C + AgAAAAAAgD+BuB6lPwAAAD8DgAAASAEAAAAAAIA/AQAAAAEAAAABAAAACAAAAERyeSByb29tBQAAAHJvb20wAACAP8zMEEEAAIA/w/UoPwAAAAAHt9g8AAAAAAAAAAAA + AAAAzcxMPwAAqkMAAMA/AACAPwEAAAAGAAAA2IuRPwAAgD8DgAAAYFgAAAAAAKhBAACAP1gAAABDBYA/hvJifz+Ez+VwP0WDZnU/ZeRnP3albj8LpGU/GmVsPywmcz92 + pW4/g2Z1PzJXdD82mW4/8ld0P9VpZD/Ez30/eWaVP1l5hj+Yp1o/sWuIPx9sgD/VaWQ/P55/P/g8gz9Dt2c/Fct4P/rTcT9L+YE/V85yPzj7eD/Nq4E/gM+FP54jkT8K + 7os/wUh1PxLLnT9Dt2c/D+JNPzl/jD/+15o/U1CMPxkfkj/8Rog/nwxrP7NXez/lvoc/W8KDPyQthz929oE/L5mvP1vCgz8K7os/otiCP6cqhz+z+YE/GIF8PxMCfz97 + ZIU/5DJ6PxkViD95ZpU/ndp3P6vVdz/D6nA/nupwPxprbj8FAIA/C6RlP2XkZz/AJGo/VCNhP69jYz+vY2M/BWXkZz+vY2M/ZeRnP1QjYT9l5Gc/A4AAAGBYAAAAAACo + QQAAgD9YAAAAhcSPP1el91c/w41bP2IjjT+VP4U/Vps9P+6Fkj9w8r8/bISEP65thz+p4Zc/5L6AP/TgmD+96DQ/FMixP0qFMj8NxoE/sMaYP1a6kD/nw4c/dhuiP5i0 + ij+cz3o/xrlpP8CMlz/CJi4/uWmxP3GCdT9Eqpk/3YhyP0p2Rj9+wIc/ybpLPz9wXz/h3Ys/1pKVP3rvUz+J/DQ/ZUNeP3K3gD95pFg/mUouP0vDdT94pFg/4xaDPzrz + jT/HDWQ/hsCqPyoNkT8iJmI/coqkPzMVWT9RZog/bb0nPxGEVj9Iv3E/HKJxPxUcXz9Ga1k/sOmPP3yAfj+Oj4Y/Ge+WP2CDkj/9bkU/mnRsP7VXsj8eRlU/VH1tPyWp + Yz8HkDo/GseJP5lsiz/2mnU/Q/R8PxiRPD93lL8/gRiCP1uxNj9WQYg/EWqjP5+tij86eZs/SNx+Pz8fpj+LKTg/6MZuP/yNjj8AAAAAA4AAAGBYAAAAAACoQQAAgD9Y + AAAAeCMXPlfIeDU+0CEAvcDZUj0Y0oO9QNTavWDpyD0g5c09EM/dPUB78j0AvE89wEkNPQClZjyA7/s9YJeNvFAHvj2AL7q8QI7jPYDuYjwA0hI9IFqzPUD9Rrx4kBM+ + AJDnvKBWVD0QzRW9wLbxPDiO472AHx0+cCCIPYDkYj1A2L28gPKYvIhVnr1gx8a8oEKMvYCvCDwcuQa+QIbCPKgGAj5gTo89+LvAvWAn2j1A+0w9CETPvbA2pr1ghAS+ + INUpPbDejj0AAIC+MPwMveAEx70AQwo8eHaHvYDkDTxAKw4+kOOWPRArG75grQm9wNa1PHC0X70AueM8cCmlPcAYgjzw7fC9gOM9PPBDIL1Gu4e+CEXjvVCElT3gwuK8 + QO9mvLxrAb4QPLe9AIMNvHhahb3gC1q9oPjFPbAEkT3gpC4+WEzovUCrrDyYPJa9ACftPPjNIj6wa9c9sMjTPdDWlj0BAAAAAAAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/ + AACAPwAAgD8DgAAAYFgAAAAAAKhBAACAP1gAAACJNrQ/gWW5tj9V9aehP0ZjtD8E3Z0/b7idPyivoT8C650/bR+hP+/wmT/d3J4/9KifP6Uqjz/zKJM/jrWYP8ilkz/i + xZQ/7heXP1u6mD9lBI8/G96UP8WziT9Kr44/LDqQPydHmD/4PpQ/YRiIP65EjD8jeYw/wISJP8Cvhz8Mhoo/ZaSCP7zjiD9Y7o0/tjOHPxuzgz9CFp8/NuKMP1bEjT+m + Q4E/9nKlPxoehj+B1Ys/SY+ZP2O9gz8OZ4c/orOKP8+Nij/tEYE/RKKHP/Angj9dfYU/0w+LP0RPmD9ugIM/cTVsP8v8gT/qyHA/w5p2P5mPeD9Sfm0/1AxrP2H/dT+R + rmo/JvV/P1i7bj8/vnM/wXWEP9hRcj/mdZc/3CCHPzIWfj9a/X4/Ual/Pz/jkD8CVIs/+G6BP6n1jD+a1H0/71WGPxLcgT9R9YE/NsGBP46vcT9poXU/mXqDPwOAAABg + WAAAAAAAqEEAAIA/WAAAAK1HYT8n9ihcPylcTz/2KFw/m5lZPylcTz/NzEw/pXA9P1yPQj+F61E/4XpUP/YoXD9b+Wg/PRhfPxiOZT/Zt2k/aettPwprYT/Nl2U/bDls + P6dbdT90528/aMJxP8Mwaz8Od24/H7V2P0oGej9b43U/975sP4MOfz9S5Xo/+WGAP8WVfD8Q9X8/IrF2Pz7rbz8mFIU/ELKBP3olfz+uR4E/rkeBPwgzM3M/SOF6P1yP + gj8AAIA/XI+CP0jhej+jcH0/rkeBP65HgT8HXI+CPwnXgz+uR4E/o3B9P+xReD98FG4/rkeBP65HgT8eAACAP5DCdT8J14M/kMJ1P+xReD+5HoU/XI+CPxOuhz+jcH0/ + Z2aGP6NwfT/sUXg/uR6FPwAAgD8zM3M/AACAPzMzcz9cj4I/MzNzP7kehT+uR4E/XI+CPwnXgz+uR4E/MzNzPwAAgD/sUXg/CdeDP5DCdT9I4Xo/A4AAAGBYAAAAAACo + QQAAgD9YAAAAR+GaPz/4Q5s/jKiSP4ZVpj/s2Jk/9k+sP+kVoT+B67Q/DCmwPypHoj+m46c/V++ZPxwDoD/MH5k/b9CiP+hxoz/Jm58/k8KeP7YKlT/mAqA/U9aXP+p6 + mD8fHZk/maCXP9Amlj9Atoo/Nf6OP6n9lT+kLYw/s+6OP9FniT/+wYo/mgaIP6X7gz/7+Yc/052GP66Thj/4BoA/ti2FPwrXgz/Xo3A/CteDP2Zmhj9H4Xo/r0eBPwAA + gD9bj4I/tx6FP6RwfT+vR4E/cj2KP3oUbj8K14M/ZmaGPzIzcz9bj4I/16NwP47CdT/rUXg/tx6FPwAAgD8K14M/R+F6P69HgT+vR4E/AwAAgD+3HoU/R+F6P0fhej8L + CteDP69HgT8AAIA/61F4PwAAgD+kcH0/ZmaGP6RwfT8yM3M/16NwP69HgT+vR4E/AqRwfT+vR4E/r0eBPwQAAIA/W4+CP7cehT9bj4I/AACAPwAAgD8HAAAAzcxMP5qZ + mT57FG4/zczMPwAAgD4IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAYFgAAAAAAKhBAACAP1gAAAAzM3M/AwAAcD/NzFw/AABwPwAAcD8QNDNT + P2dmZj8AAGA/mplZPzQzUz+ZmTk/Z2ZmPwAAYD9nZlY/AAAwPzMzIz/NzFw///9PP2dmVj8AAEA/02hEP9NoRD8LmZmBPzMzIz8AAGA/MzNzP5mZgT+amYk/NDNTP5qZ + iT/NzGw/zcx8P2Zmhj9mZoY/AWZmlj9mZpY/DwAAkD/NzGw/v9+PP83MfD8AAHA/ZmZ2P5mZOT/NzGw/mZmBPwAAID+amYk/XS6XPzMzYz9mZo4/GAyOPxgMjj8kMzNj + PwAAiD8AAHA/mZmBP83MjD80M1M/Z2ZmP5qZWT8zM4s/mpmJP2Zmlj8AAJg/ZmaePwAAiD/NzKw/AACAP5mZgT9TqZw/MzOTP2dmZj+amZE/AACIPzMzkz8Ph5M/ymSK + Pw+Hkz9mZpY/mpmRP5mZeT+ZmaE/Z2auPzMzmz9mZpY/zcycP///pz/NzGw/A4AAAGBYAAAAAACoQQAAgD9YAAAAQopKP1fjiaE/F6F1P/HfYz/5e40/u6R9P/uchT8x + 9IA/S2NrP1PvmD/Jskk/czRdP1PNdj9T+40/GMZjPwf1fD/VT4o/i2tjP/YSmj9YEnM/rbhWPwudPT/xilc/QlJ6P9SMZz9BHX4/wvikP7vRNT8ax4I/8diqP0x2jT+b + IDo/EaeQP+Pbhz9XvoE/uk14Py/qgD8nulo/KoN2P4AudT9kK4I/1GVTP2lmiT+ru1o/Qgp3P1VVVT/Es0o/QXtJPxQ4kT/PY5w/XJqDP8QQgz8voT0/VRSTP72JaT+v + umg/S4uSPwxziT8bLo4/Vu+PPxC5iD/hUHM/PYSTP49FWT/jvJk/2v+HP1mqij9dxHo/YVGBP7YldD9l/ZM/2LRzP1S6YD9Tioc/IygsP/5fZj8Lxlc/V2ZOP9dEgD92 + jF0/t3hKPxtuSz+QYVk/EC5LPz6WiT/og4Q/5jNpPzlOHj8DgAAAYFgAAAAAAKhBAACAP1gAAAD/7YI/V0hEhD9csIU//TCHP55kiT+EuoM/1/mKP+VljD/kl4c/X6KJ + PwXBhz+lwo4/Y++IP5aIiD/V2og/cL2NP3Keiz8UH40/V/KSP2DCij9TJI4/CIGQPzWZij93vY0/6EaKP4w3iz/slYk/O3CCP0w0gj+7v4U/dyOIP7pPhT+FSJE//bKL + P39bij88r44/qmGFP66oij82h4U/hT18P0ivhT+44oY/OuiKP3hniD8CWZE/hiuQP+Wtiz8Ma4U/kW2VP9nRkT+fO5A/CUmJP+mUiz/AOIw/DXePP2RAlj/SaJY/3eWN + P3CnjT+vNo4/LXqSP51RlT+EA5I/Id6RPxxekD8T2pc/lmySP1NmkT8Re5Q/IlKXP3n+kj8tp5Q/mKyYPwmClD+33ZY/sceSP1salz8M15E/w2OcP1xnlj+3GJA/ycKV + P7I3kj/+2o8/eQCWP+/rlT9R6o4/2DqXPwOAAABgWAAAAAAAqEEAAIA/WAAAACMLgj825TeCP0QZgT/6SH4/qD6BP8IzgT97goI/Ql+EP2bDhD+O1II/+mKBP5mdez82 + f3s/hCeBPwGjgj+RdYI/CpSCP1rCgj/p/YE/9hKCPwZ2gj9Fl4I/vwWDP5DYgj9sIoI/z/OCP56Qgj9HMYI/h4eDP8TDgz8PD4M/DJqBP8sjgT+iZoA/XAuDPy9kgD9J + tYI/zzGFPwEFhD/j84I/Vy+DPwIAhD9jOoM/t/eCP1Krgz8z8YI/RReEPyGhgz+ts4Y/ynuEPwQshj/qy4c/acGHP9/Khj85joc/OY6HPwOUHIY/OY6HP8dxhj/HcYY/ + HAAAhz8J7Yc/xXeIP/K3hj8qcYc/qMqHP7/biD/aS4g/QnuIP6uqiD8T2og/EXaLP8E8iT9HcYo/n5uKP0FXiT9+LIc/CkKGP4GGhz8Yv4g/pmWIP/wQij8KkIo/WlaI + P5KNhz9gEIc/hg+IPwvfij8BAAAAMQAAAAAAEEIDgAAAYJIAAAAAAAAAAACAP5IAAAAZwIFABYG9GkA38aw/3IdPP4ZaCj8AAAAAAAAAAP8AAAAAiwAAFEIDgAAAYIYA + AAAAAAAAAACAP4YAAAAu4G1ABWHYDUDIh54/ijw+P/al/T4AAAAAAAAAAP8AABhCA4AAAGCAAAAAAAAAAAAAgD+AAAAAKkBYQAVB8wBAWB6QPzfxLD/fluY+AAAAAAAA + AAD5AAAcQgOAAABgfQAAAAAAAAAAAIA/fQAAACagQkAFQhzoP+m0gT/lpRs/yYfPPgAAAAAAAAAA9gAAIEIDgAAAYHkAAAAAAAAAAACAP3kAAAAhAC1ABQJSzj/zlmY/ + k1oKP7J4uD4AAAAAAAAAAPIAACRCA4AAAGB3AAAAAAAAAAAAgD93AAAAHWAXQAXBh7Q/FMRJP4Ee8j6caaE+AAAAAAAAAADwAAAoQgOAAABgdAAAAAAAAAAAAIA/dAAA + ABjAAUAFgL2aPzbxLD/ch88+hlqKPgAAAAAAAAAAkrAcB8CBAAAAANgAACxCA4AAAGBqAAAAAAAAAAAAgD9qAAAAKEDYPwVA84A/WB4QPzjxrD7glmY+AAAAAAAAAACT + jQAYwAEAAAAAAAAAAIagY9TAAriqyr8AAAAAAAAAAMMAADBCA4AAAGBpAAAAAAAAAAAAgD9pAAAAIACtPwUAUk4/8pbmPpNaij6zeDg+AAAAAAAAAACcRZVbwAEAAAAA + AAAAAMMAADRCA4AAAGBpAAAAAAAAAAAAgD9pAAAAhy7FPwXkx1A/FB6qPhDPMz6Qve89AAAAAAAAAACbANREO4EAAAAAxAAAOEIDgAAAYGAAAAAAAAAAAACAP2AAAAAe + AC0/Bf5Rzj7wlmY+kVoKPrB4uD0AAAAAAAAAAJuz5CjAgQAAAAC7AAA8QgOAAABgXwAAAAAAAAAAAIA/XwAAAB8ArT4F/lFOPvCW5j2RWoo9sHg4PQAAAAAAAAAAm7Ac + B8ABAAAAAAAAAAC6AABAQgOAAABgWQAAAAAAAAAAAIA/WQAAAAAAAADYAABEQgOAAABgVQAAAAAAAAAAAIA/VQAAAAAAAADUAABIQgOAAABgUgAAAAAAAAAAAIA/UgAA + AAAAAADRAABMQgOAAABgTAAAAAAAAAAAAIA/TAAAAAAAAADLAABQQgOAAABgSgAAAAAAAAAAAIA/SgAAAAAAAADJAABUQgOAAABgRgAAAAAAAAAAAIA/RgAAAHpOgD8D + AAAAAD31cMAAAAAAAAAAAAlCM7LAAAAAABxSP0AXqMnAMIw3PkIzssDwVt8/nJ+mwAAAAAAAAAAAgn9FCsEBAAAAAAAAAACEuJvewAEAAAAAAAAAAKsAAFhCA4AAAGBE + AAAAAAAAAAAAgD9EAAAAAAAAAMMAAFxCA4AAAGBCAAAAAAAAAAAAgD9CAAAAAAAAAMEAAGBCA4AAAGA7AAAAAAAAAAAAgD87AAAAAAAAAIowplLAAQAAAAAAAAAArQAA + ZEIDgAAAYDcAAAAAAAAAAACAPzcAAAAAAAAAtgAAaEIDgAAAYDUAAAAAAAAAAACAPzUAAAAAAAAAtAAAbEIDgAAAYDEAAAAAAAAAAACAPzEAAAAAAAAAsAAAcEIDgAAA + YC4AAAAAAAAAAACAPy4AAAAAAAAArQAAdEIDgAAAYCsAAAAAAAAAAACAPysAAAAAAAAAqgAAeEIDgAAAYCgAAAAAAAAAAACAPygAAAAAAAAApwAAfEIDgAAAYCcAAAAA + AAAAAACAPycAAAAAAAAApgAAgEIDgAAAYCYAAAAAAAAAAACAPyYAAAAAAAAApQAAgkIDgAAAYCQAAAAAAAAAAACAPyQAAAAAAAAAowAAhEIDgAAAYCMAAAAAAAAAAACA + PyMAAAAAAAAAogAAhkIDgAAAYCIAAAAAAAAAAACAPyIAAAAAAAAAoQAAiEIDgAAAYCAAAAAAAAAAAACAPyAAAAAAAAAAnwAAikIDgAAAYB4AAAAAAAAAAACAPx4AAAAA + AAAAhE7S9z8E3M8OP9pcpr/TZAHA+aIKwPmiCsAF02QBwA5Vy7+64JO/r+ATvwAAAAAAAAAAjQAAjEIDgAAAYB4AAAAAAAAAAACAPx4AAAAAAAAAnQAAjkIDgAAAYB8A + AAAAAAAAAACAPx8AAAAAAAAAngAAkEIDgAAAYB4AAAAAAAAAAACAPx4AAAAAAAAAnQAAkkIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAAAAlwAAlEIDgAAAYBkAAAAAAAAA + AACAPxkAAAAAAAAAmAAAlkIDgAAAYBkAAAAAAAAAAACAPxkAAAAAAAAAmAAAmEIDgAAAYBgAAAAAAAAAAACAPxgAAAAAAAAAlwAAmkIDgAAAYBMAAAAAAAAAAACAPxMA + AAAAAAAAkgAAnEIDgAAAYBIAAAAAAAAAAACAPxIAAAAAAAAAkQAAnkIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAAAAjwAAoEIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAAAA + jwAAokIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAAAAjwAApEIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAAAAjwAApkIDgAAAYBAAAAAAAAAAAACAPxAAAAAAAAAAjwAAqEID + gAAAYA8AAAAAAAAAAACAPw8AAAAAAAAAjoDp7D0DgAAAQAoAAAAAAKhBAAAAQgAABEIAAEhCAABMQgAAUEIAAFRCAACYQgAAmkIAANhCCgAAAAeHlT8EsJeRPwKfmD8a + UIo/vON1P7zjdT+BZxKeP4H2TN0+AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMz + P2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAAAAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAA + AIA/BAAAAAIAAAAIAAAABAAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACAQAAAtEJGAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AA + AEgBAAAAAAAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAADAAAABwAAAAEAAAAAAAAADgAAABAAAAAAAAAAzcxMPgAAgD8AAAAAAAAAvwAAIEIAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAABAAAAAQAAAAAAgD8AAMDAAAAAAAMAAAADAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAAzczMPs3MDL8AAIA/AAAAAAAAAAACAAAABQAAAAAAAAAA + APDBAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAAAAMAAAAAAIA/zcwMvwAAgD8A + AAAAAAAAAAIAAAAFAAAAAADwwQAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAFJJnTkAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAA + AAAAAwAAAI/C9T89Cle/H4VrPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIF + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAMAAAApXE8/zczMvgAAgD8AAJBBAAAAAAAAAAAA + AAAAAQAAAAAAAAAROFY4yg8AAFByVksmAAACWEZUULoPAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8yMDIyMTExNxGmaOsky0PpDk9qpikaMDZkbGxh + eQ8AAHRkdE1FAgAAFAAAAFZpYnJhcGhvbmUgVi1CIFRyZW1vAAAAAAcAAABNb2RhcnR0LAAAAFRoZSBWLUIgVmlicmFwaG9uZSB3aXRoIGl0cyB0eXBpY2FsIHRyZW1v + bG8uAAAAAEQBAAAUAAAAVmlicmFwaG9uZSBWLUIgVHJlbW8AAAAAxgEAAEdSVVUAAAAGAAAAAQPATVRyawAAAbAA/wIPIEJsYXogSnVyamV2Y2ljAIBGABuQQTuDIYBB + AEc9ABqQP0QqgDoAgUU/AIFBkEhQhEmASAAOsEAAVpBGRwArPYM9sEB/YYArAGCQNS+DTIA1AIEAkDohgzw9LCqARgCEPJBEUYIJgEQARJBGOYIlgD0AVDoARUYAY5BE + O4NLgEQAgRmQQzyBUbBAAIQ6kCQ3gUSwQH+CbIAkAA2QKzeELoArAFKQNDKESYA0AIELkDo1hhuAQwA5kEA2gRhDNX5IOIEmPTYATEyKEYBAADdDADhIAH1MAIIZPQBe + OgCCF5BLRoE3gEsAG7BAAACQTEqBXoBMAACQTVCNM7BAf4JNkCkyhTkwOBuAKQCDWjAAOZA3QoE0gE0Agwo3AACQODKHB4A4AB2QPC2FKIA8AA+QPzWFEEM1gyFERYM9 + S02EV09Fh2yATwANkFJJnmaAQwCCUT8AfFIAN0sAY0QAiBSwQACIAYAkQAApQAArQAAwQAA0QAA1QAA3QAA4QAA6QAA8QAA9QAA/QABAQABBQABDQABEQABGQABIQABL + QABMQABNQABPQABSQAD/LwBQcnNVJA0AAEcAAAACAAAAdjEAAAAADgAAAFZpYnJhcGhvbmUgVi1CAAAAAAAAAABwcm9tWgAAAAEAAAABAAAAAQAAAAIAAAB2MQAAAAAO + AAAAVmlicmFwaG9uZSBWLUIAAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAAAAAAAAAAAAAA/////29sb3MR + AAAAAQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA + AQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAAAAggMAAAABAAAAAAAAAAAAAAADAAAAAAAAAAAA + AAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAEAAAAAAAAAzczMPQOAAABgAgAAAAAAqEEAAIxCAgAAAAAAgD+BAQAAAAAAAAAAAAAA + AACgwAAAoEAAANhCAAAAAAOAAABQAABwQgEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIED + gAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAgAAAAD8AAIA/A4AAAFAAAHBCAQAAAAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAA + AAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAgAAAAEAAAB+AAAARmxhdCB0ZW1wZXJhbWVudApBbiBlcXVhbCB0ZW1wZXJhbWVudCB3aXRoIGhhcm1vbmljIHN0 + cmV0Y2hpbmc6IHNldHRpbmcgdGhlIG9jdGF2ZSBzdHJldGNoaW5nIHRvIDEgd2lsbCBkaXNhYmxlIGFsbCBzdHJldGNoaW5nDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpD + AAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAgAAAAOAAABgAgAA + AAAAqEEAAK5CAgAAAAAAAACBAAAAAAAAAACamZk/AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BA4AA + AGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAAAACAPwOAAABQAABwQgEAAAAA + AAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAA + rkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAHBCA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAEADAAAAAACoQQAAcEIAANhC + AwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAm + k0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAAAAAAAAAAAAAAAAA/mpkZPwOAAABgAgAAAAAAqEEAAK5C + AgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAoAAABTbWFsbCBIYWxsCwAAAG1lZGl1bWhhbGwxAABAQAAAoEEAAIA/ + zcxMPwAAAACPwvU8AAAAAAAAAAAAAAAAAAAAAAAAqkMAAAAAAACAPwEAAAAGAAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAA + AKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/AACAPwAAgD8DgAAAQAMAAAAA + AKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/AACAPwcAAAAAAIA/ + mpmZPjMzMz9mZqY/AACAPggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEA + AHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAAABAAAAAABwQgOAAABQAAAA + AAEAAAAAAAAAPc/zPgOAAABgAgAAAAAAVEIAAAxCAgAAAAAAgD+BAQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9m + ZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAAPwIAAAAAAAAAA4AAAEgBAAAA + AACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAAIAAAABwAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAACgQAAAAABUAAAAAAOA + AABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAIBAA4AAAEgBAAAAAAAAAAAAAAADAAAAAQAAAAAAAAAAAAAADgAAABAAAAAAAAAAAACgQAAAgEAAAAAAAACA + PwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAgD8AAEDBAAAAAAMAAAADAAAAAAAAAAEAAAAFAAAABAAAAAEAAAADAAAAmpmZPpqZmb4zM7M/AAAA + AAAAAAAAAAAABQAAAAAAAAAAAMDCAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAQAA + AAMAAABmZmY/mpmZvjMzsz8AAAAAAAAAAAAAAAAFAAAAAADAwgAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4VrPwAAtEIAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8AAAAAAAAAAAAAAAAFAAAAAABA + wQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAMAAACamRk/mpmZ + vjMzsz8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4yg8AAFByVksmAAACWEZUULoPAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3RlcSA4LjAuMC8yMDIyMTExNyRz + wWKXZ2RJPTkEno51DXFkbGxheQ8AAHRkdE1FAgAAFAAAAFZpYnJhcGhvbmUgVi1NIFRyZW1vAAAAAAcAAABNb2RhcnR0LAAAAFRoZSBWLU0gVmlicmFwaG9uZSB3aXRo + IGl0cyB0eXBpY2FsIHRyZW1vbG8uAAAAAEQBAAAUAAAAVmlicmFwaG9uZSBWLU0gVHJlbW8AAAAAxgEAAEdSVVUAAAAGAAAAAQPATVRyawAAAbAA/wIPIEJsYXogSnVy + amV2Y2ljAIBGABuQQTuDIYBBAEc9ABqQP0QqgDoAgUU/AIFBkEhQhEmASAAOsEAAVpBGRwArPYM9sEB/YYArAGCQNS+DTIA1AIEAkDohgzw9LCqARgCEPJBEUYIJgEQA + RJBGOYIlgD0AVDoARUYAY5BEO4NLgEQAgRmQQzyBUbBAAIQ6kCQ3gUSwQH+CbIAkAA2QKzeELoArAFKQNDKESYA0AIELkDo1hhuAQwA5kEA2gRhDNX5IOIEmPTYATEyK + EYBAADdDADhIAH1MAIIZPQBeOgCCF5BLRoE3gEsAG7BAAACQTEqBXoBMAACQTVCNM7BAf4JNkCkyhTkwOBuAKQCDWjAAOZA3QoE0gE0Agwo3AACQODKHB4A4AB2QPC2F + KIA8AA+QPzWFEEM1gyFERYM9S02EV09Fh2yATwANkFJJnmaAQwCCUT8AfFIAN0sAY0QAiBSwQACIAYAkQAApQAArQAAwQAA0QAA1QAA3QAA4QAA6QAA8QAA9QAA/QABA + QABBQABDQABEQABGQABIQABLQABMQABNQABPQABSQAD/LwBQcnNVJA0AAEcAAAACAAAAdjIAAAAADgAAAFZpYnJhcGhvbmUgVi1NAAAAAAAAAABwcm9tWgAAAAEAAAAB + AAAAAQAAAAIAAAB2MgAAAAAOAAAAVmlicmFwaG9uZSBWLU0AAIA/BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVEb/////AAAAAAAAAAAAAAAAAAAAAHJ5YWwQAAAAAgAA + AAAAAAAAAAAA/////29sb3MRAAAAAQAAAAEAAABGAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAA + AAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAAAAggMAAAABAAAA + AAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAEAAAAAAAAAbts2PgOAAABgAgAAAAAAqEEAAIxCAgAA + AAAAgD+BAQAAAAAAAAAAAAAAAACgwAAAoEAAANhCAAAAAAOAAABQAABwQgEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAACgwAAAoEAAAAAAA4AAAGACAAAA + AACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gYAAAAAAAAAA0gQAAAAAAAAAAAAAAAAAgAAAAD8AAIA/A4AAAFAAAHBCAQAAAAAAAAADgAAA + YAIAAAAAAKhBAACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAgAAAAEAAAB+AAAARmxhdCB0ZW1wZXJhbWVudApBbiBlcXVhbCB0ZW1wZXJhbWVu + dCB3aXRoIGhhcm1vbmljIHN0cmV0Y2hpbmc6IHNldHRpbmcgdGhlIG9jdGF2ZSBzdHJldGNoaW5nIHRvIDEgd2lsbCBkaXNhYmxlIGFsbCBzdHJldGNoaW5nDAAAAAAA + yEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAA + AAAAAAAAAgAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAAAAAAAAAACamZk/AQAAAAEAAAABAAAAGAAAAAOAAABIAQAAAAAAgD8AAAAAAACAPwOAAABgAgAAAAAA + qEEAAK5CAgAAAAAAgD+BA4AAAGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAUAAAekQBAAAAAAAAAAOAAABQAAB6RAEAAAAAAIA/AAAAAAOAAABQAABwQgEAAAAAAAAA + AACAPwOAAABQAABwQgEAAAAAAAAAAQAAAAAAAAAAAAAAzczMvc3MzD0BAAAAAAAAAAAAAAAAAAC/AAAAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IA + AAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAHBCA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AA + AEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAAAAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAA + AIA/gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEAAIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAM3MTD4AAAAAAAAAAAAAAAAAAAA/mpkZ + PwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAAYAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAoAAABTbWFsbCBIYWxsCwAAAG1lZGl1 + bWhhbGwxAABAQAAAoEEAAIA/zcxMPwAAAACPwvU8AAAAAAAAAAAAAAAAAAAAAAAAqkMAAAAAAACAPwEAAAAGAAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EID + AAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAAAAEAAAAAAAA/AAAAQAMAAAAAAIA/ + AACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACA + P4IAAIA/AACAPwcAAAAAAIA/zcxMPmZmZj/NzMw/zcycPwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAA + AIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEA + AAABAAAAAABwQgOAAABQAAAAAAEAAAAAAAAAuSPuPgOAAABgAgAAAAAAVEIAAAxCAgAAAAAAgD+BAQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/Zmam + PwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAADwQgAAAAAOAAAAAAAAP83MzD3NzEw+AAAA + PwIAAAAAAAAAA4AAAEgBAAAAAACAPwAAAAADgAAASAEAAAAAAIA/BAAAAAIAAAAIAAAABwAAAAEAAAABAAAAAACAPwAAgD8BAAAAAQAAAAEAAAABAAAAAQAAAAAAgD8A + AAAAAACgQAAAAABUAAAAAAOAAABIAQAAAAAAAAAAAAAAA4AAAEgBAAAAAAAAAAAAAAAAAIBAA4AAAEgBAAAAAAAAAAAAAAADAAAAAQAAAAAAAAAAAAAADgAAABAAAAAA + AAAAAACgQAAAgEAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAgD8AAEDBAAAAAAMAAAADAAAAAAAAAAEAAAAFAAAABAAAAAEAAAAD + AAAAmpmZPpqZGb8zM7M/AAAAAAAAAAAAAAAABQAAAAAAAAAAAMDCAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAQAAAAAAAAAEAAAAAQAAAAMAAABmZmY/mpkZvzMzsz8AAAAAAAAAAAAAAAAFAAAAAADAwgAAAAAAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAAI/C9T89Cle/H4VrPwAAtEIAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAMzMzv1yPwj6uR2E/AAAAAAAAAAAAAAAABQAAAAAAQMEA + AEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAC4HsU/PQpXP65H4T8A + AAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA + AAAAAQAAAAMAAACamRk/mpkZvzMzsz8AAJBBAAAAAAAAAAAAAAAAAQAAAAAAAAAROFY4LxMAAFByVksmAAACWEZUUB8TAAAKAAAAMjAyMy8wNi8wNhcAAABQaWFub3Rl + cSA4LjAuMC8yMDIyMTExN8Zd92J3HrDiYKjJNc4shpZkbGxh3hIAAHRkdE01BQAAFAAAAFN0ZWVsIERydW0gLSBuYXR1cmFsAAAAAAcAAABNb2RhcnR0XQAAAFRoZSBk + ZWZhdWx0IHByZXNldCBmb3IgdGhlIHRyYWRpdGlvbmFsIFRyaW5pZGFkIFN0ZWVsIERydW0sIGNsb3NlIHRvIHRoZSBvcmlnaW5hbCBpbnN0cnVtZW50LgAAAABEAQAA + FAAAAFN0ZWVsIERydW0gLSBuYXR1cmFsAAAAAIUEAABHUlVVAAAABgAAAAEDwE1UcmsAAARvAP8CCCBNb2RhcnR0AIArQByQJGkcPG2BCoA8QIERkEBagSKAQECBSZBD + V4EggENAgQqQSFQHgCRADZAtW4EbgEhAgiYtQIFjkCtfBkhtgSaASECBGpAoWIEEgCtAgSQoQBOQJC4MSFOBO4AkQClIQCyQKVM+R1GEeUNrgiokaw+AKUCCCENAR5BF + dSWAR0CDcCRAH0VAOpAhXxpBcIEXgEFAgyIhQDiQH2saQ1FhgENAgWOQQ01egENAgXeQQ1GBAoBDQIFRkEBoGiNrTIAfQBVAQIQjkEFrgQGAQUBoI0AykCZygmZDcIEj + gENAgQ4mQASQQVYYJGuBIIBBQIEDkEBWgS6AQECBIJA+WIEOgD5AgSuQPG4NgCRADJAkdYEegDxAikUkQHeQTHcQQzAjK2mBBoBMQINUkEhrEEAfBIBDQIETSEBMQEBL + kEM2ByhRF0x3ToArQFlMQGpDQIJYkEAtOUhogQKASEAHQEAAKECBF5BIWxEkUQdAMFyASEBTQECBHpBITwRAJ4EVgEhAL0BAACRAU5AdeBVFRQhNb3qATUCDdEVAJ5BB + HhJKdYEcgEpAgSiQRTMGIXUPgB1AYkFAgTiQTXuEOIBFQBohQCuQQS0GSncwJG0zgE1AO0pAJUFAg0UkQA6QSnEGH2sIQS6DU4BKQIEGQUAWkD4RIkdogRiAR0BuPkAe + kEpxFiNrG0EpI4AfQIIXSkCBb0FAQZA+LBRHa4FCgCNAA0dANz5AMpAmZ4JaR3aBSYBHQBomQEeQQCoaSHUUJGuBM4BIQA1AQFyQR1kAPi2BO4BHQD4+QHuQSFEAQC1f + gEBAVkhANyRAG5AoUR1BPw9KeIEfgEFAH0pAgiooQIEckC1WBENXHkx6gUOATEArLUAlQ0AAkEFGCStTDkpxgSKAQUBgSkA1kEhoAEA+DyhTEoArQF1IQDFAQIEHKEAQ + kEx1FiRWGkMugQqATECDU5BIbAdAIkGAQ0BSSEBHQEBfkChrI0M3Ckx5JYAkQH1MQHtDQIJkkEAtAEhndYBIQEhAQDooQD6QJFEQSFsLQClngEhAOUBAgROQSFATQClf + gCRAKUhAOkBAb5BNZxlFRAEdbW2ATUCEDEVABJBBJBFKdIErgEpAYx1AJZAhdQpFLoECgEFAgS+QTXqDbYAhQDZFQGiQJHIdSnUHQSw7gE1AWEpAQ0FAgxSQQS4AgCRA + IJBKcQsfdYNRgEpAakFAOJA+Ew9HZ4EdgEdAbj5ADpAjdRFKagqAH0AHkEEsgkmASkCBWkFAZZBHbg0+MIEygEdAAiNARz5AI5AmbYJ+R3eBXIBHQBomQCWQJHIuQCwG + SG6BPIBIQD5AQDSQPioFR1aBN4BHQD8+QEqQSFAIQCtjgEBAS0hAcZAoaw1KeRSAJEAgkEFAgRqASkAXQUCCJihAd5ArdSNMeBNDVYE8gExAHitAR5BBQQeAQ0AbkC1r + EkpycYBBQH8tQA9KQBKQSGggK1EEQEFfgEhAVkBACytAAP8vAFByc1WZDQAARwAAAAkAAABzdGVlbGRydW0AAAAACgAAAFN0ZWVsIERydW0AAAAAAAAAAHByb21dAAAA + AQAAAAEAAAABAAAACQAAAHN0ZWVsZHJ1bQAAAAAKAAAAU3RlZWwgRHJ1bQAAgD8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABURv////8AAAAAAAAAAAAAAAAAAAAAcnlh + bBAAAAACAAAAAAAAAAAAAAD/////b2xvcxEAAAABAAAAAQAAAEYAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAA + AAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAAACC + AwAAAAEAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAAAgD8AAAAAAQAAAAAAAAAAAAA/A4AAAGACAAAAAACo + QQAAjEICAAAAAACAP4EBAAAAAAAAAAAAAAAAAKDAAACgQAAAoEEAAAAAA4AAAFAAAHBCAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAKDAAACgQAAAAAAD + gAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BgAAAAAAAAADSBAAAAQAAAAAAAAACAAAAAAAAPwAAgD8DgAAAYAIAAAAAAKhB + AACuQgIAAAAAAAAAgQOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4ECAAAAAQAAAHEAAABGbGF0IHRlbXBlcmFtZW50CkFu + IGVxdWFsIHRlbXBlcmFtZW50IHdoZXJlIHRoZSBvY3RhdmUgcmF0aW8gaXMgZXhhY3RseSAyIHdoZW4gdGhlIG9jdGF2ZSBzdHJldGNoaW5nIGlzIHNldCB0byAxLgwA + AAAAAMhCAABIQwAAlkMAAMhDAAD6QwAAFkQAAC9EAABIRAAAYUQAAHpEAICJRAAAlkQAAAAAAAAAAAQAAAAAANxDPAAAAEUAAABGAAAAAH8AAAAAAAAABwAAAERlZmF1 + bHQAAAAAAAAAAAAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAAACAPwEAAAABAAAAAQAAABgAAAADgAAAUAAAgD8BAAAAAACAPwAAAAAAAIA/A4AA + AGACAAAAAACoQQAArkICAAAAAACAP4EDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAAgD8AAIA/A4AAAGACAAAA + AABwQgAAQEECAAAAAAAAAIEAAIA/A4AAAGACAAAAAABwQgAAQEECAAAAAAAAAIEBAAAAAQAAAAAAAAAAAAC/mpmZPgEAAAABAAAAAAAAAAAAAL+amZk+AACAPwOAAABA + AwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAIEIDgAAAYAIA + AAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAAAB + AACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAIEA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAAAAAAwEEAAJBCAgAAAAAAgD+BAQAAAAEAAAAB + AAAADAAAAENsZWFuIHN0dWRpbwsAAABjbGVhbnN0dWRpbwAAEMEAAEBBAACAP83MzD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD8AAKpDAACAPwAAgD8BAAAABgAAAAAA + gD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACB + AQAAAAAAAAABAAAAAAAAPwAAgEADAAAAAACAPwAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+C + A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAAgD8HAAAAAACAP83MzD5mZmY/MzOzP5qZGT4IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAA + QAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAAAQAAAAAAcEIDgAAAUAAAAAABAAAAAAAAAC9jqT4DgAAAQA0AAAAAADhCAABIQgAAUEIAAGBCAABsQgAAgEIAAIhC + AACMQgAAkEIAAJRCAACcQgAApEIAAK5CDQAAAHj3gD+B8hVdPwN494A/kMKaP3j3gD9494A/A5DCmj9494A/8hVdP/IVXT8BePeAP3j3gD8BAAAAAQAAAAEAAADNzEw/ + zcyMPwEAAAABAAAAAQAAAM3MTD+amZk/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBC + AAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAAgD8EAAAAAgAAAAgAAAAHAAAAAQAAAAEAAAAAAIA/AACAPwEA + AAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMA + AAABAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIBAAAAAQQAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAABwAAAAAA + AAAAAAAADgAAABAAAAAAAAAAAACAPgAAAAAAAAAAzcxMvgAANEOamRk+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAIAAAAAAAAAAAAAAA4A + AAAQAAAAAAAAAAAAgD8AAABAzcxMPwAAcMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAACAPwAAQMEAAAAAAwAAAAMA + AAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAAApXM8/ZmamP5qZmT8AALRCAAAAAAAAAAAFAAAAAAAAAAAAwMIAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAArX4z/NzMw+mpmZPwAAtEIAAAAAAAAAAAUAAAAAAMDCAAAAAAAAwMIAAMDCAADAwgUA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAj8L1Pz0KV78fhWs/AAC0QgAAAAAAAAAABQAAAAAA + QMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAAzMzO/XI/CPq5H + YT8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQA + AAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAwAAADMzMz/NzMy9AADAPwAAkEEAAAAAAAAAAAAAAAABAAAAAAAAABE4VjicEAAAUHJWSyYAAAJYRlRQjBAAAAoA + AAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3f3MDA7UW/Ki/DzAgJ+FkhGRsbGFLEAAAdGR0TTkCAAAgAAAAU3BhY2VkcnVtIC0gbmF0dXJhbCwg + aGFuZCBwbGF5ZWQAAAAABwAAAE1vZGFydHRaAAAAVGhlIGRlZmF1bHQgcHJlc2V0IGZvciB0aGUgU3BhY2VkcnVtLCBjbG9zZSB0byB0aGUgb3JpZ2luYWwgaW5zdHJ1 + bWVudCwgcGxheWVkIHdpdGggaGFuZHMuAAAAAEQBAAAgAAAAU3BhY2VkcnVtIC0gbmF0dXJhbCwgaGFuZCBwbGF5ZWQAAAAAdAEAAEdSVVUAAAAGAAAAAQPATVRyawAA + AV4A/wILYm93ZWRfaW5fQW0AsEAAiFiQPDQYLSyFfTQ0hHCANEA4kDkzhR2AOUAUkDQ3gxCwQEeBLUB/eIA0QBSQOTOCbYAtQIIfkDQzJoA5QIQXNEA8kC00DbBAR3FA + ACqQO0AFgDxAhjCQNSyFFoA1QAWQOTuFaIA5QA+QNTWEe4A1QB2QOUKENbBAR3NAfw2QNTlXgC1Aghw1QGQ5QBCQLTKBCLBAR3NAAIEQkD5KBIA7QIZDkDUohUU5PGuA + NUCFJJA1NB2AOUCFfJA5OhSANUCEObBAR4E0kDUxM7BAf22ALUCDYDVAgRCQLSlLgDlAdrBARzhAAHqAPkAdkDw/hhE0N4VFgDRAJpA5N4FasEBHgS1Af4JZkDQ8B4A5 + QIVgNEALkDlAhTyALUB0kDQ3BIA5QIMFPEAFkDtCLbBAR4EtQABLgDtACzRAOJA5RTctM5R4gDlAAi1AAP8vAFByc1UCDgAARwAAAAYAAABzcGFjZTgFAAAAaGFuZHMJ + AAAAU3BhY2VkcnVtAAAAAAAAAABwcm9tXgAAAAEAAAABAAAAAQAAAAYAAABzcGFjZTgFAAAAaGFuZHMJAAAAU3BhY2VkcnVtAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAA + AAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAD + gAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAAAAAIIDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAACA + PwAAAAABAAAAAAAAAAAAAD8DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAACgQQAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAA + AAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAABAAAA + AAAAAAIAAAAAAAA/AACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIA + AAABAAAAcQAAAEZsYXQgdGVtcGVyYW1lbnQKQW4gZXF1YWwgdGVtcGVyYW1lbnQgd2hlcmUgdGhlIG9jdGF2ZSByYXRpbyBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0 + YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRvIDEuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8 + AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAQAAAAAAAAAAAIA/AQAAAAEAAAABAAAAGAAA + AAOAAABQAACAPwEAAAAAAIA/AAAAAAAAgD8DgAAAQA4AAAAAACxCAACQQgAAkkIAAJRCAACWQgAAmEIAAJpCAACcQgAAnkIAAKBCAACiQgAApEIAAKZCAACoQg4AAAAA + AIA/gTZKhj8LjOOMP+XPkz9XE5s/J7KiP9Wwqj8VFLM/2eC7P1AcxT/ry84/X/XYP6me4z8DgAAAQA4AAAAAACxCAACQQgAAkkIAAJRCAACWQgAAmEIAAJpCAACcQgAA + nkIAAKBCAACiQgAApEIAAKZCAACoQg4AAAAAAIA/gTZKhj8LjOOMP+XPkz9XE5s/J7KiP9Wwqj8VFLM/2eC7P1AcxT/ry84/X/XYP6me4z8DgAAAUAAAekQBAAAAAAAA + AAOAAABQAAB6RAEAAAAAAIA/AACAPwOAAABgAgAAAAAAcEIAAEBBAgAAAAAAAACBmpkZQAOAAABgAgAAAAAAcEIAAEBBAgAAAAAAAACBAQAAAAEAAAAAAAAAmpmZvpqZ + mT4BAAAAAQAAAAAAAADNzEy+zczMPQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAAA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAGAC + AAAAAACoQQAArkICAAAAAACAP4EAACBCA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEAAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEgBAAAAAAAA + AAEAAAADgAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAAQAAgD8DgAAAYAIAAAAAAAAAAACAPwIAAAAAAIA/gQOAAABABAAAAAAAAAAmk0k+/n4/PwAAgD8EAAAAAAAAAIEA + AIA/gQOAAABgAgAAAAAAAAAAAIA/AgAAAAAAAACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAgD+BAACAvwAAAD8DgAAA + YAIAAAAAAMBBAACQQgIAAAAAAIA/gQEAAAABAAAAAQAAAAwAAABDbGVhbiBzdHVkaW8LAAAAY2xlYW5zdHVkaW8AABDBAABAQQAAgD/NzMw+AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAMA/AACqQwAAgD8AAIA/AQAAAAYAAAAAAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ + ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQEAAAAAAAAAAQAAAAAAAD8AAIBAAwAAAAAAgD8AAIA/AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ + ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAgD8AAIA/BwAAAAAAgD8AAAA/ZmZmPzMzsz+amRk+CAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AA + AEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAQAAAAEAAAAAAHBCA4AAAFAAAAAAAQAAAAAAAACrqqo+A4AAAGAC + AAAAAABcQgAAiEECAAAAAACAP4EBAAAAAQAAAAEAAADNzEw/zcyMPwEAAAABAAAAAQAAADMzMz/NzIw/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAEAAAAzMzM/ + ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAAAAPBCAAAAAA4AAAAAAAA/zczMPc3MTD4AAAA/AgAAAAAAAAADgAAASAEAAAAAAIA/AAAAAAOAAABIAQAAAAAA + gD8EAAAAAgAAAAgAAAAHAAAAAQAAAAEAAAAAAIA/AACAPwEAAAABAAAAAQAAAAEAAAABAAAAAACAPwAAAAAAAIBAAAC0QkYAAAAAA4AAAEgBAAAAAAAAAAAAAAADgAAA + SAEAAAAAAAAAAAAAAAAAAAADgAAASAEAAAAAAAAAAAAAAAMAAAABAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIBAAAAAQQAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAABwAAAAAAAAAAAAAADgAAABAAAAAAAAAAAACAPgAAAAAAAAAAzcxMvgAANEOamRk+AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAwAAAAIAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAgD8AAABAzcxMPwAAcMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAEAAAABAAAAAACAPwAAQMEAAAAAAwAAAAMAAAAAAAAAAQAAAAUAAAAEAAAAAQAAAAMAAAA9Ctc/ZmamP5qZmT8AALRCAAAAAAAAAAAFAAAAAAAAAAAA + wMIAAMDCAADAwgAAwMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAABAAAAAwAAAAAA4D/NzMw+mpmZPwAA + tEIAAAAAAAAAAAUAAAAAAMDCAAAAAAAAwMIAAMDCAADAwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAA + AAADAAAAj8L1Pz0KV78fhWs/AAC0QgAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAAAzMzO/XI/CPq5HYT8AAAAAAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAALgexT89Clc/rkfhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAwAAADMzMz/NzMy9AADAPwAAkEEAAAAAAAAAAAAA + AAABAAAAAAAAABE4Vjh3EAAAUHJWSyYAAAJYRlRQZxAAAAoAAAAyMDIzLzA2LzA2FwAAAFBpYW5vdGVxIDguMC4wLzIwMjIxMTE3U/NFpvMGCg71WjdbU0i+92RsbGEm + EAAAdGR0TTYCAAAfAAAASGFuZCBQYW4gLSBuYXR1cmFsLCBoYW5kIHBsYXllZAAAAAAHAAAATW9kYXJ0dFkAAABUaGUgZGVmYXVsdCBwcmVzZXQgZm9yIHRoZSBIYW5k + IFBhbiwgY2xvc2UgdG8gdGhlIG9yaWdpbmFsIGluc3RydW1lbnQsIHBsYXllZCB3aXRoIGhhbmRzLgAAAABEAQAAHwAAAEhhbmQgUGFuIC0gbmF0dXJhbCwgaGFuZCBw + bGF5ZWQAAAAAdAEAAEdSVVUAAAAGAAAAAQPATVRyawAAAV4A/wILYm93ZWRfaW5fQW0AsEAAiFiQPDQYLSyFfTQ0hHCANEA4kDkzhR2AOUAUkDQ3gxCwQEeBLUB/eIA0 + QBSQOTOCbYAtQIIfkDQzJoA5QIQXNEA8kC00DbBAR3FAACqQO0AFgDxAhjCQNSyFFoA1QAWQOTuFaIA5QA+QNTWEe4A1QB2QOUKENbBAR3NAfw2QNTlXgC1Aghw1QGQ5 + QBCQLTKBCLBAR3NAAIEQkD5KBIA7QIZDkDUohUU5PGuANUCFJJA1NB2AOUCFfJA5OhSANUCEObBAR4E0kDUxM7BAf22ALUCDYDVAgRCQLSlLgDlAdrBARzhAAHqAPkAd + kDw/hhE0N4VFgDRAJpA5N4FasEBHgS1Af4JZkDQ8B4A5QIVgNEALkDlAhTyALUB0kDQ3BIA5QIMFPEAFkDtCLbBAR4EtQABLgDtACzRAOJA5RTctM5R4gDlAAi1AAP8v + AFByc1XgDQAARwAAAAQAAABoYW5nBQAAAGhhbmRzCAAAAEhhbmQgUGFuAAAAAAAAAABwcm9tWwAAAAEAAAABAAAAAQAAAAQAAABoYW5nBQAAAGhhbmRzCAAAAEhhbmQg + UGFuAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAAAAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAA + AAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEA + AAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAAAAAIIDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAA + AAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAABAQAAAAAABAAAAAAAAAAAAAD8DgAAAYAIAAAAAAKhBAACMQgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAACgQQAA + AAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAA + rkICAAAAAACAP4GAAAAAAAAAANIEAAABAAAAAAAAAAIAAAAAAAA/AACAPwOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIED + gAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIAAAABAAAAcQAAAEZsYXQgdGVtcGVyYW1lbnQKQW4gZXF1YWwgdGVtcGVyYW1lbnQgd2hlcmUgdGhlIG9jdGF2ZSByYXRp + byBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRvIDEuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAA + ekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAARGVmYXVsdAAAAAAAAAAAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACB + AQAAAAAAAAAAAIA/AQAAAAEAAAABAAAAGAAAAAOAAABQAACAPwEAAAAAAIA/AAAAAAAAgD8DgAAAQAwAAAAAABBCAACUQgAAlkIAAJhCAACaQgAAnEIAAJ5CAACgQgAA + okIAAKRCAACmQgAAqEIMAAAAAACAP4FE+4I/CU8Ihj+LJ4k/ZlmMP02ejz+z9pI/C2OWP83jmT9yeZ0/eCShPwOAAABADAAAAAAAEEIAAJRCAACWQgAAmEIAAJpCAACc + QgAAnkIAAKBCAACiQgAApEIAAKZCAACoQgwAAAAAAIA/gUT7gj8JTwiGP4sniT9mWYw/TZ6PP7P2kj8LY5Y/zeOZP3J5nT94JKE/A4AAAFAAAHpEAQAAAAAAAAADgAAA + UAAAekQBAAAAAACAPwAAgD8DgAAAYAIAAAAAAHBCAABAQQIAAAAAAAAAgQAAQEADgAAAYAIAAAAAAHBCAABAQQIAAAAAAAAAgQEAAAABAAAAAAAAAJqZmb6amZk+AQAA + AAEAAAAAAAAAzcxMvs3MzD0AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAACAPwOAAABgAgAAAAAA + qEEAAK5CAgAAAAAAgD+BAAAgQgOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABIAQAAAAAAAAABAAAA + A4AAAGACAAAAAAAAAAAAgD8CAAAAAAAAAAEAAIA/A4AAAGACAAAAAAAAAAAAgD8CAAAAAACAP4EDgAAAQAQAAAAAAAAAJpNJPv5+Pz8AAIA/BAAAAAAAAACBAACAP4ED + gAAAYAIAAAAAAAAAAACAPwIAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQAAgL8AAAA/A4AAAGACAAAA + AADAQQAAkEICAAAAAACAP4EBAAAAAQAAAAEAAAAMAAAAQ2xlYW4gc3R1ZGlvCwAAAGNsZWFuc3R1ZGlvAAAQwQAAQEEAAIA/zczMPgAAAAAAAAAAAAAAAAAAAAAAAAAA + AADAPwAAqkMAAIA/AACAPwEAAAAGAAAAAACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAAAA + A4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEBAAAAAAAAAAEAAAAAAAA/AACAQAMAAAAAAIA/AACAPwAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAA + QAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IAAIA/AACAPwcAAAAAAIA/AAAAP2ZmZj8zM7M/mpkZPggAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAA + AAAAqEEAAHBCAADYQgMAAAAAAIA/ggOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggEAAAABAAAAAABwQgOAAABQAAAAAAEAAAAAAAAA71txPgOAAABgAgAAAAAA + XEIAAIhBAgAAAPMENT8B8wS1PwEAAAABAAAAAQAAAM3MTD/NzIw/AQAAAAEAAAABAAAAMzMzP83MjD8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9m + ZqY/AQAAAAAAAAABAAAAMzMzP2Zmpj8BAAAAAAAAAAAA8EIAAAAADgAAAAAAAD/NzMw9zcxMPgAAAD8CAAAAAAAAAAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAACA + PwQAAAACAAAACAAAAAcAAAABAAAAAQAAAAAAgD8AAIA/AQAAAAEAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABI + AQAAAAAAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAgEAAAABBAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAHAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIA+AAAAAAAAAADNzEy+AAA0Q5qZGT4AAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAAAAAADgAAABAAAAAAAAAAAACAPwAAAEDNzEw/AABwwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAQAAAAEAAAAAAIA/AABAwQAAAAADAAAAAwAAAAAAAAABAAAABQAAAAQAAAABAAAAAwAAAClczz9mZqY/mpmZPwAAtEIAAAAAAAAAAAUAAAAAAAAAAADA + wgAAwMIAAMDCAADAwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAMzPzP83MzD6amZk/AAC0 + QgAAAAAAAAAABQAAAAAAwMIAAAAAAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAA + AAMAAACPwvU/PQpXvx+Faz8AALRCAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAAAAAAAAAAQAAAAAAAAAAwAAADMzM79cj8I+rkdhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAuB7FPz0KVz+uR+E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAADAAAAMzMzP83MzL0AAMA/AACQQQAAAAAAAAAAAAAA + AAEAAAAAAAAAEThWOCkRAABQclZLJgAAAlhGVFAZEQAACgAAADIwMjMvMDYvMDYXAAAAUGlhbm90ZXEgOC4wLjAvMjAyMjExMTcZjgDjW+DTuYlUeDjey9TnZGxsYdgQ + AAB0ZHRNDAIAABMAAABUYW5rIERydW0gLSBuYXR1cmFsAAAAAAcAAABNb2RhcnR0RwAAAFRoZSBkZWZhdWx0IHByZXNldCBmb3IgdGhlIFRhbmsgRHJ1bSwgY2xvc2Ug + dG8gdGhlIG9yaWdpbmFsIGluc3RydW1lbnQuAAAAAEQBAAATAAAAVGFuayBEcnVtIC0gbmF0dXJhbAAAAAB0AQAAR1JVVQAAAAYAAAABA8BNVHJrAAABXgD/Agtib3dl + ZF9pbl9BbQCwQACIWJA8NBgtLIV9NDSEcIA0QDiQOTOFHYA5QBSQNDeDELBAR4EtQH94gDRAFJA5M4JtgC1Agh+QNDMmgDlAhBc0QDyQLTQNsEBHcUAAKpA7QAWAPECG + MJA1LIUWgDVABZA5O4VogDlAD5A1NYR7gDVAHZA5QoQ1sEBHc0B/DZA1OVeALUCCHDVAZDlAEJAtMoEIsEBHc0AAgRCQPkoEgDtAhkOQNSiFRTk8a4A1QIUkkDU0HYA5 + QIV8kDk6FIA1QIQ5sEBHgTSQNTEzsEB/bYAtQINgNUCBEJAtKUuAOUB2sEBHOEAAeoA+QB2QPD+GETQ3hUWANEAmkDk3gVqwQEeBLUB/glmQNDwHgDlAhWA0QAuQOUCF + PIAtQHSQNDcEgDlAgwU8QAWQO0ItsEBHgS1AAEuAO0ALNEA4kDlFNy0zlHiAOUACLUAA/y8AUHJzVbwOAABHAAAABAAAAGhhcGkAAAAACQAAAFRhbmsgRHJ1bQAAAAAA + AAAAcHJvbVcAAAABAAAAAQAAAAEAAAAEAAAAaGFwaQAAAAAJAAAAVGFuayBEcnVtAACAPwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRG/////wAAAAAAAAAAAAAAAAAA + AAByeWFsEAAAAAIAAAAAAAAAAAAAAP////9vbG9zEQAAAAEAAAABAAAARgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA + AAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAA + AAAAAIIDAAAAAQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAAAAQAAAAAABAAAAAAAAAAAAAD8DgAAAYAIA + AAAAAKhBAACMQgIAAAAAAIA/gQEAAAAAAAAAAAAAAAAAoMAAAKBAAACgQQAAAAADgAAAUAAAcEIBAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoMAAAKBA + AAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAACAP4GAAAAAAAAAANIEAAABAAAAAAAAAAIAAAAAAAA/AACAPwOAAABgAgAA + AAAAqEEAAK5CAgAAAAAAAACBA4AAAGACAAAAAACoQQAArkICAAAAAAAAAIEDgAAAYAIAAAAAAKhBAACuQgIAAAAAAIA/gQIAAAABAAAAcQAAAEZsYXQgdGVtcGVyYW1l + bnQKQW4gZXF1YWwgdGVtcGVyYW1lbnQgd2hlcmUgdGhlIG9jdGF2ZSByYXRpbyBpcyBleGFjdGx5IDIgd2hlbiB0aGUgb2N0YXZlIHN0cmV0Y2hpbmcgaXMgc2V0IHRv + IDEuDAAAAAAAyEIAAEhDAACWQwAAyEMAAPpDAAAWRAAAL0QAAEhEAABhRAAAekQAgIlEAACWRAAAAAAAAAAABAAAAAAA3EM8AAAARQAAAEYAAAAAfwAAAAAAAAAHAAAA + RGVmYXVsdAAAAAAAAAAAAAAAAAOAAABgAgAAAAAAqEEAAK5CAgAAAAAAAACBAQAAAAAAAAAAAIA/AQAAAAEAAAABAAAAGAAAAAOAAABQAACAPwEAAAAAAIA/AAAAAAAA + gD8DgAAAQBoAAAAAACxCAAAwQgAANEIAADhCAAA8QgAAQEIAAERCAABIQgAATEIAAFBCAACKQgAAjEIAAI5CAACQQgAAkkIAAJRCAACWQgAAmEIAAJpCAACcQgAAnkIA + AKBCAACiQgAApEIAAKZCAACoQhoAAAAAACBBCa7D90DA1b9A5YeUQAsBZkB0FTJAQuIJQFKE1T+AUaU/AACAPwAAgD8PrzyVP2T/rT/I3co/WIbsP0LiCUC/wiBA6m47 + QPiHWkDhyX5A5YeUQJwsrUAH6MlA0GfrQDk7CUEAACBBA4AAAEAaAAAAAAAsQgAAMEIAADRCAAA4QgAAPEIAAEBCAABEQgAASEIAAExCAABQQgAAikIAAIxCAACOQgAA + kEIAAJJCAACUQgAAlkIAAJhCAACaQgAAnEIAAJ5CAACgQgAAokIAAKRCAACmQgAAqEIaAAAAAAAgQQmuw/dAwNW/QOWHlEALAWZAdBUyQELiCUBShNU/gFGlPwAAgD8A + AIA/D688lT9k/60/yN3KP1iG7D9C4glAv8IgQOpuO0D4h1pA4cl+QOWHlECcLK1AB+jJQNBn60A5OwlBAAAgQQOAAABQAAB6RAEAAAAAAAAAA4AAAFAAAHpEAQAAAAAA + gD8AAAAAA4AAAGACAAAAAABwQgAAQEECAAAAAAAAAIEAAIA/A4AAAGACAAAAAABwQgAAQEECAAAAAAAAAIEBAAAAAQAAAAAAAACamZm+mpmZPgEAAAABAAAAAAAAAM3M + TL7NzMw9AACAPwOAAABAAwAAAAAAqEEAAHBCAADYQgMAAAAAAIA/ggAAAAADgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAYAIAAAAAAKhBAACuQgIAAAAA + AIA/gQAAIEIDgAAAYAIAAAAAAKhBAACuQgIAAAAAAAAAgQAAgD8DgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAASAEAAAAAAAAAAQAAAAOAAABgAgAAAAAA + AAAAAIA/AgAAAAAAAAABAACAPwOAAABgAgAAAAAAAAAAAIA/AgAAAAAAgD+BA4AAAEAEAAAAAAAAACaTST7+fj8/AACAPwQAAAAAAAAAgQAAgD+BA4AAAGACAAAAAAAA + AAAAgD8CAAAAAAAAAIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAIA/A4AAAGACAAAAAACoQQAArkICAAAAAACAP4EAAIC/AAAAPwOAAABgAgAAAAAAwEEAAJBCAgAA + AAAAgD+BAQAAAAEAAAABAAAADAAAAENsZWFuIHN0dWRpbwsAAABjbGVhbnN0dWRpbwAAEMEAAEBBAACAP83MzD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD8AAKpDAACA + PwAAgD8BAAAABgAAAAAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAAAAAAOAAABgAgAAAAAA + qEEAAK5CAgAAAAAAAACBAQAAAAAAAAABAAAAAAAAPwAAgEADAAAAAACAPwAAgD8AAIA/A4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAA + cEIAANhCAwAAAAAAgD+CA4AAAEADAAAAAACoQQAAcEIAANhCAwAAAAAAgD+CAACAPwAAgD8HAAAAAACAP83MzD7NzEw/ZmamP5qZGT4IAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAADgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA + 2EIDAAAAAACAP4IDgAAAQAMAAAAAAKhBAABwQgAA2EIDAAAAAACAP4IBAAAAAQAAAAAAcEIDgAAAUAAAAAABAAAAAAAAAOQ4jj4DgAAAYAIAAAAAAKhBAACuQgIAAAAA + AIA/gQEAAAABAAAAAQAAAJqZGT/NzIw/AQAAAAEAAAABAAAAMzMzP83MjD8BAAAAAAAAAAEAAAAzMzM/ZmamPwEAAAAAAAAAAQAAADMzMz9mZqY/AQAAAAAAAAABAAAA + MzMzP2Zmpj8BAAAAAAAAAAAA8EIAAAAADgAAAAAAAD/NzMw9zcxMPgAAAD8CAAAAAAAAAAOAAABIAQAAAAAAgD8AAAAAA4AAAEgBAAAAAACAPwQAAAACAAAACAAAAAcA + AAABAAAAAQAAAAAAgD8AAIA/AQAAAAEAAAABAAAAAQAAAAEAAAAAAIA/AAAAAAAAgEAAALRCRgAAAAADgAAASAEAAAAAAAAAAAAAAAOAAABIAQAAAAAAAAAAAAAAAAAA + AAOAAABIAQAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAA4AAAAQAAAAAAAAAAAAgEAAAABBAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAMAAAAHAAAAAAAAAAAAAAAOAAAAEAAAAAAAAAAAAIA+AAAAAAAAAADNzEy+AAA0Q5qZGT4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADAAAAAgAAAAAAAAAAAAAADgAAABAAAAAAAAAAAACAPwAAAEDNzEw/AABwwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEA + AAAAAIA/AABAwQAAAAADAAAAAwAAAAAAAAABAAAABQAAAAQAAAABAAAAAwAAAClczz9mZqY/mpmZPwAAtEIAAAAAAAAAAAUAAAAAAAAAAADAwgAAwMIAAMDCAADAwgUA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABAAAAAEAAAADAAAAzczsP83MzD6amZk/AAC0QgAAAAAAAAAABQAAAAAA + wMIAAAAAAADAwgAAwMIAAMDCBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAAAAAAAAMAAACPwvU/PQpXvx+F + az8AALRCAAAAAAAAAAAFAAAAAABAwQAAQMEAAHDCAABwwgAAcMIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQA + AAAAAAAAAwAAADMzM79cj8I+rkdhPwAAAAAAAAAAAAAAAAUAAAAAAEDBAABAwQAAcMIAAHDCAABwwgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAEAAAAAAAAABAAAAAAAAAADAAAAuB7FPz0KVz+uR+E/AAAAAAAAAAAAAAAABQAAAAAAQMEAAEDBAABwwgAAcMIAAHDCBQAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAADAAAAMzMzP83MzL0AAMA/AACQQQAAAAAAAAAAAAAAAAEAAAAAAAAA + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {2557FC75-5DE9-F04D-AB87-157B9FCAB4EB} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-722-fx-enable-disable-bypass-param-feedback.RPP b/plugin-reaper-realearn/resources/test-projects/issue-722-fx-enable-disable-bypass-param-feedback.RPP new file mode 100644 index 0000000..ed639ad --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-722-fx-enable-disable-bypass-param-feedback.RPP @@ -0,0 +1,167 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 7 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAARAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS44IiwiaWQiOiIwMVFQRllIRSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkJOT2NuY0M3QjFodUE0NDN2WU43VyIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MTIsImZ4QW5jaG9yIjoiaWQiLCJmeEluZGV4IjoxLCJmeEdVSUQiOiIyM0I2 + NEQ2Qi0zREZCLUJGNDEtOTJDNy1FNDk3NEE3QUQ1OEYiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRk + cmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 863 113 752 675 + FXID {70761256-4C75-C94A-88F0-B43DA9BB8534} + WAK 0 0 + BYPASS 0 0 0 + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAAAAAAAAAFlAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAEACAABrAQAAAgAAAA== + AAAQAAAA + > + FLOAT 1361 296 576 419 + FXID {23B64D6B-3DFB-BF41-92C7-E4974A7AD58F} + + PARM_TCP 13:bypass + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-727-superior-feedback-bug.RPP b/plugin-reaper-realearn/resources/test-projects/issue-727-superior-feedback-bug.RPP new file mode 100644 index 0000000..96ed77d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-727-superior-feedback-bug.RPP @@ -0,0 +1,2371 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 2 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAD8mQEAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS45IiwiaWQiOiJkdFhlcENoLSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMTUiLCJmZWVkYmFja0RldmljZUlkIjoiMTUiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJv + bGxlckdyb3VwIjp7fSwiY29udHJvbGxlckdyb3VwcyI6W3siaWQiOiJmYWRlciIsIm5hbWUiOiIwMTAgLSBGYWRlciJ9LHsiaWQiOiJ2LXBvdCIsIm5hbWUiOiIwMDEg + LSBWLVBvdCIsImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InYtc2VsZWN0IiwibmFtZSI6IjAwMiAtIFYtU2VsZWN0IiwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiZmFkZXItdG91Y2giLCJuYW1lIjoiMDExIC0gRmFkZXIgVG91Y2giLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJzZWxlY3Qi + LCJuYW1lIjoiMDA5IC0gU2VsZWN0In0seyJpZCI6Im11dGUiLCJuYW1lIjoiMDA4IC0gTXV0ZSJ9LHsiaWQiOiJzb2xvIiwibmFtZSI6IjAwNyAtIFNvbG8ifSx7Imlk + IjoicmVjb3JkLXJlYWR5IiwibmFtZSI6IjAwNiAtIFJlY29yZC1Bcm0ifSx7ImlkIjoidi1wb3QtbGVkcyIsIm5hbWUiOiIwMDMgLSBWLVBvdCBMRURzIiwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJsY2QiLCJuYW1lIjoiMDA0IC0gTENEIiwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtZXRlciIsIm5hbWUi + OiIwMDUgLSBWVSBNZXRlcnMiLCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNmc0tINEg4YlRjY1BwUTRSdmtNUyIsIm5hbWUiOiIwMTIgLSBFbmNvZGVy + IEFzc2lnbiBCdXR0b25zIn0seyJpZCI6IjE2eUVPdk9Qc2hyb2JwQmpqTmZLVSIsIm5hbWUiOiIwMTQgLSBHbG9iYWwgRnVuY3Rpb25zIn0seyJpZCI6IlgzS2tzbnpS + SFJkMGdKQ1FxMk54bCIsIm5hbWUiOiIwMTcgLSBNb2RpZnkgQnV0dG9ucyJ9LHsiaWQiOiJ5ZDZ1c3Zmb1VzX0FYZG5CRVdoQmYiLCJuYW1lIjoiMDE4IC0gQXV0b21h + dGlvbiJ9LHsiaWQiOiJEZGRKdkhVVDRfeTVPZG9rT2w5RGgiLCJuYW1lIjoiMDE5IC0gVXRpbGl0eSJ9LHsiaWQiOiJydE1WcV9pU3NPZWJSRUpyaDNSQU0iLCJuYW1l + IjoiMDEzIC0gNy1TZWdtZW50IERpc3BsYXkifSx7ImlkIjoiMkgwMndkOTNLSS1KUS1aQ21FeWJuIiwibmFtZSI6IjAxNSAtIE1hc3RlciJ9LHsiaWQiOiJGMlE1TzBk + ajBUc3RQY1ZoNGNZSGIiLCJuYW1lIjoiMDE2IC0gRnVuY3Rpb24gQnV0dG9ucyJ9LHsiaWQiOiI5TUc2dm9OTVZmOXl6U1pQUjk4OG4iLCJuYW1lIjoiMDIwIC0gVHJh + bnNwb3J0In0seyJpZCI6ImdKZW11YWZzeXBSZjRPaVFmY093VyIsIm5hbWUiOiIwMjEgLSBOYXZpZ2F0aW9uIn1dLCJtYXBwaW5ncyI6W3siaWQiOiI0NVUtVk1qbTh5 + aFJDLXdoX1VGd0QiLCJuYW1lIjoiVGVzdCBMQ0QgMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xjZC9s + aW5lMSJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja0NvbG9yIjpbMjU1LDI1NSww + XSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiJtYWluL2ZhZGVyIiwiZ3JvdXBJZCI6IjJIMDJ3ZDkzS0ktSlEtWkNtRXlibiIsInNvdXJjZSI6 + eyJ0eXBlIjozLCJjaGFubmVsIjo4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJtYWluL2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImpvZyIsImdyb3VwSWQiOiJnSmVtdWFmc3lwUmY0T2lR + ZmNPd1ciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo2MCwiY2hhcmFjdGVyIjoyLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4Ijoiam9nIiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibWFpbi9mYWRlci90b3VjaCIsImdyb3VwSWQiOiIySDAyd2Q5M0tJLUpRLVpDbUV5Ym4iLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1haW4vZmFkZXIvdG91Y2giLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYXJrZXIiLCJncm91cElkIjoiOU1HNnZvTk1WZjl5elNaUFI5ODhuIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Im1hcmtlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJyZWFkIiwiZ3JvdXBJZCI6InlkNnVzdmZvVXNfQVhkbkJFV2hCZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0fSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IndyaXRlIiwiZ3JvdXBJZCI6InlkNnVzdmZvVXNfQVhkbkJFV2hC + ZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ3cml0ZSIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX19LHsiaWQiOiJjaC1sZWZ0IiwiZ3JvdXBJZCI6ImdKZW11YWZzeXBSZjRPaVFmY093VyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjQ4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaC1sZWZ0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoLXJpZ2h0IiwiZ3JvdXBJZCI6 + ImdKZW11YWZzeXBSZjRPaVFmY093VyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaC1yaWdodCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJiYW5rLWxlZnQiLCJncm91cElkIjoiZ0plbXVhZnN5cFJmNE9pUWZjT3dXIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstbGVmdCIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJiYW5rLXJpZ2h0IiwiZ3JvdXBJZCI6ImdKZW11YWZzeXBSZjRPaVFmY093VyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ3 + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJiYW5rLXJpZ2h0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJld2luZCIsImdyb3VwSWQiOiI5TUc2 + dm9OTVZmOXl6U1pQUjk4OG4iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + cmV3aW5kIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImZhc3QtZndkIiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4biIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjkyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmYXN0LWZ3ZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJw + bGF5IiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk0fSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJwbGF5IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InN0b3AiLCJncm91cElkIjoiOU1HNnZvTk1WZjl5elNaUFI5ODhuIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InN0b3AiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + fSx7ImlkIjoicmVjb3JkIiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk1fSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZWNvcmQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3ljbGUiLCJncm91cElkIjoiOU1HNnZvTk1WZjl5 + elNaUFI5ODhuIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN5Y2xlIiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Inpvb20iLCJncm91cElkIjoiZ0plbXVhZnN5cFJmNE9pUWZjT3dXIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTAwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJ6b29tIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InNjcnViIiwiZ3JvdXBJZCI6 + ImdKZW11YWZzeXBSZjRPaVFmY093VyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwMX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4Ijoic2NydWIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLWxlZnQiLCJncm91cElkIjoiZ0plbXVhZnN5cFJmNE9pUWZjT3dXIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1sZWZ0IiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fX0seyJpZCI6ImN1cnNvci1yaWdodCIsImdyb3VwSWQiOiJnSmVtdWFmc3lwUmY0T2lRZmNPd1ciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo5OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLXJpZ2h0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImN1cnNvci11cCIsImdyb3Vw + SWQiOiJnSmVtdWFmc3lwUmY0T2lRZmNPd1ciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5Nn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY3Vyc29yLXVwIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImN1cnNvci1kb3duIiwiZ3JvdXBJZCI6ImdKZW11YWZzeXBSZjRPaVFmY093VyIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItZG93biIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX19LHsiaWQiOiJudWRnZSIsImdyb3VwSWQiOiI5TUc2dm9OTVZmOXl6U1pQUjk4OG4iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo4NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibnVkZ2UiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZHJvcCIsImdyb3VwSWQiOiI5TUc2dm9O + TVZmOXl6U1pQUjk4OG4iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZHJv + cCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyZXBsYWNlIiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjg4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXBsYWNlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNsaWNrIiwi + Z3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg5fSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjbGljayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJzb2xvIiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4biIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImYxIiwiZ3JvdXBJZCI6IkYyUTVPMGRqMFRzdFBjVmg0Y1lIYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU0fSwibW9kZSI6eyJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJmMiIsImdyb3VwSWQiOiJGMlE1TzBkajBUc3RQY1ZoNGNZSGIiLCJzb3Vy + Y2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjIiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7 + ImlkIjoiZjMiLCJncm91cElkIjoiRjJRNU8wZGowVHN0UGNWaDRjWUhiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTZ9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImYzIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImY0IiwiZ3JvdXBJZCI6IkYyUTVPMGRqMFRzdFBjVmg0Y1lIYiIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNCIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJmNSIsImdyb3VwSWQiOiJGMlE1TzBkajBUc3RQY1ZoNGNZSGIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1OH0sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjYiLCJncm91cElkIjoiRjJRNU8wZGowVHN0UGNWaDRjWUhiIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY2IiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fX0seyJpZCI6IjlYaFpmLWFocElsVUZ2X1Z5c2NGViIsIm5hbWUiOiJmNyAoQnV0dG9uKSIsImdyb3VwSWQiOiJGMlE1TzBkajBUc3RQY1ZoNGNZSGIiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2MH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjciLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7Imlk + Ijoicl8yZ0Z2M0hlRkNKUW9NNldLaUMtIiwibmFtZSI6ImY4IChCdXR0b24pIiwiZ3JvdXBJZCI6IkYyUTVPMGRqMFRzdFBjVmg0Y1lIYiIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjYxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmOCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJzbXB0 + ZS1iZWF0cyIsImdyb3VwSWQiOiJydE1WcV9pU3NPZWJSRUpyaDNSQU0iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1M30sIm1vZGUiOnsi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4Ijoic21wdGUtYmVhdHMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibGNkL2Fzc2lnbm1lbnQiLCJncm91cElkIjoicnRNVnFf + aVNzT2ViUkVKcmgzUkFNIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImxjZC9hc3NpZ25tZW50IiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJsY2QvdGltZWNvZGUiLCJncm91cElkIjoicnRNVnFfaVNzT2ViUkVKcmgzUkFNIiwic291cmNl + Ijp7InR5cGUiOjEyLCJkaXNwbGF5VHlwZSI6Im1hY2tpZS1zZXZlbiIsImRpc3BsYXlJZCI6Mn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL3RpbWVjb2RlIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjozMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiY2gxL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6 + MTA0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6Ilki + fSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDEvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI0fSwibW9kZSI6eyJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJj + ZSI6eyJ0eXBlIjozLCJjaGFubmVsIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoi + di1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNiwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEu + MCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gx + L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVk + cyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3Qv + d3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1h + eFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qv + c2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDAgZGNiYV0ifSwibW9kZSI6 + eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL3YtcG90 + L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDExIGRjYmFdIn0sIm1vZGUi + OnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2gxL3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gxL211dGUi + LCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDEvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDEvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL3NvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL3JlY29yZC1yZWFkeSIs + Imdyb3VwSWQiOiJyZWNvcmQtcmVhZHkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjowfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDEvcmVjb3JkLXJlYWR5IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9tZXRlci9wZWFrIiwiZ3JvdXBJZCI6Im1ldGVyIiwic291cmNlIjp7InR5 + cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkQwIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL21ldGVyL3BlYWsiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoMi92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMzfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDIvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDV9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + Mi9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMi9zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gyL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMs + ImNoYW5uZWwiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE3LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvdi1wb3QiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdC93cmFwIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2Ui + OnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUi + OjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMi92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9zaW5nbGUiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZh + bHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvdi1wb3Qvc3ByZWFkIiwiZ3Jv + dXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VW + YWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDIvbXV0ZSIsImdyb3VwSWQiOiJt + dXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tdXRlIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMi9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5fSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJl + Y29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9yZWNv + cmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gyL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01p + ZGlQYXR0ZXJuIjoiRDAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2gzL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzR9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9mYWRlci90 + b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNn0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gzL2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3NlbGVjdCIsImdy + b3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gz + L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDMvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Mn0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gzL2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMy92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MTgsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBvdCIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0s + ImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJh + d01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L2Jv + b3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBv + dC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy92LXBv + dC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL211dGUiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + fSx7ImlkIjoiY2gzL3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwfSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDMvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDMvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoi + RDAgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3Ytc2Vs + ZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoNC92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9mYWRlci90b3VjaCIsImdyb3Vw + SWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwN30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g0L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxl + Y3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyN30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3NlbGVjdCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6M30sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2ZhZGVy + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNC92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + MTksImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNF + bmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJu + IjoiQjAgMzMgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRl + cm4iOiJCMCAzMyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9zaW5nbGUiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJj + ZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC92LXBvdC9zcHJlYWQiLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoxOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g0 + L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjExfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDQvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19fSx7ImlkIjoiY2g0L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAwMTEgZGNi + YV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDQvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3Ytc2VsZWN0IiwiZ3JvdXBJ + ZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92 + LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10 + b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyL3Rv + dWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX19LHsiaWQiOiJjaDUvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2ZhZGVyIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fX0seyJpZCI6ImNoNS92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjAsImNoYXJhY3Rl + ciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImNoNS92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzQgWzAw + MTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImNoNS92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBb + MDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjox + MCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNS9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjoyMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6Ilki + fSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g1L3NvbG8iLCJncm91 + cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc29s + byIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDUvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoi + Y2g1L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMDAgZGNiYV0ifSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUv + bWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0 + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXNlbGVjdCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyL3RvdWNoIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjoyOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJj + aDYvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6NX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImNoNi92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjEsImNoYXJhY3RlciI6NCwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + Ni92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUgWzAwMTAgZGNiYV0ifSwi + bW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92 + LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAwMSBkY2JhXSJ9 + LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0 + dGVybiI6IkIwIDM1IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBh + dHRlcm4iOiJCMCAzNSBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNi9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMX0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L3NvbG8iLCJncm91cElkIjoic29sbyIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc29sbyIsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX19LHsiaWQiOiJjaDYvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L21ldGVyL3Bl + YWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbWV0ZXIvcGVhayIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjExMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjozMH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvZmFkZXIiLCJn + cm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6Nn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNy92LXBv + dC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjIsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJt + b2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC93cmFw + IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhT + b3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2g3L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoNy92LXBvdC9ib29zdC1j + dXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1p + blNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g3L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2 + IFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoNy92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAz + NiBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpm + YWxzZX0seyJpZCI6ImNoNy9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMn0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJjaDcvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjZ9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L21ldGVyL3BlYWsiLCJncm91cElk + IjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTAgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6Mzl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC9mYWRlci90b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjExMX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3NlbGVjdCIsImdyb3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMX0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvZmFkZXIiLCJncm91cElkIjoiZmFk + ZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC92LXBvdC9jb250cm9sIiwi + Z3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MjMsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjoxLjAsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoOC92LXBvdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC93cmFwIiwiZ3JvdXBJZCI6 + InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6 + MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2Vs + ZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4Ijoi + Y2g4L3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoOC92LXBvdC9ib29zdC1jdXQiLCJncm91cElk + Ijoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVl + IjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3YtcG90L2Jvb3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g4L3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAwIGRjYmFd + In0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoOC92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMSBkY2Jh + XSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC92LXBvdC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImNoOC9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyM30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2g4L211dGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g4L3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjE1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvcmVj + b3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJlY29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjd9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoOC9yZWNvcmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g4L21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJz + b3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiRDAgWzAxMTEgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6 + ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJN + b3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRy + b2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMFAyeUsyLWNsSEc3aGtBRm14TXRHIiwibmFtZSI6IlRyYWNrIiwiZ3JvdXBJZCI6IjNmc0tINEg4YlRjY1BwUTRSdmtN + UyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQwfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7 + ImlkIjoiZzJ2bEdPdTVvMUJoQ3hnT1U1c3VxIiwibmFtZSI6IlBhbiAvIFN1cnJvdW5kIiwiZ3JvdXBJZCI6IjNmc0tINEg4YlRjY1BwUTRSdmtNUyIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQyfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19fSx7ImlkIjoianA5anFLSzZndEF3WVg2WFZTS0FNIiwibmFtZSI6IkVRIiwiZ3JvdXBJZCI6IjNmc0tINEg4YlRjY1BwUTRSdmtNUyIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ0fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19fSx7ImlkIjoiRFhTaUZ6MXlKNVl1NER4VnRncFZPIiwibmFtZSI6IlNlbmQiLCJncm91cElkIjoiM2ZzS0g0SDhiVGNjUHBRNFJ2a01TIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDF9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX19LHsiaWQiOiIyaTZpRDhlOXRkcUlVTDlzbC1tUEgiLCJuYW1lIjoiUGx1Zy1JbiIsImdyb3VwSWQiOiIzZnNLSDRIOGJUY2NQcFE0UnZrTVMiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0M30sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NCwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ik5zUnk4S1VaZXBkNXFIUjhVbkJmRiIsIm5hbWUiOiJJbnN0IiwiZ3JvdXBJZCI6IjNmc0tINEg4YlRjY1BwUTRSdmtNUyIs + InNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ1fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1 + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiajVwQkloMndMd0ZTVWtvZGY4dWZ4IiwibmFtZSI6Ikdsb2JhbCBWaWV3IiwiZ3JvdXBJZCI6IjE2eUVPdk9Qc2hyb2Jw + QmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUxfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo2LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiN21WSmVobDA0RlVELUVyRlRRQWZWIiwibmFtZSI6Ik1JREkgVHJhY2tzIiwiZ3JvdXBJZCI6IjE2eUVP + dk9Qc2hyb2JwQmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjYyfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo3LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZzFFeDRpV3A2bFJMNzVsUVNubUdsIiwibmFtZSI6IklucHV0cyIsImdyb3VwSWQiOiIx + NnlFT3ZPUHNocm9icEJqak5mS1UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2M30sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6OCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImtnUlY0eU1RMEFnVkZZZkhaUjVERiIsIm5hbWUiOiJBdWRpbyBUcmFja3MiLCJn + cm91cElkIjoiMTZ5RU92T1BzaHJvYnBCampOZktVIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjR9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjksIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI0ZjlubHNFUzlrUlNZNWJ0c0NoN04iLCJuYW1lIjoiQXVkaW8g + SW5zdCIsImdyb3VwSWQiOiIxNnlFT3ZPUHNocm9icEJqak5mS1UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2NX0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiIyc05XUEc5UzFrbnBuLVR0OUZjZ3AiLCJuYW1l + IjoiQXV4IiwiZ3JvdXBJZCI6IjE2eUVPdk9Qc2hyb2JwQmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY2fSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMSwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlFGVTFUaEg4d3loc0FMS3F4cVJ5WiIsIm5h + bWUiOiJCdXNlcyIsImdyb3VwSWQiOiIxNnlFT3ZPUHNocm9icEJqak5mS1UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2N30sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJHRmNPWlg2cWwtVzJKTy1BcmtlWUUi + LCJuYW1lIjoiT3V0cHV0cyIsImdyb3VwSWQiOiIxNnlFT3ZPUHNocm9icEJqak5mS1UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2OH0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTMsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJPbG90T0tWSU94bV9mNDZh + R0IxNXAiLCJuYW1lIjoiVXNlciIsImdyb3VwSWQiOiIxNnlFT3ZPUHNocm9icEJqak5mS1UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2 + OX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTQsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJleVpVekg1b1Q2a2Va + VlBIbkhBZkoiLCJuYW1lIjoiU2hpZnQiLCJncm91cElkIjoiWDNLa3NuelJIUmQwZ0pDUXEyTnhsIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6NzB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibFRibV9kZllJ + eVNYQ0JEWTI3UnIwIiwibmFtZSI6Ik9wdGlvbiIsImdyb3VwSWQiOiJYM0trc256UkhSZDBnSkNRcTJOeGwiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjo3MX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTYsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJTLTQ5 + SWtCM1pScDRIaVI3MlZndFAiLCJuYW1lIjoiQ29udHJvbCIsImdyb3VwSWQiOiJYM0trc256UkhSZDBnSkNRcTJOeGwiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjo3Mn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTcsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJN + b3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQi + OiJrc0drdW1FX3JMd1N3N1dqZmpfdC0iLCJuYW1lIjoiQWx0IiwiZ3JvdXBJZCI6IlgzS2tzbnpSSFJkMGdKQ1FxMk54bCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFu + bmVsIjowLCJudW1iZXIiOjczfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOCwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6Il92OHJTTmtFMVo3UEVTNkJLVld5YiIsIm5hbWUiOiJUcmltIiwiZ3JvdXBJZCI6InlkNnVzdmZvVXNfQVhkbkJFV2hCZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjc2fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiczFaZS1NTFBWdEQ5UG9TUWRtQnZhIiwibmFtZSI6IkxhdGNoIiwiZ3JvdXBJZCI6 + InlkNnVzdmZvVXNfQVhkbkJFV2hCZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc4fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoxLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoidHFnNFJpaGhJX083aWpKcW9DT1plIiwibmFtZSI6IlRvdWNoIiwiZ3JvdXBJZCI6InlkNnVzdmZvVXNfQVhkbkJFV2hCZiIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc3fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoyLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiLVZiV3ox + RHBVSnpldDhWT1pqaTVtIiwibmFtZSI6Ikdyb3VwIiwiZ3JvdXBJZCI6InlkNnVzdmZvVXNfQVhkbkJFV2hCZiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjow + LCJudW1iZXIiOjc5fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjozLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoieEdtUlhJLVBMTHFKcXhYOENnUzRDIiwibmFtZSI6IlNh + dmUiLCJncm91cElkIjoiRGRkSnZIVVQ0X3k1T2Rva09sOURoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODB9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE5LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiSU1TY29UMFA3MDRxMHdPZ0NhZkpqIiwibmFtZSI6 + IlVuZG8iLCJncm91cElkIjoiRGRkSnZIVVQ0X3k1T2Rva09sOURoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODF9LCJtb2RlIjp7Im1h + eFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIwLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZl + ZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiclR2NEVoenRHYWtrVFVFdFJzSEZJIiwibmFt + ZSI6IkNhbmNlbCIsImdyb3VwSWQiOiJEZGRKdkhVVDRfeTVPZG9rT2w5RGgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4Mn0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjEsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjdFFfN25ydnd1TVZibS00N1ZVZUoi + LCJuYW1lIjoiRW50ZXIiLCJncm91cElkIjoiRGRkSnZIVVQ0X3k1T2Rva09sOURoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODN9LCJt + b2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIyLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiV3FpMzdqcXpkdTNrVjh4QWtG + SE8tIiwibmFtZSI6Ik5hbWUvVmFsdWUiLCJncm91cElkIjoicnRNVnFfaVNzT2ViUkVKcmgzUkFNIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6NTJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIzLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiR0VtVFkzcHJi + QWM4SFhtSnZabUhIIiwibmFtZSI6IkZsaXAiLCJncm91cElkIjoiMkgwMndkOTNLSS1KUS1aQ21FeWJuIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6NTB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI0LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiOTAtaTkw + Z2NtM0hPT2JSN1RTQzV2IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUxIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJvYWdpUXVCZ2MyR0dremZRV0lyd1oiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5 + cGUiOjEyLCJkaXNwbGF5SWQiOjAsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImZlZWRiYWNrVHlwZSI6MX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gxL2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiRWZMZ2FGelVTTm5Fc1A2aTR2WVBoIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMn0sIm1vZGUiOnsibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDIvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJPcTJZNFg1a2xYT1ZzcHNMZXpYU28iLCJn + cm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjEsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjUsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL2xjZC9saW5lMiIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNEpCVFhVMkREc2FkWW80N1ktd0YtIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6 + eyJ0eXBlIjoxMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxG + b3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJZNnZkZmFGYldsbk1zR3RxU25jZUgiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjIsImxpbmUiOjF9LCJtb2RlIjp7Im1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoiY2gzL2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiZHpjODBTYXdZS0xaRmRrdmhMX2Rl + IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBl + IjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29u + dHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJzVTVWNnVqb0gwSm80TFNRRXBYY0siLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5 + SWQiOjMsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiS1RMSnl0VzBzRDIwVXhJNnJpcHQ1IiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xp + bmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ1MDZSWVFubWc5a1JJbUp0NUR1ZnAiLCJncm91cElkIjoibGNkIiwi + c291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjQsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImZlZWRiYWNr + VHlwZSI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0s + ImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibFA1T2IyS0VJcGVuNkNGQWxSS0VVIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMn0sIm1v + ZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlNUc4ZnJ0TTBteTRN + TGt1NUoydVIiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjUsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjUsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L2xj + ZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiTGFfRGNEd3hnUXk3VDJ1eno0TWVfIiwiZ3JvdXBJZCI6Imxj + ZCIsInNvdXJjZSI6eyJ0eXBlIjoxMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvbGNkL2xpbmUxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJkLTFyVmViY1Y3SWplQXY0WW9rZ0siLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyLCJkaXNwbGF5SWQiOjYsImxpbmUiOjF9 + LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudEluZGV4IjoiY2g3L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2si + OmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiTl9SVEowLVND + UDlObmlLdlV0V0pHIiwiZ3JvdXBJZCI6ImxjZCIsInNvdXJjZSI6eyJ0eXBlIjoxMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwi + ZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUxIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJXQ1YzMFFmbGxMclMxb3NvQzBsMDEiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUi + OjEyLCJkaXNwbGF5SWQiOjcsImxpbmUiOjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImZlZWRiYWNrVHlwZSI6MX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L2xjZC9saW5lMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfV0sImNvbnRyb2xsZXJDdXN0b21EYXRhIjp7ImNvbXBhbmlvbiI6eyJjb250cm9scyI6W3siaGVpZ2h0IjozMiwiaWQiOiIwNzIyNmRmOS0zNzBjLTQ0 + OTUtOGI1OC0xNzJhM2E1MGQ3ZTYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVs + VHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gxL3YtcG90L2NvbnRyb2wi + LCJjaDEvdi1zZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjAsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiJiNzE4YzgzNi0wNzE4LTQ2YTEt + ODYwZC05YmY4Yzc1YzgwMTgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdv + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gyL3YtcG90L2NvbnRyb2wiLCJj + aDIvdi1zZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjEyOCwieSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6IjM3ZWE4MDE1LWM4OWYtNGQ4Mi05 + YWI5LTdjOWI4MWViMjlhZSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28i + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDMvdi1wb3QvY29udHJvbCIsImNo + My92LXNlbGVjdCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6MjU2LCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiMWZhMGJiYTAtODBlNy00ZGUzLWI0 + ZWQtNjNmZjZiM2MyNDEwIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6 + eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC92LXBvdC9jb250cm9sIiwiY2g0 + L3Ytc2VsZWN0Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4IjozODQsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiI3YmVhYzgwNC05MjFlLTQ0MzctOGE3 + OC01MzM0Mjc3MTQ2MTkiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g1L3YtcG90L2NvbnRyb2wiLCJjaDUv + di1zZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjUxMiwieSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6Ijg4NGRhYTJiLTc4Y2QtNGYwNC05OWMy + LTgxM2ZhMjExNzNjZSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsi + YW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDYvdi1wb3QvY29udHJvbCIsImNoNi92 + LXNlbGVjdCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6NjQwLCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiZjAwYmE2YTYtZGVkNi00NzU3LWFlZTUt + NTkwOThkNDc1ZDBlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy92LXBvdC9jb250cm9sIiwiY2g3L3Yt + c2VsZWN0Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4Ijo3NjgsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiI0Yjc2YWFmZi02N2M3LTRlMmQtYTUzNi1k + YWQzNjQyZWU2MWYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g4L3YtcG90L2NvbnRyb2wiLCJjaDgvdi1z + ZWxlY3QiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjg5NiwieSI6MH0seyJoZWlnaHQiOjQ4MCwiaWQiOiI0NjhkYzAzNC04ZTRiLTQ3MGItOWE1MS0y + NjQ2OTA2YmI2ZjciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gxL2ZhZGVyIiwiY2gxL2ZhZGVyL3Rv + dWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjowLCJ5Ijo2NH0seyJoZWlnaHQiOjQ4MCwiaWQiOiI4NWIyNDc3ZS0xZWQxLTQ1YTYtYjZiMC1m + NWQ2Mzk2YTg0ZWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gyL2ZhZGVyIiwiY2gyL2ZhZGVyL3Rv + dWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjgsInkiOjY0fSx7ImhlaWdodCI6NDgwLCJpZCI6IjljZGEwMTU0LTM1N2QtNGJmMS05ZGRj + LTY0Zjk3OTA5ZTA2OCIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsi + YW5nbGUiOjI3MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDMvZmFkZXIiLCJjaDMvZmFkZXIv + dG91Y2giXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjI1NiwieSI6NjR9LHsiaGVpZ2h0Ijo0ODAsImlkIjoiYmRkZGJmYzQtM2E4MC00Mzk3LWJj + NDMtYTZlYjkyZWI2MGMxIiwibGFiZWxPbmUiOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6 + eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6InJpZ2h0T2ZSaWdodCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC9mYWRlciIsImNoNC9mYWRl + ci90b3VjaCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6Mzg0LCJ5Ijo2NH0seyJoZWlnaHQiOjQ4MCwiaWQiOiI5ZmUwYWRlZi04YWU5LTRhMDct + YThmNy0xMTkyM2JiMmM0MmEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdv + Ijp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g1L2ZhZGVyIiwiY2g1L2Zh + ZGVyL3RvdWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo1MTIsInkiOjY0fSx7ImhlaWdodCI6NDgwLCJpZCI6IjlkMWZkMzQ3LWUzNzMtNGFm + Mi04MWU0LWFkYTdlODBmMGY2OCIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxU + d28iOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDYvZmFkZXIiLCJjaDYv + ZmFkZXIvdG91Y2giXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjY0MCwieSI6NjR9LHsiaGVpZ2h0Ijo0ODAsImlkIjoiMjU5ZjAzYzYtYWZlNC00 + YTVkLTllMTItNjZmNmQ2MDMyNjI3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJl + bFR3byI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6InJpZ2h0T2ZSaWdodCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy9mYWRlciIsImNo + Ny9mYWRlci90b3VjaCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NzY4LCJ5Ijo2NH0seyJoZWlnaHQiOjQ4MCwiaWQiOiJhMTg2ZjFhMS1lNzhi + LTQzMjAtYjk5ZC0yMjI1NWRhZDBhZTMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxh + YmVsVHdvIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g4L2ZhZGVyIiwi + Y2g4L2ZhZGVyL3RvdWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo4OTYsInkiOjY0fSx7ImhlaWdodCI6MzIsImlkIjoiZmU0MmIzNTMtYjQ5 + MS00YTFlLTg0MzgtYjFjZjMxZDMwMzFmIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFi + ZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDEvc2VsZWN0Il0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo2NCwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiZmIyY2VkOTctNDllMy00ZWEyLThkMTMtMTg2MWVkNDFk + MzJhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDIvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lk + dGgiOjMyLCJ4IjoxOTIsInkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6IjIzMDViYzEyLTk5ZjAtNDc2MC1iNmU1LTY4ZDA0Yjc3OTYwNyIsImxhYmVsT25lIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9t + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gzL3NlbGVjdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MzIwLCJ5Ijoy + ODh9LHsiaGVpZ2h0IjozMiwiaWQiOiIwNTNjYjQ1Yi05MDFmLTRiZGItYWYyNi03MjFjZjUwYjQ3NDciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJj + ZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC9zZWxlY3QiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjQ0OCwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlk + IjoiNTQyMmE2YzUtNGQ0ZS00NjYyLThmNjItNDNkY2MyMjIxNTBlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJj + aDUvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo1NzYsInkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6ImUwYzViZGFjLTUzNDctNGM4 + NS1iMzMyLWYzOTI5ZmM3ZDgxYSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdv + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g2L3NlbGVjdCJdLCJzaGFwZSI6 + InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NzA0LCJ5IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiI0MjVkNTRlMy00OTYyLTQ3ODUtYTU4MC03ODM3MGU1ZWFmMjUi + LCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRp + b24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy9zZWxlY3QiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6 + MzIsIngiOjgzMiwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiZjkyZDliODMtN2NhOC00ZjVhLThhZWQtNzlkMWYzZDgyZTI5IiwibGFiZWxPbmUiOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJz + aXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDgvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo5NjAsInkiOjI4OH0s + eyJoZWlnaHQiOjMyLCJpZCI6ImViMWQyZjM0LTc4YjMtNDM4YS1hZjA4LWM0YTBjNDlmZmNhYyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRl + ciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVl + fSwibWFwcGluZ3MiOlsiY2gxL211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjY0LCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5YTdl + YjVmMS1jNThmLTQyY2QtYTc4NS0xOWU4ZDVhOTZhOTAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMi9tdXRl + Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxOTIsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6IjM1Yzc3Y2UzLTY2NGMtNDI2Ni05ZjcyLWFl + ZDM4YjJmOGJiOSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xl + IjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gzL211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUi + LCJ3aWR0aCI6MzIsIngiOjMyMCwieSI6MzUyfSx7ImhlaWdodCI6MzIsImlkIjoiODVkZDk3OTQtNDQ1Zi00ZjQ2LWJlNjYtODYzN2NlYzZjZWVlIiwibGFiZWxPbmUi + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dC + b3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDQvbXV0ZSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NDQ4LCJ5 + IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI1NmNkZTAwYy03MThiLTQ5NDctODQ2NS1hNjA2Y2JmZTFhMjgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5l + ZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNS9tdXRlIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo1NzYsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJp + ZCI6IjY4YWQ1ZjhmLTc0ZDktNDg3Mi1hZjQ5LWFhOWJmZmNmMDFkZiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsi + Y2g2L211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjcwNCwieSI6MzUyfSx7ImhlaWdodCI6MzIsImlkIjoiMDE4MWVjOTMtNDNkYS00MzEx + LWExM2QtOGZjZGQ1ZmJhYjFlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28i + OnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDcvbXV0ZSJdLCJzaGFwZSI6InJl + Y3RhbmdsZSIsIndpZHRoIjozMiwieCI6ODMyLCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5YTBjZThhNy01MDE3LTQxYjQtOTdjNi03OWIzYzhiYjJlYWUiLCJs + YWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoOC9tdXRlIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4 + Ijo5NjAsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6IjBmZjVhZWU1LTc3OTMtNGM3Zi1hMjA0LTEwZDU4ZWY3NTlhMiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNv + bnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gxL3NvbG8iXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjY0LCJ5Ijo0MTZ9LHsiaGVpZ2h0 + IjozMiwiaWQiOiIzYzRjMmY0YS01ZTgxLTQ4NWItYTI1YS0zMDJiZDlkZjBhMmMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXpl + Q29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBp + bmdzIjpbImNoMi9zb2xvIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxOTIsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6Ijk5MWRmNjBkLTZk + NzItNDJjMC1hY2MxLTg5NjUyNTcyYWZkMiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxh + YmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gzL3NvbG8iXSwic2hh + cGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjMyMCwieSI6NDE2fSx7ImhlaWdodCI6MzIsImlkIjoiOWIyMDBkZjctYTQxMS00YzQxLTg1YWQtZjZhY2MxYTQ5 + ZDRlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDQvc29sbyJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRo + IjozMiwieCI6NDQ4LCJ5Ijo0MTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiI1MGEyN2M2Mi01ZGQzLTQ2M2YtYjJmYS0wY2M5Yzc1MTRjMmYiLCJsYWJlbE9uZSI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNS9zb2xvIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo1NzYsInkiOjQxNn0s + eyJoZWlnaHQiOjMyLCJpZCI6IjE2YTFiZjFhLWU1MzgtNDg5Zi04NGNlLTJjNTVkODkwOThiYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRl + ciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVl + fSwibWFwcGluZ3MiOlsiY2g2L3NvbG8iXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjcwNCwieSI6NDE2fSx7ImhlaWdodCI6MzIsImlkIjoiOTJi + NmI5MjctZTViNC00NTRhLWFmNmQtN2Q2ZWE2MTZiYTgyIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDcvc29s + byJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6ODMyLCJ5Ijo0MTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiJkN2NjNzFhYi1kNzNmLTRhZWYtYTY0NC03 + ZGZjYTcyNzhhMmUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoOC9zb2xvIl0sInNoYXBlIjoicmVjdGFuZ2xl + Iiwid2lkdGgiOjMyLCJ4Ijo5NjAsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6ImRkYzUxOGQ2LWFkMDktNDg1ZS1hZDE3LTIzMDUyMjMwMTFjZCIsImxhYmVsT25l + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVs + b3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDEvcmVjb3JkLXJlYWR5Il0sInNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4 + Ijo2NCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiZmViNzYwZDEtNTk4ZC00MzE3LThmNTUtZDFlYzIxM2VkYTAyIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMi9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjE5MiwieSI6NDgwfSx7 + ImhlaWdodCI6MzIsImlkIjoiNzZhYzdiYTgtNzk4Ny00ZTg5LTlmZjItNDhkYTVlODhkOTU3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVU + b3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sIm1hcHBpbmdzIjpbImNoMy9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjMyMCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlk + IjoiYTViY2ZlMjktYjVmOS00OTM2LThhODItM2EzMGVmMzc1ZDc2IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpb + ImNoNC9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjQ0OCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiOTRhMzg4ZmQtZDE4 + Ny00OWRjLTkxM2ItZDA1YTdlN2M5MDU0IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNS9yZWNvcmQtcmVh + ZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjU3NiwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiYzQ1MTRiN2MtY2U3OC00NGNhLWExZjAtZTA0 + N2YzNTI5MjIzIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNi9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJj + aXJjbGUiLCJ3aWR0aCI6MzIsIngiOjcwNCwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiNGVlMzQ1YjktMDZmNy00MTI3LWE4YTAtOGZkZmQ1ODM5ZTgwIiwibGFi + ZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6 + MzIsIngiOjgzMiwieSI6NDgwfSx7ImhlaWdodCI6MzIsImlkIjoiNzlkNTNjNTAtNjUyYy00MDc0LTg0NDYtNDBmYmMwZGMwYzA1IiwibGFiZWxPbmUiOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoOC9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjk2MCwieSI6 + NDgwfSx7ImhlaWdodCI6NDgwLCJpZCI6ImQ0NGZjYWMzLTdkYzEtNDhmNy1iMmM5LTZlNmJiMmQ0MzI1ZiIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9u + IjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJtYWluL2ZhZGVyIiwibWFpbi9mYWRlci90b3VjaCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTAy + NCwieSI6NjR9LHsiaGVpZ2h0IjozMiwiaWQiOiI4NjFjNWEwOS1mMDMzLTQ5ZGItYjkxMy1iNDc5YmVlNzdhZWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRp + b24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbIm1hcmtlciJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTA4OCwieSI6Mjg4fSx7ImhlaWdodCI6MzIs + ImlkIjoiNjNiYzljNmYtZTU2NC00OGMyLWEzYTEtOWY0OTQzZjc0YWEyIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6 + WyJjaC1yaWdodCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTIxNiwieSI6NjR9LHsiaGVpZ2h0IjozMiwiaWQiOiJkY2NiZGFmZi03MzZlLTQy + ZjMtOGRlZC05NmNkODNhNzllZjgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3 + byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbInJlYWQiXSwic2hhcGUiOiJyZWN0 + YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEwODgsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6IjZiZDcwZDgxLTc3YzctNGE4ZC05MzI3LTM2YmZiNTBiNmQzZSIsImxh + YmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6 + ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsid3JpdGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEw + ODgsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6IjQzOWE0OThhLWJhZWEtNGIyZS1iMTA1LWM1MTAyZGFkMjcxNyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gtbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTE1MiwieSI6NjR9LHsiaGVpZ2h0Ijoz + MiwiaWQiOiIxZmQ2ZmQyZi03OTUxLTQ2ZGItYjIyMy00ZWViMDE4YTk1YmIiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdz + IjpbImJhbmstbGVmdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTE1MiwieSI6MTI4fSx7ImhlaWdodCI6MzIsImlkIjoiNWE1YWIxZDItZDNm + ZC00ZjBmLWE0NzktNDE2N2FlN2Y3NTI3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFi + ZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJiYW5rLXJpZ2h0Il0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjE2LCJ5IjoxMjh9LHsiaGVpZ2h0IjozMiwiaWQiOiIzNWZmZDc3Yy1mMzc5LTRhNGUtYjVjMS0wZjhhNWFl + NDZhMTgiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbInJld2luZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRo + IjozMiwieCI6MTE1MiwieSI6MjI0fSx7ImhlaWdodCI6MzIsImlkIjoiNDAzMTNhYzktNDFkYy00MGQwLWE5NWQtNWRmMDdmZTU3YzlhIiwibGFiZWxPbmUiOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20i + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJmYXN0LWZ3ZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTIxNiwieSI6MjI0 + fSx7ImhlaWdodCI6MzIsImlkIjoiOWE4NWM1MWItOGE3NC00YzA2LWI0YmEtN2RmYTVmMTI2YTVkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2Vu + dGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJtYXBwaW5ncyI6WyJwbGF5Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMTUyLCJ5IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiIzNzdj + N2ZmZi1iY2M2LTRjNGItOGVmZS03MDc5NWYxMTAwNDMiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbInN0b3AiXSwi + c2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEyMTYsInkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6ImViYWUxNmFjLTVlYWYtNDkxOS1hYjZlLWJmM2Fh + NjY3YWFmOCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjow + LCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsicmVjb3JkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lk + dGgiOjMyLCJ4IjoxMTUyLCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiJkZjc5YWFkYS0zNDVlLTQ0NTgtOWM0Ni00NzQ0Y2MxYTJmMDYiLCJsYWJlbE9uZSI6eyJh + bmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRv + bSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImN5Y2xlIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjE2LCJ5IjozNTJ9 + LHsiaGVpZ2h0IjozMiwiaWQiOiJkOWRhZjVkOC04Mzg0LTRlYTUtOGYyMy1hNmI1MjhhNzg0NjEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92 + ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibWFwcGluZ3MiOlsiem9vbSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjo5NiwieCI6MTE1MiwieSI6NDE2fSx7ImhlaWdodCI6MzIsImlkIjoiOTAz + ZjNhOTQtNWQ2Ny00YTlkLTk0Y2EtNDRlZjY3MjU3YWE2IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQi + OnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJqb2ciLCJzY3J1 + YiJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjo5NiwieCI6MTE1MiwieSI6NDgwfV0sImdyaWREaXZpc2lvbkNvdW50IjoyLCJncmlkU2l6ZSI6MzJ9fSwiaW5zdGFu + Y2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 1017 147 752 675 + FXID {467D098A-DF84-954F-B093-C1E5FF19944A} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABPmgEAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS45IiwiaWQiOiJkdFhlcENoLSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwibGl2ZXNPblVwcGVyRmxvb3IiOnRydWUsImNvbnRyb2xEZXZpY2VJZCI6IjE1IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjE1IiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoiZmFkZXIiLCJuYW1lIjoiMDEwIC0gRmFkZXIifSx7 + ImlkIjoidi1wb3QiLCJuYW1lIjoiMDAxIC0gVi1Qb3QiLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJ2LXNlbGVjdCIsIm5hbWUiOiIwMDIgLSBWLVNl + bGVjdCIsImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImZhZGVyLXRvdWNoIiwibmFtZSI6IjAxMSAtIEZhZGVyIFRvdWNoIiwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoic2VsZWN0IiwibmFtZSI6IjAwOSAtIFNlbGVjdCJ9LHsiaWQiOiJtdXRlIiwibmFtZSI6IjAwOCAtIE11dGUifSx7ImlkIjoic29sbyIs + Im5hbWUiOiIwMDcgLSBTb2xvIn0seyJpZCI6InJlY29yZC1yZWFkeSIsIm5hbWUiOiIwMDYgLSBSZWNvcmQtQXJtIn0seyJpZCI6InYtcG90LWxlZHMiLCJuYW1lIjoi + MDAzIC0gVi1Qb3QgTEVEcyIsImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoibGNkIiwibmFtZSI6IjAwNCAtIExDRCIsImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoibWV0ZXIiLCJuYW1lIjoiMDA1IC0gVlUgTWV0ZXJzIiwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIzZnNLSDRIOGJUY2NQcFE0UnZr + TVMiLCJuYW1lIjoiMDEyIC0gRW5jb2RlciBBc3NpZ24gQnV0dG9ucyJ9LHsiaWQiOiIxNnlFT3ZPUHNocm9icEJqak5mS1UiLCJuYW1lIjoiMDE0IC0gR2xvYmFsIEZ1 + bmN0aW9ucyJ9LHsiaWQiOiJYM0trc256UkhSZDBnSkNRcTJOeGwiLCJuYW1lIjoiMDE3IC0gTW9kaWZ5IEJ1dHRvbnMifSx7ImlkIjoieWQ2dXN2Zm9Vc19BWGRuQkVX + aEJmIiwibmFtZSI6IjAxOCAtIEF1dG9tYXRpb24ifSx7ImlkIjoiRGRkSnZIVVQ0X3k1T2Rva09sOURoIiwibmFtZSI6IjAxOSAtIFV0aWxpdHkifSx7ImlkIjoicnRN + VnFfaVNzT2ViUkVKcmgzUkFNIiwibmFtZSI6IjAxMyAtIDctU2VnbWVudCBEaXNwbGF5In0seyJpZCI6IjJIMDJ3ZDkzS0ktSlEtWkNtRXlibiIsIm5hbWUiOiIwMTUg + LSBNYXN0ZXIifSx7ImlkIjoiRjJRNU8wZGowVHN0UGNWaDRjWUhiIiwibmFtZSI6IjAxNiAtIEZ1bmN0aW9uIEJ1dHRvbnMifSx7ImlkIjoiOU1HNnZvTk1WZjl5elNa + UFI5ODhuIiwibmFtZSI6IjAyMCAtIFRyYW5zcG9ydCJ9LHsiaWQiOiJnSmVtdWFmc3lwUmY0T2lRZmNPd1ciLCJuYW1lIjoiMDIxIC0gTmF2aWdhdGlvbiJ9XSwibWFw + cGluZ3MiOlt7ImlkIjoiWlcxYzFHR0lRbmVZV0ZZa1Bvdl9sIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMS9sY2QvbGluZTEifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZWVsRmVlZGJh + Y2tUcmFuc2Zvcm1hdGlvbiI6Ikxvd1NobGYiLCJmZWVkYmFja0NvbG9yIjpbMCwyNTUsMjU1XSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InRyYWNrR1VJRCI6 + InNlbGVjdGVkIiwiZnhBbmNob3IiOiJuYW1lIiwiZnhOYW1lIjoiVlNUOiBSZWFFUSAoQ29ja29zKSIsImVuYWJsZU9ubHlJZkZ4SGFzRm9jdXMiOnRydWUsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9XSwi + Y29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6Im1haW4vZmFkZXIiLCJncm91cElkIjoiMkgwMndkOTNLSS1KUS1aQ21FeWJuIiwic291cmNlIjp7InR5cGUiOjMsImNo + YW5uZWwiOjh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6Im1haW4vZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiam9nIiwiZ3JvdXBJZCI6ImdKZW11YWZzeXBSZjRPaVFmY093VyIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYwLCJjaGFyYWN0ZXIiOjIsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJqb2ciLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJtYWluL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6IjJIMDJ3ZDkzS0ktSlEtWkNtRXlibiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjExMn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFpbi9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZl + ZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im1hcmtlciIsImdyb3VwSWQiOiI5TUc2dm9OTVZmOXl6U1pQUjk4OG4iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo4NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoibWFya2VyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJlYWQiLCJn + cm91cElkIjoieWQ2dXN2Zm9Vc19BWGRuQkVXaEJmIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6InJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoid3JpdGUiLCJncm91cElkIjoieWQ2dXN2Zm9Vc19BWGRuQkVXaEJmIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6IndyaXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImNoLWxlZnQiLCJncm91cElkIjoiZ0plbXVhZnN5cFJmNE9pUWZjT3dXIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDh9LCJtb2Rl + Ijp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLWxlZnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gtcmlnaHQiLCJncm91cElkIjoiZ0plbXVhZnN5cFJm + NE9pUWZjT3dXIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoLXJpZ2h0 + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJhbmstbGVmdCIsImdyb3VwSWQiOiJnSmVtdWFmc3lwUmY0T2lRZmNPd1ciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo0Nn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiYmFuay1sZWZ0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJhbmst + cmlnaHQiLCJncm91cElkIjoiZ0plbXVhZnN5cFJmNE9pUWZjT3dXIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDd9LCJtb2RlIjp7Im1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6ImJhbmstcmlnaHQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicmV3aW5kIiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBS + OTg4biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJyZXdpbmQiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiZmFzdC1md2QiLCJncm91cElkIjoiOU1HNnZvTk1WZjl5elNaUFI5ODhuIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6OTJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImZhc3QtZndkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InBsYXkiLCJncm91cElk + IjoiOU1HNnZvTk1WZjl5elNaUFI5ODhuIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6InBsYXkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic3RvcCIsImdyb3VwSWQiOiI5TUc2dm9OTVZmOXl6U1pQUjk4OG4iLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5M30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijoic3RvcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyZWNv + cmQiLCJncm91cElkIjoiOU1HNnZvTk1WZjl5elNaUFI5ODhuIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTV9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6InJlY29yZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjeWNsZSIsImdyb3VwSWQiOiI5TUc2dm9OTVZmOXl6U1pQUjk4OG4iLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4Nn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3ljbGUiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19fSx7ImlkIjoiem9vbSIsImdyb3VwSWQiOiJnSmVtdWFmc3lwUmY0T2lRZmNPd1ciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDB9 + LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Inpvb20iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoic2NydWIiLCJncm91cElkIjoiZ0plbXVhZnN5cFJm + NE9pUWZjT3dXIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTAxfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJzY3J1YiIs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjdXJzb3ItbGVmdCIsImdyb3VwSWQiOiJnSmVtdWFmc3lwUmY0T2lRZmNPd1ciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjo5OH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY3Vyc29yLWxlZnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vy + c29yLXJpZ2h0IiwiZ3JvdXBJZCI6ImdKZW11YWZzeXBSZjRPaVFmY093VyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk5fSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJzb3ItcmlnaHQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRi + YWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLXVwIiwiZ3JvdXBJZCI6ImdKZW11YWZz + eXBSZjRPaVFmY093VyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjk2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjdXJz + b3ItdXAiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY3Vyc29yLWRvd24iLCJncm91cElkIjoiZ0plbXVhZnN5cFJmNE9pUWZjT3dXIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6OTd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImN1cnNvci1kb3duIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6Im51ZGdlIiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1fSwibW9kZSI6 + eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJudWRnZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJkcm9wIiwiZ3JvdXBJZCI6IjlNRzZ2b05NVmY5eXpTWlBSOTg4 + biIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJkcm9wIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fX0seyJpZCI6InJlcGxhY2UiLCJncm91cElkIjoiOU1HNnZvTk1WZjl5elNaUFI5ODhuIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6ODh9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InJlcGxhY2UiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2xpY2siLCJncm91cElkIjoiOU1H + NnZvTk1WZjl5elNaUFI5ODhuIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6ODl9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNsaWNrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InNvbG8iLCJncm91cElkIjoiOU1HNnZvTk1WZjl5elNaUFI5ODhuIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6OTB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6InNvbG8iLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjEiLCJncm91 + cElkIjoiRjJRNU8wZGowVHN0UGNWaDRjWUhiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImYxIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImYyIiwiZ3JvdXBJZCI6IkYyUTVPMGRqMFRzdFBjVmg0Y1lIYiIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjU1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmMiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJmMyIsImdy + b3VwSWQiOiJGMlE1TzBkajBUc3RQY1ZoNGNZSGIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1Nn0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiZjMiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZjQiLCJncm91cElkIjoiRjJRNU8wZGowVHN0UGNWaDRjWUhiIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTd9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImY1Iiwi + Z3JvdXBJZCI6IkYyUTVPMGRqMFRzdFBjVmg0Y1lIYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjU4fSwibW9kZSI6eyJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJmNSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJmNiIsImdyb3VwSWQiOiJGMlE1TzBkajBUc3RQY1ZoNGNZSGIiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1OX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiZjYiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiOVho + WmYtYWhwSWxVRnZfVnlzY0ZWIiwibmFtZSI6ImY3IChCdXR0b24pIiwiZ3JvdXBJZCI6IkYyUTVPMGRqMFRzdFBjVmg0Y1lIYiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjYwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJmNyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyXzJnRnYzSGVG + Q0pRb002V0tpQy0iLCJuYW1lIjoiZjggKEJ1dHRvbikiLCJncm91cElkIjoiRjJRNU8wZGowVHN0UGNWaDRjWUhiIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6NjF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImY4IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InNtcHRlLWJlYXRzIiwiZ3Jv + dXBJZCI6InJ0TVZxX2lTc09lYlJFSnJoM1JBTSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjUzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJzbXB0ZS1iZWF0cyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJsY2QvYXNzaWdubWVudCIsImdyb3VwSWQiOiJydE1WcV9pU3NPZWJSRUpyaDNS + QU0iLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoibGNkL2Fzc2lnbm1lbnQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImxjZC90aW1lY29kZSIsImdyb3VwSWQiOiJydE1WcV9pU3NPZWJSRUpyaDNSQU0iLCJzb3VyY2UiOnsidHlwZSI6MTIs + ImRpc3BsYXlUeXBlIjoibWFja2llLXNldmVuIiwiZGlzcGxheUlkIjoyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJsY2QvdGltZWNvZGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6ImNoMS92LXNlbGVjdCIsImdyb3VwSWQiOiJ2LXNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMyfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJjaDEvZmFkZXIvdG91Y2giLCJncm91cElkIjoiZmFkZXItdG91Y2giLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMDR9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlci90b3VjaCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNo + MS9zZWxlY3QiLCJncm91cElkIjoic2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNoMS9zZWxlY3QiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL2ZhZGVyIiwiZ3JvdXBJZCI6ImZhZGVyIiwic291cmNlIjp7InR5cGUiOjMs + ImNoYW5uZWwiOjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMS9mYWRlciIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDEvdi1wb3QvY29udHJvbCIsImdyb3VwSWQiOiJ2LXBvdCIsInNvdXJj + ZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJjaGFyYWN0ZXIiOjQsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MS4wLCJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvdi1wb3QiLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qvd3JhcCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7 + InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMwIFswMDEwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS92LXBvdC93cmFwIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3QvYm9vc3QtY3V0IiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2Ui + OnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMDEgZGNiYV0ifSwibW9kZSI6eyJtaW5Tb3VyY2VWYWx1ZSI6MC4wNSwibWF4U291cmNlVmFsdWUi + OjAuNzUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + ImNoMS92LXBvdC9ib29zdC1jdXQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMS92LXBvdC9zaW5nbGUiLCJncm91 + cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMCBbMDAwMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZh + bHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDEvdi1wb3Qvc2luZ2xlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvdi1wb3Qvc3ByZWFkIiwiZ3Jv + dXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzAgWzAwMTEgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VW + YWx1ZSI6MC40LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOiJjaDEvdi1wb3Qvc3ByZWFkIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDEvbXV0ZSIsImdyb3VwSWQiOiJt + dXRlIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTZ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9tdXRlIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMS9zb2xvIiwiZ3JvdXBJZCI6InNvbG8iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4fSwibW9k + ZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvc29sbyIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDEvcmVjb3JkLXJlYWR5IiwiZ3JvdXBJZCI6InJl + Y29yZC1yZWFkeSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9yZWNv + cmQtcmVhZHkiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gxL21ldGVyL3BlYWsiLCJncm91cElkIjoibWV0ZXIiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01p + ZGlQYXR0ZXJuIjoiRDAgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbWV0ZXIvcGVhayIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiY2gyL3Ytc2VsZWN0IiwiZ3JvdXBJZCI6InYtc2VsZWN0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7Im1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXNlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9mYWRlci90 + b3VjaCIsImdyb3VwSWQiOiJmYWRlci10b3VjaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEwNX0sIm1vZGUiOnsibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoiY2gyL2ZhZGVyL3RvdWNoIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3NlbGVjdCIsImdy + b3VwSWQiOiJzZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyNX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gy + L3NlbGVjdCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvZmFkZXIiLCJncm91cElkIjoiZmFkZXIiLCJzb3VyY2UiOnsidHlwZSI6MywiY2hhbm5lbCI6MX0s + Im1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2gyL2ZhZGVyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMi92LXBvdC9jb250cm9sIiwiZ3JvdXBJZCI6InYtcG90Iiwic291cmNlIjp7ImNoYW5uZWwi + OjAsIm51bWJlciI6MTcsImNoYXJhY3RlciI6NCwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjoxLjAsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBvdCIsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0s + ImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC93cmFwIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJh + d01pZGlQYXR0ZXJuIjoiQjAgMzEgWzAwMTAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L3dyYXAiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9ib29zdC1jdXQiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwi + cmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblNvdXJjZVZhbHVlIjowLjA1LCJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3YtcG90L2Jv + b3N0LWN1dCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gyL3YtcG90L3NpbmdsZSIsImdyb3VwSWQiOiJ2LXBvdC1s + ZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMxIFswMDAwIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNzUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBv + dC9zaW5nbGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi92LXBvdC9zcHJlYWQiLCJncm91cElkIjoidi1wb3Qt + bGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMSBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjQsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi92LXBv + dC9zcHJlYWQiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImNoMi9tdXRlIiwiZ3JvdXBJZCI6Im11dGUiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxN30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL211dGUiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + fSx7ImlkIjoiY2gyL3NvbG8iLCJncm91cElkIjoic29sbyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjl9LCJtb2RlIjp7Im1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ImNoMi9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gyL3JlY29yZC1yZWFkeSIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDIvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJE + MCBbMDAwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMi9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1zZWxl + Y3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2gzL3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL2ZhZGVyL3RvdWNoIiwiZ3JvdXBJ + ZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDMvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVj + dCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvc2VsZWN0IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMy9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjoyfSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvZmFkZXIi + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2gzL3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjox + OCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4i + OiJCMCAzMiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVy + biI6IkIwIDMyIFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvdi1wb3QvYm9vc3QtY3V0IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDMvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2Ui + OnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzIgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NpbmdsZSIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNl + Ijp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMyIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3YtcG90L3NwcmVhZCIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2gzL211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjE4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDMv + c29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTB9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoMy9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoMy9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2gzL3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX19LHsiaWQiOiJjaDMvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAxMCBkY2Jh + XSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ImNoMy9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1zZWxlY3QiLCJncm91cElk + Ijoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3Yt + c2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRv + dWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIvdG91 + Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6 + eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fX0seyJpZCI6ImNoNC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjozfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvZmFkZXIiLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlk + IjoiIn19fSx7ImlkIjoiY2g0L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxOSwiY2hhcmFjdGVy + Ijo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNl + fSx7ImlkIjoiY2g0L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzMyBbMDAx + MCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7 + ImlkIjoiY2g0L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFsw + MDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDQvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAs + InJhd01pZGlQYXR0ZXJuIjoiQjAgMzMgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEw + LCJyYXdNaWRpUGF0dGVybiI6IkIwIDMzIFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g0L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjE5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDQvc29sbyIsImdyb3Vw + SWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTF9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9zb2xv + IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNC9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6M30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g0L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJj + aDQvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDAxMSBkY2JhXSJ9LCJtb2RlIjp7 + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9t + ZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3Qi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozNn0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3Ytc2VsZWN0IiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTA4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIvdG91Y2giLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjI4fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNo + NS9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo0fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7Imlk + IjoiY2g1L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMCwiY2hhcmFjdGVyIjo0LCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1 + L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNCBbMDAxMCBkY2JhXSJ9LCJt + b2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3Yt + cG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM0IFswMDAxIGRjYmFdIn0s + Im1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDUvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0 + ZXJuIjoiQjAgMzQgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0 + dGVybiI6IkIwIDM0IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g1L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIwfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDUvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9zb2xvIiwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fX0seyJpZCI6ImNoNS9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6NH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g1L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDUvbWV0ZXIvcGVh + ayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEwMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9tZXRlci9wZWFrIiwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsi + dHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozN30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MTA5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDYvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjI5fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNi9mYWRlciIsImdy + b3VwSWQiOiJmYWRlciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo1fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g2L3YtcG90 + L2NvbnRyb2wiLCJncm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMSwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjEuMCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L3dyYXAi + LCJncm91cElkIjoidi1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNSBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNv + dXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDYvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g2L3YtcG90L2Jvb3N0LWN1 + dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWlu + U291cmNlVmFsdWUiOjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJjaDYvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzUg + WzAwMDAgZGNiYV0ifSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g2L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM1 + IFswMDExIGRjYmFdIn0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZp + b3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g2L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIxfSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDYvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTN9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImNoNi9yZWNvcmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NX0sIm1vZGUi + OnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g2L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDYvbWV0ZXIvcGVhayIsImdyb3VwSWQi + OiJtZXRlciIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDEwMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0 + ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNi9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hh + bm5lbCI6MCwibnVtYmVyIjozOH0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJ + c0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MTEwfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDcvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMwfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNy9mYWRlciIsImdyb3VwSWQiOiJmYWRl + ciIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjo2fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9s + bEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g3L3YtcG90L2NvbnRyb2wiLCJn + cm91cElkIjoidi1wb3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMiwiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjEuMCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudElu + ZGV4IjoiY2g3L3YtcG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L3dyYXAiLCJncm91cElkIjoi + di1wb3QtbGVkcyIsInNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNiBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjow + Ljc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJj + aDcvdi1wb3Qvd3JhcCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g3L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQi + OiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUi + OjAuMDUsIm1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDcvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJj + aDcvdi1wb3Qvc2luZ2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzYgWzAwMDAgZGNiYV0i + fSwibW9kZSI6eyJtYXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g3L3YtcG90L3NwcmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM2IFswMDExIGRjYmFd + In0sIm1vZGUiOnsibWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g3L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9y + RmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + Y2g3L211dGUiLCJncm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDcvbXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5u + ZWwiOjAsIm51bWJlciI6MTR9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoNy9yZWNv + cmQtcmVhZHkiLCJncm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Nn0sIm1vZGUiOnsibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g3L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDcvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNv + dXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDExMCBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJv + bElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1zZWxlY3QiLCJncm91cElkIjoidi1zZWxlY3QiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVt + YmVyIjozOX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Ytc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfSx7ImlkIjoiY2g4L2ZhZGVyL3RvdWNoIiwiZ3JvdXBJZCI6ImZhZGVyLXRvdWNoIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTEx + fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIvdG91Y2giLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + cG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJjaDgvc2VsZWN0IiwiZ3JvdXBJZCI6InNlbGVjdCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMxfSwibW9kZSI6eyJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvc2VsZWN0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC9mYWRlciIsImdyb3VwSWQiOiJmYWRlciIsInNvdXJjZSI6 + eyJ0eXBlIjozLCJjaGFubmVsIjo3fSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvZmFkZXIiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNr + IjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY2g4L3YtcG90L2NvbnRyb2wiLCJncm91cElkIjoidi1w + b3QiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoyMywiY2hhcmFjdGVyIjo0LCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjEuMCwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY2g4L3Yt + cG90IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3dyYXAiLCJncm91cElkIjoidi1wb3QtbGVkcyIs + InNvdXJjZSI6eyJ0eXBlIjoxMCwicmF3TWlkaVBhdHRlcm4iOiJCMCAzNyBbMDAxMCBkY2JhXSJ9LCJtb2RlIjp7Im1heFNvdXJjZVZhbHVlIjowLjc1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvdi1wb3Qvd3Jh + cCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L2Jvb3N0LWN1dCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRz + Iiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDAxIGRjYmFdIn0sIm1vZGUiOnsibWluU291cmNlVmFsdWUiOjAuMDUsIm1heFNv + dXJjZVZhbHVlIjowLjc1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOiJjaDgvdi1wb3QvYm9vc3QtY3V0IiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFs + c2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjaDgvdi1wb3Qvc2lu + Z2xlIiwiZ3JvdXBJZCI6InYtcG90LWxlZHMiLCJzb3VyY2UiOnsidHlwZSI6MTAsInJhd01pZGlQYXR0ZXJuIjoiQjAgMzcgWzAwMDAgZGNiYV0ifSwibW9kZSI6eyJt + YXhTb3VyY2VWYWx1ZSI6MC43NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3NpbmdsZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L3YtcG90L3Nw + cmVhZCIsImdyb3VwSWQiOiJ2LXBvdC1sZWRzIiwic291cmNlIjp7InR5cGUiOjEwLCJyYXdNaWRpUGF0dGVybiI6IkIwIDM3IFswMDExIGRjYmFdIn0sIm1vZGUiOnsi + bWF4U291cmNlVmFsdWUiOjAuNCwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudEluZGV4IjoiY2g4L3YtcG90L3NwcmVhZCIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZh + bHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiY2g4L211dGUiLCJn + cm91cElkIjoibXV0ZSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIzfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgv + bXV0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvc29sbyIsImdyb3VwSWQiOiJzb2xvIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6MTV9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9zb2xvIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImNoOC9yZWNvcmQtcmVhZHkiLCJn + cm91cElkIjoicmVjb3JkLXJlYWR5Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6N30sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY2g4L3JlY29yZC1yZWFkeSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJjaDgvbWV0ZXIvcGVhayIsImdyb3VwSWQiOiJtZXRlciIsInNvdXJjZSI6eyJ0eXBl + IjoxMCwicmF3TWlkaVBhdHRlcm4iOiJEMCBbMDExMSBkY2JhXSJ9LCJtb2RlIjp7Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9tZXRlci9wZWFrIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiIwUDJ5SzItY2xIRzdoa0FGbXhNdEciLCJuYW1lIjoiVHJhY2siLCJncm91cElkIjoiM2ZzS0g0SDhiVGNjUHBRNFJ2a01TIiwic291cmNlIjp7 + InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6 + ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJnMnZsR091 + NW8xQmhDeGdPVTVzdXEiLCJuYW1lIjoiUGFuIC8gU3Vycm91bmQiLCJncm91cElkIjoiM2ZzS0g0SDhiVGNjUHBRNFJ2a01TIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NDJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJqcDlqcUtLNmd0QXdZWDZYVlNLQU0iLCJuYW1lIjoiRVEiLCJncm91cElkIjoiM2ZzS0g0SDhiVGNjUHBRNFJ2a01TIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6NDR9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsi + aWQiOiJEWFNpRnoxeUo1WXU0RHhWdGdwVk8iLCJuYW1lIjoiU2VuZCIsImdyb3VwSWQiOiIzZnNLSDRIOGJUY2NQcFE0UnZrTVMiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjo0MX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MywibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0s + eyJpZCI6IjJpNmlEOGU5dGRxSVVMOXNsLW1QSCIsIm5hbWUiOiJQbHVnLUluIiwiZ3JvdXBJZCI6IjNmc0tINEg4YlRjY1BwUTRSdmtNUyIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQzfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0LCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19fSx7ImlkIjoiTnNSeThLVVplcGQ1cUhSOFVuQmZGIiwibmFtZSI6Ikluc3QiLCJncm91cElkIjoiM2ZzS0g0SDhiVGNjUHBRNFJ2a01TIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDV9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjUsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQi + OiIifX19LHsiaWQiOiJqNXBCSWgyd0x3RlNVa29kZjh1ZngiLCJuYW1lIjoiR2xvYmFsIFZpZXciLCJncm91cElkIjoiMTZ5RU92T1BzaHJvYnBCampOZktVIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTF9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjYsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX19LHsiaWQiOiI3bVZKZWhsMDRGVUQtRXJGVFFBZlYiLCJuYW1lIjoiTUlESSBUcmFja3MiLCJncm91cElkIjoiMTZ5RU92T1BzaHJvYnBCampO + ZktVIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjcsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJnMUV4NGlXcDZsUkw3NWxRU25tR2wiLCJuYW1lIjoiSW5wdXRzIiwiZ3JvdXBJZCI6IjE2eUVPdk9Qc2hyb2Jw + QmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjYzfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo4LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoia2dSVjR5TVEwQWdWRllmSFpSNURGIiwibmFtZSI6IkF1ZGlvIFRyYWNrcyIsImdyb3VwSWQiOiIxNnlF + T3ZPUHNocm9icEJqak5mS1UiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2NH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6OSwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IjRmOW5sc0VTOWtSU1k1YnRzQ2g3TiIsIm5hbWUiOiJBdWRpbyBJbnN0IiwiZ3JvdXBJ + ZCI6IjE2eUVPdk9Qc2hyb2JwQmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY1fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4w + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2Us + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IjJzTldQRzlTMWtucG4tVHQ5RmNncCIsIm5hbWUiOiJBdXgiLCJncm91 + cElkIjoiMTZ5RU92T1BzaHJvYnBCampOZktVIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjZ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxz + ZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiUUZVMVRoSDh3eWhzQUxLcXhxUnlaIiwibmFtZSI6IkJ1c2VzIiwi + Z3JvdXBJZCI6IjE2eUVPdk9Qc2hyb2JwQmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY3fSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkdGY09aWDZxbC1XMkpPLUFya2VZRSIsIm5hbWUiOiJPdXRw + dXRzIiwiZ3JvdXBJZCI6IjE2eUVPdk9Qc2hyb2JwQmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY4fSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMywibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVk + YmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ik9sb3RPS1ZJT3htX2Y0NmFHQjE1cCIsIm5hbWUi + OiJVc2VyIiwiZ3JvdXBJZCI6IjE2eUVPdk9Qc2hyb2JwQmpqTmZLVSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY5fSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JG + ZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImV5WlV6SDVvVDZrZVpWUEhuSEFmSiIsIm5h + bWUiOiJTaGlmdCIsImdyb3VwSWQiOiJYM0trc256UkhSZDBnSkNRcTJOeGwiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3MH0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTUsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJsVGJtX2RmWUl5U1hDQkRZMjdScjAi + LCJuYW1lIjoiT3B0aW9uIiwiZ3JvdXBJZCI6IlgzS2tzbnpSSFJkMGdKQ1FxMk54bCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjcxfSwi + bW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlMtNDlJa0IzWlJwNEhpUjcy + Vmd0UCIsIm5hbWUiOiJDb250cm9sIiwiZ3JvdXBJZCI6IlgzS2tzbnpSSFJkMGdKQ1FxMk54bCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIi + OjcyfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNywibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImtzR2t1bUVfckx3 + U3c3V2pmal90LSIsIm5hbWUiOiJBbHQiLCJncm91cElkIjoiWDNLa3NuelJIUmQwZ0pDUXEyTnhsIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJl + ciI6NzN9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiX3Y4clNOa0Ux + WjdQRVM2QktWV3liIiwibmFtZSI6IlRyaW0iLCJncm91cElkIjoieWQ2dXN2Zm9Vc19BWGRuQkVXaEJmIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6NzZ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJzMVplLU1MUFZ0RDlQb1NRZG1CdmEiLCJuYW1lIjoiTGF0Y2giLCJncm91cElkIjoieWQ2dXN2Zm9Vc19B + WGRuQkVXaEJmIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Nzh9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJ0cWc0UmloaElfTzdpakpxb0NPWmUiLCJuYW1lIjoiVG91Y2giLCJncm91cElkIjoieWQ2dXN2Zm9Vc19BWGRuQkVXaEJmIiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6Nzd9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiItVmJXejFEcFVKemV0OFZPWmpp + NW0iLCJuYW1lIjoiR3JvdXAiLCJncm91cElkIjoieWQ2dXN2Zm9Vc19BWGRuQkVXaEJmIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Nzl9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjMsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ4R21SWEktUExMcUpxeFg4Q2dTNEMiLCJuYW1lIjoiU2F2ZSIsImdyb3VwSWQi + OiJEZGRKdkhVVDRfeTVPZG9rT2w5RGgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTksIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJJTVNjb1QwUDcwNHEwd09nQ2FmSmoiLCJuYW1lIjoiVW5kbyIsImdyb3Vw + SWQiOiJEZGRKdkhVVDRfeTVPZG9rT2w5RGgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4MX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAu + MDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNl + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyVHY0RWh6dEdha2tUVUV0UnNIRkkiLCJuYW1lIjoiQ2FuY2VsIiwi + Z3JvdXBJZCI6IkRkZEp2SFVUNF95NU9kb2tPbDlEaCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjgyfSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyMSwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6 + ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImN0UV83bnJ2d3VNVmJtLTQ3VlVlSiIsIm5hbWUiOiJFbnRl + ciIsImdyb3VwSWQiOiJEZGRKdkhVVDRfeTVPZG9rT2w5RGgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4M30sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJh + Y2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJXcWkzN2pxemR1M2tWOHhBa0ZITy0iLCJuYW1lIjoi + TmFtZS9WYWx1ZSIsImdyb3VwSWQiOiJydE1WcV9pU3NPZWJSRUpyaDNSQU0iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1Mn0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjMsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xs + Rm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJHRW1UWTNwcmJBYzhIWG1KdlptSEgi + LCJuYW1lIjoiRmxpcCIsImdyb3VwSWQiOiIySDAyd2Q5M0tJLUpRLVpDbUV5Ym4iLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1MH0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjQsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJw + b2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI5MC1pOTBnY20zSE9PYlI3VFND + NXYiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5 + cGUiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMS9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJj + b250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Im9hZ2lRdUJnYzJHR2t6ZlFXSXJ3WiIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3Bs + YXlJZCI6MCwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDEvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9 + LHsiaWQiOiJFZkxnYUZ6VVNObkVzUDZpNHZZUGgiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoMi9sY2Qv + bGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ik9xMlk0WDVrbFhPVnNwc0xlelhTbyIsImdyb3VwSWQiOiJsY2Qi + LCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MSwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJh + Y2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDIvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI0SkJUWFUyRERzYWRZbzQ3WS13Ri0iLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyfSwi + bW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNoMy9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpm + YWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6Ilk2dmRmYUZiV2xu + TXNHdHFTbmNlSCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MiwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDMv + bGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJkemM4MFNhd1lLTFpGZGt2aExfZGUiLCJncm91cElkIjoi + bGNkIiwic291cmNlIjp7InR5cGUiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNC9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6InNVNVY2dWpvSDBKbzRMU1FFcFhjSyIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6MywibGluZSI6 + MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDQvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFj + ayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJLVExKeXRX + MHNEMjBVeEk2cmlwdDUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1 + LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNS9sY2QvbGluZTEiLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6InUwNlJZUW5tZzlrUkltSnQ1RHVmcCIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlw + ZSI6MTIsImRpc3BsYXlJZCI6NCwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDUvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5h + YmxlZCI6ZmFsc2V9LHsiaWQiOiJsUDVPYjJLRUlwZW42Q0ZBbFJLRVUiLCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyfSwibW9kZSI6eyJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6ImNoNi9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImU1RzhmcnRNMG15NE1Ma3U1SjJ1UiIsImdy + b3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NSwibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDYvbGNkL2xpbmUyIiwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJMYV9EY0R3eGdReTdUMnV6ejRNZV8iLCJncm91cElkIjoibGNkIiwic291cmNlIjp7 + InR5cGUiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5cGUiOjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoNy9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZv + ckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + ImQtMXJWZWJjVjdJamVBdjRZb2tnSyIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJZCI6NiwibGluZSI6MX0sIm1vZGUiOnsibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOiJjaDcvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJOX1JUSjAtU0NQOU5uaUt2VXRXSkci + LCJncm91cElkIjoibGNkIiwic291cmNlIjp7InR5cGUiOjEyfSwibW9kZSI6eyJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJmZWVkYmFja1R5cGUi + OjF9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNoOC9sY2QvbGluZTEiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJjb250 + cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IldDVjMwUWZsbExyUzFvc29DMGwwMSIsImdyb3VwSWQiOiJsY2QiLCJzb3VyY2UiOnsidHlwZSI6MTIsImRpc3BsYXlJ + ZCI6NywibGluZSI6MX0sIm1vZGUiOnsibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjaDgvbGNkL2xpbmUyIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBv + bGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9XSwi + Y29udHJvbGxlckN1c3RvbURhdGEiOnsiY29tcGFuaW9uIjp7ImNvbnRyb2xzIjpbeyJoZWlnaHQiOjMyLCJpZCI6IjA3MjI2ZGY5LTM3MGMtNDQ5NS04YjU4LTE3MmEz + YTUwZDdlNiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUi + OjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDEvdi1wb3QvY29udHJvbCIsImNoMS92LXNlbGVj + dCJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6MCwieSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6ImI3MThjODM2LTA3MTgtNDZhMS04NjBkLTliZjhjNzVj + ODAxOCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDIvdi1wb3QvY29udHJvbCIsImNoMi92LXNlbGVjdCJd + LCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6MTI4LCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiMzdlYTgwMTUtYzg5Zi00ZDgyLTlhYjktN2M5YjgxZWIy + OWFlIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMy92LXBvdC9jb250cm9sIiwiY2gzL3Ytc2VsZWN0Il0s + InNoYXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4IjoyNTYsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiIxZmEwYmJhMC04MGU3LTRkZTMtYjRlZC02M2ZmNmIzYzI0 + MTAiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJw + b3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g0L3YtcG90L2NvbnRyb2wiLCJjaDQvdi1zZWxlY3QiXSwi + c2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjM4NCwieSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6IjdiZWFjODA0LTkyMWUtNDQzNy04YTc4LTUzMzQyNzcxNDYx + OSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBv + c2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDUvdi1wb3QvY29udHJvbCIsImNoNS92LXNlbGVjdCJdLCJz + aGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6NTEyLCJ5IjowfSx7ImhlaWdodCI6MzIsImlkIjoiODg0ZGFhMmItNzhjZC00ZjA0LTk5YzItODEzZmEyMTE3M2Nl + IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9z + aXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNi92LXBvdC9jb250cm9sIiwiY2g2L3Ytc2VsZWN0Il0sInNo + YXBlIjoiY2lyY2xlIiwid2lkdGgiOjMyLCJ4Ijo2NDAsInkiOjB9LHsiaGVpZ2h0IjozMiwiaWQiOiJmMDBiYTZhNi1kZWQ2LTQ3NTctYWVlNS01OTA5OGQ0NzVkMGUi + LCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g3L3YtcG90L2NvbnRyb2wiLCJjaDcvdi1zZWxlY3QiXSwic2hh + cGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjc2OCwieSI6MH0seyJoZWlnaHQiOjMyLCJpZCI6IjRiNzZhYWZmLTY3YzctNGUyZC1hNTM2LWRhZDM2NDJlZTYxZiIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDgvdi1wb3QvY29udHJvbCIsImNoOC92LXNlbGVjdCJdLCJzaGFw + ZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6ODk2LCJ5IjowfSx7ImhlaWdodCI6NDgwLCJpZCI6IjQ2OGRjMDM0LThlNGItNDcwYi05YTUxLTI2NDY5MDZiYjZmNyIs + ImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjI3MCwicG9z + aXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDEvZmFkZXIiLCJjaDEvZmFkZXIvdG91Y2giXSwic2hhcGUi + OiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjAsInkiOjY0fSx7ImhlaWdodCI6NDgwLCJpZCI6Ijg1YjI0NzdlLTFlZDEtNDVhNi1iNmIwLWY1ZDYzOTZhODRlZSIs + ImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjI3MCwicG9z + aXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDIvZmFkZXIiLCJjaDIvZmFkZXIvdG91Y2giXSwic2hhcGUi + OiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEyOCwieSI6NjR9LHsiaGVpZ2h0Ijo0ODAsImlkIjoiOWNkYTAxNTQtMzU3ZC00YmYxLTlkZGMtNjRmOTc5MDllMDY4 + IiwibGFiZWxPbmUiOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MjcwLCJw + b3NpdGlvbiI6InJpZ2h0T2ZSaWdodCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMy9mYWRlciIsImNoMy9mYWRlci90b3VjaCJdLCJzaGFw + ZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MjU2LCJ5Ijo2NH0seyJoZWlnaHQiOjQ4MCwiaWQiOiJiZGRkYmZjNC0zYTgwLTQzOTctYmM0My1hNmViOTJlYjYw + YzEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjoyNzAs + InBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g0L2ZhZGVyIiwiY2g0L2ZhZGVyL3RvdWNoIl0sInNo + YXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjozODQsInkiOjY0fSx7ImhlaWdodCI6NDgwLCJpZCI6IjlmZTBhZGVmLThhZTktNGEwNy1hOGY3LTExOTIzYmIy + YzQyYSIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjI3 + MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDUvZmFkZXIiLCJjaDUvZmFkZXIvdG91Y2giXSwi + c2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjUxMiwieSI6NjR9LHsiaGVpZ2h0Ijo0ODAsImlkIjoiOWQxZmQzNDctZTM3My00YWYyLTgxZTQtYWRhN2U4 + MGYwZjY4IiwibGFiZWxPbmUiOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6 + MjcwLCJwb3NpdGlvbiI6InJpZ2h0T2ZSaWdodCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNi9mYWRlciIsImNoNi9mYWRlci90b3VjaCJd + LCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NjQwLCJ5Ijo2NH0seyJoZWlnaHQiOjQ4MCwiaWQiOiIyNTlmMDNjNi1hZmU0LTRhNWQtOWUxMi02NmY2 + ZDYwMzI2MjciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xl + IjoyNzAsInBvc2l0aW9uIjoicmlnaHRPZlJpZ2h0Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g3L2ZhZGVyIiwiY2g3L2ZhZGVyL3RvdWNo + Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo3NjgsInkiOjY0fSx7ImhlaWdodCI6NDgwLCJpZCI6ImExODZmMWExLWU3OGItNDMyMC1iOTlkLTIy + MjU1ZGFkMGFlMyIsImxhYmVsT25lIjp7ImFuZ2xlIjoyNzAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5n + bGUiOjI3MCwicG9zaXRpb24iOiJyaWdodE9mUmlnaHQiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDgvZmFkZXIiLCJjaDgvZmFkZXIvdG91 + Y2giXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjg5NiwieSI6NjR9LHsiaGVpZ2h0IjozMiwiaWQiOiJmZTQyYjM1My1iNDkxLTRhMWUtODQzOC1i + MWNmMzFkMzAzMWYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMS9zZWxlY3QiXSwic2hhcGUiOiJyZWN0YW5n + bGUiLCJ3aWR0aCI6MzIsIngiOjY0LCJ5IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiJmYjJjZWQ5Ny00OWUzLTRlYTItOGQxMy0xODYxZWQ0MWQzMmEiLCJsYWJlbE9u + ZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxv + d0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMi9zZWxlY3QiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjE5 + MiwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiMjMwNWJjMTItOTlmMC00NzYwLWI2ZTUtNjhkMDRiNzc5NjA3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0 + aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3Ry + YWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDMvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjozMjAsInkiOjI4OH0seyJoZWlnaHQi + OjMyLCJpZCI6IjA1M2NiNDViLTkwMWYtNGJkYi1hZjI2LTcyMWNmNTBiNDc0NyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGlu + Z3MiOlsiY2g0L3NlbGVjdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NDQ4LCJ5IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiI1NDIyYTZjNS00 + ZDRlLTQ2NjItOGY2Mi00M2RjYzIyMjE1MGUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJs + YWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNS9zZWxlY3QiXSwi + c2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjU3NiwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiZTBjNWJkYWMtNTM0Ny00Yzg1LWIzMzItZjM5Mjlm + YzdkODFhIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDYvc2VsZWN0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwi + d2lkdGgiOjMyLCJ4Ijo3MDQsInkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6IjQyNWQ1NGUzLTQ5NjItNDc4NS1hNTgwLTc4MzcwZTVlYWYyNSIsImxhYmVsT25lIjp7 + ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90 + dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g3L3NlbGVjdCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6ODMyLCJ5 + IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiJmOTJkOWI4My03Y2E4LTRmNWEtOGFlZC03OWQxZjNkODJlMjkiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5l + ZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoOC9zZWxlY3QiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjk2MCwieSI6Mjg4fSx7ImhlaWdodCI6MzIs + ImlkIjoiZWIxZDJmMzQtNzhiMy00MzhhLWFmMDgtYzRhMGM0OWZmY2FjIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6 + WyJjaDEvbXV0ZSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NjQsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6IjlhN2ViNWYxLWM1OGYtNDJj + ZC1hNzg1LTE5ZThkNWE5NmE5MCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdv + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gyL211dGUiXSwic2hhcGUiOiJy + ZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjE5MiwieSI6MzUyfSx7ImhlaWdodCI6MzIsImlkIjoiMzVjNzdjZTMtNjY0Yy00MjY2LTlmNzItYWVkMzhiMmY4YmI5Iiwi + bGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9u + IjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDMvbXV0ZSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwi + eCI6MzIwLCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiI4NWRkOTc5NC00NDVmLTRmNDYtYmU2Ni04NjM3Y2VjNmNlZWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVD + b25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC9tdXRlIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo0NDgsInkiOjM1Mn0seyJoZWln + aHQiOjMyLCJpZCI6IjU2Y2RlMDBjLTcxOGItNDk0Ny04NDY1LWE2MDZjYmZlMWEyOCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNp + emVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFw + cGluZ3MiOlsiY2g1L211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjU3NiwieSI6MzUyfSx7ImhlaWdodCI6MzIsImlkIjoiNjhhZDVmOGYt + NzRkOS00ODcyLWFmNDktYWE5YmZmY2YwMWRmIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwi + bGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDYvbXV0ZSJdLCJz + aGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NzA0LCJ5IjozNTJ9LHsiaGVpZ2h0IjozMiwiaWQiOiIwMTgxZWM5My00M2RhLTQzMTEtYTEzZC04ZmNkZDVm + YmFiMWUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwi + cG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy9tdXRlIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lk + dGgiOjMyLCJ4Ijo4MzIsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6IjlhMGNlOGE3LTUwMTctNDFiNC05N2M2LTc5YjNjOGJiMmVhZSIsImxhYmVsT25lIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9t + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g4L211dGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjk2MCwieSI6MzUy + fSx7ImhlaWdodCI6MzIsImlkIjoiMGZmNWFlZTUtNzc5My00YzdmLWEyMDQtMTBkNThlZjc1OWEyIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2Vu + dGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRy + dWV9LCJtYXBwaW5ncyI6WyJjaDEvc29sbyJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NjQsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6IjNj + NGMyZjRhLTVlODEtNDg1Yi1hMjVhLTMwMmJkOWRmMGEyYyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6 + dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2gyL3Nv + bG8iXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjE5MiwieSI6NDE2fSx7ImhlaWdodCI6MzIsImlkIjoiOTkxZGY2MGQtNmQ3Mi00MmMwLWFjYzEt + ODk2NTI1NzJhZmQyIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJjaDMvc29sbyJdLCJzaGFwZSI6InJlY3Rhbmds + ZSIsIndpZHRoIjozMiwieCI6MzIwLCJ5Ijo0MTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5YjIwMGRmNy1hNDExLTRjNDEtODVhZC1mNmFjYzFhNDlkNGUiLCJsYWJlbE9u + ZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxv + d0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNC9zb2xvIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo0NDgs + InkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6IjUwYTI3YzYyLTVkZDMtNDYzZi1iMmZhLTBjYzljNzUxNGMyZiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g1L3NvbG8iXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjU3NiwieSI6NDE2fSx7ImhlaWdodCI6MzIs + ImlkIjoiMTZhMWJmMWEtZTUzOC00ODlmLTg0Y2UtMmM1NWQ4OTA5OGJiIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6 + WyJjaDYvc29sbyJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6NzA0LCJ5Ijo0MTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5MmI2YjkyNy1lNWI0LTQ1 + NGEtYWY2ZC03ZDZlYTYxNmJhODIiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3 + byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoNy9zb2xvIl0sInNoYXBlIjoi + cmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4Ijo4MzIsInkiOjQxNn0seyJoZWlnaHQiOjMyLCJpZCI6ImQ3Y2M3MWFiLWQ3M2YtNGFlZi1hNjQ0LTdkZmNhNzI3OGEyZSIs + ImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g4L3NvbG8iXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIs + IngiOjk2MCwieSI6NDE2fSx7ImhlaWdodCI6MzIsImlkIjoiZGRjNTE4ZDYtYWQwOS00ODVlLWFkMTctMjMwNTIyMzAxMWNkIiwibGFiZWxPbmUiOnsiYW5nbGUiOjAs + InBvc2l0aW9uIjoiYWJvdmVUb3AiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNp + emVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoMS9yZWNvcmQtcmVhZHkiXSwic2hhcGUiOiJjaXJjbGUiLCJ3aWR0aCI6MzIsIngiOjY0LCJ5Ijo0ODB9 + LHsiaGVpZ2h0IjozMiwiaWQiOiJmZWI3NjBkMS01OThkLTQzMTctOGY1NS1kMWVjMjEzZWRhMDIiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92 + ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0 + cnVlfSwibWFwcGluZ3MiOlsiY2gyL3JlY29yZC1yZWFkeSJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6MTkyLCJ5Ijo0ODB9LHsiaGVpZ2h0IjozMiwi + aWQiOiI3NmFjN2JhOC03OTg3LTRlODktOWZmMi00OGRhNWU4OGQ5NTciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25z + dHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3Mi + OlsiY2gzL3JlY29yZC1yZWFkeSJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6MzIwLCJ5Ijo0ODB9LHsiaGVpZ2h0IjozMiwiaWQiOiJhNWJjZmUyOS1i + NWY5LTQ5MzYtOGE4Mi0zYTMwZWYzNzVkNzYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + ImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g0L3JlY29yZC1y + ZWFkeSJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6NDQ4LCJ5Ijo0ODB9LHsiaGVpZ2h0IjozMiwiaWQiOiI5NGEzODhmZC1kMTg3LTQ5ZGMtOTEzYi1k + MDVhN2U3YzkwNTQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFu + Z2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g1L3JlY29yZC1yZWFkeSJdLCJzaGFwZSI6 + ImNpcmNsZSIsIndpZHRoIjozMiwieCI6NTc2LCJ5Ijo0ODB9LHsiaGVpZ2h0IjozMiwiaWQiOiJjNDUxNGI3Yy1jZTc4LTQ0Y2EtYTFmMC1lMDQ3ZjM1MjkyMjMiLCJs + YWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlv + biI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g2L3JlY29yZC1yZWFkeSJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRo + IjozMiwieCI6NzA0LCJ5Ijo0ODB9LHsiaGVpZ2h0IjozMiwiaWQiOiI0ZWUzNDViOS0wNmY3LTQxMjctYThhMC04ZmRmZDU4MzllODAiLCJsYWJlbE9uZSI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9t + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g3L3JlY29yZC1yZWFkeSJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6ODMyLCJ5 + Ijo0ODB9LHsiaGVpZ2h0IjozMiwiaWQiOiI3OWQ1M2M1MC02NTJjLTQwNzQtODQ0Ni00MGZiYzBkYzBjMDUiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24i + OiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFp + bmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY2g4L3JlY29yZC1yZWFkeSJdLCJzaGFwZSI6ImNpcmNsZSIsIndpZHRoIjozMiwieCI6OTYwLCJ5Ijo0ODB9LHsiaGVpZ2h0 + Ijo0ODAsImlkIjoiZDQ0ZmNhYzMtN2RjMS00OGY3LWIyYzktNmU2YmIyZDQzMjVmIiwibGFiZWxPbmUiOnsiYW5nbGUiOjI3MCwicG9zaXRpb24iOiJjZW50ZXIiLCJz + aXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MjcwLCJwb3NpdGlvbiI6InJpZ2h0T2ZSaWdodCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0s + Im1hcHBpbmdzIjpbIm1haW4vZmFkZXIiLCJtYWluL2ZhZGVyL3RvdWNoIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMDI0LCJ5Ijo2NH0seyJo + ZWlnaHQiOjMyLCJpZCI6Ijg2MWM1YTA5LWYwMzMtNDlkYi1iOTEzLWI0NzliZWU3N2FlZSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIs + InNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwi + bWFwcGluZ3MiOlsibWFya2VyIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMDg4LCJ5IjoyODh9LHsiaGVpZ2h0IjozMiwiaWQiOiI2M2JjOWM2 + Zi1lNTY0LTQ4YzItYTNhMS05ZjQ5NDNmNzRhYTIiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImNoLXJpZ2h0Il0s + InNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjE2LCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6ImRjY2JkYWZmLTczNmUtNDJmMy04ZGVkLTk2Y2Q4 + M2E3OWVmOCIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjow + LCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsicmVhZCJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRo + IjozMiwieCI6MTA4OCwieSI6MzUyfSx7ImhlaWdodCI6MzIsImlkIjoiNmJkNzBkODEtNzdjNy00YThkLTkzMjctMzZiZmI1MGI2ZDNlIiwibGFiZWxPbmUiOnsiYW5n + bGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20i + LCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJ3cml0ZSJdLCJzaGFwZSI6InJlY3RhbmdsZSIsIndpZHRoIjozMiwieCI6MTA4OCwieSI6NDE2fSx7 + ImhlaWdodCI6MzIsImlkIjoiNDM5YTQ5OGEtYmFlYS00YjJlLWIxMDUtYzUxMDJkYWQyNzE3IiwibGFiZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVy + Iiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9 + LCJtYXBwaW5ncyI6WyJjaC1sZWZ0Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMTUyLCJ5Ijo2NH0seyJoZWlnaHQiOjMyLCJpZCI6IjFmZDZm + ZDJmLTc5NTEtNDZkYi1iMjIzLTRlZWIwMThhOTViYiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1 + ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiYmFuay1sZWZ0 + Il0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMTUyLCJ5IjoxMjh9LHsiaGVpZ2h0IjozMiwiaWQiOiI1YTVhYjFkMi1kM2ZkLTRmMGYtYTQ3OS00 + MTY3YWU3Zjc1MjciLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmds + ZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImJhbmstcmlnaHQiXSwic2hhcGUiOiJyZWN0YW5n + bGUiLCJ3aWR0aCI6MzIsIngiOjEyMTYsInkiOjEyOH0seyJoZWlnaHQiOjMyLCJpZCI6IjM1ZmZkNzdjLWYzNzktNGE0ZS1iNWMxLTBmOGE1YWU0NmExOCIsImxhYmVs + T25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJl + bG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsicmV3aW5kIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMTUy + LCJ5IjoyMjR9LHsiaGVpZ2h0IjozMiwiaWQiOiI0MDMxM2FjOS00MWRjLTQwZDAtYTk1ZC01ZGYwN2ZlNTdjOWEiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRp + b24iOiJjZW50ZXIiLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJh + aW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImZhc3QtZndkIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjMyLCJ4IjoxMjE2LCJ5IjoyMjR9LHsiaGVpZ2h0Ijoz + MiwiaWQiOiI5YTg1YzUxYi04YTc0LTRjMDYtYjRiYS03ZGZhNWYxMjZhNWQiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJjZW50ZXIiLCJzaXplQ29u + c3RyYWluZWQiOnRydWV9LCJsYWJlbFR3byI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJiZWxvd0JvdHRvbSIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdz + IjpbInBsYXkiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjExNTIsInkiOjI4OH0seyJoZWlnaHQiOjMyLCJpZCI6IjM3N2M3ZmZmLWJjYzYtNGM0 + Yi04ZWZlLTcwNzk1ZjExMDA0MyIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdv + Ijp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsic3RvcCJdLCJzaGFwZSI6InJlY3Rh + bmdsZSIsIndpZHRoIjozMiwieCI6MTIxNiwieSI6Mjg4fSx7ImhlaWdodCI6MzIsImlkIjoiZWJhZTE2YWMtNWVhZi00OTE5LWFiNmUtYmYzYWE2NjdhYWY4IiwibGFi + ZWxPbmUiOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiY2VudGVyIiwic2l6ZUNvbnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoi + YmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5ncyI6WyJyZWNvcmQiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEx + NTIsInkiOjM1Mn0seyJoZWlnaHQiOjMyLCJpZCI6ImRmNzlhYWRhLTM0NWUtNDQ1OC05YzQ2LTQ3NDRjYzFhMmYwNiIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3Np + dGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVsVHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImJlbG93Qm90dG9tIiwic2l6ZUNvbnN0 + cmFpbmVkIjp0cnVlfSwibWFwcGluZ3MiOlsiY3ljbGUiXSwic2hhcGUiOiJyZWN0YW5nbGUiLCJ3aWR0aCI6MzIsIngiOjEyMTYsInkiOjM1Mn0seyJoZWlnaHQiOjMy + LCJpZCI6ImQ5ZGFmNWQ4LTgzODQtNGVhNS04ZjIzLWE2YjUyOGE3ODQ2MSIsImxhYmVsT25lIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImFib3ZlVG9wIiwic2l6ZUNv + bnN0cmFpbmVkIjp0cnVlfSwibGFiZWxUd28iOnsiYW5nbGUiOjAsInBvc2l0aW9uIjoiYmVsb3dCb3R0b20iLCJzaXplQ29uc3RyYWluZWQiOnRydWV9LCJtYXBwaW5n + cyI6WyJ6b29tIl0sInNoYXBlIjoicmVjdGFuZ2xlIiwid2lkdGgiOjk2LCJ4IjoxMTUyLCJ5Ijo0MTZ9LHsiaGVpZ2h0IjozMiwiaWQiOiI5MDNmM2E5NC01ZDY3LTRh + OWQtOTRjYS00NGVmNjcyNTdhYTYiLCJsYWJlbE9uZSI6eyJhbmdsZSI6MCwicG9zaXRpb24iOiJhYm92ZVRvcCIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sImxhYmVs + VHdvIjp7ImFuZ2xlIjowLCJwb3NpdGlvbiI6ImNlbnRlciIsInNpemVDb25zdHJhaW5lZCI6dHJ1ZX0sIm1hcHBpbmdzIjpbImpvZyIsInNjcnViIl0sInNoYXBlIjoi + Y2lyY2xlIiwid2lkdGgiOjk2LCJ4IjoxMTUyLCJ5Ijo0ODB9XSwiZ3JpZERpdmlzaW9uQ291bnQiOjIsImdyaWRTaXplIjozMn19LCJpbnN0YW5jZUZ4Ijp7ImFkZHJl + c3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 992 184 752 675 + FXID {69A8A5A1-B20F-7146-807B-50807C743623} + WAK 0 0 + > + > + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAH4IG6163HFAaYQQAlki+D8AAAAAAAAAQAEIAAAAAQAAAEIk/MfQlWJABKo0mjCo6j8AAAAAAAAAQAEIAAAAAQAAANMwakiUvJRAaxXJIEOJ + 9j8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAEACAABrAQAAAgAAAA== + AAAQAAAA + > + FLOATPOS 365 124 576 419 + FXID {A512E496-0DD4-8D41-B271-FA2E5C4EB12A} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-73-improved-midi-cc-action-feedback.rpp b/plugin-reaper-realearn/resources/test-projects/issue-73-improved-midi-cc-action-feedback.rpp new file mode 100644 index 0000000..2ae5d5e --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-73-improved-midi-cc-action-feedback.rpp @@ -0,0 +1,198 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAdRoAAAEAAAAAABAA + eyJpZCI6IkctN1hkVzBCIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwi + bWFwcGluZ3MiOlt7ImlkIjoiYmI5YTVjYTYtMjhiYi00MmI5LTljNmMtNjhhYWEwNGYzMGIxIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiOTk5In19XSwiY29udHJvbGxlck1hcHBp + bmdzIjpbeyJpZCI6IjBjMDI5MzYzLTcxYzctNDExNS1iOGUwLTcyNDVhYzFiNmQ0YiIsIm5hbWUiOiJFbmNvZGVyIDEvMSIsInNvdXJjZSI6eyJjaGFyYWN0ZXIiOjN9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCJ9fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFtZSI6 + IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifX0seyJpZCI6IjI3NTIxZWYwLTVjZjgtNDVlOS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUiOiJFbmNvZGVyIDEvMiIsInNv + dXJjZSI6eyJudW1iZXIiOjEsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MX19LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1i + ZXIiOjEsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiI1NGEwNjZlMi00OGUzLTQ2NTMtOWZiYi1lZjkwN2RiMDlkODciLCJuYW1lIjoiRW5jb2RlciAxLzMiLCJzb3VyY2Ui + OnsibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7 + ImlkIjoiYjgyZDc3NTItZDIyOS00NDhmLWJkNDItYWY2MWI0NTllNDgxIiwibmFtZSI6IkJ1dHRvbiAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoy + LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUyZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7Im51 + bWJlciI6MywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6 + IjFjODZlMTMxLWExYjktNGU0Yy05MWQ1LTE4Y2EyNDA0Y2M4OCIsIm5hbWUiOiJCdXR0b24gMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MywiY2hh + cmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjozfX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQzNi05MTM0LTQ4OGE2ZmViMWZiZiIsIm5hbWUiOiJFbmNvZGVyIDIvMSIsInNvdXJjZSI6eyJudW1iZXIi + OjQsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYz + ZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3Rl + ciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6NH19LHsiaWQiOiI3MTkwZTJhNi1lMDdjLTQ5MjItOWJiNC1kNmQyYjA3MmQ4MDEiLCJuYW1lIjoiRW5jb2RlciAyLzIiLCJzb3VyY2UiOnsibnVtYmVyIjo1LCJj + aGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiMDM5Zjc5OWIt + NTM3Zi00Mjc3LWFiYzEtMDNjNTA2ODI3MDgzIiwibmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjF9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9 + fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7Im51bWJlciI6NiwiY2hhcmFj + dGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImNkOTczNjAzLWM5Yjct + NGFiMC05ODE3LTIwNTU3NjUzMWVmMSIsIm5hbWUiOiJCdXR0b24gMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NiwiY2hhcmFjdGVyIjoxfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJp + ZCI6ImQyODQ5ZWM3LWM4OTUtNDRlZS1iZmZhLTM0ZWVmNTBmOTc3YyIsIm5hbWUiOiJFbmNvZGVyIDIvNCIsInNvdXJjZSI6eyJudW1iZXIiOjcsImNoYXJhY3RlciI6 + M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMt + OTA0Ny02NDEwNzgxODk2YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiIy + NWI4M2VjMi01NGRhLTRjMGEtODQxNS04ODBhMzBlZjZhMWYiLCJuYW1lIjoiRW5jb2RlciAzLzEiLCJzb3VyY2UiOnsibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjN9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiMTYwMTNjNDgtMmE0OS00NjcyLWJmM2Et + OTVlYWUwNWVmM2E3IiwibmFtZSI6IkJ1dHRvbiAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIy + YTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIgMy8yIiwic291cmNlIjp7Im51bWJlciI6OSwiY2hhcmFjdGVyIjozfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6IjJlMTg3YTFkLWZhZDAtNDdlNC04Yjg5LWQ5NmI5 + NDUwMDAzMiIsIm5hbWUiOiJCdXR0b24gMy8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6OSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6ImM4NjVjNTAzLTI1 + ZmMtNGI4MS1iYmM4LTA3MzExMDVhMjY1NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIsInNvdXJjZSI6eyJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkzMDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5 + MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiN2E1ZjE2ODAtZjVl + ZC00NjNiLTk3OWQtYjMwOTE2ZTNiYjNhIiwibmFtZSI6IkVuY29kZXIgMy80Iiwic291cmNlIjp7Im51bWJlciI6MTEsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQxMzEtYmVkOS00YmE3LWE2MDQtNDM1Y2YwNDM2 + YzkxIiwibmFtZSI6IkJ1dHRvbiAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRi + LTQ1ODEtOTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiIxYzA5MTU4MC05ODZmLTQyZmUtODk0Mi03NTlkODFkZjE4 + YjIiLCJuYW1lIjoiQnV0dG9uIDQvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEyLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6ImEzODQzNWMwLTNhYzQt + NDE3NC1iMWIxLTc4MWVmZjc5ODhjNCIsIm5hbWUiOiJFbmNvZGVyIDQvMiIsInNvdXJjZSI6eyJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNj + ZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTQwNjAzOGUtM2M0NC00 + MzJkLThlY2MtM2Y0MTU0MGFlMGM4IiwibmFtZSI6IkVuY29kZXIgNC8zIiwic291cmNlIjp7Im51bWJlciI6MTQsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJn + ZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5LTk1ODctNjY4YWMxMjU4NGEz + IiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRl + YmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI2NzcyYTllMy0yODhiLTRmNGItYTBhZS0zYmNlODMwMDlkNGQi + LCJuYW1lIjoiQnV0dG9uIDQvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjI3YmYyMTUyLWU3ZjQtNDVl + NS05Yjk4LWRjYWU4OTY1YjkxZiIsIm5hbWUiOiJMZWZ0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6ImVl + OWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJhY3Rl + ciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6MTd9fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {CCF7DBF6-1F5B-4B56-929C-197233DEC8AC} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-730-offline-notification.RPP b/plugin-reaper-realearn/resources/test-projects/issue-730-offline-notification.RPP new file mode 100644 index 0000000..ed5f0ab --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-730-offline-notification.RPP @@ -0,0 +1,186 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 2 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACJBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS45IiwiaWQiOiJHOGNhNk9jbyIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6Im02MHlwQlNaTjNvVDJEaW43UlVhMiIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MiwibWF4U3RlcFNpemUi + OjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NDIsInRyYWNrR1VJRCI6IjhFRUI2RDk0LUQyMzktMjE0Qy1B + N0RFLUU3N0NFMjE0M0I5MCIsImZ4QW5jaG9yIjoiaWQiLCJmeEdVSUQiOiIwMzdBODlBMi02QUMyLTY1NDEtQUFCQS0zMDJFQjM4QUY5NDEiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtSW5kZXgiOjIxLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Ik1yTm9MLW1GMGlFMjA4WXF1TjdmRiIsIm5hbWUiOiIyIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNp + emUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHJhY2tHVUlEIjoiOEVFQjZEOTQtRDIzOS0yMTRDLUE3REUtRTc3 + Q0UyMTQzQjkwIiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IjAzN0E4OUEyLTZBQzItNjU0MS1BQUJBLTMwMkVCMzhBRjk0MSIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1JbmRleCI6NCwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 905 209 752 675 + FXID {DD4F87BE-AD2B-FE46-B105-A9A455E89698} + WAK 0 0 + > + > + "" + bWNlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd4AAIA/AAAAAKabxDsK16M8pVKpPgAAAAAAAIA/AAAAAAAAAAAAAAAAnNEHMwAAgD8AAAAAzcxMPQAAAAAAAAAAAAAAAAAAgD4AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOAT 7 247 567 397 + FXID {037A89A2-6AC2-6541-AABA-302EB38AF941} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-744-learn-mapping-target.RPP b/plugin-reaper-realearn/resources/test-projects/issue-744-learn-mapping-target.RPP new file mode 100644 index 0000000..fd6b764 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-744-learn-mapping-target.RPP @@ -0,0 +1,247 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.21958959900071 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACODwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJNQm8wdEd5USIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91 + cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IktTclNmN0hQRnlFSjlqT2VxekVCSSIsIm5hbWUiOiJMZWFybiBvd24gbWFwcGluZyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjMyfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0s + InRhcmdldCI6eyJ0eXBlIjo2MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9LCJtYXBwaW5nS2V5IjoiZEhIYW82bFdiU3NqQlBLaEwyOUxEIiwiaW5jbHVkZWRUYXJnZXRzIjpbIlJv + dXRlVm9sdW1lIiwiQXV0b21hdGlvbk1vZGVPdmVycmlkZSIsIlJvdXRlUGFuIiwiVHJhY2tNdXRlU3RhdGUiLCJUcmFja1ZvbHVtZSIsIlRyYWNrUGFuIiwiVHJhY2tT + ZWxlY3Rpb25TdGF0ZSIsIkZ4T25PZmZTdGF0ZSIsIlRyYWNrQXJtU3RhdGUiLCJUcmFja01vbml0b3JpbmdNb2RlIiwiRnhQYXJhbWV0ZXJWYWx1ZSIsIlRlbXBvIiwi + VHJhY2tTb2xvU3RhdGUiLCJCcm93c2VGeFByZXNldHMiLCJUcmFja0F1dG9tYXRpb25Nb2RlIiwiUGxheVJhdGUiXX19LHsiaWQiOiJHZ01QZ01FT1NaWTJBbUdRWXBC + RWYiLCJuYW1lIjoiU2V0IHRhcmdldCBvZiBvd24gbWFwcGluZyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjI0fSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInBvbGxGb3JGZWVkYmFjayI6ZmFsc2UsInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9LCJtYXBwaW5n + TW9kaWZpY2F0aW9uS2luZCI6IlNldFRhcmdldFRvTGFzdFRvdWNoZWQiLCJtYXBwaW5nS2V5IjoiZEhIYW82bFdiU3NqQlBLaEwyOUxEIiwiaW5jbHVkZWRUYXJnZXRz + IjpbIkZ4T25PZmZTdGF0ZSIsIlBsYXlSYXRlIiwiUm91dGVQYW4iLCJCcm93c2VGeFByZXNldHMiLCJUcmFja1NvbG9TdGF0ZSIsIlJvdXRlVm9sdW1lIiwiQXV0b21h + dGlvbk1vZGVPdmVycmlkZSIsIlRyYWNrVm9sdW1lIiwiVHJhY2tBdXRvbWF0aW9uTW9kZSIsIlRlbXBvIiwiVHJhY2tNdXRlU3RhdGUiLCJUcmFja1BhbiIsIlRyYWNr + TW9uaXRvcmluZ01vZGUiLCJGeFBhcmFtZXRlclZhbHVlIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlRyYWNrQXJtU3RhdGUiXX19LHsiaWQiOiJEenlZWk83VVVjX2hT + Vng0YlJGdloiLCJuYW1lIjoiTGVhcm4gZm9yZWlnbiBtYXBwaW5nIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7InR5 + cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYyLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In0sInNlc3Npb25JZCI6Imp6eXFRbjdiIiwibWFwcGluZ0tleSI6Ik9OX3R2eFZxM0pkYmg2eWlrMlNYRyIsImluY2x1ZGVkVGFyZ2V0cyI6WyJUcmFja01vbml0b3Jp + bmdNb2RlIiwiVHJhY2tNdXRlU3RhdGUiLCJUcmFja1NlbGVjdGlvblN0YXRlIiwiQnJvd3NlRnhQcmVzZXRzIiwiVHJhY2tWb2x1bWUiLCJUcmFja0FybVN0YXRlIiwi + RnhPbk9mZlN0YXRlIiwiUGxheVJhdGUiLCJUcmFja1NvbG9TdGF0ZSIsIlRyYWNrQXV0b21hdGlvbk1vZGUiLCJBdXRvbWF0aW9uTW9kZU92ZXJyaWRlIiwiRnhQYXJh + bWV0ZXJWYWx1ZSIsIlRyYWNrUGFuIiwiUm91dGVQYW4iLCJUZW1wbyIsIlJvdXRlVm9sdW1lIl19fSx7ImlkIjoibktiWnZYU1UyM2RaWmtYUEdhdnVVIiwibmFtZSI6 + IlNldCB0YXJnZXQgb2YgZm9yZWlnbiBtYXBwaW5nIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjV9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjYyLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn0sIm1hcHBpbmdNb2RpZmlj + YXRpb25LaW5kIjoiU2V0VGFyZ2V0VG9MYXN0VG91Y2hlZCIsInNlc3Npb25JZCI6Imp6eXFRbjdiIiwibWFwcGluZ0tleSI6Ik9OX3R2eFZxM0pkYmg2eWlrMlNYRyIs + ImluY2x1ZGVkVGFyZ2V0cyI6WyJUcmFja0FybVN0YXRlIiwiUGxheVJhdGUiLCJUcmFja1ZvbHVtZSIsIkF1dG9tYXRpb25Nb2RlT3ZlcnJpZGUiLCJGeE9uT2ZmU3Rh + dGUiLCJUcmFja0F1dG9tYXRpb25Nb2RlIiwiVHJhY2tNb25pdG9yaW5nTW9kZSIsIlRyYWNrTXV0ZVN0YXRlIiwiQnJvd3NlRnhQcmVzZXRzIiwiRnhQYXJhbWV0ZXJW + YWx1ZSIsIlRlbXBvIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlRyYWNrU29sb1N0YXRlIiwiUm91dGVWb2x1bWUiLCJSb3V0ZVBhbiIsIlRyYWNrUGFuIl19fSx7Imlk + IjoiZEhIYW82bFdiU3NqQlBLaEwyOUxEIiwibmFtZSI6IlJlbGVhcm5hYmxlIG1hcHBpbmciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjQsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVlZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifSwiaW5jbHVkZWRUYXJnZXRzIjpbIkF1dG9tYXRpb25Nb2RlT3ZlcnJpZGUiLCJUcmFja1NlbGVjdGlvblN0YXRlIiwiVHJhY2tBcm1TdGF0ZSIs + IlJvdXRlVm9sdW1lIiwiVHJhY2tBdXRvbWF0aW9uTW9kZSIsIkZ4T25PZmZTdGF0ZSIsIkJyb3dzZUZ4UHJlc2V0cyIsIlRyYWNrTXV0ZVN0YXRlIiwiVHJhY2tTb2xv + U3RhdGUiLCJSb3V0ZVBhbiIsIlBsYXlSYXRlIiwiVHJhY2tWb2x1bWUiLCJUcmFja1BhbiIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJGeFBhcmFtZXRlclZhbHVlIiwi + VGVtcG8iXX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 502 72 752 675 + FXID {58FB2481-2D0C-E44A-84F6-42D3830785E0} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACMDAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJqenlxUW43YiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQi + OiJPTl90dnhWcTNKZGJoNnlpazJTWEciLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sImlu + Y2x1ZGVkVGFyZ2V0cyI6WyJGeFBhcmFtZXRlclZhbHVlIiwiQnJvd3NlRnhQcmVzZXRzIiwiQXV0b21hdGlvbk1vZGVPdmVycmlkZSIsIlRyYWNrVm9sdW1lIiwiVHJh + Y2tTb2xvU3RhdGUiLCJUcmFja1BhbiIsIlRyYWNrQXJtU3RhdGUiLCJQbGF5UmF0ZSIsIlRyYWNrQXV0b21hdGlvbk1vZGUiLCJGeE9uT2ZmU3RhdGUiLCJUcmFja01v + bml0b3JpbmdNb2RlIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlRlbXBvIiwiUm91dGVWb2x1bWUiLCJUcmFja011dGVTdGF0ZSIsIlJvdXRlUGFuIl19fSx7ImlkIjoi + Y1ZnSl9BY3hxWlFzTDlyR3hZVHBpIiwibmFtZSI6IjIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsi + dHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJUcmFja0F1dG9t + YXRpb25Nb2RlIiwiVHJhY2tWb2x1bWUiLCJQbGF5UmF0ZSIsIlRyYWNrTXV0ZVN0YXRlIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlRlbXBvIiwiVHJhY2tTb2xvU3Rh + dGUiLCJGeE9uT2ZmU3RhdGUiLCJBdXRvbWF0aW9uTW9kZU92ZXJyaWRlIiwiUm91dGVQYW4iLCJSb3V0ZVZvbHVtZSIsIkJyb3dzZUZ4UHJlc2V0cyIsIlRyYWNrTW9u + aXRvcmluZ01vZGUiLCJUcmFja0FybVN0YXRlIiwiRnhQYXJhbWV0ZXJWYWx1ZSIsIlRyYWNrUGFuIl19fSx7ImlkIjoiRkdrVTRRVUhtdTI1WjdkWmtUZE9JIiwibmFt + ZSI6IjMiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJGeFBhcmFtZXRlclZhbHVlIiwiVHJhY2tNdXRlU3RhdGUiLCJC + cm93c2VGeFByZXNldHMiLCJUcmFja1BhbiIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJUcmFja1NvbG9TdGF0ZSIsIlJvdXRlUGFuIiwiVHJhY2tBcm1TdGF0ZSIsIkZ4 + T25PZmZTdGF0ZSIsIkF1dG9tYXRpb25Nb2RlT3ZlcnJpZGUiLCJUcmFja1ZvbHVtZSIsIlRlbXBvIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlRyYWNrQXV0b21hdGlv + bk1vZGUiLCJQbGF5UmF0ZSIsIlJvdXRlVm9sdW1lIl19fSx7ImlkIjoiMTViTjE4N1ZEQ1hLY1M5c3piVC04IiwibmFtZSI6IjQiLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJM + YXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJGeFBhcmFtZXRlclZhbHVlIiwiQXV0b21hdGlvbk1vZGVPdmVycmlkZSIsIkJyb3dzZUZ4UHJlc2V0cyIsIlRy + YWNrTXV0ZVN0YXRlIiwiVHJhY2tWb2x1bWUiLCJUcmFja1NvbG9TdGF0ZSIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJSb3V0ZVZvbHVtZSIsIlRyYWNrU2VsZWN0aW9u + U3RhdGUiLCJSb3V0ZVBhbiIsIlRyYWNrQXJtU3RhdGUiLCJUcmFja0F1dG9tYXRpb25Nb2RlIiwiUGxheVJhdGUiLCJGeE9uT2ZmU3RhdGUiLCJUcmFja1BhbiIsIlRl + bXBvIl19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 0 0 752 675 + FXID {E44DD72E-D22F-F34A-8877-C985448713FA} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-756.RPP b/plugin-reaper-realearn/resources/test-projects/issue-756.RPP new file mode 100644 index 0000000..24d8112 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-756.RPP @@ -0,0 +1,150 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 9 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 2 8.95790244180897 142.46299074731766 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16777216 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABTBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMCIsImlkIjoiYU9oM256SG4iLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsImNvbnRyb2xEZXZpY2VJZCI6IjE1IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjE1IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRy + b2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6InF0cGx4aV9fRG1GUU9MN3lGUUM1biIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MTYsImNoYXJhY3RlciI6NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6 + MC4wMDUsIm1heFN0ZXBTaXplIjowLjAyNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoxNywiZnhBbmNob3IiOiJp + ZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhRX3h2RUEzTTJCQlFuRFBk + WktzNiIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjEyLCJjaGFubmVsIjowLCJudW1iZXIiOjE2LCJjaGFyYWN0ZXIiOjIsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUs + ImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiJ7eyB0YXJnZXQud2lkdGgubWN1IH19IiwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjE3LCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQ + cm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9XSwiaW5zdGFuY2VGeCI6eyJh + ZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 644 294 752 675 + FXID {DFDEADCB-D375-204F-B4B6-63A6948BAD3D} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-757-lua-feedback-script.RPP b/plugin-reaper-realearn/resources/test-projects/issue-757-lua-feedback-script.RPP new file mode 100644 index 0000000..b0d460d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-757-lua-feedback-script.RPP @@ -0,0 +1,145 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 5.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.78304335735222 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABmBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wIiwiaWQiOiJJaHRfbjFRQiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImZlZWRiYWNrRGV2aWNlSWQiOiJmeC1vdXRwdXQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZh + dWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiTWc5X3Raejh0bTcyRnRVaWVGSlhGIiwibmFtZSI6IkNvdW50ZXIiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJyZWFwZXIiLCJ0eXBlIjoxMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJyZWFwZXJTb3VyY2VUeXBl + Ijoic3BlZWNoIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImVlbEZlZWRiYWNrVHJhbnNmb3Jt + YXRpb24iOiJpZiBjb250ZXh0Lm1vZGUgPT0gMSB0aGVuXG4gICByZXR1cm4ge1xuICAgICAgICB1c2VkX3Byb3BzID0geyBcInRhcmdldC50ZXh0X3ZhbHVlXCIgfVxu + ICAgfVxuZWxzZVxuICAgIGNvdW50ZXIgPSBjb3VudGVyIG9yIDBcbiAgICBjb3VudGVyID0gY291bnRlciArIDFcblxuICAgIHJldHVybiB7XG4gICAgICAgICAgICAg + ICAgZmVlZGJhY2tfZXZlbnQgPSB7XG4gICAgICAgICAgICAgICAgICAgIHZhbHVlID0gXCJcIiAuLiBjb3VudGVyXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAg + ICAgIH0gXG5lbmQiLCJmZWVkYmFja1R5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZp + ZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 994 167 752 675 + FXID {32CE7E47-4532-4549-BDFC-C7521E95FA48} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-760-track-peak-solo.RPP b/plugin-reaper-realearn/resources/test-projects/issue-760-track-peak-solo.RPP new file mode 100644 index 0000000..5af1836 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-760-track-peak-solo.RPP @@ -0,0 +1,196 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 2 8.95790244180897 142.46299074731766 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 10 0 + SELECTION2 10 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16777216 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADiAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMCIsImlkIjoiTVlSY3VVRUkiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsImNvbnRyb2xEZXZpY2VJZCI6IjE1IiwiZmVlZGJhY2tEZXZpY2VJZCI6IjE1IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRy + b2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IlhBSjdYMDM1OERKLVU4N3BZY0lqYSIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5uZWwi + OjAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4VGFyZ2V0VmFsdWUiOjAuMjUsIm1heFN0ZXBT + aXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjM0LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 397 224 752 675 + FXID {0538BF4C-F54F-064B-BC63-E5C75C3C75F3} + WAK 0 0 + > + + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-761-action-invocation-swap-no-migration.RPP b/plugin-reaper-realearn/resources/test-projects/issue-761-action-invocation-swap-no-migration.RPP new file mode 100644 index 0000000..c0e7270 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-761-action-invocation-swap-no-migration.RPP @@ -0,0 +1,141 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 2 8.95790244180897 142.46299074731766 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16777216 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADMAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoiWGdjc29SVVMiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoiZ1N3QmhpZUd1TzQyeVhXUkxYSUF5IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiIyMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9h + ZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 32 348 752 675 + FXID {B3673F42-3A68-3C45-9860-28BE1ECE8A2E} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-761-action-invocation-swap.RPP b/plugin-reaper-realearn/resources/test-projects/issue-761-action-invocation-swap.RPP new file mode 100644 index 0000000..8e8d897 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-761-action-invocation-swap.RPP @@ -0,0 +1,141 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 2 8.95790244180897 142.46299074731766 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16777216 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADMAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMCIsImlkIjoiWGdjc29SVVMiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoiZ1N3QmhpZUd1TzQyeVhXUkxYSUF5IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiIyMCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJh + Y2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9h + ZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 32 348 752 675 + FXID {B3673F42-3A68-3C45-9860-28BE1ECE8A2E} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-763-specific-program-change.RPP b/plugin-reaper-realearn/resources/test-projects/issue-763-specific-program-change.RPP new file mode 100644 index 0000000..b2e3970 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-763-specific-program-change.RPP @@ -0,0 +1,159 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1.16135142141719 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABPAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoidHZ6eTJFdjYiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsImNvbnRyb2xEZXZpY2VJZCI6ImtleWJvYXJkIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1h + cHBpbmdzIjpbeyJpZCI6IkVWWXhNZURTN2dtUFB3NHhqSUxTbCIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9fSwibW9kZSI6eyJtaW5UYXJnZXRW + YWx1ZSI6MC4wMzkzNzAwNzg3NDAxNTc0OCwibWF4VGFyZ2V0VmFsdWUiOjAuNDcyNDQwOTQ0ODgxODg5NzYsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInJh + d01pZGlQYXR0ZXJuIjoiQzEgWzBnZmUgZGNiYV0gMDAiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 449 155 752 675 + FXID {BB8872C4-B8EC-434B-AE8F-DF92D8745078} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADGAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoiQnNpOXZ6cC0iLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoiZ01zakZDbXdiZXBhVnlwcjdRYmlaIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MTMsImNoYW5uZWwiOjEsIm51bWJlciI6NjB9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJlZWxDb250cm9sVHJhbnNmb3JtYXRpb24iOiJ5ID0g + cmFuZCgpIiwib3V0T2ZSYW5nZUJlaGF2aW9yIjoiaWdub3JlIn0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwicG9sbEZvckZlZWRiYWNrIjpmYWxzZSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 385 219 752 675 + FXID {71C0261D-711D-064E-B1E9-3299A11EB22A} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-764-raw-midi-sysex-editor.RPP b/plugin-reaper-realearn/resources/test-projects/issue-764-raw-midi-sysex-editor.RPP new file mode 100644 index 0000000..5f0d396 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-764-raw-midi-sysex-editor.RPP @@ -0,0 +1,150 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 2.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAhBgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoiTy1JMXBJdTMiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoiWTIwekk1a2dsdFBtQnNHMEIyYVBlIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsidHlwZSI6MTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkY3IDAwIDAwIEY3IEFGIEY3IEY3IDAwIDAwIEY3IEFGIEY3IEY3IDAwIDAwIEY3IEFGIEY3IEY3IDAwIDAwIEY3 + IFxuQUYgRjcgRjcgMDAgMDAgRjcgQUYgRjcgRjcgMDAgMDAgRjcgQUYgRjcgRjcgMDAgMDAgRjcgQUYgRjcgRjcgMDAgXG4wMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBB + RiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBcbkY3IDAwIDAwIEY3IEFGIEY3IEY3IDAwIDAwIEY3IEFGIEY3IEY3IDAwIDAwIEY3IEFGIEY3 + IEY3IDAwIDAwIEY3IFxuQUYgRjcgRjcgMDAgMDAgRjcgQUYgRjcgRjcgMDAgMDAgRjcgQUYgRjcgRjcgMDAgMDAgRjcgQUYgRjcgRjcgMDAgXG4wMCBGNyBBRiBGNyBG + NyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAw + MCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBB + RiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBG + NyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAw + MCBGNyBBRiBGNyBGNyAwMCAwMCBGNyBBRiBGNyBGNyAwMCAwMCBGNyIsIm1pZGlTY3JpcHRLaW5kIjoibHVhIiwibWlkaVNjcmlwdCI6ImFzZCIsIm9zY0FyZ0luZGV4 + IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJyYXdNaWRpUGF0dGVybiI6IkIwIDAwIFswZ2ZlIGRjYmFdIERGICIsIm9zY0FyZ0luZGV4 + IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJp + bnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 318 256 752 675 + FXID {E1ED0661-087F-A140-AF11-DAEED22CC80A} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-770-auto-load-unfocus-fx.RPP b/plugin-reaper-realearn/resources/test-projects/issue-770-auto-load-unfocus-fx.RPP new file mode 100644 index 0000000..ec69123 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-770-auto-load-unfocus-fx.RPP @@ -0,0 +1,233 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABCEwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS4xMSIsImlkIjoiUXBhNWhBeXgiLCJzdGF5QWN0aXZlV2hlblByb2plY3RJbkJhY2tncm91bmQiOiJPbmx5SWZCYWNrZ3JvdW5k + UHJvamVjdElzUnVubmluZyIsInNlbmRGZWVkYmFja09ubHlJZkFybWVkIjp0cnVlLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwi + bWFwcGluZ3MiOlt7ImlkIjoiNjZIeWl6QWN3UGljOS0zNW1wd0VYIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6ImNvbDEvcm93MS9rbm9iIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJn + ZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaW5zdGFuY2UiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInBhcmFtSW5kZXgiOjMsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImFkdmFuY2VkIjp7Im9uX2FjdGl2YXRlIjp7InNlbmRfbWlkaV9mZWVkYmFjayI6W119LCJvbl9k + ZWFjdGl2YXRlIjp7InNlbmRfbWlkaV9mZWVkYmFjayI6W119fX0seyJpZCI6ImNZLVF0eGhqZ2Q5WjF5NnBVbEtXZCIsIm5hbWUiOiIyIiwic291cmNlIjp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wyL3JvdzEva25vYiJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imluc3RhbmNlIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbUluZGV4Ijo0LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJhZHZhbmNlZCI6eyJvbl9hY3RpdmF0ZSI6eyJz + ZW5kX21pZGlfZmVlZGJhY2siOltdfSwib25fZGVhY3RpdmF0ZSI6eyJzZW5kX21pZGlfZmVlZGJhY2siOltdfX19LHsiaWQiOiJWUlMxajY1cHJNMTk5c1J5aVU3TVci + LCJuYW1lIjoiMyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMy9yb3cxL2tub2IifSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpbnN0 + YW5jZSIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1JbmRleCI6NSwic2Vla0JlaGF2aW9yIjoiSW1tZWRp + YXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwi + YWR2YW5jZWQiOnsib25fYWN0aXZhdGUiOnsic2VuZF9taWRpX2ZlZWRiYWNrIjpbXX0sIm9uX2RlYWN0aXZhdGUiOnsic2VuZF9taWRpX2ZlZWRiYWNrIjpbXX19fSx7 + ImlkIjoiOXdyOXUtVi1sYTd0SVM2VEhPU3c1IiwibmFtZSI6IjQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNv + bDQvcm93MS9rbm9iIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaW5zdGFuY2UiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtSW5k + ZXgiOjYsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImFkdmFuY2VkIjp7Im9uX2FjdGl2YXRlIjp7InNlbmRfbWlkaV9mZWVkYmFjayI6W119LCJvbl9kZWFjdGl2YXRlIjp7 + InNlbmRfbWlkaV9mZWVkYmFjayI6W119fX0seyJpZCI6IlFlbGcxM005cDc1WW40cURidEpKeiIsIm5hbWUiOiI1Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzIva25vYiJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjo1fSwidGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imluc3RhbmNlIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbUluZGV4Ijo3LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJhZHZhbmNlZCI6eyJvbl9hY3RpdmF0ZSI6eyJzZW5kX21pZGlfZmVl + ZGJhY2siOltdfSwib25fZGVhY3RpdmF0ZSI6eyJzZW5kX21pZGlfZmVlZGJhY2siOltdfX19LHsiaWQiOiJ0aWtxR1JlX09OdFktZmdlRUNkUW0iLCJuYW1lIjoiNiIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMi9yb3cyL2tub2IifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4w + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpbnN0YW5jZSIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1JbmRleCI6OCwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiYWR2YW5jZWQiOnsi + b25fYWN0aXZhdGUiOnsic2VuZF9taWRpX2ZlZWRiYWNrIjpbXX0sIm9uX2RlYWN0aXZhdGUiOnsic2VuZF9taWRpX2ZlZWRiYWNrIjpbXX19fSx7ImlkIjoidEpSc2RV + UllTOThtTXJfWmFXV2x0IiwibmFtZSI6IjciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9rbm9i + In0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaW5zdGFuY2UiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtSW5kZXgiOjksInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJC + eUlkIiwiaWQiOiIifX0sImFkdmFuY2VkIjp7Im9uX2FjdGl2YXRlIjp7InNlbmRfbWlkaV9mZWVkYmFjayI6W119LCJvbl9kZWFjdGl2YXRlIjp7InNlbmRfbWlkaV9m + ZWVkYmFjayI6W119fX0seyJpZCI6IlNnVzFqMzR2aVBOOHpHTFdZQXJLOCIsIm5hbWUiOiI4Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOiJjb2w0L3JvdzIva25vYiJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwi + dGFyZ2V0Ijp7Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imluc3RhbmNlIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJwYXJhbUluZGV4IjoxMCwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRh + a2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fSwiYWR2YW5jZWQiOnsib25fYWN0aXZhdGUiOnsic2VuZF9taWRpX2ZlZWRiYWNrIjpbXX0s + Im9uX2RlYWN0aXZhdGUiOnsic2VuZF9taWRpX2ZlZWRiYWNrIjpbXX19fSx7ImlkIjoiU1JVd0lDYThKX0xrRmVvb3pXcTRyIiwibmFtZSI6IjkiLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDMvcm93Mi9rbm9iIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaW5zdGFuY2UiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInBhcmFtSW5kZXgiOjExLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJtb3VzZUFjdGlvbiI6eyJr + aW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19LCJhZHZhbmNlZCI6eyJvbl9hY3RpdmF0 + ZSI6eyJzZW5kX21pZGlfZmVlZGJhY2siOltdfSwib25fZGVhY3RpdmF0ZSI6eyJzZW5kX21pZGlfZmVlZGJhY2siOltdfX19XSwiYWN0aXZlTWFpblByZXNldElkIjoi + cmVhZGVsYXkiLCJtYWluUHJlc2V0QXV0b0xvYWRNb2RlIjoiZm9jdXNlZC1meCIsImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifSwibWVtb3JpemVkTWFp + bkNvbXBhcnRtZW50Ijp7ImRlZmF1bHRHcm91cCI6e319fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 874 130 752 675 + FXID {867C2E80-502A-F040-B717-2628403FFB1F} + WAK 0 0 + > + > + "" + bGRlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAATAAAAAEAAAAAABAA + AAAAAAAAAAABAAAALAAAAAIAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAIA8nNEHMwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAPw== + AAAQAAAA + > + FLOAT 796 234 574 394 + FXID {8C51DB5E-72E9-C343-9EAC-5617A5B37BA5} + WAK 0 0 + BYPASS 0 0 0 + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAAAAAAAAAFlAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAEACAABrAQAAAgAAAA== + AAAQAAAA + > + FLOATPOS 906 355 576 419 + FXID {255B1D27-87EE-7547-8032-D8FA7A74E664} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-779-controller-compartment-params.rpp b/plugin-reaper-realearn/resources/test-projects/issue-779-controller-compartment-params.rpp new file mode 100644 index 0000000..a90d58d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-779-controller-compartment-params.rpp @@ -0,0 +1,137 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAB2AQAAAQAAAAAAEIA= + eyJ2ZXJzaW9uIjoiMi4xNC4wIiwiaWQiOiJOWE1HUGRwbyIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJwYXJhbWV0 + ZXJzIjp7IjAiOnsibmFtZSI6IlNhdmUgbWUsIHRvbyEiLCJ2YWx1ZUNvdW50IjoxMzIsInZhbHVlIjowLjQzM319LCJjb250cm9sbGVyUGFyYW1ldGVycyI6eyIwIjp7 + Im5hbWUiOiJTYXZlIG1lISIsInZhbHVlQ291bnQiOjIwMCwidmFsdWUiOjAuNDE4fX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + PRESETNAME "Program 1" + FLOATPOS 1272 129 1436 1951 + FXID {1DDFBE8B-1781-4693-B58B-FD6E0372109C} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-809-make-relative-trigger-action.RPP b/plugin-reaper-realearn/resources/test-projects/issue-809-make-relative-trigger-action.RPP new file mode 100644 index 0000000..b5ecc91 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-809-make-relative-trigger-action.RPP @@ -0,0 +1,170 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 510 + ZOOM 6.34429915930584 2499 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAA3BQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJCNThxamdjVyIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5n + cyI6W3siaWQiOiJPaVYwUG1fTDk3dVZuQkJOZEstdGQiLCJuYW1lIjoiQmFja3dhcmQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo0OCwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjozLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6MSwiZW5jb2RlclVzYWdlIjoiZGVjcmVtZW50LW9ubHkifSwidGFyZ2V0Ijp7InR5cGUiOjAsImNvbW1hbmROYW1lIjoiNDAwODQiLCJp + bnZvY2F0aW9uVHlwZSI6MCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJ0LW9MUHVuenl6bGpj + Q0VVbUl3TWwiLCJuYW1lIjoiRm9yd2FyZCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7InR5cGUiOjMsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJlbmNv + ZGVyVXNhZ2UiOiJpbmNyZW1lbnQtb25seSJ9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI0MDA4NSIsImludm9jYXRpb25UeXBlIjowLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 119 -23 752 675 + FXID {531BB070-012E-574F-9EC6-19FBC306782A} + WAK 0 0 + > + + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-809-pitch-bend-scrub.RPP b/plugin-reaper-realearn/resources/test-projects/issue-809-pitch-bend-scrub.RPP new file mode 100644 index 0000000..06e9974 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-809-pitch-bend-scrub.RPP @@ -0,0 +1,188 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 93.40815846677883 + ZOOM 22.73277093708083 1434 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADmAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJ4QkJON2lHOSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5n + cyI6W3siaWQiOiJPTE1DbVNMUlRIVWo3ZVA0VzBpaTMiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInJhd01pZGlQYXR0ZXJuIjoiRTAg + WzBnZmUgZGNiYV0gWzBubWwga2ppaF0iLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5n + U25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 177 126 752 675 + FXID {FF6104D9-DB44-C844-B992-A54489D73571} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADRBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJCNThxamdjVyIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5n + cyI6W3siaWQiOiJPaVYwUG1fTDk3dVZuQkJOZEstdGQiLCJuYW1lIjoiUEl0Y2ggd2hlZWw6IFNjcnViIGJhY2t3YXJkIiwic291cmNlIjp7InR5cGUiOjMsImNoYW5u + ZWwiOjAsIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsidHlwZSI6MywibWF4U291 + cmNlVmFsdWUiOjAuNSwibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsIm91dE9mUmFuZ2VCZWhhdmlvciI6Imlnbm9y + ZSIsImVuY29kZXJVc2FnZSI6ImRlY3JlbWVudC1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6Ijk5MiIsImludm9jYXRpb25UeXBlIjoyLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Im1VdVlHSUVYRVExb1lMa3QtdUR2SCIsIm5hbWUiOiJQ + SXRjaCB3aGVlbDogU2NydWIgZm9yd2FyZCIsInNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7InR5cGUiOjMsIm1pblNvdXJjZVZhbHVlIjowLjUsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjoxLCJvdXRPZlJhbmdlQmVoYXZpb3IiOiJpZ25vcmUiLCJlbmNvZGVyVXNhZ2UiOiJpbmNyZW1lbnQtb25seSJ9LCJ0YXJnZXQi + OnsidHlwZSI6MCwiY29tbWFuZE5hbWUiOiI5OTIiLCJpbnZvY2F0aW9uVHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxh + c3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 119 -23 752 675 + FXID {531BB070-012E-574F-9EC6-19FBC306782A} + WAK 0 0 + > + + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-810-jsfx-value-sequence-numbers-hidden.RPP b/plugin-reaper-realearn/resources/test-projects/issue-810-jsfx-value-sequence-numbers-hidden.RPP new file mode 100644 index 0000000..6be1bc2 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-810-jsfx-value-sequence-numbers-hidden.RPP @@ -0,0 +1,150 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAyAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJBdUE1YlE1WSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5n + cyI6W3siaWQiOiJJNHo0cWZZUnB2cC0taXdGMEt2SXQiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwi + dGFyZ2V0VmFsdWVTZXF1ZW5jZSI6IjAuMDI1LCAwLjAxIn0sInRhcmdldCI6eyJjb21tYW5kTmFtZSI6IjQwMDAxIiwiaW52b2NhdGlvblR5cGUiOjAsImZ4QW5jaG9y + IjoiaWQiLCJmeEluZGV4IjoxLCJmeEdVSUQiOiIxRUQ3OUZFRi05M0FBLTc4NEMtQjhDQi01QUFDRTQxQjdGMTQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJM + YXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 376 114 752 675 + FXID {C8E82530-3FF5-F745-8C5C-DE1E09894CD3} + WAK 0 0 + BYPASS 0 0 0 + + FLOAT 408 553 554 204 + FXID {1ED79FEF-93AA-784C-B8CB-5AACE41B7F14} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-811-group-interaction-off-only.RPP b/plugin-reaper-realearn/resources/test-projects/issue-811-group-interaction-off-only.RPP new file mode 100644 index 0000000..1fcff7a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-811-group-interaction-off-only.RPP @@ -0,0 +1,146 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADfBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4zIiwiaWQiOiJPeW5NeU5jWiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQcm9qZWN0 + SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQi + OiJ3SFZKQnBGMndZODc1Mmh2T1cwOUciLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMyLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7InR5cGUiOjIsIm1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjoxLCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10YXJnZXQtdmFsdWUtb2ZmLW9ubHkifSwidGFyZ2V0Ijp7ImZ4QW5jaG9yIjoidGhpcyIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiIyWjR4bWFTMWlZcE1Zd3ItMTE1VVIiLCJuYW1lIjoiMiIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjo3LCJjb21tYW5kTmFtZSI6IjQwMDAxIiwiaW52b2NhdGlvblR5cGUiOjAs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 593 129 752 675 + FXID {D9EF8B30-7922-E44D-A172-BBD4485B3980} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-835-glue-section-inconsistency.RPP b/plugin-reaper-realearn/resources/test-projects/issue-835-glue-section-inconsistency.RPP new file mode 100644 index 0000000..914d467 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-835-glue-section-inconsistency.RPP @@ -0,0 +1,142 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 3.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADCAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4yIiwiaWQiOiJZRXVROU12cCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YXBwaW5ncyI6W3siaWQiOiJuWGhpdktud2JydFcyZklXWXIyeWsiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im5ldmVyIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjo0LCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4 + U3RlcEZhY3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwi + aW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 32 168 752 675 + FXID {FE256540-F611-274C-8960-EA688CB07FD9} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-838-send-existence-leds.RPP b/plugin-reaper-realearn/resources/test-projects/issue-838-send-existence-leds.RPP new file mode 100644 index 0000000..6d8065b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-838-send-existence-leds.RPP @@ -0,0 +1,201 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACdBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4yIiwiaWQiOiJSQ0tNMDRNbiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6InhIOGRGY3pNNWgyZVE4VlFwR00tUyIsIm5hbWUiOiJUcmFjayBzZW5kIGV4aXN0ZW5jZSBMRUQiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjozMiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NSwiZWVsRmVlZGJhY2tUcmFuc2Zvcm1hdGlvbiI6InggPSAxIn0sInRh + cmdldCI6eyJ0eXBlIjozLCJ0cmFja0dVSUQiOiI1NzM1NTE1MC1DREEyLTMwNEQtOTc5QS00MTZCQjgyRUY3M0QiLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IjVQNmV1X1pEb3Bma0F4cmNXX2ZPRyIsIm5hbWUiOiJIdyBvdXRwdXQgc2VuZCBleGlzdGVuY2UgTEVE + Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6 + MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjUsImVlbEZlZWRiYWNrVHJhbnNmb3JtYXRpb24iOiIx + In0sInRhcmdldCI6eyJ0eXBlIjozLCJ0cmFja0dVSUQiOiI1NzM1NTE1MC1DREEyLTMwNEQtOTc5QS00MTZCQjgyRUY3M0QiLCJmeEFuY2hvciI6ImlkIiwidXNlU2Vs + ZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJyb3V0ZVR5cGUiOiJvdXRwdXQiLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 419 132 752 675 + FXID {631A143C-9B30-704F-BF3C-6DE1E97E3815} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-838-send-removal-error.RPP b/plugin-reaper-realearn/resources/test-projects/issue-838-send-removal-error.RPP new file mode 100644 index 0000000..07cb6b3 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-838-send-removal-error.RPP @@ -0,0 +1,200 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABwAwAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4yIiwiaWQiOiJHV2xtTldQUSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImZlZWRiYWNrRGV2aWNlSWQiOiJmeC1vdXRwdXQiLCJkZWZhdWx0R3JvdXAiOnt9 + LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoibVpZQW1SOUR5LUdqRHBwYkFGLUliIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MCwibnVtYmVyIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjE4LCJ0cmFja0dVSUQiOiJBNkEyNTkzRC00M0QwLTUzNDktOTFC + Ni01NTYxN0M2NEEwNTciLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJyb3V0ZVNlbGVj + dG9yVHlwZSI6ImlkIiwicm91dGVUeXBlIjoib3V0cHV0Iiwicm91dGVHdWlkIjoiMTIyNEY3QTYtRDI3RC00OTQzLTlEOTItRTE2QTRBN0U1NjRCIiwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VGeCI6eyJhZGRy + ZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 592 85 752 675 + FXID {968EBE96-C554-3541-B4CC-E1DA96B24CEF} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-840-async-target-learning.RPP b/plugin-reaper-realearn/resources/test-projects/issue-840-async-target-learning.RPP new file mode 100644 index 0000000..399e49c --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-840-async-target-learning.RPP @@ -0,0 +1,164 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.77019387072009 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACoBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4yIiwiaWQiOiJJQTJsWTFSdCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiTHZ0MGl3eHBGSUxvZUl1blJzYVNzIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJuZXZlciIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJJQ2ZhNEh3 + ZmM4aEpORy1teUJOMlciLCJuYW1lIjoiMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQi + fX19XSwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AFByb2dyYW0gMQAQAAAA + > + FLOAT 303 168 752 675 + FXID {191DE3E1-C97D-9743-8751-F95351890D4F} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADeAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4yIiwiaWQiOiJIM3h3Sjl0ayIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiSmJTTTM1ZXFEY2hsNVFRQnF5eEJ1IiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo2Nn19LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5n + aW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 229 72 752 675 + FXID {5BE6F29F-D216-B543-A3BC-D920C1C287CB} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-840-ignore-realearn-target-touch.RPP b/plugin-reaper-realearn/resources/test-projects/issue-840-ignore-realearn-target-touch.RPP new file mode 100644 index 0000000..271ca0a --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-840-ignore-realearn-target-touch.RPP @@ -0,0 +1,209 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 6.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.01441295165601 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADYCQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4xIiwiaWQiOiJUQlNKaW5vWCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJkZWZhdWx0Q29udHJvbGxlckdyb3VwIjp7fSwibWFw + cGluZ3MiOlt7ImlkIjoiX2JWYnVSbDJQUzhRSkFJcy1naWV0IiwibmFtZSI6IlNldCB2b2x1bWUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJrZXlib2FyZCIsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwia2V5c3Ryb2tlIjp7Im1vZGlmaWVycyI6MSwia2V5Ijo4M319LCJtb2RlIjp7Im1h + eFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoibHJTRkdHbm1xMXdiWFUwNnlvRXNIIiwibmFtZSI6Ikxhc3QgdG91Y2hlZCAobm9uLVJlYUxl + YXJuIG9ubHkpIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6Nzh9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwiaW5jbHVkZWRUYXJn + ZXRzIjpbIkZ4UGFyYW1ldGVyVmFsdWUiLCJUcmFja1ZvbHVtZSJdLCJ0b3VjaENhdXNlIjoiUmVhcGVyIn19LHsiaWQiOiJiaXROcURON1ZPY1NXS1dNY0JEVWMiLCJu + YW1lIjoiTGFzdCB0b3VjaGVkIChSZWFMZWFybiBvbmx5KSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjgyfX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjAsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6 + dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJM + YXN0TG9hZGVkIn0sImluY2x1ZGVkVGFyZ2V0cyI6WyJGeFBhcmFtZXRlclZhbHVlIiwiVHJhY2tWb2x1bWUiXSwidG91Y2hDYXVzZSI6IlJlYWxlYXJuIn19LHsiaWQi + OiJTY3UyVWkzblVUR2dUNnU5R2E0TXUiLCJuYW1lIjoiTGFzdCB0b3VjaGVkIChBbnkpIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9fSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwiaW5jbHVkZWRUYXJnZXRzIjpbIkZ4UGFyYW1ldGVyVmFsdWUiLCJUcmFja1ZvbHVtZSJdfX1dLCJpbnN0YW5jZUZ4 + Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 444 56 752 675 + FXID {774C528B-0801-8D40-B3D1-9BAF7670F807} + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-840-ignore-targets.RPP b/plugin-reaper-realearn/resources/test-projects/issue-840-ignore-targets.RPP new file mode 100644 index 0000000..bedfea1 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-840-ignore-targets.RPP @@ -0,0 +1,185 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1.25393700787402 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1.28206340022525 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAACMEgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4yIiwiaWQiOiJoNzZSaDlHVSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkwyUWt2QWx1LURTMEdvdDctYTh0cSIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tHVUlEIjoibWFzdGVyIiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJmZUpMS09xSjBMaVlocmt6RFUxeTciLCJuYW1lIjoiMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjQ5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0 + TG9hZGVkIn19fSx7ImlkIjoiY2RvejRxaUJkdWxOSllNUkZLS0NrIiwibmFtZSI6IlBpbm5lZCA3Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTQsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MTEsInRyYWNrR1VJRCI6Im1hc3RlciIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJz + ZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiekZOZmZ1ZFpvd0FiT1dQQ2JlMUNwIiwibmFtZSI6IlBpbiA3IChsYXN0IHRvdWNoZWQgYnkgbW91c2UpIiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NzAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjF9LCJ0YXJnZXQiOnsidHlwZSI6NjIsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn0sIm1hcHBpbmdNb2RpZmljYXRpb25LaW5kIjoiU2V0VGFyZ2V0VG9MYXN0VG91Y2hlZCIsIm1h + cHBpbmdLZXkiOiJjZG96NHFpQmR1bE5KWU1SRktLQ2siLCJpbmNsdWRlZFRhcmdldHMiOlsiVHJhY2tBcm1TdGF0ZSIsIkZ4UGFyYW1ldGVyVmFsdWUiLCJUcmFja011 + dGVTdGF0ZSIsIlRyYWNrU2VsZWN0aW9uU3RhdGUiLCJSb3V0ZVZvbHVtZSIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJUZW1wbyIsIlBsYXlSYXRlIiwiVHJhY2tWb2x1 + bWUiLCJUcmFja1NvbG9TdGF0ZSIsIlJlYXBlckFjdGlvbiIsIlRyYWNrQXV0b21hdGlvbk1vZGUiLCJBdXRvbWF0aW9uTW9kZU92ZXJyaWRlIiwiUm91dGVQYW4iLCJU + cmFja1BhbiIsIkZ4T25PZmZTdGF0ZSIsIkJyb3dzZUZ4UHJlc2V0cyJdLCJ0b3VjaENhdXNlIjoiUmVhcGVyIn19LHsiaWQiOiJ5RjF2SHpQVy12SlZnbU5Bc2xXNzMi + LCJuYW1lIjoiUGlubmVkIDgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1NSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0Fy + Z0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlJvQ2xQRW5TOS1VSTJiNWt2MWZGeSIsIm5hbWUiOiJQ + aW4gOCAoYnkgbGVhcm5pbmcgZnJvbSBtb3VzZSkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJ0eXBlIjoyLCJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6MX0sInRhcmdldCI6eyJ0eXBlIjo2MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwibWFwcGluZ0tl + eSI6InlGMXZIelBXLXZKVmdtTkFzbFc3MyIsImluY2x1ZGVkVGFyZ2V0cyI6WyJQbGF5UmF0ZSIsIkZ4T25PZmZTdGF0ZSIsIlRyYWNrTXV0ZVN0YXRlIiwiQnJvd3Nl + RnhQcmVzZXRzIiwiVHJhY2tQYW4iLCJSb3V0ZVZvbHVtZSIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJUZW1wbyIsIlRyYWNrQXJtU3RhdGUiLCJUcmFja0F1dG9tYXRp + b25Nb2RlIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIkF1dG9tYXRpb25Nb2RlT3ZlcnJpZGUiLCJSb3V0ZVBhbiIsIlJlYXBlckFjdGlvbiIsIlRyYWNrVm9sdW1lIiwi + VHJhY2tTb2xvU3RhdGUiLCJGeFBhcmFtZXRlclZhbHVlIl0sInRvdWNoQ2F1c2UiOiJSZWFwZXIifX0seyJpZCI6Ik13UkljZi1OUE1FaHZXOExQM3NEcyIsIm5hbWUi + OiJMYXN0IHRvdWNoZWQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyMCwiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifSwiaW5jbHVkZWRUYXJnZXRzIjpbIkZ4T25PZmZTdGF0ZSIsIlRyYW5zcG9y + dEFjdGlvbiIsIlRyYWNrQXJtU3RhdGUiLCJSb3V0ZVBhbiIsIlRyYWNrU29sb1N0YXRlIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlRlbXBvIiwiVHJhY2tNdXRlU3Rh + dGUiLCJBdXRvbWF0aW9uTW9kZU92ZXJyaWRlIiwiQnJvd3NlRnhQcmVzZXRzIiwiUm91dGVWb2x1bWUiLCJUcmFja0F1dG9tYXRpb25Nb2RlIiwiUmVhcGVyQWN0aW9u + IiwiUGxheVJhdGUiLCJUcmFja1BhbiIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJGeFBhcmFtZXRlclZhbHVlIiwiVHJhY2tWb2x1bWUiXSwidG91Y2hDYXVzZSI6IlJl + YXBlciJ9fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOATPOS 422 109 752 675 + FXID {F3C44474-8B2E-EE4A-A772-B1A4BD8F0516} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-842-crawling.RPP b/plugin-reaper-realearn/resources/test-projects/issue-842-crawling.RPP new file mode 100644 index 0000000..fa59e5b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-842-crawling.RPP @@ -0,0 +1,574 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + MkRNU+5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAAD2pAAAAQAAAAAAEAA= + I3BnbT1Tb2xvIFRydW1wZXQgVHJpYnV0Ci8qQE1ldGEKCkJhbms6CidUcmFuc21pc3Npb24nCgpBdXRob3I6CidCZW4gSGFsbCcKCkNhdGVnb3JpZXM6CidMZWFkczpC + cmFzcycKCkZlYXR1cmVzOgonTW9ubywgU2xvdyBSZWxlYXNlJwoKQ2hhcmFjdGVyOgonQnJpZ2h0LCBDbGVhbiwgU3ludGhldGljJwoKKi8KCiNBTT1aZWJyYTIKI1Zl + cnM9MjAwMEEKI0VuZGlhbj1saXR0bGUKI25tPTM0CiNtcz1ub25lCiNtcz1Nb2RXaGwKI21zPVBpdGNoVwojbXM9Q3RybEEKI21zPUN0cmxCCiNtcz1MZm9HMQojbXM9 + TGZvRzIKI21zPUdhdGUKI21zPUtleUZvbAojbXM9S2V5Rm9sMgojbXM9VmVsb2NpdHkKI21zPUFUb3VjaAojbXM9QXJwTW9kCiNtcz1BcnBNZDIKI21zPUVudjEKI21z + PUVudjIKI21zPUVudjMKI21zPUVudjQKI21zPU1TRUcxCiNtcz1NU0VHMgojbXM9TVNFRzMKI21zPU1TRUc0CiNtcz1MZm8xCiNtcz1MZm8yCiNtcz1MZm8zCiNtcz1M + Zm80CiNtcz1NTWFwMQojbXM9TU1hcDIKI21zPU1NYXAzCiNtcz1NTWFwNAojbXM9TU1peDEKI21zPU1NaXgyCiNtcz1NTWl4MwojbXM9TU1peDQKI252PTUKI212PUdh + dGUKI212PUVudjEKI212PUVudjIKI212PUVudjMKI212PUVudjQKI2NtPW1haW4KQ2NPcD0xMDAuMDAKI0xGT0c9MQojTEZPRzI9MQojY209UENvcmUKWF8xPS02NS4w + MApZXzE9LTQ2LjI1ClhfMj02NS4wMApZXzI9MzEuMjUKWF8zPTg4Ljc1CllfMz00My43NQpYXzQ9NjUuMDAKWV80PS04NS4wMApNVDExPVZDRjE6Q3V0Ck1MMTE9MTUw + LjAwCk1SMTE9MS44OApNVDEyPW5vbmU6YXNzaWduZWQKTUwxMj01MC4wMApNUjEyPS01MC4wMApNVDEzPW5vbmU6YXNzaWduZWQKTUwxMz01MC4wMApNUjEzPS01MC4w + MApNVDE0PW5vbmU6YXNzaWduZWQKTUwxND01MC4wMApNUjE0PS01MC4wMApNVDE1PW5vbmU6YXNzaWduZWQKTUwxNT0xNS4wMApNUjE1PS0xNS4wMApNVDE2PW5vbmU6 + YXNzaWduZWQKTUwxNj01MC4wMApNUjE2PS01MC4wMApNVDE3PW5vbmU6YXNzaWduZWQKTUwxNz01MC4wMApNUjE3PS01MC4wMApNVDE4PW5vbmU6YXNzaWduZWQKTUwx + OD01MC4wMApNUjE4PS01MC4wMApNVDIxPVZDRjE6UmVzCk1MMjE9MTAwLjAwCk1SMjE9LTEuMjUKTVQyMj1ub25lOmFzc2lnbmVkCk1MMjI9NTAuMDAKTVIyMj0tNTAu + MDAKTVQyMz1ub25lOmFzc2lnbmVkCk1MMjM9NTAuMDAKTVIyMz0tNTAuMDAKTVQyND1ub25lOmFzc2lnbmVkCk1MMjQ9NTAuMDAKTVIyND0tNTAuMDAKTVQyNT1ub25l + OmFzc2lnbmVkCk1MMjU9NTAuMDAKTVIyNT0tNTAuMDAKTVQyNj1ub25lOmFzc2lnbmVkCk1MMjY9NTAuMDAKTVIyNj0tNTAuMDAKTVQyNz1ub25lOmFzc2lnbmVkCk1M + Mjc9NTAuMDAKTVIyNz0tNTAuMDAKTVQyOD1ub25lOmFzc2lnbmVkCk1MMjg9MTUuMDAKTVIyOD0tMTUuMDAKTVQzMT1MRk8xOlJhdGUKTUwzMT01MC4wMApNUjMxPS01 + MC4wMApNVDMyPW5vbmU6YXNzaWduZWQKTUwzMj0xNS4wMApNUjMyPS0xNS4wMApNVDMzPW5vbmU6YXNzaWduZWQKTUwzMz0xNS4wMApNUjMzPS0xNS4wMApNVDM0PW5v + bmU6YXNzaWduZWQKTUwzND0xNS4wMApNUjM0PS0xNS4wMApNVDM1PW5vbmU6YXNzaWduZWQKTUwzNT0xNS4wMApNUjM1PS0xNS4wMApNVDM2PW5vbmU6YXNzaWduZWQK + TUwzNj0xNS4wMApNUjM2PS0xNS4wMApNVDM3PW5vbmU6YXNzaWduZWQKTUwzNz0xNS4wMApNUjM3PS0xNS4wMApNVDM4PW5vbmU6YXNzaWduZWQKTUwzOD0xNS4wMApN + UjM4PS0xNS4wMApNVDQxPUxGTzE6QW1wCk1MNDE9MzUuMDAKTVI0MT0tNjIuNTAKTVQ0Mj1ub25lOmFzc2lnbmVkCk1MNDI9MTUuMDAKTVI0Mj0tMTUuMDAKTVQ0Mz1u + b25lOmFzc2lnbmVkCk1MNDM9MTUuMDAKTVI0Mz0tMTUuMDAKTVQ0ND1ub25lOmFzc2lnbmVkCk1MNDQ9OC4wMApNUjQ0PS04LjAwCk1UNDU9bm9uZTphc3NpZ25lZApN + TDQ1PTguMDAKTVI0NT0tOC4wMApNVDQ2PW5vbmU6YXNzaWduZWQKTUw0Nj0xNS4wMApNUjQ2PS0xNS4wMApNVDQ3PW5vbmU6YXNzaWduZWQKTUw0Nz0xNS4wMApNUjQ3 + PS0xNS4wMApNVDQ4PW5vbmU6YXNzaWduZWQKTUw0OD0xNS4wMApNUjQ4PS0xNS4wMApNVDUxPUVOVjE6QXRrCk1MNTE9NTAuMDAKTVI1MT0tMS4yNQpNVDUyPUVOVjE6 + RGVjCk1MNTI9OC43NQpNUjUyPS01MC4wMApNVDUzPUVOVjE6UmVsCk1MNTM9NTAuMDAKTVI1Mz0tMS4yNQpNVDU0PUVOVjE6U3VzCk1MNTQ9LTIuNTAKTVI1ND0tNTYu + MjUKTVQ1NT1ub25lOmFzc2lnbmVkCk1MNTU9NTAuMDAKTVI1NT0tNTAuMDAKTVQ1Nj1ub25lOmFzc2lnbmVkCk1MNTY9NTAuMDAKTVI1Nj0tNTAuMDAKTVQ1Nz1ub25l + OmFzc2lnbmVkCk1MNTc9NTAuMDAKTVI1Nz0tNTAuMDAKTVQ1OD1ub25lOmFzc2lnbmVkCk1MNTg9NTAuMDAKTVI1OD0tNTAuMDAKTVQ2MT1FTlYyOkF0awpNTDYxPTUw + LjAwCk1SNjE9LTEuMjUKTVQ2Mj1FTlYyOkRlYwpNTDYyPTExLjI1Ck1SNjI9LTUwLjAwCk1UNjM9RU5WMjpSZWwKTUw2Mz00MS4yNQpNUjYzPS01LjAwCk1UNjQ9bm9u + ZTphc3NpZ25lZApNTDY0PTUwLjAwCk1SNjQ9LTUwLjAwCk1UNjU9bm9uZTphc3NpZ25lZApNTDY1PTUwLjAwCk1SNjU9LTUwLjAwCk1UNjY9bm9uZTphc3NpZ25lZApN + TDY2PTUwLjAwCk1SNjY9LTUwLjAwCk1UNjc9bm9uZTphc3NpZ25lZApNTDY3PTUwLjAwCk1SNjc9LTUwLjAwCk1UNjg9bm9uZTphc3NpZ25lZApNTDY4PTUwLjAwCk1S + Njg9LTUwLjAwCk1UNzE9UmV2MTpXZXQKTUw3MT03LjUwCk1SNzE9LTY2LjI1Ck1UNzI9bm9uZTphc3NpZ25lZApNTDcyPTUwLjAwCk1SNzI9LTUwLjAwCk1UNzM9bm9u + ZTphc3NpZ25lZApNTDczPTUwLjAwCk1SNzM9LTUwLjAwCk1UNzQ9bm9uZTphc3NpZ25lZApNTDc0PTQwLjAwCk1SNzQ9LTQwLjAwCk1UNzU9bm9uZTphc3NpZ25lZApN + TDc1PTQ4LjAwCk1SNzU9LTQ4LjAwCk1UNzY9bm9uZTphc3NpZ25lZApNTDc2PTUwLjAwCk1SNzY9LTUwLjAwCk1UNzc9bm9uZTphc3NpZ25lZApNTDc3PTUwLjAwCk1S + Nzc9LTUwLjAwCk1UNzg9bm9uZTphc3NpZ25lZApNTDc4PTUwLjAwCk1SNzg9LTUwLjAwCk1UODE9RGVsYXkxOk1peApNTDgxPTcyLjUwCk1SODE9LTYuMjUKTVQ4Mj1E + ZWxheTE6RkIKTUw4Mj01MC4wMApNUjgyPS04Ljc1Ck1UODM9bm9uZTphc3NpZ25lZApNTDgzPTUwLjAwCk1SODM9LTUwLjAwCk1UODQ9bm9uZTphc3NpZ25lZApNTDg0 + PTUwLjAwCk1SODQ9LTUwLjAwCk1UODU9bm9uZTphc3NpZ25lZApNTDg1PTUwLjAwCk1SODU9LTUwLjAwCk1UODY9bm9uZTphc3NpZ25lZApNTDg2PTUwLjAwCk1SODY9 + LTUwLjAwCk1UODc9bm9uZTphc3NpZ25lZApNTDg3PTUwLjAwCk1SODc9LTUwLjAwCk1UODg9bm9uZTphc3NpZ25lZApNTDg4PTUwLjAwCk1SODg9LTUwLjAwCk1NVDE9 + TEZPMTpSYXRlCk1NUzE9MTEKTU1EMT0yMi4wMApNTVZTMT0wCk1NVkQxPTAuMDAKTU1UMj1MRk8xOlJhdGUKTU1TMj0xCk1NRDI9MTQuMDAKTU1WUzI9MApNTVZEMj0w + LjAwCk1NVDM9RU5WMTpTdXMKTU1TMz0xCk1NRDM9LTEwMC4wMApNTVZTMz0wCk1NVkQzPTAuMDAKTU1UND1WQ0ExOlZvbDEKTU1TND0zMApNTUQ0PTEwMC4wMApNTVZT + ND0wCk1NVkQ0PTAuMDAKTU1UNT1ub25lOmFzc2lnbmVkCk1NUzU9MApNTUQ1PTAuMDAKTU1WUzU9MApNTVZENT0wLjAwCk1NVDY9bm9uZTphc3NpZ25lZApNTVM2PTAK + TU1ENj0wLjAwCk1NVlM2PTAKTU1WRDY9MC4wMApNTVQ3PW5vbmU6YXNzaWduZWQKTU1TNz0wCk1NRDc9MC4wMApNTVZTNz0wCk1NVkQ3PTAuMDAKTU1UOD1ub25lOmFz + c2lnbmVkCk1NUzg9MApNTUQ4PTAuMDAKTU1WUzg9MApNTVZEOD0wLjAwCk1NVDk9bm9uZTphc3NpZ25lZApNTVM5PTAKTU1EOT0wLjAwCk1NVlM5PTAKTU1WRDk9MC4w + MApNTVQxMD1ub25lOmFzc2lnbmVkCk1NUzEwPTAKTU1EMTA9MC4wMApNTVZTMTA9MApNTVZEMTA9MC4wMApNTVQxMT1ub25lOmFzc2lnbmVkCk1NUzExPTAKTU1EMTE9 + MC4wMApNTVZTMTE9MApNTVZEMTE9MC4wMApNTVQxMj1ub25lOmFzc2lnbmVkCk1NUzEyPTAKTU1EMTI9MC4wMApNTVZTMTI9MApNTVZEMTI9MC4wMApTQmFzZT00ClN3 + aW5nPTAuMDAKU1RyaWc9MQpQUHJvaj0yClBGb2xkPTAKUEZpbGU9MQpHRmlsZT0yCkdTY2FsZT0tMgpDaExheT0wClN1cnJPPTAKUmV2PTEyMDkyCkxFRD0wLjAwClBB + R0U9MApQYWdlc09uPTAKQ29yZU49MwpTbGljZT00ClVJX29wPTUKTWlkaUE9NQpNaWRpUD02CkRGb2xkPTcKQ3RybEE9MgpDdHJsQj0xMQojY209TEZPRwpTeW5jPTQK + VHJpZz0wCldhdmU9MApQaHNlPTAuMDAKUmF0ZT0xMDAuMDAKQW1wPTEwMC4wMApTbGV3PTEKTnN0cD0xNgpTdHBzPTgKVVd2PTAKI2NtPUxGT0cyClN5bmM9NApUcmln + PTAKV2F2ZT0wClBoc2U9MC4wMApSYXRlPTEwMC4wMApBbXA9MTAwLjAwClNsZXc9MQpOc3RwPTE2ClN0cHM9OQpVV3Y9MAojY209VkNDCiNMRk8xPTEKI0xGTzI9MQoj + TEZPMz0xCiNMRk80PTEKVm9pY2VzPTEKVm9pY2luZz0wCk1vZGU9MQpQb3J0YT0wLjAwClBCPTIKUEJEPTIKQXJTYz00CkFyT3JkPTAKQXJMcD0wCkFyT2N0PTEKQXJM + TD0xNgpBclRyPTAKRHJmdD0xCk1UdW5TPTAKTVR1bk49MTAKTVR1blQ9MTEKVHJzcD0xMgpGVHVuPTAuMDAKUG9ydFJnPTEwMC4wMApQb3J0YU09MApQb3J0YTI9MC4w + MApBZ3RlMT0yCkF0cnAxPTAKQXZvYzE9MQpBbXVsMT0xCkFtb2QxPTAKQU1EcHQxPTAuMDAKQU1EcEIxPTAuMDAKQWd0ZTI9MgpBdHJwMj0wCkF2b2MyPTEKQW11bDI9 + MQpBbW9kMj0wCkFNRHB0Mj0wLjAwCkFNRHBCMj0wLjAwCkFndGUzPTIKQXRycDM9MApBdm9jMz0xCkFtdWwzPTEKQW1vZDM9MApBTURwdDM9MC4wMApBTURwQjM9MC4w + MApBZ3RlND0yCkF0cnA0PTAKQXZvYzQ9MQpBbXVsND0xCkFtb2Q0PTAKQU1EcHQ0PTAuMDAKQU1EcEI0PTAuMDAKQWd0ZTU9MgpBdHJwNT0wCkF2b2M1PTEKQW11bDU9 + MQpBbW9kNT0wCkFNRHB0NT0wLjAwCkFNRHBCNT0wLjAwCkFndGU2PTIKQXRycDY9MApBdm9jNj0xCkFtdWw2PTEKQW1vZDY9MApBTURwdDY9MC4wMApBTURwQjY9MC4w + MApBZ3RlNz0yCkF0cnA3PTAKQXZvYzc9MQpBbXVsNz0xCkFtb2Q3PTAKQU1EcHQ3PTAuMDAKQU1EcEI3PTAuMDAKQWd0ZTg9MgpBdHJwOD0wCkF2b2M4PTEKQW11bDg9 + MQpBbW9kOD0wCkFNRHB0OD0wLjAwCkFNRHBCOD0wLjAwCkFndGU5PTIKQXRycDk9MApBdm9jOT0xCkFtdWw5PTEKQW1vZDk9MApBTURwdDk9MC4wMApBTURwQjk9MC4w + MApBZ3RlMTA9MgpBdHJwMTA9MApBdm9jMTA9MQpBbXVsMTA9MQpBbW9kMTA9MApBTURwdDEwPTAuMDAKQU1EcEIxMD0wLjAwCkFndGUxMT0yCkF0cnAxMT0wCkF2b2Mx + MT0xCkFtdWwxMT0xCkFtb2QxMT0wCkFNRHB0MTE9MC4wMApBTURwQjExPTAuMDAKQWd0ZTEyPTIKQXRycDEyPTAKQXZvYzEyPTEKQW11bDEyPTEKQW1vZDEyPTAKQU1E + cHQxMj0wLjAwCkFNRHBCMTI9MC4wMApBZ3RlMTM9MgpBdHJwMTM9MApBdm9jMTM9MQpBbXVsMTM9MQpBbW9kMTM9MApBTURwdDEzPTAuMDAKQU1EcEIxMz0wLjAwCkFn + dGUxND0yCkF0cnAxND0wCkF2b2MxND0xCkFtdWwxND0xCkFtb2QxND0wCkFNRHB0MTQ9MC4wMApBTURwQjE0PTAuMDAKQWd0ZTE1PTIKQXRycDE1PTAKQXZvYzE1PTEK + QW11bDE1PTEKQW1vZDE1PTAKQU1EcHQxNT0wLjAwCkFNRHBCMTU9MC4wMApBZ3RlMTY9MgpBdHJwMTY9MApBdm9jMTY9MQpBbXVsMTY9MQpBbW9kMTY9MApBTURwdDE2 + PTAuMDAKQU1EcEIxNj0wLjAwCiNjbT1FTlYxCk1vZGU9MAppTW9kZT0wCnNNb2RlPTAKaW5pdD0wLjAwCkF0az0zNC41MApEZWM9NTAuMDAKU3VzPTk1LjAwClN1c1Q9 + MC4wMApTdXMyPTAuMDAKUmVsPTI2LjUwClZlbD0zMC4wMApWMkk9MC4wMApWMkE9LTMwLjAwClYyRD0wLjAwClYyUz0wLjAwClYyRlI9MC4wMApWMlMyPTAuMDAKVjJS + PTAuMDAKSzJJPTAuMDAKSzJBPTAuMDAKSzJEPTAuMDAKSzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4wMApLMlI9MC4wMApTbG9wZT0wLjAwClRCYXNlPTAKI2NtPUVO + VjIKTW9kZT0yCmlNb2RlPTEKc01vZGU9NAppbml0PTcxLjUwCkF0az00MC4wMApEZWM9NTAuMDAKU3VzPTc0LjAwClN1c1Q9Ni4wMApTdXMyPTQ4LjAwClJlbD00NC41 + MApWZWw9MzAuMDAKVjJJPTAuMDAKVjJBPS0xMC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJTMj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksyQT0wLjAw + CksyRD0wLjAwCksyUz0wLjAwCksyRlI9MC4wMApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9LTYwLjAwClRCYXNlPTAKI2NtPUVOVjMKTW9kZT0wCmlNb2RlPTEKc01v + ZGU9MAppbml0PTMuMDAKQXRrPTAuMDAKRGVjPTExLjAwClN1cz0wLjAwClN1c1Q9MC4wMApTdXMyPTAuMDAKUmVsPTI4LjUwClZlbD0zMC4wMApWMkk9MC4wMApWMkE9 + MC4wMApWMkQ9MC4wMApWMlM9MC4wMApWMkZSPTAuMDAKVjJTMj0wLjAwClYyUj0wLjAwCksyST0wLjAwCksyQT0wLjAwCksyRD0wLjAwCksyUz0wLjAwCksyRlI9MC4w + MApLMlMyPTAuMDAKSzJSPTAuMDAKU2xvcGU9MC4wMApUQmFzZT0wCiNjbT1FTlY0Ck1vZGU9MAppTW9kZT0wCnNNb2RlPTAKaW5pdD0wLjAwCkF0az0wLjAwCkRlYz01 + MC4wMApTdXM9ODAuMDAKU3VzVD0wLjAwClN1czI9MC4wMApSZWw9MTUuMDAKVmVsPTMwLjAwClYyST0wLjAwClYyQT0wLjAwClYyRD0wLjAwClYyUz0wLjAwClYyRlI9 + MC4wMApWMlMyPTAuMDAKVjJSPTAuMDAKSzJJPTAuMDAKSzJBPTAuMDAKSzJEPTAuMDAKSzJTPTAuMDAKSzJGUj0wLjAwCksyUzI9MC4wMApLMlI9MC4wMApTbG9wZT0w + LjAwClRCYXNlPTAKI2NtPU1TRUcxClRtVW49MQpFbnY9MTIKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAKVHJpZz0wCiNjbT1NU0VHMgpUbVVuPTEK + RW52PTEzClZlbD0wLjAwCkF0az0wLjAwCkxwdD0wLjAwClJlbD0wLjAwClRyaWc9MAojY209TVNFRzMKVG1Vbj0xCkVudj0xNApWZWw9MC4wMApBdGs9MC4wMApMcHQ9 + MC4wMApSZWw9MC4wMApUcmlnPTAKI2NtPU1TRUc0ClRtVW49MQpFbnY9MTUKVmVsPTAuMDAKQXRrPTAuMDAKTHB0PTAuMDAKUmVsPTAuMDAKVHJpZz0wCiNjbT1MRk8x + ClN5bmM9NApUcmlnPTAKV2F2ZT0wClBoc2U9MC4wMApSYXRlPTEwMC4wMApBbXA9NjcuMDAKU2xldz0xCk5zdHA9MTYKU3Rwcz0xNgpVV3Y9MApEbHk9MC4wMApETVMx + PTEKRE1EMT03NC4wMApGTVMxPTAKRk1EMT0wLjAwCiNjbT1MRk8yClN5bmM9NApUcmlnPTAKV2F2ZT0wClBoc2U9MC4wMApSYXRlPTEwMC4wMApBbXA9MTAwLjAwClNs + ZXc9MQpOc3RwPTE2ClN0cHM9MTcKVVd2PTAKRGx5PTAuMDAKRE1TMT0wCkRNRDE9MC4wMApGTVMxPTAKRk1EMT0wLjAwCiNjbT1MRk8zClN5bmM9NApUcmlnPTAKV2F2 + ZT0wClBoc2U9MC4wMApSYXRlPTEwMC4wMApBbXA9MTAwLjAwClNsZXc9MQpOc3RwPTE2ClN0cHM9MTgKVVd2PTAKRGx5PTAuMDAKRE1TMT0wCkRNRDE9MC4wMApGTVMx + PTAKRk1EMT0wLjAwCiNjbT1MRk80ClN5bmM9NApUcmlnPTAKV2F2ZT0wClBoc2U9MC4wMApSYXRlPTEwMC4wMApBbXA9MTAwLjAwClNsZXc9MQpOc3RwPTE2ClN0cHM9 + MTkKVVd2PTAKRGx5PTAuMDAKRE1TMT0wCkRNRDE9MC4wMApGTVMxPTAKRk1EMT0wLjAwCiNjbT1NTWFwMQpNb2RlPTMKTVNyYz0wClN0cHM9MjAKTnVtPTE3CiNjbT1N + TWFwMgpNb2RlPTMKTVNyYz0wClN0cHM9MjEKTnVtPTE3CiNjbT1NTWFwMwpNb2RlPTMKTVNyYz0wClN0cHM9MjIKTnVtPTE3CiNjbT1NTWFwNApNb2RlPTMKTVNyYz0w + ClN0cHM9MjMKTnVtPTE3CiNjbT1NTWl4MQpUeXBlPTEKTW9kMT0xNApNb2QyPTExCk1vZDM9MQpDc3Q9NTAuMDAKI2NtPU1NaXgyClR5cGU9MApNb2QxPTAKTW9kMj0w + Ck1vZDM9MApDc3Q9NTAuMDAKI2NtPU1NaXgzClR5cGU9MApNb2QxPTAKTW9kMj0wCk1vZDM9MApDc3Q9NTAuMDAKI2NtPU1NaXg0ClR5cGU9MApNb2QxPTAKTW9kMj0w + Ck1vZDM9MApDc3Q9NTAuMDAKI2NtPUdyaWQKR3JpZD0yNApHQnlwPTAKI2NtPU9TQzEKV2F2ZT0wClR1bmU9MC4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTE2ClRNRHB0 + PTEyLjAwClBoc2U9MC4wMApQaHNNU3JjPTAKUGhzTURwdD0wLjAwCldOdW09MS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD0xMDAuMDAKQ3VydmU9MjUKUHJlYz01 + LjAwCkZYMVRwPTMKU0ZYMT03NC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgyVHA9OApTRlgyPTMwLjAwCkZYMlNjPTAKRlgyRHQ9MC4wMApQb2x5PTAKRHR1bj0tMS4w + MApLVnNjPTI2ClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApTeW5jPTAuMDAKU25jU2M9MApTbmNEdD0wLjAw + ClNuY09uPTAKUG9sVz01MC4wMApQd21Pbj0wCldhVGI9MjcKUmVQaHM9MApOb3JtPTAuMDAKUmVuZD0wCkZtdHplPTAKV1ROYW1lPTI4CiNjbT1PU0MyCldhdmU9MwpU + dW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKUGhzZT0wLjAwClBoc01TcmM9MApQaHNNRHB0PTAuMDAKV051bT0xLjAwCldQU3JjPTAKV1BE + cHQ9MC4wMApWdG9EPTAuMDAKQ3VydmU9MjkKUHJlYz01LjAwCkZYMVRwPTAKU0ZYMT0wLjAwCkZYMVNjPTAKRlgxRHQ9MC4wMApGWDJUcD0wClNGWDI9MC4wMApGWDJT + Yz0wCkZYMkR0PTAuMDAKUG9seT0wCkR0dW49MC4wMApLVnNjPTMwClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4w + MApTeW5jPTAuMDAKU25jU2M9MApTbmNEdD0wLjAwClNuY09uPTAKUG9sVz01MC4wMApQd21Pbj0wCldhVGI9MzEKUmVQaHM9MApOb3JtPTAuMDAKUmVuZD0wCkZtdHpl + PTAKV1ROYW1lPTMyCiNjbT1PU0MzCldhdmU9MwpUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKUGhzZT0wLjAwClBoc01TcmM9MApQaHNN + RHB0PTAuMDAKV051bT0xNi4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD0wLjAwCkN1cnZlPTMzClByZWM9NS4wMApGWDFUcD0wClNGWDE9MC4wMApGWDFTYz0wCkZY + MUR0PTAuMDAKRlgyVHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MApEdHVuPTAuMDAKS1ZzYz0zNApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9 + MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNPbj0wClBvbFc9NTAuMDAKUHdtT249MApXYVRiPTM1 + ClJlUGhzPTAKTm9ybT0wLjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT0zNgojY209T1NDNApXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURw + dD0wLjAwClBoc2U9MC4wMApQaHNNU3JjPTAKUGhzTURwdD0wLjAwCldOdW09MS4wMApXUFNyYz0wCldQRHB0PTAuMDAKVnRvRD0wLjAwCkN1cnZlPTM3ClByZWM9NS4w + MApGWDFUcD0wClNGWDE9MC4wMApGWDFTYz0wCkZYMUR0PTAuMDAKRlgyVHA9MApTRlgyPTAuMDAKRlgyU2M9MApGWDJEdD0wLjAwClBvbHk9MApEdHVuPTAuMDAKS1Zz + Yz0zOApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKU3luYz0wLjAwClNuY1NjPTAKU25jRHQ9MC4wMApTbmNP + bj0wClBvbFc9NTAuMDAKUHdtT249MApXYVRiPTM5ClJlUGhzPTAKTm9ybT0wLjAwClJlbmQ9MApGbXR6ZT0wCldUTmFtZT00MAojY209Tm9pc2UxClR5cGU9MApGMT0x + MDAuMDAKRjFTcmM9MApGMURwdD0wLjAwCkYyPTAuMDAKRjJTcmM9MApGMkRwdD0wLjAwCktWc2M9NDEKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAu + MDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCiNjbT1Ob2lzZTIKVHlwZT0wCkYxPTEwMC4wMApGMVNyYz0wCkYxRHB0PTAuMDAKRjI9MC4wMApG + MlNyYz0wCkYyRHB0PTAuMDAKS1ZzYz00MgpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9 + NTAuMDAKI2NtPVZDRjEKVHlwPTUKQ3V0PTAuMDAKUmVzPTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZNMT0xNTAuMDAKRlMxPTE1CkZNMj0wLjAwCkZTMj0wCktleVNj + bD0wLjAwCiNjbT1WQ0YyClR5cD0wCkN1dD0xNTAuMDAKUmVzPTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNj + bD0wLjAwCiNjbT1WQ0YzClR5cD0wCkN1dD0xNTAuMDAKUmVzPTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNj + bD0wLjAwCiNjbT1WQ0Y0ClR5cD0wCkN1dD0xNTAuMDAKUmVzPTAuMDAKRHJ2PTAuMDAKR2Fpbj0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNj + bD0wLjAwCiNjbT1GTU8xCldhdmU9MApUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKRk09MC4wMApGTVNyYz0wCkZNRHB0PTAuMDAKVnRv + RD0wLjAwCkR0dW49MC4wMApLVnNjPTQzClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4wMApQb2x5PTAKUG9sVz01 + MC4wMApHZXRyPTAKI2NtPUZNTzIKV2F2ZT0wClR1bmU9MC4wMApLZXlTY2w9MTAwLjAwClRNU3JjPTAKVE1EcHQ9MC4wMApGTT0wLjAwCkZNU3JjPTAKRk1EcHQ9MC4w + MApWdG9EPTAuMDAKRHR1bj0wLjAwCktWc2M9NDQKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFuU2M9MApQYW5EdD0wLjAwClBvbHk9MApQ + b2xXPTUwLjAwCkdldHI9MAojY209Rk1PMwpXYXZlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkZNPTAuMDAKRk1TcmM9MApGTURw + dD0wLjAwClZ0b0Q9MC4wMApEdHVuPTAuMDAKS1ZzYz00NQpWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9s + eT0wClBvbFc9NTAuMDAKR2V0cj0wCiNjbT1GTU80CldhdmU9MApUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKRk09MC4wMApGTVNyYz0w + CkZNRHB0PTAuMDAKVnRvRD0wLjAwCkR0dW49MC4wMApLVnNjPTQ2ClZvbD0xMDAuMDAKVm9sU2M9MApWb2xEdD0wLjAwClBhbj0wLjAwClBhblNjPTAKUGFuRHQ9MC4w + MApQb2x5PTAKUG9sVz01MC4wMApHZXRyPTAKI2NtPUNvbWIxCk1vZGU9NQpUdW5lPTAuMDAKS2V5U2NsPTEwMC4wMApUTVNyYz0wClRNRHB0PTAuMDAKRGV0bj0wLjAw + ClZ0b0Q9MC4wMApGQj0xMDAuMDAKRkJTcmM9MApGQkRwdD0wLjAwCkRhbXA9NzMuMDAKRG1wU3JjPTAKRG1wRHB0PTAuMDAKRXhjPTAuMDAKSW5qPS0yLjAwCklualNy + Yz0wCkluakRwdD0wLjAwClRuZT0wLjAwClRuZVNyYz0wClRuZURwdD0wLjAwClNlYz0tMTAwLjAwClNlY1NyYz0wClNlY0RwdD0wLjAwCkRpc3Q9MC4wMApEcnk9MC4w + MApWb2w9MTAwLjAwClZvbFNjPTAKVm9sRHQ9MC4wMApQYW49MC4wMApQYW5TYz0wClBhbkR0PTAuMDAKUG9seT0wClBvbFc9NTAuMDAKRmlsbD0wCiNjbT1Db21iMgpN + b2RlPTAKVHVuZT0wLjAwCktleVNjbD0xMDAuMDAKVE1TcmM9MApUTURwdD0wLjAwCkRldG49MC4wMApWdG9EPTAuMDAKRkI9MC4wMApGQlNyYz0wCkZCRHB0PTAuMDAK + RGFtcD0wLjAwCkRtcFNyYz0wCkRtcERwdD0wLjAwCkV4Yz0wLjAwCkluaj0wLjAwCklualNyYz0wCkluakRwdD0wLjAwClRuZT01MC4wMApUbmVTcmM9MApUbmVEcHQ9 + MC4wMApTZWM9MC4wMApTZWNTcmM9MApTZWNEcHQ9MC4wMApEaXN0PTAuMDAKRHJ5PTAuMDAKVm9sPTEwMC4wMApWb2xTYz0wClZvbER0PTAuMDAKUGFuPTAuMDAKUGFu + U2M9MApQYW5EdD0wLjAwClBvbHk9MApQb2xXPTUwLjAwCkZpbGw9MAojY209U2hhcGUxClR5cGU9MApEZXB0aD0wLjAwCkRNU3JjPTAKRE1EcHQ9MC4wMApFZGdlPTc1 + LjAwCkVNU3JjPTAKRU1EcHQ9MC4wMApJbnB1dD0wLjAwCk91dHB1dD0wLjAwCkhpT3V0PTAuMDAKI2NtPVNoYXBlMgpUeXBlPTAKRGVwdGg9MC4wMApETVNyYz0wCkRN + RHB0PTAuMDAKRWRnZT03NS4wMApFTVNyYz0wCkVNRHB0PTAuMDAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMApIaU91dD0wLjAwCiNjbT1NaXgxClBhbj0wLjAwCk1peD01 + MC4wMApQbk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209TWl4MgpQYW49MC4wMApNaXg9NTAuMDAKUG5NZD0wClBuTUQ9MC4wMApQbk1TPTAKI2NtPU1peDMKUGFuPTAu + MDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5NUz0wCiNjbT1NaXg0ClBhbj0wLjAwCk1peD01MC4wMApQbk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209WE1G + MQpUeXA9MApDdXQ9MTUwLjAwClJlcz0wLjAwCkZNMT0wLjAwCkZTMT0wCkZNMj0wLjAwCkZTMj0wCktleVNjbD0wLjAwCkZPZmY9MC4wMApGT01vZD0wLjAwCkZPU3Jj + PTAKWEZNPTAuMDAKWEZNRD0wLjAwClhGTVM9MApCaWFzPTAuMDAKT0xvYWQ9MC4wMApDbGljaz0wLjAwCkRydj0wClJvdXQ9MApUeXAyPS0xCiNjbT1YTUYyClR5cD0w + CkN1dD0xNTAuMDAKUmVzPTAuMDAKRk0xPTAuMDAKRlMxPTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKRk9mZj0wLjAwCkZPTW9kPTAuMDAKRk9TcmM9MApYRk09 + MC4wMApYRk1EPTAuMDAKWEZNUz0wCkJpYXM9MC4wMApPTG9hZD0wLjAwCkNsaWNrPTAuMDAKRHJ2PTAKUm91dD0wClR5cDI9LTEKI2NtPVNCMQpSYW5nZT0wCkZyZXE9 + MC4wMApGTVNyYz0wCkZNRHB0PTAuMDAKT2Zmcz0wLjAwCk9NU3JjPTAKT01EcHQ9MC4wMApNaXg9NTAuMDAKTU1TcmM9MApNTURwdD0wLjAwCiNjbT1TQjIKUmFuZ2U9 + MApGcmVxPTAuMDAKRk1TcmM9MApGTURwdD0wLjAwCk9mZnM9MC4wMApPTVNyYz0wCk9NRHB0PTAuMDAKTWl4PTUwLjAwCk1NU3JjPTAKTU1EcHQ9MC4wMAojY209RGlz + dDEKVHlwZT0wCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKUHJlVGlsdD0wLjAwClBzdFRpbHQ9MC4wMApDbnRGcmVxPTEwMC4wMApMb3c9MC4wMApIaWdoPTAuMDAKUG9z + dEZsdD0wCiNjbT1EaXN0MgpUeXBlPTAKSW5wdXQ9MC4wMApPdXRwdXQ9MC4wMApQcmVUaWx0PTAuMDAKUHN0VGlsdD0wLjAwCkNudEZyZXE9MTAwLjAwCkxvdz0wLjAw + CkhpZ2g9MC4wMApQb3N0Rmx0PTAKI2NtPUZvbGQxClJpcHBsZXM9NApGb2xkcz0xMC4wMApGTW9kPTAuMDAKRlNyYz0wCkJpYXM9MC4wMApCTW9kPTAuMDAKQlNyYz0w + CkZSYXRpbz01MC4wMApGU2xvcGU9MTAuMDAKQ0FuZ2w9NTAuMDAKTGl2ZT0wCiNjbT1Gb2xkMgpSaXBwbGVzPTQKRm9sZHM9MTAuMDAKRk1vZD0wLjAwCkZTcmM9MApC + aWFzPTAuMDAKQk1vZD0wLjAwCkJTcmM9MApGUmF0aW89NTAuMDAKRlNsb3BlPTEwLjAwCkNBbmdsPTUwLjAwCkxpdmU9MAojY209VkNBMQpQYW4xPTAuMDAKUGFuTVMx + PTAKUGFuTUQxPTAuMDAKVm9sMT01MC4wMApWQ0ExPTEKTW9kU3JjMT0wCk1vZERwdDE9MC4wMApQYW4yPTAuMDAKUGFuTVMyPTAKUGFuTUQyPTAuMDAKVm9sMj01MC4w + MApWQ0EyPTEKTW9kU3JjMj0wCk1vZERwdDI9LTEwMC4wMApQYW4zPTAuMDAKUGFuTVMzPTAKUGFuTUQzPTAuMDAKVm9sMz01MC4wMApWQ0EzPTEKTW9kU3JjMz0wCk1v + ZERwdDM9MC4wMApQYW40PTAuMDAKUGFuTVM0PTQKUGFuTUQ0PTAuMDAKVm9sND01MC4wMApWQ0E0PTEKTW9kU3JjND0wCk1vZERwdDQ9MC4wMApNVDE9MApNVDI9MApN + VDM9MApNVDQ9MApQQjE9MApQQjI9MApQQjM9MApQQjQ9MApCdXMxPTAKQnVzMj0wCkJ1czM9MApCdXM0PTAKU2VuZDE9MC4wMApTblNyYzE9MApTbkRwdDE9MC4wMApT + ZW5kMj0wLjAwClNuU3JjMj0wClNuRHB0Mj0wLjAwCkF0dFM9MQojY209R3JpZEZYCkdyaWQ9NDcKR0J5cD0wCiNjbT1Nb2RGWDEKTW9kZT0wCkNlbnQ9MjAuMDAKU3Bl + ZD01MC4wMApQaE9mZj01MC4wMApEcHRoPTUwLjAwCkZlZUI9MC4wMApNaXg9MC4wMApMQ3V0PTAuMDAKSEN1dD0xMDAuMDAKUXVhZD0wLjAwClFwaHM9MjUuMDAKTGVx + PTAuMDAKSGVxPTAuMDAKUTE9MC4wMApRMj0wLjAwCkVRb249MQojY209TW9kRlgyCk1vZGU9MApDZW50PTIwLjAwClNwZWQ9NTAuMDAKUGhPZmY9NTAuMDAKRHB0aD01 + MC4wMApGZWVCPTAuMDAKTWl4PTAuMDAKTEN1dD0wLjAwCkhDdXQ9MTAwLjAwClF1YWQ9MC4wMApRcGhzPTI1LjAwCkxlcT0wLjAwCkhlcT0wLjAwClExPTAuMDAKUTI9 + MC4wMApFUW9uPTEKI2NtPURlbGF5MQpNb2RlPTAKTWl4PTcuNTAKRkI9NTkuNTAKQ0I9MjUuMDAKTFA9MTAwLjAwCkhQPTAuMDAKRHJ2PTAuMDAKU3luYzE9MwpTeW5j + Mj05ClN5bmMzPTQKU3luYzQ9NApUMD0xMDAuMDAKVDE9MTAwLjAwClQyPTEwMC4wMApUMz0xMDAuMDAKUGFuMT0tMTAwLjAwClBhbjI9MTAwLjAwClBhbjM9LTEwMC4w + MApQYW40PTEwMC4wMAojY209RGVsYXkyCk1vZGU9MApNaXg9MC4wMApGQj0xMC4wMApDQj0yNS4wMApMUD0xMDAuMDAKSFA9MC4wMApEcnY9MC4wMApTeW5jMT00ClN5 + bmMyPTQKU3luYzM9NApTeW5jND00ClQwPTEwMC4wMApUMT0xMDAuMDAKVDI9MTAwLjAwClQzPTEwMC4wMApQYW4xPS0xMDAuMDAKUGFuMj0xMDAuMDAKUGFuMz0tMTAw + LjAwClBhbjQ9MTAwLjAwCiNjbT1TaGFwZTMKVHlwZT0wCkRlcHRoPTAuMDAKRE1TcmM9MApETURwdD0wLjAwCkVkZ2U9NzUuMDAKRU1TcmM9MApFTURwdD0wLjAwCklu + cHV0PTAuMDAKT3V0cHV0PTAuMDAKSGlPdXQ9MC4wMAojY209U2hhcGU0ClR5cGU9MApEZXB0aD0wLjAwCkRNU3JjPTQKRE1EcHQ9MC4wMApFZGdlPTc1LjAwCkVNU3Jj + PTQKRU1EcHQ9MC4wMApJbnB1dD0wLjAwCk91dHB1dD0wLjAwCkhpT3V0PTAuMDAKI2NtPU1peDUKUGFuPTAuMDAKTWl4PTUwLjAwClBuTWQ9MApQbk1EPTAuMDAKUG5N + Uz0wCiNjbT1NaXg2ClBhbj0wLjAwCk1peD01MC4wMApQbk1kPTAKUG5NRD0wLjAwClBuTVM9MAojY209UmV2MQpNb2RlPTAKRHJ5PTEwMC4wMApXZXQ9ODUuMDAKRkI9 + NzAuNTAKRGFtcD0zMC4wMApTaXplPTQ1LjUwClNwZD0yNS41MApEcHQ9NTIuMDAKREZCPTYyLjUwCkRTaXplPTUwLjAwCkVNaXg9ODUuMDAKRE1vZD0zMi4wMApEU3Bk + PTU1LjAwClByZT0wLjAwCiNjbT1Db21wMQpUeXBlPTAKUmF0PTEwMC4wMApUaHJlcz0tMjAuMDAKQXR0PTMwLjAwClJlbD01MC4wMApJbnB1dD0wLjAwCk91dHB1dD0w + LjAwCiNjbT1Db21wMgpUeXBlPTAKUmF0PTEwMC4wMApUaHJlcz0tMjAuMDAKQXR0PTMwLjAwClJlbD01MC4wMApJbnB1dD0wLjAwCk91dHB1dD0wLjAwCiNjbT1FUTEK + ZmMxPTIwLjAwCnJlczE9MjUuMDAKZ2FpbjE9MC4wMApmYzI9NDAuMDAKcmVzMj0yNS4wMApnYWluMj0wLjAwCmZjMz02MC4wMApyZXMzPTI1LjAwCmdhaW4zPTAuMDAK + ZmM0PTgwLjAwCnJlczQ9MjUuMDAKZ2FpbjQ9MC4wMAojY209RVEyCmZjMT0yMC4wMApyZXMxPTI1LjAwCmdhaW4xPTAuMDAKZmMyPTQwLjAwCnJlczI9MjUuMDAKZ2Fp + bjI9MC4wMApmYzM9NjAuMDAKcmVzMz0yNS4wMApnYWluMz0wLjAwCmZjND04MC4wMApyZXM0PTI1LjAwCmdhaW40PTAuMDAKI2NtPVZDRjUKVHlwPTAKQ3V0PTE1MC4w + MApSZXM9MC4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTAuMDAKRlMxPTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPVZDRjYKVHlwPTAKQ3V0PTE1MC4w + MApSZXM9MC4wMApEcnY9MC4wMApHYWluPTAuMDAKRk0xPTAuMDAKRlMxPTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKI2NtPVNCMwpSYW5nZT0wCkZyZXE9MC4w + MApGTVNyYz0wCkZNRHB0PTAuMDAKT2Zmcz0wLjAwCk9NU3JjPTAKT01EcHQ9MC4wMApNaXg9NTAuMDAKTU1TcmM9MApNTURwdD0wLjAwCiNjbT1YTUYzClR5cD0wCkN1 + dD0xNTAuMDAKUmVzPTAuMDAKRk0xPTAuMDAKRlMxPTAKRk0yPTAuMDAKRlMyPTAKS2V5U2NsPTAuMDAKRk9mZj0wLjAwCkZPTW9kPTAuMDAKRk9TcmM9MApYRk09MC4w + MApYRk1EPTAuMDAKWEZNUz0wCkJpYXM9MC4wMApPTG9hZD0wLjAwCkNsaWNrPTAuMDAKRHJ2PTEKUm91dD0wClR5cDI9LTEKI2NtPURpc3QzClR5cGU9MApJbnB1dD0w + LjAwCk91dHB1dD0wLjAwClByZVRpbHQ9MC4wMApQc3RUaWx0PTAuMDAKQ250RnJlcT0xMDAuMDAKTG93PTAuMDAKSGlnaD0wLjAwClBvc3RGbHQ9MAojY209RGlzdDQK + VHlwZT0wCklucHV0PTAuMDAKT3V0cHV0PTAuMDAKUHJlVGlsdD0wLjAwClBzdFRpbHQ9MC4wMApDbnRGcmVxPTEwMC4wMApMb3c9MC4wMApIaWdoPTAuMDAKUG9zdEZs + dD0wCiNjbT1OdVJldjEKUHJlPTAuMDAKRGFtcD04MC4wMApEZWNheT01MC4wMApTaXplPTEwMC4wMApUb25lPTAuMDAKV2lkdGg9NzUuMDAKTWl4PTIwLjAwCiNjbT1a + TWFzClJldDE9MC4wMApSZXQyPTAuMDAKTWFzdD02MC4wMApYWTFMPTQ4ClhZMkw9NDkKWFkzTD01MApYWTRMPTUxClhZMVQ9NTIKWFkyVD01MwpYWTNUPTU0ClhZNFQ9 + NTUKT1NDMT01NgpPU0MyPTU3Ck9TQzM9NTgKT1NDND01OQpNU0VHMT02MApNU0VHMj02MQpNU0VHMz02MgpNU0VHND02MwpNU0VHNT02NApNU0VHNj02NQpNU0VHNz02 + NgpNU0VHOD02NwpSZXYxPTY4ClBuMz0wClBuND0wClBuNT0wClBuNj0wClBuNz0wClBuOD0xClBuOT0xClBuMTA9MApQbjExPTAKUmFjazA9NjkKUmFjazE9NzAKI2Nt + PUxheU1lbQpMYXlNZW0xPTAKTGF5TWVtMj0wCkxheU1lbTM9MApMYXlNZW00PTAKTGF5TWVtNT0wCgoKCi8vIFNlY3Rpb24gZm9yIHVnbHkgY29tcHJlc3NlZCBiaW5h + cnkgRGF0YQovLyBET04nVCBUT1VDSCBUSElTCgokJCQkNzczNDAKP2FhYWFpYWRwOmprampqamxvOmtsa2tra2RvOmFhYWFpYWxwOmdkZ2NlbmViOmFhYWFhYWRwOmFh + YWFhYWFjOgplZGJnbGNsbzpoa25kamxkbzpsY2phaWZsbjphYTpkbzpsbzpkcDpscDpsbjpkbjpiZzpsYzpuZDplZDplYzpqCmE6amw6ZWI6aGs6bm86YWw6Zmo6ZGM6 + aGU6cGU6Z3A6Y2E6ZWE6bWkhdUFhYkE5Y3BMZ2Jnb2dsY3BmZWhjZ2IKZ29oZGduZ2poZDFnaldnb0EyMzd1WUExMGZkV2dtV1hmZWhjaGZnbmhhZ2ZVWGZlaGNnamdj + aGZVZ2ZYZmlTWApkYmNvZ2lUaGFBMzR1WUE3NHVZQTd3bWJrZ2RlZWxqY2FlZWdnYmxlZ2thaGtwa2Rpb1BtY29hbWVlbGxpR2hoCmtscGFuY2libWJsYUZlZ2Nsb2Vj + b2ZlZ21tZmRpVW5oZGRwZmFwZ29qa2ttcGtjbWdjbWpramJocGZjY2JpY2IKY21vZ2xlb2lwbFNlbWdndWFlQTExaWJUQXVBYWJBOUxnZmdvWGVpZ2JnbTFBMjQ3dWlh + QTEzOHVBYWJBMjY1dQppYUE2cUExMjd1aWFBNnFBMTI3dVlBMTBjbzJBaGZnbVVYaGRnZGdiZ21nZkEyMnFBMllBMVkxQTFpYVlBMWthCllBMW1hWUExb2FZdUFhY0E1 + cUEzcUEyWUExWTFBMWlhWUExa2FZQTFtYVlBMW9hWUEyT0ExYmFPQTFYT0ExZGEKT0ExWU9BMWZhT0ExZ2FPQTFoYU9BMWlhT0ExaWlPQTFNT0ExamlPQTFrYU9BMWtp + T0ExbGFPQTFsaU9BMW1hTwpBMVpPQTFuYU9BMW5pT0Exb2FPQTFvaU9BMXBhT0ExcGlPQTJMQTFhZUxBMWFpTEExYW1MQTFiYUxBMWJlTEExCmJpTEExYm1MQTFYTEEx + Y2VMQTFjaUxBMWNtTEExZGFMQTFkZUxBMWRpTEExZG1MQTFZTEExZWVMQTFlaUxBMWUKbUxBMWZhTEExZmVMQTFmaUxBMWZtTEExZ2FMQTFnZUxBMWdpTEExZ21MQTFo + YUxBMVVMQTFoaUxBMWhtTEExaQphTEExaWNMQTFpZUxBMWlnTEExaWlMQTFpa0xBMWltTEExaW9MQTFNTEExamNMQTFqZUxBMWpnTEExamlMQTFqCmtMQTFqbUxBMWpv + TEExa2FMQTFrY0xBMWtlTEExa2dMQTFraUxBMWtrTEExa21MQTFrb0xBMWxhTEExSUxBMWwKZUxBMWxnTEExbGlMQTFsa0xBMWxtTEExQ0xBMW1hTEExbWNMQTFtZUxB + MW1nTEExWkxBMW1rTEExbW1MQTFtbwpMQTFuYUxBMW5jTEExbmVMQTFuZ0xBMW5pTEExbmtMQTFubUxBMVFMQTFvYUxBMW9jTEExb2VMQTFvZ0xBMW9pCkxBMW9rTEEx + b21MQTFvb0xBMXBhTEExcGNMQTFWTEExcGdMQTFwaUxBMXBrTEExcG1MQTFwb0x1ZGFhY0E1cUEKM2JhQTJhZUEyYWlBMmFiYWVBMjVycUE3ckEyWUE3ckExWTFBN3JB + MWlhWUE3ckExa2FZQTdyQTFtYVlBN3JBMQpvYVlBN3JBMk9BN3JBMWJhT0E3ckExWE9BN3JBMWRhT0E3ckExWU9BN3JBMWZhT0E3ckExZ2FPQTdyQTFoYU9BCjdyQTFp + YU9BN3JBMWlpT0E3ckExTU9BN3JBMWppT0E3ckExa2FPQTdyQTFraU9BN3JBMWxhT0E3ckExbGlPQTcKckExbWFPQTdyQTFaT0E3ckExbmFPQTdyQTFuaU9BN3JBMW9h + T0E3ckExb2lPQTdyQTFwYU9BN3JBMXBpT0E3cgpBMTV1ZGFhY0E1cUEzYmFBMmFlQTJhaUEyYWJhZUEyNXJxQTdyQTJZQTdyQTFZMUE3ckExaWFZQTdyQTFrYVlBCjdy + QTFtYVlBN3JBMW9hWUE3ckEyT0E3ckExYmFPQTdyQTFYT0E3ckExZGFPQTdyQTFZT0E3ckExZmFPQTdyQTEKZ2FPQTdyQTFoYU9BN3JBMWlhT0E3ckExaWlPQTdyQTFN + T0E3ckExamlPQTdyQTFrYU9BN3JBMWtpT0E3ckExbAphT0E3ckExbGlPQTdyQTFtYU9BN3JBMVpPQTdyQTFuYU9BN3JBMW5pT0E3ckExb2FPQTdyQTFvaU9BN3JBMXBh + Ck9BN3JBMXBpT0E3ckExNXVkYWFjQTVxQTNiYUEyYWVBMmFpQTJhYmFlQTI1cnFBN3JBMllBN3JBMVkxQTdyQTEKaWFZQTdyQTFrYVlBN3JBMW1hWUE3ckExb2FZQTdy + QTJPQTdyQTFiYU9BN3JBMVhPQTdyQTFkYU9BN3JBMVlPQQo3ckExZmFPQTdyQTFnYU9BN3JBMWhhT0E3ckExaWFPQTdyQTFpaU9BN3JBMU1PQTdyQTFqaU9BN3JBMWth + T0E3CnJBMWtpT0E3ckExbGFPQTdyQTFsaU9BN3JBMW1hT0E3ckExWk9BN3JBMW5hT0E3ckExbmlPQTdyQTFvYU9BN3IKQTFvaU9BN3JBMXBhT0E3ckExcGlPQTdyQTE1 + dWRhYWNBNXFBM2JhQTJhZUEyYWlBMmFiYWVBMjVycUE3ckEyWQpBN3JBMVkxQTdyQTFpYVlBN3JBMWthWUE3ckExbWFZQTdyQTFvYVlBN3JBMk9BN3JBMWJhT0E3ckEx + WE9BN3JBCjFkYU9BN3JBMVlPQTdyQTFmYU9BN3JBMWdhT0E3ckExaGFPQTdyQTFpYU9BN3JBMWlpT0E3ckExTU9BN3JBMWoKaU9BN3JBMWthT0E3ckExa2lPQTdyQTFs + YU9BN3JBMWxpT0E3ckExbWFPQTdyQTFaT0E3ckExbmFPQTdyQTFuaQpPQTdyQTFvYU9BN3JBMW9pT0E3ckExcGFPQTdyQTFwaU9BN3JBMTV1aWFBNnFBMTI3dWlhQTZx + QTEyN3VpYUE2CnFBMTI3dWlhQTZxQTEyN3VBYWNBNXFuaWxpa2xDa2VhaWhkRW1hamJvZENvaWFqZ2dEa21qZ2ZpRGdhaW1sZEYKTWlsbmpDQW1ja2dCb2FjZmlvRmRh + Y2NIQmRhaGllZ0VvaW9uZmhFWVBlb0ZsaWptbmtDbmFsampuQmlhZ2dKRgprYWNsbWhHZWVibmJoRUFpZ2NtRU1Sa2ZCZGFvb2RpQmZlZ21jZURBbm5uZ2xtaWFhZGJs + QmZtZGZmZEVqZWppCmdvRUFhZGdqR1lnYVFGaGlKbGFCb2FnbG1mRmlhb21ja0dwYWVqZGZFa21tb2JrRW9hZG1vY0dBaWNocEZmYWwKZ2xtQmtha21vZ0ZBbGVjbWRs + YWllaE9FbWFFZ21FQWFpcGlka25hVWVoQ1hhaG9mQmZhYmJqbkNNa2RlbUNibQpkZ2hsRWZpaWpnaUVBWmxpbGxqaWVscGxDcGFpZGFjQllnZ2NjRXBhcGRqakNlbWJv + YnBEamVramNmRGNpYmJkCmZFb2FZaGhFZGlwamxmQ2ttYWNma0RiYWdtZmlFaWFpZFNHb2FpZGpiR21lZmxXRGJhZGRta0JmYWdrZGpEZ2EKaWhsbUZZYmNZRmthQWZl + RGdpRmZlRGtpZGRrYUNhbUJnYUVNMWtsQ2lhYmlmYkRkbWdsMUVkYWhhZmNCaWFlZgppZ0ZkYW1scG1CWG5ia25GTW9lTEJqaWZoY2tFa2Fna25qQ1Zob2NoRGhpZG1r + bUJpYWtvZm9FZWlicGFqRFZpCmdVRWtpbmxaQmRhYmpsbUJrYW1iYmJFbm1ibGdoRWthYmlvb0ZmYXBpR0ViYXBmaWdDZG1qaGZmRHBtb2RoaUUKaWFlZWhkR0FhaGdj + RmtpZGpvZ0JNZWxmZUNpYW1maG5CZmlpcGRkRWJlcG9hZkVuYWJpaGpCWGtnUkNtYVNiaQpEYmFRWUJqaVltZkJwYWRrYmVFY2lpbFRFZGljbk5CTWNuY2JCY2lsbWJw + RG1hZnBjb0JnaVJrZUJhaWxkbmZDCmlhZWpqZ0NiYWJob2NCaWFsakZkbWJlZmxkZkRBa2lkbGxsQW1mcG9HYm1iamNmRW5lZnBhbkVtYW5mYmRCTWoKZTFDb2FEcGlH + aGlvZm1iQ0FicG1sbG1uYUpoY0R1QWFjQTVxamVQZWVEZWlqaGpnQ29haWZZRWRhZWttY0NmZQpUZ2ZFaW1qZWFvRWhhZWttakJBa2lmYkJlZW1rZm9EaW1GYnBEZGFv + ZmVnRWRhY2dvY0JmbWpwYWREbGlqY2tnCkNiaWthYWpFaWFvYmxlR2RhZW9ka0NoaW5wbmJCQXBnbWZDbWFpaklGaW1va2ZhRGZlbWJmZURuYWtoampDWFEKU0VmbWVv + YWtFZ21sZWNiRGRhS2NpRGJhb2dvaUNpaUJwakNhaWpwa2xCQWRqbmpkbWJhZnBiYURpYXBmcGRsbQppYWdtam1sbW9hTGZjRGNpY2VlZ0RjaW5ubmdDZW1pZ0JFWGRh + Z2VDbWFwb2ZrRXBtZG1nYUVkYWZwZWVCaWFlCmxwakZsYWZhbmtCTWNoZGhDWmZuanBCbGFlbWxlQ1hhYm1sQ2RlbGJjaERwaXBsamtCWWNsZ29FYWlFZGFEZW0KR2dt + REFrZmJkZG1nYWVoZG1CWGZhVURnZWNjYmtEZmVmaWNqRXBhbGdnbkRabWdmb0VwYWZuR0JYYWdrYUdoaQpOZ2dFZmFmZGhoRG9tT2JlRW5tbGRka0RrYUtnZ0RtYW1t + TkZmYWtsZGtDVmpnZW9EbGlna2NlRGlhYWdnaEVtCmVwbGNoRGFtamJlbERBa2ZibEdnYW5rYmpCbm1sbWZsRGNlamdjakRqaXBtZ2pFbGFjbGJsRFZvZmhpRFllbWEK + bkZBbW9maENlaWhpZGpEYmltYWlrQm5lcGtnbERnYWJvY2VCbWFob2lqQm5tYmVhZEViZWNvZGFFbWFra2ZtQgpiYWpkaW5Db2FoYm1uR3BpZW5uZ0NtYWFmam1GTWxh + Zm9EbmVobGdoRFplbG9vQ2thZGRkZkVtYUlvZUZiZWZwCmdsRW1tamViakVwYWpjam5CaWFoZmtkQmhtb2Jlb0VlbWFuY2xEYmFsYWhsRGdhZGhibEJoYWZnZGtCam1j + cFcKRW5pZmlnb0VpYWxpa2dGZW1sbGNlRVhrZW5hRm1hbmJoYUVoaWFkZGJFamloam5nQmZhWUFCZmlocGRmRGlhbwpnYW5CWmJsbmFCa2lnZm9mQ1lob2ZkQ2xpaW9m + cERwbWlnZW9EaGlubG1sQ3Bpb2ZERW1hY21nbkN1QWFjQTVxCmdtY2dmbUVrZWdnZ2lFaWFsZWJoQmdhZmFkZUNrbWllYmlEbmFrYmhjQmhhbmFwZ0JBaWtvY0NoaXBu + aWNDaW0KcGthaURrYW1sb2RCYmlobWhkRGxpYW1qaEJrZUtCRGdhamVlakNBZmtKbG1VbmxEMWhpampsakJBamlnZkVaagpwY29EaW1PZm5EZmFoamNjQm1hZGlpZEdY + Ym5nbkVrZXBpZ2xEamVob2NjRWRhQ2duRGFpamtnaEVoaWhwRUJhCmlianBoQmRpcGRlbkViYVhlZkRrbWRhUkVqbWFqT0RYY2Nma0VrYVViZUJBbWtpaGxsbGVmb2Ri + RHBha25pYUIKWW9jZ2xEYmFraWRhQmppYWpqa0JvYWNtcGRDZmFjbm1wQ2JtcG5haURacGhraEJraWplaGREbWFvYmRnQmdpcAphaW1CYWVkZmFpRVlkYUJFWGlhY2pC + Z2lvaGtpQ2lhaGNraWRtY2lnbGFjRW1hZm5ucENnZWtqRDFrbWxlYWRECmJhbmdibkVkaWZrZm5EaWVhYlJFbWVjbmNqRGlpYXBkZ0RnYXBoWkNuaW9mbW1DbGlmYm9t + QkFSa2ZGa2FwbmcKZEJuZW1sYmlFYW1hZUhFaGFsbGpnQkFqZmlnQmRtYW5na0VhbWFvZWhEbGFja2RiRWppYXBkZERubXBsY3BEbAppZGpwYkNqaWliYWdFQWVsWEdi + aWlnampDWWhtZGJGaWFsYWdpRWxpQ2RiRWJpam9tZEJrYUJpcEdqaWpjZm1ECmdhbGlhZERjZWhjaHBEZ2FwaGtuR2dhY2xqZkJpaUFjaUVtZWFjTEVwYW1oaGZDbGlj + blhFbWFsamNqQ2NtYm4KZmdFb2ViaWdoRWlhbm5iYkJnYWJmZmdDb21iaGFoRGthYWJvY0doaWNlYmNEQWxiYWZDaWFsYWtwZG1sZWFuaApoRWJhYWRnakRjaWRqYm9F + am1iZWRmRGdlbmZnY0RtYWtvbGdGWWJpZXBDbGVsbGVsRGhtbmFicERtYWlnZGJFCmlpbGZTRGRlaWVCRWJlbm5hZERmaW1rYm1EbWFia0pDYm1oZGJqRWZhbGhka0NN + Z21jaEVNZGFpaENwbW9mZGsKRGVlcGhhcERwaWljYWNFZGFna2hnRXVBYWNBNXFuaWhvanBDZm1rZ2hkRG9hZ2hVRWthYmRoaUJtYWxrZmJHVQprbmhpRGRpb2xBREFk + bWZtRWlpZmFsZ0JpbWNoY25EZ2Fqb21lQ25hamZrb0NsaWlra2FCWGVmamVHb2lnZmNsCkRZbWhhbUNuYWNqZGxCZWVqb2dpRUFoZmdoRUFvaGxnbG1VZ2hhbkVsYWpj + Z2tDb2llbmVsRVhnZURFZWljcG0Kb0JsYWhqYWlCZGFsamRqRG9hbGxkaEJoaWpucGpCcGlobWtwQ2hhZ2FrZENiYW9qYWtEZmVib2FjRGdlT2NtRQpvYXBhaG5EbmlP + ZGpFZ21iaGNpRGxlYWRjaURiYWttcGtDbWFibWFtRUFFYmRsbWRlYW9ia0VvYWhna29DZmlMCmVpRG9la2RobkVYYWhqaEdBY21wYWxsaGFtamFkRG5hbG1haUJwaURs + a0JpYXBramdDcGFGa29DamlrZ29vQmsKYW1lSkduaWNwZmtEWWNlb2pCZ2VoYWNuRGtpZG1pY0NYcGdrbkNoYWdnbnBCbWFuaGVuR21lamNla0RiYW1mRgpCZmFvZmVu + RGthMW1sR2hhbmhobkJrYWFqaG1EZ2lmaWJqRG5lb2VkYUVWaGFmbERlaWtoR0VpYUphZkRtZW9iCmNmRFZmbWJwRWZhcGloaERnaURibUVubWRrY2tEY2VjaWVuRGlh + amdlbUdtYXBnY2RCVk5mcERkbW5mY2tEaWEKZ2ZnbkVlaXBrYmVEYW1vb2hmRGdhamppZEZuYWllaWFDbWFtY25lQm1hb25nZEZuaWdoa2xCWWpuY3BDb2FPZAptQm1l + Z2RnYkVwbWVtZmZFb2Fna2RrQ29hUG5nQ2Zhb2hmY0JvZW5sYmFFa2FtZmFiRWZhamhKQmxhZGxlaUJkCmVubmZjRGhpYnBhb0RZUmZtRWFpbGprZUJlbWZoYm5EbWFn + aGdtQ2thZ2FmbENqbXBsY2ZEWGZkb2tHY2llbmMKcERZamVYQ2thYmZuaEdpZWdjZWZFWW1lVURwYW1jb2VDZGViZk9FbmlibG9vQ2ZpZ25oYkRrYWpkaGNEaGFkYQpl + cENmYWdkZ29DaGFlZ2JoRG9hb2NQQnBta2VhY0RvYWJucGdGb2FucGFvQ25hYW9VRHVhbWFkQTVxZWVlamZjCmVoYWVBMmFtQTJlcGZkS2Rid2FiMUExZXBmZEtUQW5n + Z2xsYWFjYWJBMWVwZmRLZGRBMmpwYWNhYkExZXBmZEsKZGVBZ29VZ21hY2FiQTFLV2duZ2NkYkExZ2dhY2FiQTFlb1dnamhkZ2ZUQXBhYWNhYkExZWdlbmVwZGR3YWNh + YgpBMWZnS2VnVEFnb1VnbWFjYWJBMWZnS2VnZGR3YWNhYkExZmdLZWdkZUFuZ2dtaGFhY2FiQTFlZ2VuZXBkYkEyCmdqYWNhYkExZWdlbmVwVEFnb1VnbWFjYWJBMWZn + S2VnZGJBMmprYWIxQTFlZ2VuZXBkZUFuZ2drcGFhY2FiQTEKZW9XZ2poZGdmZGJBYWMxYWJBMUtXZ25nY1RBVWdtYWNhYkExZmRnaWdiaGFnZmRiQWFjMWFiQTFmZGdp + Z2JoYQpnZlRBcGFhY2FiQTFlbmdqaGlkYkEySGFjZ2JBMWVuZ2poaVRBZ29VZ21hY2diQTFlbmdqaGlkZHdhY2diQTFlCm5namhpZGVBbmdnam5hYWNnYkExZmNnamdv + Z2hkYkExYXBhY2diQTFmY2dqZ29naFRBVWdtYWNnYkExZmllbmUKZ2Rid2FraGJBMWZpZW5lZ1RBbmdnbGhhYWtoY0ExZmRMZGJBM2FtYWtiZUExZmRMVEFnZGdvVWdt + YWtiaUExZQplZ2poZFVkYkExYWNha2JiQTFlZWdqaGRVVEFnbHBhYWtiY0ExZWdXZ21nZWRiQTFhaWFrYmVBMWVnV2dtZ2VUCkFVZ21ha2JpQTJuZ2drbGF3QTZhZmJj + bmdna01BNGdvVWdtQTJhZUEzd2dkZ29VZ21BNG5nZ2xiYXdBM3diY24KZ2dsZGFBNGdvVWdtQTJhYkEzd2diZ2QxZ2ZBNG5ib2lNQTJhYkE2Y2tSbmJvaWhhQTRnZDFn + ZkEyY2pBNmFiZwpiZ2QxZ2ZBNG5ib2lkYUEyYWJBNmJoUm5ib2liYUE0Z2QxZ2ZBMkhBNmFiZ2JnZDFnZkE0bmdnaGxhQTJhYkEzCndSbmJvaG9hQTRnZDFnZkEyYWJB + NmFiQTE1MXVhZVhBOFlBM2ZraWNqZ2RtaWNqZWJwRFZlcGhuREExMTlhZ2YKaWFiRGFna29hYkRBMTE1b2NLaGhCdmNtb2xla0RBMTE5ZWtQYmNCcUExMTl2cHBmZkFE + QTExNWxoaWRvbUJ2cQpBMTE1dmVwaG9iZURwZm1vZG1EQTExOWhwb2NibEdxQTExOWZpa2xmZkJwZGNjbG1CQTExNWRqaGNhb0Jkam1nCmluQm9jYmtkaURBMTE5YWhh + Y2FiQmJvbGthaEJBMTE5cG9mZHBwR2pra2NjZ0JBMTE1aWhtZGdiR0xwZmVwRHEKQTExNW1nZ2NsYkd2ZGJnb2FtREExMTVuYmdrbGZCam5obG9pQnZBMTE1aGdvY05C + aGdnZG5tQnZBMTE1dFpMSApEcUExMTFwb3BwaHBEdHBvcHBocEVxQTExNXRBM3FBMTE1dHBvcHBocERxQTExNXRBM3BscHBocEVBMTExcG9wCnBocER0QTNxQTExMXFB + M3Rwb3BwaHBFQTExNXRxMUExMTV0Y29nZWNiRG1oTEhFQTExNWpnUVZCZ29QSkNxQTEKMTFwb3BwaHBEdHF0QTExMXBvcHBocER0cG9wcGhwRHBrcHBocEVBMTExT0hJ + RmNjUlNFa2dkaEdFcUExMTVwbwpwcGhwREEzcUExMTVwbXBwaHBEbm1WY2dFQTExNW5jTmZvRGtpZGhHRHBvcHBocEVBMTE1czMxQTEyN3MxQTNzCkE3NTlzMUExMTVz + MkEyNDNzMkEyNDdzQTI0N3MyQTExMXMzMUExMjdzMUEzc0E3NTlzMUExMTVzMkEyNDNzMkEKMjQ3c0EyNDdzMkExMTFhZUEydWJhQTlZZmllbWRtY2VocDNnYWVpZGFi + aWhwM3VBWEE4WUE4MTkxdVhBMTBmawpUWGZoZ2JoZ2dmVWdiZ2NnbWdmWGVmaGloYVdoY1VBMTJ1YWVYQThZQTNOa2NjZ0J2cUExMTl2cUExMTl2cUExCjE5dnFBMTE5 + dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnEKQTExOXZxQTExOXZxQTExNWFmSElkbUEzcUExMTV0QTNxQTExNXRB + M3FBMTE1dEEzcUExMTV0QTNxQTExNXRBMwpxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzCnFBMTE1 + dEEzcUExMTV0QTNxQTQyMDdhZUEydWJhQTlZZmllbWRtY2VocDNnYWVpZGFiaWhwM3VBWEE4WXo2YWUKaGRnbkZrZW1lZXBGS0hURktIVEZLSFRGS0hJbG1LSElsbUtI + SWxtS0hUbG1BM0tIVGRtV1BmZERGb2plbkRSUwplaURTWkxEa2hkaEdEcGZrZ2RoREtIVERqYmlmY21EbnBWY2dEY25nZWNiREhJYmFEUlNhaUR2bmtna3BhQmxlCm5m + b2FCaW9ZbmJCZ2lrbG1iQktISUJuZU5qb0JnZmNiaWxCb2xlbldCS0hUQkpOUUdKTlFHS0hJR0lNaWZHSU0KaWZHS0hUR0EzZm5mY3BqbG1mbmZjaGpGbWdwbmxrRmZu + ZmNwakZQSmJsQ1dQZmRDSU1pZkNISU1DY21nZWtiQwp4a2hkaEZDSk5RQ2ptUVZDQTJFam1RVkNSU1pDUEpOQ2RoRmdqQ1BKYmxDemVlSElkbW5lTlFHUlNlaUJISU1C + CmtoZGhGQmRpRm9qQlpMSERRVmNnRHBma2dkaERSU2VpREZvamVuRGNiUlNEY2JSU0RXUGZkREZvamVuRFNaTEQKcGZrZ2RoRGpiaWZjbURjbmdlY2JEY2NSYmpESEli + YURJTWFmRHZkaUZvakJISU1CUlNlaUJiY0RjZEJkZGVrcAptR0tISUdLSFRHQTNLSFRGTEhJRklNYWZDa2hkaEdDam1RVUNQSk5Da2hkaEZDa2hkaEZDUlNaQ0pOUUNq + bVFWCkNJTWFmRUhJYmFFWkxIRVBKYmxFUEpibEVnZWNiUkVkaUZvakNkbWRhbmhDWWtkbWVDeElNaWZDUlNlaUNJTWEKZkNLSElGS0hURmVlSFRkbUlNaWZHSk5RR1BK + YmxCQTE1Sk5mb0NJTWlmQ2dlY2JpbENISU1DUEpOQ25wVmtnQwp4NlBKTkNqbVFVQ0pOZm9DUEpibENJTWFmQ0lNYWZDSU1hZkNKTlFGS0hJRkEzUEpibEdQSk5HZGhG + b2pHUEpiCmxCV1BmZEJJTWlmQmVrcG1pbUJvY2doamVCeW5wVmtnQnBma2dsaEJSU1pCY2NSbmpCZGlGb2pCam1RVkJ2SU0KYWZEZ2VjYlJERm9qYW5ESEliYURISWJh + REhJYmFESEliYURkaUZvakJKTmZvQktIVEdlZUhUbG16UEpibENJTQppZkNraGRoRkNuZU5RQ0hJYmFFcGZrZ2RoRXBma2dkaEVucFZjZ0VjY1JiakVnZWNiUkVBMkVh + ZWhkb25DYWlvCmdua0NSU1pDcGZrZ2xoQ25wVmtnQ21qTGpnQ0lNaWZDb2tlbldDaGFQZmRDcGdrZ2RoQ1BKYmxDZ2thaG9nRm8KYWdoamVGa25NYWZGZGFoZG9uZGxo + am9uWUdLSElHamdnY2FjQmFrbGtjbEJob2JiZmZCcGNnaWhvQmRkb2FqZApCb25pbGtpQmtoZGhGQmhiZWxtbkJkbGZwbm5CYWZoZG9uQm1waWdwbkJlbW1uYWdEZGJu + aGFvREhvYkhEcGxvCmtib0RucFZjZ0RjanBiZGREaGRvbllERm9qZW5EYWhvZ2ZrRGZib2NnaERqbVFVRHExZGlGZ2pEamJpZmNtREoKTlFCSk5mb0JZSElkbUtIVENI + SU1DUlNaQ0EyRWNjUmJqRUtIVEVjYlJTRUEzNUhJYmFEUlNaQldQSkN4bnBWawpnQ0lNaWZDdDhkaUZnakVXUGZkRXEyZW9XUERsa2hnZ2FEY2dob2VnRGpiaWZjbURn + ZWNiUkRLSElkbUtIVEZLCkhURktIVEZlZUhUbG1LSElkbUlNaWZHSk5RR0tISUd6UEpibENqbVFVQ0pOZm9DUEpibENKTlFGemptUVZCZW8KV3BrQnZpZ2Ntb2VCYW1T + WkJqY2lma21CSElNQkhJTUJvYmRqaWNCZmhpZGdoQm9tamNla0JpYmtjY25CSEliYQpCZmdpZG9oR2lha2NrbkdmZmlkZ2hHZmVpZG9oZG1BM0tISWxtS0hURktIVEZL + SFRGekpOUUZISU1DUEpOQ0EyCjQzam1RVUJqbVFWQkEzMWtoZGhGQnZXUGZkREEzMW5wVmNnRGptUVZCQTdKTlFCQTI3Z2VjYlJEQTQwM0tIVEQKQTIzUlNaQklNYWZE + QTE5Sk5mb0JqbVFVQmRpRm9qQkExMVdQSkJXUEpCV1BKQldQSkJSU1pCQTUxUEpibEJQSgpibEJQSmJsQlBKYmxCQTM5S0hUR0tISUdJTWFmQklNYWZCQTE5S0hJbG1L + SElsbXpLSElGS0hJRktISUZLSElGCktISUZLSElGSk5RRklNYWZDS0hUQ0pOZm9DSU1pZkNISU1DeEpOUUNkaUZvakNkaUZvakNBNDNISU1Dam1RVkMKZ2VjYlJFY25n + ZWNiRWNuZ2VjYkVBMzlXUEpDZ2VjYlJFUEpibEVjbmdlY2JFamJpZmNtRUtIVEVraGRoR0VTWgpMRUEzOUtISUJSU2VpQktIVEdKTlFGeEEyRXBma2dkaEVBMjNjYlJT + RFJTZWlEbnBWY2dEdmtoZGhGQlJTZWlCCkpOUUZSU1pDY25nZWNiRWNuZ2VjYkVBMjdqYmlmY21EZ2VjYlJEY2JSbmpCeVJTZWlCekpOZm9DWkxIRWpiaWYKY21FamJp + ZmNtRUEzOUtIVER2V1BKQklNYWZCS0hUQ1BKTkN4QTMxUlNlaUJJTWFmQm5lTmZvR3pqbVFVQ1JTWgpDQTQzS0hUQkEzUEpibENLSFRDQTYzS0hJR0lNaWZHS0hJbG1L + SElGUEpibENLSFRDQTY3dGVvV1BFam1RVUVvCmtlbldFUlNnaUVjbWdlZ2JFZW5XZmtFV1BmZEVnZWNiZWxFU1pMRWVvV2RrRUtIVEVRVmNnRVBKYmxFcG1pbWIKY0Vo + b2VnYWpFQTJFam1RVkNkaUZvakNKTlFDRm9qbW5Da2hkaEZDa2xrazFDa3BibmppQ0lNaWZDZ2xhaGdnQwpoY29uWUNQSmJsQ2FlaGRvbkZiZURrZEZLSFRGZWVIVGRt + SU1pZkdSU1pHSU1hZkJQSmJsQktIVEJKTmZvQkpOCmZvQmRpRmdqQmptUVVCSElNQnlucFZrZ0JLSElCa2hkaEZCSk5RQmFkaGRvbkJkZGVrcG1CSU1hZkRkYW5oYW8K + RGtvYm5iaURjbmdlY2JEamJpZmNtRHBma2dkaERTWkxERm9qZW5EV1BmZERQSmZsRGlnY21nZURqbVFVRGVvVwpQRHEzQTMxZGlGb2pCZGlGb2pCZGlGb2pCSk5RQldQ + SkJXUEpCUlNaQlJTWkJraGRoRkJLSElCS0hJQm5wVmtnCkJ5MUhJTUJJTWlmQklNaWZCSU1pZkJqbVFVQmRsZGFmaEJua2liZGpCUEpibEJJTWFmQmptUVZHSk5RR0lN + aWYKR0xIVEdPSElkbUEzSU1hZkZ6UlNaRklNYWZDUEpibENMSFRDUlNlaUNKTmZvQ2RpRmdqQ2ptUVVDSU1pZkNJTQppZkNJTWlmQ2hvZWdpakNla3BtaW1DSElNQ1BK + TkNucFZrZ0NucFZrZ0NraGRoRkNSU1pDUlNaQ1dQSkNKTlFDCmRpRm9qQ0tISWRtS0hUR0tIVEdKTlFHUEpibEJSU2VpQlJTZWlCSU1hZkJLSElkbUtIVEZ6NEtISUZL + SElGS0gKSUZLSElkbUlNYWZCSk5mb0JISU1CUlNaQmptUVZCSU1hZkRJTWFmRElNaWZCQTNKTlFGS0hUQ1JTZWlDUlNlaQpDSk5mb0NqbVFVQ2ptUVVDam1RVUNqbVFV + Q2ptUVVDSU1pZkNSU2VpQ1BKYmxDS0hURmVlSFRsbUtISWRtSU1pCmZHS0hJR0pOUUdBM0tISUZLSFRDbnBWa2dDeFBKTkNJTWlmQ0pOZm9DTEhUQ0lNYWZDekEzSU1p + ZkdKTlFHUEoKYmxCS0hUQlBKYmxCam1RVkdLSElHS0hJZG1JTWFmRktISUZJTWFmQ0tIVENqbVFVQ1JTZWlDSU1hZkN6YWNoZApvbmxtYmFoZG9uZGxLSFRHUEpOR0pO + UUdISWJhQktIVEJSU2VpQkpOUUdLSFRHSU1hZkdLSElkbUtISWxtS0hUCkZLSFRGS0hURktIVEZLSFRGS0hURmtkbWVlcEZhZGhkZ25GelJTWkZJTWFmQ0tIVENSU2Vp + Q0lNaWZDbnBWa2cKQ2toZGhGQ25wVmtnQ0pOZm9DS0hJbG1uY05mb0dJTWFmQlJTZWlCbnBWa2dCS0hJQktISUJLSElCSElNQktISQpHQTNLSElGTEhUQ0lNaWZDUVZr + Z0NSU1pDSk5RQ2dlY2JSRW5wVmNnRWpiaWZjbUVaTEhFQTNLSFRHSU1pZkdJCk1pZkdJTWlmR0tISUdKTlFHSU1hZkJQSmJsQktIVEJLSFRCUlNlaUJKTmZvQkpOZm9C + Sk5mb0JhZGhkZ25CZGQKZWtobUJJTWlmQkhJTUJaTGpnQnlMSElCUlNaQldQSkJXUEpCV1BKQldQSkJXUEpCSk5RQkpOUUJkaUZvakJkaQpGb2pCZGlGb2pCam1RVkJq + bVFWQmptUVZCam1RVkJqbVFWQmptUVZCam1RVkJ2MW1tbGZhZERqaWdsYWhEZ2VjCmJSRFdQYmREUEpibERjbmdlY2JEamJpZmNtRGpiaWZjbURLSFRES0hURHBma2dk + aERraGRoR0RraGRoR0RpbmIKY0QxaGRvbllEU1pMRFNaTERTWkxEU1pMRFNaTERJTWVmRFJTZWlEUlNlaURSU2VpRFJTZWlERm9qZW5ERm9qZQpuREZvamVuREZvamVu + REZvamVuREZvamVuREZvamVuRFdQZmREV1BmZERXUGZkRFdQZmREV1BmZERXUGZkRFdQCmZkRGNiUlNEY2JSU0RjYlJTREpOZm9ESk5mb0RjbWdlZ2JEaWdjbWdlRGRp + RmdqRGpiaWZnbURva2VuV0RLSGgKY0RqbVFVRGVvV1BEcTM0QTNqbVFWQ2ptUVZDUlNaQ0E3b2tlbldESk5mb0R5MUhJTUJISU1CSElNQkhJTUJISQpNQmdlY2JpbEJJ + TWlmQklNaWZCam1RVUJKTmZvQlJTZWlCUlNlaUJraGRoR0JLSFRCUVZjZ0JQSmJsQklNYWZCCm5lTlFHZWVISUdsZU1pZkdLSFRHZm9mY3BqZG1kZ2hpaW9kbWRpaGlp + b2RsZGVoaWFvbG1LSElsbVBKYmxGSk4KZm9GSElNRktISUZKTlFGSU1hZkNQSmJsQ0xIVENSU2VpQ0pOZm9DTlFVQ0lNaWZDSElNQ1BKTkNRVmtnQ0xISQpDa2dkaEZD + UlNaQ1dQSkNKTlFDZGhGb2pDTlFWQ0EyRWNmaG9hZ0Vla3BtYW1FV1BiZEVqZXBpYmpFbGpoZ1hFCm5wVmNnRWpiaWZjbUVLSFRFcGZrZ2RoRWtoZGhHRUlNZWZFRm9q + ZW5FV1BmZEVBMjI3S0hUQkE2N1JTZWlCQTMKMUhJTUJBMTExeUExNW5wVmtnQm5wVmtnQkExOUtISUJLSElCQTU5S0hJQktISUJBNTVraGRoRkJBNDdLSElCSwpISUJB + NTFpZ2NtZ2VEQTM1S0hUREE1MVpMSERBNDdXUEpCQTY3SU1pZkJBMzFLSFRHS0hUR0EyM1BKYmxCQTMxCktIVEdBMTc5ZGlGZ2pEQTdkaUZnakRBMTlKTmZvREExMWpt + UVZDQTNGb2plbkRBMTFXUGZkREEyRUExMEVBN2MKYlJTREEyM29rZW5XREEzMUlNYWZFQTQ3SU1hZkRJTWFmREEzOXRBMzFISU1CQTExekExNVBKYmxDUEpibENBMwo5 + bnBWa2dCQTExSk5mb0JKTmZvQkEyM0lNaWZDSU1pZkNBMjdJTWlmR0lNaWZHQTI3dEE3amJpZmNtREE3ZW9XClBEQTE1aWdjbWdlRUExNWNuZ2VjYkRBN2toZGhGQ0Ex + NWdlY2JSREEzam1RVkNBMzFxQTExSEliYUVBMTFraGQKaEdEa2hkaEdEcGZrZ2RoRHBma2dkaERLSFRESU1hZkRqbVFWQmRpRm9qQldQSkJXUEpCV1BKQldQSkJXUEpC + VwpQSkJXUEpCV1BKQldQSkJXUEpCUlNaQlJTWkJLSElCbnBWa2dCeTVISU1CSk5mb0JQSmJsQklNaWZHQTNLSFRDCkpOUUNISWJhRWNuZ2VjYkVqYmlmY21FcGZrZ2Ro + RVNaTEVSU2VpRVJTZWlFSU1lZkVTWkxFZW9XZGtFS0hURW8Ka2VuY3BFamJpZmNtRUlNY2ZFSk5ib0VWa2diaEVISWJhRWVmZmxhbkVVYWVha0VrZGtuYWdFbmNmZ2Fk + RUEyRQpkaUZvakNXUEpDSU1tZkNwZmtnbGhDZGlGa2pDUEpOQ0lNaWZDSk5mb0NXUGZkQ1JTZWlDQTkxV1BKQlJTWkJrCmhkaEZCTWlma21CeWptUVVCSk5mb0JQSmJs + QlBKYmxCS0hJR0pOUUZSU2VpQ1BKTkNraGRoRkNXUEpDSk5RQ0kKTWFmRUlNYWZFSU1hZkVTWmFjRUEyRUEyRWptUVZDZGlGb2pDSk5RQ2NiUm5qQ1dQSkNGb2ptbkNS + U1pDeGpiaQpma21DbnBWa2dDSElNQ2ptUVVDaWdjbWdlQ2hhUGZkQ2ZrWkxDS0hUQ0lNYWZDTEhJRk9IVEZBM0pOZm9HSk5RCkdLSFRCSk5mb0JBMzFaTEhESEliYURn + ZWNiUkRJTWFmRGptUVZCbmVOUUJSU1pCbnBWa2dCeUhJTUJJTWlmQm4KZU5mb0JLSFRCSk5RR0tISWxtektISUZSU1pGSk5RRklNYWZDS0hUQ1JTZWlDSk5mb0NkaUZn + akNqbVFVQ2ptUQpVQ2ptUVVDam1RVUNqbVFVQ2ptUVVDZGlGZ2pDSk5mb0NKTmZvQ0pOZm9DSk5mb0NKTmZvQ0pOZm9DSk5mb0NKCk5mb0NKTmZvQ0pOZm9DSk5mb0NS + U2VpQ1JTZWlDUlNlaUNLSFRDUEpibENJTWFmQ2ptUVZGSk5RRnpJTWFmRkEKM0tIVGRtS0hJZG1LSElHSk5RR0lNYWZCUVZjZ0JSU2VpQklNaWZCSElNQlJTWkJBNjN4 + MTRBNDdLSElCS0hJQgpucFZrZ0JjbWdla2JCeTEzQTM1S0hUQ1JTZWlDSk5mb0NkaUZnakNqbVFVQ29nbmtpYkNob2VnaWpDSElNQ2tvCmJuamlDZWdpampwQ25wVmtn + Q25wVmtnQ25wVmtnQ25wVmtnQ25wVmtnQ25wVmtnQ25wVmtnQ25wVmtnQ25wVmsKZ0NucFZrZ0NucFZrZ0NucFZrZ0NucFZrZ0NucFZrZ0NBNTVqYmlmY21EamJpZmNt + RGpiaWZjbURrbGtrY2tEbQpmbXBjaURucFZjZ0RucFZjZ0RucFZjZ0RucFZjZ0RwamJqY2ZEYmREY2REY25nZWNiRGNuZ2VjYkRucFZjZ0RuCnBWY2dEbnBWY2dEbnBW + Y2dEamJpZmNtREEzMXVYQTEwZmtUWGZoZ2JoZ2dmVWdiZ2NnbWdmWGVmaGloYVdoY1UKQTRobGJhQTV1YWVYQThZQTNOa2NjZ0J2cUExMTl2cUExMTl2cUExMTl2cUEx + MTl2cUExMTl2cUExMTl2cUExMQo5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE5dnFBMTE1YWZISWRtQTNxCkExMTV0QTNxQTEx + NXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3EKQTExNXRBM3FBMTE1dEEzcUExMTV0QTNxQTExNXRBM3FBMTE1dEEzcUEx + MTV0QTNxQTExNXRBM3FBNDIwN2FlQQoydWJhQTlZZmllbWRtY2VocDNnYWVpZGFiaWhwM3VBWEE4WWlwa2phYmxiaWlkamZnRm1jZW1paEZtY2VtaWhGCm1jZW1paEZk + amZtaWdGaXBramliRmdla2hnb0ZoaWZpZmVGMWttREZkZmtlZGFGZmtkZ2JpRmZkbWdvbWxtaWsKYWpsa2xtY2hqZGpjbG1wY2JwY2psbWZmamdmYUdpcHBjaWdCbWJR + YmJEa2VhZktEY2NwZGVrRHBhZXBlZkRGawptRDFpbGFqZGtEZmlnZ2RlRGNmbWRjb0RpbWdsY2lEa2dtamJwRGluYWtiZkRrbzFha0Rrb25wYWJEZGVqbXBkCkJkZm1i + b2RCWXBpSkJjaXBhbWRCaWNvbElCYWNnbWthQmlwZGxpbUJpaGprZ2pCQ2JrZGZCZmwxYWlCY2JSbmoKR2ZoZ2dsZUdtYWdlamZHZmRtZ2dtR25wZmJiYUdSaWZna2Rs + aGFtbHBmbG1laGlwaG1GaGdnbEYxYWpranBwRgpKY2djaENkZmtqZmZDZmxlcGliQ21ha2JqZENFaG1rZENwYmxibGRDb3BiY21oQ29vcGpubkNYbGZwYkNsamFtCnBp + Q2xqVm9qQ2NjZGFtakNtYXBnanBDZmJtZ2dtQ2NnanBiakNwZm9kaWZGU2dnbGVkbW9vaHBvYkdjY3BkZWsKQmNoamRqY0Jpa2JmbWJCb21ibnBkQmNoamRiY0RmaWJi + Y2lEY2RmZmRqRG9wYmNlaERsbGVrZmJEZmVrY2ZoRApjYlJTRGNjaWZmZkRjY2hqZW9EbG1wZmVlRGlsYWpka0QxcGFjb0RTaWxjZURWYWhibERtYlFiYkRraW9jYWlE + CmxoamNwbEJjZGlmbmZCZWlvaGthQmlnZmJnZUJrbmtoY2lCaGRrYXBvR2NlbWRrb0dlZ2lmVEdpcGtqaWJkYWYKaGdnZGVGY2RtcGxmRmNpaWhSQ2lqYmZPQ2JvZWho + bUNpbUhqbUNwYmxibGRDZmdob21jQ29wb2JtcENmZGRlbwpjQ2xqYW1waUNpcHBjYWdFcGZoZmJhRVZpYmJoRW1ha25ia0Vpbm1uSEVrYWpnYWtFaWZvcHBmQ2pqa2Fu + bENiCmNpbW1mQ2NnR2tsQ2Rqb2NpakNsbG1lZW5DaW9tYmFwQ2NlbXBsZkZFaG1jZEZmaGdnZGVkbW9sa3BobkdwYUsKQ0dWb3BpbUdBN2Rka2pnZGxtWnBramRGYWho + ZmNsQ2FwYWJoakNrYWpkaW5DY2tibmpnQ29obGhqcENtZ2FpawprQ2Ribm1sYkNuZ2drbGZDcGdYbGdDcGdYbGdDcGdYbGdDbGUyQ2Vrb2JrbUNiY2JpTkNuY2puaWRD + Y2RlaGZpCkNrbGFkY3BDWnBrYmRDbGpqaWFpQ2NuYXBBQ2NmZWhuaUZuamVvaWpGZWVSV2xtaWZpb2JjR25oZm1qcEdrZ2MKbnBiR0FtZWNmQmlpbG1mZ0Jlb21maWFC + aWdvYmlvQm5pYWNqaUJja2Nla2JCcHBibmtuQmFvY25sbUJIb2ZtbQpCYm1qbm5uQm9hb2lvbUJMYmNwa0JsYm9oYWNEZGFnbGFpRGlwZGlhbkRkZW1oYmFEamdvamJj + RDFtbmJkRGhmCmRkYmNEaGhjbWFoRGZvYWNuZUJsZ2lwaG1CYWdpZG1iR3BpWGxnbGxucGFua2ZGbWdhaWNrQ2ppb2NpaENubmIKbGxsQ2dlbWJvcENva2xhYmVFY2pj + bGNtRW9rYmtkZEVvaUNja0VnZm5wYm1FZW5oamFwRWZrbWxhZEVmZWJkcApkQ2RlcG1ucENkZ05tbkNma3BhbGxDb2hDa2tDZmluampqQ29ra2ppakNmbWJoaGZDY2Zl + aGZpQ2lkcG5ka0NwCmJnYmJrQ3BhamdvakZYbWdqaEZJamlhaUZtbG5ncGNkbEhoYmVmR1JpamxnR21wZ2FhZkJtampwY3BCaGFvaFMKQmlsYmhpY0JmcGRsamhCYWli + ZWttQmFrbWFFQmFwa2puYkJnbWdmb2NCanBuZ3BjQm9qa2RhYkRpY25tYWpEYgptYmZiY0RsZmVuYmtET25oY2NEamVrZWNtRGpnZmhkaURnYWVvZWZEYm5qZ2ZjRG5q + bm5mcERka2FoZ25EYmFhCmJoakRxTGJjUERlbmtvZm9EaW1lbmNvRGJra2VvaUJBanBnbkJlZVJvcGRtWnBrYmRDZWVjY2piQ2FmaWttbUMKaWFuZ2FiRWhmZGtibkVw + aWJqY2xFamRpbmFrRWRmbWFpZkNjbXBpZm5GQTI3Y21nZWpiQm5uVmNnQmlnY21hZQpDa2hkaGpuQ2RoRmxqQ2hhUGFkRUZvamVmRWlnY21VRXQ0bmVOaG9FcGZrZ2ho + RWpjaWZlbUVoYVBqZENjYlJuCmpCZW9XZ2NEWWtkaG1EZW9XaGNEZG1kYWZwRGduYWhlZ0RoYVBjZERtZW1wbmlCYmZJZGFCaGpKYmxkbWNjUmIKakZBMkZJTWFmbG1X + UEpkbWtnZGhobkdQSk5HUEpibEdha1NlaUZISWJhQ2FrU2VpQ2FrU2VpQ2NtZ2VjYkNjbQpnZWtiRlpMbGdHbmVOam9Cb2tlbm9oQmhhUHBkQktIb2NCYW1TWkJtakxr + b0JvYmRqamtCZGthY2luQmZvZmNpCmJCZmhpZGdoQm9tamNla0JpY2tjY25CSEliYUJmZ2lkb2hHaWFrY2tuR2ZlaWRnaEdjZm5rb2tkbVRlaFhkbGEKb2pva2RsbWJo + SWJhRktIVEZjYlJTRmRpRmtqRmNiUmJqQ2VuV2ZrQ2lmY21lZUNBMW1hRlBKTmxtQTIyN0hJYgphR2RpRmNqQmRlcGJsZkJIb3BtcEJwam9tZ2pCSG9wZXBHQTE1Ym1r + blhHbWphZmZoQlhhbWFqRFdQZmREbGhiCmNlaERrZG1hbW9CZW5qZWxkR0ExNW9tbWZpbkdtcG1ka2hCZGdmYWJvRGpvbm1iZURhY2Fra2RCa2thZGhiQm8KbG1maW5C + b2ZhaEdCb2ZhaEcxQTExUWVqZ21HUWVqZ21CZ2hkaGxiQlFlamdtQlFlamdtR0EzNTlIb3BtcGRtZgphcGRibEJBMTltbmVmT0dtbmVmT0JTVk1CbW5lZk9CbW5lZk9H + QTdpZ2dvbmpkbVNWYmFCanBhZGprQm9lYmNrCmRCY2xjY2NtQlNWYmFHQXZqaGhiZG1qcGFkamtHY2pDZG1CbW9oYmlwQmJlb2ZpaEJtb2hiYXBCWWpoaGJHbW4KaGJh + cEJwbG1qam5CbGVja25oQmptbWJvZUJqbmNmbmVCU0xqakJqcGdoYWpCaWdnb25qZG1BMzViY2JuY2pkbQpmaGdlZmRHcGtpaG9pR09pbGJvQk9pbGJvQnBqaWhvaUdm + aGdlZmRHYmNibmNqZG1BMjNtbmVmT2RsU1ZNZG1jCmpma2VuR2pvZGxsbEdiY2lub2xHbW1lZm1iR1dkYmRmR1NWYmFkbUEzbW5lZm1ibGtZamhwYmxsbGdhaWpubG0K + bGdhaWJuRm9nbmtpYkZjbGNja21GbGdZQ0ZtbmVmbWJGbW5lZm1iRm9lZWttZUZmaWdlSkZnbmticGhGanBhZApia0NmaVpMQ2xkYmVoYUNMb3BpbkNwbGJoa2dDY2ph + bW1mQ2ZpQW9lQ1dhZm9oQ2xna2VrbkNMaWxib0NqbmtsCnBubG1BMjdsZ2Fpam5sbWxlaGlucEZiZWRkTUNmaGVvb21DcG5obGJmRWNsaWdibEVvZm5hb25DYmNwYmZr + Q0wKY2hjcEZBMjNvZGljb2ZsbWJlaWJiaUNwbGxkbGdDaWhpamFrRWNrbmVjZEVja2VqZGFFYmRsamRqRVNaTEViYwpvYUdFaWlsYWFvRWhiZm5pZENmaWdlZmRGQTIz + aGhtaG5oZG1pZGJwcGdHbmFvZWdiQmlvcGtlbEJpaG1vZG1HCm5lamRjaUNkaGhkbWpDamJRYW9FZGdwZ0FFbWZrZmljQ0lpZmZvRkE3aW1obmlkR2pqbmZrYkJwYzFj + bERjYlIKU0RvYW9sZWdEYWpmbGJtRGtwb3BuZ0JGZ2FkZUJpbWhuYWRDSWlmUUNoamtsYmtFcG9tbmFiRVZXVUNtZWltSwpGQTExZGxhamZiR2RhY29obEJlZW1hQURq + ZkRia0RXb2phZERnaVdsakJpZGRpZGZCY2phY2dtRmdjS2trQ0RsCmliaEVGZ2FkZUVtamFnYW9FbWZrZmljQ2RsYWpmYkZBMjNoaG1oZmhHZW9pbmhvQkhnZnBwQmpk + a2piY0RvaGIKaG5nQmtwYWlIQnBubW5hYkNqY2tqamNDa2tubWlnQ2ltaG5hZENoaG1obmhsbUExNWdmbWFoY2Rtb2dqa2luRwpoYmxlYWhCYmxjbmFmQm5lamRjaUdo + Zm1obmhGbmlrZ2hpQ21ma2ZpY0NmZ29sUkNnZm1hcGNsbUEyN2hobWhmCmhkbWhobWhmaEdaZGxpa0doZG1obmhsbElpZlFGZ29ib0FDaW1obmlkRmhobWhmaGxtQTQ3 + aGhtaG5oZGxXYm8KQUdjamFjZ21HYmJmY2NwR2FwZmNrcGxtYm1ra21uRm1uZ2hiakNra25tYWdDaW1obmlkRmhobWhmaGxtQTU5ZwpvZnBmY0VZbGloYkVqbWJuaGZF + aWVibFdFa25qZGdpRWtuSWdiRWJpa2Zma0VTQmZkRW5rZmJlbEVUYW1LRWtqCmlkZGtFbW1laWRiRWNpWGNoRWJlbG1ibUUxbm5iY0VtYWxiYWpFR2piYWJFbWpra3Bm + Q2hobGZvakNnYW5jbm0KQ2pkbGttbkNka21obG1DZ2Raa2tDa2pmY2ppQ2RoRWlmQ2lpZmhnZ0NrZWRhT0NtYWFqYm1DbGltZm9uRmlhYwpma2JGWGhoSEZUam5jb2Rt + a2Jhb2drR25raGFtZUcxamJhYkJiZWxtYm1Cb21vZ2RoQmhjZWlmYUJhZmVvZ2FCCnBjbmhnbUJqZmNtaWFCYWJtY2luQmdtZmhOQmFnY3BraEJwY2xpbGRCZWtrbW1l + QmxqZmduakJpYWptb21CaWUKbm5wbUJpbGhiYWdEa2pjZ2FwRGdjaGFiaURsaGVvY2NEZ2FucGNtRG9tb2dkaERhaGxkTERhbmVoZW1EZ21oZwpmZURmcG5jZm1EY2dv + bWdnRGttaWxoYkRha2xsaGpEY29MaG9EcWpoZmRoYURlbkdkYURsa2Roa2FCamZjbWlhCkdBMTVrYmFvb2tkbWNmZWpiY0Jhb29ra2FCZ2RsbG5rQmtpb2FvZkJmbm9q + UUJsbURuaEJtZ3BrbmFCY2VnaW0KbEJjb2NkbWZCaW9oakZCb3BtcGxmQmtmbmlrb0JhZmNwa2hCZ2dpZmpwQmJsaW9qaUJobW9lTUJkYm9uaWpCZApoRWlmQm1tZmZp + YkJvbGFmaGJCZWthZWZoQmZrZGpka0JnZmlmYnBCYmVnZWFrQmplZ2twY0duY2pvWkdjZWVqCmpjR29sb2dkaEdraG1ia21kbWRqRWlmbGxpaTFhaUZkaEVpZkZja2Rr + bWhGR2piYWJDZ21maGJsQ2VqZmVUQ2gKb29tZWhDZ2RsbGZrQ2thYW9na0NkZ3BuaGhDZ2lva2liQ2ZlZWlpZkNucGNkaWhDRWxiaWpDZWlrZWluQ21uawpuamRDZXBt + b05DbGVnZmtlQ2ZiY2drb0NvanBubGpDbmpoYW1lQ2hpYmttbUNnbGhnbmVDTGtib3BDRlhib0VrCm9rZ2lnbG1rY2hvZGJHZ2NvZWpjR2xka2RvbEdrZlpjZ0JvZWdj + ZWZCb2lrbWRrQkRBcG9HWmZwY2xHb2JiaW4KYWxtcGpuYlVGcGduZWlwRmdjb2VqY0ZtcHBkamZGaWNkYmtjRmFwaW9sZUZrYmhvbGJGb2NiaWZhRnBqbmJWZAptZ2Zj + b2FpQmdqY2xnbkJrZVprZ0JoYkVuaEJtZW9qQURPcGNhaURnamNsb25CYmxiYmlwQmtwa2dpZ0dhcGlvCmxlRmhwb2hjbUNqaGthZmJDY2VwbmdkQ3BrbmJVQ2luYXBp + Y0NuZW9jaWZDbGFrZ2lnQ2lsZ2tpaENnZ2NvaWkKQ2luYXBpY0Nubm1vZmtDa2tsaEhDZG1hZGpqRmtwa2dpZ2xtamVmZ25tZG1pbWFwaWNHZGZXa29HYm1sZ2lqRwpp + am1mYW1sYmFqcGttakZsbGRoZmdDa2piY2ptQ2tiaG9sYkNkaGJla2pDZ2NvZWpjQ3BpbmJVQ2hoZmRMQ2JtCmxnYWpDcGduZWlwRmNsbmxtZGxrbmdpaGlhR1hsZG9v + R2JmY2NicEJiYWRkY3BCZGpsamNkQktqaGFkQmJjbmkKa2pHbGtkaG5nZG1pam1mYW1Gb2lrbWxrRkdraWJkQ29mZ2NlZkNBZ2dmcENsbWliZWxDVmdnYmRDWmZwa2xG + WQpsbGFrRmNjcGxwbWRsZmZsbUcxZGdubmtrR3BqbmJWR2RrZm9ib0JQam5kaEJhcGRkY3BCcGdpaHBwR2djb2VqCmNHZG1hZGJqR2NubmxLZG1uZ2loaWFsbUdhZGJq + RmZmbG1HRmNvbmxLRmNvbmxLRmhob2ZlZkZvYmJpZmFGcG8KaWpnZ0ZibmNlaWdGZmxmYWtpRmVqaGFvYkZtcHBkYmZDZGFpYUJDZ2tjbGduQ0draWpkQ29scGdrcENv + aWttbAprQ2RobGprZENjaW9tZmRDYm5sZ2lqRm9hYmlmYUdnY29lYmNCcG1oZ1dCaWRkYmtjQm1kaGFsbEJtYm1sbWFCCmtjaG9sYkJwamJvaWZCbGFrZ2FnQnBvbmJW + ZGxjbm5sbWRGaGdmZExDRGVuaW9DbWZiZmxnQ2FjUm5rQ21lb2oKQUVlb2tiYmlFamJpZmNtRWdvb29jaEVsYnBvcGFDRmliZWxDU1pJQkxIcGNHSU1pZkdaTGpnR2pt + UWxlR0pOUQpHSU1hZkJlb1dia0JnZWNiY2xCY2NSZGpCUlNlaUJaTGZnQmJrY2Zmb0JpZmNtZ2VCaGdnYWdvQm9tbWFobUJuCmVOaWdCZGFuaGlvQlFWamdCTEhrY0Jl + b1dJQmhhUG1kQm5lTm1vQlNabmNCV1BKQmlmY21uZUJRVm5nQlBKbmwKQmNtZ2VvYkJaTG9nQmRpRm9qQmtoZGhvbkJjbmdlcGJCaWdjbVZCam1RVkJqbVFWQmptUVZC + SU1wZkJhbVNwaQpCZWtwbTFCY2NSYWJEY2pwYmFkRG1tbGZhaERqbVFhbURib2ppYmREZ2VjYmJsRG5lTmNjRGNjUmNqRGxkTWNuCkRiaElkYURQSmRkRHBma2dkaERi + ZERkbERJTUdEZGxkYUQxbWZtcFlETEhMRGtrMUxEU1pMRG5wVkxEUEpLREkKTWVmRG9rZW5laERpZ2NtZWlESEllaURMSGVrRGlmY21lbURsYk1lbkRGb2plbkRGb2pl + bkRGb2plbkRaTGVvRApwZmtnZXBEZGlGZmJEZ2VjYmZkRFdQZmREV1BmZERXUGZkRFBKZmREa2dkaGZmRG9qZW5maERjY1JTRFNaZmtECmNiUmZuRG9qZW5mcERkaEZn + YkRjY1JnZkRqbVFnaURha1NnbURva2VuV0RaTGhjRENvamhmRG1qTFBEQ29qaG4KRHBma2docERxMzBISWhhRFBKZGREZ2tnYWRqQ2RmaWxaQ2pqUnBnQ3BubGFsaENh + am9ma2xGb29oaGtkQmpiYQpiZGdEZmdiY2VsRGltcG1iZkRjcGhibW9CaGFQbGRCamhibGxhQm9iS2twQmFnbmlrb0JtYWxta21CZGJpZ2tpCkJvbWhoa2RCYmljZ2pu + QmdsaGZqZEJoZ2tqaWhCamVwYmhsQm9qZW5XQmtsU2dlQkFsZ2ZoQm9sZ2NlakJwbWwKYWRoQm1scGFYQm1ibmJhZ0JhZGxnbmhHa2djYmtkR2tsb2RnaEdrb2xiYmpH + Tmpia21kbWpvamJrbWRsYWVqbgpkYWxtQm5pb2RsbWJsbWNHRm1ibmJpZ0ZqaGJsbGFGZWZtZW5tRk5HYWhDaWJaY2JDT0VkbUNBbGdmaENtYmttCmhjQ21ibmJpZ0NY + ZW5qZUNpYlprYkNvYktrcENPRWxtQ2tiZGtta0NBbGduaENnY2Rib2ZDbWJrbXBjQ2JiYmUKQUVtYm5iYWdFaGJpcGFuRWJsZnBiZUVrbWhnYmxFYmlwa2NjRWdtbWZj + a0VsamtjVEVhZ2lhZGtFZmtlbExFbQpnbW9lakVmaW9nZmFFYWNsZ2ZoRWpva2pmb0VwY1VnZ0VvamVuV0VpZWxpZ2hFaWhmb2NuRWZwT2pwQ2JiYmVpCmFGQTIxMWNi + ZW5iZUZsamZwamhHamZhcG9kR2xqZnBqaEdsamZwamhkbUE1MWxhZWxra2RtbGFlbGtrR2lpaGIKcHBHbGFlbGtrR2xhZWxra2RtQTE1aW1wbHBmZG1pbXBscGZHa2to + bWRpQmltcGxwZkdpbXBscGZkbUE5NW1jaApkYWVHbWNoZGFlQmtia25lZ0JtY2hkYWVCbWNoZGFlR0NvamFuR2NuZ2VkYkJjbmdlbGJCY25nZWxiQmNuZ2VkCmJCQ29q + YW5HQTNsamZwYmhHa2lkaEdCa2hkaEZCa2hkaEZCa2lkaEdCbGpmcGJoR0E0M2xqZnBiaEdraWRoR0IKa2hkaEZCa2hkaEZCa2lkaEdCbGpmcGJoR0EzOWxmbmZYR2xm + bmZYQmlwWWhiQmxmbmZYQmxmbmZYR0EzMWxqZgpwYmhHa2lkaEdCa2hkaEZCa2hkaEZCa2lkaEdCbGpmcGJoR0EzMWxqZnBqaGRta29ibmJpQmtvYm5iaURpZ2NtCmdl + RGtvYm5iaURrb2JuYmlCQTE5YWRoZG9uR2FkaGRvbkJMSFREYWRoZG9uQmFkaGRvbkdBMzVha1NaR2FrU1oKQm1oTEhEYWtTWkJha1NaR0EzMWVqcG1pbUdlanBtaW1C + Z29QSkJlanBtaW1CZWpwbWltR0E1MUxIVEdMSFRCbApiTWlmQkxIVEJMSFRHQTE1YWRoZG9uZGxvY2doYmVHb2JnaGplR29iZ2hqZUdvY2doYmVHYWRoZG9uZGxBN2tj + Cm1lbXBkbWtjbWVtcEdoakpibEJrY21lbXBHa2NtZW1wZG1BMTVhZGhkb25kbGFkaGRvbmRtTEhUR2FkaGRvbmQKbWFkaGRvbmRsQTE2N2hsSmJsRGRpRmdqRGZrWkxE + ZmtaTERkaUZnakRobEpibERobEpibEJBM29jZ2hiZUJvYwpnaGJlREpOZm9Eb2NnaGJlRElNaWZHYmREa2RDZm9kbGxhQ0pOZm9CbmxpYmRqRGhvZWdhakRnZWNiaWxC + b2tlCm5vcEJkZWVrcG1Ca2FtZWVwR2Rhbmhpb0NmZ2ZmbmZDQTJFWWtkZWVDaWNsanBnQmNjUlNESEliYURISWJhQkEKN2VoaWpicEJlaGlqYnBEb2tlbldEZWhpamJw + RGVoaWpicEJBMTVLSElGeElNYWZFeEtISUZBMzFLSElHSk5RQgpuZU5mb0RKTmZvREpOUUJLSElHQTIza2xra2NrQ2tsa2tja0V0a2xra2NrRWtsa2tja0NBMTVoZG9u + WUdoZG9uCllCYmhJTUJoZG9uWUJvY2doYmVHS0hURnpLSFRGS0hUbG1rZG1lbXBsbW9nbmthYkNvZ25raWJDb2dua2liQ28KZ25rYWJDa2RtZW1wbG1BMjNuZU5mb0du + ZU5mb0JvYVZrZ0JuZU5mb0JubGlibGpHbmxpYmRqQm5saWJsakJubAppYmxqQm5saWJkakJvY2doYmVHQTdLSFRGSk5mb0NuZU5RQ0pOUUNKTmZvQ0tIVEZBMTFLSFRk + bUpOZm9HbmVOClFHSk5RR0pOZm9HS0hUZG1BMTVobEpibEJpamFtcGhDaGpwZGlpQ29ra2NSQmZjaG5rZ0JuY2VmamhCcG5ubmwKbUJraWtucGJCaGFib2tiQmhhYm9r + YkdIbW1qY0ZIbW1qY0NjY1RubUNIbW1qY0NIbW1qY0ZiZGtrZXBHYmRrawplcEJpb0VOQmJmZGxkYkJvamtjaWxGcGdqamRnQ3BkaGhwZEZwZGhocGRsbWZwYWZkZEdm + cGFmZGRCamljZmdmCkJkYW5ta2xkbWdvaW5EQ25qaWpibkNuamlqYm5GQTE1YWdsZGtlR2FnbGRrZUJpamFtcGhCYWdsZGtlQmlhZGgKYXBHaW1jb2RrQ29ra2NpbENp + bWNvZGtDTmNmZ2ZkbXBoY2tqaUJvZW9wY2dEamVhZGdjRGJiYmpnb0RvZm9wZwpnRGdkamdmZURnaWVqZGpEY2poZ2NjRHBmYWliZkRrY3BrYW1EaGpwZGFpRGZkYW9h + aURmZGFvYWlEZmRhb2FpCkRmZGFvYWlEZmRhb2FpRGZkYW9haURFTmFoRGhmbmJhZkRucGRtYWNEbmZuZ3BpQmRhbGtvaUJpbGpubmlCWm4KcG1uQlJnZ21qQm9maWFa + Qm9maWFaQm1hTm1oQmxqZmhtYkJrY2lsa29CYW9paWltQmduaW5EQmRkbm1rbEdtZwplb2dtRmNuY2ppaENubGJrcHBDZGdwb2NvRW9qa2NlbEVua2lqZm5FZGRubWds + RWJuaHBoaEVBV2hvRXRuamlqCmhuRWtvcGJoaEVmbW9kV0Vha25mZ2hFa2lrbmdiRW5ocGlmbEVhaGxkZmVFaG1iZmVtRWhtZ21LRWdjb25kbEUKYm9jbmRnRUZqZ2Ri + RW5nZWtjbkVpaWZvY2lFTnBhWEVkZEgxRW9pYmJha0VqY29icG9DbWdlb29tQ3BrbGxuagpDbG1oam1lQ2FwaWlrbUNuY2VmamhDZWxta2liQ3BnampkZ0Nsa29pa2NG + Y2dvZmlhbG1BNTVhZ2xka2VGYWdsCmRrZUNBMTFScGlvZWRtZ2ZqamFuQmtkb2ZqaUJLUG1qQmhuTm1rQmxoZWlsakJhb2hia2RCaWtwZ2lrQmtuaGoKZ2dCcG1lbGRt + QmlhYmVhakJKbWRkZUdhaWxra2xGZmxnamZoQ3Bra21qcENqaEhtZ0NaVm9iQ2xlbXBwa0NiaQpsZWFpRUpva2FvRWhhaGxhcEVnZmpqYW5FcGRmZ1JFb2RpZGFpRWRk + Y3BhZUVqYmhjcG5DaW1laHBkQ3BsZm9vCmxDUnBpb2VDZWxuYW5uQ25wZWVKQ0FoaG1nQ2ZrbWtsa0NobHBta25DaWRoamptQ2xrZGZpa0NrbW9uaGlDYW0KcGlnZUNn + bmFjZmJDa2RpZWRmQ2pvbGthb0NvYm5hbWFGam5tbkdGa2xqbU1kbGtiUmhoR2tiUnBoR2ZkVWNnQgpla2JvYmNCa2pqbU1HYmphZmhiZG1BMTVCbGFjY0drbW5rZWpC + V2NubmxCZWtib2JjRFpMSERkaGZrYW9EZGRjCnBhZURla2VlcGFCbGtvaG5mQm1sYW1GQmdnZGlra0JvZ29pTkJtZGJoaW1CbmdhYmdvQmRiTGRkQmNmbmVtZEcKa2pq + bWJhZG1aVmdiRm5lbWRsZUZwbm9rbmlGY2hiY3BuRmtkb2ZiaUNsaWVpZGpDaWxraWZnQ2RmaWFnbUNpZwpNaGxDY2plbmljQ2RkY3BpZUNOaXBpZUNOaXBpZUNkZGNw + aWVDY2llbmljQ2NpYmNobkNwcGlqaGZDb2xtZmhiCkNiamFmaGJDYmphZmhiQ2JqYWZoYkNiamFmaGJDYmphZmhiQ2JqYWZoYkNiamFmaGJDaGhpZFdDcGFobWdqQ2MK + aGhkZ2FDcG5va2ZpQ2Rkb2JlcENEZXBEQ2pmaGhjakNpcGNiYmZDcGphbmFkQ2ZsZ2puaEZpb2NiamZGaGRrZwpiakZraWptYmFsbVJwaWdlZG1lbUZjb0dvYWRia2VH + ZGZpYW9tR2lwY2JiZkJiaG1oZGhCZ2poZ2dkQmZtbGhpCmxCb2dvaU5CZmhpbWliQmxmYWtBQnBub2tuaWRtQTQ3bmdtb0lsbWltaWNucEZnaG5taGZDb2prYmtoQ25n + bW8KSUNuZ21vSUNuZ21vSUNuZ21vSUNuZ21vSUNuZ21vSUNuZ21vSUNuZ21vSUNuZ21vSUNuZ21vSUNuZ21vSUNvaQprYmtoQ2dobm1oZkNpbWljbnBGbmdtb0lsbUEz + MW5nbW9JZG1pbWljbnBHbWpoZ1VCaGhta2tkQmlnYWhrakJZCkNrY0JnaGluam9CZ2Ntb2ptQnBsVWptQnBsVWptQnBsVWptQnBsVWptQnBsVWptQnBsVWptQnBsVWpt + QnBsVWoKbUJwbFVqbUJwbFVqbUJrbGtuamNCbmlYZmhCZGpqY21kR3BsVWptZG1BMTluZ21vVGxtYWRiamdmRm9mZXBhZApDamJrb0JDbGRmYmZsQ2VmZGlna0NhZkxo + aENlamhwaWNDbWZuZWlqQ2FvZWlqYkNmaGxsamlDZ2xsaGpwQ2tvCm5na2VDbGZja2toQ29qa2JraENvamtia2hDb2prYmtoQ29qa2JraENvamtia2hDb2prYmtoQ29q + a2JraENvamsKYmtoQ29qa2JraENvamtia2hDbWxjaGpuQ2thaG9nZ0NnZGlrbmJGb2prYmtobG1BMzlnYWRpY25HaGppZ2ZpQgppZWNub29CYW5laGNjRGpiaWZjbURt + YkdjbERpaWptY2pEbGpmZWNpRGxoRWNoRGJsaWVjaERia29wY2dEZWtrCmhjZkRiZGFnY2VEa2piaGNkRGVmb2ljZERvZm9jY2ZEaWNlaWNoRGZicGxjaERnaWluYm9E + UGtwb2tCbmpYZmgKQmdhZGljbkdBMjN1WEExMGZrVFhmaGdiaGdnZlVnYmdjZ21nZlhlZmhpaGFXaGNVQTRraWJtQTV1YWVYQThZQQo3dnFBMTE5YWdmaWFiRGFna29h + YkRBMTE1b2NLaGhCdmNtb2xla0RBMTE5ZWtQYmNCcUExMTl2cHBmZkFEQTExCjVsaGlkb21CdnFBMTE1dmVwaG9iZURwZm1vZG1EQTExOWhwb2NibEdxQTExOWZpa2xm + ZkJwZGNjbG1CQTExNWQKamhjYW9CZGptZ2luQm9jYmtkaURBMTE5YWhhY2FiQmJvbGthaEJBMTE5cG9mZHBwR2pra2NjZ0JBMTE1aWhtZApnYkdMcGZlcERxQTExNW1n + Z2NsYkd2ZGJnb2FtREExMTVuYmdrbGZCam5obG9pQnZBMTE1aGdvY05CaGdnZG5tCkJ2QTExNXRBM3FBMTExcG9wcGhwRHRwb3BwaHBFcUExMTV0QTNxQTExNXRwb3Bw + aHBEcUExMTV0QTNwbHBwaHAKRUExMTFwb3BwaHBEdEEzcUExMTFxQTN0cG9wcGhwRUExMTV0cTFBMTE1dGNvZ2VjYkRtaExIRUExMTVqZ1FWQgpnb1BKQ3FBMTExcG9w + cGhwRHRxdEExMTFwb3BwaHBEdHBvcHBocERwa3BwaHBFQTExMU9ISUZjY1JTRWtnZGhHCkVxQTExNXBvcHBocERBM3FBMTE1cG1wcGhwRG5tVmNnRUExMTVuY05mb0Rr + aWRoR0Rwb3BwaHBFQTM3MXMxQTMKc0E3NTlzMUExMTVzMkEyNDNzMkEyNDdzQTI0N3MyQTM2N3MxQTNzQTc1OXMxQTExNXMyQTI0M3MyQTI0N3NBMgo0N3MyQTExMWFl + QTJ1YmFBOVlmaWVtZG1jZWhwM2dhZWlkYWJpaHAzdUFYQThZQTgxOTF1WEExMGZrVFhmaGdiCmhnZ2ZVZ2JnY2dtZ2ZYZWZoaWhhV2hjVUE0bWxiZkE1dWJhQTlZZmNm + YmVmQWhwNGhvWURocDN1YmFBOVlmY2YKYmVmQWhwNGhvWURocDN1YmFBOVlmY2ZiZWZBaHA0aG9ZRGhwM3ViYUE5WWZjZmJlZkFocDRob1lEaHAzdWJhQQo5WWZjZmJl + ZkFocDRob1lEaHAzdWJhQTlZZmNmYmVmQWhwNGhvWURocDN1YW1hZEE1cWVlZWpmY2VoYWRBMmFnCkEyZW5XZ2VlZ2ZpZGJBb2FhY2FiQTFlbldnZWVnZmlUQWdhYWNh + YkExZW5namhpZGZBMmFiYWNnYkExZWVnamgKZFVkZEFXZ2Nha2JlQTFmY2dmaGdkYkExYWhubmFiMWJhQWZkZ2lnYmhhZ2ZkZEFNYWNhYkExZmRnaWdiaGFnZgpkZUFh + ZGFjYWJBMWVlZ2poZFVkZUFXZ2Nha2JpQTFlZWdmZ21nYmhqZGJBZ2VhYjFBMWVuZ2poaWRnQW5nZmRwCmFhY2diQTFlZWdmZ21nYmhqVEFhYmFjYWJBMWVvaGZmY2dm + aGdkYkFnbWFrYmJiYUFmY2dqZ29naGRkQTFhYzEKZ2JBMUtXZ25oYWRiQWZmaWFha2JjQTFLV2duaGFUQWFobmhha2JlQTF3Z2Rnb1VnbUEzZWZmYmRiQXdha2JiQQox + ZWZmYlRBYmNuZ2ZnaWFha2JjQTFmZ0tlZ2RmQTJtamFjYmVBMXdnZGdvVWdtQTNmZ0tlZ2Rnd2FjYmJBMWZkCkxkZEFiY25nZmhvYWFrYmJBMWZpZW5lZ2RkQTJqb2Fr + aGNBMXdnZGdvVWdtQTRuZ1NYd0E2amtiY25nZmZnYUEKNGdvVWdtQTJqaEEzd2dkZ29VZ21BNG5nZmlrYXdBNmdqYmNuZ2ZobWFBNGdvVWdtQTJnaUEzd2dkZ29VZ21B + NApuZ2ZoWHdBNmdnYmNuZ2ZnZ2FBNGdvVWdtQTJnZkEzd2dkZ29VZ21BNG5nZmZBd0E2SGJjbmdTZ2FBNGdvVWdtCkEyYmJBM3dnZGdvVWdtQTRuZ2Zmb2F3QTZhcGJj + bmdmZmthQTRnb1VnbUEyYW9BM3dnZGdvVWdtQTRuZ2Zoa2EKd0E2YW1iY25nZmZYQTRnb1VnbUEyYWtBM3dnZGdvVWdtQTRuZ2ZpWHdBNmFpYmNuZ2ZnWEE0Z29VZ21B + MmFoQQo0aWlqZGxtZ2Rnb1VnbUE0bmdmZ29hd0E2YWZiY25nZmdtYUE0Z29VZ21BMmFlQTN3Z2Rnb1VnbUE0bmdmaFl3CkEzd2JjbmdmaGdhQTRnb1VnbUEyYWJBM3dn + YmdkMWdmQTRuZ2ZkbGFBMmFiQTZja2JjbmdmZW9hQTRnZDFnZkEKMmNqQTZhYmdiZ2QxZ2ZBM3VYQTEwZWdnamdtVWdmaGNBMjV1WEExMGZnZ2pnY2hjZ2JVV0EyNHVY + QTEwZWZnbwpoZ2dmZ21XaGFnZmhkQTIydVhBMTBlZmdnMWdmZ2RVaGRBMjR1QWFiQTlmaVhHWEtoZlVXZ2cxYW5TWEdYZmNnCmZoZFdnb2diZ29nZGdmQTIzMXVBYWJB + OWZpWEdYZmNnYlVnZmFuU1hHWE9nbmhhZ21nalVoZmdlZ2ZBMjMzdUEKYWJBOWZpWEdYT2duaGFYZ2Znb2hnYW5TWEdYZWdnamdtVWdmaGNYZ2Znb2hnQTIyOXVBYWJB + OWZpWEdYZmNnZgpoZ2dmaGNnY2FuU1hHWGVlZ2ZnbWdiaGpBMjM1dVhBMTBjbzJBaGZnbVVBMjR1WEExMGNvMkFoZmdtVUEyNHVYCkExMGNvMkFoZmdtVUEyNHVYQTEw + Y28yQWhmZ21VQTI0dVhBMTBjbzJBaGZnbVVBMjR1WEExMGNvMkFoZmdtVUEKMjR1WEExMGNvMkFoZmdtVUEyNHVYQTEwY28yQWhmZ21VQTI0dVhBMTFnZmdnZ2JoZmdt + VUFkZHBpZ2FFbGdoYwpnYUVob29uZnBFaWpnaWZwRW5qb2Rmb0VnbmZwZm9FdVhBMTFnZmdnZ2JoZmdtVUFsbGlmZGFEaWdoZGRhRGZhCmdiZGFEYmllcGRhRFFkbWRh + RGtjY2tkYUR1WEExMWdmZ2dnYmhmZ21VQWVvaGJpZ0RFaWxpZ0RkZmtnaWdEbGIKbWFpZ0RUbmxpZ0RsanBmaWdEdVhBMTFnZmdnZ2JoZmdtVUFkYXBsZWlGaGRib2Vt + RmtwT2VwRm9jZ2VmY0ZhbwppaWZmRmRia2xmaUZ1WEExMGNvMkFoZmdtVUEyNHVBYWJBOExjbzJBMjUydUFhYkE4TGNvMkEyNTI9MjMxMjkxCjIKAAA= + AFNvbG8gVHJ1bXBldCBUcmlidXQAEAAAAA== + > + PRESETNAME "Solo Trumpet Tribut" + FLOAT 42 78 920 696 + FXID {098218C5-54FD-BA49-BAF7-D52E920FA665} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-842-pot-browser.RPP b/plugin-reaper-realearn/resources/test-projects/issue-842-pot-browser.RPP new file mode 100644 index 0000000..3370b90 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-842-pot-browser.RPP @@ -0,0 +1,176 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADhAAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiI3dUtEVWtHRSIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJp + bnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 500 117 752 675 + FXID {F2701C16-6E78-544A-9A9A-CDB007F85F96} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABEAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOAT 43 205 372 469 + FXID {A6CE18CC-7E2B-BF40-82A1-7BAD5EF599F7} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-842-shim-crawling.RPP b/plugin-reaper-realearn/resources/test-projects/issue-842-shim-crawling.RPP new file mode 100644 index 0000000..ca8611f --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-842-shim-crawling.RPP @@ -0,0 +1,221 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + YU1pTu5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAAAwIAAAAQAAAAAAEAA= + MCAAAAAAAAABAAAAaHNpbgEAAAAAAAAAxbjVM6crS0eKz5+Pijz9qpYAAAAAAAAARFNJTnYAAAABAAAASAAAAAAAAABEU0lOagAAAAEAAAAYAAAAAAAAAERTSU4BAAAA + AQAAAAEAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAANYmWFAQAAAA5wEAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAAMAAwADAAAAAAAAAAAAAAAAAAAAAAAAEA + AAABAAAAAAAAAERTSU5lAAAAXh8AAAAAAAABAAAAaHNpbgEAAAAAAAAAW8iJisi/6kOCYhFN+wd5Ym0AAAAAAAAARFNJTmUAAAABAAAANAAAAAAAAABEU0lOagAAAAEA + AAAYAAAAAAAAAERTSU4BAAAAAQAAAAEAAAABAAAAAgAAAAEAAAAABwAAAAEAAAAKAAAAMQAuADMALgAwAC4AMgAwADMANgABAAAAAwAAAAAAAABEU0lObAAAAGgCAAAA + AAAAAQAAAGhzaW4BAAAAAAAAAHzZeZTJsdJOoa8PzsQFcFo4AgAAAAAAAERTSU5sAAAAAQAAABgAAAAAAAAARFNJTgEAAAABAAAAAQAAAAEAAAACAAAAAQAAAAAAAAAM + AAAAMwAgAE8AYwB0AGEAdgBlACAAUwB1AGIADgAAAEoAYQBtAGUAcwAgAFQAaABvAG0AcABzAG8AbgASAAAATgBhAHQAaQB2AGUAIABJAG4AcwB0AHIAdQBtAGUAbgB0 + AHMAAAAAAAAAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAIAAAAHAAAATQBhAHMAcwBpAHYAZQAPAAAATQBhAHMAcwBpAHYAZQAgAEYAYQBjAHQAbwBy + AHkABQAAAAQAAABCAGEAcwBzAAMAAABEAHIAeQAKAAAATQBvAG4AbwBwAGgAbwBuAGkAYwAIAAAAUwB1AGIAIABCAGEAcwBzAAkAAABTAHkAbgB0AGgAZQB0AGkAYwAA + AAAABwAAAAcAAABcAEAAYwBvAGwAbwByAAEAAAAwABEAAABcAEAAZABlAHYAaQBjAGUAdAB5AHAAZQBmAGwAYQBnAHMAAQAAADEACwAAAFwAQABzAG8AdQBuAGQAdAB5 + AHAAZQABAAAAMAAHAAAAXABAAHQAZQBtAHAAbwABAAAAMAAGAAAAXABAAHYAZQByAGwABgAAADEALgA3AC4AMQA0AAYAAABcAEAAdgBlAHIAbQAGAAAAMQAuADcALgAx + ADQABwAAAFwAQAB2AGkAcwBpAGIAAQAAADAAAQAAAAAAAAACAAAARFNJTnkAAACWEwAAAAAAAAEAAABoc2luAQAAAAAAAADFLjxX59nmRbwbvecfTEm0ZhMAAAAAAABE + U0lOeQAAAAEAAAAYAAAAAAAAAERTSU4BAAAAAQAAAAEAAAABAAAAAQAAABsAAAABAAAACAAAAAYAAABNAGEAYwByAG8AcwABAAAADgAAADwAewBbAGEAdQB0AG8AbgBh + AG0AZQBdAH0APgAAAAAAAACAPwAAAAABAAAAAQAAAA4AAAA8AHsAWwBhAHUAdABvAG4AYQBtAGUAXQB9AD4AAAAAAAAAgD8BAAAAAgAAAAEAAAAOAAAAPAB7AFsAYQB1 + AHQAbwBuAGEAbQBlAF0AfQA+AAAAAAAAAIA/AgAAAAMAAAABAAAADgAAADwAewBbAGEAdQB0AG8AbgBhAG0AZQBdAH0APgAAAAAAAACAPwMAAAAEAAAAAQAAAA4AAAA8 + AHsAWwBhAHUAdABvAG4AYQBtAGUAXQB9AD4AAAAAAAAAgD8EAAAABQAAAAEAAAAOAAAAPAB7AFsAYQB1AHQAbwBuAGEAbQBlAF0AfQA+AAAAAAAAAIA/BQAAAAYAAAAB + AAAADgAAADwAewBbAGEAdQB0AG8AbgBhAG0AZQBdAH0APgAAAAAAAACAPwYAAAAHAAAAAQAAAA4AAAA8AHsAWwBhAHUAdABvAG4AYQBtAGUAXQB9AD4AAAAAAAAAgD8H + AAAACAAAAAEAAAAEAAAAAgAAAEUAUQABAAAACAAAAEwAbwAgAFMAaABlAGwAZgAAAAAAAACAP4QAAAAFAAAAAQAAAAUAAABCAG8AbwBzAHQAAAAAAAAAgD+FAAAABgAA + AAEAAAAEAAAARgByAGUAcQAAAAAAAACAP4YAAAAHAAAAAQAAAAgAAABIAGkAIABTAGgAZQBsAGYAAAAAAAAAgD+HAAAACAAAAAEAAAADAAAABgAAAE0AYQBzAHQAZQBy + AAEAAAAGAAAAVgBvAGwAdQBtAGUAAAAAAAAAgD85AAAAAQAAAAEAAAADAAAAUABhAG4AAAAAAAAAgD84AAAAAgAAAAEAAAAGAAAAQgB5AHAAYQBzAHMAAAAAAAAAgD83 + AAAAAwAAAAEAAAAEAAAABAAAAEYAWAAgADEAAQAAAAcAAABEAHIAeQAvAFcAZQB0AAAAAAAAAIA/LAAAAAEAAAABAAAABgAAAEsAbgBvAGIAIAAyAAAAAAAAAIA/LQAA + AAIAAAABAAAABgAAAEsAbgBvAGIAIAAzAAAAAAAAAIA/LgAAAAMAAAABAAAABgAAAEsAbgBvAGIAIAA0AAAAAAAAAIA/LwAAAAQAAAABAAAABAAAAAQAAABGAFgAIAAy + AAEAAAAHAAAARAByAHkALwBXAGUAdAAAAAAAAACAPzAAAAAFAAAAAQAAAAYAAABLAG4AbwBiACAAMgAAAAAAAACAPzEAAAAGAAAAAQAAAAYAAABLAG4AbwBiACAAMwAA + AAAAAACAPzIAAAAHAAAAAQAAAAYAAABLAG4AbwBiACAANAAAAAAAAACAPzMAAAAIAAAAAQAAAAUAAAAFAAAATwBzAGMAIAAxAAEAAAAFAAAAUABpAHQAYwBoAAAAAAAA + AIA/CAAAAAEAAAABAAAABgAAAEsAbgBvAGIAIAAxAAAAAAAAAIA/CQAAAAIAAAABAAAABgAAAEsAbgBvAGIAIAAyAAAAAAAAAIA/CgAAAAMAAAABAAAAAwAAAEEAbQBw + AAAAAAAAAIA/CwAAAAQAAAABAAAABwAAAEYAbAB0ACAATQBpAHgAAAAAAAAAgD8MAAAABQAAAAEAAAAFAAAABQAAAE8AcwBjACAAMgABAAAABQAAAFAAaQB0AGMAaAAA + AAAAAACAPw0AAAABAAAAAQAAAAYAAABLAG4AbwBiACAAMQAAAAAAAACAPw4AAAACAAAAAQAAAAYAAABLAG4AbwBiACAAMgAAAAAAAACAPw8AAAADAAAAAQAAAAMAAABB + AG0AcAAAAAAAAACAPxAAAAAEAAAAAQAAAAcAAABGAGwAdAAgAE0AaQB4AAAAAAAAAIA/EQAAAAUAAAABAAAABQAAAAUAAABPAHMAYwAgADMAAQAAAAUAAABQAGkAdABj + AGgAAAAAAAAAgD8SAAAAAQAAAAEAAAAGAAAASwBuAG8AYgAgADEAAAAAAAAAgD8TAAAAAgAAAAEAAAAGAAAASwBuAG8AYgAgADIAAAAAAAAAgD8UAAAAAwAAAAEAAAAD + AAAAQQBtAHAAAAAAAAAAgD8VAAAABAAAAAEAAAAHAAAARgBsAHQAIABNAGkAeAAAAAAAAACAPxYAAAAFAAAAAQAAAAUAAAAHAAAATQBvAGQAIABPAHMAYwABAAAABQAA + AFAAaQB0AGMAaAAAAAAAAACAPxwAAAABAAAAAQAAAAgAAABSAGkAbgBnACAATQBvAGQAAAAAAAAAgD8dAAAAAgAAAAEAAAAFAAAAUABoAGEAcwBlAAAAAAAAAIA/HgAA + AAMAAAABAAAACAAAAFAAbwBzAGkAdABpAG8AbgAAAAAAAACAPx8AAAAEAAAAAQAAAAYAAABGAGwAdAAgAEYATQAAAAAAAACAPyAAAAAFAAAAAQAAAAMAAAAFAAAATgBv + AGkAcwBlAAEAAAAFAAAAQwBvAGwAbwByAAAAAAAAAIA/FwAAAAYAAAABAAAAAwAAAEEAbQBwAAAAAAAAAIA/GAAAAAcAAAABAAAABwAAAEYAbAB0ACAATQBpAHgAAAAA + AAAAgD8ZAAAACAAAAAEAAAAEAAAACAAAAEYAaQBsAHQAZQByACAAMQABAAAABgAAAEsAbgBvAGIAIAAxAAAAAAAAAIA/IQAAAAEAAAABAAAABgAAAEsAbgBvAGIAIAAy + AAAAAAAAAIA/IgAAAAIAAAABAAAABgAAAEsAbgBvAGIAIAAzAAAAAAAAAIA/IwAAAAMAAAABAAAABAAAAEcAYQBpAG4AAAAAAAAAgD80AAAABAAAAAEAAAAEAAAACAAA + AEYAaQBsAHQAZQByACAAMgABAAAABgAAAEsAbgBvAGIAIAAxAAAAAAAAAIA/JAAAAAUAAAABAAAABgAAAEsAbgBvAGIAIAAyAAAAAAAAAIA/JQAAAAYAAAABAAAABgAA + AEsAbgBvAGIAIAAzAAAAAAAAAIA/JgAAAAcAAAABAAAABAAAAEcAYQBpAG4AAAAAAAAAgD81AAAACAAAAAEAAAACAAAABwAAAEYAbAB0ACAATQBpAHgAAQAAAAcAAABT + AGUAcgAvAFAAYQByAAAAAAAAAIA/JwAAAAEAAAABAAAABwAAAE0AaQB4ACAAMQAvADIAAAAAAAAAgD82AAAAAgAAAAEAAAACAAAACAAAAEYAZQBlAGQAYgBhAGMAawAB + AAAAAwAAAEEAbQBwAAAAAAAAAIA/GgAAAAMAAAABAAAABwAAAEYAbAB0ACAATQBpAHgAAAAAAAAAgD8bAAAABAAAAAEAAAACAAAACAAAAEkAbgBzAGUAcgB0ACAAMQAB + AAAABgAAAEsAbgBvAGIAIAAxAAAAAAAAAIA/KAAAAAUAAAABAAAABgAAAEsAbgBvAGIAIAAyAAAAAAAAAIA/KQAAAAYAAAABAAAAAgAAAAgAAABJAG4AcwBlAHIAdAAg + ADIAAQAAAAYAAABLAG4AbwBiACAAMQAAAAAAAACAPyoAAAAHAAAAAQAAAAYAAABLAG4AbwBiACAAMgAAAAAAAACAPysAAAAIAAAAAQAAAAMAAAAGAAAAVQBuAGkAcwBv + AG4AAQAAAAkAAABQAGkAdABjAGgAIABNAGkAeAAAAAAAAACAP4EAAAAFAAAAAQAAAAgAAABXAGEAdgBlACAAUABvAHMAAAAAAAAAgD+CAAAABgAAAAEAAAADAAAAUABh + AG4AAAAAAAAAgD+DAAAABwAAAAEAAAABAAAABQAAAEcAbABpAGQAZQABAAAABAAAAFQAaQBtAGUAAAAAAAAAgD9+AAAAAQAAAAEAAAACAAAABwAAAFYAaQBiAHIAYQB0 + AG8AAQAAAAQAAABSAGEAdABlAAAAAAAAAIA/fwAAAAIAAAABAAAABQAAAEQAZQBwAHQAaAAAAAAAAACAP4AAAAADAAAAAQAAAAgAAAAFAAAARQBuAHYAIAAxAAEAAAAG + AAAAQQB0AHQAYQBjAGsAAAAAAAAAgD9eAAAAAQAAAAEAAAAFAAAARABlAGMAYQB5AAAAAAAAAIA/ZgAAAAIAAAABAAAABwAAAFMAdQBzAHQAYQBpAG4AAAAAAAAAgD9u + AAAAAwAAAAEAAAAHAAAAUgBlAGwAZQBhAHMAZQAAAAAAAACAP3oAAAAEAAAAAQAAAAcAAABBAHQAdAAgAEwAdgBsAAAAAAAAAIA/YgAAAAUAAAABAAAABwAAAEQAZQBj + ACAATAB2AGwAAAAAAAAAgD9qAAAABgAAAAEAAAAHAAAAUwB1AHMAIABMAHYAbAAAAAAAAACAP3IAAAAHAAAAAQAAAAgAAABTAHUAcwBNAG8AcgBwAGgAAAAAAAAAgD92 + AAAACAAAAAEAAAAIAAAABQAAAEUAbgB2ACAAMgABAAAABgAAAEEAdAB0AGEAYwBrAAAAAAAAAIA/XwAAAAEAAAABAAAABQAAAEQAZQBjAGEAeQAAAAAAAACAP2cAAAAC + AAAAAQAAAAcAAABTAHUAcwB0AGEAaQBuAAAAAAAAAIA/bwAAAAMAAAABAAAABwAAAFIAZQBsAGUAYQBzAGUAAAAAAAAAgD97AAAABAAAAAEAAAAHAAAAQQB0AHQAIABM + AHYAbAAAAAAAAACAP2MAAAAFAAAAAQAAAAcAAABEAGUAYwAgAEwAdgBsAAAAAAAAAIA/awAAAAYAAAABAAAABwAAAFMAdQBzACAATAB2AGwAAAAAAAAAgD9zAAAABwAA + AAEAAAAIAAAAUwB1AHMATQBvAHIAcABoAAAAAAAAAIA/dwAAAAgAAAABAAAACAAAAAUAAABFAG4AdgAgADMAAQAAAAYAAABBAHQAdABhAGMAawAAAAAAAACAP2AAAAAB + AAAAAQAAAAUAAABEAGUAYwBhAHkAAAAAAAAAgD9oAAAAAgAAAAEAAAAHAAAAUwB1AHMAdABhAGkAbgAAAAAAAACAP3AAAAADAAAAAQAAAAcAAABSAGUAbABlAGEAcwBl + AAAAAAAAAIA/fAAAAAQAAAABAAAABwAAAEEAdAB0ACAATAB2AGwAAAAAAAAAgD9kAAAABQAAAAEAAAAHAAAARABlAGMAIABMAHYAbAAAAAAAAACAP2wAAAAGAAAAAQAA + AAcAAABTAHUAcwAgAEwAdgBsAAAAAAAAAIA/dAAAAAcAAAABAAAACAAAAFMAdQBzAE0AbwByAHAAaAAAAAAAAACAP3gAAAAIAAAAAQAAAAgAAAAFAAAARQBuAHYAIAA0 + AAEAAAAGAAAAQQB0AHQAYQBjAGsAAAAAAAAAgD9hAAAAAQAAAAEAAAAFAAAARABlAGMAYQB5AAAAAAAAAIA/aQAAAAIAAAABAAAABwAAAFMAdQBzAHQAYQBpAG4AAAAA + AAAAgD9xAAAAAwAAAAEAAAAHAAAAUgBlAGwAZQBhAHMAZQAAAAAAAACAP30AAAAEAAAAAQAAAAcAAABBAHQAdAAgAEwAdgBsAAAAAAAAAIA/ZQAAAAUAAAABAAAABwAA + AEQAZQBjACAATAB2AGwAAAAAAAAAgD9tAAAABgAAAAEAAAAHAAAAUwB1AHMAIABMAHYAbAAAAAAAAACAP3UAAAAHAAAAAQAAAAgAAABTAHUAcwBNAG8AcgBwAGgAAAAA + AAAAgD95AAAACAAAAAEAAAAEAAAABQAAAE0AbwBkACAAMQABAAAAAwAAAEEAbQBwAAAAAAAAAIA/PgAAAAEAAAABAAAABAAAAFIAYQB0AGUAAAAAAAAAgD86AAAAAgAA + AAEAAAAGAAAASwBuAG8AYgAgADEAAAAAAAAAgD9GAAAAAwAAAAEAAAAGAAAASwBuAG8AYgAgADIAAAAAAAAAgD9CAAAABAAAAAEAAAAEAAAABQAAAE0AbwBkACAAMgAB + AAAAAwAAAEEAbQBwAAAAAAAAAIA/PwAAAAUAAAABAAAABAAAAFIAYQB0AGUAAAAAAAAAgD87AAAABgAAAAEAAAAGAAAASwBuAG8AYgAgADEAAAAAAAAAgD9HAAAABwAA + AAEAAAAGAAAASwBuAG8AYgAgADIAAAAAAAAAgD9DAAAACAAAAAEAAAAEAAAABQAAAE0AbwBkACAAMwABAAAAAwAAAEEAbQBwAAAAAAAAAIA/QAAAAAEAAAABAAAABAAA + AFIAYQB0AGUAAAAAAAAAgD88AAAAAgAAAAEAAAAGAAAASwBuAG8AYgAgADEAAAAAAAAAgD9IAAAAAwAAAAEAAAAGAAAASwBuAG8AYgAgADIAAAAAAAAAgD9EAAAABAAA + AAEAAAAEAAAABQAAAE0AbwBkACAANAABAAAAAwAAAEEAbQBwAAAAAAAAAIA/QQAAAAUAAAABAAAABAAAAFIAYQB0AGUAAAAAAAAAgD89AAAABgAAAAEAAAAGAAAASwBu + AG8AYgAgADEAAAAAAAAAgD9JAAAABwAAAAEAAAAGAAAASwBuAG8AYgAgADIAAAAAAAAAgD9FAAAACAAAAAEAAAAAAAAAAQAAAERTSU50AAAAnwgAAAAAAAABAAAAaHNp + bgEAAAAAAAAAs6wXPJfDXEKNPGSPlVjYYm8IAAAAAAAARFNJTnQAAAABAAAAVggAAAAAAABEU0lOcwAAAAEAAAAYAAAAAAAAAERTSU4BAAAAAQAAAAEAAAABAAAAAScY + AAAdCAAAAicYAGAAAAEgBQNoc2luQAdAABAWwMqCBEKoTKeKTwkO2DeTGEAUIAADRFNJTkAj4AcDQABAGwBtIAgB0xcgBCAAQCPgA1MQwn5d2OKdMECNHMTHOCm78aOg + J8A3QC/gF2dAIwBboDcABSBjAecWIAQgAAN0c3JwwDvgFQAF0D4AAIA/4AMDAinczyATQAAC9D0AoBsCMzOgoBNAAMAv4AAAID8ALyAfQBdAC+ADAASgOwBAfSAfQAAC + gDMA4AAAQAtgL+ALAEAvoAMAUSBXADUgA6APAkA9P4AL4AA7CD8AQAs/TtphPuADAwAA4FIAA202Gz/gGV4AICE7wAAAqiAL4ACvIAAgCwPANz8A4AMAAcA8IA8gF+AA + A+AGAOAXHwFASyA7wAAAeuAT/0An4AMDYkMCDAUBYmtCTwACIBMCgAAAYANCUyAAAIAgUEADwDgAQCBAQAMBgD9AMEADATwAgAfgJwvgJGzgDixgACCRQABAlYAA4A4s + 4A3G4A4sYADgCFlA40ADQPNAAEALQAPgBw9AVOABAATAAAAAQGAngC0ABiAHBVd0LVBvc0AJBkRldHVuZQcgEwdEYW1waW5nBCAKA1Jlc29AHAZGWCBNaXgIIBEHRmVl + ZGJhY2tAFQJBdHQgCUAyBlJlbGVhc2VAMkAAA1VVKUHAewD/IABAE8AJQEQAAMAoAAAhwAEDAIAAICcgKATsUXi/DYAQIAAgEiATAAkgCeASMgIzM3PgCDIACiAx4AQy + QI4DHoVrPyBQIFFAACAIIAkAC0AKQmQAHSAIwJbAvwRSuJ66DiAXgAAgLyAwYJzAKAAA4Ai/AqNwfeAIY2BC4BEyA7FH4b1gVWAAIGQgZQAPQAvgAzJAv0AAIB0gHkAA + IAggCQAQQApAv0AAwDDAvwMpXI86YHxgACAvIDAAEkALwCgAAOAIv0AAICcgKEAAIAggCQATQArgETID61F4P6X1IAAgMSAyABQgCeAEMuAPvwAVICfgAL9Cw0AAgGsA + QCKwASBBQh4EKVwPvwCgAAFAv2EbYAAgaiBrACFAC4AyAABg+kAyQAAgHSAeQAAgCOAECQAiQBTAMgAAIQRAngP//38/YU5gAAD/IAACAAAjQAtCI4As4AGSQAAgISAi + QAAgCOAECQAkQBSAMuAfkgAlICngADIAAKCSQAAgWuAEWwAmQBTAW8AHAADgEjABKAAgAOAbKAApICcBAAHAKcAHAADgEloBKgAgAOAbKAArICcBAAEgyeAQAMBFwrtA + AEAsQAfACQAcIAliJEAYwAAgEuAcgkPg4AIoAMBh80AAQEhAB8AJAB4gCeACKIIcQADgBygAH0AUwLcAAKAoA5mZmT7jBw0BIAAgAOAHKEAAgFED9CjcPmOWAQAaIANg + AOARLEAAgDZpXoAoAMAjKgHAQELEQACAHkAAgAmpnmAAAAFjY4A1AIBDVOAQNQGEACAA4BsoAIUgJ2AoButRhkDAWKzgEFEAhiAnYCgAAOAWegGHACAAgHpAAOERgQEn + ACAAgChougC/4Q8CATYAIACAKEAAwFGmfSETAIBltQIBAf4hMqAo4RUeATgAIADgIFFAYkAAwDABq6pJkoAHIH1gACB5ICgAgUALwCYBAABA1kAAiOhAACAlICYAgkAK + gCaAp+AFRQGDACAAQQyAIgVAwgAAkELgBSIBfwAgAIAeQIcBKj7gBR4BgABAAMAfAQAA4AeAAX4A4A8AQHbgCwDgCxdAc0AD4P0A4AEAYSshGOAkAiAAQDdAA+BHAGBT + 4CSMICwgAEeK4hFDAFogIeAIHgK3HgWlQABeIB3gCB5AACMBIwIAYkAK4BFcAGbgFR4AasAeB2w/BUJQGtvCgLDAXABuIAngElwAciAd4BIeAHYgHeASHgB6IB3gAABA + d0AD4AEA4RU0AFtAKOAR9gBf4BUeAGPgFR4AZ+AVHgBrwB7hDTQAb8AegADkBzIBcwAgAOARuQB3IB3gEh4AeyAd4AAAQTBAA+ABAOEVNABcQCjgEXoBYAAgAOARHgBk + IB3gEh4AaCAd4BIeAGwgHWAe4Q00AHAgHWAegADhBzQBdAAgAOARmgB4IB3gEh4AfCAd4AAAQTBAA+ABAOEVNABdQCjgB3oDXI8CP2wQAQBhIAPgCB5AACOdI54AZUAK + 4Ac9QAAgHSAeAGlACuARHlK3YB7hDTQAcSAdYB6AAOEHNAF1ACAA4BFcAHkgHeAIHgMehes+bLYBAH0gA+AAAOEBMNF2QFdAA4AA4Ad6MKjAHke7AYC/wEAg9SD2AUIA + IADgET0ARiAdYABs5IAnANgnvAEQQ+AFRgA6YCQAASABgABAKUADgADgB5NwQIAe4AuTAUMAIACAHkAA4Ac9AUcAoADgFpMAO2Am4B+TAUAAIACAdOALkwFEACAAgB5A + AOAHkwFIAKAA4BaTkRHgH5MAQUBUgHTgC5MBRQAgAIAeQADgB5MBSQCgAOAWkzOLIADgAZNHWwMgkM8+QAPgJwtDwFA74AOVQEMFj8J1PxEAwAAieSJ6ACxADuAHKAOO + wnU+4AcoAC3gCyhAACBGIEdAACAIIAkALkAK4AdRQAAgHSAeQAAgCCAJAC9ACiDygOzAB0C8UJ7gG0EAMCA74BwoADEgJ+AcKAAyICfgHCiWM8C0wAdBZQUAAPBCAAHB + PgNrbmFiQNLAAGtBARsAgAADY3RuaVL6QCDQZAABjYwhIe4A/QJzaXXnBIIGZ3B0Y1lUTWJ5Cw1iZYUBAAAAAAAAAAEAAAAAAQAAAAAAAAA= + ADxkaXNhYmxlZD4AEAAAAA== + > + PRESETNAME + FLOATPOS 0 0 0 0 + FXID {0823D1DB-B84A-8448-A6B6-1740043BB95E} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-846-clap-support.RPP b/plugin-reaper-realearn/resources/test-projects/issue-846-clap-support.RPP new file mode 100644 index 0000000..7fcf9a2 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-846-clap-support.RPP @@ -0,0 +1,585 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAB2AQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiIzcHM2MFVKUCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJt + YWluUHJlc2V0QXV0b0xvYWRNb2RlIjoiZm9jdXNlZC1meCIsImluc3RhbmNlUHJlc2V0TGlua0NvbmZpZyI6eyJsaW5rcyI6W3siZngiOnsibmFtZSI6IkNMQVBpOiBT + dXJnZSBYVCAoU3VyZ2UgU3ludGggVGVhbSkifSwicHJlc2V0SWQiOiJzdXJnZSJ9XX0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 421 40 752 675 + FXID {7E4F6ABA-566E-2642-B097-901502D50FD3} + WAK 0 0 + > + > + + FLOATPOS 48 305 554 201 + FXID {E880178C-1ECF-6E4F-8D56-54C888602A96} + WAK 0 0 + > + > + + + > + FLOAT 619 54 913 625 + FXID {20EEAA68-DBD6-FA43-820F-EE8A5EA6B22A} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-849-last-touched-reaeq.RPP b/plugin-reaper-realearn/resources/test-projects/issue-849-last-touched-reaeq.RPP new file mode 100644 index 0000000..002fd8d --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-849-last-touched-reaeq.RPP @@ -0,0 +1,187 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 2 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 4 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAUBAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiI2RlVZUlRDRiIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiNCIsImZlZWRiYWNrRGV2aWNlSWQiOiI0IiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkpaNDJueG9qMVF1c2h5S1I5eEJlYiIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + MCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjIwLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiTGFzdExvYWRlZCJ9LCJpbmNsdWRlZFRhcmdldHMiOlsiQXV0b21hdGlvbk1vZGVPdmVycmlkZSIsIkZ4UGFyYW1ldGVyVmFsdWUiLCJSb3V0ZVZvbHVtZSIs + IlJlYXBlckFjdGlvbiIsIlJvdXRlUGFuIiwiRnhPbk9mZlN0YXRlIiwiVHJhY2tTZWxlY3Rpb25TdGF0ZSIsIlRyYWNrUGFuIiwiVHJhY2tWb2x1bWUiLCJUcmFja011 + dGVTdGF0ZSIsIlRlbXBvIiwiVHJhY2tBcm1TdGF0ZSIsIlRyYWNrTW9uaXRvcmluZ01vZGUiLCJQbGF5UmF0ZSIsIkJyb3dzZUZ4UHJlc2V0cyIsIlRyYWNrQXV0b21h + dGlvbk1vZGUiLCJUcmFuc3BvcnRBY3Rpb24iLCJUcmFja1NvbG9TdGF0ZSJdfX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 86 121 752 675 + FXID {E553E805-0492-204D-B343-3EDC032977FB} + WAK 0 0 + > + > + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAAAAAAAAAFlAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAK9NZks28aJAGIm8SUdu + yD+LbOf7qfH0PwEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAEACAABrAQAAAgAAAA== + AAAQAAAA + > + FLOAT 680 150 576 419 + FXID {C041CEC8-61FE-EF40-AA15-CBA601E77AF8} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-851-toggle-with-fade.RPP b/plugin-reaper-realearn/resources/test-projects/issue-851-toggle-with-fade.RPP new file mode 100644 index 0000000..63fb472 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-851-toggle-with-fade.RPP @@ -0,0 +1,149 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 1.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAQBQAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNS4wLXByZS4zIiwiaWQiOiJvdUVpQTktTyIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IkRZdkxfdFRPOEhrcXpaS3lOM1JmNCIsIm5hbWUiOiJBYnNvbHV0ZSBmYWRlIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNp + emUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjEsImVlbENvbnRyb2xUcmFuc2Zvcm1hdGlvbiI6Ii8vIFBhcmFtZXRlcnNcblxudHJhbnNp + dGlvbl90aW1lX2luX21zID0gNTAwO1xuXG4vLyBDb2RlXG5cbi8vIE9uIGZpcnN0IGludm9jYXRpb24gb25seSwgZGV0ZXJtaW5lIHN0YXJ0IHZhbHVlIGFuZCBkaXJl + Y3Rpb25cbm1vdmluZyA9PSAwID8gKFxuICBtb3ZpbmcgPSAxO1xuICB5IDwgMC41ID8gKFxuICAgIHN0YXJ0X3ZhbCA9IDA7XG4gICAgZGlyZWN0aW9uID0gMTtcbiAg + KSA6IChcbiAgICBzdGFydF92YWwgPSAxO1xuICAgIGRpcmVjdGlvbiA9IC0xO1xuICApO1xuKTtcblxuLy8gRGV0ZXJtaW5lIG5leHQgdmFsdWVcbm5leHRfdmFsID0g + c3RhcnRfdmFsICsgZGlyZWN0aW9uICogKHJlbF90aW1lIC8gdHJhbnNpdGlvbl90aW1lX2luX21zKTtcblxuLy8gU3RvcCBpZiBib3VuZGFyeSByZWFjaGVkXG5uZXh0 + X3ZhbCA8IDAgfHwgbmV4dF92YWwgPiAxID8gKFxuICBtb3ZpbmcgPSAwO1xuICB5ID0gc3RvcCghc3RhcnRfdmFsKTtcbikgOiAoXG4gIHkgPSBuZXh0X3ZhbDtcbik7 + IiwiYnV0dG9uVXNhZ2UiOiJwcmVzcy1vbmx5In0sInRhcmdldCI6eyJ0eXBlIjoyLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFz + dExvYWRlZCJ9fX1dLCJpbnN0YW5jZUZ4Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 565 72 752 675 + FXID {49C6C5C4-9919-A942-8080-CBA4B49F1239} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-857-track-removal-error.RPP b/plugin-reaper-realearn/resources/test-projects/issue-857-track-removal-error.RPP new file mode 100644 index 0000000..4faa7e0 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-857-track-removal-error.RPP @@ -0,0 +1,1185 @@ + + + RENDER_FILE "" + RENDER_PATTERN untitled + RENDER_FMT 0 2 44100 + RENDER_1X 0 + RENDER_RANGE 1 0 0 16 1000 + RENDER_RESAMPLE 10 10 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAD4cBAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xNi4wLXByZS4xIiwiaWQiOiJfTUd5UDRQYiIsImxldE1hdGNoZWRFdmVudHNUaHJvdWdoIjp0cnVlLCJzdGF5QWN0aXZlV2hlblByb2plY3RJ + bkJhY2tncm91bmQiOiJBbHdheXMiLCJjb250cm9sRGV2aWNlSWQiOiJjYzc3OGVkMy0wYTQ5LTQ4MzUtYjIzNy01ZjA3ZmZlMmFjNTMiLCJmZWVkYmFja0RldmljZUlk + IjoiY2M3NzhlZDMtMGE0OS00ODM1LWIyMzctNWYwN2ZmZTJhYzUzIiwiZGVmYXVsdEdyb3VwIjp7fSwiZ3JvdXBzIjpbeyJpZCI6IjItV2ZMc1RIUHE2YTlOb3AtMnlt + NiIsIm5hbWUiOiJUcmFuc3BvcnQifSx7ImlkIjoiLU12Z01BRXYxZkpFYmFiSzAxN2dTIiwibmFtZSI6Ik5hdmlnYXRpb24ifSx7ImlkIjoiWW53REs2cl9RaUQtaFJm + RFlmSUtrIiwibmFtZSI6IlBhZHMifSx7ImlkIjoiWnE3TU5BOU12RkNJQVBvWjVmdFNBIiwibmFtZSI6IkF1eGlsaWFyeSJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91 + cCI6e30sImNvbnRyb2xsZXJHcm91cHMiOlt7ImlkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwibmFtZSI6IkJ1dHRvbnMifSx7ImlkIjoiWHhyX0RReEptNlM1Y3Bf + Yy1oaWROIiwibmFtZSI6IlBhZHMifSx7ImlkIjoibXh2Q0FQdl9ac3REN1o3S1FmdkhIIiwibmFtZSI6IkVuY29kZXJzIn1dLCJtYXBwaW5ncyI6W3siaWQiOiJQRGta + ekV3YkhJeGZKcno5N1lnaUYiLCJuYW1lIjoiU2hvdyBjdXJyZW50IHRyYWNrIG5hbWUgYW5kIHBvc2l0aW9uIiwiZ3JvdXBJZCI6Ii1NdmdNQUV2MWZKRWJhYkswMTdn + UyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL0N1cnJlbnRUcmFja05hbWUiLCJvc2NBcmdJbmRleCI6MCwib3NjQXJnVHlw + ZSI6InN0cmluZyJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1LCJlZWxGZWVkYmFja1RyYW5zZm9y + bWF0aW9uIjoie3t0YXJnZXQudHJhY2suaW5kZXh9fToge3t0YXJnZXQudHJhY2submFtZX19IiwiZmVlZGJhY2tUeXBlIjoxfSwidGFyZ2V0Ijp7InR5cGUiOjQ0LCJ0 + cmFja0dVSUQiOiJzZWxlY3RlZCIsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsInRyYWNrRXhjbHVzaXZpdHkiOjEsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJwb2xsRm9yRmVl + ZGJhY2siOmZhbHNlLCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfV0sImNvbnRyb2xs + ZXJNYXBwaW5ncyI6W3siaWQiOiJNc0xHSGVkTEljYll1QkVKMG5LM1UiLCJuYW1lIjoiMSIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2Ui + OnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvUGxheU9yU3RvcEJ1dHRvbiIs + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlZqX0hpUlkxM0xRWXdwOURHREd5RyIsIm5hbWUiOiIyIiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhS + YmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9Q + YXVzZUJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJtQ3FvVl9mek5nZ1FRcDA0NGx6XyIs + Im5hbWUiOiIzIiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9QbGF5QnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0Fy + Z0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19fSx7ImlkIjoidVZwUmVhRXVSak1xbTV4NHp3U1JqIiwibmFtZSI6IjQiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNhdGVn + b3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL1JlcGVhdEJ1dHRvbiIsIm9zY0FyZ0luZGV4 + IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MywidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InhENlNKLW91Ujh2Z0FySmpWZ0czcCIsIm5hbWUiOiI1IiwiZ3JvdXBJZCI6InRa + Z0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNz + UGF0dGVybiI6Ii9SZWNvcmQiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3Ii + OjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJLQmg5OGZsMWZUMEcwQjg0 + WjRnaWgiLCJuYW1lIjoiNiIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvRGVsZXRlQnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiTjc5bW9YdjhFTmpCVV9RRXVVbm9LIiwibmFtZSI6IjciLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNl + Ijp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL0pDQnV0dG9uIiwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2LCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibTU5V2JlZERWQi1USW1DZHhENER0IiwibmFtZSI6IjgiLCJncm91cElk + IjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0Fk + ZHJlc3NQYXR0ZXJuIjoiL0pDQ2hpbGRCdXR0b24iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjcsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJqcTNP + ZFM4MTlmbGpQZnpHT1h4eHMiLCJuYW1lIjoiOSIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvSkNuTXV0ZUJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ijd5WEs0QTZ5VmUzWVBLQ09RcHZlSSIsIm5hbWUiOiIxMCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9j + LWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMCIs + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OSwidXNlUHJv + amVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMi + OiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlN6ZmZ1TFpGUjNrOXZzRi1TbzRYTSIsIm5hbWUiOiIxMSIs + Imdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJCeUdwTVo5 + S2NiczVTSm9hbWFmc2QiLCJuYW1lIjoiMTIiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzIiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAu + MDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjExLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiMm9MbVBMYnBDbTFFczM4SW1TeEhfIiwibmFtZSI6IjEzIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8zIiwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IjFDbVZ6TXFqQTU4SWE1SHBBcExxMiIsIm5hbWUiOiIxNCIsImdyb3VwSWQiOiJY + eHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVz + c1BhdHRlcm4iOiIvNCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MTMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJGb0VRSTU5N21pVTF5VkxQWEx6 + Zi0iLCJuYW1lIjoiMTUiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzUiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0lu + ZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + fSx7ImlkIjoiZUtCU3lYTlBmVTZZdGNncnVUYURFIiwibmFtZSI6IjE2IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii82Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7 + Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkFrM3BaTHFMazlVM3NTYUpzVllDeCIsIm5hbWUiOiIxNyIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVj + cF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIv + NyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTYsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJRNWZsaERXUjdKYWZxb0EzMzBWWWoiLCJuYW1lIjoi + MTgiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzgiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiUTBm + blBpVmNzX1lVUlUyb2ZJMVYyIiwibmFtZSI6IjE5IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii85Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImJ1b0lUcE42NDJXemJDV09GUEdUaCIsIm5hbWUiOiIyMCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTAiLCJvc2NBcmdJ + bmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2Us + InNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE5LCJ1c2VQcm9qZWN0Ijp0 + cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwi + dGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQVg0OXNNVXJxaUJKa01BRndicDhDIiwibmFtZSI6IjIxIiwiZ3JvdXBJ + ZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + ZGRyZXNzUGF0dGVybiI6Ii8xMSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3Rv + ciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6MjAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ5cWdCQjA2YTZiTUxi + ZWhmWFB1YlMiLCJuYW1lIjoiMjIiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEyIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJt + aW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6 + ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyMSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fX0seyJpZCI6InJTcThpbXNrRGdsYldfaWNtMUxlTCIsIm5hbWUiOiIyMyIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsi + Y2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTMiLCJvc2NBcmdJbmRleCI6MH0s + Im1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + aW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhh + dmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIyLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3Zl + VmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiUVEwSVlJYkx2bXpzRWRGZXNrWDR3IiwibmFtZSI6IjI0IiwiZ3JvdXBJZCI6Ilh4cl9E + UXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0 + dGVybiI6Ii8xNCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VU + cmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6MjMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJN + b3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJHWDg0ZFQ2ck56NXgxa3RyMXJ5eGci + LCJuYW1lIjoiMjUiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzE1Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFj + dG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNl + U2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyNCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRl + eCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0s + eyJpZCI6IjVZY3JBdC1sdEZjTURCWGpNUjdrSyIsIm5hbWUiOiIyNiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTYiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsi + bWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlv + blR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hv + dCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiaWZEQ0lNUjBlT0g3OEZCX1IyZmpOIiwibmFtZSI6IjI3IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNw + X2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8x + NyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3Vw + aW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjYsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI2Ri1sbjBSQ0doYVBaTVFyV002U1IiLCJuYW1lIjoi + MjgiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzE4Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9u + R2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24i + LCJjb250cm9sRWxlbWVudEluZGV4IjoyNywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Iko4 + UFhXV0FITXFPOUdCRW00X0E0aSIsIm5hbWUiOiIyOSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJp + c1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTkiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNp + emUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMs + ImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI4LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiZUhMT05mcHBGZEVWZ1dmbjNvMmxDIiwibmFtZSI6IjMwIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8yMCIsIm9zY0Fy + Z0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjksInVzZVByb2plY3Qi + OnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9 + LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ5NVdpemw0VC1HMXkwU2paSDdzblUiLCJuYW1lIjoiMzEiLCJncm91 + cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FkZHJlc3NQYXR0ZXJuIjoiLzIxIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFj + dG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6 + ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjozMCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24i + Onsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Inp2V01HdG9ObGty + NWV5eXFpRmJOTyIsIm5hbWUiOiIzMiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMjIiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUs + Im9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIs + ImlkIjoiIn19fSx7ImlkIjoiVzRhRXctaVZOM3FWYUd6VGRDcFc2IiwibmFtZSI6IjMzIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8yMyIsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0Jl + aGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MzIsInVzZVByb2plY3QiOnRydWUsIm1v + dmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJWc2hTMWF3N0JVbFBEeDNQblVjeVIiLCJuYW1lIjoiMzQiLCJncm91cElkIjoiWHhy + X0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQ + YXR0ZXJuIjoiLzI0Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjozMywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJSNGk1YkJ0Y2N5Q1ZUM3JjQXZH + RiIsIm5hbWUiOiIzNSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNl + LCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMjUiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBG + YWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1 + c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlw + ZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM0LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0lu + ZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19 + fSx7ImlkIjoib05VbE1KQS1WYlAycFFUVXRVOFdyIiwibmFtZSI6IjM2IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8yNiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MzUsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0 + cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBz + aG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI0dnNIUS1SM1N3OGtVejJZMUQ3N1oiLCJuYW1lIjoiMzciLCJncm91cElkIjoiWHhyX0RReEptNlM1 + Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoi + LzI3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjozNiwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ii1qaUF5LUs0MnI2cnJ1Y0lkalpLZiIsIm5hbWUi + OiIzOCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMjgiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEs + Im1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rp + b25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM3LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoi + d3FrWnpkNjNrekw0aHRFVGlDTW9TIiwibmFtZSI6IjM5IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8yOSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MzgsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJrblhFSmNoUXdrdGZwYjdhZmtEMUEiLCJuYW1lIjoiNDAiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWRO + Iiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzMwIiwib3Nj + QXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZh + bHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjozOSwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkFNR1hIUXJxcEtZaWRnS2pnRHZILSIsIm5hbWUiOiI0MSIsImdy + b3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQWRkcmVzc1BhdHRlcm4iOiIvMzEiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5n + IjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjQwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiaVZMSjBRR0hF + dk8xaURQR25FUnRsIiwibmFtZSI6IjQyIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8zMiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4w + NSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNo + b3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDEsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX19LHsiaWQiOiJMU2MwS3ZFbjdGUk9EckRDcTQ1MDUiLCJuYW1lIjoiNDMiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNl + Ijp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzMzIiwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVr + QmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0MiwidXNlUHJvamVjdCI6dHJ1ZSwi + bW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VN + YXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InBMdjhNcE9iSzdWbkZ0WDdzVkN0MSIsIm5hbWUiOiI0NCIsImdyb3VwSWQiOiJY + eHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVz + c1BhdHRlcm4iOiIvMzQiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwi + dXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjQzLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoidFc2QmRQV21fdHNuck9Dei14 + OFVlIiwibmFtZSI6IjQ1IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8zNSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3Rl + cEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIs + InVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJn + SW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIi + fX19LHsiaWQiOiJUUEtUVEFtY0loMm9CVU9vV0lYdGIiLCJuYW1lIjoiNDYiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVn + b3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzM2Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2Rl + Ijp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9j + YXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3Ii + OiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0NSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ik9TODc4REhLRGFCbV9lcm5WV3gyRCIsIm5hbWUiOiI0NyIsImdyb3VwSWQiOiJYeHJfRFF4Sm02 + UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4i + OiIvMzciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQ2 + LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiOFRTY2FhV2RLSTNqaU1mOC1qai1PIiwibmFt + ZSI6IjQ4IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8zOCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NDcsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAs + Im1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQi + OiIzTUVCMXpFc183QXFoQjV4b2FiOG0iLCJuYW1lIjoiNDkiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3Nj + IiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzM5Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBl + IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0OCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNl + ZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InRkd0JaSkF6VkZTeGhEWjRzQkRMaiIsIm5hbWUiOiI1MCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNDAiLCJv + c2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6 + ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQ5LCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiTk51WFF5SzZxemt3ZXhRSVl6akRMIiwibmFtZSI6IjUxIiwi + Z3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBZGRyZXNzUGF0dGVybiI6Ii80MSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6NTAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJzQWtlbEpr + YUxLZkVQYURPckVjeHQiLCJuYW1lIjoiNTIiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzQyIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjow + LjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFu + Y2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0 + cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5 + SWQiLCJpZCI6IiJ9fX0seyJpZCI6InplLVR0cTVoRW5HRElvYWQ2MmdkUSIsIm5hbWUiOiI1MyIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3Vy + Y2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNDMiLCJvc2NBcmdJbmRl + eCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjUyLCJ1c2VQcm9qZWN0Ijp0cnVl + LCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFr + ZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoieEl2dV8wQ1ZaMVJPdGs3UWZHNllJIiwibmFtZSI6IjU0IiwiZ3JvdXBJZCI6 + Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRy + ZXNzUGF0dGVybiI6Ii80NCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6NTMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7Imtp + bmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJnSm8zZzJsakhaZkNmR0pu + elNEVDMiLCJuYW1lIjoiNTUiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzQ1Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5T + dGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6Imlk + IiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1NCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fX0seyJpZCI6InRNT3Q0Ylp4ZU9KMGo5U2xmZ09uTyIsIm5hbWUiOiI1NiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNDYiLCJvc2NBcmdJbmRleCI6MH0sIm1v + ZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52 + b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlv + ciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjU1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmll + dyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdT + bmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibzV1cDdKMHE4UTNRS3U3OXNvNlpSIiwibmFtZSI6IjU3IiwiZ3JvdXBJZCI6Ilh4cl9EUXhK + bTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVy + biI6Ii80NyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + NTYsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJTWTh4dTRUNHQwd3B0bUU5Y1RuNEYiLCJu + YW1lIjoiNTgiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzQ4Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9y + IjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2Vs + ZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1NywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6 + MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJp + ZCI6ImpwUFVPWHJXZ2xBVzNPempXaFU5ZiIsIm5hbWUiOiI1OSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJv + c2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNDkiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjU4LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwi + c2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6 + eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiTG52aEIwa2RDVjNHY2V5RnhXcUN3IiwibmFtZSI6IjYwIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2Mt + aGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii81MCIs + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5n + IjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NTksInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiIxZ25DMmhsTXdMRy1fRkJBcUlBZWoiLCJuYW1lIjoiNjEi + LCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFs + c2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzUxIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhT + dGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4Ijo2MCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VB + Y3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImZTeHdh + RE15dUw4d0NwbkFkNGRpRyIsIm5hbWUiOiI2MiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNTIiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUi + OjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjYxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiX1Y3YmRETFBmNVUweG5NRGFPb0FTIiwibmFtZSI6IjYzIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii81MyIsIm9zY0FyZ0lu + ZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NjIsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJmWjZjVTlKSHpfVk5iekhRMm9xNXgiLCJuYW1lIjoiNjQiLCJncm91cElk + IjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0Fk + ZHJlc3NQYXR0ZXJuIjoiLzU0Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4Ijo2MywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJfSm55T093QzV5YVpB + WktuQ3FJUSIsIm5hbWUiOiI2NSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNTUiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjY0LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9z + Y0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlk + IjoiIn19fSx7ImlkIjoiZ3R6dDhXRWZUOGZuM1k2TUR5ODRBIiwibmFtZSI6IjY2IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJj + YXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii81NiIsIm9zY0FyZ0luZGV4IjowfSwi + bW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NjUsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGlu + Z1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJNNDlFWG1XaDdrZWRWdzhZcm9rbTUiLCJuYW1lIjoiNjciLCJncm91cElkIjoiWHhyX0RR + eEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0 + ZXJuIjoiLzU3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRy + YWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + Ijo2NiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1v + dmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ik5vNU9YeXFJOWJ4Y0ZVbWZkX0JmLSIs + Im5hbWUiOiI2OCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJp + czE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNTgiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0 + b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VT + ZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjY3LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7 + ImlkIjoibjQtNHhOajRuSktnc2RTb01pR1FLIiwibmFtZSI6IjY5IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii81OSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9u + VHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1t + ZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NjgsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVl + LCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90 + Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJsT1RNekdpcm1kVUsyUlpIX2VSSE0iLCJuYW1lIjoiNzAiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3Bf + Yy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzYw + Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2OSwidXNl + UHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4 + aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImhLcDdwcGxic1lwak00X0t5QmgxeiIsIm5hbWUiOiI3 + MSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNjEiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjcwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoid2h4 + d1RIQjY1U1pnRTBnLTd3QzQ0IiwibmFtZSI6IjcyIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii82MiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6 + ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NzEsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJyNC01dlZDWk5DbXJxOHhYcjhrcHciLCJuYW1lIjoiNzMiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwi + c291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzYzIiwib3NjQXJn + SW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNl + LCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3MiwidXNlUHJvamVjdCI6 + dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0s + InRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImtHS1dvYldVU1F4Ymt5SXMyNk9UQyIsIm5hbWUiOiI3NCIsImdyb3Vw + SWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QWRkcmVzc1BhdHRlcm4iOiIvNjQiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjczLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6 + eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiT0Q1UFZSUEE4N0ky + bmJZQS15V0wyIiwibmFtZSI6Ijc1IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii82NSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3Ii + OiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NzQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwi + b3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwi + aWQiOiIifX19LHsiaWQiOiIwR3RYU3djV1lHcEhoOUxWcldJWm0iLCJuYW1lIjoiNzYiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7 + ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzY2Iiwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Imludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVo + YXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3NSwidXNlUHJvamVjdCI6dHJ1ZSwibW92 + ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBw + aW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IjJKRUZTWERrQlhBMVhqM3JxbVRGUiIsIm5hbWUiOiI3NyIsImdyb3VwSWQiOiJYeHJf + RFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1Bh + dHRlcm4iOiIvNjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0 + YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNl + VHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjc2LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoi + TW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiVnZVN2NMTF9VX3JXcnY3VFRkUElx + IiwibmFtZSI6Ijc4IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii82OCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZh + Y3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVz + ZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NzcsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19 + LHsiaWQiOiJuT1lkNjNOOXpBdDY0aWQ5ci1iclkiLCJuYW1lIjoiNzkiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5 + Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzY5Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7 + Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRp + b25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJ + bW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3OCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InBsWm9sX2hiVU54WmRjUWRTQi15UCIsIm5hbWUiOiI4MCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVj + cF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIv + NzAiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjc5LCJ1 + c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwi + YXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiMzNRdFAtZ21zLWQySnMyV3J6a1hjIiwibmFtZSI6 + IjgxIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii83MSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ODAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1v + dXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI1 + dmpreWhUOE9pdzB5czN1TzBhZUUiLCJuYW1lIjoiODIiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzcyIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBT + aXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjoz + LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2lu + ZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImhRb0dPLU5MV2Z2LWFxZmttZ1l5TSIsIm5hbWUiOiI4MyIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4i + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNzMiLCJvc2NB + cmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjgyLCJ1c2VQcm9qZWN0 + Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6Ilki + fSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicFNGN3ZsRE5lb1U4OTdTUHJCRnc5IiwibmFtZSI6Ijg0IiwiZ3Jv + dXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBZGRyZXNzUGF0dGVybiI6Ii83NCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6ODMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9u + Ijp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJUZnRNclp0ZkJU + S2cxNmZYb3JCXzYiLCJuYW1lIjoiODUiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzc1Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4NCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVl + LCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQi + LCJpZCI6IiJ9fX0seyJpZCI6ImF6Sjg4SDVMdVlVU0lyQjR3ZjU0NiIsIm5hbWUiOiI4NiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2Ui + OnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNzYiLCJvc2NBcmdJbmRleCI6 + MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtC + ZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjg1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJt + b3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1h + cHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiUm1hSGdMdkFNRXhfRUhXYVhGdG1lIiwibmFtZSI6Ijg3IiwiZ3JvdXBJZCI6Ilh4 + cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNz + UGF0dGVybiI6Ii83NyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6ODYsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJCZ20teTJFRDN2QnI0VkZrNzJI + S0YiLCJuYW1lIjoiODgiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzc4Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwi + dXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4NywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9 + fX0seyJpZCI6IkFPeS1WT3V1dk1nenhGbDZxTG9qbSIsIm5hbWUiOiI4OSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvNzkiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjg4LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoibjFYZ3RSQ0RrTnNva0VZVndKWkdEIiwibmFtZSI6IjkwIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZT + NWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6 + Ii84MCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dy + b3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ODks + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJMNERlbGFEMHd2S2JERXlkY0RWeXMiLCJuYW1l + IjoiOTEiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzgxIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjox + LCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5MCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwi + bW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6 + ImlVU2xSZVVTanNzVEhMNExBVmFhNyIsIm5hbWUiOiI5MiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2Mi + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvODIiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjkxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoib2VWZG5BQ3pKZ0M5Uk5ZMWJ5MnVmIiwibmFtZSI6IjkzIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlk + TiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii84MyIsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OTIsInVzZVByb2pl + Y3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoi + WSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiIwaENrRjAxeDh2cnFlQzJSYnVqbXIiLCJuYW1lIjoiOTQiLCJn + cm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FkZHJlc3NQYXR0ZXJuIjoiLzg0Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVw + RmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2lu + ZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo5MywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rp + b24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImZ2Y0F6Q1BR + TFBFZ1BPZWtrYmFQYyIsIm5hbWUiOiI5NSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvODUiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAu + MDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5j + aG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjk0LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJ + ZCIsImlkIjoiIn19fSx7ImlkIjoiU0FTRmJwTlZiaTRVai1GX3BRMGJlIiwibmFtZSI6Ijk2IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii84NiIsIm9zY0FyZ0luZGV4 + IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OTUsInVzZVByb2plY3QiOnRydWUs + Im1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtl + TWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ6bzBENVZncGRaUXQtaV81ck9EWHAiLCJuYW1lIjoiOTciLCJncm91cElkIjoi + WHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJl + c3NQYXR0ZXJuIjoiLzg3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2Us + InVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo5NiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2lu + ZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImkxaUstNE9Jem94M1p4S1RU + MEVJSiIsIm5hbWUiOiI5OCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvODgiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjk3LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0Fy + Z0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoi + In19fSx7ImlkIjoiM3ROUjR5TnZYcVFpa0dQc2JraktZIiwibmFtZSI6Ijk5IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRl + Z29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii84OSIsIm9zY0FyZ0luZGV4IjowfSwibW9k + ZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZv + Y2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9y + IjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OTgsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3 + Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1Nu + YXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJFZmVUdGY1c1ZabGxFd0tCNXBuU1QiLCJuYW1lIjoiMTAwIiwiZ3JvdXBJZCI6Ilh4cl9EUXhK + bTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVy + biI6Ii85MCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFj + a0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + OTksInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiIwLS03dXhoWndtMi1VY0FLaDdlWFYiLCJu + YW1lIjoiMTAxIiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii85MSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNl + bGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTAwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4 + IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7 + ImlkIjoiOWVBaFhDR3JsTnZuWkZsLTdEUTQwIiwibmFtZSI6IjEwMiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvOTIiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsi + bWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlv + blR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwMSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlQ5NGlrankxWEVlWENfUmRTSUl6NiIsIm5hbWUiOiIxMDMiLCJncm91cElkIjoiWHhyX0RReEptNlM1 + Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoi + LzkzIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3Jv + dXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMDIs + InVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8i + LCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiIzUDBveUJPbkFjM2FESkxQNWhnRWIiLCJuYW1l + IjoiMTA0IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii85NCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTAzLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4Ijow + LCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7Imlk + IjoiZml1UHdGY05MUVdXU3VTY0NqUnNlIiwibmFtZSI6IjEwNSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJv + c2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvOTUiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwNCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InhGcjVnSjhjTUJMZlN0OXBIMVhsNyIsIm5hbWUiOiIxMDYiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3Bf + Yy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzk2 + Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMDUsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI5SDYzNG96alh0RGs0V2pueElBaDgiLCJuYW1lIjoi + MTA3IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii85NyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwi + bWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlv + bkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTA2LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoi + RnJ5a0ZNSnZDM2ZqUkxtWTVkUjhjIiwibmFtZSI6IjEwOCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhpZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2Mi + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvOTgiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3Rl + cFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUi + OjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0 + ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwNywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNl + ZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsi + a2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IlZhamNUQ0VLQ2hGRG1KaHoyWl9pSyIsIm5hbWUiOiIxMDkiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1o + aWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzk5Iiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMDgsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJmUHNQQ3MxZ2ljaTQ0QzBRSFFXT00iLCJuYW1lIjoiMTEw + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMDAiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwOSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkxI + eVY4YlFJbXJTcURJM0k4aWhCUyIsIm5hbWUiOiIxMTEiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEwMSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTEwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiRlBvemdhS3NZVVJDV2xKNmMxSFktIiwibmFtZSI6IjExMiIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTAyIiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMTEsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJWaWd0NEpLMjlvRFNSazhrR2ZWNi0iLCJuYW1lIjoiMTEz + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMDMiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjExMiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkIz + Q2F1MU95TnNFZU1lZDl3MHcwaiIsIm5hbWUiOiIxMTQiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEwNCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTEzLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiREM0V0w5V0tnRktXNGc0Uzh1eXBkIiwibmFtZSI6IjExNSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTA1Iiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMTQsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI0S0dPck9BWHdibmNiVXEzSDNCZzUiLCJuYW1lIjoiMTE2 + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMDYiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjExNSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Ijc5 + YVpkOWhGTFhtZXE5bGRnUEU5eSIsIm5hbWUiOiIxMTciLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEwNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTE2LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQzNfX3A3U0JFOFJFRF9WZW40Wlh6IiwibmFtZSI6IjExOCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTA4Iiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMTcsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJFS25oaE5UTk56Njl3N1d4UnRYdWYiLCJuYW1lIjoiMTE5 + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMDkiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjExOCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Illy + UVFTZ1ZiUHBWSFhpOElfbGJZSyIsIm5hbWUiOiIxMjAiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzExMCIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTE5LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiNURRX2hEQk41UGI2NzNCRFdDTFQ0IiwibmFtZSI6IjEyMSIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTExIiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMjAsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJReE5YdWxMQmpJRTZIa3I0VGVhSDQiLCJuYW1lIjoiMTIy + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMTIiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyMSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IjFL + R3FMaUg2YVpGeWFvRUFvTEpVVSIsIm5hbWUiOiIxMjMiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzExMyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTIyLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiVG5hRG1WM2xnaGpyRFlwSDlSZ0cxIiwibmFtZSI6IjEyNCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTE0Iiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMjMsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiI1RXA4OEtUdTl0a1pWZk9Ib1hXVXEiLCJuYW1lIjoiMTI1 + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMTUiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyNCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkpN + ZmZIaHBjZ0FSeU9JdVBpQVBybiIsIm5hbWUiOiIxMjYiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzExNiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTI1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiMzNKUmlfR0xnUWhLYUZiWGhfQ3FXIiwibmFtZSI6IjEyNyIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTE3Iiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMjYsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ6V2FlYkJpNm43dGtqN016OThDNU4iLCJuYW1lIjoiMTI4 + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMTgiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyNywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6Inoy + bVFWX2VvUmdYVDFkMjNrV3R2USIsIm5hbWUiOiIxMjkiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzExOSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTI4LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiTFkyT2N5Y0h5ZTdNbnZnSl9LejhFIiwibmFtZSI6IjEzMCIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTIwIiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMjksInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJBaTNabHN5TUsxOUdxTWtmbU10VnEiLCJuYW1lIjoiMTMx + IiwiZ3JvdXBJZCI6Ilh4cl9EUXhKbTZTNWNwX2MtaGlkTiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii8xMjEiLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25H + YW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzMCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6ImYy + OFZBNnViZDdRTW1JYk1QN3pmLSIsIm5hbWUiOiIxMzIiLCJncm91cElkIjoiWHhyX0RReEptNlM1Y3BfYy1oaWROIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiLzEyMiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6 + MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRl + IiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTMxLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vl + a1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJr + aW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiODF4bjZkMzZlaU95NW4zOE5tTGhKIiwibmFtZSI6IjEzMyIsImdyb3VwSWQiOiJYeHJfRFF4Sm02UzVjcF9jLWhp + ZE4iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvMTIzIiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmci + OmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMzIsInVzZVBy + b2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlz + IjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJxWXllOEwyRW91NDl1TXEyMEFCdGciLCJuYW1lIjoiMTM0 + IiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9Nb3ZlQ3VyTGVmdEdyaWRCdXR0b24iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoi + aWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzMywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJv + c2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJp + ZCI6IiJ9fX0seyJpZCI6IkVSMXVfS05iX3F2Z3Ytd1E1bE81MSIsIm5hbWUiOiIxMzUiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7 + ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL01vdmVDdXJSaWdodEdyaWRCdXR0 + b24iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91 + cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzNCwi + dXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIs + ImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InJaRWZuSXlWZ0ZVQmtINV9zX0JGZiIsIm5hbWUi + OiIxMzYiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL01vdmVDdXJMZWZ0TWVhc3VyZUJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6 + MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhB + bmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTM1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXki + OnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoi + QnlJZCIsImlkIjoiIn19fSx7ImlkIjoiRWR2MFRWQUhXR2VMNnBRZkNiRTRCIiwibmFtZSI6IjEzNyIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJz + b3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvTW92ZUN1clJpZ2h0 + TWVhc3VyZUJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1 + c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MTM2LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5k + IjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiQlZtUlpqUjRtb3Q3c1lLZXZI + RWUtIiwibmFtZSI6IjEzOCIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZh + bHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvTW92ZUN1ckxlZnRNYXJrZXJCdXR0b24iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5 + cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVk + aWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzNywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUs + InNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3Qi + Onsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6InIwd1MwdDlfOEFjSHZxRTdUSFYtSyIsIm5hbWUiOiIxMzkiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJi + a084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL01v + dmVDdXJSaWdodE1hcmtlckJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTM4LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicmtSYnVDVzUz + WmtPbURmVGZJcXpBIiwibmFtZSI6IjE0MCIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvUHJlUm9sbFRvZ2dsZUJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6 + eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0 + aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoi + SW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTM5LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6 + dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFw + c2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoicUp0bnlhdGdPX2NMTmpmMFRlZ19BIiwibmFtZSI6IjE0MSIsImdyb3VwSWQiOiJ0WmdKNXdjVWRt + bDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4i + OiIvUGxheVJhdGVSZXNldEJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpbnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmci + OmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTQwLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlv + biI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiY19ZeGRhbzlm + MjR6UXpNZlFaRG1CIiwibmFtZSI6IjE0MiIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvUGxheVJhdGUrMTBCdXR0b24iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsi + bWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlv + blR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6Iklt + bWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0MSwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRy + dWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNo + b3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkEtUkppYVdpM0pYTTJGcTFJbmRBNiIsIm5hbWUiOiIxNDMiLCJncm91cElkIjoidFpnSjV3Y1VkbWww + OFJia084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoi + L1BsYXlSYXRlLTEwQnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9y + Ijo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFs + c2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoxNDIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7 + ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJ4al9rdWxVY0k2X1F6 + ZjVwS2dLWDAiLCJuYW1lIjoiMTQ0IiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9QbGF5UmF0ZSs1QnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0 + ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBl + IjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlh + dGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNDMsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJz + ZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJwWmltbW12NFhseHIwcXlKa0JrMzkiLCJuYW1lIjoiMTQ1IiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtP + OGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9QbGF5 + UmF0ZS01QnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxNDQsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJsZ2k4MmRwYnduYXZEODVudzRE + c3AiLCJuYW1lIjoiMTQ2IiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9NZXRyb25vbWVCdXR0b24iLCJvc2NBcmdJbmRleCI6MCwib3NjQXJnVHlwZSI6ImJvb2wifSwi + bW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJp + bnZvY2F0aW9uVHlwZSI6MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2 + aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTQ1LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3Zl + VmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBp + bmdTbmFwc2hvdCI6eyJraW5kIjoiQnlJZCIsImlkIjoiIn19fSx7ImlkIjoiNzJVVy1KcGNEb3JZZGhwR2dwSFFLIiwibmFtZSI6IjE0NyIsImdyb3VwSWQiOiJ0WmdK + NXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQWRkcmVzc1Bh + dHRlcm4iOiIvU2V0VGltZVNlbGVjdGlvblRvUHJldk1lYXN1cmVCdXR0b24iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0 + ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE0NiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fX0seyJpZCI6IkxWVkM0cm1HUGNzSWdjNW9NMW1GSSIsIm5hbWUiOiIxNDgiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNh + dGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL1NldFRpbWVTZWxlY3Rpb25Ub05leHRN + ZWFzdXJlQnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxNDcsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQi + OiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJabmJDWjBYcEZlWWV5NG5ZaFpu + cW8iLCJuYW1lIjoiMTQ5IiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9QbGF5UmF0ZSsxQnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJm + eEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNDgsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJKUGtrV1RhM0Jyd2VOZ1FDVmpNUWIiLCJuYW1lIjoiMTUwIiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9QbGF5UmF0ZS0x + QnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNr + R3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijox + NDksInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3Zl + VG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJOWUtQYk9PU2I2MXVRdlFMVGpNa3kiLCJu + YW1lIjoiMTUxIiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9QbGF5UmF0ZVgyQnV0dG9uIiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1 + LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNTAsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1 + ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlk + IiwiaWQiOiIifX19LHsiaWQiOiJtckJBeVRJMklkZHk5NjNmV2dFZUoiLCJuYW1lIjoiMTUyIiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9QbGF5UmF0ZS8yQnV0dG9u + Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImludm9jYXRpb25UeXBlIjozLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNTEsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJCeUlkIiwiaWQiOiIifX19LHsiaWQiOiJVYk12WTh0QTVkYTNnUlpCbV9BZEUiLCJuYW1lIjoi + MTUzIiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9TZXRUaW1lU2VsZWN0aW9uU3RhcnRQb2ludEJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVw + U2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjo1MywiaW52b2Nh + dGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1MiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6IiJ9fX0seyJpZCI6IkF3SmZleXFBWGNIZ0Q1NmZLUi1ySCIsIm5hbWUiOiIxNTQiLCJncm91cElkIjoidFpnSjV3Y1Vk + bWwwOFJia084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJu + IjoiL1NldFRpbWVTZWxlY3Rpb25FbmRQb2ludEJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjo1MywiaW52b2NhdGlvblR5cGUiOjMsImZ4QW5jaG9yIjoiaWQi + LCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1MywidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NB + cmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6IkJ5SWQiLCJpZCI6 + IiJ9fX0seyJpZCI6ImM2MjlfZ3JWLTNDNWlRVUh4cnJVbyIsIm5hbWUiOiIxNTUiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNh + dGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL1JlY29yZG4iLCJvc2NBcmdJbmRleCI6 + MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6 + IkltbWVkaWF0ZSIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1NCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXci + OnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25h + cHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJhaUVNbVZ6YXJ3ODdNcVBmOUdNTEkiLCJuYW1lIjoiMTU2IiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1s + MDhSYmtPOGlRSSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6 + Ii9SZWNvcmR0Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjUzLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBp + bmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNTUsInVz + ZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJh + eGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiZkpCR2xMcG0weHlZd2dZSGgyME40IiwibmFtZSI6IjE1 + NyIsImdyb3VwSWQiOiJ0WmdKNXdjVWRtbDA4UmJrTzhpUUkiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJvc2MiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0Ijpm + YWxzZSwib3NjQWRkcmVzc1BhdHRlcm4iOiIvUmVjb3JkaSIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6 + MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdp + bmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTU2LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFj + dGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlZHc3RUbnRt + N2JaV09oT0t0X1BmWCIsIm5hbWUiOiIxNTgiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL1JldHJvTUlESWJ1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJt + YXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjo1Mywi + ZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTU3LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1Bs + YXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5k + IjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlFUWUVQRTdGV1JBMG5pU2MxRVJQdSIsIm5hbWUiOiIxNTkiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwi + c291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL1ByZXZUcmFja0J1 + dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTU4LCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6 + IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Il9idHd6NGotT3NuRlBXM2tobFRXLSIsIm5hbWUiOiIxNjAiLCJn + cm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FkZHJlc3NQYXR0ZXJuIjoiL05leHRUcmFja0J1dHRvbiIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3Rv + ciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJ0eXBlIjo1MywiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdh + bmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTU5LCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IldDR3Za + UWU5TTljdkxNRjlheHo1OCIsIm5hbWUiOiIxNjEiLCJncm91cElkIjoidFpnSjV3Y1VkbWwwOFJia084aVFJIiwic291cmNlIjp7ImNhdGVnb3J5Ijoib3NjIiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FkZHJlc3NQYXR0ZXJuIjoiL1NvbG9CdXR0b24iLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwidHlwZSI6NTMsImZ4 + QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2MCwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6 + Ikxhc3RMb2FkZWQifX19LHsiaWQiOiI0Q2gyUzdrcUxLMHp4QmNUbXA2OHkiLCJuYW1lIjoiMTYyIiwiZ3JvdXBJZCI6InRaZ0o1d2NVZG1sMDhSYmtPOGlRSSIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6Im9zYyIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBZGRyZXNzUGF0dGVybiI6Ii9NdXRlQnV0dG9uIiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsInR5cGUiOjUzLCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNjEsInVzZVByb2plY3QiOnRy + dWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0 + YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6InRvdWNob3NjLW1rMi1yZWFwZXJjb250cm9sLW9z + YyIsImFjdGl2ZU1haW5QcmVzZXRJZCI6InJlYXBlcmNvbnRyb2wtb3NjIiwiaW5zdGFuY2VGeCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fQ== + AAAQAAAA + > + FLOAT 453 298 752 675 + FXID {AE8A7B06-D9E8-47B4-97D5-A02F8F481EE6} + WAK 0 0 + > + > + + + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/issue-97-greater-than-100-increments.rpp b/plugin-reaper-realearn/resources/test-projects/issue-97-greater-than-100-increments.rpp new file mode 100644 index 0000000..ff34a09 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/issue-97-greater-than-100-increments.rpp @@ -0,0 +1,172 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAYAEAAAEAAAAAABAA + eyJpZCI6ImxSdDZ6VG5GIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsIm1hcHBpbmdzIjpbeyJpZCI6IjIzYjIyZmI4LTRiMDktNDMwNy04MzM4LTdjZWVk + OGFhYzkwNSIsIm5hbWUiOiIxIiwic291cmNlIjp7Im51bWJlciI6MTIsImNoYXJhY3RlciI6M30sIm1vZGUiOnsibWluU3RlcFNpemUiOi0xMC4wLCJtYXhTdGVwU2l6 + ZSI6LTEwLjB9LCJ0YXJnZXQiOnsidHlwZSI6MTMsInRyYWNrR1VJRCI6IjNFM0ZCODlDLUE5RTItNEZFMS05MDczLTA4MkRCMjI1NEIzNyIsImZ4QW5jaG9yIjoiaWQi + LCJmeEluZGV4IjowLCJmeEdVSUQiOiJFQ0Q3NDg5NC1BQzQyLTQwRkUtOUQxRi1CRjA4RERDOEE2N0QifX1dfQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {164E6E06-6803-446C-9134-0BC144C9E5EC} + WAK 0 0 + > + > + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFQAAAAAAAAAAAAAAA== + 776t3g3wrd77dPc9eWgVO/mPVD3X+SA8tmeaPSdEGT7NLxs8AAAAAAAAAAC9N4Y1AACAP703hjXxQzY/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAA + AHN0b2NrIC0gc25hcmUgZ2F0ZQAAAAAA + > + PRESETNAME "stock - snare gate" + FLOATPOS 0 0 0 0 + FXID {ECD74894-AC42-40FE-9D1F-BF08DDC8A67D} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/load-fx-snapshot.rpp b/plugin-reaper-realearn/resources/test-projects/load-fx-snapshot.rpp new file mode 100644 index 0000000..6325dca --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/load-fx-snapshot.rpp @@ -0,0 +1,344 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAPkEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi43LjEiLCJpZCI6Ii1lUWl1cmttIiwiY29udHJvbERldmljZUlkIjoiMyIsImZlZWRiYWNrRGV2aWNlSWQiOiI0IiwiZGVmYXVsdEdyb3VwIjp7 + fSwibWFwcGluZ3MiOlt7ImlkIjoiNGIwZTExZjMtM2RjMC00ZmFmLWI2YTMtM2Y0ZWYyNjk5ZTdmIiwibmFtZSI6IlBhZCBzb3VuZCIsInNvdXJjZSI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjE5LCJ0cmFja0dVSUQiOiIyRjU5QzQ5OC1FMjgxLTRBMjAtODJDNC0xQkI2MEQ2OTgxNzAiLCJmeEFuY2hvciI6Imlk + IiwiZnhJbmRleCI6MCwiZnhHVUlEIjoiMEU3ODczNzgtRUY2MS00NzAwLUI3MjktMzYwREE3M0U3MTdFIiwiZnhTbmFwc2hvdCI6eyJmeFR5cGUiOiJWU1RpIiwiZnhO + YW1lIjoiUmVhU3ludGggKENvY2tvcykiLCJjaHVuayI6IjxWU1QgXCJWU1RpOiBSZWFTeW50aCAoQ29ja29zKVwiIHJlYXN5bnRoLmRsbCAwIFwiXCIgMTkxOTI1MTMy + MTw1NjUzNTQ3MjY1NzM3OTcyNjU2MTczNzk2RTc0NjgwMD4gXCJcIlxuZVhObGN1OWU3ZjRBQUFBQUFnQUFBQUVBQUFBQUFBQUFBZ0FBQUFBQUFBQkFBQUFBQUFBQUFB + QUFFQUE9XG43NzZ0M2czd3JkNEFBQ0JCRjdmUk9nQUFBQUFBQUFBQUFBQUFBTTVOQUQvcFo0ZzlBQUFBQUFBQUFEOEFBSUEvQUFDQVB3QUFBRDhBQUFBQUFBQUFBQT09 + XG5BQUFRQUFBQVxuPiJ9fX0seyJpZCI6IjYzYTIyNGEyLTljMjUtNDhhZi1iNDNmLWY4ZTQzZmM1NmEzNCIsIm5hbWUiOiJDb21wdXRlciBnYW1lIHNvdW5kIiwic291 + cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MTksInRyYWNrR1VJRCI6IjJGNTlDNDk4LUUyODEt + NEEyMC04MkM0LTFCQjYwRDY5ODE3MCIsImZ4QW5jaG9yIjoiaWQiLCJmeEluZGV4IjowLCJmeEdVSUQiOiIwRTc4NzM3OC1FRjYxLTQ3MDAtQjcyOS0zNjBEQTczRTcx + N0UiLCJmeFNuYXBzaG90Ijp7ImZ4VHlwZSI6IlZTVGkiLCJmeE5hbWUiOiJSZWFTeW50aCAoQ29ja29zKSIsImNodW5rIjoiPFZTVCBcIlZTVGk6IFJlYVN5bnRoIChD + b2Nrb3MpXCIgcmVhc3ludGguZGxsIDAgXCJcIiAxOTE5MjUxMzIxPDU2NTM1NDcyNjU3Mzc5NzI2NTYxNzM3OTZFNzQ2ODAwPiBcIlwiXG5lWE5sY3U5ZTdmNEFBQUFB + QWdBQUFBRUFBQUFBQUFBQUFnQUFBQUFBQUFCQUFBQUFBQUFBQUFBQUVBQT1cbjc3NnQzZzN3cmQ0blVydzdGN2ZST2xUakpUOEFBQUFBQUFBQUFNNU5BRC9wWjRnOUFB + QUFBQUFBQUQ4QUFJQS9BQUNBUDU3dkJ6OEFBQUFBQUFBQUFBPT1cbkFBQVFBQUFBXG4+In19fSx7ImlkIjoiMzBiYzhiMjgtMmVjMy00YmU0LTkzNjMtY2Q1NGJlMTkx + NWIzIiwibmFtZSI6IlBsdWNrIHNvdW5kIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6 + MTksInRyYWNrR1VJRCI6IjJGNTlDNDk4LUUyODEtNEEyMC04MkM0LTFCQjYwRDY5ODE3MCIsImZ4QW5jaG9yIjoiaWQiLCJmeEluZGV4IjowLCJmeEdVSUQiOiIwRTc4 + NzM3OC1FRjYxLTQ3MDAtQjcyOS0zNjBEQTczRTcxN0UiLCJmeFNuYXBzaG90Ijp7ImZ4VHlwZSI6IlZTVGkiLCJmeE5hbWUiOiJSZWFTeW50aCAoQ29ja29zKSIsImNo + dW5rIjoiPFZTVCBcIlZTVGk6IFJlYVN5bnRoIChDb2Nrb3MpXCIgcmVhc3ludGguZGxsIDAgXCJcIiAxOTE5MjUxMzIxPDU2NTM1NDcyNjU3Mzc5NzI2NTYxNzM3OTZF + NzQ2ODAwPiBcIlwiXG5lWE5sY3U5ZTdmNEFBQUFBQWdBQUFBRUFBQUFBQUFBQUFnQUFBQUFBQUFCQUFBQUFBQUFBQUFBQUVBQT1cbjc3NnQzZzN3cmQ3aUlWNDdGN2ZS + T2dBQUFBQzhkTk0rQUFBQUFNNU5BRCtTKzRvN0FBQUFBQUFBQUQ4OWpZazhBQUNBUHdBQUFEOEFBQUFBQUFBQUFBPT1cbkFBQVFBQUFBXG4+In19fV0sImNvbnRyb2xs + ZXJNYXBwaW5ncyI6W3siaWQiOiIxZmM1YmU0Zi0xOTEyLTRkMTgtOWZlZi0xY2M3ZDdlNjdjYTYiLCJuYW1lIjoiQ0IgMS8xIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MzJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyMDM3ZDUxMS0yZThmLTQzZTgtOTYxYi1kZGY0ZDZjODIzZGEiLCJuYW1lIjoiQ0IgMS8yIiwic291cmNlIjp7InR5cGUiOjEs + ImNoYW5uZWwiOjAsIm51bWJlciI6MzN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19LHsiaWQiOiJhYjMzMmM3MS02ZDMyLTQwYzItOWNhNy1jYjAxODAxMmZlZjciLCJuYW1lIjoi + Q0IgMS8zIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiI4MzdmM2E2ZS1hNTgwLTQxY2It + OGZlYS05YjU5ODhjZWZhN2EiLCJuYW1lIjoiQ0IgMS80Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MzV9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319 + LHsiaWQiOiJjYmM1OGI0OC0wMDY4LTRmNDQtYTljMS0wZjI1NjdhYmZlNDkiLCJuYW1lIjoiQ0IgMS81Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51 + bWJlciI6MzZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiJhYWIyZTYxNC1lZThiLTQ2ZmItYTNhNC00NzQ5OGU5YWFhNDYiLCJuYW1lIjoiQ0IgMS82Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzd9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIzMzEzMGRkYy0yNWQ2LTQ1ZTItYmQ4Yy0zYzBkMDY2Mzcy + MTkiLCJuYW1lIjoiQ0IgMS83Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiI4NWU0OGU1 + NC04NDhhLTQxOTEtYWE0Yi0wNGYyODNkZTIzM2MiLCJuYW1lIjoiQ0IgMS84Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mzl9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6N319LHsiaWQiOiJmMTc0Zjk5Zi01Yzk0LTRhYmEtYTNjMy0wMDY3NjlkZDMzYTEiLCJuYW1lIjoiQ0IgMi8xIiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjAsIm51bWJlciI6MjR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiJiYjhlYzRmYi1hMTQwLTQ1YzMtYTI1Mi1jNGE5OTIxMjg2Y2IiLCJuYW1lIjoiQ0Ig + Mi8yIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjV9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIzNWIyYzYyOS1lYjM3LTQwMDEtOWZj + MC0xOGE4YjY1ZmEzNGEiLCJuYW1lIjoiQ0IgMi8zIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7 + ImlkIjoiN2Y5N2I4ZWEtMDVhYy00ODljLTkxODQtZjBmNjhiYTU4NDBlIiwibmFtZSI6IkNCIDIvNCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjI3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjExfX0seyJpZCI6Ijk1NmY5YmFmLWRjMjktNDI1Ny05ODlmLTcwN2ZlZDI3NmExYyIsIm5hbWUiOiJDQiAyLzUiLCJzb3VyY2Ui + OnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyOH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiIwZWEzMzIzNS02OWM0LTRjYTgtYWVlMS05MDM0ZDhkZWQ4 + OWIiLCJuYW1lIjoiQ0IgMi82Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6Mjl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiMTI4MjMw + NzAtZDM3Ni00NmEzLWI4MzEtMWU4MDUxNzA1ODMwIiwibmFtZSI6IkNCIDIvNyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjMwfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjE0fX0seyJpZCI6Ijg5NWIwZGVkLTAwZjQtNDBhMi05MzJlLWZhODVlYTI1M2FjNyIsIm5hbWUiOiJDQiAyLzgiLCJzb3VyY2UiOnsidHlwZSI6MSwi + Y2hhbm5lbCI6MCwibnVtYmVyIjozMX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiI1NGM1ZmE3Zi05OTNlLTRlYjUtYTljNS03Y2M3N2U4MTc2OGIiLCJuYW1lIjoi + Q0IgMy8xIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTZ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNv + bG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiMWI4YTZlZjUtMDA3Zi00Yjgw + LTgwMjAtZjRkYjQ0ZDMzYzhmIiwibmFtZSI6IkNCIDMvMiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE3fSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3 + fX0seyJpZCI6IjFjNjgwODNjLTg5N2YtNDMwNy1iYjMxLTQ2M2VhOTgyNzg0MyIsIm5hbWUiOiJDQiAzLzMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwi + bnVtYmVyIjoxOH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxOH19LHsiaWQiOiI4ZTc2YThlMy02NDEwLTRkZGUtYTMzNC01ZTQ2OWExZjAyMzUiLCJuYW1lIjoiQ0IgMy80Iiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTl9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fSx7ImlkIjoiN2NjZTc5NTItYzY0OC00MTk5LTk4Y2EtM2I0NTgw + NTdmOTc1IiwibmFtZSI6IkNCIDMvNSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIwfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIwfX0seyJpZCI6Ijhk + YmU1MTI1LTFjOGEtNDlmNi05NzU4LTA1ZGJlZmJiZWU4MSIsIm5hbWUiOiJDQiAzLzYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyMX0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4IjoyMX19LHsiaWQiOiI3ZjIyNGY3NS1jNjc1LTQzOTUtODhmNS01ODdiNDJlYjdlYzIiLCJuYW1lIjoiQ0IgMy83Iiwic291cmNlIjp7InR5cGUi + OjEsImNoYW5uZWwiOjAsIm51bWJlciI6MjJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjJ9fSx7ImlkIjoiZDRkYzgzNmItZWI3MC00ZGEzLTk0ODctYmIyNDcyZmMxYzVlIiwibmFt + ZSI6IkNCIDMvOCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjIzfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjIzfX0seyJpZCI6IjQxMjczY2MwLTViMzgt + NGZlMi1iZWUzLTM5Yzg1ZDg5NmVlOCIsIm5hbWUiOiJDQiA0LzEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo4fSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjI0fX0seyJpZCI6ImRlMTA0MjdlLWVlNmItNDNhZS05NGRkLWYzZWFkZjBjNjE0MSIsIm5hbWUiOiJDQiA0LzIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo5fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI1fX0seyJpZCI6Ijc0ZDc4OWFkLTJhMTUtNDU0MC1iMDhmLWViM2QxNDA2MzFmMCIsIm5hbWUiOiJDQiA0LzMiLCJz + b3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxMH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyNn19LHsiaWQiOiIxNjkxMjc2My02YTFhLTRkMjgtYTkyNC03OGQ0 + YmNlYzJkN2YiLCJuYW1lIjoiQ0IgNC80Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mjd9fSx7ImlkIjoi + Y2E0MGI1YWItOTRkNC00ZjFjLTg3YmYtMTdmYmE1MTZlODFjIiwibmFtZSI6IkNCIDQvNSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjEy + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjI4fX0seyJpZCI6ImIyZjlmNGQ5LTRiOWItNDMwNy04MjUzLTVlMmRjZGE0YTEzOCIsIm5hbWUiOiJDQiA0LzYiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxM30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyOX19LHsiaWQiOiI4NjViMDJkMy1mMmUzLTQ0YWItOTE5OS00Y2RkNTQ2ZmMxYmIiLCJu + YW1lIjoiQ0IgNC83Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6MTR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MzB9fSx7ImlkIjoiMGZmMmExNmMtNzBm + NC00YTRmLThhOTUtYzI4NDNkN2UxNjg5IiwibmFtZSI6IkNCIDQvOCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjE1fSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjMxfX0seyJpZCI6ImM3ZjEwYjg2LTU1MTQtNDMzZC04OGE5LWVhOWYwMGI5MGU2MSIsIm5hbWUiOiJDQiA1LzEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6MCwibnVtYmVyIjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMyfX0seyJpZCI6ImUyNzJkNDk3LWM1MmUtNGVlOC1hMWQ2LTZiMDUzZDZiMDUxMyIsIm5hbWUiOiJDQiA1LzIi + LCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMzfX0seyJpZCI6ImYzODgwYjQ0LWNhN2EtNGVjYy05NGFiLTEz + OWIxZTVjMzFmMiIsIm5hbWUiOiJDQiA1LzMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoyfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM0fX0seyJpZCI6 + IjUzZDM5YzhiLTVmOTYtNDgwZC04Zjc1LThmMjM5MTY4NWVjNCIsIm5hbWUiOiJDQiA1LzQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjoz + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjM1fX0seyJpZCI6Ijk1NjQ0Yzc2LTc5OWItNDNkYi04MmI2LWRhMWY4ODNiMzBhMSIsIm5hbWUiOiJDQiA1LzUiLCJzb3VyY2UiOnsidHlw + ZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo0fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM2fX0seyJpZCI6IjMxODc1N2M0LTg3ZDktNGVhMC04NzA3LWRkMDlmNzY2OWI3YiIsIm5h + bWUiOiJDQiA1LzYiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM3fX0seyJpZCI6ImI2MTUyN2FmLTFmMTQt + NDdlYi1iNGUyLTc3NzNmMzVkMWI3ZiIsIm5hbWUiOiJDQiA1LzciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo2fSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjM4fX0seyJpZCI6ImM0MjU2Y2JhLWIzYzAtNDhiMi05NjVmLTUzYzRiMjMwYzk5MCIsIm5hbWUiOiJDQiA1LzgiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjM5fX0seyJpZCI6IjE0ZWUzYjA3LWUzM2UtNDZkYS1hYzQ1LTcwZjgxNTg2Zjg4ZiIsIm5hbWUiOiJDU0IgMSIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY0fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQwfX0seyJpZCI6ImIzNGFiZTc2LTgxNzEtNGNmNC1hYjNmLWViMmM2 + M2FjMjRiNyIsIm5hbWUiOiJDU0IgMiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQxfX0seyJpZCI6ImI2 + YTBiNmYyLTU1OTEtNDZlNy1iNjU0LWZlOTFkOGQyYzcyNyIsIm5hbWUiOiJDU0IgMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY2fSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjQyfX0seyJpZCI6ImEyN2M2MGUwLTdhYTctNDdmNy1iOTAyLTdmMmMxYWY1YzkyNyIsIm5hbWUiOiJDU0IgNCIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjowLCJudW1iZXIiOjY3fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQzfX0seyJpZCI6IjMwM2M0NGNhLWRmYmMtNDU1Mi1hMWRiLWE5MDQzYzM2MTFlNSIsIm5hbWUi + OiJDU0IgNSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY4fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQ0fX0seyJpZCI6ImZlZTlmNWY2LTI0MGEtNDJk + Zi04ODg4LTY3Zjk2ODBmMDMxMyIsIm5hbWUiOiJDU0IgNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjY5fSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQ1 + fX0seyJpZCI6IjRmNzA2MzNkLTFmNjQtNGQwNi1iM2ZjLTBlNmRlNmM1NDJiOSIsIm5hbWUiOiJDU0IgNyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJu + dW1iZXIiOjcwfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQ2fX0seyJpZCI6ImMzMzE0ZDczLTQ0NzItNDlmYS1iYWY5LTRlYmM5MGRmZWUwYyIsIm5hbWUiOiJDU0IgOCIsInNvdXJj + ZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjcxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQ3fX0seyJpZCI6IjQxYjlmYmE0LWEwMTctNDk4OS04ZWUwLTQ1M2M2ZDgz + YTg5NiIsIm5hbWUiOiJTTEIgMSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjgyfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjQ4fX0seyJpZCI6IjJjMmIz + NWQyLTc4YjAtNGNmOS04OTdjLTkwZTQ1YWQ3OTUwYiIsIm5hbWUiOiJTTEIgMiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjgzfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjQ5fX0seyJpZCI6IjI4MGRkMzgzLThjOGUtNDQ3Ni1iOWQ2LWVkNjI1ODM3NGNlOSIsIm5hbWUiOiJTTEIgMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjg0fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjUwfX0seyJpZCI6ImI0MzkzM2Y0LTRmZTItNDg0MC05MTNlLTQyZjY5ZTU1Njc3OCIsIm5hbWUiOiJT + TEIgNCIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg1fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjUxfX0seyJpZCI6Ijc4ZjVmMDM2LWFhMDYtNGVmYy1i + OWIwLWEyNjdmYjkyMGViOCIsIm5hbWUiOiJTTEIgNSIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjg2fSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjUyfX0s + eyJpZCI6ImFjNDljZDhhLWNkOTgtNGFjZC04NGEwLTI3NjM3MmFhOGQwNSIsIm5hbWUiOiJTQUMiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVy + Ijo4MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4Ijo1M319LHsiaWQiOiJmMWE5MjY4Yi0zMmZlLTQ0NTktYTZmNC0wNDU0MzE0YWM3ZDEiLCJuYW1lIjoiSyAxIiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6NDh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MX0sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6IjNhNDNlYWUyLTg5ZTktNGMxMi05OTE1LTIzYzZlNGRjYjFhNyIsIm5hbWUiOiJLIDIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVt + YmVyIjo0OX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxfSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNTA0MTg1MzItNzE2NS00NTBhLWE3YWYtZTcwNTNkZTVlNTM4IiwibmFtZSI6IksgMyIsInNvdXJjZSI6eyJjaGFu + bmVsIjowLCJudW1iZXIiOjUwfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlYmVmMzJmYi1iYzBlLTQzYjQtOTlhOS1hZDI3YmIwOWYwN2MiLCJuYW1lIjoiSyA0Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6M30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjZmNWExNmYyLTRmNWQtNDg2ZC04NWI2LTk1ZDU4YWMwZGE4YyIsIm5hbWUi + OiJLIDUiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1Mn0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9y + IjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNTk4NGZjNGMtYmMyYi00YzFkLWFhOWEtNDBhM2Q3NGM4 + ZTEwIiwibmFtZSI6IksgNiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUzfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI1NThjMWMzMi0wMzU1LTQ5YTctOWMz + NC1iNzQwZjZmODQyYTciLCJuYW1lIjoiSyA3Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NTR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImYzMTQ3MDQwLTRh + YzUtNDAyYi05ZjNmLTdkMzM0OTlhNWYwNSIsIm5hbWUiOiJLIDgiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo1NX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + MjIwNTAxODItNDgwZS00MjY3LWIyMDMtOWFkNjQxYTcyYjQ0IiwibmFtZSI6IlN1c3RhaW4iLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2NCwiY2hhcmFj + dGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjU0fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYjA5ZDYxNjktYTdkZi00ZGJlLWI3NmMtNzNkMzBjODYyNWMz + IiwibmFtZSI6IlBsYXkiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5MSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjU1fSwiZmVl + ZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNmM0NzQ1YWMtMzliYi00ZWQ3LWIyOTAtMmNjNWFlZjQ5YmJiIiwibmFtZSI6IlJlYyIsInNvdXJjZSI6eyJ0eXBl + IjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjkzLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NTZ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4 + MzhjYzllNi01ODU3LTRkZDItOTUyYi0zMzlmM2Y4ODZmM2QiLCJuYW1lIjoiU2hpZnQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo5OCwi + Y2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjU3fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6ImFwYy1rZXktMjUifQ== + AAAQAAAA + > + FLOATPOS 19 47 1436 1439 + FXID {5A5F9D0A-37AB-41CB-A139-C3C2BEBED9B4} + WAK 0 0 + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd4AACBBF7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 1172 260 683 876 + FXID {0E787378-EF61-4700-B729-360DA73E717E} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/mapping-header-panel.rpp b/plugin-reaper-realearn/resources/test-projects/mapping-header-panel.rpp new file mode 100644 index 0000000..c17ba4b --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/mapping-header-panel.rpp @@ -0,0 +1,155 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.00230696157498 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA/AAAAAEAAAAAABAA + eyJpZCI6Ik9RaU5WXy1tIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImQzNWE4YzVlLTM2 + YWEtNGExYS1hNmUyLTI5MGZlOTdkNzk0YiIsIm5hbWUiOiIxIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjB9fV19 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {0A04EFA7-F41A-4D66-B0AB-792C5FD2DAA5} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/mft-light-test.RPP b/plugin-reaper-realearn/resources/test-projects/mft-light-test.RPP new file mode 100644 index 0000000..ccd2468 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/mft-light-test.RPP @@ -0,0 +1,239 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABvJgAAAQAAAAAAEAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xNi4wLXByZS45IiwiaWQiOiJZQU5CLUxpbCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJ + ZkJhY2tncm91bmRQcm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMTkiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRHcm91cCI6e30sImdy + b3VwcyI6W3siaWQiOiJMZjE0a0hNRy1rOHdZOVFNZV9QVHYiLCJuYW1lIjoiQ29sb3JzIn0seyJpZCI6ImhBNzRyZGZaWDZrNXZyZUVIblJsQSIsIm5hbWUiOiJCcmln + aHRuZXNzIn0seyJpZCI6IjMtQkZYUTAwb00tRllxOHhDcUwxayIsIm5hbWUiOiJBbmltYXRpb24ifSx7ImlkIjoiZnY2bnAxa1pTampoeENJQ3ZLMVBpIiwibmFtZSI6 + InRlc3QifSx7ImlkIjoiRHV6Wm9laVNjeXJoV08tY2xEMHlyIiwibmFtZSI6IkluZGljYXRvciBhbmltYXRpb24ifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9 + LCJtYXBwaW5ncyI6W3siaWQiOiJISldXUnFwaHJycmVQMTZqWmZ0MDMiLCJuYW1lIjoiR3JlZW4iLCJncm91cElkIjoiTGYxNGtITUctazh3WTlRTWVfUFR2Iiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMSAwMCA3RiIsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Ikt6OUdu + VjdrN0NMcTJ5WVFmZ0FjYyIsIm5hbWUiOiJPcmFuZ2UiLCJncm91cElkIjoiTGYxNGtITUctazh3WTlRTWVfUFR2Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6NTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0 + aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMSAwMCAzRiIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlQtMWhlSElpa3FobzB2SS1QMHZLZCIsIm5hbWUi + OiJPZmYiLCJncm91cElkIjoiaEE3NHJkZlpYNms1dnJlRUhuUmxBIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTUsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2VuZE1pZGlEZXN0aW5h + dGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTEiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJFZUdORWxnRVF2bHNDSFg1YXNQbksiLCJuYW1l + IjoiRGFyayIsImdyb3VwSWQiOiJoQTc0cmRmWlg2azV2cmVFSG5SbEEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NywiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTkiLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzZW5kTWlkaURlc3Rp + bmF0aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAyNyIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IkNZQ19Sd1hNbjFKWUNzaXpPYW5UcyIsIm5h + bWUiOiJCcmlnaHQiLCJncm91cElkIjoiaEE3NHJkZlpYNms1dnJlRUhuUmxBIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTksImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2VuZE1pZGlE + ZXN0aW5hdGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMkYiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJfRWd5X182cXNCRFdqckswRldyTEMi + LCJuYW1lIjoiR2F0ZSBvZmYiLCJncm91cElkIjoiMy1CRlhRMDBvTS1GWXE4eENxTDFrIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjAs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2Vu + ZE1pZGlEZXN0aW5hdGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMDAiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJvZGpwQzV1eHdRQ0k5X2Vo + SmJWUDUiLCJuYW1lIjoiR2F0ZSBzbG93IiwiZ3JvdXBJZCI6IjMtQkZYUTAwb00tRllxOHhDcUwxayIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjYyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0luZGV4IjowLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQiLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDAzIiwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiYVIwZFJHcHgz + eF9UZFpYN2w3ZmV4IiwibmFtZSI6IkdhdGUgZmFzdCIsImdyb3VwSWQiOiIzLUJGWFEwMG9NLUZZcTh4Q3FMMWsiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo2NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTkiLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAwOCIsIm9zY0FyZ0luZGV4IjowLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlhD + b2QzdkFxNGtIU1o5NEFYeC16NiIsIm5hbWUiOiJQdWxzZSBzbG93IiwiZ3JvdXBJZCI6IjMtQkZYUTAwb00tRllxOHhDcUwxayIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjY3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQiLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDBDIiwib3NjQXJnSW5k + ZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7 + ImlkIjoidno4ZDB6VVlBMUlaOG1Da2x2SHNCIiwibmFtZSI6IlB1bHNlIGZhc3QiLCJncm91cElkIjoiMy1CRlhRMDBvTS1GWXE4eENxTDFrIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwi + b3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2VuZE1pZGlEZXN0aW5hdGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMEYiLCJv + c2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2Fk + ZWQifX19LHsiaWQiOiJaQkh6MFE2Wk5lSzhHRUg3Ny1Kc2YiLCJuYW1lIjoiSW5kaWNhdG9yIGdhdGUgb2ZmIiwiZ3JvdXBJZCI6IkR1elpvZWlTY3lyaFdPLWNsRDB5 + ciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjcyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRl + cm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3Bh + ZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQiLCJyYXdNaWRpUGF0dGVy + biI6IkI1IDAwIDMwIiwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiQVF3NUZpLUwyczRYV2pnc2M5OVhvIiwibmFtZSI6IkluZGljYXRvciBnYXRlIG9uIiwiZ3JvdXBJZCI6IkR1elpv + ZWlTY3lyaFdPLWNsRDB5ciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sMS9yb3cxL3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5 + cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQi + LCJyYXdNaWRpUGF0dGVybiI6IkI1IDAwIDM0Iiwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiNElXV0lOTGJYY2JvVldZZG5DbnVwIiwibmFtZSI6IkluZGljYXRvciBwdWxzZSBvbiIs + Imdyb3VwSWQiOiJEdXpab2VpU2N5cmhXTy1jbEQweXIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTkiLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0aW9uIjoi + ZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCNSAwMCAzYSIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IkRHbUZaUm0xTW5hR0ducEwwUFM3cSIsIm5hbWUiOiIxMiIs + Imdyb3VwSWQiOiJmdjZucDFrWlNqamh4Q0lDdksxUGkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VG + eCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fSwiYWRkaXRpb25hbFVuaXRzIjpbXX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 692 119 752 674 + FXID {1A4B459B-FC82-E442-8789-DEBD4FE90AD7} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/midi-14-bit-order-test.RPP b/plugin-reaper-realearn/resources/test-projects/midi-14-bit-order-test.RPP new file mode 100644 index 0000000..3f2abc2 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/midi-14-bit-order-test.RPP @@ -0,0 +1,170 @@ + + RIPPLE 0 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 132 + ENVATTACH 2 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 65792 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 8.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 0 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 0 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + FLOATPOS 0 0 0 0 + FXID {4103D4B1-FA2B-BD41-9621-FB1ABD5EED31} + WAK 0 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAPQAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAABDAAAAL1VzZXJzL2hlbGdvYm9zcy9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvUkVB + UEVSL0RhdGEvR00ucmVhYmFuawAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0A + AAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAAAAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {97C5B053-2C0A-1D42-873C-4CA5FB18F26C} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABOAwAAAQAAAAAAEAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xNy4yIiwiaWQiOiIxNDFXRzkzayIsIm5hbWUiOiJNYWluIiwic3RheUFjdGl2ZVdoZW5Qcm9qZWN0SW5CYWNrZ3JvdW5k + IjoiT25seUlmQmFja2dyb3VuZFByb2plY3RJc1J1bm5pbmciLCJsaXZlc09uVXBwZXJGbG9vciI6ZmFsc2UsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9s + bGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI4MWRjNjZXR1hkQk8tZHliQ3lEcXMiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJidXR0b25JbmRleCI6MCwiYnV0dG9uRGVzaWduIjp7ImJhY2tncm91 + bmQiOnsia2luZCI6IkNvbG9yIn0sImZvcmVncm91bmQiOnsia2luZCI6Ik5vbmUifSwic3RhdGljX3RleHQiOiIifX0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUs + Im1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6NTMsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpm + YWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVr + UGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7Imtp + bmQiOiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0sImFkZGl0aW9uYWxVbml0cyI6W119 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {EE54A0BA-7536-FD48-930D-45FF806B6A33} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/pages.rpp b/plugin-reaper-realearn/resources/test-projects/pages.rpp new file mode 100644 index 0000000..2f0b318 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/pages.rpp @@ -0,0 +1,1206 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 -1 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 0.01128944114301 -0.624 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAGVkAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJqSWJqWGU5ZCIsImNvbnRyb2xEZXZpY2VJZCI6IjIiLCJmZWVkYmFja0RldmljZUlkIjoiMyIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI5ZDNiOTc0MS1iOTE3LTQ0MzAtYWM1Mi03YmRhMTMzNzYwODIiLCJu + YW1lIjoiUGFnZSAxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsInR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsImJ1 + dHRvblVzYWdlIjoicHJlc3Mtb25seSIsImdyb3VwSW50ZXJhY3Rpb24iOiJpbnZlcnNlLXRhcmdldC12YWx1ZSJ9LCJ0YXJnZXQiOnsidHlwZSI6MTIsImZ4QW5jaG9y + IjoiQWxsQnlOYW1lIiwiZnhOYW1lIjoiUGFnZSAxIC0gKiIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5k + ZXgiOjB9fSx7ImlkIjoiODJkMzU1MjgtODU2YS00ODE1LWE2NGYtNzllNDg5MDVjZTUzIiwibmFtZSI6IlBhZ2UgMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJidXR0b25Vc2FnZSI6InByZXNz + LW9ubHkiLCJncm91cEludGVyYWN0aW9uIjoiaW52ZXJzZS10YXJnZXQtdmFsdWUifSwidGFyZ2V0Ijp7InR5cGUiOjEyLCJmeEFuY2hvciI6IkFsbEJ5TmFtZSIsImZ4 + TmFtZSI6IlBhZ2UgMiAtICoiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX0seyJpZCI6IjFi + OGU0NzI2LTVhZDEtNGZlNy1hM2RiLTU4MTZiNzE5YWMzMCIsIm5hbWUiOiJTdXBlcmlvciIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVy + ZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9 + LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjEyLCJmeEFuY2hvciI6ImlkIiwiZnhJbmRleCI6NSwiZnhHVUlEIjoiMDk5QTdGM0Yt + NDA5RC00MjVFLTlBMzYtNTg3OTgwRUY4QzkxIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19 + XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6IjUxOWEzNTVkLWVmM2QtNDBjZS04NDQ0LTAzMTM4MjBkNjdmMyIsIm5hbWUiOiJFbmMgMSIsInNvdXJjZSI6eyJj + aGFubmVsIjowLCJudW1iZXIiOjExMiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjM0NWI3NDIt + NGNmZC00MjJkLWI1NjgtYzg4NzUzZjY0MzdhIiwibmFtZSI6IkVuYyAyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzQsImNoYXJhY3RlciI6MywiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNjZWJlOTZiLThmNTUtNDFjYi05OTQwLWJm + MjdmZWE5NDc2NSIsIm5hbWUiOiJFbmMgMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcxLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI0Njc4MzFlOC1jMzgyLTQyNGMtYTBjNS0zZGU4ODBhODY4MWMiLCJuYW1l + IjoiRW5jIDQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3NiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijoz + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMGQzMWVjMmUtOTk0ZS00ODE5LWIwOGEtNWUwYmExZGU3YjQ4IiwibmFtZSI6IkVuYyA1Iiwic291cmNl + Ijp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzcsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwi + bW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH0sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZX0seyJpZCI6IjBmMzQ5YzE2LWU2OGItNDdlNC04OTcyLTE1MzQ1NDUxZDNhNiIsIm5hbWUiOiJFbmMgNiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJu + dW1iZXIiOjkzLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJiMDFjZGQwMS1kMGJjLTRmMDctOWIwNC05N2ZjZWRkYmM0ZmMiLCJuYW1lIjoiRW5jIDciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3MywiY2hhcmFj + dGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODFkOGRhMmQtM2UyMC00 + ZDViLTkwODQtYmU1NGY4MmQ5Yzc4IiwibmFtZSI6IkVuYyA4Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzUsImNoYXJhY3RlciI6MywiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6N30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNlYWIyMTQ3LTU4YmMtNDgxYS04MTdjLTBlMzMzZDBm + M2ZlOSIsIm5hbWUiOiJFbmMgOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExNCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0 + Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudEluZGV4Ijo4fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMmRhYjc2OGItOWRiMy00OTQ3LWE4MzEtMzIwODgyYjY1NzVhIiwibmFtZSI6IkVu + YyAxMCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NB + cmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9LCJm + ZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI2NWFmNGMyZC03OTBmLTRjOTUtOTIzZS0wNTNjN2ZmOTdlNGEiLCJuYW1lIjoiRW5jIDExIiwic291cmNlIjp7 + ImNoYW5uZWwiOjAsIm51bWJlciI6MTksImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiJkYzk4ZDQ5Ny1lZTgwLTRmOTAtODM3MC01Y2Y0ZDM2ZDllZmQiLCJuYW1lIjoiRW5jIDEyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTYsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJiMGNjMGY0Yi05ZmRjLTQwMjgtOTZkMC1iNDAyZmEwZWYzM2QiLCJuYW1lIjoiRW5jIDEzIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImNoYXJh + Y3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiNjk3ZTg1Mi1hNjVk + LTQ4ZTEtODVhZi0yMWRlMzZmMDU1OGIiLCJuYW1lIjoiRW5jIDE0Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OTEsImNoYXJhY3RlciI6MywiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhOTRmMzNhNC1kMDBmLTRlOWQtOThkMS03YmY3 + MDE3YjczMmIiLCJuYW1lIjoiRW5jIDE1Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzksImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjODU2NTg0Yy1jOGE5LTQ5ZGItOTI4NS1kNWUyYjNiZTMxOTAiLCJuYW1l + IjoiRW5jIDE2Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjYjcyMmVlMC04MjgyLTQ2NDMtOGU4Yy00NDM4MjhlYzcxODIiLCJuYW1lIjoiUGl0Y2ggYmVuZCIs + InNvdXJjZSI6eyJ0eXBlIjozLCJjaGFubmVsIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoxNn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6IjYwYmM5Y2EyLTZhNjItNGZhNy1iZDExLTI2MTg3N2NmMGQwMCIsIm5hbWUiOiJNb2R1bGF0aW9uIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1 + YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjYTZlODU0MC1hODczLTRl + MzgtODVmNS01NTExOGE0YzYwYmQiLCJuYW1lIjoiUGFkIDEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoi + YmRmMDBhMWItNmUwMi00ZDE0LWFkNzUtNTdiMDM5YTUyNWM1IiwibmFtZSI6IlBhZCAxIEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJu + dW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3MCBb + MGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRUeXBlIjoiYnV0dG9uIn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjZjNjE2YzktMTkzMS00OGE4LWE2NjUtNWYzYjU2ZDA4ZGY4Iiwi + bmFtZSI6IlBhZCAyIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6MzcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6 + IjRhYTI5MzZlLThlY2EtNDcyMS05YjBiLWJlZDRmZTE0MDJmYyIsIm5hbWUiOiJQYWQgMiBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwi + bnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzEg + WzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjViNjRiMDU1LTJkNzYt + NDU2ZS05ZWNjLTVjODAwY2Y0MTdiYyIsIm5hbWUiOiJQYWQgMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjM4LCJpc1JlZ2lzdGVyZWQi + OmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tB + ZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiJhMTJhNjIzOC0wMmIyLTRlMzEtYTMyMS1lNzFmMDNkNzJiYjkiLCJuYW1lIjoiUGFkIDMgRmVlZGJhY2siLCJzb3VyY2Ui + OnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAw + IDIwIDZCIDdGIDQyIDAyIDAwIDEwIDcyIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwiY29udHJvbElzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJiN2VlZTViZi04NWRiLTQ0MGItYTY0ZC01MTc3N2FhYzQ3YWYiLCJuYW1lIjoiUGFkIDQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwi + bnVtYmVyIjozOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M30sImZlZWRiYWNrSXNFbmFi + bGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiNmJjYWNmMTAtZmU0MC00MzI4LWJiMTAtZGU1OTExMTIzZjdmIiwibmFtZSI6 + IlBhZCA0IEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3MyBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + M30sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzE5Njg5NGYtMTA2MS00ZWE1LThkZGUtYmIzMTI4ZmJiY2ZjIiwibmFtZSI6IlBhZCA1Iiwic291cmNl + Ijp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjgyNDE2ZDI1LTdjN2YtNDc3Zi04 + ZTZhLTI4YTdjYzFlOWQxMyIsIm5hbWUiOiJQYWQgNSBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzQgWzBnZmUgZGNiYV0gRjciLCJvc2NB + cmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjU2ZjRlZTYyLTg4MTAtNDM0OS1iMzhmLWVjOWQwZGUyYjVl + NCIsIm5hbWUiOiJQYWQgNiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjQxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRydWV9LHsi + aWQiOiJiZDc4NGM0Yy1iZmI5LTQ1MWEtYTE2My0yNDk5NGYyYTMyZWMiLCJuYW1lIjoiUGFkIDYgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwi + OjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEw + IDc1IFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJj + b250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjMjQzMGRkMC01 + MzY3LTRhMGItYjBlMC04N2JhODIyMmQzNTciLCJuYW1lIjoiUGFkIDciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjo0MiwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRi + YWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiZjlhZGRhOWEtZGIwYi00YWIyLWFkNWQtM2E4ZjI5OWU1OGE1IiwibmFtZSI6IlBhZCA3IEZlZWRiYWNrIiwic291 + cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJG + MCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3NiBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sImNvbnRyb2xJc0VuYWJsZWQi + OmZhbHNlfSx7ImlkIjoiM2M5ZTNiMjgtNWVjMS00NDQ4LWI1ZDgtNTg1MmMyOGVlZTVkIiwibmFtZSI6IlBhZCA4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjksIm51bWJlciI6NDMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6ImNhNTVhY2FkLWFlNTItNGM3Mi05NDY4LTEyYjQ5ODU2ZGU4NiIsIm5h + bWUiOiJQYWQgOCBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzcgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjd9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImE0ZjA2MTEwLTUwNzQtNGY0Yy04NGI1LWFkZjE0MjE1ZGVjMiIsIm5hbWUiOiJQYWQgOSIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjQ0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4Ijo4fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiJiNmUzYWIxNC1mNmM0LTQw + NGYtYWMzMC03NTc2ZWYxNTQ0YTEiLCJuYW1lIjoiUGFkIDkgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDc4IFswZ2ZlIGRjYmFdIEY3Iiwi + b3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3M2JjNmZmNy1lNTA3LTRiY2ItYjJjZS1jYzE4MzEx + NjU3NDEiLCJuYW1lIjoiUGFkIDEwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQi + OmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1 + ZX0seyJpZCI6IjdmMzBmYmZkLWQ4NzAtNDAyMy05N2UzLTk1MjA2Nzg2ZDZkNyIsIm5hbWUiOiJQYWQgMTAgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNo + YW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAy + IDAwIDEwIDc5IFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyOTgw + MWM5YS02M2JjLTQwMWUtOWQ0NS03NjAxMzkxZWJmNGEiLCJuYW1lIjoiUGFkIDExIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDYsImlz + UmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJz + ZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiJkZDdlZDc2Zi1hMmRmLTQyOWUtYjQzYS00MjU2NTE4YzAxNWEiLCJuYW1lIjoiUGFkIDExIEZlZWRi + YWNrIiwic291cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBh + dHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3QSBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImE3Yjc3MTkwLTllZmMtNDEyMi05ODhmLTEzOGMxOWJkMzU1NCIsIm5hbWUiOiJQYWQgMTIiLCJzb3VyY2UiOnsidHlwZSI6 + MSwiY2hhbm5lbCI6OSwibnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjFkODhmODY4LTlhN2ItNGM1Yy1hMzNhLWU0MjUw + N2EyM2E4NSIsIm5hbWUiOiJQYWQgMTIgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDdCIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxMX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjliZjk0YjgtMmU2MC00MjlmLTgxZjItNmVkODgxMTdhY2Q5IiwibmFt + ZSI6IlBhZCAxMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoi + ZGMyNTAzYmEtODkyZi00YTUzLWI1YzUtZGU0NTc3NjRlNzEyIiwibmFtZSI6IlBhZCAxMyBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwi + bnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgN0Mg + WzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRy + b2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxOGQwYjA1ZS00Yzg3 + LTQ1NGYtOGUwMS1mNDliMTJiYWM0YTgiLCJuYW1lIjoiUGFkIDE0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDksImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJh + Y2tBZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiI2M2U2M2IwZi1iMGVhLTRhODQtOTg0ZC1jNDA4NzkwN2ViNmYiLCJuYW1lIjoiUGFkIDE0IEZlZWRiYWNrIiwic291 + cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJG + MCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3RCBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJjb250cm9sSXNFbmFibGVk + IjpmYWxzZX0seyJpZCI6IjViMDQ0NDY5LTQ0YTYtNDZiMi04MGZkLWRjMDc4MWI5ZWM5ZSIsIm5hbWUiOiJQYWQgMTUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5l + bCI6OSwibnVtYmVyIjo1MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6ImQ1MDY5ZDgyLWViYTEtNDZiMi1hNDY4LWJjZDg1NGU0NmE4NSIs + Im5hbWUiOiJQYWQgMTUgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDdFIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxNH0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOGViNjIwYzAtMTUzNS00ZDBmLWJkZWMtYTBiOWIxYzI4MGM4IiwibmFtZSI6IlBhZCAx + NiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjUxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjoxNX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiNjk3OGRlZTMt + YzFjNS00ZmIwLTg3Y2EtNjM4NTlmNzI1Y2UxIiwibmFtZSI6IlBhZCAxNiBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjoz + NiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgN0YgWzBnZmUgZGNi + YV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJmMDk5ZDYwMy0wOGYyLTRhZGUtYjYy + Ni1jNWYxYzI3NmFlODUiLCJuYW1lIjoiUGFkIDEgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyNjM1NTM4Yy00MGUzLTQwN2EtODU4Ni02YTY1M2FjMjk4 + OTgiLCJuYW1lIjoiUGFkIDIgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6MTl9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4MzRhNjQxMS1mYzZhLTQ3ZDItOThjNy1iMzZmNDQ5NDhmN2IiLCJuYW1lIjoi + UGFkIDMgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozOCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MjB9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlM2JhNGYwMi1kMGQ5LTQ5NDYtODc5NS1hOTkwMGEyOGYxY2EiLCJuYW1lIjoiUGFkIDQgUHJlc3N1 + cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4 + IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjF9LCJmZWVkYmFj + a0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyYzZmNTUwNC0wYTFjLTQ4NjQtYTc3Yy1mODk1NThhNDJmMDMiLCJuYW1lIjoiUGFkIDUgUHJlc3N1cmUiLCJzb3VyY2Ui + OnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJhZTE3MmZkMy04MmUxLTRiODQtODI0YS1hODlhOTVjZjMzMjUiLCJuYW1lIjoiUGFkIDYgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6Nywi + Y2hhbm5lbCI6OSwibnVtYmVyIjo0MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjN9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiI4Y2VmMTAwNC1iOTFhLTQyMWItODgxZS1hODE1MjE5MWQ4ZTMiLCJuYW1lIjoiUGFkIDcgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwi + bnVtYmVyIjo0MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3ZmNkODE0MS0z + MDFlLTQyOTUtOTE2NC1iNGI0MjcwOGYzZTciLCJuYW1lIjoiUGFkIDggUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0Mywi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5MmJhM2VlNy1mMjU3LTQ4ZTEtYWM2 + MC04Njk0ZDRjMDg2MjgiLCJuYW1lIjoiUGFkIDkgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0NCwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjZ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5YmNjMzcyYS1lZTA5LTRiODEtOGQ4Yy0xOGJjYzdmMmRl + NGUiLCJuYW1lIjoiUGFkIDEwIFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcsImNoYW5uZWwiOjksIm51bWJlciI6NDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlz + MTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjI3fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYmI5ZWY2OGUtZjRmZi00Y2MyLWE1NjctM2ZlYTVkZTM5YjZhIiwibmFtZSI6 + IlBhZCAxMSBQcmVzc3VyZSIsInNvdXJjZSI6eyJ0eXBlIjo3LCJjaGFubmVsIjo5LCJudW1iZXIiOjQ2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4 + IjoyOH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhhMDFiNWRjLWE2MGItNDAxMi04YWY5LTc1NWFkOGFkMDYwZiIsIm5hbWUiOiJQYWQgMTIgUHJl + c3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0lu + ZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mjl9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiNDQ0ZjBhNi0wYmVmLTQ4MTYtYjQ0MC05ZTVjMjA5NGFjNTciLCJuYW1lIjoiUGFkIDEzIFByZXNzdXJlIiwic291 + cmNlIjp7InR5cGUiOjcsImNoYW5uZWwiOjksIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMwfSwiZmVlZGJhY2tJc0VuYWJs + ZWQiOmZhbHNlfSx7ImlkIjoiN2JlNGI3YTUtZjA3My00MWVhLThlMWItZDUyOTg0OGM0ZTlkIiwibmFtZSI6IlBhZCAxNCBQcmVzc3VyZSIsInNvdXJjZSI6eyJ0eXBl + Ijo3LCJjaGFubmVsIjo5LCJudW1iZXIiOjQ5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjozMX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0s + eyJpZCI6ImJiYTUyNmUzLWI5NDktNGZmMS05MjU4LWY0YThlODY2N2QzNyIsIm5hbWUiOiJQYWQgMTUgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5l + bCI6OSwibnVtYmVyIjo1MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MzJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3NDlm + ODAzMy1mZWJlLTRlYWUtOTE3Ny02MWE0ZDNlNDBhNmQiLCJuYW1lIjoiUGFkIDE2IFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcsImNoYW5uZWwiOjksIm51bWJl + ciI6NTEsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMzfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzFkZDAxZjctNDZjOS00 + YzJmLTkzZDUtYWY2MmU2ZWY4NDUxIiwibmFtZSI6IkVuYyAxIFB1c2giLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTMsImNoYXJhY3RlciI6MSwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsi + aWQiOiIzZWRkM2MyZC1hMmFkLTRjNmYtOGE2MS0yOTAzYWEzMzFiMTgiLCJuYW1lIjoiRW5jIDkgUHVzaCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEx + NSwiY2hhcmFjdGVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN30sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjcwMzk3YzBlLTI5MjgtNDJhZS1hZmM4LTE1MDdkNDM3ODg4ZCIsIm5hbWUiOiJTdXN0YWluIiwic291cmNlIjp7ImNoYW5u + ZWwiOjAsIm51bWJlciI6NjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fSwiZmVlZGJh + Y2tJc0VuYWJsZWQiOmZhbHNlfV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pbmlsYWItbWtpaSJ9 + AAAQAAAA + > + FLOAT 1202 416 1436 1514 + FXID {3AA43EDD-ED4A-4A89-91AB-FDFBA7E1617F} + WAK 0 0 + BYPASS 1 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAjCQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJBSnZDRFhwWSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjE5YzYzMTQ2LTA0MmItNDJmYi1hY2FjLWVhZDQ5M2E3OTU3MSIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjEsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3Mi + Olt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2 + Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19 + LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlm + YmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUy + ZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4 + ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFj + dGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7 + ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9u + IDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1l + IjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2 + YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05 + NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIg + My8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3 + ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6 + IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkz + MDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUz + YmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19 + LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEt + OTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1 + YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3Rl + ciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291 + cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0s + eyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0 + dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRk + IiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6 + eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6 + ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0 + ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0s + ImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 1067 347 1436 1514 + FXID {619B2862-AAB3-4FB1-AB18-8E042D6D5F54} + WAK 0 0 + BYPASS 1 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAASlUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJaaEdvWXZrcCIsImNvbnRyb2xEZXZpY2VJZCI6IjIiLCJmZWVkYmFja0RldmljZUlkIjoiMyIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI3OGM3OWQ1YS1jMzVjLTQ1MjEtOWRjMS03ODI5ODE0OTdjMmEiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxMSwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJm + eEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJvbGxlck1h + cHBpbmdzIjpbeyJpZCI6IjUxOWEzNTVkLWVmM2QtNDBjZS04NDQ0LTAzMTM4MjBkNjdmMyIsIm5hbWUiOiJFbmMgMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjExMiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjM0NWI3NDItNGNmZC00MjJkLWI1Njgt + Yzg4NzUzZjY0MzdhIiwibmFtZSI6IkVuYyAyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzQsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNjZWJlOTZiLThmNTUtNDFjYi05OTQwLWJmMjdmZWE5NDc2NSIsIm5h + bWUiOiJFbmMgMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcxLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI0Njc4MzFlOC1jMzgyLTQyNGMtYTBjNS0zZGU4ODBhODY4MWMiLCJuYW1lIjoiRW5jIDQiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3NiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjozfSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiMGQzMWVjMmUtOTk0ZS00ODE5LWIwOGEtNWUwYmExZGU3YjQ4IiwibmFtZSI6IkVuYyA1Iiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NzcsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6IjBmMzQ5YzE2LWU2OGItNDdlNC04OTcyLTE1MzQ1NDUxZDNhNiIsIm5hbWUiOiJFbmMgNiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjkzLCJjaGFy + YWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiMDFjZGQwMS1kMGJj + LTRmMDctOWIwNC05N2ZjZWRkYmM0ZmMiLCJuYW1lIjoiRW5jIDciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3MywiY2hhcmFjdGVyIjozLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODFkOGRhMmQtM2UyMC00ZDViLTkwODQtYmU1NGY4 + MmQ5Yzc4IiwibmFtZSI6IkVuYyA4Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzUsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6N30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNlYWIyMTQ3LTU4YmMtNDgxYS04MTdjLTBlMzMzZDBmM2ZlOSIsIm5hbWUiOiJF + bmMgOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExNCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMmRhYjc2OGItOWRiMy00OTQ3LWE4MzEtMzIwODgyYjY1NzVhIiwibmFtZSI6IkVuYyAxMCIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiI2NWFmNGMyZC03OTBmLTRjOTUtOTIzZS0wNTNjN2ZmOTdlNGEiLCJuYW1lIjoiRW5jIDExIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTksImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJkYzk4ZDQ5Ny1lZTgwLTRmOTAtODM3MC01Y2Y0ZDM2ZDllZmQiLCJuYW1lIjoiRW5jIDEyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJh + Y3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiMGNjMGY0Yi05ZmRj + LTQwMjgtOTZkMC1iNDAyZmEwZWYzM2QiLCJuYW1lIjoiRW5jIDEzIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImNoYXJhY3RlciI6MywiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiNjk3ZTg1Mi1hNjVkLTQ4ZTEtODVhZi0yMWRl + MzZmMDU1OGIiLCJuYW1lIjoiRW5jIDE0Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OTEsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTN9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhOTRmMzNhNC1kMDBmLTRlOWQtOThkMS03YmY3MDE3YjczMmIiLCJuYW1l + IjoiRW5jIDE1Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzksImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjODU2NTg0Yy1jOGE5LTQ5ZGItOTI4NS1kNWUyYjNiZTMxOTAiLCJuYW1lIjoiRW5jIDE2Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjYjcyMmVlMC04MjgyLTQ2NDMtOGU4Yy00NDM4MjhlYzcxODIiLCJuYW1lIjoiUGl0Y2ggYmVuZCIsInNvdXJjZSI6eyJ0eXBl + IjozLCJjaGFubmVsIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoxNn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjYwYmM5 + Y2EyLTZhNjItNGZhNy1iZDExLTI2MTg3N2NmMGQwMCIsIm5hbWUiOiJNb2R1bGF0aW9uIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjYTZlODU0MC1hODczLTRlMzgtODVmNS01NTExOGE0 + YzYwYmQiLCJuYW1lIjoiUGFkIDEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiYmRmMDBhMWItNmUwMi00 + ZDE0LWFkNzUtNTdiMDM5YTUyNWM1IiwibmFtZSI6IlBhZCAxIEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3MCBbMGdmZSBkY2JhXSBGNyIs + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjZjNjE2YzktMTkzMS00OGE4LWE2NjUtNWYzYjU2ZDA4ZGY4IiwibmFtZSI6IlBhZCAyIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6MzcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjRhYTI5MzZlLThlY2Et + NDcyMS05YjBiLWJlZDRmZTE0MDJmYyIsIm5hbWUiOiJQYWQgMiBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzEgWzBnZmUgZGNiYV0gRjci + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjViNjRiMDU1LTJkNzYtNDU2ZS05ZWNjLTVjODAw + Y2Y0MTdiYyIsIm5hbWUiOiJQYWQgMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjM4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRy + dWV9LHsiaWQiOiJhMTJhNjIzOC0wMmIyLTRlMzEtYTMyMS1lNzFmMDNkNzJiYjkiLCJuYW1lIjoiUGFkIDMgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNo + YW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAy + IDAwIDEwIDcyIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiN2Vl + ZTViZi04NWRiLTQ0MGItYTY0ZC01MTc3N2FhYzQ3YWYiLCJuYW1lIjoiUGFkIDQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjozOSwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2Vu + ZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiNmJjYWNmMTAtZmU0MC00MzI4LWJiMTAtZGU1OTExMTIzZjdmIiwibmFtZSI6IlBhZCA0IEZlZWRiYWNr + Iiwic291cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRl + cm4iOiJGMCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3MyBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M30sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiMzE5Njg5NGYtMTA2MS00ZWE1LThkZGUtYmIzMTI4ZmJiY2ZjIiwibmFtZSI6IlBhZCA1Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjksIm51bWJlciI6NDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjgyNDE2ZDI1LTdjN2YtNDc3Zi04ZTZhLTI4YTdjYzFlOWQx + MyIsIm5hbWUiOiJQYWQgNSBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzQgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjR9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjU2ZjRlZTYyLTg4MTAtNDM0OS1iMzhmLWVjOWQwZGUyYjVlNCIsIm5hbWUiOiJQYWQg + NiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjQxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo1fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiJiZDc4NGM0Yy1i + ZmI5LTQ1MWEtYTE2My0yNDk5NGYyYTMyZWMiLCJuYW1lIjoiUGFkIDYgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDc1IFswZ2ZlIGRjYmFd + IEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjMjQzMGRkMC01MzY3LTRhMGItYjBlMC04 + N2JhODIyMmQzNTciLCJuYW1lIjoiUGFkIDciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjo0MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9s + Ijp0cnVlfSx7ImlkIjoiZjlhZGRhOWEtZGIwYi00YWIyLWFkNWQtM2E4ZjI5OWU1OGE1IiwibmFtZSI6IlBhZCA3IEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEw + LCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0 + MiAwMiAwMCAxMCA3NiBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + M2M5ZTNiMjgtNWVjMS00NDQ4LWI1ZDgtNTg1MmMyOGVlZTVkIiwibmFtZSI6IlBhZCA4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDMs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2Us + InNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6ImNhNTVhY2FkLWFlNTItNGM3Mi05NDY4LTEyYjQ5ODU2ZGU4NiIsIm5hbWUiOiJQYWQgOCBGZWVk + YmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQ + YXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzcgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImE0ZjA2MTEwLTUwNzQtNGY0Yy04NGI1LWFkZjE0MjE1ZGVjMiIsIm5hbWUiOiJQYWQgOSIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjo5LCJudW1iZXIiOjQ0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiJiNmUzYWIxNC1mNmM0LTQwNGYtYWMzMC03NTc2ZWYx + NTQ0YTEiLCJuYW1lIjoiUGFkIDkgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDc4IFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijo4fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3M2JjNmZmNy1lNTA3LTRiY2ItYjJjZS1jYzE4MzExNjU3NDEiLCJuYW1lIjoi + UGFkIDEwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjdmMzBm + YmZkLWQ4NzAtNDAyMy05N2UzLTk1MjA2Nzg2ZDZkNyIsIm5hbWUiOiJQYWQgMTAgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJl + ciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDc5IFswZ2Zl + IGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyOTgwMWM5YS02M2JjLTQwMWUt + OWQ0NS03NjAxMzkxZWJmNGEiLCJuYW1lIjoiUGFkIDExIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDYsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRl + ckNvbnRyb2wiOnRydWV9LHsiaWQiOiJkZDdlZDc2Zi1hMmRmLTQyOWUtYjQzYS00MjU2NTE4YzAxNWEiLCJuYW1lIjoiUGFkIDExIEZlZWRiYWNrIiwic291cmNlIjp7 + InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAy + MCA2QiA3RiA0MiAwMiAwMCAxMCA3QSBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImE3Yjc3MTkwLTllZmMtNDEyMi05ODhmLTEzOGMxOWJkMzU1NCIsIm5hbWUiOiJQYWQgMTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwi + bnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9LCJmZWVkYmFja0lzRW5h + YmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjFkODhmODY4LTlhN2ItNGM1Yy1hMzNhLWU0MjUwN2EyM2E4NSIsIm5hbWUi + OiJQYWQgMTIgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDdCIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxMX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjliZjk0YjgtMmU2MC00MjlmLTgxZjItNmVkODgxMTdhY2Q5IiwibmFtZSI6IlBhZCAxMyIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoxMn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiZGMyNTAzYmEtODkyZi00 + YTUzLWI1YzUtZGU0NTc3NjRlNzEyIiwibmFtZSI6IlBhZCAxMyBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgN0MgWzBnZmUgZGNiYV0gRjci + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxOGQwYjA1ZS00Yzg3LTQ1NGYtOGUwMS1mNDli + MTJiYWM0YTgiLCJuYW1lIjoiUGFkIDE0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wi + OnRydWV9LHsiaWQiOiI2M2U2M2IwZi1iMGVhLTRhODQtOTg0ZC1jNDA4NzkwN2ViNmYiLCJuYW1lIjoiUGFkIDE0IEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEw + LCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0 + MiAwMiAwMCAxMCA3RCBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + IjViMDQ0NDY5LTQ0YTYtNDZiMi04MGZkLWRjMDc4MWI5ZWM5ZSIsIm5hbWUiOiJQYWQgMTUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjo1 + MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6ImQ1MDY5ZDgyLWViYTEtNDZiMi1hNDY4LWJjZDg1NGU0NmE4NSIsIm5hbWUiOiJQYWQgMTUg + RmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdN + aWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDdFIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOGViNjIwYzAtMTUzNS00ZDBmLWJkZWMtYTBiOWIxYzI4MGM4IiwibmFtZSI6IlBhZCAxNiIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjUxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxNX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiNjk3OGRlZTMtYzFjNS00ZmIwLTg3Y2Et + NjM4NTlmNzI1Y2UxIiwibmFtZSI6IlBhZCAxNiBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgN0YgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJ + bmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJmMDk5ZDYwMy0wOGYyLTRhZGUtYjYyNi1jNWYxYzI3NmFlODUi + LCJuYW1lIjoiUGFkIDEgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTh9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyNjM1NTM4Yy00MGUzLTQwN2EtODU4Ni02YTY1M2FjMjk4OTgiLCJuYW1lIjoiUGFk + IDIgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4MzRhNjQxMS1mYzZhLTQ3ZDItOThjNy1iMzZmNDQ5NDhmN2IiLCJuYW1lIjoiUGFkIDMgUHJlc3N1cmUi + LCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozOCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjB9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlM2JhNGYwMi1kMGQ5LTQ5NDYtODc5NS1hOTkwMGEyOGYxY2EiLCJuYW1lIjoiUGFkIDQgUHJlc3N1cmUiLCJzb3VyY2UiOnsi + dHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiIyYzZmNTUwNC0wYTFjLTQ4NjQtYTc3Yy1mODk1NThhNDJmMDMiLCJuYW1lIjoiUGFkIDUgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hh + bm5lbCI6OSwibnVtYmVyIjo0MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJh + ZTE3MmZkMy04MmUxLTRiODQtODI0YS1hODlhOTVjZjMzMjUiLCJuYW1lIjoiUGFkIDYgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVt + YmVyIjo0MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjN9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4Y2VmMTAwNC1iOTFh + LTQyMWItODgxZS1hODE1MjE5MWQ4ZTMiLCJuYW1lIjoiUGFkIDcgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0MiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3ZmNkODE0MS0zMDFlLTQyOTUtOTE2NC1i + NGI0MjcwOGYzZTciLCJuYW1lIjoiUGFkIDggUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0MywiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MjV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5MmJhM2VlNy1mMjU3LTQ4ZTEtYWM2MC04Njk0ZDRjMDg2Mjgi + LCJuYW1lIjoiUGFkIDkgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6MjZ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5YmNjMzcyYS1lZTA5LTRiODEtOGQ4Yy0xOGJjYzdmMmRlNGUiLCJuYW1lIjoiUGFk + IDEwIFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcsImNoYW5uZWwiOjksIm51bWJlciI6NDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI3 + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYmI5ZWY2OGUtZjRmZi00Y2MyLWE1NjctM2ZlYTVkZTM5YjZhIiwibmFtZSI6IlBhZCAxMSBQcmVzc3Vy + ZSIsInNvdXJjZSI6eyJ0eXBlIjo3LCJjaGFubmVsIjo5LCJudW1iZXIiOjQ2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoyOH0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhhMDFiNWRjLWE2MGItNDAxMi04YWY5LTc1NWFkOGFkMDYwZiIsIm5hbWUiOiJQYWQgMTIgUHJlc3N1cmUiLCJzb3VyY2Ui + OnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mjl9LCJmZWVkYmFja0lzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJiNDQ0ZjBhNi0wYmVmLTQ4MTYtYjQ0MC05ZTVjMjA5NGFjNTciLCJuYW1lIjoiUGFkIDEzIFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcs + ImNoYW5uZWwiOjksIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMwfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiN2JlNGI3YTUtZjA3My00MWVhLThlMWItZDUyOTg0OGM0ZTlkIiwibmFtZSI6IlBhZCAxNCBQcmVzc3VyZSIsInNvdXJjZSI6eyJ0eXBlIjo3LCJjaGFubmVsIjo5 + LCJudW1iZXIiOjQ5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjozMX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJiYTUyNmUz + LWI5NDktNGZmMS05MjU4LWY0YThlODY2N2QzNyIsIm5hbWUiOiJQYWQgMTUgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo1 + MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MzJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3NDlmODAzMy1mZWJlLTRlYWUt + OTE3Ny02MWE0ZDNlNDBhNmQiLCJuYW1lIjoiUGFkIDE2IFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcsImNoYW5uZWwiOjksIm51bWJlciI6NTEsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMzfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzFkZDAxZjctNDZjOS00YzJmLTkzZDUtYWY2MmU2 + ZWY4NDUxIiwibmFtZSI6IkVuYyAxIFB1c2giLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTMsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIzZWRkM2MyZC1h + MmFkLTRjNmYtOGE2MS0yOTAzYWEzMzFiMTgiLCJuYW1lIjoiRW5jIDkgUHVzaCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExNSwiY2hhcmFjdGVyIjox + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6IjcwMzk3YzBlLTI5MjgtNDJhZS1hZmM4LTE1MDdkNDM3ODg4ZCIsIm5hbWUiOiJTdXN0YWluIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + NjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pbmlsYWItbWtpaSJ9 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {E8030285-B8ED-4354-8D43-AD0FF7700555} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAjCQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJBSnZDRFhwWSIsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwiZGVmYXVs + dEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6IjE5YzYzMTQ2LTA0MmItNDJmYi1hY2FjLWVhZDQ5M2E3OTU3MSIs + Im5hbWUiOiIxIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjIsImZ4QW5jaG9y + IjoiaWQiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowfX1dLCJjb250cm9sbGVyTWFwcGluZ3Mi + Olt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIgMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJl + ciI6MCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNv + bG9CZWhhdmlvciI6MX19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRiYWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5j + aG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2 + Y2Y2MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MX19 + LHsiaWQiOiJkZDFhODFkMy1kNjA3LTQzMjktOWQ5MS1hNmI4ODhiMGZhYWYiLCJuYW1lIjoiQnV0dG9uIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjEsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xv + QmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhlMy00NjUzLTlm + YmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJpczE0Qml0Ijpm + YWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJ + bmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjox + LCJudW1iZXIiOjIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYTNjMjdjMDItYzUy + ZC00NzFjLWI3MDYtMjdiNjJlMmE1ZGJlIiwibmFtZSI6IkVuY29kZXIgMS80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MywiY2hhcmFjdGVyIjozLCJp + czE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJv + bEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJuYW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4 + QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4 + ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVuY29kZXIgMi8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NCwiY2hhcmFj + dGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6 + MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NH19LHsiaWQiOiI1MGYzZDU2ZC0wZjYzLTQwODYtOTU5Mi1iM2NlOTEzZGIzODEiLCJuYW1lIjoiQnV0dG9uIDIvMSIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7 + ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZkMmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + NSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9C + ZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9u + IDIvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjV9fSx7ImlkIjoiNDViYmJkNWQtMzRmNy00YWI4LWI0OTYtZWViOWNmNjAyYTc4IiwibmFtZSI6IkVuY29kZXIgMi8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NiwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJp + ZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1l + IjoiQnV0dG9uIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291cmNlIjp7ImNo + YW5uZWwiOjAsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhB + bmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6N319LHsiaWQiOiI4MDE4YzI1OS02Zjc2LTQxMzMtOTA0Ny02NDEwNzgxODk2 + YWQiLCJuYW1lIjoiQnV0dG9uIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFtZSI6IkVuY29kZXIgMy8xIiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsiaWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05 + NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7ImlkIjoiNjQwZWIyYTQtNzM1ZC00ZDkxLWFlNWItNjM2NzFkZmFiNWQ3IiwibmFtZSI6IkVuY29kZXIg + My8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3 + ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MSwiaXMxNEJp + dCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6 + IkVuY29kZXIgMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6ImY3YTkz + MDVkLTk0YWItNGZjOS05YjY0LWE5MDllNTU5MzU0YiIsIm5hbWUiOiJCdXR0b24gMy8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTAsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUz + YmIzYSIsIm5hbWUiOiJFbmNvZGVyIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19 + LHsiaWQiOiI2YjhhZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIi + OjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiIzYjVmYzFlOC00MmRiLTQ1ODEt + OTQ3ZS01ZGZhM2QzYzBiZjQiLCJuYW1lIjoiRW5jb2RlciA0LzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9fSx7ImlkIjoiYTM4NDM1 + YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTMsImNoYXJhY3Rl + ciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6IjkxNDViOGE3LTAyMWUtNDZlOS1iYzI0LTU4OGI1YjcwZTNjZCIsIm5hbWUiOiJCdXR0b24gNC8yIiwic291 + cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0s + eyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIi + OjE0LCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29s + b0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0 + dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxNH19LHsiaWQiOiI1NWFhMjNiMC1iODIwLTRlYmUtOWIxMi05ZmU0MDg0MmVmMjciLCJuYW1lIjoiRW5jb2RlciA0LzQiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MCwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNo + b3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRk + IiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6 + eyJjaGFubmVsIjozLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE2fX0seyJpZCI6 + ImVlOWRmNTkwLTYxY2ItNDc0Ni1hMzEzLTc3MzU2ZTU4YjM1ZSIsIm5hbWUiOiJSaWdodCAxIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTEsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX0seyJpZCI6ImFiMDU0ODgwLTFjNDMtNGY0YS1iNDE4LWMzZWE0 + ZDMxYjUxMCIsIm5hbWUiOiJMZWZ0IDMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUi + Ont9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTh9fSx7ImlkIjoiYTU3YTI3ODctMzkxOS00NmZiLThmZGQtMTdmYjgzZjRiYWYxIiwibmFtZSI6IlJpZ2h0IDMiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjoxMywiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0 + dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9fV0s + ImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pZGktZmlnaHRlci10d2lzdGVyIn0= + AAAQAAAA + > + FLOATPOS 19 47 1436 1514 + FXID {4B2E1685-67B7-4327-913E-732C5755C54D} + WAK 0 0 + BYPASS 0 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAASlUAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJaaEdvWXZrcCIsImNvbnRyb2xEZXZpY2VJZCI6IjIiLCJmZWVkYmFja0RldmljZUlkIjoiMyIsImRlZmF1bHRH + cm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiI3OGM3OWQ1YS1jMzVjLTQ1MjEtOWRjMS03ODI5ODE0OTdjMmEiLCJu + YW1lIjoiMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxMSwibnVtYmVyIjoxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJm + eEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MH19XSwiY29udHJvbGxlck1h + cHBpbmdzIjpbeyJpZCI6IjUxOWEzNTVkLWVmM2QtNDBjZS04NDQ0LTAzMTM4MjBkNjdmMyIsIm5hbWUiOiJFbmMgMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjExMiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQifSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjM0NWI3NDItNGNmZC00MjJkLWI1Njgt + Yzg4NzUzZjY0MzdhIiwibmFtZSI6IkVuYyAyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzQsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNjZWJlOTZiLThmNTUtNDFjYi05OTQwLWJmMjdmZWE5NDc2NSIsIm5h + bWUiOiJFbmMgMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcxLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNl + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI0Njc4MzFlOC1jMzgyLTQyNGMtYTBjNS0zZGU4ODBhODY4MWMiLCJuYW1lIjoiRW5jIDQiLCJzb3Vy + Y2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3NiwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjozfSwiZmVlZGJhY2tJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiMGQzMWVjMmUtOTk0ZS00ODE5LWIwOGEtNWUwYmExZGU3YjQ4IiwibmFtZSI6IkVuYyA1Iiwic291cmNlIjp7ImNoYW5uZWwiOjAs + Im51bWJlciI6NzcsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NH0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJp + ZCI6IjBmMzQ5YzE2LWU2OGItNDdlNC04OTcyLTE1MzQ1NDUxZDNhNiIsIm5hbWUiOiJFbmMgNiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjkzLCJjaGFy + YWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiMDFjZGQwMS1kMGJj + LTRmMDctOWIwNC05N2ZjZWRkYmM0ZmMiLCJuYW1lIjoiRW5jIDciLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjo3MywiY2hhcmFjdGVyIjozLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiODFkOGRhMmQtM2UyMC00ZDViLTkwODQtYmU1NGY4 + MmQ5Yzc4IiwibmFtZSI6IkVuYyA4Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzUsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6N30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjNlYWIyMTQ3LTU4YmMtNDgxYS04MTdjLTBlMzMzZDBmM2ZlOSIsIm5hbWUiOiJF + bmMgOSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExNCwiY2hhcmFjdGVyIjozLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3Nj + QXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMmRhYjc2OGItOWRiMy00OTQ3LWE4MzEtMzIwODgyYjY1NzVhIiwibmFtZSI6IkVuYyAxMCIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjE4LCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9LCJmZWVkYmFja0lzRW5hYmxl + ZCI6ZmFsc2V9LHsiaWQiOiI2NWFmNGMyZC03OTBmLTRjOTUtOTIzZS0wNTNjN2ZmOTdlNGEiLCJuYW1lIjoiRW5jIDExIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTksImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQi + OiJkYzk4ZDQ5Ny1lZTgwLTRmOTAtODM3MC01Y2Y0ZDM2ZDllZmQiLCJuYW1lIjoiRW5jIDEyIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTYsImNoYXJh + Y3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiMGNjMGY0Yi05ZmRj + LTQwMjgtOTZkMC1iNDAyZmEwZWYzM2QiLCJuYW1lIjoiRW5jIDEzIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTcsImNoYXJhY3RlciI6MywiaXNSZWdp + c3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiNjk3ZTg1Mi1hNjVkLTQ4ZTEtODVhZi0yMWRl + MzZmMDU1OGIiLCJuYW1lIjoiRW5jIDE0Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6OTEsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwi + aXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTN9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJhOTRmMzNhNC1kMDBmLTRlOWQtOThkMS03YmY3MDE3YjczMmIiLCJuYW1l + IjoiRW5jIDE1Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzksImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2Us + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjODU2NTg0Yy1jOGE5LTQ5ZGItOTI4NS1kNWUyYjNiZTMxOTAiLCJuYW1lIjoiRW5jIDE2Iiwic291 + cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6NzIsImNoYXJhY3RlciI6MywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTV9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjYjcyMmVlMC04MjgyLTQ2NDMtOGU4Yy00NDM4MjhlYzcxODIiLCJuYW1lIjoiUGl0Y2ggYmVuZCIsInNvdXJjZSI6eyJ0eXBl + IjozLCJjaGFubmVsIjowLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVn + b3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoxNn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjYwYmM5 + Y2EyLTZhNjItNGZhNy1iZDExLTI2MTg3N2NmMGQwMCIsIm5hbWUiOiJNb2R1bGF0aW9uIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MSwiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6 + ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTd9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjYTZlODU0MC1hODczLTRlMzgtODVmNS01NTExOGE0 + YzYwYmQiLCJuYW1lIjoiUGFkIDEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6 + ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRU + eXBlIjoiYnV0dG9uIn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiYmRmMDBhMWItNmUwMi00 + ZDE0LWFkNzUtNTdiMDM5YTUyNWM1IiwibmFtZSI6IlBhZCAxIEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3MCBbMGdmZSBkY2JhXSBGNyIs + Im9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjZjNjE2YzktMTkzMS00OGE4LWE2NjUtNWYzYjU2ZDA4ZGY4IiwibmFtZSI6IlBhZCAyIiwi + c291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6MzcsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0s + Im1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjRhYTI5MzZlLThlY2Et + NDcyMS05YjBiLWJlZDRmZTE0MDJmYyIsIm5hbWUiOiJQYWQgMiBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzEgWzBnZmUgZGNiYV0gRjci + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjViNjRiMDU1LTJkNzYtNDU2ZS05ZWNjLTVjODAw + Y2Y0MTdiYyIsIm5hbWUiOiJQYWQgMyIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjM4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRy + dWV9LHsiaWQiOiJhMTJhNjIzOC0wMmIyLTRlMzEtYTMyMS1lNzFmMDNkNzJiYjkiLCJuYW1lIjoiUGFkIDMgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNo + YW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAy + IDAwIDEwIDcyIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoi + aWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJiN2Vl + ZTViZi04NWRiLTQ0MGItYTY0ZC01MTc3N2FhYzQ3YWYiLCJuYW1lIjoiUGFkIDQiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjozOSwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2Vu + ZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiNmJjYWNmMTAtZmU0MC00MzI4LWJiMTAtZGU1OTExMTIzZjdmIiwibmFtZSI6IlBhZCA0IEZlZWRiYWNr + Iiwic291cmNlIjp7InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRl + cm4iOiJGMCAwMCAyMCA2QiA3RiA0MiAwMiAwMCAxMCA3MyBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M30sImNvbnRyb2xJc0Vu + YWJsZWQiOmZhbHNlfSx7ImlkIjoiMzE5Njg5NGYtMTA2MS00ZWE1LThkZGUtYmIzMTI4ZmJiY2ZjIiwibmFtZSI6IlBhZCA1Iiwic291cmNlIjp7InR5cGUiOjEsImNo + YW5uZWwiOjksIm51bWJlciI6NDAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsi + Y2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9LCJmZWVk + YmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjgyNDE2ZDI1LTdjN2YtNDc3Zi04ZTZhLTI4YTdjYzFlOWQx + MyIsIm5hbWUiOiJQYWQgNSBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzQgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjR9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjU2ZjRlZTYyLTg4MTAtNDM0OS1iMzhmLWVjOWQwZGUyYjVlNCIsIm5hbWUiOiJQYWQg + NiIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjQxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo1fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiJiZDc4NGM0Yy1i + ZmI5LTQ1MWEtYTE2My0yNDk5NGYyYTMyZWMiLCJuYW1lIjoiUGFkIDYgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDc1IFswZ2ZlIGRjYmFd + IEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJjMjQzMGRkMC01MzY3LTRhMGItYjBlMC04 + N2JhODIyMmQzNTciLCJuYW1lIjoiUGFkIDciLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjo0MiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMx + NEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9s + Ijp0cnVlfSx7ImlkIjoiZjlhZGRhOWEtZGIwYi00YWIyLWFkNWQtM2E4ZjI5OWU1OGE1IiwibmFtZSI6IlBhZCA3IEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEw + LCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0 + MiAwMiAwMCAxMCA3NiBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoi + M2M5ZTNiMjgtNWVjMS00NDQ4LWI1ZDgtNTg1MmMyOGVlZTVkIiwibmFtZSI6IlBhZCA4Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDMs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwi + ZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2Us + InNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6ImNhNTVhY2FkLWFlNTItNGM3Mi05NDY4LTEyYjQ5ODU2ZGU4NiIsIm5hbWUiOiJQYWQgOCBGZWVk + YmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQ + YXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgNzcgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9LCJjb250cm9s + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImE0ZjA2MTEwLTUwNzQtNGY0Yy04NGI1LWFkZjE0MjE1ZGVjMiIsIm5hbWUiOiJQYWQgOSIsInNvdXJjZSI6eyJ0eXBlIjox + LCJjaGFubmVsIjo5LCJudW1iZXIiOjQ0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fSwi + ZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wiOnRydWV9LHsiaWQiOiJiNmUzYWIxNC1mNmM0LTQwNGYtYWMzMC03NTc2ZWYx + NTQ0YTEiLCJuYW1lIjoiUGFkIDkgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2Us + ImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDc4IFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9 + LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijo4fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3M2JjNmZmNy1lNTA3LTRiY2ItYjJjZS1jYzE4MzExNjU3NDEiLCJuYW1lIjoi + UGFkIDEwIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJ + bmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjdmMzBm + YmZkLWQ4NzAtNDAyMy05N2UzLTk1MjA2Nzg2ZDZkNyIsIm5hbWUiOiJQYWQgMTAgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJl + ciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDc5IFswZ2Zl + IGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyOTgwMWM5YS02M2JjLTQwMWUt + OWQ0NS03NjAxMzkxZWJmNGEiLCJuYW1lIjoiUGFkIDExIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDYsImlzUmVnaXN0ZXJlZCI6ZmFs + c2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNv + bnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRl + ckNvbnRyb2wiOnRydWV9LHsiaWQiOiJkZDdlZDc2Zi1hMmRmLTQyOWUtYjQzYS00MjU2NTE4YzAxNWEiLCJuYW1lIjoiUGFkIDExIEZlZWRiYWNrIiwic291cmNlIjp7 + InR5cGUiOjEwLCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAy + MCA2QiA3RiA0MiAwMiAwMCAxMCA3QSBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9LCJjb250cm9sSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6ImE3Yjc3MTkwLTllZmMtNDEyMi05ODhmLTEzOGMxOWJkMzU1NCIsIm5hbWUiOiJQYWQgMTIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwi + bnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9LCJmZWVkYmFja0lzRW5h + YmxlZCI6ZmFsc2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6IjFkODhmODY4LTlhN2ItNGM1Yy1hMzNhLWU0MjUwN2EyM2E4NSIsIm5hbWUi + OiJQYWQgMTIgRmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZh + bHNlLCJyYXdNaWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDdCIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxMX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiNjliZjk0YjgtMmU2MC00MjlmLTgxZjItNmVkODgxMTdhY2Q5IiwibmFtZSI6IlBhZCAxMyIsInNv + dXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjQ4LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJt + b2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxl + bWVudEluZGV4IjoxMn0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiZGMyNTAzYmEtODkyZi00 + YTUzLWI1YzUtZGU0NTc3NjRlNzEyIiwibmFtZSI6IlBhZCAxMyBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgN0MgWzBnZmUgZGNiYV0gRjci + LCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIxOGQwYjA1ZS00Yzg3LTQ1NGYtOGUwMS1mNDli + MTJiYWM0YTgiLCJuYW1lIjoiUGFkIDE0Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjksIm51bWJlciI6NDksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRC + aXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVt + ZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlLCJzZW5kRmVlZGJhY2tBZnRlckNvbnRyb2wi + OnRydWV9LHsiaWQiOiI2M2U2M2IwZi1iMGVhLTRhODQtOTg0ZC1jNDA4NzkwN2ViNmYiLCJuYW1lIjoiUGFkIDE0IEZlZWRiYWNrIiwic291cmNlIjp7InR5cGUiOjEw + LCJjaGFubmVsIjo5LCJudW1iZXIiOjM2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJGMCAwMCAyMCA2QiA3RiA0 + MiAwMiAwMCAxMCA3RCBbMGdmZSBkY2JhXSBGNyIsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hv + ciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9LCJjb250cm9sSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6 + IjViMDQ0NDY5LTQ0YTYtNDZiMi04MGZkLWRjMDc4MWI5ZWM5ZSIsIm5hbWUiOiJQYWQgMTUiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6OSwibnVtYmVyIjo1 + MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2UsInNlbmRGZWVkYmFja0FmdGVyQ29udHJvbCI6dHJ1ZX0seyJpZCI6ImQ1MDY5ZDgyLWViYTEtNDZiMi1hNDY4LWJjZDg1NGU0NmE4NSIsIm5hbWUiOiJQYWQgMTUg + RmVlZGJhY2siLCJzb3VyY2UiOnsidHlwZSI6MTAsImNoYW5uZWwiOjksIm51bWJlciI6MzYsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdN + aWRpUGF0dGVybiI6IkYwIDAwIDIwIDZCIDdGIDQyIDAyIDAwIDEwIDdFIFswZ2ZlIGRjYmFdIEY3Iiwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH0sImNv + bnRyb2xJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiOGViNjIwYzAtMTUzNS00ZDBmLWJkZWMtYTBiOWIxYzI4MGM4IiwibmFtZSI6IlBhZCAxNiIsInNvdXJjZSI6eyJ0 + eXBlIjoxLCJjaGFubmVsIjo5LCJudW1iZXIiOjUxLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxNX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZSwic2VuZEZlZWRiYWNrQWZ0ZXJDb250cm9sIjp0cnVlfSx7ImlkIjoiNjk3OGRlZTMtYzFjNS00ZmIwLTg3Y2Et + NjM4NTlmNzI1Y2UxIiwibmFtZSI6IlBhZCAxNiBGZWVkYmFjayIsInNvdXJjZSI6eyJ0eXBlIjoxMCwiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVk + IjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiRjAgMDAgMjAgNkIgN0YgNDIgMDIgMDAgMTAgN0YgWzBnZmUgZGNiYV0gRjciLCJvc2NBcmdJ + bmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fSwiY29udHJvbElzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJmMDk5ZDYwMy0wOGYyLTRhZGUtYjYyNi1jNWYxYzI3NmFlODUi + LCJuYW1lIjoiUGFkIDEgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozNiwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTh9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIyNjM1NTM4Yy00MGUzLTQwN2EtODU4Ni02YTY1M2FjMjk4OTgiLCJuYW1lIjoiUGFk + IDIgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozNywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9z + Y0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTl9 + LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4MzRhNjQxMS1mYzZhLTQ3ZDItOThjNy1iMzZmNDQ5NDhmN2IiLCJuYW1lIjoiUGFkIDMgUHJlc3N1cmUi + LCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozOCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4Ijow + fSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjB9LCJmZWVkYmFja0lz + RW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJlM2JhNGYwMi1kMGQ5LTQ5NDYtODc5NS1hOTkwMGEyOGYxY2EiLCJuYW1lIjoiUGFkIDQgUHJlc3N1cmUiLCJzb3VyY2UiOnsi + dHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjozOSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjF9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiIyYzZmNTUwNC0wYTFjLTQ4NjQtYTc3Yy1mODk1NThhNDJmMDMiLCJuYW1lIjoiUGFkIDUgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hh + bm5lbCI6OSwibnVtYmVyIjo0MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiJh + ZTE3MmZkMy04MmUxLTRiODQtODI0YS1hODlhOTVjZjMzMjUiLCJuYW1lIjoiUGFkIDYgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVt + YmVyIjo0MSwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjN9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI4Y2VmMTAwNC1iOTFh + LTQyMWItODgxZS1hODE1MjE5MWQ4ZTMiLCJuYW1lIjoiUGFkIDcgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0MiwiaXNS + ZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFu + Y2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MjR9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3ZmNkODE0MS0zMDFlLTQyOTUtOTE2NC1i + NGI0MjcwOGYzZTciLCJuYW1lIjoiUGFkIDggUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0MywiaXNSZWdpc3RlcmVkIjpm + YWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwi + Y29udHJvbEVsZW1lbnRJbmRleCI6MjV9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5MmJhM2VlNy1mMjU3LTQ4ZTEtYWM2MC04Njk0ZDRjMDg2Mjgi + LCJuYW1lIjoiUGFkIDkgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJp + dCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6MjZ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI5YmNjMzcyYS1lZTA5LTRiODEtOGQ4Yy0xOGJjYzdmMmRlNGUiLCJuYW1lIjoiUGFk + IDEwIFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcsImNoYW5uZWwiOjksIm51bWJlciI6NDUsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJv + c2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjI3 + fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiYmI5ZWY2OGUtZjRmZi00Y2MyLWE1NjctM2ZlYTVkZTM5YjZhIiwibmFtZSI6IlBhZCAxMSBQcmVzc3Vy + ZSIsInNvdXJjZSI6eyJ0eXBlIjo3LCJjaGFubmVsIjo5LCJudW1iZXIiOjQ2LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgi + OjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjoyOH0sImZlZWRiYWNr + SXNFbmFibGVkIjpmYWxzZX0seyJpZCI6IjhhMDFiNWRjLWE2MGItNDAxMi04YWY5LTc1NWFkOGFkMDYwZiIsIm5hbWUiOiJQYWQgMTIgUHJlc3N1cmUiLCJzb3VyY2Ui + OnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo0NywiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6 + e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mjl9LCJmZWVkYmFja0lzRW5hYmxlZCI6 + ZmFsc2V9LHsiaWQiOiJiNDQ0ZjBhNi0wYmVmLTQ4MTYtYjQ0MC05ZTVjMjA5NGFjNTciLCJuYW1lIjoiUGFkIDEzIFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcs + ImNoYW5uZWwiOjksIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMwfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7Imlk + IjoiN2JlNGI3YTUtZjA3My00MWVhLThlMWItZDUyOTg0OGM0ZTlkIiwibmFtZSI6IlBhZCAxNCBQcmVzc3VyZSIsInNvdXJjZSI6eyJ0eXBlIjo3LCJjaGFubmVsIjo5 + LCJudW1iZXIiOjQ5LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudEluZGV4IjozMX0sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxzZX0seyJpZCI6ImJiYTUyNmUz + LWI5NDktNGZmMS05MjU4LWY0YThlODY2N2QzNyIsIm5hbWUiOiJQYWQgMTUgUHJlc3N1cmUiLCJzb3VyY2UiOnsidHlwZSI6NywiY2hhbm5lbCI6OSwibnVtYmVyIjo1 + MCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MzJ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiI3NDlmODAzMy1mZWJlLTRlYWUt + OTE3Ny02MWE0ZDNlNDBhNmQiLCJuYW1lIjoiUGFkIDE2IFByZXNzdXJlIiwic291cmNlIjp7InR5cGUiOjcsImNoYW5uZWwiOjksIm51bWJlciI6NTEsImlzUmVnaXN0 + ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3Ii + OiJpZCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjMzfSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiMzFkZDAxZjctNDZjOS00YzJmLTkzZDUtYWY2MmU2 + ZWY4NDUxIiwibmFtZSI6IkVuYyAxIFB1c2giLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMTMsImNoYXJhY3RlciI6MSwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9LCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFsc2V9LHsiaWQiOiIzZWRkM2MyZC1h + MmFkLTRjNmYtOGE2MS0yOTAzYWEzMzFiMTgiLCJuYW1lIjoiRW5jIDkgUHVzaCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjExNSwiY2hhcmFjdGVyIjox + LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN30sImZlZWRiYWNrSXNFbmFibGVkIjpmYWxz + ZX0seyJpZCI6IjcwMzk3YzBlLTI5MjgtNDJhZS1hZmM4LTE1MDdkNDM3ODg4ZCIsIm5hbWUiOiJTdXN0YWluIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6 + NjQsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiZnhBbmNob3IiOiJpZCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZh + bHNlfV0sImFjdGl2ZUNvbnRyb2xsZXJJZCI6Im1pbmlsYWItbWtpaSJ9 + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {DC761D29-66C3-43C0-B41B-B7DDCE5A8ADA} + WAK 0 0 + BYPASS 1 0 0 + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAqyQAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4xMC4wLXByZS43IiwiaWQiOiJDZ3Rfd3pObSIsImxpdmVzT25VcHBlckZsb29yIjp0cnVlLCJjb250cm9sRGV2aWNlSWQiOiIxMCIsImZlZWRi + YWNrRGV2aWNlSWQiOiIxMSIsImRlZmF1bHRHcm91cCI6e30sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9LCJtYXBwaW5ncyI6W3siaWQiOiIxYzRkMGFkYy1iYzVh + LTQ0ZjUtOWU3Ny00NDY3NDJjZDA0MWUiLCJuYW1lIjoiMSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0 + IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNX0sInRhcmdldCI6eyJ0eXBlIjoy + LCJ0cmFja0dVSUQiOiJtYXN0ZXIiLCJmeEFuY2hvciI6ImlkIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJ + bmRleCI6MH19XSwiY29udHJvbGxlck1hcHBpbmdzIjpbeyJpZCI6IjBjMDI5MzYzLTcxYzctNDExNS1iOGUwLTcyNDVhYzFiNmQ0YiIsIm5hbWUiOiJFbmNvZGVyIDEv + MSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjAsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5 + IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjF9fSx7ImlkIjoiZTU1NDNhZDgtOWU4MS00YmFmLWE4MmQtMmMyODE0NDVlNzA1IiwibmFt + ZSI6IkJ1dHRvbiAxLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdl + dCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24ifX0seyJpZCI6 + IjI3NTIxZWYwLTVjZjgtNDVlOS05YzkwLTRkNDkwNmNmNjMwNCIsIm5hbWUiOiJFbmNvZGVyIDEvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEsImNo + YXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZp + b3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgxZDMtZDYwNy00MzI5LTlkOTEtYTZiODg4YjBmYWFmIiwibmFtZSI6IkJ1dHRvbiAxLzIi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijox + fX0seyJpZCI6IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIsIm5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1i + ZXIiOjIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJz + b2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9fSx7ImlkIjoiYjgyZDc3NTItZDIyOS00NDhmLWJkNDItYWY2MWI0NTllNDgxIiwibmFtZSI6IkJ1 + dHRvbiAxLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoyLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJj + YXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoyfX0seyJpZCI6ImEzYzI3YzAyLWM1MmQtNDcxYy1iNzA2LTI3YjYyZTJhNWRiZSIsIm5hbWUiOiJFbmNvZGVyIDEvNCIsInNvdXJjZSI6eyJjaGFubmVs + IjowLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9y + IjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4Iiwi + bmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4IjozfX0seyJpZCI6IjgyOGQ0NjVhLTliZDMtNDQzNi05MTM0LTQ4OGE2ZmViMWZiZiIsIm5hbWUiOiJFbmNvZGVyIDIvMSIsInNvdXJjZSI6 + eyJjaGFubmVsIjowLCJudW1iZXIiOjQsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNTBmM2Q1NmQtMGY2My00MDg2LTk1OTItYjNjZTkx + M2RiMzgxIiwibmFtZSI6IkJ1dHRvbiAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVyIDIvMiIs + InNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoi + dmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9fSx7ImlkIjoiMDM5Zjc5OWItNTM3Zi00Mjc3LWFi + YzEtMDNjNTA2ODI3MDgzIiwibmFtZSI6IkJ1dHRvbiAyLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo1LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZh + bHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5 + cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjQ1YmJiZDVkLTM0ZjctNGFiOC1iNDk2LWVlYjljZjYwMmE3OCIsIm5hbWUiOiJFbmNv + ZGVyIDIvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzli + Ny00YWIwLTk4MTctMjA1NTc2NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjEsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo2fX0seyJpZCI6ImQyODQ5ZWM3LWM4OTUtNDRlZS1iZmZhLTM0ZWVmNTBmOTc3YyIsIm5h + bWUiOiJFbmNvZGVyIDIvNCIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjcsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiODAx + OGMyNTktNmY3Ni00MTMzLTkwNDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1dHRvbiAyLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo3LCJjaGFyYWN0 + ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjox + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVm + NmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjgsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9fSx7 + ImlkIjoiMTYwMTNjNDgtMmE0OS00NjcyLWJmM2EtOTVlYWUwNWVmM2E3IiwibmFtZSI6IkJ1dHRvbiAzLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo4 + LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0Jl + aGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjY0MGViMmE0LTczNWQtNGQ5MS1hZTVi + LTYzNjcxZGZhYjVkNyIsIm5hbWUiOiJFbmNvZGVyIDMvMiIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjksImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFs + c2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjl9fSx7ImlkIjoiMmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwi + bnVtYmVyIjo5LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6Imlk + Iiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo5fX0seyJpZCI6ImM4NjVjNTAzLTI1ZmMt + NGI4MS1iYmM4LTA3MzExMDVhMjY1NiIsIm5hbWUiOiJFbmNvZGVyIDMvMyIsInNvdXJjZSI6eyJjaGFubmVsIjowLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjMsImlz + MTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9s + RWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzktOWI2NC1hOTA5ZTU1OTM1NGIiLCJuYW1lIjoiQnV0dG9uIDMvMyIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMH19LHsiaWQiOiI3 + YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxMSwiY2hh + cmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlv + ciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiNmI4YWQxMzEtYmVkOS00YmE3LWE2MDQtNDM1Y2YwNDM2YzkxIiwibmFtZSI6IkJ1dHRvbiAzLzQi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTF9fSx7ImlkIjoiM2I1ZmMxZTgtNDJkYi00NTgxLTk0N2UtNWRmYTNkM2MwYmY0IiwibmFtZSI6IkVuY29kZXIgNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51 + bWJlciI6MTIsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQi + LCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkxNTgwLTk4NmYtNDJmZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUi + OiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xF + bGVtZW50SW5kZXgiOjEyfX0seyJpZCI6ImEzODQzNWMwLTNhYzQtNDE3NC1iMWIxLTc4MWVmZjc5ODhjNCIsIm5hbWUiOiJFbmNvZGVyIDQvMiIsInNvdXJjZSI6eyJj + aGFubmVsIjowLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjMsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJm + eEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5MTQ1YjhhNy0wMjFlLTQ2ZTktYmMyNC01ODhiNWI3 + MGUzY2QiLCJuYW1lIjoiQnV0dG9uIDQvMiIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9k + ZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMi + LCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjozLCJpczE0Qml0IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiMjMyZDk4OWEtZTBkNy00N2Y5 + LTk1ODctNjY4YWMxMjU4NGEzIiwibmFtZSI6IkJ1dHRvbiA0LzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwiY2hhcmFjdGVyIjoxLCJpczE0Qml0 + IjpmYWxzZX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiZnhBbmNob3IiOiJpZCIsInNvbG9CZWhhdmlvciI6MSwiY29udHJvbEVsZW1l + bnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiNTVhYTIzYjAtYjgyMC00ZWJlLTliMTItOWZlNDA4NDJlZjI3IiwibmFtZSI6 + IkVuY29kZXIgNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjAsIm51bWJlciI6MTUsImNoYXJhY3RlciI6MywiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJh + OWUzLTI4OGItNGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3Rl + ciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEs + ImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjI3YmYyMTUyLWU3ZjQtNDVlNS05Yjk4LWRjYWU4OTY1 + YjkxZiIsIm5hbWUiOiJMZWZ0IDEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MywibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30s + InRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJj + b250cm9sRWxlbWVudEluZGV4IjoxNn19LHsiaWQiOiJlZTlkZjU5MC02MWNiLTQ3NDYtYTMxMy03NzM1NmU1OGIzNWUiLCJuYW1lIjoiUmlnaHQgMSIsInNvdXJjZSI6 + eyJjaGFubmVsIjozLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjEsImlzMTRCaXQiOmZhbHNlfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJmeEFuY2hvciI6ImlkIiwic29sb0JlaGF2aW9yIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN319LHsiaWQi + OiJhYjA1NDg4MC0xYzQzLTRmNGEtYjQxOC1jM2VhNGQzMWI1MTAiLCJuYW1lIjoiTGVmdCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTAsImNoYXJh + Y3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3Ii + OjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE4fX0seyJpZCI6ImE1N2EyNzg3LTM5MTktNDZmYi04ZmRkLTE3ZmI4 + M2Y0YmFmMSIsIm5hbWUiOiJSaWdodCAzIiwic291cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6MTMsImNoYXJhY3RlciI6MSwiaXMxNEJpdCI6ZmFsc2V9LCJtb2Rl + Ijp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImZ4QW5jaG9yIjoiaWQiLCJzb2xvQmVoYXZpb3IiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE5fX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJtaWRpLWZpZ2h0ZXItdHdpc3RlciJ9 + AAAQAAAA + > + FLOATPOS 1298 289 1436 1514 + FXID {099A7F3F-409D-425E-9A36-587980EF8C91} + WAK 0 0 + > + > + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/screenshots-with-virtual-controller.rpp b/plugin-reaper-realearn/resources/test-projects/screenshots-with-virtual-controller.rpp new file mode 100644 index 0000000..ffd6da5 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/screenshots-with-virtual-controller.rpp @@ -0,0 +1,422 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 327.06 4 4 + PLAYRATE 1.525 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAVzEAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4wLjAiLCJpZCI6IldHVlBlSGNBIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJh + Y2tEZXZpY2VJZCI6IjExIiwiZGVmYXVsdEdyb3VwIjp7fSwibWFwcGluZ3MiOlt7ImlkIjoiYWEzZTc5OGUtNTgwYi00OWQ0LWIzOWYtY2YwOWQyZDQ5NTNmIiwibmFt + ZSI6IlRyYWNrIHZvbHVtZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRy + YWNrR1VJRCI6IjgwNEM0QjQ5LTU1RkMtNDM1Qy05QjkzLUZCRDc1NDFBQTNCNyIsInRyYWNrTmFtZSI6IlN5bnRoIn19LHsiaWQiOiJmM2U2YTQ3ZS0wZDlkLTRhOTIt + OTJkZC0wYzNkM2VjOGRiNTciLCJuYW1lIjoiU2VuZCAgdm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsIm51bWJlciI6MSwiY2hhcmFjdGVyIjoz + LCJjb250cm9sRWxlbWVudEluZGV4IjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjozLCJ0cmFja0dVSUQiOiI4MDRDNEI0OS01NUZDLTQzNUMtOUI5My1GQkQ3 + NTQxQUEzQjciLCJ0cmFja05hbWUiOiJTeW50aCIsInNlbmRJbmRleCI6MH19LHsiaWQiOiJmM2EyZWMwMi00MjcwLTQ3MTUtYjQ5MS0zZDM3MDg1NjhhZmIiLCJuYW1l + IjoiVHJhY2sgcGFuIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsIm51bWJlciI6MiwiY2hhcmFjdGVyIjozLCJjb250cm9sRWxlbWVudEluZGV4IjoyfSwi + bW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjo0LCJ0cmFja0dVSUQiOiI4MDRDNEI0OS01NUZDLTQzNUMtOUI5My1GQkQ3NTQxQUEzQjciLCJ0cmFja05hbWUiOiJTeW50 + aCJ9fSx7ImlkIjoiZTI4YTgxYzYtMjJmZC00NDIwLTg3YzEtM2MyYjRiZDUzYjgxIiwibmFtZSI6IlNlbGVjdGVkIHZvbHVtZSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6 + InZpcnR1YWwiLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MywiY29udHJvbEVsZW1lbnRJbmRleCI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tH + VUlEIjoic2VsZWN0ZWQifX0seyJpZCI6IjQ4ZDgxMTE5LTczNmQtNDllOS1iMTEzLWE2YjU5M2IyYzJhNyIsIm5hbWUiOiJBcm0iLCJzb3VyY2UiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjN9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NSwidHJhY2tHVUlEIjoiODA0QzRCNDktNTVGQy00MzVDLTlCOTMtRkJENzU0MUFBM0I3Iiwi + dHJhY2tOYW1lIjoiU3ludGgifX0seyJpZCI6ImUyY2FhNTBhLWYzZTMtNGE4My05ODQzLTZkNmUzODIzZjkyYSIsIm5hbWUiOiJTZWxlY3QiLCJzb3VyY2UiOnsiY2F0 + ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjR9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6NiwidHJhY2tHVUlEIjoiODA0QzRCNDktNTVGQy00MzVDLTlCOTMtRkJENzU0MUFB + M0I3IiwidHJhY2tOYW1lIjoiU3ludGgifX0seyJpZCI6Ijg4MjA0MjU1LTZjYWItNDRjNS05NTIwLWNlMmEzNDhmZGVhNiIsIm5hbWUiOiJNdXRlIiwic291cmNlIjp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjEsIm51bWJlciI6NSwiY2hhcmFjdGVyIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9s + RWxlbWVudEluZGV4Ijo1fSwibW9kZSI6eyJ0eXBlIjoyfSwidGFyZ2V0Ijp7InR5cGUiOjcsInRyYWNrR1VJRCI6IjgwNEM0QjQ5LTU1RkMtNDM1Qy05QjkzLUZCRDc1 + NDFBQTNCNyIsInRyYWNrTmFtZSI6IlN5bnRoIn19LHsiaWQiOiJmYzcwZmViOC0wODJhLTRiZGItOWU1YS1jNjk5M2ExMTdlMzkiLCJuYW1lIjoiU29sbyIsInNvdXJj + ZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6Nn0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjo4LCJ0cmFja0dVSUQiOiI4MDRDNEI0OS01NUZDLTQzNUMtOUI5My1G + QkQ3NTQxQUEzQjciLCJ0cmFja05hbWUiOiJTeW50aCJ9fSx7ImlkIjoiZWI0Y2U5MTMtODEyNS00ZjU1LWEyZGQtYTY5NmY1MzVhMjBmIiwibmFtZSI6IlNlbmQgcGFu + Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsIm51bWJlciI6NywiY2hhcmFjdGVyIjozLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fSwibW9kZSI6e30sInRh + cmdldCI6eyJ0eXBlIjo5LCJ0cmFja0dVSUQiOiI4MDRDNEI0OS01NUZDLTQzNUMtOUI5My1GQkQ3NTQxQUEzQjciLCJ0cmFja05hbWUiOiJTeW50aCIsInNlbmRJbmRl + eCI6MH19LHsiaWQiOiJmMGM1YWYwYi00ZDE3LTQyZmEtOWUzYy1hMGM2MzA2Y2M4YmYiLCJuYW1lIjoiVGVtcG8iLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwibnVtYmVyIjo4LCJjaGFyYWN0ZXIiOjMsImNvbnRyb2xFbGVtZW50SW5kZXgiOjh9LCJtb2RlIjp7Im1pblN0ZXBTaXplIjowLjAwMTA0Mjc1Mjg2NzU3MDM4NTgs + Im1heFN0ZXBTaXplIjowLjAwMTA0Mjc1Mjg2NzU3MDM4NTh9LCJ0YXJnZXQiOnsidHlwZSI6MTB9fSx7ImlkIjoiY2QzNDJmY2EtOTY5Yy00Y2I4LWE2M2ItNTUyYjZj + ZTE1YjA2IiwibmFtZSI6IlBsYXlyYXRlIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsIm51bWJlciI6OSwiY2hhcmFjdGVyIjozLCJjb250cm9sRWxlbWVu + dEluZGV4Ijo5fSwibW9kZSI6eyJtaW5TdGVwU2l6ZSI6MC4wMDI2NjY2NjY2NjY2NjY2NjY2LCJtYXhTdGVwU2l6ZSI6MC4wMDI2NjY2NjY2NjY2NjY2NjY2fSwidGFy + Z2V0Ijp7InR5cGUiOjExfX0seyJpZCI6ImUwZDhmZTQ1LTlmYWYtNDVjMy1hY2IzLWRlNTkxZDY0Mzk1NCIsIm5hbWUiOiJBY3Rpb24iLCJzb3VyY2UiOnsiY2F0ZWdv + cnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxMH0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdldCI6eyJ0eXBlIjowLCJjb21tYW5kTmFtZSI6IjMxIn19LHsiaWQiOiI3ODQ0YWNiMS0zNzA2LTQ2ODYt + ODQ2MS1mMDAwMzdlNzQ2N2IiLCJuYW1lIjoiR2F0ZSBBdHRhY2siLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoz + LCJjb250cm9sRWxlbWVudEluZGV4IjoxMX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHJhY2tHVUlEIjoiODA0QzRCNDktNTVGQy00MzVDLTlCOTMtRkJENzU0MUFBM0I3 + IiwidHJhY2tOYW1lIjoiU3ludGgiLCJmeEFuY2hvciI6ImlkLW9yLWluZGV4IiwiZnhJbmRleCI6MCwiZnhHVUlEIjoiMzVFNTI5RDQtN0VFNy00MkQ5LTkxMzctM0RF + Q0MyRkUyOEFGIiwicGFyYW1JbmRleCI6MX19LHsiaWQiOiI5YzQ0YmY2MC02NjQzLTRjZWEtODFlYi0xZmVkYTA0NWVlN2YiLCJuYW1lIjoiUmVhR2F0ZSBwcmVzZXQi + LCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjozLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnsibWlu + U3RlcFNpemUiOi0wLjEyLCJtYXhTdGVwU2l6ZSI6LTAuMTJ9LCJ0YXJnZXQiOnsidHlwZSI6MTMsInRyYWNrR1VJRCI6IjgwNEM0QjQ5LTU1RkMtNDM1Qy05QjkzLUZC + RDc1NDFBQTNCNyIsInRyYWNrTmFtZSI6IlN5bnRoIiwiZnhBbmNob3IiOiJpZC1vci1pbmRleCIsImZ4SW5kZXgiOjAsImZ4R1VJRCI6IjM1RTUyOUQ0LTdFRTctNDJE + OS05MTM3LTNERUNDMkZFMjhBRiJ9fSx7ImlkIjoiY2I1YzNmM2UtYjc0NS00MjMzLTlkNDEtMDM2NTc2NjcxOWY1IiwibmFtZSI6IkdhdGUgb24vb2ZmIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3RlciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTJ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJnZXQiOnsidHlwZSI6MTIsInRyYWNrR1VJRCI6IjgwNEM0QjQ5LTU1RkMtNDM1Qy05Qjkz + LUZCRDc1NDFBQTNCNyIsInRyYWNrTmFtZSI6IlN5bnRoIiwiZnhBbmNob3IiOiJpZC1vci1pbmRleCIsImZ4SW5kZXgiOjAsImZ4R1VJRCI6IjM1RTUyOUQ0LTdFRTct + NDJEOS05MTM3LTNERUNDMkZFMjhBRiJ9fSx7ImlkIjoiYWQxNjg4MjEtZTc1Yy00ZDNlLWIwNGEtMjY1NTAyNjIzNzUxIiwibmFtZSI6IlJlYVN5bnRoIEF0dGFjayIs + InNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjEsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6e30sInRhcmdldCI6eyJ0cmFja0dVSUQiOiI4MDRDNEI0OS01NUZDLTQzNUMtOUI5My1GQkQ3NTQxQUEz + QjciLCJ0cmFja05hbWUiOiJTeW50aCIsImZ4QW5jaG9yIjoiaWQtb3ItaW5kZXgiLCJmeEluZGV4IjoxLCJmeEdVSUQiOiIxQjNEMzE3Ni0xNzAwLTRFQ0YtODExRC1C + ODM5MDBGNkVGNDgifSwiYWN0aXZhdGlvblR5cGUiOiJtb2RpZmllcnMiLCJtb2RpZmllckNvbmRpdGlvbjEiOnsicGFyYW1JbmRleCI6MSwiaXNPbiI6dHJ1ZX19LHsi + aWQiOiI1NjQ3YTU3My00ZjQ0LTQwNDMtYWMzNy05YWI2NmUzMjk0YjYiLCJuYW1lIjoiT3RoZXIgdm9sdW1lIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIs + Im51bWJlciI6MTQsImNoYXJhY3RlciI6MywiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrR1VJRCI6IjUw + MTRCQjNBLUU3NzctNDIxNi1CQjQzLTM3RENFNEEyNjc1QSIsInRyYWNrTmFtZSI6IsO2w7bDtsOkw6TDpMOfw5/DnyJ9fSx7ImlkIjoiMTAzNGNkYzQtYWRkZC00NTc5 + LWI5NTAtYzY3ZGM4MzhmMjcyIiwibmFtZSI6IlBsYXkvcGF1c2UiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY2hhbm5lbCI6MSwibnVtYmVyIjoxNCwi + Y2hhcmFjdGVyIjoxLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH0sIm1vZGUiOnsidHlwZSI6Mn0sInRhcmdldCI6 + eyJ0eXBlIjoxNiwidHJhY2tHVUlEIjoiODA0QzRCNDktNTVGQy00MzVDLTlCOTMtRkJENzU0MUFBM0I3IiwidHJhY2tOYW1lIjoiU3ludGgiLCJ0cmFuc3BvcnRBY3Rp + b24iOiJwbGF5UGF1c2UifX0seyJpZCI6Ijg3MmFkZTA2LWZkMWYtNDQ3Ni1hYzYyLTYwZTBiMzliYTA5ZCIsIm5hbWUiOiJTZWxlY3RlZCB0cmFjayIsInNvdXJjZSI6 + eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJudW1iZXIiOjE1LCJjaGFyYWN0ZXIiOjMsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fSwibW9kZSI6e30sInRhcmdldCI6eyJ0 + eXBlIjoxNCwidHJhY2tHVUlEIjoiODA0QzRCNDktNTVGQy00MzVDLTlCOTMtRkJENzU0MUFBM0I3IiwidHJhY2tOYW1lIjoiU3ludGgifX0seyJpZCI6ImQ1Y2FjOGYx + LTMyM2ItNDEwMy05MGY3LWE1YzMzNmFjNmQ1MiIsIm5hbWUiOiJTaGlmdCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjaGFubmVsIjozLCJudW1iZXIi + OjgsImNoYXJhY3RlciI6MSwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTZ9LCJtb2RlIjp7InR5cGUiOjJ9LCJ0YXJn + ZXQiOnsiZnhBbmNob3IiOiJpZC1vci1pbmRleCIsImZ4SW5kZXgiOjAsImZ4R1VJRCI6IjZCOTNBNkQzLUNCQzgtNDhCMi05MDMzLUFBM0UzQjI0NkREMyIsInBhcmFt + SW5kZXgiOjF9fSx7ImlkIjoiYTQxNTVhM2YtNmExZS00NDg0LWI5NjUtNjA0MmM5MDQ3YjhkIiwibmFtZSI6IlNwZWNpYWwiLCJzb3VyY2UiOnt9LCJtb2RlIjp7fSwi + dGFyZ2V0Ijp7fX1dLCJjb250cm9sbGVyTWFwcGluZ3MiOlt7ImlkIjoiMGMwMjkzNjMtNzFjNy00MTE1LWI4ZTAtNzI0NWFjMWI2ZDRiIiwibmFtZSI6IkVuY29kZXIg + MS8xIiwic291cmNlIjp7ImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIn19LHsiaWQiOiJlNTU0M2FkOC05ZTgxLTRi + YWYtYTgyZC0yYzI4MTQ0NWU3MDUiLCJuYW1lIjoiQnV0dG9uIDEvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0 + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiJ9fSx7ImlkIjoiMjc1MjFlZjAtNWNmOC00NWU5LTljOTAtNGQ0OTA2Y2Y2 + MzA0IiwibmFtZSI6IkVuY29kZXIgMS8yIiwic291cmNlIjp7Im51bWJlciI6MSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZp + cnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6ImRkMWE4MWQzLWQ2MDctNDMyOS05ZDkxLWE2Yjg4OGIwZmFhZiIsIm5hbWUiOiJCdXR0b24gMS8y + Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250 + cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxfX0seyJpZCI6IjU0YTA2NmUyLTQ4ZTMtNDY1My05ZmJiLWVmOTA3ZGIwOWQ4NyIs + Im5hbWUiOiJFbmNvZGVyIDEvMyIsInNvdXJjZSI6eyJudW1iZXIiOjIsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJiODJkNzc1Mi1kMjI5LTQ0OGYtYmQ0Mi1hZjYxYjQ1OWU0ODEiLCJuYW1lIjoiQnV0dG9uIDEvMyIsInNv + dXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjIsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVs + ZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Mn19LHsiaWQiOiJhM2MyN2MwMi1jNTJkLTQ3MWMtYjcwNi0yN2I2MmUyYTVkYmUiLCJuYW1l + IjoiRW5jb2RlciAxLzQiLCJzb3VyY2UiOnsibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNv + bnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiMWM4NmUxMzEtYTFiOS00ZTRjLTkxZDUtMThjYTI0MDRjYzg4IiwibmFtZSI6IkJ1dHRvbiAxLzQiLCJzb3VyY2Ui + OnsiY2hhbm5lbCI6MSwibnVtYmVyIjozLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjN9fSx7ImlkIjoiODI4ZDQ2NWEtOWJkMy00NDM2LTkxMzQtNDg4YTZmZWIxZmJmIiwibmFtZSI6IkVu + Y29kZXIgMi8xIiwic291cmNlIjp7Im51bWJlciI6NCwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9s + RWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjUwZjNkNTZkLTBmNjMtNDA4Ni05NTkyLWIzY2U5MTNkYjM4MSIsIm5hbWUiOiJCdXR0b24gMi8xIiwic291cmNlIjp7ImNo + YW5uZWwiOjEsIm51bWJlciI6NCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fX0seyJpZCI6IjcxOTBlMmE2LWUwN2MtNDkyMi05YmI0LWQ2ZDJiMDcyZDgwMSIsIm5hbWUiOiJFbmNvZGVy + IDIvMiIsInNvdXJjZSI6eyJudW1iZXIiOjUsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6NX19LHsiaWQiOiIwMzlmNzk5Yi01MzdmLTQyNzctYWJjMS0wM2M1MDY4MjcwODMiLCJuYW1lIjoiQnV0dG9uIDIvMiIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjUsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6NX19LHsiaWQiOiI0NWJiYmQ1ZC0zNGY3LTRhYjgtYjQ5Ni1lZWI5Y2Y2MDJhNzgiLCJuYW1lIjoiRW5jb2RlciAyLzMi + LCJzb3VyY2UiOnsibnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjZ9fSx7ImlkIjoiY2Q5NzM2MDMtYzliNy00YWIwLTk4MTctMjA1NTc2NTMxZWYxIiwibmFtZSI6IkJ1dHRvbiAyLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwi + bnVtYmVyIjo2LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjZ9fSx7ImlkIjoiZDI4NDllYzctYzg5NS00NGVlLWJmZmEtMzRlZWY1MGY5NzdjIiwibmFtZSI6IkVuY29kZXIgMi80Iiwic291 + cmNlIjp7Im51bWJlciI6NywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijo3 + fX0seyJpZCI6IjgwMThjMjU5LTZmNzYtNDEzMy05MDQ3LTY0MTA3ODE4OTZhZCIsIm5hbWUiOiJCdXR0b24gMi80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJl + ciI6NywiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250 + cm9sRWxlbWVudEluZGV4Ijo3fX0seyJpZCI6IjI1YjgzZWMyLTU0ZGEtNGMwYS04NDE1LTg4MGEzMGVmNmExZiIsIm5hbWUiOiJFbmNvZGVyIDMvMSIsInNvdXJjZSI6 + eyJudW1iZXIiOjgsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OH19LHsi + aWQiOiIxNjAxM2M0OC0yYTQ5LTQ2NzItYmYzYS05NWVhZTA1ZWYzYTciLCJuYW1lIjoiQnV0dG9uIDMvMSIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjgs + ImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVs + ZW1lbnRJbmRleCI6OH19LHsiaWQiOiI2NDBlYjJhNC03MzVkLTRkOTEtYWU1Yi02MzY3MWRmYWI1ZDciLCJuYW1lIjoiRW5jb2RlciAzLzIiLCJzb3VyY2UiOnsibnVt + YmVyIjo5LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9fSx7ImlkIjoi + MmUxODdhMWQtZmFkMC00N2U0LThiODktZDk2Yjk0NTAwMDMyIiwibmFtZSI6IkJ1dHRvbiAzLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo5LCJjaGFy + YWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjl9fSx7ImlkIjoiYzg2NWM1MDMtMjVmYy00YjgxLWJiYzgtMDczMTEwNWEyNjU2IiwibmFtZSI6IkVuY29kZXIgMy8zIiwic291cmNlIjp7Im51bWJlciI6 + MTAsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTB9fSx7ImlkIjoiZjdh + OTMwNWQtOTRhYi00ZmM5LTliNjQtYTkwOWU1NTkzNTRiIiwibmFtZSI6IkJ1dHRvbiAzLzMiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMCwiY2hhcmFj + dGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudElu + ZGV4IjoxMH19LHsiaWQiOiI3YTVmMTY4MC1mNWVkLTQ2M2ItOTc5ZC1iMzA5MTZlM2JiM2EiLCJuYW1lIjoiRW5jb2RlciAzLzQiLCJzb3VyY2UiOnsibnVtYmVyIjox + MSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxMX19LHsiaWQiOiI2Yjhh + ZDEzMS1iZWQ5LTRiYTctYTYwNC00MzVjZjA0MzZjOTEiLCJuYW1lIjoiQnV0dG9uIDMvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjExLCJjaGFyYWN0 + ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5k + ZXgiOjExfX0seyJpZCI6IjNiNWZjMWU4LTQyZGItNDU4MS05NDdlLTVkZmEzZDNjMGJmNCIsIm5hbWUiOiJFbmNvZGVyIDQvMSIsInNvdXJjZSI6eyJudW1iZXIiOjEy + LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEyfX0seyJpZCI6IjFjMDkx + NTgwLTk4NmYtNDJmZS04OTQyLTc1OWQ4MWRmMThiMiIsIm5hbWUiOiJCdXR0b24gNC8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTIsImNoYXJhY3Rl + ciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRl + eCI6MTJ9fSx7ImlkIjoiYTM4NDM1YzAtM2FjNC00MTc0LWIxYjEtNzgxZWZmNzk4OGM0IiwibmFtZSI6IkVuY29kZXIgNC8yIiwic291cmNlIjp7Im51bWJlciI6MTMs + ImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTN9fSx7ImlkIjoiOTE0NWI4 + YTctMDIxZS00NmU5LWJjMjQtNTg4YjViNzBlM2NkIiwibmFtZSI6IkJ1dHRvbiA0LzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxMywiY2hhcmFjdGVy + IjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoxM319LHsiaWQiOiI5NDA2MDM4ZS0zYzQ0LTQzMmQtOGVjYy0zZjQxNTQwYWUwYzgiLCJuYW1lIjoiRW5jb2RlciA0LzMiLCJzb3VyY2UiOnsibnVtYmVyIjoxNCwi + Y2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxNH19LHsiaWQiOiIyMzJkOTg5 + YS1lMGQ3LTQ3ZjktOTU4Ny02NjhhYzEyNTg0YTMiLCJuYW1lIjoiQnV0dG9uIDQvMyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjE0LCJjaGFyYWN0ZXIi + OjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgi + OjE0fX0seyJpZCI6IjU1YWEyM2IwLWI4MjAtNGViZS05YjEyLTlmZTQwODQyZWYyNyIsIm5hbWUiOiJFbmNvZGVyIDQvNCIsInNvdXJjZSI6eyJudW1iZXIiOjE1LCJj + aGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE1fX0seyJpZCI6IjY3NzJhOWUz + LTI4OGItNGY0Yi1hMGFlLTNiY2U4MzAwOWQ0ZCIsIm5hbWUiOiJCdXR0b24gNC80Iiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MTUsImNoYXJhY3RlciI6 + MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6 + MTV9fSx7ImlkIjoiMjdiZjIxNTItZTdmNC00NWU1LTliOTgtZGNhZTg5NjViOTFmIiwibmFtZSI6IkxlZnQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIi + OjgsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTZ9fSx7ImlkIjoiZWU5ZGY1OTAtNjFjYi00NzQ2LWEzMTMtNzczNTZlNThiMzVlIiwibmFtZSI6IlJpZ2h0IDEiLCJzb3VyY2UiOnsiY2hh + bm5lbCI6MywibnVtYmVyIjoxMSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUi + OiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxN319XSwiYWN0aXZlQ29udHJvbGxlcklkIjoibWlkaS1maWdodGVyLXR3aXN0ZXIiLCJwYXJhbWV0ZXJzIjp7 + IjEiOnsibmFtZSI6IlNoaWZ0In0sIjAiOnsidmFsdWUiOjEuMCwibmFtZSI6IlBlZGFsIn19fQ== + AAAQAAAA + > + FLOAT 1241 421 1436 1439 + FXID {6B93A6D3-CBC8-48B2-9033-AA3E3B246DD3} + PARM_TCP 0 + WAK 1 0 + BYPASS 0 0 0 + "" + ZG1jcu5e7f4AAAAAAAAAAMoAAAABAAAAAAAQAA== + /////wAAAAAAAAAAAAAAAAkAAAAMAAAAAQAAAP8/AAAAIAAAACAAAAAAAAAZAAAAQzpcUkVBUEVSXERhdGFcR00ucmVhYmFuawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAE1ham9yAA0AAAAxMDIwMzQwNTA2MDcAAQAAAAAAAAAAAAAAAAoAAAANAAAAAQAAAAAA + AAAAAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 19 47 739 1163 + FXID {6EB99711-918D-495C-BFB2-ABC8C5EE90A8} + WAK 0 0 + > + > + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAVwIAAAEAAAAAABAA + eyJ2ZXJzaW9uIjoiMi4wLjAiLCJpZCI6IjJBSWhXSHlCIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImRlZmF1bHRHcm91cCI6e30sIm1hcHBpbmdzIjpb + eyJpZCI6IjEwOWZlMjBhLTAzNWItNDUxZS1iMzYwLWU1ZDJjMzI4NDgxNSIsIm5hbWUiOiIyIiwic291cmNlIjp7InR5cGUiOjJ9LCJtb2RlIjp7Im1pblNvdXJjZVZh + bHVlIjowLjM3LCJtYXhTb3VyY2VWYWx1ZSI6MC41OSwibWF4VGFyZ2V0SnVtcCI6MC4zMywic2NhbGVNb2RlRW5hYmxlZCI6dHJ1ZX0sInRhcmdldCI6eyJ0eXBlIjoy + LCJ0cmFja0dVSUQiOiI4MDRDNEI0OS01NUZDLTQzNUMtOUI5My1GQkQ3NTQxQUEzQjciLCJ0cmFja05hbWUiOiJTeW50aCJ9fSx7ImlkIjoiZTU4YzA0YWYtNTA1MS00 + Mjg1LTlmN2QtZGJjMDhhOGU5MjljIiwibmFtZSI6IjMiLCJzb3VyY2UiOnsidHlwZSI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MCwiY29tbWFuZE5hbWUi + OiI0NCJ9fSx7ImlkIjoiMjFhNGZjYWItMmYyMy00NmViLWI0ZGEtMzllYThhNzM2ODU4IiwibmFtZSI6IjMiLCJzb3VyY2UiOnsibnVtYmVyIjoxfSwibW9kZSI6e30s + InRhcmdldCI6eyJ0eXBlIjoxMH19XX0= + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {963E0C00-D122-466A-AF4A-904643D63AE3} + WAK 0 0 + > + > + "" + dGdlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFQAAAAAAAAAAAAAAA== + 776t3g3wrd77dPc97FG4PfmPVD3X+SA8tmeaPSdEGT7NLxs8AAAAAAAAAAC9N4Y1AACAP703hjXxQzY/AAAAAAAAAAAAAAAALBYLPwAAAAAAAAAA + AHN0b2NrIC0gc25hcmUgZ2F0ZQAAAAAA + > + PRESETNAME "stock - snare gate" + FLOATPOS 1192 390 1013 739 + FXID {35E529D4-7EE7-42D9-9137-3DECC2FE28AF} + PARM_TCP 1 + WAK 0 0 + BYPASS 0 0 0 + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd4AAAAAF7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {1B3D3176-1700-4ECF-811D-B83900F6EF48} + PARM_TCP 0 + WAK 0 0 + > + > + + + +> diff --git a/plugin-reaper-realearn/resources/test-projects/selected.rpp b/plugin-reaper-realearn/resources/test-projects/selected.rpp new file mode 100644 index 0000000..0b420b7 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/selected.rpp @@ -0,0 +1,303 @@ + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 0 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 4 0 + SELECTION2 4 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_FX 1 + MASTER_SEL 0 + + + MARKER 1 0 "" 1 0 1 R {9E541D61-8712-40B9-885B-AEC82371E278} + MARKER 1 4 "" 1 0 1 R + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAA0SMAAAEAAAAAABAA + eyJpZCI6Ilk1RzFwVzZnIiwic2VuZEZlZWRiYWNrT25seUlmQXJtZWQiOnRydWUsImNvbnRyb2xEZXZpY2VJZCI6IjEwIiwiZmVlZGJhY2tEZXZpY2VJZCI6IjExIiwi + bWFwcGluZ3MiOlt7ImlkIjoiOWFiMDk2NWYtMjc2ZS00MWRjLTljZTMtZjcwZjNmNzY2YWZkIiwibmFtZSI6IjEiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFs + In0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6MH19LHsiaWQiOiI5NDM5M2Q0Yi04NTFlLTQwMDYtOWE2Yi01MGZlMDg0YzUwOWUiLCJu + YW1lIjoiMiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0 + cmFja0luZGV4IjoxfX0seyJpZCI6IjUxZDM4NzU2LTQyYmYtNDkxNC1iZDIzLWEzZTE0NzYyNWMwZCIsIm5hbWUiOiIzIiwic291cmNlIjp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjJ9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjJ9fSx7ImlkIjoiOWQ3ZTljMTMtYTBi + NC00ODYxLWEzMTQtYTJlNTk5ZDRkMzlmIiwibmFtZSI6IjQiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M30sIm1v + ZGUiOnt9LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6M319LHsiaWQiOiIxZmJiNjFiMi04MDBiLTQ4NTUtYTdiZS0zODVjODdhNTE4ZGYiLCJuYW1lIjoi + NSIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijo0fSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0lu + ZGV4Ijo0fX0seyJpZCI6IjQxMDUyZjQxLTk2MTktNGM5ZC1iNjEwLWUwZDZhZDE1MTg0OCIsIm5hbWUiOiI2Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIs + ImNvbnRyb2xFbGVtZW50SW5kZXgiOjV9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjV9fSx7ImlkIjoiOThjN2RlOGMtZWEwYS00NDA5 + LTk2NDktZGE3YzI3Njk3YWEzIiwibmFtZSI6IjciLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6Nn19LHsiaWQiOiIzZmZmMzI4NS1kZjU1LTQwMjgtOTYyYy1mZTcyY2QxMDAyNmYiLCJuYW1lIjoiOCIsInNv + dXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijo3fSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0luZGV4Ijo3 + fX0seyJpZCI6IjlmYWIwYjk3LWJkNjUtNDRkNy1iNTBiLTllMzllMTViNzhjYiIsIm5hbWUiOiI5Iiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRy + b2xFbGVtZW50SW5kZXgiOjh9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjh9fSx7ImlkIjoiNzdmYjNhNTAtZmM1ZC00ZjEwLTlkMDAt + YTMyNzU2OWQ5MmEyIiwibmFtZSI6IjEwIiwic291cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjl9LCJtb2RlIjp7fSwidGFy + Z2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjl9fSx7ImlkIjoiNjBlYTEwZDktYjcxYi00NGFhLTgzN2QtOGIxZmI3NWE4MWJjIiwibmFtZSI6IjExIiwic291cmNl + Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0luZGV4IjoxMH19 + LHsiaWQiOiI0NmYyMTJhYS01YzBkLTQ2YmUtYTc1OS02MzdiOGFiMTE1NDIiLCJuYW1lIjoiMTIiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6MTF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjExfX0seyJpZCI6IjUzYzhiNjgyLWJlODUtNGNmOC05Njk5 + LWJhZDUzYmJlZWFmYSIsIm5hbWUiOiIxMyIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn0sIm1vZGUiOnt9LCJ0 + YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6MTJ9fSx7ImlkIjoiYzJmZDg3NDEtY2Q3Zi00MWI0LWE5ZjUtYTk5Y2Y4OWE3ZGQ0IiwibmFtZSI6IjE0Iiwic291 + cmNlIjp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfSwibW9kZSI6e30sInRhcmdldCI6eyJ0eXBlIjoyLCJ0cmFja0luZGV4Ijox + M319LHsiaWQiOiJlZTM0OGVkYy1lMTNmLTQyMjctODY0Zi0wOTU5MWMxYTRjZWMiLCJuYW1lIjoiMTUiLCJzb3VyY2UiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6MTR9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7InR5cGUiOjIsInRyYWNrSW5kZXgiOjE0fX0seyJpZCI6IjhhODdkNTU1LTJlNTgtNDY2Mi1h + ZTZlLTc4ZDNjOGY0YmE0ZSIsIm5hbWUiOiIxNiIsInNvdXJjZSI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX0sIm1vZGUiOnt9 + LCJ0YXJnZXQiOnsidHlwZSI6MiwidHJhY2tJbmRleCI6MTV9fV0sImNvbnRyb2xsZXJNYXBwaW5ncyI6W3siaWQiOiIwYzAyOTM2My03MWM3LTQxMTUtYjhlMC03MjQ1 + YWMxYjZkNGIiLCJuYW1lIjoiRW5jb2RlciAxLzEiLCJzb3VyY2UiOnsiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + fX0seyJpZCI6ImU1NTQzYWQ4LTllODEtNGJhZi1hODJkLTJjMjgxNDQ1ZTcwNSIsIm5hbWUiOiJCdXR0b24gMS8xIiwic291cmNlIjp7ImNoYW5uZWwiOjEsImNoYXJh + Y3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIn19LHsiaWQiOiIyNzUyMWVm + MC01Y2Y4LTQ1ZTktOWM5MC00ZDQ5MDZjZjYzMDQiLCJuYW1lIjoiRW5jb2RlciAxLzIiLCJzb3VyY2UiOnsibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7 + fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiZGQxYTgxZDMtZDYwNy00MzI5LTlkOTEtYTZiODg4 + YjBmYWFmIiwibmFtZSI6IkJ1dHRvbiAxLzIiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjoxLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7 + ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjF9fSx7ImlkIjoiNTRhMDY2ZTItNDhl + My00NjUzLTlmYmItZWY5MDdkYjA5ZDg3IiwibmFtZSI6IkVuY29kZXIgMS8zIiwic291cmNlIjp7Im51bWJlciI6MiwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRh + cmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6ImI4MmQ3NzUyLWQyMjktNDQ4Zi1iZDQyLWFmNjFiNDU5ZTQ4 + MSIsIm5hbWUiOiJCdXR0b24gMS8zIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6MiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRl + Z29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoyfX0seyJpZCI6ImEzYzI3YzAyLWM1MmQtNDcx + Yy1iNzA2LTI3YjYyZTJhNWRiZSIsIm5hbWUiOiJFbmNvZGVyIDEvNCIsInNvdXJjZSI6eyJudW1iZXIiOjMsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQi + OnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiIxYzg2ZTEzMS1hMWI5LTRlNGMtOTFkNS0xOGNhMjQwNGNjODgiLCJu + YW1lIjoiQnV0dG9uIDEvNCIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjMsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnki + OiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6M319LHsiaWQiOiI4MjhkNDY1YS05YmQzLTQ0MzYtOTEz + NC00ODhhNmZlYjFmYmYiLCJuYW1lIjoiRW5jb2RlciAyLzEiLCJzb3VyY2UiOnsibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNh + dGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNTBmM2Q1NmQtMGY2My00MDg2LTk1OTItYjNjZTkxM2RiMzgxIiwibmFtZSI6 + IkJ1dHRvbiAyLzEiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo0LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjR9fSx7ImlkIjoiNzE5MGUyYTYtZTA3Yy00OTIyLTliYjQtZDZk + MmIwNzJkODAxIiwibmFtZSI6IkVuY29kZXIgMi8yIiwic291cmNlIjp7Im51bWJlciI6NSwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29y + eSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjAzOWY3OTliLTUzN2YtNDI3Ny1hYmMxLTAzYzUwNjgyNzA4MyIsIm5hbWUiOiJCdXR0 + b24gMi8yIiwic291cmNlIjp7ImNoYW5uZWwiOjEsIm51bWJlciI6NSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwi + LCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo1fX0seyJpZCI6IjQ1YmJiZDVkLTM0ZjctNGFiOC1iNDk2LWVlYjljZjYw + MmE3OCIsIm5hbWUiOiJFbmNvZGVyIDIvMyIsInNvdXJjZSI6eyJudW1iZXIiOjYsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2 + aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJjZDk3MzYwMy1jOWI3LTRhYjAtOTgxNy0yMDU1NzY1MzFlZjEiLCJuYW1lIjoiQnV0dG9uIDIv + MyIsInNvdXJjZSI6eyJjaGFubmVsIjoxLCJudW1iZXIiOjYsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6Nn19LHsiaWQiOiJkMjg0OWVjNy1jODk1LTQ0ZWUtYmZmYS0zNGVlZjUwZjk3N2Mi + LCJuYW1lIjoiRW5jb2RlciAyLzQiLCJzb3VyY2UiOnsibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVh + bCIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiODAxOGMyNTktNmY3Ni00MTMzLTkwNDctNjQxMDc4MTg5NmFkIiwibmFtZSI6IkJ1dHRvbiAyLzQiLCJz + b3VyY2UiOnsiY2hhbm5lbCI6MSwibnVtYmVyIjo3LCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xF + bGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjd9fSx7ImlkIjoiMjViODNlYzItNTRkYS00YzBhLTg0MTUtODgwYTMwZWY2YTFmIiwibmFt + ZSI6IkVuY29kZXIgMy8xIiwic291cmNlIjp7Im51bWJlciI6OCwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJj + b250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjE2MDEzYzQ4LTJhNDktNDY3Mi1iZjNhLTk1ZWFlMDVlZjNhNyIsIm5hbWUiOiJCdXR0b24gMy8xIiwic291cmNl + Ijp7ImNoYW5uZWwiOjEsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVu + dFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4Ijo4fX0seyJpZCI6IjY0MGViMmE0LTczNWQtNGQ5MS1hZTViLTYzNjcxZGZhYjVkNyIsIm5hbWUiOiJF + bmNvZGVyIDMvMiIsInNvdXJjZSI6eyJudW1iZXIiOjksImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJv + bEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiIyZTE4N2ExZC1mYWQwLTQ3ZTQtOGI4OS1kOTZiOTQ1MDAwMzIiLCJuYW1lIjoiQnV0dG9uIDMvMiIsInNvdXJjZSI6eyJj + aGFubmVsIjoxLCJudW1iZXIiOjksImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBl + IjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6OX19LHsiaWQiOiJjODY1YzUwMy0yNWZjLTRiODEtYmJjOC0wNzMxMTA1YTI2NTYiLCJuYW1lIjoiRW5jb2Rl + ciAzLzMiLCJzb3VyY2UiOnsibnVtYmVyIjoxMCwiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudEluZGV4IjoxMH19LHsiaWQiOiJmN2E5MzA1ZC05NGFiLTRmYzktOWI2NC1hOTA5ZTU1OTM1NGIiLCJuYW1lIjoiQnV0dG9uIDMvMyIsInNvdXJjZSI6eyJjaGFu + bmVsIjoxLCJudW1iZXIiOjEwLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEwfX0seyJpZCI6IjdhNWYxNjgwLWY1ZWQtNDYzYi05NzlkLWIzMDkxNmUzYmIzYSIsIm5hbWUiOiJFbmNvZGVy + IDMvNCIsInNvdXJjZSI6eyJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVt + ZW50SW5kZXgiOjExfX0seyJpZCI6IjZiOGFkMTMxLWJlZDktNGJhNy1hNjA0LTQzNWNmMDQzNmM5MSIsIm5hbWUiOiJCdXR0b24gMy80Iiwic291cmNlIjp7ImNoYW5u + ZWwiOjEsIm51bWJlciI6MTEsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoi + YnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTF9fSx7ImlkIjoiM2I1ZmMxZTgtNDJkYi00NTgxLTk0N2UtNWRmYTNkM2MwYmY0IiwibmFtZSI6IkVuY29kZXIg + NC8xIiwic291cmNlIjp7Im51bWJlciI6MTIsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1l + bnRJbmRleCI6MTJ9fSx7ImlkIjoiMWMwOTE1ODAtOTg2Zi00MmZlLTg5NDItNzU5ZDgxZGYxOGIyIiwibmFtZSI6IkJ1dHRvbiA0LzEiLCJzb3VyY2UiOnsiY2hhbm5l + bCI6MSwibnVtYmVyIjoxMiwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJi + dXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxMn19LHsiaWQiOiJhMzg0MzVjMC0zYWM0LTQxNzQtYjFiMS03ODFlZmY3OTg4YzQiLCJuYW1lIjoiRW5jb2RlciA0 + LzIiLCJzb3VyY2UiOnsibnVtYmVyIjoxMywiY2hhcmFjdGVyIjozfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVu + dEluZGV4IjoxM319LHsiaWQiOiI5MTQ1YjhhNy0wMjFlLTQ2ZTktYmMyNC01ODhiNWI3MGUzY2QiLCJuYW1lIjoiQnV0dG9uIDQvMiIsInNvdXJjZSI6eyJjaGFubmVs + IjoxLCJudW1iZXIiOjEzLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1 + dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjEzfX0seyJpZCI6Ijk0MDYwMzhlLTNjNDQtNDMyZC04ZWNjLTNmNDE1NDBhZTBjOCIsIm5hbWUiOiJFbmNvZGVyIDQv + MyIsInNvdXJjZSI6eyJudW1iZXIiOjE0LCJjaGFyYWN0ZXIiOjN9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5IjoidmlydHVhbCIsImNvbnRyb2xFbGVtZW50 + SW5kZXgiOjE0fX0seyJpZCI6IjIzMmQ5ODlhLWUwZDctNDdmOS05NTg3LTY2OGFjMTI1ODRhMyIsIm5hbWUiOiJCdXR0b24gNC8zIiwic291cmNlIjp7ImNoYW5uZWwi + OjEsIm51bWJlciI6MTQsImNoYXJhY3RlciI6MX0sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6MTR9fSx7ImlkIjoiNTVhYTIzYjAtYjgyMC00ZWJlLTliMTItOWZlNDA4NDJlZjI3IiwibmFtZSI6IkVuY29kZXIgNC80 + Iiwic291cmNlIjp7Im51bWJlciI6MTUsImNoYXJhY3RlciI6M30sIm1vZGUiOnt9LCJ0YXJnZXQiOnsiY2F0ZWdvcnkiOiJ2aXJ0dWFsIiwiY29udHJvbEVsZW1lbnRJ + bmRleCI6MTV9fSx7ImlkIjoiNjc3MmE5ZTMtMjg4Yi00ZjRiLWEwYWUtM2JjZTgzMDA5ZDRkIiwibmFtZSI6IkJ1dHRvbiA0LzQiLCJzb3VyY2UiOnsiY2hhbm5lbCI6 + MSwibnVtYmVyIjoxNSwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0 + b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNX19LHsiaWQiOiIyN2JmMjE1Mi1lN2Y0LTQ1ZTUtOWI5OC1kY2FlODk2NWI5MWYiLCJuYW1lIjoiTGVmdCAxIiwic291 + cmNlIjp7ImNoYW5uZWwiOjMsIm51bWJlciI6OCwiY2hhcmFjdGVyIjoxfSwibW9kZSI6e30sInRhcmdldCI6eyJjYXRlZ29yeSI6InZpcnR1YWwiLCJjb250cm9sRWxl + bWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoxNn19LHsiaWQiOiJlZTlkZjU5MC02MWNiLTQ3NDYtYTMxMy03NzM1NmU1OGIzNWUiLCJuYW1l + IjoiUmlnaHQgMSIsInNvdXJjZSI6eyJjaGFubmVsIjozLCJudW1iZXIiOjExLCJjaGFyYWN0ZXIiOjF9LCJtb2RlIjp7fSwidGFyZ2V0Ijp7ImNhdGVnb3J5Ijoidmly + dHVhbCIsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOjE3fX1dLCJhY3RpdmVDb250cm9sbGVySWQiOiJtaWRpLWZpZ2h0 + ZXItdHdpc3RlciIsImFjdGl2ZU1haW5QcmVzZXRJZCI6InRyYWNrLXZvbHVtZXMifQ== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {1D96E519-8ED1-41BA-9560-EF61506A592E} + WAK 0 0 + > + + > + > + "" + eXNlcu9e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAABAAAAAAAAAAAAAEAA= + 776t3g3wrd6mm8Q7F7fROgAAAAAAAAAAAAAAAM5NAD/pZ4g9AAAAAAAAAD8AAIA/AACAPwAAAD8AAAAAAAAAAA== + AAAQAAAA + > + FLOATPOS 0 0 0 0 + FXID {F1749ED8-DFD6-40FA-8352-A206C74398A3} + WAK 0 0 + > + > +> diff --git a/plugin-reaper-realearn/resources/test-projects/target-based-conditional-activation-auto-load-alternative.RPP b/plugin-reaper-realearn/resources/test-projects/target-based-conditional-activation-auto-load-alternative.RPP new file mode 100644 index 0000000..cb4f838 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/target-based-conditional-activation-auto-load-alternative.RPP @@ -0,0 +1,247 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 2 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0.5 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1.42145870954057 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAAB4DAAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS45IiwiaWQiOiJzSERmM2NzZCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoia2V5Ym9hcmQiLCJkZWZhdWx0R3JvdXAiOnt9LCJncm91cHMiOlt7ImlkIjoiVE41MklWdGtFLXVodHNx + Vy1vVFJEIiwibmFtZSI6IlJlYUVRIiwiYWN0aXZhdGlvblR5cGUiOiJ0YXJnZXQtdmFsdWUiLCJlZWxDb25kaXRpb24iOiJ5ID4gMCIsIm1hcHBpbmdLZXkiOiIxOFY3 + dVVzcHY3UEljbGVwWlpaSl8ifSx7ImlkIjoiX3pQQkFwZldIZklhcExweEFldFpHIiwibmFtZSI6IlJlYUNvbXAiLCJhY3RpdmF0aW9uVHlwZSI6InRhcmdldC12YWx1 + ZSIsImVlbENvbmRpdGlvbiI6InkgPiAwIiwibWFwcGluZ0tleSI6Il9hNG9UNF9LZjR4d3hSQ0JWX1AtQyJ9XSwiZGVmYXVsdENvbnRyb2xsZXJHcm91cCI6e30sIm1h + cHBpbmdzIjpbeyJpZCI6IjE4Vjd1VXNwdjdQSWNsZXBaWlpKXyIsIm5hbWUiOiJSZWFFUSBmb2N1c2VkIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1Jl + Z2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjB9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJt + YXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjEyLCJ0cmFja0dVSUQiOiIyOTc1NEI3Ri1GNDVCLTYxNEUtQjdCQy01QzhDRjExNTRCQzYiLCJmeEFuY2hv + ciI6ImlkIiwiZnhHVUlEIjoiOTNDMjA0MTEtQzMxMy03QjQwLTlDQjctMENFREMzMjNERkY5IiwiZW5hYmxlT25seUlmRnhIYXNGb2N1cyI6dHJ1ZSwidXNlU2VsZWN0 + aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJwYXJhbUluZGV4IjoxMywic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX0sImNvbnRyb2xJc0VuYWJsZWQiOmZhbHNlLCJmZWVkYmFja0lzRW5hYmxlZCI6ZmFs + c2V9LHsiaWQiOiJfYTRvVDRfS2Y0eHd4UkNCVl9QLUMiLCJuYW1lIjoiUmVhQ29tcCBmb2N1c2VkIiwic291cmNlIjp7ImNhdGVnb3J5IjoibmV2ZXIiLCJpc1JlZ2lz + dGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5kZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9fSwibW9kZSI6eyJtYXhT + dGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoxMiwidHJhY2tHVUlEIjoiNkM4NUM2MDktQzNC + Ri1DODRFLUIxQTctMzY1N0EzQUNENTJBIiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IkUxRUQzMTUwLUU4MkQtQTE0RS1CN0M2LTRBRjU5REREODY2RiIsImVuYWJs + ZU9ubHlJZkZ4SGFzRm9jdXMiOnRydWUsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1JbmRleCI6MjEsInNl + ZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNl + QWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19LCJjb250cm9sSXNFbmFi + bGVkIjpmYWxzZSwiZmVlZGJhY2tJc0VuYWJsZWQiOmZhbHNlfSx7ImlkIjoiTnpaTUJ3Rkw2UkgtNGd3UXJETWxIIiwibmFtZSI6IjMiLCJncm91cElkIjoiX3pQQkFw + ZldIZklhcExweEFldFpHIiwic291cmNlIjp7ImNhdGVnb3J5Ijoia2V5Ym9hcmQiLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwib3NjQXJnSW5k + ZXgiOjAsImtleXN0cm9rZSI6eyJtb2RpZmllcnMiOjEsImtleSI6NjV9fSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJ0cmFja0dVSUQiOiI2Qzg1QzYwOS1DM0JGLUM4NEUtQjFBNy0zNjU3QTNBQ0Q1MkEiLCJmeEFuY2hvciI6ImlkIiwiZnhHVUlE + IjoiRTFFRDMxNTAtRTgyRC1BMTRFLUI3QzYtNEFGNTlEREQ4NjZGIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJz + ZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlkifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Im9qeGkt + TUVFS3FiU0ppMUVoVzhsRSIsIm5hbWUiOiI0IiwiZ3JvdXBJZCI6IlRONTJJVnRrRS11aHRzcVctb1RSRCIsInNvdXJjZSI6eyJjYXRlZ29yeSI6ImtleWJvYXJkIiwi + aXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsIm9zY0FyZ0luZGV4IjowLCJrZXlzdHJva2UiOnsibW9kaWZpZXJzIjoxLCJrZXkiOjY1fX0sIm1vZGUi + OnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHJhY2tHVUlEIjoiMjk3NTRCN0YtRjQ1Qi02 + MTRFLUI3QkMtNUM4Q0YxMTU0QkM2IiwiZnhBbmNob3IiOiJpZCIsImZ4R1VJRCI6IjkzQzIwNDExLUMzMTMtN0I0MC05Q0I3LTBDRURDMzIzREZGOSIsInVzZVNlbGVj + dGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwicGFyYW1JbmRleCI6Miwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVj + dCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJZ + In0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwicGFyYW1ldGVycyI6eyIwIjp7InZhbHVlIjowLjg1M319LCJpbnN0YW5jZUZ4 + Ijp7ImFkZHJlc3MiOiJGb2N1c2VkIn19 + AFByb2dyYW0gMQAQAAAA + > + FLOAT 587 122 752 675 + FXID {8D09CDA0-C584-8E49-8FA2-1A5C1AF9CE17} + WAK 0 0 + > + > + "" + cWVlcu5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAArAAAAAEAAAAAABAA + IQAAAAQAAAAAAAAAAQAAAAAAAAAAAFlAAAAAAAAA8D8AAAAAAAAAAAEIAAAAAQAAAAAAAAAAwHJAAAAAAAAA8D8AAAAAAAAAQAEIAAAAAQAAAAAAAAAAQI9AAAAAAAAA + 8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAiLNAAAAAAAAA8D8AAAAAAAAAQAEBAAAAAQAAAAAAAAAAAPA/AAAAAEACAABrAQAAAgAAAA== + AAAQAAAA + > + FLOAT 598 209 576 419 + FXID {93C20411-C313-7B40-9CB7-0CEDC323DFF9} + WAK 0 0 + > + > + "" + bWNlcu9e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAFwAAAAAAAAAAAAQAA== + 776t3g3wrd4AAAAAAAAAAHv48jsPfmI8AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAnNEHMwrRYD8AAAAAzcxMPQAAAAAAAAAAAAAAAAAAgD4AAAAAAAAAAAAAAAA= + AAAQAAAA + > + FLOAT 24 86 567 397 + FXID {E1ED3150-E82D-A14E-B7C6-4AF59DDD866F} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/test-724-visibility-change-ignore-bug.RPP b/plugin-reaper-realearn/resources/test-projects/test-724-visibility-change-ignore-bug.RPP new file mode 100644 index 0000000..f620760 --- /dev/null +++ b/plugin-reaper-realearn/resources/test-projects/test-724-visibility-change-ignore-bug.RPP @@ -0,0 +1,141 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 600 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 -1 -1 -1 0 0 0 -1 -1 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAADVAgAAAQAAAAAAEAA= + eyJ2ZXJzaW9uIjoiMi4xNC4wLXByZS44IiwiaWQiOiI3bWVmV2ZxMCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJZkJhY2tncm91bmRQ + cm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMCIsImZlZWRiYWNrRGV2aWNlSWQiOiIwIiwiZGVmYXVsdEdyb3VwIjp7fSwiZGVmYXVsdENvbnRyb2xs + ZXJHcm91cCI6e30sIm1hcHBpbmdzIjpbeyJpZCI6ImZpblJJNXBleHE5WFBHQTdGbTZkdiIsIm5hbWUiOiIxIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAs + Im51bWJlciI6MzIsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1p + blN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjQsImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxz + ZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxh + eSI6dHJ1ZSwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWSJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQi + OiJMYXN0TG9hZGVkIn19fV0sImluc3RhbmNlRngiOnsiYWRkcmVzcyI6IkZvY3VzZWQifX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 611 168 752 675 + FXID {86233C57-BF96-2D40-A2F2-A40503C1AA64} + WAK 0 0 + > + > + +> diff --git a/plugin-reaper-realearn/resources/test-projects/untitled-midi-item--e9OzhLb.mid b/plugin-reaper-realearn/resources/test-projects/untitled-midi-item--e9OzhLb.mid new file mode 100644 index 0000000..ed1c4e4 Binary files /dev/null and b/plugin-reaper-realearn/resources/test-projects/untitled-midi-item--e9OzhLb.mid differ diff --git a/plugin-reaper-realearn/rustfmt.toml b/plugin-reaper-realearn/rustfmt.toml new file mode 100644 index 0000000..2e5b514 --- /dev/null +++ b/plugin-reaper-realearn/rustfmt.toml @@ -0,0 +1,4 @@ +max_width = 100 +#comment_width = 100 +#wrap_comments = true +#version = "Two" \ No newline at end of file diff --git a/plugin-reaper-realearn/rx-util/Cargo.toml b/plugin-reaper-realearn/rx-util/Cargo.toml new file mode 100644 index 0000000..242788f --- /dev/null +++ b/plugin-reaper-realearn/rx-util/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rx-util" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +rxrust.workspace = true + + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/rx-util/src/invocation_mock.rs b/plugin-reaper-realearn/rx-util/src/invocation_mock.rs new file mode 100644 index 0000000..1393374 --- /dev/null +++ b/plugin-reaper-realearn/rx-util/src/invocation_mock.rs @@ -0,0 +1,58 @@ +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +/// A simple mock which counts the number of invocations and remembers the last argument. +pub struct InvocationMock { + count: Cell, + last_arg: RefCell>, +} + +impl InvocationMock { + pub fn invoke(&self, arg: O) { + self.count.replace(self.count.get() + 1); + self.last_arg.replace(Some(arg)); + } + + /// Returns how many times `invoke()` has been called. + pub fn invocation_count(&self) -> u32 { + self.count.get() + } + + /// Returns a copy of the last argument passed to `invoke()`. + /// + /// # Panics + /// + /// Panics if there was no invocation at all. + pub fn last_arg(&self) -> O { + self.last_arg + .borrow() + .clone() + .expect("There were no invocations") + } +} + +/// Executes the given closure `op`, passing it a shared invocation mock. +/// +/// The `op` closure can take ownership of the invocation mock and pass it to another 'static +/// closure, which is supposed to call the invocation mock. It also returns a pointer to the same +/// mock, which can be used later to check the number of invocations and the last passed argument. +pub fn observe_invocations( + op: impl FnOnce(Rc>) -> R, +) -> (Rc>, R) { + let (mock_one, mock_two) = create_invocation_mock(); + (mock_one, op(mock_two)) +} + +/// Creates two pointers referring to the same invocation mock. +/// +/// This is useful for testing 'static closures, that is, closures which need captured references to +/// be 'static. Which makes tests like "this closure did run n times" non-trivial. So instead of +/// using references, we can use shared pointers. +pub fn create_invocation_mock() -> (Rc>, Rc>) { + let mock = InvocationMock { + count: Cell::new(0), + last_arg: RefCell::new(None), + }; + let shared_mock = Rc::new(mock); + (shared_mock.clone(), shared_mock) +} diff --git a/plugin-reaper-realearn/rx-util/src/lib.rs b/plugin-reaper-realearn/rx-util/src/lib.rs new file mode 100644 index 0000000..38e9f20 --- /dev/null +++ b/plugin-reaper-realearn/rx-util/src/lib.rs @@ -0,0 +1,5 @@ +mod property; +pub use property::*; + +mod invocation_mock; +pub use invocation_mock::*; diff --git a/plugin-reaper-realearn/rx-util/src/property.rs b/plugin-reaper-realearn/rx-util/src/property.rs new file mode 100644 index 0000000..26cfc7d --- /dev/null +++ b/plugin-reaper-realearn/rx-util/src/property.rs @@ -0,0 +1,547 @@ +use rxrust::prelude::*; +use std::fmt; +use std::marker::PhantomData; + +/// The lifetime parameter determines the scope of the subscribe closure, which is relevant when +/// capturing references. +/// +/// In many real-world cases you should choose 'static. But check the unit tests below. If you use +/// 'static in them, the closure would require all of its captured references to be 'static. In that +/// case we would be forced to use shared ownership (e.g. `Rc`) instead of &mut references to do +/// the test. +pub type LocalProp<'a, T, I, N, N2> = + Prop>, LocalPropSubject<'a, T>, N, N2>; +pub type LocalPropSubject<'a, T> = LocalSubject<'a, T, ()>; + +pub type SharedProp = + Prop>, SharedPropSubject, N, N2>; +pub type SharedPropSubject = SharedSubject; + +/// A reactive property which has the following characteristics: +/// +/// - It can be initialized with a transformer, which is an fn that transforms the value passed to +/// the setter before actually setting the value. It's good for restricting the value range of +/// that property. It's not good for maintaining object-wide invariants because transformers which +/// enclose over surrounding state are not advisable and therefore currently disabled by taking fn +/// only. +/// - It's cloneable if its value is cloneable (cloning it clones the value and the transformer, not +/// the change listeners) +/// - Equality operators are based just on the value, not on transformers and listeners +/// +/// # Type parameters +/// +/// - `T`: value type +/// - `I`: initiator type +/// - `S`: subject type +/// - `N`: notifier (not a function pointer because the notifier is usually everywhere the same in +/// one application, so we save memory by attaching it to the type instead of the instance) +pub struct Prop +where + // Incomparable values don't make sense because change notification is + // what properties are all about! + T: PartialEq + Clone, + I: Copy, + S: Observer, Err = ()> + Default, + S2: Observer + Default, + N: Notifier, Subject = S>, + N2: Notifier, +{ + value: T, + subject: S, + value_subject: S2, + transformer: fn(T) -> T, + p: PhantomData<(I, N, N2)>, +} + +pub trait Notifier { + type T; + type Subject: Observer; + + fn notify(subject: &mut Self::Subject, value: &Self::T); +} + +pub struct LocalSyncNotifier<'a, T>(PhantomData<&'a T>); + +impl<'a, T> Notifier for LocalSyncNotifier<'a, T> +where + T: Clone, +{ + type T = T; + type Subject = LocalPropSubject<'a, T>; + + fn notify(subject: &mut Self::Subject, value: &Self::T) { + subject.next(value.clone()) + } +} + +impl Prop +where + T: PartialEq + Clone, + I: Copy, + S: Observer, Err = ()> + Default, + S2: Observer + Default, + N: Notifier, Subject = S>, + N2: Notifier, +{ + /// Creates the property with an initial value and identity transformer. + pub fn new(initial_value: T) -> Self { + Self { + value: initial_value, + subject: Default::default(), + value_subject: Default::default(), + transformer: |v| v, + p: PhantomData, + } + } + + /// Creates the property with an initial value and a custom transformer. The transformer is not + /// applied to the initial value. + pub fn new_with_transformer(initial_value: T, transformer: fn(T) -> T) -> Self { + Self { + value: initial_value, + subject: Default::default(), + value_subject: Default::default(), + transformer, + p: PhantomData, + } + } + + /// Returns a copy of the current value of this property. + pub fn get(&self) -> T + where + T: Copy, + { + self.value + } + + /// Returns the current value of this property. + pub const fn get_ref(&self) -> &T { + &self.value + } + + /// Sets this property to the given value. If a transformer has been defined, the given value + /// might be changed into another one before. Observers are notified only if the given value + /// is different from the current value. + pub fn set(&mut self, value: T) { + self.internal_set(value, None); + } + + /// Sets this property to the given value using the given initiator. + pub fn set_with_initiator(&mut self, value: T, initiator: Option) { + self.internal_set(value, initiator); + } + + fn internal_set(&mut self, value: T, initiator: Option) { + let transformed_value = (self.transformer)(value); + if transformed_value == self.value { + return; + } + self.value = transformed_value; + self.notify(initiator); + } + + fn notify(&mut self, initiator: Option) { + N::notify(&mut self.subject, &initiator); + N2::notify(&mut self.value_subject, &self.value); + } + + pub fn set_without_notification(&mut self, value: T) { + let transformed_value = (self.transformer)(value); + if transformed_value == self.value { + return; + } + self.value = transformed_value; + } + + pub fn set_with_optional_notification(&mut self, value: T, with_notification: bool) { + if with_notification { + self.set(value); + } else { + self.set_without_notification(value); + } + } + + pub fn set_with_optional_notification_and_initiator( + &mut self, + value: T, + with_notification: bool, + initiator: Option, + ) { + if with_notification { + self.set_with_initiator(value, initiator); + } else { + self.set_without_notification(value); + } + } + + /// Like `set` but returns old value. + pub fn replace(&mut self, value: T) -> T { + let old_value = self.value.clone(); + self.set(value); + old_value + } + + /// Sets the value of this property to the value of the given one, invoking listeners. + /// + /// Consumes the given property. + pub fn apply_from(&mut self, other: Self) { + self.set(other.value) + } + + /// Like `set()`, but lets you use the previous value for calculating the new one. + pub fn set_with(&mut self, f: impl Fn(&T) -> T) { + let value = f(&self.value); + self.set(value); + } + + /// Modifies the value in place and notifies listeners. + pub fn mut_in_place(&mut self, f: impl Fn(&mut T)) { + f(&mut self.value); + self.notify(None); + } + + pub fn set_with_with_initiator(&mut self, f: impl Fn(&T) -> T, initiator: Option) { + let value = f(&self.value); + self.internal_set(value, initiator); + } +} + +impl<'a, T, I, N, N2> LocalProp<'a, T, I, N, N2> +where + T: PartialEq + Clone, + I: Copy + 'a, + N: Notifier, Subject = LocalPropSubject<'a, Option>>, + N2: Notifier>, +{ + /// Fires whenever the value has changed. + /// + /// Event always contains a unit value instead of the + /// new value. This is perfect for combining observables because observables can be combined + /// much easier if they have the same type. UI event handlers for example are often not + /// interested in the new value anyway because they will just call some reusable + /// invalidation code that queries the new value itself. + pub fn changed(&self) -> impl LocalObservable<'a, Item = (), Err = ()> { + self.subject.clone().map_to(()) + } + + /// Fires whenever the value has changed. Also delivers the initiator of the change, if any. + pub fn changed_with_initiator(&self) -> impl LocalObservable<'a, Item = Option, Err = ()> { + self.subject.clone() + } + + /// Fires whenever the value has changed to the given value. + pub fn changed_to(&self, value: T) -> impl LocalObservable<'a, Item = (), Err = ()> + where + T: Clone + 'static, + { + self.value_subject + .clone() + .filter(move |v| *v == value) + .map_to(()) + } + + pub fn values(&self) -> impl LocalObservable<'a, Item = T, Err = ()> + where + T: Clone + 'static, + { + self.value_subject.clone() + } +} + +impl SharedProp +where + T: PartialEq + Clone, + I: Copy + 'static, + N: Notifier, Subject = SharedPropSubject>>, + N2: Notifier>, +{ + pub fn changed( + &self, + ) -> impl SharedObservable + 'static + Send + Sync + { + self.subject.clone().map_to(()) + } +} + +impl fmt::Debug for Prop +where + T: PartialEq + Clone + fmt::Debug, + I: Copy, + S: Observer, Err = ()> + Default, + S2: Observer + Default, + N: Notifier, Subject = S>, + N2: Notifier, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Property") + .field("value", &self.value) + .finish() + } +} + +impl Clone for Prop +where + T: PartialEq + Clone, + I: Copy, + S: Observer, Err = ()> + Default, + S2: Observer + Default, + N: Notifier, Subject = S>, + N2: Notifier, +{ + fn clone(&self) -> Self { + Self { + value: self.value.clone(), + subject: Default::default(), + value_subject: Default::default(), + transformer: self.transformer, + p: PhantomData, + } + } +} + +impl Default for Prop +where + T: PartialEq + Clone + Default, + I: Copy, + S: Observer, Err = ()> + Default, + S2: Observer + Default, + N: Notifier, Subject = S>, + N2: Notifier, +{ + fn default() -> Self { + Self { + value: Default::default(), + subject: Default::default(), + value_subject: Default::default(), + transformer: |v| v, + p: PhantomData, + } + } +} + +impl From for Prop +where + T: PartialEq + Clone, + I: Copy, + S: Observer, Err = ()> + Default, + S2: Observer + Default, + N: Notifier, Subject = S>, + N2: Notifier, +{ + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl PartialEq for Prop +where + T: PartialEq + Clone, + I: Copy, + S: Observer, Err = ()> + Default, + S2: Observer + Default, + N: Notifier, Subject = S>, + N2: Notifier, +{ + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get() { + // Given + let p = prop(5); + // When + // Then + assert_eq!(p.get(), 5); + } + + #[test] + fn set() { + // Given + let mut p = prop(5); + // When + p.set(6); + // Then + assert_eq!(p.get(), 6); + } + + #[test] + fn clone() { + // Given + let p = prop(5); + // When + let p2 = p.clone(); + // Then + assert_eq!(p.get(), 5); + assert_eq!(p2.get(), 5); + } + + #[test] + fn clone_set_independent() { + // Given + let mut p = prop(5); + // When + let mut p2 = p.clone(); + p.set(2); + p2.set(7); + // Then + assert_eq!(p.get(), 2); + assert_eq!(p2.get(), 7); + } + + #[test] + fn transformer() { + // Given + let mut p = local_prop_with_transformer(5, |v| v.min(100)); + // When + p.set(105); + // Then + assert_eq!(p.get(), 100); + } + + #[test] + fn clone_transformer_works() { + // Given + let p = local_prop_with_transformer(5, |v| v.min(100)); + // When + let mut p2 = p.clone(); + p2.set(105); + // Then + assert_eq!(p2.get(), 100); + } + + #[test] + fn observe() { + // Given + let mut invocation_count = 0; + // When + { + let mut p = prop(5); + p.changed().subscribe(|_v| invocation_count += 1); + p.set(6); + } + // Then + assert_eq!(invocation_count, 1); + } + + #[test] + fn clone_observe_independent() { + // Given + let mut p_invocation_count = 0; + let mut p2_invocation_count = 0; + // When + { + let mut p = prop(5); + p.changed().subscribe(|_v| p_invocation_count += 1); + let mut p2 = p.clone(); + p2.changed().subscribe(|_v| p2_invocation_count += 1); + p.set(6); + p2.set(6); + } + // Then + assert_eq!(p_invocation_count, 1); + assert_eq!(p2_invocation_count, 1); + } + + /// In C++ ReaLearn, we used to automatically adjust other fields in a struct whenever the + /// value of a property in that struct has changed by subscribing to it in the constructor. + /// Either to ensure that min value is always <= max value, or to keep the + /// processor in sync with the model. Each of those cases boils down to having a + /// self-referential struct: The struct holds an rx subject which holds a subscriber which + /// points "back" to a field of the very same struct. + /// + /// In Rust, such self references would turn invalid as soon as the type moves, because moving + /// in Rust means memcpy to a different place in memory - which would let pointers/references + /// dangle. This is shown in the test. + /// + /// In C++ this was possible because C++ has a move constructor called when moving an object, in + /// which all self references can be reestablished. Rust intentionally doesn't have such move + /// constructors because always doing a simple memcpy has a multitude of advantages. + /// + /// In Rust, we can achieve the same by making sure the self-referenced data will stay where it + /// is, even if moved. We do that by putting it on the heap (e.g. using Box or Rc). + /// + /// Another way is to always calculate the memory address of the self-referenced data + /// on-the-fly: + /// + /// > So, to recap: instead of storing a pointer to an object itself, store some + /// > information so that you can calculate the pointer later. This is also commonly called + /// using > “handles”. (https://blog.sentry.io/2018/04/05/you-cant-rust-that) + /// + /// Or we use this opportunity to reconsider the design. Instead of enforcing that min value is + /// <= max value, we could just let it happen and instead provide an additional method which + /// returns the fixed value ... a more functional style. After all, the models are not the kind + /// of core domain objects for which it is important that they keep invariants. They are + /// made specifically for UI and (de)serialization needs. That's also why we have domain + /// counterparts without the suffix `Model`, which have no properties, are immutable and + /// therefore don't have this kind of issues by definition. + /// + /// Regarding use case 2, the "cached" processor to keep in sync with the model: An `Rc` would + /// certainly do the job, in our case with totally neglectable overhead. Or we don't expose + /// the properties directly and use setter methods which take care of updating the cached + /// processor. However, at first we might just want to go without caching the processor at all! + #[test] + fn update_other_member_on_change_fail() { + struct Combination<'a> { + value: TestProp<'a, i32>, + _derived_value: i32, + } + + impl<'a> Combination<'a> { + fn new(initial_value: i32) -> Combination<'a> { + let mut c = Combination { + value: prop(initial_value), + _derived_value: initial_value, + }; + let c_ptr = to_ptr(&mut c); + c.value.changed().subscribe(move |_| { + // This won't work because at the time we move c out of this `new` function, + // it will move to a different address in memory. In the old C++ code, this + // only worked because we did the subscription in a move/copy constructor, which + // was called whenever this value was moved. + // Related to discussion here: https://internals.rust-lang.org/t/idea-limited-custom-move-semantics-through-explicitly-specified-relocations/6704/12 + let c = unsafe { &mut *c_ptr }; + c._derived_value = c.value.get() * 2; + }); + c + } + } + + fn to_ptr(value: &mut T) -> *mut T { + value as *mut T + } + + // Given + let mut c = Combination::new(5); + to_ptr(&mut c); + // The following would most likely crash! + // c.value.set(8); + } + + // TODO-low Add some SharedProp tests + + type TestProp<'a, T> = + LocalProp<'a, T, u32, LocalSyncNotifier<'a, Option>, LocalSyncNotifier<'a, T>>; + + fn prop<'a, T>(initial_value: T) -> TestProp<'a, T> + where + T: PartialEq + Clone, + { + TestProp::new(initial_value) + } + + fn local_prop_with_transformer<'a, T>( + initial_value: T, + transformer: fn(T) -> T, + ) -> TestProp<'a, T> + where + T: PartialEq + Clone, + { + TestProp::new_with_transformer(initial_value, transformer) + } +} diff --git a/plugin-reaper-realearn/swell-ui/Cargo.toml b/plugin-reaper-realearn/swell-ui/Cargo.toml new file mode 100644 index 0000000..2c620b7 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "swell-ui" +version = "0.1.0" +authors = ["Benjamin Klum "] +edition = "2021" +publish = false + +[dependencies] +base.workspace = true +reaper-medium.workspace = true +reaper-low.workspace = true +reaper-common-types.workspace = true +rxrust.workspace = true +scopeguard.workspace = true +raw-window-handle.workspace = true +tracing.workspace = true +fragile.workspace = true +palette.workspace = true + +[target.'cfg(windows)'.dependencies] +winapi = { workspace = true, features = ["winuser", "uxtheme"] } +libloading.workspace = true + +[target.'cfg(target_os = "macos")'.dependencies] +objc2.workspace = true + + +# Disabled until we get egui to work nicely (search for "wonky"). It's also important to use cross v0.3.0 then +# (once it's released) because cross v2.5 uses Ubuntu < v20, which has an old glib that's not compatible with the +# glib crate (gdk-sys dependency). +# [target.'cfg(target_os = "linux")'.dependencies] +# gdk-sys = "0.17.0" +# gdkx11-sys = "0.17.0" + +[lints.clippy] +enum_glob_use = "deny" \ No newline at end of file diff --git a/plugin-reaper-realearn/swell-ui/src/brush.rs b/plugin-reaper-realearn/swell-ui/src/brush.rs new file mode 100644 index 0000000..d0638e1 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/brush.rs @@ -0,0 +1,60 @@ +use base::hash_util::NonCryptoHashMap; +use reaper_common_types::RgbColor; +use reaper_low::Swell; +use reaper_medium::Hbrush; +use std::cell::RefCell; + +#[derive(Debug, Default)] +pub struct BrushCache { + brushes: RefCell>>, +} + +impl BrushCache { + /// Returns a handle to a cached brush according to the given descriptor. + /// + /// The returned handle is guaranteed to remain valid because we require a static self. + pub fn get_brush(&'static self, descriptor: BrushDescriptor) -> Option { + let mut brushes = self.brushes.borrow_mut(); + brushes + .entry(descriptor) + .or_insert_with(|| Brush::from_descriptor(descriptor)) + .as_ref() + // It's okay to do this here because we require self to be 'static + .map(|brush| brush.to_inner()) + } +} + +#[derive(Eq, PartialEq, Copy, Clone, Hash, Debug)] +pub struct BrushDescriptor { + color: RgbColor, +} + +impl BrushDescriptor { + pub const fn solid(color: RgbColor) -> Self { + Self { color } + } +} + +/// Owned brush. +#[derive(Debug)] +pub struct Brush(Hbrush); + +impl Brush { + pub fn from_descriptor(desc: BrushDescriptor) -> Option { + let swell_rgb = Swell::RGB(desc.color.r, desc.color.g, desc.color.b); + let brush = Swell::get().CreateSolidBrush(swell_rgb as _); + Hbrush::new(brush).map(Self) + } + + pub fn to_inner(&self) -> Hbrush { + self.0 + } +} + +impl Drop for Brush { + fn drop(&mut self) { + unsafe { + Swell::get().DeleteObject(self.0.as_ptr()); + } + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/color.rs b/plugin-reaper-realearn/swell-ui/src/color.rs new file mode 100644 index 0000000..ca694e3 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/color.rs @@ -0,0 +1,13 @@ +use reaper_common_types::RgbColor; +use reaper_low::Swell; + +pub trait SwellRgbColorExt { + /// Converts this color to a single integer as expected by Win32/SWELL. + fn to_raw(&self) -> u32; +} + +impl SwellRgbColorExt for RgbColor { + fn to_raw(&self) -> u32 { + Swell::RGB(self.r, self.g, self.b) + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/device_context.rs b/plugin-reaper-realearn/swell-ui/src/device_context.rs new file mode 100644 index 0000000..b786adb --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/device_context.rs @@ -0,0 +1,30 @@ +use crate::SwellRgbColorExt; +use reaper_common_types::RgbColor; +use reaper_low::{raw, Swell}; +use reaper_medium::Hdc; + +/// Represents a device context (HDC). +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct DeviceContext(Hdc); + +impl DeviceContext { + pub fn new(hdc: Hdc) -> DeviceContext { + Self(hdc) + } + + pub fn as_ptr(&self) -> raw::HDC { + self.0.as_ptr() + } + + pub fn set_bk_mode_to_transparent(&self) { + unsafe { + Swell::get().SetBkMode(self.as_ptr(), raw::TRANSPARENT as _); + } + } + + pub fn set_text_color(&self, color: RgbColor) { + unsafe { + Swell::get().SetTextColor(self.as_ptr(), color.to_raw() as _); + } + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/font.rs b/plugin-reaper-realearn/swell-ui/src/font.rs new file mode 100644 index 0000000..2963504 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/font.rs @@ -0,0 +1,66 @@ +use base::hash_util::NonCryptoHashMap; +use reaper_low::{raw, Swell}; +use reaper_medium::Hfont; +use std::cell::RefCell; + +#[derive(Debug, Default)] +pub struct FontCache { + fonts: RefCell>>, +} + +impl FontCache { + /// Returns a handle to a cached font according to the given descriptor. + /// + /// The returned handle is guaranteed to remain valid because we require a static self. + pub fn get_font(&'static self, descriptor: FontDescriptor) -> Option { + let mut fonts = self.fonts.borrow_mut(); + fonts + .entry(descriptor) + .or_insert_with(|| Font::from_descriptor(descriptor)) + .as_ref() + // It's okay to do this here because we require self to be 'static + .map(|font| font.to_inner()) + } +} + +#[derive(Eq, PartialEq, Copy, Clone, Hash, Debug)] +pub struct FontDescriptor { + pub name: &'static str, + pub size: u32, +} + +impl FontDescriptor { + pub const fn new(name: &'static str, size: u32) -> Self { + Self { name, size } + } +} + +/// Owned font. +#[derive(Debug)] +pub struct Font(Hfont); + +impl Font { + pub fn from_descriptor(desc: FontDescriptor) -> Option { + let mut font = raw::LOGFONT { + lfHeight: desc.size as _, + ..Default::default() + }; + for (i, byte) in desc.name.bytes().take(31).enumerate() { + font.lfFaceName[i] = byte as _; + } + let font = unsafe { Swell::get().CreateFontIndirect(&mut font as *mut _) }; + Hfont::new(font).map(Self) + } + + pub fn to_inner(&self) -> Hfont { + self.0 + } +} + +impl Drop for Font { + fn drop(&mut self) { + unsafe { + Swell::get().DeleteObject(self.0.as_ptr()); + } + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/lib.rs b/plugin-reaper-realearn/swell-ui/src/lib.rs new file mode 100644 index 0000000..b85a774 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/lib.rs @@ -0,0 +1,41 @@ +mod view_manager; +pub use view_manager::*; + +mod window; +pub use window::*; + +mod device_context; +pub use device_context::*; + +mod menu; +pub use menu::*; + +mod view; +pub use view::*; + +mod units; +pub use units::*; + +mod types; +pub use types::*; + +mod string_types; +pub use string_types::*; + +pub mod menu_tree; + +#[macro_use] +mod color; +pub use color::*; + +mod brush; +pub use brush::*; + +mod font; +pub use font::*; + +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(target_os = "windows")] +mod win; diff --git a/plugin-reaper-realearn/swell-ui/src/macos.rs b/plugin-reaper-realearn/swell-ui/src/macos.rs new file mode 100644 index 0000000..33d3c1f --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/macos.rs @@ -0,0 +1,118 @@ +use objc2::foundation::{MainThreadMarker, NSObject}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::Class; +use objc2::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSApplication; + + unsafe impl ClassType for NSApplication { + #[inherits(NSObject)] + type Super = NSResponder; + } +); + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSResponder; + + unsafe impl ClassType for NSResponder { + type Super = NSObject; + } +); + +pub(crate) fn ns_app() -> Id { + NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() }) +} + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSEvent; + + unsafe impl ClassType for NSEvent { + type Super = NSObject; + } +); + +extern_methods!( + unsafe impl NSApplication { + /// This can only be called on the main thread since it may initialize + /// the application and since it's parameters may be changed by the main + /// thread at any time (hence it is only safe to access on the main thread). + pub fn shared(_mtm: MainThreadMarker) -> Id { + let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] }; + // SAFETY: `sharedApplication` always initializes the app if it isn't already + unsafe { app.unwrap_unchecked() } + } + + pub fn current_event(&self) -> Option> { + unsafe { msg_send_id![self, currentEvent] } + } + } +); + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSWindow; + + unsafe impl ClassType for NSWindow { + #[inherits(NSObject)] + type Super = NSResponder; + } +); + +extern_methods!( + unsafe impl NSWindow { + #[sel(sendEvent:)] + pub unsafe fn send_event(&self, event: &NSEvent); + + pub fn content_view(&self) -> Option> { + unsafe { msg_send_id![self, contentView] } + } + + pub fn child_windows(&self) -> Id { + unsafe { msg_send_id![self, childWindows] } + } + } +); + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSView; + + unsafe impl ClassType for NSView { + #[inherits(NSObject)] + type Super = NSResponder; + } +); + +extern_methods!( + unsafe impl NSView { + pub fn is_kind_of_class(&self, class: &Class) -> bool { + unsafe { msg_send![self, isKindOfClass: class] } + } + + pub fn window(&self) -> Option> { + unsafe { msg_send_id![self, window] } + } + } +); + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSArrayOfWindows; + + unsafe impl ClassType for NSArrayOfWindows { + #[inherits(NSObject)] + type Super = NSResponder; + } +); + +extern_methods!( + unsafe impl NSArrayOfWindows { + pub fn first_object(&self) -> Option> { + unsafe { msg_send_id![self, firstObject] } + } + } +); diff --git a/plugin-reaper-realearn/swell-ui/src/menu.rs b/plugin-reaper-realearn/swell-ui/src/menu.rs new file mode 100644 index 0000000..734064f --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/menu.rs @@ -0,0 +1,164 @@ +use crate::SwellStringArg; +use reaper_low::{raw, Swell}; + +/// Represents a top-level menu bar with resource management. +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct MenuBar { + raw: raw::HMENU, +} + +impl MenuBar { + pub fn new_popup_menu() -> MenuBar { + Self { + raw: Swell::get().CreatePopupMenu(), + } + } + + pub fn load(resource_id: u32) -> Result { + let swell = Swell::get(); + let raw = unsafe { + swell.LoadMenu( + swell.plugin_context().h_instance(), + resource_id as u16 as raw::ULONG_PTR as raw::LPSTR, + ) + }; + if raw.is_null() { + return Err("couldn't load menu"); + } + Ok(MenuBar { raw }) + } + + pub fn menu(&self) -> Menu { + Menu::new(self.raw) + } + + pub fn get_sub_menu(&self, index: u32) -> Option

    { + get_sub_menu_at(self.raw, index) + } +} + +impl Drop for MenuBar { + fn drop(&mut self) { + unsafe { + Swell::get().DestroyMenu(self.raw); + } + } +} + +/// Represents a menu or submenu. +/// +/// Doesn't need to implement Drop because Windows will destroy all sub menus automatically +/// when the root menu is destroyed. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Menu { + raw: raw::HMENU, +} + +impl Menu { + pub fn new(raw: raw::HMENU) -> Self { + Self { raw } + } + + pub fn raw(self) -> raw::HMENU { + self.raw + } + + pub fn set_item_checked(self, item_id: u32, checked: bool) { + unsafe { + Swell::get().CheckMenuItem( + self.raw, + item_id as _, + if checked { + raw::MF_CHECKED + } else { + raw::MF_UNCHECKED + } as _, + ); + } + } + + pub fn add_item<'b>(self, item_id: u32, text: impl Into>) { + unsafe { + let swell_string_arg = text.into(); + let mut mi = raw::MENUITEMINFO { + fMask: raw::MIIM_TYPE | raw::MIIM_DATA | raw::MIIM_ID, + wID: item_id, + dwTypeData: swell_string_arg.as_ptr() as _, + ..Default::default() + }; + Swell::get().InsertMenuItem(self.raw, -1, 1, &mut mi as _); + } + } + + pub fn entry_count(&self) -> Result { + let res = unsafe { Swell::get().GetMenuItemCount(self.raw) }; + if res == -1 { + return Err("couldn't menu get entry count"); + } + Ok(res as u32) + } + + pub fn add_menu<'b>(self, text: impl Into>) -> Menu { + unsafe { + let swell_string_arg = text.into(); + let sub_menu = Swell::get().CreatePopupMenu(); + let mut mi = raw::MENUITEMINFO { + fMask: raw::MIIM_TYPE | raw::MIIM_DATA | raw::MIIM_SUBMENU, + hSubMenu: sub_menu, + dwTypeData: swell_string_arg.as_ptr() as _, + ..Default::default() + }; + Swell::get().InsertMenuItem(self.raw, -1, 1, &mut mi as _); + Menu::new(sub_menu) + } + } + + pub fn add_separator(self) { + unsafe { + let mut mi = raw::MENUITEMINFO { + fMask: raw::MIIM_TYPE, + fType: raw::MF_SEPARATOR, + ..Default::default() + }; + Swell::get().InsertMenuItem(self.raw, -1, 1, &mut mi as _); + } + } + + pub fn get_sub_menu_at(&self, index: u32) -> Option { + get_sub_menu_at(self.raw, index) + } + + pub fn set_item_text<'b>(self, item_id: u32, text: impl Into>) { + unsafe { + let swell_string_arg = text.into(); + let mut mi = raw::MENUITEMINFO { + fMask: raw::MIIM_TYPE | raw::MIIM_DATA, + dwTypeData: swell_string_arg.as_ptr() as _, + ..Default::default() + }; + Swell::get().SetMenuItemInfo(self.raw, item_id as _, 0, &mut mi as _); + } + } + + pub fn set_item_enabled(self, item_id: u32, enabled: bool) { + unsafe { + Swell::get().EnableMenuItem( + self.raw, + item_id as _, + if enabled { + raw::MF_ENABLED + } else { + raw::MF_GRAYED + } as _, + ); + } + } +} + +fn get_sub_menu_at(raw: raw::HMENU, index: u32) -> Option { + let menu = unsafe { Swell::get().GetSubMenu(raw, index as _) }; + if menu.is_null() { + return None; + } + Some(Menu::new(menu)) +} diff --git a/plugin-reaper-realearn/swell-ui/src/menu_tree/input.rs b/plugin-reaper-realearn/swell-ui/src/menu_tree/input.rs new file mode 100644 index 0000000..b538a82 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/menu_tree/input.rs @@ -0,0 +1,184 @@ +use std::fmt::Debug; + +#[derive(Debug)] +pub enum Entry { + Menu(Menu), + Item(Item), + Separator(Separator), + Nothing, +} + +impl Entry { + fn index_recursive(&mut self, counter: &mut Counter) { + match self { + Entry::Menu(m) => { + m.id = counter.next_value(); + for e in &mut m.entries { + e.index_recursive(counter); + } + } + Entry::Item(i) => { + i.id = counter.next_value(); + } + Entry::Separator(i) => { + i.id = counter.next_value(); + } + _ => {} + } + } + + fn find_item_by_id_recursive(self, id: u32) -> Option> { + match self { + Entry::Menu(m) => m + .entries + .into_iter() + .find_map(|e| e.find_item_by_id_recursive(id)), + Entry::Item(i) => { + if i.id == id { + Some(i) + } else { + None + } + } + _ => None, + } + } +} + +#[derive(Debug)] +pub struct Menu { + pub id: u32, + pub text: String, + pub entries: Vec>, +} + +impl Menu { + /// Assigns all menu entries consecutive IDs starting from the given first ID. + /// + /// Returns next non-used value. + /// + /// This is useful for popup menus. + pub fn index(&mut self, first_id: u32) -> u32 { + let mut counter = Counter::starting_from(first_id); + for e in &mut self.entries { + e.index_recursive(&mut counter); + } + counter.next_value() + } + + /// Returns the item that has the given ID. + /// + /// Also looks into sub menus. + /// + /// This is useful for popup menus. + pub fn find_item_by_id(self, id: u32) -> Option> { + self.entries + .into_iter() + .find_map(|e| e.find_item_by_id_recursive(id)) + } +} + +#[derive(Debug)] +pub struct Item { + pub id: u32, + pub text: String, + pub result: R, + pub opts: ItemOpts, +} + +#[derive(Debug)] +pub struct Separator { + pub id: u32, + pub text: Option, +} + +/// Unlabeled menu. +/// +/// This is useful for aggregating a set of entries that can then be added in one go. +pub fn anonymous_menu(entries: Vec>) -> Menu { + Menu { + id: 0, + text: "".to_owned(), + entries, + } +} + +pub fn menu(text: impl Into, entries: Vec>) -> Entry { + Entry::Menu(Menu { + id: 0, + text: text.into(), + entries, + }) +} + +pub fn item(text: impl Into, result: R) -> Entry { + Entry::Item(Item { + id: 0, + text: text.into(), + result, + opts: Default::default(), + }) +} + +pub fn separator() -> Entry { + Entry::Separator(Separator { id: 0, text: None }) +} + +pub fn labeled_separator(name: impl Into) -> Entry { + Entry::Separator(Separator { + id: 0, + text: Some(name.into()), + }) +} + +pub fn item_with_opts(text: impl Into, opts: ItemOpts, result: R) -> Entry { + Entry::Item(Item { + id: 0, + text: text.into(), + result, + opts, + }) +} + +#[allow(clippy::redundant_closure)] +pub fn disabled_item(text: impl Into) -> Entry { + item_with_opts( + text, + ItemOpts { + enabled: false, + checked: false, + }, + R::default(), + ) +} + +#[derive(Debug)] +pub struct ItemOpts { + pub enabled: bool, + pub checked: bool, +} + +impl Default for ItemOpts { + fn default() -> Self { + Self { + enabled: true, + checked: false, + } + } +} + +struct Counter { + value: u32, +} + +impl Counter { + pub fn starting_from(value: u32) -> Self { + Self { value } + } + + pub fn next_value(&mut self) -> u32 { + let val = self.value; + self.value += 1; + val + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/menu_tree/mod.rs b/plugin-reaper-realearn/swell-ui/src/menu_tree/mod.rs new file mode 100644 index 0000000..e93420d --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/menu_tree/mod.rs @@ -0,0 +1,5 @@ +mod input; +pub use input::*; + +mod output; +pub use output::*; diff --git a/plugin-reaper-realearn/swell-ui/src/menu_tree/output.rs b/plugin-reaper-realearn/swell-ui/src/menu_tree/output.rs new file mode 100644 index 0000000..34e2812 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/menu_tree/output.rs @@ -0,0 +1,46 @@ +use crate::menu_tree::{Entry, Menu}; +use crate::Menu as SwellMenu; + +/// Adds all entries within the given pure menu to the given SWELL menu, ignoring the label of the pure menu. +/// +/// Also adds a separator first if the SWELL menu already contains entries. +pub fn add_all_entries_of_menu(swell_menu: SwellMenu, root_menu: &Menu) { + // Add separator if there are entries already + if swell_menu.entry_count().is_ok_and(|count| count > 0) { + swell_menu.add_separator(); + } + // Add entries + for e in &root_menu.entries { + fill_menu_recursively(swell_menu, e); + } +} + +/// Adds the given menu entry and potential sub entries to the given SWELL menu. +fn fill_menu_recursively(swell_menu: SwellMenu, entry: &Entry) { + match entry { + Entry::Menu(m) => { + let sub_menu = swell_menu.add_menu(m.text.as_str()); + for e in &m.entries { + fill_menu_recursively(sub_menu, e); + } + } + Entry::Item(i) => { + swell_menu.add_item(i.id, i.text.as_str()); + if i.opts.checked { + swell_menu.set_item_checked(i.id, true); + } + if !i.opts.enabled { + swell_menu.set_item_enabled(i.id, false); + } + } + Entry::Separator(s) => { + swell_menu.add_separator(); + if let Some(text) = &s.text { + swell_menu.add_item(s.id, text.as_str()); + swell_menu.set_item_enabled(s.id, false); + swell_menu.add_separator(); + } + } + Entry::Nothing => {} + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/string_types.rs b/plugin-reaper-realearn/swell-ui/src/string_types.rs new file mode 100644 index 0000000..3a9fb00 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/string_types.rs @@ -0,0 +1,44 @@ +use reaper_low::raw; +use std::borrow::Cow; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +pub struct SwellStringArg<'a>(Cow<'a, CStr>); + +impl SwellStringArg<'_> { + pub fn as_ptr(&self) -> *const c_char { + self.0.as_ptr() + } + + pub(super) fn as_lparam(&self) -> raw::LPARAM { + self.0.as_ptr() as _ + } +} + +impl<'a> From<&'a CStr> for SwellStringArg<'a> { + fn from(s: &'a CStr) -> Self { + SwellStringArg(s.into()) + } +} + +impl<'a> From<&'a str> for SwellStringArg<'a> { + fn from(s: &'a str) -> Self { + // Requires copying + SwellStringArg( + CString::new(s) + .expect("Rust string too exotic for REAPER") + .into(), + ) + } +} + +impl From for SwellStringArg<'_> { + fn from(s: String) -> Self { + // Doesn't require copying because we own the string now + SwellStringArg( + CString::new(s) + .expect("Rust string too exotic for REAPER") + .into(), + ) + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/types.rs b/plugin-reaper-realearn/swell-ui/src/types.rs new file mode 100644 index 0000000..50504c9 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/types.rs @@ -0,0 +1,4 @@ +use std::rc::Rc; + +pub type SharedView = Rc; +pub type WeakView = std::rc::Weak; diff --git a/plugin-reaper-realearn/swell-ui/src/units.rs b/plugin-reaper-realearn/swell-ui/src/units.rs new file mode 100644 index 0000000..b7202b2 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/units.rs @@ -0,0 +1,297 @@ +use reaper_low::raw; +use reaper_low::raw::RECT; +use std::ops::{Add, Mul, Sub}; + +/// An abstract unit used for dialog dimensions, independent of HiDPI and stuff. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct DialogUnits(pub u32); + +impl DialogUnits { + pub fn get(self) -> u32 { + self.0 + } + + pub fn as_raw(self) -> i32 { + self.0 as _ + } + + pub fn scale(&self, scale: f64) -> Self { + DialogUnits((scale * self.0 as f64).round() as _) + } +} + +impl Add for DialogUnits { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } +} + +impl Mul for DialogUnits { + type Output = Self; + + fn mul(self, rhs: u32) -> Self::Output { + Self(self.0 * rhs) + } +} + +/// Pixels on a screen. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Pixels(pub u32); + +impl Pixels { + pub fn get(self) -> u32 { + self.0 + } + + pub fn as_raw(self) -> i32 { + self.0 as _ + } + + pub fn scale(&self, scale: f64) -> Self { + Pixels((scale * self.0 as f64).round() as _) + } +} + +impl Mul for Pixels { + type Output = Self; + + fn mul(self, rhs: f64) -> Self::Output { + Self((self.0 as f64 * rhs).round() as _) + } +} + +impl Sub for Pixels { + type Output = Pixels; + + fn sub(self, rhs: Self) -> Self::Output { + Pixels(self.0 - rhs.0) + } +} + +impl Add for Pixels { + type Output = Pixels; + + fn add(self, rhs: Self) -> Self::Output { + Pixels(self.0 + rhs.0) + } +} + +/// Point in a coordinate system. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Point { + pub x: T, + pub y: T, +} + +impl Point { + pub const fn new(x: T, y: T) -> Self { + Self { x, y } + } +} + +/// These factors should correspond to those in `dialogs.cpp`. +fn effective_scale_factors() -> ScaleFactors { + #[cfg(target_os = "linux")] + { + let scaling_256 = reaper_low::Swell::get().SWELL_GetScaling256(); + let hidpi_factor = scaling_256 as f64 / 256.0; + ScaleFactors { + main: 1.9 * hidpi_factor, + y: 0.92, + } + } + #[cfg(target_os = "macos")] + { + ScaleFactors { main: 1.6, y: 0.95 } + } + #[cfg(target_os = "windows")] + { + ScaleFactors { main: 1.0, y: 1.0 } + } +} + +struct ScaleFactors { + /// The main scale factor which affects both x and y coordinates. + /// + /// Corresponds to `SWELL_DLG_SCALE_AUTOGEN` in `dialogs.cpp`. + main: f64, + /// An additional scale factor which is applied to y coordinates. + /// + /// Set to 1.0 if you want to use the main factor only. + /// + /// Corresponds to `SWELL_DLG_SCALE_AUTOGEN_YADJ` in `dialogs.cpp`. + y: f64, +} + +impl ScaleFactors { + pub fn x_factor(&self) -> f64 { + self.main + } + + pub fn y_factor(&self) -> f64 { + self.main * self.y + } +} + +impl Point { + /// Converts this dialog unit point to pixels. + /// + /// The Window struct contains a method which can do this including Windows HiDPI information. + pub fn in_pixels(&self) -> Point { + // TODO-low On Windows this works differently. See original ReaLearn. But on the other hand + // ... this is only for the first short render before the optimal size is calculated. + // So as long as it works, this heuristic is okay. + let scale_factors = effective_scale_factors(); + Point { + x: Pixels((scale_factors.x_factor() * self.x.get() as f64) as _), + y: Pixels((scale_factors.y_factor() * self.y.get() as f64) as _), + } + } + + pub fn scale(self, scaling: &DialogScaling) -> Self { + Self { + x: self.x.scale(scaling.x_scale), + y: self.y.scale(scaling.y_scale), + } + } +} + +impl Point { + pub fn to_dimensions(self) -> Dimensions { + Dimensions::new(self.x, self.y) + } +} + +impl From> for Point { + fn from(d: Dimensions) -> Self { + d.to_point() + } +} + +/// Dimensions of a rectangle. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Dimensions { + pub width: T, + pub height: T, +} + +impl Dimensions { + pub const fn new(width: T, height: T) -> Self { + Self { width, height } + } +} + +impl Dimensions { + pub fn to_point(self) -> Point { + Point::new(self.width, self.height) + } +} + +impl Dimensions { + pub fn to_vst(self) -> (i32, i32) { + (self.width.get() as _, self.height.get() as _) + } + + pub fn scale(self, scaling: DialogScaling) -> Self { + Self { + width: self.width.scale(scaling.width_scale), + height: self.height.scale(scaling.height_scale), + } + } +} + +impl Dimensions { + /// Converts the given dialog unit dimensions to pixels. + /// + /// Doesn't take window-specific HIDPI info into account! Use `Window` for this. + pub fn in_pixels(&self) -> Dimensions { + self.to_point().in_pixels().to_dimensions() + } + + pub fn scale(self, scaling: &DialogScaling) -> Self { + Self { + width: self.width.scale(scaling.width_scale), + height: self.height.scale(scaling.height_scale), + } + } +} + +impl From> for Dimensions { + fn from(p: Point) -> Self { + p.to_dimensions() + } +} + +/// This is not the scaling applied by SWELL but the one applied before by us when generating +/// the RC file. In future we might produce different RC files for different operating systems. +/// Then this is maybe the only scaling info we need and we can ditch SWELL scaling. +#[derive(Debug)] +pub struct DialogScaling { + pub x_scale: f64, + pub y_scale: f64, + pub width_scale: f64, + pub height_scale: f64, +} + +#[derive(Copy, Clone, Debug)] +pub struct Rect { + pub left: i32, + pub top: i32, + pub width: u32, + pub height: u32, +} + +impl Rect { + pub fn contains(&self, point: Point) -> bool { + point.x >= self.left + && point.y >= self.top + && point.x < self.right() + && point.y < self.bottom() + } + + pub fn right(&self) -> i32 { + self.left + self.width as i32 + } + + pub fn bottom(&self) -> i32 { + self.top + self.height as i32 + } + + // pub fn normalize(&self, parent_height: u32) -> Self { + // #[cfg(target_os = "macos")] + // { + // Self { + // top: parent_height as i32 - self.top, + // ..*self + // } + // } + // #[cfg(not(target_os = "macos"))] + // { + // *self + // } + // } +} + +impl From for Rect { + fn from(value: RECT) -> Self { + Self { + left: value.left, + top: value.top, + width: (value.right - value.left).unsigned_abs(), + height: (value.bottom - value.top).unsigned_abs(), + } + } +} + +impl From for raw::RECT { + fn from(value: Rect) -> Self { + Self { + left: value.left, + top: value.top, + right: value.right(), + bottom: value.bottom(), + } + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/view.rs b/plugin-reaper-realearn/swell-ui/src/view.rs new file mode 100644 index 0000000..45726a6 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/view.rs @@ -0,0 +1,368 @@ +use crate::{create_window, DeviceContext, Pixels, Point, SharedView, Window}; +use reaper_low::raw; +use rxrust::prelude::*; + +use reaper_medium::Hbrush; +use std::cell::{Cell, RefCell}; +use std::fmt::Debug; + +/// Represents a displayable logical part of the UI, such as a panel. +/// +/// Each view has a 1:0..1 relationship to a window. One can say that a view is +/// implemented/displayed by a window. A window (= HWND) is the more low-level technical +/// implementation concept which has lots of possible behavior (see `Window` struct) whereas a view +/// is a higher-level logical concept which uses window methods to implement very particular and +/// aptly-named logic which makes sense for that part of the UI. +/// +/// All views have a few things in common, e.g. views can be opened (window gets created) and closed +/// (window gets destroyed). These common things are modeled by this trait. In addition to a common +/// interface, this trait provides default implementations for that common behavior. +/// +/// An other important part of this trait are window events/callbacks, which implementors can +/// handle. +/// +/// # Design +/// +/// ## Why do view callback methods take self not as mutable reference? +/// win32 window procedures can be *reentered*, see the win32 docs! Now let's assume we would take +/// self as mutable reference (`&mut self`). If we would have a borrow checker (`RefCell`), it would +/// complain on reentry by panicking. Rightly so. Without `RefCell` things would get very unsafe and +/// we wouldn't even get notified about it. I think the only correct way is to never let the window +/// procedure call view methods in a mutable context. Make all view handler methods take an +/// immutable reference. The same strategy which we are using with `IReaperControlSurface` in +/// `reaper-rs`, because this is reentrant as well. +/// +/// ## Why are there no exceptions? +/// One could argue that e.g. `WM_INITDIALOG` is not reentered and we could therefore make an +/// exception. But not only the win32 window procedure might call our view, also our own code. Just +/// think of a `close()` method which takes `&mut self` and calls `DestroyWindow()`. Windows would +/// send a `WM_INITDIALOG` message while we are still in the `close()` method, et voilà ... we would +/// have 2 mutable accesses. It's just not safe and would cause a false feeling of security! +/// +/// ## So how do we mutate things in the callback methods? +/// Everything which needs to be mutable needs to be wrapped with a `RefCell`. We need to pursue the +/// fine-granular `RefCell` approach because reentrancy is unavoidable. We just need to make sure +/// not to write to the same data member non-exclusively. If we fail to achieve that, at least +/// the panic lets us know about the issue. +/// +/// ## Why do view callback methods take self as `SharedView`? +/// Given the above mentioned safety measures and knowing that we must keep views as `Rc`s anyway +/// (for lifetime reasons, see `ViewManager`), it is possible to take self as `SharedView` +/// without sacrificing anything. The obvious advantage we have is that it gives us an easy way to +/// access view methods in subscribe closures without running into lifetime problems (such as &self +/// disappearing while still being used in the closure). +pub trait View: Debug { + // Data providers (implementation required, used internally) + // ========================================================= + + /// ID of the dialog resource to look up when creating the window. + /// + /// The dialog resource basically defines the window's initial look. + fn dialog_resource_id(&self) -> u32; + + /// Returns the current window, if any. + /// + /// In order to implement behavior common to views, the `View` trait needs mutable access to + /// this context. + fn view_context(&self) -> &ViewContext; + + // Event handlers (implementation optional) + // ================================================= + + /// WM_INITDIALOG. + /// + /// Should return `true` if you want the window to be actually shown when it's created. + fn show_window_on_init(&self) -> bool { + true + } + + /// WM_INITDIALOG. + /// + /// Should return `true` if keyboard focus is desired. + fn opened(self: SharedView, _window: Window) -> bool { + false + } + + /// WM_CLOSE. + /// + /// Should return `true` if the window must not be destroyed. + fn close_requested(self: SharedView) -> bool { + false + } + + /// WM_DESTROY. + fn on_destroy(self: SharedView, _window: Window) {} + + /// WM_SHOWWINDOW. + /// + /// Should return `true` if processed. + fn shown_or_hidden(self: SharedView, shown: bool) -> bool { + let _ = shown; + false + } + + /// WM_COMMAND, HIWORD(wparam) == 0. + fn button_clicked(self: SharedView, resource_id: u32) { + let _ = resource_id; + } + + /// WM_COMMAND, HIWORD(wparam) == CBN_SELCHANGE + fn option_selected(self: SharedView, resource_id: u32) { + let _ = resource_id; + } + + /// WM_VSCROLL, LOWORD(wparam). + /// + /// Should return `true` if processed. + fn scrolled_vertically(self: SharedView, _code: u32) -> bool { + false + } + + /// WM_HSCROLL, lparam (!= 0). + fn slider_moved(self: SharedView, _slider: Window) {} + + /// Should return `true` if processed. + fn resized(self: SharedView) -> bool { + false + } + + /// Should return `true` if processed. + fn focused(self: SharedView) -> bool { + false + } + + /// WM_MOUSEWHEEL, HIWORD(wparam). + /// + /// Should return `true` if processed. + fn mouse_wheel_turned(self: SharedView, distance: i32) -> bool { + let _ = distance; + false + } + + /// WM_MOUSEMOVE. + /// + /// Should return `true` if processed. + fn mouse_moved(self: SharedView, position: Point) -> bool { + let _ = position; + false + } + + /// WM_NCHITTEST. + /// + /// Should return `true` if processed. + fn mouse_test(self: SharedView, position: Point) -> bool { + let _ = position; + false + } + + /// When F1 was pressed. + /// + /// Should return `true` if processed. + fn help_requested(&self) -> bool { + false + } + + /// WM_KEYDOWN. + /// + /// Should return `true` if processed. + fn key_down(self: SharedView, key_code: u8) -> bool { + let _ = key_code; + false + } + + /// WM_KEYUP. + /// + /// On macOS, a multi-line text field fires this instead of edit_control_changed. + /// But it's not fired on Windows! + /// + /// Should return `true` if processed. + fn key_up(self: SharedView, key_code: u8) -> bool { + let _ = key_code; + false + } + + /// EN_CHANGE, LOWORD(wparam). + /// + /// Should return `true` if processed. + fn edit_control_changed(self: SharedView, resource_id: u32) -> bool { + let _ = resource_id; + false + } + + /// EN_SETFOCUS, LOWORD(wparam). + /// + /// Should return `true` if processed. + fn edit_control_focus_set(self: SharedView, resource_id: u32) -> bool { + let _ = resource_id; + false + } + + /// EN_KILLFOCUS, LOWORD(wparam). + /// + /// Should return `true` if processed. + /// + /// Currently not fired on Linux! + fn edit_control_focus_killed(self: SharedView, _resource_id: u32) -> bool { + false + } + + /// WM_CONTEXTMENU + /// + /// Should return `true` if processed in order to prevent the context menu request going up to higher layers. + fn context_menu_wanted(self: SharedView, _location: Point) -> bool { + false + } + + /// WM_PAINT + /// + /// Should return `true` if processed. + fn paint(self: SharedView) -> bool { + false + } + + /// WM_ERASEBKGND + /// + /// Should return `true` if processed. + fn erase_background(self: SharedView, device_context: DeviceContext) -> bool { + let _ = device_context; + false + } + + /// WM_CTLCOLORSTATIC + /// + /// Can return a custom background brush for painting that control. + fn control_color_static( + self: SharedView, + device_context: DeviceContext, + window: Window, + ) -> Option { + let _ = device_context; + let _ = window; + None + } + + /// WM_CTLCOLORDLG + /// + /// Can return a custom background brush for painting that dialog. + fn control_color_dialog( + self: SharedView, + device_context: DeviceContext, + window: Window, + ) -> Option { + let _ = device_context; + let _ = window; + None + } + + /// Timer with the given ID fires. + /// + /// Should return `true` if processed. + fn timer(&self, id: usize) -> bool { + let _ = id; + false + } + + /// Called whenever the DialogProc (not WindowProc!!!) is called, before any other callback + /// method. + /// + /// Return `None` to indicate that processing should continue, that is, the other callback + /// methods should be called accordingly. + fn process_raw( + &self, + window: Window, + msg: raw::UINT, + wparam: raw::WPARAM, + lparam: raw::LPARAM, + ) -> Option { + let _ = window; + let _ = msg; + let _ = wparam; + let _ = lparam; + None + } + + fn get_keyboard_event_receiver(&self, focused_window: Window) -> Option { + Some(focused_window) + } + + /// If `true`, `RealearnAccelerator` will forward raw keyboard events to this window. + /// + /// - Absolutely necessary for egui containers (so that egui receives keyboard events) + /// - For normal dialogs this can be bad (at least on Windows) because tabbing through text fields is not possible + /// anymore (https://github.com/helgoboss/helgobox/issues/1213) + fn wants_raw_keyboard_input(&self) -> bool { + false + } + + // Public methods (intended to be used by consumers) + // ================================================= + + /// Opens this view in the given parent window. + fn open(self: SharedView, parent_window: Window) -> Option + where + Self: Sized + 'static, + { + let resource_id = self.dialog_resource_id(); + create_window(self, resource_id, Some(parent_window)) + } + + /// Opens this view in a free window. + fn open_without_parent(self: SharedView) -> Option + where + Self: Sized + 'static, + { + let resource_id = self.dialog_resource_id(); + create_window(self, resource_id, None) + } + + /// Closes this view. + fn close(&self) { + if let Some(window) = self.view_context().window.get() { + window.destroy(); + } + } + + /// Returns whether this view is currently open. + fn is_open(&self) -> bool { + self.view_context().window.get().is_some() + } +} + +/// Context data of a view. +/// +/// If Rust traits could provide data in the form of fields, this would be it. +#[derive(Clone, Default, Debug)] +pub struct ViewContext { + pub(crate) window: Cell>, + pub(crate) closed_subject: RefCell>, +} + +impl ViewContext { + /// Returns the current window associated with this view if this view is open. + pub fn window(&self) -> Option { + self.window.get() + } + + /// Returns the current window associated with this view. + /// + /// # Panics + /// + /// Panics if the window doesn't exist (the view is not open). + pub fn require_window(&self) -> Window { + self.window().expect("window not found but required") + } + + /// Returns the control with the given resource ID. + /// + /// # Panics + /// + /// Panics if the window or control doesn't exist. + pub fn require_control(&self, resource_id: u32) -> Window { + self.require_window().require_control(resource_id) + } + + /// Fires when the window is closed. + pub fn closed(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.closed_subject.borrow().clone() + } +} diff --git a/plugin-reaper-realearn/swell-ui/src/view_manager.rs b/plugin-reaper-realearn/swell-ui/src/view_manager.rs new file mode 100644 index 0000000..f235f3d --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/view_manager.rs @@ -0,0 +1,347 @@ +//! This file is supposed to encapsulate most of the (ugly) win32 API glue code +use crate::{ + BrushCache, BrushDescriptor, DeviceContext, FontCache, FontDescriptor, Pixels, Point, + SharedView, View, WeakView, Window, +}; +use std::cell::{Cell, RefCell}; + +use reaper_low::{raw, Swell}; +use rxrust::prelude::*; +use std::os::raw::c_void; +use std::panic::catch_unwind; +use std::ptr::null_mut; + +use base::hash_util::NonCryptoHashMap; +use fragile::Fragile; +use reaper_common_types::RgbColor; +use reaper_medium::{Hbrush, Hdc, Hfont, Hwnd}; +use std::sync::OnceLock; + +/// Creates a window according to the given dialog resource. +/// +/// It's added as a child to the given parent window and attached to the specified view. +/// +/// Internally, this creates a new win32 dialog using the given resource ID. Uses the methods in the +/// given view for all callbacks. +pub(crate) fn create_window( + view: SharedView, + resource_id: u32, + parent_window: Option, +) -> Option { + let swell = Swell::get(); + let hwnd = unsafe { + // This will call the dialog procedure `view_dialog_proc`. In order to still know which + // of the many view objects we are dealing with, we make use of the lparam parameter of + // `CreateDialogParamA` by passing it an address which points to the concrete view. + // `view_dialog_proc` with message WM_INITDIALOG will be called immediately, not async. + // That's important because we must be sure that the given view Rc reference is still + // valid when it arrives in `view_dialog_proc`. + swell.CreateDialogParam( + swell.plugin_context().h_instance(), + resource_id as u16 as raw::ULONG_PTR as raw::LPSTR, + parent_window.map(|w| w.raw()).unwrap_or(null_mut()), + Some(view_dialog_proc), + convert_view_ref_to_address(&view), + ) + }; + Window::new(hwnd) +} + +/// This struct manages the mapping from windows to views. +/// +/// This is necessary to get from "global" win32 world into beloved "local" Rust struct world. +#[derive(Default)] +pub struct ViewManager { + /// Holds a mapping from window handles (HWND) to views + view_map: RefCell>>, + brush_cache: BrushCache, + font_cache: FontCache, +} + +impl ViewManager { + pub fn get_solid_brush(&'static self, color: RgbColor) -> Option { + self.brush_cache.get_brush(BrushDescriptor::solid(color)) + } + + pub fn get_font(&'static self, descriptor: FontDescriptor) -> Option { + self.font_cache.get_font(descriptor) + } + + /// If the given window is one of ours (one that drives our views) and the associated view + /// still exists, it returns that associated view. + pub fn get_associated_view(&self, window: Window) -> Option> { + let view_map = self.view_map.borrow(); + let view = view_map.get(&window.raw())?; + view.upgrade() + } + + /// Returns the global window manager instance + pub fn get() -> &'static ViewManager { + static VIEW_MANAGER: OnceLock> = OnceLock::new(); + // We need to initialize the manager lazily because it's impossible to do that using a const + // function (at least in Rust stable). + VIEW_MANAGER + .get_or_init(|| Fragile::new(ViewManager::default())) + .get() + } + + /// Registers a new HWND-to-view mapping + fn register_view(&self, hwnd: raw::HWND, view: &SharedView) { + self.view_map + .borrow_mut() + .insert(hwnd, SharedView::downgrade(view)); + } + + /// Looks up a view by its corresponding HWND + fn lookup_view(&self, hwnd: raw::HWND) -> Option> { + let view_map = self.view_map.borrow(); + let weak_view = view_map.get(&hwnd)?; + weak_view.upgrade().or_else(|| { + // Not existing. The primary owner (most likely a parent view) dropped + // it already. + tracing::warn!("Requested ui is registered in ui map but has been dropped already"); + None + }) + } + + /// Unregisters a HWND-to-View mapping + fn unregister_view(&self, hwnd: raw::HWND) { + self.view_map.borrow_mut().remove(&hwnd); + } +} + +// Converts the given view Rc reference to an address which can be transmitted as LPARAM. +// `SharedView` is a so-called trait object, a *fat* pointer which is twice as large as a +// normal pointer (on 64-bit architectures 2 x 64 bit = 128 bit = 16 bytes). This is too big to +// encode within LPARAM. `&SharedView` is *not* a trait object but a reference to the +// trait object. Therefore it is a thin pointer already. +fn convert_view_ref_to_address(view_trait_object_ref: &SharedView) -> isize { + let view_trait_object_ptr = view_trait_object_ref as *const _ as *const c_void; + view_trait_object_ptr as isize +} + +// Converts the given address back to the original view Rc reference. +fn interpret_address_as_view_ref<'a>(view_trait_object_address: isize) -> &'a SharedView { + let view_trait_object_ptr = view_trait_object_address as *const c_void; + unsafe { &*(view_trait_object_ptr as *const _) } +} + +/// This is our dialog procedure. +/// +/// It's called by Windows (or the emulation layer). It basically finds the particular `View` +/// instance which matches the HWND and then delegates to its methods. Please note that this is +/// a DialogProc, not a WindowProc. The difference is mainly the return value. A WindowProc +/// usually returns 0 if the message has been processed, or it delegates to `DefWindowProc()`. +/// If we do the latter in a DialogProc, non-child windows start to become always modal (not +/// returning focus) because it's wrong! +/// +/// In DialogProc it's the opposite: It returns 1 if the message has been processed and 0 if not. +/// If we have a message where the return value has a special meaning (beyond processed or +/// unprocesssed), we need to "return" that via `SetWindowLong` instead, except for WM_INITDIALOG. +/// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-dlgproc. +unsafe extern "C" fn view_dialog_proc( + hwnd: raw::HWND, + msg: raw::UINT, + wparam: raw::WPARAM, + lparam: raw::LPARAM, +) -> raw::INT_PTR { + catch_unwind(|| { + DIALOG_PROC_ALREADY_ENTERED.with(|entered| { + // Detect reentrancy + let already_entered = entered.replace(true); + scopeguard::defer! { + if !already_entered { + entered.set(false); + } + } + // Obtain view + let view: SharedView = if msg == raw::WM_INITDIALOG { + // A view window is initializing. At this point lparam contains the value which we + // passed when calling CreateDialogParam. This contains the address of a + // view reference. At subsequent calls, this address is not passed anymore + // but only the HWND. So we need to save a HWND-to-view mapping now. + let view_ref = interpret_address_as_view_ref(lparam as _); + ViewManager::get().register_view(hwnd, view_ref); + view_ref.clone() + } else { + // Try to find view corresponding to given HWND + match ViewManager::get().lookup_view(hwnd) { + None => { + // View is not (yet) registered. Do default stuff. + return 0; + } + Some(v) => v, + } + }; + // Found view. + // Delegate to view struct methods. + let window = Window::new(hwnd).expect("window was null"); + if let Some(result) = view.process_raw(window, msg, wparam, lparam) { + return result; + } + const KEYBOARD_MSG_FOR_X_BRIDGE: u32 = raw::WM_USER + 100; + match msg { + raw::WM_INITDIALOG => { + view.view_context().window.replace(Some(window)); + let show_window = view.show_window_on_init(); + let keyboard_focus_desired = view.opened(window); + if show_window { + window.show(); + // WM_INITDIALOG is special in a DialogProc in that we don't need to use + // `SetWindowLong()` for return values with special meaning. + keyboard_focus_desired.into() + } else { + 0 + } + } + raw::WM_SHOWWINDOW => view.shown_or_hidden(wparam == 1).into(), + raw::WM_DESTROY => { + let view_context = view.view_context(); + view_context.closed_subject.borrow_mut().next(()); + view_context.window.replace(None); + view.on_destroy(window); + ViewManager::get().unregister_view(hwnd); + 1 + } + // This is called on Linux when receiving a keyboard message via SendMessage. + // The only time we do this is when we want to forward keyboard interaction from + // the RealearnAccelerator to egui. egui runs in an XBridge window - a sort + // of child window of the SWELL window. Returning 0 here makes sure that the + // messages are passed through to the XBridge window. This is SWELL functionality + // (check the SWELL code). + KEYBOARD_MSG_FOR_X_BRIDGE => 0, + raw::WM_MOUSEMOVE => view + .mouse_moved(Point::new( + loword_signed(lparam as _), + hiword_signed(lparam as _), + )) + .into(), + raw::WM_NCHITTEST => view + .mouse_test(Point::new( + loword_signed(lparam as _), + hiword_signed(lparam as _), + )) + .into(), + raw::WM_SIZE => view.resized().into(), + raw::WM_SETFOCUS => view.focused().into(), + raw::WM_COMMAND => { + let resource_id = loword(wparam as _); + match hiword(wparam as _) as u32 { + 0 => { + view.button_clicked(resource_id as _); + // We just say the click is handled. Don't know where this would not + // be the case. + 1 + } + raw::CBN_SELCHANGE => { + view.option_selected(resource_id as _); + // We just say the selection is handled. Don't know where this would not + // be the case. + 1 + } + raw::EN_SETFOCUS => view.edit_control_focus_set(resource_id as _).into(), + raw::EN_KILLFOCUS => { + view.edit_control_focus_killed(resource_id as _).into() + } + raw::EN_CHANGE => { + // Edit control change event is fired even if we change an edit control + // text programmatically. We don't want this. In general. + if already_entered { + return 0; + } + view.edit_control_changed(resource_id as _).into() + } + _ => 0, + } + } + raw::WM_VSCROLL => { + let code = loword(wparam as _); + view.scrolled_vertically(code as _).into() + } + raw::WM_HSCROLL => { + if lparam <= 0 { + // This is not a slider. Not interested. + return 0; + } + let raw_slider = Hwnd::new(lparam as raw::HWND).expect("slider hwnd is null"); + view.slider_moved(Window::from_hwnd(raw_slider)); + 1 + } + raw::WM_MOUSEWHEEL => { + let distance = hiword_signed(wparam); + view.mouse_wheel_turned(distance).into() + } + raw::WM_KEYDOWN => view.key_down(wparam as _).into(), + raw::WM_KEYUP => view.key_up(wparam as _).into(), + raw::WM_CLOSE => { + let processed = view.close_requested(); + if !processed { + window.destroy(); + } + 1 + } + raw::WM_CONTEXTMENU => { + let x = loword(lparam as _); + let y = hiword(lparam as _); + view.context_menu_wanted(Point::new(Pixels(x as _), Pixels(y as _))) + .into() + } + raw::WM_PAINT => isize::from(view.paint()), + raw::WM_ERASEBKGND => { + if let Some(hdc) = Hdc::new(wparam as raw::HDC) { + isize::from(view.erase_background(DeviceContext::new(hdc))) + } else { + 0 + } + } + raw::WM_CTLCOLORSTATIC => { + let brush = view.control_color_static( + DeviceContext::new( + Hdc::new(wparam as raw::HDC).expect("HDC in WM_CTLCOLORSTATIC is null"), + ), + Window::new(lparam as raw::HWND) + .expect("WM_CTLCOLORSTATIC control is null"), + ); + brush.map(|b| b.as_ptr()).unwrap_or(null_mut()) as _ + } + raw::WM_CTLCOLORDLG => { + let brush = view.control_color_dialog( + DeviceContext::new( + Hdc::new(wparam as raw::HDC).expect("HDC in WM_CTLCOLORDLG is null"), + ), + Window::new(lparam as raw::HWND).expect("WM_CTLCOLORDLG control is null"), + ); + brush.map(|b| b.as_ptr()).unwrap_or(null_mut()) as _ + } + raw::WM_TIMER => { + if view.timer(wparam) { + 0 + } else { + 1 + } + } + _ => 0, + } + }) + }) + .unwrap_or(0) +} + +fn loword(wparam: usize) -> u16 { + (wparam & 0xffff) as _ +} + +fn loword_signed(wparam: usize) -> i32 { + loword(wparam) as i16 as i32 +} + +fn hiword(wparam: usize) -> u16 { + ((wparam >> 16) & 0xffff) as _ +} + +fn hiword_signed(wparam: usize) -> i32 { + hiword(wparam) as i16 as i32 +} + +// Used for global dialog proc reentrancy check. +thread_local!(static DIALOG_PROC_ALREADY_ENTERED: Cell = const { Cell::new(false) }); diff --git a/plugin-reaper-realearn/swell-ui/src/win.rs b/plugin-reaper-realearn/swell-ui/src/win.rs new file mode 100644 index 0000000..7506c02 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/win.rs @@ -0,0 +1,29 @@ +use libloading::{Library, Symbol}; +use winapi::shared::minwindef::UINT; +use winapi::shared::windef::HWND; + +/// Provides access to some Win32 API functions that are not available in older Windows versions. +/// +/// This is better than eagerly linking to these functions because then the resulting binary +/// wouldn't work *at all* in the older Windows versions, whereas with this approach, we can +/// fall back to alternative logic or alternative values on a case-by-case basis. +pub struct DynamicWinApi { + user32_library: Library, +} + +impl DynamicWinApi { + pub fn load() -> Self { + unsafe { + Self { + user32_library: Library::new("user32.dll").unwrap(), + } + } + } + + /// Should be available from Windows 10 onwards. + pub fn get_dpi_for_window(&self) -> Option> { + unsafe { self.user32_library.get(b"GetDpiForWindow\0").ok() } + } +} + +type GetDpiForWindow = extern "system" fn(hwnd: HWND) -> UINT; diff --git a/plugin-reaper-realearn/swell-ui/src/window.rs b/plugin-reaper-realearn/swell-ui/src/window.rs new file mode 100644 index 0000000..7221ce1 --- /dev/null +++ b/plugin-reaper-realearn/swell-ui/src/window.rs @@ -0,0 +1,1098 @@ +use crate::{ + menu_tree, DialogUnits, Dimensions, FontDescriptor, Menu, MenuBar, Pixels, Point, Rect, + SwellStringArg, ViewManager, +}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use reaper_low::raw::RECT; +use reaper_low::{raw, Swell}; +use reaper_medium::{Hfont, Hwnd}; +use std::ffi::CString; +use std::fmt::Display; +use std::os::raw::c_char; +use std::ptr::{null, null_mut}; +use std::time::Duration; + +/// Represents a window. +/// +/// _Window_ is meant in the win32 sense, where windows are not only top-level windows but also +/// embedded components such as buttons or text fields. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Window { + raw: raw::HWND, +} + +unsafe impl Send for Window {} + +impl Window { + pub fn new(hwnd: raw::HWND) -> Option { + Hwnd::new(hwnd).map(Window::from_hwnd) + } + + pub fn from_raw_window_handle(handle: RawWindowHandle) -> Result { + let window_ptr = match handle { + RawWindowHandle::AppKit(h) => h.ns_view, + RawWindowHandle::Win32(h) => h.hwnd, + _ => return Err("unsupported raw window handle"), + }; + Self::new(window_ptr as _).ok_or("couldn't obtain window from raw window handle") + } + + /// Attention: On macOS, this returns the first subview, not the first child window. + pub fn next_child(prev_child: Window) -> Option { + get_related_window(prev_child, raw::GW_HWNDNEXT) + } + + pub fn screen_size() -> Dimensions { + Dimensions::new(Self::screen_width(), Self::screen_height()) + } + + pub fn screen_width() -> Pixels { + Pixels(Swell::get().GetSystemMetrics(raw::SM_CXSCREEN) as _) + } + + pub fn screen_height() -> Pixels { + Pixels(Swell::get().GetSystemMetrics(raw::SM_CYSCREEN) as _) + } + + pub fn cursor_pos() -> Point { + let mut point = raw::POINT { x: 0, y: 0 }; + unsafe { Swell::get().GetCursorPos(&mut point as _) }; + Point::new(Pixels(point.x as _), Pixels(point.y as _)) + } + + pub fn dark_mode_is_enabled() -> bool { + #[cfg(target_os = "macos")] + { + let swell = Swell::get(); + if swell.pointers().SWELL_osx_is_dark_mode.is_some() { + swell.SWELL_osx_is_dark_mode(0) + } else { + false + } + } + #[cfg(any(target_os = "windows", target_os = "linux"))] + { + use palette::{IntoColor, Srgb}; + let color = Swell::get().GetSysColor(raw::COLOR_WINDOW as _); + let (r, g, b) = ( + Swell::GetRValue(color as _), + Swell::GetGValue(color as _), + Swell::GetBValue(color as _), + ); + let rgb: Srgb = palette::Srgb::new(r, g, b).into_format(); + let luma: palette::luma::Luma = rgb.into_color(); + luma.luma < 0.5 + } + } + + pub fn from_hwnd(hwnd: Hwnd) -> Window { + Window { raw: hwnd.as_ptr() } + } + + pub fn focused() -> Option { + Self::new(Swell::get().GetFocus()) + } + + pub fn raw(self) -> raw::HWND { + self.raw + } + + pub fn resource_id(&self) -> u32 { + unsafe { Swell::get().GetWindowLong(self.raw, raw::GWL_ID) as u32 } + } + + pub fn center_on_screen(&self) { + let screen_size = Window::screen_size(); + let window_size = self.client_size(); + self.move_to_pixels(Point::new( + (screen_size.width - window_size.width) * 0.5, + (screen_size.height - window_size.height) * 0.5, + )); + } + + pub fn size_and_center_on_screen(&self, width_factor: f64, height_factor: f64) { + let screen_size = Window::screen_size(); + let window_size = Dimensions::new( + screen_size.width * width_factor, + screen_size.height * height_factor, + ); + self.resize(window_size); + self.move_to_pixels(Point::new( + (screen_size.width - window_size.width) * 0.5, + (screen_size.height - window_size.height) * 0.5, + )); + } + + pub fn screen_to_client_rect(&self, rect: &RECT) -> Rect { + let top_left = self.screen_to_client_point(Point::new(rect.left, rect.top)); + let bottom_right = self.screen_to_client_point(Point::new(rect.right, rect.bottom)); + Rect { + left: top_left.x, + top: top_left.y, + width: (bottom_right.x - top_left.x) as _, + height: (bottom_right.y - top_left.y) as _, + } + } + + pub fn screen_to_client_point(&self, point: Point) -> Point { + let mut point = raw::POINT { + x: point.x, + y: point.y, + }; + unsafe { + Swell::get().ScreenToClient(self.raw, &mut point as *mut _); + } + Point::new(point.x, point.y) + } + + pub fn client_size(self) -> Dimensions { + let rect = self.client_rect(); + Dimensions::new( + Pixels(rect.right() as u32 - rect.left as u32), + Pixels(rect.bottom() as u32 - rect.top as u32), + ) + } + + pub fn client_rect(self) -> Rect { + let mut rect = RECT::default(); + unsafe { Swell::get().GetClientRect(self.raw, &mut rect) }; + rect.into() + } + + pub fn raw_hwnd(self) -> Hwnd { + Hwnd::new(self.raw).expect("raw hwnd is null") + } + + pub fn find_control(self, control_id: u32) -> Option { + let hwnd = unsafe { Swell::get().GetDlgItem(self.raw, control_id as i32) }; + Window::new(hwnd) + } + + pub fn require_control(self, control_id: u32) -> Window { + self.find_control(control_id) + .expect("required control not found") + } + + /// Attention: This blocks the thread but continues the event loop, so you shouldn't have + /// anything borrowed while calling this unless you want errors due to reentrancy. + pub fn alert<'a>( + self, + caption: impl Into>, + msg: impl Into>, + ) { + unsafe { + Swell::get().MessageBox( + self.raw, + msg.into().as_ptr(), + caption.into().as_ptr(), + raw::MB_OK as _, + ); + } + } + + /// Attention: This blocks the thread but continues the event loop, so you shouldn't have + /// anything borrowed while calling this unless you want errors due to reentrancy. + pub fn confirm<'a>( + self, + caption: impl Into>, + msg: impl Into>, + ) -> bool { + let result = unsafe { + Swell::get().MessageBox( + self.raw, + msg.into().as_ptr(), + caption.into().as_ptr(), + raw::MB_YESNO as _, + ) + }; + result == raw::IDYES as i32 + } + + /// Attention: This blocks the thread but continues the event loop, so you shouldn't have + /// anything borrowed while calling this unless you want errors due to reentrancy. + pub fn ask_yes_no_or_cancel<'a>( + self, + caption: impl Into>, + msg: impl Into>, + ) -> Option { + let result = unsafe { + Swell::get().MessageBox( + self.raw, + msg.into().as_ptr(), + caption.into().as_ptr(), + raw::MB_YESNOCANCEL as _, + ) + }; + if result == raw::IDYES as i32 { + Some(true) + } else if result == raw::IDNO as i32 { + Some(false) + } else { + None + } + } + + pub fn set_checked_or_hide(self, is_checked: Option) { + if let Some(state) = is_checked { + self.show(); + self.set_checked(state); + } else { + self.hide(); + } + } + + /// This return the first child *window* (not subview) on macOS. + #[cfg(target_os = "macos")] + pub fn first_child_window(&self) -> Option { + unsafe { + let view = &*(self.raw as *const crate::macos::NSView); + let view_window = view.window()?; + let child_content_view = view_window.child_windows().first_object()?.content_view()?; + Window::new(child_content_view.as_ref() as *const _ as raw::HWND) + } + } + + /// Lets this window process the current app event if this is not a text field. + /// + /// Returns whether it was a text field or not. + #[cfg(target_os = "macos")] + pub fn process_current_app_event_if_no_text_field(self) -> bool { + unsafe { + let view = &*(self.raw as *const crate::macos::NSView); + if view.is_kind_of_class(objc2::class!(NSTextField)) + || view.is_kind_of_class(objc2::class!(NSTextView)) + { + return false; + } + let app = crate::macos::ns_app(); + if let Some(current_event) = app.current_event() { + if let Some(window) = view.window() { + window.send_event(¤t_event); + } + } + true + } + } + + /// Also invokes TranslateMessage. + #[cfg(target_family = "windows")] + pub fn process_raw_message(self, msg: raw::MSG) { + unsafe { + winapi::um::winuser::TranslateMessage(&msg as *const _ as _); + Swell::get().SendMessage(self.raw, msg.message, msg.wParam, msg.lParam); + } + } + + #[cfg(target_family = "unix")] + pub fn process_raw_message(self, msg: raw::MSG) { + unsafe { + Swell::get().SendMessage(self.raw, msg.message, msg.wParam, msg.lParam); + } + } + + /// Attention: Doesn't invoke TranslateMessage message on Windows. + pub fn process_raw_message_flexible( + self, + msg: raw::UINT, + wparam: raw::WPARAM, + lparam: raw::LPARAM, + ) -> raw::LRESULT { + unsafe { Swell::get().SendMessage(self.raw, msg, wparam, lparam) } + } + + /// Attention: On macOS, this returns the first subview, not the first child window. + pub fn first_child(self) -> Option { + get_related_window(self, raw::GW_CHILD) + } + + #[cfg(target_os = "linux")] + pub fn get_xlib_handle(&self) -> Result { + // This function actually works correctly we don't have egui enabled on Linux currently + // anyway (full-text search for "wonky" to get an explanation). We could have just let + // this function unused, but I had to comment out gdk_sys crate dependencies because + // cross v2.5 uses Ubuntu < v20, which has an old glib that's not compatible with the + // gdk_sys dependencies. So it wouldn't compile anymore, that's why I commented it out. + todo!() + // let swell = Swell::get(); + // swell.pointers().SWELL_GetOSWindow.ok_or( + // "Couldn't load function SWELL_GetOSWindow. Please use an up-to-date REAPER version!", + // )?; + // let gdk_window = unsafe { + // swell.SWELL_GetOSWindow( + // self.raw, + // reaper_medium::reaper_str!("GdkWindow").as_c_str().as_ptr(), + // ) + // } as *mut gdk_sys::GdkWindow; + // if gdk_window.is_null() { + // return Err("Couldn't get OS window from SWELL window"); + // } + // let gdk_display = unsafe { gdk_sys::gdk_window_get_display(gdk_window) }; + // if gdk_display.is_null() { + // return Err("Couldn't get GDK display from SWELL OS window"); + // } + // let x_display = unsafe { gdk_x11_sys::gdk_x11_display_get_xdisplay(gdk_display as _) }; + // if x_display.is_null() { + // return Err("Couldn't get X display from GDK display"); + // } + // let x_window = unsafe { gdk_x11_sys::gdk_x11_window_get_xid(gdk_window as _) }; + // if x_window == 0 { + // return Err("Couldn't get X window from GDK window"); + // } + // let mut handle = raw_window_handle::XlibHandle::empty(); + // handle.window = x_window as _; + // handle.display = x_display as _; + // Ok(handle) + } + + pub fn set_checked(self, is_checked: bool) { + unsafe { + Swell::get().SendMessage( + self.raw, + raw::BM_SETCHECK, + if is_checked { + raw::BST_CHECKED + } else { + raw::BST_UNCHECKED + } as usize, + 0, + ); + } + } + + pub fn check(self) { + self.set_checked(true); + } + + pub fn uncheck(self) { + self.set_checked(false); + } + + pub fn is_checked(self) -> bool { + let result = unsafe { Swell::get().SendMessage(self.raw, raw::BM_GETCHECK, 0, 0) }; + result == raw::BST_CHECKED as isize + } + + pub fn fill_combo_box_with_data_vec(self, items: Vec<(isize, I)>) { + self.fill_combo_box_with_data(items.into_iter()) + } + + // TODO-low Check if we can take the items by reference. Probably wouldn't make a big + // difference because moving is not a problem in all known cases. + pub fn fill_combo_box_with_data( + self, + items: impl ExactSizeIterator, + ) { + self.clear_combo_box(); + self.maybe_init_combo_box_storage(items.len()); + for (i, (data, item)) in items.enumerate() { + self.insert_combo_box_item_with_data(i, data, item.to_string()); + } + } + + /// Okay to use if approximately less than 100 items, otherwise might become slow. + pub fn fill_combo_box_with_data_small( + self, + items: impl Iterator, + ) { + self.clear_combo_box(); + self.fill_combo_box_with_data_internal(items); + } + + pub fn fill_combo_box_indexed(self, items: impl ExactSizeIterator) { + self.clear_combo_box(); + self.maybe_init_combo_box_storage(items.len()); + for item in items { + self.add_combo_box_item(item.to_string()); + } + } + + pub fn fill_combo_box_indexed_vec(self, items: Vec) { + self.fill_combo_box_indexed(items.into_iter()); + } + + pub fn fill_combo_box_small(self, items: impl Iterator) { + self.clear_combo_box(); + for item in items { + self.add_combo_box_item(item.to_string()); + } + } + + /// Reserves some capacity if there are many items. + /// + /// See https://docs.microsoft.com/en-us/windows/win32/controls/cb-initstorage. + fn maybe_init_combo_box_storage(self, item_count: usize) { + if item_count > 100 { + // The 32 is just a rough estimate. + self.init_combo_box_storage(item_count, 32); + } + } + + fn fill_combo_box_with_data_internal( + self, + items: impl Iterator, + ) { + for (i, (data, item)) in items.enumerate() { + self.insert_combo_box_item_with_data(i, data, item.to_string()); + } + } + + pub fn init_combo_box_storage(self, item_count: usize, item_size: usize) { + unsafe { + Swell::get().SendMessage(self.raw, raw::CB_INITSTORAGE, item_count, item_size as _); + } + } + + pub fn insert_combo_box_item_with_data<'a>( + &self, + index: usize, + data: isize, + label: impl Into>, + ) { + self.insert_combo_box_item(index, label); + self.set_combo_box_item_data(index, data); + } + + pub fn selected_combo_box_item_index(self) -> usize { + let result = unsafe { Swell::get().SendMessage(self.raw, raw::CB_GETCURSEL, 0, 0) }; + result as usize + } + + pub fn selected_combo_box_item_data(self) -> isize { + let index = self.selected_combo_box_item_index(); + self.combo_box_item_data(index) + } + + pub fn insert_combo_box_item<'a>(self, index: usize, label: impl Into>) { + unsafe { + Swell::get().SendMessage( + self.raw, + raw::CB_INSERTSTRING, + index, + label.into().as_lparam(), + ); + } + } + + pub fn add_combo_box_item<'a>(self, label: impl Into>) { + unsafe { + Swell::get().SendMessage(self.raw, raw::CB_ADDSTRING, 0, label.into().as_lparam()); + } + } + + pub fn set_combo_box_item_data(self, index: usize, data: isize) { + unsafe { + Swell::get().SendMessage(self.raw, raw::CB_SETITEMDATA, index, data); + } + } + + pub fn combo_box_item_data(self, index: usize) -> isize { + unsafe { Swell::get().SendMessage(self.raw, raw::CB_GETITEMDATA, index, 0) } + } + + pub fn clear_combo_box(self) { + unsafe { + Swell::get().SendMessage(self.raw, raw::CB_RESETCONTENT, 0, 0); + } + } + + pub fn select_combo_box_item_by_index_checked(self, index: usize) -> Result<(), &'static str> { + if index >= self.combo_box_item_count() { + return Err("index for combo box selection out of bound"); + } + self.select_combo_box_item_by_index_internal(index); + Ok(()) + } + + /// # Panics + /// + /// Panics if the index is out of bound. + pub fn select_combo_box_item_by_index(self, index: usize) { + self.select_combo_box_item_by_index_checked(index) + .expect("couldn't select combo box item by index"); + } + + fn select_combo_box_item_by_index_internal(self, index: usize) { + unsafe { + Swell::get().SendMessage(self.raw, raw::CB_SETCURSEL, index, 0); + } + } + + pub fn select_combo_box_item_by_data(self, item_data: isize) -> Result<(), &'static str> { + let item_index = (0..self.combo_box_item_count()) + .find(|index| self.combo_box_item_data(*index) == item_data) + .ok_or("couldn't find combo box item by item data")?; + self.select_combo_box_item_by_index_internal(item_index); + Ok(()) + } + + pub fn select_only_combo_box_item<'a>(self, label: impl Into>) { + self.clear_combo_box(); + self.select_new_combo_box_item(label); + } + + pub fn select_new_combo_box_item<'a>(self, label: impl Into>) { + self.add_combo_box_item(label); + self.select_combo_box_item_by_index_internal(self.combo_box_item_count() - 1); + } + + pub fn combo_box_item_count(self) -> usize { + let result = unsafe { Swell::get().SendMessage(self.raw, raw::CB_GETCOUNT, 0, 0) }; + result as _ + } + + pub fn close(self) { + unsafe { + Swell::get().SendMessage(self.raw, raw::WM_CLOSE, 0, 0); + } + } + + pub fn set_timer(self, id: usize, duration: Duration) { + unsafe { + Swell::get().SetTimer(self.raw, id, duration.as_millis() as _, None); + } + } + + pub fn kill_timer(self, id: usize) { + unsafe { + Swell::get().KillTimer(self.raw, id); + } + } + + pub fn redraw(self) { + let swell = Swell::get(); + unsafe { + let mut rect = RECT::default(); + swell.GetClientRect(self.raw, &mut rect as _); + swell.InvalidateRect(self.raw, &rect as _, 0); + swell.UpdateWindow(self.raw); + } + } + + pub fn has_focus(&self) -> bool { + Swell::get().GetFocus() == self.raw + } + + pub fn focus(&self) { + unsafe { + Swell::get().SetFocus(self.raw); + } + } + + pub fn set_slider_range(&self, min: u32, max: u32) { + unsafe { + Swell::get().SendMessage(self.raw, raw::TBM_SETRANGE, 0, make_long(min, max)); + } + } + + pub fn set_slider_value(&self, value: u32) { + if self.has_focus() && (key_is_down(raw::VK_LBUTTON) || key_is_down(raw::VK_RBUTTON)) { + // No need to set value if this is the slider which we are currently tracking. + return; + } + unsafe { + Swell::get().SendMessage(self.raw, raw::TBM_SETPOS, 1, value as _); + } + } + + pub fn slider_value(&self) -> u32 { + let result = unsafe { Swell::get().SendMessage(self.raw, raw::TBM_GETPOS, 0, 0) }; + result as _ + } + + /// Returns up to 256 bytes and doesn't do line break conversion. + pub fn text(self) -> Result { + self.text_internal(256) + } + + /// Returns up to 20000 bytes. Also normalizes line breaks to simple unix-style linefeeds. + pub fn multi_line_text(self) -> Result { + let text = self.text_internal(20_000)?; + let fixed_text = if cfg!(windows) { + text.replace("\r\n", "\n") + } else { + text + }; + Ok(fixed_text) + } + + fn text_internal(self, max_size: u32) -> Result { + let (text, result) = with_string_buffer(max_size, |buffer, max_size| unsafe { + Swell::get().GetWindowText(self.raw, buffer, max_size) + }); + if result == 0 { + return Err("handle not found or doesn't support text"); + } + text.into_string().map_err(|_| "non UTF-8") + } + + pub fn set_cached_font(self, descriptor: FontDescriptor) { + if let Some(font) = ViewManager::get().get_font(descriptor) { + self.set_font(font); + } + } + + pub fn set_font(self, font: Hfont) { + unsafe { + Swell::get().SendMessage(self.raw, raw::WM_SETFONT, font.as_ptr() as _, 1); + } + } + + pub fn set_text<'a>(self, text: impl Into>) { + unsafe { Swell::get().SetWindowText(self.raw, text.into().as_ptr()) }; + } + + /// Converts line breaks to Windows-style if necessary. + pub fn set_multi_line_text(self, text: impl AsRef) { + #[cfg(windows)] + { + let fixed_text = text.as_ref().replace('\n', "\r\n"); + self.set_text(fixed_text) + } + #[cfg(unix)] + { + self.set_text(text.as_ref()); + } + } + + pub fn set_text_or_hide<'a>(self, text: Option>>) { + if let Some(t) = text { + self.set_text(t); + self.show(); + } else { + self.hide(); + } + } + + pub fn children(self) -> impl Iterator { + let mut child = self.first_child(); + child.into_iter().chain( + (0..) + .map(move |_| { + child = Window::next_child(child?); + child + }) + .take_while(|w| w.is_some()) + .map(|w| w.unwrap()), + ) + } + + pub fn resize_first_child_according_to_parent(self) { + unsafe { + extern "C" fn resize_proc(arg1: raw::HWND, _arg2: raw::LPARAM) -> raw::BOOL { + if let Some(win) = Window::new(arg1) { + win.resize(win.parent().unwrap().client_size()); + } + 0 + } + Swell::get().EnumChildWindows(self.raw, Some(resize_proc), 0); + } + } + + pub fn focus_first_child(self) { + unsafe { + extern "C" fn focus_proc(arg1: raw::HWND, _arg2: raw::LPARAM) -> raw::BOOL { + if let Some(win) = Window::new(arg1) { + win.focus(); + } + 0 + } + Swell::get().EnumChildWindows(self.raw, Some(focus_proc), 0); + } + } + + pub fn parent(self) -> Option { + Window::new(unsafe { Swell::get().GetParent(self.raw) }) + } + + pub fn set_visible(self, is_shown: bool) { + unsafe { + Swell::get().ShowWindow(self.raw, if is_shown { raw::SW_SHOW } else { raw::SW_HIDE }); + } + } + + pub fn is_visible(self) -> bool { + unsafe { Swell::get().IsWindowVisible(self.raw) } + } + + pub fn is_open(self) -> bool { + unsafe { Swell::get().IsWindow(self.raw) } + } + + pub fn show(self) { + self.set_visible(true); + } + + pub fn hide(self) { + self.set_visible(false); + } + + pub fn set_enabled(self, is_enabled: bool) { + unsafe { + Swell::get().EnableWindow(self.raw, is_enabled.into()); + } + } + + pub fn enable(self) { + self.set_enabled(true); + } + + pub fn disable(self) { + self.set_enabled(false); + } + + pub fn destroy(self) { + unsafe { + Swell::get().DestroyWindow(self.raw); + } + } + + pub fn open_popup_menu_internal(self, menu: Menu, location: Point) -> Option { + let swell = Swell::get(); + let result = unsafe { + swell.TrackPopupMenu( + menu.raw(), + raw::TPM_RETURNCMD as _, + location.x.get() as _, + location.y.get() as _, + 0, + self.raw(), + null(), + ) + }; + if result == 0 { + return None; + } + Some(result as _) + } + + pub fn open_popup_menu( + self, + mut pure_menu: menu_tree::Menu, + location: Point, + ) -> Option { + let menu_bar = MenuBar::new_popup_menu(); + pure_menu.index(1); + menu_tree::add_all_entries_of_menu(menu_bar.menu(), &pure_menu); + let result_index = self.open_popup_menu_internal(menu_bar.menu(), location)?; + let res = pure_menu + .find_item_by_id(result_index) + .expect("selected menu item not found") + .result; + Some(res) + } + + pub fn set_everything_in_dialog_units( + self, + position: Point, + size: Dimensions, + z_order: ZOrder, + ) { + self.set_everything_in_pixels( + self.convert_to_pixels(position), + self.convert_to_pixels(size), + z_order, + ); + } + + pub fn set_everything_in_pixels( + self, + position: Point, + size: Dimensions, + z_order: ZOrder, + ) { + unsafe { + Swell::get().SetWindowPos( + self.raw, + z_order.to_raw(), + position.x.as_raw(), + position.y.as_raw(), + size.width.as_raw(), + size.height.as_raw(), + 0, + ); + } + } + + /// Uses the OS coordinate system. On macOS, y == 0 is at the bottom of the screen! + pub fn window_rect(self) -> raw::RECT { + let mut rect = RECT::default(); + unsafe { + Swell::get().GetWindowRect(self.raw, &mut rect as *mut _); + } + rect + } + + pub fn move_to_pixels(self, point: Point) { + unsafe { + Swell::get().SetWindowPos( + self.raw, + null_mut(), + point.x.as_raw(), + point.y.as_raw(), + 0, + 0, + (raw::SWP_NOSIZE | raw::SWP_NOZORDER) as _, + ); + } + } + + pub fn move_to_dialog_units(self, point: Point) { + self.move_to_pixels(self.convert_to_pixels(point)); + } + + pub fn resize(self, dimensions: Dimensions) { + unsafe { + Swell::get().SetWindowPos( + self.raw, + null_mut(), + 0, + 0, + dimensions.width.as_raw(), + dimensions.height.as_raw(), + // (raw::SWP_NOMOVE | raw::SWP_NOZORDER) as _, + 0, + ); + } + } + + pub fn taborder_first(self) { + /// zorder is used to set taborder, + /// note HWND_BOTTOM should be drawn as the first (to be the last in zorder), + /// and so it is the first child (and so the first in taborder) + /// to check: I have not found HWND_xxx constants in reaper-low bindings... + const HWND_BOTTOM: raw::HWND = 0x1 as raw::HWND; + unsafe { + Swell::get().SetWindowPos( + self.raw, + HWND_BOTTOM, + 0, + 0, + 0, + 0, + (raw::SWP_NOSIZE | raw::SWP_NOMOVE) as _, + ); + } + } + + #[cfg(target_family = "windows")] + pub fn dpi_scaling_factor(&self) -> f64 { + self.dpi() as f64 / 96.0 + } + + #[cfg(target_family = "windows")] + pub fn dpi(&self) -> u32 { + let dynamic_win_api = crate::win::DynamicWinApi::load(); + if let Some(f) = dynamic_win_api.get_dpi_for_window() { + f(self.raw as _) + } else { + 96 + } + } + + /// Converts the given dialog unit point or dimensions to a pixels point or dimensions by using + /// window information. + /// + /// Makes difference on Windows. On Windows the calculation is based on HiDPI settings. The + /// given window must be a dialog window, otherwise it returns the wrong value + /// + /// On other systems the calculation just uses a constant factor. + pub fn convert_to_pixels>>( + &self, + point: impl Into>, + ) -> T { + let point = point.into(); + #[cfg(target_family = "windows")] + { + let mut rect = winapi::shared::windef::RECT { + left: 0, + top: 0, + right: point.x.as_raw(), + bottom: point.y.as_raw(), + }; + unsafe { + winapi::um::winuser::MapDialogRect(self.raw as _, &mut rect as _); + } + Point { + x: Pixels(rect.right as u32), + y: Pixels(rect.bottom as u32), + } + .into() + } + #[cfg(target_family = "unix")] + point.in_pixels().into() + } + + /// The reverse of `convert_to_pixels`. + pub fn convert_to_dialog_units>>( + &self, + point: impl Into>, + ) -> T { + // Because Windows doesn't provide the reverse of MapDialogRect, we calculate the + // scaling factors ourselves. + let (x_factor, y_factor) = self.dialog_unit_to_pixel_scaling_factors(); + let point = point.into(); + Point::new( + DialogUnits((point.x.get() as f64 / x_factor).round() as u32), + DialogUnits((point.y.get() as f64 / y_factor).round() as u32), + ) + .into() + } + + /// Calculates the dialog-unit to pixel scaling factors for each axis. + fn dialog_unit_to_pixel_scaling_factors(&self) -> (f64, f64) { + let du_rect = Point::new(DialogUnits(1000), DialogUnits(1000)); + let px_rect: Point<_> = self.convert_to_pixels(du_rect); + let x_factor = px_rect.x.get() as f64 / du_rect.x.get() as f64; + let y_factor = px_rect.y.get() as f64 / du_rect.y.get() as f64; + (x_factor, y_factor) + } +} + +unsafe impl HasRawWindowHandle for Window { + fn raw_window_handle(&self) -> RawWindowHandle { + #[cfg(target_os = "macos")] + { + let mut handle = raw_window_handle::AppKitHandle::empty(); + handle.ns_view = self.raw as *mut core::ffi::c_void; + RawWindowHandle::AppKit(handle) + } + #[cfg(target_os = "windows")] + { + let mut handle = raw_window_handle::Win32Handle::empty(); + handle.hwnd = self.raw as *mut core::ffi::c_void; + RawWindowHandle::Win32(handle) + } + #[cfg(target_os = "linux")] + { + let handle = self.get_xlib_handle().expect("couldn't get xlib handle"); + RawWindowHandle::Xlib(handle) + } + } +} + +/// We use this on Linux to create a parented baseview/egui window (OpenGL) inside. +#[cfg(target_os = "linux")] +pub struct XBridgeWindow { + /// Our parent. A normal SWELL window as we have many in ReaLearn. Usually an empty window. + parent_window: Window, + /// This is a SWELL window created by SWELL_CreateXBridgeWindow, but it's not the X window! + /// Plus, it's not a normal SWELL window, it's a *special* one. E.g. it can't + /// give us a GdkWindow when queried via GetOSWindow()! The parent window needs to be used + /// for that. + _bridge_window: Window, + /// ID of the X window. This is what it is all about! This is the X window inside which we + /// fire up the OpenGL context. + x_window_id: core::ffi::c_ulong, +} + +#[cfg(target_os = "linux")] +impl XBridgeWindow { + /// Creates an X bridge window inside the given parent window. + /// + /// The parent window must be a normal SWELL window as we have many of them in ReaLearn. + /// Usually an empty window. + pub fn create(parent_window: Window) -> Result { + let mut x_window_id: core::ffi::c_ulong = 0; + let rect: raw::RECT = parent_window.client_rect().into(); + let x_bridge_hwnd = unsafe { + Swell::get().SWELL_CreateXBridgeWindow( + parent_window.raw(), + &mut x_window_id as *mut core::ffi::c_ulong as *mut *mut core::ffi::c_void, + &rect as *const _, + ) + }; + let bridge_window = + Window::new(x_bridge_hwnd).ok_or("SWELL_CreateXBridgeWindow returned null")?; + if x_window_id == 0 { + return Err("SWELL_CreateXBridgeWindow didn't assign the X window ref"); + } + unsafe { + let prop_key = reaper_medium::reaper_str!("SWELL_XBRIDGE_KBHOOK_CHECK"); + let msg = raw::WM_USER as i32 + 100; + Swell::get().SetProp( + bridge_window.raw(), + prop_key.as_c_str().as_ptr(), + msg as *mut core::ffi::c_void, + ); + } + let x_bridge_window = Self { + parent_window, + _bridge_window: bridge_window, + x_window_id, + }; + Ok(x_bridge_window) + } +} + +#[cfg(target_os = "linux")] +unsafe impl HasRawWindowHandle for XBridgeWindow { + fn raw_window_handle(&self) -> RawWindowHandle { + let mut handle = raw_window_handle::XlibHandle::empty(); + // It's important to get the X display from the parent. The bridge_window itself is a + // special SWELL window which doesn't give us the GdkWindow and therefore we can't obtain + // the xlib handle from it and as a consequence also no the X display. + let parent_xlib_handle = self + .parent_window + .get_xlib_handle() + .expect("couldn't get xlib handle of x bridge parent"); + handle.window = self.x_window_id; + handle.display = parent_xlib_handle.display; + RawWindowHandle::Xlib(handle) + } +} + +pub enum SwellWindow { + Normal(Window), + #[cfg(target_os = "linux")] + XBridge(XBridgeWindow), +} + +unsafe impl HasRawWindowHandle for SwellWindow { + fn raw_window_handle(&self) -> RawWindowHandle { + match self { + SwellWindow::Normal(w) => w.raw_window_handle(), + #[cfg(target_os = "linux")] + SwellWindow::XBridge(w) => w.raw_window_handle(), + } + } +} + +fn with_string_buffer( + max_size: u32, + fill_buffer: impl FnOnce(*mut c_char, i32) -> T, +) -> (CString, T) { + let vec: Vec = vec![1; max_size as usize]; + let c_string = unsafe { CString::from_vec_unchecked(vec) }; + let raw = c_string.into_raw(); + let result = fill_buffer(raw, max_size as i32); + let string = unsafe { CString::from_raw(raw) }; + (string, result) +} + +fn make_long(lo: u32, hi: u32) -> isize { + ((lo & 0xffff) | ((hi & 0xffff) << 16)) as _ +} + +fn key_is_down(key: u32) -> bool { + Swell::get().GetAsyncKeyState(key as _) & 0x8000 != 0 +} + +pub enum ZOrder { + Bottom, + Top, +} + +impl ZOrder { + fn to_raw(&self) -> raw::HWND { + match self { + ZOrder::Bottom => 1 as _, + ZOrder::Top => 0 as _, + } + } +} + +fn get_related_window(window: Window, relation: u32) -> Option { + let swell = Swell::get(); + let ptr = unsafe { swell.GetWindow(window.raw, relation as _) }; + Window::new(ptr) +}

    { + TimelessControlEvent::new(payload, NoopTimestamp) + } + + const FB_OPTS: ModeFeedbackOptions = ModeFeedbackOptions { + source_is_virtual: false, + // Count: 101 + max_discrete_source_value: Some(100), + }; +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/press_duration_processor.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/press_duration_processor.rs new file mode 100644 index 0000000..5623b67 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/press_duration_processor.rs @@ -0,0 +1,330 @@ +use crate::{AbsoluteValue, ButtonUsage, FireMode, Interval}; +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +pub struct PressDurationProcessor { + // # Configuration data (stays constant) + fire_mode: FireMode, + interval: Interval, + /// Double press detection: How long to wait for a second press + multi_press_span: Duration, + turbo_rate: Duration, + // # Runtime data (changes during usage) + last_button_press: Option, + button_usage: ButtonUsage, +} + +#[derive(Clone, Debug)] +struct ButtonPress { + time: Instant, + value: AbsoluteValue, + /// Used for after-timeout-keep-firing mode. + time_of_last_turbo_fire: Option, + /// Whether we already fired in response to this press. + /// + /// Important for after-timeout mode: We must not clear the press on first fire, otherwise we can't + /// decide anymore what will happen on release. + fired_already: bool, + /// Number of tap-downs so far. Used for double-press detection. + tap_down_count: u32, + /// Whether the button has been released already. + /// + /// This is relevant for distinction between single and double press. A button press that is + /// released after a short time can still develop into a double press, so we can't clear the press yet. + released: bool, +} + +impl ButtonPress { + pub fn new(value: AbsoluteValue) -> Self { + Self { + time: Instant::now(), + value, + time_of_last_turbo_fire: None, + fired_already: false, + tap_down_count: 1, + released: false, + } + } +} + +const ZERO_DURATION: Duration = Duration::from_millis(0); + +impl Default for PressDurationProcessor { + fn default() -> Self { + Self { + fire_mode: FireMode::Normal, + interval: Interval::new(ZERO_DURATION, ZERO_DURATION), + multi_press_span: Duration::from_millis(300), + turbo_rate: ZERO_DURATION, + last_button_press: None, + button_usage: ButtonUsage::Both, + } + } +} + +impl PressDurationProcessor { + pub fn new( + mode: FireMode, + interval: Interval, + turbo_rate: Duration, + button_usage: ButtonUsage, + ) -> PressDurationProcessor { + PressDurationProcessor { + fire_mode: mode, + interval, + turbo_rate, + button_usage, + ..Default::default() + } + } + + /// Should be called once at initialization time to check if this processor wants that you call + /// `poll()`, regularly. + pub fn wants_to_be_polled(&self) -> bool { + // This must not depend on the button press state! + use FireMode::*; + match self.fire_mode { + AfterTimeout | AfterTimeoutKeepFiring | OnSinglePress => true, + Normal | OnDoublePress => false, + } + } + + pub fn process_press_or_release( + &mut self, + control_value: AbsoluteValue, + button_usage: ButtonUsage, + ) -> Option { + let min = self.interval.min_val(); + let max = self.interval.max_val(); + match self.fire_mode { + FireMode::Normal => { + // In the past, the button usage setting was always checked before processing the press duration. + // In normal fire mode, we keep doing it that way (although the button setting actually doesn't make + // sense in case of min/max being > 0). + if button_usage.should_ignore(control_value) { + return None; + } + if min == ZERO_DURATION && max == ZERO_DURATION { + // No-op case: Just fire immediately. If just min is zero, we don't fire + // immediately but wait for button release. That way we can support different + // stacked press durations (or just "fire on release" behavior no matter the + // press duration if user chooses max very high)! + return Some(control_value); + } + if control_value.is_on() { + // This is a button press. + // Don't fire now because we don't know yet how long it will be pressed. + self.last_button_press = Some(ButtonPress::new(control_value)); + None + } else { + // Looks like a button release. + // Measure duration since button press. + match self.last_button_press.take() { + // Button has not been pressed before. Just ignore. + None => None, + // Button has been pressed before. + Some(press) => { + if self.interval.contains(press.time.elapsed()) { + // Duration within interval. Fire initial press value. + Some(press.value) + } else { + // Released too early or too late. + None + } + } + } + } + } + FireMode::AfterTimeout => { + // This fire mode has been improved in 2.16.0 to let button release fire 0% if not prevented + // by button usage setting. + if min == ZERO_DURATION { + // No-op case: Fire immediately. + if button_usage.should_ignore(control_value) { + return None; + } + return Some(control_value); + } + if control_value.is_on() { + // Button press + self.last_button_press = Some(ButtonPress::new(control_value)); + None + } else { + // Button release + self.process_timeout_button_release(control_value) + } + } + FireMode::AfterTimeoutKeepFiring => { + // In the past, the button usage setting was always checked before processing the press duration. + // We should keep doing it that way in order to not destroy existing setups. Also, that makes it + // possible to keep firing even after releasing a button! + if button_usage.should_ignore(control_value) { + return None; + } + if control_value.is_on() { + // Button press + let mut button_press = ButtonPress::new(control_value); + let result = if min == ZERO_DURATION { + // No initial delay. Fire immediately and count as first turbo fire! + button_press.time_of_last_turbo_fire = Some(Instant::now()); + Some(control_value) + } else { + // Initial delay (wait for timeout). + None + }; + self.last_button_press = Some(button_press); + result + } else { + // Button release + self.process_timeout_button_release(control_value) + } + } + FireMode::OnSinglePress => { + // Button usage setting doesn't make sense here. We need to process both press and release but only + // output press. That's why we started hiding the dropdown in 2.16.1. If someone has previously used + // the button filter together with this fire mode, it would have been a weird misconfiguration, + // qualifying as "undefined behavior". Breaking change is okay. + if control_value.is_on() { + // Button press + if let Some(press) = self.last_button_press.as_mut() { + // Must be more than single press already. + press.tap_down_count += 1; + press.time = Instant::now(); + } else { + // First press + self.last_button_press = Some(ButtonPress::new(control_value)); + }; + None + } else { + // Button release. + let fire_value = { + let press = self.last_button_press.as_mut()?; + if press.tap_down_count != 1 { + return None; + } + let elapsed = press.time.elapsed(); + if elapsed < self.multi_press_span { + press.released = true; + return None; + } + if self.interval.max_val() != ZERO_DURATION + && elapsed > self.interval.max_val() + { + // Exceeded max press time + return None; + } + press.value + }; + self.last_button_press = None; + Some(fire_value) + } + } + FireMode::OnDoublePress => { + // Button usage setting doesn't make sense here. We need to process both press and release but only + // output press. That's why we started hiding the dropdown in 2.16.1. If someone has previously used + // the button filter together with this fire mode, it would have been a weird misconfiguration, + // qualifying as "undefined behavior". Breaking change is okay. + if control_value.is_on() { + if let Some(press) = &self.last_button_press { + // Button was pressed before + let (result, next_press) = if press.time.elapsed() <= self.multi_press_span + { + // Double press detected + (Some(press.value), None) + } else { + // Previous press too long in past. Handle just like first press. + (None, Some(ButtonPress::new(control_value))) + }; + self.last_button_press = next_press; + result + } else { + // First press + self.last_button_press = Some(ButtonPress::new(control_value)); + None + } + } else { + // Button release + None + } + } + } + } + + /// Should be called regularly if `wants_to_be_polled()` returned `true` at initialization + /// time. + pub fn poll(&mut self) -> Option { + match self.fire_mode { + FireMode::Normal | FireMode::OnDoublePress => None, + FireMode::AfterTimeout => { + let last_button_press = self.last_button_press.as_mut()?; + if last_button_press.fired_already + || last_button_press.time.elapsed() < self.interval.min_val() + { + return None; + } + last_button_press.fired_already = true; + Some(last_button_press.value) + } + FireMode::AfterTimeoutKeepFiring => { + let last_button_press = self.last_button_press.as_mut()?; + if let Some(last_turbo) = last_button_press.time_of_last_turbo_fire { + // We are in turbo stage already. + if last_turbo.elapsed() >= self.turbo_rate { + // Subsequent turbo fire! + last_button_press.time_of_last_turbo_fire = Some(Instant::now()); + Some(last_button_press.value) + } else { + // Not yet time for next turbo fire. + None + } + } else if last_button_press.time.elapsed() >= self.interval.min_val() { + // We reached the initial delay. First turbo fire! + last_button_press.time_of_last_turbo_fire = Some(Instant::now()); + Some(last_button_press.value) + } else { + None + } + } + FireMode::OnSinglePress => { + let fire_value = { + let press = self.last_button_press.as_ref()?; + let elapsed = press.time.elapsed(); + if elapsed < self.multi_press_span { + // Can't decide yet if this is a single press. + return None; + } + if self.interval.max_val() > ZERO_DURATION && !press.released { + // The button is still being hold. + if elapsed > self.interval.max_val() { + // The maximum hold time is already exceeded. Reset! + self.last_button_press = None; + } + return None; + } + if press.tap_down_count > 1 { + // Button was pressed more than one time and waiting time is over. Reset! + self.last_button_press = None; + return None; + } + press.value + }; + self.last_button_press = None; + Some(fire_value) + } + } + } + + fn process_timeout_button_release( + &mut self, + control_value: AbsoluteValue, + ) -> Option { + let last_button_press = self.last_button_press.take()?; + if self.button_usage == ButtonUsage::PressOnly { + return None; + } + if last_button_press.time.elapsed() < self.interval.min_val() { + return None; + } + Some(control_value) + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/target.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/target.rs new file mode 100644 index 0000000..a5cbfa9 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/target.rs @@ -0,0 +1,151 @@ +use crate::{AbsoluteValue, UnitValue}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ControlType { + /// Targets which don't have a step size, targets that have just on/off states and trigger + /// targets. + AbsoluteContinuous, + /// The only difference to AbsoluteContinuous is that it gets retriggered even it already has + /// the desired target value. + /// + /// This is commonly used for targets that can be triggered only instead of having a possible + /// range of values (e.g. load FX snapshot). But it can also be used for targets that can be + /// triggered with different values and still are somehow "trigger-like" + /// (e.g. send MIDI message). + AbsoluteContinuousRetriggerable, + /// Imagine a "tempo" target: Musical tempo is continuous in nature and still you might want to + /// offer the possibility to round on fraction-less bpm values. Discrete and continuous at the + /// same time. + /// + /// In more recent versions, the same can be achieved using target value sequences. + AbsoluteContinuousRoundable { rounding_step_size: UnitValue }, + /// Targets which have a grid of discrete values (and therefore a step size). + AbsoluteDiscrete { + atomic_step_size: UnitValue, + is_retriggerable: bool, + }, + /// If target wants to be controlled via relative increments. + Relative, + /// For virtual continuous targets (that don't know about the nature of the real target). + VirtualMulti, + /// For virtual button targets (that don't know about the nature of the real target). + VirtualButton, +} + +impl ControlType { + pub fn is_relative(&self) -> bool { + *self == ControlType::Relative + } + + pub fn is_retriggerable(&self) -> bool { + matches!( + self, + ControlType::AbsoluteContinuousRetriggerable + | ControlType::AbsoluteDiscrete { + is_retriggerable: true, + .. + } + ) + } + + pub fn step_size(&self) -> Option { + use ControlType::*; + match self { + AbsoluteContinuousRoundable { rounding_step_size } => Some(*rounding_step_size), + AbsoluteDiscrete { + atomic_step_size, .. + } => Some(*atomic_step_size), + _ => None, + } + } + + pub fn discrete_count(&self) -> Option { + Some(self.discrete_max()? + 1) + } + + pub fn discrete_max(&self) -> Option { + let step_size = self.step_size()?; + if step_size.is_zero() { + return None; + } + Some((1.0 / step_size.get()).round() as u32) + } + + pub fn is_virtual(&self) -> bool { + use ControlType::*; + matches!(self, VirtualMulti | VirtualButton) + } +} + +pub trait Target<'a> { + type Context: Copy; + + /// Should return the current value of the target. + /// + /// Some targets don't have the notion of a current value, e.g. virtual targets (which are just + /// mediators really). Other targets might momentarily not be able to return a current value. + /// In such cases, `None` should be returned so that the mode can handle this situation + /// gracefully. Of course, some mode features won't work without knowing the current value, + /// but others will still work. + fn current_value(&self, context: Self::Context) -> Option { + let _ = context; + None + } + + fn control_type(&self, context: Self::Context) -> ControlType { + let _ = context; + ControlType::AbsoluteContinuous + } +} + +/// Some standardized property keys. +pub mod target_prop_keys { + /// Short text representing the current target value, including a possible unit. + /// + /// This is the default value shown if textual feedback is enabled and the textual feedback + /// expression is empty. Choose the textual representation that's most likely to be desired. + /// If there's some name to display, prefer that name over a numeric representation. + /// + /// Examples: + /// + /// - Track: Volume → "-6.00 dB" + /// - Track: Mute/unmute → "Mute" + /// - Project: Browse tracks → "Guitar" + pub const TEXT_VALUE: &str = "text_value"; + + /// Non-normalized representing the current target value as a *human-friendly number* + /// (type: [`crate::NumericValue`]). + /// + /// The purpose of this is to allow for more freedom in formatting numerical target values than + /// when using [`TEXT_VALUE`]. Future versions of ReaLearn might extend textual feedback + /// expressions in a way so the user can define how exactly the numerical value is presented + /// (decimal points etc.). + /// + /// "Human-readable" also means that if it's a position, then it's really a position number + /// (one-rooted), not an index number (zero-rooted). + /// + /// - Track: Volume → -6.00 + /// - Track: Mute/unmute → 1.0 + /// - Project: Browse tracks → 5 + pub const NUMERIC_VALUE: &str = "numeric_value"; + + /// Unit of the non-normalized number in human-friendly form. + /// + /// - Track: Volume → "dB" + /// - Track: Mute/unmute → "" + /// - Project: Browse tracks → "" + pub const NUMERIC_VALUE_UNIT: &str = "numeric_value.unit"; + + /// Normalized value in the unit interval. You can think of it as a percentage. + /// + /// This value is available for most targets and good if you need a totally uniform + /// representation of the target value that doesn't differ between target types. By default, + /// this is formatted as percentage. Future versions of ReaLearn might offer user-defined + /// formatting. E.g. this will also be the preferred form to format on/off states in a + /// custom way (where 0% represents "off"). + /// + /// - Track: Volume → 0.5 + /// - Track: Mute/unmute → 0.0 + /// - Project: Browse tracks → 0.7 + pub const NORMALIZED_VALUE: &str = "normalized_value"; +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/test_util.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/test_util.rs new file mode 100644 index 0000000..0bec40e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/test_util.rs @@ -0,0 +1,80 @@ +use crate::{ + AbsoluteValue, ControlType, ControlValueKind, FeedbackScript, FeedbackScriptInput, + FeedbackScriptOutput, Target, Transformation, TransformationInput, TransformationOutput, +}; +use base::hash_util::NonCryptoHashSet; +use std::borrow::Cow; +use std::error::Error; + +pub struct TestTarget { + pub current_value: Option, + pub control_type: ControlType, +} + +impl<'a> Target<'a> for TestTarget { + type Context = (); + + fn current_value(&self, _: ()) -> Option { + self.current_value + } + + fn control_type(&self, _: ()) -> ControlType { + self.control_type + } +} + +pub struct TestTransformation { + transformer: Box Result>, + produced_kind: ControlValueKind, +} + +impl TestTransformation { + pub fn new( + produced_kind: ControlValueKind, + transformer: impl Fn(f64) -> Result + 'static, + ) -> TestTransformation { + Self { + transformer: Box::new(transformer), + produced_kind, + } + } +} + +impl Transformation for TestTransformation { + type AdditionalInput = (); + + fn transform( + &self, + input: TransformationInput, + ) -> Result { + let out_val = (self.transformer)(input.event.input_value)?; + let out = TransformationOutput { + produced_kind: self.produced_kind, + value: Some(out_val), + instruction: None, + }; + Ok(out) + } + + fn wants_to_be_polled(&self) -> bool { + false + } +} + +pub struct TestFeedbackScript; + +impl FeedbackScript<'_> for TestFeedbackScript { + type AdditionalInput = (); + + fn feedback( + &self, + _: FeedbackScriptInput, + _: (), + ) -> Result> { + unimplemented!() + } + + fn used_props(&self) -> Result, Box> { + Ok(Default::default()) + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/transformation.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/transformation.rs new file mode 100644 index 0000000..8f57094 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/transformation.rs @@ -0,0 +1,100 @@ +use crate::{ + ControlValue, ControlValueKind, DiscreteIncrement, Fraction, UnitIncrement, UnitValue, +}; +use std::time::Duration; + +/// Represents an arbitrary transformation from one unit value into another one, intended to be +/// implemented by using some form of expression language. +pub trait Transformation { + type AdditionalInput: Default; + + /// Applies the transformation. + /// + /// Should execute fast. If you use an expression or scripting language, make sure that you + /// compile the expression beforehand. + fn transform( + &self, + input: TransformationInput, + ) -> Result; + + fn wants_to_be_polled(&self) -> bool; +} + +#[derive(Default)] +pub struct TransformationInput { + pub event: TransformationInputEvent, + pub context: TransformationInputContext, + /// Consumers can pass through more stuff to the transformation script if they want. + pub additional_input: A, +} + +#[derive(Default)] +pub struct TransformationInputEvent { + pub input_value: f64, + pub timestamp: Duration, +} + +#[derive(Default)] +pub struct TransformationInputContext { + pub output_value: f64, + /// Duration since last interaction. For modulations/transitions only. + pub rel_time: Duration, +} + +/// Output of the transformation. +/// +/// If both `value` and `instruction` are `None`, it means that the target shouldn't be invoked: +/// +/// - Usually, each repeated invocation always results in a target invocation (unless the target is +/// not retriggerable and already has the desired value). +/// - Sometimes this is not desired. In this case, one can return `none`, in which case the target +/// will not be touched. +/// - Good for transitions that are not continuous, especially if other mappings want to control +/// the parameter as well from time to time. +#[derive(Copy, Clone, Debug)] +pub struct TransformationOutput { + /// The kind of control values which this transformation produces. + /// + /// This should always be available, as it might be queried statically for GUI purposes. + pub produced_kind: ControlValueKind, + pub value: Option, + pub instruction: Option, +} + +impl TransformationOutput { + pub fn extract_control_value(&self, in_discrete_max: Option) -> Option { + let raw = self.value?; + let cv = match self.produced_kind { + ControlValueKind::AbsoluteContinuous => { + ControlValue::AbsoluteContinuous(UnitValue::new_clamped(raw)) + } + ControlValueKind::RelativeDiscrete => { + let inc = raw.round() as i32; + ControlValue::RelativeDiscrete(DiscreteIncrement::new_checked(inc)?) + } + ControlValueKind::RelativeContinuous => { + ControlValue::RelativeContinuous(UnitIncrement::new_clamped_checked(raw)?) + } + ControlValueKind::AbsoluteDiscrete => { + let actual = raw.round() as _; + let max = match in_discrete_max { + None => actual, + Some(max) => std::cmp::max(max, actual), + }; + ControlValue::AbsoluteDiscrete(Fraction::new(actual, max)) + } + }; + Some(cv) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum TransformationInstruction { + /// This stops repeated invocation of the formula until the mapping is triggered again. + /// + /// - Good for building transitions with a defined end. + /// - Stopping the invocation at some point is also important if the same parameter shall be + /// controlled by other mappings as well. If multiple mappings continuously change the target + /// parameter, only the last one wins. + Stop, +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/base.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/base.rs new file mode 100644 index 0000000..ba7bc02 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/base.rs @@ -0,0 +1,473 @@ +use crate::mode::value_sequence::parser::RawEntry; +use crate::{ + format_percentage_without_unit, parse_percentage_without_unit, UnitValue, BASE_EPSILON, +}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::convert::TryInto; +use std::fmt; +use std::fmt::{Debug, Display, Formatter, Write}; + +#[derive(Clone, Eq, PartialEq, Debug, Default, SerializeDisplay, DeserializeFromStr)] +pub struct ValueSequence { + entries: Vec, +} + +impl ValueSequence { + pub fn parse( + single_value_parser: &P, + input: &str, + ) -> Result { + let (_, raw_entries) = + super::parser::parse_entries(input).map_err(|_| "couldn't parse sequence")?; + let sequence = ValueSequence { + entries: { + raw_entries + .iter() + .map(|e| match e { + RawEntry::SingleValue(e) => ValueSequenceEntry::SingleValue( + single_value_parser.parse_value(e).unwrap_or_default(), + ), + RawEntry::Range(e) => { + let entry = ValueSequenceRangeEntry { + from: single_value_parser + .parse_value(e.simple_range.from) + .unwrap_or_default(), + to: single_value_parser + .parse_value(e.simple_range.to) + .unwrap_or_default(), + step_size: e + .step_size + .map(|s| single_value_parser.parse_step(s).unwrap_or_default()), + }; + ValueSequenceEntry::Range(entry) + } + }) + .collect() + }, + }; + Ok(sequence) + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn entries(&self) -> &[ValueSequenceEntry] { + &self.entries + } + + pub fn displayable<'a>(&'a self, f: &'a impl ValueFormatter) -> impl Display + 'a { + DisplayableValueSequence { + value_sequence: self, + value_formatter: f, + } + } + + pub fn unpack(&self, default_step_size: UnitValue) -> Vec { + self.entries + .iter() + .flat_map(|e| WithDefaultStepSize::new(e, default_step_size)) + .collect() + } +} + +struct WithDefaultStepSize<'a, A> { + actual: &'a A, + default_step_size: UnitValue, +} + +impl<'a, A> WithDefaultStepSize<'a, A> { + fn new(actual: &'a A, default_step_size: UnitValue) -> Self { + Self { + actual, + default_step_size, + } + } +} + +struct WithFormatter<'a, A, F: ValueFormatter> { + actual: &'a A, + value_formatter: &'a F, +} + +impl<'a, A, F: ValueFormatter> WithFormatter<'a, A, F> { + fn new(actual: &'a A, value_formatter: &'a F) -> Self { + WithFormatter { + actual, + value_formatter, + } + } +} + +struct DisplayableValueSequence<'a, F> { + value_sequence: &'a ValueSequence, + value_formatter: &'a F, +} + +impl Display for DisplayableValueSequence<'_, F> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let snippets: Vec<_> = self + .value_sequence + .entries + .iter() + .map(|e| WithFormatter::new(e, self.value_formatter).to_string()) + .collect(); + let csv = snippets.join(", "); + f.write_str(&csv) + } +} + +pub trait ValueFormatter { + fn format_value(&self, value: UnitValue, f: &mut fmt::Formatter) -> fmt::Result; + fn format_step(&self, value: UnitValue, f: &mut fmt::Formatter) -> fmt::Result; +} + +pub trait ValueParser { + fn parse_value(&self, text: &str) -> Result; + fn parse_step(&self, text: &str) -> Result; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ValueSequenceEntry { + SingleValue(UnitValue), + Range(ValueSequenceRangeEntry), +} + +impl Display for WithFormatter<'_, ValueSequenceEntry, F> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ValueSequenceEntry as E; + match self.actual { + E::SingleValue(v) => self.value_formatter.format_value(*v, f), + E::Range(r) => WithFormatter::new(r, self.value_formatter).fmt(f), + } + } +} + +impl IntoIterator for WithDefaultStepSize<'_, ValueSequenceEntry> { + type Item = UnitValue; + type IntoIter = ValueSequenceRangeIterator; + + fn into_iter(self) -> ValueSequenceRangeIterator { + use ValueSequenceEntry as E; + match self.actual { + E::SingleValue(uv) => { + let simple_range_entry = ValueSequenceRangeEntry { + from: *uv, + to: *uv, + step_size: Some(UnitValue::MAX), + }; + WithDefaultStepSize::new(&simple_range_entry, self.default_step_size).into_iter() + } + E::Range(r) => WithDefaultStepSize::new(r, self.default_step_size).into_iter(), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct ValueSequenceRangeEntry { + from: UnitValue, + to: UnitValue, + step_size: Option, +} + +impl Display for WithFormatter<'_, ValueSequenceRangeEntry, F> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.value_formatter.format_value(self.actual.from, f)?; + f.write_str(" - ")?; + self.value_formatter.format_value(self.actual.to, f)?; + if let Some(step_size) = self.actual.step_size { + f.write_str(" (")?; + self.value_formatter.format_step(step_size, f)?; + f.write_char(')')?; + } + Ok(()) + } +} + +impl IntoIterator for WithDefaultStepSize<'_, ValueSequenceRangeEntry> { + type Item = UnitValue; + type IntoIter = ValueSequenceRangeIterator; + + fn into_iter(self) -> ValueSequenceRangeIterator { + ValueSequenceRangeIterator { + i: self.actual.from.get(), + from: self.actual.from.get(), + to: self.actual.to.get(), + step_size: self + .actual + .step_size + .unwrap_or(self.default_step_size) + .get(), + } + } +} + +pub struct ValueSequenceRangeIterator { + i: f64, + from: f64, + to: f64, + step_size: f64, +} + +impl Iterator for ValueSequenceRangeIterator { + type Item = UnitValue; + + fn next(&mut self) -> Option { + if self.step_size == 0.0 { + return None; + } + let i = self.i; + self.i = if self.from <= self.to { + // Forward + if i > self.to + BASE_EPSILON { + return None; + } + i + self.step_size + } else { + // Backward + if i + BASE_EPSILON < self.to { + return None; + } + i - self.step_size + }; + i.try_into().ok() + } +} + +impl Display for ValueSequence { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.displayable(&UnitValueIo).fmt(f) + } +} + +impl std::str::FromStr for ValueSequence { + type Err = &'static str; + + fn from_str(input: &str) -> Result { + ValueSequence::parse(&UnitValueIo, input) + } +} + +pub struct UnitValueIo; + +impl ValueFormatter for UnitValueIo { + fn format_value(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + write!(f, "{value}") + } + + fn format_step(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + self.format_value(value, f) + } +} + +impl ValueParser for UnitValueIo { + fn parse_value(&self, text: &str) -> Result { + text.parse() + } + + fn parse_step(&self, text: &str) -> Result { + self.parse_value(text) + } +} + +pub struct PercentIo; + +impl ValueFormatter for PercentIo { + fn format_value(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + f.write_str(&format_percentage_without_unit(value.get())) + } + + fn format_step(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + self.format_value(value, f) + } +} + +impl ValueParser for PercentIo { + fn parse_value(&self, text: &str) -> Result { + parse_percentage_without_unit(text)?.try_into() + } + + fn parse_step(&self, text: &str) -> Result { + self.parse_value(text) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_abs_diff_eq; + + struct TestValueContext; + + impl ValueFormatter for TestValueContext { + fn format_value(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", (value.get() * 1000.0) as u32) + } + fn format_step(&self, value: UnitValue, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", value) + } + } + + impl ValueParser for TestValueContext { + fn parse_value(&self, text: &str) -> Result { + let number: u32 = text.parse().map_err(|_| "")?; + (number as f64 / 1000.0).try_into() + } + + fn parse_step(&self, text: &str) -> Result { + text.parse() + } + } + + fn default_test_step_size() -> UnitValue { + UnitValue::new(0.001) + } + + #[test] + fn simple_values() { + // Given + let sequence = ValueSequence::parse(&TestValueContext, "250, 500, 750, 500, 1000").unwrap(); + // When + // Then + assert_eq!( + sequence.entries(), + &[ + ValueSequenceEntry::SingleValue(uv(0.25)), + ValueSequenceEntry::SingleValue(uv(0.50)), + ValueSequenceEntry::SingleValue(uv(0.75)), + ValueSequenceEntry::SingleValue(uv(0.50)), + ValueSequenceEntry::SingleValue(uv(1.00)), + ] + ); + assert_eq!( + sequence.unpack(default_test_step_size()), + vec![uv(0.25), uv(0.50), uv(0.75), uv(0.50), uv(1.00)] + ); + assert_eq!( + &sequence.displayable(&TestValueContext).to_string(), + "250, 500, 750, 500, 1000" + ); + assert_eq!(&sequence.to_string(), "0.25, 0.5, 0.75, 0.5, 1") + } + + #[test] + fn ranges_native() { + // Given + let sequence = ValueSequence::parse( + &TestValueContext, + "250 - 255, 500 - 501, 750 - 755 (0.002), 520 - 500(0.01), 999", + ) + .unwrap(); + // When + // Then + assert_eq!( + sequence.entries(), + &[ + ValueSequenceEntry::Range(ValueSequenceRangeEntry { + from: uv(0.250), + to: uv(0.255), + step_size: None + }), + ValueSequenceEntry::Range(ValueSequenceRangeEntry { + from: uv(0.500), + to: uv(0.501), + step_size: None + }), + ValueSequenceEntry::Range(ValueSequenceRangeEntry { + from: uv(0.750), + to: uv(0.755), + step_size: Some(uv(0.002)) + }), + ValueSequenceEntry::Range(ValueSequenceRangeEntry { + from: uv(0.520), + to: uv(0.500), + step_size: Some(uv(0.010)) + }), + ValueSequenceEntry::SingleValue(uv(0.999)) + ] + ); + assert_eq!( + sequence.unpack(default_test_step_size()), + vec![ + uv(0.250), + uv(0.251), + uv(0.252), + uv(0.253), + uv(0.254), + uv(0.255), + uv(0.500), + uv(0.501), + uv(0.750), + uv(0.752), + uv(0.754), + uv(0.520), + uv(0.510), + uv(0.500), + uv(0.999) + ] + ); + assert_eq!( + &sequence.displayable(&TestValueContext).to_string(), + "250 - 255, 500 - 501, 750 - 755 (0.002), 520 - 500 (0.01), 999" + ); + assert_eq!( + &sequence.to_string(), + "0.25 - 0.255, 0.5 - 0.501, 0.75 - 0.755 (0.002), 0.52 - 0.5 (0.01), 0.999" + ) + } + + #[test] + fn ranges_rounding() { + // Given + let sequence = ValueSequence::parse(&PercentIo, "25 - 50, 75, 50, 10").unwrap(); + // When + let unpacked = sequence.unpack(UnitValue::new(0.01)); + // Then + assert_eq!(unpacked.len(), 29); + let at = |i| *unpacked.get(i).unwrap(); + assert_abs_diff_eq!(at(0), uv(0.25)); + assert_abs_diff_eq!(at(1), uv(0.26)); + assert_abs_diff_eq!(at(2), uv(0.27)); + assert_abs_diff_eq!(at(3), uv(0.28)); + assert_abs_diff_eq!(at(4), uv(0.29)); + assert_abs_diff_eq!(at(5), uv(0.30)); + assert_abs_diff_eq!(at(6), uv(0.31)); + assert_abs_diff_eq!(at(7), uv(0.32)); + assert_abs_diff_eq!(at(8), uv(0.33)); + assert_abs_diff_eq!(at(9), uv(0.34)); + assert_abs_diff_eq!(at(10), uv(0.35)); + assert_abs_diff_eq!(at(11), uv(0.36)); + assert_abs_diff_eq!(at(12), uv(0.37)); + assert_abs_diff_eq!(at(13), uv(0.38)); + assert_abs_diff_eq!(at(14), uv(0.39)); + assert_abs_diff_eq!(at(15), uv(0.40)); + assert_abs_diff_eq!(at(16), uv(0.41)); + assert_abs_diff_eq!(at(17), uv(0.42)); + assert_abs_diff_eq!(at(18), uv(0.43)); + assert_abs_diff_eq!(at(19), uv(0.44)); + assert_abs_diff_eq!(at(20), uv(0.45)); + assert_abs_diff_eq!(at(21), uv(0.46)); + assert_abs_diff_eq!(at(22), uv(0.47)); + assert_abs_diff_eq!(at(23), uv(0.48)); + assert_abs_diff_eq!(at(24), uv(0.49)); + assert_abs_diff_eq!(at(25), uv(0.50)); + assert_abs_diff_eq!(at(26), uv(0.75)); + assert_abs_diff_eq!(at(27), uv(0.50)); + assert_abs_diff_eq!(at(28), uv(0.10)); + } + + #[test] + fn range_corner_cases() { + // Given + let sequence = + ValueSequence::parse(&TestValueContext, "250 - 250, 500 - 501 (0), 601 - 600 (0)") + .unwrap(); + // When + // Then + assert_eq!(sequence.unpack(default_test_step_size()), vec![uv(0.250)]); + } + + fn uv(value: f64) -> UnitValue { + UnitValue::new(value) + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/mod.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/mod.rs new file mode 100644 index 0000000..aacb961 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/mod.rs @@ -0,0 +1,3 @@ +mod base; +mod parser; +pub use base::*; diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/parser.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/parser.rs new file mode 100644 index 0000000..2acdbc9 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/mode/value_sequence/parser.rs @@ -0,0 +1,165 @@ +use nom::branch::alt; +use nom::character::complete::{space0, space1}; +use nom::combinator::opt; +use nom::multi::separated_list0; +use nom::sequence::separated_pair; +use nom::{ + bytes::complete::is_not, character::complete::char, sequence::delimited, sequence::tuple, + IResult, +}; + +fn parse_value(input: &str) -> IResult<&str, &str> { + let parser = is_not("(), "); + parser(input) +} + +fn parse_step_size(input: &str) -> IResult<&str, &str> { + delimited( + tuple((char('('), space0)), + parse_value, + tuple((space0, char(')'))), + )(input) +} + +fn parse_simple_range(input: &str) -> IResult<&str, RawSimpleRange> { + let mut parser = separated_pair(parse_value, tuple((space1, char('-'), space1)), parse_value); + let (remainder, (from, to)) = parser(input)?; + Ok((remainder, RawSimpleRange { from, to })) +} + +fn parse_full_range(input: &str) -> IResult<&str, RawFullRange> { + let mut parser = tuple((parse_simple_range, space0, opt(parse_step_size))); + let (remainder, (simple_range, _, step_size)) = parser(input)?; + Ok((remainder, RawFullRange::new(simple_range, step_size))) +} + +fn parse_range_entry(input: &str) -> IResult<&str, RawEntry> { + let (remainder, range) = parse_full_range(input)?; + Ok((remainder, RawEntry::Range(range))) +} + +fn parse_single_value_entry(input: &str) -> IResult<&str, RawEntry> { + let (remainder, single_value) = parse_value(input)?; + Ok((remainder, RawEntry::SingleValue(single_value))) +} + +fn parse_entry(input: &str) -> IResult<&str, RawEntry> { + let mut parser = alt((parse_range_entry, parse_single_value_entry)); + parser(input) +} + +pub fn parse_entries(input: &str) -> IResult<&str, Vec> { + let mut parser = separated_list0(tuple((space0, char(','), space0)), parse_entry); + parser(input) +} + +#[derive(Eq, PartialEq, Debug)] +pub enum RawEntry<'a> { + SingleValue(&'a str), + Range(RawFullRange<'a>), +} + +#[derive(Eq, PartialEq, Debug)] +pub struct RawFullRange<'a> { + pub simple_range: RawSimpleRange<'a>, + pub step_size: Option<&'a str>, +} + +impl<'a> RawFullRange<'a> { + fn new(simple_range: RawSimpleRange<'a>, step_size: Option<&'a str>) -> Self { + Self { + simple_range, + step_size, + } + } +} + +#[derive(Eq, PartialEq, Debug)] +pub struct RawSimpleRange<'a> { + pub from: &'a str, + pub to: &'a str, +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> RawSimpleRange<'a> { + #[cfg(test)] + fn new(from: &'a str, to: &'a str) -> Self { + Self { from, to } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step_size() { + assert_eq!(parse_step_size("(0.5)"), Ok(("", "0.5"))); + assert_eq!(parse_step_size("(8)"), Ok(("", "8"))); + assert_eq!(parse_step_size("( abc )"), Ok(("", "abc"))); + } + + #[test] + fn simple_range() { + assert_eq!( + parse_simple_range("5 - 10"), + Ok(("", RawSimpleRange::new("5", "10"))) + ); + assert_eq!( + parse_simple_range("5.0 - 10.0"), + Ok(("", RawSimpleRange::new("5.0", "10.0"))) + ); + assert_eq!( + parse_simple_range("a - f"), + Ok(("", RawSimpleRange::new("a", "f"))) + ); + } + + #[test] + fn full_range() { + assert_eq!( + parse_full_range("5 - 10"), + Ok(("", RawFullRange::new(RawSimpleRange::new("5", "10"), None))) + ); + assert_eq!( + parse_full_range("5 - 10 (0.1)"), + Ok(( + "", + RawFullRange::new(RawSimpleRange::new("5", "10"), Some("0.1")) + )) + ); + } + + #[test] + fn entry() { + assert_eq!( + parse_entry("5 - 10 (0.1)"), + Ok(( + "", + RawEntry::Range(RawFullRange::new( + RawSimpleRange::new("5", "10"), + Some("0.1") + )) + )) + ); + assert_eq!(parse_entry("75.5"), Ok(("", RawEntry::SingleValue("75.5")))); + } + + #[test] + fn entries() { + assert_eq!( + parse_entries("5 - 10 (0.1), 12.5, 15 - 20"), + Ok(( + "", + vec![ + RawEntry::Range(RawFullRange::new( + RawSimpleRange::new("5", "10"), + Some("0.1") + )), + RawEntry::SingleValue("12.5"), + RawEntry::Range(RawFullRange::new(RawSimpleRange::new("15", "20"), None)) + ] + )) + ); + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/color_util.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/color_util.rs new file mode 100644 index 0000000..65a5ee8 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/color_util.rs @@ -0,0 +1,22 @@ +use crate::RgbColor; + +// Initially taken from https://github.com/jamesmunns/launch-rs/blob/master/lib/src/color.rs +pub fn find_closest_color_in_palette(color: RgbColor, palette: &[RgbColor]) -> u8 { + let (red, green, blue) = (color.r(), color.g(), color.b()); + let mut ifurthest = 0usize; + let mut furthest = 3 * 255_i32.pow(2) + 1; + for (i, c) in palette.iter().enumerate() { + if red == c.r() && green == c.g() && blue == c.b() { + // Exact match + return i as u8; + } + let distance = (red as i32 - c.r() as i32).pow(2) + + (green as i32 - c.g() as i32).pow(2) + + (blue as i32 - c.b() as i32).pow(2); + if distance < furthest { + furthest = distance; + ifurthest = i; + } + } + ifurthest as u8 +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/launchpad.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/launchpad.rs new file mode 100644 index 0000000..2dbe5ea --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/launchpad.rs @@ -0,0 +1,136 @@ +//! Initially taken from https://github.com/jamesmunns/launch-rs/blob/master/lib/src/color.rs +use crate::RgbColor; + +/// http://launchpaddr.com/mk2palette/ +pub const COLOR_PALETTE: [RgbColor; 128] = [ + // 0..64 + RgbColor::new(0x00, 0x00, 0x00), + RgbColor::new(0x1c, 0x1c, 0x1c), + RgbColor::new(0x7c, 0x7c, 0x7c), + RgbColor::new(0xfc, 0xfc, 0xfc), + RgbColor::new(0xff, 0x4e, 0x48), + RgbColor::new(0xfe, 0x0a, 0x00), + RgbColor::new(0x5a, 0x00, 0x00), + RgbColor::new(0x18, 0x00, 0x02), + RgbColor::new(0xff, 0xbc, 0x63), + RgbColor::new(0xff, 0x57, 0x00), + RgbColor::new(0x5a, 0x1d, 0x00), + RgbColor::new(0x24, 0x18, 0x02), + RgbColor::new(0xfd, 0xfd, 0x21), + RgbColor::new(0xfd, 0xfd, 0x00), + RgbColor::new(0x58, 0x58, 0x00), + RgbColor::new(0x18, 0x18, 0x00), + RgbColor::new(0x81, 0xfd, 0x2b), + RgbColor::new(0x40, 0xfd, 0x01), + RgbColor::new(0x16, 0x58, 0x00), + RgbColor::new(0x13, 0x28, 0x01), + RgbColor::new(0x35, 0xfd, 0x2b), + RgbColor::new(0x00, 0xfe, 0x00), + RgbColor::new(0x00, 0x58, 0x01), + RgbColor::new(0x00, 0x18, 0x00), + RgbColor::new(0x35, 0xfc, 0x47), + RgbColor::new(0x00, 0xfe, 0x00), + RgbColor::new(0x00, 0x58, 0x01), + RgbColor::new(0x00, 0x18, 0x00), + RgbColor::new(0x32, 0xfd, 0x7f), + RgbColor::new(0x00, 0xfd, 0x3a), + RgbColor::new(0x01, 0x58, 0x14), + RgbColor::new(0x00, 0x1c, 0x0e), + RgbColor::new(0x2f, 0xfc, 0xb1), + RgbColor::new(0x00, 0xfb, 0x91), + RgbColor::new(0x01, 0x57, 0x32), + RgbColor::new(0x01, 0x18, 0x10), + RgbColor::new(0x39, 0xbe, 0xff), + RgbColor::new(0x00, 0xa7, 0xff), + RgbColor::new(0x01, 0x40, 0x51), + RgbColor::new(0x00, 0x10, 0x18), + RgbColor::new(0x41, 0x86, 0xff), + RgbColor::new(0x00, 0x50, 0xff), + RgbColor::new(0x01, 0x1a, 0x5a), + RgbColor::new(0x01, 0x06, 0x19), + RgbColor::new(0x47, 0x47, 0xff), + RgbColor::new(0x00, 0x00, 0xfe), + RgbColor::new(0x00, 0x00, 0x5a), + RgbColor::new(0x00, 0x00, 0x18), + RgbColor::new(0x83, 0x47, 0xff), + RgbColor::new(0x50, 0x00, 0xff), + RgbColor::new(0x16, 0x00, 0x67), + RgbColor::new(0x0a, 0x00, 0x32), + RgbColor::new(0xff, 0x48, 0xfe), + RgbColor::new(0xff, 0x00, 0xfe), + RgbColor::new(0x5a, 0x00, 0x5a), + RgbColor::new(0x18, 0x00, 0x18), + RgbColor::new(0xfb, 0x4e, 0x83), + RgbColor::new(0xff, 0x07, 0x53), + RgbColor::new(0x5a, 0x02, 0x1b), + RgbColor::new(0x21, 0x01, 0x10), + RgbColor::new(0xff, 0x19, 0x01), + RgbColor::new(0x9a, 0x35, 0x00), + RgbColor::new(0x7a, 0x51, 0x01), + RgbColor::new(0x3e, 0x65, 0x00), + // 64..128 + RgbColor::new(0x01, 0x38, 0x00), + RgbColor::new(0x00, 0x54, 0x32), + RgbColor::new(0x00, 0x53, 0x7f), + RgbColor::new(0x00, 0x00, 0xfe), + RgbColor::new(0x01, 0x44, 0x4d), + RgbColor::new(0x1a, 0x00, 0xd1), + RgbColor::new(0x7c, 0x7c, 0x7c), + RgbColor::new(0x20, 0x20, 0x20), + RgbColor::new(0xff, 0x0a, 0x00), + RgbColor::new(0xba, 0xfd, 0x00), + RgbColor::new(0xac, 0xec, 0x00), + RgbColor::new(0x56, 0xfd, 0x00), + RgbColor::new(0x00, 0x88, 0x00), + RgbColor::new(0x01, 0xfc, 0x7b), + RgbColor::new(0x00, 0xa7, 0xff), + RgbColor::new(0x02, 0x1a, 0xff), + RgbColor::new(0x35, 0x00, 0xff), + RgbColor::new(0x78, 0x00, 0xff), + RgbColor::new(0xb4, 0x17, 0x7e), + RgbColor::new(0x41, 0x20, 0x00), + RgbColor::new(0xff, 0x4a, 0x01), + RgbColor::new(0x82, 0xe1, 0x00), + RgbColor::new(0x66, 0xfd, 0x00), + RgbColor::new(0x00, 0xfe, 0x00), + RgbColor::new(0x00, 0xfe, 0x00), + RgbColor::new(0x45, 0xfd, 0x61), + RgbColor::new(0x01, 0xfb, 0xcb), + RgbColor::new(0x50, 0x86, 0xff), + RgbColor::new(0x27, 0x4d, 0xc8), + RgbColor::new(0x84, 0x7a, 0xed), + RgbColor::new(0xd3, 0x0c, 0xff), + RgbColor::new(0xff, 0x06, 0x5a), + RgbColor::new(0xff, 0x7d, 0x01), + RgbColor::new(0xb8, 0xb1, 0x00), + RgbColor::new(0x8a, 0xfd, 0x00), + RgbColor::new(0x81, 0x5d, 0x00), + RgbColor::new(0x3a, 0x28, 0x02), + RgbColor::new(0x0d, 0x4c, 0x05), + RgbColor::new(0x00, 0x50, 0x37), + RgbColor::new(0x13, 0x14, 0x29), + RgbColor::new(0x10, 0x1f, 0x5a), + RgbColor::new(0x6a, 0x3c, 0x18), + RgbColor::new(0xac, 0x04, 0x01), + RgbColor::new(0xe1, 0x51, 0x36), + RgbColor::new(0xdc, 0x69, 0x00), + RgbColor::new(0xfe, 0xe1, 0x00), + RgbColor::new(0x99, 0xe1, 0x01), + RgbColor::new(0x60, 0xb5, 0x00), + RgbColor::new(0x1b, 0x1c, 0x31), + RgbColor::new(0xdc, 0xfd, 0x54), + RgbColor::new(0x76, 0xfb, 0xb9), + RgbColor::new(0x96, 0x98, 0xff), + RgbColor::new(0x8b, 0x62, 0xff), + RgbColor::new(0x40, 0x40, 0x40), + RgbColor::new(0x74, 0x74, 0x74), + RgbColor::new(0xde, 0xfc, 0xfc), + RgbColor::new(0xa2, 0x04, 0x01), + RgbColor::new(0x34, 0x01, 0x00), + RgbColor::new(0x00, 0xd2, 0x01), + RgbColor::new(0x00, 0x41, 0x01), + RgbColor::new(0xb8, 0xb1, 0x00), + RgbColor::new(0x3c, 0x30, 0x00), + RgbColor::new(0xb4, 0x5d, 0x00), + RgbColor::new(0x4c, 0x13, 0x00), +]; diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/mod.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/mod.rs new file mode 100644 index 0000000..6b179ae --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/mod.rs @@ -0,0 +1,2 @@ +pub mod launchpad; +pub mod x_touch; diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/x_touch.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/x_touch.rs new file mode 100644 index 0000000..877a990 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/devices/x_touch.rs @@ -0,0 +1,96 @@ +use crate::source::color_util::find_closest_color_in_palette; +use crate::{MackieLcdScope, RgbColor}; +use base::hash_util::NonCryptoHashMap; + +mod colors { + use crate::RgbColor; + + pub const BLANK: RgbColor = RgbColor::new(0, 0, 0); + pub const RED: RgbColor = RgbColor::new(255, 0, 0); + pub const GREEN: RgbColor = RgbColor::new(0, 255, 0); + pub const YELLOW: RgbColor = RgbColor::new(255, 255, 0); + pub const BLUE: RgbColor = RgbColor::new(0, 0, 255); + pub const PURPLE: RgbColor = RgbColor::new(128, 0, 128); + pub const CYAN: RgbColor = RgbColor::new(0, 255, 255); + pub const WHITE: RgbColor = RgbColor::new(255, 255, 255); +} + +use colors::*; + +const COLOR_PALETTE: [RgbColor; 8] = [BLANK, RED, GREEN, YELLOW, BLUE, PURPLE, CYAN, WHITE]; + +/// Global state for a particular Behringer X-Touch device. +/// +/// It's used when choosing the X-Touch Mackie display MIDI source in order to determine if a +/// sys-ex message needs to be sent to change the display color, and if yes, which one. We need +/// global state here because, unfortunately, the color can only be changed for all displays +/// (channels) at once. However, ReaLearn's color feedback design allows for defining the color +/// in a very fine-granular way - as part of the feedback value (its "style"), and thus resides +/// within the scope of a mapping. +/// +/// We need to make sure that when changing the color for one display, that the colors of the other +/// displays remain unchanged. This is impossible without having access to the current state of the +/// other displays because there's no sys-ex to change the color of just one display. +/// +/// One alternative would have been to somehow restructure ReaLearn's feedback design so that +/// we always transfer batches of texts and colors ... but that wouldn't go well with the +/// concept where one mapping can change something very small and specific (which makes ReaLearn so +/// flexible and composable). +/// +/// Another alternative would have been to make the feedback source value something more +/// abstract than concrete MIDI messages and then creating the concrete MIDI message at a later +/// stage when all information is available (probably in the struct that has access to the global +/// source context state). +#[derive(Debug, Default)] +pub struct XTouchMackieLcdState { + state_by_extender: NonCryptoHashMap, +} + +#[derive(Debug, Default)] +struct XTouchMackieExtenderLcdState { + color_index_by_channel: [Option; MackieLcdScope::CHANNEL_COUNT as usize], +} + +const EMPTY_COLOR_INDEX_BY_CHANNEL: XTouchMackieExtenderLcdState = XTouchMackieExtenderLcdState { + color_index_by_channel: [None; MackieLcdScope::CHANNEL_COUNT as usize], +}; + +impl XTouchMackieLcdState { + /// Returns `true` if something has changed for the given extender. + /// + /// In that case, the sys-ex should be sent again. + pub fn notify_color_requested( + &mut self, + extender_index: u8, + channel: u8, + color_index: Option, + ) -> bool { + let extender_state = self.state_by_extender.entry(extender_index).or_default(); + let previous_color_index = extender_state.color_index_by_channel[channel as usize]; + extender_state.color_index_by_channel[channel as usize] = color_index; + color_index != previous_color_index + } + + /// Returns the sys-ex bytes for setting the colors for the given extender. + pub fn sysex(&self, extender_index: u8) -> impl Iterator + '_ { + let start = [0xF0, 0x00, 0x00, 0x66, 0x14 + extender_index, 0x72]; + let extender_state = self + .state_by_extender + .get(&extender_index) + .unwrap_or(&EMPTY_COLOR_INDEX_BY_CHANNEL); + let color_indexes = extender_state + .color_index_by_channel + .iter() + .map(|color_index| color_index.unwrap_or(X_TOUCH_DEFAULT_COLOR_INDEX)); + start + .into_iter() + .chain(color_indexes) + .chain(std::iter::once(0xF7)) + } +} + +pub fn get_x_touch_color_index_for_color(color: RgbColor) -> u8 { + find_closest_color_in_palette(color, &COLOR_PALETTE) +} + +const X_TOUCH_DEFAULT_COLOR_INDEX: u8 = 0; diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/feedback_script.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/feedback_script.rs new file mode 100644 index 0000000..2a4551c --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/feedback_script.rs @@ -0,0 +1,41 @@ +use crate::{FeedbackValue, PropValue}; +use base::hash_util::NonCryptoHashSet; +use std::borrow::Cow; +use std::error::Error; + +// The lifetime 'a is necessary in case we want to parameterize the lifetime +// of the additional input dynamically. An alternative would have been to +// require the additional input type to be static and take it by reference. +// But that would be less generic. +pub trait FeedbackScript<'a> { + type AdditionalInput: Default; + + fn feedback( + &self, + input: FeedbackScriptInput, + additional_input: Self::AdditionalInput, + ) -> Result>; + + fn used_props(&self) -> Result, Box>; +} + +pub trait PropProvider { + fn get_prop_value(&self, key: &str) -> Option; +} + +impl PropProvider for F +where + F: Fn(&str) -> Option, +{ + fn get_prop_value(&self, key: &str) -> Option { + (self)(key) + } +} + +pub struct FeedbackScriptInput<'a> { + pub prop_provider: &'a dyn PropProvider, +} + +pub struct FeedbackScriptOutput { + pub feedback_value: FeedbackValue<'static>, +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source.rs new file mode 100644 index 0000000..12e8140 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source.rs @@ -0,0 +1,3339 @@ +use crate::{ + create_raw_midi_events_singleton, format_percentage_without_unit, + parse_percentage_without_unit, AbsoluteValue, ControlValue, DetailedSourceCharacter, + DiscreteIncrement, FeedbackValue, Fraction, MidiSourceScript, MidiSourceValue, + PreliminaryMidiSourceFeedbackValue, RawFeedbackAddressInfo, RawMidiEvent, RawMidiEvents, + RawMidiPattern, RgbColor, SourceContext, TextualFeedbackValue, UnitValue, + XTouchMackieLcdColorRequest, +}; +use core::iter; +use derivative::Derivative; +use derive_more::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::cell::Cell; +use strum::EnumIter; + +use crate::devices::x_touch::get_x_touch_color_index_for_color; +use crate::source::color_util::find_closest_color_in_palette; +use helgoboss_midi::{ + Channel, ControlChange14BitMessage, ControllerNumber, DataType, KeyNumber, + ParameterNumberMessage, RawShortMessage, ShortMessage, ShortMessageFactory, ShortMessageType, + StructuredShortMessage, U14, U7, +}; +use reaper_common_types::Bpm; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::convert::{TryFrom, TryInto}; +use std::ops::Range; + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize_repr, + Deserialize_repr, +)] +#[repr(usize)] +pub enum SourceCharacter { + #[default] + #[display(fmt = "Range element (knob, fader, etc.)")] + RangeElement = 0, + #[display(fmt = "Button (momentary)")] + MomentaryButton = 1, + #[display(fmt = "Encoder (relative type 1)")] + Encoder1 = 2, + #[display(fmt = "Encoder (relative type 2)")] + Encoder2 = 3, + #[display(fmt = "Encoder (relative type 3)")] + Encoder3 = 4, + /// This exists as a workaround for buttons that only toggle and can't be configured to work + /// as momentary buttons. A source with this character will always emit 1, even if the + /// hardware toggle is switching to off. + #[display(fmt = "Toggle-only button (avoid!)")] + ToggleButton = 5, +} + +impl SourceCharacter { + /// Returns whether sources with this character emit relative increments instead of absolute + /// values. + pub fn emits_increments(&self) -> bool { + use SourceCharacter::*; + matches!(self, Encoder1 | Encoder2 | Encoder3) + } + + pub fn possible_detailed_characters(&self) -> Vec { + use SourceCharacter::*; + match self { + RangeElement => vec![DetailedSourceCharacter::RangeControl], + MomentaryButton => vec![ + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + ], + Encoder1 | Encoder2 | Encoder3 => vec![DetailedSourceCharacter::Relative], + ToggleButton => vec![DetailedSourceCharacter::Trigger], + } + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize_repr, + Deserialize_repr, +)] +#[repr(usize)] +pub enum MidiClockTransportMessage { + #[default] + Start = 0, + Continue = 1, + Stop = 2, +} + +impl From for ShortMessageType { + fn from(msg: MidiClockTransportMessage) -> Self { + use MidiClockTransportMessage::*; + match msg { + Start => ShortMessageType::Start, + Continue => ShortMessageType::Continue, + Stop => ShortMessageType::Stop, + } + } +} + +#[derive(Clone, Debug, Derivative)] +#[derivative(PartialEq)] +pub enum MidiSource MidiSourceScript<'a>> { + NoteVelocity { + channel: Option, + key_number: Option, + }, + NoteKeyNumber { + channel: Option, + }, + // ShortMessageType::PolyphonicKeyPressure + PolyphonicKeyPressureAmount { + channel: Option, + key_number: Option, + }, + // ShortMessageType::ControlChange + ControlChangeValue { + channel: Option, + controller_number: Option, + custom_character: SourceCharacter, + }, + // ShortMessageType::ProgramChange + ProgramChangeNumber { + channel: Option, + }, + // ShortMessageType::ProgramChange + SpecificProgramChange { + channel: Option, + program_number: Option, + }, + // ShortMessageType::ChannelPressure + ChannelPressureAmount { + channel: Option, + }, + // ShortMessageType::PitchBendChange + PitchBendChangeValue { + channel: Option, + }, + // ControlChange14BitMessage + ControlChange14BitValue { + channel: Option, + msb_controller_number: Option, + custom_character: SourceCharacter, + }, + // ParameterNumberMessage + ParameterNumberValue { + channel: Option, + number: Option, + is_14_bit: Option, + is_registered: Option, + custom_character: SourceCharacter, + }, + // ShortMessageType::TimingClock + ClockTempo, + // ShortMessageType::{Start, Continue, Stop} + ClockTransport { + message: MidiClockTransportMessage, + }, + // E.g. SysEx + Raw { + pattern: RawMidiPattern, + custom_character: SourceCharacter, + }, + // For advanced programmable feedback (e.g. to drive hardware displays). + Script { + #[derivative(PartialEq = "ignore")] + script: S, + }, + Display { + spec: DisplaySpec, + }, +} + +/// Uniquely addresses a source (e.g. used for source takeover and filtering). +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum MidiSourceAddress { + Note { + channel: Channel, + key_number: KeyNumber, + }, + PolyphonicKeyPressure { + channel: Channel, + key_number: KeyNumber, + }, + ControlChange { + channel: Channel, + controller_number: ControllerNumber, + is_14_bit: bool, + }, + ProgramChange { + channel: Channel, + }, + ChannelPressure { + channel: Channel, + }, + PitchBendChange { + channel: Channel, + }, + ParameterNumber { + channel: Channel, + number: U14, + is_registered: bool, + }, + Display { + spec: DisplaySpecAddress, + }, + Raw { + pattern: Vec, + }, + Script { + /// Example: 0x4bb0 for a generated MIDI message with: + /// + /// - status byte = 0xb0 + /// - data byte 1 = 0x4b + /// - data byte 2 = 0x0a + bytes: u64, + }, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum PatternByte { + Fixed(u8), + Variable, +} + +impl MidiSource +where + S: for<'a> MidiSourceScript<'a>, +{ + /// This will be very fast except maybe for raw or script sources. + pub fn extract_feedback_address( + &self, + context: SourceContext<>::AdditionalInput>, + ) -> Option { + use MidiSource::*; + let res = match self { + NoteVelocity { + channel: Some(ch), + key_number: Some(kn), + } => MidiSourceAddress::Note { + channel: *ch, + key_number: *kn, + }, + PolyphonicKeyPressureAmount { + channel: Some(ch), + key_number: Some(kn), + } => MidiSourceAddress::PolyphonicKeyPressure { + channel: *ch, + key_number: *kn, + }, + ControlChangeValue { + channel: Some(ch), + controller_number: Some(cn), + .. + } => MidiSourceAddress::ControlChange { + channel: *ch, + controller_number: *cn, + is_14_bit: false, + }, + ProgramChangeNumber { channel: Some(ch) } => { + MidiSourceAddress::ProgramChange { channel: *ch } + } + ChannelPressureAmount { channel: Some(ch) } => { + MidiSourceAddress::ChannelPressure { channel: *ch } + } + PitchBendChangeValue { channel: Some(ch) } => { + MidiSourceAddress::PitchBendChange { channel: *ch } + } + ControlChange14BitValue { + channel: Some(ch), + msb_controller_number: Some(cn), + .. + } => MidiSourceAddress::ControlChange { + channel: *ch, + controller_number: *cn, + is_14_bit: true, + }, + ParameterNumberValue { + channel: Some(ch), + number: Some(n), + is_registered: Some(is_registered), + .. + } => MidiSourceAddress::ParameterNumber { + channel: *ch, + number: *n, + is_registered: *is_registered, + }, + Display { spec } => MidiSourceAddress::Display { + spec: spec.clone().into(), + }, + Raw { pattern, .. } => MidiSourceAddress::Raw { + pattern: pattern.to_pattern_bytes(), + }, + Script { script } => { + return match script.execute(FeedbackValue::Off, context.additional_script_input) { + Ok(outcome) => outcome.address, + Err(e) => { + tracing::warn!(msg = "MIDI script failed while extracting feedback address", %e); + None + } + }; + } + // No feedback + ClockTempo | ClockTransport { .. } | NoteKeyNumber { .. } => return None, + // Non-feedback-compatible configurations (e.g. channel == ) + _ => return None, + }; + Some(res) + } + + /// Checks if the given message is directed to the same address as the one of this source. + /// + /// Used for: + /// + /// - Source takeover (feedback) + pub fn has_same_feedback_address_as_value( + &self, + value: &MidiSourceValue, + context: SourceContext<>::AdditionalInput>, + ) -> bool { + if let (Some(addr1), Some(addr2)) = ( + self.extract_feedback_address(context), + value.extract_feedback_address(), + ) { + addr1 == addr2 + } else { + false + } + } + + /// Checks if this and the given source share the same address. + /// + /// For performance reasons, this doesn't try to match sources of different types that could + /// end up the same on byte level. E.g. right now when doing source filtering, an incoming pitch + /// bend message wouldn't match a Raw source that simulates a pitch bend message and therefore + /// the mapping with the Raw source would not show up. + /// + /// If we need that one day, it should be not difficult to implement. However, I think it's not + /// urgent at all. Just because one *can* simulate short MIDI messages with the Raw source + /// doesn't mean it's encouraged. + /// + /// Used for: + /// + /// - Feedback diffing + pub fn has_same_feedback_address_as_source( + &self, + other: &Self, + context: SourceContext<>::AdditionalInput>, + ) -> bool + where + for<'a> >::AdditionalInput: Copy, + { + self.extract_feedback_address(context) == other.extract_feedback_address(context) + } + + /// Used for creating sources when learning. + /// + /// Might allocate! + pub fn from_source_value( + source_value: MidiSourceValue, + custom_character_hint: Option, + ) -> Option { + use MidiSourceValue::*; + let source = match source_value { + ParameterNumber(msg) => MidiSource::ParameterNumberValue { + channel: Some(msg.channel()), + number: Some(msg.number()), + is_14_bit: Some(msg.is_14_bit()), + is_registered: Some(msg.is_registered()), + custom_character: custom_character_hint.unwrap_or_default(), + }, + ControlChange14Bit(msg) => MidiSource::ControlChange14BitValue { + channel: Some(msg.channel()), + msb_controller_number: Some(msg.msb_controller_number()), + custom_character: custom_character_hint.unwrap_or_default(), + }, + Tempo(_) => MidiSource::ClockTempo, + Plain(msg) => MidiSource::from_short_message(msg, custom_character_hint)?, + BorrowedSysEx(msg) => MidiSource::from_raw(msg), + // Important (and working) for learning. + Raw { events, .. } => MidiSource::from_raw(events.first()?.bytes()), + }; + Some(source) + } + + /// Allocates! + pub fn from_raw(msg: &[u8]) -> Self { + MidiSource::Raw { + pattern: RawMidiPattern::fixed_from_slice(msg), + custom_character: Default::default(), + } + } + + fn from_short_message( + msg: impl ShortMessage, + custom_character_hint: Option, + ) -> Option { + use StructuredShortMessage::*; + let source = match msg.to_structured() { + NoteOn { + channel, + key_number, + .. + } + | NoteOff { + channel, + key_number, + .. + } => MidiSource::NoteVelocity { + channel: Some(channel), + key_number: Some(key_number), + }, + PolyphonicKeyPressure { + channel, + key_number, + .. + } => MidiSource::PolyphonicKeyPressureAmount { + channel: Some(channel), + key_number: Some(key_number), + }, + ControlChange { + channel, + controller_number, + .. + } => MidiSource::ControlChangeValue { + channel: Some(channel), + controller_number: Some(controller_number), + custom_character: custom_character_hint.unwrap_or_default(), + }, + ProgramChange { + channel, + program_number, + } => MidiSource::SpecificProgramChange { + channel: Some(channel), + program_number: Some(program_number), + }, + ChannelPressure { channel, .. } => MidiSource::ChannelPressureAmount { + channel: Some(channel), + }, + PitchBendChange { channel, .. } => MidiSource::PitchBendChangeValue { + channel: Some(channel), + }, + TimingClock => MidiSource::ClockTempo, + Start => MidiSource::ClockTransport { + message: MidiClockTransportMessage::Start, + }, + Continue => MidiSource::ClockTransport { + message: MidiClockTransportMessage::Continue, + }, + Stop => MidiSource::ClockTransport { + message: MidiClockTransportMessage::Stop, + }, + _ => { + return None; + } + }; + Some(source) + } + + pub fn channel(&self) -> Option { + use MidiSource::*; + match self { + NoteVelocity { channel, .. } + | NoteKeyNumber { channel } + | PolyphonicKeyPressureAmount { channel, .. } + | ControlChangeValue { channel, .. } + | ProgramChangeNumber { channel } + | SpecificProgramChange { channel, .. } + | ChannelPressureAmount { channel } + | PitchBendChangeValue { channel } + | ControlChange14BitValue { channel, .. } + | ParameterNumberValue { channel, .. } => *channel, + ClockTempo | ClockTransport { .. } | Raw { .. } | Script { .. } | Display { .. } => { + None + } + } + } + + pub fn character(&self) -> SourceCharacter { + use MidiSource::*; + match self { + NoteVelocity { .. } => SourceCharacter::MomentaryButton, + // TODO-low Introduce new character "Trigger" + ClockTransport { .. } | SpecificProgramChange { .. } => { + SourceCharacter::MomentaryButton + } + Raw { + custom_character, .. + } + | ControlChangeValue { + custom_character, .. + } + | ControlChange14BitValue { + custom_character, .. + } + | ParameterNumberValue { + custom_character, .. + } => *custom_character, + NoteKeyNumber { .. } + | PolyphonicKeyPressureAmount { .. } + | ProgramChangeNumber { .. } + | ChannelPressureAmount { .. } + | PitchBendChangeValue { .. } + | Script { .. } + | Display { .. } + | ClockTempo => SourceCharacter::RangeElement, + } + } + + pub fn possible_detailed_characters(&self) -> Vec { + use MidiSource::*; + match self { + NoteVelocity { .. } => vec![ + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + DetailedSourceCharacter::MomentaryOnOffButton, + ], + ClockTransport { .. } | SpecificProgramChange { .. } => { + vec![DetailedSourceCharacter::Trigger] + } + // User can choose. + Raw { + custom_character, .. + } + | ControlChangeValue { + custom_character, .. + } + | ControlChange14BitValue { + custom_character, .. + } => custom_character.possible_detailed_characters(), + ParameterNumberValue { + custom_character, + is_14_bit, + .. + } => { + if *is_14_bit == Some(true) { + custom_character.possible_detailed_characters() + } else { + let mut res = custom_character.possible_detailed_characters(); + res.push(DetailedSourceCharacter::Relative); + res + } + } + // Usually a range control but sometimes more like a button (e.g. see #316). + ProgramChangeNumber { .. } | ChannelPressureAmount { .. } => vec![ + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + ], + // Usually a range control but could also be a velocity-sensitive button. + PolyphonicKeyPressureAmount { .. } | PitchBendChangeValue { .. } => { + vec![ + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + ] + } + // We exposed this as range-only ("key range") before but this actually also works as + // buttons that are never released. + NoteKeyNumber { .. } => { + vec![ + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::Trigger, + ] + } + // Special targets for which we can safely say it's a range. + ClockTempo => vec![DetailedSourceCharacter::RangeControl], + // Feedback-only but characters also matter for feedback. + Script { .. } => { + vec![ + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + ] + } + // Feedback-only but characters also matter for feedback. + Display { .. } => vec![DetailedSourceCharacter::RangeControl], + } + } + + /// Determines the appropriate control value from the given MIDI source value. If this source + /// doesn't process values of that type or just consumes the value without leading to a + /// control value (e.g. relative-zero), it returns None. + pub fn control(&self, value: &MidiSourceValue) -> Option { + match self.control_flexible(value)? { + ControlResult::Consumed => None, + ControlResult::Processed(v) => Some(v), + } + } + + pub fn control_flexible( + &self, + value: &MidiSourceValue, + ) -> Option { + use MidiSource as S; + use MidiSourceValue::*; + use StructuredShortMessage::*; + let control_value = match self { + S::NoteVelocity { + channel, + key_number, + } => match value { + Plain(msg) => match msg.to_structured() { + NoteOn { + channel: ch, + key_number: kn, + velocity, + } if matches(ch, *channel) && matches(kn, *key_number) => { + Some(abs(normalize_7_bit(velocity))) + } + NoteOff { + channel: ch, + key_number: kn, + .. + } if matches(ch, *channel) && matches(kn, *key_number) => { + Some(abs(MIN_U7_FRACTION)) + } + _ => None, + }, + _ => None, + }, + S::NoteKeyNumber { channel } => match value { + Plain(msg) => match msg.to_structured() { + NoteOn { + channel: ch, + key_number, + velocity, + } if velocity > U7::MIN && matches(ch, *channel) => { + Some(abs(normalize_7_bit(key_number))) + } + _ => None, + }, + _ => None, + }, + S::PitchBendChangeValue { channel } => match value { + Plain(msg) => match msg.to_structured() { + PitchBendChange { + channel: ch, + pitch_bend_value, + } if matches(ch, *channel) => { + Some(abs(normalize_14_bit_centered(pitch_bend_value))) + } + _ => None, + }, + _ => None, + }, + S::ChannelPressureAmount { channel } => match value { + Plain(msg) => match msg.to_structured() { + ChannelPressure { + channel: ch, + pressure_amount, + } if matches(ch, *channel) => Some(abs(normalize_7_bit(pressure_amount))), + _ => None, + }, + _ => None, + }, + S::ProgramChangeNumber { channel } => match value { + Plain(msg) => match msg.to_structured() { + ProgramChange { + channel: ch, + program_number, + } if matches(ch, *channel) => Some(abs(normalize_7_bit(program_number))), + _ => None, + }, + _ => None, + }, + S::SpecificProgramChange { + channel, + program_number, + } => match value { + Plain(msg) => match msg.to_structured() { + ProgramChange { + channel: ch, + program_number: pn, + } if matches(ch, *channel) && matches(pn, *program_number) => { + Some(abs(Fraction::new_max(1))) + } + _ => None, + }, + _ => None, + }, + S::PolyphonicKeyPressureAmount { + channel, + key_number, + } => match value { + Plain(msg) => match msg.to_structured() { + PolyphonicKeyPressure { + channel: ch, + key_number: kn, + pressure_amount, + } if matches(ch, *channel) && matches(kn, *key_number) => { + Some(abs(normalize_7_bit(pressure_amount))) + } + _ => None, + }, + _ => None, + }, + S::ControlChangeValue { + channel, + controller_number, + custom_character, + } => match value { + Plain(msg) => match msg.to_structured() { + ControlChange { + channel: ch, + controller_number: cn, + control_value, + } if matches(ch, *channel) && matches(cn, *controller_number) => { + let control_outcome = + calc_control_value_from_n_bit_cc(*custom_character, control_value, 7) + .map(ControlResult::Processed); + return Some(control_outcome.unwrap_or(ControlResult::Consumed)); + } + _ => None, + }, + _ => None, + }, + S::ControlChange14BitValue { + channel, + msb_controller_number, + custom_character, + } => match value { + ControlChange14Bit(msg) + if matches(msg.channel(), *channel) + && matches(msg.msb_controller_number(), *msb_controller_number) => + { + calc_control_value_from_n_bit_cc(*custom_character, msg.value(), 14).ok() + } + _ => None, + }, + S::ParameterNumberValue { + channel, + number, + is_14_bit, + is_registered, + custom_character, + } => match value { + ParameterNumber(msg) + if matches(msg.channel(), *channel) + && matches(msg.number(), *number) + && matches(msg.is_14_bit(), *is_14_bit) + && matches(msg.is_registered(), *is_registered) => + { + match msg.data_type() { + DataType::DataEntry => { + if msg.is_14_bit() { + calc_control_value_from_n_bit_cc(*custom_character, msg.value(), 14) + .ok() + } else { + let u7_value = U7::try_from(msg.value()).unwrap(); + calc_control_value_from_n_bit_cc(*custom_character, u7_value, 7) + .ok() + } + } + DataType::DataIncrement => { + (msg.value().get() as i32).try_into().ok().map(rel) + } + DataType::DataDecrement => { + (-(msg.value().get() as i32)).try_into().ok().map(rel) + } + } + } + _ => None, + }, + S::ClockTransport { message } => match value { + Plain(msg) if msg.r#type() == (*message).into() => Some(abs(Fraction::new_max(1))), + _ => None, + }, + S::ClockTempo => match value { + Tempo(bpm) => Some(ControlValue::AbsoluteContinuous((*bpm).into())), + _ => None, + }, + S::Raw { + pattern, + custom_character, + } => { + let fraction = match value { + Raw { events, .. } => pattern.match_and_capture(events.first()?.bytes())?, + BorrowedSysEx(bytes) => pattern.match_and_capture(bytes)?, + Plain(msg) => { + let (b1, b2, b3) = msg.to_bytes(); + let bytes = [b1, b2.get(), b3.get()]; + pattern.match_and_capture(&bytes)? + } + _ => return None, + }; + if fraction.max_val() == 0 { + // Fixed pattern with no variable parts. This should act like a trigger! + Some(ControlValue::AbsoluteContinuous(UnitValue::MAX)) + } else { + calc_control_value_from_n_bit_cc( + *custom_character, + fraction.actual(), + pattern.resolution() as _, + ) + .ok() + } + } + // Feedback-only forever. + S::Script { .. } | S::Display { .. } => None, + }; + control_value.map(ControlResult::Processed) + } + + /// Checks if this source consumes the given MIDI message. This is for sources whose events are + /// composed of multiple MIDI messages, which is 14-bit CC and (N)RPN. + // TODO-low Don't take ShortMessage by reference, never! + pub fn consumes(&self, msg: &impl ShortMessage) -> bool { + use MidiSource::*; + use StructuredShortMessage::*; + match self { + ControlChange14BitValue { + channel, + msb_controller_number, + .. + } => match msg.to_structured() { + ControlChange { + channel: ch, + controller_number, + .. + } => { + matches(ch, *channel) + && (matches(controller_number, *msb_controller_number) + || matches( + controller_number, + msb_controller_number.map(|n| { + n.corresponding_14_bit_lsb_controller_number().unwrap() + }), + )) + } + _ => false, + }, + ParameterNumberValue { channel, .. } => match msg.to_structured() { + ControlChange { + channel: ch, + controller_number, + .. + } => { + matches(ch, *channel) + && controller_number.is_parameter_number_message_controller_number() + } + _ => false, + }, + _ => false, + } + } + + /// Returns an appropriate MIDI source value for the given feedback value if feedback is + /// supported by this source. + /// + /// The source context allows us to pass in more global state, e.g. about the connected device. + pub fn feedback_flexible( + &self, + feedback_value: FeedbackValue, + context: SourceContext<>::AdditionalInput>, + ) -> Option> { + use MidiSource::*; + use MidiSourceValue as V; + let concrete_value = match self { + NoteVelocity { + channel: Some(ch), + key_number: Some(kn), + } => Some(V::Plain(M::note_on( + *ch, + *kn, + denormalize_7_bit(feedback_value.to_numeric()?.value), + ))), + NoteKeyNumber { channel: Some(ch) } => Some(V::Plain(M::note_on( + *ch, + denormalize_7_bit(feedback_value.to_numeric()?.value), + U7::MAX, + ))), + PolyphonicKeyPressureAmount { + channel: Some(ch), + key_number: Some(kn), + } => Some(V::Plain(M::polyphonic_key_pressure( + *ch, + *kn, + denormalize_7_bit(feedback_value.to_numeric()?.value), + ))), + ControlChangeValue { + channel: Some(ch), + controller_number: Some(cn), + .. + } => Some(V::Plain(M::control_change( + *ch, + *cn, + denormalize_7_bit(feedback_value.to_numeric()?.value), + ))), + ProgramChangeNumber { channel: Some(ch) } => Some(V::Plain(M::program_change( + *ch, + denormalize_7_bit(feedback_value.to_numeric()?.value), + ))), + SpecificProgramChange { + channel: Some(ch), + program_number, + } => { + let program_number = (*program_number)?; + if feedback_value.to_numeric()?.value.is_on() { + Some(V::Plain(M::program_change(*ch, program_number))) + } else { + None + } + } + ChannelPressureAmount { channel: Some(ch) } => Some(V::Plain(M::channel_pressure( + *ch, + denormalize_7_bit(feedback_value.to_numeric()?.value), + ))), + PitchBendChangeValue { channel: Some(ch) } => Some(V::Plain(M::pitch_bend_change( + *ch, + denormalize_14_bit_centered(feedback_value.to_numeric()?.value), + ))), + ControlChange14BitValue { + channel: Some(ch), + msb_controller_number: Some(mcn), + .. + } => Some(V::ControlChange14Bit(ControlChange14BitMessage::new( + *ch, + *mcn, + denormalize_14_bit(feedback_value.to_numeric()?.value), + ))), + ParameterNumberValue { + channel: Some(ch), + number: Some(n), + is_14_bit: Some(is_14_bit), + is_registered: Some(is_registered), + .. + } => { + let n = if !*is_registered && !*is_14_bit { + ParameterNumberMessage::non_registered_7_bit( + *ch, + *n, + denormalize_7_bit(feedback_value.to_numeric()?.value), + ) + } else if !*is_registered && *is_14_bit { + ParameterNumberMessage::non_registered_14_bit( + *ch, + *n, + denormalize_14_bit(feedback_value.to_numeric()?.value), + ) + } else if *is_registered && !*is_14_bit { + ParameterNumberMessage::registered_7_bit( + *ch, + *n, + denormalize_7_bit(feedback_value.to_numeric()?.value), + ) + } else if *is_registered && *is_14_bit { + ParameterNumberMessage::registered_14_bit( + *ch, + *n, + denormalize_14_bit(feedback_value.to_numeric()?.value), + ) + } else { + unreachable!() + }; + Some(V::ParameterNumber(n)) + } + Raw { pattern, .. } => { + let raw_midi_event = + pattern.to_concrete_midi_event(0, feedback_value.to_numeric()?.value); + let address_info = RawFeedbackAddressInfo::Raw { + variable_range: pattern.variable_range(), + }; + let value = V::single_raw(Some(address_info), raw_midi_event); + Some(value) + } + Script { script } => { + let outcome = script + .execute(feedback_value, context.additional_script_input) + .ok()?; + let value = V::Raw { + feedback_address_info: outcome.address.map(RawFeedbackAddressInfo::Custom), + events: outcome.events, + }; + Some(value) + } + Display { spec } => { + let value = feedback_value.to_textual(); + let style = value.style; + let (events, non_final): (RawMidiEvents, _) = match spec { + DisplaySpec::MackieLcd { + scope, + extender_index, + } => ( + feedback_mackie_lcd(&value, scope, *extender_index).collect(), + None, + ), + DisplaySpec::XTouchMackieLcd { + scope, + extender_index, + } => { + let x_touch_color_request = XTouchMackieLcdColorRequest { + extender_index: *extender_index, + channel: scope.channel, + color_index: style.color.map(get_x_touch_color_index_for_color), + }; + ( + feedback_mackie_lcd(&value, scope, *extender_index).collect(), + Some(x_touch_color_request), + ) + } + DisplaySpec::SiniConE24 { + scope, + last_sent_background_color: previous_background_color, + } => { + let controller_number = 1; + let color = style.color.unwrap_or(RgbColor::WHITE); + let background_color = style.background_color.unwrap_or(RgbColor::BLACK); + let previous_background_color = + previous_background_color.replace(Some(background_color)); + let update_background = Some(background_color) != previous_background_color; + let mut ascii_chars = filter_ascii_chars(&value.text); + let events = scope + .destinations() + .iter() + .flat_map(move |dest| { + let background_event = if update_background { + let bg_update_sysex = sinicon_e24_sysex( + controller_number, + dest.cell_index, + dest.item_index, + // Line length of zero means setting the background color. + 0, + background_color, + iter::empty(), + ); + RawMidiEvent::try_from_iter(0, bg_update_sysex).ok() + } else { + None + }; + let text_event = { + let line_length = dest.line_length(); + let body = (0..line_length) + .map(|_| ascii_chars.next().unwrap_or_default()); + let text_sysex = sinicon_e24_sysex( + controller_number, + dest.cell_index, + dest.item_index, + line_length, + color, + body, + ); + RawMidiEvent::try_from_iter(0, text_sysex).ok() + }; + [background_event, text_event] + }) + .flatten() + .collect(); + (events, None) + } + DisplaySpec::SlKeyboard { scope } => { + let mut ascii_chars = filter_ascii_chars(&value.text); + let events = scope + .destinations() + .iter() + .flat_map(move |dest| { + let line_length = dest.line_length(); + let body = (0..line_length) + .map(|_| ascii_chars.next().unwrap_or_default()); + let text_sysex = sl_keyboard_display_sysex( + dest.section_index, + dest.line_index, + body, + ); + RawMidiEvent::try_from_iter(0, text_sysex).ok() + }) + .collect(); + (events, None) + } + DisplaySpec::MackieSevenSegmentDisplay { scope } => { + // Reverse because we want right-aligned + let mut peekable_chars = value.text.chars().rev().peekable(); + let mut codes = iter::from_fn(|| { + let ch = peekable_chars.next()?; + let next_ch = peekable_chars.peek().copied(); + let result = convert_to_7_segment_code(ch, next_ch, true); + if result.consumed_one_more { + peekable_chars.next(); + } + Some(result.code) + }) + .flatten() + .fuse(); + // Reverse because we want right-aligned + let positions = scope.positions(); + let events = positions + .iter() + .rev() + .map(|pos| { + let bytes = [0xB0, 0x40 + pos, codes.next().unwrap_or(ASCII_SPACE)]; + RawMidiEvent::try_from_iter(0, bytes.into_iter()).unwrap() + }) + .collect(); + (events, None) + } + DisplaySpec::LaunchpadProScrollingText => { + let body = filter_ascii_chars(&value.text); + let color = style.color.unwrap_or(RgbColor::WHITE); + let sysex = launchpad_pro_scrolling_text_sysex(color, true, body); + let event = RawMidiEvent::try_from_iter(0, sysex).ok()?; + (create_raw_midi_events_singleton(event), None) + } + }; + let feedback_info = RawFeedbackAddressInfo::Display { + spec: spec.clone().into(), + }; + let raw_value = V::Raw { + feedback_address_info: Some(feedback_info), + events, + }; + return Some(PreliminaryMidiSourceFeedbackValue { + final_value: raw_value, + x_touch_mackie_lcd_color_request: non_final, + }); + } + _ => None, + }?; + Some(PreliminaryMidiSourceFeedbackValue { + final_value: concrete_value, + x_touch_mackie_lcd_color_request: None, + }) + } + + #[cfg(test)] + fn test_feedback( + &self, + feedback_value: FeedbackValue, + ) -> Option> { + let context = SourceContext::default(); + Some(self.feedback_flexible(feedback_value, context)?.final_value) + } + + /// Formats the given absolute control value. + /// + /// The formatting is done according to how source values of this source type usually look like. + /// + /// # Errors + /// + /// Returns an error if formatting values is not supported for this source type or if the + /// control value type is not compatible with this source type. + pub fn format_control_value(&self, value: ControlValue) -> Result { + use MidiSource::*; + let result = match self { + ClockTempo => { + let bpm = Bpm::from(value.to_unit_value()?); + format!("{:.2}", bpm.get()) + } + ClockTransport { .. } => { + return Err("clock transport sources have just one possible control value"); + } + Script { .. } | Display { .. } => { + format_percentage_without_unit(value.to_unit_value()?.get()) + } + _ => self + .convert_control_value_to_midi_value(value.to_unit_value()?)? + .to_string(), + }; + Ok(result) + } + + /// Interprets the given text as MIDI value and returns the corresponding absolute control + /// value. + pub fn parse_control_value(&self, text: &str) -> Result { + use MidiSource::*; + let unit_value = match self { + ClockTempo => { + let bpm: Bpm = text.parse().map_err(|_| "not a valid BPM value")?; + UnitValue::from(bpm) + } + ClockTransport { .. } => { + return Err("parsing doesn't make sense for clock transport MIDI source"); + } + Script { .. } | Display { .. } => parse_percentage_without_unit(text)?.try_into()?, + _ => { + let midi_value: i32 = text.parse().map_err(|_| "not a valid integer")?; + self.convert_midi_value_to_control_value(midi_value)? + } + }; + Ok(unit_value) + } + + /// Returns whether this source emits relative increments instead of absolute values. + pub fn emits_increments(&self) -> bool { + matches!( + self, + MidiSource::ControlChangeValue { + custom_character, .. + } | MidiSource::ControlChange14BitValue { + custom_character, .. + } | MidiSource::ParameterNumberValue { + custom_character, .. + } if custom_character.emits_increments() + ) + } + + /// Converts the given absolute control value to an integer which reflects the MIDI source + /// value. + /// + /// The returned integer is in most cases a 7-bit value. But it can also be 14-bit or even + /// negative (if it's an increment). It depends on the source type. + /// + /// This method is intended for visualization purposes. + /// + /// # Errors + /// + /// Returns an error if it doesn't make sense for this source type (e.g. MIDI clock and sources + /// which emit increments). + fn convert_control_value_to_midi_value(&self, v: UnitValue) -> Result { + let value = AbsoluteValue::Continuous(v); + use MidiSource::*; + let midi_value: i32 = match self { + NoteVelocity { .. } + | NoteKeyNumber { .. } + | PolyphonicKeyPressureAmount { .. } + | ProgramChangeNumber { .. } + | ChannelPressureAmount { .. } + | ControlChangeValue { .. } => denormalize_7_bit(value), + PitchBendChangeValue { .. } => denormalize_14_bit_centered::(value) - 8192, + ControlChange14BitValue { .. } => denormalize_14_bit(value), + ParameterNumberValue { is_14_bit, .. } => match *is_14_bit { + None => return Err("not clear if 7- or 14-bit"), + Some(is_14_bit) => { + if is_14_bit { + denormalize_14_bit(value) + } else { + denormalize_7_bit(value) + } + } + }, + Raw { pattern, .. } => v.to_discrete(pattern.max_discrete_value()) as _, + ClockTempo + | ClockTransport { .. } + | SpecificProgramChange { .. } + | Script { .. } + | Display { .. } => { + return Err("not supported"); + } + }; + Ok(midi_value) + } + + /// Like `convert_control_value_to_midi_value()` but in other direction. + fn convert_midi_value_to_control_value(&self, value: i32) -> Result { + use MidiSource::*; + let unit_value = match self { + NoteVelocity { .. } + | NoteKeyNumber { .. } + | PolyphonicKeyPressureAmount { .. } + | ProgramChangeNumber { .. } + | ChannelPressureAmount { .. } => { + normalize_7_bit(U7::try_from(value).map_err(|_| "value not 7-bit")?) + } + ControlChangeValue { .. } => { + normalize_7_bit(U7::try_from(value).map_err(|_| "value not 7-bit")?) + } + PitchBendChangeValue { .. } => normalize_14_bit_centered( + U14::try_from(value + 8192).map_err(|_| "value not 14-bit")?, + ), + ControlChange14BitValue { .. } => { + normalize_14_bit(U14::try_from(value).map_err(|_| "value not 14-bit")?) + } + ParameterNumberValue { is_14_bit, .. } => match *is_14_bit { + None => return Err("not clear if 7- or 14-bit"), + Some(is_14_bit) => { + if is_14_bit { + normalize_14_bit(U14::try_from(value).map_err(|_| "value not 14-bit")?) + } else { + normalize_7_bit(U7::try_from(value).map_err(|_| "value not 7-bit")?) + } + } + }, + Raw { pattern, .. } => { + if value < 0 { + return Err("negative values not supported"); + } + Fraction::new(value as _, pattern.max_discrete_value() as _) + } + ClockTempo + | ClockTransport { .. } + | SpecificProgramChange { .. } + | Script { .. } + | Display { .. } => { + return Err("not supported"); + } + }; + Ok(unit_value.to_unit_value()) + } + + pub fn max_discrete_value(&self) -> Option { + use MidiSource::*; + match self { + NoteVelocity { .. } + | PolyphonicKeyPressureAmount { .. } + | ProgramChangeNumber { .. } + | ChannelPressureAmount { .. } + | NoteKeyNumber { .. } => Some(127), + ControlChange14BitValue { .. } | PitchBendChangeValue { .. } => Some(16383), + ControlChangeValue { + custom_character, .. + } => { + if custom_character.emits_increments() { + None + } else { + Some(127) + } + } + ParameterNumberValue { + custom_character, + is_14_bit, + .. + } => { + if custom_character.emits_increments() { + None + } else if *is_14_bit == Some(true) { + Some(16383) + } else { + Some(127) + } + } + ClockTempo + | ClockTransport { .. } + | SpecificProgramChange { .. } + | Script { .. } + | Display { .. } => None, + Raw { + custom_character, + pattern, + } => { + if custom_character.emits_increments() { + None + } else { + Some(pattern.max_discrete_value() as _) + } + } + } + } +} + +fn feedback_mackie_lcd<'a, 'b>( + value: &'a TextualFeedbackValue, + scope: &'b MackieLcdScope, + extender_index: u8, +) -> impl Iterator + 'a +where + 'a: 'b, +{ + let mut ascii_chars = filter_ascii_chars(&value.text); + scope.lcd_portions().into_iter().filter_map(move |range| { + let body = range + .clone() + .map(|_| ascii_chars.next().unwrap_or(ASCII_SPACE)); + let sysex = mackie_lcd_sysex(0x14 + extender_index, range.start, body); + RawMidiEvent::try_from_iter(0, sysex).ok() + }) +} + +pub enum ControlResult { + /// The value is consumed but doesn't emit a control value. + /// + /// Currently, the only example are neutral relative values + /// (e.g. 64 when using relative mode 2). They don't produce a `ControlValue` because + /// [`DiscreteIncrement`] doesn't permit the zero increment (which is debatable but at least + /// makes sure on type level that zero increments are discarded very early in the processing + /// chain instead of being processed a long way just to lead to no effect when they arrive at + /// the target). + Consumed, + Processed(ControlValue), +} + +fn matches(actual_value: T, configured_value: Option) -> bool { + match configured_value { + None => true, + Some(v) => actual_value == v, + } +} + +/// Returns an error if the source character is relative (one of the encoders types) but the +/// value is neutral (neither an increment nor a decrement), in which case you can discard the +/// value. +fn calc_control_value_from_n_bit_cc>( + character: SourceCharacter, + cc_control_value: T, + resolution: u32, +) -> Result { + use SourceCharacter::*; + let cc_control_value = cc_control_value.into(); + let result = match character { + RangeElement | MomentaryButton => abs(normalize_n_bit(cc_control_value, resolution)), + Encoder1 | Encoder2 | Encoder3 => { + let value_7_bit = extract_low_7_bit(cc_control_value); + let increment = match character { + Encoder1 => DiscreteIncrement::from_encoder_1_value(value_7_bit)?, + Encoder2 => DiscreteIncrement::from_encoder_2_value(value_7_bit)?, + Encoder3 => DiscreteIncrement::from_encoder_3_value(value_7_bit)?, + _ => unreachable!("impossible"), + }; + rel(increment) + } + ToggleButton => abs(max_n_bit_fraction(resolution)), + }; + Ok(result) +} + +const MIN_U7_FRACTION: Fraction = Fraction::new_min(U7::MAX.get() as _); + +fn normalize_7_bit>(value: T) -> Fraction { + normalize_n_bit(value, 7) +} + +fn normalize_14_bit(value: U14) -> Fraction { + normalize_n_bit(value, 14) +} + +fn normalize_n_bit>(value: T, resolution: u32) -> Fraction { + Fraction::new(value.into(), 2u32.pow(resolution) - 1) +} + +fn max_n_bit_fraction(resolution: u32) -> Fraction { + Fraction::new_max(2u32.pow(resolution) - 1) +} + +/// See denormalize_14_bit_centered for an explanation +fn normalize_14_bit_centered(value: U14) -> Fraction { + if value == U14::MAX { + return Fraction::new_max(U14::MAX.into()); + } + Fraction::new(value.into(), U14::MAX.get() as u32 + 1) +} + +fn denormalize_7_bit>(value: AbsoluteValue) -> T { + match value { + AbsoluteValue::Continuous(v) => { + unsafe { U7::new_unchecked((v.get() * U7::MAX.get() as f64).round() as u8) }.into() + } + AbsoluteValue::Discrete(f) => U7::try_from(f.actual()).unwrap_or(U7::MAX).into(), + } +} + +fn denormalize_14_bit>(value: AbsoluteValue) -> T { + match value { + AbsoluteValue::Continuous(v) => { + unsafe { U14::new_unchecked((v.get() * U14::MAX.get() as f64).round() as u16) }.into() + } + AbsoluteValue::Discrete(f) => U14::try_from(f.actual()).unwrap_or(U14::MAX).into(), + } +} + +/// When doing the mapping, this doesn't consider 16383 as maximum value but 16384. However, the +/// result is clamped again to (0..=16383) ... it's a bit like using `ceil()` instead of `round()`. +/// The intended effect is that now the range has a discrete center, which it normally doesn't have +/// because there's an even number of possible values. This way of denormalization is intended to be +/// used with controllers that are known to be centered. However, as you can imagine, there's also +/// a side effect: Instead of having an equal distribution, this algorithm slightly favors higher +/// numbers, so ideally it really should only be used for centered controllers. +/// +/// Take pitch bend change as an example: The official center of pitch bend is considered to be the +/// ceiling of the actual center value, which is 8192. +/// +/// - Unsigned view: Possible pitch bend values go from 0 to 16383. Exact center would be 8191.5. +/// Official center is 8192. +/// - Signed view: Possible pitch bend values go from -8192 to 8191. Exact center would be -0.5. +/// Official center is 0. +fn denormalize_14_bit_centered>(value: AbsoluteValue) -> T { + match value { + AbsoluteValue::Continuous(v) => { + let spread = (v.get() * (U14::MAX.get() + 1) as f64).round() as u16; + unsafe { U14::new_unchecked(spread.min(16383)) }.into() + } + AbsoluteValue::Discrete(f) => U14::try_from(f.actual()).unwrap_or(U14::MAX).into(), + } +} + +const fn abs(value: Fraction) -> ControlValue { + ControlValue::AbsoluteDiscrete(value) +} + +const fn rel(increment: DiscreteIncrement) -> ControlValue { + ControlValue::RelativeDiscrete(increment) +} + +fn extract_low_7_bit>(value: T) -> U7 { + U7::new((value.into() & 0x7f) as u8) +} + +fn mackie_lcd_sysex( + model_id: u8, + display_offset: u8, + body: impl Iterator, +) -> impl Iterator { + let start = [0xF0, 0x00, 0x00, 0x66, model_id, 0x12, display_offset]; + start.into_iter().chain(body).chain(end()) +} + +fn end() -> impl Iterator { + iter::once(0xF7) +} + +fn sinicon_e24_sysex( + controller_number: u8, + cell_index: u8, + item_index: u8, + line_length: u8, + color: RgbColor, + body: impl Iterator, +) -> impl Iterator { + // Display type 1 (4 lines) + let display_type = 1; + // Item type 1 (text) + let item_type = 1; + // Item style + let item_style = 0; + // Wildcard (0 means background color, anything else means line length) + let wildcard = line_length; + let start = [ + // 1 + 0xF0, + 0x00, + 0x02, + 0x38, + // 5 + controller_number, + // 6 + cell_index + 1, + // 7 + display_type, + // 8 + item_index + 1, + // 9 + item_type, + // 10 + item_style, + // 11 + wildcard, + // 12 + color.r(), + color.g(), + color.b(), + ]; + start.into_iter().chain(body).chain(end()) +} + +fn sl_keyboard_display_sysex( + section_index: u8, + line_index: u8, + body: impl Iterator, +) -> impl Iterator { + let start = [ + // Device + 0xF0, 0x00, 0x20, 0x1A, 0x00, // Display changes + 0x02, + ]; + let display_prefix = match (section_index, line_index) { + (0, 0 | 1) => [0x01, 0x00, 0x0E], + (1, 0) => [0x18, 0x00, 0x0B], + (1, 1) => [0x48, 0x00, 0x0A], + (2, 0) => [0x24, 0x00, 0x0B], + (2, 1) => [0x53, 0x00, 0x0A], + (3, 0) => [0x30, 0x00, 0x0B], + (3, 1) => [0x5E, 0x00, 0x0A], + (4, 0) => [0x3C, 0x00, 0x0B], + (4, 1) => [0x69, 0x00, 0x0A], + x => panic!("unsupported combination of section and line: {x:?}"), + }; + let expanded_body = body.flat_map(|ch| [ch, 0x00]); + let end = [0x00, 0x00, 0xF7]; + start + .into_iter() + .chain(display_prefix) + .chain(expanded_body) + .chain(end) +} + +fn launchpad_pro_scrolling_text_sysex( + color: RgbColor, + looped: bool, + body: impl Iterator, +) -> impl Iterator { + let color_code = + find_closest_color_in_palette(color, &super::devices::launchpad::COLOR_PALETTE); + let start = [ + 0xF0, + 0x00, + 0x20, + 0x29, + 0x02, + 0x10, + 0x14, + color_code, + u8::from(looped), + ]; + start.into_iter().chain(body).chain(end()) +} + +fn filter_ascii_chars(text: &str) -> impl Iterator + '_ { + text.chars() + .filter_map(|ch| if ch.is_ascii() { Some(ch as u8) } else { None }) + .fuse() +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum DisplayType { + #[default] + #[serde(rename = "mackie-lcd")] + #[display(fmt = "Mackie LCD")] + MackieLcd, + #[serde(rename = "mackie-xt-lcd")] + #[display(fmt = "Mackie XT LCD")] + MackieXtLcd, + #[serde(rename = "x-touch-mackie-lcd")] + #[display(fmt = "X-Touch Mackie LCD")] + XTouchMackieLcd, + #[serde(rename = "x-touch-mackie-xt-lcd")] + #[display(fmt = "X-Touch Mackie XT LCD")] + XTouchMackieXtLcd, + #[serde(rename = "mackie-seven")] + #[display(fmt = "Mackie 7-segment display")] + MackieSevenSegmentDisplay, + #[serde(rename = "sinicon-e24")] + #[display(fmt = "SiniCon E24 display")] + SiniConE24, + #[serde(rename = "launchpad-pro-scrolling-text")] + #[display(fmt = "Launchpad Pro - Scrolling text")] + LaunchpadProScrollingText, + #[serde(rename = "sl-keyboard")] + #[display(fmt = "Studiologic SL Keyboard display")] + SlKeyboardDisplay, +} + +impl DisplayType { + pub fn display_count(self) -> u8 { + use DisplayType::*; + match self { + MackieLcd | MackieXtLcd | XTouchMackieLcd | XTouchMackieXtLcd => { + MackieLcdScope::CHANNEL_COUNT + } + SiniConE24 => SiniConE24Scope::CELL_COUNT, + SlKeyboardDisplay => 5, + // Not applicable + MackieSevenSegmentDisplay | LaunchpadProScrollingText => 0, + } + } + + pub fn line_count(self) -> u8 { + use DisplayType::*; + match self { + MackieLcd | MackieXtLcd | XTouchMackieLcd | XTouchMackieXtLcd => { + MackieLcdScope::LINE_COUNT + } + SiniConE24 => SiniConE24Scope::ITEM_COUNT, + SlKeyboardDisplay => 2, + // Not applicable + MackieSevenSegmentDisplay | LaunchpadProScrollingText => 1, + } + } +} + +#[derive(Clone, Debug, Derivative)] +#[derivative(PartialEq)] +pub enum DisplaySpec { + MackieLcd { + scope: MackieLcdScope, + extender_index: u8, + }, + XTouchMackieLcd { + scope: MackieLcdScope, + extender_index: u8, + }, + MackieSevenSegmentDisplay { + scope: MackieSevenSegmentDisplayScope, + }, + SlKeyboard { + scope: SlKeyboardDisplayScope, + }, + SiniConE24 { + scope: SiniConE24Scope, + #[derivative(PartialEq = "ignore")] + last_sent_background_color: Cell>, + }, + LaunchpadProScrollingText, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum DisplaySpecAddress { + MackieLcd { + scope: MackieLcdScope, + extender_index: u8, + }, + MackieSevenSegmentDisplay { + scope: MackieSevenSegmentDisplayScope, + }, + SlKeyboardDisplay { + scope: SlKeyboardDisplayScope, + }, + SiniConE24 { + scope: SiniConE24Scope, + }, + LaunchpadProScrollingText, + XTouchMackieLcdColors { + extender_index: u8, + }, +} + +impl From for DisplaySpecAddress { + fn from(spec: DisplaySpec) -> Self { + use DisplaySpec::*; + match spec { + MackieLcd { + scope, + extender_index, + } + | XTouchMackieLcd { + scope, + extender_index, + } => Self::MackieLcd { + scope, + extender_index, + }, + MackieSevenSegmentDisplay { scope } => Self::MackieSevenSegmentDisplay { scope }, + SiniConE24 { scope, .. } => Self::SiniConE24 { scope }, + LaunchpadProScrollingText => Self::LaunchpadProScrollingText, + SlKeyboard { scope } => Self::SlKeyboardDisplay { scope }, + } + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize_repr, + Deserialize_repr, +)] +#[repr(usize)] +pub enum MackieSevenSegmentDisplayScope { + #[display(fmt = "")] + All = 0, + #[default] + #[display(fmt = "Assignment")] + Assignment = 1, + #[display(fmt = "Time code")] + Tc = 2, + #[display(fmt = ".... Hours/bars (3)")] + TcHoursBars = 3, + #[display(fmt = ".... Minutes/beats (2)")] + TcMinutesBeats = 4, + #[display(fmt = ".... Seconds/sub (2)")] + TcSecondsSub = 5, + #[display(fmt = ".... Frames/ticks (3)")] + TcFramesTicks = 6, +} + +impl MackieSevenSegmentDisplayScope { + pub fn positions(&self) -> DisplayPositions { + use MackieSevenSegmentDisplayScope::*; + let positions = match self { + All => vec![0x0B, 0x0A, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + Assignment => vec![0x0B, 0x0A], + Tc => vec![9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + TcHoursBars => vec![9, 8, 7], + TcMinutesBeats => vec![6, 5], + TcSecondsSub => vec![4, 3], + TcFramesTicks => vec![2, 1, 0], + }; + DisplayPositions::new(positions) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct MackieLcdScope { + pub channel: Option, + pub line: Option, +} + +impl MackieLcdScope { + const CHANNEL_LEN: u8 = 7; + pub const CHANNEL_COUNT: u8 = 8; + const LINE_COUNT: u8 = 2; + const LINE_LEN: u8 = Self::CHANNEL_COUNT * Self::CHANNEL_LEN; + + pub fn new(channel: Option, line: Option) -> Self { + Self { + channel: channel.map(|ch| ch.min(Self::CHANNEL_COUNT - 1)), + line: line.map(|l| l.min(Self::LINE_COUNT - 1)), + } + } + + pub fn lcd_portions(&self) -> LcdPortions { + fn range(start: u8, len: u8) -> Range { + start..(start + len) + } + let ranges = match (self.channel, self.line) { + (None, None) => vec![range(0, Self::LINE_COUNT * Self::LINE_LEN)], + (None, Some(l)) => vec![range(l * Self::LINE_LEN, Self::LINE_LEN)], + (Some(ch), None) => (0..Self::LINE_COUNT) + .map(|l| { + range( + l * Self::LINE_LEN + ch * Self::CHANNEL_LEN, + Self::CHANNEL_LEN, + ) + }) + .collect(), + (Some(ch), Some(l)) => vec![range( + l * Self::LINE_LEN + ch * Self::CHANNEL_LEN, + Self::CHANNEL_LEN, + )], + }; + LcdPortions::new(ranges) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct SlKeyboardDisplayScope { + pub section: Option, + pub line: Option, +} + +impl SlKeyboardDisplayScope { + const SECTION_COUNT: u8 = 5; + /// The first display (the main display) has 1 line only, but for simplicity we treat the + /// second line like the first line. + const LINE_COUNT: u8 = 2; + + pub fn new(section: Option, line: Option) -> Self { + Self { + section: section.map(|s| s.min(Self::SECTION_COUNT - 1)), + line: line.map(|l| l.min(Self::LINE_COUNT - 1)), + } + } + + pub fn destinations(&self) -> Vec { + match (self.section, self.line) { + (None, None) => (0..Self::SECTION_COUNT) + .flat_map(|s| { + (0..Self::LINE_COUNT).map(move |l| SlKeyboardDisplayDestination::new(s, l)) + }) + .collect(), + (None, Some(l)) => (0..Self::SECTION_COUNT) + .map(|s| SlKeyboardDisplayDestination::new(s, l)) + .collect(), + (Some(s), None) => (0..Self::LINE_COUNT) + .map(|l| SlKeyboardDisplayDestination::new(s, l)) + .collect(), + (Some(s), Some(l)) => vec![SlKeyboardDisplayDestination::new(s, l)], + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct SiniConE24Scope { + pub cell_index: Option, + pub item_index: Option, +} + +impl SiniConE24Scope { + const CELL_COUNT: u8 = 24; + const ITEM_COUNT: u8 = 4; + + pub fn new(cell_index: Option, item_index: Option) -> Self { + Self { + cell_index: cell_index.map(|c| c.min(Self::CELL_COUNT - 1)), + item_index: item_index.map(|i| i.min(Self::ITEM_COUNT - 1)), + } + } + + pub fn destinations(&self) -> Vec { + match (self.cell_index, self.item_index) { + (None, None) => (0..Self::CELL_COUNT) + .flat_map(|c| (0..Self::ITEM_COUNT).map(move |i| SiniConE24Destination::new(c, i))) + .collect(), + (None, Some(i)) => (0..Self::CELL_COUNT) + .map(|c| SiniConE24Destination::new(c, i)) + .collect(), + (Some(c), None) => (0..Self::ITEM_COUNT) + .map(|i| SiniConE24Destination::new(c, i)) + .collect(), + (Some(c), Some(i)) => vec![SiniConE24Destination::new(c, i)], + } + } +} + +/// A sequence of positions on a display, left-to-right. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct DisplayPositions { + positions: Vec, +} + +impl DisplayPositions { + /// Takes left-to-right display positions. + pub fn new(positions: Vec) -> Self { + Self { positions } + } + + /// Returns left-to-right display positions. + pub fn iter(&self) -> impl DoubleEndedIterator + '_ { + self.positions.iter().copied() + } +} + +/// A list of disjoint position intervals on a display, each one left-to-right. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct LcdPortions { + ranges: Vec>, +} + +impl LcdPortions { + pub fn new(ranges: Vec>) -> Self { + Self { ranges } + } + + pub fn iter(&self) -> impl Iterator> + '_ { + self.ranges.iter() + } +} + +impl IntoIterator for LcdPortions { + type Item = Range; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.ranges.into_iter() + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct SiniConE24Destination { + pub cell_index: u8, + pub item_index: u8, +} + +impl SiniConE24Destination { + pub fn new(cell_index: u8, item_index: u8) -> Self { + Self { + cell_index, + item_index, + } + } + + pub fn line_length(&self) -> u8 { + match self.item_index { + 0..=2 => 16, + 3 => 9, + _ => 0, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct SlKeyboardDisplayDestination { + pub section_index: u8, + pub line_index: u8, +} + +impl SlKeyboardDisplayDestination { + pub fn new(section_index: u8, line_index: u8) -> Self { + Self { + section_index, + line_index, + } + } + + pub fn line_length(&self) -> u8 { + match (self.section_index, self.line_index) { + (0, 0) => 13, + (1..=3, 0) => 10, + (1..=3, 1) => 9, + _ => 0, + } + } +} + +/// `reverse` must be used if we are iterating over the text in reverse order (for right alignment). +fn convert_to_7_segment_code(ch: char, next_ch: Option, reverse: bool) -> ConversionResult { + let (ch, with_decimal_point) = if reverse { + if ch == '.' { + (next_ch.unwrap_or(' '), true) + } else { + (ch, false) + } + } else { + (ch, next_ch == Some('.')) + }; + if ch == '.' { + // Translate period to space with decimal point, not to underscore. + return ConversionResult { + code: Some(0x20 + 0x40), + consumed_one_more: false, + }; + } + if !ch.is_ascii() { + return Default::default(); + } + let ch = ch.to_ascii_uppercase() as u8; + let res = match ch { + b':' | b'!' | b'@' => return Default::default(), + b'@'..=b'`' => ch - 0x40, + b' '..=b'?' => ch, + _ => { + return Default::default(); + } + }; + if with_decimal_point { + ConversionResult { + code: Some(res + 0x40), + consumed_one_more: true, + } + } else { + ConversionResult { + code: Some(res), + consumed_one_more: false, + } + } +} + +#[derive(Default)] +struct ConversionResult { + code: Option, + consumed_one_more: bool, +} + +const ASCII_SPACE: u8 = b' '; + +#[cfg(test)] +mod tests { + use super::*; + use crate::source::test_util::TestMidiSourceScript; + use crate::NumericFeedbackValue; + use approx::*; + use helgoboss_midi::test_util::{channel as ch, controller_number as cn, key_number as kn, *}; + use helgoboss_midi::RawShortMessage; + + type TestMidiSource = MidiSource; + + #[test] + fn note_velocity_1() { + // Given + let source = TestMidiSource::NoteVelocity { + channel: Some(ch(0)), + key_number: None, + }; + // When + // Then + assert_abs_diff_eq!( + source + .control(&plain(note_on(0, 64, 127,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&plain(note_on(0, 64, 127,))).unwrap(), + frac(127, 127) + ); + assert_abs_diff_eq!( + source + .control(&plain(note_on(0, 20, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(note_on(0, 20, 0,))).unwrap(), + frac(0, 127) + ); + assert_abs_diff_eq!( + source + .control(&plain(note_off(0, 20, 100,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(note_off(0, 20, 100,))).unwrap(), + frac(0, 127) + ); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(program_change(5, 64,))), None); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(3, 14, 64,))), + None + ); + assert_eq!(source.control(&plain(channel_pressure(3, 79,))), None); + assert_eq!(source.control(&plain(pitch_bend_change(3, 15012,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "64" + ); + } + + #[test] + fn note_velocity_2() { + // Given + let source = TestMidiSource::NoteVelocity { + channel: Some(ch(4)), + key_number: Some(kn(20)), + }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 64, 127,))), None); + assert_abs_diff_eq!( + source + .control(&plain(note_on(4, 20, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(note_on(4, 20, 0,))).unwrap(), + frac(0, 127) + ); + assert_eq!(source.control(&plain(note_off(15, 20, 100,))), None); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(plain(note_on(4, 20, 64))) + ); + } + + #[test] + fn note_key_number_1() { + // Given + let source = TestMidiSource::NoteKeyNumber { channel: None }; + // When + // Then + assert_abs_diff_eq!( + source + .control(&plain(note_on(0, 127, 55,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&plain(note_on(0, 127, 55,))).unwrap(), + frac(127, 127) + ); + assert_abs_diff_eq!( + source + .control(&plain(note_on(1, 0, 64,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(note_on(1, 0, 64,))).unwrap(), + frac(0, 127) + ); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(program_change(5, 64,))), None); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(3, 14, 64,))), + None + ); + assert_eq!(source.control(&plain(channel_pressure(3, 79,))), None); + assert_eq!(source.control(&plain(pitch_bend_change(3, 15012,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "64" + ); + } + + #[test] + fn note_key_number_2() { + // Given + let source = TestMidiSource::NoteKeyNumber { + channel: Some(ch(1)), + }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_abs_diff_eq!( + source + .control(&plain(note_on(1, 0, 64,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(note_on(1, 0, 64,))).unwrap(), + frac(0, 127) + ); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(plain(note_on(1, 64, 127))) + ); + } + + #[test] + fn polyphonic_key_pressure_amount_1() { + // Given + let source = TestMidiSource::PolyphonicKeyPressureAmount { + channel: Some(ch(1)), + key_number: None, + }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(program_change(5, 64,))), None); + assert_abs_diff_eq!( + source + .control(&plain(polyphonic_key_pressure(1, 14, 127,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source + .control(&plain(polyphonic_key_pressure(1, 14, 127,))) + .unwrap(), + frac(127, 127) + ); + assert_abs_diff_eq!( + source + .control(&plain(polyphonic_key_pressure(1, 16, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source + .control(&plain(polyphonic_key_pressure(1, 16, 0,))) + .unwrap(), + frac(0, 127) + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(3, 14, 127))), + None + ); + assert_eq!(source.control(&plain(channel_pressure(3, 79,))), None); + assert_eq!(source.control(&plain(pitch_bend_change(3, 15012,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "64" + ); + } + + #[test] + fn polyphonic_key_pressure_amount_2() { + // Given + let source = TestMidiSource::PolyphonicKeyPressureAmount { + channel: Some(ch(1)), + key_number: Some(kn(53)), + }; + // When + // Then + assert_abs_diff_eq!( + source + .control(&plain(polyphonic_key_pressure(1, 53, 127,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source + .control(&plain(polyphonic_key_pressure(1, 53, 127,))) + .unwrap(), + frac(127, 127) + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(channel_pressure(3, 79,))), None); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(plain(polyphonic_key_pressure(1, 53, 64))) + ); + } + + #[test] + fn control_change_value_1() { + // Given + let source = TestMidiSource::ControlChangeValue { + channel: Some(ch(1)), + controller_number: None, + custom_character: SourceCharacter::RangeElement, + }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_abs_diff_eq!( + source + .control(&plain(control_change(1, 64, 127,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&plain(control_change(1, 64, 127,))).unwrap(), + frac(127, 127) + ); + assert_eq!(source.control(&plain(program_change(5, 64,))), None); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(channel_pressure(3, 79,))), None); + assert_eq!(source.control(&plain(pitch_bend_change(3, 15012,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "64" + ); + } + + #[test] + fn control_change_value_2() { + // Given + let source = TestMidiSource::ControlChangeValue { + channel: Some(ch(1)), + controller_number: Some(cn(64)), + custom_character: SourceCharacter::Encoder2, + }; + // When + // Then + assert_eq!(source.control(&plain(control_change(1, 65, 127,))), None); + assert_abs_diff_eq!( + source.control(&plain(control_change(1, 64, 62,))).unwrap(), + rel(-2) + ); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!( + source.test_feedback::(fv(0.0)), + Some(plain(control_change(1, 64, 0))) + ); + assert_eq!( + source.test_feedback::(fv(0.25)), + Some(plain(control_change(1, 64, 32))) + ); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(plain(control_change(1, 64, 64))) + ); + // In a center-oriented mapping this would yield 96 instead of 95 + assert_eq!( + source.test_feedback::(fv(0.75)), + Some(plain(control_change(1, 64, 95))) + ); + assert_eq!( + source.test_feedback::(fv(1.0)), + Some(plain(control_change(1, 64, 127))) + ); + } + + #[test] + fn program_change_number_1() { + // Given + let source = TestMidiSource::ProgramChangeNumber { channel: None }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_abs_diff_eq!( + source + .control(&plain(program_change(5, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(program_change(5, 0,))).unwrap(), + frac(0, 127) + ); + assert_abs_diff_eq!( + source + .control(&plain(program_change(6, 127,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&plain(program_change(6, 127,))).unwrap(), + frac(127, 127) + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(channel_pressure(3, 79,))), None); + assert_eq!(source.control(&plain(pitch_bend_change(3, 15012,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "64" + ); + } + + #[test] + fn program_change_number_2() { + // Given + let source = TestMidiSource::ProgramChangeNumber { + channel: Some(ch(10)), + }; + // When + // Then + assert_abs_diff_eq!( + source + .control(&plain(program_change(10, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(program_change(10, 0,))).unwrap(), + frac(0, 127) + ); + assert_eq!(source.control(&plain(program_change(6, 127,))), None); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(plain(program_change(10, 64))) + ); + } + + #[test] + fn channel_pressure_amount_1() { + // Given + let source = TestMidiSource::ChannelPressureAmount { channel: None }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_abs_diff_eq!( + source + .control(&plain(channel_pressure(5, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(channel_pressure(5, 0,))).unwrap(), + frac(0, 127) + ); + assert_abs_diff_eq!( + source + .control(&plain(channel_pressure(6, 127,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&plain(channel_pressure(6, 127,))).unwrap(), + frac(127, 127) + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(program_change(3, 79,))), None); + assert_eq!(source.control(&plain(pitch_bend_change(3, 15012,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "64" + ); + } + + #[test] + fn channel_pressure_amount_2() { + // Given + let source = TestMidiSource::ChannelPressureAmount { + channel: Some(ch(15)), + }; + // When + // Then + assert_eq!(source.control(&plain(channel_pressure(5, 0,))), None); + assert_abs_diff_eq!( + source + .control(&plain(channel_pressure(15, 127,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&plain(channel_pressure(15, 127,))).unwrap(), + frac(127, 127) + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(plain(channel_pressure(15, 64))) + ); + } + + #[test] + fn pitch_bend_change_value_1() { + // Given + let source = TestMidiSource::PitchBendChangeValue { channel: None }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_abs_diff_eq!( + source + .control(&plain(pitch_bend_change(5, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(pitch_bend_change(5, 0,))).unwrap(), + frac(0, 16384) + ); + assert_abs_diff_eq!( + source + .control(&plain(pitch_bend_change(6, 4096,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.25) + ); + assert_eq!( + source.control(&plain(pitch_bend_change(6, 4096,))).unwrap(), + frac(4096, 16384) + ); + assert_abs_diff_eq!( + source + .control(&plain(pitch_bend_change(6, 8192,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.5) + ); + assert_eq!( + source.control(&plain(pitch_bend_change(6, 8192,))).unwrap(), + frac(8192, 16384) + ); + assert_abs_diff_eq!( + source + .control(&plain(pitch_bend_change(6, 12288,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.75) + ); + assert_eq!( + source + .control(&plain(pitch_bend_change(6, 12288,))) + .unwrap(), + frac(12288, 16384) + ); + assert_abs_diff_eq!( + source + .control(&plain(pitch_bend_change(6, 16383,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source + .control(&plain(pitch_bend_change(6, 16383,))) + .unwrap(), + frac(16383, 16383) + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(program_change(3, 79,))), None); + assert_eq!(source.control(&plain(channel_pressure(3, 2,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 12000))), + None + ); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "0" + ); + } + + #[test] + fn pitch_bend_change_value_2() { + // Given + let source = TestMidiSource::PitchBendChangeValue { + channel: Some(ch(3)), + }; + // When + // Then + assert_abs_diff_eq!( + source + .control(&plain(pitch_bend_change(3, 0,))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!( + source.control(&plain(pitch_bend_change(3, 0,))).unwrap(), + frac(0, 16384) + ); + assert_eq!(source.control(&plain(pitch_bend_change(6, 8192,))), None); + assert_eq!( + source.test_feedback::(fv(0.0)), + Some(plain(pitch_bend_change(3, 0))) + ); + assert_eq!( + source.test_feedback::(fv(0.25)), + Some(plain(pitch_bend_change(3, 4096))) + ); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(plain(pitch_bend_change(3, 8192))) + ); + assert_eq!( + source.test_feedback::(fv(0.75)), + Some(plain(pitch_bend_change(3, 12288))) + ); + assert_eq!( + source.test_feedback::(fv(1.0)), + Some(plain(pitch_bend_change(3, 16383))) + ); + } + + #[test] + fn control_change_14_bit_value_1() { + // Given + let source = TestMidiSource::ControlChange14BitValue { + channel: Some(ch(1)), + msb_controller_number: None, + custom_character: Default::default(), + }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_abs_diff_eq!( + source + .control(&cc(control_change_14_bit(1, 10, 4096))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.2500152597204419) + ); + assert_eq!( + source + .control(&cc(control_change_14_bit(1, 10, 4096))) + .unwrap(), + frac(4096, 16383) + ); + assert_abs_diff_eq!( + source + .control(&cc(control_change_14_bit(1, 10, 16383))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source + .control(&cc(control_change_14_bit(1, 10, 16383))) + .unwrap(), + frac(16383, 16383) + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(program_change(3, 79,))), None); + assert_eq!(source.control(&plain(channel_pressure(3, 2,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!(source.control(&plain(pitch_bend_change(6, 8192,))), None); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "8192" + ); + } + + #[test] + fn control_change_14_bit_value_2() { + // Given + let source = TestMidiSource::ControlChange14BitValue { + channel: Some(ch(1)), + msb_controller_number: Some(cn(7)), + custom_character: Default::default(), + }; + // When + // Then + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 4096))), + None + ); + assert_abs_diff_eq!( + source + .control(&cc(control_change_14_bit(1, 7, 16383))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source + .control(&cc(control_change_14_bit(1, 7, 16383))) + .unwrap(), + frac(16383, 16383) + ); + assert_eq!( + source.test_feedback::(fv(0.0)), + Some(cc(control_change_14_bit(1, 7, 0))) + ); + assert_eq!( + source.test_feedback::(fv(0.25)), + Some(cc(control_change_14_bit(1, 7, 4096))) + ); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(cc(control_change_14_bit(1, 7, 8192))) + ); + // In a center-oriented mapping this would yield 12288 instead of 12287 + assert_eq!( + source.test_feedback::(fv(0.75)), + Some(cc(control_change_14_bit(1, 7, 12287))) + ); + assert_eq!( + source.test_feedback::(fv(1.0)), + Some(cc(control_change_14_bit(1, 7, 16383))) + ); + } + + #[test] + fn parameter_number_value_1() { + // Given + let source = TestMidiSource::ParameterNumberValue { + channel: None, + number: None, + is_14_bit: None, + is_registered: None, + custom_character: SourceCharacter::RangeElement, + }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 4096))), + None + ); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 16383))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(program_change(3, 79,))), None); + assert_eq!(source.control(&plain(channel_pressure(3, 2,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_abs_diff_eq!( + source + .control(&pn(rpn_14_bit(1, 520, 2048))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.12500762986022096) + ); + assert_eq!( + source.control(&pn(rpn_14_bit(1, 520, 2048))).unwrap(), + frac(2048, 16383) + ); + assert_abs_diff_eq!( + source + .control(&pn(nrpn_14_bit(1, 520, 16383))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&pn(nrpn_14_bit(1, 520, 16383))).unwrap(), + frac(16383, 16383) + ); + assert_abs_diff_eq!( + source + .control(&pn(rpn(1, 342, 0))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!(source.control(&pn(rpn(1, 342, 0))).unwrap(), frac(0, 127)); + assert_abs_diff_eq!( + source + .control(&pn(nrpn(1, 520, 127))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&pn(nrpn(1, 520, 127))).unwrap(), + frac(127, 127) + ); + assert_eq!(source.control(&plain(pitch_bend_change(6, 8192,))), None); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert!(source.format_control_value(abs(0.5)).is_err()); + } + + #[test] + fn parameter_number_value_2() { + // Given + let source = TestMidiSource::ParameterNumberValue { + channel: Some(ch(7)), + number: Some(u14(3000)), + is_14_bit: Some(false), + is_registered: Some(true), + custom_character: SourceCharacter::RangeElement, + }; + // When + // Then + assert_eq!(source.control(&plain(control_change(7, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(7, 64, 127,))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(7, 10, 4096))), + None + ); + assert_eq!( + source.control(&cc(control_change_14_bit(7, 10, 16383))), + None + ); + assert_eq!(source.control(&pn(rpn_14_bit(7, 3000, 11253))), None); + assert_abs_diff_eq!( + source + .control(&pn(rpn(7, 3000, 0))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(0.0) + ); + assert_eq!(source.control(&pn(rpn(7, 3000, 0))).unwrap(), frac(0, 127)); + assert_eq!(source.control(&pn(nrpn_14_bit(7, 3000, 45))), None); + assert_eq!(source.control(&pn(nrpn(7, 3000, 24))), None); + assert_eq!( + source.test_feedback::(fv(0.0)), + Some(pn(rpn(7, 3000, 0))) + ); + assert_eq!( + source.test_feedback::(fv(0.25)), + Some(pn(rpn(7, 3000, 32))) + ); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(pn(rpn(7, 3000, 64))) + ); + // In a center-oriented mapping this would yield 96 instead of 95 + assert_eq!( + source.test_feedback::(fv(0.75)), + Some(pn(rpn(7, 3000, 95))) + ); + assert_eq!( + source.test_feedback::(fv(1.0)), + Some(pn(rpn(7, 3000, 127))) + ); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "64" + ); + } + + #[test] + fn parameter_number_value_2_toggle() { + // Given + let source = TestMidiSource::ParameterNumberValue { + channel: Some(ch(7)), + number: Some(u14(3000)), + is_14_bit: Some(false), + is_registered: Some(true), + custom_character: SourceCharacter::ToggleButton, + }; + // When + // Then + assert_abs_diff_eq!( + source + .control(&pn(rpn(7, 3000, 0))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&pn(rpn(7, 3000, 0))).unwrap(), + frac(127, 127) + ); + assert_abs_diff_eq!( + source + .control(&pn(rpn(7, 3000, 50))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&pn(rpn(7, 3000, 50))).unwrap(), + frac(127, 127) + ); + assert_abs_diff_eq!( + source + .control(&pn(rpn(7, 3000, 127))) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!( + source.control(&pn(rpn(7, 3000, 127))).unwrap(), + frac(127, 127) + ); + } + + #[test] + fn parameter_number_value_3() { + // Given + let source = TestMidiSource::ParameterNumberValue { + channel: Some(ch(7)), + number: Some(u14(3000)), + is_14_bit: Some(true), + is_registered: Some(true), + custom_character: SourceCharacter::RangeElement, + }; + // When + // Then + assert_eq!( + source.test_feedback::(fv(0.0)), + Some(pn(rpn_14_bit(7, 3000, 0))) + ); + assert_eq!( + source.test_feedback::(fv(0.25)), + Some(pn(rpn_14_bit(7, 3000, 4096))) + ); + assert_eq!( + source.test_feedback::(fv(0.5)), + Some(pn(rpn_14_bit(7, 3000, 8192))) + ); + // In a center-oriented mapping this would yield 12288 instead of 12287 + assert_eq!( + source.test_feedback::(fv(0.75)), + Some(pn(rpn_14_bit(7, 3000, 12287))) + ); + assert_eq!( + source.test_feedback::(fv(1.0)), + Some(pn(rpn_14_bit(7, 3000, 16383))) + ); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "8192" + ); + } + + #[test] + fn clock_tempo() { + // Given + let source = TestMidiSource::ClockTempo; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 4096))), + None + ); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 16383))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(program_change(3, 79,))), None); + assert_eq!(source.control(&plain(channel_pressure(3, 2,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!(source.control(&plain(r#continue())), None); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 16383))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!(source.control(&plain(pitch_bend_change(6, 8192,))), None); + assert_abs_diff_eq!( + source.control(&tempo(120.0)).unwrap(), + abs(0.12408759124087591) + ); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert_eq!( + source.format_control_value(abs(0.5)).expect("bad").as_str(), + "480.50" + ); + } + + #[test] + fn clock_transport() { + // Given + let source = TestMidiSource::ClockTransport { + message: MidiClockTransportMessage::Continue, + }; + // When + // Then + assert_eq!(source.control(&plain(note_on(0, 127, 55,))), None); + assert_eq!(source.control(&plain(note_on(1, 0, 64,))), None); + assert_eq!(source.control(&plain(note_off(0, 20, 100,))), None); + assert_eq!(source.control(&plain(note_on(4, 20, 0,))), None); + assert_eq!(source.control(&plain(control_change(3, 64, 127,))), None); + assert_eq!(source.control(&plain(control_change(1, 64, 127,))), None); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 4096))), + None + ); + assert_eq!( + source.control(&cc(control_change_14_bit(1, 10, 16383))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 53, 127,))), + None + ); + assert_eq!( + source.control(&plain(polyphonic_key_pressure(1, 16, 0,))), + None + ); + assert_eq!(source.control(&plain(program_change(3, 79,))), None); + assert_eq!(source.control(&plain(channel_pressure(3, 2,))), None); + assert_eq!(source.control(&plain(timing_clock())), None); + assert_eq!(source.control(&plain(start())), None); + assert_eq!( + source + .control(&plain(r#continue())) + .unwrap() + .to_absolute_continuous() + .unwrap(), + abs(1.0) + ); + assert_eq!(source.control(&plain(r#continue())).unwrap(), frac(1, 1)); + assert_eq!(source.control(&plain(stop())), None); + assert_eq!(source.control(&plain(active_sensing())), None); + assert_eq!(source.control(&plain(system_reset())), None); + assert_eq!(source.control(&pn(rpn_14_bit(1, 520, 11253))), None); + assert_eq!(source.control(&pn(nrpn_14_bit(1, 520, 16383))), None); + assert_eq!(source.control(&pn(rpn(1, 342, 45))), None); + assert_eq!(source.control(&pn(nrpn(1, 520, 24))), None); + assert_eq!(source.control(&plain(pitch_bend_change(6, 8192,))), None); + assert_eq!(source.control(&tempo(120.0)), None); + assert_eq!(source.test_feedback::(fv(0.5)), None); + assert!(source.format_control_value(abs(0.5)).is_err()); + } + + fn abs(value: f64) -> ControlValue { + ControlValue::absolute_continuous(value) + } + + fn frac(actual: u32, max: u32) -> ControlValue { + ControlValue::absolute_discrete(actual, max) + } + + fn rel(increment: i32) -> ControlValue { + ControlValue::relative(increment) + } + + fn plain(msg: RawShortMessage) -> MidiSourceValue<'static, RawShortMessage> { + MidiSourceValue::Plain(msg) + } + + fn pn(msg: ParameterNumberMessage) -> MidiSourceValue<'static, RawShortMessage> { + MidiSourceValue::ParameterNumber(msg) + } + + fn cc(msg: ControlChange14BitMessage) -> MidiSourceValue<'static, RawShortMessage> { + MidiSourceValue::ControlChange14Bit(msg) + } + + fn fv(value: f64) -> FeedbackValue<'static> { + FeedbackValue::Numeric(NumericFeedbackValue::new( + Default::default(), + AbsoluteValue::Continuous(UnitValue::new(value)), + )) + } + + fn tempo(bpm: f64) -> MidiSourceValue<'static, RawShortMessage> { + MidiSourceValue::Tempo(Bpm::new_panic(bpm)) + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source_script.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source_script.rs new file mode 100644 index 0000000..89d6f5a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source_script.rs @@ -0,0 +1,22 @@ +use crate::{FeedbackValue, MidiSourceAddress, RawMidiEvents}; +use std::borrow::Cow; + +// The lifetime 'a is necessary in case we want to parameterize the lifetime +// of the additional input dynamically. An alternative would have been to +// require the additional input type to be static and take it by reference. +// But that would be less generic. +pub trait MidiSourceScript<'a> { + type AdditionalInput: Default; + + /// Returns raw MIDI bytes. + fn execute( + &self, + input_value: FeedbackValue, + additional_input: Self::AdditionalInput, + ) -> Result>; +} + +pub struct MidiSourceScriptOutcome { + pub address: Option, + pub events: RawMidiEvents, +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source_value.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source_value.rs new file mode 100644 index 0000000..4549455 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/midi_source_value.rs @@ -0,0 +1,349 @@ +use crate::{DisplaySpecAddress, MidiSourceAddress, PatternByte, UnitValue}; +use helgoboss_midi::{ + Channel, ControlChange14BitMessage, DataEntryByteOrder, ParameterNumberMessage, ShortMessage, + ShortMessageFactory, StructuredShortMessage, +}; +use reaper_common_types::Bpm; +use std::ops::RangeInclusive; + +pub type RawMidiEvents = Vec; + +/// Values produced when asking for feedback from MIDI sources. +/// +/// At the moment, we always produce a final value and maybe a non-final one in addition, so this +/// isn't an enum. +#[derive(Clone, PartialEq, Debug)] +pub struct PreliminaryMidiSourceFeedbackValue<'a, M: ShortMessage> { + /// A concrete MIDI message. + pub final_value: MidiSourceValue<'a, M>, + /// Request to set the color of one particular XTouch channel display. + /// + /// The XTouch doesn't provide a way to set the color for one particular channel, only one to + /// set the colors of all channels at once. That means we need to keep the current color of + /// each channel around as state, "integrate" these requests after collecting them from the + /// sources and then build the final sys-ex message. + pub x_touch_mackie_lcd_color_request: Option, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct XTouchMackieLcdColorRequest { + pub extender_index: u8, + pub channel: Option, + pub color_index: Option, +} + +/// Incoming or outgoing value which might be used to control something or send feedback. +#[derive(Clone, PartialEq, Debug)] +pub enum MidiSourceValue<'a, M: ShortMessage> { + // Feedback and control + Plain(M), + ParameterNumber(ParameterNumberMessage), + ControlChange14Bit(ControlChange14BitMessage), + /// We must take care not to allocate this in real-time thread! + Raw { + feedback_address_info: Option, + events: RawMidiEvents, + }, + // Control-only + Tempo(Bpm), + // Control-only + BorrowedSysEx(&'a [u8]), +} + +/// For being able to reconstructing the source address for feedback purposes (in particular, +/// source takeover). +/// +/// Also important for preventing duplicate feedback. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum RawFeedbackAddressInfo { + Raw { + variable_range: Option>, + }, + Display { + spec: DisplaySpecAddress, + }, + Custom(MidiSourceAddress), +} + +impl MidiSourceValue<'_, M> { + pub fn single_raw( + feedback_address_info: Option, + event: RawMidiEvent, + ) -> Self { + Self::Raw { + feedback_address_info, + events: create_raw_midi_events_singleton(event), + } + } +} + +pub fn create_raw_midi_events_singleton(event: RawMidiEvent) -> RawMidiEvents { + vec![event] +} + +impl MidiSourceValue<'_, M> { + pub fn extract_feedback_address(&self) -> Option { + use MidiSourceValue::*; + let res = match self { + Plain(m) => { + use StructuredShortMessage::*; + match m.to_structured() { + NoteOn { + channel, + key_number, + .. + } + | NoteOff { + channel, + key_number, + .. + } => MidiSourceAddress::Note { + channel, + key_number, + }, + PolyphonicKeyPressure { + channel, + key_number, + .. + } => MidiSourceAddress::PolyphonicKeyPressure { + channel, + key_number, + }, + ControlChange { + channel, + controller_number, + .. + } => MidiSourceAddress::ControlChange { + channel, + controller_number, + is_14_bit: false, + }, + ProgramChange { channel, .. } => MidiSourceAddress::ProgramChange { channel }, + ChannelPressure { channel, .. } => { + MidiSourceAddress::ChannelPressure { channel } + } + PitchBendChange { channel, .. } => { + MidiSourceAddress::PitchBendChange { channel } + } + // No feedback supported for other types of MIDI messages + _ => return None, + } + } + ParameterNumber(msg) => MidiSourceAddress::ParameterNumber { + channel: msg.channel(), + number: msg.number(), + is_registered: msg.is_registered(), + }, + ControlChange14Bit(msg) => MidiSourceAddress::ControlChange { + channel: msg.channel(), + controller_number: msg.msb_controller_number(), + is_14_bit: true, + }, + Raw { + feedback_address_info, + events, + } => match feedback_address_info.as_ref()? { + RawFeedbackAddressInfo::Raw { variable_range } => MidiSourceAddress::Raw { + pattern: events + .first()? + .bytes() + .iter() + .enumerate() + .map(|(i, b)| { + if let Some(vr) = variable_range { + if vr.contains(&i) { + PatternByte::Variable + } else { + PatternByte::Fixed(*b) + } + } else { + PatternByte::Fixed(*b) + } + }) + .collect(), + }, + RawFeedbackAddressInfo::Display { spec } => { + MidiSourceAddress::Display { spec: spec.clone() } + } + RawFeedbackAddressInfo::Custom(addr) => addr.clone(), + }, + // No feedback + Tempo(_) | BorrowedSysEx(_) => return None, + }; + Some(res) + } + + pub fn channel(&self) -> Option { + use MidiSourceValue::*; + match self { + Plain(m) => m.channel(), + ParameterNumber(m) => Some(m.channel()), + ControlChange14Bit(m) => Some(m.channel()), + _ => None, + } + } + + /// Might allocate! + /// + /// Not usable for producing feedback output that should participate in feedback relay + /// (since BorrowedSysEx doesn't contain a feedback address). + pub fn try_into_owned(self) -> Result, &'static str> { + use MidiSourceValue::*; + let res = match self { + Plain(v) => Plain(v), + ParameterNumber(v) => ParameterNumber(v), + ControlChange14Bit(v) => ControlChange14Bit(v), + Tempo(v) => Tempo(v), + Raw { + feedback_address_info, + events, + } => Raw { + feedback_address_info, + events, + }, + BorrowedSysEx(bytes) => { + // Situations where we convert a borrowed message into an owned are not + // situations in which we want to send a feedback value. So it's not bad that + // we can't provide a feedback address here. + let feedback_address_info = None; + let event = RawMidiEvent::try_from_slice(0, bytes)?; + MidiSourceValue::single_raw(feedback_address_info, event) + } + }; + Ok(res) + } + + pub fn into_garbage(self) -> Option { + use MidiSourceValue::*; + match self { + Raw { events, .. } => Some(events), + _ => None, + } + } + + /// For values that are best sent raw, e.g. sys-ex. + pub fn to_raw(&self) -> Option> { + use MidiSourceValue::*; + match self { + Raw { events, .. } => Some(events.iter()), + _ => None, + } + } + + /// For values that are best sent as short messages. + pub fn to_short_messages( + &self, + nrpn_data_entry_byte_order: DataEntryByteOrder, + ) -> [Option; 4] { + use MidiSourceValue::*; + match self { + Plain(msg) => [Some(*msg), None, None, None], + ParameterNumber(msg) => msg.to_short_messages(nrpn_data_entry_byte_order), + ControlChange14Bit(msg) => { + let inner_shorts = msg.to_short_messages(); + [Some(inner_shorts[0]), Some(inner_shorts[1]), None, None] + } + Tempo(_) | Raw { .. } | BorrowedSysEx(_) => [None; 4], + } + } +} + +impl From for Bpm { + fn from(value: UnitValue) -> Self { + let min = Bpm::ONE_BPM.get(); + let span = Bpm::NINE_HUNDRED_SIXTY_BPM.get() - min; + Bpm::new_panic(min + value.get() * span) + } +} + +impl From for UnitValue { + fn from(value: Bpm) -> Self { + let min = Bpm::ONE_BPM.get(); + let span = Bpm::NINE_HUNDRED_SIXTY_BPM.get() - min; + // At some point, we allowed BPM values higher than 960 BPM (it's just that REAPER doesn't take them). + // That's why we clamp. + UnitValue::new_clamped((value.get() - min) / span) + } +} + +/// Raw MIDI data which is compatible to both VST and REAPER MIDI data structures. The REAPER +/// struct is more picky in that it needs offset and size directly in front of the raw data whereas +/// the VST struct allows the data to be at a different address. That's why we need to follow the +/// REAPER requirement. +/// +/// Conforms to the LongMidiEvent in `reaper-medium` but the goal of `helgoboss-learn` is to be +/// DAW-agnostic, so we have to recreate the lowest common denominator. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[repr(C)] +pub struct RawMidiEvent { + /// A MIDI frame offset. + /// + /// This is a 1/1024000 of a second, *not* a sample frame! + frame_offset: i32, + size: i32, + midi_message: [u8; RawMidiEvent::MAX_LENGTH], +} + +impl Default for RawMidiEvent { + fn default() -> Self { + Self { + frame_offset: 0, + size: 0, + midi_message: [0; RawMidiEvent::MAX_LENGTH], + } + } +} + +impl RawMidiEvent { + pub const MAX_LENGTH: usize = 256; + + pub const fn new(frame_offset: u32, size: u32, midi_message: [u8; Self::MAX_LENGTH]) -> Self { + Self { + frame_offset: frame_offset as _, + size: size as _, + midi_message, + } + } + + /// If you already have a slice, use this. If you are just building something, `try_from_iter` + /// is probably more efficient. + pub fn try_from_slice(frame_offset: u32, midi_message: &[u8]) -> Result { + if midi_message.len() > Self::MAX_LENGTH { + return Err("given MIDI message too long"); + } + let mut array = [0; Self::MAX_LENGTH]; + // TODO-low I think copying from a slice is the only way to go, even we own a vec or array. + // REAPER's struct layout requires us to put something in front of the vec, which is + // not or at least not easily possible without copying. + array[..midi_message.len()].copy_from_slice(midi_message); + Ok(Self::new(frame_offset, midi_message.len() as _, array)) + } + + pub fn try_from_iter>( + frame_offset: u32, + iter: T, + ) -> Result { + let mut array = [0; Self::MAX_LENGTH]; + let mut i = 0usize; + for b in iter { + if i == Self::MAX_LENGTH { + return Err("given content too long"); + } + let elem = unsafe { array.get_unchecked_mut(i) }; + *elem = b; + i += 1; + } + Ok(Self::new(frame_offset, i as u32, array)) + } + + pub fn bytes(&self) -> &[u8] { + &self.midi_message[..self.size as usize] + } +} + +#[cfg(feature = "reaper-low")] +impl AsRef for RawMidiEvent { + fn as_ref(&self) -> &reaper_low::raw::MIDI_event_t { + unsafe { &*(self as *const RawMidiEvent as *const reaper_low::raw::MIDI_event_t) } + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/mod.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/mod.rs new file mode 100644 index 0000000..c2c131e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/mod.rs @@ -0,0 +1,27 @@ +mod midi_source_value; +pub use midi_source_value::*; + +mod midi_source; +pub use midi_source::*; + +mod osc_source; +pub use osc_source::*; + +mod raw_midi; +pub use raw_midi::*; + +mod midi_source_script; +pub use midi_source_script::*; + +mod feedback_script; +pub use feedback_script::*; + +mod source_context; +pub use source_context::*; + +mod color_util; + +#[cfg(test)] +mod test_util; + +pub mod devices; diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/osc_source.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/osc_source.rs new file mode 100644 index 0000000..2256e0d --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/osc_source.rs @@ -0,0 +1,573 @@ +use crate::DetailedSourceCharacter::Trigger; +use std::cmp; + +use crate::{ + format_percentage_without_unit, parse_percentage_without_unit, AbsoluteValue, ControlValue, + DetailedSourceCharacter, DiscreteIncrement, FeedbackValue, Fraction, Interval, RgbColor, + SourceCharacter, UnitValue, UNIT_INTERVAL, +}; +use derive_more::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use rosc::{OscColor, OscMessage, OscType}; +use serde::{Deserialize, Serialize}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::convert::TryInto; +use strum::EnumIter; + +/// With OSC it's easy: The source address is the address! +pub type OscSourceAddress = String; + +#[derive(Clone, PartialEq, Debug)] +pub struct OscSource { + /// To filter out the correct messages. + address_pattern: String, + /// To process a value (not just trigger). + arg_descriptor: Option, + /// If non-empty, these are used for mapping feedback data to arguments. + feedback_args: Vec, +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + strum::EnumString, + strum::Display, + SerializeDisplay, + DeserializeFromStr, +)] +pub enum OscFeedbackProp { + // Floats + #[strum(serialize = "value.float")] + ValueAsFloat, + // Doubles + #[strum(serialize = "value.double")] + ValueAsDouble, + // Bools + #[strum(serialize = "value.bool")] + ValueAsBool, + // Nil + #[strum(serialize = "nil")] + Nil, + // Inf + #[strum(serialize = "inf")] + Inf, + // Integers + #[strum(serialize = "value.int")] + ValueAsInt, + // Strings + #[strum(serialize = "value.string")] + ValueAsString, + // Longs + #[strum(serialize = "value.long")] + ValueAsLong, + #[strum(serialize = "style.color.rrggbb")] + ColorRrggbb, + #[strum(serialize = "style.background_color.rrggbb")] + BackgroundColorRrggbb, + // Colors + #[strum(serialize = "style.color")] + Color, + #[strum(serialize = "style.backround_color")] + BackgroundColor, +} + +impl Default for OscFeedbackProp { + fn default() -> Self { + Self::Nil + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct OscArgDescriptor { + /// To select the correct value. + index: u32, + /// To send the correct value type on feedback. + type_tag: OscTypeTag, + /// Interpret 1 values as increments and 0 values as decrements. + is_relative: bool, + /// Value range for all range types (double, float, int, long). + value_range: Interval, +} + +impl OscArgDescriptor { + pub fn new( + index: u32, + type_tag: OscTypeTag, + is_relative: bool, + value_range: Interval, + ) -> Self { + Self { + index, + type_tag, + is_relative, + value_range, + } + } + + pub fn index(self) -> u32 { + self.index + } + + pub fn type_tag(self) -> OscTypeTag { + self.type_tag + } + + pub fn is_relative(self) -> bool { + self.is_relative + } + + pub fn value_range(&self) -> Interval { + self.value_range + } + + pub fn from_msg(msg: &OscMessage, arg_index_hint: u32) -> Option { + let desc = if let Some(hinted_arg) = msg.args.get(arg_index_hint as usize) { + Self::from_arg(arg_index_hint, hinted_arg) + } else { + let first_arg = msg.args.first()?; + Self::from_arg(0, first_arg) + }; + Some(desc) + } + + pub fn to_concrete_args(self, value: FeedbackValue) -> Option> { + self.type_tag + .to_concrete_args(self.index, value, self.value_range) + } + + fn from_arg(index: u32, arg: &OscType) -> Self { + Self { + index, + type_tag: OscTypeTag::from_arg(arg), + // Relative is the exception, so we reset it when learning. + is_relative: false, + value_range: match get_range_value(arg) { + None => DEFAULT_OSC_ARG_VALUE_RANGE, + Some(v) => Interval::new_auto(0.0, v), + }, + } + } +} + +pub const DEFAULT_OSC_ARG_VALUE_RANGE: Interval = UNIT_INTERVAL; + +fn get_range_value(arg: &OscType) -> Option { + use OscType::*; + match arg { + Int(v) => Some(*v as f64), + Float(v) => Some(*v as f64), + Long(v) => Some(*v as f64), + Double(v) => Some(*v), + _ => None, + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[serde(rename_all = "camelCase")] +#[repr(usize)] +// TODO-low Rename. This it not the tag, it's rather the OscType without value. +pub enum OscTypeTag { + #[display(fmt = "Float")] + Float, + #[display(fmt = "Double")] + Double, + #[display(fmt = "Bool (on/off)")] + Bool, + #[display(fmt = "Nil (trigger only)")] + Nil, + #[display(fmt = "Infinitum (trigger only)")] + Inf, + #[display(fmt = "Int")] + Int, + #[display(fmt = "String (feedback only)")] + String, + #[display(fmt = "Blob (ignored)")] + Blob, + #[display(fmt = "Time (ignored)")] + Time, + #[display(fmt = "Long")] + Long, + #[display(fmt = "Char (ignored)")] + Char, + #[display(fmt = "Color (feedback only)")] + Color, + #[display(fmt = "MIDI (ignored)")] + Midi, + #[display(fmt = "Array (ignored)")] + Array, +} + +impl Default for OscTypeTag { + fn default() -> Self { + Self::Float + } +} + +impl OscTypeTag { + pub fn from_arg(arg: &OscType) -> Self { + use OscType::*; + match arg { + Int(_) => Self::Int, + Float(_) => Self::Float, + String(_) => Self::String, + Blob(_) => Self::Blob, + Time(_) => Self::Time, + Long(_) => Self::Long, + Double(_) => Self::Double, + Char(_) => Self::Char, + Color(_) => Self::Color, + Midi(_) => Self::Midi, + Bool(_) => Self::Bool, + Array(_) => Self::Array, + Nil => Self::Nil, + Inf => Self::Inf, + } + } + + pub fn to_concrete_args( + self, + index: u32, + v: FeedbackValue, + value_range: Interval, + ) -> Option> { + use OscTypeTag::*; + let value = match self { + Float => convert_feedback_prop_to_arg(OscFeedbackProp::ValueAsFloat, &v, value_range)?, + Double => { + convert_feedback_prop_to_arg(OscFeedbackProp::ValueAsDouble, &v, value_range)? + } + Bool => convert_feedback_prop_to_arg(OscFeedbackProp::ValueAsBool, &v, value_range)?, + Nil => OscType::Nil, + Inf => OscType::Inf, + Int => convert_feedback_prop_to_arg(OscFeedbackProp::ValueAsInt, &v, value_range)?, + String => { + convert_feedback_prop_to_arg(OscFeedbackProp::ValueAsString, &v, value_range)? + } + Long => convert_feedback_prop_to_arg(OscFeedbackProp::ValueAsLong, &v, value_range)?, + Color => convert_feedback_prop_to_arg(OscFeedbackProp::Color, &v, value_range)?, + _ => return None, + }; + // Send nil for all other elements + let mut vec = vec![OscType::Nil; (index + 1) as usize]; + vec[index as usize] = value; + Some(vec) + } + + pub fn supports_control(self) -> bool { + use OscTypeTag::*; + matches!(self, Float | Double | Bool | Nil | Inf | Int | Long) + } + + pub fn supports_feedback(self) -> bool { + use OscTypeTag::*; + matches!( + self, + Float | Double | Bool | Nil | Inf | Int | String | Long | Color + ) + } + + pub fn supports_value_range(self) -> bool { + use OscTypeTag::*; + matches!(self, Float | Double | Int | Long) + } + + pub fn is_discrete(self) -> bool { + use OscTypeTag::*; + matches!(self, Int | Long) + } +} + +impl OscSource { + pub fn feedback_address(&self) -> &OscSourceAddress { + &self.address_pattern + } + + /// Checks if the given message is directed to the same address as the one of this source. + /// + /// Used for: + /// + /// - Source takeover (feedback) + pub fn has_same_feedback_address_as_value(&self, value: &OscMessage) -> bool { + self.address_pattern == value.addr + } + + /// Checks if this and the given source share the same address. + /// + /// Used for: + /// + /// - Feedback diffing + pub fn has_same_feedback_address_as_source(&self, other: &Self) -> bool { + self.address_pattern == other.address_pattern + } + + pub fn new( + address_pattern: String, + arg_descriptor: Option, + feedback_args: Vec, + ) -> Self { + Self { + address_pattern, + arg_descriptor, + feedback_args, + } + } + + pub fn from_source_value(msg: OscMessage, arg_index_hint: Option) -> OscSource { + let arg_descriptor = OscArgDescriptor::from_msg(&msg, arg_index_hint.unwrap_or(0)); + OscSource::new(msg.addr, arg_descriptor, vec![]) + } + + pub fn address_pattern(&self) -> &str { + &self.address_pattern + } + + pub fn arg_descriptor(&self) -> Option { + self.arg_descriptor + } + + pub fn control(&self, msg: &OscMessage) -> Option { + let (absolute_value, is_relative) = { + if msg.addr != self.address_pattern { + return None; + } + if let Some(desc) = self.arg_descriptor { + if let Some(arg) = msg.args.get(desc.index as usize) { + use OscType::*; + let v = + match arg { + Float(f) => AbsoluteValue::Continuous( + map_continuous_from_range_to_unit(*f as f64, desc.value_range), + ), + Double(d) => AbsoluteValue::Continuous( + map_continuous_from_range_to_unit(*d, desc.value_range), + ), + Bool(on) => AbsoluteValue::Continuous(if *on { + UnitValue::MAX + } else { + UnitValue::MIN + }), + // Infinity/impulse or nil/null - act like a trigger. + Inf | Nil => AbsoluteValue::Continuous(UnitValue::MAX), + Int(i) => AbsoluteValue::Discrete(map_discrete_from_range_to_positive( + *i, + desc.value_range, + )), + Long(l) => { + // TODO-low-discrete Maybe increase fraction integers to 64-bit? Right now + // we don't really take advantage of fractions, so we emit continuous control + // values as long as this doesn't change. + AbsoluteValue::Continuous(map_continuous_from_range_to_unit( + *l as f64, + desc.value_range, + )) + } + String(_) | Blob(_) | Time(_) | Char(_) | Color(_) | Midi(_) + | Array(_) => return None, + }; + (v, desc.is_relative) + } else { + // Argument not found. Don't do anything. + return None; + } + } else { + // Source shall not look at any argument. Act like a trigger. + (AbsoluteValue::Continuous(UnitValue::MAX), false) + } + }; + let control_value = if is_relative { + let inc = if absolute_value.is_on() { 1 } else { -1 }; + ControlValue::RelativeDiscrete(DiscreteIncrement::new(inc)) + } else { + ControlValue::from_absolute(absolute_value) + }; + Some(control_value) + } + + pub fn format_control_value(&self, value: ControlValue) -> Result { + let v = value.to_unit_value()?.get(); + Ok(format_percentage_without_unit(v)) + } + + pub fn parse_control_value(&self, text: &str) -> Result { + parse_percentage_without_unit(text)?.try_into() + } + + pub fn character(&self) -> SourceCharacter { + use SourceCharacter::*; + if let Some(desc) = self.arg_descriptor { + use OscTypeTag::*; + match desc.type_tag { + Float | Double | Int | Long => RangeElement, + Bool | Nil | Inf => MomentaryButton, + _ => MomentaryButton, + } + } else { + MomentaryButton + } + } + + pub fn possible_detailed_characters(&self) -> Vec { + if let Some(desc) = self.arg_descriptor { + if desc.is_relative { + vec![DetailedSourceCharacter::Relative] + } else { + use OscTypeTag::*; + match desc.type_tag { + Float | Double | Int | Long => vec![ + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + ], + _ => vec![DetailedSourceCharacter::MomentaryOnOffButton, Trigger], + } + } + } else { + vec![DetailedSourceCharacter::Trigger] + } + } + + pub fn feedback(&self, feedback_value: FeedbackValue) -> Option { + let msg = OscMessage { + addr: self.address_pattern.clone(), + args: if !self.feedback_args.is_empty() { + // Explicit feedback args given. + let value_range = self + .arg_descriptor + .map(|desc| desc.value_range) + .unwrap_or(DEFAULT_OSC_ARG_VALUE_RANGE); + self.feedback_args + .iter() + .map(|prop| { + convert_feedback_prop_to_arg(*prop, &feedback_value, value_range) + .unwrap_or(OscType::Nil) + }) + .collect() + } else if let Some(desc) = self.arg_descriptor { + // No explicit feedback args given. Just derive from argument descriptor. + desc.to_concrete_args(feedback_value)? + } else { + // No arguments shall be sent. + vec![] + }, + }; + Some(msg) + } +} + +fn convert_feedback_prop_to_arg( + prop: OscFeedbackProp, + v: &FeedbackValue, + value_range: Interval, +) -> Option { + use OscFeedbackProp::*; + let arg = match prop { + ValueAsFloat | ValueAsDouble | ValueAsLong => { + let unit_value = v.to_numeric()?.value.to_unit_value(); + let range_value = map_continuous_from_unit_to_range(unit_value, value_range); + match prop { + ValueAsFloat => OscType::Float(range_value as _), + ValueAsDouble => OscType::Double(range_value), + ValueAsLong => OscType::Long(range_value.round() as i64), + _ => unreachable!(), + } + } + ValueAsBool => OscType::Bool(v.to_numeric()?.value.is_on()), + Nil => OscType::Nil, + Inf => OscType::Inf, + ValueAsInt => { + let range_value = match v.to_numeric()?.value { + AbsoluteValue::Continuous(uv) => { + map_continuous_from_unit_to_range(uv, value_range).round() as i32 + } + AbsoluteValue::Discrete(f) => { + map_discrete_from_positive_to_range(f.actual(), value_range) + } + }; + OscType::Int(range_value) + } + ValueAsString => OscType::String(v.to_textual().text.into_owned()), + ColorRrggbb => convert_color_to_rrggbb_string_arg(v.color()), + BackgroundColorRrggbb => convert_color_to_rrggbb_string_arg(v.background_color()), + Color => convert_color_to_native_color_arg(v.color()), + BackgroundColor => convert_color_to_native_color_arg(v.background_color()), + }; + Some(arg) +} + +fn convert_color_to_rrggbb_string_arg(v: Option) -> OscType { + match v { + // Nil is hopefully interpreted as "Default color". + None => OscType::Nil, + Some(c) => { + let color_string = format!("{:02X}{:02X}{:02X}", c.r(), c.g(), c.b()); + OscType::String(color_string) + } + } +} + +fn convert_color_to_native_color_arg(v: Option) -> OscType { + match v { + // Nil is hopefully interpreted as "Default color". + None => OscType::Nil, + Some(c) => OscType::Color(OscColor { + red: c.r(), + green: c.g(), + blue: c.b(), + alpha: 255, + }), + } +} + +fn map_continuous_from_range_to_unit(x: f64, value_range: Interval) -> UnitValue { + // y = (x - min) / span + let y = (x - value_range.min_val()) / value_range.span(); + UnitValue::new_clamped(y) +} + +fn map_continuous_from_unit_to_range(y: UnitValue, value_range: Interval) -> f64 { + // y = (x - min) / span + // y * span = x - min + // x = y * span + min + y.get() * value_range.span() + value_range.min_val() +} + +fn map_discrete_from_range_to_positive(x: i32, value_range: Interval) -> Fraction { + let rounded_range = round_value_range(value_range); + Fraction::new( + clamp_to_positive(x - rounded_range.min_val()), + clamp_to_positive(rounded_range.span()), + ) +} + +fn map_discrete_from_positive_to_range(y: u32, value_range: Interval) -> i32 { + let rounded_range = round_value_range(value_range); + y as i32 + rounded_range.min_val() +} + +fn round_value_range(value_range: Interval) -> Interval { + Interval::new( + value_range.min_val().round() as i32, + value_range.max_val().round() as i32, + ) +} + +fn clamp_to_positive(v: i32) -> u32 { + cmp::max(0, v) as u32 +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/raw_midi.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/raw_midi.rs new file mode 100644 index 0000000..f279914 --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/raw_midi.rs @@ -0,0 +1,554 @@ +use crate::{AbsoluteValue, Fraction, PatternByte, RawMidiEvent, UnitValue}; +use logos::{Lexer, Logos}; +use std::fmt; +use std::fmt::{Display, Formatter, Write}; +use std::num::ParseIntError; +use std::ops::RangeInclusive; +use std::str::FromStr; + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct RawMidiPattern { + entries: Vec, + resolution: u8, +} + +impl RawMidiPattern { + pub fn from_entries(entries: Vec) -> Self { + let max_variable_bit_index = entries + .iter() + .filter_map(|e| e.max_variable_bit_index()) + .max(); + Self { + entries, + resolution: if let Some(i) = max_variable_bit_index { + i + 1 + } else { + 0 + }, + } + } + + pub fn fixed_from_slice(bytes: &[u8]) -> Self { + let entries = bytes + .iter() + .map(|byte| RawMidiPatternEntry::FixedByte(*byte)) + .collect(); + Self { + entries, + resolution: 0, + } + } + + pub fn variable_range(&self) -> Option> { + let left = self.entries().iter().position(|e| !e.is_fixed())?; + let right = self.entries().iter().rposition(|e| !e.is_fixed())?; + Some(left..=right) + } + + pub fn to_pattern_bytes(&self) -> Vec { + self.entries() + .iter() + .map(|e| { + if let Some(b) = e.byte_if_fixed() { + PatternByte::Fixed(b) + } else { + PatternByte::Variable + } + }) + .collect() + } + + pub fn entries(&self) -> &[RawMidiPatternEntry] { + &self.entries + } + + /// Resolution in bit (maximum 16 bit). + /// + /// If no variable bytes, this returns 0. + pub fn resolution(&self) -> u8 { + self.resolution + } + + /// If no variable bytes, this returns 0. + pub fn max_discrete_value(&self) -> u16 { + (2u32.pow(self.resolution as _) - 1) as u16 + } + + pub fn step_size(&self) -> Option { + let max = self.max_discrete_value(); + if max == 0 { + return None; + } + Some(UnitValue::new_clamped(1.0 / max as f64)) + } + + /// If it matches and there are no variable bytes in the pattern, this returns + /// `Some(Fraction(0, 0))`. + pub fn match_and_capture(&self, bytes: &[u8]) -> Option { + if bytes.len() != self.entries.len() { + return None; + } + let mut current_value: u16 = 0; + for (i, b) in bytes.iter().enumerate() { + let pattern_entry = self.entries[i]; + if let Some(v) = pattern_entry.match_and_capture(*b, current_value) { + current_value = v; + } else { + return None; + } + } + let fraction = Fraction::new(current_value as _, self.max_discrete_value() as _); + Some(fraction) + } + + pub fn to_bytes(&self, variable_value: AbsoluteValue) -> Vec { + self.byte_iter(variable_value).collect() + } + + pub fn byte_iter( + &self, + variable_value: AbsoluteValue, + ) -> impl ExactSizeIterator + '_ { + let discrete_value = match variable_value { + AbsoluteValue::Continuous(v) => v.to_discrete(self.max_discrete_value()), + AbsoluteValue::Discrete(f) => { + std::cmp::min(f.actual(), self.max_discrete_value() as u32) as u16 + } + }; + self.entries.iter().map(move |e| e.to_byte(discrete_value)) + } + + pub fn to_concrete_midi_event( + &self, + frame_offset: u32, + variable_value: AbsoluteValue, + ) -> RawMidiEvent { + // TODO-medium Use RawMidiEvent::try_from_iter + let mut array = [0; RawMidiEvent::MAX_LENGTH]; + let mut i = 0u32; + for byte in self + .byte_iter(variable_value) + .take(RawMidiEvent::MAX_LENGTH) + { + array[i as usize] = byte; + i += 1; + } + RawMidiEvent::new(frame_offset, i, array) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum RawMidiPatternEntry { + FixedByte(u8), + PotentiallyVariableByte(BitPattern), +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct BitPattern { + /// From most significant to least significant bit. + entries: [BitPatternEntry; 8], +} + +impl BitPattern { + pub fn contains_variable_portions(&self) -> bool { + self.entries + .iter() + .any(|bpe| matches!(bpe, BitPatternEntry::VariableBit(_))) + } + + fn max_variable_bit_index(&self) -> Option { + self.entries + .iter() + .filter_map(|bpe| bpe.variable_bit_index()) + .max() + } + + pub fn to_byte(self, discrete_value: u16) -> u8 { + let mut final_byte: u8 = 0; + for i in 0..8 { + use BitPatternEntry::*; + let final_bit = match self.entries[i] { + FixedBit(bit) => bit, + VariableBit(bit_index) => (discrete_value & (1 << bit_index) as u16) > 0, + }; + if final_bit { + final_byte |= 1 << (7 - i); + } + } + final_byte + } + + fn match_and_capture(&self, actual_byte: u8, current_value: u16) -> Option { + let mut new_value = current_value; + for i in 0..8 { + let actual_bit = (actual_byte >> (7 - i)) & 1 == 1; + use BitPatternEntry::*; + match self.entries[i] { + FixedBit(bit) => { + if bit != actual_bit { + return None; + } + } + VariableBit(bit_index) => { + if actual_bit { + new_value |= 1 << bit_index; + } + } + }; + } + Some(new_value) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum BitPatternEntry { + FixedBit(bool), + /// The number represents the bit index starting from 0 where 0 represents the *least* + /// significant bit!. + VariableBit(u8), +} + +impl Default for BitPatternEntry { + fn default() -> Self { + BitPatternEntry::FixedBit(false) + } +} + +impl BitPatternEntry { + fn variable_bit_index(&self) -> Option { + use BitPatternEntry::*; + match self { + FixedBit(_) => None, + VariableBit(i) => Some(*i), + } + } +} + +impl RawMidiPatternEntry { + fn is_fixed(&self) -> bool { + // TODO-low This could be implemented better by transforming potentially variable + // bytes that are not variable into fixed bytes in the first place! + self.byte_if_fixed().is_some() + } + + fn byte_if_fixed(&self) -> Option { + use RawMidiPatternEntry::*; + match self { + FixedByte(b) => Some(*b), + PotentiallyVariableByte(p) => { + if p.contains_variable_portions() { + None + } else { + // Value parameter not important if pattern doesn't contain + // variable portions. + Some(p.to_byte(0)) + } + } + } + } + + fn match_and_capture(&self, actual_byte: u8, current_value: u16) -> Option { + use RawMidiPatternEntry::*; + match self { + FixedByte(b) => { + if actual_byte == *b { + Some(current_value) + } else { + None + } + } + PotentiallyVariableByte(pattern) => { + pattern.match_and_capture(actual_byte, current_value) + } + } + } + + fn max_variable_bit_index(&self) -> Option { + use RawMidiPatternEntry::*; + match self { + FixedByte(_) => None, + PotentiallyVariableByte(bit_pattern) => bit_pattern.max_variable_bit_index(), + } + } + + fn to_byte(self, discrete_value: u16) -> u8 { + use RawMidiPatternEntry::*; + match self { + FixedByte(byte) => byte, + PotentiallyVariableByte(bit_pattern) => bit_pattern.to_byte(discrete_value), + } + } +} + +impl Display for RawMidiPattern { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let string_vec: Vec<_> = self.entries.iter().map(|e| e.to_string()).collect(); + f.write_str(&string_vec.join(" ")) + } +} + +impl Display for RawMidiPatternEntry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use RawMidiPatternEntry::*; + match self { + FixedByte(byte) => write!(f, "{:02X}", *byte), + PotentiallyVariableByte(pattern) => write!(f, "[{pattern}]"), + } + } +} + +impl Display for BitPattern { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for entry in &self.entries[..4] { + let _ = entry.fmt(f); + } + let _ = f.write_char(' '); + for entry in &self.entries[4..] { + let _ = entry.fmt(f); + } + Ok(()) + } +} + +impl Display for BitPatternEntry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use BitPatternEntry::*; + match self { + FixedBit(bit) => write!(f, "{}", if *bit { '1' } else { '0' }), + VariableBit(bit_index) => write!(f, "{}", (97 + bit_index) as char), + } + } +} + +impl FromStr for RawMidiPattern { + type Err = ParseRawMidiPatternError; + + fn from_str(s: &str) -> Result { + let lex: Lexer = RawMidiPatternToken::lexer(s); + use RawMidiPatternToken::*; + let entries: Result, ParseRawMidiPatternError> = lex + .map(|token| { + let entry = match token? { + FixedByte(byte) => RawMidiPatternEntry::FixedByte(byte), + PotentiallyVariableByte(pattern) => { + RawMidiPatternEntry::PotentiallyVariableByte(pattern) + } + }; + Ok(entry) + }) + .collect(); + let entries = entries.map_err(|_| "couldn't parse raw MIDI pattern")?; + Ok(RawMidiPattern::from_entries(entries)) + } +} + +#[derive(Debug, PartialEq, Logos)] +#[logos(skip r"[ \t\n\f]+")] +#[logos(error = ParseRawMidiPatternError)] +enum RawMidiPatternToken { + #[regex(r"\[[01abcdefghijklmnop ]*\]", parse_as_bit_pattern)] + PotentiallyVariableByte(BitPattern), + #[regex(r"[0-9a-fA-F][0-9a-fA-F]?", parse_as_byte)] + FixedByte(u8), +} + +#[derive(Clone, PartialEq, Debug, Default, thiserror::Error)] +#[error("{msg}")] +pub struct ParseRawMidiPatternError { + msg: &'static str, +} + +impl From<&'static str> for ParseRawMidiPatternError { + fn from(msg: &'static str) -> Self { + Self { msg } + } +} + +impl From for ParseRawMidiPatternError { + fn from(_: ParseIntError) -> Self { + Self { + msg: "problem parsing fixed byte", + } + } +} + +fn parse_as_byte(lex: &mut Lexer) -> Result { + u8::from_str_radix(lex.slice(), 16) +} + +fn parse_as_bit_pattern(lex: &mut Lexer) -> Result { + let mut entries: [BitPatternEntry; 8] = Default::default(); + let slice: &str = lex.slice(); + let mut i = 0; + for c in slice.chars() { + use BitPatternEntry::*; + let entry = match c { + '0' => FixedBit(false), + '1' => FixedBit(true), + 'a'..='p' => VariableBit(c as u8 - 97), + _ => continue, + }; + if i > 7 { + return Err("too many bits in bit pattern"); + } + entries[i] = entry; + i += 1; + } + let pattern = BitPattern { entries }; + Ok(pattern) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn one_variable_nibble() { + // Given + let pattern: RawMidiPattern = "F0 [0000 dcba] F7".parse().unwrap(); + // When + // Then + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::MAX)), + vec![0xf0, 0x0f, 0xf7] + ); + assert_eq!( + pattern.match_and_capture(&[0xf0, 0x0f, 0xf7]), + Some(Fraction::new(15, 15)) + ); + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::MIN)), + vec![0xf0, 0x00, 0xf7] + ); + assert_eq!( + pattern.match_and_capture(&[0xf0, 0x00, 0xf7]), + Some(Fraction::new(0, 15)) + ); + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::new(0.5))), + vec![0xf0, 0x08, 0xf7] + ); + assert_eq!( + pattern.match_and_capture(&[0xf0, 0x08, 0xf7]), + Some(Fraction::new(8, 15)) + ); + assert_eq!(&pattern.to_string(), "F0 [0000 dcba] F7"); + assert_eq!(pattern.match_and_capture(&[0xf1, 0x0f, 0xf7]), None); + } + + #[test] + fn one_variable_nibble_no_spaces() { + // Given + let pattern: RawMidiPattern = "F0[0000dcba]F7".parse().unwrap(); + // When + // Then + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::MAX)), + vec![0xf0, 0x0f, 0xf7] + ); + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::MIN)), + vec![0xf0, 0x00, 0xf7] + ); + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::new(0.5))), + vec![0xf0, 0x08, 0xf7] + ); + assert_eq!(&pattern.to_string(), "F0 [0000 dcba] F7"); + } + + #[test] + fn one_variable_nibble_variation() { + // Given + let pattern: RawMidiPattern = "F0[1111dcba]F7".parse().unwrap(); + // When + // Then + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::MAX)), + vec![0xf0, 0xff, 0xf7] + ); + assert_eq!( + pattern.match_and_capture(&[0xf0, 0x0ff, 0xf7]), + Some(Fraction::new(15, 15)) + ); + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::MIN)), + vec![0xf0, 0xf0, 0xf7] + ); + assert_eq!( + pattern.match_and_capture(&[0xf0, 0x0f0, 0xf7]), + Some(Fraction::new(0, 15)) + ); + assert_eq!( + pattern.to_bytes(AbsoluteValue::Continuous(UnitValue::new(0.5))), + vec![0xf0, 0xf8, 0xf7] + ); + assert_eq!( + pattern.match_and_capture(&[0xf0, 0x0f8, 0xf7]), + Some(Fraction::new(8, 15)) + ); + assert_eq!(&pattern.to_string(), "F0 [1111 dcba] F7"); + } + + #[test] + fn wrong_variable_pattern() { + let result = "F0[0000dcbaa]F7".parse::(); + assert!(result.is_err()); + } + + #[test] + fn correct_resolution_1() { + // Given + let pattern: RawMidiPattern = "B0 00 [0nml kjih]".parse().unwrap(); + // When + // Then + assert_eq!(pattern.resolution(), 14); + } + + #[test] + fn correct_resolution_2() { + // Given + let pattern: RawMidiPattern = "B0 00 [0gfe dcba]".parse().unwrap(); + // When + // Then + assert_eq!(pattern.resolution(), 7); + } + + #[test] + fn fixed_pattern() { + // Given + let pattern: RawMidiPattern = "B0 00 F7".parse().unwrap(); + // When + // Then + assert_eq!(pattern.resolution(), 0); + assert_eq!(pattern.max_discrete_value(), 0); + assert_eq!(pattern.match_and_capture(&[0xf0, 0x0f8, 0xf7]), None); + assert_eq!( + pattern.match_and_capture(&[0xb0, 0x00, 0xf7]), + Some(Fraction::new(0, 0)) + ); + } + + #[test] + fn real_world_fixed_pattern() { + // Given + let pattern: RawMidiPattern = "F0 0 20 6B 7F 42 02 00 0 2F 7F F7".parse().unwrap(); + // When + // Then + assert_eq!(pattern.resolution(), 0); + assert_eq!(pattern.max_discrete_value(), 0); + assert_eq!(pattern.match_and_capture(&[0xf0, 0x0f8, 0xf7]), None); + assert_eq!( + pattern.match_and_capture(&[ + 0xF0, 0x0, 0x20, 0x6B, 0x7F, 0x42, 0x2, 0x0, 0x0, 0x2F, 0x7F, 0xF6 + ]), + None + ); + assert_eq!( + pattern.match_and_capture(&[ + 0xF0, 0x0, 0x20, 0x6B, 0x7F, 0x42, 0x2, 0x0, 0x0, 0x2F, 0x7F, 0xF7 + ]), + Some(Fraction::new(0, 0)) + ); + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/source_context.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/source_context.rs new file mode 100644 index 0000000..7dbe7bf --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/source_context.rs @@ -0,0 +1,5 @@ +/// Context for source-related functions. +#[derive(Copy, Clone, Debug, Default)] +pub struct SourceContext { + pub additional_script_input: A, +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/test_util.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/test_util.rs new file mode 100644 index 0000000..42caa9a --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/source/test_util.rs @@ -0,0 +1,16 @@ +use crate::{FeedbackValue, MidiSourceScript, MidiSourceScriptOutcome}; +use std::borrow::Cow; + +pub struct TestMidiSourceScript; + +impl MidiSourceScript<'_> for TestMidiSourceScript { + type AdditionalInput = (); + + fn execute( + &self, + _input_value: FeedbackValue, + _additional_input: (), + ) -> Result> { + unimplemented!() + } +} diff --git a/plugin-reaper-realearn/main/lib/helgoboss-learn/src/test_util.rs b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/test_util.rs new file mode 100644 index 0000000..7f5679e --- /dev/null +++ b/plugin-reaper-realearn/main/lib/helgoboss-learn/src/test_util.rs @@ -0,0 +1,48 @@ +use crate::{AbsoluteValue, ControlValue, UnitValue}; +use approx::AbsDiffEq; + +impl AbsDiffEq for UnitValue { + type Epsilon = f64; + + fn default_epsilon() -> f64 { + f64::EPSILON + } + + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + self.get().abs_diff_eq(&other.get(), epsilon) + } +} + +impl AbsDiffEq for ControlValue { + type Epsilon = f64; + + fn default_epsilon() -> f64 { + f64::EPSILON + } + + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + match (self, other) { + (ControlValue::AbsoluteContinuous(v1), ControlValue::AbsoluteContinuous(v2)) => { + v1.abs_diff_eq(v2, epsilon) + } + _ => self == other, + } + } +} + +impl AbsDiffEq for AbsoluteValue { + type Epsilon = f64; + + fn default_epsilon() -> f64 { + f64::EPSILON + } + + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + match (self, other) { + (AbsoluteValue::Continuous(v1), AbsoluteValue::Continuous(v2)) => { + v1.abs_diff_eq(v2, epsilon) + } + _ => self == other, + } + } +} diff --git a/plugin-reaper-realearn/main/src/application/actions.rs b/plugin-reaper-realearn/main/src/application/actions.rs new file mode 100644 index 0000000..37274c1 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/actions.rs @@ -0,0 +1,35 @@ +use crate::base::notification; +use reaper_high::{Action, Reaper}; +use reaper_medium::SectionId; + +pub fn build_smart_command_name_from_action(action: &Action) -> Option { + match action.command_name() { + // Built-in actions don't have a command name but a persistent command ID. + // Use command ID as string. + None => action.command_id().ok().map(|id| id.to_string()), + // ReaScripts and custom actions have a command name as persistent identifier. + Some(name) => Some(name.into_string()), + } +} + +pub fn build_action_from_smart_command_name( + section_id: SectionId, + smart_command_name: &str, +) -> Option { + match smart_command_name.parse::() { + // Could parse this as command ID integer. This is a built-in action. + Ok(command_id_int) => match command_id_int.try_into() { + Ok(command_id) => Some( + Reaper::get() + .section_by_id(section_id) + .action_by_command_id(command_id), + ), + Err(_) => { + notification::warn(format!("Invalid command ID {command_id_int}")); + None + } + }, + // Couldn't parse this as integer. This is a ReaScript or custom action. + Err(_) => Some(Reaper::get().action_by_command_name(smart_command_name)), + } +} diff --git a/plugin-reaper-realearn/main/src/application/activation_condition_model.rs b/plugin-reaper-realearn/main/src/application/activation_condition_model.rs new file mode 100644 index 0000000..121efdf --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/activation_condition_model.rs @@ -0,0 +1,148 @@ +use crate::application::{ + ActivationType, Affected, BankConditionModel, Change, GetProcessingRelevance, + ModifierConditionModel, ProcessingRelevance, +}; +use crate::domain::{ + ActivationCondition, EelCondition, ExpressionCondition, ExpressionEvaluator, MappingId, +}; + +#[allow(clippy::enum_variant_names)] +pub enum ActivationConditionCommand { + SetActivationType(ActivationType), + SetModifierCondition1(ModifierConditionModel), + SetModifierCondition2(ModifierConditionModel), + SetBankCondition(BankConditionModel), + SetScript(String), + SetMappingId(Option), +} + +#[derive(Eq, PartialEq)] +pub enum ActivationConditionProp { + ActivationType, + ModifierCondition1, + ModifierCondition2, + BankCondition, + Script, + MappingId, +} + +impl GetProcessingRelevance for ActivationConditionProp { + fn processing_relevance(&self) -> Option { + Some(ProcessingRelevance::ProcessingRelevant) + } +} + +#[derive(Clone, Debug, Default)] +pub struct ActivationConditionModel { + activation_type: ActivationType, + modifier_condition_1: ModifierConditionModel, + modifier_condition_2: ModifierConditionModel, + bank_condition: BankConditionModel, + script: String, + mapping_id: Option, +} + +impl Change<'_> for ActivationConditionModel { + type Command = ActivationConditionCommand; + type Prop = ActivationConditionProp; + + fn change( + &mut self, + cmd: ActivationConditionCommand, + ) -> Option> { + use ActivationConditionCommand as C; + use ActivationConditionProp as P; + use Affected::*; + let affected = match cmd { + C::SetActivationType(v) => { + self.activation_type = v; + One(P::ActivationType) + } + C::SetModifierCondition1(v) => { + self.modifier_condition_1 = v; + One(P::ModifierCondition1) + } + C::SetModifierCondition2(v) => { + self.modifier_condition_2 = v; + One(P::ModifierCondition2) + } + C::SetBankCondition(v) => { + self.bank_condition = v; + One(P::BankCondition) + } + C::SetScript(v) => { + self.script = v; + One(P::Script) + } + C::SetMappingId(v) => { + self.mapping_id = v; + One(P::MappingId) + } + }; + Some(affected) + } +} + +impl ActivationConditionModel { + pub fn activation_type(&self) -> ActivationType { + self.activation_type + } + + pub fn modifier_condition_1(&self) -> ModifierConditionModel { + self.modifier_condition_1 + } + + pub fn modifier_condition_2(&self) -> ModifierConditionModel { + self.modifier_condition_2 + } + + pub fn bank_condition(&self) -> BankConditionModel { + self.bank_condition + } + + pub fn script(&self) -> &str { + &self.script + } + + pub fn mapping_id(&self) -> Option { + self.mapping_id + } + + pub fn create_activation_condition(&self) -> ActivationCondition { + use ActivationType::*; + match self.activation_type() { + Always => ActivationCondition::Always, + Modifiers => { + let conditions = self + .modifier_conditions() + .filter_map(|m| m.create_modifier_condition()) + .collect(); + ActivationCondition::Modifiers(conditions) + } + Bank => ActivationCondition::Program { + param_index: self.bank_condition().param_index(), + program_index: self.bank_condition().bank_index(), + }, + Eel => match EelCondition::compile(self.script()) { + Ok(c) => ActivationCondition::Eel(Box::new(c)), + Err(_) => ActivationCondition::Always, + }, + Expression => match ExpressionCondition::compile(self.script()) { + Ok(e) => ActivationCondition::Expression(Box::new(e)), + Err(_) => ActivationCondition::Always, + }, + TargetValue => match ExpressionEvaluator::compile(self.script()) { + Ok(e) => ActivationCondition::TargetValue { + lead_mapping: self.mapping_id, + condition: Box::new(e), + }, + Err(_) => ActivationCondition::Always, + }, + } + } + + fn modifier_conditions(&self) -> impl Iterator { + use std::iter::once; + once(self.modifier_condition_1()).chain(once(self.modifier_condition_2())) + } +} diff --git a/plugin-reaper-realearn/main/src/application/auto_units.rs b/plugin-reaper-realearn/main/src/application/auto_units.rs new file mode 100644 index 0000000..19f95f2 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/auto_units.rs @@ -0,0 +1,61 @@ +use crate::domain::{ControlInput, DeviceControlInput, DeviceFeedbackOutput, FeedbackOutput}; +use strum::EnumIs; + +/// Data about an automatically loaded unit. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AutoUnitData { + pub controller_id: String, + pub controller_palette_color: Option, + pub input: Option, + pub output: Option, + pub controller_preset_usage: Option, + pub main_preset_id: String, +} +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ControllerPresetUsage { + pub controller_preset_id: String, + /// `None` means that the default controller preset has been taken as a last resort even + /// though it couldn't be verified that it's suitable. + pub main_preset_suitability: Option, + /// `None` means that the default controller preset has been taken as a last resort even + /// though it couldn't be verified that it's suitable. + pub controller_suitability: Option, +} + +/// Suitability of a controller preset for a main preset. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] +pub struct MainPresetSuitability(u8); + +impl MainPresetSuitability { + pub fn new(raw: u8) -> Self { + Self(raw) + } + + pub fn get(&self) -> u8 { + self.0 + } + + pub fn is_generally_suitable(&self) -> bool { + self.0 > 0 + } +} + +/// Suitability of a controller preset for a connected controller. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, EnumIs)] +pub enum ControllerSuitability { + NotSuitable = 0, + MaybeSuitable = 1, + Suitable = 2, +} + +impl AutoUnitData { + pub fn control_input(&self) -> ControlInput { + self.input + .map(ControlInput::from_device_input) + .unwrap_or_default() + } + + pub fn feedback_output(&self) -> Option { + self.output.map(FeedbackOutput::from_device_output) + } +} diff --git a/plugin-reaper-realearn/main/src/application/compartment_model.rs b/plugin-reaper-realearn/main/src/application/compartment_model.rs new file mode 100644 index 0000000..84d7b3e --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/compartment_model.rs @@ -0,0 +1,29 @@ +use crate::application::{ + Affected, GroupModel, GroupProp, MappingCommand, MappingModel, MappingProp, +}; +use crate::domain::{CompartmentParamIndex, GroupId, MappingId, ParamSetting}; +use base::hash_util::NonCryptoHashMap; + +#[derive(Clone, Debug)] +pub struct CompartmentModel { + pub parameters: Vec<(CompartmentParamIndex, ParamSetting)>, + pub default_group: GroupModel, + pub groups: Vec, + pub mappings: Vec, + pub common_lua: String, + pub custom_data: NonCryptoHashMap, + pub notes: String, +} + +pub enum CompartmentCommand { + SetNotes(String), + SetCommonLua(String), + ChangeMapping(MappingId, Box), +} + +pub enum CompartmentProp { + Notes, + CommonLua, + InGroup(GroupId, Affected), + InMapping(MappingId, Affected), +} diff --git a/plugin-reaper-realearn/main/src/application/compartment_preset.rs b/plugin-reaper-realearn/main/src/application/compartment_preset.rs new file mode 100644 index 0000000..8e0117c --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/compartment_preset.rs @@ -0,0 +1,61 @@ +use crate::application::CompartmentModel; +use crate::domain::CompartmentKind; +use std::fmt; + +pub trait CompartmentPresetManager: fmt::Debug { + fn find_by_id(&self, id: &str) -> Option; +} + +#[derive(Clone, Debug)] +pub struct CompartmentPresetModel { + id: String, + name: String, + compartment: CompartmentKind, + model: CompartmentModel, +} + +impl CompartmentPresetModel { + pub fn new( + id: String, + name: String, + compartment: CompartmentKind, + model: CompartmentModel, + ) -> CompartmentPresetModel { + CompartmentPresetModel { + id, + name, + compartment, + model, + } + } + + pub fn id(&self) -> &str { + &self.id + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn compartment(&self) -> CompartmentKind { + self.compartment + } + + pub fn model(&self) -> &CompartmentModel { + &self.model + } + + pub fn set_model(&mut self, data: CompartmentModel) { + self.model = data; + } + + pub fn patch_custom_data(&mut self, key: String, value: serde_json::Value) { + self.model.custom_data.insert(key, value); + } +} + +impl fmt::Display for CompartmentPresetModel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } +} diff --git a/plugin-reaper-realearn/main/src/application/conditional_activation_model.rs b/plugin-reaper-realearn/main/src/application/conditional_activation_model.rs new file mode 100644 index 0000000..5fd9e3f --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/conditional_activation_model.rs @@ -0,0 +1,111 @@ +use crate::domain::{CompartmentParamIndex, ModifierCondition}; +use derive_more::Display; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use serde::{Deserialize, Serialize}; +use strum::EnumIter; + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum ActivationType { + #[default] + #[serde(rename = "always")] + #[display(fmt = "Always")] + Always, + #[serde(rename = "modifiers")] + #[display(fmt = "When modifiers on/off")] + Modifiers, + #[serde(rename = "program")] + #[display(fmt = "When bank selected")] + Bank, + #[serde(rename = "eel")] + #[display(fmt = "When EEL met")] + Eel, + #[serde(rename = "expression")] + #[display(fmt = "When expression met")] + Expression, + #[serde(rename = "target-value")] + #[display(fmt = "When target value met")] + TargetValue, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Default)] +pub struct ModifierConditionModel { + #[serde(rename = "paramIndex")] + pub param_index: Option, + #[serde(rename = "isOn")] + pub is_on: bool, +} + +impl ModifierConditionModel { + pub fn create_modifier_condition(&self) -> Option { + self.param_index + .map(|i| ModifierCondition::new(i, self.is_on)) + } + + pub fn param_index(&self) -> Option { + self.param_index + } + + pub fn with_param_index( + &self, + param_index: Option, + ) -> ModifierConditionModel { + ModifierConditionModel { + param_index, + ..*self + } + } + + pub fn is_on(&self) -> bool { + self.is_on + } + + pub fn with_is_on(&self, is_on: bool) -> ModifierConditionModel { + ModifierConditionModel { is_on, ..*self } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Default)] +pub struct BankConditionModel { + #[serde(rename = "paramIndex")] + pub param_index: CompartmentParamIndex, + #[serde(rename = "programIndex")] + pub bank_index: u32, +} + +impl BankConditionModel { + pub fn param_index(&self) -> CompartmentParamIndex { + self.param_index + } + + pub fn with_param_index(&self, param_index: CompartmentParamIndex) -> BankConditionModel { + BankConditionModel { + param_index, + ..*self + } + } + + pub fn bank_index(&self) -> u32 { + self.bank_index + } + + pub fn with_bank_index(&self, bank_index: u32) -> BankConditionModel { + BankConditionModel { + bank_index, + ..*self + } + } +} diff --git a/plugin-reaper-realearn/main/src/application/group_model.rs b/plugin-reaper-realearn/main/src/application/group_model.rs new file mode 100644 index 0000000..b629141 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/group_model.rs @@ -0,0 +1,200 @@ +use crate::application::{ + ActivationConditionCommand, ActivationConditionModel, ActivationConditionProp, Affected, + Change, GetProcessingRelevance, GroupData, ProcessingRelevance, +}; +use crate::domain::{CompartmentKind, GroupId, GroupKey, Tag}; +use core::fmt; +use std::cell::RefCell; +use std::rc::{Rc, Weak}; + +pub enum GroupCommand { + SetName(String), + SetTags(Vec), + SetControlIsEnabled(bool), + SetFeedbackIsEnabled(bool), + ChangeActivationCondition(ActivationConditionCommand), +} + +pub enum GroupProp { + Name, + Tags, + ControlIsEnabled, + FeedbackIsEnabled, + InActivationCondition(Affected), +} + +impl GetProcessingRelevance for GroupProp { + fn processing_relevance(&self) -> Option { + use GroupProp as P; + match self { + P::Tags | P::ControlIsEnabled | P::FeedbackIsEnabled => { + Some(ProcessingRelevance::ProcessingRelevant) + } + P::InActivationCondition(p) => p.processing_relevance(), + P::Name => None, + } + } +} + +/// A mapping group. +#[derive(Clone, Debug)] +pub struct GroupModel { + compartment: CompartmentKind, + id: GroupId, + key: GroupKey, + name: String, + tags: Vec, + control_is_enabled: bool, + feedback_is_enabled: bool, + pub activation_condition_model: ActivationConditionModel, +} + +impl Change<'_> for GroupModel { + type Command = GroupCommand; + type Prop = GroupProp; + + fn change(&mut self, cmd: GroupCommand) -> Option> { + use Affected::*; + use GroupCommand as C; + use GroupProp as P; + let affected = match cmd { + C::SetName(v) => { + self.name = v; + One(P::Name) + } + C::SetTags(v) => { + self.tags = v; + One(P::Tags) + } + C::SetControlIsEnabled(v) => { + self.control_is_enabled = v; + One(P::ControlIsEnabled) + } + C::SetFeedbackIsEnabled(v) => { + self.feedback_is_enabled = v; + One(P::FeedbackIsEnabled) + } + C::ChangeActivationCondition(cmd) => { + return self + .activation_condition_model + .change(cmd) + .map(|affected| One(P::InActivationCondition(affected))); + } + }; + Some(affected) + } +} + +impl GroupModel { + pub fn effective_name(&self) -> &str { + if self.is_default_group() { + "" + } else { + self.name() + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn tags(&self) -> &[Tag] { + &self.tags + } + + pub fn control_is_enabled(&self) -> bool { + self.control_is_enabled + } + + pub fn feedback_is_enabled(&self) -> bool { + self.feedback_is_enabled + } + + pub fn activation_condition_model(&self) -> &ActivationConditionModel { + &self.activation_condition_model + } +} + +impl fmt::Display for GroupModel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.effective_name()) + } +} + +/// See MappingModel for explanation. +impl PartialEq for GroupModel { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self as _, other as _) + } +} + +pub type SharedGroup = Rc>; +pub type WeakGroup = Weak>; + +pub fn share_group(group: GroupModel) -> SharedGroup { + Rc::new(RefCell::new(group)) +} + +impl GroupModel { + pub fn new_from_ui(compartment: CompartmentKind, name: String) -> Self { + Self::new_internal(compartment, GroupId::random(), GroupKey::random(), name) + } + + pub fn new_from_data(compartment: CompartmentKind, id: GroupId, key: GroupKey) -> Self { + Self::new_internal(compartment, id, key, "".to_string()) + } + + pub fn default_for_compartment(compartment: CompartmentKind) -> Self { + Self { + compartment, + id: GroupId::default(), + key: GroupKey::default(), + name: Default::default(), + tags: Default::default(), + control_is_enabled: true, + feedback_is_enabled: true, + activation_condition_model: ActivationConditionModel::default(), + } + } + + fn new_internal( + compartment: CompartmentKind, + id: GroupId, + key: GroupKey, + name: String, + ) -> Self { + Self { + id, + key, + name, + ..Self::default_for_compartment(compartment) + } + } + + pub fn compartment(&self) -> CompartmentKind { + self.compartment + } + + pub fn id(&self) -> GroupId { + self.id + } + + pub fn key(&self) -> &GroupKey { + &self.key + } + + pub fn is_default_group(&self) -> bool { + self.id.is_default() + } + + pub fn create_data(&self) -> GroupData { + GroupData { + control_is_enabled: self.control_is_enabled(), + feedback_is_enabled: self.feedback_is_enabled(), + activation_condition: self + .activation_condition_model + .create_activation_condition(), + tags: self.tags.clone(), + } + } +} diff --git a/plugin-reaper-realearn/main/src/application/instance_model.rs b/plugin-reaper-realearn/main/src/application/instance_model.rs new file mode 100644 index 0000000..7e5f449 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/instance_model.rs @@ -0,0 +1,85 @@ +use crate::application::{Affected, ChangeResult}; +use crate::domain::{SharedInstance, Tag}; +use anyhow::Context; +use base::spawn_in_main_thread; +use derivative::Derivative; +use std::cell::RefCell; +use std::rc::{Rc, Weak}; + +pub type SharedInstanceModel = Rc>; +pub type WeakInstanceModel = Weak>; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct InstanceModel { + tags: Vec, + instance: SharedInstance, + #[derivative(Debug = "ignore")] + ui: Box, +} + +impl InstanceModel { + pub fn new(instance: SharedInstance, ui: Box) -> Self { + Self { + tags: Default::default(), + instance, + ui, + } + } + + pub fn instance(&self) -> &SharedInstance { + &self.instance + } + + /// Returns all instance tags. + pub fn tags(&self) -> &[Tag] { + &self.tags + } + + /// Modifies this instance by executing an instance command and returns the affected properties. + /// + /// Doesn't invoke listeners. + pub fn change(&mut self, cmd: InstanceCommand) -> ChangeResult { + let affected = match cmd { + InstanceCommand::SetTags(tags) => { + self.tags = tags; + Some(Affected::One(InstanceProp::Tags)) + } + }; + Ok(affected) + } + + /// Modifies this instance by executing an instance command and returns the affected properties. + /// + /// Invokes listeners. + pub fn change_with_notification( + &mut self, + cmd: InstanceCommand, + initiator: Option, + weak_model: WeakInstanceModel, + ) { + if let Ok(Some(affected)) = self.change(cmd) { + spawn_in_main_thread(async move { + let model = weak_model.upgrade().context("upgrading model")?; + model.borrow().ui.handle_affected(affected, initiator)?; + Ok(()) + }); + } + } +} + +pub trait InstanceUi { + fn handle_affected( + &self, + affected: Affected, + initiator: Option, + ) -> anyhow::Result<()>; +} + +pub enum InstanceCommand { + SetTags(Vec), +} + +pub enum InstanceProp { + Tags, +} diff --git a/plugin-reaper-realearn/main/src/application/mapping_extension_model.rs b/plugin-reaper-realearn/main/src/application/mapping_extension_model.rs new file mode 100644 index 0000000..104b1c0 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/mapping_extension_model.rs @@ -0,0 +1,98 @@ +use crate::domain::{ + parse_hex_string, DisplayAsPrettyHex, LifecycleMidiData, LifecycleMidiMessage, MappingExtension, +}; + +use helgoboss_learn::RawMidiEvent; +use serde::{Deserialize, Serialize}; +use serde_with::SerializeDisplay; +use std::convert::TryFrom; +use std::fmt::{Display, Formatter}; + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct MappingExtensionModel { + pub on_activate: LifecycleModel, + pub on_deactivate: LifecycleModel, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct LifecycleModel { + pub send_midi_feedback: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum LifecycleMidiMessageModel { + Raw(RawMidiMessage), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum RawMidiMessage { + HexString(RawHexStringMidiMessage), + ByteArray(RawByteArrayMidiMessage), +} + +impl RawMidiMessage { + fn bytes(&self) -> &[u8] { + use RawMidiMessage::*; + match self { + HexString(msg) => &msg.0, + ByteArray(msg) => &msg.0, + } + } +} + +#[derive(Clone, Debug, SerializeDisplay, Deserialize)] +#[serde(try_from = "String")] +pub struct RawHexStringMidiMessage(pub Vec); + +impl Display for RawHexStringMidiMessage { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + DisplayAsPrettyHex(&self.0).fmt(f) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RawByteArrayMidiMessage(pub Vec); + +impl TryFrom for RawHexStringMidiMessage { + type Error = hex::FromHexError; + + fn try_from(value: String) -> Result { + let vec = parse_hex_string(&value)?; + Ok(Self(vec)) + } +} + +impl LifecycleMidiMessageModel { + pub fn create_lifecycle_midi_message(&self) -> Result { + use LifecycleMidiMessageModel::*; + let message = match self { + Raw(msg) => { + let event = RawMidiEvent::try_from_slice(0, msg.bytes())?; + LifecycleMidiMessage::Raw(Box::new(event)) + } + }; + Ok(message) + } +} + +impl MappingExtensionModel { + pub fn create_mapping_extension(&self) -> Result { + fn convert_messages( + model: &[LifecycleMidiMessageModel], + ) -> Result, &'static str> { + model + .iter() + .map(|m| m.create_lifecycle_midi_message()) + .collect() + } + let ext = MappingExtension::new(LifecycleMidiData { + activation_midi_messages: convert_messages(&self.on_activate.send_midi_feedback)?, + deactivation_midi_messages: convert_messages(&self.on_deactivate.send_midi_feedback)?, + }); + Ok(ext) + } +} diff --git a/plugin-reaper-realearn/main/src/application/mapping_model.rs b/plugin-reaper-realearn/main/src/application/mapping_model.rs new file mode 100644 index 0000000..825e364 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/mapping_model.rs @@ -0,0 +1,790 @@ +use crate::application::{ + merge_affected, ActivationConditionCommand, ActivationConditionModel, ActivationConditionProp, + Affected, Change, ChangeResult, GetProcessingRelevance, MakeFxNonStickyMode, + MakeTrackNonStickyMode, MappingExtensionModel, ModeCommand, ModeModel, ModeProp, + ProcessingRelevance, SourceCommand, SourceModel, SourceProp, TargetCategory, TargetCommand, + TargetModel, TargetModelFormatVeryShort, TargetModelWithContext, TargetProp, +}; +use crate::domain::{ + ActivationCondition, CompartmentKind, CompoundMappingSource, CompoundMappingTarget, + EelTransformation, ExtendedProcessorContext, ExtendedSourceCharacter, FeedbackSendBehavior, + GroupId, MainMapping, MappingId, MappingKey, Mode, PersistentMappingProcessingState, + ProcessorMappingOptions, QualifiedMappingId, RealearnTarget, ReaperTarget, Script, Tag, + TargetCharacter, UnresolvedCompoundMappingTarget, VirtualFx, VirtualTrack, +}; +use helgoboss_learn::{ + AbsoluteMode, ControlType, DetailedSourceCharacter, DiscreteIncrement, Interval, + ModeApplicabilityCheckInput, ModeParameter, SourceCharacter, Target, UnitValue, +}; + +use reaper_high::{Fx, Track}; +use std::cell::RefCell; +use std::error::Error; +use std::rc::Rc; + +pub enum MappingCommand { + SetName(String), + SetTags(Vec), + SetGroupId(GroupId), + SetIsEnabled(bool), + SetControlIsEnabled(bool), + SetFeedbackIsEnabled(bool), + SetFeedbackSendBehavior(FeedbackSendBehavior), + SetVisibleInProjection(bool), + SetBeepOnSuccess(bool), + ChangeActivationCondition(ActivationConditionCommand), + ChangeSource(SourceCommand), + ChangeMode(ModeCommand), + ChangeTarget(TargetCommand), +} + +#[derive(Eq, PartialEq)] +pub enum MappingProp { + Name, + Tags, + GroupId, + IsEnabled, + ControlIsEnabled, + FeedbackIsEnabled, + FeedbackSendBehavior, + VisibleInProjection, + BeepOnSuccess, + AdvancedSettings, + InActivationCondition(Affected), + InSource(Affected), + InMode(Affected), + InTarget(Affected), +} + +impl GetProcessingRelevance for MappingProp { + fn processing_relevance(&self) -> Option { + use MappingProp as P; + match self { + P::Name + | P::Tags + | P::ControlIsEnabled + | P::FeedbackIsEnabled + | P::FeedbackSendBehavior + | P::VisibleInProjection + | P::AdvancedSettings + | P::BeepOnSuccess => Some(ProcessingRelevance::ProcessingRelevant), + P::InActivationCondition(p) => p.processing_relevance(), + P::InMode(p) => p.processing_relevance(), + P::InSource(p) => p.processing_relevance(), + P::InTarget(p) => p.processing_relevance(), + P::IsEnabled => Some(ProcessingRelevance::PersistentProcessingRelevant), + MappingProp::GroupId => { + // This is handled in different ways. + None + } + } + } +} + +/// A model for creating mappings (a combination of source, mode and target). +#[derive(Clone, Debug)] +pub struct MappingModel { + id: MappingId, + key: MappingKey, + compartment: CompartmentKind, + name: String, + tags: Vec, + group_id: GroupId, + is_enabled: bool, + control_is_enabled: bool, + feedback_is_enabled: bool, + feedback_send_behavior: FeedbackSendBehavior, + pub activation_condition_model: ActivationConditionModel, + visible_in_projection: bool, + beep_on_success: bool, + pub source_model: SourceModel, + pub mode_model: ModeModel, + pub target_model: TargetModel, + advanced_settings: Option, + extension_model: MappingExtensionModel, +} + +pub type SharedMapping = Rc>; + +pub fn share_mapping(mapping: MappingModel) -> SharedMapping { + Rc::new(RefCell::new(mapping)) +} + +// We design mapping models as entity (in the DDD sense), so we compare them by ID, not by value. +// Because we store everything in memory instead of working with a database, the memory +// address serves us as ID. That means we just compare pointers. +// +// In all functions which don't need access to the mapping's internal state (comparisons, hashing +// etc.) we use `*const MappingModel` as parameter type because this saves the consumer from +// having to borrow the mapping (when kept in a RefCell). Whenever we can we should compare pointers +// directly, in order to prevent borrowing just to make the following comparison (the RefCell +// comparison internally calls `borrow()`!). +impl PartialEq for MappingModel { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self as _, other as _) + } +} + +impl Change<'_> for MappingModel { + type Command = MappingCommand; + type Prop = MappingProp; + + fn change(&mut self, cmd: MappingCommand) -> Option> { + use Affected::*; + use MappingCommand as C; + use MappingProp as P; + let affected = match cmd { + C::SetName(v) => { + self.name = v; + One(P::Name) + } + C::SetTags(v) => { + self.tags = v; + One(P::Tags) + } + C::SetGroupId(v) => { + self.group_id = v; + One(P::GroupId) + } + C::SetIsEnabled(v) => { + self.is_enabled = v; + One(P::IsEnabled) + } + C::SetControlIsEnabled(v) => { + self.control_is_enabled = v; + One(P::ControlIsEnabled) + } + C::SetFeedbackIsEnabled(v) => { + self.feedback_is_enabled = v; + One(P::FeedbackIsEnabled) + } + C::SetFeedbackSendBehavior(v) => { + self.feedback_send_behavior = v; + One(P::FeedbackSendBehavior) + } + C::SetVisibleInProjection(v) => { + self.visible_in_projection = v; + One(P::VisibleInProjection) + } + C::SetBeepOnSuccess(v) => { + self.beep_on_success = v; + One(P::BeepOnSuccess) + } + C::ChangeActivationCondition(cmd) => { + return self + .activation_condition_model + .change(cmd) + .map(|affected| One(P::InActivationCondition(affected))); + } + C::ChangeSource(cmd) => { + return self + .source_model + .change(cmd) + .map(|affected| One(P::InSource(affected))); + } + C::ChangeMode(cmd) => { + return self + .mode_model + .change(cmd) + .map(|affected| One(P::InMode(affected))); + } + C::ChangeTarget(cmd) => { + return self + .target_model + .change(cmd) + .map(|affected| One(P::InTarget(affected))); + } + }; + Some(affected) + } +} + +impl MappingModel { + pub fn new( + compartment: CompartmentKind, + initial_group_id: GroupId, + key: MappingKey, + id: MappingId, + ) -> Self { + Self { + id, + key, + compartment, + name: Default::default(), + tags: Default::default(), + group_id: initial_group_id, + is_enabled: true, + control_is_enabled: true, + feedback_is_enabled: true, + feedback_send_behavior: Default::default(), + activation_condition_model: Default::default(), + visible_in_projection: true, + beep_on_success: false, + source_model: SourceModel::new(), + mode_model: Default::default(), + target_model: TargetModel::default_for_compartment(compartment), + advanced_settings: None, + extension_model: Default::default(), + } + } + + pub fn id(&self) -> MappingId { + self.id + } + + pub fn key(&self) -> &MappingKey { + &self.key + } + + pub fn group_id(&self) -> GroupId { + self.group_id + } + + pub fn is_enabled(&self) -> bool { + self.is_enabled + } + + pub fn control_is_enabled(&self) -> bool { + self.control_is_enabled + } + + pub fn feedback_is_enabled(&self) -> bool { + self.feedback_is_enabled + } + + pub fn feedback_send_behavior(&self) -> FeedbackSendBehavior { + self.feedback_send_behavior + } + + pub fn visible_in_projection(&self) -> bool { + self.visible_in_projection + } + + pub fn beep_on_success(&self) -> bool { + self.beep_on_success + } + + pub fn activation_condition_model(&self) -> &ActivationConditionModel { + &self.activation_condition_model + } + + pub fn reset_key(&mut self) { + self.key = MappingKey::random(); + } + + pub fn qualified_id(&self) -> QualifiedMappingId { + QualifiedMappingId::new(self.compartment, self.id) + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn tags(&self) -> &[Tag] { + &self.tags + } + + pub fn effective_name(&self) -> String { + if self.name.is_empty() { + TargetModelFormatVeryShort(&self.target_model).to_string() + } else { + self.name.clone() + } + } + + pub fn make_target_non_sticky( + &mut self, + context: ExtendedProcessorContext, + track_mode: MakeTrackNonStickyMode, + fx_mode: MakeFxNonStickyMode, + ) -> Option> { + self.make_target_non_sticky_internal( + context, + |t| track_mode.build_virtual_track(t.as_ref()), + |fx| fx_mode.build_virtual_fx(fx.as_ref()), + ) + } + + #[must_use] + fn make_target_non_sticky_internal( + &mut self, + context: ExtendedProcessorContext, + create_virtual_track: impl FnOnce(Option) -> Option, + create_virtual_fx: impl FnOnce(Option) -> Option, + ) -> Option> { + let compartment = self.compartment(); + let target = &mut self.target_model; + match target.category() { + TargetCategory::Reaper => { + // Change FX + if target.supports_fx() { + let target_with_context = target.with_context(context, compartment); + let containing_fx = context.context().containing_fx(); + let resolved_fx = target_with_context.first_fx().ok(); + let new_virtual_fx = if resolved_fx.as_ref() == Some(containing_fx) { + // This is ourselves! + Some(VirtualFx::This) + } else { + create_virtual_fx(resolved_fx) + }; + if let Some(fx) = new_virtual_fx { + let _ = target.set_virtual_fx(fx, context, compartment); + } + } + // Change track + if target.target_type().supports_track() { + let new_virtual_track = if target.fx_type().requires_fx_chain() { + let resolved_track = target + .with_context(context, compartment) + .first_effective_track() + .ok(); + create_virtual_track(resolved_track) + } else { + // Track doesn't matter at all. We change it to . Looks nice. + Some(VirtualTrack::This) + }; + if let Some(t) = new_virtual_track { + let _ = target.set_virtual_track(t, Some(context.context())); + } + } + Some(Affected::Multiple) + } + TargetCategory::Virtual => None, + } + } + + pub fn make_target_sticky( + &mut self, + context: ExtendedProcessorContext, + ) -> Result>, Box> { + let target = &mut self.target_model; + match target.category() { + TargetCategory::Reaper => { + if target.supports_track() { + target.make_track_sticky(self.compartment, context)?; + } + if target.supports_fx() { + target.make_fx_sticky(self.compartment, context)?; + } + if target.supports_route() { + target.make_route_sticky(self.compartment, context)?; + } + } + TargetCategory::Virtual => {} + } + Ok(Some(Affected::Multiple)) + } + + pub fn advanced_settings(&self) -> Option<&serde_yaml::Mapping> { + self.advanced_settings.as_ref() + } + + fn update_extension_model_from_advanced_settings(&mut self) -> Result<(), String> { + // Immediately update extension model + let extension_model = if let Some(yaml_mapping) = self.advanced_settings() { + serde_yaml::from_value(serde_yaml::Value::Mapping(yaml_mapping.clone())) + .map_err(|e| e.to_string())? + } else { + Default::default() + }; + self.extension_model = extension_model; + Ok(()) + } + + pub fn duplicate(&self) -> MappingModel { + MappingModel { + id: MappingId::random(), + key: MappingKey::random(), + ..self.clone() + } + } + + pub fn compartment(&self) -> CompartmentKind { + self.compartment + } + + pub fn with_context<'a>( + &'a self, + context: ExtendedProcessorContext<'a>, + ) -> MappingModelWithContext<'a> { + MappingModelWithContext { + mapping: self, + context, + } + } + + #[must_use] + pub fn adjust_mode_if_necessary( + &mut self, + context: ExtendedProcessorContext, + ) -> Option> { + let with_context = self.with_context(context); + if with_context.absolute_mode_makes_sense() == Ok(false) { + if let Ok(preferred_mode_type) = with_context.preferred_mode_type() { + self.mode_model + .change(ModeCommand::SetAbsoluteMode(preferred_mode_type)); + self.set_preferred_mode_values(context) + } else { + None + } + } else { + None + } + } + + #[must_use] + pub fn reset_mode( + &mut self, + context: ExtendedProcessorContext, + ) -> Option> { + self.mode_model.change(ModeCommand::ResetWithinType); + let _ = self.set_preferred_mode_values(context); + Some(Affected::Multiple) + } + + pub fn set_advanced_settings( + &mut self, + yaml: Option, + ) -> ChangeResult { + self.advanced_settings = yaml; + self.update_extension_model_from_advanced_settings()?; + Ok(Some(Affected::One(MappingProp::AdvancedSettings))) + } + + #[must_use] + pub fn set_absolute_mode_and_preferred_values( + &mut self, + context: ExtendedProcessorContext, + mode: AbsoluteMode, + ) -> Option> { + let affected_1 = self.change(MappingCommand::ChangeMode(ModeCommand::SetAbsoluteMode( + mode, + ))); + let affected_2 = self.set_preferred_mode_values(context); + merge_affected(affected_1, affected_2) + } + + // Changes mode settings if there are some preferred ones for a certain source or target. + #[must_use] + fn set_preferred_mode_values( + &mut self, + context: ExtendedProcessorContext, + ) -> Option> { + let affected_1 = self + .mode_model + .change(ModeCommand::SetStepSizeInterval( + self.with_context(context).preferred_step_size_interval(), + )) + .map(|affected| Affected::One(MappingProp::InMode(affected))); + let affected_2 = self + .mode_model + .change(ModeCommand::SetStepFactorInterval( + self.with_context(context).preferred_step_factor_interval(), + )) + .map(|affected| Affected::One(MappingProp::InMode(affected))); + merge_affected(affected_1, affected_2) + } + + pub fn base_mode_applicability_check_input(&self) -> ModeApplicabilityCheckInput { + let transformation = + EelTransformation::compile_for_control(self.mode_model.eel_control_transformation()); + ModeApplicabilityCheckInput { + target_is_virtual: self.target_model.is_virtual(), + // TODO-high-discrete Enable (also taking source into consideration!) + target_supports_discrete_values: false, + control_transformation_uses_time: transformation + .as_ref() + .map(|t| t.uses_time()) + .unwrap_or(false), + control_transformation_produces_relative_values: transformation + .as_ref() + .map(|t| t.produces_relative_values()) + .unwrap_or(false), + is_feedback: false, + make_absolute: self.mode_model.make_absolute(), + use_textual_feedback: self.mode_model.feedback_type().is_textual(), + // Any is okay, will be overwritten. + source_character: DetailedSourceCharacter::RangeControl, + absolute_mode: self.mode_model.absolute_mode(), + fire_mode: self.mode_model.fire_mode(), + target_value_sequence_is_set: !self.mode_model.target_value_sequence().is_empty(), + } + } + + pub fn control_is_enabled_and_supported(&self) -> bool { + self.control_is_enabled() + && self.source_model.supports_control() + && self.target_model.supports_control() + } + + pub fn feedback_is_enabled_and_supported(&self) -> bool { + self.feedback_is_enabled() + && self.source_model.supports_feedback() + && self.target_model.supports_feedback() + } + + pub fn mode_parameter_is_relevant( + &self, + mode_parameter: ModeParameter, + base_input: ModeApplicabilityCheckInput, + possible_source_characters: &[DetailedSourceCharacter], + ) -> bool { + self.mode_model.mode_parameter_is_relevant( + mode_parameter, + base_input, + possible_source_characters, + self.control_is_enabled_and_supported(), + self.feedback_is_enabled_and_supported(), + ) + } + + fn create_source(&self) -> CompoundMappingSource { + self.source_model.create_source() + } + + fn create_mode(&self) -> Mode { + let possible_source_characters = self.source_model.possible_detailed_characters(); + self.mode_model.create_mode( + self.base_mode_applicability_check_input(), + &possible_source_characters, + ) + } + + fn create_target(&self) -> Option { + self.target_model.create_target(self.compartment).ok() + } + + pub fn create_persistent_mapping_processing_state(&self) -> PersistentMappingProcessingState { + PersistentMappingProcessingState { + is_enabled: self.is_enabled(), + } + } + + pub fn get_simple_mapping(&self) -> Option { + let target = self.target_model.simple_target()?; + let source = self.source_model.simple_source()?; + let mapping = playtime_api::runtime::SimpleMapping { source, target }; + Some(mapping) + } + + /// Creates an intermediate mapping for splintering into very dedicated mapping types that are + /// then going to be distributed to real-time and main processor. + pub fn create_main_mapping(&self, group_data: GroupData) -> MainMapping { + let id = self.id; + let source = self.create_source(); + let mode = self.create_mode(); + let unresolved_target = self.create_target(); + let activation_condition = self + .activation_condition_model + .create_activation_condition(); + let options = ProcessorMappingOptions { + // TODO-medium Encapsulate, don't set here + target_is_active: false, + persistent_processing_state: self.create_persistent_mapping_processing_state(), + control_is_enabled: group_data.control_is_enabled && self.control_is_enabled(), + feedback_is_enabled: group_data.feedback_is_enabled && self.feedback_is_enabled(), + feedback_send_behavior: self.feedback_send_behavior(), + beep_on_success: self.beep_on_success, + }; + let mut merged_tags = group_data.tags; + merged_tags.extend_from_slice(&self.tags); + MainMapping::new( + self.compartment, + id, + &self.key, + self.group_id(), + self.name.clone(), + merged_tags, + source, + mode, + self.mode_model.group_interaction(), + unresolved_target, + group_data.activation_condition, + activation_condition, + options, + self.extension_model + .create_mapping_extension() + .unwrap_or_default(), + ) + } +} + +pub struct GroupData { + pub control_is_enabled: bool, + pub feedback_is_enabled: bool, + pub activation_condition: ActivationCondition, + pub tags: Vec, +} + +impl Default for GroupData { + fn default() -> Self { + Self { + control_is_enabled: true, + feedback_is_enabled: true, + activation_condition: ActivationCondition::Always, + tags: vec![], + } + } +} + +pub struct MappingModelWithContext<'a> { + mapping: &'a MappingModel, + context: ExtendedProcessorContext<'a>, +} + +impl MappingModelWithContext<'_> { + /// Returns if the absolute make sense under the current conditions. + /// + /// Conditions are: + /// + /// - Source character + /// - Target character and control type + pub fn absolute_mode_makes_sense(&self) -> Result { + use ExtendedSourceCharacter::*; + use SourceCharacter::*; + let source_character = self.mapping.source_model.character(); + let absolute_mode = self.mapping.mode_model.absolute_mode(); + let makes_sense = match source_character { + Normal(RangeElement) => match absolute_mode { + AbsoluteMode::Normal + | AbsoluteMode::MakeRelative + | AbsoluteMode::PerformanceControl => true, + AbsoluteMode::IncrementalButton | AbsoluteMode::ToggleButton => false, + }, + Normal(MomentaryButton | ToggleButton) => { + let target = self.target_with_context().resolve_first()?; + let target_is_relative = target + .control_type(self.context.control_context()) + .is_relative(); + match absolute_mode { + AbsoluteMode::Normal | AbsoluteMode::ToggleButton => !target_is_relative, + AbsoluteMode::IncrementalButton => { + if target_is_relative { + true + } else { + match target.character(self.context.control_context()) { + TargetCharacter::Discrete + | TargetCharacter::Continuous + | TargetCharacter::VirtualMulti => true, + TargetCharacter::Trigger + | TargetCharacter::Switch + | TargetCharacter::VirtualButton => false, + } + } + } + AbsoluteMode::MakeRelative => { + // "Incremental button" is the correct special form of "Make relative" + // for button presses! + false + } + AbsoluteMode::PerformanceControl => false, + } + } + Normal(Encoder1) | Normal(Encoder2) | Normal(Encoder3) => { + // TODO-low No idea why this is true. But so what, auto-correct settings is not + // really a thing anymore? + true + } + VirtualContinuous => true, + }; + Ok(makes_sense) + } + + pub fn has_target(&self, target: &ReaperTarget) -> bool { + self.target_with_context() + .resolve() + .iter() + .flatten() + .any(|t| match t { + CompoundMappingTarget::Reaper(t) => &**t == target, + _ => false, + }) + } + + pub fn preferred_mode_type(&self) -> Result { + use ExtendedSourceCharacter::*; + use SourceCharacter::*; + let result = match self.mapping.source_model.character() { + Normal(RangeElement) | VirtualContinuous => AbsoluteMode::Normal, + Normal(MomentaryButton) | Normal(ToggleButton) => { + let target = self.target_with_context().resolve_first()?; + if target + .control_type(self.context.control_context()) + .is_relative() + { + AbsoluteMode::IncrementalButton + } else { + match target.character(self.context.control_context()) { + TargetCharacter::Trigger + | TargetCharacter::Continuous + | TargetCharacter::VirtualMulti => AbsoluteMode::Normal, + TargetCharacter::Switch | TargetCharacter::VirtualButton => { + AbsoluteMode::ToggleButton + } + TargetCharacter::Discrete => AbsoluteMode::IncrementalButton, + } + } + } + Normal(Encoder1) | Normal(Encoder2) | Normal(Encoder3) => AbsoluteMode::Normal, + }; + Ok(result) + } + + /// If this returns `true`, the Speed sliders will be shown, allowing relative + /// increments/decrements to be throttled or multiplied. + pub fn uses_step_factors(&self) -> bool { + let mode = self.mapping.create_mode(); + if mode.settings().make_absolute { + // If we convert increments to absolute values, we want step sizes of course. + return false; + } + if !mode.settings().target_value_sequence.is_empty() { + // If we have a target value sequence, we are discrete all the way! + return true; + } + let target = match self.target_with_context().resolve_first().ok() { + None => return false, + Some(t) => t, + }; + match target.control_type(self.context.control_context()) { + ControlType::AbsoluteContinuousRetriggerable => { + // Retriggerable targets which can't report the current value and are pure triggers. + // In #613, we introduced a convenient behavior that allows encoder movements + // trigger such targets. But we want to support throttling the encoder speed, so + // we consider this as using step counts. + !target.can_report_current_value() + } + ControlType::AbsoluteContinuous => false, + ControlType::AbsoluteContinuousRoundable { .. } => false, + ControlType::AbsoluteDiscrete { .. } => true, + ControlType::Relative => true, + ControlType::VirtualMulti => true, + ControlType::VirtualButton => false, + } + } + + fn preferred_step_size_interval(&self) -> Interval { + match self.target_step_size() { + Some(step_size) => Interval::new(step_size, step_size), + None => ModeModel::default_step_size_interval(), + } + } + + fn preferred_step_factor_interval(&self) -> Interval { + let inc = DiscreteIncrement::new(1); + Interval::new(inc, inc) + } + + fn target_step_size(&self) -> Option { + let target = self.target_with_context().resolve_first().ok()?; + target + .control_type(self.context.control_context()) + .step_size() + } + + fn target_with_context(&self) -> TargetModelWithContext<'_> { + self.mapping + .target_model + .with_context(self.context, self.mapping.compartment) + } +} diff --git a/plugin-reaper-realearn/main/src/application/mod.rs b/plugin-reaper-realearn/main/src/application/mod.rs new file mode 100644 index 0000000..b1f4b33 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/mod.rs @@ -0,0 +1,49 @@ +mod instance_model; +pub use instance_model::*; + +mod unit_model; +pub use unit_model::*; + +mod source_model; +pub use source_model::*; + +mod mode_model; +pub use mode_model::*; + +mod target_model; +pub use target_model::*; + +mod mapping_model; +pub use mapping_model::*; + +mod group_model; +pub use group_model::*; + +mod activation_condition_model; +pub use activation_condition_model::*; + +mod compartment_preset; +pub use compartment_preset::*; + +mod conditional_activation_model; +pub use conditional_activation_model::*; + +mod preset_link; +pub use preset_link::*; + +mod mapping_extension_model; +pub use mapping_extension_model::*; + +mod compartment_model; +pub use compartment_model::*; + +mod props; +pub use props::*; + +mod auto_units; + +mod actions; + +pub use actions::*; + +pub use auto_units::*; diff --git a/plugin-reaper-realearn/main/src/application/mode_model.rs b/plugin-reaper-realearn/main/src/application/mode_model.rs new file mode 100644 index 0000000..eac3589 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/mode_model.rs @@ -0,0 +1,670 @@ +use crate::domain::{Backbone, EelTransformation, LuaFeedbackScript, Mode}; + +use helgoboss_learn::{ + check_mode_applicability, create_unit_value_interval, full_discrete_interval, + full_unit_interval, AbsoluteMode, ButtonUsage, DetailedSourceCharacter, DiscreteIncrement, + EncoderUsage, FeedbackProcessor, FeedbackType, FireMode, GroupInteraction, Interval, + ModeApplicabilityCheckInput, ModeParameter, ModeSettings, OutOfRangeBehavior, TakeoverMode, + UnitValue, ValueSequence, VirtualColor, +}; + +use crate::application::{Affected, Change, GetProcessingRelevance, ProcessingRelevance}; +use crate::base::CloneAsDefault; +use base::hash_util::clone_to_other_hash_map; +use helgobox_api::persistence::FeedbackValueTable; +use std::time::Duration; + +pub enum ModeCommand { + SetAbsoluteMode(AbsoluteMode), + SetTargetValueInterval(Interval), + SetMinTargetValue(UnitValue), + SetMaxTargetValue(UnitValue), + SetSourceValueInterval(Interval), + SetMinSourceValue(UnitValue), + SetMaxSourceValue(UnitValue), + SetReverse(bool), + SetPressDurationInterval(Interval), + SetMinPressDuration(Duration), + SetMaxPressDuration(Duration), + SetTurboRate(Duration), + SetLegacyJumpInterval(Option>), + SetOutOfRangeBehavior(OutOfRangeBehavior), + SetFireMode(FireMode), + SetRoundTargetValue(bool), + SetTakeoverMode(TakeoverMode), + SetButtonUsage(ButtonUsage), + SetEncoderUsage(EncoderUsage), + SetEelControlTransformation(String), + SetEelFeedbackTransformation(String), + SetStepSizeInterval(Interval), + SetStepFactorInterval(Interval), + SetMinStepSize(UnitValue), + SetMaxStepSize(UnitValue), + SetMinStepFactor(DiscreteIncrement), + SetMaxStepFactor(DiscreteIncrement), + SetRotate(bool), + SetMakeAbsolute(bool), + SetGroupInteraction(GroupInteraction), + SetTargetValueSequence(ValueSequence), + SetFeedbackType(FeedbackType), + SetTextualFeedbackExpression(String), + SetFeedbackColor(Option), + SetFeedbackBackgroundColor(Option), + SetFeedbackValueTable(Option), + /// This doesn't reset the mode type, just all the values. + ResetWithinType, +} + +#[derive(Eq, PartialEq)] +pub enum ModeProp { + AbsoluteMode, + TargetValueInterval, + SourceValueInterval, + Reverse, + PressDurationInterval, + TurboRate, + LegacyJumpInterval, + OutOfRangeBehavior, + FireMode, + RoundTargetValue, + TakeoverMode, + ButtonUsage, + EncoderUsage, + EelControlTransformation, + EelFeedbackTransformation, + StepSizeInterval, + StepFactorInterval, + Rotate, + MakeAbsolute, + GroupInteraction, + TargetValueSequence, + FeedbackType, + TextualFeedbackExpression, + FeedbackColor, + FeedbackBackgroundColor, + FeedbackValueTable, +} + +impl GetProcessingRelevance for ModeProp { + fn processing_relevance(&self) -> Option { + // At the moment, all mode aspects are relevant for processing. + Some(ProcessingRelevance::ProcessingRelevant) + } +} + +/// A model for creating modes +#[derive(Clone, Debug)] +pub struct ModeModel { + absolute_mode: AbsoluteMode, + target_value_interval: Interval, + source_value_interval: Interval, + reverse: bool, + press_duration_interval: Interval, + turbo_rate: Duration, + /// Since 2.14.0-pre.10, this should be `None` for all new mappings. + /// + /// In this case, a dynamic jump interval will be used. + /// + /// This is only set for old presets in order to not change behavior. + legacy_jump_interval: Option>, + out_of_range_behavior: OutOfRangeBehavior, + fire_mode: FireMode, + round_target_value: bool, + takeover_mode: TakeoverMode, + button_usage: ButtonUsage, + encoder_usage: EncoderUsage, + eel_control_transformation: String, + eel_feedback_transformation: String, + // For relative control values. + /// A step size is the positive, absolute size of an increment. 0.0 represents no increment, + /// 1.0 represents an increment over the whole value range (not very useful). + /// + /// It's an interval. When using rotary encoders, the most important value is the interval + /// minimum. There are some controllers which deliver higher increments if turned faster. This + /// is where the maximum comes in. The maximum is also important if using the relative mode + /// with buttons. The harder you press the button, the higher the increment. It's limited + /// by the maximum value. + step_size_interval: Interval, + /// A step factor is a coefficient which multiplies the atomic step size. E.g. a step count of 2 + /// can be read as 2 * step_size which means double speed. When the step count is negative, + /// it's interpreted as a fraction of 1. E.g. a step count of -2 is 1/2 * step_size which + /// means half speed. The increment is fired only every nth time, which results in a + /// slow-down, or in other words, less sensitivity. + step_factor_interval: Interval, + rotate: bool, + make_absolute: bool, + group_interaction: GroupInteraction, + target_value_sequence: ValueSequence, + feedback_type: FeedbackType, + textual_feedback_expression: String, + feedback_color: Option, + feedback_background_color: Option, + feedback_value_table: Option, +} + +impl Default for ModeModel { + fn default() -> Self { + Self { + absolute_mode: AbsoluteMode::Normal, + target_value_interval: full_unit_interval(), + source_value_interval: full_unit_interval(), + reverse: false, + press_duration_interval: Interval::new( + Duration::from_millis(0), + Duration::from_millis(0), + ), + turbo_rate: Duration::from_millis(0), + legacy_jump_interval: None, + out_of_range_behavior: Default::default(), + fire_mode: Default::default(), + round_target_value: false, + takeover_mode: Default::default(), + button_usage: Default::default(), + encoder_usage: Default::default(), + eel_control_transformation: String::new(), + eel_feedback_transformation: String::new(), + step_size_interval: Self::default_step_size_interval(), + step_factor_interval: Self::default_step_factor_interval(), + rotate: false, + make_absolute: false, + group_interaction: Default::default(), + target_value_sequence: Default::default(), + feedback_type: Default::default(), + textual_feedback_expression: Default::default(), + feedback_color: Default::default(), + feedback_background_color: Default::default(), + feedback_value_table: None, + } + } +} + +impl Change<'_> for ModeModel { + type Command = ModeCommand; + type Prop = ModeProp; + + fn change(&mut self, cmd: ModeCommand) -> Option> { + use Affected::*; + use ModeCommand as C; + use ModeProp as P; + let affected = match cmd { + C::SetAbsoluteMode(v) => { + self.absolute_mode = v; + One(P::AbsoluteMode) + } + C::SetTargetValueInterval(v) => { + self.target_value_interval = v; + One(P::TargetValueInterval) + } + C::SetMinTargetValue(v) => { + return self.change(C::SetTargetValueInterval( + self.target_value_interval.with_min(v), + )) + } + C::SetMaxTargetValue(v) => { + return self.change(C::SetTargetValueInterval( + self.target_value_interval.with_max(v), + )) + } + C::SetSourceValueInterval(v) => { + self.source_value_interval = v; + One(P::SourceValueInterval) + } + C::SetMinSourceValue(v) => { + return self.change(C::SetSourceValueInterval( + self.source_value_interval.with_min(v), + )) + } + C::SetMaxSourceValue(v) => { + return self.change(C::SetSourceValueInterval( + self.source_value_interval.with_max(v), + )) + } + C::SetReverse(v) => { + self.reverse = v; + One(P::Reverse) + } + C::SetPressDurationInterval(v) => { + self.press_duration_interval = v; + One(P::PressDurationInterval) + } + C::SetMinPressDuration(v) => { + return self.change(C::SetPressDurationInterval( + self.press_duration_interval.with_min(v), + )) + } + C::SetMaxPressDuration(v) => { + return self.change(C::SetPressDurationInterval( + self.press_duration_interval.with_max(v), + )) + } + C::SetTurboRate(v) => { + self.turbo_rate = v; + One(P::TurboRate) + } + C::SetLegacyJumpInterval(v) => { + self.legacy_jump_interval = v; + One(P::LegacyJumpInterval) + } + C::SetOutOfRangeBehavior(v) => { + self.out_of_range_behavior = v; + One(P::OutOfRangeBehavior) + } + C::SetFireMode(v) => { + self.fire_mode = v; + One(P::FireMode) + } + C::SetRoundTargetValue(v) => { + self.round_target_value = v; + One(P::RoundTargetValue) + } + C::SetTakeoverMode(v) => { + self.takeover_mode = v; + One(P::TakeoverMode) + } + C::SetButtonUsage(v) => { + self.button_usage = v; + One(P::ButtonUsage) + } + C::SetEncoderUsage(v) => { + self.encoder_usage = v; + One(P::EncoderUsage) + } + C::SetEelControlTransformation(v) => { + self.eel_control_transformation = v; + One(P::EelControlTransformation) + } + C::SetEelFeedbackTransformation(v) => { + self.eel_feedback_transformation = v; + One(P::EelFeedbackTransformation) + } + C::SetStepSizeInterval(v) => { + self.step_size_interval = v; + One(P::StepSizeInterval) + } + C::SetStepFactorInterval(v) => { + self.step_factor_interval = v; + One(P::StepFactorInterval) + } + C::SetMinStepSize(v) => { + return self.change(C::SetStepSizeInterval(self.step_size_interval.with_min(v))) + } + C::SetMaxStepSize(v) => { + return self.change(C::SetStepSizeInterval(self.step_size_interval.with_max(v))) + } + C::SetMinStepFactor(v) => { + return self.change(C::SetStepFactorInterval( + self.step_factor_interval.with_min(v), + )) + } + C::SetMaxStepFactor(v) => { + return self.change(C::SetStepFactorInterval( + self.step_factor_interval.with_max(v), + )) + } + C::SetRotate(v) => { + self.rotate = v; + One(P::Rotate) + } + C::SetMakeAbsolute(v) => { + self.make_absolute = v; + One(P::MakeAbsolute) + } + C::SetGroupInteraction(v) => { + self.group_interaction = v; + One(P::GroupInteraction) + } + C::SetTargetValueSequence(v) => { + self.target_value_sequence = v; + One(P::TargetValueSequence) + } + C::SetFeedbackType(v) => { + self.feedback_type = v; + One(P::FeedbackType) + } + C::SetTextualFeedbackExpression(v) => { + self.textual_feedback_expression = v; + One(P::TextualFeedbackExpression) + } + C::SetFeedbackColor(v) => { + self.feedback_color = v; + One(P::FeedbackColor) + } + C::SetFeedbackBackgroundColor(v) => { + self.feedback_background_color = v; + One(P::FeedbackBackgroundColor) + } + C::SetFeedbackValueTable(v) => { + self.feedback_value_table = v; + One(P::FeedbackValueTable) + } + C::ResetWithinType => { + *self = Default::default(); + Multiple + } + }; + Some(affected) + } +} + +impl ModeModel { + pub fn default_step_size_interval() -> Interval { + // 0.01 has been chosen as default minimum step size because it corresponds to 1%. + // + // 0.05 has been chosen as default maximum step size in order to make users aware that + // ReaLearn supports encoder acceleration ("dial harder = more increments") and + // velocity-sensitive buttons ("press harder = more increments") but still is low + // enough to not lead to surprising results such as ugly parameter jumps. + Interval::new(UnitValue::new(0.01), UnitValue::new(0.05)) + } + + pub fn default_step_factor_interval() -> Interval { + Interval::new(DiscreteIncrement::new(1), DiscreteIncrement::new(5)) + } + + pub fn feedback_value_table(&self) -> Option<&FeedbackValueTable> { + self.feedback_value_table.as_ref() + } + + pub fn absolute_mode(&self) -> AbsoluteMode { + self.absolute_mode + } + + pub fn target_value_interval(&self) -> Interval { + self.target_value_interval + } + + pub fn source_value_interval(&self) -> Interval { + self.source_value_interval + } + + pub fn reverse(&self) -> bool { + self.reverse + } + + pub fn press_duration_interval(&self) -> Interval { + self.press_duration_interval + } + + pub fn turbo_rate(&self) -> Duration { + self.turbo_rate + } + + pub fn legacy_jump_interval(&self) -> Option> { + self.legacy_jump_interval + } + + pub fn out_of_range_behavior(&self) -> OutOfRangeBehavior { + self.out_of_range_behavior + } + + pub fn fire_mode(&self) -> FireMode { + self.fire_mode + } + + pub fn round_target_value(&self) -> bool { + self.round_target_value + } + + pub fn takeover_mode(&self) -> TakeoverMode { + self.takeover_mode + } + + pub fn button_usage(&self) -> ButtonUsage { + self.button_usage + } + + pub fn encoder_usage(&self) -> EncoderUsage { + self.encoder_usage + } + + pub fn eel_control_transformation(&self) -> &str { + &self.eel_control_transformation + } + + pub fn eel_feedback_transformation(&self) -> &str { + &self.eel_feedback_transformation + } + + pub fn step_size_interval(&self) -> Interval { + self.step_size_interval + } + + pub fn step_factor_interval(&self) -> Interval { + self.step_factor_interval + } + + pub fn rotate(&self) -> bool { + self.rotate + } + + pub fn make_absolute(&self) -> bool { + self.make_absolute + } + + pub fn group_interaction(&self) -> GroupInteraction { + self.group_interaction + } + + pub fn target_value_sequence(&self) -> &ValueSequence { + &self.target_value_sequence + } + + pub fn feedback_type(&self) -> FeedbackType { + self.feedback_type + } + + pub fn textual_feedback_expression(&self) -> &str { + &self.textual_feedback_expression + } + + pub fn feedback_color(&self) -> Option<&VirtualColor> { + self.feedback_color.as_ref() + } + + pub fn feedback_background_color(&self) -> Option<&VirtualColor> { + self.feedback_background_color.as_ref() + } + + pub fn mode_parameter_is_relevant( + &self, + mode_parameter: ModeParameter, + base_input: ModeApplicabilityCheckInput, + possible_source_characters: &[DetailedSourceCharacter], + control_is_relevant: bool, + feedback_is_relevant: bool, + ) -> bool { + possible_source_characters.iter().any(|source_character| { + let is_applicable = |is_feedback| { + let input = ModeApplicabilityCheckInput { + is_feedback, + source_character: *source_character, + ..base_input + }; + check_mode_applicability(mode_parameter, input).is_relevant() + }; + (control_is_relevant && is_applicable(false)) + || (feedback_is_relevant && is_applicable(true)) + }) + } + + /// Creates a mode reflecting this model's current values + #[allow(clippy::if_same_then_else)] + pub fn create_mode( + &self, + base_input: ModeApplicabilityCheckInput, + possible_source_characters: &[DetailedSourceCharacter], + ) -> Mode { + let is_relevant = |mode_parameter: ModeParameter| { + // We take both control and feedback into account to not accidentally get slightly + // different behavior if feedback is not enabled. + self.mode_parameter_is_relevant( + mode_parameter, + base_input, + possible_source_characters, + true, + true, + ) + }; + // We know that just step max sometimes needs to be set to a sensible default (= step min) + // and we know that step size and speed is mutually exclusive and therefore doesn't need + // to be handled separately. + let step_size_max_is_relevant = is_relevant(ModeParameter::StepSizeMax); + let step_factor_max_is_relevant = is_relevant(ModeParameter::StepFactorMax); + Mode::new(ModeSettings { + absolute_mode: if is_relevant(ModeParameter::AbsoluteMode) { + self.absolute_mode + } else { + AbsoluteMode::default() + }, + source_value_interval: if is_relevant(ModeParameter::SourceMinMax) { + self.source_value_interval + } else { + full_unit_interval() + }, + discrete_source_value_interval: if is_relevant(ModeParameter::SourceMinMax) { + // TODO-high-discrete Use dedicated discrete source interval + full_discrete_interval() + } else { + full_discrete_interval() + }, + target_value_interval: if is_relevant(ModeParameter::TargetMinMax) { + self.target_value_interval + } else { + full_unit_interval() + }, + discrete_target_value_interval: if is_relevant(ModeParameter::TargetMinMax) { + // TODO-high-discrete Use dedicated discrete target interval + full_discrete_interval() + } else { + full_discrete_interval() + }, + step_factor_interval: Interval::new( + self.step_factor_interval.min_val(), + if step_factor_max_is_relevant { + self.step_factor_interval.max_val() + } else { + self.step_factor_interval.min_val() + }, + ), + step_size_interval: Interval::new_auto( + self.step_size_interval.min_val(), + if step_size_max_is_relevant { + self.step_size_interval.max_val() + } else { + self.step_size_interval.min_val() + }, + ), + jump_interval: if is_relevant(ModeParameter::JumpMinMax) { + self.legacy_jump_interval + .unwrap_or_else(default_jump_interval) + } else { + full_unit_interval() + }, + discrete_jump_interval: if is_relevant(ModeParameter::JumpMinMax) { + // TODO-high-discrete Use dedicated discrete jump interval + full_discrete_interval() + } else { + full_discrete_interval() + }, + fire_mode: if is_relevant(ModeParameter::FireMode) { + self.fire_mode + } else { + FireMode::default() + }, + press_duration_interval: self.press_duration_interval, + turbo_rate: self.turbo_rate, + takeover_mode: if is_relevant(ModeParameter::TakeoverMode) { + self.takeover_mode + } else { + TakeoverMode::default() + }, + encoder_usage: if is_relevant(ModeParameter::RelativeFilter) { + self.encoder_usage + } else { + EncoderUsage::default() + }, + button_usage: if is_relevant(ModeParameter::ButtonFilter) { + self.button_usage + } else { + ButtonUsage::default() + }, + reverse: if is_relevant(ModeParameter::Reverse) { + self.reverse + } else { + false + }, + rotate: if is_relevant(ModeParameter::Rotate) { + self.rotate + } else { + false + }, + round_target_value: if is_relevant(ModeParameter::RoundTargetValue) { + self.round_target_value + } else { + false + }, + out_of_range_behavior: if is_relevant(ModeParameter::OutOfRangeBehavior) { + self.out_of_range_behavior + } else { + OutOfRangeBehavior::default() + }, + control_transformation: if is_relevant(ModeParameter::ControlTransformation) { + EelTransformation::compile_for_control(&self.eel_control_transformation).ok() + } else { + None + }, + feedback_transformation: if is_relevant(ModeParameter::FeedbackTransformation) { + EelTransformation::compile_for_feedback(&self.eel_feedback_transformation).ok() + } else { + None + }, + feedback_value_table: self.feedback_value_table.as_ref().map(|t| match t { + FeedbackValueTable::FromTextToDiscrete(v) => { + helgoboss_learn::FeedbackValueTable::FromTextToDiscrete( + clone_to_other_hash_map(&v.value), + ) + } + FeedbackValueTable::FromTextToContinuous(v) => { + helgoboss_learn::FeedbackValueTable::FromTextToContinuous( + clone_to_other_hash_map(&v.value), + ) + } + }), + make_absolute: if is_relevant(ModeParameter::MakeAbsolute) { + self.make_absolute + } else { + false + }, + // TODO-high-discrete Use discrete IF both source and target support it AND enabled + use_discrete_processing: false, + target_value_sequence: if is_relevant(ModeParameter::TargetValueSequence) { + self.target_value_sequence.clone() + } else { + Default::default() + }, + feedback_processor: match self.feedback_type { + FeedbackType::Numeric => FeedbackProcessor::Numeric, + FeedbackType::Text => FeedbackProcessor::Text { + expression: self.textual_feedback_expression.to_owned(), + }, + FeedbackType::Dynamic => { + let lua = unsafe { Backbone::main_thread_lua() }; + match LuaFeedbackScript::compile(lua, &self.textual_feedback_expression) { + Ok(script) => FeedbackProcessor::Dynamic { + script: CloneAsDefault::new(Some(script)), + }, + Err(_) => FeedbackProcessor::Text { + expression: " ".to_string(), + }, + } + } + }, + feedback_color: self.feedback_color.clone(), + feedback_background_color: self.feedback_background_color.clone(), + }) + } +} + +fn default_jump_interval() -> Interval { + create_unit_value_interval(0.0, 0.03) +} diff --git a/plugin-reaper-realearn/main/src/application/preset_link.rs b/plugin-reaper-realearn/main/src/application/preset_link.rs new file mode 100644 index 0000000..de73adf --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/preset_link.rs @@ -0,0 +1,251 @@ +use base::default_util::is_default; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use serde::{Deserialize, Serialize}; + +use derive_more::Display; +use reaper_high::{Fx, FxInfo}; +use reaper_medium::ReaperStr; +use std::fmt; +use std::fmt::Formatter; +use strum::EnumIter; + +pub trait PresetLinkManager: fmt::Debug { + fn find_preset_linked_to_fx(&self, fx_id: &FxId) -> Option; +} + +pub trait PresetLinkMutator { + fn update_fx_id(&mut self, old_fx_id: FxId, new_fx_id: FxId); + + fn remove_link(&mut self, fx_id: &FxId); + + fn link_preset_to_fx(&mut self, preset_id: String, fx_id: FxId); +} + +#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FxPresetLinkConfig { + links: Vec, +} + +impl PresetLinkManager for FxPresetLinkConfig { + fn find_preset_linked_to_fx(&self, fx_id: &FxId) -> Option { + // Let the links with preset name have precedence. + find_match( + self.links.iter().filter(|l| l.fx_id.has_preset_name()), + fx_id, + ) + .or_else(|| { + find_match( + self.links.iter().filter(|l| !l.fx_id.has_preset_name()), + fx_id, + ) + }) + } +} + +impl PresetLinkMutator for FxPresetLinkConfig { + fn update_fx_id(&mut self, old_fx_id: FxId, new_fx_id: FxId) { + for link in &mut self.links { + if link.fx_id == old_fx_id { + link.fx_id = new_fx_id; + return; + } + } + } + + fn remove_link(&mut self, fx_id: &FxId) { + self.links.retain(|l| &l.fx_id != fx_id); + } + + fn link_preset_to_fx(&mut self, preset_id: String, fx_id: FxId) { + let link = FxPresetLink { fx_id, preset_id }; + if let Some(l) = self.links.iter_mut().find(|l| l.fx_id == link.fx_id) { + *l = link; + } else { + self.links.push(link); + } + } +} + +impl FxPresetLinkConfig { + pub fn links(&self) -> impl ExactSizeIterator + '_ { + self.links.iter() + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FxPresetLink { + #[serde(rename = "fx")] + pub fx_id: FxId, + #[serde(rename = "presetId")] + pub preset_id: String, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FxId { + #[serde(default, skip_serializing_if = "is_default")] + pub name: String, + #[serde(default, skip_serializing_if = "is_default")] + pub file_name: String, + #[serde(default, skip_serializing_if = "is_default")] + pub preset_name: String, +} + +impl fmt::Display for FxId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn dash_if_empty(s: &str) -> &str { + if s.is_empty() { + "-" + } else { + s + } + } + write!( + f, + "Name: {} | File: {} | Preset: {}", + dash_if_empty(&self.name), + dash_if_empty(&self.file_name), + dash_if_empty(&self.preset_name) + ) + } +} + +impl FxId { + pub fn from_fx(fx: &Fx, most_relevant_only: bool) -> anyhow::Result { + let fx_info = fx.info()?; + let preset_name = fx.preset_name(); + let fx_id = Self::from_fx_info_and_preset_name( + &fx_info, + preset_name.as_deref(), + most_relevant_only, + ); + Ok(fx_id) + } + + pub fn from_fx_info_and_preset_name( + fx_info: &FxInfo, + preset_name: Option<&ReaperStr>, + most_relevant_only: bool, + ) -> FxId { + let mut fx_id = FxId { + name: fx_info.effect_name.trim().to_string(), + ..Default::default() + }; + if !fx_id.name.is_empty() && most_relevant_only { + return fx_id; + } + fx_id.file_name = fx_info.file_name.to_string_lossy().trim().to_string(); + if !fx_id.file_name.is_empty() && most_relevant_only { + return fx_id; + } + fx_id.preset_name = preset_name + .map(|s| s.to_str().trim().to_string()) + .unwrap_or_default(); + fx_id + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn has_name(&self) -> bool { + !self.name.is_empty() + } + + pub fn file_name(&self) -> &str { + &self.file_name + } + + pub fn has_file_name(&self) -> bool { + !self.file_name.is_empty() + } + + pub fn preset_name(&self) -> &str { + &self.preset_name + } + + pub fn has_preset_name(&self) -> bool { + !self.preset_name.is_empty() + } + + /// Every field in the pattern that's filled must match! + /// + /// The pattern FX ID fields can contain wildcards. + pub fn matches(&self, fx_id_pattern: &FxId) -> bool { + if fx_id_pattern.has_name() && !self.name_matches(fx_id_pattern) { + return false; + } + if fx_id_pattern.has_file_name() && !self.file_name_matches(fx_id_pattern) { + return false; + } + if fx_id_pattern.has_preset_name() && !self.preset_name_matches(fx_id_pattern) { + return false; + } + true + } + + fn name_matches(&self, fx_id_pattern: &FxId) -> bool { + let wild_match = wildmatch::WildMatch::new(&fx_id_pattern.name); + wild_match.matches(self.name()) + } + + fn file_name_matches(&self, fx_id_pattern: &FxId) -> bool { + let wild_match = wildmatch::WildMatch::new(&fx_id_pattern.file_name); + wild_match.matches(self.file_name()) + } + + fn preset_name_matches(&self, fx_id_pattern: &FxId) -> bool { + let wild_match = wildmatch::WildMatch::new(&fx_id_pattern.preset_name); + wild_match.matches(self.preset_name()) + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum AutoLoadMode { + #[serde(rename = "off")] + #[display(fmt = "Off")] + Off, + #[serde(rename = "focused-fx")] + #[display(fmt = "Based on unit FX")] + UnitFx, +} + +impl Default for AutoLoadMode { + fn default() -> Self { + Self::Off + } +} + +impl AutoLoadMode { + pub fn is_on(&self) -> bool { + *self != Self::Off + } +} + +fn find_match<'a>( + mut links: impl Iterator, + fx_id: &FxId, +) -> Option { + links.find_map(|link| { + if fx_id.matches(&link.fx_id) { + Some(link.preset_id.clone()) + } else { + None + } + }) +} diff --git a/plugin-reaper-realearn/main/src/application/props.rs b/plugin-reaper-realearn/main/src/application/props.rs new file mode 100644 index 0000000..d6ff230 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/props.rs @@ -0,0 +1,108 @@ +/// A type which can express what properties are potentially be affected by a change operation. +#[derive(Eq, PartialEq)] +pub enum Affected { + /// Just the given property might be affected. + One(T), + /// Multiple properties might be affected. + Multiple, +} + +impl Affected { + pub fn processing_relevance(&self) -> Option + where + T: GetProcessingRelevance, + { + use Affected::*; + match self { + One(p) => p.processing_relevance(), + Multiple => Some(ProcessingRelevance::ProcessingRelevant), + } + } +} + +/// Defines how relevant a change to a model object is for the processing logic. +/// +/// Depending on this value, the session will decide whether to sync data to the processing layer +/// or not. +#[derive(Eq, PartialEq, Ord, PartialOrd)] +pub enum ProcessingRelevance { + /// Lowest relevance level: Syncing of persistent processing state necessary. + /// + /// Returned if a change of the given prop would have an effect on control/feedback + /// processing and is also changed by the processing layer itself, so it shouldn't contain much! + /// The session takes care to not sync the complete mapping properties but only the ones + /// mentioned here. + // + // Important to keep this on top! Order matters. + PersistentProcessingRelevant, + /// Highest relevance level: Syncing of complete mapping state necessary. + /// + /// Returned if this is a property that has an effect on control/feedback processing. + /// + /// However, we don't include properties here which are changed by the processing layer + /// (such as `is_enabled`) because that would mean the complete mapping will be synced as a + /// result, whereas we want to sync processing stuff faster! + ProcessingRelevant, +} + +pub type ChangeResult = Result>, String>; + +/// Usable for changing values of properties in an infallible way. +/// +/// This is a bit like the Flux pattern. One has commands (or actions) that describe how to change +/// state and the store (in this case the value itself) changes its state accordingly. +/// +/// This pattern has been introduced in #492 when changing the change-notification mechanism. +/// We moved from an Rx-based approach where each property has its own subscribers to a more +/// flexible and much more memory-friendly approach that works by letting any property change start +/// at the session and letting the session handle the notification centrally. +/// +/// This command pattern is actually not necessary for this new change-notification mechanism. We +/// could also provide simple setter methods that return `Affected` values (as we already do when +/// we apply more complex changes than just changing a few properties). This would have the +/// advantage that we can choose more specific return types, not such a generic one (e.g. `Result` +/// if the change is fallible). However, we introduced the pattern and it's too early to remove it. +/// +/// Because it also has some potential advantages: +/// +/// - It unifies infallible property write access. +/// - It allows for hierarchical changes without the need for closures, e.g. the command to change a +/// target property p of a mapping m in compartment c is expressed as a simple object! This also +/// has a nice symmetry to the way affected properties are returned (which is also hierarchial). +/// - Because of this unification, we could record changes, log them easily, even build some undo +/// system on it. +/// +/// Let's see where this goes! +pub trait Change<'a> { + type Command; + type Prop; + + fn change(&mut self, cmd: Self::Command) -> Option>; +} + +pub trait GetProcessingRelevance { + fn processing_relevance(&self) -> Option; +} + +pub fn merge_affected( + affected_1: Option>, + affected_2: Option>, +) -> Option> { + match (affected_1, affected_2) { + (None, None) => None, + (None, Some(a)) | (Some(a), None) => Some(a), + (Some(a), Some(b)) => { + use Affected::*; + match (a, b) { + (_, Multiple) | (Multiple, _) => Some(Multiple), + (One(p1), One(p2)) => { + if p1 == p2 { + Some(One(p1)) + } else { + Some(Multiple) + } + } + } + } + } +} diff --git a/plugin-reaper-realearn/main/src/application/source_model.rs b/plugin-reaper-realearn/main/src/application/source_model.rs new file mode 100644 index 0000000..2698935 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/source_model.rs @@ -0,0 +1,1479 @@ +use crate::application::{ + Affected, Change, GetProcessingRelevance, MappingProp, ProcessingRelevance, +}; +use crate::base::CloneAsDefault; +use crate::domain::{ + Backbone, CompartmentKind, CompartmentParamIndex, CompoundMappingSource, EelMidiSourceScript, + ExtendedSourceCharacter, FlexibleMidiSourceScript, KeySource, Keystroke, LuaMidiSourceScript, + MidiSource, RealearnParameterSource, ReaperSource, SpeechSource, StreamDeckSource, TimerSource, + VirtualControlElement, VirtualControlElementId, VirtualSource, +}; +use camino::{Utf8Path, Utf8PathBuf}; +use derive_more::Display; +use helgoboss_learn::{ + ControlValue, DetailedSourceCharacter, DisplaySpec, DisplayType, Interval, MackieLcdScope, + MackieSevenSegmentDisplayScope, MidiClockTransportMessage, OscArgDescriptor, OscSource, + OscTypeTag, SiniConE24Scope, SlKeyboardDisplayScope, SourceCharacter, UnitValue, + DEFAULT_OSC_ARG_VALUE_RANGE, +}; +use helgoboss_midi::{Channel, U14, U7}; +use helgobox_api::persistence::{ + MidiScriptKind, StreamDeckButtonBackground, StreamDeckButtonDesign, + StreamDeckButtonFadingImageForeground, StreamDeckButtonForeground, + StreamDeckButtonImageBackground, StreamDeckButtonSlidingImageForeground, + VirtualControlElementCharacter, +}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use reaper_high::Reaper; +use serde::{Deserialize, Serialize}; +use serde_repr::*; +use std::borrow::Cow; +use std::convert::TryFrom; +use std::fmt; +use std::fmt::Display; +use std::time::Duration; +use strum::EnumIter; + +#[allow(clippy::enum_variant_names)] +pub enum SourceCommand { + SetCategory(SourceCategory), + SetMidiSourceType(MidiSourceType), + SetChannel(Option), + SetMidiMessageNumber(Option), + SetParameterNumberMessageNumber(Option), + SetCustomCharacter(SourceCharacter), + SetMidiClockTransportMessage(MidiClockTransportMessage), + SetIsRegistered(Option), + SetIs14Bit(Option), + SetRawMidiPattern(String), + SetMidiScriptKind(MidiScriptKind), + SetMidiScript(String), + SetDisplayType(DisplayType), + SetDisplayId(Option), + SetLine(Option), + SetOscAddressPattern(String), + SetOscArgIndex(Option), + SetOscArgTypeTag(OscTypeTag), + SetOscArgIsRelative(bool), + SetOscArgValueRange(Interval), + SetOscFeedbackArgs(Vec), + SetReaperSourceType(ReaperSourceType), + SetTimerMillis(u64), + SetParameterIndex(CompartmentParamIndex), + SetKeystroke(Option), + SetButtonIndex(u32), + SetButtonBackgroundType(StreamDeckButtonBackgroundType), + SetButtonBackgroundImagePath(Utf8PathBuf), + SetButtonForegroundType(StreamDeckButtonForegroundType), + SetButtonForegroundImagePath(Utf8PathBuf), + SetButtonStaticText(String), + SetControlElementCharacter(VirtualControlElementCharacter), + SetControlElementId(VirtualControlElementId), +} + +#[derive(Eq, PartialEq)] +pub enum SourceProp { + Category, + MidiSourceType, + Channel, + MidiMessageNumber, + ParameterNumberMessageNumber, + CustomCharacter, + MidiClockTransportMessage, + IsRegistered, + Is14Bit, + RawMidiPattern, + MidiScriptKind, + MidiScript, + DisplayType, + DisplayId, + Line, + OscAddressPattern, + OscArgIndex, + OscArgTypeTag, + OscArgIsRelative, + OscArgValueRange, + OscFeedbackArgs, + ReaperSourceType, + ControlElementType, + ControlElementId, + TimerMillis, + ParameterIndex, + Keystroke, + ButtonIndex, + ButtonBackgroundType, + ButtonBackgroundImagePath, + ButtonForegroundType, + ButtonForegroundImagePath, + ButtonStaticText, +} + +impl GetProcessingRelevance for SourceProp { + fn processing_relevance(&self) -> Option { + // At the moment, all source aspects are relevant for processing. + Some(ProcessingRelevance::ProcessingRelevant) + } +} + +impl Change<'_> for SourceModel { + type Command = SourceCommand; + type Prop = SourceProp; + + fn change(&mut self, cmd: Self::Command) -> Option> { + use Affected as A; + use SourceCommand as C; + use SourceProp as P; + let affected = match cmd { + C::SetCategory(v) => { + self.category = v; + A::One(P::Category) + } + C::SetMidiSourceType(v) => { + self.midi_source_type = v; + A::One(P::MidiSourceType) + } + C::SetChannel(v) => { + self.channel = v; + A::One(P::Channel) + } + C::SetMidiMessageNumber(v) => { + self.midi_message_number = v; + A::One(P::MidiMessageNumber) + } + C::SetParameterNumberMessageNumber(v) => { + self.parameter_number_message_number = v; + A::One(P::ParameterNumberMessageNumber) + } + C::SetCustomCharacter(v) => { + self.custom_character = v; + A::One(P::CustomCharacter) + } + C::SetMidiClockTransportMessage(v) => { + self.midi_clock_transport_message = v; + A::One(P::MidiClockTransportMessage) + } + C::SetIsRegistered(v) => { + self.is_registered = v; + A::One(P::IsRegistered) + } + C::SetIs14Bit(v) => { + self.is_14_bit = v; + A::One(P::Is14Bit) + } + C::SetRawMidiPattern(v) => { + self.raw_midi_pattern = v; + A::One(P::RawMidiPattern) + } + C::SetMidiScriptKind(v) => { + self.midi_script_kind = v; + A::One(P::MidiScriptKind) + } + C::SetMidiScript(v) => { + self.midi_script = v; + A::One(P::MidiScript) + } + C::SetDisplayType(v) => { + self.display_type = v; + A::One(P::DisplayType) + } + C::SetDisplayId(v) => { + self.display_id = v; + A::One(P::DisplayId) + } + C::SetLine(v) => { + self.line = v; + A::One(P::Line) + } + C::SetOscAddressPattern(v) => { + self.osc_address_pattern = v; + A::One(P::OscAddressPattern) + } + C::SetOscArgIndex(v) => { + self.osc_arg_index = v; + A::One(P::OscArgIndex) + } + C::SetOscArgTypeTag(v) => { + self.osc_arg_type_tag = v; + A::One(P::OscArgTypeTag) + } + C::SetOscArgIsRelative(v) => { + self.osc_arg_is_relative = v; + A::One(P::OscArgIsRelative) + } + C::SetOscArgValueRange(v) => { + self.osc_arg_value_range = v; + A::One(P::OscArgValueRange) + } + C::SetOscFeedbackArgs(v) => { + self.osc_feedback_args = v; + A::One(P::OscFeedbackArgs) + } + C::SetReaperSourceType(v) => { + self.reaper_source_type = v; + A::One(P::ReaperSourceType) + } + C::SetControlElementCharacter(v) => { + self.control_element_character = v; + A::One(P::ControlElementType) + } + C::SetControlElementId(v) => { + self.control_element_id = v; + A::One(P::ControlElementId) + } + C::SetTimerMillis(v) => { + self.timer_millis = v; + A::One(P::TimerMillis) + } + C::SetParameterIndex(v) => { + self.parameter_index = v; + A::One(P::ParameterIndex) + } + C::SetKeystroke(v) => { + self.keystroke = v; + A::One(P::Keystroke) + } + C::SetButtonIndex(v) => { + self.button_index = v; + A::One(P::ButtonIndex) + } + C::SetButtonBackgroundType(v) => { + self.button_background_type = v; + A::One(P::ButtonBackgroundType) + } + C::SetButtonBackgroundImagePath(v) => { + self.button_background_image_path = relativize_against_resource_dir(v); + A::One(P::ButtonBackgroundImagePath) + } + C::SetButtonForegroundType(v) => { + self.button_foreground_type = v; + A::One(P::ButtonForegroundType) + } + C::SetButtonForegroundImagePath(v) => { + self.button_foreground_image_path = relativize_against_resource_dir(v); + A::One(P::ButtonForegroundImagePath) + } + C::SetButtonStaticText(v) => { + self.button_static_text = v; + A::One(P::ButtonStaticText) + } + }; + Some(affected) + } +} + +/// A model for creating sources +#[derive(Clone, Debug)] +pub struct SourceModel { + category: SourceCategory, + custom_character: SourceCharacter, + // MIDI + midi_source_type: MidiSourceType, + channel: Option, + midi_message_number: Option, + parameter_number_message_number: Option, + midi_clock_transport_message: MidiClockTransportMessage, + is_registered: Option, + is_14_bit: Option, + raw_midi_pattern: String, + midi_script_kind: MidiScriptKind, + midi_script: String, + display_type: DisplayType, + display_id: Option, + line: Option, + // OSC + osc_address_pattern: String, + osc_arg_index: Option, + osc_arg_type_tag: OscTypeTag, + osc_arg_is_relative: bool, + osc_arg_value_range: Interval, + osc_feedback_args: Vec, + // REAPER + reaper_source_type: ReaperSourceType, + timer_millis: u64, + parameter_index: CompartmentParamIndex, + // Key + keystroke: Option, + // Stream Deck + button_index: u32, + button_background_type: StreamDeckButtonBackgroundType, + button_background_image_path: Utf8PathBuf, + button_foreground_type: StreamDeckButtonForegroundType, + button_foreground_image_path: Utf8PathBuf, + button_static_text: String, + // Virtual + control_element_character: VirtualControlElementCharacter, + control_element_id: VirtualControlElementId, +} + +impl Default for SourceModel { + fn default() -> Self { + Self::new() + } +} + +impl SourceModel { + pub fn new() -> Self { + Self { + category: SourceCategory::Never, + midi_source_type: Default::default(), + control_element_character: Default::default(), + control_element_id: Default::default(), + channel: None, + midi_message_number: None, + parameter_number_message_number: None, + custom_character: Default::default(), + midi_clock_transport_message: Default::default(), + is_registered: Some(false), + is_14_bit: Some(false), + raw_midi_pattern: "".to_owned(), + midi_script_kind: Default::default(), + midi_script: "".to_owned(), + display_type: Default::default(), + display_id: Default::default(), + line: None, + osc_address_pattern: "".to_owned(), + osc_arg_index: Some(0), + osc_arg_type_tag: Default::default(), + osc_arg_is_relative: false, + osc_arg_value_range: DEFAULT_OSC_ARG_VALUE_RANGE, + osc_feedback_args: vec![], + reaper_source_type: Default::default(), + timer_millis: Default::default(), + parameter_index: Default::default(), + keystroke: None, + button_index: 0, + button_background_type: Default::default(), + button_background_image_path: Default::default(), + button_foreground_type: Default::default(), + button_foreground_image_path: Default::default(), + button_static_text: Default::default(), + } + } + + pub fn category(&self) -> SourceCategory { + self.category + } + + pub fn midi_source_type(&self) -> MidiSourceType { + self.midi_source_type + } + + pub fn channel(&self) -> Option { + self.channel + } + + pub fn midi_message_number(&self) -> Option { + self.midi_message_number + } + + pub fn parameter_number_message_number(&self) -> Option { + self.parameter_number_message_number + } + + pub fn custom_character(&self) -> SourceCharacter { + self.custom_character + } + + pub fn midi_clock_transport_message(&self) -> MidiClockTransportMessage { + self.midi_clock_transport_message + } + + pub fn is_registered(&self) -> Option { + self.is_registered + } + + pub fn is_14_bit(&self) -> Option { + self.is_14_bit + } + + pub fn raw_midi_pattern(&self) -> &str { + &self.raw_midi_pattern + } + + pub fn midi_script_kind(&self) -> MidiScriptKind { + self.midi_script_kind + } + + pub fn midi_script(&self) -> &str { + &self.midi_script + } + + pub fn display_type(&self) -> DisplayType { + self.display_type + } + + pub fn display_id(&self) -> Option { + self.display_id + } + + pub fn line(&self) -> Option { + self.line + } + + pub fn osc_address_pattern(&self) -> &str { + &self.osc_address_pattern + } + + pub fn osc_arg_index(&self) -> Option { + self.osc_arg_index + } + + pub fn osc_arg_type_tag(&self) -> OscTypeTag { + self.osc_arg_type_tag + } + + pub fn osc_arg_is_relative(&self) -> bool { + self.osc_arg_is_relative + } + + pub fn osc_arg_value_range(&self) -> Interval { + self.osc_arg_value_range + } + + pub fn osc_feedback_args(&self) -> &[String] { + &self.osc_feedback_args + } + + pub fn keystroke(&self) -> Option { + self.keystroke + } + + pub fn button_static_text(&self) -> &str { + &self.button_static_text + } + + pub fn button_index(&self) -> u32 { + self.button_index + } + + pub fn button_background_type(&self) -> StreamDeckButtonBackgroundType { + self.button_background_type + } + + pub fn button_background_image_path(&self) -> &Utf8Path { + &self.button_background_image_path + } + + pub fn button_foreground_image_path(&self) -> &Utf8Path { + &self.button_foreground_image_path + } + + pub fn button_foreground_type(&self) -> StreamDeckButtonForegroundType { + self.button_foreground_type + } + + pub fn reaper_source_type(&self) -> ReaperSourceType { + self.reaper_source_type + } + + pub fn parameter_index(&self) -> CompartmentParamIndex { + self.parameter_index + } + + pub fn timer_millis(&self) -> u64 { + self.timer_millis + } + + pub fn control_element_character(&self) -> VirtualControlElementCharacter { + self.control_element_character + } + + pub fn control_element_id(&self) -> VirtualControlElementId { + self.control_element_id + } + + pub fn supports_control(&self) -> bool { + use SourceCategory as C; + match self.category { + C::Midi => self.midi_source_type.supports_control(), + C::Osc => self.osc_arg_type_tag.supports_control(), + C::Reaper => self.reaper_source_type.supports_control(), + C::Virtual | C::Keyboard | C::StreamDeck => true, + // Main use case: Group interaction (follow-only). + C::Never => true, + } + } + + pub fn supports_feedback(&self) -> bool { + use SourceCategory as C; + match self.category { + C::Midi => self.midi_source_type.supports_feedback(), + C::Osc => self.osc_arg_type_tag.supports_feedback(), + C::Reaper => self.reaper_source_type.supports_feedback(), + C::StreamDeck | C::Virtual => true, + C::Keyboard | C::Never => false, + } + } + + #[must_use] + pub fn apply_from_source( + &mut self, + source: &CompoundMappingSource, + ) -> Option> { + use CompoundMappingSource as S; + match source { + S::Midi(s) => { + self.category = SourceCategory::Midi; + self.midi_source_type = MidiSourceType::from_source(s); + self.channel = s.channel(); + use helgoboss_learn::MidiSource::*; + match s { + NoteVelocity { key_number, .. } + | PolyphonicKeyPressureAmount { key_number, .. } => { + self.midi_message_number = key_number.map(Into::into); + } + SpecificProgramChange { program_number, .. } => { + self.midi_message_number = *program_number; + } + ControlChangeValue { + controller_number, + custom_character, + .. + } => { + self.is_14_bit = Some(false); + self.midi_message_number = controller_number.map(Into::into); + self.custom_character = *custom_character; + } + ControlChange14BitValue { + msb_controller_number, + .. + } => { + self.is_14_bit = Some(true); + self.midi_message_number = msb_controller_number.map(Into::into); + } + ParameterNumberValue { + number, + is_14_bit, + is_registered, + custom_character, + .. + } => { + self.parameter_number_message_number = *number; + self.is_14_bit = *is_14_bit; + self.is_registered = *is_registered; + self.custom_character = *custom_character; + } + ClockTransport { message } => { + self.midi_clock_transport_message = *message; + } + Raw { + pattern, + custom_character, + } => { + self.custom_character = *custom_character; + self.raw_midi_pattern = pattern.to_string(); + } + _ => {} + } + } + S::Virtual(s) => { + self.category = SourceCategory::Virtual; + self.control_element_character = s.control_element().character(); + self.control_element_id = s.control_element().id(); + } + S::Osc(s) => { + self.category = SourceCategory::Osc; + s.address_pattern() + .clone_into(&mut self.osc_address_pattern); + self.osc_arg_index = s.arg_descriptor().map(|d| d.index()); + self.osc_arg_type_tag = + s.arg_descriptor().map(|d| d.type_tag()).unwrap_or_default(); + self.osc_arg_is_relative = s + .arg_descriptor() + .map(|d| d.is_relative()) + .unwrap_or_default(); + } + S::Reaper(s) => { + self.category = SourceCategory::Reaper; + self.reaper_source_type = ReaperSourceType::from_source(s); + use ReaperSource::*; + match s { + RealearnParameter(p) => { + self.parameter_index = p.parameter_index; + } + MidiDeviceChanges + | RealearnInstanceStart + | RealearnCompartmentLoaded + | Timer(_) + | Speech(_) => {} + } + } + S::StreamDeck(s) => { + self.category = SourceCategory::StreamDeck; + self.button_index = s.button_index; + } + S::Never => { + self.category = SourceCategory::Never; + } + S::Key(s) => { + self.category = SourceCategory::Keyboard; + self.keystroke = Some(s.stroke()); + } + }; + Some(Affected::Multiple) + } + + pub fn format_control_value(&self, value: ControlValue) -> Result { + self.create_source().format_control_value(value) + } + + pub fn parse_control_value(&self, text: &str) -> Result { + self.create_source().parse_control_value(text) + } + + pub fn character(&self) -> ExtendedSourceCharacter { + self.create_source().character() + } + + pub fn possible_detailed_characters(&self) -> Vec { + match self.create_source() { + CompoundMappingSource::Midi(s) => s.possible_detailed_characters(), + CompoundMappingSource::Osc(s) => s.possible_detailed_characters(), + CompoundMappingSource::Virtual(s) => match s.control_element().character() { + VirtualControlElementCharacter::Multi => vec![ + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::Relative, + ], + VirtualControlElementCharacter::Button => vec![ + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + ], + }, + CompoundMappingSource::Reaper(s) => s.possible_detailed_characters(), + // Can be anything, depending on the mapping that uses the group interaction. + CompoundMappingSource::Never => vec![ + DetailedSourceCharacter::MomentaryVelocitySensitiveButton, + DetailedSourceCharacter::MomentaryOnOffButton, + DetailedSourceCharacter::Trigger, + DetailedSourceCharacter::RangeControl, + DetailedSourceCharacter::Relative, + ], + CompoundMappingSource::Key(_) | CompoundMappingSource::StreamDeck(_) => { + vec![DetailedSourceCharacter::MomentaryOnOffButton] + } + } + } + + /// Creates a source reflecting this model's current values + pub fn create_source(&self) -> CompoundMappingSource { + self.create_source_internal() + .unwrap_or(CompoundMappingSource::Never) + } + + fn create_source_internal(&self) -> Option { + use SourceCategory::*; + let source = match self.category { + Midi => { + use MidiSourceType::*; + let channel = self.channel; + let key_number = self.midi_message_number.map(|n| n.into()); + let midi_source = match self.midi_source_type { + NoteVelocity => MidiSource::NoteVelocity { + channel, + key_number, + }, + NoteKeyNumber => MidiSource::NoteKeyNumber { channel }, + PolyphonicKeyPressureAmount => MidiSource::PolyphonicKeyPressureAmount { + channel, + key_number, + }, + ControlChangeValue => { + if self.is_14_bit == Some(true) { + MidiSource::ControlChange14BitValue { + channel, + msb_controller_number: self.midi_message_number.map(|n| { + // We accept even non-MSB numbers and convert them into them. + // https://github.com/helgoboss/helgobox/issues/30 + let msb_controller_number = U7::new(n.get() % 32); + msb_controller_number.into() + }), + custom_character: self.custom_character, + } + } else { + MidiSource::ControlChangeValue { + channel, + controller_number: self.midi_message_number.map(|n| n.into()), + custom_character: self.custom_character, + } + } + } + ProgramChangeNumber => MidiSource::ProgramChangeNumber { channel }, + SpecificProgramChange => MidiSource::SpecificProgramChange { + channel, + program_number: self.midi_message_number, + }, + ChannelPressureAmount => MidiSource::ChannelPressureAmount { channel }, + PitchBendChangeValue => MidiSource::PitchBendChangeValue { channel }, + ParameterNumberValue => MidiSource::ParameterNumberValue { + channel, + number: self.parameter_number_message_number, + is_14_bit: self.is_14_bit, + is_registered: self.is_registered, + custom_character: self.custom_character, + }, + ClockTempo => MidiSource::ClockTempo, + ClockTransport => MidiSource::ClockTransport { + message: self.midi_clock_transport_message, + }, + Raw => MidiSource::Raw { + pattern: self.raw_midi_pattern.parse().unwrap_or_default(), + custom_character: self.custom_character, + }, + Script => MidiSource::Script { + script: { + let script = match self.midi_script_kind { + MidiScriptKind::Eel => FlexibleMidiSourceScript::Eel( + EelMidiSourceScript::compile(&self.midi_script).ok()?, + ), + MidiScriptKind::Lua => { + let lua = unsafe { Backbone::main_thread_lua() }; + FlexibleMidiSourceScript::Lua( + LuaMidiSourceScript::compile(lua, &self.midi_script) + .ok()?, + ) + } + }; + CloneAsDefault::new(Some(script)) + }, + }, + Display => MidiSource::Display { + spec: self.display_spec(), + }, + }; + CompoundMappingSource::Midi(midi_source) + } + Virtual => { + let virtual_source = VirtualSource::new(self.create_control_element()); + CompoundMappingSource::Virtual(virtual_source) + } + Osc => { + let osc_source = OscSource::new( + self.osc_address_pattern.clone(), + self.osc_arg_descriptor(), + self.osc_feedback_args + .iter() + .map(|prop_string| prop_string.parse().unwrap_or_default()) + .collect(), + ); + CompoundMappingSource::Osc(osc_source) + } + Reaper => { + use ReaperSourceType::*; + let reaper_source = match self.reaper_source_type { + MidiDeviceChanges => ReaperSource::MidiDeviceChanges, + RealearnUnitStart => ReaperSource::RealearnInstanceStart, + RealearnCompartmentLoaded => ReaperSource::RealearnCompartmentLoaded, + Timer => ReaperSource::Timer(self.create_timer_source()), + RealearnParameter => { + ReaperSource::RealearnParameter(self.create_realearn_parameter_source()) + } + Speech => ReaperSource::Speech(SpeechSource::new()), + }; + CompoundMappingSource::Reaper(reaper_source) + } + Keyboard => CompoundMappingSource::Key(self.create_key_source()?), + StreamDeck => CompoundMappingSource::StreamDeck(self.create_stream_deck_source()), + Never => CompoundMappingSource::Never, + }; + Some(source) + } + + pub fn create_stream_deck_source(&self) -> StreamDeckSource { + StreamDeckSource::new(self.button_index, self.create_stream_deck_button_design()) + } + + pub fn create_stream_deck_button_design(&self) -> StreamDeckButtonDesign { + StreamDeckButtonDesign { + background: match self.button_background_type { + StreamDeckButtonBackgroundType::Color => { + StreamDeckButtonBackground::Color(Default::default()) + } + StreamDeckButtonBackgroundType::Image => { + StreamDeckButtonBackground::Image(StreamDeckButtonImageBackground { + path: self.button_background_image_path.to_string(), + }) + } + }, + foreground: match self.button_foreground_type { + StreamDeckButtonForegroundType::None => StreamDeckButtonForeground::None, + StreamDeckButtonForegroundType::FadingColor => { + StreamDeckButtonForeground::FadingColor(Default::default()) + } + StreamDeckButtonForegroundType::FadingImage => { + StreamDeckButtonForeground::FadingImage(StreamDeckButtonFadingImageForeground { + path: self.button_foreground_image_path.to_string(), + }) + } + StreamDeckButtonForegroundType::SlidingImage => { + StreamDeckButtonForeground::SlidingImage( + StreamDeckButtonSlidingImageForeground { + path: self.button_foreground_image_path.to_string(), + }, + ) + } + StreamDeckButtonForegroundType::FullBar => { + StreamDeckButtonForeground::FullBar(Default::default()) + } + StreamDeckButtonForegroundType::Knob => { + StreamDeckButtonForeground::Knob(Default::default()) + } + }, + static_text: self.button_static_text.clone(), + } + } + + pub fn create_key_source(&self) -> Option { + Some(KeySource::new(self.keystroke?)) + } + + fn create_timer_source(&self) -> TimerSource { + TimerSource::new(Duration::from_millis(self.timer_millis)) + } + + fn create_realearn_parameter_source(&self) -> RealearnParameterSource { + RealearnParameterSource { + parameter_index: self.parameter_index, + } + } + + fn display_spec(&self) -> DisplaySpec { + use DisplayType::*; + match self.display_type { + MackieLcd => DisplaySpec::MackieLcd { + scope: self.mackie_lcd_scope(), + extender_index: 0, + }, + MackieXtLcd => DisplaySpec::MackieLcd { + scope: self.mackie_lcd_scope(), + extender_index: 1, + }, + XTouchMackieLcd => DisplaySpec::XTouchMackieLcd { + scope: self.mackie_lcd_scope(), + extender_index: 0, + }, + XTouchMackieXtLcd => DisplaySpec::XTouchMackieLcd { + scope: self.mackie_lcd_scope(), + extender_index: 1, + }, + MackieSevenSegmentDisplay => DisplaySpec::MackieSevenSegmentDisplay { + scope: self.mackie_7_segment_display_scope(), + }, + SlKeyboardDisplay => DisplaySpec::SlKeyboard { + scope: self.sl_keyboard_display_scope(), + }, + SiniConE24 => DisplaySpec::SiniConE24 { + scope: self.sinicon_e24_scope(), + // TODO-low Not so nice to have runtime state in this descriptor. + last_sent_background_color: Default::default(), + }, + LaunchpadProScrollingText => DisplaySpec::LaunchpadProScrollingText, + } + } + + pub fn mackie_lcd_scope(&self) -> MackieLcdScope { + MackieLcdScope::new(self.display_id, self.line) + } + + pub fn sinicon_e24_scope(&self) -> SiniConE24Scope { + SiniConE24Scope::new(self.display_id, self.line) + } + + pub fn sl_keyboard_display_scope(&self) -> SlKeyboardDisplayScope { + SlKeyboardDisplayScope::new(self.display_id, self.line) + } + + pub fn mackie_7_segment_display_scope(&self) -> MackieSevenSegmentDisplayScope { + self.display_id + .and_then(|id| MackieSevenSegmentDisplayScope::try_from(id as usize).ok()) + .unwrap_or_default() + } + + pub fn simple_source(&self) -> Option { + use playtime_api::runtime::*; + use MidiSourceType::*; + if self.category == SourceCategory::Never { + return None; + } + let s = match self.midi_source_type { + NoteVelocity => { + if let (Some(channel), Some(number)) = (self.channel, self.midi_message_number) { + let s = NoteSource { + channel: channel.get(), + number: number.get(), + }; + SimpleSource::Note(s) + } else { + SimpleSource::MoreComplicated + } + } + _ => SimpleSource::MoreComplicated, + }; + Some(s) + } + + fn osc_arg_descriptor(&self) -> Option { + let arg_index = self.osc_arg_index?; + let arg_desc = OscArgDescriptor::new( + arg_index, + self.osc_arg_type_tag, + self.osc_arg_is_relative, + self.osc_arg_value_range, + ); + Some(arg_desc) + } + + pub fn supports_type(&self) -> bool { + use SourceCategory::*; + matches!(self.category, Midi | Virtual | Reaper) + } + + pub fn supports_channel(&self) -> bool { + if !self.is_midi() { + return false; + } + self.midi_source_type.supports_channel() + } + + pub fn supports_osc_arg_value_range(&self) -> bool { + self.category == SourceCategory::Osc + && self.osc_arg_index.is_some() + && self.osc_arg_type_tag.supports_value_range() + } + + pub fn display_count(&self) -> u8 { + self.display_type.display_count() + } + + fn is_midi(&self) -> bool { + self.category == SourceCategory::Midi + } + + fn channel_label(&self) -> Cow { + if self.supports_channel() { + match self.channel { + None => "Any channel".into(), + Some(ch) => format!("Channel {}", ch.get() + 1).into(), + } + } else { + "".into() + } + } + + fn note_label(&self) -> Cow { + match self.midi_message_number { + None => "Any note".into(), + Some(n) => format!("Note number {}", n.get()).into(), + } + } + + fn program_label(&self) -> Cow { + match self.midi_message_number { + None => "Any program".into(), + Some(n) => format!("Program {}", n.get()).into(), + } + } + + pub fn create_control_element(&self) -> VirtualControlElement { + VirtualControlElement::new(self.control_element_id, self.control_element_character) + } +} + +impl Display for SourceModel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use SourceCategory::*; + let lines: Vec> = match self.category { + Midi => match self.midi_source_type { + t @ MidiSourceType::NoteVelocity => { + vec![ + t.to_string().into(), + self.channel_label(), + self.note_label(), + ] + } + MidiSourceType::ParameterNumberValue => { + let line_1 = match self.is_registered { + None => MidiSourceType::ParameterNumberValue.to_string().into(), + Some(is_registered) => { + if is_registered { + "RPN".into() + } else { + "NRPN".into() + } + } + }; + let line_3 = match self.parameter_number_message_number { + None => "Any number".into(), + Some(n) => format!("Number {}", n.get()).into(), + }; + vec![line_1, self.channel_label(), line_3] + } + MidiSourceType::PolyphonicKeyPressureAmount => { + vec![ + "Poly after touch".into(), + self.channel_label(), + self.note_label(), + ] + } + t @ MidiSourceType::SpecificProgramChange => { + vec![ + t.to_string().into(), + self.channel_label(), + self.program_label(), + ] + } + MidiSourceType::ClockTempo => vec!["MIDI clock".into(), "Tempo".into()], + MidiSourceType::ClockTransport => { + vec![ + "MIDI clock".into(), + self.midi_clock_transport_message.to_string().into(), + ] + } + t @ MidiSourceType::ControlChangeValue => { + let line_3 = match self.midi_message_number { + None => "Any CC".into(), + Some(n) => format!("CC number {}", n.get()).into(), + }; + use MidiSourceType::*; + let line_4 = match self.midi_source_type { + ControlChangeValue if self.is_14_bit == Some(false) => { + use SourceCharacter::*; + let label = match self.custom_character { + RangeElement => "Range element", + MomentaryButton => "Momentary button", + Encoder1 => "Encoder 1", + Encoder2 => "Encoder 2", + Encoder3 => "Encoder 3", + ToggleButton => "Toggle-only button", + }; + label.into() + } + _ => "".into(), + }; + vec![t.to_string().into(), self.channel_label(), line_3, line_4] + } + t @ MidiSourceType::Display => vec![t.to_string().into()], + t => vec![t.to_string().into(), self.channel_label()], + }, + Virtual => vec![ + "Virtual".into(), + self.create_control_element().to_string().into(), + ], + Osc => vec!["OSC".into(), (&self.osc_address_pattern).into()], + Reaper => { + let type_label = self.reaper_source_type.to_string().into(); + match self.reaper_source_type { + ReaperSourceType::Timer => { + vec![type_label, format!("{} ms", self.timer_millis).into()] + } + ReaperSourceType::RealearnParameter => { + vec![ + type_label, + format!("Parameter #{}", self.parameter_index.get() + 1).into(), + ] + } + _ => { + vec![type_label] + } + } + } + Never => vec!["None".into()], + Keyboard => { + let text = self + .create_key_source() + .map(|s| Cow::Owned(s.to_string())) + .unwrap_or_else(|| Cow::Borrowed(KEY_UNDEFINED_LABEL)); + vec![text] + } + StreamDeck => { + let text = self.create_stream_deck_source().to_string(); + vec![Cow::Owned(text)] + } + }; + let non_empty_lines: Vec<_> = lines.into_iter().filter(|l| !l.is_empty()).collect(); + write!(f, "{}", non_empty_lines.join("\n")) + } +} + +pub const KEY_UNDEFINED_LABEL: &str = ""; + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum SourceCategory { + #[serde(rename = "never")] + #[display(fmt = "None")] + Never, + #[default] + #[serde(rename = "midi")] + #[display(fmt = "MIDI")] + Midi, + #[serde(rename = "osc")] + #[display(fmt = "OSC")] + Osc, + #[serde(rename = "keyboard")] + #[display(fmt = "Keyboard")] + Keyboard, + #[serde(rename = "reaper")] + #[display(fmt = "REAPER")] + Reaper, + #[serde(rename = "stream-deck")] + #[display(fmt = "Stream Deck")] + StreamDeck, + #[serde(rename = "virtual")] + #[display(fmt = "Virtual")] + Virtual, +} + +impl SourceCategory { + pub fn default_for(compartment: CompartmentKind) -> Self { + use SourceCategory::*; + match compartment { + CompartmentKind::Controller => Midi, + CompartmentKind::Main => Midi, + } + } + + pub fn is_allowed_in(self, compartment: CompartmentKind) -> bool { + use SourceCategory::*; + match compartment { + CompartmentKind::Controller => match self { + Never => true, + Midi => true, + Osc => true, + Reaper => true, + Keyboard => true, + StreamDeck => true, + Virtual => false, + }, + CompartmentKind::Main => true, + } + } +} + +/// Type of a MIDI source +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + Serialize_repr, + Deserialize_repr, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum MidiSourceType { + #[default] + #[display(fmt = "CC value")] + ControlChangeValue = 0, + #[display(fmt = "Note velocity")] + NoteVelocity = 1, + #[display(fmt = "Note number")] + NoteKeyNumber = 2, + #[display(fmt = "Pitch wheel")] + PitchBendChangeValue = 3, + #[display(fmt = "Channel after touch")] + ChannelPressureAmount = 4, + #[display(fmt = "Program change number")] + ProgramChangeNumber = 5, + #[display(fmt = "(N)RPN value")] + ParameterNumberValue = 6, + #[display(fmt = "Polyphonic after touch")] + PolyphonicKeyPressureAmount = 7, + #[display(fmt = "MIDI clock tempo (experimental)")] + ClockTempo = 8, + #[display(fmt = "MIDI clock transport")] + ClockTransport = 9, + #[display(fmt = "Raw MIDI / SysEx")] + Raw = 10, + #[display(fmt = "MIDI script (feedback only)")] + Script = 11, + #[display(fmt = "Display (feedback only)")] + Display = 12, + #[display(fmt = "Specific program change")] + SpecificProgramChange = 13, +} + +impl MidiSourceType { + pub fn from_source(source: &MidiSource) -> MidiSourceType { + use helgoboss_learn::MidiSource::*; + match source { + NoteVelocity { .. } => MidiSourceType::NoteVelocity, + NoteKeyNumber { .. } => MidiSourceType::NoteKeyNumber, + PolyphonicKeyPressureAmount { .. } => MidiSourceType::PolyphonicKeyPressureAmount, + ControlChangeValue { .. } => MidiSourceType::ControlChangeValue, + ProgramChangeNumber { .. } => MidiSourceType::ProgramChangeNumber, + SpecificProgramChange { .. } => MidiSourceType::SpecificProgramChange, + ChannelPressureAmount { .. } => MidiSourceType::ChannelPressureAmount, + PitchBendChangeValue { .. } => MidiSourceType::PitchBendChangeValue, + ControlChange14BitValue { .. } => MidiSourceType::ControlChangeValue, + ParameterNumberValue { .. } => MidiSourceType::ParameterNumberValue, + ClockTempo => MidiSourceType::ClockTempo, + ClockTransport { .. } => MidiSourceType::ClockTransport, + Raw { .. } => MidiSourceType::Raw, + Script { .. } => MidiSourceType::Script, + Display { .. } => MidiSourceType::Display, + } + } + + pub fn number_label(self) -> &'static str { + use MidiSourceType::*; + match self { + ControlChangeValue => "CC", + NoteVelocity | PolyphonicKeyPressureAmount => "Note", + ParameterNumberValue => "Number", + SpecificProgramChange => "Program", + _ => "", + } + } + + pub fn supports_channel(self) -> bool { + use MidiSourceType::*; + matches!( + self, + ChannelPressureAmount + | ControlChangeValue + | NoteVelocity + | PolyphonicKeyPressureAmount + | NoteKeyNumber + | ParameterNumberValue + | PitchBendChangeValue + | ProgramChangeNumber + | SpecificProgramChange + ) + } + + pub fn supports_midi_message_number(self) -> bool { + use MidiSourceType::*; + matches!( + self, + ControlChangeValue | NoteVelocity | PolyphonicKeyPressureAmount | SpecificProgramChange + ) + } + + pub fn supports_parameter_number_message_number(self) -> bool { + self.supports_parameter_number_message_props() + } + + pub fn supports_14_bit(self) -> bool { + use MidiSourceType::*; + matches!(self, ControlChangeValue | ParameterNumberValue) + } + + pub fn supports_is_registered(self) -> bool { + self.supports_parameter_number_message_props() + } + + pub fn supports_custom_character(self) -> bool { + use MidiSourceType::*; + matches!(self, ControlChangeValue | ParameterNumberValue | Raw) + } + + fn supports_parameter_number_message_props(self) -> bool { + self == MidiSourceType::ParameterNumberValue + } + + pub fn supports_control(self) -> bool { + use MidiSourceType::*; + !matches!(self, Script | Display) + } + + pub fn supports_feedback(self) -> bool { + use MidiSourceType::*; + !matches!(self, ClockTempo | ClockTransport) + } +} + +/// Type of a REAPER source +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum ReaperSourceType { + #[default] + #[serde(rename = "midi-device-changes")] + #[display(fmt = "MIDI device changes")] + MidiDeviceChanges, + #[serde(rename = "realearn-instance-start")] + #[display(fmt = "ReaLearn unit start")] + RealearnUnitStart, + #[serde(rename = "realearn-compartment-loaded")] + #[display(fmt = "ReaLearn compartment loaded")] + RealearnCompartmentLoaded, + #[serde(rename = "timer")] + #[display(fmt = "Timer")] + Timer, + #[serde(rename = "realearn-parameter")] + #[display(fmt = "ReaLearn parameter")] + RealearnParameter, + #[serde(rename = "speech")] + #[display(fmt = "Speech (feedback only, no Linux)")] + Speech, +} + +impl ReaperSourceType { + pub fn from_source(source: &ReaperSource) -> Self { + use ReaperSource::*; + match source { + MidiDeviceChanges => Self::MidiDeviceChanges, + RealearnInstanceStart => Self::RealearnUnitStart, + RealearnCompartmentLoaded => Self::RealearnCompartmentLoaded, + Timer(_) => Self::Timer, + RealearnParameter(_) => Self::RealearnParameter, + Speech(_) => Self::Speech, + } + } + + pub fn supports_control(self) -> bool { + use ReaperSourceType::*; + match self { + MidiDeviceChanges + | RealearnUnitStart + | RealearnCompartmentLoaded + | Timer + | RealearnParameter => true, + Speech => false, + } + } + + pub fn supports_feedback(self) -> bool { + use ReaperSourceType::*; + match self { + MidiDeviceChanges + | RealearnUnitStart + | RealearnCompartmentLoaded + | Timer + | RealearnParameter => false, + Speech => true, + } + } +} + +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Default, EnumIter, TryFromPrimitive, IntoPrimitive, Display, +)] +#[repr(usize)] +pub enum StreamDeckButtonBackgroundType { + #[default] + #[display(fmt = "Color")] + Color, + #[display(fmt = "Image")] + Image, +} + +impl StreamDeckButtonBackgroundType { + pub fn wants_image(&self) -> bool { + *self == Self::Image + } +} + +impl From<&StreamDeckButtonBackground> for StreamDeckButtonBackgroundType { + fn from(value: &StreamDeckButtonBackground) -> Self { + match value { + StreamDeckButtonBackground::Color(_) => Self::Color, + StreamDeckButtonBackground::Image(_) => Self::Image, + } + } +} + +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Default, EnumIter, TryFromPrimitive, IntoPrimitive, Display, +)] +#[repr(usize)] +pub enum StreamDeckButtonForegroundType { + #[default] + #[display(fmt = "None")] + None, + #[display(fmt = "Color fade")] + FadingColor, + #[display(fmt = "Image fade")] + FadingImage, + #[display(fmt = "Image slide")] + SlidingImage, + #[display(fmt = "Full bar")] + FullBar, + #[display(fmt = "Knob")] + Knob, +} + +impl StreamDeckButtonForegroundType { + pub fn wants_image(&self) -> bool { + use StreamDeckButtonForegroundType::*; + matches!(self, FadingImage | SlidingImage) + } +} + +impl From<&StreamDeckButtonForeground> for StreamDeckButtonForegroundType { + fn from(value: &StreamDeckButtonForeground) -> Self { + match value { + StreamDeckButtonForeground::None => Self::None, + StreamDeckButtonForeground::FadingColor(_) => Self::FadingColor, + StreamDeckButtonForeground::FadingImage(_) => Self::FadingImage, + StreamDeckButtonForeground::SlidingImage(_) => Self::SlidingImage, + StreamDeckButtonForeground::Knob(_) => Self::Knob, + StreamDeckButtonForeground::FullBar(_) => Self::FullBar, + } + } +} + +pub fn parse_osc_feedback_args(text: &str) -> Vec { + text.split_whitespace().map(|s| s.to_owned()).collect() +} + +pub fn format_osc_feedback_args(args: &[String]) -> String { + itertools::join(args.iter(), " ") +} + +fn relativize_against_resource_dir(path: Utf8PathBuf) -> Utf8PathBuf { + let reaper_resource_path = Reaper::get().resource_path(); + if let Some(relative_path) = pathdiff::diff_paths(&path, reaper_resource_path) { + Utf8PathBuf::try_from(relative_path).unwrap() + } else { + path + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_source() { + // Given + let m = SourceModel::new(); + // When + let s = m.create_source(); + // Then + assert_eq!(s, CompoundMappingSource::Never); + } +} diff --git a/plugin-reaper-realearn/main/src/application/target_model.rs b/plugin-reaper-realearn/main/src/application/target_model.rs new file mode 100644 index 0000000..0a1e5e1 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/target_model.rs @@ -0,0 +1,4884 @@ +use base::default_util::is_default; +use derive_more::Display; +use helgoboss_learn::{ + AbsoluteValue, ControlType, Interval, OscArgDescriptor, OscTypeTag, Target, + DEFAULT_OSC_ARG_VALUE_RANGE, +}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use reaper_high::{ + Action, BookmarkType, Fx, FxChain, FxParameter, Guid, Project, Track, TrackRoute, + TrackRoutePartner, +}; + +use serde::{Deserialize, Serialize}; + +use crate::application::{ + build_action_from_smart_command_name, build_smart_command_name_from_action, Affected, Change, + GetProcessingRelevance, ProcessingRelevance, UnitModel, +}; +use crate::domain::{ + find_bookmark, get_fx_name, get_fx_params, get_non_present_virtual_route_label, + get_non_present_virtual_track_label, get_track_routes, ActionInvocationType, AnyOnParameter, + CompartmentKind, CompartmentParamIndex, CompoundMappingTarget, Exclusivity, + ExpressionEvaluator, ExtendedProcessorContext, FeedbackResolution, FxDescriptor, FxDisplayType, + FxParameterDescriptor, GroupId, MappingId, MappingKey, MappingRef, MappingSnapshotId, + MouseActionType, OscDeviceId, PotFilterItemsTargetSettings, ProcessorContext, + QualifiedMappingId, RealearnTarget, ReaperTarget, ReaperTargetType, SeekOptions, + SendMidiDestinationType, SoloBehavior, Tag, TagScope, TouchedRouteParameterType, + TouchedTrackParameterType, TrackDescriptor, TrackExclusivity, TrackGangBehavior, + TrackRouteDescriptor, TrackRouteSelector, TrackRouteType, TransportAction, + UnresolvedActionTarget, UnresolvedAllTrackFxEnableTarget, UnresolvedAnyOnTarget, + UnresolvedAutomationModeOverrideTarget, UnresolvedBrowseFxsTarget, UnresolvedBrowseGroupTarget, + UnresolvedBrowsePotFilterItemsTarget, UnresolvedBrowsePotPresetsTarget, + UnresolvedBrowseTracksTarget, UnresolvedCompartmentParameterValueTarget, + UnresolvedCompoundMappingTarget, UnresolvedDummyTarget, UnresolvedEnableInstancesTarget, + UnresolvedEnableMappingsTarget, UnresolvedEnableUnitsTarget, UnresolvedFxEnableTarget, + UnresolvedFxOnlineTarget, UnresolvedFxOpenTarget, UnresolvedFxParameterTarget, + UnresolvedFxParameterTouchStateTarget, UnresolvedFxPresetTarget, UnresolvedFxToolTarget, + UnresolvedGoToBookmarkTarget, UnresolvedLastTouchedTarget, UnresolvedLoadFxSnapshotTarget, + UnresolvedLoadMappingSnapshotTarget, UnresolvedLoadPotPresetTarget, UnresolvedMidiSendTarget, + UnresolvedModifyMappingTarget, UnresolvedMouseTarget, UnresolvedOscSendTarget, + UnresolvedPlayrateTarget, UnresolvedPreviewPotPresetTarget, UnresolvedReaperTarget, + UnresolvedRouteAutomationModeTarget, UnresolvedRouteMonoTarget, UnresolvedRouteMuteTarget, + UnresolvedRoutePanTarget, UnresolvedRoutePhaseTarget, UnresolvedRouteTouchStateTarget, + UnresolvedRouteVolumeTarget, UnresolvedSeekTarget, UnresolvedStreamDeckBrightnessTarget, + UnresolvedTakeMappingSnapshotTarget, UnresolvedTempoTarget, UnresolvedTrackArmTarget, + UnresolvedTrackAutomationModeTarget, UnresolvedTrackMonitoringModeTarget, + UnresolvedTrackMuteTarget, UnresolvedTrackPanTarget, UnresolvedTrackParentSendTarget, + UnresolvedTrackPeakTarget, UnresolvedTrackPhaseTarget, UnresolvedTrackSelectionTarget, + UnresolvedTrackShowTarget, UnresolvedTrackSoloTarget, UnresolvedTrackToolTarget, + UnresolvedTrackTouchStateTarget, UnresolvedTrackVolumeTarget, UnresolvedTrackWidthTarget, + UnresolvedTransportTarget, VirtualChainFx, VirtualControlElement, VirtualControlElementId, + VirtualFx, VirtualFxParameter, VirtualMappingSnapshotIdForLoad, + VirtualMappingSnapshotIdForTake, VirtualTarget, VirtualTrack, VirtualTrackRoute, +}; + +use crate::domain::{VirtualPlaytimeColumn, VirtualPlaytimeRow, VirtualPlaytimeSlot}; +use serde_repr::*; +use std::borrow::Cow; +use std::error::Error; + +use crate::domain::ui_util::format_tags_as_csv; +use base::hash_util::NonCryptoHashSet; +use helgobox_api::persistence::{ + ActionScope, Axis, BrowseTracksMode, ClipColumnTrackContext, FxChainDescriptor, + FxDescriptorCommons, FxToolAction, InputDeviceMidiDestination, InstanceTagKind, + LearnTargetMappingModification, LearnableTargetKind, MappingModification, + MappingSnapshotDescForLoad, MappingSnapshotDescForTake, MonitoringMode, MouseAction, + MouseButton, PlaytimeColumnAction, PlaytimeColumnDescriptor, PlaytimeMatrixAction, + PlaytimeRowAction, PlaytimeRowDescriptor, PlaytimeSlotDescriptor, PlaytimeSlotManagementAction, + PlaytimeSlotTransportAction, PotFilterKind, SeekBehavior, SendMidiDestination, + SetTargetToLastTouchedMappingModification, TargetTouchCause, TrackDescriptorCommons, + TrackFxChain, TrackScope, TrackToolAction, VirtualControlElementCharacter, +}; +use playtime_api::persistence::ColumnAddress; +use reaper_medium::{ + AutomationMode, BookmarkId, GlobalAutomationModeOverride, InputMonitoringMode, + MidiInputDeviceId, SectionId, TrackArea, TrackLocation, TrackSendDirection, +}; +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::rc::Rc; +use strum::{EnumIter, IntoEnumIterator}; +use wildmatch::WildMatch; + +#[allow(clippy::enum_variant_names)] +pub enum TargetCommand { + SetCategory(TargetCategory), + SetUnit(TargetUnit), + SetControlElementCharacter(VirtualControlElementCharacter), + SetControlElementId(VirtualControlElementId), + SetLearnable(bool), + SetTargetType(ReaperTargetType), + SetActionScope(ActionScope), + SetSmartCommandName(Option), + SetActionInvocationType(ActionInvocationType), + SetWithTrack(bool), + SetTrackName(String), + SetTrackIndex(u32), + SetTrackExpression(String), + SetEnableOnlyIfTrackSelected(bool), + SetFxIsInputFx(bool), + SetFxName(String), + SetFxIndex(u32), + SetFxExpression(String), + SetEnableOnlyIfFxHasFocus(bool), + SetParamType(VirtualFxParameterType), + SetParamIndex(u32), + SetParamName(String), + SetParamExpression(String), + SetRetrigger(bool), + SetRealTime(bool), + SetRouteSelectorType(TrackRouteSelectorType), + SetRouteType(TrackRouteType), + SetRouteId(Option), + SetRouteIndex(u32), + SetRouteName(String), + SetRouteExpression(String), + SetSeekBehavior(SeekBehavior), + SetSoloBehavior(SoloBehavior), + SetTrackExclusivity(TrackExclusivity), + SetTrackToolAction(TrackToolAction), + SetGangBehavior(TrackGangBehavior), + SetBrowseTracksMode(BrowseTracksMode), + SetFxToolAction(FxToolAction), + SetTransportAction(TransportAction), + SetAnyOnParameter(AnyOnParameter), + SetFxSnapshot(Option), + SetTouchedTrackParameterType(TouchedTrackParameterType), + SetTouchedRouteParameterType(TouchedRouteParameterType), + SetBookmarkRef(u32), + SetBookmarkType(BookmarkType), + SetBookmarkAnchorType(BookmarkAnchorType), + SetUseTimeSelection(bool), + SetUseLoopPoints(bool), + SetUseRegions(bool), + SetUseProject(bool), + SetMoveView(bool), + SetSeekPlay(bool), + SetFeedbackResolution(FeedbackResolution), + SetTrackArea(RealearnTrackArea), + SetAutomationMode(RealearnAutomationMode), + SetMonitoringMode(MonitoringMode), + SetAutomationModeOverrideType(AutomationModeOverrideType), + SetFxDisplayType(FxDisplayType), + SetScrollArrangeView(bool), + SetScrollMixer(bool), + SetRawMidiPattern(String), + SetSendMidiDestinationType(SendMidiDestinationType), + SetMidiInputDevice(Option), + SetOscAddressPattern(String), + SetOscArgIndex(Option), + SetOscArgTypeTag(OscTypeTag), + SetOscArgValueRange(Interval), + SetOscDevId(Option), + SetMouseActionType(MouseActionType), + SetAxis(Axis), + SetMouseButton(MouseButton), + SetPlaytimeSlot(PlaytimeSlotDescriptor), + SetPlaytimeColumn(PlaytimeColumnDescriptor), + SetPlaytimeRow(PlaytimeRowDescriptor), + SetPlaytimeSlotManagementAction(PlaytimeSlotManagementAction), + SetPlaytimeSlotTransportAction(PlaytimeSlotTransportAction), + SetPlaytimeMatrixAction(PlaytimeMatrixAction), + SetPlaytimeColumnAction(PlaytimeColumnAction), + SetPlaytimeRowAction(PlaytimeRowAction), + SetStopColumnIfSlotEmpty(bool), + SetPollForFeedback(bool), + SetInstanceTagKind(InstanceTagKind), + SetTags(Vec), + SetExclusivity(Exclusivity), + SetGroupId(GroupId), + SetActiveMappingsOnly(bool), + SetMappingSnapshotTypeForLoad(MappingSnapshotTypeForLoad), + SetMappingSnapshotTypeForTake(MappingSnapshotTypeForTake), + SetMappingSnapshotId(Option), + SetMappingSnapshotDefaultValue(Option), + SetPotFilterItemKind(PotFilterKind), + SetMappingModificationKind(MappingModificationKind), + SetMappingRef(MappingRefModel), + SetLearnableTargetKinds(NonCryptoHashSet), + SetTouchCause(TargetTouchCause), +} + +#[derive(Eq, PartialEq)] +pub enum TargetProp { + Category, + Unit, + ControlElementType, + ControlElementId, + Learnable, + TargetType, + ActionScope, + SmartCommandName, + ActionInvocationType, + WithTrack, + TrackType, + TrackId, + TrackName, + TrackIndex, + TrackExpression, + EnableOnlyIfTrackSelected, + FxType, + FxIsInputFx, + FxId, + FxName, + FxIndex, + FxExpression, + EnableOnlyIfFxHasFocus, + ParamType, + ParamIndex, + ParamName, + ParamExpression, + Retrigger, + RealTime, + RouteSelectorType, + RouteType, + RouteId, + RouteIndex, + RouteName, + RouteExpression, + SoloBehavior, + SeekBehavior, + TrackExclusivity, + TrackToolAction, + GangBehavior, + BrowseTracksMode, + FxToolAction, + TransportAction, + AnyOnParameter, + FxSnapshot, + TouchedTrackParameterType, + TouchedRouteParameterType, + BookmarkRef, + BookmarkType, + BookmarkAnchorType, + UseTimeSelection, + UseLoopPoints, + UseRegions, + UseProject, + MoveView, + SeekPlay, + FeedbackResolution, + TrackArea, + AutomationMode, + MonitoringMode, + AutomationModeOverrideType, + FxDisplayType, + ScrollArrangeView, + ScrollMixer, + RawMidiPattern, + SendMidiDestination, + MidiInputDevice, + OscAddressPattern, + OscArgIndex, + OscArgTypeTag, + OscArgValueRange, + OscDevId, + MouseActionType, + Axis, + MouseButton, + PlaytimeSlot, + PlaytimeColumn, + PlaytimeRow, + PlaytimeSlotManagementAction, + PlaytimeSlotTransportAction, + PlaytimeMatrixAction, + PlaytimeColumnAction, + PlaytimeRowAction, + StopColumnIfSlotEmpty, + PollForFeedback, + InstanceTagKind, + Tags, + Exclusivity, + GroupId, + ActiveMappingsOnly, + MappingSnapshotTypeForLoad, + MappingSnapshotTypeForTake, + MappingSnapshotId, + MappingSnapshotDefaultValue, + PotFilterItemKind, + MappingModificationKind, + MappingRef, + IncludedTargets, + TouchCause, +} + +impl GetProcessingRelevance for TargetProp { + fn processing_relevance(&self) -> Option { + // At the moment, all target aspects are relevant for processing. + Some(ProcessingRelevance::ProcessingRelevant) + } +} + +impl Change<'_> for TargetModel { + type Command = TargetCommand; + type Prop = TargetProp; + + fn change(&mut self, cmd: Self::Command) -> Option> { + use Affected::*; + use TargetCommand as C; + use TargetProp as P; + let affected = match cmd { + C::SetCategory(v) => { + self.category = v; + One(P::Category) + } + C::SetUnit(v) => { + self.unit = v; + One(P::Unit) + } + C::SetControlElementCharacter(v) => { + self.control_element_character = v; + One(P::ControlElementType) + } + C::SetControlElementId(v) => { + self.control_element_id = v; + One(P::ControlElementId) + } + C::SetLearnable(v) => { + self.learnable = v; + One(P::Learnable) + } + C::SetTargetType(v) => { + self.r#type = v; + One(P::TargetType) + } + C::SetActionScope(v) => { + self.action_scope = v; + One(P::ActionScope) + } + C::SetSmartCommandName(v) => { + self.smart_command_name = v; + One(P::SmartCommandName) + } + C::SetActionInvocationType(v) => { + self.action_invocation_type = v; + One(P::ActionInvocationType) + } + C::SetWithTrack(v) => { + self.with_track = v; + One(P::WithTrack) + } + C::SetTrackName(v) => { + self.track_name = v; + One(P::TrackName) + } + C::SetTrackIndex(v) => { + self.track_index = v; + One(P::TrackIndex) + } + C::SetTrackExpression(v) => { + self.track_expression = v; + One(P::TrackExpression) + } + C::SetEnableOnlyIfTrackSelected(v) => { + self.enable_only_if_track_selected = v; + One(P::EnableOnlyIfTrackSelected) + } + C::SetFxIsInputFx(v) => { + self.fx_is_input_fx = v; + One(P::FxIsInputFx) + } + C::SetFxName(v) => { + self.fx_name = v; + One(P::FxName) + } + C::SetFxIndex(v) => { + self.fx_index = v; + One(P::FxIndex) + } + C::SetFxExpression(v) => { + self.fx_expression = v; + One(P::FxExpression) + } + C::SetEnableOnlyIfFxHasFocus(v) => { + self.enable_only_if_fx_has_focus = v; + One(P::EnableOnlyIfFxHasFocus) + } + C::SetParamType(v) => { + self.param_type = v; + One(P::ParamType) + } + C::SetParamIndex(v) => { + self.param_index = v; + One(P::ParamIndex) + } + C::SetParamName(v) => { + self.param_name = v; + One(P::ParamName) + } + C::SetParamExpression(v) => { + self.param_expression = v; + One(P::ParamExpression) + } + C::SetRetrigger(v) => { + self.retrigger = v; + One(P::Retrigger) + } + C::SetRealTime(v) => { + self.real_time = v; + One(P::RealTime) + } + C::SetRouteSelectorType(v) => { + self.route_selector_type = v; + One(P::RouteSelectorType) + } + C::SetRouteType(v) => { + self.route_type = v; + One(P::RouteType) + } + C::SetRouteId(v) => { + self.route_id = v; + One(P::RouteId) + } + C::SetRouteIndex(v) => { + self.route_index = v; + One(P::RouteIndex) + } + C::SetRouteName(v) => { + self.route_name = v; + One(P::RouteName) + } + C::SetRouteExpression(v) => { + self.route_expression = v; + One(P::RouteExpression) + } + C::SetSoloBehavior(v) => { + self.solo_behavior = v; + One(P::SoloBehavior) + } + C::SetSeekBehavior(v) => { + self.seek_behavior = v; + One(P::SeekBehavior) + } + C::SetTrackExclusivity(v) => { + self.track_exclusivity = v; + One(P::TrackExclusivity) + } + C::SetTrackToolAction(v) => { + self.track_tool_action = v; + One(P::TrackToolAction) + } + C::SetGangBehavior(v) => { + self.gang_behavior = v; + One(P::GangBehavior) + } + C::SetBrowseTracksMode(v) => { + self.browse_tracks_mode = v; + One(P::BrowseTracksMode) + } + C::SetFxToolAction(v) => { + self.fx_tool_action = v; + One(P::FxToolAction) + } + C::SetTransportAction(v) => { + self.transport_action = v; + One(P::TransportAction) + } + C::SetAnyOnParameter(v) => { + self.any_on_parameter = v; + One(P::AnyOnParameter) + } + C::SetFxSnapshot(v) => { + self.fx_snapshot = v; + One(P::FxSnapshot) + } + C::SetTouchedTrackParameterType(v) => { + self.touched_track_parameter_type = v; + One(P::TouchedTrackParameterType) + } + C::SetTouchedRouteParameterType(v) => { + self.touched_route_parameter_type = v; + One(P::TouchedRouteParameterType) + } + C::SetBookmarkRef(v) => { + self.bookmark_ref = v; + One(P::BookmarkRef) + } + C::SetBookmarkType(v) => { + self.bookmark_type = v; + One(P::BookmarkType) + } + C::SetBookmarkAnchorType(v) => { + self.bookmark_anchor_type = v; + One(P::BookmarkAnchorType) + } + C::SetUseTimeSelection(v) => { + self.use_time_selection = v; + One(P::UseTimeSelection) + } + C::SetUseLoopPoints(v) => { + self.use_loop_points = v; + One(P::UseLoopPoints) + } + C::SetUseRegions(v) => { + self.use_regions = v; + One(P::UseRegions) + } + C::SetUseProject(v) => { + self.use_project = v; + One(P::UseProject) + } + C::SetMoveView(v) => { + self.move_view = v; + One(P::MoveView) + } + C::SetSeekPlay(v) => { + self.seek_play = v; + One(P::SeekPlay) + } + C::SetFeedbackResolution(v) => { + self.feedback_resolution = v; + One(P::FeedbackResolution) + } + C::SetTrackArea(v) => { + self.track_area = v; + One(P::TrackArea) + } + C::SetAutomationMode(v) => { + self.automation_mode = v; + One(P::AutomationMode) + } + C::SetMonitoringMode(v) => { + self.monitoring_mode = v; + One(P::MonitoringMode) + } + C::SetAutomationModeOverrideType(v) => { + self.automation_mode_override_type = v; + One(P::AutomationModeOverrideType) + } + C::SetFxDisplayType(v) => { + self.fx_display_type = v; + One(P::FxDisplayType) + } + C::SetScrollArrangeView(v) => { + self.scroll_arrange_view = v; + One(P::ScrollArrangeView) + } + C::SetScrollMixer(v) => { + self.scroll_mixer = v; + One(P::ScrollMixer) + } + C::SetRawMidiPattern(v) => { + self.raw_midi_pattern = v; + One(P::RawMidiPattern) + } + C::SetSendMidiDestinationType(v) => { + self.send_midi_destination_type = v; + One(P::SendMidiDestination) + } + C::SetMidiInputDevice(v) => { + self.midi_input_device = v; + One(P::MidiInputDevice) + } + C::SetOscAddressPattern(v) => { + self.osc_address_pattern = v; + One(P::OscAddressPattern) + } + C::SetOscArgIndex(v) => { + self.osc_arg_index = v; + One(P::OscArgIndex) + } + C::SetOscArgTypeTag(v) => { + self.osc_arg_type_tag = v; + One(P::OscArgTypeTag) + } + C::SetOscArgValueRange(v) => { + self.osc_arg_value_range = v; + One(P::OscArgValueRange) + } + C::SetOscDevId(v) => { + self.osc_dev_id = v; + One(P::OscDevId) + } + C::SetMouseActionType(v) => { + self.mouse_action_type = v; + One(P::MouseActionType) + } + C::SetAxis(v) => { + self.axis = v; + One(P::Axis) + } + C::SetMouseButton(v) => { + self.mouse_button = v; + One(P::MouseButton) + } + C::SetPollForFeedback(v) => { + self.poll_for_feedback = v; + One(P::PollForFeedback) + } + C::SetInstanceTagKind(v) => { + self.instance_tag_kind = v; + One(P::InstanceTagKind) + } + C::SetTags(v) => { + self.tags = v; + One(P::Tags) + } + C::SetExclusivity(v) => { + self.exclusivity = v; + One(P::Exclusivity) + } + C::SetGroupId(v) => { + self.group_id = v; + One(P::GroupId) + } + C::SetActiveMappingsOnly(v) => { + self.active_mappings_only = v; + One(P::ActiveMappingsOnly) + } + C::SetMappingSnapshotTypeForLoad(v) => { + self.mapping_snapshot_type_for_load = v; + One(P::MappingSnapshotTypeForLoad) + } + C::SetMappingSnapshotTypeForTake(v) => { + self.mapping_snapshot_type_for_take = v; + One(P::MappingSnapshotTypeForTake) + } + C::SetMappingSnapshotId(v) => { + self.mapping_snapshot_id = v; + One(P::MappingSnapshotId) + } + C::SetMappingSnapshotDefaultValue(v) => { + self.mapping_snapshot_default_value = v; + One(P::MappingSnapshotDefaultValue) + } + C::SetPlaytimeSlot(s) => { + self.playtime_slot = s; + One(P::PlaytimeSlot) + } + C::SetPlaytimeColumn(c) => { + self.playtime_column = c; + One(P::PlaytimeColumn) + } + C::SetPlaytimeRow(r) => { + self.playtime_row = r; + One(P::PlaytimeRow) + } + C::SetPlaytimeSlotManagementAction(v) => { + self.playtime_slot_management_action = v; + One(P::PlaytimeSlotManagementAction) + } + C::SetPlaytimeSlotTransportAction(v) => { + self.playtime_slot_transport_action = v; + One(P::PlaytimeSlotTransportAction) + } + C::SetPlaytimeMatrixAction(v) => { + self.playtime_matrix_action = v; + One(P::PlaytimeMatrixAction) + } + C::SetPlaytimeColumnAction(v) => { + self.playtime_column_action = v; + One(P::PlaytimeColumnAction) + } + C::SetPlaytimeRowAction(v) => { + self.playtime_row_action = v; + One(P::PlaytimeRowAction) + } + C::SetStopColumnIfSlotEmpty(v) => { + self.stop_column_if_slot_empty = v; + One(P::StopColumnIfSlotEmpty) + } + C::SetPotFilterItemKind(v) => { + self.pot_filter_item_kind = v; + One(P::PotFilterItemKind) + } + C::SetMappingModificationKind(k) => { + self.mapping_modification_kind = k; + One(P::MappingModificationKind) + } + C::SetMappingRef(mapping_ref) => { + self.mapping_ref = mapping_ref; + One(P::MappingRef) + } + C::SetLearnableTargetKinds(kinds) => { + self.included_targets = kinds; + One(P::IncludedTargets) + } + C::SetTouchCause(touch_cause) => { + self.touch_cause = touch_cause; + One(P::TouchCause) + } + }; + Some(affected) + } +} + +/// A model for creating targets +#[derive(Clone, Debug)] +pub struct TargetModel { + // # For all targets + category: TargetCategory, + unit: TargetUnit, + // # For virtual targets + control_element_character: VirtualControlElementCharacter, + control_element_id: VirtualControlElementId, + learnable: bool, + // # For REAPER targets + // TODO-low Rename this to reaper_target_type + r#type: ReaperTargetType, + // # For action targets only + action_scope: ActionScope, + smart_command_name: Option, + action_invocation_type: ActionInvocationType, + with_track: bool, + // # For track targets + track_type: VirtualTrackType, + track_id: Option, + track_name: String, + track_index: u32, + track_expression: String, + enable_only_if_track_selected: bool, + clip_column_track_context: ClipColumnTrackContext, + track_tool_action: TrackToolAction, + gang_behavior: TrackGangBehavior, + browse_tracks_mode: BrowseTracksMode, + // # For track FX targets + fx_type: VirtualFxType, + fx_is_input_fx: bool, + fx_id: Option, + fx_name: String, + fx_index: u32, + fx_expression: String, + enable_only_if_fx_has_focus: bool, + fx_tool_action: FxToolAction, + // # For track FX or compartment parameter targets + param_index: u32, + // # For track FX parameter targets + param_type: VirtualFxParameterType, + param_name: String, + param_expression: String, + retrigger: bool, + real_time: bool, + // # For track route targets + route_selector_type: TrackRouteSelectorType, + route_type: TrackRouteType, + route_id: Option, + route_index: u32, + route_name: String, + route_expression: String, + touched_route_parameter_type: TouchedRouteParameterType, + // # For track solo targets + solo_behavior: SoloBehavior, + // # For seek and goto bookmark targets + seek_behavior: SeekBehavior, + // # For toggleable track targets + track_exclusivity: TrackExclusivity, + // # For transport target + transport_action: TransportAction, + // # For any-on target + any_on_parameter: AnyOnParameter, + // # For "Load FX snapshot" target + fx_snapshot: Option, + // # For "Automation touch state" target + touched_track_parameter_type: TouchedTrackParameterType, + // # For "Go to marker/region" target + bookmark_ref: u32, + bookmark_type: BookmarkType, + bookmark_anchor_type: BookmarkAnchorType, + // # For "Go to marker/region" target and "Seek" target + use_time_selection: bool, + use_loop_points: bool, + // # For "Seek" target + use_regions: bool, + use_project: bool, + move_view: bool, + seek_play: bool, + feedback_resolution: FeedbackResolution, + // # For track show target + track_area: RealearnTrackArea, + // # For track and route automation mode target + automation_mode: RealearnAutomationMode, + // # For track monitoring mode target + monitoring_mode: MonitoringMode, + // # For automation mode override target + automation_mode_override_type: AutomationModeOverrideType, + // # For FX Open and Browse FXs target + fx_display_type: FxDisplayType, + // # For track selection related targets + scroll_arrange_view: bool, + scroll_mixer: bool, + // # For Send MIDI target + raw_midi_pattern: String, + send_midi_destination_type: SendMidiDestinationType, + midi_input_device: Option, + // # For Send OSC target + osc_address_pattern: String, + osc_arg_index: Option, + osc_arg_type_tag: OscTypeTag, + osc_arg_value_range: Interval, + osc_dev_id: Option, + // # For mouse target + mouse_action_type: MouseActionType, + axis: Axis, + mouse_button: MouseButton, + // # For clip targets + playtime_slot: PlaytimeSlotDescriptor, + playtime_column: PlaytimeColumnDescriptor, + playtime_row: PlaytimeRowDescriptor, + playtime_slot_management_action: PlaytimeSlotManagementAction, + playtime_slot_transport_action: PlaytimeSlotTransportAction, + playtime_matrix_action: PlaytimeMatrixAction, + playtime_column_action: PlaytimeColumnAction, + playtime_row_action: PlaytimeRowAction, + stop_column_if_slot_empty: bool, + // # For targets that might have to be polled to get automatic feedback in all cases. + poll_for_feedback: bool, + instance_tag_kind: InstanceTagKind, + tags: Vec, + mapping_snapshot_type_for_load: MappingSnapshotTypeForLoad, + mapping_snapshot_type_for_take: MappingSnapshotTypeForTake, + mapping_snapshot_id: Option, + mapping_snapshot_default_value: Option, + exclusivity: Exclusivity, + group_id: GroupId, + active_mappings_only: bool, + mapping_modification_kind: MappingModificationKind, + mapping_ref: MappingRefModel, + // # For Pot targets + pot_filter_item_kind: PotFilterKind, + // # For targets that deal with target learning/touching + included_targets: NonCryptoHashSet, + touch_cause: TargetTouchCause, +} + +#[derive(Clone, Debug)] +pub enum MappingRefModel { + OwnMapping { + mapping_id: Option, + }, + ForeignMapping { + session_id: String, + mapping_key: Option, + }, +} + +impl MappingRefModel { + pub fn session_id(&self) -> Option<&str> { + if let Self::ForeignMapping { session_id, .. } = self { + Some(session_id) + } else { + None + } + } +} + +impl Default for MappingRefModel { + fn default() -> Self { + Self::OwnMapping { mapping_id: None } + } +} + +impl MappingRefModel { + fn create_mapping_ref(&self) -> Result { + let mapping_ref = match self { + MappingRefModel::OwnMapping { mapping_id } => MappingRef::OwnMapping { + mapping_id: (*mapping_id).ok_or("mapping not specified")?, + }, + MappingRefModel::ForeignMapping { + session_id, + mapping_key, + } => MappingRef::ForeignMapping { + session_id: session_id.clone(), + mapping_key: mapping_key.clone().ok_or("mapping_not_specified")?, + }, + }; + Ok(mapping_ref) + } +} + +impl Default for TargetModel { + fn default() -> Self { + Self { + category: TargetCategory::default(), + unit: Default::default(), + control_element_character: VirtualControlElementCharacter::default(), + control_element_id: Default::default(), + learnable: true, + r#type: ReaperTargetType::Dummy, + action_scope: Default::default(), + smart_command_name: None, + action_invocation_type: ActionInvocationType::default(), + track_type: Default::default(), + track_id: None, + track_name: "".to_owned(), + track_index: 0, + track_expression: "".to_owned(), + enable_only_if_track_selected: false, + with_track: false, + fx_type: Default::default(), + fx_is_input_fx: false, + fx_id: None, + fx_name: "".to_owned(), + fx_index: 0, + fx_expression: "".to_owned(), + enable_only_if_fx_has_focus: false, + param_type: Default::default(), + param_index: 0, + param_name: "".to_owned(), + param_expression: "".to_owned(), + retrigger: false, + real_time: false, + route_selector_type: Default::default(), + route_type: Default::default(), + route_id: None, + route_index: 0, + route_name: Default::default(), + route_expression: Default::default(), + touched_route_parameter_type: Default::default(), + solo_behavior: Default::default(), + seek_behavior: Default::default(), + track_exclusivity: Default::default(), + transport_action: TransportAction::default(), + any_on_parameter: AnyOnParameter::default(), + fx_snapshot: None, + touched_track_parameter_type: Default::default(), + bookmark_ref: 0, + bookmark_type: BookmarkType::Marker, + bookmark_anchor_type: Default::default(), + use_time_selection: false, + use_loop_points: false, + use_regions: false, + use_project: true, + move_view: true, + seek_play: true, + feedback_resolution: Default::default(), + track_area: Default::default(), + automation_mode: Default::default(), + monitoring_mode: Default::default(), + automation_mode_override_type: Default::default(), + fx_display_type: Default::default(), + scroll_arrange_view: false, + scroll_mixer: false, + raw_midi_pattern: Default::default(), + send_midi_destination_type: Default::default(), + midi_input_device: None, + osc_address_pattern: "".to_owned(), + osc_arg_index: Some(0), + osc_arg_type_tag: Default::default(), + osc_arg_value_range: DEFAULT_OSC_ARG_VALUE_RANGE, + osc_dev_id: None, + mouse_action_type: Default::default(), + axis: Default::default(), + mouse_button: Default::default(), + poll_for_feedback: true, + instance_tag_kind: Default::default(), + tags: Default::default(), + mapping_snapshot_type_for_load: MappingSnapshotTypeForLoad::Initial, + mapping_snapshot_type_for_take: MappingSnapshotTypeForTake::LastLoaded, + mapping_snapshot_id: None, + mapping_snapshot_default_value: None, + exclusivity: Default::default(), + group_id: Default::default(), + active_mappings_only: false, + playtime_slot: Default::default(), + playtime_column: Default::default(), + playtime_row: Default::default(), + playtime_slot_management_action: Default::default(), + playtime_slot_transport_action: Default::default(), + playtime_column_action: Default::default(), + playtime_matrix_action: Default::default(), + stop_column_if_slot_empty: false, + clip_column_track_context: Default::default(), + playtime_row_action: Default::default(), + track_tool_action: Default::default(), + fx_tool_action: Default::default(), + gang_behavior: Default::default(), + browse_tracks_mode: Default::default(), + pot_filter_item_kind: Default::default(), + mapping_modification_kind: Default::default(), + mapping_ref: Default::default(), + included_targets: LearnableTargetKind::iter().collect(), + touch_cause: Default::default(), + } + } +} + +impl TargetModel { + pub fn category(&self) -> TargetCategory { + self.category + } + + pub fn unit(&self) -> TargetUnit { + self.unit + } + + pub fn control_element_character(&self) -> VirtualControlElementCharacter { + self.control_element_character + } + + pub fn control_element_id(&self) -> VirtualControlElementId { + self.control_element_id + } + + pub fn learnable(&self) -> bool { + self.learnable + } + + pub fn target_type(&self) -> ReaperTargetType { + self.r#type + } + + pub fn action_scope(&self) -> ActionScope { + self.action_scope + } + + pub fn smart_command_name(&self) -> Option<&str> { + self.smart_command_name.as_deref() + } + + pub fn action_invocation_type(&self) -> ActionInvocationType { + self.action_invocation_type + } + + pub fn with_track(&self) -> bool { + self.with_track + } + + pub fn track_type(&self) -> VirtualTrackType { + self.track_type + } + + pub fn track_name(&self) -> &str { + &self.track_name + } + + pub fn track_index(&self) -> u32 { + self.track_index + } + + pub fn track_expression(&self) -> &str { + &self.track_expression + } + + pub fn enable_only_if_track_selected(&self) -> bool { + self.enable_only_if_track_selected + } + + pub fn fx_type(&self) -> VirtualFxType { + self.fx_type + } + + pub fn fx_is_input_fx(&self) -> bool { + self.fx_is_input_fx + } + + pub fn fx_name(&self) -> &str { + &self.fx_name + } + + pub fn fx_index(&self) -> u32 { + self.fx_index + } + + pub fn fx_expression(&self) -> &str { + &self.fx_expression + } + + pub fn enable_only_if_fx_has_focus(&self) -> bool { + self.enable_only_if_fx_has_focus + } + + pub fn fixed_gang_behavior(&self) -> TrackGangBehavior { + self.gang_behavior.fixed(self.r#type.definition()) + } + + pub fn browse_tracks_mode(&self) -> BrowseTracksMode { + self.browse_tracks_mode + } + + pub fn param_type(&self) -> VirtualFxParameterType { + self.param_type + } + + pub fn param_index(&self) -> u32 { + self.param_index + } + + pub fn compartment_param_index(&self) -> CompartmentParamIndex { + CompartmentParamIndex::try_from(self.param_index).unwrap_or_default() + } + + pub fn param_name(&self) -> &str { + &self.param_name + } + + pub fn param_expression(&self) -> &str { + &self.param_expression + } + + pub fn route_selector_type(&self) -> TrackRouteSelectorType { + self.route_selector_type + } + + pub fn route_type(&self) -> TrackRouteType { + self.route_type + } + + pub fn route_index(&self) -> u32 { + self.route_index + } + + pub fn route_name(&self) -> &str { + &self.route_name + } + + pub fn route_expression(&self) -> &str { + &self.route_expression + } + + pub fn solo_behavior(&self) -> SoloBehavior { + self.solo_behavior + } + + pub fn seek_behavior(&self) -> SeekBehavior { + self.seek_behavior + } + + pub fn track_exclusivity(&self) -> TrackExclusivity { + self.track_exclusivity + } + + pub fn track_tool_action(&self) -> TrackToolAction { + self.track_tool_action + } + + pub fn fx_tool_action(&self) -> FxToolAction { + self.fx_tool_action + } + + pub fn transport_action(&self) -> TransportAction { + self.transport_action + } + + pub fn mouse_action_type(&self) -> MouseActionType { + self.mouse_action_type + } + + pub fn axis(&self) -> Axis { + self.axis + } + + pub fn mouse_button(&self) -> MouseButton { + self.mouse_button + } + + pub fn any_on_parameter(&self) -> AnyOnParameter { + self.any_on_parameter + } + + pub fn fx_snapshot(&self) -> Option<&FxSnapshot> { + self.fx_snapshot.as_ref() + } + + pub fn mapping_snapshot_type_for_load(&self) -> MappingSnapshotTypeForLoad { + self.mapping_snapshot_type_for_load + } + + pub fn mapping_snapshot_type_for_take(&self) -> MappingSnapshotTypeForTake { + self.mapping_snapshot_type_for_take + } + + pub fn mapping_snapshot_id(&self) -> Option<&MappingSnapshotId> { + self.mapping_snapshot_id.as_ref() + } + + pub fn touched_track_parameter_type(&self) -> TouchedTrackParameterType { + self.touched_track_parameter_type + } + + pub fn touched_route_parameter_type(&self) -> TouchedRouteParameterType { + self.touched_route_parameter_type + } + + pub fn bookmark_ref(&self) -> u32 { + self.bookmark_ref + } + + pub fn bookmark_type(&self) -> BookmarkType { + self.bookmark_type + } + + pub fn bookmark_anchor_type(&self) -> BookmarkAnchorType { + self.bookmark_anchor_type + } + + pub fn use_time_selection(&self) -> bool { + self.use_time_selection + } + + pub fn use_loop_points(&self) -> bool { + self.use_loop_points + } + + pub fn use_regions(&self) -> bool { + self.use_regions + } + + pub fn use_project(&self) -> bool { + self.use_project + } + + pub fn move_view(&self) -> bool { + self.move_view + } + + pub fn seek_play(&self) -> bool { + self.seek_play + } + + pub fn feedback_resolution(&self) -> FeedbackResolution { + self.feedback_resolution + } + + pub fn track_area(&self) -> RealearnTrackArea { + self.track_area + } + + pub fn automation_mode(&self) -> RealearnAutomationMode { + self.automation_mode + } + + pub fn monitoring_mode(&self) -> MonitoringMode { + self.monitoring_mode + } + + pub fn automation_mode_override_type(&self) -> AutomationModeOverrideType { + self.automation_mode_override_type + } + + pub fn fx_display_type(&self) -> FxDisplayType { + self.fx_display_type + } + + pub fn scroll_arrange_view(&self) -> bool { + self.scroll_arrange_view + } + + pub fn scroll_mixer(&self) -> bool { + self.scroll_mixer + } + + pub fn raw_midi_pattern(&self) -> &str { + &self.raw_midi_pattern + } + + pub fn send_midi_destination_type(&self) -> SendMidiDestinationType { + self.send_midi_destination_type + } + + pub fn midi_input_device(&self) -> Option { + self.midi_input_device + } + + pub fn osc_address_pattern(&self) -> &str { + &self.osc_address_pattern + } + + pub fn mapping_snapshot_default_value(&self) -> Option { + self.mapping_snapshot_default_value + } + + pub fn osc_arg_index(&self) -> Option { + self.osc_arg_index + } + + pub fn osc_arg_type_tag(&self) -> OscTypeTag { + self.osc_arg_type_tag + } + + pub fn osc_arg_value_range(&self) -> Interval { + self.osc_arg_value_range + } + + pub fn osc_dev_id(&self) -> Option { + self.osc_dev_id + } + + pub fn playtime_slot_management_action(&self) -> PlaytimeSlotManagementAction { + self.playtime_slot_management_action + } + + pub fn poll_for_feedback(&self) -> bool { + self.poll_for_feedback + } + + pub fn retrigger(&self) -> bool { + self.retrigger + } + + pub fn real_time(&self) -> bool { + self.real_time + } + + pub fn instance_tag_kind(&self) -> InstanceTagKind { + self.instance_tag_kind + } + + pub fn tags(&self) -> &[Tag] { + &self.tags + } + + pub fn exclusivity(&self) -> Exclusivity { + self.exclusivity + } + + pub fn group_id(&self) -> GroupId { + self.group_id + } + + pub fn active_mappings_only(&self) -> bool { + self.active_mappings_only + } + + pub fn supports_control(&self) -> bool { + use TargetCategory::*; + match self.category { + Reaper => self.r#type.supports_control(), + Virtual => true, + } + } + + pub fn supports_feedback(&self) -> bool { + use TargetCategory::*; + match self.category { + Reaper => self.r#type.supports_feedback(), + Virtual => true, + } + } + + pub fn make_track_sticky( + &mut self, + compartment: CompartmentKind, + context: ExtendedProcessorContext, + ) -> Result>, Box> { + if self.track_type.is_sticky() { + return Ok(None); + }; + let track = self + .with_context(context, compartment) + .first_effective_track()?; + let virtual_track = virtualize_track(&track, context.context(), false); + let _ = self.set_virtual_track(virtual_track, Some(context.context())); + Ok(Some(Affected::Multiple)) + } + + pub fn make_fx_sticky( + &mut self, + compartment: CompartmentKind, + context: ExtendedProcessorContext, + ) -> Result>, Box> { + if self.fx_type.is_sticky() { + return Ok(None); + }; + let fx = self.with_context(context, compartment).first_fx()?; + let virtual_fx = virtualize_fx(&fx, context.context(), false); + Ok(self.set_virtual_fx(virtual_fx, context, compartment)) + } + + pub fn make_route_sticky( + &mut self, + compartment: CompartmentKind, + context: ExtendedProcessorContext, + ) -> Result>, Box> { + if self.route_selector_type.is_sticky() { + return Ok(None); + }; + let desc = self.route_descriptor()?; + let route = desc.resolve_first(context, compartment)?; + let virtual_route = virtualize_route(&route, context.context(), false); + Ok(self.set_virtual_route(virtual_route)) + } + + pub fn take_fx_snapshot( + &self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> anyhow::Result { + let fx = self + .with_context(context, compartment) + .first_fx() + .map_err(anyhow::Error::msg)?; + let fx_info = fx.info()?; + let fx_snapshot = FxSnapshot { + fx_type: if fx_info.sub_type_expression.is_empty() { + fx_info.type_expression + } else { + fx_info.sub_type_expression + }, + fx_name: fx_info.effect_name, + preset_name: fx.preset_name().map(|n| n.into_string()), + chunk: Rc::new(fx.tag_chunk()?.content().to_owned()), + // chunk: Rc::new(fx.vst_chunk_encoded()?.into_string()), + }; + Ok(fx_snapshot) + } + + #[must_use] + pub fn invalidate_fx_index( + &mut self, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Option> { + if !self.supports_fx() { + return None; + } + if let Ok(actual_fx) = self.with_context(context, compartment).first_fx() { + let new_virtual_fx = match self.virtual_fx() { + Some(virtual_fx) => { + match virtual_fx { + VirtualFx::ChainFx { + is_input_fx, + chain_fx: anchor, + } => match anchor { + VirtualChainFx::ByIdOrIndex(guid, _) => Some(VirtualFx::ChainFx { + is_input_fx, + chain_fx: VirtualChainFx::ByIdOrIndex(guid, actual_fx.index()), + }), + _ => None, + }, + // No update necessary + VirtualFx::Unit | VirtualFx::LastFocused | VirtualFx::This => None, + } + } + // Shouldn't happen + None => None, + }; + if let Some(virtual_fx) = new_virtual_fx { + self.set_virtual_fx(virtual_fx, context, compartment) + } else { + None + } + } else { + None + } + } + + #[must_use] + pub fn set_virtual_track( + &mut self, + track: VirtualTrack, + context: Option<&ProcessorContext>, + ) -> Option> { + self.set_track_from_prop_values(TrackPropValues::from_virtual_track(track), true, context) + } + + /// Sets the track type and in certain cases also updates a few other target properties. + #[must_use] + pub fn set_track_type_from_ui( + &mut self, + track_type: VirtualTrackType, + context: &ProcessorContext, + ) -> Option> { + use VirtualTrackType::*; + match track_type { + This => { + self.set_concrete_track(ConcreteTrackInstruction::This(Some(context)), true, false) + } + ById => self.set_concrete_track( + ConcreteTrackInstruction::ById { + id: None, + context: Some(context), + }, + true, + false, + ), + _ => { + self.track_type = track_type; + Some(Affected::One(TargetProp::TrackType)) + } + } + } + + #[must_use] + pub fn set_fx_type_from_ui( + &mut self, + fx_type: VirtualFxType, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Option> { + use VirtualFxType::*; + match fx_type { + This => self.set_concrete_fx( + ConcreteFxInstruction::This(Some(context.context())), + true, + false, + ), + ById => self.set_concrete_fx( + ConcreteFxInstruction::ById { + is_input_fx: None, + id: None, + track: self + .with_context(context, compartment) + .first_effective_track() + .ok(), + }, + true, + false, + ), + _ => { + self.fx_type = fx_type; + Some(Affected::One(TargetProp::FxType)) + } + } + } + + #[must_use] + pub fn set_track_from_prop_values( + &mut self, + track: TrackPropValues, + with_notification: bool, + context: Option<&ProcessorContext>, + ) -> Option> { + self.track_type = track.r#type; + self.track_expression = track.expression; + use VirtualTrackType::*; + match track.r#type { + This => { + let _ = self.set_concrete_track( + ConcreteTrackInstruction::This(context), + // Already notified above + false, + with_notification, + ); + } + ById => { + let _ = self.set_concrete_track( + ConcreteTrackInstruction::ById { + id: track.id, + context, + }, + // Already notified above + false, + with_notification, + ); + } + ByName | AllByName => { + self.track_name = track.name; + } + ByIndex | ByIndexTcp | ByIndexMcp => { + self.track_index = track.index; + } + ByIdOrName => { + self.track_id = track.id; + self.track_name = track.name; + } + FromClipColumn => { + self.playtime_column = track.clip_column; + self.clip_column_track_context = track.clip_column_track_context; + } + Unit | Selected | AllSelected | Master | Dynamic | DynamicTcp | DynamicMcp => {} + } + Some(Affected::Multiple) + } + + #[must_use] + pub fn set_virtual_route(&mut self, route: VirtualTrackRoute) -> Option> { + self.set_route(TrackRoutePropValues::from_virtual_route(route)) + } + + #[must_use] + pub fn set_route(&mut self, route: TrackRoutePropValues) -> Option> { + self.route_selector_type = route.selector_type; + self.route_type = route.r#type; + self.route_id = route.id; + self.route_name = route.name; + self.route_index = route.index; + self.route_expression = route.expression; + Some(Affected::Multiple) + } + + #[must_use] + pub fn set_virtual_fx( + &mut self, + fx: VirtualFx, + context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Option> { + self.set_fx_from_prop_values( + FxPropValues::from_virtual_fx(fx), + true, + Some(context), + compartment, + ) + } + + #[must_use] + pub fn set_fx_from_prop_values( + &mut self, + fx: FxPropValues, + with_notification: bool, + context: Option, + compartment: CompartmentKind, + ) -> Option> { + self.fx_type = fx.r#type; + self.fx_expression = fx.expression; + self.fx_is_input_fx = fx.is_input_fx; + use VirtualFxType::*; + match fx.r#type { + This => { + let _ = self.set_concrete_fx( + ConcreteFxInstruction::This(context.map(|c| c.context())), + // Already notified above + false, + with_notification, + ); + } + ById => { + let _ = self.set_concrete_fx( + ConcreteFxInstruction::ById { + is_input_fx: Some(fx.is_input_fx), + id: fx.id, + track: context.and_then(|c| { + self.with_context(c, compartment) + .first_effective_track() + .ok() + }), + }, + // Already notified above + false, + with_notification, + ); + } + ByName | AllByName => { + self.fx_name = fx.name; + } + ByIndex => { + self.fx_index = fx.index; + } + ByIdOrIndex => { + self.fx_id = fx.id; + self.fx_index = fx.index; + } + Dynamic | Focused | Unit => {} + }; + Some(Affected::Multiple) + } + + #[must_use] + pub fn set_fx_parameter( + &mut self, + param: FxParameterPropValues, + ) -> Option> { + self.param_type = param.r#type; + self.param_name = param.name; + self.param_index = param.index; + self.param_expression = param.expression; + Some(Affected::Multiple) + } + + #[must_use] + pub fn set_seek_options(&mut self, options: SeekOptions) -> Option> { + self.use_time_selection = options.use_time_selection; + self.use_loop_points = options.use_loop_points; + self.use_regions = options.use_regions; + self.use_project = options.use_project; + self.move_view = options.move_view; + self.seek_play = options.seek_play; + self.feedback_resolution = options.feedback_resolution; + Some(Affected::Multiple) + } + + /// Sets the track to one of the concrete types ById or This, also setting other important + /// properties for UI convenience. + #[must_use] + pub fn set_concrete_track( + &mut self, + instruction: ConcreteTrackInstruction, + notify_about_type_change: bool, + notify_about_id_change: bool, + ) -> Option> { + let resolved = instruction.resolve(); + self.track_type = resolved.virtual_track_type(); + if let Some(id) = resolved.id() { + self.track_id = Some(id); + } + // We also set index and name so that we can easily switch between types. + if let Some(i) = resolved.index() { + self.track_index = i; + } + if let Some(name) = resolved.name() { + self.track_name = name; + } + if !notify_about_type_change && !notify_about_id_change { + None + } else if !notify_about_type_change && notify_about_id_change { + Some(Affected::One(TargetProp::TrackId)) + } else if notify_about_type_change && !notify_about_id_change { + Some(Affected::One(TargetProp::TrackType)) + } else { + Some(Affected::Multiple) + } + } + + /// Sets the FX to one of the concrete types (ById only for now), also setting other important + /// properties for UI convenience. + pub fn set_concrete_fx( + &mut self, + instruction: ConcreteFxInstruction, + notify_about_type_and_input_fx_change: bool, + notify_about_id_change: bool, + ) -> Option> { + let resolved = instruction.resolve(); + self.fx_type = resolved.virtual_fx_type(); + if let Some(is_input_fx) = resolved.is_input_fx() { + self.fx_is_input_fx = is_input_fx; + } + if let Some(id) = resolved.id() { + self.fx_id = Some(id); + } + // We also set index and name so that we can easily switch between types. + if let Some(i) = resolved.index() { + self.fx_index = i; + } + if let Some(name) = resolved.name() { + self.fx_name = name; + } + if notify_about_type_and_input_fx_change { + Some(Affected::Multiple) + } else if notify_about_id_change { + Some(Affected::One(TargetProp::FxId)) + } else { + None + } + } + + pub fn seek_options(&self) -> SeekOptions { + SeekOptions { + use_time_selection: self.use_time_selection, + use_loop_points: self.use_loop_points, + use_regions: self.use_regions, + use_project: self.use_project, + move_view: self.move_view, + seek_play: self.seek_play, + feedback_resolution: self.feedback_resolution, + } + } + + #[must_use] + pub fn apply_from_target( + &mut self, + target: &ReaperTarget, + extended_context: ExtendedProcessorContext, + compartment: CompartmentKind, + ) -> Option> { + let context = extended_context.context(); + use ReaperTarget::*; + self.category = TargetCategory::Reaper; + self.r#type = ReaperTargetType::from_target(target); + if let Some(actual_fx) = target.fx() { + let virtual_fx = virtualize_fx(actual_fx, context, true); + let _ = self.set_virtual_fx(virtual_fx, extended_context, compartment); + let track = if let Some(track) = actual_fx.track() { + track.clone() + } else { + // Must be monitoring FX. In this case we want the master track (it's REAPER's + // convention and ours). + context + .project_or_current_project() + .master_track() + .expect("no way") + }; + let _ = self.set_virtual_track(virtualize_track(&track, context, true), Some(context)); + } else if let Some(track) = target.track() { + let _ = self.set_virtual_track(virtualize_track(track, context, true), Some(context)); + } + if let Some(send) = target.route() { + let virtual_route = virtualize_route(send, context, true); + let _ = self.set_virtual_route(virtual_route); + } + if let Some(track_exclusivity) = target.track_exclusivity() { + self.track_exclusivity = track_exclusivity; + } + if let Some(slot_address) = target.clip_slot_address() { + self.playtime_slot = PlaytimeSlotDescriptor::ByIndex(slot_address); + } + if let Some(column_address) = target.clip_column_address() { + self.playtime_column = PlaytimeColumnDescriptor::ByIndex(column_address); + } + if let Some(row_address) = target.clip_row_address() { + self.playtime_row = PlaytimeRowDescriptor::ByIndex(row_address); + } + + match target { + Action(t) => { + let section_id = t.action.section().map(|s| s.id()).unwrap_or_default(); + self.action_scope = ActionScope::guess_from_section_id(section_id.get()); + self.smart_command_name = build_smart_command_name_from_action(&t.action); + self.action_invocation_type = t.invocation_type; + } + FxParameter(t) => { + self.param_type = VirtualFxParameterType::ById; + self.param_index = t.param.index(); + } + Transport(t) => { + self.transport_action = t.action; + } + TrackSolo(t) => { + self.solo_behavior = t.behavior; + } + GoToBookmark(t) => { + self.bookmark_ref = t.index; + self.bookmark_type = t.bookmark_type; + } + TrackAutomationMode(t) => { + self.automation_mode = RealearnAutomationMode::from_reaper(t.mode); + } + TrackMonitoringMode(t) => { + self.monitoring_mode = convert_monitoring_mode_to_realearn(t.mode); + } + RouteAutomationMode(t) => { + self.automation_mode = RealearnAutomationMode::from_reaper(t.mode); + } + AutomationModeOverride(t) => match t.mode_override { + None => { + self.automation_mode_override_type = AutomationModeOverrideType::None; + } + Some(GlobalAutomationModeOverride::Bypass) => { + self.automation_mode_override_type = AutomationModeOverrideType::Bypass; + } + Some(GlobalAutomationModeOverride::Mode(am)) => { + self.automation_mode_override_type = AutomationModeOverrideType::Override; + self.automation_mode = RealearnAutomationMode::from_reaper(am); + } + }, + PlaytimeMatrixAction(t) => { + self.playtime_matrix_action = t.action; + } + PlaytimeColumnAction(t) => { + self.playtime_column_action = t.action; + } + PlaytimeRowAction(t) => { + self.playtime_row_action = t.basics.action; + } + PlaytimeSlotTransportAction(t) => { + self.playtime_slot_transport_action = t.basics.action; + } + _ => {} + }; + Some(Affected::Multiple) + } + + pub fn virtual_default( + control_element_character: VirtualControlElementCharacter, + next_index: u32, + ) -> Self { + TargetModel { + category: TargetCategory::Virtual, + control_element_character, + control_element_id: VirtualControlElementId::Indexed(next_index), + ..Default::default() + } + } + + pub fn default_for_compartment(compartment: CompartmentKind) -> Self { + use CompartmentKind::*; + TargetModel { + category: match compartment { + Controller => TargetCategory::Virtual, + Main => TargetCategory::Reaper, + }, + ..Default::default() + } + } + + pub fn virtual_track(&self) -> Option { + use VirtualTrackType::*; + let track = match self.track_type { + This => VirtualTrack::This, + Selected => VirtualTrack::Selected { + allow_multiple: false, + }, + AllSelected => VirtualTrack::Selected { + allow_multiple: true, + }, + Master => VirtualTrack::Master, + Unit => VirtualTrack::Unit, + ById => VirtualTrack::ById(self.track_id?), + ByName => VirtualTrack::ByName { + wild_match: WildMatch::new(&self.track_name), + allow_multiple: false, + }, + AllByName => VirtualTrack::ByName { + wild_match: WildMatch::new(&self.track_name), + allow_multiple: true, + }, + ByIndex | ByIndexTcp | ByIndexMcp => VirtualTrack::ByIndex { + index: self.track_index, + scope: self.track_type.virtual_track_scope().unwrap_or_default(), + }, + ByIdOrName => { + VirtualTrack::ByIdOrName(self.track_id?, WildMatch::new(&self.track_name)) + } + Dynamic | DynamicTcp | DynamicMcp => { + let evaluator = ExpressionEvaluator::compile(&self.track_expression).ok()?; + VirtualTrack::Dynamic { + evaluator: Box::new(evaluator), + scope: self.track_type.virtual_track_scope().unwrap_or_default(), + } + } + FromClipColumn => VirtualTrack::FromClipColumn { + column: self.virtual_clip_column().ok()?, + context: self.clip_column_track_context, + }, + }; + Some(track) + } + + pub fn track(&self) -> TrackPropValues { + TrackPropValues { + r#type: self.track_type, + id: self.track_id, + name: self.track_name.clone(), + expression: self.track_expression.clone(), + index: self.track_index, + clip_column: self.playtime_column.clone(), + clip_column_track_context: self.clip_column_track_context, + } + } + + pub fn virtual_fx(&self) -> Option { + use VirtualFxType::*; + let fx = match self.fx_type { + Focused => VirtualFx::LastFocused, + This => VirtualFx::This, + Unit => VirtualFx::Unit, + _ => VirtualFx::ChainFx { + is_input_fx: self.fx_is_input_fx, + chain_fx: self.virtual_chain_fx()?, + }, + }; + Some(fx) + } + + pub fn virtual_mapping_snapshot_id_for_load( + &self, + ) -> Result { + match self.mapping_snapshot_type_for_load { + MappingSnapshotTypeForLoad::Initial => Ok(VirtualMappingSnapshotIdForLoad::Initial), + MappingSnapshotTypeForLoad::ById => { + let id = self + .mapping_snapshot_id + .as_ref() + .ok_or("no mapping snapshot ID")? + .clone(); + Ok(VirtualMappingSnapshotIdForLoad::ById(id)) + } + } + } + + pub fn virtual_mapping_snapshot_id_for_take( + &self, + ) -> Result { + match self.mapping_snapshot_type_for_take { + MappingSnapshotTypeForTake::LastLoaded => { + Ok(VirtualMappingSnapshotIdForTake::LastLoaded) + } + MappingSnapshotTypeForTake::ById => { + let id = self + .mapping_snapshot_id + .as_ref() + .ok_or("no mapping snapshot ID")? + .clone(); + Ok(VirtualMappingSnapshotIdForTake::ById(id)) + } + } + } + + pub fn mapping_snapshot_desc_for_load(&self) -> MappingSnapshotDescForLoad { + if self.target_type() == ReaperTargetType::TakeMappingSnapshot { + Default::default() + } else { + match self.mapping_snapshot_type_for_load { + MappingSnapshotTypeForLoad::Initial => MappingSnapshotDescForLoad::Initial, + MappingSnapshotTypeForLoad::ById => MappingSnapshotDescForLoad::ById { + id: self + .mapping_snapshot_id + .as_ref() + .map(|id| id.to_string()) + .unwrap_or_default(), + }, + } + } + } + + pub fn mapping_snapshot_desc_for_take(&self) -> MappingSnapshotDescForTake { + if self.target_type() == ReaperTargetType::LoadMappingSnapshot { + Default::default() + } else { + match self.mapping_snapshot_type_for_take { + MappingSnapshotTypeForTake::LastLoaded => MappingSnapshotDescForTake::LastLoaded, + MappingSnapshotTypeForTake::ById => MappingSnapshotDescForTake::ById { + id: self + .mapping_snapshot_id + .as_ref() + .map(|id| id.to_string()) + .unwrap_or_default(), + }, + } + } + } + + pub fn track_route_selector(&self) -> Option { + use TrackRouteSelectorType::*; + let selector = match self.route_selector_type { + Dynamic => { + let evaluator = ExpressionEvaluator::compile(&self.route_expression).ok()?; + TrackRouteSelector::Dynamic(Box::new(evaluator)) + } + ById => { + if self.route_type == TrackRouteType::HardwareOutput { + // Hardware outputs don't offer stable IDs. + TrackRouteSelector::ByIndex(self.route_index) + } else { + TrackRouteSelector::ById(self.route_id?) + } + } + ByName => TrackRouteSelector::ByName(WildMatch::new(&self.route_name)), + ByIndex => TrackRouteSelector::ByIndex(self.route_index), + }; + Some(selector) + } + + pub fn virtual_chain_fx(&self) -> Option { + use VirtualFxType::*; + let fx = match self.fx_type { + Focused | This | Unit => return None, + ById => VirtualChainFx::ById(self.fx_id?, Some(self.fx_index)), + ByName => VirtualChainFx::ByName { + wild_match: WildMatch::new(&self.fx_name), + allow_multiple: false, + }, + AllByName => VirtualChainFx::ByName { + wild_match: WildMatch::new(&self.fx_name), + allow_multiple: true, + }, + ByIndex => VirtualChainFx::ByIndex(self.fx_index), + ByIdOrIndex => VirtualChainFx::ByIdOrIndex(self.fx_id, self.fx_index), + Dynamic => { + let evaluator = ExpressionEvaluator::compile(&self.fx_expression).ok()?; + VirtualChainFx::Dynamic(Box::new(evaluator)) + } + }; + Some(fx) + } + + pub fn fx(&self) -> FxPropValues { + FxPropValues { + r#type: self.fx_type, + is_input_fx: self.fx_is_input_fx, + id: self.fx_id, + name: self.fx_name.clone(), + expression: self.fx_expression.clone(), + index: self.fx_index, + } + } + + pub fn track_route(&self) -> TrackRoutePropValues { + TrackRoutePropValues { + selector_type: self.route_selector_type, + r#type: self.route_type, + id: self.route_id, + name: self.route_name.clone(), + expression: self.route_expression.clone(), + index: self.route_index, + } + } + + pub fn fx_parameter(&self) -> FxParameterPropValues { + FxParameterPropValues { + r#type: self.param_type, + name: self.param_name.clone(), + expression: self.param_expression.clone(), + index: self.param_index, + } + } + + pub fn track_descriptor(&self) -> Result { + let desc = TrackDescriptor { + track: self.virtual_track().ok_or("virtual track not complete")?, + enable_only_if_track_selected: self.enable_only_if_track_selected, + }; + Ok(desc) + } + + pub fn api_track_descriptor(&self) -> helgobox_api::persistence::TrackDescriptor { + use helgobox_api::persistence::TrackDescriptor; + use VirtualTrackType::*; + let commons = TrackDescriptorCommons { + track_must_be_selected: Some(self.enable_only_if_track_selected), + }; + match self.track_type { + This => TrackDescriptor::This { commons }, + Selected => TrackDescriptor::Selected { + allow_multiple: Some(false), + }, + AllSelected => TrackDescriptor::Selected { + allow_multiple: Some(true), + }, + Master => TrackDescriptor::Master { commons }, + Unit => TrackDescriptor::Instance { commons }, + ById | ByIdOrName => TrackDescriptor::ById { + commons, + id: self + .track_id + .as_ref() + .map(|id| id.to_string_without_braces()), + }, + ByName => TrackDescriptor::ByName { + commons, + name: self.track_name.clone(), + allow_multiple: Some(false), + }, + AllByName => TrackDescriptor::ByName { + commons, + name: self.track_name.clone(), + allow_multiple: Some(true), + }, + ByIndex | ByIndexTcp | ByIndexMcp => TrackDescriptor::ByIndex { + commons, + index: self.track_index, + scope: self.track_type.virtual_track_scope(), + }, + Dynamic | DynamicTcp | DynamicMcp => TrackDescriptor::Dynamic { + commons, + expression: self.track_expression.clone(), + scope: self.track_type.virtual_track_scope(), + }, + FromClipColumn => TrackDescriptor::FromClipColumn { + commons, + column: self.playtime_column.clone(), + context: self.clip_column_track_context, + }, + } + } + + pub fn api_fx_descriptor(&self) -> helgobox_api::persistence::FxDescriptor { + use helgobox_api::persistence::FxDescriptor; + use VirtualFxType::*; + let commons = FxDescriptorCommons { + fx_must_have_focus: Some(self.enable_only_if_fx_has_focus), + }; + let chain = FxChainDescriptor::Track { + track: Some(self.api_track_descriptor()), + chain: Some(if self.fx_is_input_fx { + TrackFxChain::Input + } else { + TrackFxChain::Normal + }), + }; + match self.fx_type { + This => FxDescriptor::This { commons }, + Focused => FxDescriptor::Focused, + Dynamic => FxDescriptor::Dynamic { + commons, + chain, + expression: self.track_expression.clone(), + }, + Unit => FxDescriptor::Instance { commons }, + ById => FxDescriptor::ById { + commons, + chain, + id: self.fx_id.as_ref().map(|id| id.to_string_without_braces()), + }, + ByName => FxDescriptor::ByName { + commons, + chain, + name: self.fx_name.clone(), + allow_multiple: Some(false), + }, + AllByName => FxDescriptor::ByName { + commons, + chain, + name: self.fx_name.clone(), + allow_multiple: Some(true), + }, + ByIndex | ByIdOrIndex => FxDescriptor::ByIndex { + commons, + chain, + index: self.fx_index, + }, + } + } + + fn virtual_clip_slot(&self) -> Result { + use PlaytimeSlotDescriptor::*; + let slot = match &self.playtime_slot { + Active => VirtualPlaytimeSlot::Active, + ByIndex(address) => VirtualPlaytimeSlot::ByIndex(*address), + Dynamic { + column_expression, + row_expression, + } => { + let column_evaluator = ExpressionEvaluator::compile(column_expression) + .map_err(|_| "couldn't evaluate row")?; + let row_evaluator = ExpressionEvaluator::compile(row_expression) + .map_err(|_| "couldn't evaluate row")?; + VirtualPlaytimeSlot::Dynamic { + column_evaluator: Box::new(column_evaluator), + row_evaluator: Box::new(row_evaluator), + } + } + }; + Ok(slot) + } + + fn virtual_clip_column(&self) -> Result { + VirtualPlaytimeColumn::from_descriptor(&self.playtime_column) + } + + fn virtual_clip_row(&self) -> Result { + use PlaytimeRowDescriptor::*; + let row = match &self.playtime_row { + Active => VirtualPlaytimeRow::Active, + ByIndex(address) => VirtualPlaytimeRow::ByIndex(address.index), + Dynamic { + expression: index_expression, + } => { + let index_evaluator = ExpressionEvaluator::compile(index_expression) + .map_err(|_| "couldn't evaluate row index")?; + VirtualPlaytimeRow::Dynamic(Box::new(index_evaluator)) + } + }; + Ok(row) + } + + pub fn fx_descriptor(&self) -> Result { + let desc = FxDescriptor { + track_descriptor: if let Ok(desc) = self.track_descriptor() { + desc + } else if self.fx_type.requires_fx_chain() { + return Err("couldn't resolve track but track required"); + } else { + TrackDescriptor::default() + }, + enable_only_if_fx_has_focus: self.enable_only_if_fx_has_focus, + fx: self.virtual_fx().ok_or("FX not set")?, + }; + Ok(desc) + } + + pub fn route_descriptor(&self) -> Result { + let desc = TrackRouteDescriptor { + track_descriptor: self.track_descriptor()?, + route: self.virtual_track_route()?, + }; + Ok(desc) + } + + pub fn virtual_track_route(&self) -> Result { + let route = VirtualTrackRoute { + r#type: self.route_type, + selector: self.track_route_selector().ok_or("track route not set")?, + }; + Ok(route) + } + + pub fn virtual_fx_parameter(&self) -> Option { + use VirtualFxParameterType::*; + let param = match self.param_type { + ByName => VirtualFxParameter::ByName(WildMatch::new(&self.param_name)), + ById => VirtualFxParameter::ById(self.param_index), + ByIndex => VirtualFxParameter::ByIndex(self.param_index), + Dynamic => { + let evaluator = ExpressionEvaluator::compile(&self.param_expression).ok()?; + VirtualFxParameter::Dynamic(Box::new(evaluator)) + } + }; + Some(param) + } + + fn fx_parameter_descriptor(&self) -> Result { + let desc = FxParameterDescriptor { + fx_descriptor: self.fx_descriptor()?, + fx_parameter: self.virtual_fx_parameter().ok_or("FX parameter not set")?, + }; + Ok(desc) + } + + pub fn create_target( + &self, + compartment: CompartmentKind, + ) -> Result { + use TargetCategory::*; + match self.category { + Reaper => { + use ReaperTargetType::*; + let target = match self.r#type { + Mouse => UnresolvedReaperTarget::Mouse(UnresolvedMouseTarget { + action_type: self.mouse_action_type, + axis: self.axis, + button: self.mouse_button, + }), + CompartmentParameterValue => UnresolvedReaperTarget::CompartmentParameterValue( + UnresolvedCompartmentParameterValueTarget { + compartment, + index: self.compartment_param_index(), + }, + ), + Action => UnresolvedReaperTarget::Action(UnresolvedActionTarget { + action: self.resolved_available_action()?, + scope: self.action_scope, + invocation_type: self.action_invocation_type, + track_descriptor: if self.with_track { + Some(self.track_descriptor()?) + } else { + None + }, + }), + FxParameterValue => { + UnresolvedReaperTarget::FxParameter(UnresolvedFxParameterTarget { + fx_parameter_descriptor: self.fx_parameter_descriptor()?, + poll_for_feedback: self.poll_for_feedback, + retrigger: self.retrigger, + real_time_even_if_not_rendering: self.real_time, + }) + } + FxParameterTouchState => UnresolvedReaperTarget::FxParameterTouchState( + UnresolvedFxParameterTouchStateTarget { + fx_parameter_descriptor: self.fx_parameter_descriptor()?, + }, + ), + TrackVolume => { + UnresolvedReaperTarget::TrackVolume(UnresolvedTrackVolumeTarget { + track_descriptor: self.track_descriptor()?, + gang_behavior: self.fixed_gang_behavior(), + }) + } + TrackTool => UnresolvedReaperTarget::TrackTool(UnresolvedTrackToolTarget { + track_descriptor: self.track_descriptor()?, + action: self.track_tool_action, + scope: self.tag_scope(), + }), + TrackPeak => UnresolvedReaperTarget::TrackPeak(UnresolvedTrackPeakTarget { + track_descriptor: self.track_descriptor()?, + }), + RouteVolume => { + UnresolvedReaperTarget::TrackSendVolume(UnresolvedRouteVolumeTarget { + descriptor: self.route_descriptor()?, + }) + } + TrackPan => UnresolvedReaperTarget::TrackPan(UnresolvedTrackPanTarget { + track_descriptor: self.track_descriptor()?, + gang_behavior: self.fixed_gang_behavior(), + }), + TrackWidth => UnresolvedReaperTarget::TrackWidth(UnresolvedTrackWidthTarget { + track_descriptor: self.track_descriptor()?, + gang_behavior: self.fixed_gang_behavior(), + }), + TrackArm => UnresolvedReaperTarget::TrackArm(UnresolvedTrackArmTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + gang_behavior: self.fixed_gang_behavior(), + }), + TrackParentSend => { + UnresolvedReaperTarget::TrackParentSend(UnresolvedTrackParentSendTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + }) + } + TrackSelection => { + UnresolvedReaperTarget::TrackSelection(UnresolvedTrackSelectionTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + scroll_arrange_view: self.scroll_arrange_view, + scroll_mixer: self.scroll_mixer, + }) + } + TrackMute => UnresolvedReaperTarget::TrackMute(UnresolvedTrackMuteTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + gang_behavior: self.fixed_gang_behavior(), + }), + TrackPhase => UnresolvedReaperTarget::TrackPhase(UnresolvedTrackPhaseTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + gang_behavior: self.gang_behavior, + poll_for_feedback: self.poll_for_feedback, + }), + TrackShow => UnresolvedReaperTarget::TrackShow(UnresolvedTrackShowTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + area: match self.track_area { + RealearnTrackArea::Tcp => TrackArea::Tcp, + RealearnTrackArea::Mcp => TrackArea::Mcp, + }, + }), + TrackAutomationMode => UnresolvedReaperTarget::TrackAutomationMode( + UnresolvedTrackAutomationModeTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + mode: self.automation_mode.to_reaper(), + }, + ), + TrackMonitoringMode => UnresolvedReaperTarget::TrackMonitoringMode( + UnresolvedTrackMonitoringModeTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + mode: convert_monitoring_mode_to_reaper(self.monitoring_mode), + gang_behavior: self.fixed_gang_behavior(), + }, + ), + TrackSolo => UnresolvedReaperTarget::TrackSolo(UnresolvedTrackSoloTarget { + track_descriptor: self.track_descriptor()?, + behavior: self.solo_behavior, + exclusivity: self.track_exclusivity, + gang_behavior: self.fixed_gang_behavior(), + }), + RoutePan => UnresolvedReaperTarget::RoutePan(UnresolvedRoutePanTarget { + descriptor: self.route_descriptor()?, + }), + RouteMute => UnresolvedReaperTarget::RouteMute(UnresolvedRouteMuteTarget { + descriptor: self.route_descriptor()?, + poll_for_feedback: self.poll_for_feedback, + }), + RoutePhase => UnresolvedReaperTarget::RoutePhase(UnresolvedRoutePhaseTarget { + descriptor: self.route_descriptor()?, + poll_for_feedback: self.poll_for_feedback, + }), + RouteMono => UnresolvedReaperTarget::RouteMono(UnresolvedRouteMonoTarget { + descriptor: self.route_descriptor()?, + poll_for_feedback: self.poll_for_feedback, + }), + RouteAutomationMode => UnresolvedReaperTarget::RouteAutomationMode( + UnresolvedRouteAutomationModeTarget { + descriptor: self.route_descriptor()?, + mode: self.automation_mode.to_reaper(), + poll_for_feedback: self.poll_for_feedback, + }, + ), + RouteTouchState => { + UnresolvedReaperTarget::RouteTouchState(UnresolvedRouteTouchStateTarget { + descriptor: self.route_descriptor()?, + parameter_type: self.touched_route_parameter_type, + }) + } + Tempo => UnresolvedReaperTarget::Tempo(UnresolvedTempoTarget), + PlayRate => UnresolvedReaperTarget::Playrate(UnresolvedPlayrateTarget), + AutomationModeOverride => UnresolvedReaperTarget::AutomationModeOverride( + UnresolvedAutomationModeOverrideTarget { + mode_override: match self.automation_mode_override_type { + AutomationModeOverrideType::Bypass => { + Some(GlobalAutomationModeOverride::Bypass) + } + AutomationModeOverrideType::Override => { + Some(GlobalAutomationModeOverride::Mode( + self.automation_mode.to_reaper(), + )) + } + AutomationModeOverrideType::None => None, + }, + }, + ), + FxTool => UnresolvedReaperTarget::FxTool(UnresolvedFxToolTarget { + fx_descriptor: self.fx_descriptor()?, + action: self.fx_tool_action, + scope: self.tag_scope(), + }), + FxEnable => UnresolvedReaperTarget::FxEnable(UnresolvedFxEnableTarget { + fx_descriptor: self.fx_descriptor()?, + }), + FxOnline => UnresolvedReaperTarget::FxOnline(UnresolvedFxOnlineTarget { + fx_descriptor: self.fx_descriptor()?, + }), + FxOpen => UnresolvedReaperTarget::FxOpen(UnresolvedFxOpenTarget { + fx_descriptor: self.fx_descriptor()?, + display_type: self.fx_display_type, + }), + FxPreset => UnresolvedReaperTarget::FxPreset(UnresolvedFxPresetTarget { + fx_descriptor: self.fx_descriptor()?, + }), + BrowseTracks => { + UnresolvedReaperTarget::SelectedTrack(UnresolvedBrowseTracksTarget { + scroll_arrange_view: self.scroll_arrange_view, + scroll_mixer: self.scroll_mixer, + mode: self.browse_tracks_mode, + }) + } + BrowseFxs => UnresolvedReaperTarget::BrowseFxs(UnresolvedBrowseFxsTarget { + track_descriptor: self.track_descriptor()?, + is_input_fx: self.fx_is_input_fx, + display_type: self.fx_display_type, + }), + AllTrackFxEnable => { + UnresolvedReaperTarget::AllTrackFxEnable(UnresolvedAllTrackFxEnableTarget { + track_descriptor: self.track_descriptor()?, + exclusivity: self.track_exclusivity, + poll_for_feedback: self.poll_for_feedback, + }) + } + Transport => UnresolvedReaperTarget::Transport(UnresolvedTransportTarget { + action: self.transport_action, + }), + LoadFxSnapshot => { + UnresolvedReaperTarget::LoadFxPreset(UnresolvedLoadFxSnapshotTarget { + fx_descriptor: self.fx_descriptor()?, + chunk: self + .fx_snapshot + .as_ref() + .ok_or("FX chunk not set")? + .chunk + .clone(), + }) + } + LastTouched => { + UnresolvedReaperTarget::LastTouched(UnresolvedLastTouchedTarget { + included_targets: self + .included_targets + .iter() + .copied() + .map(ReaperTargetType::from_learnable_target_kind) + .collect(), + touch_cause: self.touch_cause, + }) + } + TrackTouchState => { + UnresolvedReaperTarget::TrackTouchState(UnresolvedTrackTouchStateTarget { + track_descriptor: self.track_descriptor()?, + parameter_type: self.touched_track_parameter_type, + exclusivity: self.track_exclusivity, + gang_behavior: self.fixed_gang_behavior(), + }) + } + GoToBookmark => { + UnresolvedReaperTarget::GoToBookmark(UnresolvedGoToBookmarkTarget { + bookmark_type: self.bookmark_type, + bookmark_anchor_type: self.bookmark_anchor_type, + bookmark_ref: self.bookmark_ref, + set_time_selection: self.use_time_selection, + set_loop_points: self.use_loop_points, + seek_behavior: self.seek_behavior, + }) + } + Seek => UnresolvedReaperTarget::Seek(UnresolvedSeekTarget { + options: self.seek_options(), + behavior: self.seek_behavior, + }), + SendMidi => UnresolvedReaperTarget::SendMidi(UnresolvedMidiSendTarget { + pattern: self.raw_midi_pattern.parse().unwrap_or_default(), + destination: match self.send_midi_destination_type { + SendMidiDestinationType::FxOutput => SendMidiDestination::FxOutput, + SendMidiDestinationType::FeedbackOutput => { + SendMidiDestination::FeedbackOutput + } + SendMidiDestinationType::InputDevice => { + SendMidiDestination::InputDevice(InputDeviceMidiDestination { + device_id: self.midi_input_device.map(|d| d.get()), + }) + } + }, + }), + SendOsc => UnresolvedReaperTarget::SendOsc(UnresolvedOscSendTarget { + address_pattern: self.osc_address_pattern.clone(), + arg_descriptor: self.osc_arg_descriptor(), + device_id: self.osc_dev_id, + }), + PlaytimeSlotTransportAction => { + UnresolvedReaperTarget::PlaytimeSlotTransportAction( + crate::domain::UnresolvedPlaytimeSlotTransportTarget { + slot: self.virtual_clip_slot()?, + action: self.playtime_slot_transport_action, + options: self.clip_transport_options(), + }, + ) + } + PlaytimeColumnAction => UnresolvedReaperTarget::PlaytimeColumnAction( + crate::domain::UnresolvedPlaytimeColumnActionTarget { + column: self.virtual_clip_column()?, + action: self.playtime_column_action, + }, + ), + PlaytimeRowAction => UnresolvedReaperTarget::PlaytimeRowAction( + crate::domain::UnresolvedPlaytimeRowActionTarget { + row: self.virtual_clip_row()?, + action: self.playtime_row_action, + }, + ), + PlaytimeSlotSeek => UnresolvedReaperTarget::PlaytimeSlotSeek( + crate::domain::UnresolvedPlaytimeSlotSeekTarget { + slot: self.virtual_clip_slot()?, + feedback_resolution: self.feedback_resolution, + }, + ), + PlaytimeSlotVolume => UnresolvedReaperTarget::PlaytimeSlotVolume( + crate::domain::UnresolvedPlaytimeSlotVolumeTarget { + slot: self.virtual_clip_slot()?, + }, + ), + PlaytimeSlotManagementAction => { + UnresolvedReaperTarget::PlaytimeSlotManagementAction( + crate::domain::UnresolvedPlaytimeSlotManagementActionTarget { + slot: self.virtual_clip_slot()?, + action: self.playtime_slot_management_action, + }, + ) + } + PlaytimeMatrixAction => UnresolvedReaperTarget::PlaytimeMatrixAction( + crate::domain::UnresolvedPlaytimeMatrixActionTarget { + action: self.playtime_matrix_action, + }, + ), + PlaytimeControlUnitScroll => UnresolvedReaperTarget::PlaytimeControlUnitScroll( + crate::domain::UnresolvedPlaytimeControlUnitScrollTarget { + axis: self.axis, + }, + ), + PlaytimeBrowseCells => UnresolvedReaperTarget::PlaytimeBrowseCells( + crate::domain::UnresolvedPlaytimeBrowseCellsTarget { axis: self.axis }, + ), + LoadMappingSnapshot => UnresolvedReaperTarget::LoadMappingSnapshot( + UnresolvedLoadMappingSnapshotTarget { + compartment, + scope: self.tag_scope(), + active_mappings_only: self.active_mappings_only, + snapshot_id: self.virtual_mapping_snapshot_id_for_load()?, + default_value: self.mapping_snapshot_default_value, + }, + ), + TakeMappingSnapshot => UnresolvedReaperTarget::TakeMappingSnapshot( + UnresolvedTakeMappingSnapshotTarget { + compartment, + scope: self.tag_scope(), + active_mappings_only: self.active_mappings_only, + snapshot_id: self.virtual_mapping_snapshot_id_for_take()?, + }, + ), + EnableMappings => { + UnresolvedReaperTarget::EnableMappings(UnresolvedEnableMappingsTarget { + compartment, + scope: TagScope { + tags: self.tags.iter().cloned().collect(), + }, + exclusivity: self.exclusivity, + }) + } + ModifyMapping => { + UnresolvedReaperTarget::ModifyMapping(UnresolvedModifyMappingTarget { + compartment, + modification: match self.mapping_modification_kind { + MappingModificationKind::LearnTarget => { + MappingModification::LearnTarget( + LearnTargetMappingModification { + included_targets: Some( + self.included_targets.iter().cloned().collect(), + ), + touch_cause: Some(self.touch_cause), + }, + ) + } + MappingModificationKind::SetTargetToLastTouched => { + MappingModification::SetTargetToLastTouched( + SetTargetToLastTouchedMappingModification { + included_targets: Some( + self.included_targets.iter().cloned().collect(), + ), + touch_cause: Some(self.touch_cause), + }, + ) + } + }, + mapping_ref: self.mapping_ref.create_mapping_ref()?, + }) + } + EnableInstances => { + UnresolvedReaperTarget::EnableInstances(UnresolvedEnableInstancesTarget { + tag_kind: self.instance_tag_kind, + scope: TagScope { + tags: self.tags.iter().cloned().collect(), + }, + exclusivity: self.exclusivity, + }) + } + EnableUnits => { + UnresolvedReaperTarget::EnableUnits(UnresolvedEnableUnitsTarget { + scope: TagScope { + tags: self.tags.iter().cloned().collect(), + }, + exclusivity: self.exclusivity, + }) + } + BrowseGroup => { + UnresolvedReaperTarget::BrowseGroup(UnresolvedBrowseGroupTarget { + compartment, + group_id: self.group_id, + exclusivity: self.exclusivity.into(), + }) + } + AnyOn => UnresolvedReaperTarget::AnyOn(UnresolvedAnyOnTarget { + parameter: self.any_on_parameter, + }), + Dummy => UnresolvedReaperTarget::Dummy(UnresolvedDummyTarget), + BrowsePotFilterItems => UnresolvedReaperTarget::BrowsePotFilterItems( + UnresolvedBrowsePotFilterItemsTarget { + settings: PotFilterItemsTargetSettings { + kind: self.pot_filter_item_kind, + }, + }, + ), + BrowsePotPresets => UnresolvedReaperTarget::BrowsePotPresets( + UnresolvedBrowsePotPresetsTarget {}, + ), + PreviewPotPreset => UnresolvedReaperTarget::PreviewPotPreset( + UnresolvedPreviewPotPresetTarget {}, + ), + LoadPotPreset => { + UnresolvedReaperTarget::LoadPotPreset(UnresolvedLoadPotPresetTarget { + fx_descriptor: self.fx_descriptor()?, + }) + } + StreamDeckBrightness => UnresolvedReaperTarget::StreamDeckBrightness( + UnresolvedStreamDeckBrightnessTarget {}, + ), + }; + Ok(UnresolvedCompoundMappingTarget::Reaper(Box::new(target))) + } + Virtual => { + let virtual_target = VirtualTarget { + control_element: self.create_control_element(), + learnable: self.learnable, + }; + Ok(UnresolvedCompoundMappingTarget::Virtual(virtual_target)) + } + } + } + + pub fn playtime_slot(&self) -> &PlaytimeSlotDescriptor { + &self.playtime_slot + } + + pub fn playtime_column(&self) -> &PlaytimeColumnDescriptor { + &self.playtime_column + } + + pub fn playtime_row(&self) -> &PlaytimeRowDescriptor { + &self.playtime_row + } + + pub fn playtime_slot_transport_action(&self) -> PlaytimeSlotTransportAction { + self.playtime_slot_transport_action + } + + pub fn playtime_matrix_action(&self) -> PlaytimeMatrixAction { + self.playtime_matrix_action + } + + pub fn playtime_column_action(&self) -> PlaytimeColumnAction { + self.playtime_column_action + } + + pub fn simple_target(&self) -> Option { + use helgobox_api::persistence; + use playtime_api::runtime::SimpleMappingTarget; + use ReaperTargetType as T; + if self.category != TargetCategory::Reaper { + return None; + } + let t = match self.r#type { + T::PlaytimeSlotTransportAction + if self.playtime_slot_transport_action() + == persistence::PlaytimeSlotTransportAction::Trigger => + { + SimpleMappingTarget::TriggerSlot(self.playtime_slot.fixed_address()?) + } + T::PlaytimeColumnAction + if self.playtime_column_action() == persistence::PlaytimeColumnAction::Stop => + { + SimpleMappingTarget::TriggerColumn(self.playtime_column.fixed_address()?) + } + T::PlaytimeRowAction + if self.playtime_row_action() == persistence::PlaytimeRowAction::PlayScene => + { + SimpleMappingTarget::TriggerRow(self.playtime_row.fixed_address()?) + } + T::PlaytimeMatrixAction => match self.playtime_matrix_action { + PlaytimeMatrixAction::Stop => SimpleMappingTarget::TriggerMatrix, + PlaytimeMatrixAction::SmartRecord => SimpleMappingTarget::SmartRecord, + PlaytimeMatrixAction::StartOrStopPlayback => { + SimpleMappingTarget::EnterSilenceModeOrPlayIgnited + } + PlaytimeMatrixAction::SequencerRecordOnOffState => { + SimpleMappingTarget::SequencerRecordOnOffState + } + PlaytimeMatrixAction::SequencerPlayOnOffState => { + SimpleMappingTarget::SequencerPlayOnOffState + } + PlaytimeMatrixAction::TapTempo => SimpleMappingTarget::TapTempo, + PlaytimeMatrixAction::Undo + | PlaytimeMatrixAction::Redo + | PlaytimeMatrixAction::BuildScene + | PlaytimeMatrixAction::SetRecordLengthMode + | PlaytimeMatrixAction::SetCustomRecordLengthInBars + | PlaytimeMatrixAction::ClickOnOffState + | PlaytimeMatrixAction::MidiAutoQuantizationOnOffState + | PlaytimeMatrixAction::SilenceModeOnOffState + | PlaytimeMatrixAction::Panic => return None, + }, + _ => return None, + }; + Some(t) + } + + pub fn tag_scope(&self) -> TagScope { + TagScope { + tags: self.tags.iter().cloned().collect(), + } + } + + pub fn playtime_row_action(&self) -> PlaytimeRowAction { + self.playtime_row_action + } + + pub fn stop_column_if_slot_empty(&self) -> bool { + self.stop_column_if_slot_empty + } + + pub fn clip_transport_options(&self) -> crate::domain::ClipTransportOptions { + crate::domain::ClipTransportOptions { + stop_column_if_slot_empty: self.stop_column_if_slot_empty, + } + } + + fn osc_arg_descriptor(&self) -> Option { + let arg_index = self.osc_arg_index?; + Some(OscArgDescriptor::new( + arg_index, + self.osc_arg_type_tag, + // Doesn't matter for sending + false, + self.osc_arg_value_range, + )) + } + + pub fn mouse_action(&self) -> MouseAction { + match self.mouse_action_type { + MouseActionType::MoveTo => MouseAction::MoveTo { + axis: Some(self.axis), + }, + MouseActionType::MoveBy => MouseAction::MoveBy { + axis: Some(self.axis), + }, + MouseActionType::PressOrRelease => MouseAction::PressOrRelease { + button: Some(self.mouse_button), + }, + MouseActionType::Scroll => MouseAction::Scroll { + axis: Some(self.axis), + }, + } + } + + pub fn pot_filter_item_kind(&self) -> PotFilterKind { + self.pot_filter_item_kind + } + + pub fn included_targets(&self) -> &NonCryptoHashSet { + &self.included_targets + } + + pub fn touch_cause(&self) -> TargetTouchCause { + self.touch_cause + } + + pub fn mapping_modification_kind(&self) -> MappingModificationKind { + self.mapping_modification_kind + } + + pub fn mapping_ref(&self) -> &MappingRefModel { + &self.mapping_ref + } + + pub fn set_mouse_action_without_notification(&mut self, mouse_action: MouseAction) { + match mouse_action { + MouseAction::MoveTo { axis } => { + self.mouse_action_type = MouseActionType::MoveTo; + self.axis = axis.unwrap_or_default(); + } + MouseAction::MoveBy { axis } => { + self.mouse_action_type = MouseActionType::MoveBy; + self.axis = axis.unwrap_or_default(); + } + MouseAction::PressOrRelease { button } => { + self.mouse_action_type = MouseActionType::PressOrRelease; + self.mouse_button = button.unwrap_or_default(); + } + MouseAction::Scroll { axis } => { + self.mouse_action_type = MouseActionType::Scroll; + self.axis = axis.unwrap_or_default(); + } + } + } + + pub fn with_context<'a>( + &'a self, + context: ExtendedProcessorContext<'a>, + compartment: CompartmentKind, + ) -> TargetModelWithContext<'a> { + TargetModelWithContext { + target: self, + context, + compartment, + } + } + + pub fn supports_axis(&self) -> bool { + if !self.r#type.definition().supports_axis() { + return false; + } + if self.r#type == ReaperTargetType::Mouse { + matches!( + self.mouse_action_type, + MouseActionType::MoveTo | MouseActionType::MoveBy | MouseActionType::Scroll + ) + } else { + true + } + } + + pub fn supports_mouse_button(&self) -> bool { + if !self.r#type.definition().supports_mouse_button() { + return false; + } + matches!(self.mouse_action_type, MouseActionType::PressOrRelease) + } + + pub fn supports_gang_selected(&self) -> bool { + self.r#type.definition().supports_gang_selected() + } + + pub fn supports_gang_grouping(&self) -> bool { + self.r#type.definition().supports_gang_grouping() + } + + pub fn supports_track(&self) -> bool { + if !self.r#type.supports_track() { + return false; + } + self.requires_track_apart_from_type() + } + + pub fn supports_fx_chain(&self) -> bool { + let target_type = self.r#type; + if !target_type.supports_fx_chain() { + return false; + } + match self.r#type { + t if t.supports_fx() => self.fx_type.requires_fx_chain(), + _ => true, + } + } + + pub fn supports_track_must_be_selected(&self) -> bool { + if !self.r#type.supports_track_must_be_selected() { + return false; + } + self.uses_track_apart_from_type() + } + + pub fn supports_osc_arg_value_range(&self) -> bool { + self.category == TargetCategory::Reaper + && self.osc_arg_index.is_some() + && self.osc_arg_type_tag.supports_value_range() + } + + /// "Requires" means that it requires the user to provide a track in the GUI. + fn requires_track_apart_from_type(&self) -> bool { + if !self.uses_track_apart_from_type() { + return false; + } + if self.r#type.supports_fx() && !self.fx_type.requires_fx_chain() { + return false; + } + true + } + + /// "Uses" means that it works on a track (even if the user doesn't need to provide it). + /// + /// It makes sense then to present the "Track must be selected" checkbox then. + fn uses_track_apart_from_type(&self) -> bool { + match self.r#type { + ReaperTargetType::PlaytimeSlotTransportAction => { + use TransportAction::*; + matches!(self.transport_action, PlayStop | PlayPause) + } + ReaperTargetType::Action => self.with_track, + _ => true, + } + } + + pub fn supports_fx(&self) -> bool { + if !self.is_reaper() { + return false; + } + self.r#type.supports_fx() + } + + pub fn supports_route(&self) -> bool { + if !self.is_reaper() { + return false; + } + self.r#type.supports_send() + } + + pub fn supports_automation_mode(&self) -> bool { + if !self.is_reaper() { + return false; + } + use ReaperTargetType::*; + match self.r#type { + TrackAutomationMode | RouteAutomationMode => true, + AutomationModeOverride => { + self.automation_mode_override_type == AutomationModeOverrideType::Override + } + _ => false, + } + } + + pub fn supports_mapping_snapshot_id(&self) -> bool { + if !self.is_reaper() { + return false; + } + use ReaperTargetType::*; + match self.r#type { + LoadMappingSnapshot => { + self.mapping_snapshot_type_for_load == MappingSnapshotTypeForLoad::ById + } + TakeMappingSnapshot => { + self.mapping_snapshot_type_for_take == MappingSnapshotTypeForTake::ById + } + _ => false, + } + } + + pub fn create_control_element(&self) -> VirtualControlElement { + VirtualControlElement::new(self.control_element_id, self.control_element_character) + } + + fn is_reaper(&self) -> bool { + self.category == TargetCategory::Reaper + } + + pub fn is_virtual(&self) -> bool { + self.category == TargetCategory::Virtual + } + + fn command_id_label(&self) -> Cow { + match self.resolve_action() { + None => "-".into(), + Some(action) => { + if action.is_available() { + action + .command_id() + .expect("should be available") + .to_string() + .into() + } else if let Some(command_name) = action.command_name() { + format!(" ({})", command_name.to_str()).into() + } else { + "".into() + } + } + } + } + + pub fn resolve_action(&self) -> Option { + let command_name = self.smart_command_name.as_deref()?; + build_action_from_smart_command_name( + SectionId::new(self.action_scope.section_id()), + command_name, + ) + } + + pub fn resolved_available_action(&self) -> Result { + let action = self.resolve_action().ok_or("action not set")?; + if !action.is_available() { + return Err("action not available"); + } + Ok(action.clone()) + } + + pub fn action_name_label(&self) -> Cow { + match self.resolved_available_action().ok() { + None => "-".into(), + Some(a) => a + .name() + .map(|n| n.to_string().into()) + .unwrap_or_else(|_| "".into()), + } + } +} + +pub struct TargetModelFormatVeryShort<'a>(pub &'a TargetModel); + +impl Display for TargetModelFormatVeryShort<'_> { + /// Produces a short single-line name which is for example used to derive the automatic name. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0.category { + TargetCategory::Reaper => { + use ReaperTargetType::*; + let tt = self.0.r#type; + match tt { + Action => match self.0.resolved_available_action().ok() { + None => write!(f, "Action {}", self.0.command_id_label()), + Some(a) => f.write_str(a.name().expect("should be available").to_str()), + }, + AutomationModeOverride => { + write!(f, "{}: ", tt.short_name())?; + use AutomationModeOverrideType::*; + let ovr_type = self.0.automation_mode_override_type; + match ovr_type { + None | Bypass => write!(f, "{ovr_type}"), + Override => write!(f, "{}", self.0.automation_mode), + } + } + Transport => { + write!(f, "{}", self.0.transport_action) + } + AnyOn => { + write!(f, "{}", self.0.any_on_parameter) + } + GoToBookmark => { + let type_label = match self.0.bookmark_type { + BookmarkType::Marker => "Marker", + BookmarkType::Region => "Region", + }; + let bm_prefix = match self.0.bookmark_anchor_type { + BookmarkAnchorType::Id => "", + BookmarkAnchorType::Index => "#", + }; + write!( + f, + "Go to {} {}{}", + type_label, bm_prefix, self.0.bookmark_ref + ) + } + TrackAutomationMode => { + write!(f, "{}: {}", tt.short_name(), self.0.automation_mode) + } + TrackTouchState => { + write!( + f, + "{}: {}", + tt.short_name(), + self.0.touched_track_parameter_type + ) + } + _ => f.write_str(tt.short_name()), + } + } + TargetCategory::Virtual => match self.0.control_element_id { + VirtualControlElementId::Indexed(i) => { + write!(f, "{} {}", self.0.control_element_character, i + 1) + } + VirtualControlElementId::Named(n) => { + write!(f, "{} ({})", n, self.0.control_element_character) + } + }, + } + } +} + +pub struct TargetModelFormatMultiLine<'a> { + target: &'a TargetModel, + context: ExtendedProcessorContext<'a>, + session: &'a UnitModel, + compartment: CompartmentKind, +} + +impl<'a> TargetModelFormatMultiLine<'a> { + pub fn new( + target: &'a TargetModel, + session: &'a UnitModel, + compartment: CompartmentKind, + ) -> Self { + TargetModelFormatMultiLine { + target, + context: session.extended_context(), + session, + compartment, + } + } + + fn track_label(&self) -> String { + let virtual_track = self.target.virtual_track(); + let virtual_track = match virtual_track.as_ref() { + None => return TARGET_UNDEFINED_LABEL.into(), + Some(t) => t, + }; + if self.target.supports_track() { + get_virtual_track_label(virtual_track, self.compartment, self.context) + } else { + TARGET_OBJECT_IRRELEVANT_LABEL.to_string() + } + } + + fn mapping_snapshot_for_load_label(&self) -> String { + match self.target.mapping_snapshot_type_for_load { + MappingSnapshotTypeForLoad::Initial => MappingSnapshotTypeForLoad::Initial.to_string(), + MappingSnapshotTypeForLoad::ById => self.mapping_snapshot_id_label(), + } + } + + fn mapping_snapshot_for_take_label(&self) -> String { + match self.target.mapping_snapshot_type_for_take { + MappingSnapshotTypeForTake::LastLoaded => { + MappingSnapshotTypeForTake::LastLoaded.to_string() + } + MappingSnapshotTypeForTake::ById => self.mapping_snapshot_id_label(), + } + } + + fn mapping_snapshot_id_label(&self) -> String { + match &self.target.mapping_snapshot_id { + None => "-".into(), + Some(id) => id.to_string(), + } + } + + fn route_label(&self) -> Cow { + let virtual_route = self.target.virtual_track_route().ok(); + let virtual_route = match virtual_route.as_ref() { + None => return TARGET_UNDEFINED_LABEL.into(), + Some(r) => r, + }; + use TrackRouteSelector::*; + match &virtual_route.selector { + ById(_) => { + if let Ok(r) = self.resolve_first_track_route() { + get_route_label(&r).into() + } else { + get_non_present_virtual_route_label(virtual_route).into() + } + } + _ => virtual_route.to_string().into(), + } + } + + fn fx_label(&self) -> Cow { + let fx_descriptor = match self.target.fx_descriptor() { + Ok(d) => d, + Err(_) => return TARGET_UNDEFINED_LABEL.into(), + }; + get_virtual_fx_label(&fx_descriptor, self.compartment, self.context).into() + } + + fn fx_param_label(&self) -> Cow { + let virtual_param = self.target.virtual_fx_parameter(); + let virtual_param = match virtual_param.as_ref() { + None => return TARGET_UNDEFINED_LABEL.into(), + Some(p) => p, + }; + use VirtualFxParameter::*; + match virtual_param { + ById(_) => { + if let Ok(p) = self.resolve_first_fx_param() { + get_fx_param_label(Some(&p), p.index()) + } else { + format!(" ({virtual_param})").into() + } + } + _ => virtual_param.to_string().into(), + } + } + + fn bookmark_label(&self) -> String { + // TODO-medium We should do this similar to the other target objects and introduce a + // virtual struct. + let bookmark_type = self.target.bookmark_type; + let anchor_type = self.target.bookmark_anchor_type; + let bookmark_ref = self.target.bookmark_ref; + match anchor_type { + BookmarkAnchorType::Id => { + let res = find_bookmark( + self.context.context().project_or_current_project(), + bookmark_type, + anchor_type, + bookmark_ref, + ); + if let Ok(res) = res { + get_bookmark_label_by_id(bookmark_type, res.basic_info.id, &res.bookmark.name()) + } else { + get_non_present_bookmark_label(anchor_type, bookmark_ref) + } + } + BookmarkAnchorType::Index => { + get_bookmark_label_by_position(bookmark_type, bookmark_ref) + } + } + } + + // Returns an error if that send (or track) doesn't exist. + pub fn resolve_first_track_route(&self) -> Result { + let routes = get_track_routes( + self.context, + &self.target.route_descriptor()?, + self.compartment, + )?; + routes.into_iter().next().ok_or("empty list of routes") + } + + // Returns an error if that param (or FX) doesn't exist. + fn resolve_first_fx_param(&self) -> Result { + let params = get_fx_params( + self.context, + &self.target.fx_parameter_descriptor()?, + self.compartment, + )?; + params.into_iter().next().ok_or("empty FX param list") + } +} + +const UNIT_NOT_FOUND_LABEL: &str = ""; +const MAPPING_NOT_FOUND_LABEL: &str = ""; + +const NONE_LABEL: &str = ""; + +const MAPPING_LABEL: &str = "Mapping: "; + +impl Display for TargetModelFormatMultiLine<'_> { + /// Produces a multi-line description of the target. + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use TargetCategory::*; + match self.target.category { + Reaper => { + use ReaperTargetType::*; + let tt = self.target.r#type; + match tt { + PlaytimeSlotTransportAction => { + let slot = &self.target.playtime_slot; + let action = &self.target.playtime_slot_transport_action; + write!(f, "{tt}\n{slot}\n{action}") + } + PlaytimeSlotSeek => { + let slot = &self.target.playtime_slot; + write!(f, "{tt}\n{slot}") + } + PlaytimeSlotVolume => { + let slot = &self.target.playtime_slot; + write!(f, "{tt}\n{slot}") + } + PlaytimeColumnAction => { + let column = &self.target.playtime_column; + let action = &self.target.playtime_column_action; + write!(f, "{tt}\n{column}\n{action}") + } + PlaytimeRowAction => { + let row = &self.target.playtime_row; + let action = &self.target.playtime_row_action; + write!(f, "{tt}\n{row}\n{action}") + } + PlaytimeMatrixAction => { + let action = &self.target.playtime_matrix_action; + write!(f, "{tt}\n{action}") + } + PlaytimeControlUnitScroll | PlaytimeBrowseCells => { + let axis = &self.target.axis; + write!(f, "{tt}\n{axis}") + } + PlaytimeSlotManagementAction => { + let slot = &self.target.playtime_slot; + let action = &self.target.playtime_slot_management_action; + write!(f, "{tt}\n{slot}\n{action}") + } + Action => write!( + f, + "{}\n{}\n{}", + tt, + self.target.command_id_label(), + self.target.action_name_label() + ), + FxParameterValue => write!( + f, + "{}\nTrack {}\nFX {}\nParam {}", + tt, + self.track_label(), + self.fx_label(), + self.fx_param_label() + ), + TrackTool | TrackVolume | TrackPeak | TrackPan | TrackWidth | TrackArm + | TrackSelection | TrackMute | TrackPhase | TrackSolo | TrackShow + | BrowseFxs | AllTrackFxEnable | TrackParentSend => { + write!(f, "{}\nTrack {}", tt, self.track_label()) + } + EnableInstances => { + write!( + f, + "{tt}\n{}\n{}", + self.target.instance_tag_kind(), + format_tags_as_csv(&self.target.tags), + ) + } + EnableUnits => { + write!(f, "{tt}\n{}", format_tags_as_csv(&self.target.tags),) + } + TrackAutomationMode => { + write!( + f, + "{}\nTrack {}\n{}", + tt, + self.track_label(), + self.target.automation_mode + ) + } + RouteVolume | RoutePan | RouteMute | RoutePhase | RouteMono + | RouteAutomationMode => write!( + f, + "{}\nTrack {}\n{} {}", + tt, + self.track_label(), + self.target.route_type, + self.route_label() + ), + FxOpen | FxEnable | FxPreset | FxTool => write!( + f, + "{}\nTrack {}\nFX {}", + tt, + self.track_label(), + self.fx_label(), + ), + Transport => write!(f, "{}\n{}", tt, self.target.transport_action), + AnyOn => write!(f, "{}\n{}", tt, self.target.any_on_parameter), + AutomationModeOverride => { + write!(f, "{}\n{}", tt, self.target.automation_mode_override_type) + } + LoadFxSnapshot => write!( + f, + "{}\n{}", + tt, + self.target + .fx_snapshot + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_else(|| "-".to_owned()) + ), + LoadMappingSnapshot => { + write!( + f, + "{}\n\ + Snapshot: {}\n\ + Tags: {}", + tt, + self.mapping_snapshot_for_load_label(), + format_tags_as_csv(self.target.tags()) + ) + } + TakeMappingSnapshot => { + write!( + f, + "{}\n\ + Snapshot: {}\n\ + Tags: {}", + tt, + self.mapping_snapshot_for_take_label(), + format_tags_as_csv(self.target.tags()) + ) + } + TrackTouchState => write!( + f, + "{}\nTrack {}\n{}", + tt, + self.track_label(), + self.target.touched_track_parameter_type + ), + GoToBookmark => { + write!(f, "{}\n{}", tt, self.bookmark_label()) + } + Mouse => { + write!(f, "{}\n{}", tt, self.target.mouse_action_type)?; + if self.target.supports_axis() { + write!(f, "\n{}", self.target.axis)?; + } + if self.target.supports_mouse_button() { + write!(f, "\n{}", self.target.mouse_button)?; + } + Ok(()) + } + ModifyMapping => { + write!(f, "{}\n{}\n", tt, self.target.mapping_modification_kind)?; + match &self.target.mapping_ref { + MappingRefModel::OwnMapping { mapping_id } => { + MAPPING_LABEL.fmt(f)?; + if let Some(id) = mapping_id { + let qualified_id = + QualifiedMappingId::new(self.compartment, *id); + if let Some(m) = + self.session.find_mapping_by_qualified_id(qualified_id) + { + m.borrow().effective_name().fmt(f)?; + } else { + MAPPING_NOT_FOUND_LABEL.fmt(f)?; + } + } else { + NONE_LABEL.fmt(f)?; + } + } + MappingRefModel::ForeignMapping { + session_id, + mapping_key, + } => { + write!(f, "Unit: {session_id}\n{MAPPING_LABEL}")?; + if let Some(mapping_key) = mapping_key { + let session = self + .context + .control_context() + .unit_container + .find_session_by_id(session_id); + if let Some(session) = session { + let session = session.borrow(); + if let Some(m) = session + .find_mapping_by_key(self.compartment, mapping_key) + { + m.borrow().effective_name().fmt(f)?; + } else { + MAPPING_NOT_FOUND_LABEL.fmt(f)?; + } + } else { + UNIT_NOT_FOUND_LABEL.fmt(f)?; + } + } else { + NONE_LABEL.fmt(f)?; + } + } + } + Ok(()) + } + _ => write!(f, "{tt}"), + } + } + Virtual => write!(f, "Virtual\n{}", self.target.create_control_element()), + } + } +} + +pub fn get_fx_param_label(fx_param: Option<&FxParameter>, index: u32) -> Cow<'static, str> { + let position = index + 1; + match fx_param.and_then(|p| p.name().ok()) { + None => format!("{position}. ").into(), + Some(name) => { + let name = name.into_inner(); + // Parameter names are not reliably UTF-8-encoded (e.g. "JS: Stereo Width") + let name = name.to_string_lossy(); + if name.is_empty() { + position.to_string().into() + } else { + format!("{position}. {name}").into() + } + } + } +} + +pub fn get_route_label(route: &TrackRoute) -> String { + format!("{}. {}", route.index() + 1, route.name().to_str()) +} + +pub fn get_optional_fx_label(virtual_chain_fx: &VirtualChainFx, fx: Option<&Fx>) -> String { + match virtual_chain_fx { + VirtualChainFx::Dynamic(_) => virtual_chain_fx.to_string(), + _ => match fx { + None => format!(" ({virtual_chain_fx})"), + Some(fx) => get_fx_label(fx.index(), fx), + }, + } +} + +pub fn get_fx_label(index: u32, fx: &Fx) -> String { + format!( + "{}. {}", + index + 1, + // When closing project, this is sometimes not available anymore although the FX is still + // picked up when querying the list of FXs! Prevent a panic. + if fx.is_available() { + get_fx_name(fx) + } else { + "".to_owned() + } + ) +} + +pub struct TargetModelWithContext<'a> { + target: &'a TargetModel, + context: ExtendedProcessorContext<'a>, + compartment: CompartmentKind, +} + +impl TargetModelWithContext<'_> { + /// Creates a target based on this model's properties and the current REAPER state. + /// + /// This returns a target regardless of the activation conditions of the target. Example: + /// If `enable_only_if_track_selected` is `true` and the track is _not_ selected when calling + /// this function, the target will still be created! + /// + /// # Errors + /// + /// Returns an error if not enough information is provided by the model or if something (e.g. + /// track/FX/parameter) is not available. + pub fn resolve(&self) -> Result, &'static str> { + let unresolved = self.target.create_target(self.compartment)?; + unresolved.resolve(self.context, self.compartment) + } + + pub fn resolve_first(&self) -> Result { + let targets = self.resolve()?; + targets.into_iter().next().ok_or("resolved to empty list") + } + + pub fn is_known_to_be_roundable(&self) -> bool { + // TODO-low use cached + self.resolve_first() + .map(|t| { + matches!( + t.control_type(self.context.control_context()), + ControlType::AbsoluteContinuousRoundable { .. } + ) + }) + .unwrap_or(false) + } + // Returns an error if the FX doesn't exist. + pub fn first_fx(&self) -> Result { + first_effective_fx( + &self.target.fx_descriptor()?, + self.compartment, + self.context, + ) + } + + pub fn project(&self) -> Project { + self.context.context().project_or_current_project() + } + + pub fn first_fx_chain(&self) -> Result { + let track = self.first_effective_track()?; + let chain = if self.target.fx_is_input_fx { + track.input_fx_chain() + } else { + track.normal_fx_chain() + }; + Ok(chain) + } + + pub fn first_effective_track(&self) -> Result { + let virtual_track = self + .target + .virtual_track() + .ok_or("virtual track not complete")?; + first_effective_track(&virtual_track, self.compartment, self.context) + } +} + +pub fn first_effective_track( + virtual_track: &VirtualTrack, + compartment: CompartmentKind, + context: ExtendedProcessorContext, +) -> Result { + virtual_track + .resolve(context, compartment) + .map_err(|_| "particular track couldn't be resolved")? + .into_iter() + .next() + .ok_or("resolved to empty track list") +} + +pub fn first_effective_fx( + fx_descriptor: &FxDescriptor, + compartment: CompartmentKind, + context: ExtendedProcessorContext, +) -> Result { + fx_descriptor + .resolve(context, compartment)? + .into_iter() + .next() + .ok_or("resolves to empty FX list") +} + +pub fn get_bookmark_label_by_id(bookmark_type: BookmarkType, id: BookmarkId, name: &str) -> String { + format!( + "{} {}. {}", + FormattableBookmarkType(bookmark_type), + id, + name + ) +} + +pub fn get_bookmark_label_by_position( + bookmark_type: BookmarkType, + index_within_type: u32, +) -> String { + format!( + "{} #{}", + FormattableBookmarkType(bookmark_type), + index_within_type + 1 + ) +} + +struct FormattableBookmarkType(BookmarkType); + +impl Display for FormattableBookmarkType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self.0 { + BookmarkType::Marker => f.write_str("Marker"), + BookmarkType::Region => f.write_str("Region"), + } + } +} + +pub fn get_non_present_bookmark_label( + anchor_type: BookmarkAnchorType, + bookmark_ref: u32, +) -> String { + match anchor_type { + BookmarkAnchorType::Id => format!(" (ID {bookmark_ref})"), + BookmarkAnchorType::Index => format!("{bookmark_ref}. "), + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Default, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum TargetCategory { + #[default] + #[serde(rename = "reaper")] + #[display(fmt = "Real")] + Reaper, + #[serde(rename = "virtual")] + #[display(fmt = "Virtual")] + Virtual, +} + +impl TargetCategory { + pub fn default_for(compartment: CompartmentKind) -> Self { + use TargetCategory::*; + match compartment { + CompartmentKind::Controller => Virtual, + CompartmentKind::Main => Reaper, + } + } + + pub fn is_allowed_in(self, compartment: CompartmentKind) -> bool { + use TargetCategory::*; + match compartment { + CompartmentKind::Controller => true, + CompartmentKind::Main => match self { + Reaper => true, + Virtual => false, + }, + } + } +} + +fn virtualize_track( + track: &Track, + context: &ProcessorContext, + special_monitoring_fx_handling: bool, +) -> VirtualTrack { + if !track.is_available() { + // Fixes https://github.com/helgoboss/helgobox/issues/1126 + return VirtualTrack::ById(*track.guid()); + } + let own_track = context.track().cloned().unwrap_or_else(|| { + context + .project_or_current_project() + .master_track() + .expect("no way") + }); + if own_track == *track { + VirtualTrack::This + } else if track.is_master_track() { + VirtualTrack::Master + } else if special_monitoring_fx_handling && context.is_on_monitoring_fx_chain() { + // It doesn't make sense to refer to tracks via ID if we are on the monitoring FX chain. + VirtualTrack::ByIndex { + index: track.index().expect("impossible"), + scope: TrackScope::AllTracks, + } + } else { + VirtualTrack::ById(*track.guid()) + } +} + +fn virtualize_fx( + fx: &Fx, + context: &ProcessorContext, + special_monitoring_fx_handling: bool, +) -> VirtualFx { + if context.containing_fx() == fx { + VirtualFx::This + } else { + VirtualFx::ChainFx { + is_input_fx: fx.is_input_fx(), + chain_fx: if special_monitoring_fx_handling && context.is_on_monitoring_fx_chain() { + // Doesn't make sense to refer to FX via UUID if we are on monitoring FX chain. + VirtualChainFx::ByIndex(fx.index()) + } else if let Ok(guid) = fx.get_or_query_guid() { + VirtualChainFx::ById(guid, Some(fx.index())) + } else { + VirtualChainFx::ByIdOrIndex(None, fx.index()) + }, + } + } +} + +fn virtualize_route( + route: &TrackRoute, + context: &ProcessorContext, + special_monitoring_fx_handling: bool, +) -> VirtualTrackRoute { + let partner = route.partner(); + VirtualTrackRoute { + r#type: match route.direction() { + TrackSendDirection::Receive => TrackRouteType::Receive, + TrackSendDirection::Send => { + if matches!(partner, Some(TrackRoutePartner::HardwareOutput(_))) { + TrackRouteType::HardwareOutput + } else { + TrackRouteType::Send + } + } + }, + selector: if special_monitoring_fx_handling && context.is_on_monitoring_fx_chain() { + // Doesn't make sense to refer to route via related-track UUID if we are on monitoring + // FX chain. + TrackRouteSelector::ByIndex(route.index()) + } else { + match partner { + None | Some(TrackRoutePartner::HardwareOutput(_)) => { + TrackRouteSelector::ByIndex(route.index()) + } + Some(TrackRoutePartner::Track(t)) => TrackRouteSelector::ById(*t.guid()), + } + }, + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, TryFromPrimitive, IntoPrimitive, Display)] +#[repr(usize)] +pub enum VirtualTrackType { + #[display(fmt = "")] + This, + #[display(fmt = "")] + Selected, + #[display(fmt = "")] + AllSelected, + #[display(fmt = "")] + Dynamic, + #[display(fmt = "")] + DynamicTcp, + #[display(fmt = "")] + DynamicMcp, + #[display(fmt = "")] + Master, + #[display(fmt = "")] + Unit, + #[display(fmt = "Particular")] + ById, + #[display(fmt = "Named")] + ByName, + #[display(fmt = "All named")] + AllByName, + #[display(fmt = "At position")] + ByIndex, + #[display(fmt = "At TCP position")] + ByIndexTcp, + #[display(fmt = "At MCP position")] + ByIndexMcp, + #[display(fmt = "By ID or name (legacy)")] + ByIdOrName, + #[display(fmt = "From Playtime column")] + FromClipColumn, +} + +impl Default for VirtualTrackType { + fn default() -> Self { + Self::This + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Serialize, + Deserialize, + Display, +)] +#[repr(usize)] +pub enum MappingSnapshotTypeForLoad { + #[display(fmt = "")] + #[serde(rename = "initial")] + Initial, + #[display(fmt = "By ID")] + ById, +} + +impl Default for MappingSnapshotTypeForLoad { + fn default() -> Self { + Self::Initial + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Serialize, + Deserialize, + Display, +)] +#[repr(usize)] +pub enum MappingSnapshotTypeForTake { + #[display(fmt = "")] + #[serde(rename = "last-loaded")] + LastLoaded, + #[display(fmt = "By ID")] + ById, +} + +impl Default for MappingSnapshotTypeForTake { + fn default() -> Self { + Self::LastLoaded + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum BookmarkAnchorType { + #[display(fmt = "By ID")] + Id, + #[display(fmt = "At position")] + Index, +} + +impl Default for BookmarkAnchorType { + fn default() -> Self { + Self::Id + } +} + +impl VirtualTrackType { + pub fn from_virtual_track(virtual_track: &VirtualTrack) -> Self { + use VirtualTrack::*; + match virtual_track { + This => Self::This, + Selected { allow_multiple } => { + if *allow_multiple { + Self::AllSelected + } else { + Self::Selected + } + } + Dynamic { scope, .. } => match scope { + TrackScope::AllTracks => Self::Dynamic, + TrackScope::TracksVisibleInTcp => Self::DynamicTcp, + TrackScope::TracksVisibleInMcp => Self::DynamicMcp, + }, + Master => Self::Master, + Unit => Self::Unit, + ByIdOrName(_, _) => Self::ByIdOrName, + ById(_) => Self::ById, + ByName { allow_multiple, .. } => { + if *allow_multiple { + Self::AllByName + } else { + Self::ByName + } + } + ByIndex { scope, .. } => match scope { + TrackScope::AllTracks => Self::ByIndex, + TrackScope::TracksVisibleInTcp => Self::ByIndexTcp, + TrackScope::TracksVisibleInMcp => Self::ByIndexMcp, + }, + FromClipColumn { .. } => Self::FromClipColumn, + } + } + + pub fn is_dynamic(&self) -> bool { + matches!(self, Self::Dynamic | Self::DynamicTcp | Self::DynamicMcp) + } + + pub fn is_by_index(&self) -> bool { + matches!(self, Self::ByIndex | Self::ByIndexTcp | Self::ByIndexMcp) + } + + pub fn virtual_track_scope(&self) -> Option { + use VirtualTrackType::*; + match self { + ByIndex | Dynamic => Some(TrackScope::AllTracks), + ByIndexTcp | DynamicTcp => Some(TrackScope::TracksVisibleInTcp), + ByIndexMcp | DynamicMcp => Some(TrackScope::TracksVisibleInMcp), + _ => None, + } + } + + pub fn refers_to_project(&self) -> bool { + use VirtualTrackType::*; + matches!(self, ByIdOrName | ById) + } + + pub fn is_sticky(&self) -> bool { + use VirtualTrackType::*; + matches!(self, ByIdOrName | ById | This | Master) + } + + pub fn track_selected_condition_makes_sense(&self) -> bool { + use VirtualTrackType::*; + !matches!(self, Selected | AllSelected) + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum VirtualFxType { + #[display(fmt = "")] + #[serde(rename = "this")] + This, + #[display(fmt = "")] + #[serde(rename = "focused")] + Focused, + #[display(fmt = "")] + #[serde(rename = "instance")] + Unit, + #[display(fmt = "")] + #[serde(rename = "dynamic")] + Dynamic, + #[display(fmt = "Particular")] + #[serde(rename = "id")] + ById, + #[display(fmt = "Named")] + #[serde(rename = "name")] + ByName, + #[display(fmt = "All named")] + AllByName, + #[display(fmt = "At position")] + #[serde(rename = "index")] + ByIndex, + #[display(fmt = "By ID or pos (legacy)")] + #[serde(rename = "id-or-index")] + ByIdOrIndex, +} + +impl Default for VirtualFxType { + fn default() -> Self { + Self::ById + } +} + +impl VirtualFxType { + pub fn from_virtual_fx(virtual_fx: &VirtualFx) -> Self { + use VirtualFx::*; + match virtual_fx { + This => VirtualFxType::This, + LastFocused => VirtualFxType::Focused, + Unit => VirtualFxType::Unit, + ChainFx { chain_fx, .. } => { + use VirtualChainFx::*; + match chain_fx { + Dynamic(_) => Self::Dynamic, + ById(_, _) => Self::ById, + ByName { allow_multiple, .. } => { + if *allow_multiple { + Self::AllByName + } else { + Self::ByName + } + } + ByIndex(_) => Self::ByIndex, + ByIdOrIndex(_, _) => Self::ByIdOrIndex, + } + } + } + } + + pub fn requires_fx_chain(&self) -> bool { + use VirtualFxType::*; + match self { + This => false, + Focused => false, + Dynamic => true, + Unit => false, + ById => true, + ByName => true, + AllByName => true, + ByIndex => true, + ByIdOrIndex => true, + } + } + + pub fn refers_to_project(&self) -> bool { + use VirtualFxType::*; + matches!(self, ById | ByIdOrIndex) + } + + pub fn is_sticky(&self) -> bool { + use VirtualFxType::*; + matches!(self, ById | ByIdOrIndex | This) + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum VirtualFxParameterType { + #[display(fmt = "")] + #[serde(rename = "dynamic")] + Dynamic, + #[display(fmt = "Named")] + #[serde(rename = "name")] + ByName, + #[display(fmt = "Particular")] + #[serde(rename = "index")] + ById, + #[display(fmt = "At position")] + #[serde(rename = "index-manual")] + ByIndex, +} + +impl Default for VirtualFxParameterType { + fn default() -> Self { + Self::ById + } +} + +impl VirtualFxParameterType { + pub fn from_virtual_fx_parameter(param: &VirtualFxParameter) -> Self { + use VirtualFxParameter::*; + match param { + Dynamic(_) => Self::Dynamic, + ByName(_) => Self::ByName, + ByIndex(_) => Self::ByIndex, + ById(_) => Self::ById, + } + } + + pub fn is_sticky(&self) -> bool { + use VirtualFxParameterType::*; + matches!(self, ById) + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum TrackRouteSelectorType { + #[display(fmt = "")] + #[serde(rename = "dynamic")] + Dynamic, + #[display(fmt = "Particular")] + #[serde(rename = "id")] + ById, + #[display(fmt = "Named")] + #[serde(rename = "name")] + ByName, + #[display(fmt = "At position")] + #[serde(rename = "index")] + ByIndex, +} + +impl Default for TrackRouteSelectorType { + fn default() -> Self { + Self::ByIndex + } +} + +impl TrackRouteSelectorType { + pub fn from_route_selector(selector: &TrackRouteSelector) -> Self { + use TrackRouteSelector::*; + match selector { + Dynamic(_) => Self::Dynamic, + ById(_) => Self::ById, + ByName(_) => Self::ByName, + ByIndex(_) => Self::ByIndex, + } + } + + pub fn refers_to_project(&self) -> bool { + use TrackRouteSelectorType::*; + matches!(self, ById) + } + + pub fn is_sticky(&self) -> bool { + use TrackRouteSelectorType::*; + matches!(self, ById) + } +} + +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FxSnapshot { + #[serde(default, skip_serializing_if = "is_default")] + pub fx_type: String, + #[serde(default, skip_serializing_if = "is_default")] + pub fx_name: String, + #[serde(default, skip_serializing_if = "is_default")] + pub preset_name: Option, + #[serde(default, skip_serializing_if = "is_default")] + pub chunk: Rc, +} + +impl Clone for FxSnapshot { + fn clone(&self) -> Self { + Self { + fx_type: self.fx_type.clone(), + fx_name: self.fx_name.clone(), + preset_name: self.preset_name.clone(), + // We want a totally detached duplicate. + chunk: Rc::new((*self.chunk).clone()), + } + } +} + +impl Display for FxSnapshot { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let fmt_size = bytesize::ByteSize(self.chunk.len() as _); + write!( + f, + "{} | {} | {}", + self.preset_name.as_deref().unwrap_or("-"), + fmt_size, + self.fx_name, + ) + } +} + +#[derive(Default)] +pub struct TrackPropValues { + pub r#type: VirtualTrackType, + pub id: Option, + pub name: String, + pub expression: String, + pub index: u32, + pub clip_column: PlaytimeColumnDescriptor, + pub clip_column_track_context: ClipColumnTrackContext, +} + +impl TrackPropValues { + pub fn from_virtual_track(track: VirtualTrack) -> Self { + Self { + r#type: VirtualTrackType::from_virtual_track(&track), + id: track.id(), + name: track.name().unwrap_or_default(), + index: track.index().unwrap_or_default(), + clip_column: { + match track.clip_column().unwrap_or(&Default::default()) { + VirtualPlaytimeColumn::Active => PlaytimeColumnDescriptor::Active, + VirtualPlaytimeColumn::ByIndex(i) => { + PlaytimeColumnDescriptor::ByIndex(ColumnAddress::new(*i)) + } + VirtualPlaytimeColumn::Dynamic(_) => PlaytimeColumnDescriptor::Dynamic { + expression: Default::default(), + }, + } + }, + expression: Default::default(), + clip_column_track_context: track.clip_column_track_context().unwrap_or_default(), + } + } +} + +#[derive(Default)] +pub struct TrackRoutePropValues { + pub selector_type: TrackRouteSelectorType, + pub r#type: TrackRouteType, + pub id: Option, + pub name: String, + pub expression: String, + pub index: u32, +} + +impl TrackRoutePropValues { + pub fn from_virtual_route(route: VirtualTrackRoute) -> Self { + Self { + selector_type: TrackRouteSelectorType::from_route_selector(&route.selector), + r#type: route.r#type, + id: route.id(), + name: route.name().unwrap_or_default(), + index: route.index().unwrap_or_default(), + expression: Default::default(), + } + } +} + +#[derive(Default)] +pub struct FxPropValues { + pub r#type: VirtualFxType, + pub is_input_fx: bool, + pub id: Option, + pub name: String, + pub expression: String, + pub index: u32, +} + +impl FxPropValues { + pub fn from_virtual_fx(fx: VirtualFx) -> Self { + Self { + r#type: VirtualFxType::from_virtual_fx(&fx), + is_input_fx: fx.is_input_fx(), + id: fx.id(), + name: fx.name().unwrap_or_default(), + index: fx.index().unwrap_or_default(), + expression: Default::default(), + } + } +} + +#[derive(Default)] +pub struct FxParameterPropValues { + pub r#type: VirtualFxParameterType, + pub name: String, + pub expression: String, + pub index: u32, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum RealearnTrackArea { + #[serde(rename = "tcp")] + #[display(fmt = "Track control panel")] + Tcp, + #[serde(rename = "mcp")] + #[display(fmt = "Mixer control panel")] + Mcp, +} + +impl Default for RealearnTrackArea { + fn default() -> Self { + Self::Tcp + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Serialize_repr, + Deserialize_repr, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum RealearnAutomationMode { + #[display(fmt = "Trim/Read")] + TrimRead = 0, + #[display(fmt = "Read")] + Read = 1, + #[display(fmt = "Touch")] + Touch = 2, + #[display(fmt = "Write")] + Write = 3, + #[display(fmt = "Latch")] + Latch = 4, + #[display(fmt = "Latch Preview")] + LatchPreview = 5, +} + +impl Default for RealearnAutomationMode { + fn default() -> Self { + Self::TrimRead + } +} + +impl RealearnAutomationMode { + fn to_reaper(self) -> AutomationMode { + use RealearnAutomationMode::*; + match self { + TrimRead => AutomationMode::TrimRead, + Read => AutomationMode::Read, + Touch => AutomationMode::Touch, + Write => AutomationMode::Write, + Latch => AutomationMode::Latch, + LatchPreview => AutomationMode::LatchPreview, + } + } + + fn from_reaper(value: AutomationMode) -> Self { + use AutomationMode::*; + match value { + TrimRead => Self::TrimRead, + Read => Self::Read, + Touch => Self::Touch, + Write => Self::Write, + Latch => Self::Latch, + LatchPreview => Self::LatchPreview, + Unknown(_) => Self::TrimRead, + } + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + Serialize, + Deserialize, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum AutomationModeOverrideType { + #[serde(rename = "none")] + #[display(fmt = "None")] + None, + #[serde(rename = "bypass")] + #[display(fmt = "Bypass all envelopes")] + Bypass, + #[serde(rename = "override")] + #[display(fmt = "Override")] + Override, +} + +impl Default for AutomationModeOverrideType { + fn default() -> Self { + Self::Bypass + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + EnumIter, + Serialize, + Deserialize, + TryFromPrimitive, + IntoPrimitive, + Display, +)] +#[repr(usize)] +pub enum TargetUnit { + #[serde(rename = "native")] + Native, + #[serde(rename = "percent")] + Percent, +} + +impl Default for TargetUnit { + fn default() -> Self { + Self::Native + } +} + +#[derive(Debug)] +pub enum ConcreteTrackInstruction<'a> { + /// If the context is not available, other track properties won't get set. + This(Option<&'a ProcessorContext>), + /// If the context is not available, other track properties won't get set. + ById { + id: Option, + context: Option<&'a ProcessorContext>, + }, + ByIdWithTrack(Track), +} + +impl<'a> ConcreteTrackInstruction<'a> { + pub fn resolve(self) -> ResolvedConcreteTrackInstruction<'a> { + use ConcreteTrackInstruction::*; + ResolvedConcreteTrackInstruction { + track: match &self { + This(context) => context.and_then(|c| c.track().cloned()), + ById { + id: Some(id), + context: Some(c), + } => c + .project_or_current_project() + .track_by_guid(id) + .ok() + .and_then(|t| if t.is_available() { Some(t) } else { None }), + ByIdWithTrack(t) => Some(t.clone()), + _ => None, + }, + instruction: self, + } + } +} + +pub struct ResolvedConcreteTrackInstruction<'a> { + instruction: ConcreteTrackInstruction<'a>, + track: Option, +} + +impl ResolvedConcreteTrackInstruction<'_> { + pub fn virtual_track_type(&self) -> VirtualTrackType { + use ConcreteTrackInstruction::*; + match &self.instruction { + This(_) => VirtualTrackType::This, + ById { .. } | ByIdWithTrack(_) => VirtualTrackType::ById, + } + } + + pub fn id(&self) -> Option { + use ConcreteTrackInstruction::*; + match &self.instruction { + ById { id, .. } => *id, + _ => Some(*self.track.as_ref()?.guid()), + } + } + + pub fn name(&self) -> Option { + Some(self.track.as_ref()?.name()?.into_string()) + } + + pub fn index(&self) -> Option { + self.track.as_ref()?.index() + } +} + +#[derive(Debug)] +pub enum ConcreteFxInstruction<'a> { + /// If the context is not available, other FX properties won't get set. + This(Option<&'a ProcessorContext>), + /// If the context is not available, other FX properties won't get set. + ById { + is_input_fx: Option, + id: Option, + track: Option, + }, + ByIdWithFx(Fx), +} + +impl<'a> ConcreteFxInstruction<'a> { + pub fn resolve(self) -> ResolvedConcreteFxInstruction<'a> { + use ConcreteFxInstruction::*; + ResolvedConcreteFxInstruction { + fx: match &self { + This(context) => context.map(|c| c.containing_fx().clone()), + ById { + is_input_fx: Some(is_input_fx), + id: Some(id), + track: Some(t), + } => { + let chain = if *is_input_fx { + t.input_fx_chain() + } else { + t.normal_fx_chain() + }; + let fx = chain.fx_by_guid(id); + if fx.is_available() { + Some(fx) + } else { + None + } + } + ByIdWithFx(fx) => Some(fx.clone()), + _ => None, + }, + instruction: self, + } + } +} + +pub struct ResolvedConcreteFxInstruction<'a> { + instruction: ConcreteFxInstruction<'a>, + fx: Option, +} + +impl ResolvedConcreteFxInstruction<'_> { + pub fn virtual_fx_type(&self) -> VirtualFxType { + use ConcreteFxInstruction::*; + match self.instruction { + This(_) => VirtualFxType::This, + ById { .. } | ByIdWithFx(_) => VirtualFxType::ById, + } + } + + pub fn is_input_fx(&self) -> Option { + use ConcreteFxInstruction::*; + match &self.instruction { + ById { is_input_fx, .. } => *is_input_fx, + _ => Some(self.fx.as_ref()?.is_input_fx()), + } + } + + pub fn id(&self) -> Option { + use ConcreteFxInstruction::*; + match &self.instruction { + ById { id, .. } => *id, + _ => self.fx.as_ref()?.get_or_query_guid().ok(), + } + } + + pub fn name(&self) -> Option { + Some(get_fx_name(self.fx.as_ref()?)) + } + + pub fn index(&self) -> Option { + Some(self.fx.as_ref()?.index()) + } +} + +const TARGET_UNDEFINED_LABEL: &str = ""; +const TARGET_OBJECT_IRRELEVANT_LABEL: &str = ""; + +pub fn get_virtual_track_label( + virtual_track: &VirtualTrack, + compartment: CompartmentKind, + context: ExtendedProcessorContext, +) -> String { + use VirtualTrack::*; + match virtual_track { + ById(_) | ByIdOrName(_, _) => { + if let Ok(t) = first_effective_track(virtual_track, compartment, context) { + get_track_label(&t) + } else { + get_non_present_virtual_track_label(virtual_track) + } + } + _ => virtual_track.to_string(), + } +} + +pub fn get_virtual_fx_label( + fx_descriptor: &FxDescriptor, + compartment: CompartmentKind, + context: ExtendedProcessorContext, +) -> String { + match &fx_descriptor.fx { + VirtualFx::ChainFx { chain_fx, .. } => { + use VirtualChainFx::*; + match chain_fx { + ById(_, _) | ByIdOrIndex(_, _) => { + let optional_fx = first_effective_fx(fx_descriptor, compartment, context); + get_optional_fx_label(chain_fx, optional_fx.ok().as_ref()) + } + _ => fx_descriptor.fx.to_string(), + } + } + _ => fx_descriptor.fx.to_string(), + } +} + +pub fn get_track_label(track: &Track) -> String { + match track.location() { + TrackLocation::MasterTrack => MASTER_TRACK_LABEL.into(), + TrackLocation::NormalTrack(i) => { + let position = i + 1; + let name = track.name().expect("non-master track must have name"); + let name = name.to_str(); + if name.is_empty() { + format!("{position}. ") + } else { + format!("{position}. {name}") + } + } + } +} + +pub const MASTER_TRACK_LABEL: &str = ""; + +fn convert_monitoring_mode_to_reaper(monitoring_mode: MonitoringMode) -> InputMonitoringMode { + match monitoring_mode { + MonitoringMode::Off => InputMonitoringMode::Off, + MonitoringMode::Normal => InputMonitoringMode::Normal, + MonitoringMode::TapeStyle => InputMonitoringMode::NotWhenPlaying, + } +} + +fn convert_monitoring_mode_to_realearn(monitoring_mode: InputMonitoringMode) -> MonitoringMode { + match monitoring_mode { + InputMonitoringMode::Off => MonitoringMode::Off, + InputMonitoringMode::Normal => MonitoringMode::Normal, + InputMonitoringMode::NotWhenPlaying => MonitoringMode::TapeStyle, + InputMonitoringMode::Unknown(_) => MonitoringMode::Off, + } +} + +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Debug, + Default, + Serialize, + Deserialize, + derive_more::Display, + EnumIter, + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, +)] +#[repr(usize)] +pub enum MappingModificationKind { + #[display(fmt = "Learn target")] + #[default] + LearnTarget, + #[display(fmt = "Set target to last touched")] + SetTargetToLastTouched, +} + +impl MappingModificationKind { + pub fn from_modification(modification: &MappingModification) -> Self { + match modification { + MappingModification::LearnTarget(_) => Self::LearnTarget, + MappingModification::SetTargetToLastTouched(_) => Self::SetTargetToLastTouched, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, derive_more::Display, EnumIter)] +pub enum MakeFxNonStickyMode { + #[display(fmt = "")] + Focused, + #[display(fmt = "")] + Unit, + #[display(fmt = "Named")] + Named, + #[display(fmt = "All named")] + AllNamed, + #[display(fmt = "At position")] + AtPosition, +} + +#[derive(Copy, Clone, Eq, PartialEq, derive_more::Display, EnumIter)] +pub enum MakeTrackNonStickyMode { + #[display(fmt = "")] + Selected, + #[display(fmt = "")] + AllSelected, + #[display(fmt = "")] + Unit, + #[display(fmt = "Named")] + Named, + #[display(fmt = "All named")] + AllNamed, + #[display(fmt = "At position")] + AtPosition, + #[display(fmt = "At TCP position")] + AtTcpPosition, + #[display(fmt = "At MCP position")] + AtMcpPosition, +} + +impl MakeFxNonStickyMode { + pub fn build_virtual_fx(&self, fx: Option<&Fx>) -> Option { + let virtual_fx = match self { + MakeFxNonStickyMode::Focused => VirtualFx::LastFocused, + MakeFxNonStickyMode::Unit => VirtualFx::Unit, + MakeFxNonStickyMode::Named | MakeFxNonStickyMode::AllNamed => { + let fx = fx?; + VirtualFx::ChainFx { + is_input_fx: fx.is_input_fx(), + chain_fx: VirtualChainFx::ByName { + wild_match: WildMatch::new(fx.name().to_str()), + allow_multiple: *self == MakeFxNonStickyMode::AllNamed, + }, + } + } + MakeFxNonStickyMode::AtPosition => { + let fx = fx?; + VirtualFx::ChainFx { + is_input_fx: fx.is_input_fx(), + chain_fx: VirtualChainFx::ByIndex(fx.index()), + } + } + }; + Some(virtual_fx) + } +} + +impl MakeTrackNonStickyMode { + pub fn build_virtual_track(&self, track: Option<&Track>) -> Option { + let virtual_track = match self { + MakeTrackNonStickyMode::Selected => VirtualTrack::Selected { + allow_multiple: false, + }, + MakeTrackNonStickyMode::AllSelected => VirtualTrack::Selected { + allow_multiple: true, + }, + MakeTrackNonStickyMode::Unit => VirtualTrack::Unit, + MakeTrackNonStickyMode::Named | MakeTrackNonStickyMode::AllNamed => { + if let Some(name) = track?.name() { + VirtualTrack::ByName { + wild_match: WildMatch::new(name.to_str()), + allow_multiple: *self == MakeTrackNonStickyMode::AllNamed, + } + } else { + VirtualTrack::Master + } + } + MakeTrackNonStickyMode::AtPosition + | MakeTrackNonStickyMode::AtTcpPosition + | MakeTrackNonStickyMode::AtMcpPosition => { + if let Some(index) = track?.index() { + VirtualTrack::ByIndex { + index, + scope: match *self { + MakeTrackNonStickyMode::AtPosition => TrackScope::AllTracks, + MakeTrackNonStickyMode::AtTcpPosition => TrackScope::TracksVisibleInTcp, + MakeTrackNonStickyMode::AtMcpPosition => TrackScope::TracksVisibleInMcp, + _ => unreachable!(), + }, + } + } else { + VirtualTrack::Master + } + } + }; + Some(virtual_track) + } +} diff --git a/plugin-reaper-realearn/main/src/application/unit_model.rs b/plugin-reaper-realearn/main/src/application/unit_model.rs new file mode 100644 index 0000000..b075e82 --- /dev/null +++ b/plugin-reaper-realearn/main/src/application/unit_model.rs @@ -0,0 +1,3195 @@ +use crate::application::{ + get_track_label, share_group, share_mapping, Affected, AutoLoadMode, AutoUnitData, Change, + ChangeResult, CompartmentCommand, CompartmentModel, CompartmentPresetManager, + CompartmentPresetModel, CompartmentProp, FxId, FxPresetLinkConfig, GroupCommand, GroupModel, + MappingCommand, MappingModel, MappingProp, ModeCommand, PresetLinkManager, ProcessingRelevance, + SharedGroup, SharedInstanceModel, SharedMapping, SourceModel, TargetCategory, TargetModel, + TargetProp, MASTER_TRACK_LABEL, +}; +use crate::base::{notification, prop, when, AsyncNotifier, Prop}; +use crate::domain::{ + convert_plugin_param_index_range_to_iter, create_lua_feedback_script_runtime, + create_lua_midi_script_source_runtime, lua_module_path_without_ext, Backbone, BasicSettings, + CompartmentKind, CompartmentParamIndex, CompartmentSettings, CompoundMappingSource, + ControlContext, ControlInput, DomainEvent, DomainEventHandler, ExtendedProcessorContext, + FeedbackAudioHookTask, FeedbackOutput, FeedbackRealTimeTask, FinalSourceFeedbackValue, GroupId, + GroupKey, IncomingCompoundSourceValue, InputDescriptor, InstanceId, InternalInfoEvent, + LastTouchedTargetFilter, MainMapping, MappingId, MappingKey, MappingMatchedEvent, + MessageCaptureEvent, MidiControlInput, NormalMainTask, OscFeedbackTask, ParamSetting, + PluginParams, ProcessorContext, ProjectionFeedbackValue, QualifiedMappingId, + RealearnControlSurfaceMainTask, RealearnTarget, ReaperTarget, ReaperTargetType, SharedInstance, + SharedUnit, SourceFeedbackEvent, StayActiveWhenProjectInBackground, StreamDeckDeviceId, Tag, + TargetControlEvent, TargetTouchEvent, TargetValueChangedEvent, Unit, UnitContainer, UnitId, + VirtualControlElementId, VirtualFx, VirtualSource, VirtualSourceValue, + LUA_FEEDBACK_SCRIPT_RUNTIME_NAME, LUA_MIDI_SCRIPT_SOURCE_RUNTIME_NAME, +}; +use base::{Global, NamedChannelSender, SenderToNormalThread, SenderToRealTimeThread}; +use derivative::Derivative; +use enum_map::EnumMap; + +use reaper_high::Reaper; +use rx_util::Notifier; +use rxrust::prelude::*; +use std::cell::{OnceCell, Ref, RefCell}; +use std::fmt::{Debug, Display, Formatter}; + +use crate::domain; +use anyhow::{ensure, Context}; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use core::iter; +use helgoboss_learn::{AbsoluteMode, ControlResult, ControlValue, UnitValue}; +use helgobox_api::persistence::{ + FxDescriptor, MappingModification, TargetTouchCause, TrackDescriptor, + VirtualControlElementCharacter, +}; +use helgobox_api::runtime::InstanceInfoEvent; +use itertools::Itertools; +use reaper_medium::{InputMonitoringMode, RecordingInput}; +use std::error::Error; +use std::rc::{Rc, Weak}; +use std::{fmt, ptr}; +use tracing::{debug, trace}; + +pub trait UnitUi { + fn show_mapping(&self, compartment: CompartmentKind, mapping_id: MappingId); + fn show_pot_browser(&self); + fn target_value_changed(&self, event: TargetValueChangedEvent); + fn parameters_changed(&self, unit: &UnitModel); + fn midi_devices_changed(&self); + fn celebrate_success(&self); + fn conditions_changed(&self); + fn send_projection_feedback(&self, unit: &UnitModel, value: ProjectionFeedbackValue); + fn mapping_matched(&self, event: MappingMatchedEvent); + fn handle_target_control(&self, event: TargetControlEvent); + fn handle_source_feedback(&self, event: SourceFeedbackEvent); + fn handle_internal_info_event(&self, event: &InternalInfoEvent); + fn handle_external_info_event(&self, event: InstanceInfoEvent); + fn handle_everything_changed(&self, unit_model: &UnitModel); + fn handle_global_control_and_feedback_state_changed(&self); + fn handle_affected( + &self, + unit: &UnitModel, + affected: Affected, + initiator: Option, + ); +} + +/// This represents the user unit with one ReaLearn instance. +/// +/// It's ReaLearn's main object which keeps everything together. +#[derive(Derivative)] +#[derivative(Debug)] +pub struct UnitModel { + instance_id: InstanceId, + unit_id: UnitId, + enabled: bool, + /// Persisted and can be user-customized. Should be + /// unique but if not it's not a big deal, then it won't crash but the user can't be sure which + /// unit will be picked. Most relevant for HTTP/WS API. + unit_key: String, + pub name: Option, + pub let_matched_events_through: Prop, + pub let_unmatched_events_through: Prop, + pub stay_active_when_project_in_background: Prop, + pub real_input_logging_enabled: Prop, + pub real_output_logging_enabled: Prop, + pub virtual_input_logging_enabled: Prop, + pub virtual_output_logging_enabled: Prop, + pub target_control_logging_enabled: Prop, + pub send_feedback_only_if_armed: Prop, + pub reset_feedback_when_releasing_source: Prop, + pub control_input: Prop, + wants_keyboard_input: bool, + match_even_inactive_mappings: bool, + stream_deck_device_id: Option, + pub feedback_output: Prop>, + pub auto_load_mode: Prop, + pub auto_load_fallback_compartment: Option, + pub auto_load_fallback_preset_id: Option, + // -- + pub lives_on_upper_floor: Prop, + /// Unit tags + tags: Vec, + pub compartment_is_dirty: EnumMap>, + // Is set when in the state of learning multiple mappings ("batch learn") + learn_many_state: Prop>, + // We want that learn works independently of the UI, so they are unit properties. + active_controller_preset_id: Option, + active_main_preset_id: Option, + processor_context: ProcessorContext, + mappings: EnumMap>, + compartment_common_lua: EnumMap, + compartment_notes: EnumMap, + default_main_group: SharedGroup, + default_controller_group: SharedGroup, + groups: EnumMap>, + everything_changed_subject: LocalSubject<'static, (), ()>, + mapping_list_changed_subject: LocalSubject<'static, (CompartmentKind, Option), ()>, + group_list_changed_subject: LocalSubject<'static, CompartmentKind, ()>, + incoming_msg_captured_subject: LocalSubject<'static, MessageCaptureEvent, ()>, + mapping_subscriptions: EnumMap>>, + group_subscriptions: EnumMap>>, + normal_main_task_sender: SenderToNormalThread, + party_is_over_subject: LocalSubject<'static, (), ()>, + #[derivative(Debug = "ignore")] + ui: OnceCell>, + unit_container: &'static dyn UnitContainer, + // TODO-medium I already made an attempt to remove this because we share parameters via + // Arc now. However, the extended_context() method borrows this and it's + // used in many places. This is harder to replace because we can't just return something + // borrowed from the Arc. + /// Copy of all parameters (`ParameterManager` is the rightful owner). + params: PluginParams, + controller_preset_manager: Box, + main_preset_manager: Box, + global_preset_link_manager: Box, + unit_preset_link_config: FxPresetLinkConfig, + use_unit_preset_links_only: bool, + // It's okay not to use Weak here because the instance lives longer than the unit. + instance_model: SharedInstanceModel, + // It's okay not to use Weak here because the instance lives longer than the unit. + instance: SharedInstance, + unit: SharedUnit, + global_feedback_audio_hook_task_sender: &'static SenderToRealTimeThread, + feedback_real_time_task_sender: SenderToRealTimeThread, + global_osc_feedback_task_sender: &'static SenderToNormalThread, + control_surface_main_task_sender: &'static RealearnControlSurfaceMainTaskSender, + instance_track_descriptor: TrackDescriptor, + instance_fx_descriptor: FxDescriptor, + /// Set if this unit was added automatically. + auto_unit: Option, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct LearnManyState { + pub compartment: CompartmentKind, + pub current_mapping_id: MappingId, + pub sub_state: LearnManySubState, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum LearnManySubState { + LearningSource { + // Only relevant in controller compartment + control_element_character: VirtualControlElementCharacter, + }, + LearningTarget, +} + +impl LearnManyState { + pub fn learning_source( + compartment: CompartmentKind, + current_mapping_id: MappingId, + control_element_character: VirtualControlElementCharacter, + ) -> LearnManyState { + LearnManyState { + compartment, + current_mapping_id, + sub_state: LearnManySubState::LearningSource { + control_element_character, + }, + } + } + + pub fn learning_target( + compartment: CompartmentKind, + current_mapping_id: MappingId, + ) -> LearnManyState { + LearnManyState { + compartment, + current_mapping_id, + sub_state: LearnManySubState::LearningTarget, + } + } +} + +pub mod unit_defaults { + use crate::application::AutoLoadMode; + use crate::domain::StayActiveWhenProjectInBackground; + use helgobox_api::persistence::FxDescriptor; + + pub const LET_MATCHED_EVENTS_THROUGH: bool = false; + pub const LET_UNMATCHED_EVENTS_THROUGH: bool = true; + pub const STAY_ACTIVE_WHEN_PROJECT_IN_BACKGROUND: StayActiveWhenProjectInBackground = + StayActiveWhenProjectInBackground::OnlyIfBackgroundProjectIsRunning; + pub const WANTS_KEYBOARD_INPUT: bool = false; + pub const MATCH_EVEN_INACTIVE_MAPPINGS: bool = false; + pub const LIVES_ON_UPPER_FLOOR: bool = false; + pub const SEND_FEEDBACK_ONLY_IF_ARMED: bool = false; + pub const RESET_FEEDBACK_WHEN_RELEASING_SOURCE: bool = true; + pub const MAIN_PRESET_AUTO_LOAD_MODE: AutoLoadMode = AutoLoadMode::Off; + /// This is mainly for backward-compatibility with "Auto-load: Depending on focused FX" + /// but also is a quite common use case, so why not. + pub const INSTANCE_FX_DESCRIPTOR: FxDescriptor = FxDescriptor::Focused; +} + +impl UnitModel { + /// Creates an empty unit model. + #[allow(clippy::too_many_arguments)] + pub fn new( + instance_id: InstanceId, + unit_id: UnitId, + initial_name: Option, + context: ProcessorContext, + normal_main_task_sender: SenderToNormalThread, + instance_container: &'static dyn UnitContainer, + controller_manager: impl CompartmentPresetManager + 'static, + main_preset_manager: impl CompartmentPresetManager + 'static, + preset_link_manager: impl PresetLinkManager + 'static, + instance_model: SharedInstanceModel, + unit: SharedUnit, + global_feedback_audio_hook_task_sender: &'static SenderToRealTimeThread< + FeedbackAudioHookTask, + >, + feedback_real_time_task_sender: SenderToRealTimeThread, + global_osc_feedback_task_sender: &'static SenderToNormalThread, + control_surface_main_task_sender: &'static RealearnControlSurfaceMainTaskSender, + auto_unit: Option, + ) -> UnitModel { + let initial_input = auto_unit + .as_ref() + .map(|au| au.control_input()) + .unwrap_or_default(); + let initial_output = auto_unit.as_ref().and_then(|au| au.feedback_output()); + // Make unit key deterministic if we have an auto unit. That can help later if we want to let other units + // (or anything really) refer to automatically loaded units. + let initial_unit_key = auto_unit + .as_ref() + .map(|au| au.controller_id.clone()) + .unwrap_or_else(|| nanoid::nanoid!(8)); + let instance = instance_model.borrow().instance().clone(); + let mut model = Self { + unit_key: initial_unit_key, + instance_id, + unit_id, + enabled: true, + let_matched_events_through: prop(unit_defaults::LET_MATCHED_EVENTS_THROUGH), + let_unmatched_events_through: prop(unit_defaults::LET_UNMATCHED_EVENTS_THROUGH), + stay_active_when_project_in_background: prop( + unit_defaults::STAY_ACTIVE_WHEN_PROJECT_IN_BACKGROUND, + ), + real_input_logging_enabled: prop(false), + real_output_logging_enabled: prop(false), + virtual_input_logging_enabled: prop(false), + virtual_output_logging_enabled: prop(false), + target_control_logging_enabled: prop(false), + send_feedback_only_if_armed: prop(unit_defaults::SEND_FEEDBACK_ONLY_IF_ARMED), + reset_feedback_when_releasing_source: prop( + unit_defaults::RESET_FEEDBACK_WHEN_RELEASING_SOURCE, + ), + control_input: prop(initial_input), + wants_keyboard_input: unit_defaults::WANTS_KEYBOARD_INPUT, + match_even_inactive_mappings: unit_defaults::MATCH_EVEN_INACTIVE_MAPPINGS, + stream_deck_device_id: None, + feedback_output: prop(initial_output), + auto_load_mode: prop(unit_defaults::MAIN_PRESET_AUTO_LOAD_MODE), + auto_load_fallback_compartment: None, + auto_load_fallback_preset_id: None, + lives_on_upper_floor: prop(false), + tags: Default::default(), + compartment_is_dirty: Default::default(), + learn_many_state: prop(None), + active_controller_preset_id: None, + active_main_preset_id: None, + processor_context: context, + mappings: Default::default(), + compartment_common_lua: Default::default(), + compartment_notes: Default::default(), + default_main_group: Rc::new(RefCell::new(GroupModel::default_for_compartment( + CompartmentKind::Main, + ))), + default_controller_group: Rc::new(RefCell::new(GroupModel::default_for_compartment( + CompartmentKind::Controller, + ))), + groups: Default::default(), + everything_changed_subject: Default::default(), + mapping_list_changed_subject: Default::default(), + group_list_changed_subject: Default::default(), + incoming_msg_captured_subject: Default::default(), + mapping_subscriptions: Default::default(), + group_subscriptions: Default::default(), + normal_main_task_sender, + party_is_over_subject: Default::default(), + ui: OnceCell::new(), + unit_container: instance_container, + params: Default::default(), + controller_preset_manager: Box::new(controller_manager), + main_preset_manager: Box::new(main_preset_manager), + global_preset_link_manager: Box::new(preset_link_manager), + unit_preset_link_config: Default::default(), + use_unit_preset_links_only: false, + instance_model, + instance, + unit, + global_feedback_audio_hook_task_sender, + feedback_real_time_task_sender, + global_osc_feedback_task_sender, + control_surface_main_task_sender, + instance_track_descriptor: Default::default(), + instance_fx_descriptor: unit_defaults::INSTANCE_FX_DESCRIPTOR, + auto_unit: auto_unit.clone(), + name: initial_name, + }; + if let Some(auto_unit) = auto_unit { + let initial_controller_preset_id = auto_unit + .controller_preset_usage + .map(|u| u.controller_preset_id); + if let Some(id) = initial_controller_preset_id { + model.activate_controller_preset(Some(id)); + } + model.activate_main_preset(Some(auto_unit.main_preset_id)); + model + .unit() + .borrow_mut() + .set_control_unit_palette_color(auto_unit.controller_palette_color); + } + model + } + + pub fn is_enabled(&self) -> bool { + self.enabled + } + + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn name_or_key(&self) -> &str { + self.name.as_deref().unwrap_or(&self.unit_key) + } + + pub fn auto_unit(&self) -> Option<&AutoUnitData> { + self.auto_unit.as_ref() + } + + /// # Preconditions + /// + /// This unit model must have an auto unit whose ID matches the given one. + pub fn update_auto_unit(&mut self, new_unit: AutoUnitData) { + // Check preconditions + let old_auto_unit = self.auto_unit.take().expect("auto unit must be set"); + assert_eq!(old_auto_unit.controller_id, new_unit.controller_id); + // Update actual properties of unit + if new_unit.input != old_auto_unit.input { + self.control_input.set(new_unit.control_input()); + } + if new_unit.output != old_auto_unit.output { + self.feedback_output.set(new_unit.feedback_output()); + } + if new_unit.controller_preset_usage != old_auto_unit.controller_preset_usage { + let controller_preset_id = new_unit + .controller_preset_usage + .as_ref() + .map(|usage| usage.controller_preset_id.clone()); + self.activate_controller_preset(controller_preset_id) + } + if new_unit.main_preset_id != old_auto_unit.main_preset_id { + self.activate_main_preset(Some(new_unit.main_preset_id.clone())) + } + if new_unit.main_preset_id != old_auto_unit.main_preset_id { + self.activate_main_preset(Some(new_unit.main_preset_id.clone())) + } + if new_unit.controller_palette_color != old_auto_unit.controller_palette_color { + self.unit + .borrow_mut() + .set_control_unit_palette_color(new_unit.controller_palette_color); + } + // Update unit data itself + self.auto_unit.replace(new_unit); + } + + pub fn set_ui(&mut self, ui: impl UnitUi + 'static) { + if self.ui.set(Box::new(ui)).is_err() { + panic!("can set ui only once"); + } + } + + pub fn instance_id(&self) -> InstanceId { + self.instance_id + } + + pub fn unit_id(&self) -> UnitId { + self.unit_id + } + + pub fn unit_key(&self) -> &str { + &self.unit_key + } + + pub fn instance_track_descriptor(&self) -> &TrackDescriptor { + &self.instance_track_descriptor + } + + pub fn instance_fx_descriptor(&self) -> &FxDescriptor { + &self.instance_fx_descriptor + } + + /// Returns all unit tags. + pub fn tags(&self) -> &[Tag] { + &self.tags + } + + /// Returns whether this unit allows the given global auto unit to be created (not necessarily + /// and even most likely not in this instance by the way). It can give its veto by returning + /// `false`. + pub fn is_fine_with_global_auto_unit(&self, auto_unit_candidate: &AutoUnitData) -> bool { + if self.auto_unit.is_some() { + // Only manual units give veto + return true; + } + // Only active units give veto + let state = self.unit.borrow().global_control_and_feedback_state(); + // If the manual unit uses the same connection as the controller, the manual unit wins. We + // do this to prevent conflicts of manual units with automatic ones. Manual units need to + // take care of that manually, but with auto units we can easily avoid it. + if state.control_active && auto_unit_candidate.control_input() == self.control_input() { + return false; + } + if state.feedback_active && auto_unit_candidate.feedback_output() == self.feedback_output() + { + return false; + } + true + } + + pub fn receives_input_from(&self, input_descriptor: &InputDescriptor) -> bool { + match input_descriptor { + InputDescriptor::Midi { device_id, channel } => match self.control_input() { + ControlInput::Midi(MidiControlInput::FxInput) => { + if let Some(track) = self.processor_context().track() { + if !track.is_armed(true) { + return false; + } + if let Some(RecordingInput::Midi { + device_id: dev_id, + channel: ch, + }) = track.recording_input() + { + (dev_id.is_none() || dev_id == Some(*device_id)) + && (ch.is_none() || ch == *channel) + } else { + false + } + } else { + false + } + } + ControlInput::Midi(MidiControlInput::Device(dev_id)) => dev_id == *device_id, + _ => false, + }, + InputDescriptor::Osc { device_id } => match self.control_input() { + ControlInput::Osc(dev_id) => dev_id == *device_id, + _ => false, + }, + InputDescriptor::Keyboard => self.wants_keyboard_input, + InputDescriptor::StreamDeck { device_id } => { + self.stream_deck_device_id == Some(*device_id) + } + } + } + + pub fn find_mapping_with_source( + &self, + compartment: CompartmentKind, + source_value: IncomingCompoundSourceValue, + ) -> Option { + let virtualization = self.virtualize_source_value(source_value); + let instance_state = self.unit.borrow(); + let mapping = self.mappings(compartment).find(|m| { + let m = m.borrow(); + if !instance_state.mapping_is_on(m.qualified_id()) { + return false; + } + let mapping_source = m.source_model.create_source(); + if let (CompoundMappingSource::Virtual(virtual_source), Some(v)) = + (&mapping_source, &virtualization) + { + virtual_source.control(&v.virtual_source_value).is_some() + } else { + mapping_source + .reacts_to_source_value_with(source_value) + .is_some() + } + }); + let outcome = FindMappingOutcome { + mapping: mapping.cloned()?, + source_is_learnable: virtualization.map(|v| v.learnable).unwrap_or(true), + }; + Some(outcome) + } + + pub fn mappings_have_project_references(&self, compartment: CompartmentKind) -> bool { + mappings_have_project_references(self.mappings[compartment].iter()) + } + + pub fn virtualize_main_mappings(&mut self) -> Result<(), String> { + let count = self.mappings[CompartmentKind::Main] + .iter() + .filter(|m| { + let mut m = m.borrow_mut(); + if let Some(virtual_source_model) = self.virtualize_source_model(&m.source_model) { + m.source_model = virtual_source_model; + false + } else { + true + } + }) + .count(); + self.notify_everything_has_changed(); + if count > 0 { + return Err(format!("Couldn't virtualize {count} mappings.")); + } + Ok(()) + } + + pub fn mappings_are_read_only(&self, compartment: CompartmentKind) -> bool { + self.is_learning_many_mappings() + || (compartment == CompartmentKind::Main && self.main_preset_is_auto_loaded()) + } + + fn full_sync(&mut self) { + for compartment in CompartmentKind::enum_iter() { + self.sync_compartment_settings(compartment); + } + // It's important to sync feedback device first, otherwise the initial feedback messages + // won't arrive! + self.sync_settings(); + self.sync_upper_floor_membership(); + // Now sync mappings - which includes initial feedback. + for compartment in CompartmentKind::enum_iter() { + self.sync_all_mappings_full(compartment); + } + } + + /// Makes all autostart mappings hit the target. + pub fn notify_realearn_unit_started(&self) { + debug!("Sending NotifyRealearnUnitStarted"); + self.normal_main_task_sender + .send_complaining(NormalMainTask::NotifyRealearnUnitStarted); + } + + /// Instructs the main processor to hit the target directly. + /// + /// This doesn't invoke group interaction because it's meant to totally skip the mode. + pub fn hit_target(&self, id: QualifiedMappingId, value: ControlValue) { + self.normal_main_task_sender + .send_complaining(NormalMainTask::HitTarget { id, value }); + } + + /// Connects the dots. + // TODO-low Too large. Split this into several methods. + pub fn activate(&mut self, weak_unit: WeakUnitModel) { + // Initial sync + self.full_sync(); + // Whenever something in the group list changes, resubscribe to those groups and sync + // (because a mapping could have changed its group). + when(self.group_list_changed()) + .with(weak_unit.clone()) + .do_async(|shared_unit, compartment| { + let mut unit = shared_unit.borrow_mut(); + unit.sync_all_mappings_full(compartment); + unit.mark_compartment_dirty(compartment); + }); + // Whenever anything in a mapping list changes and other things which affect all + // processors (including the real-time processor which takes care of sources only), resync + // all mappings to *all* processors. + when(self.mapping_list_changed()) + .with(weak_unit.clone()) + .do_async(move |unit, (compartment, _)| { + unit.borrow_mut().sync_all_mappings_full(compartment); + }); + // Marking project as dirty if certain things are changed. Should only contain events that + // are triggered by the user. + when(self.settings_changed()) + .with(weak_unit.clone()) + .do_sync(move |s, _| { + s.borrow().mark_dirty(); + }); + when(self.mapping_list_changed()) + .with(weak_unit.clone()) + .do_sync(move |s, (compartment, _)| { + s.borrow_mut().mark_compartment_dirty(compartment); + }); + // Keep adding/removing instance to/from upper floor. + when(self.lives_on_upper_floor.changed()) + .with(weak_unit.clone()) + .do_sync(move |s, _| { + s.borrow().sync_upper_floor_membership(); + }); + // Keep syncing some general settings to real-time processor. + when(self.settings_changed()) + .with(weak_unit.clone()) + .do_async(move |s, _| { + s.borrow().sync_settings(); + }); + // When FX is reordered, invalidate FX indexes. This is primarily for the GUI. + // Existing GUID-tracked `Fx` instances will detect wrong index automatically. + when( + Global::control_surface_rx() + .fx_reordered() + // We have this explicit stop criteria because we listen to global REAPER events. + .take_until(self.party_is_over()), + ) + .with(weak_unit) + .do_async(|s, _| { + s.borrow_mut() + .invalidate_fx_indexes_of_mapping_targets(Rc::downgrade(&s)); + }); + } + + pub fn activate_auto_load_mode(&mut self, mode: AutoLoadMode) -> anyhow::Result<()> { + // Memorize or clear current preset ID for fallback purposes + self.auto_load_fallback_preset_id = match mode { + AutoLoadMode::Off => None, + AutoLoadMode::UnitFx => { + ensure!( + Reaper::get().version().revision() >= "6.37", + "Auto-load needs at least REAPER version 6.37!" + ); + self.active_main_preset_id.clone() + } + }; + // Set auto-load mode + self.auto_load_mode.set(mode); + Ok(()) + } + + pub fn main_preset_is_auto_loaded(&self) -> bool { + self.auto_load_mode.get().is_on() && self.active_main_preset_id.is_some() + } + + /// This returns an early `false` if the desired preset is already active. + fn auto_load_preset_linked_to_fx(&mut self, fx_id: Option) -> bool { + let final_preset_id = fx_id.and_then(|fx_id| self.find_preset_linked_to_fx(fx_id)); + self.auto_load_preset(final_preset_id) + } + + fn find_preset_linked_to_fx(&self, fx_id: FxId) -> Option { + if let Some(preset_id) = self + .unit_preset_link_config + .find_preset_linked_to_fx(&fx_id) + { + return Some(preset_id); + } + if self.use_unit_preset_links_only { + return None; + } + self.global_preset_link_manager + .find_preset_linked_to_fx(&fx_id) + } + + fn invalidate_fx_indexes_of_mapping_targets(&mut self, weak_unit: WeakUnitModel) { + let ids: Vec<_> = self + .all_mappings() + .map(|m| m.borrow().qualified_id()) + .collect(); + for id in ids { + self.change_mapping_by_id_with_closure(id, None, weak_unit.clone(), |ctx| { + let affected = ctx + .mapping + .target_model + .invalidate_fx_index(ctx.extended_context, ctx.mapping.compartment()) + .map(|affected| Affected::One(MappingProp::InTarget(affected))); + Ok(affected) + }) + .expect("error when invalidating FX indexes"); + } + } + + /// Settings are all the things displayed in the ReaLearn header panel. + fn settings_changed(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.let_matched_events_through + .changed() + .merge(self.let_unmatched_events_through.changed()) + .merge(self.stay_active_when_project_in_background.changed()) + .merge(self.control_input.changed()) + .merge(self.feedback_output.changed()) + .merge(self.send_feedback_only_if_armed.changed()) + .merge(self.reset_feedback_when_releasing_source.changed()) + .merge(self.auto_load_mode.changed()) + .merge(self.real_input_logging_enabled.changed()) + .merge(self.real_output_logging_enabled.changed()) + .merge(self.virtual_input_logging_enabled.changed()) + .merge(self.virtual_output_logging_enabled.changed()) + .merge(self.target_control_logging_enabled.changed()) + } + + pub fn captured_incoming_message(&mut self, event: MessageCaptureEvent) { + self.incoming_msg_captured_subject.next(event); + } + + pub fn create_compound_source_for_learning( + &self, + event: MessageCaptureEvent, + ) -> Option { + // At first, try virtualized + if event.allow_virtual_sources { + if let Some(virtualization) = self.virtualize_source_value(event.result.message()) { + if !virtualization.learnable { + return None; + } + let virtual_source = + VirtualSource::from_source_value(virtualization.virtual_source_value); + return Some(CompoundMappingSource::Virtual(virtual_source)); + } + } + // Then direct + CompoundMappingSource::from_message_capture_event(event) + } + + /// Attention: If a mapping matches but given source value is a relative-zero (and matches), it + /// will return a control value of "zero". This should be fine for most use cases, mostly it's + /// only the control element which matters, not the concrete control value. + pub fn virtualize_source_value( + &self, + source_value: IncomingCompoundSourceValue, + ) -> Option { + let instance_state = self.unit.borrow(); + let res = self + .active_virtual_controller_mappings(&instance_state) + .find_map(|m| { + let m = m.borrow(); + let control_result = m + .source_model + .create_source() + .reacts_to_source_value_with(source_value)?; + let control_value = match control_result { + ControlResult::Consumed => ControlValue::AbsoluteContinuous(UnitValue::MIN), + ControlResult::Processed(v) => v, + }; + let virtualization = SourceValueVirtualization { + virtual_source_value: VirtualSourceValue::new( + m.target_model.create_control_element(), + control_value, + ), + learnable: m.target_model.learnable(), + }; + Some(virtualization) + }); + res + } + + pub fn virtualize_source_model(&self, source_model: &SourceModel) -> Option { + let instance_state = self.unit.borrow(); + let res = self + .active_virtual_controller_mappings(&instance_state) + .find_map(|m| { + let m = m.borrow(); + if m.source_model.create_source() == source_model.create_source() { + let element = m.target_model.create_control_element(); + let virtual_source = + CompoundMappingSource::Virtual(VirtualSource::new(element)); + let mut virtual_model = SourceModel::new(); + let _ = virtual_model.apply_from_source(&virtual_source); + Some(virtual_model) + } else { + None + } + }); + res + } + + pub fn stream_deck_device_id(&self) -> Option { + self.stream_deck_device_id + } + + pub fn wants_keyboard_input(&self) -> bool { + self.wants_keyboard_input + } + + pub fn match_even_inactive_mappings(&self) -> bool { + self.match_even_inactive_mappings + } + + fn active_virtual_controller_mappings<'a>( + &'a self, + instance_state: &'a Unit, + ) -> impl Iterator { + self.mappings(CompartmentKind::Controller).filter(move |m| { + let m = m.borrow(); + if !m.control_is_enabled() { + return false; + } + if m.target_model.category() != TargetCategory::Virtual { + return false; + } + if !instance_state.mapping_is_on(m.qualified_id()) { + // Since virtual mappings support conditional activation, too! + return false; + } + true + }) + } + + pub fn incoming_msg_captured( + &self, + reenable_control_after_touched: bool, + allow_virtual_sources: bool, + osc_arg_index_hint: Option, + ) -> impl LocalObservable<'static, Item = MessageCaptureEvent, Err = ()> + 'static { + // TODO-low We should migrate this to the nice async-await mechanism that we use for global + // learning (via REAPER action). That way we don't need the subject and also don't need + // to pass the information through multiple processors whether we allow virtual sources. + // TODO-low Would be nicer to do this on subscription instead of immediately. from_fn()? + self.normal_main_task_sender + .send_complaining(NormalMainTask::StartLearnSource { + allow_virtual_sources, + osc_arg_index_hint, + }); + let main_sender = self.normal_main_task_sender.clone(); + self.incoming_msg_captured_subject + .clone() + .finalize(move || { + if reenable_control_after_touched { + main_sender.send_complaining(NormalMainTask::ReturnToControlMode); + } + }) + } + + fn learn_target(&mut self, target: &ReaperTarget, weak_unit: WeakUnitModel) { + // Prevent learning targets from other project tabs (leads to weird effects, just think + // about it) + if let Some(p) = target.project() { + if p != self.processor_context.project_or_current_project() { + return; + } + } + let qualified_id = self.unit.borrow_mut().set_mapping_which_learns_target(None); + if let Some(qualified_id) = qualified_id { + if let Some(mapping) = self.find_mapping_by_qualified_id(qualified_id).cloned() { + let mut mapping = mapping.borrow_mut(); + let compartment = mapping.compartment(); + self.change_target_with_closure(&mut mapping, None, weak_unit, |ctx| { + ctx.mapping.target_model.apply_from_target( + target, + ctx.extended_context, + compartment, + ) + }); + } + } + } + + pub fn processor_context(&self) -> &ProcessorContext { + &self.processor_context + } + + pub fn extended_context(&self) -> ExtendedProcessorContext { + self.extended_context_with_params(&self.params) + } + + pub fn extended_context_with_params<'a>( + &'a self, + params: &'a PluginParams, + ) -> ExtendedProcessorContext<'a> { + ExtendedProcessorContext::new(&self.processor_context, params, self.control_context()) + } + + pub fn control_context(&self) -> ControlContext { + ControlContext { + feedback_audio_hook_task_sender: self.global_feedback_audio_hook_task_sender, + feedback_real_time_task_sender: &self.feedback_real_time_task_sender, + osc_feedback_task_sender: self.global_osc_feedback_task_sender, + feedback_output: self.feedback_output(), + stream_deck_dev_id: self.stream_deck_device_id, + unit_container: self.unit_container, + instance_id: self.instance_id, + instance: &self.instance, + unit: self.unit(), + unit_id: self.unit_id(), + output_logging_enabled: self.real_output_logging_enabled.get(), + source_context: Default::default(), + mode_context: Default::default(), + processor_context: &self.processor_context, + } + } + + pub fn add_group_with_default_values( + &mut self, + compartment: CompartmentKind, + name: String, + ) -> GroupId { + let group = GroupModel::new_from_ui(compartment, name); + self.add_group(compartment, group) + } + + fn add_group(&mut self, compartment: CompartmentKind, group: GroupModel) -> GroupId { + let id = group.id(); + let shared_group = Rc::new(RefCell::new(group)); + self.groups[compartment].push(shared_group); + self.notify_group_list_changed(compartment); + id + } + + /// Also finds default group. + pub fn find_group_index_by_id_sorted( + &self, + compartment: CompartmentKind, + id: GroupId, + ) -> Option { + self.groups_sorted(compartment) + .position(|g| g.borrow().id() == id) + } + + pub fn group_contains_mappings(&self, compartment: CompartmentKind, id: GroupId) -> bool { + self.mappings(compartment) + .filter(|m| m.borrow().group_id() == id) + .count() + > 0 + } + + /// Doesn't find default group. + pub fn find_group_by_id( + &self, + compartment: CompartmentKind, + id: GroupId, + ) -> Option<&SharedGroup> { + self.groups[compartment] + .iter() + .find(|g| g.borrow().id() == id) + } + + pub fn find_group_by_key( + &self, + compartment: CompartmentKind, + key: &GroupKey, + ) -> Option<&SharedGroup> { + self.groups[compartment] + .iter() + .find(|g| g.borrow().key() == key) + } + + pub fn find_group_by_id_including_default_group( + &self, + compartment: CompartmentKind, + id: GroupId, + ) -> Option<&SharedGroup> { + if id.is_default() { + Some(self.default_group(compartment)) + } else { + self.find_group_by_id(compartment, id) + } + } + + pub fn find_group_by_index_sorted( + &self, + compartment: CompartmentKind, + index: usize, + ) -> Option<&SharedGroup> { + self.groups_sorted(compartment).nth(index) + } + + pub fn groups_sorted( + &self, + compartment: CompartmentKind, + ) -> impl Iterator { + iter::once(self.default_group(compartment)).chain( + self.groups[compartment] + .iter() + .sorted_by_key(|g| g.borrow().effective_name().to_owned()), + ) + } + + pub fn move_mappings_to_group( + &mut self, + compartment: CompartmentKind, + mapping_ids: &[MappingId], + group_id: GroupId, + weak_unit: WeakUnitModel, + ) -> anyhow::Result<()> { + for mapping_id in mapping_ids.iter() { + let id = QualifiedMappingId::new(compartment, *mapping_id); + self.change_mapping_from_unit( + id, + MappingCommand::SetGroupId(group_id), + weak_unit.clone(), + ); + } + self.notify_group_list_changed(compartment); + Ok(()) + } + + pub fn remove_group( + &mut self, + compartment: CompartmentKind, + id: GroupId, + delete_mappings: bool, + ) { + self.groups[compartment].retain(|g| g.borrow().id() != id); + if delete_mappings { + self.mappings[compartment].retain(|m| m.borrow().group_id() != id); + } else { + for m in self.mappings(compartment) { + let mut m = m.borrow_mut(); + if m.group_id() == id { + let _ = m.change(MappingCommand::SetGroupId(GroupId::default())); + } + } + } + self.notify_group_list_changed(compartment); + } + + /// Changes a mapping with notification and without initiator, expecting the mutable mapping + /// itself to be passed as parameter. + /// + /// # Panics + /// + /// Panics if mapping not found. + pub fn change_mapping_from_ui_simple( + weak_unit: WeakUnitModel, + mapping: &mut MappingModel, + cmd: MappingCommand, + initiator: Option, + ) { + let Some(unit) = weak_unit.upgrade() else { + // If unit is gone, it doesn't matter anyway + return; + }; + let mut unit = unit.borrow_mut(); + unit.change_mapping_from_ui_expert(mapping, cmd, initiator, weak_unit); + } + + pub fn change_mapping_from_ui_expert( + &mut self, + mapping: &mut MappingModel, + cmd: MappingCommand, + initiator: Option, + weak_unit: WeakUnitModel, + ) { + if let Some(affected) = mapping.change(cmd) { + use Affected as A; + let affected = A::One(UnitProp::InCompartment( + mapping.compartment(), + A::One(CompartmentProp::InMapping(mapping.id(), affected)), + )); + self.handle_affected(affected, initiator, weak_unit); + } + } + + pub fn change_group_from_ui_simple( + weak_unit: WeakUnitModel, + group: &mut GroupModel, + cmd: GroupCommand, + initiator: Option, + ) { + let unit = weak_unit.upgrade().expect("unit gone"); + let mut unit = unit.borrow_mut(); + unit.change_group_from_ui_expert(group, cmd, initiator, weak_unit); + } + + pub fn change_group_from_ui_expert( + &mut self, + group: &mut GroupModel, + cmd: GroupCommand, + initiator: Option, + weak_unit: WeakUnitModel, + ) { + if let Some(affected) = group.change(cmd) { + let affected = Affected::One(UnitProp::InCompartment( + group.compartment(), + Affected::One(CompartmentProp::InGroup(group.id(), affected)), + )); + self.handle_affected(affected, initiator, weak_unit); + } + } + + /// Changes a mapping with notification and without initiator. + /// + /// # Panics + /// + /// Panics if mapping not found. + fn change_mapping_from_unit( + &mut self, + id: QualifiedMappingId, + val: MappingCommand, + weak_unit: WeakUnitModel, + ) { + self.change_with_notification( + UnitCommand::ChangeCompartment( + id.compartment, + CompartmentCommand::ChangeMapping(id.id, Box::new(val)), + ), + None, + weak_unit, + ); + } + + /// The gateway point to change something in the unit just using commands, also deeply nested + /// things such as target properties. + /// + /// Reasoning: With this single point of entry for changing something in the unit, we can + /// easily intercept certain changes, notify the UI and so on. Without magic and without rxRust! + pub fn change_with_notification( + &mut self, + cmd: UnitCommand, + initiator: Option, + weak_unit: WeakUnitModel, + ) { + if let Ok(Some(affected)) = self.change(cmd) { + self.handle_affected(affected, initiator, weak_unit); + } + } + + /// Changes unit properties without notifying listeners. + pub fn change(&mut self, cmd: UnitCommand) -> ChangeResult { + use Affected as A; + use UnitCommand as C; + use UnitProp as P; + let affected = match cmd { + C::ToggleEnabled => { + self.enabled = !self.enabled; + Some(A::One(UnitProp::Enabled)) + } + C::SetEnabled(enabled) => { + self.enabled = enabled; + Some(A::One(UnitProp::Enabled)) + } + C::SetUnitName(unit_name) => { + self.name = unit_name; + Some(A::One(UnitProp::UnitName)) + } + C::SetUnitKey(unit_key) => { + self.unit_key = unit_key; + Some(A::One(UnitProp::UnitKey)) + } + C::SetUnitTags(tags) => { + self.tags = tags; + Some(A::One(UnitProp::UnitTags)) + } + C::SetInstanceTrack(api_desc) => { + let virtual_track = + domain::TrackDescriptor::from_api(api_desc.clone()).unwrap_or_default(); + let virtual_track = domain::TrackDescriptor { + enable_only_if_track_selected: false, + ..virtual_track + }; + self.instance_track_descriptor = api_desc; + self.unit + .borrow_mut() + .set_instance_track_descriptor(virtual_track); + self.normal_main_task_sender + .send_complaining(NormalMainTask::NotifyConditionsChanged); + Some(A::One(P::InstanceTrack)) + } + C::SetInstanceFx(api_desc) => { + let virtual_fx = + domain::FxDescriptor::from_api(api_desc.clone()).unwrap_or_default(); + let virtual_fx = domain::FxDescriptor { + enable_only_if_fx_has_focus: false, + ..virtual_fx + }; + self.instance_fx_descriptor = api_desc; + self.unit + .borrow_mut() + .set_instance_fx_descriptor(virtual_fx); + self.normal_main_task_sender + .send_complaining(NormalMainTask::NotifyConditionsChanged); + Some(A::One(P::InstanceFx)) + } + C::SetMatchEvenInactiveMappings(value) => { + self.match_even_inactive_mappings = value; + Some(A::One(P::MatchEvenInactiveMappings)) + } + C::SetWantsKeyboardInput(value) => { + self.wants_keyboard_input = value; + Some(A::One(P::WantsKeyboardInput)) + } + C::SetStreamDeckDevice(value) => { + self.stream_deck_device_id = value; + Some(A::One(P::StreamDeckDeviceId)) + } + C::ChangeCompartment(compartment, cmd) => self + .change_compartment_internal(compartment, cmd)? + .map(|affected| A::One(P::InCompartment(compartment, affected))), + C::AdjustMappingModeIfNecessary(id) => self + .changing_mapping_by_id(id, |ctx| { + Ok(ctx.mapping.adjust_mode_if_necessary(ctx.extended_context)) + })? + .map(|affected| A::One(P::InCompartment(id.compartment, affected))), + }; + Ok(affected) + } + + pub fn change_mapping_by_id_with_closure( + &mut self, + id: QualifiedMappingId, + initiator: Option, + weak_unit: WeakUnitModel, + f: impl FnOnce(MappingChangeContext) -> ChangeResult, + ) -> Result<(), String> { + use Affected as A; + use UnitProp as P; + let affected = self + .changing_mapping_by_id(id, f)? + .map(|affected| A::One(P::InCompartment(id.compartment, affected))); + if let Some(affected) = affected { + self.handle_affected(affected, initiator, weak_unit); + } + Ok(()) + } + + pub fn change_target_with_closure( + &mut self, + mapping: &mut MappingModel, + initiator: Option, + weak_unit: WeakUnitModel, + f: impl FnOnce(MappingChangeContext) -> Option>, + ) { + let _ = self.change_mapping_with_closure(mapping, initiator, weak_unit, |ctx| { + Ok(f(ctx).map(|affected| Affected::One(MappingProp::InTarget(affected)))) + }); + } + + pub fn change_mapping_with_closure( + &mut self, + mapping: &mut MappingModel, + initiator: Option, + weak_unit: WeakUnitModel, + f: impl FnOnce(MappingChangeContext) -> ChangeResult, + ) -> Result<(), String> { + use Affected as A; + use UnitProp as P; + let affected = self + .changing_mapping(mapping, f)? + .map(|affected| A::One(P::InCompartment(mapping.compartment(), affected))); + if let Some(affected) = affected { + self.handle_affected(affected, initiator, weak_unit); + } + Ok(()) + } + + pub fn notify_compartment_has_changed( + &mut self, + compartment: CompartmentKind, + weak_unit: WeakUnitModel, + ) { + use Affected as A; + self.handle_affected( + A::One(UnitProp::InCompartment(compartment, A::Multiple)), + None, + weak_unit, + ); + } + + pub fn notify_mapping_has_changed(&mut self, id: QualifiedMappingId, weak_unit: WeakUnitModel) { + use Affected as A; + self.handle_affected( + A::One(UnitProp::InCompartment( + id.compartment, + A::One(CompartmentProp::InMapping(id.id, A::Multiple)), + )), + None, + weak_unit, + ); + } + + fn handle_affected( + &self, + affected: Affected, + initiator: Option, + weak_model: WeakUnitModel, + ) { + // We react in the next main loop cycle. First, because otherwise we can easily run into + // BorrowMut errors (because the handler might borrow the unit, but we still have it + // borrowed at this point because this handler is called by the unit). Second, because + // deferring the reaction seems to result in a smoother user experience. + // + // Sending all affected properties to the next main loop cycle as one batch can improve + // could make flickering less likely, so do it. + Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(move || { + // Internal reaction + let Some(model) = weak_model.upgrade() else { + // We panicked here before and that was sometimes popping up as an error. But + // if the unit doesn't exist anymore, then this is always a sign that the + // ReaLearn FX instance has been removed, which is fine. And whatever we + // want to do here then wouldn't matter anyway. So don't panic! + return; + }; + { + use Affected as A; + use CompartmentProp as CP; + use UnitProp as UP; + let mut model = model.borrow_mut(); + match &affected { + A::One( + UP::Enabled + | UP::WantsKeyboardInput + | UP::StreamDeckDeviceId + | UP::MatchEvenInactiveMappings, + ) => { + model.sync_settings(); + } + A::One(UP::InCompartment(compartment, A::One(CP::Notes))) => { + model.mark_compartment_dirty(*compartment); + } + A::One(UP::InCompartment(compartment, A::One(CP::CommonLua))) => { + model.sync_compartment_settings(*compartment); + model.mark_compartment_dirty(*compartment); + } + A::One(UP::InCompartment( + compartment, + A::One(CP::InGroup(_, affected)), + )) => { + // Sync all mappings to processor if necessary (change of a single + // group can affect many mappings) + if affected.processing_relevance().is_some() { + model.sync_all_mappings_full(*compartment); + } + // Mark dirty + model.mark_compartment_dirty(*compartment); + } + A::One(UP::InCompartment( + compartment, + A::One(CP::InMapping(mapping_id, affected)), + )) => { + // Sync mapping to processors if necessary. + if let Some(relevance) = affected.processing_relevance() { + if let Some(mapping) = + model.find_mapping_by_id(*compartment, *mapping_id) + { + let mapping = mapping.borrow(); + use ProcessingRelevance as R; + match relevance { + R::PersistentProcessingRelevant => { + // Keep syncing persistent mapping processing state only + // (must be cheap because can be triggered by processing). + model + .sync_persistent_mapping_processing_state(&mapping); + } + R::ProcessingRelevant => { + // Keep syncing complete mappings to processors. + model.sync_single_mapping_to_processors(&mapping); + } + } + } + } + // Mark dirty + model.mark_compartment_dirty(*compartment); + } + _ => {} + } + } + // UI reaction + { + // Borrowing the unit while UI update shouldn't be an issue + // because we are just invalidating the UI. A UI reaction shouldn't + // need to borrow the unit mutably. In case it's going to be an issue, + // we can also choose to clone the weak main panel instead. + let unit = model.borrow(); + unit.ui().handle_affected(&unit, affected, initiator); + } + }) + .unwrap(); + } + + pub fn ui(&self) -> &dyn UnitUi { + &**(self.ui.get().expect("UI not yet set")) + } + + fn change_compartment_internal( + &mut self, + compartment: CompartmentKind, + cmd: CompartmentCommand, + ) -> ChangeResult { + use CompartmentCommand as C; + let affected = match cmd { + C::ChangeMapping(mapping_id, cmd) => self.changing_mapping_by_id( + QualifiedMappingId::new(compartment, mapping_id), + move |ctx| Ok(ctx.mapping.change(*cmd)), + )?, + C::SetNotes(notes) => { + self.compartment_notes[compartment] = notes; + Some(Affected::One(CompartmentProp::Notes)) + } + C::SetCommonLua(notes) => { + self.compartment_common_lua[compartment] = notes; + Some(Affected::One(CompartmentProp::CommonLua)) + } + }; + Ok(affected) + } + + fn changing_mapping_by_id( + &mut self, + id: QualifiedMappingId, + f: impl FnOnce(MappingChangeContext) -> ChangeResult, + ) -> ChangeResult { + let mapping = self + .find_mapping_by_id(id.compartment, id.id) + .ok_or_else(|| String::from("mapping not found"))? + .clone(); + let mut mapping = mapping.borrow_mut(); + self.changing_mapping(&mut mapping, f) + } + + fn changing_mapping( + &mut self, + mapping: &mut MappingModel, + f: impl FnOnce(MappingChangeContext) -> ChangeResult, + ) -> ChangeResult { + let change_context = MappingChangeContext { + mapping, + extended_context: self.extended_context(), + }; + Ok(f(change_context)? + .map(|affected| Affected::One(CompartmentProp::InMapping(mapping.id(), affected)))) + } + + pub fn compartment_in_unit(&self, compartment: CompartmentKind) -> CompartmentInUnit { + CompartmentInUnit { + unit: self, + compartment, + } + } + + pub fn add_default_mapping( + &mut self, + compartment: CompartmentKind, + // Only relevant for main mapping compartment + initial_group_id: GroupId, + // Only relevant for controller mapping compartment + control_element_character: VirtualControlElementCharacter, + ) -> SharedMapping { + let mut mapping = MappingModel::new( + compartment, + initial_group_id, + MappingKey::random(), + MappingId::random(), + ); + let new_name = self.generate_name_for_new_mapping(compartment); + let _ = mapping.change(MappingCommand::SetName(new_name)); + if compartment == CompartmentKind::Controller { + let next_control_element_index = + self.get_next_control_element_index(control_element_character); + mapping.target_model = + TargetModel::virtual_default(control_element_character, next_control_element_index); + } + self.add_mapping(compartment, mapping) + } + + /// Silently assigns random keys if given keys conflict with existing keys or are not unique. + pub fn insert_mappings_at( + &mut self, + compartment: CompartmentKind, + index: usize, + mappings: impl Iterator, + ) { + let mut mapping_key_set = self.mapping_key_set(compartment); + let mut index = index.min(self.mappings[compartment].len()); + let mut first_mapping_id = None; + for mut m in mappings { + if !mapping_key_set.insert(m.key().clone()) { + m.reset_key(); + } + if first_mapping_id.is_none() { + first_mapping_id = Some(m.id()); + } + let shared_mapping = share_mapping(m); + self.mappings[compartment].insert(index, shared_mapping); + index += 1; + } + self.notify_mapping_list_changed(compartment, first_mapping_id); + } + + /// Silently assigns random keys if given keys conflict with existing keys or are not unique. + pub fn replace_mappings_of_group( + &mut self, + compartment: CompartmentKind, + group_id: GroupId, + mappings: impl Iterator, + ) { + let mut mapping_key_set = self.mapping_key_set(compartment); + self.mappings[compartment].retain(|m| m.borrow().group_id() != group_id); + for mut m in mappings { + if !mapping_key_set.insert(m.key().clone()) { + m.reset_key(); + } + let shared_mapping = share_mapping(m); + self.mappings[compartment].push(shared_mapping); + } + self.notify_mapping_list_changed(compartment, None); + } + + fn mapping_key_set(&self, compartment: CompartmentKind) -> NonCryptoHashSet { + self.mappings[compartment] + .iter() + .map(|m| m.borrow().key().clone()) + .collect() + } + + fn get_next_control_element_index(&self, character: VirtualControlElementCharacter) -> u32 { + let max_index_so_far = self + .mappings(CompartmentKind::Controller) + .filter_map(|m| { + let m = m.borrow(); + let target = &m.target_model; + if target.category() != TargetCategory::Virtual + || target.control_element_character() != character + { + return None; + } + if let VirtualControlElementId::Indexed(i) = target.control_element_id() { + Some(i) + } else { + None + } + }) + .max(); + if let Some(i) = max_index_so_far { + i + 1 + } else { + 0 + } + } + + pub fn start_learning_many_mappings( + &mut self, + unit: &SharedUnitModel, + compartment: CompartmentKind, + // Only relevant for main mapping compartment + initial_group_id: GroupId, + // Only relevant for controller mapping compartment + control_element_character: VirtualControlElementCharacter, + ) { + // Prepare + self.disable_control(); + self.stop_mapping_actions(); + // Add initial mapping and start learning its source + self.add_and_learn_one_of_many_mappings( + unit, + compartment, + initial_group_id, + control_element_character, + ); + // After target learned, add new mapping and start learning its source + let instance_state = self.unit.borrow(); + let prop_to_observe = match compartment { + // For controller mappings we don't need to learn a target so we move on to the next + // mapping as soon as the source has been learned. + CompartmentKind::Controller => instance_state.mapping_which_learns_source(), + // For main mappings we want to learn a target before moving on to the next mapping. + CompartmentKind::Main => instance_state.mapping_which_learns_target(), + }; + when( + prop_to_observe + .changed_to(None) + .take_until(self.learn_many_state.changed_to(None)), + ) + .with(Rc::downgrade(unit)) + .do_async(move |unit, _| { + unit.borrow_mut().add_and_learn_one_of_many_mappings( + &unit, + compartment, + initial_group_id, + control_element_character, + ); + }); + } + + fn add_and_learn_one_of_many_mappings( + &mut self, + unit: &SharedUnitModel, + compartment: CompartmentKind, + // Only relevant for main mapping compartment + initial_group_id: GroupId, + // Only relevant for controller mapping compartment + control_element_character: VirtualControlElementCharacter, + ) { + let ignore_sources: Vec<_> = match compartment { + // When batch-learning controller mappings, we just want to learn sources that have + // not yet been learned. Otherwise when we move a fader, we create many mappings in + // one go. + CompartmentKind::Controller => self + .mappings(compartment) + .map(|m| m.borrow().source_model.create_source()) + .collect(), + // When batch-learning main mappings, we always wait for a target touch between the + // mappings, so this is not necessary. + CompartmentKind::Main => vec![], + }; + let mapping = + self.add_default_mapping(compartment, initial_group_id, control_element_character); + let qualified_mapping_id = mapping.borrow().qualified_id(); + self.learn_many_state + .set(Some(LearnManyState::learning_source( + compartment, + qualified_mapping_id.id, + control_element_character, + ))); + self.start_learning_source_internal( + Rc::downgrade(unit), + qualified_mapping_id, + false, + ignore_sources, + ) + .expect("error during learn many"); + // If this is a main mapping, start learning target as soon as source learned. For + // controller mappings we don't need to do this because adding the default mapping will + // automatically increase the virtual target control element index (which is usually what + // one wants when creating a controller mapping). + if compartment == CompartmentKind::Main { + when( + self.unit() + .borrow() + .mapping_which_learns_source() + .changed_to(None) + .take_until(self.learn_many_state.changed_to(None)) + .take(1), + ) + .with(Rc::downgrade(unit)) + .do_async(move |shared_unit, _| { + let mut unit = shared_unit.borrow_mut(); + unit.learn_many_state + .set(Some(LearnManyState::learning_target( + compartment, + qualified_mapping_id.id, + ))); + let filter = (ReaperTargetType::all(), TargetTouchCause::Reaper); + unit.start_learning_target_internal( + Rc::downgrade(&shared_unit), + qualified_mapping_id, + false, + filter, + ); + }); + } + } + + pub fn stop_learning_many_mappings(&mut self) { + self.learn_many_state.set(None); + let source_learning_mapping_id = self.unit.borrow().mapping_which_learns_source().get(); + self.stop_mapping_actions(); + self.enable_control(); + // Remove last added mapping if source not learned already + if let Some(id) = source_learning_mapping_id { + self.remove_mapping(id); + } + } + + pub fn learn_many_state_changed( + &self, + ) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.learn_many_state.changed() + } + + pub fn is_learning_many_mappings(&self) -> bool { + self.learn_many_state.get_ref().is_some() + } + + pub fn learn_many_state(&self) -> Option<&LearnManyState> { + self.learn_many_state.get_ref().as_ref() + } + + pub fn mapping_count(&self, compartment: CompartmentKind) -> usize { + self.mappings[compartment].len() + } + + pub fn find_mapping_by_qualified_id(&self, id: QualifiedMappingId) -> Option<&SharedMapping> { + self.find_mapping_by_id(id.compartment, id.id) + } + + pub fn find_mapping_and_index_by_qualified_id( + &self, + id: QualifiedMappingId, + ) -> Option<(usize, &SharedMapping)> { + self.find_mapping_and_index_by_id(id.compartment, id.id) + } + + pub fn find_mapping_by_id( + &self, + compartment: CompartmentKind, + mapping_id: MappingId, + ) -> Option<&SharedMapping> { + Some( + self.find_mapping_and_index_by_id(compartment, mapping_id)? + .1, + ) + } + + pub fn find_mapping_and_index_by_id( + &self, + compartment: CompartmentKind, + mapping_id: MappingId, + ) -> Option<(usize, &SharedMapping)> { + self.mappings(compartment) + .enumerate() + .find(|(_, m)| m.borrow().id() == mapping_id) + } + + pub fn find_mapping_id_by_key( + &self, + compartment: CompartmentKind, + key: &MappingKey, + ) -> Option { + self.mappings(compartment).find_map(|m| { + let m = m.try_borrow().ok()?; + if m.key() == key { + Some(m.id()) + } else { + None + } + }) + } + + pub fn find_mapping_by_key( + &self, + compartment: CompartmentKind, + key: &MappingKey, + ) -> Option { + self.mappings(compartment) + .find(|m| { + let m = m.borrow(); + m.key() == key + }) + .cloned() + } + + pub fn mappings(&self, compartment: CompartmentKind) -> impl Iterator { + self.mappings[compartment].iter() + } + + pub fn default_group(&self, compartment: CompartmentKind) -> &SharedGroup { + match compartment { + CompartmentKind::Controller => &self.default_controller_group, + CompartmentKind::Main => &self.default_main_group, + } + } + + pub fn groups(&self, compartment: CompartmentKind) -> impl Iterator { + self.groups[compartment].iter() + } + + fn groups_including_default_group( + &self, + compartment: CompartmentKind, + ) -> impl Iterator { + std::iter::once(self.default_group(compartment)).chain(self.groups[compartment].iter()) + } + + fn all_mappings(&self) -> impl Iterator { + CompartmentKind::enum_iter().flat_map(move |compartment| self.mappings(compartment)) + } + + pub fn toggle_learning_source( + &mut self, + unit: WeakUnitModel, + mapping_id: QualifiedMappingId, + ) -> anyhow::Result<()> { + let currently_learning_mapping_id = self.unit.borrow().mapping_which_learns_source().get(); + if let Some(id) = currently_learning_mapping_id { + if id == mapping_id { + self.stop_learning_source(); + return Ok(()); + } + } + self.start_learning_source(unit, mapping_id, vec![]) + } + + fn start_learning_source( + &mut self, + unit: WeakUnitModel, + mapping_id: QualifiedMappingId, + ignore_sources: Vec, + ) -> anyhow::Result<()> { + let is_learning_already = self + .unit + .borrow() + .mapping_which_learns_source() + .get_ref() + .is_some(); + if is_learning_already { + // Learning active already. Simply change the mapping that's going to be learned. + self.unit + .borrow_mut() + .set_mapping_which_learns_source(Some(mapping_id)); + return Ok(()); + } + self.start_learning_source_internal(unit, mapping_id, true, ignore_sources) + } + + fn start_learning_source_internal( + &mut self, + unit: WeakUnitModel, + mapping_id: QualifiedMappingId, + reenable_control_after_touched: bool, + ignore_sources: Vec, + ) -> anyhow::Result<()> { + // Warn if settings are not good + if self.control_input.get().is_midi_fx_input() { + if let Some(track) = self.processor_context.track() { + if !track + .recording_input() + .is_some_and(|i| matches!(i, RecordingInput::Midi { .. })) + { + self.ui().handle_external_info_event( + InstanceInfoEvent::MidiLearnFromFxInputButTrackHasAudioInput, + ); + } else if !track.is_armed(false) + || track.input_monitoring_mode() != InputMonitoringMode::Normal + { + self.ui().handle_external_info_event( + InstanceInfoEvent::MidiLearnFromFxInputButTrackNotArmed, + ); + } + } + } + let allow_virtual_sources = mapping_id.compartment != CompartmentKind::Controller; + let osc_arg_index_hint = { + let mapping = self + .find_mapping_by_qualified_id(mapping_id) + .context("mapping not found")?; + let m = mapping.borrow(); + m.source_model.osc_arg_index() + }; + self.unit + .borrow_mut() + .set_mapping_which_learns_source(Some(mapping_id)); + when( + self.incoming_msg_captured( + reenable_control_after_touched, + allow_virtual_sources, + osc_arg_index_hint, + ) + .filter(move |capture_event: &MessageCaptureEvent| { + !ignore_sources.iter().any(|is| { + is.reacts_to_source_value_with(capture_event.result.message()) + .is_some() + }) + }) + // We have this explicit stop criteria because we listen to global REAPER + // events. + .take_until(self.party_is_over()) + // If the user stops learning manually without ever touching the controller. + .take_until( + self.unit + .borrow() + .mapping_which_learns_source() + .changed_to(None), + ), + ) + .with(unit) + .do_async(|shared_unit, event: MessageCaptureEvent| { + let mut unit = shared_unit.borrow_mut(); + let qualified_id = unit.unit.borrow().mapping_which_learns_source().get(); + if let Some(qualified_id) = qualified_id { + if let Some(source) = unit.create_compound_source_for_learning(event) { + // The learn process should stop when removing a mapping but just in case, + // let's react gracefully if the mapping doesn't exist anymore (do nothing). + let _ = unit.change_mapping_by_id_with_closure( + qualified_id, + None, + Rc::downgrade(&shared_unit), + |ctx| Ok(ctx.mapping.source_model.apply_from_source(&source)), + ); + unit.unit.borrow_mut().set_mapping_which_learns_source(None); + } + } + }); + Ok(()) + } + + fn stop_learning_source(&self) { + self.unit.borrow_mut().set_mapping_which_learns_source(None); + } + + pub fn toggle_learning_target(&mut self, unit: WeakUnitModel, mapping_id: QualifiedMappingId) { + let currently_learning_mapping_id = self.unit.borrow().mapping_which_learns_target().get(); + if let Some(id) = currently_learning_mapping_id { + if id == mapping_id { + self.stop_learning_target(); + return; + } + } + let filter = (ReaperTargetType::all(), TargetTouchCause::Reaper); + self.start_learning_target_internal(unit, mapping_id, true, filter); + } + + /// Not setting `included_targets` means all targets are potentially included. This should + /// not be used for presets / Lua code because the set of learnable targets will widen in + /// future. + fn start_learning_target_internal( + &mut self, + weak_unit: WeakUnitModel, + mapping_id: QualifiedMappingId, + handle_control_disabling: bool, + filter: (NonCryptoHashSet, TargetTouchCause), + ) { + let instance_id = self.unit_id; + if handle_control_disabling { + self.disable_control(); + } + self.unit + .borrow_mut() + .set_mapping_which_learns_target(Some(mapping_id)); + Global::future_support().spawn_in_main_thread_from_main_thread(async move { + let receiver = weak_unit + .upgrade() + .ok_or(UNIT_GONE)? + .borrow() + .control_surface_main_task_sender + .capture_targets(Some(instance_id)); + while let Ok(event) = receiver.recv().await { + let filter = LastTouchedTargetFilter { + included_target_types: &filter.0, + touch_cause: filter.1, + }; + if !filter.matches(&event) { + continue; + } + let unit = weak_unit.upgrade().ok_or(UNIT_GONE)?; + let mut unit = unit.borrow_mut(); + unit.learn_target(&event.target, weak_unit.clone()); + unit.stop_learning_target(); + } + Ok(()) + }); + } + + fn disable_control(&self) { + self.normal_main_task_sender + .send_complaining(NormalMainTask::DisableControl); + } + + fn enable_control(&self) { + self.normal_main_task_sender + .send_complaining(NormalMainTask::ReturnToControlMode); + } + + fn stop_learning_target(&self) { + self.control_surface_main_task_sender + .stop_capturing_targets(Some(self.unit_id)); + self.unit.borrow_mut().set_mapping_which_learns_target(None); + self.enable_control(); + } + + fn find_index_of_closest_mapping( + &self, + compartment: CompartmentKind, + mapping: &SharedMapping, + index: usize, + within_same_group: bool, + increment: isize, + ) -> Option { + let mappings = &self.mappings[compartment]; + let total_mapping_count = mappings.len(); + let result_index = if within_same_group { + let group_id = mapping.borrow().group_id(); + let mut i = index as isize + increment; + while i >= 0 && i < total_mapping_count as isize { + let m = &mappings[i as usize]; + if m.borrow().group_id() == group_id { + break; + } + i += increment; + } + i + } else { + index as isize + increment + }; + if result_index < 0 || result_index as usize >= total_mapping_count { + return None; + } + Some(result_index as usize) + } + + pub fn move_mapping_within_list( + &mut self, + compartment: CompartmentKind, + mapping_id: MappingId, + within_same_group: bool, + increment: isize, + ) -> Result<(), &str> { + let (current_index, mapping) = self + .find_mapping_and_index_by_id(compartment, mapping_id) + .ok_or("mapping not found")?; + let dest_index = self + .find_index_of_closest_mapping( + compartment, + mapping, + current_index, + within_same_group, + increment, + ) + .ok_or("move not possible because boundary reached")?; + let pending_mapping = self.mappings[compartment].remove(current_index); + self.mappings[compartment].insert(dest_index, pending_mapping); + self.notify_mapping_list_changed(compartment, None); + Ok(()) + } + + pub fn remove_mapping(&mut self, id: QualifiedMappingId) { + self.remove_mapping_internal(id); + } + + fn remove_mapping_internal(&mut self, id: QualifiedMappingId) { + self.stop_mapping_actions(); + self.mappings[id.compartment].retain(|m| m.borrow().id() != id.id); + self.notify_mapping_list_changed(id.compartment, None); + } + + fn stop_mapping_actions(&mut self) { + self.stop_learning_source(); + self.stop_learning_target(); + } + + pub fn duplicate_mapping(&mut self, id: QualifiedMappingId) -> anyhow::Result<()> { + let (index, mapping) = self.mappings[id.compartment] + .iter() + .enumerate() + .find(|(_i, m)| m.borrow().id() == id.id) + .context("mapping not found")?; + let duplicate = mapping.borrow().duplicate(); + let duplicate_id = duplicate.id(); + self.mappings[id.compartment].insert(index + 1, share_mapping(duplicate)); + self.notify_mapping_list_changed(id.compartment, Some(duplicate_id)); + Ok(()) + } + + pub fn has_mapping(&self, mapping: *const MappingModel) -> bool { + self.all_mappings().any(|m| ptr::eq(m.as_ptr(), mapping)) + } + + pub fn index_of_mapping( + &self, + compartment: CompartmentKind, + mapping_id: MappingId, + ) -> Option { + self.mappings[compartment] + .iter() + .position(|m| m.borrow().id() == mapping_id) + } + + pub fn show_in_floating_window(&self) { + let _ = self + .processor_context() + .containing_fx() + .show_in_floating_window(); + } + + pub fn containing_fx_is_in_input_fx_chain(&self) -> bool { + self.processor_context.containing_fx().is_input_fx() + } + + pub fn use_unit_preset_links_only(&self) -> bool { + self.use_unit_preset_links_only + } + + pub fn set_use_unit_preset_links_only(&mut self, value: bool) { + self.use_unit_preset_links_only = value; + } + + pub fn instance_preset_link_config(&self) -> &FxPresetLinkConfig { + &self.unit_preset_link_config + } + + pub fn instance_preset_link_config_mut(&mut self) -> &mut FxPresetLinkConfig { + &mut self.unit_preset_link_config + } + + pub fn set_instance_preset_link_config(&mut self, config: FxPresetLinkConfig) { + self.unit_preset_link_config = config; + } + + pub fn set_active_controller_id_without_notification( + &mut self, + active_controller_id: Option, + ) { + self.active_controller_preset_id = active_controller_id; + } + + pub fn set_active_main_preset_id_without_notification( + &mut self, + active_main_preset_id: Option, + ) { + self.active_main_preset_id = active_main_preset_id; + } + + pub fn active_controller_preset_id(&self) -> Option<&str> { + self.active_controller_preset_id.as_deref() + } + + pub fn active_preset_id(&self, compartment: CompartmentKind) -> Option<&str> { + let id = match compartment { + CompartmentKind::Controller => &self.active_controller_preset_id, + CompartmentKind::Main => &self.active_main_preset_id, + }; + id.as_deref() + } + + pub fn compartment_common_lua(&self, compartment: CompartmentKind) -> &str { + &self.compartment_common_lua[compartment] + } + + pub fn compartment_notes(&self, compartment: CompartmentKind) -> &str { + &self.compartment_notes[compartment] + } + + pub fn active_main_preset(&self) -> Option { + let id = self.active_preset_id(CompartmentKind::Main)?; + self.main_preset_manager.find_by_id(id) + } + + /// Returns `true` if the preset has unsaved changes (if a preset is active) or if at least one + /// mapping or group exists (if no preset is active). + pub fn compartment_or_preset_is_dirty(&self, compartment: CompartmentKind) -> bool { + if self.active_preset_id(compartment).is_some() { + // Preset active. + self.compartment_is_dirty[compartment].get() + } else { + // No preset active. + !self.mappings[compartment].is_empty() || !self.groups[compartment].is_empty() + } + } + + pub fn activate_controller_preset(&mut self, id: Option) { + let compartment = CompartmentKind::Controller; + let model = if let Some(id) = id.as_ref() { + self.controller_preset_manager + .find_by_id(id) + .map(|preset| preset.model().clone()) + } else { + // preset + None + }; + self.active_controller_preset_id = id; + self.replace_compartment(compartment, model); + self.compartment_is_dirty[compartment].set(false); + } + + pub fn auto_load_fallback_compartment(&self) -> Option<&CompartmentModel> { + self.auto_load_fallback_compartment.as_ref() + } + + pub fn set_auto_load_fallback_compartment_model( + &mut self, + compartment: Option, + ) { + self.auto_load_fallback_compartment = compartment; + } + + pub fn auto_load_fallback_preset_id(&self) -> Option<&str> { + self.auto_load_fallback_preset_id.as_deref() + } + + pub fn set_auto_load_fallback_preset_id(&mut self, preset_id: Option) { + self.auto_load_fallback_preset_id = preset_id; + } + + pub fn activate_preset(&mut self, compartment: CompartmentKind, id: Option) { + match compartment { + CompartmentKind::Controller => self.activate_controller_preset(id), + CompartmentKind::Main => self.activate_main_preset(id), + } + } + + pub fn activate_main_preset(&mut self, id: Option) { + let model = if let Some(id) = id.as_ref() { + self.main_preset_manager + .find_by_id(id) + .map(|preset| preset.model().clone()) + } else { + // preset + None + }; + self.activate_main_preset_internal(id, model); + } + + /// This returns an early `false` if the desired preset is already active. + fn auto_load_preset(&mut self, preset_id: Option) -> bool { + if self.active_main_preset_id == preset_id { + // Desired preset loaded already + return false; + } + if preset_id.is_none() + && self.auto_load_fallback_preset_id.is_some() + && self.active_main_preset_id == self.auto_load_fallback_preset_id + { + // Fallback preset loaded already + return false; + } + let instruction = self.take_auto_load_preset_load_instruction(preset_id); + self.activate_main_preset_internal(instruction.preset_id, instruction.compartment_model); + true + } + + fn take_auto_load_preset_load_instruction( + &mut self, + preset_id: Option, + ) -> PresetLoadInstruction { + if let Some(preset_id) = preset_id { + // There's a linked preset. Try to load it. + let compartment_model = self + .main_preset_manager + .find_by_id(&preset_id) + .map(|p| p.model().clone()); + // Maybe save current mappings for later fallback + if compartment_model.is_some() + && self.active_main_preset_id.is_none() + && self.auto_load_fallback_preset_id.is_none() + { + // Currently, preset is active and there's no fallback preset. + // Save current mappings, so that we can fall back to them later on. + let compartment_model = self.extract_compartment_model(CompartmentKind::Main); + self.auto_load_fallback_compartment = Some(compartment_model); + } + PresetLoadInstruction::new(Some(preset_id), compartment_model) + } else if let Some(fallback_preset_id) = &self.auto_load_fallback_preset_id { + // There's no linked preset. We have a fallback preset though. + let compartment_model = self + .main_preset_manager + .find_by_id(fallback_preset_id) + .map(|p| p.model().clone()); + PresetLoadInstruction::new(Some(fallback_preset_id.clone()), compartment_model) + } else { + // There's no linked preset and also no fallback preset. Fall back to ad-hoc memorized compartment. + PresetLoadInstruction::new(None, self.auto_load_fallback_compartment.take()) + } + } + + fn activate_main_preset_internal( + &mut self, + id: Option, + model: Option, + ) { + let compartment = CompartmentKind::Main; + self.active_main_preset_id = id; + self.replace_compartment(compartment, model); + self.compartment_is_dirty[compartment].set(false); + } + + pub fn extract_compartment_model(&self, compartment: CompartmentKind) -> CompartmentModel { + CompartmentModel { + parameters: self + .params + .compartment_params(compartment) + .non_default_settings(), + default_group: self.default_group(compartment).borrow().clone(), + groups: self + .groups(compartment) + .map(|ptr| ptr.borrow().clone()) + .collect(), + mappings: self + .mappings(compartment) + .map(|ptr| ptr.borrow().clone()) + .collect(), + custom_data: self + .unit() + .borrow() + .custom_compartment_data(compartment) + .clone(), + common_lua: self.compartment_common_lua[compartment].clone(), + notes: self.compartment_notes[compartment].clone(), + } + } + + /// Precondition: The given compartment model should be valid (e.g. no duplicate IDs)! + pub fn import_compartment( + &mut self, + compartment: CompartmentKind, + model: Option, + ) { + self.replace_compartment(compartment, model); + self.mark_compartment_dirty(compartment); + } + + /// Precondition: The given compartment model should be valid (e.g. no duplicate IDs)! + fn replace_compartment( + &mut self, + compartment: CompartmentKind, + model: Option, + ) { + self.stop_mapping_actions(); + if let Some(model) = model { + let default_group = match compartment { + CompartmentKind::Main => &mut self.default_main_group, + CompartmentKind::Controller => &mut self.default_controller_group, + }; + default_group.replace(model.default_group); + self.set_groups_without_notification(compartment, model.groups); + self.set_mappings_without_notification(compartment, model.mappings); + let compartment_params = self.params.compartment_params_mut(compartment); + compartment_params.reset_all(); + compartment_params.apply_given_settings(model.parameters); + self.unit + .borrow() + .parameter_manager() + .update_compartment_params(compartment, compartment_params.clone()); + self.unit + .borrow_mut() + .set_custom_compartment_data(compartment, model.custom_data); + self.compartment_common_lua[compartment] = model.common_lua; + self.compartment_notes[compartment] = model.notes; + } else { + self.clear_compartment_data(compartment); + } + self.reset_parameters(compartment); + self.notify_everything_has_changed(); + self.notify_compartment_loaded(compartment); + } + + pub fn notify_compartment_loaded(&self, compartment: CompartmentKind) { + self.normal_main_task_sender + .send_complaining(NormalMainTask::NotifyRealearnCompartmentLoaded(compartment)); + } + + fn reset_parameters(&self, compartment: CompartmentKind) { + let fx = self.processor_context.containing_fx().clone(); + let _ = Global::task_support().do_later_in_main_thread_from_main_thread_asap(move || { + for i in convert_plugin_param_index_range_to_iter(&compartment.plugin_param_range()) { + let _ = fx + .parameter_by_index(i.get()) + .set_reaper_normalized_value(0.0); + } + }); + } + + fn clear_compartment_data(&mut self, compartment: CompartmentKind) { + self.default_group(compartment) + .replace(GroupModel::default_for_compartment(compartment)); + self.set_groups_without_notification(compartment, std::iter::empty()); + self.set_mappings_without_notification(compartment, std::iter::empty()); + self.params.compartment_params_mut(compartment).reset_all(); + self.unit + .borrow() + .parameter_manager() + .update_compartment_params(compartment, Default::default()); + self.unit + .borrow_mut() + .set_custom_compartment_data(compartment, Default::default()); + self.compartment_common_lua[compartment] = Default::default(); + self.compartment_notes[compartment] = Default::default(); + } + + pub fn update_certain_param_settings( + &mut self, + compartment: CompartmentKind, + settings: Vec<(CompartmentParamIndex, ParamSetting)>, + ) { + let compartment_params = self.params.compartment_params_mut(compartment); + compartment_params.apply_given_settings(settings); + self.unit + .borrow() + .parameter_manager() + .update_compartment_params(compartment, compartment_params.clone()); + // We don't need to notify the UI because it will be done once the param container has + // propagated the changes to the unit again via event (uni-directional dataflow). + self.mark_compartment_dirty(compartment); + } + + /// Fires if everything has changed. Supposed to be used by UI, should rerender everything. + /// + /// The unit itself shouldn't subscribe to this. + pub fn everything_changed( + &self, + ) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.everything_changed_subject.clone() + } + + /// Fires when a mapping has been added, removed or changed its position in the list. + /// + /// Doesn't fire if a mapping in the list or if the complete list has changed. + pub fn mapping_list_changed( + &self, + ) -> impl LocalObservable<'static, Item = (CompartmentKind, Option), Err = ()> + 'static + { + self.mapping_list_changed_subject.clone() + } + + /// Fires when a group has been added or removed. + /// + /// Doesn't fire if a group in the list or if the complete list has changed. + pub fn group_list_changed( + &self, + ) -> impl LocalObservable<'static, Item = CompartmentKind, Err = ()> + 'static { + self.group_list_changed_subject.clone() + } + + pub fn params(&self) -> &PluginParams { + &self.params + } + + pub fn set_mappings_without_notification( + &mut self, + compartment: CompartmentKind, + mappings: impl IntoIterator, + ) { + self.mappings[compartment] = mappings.into_iter().map(share_mapping).collect(); + } + + pub fn set_groups_without_notification( + &mut self, + compartment: CompartmentKind, + groups: impl IntoIterator, + ) { + self.groups[compartment] = groups.into_iter().map(share_group).collect(); + } + + fn add_mapping( + &mut self, + compartment: CompartmentKind, + mapping: MappingModel, + ) -> SharedMapping { + let mapping_id = mapping.id(); + let shared_mapping = share_mapping(mapping); + self.mappings[compartment].push(shared_mapping.clone()); + self.notify_mapping_list_changed(compartment, Some(mapping_id)); + shared_mapping + } + + pub fn send_all_feedback(&self) { + self.normal_main_task_sender + .send_complaining(NormalMainTask::SendAllFeedback); + } + + pub fn log_debug_info(&self) { + self.log_debug_info_internal(); + self.normal_main_task_sender + .send_complaining(NormalMainTask::LogDebugInfo); + } + + pub fn log_mapping( + &self, + compartment: CompartmentKind, + mapping_id: MappingId, + ) -> anyhow::Result<()> { + let mapping = self + .find_mapping_by_id(compartment, mapping_id) + .context("mapping not found")?; + debug!( + "MappingModel struct size: {}", + std::mem::size_of::() + ); + debug!("{:?}", mapping); + self.normal_main_task_sender + .send_complaining(NormalMainTask::LogMapping(compartment, mapping_id)); + Ok(()) + } + + pub fn mapping_is_on(&self, id: QualifiedMappingId) -> bool { + self.unit.borrow().mapping_is_on(id) + } + + fn log_debug_info_internal(&self) { + // Summary + let msg = format!( + "\n\ + # Unit\n\ + \n\ + - Instance ID (random): {}\n\ + - ID (persistent, maybe custom): {}\n\ + - Main mapping count: {}\n\ + - Main mapping subscription count: {}\n\ + - Group count: {}\n\ + - Group subscription count: {}\n\ + - Controller mapping model count: {}\n\ + - Controller mapping subscription count: {}\n\ + ", + self.unit_id, + &self.unit_key, + self.mappings[CompartmentKind::Main].len(), + self.mapping_subscriptions[CompartmentKind::Main].len(), + self.groups.len(), + self.group_subscriptions.len(), + self.mappings[CompartmentKind::Controller].len(), + self.mapping_subscriptions[CompartmentKind::Controller].len(), + ); + Reaper::get().show_console_msg(msg); + // Detailled + trace!( + "\n\ + # Unit\n\ + \n\ + {:#?} + ", + self + ); + } + + pub fn find_mapping_with_target( + &self, + compartment: CompartmentKind, + target: &ReaperTarget, + ) -> Option<&SharedMapping> { + self.mappings(compartment).find(|m| { + m.borrow() + .with_context(self.extended_context()) + .has_target(target) + }) + } + + pub fn toggle_learn_source_for_target( + &mut self, + unit: &SharedUnitModel, + compartment: CompartmentKind, + target: &ReaperTarget, + toggle: bool, + ) -> SharedMapping { + let mapping = match self.find_mapping_with_target(compartment, target) { + None => { + let m = self.add_default_mapping( + compartment, + GroupId::default(), + VirtualControlElementCharacter::Multi, + ); + { + let mut mapping = m.borrow_mut(); + self.change_target_with_closure( + &mut mapping, + None, + Rc::downgrade(unit), + |ctx| { + if toggle { + ctx.mapping.mode_model.change(ModeCommand::SetAbsoluteMode( + AbsoluteMode::ToggleButton, + )); + } + ctx.mapping.target_model.apply_from_target( + target, + ctx.extended_context, + compartment, + ) + }, + ); + } + m + } + Some(m) => m.clone(), + }; + self.toggle_learning_source(Rc::downgrade(unit), mapping.borrow().qualified_id()) + .expect("error during toggle learn-source for target"); + mapping + } + + pub fn remove_mapping_by_target( + &mut self, + compartment: CompartmentKind, + target: &ReaperTarget, + ) { + if let Some(mapping) = self.find_mapping_with_target(compartment, target) { + let id = mapping.borrow().qualified_id(); + self.remove_mapping_internal(id); + } + } + + pub fn show_mapping(&self, compartment: CompartmentKind, mapping_id: MappingId) { + self.ui().show_mapping(compartment, mapping_id); + } + + /// Makes the main processor send feedback to the given sender instead of the configured + /// feedback output. + /// + /// Good for checking produced feedback when doing integration testing. + pub fn use_integration_test_feedback_sender( + &self, + sender: SenderToNormalThread, + ) { + self.normal_main_task_sender + .send_complaining(NormalMainTask::UseIntegrationTestFeedbackSender(sender)); + } + + /// Notifies listeners async that something in a mapping list has changed. + /// + /// Shouldn't be used if the complete list has changed. + fn notify_mapping_list_changed( + &mut self, + compartment: CompartmentKind, + new_mapping_id: Option, + ) { + AsyncNotifier::notify( + &mut self.mapping_list_changed_subject, + &(compartment, new_mapping_id), + ); + } + + /// Notifies listeners async that something in a group list has changed. + /// + /// Shouldn't be used if the complete list has changed. + fn notify_group_list_changed(&mut self, compartment: CompartmentKind) { + AsyncNotifier::notify(&mut self.group_list_changed_subject, &compartment); + } + + fn sync_upper_floor_membership(&self) { + let backbone_state = Backbone::get(); + if self.lives_on_upper_floor.get() { + backbone_state.make_superior(self.unit_id); + } else { + backbone_state.make_inferior(&self.unit_id); + } + } + + pub fn control_input(&self) -> ControlInput { + self.control_input.get() + } + + pub fn feedback_output(&self) -> Option { + self.feedback_output.get() + } + + pub fn unit(&self) -> &SharedUnit { + &self.unit + } + + fn sync_compartment_settings(&self, compartment: CompartmentKind) { + let common_lua_code = &self.compartment_common_lua[compartment]; + let common_lua_value = if common_lua_code.trim().is_empty() { + None + } else { + match compile_common_lua(compartment, common_lua_code) { + Ok(v) => Some(v), + Err(e) => { + notification::warn_user_about_anyhow_error(e); + None + } + } + }; + let settings = CompartmentSettings { + common_lua: common_lua_value, + }; + self.normal_main_task_sender + .send_complaining(NormalMainTask::UpdateCompartmentSettings( + compartment, + settings, + )); + } + + fn sync_settings(&self) { + let settings = BasicSettings { + unit_enabled: self.enabled, + control_input: self.control_input(), + wants_keyboard_input: self.wants_keyboard_input, + match_even_inactive_mappings: self.match_even_inactive_mappings, + streamdeck_device_id: self.stream_deck_device_id, + feedback_output: self.feedback_output(), + real_input_logging_enabled: self.real_input_logging_enabled.get(), + real_output_logging_enabled: self.real_output_logging_enabled.get(), + virtual_input_logging_enabled: self.virtual_input_logging_enabled.get(), + virtual_output_logging_enabled: self.virtual_output_logging_enabled.get(), + target_control_logging_enabled: self.target_control_logging_enabled.get(), + send_feedback_only_if_armed: self.send_feedback_only_if_armed.get(), + reset_feedback_when_releasing_source: self.reset_feedback_when_releasing_source.get(), + let_matched_events_through: self.let_matched_events_through.get(), + let_unmatched_events_through: self.let_unmatched_events_through.get(), + stay_active_when_project_in_background: self + .stay_active_when_project_in_background + .get(), + }; + self.normal_main_task_sender + .send_complaining(NormalMainTask::UpdateSettings(settings)); + } + + fn sync_persistent_mapping_processing_state(&self, mapping: &MappingModel) { + self.normal_main_task_sender.send_complaining( + NormalMainTask::UpdatePersistentMappingProcessingState { + id: mapping.qualified_id(), + state: mapping.create_persistent_mapping_processing_state(), + }, + ); + } + + fn sync_single_mapping_to_processors(&self, m: &MappingModel) { + self.instance + .borrow() + .notify_mappings_in_unit_changed(self.unit_id); + let group_data = self + .find_group_of_mapping(m) + .map(|g| g.borrow().create_data()) + .unwrap_or_default(); + let main_mapping = m.create_main_mapping(group_data); + self.normal_main_task_sender + .send_complaining(NormalMainTask::UpdateSingleMapping(Box::new(main_mapping))); + } + + pub fn instance_model(&self) -> &SharedInstanceModel { + &self.instance_model + } + + pub fn instance(&self) -> &SharedInstance { + &self.instance + } + + fn find_group_of_mapping(&self, mapping: &MappingModel) -> Option<&SharedGroup> { + let group_id = mapping.group_id(); + if group_id.is_default() { + let group = match mapping.compartment() { + CompartmentKind::Controller => &self.default_controller_group, + CompartmentKind::Main => &self.default_main_group, + }; + Some(group) + } else { + self.find_group_by_id(mapping.compartment(), group_id) + } + } + + /// Does a full mapping sync. + fn sync_all_mappings_full(&self, compartment: CompartmentKind) { + tracing::debug!(msg = "Sync all mappings full", %compartment); + self.instance + .borrow() + .notify_mappings_in_unit_changed(self.unit_id); + let main_mappings = self.create_main_mappings(compartment); + self.normal_main_task_sender + .send_complaining(NormalMainTask::UpdateAllMappings( + compartment, + main_mappings, + )); + } + + /// Creates mappings from mapping models so they can be distributed to different processors. + fn create_main_mappings(&self, compartment: CompartmentKind) -> Vec { + let group_map: NonCryptoHashMap> = self + .groups_including_default_group(compartment) + .map(|group| { + let group = group.borrow(); + (group.id(), group) + }) + .collect(); + // TODO-medium This is non-optimal if we have a group that uses an EEL activation condition + // and has many mappings. Because of our strategy of groups being an application-layer + // concept only, we equip *all* n mappings in that group with the group activation + // condition. The EEL compilation is done n times, but maybe worse: There are n EEL VMs + // in the domain layer and all of them have to run on parameter changes - whereas 1 would + // be enough if the domain layer would know about groups. + self.mappings(compartment) + .map(|mapping| { + let mapping = mapping.borrow(); + let group_data = group_map + .get(&mapping.group_id()) + .map(|g| g.create_data()) + .unwrap_or_default(); + mapping.create_main_mapping(group_data) + }) + .collect() + } + + fn generate_name_for_new_mapping(&self, compartment: CompartmentKind) -> String { + format!("{}", self.mappings[compartment].len() + 1) + } + + fn party_is_over(&self) -> impl LocalObservable<'static, Item = (), Err = ()> + 'static { + self.party_is_over_subject.clone() + } + + /// Shouldn't be called on load (project load, undo, redo, preset change). + pub fn mark_compartment_dirty(&mut self, compartment: CompartmentKind) { + debug!("Marking compartment as dirty"); + self.compartment_is_dirty[compartment].set(true); + self.mark_dirty(); + } + + /// Shouldn't be called on load (project load, undo, redo, preset change). + pub fn mark_dirty(&self) { + debug!("Marking unit as dirty"); + self.processor_context.notify_dirty(); + } + + /// Does a full resync and notifies the UI async. + /// + /// Explicitly doesn't mark the project as dirty - because this is also used when loading data + /// (project load, undo, redo, preset change). + pub fn notify_everything_has_changed(&mut self) { + if let Some(ui) = self.ui.get() { + ui.handle_everything_changed(self); + } + self.full_sync(); + // For UI + AsyncNotifier::notify(&mut self.everything_changed_subject, &()); + } +} + +impl Drop for UnitModel { + fn drop(&mut self) { + debug!("Dropping UnitModel..."); + self.party_is_over_subject.next(()); + } +} + +impl Display for UnitModel { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let fx_pos = self.processor_context.containing_fx().index() + 1; + let fx_name = self.processor_context.containing_fx().name(); + let unit_key = self.unit_key(); + if let Some(track) = self.processor_context.track() { + if track.is_master_track() { + f.write_str(MASTER_TRACK_LABEL)?; + } else { + let track_label = get_track_label(track); + let chain = if self.processor_context.containing_fx().chain().is_input_fx() { + "Input FX" + } else { + "Normal FX" + }; + write!(f, "Track \"{track_label}\" / {chain}")?; + } + } else { + write!(f, "Monitoring FX chain")?; + }; + write!(f, " / \"{fx_pos}. {fx_name}\" (ID \"{unit_key}\")")?; + Ok(()) + } +} + +impl DomainEventHandler for WeakUnitModel { + fn handle_event(&self, event: DomainEvent) -> Result<(), Box> { + let unit = self.upgrade().ok_or("unit not existing anymore")?; + use DomainEvent as E; + match event { + E::Info(evt) => { + let s = unit.try_borrow()?; + s.ui().handle_internal_info_event(evt); + } + E::ConditionsChanged => { + let s = unit.try_borrow()?; + s.ui().conditions_changed() + } + E::TimeForCelebratingSuccess => { + let s = unit.try_borrow()?; + s.ui().celebrate_success() + } + E::CapturedIncomingMessage(event) => { + unit.try_borrow_mut()?.captured_incoming_message(event); + } + E::UpdatedOnMappings(on_mappings) => { + unit.try_borrow()? + .unit + .try_borrow_mut()? + .set_on_mappings(on_mappings); + } + E::GlobalControlAndFeedbackStateChanged(state) => { + let unit = unit.try_borrow()?; + unit.unit + .try_borrow_mut()? + .set_global_control_and_feedback_state(state); + unit.ui().handle_global_control_and_feedback_state_changed(); + } + E::UpdatedSingleMappingOnState(event) => { + unit.try_borrow()? + .unit + .try_borrow_mut()? + .set_mapping_on(event.id, event.is_on); + } + E::TargetValueChanged(e) => { + // If the unit is borrowed already, just let it be. It happens only in a very + // particular case of reentrancy (because of a quirk in REAPER related to master + // tempo notification, https://github.com/helgoboss/helgobox/issues/199). If the + // target value slider is not updated then ... so what. + unit.try_borrow()?.ui().target_value_changed(e); + } + E::UpdatedSingleParameterValue { index, value } => { + let mut unit = unit.try_borrow_mut()?; + unit.params.at_mut(index).set_raw_value(value); + unit.ui().parameters_changed(&unit); + } + E::UpdatedAllParameters(params) => { + let mut unit = unit.try_borrow_mut()?; + unit.params = params; + unit.ui().parameters_changed(&unit); + } + E::FullResyncRequested => { + debug!("FullResyncRequested received"); + unit.try_borrow_mut()?.full_sync(); + } + E::MidiDevicesChanged => { + unit.try_borrow()?.ui().midi_devices_changed(); + } + E::ProjectionFeedback(value) => { + let s = unit.try_borrow()?; + s.ui().send_projection_feedback(&s, value); + } + E::MappingMatched(event) => { + let s = unit.try_borrow()?; + s.ui().mapping_matched(event); + } + E::HandleTargetControl(event) => { + let s = unit.try_borrow()?; + s.ui().handle_target_control(event); + } + E::HandleSourceFeedback(event) => { + let s = unit.try_borrow()?; + s.ui().handle_source_feedback(event); + } + E::MappingEnabledChangeRequested(event) => { + let mut s = unit.try_borrow_mut()?; + let id = QualifiedMappingId::new(event.compartment, event.mapping_id); + s.change_mapping_from_unit( + id, + MappingCommand::SetIsEnabled(event.is_enabled), + self.clone(), + ); + } + E::MappingModificationRequested(event) => { + let mut s = unit.try_borrow_mut()?; + let id = QualifiedMappingId::new(event.compartment, event.mapping_id); + match event.modification { + MappingModification::LearnTarget(m) => { + if event.value.is_on() { + let included_targets = m + .included_targets + .map(ReaperTargetType::from_learnable_target_kinds) + .unwrap_or_else(ReaperTargetType::all); + let filter = (included_targets, m.touch_cause.unwrap_or_default()); + s.start_learning_target_internal(self.clone(), id, false, filter); + } else { + s.stop_learning_target(); + } + } + MappingModification::SetTargetToLastTouched(m) => { + let included_targets = m + .included_targets + .map(ReaperTargetType::from_learnable_target_kinds) + .unwrap_or_else(ReaperTargetType::all); + let filter = LastTouchedTargetFilter { + included_target_types: &included_targets, + touch_cause: m.touch_cause.unwrap_or_default(), + }; + let Some(target) = Backbone::get().find_last_touched_target(filter) else { + return Ok(()); + }; + let Some(m) = s.find_mapping_by_qualified_id(id).cloned() else { + return Ok(()); + }; + let mut m = m.borrow_mut(); + s.change_target_with_closure(&mut m, None, self.clone(), |ctx| { + ctx.mapping.target_model.apply_from_target( + &target, + ctx.extended_context, + ctx.mapping.compartment(), + ) + }); + } + } + } + } + Ok(()) + } + + fn auto_load_different_preset_if_necessary(&self) -> anyhow::Result { + let unit_model = self.upgrade().context("unit model not existing anymore")?; + let unit_fx = { + let unit_model = unit_model + .try_borrow_mut() + .context("unit model already borrowed")?; + if unit_model.auto_load_mode.get() != AutoLoadMode::UnitFx { + return Ok(false); + } + let unit = unit_model + .unit + .try_borrow() + .context("unit already borrowed")?; + let unit_fx_descriptor = unit.instance_fx_descriptor(); + let unit_fx = unit_fx_descriptor + .resolve(unit_model.extended_context(), CompartmentKind::Main) + .unwrap_or_default() + .into_iter() + .next(); + unit_fx.filter(|unit_fx| { + if matches!(&unit_fx_descriptor.fx, VirtualFx::LastFocused) { + // Unit FX refers to the last-focused FX. We need to make a few more checks. + unit_fx.window_is_open() && unit_fx.window_has_focus() + } else { + // Unit FX is something else than the last-focused FX. Load linked preset! + true + } + }) + }; + let fx_id = unit_fx.and_then(|fx| FxId::from_fx(&fx, false).ok()); + let loaded = unit_model + .try_borrow_mut() + .context("unit model already borrowed")? + .auto_load_preset_linked_to_fx(fx_id); + Ok(loaded) + } +} + +/// Never store the strong reference to a unit (except in the main owner RealearnPlugin)! +/// +/// # Design +/// +/// ## Why `Rc>`? +/// +/// `Plugin#get_editor()` must return a Box of something 'static, so it's impossible to take a +/// reference here. Why? Because a reference needs a lifetime. Any non-static lifetime would +/// not satisfy the 'static requirement. Why not require a 'static reference then? Simply +/// because we don't have a unit object with static lifetime. The unit object is +/// owned by the `Plugin` object, which itself doesn't have a static lifetime. The only way +/// to get a 'static unit would be to not let the plugin object own the unit but to +/// define a static global. This, however, would be a far worse design than just using a +/// smart pointer here. So using a smart pointer is the best we can do really. +/// +/// This is not the only reason why taking a reference here is not feasible. During the +/// lifecycle of a ReaLearn unit we need mutable access to the unit both from the +/// editor (of course) and from the plugin (e.g. when REAPER wants us to load some data). +/// When using references, Rust's borrow checker wouldn't let that happen. We can't do anything +/// about this multiple-access requirement, it's just how the VST plugin API works (and +/// many other similar plugin interfaces as well - for good reasons). And being a plugin we +/// have to conform. +/// +/// Fortunately, we know that actually both DAW-plugin interaction (such as loading data) and +/// UI interaction happens in the main thread, in the so called main loop. So there's no +/// need for using a thread-safe smart pointer here. We even can and also should satisfy +/// the borrow checker, meaning that if the unit is mutably accessed at a given point in +/// time, it is not accessed from another point as well. This can happen even in a +/// single-threaded environment because functions can call other functions and thereby +/// accessing the same data - just in different stack positions. Just think of reentrancy. +/// Fortunately this is something we can control. And we should, because when this kind of +/// parallel access happens, this can lead to strange bugs which are particularly hard to +/// find. +/// +/// Unfortunately we can't make use of Rust's compile time borrow checker because there's no +/// way that the compiler understands what's going on here. Why? For one thing, because of +/// the VST plugin API design. But first and foremost because we use the FFI, which means +/// we interface with non-Rust code, so Rust couldn't get the complete picture even if the +/// plugin system would be designed in a different way. However, we *can* use Rust's +/// runtime borrow checker `RefCell`. And we should, because it gives us fail-fast +/// behavior. It will let us know immediately when we violated that safety rule. +/// TODO-low We must take care, however, that REAPER will not crash as a result, that would be +/// very bad. See https://github.com/RustAudio/vst-rs/issues/122 +pub type SharedUnitModel = Rc>; + +/// Always use this when storing a reference to a unit. This avoids memory leaks and ghost +/// units. +pub type WeakUnitModel = Weak>; + +fn mappings_have_project_references<'a>( + mut mappings: impl Iterator, +) -> bool { + mappings.any(mapping_has_project_references) +} + +/// Checks if the given mapping has references to a project, e.g. refers to track or FX by ID. +fn mapping_has_project_references(mapping: &SharedMapping) -> bool { + let mapping = mapping.borrow(); + let target = &mapping.target_model; + match target.category() { + TargetCategory::Reaper => { + if target.target_type().supports_track() && target.track_type().refers_to_project() { + return true; + } + target.supports_fx() && target.fx_type().refers_to_project() + } + TargetCategory::Virtual => false, + } +} + +pub fn reaper_supports_global_midi_filter() -> bool { + Reaper::get().version().revision() >= "6.35+dev0831" +} + +#[allow(dead_code)] +pub enum UnitCommand { + ToggleEnabled, + SetEnabled(bool), + SetUnitName(Option), + SetUnitKey(String), + SetUnitTags(Vec), + SetInstanceTrack(TrackDescriptor), + SetInstanceFx(FxDescriptor), + SetWantsKeyboardInput(bool), + SetMatchEvenInactiveMappings(bool), + SetStreamDeckDevice(Option), + ChangeCompartment(CompartmentKind, CompartmentCommand), + AdjustMappingModeIfNecessary(QualifiedMappingId), +} + +pub enum UnitProp { + Enabled, + UnitName, + UnitKey, + UnitTags, + InstanceTrack, + InstanceFx, + WantsKeyboardInput, + MatchEvenInactiveMappings, + StreamDeckDeviceId, + InCompartment(CompartmentKind, Affected), +} + +#[derive(Copy, Clone)] +pub struct CompartmentInUnit<'a> { + pub unit: &'a UnitModel, + pub compartment: CompartmentKind, +} + +impl<'a> CompartmentInUnit<'a> { + pub fn new(unit: &'a UnitModel, compartment: CompartmentKind) -> Self { + Self { unit, compartment } + } +} + +pub struct MappingChangeContext<'a> { + pub mapping: &'a mut MappingModel, + pub extended_context: ExtendedProcessorContext<'a>, +} + +#[derive(Debug)] +pub struct RealearnControlSurfaceMainTaskSender( + pub SenderToNormalThread>, +); + +impl RealearnControlSurfaceMainTaskSender { + pub fn remove_main_processor(&self, unit_id: UnitId) { + self.0 + .send_complaining(RealearnControlSurfaceMainTask::RemoveMainProcessor(unit_id)); + } + + pub fn capture_targets( + &self, + instance_id: Option, + ) -> async_channel::Receiver { + let (sender, receiver) = async_channel::bounded(500); + self.0 + .send_complaining(RealearnControlSurfaceMainTask::StartCapturingTargets( + instance_id, + sender, + )); + receiver + } + + pub fn stop_capturing_targets(&self, instance_id: Option) { + self.0 + .send_complaining(RealearnControlSurfaceMainTask::StopCapturingTargets( + instance_id, + )); + } +} + +const UNIT_GONE: &str = "unit gone"; + +fn compile_common_lua(compartment: CompartmentKind, code: &str) -> anyhow::Result { + let lua = unsafe { Backbone::main_thread_lua() }; + let env = lua.create_fresh_environment(false)?; + let require = lua.as_ref().create_function(move |lua, path: String| { + let val = match lua_module_path_without_ext(&path) { + LUA_MIDI_SCRIPT_SOURCE_RUNTIME_NAME => create_lua_midi_script_source_runtime(lua), + LUA_FEEDBACK_SCRIPT_RUNTIME_NAME => create_lua_feedback_script_runtime(lua), + _ => return Err(mlua::Error::runtime("Common compartment Lua scripts don't support the usage of 'require' for anything else than runtimes (at the moment)!")) + }; + Ok(val) + })?; + env.set("require", require)?; + lua.compile_and_execute(compartment.to_string(), code, env) +} + +struct PresetLoadInstruction { + preset_id: Option, + compartment_model: Option, +} + +impl PresetLoadInstruction { + pub fn new(preset_id: Option, compartment_model: Option) -> Self { + Self { + preset_id, + compartment_model, + } + } +} + +pub struct SourceValueVirtualization { + pub virtual_source_value: VirtualSourceValue, + pub learnable: bool, +} + +pub struct FindMappingOutcome { + pub mapping: SharedMapping, + pub source_is_learnable: bool, +} diff --git a/plugin-reaper-realearn/main/src/base/allocator.rs b/plugin-reaper-realearn/main/src/base/allocator.rs new file mode 100644 index 0000000..80a1032 --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/allocator.rs @@ -0,0 +1,54 @@ +use base::metrics_util::record_occurrence; +use helgobox_allocator::{AsyncDeallocationIntegration, Deallocate, HelgobossAllocator}; +use std::alloc::{GlobalAlloc, Layout, System}; + +#[global_allocator] +pub static GLOBAL_ALLOCATOR: HelgobossAllocator = + HelgobossAllocator::new(RealearnDeallocator::without_metrics()); + +/// Allocator integration which defers deallocation when in real-time thread. +pub struct RealearnAllocatorIntegration { + is_in_real_time_audio: IsInRealTimeAudio, +} + +pub type IsInRealTimeAudio = extern "C" fn() -> ::std::os::raw::c_int; + +impl RealearnAllocatorIntegration { + pub fn new(is_in_real_time_audio: IsInRealTimeAudio) -> Self { + Self { + is_in_real_time_audio, + } + } +} + +impl AsyncDeallocationIntegration for RealearnAllocatorIntegration { + fn offload_deallocation(&self) -> bool { + // Defer deallocation whenever we are in a real-time audio thread. + (self.is_in_real_time_audio)() != 0 + } +} + +pub struct RealearnDeallocator { + metric_id: Option<&'static str>, +} + +impl RealearnDeallocator { + pub const fn without_metrics() -> Self { + Self { metric_id: None } + } + + pub const fn with_metrics(metric_id: &'static str) -> Self { + Self { + metric_id: Some(metric_id), + } + } +} + +impl Deallocate for RealearnDeallocator { + fn deallocate(&self, ptr: *mut u8, layout: Layout) { + if let Some(id) = self.metric_id { + record_occurrence(id); + } + unsafe { System.dealloc(ptr, layout) }; + } +} diff --git a/plugin-reaper-realearn/main/src/base/bindings.rs b/plugin-reaper-realearn/main/src/base/bindings.rs new file mode 100644 index 0000000..fa67537 --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/bindings.rs @@ -0,0 +1,423 @@ +/* automatically generated by rust-bindgen 0.69.2 */ + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] +#![allow(deref_nullptr)] + +#[allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] +pub mod root { + #[allow(unused_imports)] + use self::super::root; + pub const NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS: u32 = 1; + pub const NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS_RESET: u32 = 2; + pub const NSEEL_CODE_COMPILE_FLAG_NOFPSTATE: u32 = 4; + pub const NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS: u32 = 8; + pub const NSEEL_MAX_VARIABLE_NAMELEN: u32 = 128; + pub const NSEEL_MAX_EELFUNC_PARAMETERS: u32 = 40; + pub const NSEEL_MAX_FUNCSIG_NAME: u32 = 2048; + pub const NSEEL_LOOPFUNC_SUPPORT_MAXLEN: u32 = 1048576; + pub const NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE: u32 = 2048; + pub const NSEEL_SHARED_GRAM_SIZE: u32 = 1048576; + pub const NSEEL_RAM_BLOCKS_DEFAULTMAX: u32 = 128; + pub const NSEEL_RAM_BLOCKS_LOG2: u32 = 9; + pub const NSEEL_RAM_ITEMSPERBLOCK_LOG2: u32 = 16; + pub const NSEEL_RAM_BLOCKS: u32 = 512; + pub const NSEEL_RAM_ITEMSPERBLOCK: u32 = 65536; + pub const NSEEL_STACK_SIZE: u32 = 4096; + pub mod std { + #[allow(unused_imports)] + use self::super::super::root; + } + pub type INT_PTR = isize; + pub type EEL_F = f64; + extern "C" { + pub fn NSEEL_HOSTSTUB_EnterMutex(); + } + extern "C" { + pub fn NSEEL_HOSTSTUB_LeaveMutex(); + } + extern "C" { + pub fn NSEEL_init() -> ::std::os::raw::c_int; + } + extern "C" { + pub fn NSEEL_quit(); + } + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct functionType { + _unused: [u8; 0], + } + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct eel_function_table { + pub list: *mut root::functionType, + pub list_size: ::std::os::raw::c_int, + } + #[test] + fn bindgen_test_layout_eel_function_table() { + const UNINIT: ::std::mem::MaybeUninit = + ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(eel_function_table)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(eel_function_table)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).list) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(eel_function_table), + "::", + stringify!(list) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).list_size) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(eel_function_table), + "::", + stringify!(list_size) + ) + ); + } + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct _compileContext { + _unused: [u8; 0], + } + pub type NSEEL_PPPROC = ::std::option::Option< + unsafe extern "C" fn( + data: *mut ::std::os::raw::c_void, + data_size: ::std::os::raw::c_int, + userfunc_data: *mut root::_compileContext, + ) -> *mut ::std::os::raw::c_void, + >; + extern "C" { + pub fn NSEEL_addfunctionex2( + name: *const ::std::os::raw::c_char, + nparms: ::std::os::raw::c_int, + code_startaddr: *mut ::std::os::raw::c_char, + code_len: ::std::os::raw::c_int, + pproc: root::NSEEL_PPPROC, + fptr: *mut ::std::os::raw::c_void, + fptr2: *mut ::std::os::raw::c_void, + destination: *mut root::eel_function_table, + ); + } + extern "C" { + pub fn NSEEL_addfunc_ret_type( + name: *const ::std::os::raw::c_char, + np: ::std::os::raw::c_int, + ret_type: ::std::os::raw::c_int, + pproc: root::NSEEL_PPPROC, + fptr: *mut ::std::os::raw::c_void, + destination: *mut root::eel_function_table, + ); + } + extern "C" { + pub fn NSEEL_addfunc_varparm_ex( + name: *const ::std::os::raw::c_char, + min_np: ::std::os::raw::c_int, + want_exact: ::std::os::raw::c_int, + pproc: root::NSEEL_PPPROC, + fptr: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut ::std::os::raw::c_void, + arg2: root::INT_PTR, + arg3: *mut *mut root::EEL_F, + ) -> root::EEL_F, + >, + destination: *mut root::eel_function_table, + ); + } + extern "C" { + pub fn NSEEL_addfunc_varparm_ctxptr( + name: *const ::std::os::raw::c_char, + min_np: ::std::os::raw::c_int, + want_exact: ::std::os::raw::c_int, + ctxptr: *mut ::std::os::raw::c_void, + fptr: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut ::std::os::raw::c_void, + arg2: root::INT_PTR, + arg3: *mut *mut root::EEL_F, + ) -> root::EEL_F, + >, + destination: *mut root::eel_function_table, + ); + } + extern "C" { + pub fn NSEEL_addfunc_varparm_ctxptr2( + name: *const ::std::os::raw::c_char, + min_np: ::std::os::raw::c_int, + want_exact: ::std::os::raw::c_int, + pproc: root::NSEEL_PPPROC, + ctx: *mut ::std::os::raw::c_void, + fptr: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut ::std::os::raw::c_void, + arg2: *mut ::std::os::raw::c_void, + arg3: root::INT_PTR, + arg4: *mut *mut root::EEL_F, + ) -> root::EEL_F, + >, + destination: *mut root::eel_function_table, + ); + } + extern "C" { + pub fn NSEEL_getstats() -> *mut ::std::os::raw::c_int; + } + pub type NSEEL_VMCTX = *mut ::std::os::raw::c_void; + pub type NSEEL_CODEHANDLE = *mut ::std::os::raw::c_void; + extern "C" { + pub fn NSEEL_VM_alloc() -> root::NSEEL_VMCTX; + } + extern "C" { + pub fn NSEEL_VM_free(ctx: root::NSEEL_VMCTX); + } + extern "C" { + pub fn NSEEL_VM_SetFunctionTable( + arg1: root::NSEEL_VMCTX, + tab: *mut root::eel_function_table, + ); + } + extern "C" { + pub fn NSEEL_VM_SetFunctionValidator( + arg1: root::NSEEL_VMCTX, + validateFunc: ::std::option::Option< + unsafe extern "C" fn( + fn_name: *const ::std::os::raw::c_char, + user: *mut ::std::os::raw::c_void, + ) -> *const ::std::os::raw::c_char, + >, + user: *mut ::std::os::raw::c_void, + ); + } + extern "C" { + pub fn NSEEL_VM_remove_unused_vars(_ctx: root::NSEEL_VMCTX); + } + extern "C" { + pub fn NSEEL_VM_clear_var_refcnts(_ctx: root::NSEEL_VMCTX); + } + extern "C" { + pub fn NSEEL_VM_remove_all_nonreg_vars(_ctx: root::NSEEL_VMCTX); + } + extern "C" { + pub fn NSEEL_VM_enumallvars( + ctx: root::NSEEL_VMCTX, + func: ::std::option::Option< + unsafe extern "C" fn( + name: *const ::std::os::raw::c_char, + val: *mut root::EEL_F, + ctx: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, + >, + userctx: *mut ::std::os::raw::c_void, + ); + } + extern "C" { + pub fn NSEEL_VM_regvar( + ctx: root::NSEEL_VMCTX, + name: *const ::std::os::raw::c_char, + ) -> *mut root::EEL_F; + } + extern "C" { + pub fn NSEEL_VM_getvar( + ctx: root::NSEEL_VMCTX, + name: *const ::std::os::raw::c_char, + ) -> *mut root::EEL_F; + } + extern "C" { + pub fn NSEEL_VM_get_var_refcnt( + _ctx: root::NSEEL_VMCTX, + name: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; + } + extern "C" { + pub fn NSEEL_VM_set_var_resolver( + ctx: root::NSEEL_VMCTX, + res: ::std::option::Option< + unsafe extern "C" fn( + userctx: *mut ::std::os::raw::c_void, + name: *const ::std::os::raw::c_char, + ) -> *mut root::EEL_F, + >, + userctx: *mut ::std::os::raw::c_void, + ); + } + extern "C" { + pub fn NSEEL_VM_freeRAM(ctx: root::NSEEL_VMCTX); + } + extern "C" { + pub fn NSEEL_VM_freeRAMIfCodeRequested(arg1: root::NSEEL_VMCTX); + } + extern "C" { + pub fn NSEEL_VM_wantfreeRAM(ctx: root::NSEEL_VMCTX) -> ::std::os::raw::c_int; + } + extern "C" { + pub fn NSEEL_VM_SetGRAM(ctx: root::NSEEL_VMCTX, gram: *mut *mut ::std::os::raw::c_void); + } + extern "C" { + pub fn NSEEL_VM_FreeGRAM(ufd: *mut *mut ::std::os::raw::c_void); + } + extern "C" { + pub fn NSEEL_VM_SetCustomFuncThis( + ctx: root::NSEEL_VMCTX, + thisptr: *mut ::std::os::raw::c_void, + ); + } + extern "C" { + pub fn NSEEL_VM_getramptr( + ctx: root::NSEEL_VMCTX, + offs: ::std::os::raw::c_uint, + validCount: *mut ::std::os::raw::c_int, + ) -> *mut root::EEL_F; + } + extern "C" { + pub fn NSEEL_VM_getramptr_noalloc( + ctx: root::NSEEL_VMCTX, + offs: ::std::os::raw::c_uint, + validCount: *mut ::std::os::raw::c_int, + ) -> *mut root::EEL_F; + } + extern "C" { + pub fn NSEEL_VM_setramsize( + ctx: root::NSEEL_VMCTX, + maxent: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; + } + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct eelStringSegmentRec { + pub _next: *mut root::eelStringSegmentRec, + pub str_start: *const ::std::os::raw::c_char, + pub str_len: ::std::os::raw::c_int, + } + #[test] + fn bindgen_test_layout_eelStringSegmentRec() { + const UNINIT: ::std::mem::MaybeUninit = + ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(eelStringSegmentRec)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(eelStringSegmentRec)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr)._next) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(eelStringSegmentRec), + "::", + stringify!(_next) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).str_start) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(eelStringSegmentRec), + "::", + stringify!(str_start) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).str_len) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(eelStringSegmentRec), + "::", + stringify!(str_len) + ) + ); + } + extern "C" { + pub fn NSEEL_VM_SetStringFunc( + ctx: root::NSEEL_VMCTX, + onString: ::std::option::Option< + unsafe extern "C" fn( + caller_this: *mut ::std::os::raw::c_void, + list: *mut root::eelStringSegmentRec, + ) -> root::EEL_F, + >, + onNamedString: ::std::option::Option< + unsafe extern "C" fn( + caller_this: *mut ::std::os::raw::c_void, + name: *const ::std::os::raw::c_char, + ) -> root::EEL_F, + >, + ); + } + extern "C" { + pub fn NSEEL_code_compile( + ctx: root::NSEEL_VMCTX, + code: *const ::std::os::raw::c_char, + lineoffs: ::std::os::raw::c_int, + ) -> root::NSEEL_CODEHANDLE; + } + extern "C" { + pub fn NSEEL_code_compile_ex( + ctx: root::NSEEL_VMCTX, + code: *const ::std::os::raw::c_char, + lineoffs: ::std::os::raw::c_int, + flags: ::std::os::raw::c_int, + ) -> root::NSEEL_CODEHANDLE; + } + extern "C" { + pub fn NSEEL_code_getcodeerror(ctx: root::NSEEL_VMCTX) -> *mut ::std::os::raw::c_char; + } + extern "C" { + pub fn NSEEL_code_geterror_flag(ctx: root::NSEEL_VMCTX) -> ::std::os::raw::c_int; + } + extern "C" { + pub fn NSEEL_code_execute(code: root::NSEEL_CODEHANDLE); + } + extern "C" { + pub fn NSEEL_code_free(code: root::NSEEL_CODEHANDLE); + } + extern "C" { + pub fn NSEEL_code_getstats(code: root::NSEEL_CODEHANDLE) -> *mut ::std::os::raw::c_int; + } + extern "C" { + pub static mut NSEEL_RAM_limitmem: ::std::os::raw::c_uint; + } + extern "C" { + pub static mut NSEEL_RAM_memused: ::std::os::raw::c_uint; + } + extern "C" { + pub static mut NSEEL_RAM_memused_errors: ::std::os::raw::c_int; + } + extern "C" { + pub fn NSEEL_PProc_RAM( + data: *mut ::std::os::raw::c_void, + data_size: ::std::os::raw::c_int, + ctx: *mut root::_compileContext, + ) -> *mut ::std::os::raw::c_void; + } + extern "C" { + pub fn NSEEL_PProc_THIS( + data: *mut ::std::os::raw::c_void, + data_size: ::std::os::raw::c_int, + ctx: *mut root::_compileContext, + ) -> *mut ::std::os::raw::c_void; + } +} diff --git a/plugin-reaper-realearn/main/src/base/clone_as_default.rs b/plugin-reaper-realearn/main/src/base/clone_as_default.rs new file mode 100644 index 0000000..bbb0f67 --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/clone_as_default.rs @@ -0,0 +1,33 @@ +/// A wrapper that implements Clone but not really by cloning the wrapped value but by +/// creating the inner type's default value. +/// +/// This is useful if you have a large nested value of which almost anything inside must be cloned +/// but you also have a few values in there that don't suit themselves to be cloned (e.g. compiled +/// scripts). This must be well documented though, it's a very surprising behavior. +/// +/// Alternatives to be considered before reaching out to this: +/// +/// - Making the whole graph not cloneable +/// - Using `Rc` or `Arc` (is cloneable but means that the inner value is not "standalone" anymore, +/// can be accessed from multiple places or in case of `Arc` even threads ... and it means that +/// you have less control over when and in which thread deallocation happens). +/// - Writing a dedicated method (not `clone`) which makes it clear that this is not a standard +/// clone operation. +#[derive(Debug)] +pub struct CloneAsDefault(T); + +impl CloneAsDefault { + pub fn new(value: T) -> Self { + Self(value) + } + + pub fn get(&self) -> &T { + &self.0 + } +} + +impl Clone for CloneAsDefault { + fn clone(&self) -> Self { + Self(T::default()) + } +} diff --git a/plugin-reaper-realearn/main/src/base/eel.rs b/plugin-reaper-realearn/main/src/base/eel.rs new file mode 100644 index 0000000..0e5e601 --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/eel.rs @@ -0,0 +1,212 @@ +use crate::base::allocator::GLOBAL_ALLOCATOR; +use crate::base::bindings::root; +use crate::base::bindings::root::eel_function_table; +use reaper_medium::ReaperStr; +use std::ffi::{CStr, CString}; +use std::mem::MaybeUninit; +use std::os::raw::c_void; +use std::ptr::null_mut; + +#[derive(Debug)] +pub struct Vm { + vm_ctx: root::NSEEL_VMCTX, + function_table: Box, +} + +unsafe impl Send for Vm {} + +#[derive(Debug)] +pub struct Program(root::NSEEL_CODEHANDLE); + +unsafe impl Send for Program {} + +#[derive(Copy, Clone, Debug)] +pub struct Variable(*mut f64); + +unsafe impl Send for Variable {} + +// TODO-medium It's actually not Sync. It's safe in our case because we know that we never use an +// EEL program at the same time in 2 threads. +unsafe impl Sync for Vm {} +unsafe impl Sync for Program {} +unsafe impl Sync for Variable {} + +impl Vm { + pub fn new() -> Vm { + let vm_ctx = unsafe { root::NSEEL_VM_alloc() }; + let mut function_table = Box::new(eel_function_table { + list: null_mut(), + list_size: 0, + }); + unsafe { + root::NSEEL_VM_SetFunctionTable(vm_ctx, &mut *function_table as *mut _); + } + Vm { + vm_ctx, + function_table, + } + } + + pub fn register_single_arg_function( + &mut self, + name: &'static ReaperStr, + f: unsafe extern "C" fn(opaque: *mut c_void, amt: *mut f64) -> f64, + ) { + unsafe { + root::NSEEL_addfunc_ret_type( + name.as_c_str().as_ptr(), + 1, + // Return double + 1, + Some(root::NSEEL_PProc_THIS), + f as *mut c_void, + &mut *self.function_table as *mut _, + ); + } + } + + pub fn register_void_or_bool_function( + &mut self, + name: &'static ReaperStr, + f: unsafe extern "C" fn(opaque: *mut c_void, amt: *mut f64) -> bool, + ) { + unsafe { + root::NSEEL_addfunc_ret_type( + name.as_c_str().as_ptr(), + 1, + // Return void or bool + -1, + Some(root::NSEEL_PProc_THIS), + f as *mut c_void, + &mut *self.function_table as *mut _, + ); + } + } + + pub fn register_variable(&self, name: &str) -> Variable { + let c_string = CString::new(name).expect("variable name is not valid UTF-8"); + let ptr = unsafe { root::NSEEL_VM_regvar(self.vm_ctx, c_string.as_ptr()) }; + Variable(ptr) + } + + pub fn register_and_set_variable(&self, name: &str, value: f64) -> Variable { + let v = self.register_variable(name); + unsafe { + v.set(value); + } + v + } + + pub fn get_mem_slice(&self, index: u32, size: u32) -> &[f64] { + let mut valid_count = MaybeUninit::zeroed(); + let ptr = unsafe { + root::NSEEL_VM_getramptr_noalloc(self.vm_ctx, index, valid_count.as_mut_ptr()) + }; + let valid_count = unsafe { valid_count.assume_init() }; + if ptr.is_null() || valid_count <= 0 { + return &[]; + } + let slice_len = std::cmp::min(valid_count as u32, size); + let slice = std::ptr::slice_from_raw_parts(ptr, slice_len as _); + unsafe { &*slice } + } + + pub fn compile(&self, code: &str) -> Result { + if code.trim().is_empty() { + return Err("Empty".to_owned()); + } + let c_string = CString::new(code).map_err(|_| "Code is not valid UTF-8")?; + let code_handle = unsafe { root::NSEEL_code_compile(self.vm_ctx, c_string.as_ptr(), 0) }; + if code_handle.is_null() { + let error = unsafe { root::NSEEL_code_getcodeerror(self.vm_ctx) }; + if error.is_null() { + return Err("Unknown error".to_string()); + } + let c_str = unsafe { CStr::from_ptr(error) }; + let string = c_str + .to_owned() + .into_string() + .unwrap_or_else(|_| "Couldn't convert error to string".to_string()); + return Err(string); + } + Ok(Program(code_handle)) + } +} + +impl Program { + pub unsafe fn execute(&self) { + root::NSEEL_code_execute(self.0); + } +} + +impl Variable { + pub unsafe fn get(&self) -> f64 { + *self.0 + } + + pub unsafe fn set(&self, value: f64) { + *self.0 = value; + } +} + +impl Drop for Vm { + fn drop(&mut self) { + GLOBAL_ALLOCATOR.dealloc_foreign_value(root::NSEEL_VM_free, self.vm_ctx); + } +} + +impl Drop for Program { + fn drop(&mut self) { + GLOBAL_ALLOCATOR.dealloc_foreign_value(root::NSEEL_code_free, self.0); + } +} + +#[no_mangle] +extern "C" fn NSEEL_HOSTSTUB_EnterMutex() {} + +#[no_mangle] +extern "C" fn NSEEL_HOSTSTUB_LeaveMutex() {} + +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_abs_diff_eq; + + #[test] + fn basics() { + // Given + let vm = Vm::new(); + let x = vm.register_variable("x"); + let y = vm.register_variable("y"); + let program = vm.compile("y = x + 1;").expect("couldn't compile"); + // // When + let y_result = unsafe { + x.set(42.0); + y.set(0.0); + program.execute(); + y.get() + }; + // Then + assert_abs_diff_eq!(y_result, 43.0); + } + + #[test] + fn get_mem_slice() { + // Given + let vm = Vm::new(); + let x = vm.register_variable("x"); + let program = vm + .compile("y[0] = x + 1; y[1] = x + 2; y[2] = x + 5") + .expect("couldn't compile"); + // // When + let slice = unsafe { + x.set(42.0); + program.execute(); + vm.get_mem_slice(0, 3) + }; + // Then + assert_abs_diff_eq!(slice[0], 43.0); + assert_abs_diff_eq!(slice[1], 44.0); + assert_abs_diff_eq!(slice[2], 47.0); + } +} diff --git a/plugin-reaper-realearn/main/src/base/mod.rs b/plugin-reaper-realearn/main/src/base/mod.rs new file mode 100644 index 0000000..82260fd --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/mod.rs @@ -0,0 +1,16 @@ +mod scheduling; +pub use scheduling::*; + +mod property; +pub use property::*; + +pub mod notification; + +pub mod eel; + +pub mod bindings; + +mod clone_as_default; +pub use clone_as_default::*; + +pub mod allocator; diff --git a/plugin-reaper-realearn/main/src/base/notification.rs b/plugin-reaper-realearn/main/src/base/notification.rs new file mode 100644 index 0000000..64bebc5 --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/notification.rs @@ -0,0 +1,58 @@ +use reaper_high::Reaper; +use reaper_medium::{MessageBoxType, ReaperStringArg}; +use std::error::Error; + +pub fn notify_processing_result(heading: &str, msgs: Vec) { + let joined_msg = msgs.join("\n\n"); + let msg = format!( + "{}\n{}\n\n{}\n\n", + heading, + "-".repeat(heading.len()), + joined_msg + ); + Reaper::get().show_console_msg(msg); +} + +pub fn warn_user_on_anyhow_error(result: anyhow::Result<()>) { + if let Err(e) = result { + warn_user_about_anyhow_error(e) + } +} + +pub fn warn_user_about_anyhow_error(error: anyhow::Error) { + warn(format!("{error:#}")); +} + +pub fn warn(msg: String) { + Reaper::get().show_console_msg(format!("\n\nReaLearn warning: {msg} ")); +} + +#[allow(dead_code)] +pub fn notify_user_on_error(result: Result<(), Box>) { + if let Err(e) = result { + notify_user_about_error(e); + } +} + +#[allow(dead_code)] +pub fn notify_user_on_anyhow_error(result: anyhow::Result<()>) { + if let Err(e) = result { + notify_user_about_anyhow_error(&e); + } +} + +#[allow(dead_code)] +pub fn notify_user_about_error(e: Box) { + alert(e.to_string()); +} + +#[allow(dead_code)] +pub fn notify_user_about_anyhow_error(e: &anyhow::Error) { + alert(format!("{e:#}")); +} + +pub fn alert<'a>(msg: impl Into>) { + Reaper::get() + .medium_reaper() + .show_message_box(msg, "Helgobox", MessageBoxType::Okay); +} diff --git a/plugin-reaper-realearn/main/src/base/property.rs b/plugin-reaper-realearn/main/src/base/property.rs new file mode 100644 index 0000000..3e884c6 --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/property.rs @@ -0,0 +1,44 @@ +//! In this file we assemble the a custom-tailored property type which we are going to use +//! throughout ReaLearn. +use rx_util::{LocalProp, LocalPropSubject, Notifier}; +use rxrust::prelude::*; +use std::marker::PhantomData; + +/// Creates a ReaLearn property. +pub fn prop(initial_value: T) -> Prop +where + T: PartialEq + Clone + 'static, +{ + Prop::new(initial_value) +} + +/// ReaLearn property type. +pub type Prop = LocalProp<'static, T, u32, AsyncNotifier>, AsyncNotifier>; + +pub struct AsyncNotifier(PhantomData); + +impl Notifier for AsyncNotifier +where + T: Clone + 'static, +{ + type T = T; + type Subject = LocalPropSubject<'static, T>; + + fn notify(subject: &mut Self::Subject, value: &Self::T) { + if subject.subscribed_size() == 0 { + return; + } + #[cfg(test)] + subject.next(value.clone()); + #[cfg(not(test))] + { + let mut subject = subject.clone(); + let value = value.clone(); + base::Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(move || { + subject.next(value); + }) + .unwrap(); + } + } +} diff --git a/plugin-reaper-realearn/main/src/base/scheduling.rs b/plugin-reaper-realearn/main/src/base/scheduling.rs new file mode 100644 index 0000000..5f12ed4 --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/scheduling.rs @@ -0,0 +1,189 @@ +use rxrust::prelude::*; + +use base::Global; +use std::marker::PhantomData; +use std::rc::{Rc, Weak}; +use tracing::debug; + +pub fn when(trigger: Trigger) -> ReactionBuilderStepOne +where + Trigger: LocalObservable<'static, Item = Item, Err = ()> + 'static, +{ + ReactionBuilderStepOne { + trigger, + p: PhantomData, + } +} + +pub struct ReactionBuilderStepOne { + trigger: Trigger, + p: PhantomData, +} + +impl ReactionBuilderStepOne +where + Trigger: LocalObservable<'static, Item = Item, Err = ()> + 'static, +{ + pub fn with( + self, + weak_receiver: Weak, + ) -> ReactionBuilderStepTwo + where + Receiver: 'static, + { + ReactionBuilderStepTwo { + parent: self, + weak_receiver, + } + } +} + +pub struct ReactionBuilderStepTwo { + parent: ReactionBuilderStepOne, + weak_receiver: Weak, +} + +impl ReactionBuilderStepTwo +where + Trigger: LocalObservable<'static, Item = Item, Err = ()> + 'static, + Receiver: 'static, +{ + pub fn finally( + self, + finalizer: Finalizer, + ) -> ReactionBuilderStepThree + where + Finalizer: Fn(Rc) + 'static, + { + ReactionBuilderStepThree { + parent: self, + finalizer, + } + } + + /// Executes the given reaction synchronously whenever the specified event is raised. + pub fn do_sync( + self, + reaction: impl Fn(Rc, Item) + 'static, + ) -> SubscriptionWrapper { + Self::do_sync_internal(self.weak_receiver, self.parent.trigger, reaction) + } + + /// Executes the given reaction whenever the specified event is raised. + /// + /// It doesn't execute the reaction immediately but in the next main run loop cycle. That's also + /// why the "trigger" and "until" items need to be shareable (Clone + Send + Sync), which is + /// no problem in practice because they are just `()` in our case. + /// + /// The observables are expected to be local (not shareable). Application of the `delay()` + /// operator would require them to be shared, too. But `delay()` doesn't work anyway right + /// now (https://github.com/rxRust/rxRust/issues/106). So there's no reason to change all the + /// trigger/until subjects/properties into shared ones. + pub fn do_async( + self, + reaction: impl Fn(Rc, Item) + Clone + 'static, + ) -> SubscriptionWrapper + where + Item: 'static, + { + Self::do_async_internal(self.weak_receiver, self.parent.trigger, reaction) + } + + fn do_sync_internal( + weak_receiver: Weak, + trigger: impl LocalObservable<'static, Item = Item, Err = ()> + 'static, + reaction: impl Fn(Rc, Item) + 'static, + ) -> SubscriptionWrapper { + trigger.subscribe(move |item| { + if let Some(receiver) = upgrade(&weak_receiver) { + (reaction)(receiver, item); + } + }) + } + + fn do_async_internal( + weak_receiver: Weak, + trigger: impl LocalObservable<'static, Item = Item, Err = ()> + 'static, + reaction: impl Fn(Rc, Item) + Clone + 'static, + ) -> SubscriptionWrapper + where + Item: 'static, + { + trigger.subscribe(move |item| { + let weak_receiver = weak_receiver.clone(); + let reaction = reaction.clone(); + Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(move || { + if let Some(receiver) = upgrade(&weak_receiver) { + (reaction)(receiver, item); + } + }) + .unwrap(); + }) + } +} + +pub struct ReactionBuilderStepThree { + parent: ReactionBuilderStepTwo, + finalizer: Finalizer, +} + +impl + ReactionBuilderStepThree +where + Trigger: LocalObservable<'static, Item = Item, Err = ()> + 'static, + Receiver: 'static, + Finalizer: Fn(Rc) + Clone + 'static, + Item: 'static, +{ + pub fn do_sync( + self, + reaction: impl Fn(Rc, Item) + Clone + 'static, + ) -> SubscriptionWrapper { + let weak_receiver = self.parent.weak_receiver.clone(); + let finalizer = self.finalizer; + ReactionBuilderStepTwo::::do_sync_internal( + self.parent.weak_receiver, + self.parent.parent.trigger.finalize(move || { + if let Some(receiver) = upgrade(&weak_receiver) { + (finalizer)(receiver); + } + }), + reaction, + ) + } + + pub fn do_async( + self, + reaction: impl Fn(Rc, Item) + Clone + 'static, + ) -> SubscriptionWrapper + where + Item: 'static, + { + let weak_receiver = self.parent.weak_receiver.clone(); + let finalizer = self.finalizer; + ReactionBuilderStepTwo::::do_async_internal( + self.parent.weak_receiver, + self.parent.parent.trigger.finalize(move || { + let weak_receiver = weak_receiver.clone(); + let finalizer = finalizer.clone(); + Global::task_support() + .do_later_in_main_thread_from_main_thread_asap(move || { + if let Some(receiver) = upgrade(&weak_receiver) { + (finalizer)(receiver); + } + }) + .unwrap(); + }), + reaction, + ) + } +} + +fn upgrade(weak_receiver: &Weak) -> Option> { + let shared_receiver = weak_receiver.upgrade(); + if shared_receiver.is_none() { + debug!("Receiver gone"); + } + shared_receiver +} diff --git a/plugin-reaper-realearn/main/src/base/wrapper.hpp b/plugin-reaper-realearn/main/src/base/wrapper.hpp new file mode 100644 index 0000000..508483b --- /dev/null +++ b/plugin-reaper-realearn/main/src/base/wrapper.hpp @@ -0,0 +1,3 @@ + // EEL language support for value transformation in ReaLearn mode section +#include "../../lib/WDL/WDL/eel2/ns-eel.h" +#include "../../lib/WDL/WDL/eel2/ns-eel-addfuncs.h" \ No newline at end of file diff --git a/plugin-reaper-realearn/main/src/domain/Exo2-Light.otf b/plugin-reaper-realearn/main/src/domain/Exo2-Light.otf new file mode 100644 index 0000000..898aecc Binary files /dev/null and b/plugin-reaper-realearn/main/src/domain/Exo2-Light.otf differ diff --git a/plugin-reaper-realearn/main/src/domain/accelerator.rs b/plugin-reaper-realearn/main/src/domain/accelerator.rs new file mode 100644 index 0000000..cdf1dc3 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/accelerator.rs @@ -0,0 +1,166 @@ +use crate::domain::{ + Backbone, ControlEvent, ControlEventTimestamp, DomainEventHandler, KeyMessage, Keystroke, + SharedMainProcessors, +}; +use reaper_high::Reaper; +use reaper_low::raw; +use reaper_medium::{ + virt_keys, AccelMsg, AccelMsgKind, AcceleratorBehavior, TranslateAccel, TranslateAccelArgs, + TranslateAccelResult, +}; +use swell_ui::{SharedView, View, Window}; + +pub trait HelgoboxWindowSnitch { + /// Goes up the window hierarchy trying to find an associated ReaLearn view, starting with + /// the given window. + fn find_closest_realearn_view(&self, window: Window) -> Option>; +} + +#[derive(Debug)] +pub struct RealearnAccelerator { + main_processors: SharedMainProcessors, + snitch: S, +} + +impl RealearnAccelerator { + pub fn new(main_processors: SharedMainProcessors, snitch: S) -> Self { + Self { + main_processors, + snitch, + } + } +} + +impl RealearnAccelerator +where + EH: DomainEventHandler, + S: HelgoboxWindowSnitch, +{ + /// Sends the message to all main processors as control events. + /// + /// Returns `true` if at least one main processor used the message, that is if at least one + /// main processor had control input set to "Keyboard" and a mapping matched the given key. + /// + /// If yes, we should completely "eat" the message and don't do anything else with it. + fn process_control(&mut self, msg: KeyMessage) -> bool { + let evt = ControlEvent::new(msg, ControlEventTimestamp::from_main_thread()); + let mut filter_out_event = false; + let mut notified_backbone = false; + for proc in &mut *self.main_processors.borrow_mut() { + if !proc.wants_keyboard_input() { + continue; + } + let result = proc.process_incoming_key_msg(evt); + // Notify backbone that ReaLearn mappings successfully matched keyboard input in this + // main loop cycle. We need to do that only once. + if !notified_backbone && result.match_outcome.matched() { + notified_backbone = true; + Backbone::get().set_keyboard_input_match_flag(); + } + // If at least one instance wants to filter the key out, we filter it out, not + // passing it forward to the rest of the keyboard processing chain! + if result.filter_out_event { + filter_out_event = true; + } + } + filter_out_event + } + + /// Decides what to do with the key if no main processor used it. + fn process_unmatched(&self, msg: AccelMsg) -> TranslateAccelResult { + let is_virt_key = msg.message() != AccelMsgKind::Char + && msg.behavior().contains(AcceleratorBehavior::VirtKey); + if is_virt_key && msg.key().get() as u32 == raw::VK_ESCAPE { + // Don't process escape in special ways. We want the normal close behavior. Especially + // important for the floating ReaLearn FX window where closing the main panel would not + // close the surrounding floating window. + return TranslateAccelResult::NotOurWindow; + } + let Some(focused_window) = Window::focused() else { + // No window focused + return TranslateAccelResult::NotOurWindow; + }; + let Some(view) = self.snitch.find_closest_realearn_view(focused_window) else { + // Not our window which is focused. Act normally. + return TranslateAccelResult::NotOurWindow; + }; + // Support F1 in our windows (key_down and key_up don't work very well on Linux and Windows if there's + // a text field) + if is_virt_key && msg.key().get() == virt_keys::F1.get() { + if msg.message() == AccelMsgKind::KeyUp { + view.help_requested(); + } + return TranslateAccelResult::Eat; + } + if !view.wants_raw_keyboard_input() { + return TranslateAccelResult::NotOurWindow; + } + let Some(w) = view.get_keyboard_event_receiver(focused_window) else { + // No window is interested. + return TranslateAccelResult::Eat; + }; + // A ReaLearn window is focused. + // - We want to get almost all keyboard input (without having to enable "Send all keyboard input to plug-in") + // - We don't want REAPER to execute actions or the system to execute menu commands + // - We want the egui window to receive raw keys + // + // All of this is achieved in different ways depending on the OS. + #[cfg(target_os = "macos")] + { + // On macOS, we must explicitly send the message to the focused view. If we + // let the system take care of it (e.g. via NotOurWindow, PassOnToWindow or + // ProcessEventRaw), REAPER's main menu shortcuts have priority, which we don't + // want! Especially important for egui text edit because Cmd+C and Cmd+X would not + // work in there. + if w.process_current_app_event_if_no_text_field() { + TranslateAccelResult::Eat + } else { + // However, if the focused view is a text field, this would break copy & paste + // in that text field ;) So in this special case, we need the system to do its + // job. + TranslateAccelResult::NotOurWindow + } + } + #[cfg(not(target_os = "macos"))] + { + // This is necessary for Pot Browser to receive keys + w.process_raw_message(msg.raw()); + TranslateAccelResult::Eat + } + } +} + +impl TranslateAccel for RealearnAccelerator +where + EH: DomainEventHandler, + S: HelgoboxWindowSnitch, +{ + fn call(&mut self, args: TranslateAccelArgs) -> TranslateAccelResult { + // Ignore char messages + if args.msg.message() == AccelMsgKind::Char { + // Char messages are only sent on Windows and for all relevant control purposes, + // they are preceded by a KeyDown event, so we must ignore them. + return self.process_unmatched(args.msg); + } + // If REAPER 7.23+, check if window is text field + let reaper = Reaper::get().medium_reaper(); + if reaper.low().pointers().IsWindowTextField.is_some() { + if let Some(window) = Window::focused() { + let is_text_field = unsafe { reaper.is_window_text_field(window.raw_hwnd()) }; + if is_text_field { + return self.process_unmatched(args.msg); + } + } + } + // If we end up here, it could be interesting for the main processors + let stroke = Keystroke::new(args.msg.behavior(), args.msg.key()); + let normalized_stroke = stroke.normalized(); + let normalized_msg = KeyMessage::new(args.msg.message(), normalized_stroke); + let filter_out = self.process_control(normalized_msg); + if filter_out { + TranslateAccelResult::Eat + } else { + self.process_unmatched(args.msg) + } + } +} diff --git a/plugin-reaper-realearn/main/src/domain/audio_hook.rs b/plugin-reaper-realearn/main/src/domain/audio_hook.rs new file mode 100644 index 0000000..b3add5f --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/audio_hook.rs @@ -0,0 +1,711 @@ +use crate::domain::{ + classify_midi_message, AudioBlockProps, ControlEvent, ControlEventTimestamp, + DisplayAsPrettyHex, IncomingMidiMessage, InstanceId, MidiControlInput, MidiEvent, + MidiMessageClassification, MidiScanResult, MidiScanner, MidiTransformationContainer, + RealTimeProcessor, SharedRealTimeInstance, UnitId, GLOBAL_AUDIO_STATE, +}; +use base::byte_pattern::{BytePattern, PatternByte}; +use base::metrics_util::{measure_time, record_duration}; +use base::non_blocking_lock; +use helgoboss_learn::{MidiSourceValue, RawMidiEvent, RawMidiEvents}; +use helgoboss_midi::{DataEntryByteOrder, RawShortMessage, ShortMessage, ShortMessageType}; +use helgobox_allocator::*; +use reaper_common_types::DurationInSeconds; +use reaper_high::{MidiInputDevice, MidiOutputDevice, Reaper}; +use reaper_medium::{ + MidiInputDeviceId, MidiOutputDeviceId, OnAudioBuffer, OnAudioBufferArgs, SendMidiTime, + MIDI_INPUT_FRAME_RATE, +}; +use smallvec::SmallVec; +use std::fmt::{Display, Formatter}; +use std::sync::{Arc, Mutex, MutexGuard, OnceLock}; +use std::time::{Duration, Instant}; +use tinyvec::ArrayVec; + +const AUDIO_HOOK_TASK_BULK_SIZE: usize = 1; +const FEEDBACK_TASK_BULK_SIZE: usize = 1000; + +/// This needs to be thread-safe because if "Allow live FX multiprocessing" is active in the REAPER +/// preferences, the VST processing is executed in another thread than the audio hook! +pub type SharedRealTimeProcessor = Arc>; + +pub type MidiCaptureSender = async_channel::Sender; + +#[derive(Debug)] +pub struct RequestMidiDeviceIdentityCommand { + pub output_device_id: MidiOutputDeviceId, + pub input_device_id: Option, + pub sender: async_channel::Sender, +} + +#[derive(Debug)] +struct MidiDeviceInquiryTask { + command: RequestMidiDeviceIdentityCommand, + inquiry_sent_at: Instant, +} + +#[derive(Clone, Debug)] +pub struct RequestMidiDeviceIdentityReply { + pub input_device_id: MidiInputDeviceId, + pub device_inquiry_reply: MidiDeviceInquiryReply, +} + +#[derive(Clone, Debug)] +pub struct MidiDeviceInquiryReply { + pub message: ArrayVec<[u8; RawMidiEvent::MAX_LENGTH]>, +} + +impl Display for MidiDeviceInquiryReply { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + DisplayAsPrettyHex(self.message.as_slice()).fmt(f) + } +} + +// This kind of tasks is always processed, even after a rebirth when multiple processor syncs etc. +// have already accumulated. Because at the moment there's no way to request a full resync of all +// real-time processors from the control surface. In practice there's no danger that too many of +// those infrequent tasks accumulate so it's not an issue. Therefore the convention for now is to +// also send them when audio is not running. +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum NormalAudioHookTask { + AddRealTimeInstance(InstanceId, SharedRealTimeInstance), + RemoveRealTimeInstance(InstanceId), + /// First parameter is the ID. + // + // Having the ID saves us from unnecessarily blocking the audio thread by looking into the + // processor. + AddRealTimeProcessor(UnitId, SharedRealTimeProcessor), + RemoveRealTimeProcessor(UnitId), + StartCapturingMidi(MidiCaptureSender), + StopCapturingMidi, + /// Instructs the audio hook to send a MIDI device inquiry to the given output device. + /// + /// Gives up after about one second if no response received (by dropping the sender). + /// + /// Gives up immediately if the output device or optional input device is not open. + RequestMidiDeviceIdentity(RequestMidiDeviceIdentityCommand), + #[cfg(feature = "playtime")] + PlaytimeClipEngineCommand(playtime_clip_engine::rt::audio_hook::PlaytimeAudioHookCommand), +} + +/// A global feedback task (which is potentially sent very frequently). +#[derive(Debug)] +pub enum FeedbackAudioHookTask { + MidiDeviceFeedback( + MidiOutputDeviceId, + MidiSourceValue<'static, RawShortMessage>, + ), + SendMidi(MidiOutputDeviceId, RawMidiEvents), +} + +pub fn send_midi_device_feedback( + dev_id: MidiOutputDeviceId, + value: MidiSourceValue, +) { + if let Some(events) = value.to_raw() { + MidiOutputDevice::new(dev_id).with_midi_output(|mo| { + if let Some(mo) = mo { + for event in events { + mo.send_msg(event, SendMidiTime::Instantly); + } + } + }); + } else { + let shorts = value.to_short_messages(DataEntryByteOrder::MsbFirst); + if shorts[0].is_none() { + return; + } + MidiOutputDevice::new(dev_id).with_midi_output(|mo| { + if let Some(mo) = mo { + for short in shorts.iter().flatten() { + mo.send(*short, SendMidiTime::Instantly); + } + } + }); + } +} + +#[derive(Debug)] +pub struct RealearnAudioHook { + state: AudioHookState, + midi_device_inquiry_task: Option, + real_time_instances: SmallVec<[(InstanceId, SharedRealTimeInstance); 256]>, + real_time_processors: SmallVec<[(UnitId, SharedRealTimeProcessor); 256]>, + normal_task_receiver: crossbeam_channel::Receiver, + feedback_task_receiver: crossbeam_channel::Receiver, + time_of_last_run: Option, + initialized: bool, + midi_transformation_container: MidiTransformationContainer, + #[cfg(feature = "playtime")] + clip_engine_audio_hook: playtime_clip_engine::rt::audio_hook::PlaytimeAudioHook, +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum AudioHookState { + Normal, + // This is not the instance-specific learning but the global one. + LearningSource { + sender: MidiCaptureSender, + midi_scanner: MidiScanner, + }, +} + +impl RealearnAudioHook { + pub fn new( + normal_task_receiver: crossbeam_channel::Receiver, + feedback_task_receiver: crossbeam_channel::Receiver, + ) -> RealearnAudioHook { + Self { + state: AudioHookState::Normal, + midi_device_inquiry_task: None, + real_time_instances: Default::default(), + real_time_processors: Default::default(), + normal_task_receiver, + feedback_task_receiver, + time_of_last_run: None, + initialized: false, + midi_transformation_container: MidiTransformationContainer::new(), + #[cfg(feature = "playtime")] + clip_engine_audio_hook: playtime_clip_engine::rt::audio_hook::PlaytimeAudioHook::new(), + } + } + + /// This should be called only once in the audio hardware thread, before everything else. + /// + /// It does some per-thread allocation. + fn init_from_rt_thread(&mut self) { + // We have code, e.g. triggered by crossbeam_channel that requests the ID of the + // current thread. This operation needs an allocation at the first time it's executed + // on a specific thread. Let's do it here, globally exactly once. Then we can + // use assert_no_alloc() to detect real regular allocation issues. + // Please note that this doesn't have an effect if + // "Audio => Buffering => Allow live FX multiprocessing" is enabled in the REAPER prefs. + // Because then worker threads will drive ReaLearn plug-in and clips. That's not an + // issue for actual usage because the allocation is done only once per worker + // thread, right at the beginning. It's only a problem for testing with + // assert_no_alloc(). We introduced a similar thing in ColumnSource get_samples. + let thread_id = std::thread::current().id(); + // The tracing library also does some allocation per thread (independent from the + // allocations that a subscriber does anyway). + tracing::info!( + "Initializing real-time logging from preview register (thread {:?})", + thread_id + ); + #[cfg(feature = "playtime")] + { + self.clip_engine_audio_hook.init_from_rt_thread(); + } + } + + fn on_pre(&mut self, args: OnAudioBufferArgs) { + let current_time = Instant::now(); + let time_of_last_run = self.time_of_last_run.replace(current_time); + // Increment counter + let block_props = AudioBlockProps::from_on_audio_buffer_args(&args); + let block_count = GLOBAL_AUDIO_STATE.advance(block_props); + let sample_count = block_count * args.len as u64; + // Call ReaLearn real-time processors (= process MIDI messages coming in from hardware devices). + // We do this here already, *before* pre-polling recording and advancing Playtime's tempo buffer (done in `on_pre_poll_1`)! + // Reason: When recording a new clip with tempo detection (= recording in silence mode), it's ideal + // if pressing a stop button on the controller *instantly* stops recording, detects the new tempo, + // applies it to the current block and starts playing the clip. Instantly = at the start of *this* block, + // without waiting until the next block. This is only possible if at the very beginning of the + // block it's already known that the stop button was pressed, before the block tempo props are determined. + // + // Playtime steps: + // + // 1. Process MIDI messages coming in from hardware devices + // 2. For each record-clip task: + // 2.1 Write incoming data to recording clip + // 2.2 Commit recording if necessary (if tempo detection enabled, also reset timeline with new tempo and no count-in) + // - Maybe we can unify manual stop and scheduled stop this way + // - One way is to process RtColumn commands generally in the audio hook. + // - Pro: No need to make "stop recording" a special command. All commands will be executed before preview registers, + // that is, before other columns have been played. + // - Con: I had the idea of a future refactoring: To do resolving/scheduling eagerly when processing + // the command instead of later when processing the slots. Since block tempo props are by definition + // not 100% decided yet when processing commands from the audio hook in this early stage, this would + // become impossible. + // + // 3. Advance tempo buffer + // 4. Play clip from start (process preview registers, which REAPER does after executing the pre-audio-hook) + let might_be_rebirth = { + if let Some(time) = time_of_last_run { + current_time.duration_since(time) > Duration::from_secs(1) + } else { + false + } + }; + self.call_real_time_processors(block_props, sample_count, might_be_rebirth); + // Process ReaLearn feedback commands + self.process_feedback_commands(); + // Process incoming commands, including Playtime commands + self.process_normal_commands(block_props); + // Pre-poll Playtime + #[cfg(feature = "playtime")] + { + self.clip_engine_audio_hook + .on_pre_poll(block_props.to_playtime(), args.reg); + } + // Poll real-time instances. If an instance has Playtime enabled, this also polls the real-time matrix. + // Important to do after pre-polling the Playtime audio hook, especially for one scenario: + // Leaving silence mode immediately with playing ignited clips: In this case, we do a timeline reset to zero. + // The ignited clips should start immediately, exactly from zero as soon as the timeline has been reset. + // If we called this before pre-polling Playtime audio hook, the real-time matrix would be called in the + // next audio cycle, after the timeline has already advanced on block from zero. + self.pre_poll_real_time_instances(block_props); + // Process some tasks + self.check_for_midi_device_inquiry_response(); + } + + fn on_post(&mut self, args: OnAudioBufferArgs) { + self.post_poll_real_time_instances(); + #[cfg(not(feature = "playtime"))] + { + let _ = args; + } + #[cfg(feature = "playtime")] + { + let block_props = AudioBlockProps::from_on_audio_buffer_args(&args); + self.clip_engine_audio_hook + .on_post(block_props.to_playtime(), args.reg); + } + // Record some metrics + if let Some(time_of_last_run) = self.time_of_last_run { + record_duration("helgobox.rt.audio_hook.total", time_of_last_run.elapsed()); + } + } + + fn process_feedback_commands(&mut self) { + // Process global direct device feedback (since v2.8.0-pre6) - in order to + // have deterministic feedback ordering, which is important for multi-instance + // orchestration. + for task in self + .feedback_task_receiver + .try_iter() + .take(FEEDBACK_TASK_BULK_SIZE) + { + use FeedbackAudioHookTask::*; + match task { + MidiDeviceFeedback(dev_id, value) => { + send_midi_device_feedback(dev_id, value); + } + SendMidi(dev_id, raw_midi_events) => { + MidiOutputDevice::new(dev_id).with_midi_output(|mo| { + if let Some(mo) = mo { + for event in &raw_midi_events { + mo.send_msg(event, SendMidiTime::Instantly); + } + } + }); + } + } + } + } + + fn pre_poll_real_time_instances(&self, block_props: AudioBlockProps) { + for (_, i) in self.real_time_instances.iter() { + non_blocking_lock(i, "RealTimeInstance pre_poll").pre_poll(block_props); + } + } + + fn post_poll_real_time_instances(&self) { + for (_, i) in self.real_time_instances.iter() { + non_blocking_lock(i, "RealTimeInstance post_poll").post_poll(); + } + } + + fn call_real_time_processors( + &mut self, + block_props: AudioBlockProps, + sample_count: u64, + might_be_rebirth: bool, + ) { + match &mut self.state { + AudioHookState::Normal => { + self.call_real_time_processors_in_normal_state( + block_props, + might_be_rebirth, + sample_count, + ); + } + AudioHookState::LearningSource { + sender, + midi_scanner, + } => { + for (_, p) in self.real_time_processors.iter() { + p.lock_recover() + .run_from_audio_hook_essential(might_be_rebirth); + } + for dev in Reaper::get().midi_input_devices() { + dev.with_midi_input(|mi| { + if let Some(mi) = mi { + for e in mi.get_read_buf() { + if let Some(res) = scan_midi(dev.id(), e, midi_scanner) { + let _ = sender.try_send(res); + } + } + } + }); + } + if let Some(res) = midi_scanner.poll() { + // Source detected via polling. Return to normal mode. + let _ = sender.try_send(res); + } + } + }; + } + + fn call_real_time_processors_in_normal_state( + &mut self, + block_props: AudioBlockProps, + might_be_rebirth: bool, + sample_count: u64, + ) { + // 1a. Drive real-time processors and determine used MIDI devices "on the go". + // + // Calling the real-time processor *before* processing its remove task has + // the benefit that it can still do some final work (e.g. clearing + // LEDs by sending zero feedback) before it's removed. That's also + // one of the reasons why we remove the real-time processor async by + // sending a message. It's okay if it's around for one cycle after a + // plug-in instance has unloaded (only the case if not the last instance). + // + let mut midi_dev_id_is_used = [false; MidiInputDeviceId::MAX_DEVICE_COUNT as usize]; + let mut midi_devs_used_at_all = false; + let start_of_block_timestamp = ControlEventTimestamp::from_rt( + sample_count, + block_props.frame_rate, + DurationInSeconds::ZERO, + ); + for (_, p) in self.real_time_processors.iter() { + // Since 1.12.0, we "drive" each plug-in instance's real-time processor + // primarily by the global audio hook. See https://github.com/helgoboss/helgobox/issues/84 why this is + // better. We also call it by the plug-in `process()` method though in order + // to be able to send MIDI to and to + // stop doing so synchronously if the plug-in is + // gone. + let mut guard = p.lock_recover(); + guard.run_from_audio_hook_all(might_be_rebirth, start_of_block_timestamp); + if guard.control_is_globally_enabled() { + if let MidiControlInput::Device(dev_id) = guard.midi_control_input() { + midi_dev_id_is_used[dev_id.get() as usize] = true; + midi_devs_used_at_all = true; + } + } + } + // 1b. Forward MIDI events from MIDI devices to ReaLearn instances and filter + // them globally if desired by the instance. + if midi_devs_used_at_all { + self.distribute_midi_events_to_processors( + block_props, + &midi_dev_id_is_used, + sample_count, + ); + } + } + + fn distribute_midi_events_to_processors( + &mut self, + block_props: AudioBlockProps, + midi_dev_id_is_used: &[bool; MidiInputDeviceId::MAX_DEVICE_COUNT as usize], + sample_count: u64, + ) { + self.midi_transformation_container + .prepare(block_props.frame_rate); + for dev_id in 0..MidiInputDeviceId::MAX_DEVICE_COUNT { + if !midi_dev_id_is_used[dev_id as usize] { + continue; + } + let dev_id = MidiInputDeviceId::new(dev_id); + MidiInputDevice::new(dev_id).with_midi_input(|mi| { + if let Some(mi) = mi { + let event_list = mi.get_read_buf(); + let mut bpos = 0; + while let Some(res) = event_list.enum_items(bpos) { + let next_bpos = res.next_bpos; + // Current control mode is checked further down the callstack. No need to + // check it here. + let our_event = + match MidiEvent::from_reaper(res.midi_event, block_props.frame_rate) { + Err(_) => continue, + Ok(e) => e, + }; + let frame_offset_in_secs = + res.midi_event.frame_offset() as f64 / MIDI_INPUT_FRAME_RATE.get(); + let timestamp = ControlEventTimestamp::from_rt( + sample_count, + block_props.frame_rate, + DurationInSeconds::new_panic(frame_offset_in_secs), + ); + let our_event = ControlEvent::new(our_event, timestamp); + let mut filter_out_event = false; + for (_, p) in self.real_time_processors.iter() { + let mut guard = p.lock_recover(); + if guard.control_is_globally_enabled() + && guard.midi_control_input() == MidiControlInput::Device(dev_id) + && guard.process_incoming_midi_from_audio_hook( + our_event, + &mut self.midi_transformation_container, + ) + { + filter_out_event = true; + } + } + if filter_out_event { + // Take event out of input buffer. In this case, we must not adjust bpos + // because just deleting the item has the same effect. + event_list.delete_item(bpos); + } else { + // Move cursor to next position + bpos = next_bpos; + } + } + // Add transformed events *after* iterating + for event in self + .midi_transformation_container + .drain_same_device_events() + { + let reaper_event = reaper_medium::MidiEvent::from_raw_ref(event.as_ref()); + event_list.add_item(reaper_event); + } + } + }); + } + // Process MIDI "MIDI: Send message" to "Device input" across multiple devices + for evt in self + .midi_transformation_container + .drain_other_device_events() + { + MidiInputDevice::new(evt.input_device_id).with_midi_input(|mi| { + if let Some(mi) = mi { + let event_list = mi.get_read_buf(); + let reaper_event = reaper_medium::MidiEvent::from_raw_ref(evt.event.as_ref()); + event_list.add_item(reaper_event); + } + }); + } + } + + fn process_midi_device_inquiry_command( + &mut self, + command: RequestMidiDeviceIdentityCommand, + ) -> Result<(), &'static str> { + let output_dev_id = command.output_device_id; + let output_dev = Reaper::get().midi_output_device_by_id(output_dev_id); + output_dev.with_midi_output(|output| -> Result<(), &'static str> { + let output = output.ok_or("MIDI output device not open")?; + let inquiry = RawMidiEvent::try_from_slice(0, MIDI_DEVICE_INQUIRY_REQUEST)?; + tracing::debug!(msg = "Sending MIDI device inquiry...", ?output_dev_id); + output.send_msg(inquiry, SendMidiTime::Instantly); + Ok(()) + })?; + let task = MidiDeviceInquiryTask { + command, + inquiry_sent_at: Instant::now(), + }; + self.midi_device_inquiry_task = Some(task); + Ok(()) + } + + fn check_for_midi_device_inquiry_response(&mut self) { + let Some(task) = self.midi_device_inquiry_task.as_ref() else { + // No task + return; + }; + if !task.check_for_midi_device_inquiry_response() { + // Task done + self.midi_device_inquiry_task = None; + } + } + + fn process_normal_commands(&mut self, block_props: AudioBlockProps) { + use NormalAudioHookTask::*; + let mut count = 0; + while let Ok(task) = self.normal_task_receiver.try_recv() { + match task { + AddRealTimeInstance(id, p) => { + self.real_time_instances.push((id, p)); + } + RemoveRealTimeInstance(id) => { + if let Some(pos) = self.real_time_instances.iter().position(|(i, _)| i == &id) { + self.real_time_instances.swap_remove(pos); + } + } + AddRealTimeProcessor(id, p) => { + self.real_time_processors.push((id, p)); + } + RemoveRealTimeProcessor(id) => { + if let Some(pos) = self.real_time_processors.iter().position(|(i, _)| i == &id) + { + self.real_time_processors.swap_remove(pos); + } + } + StartCapturingMidi(sender) => { + self.state = AudioHookState::LearningSource { + sender, + midi_scanner: Default::default(), + } + } + StopCapturingMidi => { + self.state = AudioHookState::Normal; + } + RequestMidiDeviceIdentity(command) => { + let _ = self.process_midi_device_inquiry_command(command); + } + #[cfg(feature = "playtime")] + PlaytimeClipEngineCommand(command) => { + let _ = self + .clip_engine_audio_hook + .on_pre_process_command(command, block_props.to_playtime()); + } + } + // Don't take too much at once + count += 1; + if count == AUDIO_HOOK_TASK_BULK_SIZE { + break; + } + } + let _ = block_props; + } +} + +impl OnAudioBuffer for RealearnAudioHook { + fn call(&mut self, args: OnAudioBufferArgs) { + if !self.initialized { + self.init_from_rt_thread(); + self.initialized = true; + } + assert_no_alloc(|| { + let is_pre = !args.is_post; + if is_pre { + measure_time("helgobox.rt.audio_hook.pre", || { + self.on_pre(args); + }); + } else { + measure_time("helgobox.rt.audio_hook.post", || { + self.on_post(args); + }); + } + }); + } +} + +fn scan_midi( + dev_id: MidiInputDeviceId, + evt: &reaper_medium::MidiEvent, + midi_scanner: &mut MidiScanner, +) -> Option { + let msg = IncomingMidiMessage::from_reaper(evt.message()).ok()?; + if classify_midi_message(msg) != MidiMessageClassification::Normal { + return None; + } + use IncomingMidiMessage::*; + match msg { + Short(short_msg) => midi_scanner.feed_short(short_msg, Some(dev_id)), + SysEx(bytes) => { + // It's okay here to temporarily permit allocation because crackling during learning + // is not a showstopper. + permit_alloc(|| MidiScanResult::try_from_bytes(bytes, Some(dev_id)).ok()) + } + } +} + +pub trait RealTimeProcessorLocker { + fn lock_recover(&self) -> MutexGuard; +} + +impl RealTimeProcessorLocker for SharedRealTimeProcessor { + /// This ignores poisoning, which is okay in our case because if the real-time + /// processor has panicked, we will see it in the REAPER console. No need to + /// hide that error with lots of follow-up poisoning errors! This is a kind of + /// recovery mechanism. + fn lock_recover(&self) -> MutexGuard { + non_blocking_lock(self, "RealTimeProcessor") + } +} + +impl MidiDeviceInquiryTask { + /// Returns `false` if task not necessary anymore. + pub fn check_for_midi_device_inquiry_response(&self) -> bool { + // Give up if waited too long for response. + if self.inquiry_sent_at.elapsed() > Duration::from_secs(1) { + tracing::debug!(msg = "Gave up waiting for MIDI device identity reply after timeout"); + return false; + } + // Check MIDI devices in question for response + if let Some(id) = self.command.input_device_id { + // Check user-defined input device for possible response + let dev = Reaper::get().midi_input_device_by_id(id); + if !self.process_input_dev(dev) { + return false; + } + } else { + // Check all input devices for possible response + for dev in Reaper::get().midi_input_devices() { + if !self.process_input_dev(dev) { + return false; + } + } + } + // Return true as long as we haven't got a response yet. + true + } + + /// Returns `false` if task not necessary anymore. + fn process_input_dev(&self, dev: MidiInputDevice) -> bool { + dev.with_midi_input(|mi| { + let Some(mi) = mi else { + return true; + }; + for evt in mi.get_read_buf() { + let msg = evt.message(); + let Ok(short) = msg.to_short_message() else { + continue; + }; + if short.r#type() == ShortMessageType::SystemExclusiveStart { + let reply_pattern = &MIDI_DEVICE_INQUIRY_REPLY_PATTERN; + let reply_pattern = + reply_pattern.get_or_init(create_device_inquiry_reply_pattern); + let is_identity_reply = reply_pattern.matches(msg.as_slice()); + let Ok(message) = ArrayVec::try_from(msg.as_slice()) else { + // Couldn't store the reply in the array. Shouldn't happen here because + // we set the ArrayVec's capacity to the max size of the raw event. + // So at a maximum it will be cropped. + return false; + }; + if is_identity_reply { + let reply = RequestMidiDeviceIdentityReply { + input_device_id: dev.id(), + device_inquiry_reply: MidiDeviceInquiryReply { message }, + }; + tracing::debug!(msg = "Received MIDI device identity reply", ?reply); + let _ = self.command.sender.try_send(reply); + return false; + } + } + } + true + }) + } +} + +const MIDI_DEVICE_INQUIRY_REQUEST: &[u8] = &[0xF0, 0x7E, 0x7F, 0x06, 0x01, 0xF7]; + +static MIDI_DEVICE_INQUIRY_REPLY_PATTERN: OnceLock = OnceLock::new(); + +fn create_device_inquiry_reply_pattern() -> BytePattern { + use Fixed as F; + use PatternByte::*; + BytePattern::new(vec![ + F(0xF0), + F(0x7E), + Single, + F(0x06), + F(0x02), + Multi, + F(0xF7), + ]) +} diff --git a/plugin-reaper-realearn/main/src/domain/backbone.rs b/plugin-reaper-realearn/main/src/domain/backbone.rs new file mode 100644 index 0000000..9cb8435 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/backbone.rs @@ -0,0 +1,865 @@ +use base::{ + make_available_globally_in_main_thread_on_demand, NamedChannelSender, SenderToNormalThread, +}; + +use crate::domain::{ + AdditionalFeedbackEvent, ControlInput, DeviceControlInput, DeviceFeedbackOutput, + FeedbackOutput, InstanceId, QualifiedStreamDeckMessage, RealearnSourceState, + RealearnTargetState, ReaperTarget, ReaperTargetType, SafeLua, SharedInstance, + StreamDeckDeviceId, StreamDeckDeviceManager, StreamDeckMessage, + StreamDeckSourceFeedbackPayload, StreamDeckSourceFeedbackValue, UnitId, WeakInstance, +}; +#[allow(unused)] +use anyhow::{anyhow, Context}; +use pot::{PotFavorites, PotFilterExcludes}; + +use ab_glyph::{FontRef, PxScale}; +use base::hash_util::{NonCryptoHashMap, NonCryptoHashSet}; +use cached::proc_macro::cached; +use camino::Utf8PathBuf; +use fragile::Fragile; +use helgoboss_learn::{RgbColor, UnitValue}; +use helgobox_api::persistence::{ + StreamDeckButtonBackground, StreamDeckButtonForeground, TargetTouchCause, +}; +use imageproc::definitions::{HasBlack, HasWhite}; +use once_cell::sync::Lazy; +// Use once_cell::sync::Lazy instead of std::sync::LazyLock to be able to build with Rust 1.77.2 (to stay Win7-compatible) +use once_cell::sync::Lazy as LazyLock; +use palette::IntoColor; +use reaper_high::{Fx, Reaper}; +use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::cmp::min; +use std::hash::Hash; +use std::rc::Rc; +use std::sync::RwLock; +use std::time::{Duration, Instant}; +use streamdeck::StreamDeck; +use strum::EnumCount; + +make_available_globally_in_main_thread_on_demand!(Backbone); + +/// Just the old term as alias for easier class search. +type _BackboneState = Backbone; + +/// This is the domain-layer "backbone" which can hold state that's shared among all ReaLearn +/// instances. +pub struct Backbone { + time_of_start: Instant, + additional_feedback_event_sender: SenderToNormalThread, + source_state: RefCell, + target_state: RefCell, + last_touched_targets_container: RefCell, + /// Value: Instance ID of the ReaLearn instance that owns the control input. + control_input_usages: RefCell>>, + /// Value: Instance ID of the ReaLearn instance that owns the feedback output. + feedback_output_usages: + RefCell>>, + superior_units: RefCell>, + /// We hold pointers to all ReaLearn instances in order to let instance B + /// borrow a Playtime matrix which is owned by instance A. This is great because it allows us to + /// control the same Playtime matrix from different controllers. + // TODO-high-playtime-refactoring Since the introduction of units, foreign matrices are not used in practice. Let's + // keep this for a while and remove. + instances: RefCell>, + was_processing_keyboard_input: Cell, + global_pot_filter_exclude_list: RefCell, + recently_focused_fx_container: Rc>, + stream_deck_device_manager: RefCell, + stream_decks: RefCell>, + stream_deck_button_states: RefCell>>, +} + +#[derive(Debug, Default)] +pub struct AnyThreadBackboneState { + /// Thread-safe because we need to access the favorites both from the main thread (e.g. for + /// display purposes) and from the pot worker (for building the collections). Alternative would + /// be to clone the favorites whenever we build the collections. + pub pot_favorites: RwLock, +} + +impl AnyThreadBackboneState { + pub fn get() -> &'static AnyThreadBackboneState { + static INSTANCE: Lazy = Lazy::new(AnyThreadBackboneState::default); + &INSTANCE + } +} + +struct LastTouchedTargetsContainer { + /// Contains the most recently touched targets at the end! + last_target_touches: Vec, +} + +struct TargetTouch { + pub target: ReaperTarget, + pub caused_by_realearn: bool, +} + +impl Default for LastTouchedTargetsContainer { + fn default() -> Self { + // Each target type can be there twice: Once touched via ReaLearn, once touched in other way + let max_count = ReaperTargetType::COUNT * 2; + Self { + last_target_touches: Vec::with_capacity(max_count), + } + } +} + +impl LastTouchedTargetsContainer { + /// Returns `true` if the last touched target has changed. + pub fn update(&mut self, event: TargetTouchEvent) -> bool { + // Don't do anything if the given target is the same as the last touched one + if let Some(last_target_touch) = self.last_target_touches.last() { + if event.target == last_target_touch.target + && event.caused_by_realearn == last_target_touch.caused_by_realearn + { + return false; + } + } + // Remove all previous entries of that target type and conditions + let last_touched_target_type = ReaperTargetType::from_target(&event.target); + self.last_target_touches.retain(|t| { + ReaperTargetType::from_target(&t.target) != last_touched_target_type + || t.caused_by_realearn != event.caused_by_realearn + }); + // Push it as last touched target + let touch = TargetTouch { + target: event.target, + caused_by_realearn: event.caused_by_realearn, + }; + self.last_target_touches.push(touch); + true + } + + pub fn find(&self, filter: LastTouchedTargetFilter) -> Option<&ReaperTarget> { + let touch = self.last_target_touches.iter().rev().find(|t| { + match filter.touch_cause { + TargetTouchCause::Reaper if t.caused_by_realearn => return false, + TargetTouchCause::Realearn if !t.caused_by_realearn => return false, + _ => {} + } + let target_type = ReaperTargetType::from_target(&t.target); + filter.included_target_types.contains(&target_type) + })?; + Some(&touch.target) + } +} + +pub struct LastTouchedTargetFilter<'a> { + pub included_target_types: &'a NonCryptoHashSet, + pub touch_cause: TargetTouchCause, +} + +impl LastTouchedTargetFilter<'_> { + pub fn matches(&self, event: &TargetTouchEvent) -> bool { + // Check touch cause + match self.touch_cause { + TargetTouchCause::Realearn if !event.caused_by_realearn => return false, + TargetTouchCause::Reaper if event.caused_by_realearn => return false, + _ => {} + } + // Check target types + let actual_target_type = ReaperTargetType::from_target(&event.target); + self.included_target_types.contains(&actual_target_type) + } +} + +impl Backbone { + pub fn new( + additional_feedback_event_sender: SenderToNormalThread, + target_context: RealearnTargetState, + ) -> Self { + Self { + time_of_start: Instant::now(), + additional_feedback_event_sender, + source_state: Default::default(), + target_state: RefCell::new(target_context), + last_touched_targets_container: Default::default(), + control_input_usages: Default::default(), + feedback_output_usages: Default::default(), + superior_units: Default::default(), + instances: Default::default(), + was_processing_keyboard_input: Default::default(), + global_pot_filter_exclude_list: Default::default(), + recently_focused_fx_container: Default::default(), + stream_deck_device_manager: Default::default(), + stream_decks: Default::default(), + stream_deck_button_states: Default::default(), + } + } + + /// Returns IDs of newly connected Stream Deck devices. + pub fn detect_stream_deck_device_changes(&self) -> NonCryptoHashSet { + let devices_in_use = self.stream_deck_device_manager.borrow().devices_in_use(); + let actually_connected_devices: NonCryptoHashSet<_> = + self.stream_decks.borrow().keys().copied().collect(); + if devices_in_use == actually_connected_devices { + return Default::default(); + } + self.connect_or_disconnect_stream_deck_devices(&devices_in_use) + } + + pub fn register_stream_deck_usage(&self, unit_id: UnitId, device: Option) { + // Change device usage + let mut manager = self.stream_deck_device_manager.borrow_mut(); + manager.register_device_usage(unit_id, device); + let devices_in_use = manager.devices_in_use(); + // Update connections + self.connect_or_disconnect_stream_deck_devices(&devices_in_use); + } + + fn connect_or_disconnect_stream_deck_devices( + &self, + devices_in_use: &NonCryptoHashSet, + ) -> NonCryptoHashSet { + let mut decks = self.stream_decks.borrow_mut(); + // Disconnect from devices that are not in use anymore + decks.retain(|id, _| devices_in_use.contains(id)); + // Connect to devices + devices_in_use + .iter() + .filter_map(|dev_id| { + if decks.contains_key(dev_id) { + return None; + } + match dev_id.connect() { + Ok(dev) => { + decks.insert(*dev_id, dev); + Some(*dev_id) + } + Err(e) => { + tracing::warn!(msg = "Couldn't connect to Stream Deck device", %e); + None + } + } + }) + .collect() + } + + pub fn set_stream_deck_brightness( + &self, + dev_id: StreamDeckDeviceId, + percent: UnitValue, + ) -> anyhow::Result<()> { + let mut decks = self.stream_decks.borrow_mut(); + let sd = decks + .get_mut(&dev_id) + .context("stream deck not connected")?; + sd.set_brightness((percent.get() * 100.0).round() as _)?; + Ok(()) + } + + pub fn poll_stream_deck_messages(&self) -> Vec { + let mut decks = self.stream_decks.borrow_mut(); + let mut button_states = self.stream_deck_button_states.borrow_mut(); + let mut messages = vec![]; + decks.retain(|id, deck| { + let result = poll_stream_deck_messages(&mut messages, *id, deck, &mut button_states); + match result { + Ok(_) => true, + Err(streamdeck::Error::NoData) => true, + Err(e) => { + tracing::warn!(msg = "Error polling for stream deck events", %e); + false + } + } + }); + messages + } + + pub fn send_stream_deck_feedback( + &self, + dev_id: StreamDeckDeviceId, + value: StreamDeckSourceFeedbackValue, + ) -> anyhow::Result<()> { + use image::{Pixel, Rgba, RgbaImage}; + const DEFAULT_BG_COLOR: RgbColor = RgbColor::BLACK; + const DEFAULT_FG_COLOR: RgbColor = RgbColor::WHITE; + #[derive(Eq, PartialEq, Clone, Hash)] + enum ResizeMode { + Square, + ShortestSide, + } + fn adjust_size(dimension: u32, factor: f64) -> u32 { + (dimension as f64 * factor).floor() as u32 + } + #[cached(result = true)] + fn load_image_for_stream_deck( + path: String, + size: u32, + resize_mode: ResizeMode, + ) -> anyhow::Result { + let path: Utf8PathBuf = path.into(); + let path = if path.is_relative() { + Reaper::get().resource_path().join(path) + } else { + path + }; + let image = image::open(path)?; + let (width, height) = match resize_mode { + ResizeMode::Square => (size, size), + ResizeMode::ShortestSide => { + let factor = size as f64 / min(image.width(), image.height()) as f64; + ( + adjust_size(image.width(), factor), + adjust_size(image.height(), factor), + ) + } + }; + let image = image + .resize_to_fill(width, height, image::imageops::FilterType::Lanczos3) + .into(); + Ok(image) + } + let mut error_msg: Option = None; + let mut load_image_for_stream_deck_reporting_error = + |path: String, size: u32, resize_mode: ResizeMode| -> Option { + load_image_for_stream_deck(path, size, resize_mode) + .inspect_err(|e| { + error_msg = Some(e.to_string()); + }) + .ok() + }; + fn set_alpha(pixel: &mut Rgba, alpha: f32) { + pixel[3] = (alpha * 255.0).round() as u8; + } + fn overlay_with_image(target: &mut RgbaImage, overlay: &RgbaImage, alpha: Option) { + for (x, y, target_pixel) in target.enumerate_pixels_mut() { + let mut overlay_pixel = *overlay.get_pixel(x, y); + if let Some(alpha) = alpha { + set_alpha(&mut overlay_pixel, alpha); + } + target_pixel.blend(&overlay_pixel) + } + } + fn overlay_with_color(target: &mut RgbaImage, mut overlay: Rgba, alpha: f32) { + set_alpha(&mut overlay, alpha); + for pixel in target.pixels_mut() { + pixel.blend(&overlay); + } + } + use std::f32::consts::PI; + + /// Draws a knob with an indicator showing the given value (0% to 100%). + /// The knob is centered within the specified width and height. + /// - `width`: The width of the image. + /// - `height`: The height of the image. + /// - `value`: The value to display, from 0.0 to 100.0 (percentage). + /// - `filename`: The filename to save the image as. + fn draw_knob(img: &mut RgbaImage, width: u32, height: u32, value: f32) { + let cx = width as f32 / 2.0; + let cy = height as f32 / 2.0; + // Circle + let radius = cx.min(cy) - 10.0; + let gray = 40; + let knob_color = Rgba([gray, gray, gray, 100]); + imageproc::drawing::draw_filled_circle_mut( + img, + (cx as _, cy as _), + radius as _, + knob_color, + ); + // Indicator line + let angle = value * 1.5 * PI + PI * 0.75; + let line_length = radius - 5.0; + let line_x = cx + line_length * angle.cos(); + let line_y = cy + line_length * angle.sin(); + let indicator_color = Rgba::white(); + draw_line( + img, + cx as i32, + cy as i32, + line_x as i32, + line_y as i32, + indicator_color, + ); + } + + /// Draws a simple line between two points on the image using Bresenham's line algorithm. + fn draw_line(img: &mut RgbaImage, x0: i32, y0: i32, x1: i32, y1: i32, color: Rgba) { + imageproc::drawing::draw_antialiased_line_segment_mut( + img, + (x0, y0), + (x1, y1), + color, + |i, _, _| i, + ); + } + + let mut stream_decks = self.stream_decks.borrow_mut(); + let sd = stream_decks + .get_mut(&dev_id) + .context("stream deck not connected")?; + let button_size = sd.kind().image_size().0 as u32; + let StreamDeckSourceFeedbackPayload::On(payload) = value.payload else { + // Switch display off + let black = RgbaImage::from_pixel(button_size, button_size, Rgba::black()); + sd.set_button_image(value.button_index as _, black.into())?; + return Ok(()); + }; + // Paint grounding (important for images with alpha channel) + let bg_color: Rgba = payload.background_color.unwrap_or(DEFAULT_BG_COLOR).into(); + let mut bg_layer = RgbaImage::from_pixel(button_size, button_size, bg_color); + // Paint background + let solid_bg_color = match payload.button_design.background { + StreamDeckButtonBackground::Color(_) => Some(bg_color), + StreamDeckButtonBackground::Image(b) => { + if let Some(bg_img) = load_image_for_stream_deck_reporting_error( + b.path, + button_size, + ResizeMode::Square, + ) { + overlay_with_image(&mut bg_layer, &bg_img, None); + None + } else { + Some(bg_color) + } + } + }; + // Paint foreground + let mut fg_color: Rgba = payload.foreground_color.unwrap_or(DEFAULT_FG_COLOR).into(); + let solid_bg_color = if payload.numeric_value.is_some() { + let numeric_value = payload.numeric_value.unwrap_or(UnitValue::MIN).get() as f32; + match payload.button_design.foreground { + StreamDeckButtonForeground::None => solid_bg_color, + StreamDeckButtonForeground::FadingColor(_) => { + overlay_with_color(&mut bg_layer, fg_color, numeric_value); + // If the grounding was a solid color, the result is a solid color + solid_bg_color.map(|mut c| { + set_alpha(&mut fg_color, numeric_value); + c.blend(&fg_color); + c + }) + } + StreamDeckButtonForeground::FadingImage(b) => { + let mut fg_layer = RgbaImage::from_pixel(button_size, button_size, fg_color); + if let Some(fg_img) = load_image_for_stream_deck_reporting_error( + b.path, + button_size, + ResizeMode::Square, + ) { + overlay_with_image(&mut fg_layer, &fg_img, None); + } + overlay_with_image(&mut bg_layer, &fg_layer, Some(numeric_value)); + // An image can have any colors, so we can't assume that the result is solid + None + } + StreamDeckButtonForeground::SlidingImage(b) => { + if let Some(fg_img) = load_image_for_stream_deck_reporting_error( + b.path, + button_size, + ResizeMode::ShortestSide, + ) { + let (longest_size_of_img, height_is_longest) = + if fg_img.width() < fg_img.height() { + (fg_img.height(), true) + } else { + (fg_img.width(), false) + }; + let max_pos_offset = longest_size_of_img - button_size; + let pos_offset = (numeric_value * max_pos_offset as f32).floor() as u32; + for (button_x, button_y, target_pixel) in bg_layer.enumerate_pixels_mut() { + let (fg_x, fg_y) = if height_is_longest { + (button_x, pos_offset + button_y) + } else { + (pos_offset + button_x, button_y) + }; + let fg_pixel = fg_img + .get_pixel_checked(fg_x, fg_y) + .copied() + .unwrap_or(Rgba([0, 0, 0, 0])); + target_pixel.blend(&fg_pixel) + } + } + // An image can have any colors, so we can't assume that the result is solid + None + } + StreamDeckButtonForeground::FullBar(_) => { + fg_color[3] = 100; + let rect_height = (button_size as f32 * numeric_value) as u32; + // Fill the background + for (_, y, pixel) in bg_layer.enumerate_pixels_mut() { + if y >= button_size - rect_height { + pixel.blend(&fg_color); + } + } + // The bar changes the result quite a bit, so we can't assume that the result is solid + None + } + StreamDeckButtonForeground::Knob(_) => { + draw_knob(&mut bg_layer, button_size, button_size, numeric_value); + // The knob changes the result quite a bit, so we can't assume that the result is solid + None + } + } + } else { + solid_bg_color + }; + // Draw text + let text = error_msg + .or(payload.text_value) + .unwrap_or(payload.button_design.static_text); + if !text.trim().is_empty() { + static FONT: LazyLock = LazyLock::new(|| { + FontRef::try_from_slice(include_bytes!("./Exo2-Light.otf")).unwrap() + }); + let text_color = if let Some(c) = solid_bg_color { + // Background is a solid color. Calculate the contrast color. + let solid_bg_color = palette::Srgb::new(c[0], c[1], c[2]); + let solid_bg_color: palette::Srgb = solid_bg_color.into_format(); + let lab: palette::Lab = solid_bg_color.into_color(); + if lab.l > 50.0 { + // Bright background => Dark text color + Rgba::black() + } else { + // Dark background => Bright text color + Rgba::white() + } + } else { + // Background is not a solid color, so we need to add some background to ensure the text is visible + overlay_with_color(&mut bg_layer, Rgba::black(), 0.5); + Rgba::white() + }; + let num_display_lines = 4; + let line_height = (button_size as f32 / num_display_lines as f32).round() as u32; + let scale = PxScale::from(line_height as f32); + let num_text_lines = text.lines().count() as u32; + // Center text vertically + let y_offset = button_size.saturating_sub(num_text_lines * line_height) / 2; + for (l, text_line) in text.lines().take(num_display_lines).enumerate() { + let (text_line_width, _) = imageproc::drawing::text_size(scale, &*FONT, text_line); + // Center text horizontally + let x = button_size.saturating_sub(text_line_width) / 2; + let y = y_offset + l as u32 * line_height; + imageproc::drawing::draw_text_mut( + &mut bg_layer, + text_color, + x as _, + y as _, + scale, + &*FONT, + text_line, + ); + } + } + sd.set_button_image(value.button_index as _, bg_layer.into())?; + Ok(()) + } + + pub fn duration_since_time_of_start(&self) -> Duration { + self.time_of_start.elapsed() + } + + pub fn pot_filter_exclude_list(&self) -> Ref { + self.global_pot_filter_exclude_list.borrow() + } + + pub fn pot_filter_exclude_list_mut(&self) -> RefMut { + self.global_pot_filter_exclude_list.borrow_mut() + } + + /// Sets a flag that indicates that there's at least one ReaLearn mapping (in any instance) + /// which matched some computer keyboard input in this main loop cycle. This flag will be read + /// and reset a bit later in the same main loop cycle by [`RealearnControlSurfaceMiddleware`]. + pub fn set_keyboard_input_match_flag(&self) { + self.was_processing_keyboard_input.set(true); + } + + /// Resets the flag which indicates that there was at least one ReaLearn mapping which matched + /// some computer keyboard input. Returns whether the flag was set. + pub fn reset_keyboard_input_match_flag(&self) -> bool { + self.was_processing_keyboard_input.replace(false) + } + + /// Returns a static reference to a Lua state, intended to be used in the main thread only! + /// + /// This should only be used for Lua stuff like MIDI scripts, where it would be too expensive + /// to create a new Lua state for each single script and too complex to have narrow-scoped + /// lifetimes. For all other situations, a new Lua state should be constructed. + /// + /// # Panics + /// + /// Panics if not called from main thread. + /// + /// # Safety + /// + /// If this static reference is passed to other user threads and used there, we are done. + pub unsafe fn main_thread_lua() -> &'static SafeLua { + static LUA: Lazy> = Lazy::new(|| Fragile::new(SafeLua::new().unwrap())); + LUA.get() + } + + pub fn source_state() -> &'static RefCell { + &Backbone::get().source_state + } + + pub fn target_state() -> &'static RefCell { + &Backbone::get().target_state + } + + /// Returns the last touched targets (max. one per touchable type, so not much more than a + /// dozen). The most recently touched ones are at the end, so it's ascending order! + pub fn extract_last_touched_targets(&self) -> Vec { + self.last_touched_targets_container + .borrow() + .last_target_touches + .iter() + .map(|t| t.target.clone()) + .collect() + } + + pub fn find_last_touched_target( + &self, + filter: LastTouchedTargetFilter, + ) -> Option { + let container = self.last_touched_targets_container.borrow(); + container.find(filter).cloned() + } + + pub fn is_superior(&self, instance_id: &UnitId) -> bool { + self.superior_units.borrow().contains(instance_id) + } + + pub fn make_superior(&self, instance_id: UnitId) { + self.superior_units.borrow_mut().insert(instance_id); + } + + pub fn make_inferior(&self, instance_id: &UnitId) { + self.superior_units.borrow_mut().remove(instance_id); + } + + // + // /// Returns and - if necessary - installs an owned Playtime matrix. + // /// + // /// If this instance already contains an owned Playtime matrix, returns it. If not, creates + // /// and installs one, removing a possibly existing foreign matrix reference. + // pub fn get_or_insert_owned_clip_matrix(&mut self) -> &mut playtime_clip_engine::base::Matrix { + // self.create_and_install_owned_clip_matrix_if_necessary(); + // self.owned_clip_matrix_mut().unwrap() + // } + + // TODO-high-playtime-refactoring Woah, ugly. This shouldn't be here anymore, the design involved and this dirt + // stayed. self is not used. Same with _mut. + /// Grants immutable access to the Playtime matrix defined for the given ReaLearn instance, + /// if one is defined. + /// + /// # Errors + /// + /// Returns an error if the given instance doesn't have any Playtime matrix defined. + #[cfg(feature = "playtime")] + pub fn with_clip_matrix( + &self, + instance: &SharedInstance, + f: impl FnOnce(&playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + let instance = instance.borrow(); + let matrix = instance.get_playtime_matrix()?; + Ok(f(matrix)) + } + + /// Grants mutable access to the Playtime matrix defined for the given ReaLearn instance, + /// if one is defined. + #[cfg(feature = "playtime")] + pub fn with_clip_matrix_mut( + &self, + instance: &SharedInstance, + f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + let mut instance = instance.borrow_mut(); + let matrix = instance.get_playtime_matrix_mut()?; + Ok(f(matrix)) + } + + pub fn register_instance(&self, id: InstanceId, instance: WeakInstance) { + self.instances.borrow_mut().insert(id, instance); + } + + pub(super) fn unregister_instance(&self, id: &InstanceId) { + self.instances.borrow_mut().remove(id); + } + + pub fn control_is_allowed(&self, unit_id: &UnitId, control_input: ControlInput) -> bool { + if let Some(dev_input) = control_input.device_input() { + self.interaction_is_allowed(unit_id, dev_input, &self.control_input_usages) + } else { + true + } + } + + #[allow(dead_code)] + pub fn find_instance(&self, instance_id: InstanceId) -> Option { + let weak_instance_states = self.instances.borrow(); + let weak_instance_state = weak_instance_states.get(&instance_id)?; + weak_instance_state.upgrade() + } + + /// This should be called whenever the focused FX changes. + /// + /// We use this in order to be able to access the previously focused FX at all times. + pub fn notify_fx_focused(&self, new_fx: Option) { + self.recently_focused_fx_container.borrow_mut().feed(new_fx); + } + + /// Returns the last relevant focused FX even if it's not focused anymore and even if it's closed. + /// + /// Returns `None` only if no FX has been focused yet or if the last focused FX doesn't exist anymore. + /// + /// One special thing about this is that this doesn't necessarily return the currently focused + /// FX. It could also be the previously focused one. That's important because when queried from ReaLearn UI, the + /// current one is mostly ReaLearn itself - which is in most cases not what we want. + pub fn last_relevant_available_focused_fx(&self, this_realearn_fx: &Fx) -> Option { + self.recently_focused_fx_container + .borrow() + .last_relevant_available_fx(this_realearn_fx) + .cloned() + } + + pub fn feedback_is_allowed(&self, unit_id: &UnitId, feedback_output: FeedbackOutput) -> bool { + if let Some(dev_output) = feedback_output.device_output() { + self.interaction_is_allowed(unit_id, dev_output, &self.feedback_output_usages) + } else { + true + } + } + + /// Also drops all previous usage of that instance. + /// + /// Returns true if this actually caused a change in *feedback output* usage. + pub fn update_io_usage( + &self, + instance_id: &UnitId, + control_input: Option, + feedback_output: Option, + ) -> bool { + { + let mut usages = self.control_input_usages.borrow_mut(); + update_io_usage(&mut usages, instance_id, control_input); + } + { + let mut usages = self.feedback_output_usages.borrow_mut(); + update_io_usage(&mut usages, instance_id, feedback_output) + } + } + + pub(super) fn notify_target_touched(&self, event: TargetTouchEvent) { + let has_changed = self + .last_touched_targets_container + .borrow_mut() + .update(event); + if has_changed { + self.additional_feedback_event_sender + .send_complaining(AdditionalFeedbackEvent::LastTouchedTargetChanged) + } + } + + fn interaction_is_allowed( + &self, + instance_id: &UnitId, + device: D, + usages: &RefCell>>, + ) -> bool { + let superior_instances = self.superior_units.borrow(); + if superior_instances.is_empty() || superior_instances.contains(instance_id) { + // There's no instance living on a higher floor. + true + } else { + // There's at least one instance living on a higher floor and it's not ours. + let usages = usages.borrow(); + if let Some(instances) = usages.get(&device) { + if instances.len() <= 1 { + // It's just us using this device (or nobody, but shouldn't happen). + true + } else { + // Other instances use this device as well. + // Allow usage only if none of these instances are on the upper floor. + !instances.iter().any(|id| superior_instances.contains(id)) + } + } else { + // No instance using this device (shouldn't happen because at least we use it). + true + } + } + } +} + +/// Returns `true` if there was an actual change. +fn update_io_usage( + usages: &mut NonCryptoHashMap>, + instance_id: &UnitId, + device: Option, +) -> bool { + let mut previously_used_device: Option = None; + for (dev, ids) in usages.iter_mut() { + let was_removed = ids.remove(instance_id); + if was_removed { + previously_used_device = Some(*dev); + } + } + if let Some(dev) = device { + usages + .entry(dev) + .or_default() + .insert(instance_id.to_owned()); + } + device != previously_used_device +} + +#[derive(Clone, Debug)] +pub struct TargetTouchEvent { + pub target: ReaperTarget, + pub caused_by_realearn: bool, +} + +#[derive(Debug, Default)] +struct RecentlyFocusedFxContainer { + previous: Option, + current: Option, +} + +impl RecentlyFocusedFxContainer { + pub fn last_relevant_available_fx(&self, this_realearn_fx: &Fx) -> Option<&Fx> { + [self.current.as_ref(), self.previous.as_ref()] + .into_iter() + .flatten() + .find(|fx| fx.is_available() && *fx != this_realearn_fx) + } + + pub fn feed(&mut self, new_fx: Option) { + // Never clear any memorized FX. + let Some(new_fx) = new_fx else { + return; + }; + // Don't rotate if current FX has not changed. + if let Some(current) = self.current.as_ref() { + if &new_fx == current { + return; + } + } + // Rotate + self.previous = self.current.take(); + self.current = Some(new_fx); + } +} + +fn poll_stream_deck_messages( + messages: &mut Vec, + dev_id: StreamDeckDeviceId, + sd: &mut StreamDeck, + button_states: &mut NonCryptoHashMap>, +) -> Result<(), streamdeck::Error> { + let old_button_states = button_states.entry(dev_id).or_default(); + let new_button_states = sd.read_buttons(None)?; + for (i, new_is_on) in new_button_states.iter().enumerate() { + let old_is_on = old_button_states.get(i).copied().unwrap_or(0); + if *new_is_on == old_is_on { + continue; + } + let msg = QualifiedStreamDeckMessage { + dev_id, + msg: StreamDeckMessage::new(i as u32, *new_is_on > 0), + }; + messages.push(msg); + } + *old_button_states = new_button_states; + Ok(()) +} diff --git a/plugin-reaper-realearn/main/src/domain/conditional_activation.rs b/plugin-reaper-realearn/main/src/domain/conditional_activation.rs new file mode 100644 index 0000000..c869015 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/conditional_activation.rs @@ -0,0 +1,320 @@ +use crate::base::eel; +use crate::domain::{ + CompartmentParamIndex, CompartmentParams, EffectiveParamValue, ExpressionEvaluator, MappingId, + RawParamValue, COMPARTMENT_PARAMETER_COUNT, EXPRESSION_NONE_VALUE, +}; +use base::hash_util::NonCryptoHashSet; +use base::regex; +use helgoboss_learn::AbsoluteValue; +use std::error::Error; + +#[derive(Debug)] +pub enum ActivationCondition { + Always, + Modifiers(Vec), + Program { + param_index: CompartmentParamIndex, + program_index: u32, + }, + // Boxed in order to keep the enum variants at a similar size (clippy gave that hint) + Eel(Box), + Expression(Box), + TargetValue { + lead_mapping: Option, + condition: Box, + }, +} + +impl ActivationCondition { + /// Returns if this activation condition can be affected by parameter changes in general. + pub fn can_be_affected_by_parameters(&self) -> bool { + !matches!(self, ActivationCondition::Always) + } + + /// Returns the referenced lead mapping of this activation condition if it's a target-value + /// based one. + pub fn target_value_lead_mapping(&self) -> Option { + match self { + ActivationCondition::TargetValue { + lead_mapping: Some(m), + .. + } => Some(*m), + _ => None, + } + } + + /// Returns if this activation condition is fulfilled in presence of the given set of + /// parameters. + /// + /// Returns `None` if the condition doesn't depend on parameter values (in which case it must + /// be evaluated in other ways). + pub fn is_fulfilled(&self, params: &CompartmentParams) -> Option { + use ActivationCondition::*; + let res = match self { + Always => true, + Modifiers(conditions) => modifier_conditions_are_fulfilled(conditions, params), + Program { + param_index, + program_index, + } => program_condition_is_fulfilled(*param_index, *program_index, params), + Eel(condition) => { + condition.notify_params_changed(params); + condition.is_fulfilled() + } + Expression(condition) => condition.is_fulfilled(params), + TargetValue { .. } => return None, + }; + Some(res) + } + + /// Returns `Some` if the given value update affects the mapping's activation state and if the + /// resulting state is on or off. + /// + /// Passing a `None` target value means the target is inactive. + pub fn process_target_value_update( + &self, + lead_mapping_id: MappingId, + target_value: Option, + ) -> Option { + match self { + ActivationCondition::TargetValue { + lead_mapping: Some(rm), + condition, + } if lead_mapping_id == *rm => { + let y = match target_value { + None => EXPRESSION_NONE_VALUE, + Some(v) => v.to_unit_value().get(), + }; + let result = condition.evaluate_with_additional_vars(|name, _| match name { + "none" => Some(EXPRESSION_NONE_VALUE), + "y" => Some(y), + _ => None, + }); + result.ok().map(|v| v > 0.0) + } + _ => None, + } + } + + /// Returns `Some` if the given value update affects the mapping's activation state and if the + /// resulting state is on or off. + /// + /// Other parameters in the given array should not have changed! That's especially important + /// for the EEL activation condition which will ignore the other values in the array for + /// performance reasons and just look at the difference (because it has the array already + /// stored in the EEL VM). For performance reasons as well, the other activation condition types + /// don't store anything and read the given parameter array. + /// + /// Attention: For EEL condition, this has a side effect! + /// TODO-low This is not visible because it's &self. + pub fn process_param_update( + &self, + params: &CompartmentParams, + // Changed index + index: CompartmentParamIndex, + // Previous value at changed index + previous_value: RawParamValue, + ) -> Option { + use ActivationCondition::*; + let is_fulfilled = match self { + Modifiers(conditions) => { + let is_affected = conditions.iter().any(|c| { + c.is_affected_by_param_change( + index, + previous_value, + params.at(index).raw_value(), + ) + }); + if !is_affected { + return None; + } + modifier_conditions_are_fulfilled(conditions, params) + } + Program { + param_index, + program_index, + } => { + if index != *param_index { + return None; + } + program_condition_is_fulfilled(*param_index, *program_index, params) + } + Eel(condition) => { + let is_affected = condition + .notify_param_changed(index, params.at(index).effective_value().into()); + if !is_affected { + return None; + } + condition.is_fulfilled() + } + Expression(condition) => condition.is_fulfilled(params), + Always => return None, + // This conditional activation doesn't depend on parameter values, it's evaluated + // in other ways. + TargetValue { .. } => return None, + }; + Some(is_fulfilled) + } +} + +fn modifier_conditions_are_fulfilled( + conditions: &[ModifierCondition], + params: &CompartmentParams, +) -> bool { + conditions + .iter() + .all(|condition| condition.is_fulfilled(params)) +} + +fn program_condition_is_fulfilled( + param_index: CompartmentParamIndex, + program_index: u32, + params: &CompartmentParams, +) -> bool { + let current_program_index = match params.at(param_index).effective_value() { + EffectiveParamValue::Continuous(v) => { + // If no count given for the parameter, we just assume a count of 100. + (v * 99.0).round() as u32 + } + EffectiveParamValue::Discrete(v) => v, + }; + current_program_index == program_index +} + +fn param_value_is_on(value: f32) -> bool { + value > 0.0 +} + +#[derive(Debug)] +pub struct ModifierCondition { + param_index: CompartmentParamIndex, + is_on: bool, +} + +impl ModifierCondition { + pub fn new(param_index: CompartmentParamIndex, is_on: bool) -> ModifierCondition { + ModifierCondition { param_index, is_on } + } + + pub fn is_affected_by_param_change( + &self, + index: CompartmentParamIndex, + previous_value: RawParamValue, + value: RawParamValue, + ) -> bool { + self.param_index == index && param_value_is_on(previous_value) != param_value_is_on(value) + } + + /// Returns if this activation condition is fulfilled in presence of the given set of + /// parameters. + pub fn is_fulfilled(&self, params: &CompartmentParams) -> bool { + let param_value = params.at(self.param_index).raw_value(); + let is_on = param_value_is_on(param_value); + is_on == self.is_on + } +} + +#[derive(Debug)] +pub struct ExpressionCondition { + evaluator: ExpressionEvaluator, +} + +impl ExpressionCondition { + pub fn compile(expression: &str) -> Result> { + let condition = Self { + evaluator: ExpressionEvaluator::compile(expression)?, + }; + Ok(condition) + } + + pub fn is_fulfilled(&self, params: &CompartmentParams) -> bool { + let result = self.evaluator.evaluate_with_params(params); + result.map(|v| v > 0.0).unwrap_or(false) + } +} + +#[derive(Debug)] +pub struct EelCondition { + // Declared above VM in order to be dropped before VM is dropped. + program: eel::Program, + // The existence in memory and the Drop is important. + _vm: eel::Vm, + params: [Option; COMPARTMENT_PARAMETER_COUNT as usize], + y: eel::Variable, +} + +impl EelCondition { + // Compiles the given script and creates an appropriate condition. + pub fn compile(eel_script: &str) -> Result { + if eel_script.trim().is_empty() { + return Err("script empty".to_string()); + } + let vm = eel::Vm::new(); + let program = vm.compile(eel_script)?; + let y = vm.register_variable("y"); + let params = { + let mut array = [None; COMPARTMENT_PARAMETER_COUNT as usize]; + for i in extract_used_param_indexes(eel_script).into_iter() { + let variable_name = format!("p{}", i + 1); + let variable = vm.register_variable(&variable_name); + // Set initial value so we can calculate the initial activation result after + // compilation. All subsequent parameter value changes are done incrementally via + // single parameter updates (which is more efficient). + unsafe { + // We initialize this to zero. It will be constantly updated to current values + // in main processor. + variable.set(0.0); + } + array[i as usize] = Some(variable); + } + array + }; + Ok(EelCondition { + program, + _vm: vm, + params, + y, + }) + } + + pub fn notify_params_changed(&self, params: &CompartmentParams) { + for (i, p) in self.params.iter().enumerate() { + let i = CompartmentParamIndex::try_from(i as u32).unwrap(); + if let Some(v) = p { + unsafe { + v.set(params.at(i).effective_value().into()); + } + } + } + } + + /// Returns true if activation might have changed. + pub fn notify_param_changed(&self, param_index: CompartmentParamIndex, value: f64) -> bool { + if let Some(v) = &self.params[param_index.get() as usize] { + unsafe { + v.set(value); + } + true + } else { + false + } + } + + pub fn is_fulfilled(&self) -> bool { + let result = unsafe { + self.program.execute(); + self.y.get() + }; + result > 0.0 + } +} + +fn extract_used_param_indexes(eel_script: &str) -> NonCryptoHashSet { + let param_regex = regex!(r"\bp([0-9]+)\b"); + param_regex + .captures_iter(eel_script) + .flat_map(|m| m[1].parse()) + .filter(|i| *i >= 1 && *i <= COMPARTMENT_PARAMETER_COUNT) + .map(|i: u32| i - 1) + .collect() +} diff --git a/plugin-reaper-realearn/main/src/domain/control_event.rs b/plugin-reaper-realearn/main/src/domain/control_event.rs new file mode 100644 index 0000000..c628540 --- /dev/null +++ b/plugin-reaper-realearn/main/src/domain/control_event.rs @@ -0,0 +1,60 @@ +use crate::domain::GLOBAL_AUDIO_STATE; +use helgoboss_learn::AbstractTimestamp; +use reaper_common_types::{DurationInSeconds, Hz}; +use std::fmt::{Display, Formatter}; +use std::ops::Sub; +use std::time::Duration; + +pub type ControlEvent